test(ci): cover the owned promotion paths
An agent-attributed timeout promotion fences the job to the calling session in both shell tools, and the pwsh promoted job's cancel hook is exercised through a registry kill.
This commit is contained in:
1 parent
da48f65160
commit
592dfd8854
2 files changed
+75
No files matched your search
@@ -217,6 +217,31 @@ describe('foreground timeout promotion', () => {
|
||||
await until(() => ctx.jobs.get(job!.id).status === 'killed' ? true : undefined)
|
||||
})
|
||||
|
||||
it('promotes under the calling agent so the job is fenced to its session', async () => {
|
||||
const ctx = await setup()
|
||||
const owner = {
|
||||
id: SessionId('promote-owner'),
|
||||
session: { id: SessionId('promote-owner'), header: { cwd: process.cwd() } },
|
||||
status: 'idle',
|
||||
ctx,
|
||||
} as unknown as Agent
|
||||
ctx.agents.register(owner)
|
||||
const result = await ctx.tools.execute({
|
||||
signal: testToolSignal,
|
||||
callId: ToolCallId('observe-promote-owned'),
|
||||
name: 'bash',
|
||||
arguments: { command: 'printf "held\\n"; sleep 30', description: 'test command', timeoutMs: 250 },
|
||||
agent: owner,
|
||||
})
|
||||
expect(text(result)).toContain('moved to background job')
|
||||
const job = ctx.jobs.list(owner)[0]
|
||||
expect(job).toBeDefined()
|
||||
expect(job!.ownerSession).toBe(owner.id)
|
||||
expect(() => ctx.jobs.readRecord(job!.id, 0)).toThrow(/belongs to another session/)
|
||||
expect(ctx.jobs.kill(job!.id, owner, { reason: 'test cleanup' })).toBe('requested')
|
||||
await until(() => ctx.jobs.get(job!.id, owner).status === 'killed' ? true : undefined)
|
||||
})
|
||||
|
||||
it('falls back to the timeout kill when the job admission refuses the promotion', async () => {
|
||||
const ctx = await setup()
|
||||
const warn = vi.spyOn(ctx.logger, 'warn').mockImplementation(() => {})
|
||||
|
||||
@@ -275,6 +275,56 @@ describe('foreground timeout promotion (pwsh)', () => {
|
||||
await until(() => ctx.jobs.get(job!.id).status === 'completed' ? true : undefined)
|
||||
})
|
||||
|
||||
it('promotes under the calling agent and stops the promoted job through the registry kill', async () => {
|
||||
const { ctx, pwsh } = await setup()
|
||||
const owner = {
|
||||
id: SessionId('pwsh-promote-owner'),
|
||||
session: { id: SessionId('pwsh-promote-owner'), header: { cwd: process.cwd() } },
|
||||
status: 'idle',
|
||||
ctx,
|
||||
} as unknown as Agent
|
||||
ctx.agents.register(owner)
|
||||
let killed = false
|
||||
let resolveDone: () => void = () => {}
|
||||
const proc = {
|
||||
status: 'running',
|
||||
exitCode: null,
|
||||
signal: null,
|
||||
done: new Promise<void>((resolve) => { resolveDone = resolve }),
|
||||
readOutput: () => ({ delta: '', lossy: false }),
|
||||
kill: () => {
|
||||
killed = true
|
||||
proc.status = 'killed'
|
||||
proc.signal = 'SIGTERM'
|
||||
resolveDone()
|
||||
return true
|
||||
},
|
||||
promotion: Promise.resolve({ accepted: false, accept() { this.accepted = true }, decline() {} }),
|
||||
result: () => Promise.reject(new Error('result projection unused after promotion')),
|
||||
} as unknown as ShellExecution & { status: string; signal: string | null }
|
||||
pwsh.foregroundHandler = () => proc
|
||||
|
||||
const result = await ctx.tools.execute({
|
||||
signal: testToolSignal,
|
||||
callId: ToolCallId('pwsh-observe-promote-owned'),
|
||||
name: 'pwsh',
|
||||
arguments: { command: 'Get-Slow', description: 'test command', timeoutMs: 250 },
|
||||
agent: owner,
|
||||
})
|
||||
expect((result.content[0] as { text: string }).text).toContain('moved to background job')
|
||||
const job = ctx.jobs.list(owner)[0]
|
||||
expect(job).toBeDefined()
|
||||
expect(job!.ownerSession).toBe(owner.id)
|
||||
|
||||
expect(ctx.jobs.kill(job!.id, owner, { reason: 'test cleanup' })).toBe('requested')
|
||||
await until(() => killed ? true : undefined)
|
||||
const settled = await until(() => {
|
||||
const snapshot = ctx.jobs.get(job!.id, owner)
|
||||
return snapshot.status === 'killed' ? snapshot : undefined
|
||||
})
|
||||
expect(settled.detail).toBe('signal: SIGTERM; test cleanup')
|
||||
})
|
||||
|
||||
it('declines the offer and reports the timeout when no job controller serves the owner', async () => {
|
||||
// The same composition minus dsh-tool-jobs: the registry exists, so the
|
||||
// deadline still offers, but admission refuses and the tool falls back.
|
||||
|
||||
Reference in new issue
Block a user