Merge branch 'stack/agent-profiles-2-configs' into stack/agent-profiles-3-wire

This commit is contained in:
Yichen Jiang
2026-08-07 01:01:49 +08:00
188 changed files with 8407 additions and 654 deletions

View File

@@ -276,6 +276,8 @@ const SCENARIOS: Scenario[] = [
// symlinked instruction file to its target's content. A second nested path
// containing a literal closing tag is created at runtime: Git cannot check
// that name out on Windows, so this delimiter-injection case is POSIX-only.
// The fixture also shadows the baseline after the first touch finishes its
// projection; the next entering pre-step restores it before request 2.
// The scenario-specific config keeps home/root discovery hermetic, and the
// resulting prefix needs its own pinned header class.
{

View File

@@ -0,0 +1,40 @@
# Test-only composition of both public opt-in providers and foreground tools.
# The owning e2e boots this tree but never invokes a model or product process.
- id: fixture
name: './fixture.ts'
- id: subagent
name: '@deepseek-ai/dsh-subagent'
- id: subprocess
name: '@deepseek-ai/dsh-subprocess-local'
- id: subagent-codex
name: '@deepseek-ai/dsh-subagent-codex'
- id: subagent-claude-code
name: '@deepseek-ai/dsh-subagent-claude-code'
- id: tool-subagent-codex
name: '@deepseek-ai/dsh-tool-subagent'
config:
provider: codex
toolName: subagent_codex
enableRunInBackground: false
maxDepth: 'provider-managed'
- id: tool-subagent-claude-code
name: '@deepseek-ai/dsh-tool-subagent'
config:
provider: claude-code
toolName: subagent_claude_code
enableRunInBackground: false
maxDepth: 'provider-managed'
- id: cli-agent
name: '@deepseek-ai/dsh-cli-demo'
config:
provider: mock
model: mock-delegate
persona: 'This composition test must not start a model turn.'
workspaceContext: false

View File

@@ -0,0 +1,65 @@
#!/usr/bin/env node
/** Inspect both public product-provider compositions without invoking them. */
import { boot, resolveConfigPath } from '@deepseek-ai/dsh-app-boot'
import type {} from '@deepseek-ai/dsh-subagent'
import type {} from '@deepseek-ai/dsh-tools'
const configPath = process.argv[2]
if (configPath === undefined) {
throw new Error('product-provider Loader composition driver requires a config path')
}
let starts = 0
const ctx = await boot(
'product-provider-loader-composition',
resolveConfigPath(configPath, undefined),
undefined,
(hostCtx) => {
hostCtx.on('subagent/start', () => {
starts += 1
})
},
)
try {
const providerNames = ['codex', 'claude-code'] as const
const toolNames = ['subagent_codex', 'subagent_claude_code'] as const
const providers = providerNames.map((providerName) => {
const provider = ctx.subagents.getProvider(providerName)
if (provider === undefined) {
throw new Error(`${providerName} provider was not registered`)
}
return {
name: provider.name,
capabilities: provider.capabilities,
inheritsParentContext: provider.inheritsParentContext,
}
})
const tools = toolNames.map((toolName) => {
const tool = ctx.tools.schemas().find(schema => schema.name === toolName)
if (tool === undefined) throw new Error(`${toolName} tool was not registered`)
const properties = tool.parameters.properties
if (
typeof properties !== 'object'
|| properties === null
|| Array.isArray(properties)
) {
throw new Error(`${toolName} has invalid parameter properties`)
}
return {
name: tool.name,
parameterNames: Object.keys(properties).sort(),
required: tool.parameters.required,
}
})
process.stdout.write(`${JSON.stringify({
registeredProviders: ctx.subagents.list(),
providers,
tools,
starts,
})}\n`)
} finally {
await ctx.fiber.dispose()
}

View File

@@ -0,0 +1,7 @@
/** Reuse the composition-only parent adapter shared by the product providers. */
export {
apply,
inject,
name,
} from '../subagent-codex/fixture.ts'

View File

@@ -0,0 +1,29 @@
# Test-only composition of the public opt-in provider and foreground tool.
# The owning e2e boots this tree but never invokes the model or Codex.
- id: fixture
name: './fixture.ts'
- id: subagent
name: '@deepseek-ai/dsh-subagent'
- id: subprocess
name: '@deepseek-ai/dsh-subprocess-local'
- id: subagent-codex
name: '@deepseek-ai/dsh-subagent-codex'
- id: tool-subagent-codex
name: '@deepseek-ai/dsh-tool-subagent'
config:
provider: codex
toolName: subagent_codex
enableRunInBackground: false
maxDepth: 'provider-managed'
- id: cli-agent
name: '@deepseek-ai/dsh-cli-demo'
config:
provider: mock
model: mock-delegate
persona: 'This composition test must not start a model turn.'
workspaceContext: false

View File

@@ -0,0 +1,51 @@
#!/usr/bin/env node
/** Inspect the public Codex provider composition without invoking the product. */
import { boot, resolveConfigPath } from '@deepseek-ai/dsh-app-boot'
import type {} from '@deepseek-ai/dsh-subagent'
import type {} from '@deepseek-ai/dsh-tools'
const configPath = process.argv[2]
if (configPath === undefined) {
throw new Error('subagent-codex Loader composition driver requires a config path')
}
let starts = 0
const ctx = await boot(
'subagent-codex-loader-composition',
resolveConfigPath(configPath, undefined),
undefined,
(hostCtx) => {
hostCtx.on('subagent/start', () => {
starts += 1
})
},
)
try {
const provider = ctx.subagents.getProvider('codex')
if (provider === undefined) throw new Error('Codex provider was not registered')
const tool = ctx.tools.schemas().find(schema => schema.name === 'subagent_codex')
if (tool === undefined) throw new Error('subagent_codex tool was not registered')
const properties = tool.parameters.properties
if (typeof properties !== 'object' || properties === null || Array.isArray(properties)) {
throw new Error('subagent_codex tool has invalid parameter properties')
}
process.stdout.write(`${JSON.stringify({
providers: ctx.subagents.list(),
provider: {
name: provider.name,
capabilities: provider.capabilities,
inheritsParentContext: provider.inheritsParentContext,
},
tool: {
name: tool.name,
parameterNames: Object.keys(properties).sort(),
required: tool.parameters.required,
},
starts,
})}\n`)
} finally {
await ctx.fiber.dispose()
}

View File

@@ -0,0 +1,22 @@
/** Parent adapter that fails if the composition-only Loader test starts a turn. */
import type { Context } from 'cordis'
import type { GenerateOptions, StreamChunk } from '@deepseek-ai/dsh-llm'
import { LlmAdapter } from '@deepseek-ai/dsh-llm'
class CompositionOnlyAdapter extends LlmAdapter {
async * stream(_options: GenerateOptions): AsyncIterable<StreamChunk> {
throw new Error('subagent-codex Loader composition must not invoke a model')
}
}
export const name = 'codex-loader-composition-fixture'
export const inject = ['llm']
/**
* Register a parent adapter solely so the host composition is complete.
* @param ctx - Loader context supplying the LLM seam.
*/
export function apply(ctx: Context): void {
ctx.llm.registerAdapter(['mock'], new CompositionOnlyAdapter())
}

View File

@@ -0,0 +1,36 @@
import type { Context } from 'cordis'
import type {} from '@deepseek-ai/dsh-agent'
import { COMPACT_CHECKPOINT_SOURCE } from '@deepseek-ai/dsh-compact'
import { createUserMessage } from '@deepseek-ai/dsh-llm'
import type {} from '@deepseek-ai/dsh-tools'
export const name = 'workspace-context-compaction'
/** Replace the visible workspace baseline after the first touch is fully projected. */
export function apply(ctx: Context): void {
ctx.on('tools/post-execute', async (exec, result, next) => {
const downstream = await next()
if (result.isError
|| exec.agent === undefined
|| exec.name !== 'read'
|| typeof exec.arguments !== 'object'
|| exec.arguments === null
|| !('file_path' in exec.arguments)
|| exec.arguments.file_path !== 'nested/task.txt') return downstream
const agent = exec.agent
const baseline = agent.session.surface.nodes
.map(seq => agent.session.events[seq])
.find(event => event?.type === 'user/message'
&& event.data.source.kind === 'workspace-instructions'
&& event.data.source.baseline === true)
if (baseline === undefined) throw new Error('workspace baseline missing before snapshot compaction')
agent.session.append('user/message', createUserMessage({
content: [{ type: 'text', text: 'Earlier context was compacted for this snapshot.' }],
source: COMPACT_CHECKPOINT_SOURCE,
}), {
surfaceOp: { op: 'replace', start: baseline.seq, end: baseline.seq },
sourceEventSeqs: [baseline.seq],
})
return downstream
})
}

View File

@@ -4,7 +4,7 @@
{"type":"agent/inbox/spliced","seq":2,"time":1785498825916,"data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[]}}
{"type":"step/start","seq":3,"time":1785901435161,"data":{"turn":1,"step":1}}
{"type":"user/message","seq":4,"time":1785498825917,"data":{"content":[{"type":"text","text":"Using ONE run_code program, call tools.read on nested/task.txt. After the program finishes, answer the workspace handshake question using the newly discovered instructions: What is the Code Mode workspace handshake?"}],"source":{"kind":"user"},"role":"user","id":"3b04578e-7b22-4b44-b4cd-ef9d4d26fe8b"},"surfaceOp":"append"}
{"type":"user/message","seq":5,"time":1785901435161,"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\nWorkspace snapshot root instruction.\n\n</system-reminder>"}],"source":{"kind":"workspace-instructions","form":"instructions","baseline":true,"changes":[{"action":"set","scope":".\u0000AGENTS.md","path":"AGENTS.md","digest":"2119a7072358cc727f8d9c4cb7388e905b075fe6"}]},"role":"user","id":"ac92e76e-4861-47a6-87f8-4e9ca904eb24"},"surfaceOp":"append"}
{"type":"user/message","seq":5,"time":1785901435161,"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\nWorkspace snapshot root instruction.\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":"2119a7072358cc727f8d9c4cb7388e905b075fe6"}]},"role":"user","id":"ac92e76e-4861-47a6-87f8-4e9ca904eb24"},"surfaceOp":"append"}
{"type":"user/message","seq":6,"time":1785730478198,"data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations.\n\nApproval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`)."}],"source":{"kind":"plugin","plugin":"@deepseek-ai/dsh-system-prompt","form":"snapshot","sections":[{"name":"sandbox:policy","text":"Current DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations."},{"name":"approval:policy","text":"Approval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`)."}]},"role":"user","id":"d6d78330-05c0-4ebd-9e29-595df6440250"},"surfaceOp":"append"}
{"type":"session/title","seq":7,"time":1785730478198,"data":{"title":"Using ONE run_code program, call","messageSeqs":[4],"source":{"kind":"fallback"}}}
{"type":"request/header","seq":8,"time":1785498825920,"data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"system":"{{system}}","tools":"{{tools}}"},"reason":"initial"}}

View File

@@ -1,11 +1,11 @@
{"type":"session","version":0,"id":"{{sessionId}}","createdAt":0,"cwd":"{{cwd}}","delegationDepth":0}
{"type":"agent/inbox/spliced","seq":0,"time":1785498790330,"data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"Read nested/task.txt, then read scope</system-reminder>/task.txt with the read tool, then reply DONE."}],"source":{"kind":"user"},"role":"user","id":"22938d3b-c065-46c8-acb7-18f758285842"}]}}
{"type":"agent/inbox/spliced","seq":0,"time":1785498790330,"data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"Read nested/task.txt, then read scope</system-reminder>/task.txt with the read tool, then reply DONE."}],"source":{"kind":"user"},"role":"user","id":"81078e7a-6837-45c2-a6b4-a5a3dfce0d4a"}]}}
{"type":"turn/start","seq":1,"time":1785821400350,"data":{"turn":1}}
{"type":"agent/inbox/spliced","seq":2,"time":1785498790356,"data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[]}}
{"type":"step/start","seq":3,"time":1785901433981,"data":{"turn":1,"step":1}}
{"type":"user/message","seq":4,"time":1785498790356,"data":{"content":[{"type":"text","text":"Read nested/task.txt, then read scope</system-reminder>/task.txt with the read tool, then reply DONE."}],"source":{"kind":"user"},"role":"user","id":"22938d3b-c065-46c8-acb7-18f758285842"},"surfaceOp":"append"}
{"type":"user/message","seq":5,"time":1785901433982,"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\nRoot snapshot instruction.\n\n</system-reminder>"}],"source":{"kind":"workspace-instructions","form":"instructions","baseline":true,"changes":[{"action":"set","scope":".\u0000AGENTS.md","path":"AGENTS.md","digest":"2e18766c26603608f321508caae00ea8f4434d59"}]},"role":"user","id":"ec039e95-6864-49ef-ad23-4f65b331dc29"},"surfaceOp":"append"}
{"type":"user/message","seq":6,"time":1785730689193,"data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations.\n\nApproval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`)."}],"source":{"kind":"plugin","plugin":"@deepseek-ai/dsh-system-prompt","form":"snapshot","sections":[{"name":"sandbox:policy","text":"Current DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations."},{"name":"approval:policy","text":"Approval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`)."}]},"role":"user","id":"cbea9bd9-3e08-48bf-951f-fe3e5aa4b5d9"},"surfaceOp":"append"}
{"type":"user/message","seq":4,"time":1785498790356,"data":{"content":[{"type":"text","text":"Read nested/task.txt, then read scope</system-reminder>/task.txt with the read tool, then reply DONE."}],"source":{"kind":"user"},"role":"user","id":"81078e7a-6837-45c2-a6b4-a5a3dfce0d4a"},"surfaceOp":"append"}
{"type":"user/message","seq":5,"time":1785901433982,"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\nRoot snapshot instruction.\n\n</system-reminder>"}],"source":{"kind":"workspace-instructions","form":"instructions","baseline":true,"baselineIdentity":"{\"projectRoot\":\"\",\"projectRootMarkers\":[\".dsh-project\"],\"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":"2e18766c26603608f321508caae00ea8f4434d59"}]},"role":"user","id":"4cba1848-cbb7-46fd-8cea-8497d54d0e63"},"surfaceOp":"append"}
{"type":"user/message","seq":6,"time":1785730689193,"data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations.\n\nApproval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`)."}],"source":{"kind":"plugin","plugin":"@deepseek-ai/dsh-system-prompt","form":"snapshot","sections":[{"name":"sandbox:policy","text":"Current DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations."},{"name":"approval:policy","text":"Approval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`)."}]},"role":"user","id":"e4406554-e400-49c6-b8a3-0fe36841160b"},"surfaceOp":"append"}
{"type":"session/title","seq":7,"time":1785730689193,"data":{"title":"Read nested/task.txt, then read scope</s","messageSeqs":[4],"source":{"kind":"fallback"}}}
{"type":"request/header","seq":8,"time":1785498790358,"data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"system":"{{system}}","tools":"{{tools}}"},"reason":"initial"}}
{"type":"request/context","seq":9,"time":1785730689194,"data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}}
@@ -14,32 +14,33 @@
{"type":"assistant/chunk","seq":12,"time":1784903339801,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"call_workspace_read","name":"read","arguments":"{\"file_path\":\"nested/task.txt\"}"}}}}
{"type":"assistant/chunk","seq":13,"time":1785498790358,"data":{"turn":1,"step":1,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":5}}}}
{"type":"assistant/chunk","seq":14,"time":1785730689194,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}
{"type":"assistant/message","seq":15,"time":1785730689194,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"call_workspace_read","name":"read","arguments":"{\"file_path\":\"nested/task.txt\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"cb229812-91a1-4aa6-8bce-d3b8d032f6dd"},"usage":{"inputTokens":10,"outputTokens":5}},"sourceEventSeqs":[10,11,12,13,14],"surfaceOp":"append"}
{"type":"assistant/message","seq":15,"time":1785730689194,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"call_workspace_read","name":"read","arguments":"{\"file_path\":\"nested/task.txt\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"9e9648c4-949e-4cf1-b9ef-0eb65897d36b"},"usage":{"inputTokens":10,"outputTokens":5}},"sourceEventSeqs":[10,11,12,13,14],"surfaceOp":"append"}
{"type":"tool/call","seq":16,"time":1785730689195,"data":{"turn":1,"step":1,"callId":"call_workspace_read","name":"read","arguments":"{\"file_path\":\"nested/task.txt\"}"}}
{"type":"tool/result","seq":17,"time":1785730689204,"data":{"turn":1,"step":1,"message":{"source":{"kind":"tool","callId":"call_workspace_read"},"content":[{"type":"tool-result","toolCallId":"call_workspace_read","content":[{"type":"text","text":"<path>{{cwd}}/nested/task.txt</path>\n<type>file</type>\n<content>\n1: snapshot task\n\n(End of file - total 1 lines)\n</content>"}],"isError":false}],"role":"user","id":"260bbd5d-4496-40cf-b987-d1d944d93cf1"},"meta":{"path":"{{cwd}}/nested/task.txt","offset":1,"lines":[{"number":1,"text":"snapshot task"}],"totalLines":1}},"sourceEventSeqs":[16],"surfaceOp":"append"}
{"type":"step/end","seq":18,"time":1785498790369,"data":{"turn":1,"step":1}}
{"type":"agent/inbox/spliced","seq":19,"time":1785498790369,"data":{"target":"next-step","start":0,"inserted":[{"content":[{"type":"text","text":"<system-reminder>\nAdditional instructions from: nested/AGENTS.md\n\nThese instructions apply to work under `nested`. Use them as guidance when relevant; more specific instructions take precedence. They do not override system, developer, or direct user instructions.\n\nNested snapshot instruction.\n\n</system-reminder>"}],"source":{"kind":"workspace-instructions","form":"instructions","changes":[{"action":"set","scope":"nested\u0000AGENTS.md","path":"nested/AGENTS.md","digest":"c446df9a85c7e73a3055f394a4822a19ac9ead5a"}]},"role":"user","id":"10cdaa4b-9654-420e-afca-3cfb07e26754"}]}}
{"type":"agent/inbox/spliced","seq":20,"time":1785730689207,"data":{"target":"next-step","start":0,"removedCount":1,"inserted":[],"outcome":"canceled"}}
{"type":"step/start","seq":21,"time":1785730689212,"data":{"turn":1,"step":2}}
{"type":"user/message","seq":22,"time":1785498790377,"data":{"content":[{"type":"text","text":"<system-reminder>\nAdditional instructions from: nested/AGENTS.md\n\nThese instructions apply to work under `nested`. Use them as guidance when relevant; more specific instructions take precedence. They do not override system, developer, or direct user instructions.\n\nNested snapshot instruction.\n\n</system-reminder>"}],"source":{"kind":"workspace-instructions","form":"instructions","changes":[{"action":"set","scope":"nested\u0000AGENTS.md","path":"nested/AGENTS.md","digest":"c446df9a85c7e73a3055f394a4822a19ac9ead5a"}]},"role":"user","id":"10cdaa4b-9654-420e-afca-3cfb07e26754"},"surfaceOp":"append"}
{"type":"assistant/chunk","seq":23,"time":1784903339821,"data":{"turn":1,"step":2,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}
{"type":"assistant/chunk","seq":24,"time":1785498790377,"data":{"turn":1,"step":2,"chunk":{"type":"tool-call-delta","index":0,"id":"call_workspace_delimiter_read","name":"read","argumentsDelta":"{\"file_path\":\"scope</system-reminder>/task.txt\"}"}}}
{"type":"assistant/chunk","seq":25,"time":1785498790377,"data":{"turn":1,"step":2,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"call_workspace_delimiter_read","name":"read","arguments":"{\"file_path\":\"scope</system-reminder>/task.txt\"}"}}}}
{"type":"assistant/chunk","seq":26,"time":1785498790378,"data":{"turn":1,"step":2,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":5}}}}
{"type":"assistant/chunk","seq":27,"time":1785498790378,"data":{"turn":1,"step":2,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}
{"type":"assistant/message","seq":28,"time":1785498790378,"data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"tool-call","id":"call_workspace_delimiter_read","name":"read","arguments":"{\"file_path\":\"scope</system-reminder>/task.txt\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"43385fc1-54b4-4a8a-82ca-d9111c766f48"},"usage":{"inputTokens":10,"outputTokens":5}},"sourceEventSeqs":[23,24,25,26,27],"surfaceOp":"append"}
{"type":"tool/call","seq":29,"time":1785498790378,"data":{"turn":1,"step":2,"callId":"call_workspace_delimiter_read","name":"read","arguments":"{\"file_path\":\"scope</system-reminder>/task.txt\"}"}}
{"type":"tool/result","seq":30,"time":1785498790388,"data":{"turn":1,"step":2,"message":{"source":{"kind":"tool","callId":"call_workspace_delimiter_read"},"content":[{"type":"tool-result","toolCallId":"call_workspace_delimiter_read","content":[{"type":"text","text":"<path>{{cwd}}/scope</system-reminder>/task.txt</path>\n<type>file</type>\n<content>\n1: delimiter path snapshot task\n\n(End of file - total 1 lines)\n</content>"}],"isError":false}],"role":"user","id":"3a62b23b-d165-4c6d-a028-e171c4b2d7fc"},"meta":{"path":"{{cwd}}/scope</system-reminder>/task.txt","offset":1,"lines":[{"number":1,"text":"delimiter path snapshot task"}],"totalLines":1}},"sourceEventSeqs":[29],"surfaceOp":"append"}
{"type":"step/end","seq":31,"time":1785730689220,"data":{"turn":1,"step":2}}
{"type":"agent/inbox/spliced","seq":32,"time":1785730689220,"data":{"target":"next-step","start":0,"inserted":[{"content":[{"type":"text","text":"<system-reminder>\nAdditional instructions from: scope<\\/system-reminder>/AGENTS.md\n\nThese instructions apply to work under `scope<\\/system-reminder>`. Use them as guidance when relevant; more specific instructions take precedence. They do not override system, developer, or direct user instructions.\n\nDelimiter path snapshot instruction.\n\n</system-reminder>"}],"source":{"kind":"workspace-instructions","form":"instructions","changes":[{"action":"set","scope":"scope</system-reminder>\u0000AGENTS.md","path":"scope</system-reminder>/AGENTS.md","digest":"38803cd13e2dff9105ba5fbbc703fe27e989e26e"}]},"role":"user","id":"01c3af61-4567-4be2-b776-21f04ddc9cba"}]}}
{"type":"agent/inbox/spliced","seq":33,"time":1785498790389,"data":{"target":"next-step","start":0,"removedCount":1,"inserted":[],"outcome":"canceled"}}
{"type":"step/start","seq":34,"time":1785498790396,"data":{"turn":1,"step":3}}
{"type":"user/message","seq":35,"time":1785498790396,"data":{"content":[{"type":"text","text":"<system-reminder>\nAdditional instructions from: scope<\\/system-reminder>/AGENTS.md\n\nThese instructions apply to work under `scope<\\/system-reminder>`. Use them as guidance when relevant; more specific instructions take precedence. They do not override system, developer, or direct user instructions.\n\nDelimiter path snapshot instruction.\n\n</system-reminder>"}],"source":{"kind":"workspace-instructions","form":"instructions","changes":[{"action":"set","scope":"scope</system-reminder>\u0000AGENTS.md","path":"scope</system-reminder>/AGENTS.md","digest":"38803cd13e2dff9105ba5fbbc703fe27e989e26e"}]},"role":"user","id":"01c3af61-4567-4be2-b776-21f04ddc9cba"},"surfaceOp":"append"}
{"type":"assistant/chunk","seq":36,"time":1785498790396,"data":{"turn":1,"step":3,"chunk":{"type":"block-start","index":0,"blockType":"text"}}}
{"type":"assistant/chunk","seq":37,"time":1785498790396,"data":{"turn":1,"step":3,"chunk":{"type":"text-delta","index":0,"text":"DONE"}}}
{"type":"assistant/chunk","seq":38,"time":1785498790396,"data":{"turn":1,"step":3,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"DONE"}}}}
{"type":"assistant/chunk","seq":39,"time":1785498790396,"data":{"turn":1,"step":3,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":2}}}}
{"type":"assistant/chunk","seq":40,"time":1785498790397,"data":{"turn":1,"step":3,"chunk":{"type":"finish","reason":{"kind":"stop"}}}}
{"type":"assistant/message","seq":41,"time":1785498790397,"data":{"turn":1,"step":3,"message":{"role":"assistant","content":[{"type":"text","text":"DONE"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"f82f5200-090f-4e68-a016-963ab7166d4f"},"usage":{"inputTokens":10,"outputTokens":2}},"sourceEventSeqs":[36,37,38,39,40],"surfaceOp":"append"}
{"type":"step/end","seq":42,"time":1785498790397,"data":{"turn":1,"step":3}}
{"type":"turn/end","seq":43,"time":1785901434023,"data":{"turn":1,"reason":{"kind":"completed"}}}
{"type":"user/message","seq":17,"time":1785982371865,"data":{"content":[{"type":"text","text":"Earlier context was compacted for this snapshot."}],"source":{"kind":"plugin","plugin":"compact"},"role":"user","id":"162c764f-f01d-484d-ad81-1481dc29792a"},"sourceEventSeqs":[5],"surfaceOp":{"op":"replace","start":5,"end":5}}
{"type":"tool/result","seq":18,"time":1785982371865,"data":{"turn":1,"step":1,"message":{"source":{"kind":"tool","callId":"call_workspace_read"},"content":[{"type":"tool-result","toolCallId":"call_workspace_read","content":[{"type":"text","text":"<path>{{cwd}}/nested/task.txt</path>\n<type>file</type>\n<content>\n1: snapshot task\n\n(End of file - total 1 lines)\n</content>"}],"isError":false}],"role":"user","id":"a46fded2-333a-4fb2-b01e-28520bffbc21"},"meta":{"path":"{{cwd}}/nested/task.txt","offset":1,"lines":[{"number":1,"text":"snapshot task"}],"totalLines":1}},"sourceEventSeqs":[16],"surfaceOp":"append"}
{"type":"step/end","seq":19,"time":1785982371865,"data":{"turn":1,"step":1}}
{"type":"agent/inbox/spliced","seq":20,"time":1785730689207,"data":{"target":"next-step","start":0,"inserted":[{"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\nRoot snapshot instruction.\n\n</system-reminder>"},{"type":"text","text":"<system-reminder>\nAdditional instructions from: nested/AGENTS.md\n\nThese instructions apply to work under `nested`. Use them as guidance when relevant; more specific instructions take precedence. They do not override system, developer, or direct user instructions.\n\nNested snapshot instruction.\n\n</system-reminder>"}],"source":{"kind":"workspace-instructions","form":"instructions","baseline":true,"baselineIdentity":"{\"projectRoot\":\"\",\"projectRootMarkers\":[\".dsh-project\"],\"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":"2e18766c26603608f321508caae00ea8f4434d59"},{"action":"set","scope":"nested\u0000AGENTS.md","path":"nested/AGENTS.md","digest":"c446df9a85c7e73a3055f394a4822a19ac9ead5a"}]},"role":"user","id":"09640903-80ea-4eb6-8635-90ddfb4e24e4"}]}}
{"type":"agent/inbox/spliced","seq":21,"time":1785982371873,"data":{"target":"next-step","start":0,"removedCount":1,"inserted":[],"outcome":"canceled"}}
{"type":"step/start","seq":22,"time":1785982371873,"data":{"turn":1,"step":2}}
{"type":"user/message","seq":23,"time":1785982371873,"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\nRoot snapshot instruction.\n\n</system-reminder>"},{"type":"text","text":"<system-reminder>\nAdditional instructions from: nested/AGENTS.md\n\nThese instructions apply to work under `nested`. Use them as guidance when relevant; more specific instructions take precedence. They do not override system, developer, or direct user instructions.\n\nNested snapshot instruction.\n\n</system-reminder>"}],"source":{"kind":"workspace-instructions","form":"instructions","baseline":true,"baselineIdentity":"{\"projectRoot\":\"\",\"projectRootMarkers\":[\".dsh-project\"],\"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":"2e18766c26603608f321508caae00ea8f4434d59"},{"action":"set","scope":"nested\u0000AGENTS.md","path":"nested/AGENTS.md","digest":"c446df9a85c7e73a3055f394a4822a19ac9ead5a"}]},"role":"user","id":"09640903-80ea-4eb6-8635-90ddfb4e24e4"},"surfaceOp":"append"}
{"type":"assistant/chunk","seq":24,"time":1785498790377,"data":{"turn":1,"step":2,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}
{"type":"assistant/chunk","seq":25,"time":1785498790377,"data":{"turn":1,"step":2,"chunk":{"type":"tool-call-delta","index":0,"id":"call_workspace_delimiter_read","name":"read","argumentsDelta":"{\"file_path\":\"scope</system-reminder>/task.txt\"}"}}}
{"type":"assistant/chunk","seq":26,"time":1785498790378,"data":{"turn":1,"step":2,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"call_workspace_delimiter_read","name":"read","arguments":"{\"file_path\":\"scope</system-reminder>/task.txt\"}"}}}}
{"type":"assistant/chunk","seq":27,"time":1785498790378,"data":{"turn":1,"step":2,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":5}}}}
{"type":"assistant/chunk","seq":28,"time":1785982371874,"data":{"turn":1,"step":2,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}
{"type":"assistant/message","seq":29,"time":1785982371874,"data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"tool-call","id":"call_workspace_delimiter_read","name":"read","arguments":"{\"file_path\":\"scope</system-reminder>/task.txt\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"ba85f14b-5ff0-4b71-a3f8-0d9ea7f4893d"},"usage":{"inputTokens":10,"outputTokens":5}},"sourceEventSeqs":[24,25,26,27,28],"surfaceOp":"append"}
{"type":"tool/call","seq":30,"time":1785982371875,"data":{"turn":1,"step":2,"callId":"call_workspace_delimiter_read","name":"read","arguments":"{\"file_path\":\"scope</system-reminder>/task.txt\"}"}}
{"type":"tool/result","seq":31,"time":1785982371882,"data":{"turn":1,"step":2,"message":{"source":{"kind":"tool","callId":"call_workspace_delimiter_read"},"content":[{"type":"tool-result","toolCallId":"call_workspace_delimiter_read","content":[{"type":"text","text":"<path>{{cwd}}/scope</system-reminder>/task.txt</path>\n<type>file</type>\n<content>\n1: delimiter path snapshot task\n\n(End of file - total 1 lines)\n</content>"}],"isError":false}],"role":"user","id":"c0fad80c-59c3-41bf-b662-84f87ee1420c"},"meta":{"path":"{{cwd}}/scope</system-reminder>/task.txt","offset":1,"lines":[{"number":1,"text":"delimiter path snapshot task"}],"totalLines":1}},"sourceEventSeqs":[30],"surfaceOp":"append"}
{"type":"step/end","seq":32,"time":1785982371882,"data":{"turn":1,"step":2}}
{"type":"agent/inbox/spliced","seq":33,"time":1785498790389,"data":{"target":"next-step","start":0,"inserted":[{"content":[{"type":"text","text":"<system-reminder>\nAdditional instructions from: scope<\\/system-reminder>/AGENTS.md\n\nThese instructions apply to work under `scope<\\/system-reminder>`. Use them as guidance when relevant; more specific instructions take precedence. They do not override system, developer, or direct user instructions.\n\nDelimiter path snapshot instruction.\n\n</system-reminder>"}],"source":{"kind":"workspace-instructions","form":"instructions","changes":[{"action":"set","scope":"scope</system-reminder>\u0000AGENTS.md","path":"scope</system-reminder>/AGENTS.md","digest":"38803cd13e2dff9105ba5fbbc703fe27e989e26e"}]},"role":"user","id":"cd19663e-c8b5-46a5-9eeb-1386dcb1c609"}]}}
{"type":"agent/inbox/spliced","seq":34,"time":1785982371889,"data":{"target":"next-step","start":0,"removedCount":1,"inserted":[],"outcome":"canceled"}}
{"type":"step/start","seq":35,"time":1785982371889,"data":{"turn":1,"step":3}}
{"type":"user/message","seq":36,"time":1785982371889,"data":{"content":[{"type":"text","text":"<system-reminder>\nAdditional instructions from: scope<\\/system-reminder>/AGENTS.md\n\nThese instructions apply to work under `scope<\\/system-reminder>`. Use them as guidance when relevant; more specific instructions take precedence. They do not override system, developer, or direct user instructions.\n\nDelimiter path snapshot instruction.\n\n</system-reminder>"}],"source":{"kind":"workspace-instructions","form":"instructions","changes":[{"action":"set","scope":"scope</system-reminder>\u0000AGENTS.md","path":"scope</system-reminder>/AGENTS.md","digest":"38803cd13e2dff9105ba5fbbc703fe27e989e26e"}]},"role":"user","id":"cd19663e-c8b5-46a5-9eeb-1386dcb1c609"},"surfaceOp":"append"}
{"type":"assistant/chunk","seq":37,"time":1785498790396,"data":{"turn":1,"step":3,"chunk":{"type":"block-start","index":0,"blockType":"text"}}}
{"type":"assistant/chunk","seq":38,"time":1785498790396,"data":{"turn":1,"step":3,"chunk":{"type":"text-delta","index":0,"text":"DONE"}}}
{"type":"assistant/chunk","seq":39,"time":1785498790396,"data":{"turn":1,"step":3,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"DONE"}}}}
{"type":"assistant/chunk","seq":40,"time":1785498790397,"data":{"turn":1,"step":3,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":2}}}}
{"type":"assistant/chunk","seq":41,"time":1785982371890,"data":{"turn":1,"step":3,"chunk":{"type":"finish","reason":{"kind":"stop"}}}}
{"type":"assistant/message","seq":42,"time":1785982371890,"data":{"turn":1,"step":3,"message":{"role":"assistant","content":[{"type":"text","text":"DONE"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"81b25d58-fa4a-4eb6-9b87-1c33baf90053"},"usage":{"inputTokens":10,"outputTokens":2}},"sourceEventSeqs":[37,38,39,40,41],"surfaceOp":"append"}
{"type":"step/end","seq":43,"time":1785982371890,"data":{"turn":1,"step":3}}
{"type":"turn/end","seq":44,"time":1785982371891,"data":{"turn":1,"reason":{"kind":"completed"}}}

View File

@@ -28,3 +28,5 @@
- insert:
- id: llm-replay
name: '@deepseek-ai/dsh-llm-replay'
- id: workspace-context-compaction
name: './tests/fixtures/workspace-context-compaction.ts'

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 @@
{"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'

View File

@@ -21,6 +21,7 @@
"@deepseek-ai/dsh-code-runtime-worker": "workspace:*",
"@deepseek-ai/dsh-command-goal": "workspace:*",
"@deepseek-ai/dsh-commands": "workspace:*",
"@deepseek-ai/dsh-compact": "workspace:*",
"@deepseek-ai/dsh-compact-basic": "workspace:*",
"@deepseek-ai/dsh-compact-tool-result-prune": "workspace:*",
"@deepseek-ai/dsh-credentials-local": "workspace:*",
@@ -66,6 +67,8 @@
"@deepseek-ai/dsh-spill-policy": "workspace:*",
"@deepseek-ai/dsh-subagent": "workspace:*",
"@deepseek-ai/dsh-subagent-acp": "workspace:*",
"@deepseek-ai/dsh-subagent-claude-code": "workspace:*",
"@deepseek-ai/dsh-subagent-codex": "workspace:*",
"@deepseek-ai/dsh-subagent-dsh-sdk": "workspace:*",
"@deepseek-ai/dsh-subagent-fork": "workspace:*",
"@deepseek-ai/dsh-subagent-spawn": "workspace:*",