Merge remote-tracking branch 'origin/master' into dshw/pr-2250

This commit is contained in:
_Kerman
2026-08-11 22:33:12 +08:00
422 changed files with 15607 additions and 1120 deletions

View File

@@ -468,6 +468,80 @@ describe('subagent ownership fence', () => {
expect(response.result.ok).toBe(true)
expect(followup).toHaveBeenCalledOnce()
})
it('canonicalizes a supplied browser zone on the exact prompt and rejects invalid names', async () => {
const ctx = new Context()
await ctx.plugin(SessionStore)
await ctx.plugin(AgentRegistry)
await ctx.plugin(UserInteractionService)
const session = ctx.sessions.create(sid('session-browser-zone'), { meta: { cwd: '/proj' } })
const followup = vi.fn()
const agent = { id: session.id, session, status: 'idle', ctx, followup } as unknown as Agent
ctx.agents.register(agent)
const api = createApiProxy(ctx, {
defaultModelSelection: () => ({ provider: 'p', model: 'm' }),
cwd: '/tmp',
})
const alias = 'US/Pacific'
const canonical = new Intl.DateTimeFormat('en-US', { timeZone: alias })
.resolvedOptions().timeZone
const zonedRequest = request({
sessionId: agent.id,
mode: 'queue' as const,
content: [{ type: 'text' as const, text: 'zoned work' }],
clientTimeZone: alias,
})
await expect(api.sessions.prompt(zonedRequest)).resolves.toMatchObject({
result: { ok: true },
})
expect(followup).toHaveBeenNthCalledWith(1, expect.objectContaining({
source: { kind: 'user', rpcId: zonedRequest.rpcId, clientTimeZone: canonical },
}))
const utcRequest = request({
sessionId: agent.id,
mode: 'queue' as const,
content: [{ type: 'text' as const, text: 'UTC work' }],
clientTimeZone: 'UTC',
})
await expect(api.sessions.prompt(utcRequest)).resolves.toMatchObject({
result: { ok: true },
})
expect(followup).toHaveBeenNthCalledWith(2, expect.objectContaining({
source: { kind: 'user', rpcId: utcRequest.rpcId, clientTimeZone: 'UTC' },
}))
const unzonedRequest = request({
sessionId: agent.id,
mode: 'queue' as const,
content: [{ type: 'text' as const, text: 'headless work' }],
})
await expect(api.sessions.prompt(unzonedRequest)).resolves.toMatchObject({
result: { ok: true },
})
expect(followup).toHaveBeenNthCalledWith(3, expect.objectContaining({
source: { kind: 'user', rpcId: unzonedRequest.rpcId },
}))
for (const clientTimeZone of ['', ' UTC', 'CST', 'Not/A_Real_Zone']) {
const invalid = await api.sessions.prompt(request({
sessionId: agent.id,
mode: 'queue' as const,
content: [{ type: 'text' as const, text: 'invalid zone' }],
clientTimeZone,
}))
expect(invalid.result).toEqual({
ok: false,
error: {
code: 'invalid-time-zone',
message: 'clientTimeZone must be UTC or a valid IANA Area/Location name',
details: { value: clientTimeZone },
},
})
}
expect(followup).toHaveBeenCalledTimes(3)
})
})
describe('degenerate composition (no persistence, no factory)', () => {

View File

@@ -344,11 +344,21 @@ describe('settings domain', () => {
ctx.settings.register(settingsNamespace('ui-conversation'), z.object({
busyEnter: z.union(['queue', 'steer']).default('queue'),
}))
ctx.settings.register(settingsNamespace('bash'), z.object({
timeoutMs: z.number().default(120_000),
}))
ctx.settings.register(settingsNamespace('agent-loop'), z.object({
maxParallelToolCalls: z.number().default(10),
}))
ctx.settings.register(settingsNamespace('web-search-deepseek'), z.object({
baseURL: z.string(),
}))
const api = createApiProxy(ctx, DEFAULTS)
const value = expectOk(await api.settings.describe(request({})))
expect(value.namespaces.map(view => view.ns)).toEqual([
'llm-deepseek', 'permission', 'ui-theme', 'locale', 'ui-conversation',
'bash', 'agent-loop', 'web-search-deepseek',
])
const permission = expectOk(await api.settings.mutate(request({
ns: 'permission',
@@ -370,6 +380,21 @@ describe('settings domain', () => {
ops: [{ op: 'set', path: ['busyEnter'], value: 'steer' }],
})))
expect(conversation.value).toEqual({ busyEnter: 'steer' })
const bash = expectOk(await api.settings.mutate(request({
ns: 'bash',
ops: [{ op: 'set', path: ['timeoutMs'], value: 5_000 }],
})))
expect(bash.value).toEqual({ timeoutMs: 5_000 })
const agentLoop = expectOk(await api.settings.mutate(request({
ns: 'agent-loop',
ops: [{ op: 'set', path: ['maxParallelToolCalls'], value: 2 }],
})))
expect(agentLoop.value).toEqual({ maxParallelToolCalls: 2 })
const webSearch = expectOk(await api.settings.mutate(request({
ns: 'web-search-deepseek',
ops: [{ op: 'set', path: ['baseURL'], value: 'https://search.test/v1' }],
})))
expect(webSearch.value).toEqual({ baseURL: 'https://search.test/v1' })
for (const response of [
await api.settings.update(request({ ns: 'some-other-plugin', patch: { secretPath: '/etc/shadow' } })),

View File

@@ -50,7 +50,10 @@ function bench(options: {
_parent: unknown,
_childId: SessionId,
_content: unknown,
_delivery: { source: { kind: string; rpcId: RpcId }; signal: AbortSignal },
_delivery: {
source: { kind: string; rpcId: RpcId; clientTimeZone?: string }
signal: AbortSignal
},
) => options.followupError === undefined
? Promise.resolve('message-1')
: Promise.reject(options.followupError))
@@ -270,6 +273,43 @@ describe('subagent gateway', () => {
)
})
it('canonicalizes browser-zone provenance before delivering a child prompt', async () => {
const { api, parent, followup } = bench()
const alias = 'US/Pacific'
const canonical = new Intl.DateTimeFormat('en-US', { timeZone: alias })
.resolvedOptions().timeZone
const content = [{ type: 'text' as const, text: 'continue locally' }]
const signal = new AbortController().signal
await expect(api.subagents.prompt(request({
parentSessionId: PARENT,
childSessionId: CHILD,
mode: 'continuable',
content,
clientTimeZone: alias,
}), signal)).resolves.toMatchObject({ result: { ok: true } })
expect(followup).toHaveBeenCalledWith(parent, CHILD, content, {
source: { kind: 'user', rpcId: RpcId('subagent-rpc'), clientTimeZone: canonical },
signal,
})
const invalid = await api.subagents.prompt(request({
parentSessionId: PARENT,
childSessionId: CHILD,
mode: 'continuable',
content,
clientTimeZone: 'Not/A_Real_Zone',
}), signal)
expect(invalid.result).toEqual({
ok: false,
error: {
code: 'invalid-time-zone',
message: 'clientTimeZone must be UTC or a valid IANA Area/Location name',
details: { value: 'Not/A_Real_Zone' },
},
})
expect(followup).toHaveBeenCalledOnce()
})
it('fails before delivery when the parent is absent and maps continuation failures', async () => {
const absent = bench({ parentLive: false })
expect((await absent.api.subagents.prompt(request({

View File

@@ -61,7 +61,10 @@ function stubAgent(session: Session): Agent {
async function harness(
root = realpathSync.native(mkdtempSync(join(tmpdir(), 'dsh-apiproxy-workspace-'))),
picker: DirectoryPickerCapability = { kind: 'native', pick: async () => null },
extras: { openPath?: (path: string, signal: AbortSignal) => Promise<void> } = {},
extras: {
openPath?: (path: string, signal: AbortSignal) => Promise<void>
canOpenPath?: () => boolean
} = {},
) {
const ctx = new Context()
await ctx.plugin(SessionStore)
@@ -103,6 +106,7 @@ async function harness(
defaultModelSelection: () => ({ provider: 'test', model: 'test-model' }),
cwd: root,
...extras.openPath === undefined ? {} : { openPath: extras.openPath },
...extras.canOpenPath === undefined ? {} : { canOpenPath: extras.canOpenPath },
})
return { api, ctx, storageDomain, root }
}
@@ -225,6 +229,13 @@ describe('host.listDirectory / host.createDirectory', () => {
})
describe('host.openPath', () => {
it('describes whether this deployment can reach a user-visible native desktop', async () => {
const visible = await harness(undefined, undefined, { canOpenPath: () => true })
const headless = await harness(undefined, undefined, { canOpenPath: () => false })
expect(expectOk(await visible.api.host.describe(request({}))).canOpenPath).toBe(true)
expect(expectOk(await headless.api.host.describe(request({}))).canOpenPath).toBe(false)
})
it('opens through the injected native boundary', async () => {
const opened: string[] = []
const { api } = await harness(undefined, undefined, {

View File

@@ -72,7 +72,9 @@ function scriptedApi(overrides: {
...overrides.subagents,
},
host: {
describe: r => ok(r, { version: '0-test', cwd: '/t', attachedSessions: 0 }),
describe: r => ok(r, {
version: '0-test', cwd: '/t', attachedSessions: 0, canOpenPath: true,
}),
pickDirectory: r => ok(r, { path: null }),
listDirectory: r => ok(r, { path: '/t', home: '/t', crumbs: [], entries: [], truncated: false }),
createDirectory: r => ok(r, { path: '/t/new' }),

View File

@@ -140,7 +140,13 @@ function fakeApi(overrides: Partial<{ muxFrames: MuxFrame[]; hostFrames: HostFra
},
host: {
async describe(request) {
return { rpcId: request.rpcId, result: { ok: true, value: { version: 'v', cwd: '/w', attachedSessions: 0 } } }
return {
rpcId: request.rpcId,
result: {
ok: true,
value: { version: 'v', cwd: '/w', attachedSessions: 0, canOpenPath: true },
},
}
},
async pickDirectory(request) {
return { rpcId: request.rpcId, result: { ok: true, value: { path: null } } }

View File

@@ -40,6 +40,7 @@ import { hostFrameSchema, muxFrameSchema, askUserQuestionItemSchema } from '../s
import { approvalRequestIdSchema, approvalResponsePayloadSchema } from '../src/api/approvals.schema.ts'
import { askUserQuestionAnswerSchema, questionResponsePayloadSchema } from '../src/api/questions.schema.ts'
import { goalEditRequestSchema } from '../src/api/goals.schema.ts'
import { subagentPromptRequestSchema } from '../src/api/subagents.schema.ts'
describe('RpcId', () => {
it('brands a raw string at zero runtime cost', () => {
@@ -64,6 +65,7 @@ describe('rpcErrorSchema', () => {
expect(rpcErrorSchema.parse({ code: 'cancelled', message: 'm', details: {} }).code).toBe('cancelled')
expect(rpcErrorSchema.parse({ code: 'session-not-found', message: 'm', details: { sessionId: 's' } }).code).toBe('session-not-found')
expect(rpcErrorSchema.parse({ code: 'session-conflict', message: 'm', details: { sessionId: 's', requestedCwd: '/a', existingCwd: '/b' } }).code).toBe('session-conflict')
expect(rpcErrorSchema.parse({ code: 'invalid-time-zone', message: 'm', details: { value: 'CST' } }).code).toBe('invalid-time-zone')
expect(rpcErrorSchema.parse({ code: 'workspace-attach-failed', message: 'm', details: { sessionId: 's', workspaceId: 'w' } }).code).toBe('workspace-attach-failed')
expect(rpcErrorSchema.parse({ code: 'workspace-not-found', message: 'm', details: { workspaceId: 'w' } }).code).toBe('workspace-not-found')
expect(rpcErrorSchema.parse({ code: 'workspace-invalid-path', message: 'm', details: { path: '/x' } }).code).toBe('workspace-invalid-path')
@@ -249,8 +251,17 @@ describe('sessions domain schemas', () => {
}],
failures: [],
})).toThrow()
const prompt = sessionPromptRequestSchema.parse({ sessionId: 's1', mode: 'queue', content: [{ type: 'text', text: 'hi' }] })
const prompt = sessionPromptRequestSchema.parse({
sessionId: 's1',
mode: 'queue',
content: [{ type: 'text', text: 'hi' }],
clientTimeZone: 'Asia/Shanghai',
})
expect(prompt.mode).toBe('queue')
expect(prompt.clientTimeZone).toBe('Asia/Shanghai')
expect(sessionPromptRequestSchema.parse({
sessionId: 's1', mode: 'queue', content: [],
}).clientTimeZone).toBeUndefined()
expect(() => sessionPromptRequestSchema.parse({ sessionId: 's1', mode: 'inject', content: [] })).toThrow()
expect(sessionPromptValueSchema.parse({ accepted: true }).accepted).toBe(true)
// The command slot appears only when the prompt dispatched a slash command.
@@ -276,14 +287,37 @@ describe('sessions domain schemas', () => {
})
})
describe('subagent domain schemas', () => {
it('carries optional request-local browser-zone provenance on prompts', () => {
expect(subagentPromptRequestSchema.parse({
parentSessionId: 'parent',
childSessionId: 'child',
mode: 'continuable',
content: [{ type: 'text', text: 'continue' }],
clientTimeZone: 'Asia/Shanghai',
}).clientTimeZone).toBe('Asia/Shanghai')
expect(subagentPromptRequestSchema.parse({
parentSessionId: 'parent',
childSessionId: 'child',
mode: 'continuable',
content: [],
}).clientTimeZone).toBeUndefined()
})
})
describe('host domain schemas', () => {
it('validates describe request/value', () => {
expect(hostDescribeRequestSchema.parse({})).toEqual({})
const value = hostDescribeValueSchema.parse({
version: '1', cwd: '/x', provider: 'p', model: 'm', attachedSessions: 2,
version: '1', cwd: '/x', provider: 'p', model: 'm', attachedSessions: 2, canOpenPath: true,
})
expect(value).toMatchObject({ provider: 'p', model: 'm', attachedSessions: 2 })
expect(hostDescribeValueSchema.parse({ version: '1', cwd: '/x', attachedSessions: 0 }).provider).toBeUndefined()
expect(value).toMatchObject({ provider: 'p', model: 'm', attachedSessions: 2, canOpenPath: true })
expect(hostDescribeValueSchema.parse({
version: '1', cwd: '/x', attachedSessions: 0, canOpenPath: false,
}).provider).toBeUndefined()
expect(() => hostDescribeValueSchema.parse({
version: '1', cwd: '/x', attachedSessions: 0,
})).toThrow()
})
it('validates the browse listing/creation payloads', () => {