@ashx-j/lunr 0.2.19 → 0.2.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +23 -0
- package/README.md +2 -2
- package/dist/builtin-extensions/index.d.ts.map +1 -1
- package/dist/builtin-extensions/index.js +0 -2
- package/dist/builtin-extensions/index.js.map +1 -1
- package/dist/builtin-extensions/pi-intercom/broker/spawn.d.ts +6 -4
- package/dist/builtin-extensions/pi-intercom/broker/spawn.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-intercom/broker/spawn.js +93 -49
- package/dist/builtin-extensions/pi-intercom/broker/spawn.js.map +1 -1
- package/dist/builtin-extensions/pi-intercom/index.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-intercom/index.js +351 -351
- package/dist/builtin-extensions/pi-intercom/index.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/index.d.ts +2 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/index.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/index.js +80 -43
- package/dist/builtin-extensions/pi-subagents/src/extension/index.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/schemas.d.ts +18 -8
- package/dist/builtin-extensions/pi-subagents/src/extension/schemas.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/schemas.js +36 -11
- package/dist/builtin-extensions/pi-subagents/src/extension/schemas.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.d.ts +4 -3
- package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.js +21 -12
- package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/intercom/native-supervisor-channel.d.ts +7 -1
- package/dist/builtin-extensions/pi-subagents/src/intercom/native-supervisor-channel.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/intercom/native-supervisor-channel.js +225 -15
- package/dist/builtin-extensions/pi-subagents/src/intercom/native-supervisor-channel.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/intercom/supervisor-ask.d.ts +29 -0
- package/dist/builtin-extensions/pi-subagents/src/intercom/supervisor-ask.d.ts.map +1 -0
- package/dist/builtin-extensions/pi-subagents/src/intercom/supervisor-ask.js +179 -0
- package/dist/builtin-extensions/pi-subagents/src/intercom/supervisor-ask.js.map +1 -0
- package/dist/builtin-extensions/pi-subagents/src/intercom/supervisor-questions.d.ts +89 -0
- package/dist/builtin-extensions/pi-subagents/src/intercom/supervisor-questions.d.ts.map +1 -0
- package/dist/builtin-extensions/pi-subagents/src/intercom/supervisor-questions.js +541 -0
- package/dist/builtin-extensions/pi-subagents/src/intercom/supervisor-questions.js.map +1 -0
- package/dist/builtin-extensions/pi-subagents/src/runs/background/async-execution.d.ts +9 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/async-execution.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/async-execution.js +34 -9
- package/dist/builtin-extensions/pi-subagents/src/runs/background/async-execution.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/async-job-tracker.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/async-job-tracker.js +1 -2
- package/dist/builtin-extensions/pi-subagents/src/runs/background/async-job-tracker.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/async-resume.d.ts +2 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/async-resume.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/async-resume.js +35 -8
- package/dist/builtin-extensions/pi-subagents/src/runs/background/async-resume.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/notify.d.ts +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/notify.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/notify.js +3 -3
- package/dist/builtin-extensions/pi-subagents/src/runs/background/notify.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/result-watcher.d.ts +1 -0
- package/dist/builtin-extensions/pi-subagents/src/runs/background/result-watcher.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/result-watcher.js +54 -38
- package/dist/builtin-extensions/pi-subagents/src/runs/background/result-watcher.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-wait.d.ts +3 -6
- package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-wait.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-wait.js +64 -5
- package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-wait.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/wait-tool.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/wait-tool.js +1 -0
- package/dist/builtin-extensions/pi-subagents/src/runs/background/wait-tool.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/chain-execution.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/chain-execution.js +21 -8
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/chain-execution.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.js +62 -16
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/acceptance.d.ts +2 -0
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/acceptance.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/acceptance.js +75 -18
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/acceptance.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/model-fallback.d.ts +13 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/model-fallback.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/model-fallback.js +67 -11
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/model-fallback.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/pi-args.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/pi-args.js +5 -2
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/pi-args.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.js +13 -3
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/shared/child-spec.d.ts +3 -0
- package/dist/builtin-extensions/pi-subagents/src/shared/child-spec.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/shared/child-spec.js +68 -11
- package/dist/builtin-extensions/pi-subagents/src/shared/child-spec.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/shared/formatters.d.ts +12 -1
- package/dist/builtin-extensions/pi-subagents/src/shared/formatters.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/shared/formatters.js +21 -2
- package/dist/builtin-extensions/pi-subagents/src/shared/formatters.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/shared/types.d.ts +16 -4
- package/dist/builtin-extensions/pi-subagents/src/shared/types.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/shared/types.js +1 -1
- package/dist/builtin-extensions/pi-subagents/src/shared/types.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/slash/slash-commands.d.ts +2 -0
- package/dist/builtin-extensions/pi-subagents/src/slash/slash-commands.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/slash/slash-commands.js +24 -6
- package/dist/builtin-extensions/pi-subagents/src/slash/slash-commands.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/tui/render.d.ts +44 -4
- package/dist/builtin-extensions/pi-subagents/src/tui/render.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/tui/render.js +227 -133
- package/dist/builtin-extensions/pi-subagents/src/tui/render.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/watchdog/register-main.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/watchdog/register-main.js +1 -1
- package/dist/builtin-extensions/pi-subagents/src/watchdog/register-main.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/watchdog/tool-actions.d.ts +2 -13
- package/dist/builtin-extensions/pi-subagents/src/watchdog/tool-actions.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/watchdog/tool-actions.js +5 -91
- package/dist/builtin-extensions/pi-subagents/src/watchdog/tool-actions.js.map +1 -1
- package/dist/core/permissions.d.ts +1 -0
- package/dist/core/permissions.d.ts.map +1 -1
- package/dist/core/permissions.js +8 -4
- package/dist/core/permissions.js.map +1 -1
- package/dist/core/plan-mode.d.ts.map +1 -1
- package/dist/core/plan-mode.js +0 -5
- package/dist/core/plan-mode.js.map +1 -1
- package/dist/core/runtime-bridges.d.ts.map +1 -1
- package/dist/core/runtime-bridges.js +0 -2
- package/dist/core/runtime-bridges.js.map +1 -1
- package/dist/core/subagent-cancellation.d.ts +21 -0
- package/dist/core/subagent-cancellation.d.ts.map +1 -0
- package/dist/core/subagent-cancellation.js +53 -0
- package/dist/core/subagent-cancellation.js.map +1 -0
- package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/assistant-message.js +12 -18
- package/dist/modes/interactive/components/assistant-message.js.map +1 -1
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution.js +12 -0
- package/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +1 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +29 -0
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/node-runtime/{agent-session-services-35WNVGMR.js → agent-session-services-75JKASCW.js} +3 -3
- package/dist/node-runtime/{builtin-extensions-322XDDEM.js → builtin-extensions-X4JMM4ZS.js} +2 -2
- package/dist/node-runtime/{chunk-XATUM2HK.js → chunk-25I2BHAA.js} +1 -1
- package/dist/node-runtime/{chunk-MCUNIRD5.js → chunk-46ECNOLF.js} +477 -246
- package/dist/node-runtime/{chunk-J26TSGDL.js → chunk-6N22HULO.js} +8 -5
- package/dist/node-runtime/{chunk-TFBIJTM3.js → chunk-AL2FMKKM.js} +7 -7
- package/dist/node-runtime/chunk-AOFXZQKA.js +24 -0
- package/dist/node-runtime/chunk-BBUVIEZB.js +51 -0
- package/dist/node-runtime/{chunk-ZRCBN365.js → chunk-ELWEXVO5.js} +7 -7
- package/dist/node-runtime/{chunk-ATGA2WLC.js → chunk-JYNNB7DU.js} +397 -34
- package/dist/node-runtime/{chunk-4FX25LWP.js → chunk-K3646JTM.js} +16 -117
- package/dist/node-runtime/{chunk-PNJSG3IZ.js → chunk-LTYWTR3V.js} +1 -369
- package/dist/node-runtime/{chunk-TXHKBRHZ.js → chunk-QKFMBRAL.js} +45 -31
- package/dist/node-runtime/{command-DWUM5X3U.js → command-RBTRL55A.js} +1 -1
- package/dist/node-runtime/{gateway-UZXMFGWY.js → gateway-L4ANRTVE.js} +2 -2
- package/dist/node-runtime/index.js +9 -8
- package/dist/node-runtime/{interactive-mode-QT5IKGQ5.js → interactive-mode-HPUNH7SV.js} +5 -4
- package/dist/node-runtime/{lunr-cron-QPBTKBGI.js → lunr-cron-NNYTWTGF.js} +2 -2
- package/dist/node-runtime/main.js +7 -7
- package/dist/node-runtime/{pi-intercom-EEMBGQID.js → pi-intercom-DDQMRNPJ.js} +96 -76
- package/dist/node-runtime/{pi-subagents-7YJ3I4VE.js → pi-subagents-T5WMMLBB.js} +1040 -318
- package/dist/node-runtime/rpc-entry.js +7 -7
- package/dist/node-runtime/{runtime-bridges-3RKBDLNA.js → runtime-bridges-NJQERQ72.js} +2 -4
- package/dist/node-runtime/{subagent-executor-5Q5A4HJ2.js → subagent-executor-SRFC26SJ.js} +168 -249
- package/docs/features.md +17 -3
- package/docs/keybindings.md +3 -1
- package/docs/settings.md +1 -1
- package/package.json +4 -4
- package/dist/builtin-extensions/lunr-settings-tools.d.ts +0 -4
- package/dist/builtin-extensions/lunr-settings-tools.d.ts.map +0 -1
- package/dist/builtin-extensions/lunr-settings-tools.js +0 -120
- package/dist/builtin-extensions/lunr-settings-tools.js.map +0 -1
- package/dist/core/settings-tools-bridge.d.ts +0 -35
- package/dist/core/settings-tools-bridge.d.ts.map +0 -1
- package/dist/core/settings-tools-bridge.js +0 -56
- package/dist/core/settings-tools-bridge.js.map +0 -1
- package/dist/node-runtime/chunk-3NMWLTPV.js +0 -64
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
import type { ExtensionAPI } from "@ashx-j/lunr";
|
|
15
15
|
import { Text } from "@ashx-j/lunr-tui";
|
|
16
16
|
export { loadConfig } from "./config.ts";
|
|
17
|
-
export declare function renderSubagentCall(args: any, theme: any, context: any): Text;
|
|
17
|
+
export declare function renderSubagentCall(args: any, theme: any, context: any, options: any): Text;
|
|
18
|
+
export declare function renderSubagentNotify(details: any, theme: any): Text;
|
|
18
19
|
export default function registerSubagentExtension(pi: ExtensionAPI): void;
|
|
19
20
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/builtin-extensions/pi-subagents/src/extension/index.ts"],"names":[],"mappings":"AACA;;;;;;;;;;;;GAYG;AAOH,OAAO,KAAK,EAAE,YAAY,EAAoC,MAAM,iCAAiC,CAAC;AAGtG,OAAO,EAA0B,IAAI,EAAmE,MAAM,wBAAwB,CAAC;AAmDvI,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAwEzC,wBAAgB,kBAAkB,CAAC,IAAI,KAAA,EAAE,KAAK,KAAA,EAAE,OAAO,KAAA,GAAG,IAAI,CAiC7D;AA8FD,MAAM,CAAC,OAAO,UAAU,yBAAyB,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CAyaxE","sourcesContent":["// @ts-nocheck\n/**\n * Subagent Tool\n *\n * Full-featured subagent with sync and async modes.\n * - Sync (default): Streams output, renders markdown, tracks usage\n * - Async: Background execution, emits events when done\n *\n * Modes: single (agent + task), parallel (tasks[]), chain (chain[] with {previous})\n * Toggle: async parameter (default: false, configurable via config.json)\n *\n * Config file: ~/.lunr/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 \"@ashx-j/lunr-agent\";\nimport type { ExtensionAPI, ExtensionContext, ToolDefinition } from \"@ashx-j/lunr\";\nimport { keyText } from \"../../../../modes/interactive/components/keybinding-hints.ts\";\nimport { awaitWithAbort } from \"../../../../utils/await-with-abort.ts\";\nimport { Box, Container, Spacer, Text, truncateToWidth, visibleWidth, wrapTextWithAnsi, type Component } from \"@ashx-j/lunr-tui\";\n\nimport { cleanupAllArtifactDirs, cleanupOldArtifacts, getArtifactsDir } from \"../shared/artifacts.ts\";\nimport { resolveCurrentSessionId } from \"../shared/session-identity.ts\";\nimport { cleanupOldChainDirs } from \"../shared/settings.ts\";\nimport { clearLegacyResultAnimationTimer, renderSubagentResult, subagentAnimSink } from \"../tui/render.ts\";\nimport { SubagentParams } from \"./schemas.ts\";\nimport { validateChainInput } from \"./chain-validation.ts\";\nimport type { createSubagentExecutor, SubagentParamsLike } from \"../runs/foreground/subagent-executor.ts\";\nimport { createAsyncJobTracker } from \"../runs/background/async-job-tracker.ts\";\nimport { createResultWatcher } from \"../runs/background/result-watcher.ts\";\nimport { createScheduledRunManager } from \"../runs/background/scheduled-runs.ts\";\nimport { registerSlashCommands } from \"../slash/slash-commands.ts\";\nimport { registerPromptTemplateDelegationBridge } from \"../slash/prompt-template-bridge.ts\";\nimport { registerMainWatchdog } from \"../watchdog/register-main.ts\";\nimport { registerSlashSubagentBridge } from \"../slash/slash-bridge.ts\";\nimport { createNativeSupervisorChannel } from \"../intercom/native-supervisor-channel.ts\";\nimport { registerSubagentRpcBridge } from \"./rpc.ts\";\nimport { clearSlashSnapshots, getSlashRenderableSnapshot, resolveSlashMessageDetails, restoreSlashFinalSnapshots, type SlashMessageDetails } from \"../slash/slash-live-state.ts\";\nimport { inspectSubagentStatus } from \"../runs/background/run-status.ts\";\nimport { resolveWaitToolConfig } from \"../runs/background/subagent-wait.ts\";\nimport { registerWaitTool } from \"../runs/background/wait-tool.ts\";\nimport { drainOutstandingWork } from \"../runs/background/auto-drain.ts\";\nimport registerSubagentNotify, { parseSubagentNotifyContent, type SubagentNotifyDetails } from \"../runs/background/notify.ts\";\nimport { formatSteeringNotice, handleSubagentSteeringNotice, SUBAGENT_STEERING_MESSAGE_TYPE, type SubagentSteeringMessageDetails } from \"./steering-notices.ts\";\nimport { SUBAGENT_CHILD_ENV, SUBAGENT_PARENT_SESSION_ENV } from \"../runs/shared/pi-args.ts\";\nimport { formatDuration, shortenPath } from \"../shared/formatters.ts\";\nimport { loadConfig } from \"./config.ts\";\nimport { buildSubagentToolDescription } from \"./tool-description.ts\";\nimport {\n\ttype Details,\n\ttype SubagentState,\n\tASYNC_DIR,\n\tDEFAULT_ARTIFACT_CONFIG,\n\tRESULTS_DIR,\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\tWIDGET_KEY,\n} from \"../shared/types.ts\";\nimport {\n\tclearPendingForegroundControlNotices,\n\tformatSubagentControlNotice,\n\thandleSubagentControlNotice,\n\tSUBAGENT_CONTROL_MESSAGE_TYPE,\n\ttype SubagentControlMessageDetails,\n} from \"./control-notices.ts\";\n\nexport { loadConfig } from \"./config.ts\";\n\n/**\n * Derive subagent session base directory from parent session file.\n * If parent session is ~/.lunr/agent/sessions/abc123.jsonl,\n * returns ~/.lunr/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\n/**\n * Create a directory and verify it is actually accessible.\n * On Windows with Azure AD/Entra ID, directories created shortly after\n * wake-from-sleep can end up with broken NTFS ACLs (null DACL) when the\n * cloud SID cannot be resolved without network connectivity. This leaves\n * the directory completely inaccessible to the creating user.\n */\nfunction ensureAccessibleDir(dirPath: string): void {\n\tfs.mkdirSync(dirPath, { recursive: true });\n\ttry {\n\t\tfs.accessSync(dirPath, fs.constants.R_OK | fs.constants.W_OK);\n\t} catch {\n\t\ttry {\n\t\t\tfs.rmSync(dirPath, { recursive: true, force: true });\n\t\t} catch {\n\t\t\t// Best effort: retry mkdir/access even if cleanup fails.\n\t\t}\n\t\tfs.mkdirSync(dirPath, { recursive: true });\n\t\tfs.accessSync(dirPath, fs.constants.R_OK | fs.constants.W_OK);\n\t}\n}\n\nfunction isSlashResultRunning(result: { details?: Details }): boolean {\n\treturn result.details?.progress?.some((entry) => entry.status === \"running\")\n\t\t|| result.details?.results.some((entry) => entry.progress?.status === \"running\")\n\t\t|| false;\n}\n\n// Drives the inline running-indicator braille animation for foreground subagent\n// results. Foreground runs receive progress only on child events, so the glyph\n// (derived from progress fields) would freeze between events. While a result is\n// running we tick a frame counter every 80ms; renderResult registers the\n// running-glyph lines in state.animEntries (via subagentAnimSink) and the timer\n// rewrites just those Text nodes + requests a repaint, instead of invalidate()-ing\n// the whole tool component (a full renderSubagentResult rebuild 12.5x/s made the\n// spinner laggy).\nfunction subagentResultIsRunning(result: { details?: Details }): boolean {\n\treturn result.details?.progress?.some((entry) => entry.status === \"running\")\n\t\t|| result.details?.results.some((entry) => entry.progress?.status === \"running\")\n\t\t|| false;\n}\n\nfunction effectiveParallelTaskCount(tasks: Array<{ count?: unknown }> | undefined): number {\n\tif (!tasks || tasks.length === 0) return 0;\n\treturn tasks.reduce((total, task) => {\n\t\tconst count = typeof task.count === \"number\" && Number.isInteger(task.count) && task.count >= 1 ? task.count : 1;\n\t\treturn total + count;\n\t}, 0);\n}\n\nexport function renderSubagentCall(args, theme, context): Text {\n\tif (args.action) {\n\t\tconst target = args.id || args.runId || \"\";\n\t\treturn new Text(\n\t\t\t`${theme.fg(\"toolTitle\", theme.bold(\"subagent \"))}${args.action}${target ? ` ${theme.fg(\"accent\", target)}` : \"\"}`,\n\t\t\t0,\n\t\t\t0,\n\t\t);\n\t}\n\tconst isParallel = (args.tasks?.length ?? 0) > 0;\n\tconst parallelCount = effectiveParallelTaskCount(args.tasks as Array<{ count?: unknown }> | undefined);\n\tconst asyncLabel = args.async === true && args.clarify !== true ? theme.fg(\"warning\", \" [async]\") : \"\";\n\tif (args.chain?.length) {\n\t\treturn new Text(\n\t\t\t`${theme.fg(\"toolTitle\", theme.bold(\"subagent \"))}chain (${args.chain.length})${asyncLabel}`,\n\t\t\t0,\n\t\t\t0,\n\t\t);\n\t}\n\tif (isParallel) {\n\t\treturn new Text(\n\t\t\t`${theme.fg(\"toolTitle\", theme.bold(\"subagent \"))}parallel (${parallelCount})${asyncLabel}`,\n\t\t\t0,\n\t\t\t0,\n\t\t);\n\t}\n\tconst showDescription = !context?.isPartial && !context?.expanded && !context?.isError;\n\tconst description = showDescription ? theme.fg(\"accent\", args.description || args.task || \"?\") : \"\";\n\treturn new Text(\n\t\t`${theme.fg(\"toolTitle\", theme.bold(description ? \"subagent \" : \"subagent\"))}${description}${asyncLabel}`,\n\t\t0,\n\t\t0,\n\t);\n}\n\nfunction ensureSubagentResultAnimation(context: { state: Record<string, unknown>; invalidate?: () => void; requestRender?: () => void }): void {\n\tconst state = context.state as {\n\t\tsubagentResultAnimationTimer?: ReturnType<typeof setInterval>;\n\t\tframe?: number;\n\t\tanimEntries?: Array<{ text: Text; line: (frame: number, now: number) => string }>;\n\t};\n\tif (state.subagentResultAnimationTimer) return;\n\tif (typeof context.invalidate !== \"function\") return;\n\tif (state.frame === undefined) state.frame = 0;\n\tstate.subagentResultAnimationTimer = setInterval(() => {\n\t\tstate.frame = ((state.frame ?? 0) + 1) % 10;\n\t\ttry {\n\t\t\tconst entries = state.animEntries;\n\t\t\tif (entries?.length && typeof context.requestRender === \"function\") {\n\t\t\t\tconst now = Date.now();\n\t\t\t\tfor (const entry of entries) entry.text.setText(entry.line(state.frame ?? 0, now));\n\t\t\t\tcontext.requestRender();\n\t\t\t} else {\n\t\t\t\tcontext.invalidate();\n\t\t\t}\n\t\t} catch {}\n\t}, 80);\n}\n\nfunction isSlashResultError(result: { details?: Details }): boolean {\n\treturn result.details?.results.some((entry) => entry.exitCode !== 0 && entry.progress?.status !== \"running\") || false;\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) ? \"toolPendingBg\" : isSlashResultError(result) ? \"toolErrorBg\" : \"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\tconstructor(\n\t\tprivate readonly details: SubagentControlMessageDetails,\n\t\tprivate readonly theme: ExtensionContext[\"ui\"][\"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\tensureAccessibleDir(RESULTS_DIR);\n\tensureAccessibleDir(ASYNC_DIR);\n\tcleanupOldChainDirs();\n\n\tconst config = loadConfig();\n\tconst waitToolConfig = resolveWaitToolConfig(config.waitTool);\n\tconst asyncByDefault = config.asyncByDefault === true;\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\tforegroundSubagentInFlight: 0,\n\t\tsubagentSpawns: { sessionId: null, count: 0 },\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\tpendingForegroundControlNotices: new Map(),\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 mainWatchdog = registerMainWatchdog(pi);\n\tconst { startResultWatcher, primeExistingResults, stopResultWatcher } = createResultWatcher(\n\t\tpi,\n\t\tstate,\n\t\tRESULTS_DIR,\n\t\t10 * 60 * 1000,\n\t);\n\tstartResultWatcher();\n\tprimeExistingResults();\n\n\tlet generation = 0;\n\tlet executorPromise: Promise<ReturnType<typeof createSubagentExecutor>> | undefined;\n\tconst pendingLaunches = new Set<Promise<void>>();\n\tconst runtimeCleanup = () => {\n\t\tgeneration++;\n\t\texecutorPromise = undefined;\n\t\tpendingLaunches.clear();\n\t\tmainWatchdog.dispose();\n\t\tstopResultWatcher();\n\t\tscheduledRunManager.stop();\n\t\tsupervisorChannel.dispose();\n\t\tclearPendingForegroundControlNotices(state);\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 } = createAsyncJobTracker(pi, state, ASYNC_DIR, {\n\t\twidgetEnabled: config.asyncWidget !== false,\n\t});\n\tlet executorExecute: ((id: string, params: SubagentParamsLike, signal: AbortSignal, onUpdate: ((r: AgentToolResult<Details>) => void) | undefined, ctx: ExtensionContext) => Promise<AgentToolResult<Details>>) | undefined;\n\tconst scheduledRunManager = createScheduledRunManager({\n\t\tconfig,\n\t\tlaunch: (params, ctx, signal) => {\n\t\t\tif (!executorExecute) {\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 executorExecute(randomUUID(), params, signal, undefined, ctx);\n\t\t},\n\t});\n\tconst executor: ReturnType<typeof createSubagentExecutor> = {\n\t\tasync execute(id, params, signal, onUpdate, ctx) {\n\t\t\tlet resolveLaunchReady!: () => void;\n\t\t\tlet launchReady = false;\n\t\t\tconst launchReadyPromise = new Promise<void>((resolve) => {\n\t\t\t\tresolveLaunchReady = resolve;\n\t\t\t});\n\t\t\tconst markLaunchReady = () => {\n\t\t\t\tif (launchReady) return;\n\t\t\t\tlaunchReady = true;\n\t\t\t\tpendingLaunches.delete(launchReadyPromise);\n\t\t\t\tresolveLaunchReady();\n\t\t\t};\n\t\t\tpendingLaunches.add(launchReadyPromise);\n\t\t\ttry {\n\t\t\t\tconst currentGeneration = generation;\n\t\t\t\tsignal?.throwIfAborted();\n\t\t\t\tif (!executorPromise) {\n\t\t\t\t\tconst pending = import(\"../runs/foreground/subagent-executor.ts\").then(({ createSubagentExecutor }) => {\n\t\t\t\t\t\tif (generation !== currentGeneration) throw new Error(\"Subagent session changed before initialization\");\n\t\t\t\t\t\treturn createSubagentExecutor({\n\t\t\t\t\t\t\tpi, state, config, asyncByDefault,\n\t\t\t\t\t\t\twaitToolEnabled: waitToolConfig.enabled,\n\t\t\t\t\t\t\thandleScheduledRunAction: (params, ctx) => scheduledRunManager.handleToolCall(params, ctx),\n\t\t\t\t\t\t\twatchdog: mainWatchdog, tempArtifactsDir, getSubagentSessionRoot, expandTilde,\n\t\t\t\t\t\t});\n\t\t\t\t\t});\n\t\t\t\t\texecutorPromise = pending;\n\t\t\t\t\tvoid pending.catch(() => {\n\t\t\t\t\t\tif (executorPromise === pending) executorPromise = undefined;\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tconst ready = await awaitWithAbort(executorPromise, signal);\n\t\t\t\tsignal?.throwIfAborted();\n\t\t\t\tif (generation !== currentGeneration) throw new Error(\"Subagent session changed before execution\");\n\t\t\t\tconst execution = ready.execute(id, params, signal, onUpdate, ctx);\n\t\t\t\tmarkLaunchReady();\n\t\t\t\treturn await execution;\n\t\t\t} finally {\n\t\t\t\tmarkLaunchReady();\n\t\t\t}\n\t\t},\n\t};\n\texecutorExecute = executor.execute;\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 = typeof message.content === \"string\"\n\t\t\t? message.content\n\t\t\t: message.content\n\t\t\t\t.filter((entry) => entry.type === \"text\")\n\t\t\t\t.map((entry) => entry.text)\n\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 = details.status === \"completed\"\n\t\t\t? theme.fg(\"success\", \"✓\")\n\t\t\t: details.status === \"paused\"\n\t\t\t\t? theme.fg(\"warning\", \"■\")\n\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\tconst notifyLabel = details.taskInfo || details.agent || \"child\";\n\t\tlet text = `${icon} ${theme.bold(notifyLabel)} ${theme.fg(\"dim\", details.status)}`;\n\t\tif (parts.length > 0) text += ` ${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>(SUBAGENT_STEERING_MESSAGE_TYPE, (message, _options, theme) => {\n\t\tconst details = message.details as SubagentSteeringMessageDetails | undefined;\n\t\tif (!details) return undefined;\n\t\treturn new Text(theme.fg(details.state === \"recovered\" ? \"warning\" : \"error\", formatSteeringNotice(details)), 0, 0);\n\t});\n\n\tpi.registerMessageRenderer<SubagentControlMessageDetails>(SUBAGENT_CONTROL_MESSAGE_TYPE, () => undefined);\n\n\tconst executeSubagentCollapsed = (id: string, params: SubagentParamsLike, signal: AbortSignal, onUpdate: ((result: AgentToolResult<Details>) => void) | undefined, ctx: ExtensionContext) => {\n\t\tif (ctx.hasUI) ctx.ui.setToolsExpanded(false);\n\t\treturn executor.execute(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) =>\n\t\t\texecuteSubagentCollapsed(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});\n\n\tconst rpcBridge = registerSubagentRpcBridge({\n\t\tevents: pi.events,\n\t\tgetContext: () => state.lastUiContext,\n\t\texecute: (id, params, signal, onUpdate, ctx) => executor.execute(id, params, signal, onUpdate, ctx),\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\t\t// lunr: same-turn SINGLE calls overlap; sequential work uses chain.\n\t\texecutionMode: \"parallel\",\n\n\t\tprepareArguments(args) {\n\t\t\t// Run friendly chain validation before pi-ai's raw TypeBox schema check\n\t\t\t// so the model sees which property is disallowed, what is allowed, and a\n\t\t\t// valid example instead of `chain.N: must not have additional properties`.\n\t\t\tvalidateChainInput(args);\n\t\t\treturn args as never;\n\t\t},\n\n\t\texecute(id, params, signal, onUpdate, ctx) {\n\t\t\treturn executeSubagentCollapsed(id, params, signal, onUpdate, ctx);\n\t\t},\n\n\t\trenderCall: renderSubagentCall,\n\n\t\trenderResult(result, options, theme, context) {\n\t\t\tif (subagentResultIsRunning(result)) {\n\t\t\t\tensureSubagentResultAnimation(context);\n\t\t\t} else {\n\t\t\t\tclearLegacyResultAnimationTimer(context);\n\t\t\t}\n\t\t\tconst state = context.state as { frame?: number; animEntries?: Array<{ text: Text; line: (frame: number) => string }> };\n\t\t\t// lunr: collect running-glyph line templates while rendering so the\n\t\t\t// animation timer can rewrite them in place (see ensureSubagentResultAnimation).\n\t\t\tconst entries: Array<{ text: Text; line: (frame: number) => string }> = [];\n\t\t\tstate.animEntries = entries;\n\t\t\tsubagentAnimSink.current = entries;\n\t\t\ttry {\n\t\t\t\treturn renderSubagentResult(result, options, theme, state.frame ?? 0);\n\t\t\t} finally {\n\t\t\t\tsubagentAnimSink.current = null;\n\t\t\t}\n\t\t},\n\n\t};\n\n\tpi.registerTool(tool);\n\n\t// lunr: register a callback so toggling model tiers mid-session rebuilds the subagent\n\t// tool description (adds/removes MODEL TIERS guidance) and refreshes the tool registry.\n\tconst modelTierBridge = (globalThis as Record<symbol, unknown>)[Symbol.for(\"@lunr/model-tiers\")] as\n\t\t| { registerToolDescriptionRefresher?: (refresher: () => void) => void }\n\t\t| undefined;\n\tmodelTierBridge?.registerToolDescriptionRefresher?.(() => {\n\t\ttool.description = buildSubagentToolDescription(config);\n\t\t// pi.runtime is not on ExtensionAPI; registerTool refreshes the live registry.\n\t\tpi.registerTool(tool);\n\t});\n\n\tregisterWaitTool(pi, state, waitToolConfig.enabled, async (signal) => {\n\t\tconst launches = [...pendingLaunches];\n\t\tif (launches.length === 0) return;\n\t\tawait awaitWithAbort(Promise.all(launches).then(() => {}), signal);\n\t});\n\n\tpi.on(\"agent_end\", async (_event, ctx) => {\n\t\tif (ctx.hasUI) return;\n\t\tawait drainOutstandingWork({ state, events: pi.events });\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\tregisterSubagentNotify(pi, state, { batchConfig: config.completionBatch });\n\n\tconst existingVisibleControlNotices = globalStore[controlNoticeSeenStoreKey];\n\tconst visibleControlNotices = existingVisibleControlNotices instanceof Set ? existingVisibleControlNotices as Set<string> : new Set<string>();\n\tglobalStore[controlNoticeSeenStoreKey] = visibleControlNotices;\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 eventUnsubscribes = [\n\t\tpi.events.on(SUBAGENT_ASYNC_STARTED_EVENT, handleStarted),\n\t\tpi.events.on(SUBAGENT_ASYNC_COMPLETE_EVENT, handleComplete),\n\t\tpi.events.on(SUBAGENT_CONTROL_EVENT, controlEventHandler),\n\t\tpi.events.on(SUBAGENT_STEERING_NOTICE_EVENT, steeringNoticeHandler),\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\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) => {\n\t\tgeneration++;\n\t\texecutorPromise = undefined;\n\t\tpendingLaunches.clear();\n\t\tstate.baseCwd = ctx.cwd;\n\t\tstate.currentSessionId = resolveCurrentSessionId(ctx.sessionManager);\n\t\tstate.subagentSpawns = { sessionId: state.currentSessionId, count: 0 };\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\tclearPendingForegroundControlNotices(state);\n\t\tresetJobs(ctx);\n\t\trestoreActiveJobs(ctx);\n\t\tscheduledRunManager.bindSession(ctx);\n\t\trestoreSlashFinalSnapshots(ctx.sessionManager.getEntries());\n\t\tprimeExistingResults();\n\t};\n\n\tpi.on(\"session_start\", (_event, ctx) => {\n\t\tresetSessionState(ctx);\n\t\trpcBridge.emitReady(ctx);\n\t\tsupervisorChannel.start();\n\t});\n\n\tpi.on(\"session_shutdown\", () => {\n\t\tgeneration++;\n\t\texecutorPromise = undefined;\n\t\tpendingLaunches.clear();\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\tstopResultWatcher();\n\t\tscheduledRunManager.stop();\n\t\tif (state.poller) clearInterval(state.poller);\n\t\tstate.poller = null;\n\t\tclearPendingForegroundControlNotices(state);\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\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});\n}\n"]}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/builtin-extensions/pi-subagents/src/extension/index.ts"],"names":[],"mappings":"AACA;;;;;;;;;;;;GAYG;AAQH,OAAO,KAAK,EAAE,YAAY,EAAoC,MAAM,iCAAiC,CAAC;AAEtG,OAAO,EAA0B,IAAI,EAAmE,MAAM,wBAAwB,CAAC;AAmDvI,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AA+EzC,wBAAgB,kBAAkB,CAAC,IAAI,KAAA,EAAE,KAAK,KAAA,EAAE,OAAO,KAAA,EAAE,OAAO,KAAA,QA+B/D;AAED,wBAAgB,oBAAoB,CAAC,OAAO,KAAA,EAAE,KAAK,KAAA,QASlD;AA8FD,MAAM,CAAC,OAAO,UAAU,yBAAyB,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CA2bxE","sourcesContent":["// @ts-nocheck\n/**\n * Subagent Tool\n *\n * Full-featured subagent with sync and async modes.\n * - Sync (default): Streams output, renders markdown, tracks usage\n * - Async: Background execution, emits events when done\n *\n * Modes: single (agent + task), parallel (tasks[]), chain (chain[] with {previous})\n * Toggle: async parameter (default: false, configurable via config.json)\n *\n * Config file: ~/.lunr/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 { createSubagentCancellation, registerSubagentCancellation } from \"../../../../core/subagent-cancellation.ts\";\nimport * as fs from \"node:fs\";\nimport * as os from \"node:os\";\nimport * as path from \"node:path\";\nimport type { AgentToolResult } from \"@ashx-j/lunr-agent\";\nimport type { ExtensionAPI, ExtensionContext, ToolDefinition } from \"@ashx-j/lunr\";\nimport { awaitWithAbort } from \"../../../../utils/await-with-abort.ts\";\nimport { Box, Container, Spacer, Text, truncateToWidth, visibleWidth, wrapTextWithAnsi, type Component } from \"@ashx-j/lunr-tui\";\n\nimport { cleanupAllArtifactDirs, cleanupOldArtifacts, getArtifactsDir } from \"../shared/artifacts.ts\";\nimport { resolveCurrentSessionId } from \"../shared/session-identity.ts\";\nimport { cleanupOldChainDirs } from \"../shared/settings.ts\";\nimport { clearLegacyResultAnimationTimer, disposeSubagentWidget, renderSubagentResult, subagentAnimSink } from \"../tui/render.ts\";\nimport { SubagentParams } from \"./schemas.ts\";\nimport { validateChainInput } from \"./chain-validation.ts\";\nimport type { createSubagentExecutor, SubagentParamsLike } from \"../runs/foreground/subagent-executor.ts\";\nimport { createAsyncJobTracker } from \"../runs/background/async-job-tracker.ts\";\nimport { createResultWatcher } from \"../runs/background/result-watcher.ts\";\nimport { createScheduledRunManager } from \"../runs/background/scheduled-runs.ts\";\nimport { registerSlashCommands } from \"../slash/slash-commands.ts\";\nimport { registerPromptTemplateDelegationBridge } from \"../slash/prompt-template-bridge.ts\";\nimport { registerMainWatchdog } from \"../watchdog/register-main.ts\";\nimport { registerSlashSubagentBridge } from \"../slash/slash-bridge.ts\";\nimport { createNativeSupervisorChannel } from \"../intercom/native-supervisor-channel.ts\";\nimport { registerSubagentRpcBridge } from \"./rpc.ts\";\nimport { clearSlashSnapshots, getSlashRenderableSnapshot, resolveSlashMessageDetails, restoreSlashFinalSnapshots, type SlashMessageDetails } from \"../slash/slash-live-state.ts\";\nimport { inspectSubagentStatus } from \"../runs/background/run-status.ts\";\nimport { resolveWaitToolConfig } from \"../runs/background/subagent-wait.ts\";\nimport { registerWaitTool } from \"../runs/background/wait-tool.ts\";\nimport { drainOutstandingWork } from \"../runs/background/auto-drain.ts\";\nimport registerSubagentNotify, { parseSubagentNotifyContent, type SubagentNotifyDetails } from \"../runs/background/notify.ts\";\nimport { formatSteeringNotice, handleSubagentSteeringNotice, SUBAGENT_STEERING_MESSAGE_TYPE, type SubagentSteeringMessageDetails } from \"./steering-notices.ts\";\nimport { SUBAGENT_CHILD_ENV, SUBAGENT_PARENT_SESSION_ENV } from \"../runs/shared/pi-args.ts\";\n\nimport { loadConfig } from \"./config.ts\";\nimport { buildSubagentToolDescription } from \"./tool-description.ts\";\nimport {\n\ttype Details,\n\ttype SubagentState,\n\tASYNC_DIR,\n\tDEFAULT_ARTIFACT_CONFIG,\n\tRESULTS_DIR,\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\tWIDGET_KEY,\n} from \"../shared/types.ts\";\nimport {\n\tclearPendingForegroundControlNotices,\n\tformatSubagentControlNotice,\n\thandleSubagentControlNotice,\n\tSUBAGENT_CONTROL_MESSAGE_TYPE,\n\ttype SubagentControlMessageDetails,\n} from \"./control-notices.ts\";\n\nexport { loadConfig } from \"./config.ts\";\n\n/**\n * Derive subagent session base directory from parent session file.\n * If parent session is ~/.lunr/agent/sessions/abc123.jsonl,\n * returns ~/.lunr/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\n/**\n * Create a directory and verify it is actually accessible.\n * On Windows with Azure AD/Entra ID, directories created shortly after\n * wake-from-sleep can end up with broken NTFS ACLs (null DACL) when the\n * cloud SID cannot be resolved without network connectivity. This leaves\n * the directory completely inaccessible to the creating user.\n */\nfunction ensureAccessibleDir(dirPath: string): void {\n\tfs.mkdirSync(dirPath, { recursive: true });\n\ttry {\n\t\tfs.accessSync(dirPath, fs.constants.R_OK | fs.constants.W_OK);\n\t} catch {\n\t\ttry {\n\t\t\tfs.rmSync(dirPath, { recursive: true, force: true });\n\t\t} catch {\n\t\t\t// Best effort: retry mkdir/access even if cleanup fails.\n\t\t}\n\t\tfs.mkdirSync(dirPath, { recursive: true });\n\t\tfs.accessSync(dirPath, fs.constants.R_OK | fs.constants.W_OK);\n\t}\n}\n\nfunction isSlashResultRunning(result: { details?: Details }): boolean {\n\treturn result.details?.progress?.some((entry) => entry.status === \"running\")\n\t\t|| result.details?.results.some((entry) => entry.progress?.status === \"running\")\n\t\t|| false;\n}\n\n// Drives the inline running-indicator braille animation for foreground subagent\n// results. Foreground runs receive progress only on child events, so the glyph\n// (derived from progress fields) would freeze between events. While a result is\n// running we tick a frame counter every 80ms; renderResult registers the\n// running-glyph lines in state.animEntries (via subagentAnimSink) and the timer\n// rewrites just those Text nodes + requests a repaint, instead of invalidate()-ing\n// the whole tool component (a full renderSubagentResult rebuild 12.5x/s made the\n// spinner laggy).\nfunction subagentResultIsRunning(result: { details?: Details }): boolean {\n\treturn result.details?.progress?.some((entry) => entry.status === \"running\")\n\t\t|| result.details?.results.some((entry) => entry.progress?.status === \"running\")\n\t\t|| false;\n}\n\nfunction effectiveParallelTaskCount(tasks: Array<{ count?: unknown }> | undefined): number {\n\tif (!tasks || tasks.length === 0) return 0;\n\treturn tasks.reduce((total, task) => {\n\t\tconst count = typeof task.count === \"number\" && Number.isInteger(task.count) && task.count >= 1 ? task.count : 1;\n\t\treturn total + count;\n\t}, 0);\n}\n\nfunction callIsAsync(args, asyncByDefault) {\n\tif (args?.clarify === true) return false;\n\tif (args?.async === true) return true;\n\tif (args?.async === false) return false;\n\treturn asyncByDefault === true;\n}\n\nexport function renderSubagentCall(args, theme, context, options) {\n\tif (args.action) {\n\t\tconst target = args.id || args.runId || \"\";\n\t\treturn new Text(\n\t\t\t`${theme.fg(\"toolTitle\", theme.bold(\"subagent \"))}${args.action}${target ? ` ${theme.fg(\"accent\", target)}` : \"\"}`,\n\t\t\t0,\n\t\t\t0,\n\t\t);\n\t}\n\tconst isParallel = (args.tasks?.length ?? 0) > 0;\n\tconst parallelCount = effectiveParallelTaskCount(args.tasks as Array<{ count?: unknown }> | undefined);\n\tconst asyncMark = callIsAsync(args, options?.asyncByDefault) ? `${theme.fg(\"warning\", \"async\")} ` : \"\";\n\tif (args.chain?.length) {\n\t\treturn new Text(\n\t\t\t`${theme.fg(\"toolTitle\", theme.bold(\"subagent \"))}${asyncMark}chain (${args.chain.length})`,\n\t\t\t0,\n\t\t\t0,\n\t\t);\n\t}\n\tif (isParallel) {\n\t\treturn new Text(\n\t\t\t`${theme.fg(\"toolTitle\", theme.bold(\"subagent \"))}${asyncMark}parallel (${parallelCount})`,\n\t\t\t0,\n\t\t\t0,\n\t\t);\n\t}\n\tconst showDescription = !context?.result && !context?.isPartial && !context?.expanded && !context?.isError;\n\tconst description = showDescription ? theme.fg(\"accent\", args.description || args.task || \"?\") : \"\";\n\tconst title = theme.fg(\"toolTitle\", theme.bold(\"subagent\"));\n\tconst rest = [asyncMark.trim() ? theme.fg(\"warning\", \"async\") : \"\", description].filter(Boolean).join(\" \");\n\treturn new Text(rest ? `${title} ${rest}` : title, 0, 0);\n}\n\nexport function renderSubagentNotify(details, theme) {\n\tconst status = typeof details?.status === \"string\" ? details.status : \"\";\n\tconst icon = status === \"completed\"\n\t\t? theme.fg(\"success\", \"✓\")\n\t\t: status === \"failed\"\n\t\t\t? theme.fg(\"error\", \"✗\")\n\t\t\t: theme.fg(\"warning\", \"■\");\n\tconst title = details?.agent || \"child\";\n\treturn new Text(`${icon} ${theme.bold(title)} ${theme.fg(\"dim\", status)}`, 0, 0);\n}\n\nfunction ensureSubagentResultAnimation(context: { state: Record<string, unknown>; invalidate?: () => void; requestRender?: () => void }): void {\n\tconst state = context.state as {\n\t\tsubagentResultAnimationTimer?: ReturnType<typeof setInterval>;\n\t\tframe?: number;\n\t\tanimEntries?: Array<{ text: Text; line: (frame: number, now: number) => string }>;\n\t};\n\tif (state.subagentResultAnimationTimer) return;\n\tif (typeof context.invalidate !== \"function\") return;\n\tif (state.frame === undefined) state.frame = 0;\n\tstate.subagentResultAnimationTimer = setInterval(() => {\n\t\tstate.frame = ((state.frame ?? 0) + 1) % 10;\n\t\ttry {\n\t\t\tconst entries = state.animEntries;\n\t\t\tif (entries?.length && typeof context.requestRender === \"function\") {\n\t\t\t\tconst now = Date.now();\n\t\t\t\tfor (const entry of entries) entry.text.setText(entry.line(state.frame ?? 0, now));\n\t\t\t\tcontext.requestRender();\n\t\t\t} else {\n\t\t\t\tcontext.invalidate();\n\t\t\t}\n\t\t} catch {}\n\t}, 80);\n}\n\nfunction isSlashResultError(result: { details?: Details }): boolean {\n\treturn result.details?.results.some((entry) => entry.exitCode !== 0 && entry.progress?.status !== \"running\") || false;\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) ? \"toolPendingBg\" : isSlashResultError(result) ? \"toolErrorBg\" : \"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\tconstructor(\n\t\tprivate readonly details: SubagentControlMessageDetails,\n\t\tprivate readonly theme: ExtensionContext[\"ui\"][\"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\tensureAccessibleDir(RESULTS_DIR);\n\tensureAccessibleDir(ASYNC_DIR);\n\tcleanupOldChainDirs();\n\n\tconst config = loadConfig();\n\tconst waitToolConfig = resolveWaitToolConfig(config.waitTool);\n\tconst asyncByDefault = config.asyncByDefault === true;\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\tsessionGeneration: 0,\n\t\tforegroundSubagentInFlight: 0,\n\t\tsubagentSpawns: { sessionId: null, count: 0 },\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\tpendingForegroundControlNotices: new Map(),\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 mainWatchdog = registerMainWatchdog(pi);\n\tconst { startResultWatcher, primeExistingResults, stopResultWatcher } = createResultWatcher(\n\t\tpi,\n\t\tstate,\n\t\tRESULTS_DIR,\n\t\t10 * 60 * 1000,\n\t);\n\tstartResultWatcher();\n\tprimeExistingResults();\n\n\tlet generation = 0;\n\tlet executorPromise: Promise<ReturnType<typeof createSubagentExecutor>> | undefined;\n\tconst pendingLaunches = new Set<Promise<void>>();\n\tconst pendingAsyncLaunches = new Set<Promise<void>>();\n\tlet unregisterCancellation: (() => void) | undefined;\n\tconst runtimeCleanup = () => {\n\t\tgeneration++;\n\t\tsupervisorChannel.cancelOwnedQuestions(\"parent runtime replaced\");\n\t\tstate.sessionGeneration = (state.sessionGeneration ?? 0) + 1;\n\t\texecutorPromise = undefined;\n\t\tpendingLaunches.clear();\n\t\tpendingAsyncLaunches.clear();\n\t\tunregisterCancellation?.();\n\t\tmainWatchdog.dispose();\n\t\tstopResultWatcher();\n\t\tscheduledRunManager.stop();\n\t\tsupervisorChannel.dispose();\n\t\tclearPendingForegroundControlNotices(state);\n\t\tdisposeSubagentWidget();\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: trackComplete, resetJobs, restoreActiveJobs } = createAsyncJobTracker(pi, state, ASYNC_DIR, {\n\t\twidgetEnabled: config.asyncWidget !== false,\n\t});\n\tconst handleComplete = (data: unknown) => {\n\t\ttrackComplete(data);\n\t\tconst result = data as { id?: string; sessionId?: string };\n\t\tif (typeof state.currentSessionId === \"string\" && result.sessionId !== state.currentSessionId) return;\n\t\tif (typeof result.id === \"string\" && result.id.trim()) {\n\t\t\tsupervisorChannel.settleRunQuestions(result.id, \"async run settled\");\n\t\t}\n\t};\n\tlet executorExecute: ((id: string, params: SubagentParamsLike, signal: AbortSignal, onUpdate: ((r: AgentToolResult<Details>) => void) | undefined, ctx: ExtensionContext) => Promise<AgentToolResult<Details>>) | undefined;\n\tconst scheduledRunManager = createScheduledRunManager({\n\t\tconfig,\n\t\tlaunch: (params, ctx, signal) => {\n\t\t\tif (!executorExecute) {\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 executorExecute(randomUUID(), params, signal, undefined, ctx);\n\t\t},\n\t});\n\tconst executor: ReturnType<typeof createSubagentExecutor> = {\n\t\tasync execute(id, params, signal, onUpdate, ctx) {\n\t\t\tlet resolveLaunchReady!: () => void;\n\t\t\tlet launchReady = false;\n\t\t\tconst launchReadyPromise = new Promise<void>((resolve) => {\n\t\t\t\tresolveLaunchReady = resolve;\n\t\t\t});\n\t\t\tconst markLaunchReady = () => {\n\t\t\t\tif (launchReady) return;\n\t\t\t\tlaunchReady = true;\n\t\t\t\tpendingLaunches.delete(launchReadyPromise);\n\t\t\t\tpendingAsyncLaunches.delete(launchReadyPromise);\n\t\t\t\tresolveLaunchReady();\n\t\t\t};\n\t\t\tpendingLaunches.add(launchReadyPromise);\n\t\t\tif ((params.task || params.tasks || params.chain) && !params.clarify &&\n\t\t\t\t(params.async === true || (params.async === undefined && asyncByDefault) || config.forceTopLevelAsync === true)) {\n\t\t\t\tpendingAsyncLaunches.add(launchReadyPromise);\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst currentGeneration = generation;\n\t\t\t\tsignal?.throwIfAborted();\n\t\t\t\tif (!executorPromise) {\n\t\t\t\t\tconst pending = import(\"../runs/foreground/subagent-executor.ts\").then(({ createSubagentExecutor }) => {\n\t\t\t\t\t\tif (generation !== currentGeneration) throw new Error(\"Subagent session changed before initialization\");\n\t\t\t\t\t\treturn createSubagentExecutor({\n\t\t\t\t\t\t\tpi, state, config, asyncByDefault,\n\t\t\t\t\t\t\twaitToolEnabled: waitToolConfig.enabled,\n\t\t\t\t\t\t\thandleScheduledRunAction: (params, ctx) => scheduledRunManager.handleToolCall(params, ctx),\n\t\t\t\t\t\t\twatchdog: mainWatchdog, tempArtifactsDir, getSubagentSessionRoot, expandTilde,\n\t\t\t\t\t\t});\n\t\t\t\t\t});\n\t\t\t\t\texecutorPromise = pending;\n\t\t\t\t\tvoid pending.catch(() => {\n\t\t\t\t\t\tif (executorPromise === pending) executorPromise = undefined;\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tconst ready = await awaitWithAbort(executorPromise, signal);\n\t\t\t\tsignal?.throwIfAborted();\n\t\t\t\tif (generation !== currentGeneration) throw new Error(\"Subagent session changed before execution\");\n\t\t\t\tconst execution = ready.execute(id, params, signal, onUpdate, ctx);\n\t\t\t\tmarkLaunchReady();\n\t\t\t\treturn await execution;\n\t\t\t} finally {\n\t\t\t\tmarkLaunchReady();\n\t\t\t}\n\t\t},\n\t};\n\texecutorExecute = executor.execute;\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 = typeof message.content === \"string\"\n\t\t\t? message.content\n\t\t\t: message.content\n\t\t\t\t.filter((entry) => entry.type === \"text\")\n\t\t\t\t.map((entry) => entry.text)\n\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\treturn renderSubagentNotify(details, theme);\n\t});\n\n\tpi.registerMessageRenderer<SubagentSteeringMessageDetails>(SUBAGENT_STEERING_MESSAGE_TYPE, (message, _options, theme) => {\n\t\tconst details = message.details as SubagentSteeringMessageDetails | undefined;\n\t\tif (!details) return undefined;\n\t\treturn new Text(theme.fg(details.state === \"recovered\" ? \"warning\" : \"error\", formatSteeringNotice(details)), 0, 0);\n\t});\n\n\tpi.registerMessageRenderer<SubagentControlMessageDetails>(SUBAGENT_CONTROL_MESSAGE_TYPE, () => undefined);\n\n\tconst executeSubagentCollapsed = (id: string, params: SubagentParamsLike, signal: AbortSignal, onUpdate: ((result: AgentToolResult<Details>) => void) | undefined, ctx: ExtensionContext) => {\n\t\tif (ctx.hasUI) ctx.ui.setToolsExpanded(false);\n\t\treturn executor.execute(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) =>\n\t\t\texecuteSubagentCollapsed(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});\n\n\tconst rpcBridge = registerSubagentRpcBridge({\n\t\tevents: pi.events,\n\t\tgetContext: () => state.lastUiContext,\n\t\texecute: (id, params, signal, onUpdate, ctx) => executor.execute(id, params, signal, onUpdate, ctx),\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\t\t// lunr: same-turn SINGLE calls overlap; sequential work uses chain.\n\t\texecutionMode: \"parallel\",\n\n\t\tprepareArguments(args) {\n\t\t\t// Run friendly chain validation before pi-ai's raw TypeBox schema check\n\t\t\t// so the model sees which property is disallowed, what is allowed, and a\n\t\t\t// valid example instead of `chain.N: must not have additional properties`.\n\t\t\tvalidateChainInput(args);\n\t\t\treturn args as never;\n\t\t},\n\n\t\texecute(id, params, signal, onUpdate, ctx) {\n\t\t\treturn executeSubagentCollapsed(id, params, signal, onUpdate, ctx);\n\t\t},\n\n\t\trenderCall: (args, theme, context) => renderSubagentCall(args, theme, context, { asyncByDefault }),\n\n\t\trenderResult(result, options, theme, context) {\n\t\t\tif (subagentResultIsRunning(result)) {\n\t\t\t\tensureSubagentResultAnimation(context);\n\t\t\t} else {\n\t\t\t\tclearLegacyResultAnimationTimer(context);\n\t\t\t}\n\t\t\tconst state = context.state as { frame?: number; animEntries?: Array<{ text: Text; line: (frame: number) => string }> };\n\t\t\t// lunr: collect running-glyph line templates while rendering so the\n\t\t\t// animation timer can rewrite them in place (see ensureSubagentResultAnimation).\n\t\t\tconst entries: Array<{ text: Text; line: (frame: number) => string }> = [];\n\t\t\tstate.animEntries = entries;\n\t\t\tsubagentAnimSink.current = entries;\n\t\t\ttry {\n\t\t\t\treturn renderSubagentResult(result, options, theme, state.frame ?? 0);\n\t\t\t} finally {\n\t\t\t\tsubagentAnimSink.current = null;\n\t\t\t}\n\t\t},\n\n\t};\n\n\tpi.registerTool(tool);\n\n\t// lunr: register a callback so model-tier settings changes rebuild the subagent\n\t// tool description and refresh the live registry.\n\tconst modelTierBridge = (globalThis as Record<symbol, unknown>)[Symbol.for(\"@lunr/model-tiers\")] as\n\t\t| { registerToolDescriptionRefresher?: (refresher: () => void) => void }\n\t\t| undefined;\n\tmodelTierBridge?.registerToolDescriptionRefresher?.(() => {\n\t\ttool.description = buildSubagentToolDescription(config);\n\t\t// pi.runtime is not on ExtensionAPI; registerTool refreshes the live registry.\n\t\tpi.registerTool(tool);\n\t});\n\n\tregisterWaitTool(pi, state, waitToolConfig.enabled, async (signal) => {\n\t\tconst launches = [...pendingLaunches];\n\t\tif (launches.length === 0) return;\n\t\tawait awaitWithAbort(Promise.all(launches).then(() => {}), signal);\n\t});\n\n\tpi.on(\"agent_end\", async (_event, ctx) => {\n\t\tif (ctx.hasUI) return;\n\t\tawait drainOutstandingWork({ state, events: pi.events });\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\tregisterSubagentNotify(pi, state, { batchConfig: config.completionBatch });\n\n\tconst existingVisibleControlNotices = globalStore[controlNoticeSeenStoreKey];\n\tconst visibleControlNotices = existingVisibleControlNotices instanceof Set ? existingVisibleControlNotices as Set<string> : new Set<string>();\n\tglobalStore[controlNoticeSeenStoreKey] = visibleControlNotices;\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 eventUnsubscribes = [\n\t\tpi.events.on(SUBAGENT_ASYNC_STARTED_EVENT, handleStarted),\n\t\tpi.events.on(SUBAGENT_ASYNC_COMPLETE_EVENT, handleComplete),\n\t\tpi.events.on(SUBAGENT_CONTROL_EVENT, controlEventHandler),\n\t\tpi.events.on(SUBAGENT_STEERING_NOTICE_EVENT, steeringNoticeHandler),\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\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) => {\n\t\tgeneration++;\n\t\tsupervisorChannel.cancelOwnedQuestions(\"parent session replaced\");\n\t\tstate.sessionGeneration = (state.sessionGeneration ?? 0) + 1;\n\t\texecutorPromise = undefined;\n\t\tpendingLaunches.clear();\n\t\tpendingAsyncLaunches.clear();\n\t\tunregisterCancellation?.();\n\t\tstate.baseCwd = ctx.cwd;\n\t\tstate.currentSessionId = resolveCurrentSessionId(ctx.sessionManager);\n\t\tstate.subagentSpawns = { sessionId: state.currentSessionId, count: 0 };\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\tclearPendingForegroundControlNotices(state);\n\t\tresetJobs(ctx);\n\t\trestoreActiveJobs(ctx);\n\t\tconst sessionId = state.currentSessionId;\n\t\tconst sessionGeneration = generation;\n\t\tif (sessionId) unregisterCancellation = registerSubagentCancellation(sessionId, createSubagentCancellation({\n\t\t\tpendingLaunches: pendingAsyncLaunches,\n\t\t\tisCurrent: () => generation === sessionGeneration && state.currentSessionId === sessionId,\n\t\t\tgetActiveRunIds: () => [...state.asyncJobs.values()]\n\t\t\t\t.filter((job) => job.sessionId === sessionId && (job.status === \"queued\" || job.status === \"running\"))\n\t\t\t\t.map((job) => job.asyncId),\n\t\t\tasync stopRun(id) {\n\t\t\t\tconst result = await executor.execute(randomUUID(), { action: \"stop\", id }, new AbortController().signal, undefined, ctx);\n\t\t\t\treturn !result.isError;\n\t\t\t},\n\t\t}));\n\t\tscheduledRunManager.bindSession(ctx);\n\t\trestoreSlashFinalSnapshots(ctx.sessionManager.getEntries());\n\t\tprimeExistingResults();\n\t};\n\n\tpi.on(\"session_start\", (_event, ctx) => {\n\t\tresetSessionState(ctx);\n\t\trpcBridge.emitReady(ctx);\n\t\tsupervisorChannel.start();\n\t});\n\n\tpi.on(\"session_shutdown\", () => {\n\t\tgeneration++;\n\t\tsupervisorChannel.cancelOwnedQuestions(\"parent session shutdown\");\n\t\tstate.sessionGeneration = (state.sessionGeneration ?? 0) + 1;\n\t\texecutorPromise = undefined;\n\t\tpendingLaunches.clear();\n\t\tpendingAsyncLaunches.clear();\n\t\tunregisterCancellation?.();\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\tstopResultWatcher();\n\t\tscheduledRunManager.stop();\n\t\tif (state.poller) clearInterval(state.poller);\n\t\tstate.poller = null;\n\t\tclearPendingForegroundControlNotices(state);\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\tif (globalStore[runtimeCleanupStoreKey] === runtimeCleanup) {\n\t\t\tdelete globalStore[runtimeCleanupStoreKey];\n\t\t}\n\t\tdisposeSubagentWidget();\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});\n}\n"]}
|
|
@@ -13,16 +13,16 @@
|
|
|
13
13
|
* { "asyncByDefault": true, "forceTopLevelAsync": true, "maxSubagentDepth": 1, "intercomBridge": { "mode": "always", "instructionFile": "./intercom-bridge.md" }, "worktreeSetupHook": "./scripts/setup-worktree.mjs" }
|
|
14
14
|
*/
|
|
15
15
|
import { randomUUID } from "node:crypto";
|
|
16
|
+
import { createSubagentCancellation, registerSubagentCancellation } from "../../../../core/subagent-cancellation.js";
|
|
16
17
|
import * as fs from "node:fs";
|
|
17
18
|
import * as os from "node:os";
|
|
18
19
|
import * as path from "node:path";
|
|
19
|
-
import { keyText } from "../../../../modes/interactive/components/keybinding-hints.js";
|
|
20
20
|
import { awaitWithAbort } from "../../../../utils/await-with-abort.js";
|
|
21
21
|
import { Box, Container, Spacer, Text, truncateToWidth, visibleWidth, wrapTextWithAnsi } from "@ashx-j/lunr-tui";
|
|
22
22
|
import { cleanupAllArtifactDirs, cleanupOldArtifacts, getArtifactsDir } from "../shared/artifacts.js";
|
|
23
23
|
import { resolveCurrentSessionId } from "../shared/session-identity.js";
|
|
24
24
|
import { cleanupOldChainDirs } from "../shared/settings.js";
|
|
25
|
-
import { clearLegacyResultAnimationTimer, renderSubagentResult, subagentAnimSink } from "../tui/render.js";
|
|
25
|
+
import { clearLegacyResultAnimationTimer, disposeSubagentWidget, renderSubagentResult, subagentAnimSink } from "../tui/render.js";
|
|
26
26
|
import { SubagentParams } from "./schemas.js";
|
|
27
27
|
import { validateChainInput } from "./chain-validation.js";
|
|
28
28
|
import { createAsyncJobTracker } from "../runs/background/async-job-tracker.js";
|
|
@@ -41,7 +41,6 @@ import { drainOutstandingWork } from "../runs/background/auto-drain.js";
|
|
|
41
41
|
import registerSubagentNotify, { parseSubagentNotifyContent } from "../runs/background/notify.js";
|
|
42
42
|
import { formatSteeringNotice, handleSubagentSteeringNotice, SUBAGENT_STEERING_MESSAGE_TYPE } from "./steering-notices.js";
|
|
43
43
|
import { SUBAGENT_CHILD_ENV, SUBAGENT_PARENT_SESSION_ENV } from "../runs/shared/pi-args.js";
|
|
44
|
-
import { formatDuration, shortenPath } from "../shared/formatters.js";
|
|
45
44
|
import { loadConfig } from "./config.js";
|
|
46
45
|
import { buildSubagentToolDescription } from "./tool-description.js";
|
|
47
46
|
import { ASYNC_DIR, DEFAULT_ARTIFACT_CONFIG, RESULTS_DIR, SLASH_RESULT_TYPE, SLASH_TEXT_RESULT_TYPE, SUBAGENT_ASYNC_COMPLETE_EVENT, SUBAGENT_ASYNC_STARTED_EVENT, SUBAGENT_CONTROL_EVENT, SUBAGENT_STEERING_NOTICE_EVENT, WIDGET_KEY, } from "../shared/types.js";
|
|
@@ -114,23 +113,44 @@ function effectiveParallelTaskCount(tasks) {
|
|
|
114
113
|
return total + count;
|
|
115
114
|
}, 0);
|
|
116
115
|
}
|
|
117
|
-
|
|
116
|
+
function callIsAsync(args, asyncByDefault) {
|
|
117
|
+
if (args?.clarify === true)
|
|
118
|
+
return false;
|
|
119
|
+
if (args?.async === true)
|
|
120
|
+
return true;
|
|
121
|
+
if (args?.async === false)
|
|
122
|
+
return false;
|
|
123
|
+
return asyncByDefault === true;
|
|
124
|
+
}
|
|
125
|
+
export function renderSubagentCall(args, theme, context, options) {
|
|
118
126
|
if (args.action) {
|
|
119
127
|
const target = args.id || args.runId || "";
|
|
120
128
|
return new Text(`${theme.fg("toolTitle", theme.bold("subagent "))}${args.action}${target ? ` ${theme.fg("accent", target)}` : ""}`, 0, 0);
|
|
121
129
|
}
|
|
122
130
|
const isParallel = (args.tasks?.length ?? 0) > 0;
|
|
123
131
|
const parallelCount = effectiveParallelTaskCount(args.tasks);
|
|
124
|
-
const
|
|
132
|
+
const asyncMark = callIsAsync(args, options?.asyncByDefault) ? `${theme.fg("warning", "async")} ` : "";
|
|
125
133
|
if (args.chain?.length) {
|
|
126
|
-
return new Text(`${theme.fg("toolTitle", theme.bold("subagent "))}chain (${args.chain.length})
|
|
134
|
+
return new Text(`${theme.fg("toolTitle", theme.bold("subagent "))}${asyncMark}chain (${args.chain.length})`, 0, 0);
|
|
127
135
|
}
|
|
128
136
|
if (isParallel) {
|
|
129
|
-
return new Text(`${theme.fg("toolTitle", theme.bold("subagent "))}parallel (${parallelCount})
|
|
137
|
+
return new Text(`${theme.fg("toolTitle", theme.bold("subagent "))}${asyncMark}parallel (${parallelCount})`, 0, 0);
|
|
130
138
|
}
|
|
131
|
-
const showDescription = !context?.isPartial && !context?.expanded && !context?.isError;
|
|
139
|
+
const showDescription = !context?.result && !context?.isPartial && !context?.expanded && !context?.isError;
|
|
132
140
|
const description = showDescription ? theme.fg("accent", args.description || args.task || "?") : "";
|
|
133
|
-
|
|
141
|
+
const title = theme.fg("toolTitle", theme.bold("subagent"));
|
|
142
|
+
const rest = [asyncMark.trim() ? theme.fg("warning", "async") : "", description].filter(Boolean).join(" ");
|
|
143
|
+
return new Text(rest ? `${title} ${rest}` : title, 0, 0);
|
|
144
|
+
}
|
|
145
|
+
export function renderSubagentNotify(details, theme) {
|
|
146
|
+
const status = typeof details?.status === "string" ? details.status : "";
|
|
147
|
+
const icon = status === "completed"
|
|
148
|
+
? theme.fg("success", "✓")
|
|
149
|
+
: status === "failed"
|
|
150
|
+
? theme.fg("error", "✗")
|
|
151
|
+
: theme.fg("warning", "■");
|
|
152
|
+
const title = details?.agent || "child";
|
|
153
|
+
return new Text(`${icon} ${theme.bold(title)} ${theme.fg("dim", status)}`, 0, 0);
|
|
134
154
|
}
|
|
135
155
|
function ensureSubagentResultAnimation(context) {
|
|
136
156
|
const state = context.state;
|
|
@@ -237,6 +257,7 @@ export default function registerSubagentExtension(pi) {
|
|
|
237
257
|
const state = {
|
|
238
258
|
baseCwd: "",
|
|
239
259
|
currentSessionId: null,
|
|
260
|
+
sessionGeneration: 0,
|
|
240
261
|
foregroundSubagentInFlight: 0,
|
|
241
262
|
subagentSpawns: { sessionId: null, count: 0 },
|
|
242
263
|
asyncJobs: new Map(),
|
|
@@ -264,24 +285,40 @@ export default function registerSubagentExtension(pi) {
|
|
|
264
285
|
let generation = 0;
|
|
265
286
|
let executorPromise;
|
|
266
287
|
const pendingLaunches = new Set();
|
|
288
|
+
const pendingAsyncLaunches = new Set();
|
|
289
|
+
let unregisterCancellation;
|
|
267
290
|
const runtimeCleanup = () => {
|
|
268
291
|
generation++;
|
|
292
|
+
supervisorChannel.cancelOwnedQuestions("parent runtime replaced");
|
|
293
|
+
state.sessionGeneration = (state.sessionGeneration ?? 0) + 1;
|
|
269
294
|
executorPromise = undefined;
|
|
270
295
|
pendingLaunches.clear();
|
|
296
|
+
pendingAsyncLaunches.clear();
|
|
297
|
+
unregisterCancellation?.();
|
|
271
298
|
mainWatchdog.dispose();
|
|
272
299
|
stopResultWatcher();
|
|
273
300
|
scheduledRunManager.stop();
|
|
274
301
|
supervisorChannel.dispose();
|
|
275
302
|
clearPendingForegroundControlNotices(state);
|
|
303
|
+
disposeSubagentWidget();
|
|
276
304
|
if (state.poller) {
|
|
277
305
|
clearInterval(state.poller);
|
|
278
306
|
state.poller = null;
|
|
279
307
|
}
|
|
280
308
|
};
|
|
281
309
|
globalStore[runtimeCleanupStoreKey] = runtimeCleanup;
|
|
282
|
-
const { ensurePoller, refreshWidget, handleStarted, handleComplete, resetJobs, restoreActiveJobs } = createAsyncJobTracker(pi, state, ASYNC_DIR, {
|
|
310
|
+
const { ensurePoller, refreshWidget, handleStarted, handleComplete: trackComplete, resetJobs, restoreActiveJobs } = createAsyncJobTracker(pi, state, ASYNC_DIR, {
|
|
283
311
|
widgetEnabled: config.asyncWidget !== false,
|
|
284
312
|
});
|
|
313
|
+
const handleComplete = (data) => {
|
|
314
|
+
trackComplete(data);
|
|
315
|
+
const result = data;
|
|
316
|
+
if (typeof state.currentSessionId === "string" && result.sessionId !== state.currentSessionId)
|
|
317
|
+
return;
|
|
318
|
+
if (typeof result.id === "string" && result.id.trim()) {
|
|
319
|
+
supervisorChannel.settleRunQuestions(result.id, "async run settled");
|
|
320
|
+
}
|
|
321
|
+
};
|
|
285
322
|
let executorExecute;
|
|
286
323
|
const scheduledRunManager = createScheduledRunManager({
|
|
287
324
|
config,
|
|
@@ -308,9 +345,14 @@ export default function registerSubagentExtension(pi) {
|
|
|
308
345
|
return;
|
|
309
346
|
launchReady = true;
|
|
310
347
|
pendingLaunches.delete(launchReadyPromise);
|
|
348
|
+
pendingAsyncLaunches.delete(launchReadyPromise);
|
|
311
349
|
resolveLaunchReady();
|
|
312
350
|
};
|
|
313
351
|
pendingLaunches.add(launchReadyPromise);
|
|
352
|
+
if ((params.task || params.tasks || params.chain) && !params.clarify &&
|
|
353
|
+
(params.async === true || (params.async === undefined && asyncByDefault) || config.forceTopLevelAsync === true)) {
|
|
354
|
+
pendingAsyncLaunches.add(launchReadyPromise);
|
|
355
|
+
}
|
|
314
356
|
try {
|
|
315
357
|
const currentGeneration = generation;
|
|
316
358
|
signal?.throwIfAborted();
|
|
@@ -360,40 +402,12 @@ export default function registerSubagentExtension(pi) {
|
|
|
360
402
|
.join("\n");
|
|
361
403
|
return new Text(content, 0, 0);
|
|
362
404
|
});
|
|
363
|
-
pi.registerMessageRenderer("subagent-notify", (message,
|
|
405
|
+
pi.registerMessageRenderer("subagent-notify", (message, _options, theme) => {
|
|
364
406
|
const content = typeof message.content === "string" ? message.content : "";
|
|
365
407
|
const details = message.details ?? parseSubagentNotifyContent(content);
|
|
366
408
|
if (!details)
|
|
367
409
|
return new Text(content, 0, 0);
|
|
368
|
-
|
|
369
|
-
? theme.fg("success", "✓")
|
|
370
|
-
: details.status === "paused"
|
|
371
|
-
? theme.fg("warning", "■")
|
|
372
|
-
: theme.fg("error", "✗");
|
|
373
|
-
const parts = [];
|
|
374
|
-
if (details.taskInfo)
|
|
375
|
-
parts.push(details.taskInfo);
|
|
376
|
-
if (details.durationMs !== undefined)
|
|
377
|
-
parts.push(formatDuration(details.durationMs));
|
|
378
|
-
const notifyLabel = details.taskInfo || details.agent || "child";
|
|
379
|
-
let text = `${icon} ${theme.bold(notifyLabel)} ${theme.fg("dim", details.status)}`;
|
|
380
|
-
if (parts.length > 0)
|
|
381
|
-
text += ` ${theme.fg("dim", "·")} ${parts.map((part) => theme.fg("dim", part)).join(` ${theme.fg("dim", "·")} `)}`;
|
|
382
|
-
const trimmedPreview = details.resultPreview.trim();
|
|
383
|
-
const previewLines = options.expanded
|
|
384
|
-
? trimmedPreview.split("\n").filter((line) => line.trim())
|
|
385
|
-
: [trimmedPreview.split("\n", 1)[0] ?? ""].filter((line) => line.trim());
|
|
386
|
-
for (const line of previewLines.length > 0 ? previewLines : ["(no output)"]) {
|
|
387
|
-
text += `\n ${theme.fg("dim", `⎿ ${line}`)}`;
|
|
388
|
-
}
|
|
389
|
-
if (!options.expanded && trimmedPreview.includes("\n")) {
|
|
390
|
-
const expandKey = keyText("app.tools.expand");
|
|
391
|
-
text += `\n ${theme.fg("dim", `${expandKey} full notification`)}`;
|
|
392
|
-
}
|
|
393
|
-
if (details.sessionLabel && details.sessionValue) {
|
|
394
|
-
text += `\n ${theme.fg("muted", `${details.sessionLabel}: ${shortenPath(details.sessionValue)}`)}`;
|
|
395
|
-
}
|
|
396
|
-
return new Text(text, 0, 0);
|
|
410
|
+
return renderSubagentNotify(details, theme);
|
|
397
411
|
});
|
|
398
412
|
pi.registerMessageRenderer(SUBAGENT_STEERING_MESSAGE_TYPE, (message, _options, theme) => {
|
|
399
413
|
const details = message.details;
|
|
@@ -439,7 +453,7 @@ export default function registerSubagentExtension(pi) {
|
|
|
439
453
|
execute(id, params, signal, onUpdate, ctx) {
|
|
440
454
|
return executeSubagentCollapsed(id, params, signal, onUpdate, ctx);
|
|
441
455
|
},
|
|
442
|
-
renderCall: renderSubagentCall,
|
|
456
|
+
renderCall: (args, theme, context) => renderSubagentCall(args, theme, context, { asyncByDefault }),
|
|
443
457
|
renderResult(result, options, theme, context) {
|
|
444
458
|
if (subagentResultIsRunning(result)) {
|
|
445
459
|
ensureSubagentResultAnimation(context);
|
|
@@ -462,8 +476,8 @@ export default function registerSubagentExtension(pi) {
|
|
|
462
476
|
},
|
|
463
477
|
};
|
|
464
478
|
pi.registerTool(tool);
|
|
465
|
-
// lunr: register a callback so
|
|
466
|
-
// tool description
|
|
479
|
+
// lunr: register a callback so model-tier settings changes rebuild the subagent
|
|
480
|
+
// tool description and refresh the live registry.
|
|
467
481
|
const modelTierBridge = globalThis[Symbol.for("@lunr/model-tiers")];
|
|
468
482
|
modelTierBridge?.registerToolDescriptionRefresher?.(() => {
|
|
469
483
|
tool.description = buildSubagentToolDescription(config);
|
|
@@ -544,8 +558,12 @@ export default function registerSubagentExtension(pi) {
|
|
|
544
558
|
};
|
|
545
559
|
const resetSessionState = (ctx) => {
|
|
546
560
|
generation++;
|
|
561
|
+
supervisorChannel.cancelOwnedQuestions("parent session replaced");
|
|
562
|
+
state.sessionGeneration = (state.sessionGeneration ?? 0) + 1;
|
|
547
563
|
executorPromise = undefined;
|
|
548
564
|
pendingLaunches.clear();
|
|
565
|
+
pendingAsyncLaunches.clear();
|
|
566
|
+
unregisterCancellation?.();
|
|
549
567
|
state.baseCwd = ctx.cwd;
|
|
550
568
|
state.currentSessionId = resolveCurrentSessionId(ctx.sessionManager);
|
|
551
569
|
state.subagentSpawns = { sessionId: state.currentSessionId, count: 0 };
|
|
@@ -565,6 +583,20 @@ export default function registerSubagentExtension(pi) {
|
|
|
565
583
|
clearPendingForegroundControlNotices(state);
|
|
566
584
|
resetJobs(ctx);
|
|
567
585
|
restoreActiveJobs(ctx);
|
|
586
|
+
const sessionId = state.currentSessionId;
|
|
587
|
+
const sessionGeneration = generation;
|
|
588
|
+
if (sessionId)
|
|
589
|
+
unregisterCancellation = registerSubagentCancellation(sessionId, createSubagentCancellation({
|
|
590
|
+
pendingLaunches: pendingAsyncLaunches,
|
|
591
|
+
isCurrent: () => generation === sessionGeneration && state.currentSessionId === sessionId,
|
|
592
|
+
getActiveRunIds: () => [...state.asyncJobs.values()]
|
|
593
|
+
.filter((job) => job.sessionId === sessionId && (job.status === "queued" || job.status === "running"))
|
|
594
|
+
.map((job) => job.asyncId),
|
|
595
|
+
async stopRun(id) {
|
|
596
|
+
const result = await executor.execute(randomUUID(), { action: "stop", id }, new AbortController().signal, undefined, ctx);
|
|
597
|
+
return !result.isError;
|
|
598
|
+
},
|
|
599
|
+
}));
|
|
568
600
|
scheduledRunManager.bindSession(ctx);
|
|
569
601
|
restoreSlashFinalSnapshots(ctx.sessionManager.getEntries());
|
|
570
602
|
primeExistingResults();
|
|
@@ -576,8 +608,12 @@ export default function registerSubagentExtension(pi) {
|
|
|
576
608
|
});
|
|
577
609
|
pi.on("session_shutdown", () => {
|
|
578
610
|
generation++;
|
|
611
|
+
supervisorChannel.cancelOwnedQuestions("parent session shutdown");
|
|
612
|
+
state.sessionGeneration = (state.sessionGeneration ?? 0) + 1;
|
|
579
613
|
executorPromise = undefined;
|
|
580
614
|
pendingLaunches.clear();
|
|
615
|
+
pendingAsyncLaunches.clear();
|
|
616
|
+
unregisterCancellation?.();
|
|
581
617
|
delete process.env[SUBAGENT_PARENT_SESSION_ENV];
|
|
582
618
|
for (const unsubscribe of eventUnsubscribes) {
|
|
583
619
|
try {
|
|
@@ -610,6 +646,7 @@ export default function registerSubagentExtension(pi) {
|
|
|
610
646
|
if (globalStore[runtimeCleanupStoreKey] === runtimeCleanup) {
|
|
611
647
|
delete globalStore[runtimeCleanupStoreKey];
|
|
612
648
|
}
|
|
649
|
+
disposeSubagentWidget();
|
|
613
650
|
try {
|
|
614
651
|
if (state.lastUiContext?.hasUI) {
|
|
615
652
|
state.lastUiContext.ui.setWidget(WIDGET_KEY, undefined);
|