Merge branch 'master' into worktree/charming-swartz-83bf33

This commit is contained in:
Yichen Jiang
2026-08-07 11:28:05 +08:00
committed by GitHub
386 changed files with 16174 additions and 3625 deletions

View File

@@ -31,6 +31,8 @@ flowchart LR
cfg --> plugin_headless_token_meter
plugin_headless_compact_basic["compact-basic<br/>@deepseek-ai/dsh-compact-basic"]
cfg --> plugin_headless_compact_basic
plugin_headless_session_projection["session-projection<br/>@deepseek-ai/dsh-session-projection"]
cfg --> plugin_headless_session_projection
plugin_headless_subagent["subagent<br/>@deepseek-ai/dsh-subagent"]
cfg --> plugin_headless_subagent
plugin_headless_subagent_spawn["subagent-spawn<br/>@deepseek-ai/dsh-subagent-spawn"]
@@ -71,6 +73,7 @@ flowchart LR
| `cli-agent` | `@deepseek-ai/dsh-cli-demo` |
| `token-meter` | `@deepseek-ai/dsh-token-meter` |
| `compact-basic` | `@deepseek-ai/dsh-compact-basic` |
| `session-projection` | `@deepseek-ai/dsh-session-projection` |
| `subagent` | `@deepseek-ai/dsh-subagent` |
| `subagent-spawn` | `@deepseek-ai/dsh-subagent-spawn` |
| `subagent-fork` | `@deepseek-ai/dsh-subagent-fork` |

View File

@@ -71,6 +71,11 @@
maxTokens: 8192
compactionRetries: 1
# Projection registry: durable subagent identity (mode/label) folds through
# its registered units; subagent catalog reads fail loud without the capability.
- id: session-projection
name: '@deepseek-ai/dsh-session-projection'
# Expose fresh-child `spawn` and completed-prefix `fork` through independent
# in-process backends.
- id: subagent

View File

@@ -0,0 +1,44 @@
# Keyless real-Loader composition for the descriptor-less cold-child
# diagnostic snapshot. The seeded parent owns one session-backed child whose
# log carries `origin: 'subagent'` but no descriptor event, so the projection
# fold produces no identity and `list_agents` must surface the child as a
# `[diagnostic: corrupt]` row instead of silently dropping it.
- id: persistence
name: '@deepseek-ai/dsh-session-persistence-jsonl'
config:
root: './.sessions'
compression: none
# file/override both default to their DSH_SNAPSHOT_* env vars.
- id: replay
name: '@deepseek-ai/dsh-llm-replay'
# This scenario probes the subagent catalog only, so the bash/filesystem
# stacks are absent; the bundle must opt out of the tools that would wait
# forever for executors this tree never mounts.
- id: agent
name: '@deepseek-ai/dsh-agent-spine-demo'
config:
agents: []
workspaceContext: false
skills:
enabled: false
toolBash: false
toolTasks: false
goals: false
# Projection registry: the cold child's identity fold runs through it; the
# catalog read fails loud when the capability is absent.
- id: session-projection
name: '@deepseek-ai/dsh-session-projection'
- id: subagent
name: '@deepseek-ai/dsh-subagent'
- id: tool-subagent-list-agents
name: '@deepseek-ai/dsh-tool-subagent-control/list-agents'
# Await the persisted resume before the headless driver inspects root agents.
- id: resumed-agent
name: './tests/fixtures/subagent-diagnostic-agent.ts'

View File

@@ -0,0 +1,26 @@
/**
* Loader fixture that resumes the seeded diagnostic-scenario parent before
* CLI dispatch, so `list_agents` runs against its pre-seeded cold child.
* @module subagent-diagnostic-agent
*/
import type { Context } from 'cordis'
import type { SessionId } from '@deepseek-ai/dsh-session'
/** Fixture plugin name. */
export const name = 'subagent-diagnostic-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: 'subagent-diagnostic-parent' as SessionId,
agentOptions: { provider: 'deepseek-official', model: 'deepseek-v4-flash' },
})
ctx.effect(() => () => handle.dispose(), 'subagent-diagnostic-agent.handle')
}

View File

@@ -0,0 +1,25 @@
/**
* Loader fixture that resumes the seeded workspace-context session.
* @module workspace-context-resume-agent
*/
import type { Context } from 'cordis'
import type { SessionId } from '@deepseek-ai/dsh-session'
/** Fixture plugin name. */
export const name = 'workspace-context-resume-agent'
/** Services that must exist before the fixture resumes its agent. */
export const inject = ['agents', 'agentLoop', 'sessionPersistence']
/**
* Resume the seeded session and bind its 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: 'workspace-context-resume' as SessionId,
agentOptions: { provider: 'deepseek-official', model: 'deepseek-v4-flash' },
})
ctx.effect(() => () => handle.dispose(), 'workspace-context-resume-agent.handle')
}

View File

@@ -0,0 +1,31 @@
{"type":"session","version":0,"id":"{{sessionId}}","createdAt":0,"cwd":"{{cwd}}","delegationDepth":0}
{"type":"turn/start","seq":0,"time":0,"data":{"turn":1}}
{"type":"user/message","seq":1,"time":0,"data":{"content":[{"type":"text","text":"Start a background task."}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"}
{"type":"turn/end","seq":2,"time":0,"data":{"turn":1,"reason":{"kind":"completed"}}}
{"type":"session/end-seed","seq":3,"time":0,"data":{}}
{"type":"agent/inbox/spliced","seq":4,"time":0,"data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"Call list_agents once and report what it shows."}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"}]}}
{"type":"turn/start","seq":5,"time":0,"data":{"turn":2}}
{"type":"agent/inbox/spliced","seq":6,"time":0,"data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[]}}
{"type":"step/start","seq":7,"time":0,"data":{"turn":2,"step":1}}
{"type":"user/message","seq":8,"time":0,"data":{"content":[{"type":"text","text":"Call list_agents once and report what it shows."}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"}
{"type":"session/title","seq":9,"time":0,"data":{"title":"Start a background task.","messageSeqs":[1],"source":{"kind":"fallback"}}}
{"type":"request/header","seq":10,"time":0,"data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"system":"{{system}}","tools":"{{tools}}"},"reason":"initial"}}
{"type":"request/context","seq":11,"time":0,"data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}}
{"type":"assistant/chunk","seq":12,"time":0,"data":{"turn":2,"step":1,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}
{"type":"assistant/chunk","seq":13,"time":0,"data":{"turn":2,"step":1,"chunk":{"type":"tool-call-delta","index":0,"id":"list-once","name":"list_agents","argumentsDelta":"{}"}}}
{"type":"assistant/chunk","seq":14,"time":0,"data":{"turn":2,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"list-once","name":"list_agents","arguments":"{}"}}}}
{"type":"assistant/chunk","seq":15,"time":0,"data":{"turn":2,"step":1,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":5}}}}
{"type":"assistant/chunk","seq":16,"time":0,"data":{"turn":2,"step":1,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}
{"type":"assistant/message","seq":17,"time":0,"data":{"turn":2,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"list-once","name":"list_agents","arguments":"{}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":10,"outputTokens":5}},"sourceEventSeqs":[12,13,14,15,16],"surfaceOp":"append"}
{"type":"tool/call","seq":18,"time":0,"data":{"turn":2,"step":1,"callId":"list-once","name":"list_agents","arguments":"{}"}}
{"type":"tool/result","seq":19,"time":0,"data":{"turn":2,"step":1,"message":{"source":{"kind":"tool","callId":"list-once"},"content":[{"type":"tool-result","toolCallId":"list-once","content":[{"type":"text","text":"{{sessionId}} [diagnostic: corrupt]"}],"isError":false}],"role":"user","id":"{{sessionId}}"}},"sourceEventSeqs":[18],"surfaceOp":"append"}
{"type":"step/end","seq":20,"time":0,"data":{"turn":2,"step":1}}
{"type":"step/start","seq":21,"time":0,"data":{"turn":2,"step":2}}
{"type":"assistant/chunk","seq":22,"time":0,"data":{"turn":2,"step":2,"chunk":{"type":"block-start","index":0,"blockType":"text"}}}
{"type":"assistant/chunk","seq":23,"time":0,"data":{"turn":2,"step":2,"chunk":{"type":"text-delta","index":0,"text":"The stored subagent is unreadable. PARENT_DONE"}}}
{"type":"assistant/chunk","seq":24,"time":0,"data":{"turn":2,"step":2,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"The stored subagent is unreadable. PARENT_DONE"}}}}
{"type":"assistant/chunk","seq":25,"time":0,"data":{"turn":2,"step":2,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":5}}}}
{"type":"assistant/chunk","seq":26,"time":0,"data":{"turn":2,"step":2,"chunk":{"type":"finish","reason":{"kind":"stop"}}}}
{"type":"assistant/message","seq":27,"time":0,"data":{"turn":2,"step":2,"message":{"role":"assistant","content":[{"type":"text","text":"The stored subagent is unreadable. PARENT_DONE"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":10,"outputTokens":5}},"sourceEventSeqs":[22,23,24,25,26],"surfaceOp":"append"}
{"type":"step/end","seq":28,"time":0,"data":{"turn":2,"step":2}}
{"type":"turn/end","seq":29,"time":0,"data":{"turn":2,"reason":{"kind":"completed"}}}

View File

@@ -0,0 +1 @@
[{"kind": "chunks", "chunks": [{"type": "block-start", "index": 0, "blockType": "tool-call"}, {"type": "tool-call-delta", "index": 0, "id": "list-once", "name": "list_agents", "argumentsDelta": "{}"}, {"type": "block-end", "index": 0, "block": {"type": "tool-call", "id": "list-once", "name": "list_agents", "arguments": "{}"}}, {"type": "usage", "usage": {"inputTokens": 10, "outputTokens": 5}}, {"type": "finish", "reason": {"kind": "tool-calls"}}]}, {"kind": "chunks", "chunks": [{"type": "block-start", "index": 0, "blockType": "text"}, {"type": "text-delta", "index": 0, "text": "The stored subagent is unreadable. PARENT_DONE"}, {"type": "block-end", "index": 0, "block": {"type": "text", "text": "The stored subagent is unreadable. PARENT_DONE"}}, {"type": "usage", "usage": {"inputTokens": 10, "outputTokens": 5}}, {"type": "finish", "reason": {"kind": "stop"}}]}]

View File

@@ -0,0 +1,119 @@
/**
* Assembled-app regression: a persisted `origin: 'subagent'` child whose log
* carries no descriptor event is surfaced by `list_agents` as a
* `[diagnostic: corrupt]` row instead of being silently dropped.
*/
import { readFile, readdir, writeFile } from 'node:fs/promises'
import { 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 { createUserMessage } 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 = fileURLToPath(new URL('./subagent-diagnostic-snapshots/descriptorless-child', import.meta.url))
const replayOverride = join(fixtureDir, 'replay.override.json')
const parentExpected = join(fixtureDir, 'parent.expected.jsonl')
const configPath = fileURLToPath(new URL('../subagent-diagnostic.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 parentId = SessionId('subagent-diagnostic-parent')
const childId = SessionId('subagent-diagnostic-child')
const refreshing = process.env.DSH_SNAPSHOT === 'refresh'
const task = 'Call list_agents once and report what it shows.'
/**
* Seed a completed parent turn plus one cold child that durably classifies
* as a subagent (`origin`) but never appended its descriptor event — the
* publication-window death the diagnostic row exists for.
*/
async function seedDescriptorlessChild(root: string, cwd: string): Promise<void> {
const ctx = new Context()
await ctx.plugin(SessionStore)
await ctx.plugin(SessionPersistenceJsonl, { root, compression: 'none' })
const parentMeta: SessionHeader = {
version: SESSION_FORMAT_VERSION,
id: parentId,
createdAt: 1,
cwd,
delegationDepth: 0,
}
const parentEvents: SessionEvent[] = [
{ type: 'turn/start', seq: 0, time: 10, data: { turn: 1 } },
{ type: 'user/message', seq: 1, time: 11, data: createUserMessage({ content: [{ type: 'text', text: 'Start a background task.' }], source: { kind: 'user' } }), surfaceOp: 'append' },
{ type: 'turn/end', seq: 2, time: 12, data: { turn: 1, reason: { kind: 'completed' } } },
]
const childMeta: SessionHeader = {
version: SESSION_FORMAT_VERSION,
id: childId,
createdAt: 2,
cwd,
parentSession: parentId,
origin: 'subagent',
delegationDepth: 1,
}
const childEvents: SessionEvent[] = [
{ type: 'turn/start', seq: 0, time: 20, data: { turn: 1 } },
{ type: 'turn/end', seq: 1, time: 21, data: { turn: 1, reason: { kind: 'interrupted' } } },
]
try {
await ctx.sessionPersistence.create(parentMeta)
await ctx.sessionPersistence.append(parentId, parentEvents)
await ctx.sessionPersistence.create(childMeta)
await ctx.sessionPersistence.append(childId, childEvents)
} finally {
await ctx.fiber.dispose()
}
}
describe('descriptor-less cold child diagnostic snapshot', () => {
it('surfaces the unreadable child as a corrupt diagnostic through the assembled headless app', async () => {
let cwd = ''
const result = await runLoaderSmoke({
label: 'subagent diagnostic headless stream-json snapshot',
tempDirPrefix: 'dsh-subagent-diag-',
binScript,
configPath,
binArgs: ['--config', configPath, '--output-format', 'stream-json', task],
tsconfigPath,
env: {
DSH_SNAPSHOT_FILE: replayOverride,
DSH_SNAPSHOT_OVERRIDE: replayOverride,
},
prepare: async (runCwd) => {
cwd = runCwd
await seedDescriptorlessChild(join(runCwd, '.sessions'), runCwd)
},
inspect: async (runCwd) => {
const sessionsDir = join(runCwd, '.sessions')
const files = (await readdir(sessionsDir, { recursive: true })).filter(file => file.endsWith('.jsonl'))
const logs = await Promise.all(files.map(async file => readFile(join(sessionsDir, file), 'utf8')))
const parent = logs.find(content => content.includes('"subagent-diagnostic-parent"'))
if (parent === undefined) throw new Error('missing persisted parent log')
// THE model-visible fact: the descriptor-less child is reported, not
// silently dropped, and its reason is the corrupt classification.
expect(parent).toContain(`${childId} [diagnostic: corrupt]`)
const context: NormalizeContext = { sessionIds: [parentId, childId], cwd }
const normalizedParent = scrubRequestHeaders(normalizeSessionLog(parent, context))
if (refreshing) {
await writeFile(parentExpected, normalizedParent)
}
expect(normalizedParent).toBe(await readFile(parentExpected, 'utf8'))
},
})
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',
sessionId: parentId,
output: 'The stored subagent is unreadable. PARENT_DONE',
})
}, LOADER_SMOKE_TEST_TIMEOUT_MS)
})

View File

@@ -0,0 +1 @@
{"type":"session","version":0,"id":"workspace-context-resume-replay","createdAt":1,"delegationDepth":0}

View File

@@ -0,0 +1,11 @@
[
{
"kind": "chunks",
"chunks": [
{ "type": "block-start", "index": 0, "blockType": "text" },
{ "type": "text-delta", "index": 0, "text": "RESUME_DONE" },
{ "type": "block-end", "index": 0, "block": { "type": "text", "text": "RESUME_DONE" } },
{ "type": "finish", "reason": { "kind": "stop" } }
]
}
]

View File

@@ -0,0 +1,22 @@
{"type":"session","version":0,"id":"{{sessionId}}","createdAt":0,"cwd":"{{cwd}}","delegationDepth":0}
{"type":"turn/start","seq":0,"time":0,"data":{"turn":1}}
{"type":"user/message","seq":1,"time":0,"data":{"content":[{"type":"text","text":"Remember the workspace instruction."}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"}
{"type":"user/message","seq":2,"time":0,"data":{"content":[{"type":"text","text":"<system-reminder>\nThe following workspace instructions may be relevant to your work. Use them as guidance when applicable. More specific instructions take precedence over broader ones. They do not override system, developer, or direct user instructions.\n\nInstructions from: AGENTS.md\n\nOld workspace instruction.\n</system-reminder>"}],"source":{"kind":"workspace-instructions","form":"instructions","baseline":true,"baselineIdentity":"{\"projectRoot\":\"\",\"projectRootMarkers\":[\".git\"],\"maxBytes\":65536,\"maxSourceBytes\":1048576,\"instructionFileCandidates\":[\"AGENTS.md\",\"CLAUDE.md\"],\"localInstructionFileCandidates\":[\"AGENTS.local.md\",\"CLAUDE.local.md\"]}","changes":[{"action":"set","scope":".\u0000AGENTS.md","path":"AGENTS.md","digest":"ba65bdb41810f4d0129129dcbd6cadcd643c069d"}]},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"}
{"type":"turn/end","seq":3,"time":0,"data":{"turn":1,"reason":{"kind":"completed"}}}
{"type":"session/end-seed","seq":4,"time":0,"data":{}}
{"type":"agent/inbox/spliced","seq":5,"time":0,"data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"Acknowledge the current workspace instruction."}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"}]}}
{"type":"turn/start","seq":6,"time":0,"data":{"turn":2}}
{"type":"agent/inbox/spliced","seq":7,"time":0,"data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[]}}
{"type":"step/start","seq":8,"time":0,"data":{"turn":2,"step":1}}
{"type":"user/message","seq":9,"time":0,"data":{"content":[{"type":"text","text":"Acknowledge the current workspace instruction."}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"}
{"type":"user/message","seq":10,"time":0,"data":{"content":[{"type":"text","text":"<system-reminder>\nUpdated instructions from: AGENTS.md\n\nThis file changed after it was loaded. Use the following content instead of the previously loaded instructions from this file.\n\nNew workspace instruction after offline edit.\n\n</system-reminder>"}],"source":{"kind":"workspace-instructions","form":"instructions","changes":[{"action":"replace","scope":".\u0000AGENTS.md","path":"AGENTS.md","digest":"d8375b516f158718bd3463bc8eb7ed42c011b29f"}]},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"}
{"type":"session/title","seq":11,"time":0,"data":{"title":"Remember the workspace instruction.","messageSeqs":[1],"source":{"kind":"fallback"}}}
{"type":"request/header","seq":12,"time":0,"data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"system":"{{system}}"},"reason":"initial"}}
{"type":"request/context","seq":13,"time":0,"data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}}
{"type":"assistant/chunk","seq":14,"time":0,"data":{"turn":2,"step":1,"chunk":{"type":"block-start","index":0,"blockType":"text"}}}
{"type":"assistant/chunk","seq":15,"time":0,"data":{"turn":2,"step":1,"chunk":{"type":"text-delta","index":0,"text":"RESUME_DONE"}}}
{"type":"assistant/chunk","seq":16,"time":0,"data":{"turn":2,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"RESUME_DONE"}}}}
{"type":"assistant/chunk","seq":17,"time":0,"data":{"turn":2,"step":1,"chunk":{"type":"finish","reason":{"kind":"stop"}}}}
{"type":"assistant/message","seq":18,"time":0,"data":{"turn":2,"step":1,"message":{"role":"assistant","content":[{"type":"text","text":"RESUME_DONE"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"}},"sourceEventSeqs":[14,15,16,17],"surfaceOp":"append"}
{"type":"step/end","seq":19,"time":0,"data":{"turn":2,"step":1}}
{"type":"turn/end","seq":20,"time":0,"data":{"turn":2,"reason":{"kind":"completed"}}}

View File

@@ -0,0 +1,22 @@
{"type":"session","version":0,"id":"{{sessionId}}","createdAt":0,"cwd":"{{cwd}}","delegationDepth":0}
{"type":"turn/start","seq":0,"time":0,"data":{"turn":1}}
{"type":"user/message","seq":1,"time":0,"data":{"content":[{"type":"text","text":"Remember the workspace instruction."}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"}
{"type":"user/message","seq":2,"time":0,"data":{"content":[{"type":"text","text":"<system-reminder>\nThe following workspace instructions may be relevant to your work. Use them as guidance when applicable. More specific instructions take precedence over broader ones. They do not override system, developer, or direct user instructions.\n\nInstructions from: CLAUDE.md\n\nOld CLAUDE rule.\n\nInstructions from: AGENTS.md\n\nOld AGENTS rule.\n</system-reminder>"}],"source":{"kind":"workspace-instructions","form":"instructions","baseline":true,"baselineIdentity":"{\"projectRoot\":\"\",\"projectRootMarkers\":[\".git\"],\"maxBytes\":65536,\"maxSourceBytes\":1048576,\"instructionFileCandidates\":[\"CLAUDE.md\",\"AGENTS.md\"],\"localInstructionFileCandidates\":[\"AGENTS.local.md\",\"CLAUDE.local.md\"]}","changes":[{"action":"set","scope":".\u0000CLAUDE.md","path":"CLAUDE.md","digest":"b525eb8a6d3660b732dad4b0aff1b7c63ab32890"},{"action":"set","scope":".\u0000AGENTS.md","path":"AGENTS.md","digest":"3113bd093ae91976207dcef7390bdc0b2bfcfa10"}]},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"}
{"type":"turn/end","seq":3,"time":0,"data":{"turn":1,"reason":{"kind":"completed"}}}
{"type":"session/end-seed","seq":4,"time":0,"data":{}}
{"type":"agent/inbox/spliced","seq":5,"time":0,"data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"Acknowledge the current workspace instruction."}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"}]}}
{"type":"turn/start","seq":6,"time":0,"data":{"turn":2}}
{"type":"agent/inbox/spliced","seq":7,"time":0,"data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[]}}
{"type":"step/start","seq":8,"time":0,"data":{"turn":2,"step":1}}
{"type":"user/message","seq":9,"time":0,"data":{"content":[{"type":"text","text":"Acknowledge the current workspace instruction."}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"}
{"type":"user/message","seq":10,"time":0,"data":{"content":[{"type":"text","text":"<system-reminder>\nThis complete workspace instruction baseline replaces all earlier workspace instruction baselines. The following workspace instructions may be relevant to your work. Use them as guidance when applicable. More specific instructions take precedence over broader ones. They do not override system, developer, or direct user instructions.\n\nInstructions from: AGENTS.md\n\nCurrent AGENTS rule.\n\n\nInstructions from: CLAUDE.md\n\nCurrent CLAUDE rule.\n\n</system-reminder>"}],"source":{"kind":"workspace-instructions","form":"instructions","baseline":true,"baselineIdentity":"{\"projectRoot\":\"\",\"projectRootMarkers\":[\".git\"],\"maxBytes\":65536,\"maxSourceBytes\":1048576,\"instructionFileCandidates\":[\"AGENTS.md\",\"CLAUDE.md\"],\"localInstructionFileCandidates\":[\"AGENTS.local.md\",\"CLAUDE.local.md\"]}","changes":[{"action":"set","scope":".\u0000AGENTS.md","path":"AGENTS.md","digest":"7f53d2327837129750aef117f9754a001c46cf68"},{"action":"set","scope":".\u0000CLAUDE.md","path":"CLAUDE.md","digest":"5b1e9e3fd759eee6b43ceff899e47fb10c64701a"}]},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"}
{"type":"session/title","seq":11,"time":0,"data":{"title":"Remember the workspace instruction.","messageSeqs":[1],"source":{"kind":"fallback"}}}
{"type":"request/header","seq":12,"time":0,"data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"system":"{{system}}"},"reason":"initial"}}
{"type":"request/context","seq":13,"time":0,"data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}}
{"type":"assistant/chunk","seq":14,"time":0,"data":{"turn":2,"step":1,"chunk":{"type":"block-start","index":0,"blockType":"text"}}}
{"type":"assistant/chunk","seq":15,"time":0,"data":{"turn":2,"step":1,"chunk":{"type":"text-delta","index":0,"text":"RESUME_DONE"}}}
{"type":"assistant/chunk","seq":16,"time":0,"data":{"turn":2,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"RESUME_DONE"}}}}
{"type":"assistant/chunk","seq":17,"time":0,"data":{"turn":2,"step":1,"chunk":{"type":"finish","reason":{"kind":"stop"}}}}
{"type":"assistant/message","seq":18,"time":0,"data":{"turn":2,"step":1,"message":{"role":"assistant","content":[{"type":"text","text":"RESUME_DONE"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"}},"sourceEventSeqs":[14,15,16,17],"surfaceOp":"append"}
{"type":"step/end","seq":19,"time":0,"data":{"turn":2,"step":1}}
{"type":"turn/end","seq":20,"time":0,"data":{"turn":2,"reason":{"kind":"completed"}}}

View File

@@ -0,0 +1,233 @@
/**
* Assembled-app regression for persisted workspace-instruction resume state.
* @module workspace-context-resume-snapshot
*/
import { createHash } from 'node:crypto'
import { mkdir, readFile, readdir, 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 { createUserMessage } 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 { renderWorkspaceContext } from '@deepseek-ai/dsh-workspace-context'
import { resolveConfig, workspaceBaselineIdentity } from '@deepseek-ai/dsh-workspace-context/src/config.ts'
import { describe, expect, it } from 'vitest'
const fixtureDir = join(dirname(fileURLToPath(import.meta.url)), 'workspace-context-resume-snapshots/offline-edit')
const replayFixture = join(fixtureDir, 'replay.jsonl')
const replayOverride = join(fixtureDir, 'replay.override.json')
const sessionExpected = join(fixtureDir, 'session.expected.jsonl')
const precedenceExpected = join(dirname(fixtureDir), 'precedence-change/session.expected.jsonl')
const configPath = fileURLToPath(new URL('../workspace-context-resume.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('workspace-context-resume')
const refreshing = process.env.DSH_SNAPSHOT === 'refresh'
const oldInstruction = 'Old workspace instruction.'
const newInstruction = 'New workspace instruction after offline edit.'
interface SeedBaselineOptions {
files?: Array<{ name: string; content: string }>
instructionFileCandidates?: string[]
}
async function seedVisibleBaseline(
root: string,
cwd: string,
options: SeedBaselineOptions = {},
): 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 files = options.files ?? [{ name: 'AGENTS.md', content: oldInstruction }]
const baseline = renderWorkspaceContext(files.map(file => ({
absolutePath: join(cwd, file.name),
displayPath: file.name,
content: file.content,
})), { maxBytes: 65536 })
const config = resolveConfig({
dshHome: join(cwd, '.dsh'),
maxBytes: 65536,
...options.instructionFileCandidates === undefined
? {}
: { instructionFileCandidates: options.instructionFileCandidates },
})
const events: SessionEvent[] = [
{ type: 'turn/start', seq: 0, time: 10, data: { turn: 1 } },
{
type: 'user/message',
seq: 1,
time: 11,
data: createUserMessage({ content: [{ type: 'text', text: 'Remember the workspace instruction.' }], source: { kind: 'user' } }),
surfaceOp: 'append',
},
{
type: 'user/message',
seq: 2,
time: 12,
data: createUserMessage({
content: [{ type: 'text', text: baseline.text }],
source: {
kind: 'workspace-instructions',
form: 'instructions',
baseline: true,
baselineIdentity: workspaceBaselineIdentity(config, cwd, cwd),
changes: files.map(file => ({
action: 'set',
scope: `.\0${file.name}`,
path: file.name,
digest: createHash('sha1').update(file.content).digest('hex'),
})),
},
}),
surfaceOp: 'append',
},
{ type: 'turn/end', seq: 3, time: 13, data: { turn: 1, reason: { kind: 'completed' } } },
]
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('workspace-context resume snapshot', () => {
it('appends an offline replacement without duplicating the visible baseline', async () => {
let cwd = ''
let sessionPath = ''
const result = await runLoaderSmoke({
label: 'workspace-context resume headless stream-json snapshot',
tempDirPrefix: 'dsh-workspace-context-resume-',
binScript,
configPath,
binArgs: ['--config', configPath, '--output-format', 'stream-json', 'Acknowledge the current workspace instruction.'],
tsconfigPath,
env: {
DSH_SNAPSHOT_FILE: replayFixture,
DSH_SNAPSHOT_OVERRIDE: replayOverride,
},
prepare: async (runCwd) => {
cwd = runCwd
await mkdir(join(runCwd, '.git'), { recursive: true })
await writeFile(join(runCwd, 'AGENTS.md'), `${newInstruction}\n`)
sessionPath = await seedVisibleBaseline(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'))
const records = session.trimEnd().split('\n').map(line => JSON.parse(line) as {
type?: string
data?: {
source?: { kind?: string; baseline?: boolean; changes?: Array<Record<string, unknown>> }
content?: Array<{ type?: string; text?: string }>
}
})
const workspaceEvents = records.filter(record => record.type === 'user/message'
&& record.data?.source?.kind === 'workspace-instructions')
expect(workspaceEvents.filter(record => record.data?.source?.baseline === true)).toHaveLength(1)
expect(workspaceEvents.filter(record => record.data?.source?.baseline !== true)).toHaveLength(1)
expect(workspaceEvents.at(-1)?.data?.source?.changes).toMatchObject([{
action: 'replace', scope: '.\0AGENTS.md', path: 'AGENTS.md',
}])
expect(JSON.stringify(workspaceEvents.at(-1)?.data?.content)).toContain(newInstruction)
const files = await readdir(join(cwd, '.sessions'), { recursive: true })
expect(files.filter(file => file.endsWith('.jsonl'))).toHaveLength(1)
},
})
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',
sessionId,
output: 'RESUME_DONE',
})
}, LOADER_SMOKE_TEST_TIMEOUT_MS)
it('supersedes an incompatible baseline when precedence changed offline', async () => {
let cwd = ''
let sessionPath = ''
const result = await runLoaderSmoke({
label: 'workspace-context precedence-change resume snapshot',
tempDirPrefix: 'dsh-workspace-context-precedence-',
binScript,
configPath,
binArgs: ['--config', configPath, '--output-format', 'stream-json', 'Acknowledge the current workspace instruction.'],
tsconfigPath,
env: {
DSH_SNAPSHOT_FILE: replayFixture,
DSH_SNAPSHOT_OVERRIDE: replayOverride,
},
prepare: async (runCwd) => {
cwd = runCwd
await mkdir(join(runCwd, '.git'), { recursive: true })
await writeFile(join(runCwd, 'AGENTS.md'), 'Current AGENTS rule.\n')
await writeFile(join(runCwd, 'CLAUDE.md'), 'Current CLAUDE rule.\n')
sessionPath = await seedVisibleBaseline(join(runCwd, '.sessions'), runCwd, {
files: [
{ name: 'CLAUDE.md', content: 'Old CLAUDE rule.' },
{ name: 'AGENTS.md', content: 'Old AGENTS rule.' },
],
instructionFileCandidates: ['CLAUDE.md', 'AGENTS.md'],
})
},
inspect: async () => {
const normalization: NormalizeContext = { sessionIds: [sessionId], cwd }
const session = scrubRequestHeaders(normalizeSessionLog(await readFile(sessionPath, 'utf8'), normalization))
if (refreshing) {
await mkdir(dirname(precedenceExpected), { recursive: true })
await writeFile(precedenceExpected, session)
}
expect(session).toBe(await readFile(precedenceExpected, 'utf8'))
const records = session.trimEnd().split('\n').map(line => JSON.parse(line) as {
type?: string
data?: {
source?: { kind?: string; baseline?: boolean }
content?: Array<{ type?: string; text?: string }>
}
})
const baselines = records.filter(record => record.type === 'user/message'
&& record.data?.source?.kind === 'workspace-instructions'
&& record.data.source.baseline === true)
expect(baselines).toHaveLength(2)
const replacement = JSON.stringify(baselines.at(-1)?.data?.content)
expect(replacement).toContain('replaces all earlier workspace instruction baselines')
expect(replacement.indexOf('Instructions from: AGENTS.md'))
.toBeLessThan(replacement.indexOf('Instructions from: CLAUDE.md'))
},
})
expect(result.stderr).toBe('')
expect(result.stdout.trimEnd().split('\n').map(line => JSON.parse(line) as Record<string, unknown>).at(-1))
.toMatchObject({
type: 'result',
sessionId,
output: 'RESUME_DONE',
})
}, LOADER_SMOKE_TEST_TIMEOUT_MS)
})

View File

@@ -0,0 +1,37 @@
# Keyless real-Loader composition for workspace-instruction resume
# reconciliation. The test seeds one persisted baseline, changes AGENTS.md
# while the session is offline, then resumes through the public agent service.
- id: persistence
name: '@deepseek-ai/dsh-session-persistence-jsonl'
config:
root: './.sessions'
compression: none
- id: replay
name: '@deepseek-ai/dsh-llm-replay'
config:
file: !!js process.env.DSH_SNAPSHOT_FILE
overrideFile: !!js process.env.DSH_SNAPSHOT_OVERRIDE
- id: fs-local
name: '@deepseek-ai/dsh-fs-local'
config:
cwd: !!js process.cwd()
- id: agent
name: '@deepseek-ai/dsh-agent-spine-demo'
config:
agents: []
workspaceContext:
maxBytes: 65536
dshHome: !!js process.cwd() + '/.dsh'
skills:
enabled: false
toolBash: false
toolTasks: false
goals: false
# Await the persisted resume before the headless driver inspects root agents.
- id: resumed-agent
name: './tests/fixtures/workspace-context-resume-agent.ts'