fix(tmux-context): contain executor rejection as a warning, correct suppression claim
Review round on #758. bash.run() only promises to resolve for nonzero exits, timeouts, and aborts, and bash.resolve() can reject on policy grounds, so either could escape the serial agent/step listener and abort the model turn — contradicting the plugin's documented failed-query no-op contract. Contain both and log a warning instead; the location is optional context. The Agent Note claimed an unchanged location suppresses the query. It does not: only the interval floor is checked before the query, while change suppression compares state the query returned. Corrected in both languages and re-recorded the i18n pairs.
This commit is contained in:
@@ -51,8 +51,10 @@ class FakeBash extends BashExecutor {
|
||||
commands: string[] = []
|
||||
result: BashRunResult = runResult(`${tmuxLine()}\n`)
|
||||
runError?: Error
|
||||
resolveError?: Error
|
||||
|
||||
override resolve(request: BashExecRequest): BashExecSpec {
|
||||
if (this.resolveError) throw this.resolveError
|
||||
return {
|
||||
command: request.command,
|
||||
workdir: request.workdir ?? '/work',
|
||||
@@ -325,6 +327,46 @@ describe('tmux-context no-op paths', () => {
|
||||
expect(contextTexts(session)).toHaveLength(0)
|
||||
})
|
||||
|
||||
it('warns and injects nothing when the executor rejects the run', async () => {
|
||||
const { ctx, bash } = await mount({}, true)
|
||||
bash.runError = new Error('bash executor unavailable')
|
||||
const warn = vi.spyOn(ctx.logger, 'warn')
|
||||
const session = new Session(SessionId('run-rejected'))
|
||||
openMessageTurn(session, 1)
|
||||
|
||||
await fire(ctx, sessionAgent(session), 1, 1)
|
||||
|
||||
expect(contextTexts(session)).toHaveLength(0)
|
||||
expect(warn).toHaveBeenCalledWith(expect.stringContaining('bash executor unavailable'))
|
||||
})
|
||||
|
||||
it('warns and injects nothing when the executor rejects the command at resolve', async () => {
|
||||
const { ctx, bash } = await mount({}, true)
|
||||
bash.resolveError = new Error('command denied by policy')
|
||||
const warn = vi.spyOn(ctx.logger, 'warn')
|
||||
const session = new Session(SessionId('resolve-rejected'))
|
||||
openMessageTurn(session, 1)
|
||||
|
||||
await fire(ctx, sessionAgent(session), 1, 1)
|
||||
|
||||
expect(contextTexts(session)).toHaveLength(0)
|
||||
expect(warn).toHaveBeenCalledWith(expect.stringContaining('command denied by policy'))
|
||||
})
|
||||
|
||||
it('reports a non-Error rejection in the warning', async () => {
|
||||
const { ctx, bash } = await mount({}, true)
|
||||
// Non-Error throw: the executor seam is typed, but a bad impl can reject with anything.
|
||||
bash.runError = 'spawn refused' as unknown as Error
|
||||
const warn = vi.spyOn(ctx.logger, 'warn')
|
||||
const session = new Session(SessionId('non-error-rejection'))
|
||||
openMessageTurn(session, 1)
|
||||
|
||||
await fire(ctx, sessionAgent(session), 1, 1)
|
||||
|
||||
expect(contextTexts(session)).toHaveLength(0)
|
||||
expect(warn).toHaveBeenCalledWith(expect.stringContaining('spawn refused'))
|
||||
})
|
||||
|
||||
it('skips an already-aborted step and runs before ordinary agent/step listeners', async () => {
|
||||
const { ctx } = await mount({}, true)
|
||||
const session = new Session(SessionId('ordering'))
|
||||
|
||||
Reference in New Issue
Block a user