Default shipped UI sessions to workspace-write

This commit is contained in:
Tianyi Cui
2026-07-31 20:39:53 +08:00
parent e1eb581f2b
commit b694c33d18
48 changed files with 227 additions and 143 deletions

View File

@@ -11,6 +11,7 @@ import { acknowledgeTuiFirstRunWelcome } from '../src/tui-onboarding/tui-first-r
const dshBinScript = fileURLToPath(new URL('../src/bin.ts', import.meta.url))
const tsconfigPath = fileURLToPath(new URL('../../../tsconfig.json', import.meta.url))
const PERMISSION_SUMMARY = 'current preset workspace-write (available: read-only, workspace-write, danger-full-access)'
// An overlay over the shipped tree, so the catalog under test is the one
// `base.cordis.yml` + `tui.cordis.yml` assemble; the tail only swaps the model
// and redirects session artifacts.
@@ -67,6 +68,8 @@ interface LoggedHeader {
names: string[]
/** `bash`'s assembled parameter properties; the escalation pair is present only under a confining executor. */
bashArguments: Record<string, unknown>
/** Initial permission facts pinned by the shipped composition. */
permissionEvents: Array<[string, unknown]>
}
/**
@@ -82,18 +85,22 @@ async function loggedHeader(cwd: string): Promise<LoggedHeader> {
// A single keyless run writes one session log.
const logRelPath = entries.find(name => name.endsWith('.jsonl'))
if (logRelPath === undefined) throw new Error(`no session log written under ${sessionsDir}`)
const lines = (await readFile(join(sessionsDir, logRelPath), 'utf8')).split('\n').filter(Boolean)
for (const line of lines) {
const event = JSON.parse(line) as SessionEvent
if (event.type !== 'request/header') continue
const tools = event.data.header.tools ?? []
const bash = tools.find(schema => schema.name === 'bash')
return {
names: tools.map(schema => schema.name).sort(),
bashArguments: (bash?.parameters as { properties?: Record<string, unknown> } | undefined)?.properties ?? {},
}
const events = (await readFile(join(sessionsDir, logRelPath), 'utf8')).split('\n').filter(Boolean)
.map(line => JSON.parse(line) as SessionEvent)
const header = events.find(event => event.type === 'request/header')
if (header === undefined || header.type !== 'request/header') {
throw new Error(`session log ${logRelPath} has no request/header event`)
}
const tools = header.data.header.tools ?? []
const bash = tools.find(schema => schema.name === 'bash')
return {
names: tools.map(schema => schema.name).sort(),
bashArguments: (bash?.parameters as { properties?: Record<string, unknown> } | undefined)?.properties ?? {},
permissionEvents: events.flatMap(event =>
event.type === 'permission/preset' || event.type === 'sandbox/mode' || event.type === 'approval/policy'
? [[event.type, event.data] as [string, unknown]]
: []),
}
throw new Error(`session log ${logRelPath} has no request/header event`)
}
describe('shipped dsh composition (real Loader tree in a PTY)', () => {
@@ -110,17 +117,22 @@ describe('shipped dsh composition (real Loader tree in a PTY)', () => {
// Artifact CI builds and smokes concurrently on a contended runner.
...(process.env.DSH_EXAMPLE_MODE === 'lib' ? { timeoutMs: 60_000 } : {}),
actions: [
{ waitFor: COMPOSITION_SETTLED_MARKER, send: 'Describe the shipped composition.\r' },
{ waitFor: COMPOSITION_SETTLED_MARKER, send: '/permission\r' },
{ waitFor: PERMISSION_SUMMARY, send: 'Describe the shipped composition.\r' },
{ waitFor: COMPOSITION_REPLY_TEXT, send: '/exit\r' },
],
inspect: async (cwd) => { observed = await loggedHeader(cwd) },
})
expect(output).toContain(COMPOSITION_REPLY_TEXT)
expect(output).toContain(PERMISSION_SUMMARY)
expect(observed?.names.filter(name => !RIPGREP_TOOLS.includes(name))).toEqual(EXPECTED_TUI_TOOLS)
expect([[], RIPGREP_TOOLS]).toContainEqual(observed?.names.filter(name => RIPGREP_TOOLS.includes(name)))
// The TUI mounts the unrestricted local executors, so `tool-bash` emits no
// escalation pair. Pinning its absence keeps a later sandbox change from
// arriving here unannounced.
expect(Object.keys(observed?.bashArguments ?? {})).not.toContain('sandbox_permissions')
expect(observed?.bashArguments).toHaveProperty('sandbox_permissions')
expect(observed?.bashArguments).toHaveProperty('justification')
expect(observed?.permissionEvents).toEqual([
['permission/preset', { preset: 'workspace-write' }],
['sandbox/mode', { mode: 'workspace-write' }],
['approval/policy', { policy: 'ask' }],
])
}, LOADER_SMOKE_TEST_TIMEOUT_MS)
})