refactor(agent): publish agent/inbox/claimed from Inbox.claim
Move the claimed-message notification loop out of the loop's pre-step into Inbox.claim(target, turn), so the step-boundary operation publishes its own claimed notifications like insertions and discards do.
This commit is contained in:
@@ -40,7 +40,7 @@ function sessionAgent(session: Session, id = 'agent'): Agent {
|
|||||||
id: SessionId(id),
|
id: SessionId(id),
|
||||||
options: {},
|
options: {},
|
||||||
session,
|
session,
|
||||||
inbox: new Inbox(session, { inserted: () => {}, discarded: () => {} }),
|
inbox: new Inbox(session, { inserted: () => {}, discarded: () => {}, claimed: () => {} }),
|
||||||
status: 'running',
|
status: 'running',
|
||||||
ctx: new Context(),
|
ctx: new Context(),
|
||||||
send: () => {},
|
send: () => {},
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ function sessionAgent(session: Session, id = 'agent'): Agent {
|
|||||||
id: SessionId(id),
|
id: SessionId(id),
|
||||||
options: {},
|
options: {},
|
||||||
session,
|
session,
|
||||||
inbox: new Inbox(session, { inserted: () => {}, discarded: () => {} }),
|
inbox: new Inbox(session, { inserted: () => {}, discarded: () => {}, claimed: () => {} }),
|
||||||
status: 'running',
|
status: 'running',
|
||||||
ctx: new Context(),
|
ctx: new Context(),
|
||||||
send: () => {},
|
send: () => {},
|
||||||
|
|||||||
@@ -175,7 +175,7 @@ function stubAgent(cwd?: string, seed: SessionEvent[] = []): Agent {
|
|||||||
id: SessionId('a1'),
|
id: SessionId('a1'),
|
||||||
options: {},
|
options: {},
|
||||||
session,
|
session,
|
||||||
inbox: new Inbox(session, { inserted: () => {}, discarded: () => {} }),
|
inbox: new Inbox(session, { inserted: () => {}, discarded: () => {}, claimed: () => {} }),
|
||||||
status: 'idle',
|
status: 'idle',
|
||||||
send: () => {},
|
send: () => {},
|
||||||
followup: () => {},
|
followup: () => {},
|
||||||
@@ -230,7 +230,7 @@ function baselineEvents(agent: Agent): SessionEvent[] {
|
|||||||
async function appendAdditionalContexts(ctx: Context, agent: Agent): Promise<number | undefined> {
|
async function appendAdditionalContexts(ctx: Context, agent: Agent): Promise<number | undefined> {
|
||||||
await syncedWorkspaceContext(ctx, agent)
|
await syncedWorkspaceContext(ctx, agent)
|
||||||
let lastSeq: number | undefined
|
let lastSeq: number | undefined
|
||||||
for (const claimed of agent.inbox.claim('next-step')) {
|
for (const claimed of agent.inbox.claim('next-step', 1)) {
|
||||||
if (claimed.source.kind !== 'workspace-instructions') continue
|
if (claimed.source.kind !== 'workspace-instructions') continue
|
||||||
const event = agent.session.append('user/message', claimed, { surfaceOp: 'append' })
|
const event = agent.session.append('user/message', claimed, { surfaceOp: 'append' })
|
||||||
ctx.emit('session/event', agent.session, event)
|
ctx.emit('session/event', agent.session, event)
|
||||||
@@ -249,7 +249,7 @@ async function composeBaselinePrefix(ctx: Context, agent: Agent): Promise<Messag
|
|||||||
{ turn: 1, step: 1, signal },
|
{ turn: 1, step: 1, signal },
|
||||||
() => Promise.resolve({ kind: 'enter' as const, messages: [] }),
|
() => Promise.resolve({ kind: 'enter' as const, messages: [] }),
|
||||||
)
|
)
|
||||||
const claimed = agent.inbox.claim('next-step')
|
const claimed = agent.inbox.claim('next-step', 1)
|
||||||
const decision = await agentEvents(ctx, agent).waterfall(
|
const decision = await agentEvents(ctx, agent).waterfall(
|
||||||
'agent/pre-step',
|
'agent/pre-step',
|
||||||
claimed,
|
claimed,
|
||||||
@@ -979,7 +979,7 @@ describe('workspace context request injection', () => {
|
|||||||
await ctx.plugin(workspaceContext, { dshHome: home, maxBytes: 65536 })
|
await ctx.plugin(workspaceContext, { dshHome: home, maxBytes: 65536 })
|
||||||
const resumed = stubAgent(root, [...original.session.events])
|
const resumed = stubAgent(root, [...original.session.events])
|
||||||
agentEvents(ctx, resumed).emit('agent/session-start', 'resume')
|
agentEvents(ctx, resumed).emit('agent/session-start', 'resume')
|
||||||
const claimed = resumed.inbox.claim('next-step')
|
const claimed = resumed.inbox.claim('next-step', 1)
|
||||||
const decision = await agentEvents(ctx, resumed).waterfall(
|
const decision = await agentEvents(ctx, resumed).waterfall(
|
||||||
'agent/pre-step',
|
'agent/pre-step',
|
||||||
claimed,
|
claimed,
|
||||||
@@ -1027,7 +1027,7 @@ describe('workspace context request injection', () => {
|
|||||||
await ctx.plugin(workspaceContext, { dshHome: home, maxBytes: 65536 })
|
await ctx.plugin(workspaceContext, { dshHome: home, maxBytes: 65536 })
|
||||||
const resumed = stubAgent(root, [...original.session.events])
|
const resumed = stubAgent(root, [...original.session.events])
|
||||||
agentEvents(ctx, resumed).emit('agent/session-start', 'resume')
|
agentEvents(ctx, resumed).emit('agent/session-start', 'resume')
|
||||||
const staleClaim = resumed.inbox.claim('next-step')
|
const staleClaim = resumed.inbox.claim('next-step', 1)
|
||||||
const staleDecision = await agentEvents(ctx, resumed).waterfall(
|
const staleDecision = await agentEvents(ctx, resumed).waterfall(
|
||||||
'agent/pre-step',
|
'agent/pre-step',
|
||||||
staleClaim,
|
staleClaim,
|
||||||
@@ -1082,7 +1082,7 @@ describe('workspace context request injection', () => {
|
|||||||
await resumedCtx.plugin(workspaceContext, { dshHome: home, maxBytes })
|
await resumedCtx.plugin(workspaceContext, { dshHome: home, maxBytes })
|
||||||
const resumed = stubAgent(root, [...original.session.events])
|
const resumed = stubAgent(root, [...original.session.events])
|
||||||
agentEvents(resumedCtx, resumed).emit('agent/session-start', 'resume')
|
agentEvents(resumedCtx, resumed).emit('agent/session-start', 'resume')
|
||||||
const claimed = resumed.inbox.claim('next-step')
|
const claimed = resumed.inbox.claim('next-step', 1)
|
||||||
const decision = await agentEvents(resumedCtx, resumed).waterfall(
|
const decision = await agentEvents(resumedCtx, resumed).waterfall(
|
||||||
'agent/pre-step',
|
'agent/pre-step',
|
||||||
claimed,
|
claimed,
|
||||||
@@ -3966,7 +3966,7 @@ describe('workspace context inbox synchronization', () => {
|
|||||||
await mountFileToolsAndWorkspaceContext(ctx, { dshHome: home, maxBytes: 65536 })
|
await mountFileToolsAndWorkspaceContext(ctx, { dshHome: home, maxBytes: 65536 })
|
||||||
const agent = stubAgent(join(root, 'pkg'))
|
const agent = stubAgent(join(root, 'pkg'))
|
||||||
await syncedWorkspaceContext(ctx, agent)
|
await syncedWorkspaceContext(ctx, agent)
|
||||||
const claimed = agent.inbox.claim('next-step')
|
const claimed = agent.inbox.claim('next-step', 1)
|
||||||
await write(join(root, 'pkg/AGENTS.md'), 'new claimed rule with more detail')
|
await write(join(root, 'pkg/AGENTS.md'), 'new claimed rule with more detail')
|
||||||
const downstream = { kind: 'enter' as const, messages: claimed }
|
const downstream = { kind: 'enter' as const, messages: claimed }
|
||||||
|
|
||||||
|
|||||||
@@ -2007,11 +2007,11 @@ export const TYPE_API: readonly TypeApiEntry[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Inbox',
|
name: 'Inbox',
|
||||||
declaration: 'export class Inbox {\n constructor(private readonly session: Session, private readonly notifications: InboxNotifications);\n get nextTurn(): readonly UserMessage[];\n get nextStep(): readonly UserMessage[];\n get hasPending(): boolean;\n clear(): void;\n claim(target: InboxTarget): UserMessage[];\n append(target: InboxTarget, message: UserMessage): void;\n prepend(target: InboxTarget, message: UserMessage): void;\n replace(messageId: MessageId, newMessage: UserMessage): boolean;\n remove(messageId: MessageId): boolean;\n splice(target: InboxTarget, start: number, deleteCount: number, inserted: UserMessage[]): UserMessage[];\n}',
|
declaration: 'export class Inbox {\n constructor(private readonly session: Session, private readonly notifications: InboxNotifications);\n get nextTurn(): readonly UserMessage[];\n get nextStep(): readonly UserMessage[];\n get hasPending(): boolean;\n clear(): void;\n claim(target: InboxTarget, turn: number): UserMessage[];\n append(target: InboxTarget, message: UserMessage): void;\n prepend(target: InboxTarget, message: UserMessage): void;\n replace(messageId: MessageId, newMessage: UserMessage): boolean;\n remove(messageId: MessageId): boolean;\n splice(target: InboxTarget, start: number, deleteCount: number, inserted: UserMessage[]): UserMessage[];\n}',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'InboxNotifications',
|
name: 'InboxNotifications',
|
||||||
declaration: 'export interface InboxNotifications {\n inserted(message: UserMessage): void;\n discarded(message: UserMessage): void;\n}',
|
declaration: 'export interface InboxNotifications {\n inserted(message: UserMessage): void;\n discarded(message: UserMessage): void;\n claimed(message: UserMessage, turn: number): void;\n}',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'InboxTarget',
|
name: 'InboxTarget',
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ export class ReactLoopAgent implements Agent {
|
|||||||
this.inbox = new Inbox(session, {
|
this.inbox = new Inbox(session, {
|
||||||
inserted: (message) => { emitAgentEvent(loopCtx, this, 'agent/inbox/inserted', { message }) },
|
inserted: (message) => { emitAgentEvent(loopCtx, this, 'agent/inbox/inserted', { message }) },
|
||||||
discarded: (message) => { emitAgentEvent(loopCtx, this, 'agent/inbox/discarded', { message }) },
|
discarded: (message) => { emitAgentEvent(loopCtx, this, 'agent/inbox/discarded', { message }) },
|
||||||
|
claimed: (message, turn) => { emitAgentEvent(loopCtx, this, 'agent/inbox/claimed', { message, turn }) },
|
||||||
})
|
})
|
||||||
const lastTurn = session.events.findLast(event => event.type === 'turn/start')?.data.turn ?? 0
|
const lastTurn = session.events.findLast(event => event.type === 'turn/start')?.data.turn ?? 0
|
||||||
this.phase = { kind: 'idle', lastTurn }
|
this.phase = { kind: 'idle', lastTurn }
|
||||||
@@ -198,10 +199,7 @@ export class ReactLoopAgent implements Agent {
|
|||||||
/* v8 ignore next -- private callers establish the running phase before proposing a step */
|
/* v8 ignore next -- private callers establish the running phase before proposing a step */
|
||||||
if (this.phase.kind !== 'running') throw new Error(`agent "${this.id}": pre-step outside running phase`)
|
if (this.phase.kind !== 'running') throw new Error(`agent "${this.id}": pre-step outside running phase`)
|
||||||
const signal = this.phase.abort.signal
|
const signal = this.phase.abort.signal
|
||||||
const claimed = this.inbox.claim(target)
|
const claimed = this.inbox.claim(target, position.turn)
|
||||||
for (const message of claimed) {
|
|
||||||
emitAgentEvent(this.loopCtx, this, 'agent/inbox/claimed', { message, turn: position.turn })
|
|
||||||
}
|
|
||||||
const assembly = await this.loopCtx.systemPrompt.assemble(assembleContextFor(this, signal))
|
const assembly = await this.loopCtx.systemPrompt.assemble(assembleContextFor(this, signal))
|
||||||
signal.throwIfAborted()
|
signal.throwIfAborted()
|
||||||
const context = this.runtimeContext.project(renderContextSnapshot(assembly))
|
const context = this.runtimeContext.project(renderContextSnapshot(assembly))
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ export interface InboxNotifications {
|
|||||||
inserted(message: UserMessage): void
|
inserted(message: UserMessage): void
|
||||||
/** Publish one discarded message. */
|
/** Publish one discarded message. */
|
||||||
discarded(message: UserMessage): void
|
discarded(message: UserMessage): void
|
||||||
|
/** Publish one claimed message inside its owning turn. */
|
||||||
|
claimed(message: UserMessage, turn: number): void
|
||||||
}
|
}
|
||||||
|
|
||||||
/** A replay-once projection that incrementally consumes later inbox splices. */
|
/** A replay-once projection that incrementally consumes later inbox splices. */
|
||||||
@@ -61,17 +63,19 @@ export class Inbox {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove and return the complete batch proposed for one step. The durable
|
* Remove and return the complete batch proposed for one step, publishing
|
||||||
* splices are pure deletions; the caller publishes claimed notifications.
|
* each claimed message. The durable splices are pure deletions.
|
||||||
* @param target - whether this boundary also consumes one queued turn.
|
* @param target - whether this boundary also consumes one queued turn.
|
||||||
|
* @param turn - turn that will own the claimed batch.
|
||||||
* @returns next-step input followed by the queued turn, when requested.
|
* @returns next-step input followed by the queued turn, when requested.
|
||||||
* @internal - the agent loop's step-boundary operation, not a plugin seam.
|
* @internal - the agent loop's step-boundary operation, not a plugin seam.
|
||||||
*/
|
*/
|
||||||
claim(target: InboxTarget): UserMessage[] {
|
claim(target: InboxTarget, turn: number): UserMessage[] {
|
||||||
const claimed = this.mutate('next-step', 0, this.nextStep.length, [], false)
|
const claimed = this.mutate('next-step', 0, this.nextStep.length, [], false)
|
||||||
if (target === 'next-turn') {
|
if (target === 'next-turn') {
|
||||||
claimed.push(...this.mutate('next-turn', 0, 1, [], false))
|
claimed.push(...this.mutate('next-turn', 0, 1, [], false))
|
||||||
}
|
}
|
||||||
|
for (const message of claimed) this.notifications.claimed(message, turn)
|
||||||
return claimed
|
return claimed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ function stubAgent(rawId: string, overrides: Partial<Agent> = {}): Agent {
|
|||||||
id,
|
id,
|
||||||
options: {},
|
options: {},
|
||||||
session,
|
session,
|
||||||
inbox: new Inbox(session, { inserted: () => {}, discarded: () => {} }),
|
inbox: new Inbox(session, { inserted: () => {}, discarded: () => {}, claimed: () => {} }),
|
||||||
status: 'idle',
|
status: 'idle',
|
||||||
ctx: new Context(),
|
ctx: new Context(),
|
||||||
send: () => {},
|
send: () => {},
|
||||||
@@ -45,7 +45,7 @@ describe('Inbox', () => {
|
|||||||
inserted: [],
|
inserted: [],
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(() => new Inbox(session, { inserted: () => {}, discarded: () => {} }))
|
expect(() => new Inbox(session, { inserted: () => {}, discarded: () => {}, claimed: () => {} }))
|
||||||
.toThrow('invalid persisted inbox splice at session seq 0')
|
.toThrow('invalid persisted inbox splice at session seq 0')
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -54,6 +54,7 @@ describe('Inbox', () => {
|
|||||||
const inserted: UserMessage[] = []
|
const inserted: UserMessage[] = []
|
||||||
const discarded: UserMessage[] = []
|
const discarded: UserMessage[] = []
|
||||||
const inbox = new Inbox(session, {
|
const inbox = new Inbox(session, {
|
||||||
|
claimed: () => {},
|
||||||
inserted: message => void inserted.push(message),
|
inserted: message => void inserted.push(message),
|
||||||
discarded: message => void discarded.push(message),
|
discarded: message => void discarded.push(message),
|
||||||
})
|
})
|
||||||
@@ -92,7 +93,7 @@ describe('Inbox', () => {
|
|||||||
|
|
||||||
it('normalizes splice coordinates, rejects duplicate identities, and reports missing removals', () => {
|
it('normalizes splice coordinates, rejects duplicate identities, and reports missing removals', () => {
|
||||||
const session = Session.create(SessionId('splice-inbox'))
|
const session = Session.create(SessionId('splice-inbox'))
|
||||||
const inbox = new Inbox(session, { inserted: () => {}, discarded: () => {} })
|
const inbox = new Inbox(session, { inserted: () => {}, discarded: () => {}, claimed: () => {} })
|
||||||
const first = createUserMessage({
|
const first = createUserMessage({
|
||||||
content: [{ type: 'text', text: 'first' }],
|
content: [{ type: 'text', text: 'first' }],
|
||||||
source: { kind: 'user' },
|
source: { kind: 'user' },
|
||||||
@@ -113,6 +114,7 @@ describe('Inbox', () => {
|
|||||||
const session = Session.create(SessionId('clear-inbox'))
|
const session = Session.create(SessionId('clear-inbox'))
|
||||||
const discarded: UserMessage[] = []
|
const discarded: UserMessage[] = []
|
||||||
const inbox = new Inbox(session, {
|
const inbox = new Inbox(session, {
|
||||||
|
claimed: () => {},
|
||||||
inserted: () => {},
|
inserted: () => {},
|
||||||
discarded: message => void discarded.push(message),
|
discarded: message => void discarded.push(message),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ function agent(ctx: Context, cwd: string): Agent {
|
|||||||
id,
|
id,
|
||||||
options: {},
|
options: {},
|
||||||
session,
|
session,
|
||||||
inbox: new Inbox(session, { inserted: () => {}, discarded: () => {} }),
|
inbox: new Inbox(session, { inserted: () => {}, discarded: () => {}, claimed: () => {} }),
|
||||||
status: 'idle',
|
status: 'idle',
|
||||||
ctx: scope.ctx,
|
ctx: scope.ctx,
|
||||||
send: () => {},
|
send: () => {},
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ interface Harness {
|
|||||||
function stubAgent(ctx: Context, id: string): { agent: Agent; session: Session } {
|
function stubAgent(ctx: Context, id: string): { agent: Agent; session: Session } {
|
||||||
// Store-created: the command executor durably logs lifecycle events on it.
|
// Store-created: the command executor durably logs lifecycle events on it.
|
||||||
const session = ctx.sessions.create(SessionId(id))
|
const session = ctx.sessions.create(SessionId(id))
|
||||||
const inbox = new Inbox(session, { inserted: () => {}, discarded: () => {} })
|
const inbox = new Inbox(session, { inserted: () => {}, discarded: () => {}, claimed: () => {} })
|
||||||
let status: AgentStatus = 'idle'
|
let status: AgentStatus = 'idle'
|
||||||
const agent: Agent = {
|
const agent: Agent = {
|
||||||
id: session.id,
|
id: session.id,
|
||||||
|
|||||||
@@ -24,13 +24,13 @@ function nextTurn(session: Session): number {
|
|||||||
|
|
||||||
/** Mirror the public Agent.inject contract for domain tests. */
|
/** Mirror the public Agent.inject contract for domain tests. */
|
||||||
function appendInjection(session: Session, input: UserMessage): void {
|
function appendInjection(session: Session, input: UserMessage): void {
|
||||||
new Inbox(session, { inserted: () => {}, discarded: () => {} }).append('next-step', input)
|
new Inbox(session, { inserted: () => {}, discarded: () => {}, claimed: () => {} }).append('next-step', input)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Build a registry-compatible agent around one concrete session. */
|
/** Build a registry-compatible agent around one concrete session. */
|
||||||
function stubAgentForSession(session: Session): StubAgent {
|
function stubAgentForSession(session: Session): StubAgent {
|
||||||
const id = session.id
|
const id = session.id
|
||||||
const inbox = new Inbox(session, { inserted: () => {}, discarded: () => {} })
|
const inbox = new Inbox(session, { inserted: () => {}, discarded: () => {}, claimed: () => {} })
|
||||||
const agent: Agent = {
|
const agent: Agent = {
|
||||||
id,
|
id,
|
||||||
options: {},
|
options: {},
|
||||||
@@ -554,7 +554,7 @@ describe('goal replay validation', () => {
|
|||||||
content: [{ type: 'text', text: 'unrelated pending context' }],
|
content: [{ type: 'text', text: 'unrelated pending context' }],
|
||||||
source: { kind: 'plugin', plugin: 'test' },
|
source: { kind: 'plugin', plugin: 'test' },
|
||||||
})
|
})
|
||||||
const inbox = new Inbox(session, { inserted: () => {}, discarded: () => {} })
|
const inbox = new Inbox(session, { inserted: () => {}, discarded: () => {}, claimed: () => {} })
|
||||||
inbox.append('next-step', message)
|
inbox.append('next-step', message)
|
||||||
expect(inbox.remove(message.id)).toBe(true)
|
expect(inbox.remove(message.id)).toBe(true)
|
||||||
expect(foldGoal(session.events)).toMatchObject({ goal: { id: change.goal.id, revision: 1 } })
|
expect(foldGoal(session.events)).toMatchObject({ goal: { id: change.goal.id, revision: 1 } })
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ interface Bench {
|
|||||||
/** Register a minimal registry-compatible live agent over a store session. */
|
/** Register a minimal registry-compatible live agent over a store session. */
|
||||||
function liveAgent(ctx: Context, session: Session): Agent {
|
function liveAgent(ctx: Context, session: Session): Agent {
|
||||||
const status: AgentStatus = 'idle'
|
const status: AgentStatus = 'idle'
|
||||||
const inbox = new Inbox(session, { inserted: () => {}, discarded: () => {} })
|
const inbox = new Inbox(session, { inserted: () => {}, discarded: () => {}, claimed: () => {} })
|
||||||
const agent: Agent = {
|
const agent: Agent = {
|
||||||
id: session.id,
|
id: session.id,
|
||||||
options: {},
|
options: {},
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ function stubAgent(rawId: string, supplied?: Session): StubAgent {
|
|||||||
id: session.id,
|
id: session.id,
|
||||||
options: {},
|
options: {},
|
||||||
session,
|
session,
|
||||||
inbox: new Inbox(session, { inserted: () => {}, discarded: () => {} }),
|
inbox: new Inbox(session, { inserted: () => {}, discarded: () => {}, claimed: () => {} }),
|
||||||
get status() { return status },
|
get status() { return status },
|
||||||
ctx: new Context(),
|
ctx: new Context(),
|
||||||
send: () => {},
|
send: () => {},
|
||||||
@@ -55,7 +55,7 @@ function openTurn(stub: StubAgent, source: MessageSource, text = 'prompt'): numb
|
|||||||
source,
|
source,
|
||||||
})
|
})
|
||||||
stub.agent.inbox.append('next-turn', message)
|
stub.agent.inbox.append('next-turn', message)
|
||||||
const claimed = stub.agent.inbox.claim('next-turn')
|
const claimed = stub.agent.inbox.claim('next-turn', turn)
|
||||||
if (claimed.length === 0) throw new Error('expected queued turn input')
|
if (claimed.length === 0) throw new Error('expected queued turn input')
|
||||||
stub.session.append('turn/start', { turn })
|
stub.session.append('turn/start', { turn })
|
||||||
for (const admitted of claimed) {
|
for (const admitted of claimed) {
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ async function harness(options: { commands?: boolean; skills?: boolean } = {}):
|
|||||||
/** Register a live structural agent stub (api-proxy-view precedent: only id/session/status/ctx are read). */
|
/** Register a live structural agent stub (api-proxy-view precedent: only id/session/status/ctx are read). */
|
||||||
function stubAgent(ctx: Context, sessionId?: SessionId): Agent {
|
function stubAgent(ctx: Context, sessionId?: SessionId): Agent {
|
||||||
const session = ctx.sessions.create(sessionId)
|
const session = ctx.sessions.create(sessionId)
|
||||||
const inbox = new Inbox(session, { inserted: () => {}, discarded: () => {} })
|
const inbox = new Inbox(session, { inserted: () => {}, discarded: () => {}, claimed: () => {} })
|
||||||
const agent = {
|
const agent = {
|
||||||
id: session.id,
|
id: session.id,
|
||||||
session,
|
session,
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ async function harness(withRegistry: boolean): Promise<{ ctx: Context; session:
|
|||||||
if (withRegistry) await ctx.plugin(SessionProjectionRegistry)
|
if (withRegistry) await ctx.plugin(SessionProjectionRegistry)
|
||||||
const session = ctx.sessions.create()
|
const session = ctx.sessions.create()
|
||||||
// The gateway reads both the session and durable inbox baseline.
|
// The gateway reads both the session and durable inbox baseline.
|
||||||
ctx.agents.register({ id: session.id, session, inbox: new Inbox(session, { inserted: () => {}, discarded: () => {} }), status: 'idle', ctx } as Agent)
|
ctx.agents.register({ id: session.id, session, inbox: new Inbox(session, { inserted: () => {}, discarded: () => {}, claimed: () => {} }), status: 'idle', ctx } as Agent)
|
||||||
return { ctx, session }
|
return { ctx, session }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ function stubAgent(session: Session): Agent {
|
|||||||
id: session.id,
|
id: session.id,
|
||||||
options: {},
|
options: {},
|
||||||
session,
|
session,
|
||||||
inbox: new Inbox(session, { inserted: () => {}, discarded: () => {} }),
|
inbox: new Inbox(session, { inserted: () => {}, discarded: () => {}, claimed: () => {} }),
|
||||||
status: 'idle',
|
status: 'idle',
|
||||||
ctx: new Context(),
|
ctx: new Context(),
|
||||||
send: () => {},
|
send: () => {},
|
||||||
|
|||||||
@@ -42,8 +42,9 @@ function agent(ctx: Context, cwd?: string): Agent {
|
|||||||
const id = SessionId('agent')
|
const id = SessionId('agent')
|
||||||
const session = Session.create(id, undefined, { version: 0, id, createdAt: 0, ...cwd === undefined ? {} : { cwd } })
|
const session = Session.create(id, undefined, { version: 0, id, createdAt: 0, ...cwd === undefined ? {} : { cwd } })
|
||||||
return {
|
return {
|
||||||
id, options: {}, session, inbox: new Inbox(session, { inserted: () => {}, discarded: () => {} }),
|
id, options: {}, session, inbox: new Inbox(session, { inserted: () => {}, discarded: () => {}, claimed: () => {} }),
|
||||||
status: 'idle', ctx,
|
status: 'idle',
|
||||||
|
ctx,
|
||||||
send: () => {},
|
send: () => {},
|
||||||
followup: () => {}, steer: () => {}, inject: () => {}, cancel() {},
|
followup: () => {}, steer: () => {}, inject: () => {}, cancel() {},
|
||||||
runMaintenance: task => task(new AbortController().signal),
|
runMaintenance: task => task(new AbortController().signal),
|
||||||
@@ -259,8 +260,9 @@ describe('pty-local plugin shape', () => {
|
|||||||
const session = ctx.sessions.create(SessionId('mode-owner'))
|
const session = ctx.sessions.create(SessionId('mode-owner'))
|
||||||
const ownerFiber = await ctx.plugin(() => {})
|
const ownerFiber = await ctx.plugin(() => {})
|
||||||
const owner: Agent = {
|
const owner: Agent = {
|
||||||
id: session.id, options: {}, session, inbox: new Inbox(session, { inserted: () => {}, discarded: () => {} }),
|
id: session.id, options: {}, session, inbox: new Inbox(session, { inserted: () => {}, discarded: () => {}, claimed: () => {} }),
|
||||||
status: 'idle', ctx: ownerFiber.ctx,
|
status: 'idle',
|
||||||
|
ctx: ownerFiber.ctx,
|
||||||
send: () => {},
|
send: () => {},
|
||||||
followup: () => {}, steer: () => {}, inject: () => {}, cancel() {},
|
followup: () => {}, steer: () => {}, inject: () => {}, cancel() {},
|
||||||
runMaintenance: task => task(new AbortController().signal),
|
runMaintenance: task => task(new AbortController().signal),
|
||||||
@@ -306,8 +308,9 @@ describe('pty-local plugin shape', () => {
|
|||||||
const session = ctx.sessions.create(SessionId('pending-mode-owner'))
|
const session = ctx.sessions.create(SessionId('pending-mode-owner'))
|
||||||
const ownerFiber = await ctx.plugin(() => {})
|
const ownerFiber = await ctx.plugin(() => {})
|
||||||
const owner: Agent = {
|
const owner: Agent = {
|
||||||
id: session.id, options: {}, session, inbox: new Inbox(session, { inserted: () => {}, discarded: () => {} }),
|
id: session.id, options: {}, session, inbox: new Inbox(session, { inserted: () => {}, discarded: () => {}, claimed: () => {} }),
|
||||||
status: 'idle', ctx: ownerFiber.ctx,
|
status: 'idle',
|
||||||
|
ctx: ownerFiber.ctx,
|
||||||
send: () => {},
|
send: () => {},
|
||||||
followup: () => {}, steer: () => {}, inject: () => {}, cancel() {},
|
followup: () => {}, steer: () => {}, inject: () => {}, cancel() {},
|
||||||
runMaintenance: task => task(new AbortController().signal),
|
runMaintenance: task => task(new AbortController().signal),
|
||||||
|
|||||||
@@ -35,8 +35,9 @@ function stubAgent(ctx: Context, rawId: string): Agent {
|
|||||||
const scope = ctx.plugin(() => {})
|
const scope = ctx.plugin(() => {})
|
||||||
const session = Session.create(id)
|
const session = Session.create(id)
|
||||||
return {
|
return {
|
||||||
id, options: {}, session, inbox: new Inbox(session, { inserted: () => {}, discarded: () => {} }),
|
id, options: {}, session, inbox: new Inbox(session, { inserted: () => {}, discarded: () => {}, claimed: () => {} }),
|
||||||
status: 'idle', ctx: scope.ctx,
|
status: 'idle',
|
||||||
|
ctx: scope.ctx,
|
||||||
send: () => {},
|
send: () => {},
|
||||||
followup: () => {}, steer: () => {}, inject: () => {}, cancel() {},
|
followup: () => {}, steer: () => {}, inject: () => {}, cancel() {},
|
||||||
runMaintenance: task => task(new AbortController().signal),
|
runMaintenance: task => task(new AbortController().signal),
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ function stubAgent(ctx: Context, rawId: string): Agent {
|
|||||||
id,
|
id,
|
||||||
options: {},
|
options: {},
|
||||||
session,
|
session,
|
||||||
inbox: new Inbox(session, { inserted: () => {}, discarded: () => {} }),
|
inbox: new Inbox(session, { inserted: () => {}, discarded: () => {}, claimed: () => {} }),
|
||||||
status: 'idle',
|
status: 'idle',
|
||||||
ctx: scopeFiber.ctx,
|
ctx: scopeFiber.ctx,
|
||||||
send: () => {},
|
send: () => {},
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ function agent(ctx: Context, cwd: string): Agent {
|
|||||||
id,
|
id,
|
||||||
options: {},
|
options: {},
|
||||||
session,
|
session,
|
||||||
inbox: new Inbox(session, { inserted: () => {}, discarded: () => {} }),
|
inbox: new Inbox(session, { inserted: () => {}, discarded: () => {}, claimed: () => {} }),
|
||||||
status: 'idle',
|
status: 'idle',
|
||||||
ctx: scope.ctx,
|
ctx: scope.ctx,
|
||||||
send: () => {},
|
send: () => {},
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ function agent(ctx: Context, cwd: string | undefined): Agent {
|
|||||||
id,
|
id,
|
||||||
options: {},
|
options: {},
|
||||||
session,
|
session,
|
||||||
inbox: new Inbox(session, { inserted: () => {}, discarded: () => {} }),
|
inbox: new Inbox(session, { inserted: () => {}, discarded: () => {}, claimed: () => {} }),
|
||||||
status: 'idle',
|
status: 'idle',
|
||||||
ctx: scope.ctx,
|
ctx: scope.ctx,
|
||||||
send: () => {},
|
send: () => {},
|
||||||
|
|||||||
@@ -40,8 +40,9 @@ function agent(ctx: Context): Agent {
|
|||||||
const id = SessionId('pty-loader-agent')
|
const id = SessionId('pty-loader-agent')
|
||||||
const session = Session.create(id)
|
const session = Session.create(id)
|
||||||
const value: Agent = {
|
const value: Agent = {
|
||||||
id, options: {}, session, inbox: new Inbox(session, { inserted: () => {}, discarded: () => {} }),
|
id, options: {}, session, inbox: new Inbox(session, { inserted: () => {}, discarded: () => {}, claimed: () => {} }),
|
||||||
status: 'idle', ctx: scope.ctx,
|
status: 'idle',
|
||||||
|
ctx: scope.ctx,
|
||||||
send: () => {},
|
send: () => {},
|
||||||
followup: () => {}, steer: () => {}, inject: () => {}, cancel() {},
|
followup: () => {}, steer: () => {}, inject: () => {}, cancel() {},
|
||||||
runMaintenance: task => task(new AbortController().signal),
|
runMaintenance: task => task(new AbortController().signal),
|
||||||
|
|||||||
@@ -18,8 +18,9 @@ function fakeAgent(ctx: Context, rawId: string): Agent {
|
|||||||
const id = SessionId(rawId)
|
const id = SessionId(rawId)
|
||||||
const session = Session.create(id)
|
const session = Session.create(id)
|
||||||
const agent: Agent = {
|
const agent: Agent = {
|
||||||
id, options: {}, session, inbox: new Inbox(session, { inserted: () => {}, discarded: () => {} }),
|
id, options: {}, session, inbox: new Inbox(session, { inserted: () => {}, discarded: () => {}, claimed: () => {} }),
|
||||||
status: 'idle', ctx: scope.ctx,
|
status: 'idle',
|
||||||
|
ctx: scope.ctx,
|
||||||
send: () => {},
|
send: () => {},
|
||||||
followup: () => {}, steer: () => {}, inject: () => {}, cancel() {},
|
followup: () => {}, steer: () => {}, inject: () => {}, cancel() {},
|
||||||
runMaintenance: task => task(new AbortController().signal),
|
runMaintenance: task => task(new AbortController().signal),
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ function agentForCwd(cwd: string): Agent {
|
|||||||
id,
|
id,
|
||||||
options: {},
|
options: {},
|
||||||
session,
|
session,
|
||||||
inbox: new Inbox(session, { inserted: () => {}, discarded: () => {} }),
|
inbox: new Inbox(session, { inserted: () => {}, discarded: () => {}, claimed: () => {} }),
|
||||||
status: 'idle',
|
status: 'idle',
|
||||||
send: () => {},
|
send: () => {},
|
||||||
followup: () => {},
|
followup: () => {},
|
||||||
@@ -61,7 +61,7 @@ function sessionAgent(session: Session, id = 'tool-skill-agent'): Agent {
|
|||||||
id: SessionId(id),
|
id: SessionId(id),
|
||||||
options: {},
|
options: {},
|
||||||
session,
|
session,
|
||||||
inbox: new Inbox(session, { inserted: () => {}, discarded: () => {} }),
|
inbox: new Inbox(session, { inserted: () => {}, discarded: () => {}, claimed: () => {} }),
|
||||||
status: 'running',
|
status: 'running',
|
||||||
ctx: new Context(),
|
ctx: new Context(),
|
||||||
send: () => {},
|
send: () => {},
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ function stubAgent(ctx: Context, rawId: string): Agent {
|
|||||||
id,
|
id,
|
||||||
options: {},
|
options: {},
|
||||||
session,
|
session,
|
||||||
inbox: new Inbox(session, { inserted: () => {}, discarded: () => {} }),
|
inbox: new Inbox(session, { inserted: () => {}, discarded: () => {}, claimed: () => {} }),
|
||||||
status: 'idle' as const,
|
status: 'idle' as const,
|
||||||
ctx: scopeFiber.ctx,
|
ctx: scopeFiber.ctx,
|
||||||
send: () => {},
|
send: () => {},
|
||||||
|
|||||||
Reference in New Issue
Block a user