fix(schedule): reconcile absolute-time stack layer
This commit is contained in:
@@ -810,7 +810,9 @@ describe('createFixtureApi', () => {
|
||||
] as const)('rejects invalid fixture %s input %j', async (field, value) => {
|
||||
const api = createFixtureApi({ empty: true })
|
||||
if (field === 'timeZone') {
|
||||
const created = await api.sessions.create(req({ timeZone: value }))
|
||||
const invalidRequest = req({})
|
||||
Object.assign(invalidRequest.payload, { timeZone: value })
|
||||
const created = await api.sessions.create(invalidRequest)
|
||||
expect(created.result).toMatchObject({
|
||||
ok: false,
|
||||
error: { code: 'invalid-time-zone', details: { field, value: value ?? null } },
|
||||
@@ -819,12 +821,13 @@ describe('createFixtureApi', () => {
|
||||
}
|
||||
const created = await api.sessions.create(req({ timeZone: 'UTC' }))
|
||||
if (!created.result.ok) throw new Error('fixture create failed')
|
||||
const prompted = await api.sessions.prompt(req({
|
||||
const invalidRequest = req({
|
||||
sessionId: created.result.value.sessionId,
|
||||
mode: 'queue',
|
||||
content: [{ type: 'text', text: 'rejected' }],
|
||||
clientTimeZone: value,
|
||||
}))
|
||||
mode: 'queue' as const,
|
||||
content: [{ type: 'text' as const, text: 'rejected' }],
|
||||
})
|
||||
Object.assign(invalidRequest.payload, { clientTimeZone: value })
|
||||
const prompted = await api.sessions.prompt(invalidRequest)
|
||||
expect(prompted.result).toMatchObject({
|
||||
ok: false,
|
||||
error: { code: 'invalid-time-zone', details: { field, value: value ?? null } },
|
||||
|
||||
@@ -237,9 +237,19 @@ describe('durable step context', () => {
|
||||
const event = session.events.at(-1)
|
||||
expect(event?.type).toBe('user/message')
|
||||
if (event?.type !== 'user/message') throw new Error('missing time context')
|
||||
const text = event.data.content.find(block => block.type === 'text')?.text
|
||||
if (text === undefined) throw new Error('missing time-context text')
|
||||
// The reading is a `snapshot`-form context: one named contribution whose
|
||||
// text is exactly what the model read, so a consumer attributes it without
|
||||
// re-splitting prose.
|
||||
expect(event.data.source).toEqual({
|
||||
kind: 'plugin',
|
||||
plugin: 'time-context',
|
||||
form: 'snapshot',
|
||||
sections: [{
|
||||
name: 'time-context',
|
||||
text,
|
||||
}],
|
||||
})
|
||||
expect(event.surfaceOp).toBe('append')
|
||||
})
|
||||
@@ -550,15 +560,15 @@ describe('real agent-loop request history', () => {
|
||||
it('does not revive an empty continuation after a completed step', async () => {
|
||||
const adapter = new ScriptedAdapter([textResponse('done')])
|
||||
const ctx = await loopHarness(adapter)
|
||||
ctx.on('agent/turn-stopping', (subject) => {
|
||||
ctx.on('agent/turn-stopping', ({ agent: subject }) => {
|
||||
subject.inject(createUserMessage({
|
||||
content: [{ type: 'text', text: 'pending context' }],
|
||||
source: { kind: 'plugin', plugin: 'test' },
|
||||
}))
|
||||
})
|
||||
ctx.on('agent/pre-step', async (_agent, _messages, context, next) => {
|
||||
ctx.on('agent/pre-step', async ({ step }, next) => {
|
||||
const decision = await next()
|
||||
return context.step === 1 || decision.kind === 'reject'
|
||||
return step === 1 || decision.kind === 'reject'
|
||||
? decision
|
||||
: { kind: 'enter', messages: [] }
|
||||
})
|
||||
|
||||
@@ -79,11 +79,11 @@ export interface CreateAgentOptions {
|
||||
/** The live agent/session identity. */
|
||||
readonly sessionId: SessionId
|
||||
/**
|
||||
* Session creation metadata: validated absolute `cwd`, `parentSession`
|
||||
* fork lineage, the `seedLength` seed boundary, the coarse `origin`
|
||||
* classification, and the `delegationDepth` recursion budget. Mirrors the
|
||||
* `cwd`/`parentSession`/`seedLength`/`origin`/`delegationDepth` fields of
|
||||
* {@link CreateSessionOptions.meta} in dsh-session (the internal-only
|
||||
* Session creation metadata: validated absolute `cwd`, caller-validated
|
||||
* `timeZone`, `parentSession` fork lineage, the `seedLength` seed boundary,
|
||||
* the coarse `origin` classification, and the `delegationDepth` recursion
|
||||
* budget. Mirrors the `cwd`/`timeZone`/`parentSession`/`seedLength`/`origin`/
|
||||
* `delegationDepth` fields of {@link CreateSessionOptions.meta} in dsh-session (the internal-only
|
||||
* `createdAt`, used when reconstructing a persisted session, is deliberately
|
||||
* excluded — a factory caller never sets it). This is durable session data,
|
||||
* so the session boundary validates and snapshots it before asynchronous
|
||||
@@ -91,6 +91,7 @@ export interface CreateAgentOptions {
|
||||
*/
|
||||
readonly meta?: {
|
||||
readonly cwd?: string
|
||||
readonly timeZone?: string
|
||||
readonly parentSession?: SessionId
|
||||
readonly seedLength?: number
|
||||
readonly origin?: 'subagent'
|
||||
|
||||
@@ -527,7 +527,7 @@ describe('cold Session zone identity', () => {
|
||||
locate: () => undefined,
|
||||
} as never)
|
||||
const resume = vi.spyOn(ctx.agents, 'resume')
|
||||
const api = createApiProxy(ctx, { provider: 'p', model: 'm', cwd: '/tmp', workspaceRoot: '/tmp' })
|
||||
const api = createApiProxy(ctx, { defaultTarget: () => ({ provider: 'p', model: 'm' }), cwd: '/tmp', workspaceRoot: '/tmp' })
|
||||
|
||||
const response = await api.sessions.create(request({
|
||||
sessionId,
|
||||
|
||||
@@ -315,6 +315,7 @@ describe('Web session model selection', () => {
|
||||
// callable, so the refusal has to live here.
|
||||
const refused = await api.sessions.prompt(request({
|
||||
sessionId, mode: 'queue' as const, content: [{ type: 'text' as const, text: 'hi' }],
|
||||
clientTimeZone: 'UTC',
|
||||
}))
|
||||
expect(refused.result).toMatchObject({
|
||||
ok: false,
|
||||
|
||||
@@ -441,7 +441,9 @@ describe('session creation and Workspace membership', () => {
|
||||
['Not/A_Real_Zone', 'Not/A_Real_Zone'],
|
||||
] as const)('rejects invalid Session zone input %j before Agent creation', async (timeZone, value) => {
|
||||
const { api, ctx } = await harness()
|
||||
const response = await api.sessions.create(request({ timeZone }))
|
||||
const invalidRequest = request({})
|
||||
Object.assign(invalidRequest.payload, { timeZone })
|
||||
const response = await api.sessions.create(invalidRequest)
|
||||
|
||||
expect(response.result).toMatchObject({
|
||||
ok: false,
|
||||
@@ -493,12 +495,13 @@ describe('session creation and Workspace membership', () => {
|
||||
if (agent === undefined) throw new Error('created Agent missing')
|
||||
const followup = vi.spyOn(agent, 'followup')
|
||||
|
||||
const response = await api.sessions.prompt(request({
|
||||
const invalidRequest = request({
|
||||
sessionId,
|
||||
mode: 'queue',
|
||||
content: [{ type: 'text', text: 'rejected' }],
|
||||
clientTimeZone,
|
||||
}))
|
||||
mode: 'queue' as const,
|
||||
content: [{ type: 'text' as const, text: 'rejected' }],
|
||||
})
|
||||
Object.assign(invalidRequest.payload, { clientTimeZone })
|
||||
const response = await api.sessions.prompt(invalidRequest)
|
||||
|
||||
expect(response.result).toMatchObject({
|
||||
ok: false,
|
||||
|
||||
@@ -59,7 +59,7 @@ describe('rpcErrorSchema', () => {
|
||||
expect(rpcErrorSchema.parse({ code: 'bad-request', message: 'm', details: { issues: [] } }).code).toBe('bad-request')
|
||||
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: 'session-conflict', message: 'm', details: { sessionId: 's', requestedCwd: '/a', existingCwd: '/b', requestedTimeZone: 'UTC' } }).code).toBe('session-conflict')
|
||||
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')
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write packages/schedule/tool-schedule/README.md
|
||||
README.md: a0a51a94ff9b529a8c8f73e87d8ba75af50ffbc9
|
||||
README.zh.md: 0cbbe4e290c6877cd7af3e73c9a595bb992ca637
|
||||
README.md: 3e0a0cea98dbe593974c5604736d155508f28044
|
||||
README.zh.md: b08bad14d50b07af2c36796335452b835fb9680a
|
||||
|
||||
@@ -1909,7 +1909,7 @@ export const TYPE_API: readonly TypeApiEntry[] = [
|
||||
},
|
||||
{
|
||||
name: 'CreateAgentOptions',
|
||||
declaration: 'export interface CreateAgentOptions {\n readonly sessionId: SessionId;\n readonly meta?: {\n readonly cwd?: string;\n readonly parentSession?: SessionId;\n readonly seedLength?: number;\n readonly origin?: \'subagent\';\n readonly delegationDepth?: number;\n };\n readonly seed?: readonly SessionEvent[];\n readonly agentOptions?: AgentOptions;\n readonly signal?: AbortSignal;\n readonly setup?: AgentSetup;\n}',
|
||||
declaration: 'export interface CreateAgentOptions {\n readonly sessionId: SessionId;\n readonly meta?: {\n readonly cwd?: string;\n readonly timeZone?: string;\n readonly parentSession?: SessionId;\n readonly seedLength?: number;\n readonly origin?: \'subagent\';\n readonly delegationDepth?: number;\n };\n readonly seed?: readonly SessionEvent[];\n readonly agentOptions?: AgentOptions;\n readonly signal?: AbortSignal;\n readonly setup?: AgentSetup;\n}',
|
||||
},
|
||||
{
|
||||
name: 'CreateGoalRequest',
|
||||
|
||||
Reference in New Issue
Block a user