Merge branch 'codex/fix-compact-agents-reinjection' into codex/fix-resume-baseline-dedup
# Conflicts: # examples/acp-agent/tests/snapshots/code-mode-workspace-context/session.jsonl # examples/acp-agent/tests/snapshots/workspace-context/session.jsonl
This commit is contained in:
@@ -199,6 +199,7 @@ export class SessionReferenceService extends Service {
|
||||
const prompt = renderPrompt(rendered.map(source => source.data))
|
||||
const source: SessionReferenceSource = {
|
||||
kind: 'session-reference',
|
||||
form: 'recall',
|
||||
version: 1,
|
||||
references: rendered.map((source, index) => ({
|
||||
sessionId: source.data.sessionId,
|
||||
|
||||
@@ -6,6 +6,8 @@ import type { SessionId, UserMessage } from '@deepseek-ai/dsh-session'
|
||||
/** Durable provenance for one prepared cross-session context. */
|
||||
export interface SessionReferenceSource {
|
||||
kind: 'session-reference'
|
||||
/** Material lifted out of another session's log (`recall` context form). */
|
||||
form: 'recall'
|
||||
version: 1
|
||||
references: {
|
||||
sessionId: string
|
||||
|
||||
@@ -157,9 +157,7 @@ export function apply(ctx: Context, config: Config): void {
|
||||
const resolvedTimeZone = formatter.resolvedOptions().timeZone
|
||||
|
||||
ctx.on('agent/pre-step', async (
|
||||
agent: Agent,
|
||||
_messages,
|
||||
{ turn, step, signal },
|
||||
{ agent, turn, step, signal },
|
||||
next,
|
||||
): Promise<PreStepDecision> => {
|
||||
const decision = await next()
|
||||
@@ -174,13 +172,14 @@ export function apply(ctx: Context, config: Config): void {
|
||||
const previous = step === 1
|
||||
? precedingMessageTime(agent)
|
||||
: precedingStepContextTime(agent, turn)
|
||||
const text = renderText(now, turn, step, previous, formatter, resolvedTimeZone)
|
||||
return {
|
||||
kind: 'enter',
|
||||
messages: [
|
||||
...decision.messages,
|
||||
createUserMessage({
|
||||
content: [{ type: 'text', text: renderText(now, turn, step, previous, formatter, resolvedTimeZone) }],
|
||||
source: { kind: 'plugin', plugin: name },
|
||||
content: [{ type: 'text', text }],
|
||||
source: { kind: 'plugin', plugin: name, form: 'snapshot', sections: [{ name, text }] },
|
||||
}),
|
||||
],
|
||||
}
|
||||
|
||||
@@ -56,7 +56,14 @@ describe('time-context through a real headless cordis.yml', () => {
|
||||
for (let index = 0; index < contexts.length; index += 1) {
|
||||
expect(contexts[index]!.seq).toBeGreaterThan(starts[index]!.seq)
|
||||
expect(contexts[index]!.surfaceOp).toBe('append')
|
||||
expect(contexts[index]!.data.source).toEqual({ kind: 'plugin', plugin: 'time-context' })
|
||||
// `snapshot` form: one named contribution whose text is exactly what the
|
||||
// model read, so a consumer attributes it without re-splitting prose.
|
||||
expect(contexts[index]!.data.source).toMatchObject({
|
||||
kind: 'plugin',
|
||||
plugin: 'time-context',
|
||||
form: 'snapshot',
|
||||
sections: [{ name: 'time-context' }],
|
||||
})
|
||||
}
|
||||
const contextText = contexts.map(event => event.data.content
|
||||
.filter(block => block.type === 'text')
|
||||
|
||||
@@ -82,8 +82,7 @@ async function fire(
|
||||
): Promise<void> {
|
||||
const decision = await agentEvents(ctx, agent).waterfall(
|
||||
'agent/pre-step',
|
||||
[],
|
||||
{ turn, step, signal },
|
||||
{ messages: [], turn, step, signal },
|
||||
() => Promise.resolve({ kind: 'enter' as const, messages: [] }),
|
||||
)
|
||||
if (decision.kind === 'enter') {
|
||||
@@ -161,7 +160,19 @@ describe('durable step context', () => {
|
||||
const event = session.events.at(-1)
|
||||
expect(event?.type).toBe('user/message')
|
||||
if (event?.type !== 'user/message') throw new Error('missing time context')
|
||||
expect(event.data.source).toEqual({ kind: 'plugin', plugin: 'time-context' })
|
||||
// The reading is a `snapshot`-form context: one named contribution whose
|
||||
// text is exactly what the model read, so a consumer attributes it without
|
||||
// re-splitting prose.
|
||||
expect(event.data.source).toEqual({
|
||||
kind: 'plugin',
|
||||
plugin: 'time-context',
|
||||
form: 'snapshot',
|
||||
sections: [{
|
||||
name: 'time-context',
|
||||
text: 'Time sampled while preparing turn 1, step 1: 2026-07-15T09:01:01+08:00[Asia/Shanghai]\n'
|
||||
+ 'Elapsed since the preceding model-visible message: 1d 1h 1m 1s.',
|
||||
}],
|
||||
})
|
||||
expect(event.surfaceOp).toBe('append')
|
||||
})
|
||||
|
||||
@@ -366,7 +377,7 @@ describe('real agent-loop request history', () => {
|
||||
] as const)('does not commit a preparation reading when a downstream pre-step listener %s', async (mode) => {
|
||||
const adapter = new ScriptedAdapter([textResponse('unused')])
|
||||
const ctx = await loopHarness(adapter)
|
||||
ctx.on('agent/pre-step', (subject, _messages, _context, next) => {
|
||||
ctx.on('agent/pre-step', ({ agent: subject }, next) => {
|
||||
if (mode === 'throws') throw new Error('later pre-step failure')
|
||||
subject.cancel({ kind: 'user' })
|
||||
return next()
|
||||
|
||||
@@ -216,9 +216,7 @@ export function apply(ctx: Context, config: Config): void {
|
||||
validateRefreshInterval(refreshIntervalMs)
|
||||
|
||||
ctx.on('agent/pre-step', async (
|
||||
agent: Agent,
|
||||
_messages,
|
||||
{ turn, step, signal },
|
||||
{ agent, turn, step, signal },
|
||||
next,
|
||||
): Promise<PreStepDecision> => {
|
||||
const decision = await next()
|
||||
@@ -234,12 +232,13 @@ export function apply(ctx: Context, config: Config): void {
|
||||
if (location === undefined) return decision
|
||||
const state = renderState(location)
|
||||
if (previous !== undefined && previous.state === state) return decision
|
||||
const text = renderReading(location, turn)
|
||||
return {
|
||||
kind: 'enter',
|
||||
messages: [
|
||||
createUserMessage({
|
||||
content: [{ type: 'text', text: renderReading(location, turn) }],
|
||||
source: { kind: 'plugin', plugin: name },
|
||||
content: [{ type: 'text', text }],
|
||||
source: { kind: 'plugin', plugin: name, form: 'snapshot', sections: [{ name, text }] },
|
||||
}),
|
||||
...decision.messages,
|
||||
],
|
||||
|
||||
@@ -138,8 +138,7 @@ async function fire(
|
||||
): Promise<void> {
|
||||
const decision = await agentEvents(ctx, agent).waterfall(
|
||||
'agent/pre-step',
|
||||
[],
|
||||
{ turn, step, signal },
|
||||
{ messages: [], turn, step, signal },
|
||||
() => Promise.resolve({ kind: 'enter' as const, messages: [] }),
|
||||
)
|
||||
if (decision.kind === 'enter') {
|
||||
@@ -170,7 +169,14 @@ describe('tmux-context injection', () => {
|
||||
])
|
||||
const event = session.events.at(-1)
|
||||
if (event?.type !== 'user/message') throw new Error('missing tmux context')
|
||||
expect(event.data.source).toEqual({ kind: 'plugin', plugin: 'tmux-context' })
|
||||
// `snapshot` form: one named contribution carrying exactly the reading the
|
||||
// model saw, so a consumer attributes it without re-splitting prose.
|
||||
expect(event.data.source).toMatchObject({
|
||||
kind: 'plugin',
|
||||
plugin: 'tmux-context',
|
||||
form: 'snapshot',
|
||||
sections: [{ name: 'tmux-context' }],
|
||||
})
|
||||
expect(event.surfaceOp).toBe('append')
|
||||
})
|
||||
|
||||
|
||||
@@ -168,6 +168,7 @@ export function apply(ctx: Context, config: Config): void {
|
||||
content: baselineContent,
|
||||
source: {
|
||||
kind: 'workspace-instructions',
|
||||
form: 'instructions',
|
||||
baseline: true,
|
||||
baselineIdentity: identity,
|
||||
changes: baselineChanges,
|
||||
@@ -205,6 +206,7 @@ export function apply(ctx: Context, config: Config): void {
|
||||
content,
|
||||
source: {
|
||||
kind: 'workspace-instructions',
|
||||
form: 'instructions',
|
||||
...desiredBaseline ? { baseline: true } : {},
|
||||
...desiredBaseline ? { baselineIdentity: identity } : {},
|
||||
changes,
|
||||
@@ -271,9 +273,7 @@ export function apply(ctx: Context, config: Config): void {
|
||||
}
|
||||
|
||||
ctx.on('agent/pre-step', async (
|
||||
agent: Agent,
|
||||
messages,
|
||||
{ step, signal },
|
||||
{ agent, messages, step, signal },
|
||||
next,
|
||||
): Promise<PreStepDecision> => {
|
||||
const decision = await next()
|
||||
|
||||
@@ -36,6 +36,8 @@ export const name = 'workspace-context'
|
||||
/** Durable provenance and reconciliation facts for one workspace context. */
|
||||
export interface WorkspaceInstructionSource {
|
||||
kind: 'workspace-instructions'
|
||||
/** Every workspace context carries instructions read out of a file (the `instructions` context form). */
|
||||
form: 'instructions'
|
||||
/** Marks the complete startup/resume baseline rather than a later delta. */
|
||||
baseline?: true
|
||||
/** Discovery, precedence, and budget identity used to validate a resumed baseline. */
|
||||
@@ -79,7 +81,7 @@ export interface ReconciledInstructionContext {
|
||||
function workspaceContextHook(text: string, changes: WorkspaceInstructionChange[]): UserMessage {
|
||||
return createUserMessage({
|
||||
content: [{ type: 'text', text }],
|
||||
source: { kind: 'workspace-instructions', changes },
|
||||
source: { kind: 'workspace-instructions', form: 'instructions', changes },
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ async function harness(): Promise<{ ctx: Context; agent: Agent }> {
|
||||
|
||||
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()
|
||||
|
||||
@@ -209,8 +209,7 @@ async function workspaceContextOf(agent: Agent): Promise<UserMessage> {
|
||||
|
||||
async function syncWorkspaceContext(ctx: Context, agent: Agent): Promise<void> {
|
||||
await agentEvents(ctx, agent).waterfall(
|
||||
'agent/pre-step', [],
|
||||
{ turn: 1, step: 1, signal: testToolSignal },
|
||||
'agent/pre-step', { messages: [], turn: 1, step: 1, signal: testToolSignal },
|
||||
async () => ({ kind: 'enter' as const, messages: [] }),
|
||||
)
|
||||
}
|
||||
@@ -245,15 +244,13 @@ async function composeBaselinePrefix(ctx: Context, agent: Agent): Promise<Messag
|
||||
const signal = AbortSignal.timeout(1000)
|
||||
await agentEvents(ctx, agent).waterfall(
|
||||
'agent/pre-step',
|
||||
[],
|
||||
{ turn: 1, step: 1, signal },
|
||||
{ messages: [], turn: 1, step: 1, signal },
|
||||
() => Promise.resolve({ kind: 'enter' as const, messages: [] }),
|
||||
)
|
||||
const claimed = agent.inbox.claim('next-step', 1)
|
||||
const decision = await agentEvents(ctx, agent).waterfall(
|
||||
'agent/pre-step',
|
||||
claimed,
|
||||
{ turn: 1, step: 2, signal },
|
||||
{ messages: claimed, turn: 1, step: 2, signal },
|
||||
() => Promise.resolve({ kind: 'enter' as const, messages: claimed }),
|
||||
)
|
||||
const entered = decision.kind === 'enter' ? decision.messages : []
|
||||
@@ -916,6 +913,7 @@ describe('workspace context request injection', () => {
|
||||
role: 'user',
|
||||
source: {
|
||||
kind: 'workspace-instructions',
|
||||
form: 'instructions',
|
||||
baseline: true,
|
||||
changes: [{ action: 'set', scope: sk('.', 'AGENTS.md'), path: 'AGENTS.md' }],
|
||||
},
|
||||
@@ -1229,8 +1227,7 @@ describe('workspace context request injection', () => {
|
||||
const original = stubAgent(root)
|
||||
await agentEvents(ctx, original).waterfall(
|
||||
'agent/pre-step',
|
||||
[],
|
||||
{ turn: 1, step: 1, signal: AbortSignal.timeout(1000) },
|
||||
{ messages: [], turn: 1, step: 1, signal: AbortSignal.timeout(1000) },
|
||||
() => Promise.resolve({ kind: 'enter' as const, messages: [] }),
|
||||
)
|
||||
const inserted = original.inbox.nextStep[0]
|
||||
@@ -1239,12 +1236,11 @@ describe('workspace context request injection', () => {
|
||||
await fiber.dispose()
|
||||
await ctx.plugin(workspaceContext, { dshHome: home, maxBytes: 65536 })
|
||||
const resumed = stubAgent(root, [...original.session.events])
|
||||
agentEvents(ctx, resumed).emit('agent/session-start', 'resume')
|
||||
agentEvents(ctx, resumed).emit('agent/session-start', { source: 'resume' })
|
||||
const claimed = resumed.inbox.claim('next-step', 1)
|
||||
const decision = await agentEvents(ctx, resumed).waterfall(
|
||||
'agent/pre-step',
|
||||
claimed,
|
||||
{ turn: 1, step: 1, signal: AbortSignal.timeout(1000) },
|
||||
{ messages: claimed, turn: 1, step: 1, signal: AbortSignal.timeout(1000) },
|
||||
() => Promise.resolve({ kind: 'enter' as const, messages: claimed }),
|
||||
)
|
||||
if (decision.kind !== 'enter') throw new Error('recovered baseline was rejected')
|
||||
@@ -1276,8 +1272,7 @@ describe('workspace context request injection', () => {
|
||||
const original = stubAgent(root)
|
||||
await agentEvents(ctx, original).waterfall(
|
||||
'agent/pre-step',
|
||||
[],
|
||||
{ turn: 1, step: 1, signal: AbortSignal.timeout(1000) },
|
||||
{ messages: [], turn: 1, step: 1, signal: AbortSignal.timeout(1000) },
|
||||
() => Promise.resolve({ kind: 'enter' as const, messages: [] }),
|
||||
)
|
||||
const stale = original.inbox.nextStep[0]
|
||||
@@ -1287,12 +1282,11 @@ describe('workspace context request injection', () => {
|
||||
await fiber.dispose()
|
||||
await ctx.plugin(workspaceContext, { dshHome: home, maxBytes: 65536 })
|
||||
const resumed = stubAgent(root, [...original.session.events])
|
||||
agentEvents(ctx, resumed).emit('agent/session-start', 'resume')
|
||||
agentEvents(ctx, resumed).emit('agent/session-start', { source: 'resume' })
|
||||
const staleClaim = resumed.inbox.claim('next-step', 1)
|
||||
const staleDecision = await agentEvents(ctx, resumed).waterfall(
|
||||
'agent/pre-step',
|
||||
staleClaim,
|
||||
{ turn: 1, step: 1, signal: AbortSignal.timeout(1000) },
|
||||
{ messages: staleClaim, turn: 1, step: 1, signal: AbortSignal.timeout(1000) },
|
||||
() => Promise.resolve({ kind: 'enter' as const, messages: staleClaim }),
|
||||
)
|
||||
|
||||
@@ -1331,8 +1325,7 @@ describe('workspace context request injection', () => {
|
||||
const original = stubAgent(root)
|
||||
await agentEvents(originalCtx, original).waterfall(
|
||||
'agent/pre-step',
|
||||
[],
|
||||
{ turn: 1, step: 1, signal: AbortSignal.timeout(1000) },
|
||||
{ messages: [], turn: 1, step: 1, signal: AbortSignal.timeout(1000) },
|
||||
() => Promise.resolve({ kind: 'enter' as const, messages: [] }),
|
||||
)
|
||||
const stale = original.inbox.nextStep[0]
|
||||
@@ -1342,12 +1335,11 @@ describe('workspace context request injection', () => {
|
||||
if (provideFs) await resumedCtx.plugin(LocalFileSystem, { cwd: '/' })
|
||||
await resumedCtx.plugin(workspaceContext, { dshHome: home, maxBytes })
|
||||
const resumed = stubAgent(root, [...original.session.events])
|
||||
agentEvents(resumedCtx, resumed).emit('agent/session-start', 'resume')
|
||||
agentEvents(resumedCtx, resumed).emit('agent/session-start', { source: 'resume' })
|
||||
const claimed = resumed.inbox.claim('next-step', 1)
|
||||
const decision = await agentEvents(resumedCtx, resumed).waterfall(
|
||||
'agent/pre-step',
|
||||
claimed,
|
||||
{ turn: 1, step: 1, signal: AbortSignal.timeout(1000) },
|
||||
{ messages: claimed, turn: 1, step: 1, signal: AbortSignal.timeout(1000) },
|
||||
() => Promise.resolve({ kind: 'enter' as const, messages: claimed }),
|
||||
)
|
||||
|
||||
@@ -1374,6 +1366,7 @@ describe('workspace context request injection', () => {
|
||||
content: [{ type: 'text', text: 'stale nested instructions' }],
|
||||
source: {
|
||||
kind: 'workspace-instructions',
|
||||
form: 'instructions',
|
||||
changes: [{ action: 'set', scope: sk('pkg', 'AGENTS.md'), path: join('pkg', 'AGENTS.md'), digest: 'stale' }],
|
||||
},
|
||||
}), {
|
||||
@@ -1407,6 +1400,7 @@ describe('workspace context request injection', () => {
|
||||
content: [{ type: 'text', text: 'stale nested instructions' }],
|
||||
source: {
|
||||
kind: 'workspace-instructions',
|
||||
form: 'instructions',
|
||||
changes: [{ action: 'set', scope: sk('pkg', 'AGENTS.md'), path: join('pkg', 'AGENTS.md'), digest: 'stale' }],
|
||||
},
|
||||
}), {
|
||||
@@ -1449,8 +1443,7 @@ describe('workspace context request injection', () => {
|
||||
|
||||
const decision = await agentEvents(ctx, agent).waterfall(
|
||||
'agent/pre-step',
|
||||
[prompt],
|
||||
{ turn: 1, step: 1, signal: AbortSignal.timeout(1000) },
|
||||
{ messages: [prompt], turn: 1, step: 1, signal: AbortSignal.timeout(1000) },
|
||||
() => Promise.resolve(downstream),
|
||||
)
|
||||
|
||||
@@ -1507,8 +1500,7 @@ describe('workspace context request injection', () => {
|
||||
|
||||
const decision = await agentEvents(ctx, agent).waterfall(
|
||||
'agent/pre-step',
|
||||
[],
|
||||
{ turn: 1, step: 1, signal: AbortSignal.timeout(1000) },
|
||||
{ messages: [], turn: 1, step: 1, signal: AbortSignal.timeout(1000) },
|
||||
() => Promise.resolve(downstream),
|
||||
)
|
||||
|
||||
@@ -1619,8 +1611,7 @@ describe('workspace context request injection', () => {
|
||||
|
||||
const decision = await agentEvents(ctx, agent).waterfall(
|
||||
'agent/pre-step',
|
||||
[prompt],
|
||||
{ turn: 2, step: 1, signal: AbortSignal.timeout(1000) },
|
||||
{ messages: [prompt], turn: 2, step: 1, signal: AbortSignal.timeout(1000) },
|
||||
() => Promise.resolve({ kind: 'enter' as const, messages: [prompt] }),
|
||||
)
|
||||
|
||||
@@ -1653,7 +1644,7 @@ describe('workspace context request injection', () => {
|
||||
const resumed = stubAgent(root, [...original.session.events])
|
||||
|
||||
// Resume announces its lifecycle start before the first step.
|
||||
agentEvents(ctx, resumed).emit('agent/session-start', 'resume')
|
||||
agentEvents(ctx, resumed).emit('agent/session-start', { source: 'resume' })
|
||||
await composeBaselinePrefix(ctx, resumed)
|
||||
|
||||
const baselines = baselineEvents(resumed)
|
||||
@@ -1704,7 +1695,7 @@ describe('workspace context request injection', () => {
|
||||
await write(join(root, 'AGENTS.md'), 'repo rule')
|
||||
const ctx = new Context()
|
||||
await mountWorkspaceContext(ctx, { dshHome: home, maxBytes: 65536 })
|
||||
ctx.on('agent/pre-step', async (_agent, _messages, _context, next) => {
|
||||
ctx.on('agent/pre-step', async (_payload, next) => {
|
||||
const decision = await next()
|
||||
if (decision.kind === 'reject') return decision
|
||||
return {
|
||||
@@ -1978,8 +1969,7 @@ describe('workspace context request injection', () => {
|
||||
const reason = new Error('cancel prefix')
|
||||
const pending = agentEvents(ctx, stubAgent(root)).waterfall(
|
||||
'agent/pre-step',
|
||||
[],
|
||||
{ turn: 1, step: 1, signal: controller.signal },
|
||||
{ messages: [], turn: 1, step: 1, signal: controller.signal },
|
||||
() => Promise.resolve({ kind: 'enter' as const, messages: [] }),
|
||||
)
|
||||
|
||||
@@ -2521,7 +2511,7 @@ describe('dynamic nested workspace context injection', () => {
|
||||
expect(result.isError).toBe(false)
|
||||
expect(((await syncedWorkspaceContext(ctx, agent))).source).toMatchObject({ kind: 'workspace-instructions' })
|
||||
const queuedSource = ((await syncedWorkspaceContext(ctx, agent))).source
|
||||
expect(queuedSource).toMatchObject({ kind: 'workspace-instructions' })
|
||||
expect(queuedSource).toMatchObject({ kind: 'workspace-instructions', form: 'instructions' })
|
||||
expect(queuedSource.kind === 'workspace-instructions' && queuedSource.changes.some(change =>
|
||||
change.action === 'set'
|
||||
&& change.scope === sk('pkg', 'AGENTS.md')
|
||||
@@ -2864,6 +2854,7 @@ describe('dynamic nested workspace context injection', () => {
|
||||
|
||||
expect(((await syncedWorkspaceContext(ctx, agent))).source).toMatchObject({
|
||||
kind: 'workspace-instructions',
|
||||
form: 'instructions',
|
||||
changes: [{ action: 'replace', scope: sk('pkg', 'AGENTS.md'), path: join('pkg', 'AGENTS.md') }],
|
||||
})
|
||||
expect(blocksText(((await syncedWorkspaceContext(ctx, agent))).content)).toBe([
|
||||
@@ -2973,7 +2964,7 @@ describe('dynamic nested workspace context injection', () => {
|
||||
if (previous === undefined) throw new Error('missing AGENTS.md baseline state')
|
||||
const authoritative = createUserMessage({
|
||||
content: [{ type: 'text', text: 'nested rule' }],
|
||||
source: { kind: 'workspace-instructions', changes: [previous] },
|
||||
source: { kind: 'workspace-instructions', form: 'instructions', changes: [previous] },
|
||||
})
|
||||
if (authority === 'visible') {
|
||||
agent.session.append('user/message', authoritative, { surfaceOp: 'append' })
|
||||
@@ -2999,6 +2990,7 @@ describe('dynamic nested workspace context injection', () => {
|
||||
content: [{ type: 'text', text: 'pending baseline duplicate' }],
|
||||
source: {
|
||||
kind: 'workspace-instructions',
|
||||
form: 'instructions',
|
||||
changes: [{ action: 'set', scope: sk('.', 'AGENTS.md'), path: 'AGENTS.md' }],
|
||||
},
|
||||
})],
|
||||
@@ -3042,7 +3034,7 @@ describe('dynamic nested workspace context injection', () => {
|
||||
if (previous === undefined) throw new Error('missing AGENTS.md baseline state')
|
||||
const authoritative = createUserMessage({
|
||||
content: [{ type: 'text', text: 'repo rule' }],
|
||||
source: { kind: 'workspace-instructions', changes: [previous] },
|
||||
source: { kind: 'workspace-instructions', form: 'instructions', changes: [previous] },
|
||||
})
|
||||
agent.session.append('user/message', authoritative, { surfaceOp: 'append' })
|
||||
const resolved = resolveConfig({ dshHome: home, maxBytes: 65536, localInstructionFileCandidates: [] })
|
||||
@@ -3239,6 +3231,7 @@ describe('dynamic nested workspace context injection', () => {
|
||||
|
||||
expect(((await syncedWorkspaceContext(ctx, agent))).source).toMatchObject({
|
||||
kind: 'workspace-instructions',
|
||||
form: 'instructions',
|
||||
changes: [{ action: 'remove', scope: sk('pkg', 'AGENTS.md'), path: join('pkg', 'AGENTS.md') }],
|
||||
})
|
||||
expect(blocksText(((await syncedWorkspaceContext(ctx, agent))).content)).toBe([
|
||||
@@ -3641,6 +3634,7 @@ describe('dynamic nested workspace context injection', () => {
|
||||
],
|
||||
source: {
|
||||
kind: 'workspace-instructions',
|
||||
form: 'instructions',
|
||||
changes: [
|
||||
null,
|
||||
{ action: 'unknown', scope: 'pkg', path: join('pkg', 'AGENTS.md') },
|
||||
@@ -3794,6 +3788,7 @@ describe('dynamic nested workspace context injection', () => {
|
||||
expect(((await syncedWorkspaceContext(ctx, agent))).source).toMatchObject({ kind: 'workspace-instructions' })
|
||||
expect(((await syncedWorkspaceContext(ctx, agent))).source).toMatchObject({
|
||||
kind: 'workspace-instructions',
|
||||
form: 'instructions',
|
||||
changes: [{ action: 'set', scope: sk('pkg', 'AGENTS.md'), path: join('pkg', 'AGENTS.md') }],
|
||||
})
|
||||
expect(blocksText(((await syncedWorkspaceContext(ctx, agent))).content)).toContain('nested package rule')
|
||||
@@ -4236,8 +4231,7 @@ describe('workspace context inbox synchronization', () => {
|
||||
controller.abort(new Error('abort pre-step reconciliation'))
|
||||
|
||||
await expect(agentEvents(ctx, agent).waterfall(
|
||||
'agent/pre-step', [],
|
||||
{ turn: 1, step: 1, signal: controller.signal },
|
||||
'agent/pre-step', { messages: [], turn: 1, step: 1, signal: controller.signal },
|
||||
async () => ({ kind: 'enter' as const, messages: [] }),
|
||||
)).rejects.toThrow('abort pre-step reconciliation')
|
||||
|
||||
@@ -4347,8 +4341,7 @@ describe('workspace context inbox synchronization', () => {
|
||||
const downstream = { kind: 'enter' as const, messages: claimed }
|
||||
|
||||
const decision = await agentEvents(ctx, agent).waterfall(
|
||||
'agent/pre-step', claimed,
|
||||
{ turn: 1, step: 1, signal: testToolSignal },
|
||||
'agent/pre-step', { messages: claimed, turn: 1, step: 1, signal: testToolSignal },
|
||||
async () => downstream,
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user