36 lines
1.3 KiB
TypeScript
36 lines
1.3 KiB
TypeScript
// Pure settle decision for the goal loop — extracted so the abort/error/cap
|
|
// paths (AC4, AC5) are hermetically testable without a live agent run.
|
|
|
|
import { checkLimitReached, type GoalState } from "./state.ts";
|
|
|
|
export type SettleDecision = { action: "inject" } | { action: "wait" } | { action: "pause"; reason: string };
|
|
|
|
/** An untimed wait never spends model tokens on automatic reconciliation. */
|
|
export function hasUnresolvedWait(state: GoalState): boolean {
|
|
return !!state.activeWait && (!state.waitTimeoutSeconds || !state.activeWait.wakeSent);
|
|
}
|
|
|
|
/**
|
|
* Decide what the loop does when the agent settles while a goal is active.
|
|
* stopReason comes from the last assistant message ("stop", "toolUse",
|
|
* "aborted", "error", ...).
|
|
*/
|
|
export function decideSettle(state: GoalState, stopReason: string | undefined): SettleDecision {
|
|
if (stopReason === "aborted") {
|
|
return { action: "pause", reason: "paused: run aborted (Esc)" };
|
|
}
|
|
if (stopReason === "error") {
|
|
return { action: "pause", reason: "paused: run error" };
|
|
}
|
|
if (hasUnresolvedWait(state)) {
|
|
return { action: "wait" };
|
|
}
|
|
if (checkLimitReached(state)) {
|
|
return {
|
|
action: "pause",
|
|
reason: `paused: cap of ${state.maxChecks} checks reached without a report`,
|
|
};
|
|
}
|
|
return { action: "inject" };
|
|
}
|