{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../../src/watchdog/runtime.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash } from \"node:crypto\";\nimport type { ExtensionContext } from \"@lpb-work/pi-coding-agent\";\nimport {\n\tcomputeWatchdogRepoChangeSignature,\n\teventIndicatesRepoEdit,\n\ttype WatchdogRepoChangeSignature,\n} from \"./change-signature.ts\";\nimport { WatchdogEmissionGuard } from \"./emission-guard.ts\";\nimport {\n\tcollectWatchdogLspDiagnostics,\n\tformatWatchdogLspDiagnosticsBlock,\n\ttype WatchdogLspDiagnosticsFunction,\n\tWatchdogLspDiagnosticsLedger,\n\twatchdogWarningFromLspDiagnostics,\n} from \"./lsp-diagnostics.ts\";\nimport { isWatchdogAutoFollowPromptEvent, WatchdogScopeArtifact } from \"./scope.ts\";\nimport { resolveWatchdogConfig } from \"./settings.ts\";\nimport { formatWatchdogTurnDelta } from \"./turn-delta.ts\";\nimport type {\n\tResolvedWatchdogConfig,\n\tWatchdogEndpointConfig,\n\tWatchdogLspRuntimeSnapshot,\n\tWatchdogRuntimeStatus,\n\tWatchdogSettingsError,\n\tWatchdogSettingsResult,\n\tWatchdogSettingsSource,\n\tWatchdogWarning,\n\tWatchdogWarningDetails,\n} from \"./types.ts\";\nimport { normalizeWatchdogWarningDetails } from \"./warning-format.ts\";\n\ntype ReviewStopReason = \"stop\" | \"error\" | \"aborted\" | \"length\";\n\nexport interface WatchdogReviewResult {\n\twarnings?: WatchdogWarning[];\n\tstopReason?: ReviewStopReason;\n}\n\nexport interface WatchdogReviewRequest {\n\tdelta: string;\n\tepoch: number;\n\thasScope: boolean;\n\treviewId: number;\n\tconfig: ResolvedWatchdogConfig;\n\temitWarning(warning: WatchdogWarning): boolean;\n\tsignal?: AbortSignal;\n}\n\nexport type WatchdogReviewFunction = (\n\trequest: WatchdogReviewRequest,\n) => Promise<WatchdogReviewResult | undefined> | WatchdogReviewResult | undefined;\n\nexport interface WatchdogRuntimeSnapshot {\n\tstatus: WatchdogRuntimeStatus;\n\tenabled: boolean;\n\tconfig: ResolvedWatchdogConfig;\n\tconfigOk: boolean;\n\terrors: WatchdogSettingsError[];\n\tsources: WatchdogSettingsSource[];\n\tbufferedDeltas: number;\n\tepoch: number;\n\tactiveReviewId?: number;\n\tsessionOverride?: boolean;\n\tsessionModelOverride?: Partial<Pick<WatchdogEndpointConfig, \"model\" | \"thinking\">>;\n\tlastWarning?: WatchdogWarningDetails;\n\tlastError?: string;\n\tfailedReviews: number;\n\tstaleReviews: number;\n\treviewConnected: boolean;\n\treviewDescription: string;\n\tautoFollowQueued: boolean;\n\tautoFollowAttempts: number;\n\tautoFollowStalemate: boolean;\n\treviewTrigger: \"turn-delta\" | \"repo-edits\";\n\tchangedPaths?: string[];\n\tlsp: WatchdogLspRuntimeSnapshot;\n}\n\ninterface Waiter {\n\tresolve(settled: boolean): void;\n\ttimer: ReturnType<typeof setTimeout>;\n}\n\ninterface MainWatchdogRuntimeOptions {\n\tcwd?: string;\n\tresolveConfig?: (cwd: string, options?: { session?: Record<string, unknown> }) => WatchdogSettingsResult;\n\treview?: WatchdogReviewFunction;\n\treviewDescription?: string;\n\tdisplayWarning?: (warning: WatchdogWarningDetails, options?: { deliverAs?: \"steer\" }) => void;\n\tsendUserMessage?: (message: string) => void | Promise<void>;\n\treviewChangesOnly?: boolean;\n\tlspDiagnostics?: WatchdogLspDiagnosticsFunction;\n\trepoChangeSignature?: typeof computeWatchdogRepoChangeSignature;\n}\n\ntype ContextLike = Pick<ExtensionContext, \"cwd\">;\ntype ReviewDeltaOutcome = \"completed\" | \"timeout\" | \"stale\";\n\nconst DEFAULT_REVIEW: WatchdogReviewFunction = () => ({ warnings: [] });\nconst MAX_REVIEW_INPUT_CHARS = 24_000;\nconst REVIEW_DELTA_SEPARATOR = \"\\n\\n---\\n\\n\";\n\nfunction errorMessage(error: unknown): string {\n\treturn error instanceof Error ? error.message : String(error);\n}\n\nfunction promptFromBeforeAgentStart(event: unknown): string | undefined {\n\tif (!event || typeof event !== \"object\") return undefined;\n\tconst input = event as { prompt?: unknown; systemPrompt?: unknown };\n\tif (typeof input.prompt === \"string\") return input.prompt;\n\tif (typeof input.systemPrompt === \"string\") return input.systemPrompt;\n\treturn undefined;\n}\n\nfunction reviewInputSignature(input: string): string {\n\treturn createHash(\"sha256\").update(input).digest(\"hex\");\n}\n\nexport class MainWatchdogRuntime {\n\tprivate cwd: string;\n\tprivate readonly resolveConfig: (\n\t\tcwd: string,\n\t\toptions?: { session?: Record<string, unknown> },\n\t) => WatchdogSettingsResult;\n\tprivate readonly review: WatchdogReviewFunction;\n\tprivate readonly reviewConnected: boolean;\n\tprivate readonly reviewDescription: string;\n\tprivate readonly displayWarning:\n\t\t| ((warning: WatchdogWarningDetails, options?: { deliverAs?: \"steer\" }) => void)\n\t\t| undefined;\n\tprivate readonly sendUserMessage: ((message: string) => void | Promise<void>) | undefined;\n\tprivate readonly reviewChangesOnly: boolean;\n\tprivate readonly lspDiagnostics: WatchdogLspDiagnosticsFunction;\n\tprivate readonly repoChangeSignature: typeof computeWatchdogRepoChangeSignature;\n\tprivate readonly lspLedger = new WatchdogLspDiagnosticsLedger();\n\tprivate readonly scope = new WatchdogScopeArtifact();\n\tprivate configResult: WatchdogSettingsResult;\n\tprivate sessionOverrideEnabled: boolean | undefined;\n\tprivate sessionModelOverride: Partial<Pick<WatchdogEndpointConfig, \"model\" | \"thinking\">> | undefined;\n\tprivate status: WatchdogRuntimeStatus = \"idle\";\n\tprivate pendingDeltas: string[] = [];\n\tprivate pendingDeltaChars = 0;\n\tprivate guard = new WatchdogEmissionGuard();\n\tprivate guardMaxWarnings: number | null = null;\n\tprivate epoch = 0;\n\tprivate reviewIdCounter = 0;\n\tprivate agentEndIdCounter = 0;\n\tprivate activeAgentEndId: number | undefined;\n\tprivate activeAgentEndAbortController: AbortController | undefined;\n\tprivate activeReviewId: number | undefined;\n\tprivate activeReviewWarning: WatchdogWarningDetails | undefined;\n\tprivate reviewing = false;\n\tprivate waitingAtAgentEnd = false;\n\tprivate disposed = false;\n\tprivate includeUserPromptInNextDelta = false;\n\tprivate userPrompt: string | undefined;\n\tprivate waiters: Waiter[] = [];\n\tprivate lastWarning: WatchdogWarningDetails | undefined;\n\tprivate displayedWarningSequence = 0;\n\tprivate lastError: string | undefined;\n\tprivate lastReviewInputSignature: string | undefined;\n\tprivate turnStartChangeSignature: WatchdogRepoChangeSignature | undefined;\n\tprivate lastReviewedChangeSignature: string | undefined;\n\tprivate currentChangedPaths: string[] | undefined;\n\tprivate lastLspSnapshot: WatchdogLspRuntimeSnapshot | undefined;\n\tprivate observedRepoEditThisTurn = false;\n\tprivate toolResultsThisRun = 0;\n\tprivate midRunReviewing = false;\n\tprivate autoFollowQueued = false;\n\tprivate autoFollowAttempts = 0;\n\tprivate consecutiveAutoFollowIdentity: string | undefined;\n\tprivate consecutiveAutoFollowRepeats = 0;\n\tprivate autoFollowStalemate = false;\n\tprivate pendingAutoFollowPrompts: string[] = [];\n\tprivate midRunGeneration = 0;\n\tprivate activeReviewAbortController: AbortController | undefined;\n\tprivate failedReviews = 0;\n\tprivate staleReviews = 0;\n\n\tconstructor(options: MainWatchdogRuntimeOptions = {}) {\n\t\tthis.cwd = options.cwd ?? process.cwd();\n\t\tthis.resolveConfig = options.resolveConfig ?? resolveWatchdogConfig;\n\t\tthis.review = options.review ?? DEFAULT_REVIEW;\n\t\tthis.reviewConnected = Boolean(options.review);\n\t\tthis.reviewDescription = options.reviewDescription ?? (options.review ? \"injected seam\" : \"not wired\");\n\t\tthis.displayWarning = options.displayWarning;\n\t\tthis.sendUserMessage = options.sendUserMessage;\n\t\tthis.reviewChangesOnly = options.reviewChangesOnly === true;\n\t\tthis.lspDiagnostics = options.lspDiagnostics ?? collectWatchdogLspDiagnostics;\n\t\tthis.repoChangeSignature = options.repoChangeSignature ?? computeWatchdogRepoChangeSignature;\n\t\tthis.configResult = this.resolveConfig(this.cwd);\n\t\tthis.guardMaxWarnings = this.configResult.config.maxWarnings;\n\t\tthis.guard = new WatchdogEmissionGuard({ maxWarnings: this.guardMaxWarnings });\n\t\tthis.turnStartChangeSignature = this.currentRepoChangeSignature();\n\t\tthis.lastReviewedChangeSignature = this.turnStartChangeSignature?.key;\n\t}\n\n\tbindSession(ctx: ContextLike): void {\n\t\tthis.cwd = ctx.cwd;\n\t\tthis.sessionOverrideEnabled = undefined;\n\t\tthis.sessionModelOverride = undefined;\n\t\tthis.refreshConfig(ctx.cwd);\n\t\tthis.reset(\"session_start\", {\n\t\t\tclearReviewInputSignature: true,\n\t\t\tresetChangeSignature: true,\n\t\t\tclearLspLedger: true,\n\t\t\tclearScope: true,\n\t\t\tresetAutoFollow: true,\n\t\t});\n\t}\n\n\trefreshConfig(cwd = this.cwd): WatchdogSettingsResult {\n\t\tthis.cwd = cwd;\n\t\tconst wasEnabled = this.isEnabled();\n\t\tconst session =\n\t\t\tthis.sessionOverrideEnabled === undefined && this.sessionModelOverride === undefined\n\t\t\t\t? undefined\n\t\t\t\t: {\n\t\t\t\t\t\t...(this.sessionOverrideEnabled === undefined ? {} : { enabled: this.sessionOverrideEnabled }),\n\t\t\t\t\t\tmain: {\n\t\t\t\t\t\t\t...(this.sessionOverrideEnabled === undefined ? {} : { enabled: this.sessionOverrideEnabled }),\n\t\t\t\t\t\t\t...(this.sessionModelOverride ?? {}),\n\t\t\t\t\t\t},\n\t\t\t\t\t};\n\t\tthis.configResult = this.resolveConfig(this.cwd, session === undefined ? undefined : { session });\n\t\tif (this.configResult.config.maxWarnings !== this.guardMaxWarnings) {\n\t\t\tthis.guardMaxWarnings = this.configResult.config.maxWarnings;\n\t\t\tthis.guard = new WatchdogEmissionGuard({ maxWarnings: this.guardMaxWarnings });\n\t\t}\n\t\tif (wasEnabled && !this.isEnabled()) this.invalidateActiveReview(\"watchdog disabled\");\n\t\treturn this.configResult;\n\t}\n\n\tsetSessionEnabled(enabled: boolean, cwd = this.cwd): WatchdogRuntimeSnapshot {\n\t\tthis.sessionOverrideEnabled = enabled;\n\t\tthis.reset(\"session override\");\n\t\tthis.refreshConfig(cwd);\n\t\treturn this.getSnapshot();\n\t}\n\n\tsetSessionModel(\n\t\tpatch: { model?: string | null; thinking?: WatchdogEndpointConfig[\"thinking\"] | null },\n\t\tcwd = this.cwd,\n\t): WatchdogRuntimeSnapshot {\n\t\tconst next = { ...(this.sessionModelOverride ?? {}) };\n\t\tif (patch.model === null) delete next.model;\n\t\telse if (patch.model !== undefined) next.model = patch.model;\n\t\tif (patch.thinking === null) delete next.thinking;\n\t\telse if (patch.thinking !== undefined) next.thinking = patch.thinking;\n\t\tthis.sessionModelOverride = next.model === undefined && next.thinking === undefined ? undefined : next;\n\t\tthis.reset(\"session model override\");\n\t\tthis.refreshConfig(cwd);\n\t\treturn this.getSnapshot();\n\t}\n\n\tclearSessionModel(cwd = this.cwd): WatchdogRuntimeSnapshot {\n\t\tthis.sessionModelOverride = undefined;\n\t\tthis.reset(\"session model override cleared\");\n\t\tthis.refreshConfig(cwd);\n\t\treturn this.getSnapshot();\n\t}\n\n\tclearSessionOverride(cwd = this.cwd): WatchdogRuntimeSnapshot {\n\t\tthis.sessionOverrideEnabled = undefined;\n\t\tthis.sessionModelOverride = undefined;\n\t\tthis.reset(\"session override cleared\");\n\t\tthis.refreshConfig(cwd);\n\t\treturn this.getSnapshot();\n\t}\n\n\treset(\n\t\t_reason = \"reset\",\n\t\toptions: {\n\t\t\tclearReviewInputSignature?: boolean;\n\t\t\tresetChangeSignature?: boolean;\n\t\t\tclearLspLedger?: boolean;\n\t\t\tclearScope?: boolean;\n\t\t\tresetAutoFollow?: boolean;\n\t\t} = {},\n\t): void {\n\t\tthis.abortActiveAgentEnd();\n\t\tthis.epoch++;\n\t\tthis.status = \"idle\";\n\t\tthis.clearPendingDeltas();\n\t\tthis.reviewing = false;\n\t\tthis.waitingAtAgentEnd = false;\n\t\tthis.activeReviewId = undefined;\n\t\tthis.activeReviewWarning = undefined;\n\t\tthis.includeUserPromptInNextDelta = false;\n\t\tthis.userPrompt = undefined;\n\t\tthis.lastError = undefined;\n\t\tthis.currentChangedPaths = undefined;\n\t\tthis.observedRepoEditThisTurn = false;\n\t\tthis.toolResultsThisRun = 0;\n\t\tthis.midRunReviewing = false;\n\t\tthis.autoFollowQueued = false;\n\t\tif (options.clearLspLedger) {\n\t\t\tthis.lspLedger.reset();\n\t\t\tthis.lastLspSnapshot = undefined;\n\t\t}\n\t\tif (options.clearScope) {\n\t\t\tthis.scope.reset();\n\t\t\tthis.pendingAutoFollowPrompts = [];\n\t\t}\n\t\tif (options.resetAutoFollow) this.resetAutoFollowState();\n\t\tif (options.clearReviewInputSignature) this.lastReviewInputSignature = undefined;\n\t\tif (options.resetChangeSignature) this.resetRepoChangeBaseline({ reviewed: true });\n\t\tthis.guard.reset();\n\t\tthis.resolveWaiters(true);\n\t}\n\n\tdispose(): void {\n\t\tthis.disposed = true;\n\t\tthis.abortActiveAgentEnd();\n\t\tthis.epoch++;\n\t\tthis.status = \"idle\";\n\t\tthis.clearPendingDeltas();\n\t\tthis.reviewing = false;\n\t\tthis.waitingAtAgentEnd = false;\n\t\tthis.activeReviewId = undefined;\n\t\tthis.activeReviewWarning = undefined;\n\t\tthis.lastReviewInputSignature = undefined;\n\t\tthis.currentChangedPaths = undefined;\n\t\tthis.lastLspSnapshot = undefined;\n\t\tthis.lspLedger.reset();\n\t\tthis.scope.reset();\n\t\tthis.observedRepoEditThisTurn = false;\n\t\tthis.toolResultsThisRun = 0;\n\t\tthis.midRunReviewing = false;\n\t\tthis.autoFollowQueued = false;\n\t\tthis.resolveWaiters(false);\n\t}\n\n\thandleBeforeAgentStart(event: unknown, ctx: ContextLike): void {\n\t\tif (this.disposed) return;\n\t\tconst incomingPrompt = promptFromBeforeAgentStart(event);\n\t\t// Only exact queued auto-follow text is treated as an auto-follow turn; a real user\n\t\t// prompt racing in ahead of one stays real and the pending matches survive for later.\n\t\tconst pendingIndex = incomingPrompt === undefined ? -1 : this.pendingAutoFollowPrompts.indexOf(incomingPrompt);\n\t\tconst autoFollowPrompt = pendingIndex >= 0 || isWatchdogAutoFollowPromptEvent(event);\n\t\tif (pendingIndex >= 0) this.pendingAutoFollowPrompts.splice(pendingIndex, 1);\n\t\tthis.reset(\"before_agent_start\", { resetAutoFollow: !autoFollowPrompt });\n\t\tthis.refreshConfig(ctx.cwd);\n\t\tthis.userPrompt = incomingPrompt;\n\t\tif (!autoFollowPrompt && this.userPrompt?.trim()) {\n\t\t\tthis.includeUserPromptInNextDelta = true;\n\t\t\tthis.scope.addPrompt(this.userPrompt);\n\t\t} else {\n\t\t\tthis.includeUserPromptInNextDelta = false;\n\t\t}\n\t\tthis.resetRepoChangeBaseline();\n\t}\n\n\thandleTurnEnd(event: unknown, ctx: ContextLike): void {\n\t\tif (this.disposed) return;\n\t\tthis.refreshConfig(ctx.cwd);\n\t\tif (!this.isEnabled()) return;\n\t\ttry {\n\t\t\tthis.observedRepoEditThisTurn ||= eventIndicatesRepoEdit(event);\n\t\t\tconst delta = formatWatchdogTurnDelta({\n\t\t\t\tincludeUserPrompt: this.includeUserPromptInNextDelta,\n\t\t\t\tuserPrompt: this.userPrompt,\n\t\t\t\tevents: [event],\n\t\t\t});\n\t\t\tthis.includeUserPromptInNextDelta = false;\n\t\t\tthis.enqueueDelta(delta);\n\t\t} catch (error) {\n\t\t\tthis.fail(`Failed to format watchdog turn delta: ${errorMessage(error)}`);\n\t\t}\n\t}\n\n\tenqueueDelta(delta: string): void {\n\t\tif (this.disposed || !delta.trim() || !this.isEnabled()) return;\n\t\tthis.appendBoundedDelta(delta);\n\t\tif (!this.reviewing && !this.waitingAtAgentEnd && !this.midRunReviewing) this.status = \"queued\";\n\t}\n\n\thandleToolResult(ctx: ContextLike): void {\n\t\tif (this.disposed) return;\n\t\tthis.refreshConfig(ctx.cwd);\n\t\tif (!this.isEnabled()) return;\n\t\tconst everyNTools = this.configResult.config.cadence.everyNTools;\n\t\tif (everyNTools === null) return;\n\t\tthis.toolResultsThisRun++;\n\t\tif (this.toolResultsThisRun % everyNTools !== 0) return;\n\t\tif (this.reviewing || this.waitingAtAgentEnd || this.midRunReviewing) return;\n\t\tconst delta = this.buildReviewInput(undefined, \"\");\n\t\tif (!delta.trim()) return;\n\t\tvoid this.reviewMidRunDelta(delta);\n\t}\n\n\tasync handleAgentEnd(_event: unknown, ctx: ContextLike): Promise<void> {\n\t\tif (this.disposed) return;\n\t\tthis.refreshConfig(ctx.cwd);\n\t\tif (!this.isEnabled()) return;\n\t\tconst changeSignature = this.resolveReviewChangeSignature(ctx.cwd);\n\t\tif (this.reviewChangesOnly && !changeSignature) {\n\t\t\tthis.clearPendingDeltas();\n\t\t\tif (this.status === \"queued\") this.status = \"idle\";\n\t\t\tthis.resolveWaiters(true);\n\t\t\treturn;\n\t\t}\n\t\tif (changeSignature && changeSignature.key === this.lastReviewedChangeSignature) {\n\t\t\tthis.clearPendingDeltas();\n\t\t\tthis.status = \"idle\";\n\t\t\tthis.resolveWaiters(true);\n\t\t\treturn;\n\t\t}\n\t\tthis.cancelMidRunReview();\n\t\tthis.waitingAtAgentEnd = true;\n\t\tconst agentEndEpoch = this.epoch;\n\t\tconst agentEndId = ++this.agentEndIdCounter;\n\t\tconst lspAbortController = new AbortController();\n\t\tthis.activeAgentEndId = agentEndId;\n\t\tthis.activeAgentEndAbortController = lspAbortController;\n\t\tlet displayedDuringAgentEnd: WatchdogWarningDetails | undefined;\n\t\tconst previousDisplayedSequence = this.displayedWarningSequence;\n\t\ttry {\n\t\t\tthis.guard.startModelUpdate();\n\t\t\tconst lspBlock = await this.collectLspDiagnostics(changeSignature, {\n\t\t\t\tepoch: agentEndEpoch,\n\t\t\t\tagentEndId,\n\t\t\t\tsignal: lspAbortController.signal,\n\t\t\t});\n\t\t\tif (this.activeAgentEndAbortController === lspAbortController) this.activeAgentEndAbortController = undefined;\n\t\t\tif (!this.isAgentEndCurrent(agentEndEpoch, agentEndId)) return;\n\t\t\tconst delta = this.buildReviewInput(changeSignature, lspBlock);\n\t\t\tthis.clearPendingDeltas();\n\t\t\tif (!delta.trim()) {\n\t\t\t\tthis.waitingAtAgentEnd = false;\n\t\t\t\tif (this.status === \"queued\") this.status = \"idle\";\n\t\t\t\tthis.resolveWaiters(true);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst signature = reviewInputSignature(delta);\n\t\t\tif (!this.reviewChangesOnly && signature === this.lastReviewInputSignature) {\n\t\t\t\tthis.waitingAtAgentEnd = false;\n\t\t\t\tthis.status = \"idle\";\n\t\t\t\tthis.resolveWaiters(true);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst outcome = await this.reviewDelta(delta, this.configResult.config.agentEndTimeoutMs);\n\t\t\tthis.waitingAtAgentEnd = false;\n\t\t\tif (outcome === \"timeout\") {\n\t\t\t\tthis.staleReviews++;\n\t\t\t\tthis.invalidateActiveReview(\"agent-end timeout\");\n\t\t\t\tthis.status = \"stale\";\n\t\t\t\tthis.markLastWarningStale();\n\t\t\t\tthis.resolveWaiters(true);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (outcome === \"completed\" && this.status !== \"failed\" && this.status !== \"stale\") {\n\t\t\t\tthis.lastReviewInputSignature = signature;\n\t\t\t\tif (changeSignature) this.lastReviewedChangeSignature = changeSignature.key;\n\t\t\t\tthis.currentChangedPaths = changeSignature?.changedPaths;\n\t\t\t\tthis.status = \"idle\";\n\t\t\t}\n\t\t\tdisplayedDuringAgentEnd =\n\t\t\t\tthis.displayedWarningSequence !== previousDisplayedSequence ? this.lastWarning : undefined;\n\t\t\tthis.queueAutoFollowIfNeeded(displayedDuringAgentEnd);\n\t\t\tthis.resolveWaiters(true);\n\t\t} finally {\n\t\t\tif (this.activeAgentEndAbortController === lspAbortController) this.activeAgentEndAbortController = undefined;\n\t\t\tif (this.activeAgentEndId === agentEndId) this.activeAgentEndId = undefined;\n\t\t}\n\t}\n\n\trecordDisplayedWarning(warning: WatchdogWarning): WatchdogWarningDetails {\n\t\tconst details = normalizeWatchdogWarningDetails(warning, {\n\t\t\tstate: \"displayed\",\n\t\t\tsource: warning.source ?? \"main\",\n\t\t});\n\t\tthis.lastWarning = details;\n\t\treturn details;\n\t}\n\n\tgetSnapshot(cwd?: string): WatchdogRuntimeSnapshot {\n\t\tif (cwd) this.refreshConfig(cwd);\n\t\treturn {\n\t\t\tstatus: this.status,\n\t\t\tenabled: this.isEnabled(),\n\t\t\tconfig: this.configResult.config,\n\t\t\tconfigOk: this.configResult.ok,\n\t\t\terrors: [...this.configResult.errors],\n\t\t\tsources: [...this.configResult.sources],\n\t\t\tbufferedDeltas: this.pendingDeltas.length,\n\t\t\tepoch: this.epoch,\n\t\t\t...(this.activeReviewId !== undefined ? { activeReviewId: this.activeReviewId } : {}),\n\t\t\t...(this.sessionOverrideEnabled !== undefined ? { sessionOverride: this.sessionOverrideEnabled } : {}),\n\t\t\t...(this.sessionModelOverride !== undefined ? { sessionModelOverride: { ...this.sessionModelOverride } } : {}),\n\t\t\t...(this.lastWarning ? { lastWarning: this.lastWarning } : {}),\n\t\t\t...(this.lastError ? { lastError: this.lastError } : {}),\n\t\t\tfailedReviews: this.failedReviews,\n\t\t\tstaleReviews: this.staleReviews,\n\t\t\treviewConnected: this.reviewConnected,\n\t\t\treviewDescription: this.reviewDescription,\n\t\t\tautoFollowQueued: this.autoFollowQueued,\n\t\t\tautoFollowAttempts: this.autoFollowAttempts,\n\t\t\tautoFollowStalemate: this.autoFollowStalemate,\n\t\t\treviewTrigger: this.reviewChangesOnly ? \"repo-edits\" : \"turn-delta\",\n\t\t\t...(this.currentChangedPaths?.length ? { changedPaths: [...this.currentChangedPaths] } : {}),\n\t\t\tlsp: this.lspSnapshot(),\n\t\t};\n\t}\n\n\tasync waitForIdle(timeoutMs = 1_000): Promise<boolean> {\n\t\treturn this.waitForSettled(timeoutMs);\n\t}\n\n\tprivate isEnabled(): boolean {\n\t\treturn this.configResult.ok && this.configResult.config.main.enabled;\n\t}\n\n\tprivate abortActiveAgentEnd(): void {\n\t\tthis.activeAgentEndAbortController?.abort();\n\t\tthis.activeAgentEndAbortController = undefined;\n\t\tthis.activeAgentEndId = undefined;\n\t}\n\n\tprivate isAgentEndCurrent(epoch: number, agentEndId: number): boolean {\n\t\treturn (\n\t\t\t!this.disposed &&\n\t\t\tthis.epoch === epoch &&\n\t\t\tthis.activeAgentEndId === agentEndId &&\n\t\t\tthis.waitingAtAgentEnd &&\n\t\t\tthis.isEnabled()\n\t\t);\n\t}\n\n\tprivate isCurrent(epoch: number, reviewId: number): boolean {\n\t\treturn !this.disposed && this.epoch === epoch && this.activeReviewId === reviewId;\n\t}\n\n\tprivate warningMeetsThreshold(warning: WatchdogWarning): boolean {\n\t\treturn this.configResult.config.severityThreshold === \"concern\" || warning.severity === \"blocker\";\n\t}\n\n\tprivate acceptWarning(epoch: number, reviewId: number, warning: WatchdogWarning): boolean {\n\t\tif (!this.isCurrent(epoch, reviewId) || !this.isEnabled() || !this.warningMeetsThreshold(warning)) return false;\n\t\tconst decision = this.guard.evaluate(warning);\n\t\tif (!decision.accepted) return false;\n\t\tconst details = normalizeWatchdogWarningDetails(warning, {\n\t\t\tstate: \"candidate\",\n\t\t\tsource: warning.source ?? \"main\",\n\t\t\tidentity: decision.identity,\n\t\t});\n\t\tthis.lastWarning = details;\n\t\tthis.activeReviewWarning = details;\n\t\treturn true;\n\t}\n\n\tprivate displayBoundaryWarning(warning: WatchdogWarning): boolean {\n\t\tif (!this.isEnabled() || !this.warningMeetsThreshold(warning)) return false;\n\t\tconst decision = this.guard.evaluate(warning);\n\t\tif (!decision.accepted) return false;\n\t\tconst details = normalizeWatchdogWarningDetails(warning, {\n\t\t\tstate: \"displayed\",\n\t\t\tsource: warning.source ?? \"main\",\n\t\t\tidentity: decision.identity,\n\t\t\tdisplayedAt: new Date().toISOString(),\n\t\t});\n\t\tthis.lastWarning = details;\n\t\tthis.displayedWarningSequence++;\n\t\tthis.displayWarning?.(details);\n\t\treturn true;\n\t}\n\n\tprivate invalidateActiveReview(_reason: string): void {\n\t\tthis.abortActiveAgentEnd();\n\t\tthis.epoch++;\n\t\tthis.status = \"idle\";\n\t\tthis.clearPendingDeltas();\n\t\tthis.reviewing = false;\n\t\tthis.waitingAtAgentEnd = false;\n\t\tthis.activeReviewId = undefined;\n\t\tthis.activeReviewWarning = undefined;\n\t}\n\n\tprivate async reviewMidRunDelta(delta: string): Promise<void> {\n\t\tif (this.midRunReviewing || this.reviewing || this.waitingAtAgentEnd || this.disposed) return;\n\t\tthis.midRunReviewing = true;\n\t\tconst generation = this.midRunGeneration;\n\t\ttry {\n\t\t\tconst outcome = await this.reviewDelta(delta, this.configResult.config.agentEndTimeoutMs, {\n\t\t\t\tcorrection: true,\n\t\t\t});\n\t\t\tif (generation !== this.midRunGeneration) return;\n\t\t\tif (outcome === \"timeout\") {\n\t\t\t\tthis.staleReviews++;\n\t\t\t\tthis.status = \"stale\";\n\t\t\t\tthis.markLastWarningStale();\n\t\t\t}\n\t\t} finally {\n\t\t\tif (generation === this.midRunGeneration) {\n\t\t\t\tthis.midRunReviewing = false;\n\t\t\t\tif (this.status === \"reviewing\") this.status = this.pendingDeltas.length ? \"queued\" : \"idle\";\n\t\t\t\tthis.resolveWaiters(this.isSettled());\n\t\t\t}\n\t\t}\n\t}\n\n\t// The agent-end boundary review is authoritative; an in-flight cadence review is superseded.\n\tprivate cancelMidRunReview(): void {\n\t\tif (!this.midRunReviewing) return;\n\t\tthis.midRunGeneration++;\n\t\tthis.activeReviewAbortController?.abort();\n\t\tthis.activeReviewAbortController = undefined;\n\t\tthis.staleReviews++;\n\t\tthis.reviewing = false;\n\t\tthis.midRunReviewing = false;\n\t\tthis.activeReviewId = undefined;\n\t\tthis.activeReviewWarning = undefined;\n\t}\n\n\tprivate async reviewDelta(\n\t\tdelta: string,\n\t\ttimeoutMs: number,\n\t\toptions: { correction?: boolean } = {},\n\t): Promise<ReviewDeltaOutcome> {\n\t\tif (this.reviewing || this.disposed) return \"stale\";\n\t\tthis.reviewing = true;\n\t\tconst reviewEpoch = this.epoch;\n\t\tconst reviewId = ++this.reviewIdCounter;\n\t\tthis.activeReviewId = reviewId;\n\t\tthis.activeReviewWarning = undefined;\n\t\tthis.status = \"reviewing\";\n\t\tlet timeout: ReturnType<typeof setTimeout> | undefined;\n\t\tconst abortController = new AbortController();\n\t\tthis.activeReviewAbortController = abortController;\n\t\tconst reviewPromise = Promise.resolve().then(() =>\n\t\t\tthis.review({\n\t\t\t\tdelta,\n\t\t\t\tepoch: reviewEpoch,\n\t\t\t\treviewId,\n\t\t\t\tconfig: this.configResult.config,\n\t\t\t\thasScope: this.scopeBlock().trim().length > 0,\n\t\t\t\tsignal: abortController.signal,\n\t\t\t\temitWarning: (warning) => this.acceptWarning(reviewEpoch, reviewId, warning),\n\t\t\t}),\n\t\t);\n\t\ttry {\n\t\t\tconst result = await Promise.race([\n\t\t\t\treviewPromise,\n\t\t\t\tnew Promise<\"timeout\">((resolve) => {\n\t\t\t\t\ttimeout = setTimeout(() => resolve(\"timeout\"), timeoutMs);\n\t\t\t\t}),\n\t\t\t]);\n\t\t\tif (result === \"timeout\") {\n\t\t\t\tabortController.abort();\n\t\t\t\treturn \"timeout\";\n\t\t\t}\n\t\t\tif (!this.isCurrent(reviewEpoch, reviewId)) return \"stale\";\n\t\t\tif (!result) return \"stale\";\n\t\t\tfor (const warning of result.warnings ?? []) this.acceptWarning(reviewEpoch, reviewId, warning);\n\t\t\tif (result.stopReason && result.stopReason !== \"stop\") {\n\t\t\t\tthis.fail(`Watchdog review ended with stop reason '${result.stopReason}'.`);\n\t\t\t\treturn \"completed\";\n\t\t\t}\n\t\t\tthis.displayAcceptedReviewWarning(options.correction);\n\t\t\treturn \"completed\";\n\t\t} catch (error) {\n\t\t\tif (this.isCurrent(reviewEpoch, reviewId)) {\n\t\t\t\tthis.fail(`Watchdog review failed: ${errorMessage(error)}`);\n\t\t\t\treturn \"completed\";\n\t\t\t}\n\t\t\treturn \"stale\";\n\t\t} finally {\n\t\t\tif (timeout) clearTimeout(timeout);\n\t\t\tif (this.activeReviewAbortController === abortController) this.activeReviewAbortController = undefined;\n\t\t\tif (this.epoch === reviewEpoch && this.activeReviewId === reviewId) {\n\t\t\t\tthis.reviewing = false;\n\t\t\t\tthis.activeReviewId = undefined;\n\t\t\t\tthis.activeReviewWarning = undefined;\n\t\t\t}\n\t\t\tthis.resolveWaiters(this.isSettled());\n\t\t}\n\t}\n\n\tprivate displayAcceptedReviewWarning(correction = false): void {\n\t\tif (!this.activeReviewWarning) return;\n\t\tconst details: WatchdogWarningDetails = {\n\t\t\t...this.activeReviewWarning,\n\t\t\tstate: \"displayed\",\n\t\t\tdisplayedAt: new Date().toISOString(),\n\t\t};\n\t\tthis.lastWarning = details;\n\t\tthis.displayedWarningSequence++;\n\t\tthis.displayWarning?.(details, correction ? { deliverAs: \"steer\" } : undefined);\n\t}\n\n\tprivate resetAutoFollowState(): void {\n\t\tthis.autoFollowQueued = false;\n\t\tthis.autoFollowAttempts = 0;\n\t\tthis.consecutiveAutoFollowIdentity = undefined;\n\t\tthis.consecutiveAutoFollowRepeats = 0;\n\t\tthis.autoFollowStalemate = false;\n\t}\n\n\tprivate queueAutoFollowIfNeeded(warning: WatchdogWarningDetails | undefined): void {\n\t\tif (\n\t\t\t!warning ||\n\t\t\twarning.severity !== \"blocker\" ||\n\t\t\twarning.stale ||\n\t\t\t!this.configResult.config.autoFollow.blockers ||\n\t\t\t!this.isEnabled()\n\t\t)\n\t\t\treturn;\n\t\tconst identity =\n\t\t\twarning.identity ?? reviewInputSignature([warning.severity, warning.summary, warning.evidence].join(\"\\n\"));\n\t\tif (this.consecutiveAutoFollowIdentity === identity) this.consecutiveAutoFollowRepeats++;\n\t\telse {\n\t\t\tthis.consecutiveAutoFollowIdentity = identity;\n\t\t\tthis.consecutiveAutoFollowRepeats = 1;\n\t\t}\n\t\tif (this.consecutiveAutoFollowRepeats >= this.configResult.config.autoFollow.stalemateRepeats) {\n\t\t\tthis.autoFollowStalemate = true;\n\t\t\tthis.lastWarning = { ...warning, state: \"stalemate\", stalemateRepeats: this.consecutiveAutoFollowRepeats };\n\t\t\treturn;\n\t\t}\n\t\tconst maxAttempts = this.configResult.config.autoFollow.maxAttempts;\n\t\tif (maxAttempts !== null && this.autoFollowAttempts >= maxAttempts) return;\n\t\tif (!this.sendUserMessage) return;\n\t\tthis.autoFollowAttempts++;\n\t\tthis.autoFollowQueued = true;\n\t\tconst prompt = [\n\t\t\t\"Watchdog auto-follow: address this blocker before continuing.\",\n\t\t\t`Summary: ${warning.summary}`,\n\t\t\t`Evidence: ${warning.evidence}`,\n\t\t\t`Recommended action: ${warning.recommendedAction}`,\n\t\t].join(\"\\n\");\n\t\tthis.pendingAutoFollowPrompts.push(prompt);\n\t\tif (this.pendingAutoFollowPrompts.length > 8) this.pendingAutoFollowPrompts.shift();\n\t\tvoid Promise.resolve(this.sendUserMessage(prompt)).catch((error) => {\n\t\t\tthis.autoFollowQueued = false;\n\t\t\tconst index = this.pendingAutoFollowPrompts.indexOf(prompt);\n\t\t\tif (index >= 0) this.pendingAutoFollowPrompts.splice(index, 1);\n\t\t\tthis.lastError = `Watchdog auto-follow failed: ${errorMessage(error)}`;\n\t\t});\n\t}\n\n\tprivate currentRepoChangeSignature(cwd = this.cwd): WatchdogRepoChangeSignature | undefined {\n\t\treturn this.reviewChangesOnly && this.isEnabled() ? this.repoChangeSignature(cwd) : undefined;\n\t}\n\n\tprivate resetRepoChangeBaseline(options: { cwd?: string; reviewed?: boolean } = {}): void {\n\t\tthis.turnStartChangeSignature = this.currentRepoChangeSignature(options.cwd ?? this.cwd);\n\t\tif (options.reviewed) this.lastReviewedChangeSignature = this.turnStartChangeSignature?.key;\n\t\telse this.lastReviewedChangeSignature ??= this.turnStartChangeSignature?.key;\n\t\tthis.currentChangedPaths = this.turnStartChangeSignature?.changedPaths;\n\t\tthis.observedRepoEditThisTurn = false;\n\t}\n\n\tprivate resolveReviewChangeSignature(cwd = this.cwd): WatchdogRepoChangeSignature | undefined {\n\t\tif (!this.reviewChangesOnly) return undefined;\n\t\tconst current = this.currentRepoChangeSignature(cwd);\n\t\tif (current) {\n\t\t\tthis.currentChangedPaths = current.changedPaths;\n\t\t\tif (current.key === this.turnStartChangeSignature?.key) return undefined;\n\t\t\tif (current.changedPaths.length === 0) return undefined;\n\t\t\treturn current;\n\t\t}\n\t\treturn this.observedRepoEditThisTurn\n\t\t\t? {\n\t\t\t\t\troot: cwd,\n\t\t\t\t\tkey: `observed-edit:${this.epoch}:${this.reviewIdCounter}:${this.pendingDeltaChars}`,\n\t\t\t\t\tchangedPaths: [],\n\t\t\t\t}\n\t\t\t: undefined;\n\t}\n\n\tprivate async collectLspDiagnostics(\n\t\tchangeSignature: WatchdogRepoChangeSignature | undefined,\n\t\tcurrent: { epoch: number; agentEndId: number; signal: AbortSignal },\n\t): Promise<string> {\n\t\tconst config = this.configResult.config.lsp;\n\t\tif (!config.enabled || !changeSignature?.changedPaths.length) {\n\t\t\tthis.lastLspSnapshot = {\n\t\t\t\tenabled: config.enabled,\n\t\t\t\tstatus: config.enabled ? \"skipped\" : \"disabled\",\n\t\t\t\tcheckedPaths: [],\n\t\t\t\tskippedPaths: [],\n\t\t\t\tdiagnostics: [],\n\t\t\t\tdiagnosticCount: 0,\n\t\t\t\tfreshDiagnosticCount: 0,\n\t\t\t\tupdatedAt: new Date().toISOString(),\n\t\t\t};\n\t\t\treturn \"\";\n\t\t}\n\t\ttry {\n\t\t\tconst raw = await this.lspDiagnostics({\n\t\t\t\tcwd: this.cwd,\n\t\t\t\troot: changeSignature.root,\n\t\t\t\tchangedPaths: changeSignature.changedPaths,\n\t\t\t\tconfig,\n\t\t\t\tsignal: current.signal,\n\t\t\t});\n\t\t\tif (!this.isAgentEndCurrent(current.epoch, current.agentEndId)) return \"\";\n\t\t\tconst diagnosticCount = raw.diagnostics.length;\n\t\t\tconst fresh = this.lspLedger.reduce(raw);\n\t\t\tthis.lastLspSnapshot = {\n\t\t\t\t...fresh,\n\t\t\t\tenabled: true,\n\t\t\t\tdiagnosticCount,\n\t\t\t\tfreshDiagnosticCount: fresh.diagnostics.length,\n\t\t\t\tupdatedAt: new Date().toISOString(),\n\t\t\t};\n\t\t\tconst warning = watchdogWarningFromLspDiagnostics(fresh);\n\t\t\tif (warning) this.displayBoundaryWarning(warning);\n\t\t\treturn formatWatchdogLspDiagnosticsBlock(fresh);\n\t\t} catch (error) {\n\t\t\tif (!this.isAgentEndCurrent(current.epoch, current.agentEndId)) return \"\";\n\t\t\tthis.lastLspSnapshot = {\n\t\t\t\tenabled: true,\n\t\t\t\tstatus: \"failed\",\n\t\t\t\tcheckedPaths: [],\n\t\t\t\tskippedPaths: changeSignature.changedPaths,\n\t\t\t\tdiagnostics: [],\n\t\t\t\tdiagnosticCount: 0,\n\t\t\t\tfreshDiagnosticCount: 0,\n\t\t\t\tmessage: `LSP diagnostics failed: ${errorMessage(error)}`,\n\t\t\t\tupdatedAt: new Date().toISOString(),\n\t\t\t};\n\t\t\treturn \"\";\n\t\t}\n\t}\n\n\tprivate lspSnapshot(): WatchdogLspRuntimeSnapshot {\n\t\tif (this.lastLspSnapshot)\n\t\t\treturn {\n\t\t\t\t...this.lastLspSnapshot,\n\t\t\t\tcheckedPaths: [...this.lastLspSnapshot.checkedPaths],\n\t\t\t\tskippedPaths: [...this.lastLspSnapshot.skippedPaths],\n\t\t\t\tdiagnostics: [...this.lastLspSnapshot.diagnostics],\n\t\t\t};\n\t\tconst enabled = this.isEnabled() && this.configResult.config.lsp.enabled;\n\t\treturn {\n\t\t\tenabled,\n\t\t\tstatus: enabled ? \"skipped\" : \"disabled\",\n\t\t\tcheckedPaths: [],\n\t\t\tskippedPaths: [],\n\t\t\tdiagnostics: [],\n\t\t\tdiagnosticCount: 0,\n\t\t\tfreshDiagnosticCount: 0,\n\t\t};\n\t}\n\n\tprivate appendBoundedDelta(delta: string): void {\n\t\tlet entry = delta.trim();\n\t\tif (!entry) return;\n\t\tif (entry.length > MAX_REVIEW_INPUT_CHARS) entry = entry.slice(-MAX_REVIEW_INPUT_CHARS);\n\t\tthis.pendingDeltas.push(entry);\n\t\tthis.pendingDeltaChars += entry.length;\n\t\twhile (\n\t\t\tthis.pendingDeltas.length > 1 &&\n\t\t\tthis.pendingDeltaChars + (this.pendingDeltas.length - 1) * REVIEW_DELTA_SEPARATOR.length >\n\t\t\t\tMAX_REVIEW_INPUT_CHARS\n\t\t) {\n\t\t\tconst removed = this.pendingDeltas.shift();\n\t\t\tif (removed) this.pendingDeltaChars -= removed.length;\n\t\t}\n\t}\n\n\tprivate buildReviewInput(changeSignature?: WatchdogRepoChangeSignature, lspBlock = \"\"): string {\n\t\tconst input = this.pendingDeltas.join(REVIEW_DELTA_SEPARATOR);\n\t\tconst scopeBlock = this.scopeBlock();\n\t\tconst changes = changeSignature?.changedPaths.length\n\t\t\t? [\"Changed repo paths:\", ...changeSignature.changedPaths.slice(0, 200).map((file) => `- ${file}`)].join(\"\\n\")\n\t\t\t: \"\";\n\t\tconst contextPieces = [scopeBlock, changes, lspBlock].filter(Boolean);\n\t\tif (!contextPieces.length)\n\t\t\treturn input.length > MAX_REVIEW_INPUT_CHARS ? input.slice(-MAX_REVIEW_INPUT_CHARS) : input;\n\n\t\tconst maxContextLength = Math.floor(MAX_REVIEW_INPUT_CHARS / 2);\n\t\tconst maxPieceLength = Math.max(1_000, Math.floor(maxContextLength / contextPieces.length));\n\t\tconst boundedContext = contextPieces\n\t\t\t.map((piece) => (piece.length > maxPieceLength ? `${piece.slice(0, maxPieceLength - 6)}\\n- ...` : piece))\n\t\t\t.join(REVIEW_DELTA_SEPARATOR);\n\t\tconst separatorLength = input ? REVIEW_DELTA_SEPARATOR.length : 0;\n\t\tconst inputBudget = MAX_REVIEW_INPUT_CHARS - boundedContext.length - separatorLength;\n\t\tconst boundedInput = inputBudget <= 0 ? \"\" : input.length > inputBudget ? input.slice(-inputBudget) : input;\n\t\treturn [boundedContext, boundedInput].filter(Boolean).join(REVIEW_DELTA_SEPARATOR);\n\t}\n\n\tprivate scopeBlock(): string {\n\t\treturn this.configResult.config.scope.enabled ? this.scope.render() : \"\";\n\t}\n\n\tprivate clearPendingDeltas(): void {\n\t\tthis.pendingDeltas = [];\n\t\tthis.pendingDeltaChars = 0;\n\t}\n\n\tprivate fail(message: string): void {\n\t\tthis.failedReviews++;\n\t\tthis.lastError = message;\n\t\tthis.status = \"failed\";\n\t\tthis.clearPendingDeltas();\n\t\tthis.resolveWaiters(true);\n\t}\n\n\tprivate markLastWarningStale(): void {\n\t\tif (!this.lastWarning || this.lastWarning.state === \"displayed\") return;\n\t\tthis.lastWarning = { ...this.lastWarning, stale: true, state: \"stale\" };\n\t}\n\n\tprivate isSettled(): boolean {\n\t\treturn !this.reviewing && this.pendingDeltas.length === 0;\n\t}\n\n\tprivate waitForSettled(timeoutMs: number): Promise<boolean> {\n\t\tif (this.isSettled()) return Promise.resolve(true);\n\t\treturn new Promise((resolve) => {\n\t\t\tconst waiter: Waiter = {\n\t\t\t\tresolve,\n\t\t\t\ttimer: setTimeout(() => {\n\t\t\t\t\tthis.waiters = this.waiters.filter((entry) => entry !== waiter);\n\t\t\t\t\tresolve(false);\n\t\t\t\t}, timeoutMs),\n\t\t\t};\n\t\t\tthis.waiters.push(waiter);\n\t\t});\n\t}\n\n\tprivate resolveWaiters(settled: boolean): void {\n\t\tif (!settled && !this.disposed) return;\n\t\tconst waiters = this.waiters;\n\t\tthis.waiters = [];\n\t\tfor (const waiter of waiters) {\n\t\t\tclearTimeout(waiter.timer);\n\t\t\twaiter.resolve(settled);\n\t\t}\n\t}\n}\n"]}