Team events: a change inside the pace window is said at its end, so the last state always goes out; the main agent waiting for a slot is the waiting_for_model phase
ci / check (push) Waiting to run

Found by the full suite: the link's tree could miss a subagent's last change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
HomerandClaude Opus 5.5 committed 2026-11-10 08:38:12 +00:00
1 parent ae1201540a
commit f3db970c9d
2 files changed
+15 -3

No files matched your search

+5 -1
View File
@@ -752,7 +752,11 @@ export function createApp(o: AppOptions): App {
teamRt.memberDone(member.name, "", `error — ${(e as Error).message}`, []) teamRt.memberDone(member.name, "", `error — ${(e as Error).message}`, [])
}) })
}, },
lead: { note: (text) => engine.note(text), wake: (text) => wake(text, shownLine(text)) }, lead: {
note: (text) => engine.note(text),
wake: (text) => wake(text, shownLine(text)),
waiting: (model) => engine.progress.set({ phase: "waiting_for_model", model: engine.model.spec.name ?? model, reason: "slots" }),
},
tools: () => tools, tools: () => tools,
resolve: (agent, model) => { resolve: (agent, model) => {
if (!agentList.some((a) => a.name === agent)) return { error: `There is no agent "${agent}". The agents are: ${agentList.map((a) => a.name).join(", ")}.` } if (!agentList.some((a) => a.name === agent)) return { error: `There is no agent "${agent}". The agents are: ${agentList.map((a) => a.name).join(", ")}.` }
+10 -2
View File
@@ -29,7 +29,7 @@ export interface RuntimeOptions {
/** Make a member's engine (attached here with `attach`) and start its first turn with `text`. */ /** Make a member's engine (attached here with `attach`) and start its first turn with `text`. */
spawnMember(member: TeamAgent, text: string): void spawnMember(member: TeamAgent, text: string): void
/** The lead: a message at its next step (`note`), or — idle — a turn of its own (`wake`). */ /** The lead: a message at its next step (`note`), or — idle — a turn of its own (`wake`). */
lead: { note(text: string): void; wake(text: string): void } lead: { note(text: string): void; wake(text: string): void; /** Spec 3.4.0: its progress while it waits for a slot. */ waiting?(model: string): void }
/** An unknown agent or model, answered as task answers it; undefined when both are known. The /** An unknown agent or model, answered as task answers it; undefined when both are known. The
* model's ref as the session knows it. */ * model's ref as the session knows it. */
resolve?(agent: string, model: string | undefined): { error: string } | { model: string } resolve?(agent: string, model: string | undefined): { error: string } | { model: string }
@@ -74,17 +74,24 @@ export class TeamRuntime {
/** Said on the lead's bus as `team`, at most four times a second (acp.md `team` event). */ /** Said on the lead's bus as `team`, at most four times a second (acp.md `team` event). */
private changed() { private changed() {
if (this.pace) return // A change inside the pace window is said once at its end, so the last state always goes out.
if (this.pace) return void (this.trailing = true)
this.o.bus.emit({ type: "team" }) this.o.bus.emit({ type: "team" })
this.pace = setTimeout(() => { this.pace = setTimeout(() => {
this.pace = undefined this.pace = undefined
if (this.trailing) {
this.trailing = false
this.changed()
}
}, 1000 / 4) }, 1000 / 4)
} }
private trailing = false
/** Something changed that a reader must see now (the tools' answers, tests): said at once. */ /** Something changed that a reader must see now (the tools' answers, tests): said at once. */
private changedNow() { private changedNow() {
if (this.pace) clearTimeout(this.pace) if (this.pace) clearTimeout(this.pace)
this.pace = undefined this.pace = undefined
this.trailing = false
this.o.bus.emit({ type: "team" }) this.o.bus.emit({ type: "team" })
} }
@@ -206,6 +213,7 @@ export class TeamRuntime {
return true return true
} }
this.o.bus.emit({ type: "notice", message: `waiting for a free slot: the model serves ${parallelFrom(this.team.capacityOf(this.team.model))} requests at once and the team's members hold them` }) this.o.bus.emit({ type: "notice", message: `waiting for a free slot: the model serves ${parallelFrom(this.team.capacityOf(this.team.model))} requests at once and the team's members hold them` })
this.o.lead.waiting?.(this.team.model)
this.changed() this.changed()
return await new Promise<boolean>((resolve) => { return await new Promise<boolean>((resolve) => {
const onAbort = () => { const onAbort = () => {