{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/extension/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAOH,OAAO,EACN,KAAK,YAAY,EAIjB,MAAM,2BAA2B,CAAC;AAkFnC,OAAO,EAAE,UAAU,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AA+PhE,MAAM,CAAC,OAAO,UAAU,yBAAyB,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CAmiBxE","sourcesContent":["/**\n * Subagent Tool\n *\n * Full-featured subagent with sync and async modes.\n * - Direct child calls `{ agent?, task }` wait by default.\n * - Workflow scripts and explicit `async:true` calls run in the background.\n * - Management actions control existing agents, runs, and schedules.\n *\n * Public execution mode: direct `{ agent?, task }` or `workflowScript`\n * Toggle: `async` (default true for workflowScript; direct calls default false)\n *\n * Config file: ~/.pi/agent/extensions/subagent/config.json\n *   { \"asyncByDefault\": true, \"forceTopLevelAsync\": true, \"maxSubagentDepth\": 1, \"intercomBridge\": { \"mode\": \"always\", \"instructionFile\": \"./intercom-bridge.md\" }, \"worktreeSetupHook\": \"./scripts/setup-worktree.mjs\" }\n */\n\nimport { randomUUID } from \"node:crypto\";\nimport * as fs from \"node:fs\";\nimport * as os from \"node:os\";\nimport * as path from \"node:path\";\nimport type { AgentToolResult } from \"@lpb-work/pi-agent-core\";\nimport {\n\ttype ExtensionAPI,\n\ttype ExtensionContext,\n\tkeyText,\n\ttype ToolDefinition,\n} from \"@lpb-work/pi-coding-agent\";\nimport {\n\tBox,\n\ttype Component,\n\tContainer,\n\tSpacer,\n\tText,\n\ttruncateToWidth,\n\tvisibleWidth,\n\twrapTextWithAnsi,\n} from \"@lpb-work/pi-tui\";\nimport { discoverAgents } from \"../agents/agents.ts\";\nimport { registerHerdrStatusBridge } from \"../integrations/herdr-status.ts\";\nimport { createNativeSupervisorChannel } from \"../intercom/native-supervisor-channel.ts\";\nimport { collectGoalContinuationNotices } from \"../missions/goal-driver.ts\";\nimport { syncMissionFromAsyncCompletion } from \"../missions/lifecycle.ts\";\nimport { resolveMissionStoreLocation } from \"../missions/store.ts\";\nimport { createAsyncJobTracker } from \"../runs/background/async-job-tracker.ts\";\nimport { drainOutstandingWork } from \"../runs/background/auto-drain.ts\";\nimport registerSubagentNotify, {\n\tparseSubagentNotifyContent,\n\ttype SubagentNotifyDetails,\n} from \"../runs/background/notify.ts\";\nimport { createResultWatcher } from \"../runs/background/result-watcher.ts\";\nimport { listRetainedChildren } from \"../runs/background/retained-children.ts\";\nimport { createScheduledRunManager } from \"../runs/background/scheduled-runs.ts\";\nimport { resolveWaitToolConfig } from \"../runs/background/subagent-wait.ts\";\nimport { createWaitSubscriptionManager } from \"../runs/background/wait-subscriptions.ts\";\nimport { registerWaitTool } from \"../runs/background/wait-tool.ts\";\nimport { createSubagentExecutor, type SubagentParamsLike } from \"../runs/foreground/subagent-executor.ts\";\nimport { resolveCurrentSubagentCapabilityCeiling } from \"../runs/shared/capability-ceiling.ts\";\nimport { SUBAGENT_CHILD_ENV, SUBAGENT_PARENT_SESSION_ENV } from \"../runs/shared/pi-args.ts\";\nimport { ensureAccessibleDir } from \"../shared/accessible-dir.ts\";\nimport { cleanupAllArtifactDirs, cleanupOldArtifacts, getArtifactsDir } from \"../shared/artifacts.ts\";\nimport { formatDuration, shortenPath } from \"../shared/formatters.ts\";\nimport { resolveCurrentSessionId } from \"../shared/session-identity.ts\";\nimport { cleanupOldChainDirs } from \"../shared/settings.ts\";\nimport {\n\tDEFAULT_ARTIFACT_CONFIG,\n\ttype Details,\n\tDIRS,\n\tresolveMaxSubagentSpawnsPerSession,\n\tSLASH_RESULT_TYPE,\n\tSLASH_TEXT_RESULT_TYPE,\n\tSUBAGENT_ASYNC_COMPLETE_EVENT,\n\tSUBAGENT_ASYNC_STARTED_EVENT,\n\tSUBAGENT_CONTROL_EVENT,\n\tSUBAGENT_STEERING_NOTICE_EVENT,\n\ttype SubagentState,\n\tWIDGET_KEY,\n} from \"../shared/types.ts\";\nimport { registerPromptTemplateDelegationBridge } from \"../slash/prompt-template-bridge.ts\";\nimport { registerSlashSubagentBridge } from \"../slash/slash-bridge.ts\";\nimport { registerSlashCommands } from \"../slash/slash-commands.ts\";\nimport {\n\tclearSlashSnapshots,\n\tgetSlashRenderableSnapshot,\n\tresolveSlashMessageDetails,\n\trestoreSlashFinalSnapshots,\n\ttype SlashMessageDetails,\n} from \"../slash/slash-live-state.ts\";\nimport { openSubagentFleet } from \"../tui/fleet.ts\";\nimport { resolveFleetViewPlacement, SubagentFleetStatus } from \"../tui/fleet-status.ts\";\nimport { clearLegacyResultAnimationTimer, renderSubagentResult, renderSubagentSummary } from \"../tui/render.ts\";\nimport { registerMainWatchdog } from \"../watchdog/register-main.ts\";\nimport { loadConfig, resolveAsyncByDefault } from \"./config.ts\";\nimport {\n\tformatSubagentControlNotice,\n\thandleSubagentControlNotice,\n\tSUBAGENT_CONTROL_MESSAGE_TYPE,\n\ttype SubagentControlMessageDetails,\n} from \"./control-notices.ts\";\nimport { registerSubagentRpcBridge } from \"./rpc.ts\";\nimport { SubagentParams } from \"./schemas.ts\";\nimport {\n\tformatSteeringNotice,\n\thandleSubagentSteeringNotice,\n\tSUBAGENT_STEERING_MESSAGE_TYPE,\n\ttype SubagentSteeringMessageDetails,\n} from \"./steering-notices.ts\";\nimport { buildSubagentToolDescription } from \"./tool-description.ts\";\n\nexport { loadConfig, resolveAsyncByDefault } from \"./config.ts\";\n\nfunction workflowLaneKeys(script: string): string[] {\n\tconst keys: string[] = [];\n\tconst seen = new Set<string>();\n\tconst add = (key: string): void => {\n\t\tif (!seen.has(key)) {\n\t\t\tseen.add(key);\n\t\t\tkeys.push(key);\n\t\t}\n\t};\n\tconst isIdentifier = (char: string | undefined): boolean => char !== undefined && /[\\w$]/.test(char);\n\tconst skipTrivia = (start: number): number => {\n\t\tlet index = start;\n\t\twhile (index < script.length) {\n\t\t\tif (/\\s/.test(script[index]!)) index += 1;\n\t\t\telse if (script.startsWith(\"//\", index)) {\n\t\t\t\tconst end = script.indexOf(\"\\n\", index + 2);\n\t\t\t\tindex = end === -1 ? script.length : end + 1;\n\t\t\t} else if (script.startsWith(\"/*\", index)) {\n\t\t\t\tconst end = script.indexOf(\"*/\", index + 2);\n\t\t\t\tindex = end === -1 ? script.length : end + 2;\n\t\t\t} else break;\n\t\t}\n\t\treturn index;\n\t};\n\tconst readLiteral = (start: number): { key?: string; end: number } | undefined => {\n\t\tconst quote = script[start];\n\t\tif (quote !== \"'\" && quote !== '\"' && quote !== \"`\") return undefined;\n\t\tlet index = start + 1;\n\t\tlet dynamicTemplate = false;\n\t\twhile (index < script.length) {\n\t\t\tif (script[index] === \"\\\\\") {\n\t\t\t\tindex += 2;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (quote === \"`\" && script.startsWith(\"${\", index)) dynamicTemplate = true;\n\t\t\tif (script[index] === quote)\n\t\t\t\treturn { key: dynamicTemplate ? undefined : script.slice(start + 1, index), end: index + 1 };\n\t\t\tif (quote !== \"`\" && /[\\r\\n]/.test(script[index]!)) return { end: index + 1 };\n\t\t\tindex += 1;\n\t\t}\n\t\treturn { end: script.length };\n\t};\n\n\tconst collectRunsAllKeys = (start: number): number => {\n\t\tlet index = skipTrivia(start);\n\t\tif (script[index] !== \"(\") return start;\n\t\tindex = skipTrivia(index + 1);\n\t\tif (script[index] !== \"[\") return start;\n\t\tlet arrayDepth = 1;\n\t\tlet objectDepth = 0;\n\t\tlet directChildObject = false;\n\t\tlet expectingElement = true;\n\t\tfor (index += 1; index < script.length; index += 1) {\n\t\t\tindex = skipTrivia(index);\n\t\t\tconst literal = readLiteral(index);\n\t\t\tif (literal) {\n\t\t\t\tindex = literal.end - 1;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (script[index] === \"[\") {\n\t\t\t\tarrayDepth += 1;\n\t\t\t\texpectingElement = false;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (script[index] === \"]\") {\n\t\t\t\tarrayDepth -= 1;\n\t\t\t\tif (arrayDepth === 0) return index + 1;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (script[index] === \"{\") {\n\t\t\t\tobjectDepth += 1;\n\t\t\t\tif (objectDepth === 1) directChildObject = arrayDepth === 1 && expectingElement;\n\t\t\t\texpectingElement = false;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (script[index] === \"}\") {\n\t\t\t\tobjectDepth -= 1;\n\t\t\t\tif (objectDepth === 0) directChildObject = false;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (script[index] === \",\" && arrayDepth === 1 && objectDepth === 0) {\n\t\t\t\texpectingElement = true;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (\n\t\t\t\tdirectChildObject &&\n\t\t\t\tobjectDepth === 1 &&\n\t\t\t\t!isIdentifier(script[index - 1]) &&\n\t\t\t\tscript.startsWith(\"key\", index) &&\n\t\t\t\t!isIdentifier(script[index + 3])\n\t\t\t) {\n\t\t\t\tconst colon = skipTrivia(index + 3);\n\t\t\t\tconst key = script[colon] === \":\" ? readLiteral(skipTrivia(colon + 1)) : undefined;\n\t\t\t\tif (key) {\n\t\t\t\t\tconst next = skipTrivia(key.end);\n\t\t\t\t\tif (key.key !== undefined && (script[next] === \",\" || script[next] === \"}\")) add(key.key);\n\t\t\t\t\tindex = key.end - 1;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn index;\n\t};\n\n\tfor (let index = 0; index < script.length; ) {\n\t\tindex = skipTrivia(index);\n\t\tconst literal = readLiteral(index);\n\t\tif (literal) {\n\t\t\tindex = literal.end;\n\t\t\tcontinue;\n\t\t}\n\t\tif (\n\t\t\t!isIdentifier(script[index - 1]) &&\n\t\t\tscript.startsWith(\"runs.run\", index) &&\n\t\t\t!isIdentifier(script[index + 8])\n\t\t) {\n\t\t\tconst open = skipTrivia(index + 8);\n\t\t\tconst key = script[open] === \"(\" ? readLiteral(skipTrivia(open + 1)) : undefined;\n\t\t\tif (key) {\n\t\t\t\tconst next = skipTrivia(key.end);\n\t\t\t\tif (key.key !== undefined && (script[next] === \",\" || script[next] === \")\")) add(key.key);\n\t\t\t\tindex = key.end;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\t\tif (\n\t\t\t!isIdentifier(script[index - 1]) &&\n\t\t\tscript.startsWith(\"runs.all\", index) &&\n\t\t\t!isIdentifier(script[index + 8])\n\t\t) {\n\t\t\tindex = collectRunsAllKeys(index + 8);\n\t\t\tcontinue;\n\t\t}\n\t\tindex += 1;\n\t}\n\treturn keys;\n}\n\nfunction formatWorkflowManifest(script: string, async: unknown, clarify: unknown): string {\n\tif (clarify === true) return \"workflow script · rejected: clarify UI unsupported\";\n\tconst keys = workflowLaneKeys(script);\n\t// The workflow executor starts background work unless callers pass async:false.\n\tconst mode = async === false ? \"foreground\" : \"background\";\n\tif (keys.length === 0) return `workflow script · ${mode}`;\n\tconst visibleKeys = keys.slice(0, 4).join(\", \");\n\tconst remainder = keys.length > 4 ? `, +${keys.length - 4}` : \"\";\n\treturn `workflow · ${mode} · ${keys.length} lane${keys.length === 1 ? \"\" : \"s\"}: ${visibleKeys}${remainder}`;\n}\n\n/**\n * Derive subagent session base directory from parent session file.\n * If parent session is ~/.pi/agent/sessions/abc123.jsonl,\n * returns ~/.pi/agent/sessions/abc123/ as the base.\n * Callers add runId to create the actual session root: abc123/{runId}/\n * Falls back to a unique temp directory if no parent session.\n */\nfunction getSubagentSessionRoot(parentSessionFile: string | null): string {\n\tif (parentSessionFile) {\n\t\tconst baseName = path.basename(parentSessionFile, \".jsonl\");\n\t\tconst sessionsDir = path.dirname(parentSessionFile);\n\t\treturn path.join(sessionsDir, baseName);\n\t}\n\treturn fs.mkdtempSync(path.join(os.tmpdir(), \"pi-subagent-session-\"));\n}\n\nfunction expandTilde(p: string): string {\n\treturn p.startsWith(\"~/\") ? path.join(os.homedir(), p.slice(2)) : p;\n}\n\nfunction isSlashResultRunning(result: { details?: Details }): boolean {\n\treturn (\n\t\tresult.details?.progress?.some((entry) => entry.status === \"running\") ||\n\t\tresult.details?.results.some((entry) => entry.progress?.status === \"running\") ||\n\t\tfalse\n\t);\n}\n\nfunction isSlashResultError(result: { details?: Details }): boolean {\n\treturn (\n\t\tresult.details?.results.some((entry) => entry.exitCode !== 0 && entry.progress?.status !== \"running\") || false\n\t);\n}\n\nfunction isStaleExtensionContextError(error: unknown): boolean {\n\treturn error instanceof Error && error.message.includes(\"Extension context no longer active\");\n}\n\nfunction rebuildSlashResultContainer(\n\tcontainer: Container,\n\tresult: AgentToolResult<Details>,\n\toptions: { expanded: boolean },\n\ttheme: ExtensionContext[\"ui\"][\"theme\"],\n): void {\n\tcontainer.clear();\n\tcontainer.addChild(new Spacer(1));\n\tconst boxTheme = isSlashResultRunning(result)\n\t\t? \"toolPendingBg\"\n\t\t: isSlashResultError(result)\n\t\t\t? \"toolErrorBg\"\n\t\t\t: \"toolSuccessBg\";\n\tconst box = new Box(1, 1, (text: string) => theme.bg(boxTheme, text));\n\tbox.addChild(renderSubagentResult(result, options, theme));\n\tcontainer.addChild(box);\n}\n\nfunction createSlashResultComponent(\n\tdetails: SlashMessageDetails,\n\toptions: { expanded: boolean },\n\ttheme: ExtensionContext[\"ui\"][\"theme\"],\n): Container {\n\tconst container = new Container();\n\tlet lastVersion = -1;\n\tcontainer.render = (width: number): string[] => {\n\t\tconst snapshot = getSlashRenderableSnapshot(details);\n\t\tif (snapshot.version !== lastVersion || isSlashResultRunning(snapshot.result)) {\n\t\t\tlastVersion = snapshot.version;\n\t\t\trebuildSlashResultContainer(container, snapshot.result, options, theme);\n\t\t}\n\t\treturn Container.prototype.render.call(container, width);\n\t};\n\treturn container;\n}\n\nclass SubagentControlNoticeComponent implements Component {\n\tprivate readonly details: SubagentControlMessageDetails;\n\tprivate readonly theme: ExtensionContext[\"ui\"][\"theme\"];\n\n\tconstructor(details: SubagentControlMessageDetails, theme: ExtensionContext[\"ui\"][\"theme\"]) {\n\t\tthis.details = details;\n\t\tthis.theme = theme;\n\t}\n\n\tinvalidate(): void {}\n\n\trender(width: number): string[] {\n\t\tconst eventLabel = this.details.event.type.replaceAll(\"_\", \" \");\n\t\tif (width < 3) return [truncateToWidth(`Subagent ${eventLabel}`, width)];\n\t\tconst bodyWidth = Math.max(1, width - 2);\n\t\tconst borderChar = \"─\";\n\t\tconst header = ` ⚠ Subagent ${eventLabel}: ${this.details.event.agent} `;\n\t\tconst headerText = truncateToWidth(header, bodyWidth, \"\");\n\t\tconst headerPadding = Math.max(0, bodyWidth - visibleWidth(headerText));\n\t\tconst lines = [this.theme.fg(\"accent\", `╭${headerText}${borderChar.repeat(headerPadding)}╮`)];\n\n\t\tfor (const line of wrapTextWithAnsi(formatSubagentControlNotice(this.details), bodyWidth)) {\n\t\t\tconst text = truncateToWidth(line, bodyWidth, \"\");\n\t\t\tconst padding = Math.max(0, bodyWidth - visibleWidth(text));\n\t\t\tlines.push(this.theme.fg(\"accent\", `│${text}${\" \".repeat(padding)}│`));\n\t\t}\n\t\tlines.push(this.theme.fg(\"accent\", `╰${borderChar.repeat(bodyWidth)}╯`));\n\t\treturn lines;\n\t}\n}\n\nexport default function registerSubagentExtension(pi: ExtensionAPI): void {\n\tif (process.env[SUBAGENT_CHILD_ENV] === \"1\") {\n\t\treturn;\n\t}\n\tconst globalStore = globalThis as Record<string, unknown>;\n\tconst runtimeCleanupStoreKey = \"__piSubagentRuntimeCleanup\";\n\tconst previousRuntimeCleanup = globalStore[runtimeCleanupStoreKey];\n\tif (typeof previousRuntimeCleanup === \"function\") {\n\t\ttry {\n\t\t\tpreviousRuntimeCleanup();\n\t\t} catch {\n\t\t\t// Best effort cleanup for stale timers from an older reload.\n\t\t}\n\t}\n\n\tDIRS.results = ensureAccessibleDir(DIRS.results);\n\tDIRS.async = ensureAccessibleDir(DIRS.async);\n\tcleanupOldChainDirs();\n\n\tconst config = loadConfig();\n\tconst waitToolConfig = resolveWaitToolConfig(config.waitTool);\n\tconst asyncByDefault = resolveAsyncByDefault(config);\n\tconst fleetViewEnabled = config.fleetView !== false;\n\tconst fleetViewPlacement = resolveFleetViewPlacement(config.fleetViewPlacement);\n\tconst asyncWidgetEnabled = config.asyncWidget !== false;\n\tconst summaryInlineToolDisplay = config.inlineToolDisplay === \"summary\";\n\tconst tempArtifactsDir = getArtifactsDir(null);\n\tcleanupAllArtifactDirs(DEFAULT_ARTIFACT_CONFIG.cleanupDays);\n\n\tconst state: SubagentState = {\n\t\tbaseCwd: \"\",\n\t\tcurrentSessionId: null,\n\t\tartifactDirPreference: config.artifactDir ?? DEFAULT_ARTIFACT_CONFIG.dir,\n\t\t...(config.authorityPolicy ? { authorityPolicy: config.authorityPolicy } : {}),\n\t\t...(config.missions ? { missionStoreConfig: config.missions } : {}),\n\t\tparentSessionFile: null,\n\t\tsubagentInProgress: false,\n\t\tsubagentSpawns: {\n\t\t\tsessionId: null,\n\t\t\tcount: 0,\n\t\t\tconfiguredLimit: resolveMaxSubagentSpawnsPerSession(config.maxSubagentSpawnsPerSession) ?? null,\n\t\t\tgranted: 0,\n\t\t\tgrantHistory: [],\n\t\t},\n\t\tasyncJobs: new Map(),\n\t\tfleetJobs: new Map(),\n\t\tforegroundRuns: new Map(),\n\t\tforegroundControls: new Map(),\n\t\tlastForegroundControlId: null,\n\t\tcleanupTimers: new Map(),\n\t\tlastUiContext: null,\n\t\tpoller: null,\n\t\tcompletionSeen: new Map(),\n\t\twatcher: null,\n\t\twatcherRestartTimer: null,\n\t\tresultFileCoalescer: {\n\t\t\tschedule: () => false,\n\t\t\tclear: () => {},\n\t\t},\n\t};\n\n\tconst supervisorChannel = createNativeSupervisorChannel(pi, state);\n\tconst waitSubscriptionManager = createWaitSubscriptionManager(pi, state);\n\tconst mainWatchdog = registerMainWatchdog(pi);\n\tconst completionNotifier = registerSubagentNotify(pi, state, { batchConfig: config.completionBatch });\n\tconst fleetStatus = fleetViewEnabled\n\t\t? new SubagentFleetStatus(\n\t\t\t\tstate,\n\t\t\t\tasync (itemKey) => {\n\t\t\t\t\tconst ctx = state.lastUiContext;\n\t\t\t\t\tif (!ctx?.hasUI) return;\n\t\t\t\t\tawait openSubagentFleet(ctx, state, {\n\t\t\t\t\t\tinitialKey: itemKey,\n\t\t\t\t\t\tasyncDirRoot: DIRS.async,\n\t\t\t\t\t\tresultsDir: DIRS.results,\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t\t{ placement: fleetViewPlacement },\n\t\t\t)\n\t\t: undefined;\n\tlet executorScheduled:\n\t\t| ((\n\t\t\t\tid: string,\n\t\t\t\tparams: SubagentParamsLike,\n\t\t\t\tsignal: AbortSignal,\n\t\t\t\tctx: ExtensionContext,\n\t\t  ) => Promise<AgentToolResult<Details>>)\n\t\t| undefined;\n\tlet goalTurnId = 0;\n\tconst scheduledRunManager = createScheduledRunManager({\n\t\tconfig,\n\t\tlaunch: (params, ctx, signal) => {\n\t\t\tif (!executorScheduled) {\n\t\t\t\treturn Promise.resolve({\n\t\t\t\t\tcontent: [{ type: \"text\", text: \"Scheduled subagent launch is unavailable (executor not ready).\" }],\n\t\t\t\t\tisError: true,\n\t\t\t\t\tdetails: { mode: \"management\" as const, results: [] },\n\t\t\t\t});\n\t\t\t}\n\t\t\treturn executorScheduled(randomUUID(), params, signal, ctx);\n\t\t},\n\t\tresolveCapabilityCeiling: (sessionId) => resolveCurrentSubagentCapabilityCeiling(sessionId),\n\t});\n\tconst { startResultWatcher, primeExistingResults, stopResultWatcher } = createResultWatcher(\n\t\tpi,\n\t\tstate,\n\t\tDIRS.results,\n\t\t10 * 60 * 1000,\n\t\t{\n\t\t\tnotifier: completionNotifier,\n\t\t\tobserveCompletion: (result) => scheduledRunManager.handleAsyncCompletion(result),\n\t\t\tdeliverIntercomResults: config.intercomBridge?.resultDelivery === true,\n\t\t},\n\t);\n\n\tconst runtimeCleanup = () => {\n\t\tstopResultWatcher();\n\t\tstate.currentSessionId = null;\n\t\tcompletionNotifier.dispose();\n\t\tmainWatchdog.dispose();\n\t\tscheduledRunManager.stop();\n\t\tsupervisorChannel.dispose();\n\t\twaitSubscriptionManager.dispose();\n\t\tfleetStatus?.dispose();\n\t\tif (state.poller) {\n\t\t\tclearInterval(state.poller);\n\t\t\tstate.poller = null;\n\t\t}\n\t};\n\tglobalStore[runtimeCleanupStoreKey] = runtimeCleanup;\n\n\tconst { ensurePoller, refreshWidget, handleStarted, handleComplete, resetJobs, restoreActiveJobs } =\n\t\tcreateAsyncJobTracker(pi, state, DIRS.async, {\n\t\t\twidgetEnabled: asyncWidgetEnabled,\n\t\t});\n\tconst executor = createSubagentExecutor({\n\t\tpi,\n\t\tstate,\n\t\tconfig,\n\t\tasyncByDefault,\n\t\twaitToolEnabled: waitToolConfig.enabled,\n\t\thandleScheduledRunAction: (params, ctx) => scheduledRunManager.handleToolCall(params, ctx),\n\t\twatchdog: mainWatchdog,\n\t\ttempArtifactsDir,\n\t\tgetSubagentSessionRoot,\n\t\texpandTilde,\n\t\tdiscoverAgents,\n\t});\n\texecutorScheduled = executor.executeScheduled;\n\n\tpi.registerMessageRenderer<SlashMessageDetails>(SLASH_RESULT_TYPE, (message, options, theme) => {\n\t\tconst details = resolveSlashMessageDetails(message.details);\n\t\tif (!details) return undefined;\n\t\treturn createSlashResultComponent(details, options, theme);\n\t});\n\n\tpi.registerMessageRenderer<undefined>(SLASH_TEXT_RESULT_TYPE, (message, _options, _theme) => {\n\t\tconst content =\n\t\t\ttypeof message.content === \"string\"\n\t\t\t\t? message.content\n\t\t\t\t: message.content\n\t\t\t\t\t\t.filter((entry) => entry.type === \"text\")\n\t\t\t\t\t\t.map((entry) => entry.text)\n\t\t\t\t\t\t.join(\"\\n\");\n\t\treturn new Text(content, 0, 0);\n\t});\n\n\tpi.registerMessageRenderer<SubagentNotifyDetails>(\"subagent-notify\", (message, options, theme) => {\n\t\tconst content = typeof message.content === \"string\" ? message.content : \"\";\n\t\tconst details = (message.details as SubagentNotifyDetails | undefined) ?? parseSubagentNotifyContent(content);\n\t\tif (!details) return new Text(content, 0, 0);\n\t\tconst icon =\n\t\t\tdetails.status === \"completed\"\n\t\t\t\t? theme.fg(\"success\", \"✓\")\n\t\t\t\t: details.status === \"paused\"\n\t\t\t\t\t? theme.fg(\"warning\", \"■\")\n\t\t\t\t\t: theme.fg(\"error\", \"✗\");\n\t\tconst parts: string[] = [];\n\t\tif (details.taskInfo) parts.push(details.taskInfo);\n\t\tif (details.durationMs !== undefined) parts.push(formatDuration(details.durationMs));\n\t\tlet text = `${icon} ${theme.bold(details.agent)} ${theme.fg(\"dim\", details.status)}`;\n\t\tif (parts.length > 0)\n\t\t\ttext += ` ${theme.fg(\"dim\", \"·\")} ${parts.map((part) => theme.fg(\"dim\", part)).join(` ${theme.fg(\"dim\", \"·\")} `)}`;\n\t\tconst trimmedPreview = details.resultPreview.trim();\n\t\tconst previewLines = options.expanded\n\t\t\t? trimmedPreview.split(\"\\n\").filter((line) => line.trim())\n\t\t\t: [trimmedPreview.split(\"\\n\", 1)[0] ?? \"\"].filter((line) => line.trim());\n\t\tfor (const line of previewLines.length > 0 ? previewLines : [\"(no output)\"]) {\n\t\t\ttext += `\\n  ${theme.fg(\"dim\", `⎿  ${line}`)}`;\n\t\t}\n\t\tif (!options.expanded && trimmedPreview.includes(\"\\n\")) {\n\t\t\tconst expandKey = keyText(\"app.tools.expand\");\n\t\t\ttext += `\\n  ${theme.fg(\"dim\", `${expandKey} full notification`)}`;\n\t\t}\n\t\tif (details.sessionLabel && details.sessionValue) {\n\t\t\ttext += `\\n  ${theme.fg(\"muted\", `${details.sessionLabel}: ${shortenPath(details.sessionValue)}`)}`;\n\t\t}\n\t\treturn new Text(text, 0, 0);\n\t});\n\n\tpi.registerMessageRenderer<SubagentSteeringMessageDetails>(\n\t\tSUBAGENT_STEERING_MESSAGE_TYPE,\n\t\t(message, _options, theme) => {\n\t\t\tconst details = message.details as SubagentSteeringMessageDetails | undefined;\n\t\t\tif (!details) return undefined;\n\t\t\treturn new Text(\n\t\t\t\ttheme.fg(details.state === \"recovered\" ? \"warning\" : \"error\", formatSteeringNotice(details)),\n\t\t\t\t0,\n\t\t\t\t0,\n\t\t\t);\n\t\t},\n\t);\n\n\tpi.registerMessageRenderer<SubagentControlMessageDetails>(\n\t\tSUBAGENT_CONTROL_MESSAGE_TYPE,\n\t\t(message, _options, theme) => {\n\t\t\tconst details = message.details as SubagentControlMessageDetails | undefined;\n\t\t\tif (!details?.event) return undefined;\n\t\t\tconst content = typeof message.content === \"string\" ? message.content : undefined;\n\t\t\treturn new SubagentControlNoticeComponent(\n\t\t\t\t{ ...details, noticeText: formatSubagentControlNotice(details, content) },\n\t\t\t\ttheme,\n\t\t\t);\n\t\t},\n\t);\n\n\tconst executeSubagentCollapsed = (\n\t\tid: string,\n\t\tparams: SubagentParamsLike,\n\t\tsignal: AbortSignal,\n\t\tonUpdate: ((result: AgentToolResult<Details>) => void) | undefined,\n\t\tctx: ExtensionContext,\n\t) => {\n\t\tif (ctx.hasUI) ctx.ui.setToolsExpanded(false);\n\t\treturn executor.executePublic(id, params, signal, onUpdate, ctx);\n\t};\n\n\tconst slashBridge = registerSlashSubagentBridge({\n\t\tevents: pi.events,\n\t\tgetContext: () => state.lastUiContext,\n\t\texecute: (id, params, signal, onUpdate, ctx) => executeSubagentCollapsed(id, params, signal, onUpdate, ctx),\n\t});\n\n\tconst promptTemplateBridge = registerPromptTemplateDelegationBridge({\n\t\tevents: pi.events,\n\t\tgetContext: () => state.lastUiContext,\n\t\texecute: (requestId, params, signal, ctx, onUpdate) =>\n\t\t\texecuteSubagentCollapsed(requestId, params, signal, onUpdate, ctx),\n\t\texecuteStructured: (requestId, params, signal, ctx, onUpdate) => {\n\t\t\tif (ctx.hasUI) ctx.ui.setToolsExpanded(false);\n\t\t\treturn executor.executeDelegated(requestId, params, signal, onUpdate, ctx);\n\t\t},\n\t});\n\n\tconst rpcBridge = registerSubagentRpcBridge({\n\t\tevents: pi.events,\n\t\tgetContext: () => state.lastUiContext,\n\t\texecute: (id, params, signal, onUpdate, ctx) => executor.executePublic(id, params, signal, onUpdate, ctx),\n\t\tstate,\n\t});\n\n\tconst tool: ToolDefinition<typeof SubagentParams, Details> = {\n\t\tname: \"subagent\",\n\t\tlabel: \"Subagent\",\n\t\tdescription: buildSubagentToolDescription(config),\n\t\tparameters: SubagentParams,\n\n\t\texecute(id, params, signal, onUpdate, ctx) {\n\t\t\treturn executeSubagentCollapsed(\n\t\t\t\tid,\n\t\t\t\tparams as SubagentParamsLike,\n\t\t\t\tsignal ?? new AbortController().signal,\n\t\t\t\tonUpdate,\n\t\t\t\tctx,\n\t\t\t);\n\t\t},\n\n\t\trenderCall(args, theme) {\n\t\t\tif (args.action) {\n\t\t\t\tconst target = args.agent || args.chainName || \"\";\n\t\t\t\treturn new Text(\n\t\t\t\t\t`${theme.fg(\"toolTitle\", theme.bold(\"subagent \"))}${args.action}${target ? ` ${theme.fg(\"accent\", target)}` : \"\"}`,\n\t\t\t\t\t0,\n\t\t\t\t\t0,\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (args.workflowScript)\n\t\t\t\treturn new Text(\n\t\t\t\t\t`${theme.fg(\"toolTitle\", theme.bold(\"subagent \"))}${formatWorkflowManifest(args.workflowScript, args.async, false)}`,\n\t\t\t\t\t0,\n\t\t\t\t\t0,\n\t\t\t\t);\n\t\t\tconst directAgent = args.agent || \"worker\";\n\t\t\tconst asyncLabel = args.async === true ? theme.fg(\"warning\", \" [async]\") : \"\";\n\t\t\treturn new Text(\n\t\t\t\t`${theme.fg(\"toolTitle\", theme.bold(\"subagent \"))}${theme.fg(\"accent\", directAgent)}${asyncLabel}`,\n\t\t\t\t0,\n\t\t\t\t0,\n\t\t\t);\n\t\t},\n\n\t\trenderResult(result, options, theme, context) {\n\t\t\tclearLegacyResultAnimationTimer(context);\n\t\t\tconst renderedResult = { ...result, isError: context.isError };\n\t\t\treturn summaryInlineToolDisplay\n\t\t\t\t? renderSubagentSummary(renderedResult, options, theme)\n\t\t\t\t: renderSubagentResult(renderedResult, options, theme);\n\t\t},\n\t};\n\n\tpi.registerTool(tool);\n\n\tregisterWaitTool(pi, state, waitToolConfig.enabled, waitSubscriptionManager);\n\n\tpi.on(\"agent_end\", async (_event, ctx) => {\n\t\tif (!ctx.hasUI) await drainOutstandingWork({ state, events: pi.events });\n\t\tconst ownerSessionId = state.currentSessionId;\n\t\tif (!ownerSessionId) return;\n\t\tgoalTurnId += 1;\n\t\ttry {\n\t\t\tconst location = resolveMissionStoreLocation({\n\t\t\t\tprojectRoot: state.baseCwd,\n\t\t\t\t...(config.missions ? { config: config.missions } : {}),\n\t\t\t});\n\t\t\tconst retainedChildren = listRetainedChildren(DIRS.async, ownerSessionId);\n\t\t\tfor (const notice of collectGoalContinuationNotices({\n\t\t\t\tlocation,\n\t\t\t\townerSessionId,\n\t\t\t\tretainedChildren,\n\t\t\t\tturnId: goalTurnId,\n\t\t\t})) {\n\t\t\t\thandleSubagentControlNotice({\n\t\t\t\t\tpi,\n\t\t\t\t\tstate,\n\t\t\t\t\tvisibleControlNotices: new Set(),\n\t\t\t\t\tdetails: { source: \"goal\", event: notice.event, noticeText: notice.message },\n\t\t\t\t});\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tconsole.error(\"Failed to evaluate goal missions:\", error);\n\t\t}\n\t});\n\n\tregisterSlashCommands(pi, state);\n\n\tconst eventUnsubscribeStoreKey = \"__piSubagentEventUnsubscribes\";\n\tconst controlNoticeSeenStoreKey = \"__piSubagentVisibleControlNotices\";\n\tconst previousEventUnsubscribes = globalStore[eventUnsubscribeStoreKey];\n\tif (Array.isArray(previousEventUnsubscribes)) {\n\t\tfor (const unsubscribe of previousEventUnsubscribes) {\n\t\t\tif (typeof unsubscribe !== \"function\") continue;\n\t\t\ttry {\n\t\t\t\tunsubscribe();\n\t\t\t} catch {\n\t\t\t\t// Best effort cleanup for stale handlers from an older reload.\n\t\t\t}\n\t\t}\n\t}\n\tconst existingVisibleControlNotices = globalStore[controlNoticeSeenStoreKey];\n\tconst visibleControlNotices =\n\t\texistingVisibleControlNotices instanceof Set ? (existingVisibleControlNotices as Set<string>) : new Set<string>();\n\tglobalStore[controlNoticeSeenStoreKey] = visibleControlNotices;\n\tconst activeHerdrRuns = () =>\n\t\t[...state.asyncJobs.values()]\n\t\t\t.filter((job) => job.status === \"queued\" || job.status === \"running\")\n\t\t\t.map((job) => ({\n\t\t\t\tid: job.asyncId,\n\t\t\t\tagents: job.agents,\n\t\t\t\tneedsAttention: job.activityState === \"needs_attention\",\n\t\t\t}));\n\tconst herdrStatusBridge = registerHerdrStatusBridge({\n\t\tevents: pi.events,\n\t\tgetRuns: activeHerdrRuns,\n\t\tasync runHerdr(args) {\n\t\t\tawait pi.exec(process.env.HERDR_BIN || \"herdr\", [...args], { timeout: 5_000 });\n\t\t},\n\t});\n\tconst controlEventHandler = (payload: unknown) => {\n\t\thandleSubagentControlNotice({\n\t\t\tpi,\n\t\t\tstate,\n\t\t\tvisibleControlNotices,\n\t\t\tdetails: payload as SubagentControlMessageDetails,\n\t\t});\n\t};\n\tconst steeringNoticeHandler = (payload: unknown) => {\n\t\thandleSubagentSteeringNotice({ pi, state, details: payload as SubagentSteeringMessageDetails });\n\t};\n\tconst asyncStartedHandler = (payload: unknown) => {\n\t\thandleStarted(payload);\n\t\tfleetStatus?.refresh();\n\t};\n\tconst asyncCompleteHandler = (payload: unknown) => {\n\t\thandleComplete(payload);\n\t\tscheduledRunManager.handleAsyncCompletion(payload);\n\t\ttry {\n\t\t\tsyncMissionFromAsyncCompletion(payload);\n\t\t} catch (error) {\n\t\t\tconsole.error(\"Failed to update mission from async completion:\", error);\n\t\t}\n\t\tfleetStatus?.refresh();\n\t};\n\tconst eventUnsubscribes = [\n\t\tpi.events.on(SUBAGENT_ASYNC_STARTED_EVENT, asyncStartedHandler),\n\t\tpi.events.on(SUBAGENT_ASYNC_COMPLETE_EVENT, asyncCompleteHandler),\n\t\tpi.events.on(SUBAGENT_CONTROL_EVENT, controlEventHandler),\n\t\tpi.events.on(SUBAGENT_STEERING_NOTICE_EVENT, steeringNoticeHandler),\n\t\therdrStatusBridge.dispose,\n\t\trpcBridge.dispose,\n\t];\n\tglobalStore[eventUnsubscribeStoreKey] = eventUnsubscribes;\n\n\tpi.on(\"tool_result\", (event, ctx) => {\n\t\tif (event.toolName !== \"subagent\") return;\n\t\tif (!ctx.hasUI) return;\n\t\tstate.lastUiContext = ctx;\n\t\trestoreActiveJobs(ctx);\n\t\tfleetStatus?.setContext(ctx);\n\t\tfleetStatus?.refresh();\n\t\tif (state.asyncJobs.size > 0) {\n\t\t\trefreshWidget(ctx);\n\t\t\tensurePoller();\n\t\t}\n\t});\n\n\tconst cleanupSessionArtifacts = (ctx: ExtensionContext) => {\n\t\ttry {\n\t\t\tconst sessionFile = ctx.sessionManager.getSessionFile();\n\t\t\tif (sessionFile) {\n\t\t\t\tcleanupOldArtifacts(getArtifactsDir(sessionFile), DEFAULT_ARTIFACT_CONFIG.cleanupDays);\n\t\t\t}\n\t\t} catch {\n\t\t\t// Cleanup failures should not block session lifecycle events.\n\t\t}\n\t};\n\n\tconst resetSessionState = (ctx: ExtensionContext, recovering: boolean) => {\n\t\tstate.baseCwd = ctx.cwd;\n\t\tgoalTurnId = 0;\n\t\tstate.currentSessionId = resolveCurrentSessionId(ctx.sessionManager);\n\t\tstate.parentSessionFile = ctx.sessionManager.getSessionFile();\n\t\tstate.subagentSpawns = {\n\t\t\tsessionId: state.currentSessionId,\n\t\t\tcount: 0,\n\t\t\tconfiguredLimit: resolveMaxSubagentSpawnsPerSession(config.maxSubagentSpawnsPerSession) ?? null,\n\t\t\tgranted: 0,\n\t\t\tgrantHistory: [],\n\t\t};\n\t\t// Set PI_SUBAGENT_PARENT_SESSION for permission-system forwarding.\n\t\t// Only set in the root session (the interactive UI session), not in\n\t\t// child subagent processes — children inherit the parent's value\n\t\t// through the process environment at spawn time and must not overwrite\n\t\t// it with their own session identity.\n\t\tif (!process.env[SUBAGENT_CHILD_ENV]) {\n\t\t\tconst sessionId = ctx.sessionManager.getSessionId();\n\t\t\tif (sessionId) {\n\t\t\t\tprocess.env[SUBAGENT_PARENT_SESSION_ENV] = sessionId;\n\t\t\t}\n\t\t}\n\t\tstate.lastUiContext = ctx;\n\t\tcleanupSessionArtifacts(ctx);\n\t\tstate.foregroundControls.clear();\n\t\tstate.lastForegroundControlId = null;\n\t\tresetJobs(ctx);\n\t\trestoreActiveJobs(ctx);\n\t\tscheduledRunManager.bindSession(ctx);\n\t\trestoreSlashFinalSnapshots(ctx.sessionManager.getEntries());\n\t\twaitSubscriptionManager.restore();\n\t\tstartResultWatcher();\n\t\tprimeExistingResults({ triggerTurn: !recovering });\n\t\tfleetStatus?.setContext(ctx);\n\t};\n\n\tpi.on(\"agent_start\", () => {\n\t\therdrStatusBridge.agentStarted();\n\t});\n\n\tpi.on(\"session_compact\", () => {\n\t\tconst hasActiveAsyncWork = [...state.asyncJobs.values()].some(\n\t\t\t(job) => job.status === \"queued\" || job.status === \"running\",\n\t\t);\n\t\tif (!hasActiveAsyncWork || state.lastUiContext?.hasUI !== true) return;\n\t\tpi.sendMessage(\n\t\t\t{\n\t\t\t\tcustomType: \"subagent-compaction-resume\",\n\t\t\t\tcontent:\n\t\t\t\t\t\"Compaction is complete. Resume the parent task now; background subagent results will arrive separately when ready.\",\n\t\t\t\tdisplay: false,\n\t\t\t},\n\t\t\t{ triggerTurn: true },\n\t\t);\n\t});\n\n\tpi.on(\"session_start\", (event, ctx) => {\n\t\tconst recovering = event.reason === \"startup\" || event.reason === \"reload\" || event.reason === \"resume\";\n\t\tresetSessionState(ctx, recovering);\n\t\therdrStatusBridge.sessionStarted({\n\t\t\thasUI: ctx.hasUI === true,\n\t\t\truns: activeHerdrRuns(),\n\t\t});\n\t\trpcBridge.emitReady(ctx);\n\t\tsupervisorChannel.start();\n\t});\n\n\tpi.on(\"session_shutdown\", async () => {\n\t\tstopResultWatcher();\n\t\tstate.currentSessionId = null;\n\t\tstate.parentSessionFile = null;\n\t\tcompletionNotifier.dispose();\n\t\tdelete process.env[SUBAGENT_PARENT_SESSION_ENV];\n\t\tfor (const unsubscribe of eventUnsubscribes) {\n\t\t\ttry {\n\t\t\t\tunsubscribe();\n\t\t\t} catch {\n\t\t\t\t// Best effort cleanup during shutdown.\n\t\t\t}\n\t\t}\n\t\tif (globalStore[eventUnsubscribeStoreKey] === eventUnsubscribes) {\n\t\t\tdelete globalStore[eventUnsubscribeStoreKey];\n\t\t}\n\t\tscheduledRunManager.stop();\n\t\tif (state.poller) clearInterval(state.poller);\n\t\tstate.poller = null;\n\t\tfor (const timer of state.cleanupTimers.values()) {\n\t\t\tclearTimeout(timer);\n\t\t}\n\t\tstate.cleanupTimers.clear();\n\t\tstate.asyncJobs.clear();\n\t\tclearSlashSnapshots();\n\t\tslashBridge.cancelAll();\n\t\tslashBridge.dispose();\n\t\tpromptTemplateBridge.cancelAll();\n\t\tpromptTemplateBridge.dispose();\n\t\tsupervisorChannel.dispose();\n\t\tfleetStatus?.dispose();\n\t\tif (globalStore[runtimeCleanupStoreKey] === runtimeCleanup) {\n\t\t\tdelete globalStore[runtimeCleanupStoreKey];\n\t\t}\n\t\ttry {\n\t\t\tif (state.lastUiContext?.hasUI) {\n\t\t\t\tstate.lastUiContext.ui.setWidget(WIDGET_KEY, undefined);\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tif (!isStaleExtensionContextError(error)) throw error;\n\t\t}\n\t\tawait herdrStatusBridge.flush();\n\t});\n}\n"]}