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

@@ -31,6 +31,10 @@ async function mount(): Promise<Bench> {
const handle: ConnectionHandle = {
api,
isLoopback: true,
hostDescription: {
getSnapshot: () => undefined,
subscribe: () => () => {},
},
rpc: {
call: () => Promise.reject(new Error('unexpected generic RPC call')),
},
@@ -86,7 +90,7 @@ describe('runtime client apply', () => {
expect(workspaces.list.getSnapshot().items[0]?.workspaceId).toBe('w-new')
// Mux sink and onConnected route without throwing (manager semantics own the behavior).
bench.sinks?.onMuxEnvelope?.({ rpcId: 'r2' as never, payload: { type: 'stream/error', message: 'x' } as never })
bench.sinks?.onConnected?.()
bench.sinks?.onConnected?.({ version: '0', cwd: '/f', attachedSessions: 0, canOpenPath: true })
})
it('selects the recent Workspace once when the first baselines have no current session', async () => {
@@ -99,7 +103,7 @@ describe('runtime client apply', () => {
}))
bench.api.onList = () => Promise.resolve(ok({ items: [] }))
bench.sinks?.onConnected?.()
bench.sinks?.onConnected?.({ version: '0', cwd: '/f', attachedSessions: 0, canOpenPath: true })
await flushMicrotasks()
const sessions = bench.ctx.get('sessions') as SessionsService

View File

@@ -90,8 +90,15 @@ export class FakeApiClient implements IApiClient {
onUpdateQueue: (payload: unknown) => Promise<RpcResponse<{ accepted: true }>> = () => Promise.resolve(ok({ accepted: true as const }))
onCancel: (payload: unknown) => Promise<RpcResponse<{ accepted: true }>> = () => Promise.resolve(ok({ accepted: true as const }))
onDescribe: (payload: unknown) => Promise<RpcResponse<{ version: string; cwd: string; attachedSessions: number }>> =
() => Promise.resolve(ok({ version: '0-fake', cwd: '/f', attachedSessions: 0 }))
onDescribe: (payload: unknown) => Promise<RpcResponse<{
version: string
cwd: string
attachedSessions: number
canOpenPath: boolean
}>> =
() => Promise.resolve(ok({
version: '0-fake', cwd: '/f', attachedSessions: 0, canOpenPath: true,
}))
onPickDirectory: (payload: unknown) => Promise<RpcResponse<{ path: string | null }>> =
() => Promise.resolve(ok({ path: null }))
onOpenPath: (payload: unknown) => Promise<RpcResponse<{ opened: true }>> =

View File

@@ -374,6 +374,7 @@ describe('subagent catalogs', () => {
{
parentSessionId: S1, childSessionId: S2, mode: 'continuable',
content: [{ type: 'text', text: 'continue' }],
clientTimeZone: new Intl.DateTimeFormat().resolvedOptions().timeZone,
},
])
expect(api.callsOf('session.history')).toEqual([])

View File

@@ -465,6 +465,7 @@ describe('prompt and cancel errors', () => {
{
parentSessionId: PARENT, childSessionId: SID, mode: 'continuable',
content: [{ type: 'text', text: '继续' }],
clientTimeZone: new Intl.DateTimeFormat().resolvedOptions().timeZone,
},
])
expect(api.callsOf('subagent.interrupt')).toEqual([
@@ -530,7 +531,12 @@ describe('prompt and cancel errors', () => {
expect(result.ok).toBe(true)
// Monotone: settlement alone does not step the phase anywhere.
expect(session.getSnapshot().composerPhase).toBe('engaging')
expect(api.callsOf('session.prompt')).toMatchObject([{ sessionId: SID, mode: 'queue', content: [{ type: 'text', text: '要发的' }] }])
expect(api.callsOf('session.prompt')).toMatchObject([{
sessionId: SID,
mode: 'queue',
content: [{ type: 'text', text: '要发的' }],
clientTimeZone: new Intl.DateTimeFormat().resolvedOptions().timeZone,
}])
// First content lands (running turn): engaging → active.
session.handleRunning(true)
expect(session.getSnapshot().composerPhase).toBe('active')

View File

@@ -0,0 +1,24 @@
import { afterEach, describe, expect, it, vi } from 'vitest'
import { resolvedClientTimeZone } from '../src/client/time-zone.ts'
afterEach(() => {
vi.restoreAllMocks()
})
describe('browser time zone', () => {
it('returns the runtime-resolved zone', () => {
expect(resolvedClientTimeZone()).toBe(
new Intl.DateTimeFormat().resolvedOptions().timeZone,
)
})
it.each([undefined, ''])('fails loud when the runtime exposes no zone %#', (timeZone) => {
const options = new Intl.DateTimeFormat().resolvedOptions()
vi.spyOn(Intl.DateTimeFormat.prototype, 'resolvedOptions').mockReturnValue({
...options,
timeZone: timeZone as string,
})
expect(() => resolvedClientTimeZone()).toThrow('browser time zone is unavailable')
})
})

View File

@@ -61,6 +61,10 @@ async function mount(): Promise<Bench> {
const handle: ConnectionHandle = {
api,
isLoopback: true,
hostDescription: {
getSnapshot: () => undefined,
subscribe: () => () => {},
},
rpc: {
call: () => Promise.reject(new Error('unexpected generic RPC call')),
},
@@ -122,8 +126,9 @@ describe('wire event bridge', () => {
const bench = await mount()
let resets = 0
bench.ctx.on('connection/reset', () => { resets++ })
bench.sinks?.onConnected?.()
bench.sinks?.onConnected?.() // second generation after a reconnect
const description = { version: '0', cwd: '/f', attachedSessions: 0, canOpenPath: true }
bench.sinks?.onConnected?.(description)
bench.sinks?.onConnected?.(description) // second generation after a reconnect
expect(resets).toBe(2)
})
})