fix(workspace-context): commit projections after tool steps
This commit is contained in:
@@ -187,9 +187,11 @@ function stubAgent(cwd?: string, seed: SessionEvent[] = []): Agent {
|
||||
}
|
||||
}
|
||||
|
||||
function stubToolExecution(input: Omit<ToolExecution, 'token'>): ToolExecution {
|
||||
function stubToolExecution(
|
||||
input: Omit<ToolExecution, 'token'> & { token?: ToolExecutionToken },
|
||||
): ToolExecution {
|
||||
return {
|
||||
token: Symbol('workspace-context-test-execution') as ToolExecutionToken,
|
||||
token: input.token ?? Symbol('workspace-context-test-execution') as ToolExecutionToken,
|
||||
...input,
|
||||
}
|
||||
}
|
||||
@@ -3948,6 +3950,106 @@ describe('dynamic nested workspace context injection', () => {
|
||||
}
|
||||
})
|
||||
|
||||
it('defers a nested file projection until the enclosing step commits', async () => {
|
||||
const root = await tempRepo()
|
||||
const home = await tempRepo()
|
||||
const ctx = new Context()
|
||||
try {
|
||||
await ctx.plugin(RecordingFileSystem)
|
||||
await ctx.plugin(workspaceContext, { dshHome: home, maxBytes: 65536 })
|
||||
const fs = ctx.fs as RecordingFileSystem
|
||||
fs.entries.set(join(root, '.git'), { type: 'directory' })
|
||||
fs.entries.set(join(root, 'pkg/AGENTS.md'), { type: 'file', content: 'nested package rule' })
|
||||
const agent = stubAgent(root)
|
||||
const turnStart = agent.session.append('turn/start', { turn: 1 })
|
||||
ctx.emit('session/event', agent.session, turnStart)
|
||||
const stepStart = agent.session.append('step/start', { turn: 1, step: 1 })
|
||||
ctx.emit('session/event', agent.session, stepStart)
|
||||
const outerToken = Symbol('outer-code-run') as ToolExecutionToken
|
||||
|
||||
ctx.emit('tools/result', stubToolExecution({
|
||||
token: Symbol('nested-read') as ToolExecutionToken,
|
||||
parent: outerToken,
|
||||
signal: testToolSignal,
|
||||
callId: CallId('nested-read'),
|
||||
name: 'read',
|
||||
arguments: { file_path: join('pkg', 'file.txt') },
|
||||
agent,
|
||||
}), { content: [], isError: false, value: null })
|
||||
ctx.emit('tools/result', stubToolExecution({
|
||||
token: Symbol('nested-non-file') as ToolExecutionToken,
|
||||
parent: outerToken,
|
||||
signal: testToolSignal,
|
||||
callId: CallId('nested-non-file'),
|
||||
name: 'search',
|
||||
arguments: {},
|
||||
agent,
|
||||
}), { content: [], isError: false, value: null })
|
||||
ctx.emit('tools/result', stubToolExecution({
|
||||
token: Symbol('second-nested-read') as ToolExecutionToken,
|
||||
parent: outerToken,
|
||||
signal: testToolSignal,
|
||||
callId: CallId('second-nested-read'),
|
||||
name: 'read',
|
||||
arguments: { file_path: join('pkg', 'second.txt') },
|
||||
agent,
|
||||
}), { content: [], isError: false, value: null })
|
||||
ctx.emit('tools/result', stubToolExecution({
|
||||
token: outerToken,
|
||||
signal: testToolSignal,
|
||||
callId: CallId('outer-code-run'),
|
||||
name: 'run_code',
|
||||
arguments: {},
|
||||
agent,
|
||||
}), { content: [], isError: false, value: null })
|
||||
|
||||
await syncWorkspaceContext(ctx, agent)
|
||||
expect(agent.inbox.nextStep).toEqual([])
|
||||
|
||||
const stepEnd = agent.session.append('step/end', { turn: 1, step: 1 })
|
||||
ctx.emit('session/event', agent.session, stepEnd)
|
||||
expect(blocksText((await syncedWorkspaceContext(ctx, agent)).content))
|
||||
.toContain('nested package rule')
|
||||
} finally {
|
||||
await ctx.fiber.dispose()
|
||||
await rm(root, { recursive: true, force: true })
|
||||
await rm(home, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
|
||||
it('seeds closed step state from existing session history', async () => {
|
||||
const root = await tempRepo()
|
||||
const home = await tempRepo()
|
||||
const ctx = new Context()
|
||||
try {
|
||||
await ctx.plugin(RecordingFileSystem)
|
||||
const fs = ctx.fs as RecordingFileSystem
|
||||
fs.entries.set(join(root, '.git'), { type: 'directory' })
|
||||
fs.entries.set(join(root, 'pkg/AGENTS.md'), { type: 'file', content: 'nested package rule' })
|
||||
const agent = stubAgent(root)
|
||||
agent.session.append('turn/start', { turn: 1 })
|
||||
agent.session.append('step/start', { turn: 1, step: 1 })
|
||||
agent.session.append('step/end', { turn: 1, step: 1 })
|
||||
agent.session.append('turn/end', { turn: 1, reason: { kind: 'completed' } })
|
||||
await ctx.plugin(workspaceContext, { dshHome: home, maxBytes: 65536 })
|
||||
|
||||
ctx.emit('tools/result', stubToolExecution({
|
||||
signal: testToolSignal,
|
||||
callId: CallId('read-after-closed-step'),
|
||||
name: 'read',
|
||||
arguments: { file_path: join('pkg', 'file.txt') },
|
||||
agent,
|
||||
}), { content: [], isError: false, value: null })
|
||||
|
||||
expect(blocksText((await syncedWorkspaceContext(ctx, agent)).content))
|
||||
.toContain('nested package rule')
|
||||
} finally {
|
||||
await ctx.fiber.dispose()
|
||||
await rm(root, { recursive: true, force: true })
|
||||
await rm(home, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
|
||||
it('ignores failed, aborted, agentless, and non-file final results', async () => {
|
||||
const ctx = new Context()
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user