refactor: identify and freeze messages at creation

This commit is contained in:
_Kerman
2026-07-28 13:55:59 +08:00
parent c49c0ba497
commit fbf87e660c
345 changed files with 5220 additions and 2901 deletions
@@ -5,9 +5,9 @@ import { describe, expect, it, vi } from 'vitest'
import { Context } from 'cordis'
import Loader from '@cordisjs/plugin-loader'
import * as workspaceContext from '@deepseek-ai/dsh-workspace-context'
import LlmService, { CallId, type Message, type StreamChunk } from '@deepseek-ai/dsh-llm'
import SessionStore, { Session, SessionId, SESSION_FORMAT_VERSION, type SessionEvent, type UserMessageData } from '@deepseek-ai/dsh-session'
import AgentRegistry, { agentEvents, AgentMessageId, type Agent } from '@deepseek-ai/dsh-agent'
import LlmService, { createUserMessage, CallId, type Message, type StreamChunk } from '@deepseek-ai/dsh-llm'
import SessionStore, { Session, SessionId, SESSION_FORMAT_VERSION, type SessionEvent, type UserMessage } from '@deepseek-ai/dsh-session'
import AgentRegistry, { agentEvents, type Agent } from '@deepseek-ai/dsh-agent'
import AgentLoop from '@deepseek-ai/dsh-agent-loop'
import { FileSystem, FsTargetKey, FsVersion } from '@deepseek-ai/dsh-fs'
import type {
@@ -178,13 +178,12 @@ function stubAgent(cwd?: string, seed: SessionEvent[] = []): Agent {
session,
status: 'idle',
acceptsNextStep: false,
followup: () => AgentMessageId('stub'),
steer: () => AgentMessageId('stub'),
followup: () => {},
steer: () => {},
inject(input) {
session.append('user/message', input, { surfaceOp: 'append' })
return AgentMessageId('stub')
},
send: () => AgentMessageId('stub'),
send: () => {},
cancel() {},
whenIdle: () => Promise.resolve(),
}
@@ -201,7 +200,7 @@ function blocksText(blocks: { type: string; text?: string }[] | undefined): stri
return blocks?.map(block => block.type === 'text' ? block.text ?? '' : '').join('\n') ?? ''
}
function workspaceContextOf(result: { additionalContexts?: UserMessageData[] }): UserMessageData | undefined {
function workspaceContextOf(result: { additionalContexts?: UserMessage[] }): UserMessage | undefined {
return result.additionalContexts?.find(context =>
context.source.kind === 'workspace-instructions')
}
@@ -213,23 +212,20 @@ function baselineEvents(agent: Agent): SessionEvent[] {
&& event.data.source.baseline === true)
}
function workspaceChangeContext(scope: string, digest: string): UserMessageData {
return {
function workspaceChangeContext(scope: string, digest: string): UserMessage {
return createUserMessage({
content: [{ type: 'text', text: `instructions for ${scope}` }],
source: {
kind: 'workspace-instructions',
changes: [{ action: 'set', scope, path: `${scope}/AGENTS.md`, digest }],
},
}
})
}
function appendAdditionalContexts(agent: Agent, result: { additionalContexts?: UserMessageData[] }): number | undefined {
function appendAdditionalContexts(agent: Agent, result: { additionalContexts?: UserMessage[] }): number | undefined {
let lastSeq: number | undefined
for (const context of result.additionalContexts ?? []) {
lastSeq = agent.session.append('user/message', {
content: context.content,
source: context.source,
}, { surfaceOp: 'append' }).seq
lastSeq = agent.session.append('user/message', context, { surfaceOp: 'append' }).seq
}
return lastSeq
}
@@ -953,6 +949,7 @@ describe('workspace context request injection', () => {
expect(baselineEvents(agent)[0]).toMatchObject({
type: 'user/message',
data: {
role: 'user',
source: {
kind: 'workspace-instructions',
baseline: true,
@@ -960,6 +957,8 @@ describe('workspace context request injection', () => {
},
},
})
const baseline = baselineEvents(agent)[0]
expect(baseline?.type === 'user/message' && Array.isArray(baseline.data.content)).toBe(true)
expect(composedPrefixes.get(agent)).toHaveLength(1)
expect(derivedText(agent)).toContain('<system-reminder>')
expect(derivedText(agent)).toContain('Instructions from: AGENTS.md')
@@ -1045,10 +1044,10 @@ describe('workspace context request injection', () => {
const baseline = baselineEvents(agent)[0]
expect(baseline).toBeDefined()
agent.session.append('user/message', {
agent.session.append('user/message', createUserMessage({
content: [{ type: 'text', text: 'compacted summary' }],
source: { kind: 'plugin', plugin: 'compact' },
}, {
}), {
surfaceOp: { op: 'replace', start: baseline!.seq, end: baseline!.seq },
sourceEventSeqs: [baseline!.seq],
})
@@ -1135,7 +1134,7 @@ describe('workspace context request injection', () => {
const ctx = new Context()
await mountWorkspaceContext(ctx, { dshHome: home, maxBytes: 65536 })
ctx.on('agent/step', (agent) => {
agent.inject({ content: [{ type: 'text', text: '<system-reminder>Available skills</system-reminder>' }], source: { kind: 'plugin', plugin: 'test-skills' } })
agent.inject(createUserMessage({ content: [{ type: 'text', text: '<system-reminder>Available skills</system-reminder>' }], source: { kind: 'plugin', plugin: 'test-skills' } }))
})
const prefix = await composeBaselinePrefix(ctx, stubAgent(root))
@@ -1831,13 +1830,13 @@ describe('dynamic nested workspace context injection', () => {
},
}))
agent.followup({ content: [{ type: 'text', text: 'read and abort' }], source: { kind: 'user' } })
agent.followup(createUserMessage({ content: [{ type: 'text', text: 'read and abort' }], source: { kind: 'user' } }))
await agent.whenIdle()
expect(agent.session.events.filter(event =>
event.type === 'user/message' && event.data.source.kind !== 'user',
)).toHaveLength(0)
agent.followup({ content: [{ type: 'text', text: 'retry the read' }], source: { kind: 'user' } })
agent.followup(createUserMessage({ content: [{ type: 'text', text: 'retry the read' }], source: { kind: 'user' } }))
await agent.whenIdle()
const contexts = agent.session.events.filter(event => event.type === 'user/message' && event.data.source.kind !== 'user')
@@ -2689,10 +2688,10 @@ describe('dynamic nested workspace context injection', () => {
agent,
})
agent.session.append('user/message', {
agent.session.append('user/message', createUserMessage({
content: [{ type: 'text', text: 'compacted summary' }],
source: { kind: 'plugin', plugin: 'compact' },
}, {
}), {
surfaceOp: { op: 'replace', start: contextSeq, end: contextSeq },
sourceEventSeqs: [contextSeq],
})
@@ -2736,10 +2735,10 @@ describe('dynamic nested workspace context injection', () => {
arguments: { file_path: 'file.txt' },
agent,
})
agent.session.append('user/message', {
agent.session.append('user/message', createUserMessage({
content: [{ type: 'text', text: 'compacted summary' }],
source: { kind: 'plugin', plugin: 'compact' },
}, {
}), {
surfaceOp: { op: 'replace', start: baseline!.seq, end: baseline!.seq },
sourceEventSeqs: [baseline!.seq],
})
@@ -2859,7 +2858,7 @@ describe('dynamic nested workspace context injection', () => {
const ctx = new Context()
await mountFileToolsAndWorkspaceContext(ctx, { dshHome: home, maxBytes: 65536 })
const agent = stubAgent(root)
agent.session.append('user/message', {
agent.session.append('user/message', createUserMessage({
content: [
{ type: 'reasoning', text: 'Additional instructions from: pkg/AGENTS.md' },
{ type: 'text', text: 'Updated instructions from: pkg/AGENTS.md' },
@@ -2873,15 +2872,15 @@ describe('dynamic nested workspace context injection', () => {
{ action: 'set', scope: 'pkg', path: join('pkg', 'AGENTS.md'), digest: 42 },
],
} as never,
}, { surfaceOp: 'append' })
agent.session.append('user/message', {
}), { surfaceOp: 'append' })
agent.session.append('user/message', createUserMessage({
content: [{ type: 'text', text: 'stale metadata version' }],
source: { kind: 'workspace-instructions', changes: 'invalid' } as never,
}, { surfaceOp: 'append' })
agent.session.append('user/message', {
}), { surfaceOp: 'append' })
agent.session.append('user/message', createUserMessage({
content: [{ type: 'text', text: 'foreign plugin context' }],
source: { kind: 'plugin', plugin: 'other' },
}, { surfaceOp: 'append' })
}), { surfaceOp: 'append' })
const result = await ctx.tools.execute({
signal: testToolSignal,
@@ -3025,10 +3024,10 @@ describe('dynamic nested workspace context injection', () => {
lines: [{ number: 1, text: 'downstream replacement' }],
totalLines: 1,
},
additionalContexts: [{
additionalContexts: [createUserMessage({
content: [{ type: 'text' as const, text: 'downstream context' }],
source: { kind: 'plugin' as const, plugin: 'downstream' },
}],
})],
}))
const result = await ctx.tools.execute({
@@ -3228,7 +3227,9 @@ describe('dynamic nested workspace context injection', () => {
ctx.emit('tools/result', stubToolExecution({
signal: testToolSignal,
callId: CallId('contextless-child'), name: 'read', arguments: {}, agent, parent,
}), { ...plainResult, additionalContexts: [{ content: [], source: { kind: 'plugin', plugin: 'workspace-context' } }] })
}), { ...plainResult, additionalContexts: [createUserMessage({
content: [], source: { kind: 'plugin', plugin: 'workspace-context' },
})] })
ctx.emit('tools/result', stubToolExecution({
signal: testToolSignal,
callId: CallId('first-child'), name: 'read', arguments: {}, agent, parent,
@@ -3401,25 +3402,25 @@ describe('workspace context pending state', () => {
path: join('pkg', 'AGENTS.md'), version: FsVersion('v1'), digest: 'one', trimmedDigest: 'one',
}]]))
const unrelated = agent.session.append('user/message', {
const unrelated = agent.session.append('user/message', createUserMessage({
content: [], source: { kind: 'plugin', plugin: 'other' },
}, { surfaceOp: 'append' })
}), { surfaceOp: 'append' })
observeInstructionSessionEvent(agent.session, unrelated, pending, versions)
expect(pending.get(agent.session)?.has('pkg')).toBe(true)
const otherContext = workspaceChangeContext('other', 'other')
const otherWorkspaceEvent = agent.session.append('user/message', {
const otherWorkspaceEvent = agent.session.append('user/message', createUserMessage({
content: otherContext.content,
source: otherContext.source,
}, { surfaceOp: 'append' })
}), { surfaceOp: 'append' })
observeInstructionSessionEvent(agent.session, otherWorkspaceEvent, pending, versions)
expect(pending.get(agent.session)?.has('pkg')).toBe(true)
const context = workspaceChangeContext('pkg', 'one')
const confirmed = agent.session.append('user/message', {
const confirmed = agent.session.append('user/message', createUserMessage({
content: context.content,
source: context.source,
}, { surfaceOp: 'append' })
}), { surfaceOp: 'append' })
observeInstructionSessionEvent(agent.session, confirmed, pending, versions)
expect(pending.has(agent.session)).toBe(false)
@@ -3473,15 +3474,15 @@ describe('workspace context pending state', () => {
rollbackPendingInstructionChanges(agent, [{
action: 'set', scope: 'missing', path: 'missing/AGENTS.md', digest: 'none',
}], pending)
expect(commitPendingInstructionContexts(agent, [{
expect(commitPendingInstructionContexts(agent, [createUserMessage({
content: [], source: { kind: 'plugin', plugin: 'workspace-context' },
}], pending)).toEqual([])
})], pending)).toEqual([])
// A workspace-instructions source whose change list filters to nothing
// must not mint per-session pending state.
expect(commitPendingInstructionContexts(agent, [{
expect(commitPendingInstructionContexts(agent, [createUserMessage({
content: [],
source: { kind: 'workspace-instructions', changes: [] },
}], pending)).toEqual([])
})], pending)).toEqual([])
expect(pending.has(agent.session)).toBe(false)
const committed = commitPendingInstructionContexts(agent, [