Merge remote-tracking branch 'origin/master' into feature/shared-cli-config-foundation

# Conflicts:
#	packages/host/apiproxy/README.i18n.yaml
This commit is contained in:
Turtle
2026-07-30 00:01:13 +08:00
61 changed files with 1305 additions and 208 deletions

View File

@@ -225,11 +225,12 @@ describe('dsh TUI keyless smoke (real Loader tree in a PTY)', () => {
}, LOADER_SMOKE_TEST_TIMEOUT_MS)
it('loads a local skill via /skill: and delivers its body to the model as a user turn', async () => {
// The whole manual-invocation path in one keyless boot: `ctx.get('skills')`
// The whole user-only invocation path in one keyless boot: `ctx.get('skills')`
// resolves in the shipped tree, the client-side `/skill:` command parses,
// the local provider loads `scripted-skill` from the agents home, and the
// rendered `<skill name="…">` block reaches the model — proven by the
// scripted adapter echoing the fixture's body marker only when it arrives.
// and the local provider admits a model-disabled skill by the omitted
// `user-invocable` default. The rendered `<skill name="…">` block reaches
// the model — proven by the scripted adapter echoing the fixture's body
// marker only when it arrives.
const output = await smoke({
label: 'dsh skill',
tempDirPrefix: 'dsh-tui-skill-',
@@ -240,6 +241,7 @@ describe('dsh TUI keyless smoke (real Loader tree in a PTY)', () => {
'---',
'name: scripted-skill',
'description: Keyless PTY proof that the skill command loads a local skill into the conversation.',
'disable-model-invocation: true',
'---',
'',
'SCRIPTED SKILL BODY MARKER',

View File

@@ -41,6 +41,7 @@ const UUID_RE = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi
type SnapshotMode = 'replay' | 'record' | 'refresh'
type Composition = 'native' | 'code' | 'advanced'
type ScenarioInteraction = 'skill-invocation-policy'
interface Scenario {
name: string
@@ -65,6 +66,8 @@ interface Scenario {
* preview + locator while the program value stays whole.
*/
spillMaxInlineBytes?: number
/** Run scenario-specific terminal input instead of replaying recorded user prompts. */
interaction?: ScenarioInteraction
}
const SCENARIOS: Scenario[] = [
@@ -98,6 +101,14 @@ const SCENARIOS: Scenario[] = [
recorded: true,
seedWorkspace: true,
},
{
name: 'skill-invocation-policy',
composition: 'native',
expectedTools: [],
recorded: false,
seedWorkspace: true,
interaction: 'skill-invocation-policy',
},
{
name: 'code-mode',
composition: 'code',
@@ -269,9 +280,10 @@ async function runScenario(scenario: Scenario): Promise<ScenarioResult> {
const dir = scenarioDir(scenario)
const fixtureFile = join(dir, 'session.jsonl')
const childFiles = childFixturePaths(scenario)
const fixture = await readFile(fixtureFile, 'utf8')
const prompts = userPrompts(fixture)
expect(prompts.length, `${scenario.name} must carry at least one recorded user prompt`).toBeGreaterThan(0)
const prompts = userPrompts(await readFile(fixtureFile, 'utf8'))
if (scenario.interaction === undefined) {
expect(prompts.length, `${scenario.name} must carry at least one recorded user prompt`).toBeGreaterThan(0)
}
const cwd = await mkdtemp(join(SNAPSHOT_TMP_ROOT, `dsh-tui-snapshot-${scenario.name}-`))
const displayCwd = `/tmp/${basename(cwd)}`
@@ -310,6 +322,63 @@ async function runScenario(scenario: Scenario): Promise<ScenarioResult> {
})
await settleTerminal(terminal)
let interactionSnapshot: string | undefined
if (scenario.interaction === 'skill-invocation-policy') {
terminal.send('/skill')
await settleTerminal(terminal)
const discovery = normalizeTerminalSnapshot(
await terminal.snapshot({ includeScrollback: true }),
cwd,
displayCwd,
)
expect(discovery).toContain('user-only-skill')
expect(discovery).not.toContain('model-only-skill')
terminal.send('\x03')
await settleTerminal(terminal)
const skillContext = ctx
const skillTurnEnded = new Promise<void>((resolve) => {
const detach = skillContext.on('session/event', (session, event) => {
if (session !== agent.session || event.type !== 'turn/end') return
detach()
resolve()
})
})
terminal.send('/skill:user-only-skill')
terminal.send('\r')
await skillTurnEnded
await agent.whenIdle()
await settleTerminal(terminal)
const loaded = normalizeTerminalSnapshot(
await terminal.snapshot({ includeScrollback: true }),
cwd,
displayCwd,
)
expect(loaded).toContain('USER-ONLY SKILL LOADED')
terminal.send('/skill:model-only-skill')
terminal.send('\r')
await settleTerminal(terminal)
const denied = normalizeTerminalSnapshot(
await terminal.snapshot({ includeScrollback: true }),
cwd,
displayCwd,
)
expect(denied).toContain('model-only-skill')
expect(denied).toContain('not available for user invocation.')
expect(denied).not.toContain('MODEL-ONLY BODY MUST NOT LOAD')
interactionSnapshot = [
'=== skill autocomplete ===',
discovery,
'',
'=== loaded exact invocation ===',
loaded,
'',
'=== denied exact invocation ===',
denied,
].join('\n')
}
let remainingPrompts = prompts
if (scenario.enterPlanMode === true) {
const firstPrompt = prompts[0]!
@@ -392,7 +461,7 @@ async function runScenario(scenario: Scenario): Promise<ScenarioResult> {
}
expect(terminal.themeViolations(), `${scenario.name} must remain theme-agnostic`).toEqual([])
const snapshot = normalizeTerminalSnapshot(
const snapshot = interactionSnapshot ?? normalizeTerminalSnapshot(
await terminal.snapshot({ includeScrollback: true }),
cwd,
displayCwd,