import type { SessionId, TurnId } from '../../types/ids/index.js'; import type { Plan, PlanApprovalRequest, PlanApprovalResponse, PlanStep } from '../../types/plan/index.js'; export interface PlanEvent { type: 'plan.generating' | 'plan.ready' | 'plan.approved' | 'plan.rejected' | 'plan.executing' | 'plan.step_updated' | 'plan.completed' | 'plan.failed'; plan: Plan; step?: PlanStep; } export type PlanEventListener = (event: PlanEvent) => void | Promise; export type PlanApprovalHandler = (request: PlanApprovalRequest) => Promise; /** * The plan a turn declares, and the gate a host approves it through. * * **The kernel deliberately drives only part of this class.** It builds a plan * (`approve_plan` calls `startGenerating` / `addStep` / `markReady`), gates it * (`iteration/phases/context.ts` calls `approve` and `startExecution`), * translates its events onto the session event stream (`EventTranslator.wirePlanManager`), * and settles it on failure (`runtime/query/result.ts` calls `failPlan`). It * never reports a step outcome and never settles a plan that succeeded. * * That is a split, not an omission — `drainQuery` hands the manager to the host * through `onContextCreated({ planManager })` BEFORE the iteration loop starts, * precisely so a host can drive the half the kernel does not. So a grep for * callers of `updateStepStatus` or `completePlan` inside this package finds * none, and that is not evidence the methods are dead: the callers are hosts, * and they are outside the repository. `PlanManager` is exported from * `public-runtime.ts` for this reason. * * Recorded here because the absence has already been read once as a dead layer * and proposed for deletion. What it would have deleted is a working * human-in-the-loop approval gate. * * The one genuine gap in the split is tracked separately: nothing settles a * plan that SUCCEEDED, so its status can reach `failed` or stay `executing` * but never `completed`. Fixing that needs a decision about what a * kernel-built plan's steps mean, not a guessed status — see `completePlan`. */ export declare class PlanManager { private currentPlan; private readonly scope; private listeners; private approvalHandler?; /** `scope` is the turn the plan belongs to; every plan and approval request carries it. */ constructor(scope: { readonly sessionId: SessionId; readonly turnId: TurnId; }, approvalHandler?: PlanApprovalHandler); setApprovalHandler(handler: PlanApprovalHandler): void; on(listener: PlanEventListener): () => void; private emit; get active(): Plan | null; get isActive(): boolean; get needsApproval(): boolean; /** * Steps that have not said how they went — the reason `completePlan` may * refuse, exposed so a caller can ask before it commits. * * The kernel settles a successful plan only when this is empty. It cannot * catch the refusal instead: a throw on the success path would make a turn * that worked into one that crashed on its way out, which is a worse * version of the bug the refusal exists to prevent. */ get unreportedSteps(): readonly PlanStep[]; startGenerating(title: string): Plan; addStep(step: Omit): PlanStep | null; markReady(summary?: string): Plan | null; requestApproval(): Promise; approve(modifiedSteps?: PlanStep[]): Plan | null; startExecution(): Plan | null; updateStepStatus(stepId: string, status: PlanStep['status'], error?: string): PlanStep | null; /** * Settle the plan, computing its outcome from its steps. * * A step that is still `pending` or `running` used to land here as * **`failed`**, because the test was "is every step completed or skipped" * and anything else fell to the same branch. So a caller that added steps, * did the work, and settled the plan without reporting each step got * `failed` for a plan that fully succeeded — and `addStep` defaults every * step to `pending`, so that is the path of least effort, not an unusual * one. * * The two cases are different facts and want different responses. A step * that FAILED is an outcome: the plan failed, report it. A step nobody * reported on is not an outcome at all — it says the caller and this plan * disagree about whether the work is over, and answering "failed" resolves * that disagreement by inventing a result. * * So an unfinished step is refused rather than scored. The message names * the steps and the two ways out, because a caller in this position either * forgot to report progress or called too early, and only they know which. */ completePlan(): Plan | null; failPlan(error: string): Plan | null; getNextPendingStep(): PlanStep | null; reset(): void; } //# sourceMappingURL=lifecycle.d.ts.map