Merge remote-tracking branch 'origin/master' into feat/telemetry-otel-plugin
Resolutions: keep both sides where master's new storage/workspace group registrations sit adjacent to the telemetry ones (gen-doc-graphs SERVICE_ROLES/GROUP_ORDER, tsconfig.base.json wildcards, packages/README.md rows); regenerate the graph docs; condense four packages/README.md rows back under the 790-word ceiling.
This commit is contained in:
25
examples/headless-agent/tests/fixtures/semantic-checkpoint-agent.ts
vendored
Normal file
25
examples/headless-agent/tests/fixtures/semantic-checkpoint-agent.ts
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Loader fixture that publishes the semantic-checkpoint session before CLI dispatch.
|
||||
* @module semantic-checkpoint-agent
|
||||
*/
|
||||
|
||||
import type { Context } from 'cordis'
|
||||
import type { SessionId } from '@deepseek-ai/dsh-session'
|
||||
|
||||
/** Fixture plugin name. */
|
||||
export const name = 'semantic-checkpoint-agent'
|
||||
/** Services that must exist before the fixture resumes its agent. */
|
||||
export const inject = ['agents', 'agentLoop', 'sessionPersistence']
|
||||
|
||||
/**
|
||||
* Resume the seeded session and bind its exact handle to this fixture's lifetime.
|
||||
* @param ctx - settled agent and persistence services from the Loader tree.
|
||||
* @returns after the resumed agent is published.
|
||||
*/
|
||||
export async function apply(ctx: Context): Promise<void> {
|
||||
const handle = await ctx.agents.resume({
|
||||
resumeSessionId: 'semantic-checkpoint-unknown-outcome' as SessionId,
|
||||
agentOptions: { provider: 'deepseek', model: 'deepseek-v4-flash' },
|
||||
})
|
||||
ctx.effect(() => () => handle.dispose(), 'semantic-checkpoint-agent.handle')
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{"type":"session","version":0,"id":"semantic-checkpoint-replay","createdAt":1,"delegationDepth":0}
|
||||
@@ -0,0 +1,11 @@
|
||||
[
|
||||
{
|
||||
"kind": "chunks",
|
||||
"chunks": [
|
||||
{ "type": "block-start", "index": 0, "blockType": "text" },
|
||||
{ "type": "text-delta", "index": 0, "text": "I will verify the external state before deciding whether to retry the side-effecting operation." },
|
||||
{ "type": "block-end", "index": 0, "block": { "type": "text", "text": "I will verify the external state before deciding whether to retry the side-effecting operation." } },
|
||||
{ "type": "finish", "reason": { "kind": "stop" } }
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,21 @@
|
||||
{"type":"session","version":0,"id":"{{sessionId}}","createdAt":0,"cwd":"{{cwd}}","delegationDepth":0}
|
||||
{"type":"turn/start","seq":0,"time":0,"data":{"turn":1,"trigger":{"kind":"message","source":{"kind":"user"}}}}
|
||||
{"type":"user/message","seq":1,"time":0,"data":{"content":[{"type":"text","text":"Perform one side-effecting remote mutation."}],"source":{"kind":"user"}},"surfaceOp":"append"}
|
||||
{"type":"step/start","seq":2,"time":0,"data":{"turn":1,"step":1}}
|
||||
{"type":"assistant/message","seq":3,"time":0,"data":{"turn":1,"step":1,"content":[{"type":"tool-call","id":"unknown-outcome-call","name":"write_remote","arguments":"{\"value\":1}"}],"provenance":{"provider":"deepseek","model":"deepseek-v4-flash"}},"surfaceOp":"append"}
|
||||
{"type":"tool/call","seq":4,"time":0,"data":{"turn":1,"step":1,"callId":"unknown-outcome-call","name":"write_remote","arguments":"{\"value\":1}"}}
|
||||
{"type":"tool/result","seq":5,"time":0,"data":{"turn":1,"step":1,"callId":"unknown-outcome-call","content":[{"type":"text","text":"The tool call was interrupted after it was recorded, but no result was durably recorded. Its outcome is unknown. Decide whether to retry from the tool semantics: retry only if the operation is read-only or idempotent; if it may have side effects, first verify external state or ask the user. Do not retry blindly."}],"isError":true,"error":{"name":"ToolOutcomeUnknownError","code":"TOOL_OUTCOME_UNKNOWN"}},"surfaceOp":"append","sourceEventSeqs":[4]}
|
||||
{"type":"step/end","seq":6,"time":0,"data":{"turn":1,"step":1}}
|
||||
{"type":"turn/end","seq":7,"time":0,"data":{"turn":1,"reason":{"kind":"interrupted"}}}
|
||||
{"type":"turn/start","seq":8,"time":0,"data":{"turn":2,"trigger":{"kind":"message","source":{"kind":"user"}}}}
|
||||
{"type":"user/message","seq":9,"time":0,"data":{"content":[{"type":"text","text":"Continue safely from the interrupted operation."}],"source":{"kind":"user"}},"surfaceOp":"append"}
|
||||
{"type":"session/title","seq":10,"time":0,"data":{"title":"Perform one side-effecting remote mutati","messageSeqs":[1],"source":{"kind":"fallback"}}}
|
||||
{"type":"step/start","seq":11,"time":0,"data":{"turn":2,"step":1}}
|
||||
{"type":"request/header","seq":12,"time":0,"data":{"header":{"config":{"provider":"deepseek","model":"deepseek-v4-flash"},"system":"{{system}}","tools":"{{tools}}"},"reason":"initial"}}
|
||||
{"type":"assistant/chunk","seq":13,"time":0,"data":{"turn":2,"step":1,"chunk":{"type":"block-start","index":0,"blockType":"text"}}}
|
||||
{"type":"assistant/chunk","seq":14,"time":0,"data":{"turn":2,"step":1,"chunk":{"type":"text-delta","index":0,"text":"I will verify the external state before deciding whether to retry the side-effecting operation."}}}
|
||||
{"type":"assistant/chunk","seq":15,"time":0,"data":{"turn":2,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"I will verify the external state before deciding whether to retry the side-effecting operation."}}}}
|
||||
{"type":"assistant/chunk","seq":16,"time":0,"data":{"turn":2,"step":1,"chunk":{"type":"finish","reason":{"kind":"stop"}}}}
|
||||
{"type":"assistant/message","seq":17,"time":0,"data":{"turn":2,"step":1,"content":[{"type":"text","text":"I will verify the external state before deciding whether to retry the side-effecting operation."}],"provenance":{"provider":"deepseek","model":"deepseek-v4-flash"}},"sourceEventSeqs":[13,14,15,16],"surfaceOp":"append"}
|
||||
{"type":"step/end","seq":18,"time":0,"data":{"turn":2,"step":1}}
|
||||
{"type":"turn/end","seq":19,"time":0,"data":{"turn":2,"reason":{"kind":"completed"}}}
|
||||
113
examples/headless-agent/tests/semantic-checkpoint.snapshot.ts
Normal file
113
examples/headless-agent/tests/semantic-checkpoint.snapshot.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
import { readFile, writeFile } from 'node:fs/promises'
|
||||
import { dirname, join } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { Context } from 'cordis'
|
||||
import { normalizeSessionLog, scrubRequestHeaders, type NormalizeContext } from '@deepseek-ai/dsh-acp-snapshot'
|
||||
import { LOADER_SMOKE_TEST_TIMEOUT_MS, runLoaderSmoke } from '@deepseek-ai/dsh-loader-smoke'
|
||||
import { CallId } from '@deepseek-ai/dsh-llm'
|
||||
import SessionStore, { SESSION_FORMAT_VERSION, SessionId, type SessionEvent, type SessionHeader } from '@deepseek-ai/dsh-session'
|
||||
import SessionPersistenceJsonl from '@deepseek-ai/dsh-session-persistence-jsonl'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
const fixtureDir = join(dirname(fileURLToPath(import.meta.url)), 'semantic-checkpoint-snapshots/tool-outcome-unknown')
|
||||
const replayFixture = join(fixtureDir, 'replay.jsonl')
|
||||
const replayOverride = join(fixtureDir, 'replay.override.json')
|
||||
const sessionExpected = join(fixtureDir, 'session.expected.jsonl')
|
||||
const configPath = fileURLToPath(new URL('../semantic-checkpoint.cordis.snapshot.yml', import.meta.url))
|
||||
const binScript = fileURLToPath(new URL('../../../packages/examples/cli-demo/src/bin.ts', import.meta.url))
|
||||
const tsconfigPath = fileURLToPath(new URL('../../../tsconfig.json', import.meta.url))
|
||||
const sessionId = SessionId('semantic-checkpoint-unknown-outcome')
|
||||
const refreshing = process.env.DSH_SNAPSHOT === 'refresh'
|
||||
const task = 'Continue safely from the interrupted operation.'
|
||||
|
||||
async function seedInterruptedSession(root: string, cwd: string): Promise<string> {
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(SessionStore)
|
||||
await ctx.plugin(SessionPersistenceJsonl, { root, compression: 'none' })
|
||||
const meta: SessionHeader = {
|
||||
version: SESSION_FORMAT_VERSION,
|
||||
id: sessionId,
|
||||
createdAt: 1,
|
||||
cwd,
|
||||
delegationDepth: 0,
|
||||
}
|
||||
const events: SessionEvent[] = [
|
||||
{ type: 'turn/start', seq: 0, time: 10, data: { turn: 1, trigger: { kind: 'message', source: { kind: 'user' } } } },
|
||||
{ type: 'user/message', seq: 1, time: 11, data: { content: [{ type: 'text', text: 'Perform one side-effecting remote mutation.' }], source: { kind: 'user' } }, surfaceOp: 'append' },
|
||||
{ type: 'step/start', seq: 2, time: 12, data: { turn: 1, step: 1 } },
|
||||
{
|
||||
type: 'assistant/message',
|
||||
seq: 3,
|
||||
time: 13,
|
||||
data: {
|
||||
turn: 1,
|
||||
step: 1,
|
||||
content: [{ type: 'tool-call', id: CallId('unknown-outcome-call'), name: 'write_remote', arguments: '{"value":1}' }],
|
||||
provenance: { provider: 'deepseek', model: 'deepseek-v4-flash' },
|
||||
},
|
||||
surfaceOp: 'append',
|
||||
},
|
||||
{
|
||||
type: 'tool/call',
|
||||
seq: 4,
|
||||
time: 14,
|
||||
data: {
|
||||
turn: 1,
|
||||
step: 1,
|
||||
callId: CallId('unknown-outcome-call'),
|
||||
name: 'write_remote',
|
||||
arguments: '{"value":1}',
|
||||
},
|
||||
},
|
||||
]
|
||||
try {
|
||||
await ctx.sessionPersistence.create(meta)
|
||||
await ctx.sessionPersistence.append(sessionId, events)
|
||||
const location = ctx.sessionPersistence.locate(meta)
|
||||
if (location === undefined) throw new Error('JSONL backend did not locate the seeded session')
|
||||
return location.path
|
||||
} finally {
|
||||
await ctx.fiber.dispose()
|
||||
}
|
||||
}
|
||||
|
||||
describe('semantic checkpoint recovery snapshot', () => {
|
||||
it('resumes an unknown tool outcome through the headless stream-json app', async () => {
|
||||
let cwd = ''
|
||||
let sessionPath = ''
|
||||
const result = await runLoaderSmoke({
|
||||
label: 'semantic checkpoint headless stream-json snapshot',
|
||||
tempDirPrefix: 'dsh-semantic-snapshot-',
|
||||
binScript,
|
||||
configPath,
|
||||
binArgs: ['--config', configPath, '--output-format', 'stream-json', task],
|
||||
tsconfigPath,
|
||||
env: {
|
||||
DSH_SNAPSHOT_FILE: replayFixture,
|
||||
DSH_SNAPSHOT_OVERRIDE: replayOverride,
|
||||
},
|
||||
prepare: async (runCwd) => {
|
||||
cwd = runCwd
|
||||
sessionPath = await seedInterruptedSession(join(runCwd, '.sessions'), runCwd)
|
||||
},
|
||||
inspect: async () => {
|
||||
const normalization: NormalizeContext = { sessionIds: [sessionId], cwd }
|
||||
const session = scrubRequestHeaders(normalizeSessionLog(await readFile(sessionPath, 'utf8'), normalization))
|
||||
if (refreshing) await writeFile(sessionExpected, session)
|
||||
expect(session).toBe(await readFile(sessionExpected, 'utf8'))
|
||||
expect(session).toContain('TOOL_OUTCOME_UNKNOWN')
|
||||
expect(session).toContain('Do not retry blindly.')
|
||||
},
|
||||
})
|
||||
|
||||
expect(result.stderr).toBe('')
|
||||
const records = result.stdout.trimEnd().split('\n').map(line => JSON.parse(line) as Record<string, unknown>)
|
||||
expect(records.at(-1)).toMatchObject({
|
||||
type: 'result',
|
||||
success: true,
|
||||
sessionId,
|
||||
result: 'I will verify the external state before deciding whether to retry the side-effecting operation.',
|
||||
reason: { kind: 'completed' },
|
||||
})
|
||||
}, LOADER_SMOKE_TEST_TIMEOUT_MS)
|
||||
})
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user