Merge remote-tracking branch 'origin/master' into feat/todo-multi-in-progress
# Conflicts: # apps/cli/config/base.cordis.yml # packages/client/ui-conversation/README.i18n.yaml
This commit is contained in:
@@ -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` |
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'
|
||||
@@ -302,7 +302,7 @@ describe('Code Mode typed values: keyless real-worker contracts', () => {
|
||||
|
||||
function waitForIdle(harness: Context, agent: Agent): Promise<void> {
|
||||
return new Promise((resolve) => {
|
||||
const dispose = harness.on('agent/status', (subject, status) => {
|
||||
const dispose = harness.on('agent/status', ({ agent: subject, status }) => {
|
||||
if (subject === agent && status === 'idle') {
|
||||
dispose()
|
||||
resolve()
|
||||
|
||||
@@ -59,7 +59,7 @@ export const inject = ['llm']
|
||||
/** Register the keyless `cli-mock` adapter. */
|
||||
export function apply(ctx: Context): void {
|
||||
ctx.llm.registerAdapter(['cli-mock'], new CliMockAdapter())
|
||||
ctx.on('agent/request', async (_agent, _turn, step, _signal, next) => {
|
||||
ctx.on('agent/request', async ({ step }, next) => {
|
||||
const config = await next()
|
||||
return step === 2 ? { ...config, reasoningEffort: OFF } : config
|
||||
})
|
||||
|
||||
@@ -7,7 +7,7 @@ export const name = 'seed-goal'
|
||||
export const inject = ['goals']
|
||||
|
||||
export function apply(ctx: Context): void {
|
||||
ctx.on('agent/pre-step', (agent, _messages, _context, next) => {
|
||||
ctx.on('agent/pre-step', ({ agent }, next) => {
|
||||
if (ctx.goals.get(agent) === undefined) {
|
||||
ctx.goals.create(agent, {
|
||||
objective: 'Prove the composed goal survives in the session log',
|
||||
|
||||
26
examples/headless-agent/tests/fixtures/subagent-diagnostic-agent.ts
vendored
Normal file
26
examples/headless-agent/tests/fixtures/subagent-diagnostic-agent.ts
vendored
Normal 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')
|
||||
}
|
||||
25
examples/headless-agent/tests/fixtures/workspace-context-resume-agent.ts
vendored
Normal file
25
examples/headless-agent/tests/fixtures/workspace-context-resume-agent.ts
vendored
Normal 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')
|
||||
}
|
||||
@@ -85,7 +85,7 @@ export async function codingHarness(workdir: string, options: CodingHarnessOptio
|
||||
|
||||
export function waitForIdle(ctx: Context, agent: Agent): Promise<void> {
|
||||
return new Promise((resolve) => {
|
||||
const dispose = ctx.on('agent/status', (subject, status) => {
|
||||
const dispose = ctx.on('agent/status', ({ agent: subject, status }) => {
|
||||
if (subject === agent && status === 'idle') {
|
||||
dispose()
|
||||
resolve()
|
||||
|
||||
@@ -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"}}}
|
||||
@@ -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"}}]}]
|
||||
119
examples/headless-agent/tests/subagent-diagnostic.snapshot.ts
Normal file
119
examples/headless-agent/tests/subagent-diagnostic.snapshot.ts
Normal 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)
|
||||
})
|
||||
@@ -0,0 +1 @@
|
||||
{"type":"session","version":0,"id":"workspace-context-resume-replay","createdAt":1,"delegationDepth":0}
|
||||
@@ -0,0 +1,11 @@
|
||||
[
|
||||
{
|
||||
"kind": "chunks",
|
||||
"chunks": [
|
||||
{ "type": "block-start", "index": 0, "blockType": "text" },
|
||||
{ "type": "text-delta", "index": 0, "text": "RESUME_DONE" },
|
||||
{ "type": "block-end", "index": 0, "block": { "type": "text", "text": "RESUME_DONE" } },
|
||||
{ "type": "finish", "reason": { "kind": "stop" } }
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -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"}}}
|
||||
@@ -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"}}}
|
||||
@@ -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)
|
||||
})
|
||||
@@ -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'
|
||||
Reference in New Issue
Block a user