fix(workspace-context): deduplicate baseline on resume

This commit is contained in:
fz
2026-08-04 19:55:27 +08:00
parent 237b80f4e4
commit 207c45d15f
16 changed files with 358 additions and 53 deletions

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,20 @@
{"type":"session","version":0,"id":"{{sessionId}}","createdAt":0,"cwd":"{{cwd}}","delegationDepth":0}
{"type":"turn/start","seq":0,"time":0,"data":{"turn":1,"trigger":{"kind":"message","source":{"kind":"user"}}}}
{"type":"user/message","seq":1,"time":0,"data":{"content":[{"type":"text","text":"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","baseline":true,"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":"turn/start","seq":5,"time":0,"data":{"turn":2,"trigger":{"kind":"message","source":{"kind":"user"}}}}
{"type":"user/message","seq":6,"time":0,"data":{"content":[{"type":"text","text":"Acknowledge the current workspace instruction."}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"}
{"type":"session/title","seq":7,"time":0,"data":{"title":"Remember the workspace instruction.","messageSeqs":[1],"source":{"kind":"fallback"}}}
{"type":"user/message","seq":8,"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","changes":[{"action":"replace","scope":".\u0000AGENTS.md","path":"AGENTS.md","digest":"d8375b516f158718bd3463bc8eb7ed42c011b29f"}]},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"}
{"type":"step/start","seq":9,"time":0,"data":{"turn":2,"step":1}}
{"type":"request/header","seq":10,"time":0,"data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"system":"{{system}}"},"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":"text"}}}
{"type":"assistant/chunk","seq":13,"time":0,"data":{"turn":2,"step":1,"chunk":{"type":"text-delta","index":0,"text":"RESUME_DONE"}}}
{"type":"assistant/chunk","seq":14,"time":0,"data":{"turn":2,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"RESUME_DONE"}}}}
{"type":"assistant/chunk","seq":15,"time":0,"data":{"turn":2,"step":1,"chunk":{"type":"finish","reason":{"kind":"stop"}}}}
{"type":"assistant/message","seq":16,"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":[12,13,14,15],"surfaceOp":"append"}
{"type":"step/end","seq":17,"time":0,"data":{"turn":2,"step":1}}
{"type":"turn/end","seq":18,"time":0,"data":{"turn":2,"reason":{"kind":"completed"}}}

View File

@@ -0,0 +1,151 @@
/**
* 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 { 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 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.'
async function seedVisibleBaseline(root: string, cwd: string): Promise<string> {
const ctx = new Context()
await ctx.plugin(SessionStore)
await ctx.plugin(SessionPersistenceJsonl, { root, compression: 'none' })
const meta: SessionHeader = {
version: SESSION_FORMAT_VERSION,
id: sessionId,
createdAt: 1,
cwd,
delegationDepth: 0,
}
const baseline = renderWorkspaceContext([{
absolutePath: join(cwd, 'AGENTS.md'),
displayPath: 'AGENTS.md',
content: oldInstruction,
}], { maxBytes: 65536 })
const events: SessionEvent[] = [
{ type: 'turn/start', seq: 0, time: 10, data: { turn: 1, trigger: { kind: 'message', source: { kind: 'user' } } } },
{
type: 'user/message',
seq: 1,
time: 11,
data: 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',
baseline: true,
changes: [{
action: 'set',
scope: '.\0AGENTS.md',
path: 'AGENTS.md',
digest: createHash('sha1').update(oldInstruction).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',
success: true,
sessionId,
result: 'RESUME_DONE',
reason: { kind: 'completed' },
})
}, LOADER_SMOKE_TEST_TIMEOUT_MS)
})