fix(coding-agent): suppress WIP advisor non-blockers
This commit is contained in:
@@ -180,12 +180,23 @@ export class AdviseTool implements AgentTool<typeof adviseSchema, AdviseDetails>
|
||||
* escalation: nit → concern → blocker), so an advisor cannot bypass dedupe
|
||||
* by retagging the same text at a lower or equal severity. */
|
||||
#deliveredNoteSeverities = new Map<string, number>();
|
||||
#inProgressUpdate = false;
|
||||
|
||||
constructor(private readonly onAdvice: (note: string, severity?: AdviseDetails["severity"]) => void) {}
|
||||
|
||||
/**
|
||||
* Mark whether the next advisor prompt reviews an in-progress primary turn.
|
||||
* Non-blockers are withheld until a completed update so partial work does
|
||||
* not interrupt the primary before it can finish its planned steps.
|
||||
*/
|
||||
beginUpdate(inProgress: boolean): void {
|
||||
this.#inProgressUpdate = inProgress;
|
||||
}
|
||||
|
||||
/** Clear delivered-note memory when the advisor starts a fresh conversation. */
|
||||
resetDeliveredNotes(): void {
|
||||
this.#deliveredNoteSeverities.clear();
|
||||
this.#inProgressUpdate = false;
|
||||
}
|
||||
|
||||
async execute(
|
||||
@@ -195,6 +206,13 @@ export class AdviseTool implements AgentTool<typeof adviseSchema, AdviseDetails>
|
||||
_onUpdate?: AgentToolUpdateCallback<AdviseDetails>,
|
||||
_context?: AgentToolContext,
|
||||
): Promise<AgentToolResult<AdviseDetails>> {
|
||||
if (this.#inProgressUpdate && args.severity !== "blocker") {
|
||||
return {
|
||||
content: [{ type: "text", text: "Recorded." }],
|
||||
details: { note: args.note, severity: args.severity },
|
||||
useless: true,
|
||||
};
|
||||
}
|
||||
const key = advisorNoteDedupeKey(args.note);
|
||||
const rank = advisorSeverityRank(args.severity);
|
||||
const previousRank = this.#deliveredNoteSeverities.get(key) ?? 0;
|
||||
|
||||
@@ -51,11 +51,11 @@ export interface AdvisorRuntimeHost {
|
||||
maintainContext?(incomingTokens: number, signal: AbortSignal): Promise<boolean>;
|
||||
/**
|
||||
* Called immediately before each `agent.prompt(batch)` cycle. Lets the host
|
||||
* clear per-update advisor state — currently the one-advise-per-update gate
|
||||
* in {@link AdvisorEmissionGuard}, which the host owns because it is the
|
||||
* one that routes `advise()` results back to the primary.
|
||||
* clear per-update advisor state and apply the in-progress delivery policy.
|
||||
* The host owns these gates because it routes `advise()` results back to the
|
||||
* primary.
|
||||
*/
|
||||
beginAdvisorUpdate?(): void;
|
||||
beginAdvisorUpdate?(inProgress: boolean): void;
|
||||
/**
|
||||
* Called with the error of every failed advisor turn, before the retry sleep
|
||||
* or the dropped-after-3 path. Lets the host apply credential-level remedies
|
||||
@@ -909,8 +909,8 @@ export class AdvisorRuntime {
|
||||
const contextWasFresh = resetContext || recoveringOverflow || messageSnapshot === 0;
|
||||
try {
|
||||
// Reset the host's per-update advisor state (one-advise-per-update
|
||||
// gate) before each model cycle so the new batch starts fresh.
|
||||
this.host.beginAdvisorUpdate?.();
|
||||
// gate) and pass through whether this batch reviews partial work.
|
||||
this.host.beginAdvisorUpdate?.(wip);
|
||||
const prompt = this.agent.prompt(batch);
|
||||
this.#promptInFlight = prompt;
|
||||
try {
|
||||
|
||||
@@ -877,7 +877,10 @@ export class SessionAdvisors {
|
||||
this.#maintainAdvisorContext(advisorRef, incomingTokens, signal),
|
||||
obfuscator: this.#host.obfuscator,
|
||||
getModelIdentity: () => formatModelString(advisorRef.agent.state.model),
|
||||
beginAdvisorUpdate: () => advisorRef.emissionGuard.beginUpdate(),
|
||||
beginAdvisorUpdate: inProgress => {
|
||||
advisorRef.adviseTool.beginUpdate(inProgress);
|
||||
advisorRef.emissionGuard.beginUpdate();
|
||||
},
|
||||
onTurnError: (error, failedMessages, signal) =>
|
||||
this.#recoverAdvisorTurn(advisorRef, error, failedMessages, signal),
|
||||
onTurnSuccess: async () => {
|
||||
|
||||
Reference in New Issue
Block a user