import type { ExtensionContext } from "@earendil-works/pi-coding-agent"; import { applyGraphPatch } from "../domain/apply-patch.ts"; import { deriveAttentionView } from "../domain/attention.ts"; import { createInitialState } from "../domain/initial-state.ts"; import { normalizePatch } from "../domain/normalize-patch.ts"; import type { GraphPatchParams } from "../domain/schemas.ts"; import { UNKNOWN_EXTENSION_IDENTITY, type ExtensionIdentity, } from "../extension/identity.ts"; import { snapshotHash } from "../domain/snapshot.ts"; import type { AttentionView, CommittedBatch, GraphCheckpointDetails, GraphPatch, WorkflowState, } from "../domain/types.ts"; import { sessionProjectionDir, writeActivityProjection, writeProjectionFiles, type CurrentProjectionFile, } from "../persistence/projection-files.ts"; import { SqliteStore, type StoreStats } from "../persistence/sqlite-store.ts"; import { reconstructFromBranch, type ReconstructedCheckpoint, } from "./branch-reconstruction.ts"; export interface DoctorReport { version: string; sourcePath: string; sourceKind: string; installedUserVersion?: string; revision: number; snapshotHash: string; branchCheckpoints: number; invalidBranchCheckpoints: number; ignoredDivergentCheckpoints: number; branchIssues: Array<{ entryId: string; revision?: number; kind: string; reason: string }>; migratedBranchCheckpoints: number; branchHashVerified: boolean; projectionPath?: string; projectionError?: string; activityProjectionError?: string; sqlite: StoreStats | undefined; } export type RuntimeListener = () => void; interface ActiveToolActivity { toolCallId: string; toolName: string; startedAt: number; lastActivityAt: number; } interface RecentToolActivity extends ActiveToolActivity { endedAt: number; isError: boolean; } export interface RuntimeActivityView { status: "idle" | "running" | "completed" | "failed"; summary: string; activeCount: number; toolName?: string; elapsedMs?: number; idleMs?: number; } function durationLabel(milliseconds: number): string { const totalSeconds = Math.max(0, Math.floor(milliseconds / 1_000)); const minutes = Math.floor(totalSeconds / 60); const seconds = totalSeconds % 60; return minutes > 0 ? `${minutes}m ${String(seconds).padStart(2, "0")}s` : `${seconds}s`; } function normalizedStatement(value: string): string { return value.trim().replace(/\s+/g, " "); } function messageText(content: unknown): string { if (typeof content === "string") return content; if (!Array.isArray(content)) return ""; return content .map((part) => part && typeof part === "object" && "text" in part && typeof part.text === "string" ? part.text : "", ) .join("\n"); } function bindTrustedHumanEvidence(patch: GraphPatch, ctx: ExtensionContext): GraphPatch { const humanMessages = ctx.sessionManager .getBranch() .flatMap((entry) => { if (entry.type !== "message" || entry.message.role !== "user") return []; return [{ id: entry.id, text: normalizedStatement(messageText(entry.message.content)) }]; }) .filter((item) => item.text.length > 0); const trustedHumanEvidenceIds: string[] = []; const ops = patch.ops.map((op) => { if (op.op !== "attach_evidence" || op.evidence.type !== "human_statement") return op; const summary = normalizedStatement(op.evidence.summary); const source = [...humanMessages].reverse().find((message) => message.text === summary); if (!source) { return { ...op, evidence: { ...op.evidence, type: "agent_declaration" as const, source: `unverified-human-claim:${op.evidence.source}`, }, }; } trustedHumanEvidenceIds.push(op.evidence.evidenceId); return { ...op, evidence: { ...op.evidence, source: `pi:user-entry:${source.id}` }, }; }); return { ...patch, ops, ...(trustedHumanEvidenceIds.length > 0 ? { trustedHumanEvidenceIds } : {}), }; } interface PendingCheckpoint { state: WorkflowState; view: AttentionView; batch: CommittedBatch; migratedFromV1: boolean; } export class CorridorRuntime { private state: WorkflowState = createInitialState(); private view: AttentionView = deriveAttentionView(this.state); private history: ReconstructedCheckpoint[] = []; private store: SqliteStore | undefined; private sessionId = ""; private branchHeadEntryId: string | undefined; private initialized = false; private migratedFromV1 = false; private projection: CurrentProjectionFile | undefined; private projectionError: string | undefined; private activityProjectionError: string | undefined; private readonly listeners = new Set(); private readonly pendingCheckpoints = new Map(); private readonly activeTools = new Map(); private recentTool: RecentToolActivity | undefined; private activityTicker: ReturnType | undefined; private recentToolTimer: ReturnType | undefined; constructor(private readonly identity: ExtensionIdentity = UNKNOWN_EXTENSION_IDENTITY) {} initialize(ctx: ExtensionContext): void { const reconstruction = reconstructFromBranch(ctx.sessionManager); this.pendingCheckpoints.clear(); this.state = reconstruction.state; this.view = deriveAttentionView(this.state); this.history = reconstruction.history; this.branchHeadEntryId = reconstruction.branchHeadEntryId; this.migratedFromV1 = reconstruction.migratedCheckpoints > 0; if (reconstruction.invalidCheckpoints > 0 && ctx.hasUI) { const firstIssue = reconstruction.issues.find((issue) => issue.kind === "invalid"); ctx.ui.notify( `Intent Petri skipped ${reconstruction.invalidCheckpoints} invalid branch checkpoint(s): ${firstIssue?.reason ?? "unknown validation error"}`, "warning", ); } if (reconstruction.ignoredDivergentCheckpoints > 0 && ctx.hasUI) { ctx.ui.notify( `Intent Petri recovered revision ${reconstruction.state.revision} and ignored ${reconstruction.ignoredDivergentCheckpoints} stale rollback checkpoint(s).`, "info", ); } if (this.identity.sourceKind === "project-local" && this.identity.installedUserVersion && ctx.hasUI) { ctx.ui.notify( `Intent Petri v${this.identity.version} is loaded from project-local source ${this.identity.sourcePath}; user npm v${this.identity.installedUserVersion} is also installed. Restart without -e . to use the npm package.`, "warning", ); } if (this.identity.sourceKind === "project-npm" && this.identity.installedUserVersion && ctx.hasUI) { ctx.ui.notify( `Intent Petri v${this.identity.version} is loaded from this project's .pi/npm and overrides user npm v${this.identity.installedUserVersion}. Update or remove the project-scoped package to use the user version.`, "warning", ); } const nextSessionId = ctx.sessionManager.getSessionId(); if (!this.store || this.sessionId !== nextSessionId) { this.store?.close(); this.store = new SqliteStore(); this.sessionId = nextSessionId; } this.initialized = true; this.writeProjection(); this.writeActivity(); this.notifyListeners(); } close(_ctx?: ExtensionContext): void { this.store?.close(); this.store = undefined; this.initialized = false; this.pendingCheckpoints.clear(); this.activeTools.clear(); this.recentTool = undefined; if (this.activityTicker) clearInterval(this.activityTicker); if (this.recentToolTimer) clearTimeout(this.recentToolTimer); this.activityTicker = undefined; this.recentToolTimer = undefined; if (this.sessionId) { try { writeActivityProjection(this.sessionId, { status: "closed", summary: "Pi session closed", activeCount: 0, }); } catch { // Activity is a best-effort external projection, never branch authority. } } this.listeners.clear(); } apply(params: GraphPatchParams, ctx: ExtensionContext, toolCallId: string): GraphCheckpointDetails { const patch = bindTrustedHumanEvidence(normalizePatch(params), ctx); const result = applyGraphPatch(this.state, patch); if (result.status === "applied") { this.state = result.state; this.view = deriveAttentionView(this.state); this.pendingCheckpoints.set(toolCallId, { state: structuredClone(this.state), view: structuredClone(this.view), batch: structuredClone(result.batch), migratedFromV1: this.migratedFromV1, }); const migratedFromSchemaVersion = this.migratedFromV1 ? (1 as const) : undefined; return { kind: "intent-petri/graph-checkpoint", checkpointSchemaVersion: 2, ...(migratedFromSchemaVersion ? { migratedFromSchemaVersion } : {}), status: result.status, patchId: patch.patchId, revision: this.state.revision, snapshotHash: result.batch.snapshotHash, state: this.state, batch: result.batch, attention: this.view, }; } if (result.status === "already_applied") { return { kind: "intent-petri/graph-checkpoint", checkpointSchemaVersion: 2, status: result.status, patchId: patch.patchId, revision: result.revision, snapshotHash: result.snapshotHash, attention: this.view, }; } return { kind: "intent-petri/graph-checkpoint", checkpointSchemaVersion: 2, status: result.status, patchId: patch.patchId, revision: result.currentRevision, snapshotHash: snapshotHash(this.state), attention: this.view, error: result.error, }; } finalizePersistedToolResult(toolCallId: string, ctx: ExtensionContext): boolean { const pending = this.pendingCheckpoints.get(toolCallId); if (!pending) return false; const reconstruction = reconstructFromBranch(ctx.sessionManager); const checkpoint = reconstruction.history.find((item) => item.toolCallId === toolCallId); if ( !checkpoint || !checkpoint.batch || checkpoint.sourceSchemaVersion !== 2 || snapshotHash(checkpoint.state) !== pending.batch.snapshotHash ) { this.pendingCheckpoints.delete(toolCallId); this.state = reconstruction.state; this.view = deriveAttentionView(this.state); this.history = reconstruction.history; this.branchHeadEntryId = reconstruction.branchHeadEntryId; this.migratedFromV1 = reconstruction.migratedCheckpoints > 0; if (ctx.hasUI) ctx.ui.notify("Intent Petri discarded an unpersisted or invalid tentative checkpoint.", "warning"); return false; } this.state = reconstruction.state; this.view = deriveAttentionView(this.state); this.history = reconstruction.history; this.branchHeadEntryId = reconstruction.branchHeadEntryId; this.store?.record( this.sessionId || ctx.sessionManager.getSessionId(), toolCallId, checkpoint.parentEntryId ?? null, pending.batch, pending.state, ); this.pendingCheckpoints.delete(toolCallId); if (pending.migratedFromV1) this.migratedFromV1 = false; this.writeProjection(); this.notifyListeners(); return true; } getState(): WorkflowState { return structuredClone(this.state); } getView(): AttentionView { return structuredClone(this.view); } getHistory(): ReconstructedCheckpoint[] { return structuredClone(this.history); } getIdentity(): ExtensionIdentity { return { ...this.identity }; } getProjection(): CurrentProjectionFile | undefined { return this.projection ? structuredClone(this.projection) : undefined; } getProjectionPath(): string | undefined { return this.sessionId ? sessionProjectionDir(this.sessionId) : undefined; } getSessionId(): string | undefined { return this.sessionId || undefined; } toolStarted(toolCallId: string, toolName: string, _ctx: ExtensionContext, now = Date.now()): void { this.recentTool = undefined; if (this.recentToolTimer) clearTimeout(this.recentToolTimer); this.recentToolTimer = undefined; this.activeTools.set(toolCallId, { toolCallId, toolName, startedAt: now, lastActivityAt: now }); this.ensureActivityTicker(); this.publishActivity(); } toolUpdated(toolCallId: string, _ctx: ExtensionContext, now = Date.now()): void { const activity = this.activeTools.get(toolCallId); if (!activity) return; activity.lastActivityAt = now; this.publishActivity(); } toolEnded(toolCallId: string, isError: boolean, _ctx: ExtensionContext, now = Date.now()): void { const activity = this.activeTools.get(toolCallId); if (!activity) return; this.activeTools.delete(toolCallId); this.recentTool = { ...activity, lastActivityAt: now, endedAt: now, isError }; if (this.activeTools.size === 0 && this.activityTicker) { clearInterval(this.activityTicker); this.activityTicker = undefined; } if (this.recentToolTimer) clearTimeout(this.recentToolTimer); this.recentToolTimer = setTimeout(() => { this.recentTool = undefined; this.publishActivity(); }, 5_000); this.recentToolTimer.unref?.(); this.publishActivity(); } isToolActive(toolCallId: string): boolean { return this.activeTools.has(toolCallId); } getActivity(now = Date.now()): RuntimeActivityView { if (this.activeTools.size > 0) { const activity = [...this.activeTools.values()].sort((a, b) => a.startedAt - b.startedAt)[0]!; const elapsedMs = now - activity.startedAt; const idleMs = now - activity.lastActivityAt; const additional = this.activeTools.size > 1 ? ` +${this.activeTools.size - 1}` : ""; const heartbeat = idleMs >= 5_000 ? `no progress event for ${durationLabel(idleMs)} · still running` : `active ${durationLabel(idleMs)} ago`; return { status: "running", summary: `${activity.toolName}${additional} · ${durationLabel(elapsedMs)} elapsed · ${heartbeat}`, activeCount: this.activeTools.size, toolName: activity.toolName, elapsedMs, idleMs, }; } if (this.recentTool) { const elapsedMs = this.recentTool.endedAt - this.recentTool.startedAt; return { status: this.recentTool.isError ? "failed" : "completed", summary: `${this.recentTool.toolName} · ${this.recentTool.isError ? "failed" : "completed"} in ${durationLabel(elapsedMs)}`, activeCount: 0, toolName: this.recentTool.toolName, elapsedMs, idleMs: 0, }; } return { status: "idle", summary: "No tool running", activeCount: 0 }; } subscribe(listener: RuntimeListener): () => void { this.listeners.add(listener); return () => this.listeners.delete(listener); } doctor(ctx: ExtensionContext): DoctorReport { const reconstructed = reconstructFromBranch(ctx.sessionManager); const projectionPath = this.getProjectionPath(); return { version: this.identity.version, sourcePath: this.identity.sourcePath, sourceKind: this.identity.sourceKind, ...(this.identity.installedUserVersion ? { installedUserVersion: this.identity.installedUserVersion } : {}), revision: this.state.revision, snapshotHash: snapshotHash(this.state), branchCheckpoints: reconstructed.checkpoints, invalidBranchCheckpoints: reconstructed.invalidCheckpoints, ignoredDivergentCheckpoints: reconstructed.ignoredDivergentCheckpoints, branchIssues: reconstructed.issues.map((issue) => ({ ...issue })), migratedBranchCheckpoints: reconstructed.migratedCheckpoints, branchHashVerified: reconstructed.hashVerified, ...(projectionPath ? { projectionPath } : {}), ...(this.projectionError ? { projectionError: this.projectionError } : {}), ...(this.activityProjectionError ? { activityProjectionError: this.activityProjectionError } : {}), sqlite: this.store?.stats(this.sessionId || ctx.sessionManager.getSessionId()), }; } toAgentContext(): string { const transitions = Object.values(this.state.transitions) .filter((transition) => transition.status === "active" || transition.status === "planned") .sort((a, b) => { const rank = (status: string) => (status === "active" ? 0 : 1); return rank(a.status) - rank(b.status) || a.id.localeCompare(b.id); }) .slice(0, 24) .map((transition) => ({ id: transition.id, status: transition.status, commitment: transition.planning.commitment, intent: transition.intent, dependsOn: transition.planning.dependsOn, reconsiderWhen: transition.planning.reconsiderWhen ?? null, refinementTrigger: transition.planning.refinementTrigger ?? null, whyNow: transition.contract.whyNow ?? null, scope: transition.contract.scope ?? null, expectedEvidence: transition.contract.expectedEvidence, exitCondition: transition.contract.exitCondition ?? null, failureCondition: transition.contract.failureCondition ?? null, })); return [ "Intent Petri current action-path state:", JSON.stringify( { schemaVersion: this.state.schemaVersion, revision: this.state.revision, rootIntent: this.state.rootIntent ?? null, position: this.view.position, currentStack: this.view.stack, future: this.view.future, token: this.state.token, transitions, refinements: Object.values(this.state.refinements).map((refinement) => ({ id: refinement.id, parentTransitionId: refinement.parentTransitionId, status: refinement.status, outcome: refinement.outcome ?? null, })), }, null, 2, ), "The current execution position must be structurally unambiguous. Future transitions may be provisional or directional and must be promoted to committed before activation.", "Use update_action_path only at genuine strategy boundaries. Use baseRevision exactly as shown. Do not expose hidden reasoning.", ].join("\n"); } private writeProjection(): void { if (!this.initialized || !this.sessionId) return; try { this.projection = writeProjectionFiles({ sessionId: this.sessionId, ...(this.branchHeadEntryId ? { branchHeadEntryId: this.branchHeadEntryId } : {}), state: this.state, attention: this.view, history: this.history, }); this.projectionError = this.projection.renderError; } catch (error) { this.projectionError = error instanceof Error ? error.message : String(error); } } private ensureActivityTicker(): void { if (this.activityTicker) return; // Sparse heartbeat for external web hub only. Avoid 1s disk writes that stall Pi input. this.activityTicker = setInterval(() => { this.publishActivity(); }, 5_000); this.activityTicker.unref?.(); } private publishActivity(): void { this.writeActivity(); this.notifyListeners(); } private notifyListeners(): void { for (const listener of this.listeners) listener(); } private writeActivity(): void { if (!this.initialized || !this.sessionId) return; try { writeActivityProjection(this.sessionId, this.getActivity()); this.activityProjectionError = undefined; } catch (error) { this.activityProjectionError = error instanceof Error ? error.message : String(error); } } }