feat(agent): add addressable queue operations

This commit is contained in:
kingwl
2026-07-30 00:05:00 +08:00
parent 87c9ab06b3
commit f893e2281d
108 changed files with 1785 additions and 608 deletions

View File

@@ -38,6 +38,7 @@ function stubAgent(ctx: Context, id: string): { agent: Agent; session: Session }
get status() { return status },
get acceptsNextStep() { return status === 'running' },
send: () => {},
updateInbox: () => 'not-found',
followup: () => {},
steer: () => {},
inject(input) { appendInjection(session, input) },

View File

@@ -307,10 +307,10 @@ export function apply(ctx: Context): void {
requestDrive(state)
}
})
ctx.on('agent/inbox/enqueue', (agent, info) => {
ctx.on('agent/inbox/enqueue', (agent, item) => {
const state = stateFor(agent)
const attempt = state.attempt
if (attempt !== undefined && sameQueued(info.content, info.source, attempt)) return
if (attempt !== undefined && sameQueued(item.message.content, item.message.source, attempt)) return
state.competingQueued = true
if (attempt?.phase === 'queued') attempt.stale = true
})

View File

@@ -287,7 +287,7 @@ describe('same-session goal driving', () => {
it('pauses and drops a reserved round when cancellation lands before admission', async () => {
const test = await harness([])
const cancel = test.ctx.on('agent/inbox/enqueue', (agent, info) => {
if (agent === test.agent && info.source.kind === 'goal') {
if (agent === test.agent && info.message.source.kind === 'goal') {
cancel()
agent.cancel({ kind: 'user' })
}
@@ -340,7 +340,7 @@ describe('same-session goal driving', () => {
test.ctx.logger.warn = ((message: unknown) => { warnings.push(String(message)) }) as typeof test.ctx.logger.warn
let inserted = false
test.ctx.on('agent/inbox/enqueue', (agent, info) => {
if (agent !== test.agent || info.source.kind !== 'goal' || inserted) return
if (agent !== test.agent || info.message.source.kind !== 'goal' || inserted) return
inserted = true
const lastStart = agent.session.events.findLast(event => event.type === 'turn/start')
const turn = (lastStart?.data.turn ?? 0) + 1
@@ -363,7 +363,7 @@ describe('same-session goal driving', () => {
const test = await harness([textResponse('human batch'), textResponse('later goal')])
let inserted = false
test.ctx.on('agent/inbox/enqueue', (agent, info) => {
if (agent !== test.agent || info.source.kind !== 'goal' || inserted) return
if (agent !== test.agent || info.message.source.kind !== 'goal' || inserted) return
inserted = true
agent.followup(createUserMessage({ content: [{ type: 'text', text: 'human joined the pending batch' }], source: { kind: 'user' } }))
})
@@ -381,7 +381,7 @@ describe('same-session goal driving', () => {
const test = await harness([textResponse('new revision')])
let edited = false
test.ctx.on('agent/inbox/enqueue', (agent, info) => {
if (agent !== test.agent || info.source.kind !== 'goal' || edited) return
if (agent !== test.agent || info.message.source.kind !== 'goal' || edited) return
edited = true
const current = test.ctx.goals.get(agent)
if (current === undefined) throw new Error('missing goal during queued edit')
@@ -661,7 +661,7 @@ describe('same-session goal driving', () => {
const test = await harness([textResponse('retry after containment')])
let armed = true
test.ctx.on('agent/inbox/enqueue', (agent, info) => {
if (agent !== test.agent || info.source.kind !== 'goal' || !armed) return
if (agent !== test.agent || info.message.source.kind !== 'goal' || !armed) return
armed = false
vi.spyOn(test.ctx.goals, 'get').mockImplementationOnce(() => {
throw new Error('admission projection failed')
@@ -737,7 +737,7 @@ describe('same-session goal driving', () => {
it('falls back to disarming when a cancelled reservation cannot be paused', async () => {
const test = await harness([])
const cancel = test.ctx.on('agent/inbox/enqueue', (agent, info) => {
if (agent !== test.agent || info.source.kind !== 'goal') return
if (agent !== test.agent || info.message.source.kind !== 'goal') return
cancel()
vi.spyOn(test.ctx.goals, 'pause').mockImplementationOnce(() => {
throw new Error('pause failed')
@@ -791,7 +791,7 @@ describe('same-session goal driving', () => {
const test = await harness([])
let unloading: Promise<void> | undefined
test.ctx.on('agent/inbox/enqueue', (agent, info) => {
if (agent === test.agent && info.source.kind === 'goal' && unloading === undefined) {
if (agent === test.agent && info.message.source.kind === 'goal' && unloading === undefined) {
unloading = Promise.resolve(test.driver.dispose())
}
})

View File

@@ -48,6 +48,7 @@ function stubAgentForSession(session: Session): StubAgent {
get status() { return status },
get acceptsNextStep() { return status === 'running' },
send: () => {},
updateInbox: () => 'not-found',
followup: () => {},
steer: () => {},
inject(input) {

View File

@@ -39,6 +39,7 @@ function liveAgent(ctx: Context, session: Session): Agent {
get status() { return status },
get acceptsNextStep() { return false },
send: () => {},
updateInbox: () => 'not-found',
followup: () => {},
steer: () => {},
inject(input: UserMessage) {

View File

@@ -33,6 +33,7 @@ function stubAgent(rawId: string, supplied?: Session): StubAgent {
get acceptsNextStep() { return status === 'running' },
ctx: new Context(),
send: () => {},
updateInbox: () => 'not-found',
followup: () => {},
steer: () => {},
inject(input) {