@ashx-j/lunr 0.2.8 → 0.2.9

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.
Files changed (127) hide show
  1. package/dist/builtin-extensions/ashxj-spinners.d.ts +7 -0
  2. package/dist/builtin-extensions/ashxj-spinners.d.ts.map +1 -1
  3. package/dist/builtin-extensions/ashxj-spinners.js +40 -3
  4. package/dist/builtin-extensions/ashxj-spinners.js.map +1 -1
  5. package/dist/builtin-extensions/ashxj-tui.d.ts +4 -0
  6. package/dist/builtin-extensions/ashxj-tui.d.ts.map +1 -1
  7. package/dist/builtin-extensions/ashxj-tui.js +44 -0
  8. package/dist/builtin-extensions/ashxj-tui.js.map +1 -1
  9. package/dist/builtin-extensions/lunr-behavior.d.ts +2 -1
  10. package/dist/builtin-extensions/lunr-behavior.d.ts.map +1 -1
  11. package/dist/builtin-extensions/lunr-behavior.js +26 -14
  12. package/dist/builtin-extensions/lunr-behavior.js.map +1 -1
  13. package/dist/builtin-extensions/lunr-todos.d.ts +4 -7
  14. package/dist/builtin-extensions/lunr-todos.d.ts.map +1 -1
  15. package/dist/builtin-extensions/lunr-todos.js +18 -10
  16. package/dist/builtin-extensions/lunr-todos.js.map +1 -1
  17. package/dist/builtin-extensions/pi-subagents/src/agents/agent-management.d.ts.map +1 -1
  18. package/dist/builtin-extensions/pi-subagents/src/agents/agent-management.js +3 -3
  19. package/dist/builtin-extensions/pi-subagents/src/agents/agent-management.js.map +1 -1
  20. package/dist/builtin-extensions/pi-subagents/src/extension/schemas.d.ts.map +1 -1
  21. package/dist/builtin-extensions/pi-subagents/src/extension/schemas.js +2 -2
  22. package/dist/builtin-extensions/pi-subagents/src/extension/schemas.js.map +1 -1
  23. package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.d.ts +2 -2
  24. package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.d.ts.map +1 -1
  25. package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.js +3 -4
  26. package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.js.map +1 -1
  27. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.d.ts.map +1 -1
  28. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.js +6 -18
  29. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.js.map +1 -1
  30. package/dist/builtin-extensions/pi-subagents/src/runs/shared/model-fallback.d.ts.map +1 -1
  31. package/dist/builtin-extensions/pi-subagents/src/runs/shared/model-fallback.js +13 -5
  32. package/dist/builtin-extensions/pi-subagents/src/runs/shared/model-fallback.js.map +1 -1
  33. package/dist/builtin-extensions/pi-subagents/src/shared/lunr-child-context.d.ts +21 -0
  34. package/dist/builtin-extensions/pi-subagents/src/shared/lunr-child-context.d.ts.map +1 -0
  35. package/dist/builtin-extensions/pi-subagents/src/shared/lunr-child-context.js +22 -0
  36. package/dist/builtin-extensions/pi-subagents/src/shared/lunr-child-context.js.map +1 -0
  37. package/dist/builtin-extensions/pi-subagents/src/slash/prompt-workflows.d.ts.map +1 -1
  38. package/dist/builtin-extensions/pi-subagents/src/slash/prompt-workflows.js +2 -1
  39. package/dist/builtin-extensions/pi-subagents/src/slash/prompt-workflows.js.map +1 -1
  40. package/dist/builtin-extensions/pi-subagents/src/slash/slash-commands.d.ts.map +1 -1
  41. package/dist/builtin-extensions/pi-subagents/src/slash/slash-commands.js +10 -18
  42. package/dist/builtin-extensions/pi-subagents/src/slash/slash-commands.js.map +1 -1
  43. package/dist/builtin-extensions/pi-web-access/render-search-error.d.ts.map +1 -1
  44. package/dist/builtin-extensions/pi-web-access/render-search-error.js +1 -1
  45. package/dist/builtin-extensions/pi-web-access/render-search-error.js.map +1 -1
  46. package/dist/catalog/models.json +1 -1
  47. package/dist/catalog/providers/openrouter.json +1 -1
  48. package/dist/catalog/providers/vercel-ai-gateway.json +1 -1
  49. package/dist/catalog/publication.json +3 -3
  50. package/dist/cli/update-cli.d.ts +16 -0
  51. package/dist/cli/update-cli.d.ts.map +1 -0
  52. package/dist/cli/update-cli.js +83 -0
  53. package/dist/cli/update-cli.js.map +1 -0
  54. package/dist/cli.d.ts.map +1 -1
  55. package/dist/cli.js +6 -0
  56. package/dist/cli.js.map +1 -1
  57. package/dist/core/behavior-preset.d.ts +60 -0
  58. package/dist/core/behavior-preset.d.ts.map +1 -0
  59. package/dist/core/behavior-preset.js +257 -0
  60. package/dist/core/behavior-preset.js.map +1 -0
  61. package/dist/core/customize.d.ts +3 -0
  62. package/dist/core/customize.d.ts.map +1 -1
  63. package/dist/core/customize.js +9 -0
  64. package/dist/core/customize.js.map +1 -1
  65. package/dist/core/footer-data-provider.d.ts +12 -1
  66. package/dist/core/footer-data-provider.d.ts.map +1 -1
  67. package/dist/core/footer-data-provider.js +70 -5
  68. package/dist/core/footer-data-provider.js.map +1 -1
  69. package/dist/core/keybindings.d.ts +2 -2
  70. package/dist/core/keybindings.d.ts.map +1 -1
  71. package/dist/core/keybindings.js +1 -1
  72. package/dist/core/keybindings.js.map +1 -1
  73. package/dist/core/model-tiers.d.ts +9 -0
  74. package/dist/core/model-tiers.d.ts.map +1 -1
  75. package/dist/core/model-tiers.js +16 -0
  76. package/dist/core/model-tiers.js.map +1 -1
  77. package/dist/core/settings-manager.d.ts +23 -0
  78. package/dist/core/settings-manager.d.ts.map +1 -1
  79. package/dist/core/settings-manager.js +66 -0
  80. package/dist/core/settings-manager.js.map +1 -1
  81. package/dist/core/tools/bash.d.ts.map +1 -1
  82. package/dist/core/tools/bash.js +1 -3
  83. package/dist/core/tools/bash.js.map +1 -1
  84. package/dist/core/update-check.d.ts +32 -0
  85. package/dist/core/update-check.d.ts.map +1 -0
  86. package/dist/core/update-check.js +112 -0
  87. package/dist/core/update-check.js.map +1 -0
  88. package/dist/core/usage-service.d.ts +24 -1
  89. package/dist/core/usage-service.d.ts.map +1 -1
  90. package/dist/core/usage-service.js +91 -14
  91. package/dist/core/usage-service.js.map +1 -1
  92. package/dist/gateway/agent-bridge.d.ts.map +1 -1
  93. package/dist/gateway/agent-bridge.js +5 -1
  94. package/dist/gateway/agent-bridge.js.map +1 -1
  95. package/dist/gateway/cron.d.ts.map +1 -1
  96. package/dist/gateway/cron.js +5 -1
  97. package/dist/gateway/cron.js.map +1 -1
  98. package/dist/main.d.ts.map +1 -1
  99. package/dist/main.js +13 -1
  100. package/dist/main.js.map +1 -1
  101. package/dist/modes/interactive/components/assistant-message.d.ts +4 -0
  102. package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  103. package/dist/modes/interactive/components/assistant-message.js +68 -10
  104. package/dist/modes/interactive/components/assistant-message.js.map +1 -1
  105. package/dist/modes/interactive/components/settings-selector.d.ts +13 -0
  106. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  107. package/dist/modes/interactive/components/settings-selector.js +157 -10
  108. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  109. package/dist/modes/interactive/components/tool-execution.d.ts +1 -0
  110. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  111. package/dist/modes/interactive/components/tool-execution.js +9 -0
  112. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  113. package/dist/modes/interactive/interactive-mode.d.ts +4 -1
  114. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  115. package/dist/modes/interactive/interactive-mode.js +82 -3
  116. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  117. package/docs/keybindings.md +1 -1
  118. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  119. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  120. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  121. package/examples/extensions/gondolin/package-lock.json +2 -2
  122. package/examples/extensions/gondolin/package.json +1 -1
  123. package/examples/extensions/sandbox/package-lock.json +2 -2
  124. package/examples/extensions/sandbox/package.json +1 -1
  125. package/examples/extensions/with-deps/package-lock.json +2 -2
  126. package/examples/extensions/with-deps/package.json +1 -1
  127. package/package.json +4 -4
@@ -1 +1 @@
1
- {"version":3,"file":"slash-commands.d.ts","sourceRoot":"","sources":["../../../../../src/builtin-extensions/pi-subagents/src/slash/slash-commands.ts"],"names":[],"mappings":"AAIA,OAAO,EAAW,KAAK,YAAY,EAAE,KAAK,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AAapG,OAAO,EAAyC,KAAK,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAkB9F,OAAO,EAYN,KAAK,aAAa,EAElB,MAAM,oBAAoB,CAAC;AAE5B,UAAU,YAAY;IACrB,MAAM,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;IACxB,UAAU,CAAC,EAAE,QAAQ,GAAG,WAAW,CAAC;IACpC,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,KAAK,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,KAAK,CAAC;IACzB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;CACpB;AAmqBD,MAAM,WAAW,WAAW;IAAG,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAE;AAC7F,MAAM,WAAW,UAAU;IAAG,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,YAAY,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE;AAC/F,MAAM,WAAW,WAAW;IAAG,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,UAAU,EAAE,CAAC;IAAC,MAAM,EAAE,WAAW,CAAA;CAAE;AACxF,MAAM,MAAM,eAAe,GAAG,UAAU,GAAG,WAAW,CAAC;AAEvD,eAAO,MAAM,oBAAoB,kFACyC,CAAC;AAE3E,qBAAa,eAAgB,SAAQ,KAAK;CAAG;AA4D7C,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,CAiB9D;AA0CD,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,WAAW,CAW9D;AAOD,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAErD;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,eAAe,EAAE,CAAA;CAAE,CAyBhF;AAyID,wBAAgB,yBAAyB,CACxC,KAAK,EAAE,aAAa,EACpB,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,gBAAgB,GACnB;IAAE,KAAK,EAAE,SAAS,EAAE,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CA8E7C;AAED,wBAAgB,qBAAqB,CACpC,EAAE,EAAE,YAAY,EAChB,KAAK,EAAE,aAAa,GAClB,IAAI,CA+XN","sourcesContent":["// @ts-nocheck\nimport { randomUUID } from \"node:crypto\";\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport { keyText, type ExtensionAPI, type ExtensionContext } from \"@ashx-j/lunr\";\nimport { Key, matchesKey, type Component, type TUI } from \"@ashx-j/lunr-tui\";\nimport { BUILTIN_AGENT_NAMES, discoverAgents, discoverAgentsAll, type ChainConfig } from \"../agents/agents.ts\";\nimport {\n\tDEFAULT_PROVIDER_MODELS_MAX_AGE_DAYS,\n\tapplySubagentProfile,\n\tcheckSubagentProfile,\n\tgenerateProfilesForProvider,\n\tlistSubagentProfiles,\n\treadSubagentProfile,\n\trefreshProviderModelCatalog,\n} from \"../profiles/profiles.ts\";\nimport type { SubagentParamsLike } from \"../runs/foreground/subagent-executor.ts\";\nimport { isDynamicParallelStep, isParallelStep, type ChainStep } from \"../shared/settings.ts\";\nimport { findModelInfo, toModelInfo } from \"../shared/model-info.ts\";\nimport { formatTokens, shortenPath } from \"../shared/formatters.ts\";\nimport { listAsyncRuns, formatAsyncRunProgressLabel, type AsyncRunSummary } from \"../runs/background/async-status.ts\";\nimport { scheduledRunStorePath } from \"../runs/background/scheduled-runs.ts\";\nimport { SUBAGENT_FANOUT_CHILD_ENV } from \"../runs/shared/pi-args.ts\";\nimport { assertJsonSchemaObject } from \"../runs/shared/structured-output.ts\";\nimport { validateAcceptanceInput } from \"../runs/shared/acceptance.ts\";\nimport type { SlashSubagentResponse, SlashSubagentUpdate } from \"./slash-bridge.ts\";\nimport { registerPromptWorkflowCommands } from \"./prompt-workflows.ts\";\nimport { openSubagentFleet } from \"../tui/fleet.ts\";\nimport {\n\tapplySlashUpdate,\n\tbuildSlashInitialResult,\n\tfailSlashResult,\n\tfinalizeSlashResult,\n\tresolveSlashMessageDetails,\n} from \"./slash-live-state.ts\";\nimport {\n\tSLASH_RESULT_TYPE,\n\tSLASH_TEXT_RESULT_TYPE,\n\tSLASH_SUBAGENT_CANCEL_EVENT,\n\tSLASH_SUBAGENT_REQUEST_EVENT,\n\tSLASH_SUBAGENT_RESPONSE_EVENT,\n\tSLASH_SUBAGENT_STARTED_EVENT,\n\tSLASH_SUBAGENT_UPDATE_EVENT,\n\tASYNC_DIR,\n\ttype Details,\n\ttype JsonSchemaObject,\n\ttype SingleResult,\n\ttype SubagentState,\n\ttype Usage,\n} from \"../shared/types.ts\";\n\ninterface InlineConfig {\n\toutput?: string | false;\n\toutputMode?: \"inline\" | \"file-only\";\n\treads?: string[] | false;\n\tmodel?: string;\n\tskill?: string[] | false;\n\tprogress?: boolean;\n\tas?: string;\n\tlabel?: string;\n\tphase?: string;\n\tcwd?: string;\n\tcount?: number;\n\toutputSchema?: string;\n\tacceptance?: string;\n}\n\nconst parseInlineConfig = (raw: string): InlineConfig => {\n\tconst config: InlineConfig = {};\n\tfor (const part of raw.split(\",\")) {\n\t\tconst trimmed = part.trim();\n\t\tif (!trimmed) continue;\n\t\tconst eq = trimmed.indexOf(\"=\");\n\t\tif (eq === -1) {\n\t\t\tif (trimmed === \"progress\") config.progress = true;\n\t\t\tcontinue;\n\t\t}\n\t\tconst key = trimmed.slice(0, eq).trim();\n\t\tconst val = trimmed.slice(eq + 1).trim();\n\t\tswitch (key) {\n\t\t\tcase \"output\": config.output = val === \"false\" ? false : val; break;\n\t\t\tcase \"outputMode\": if (val === \"inline\" || val === \"file-only\") config.outputMode = val; break;\n\t\t\tcase \"reads\": config.reads = val === \"false\" ? false : val.split(\"+\").filter(Boolean); break;\n\t\t\tcase \"model\": config.model = val || undefined; break;\n\t\t\tcase \"skill\": case \"skills\": config.skill = val === \"false\" ? false : val.split(\"+\").filter(Boolean); break;\n\t\t\tcase \"progress\": config.progress = val !== \"false\"; break;\n\t\t\tcase \"as\": config.as = val || undefined; break;\n\t\t\tcase \"label\": config.label = val || undefined; break;\n\t\t\tcase \"phase\": config.phase = val || undefined; break;\n\t\t\tcase \"cwd\": config.cwd = val || undefined; break;\n\t\t\tcase \"count\": { const n = Number(val); if (Number.isInteger(n) && n > 0) config.count = n; break; }\n\t\t\tcase \"outputSchema\": config.outputSchema = val || undefined; break;\n\t\t\tcase \"acceptance\": config.acceptance = val || undefined; break;\n\t\t}\n\t}\n\treturn config;\n};\n\nconst parseAgentToken = (token: string): { name: string; config: InlineConfig } => {\n\tconst bracket = token.indexOf(\"[\");\n\tif (bracket === -1) return { name: token, config: {} };\n\tconst end = token.lastIndexOf(\"]\");\n\treturn { name: token.slice(0, bracket), config: parseInlineConfig(token.slice(bracket + 1, end !== -1 ? end : undefined)) };\n};\n\nconst extractExecutionFlags = (rawArgs: string): { args: string; bg: boolean; fork: boolean } => {\n\tlet args = rawArgs.trim();\n\tlet bg = false;\n\tlet fork = false;\n\n\twhile (true) {\n\t\tif (args.endsWith(\" --bg\") || args === \"--bg\") {\n\t\t\tbg = true;\n\t\t\targs = args === \"--bg\" ? \"\" : args.slice(0, -5).trim();\n\t\t\tcontinue;\n\t\t}\n\t\tif (args.endsWith(\" --fork\") || args === \"--fork\") {\n\t\t\tfork = true;\n\t\t\targs = args === \"--fork\" ? \"\" : args.slice(0, -7).trim();\n\t\t\tcontinue;\n\t\t}\n\t\tbreak;\n\t}\n\n\treturn { args, bg, fork };\n};\n\nconst makeAgentCompletions = (state: SubagentState, multiAgent: boolean) => (prefix: string) => {\n\tif (!state.baseCwd) return null;\n\tconst agents = discoverAgents(state.baseCwd, \"both\").agents;\n\tif (!multiAgent) {\n\t\tif (prefix.includes(\" \")) return null;\n\t\treturn agents.filter((a) => a.name.startsWith(prefix)).map((a) => ({ value: a.name, label: a.name }));\n\t}\n\n\t// Find the start of the current chain step: after the last top-level `->` arrow or `(`,\n\t// or after a `|` *inside* a group. A `|` at depth 0 is plain task text (only `(` opens a\n\t// group), so it must not restart agent completion — otherwise `scout -- do x | wr` would\n\t// wrongly resume suggesting agents past the `--` task. Quotes are tracked so separators\n\t// inside a task are ignored.\n\tlet inSingle = false, inDouble = false, depth = 0, segStart = 0;\n\tfor (let i = 0; i < prefix.length; i++) {\n\t\tconst ch = prefix[i]!;\n\t\tif (inSingle) { if (ch === \"'\") inSingle = false; continue; }\n\t\tif (inDouble) { if (ch === '\"') inDouble = false; continue; }\n\t\tif (ch === \"'\") { inSingle = true; continue; }\n\t\tif (ch === '\"') { inDouble = true; continue; }\n\t\tif (ch === \"(\") {\n\t\t\tif (!prefix.slice(segStart, i).includes(\" -- \")) {\n\t\t\t\tdepth++;\n\t\t\t\tsegStart = i + 1;\n\t\t\t}\n\t\t}\n\t\telse if (ch === \")\") {\n\t\t\tif (depth > 0) {\n\t\t\t\tdepth--;\n\t\t\t\tsegStart = i + 1;\n\t\t\t}\n\t\t}\n\t\telse if (ch === \"|\" && depth > 0) segStart = i + 1;\n\t\telse if (ch === \">\" && prefix[i - 1] === \"-\" && depth === 0) segStart = i + 1;\n\t}\n\t// Inside an open quote, or once the task has started (`--` / a quote), we are no\n\t// longer typing an agent name.\n\tif (inSingle || inDouble) return null;\n\tconst segment = prefix.slice(segStart);\n\tif (segment.includes(\" -- \") || segment.includes('\"') || segment.includes(\"'\")) return null;\n\n\tconst lastWord = (segment.match(/(\\S*)$/) || [\"\", \"\"])[1];\n\tlet beforeLastWord = prefix.slice(0, prefix.length - lastWord.length);\n\t// A bare `->` or `|` just typed (no trailing space) needs a separating space;\n\t// `(` glues naturally to the agent name.\n\tif (lastWord === \"\" && /[>|]$/.test(beforeLastWord)) beforeLastWord = `${beforeLastWord} `;\n\n\treturn agents.filter((a) => a.name.startsWith(lastWord)).map((a) => ({ value: `${beforeLastWord}${a.name}`, label: a.name }));\n};\n\nconst discoverSavedChains = (cwd: string): ChainConfig[] => {\n\tconst chainsByName = new Map<string, ChainConfig>();\n\tfor (const chain of discoverAgentsAll(cwd).chains) {\n\t\tchainsByName.set(chain.name, chain);\n\t}\n\treturn Array.from(chainsByName.values());\n};\n\nconst makeChainCompletions = (state: SubagentState) => (prefix: string) => {\n\tif (prefix.includes(\" \") || !state.baseCwd) return null;\n\treturn discoverSavedChains(state.baseCwd)\n\t\t.filter((chain) => chain.name.startsWith(prefix))\n\t\t.map((chain) => ({ value: chain.name, label: chain.name }));\n};\n\nconst makeBuiltinAgentNameCompletions = () => (prefix: string) => {\n\tif (prefix.includes(\" \")) return null;\n\treturn BUILTIN_AGENT_NAMES\n\t\t.filter((name) => name.startsWith(prefix))\n\t\t.map((name) => ({ value: name, label: name }));\n};\n\nconst makeProviderCompletions = (state: SubagentState) => (prefix: string) => {\n\tif (prefix.includes(\" \")) return null;\n\tconst available = state.lastUiContext?.modelRegistry?.getAvailable?.();\n\tif (!Array.isArray(available)) return null;\n\tconst providers = [...new Set(available\n\t\t.map((model) => typeof model?.provider === \"string\" ? model.provider : \"\")\n\t\t.filter(Boolean))]\n\t\t.sort((a, b) => a.localeCompare(b));\n\treturn providers\n\t\t.filter((provider) => provider.startsWith(prefix))\n\t\t.map((provider) => ({ value: provider, label: provider }));\n};\n\nfunction sendSlashText(pi: ExtensionAPI, text: string): void {\n\tpi.sendMessage({ customType: SLASH_TEXT_RESULT_TYPE, content: text, display: true });\n}\n\nasync function withSlashStatus<T>(\n\tctx: ExtensionContext,\n\ttext: string,\n\trun: () => Promise<T>,\n): Promise<T> {\n\tif (ctx.hasUI) ctx.ui.setStatus(\"subagent-slash-text\", text);\n\ttry {\n\t\treturn await run();\n\t} finally {\n\t\tif (ctx.hasUI) ctx.ui.setStatus(\"subagent-slash-text\", undefined);\n\t}\n}\n\ntype Theme = ExtensionContext[\"ui\"][\"theme\"];\n\ntype StopSelectorTarget = {\n\tkind: \"async\" | \"scheduled\";\n\tid: string;\n\tlabel: string;\n\tdetail: string;\n\tactionLabel: string;\n};\n\ntype StopSelectorResult = { confirmed: boolean; target?: StopSelectorTarget };\n\nfunction commandForTarget(target: StopSelectorTarget): string {\n\treturn target.kind === \"scheduled\"\n\t\t? `subagent({ action: \"schedule-cancel\", id: ${JSON.stringify(target.id)} })`\n\t\t: `subagent({ action: \"stop\", id: ${JSON.stringify(target.id)} })`;\n}\n\nfunction formatAsyncStopTarget(run: AsyncRunSummary): StopSelectorTarget {\n\tconst progress = formatAsyncRunProgressLabel(run);\n\tconst cwd = run.cwd ? shortenPath(run.cwd) : shortenPath(run.asyncDir);\n\treturn {\n\t\tkind: \"async\",\n\t\tid: run.id,\n\t\tlabel: `${run.id} · ${run.mode} · ${progress}`,\n\t\tdetail: `${run.state} · ${cwd}`,\n\t\tactionLabel: \"stop async run\",\n\t};\n}\n\nfunction scheduledStopTargets(ctx: ExtensionContext, state: SubagentState): StopSelectorTarget[] {\n\tconst sessionId = state.currentSessionId ?? ctx.sessionManager.getSessionId();\n\tif (!sessionId) return [];\n\tconst storePath = scheduledRunStorePath(ctx.cwd, sessionId);\n\tif (!fs.existsSync(storePath)) return [];\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(fs.readFileSync(storePath, \"utf-8\"));\n\t} catch {\n\t\treturn [];\n\t}\n\tconst jobs = parsed && typeof parsed === \"object\" && Array.isArray((parsed as { jobs?: unknown }).jobs)\n\t\t? (parsed as { jobs: Array<Record<string, unknown>> }).jobs\n\t\t: [];\n\treturn jobs\n\t\t.filter((job) => job.state === \"scheduled\" && typeof job.id === \"string\" && typeof job.name === \"string\" && typeof job.runAt === \"number\")\n\t\t.sort((left, right) => Number(left.runAt) - Number(right.runAt))\n\t\t.map((job) => ({\n\t\t\tkind: \"scheduled\" as const,\n\t\t\tid: String(job.id),\n\t\t\tlabel: `${String(job.id)} · ${String(job.name)}`,\n\t\t\tdetail: `scheduled · ${new Date(Number(job.runAt)).toISOString()}`,\n\t\t\tactionLabel: \"cancel scheduled run\",\n\t\t}));\n}\n\nfunction discoverStopTargets(ctx: ExtensionContext, state: SubagentState): StopSelectorTarget[] {\n\tconst sessionId = state.currentSessionId ?? ctx.sessionManager.getSessionId() ?? undefined;\n\tconst asyncTargets = listAsyncRuns(ASYNC_DIR, {\n\t\tstates: [\"queued\", \"running\"],\n\t\t...(sessionId ? { sessionId } : {}),\n\t}).map(formatAsyncStopTarget);\n\treturn [...asyncTargets, ...scheduledStopTargets(ctx, state)];\n}\n\nfunction stopFallbackText(targets: StopSelectorTarget[]): string {\n\tif (targets.length === 0) return \"No active current-session async runs or scheduled subagent runs to stop.\";\n\tconst lines = [\"Subagent stop targets:\", \"\"];\n\tfor (const target of targets) {\n\t\tlines.push(`- ${target.label}`);\n\t\tlines.push(` ${target.detail}`);\n\t\tlines.push(` ${target.actionLabel}: ${commandForTarget(target)}`);\n\t\tif (target.kind === \"async\") lines.push(` slash: /subagents-stop ${target.id}`);\n\t}\n\treturn lines.join(\"\\n\");\n}\n\nclass SubagentsStopSelector implements Component {\n\treadonly width = 84;\n\tprivate selected = 0;\n\tprivate confirming = false;\n\tprivate readonly tui: TUI;\n\tprivate readonly theme: Theme;\n\tprivate readonly targets: StopSelectorTarget[];\n\tprivate readonly done: (result: StopSelectorResult) => void;\n\n\tconstructor(tui: TUI, theme: Theme, targets: StopSelectorTarget[], done: (result: StopSelectorResult) => void) {\n\t\tthis.tui = tui;\n\t\tthis.theme = theme;\n\t\tthis.targets = targets;\n\t\tthis.done = done;\n\t}\n\n\thandleInput(data: string): void {\n\t\tif (matchesKey(data, \"escape\") || matchesKey(data, \"ctrl+c\")) {\n\t\t\tthis.done({ confirmed: false });\n\t\t\treturn;\n\t\t}\n\t\tif (this.confirming) {\n\t\t\tif (matchesKey(data, \"return\") || data.toLowerCase() === \"y\") {\n\t\t\t\tthis.done({ confirmed: true, target: this.targets[this.selected] });\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (data.toLowerCase() === \"n\" || matchesKey(data, \"backspace\")) {\n\t\t\t\tthis.confirming = false;\n\t\t\t\tthis.tui.requestRender();\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\tif (matchesKey(data, \"up\")) {\n\t\t\tthis.selected = Math.max(0, this.selected - 1);\n\t\t\tthis.tui.requestRender();\n\t\t\treturn;\n\t\t}\n\t\tif (matchesKey(data, \"down\")) {\n\t\t\tthis.selected = Math.min(this.targets.length - 1, this.selected + 1);\n\t\t\tthis.tui.requestRender();\n\t\t\treturn;\n\t\t}\n\t\tif (matchesKey(data, \"return\")) {\n\t\t\tthis.confirming = true;\n\t\t\tthis.tui.requestRender();\n\t\t}\n\t}\n\n\trender(width: number): string[] {\n\t\tconst contentWidth = Math.max(40, Math.min(this.width, width || this.width));\n\t\tconst lines = [this.theme.bold(\"Stop subagent run\"), this.theme.fg(\"dim\", \"Select a current-session async run to stop, or a scheduled run to cancel.\"), \"\"];\n\t\tconst maxRows = 10;\n\t\tconst start = Math.max(0, Math.min(this.selected - maxRows + 1, Math.max(0, this.targets.length - maxRows)));\n\t\tfor (let index = start; index < Math.min(this.targets.length, start + maxRows); index++) {\n\t\t\tconst target = this.targets[index]!;\n\t\t\tconst selected = index === this.selected;\n\t\t\tconst marker = selected ? \"›\" : \" \";\n\t\t\t\tconst actionLabel = target.actionLabel;\n\t\t\tconst action = target.kind === \"scheduled\" ? this.theme.fg(\"warning\", actionLabel) : this.theme.fg(\"accent\", actionLabel);\n\t\t\tconst labelWidth = Math.max(0, contentWidth - marker.length - actionLabel.length - 2);\n\t\t\tlines.push(`${marker} ${action} ${target.label.slice(0, labelWidth)}`);\n\t\t\tif (selected) lines.push(this.theme.fg(\"dim\", ` ${target.detail}`.slice(0, contentWidth)));\n\t\t}\n\t\tif (this.targets.length > maxRows) lines.push(this.theme.fg(\"dim\", `Showing ${start + 1}-${Math.min(this.targets.length, start + maxRows)} of ${this.targets.length}`));\n\t\tlines.push(\"\");\n\t\tif (this.confirming) {\n\t\t\tconst target = this.targets[this.selected]!;\n\t\t\tlines.push(this.theme.fg(\"warning\", `Confirm: ${target.actionLabel} ${target.id}?`));\n\t\t\tif (target.kind === \"async\") lines.push(this.theme.fg(\"dim\", \"Stop ends this run; use interrupt for a resumable pause.\"));\n\t\t\tlines.push(this.theme.fg(\"dim\", \"Enter/Y confirms · N returns · Esc cancels\"));\n\t\t} else {\n\t\t\tlines.push(this.theme.fg(\"dim\", \"↑/↓ select · Enter confirm · Esc cancel\"));\n\t\t}\n\t\treturn lines;\n\t}\n}\n\nfunction emptyUsage(): Usage {\n\treturn { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, turns: 0 };\n}\n\nfunction addUsage(target: Usage, source: Usage): void {\n\ttarget.input += source.input;\n\ttarget.output += source.output;\n\ttarget.cacheRead += source.cacheRead;\n\ttarget.cacheWrite += source.cacheWrite;\n\ttarget.cost += source.cost;\n\ttarget.turns += source.turns;\n}\n\nfunction usageHasValue(usage: Usage): boolean {\n\treturn usage.input !== 0 || usage.output !== 0 || usage.cacheRead !== 0 || usage.cacheWrite !== 0 || usage.cost !== 0 || usage.turns !== 0;\n}\n\nfunction assistantUsageFromMessage(message: unknown): Usage | undefined {\n\tif (!message || typeof message !== \"object\") return undefined;\n\tconst msg = message as { role?: unknown; usage?: unknown };\n\tif (msg.role !== \"assistant\" || !msg.usage || typeof msg.usage !== \"object\") return undefined;\n\tconst usage = msg.usage as {\n\t\tinput?: unknown;\n\t\toutput?: unknown;\n\t\tcacheRead?: unknown;\n\t\tcacheWrite?: unknown;\n\t\tcost?: { total?: unknown };\n\t};\n\treturn {\n\t\tinput: typeof usage.input === \"number\" ? usage.input : 0,\n\t\toutput: typeof usage.output === \"number\" ? usage.output : 0,\n\t\tcacheRead: typeof usage.cacheRead === \"number\" ? usage.cacheRead : 0,\n\t\tcacheWrite: typeof usage.cacheWrite === \"number\" ? usage.cacheWrite : 0,\n\t\tcost: typeof usage.cost?.total === \"number\" ? usage.cost.total : 0,\n\t\tturns: 1,\n\t};\n}\n\nfunction isSubagentDetails(value: unknown): value is Details {\n\tif (!value || typeof value !== \"object\") return false;\n\tconst details = value as { mode?: unknown; results?: unknown };\n\treturn typeof details.mode === \"string\" && Array.isArray(details.results);\n}\n\nfunction detailsFromSessionEntry(entry: unknown): Details | undefined {\n\tif (!entry || typeof entry !== \"object\") return undefined;\n\tconst record = entry as { type?: unknown; customType?: unknown; details?: unknown; message?: unknown };\n\tif (record.type === \"custom_message\" && record.customType === SLASH_RESULT_TYPE) {\n\t\tconst details = resolveSlashMessageDetails(record.details)?.result.details;\n\t\treturn isSubagentDetails(details) ? details : undefined;\n\t}\n\tif (record.type !== \"message\" || !record.message || typeof record.message !== \"object\") return undefined;\n\tconst message = record.message as { role?: unknown; toolName?: unknown; details?: unknown };\n\tif (message.role !== \"toolResult\" || message.toolName !== \"subagent\") return undefined;\n\treturn isSubagentDetails(message.details) ? message.details : undefined;\n}\n\nfunction formatCostUsage(label: string, usage: Usage): string {\n\tconst extras = [\n\t\tusage.cacheRead ? `cache read ${formatTokens(usage.cacheRead)}` : \"\",\n\t\tusage.cacheWrite ? `cache write ${formatTokens(usage.cacheWrite)}` : \"\",\n\t\tusage.turns ? `${usage.turns} turn${usage.turns === 1 ? \"\" : \"s\"}` : \"\",\n\t].filter(Boolean);\n\treturn `${label}: ↑${formatTokens(usage.input)} ↓${formatTokens(usage.output)} $${usage.cost.toFixed(4)}${extras.length ? ` (${extras.join(\", \")})` : \"\"}`;\n}\n\nfunction buildSubagentCostReport(ctx: ExtensionContext): string {\n\tconst parent = emptyUsage();\n\tconst childTotal = emptyUsage();\n\tconst total = emptyUsage();\n\tconst children: Array<{ label: string; usage: Usage; sessionFile?: string }> = [];\n\tfor (const entry of ctx.sessionManager.getBranch()) {\n\t\tconst message = entry.type === \"message\" ? (entry as { message?: unknown }).message : undefined;\n\t\tconst parentUsage = assistantUsageFromMessage(message);\n\t\tif (parentUsage) addUsage(parent, parentUsage);\n\t\tconst details = detailsFromSessionEntry(entry);\n\t\tif (!details) continue;\n\t\tfor (const result of details.results) {\n\t\t\tif (!usageHasValue(result.usage)) continue;\n\t\t\tconst usage = { ...result.usage };\n\t\t\tchildren.push({\n\t\t\t\tlabel: `Child ${children.length + 1} (${result.agent})`,\n\t\t\t\tusage,\n\t\t\t\t...(result.sessionFile ? { sessionFile: result.sessionFile } : {}),\n\t\t\t});\n\t\t\taddUsage(childTotal, usage);\n\t\t}\n\t}\n\taddUsage(total, parent);\n\taddUsage(total, childTotal);\n\tconst lines = [\n\t\t\"Subagent cost\",\n\t\t\"\",\n\t\tformatCostUsage(\"Parent\", parent),\n\t];\n\tif (children.length === 0) {\n\t\tlines.push(\"No subagent child usage found in this session.\");\n\t} else {\n\t\tfor (const child of children) {\n\t\t\tlines.push(formatCostUsage(child.label, child.usage));\n\t\t\tif (child.sessionFile) lines.push(` Session: ${child.sessionFile}`);\n\t\t}\n\t}\n\tlines.push(\"────────────────────────────\", formatCostUsage(\"Children\", childTotal), formatCostUsage(\"Total\", total));\n\treturn lines.join(\"\\n\");\n}\n\nfunction parseSingleRequiredArg(args: string, usage: string): { ok: true; value: string } | { ok: false; message: string } {\n\tconst parts = args.trim().split(/\\s+/).filter(Boolean);\n\tif (parts.length !== 1) return { ok: false, message: usage };\n\treturn { ok: true, value: parts[0]! };\n}\n\nfunction getProfileWorkerModel(profile: { subagents?: { agentOverrides?: Record<string, { model?: string }> } }): string | undefined {\n\tconst model = profile.subagents?.agentOverrides?.worker?.model;\n\treturn typeof model === \"string\" && model.trim() ? model.trim() : undefined;\n}\n\nfunction loadSavedOutputSchema(chain: ChainConfig, stepAgent: string, outputSchema: unknown): JsonSchemaObject | undefined {\n\tif (outputSchema === undefined) return undefined;\n\tif (typeof outputSchema === \"string\") {\n\t\tconst schemaPath = path.isAbsolute(outputSchema)\n\t\t\t? outputSchema\n\t\t\t: path.join(path.dirname(chain.filePath), outputSchema);\n\t\tconst parsed = JSON.parse(fs.readFileSync(schemaPath, \"utf-8\")) as unknown;\n\t\tassertJsonSchemaObject(parsed, `outputSchema for chain '${chain.name}' step '${stepAgent}' (${schemaPath})`);\n\t\treturn parsed;\n\t}\n\tassertJsonSchemaObject(outputSchema, `outputSchema for chain '${chain.name}' step '${stepAgent}'`);\n\treturn outputSchema;\n}\n\nconst mapSavedChainSteps = (chain: ChainConfig, worktree = false): ChainStep[] => {\n\treturn (chain.steps as unknown as Array<ChainStep & { skills?: string[] | false }>).map((step) => {\n\t\tif (isParallelStep(step)) {\n\t\t\tconst parallel = step.parallel.map((task) => {\n\t\t\t\tconst { outputSchema: rawOutputSchema, ...rest } = task as typeof task & { outputSchema?: unknown };\n\t\t\t\tconst outputSchema = loadSavedOutputSchema(chain, task.agent, rawOutputSchema);\n\t\t\t\treturn { ...rest, ...(outputSchema ? { outputSchema } : {}) };\n\t\t\t});\n\t\t\treturn { ...step, parallel, ...(worktree ? { worktree: true } : {}) };\n\t\t}\n\t\tif (isDynamicParallelStep(step)) {\n\t\t\tconst { outputSchema: rawOutputSchema, ...parallelRest } = step.parallel as typeof step.parallel & { outputSchema?: unknown };\n\t\t\tconst outputSchema = loadSavedOutputSchema(chain, step.parallel.agent, rawOutputSchema);\n\t\t\tconst collectSchema = loadSavedOutputSchema(chain, `${step.collect.as} collection`, step.collect.outputSchema);\n\t\t\treturn {\n\t\t\t\t...step,\n\t\t\t\tparallel: { ...parallelRest, ...(outputSchema ? { outputSchema } : {}) },\n\t\t\t\tcollect: { ...step.collect, ...(collectSchema ? { outputSchema: collectSchema } : {}) },\n\t\t\t};\n\t\t}\n\t\tconst outputSchema = loadSavedOutputSchema(chain, step.agent, (step as { outputSchema?: unknown }).outputSchema);\n\t\treturn {\n\t\t\tagent: step.agent,\n\t\t\ttask: step.task || undefined,\n\t\t\t...(step.phase ? { phase: step.phase } : {}),\n\t\t\t...(step.label ? { label: step.label } : {}),\n\t\t\t...(step.as ? { as: step.as } : {}),\n\t\t\t...(outputSchema ? { outputSchema } : {}),\n\t\t\t...((step as { acceptance?: unknown }).acceptance !== undefined ? { acceptance: (step as { acceptance?: unknown }).acceptance } : {}),\n\t\t\toutput: step.output,\n\t\t\toutputMode: step.outputMode,\n\t\t\treads: step.reads,\n\t\t\tprogress: step.progress,\n\t\t\tskill: step.skill ?? step.skills,\n\t\t\tmodel: step.model,\n\t\t};\n\t});\n};\n\nasync function requestSlashRun(\n\tpi: ExtensionAPI,\n\tctx: ExtensionContext,\n\trequestId: string,\n\tparams: SubagentParamsLike,\n): Promise<SlashSubagentResponse> {\n\treturn new Promise((resolve, reject) => {\n\t\tlet done = false;\n\t\tlet started = false;\n\n\t\tconst startTimeoutMs = 15_000;\n\t\tconst startTimeout = setTimeout(() => {\n\t\t\tfinish(() => reject(new Error(\n\t\t\t\t\"Slash subagent bridge did not start within 15s. Ensure the extension is loaded correctly.\",\n\t\t\t)));\n\t\t}, startTimeoutMs);\n\n\t\tconst onStarted = (data: unknown) => {\n\t\t\tif (done || !data || typeof data !== \"object\") return;\n\t\t\tif ((data as { requestId?: unknown }).requestId !== requestId) return;\n\t\t\tstarted = true;\n\t\t\tclearTimeout(startTimeout);\n\t\t\tif (ctx.hasUI) ctx.ui.setStatus(\"subagent-slash\", \"running...\");\n\t\t};\n\n\t\tconst onResponse = (data: unknown) => {\n\t\t\tif (done || !data || typeof data !== \"object\") return;\n\t\t\tconst response = data as Partial<SlashSubagentResponse>;\n\t\t\tif (response.requestId !== requestId) return;\n\t\t\tclearTimeout(startTimeout);\n\t\t\tfinish(() => resolve(response as SlashSubagentResponse));\n\t\t};\n\n\t\tconst onUpdate = (data: unknown) => {\n\t\t\tif (done || !data || typeof data !== \"object\") return;\n\t\t\tconst update = data as SlashSubagentUpdate;\n\t\t\tif (update.requestId !== requestId) return;\n\t\t\tapplySlashUpdate(requestId, update);\n\t\t\tif (!ctx.hasUI) return;\n\t\t\tconst tool = update.currentTool ? ` ${update.currentTool}` : \"\";\n\t\t\tconst count = update.toolCount ?? 0;\n\t\t\tconst liveDetailKey = keyText(\"app.tools.expand\");\n\t\t\tctx.ui.setStatus(\"subagent-slash\", `${count} tools${tool} | ${liveDetailKey} live detail`);\n\t\t};\n\n\t\tconst onTerminalInput = ctx.hasUI\n\t\t\t? ctx.ui.onTerminalInput((input) => {\n\t\t\t\tif (!matchesKey(input, Key.escape)) return undefined;\n\t\t\t\tpi.events.emit(SLASH_SUBAGENT_CANCEL_EVENT, { requestId });\n\t\t\t\tfinish(() => reject(new Error(\"Cancelled\")));\n\t\t\t\treturn { consume: true };\n\t\t\t})\n\t\t\t: undefined;\n\n\t\tconst unsubStarted = pi.events.on(SLASH_SUBAGENT_STARTED_EVENT, onStarted);\n\t\tconst unsubResponse = pi.events.on(SLASH_SUBAGENT_RESPONSE_EVENT, onResponse);\n\t\tconst unsubUpdate = pi.events.on(SLASH_SUBAGENT_UPDATE_EVENT, onUpdate);\n\n\t\tconst finish = (next: () => void) => {\n\t\t\tif (done) return;\n\t\t\tdone = true;\n\t\t\tclearTimeout(startTimeout);\n\t\t\tunsubStarted();\n\t\t\tunsubResponse();\n\t\t\tunsubUpdate();\n\t\t\tonTerminalInput?.();\n\t\t\tnext();\n\t\t};\n\n\t\tpi.events.emit(SLASH_SUBAGENT_REQUEST_EVENT, { requestId, params, ctx });\n\n\t\t// Bridge emits STARTED synchronously during REQUEST emit.\n\t\t// If not started, no bridge received the request.\n\t\tif (!started && done) return;\n\t\tif (!started) {\n\t\t\tfinish(() => reject(new Error(\n\t\t\t\t\"No slash subagent bridge responded. Ensure the subagent extension is loaded correctly.\",\n\t\t\t)));\n\t\t}\n\t});\n}\n\nfunction extractSlashMessageText(content: string | Array<{ type?: string; text?: string }>): string {\n\tif (typeof content === \"string\") return content;\n\tif (!Array.isArray(content)) return \"\";\n\treturn content\n\t\t.filter((part): part is { type: \"text\"; text: string } => part?.type === \"text\" && typeof part.text === \"string\")\n\t\t.map((part) => part.text)\n\t\t.join(\"\\n\");\n}\n\nfunction formatExportPathList(paths: string[]): string {\n\treturn paths.map((file) => `- \\`${file}\\``).join(\"\\n\");\n}\n\nfunction collectResultPaths(results: SingleResult[], getPath: (result: SingleResult) => string | undefined): string[] {\n\treturn results\n\t\t.map(getPath)\n\t\t.filter((file): file is string => typeof file === \"string\" && file.length > 0);\n}\n\nfunction buildSlashExportText(response: SlashSubagentResponse): string {\n\tconst output = extractSlashMessageText(response.result.content) || response.errorText || \"(no output)\";\n\tconst results = response.result.details?.results ?? [];\n\tconst sessionFiles = collectResultPaths(results, (result) => result.sessionFile);\n\tconst savedOutputs = collectResultPaths(results, (result) => result.savedOutputPath);\n\tconst artifactOutputs = collectResultPaths(results, (result) => result.artifactPaths?.outputPath);\n\tconst sections = [\"## Subagent result\", output];\n\tif (sessionFiles.length > 0) sections.push(\"## Child session exports\", formatExportPathList(sessionFiles));\n\tif (savedOutputs.length > 0) sections.push(\"## Saved outputs\", formatExportPathList(savedOutputs));\n\tif (artifactOutputs.length > 0) sections.push(\"## Artifact outputs\", formatExportPathList(artifactOutputs));\n\treturn sections.join(\"\\n\\n\");\n}\n\nfunction persistSlashSessionSnapshot(ctx: ExtensionContext): void {\n\ttry {\n\t\tif (!ctx.sessionManager) return;\n\t\tconst sessionManager = ctx.sessionManager as typeof ctx.sessionManager & {\n\t\t\t_rewriteFile?: () => void;\n\t\t\tflushed?: boolean;\n\t\t};\n\t\tconst sessionFile = sessionManager.getSessionFile();\n\t\tif (!sessionFile || typeof sessionManager._rewriteFile !== \"function\") return;\n\t\tfs.mkdirSync(path.dirname(sessionFile), { recursive: true });\n\t\tsessionManager._rewriteFile();\n\t\tsessionManager.flushed = true;\n\t} catch (error) {\n\t\tconsole.error(\"Failed to persist slash session snapshot for export:\", error);\n\t}\n}\n\nasync function runSlashSubagent(\n\tpi: ExtensionAPI,\n\tctx: ExtensionContext,\n\tparams: SubagentParamsLike,\n): Promise<void> {\n\tif (ctx.hasUI) ctx.ui.setToolsExpanded(false);\n\tconst requestId = randomUUID();\n\tconst initialDetails = buildSlashInitialResult(requestId, params);\n\tconst initialText = extractSlashMessageText(initialDetails.result.content) || \"Running subagent...\";\n\tpi.sendMessage({\n\t\tcustomType: SLASH_RESULT_TYPE,\n\t\tcontent: initialText,\n\t\tdisplay: true,\n\t\tdetails: initialDetails,\n\t});\n\tpersistSlashSessionSnapshot(ctx);\n\n\ttry {\n\t\tconst response = await requestSlashRun(pi, ctx, requestId, params);\n\t\tconst finalDetails = finalizeSlashResult(response);\n\t\tpi.sendMessage({\n\t\t\tcustomType: SLASH_RESULT_TYPE,\n\t\t\tcontent: buildSlashExportText(response),\n\t\t\tdisplay: !ctx.hasUI,\n\t\t\tdetails: finalDetails,\n\t\t});\n\t\tpersistSlashSessionSnapshot(ctx);\n\t\tif (ctx.hasUI) {\n\t\t\tctx.ui.setStatus(\"subagent-slash\", undefined);\n\t\t}\n\t\tif (response.isError && ctx.hasUI) {\n\t\t\tctx.ui.notify(response.errorText || \"Subagent failed\", \"error\");\n\t\t}\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\tconst failedDetails = failSlashResult(requestId, params, message);\n\t\tpi.sendMessage({\n\t\t\tcustomType: SLASH_RESULT_TYPE,\n\t\t\tcontent: `## Subagent result\\n\\n${message}`,\n\t\t\tdisplay: !ctx.hasUI,\n\t\t\tdetails: failedDetails,\n\t\t});\n\t\tpersistSlashSessionSnapshot(ctx);\n\t\tif (ctx.hasUI) {\n\t\t\tctx.ui.setStatus(\"subagent-slash\", undefined);\n\t\t}\n\t\tif (message === \"Cancelled\") {\n\t\t\tif (ctx.hasUI) ctx.ui.notify(\"Cancelled\", \"warning\");\n\t\t\treturn;\n\t\t}\n\t\tif (ctx.hasUI) ctx.ui.notify(message, \"error\");\n\t}\n}\n\n\nexport interface GroupConfig { concurrency?: number; failFast?: boolean; worktree?: boolean }\nexport interface ParsedStep { kind: \"step\"; name: string; config: InlineConfig; task?: string }\nexport interface ParsedGroup { kind: \"group\"; tasks: ParsedStep[]; config: GroupConfig }\nexport type ParsedGroupStep = ParsedStep | ParsedGroup;\n\nexport const PARALLEL_GROUP_USAGE =\n\t'Usage: /chain agent \"task\" -> (agent2 \"task\" | agent3 \"task\") -> agent4';\n\nexport class SlashParseError extends Error {}\n\n// Walk `input` tracking quote/paren state; returns true if parens are unbalanced.\nfunction findUnmatchedCloseParen(input: string): boolean {\n\tlet depth = 0, inSingle = false, inDouble = false;\n\tfor (let i = 0; i < input.length; i++) {\n\t\tconst ch = input[i]!;\n\t\tif (inSingle) { if (ch === \"'\") inSingle = false; continue; }\n\t\tif (inDouble) { if (ch === '\"') inDouble = false; continue; }\n\t\tif (ch === \"'\") { inSingle = true; continue; }\n\t\tif (ch === '\"') { inDouble = true; continue; }\n\t\tif (ch === \"(\") depth++;\n\t\telse if (ch === \")\") { depth--; if (depth < 0) return true; }\n\t}\n\treturn depth !== 0;\n}\n\n// Split on top-level \" -> \", ignoring arrows inside quotes or parentheses.\nfunction splitOnArrow(input: string): string[] {\n\tconst segments: string[] = [];\n\tlet depth = 0, inSingle = false, inDouble = false, start = 0;\n\tfor (let i = 0; i < input.length; i++) {\n\t\tconst ch = input[i]!;\n\t\tif (inSingle) { if (ch === \"'\") inSingle = false; continue; }\n\t\tif (inDouble) { if (ch === '\"') inDouble = false; continue; }\n\t\tif (ch === \"'\") { inSingle = true; continue; }\n\t\tif (ch === '\"') { inDouble = true; continue; }\n\t\tif (ch === \"(\") depth++;\n\t\telse if (ch === \")\") depth--;\n\t\telse if (depth === 0 && ch === \"-\" && input[i + 1] === \">\" && input[i + 2] === \" \") {\n\t\t\tsegments.push(input.slice(start, i));\n\t\t\ti += 2;\n\t\t\tstart = i + 1;\n\t\t}\n\t}\n\tsegments.push(input.slice(start));\n\treturn segments;\n}\n\n// Split a group's inner text on top-level \" | \", ignoring pipes inside quotes/parens.\nfunction splitGroupTasks(inner: string): string[] {\n\tconst parts: string[] = [];\n\tlet depth = 0, inSingle = false, inDouble = false, start = 0;\n\tfor (let i = 0; i < inner.length; i++) {\n\t\tconst ch = inner[i]!;\n\t\tif (inSingle) { if (ch === \"'\") inSingle = false; continue; }\n\t\tif (inDouble) { if (ch === '\"') inDouble = false; continue; }\n\t\tif (ch === \"'\") { inSingle = true; continue; }\n\t\tif (ch === '\"') { inDouble = true; continue; }\n\t\tif (ch === \"(\") depth++;\n\t\telse if (ch === \")\") depth--;\n\t\telse if (ch === \"|\" && depth === 0) {\n\t\t\tparts.push(inner.slice(start, i));\n\t\t\tstart = i + 1;\n\t\t}\n\t}\n\tparts.push(inner.slice(start));\n\treturn parts;\n}\n\nexport function parseSingleTaskToken(token: string): ParsedStep {\n\tlet agentPart: string;\n\tlet task: string | undefined;\n\tconst qMatch = token.match(/^(\\S+(?:\\[[^\\]]*\\])?)\\s+(?:\"([^\"]*)\"|'([^']*)')$/);\n\tif (qMatch) {\n\t\tagentPart = qMatch[1]!;\n\t\ttask = (qMatch[2] ?? qMatch[3]) || undefined;\n\t} else {\n\t\tconst dashIdx = token.indexOf(\" -- \");\n\t\tif (dashIdx !== -1) {\n\t\t\tagentPart = token.slice(0, dashIdx).trim();\n\t\t\ttask = token.slice(dashIdx + 4).trim() || undefined;\n\t\t} else {\n\t\t\tagentPart = token;\n\t\t}\n\t}\n\treturn { kind: \"step\", ...parseAgentToken(agentPart), task };\n}\n\nconst parseGroupConfig = (raw: string): GroupConfig => {\n\tconst config: GroupConfig = {};\n\tfor (const part of raw.split(\",\")) {\n\t\tconst trimmed = part.trim();\n\t\tif (!trimmed) continue;\n\t\tconst eq = trimmed.indexOf(\"=\");\n\t\tconst key = eq === -1 ? trimmed : trimmed.slice(0, eq).trim();\n\t\tconst val = eq === -1 ? \"\" : trimmed.slice(eq + 1).trim();\n\t\tswitch (key) {\n\t\t\tcase \"concurrency\": { const n = Number(val); if (Number.isInteger(n) && n > 0) config.concurrency = n; break; }\n\t\t\tcase \"failFast\": config.failFast = eq === -1 ? true : val !== \"false\"; break;\n\t\t\tcase \"worktree\": config.worktree = eq === -1 ? true : val !== \"false\"; break;\n\t\t}\n\t}\n\treturn config;\n};\n\n// Split `(...)` from an optional trailing `[...]` group-config suffix, respecting\n// quotes and nested parens. Returns the inner group text and the parsed config.\nconst splitGroupBody = (trimmed: string): { inner: string; config: GroupConfig } => {\n\tlet depth = 0, inSingle = false, inDouble = false, closeIdx = -1;\n\tfor (let i = 0; i < trimmed.length; i++) {\n\t\tconst ch = trimmed[i]!;\n\t\tif (inSingle) { if (ch === \"'\") inSingle = false; continue; }\n\t\tif (inDouble) { if (ch === '\"') inDouble = false; continue; }\n\t\tif (ch === \"'\") { inSingle = true; continue; }\n\t\tif (ch === '\"') { inDouble = true; continue; }\n\t\tif (ch === \"(\") depth++;\n\t\telse if (ch === \")\") { depth--; if (depth === 0) { closeIdx = i; break; } }\n\t}\n\tif (closeIdx === -1) throw new SlashParseError(`Unmatched parentheses in group: '${trimmed}'`);\n\tconst inner = trimmed.slice(1, closeIdx);\n\tconst suffix = trimmed.slice(closeIdx + 1).trim();\n\tif (!suffix) return { inner, config: {} };\n\tif (!suffix.startsWith(\"[\") || !suffix.endsWith(\"]\")) {\n\t\tthrow new SlashParseError(`Group options must be wrapped in [...]: '${suffix}'`);\n\t}\n\treturn { inner, config: parseGroupConfig(suffix.slice(1, -1)) };\n};\n\nexport function parseGroupSegment(segment: string): ParsedGroup {\n\tconst trimmed = segment.trim();\n\tif (!trimmed.startsWith(\"(\")) {\n\t\tthrow new SlashParseError(`Parallel group must be wrapped in parentheses: '${trimmed}'`);\n\t}\n\tconst { inner, config } = splitGroupBody(trimmed);\n\tconst rawParts = splitGroupTasks(inner).map((p) => p.trim()).filter((p) => p.length > 0);\n\tif (rawParts.length < 2) {\n\t\tthrow new SlashParseError(\"Parallel group must contain at least two tasks separated by ' | '\");\n\t}\n\treturn { kind: \"group\", tasks: rawParts.map((part) => parseSingleTaskToken(part)), config };\n}\n\n// True if `input` uses inline parallel-group syntax. A group is a *step* that begins\n// with `(` at the top level, so we split on top-level ` -> ` arrows and look for a\n// segment that opens with `(`. Parentheses appearing inside a task (e.g.\n// `scout -- inspect auth (backend)`) do not count, which keeps legacy shared-task\n// `-- task` parsing — including a bare `|` inside the task — on the single-agent path.\nexport function hasGroupSyntax(input: string): boolean {\n\treturn splitOnArrow(input).some((seg) => seg.trim().startsWith(\"(\"));\n}\n\nexport function parseChainExpression(input: string): { steps: ParsedGroupStep[] } {\n\tconst trimmed = input.trim();\n\tif (!trimmed.includes(\" -> \")) {\n\t\tthrow new SlashParseError('Parallel groups in /chain require \" -> \" between steps');\n\t}\n\tif (findUnmatchedCloseParen(trimmed)) {\n\t\tthrow new SlashParseError(\"Unmatched parentheses in /chain expression\");\n\t}\n\tconst steps: ParsedGroupStep[] = [];\n\tfor (const seg of splitOnArrow(trimmed)) {\n\t\tconst t = seg.trim();\n\t\tif (!t) continue;\n\t\tif (t.startsWith(\"(\")) {\n\t\t\tsteps.push(parseGroupSegment(t));\n\t\t\tcontinue;\n\t\t}\n\t\tif (findUnmatchedCloseParen(t)) {\n\t\t\tthrow new SlashParseError(`Unmatched parentheses in chain segment: '${t}'`);\n\t\t}\n\t\tsteps.push(parseSingleTaskToken(t));\n\t}\n\tif (steps.length === 0) {\n\t\tthrow new SlashParseError(\"/chain expression must include at least one step\");\n\t}\n\treturn { steps };\n}\n\nconst parseAgentArgs = (\n\tstate: SubagentState,\n\targs: string,\n\tcommand: string,\n\tctx: ExtensionContext,\n): { steps: ParsedStep[]; task: string } | null => {\n\tconst input = args.trim();\n\tconst usage = `Usage: /${command} agent1 \"task1\" -> agent2 \"task2\"`;\n\tlet steps: ParsedStep[];\n\tlet sharedTask: string;\n\tlet perStep = false;\n\n\tif (input.includes(\" -> \")) {\n\t\tperStep = true;\n\t\tconst segments = input.split(\" -> \");\n\t\tsteps = [];\n\t\tfor (const seg of segments) {\n\t\t\tconst trimmed = seg.trim();\n\t\t\tif (!trimmed) continue;\n\t\t\tsteps.push(parseSingleTaskToken(trimmed));\n\t\t}\n\t\tsharedTask = steps.find((s) => s.task)?.task ?? \"\";\n\t} else {\n\t\tconst delimiterIndex = input.indexOf(\" -- \");\n\t\tif (delimiterIndex === -1) {\n\t\t\tctx.ui.notify(usage, \"error\");\n\t\t\treturn null;\n\t\t}\n\t\tconst agentsPart = input.slice(0, delimiterIndex).trim();\n\t\tsharedTask = input.slice(delimiterIndex + 4).trim();\n\t\tif (!agentsPart || !sharedTask) {\n\t\t\tctx.ui.notify(usage, \"error\");\n\t\t\treturn null;\n\t\t}\n\t\tsteps = agentsPart.split(/\\s+/).filter(Boolean).map((t) => parseSingleTaskToken(t));\n\t}\n\n\tif (steps.length === 0) {\n\t\tctx.ui.notify(usage, \"error\");\n\t\treturn null;\n\t}\n\tif (!state.baseCwd) {\n\t\tctx.ui.notify(\"Subagent session cwd is not initialized yet\", \"error\");\n\t\treturn null;\n\t}\n\tconst agents = discoverAgents(state.baseCwd, \"both\").agents;\n\tfor (const step of steps) {\n\t\tif (!agents.find((a) => a.name === step.name)) {\n\t\t\tctx.ui.notify(`Unknown agent: ${step.name}`, \"error\");\n\t\t\treturn null;\n\t\t}\n\t}\n\tif (command === \"chain\" && !steps[0]?.task && (perStep || !sharedTask)) {\n\t\tctx.ui.notify(`First step must have a task: /chain agent \"task\" -> agent2`, \"error\");\n\t\treturn null;\n\t}\n\tif (command === \"parallel\" && !steps.some((s) => s.task) && !sharedTask) {\n\t\tctx.ui.notify(\"At least one step must have a task\", \"error\");\n\t\treturn null;\n\t}\n\treturn { steps, task: sharedTask };\n};\n\ntype ChainStepObject = {\n\tagent: string;\n\ttask?: string;\n\toutput?: string | false;\n\toutputMode?: \"inline\" | \"file-only\";\n\treads?: string[] | false;\n\tmodel?: string;\n\tskill?: string[] | false;\n\tprogress?: boolean;\n\tas?: string;\n\tlabel?: string;\n\tphase?: string;\n\tcwd?: string;\n\tcount?: number;\n\toutputSchema?: JsonSchemaObject;\n\tacceptance?: string;\n};\n\nconst INLINE_ACCEPTANCE_LEVELS = new Set([\"auto\", \"attested\", \"checked\"]);\n\nfunction validateInlineAcceptanceInput(value: string, agent: string): void {\n\tconst errors = validateAcceptanceInput(value, `acceptance for step '${agent}'`);\n\tif (errors.length > 0) throw new SlashParseError(errors[0]!);\n\tif (!INLINE_ACCEPTANCE_LEVELS.has(value)) {\n\t\tthrow new SlashParseError(`Inline acceptance for step '${agent}' supports auto, attested, or checked. Use the subagent tool API or a saved .chain.json file for none or verified acceptance contracts; reviewed is inferred-only.`);\n\t}\n}\n\n// Load an inline `outputSchema=<path>` JSON file, resolved against the session cwd.\n// Throws (SlashParseError / fs / JSON) on a missing or malformed schema.\nfunction loadInlineOutputSchema(baseCwd: string, agent: string, value: string): JsonSchemaObject {\n\tconst schemaPath = path.isAbsolute(value) ? value : path.join(baseCwd, value);\n\tconst label = `outputSchema for step '${agent}' (${schemaPath})`;\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(fs.readFileSync(schemaPath, \"utf-8\"));\n\t} catch (error) {\n\t\tthrow new SlashParseError(`Cannot read ${label}: ${error instanceof Error ? error.message : String(error)}`);\n\t}\n\tassertJsonSchemaObject(parsed, label);\n\treturn parsed;\n}\n\n// Build a ChainStep object from a parsed token. `inGroup` enables `count` (parallel-only).\n// May throw SlashParseError for an invalid acceptance level or outputSchema path.\nconst mapParsedTaskToStepObject = (\n\tstep: ParsedStep,\n\tfallbackTask: string | undefined,\n\tisFirst: boolean,\n\topts: { baseCwd: string; inGroup: boolean },\n): ChainStepObject => {\n\tconst { name, config, task: stepTask } = step;\n\tif (config.acceptance !== undefined) validateInlineAcceptanceInput(config.acceptance, name);\n\treturn {\n\t\tagent: name,\n\t\t...(stepTask ? { task: stepTask } : isFirst && fallbackTask ? { task: fallbackTask } : {}),\n\t\t...(config.output !== undefined ? { output: config.output } : {}),\n\t\t...(config.outputMode !== undefined ? { outputMode: config.outputMode } : {}),\n\t\t...(config.reads !== undefined ? { reads: config.reads } : {}),\n\t\t...(config.model ? { model: config.model } : {}),\n\t\t...(config.skill !== undefined ? { skill: config.skill } : {}),\n\t\t...(config.progress !== undefined ? { progress: config.progress } : {}),\n\t\t...(config.as ? { as: config.as } : {}),\n\t\t...(config.label ? { label: config.label } : {}),\n\t\t...(config.phase ? { phase: config.phase } : {}),\n\t\t...(config.cwd ? { cwd: config.cwd } : {}),\n\t\t...(opts.inGroup && config.count !== undefined ? { count: config.count } : {}),\n\t\t...(config.outputSchema ? { outputSchema: loadInlineOutputSchema(opts.baseCwd, name, config.outputSchema) } : {}),\n\t\t...(config.acceptance ? { acceptance: config.acceptance } : {}),\n\t};\n};\n\nexport function buildChainExpressionSteps(\n\tstate: SubagentState,\n\tinput: string,\n\tctx: ExtensionContext,\n): { chain: ChainStep[]; task: string } | null {\n\tconst notify = (message: string) => ctx.ui.notify(message, \"error\");\n\tif (!hasGroupSyntax(input)) {\n\t\tconst parsed = parseAgentArgs(state, input, \"chain\", ctx);\n\t\tif (!parsed) return null;\n\t\tconst baseCwd = state.baseCwd!; // parseAgentArgs already verified baseCwd is set\n\t\ttry {\n\t\t\tconst chain: ChainStep[] = parsed.steps.map((step, i) =>\n\t\t\t\tmapParsedTaskToStepObject(step, parsed.task || undefined, i === 0, { baseCwd, inGroup: false }),\n\t\t\t);\n\t\t\treturn { chain, task: parsed.task };\n\t\t} catch (error) {\n\t\t\tnotify(error instanceof Error ? error.message : String(error));\n\t\t\treturn null;\n\t\t}\n\t}\n\n\tlet expression: { steps: ParsedGroupStep[] };\n\ttry {\n\t\texpression = parseChainExpression(input);\n\t} catch (error) {\n\t\tnotify(error instanceof Error ? error.message : String(error));\n\t\treturn null;\n\t}\n\tif (!state.baseCwd) {\n\t\tnotify(\"Subagent session cwd is not initialized yet\");\n\t\treturn null;\n\t}\n\tconst agents = discoverAgents(state.baseCwd, \"both\").agents;\n\tconst stepAgentNames = expression.steps.flatMap((step) =>\n\t\tstep.kind === \"group\" ? step.tasks.map((t) => t.name) : [step.name],\n\t);\n\tfor (const name of stepAgentNames) {\n\t\tif (!agents.find((a) => a.name === name)) {\n\t\t\tnotify(`Unknown agent: ${name}`);\n\t\t\treturn null;\n\t\t}\n\t}\n\t// Every task inside a parallel group needs its own task; there is no shared-task fallback.\n\tfor (const step of expression.steps) {\n\t\tif (step.kind === \"group\" && step.tasks.some((t) => !t.task)) {\n\t\t\tnotify('Each task in a parallel group needs a task: (agent \"a\" | agent \"b\")');\n\t\t\treturn null;\n\t\t}\n\t}\n\tconst firstStep = expression.steps[0]!;\n\tconst firstHasTask =\n\t\tfirstStep.kind === \"group\"\n\t\t\t? firstStep.tasks.some((t) => Boolean(t.task))\n\t\t\t: Boolean(firstStep.task);\n\tif (!firstHasTask) {\n\t\tnotify('First step must have a task: /chain agent \"task\" -> agent2');\n\t\treturn null;\n\t}\n\tconst sharedTask =\n\t\tfirstStep.kind === \"group\"\n\t\t\t? (firstStep.tasks.find((t) => t.task)?.task ?? \"\")\n\t\t\t: (firstStep.task ?? \"\");\n\tconst baseCwd = state.baseCwd;\n\tlet chain: ChainStep[];\n\ttry {\n\t\tchain = expression.steps.map((step) => {\n\t\t\tif (step.kind === \"group\") {\n\t\t\t\tconst parallel = step.tasks.map((t) => mapParsedTaskToStepObject(t, undefined, false, { baseCwd, inGroup: true }));\n\t\t\t\treturn {\n\t\t\t\t\tparallel,\n\t\t\t\t\t...(step.config.concurrency !== undefined ? { concurrency: step.config.concurrency } : {}),\n\t\t\t\t\t...(step.config.failFast !== undefined ? { failFast: step.config.failFast } : {}),\n\t\t\t\t\t...(step.config.worktree !== undefined ? { worktree: step.config.worktree } : {}),\n\t\t\t\t};\n\t\t\t}\n\t\t\treturn mapParsedTaskToStepObject(step, sharedTask || undefined, false, { baseCwd, inGroup: false });\n\t\t});\n\t} catch (error) {\n\t\tnotify(error instanceof Error ? error.message : String(error));\n\t\treturn null;\n\t}\n\treturn { chain, task: sharedTask };\n}\n\nexport function registerSlashCommands(\n\tpi: ExtensionAPI,\n\tstate: SubagentState,\n): void {\n\tlet fleetOpen = false;\n\tconst showFleet = async (ctx: ExtensionContext) => {\n\t\tstate.lastUiContext = ctx;\n\t\tif (!ctx.hasUI) {\n\t\t\tawait runSlashSubagent(pi, ctx, { action: \"status\", view: \"fleet\" });\n\t\t\treturn;\n\t\t}\n\t\tif (fleetOpen) {\n\t\t\tctx.ui.notify(\"Subagent fleet inspector is already open.\", \"info\");\n\t\t\treturn;\n\t\t}\n\t\tfleetOpen = true;\n\t\ttry {\n\t\t\tawait openSubagentFleet(ctx, state);\n\t\t} finally {\n\t\t\tfleetOpen = false;\n\t\t}\n\t};\n\n\tpi.registerCommand(\"run\", {\n\t\tdescription: \"Run a subagent directly: /run agent[output=file] [task] [--bg] [--fork]\",\n\t\tgetArgumentCompletions: makeAgentCompletions(state, false),\n\t\thandler: async (args, ctx) => {\n\t\t\tconst { args: cleanedArgs, bg, fork } = extractExecutionFlags(args);\n\t\t\tconst input = cleanedArgs.trim();\n\t\t\tconst firstSpace = input.indexOf(\" \");\n\t\t\tif (!input) { ctx.ui.notify(\"Usage: /run <agent> [task] [--bg] [--fork]\", \"error\"); return; }\n\t\t\tconst { name: agentName, config: inline } = parseAgentToken(firstSpace === -1 ? input : input.slice(0, firstSpace));\n\t\t\tconst task = firstSpace === -1 ? \"\" : input.slice(firstSpace + 1).trim();\n\n\t\t\tif (!state.baseCwd) { ctx.ui.notify(\"Subagent session cwd is not initialized yet\", \"error\"); return; }\n\t\t\tconst agents = discoverAgents(state.baseCwd, \"both\").agents;\n\t\t\tif (!agents.find((a) => a.name === agentName)) { ctx.ui.notify(`Unknown agent: ${agentName}`, \"error\"); return; }\n\n\t\t\tlet finalTask = task;\n\t\t\tif (inline.reads && Array.isArray(inline.reads) && inline.reads.length > 0) {\n\t\t\t\tfinalTask = `[Read from: ${inline.reads.join(\", \")}]\\n\\n${finalTask}`;\n\t\t\t}\n\t\t\tconst params: SubagentParamsLike = { agent: agentName, task: finalTask, clarify: false, agentScope: \"both\" };\n\t\t\tif (inline.output !== undefined) params.output = inline.output;\n\t\t\tif (inline.outputMode !== undefined) params.outputMode = inline.outputMode;\n\t\t\tif (inline.skill !== undefined) params.skill = inline.skill;\n\t\t\tif (inline.model) params.model = inline.model;\n\t\t\tif (bg) params.async = true;\n\t\t\tif (fork) params.context = \"fork\";\n\t\t\tawait runSlashSubagent(pi, ctx, params);\n\t\t},\n\t});\n\n\tpi.registerCommand(\"chain\", {\n\t\tdescription: \"Run agents in sequence: /chain scout \\\"task\\\" -> planner [--bg] [--fork]\",\n\t\tgetArgumentCompletions: makeAgentCompletions(state, true),\n\t\thandler: async (args, ctx) => {\n\t\t\tconst { args: cleanedArgs, bg, fork } = extractExecutionFlags(args);\n\t\t\tconst built = buildChainExpressionSteps(state, cleanedArgs, ctx);\n\t\t\tif (!built) return;\n\t\t\tconst params: SubagentParamsLike = { chain: built.chain, task: built.task, clarify: false, agentScope: \"both\" };\n\t\t\tif (bg) params.async = true;\n\t\t\tif (fork) params.context = \"fork\";\n\t\t\tawait runSlashSubagent(pi, ctx, params);\n\t\t},\n\t});\n\n\tpi.registerCommand(\"run-chain\", {\n\t\tdescription: \"Run a saved chain: /run-chain chainName -- task [--bg] [--fork]\",\n\t\tgetArgumentCompletions: makeChainCompletions(state),\n\t\thandler: async (args, ctx) => {\n\t\t\tconst { args: cleanedArgs, bg, fork } = extractExecutionFlags(args);\n\t\t\tconst delimiterIndex = cleanedArgs.indexOf(\" -- \");\n\t\t\tconst usage = \"Usage: /run-chain <chainName> -- <task> [--bg] [--fork]\";\n\t\t\tif (delimiterIndex === -1) {\n\t\t\t\tctx.ui.notify(usage, \"error\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst chainName = cleanedArgs.slice(0, delimiterIndex).trim();\n\t\t\tconst task = cleanedArgs.slice(delimiterIndex + 4).trim();\n\t\t\tif (!chainName || !task) {\n\t\t\t\tctx.ui.notify(usage, \"error\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (!state.baseCwd) { ctx.ui.notify(\"Subagent session cwd is not initialized yet\", \"error\"); return; }\n\t\t\tconst chain = discoverSavedChains(state.baseCwd).find((candidate) => candidate.name === chainName);\n\t\t\tif (!chain) {\n\t\t\t\tctx.ui.notify(`Unknown chain: ${chainName}`, \"error\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst params: SubagentParamsLike = { chain: mapSavedChainSteps(chain), task, clarify: false, agentScope: \"both\" };\n\t\t\tif (bg) params.async = true;\n\t\t\tif (fork) params.context = \"fork\";\n\t\t\tawait runSlashSubagent(pi, ctx, params);\n\t\t},\n\t});\n\n\tpi.registerCommand(\"parallel\", {\n\t\tdescription: \"Run agents in parallel: /parallel scout \\\"task1\\\" -> reviewer \\\"task2\\\" [--bg] [--fork]\",\n\t\tgetArgumentCompletions: makeAgentCompletions(state, true),\n\t\thandler: async (args, ctx) => {\n\t\t\tconst { args: cleanedArgs, bg, fork } = extractExecutionFlags(args);\n\t\t\tconst parsed = parseAgentArgs(state, cleanedArgs, \"parallel\", ctx);\n\t\t\tif (!parsed) return;\n\t\t\tconst tasks = parsed.steps.map(({ name, config, task: stepTask }) => ({\n\t\t\t\tagent: name,\n\t\t\t\ttask: stepTask ?? parsed.task,\n\t\t\t\t...(config.output !== undefined ? { output: config.output } : {}),\n\t\t\t\t...(config.outputMode !== undefined ? { outputMode: config.outputMode } : {}),\n\t\t\t\t...(config.reads !== undefined ? { reads: config.reads } : {}),\n\t\t\t\t...(config.model ? { model: config.model } : {}),\n\t\t\t\t...(config.skill !== undefined ? { skill: config.skill } : {}),\n\t\t\t\t...(config.progress !== undefined ? { progress: config.progress } : {}),\n\t\t\t}));\n\t\t\tconst params: SubagentParamsLike = { tasks, clarify: false, agentScope: \"both\" };\n\t\t\tif (bg) params.async = true;\n\t\t\tif (fork) params.context = \"fork\";\n\t\t\tawait runSlashSubagent(pi, ctx, params);\n\t\t},\n\t});\n\n\tpi.registerCommand(\"subagent-cost\", {\n\t\tdescription: \"Show parent and subagent child usage cost for this session\",\n\t\thandler: async (_args, ctx) => {\n\t\t\tsendSlashText(pi, buildSubagentCostReport(ctx));\n\t\t},\n\t});\n\n\tpi.registerCommand(\"subagents-doctor\", {\n\t\tdescription: \"Show subagent diagnostics\",\n\t\thandler: async (_args, ctx) => {\n\t\t\tawait runSlashSubagent(pi, ctx, { action: \"doctor\" });\n\t\t},\n\t});\n\n\tpi.registerCommand(\"subagents-fleet\", {\n\t\tdescription: \"Open the live, inspection-only subagent fleet\",\n\t\thandler: async (_args, ctx) => showFleet(ctx),\n\t});\n\n\tpi.registerShortcut(Key.ctrlAlt(\"f\"), {\n\t\tdescription: \"Open subagent fleet inspector\",\n\t\thandler: async (ctx) => showFleet(ctx),\n\t});\n\n\tpi.registerCommand(\"subagents-stop\", {\n\t\tdescription: \"Stop a current-session async subagent run\",\n\t\thandler: async (args, ctx) => {\n\t\t\tconst id = args.trim();\n\t\t\tif (id) {\n\t\t\t\tawait runSlashSubagent(pi, ctx, { action: \"stop\", id });\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (process.env[SUBAGENT_FANOUT_CHILD_ENV] === \"1\") {\n\t\t\t\tsendSlashText(pi, \"Selector unavailable in child-safe fanout mode. Pass an explicit current-session top-level async run id, for example `/subagents-stop <run-id>` or `subagent({ action: \\\"stop\\\", id: \\\"<run-id>\\\" })`.\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tlet targets: StopSelectorTarget[];\n\t\t\ttry {\n\t\t\t\ttargets = discoverStopTargets(ctx, state);\n\t\t\t} catch (error) {\n\t\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\t\tctx.ui.notify(message, \"error\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (!ctx.hasUI) {\n\t\t\t\tsendSlashText(pi, stopFallbackText(targets));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (targets.length === 0) {\n\t\t\t\tctx.ui.notify(\"No active current-session async runs or scheduled subagent runs to stop.\", \"info\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst result = await ctx.ui.custom<StopSelectorResult>(\n\t\t\t\t(tui, theme, _kb, done) => new SubagentsStopSelector(tui, theme, targets, done),\n\t\t\t\t{ overlay: true, overlayOptions: { anchor: \"center\", width: 88, maxHeight: \"80%\" } },\n\t\t\t);\n\t\t\tif (!result?.confirmed || !result.target) return;\n\t\t\tif (result.target.kind === \"scheduled\") {\n\t\t\t\tawait runSlashSubagent(pi, ctx, { action: \"schedule-cancel\", id: result.target.id });\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tawait runSlashSubagent(pi, ctx, { action: \"stop\", id: result.target.id });\n\t\t},\n\t});\n\n\tregisterPromptWorkflowCommands({\n\t\tpi,\n\t\trun: (params, ctx) => runSlashSubagent(pi, ctx, params),\n\t});\n\n\tpi.registerCommand(\"subagents-models\", {\n\t\tdescription: \"Show runtime-loaded builtin subagent models\",\n\t\tgetArgumentCompletions: makeBuiltinAgentNameCompletions(),\n\t\thandler: async (args, ctx) => {\n\t\t\tconst trimmed = args.trim();\n\t\t\tif (!trimmed) {\n\t\t\t\tawait runSlashSubagent(pi, ctx, { action: \"models\" });\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst parts = trimmed.split(/\\s+/).filter(Boolean);\n\t\t\tif (parts.length !== 1) {\n\t\t\t\tctx.ui.notify(\"Usage: /subagents-models [builtin-agent-name]\", \"error\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst agent = parts[0]!;\n\t\t\tif (!(BUILTIN_AGENT_NAMES as readonly string[]).includes(agent)) {\n\t\t\t\tctx.ui.notify(`Unknown builtin agent: ${agent}`, \"error\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tawait runSlashSubagent(pi, ctx, { action: \"models\", agent });\n\t\t},\n\t});\n\n\tpi.registerCommand(\"subagents-profiles\", {\n\t\tdescription: \"List saved subagent profiles\",\n\t\thandler: async (_args, _ctx) => {\n\t\t\tconst profiles = listSubagentProfiles();\n\t\t\tif (profiles.length === 0) {\n\t\t\t\tsendSlashText(pi, \"Subagent profiles\\n\\nNo subagent profiles found in ~/.pi/agent/profiles/pi-subagents/\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tsendSlashText(pi, `Subagent profiles\\n\\n${profiles.join(\"\\n\")}`);\n\t\t},\n\t});\n\n\tpi.registerCommand(\"subagents-load-profile\", {\n\t\tdescription: \"Load a subagent profile into ~/.pi/agent/settings.json\",\n\t\tgetArgumentCompletions: (prefix) => {\n\t\t\tif (prefix.includes(\" \")) return null;\n\t\t\treturn listSubagentProfiles()\n\t\t\t\t.filter((name) => name.startsWith(prefix))\n\t\t\t\t.map((name) => ({ value: name, label: name }));\n\t\t},\n\t\thandler: async (args, ctx) => {\n\t\t\tconst parsed = parseSingleRequiredArg(args, \"Usage: /subagents-load-profile <name>\");\n\t\t\tif (!parsed.ok) {\n\t\t\t\tctx.ui.notify(parsed.message, \"error\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tawait withSlashStatus(ctx, `Loading profile ${parsed.value}…`, async () => {\n\t\t\t\t\tconst { profile } = readSubagentProfile(parsed.value);\n\t\t\t\t\tconst workerModel = getProfileWorkerModel(profile);\n\t\t\t\t\tconst result = applySubagentProfile(parsed.value);\n\t\t\t\t\tconst lines = [\n\t\t\t\t\t\t`Loaded subagent profile: ${parsed.value}`,\n\t\t\t\t\t\t`Profile: ${result.filePath}`,\n\t\t\t\t\t\t`Updated: ${result.settingsPath}`,\n\t\t\t\t\t];\n\n\t\t\t\t\tif (workerModel && typeof pi.setModel === \"function\" && typeof ctx.modelRegistry?.find === \"function\" && typeof ctx.modelRegistry?.getAvailable === \"function\") {\n\t\t\t\t\t\tconst shouldSwitch = await ctx.ui.confirm(\n\t\t\t\t\t\t\t\"\",\n\t\t\t\t\t\t\t`Profile loaded. Also switch this session to the profile worker model?\\n\\n${workerModel}`,\n\t\t\t\t\t\t);\n\t\t\t\t\t\tif (shouldSwitch) {\n\t\t\t\t\t\t\tconst modelInfo = findModelInfo(workerModel, ctx.modelRegistry.getAvailable().map(toModelInfo));\n\t\t\t\t\t\t\tconst model = modelInfo ? ctx.modelRegistry.find(modelInfo.provider, modelInfo.id) : undefined;\n\t\t\t\t\t\t\tif (!modelInfo || !model) {\n\t\t\t\t\t\t\t\tlines.push(`Could not switch current session model: '${workerModel}' is not available in the current model registry.`);\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tconst success = await pi.setModel(model);\n\t\t\t\t\t\t\t\tif (success) lines.push(`Current session model switched to: ${modelInfo.fullId}`);\n\t\t\t\t\t\t\t\telse lines.push(`Could not switch current session model to '${workerModel}': no API key or provider access is available.`);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (workerModel) {\n\t\t\t\t\t\tlines.push(`Profile worker model: ${workerModel}`);\n\t\t\t\t\t}\n\n\t\t\t\t\tsendSlashText(pi, lines.join(\"\\n\"));\n\t\t\t\t});\n\t\t\t} catch (error) {\n\t\t\t\tctx.ui.notify(error instanceof Error ? error.message : String(error), \"error\");\n\t\t\t}\n\t\t},\n\t});\n\n\tpi.registerCommand(\"subagents-refresh-provider-models\", {\n\t\tdescription: \"Refresh the cached model catalog for one provider\",\n\t\tgetArgumentCompletions: makeProviderCompletions(state),\n\t\thandler: async (args, ctx) => {\n\t\t\tconst trimmed = args.trim();\n\t\t\tconst force = /(?:^|\\s)--force$/.test(trimmed) || /(?:^|\\s)force$/.test(trimmed);\n\t\t\tconst withoutForce = trimmed.replace(/(?:^|\\s)(?:--force|force)$/, \"\").trim();\n\t\t\tconst parsed = parseSingleRequiredArg(withoutForce, \"Usage: /subagents-refresh-provider-models <provider> [--force]\");\n\t\t\tif (!parsed.ok) {\n\t\t\t\tctx.ui.notify(parsed.message, \"error\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tawait withSlashStatus(ctx, `Refreshing provider models for ${parsed.value}…`, async () => {\n\t\t\t\t\tconst result = await refreshProviderModelCatalog(pi, ctx, parsed.value, { force, maxAgeDays: DEFAULT_PROVIDER_MODELS_MAX_AGE_DAYS });\n\t\t\t\t\tconst lines = [\n\t\t\t\t\t\t\"Provider model catalog\",\n\t\t\t\t\t\t`Provider: ${parsed.value}`,\n\t\t\t\t\t\t`Status: ${result.reused ? \"fresh cache reused\" : \"refreshed\"}`,\n\t\t\t\t\t\t`File: ${result.filePath}`,\n\t\t\t\t\t\t`Models: ${result.catalog.models.length}`,\n\t\t\t\t\t\t`Refreshed at: ${result.catalog.refreshedAt}`,\n\t\t\t\t\t];\n\t\t\t\t\tif (result.heuristicFallbackCount > 0) {\n\t\t\t\t\t\tlines.push(`Warning: ${result.heuristicFallbackCount} model${result.heuristicFallbackCount === 1 ? \" was\" : \"s were\"} classified with name heuristics fallback.`);\n\t\t\t\t\t}\n\t\t\t\t\tsendSlashText(pi, lines.join(\"\\n\"));\n\t\t\t\t});\n\t\t\t} catch (error) {\n\t\t\t\tctx.ui.notify(error instanceof Error ? error.message : String(error), \"error\");\n\t\t\t}\n\t\t},\n\t});\n\n\tpi.registerCommand(\"subagents-generate-profiles\", {\n\t\tdescription: \"Generate <provider>.quota and <provider>.quality subagent profiles\",\n\t\tgetArgumentCompletions: makeProviderCompletions(state),\n\t\thandler: async (args, ctx) => {\n\t\t\tconst parsed = parseSingleRequiredArg(args, \"Usage: /subagents-generate-profiles <provider>\");\n\t\t\tif (!parsed.ok) {\n\t\t\t\tctx.ui.notify(parsed.message, \"error\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tawait withSlashStatus(ctx, `Generating profiles for ${parsed.value}…`, async () => {\n\t\t\t\t\tconst result = await generateProfilesForProvider(pi, ctx, parsed.value, { maxAgeDays: DEFAULT_PROVIDER_MODELS_MAX_AGE_DAYS });\n\t\t\t\t\tconst lines = [\n\t\t\t\t\t\t\"Generated subagent profiles\",\n\t\t\t\t\t\t`Provider: ${parsed.value}`,\n\t\t\t\t\t\t`Catalog: ${result.catalogPath}`,\n\t\t\t\t\t\t`Quota: ${result.quotaPath}`,\n\t\t\t\t\t\t` cheap=${result.quotaModels.cheap}`,\n\t\t\t\t\t\t` medium=${result.quotaModels.medium}`,\n\t\t\t\t\t\t` strong=${result.quotaModels.strong}`,\n\t\t\t\t\t\t`Quality: ${result.qualityPath}`,\n\t\t\t\t\t\t` cheap=${result.qualityModels.cheap}`,\n\t\t\t\t\t\t` medium=${result.qualityModels.medium}`,\n\t\t\t\t\t\t` strong=${result.qualityModels.strong}`,\n\t\t\t\t\t];\n\t\t\t\t\tif (result.selectedHeuristicFallbackCount > 0) {\n\t\t\t\t\t\tlines.push(`Warning: generated profiles depend on heuristic-only classification for ${result.selectedHeuristicFallbackCount} selected model${result.selectedHeuristicFallbackCount === 1 ? \"\" : \"s\"}.`);\n\t\t\t\t\t} else if (result.heuristicFallbackCount > 0) {\n\t\t\t\t\t\tlines.push(`Warning: provider catalog still contains ${result.heuristicFallbackCount} heuristic-classified model${result.heuristicFallbackCount === 1 ? \"\" : \"s\"}.`);\n\t\t\t\t\t}\n\t\t\t\t\tsendSlashText(pi, lines.join(\"\\n\"));\n\t\t\t\t});\n\t\t\t} catch (error) {\n\t\t\t\tctx.ui.notify(error instanceof Error ? error.message : String(error), \"error\");\n\t\t\t}\n\t\t},\n\t});\n\n\tpi.registerCommand(\"subagents-check-profile\", {\n\t\tdescription: \"Check whether a saved profile still points to usable models\",\n\t\tgetArgumentCompletions: (prefix) => {\n\t\t\tif (prefix.includes(\" \")) return null;\n\t\t\treturn listSubagentProfiles()\n\t\t\t\t.filter((name) => name.startsWith(prefix))\n\t\t\t\t.map((name) => ({ value: name, label: name }));\n\t\t},\n\t\thandler: async (args, ctx) => {\n\t\t\tconst parsed = parseSingleRequiredArg(args, \"Usage: /subagents-check-profile <name>\");\n\t\t\tif (!parsed.ok) {\n\t\t\t\tctx.ui.notify(parsed.message, \"error\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tawait withSlashStatus(ctx, `Checking profile ${parsed.value}…`, async () => {\n\t\t\t\t\tconst result = await checkSubagentProfile(pi, ctx, parsed.value);\n\t\t\t\t\tconst lines = [\n\t\t\t\t\t\t\"Subagent profile check\",\n\t\t\t\t\t\t`Profile: ${result.profileName}`,\n\t\t\t\t\t\t`File: ${result.filePath}`,\n\t\t\t\t\t\t\"\",\n\t\t\t\t\t\t...result.results.map((entry) => `${entry.agent} → ${entry.model} — registry ${entry.inRegistry ? \"ok\" : \"missing\"}; probe ${entry.probe.status}${entry.probe.message ? ` (${entry.probe.message.split(/\\r?\\n/, 1)[0]})` : \"\"}`),\n\t\t\t\t\t];\n\t\t\t\t\tsendSlashText(pi, lines.join(\"\\n\"));\n\t\t\t\t});\n\t\t\t} catch (error) {\n\t\t\t\tctx.ui.notify(error instanceof Error ? error.message : String(error), \"error\");\n\t\t\t}\n\t\t},\n\t});\n\n}\n"]}
1
+ {"version":3,"file":"slash-commands.d.ts","sourceRoot":"","sources":["../../../../../src/builtin-extensions/pi-subagents/src/slash/slash-commands.ts"],"names":[],"mappings":"AAIA,OAAO,EAAW,KAAK,YAAY,EAAE,KAAK,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AAapG,OAAO,EAAyC,KAAK,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAkB9F,OAAO,EAYN,KAAK,aAAa,EAElB,MAAM,oBAAoB,CAAC;AAE5B,UAAU,YAAY;IACrB,MAAM,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;IACxB,UAAU,CAAC,EAAE,QAAQ,GAAG,WAAW,CAAC;IACpC,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,KAAK,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,KAAK,CAAC;IACzB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;CACpB;AAmqBD,MAAM,WAAW,WAAW;IAAG,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAE;AAC7F,MAAM,WAAW,UAAU;IAAG,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,YAAY,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE;AAC/F,MAAM,WAAW,WAAW;IAAG,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,UAAU,EAAE,CAAC;IAAC,MAAM,EAAE,WAAW,CAAA;CAAE;AACxF,MAAM,MAAM,eAAe,GAAG,UAAU,GAAG,WAAW,CAAC;AAEvD,eAAO,MAAM,oBAAoB,kFACyC,CAAC;AAE3E,qBAAa,eAAgB,SAAQ,KAAK;CAAG;AA4D7C,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,CAiB9D;AA0CD,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,WAAW,CAW9D;AAOD,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAErD;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,eAAe,EAAE,CAAA;CAAE,CAyBhF;AAyID,wBAAgB,yBAAyB,CACxC,KAAK,EAAE,aAAa,EACpB,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,gBAAgB,GACnB;IAAE,KAAK,EAAE,SAAS,EAAE,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CA8E7C;AAED,wBAAgB,qBAAqB,CACpC,EAAE,EAAE,YAAY,EAChB,KAAK,EAAE,aAAa,GAClB,IAAI,CA2XN","sourcesContent":["// @ts-nocheck\nimport { randomUUID } from \"node:crypto\";\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport { keyText, type ExtensionAPI, type ExtensionContext } from \"@ashx-j/lunr\";\nimport { Key, matchesKey, type Component, type TUI } from \"@ashx-j/lunr-tui\";\nimport { BUILTIN_AGENT_NAMES, discoverAgents, discoverAgentsAll, type ChainConfig } from \"../agents/agents.ts\";\nimport {\n\tDEFAULT_PROVIDER_MODELS_MAX_AGE_DAYS,\n\tapplySubagentProfile,\n\tcheckSubagentProfile,\n\tgenerateProfilesForProvider,\n\tlistSubagentProfiles,\n\treadSubagentProfile,\n\trefreshProviderModelCatalog,\n} from \"../profiles/profiles.ts\";\nimport type { SubagentParamsLike } from \"../runs/foreground/subagent-executor.ts\";\nimport { isDynamicParallelStep, isParallelStep, type ChainStep } from \"../shared/settings.ts\";\nimport { findModelInfo, toModelInfo } from \"../shared/model-info.ts\";\nimport { formatTokens, shortenPath } from \"../shared/formatters.ts\";\nimport { listAsyncRuns, formatAsyncRunProgressLabel, type AsyncRunSummary } from \"../runs/background/async-status.ts\";\nimport { scheduledRunStorePath } from \"../runs/background/scheduled-runs.ts\";\nimport { SUBAGENT_FANOUT_CHILD_ENV } from \"../runs/shared/pi-args.ts\";\nimport { assertJsonSchemaObject } from \"../runs/shared/structured-output.ts\";\nimport { validateAcceptanceInput } from \"../runs/shared/acceptance.ts\";\nimport type { SlashSubagentResponse, SlashSubagentUpdate } from \"./slash-bridge.ts\";\nimport { registerPromptWorkflowCommands } from \"./prompt-workflows.ts\";\nimport { openSubagentFleet } from \"../tui/fleet.ts\";\nimport {\n\tapplySlashUpdate,\n\tbuildSlashInitialResult,\n\tfailSlashResult,\n\tfinalizeSlashResult,\n\tresolveSlashMessageDetails,\n} from \"./slash-live-state.ts\";\nimport {\n\tSLASH_RESULT_TYPE,\n\tSLASH_TEXT_RESULT_TYPE,\n\tSLASH_SUBAGENT_CANCEL_EVENT,\n\tSLASH_SUBAGENT_REQUEST_EVENT,\n\tSLASH_SUBAGENT_RESPONSE_EVENT,\n\tSLASH_SUBAGENT_STARTED_EVENT,\n\tSLASH_SUBAGENT_UPDATE_EVENT,\n\tASYNC_DIR,\n\ttype Details,\n\ttype JsonSchemaObject,\n\ttype SingleResult,\n\ttype SubagentState,\n\ttype Usage,\n} from \"../shared/types.ts\";\n\ninterface InlineConfig {\n\toutput?: string | false;\n\toutputMode?: \"inline\" | \"file-only\";\n\treads?: string[] | false;\n\tmodel?: string;\n\tskill?: string[] | false;\n\tprogress?: boolean;\n\tas?: string;\n\tlabel?: string;\n\tphase?: string;\n\tcwd?: string;\n\tcount?: number;\n\toutputSchema?: string;\n\tacceptance?: string;\n}\n\nconst parseInlineConfig = (raw: string): InlineConfig => {\n\tconst config: InlineConfig = {};\n\tfor (const part of raw.split(\",\")) {\n\t\tconst trimmed = part.trim();\n\t\tif (!trimmed) continue;\n\t\tconst eq = trimmed.indexOf(\"=\");\n\t\tif (eq === -1) {\n\t\t\tif (trimmed === \"progress\") config.progress = true;\n\t\t\tcontinue;\n\t\t}\n\t\tconst key = trimmed.slice(0, eq).trim();\n\t\tconst val = trimmed.slice(eq + 1).trim();\n\t\tswitch (key) {\n\t\t\tcase \"output\": config.output = val === \"false\" ? false : val; break;\n\t\t\tcase \"outputMode\": if (val === \"inline\" || val === \"file-only\") config.outputMode = val; break;\n\t\t\tcase \"reads\": config.reads = val === \"false\" ? false : val.split(\"+\").filter(Boolean); break;\n\t\t\tcase \"model\": config.model = val || undefined; break;\n\t\t\tcase \"skill\": case \"skills\": config.skill = val === \"false\" ? false : val.split(\"+\").filter(Boolean); break;\n\t\t\tcase \"progress\": config.progress = val !== \"false\"; break;\n\t\t\tcase \"as\": config.as = val || undefined; break;\n\t\t\tcase \"label\": config.label = val || undefined; break;\n\t\t\tcase \"phase\": config.phase = val || undefined; break;\n\t\t\tcase \"cwd\": config.cwd = val || undefined; break;\n\t\t\tcase \"count\": { const n = Number(val); if (Number.isInteger(n) && n > 0) config.count = n; break; }\n\t\t\tcase \"outputSchema\": config.outputSchema = val || undefined; break;\n\t\t\tcase \"acceptance\": config.acceptance = val || undefined; break;\n\t\t}\n\t}\n\treturn config;\n};\n\nconst parseAgentToken = (token: string): { name: string; config: InlineConfig } => {\n\tconst bracket = token.indexOf(\"[\");\n\tif (bracket === -1) return { name: token, config: {} };\n\tconst end = token.lastIndexOf(\"]\");\n\treturn { name: token.slice(0, bracket), config: parseInlineConfig(token.slice(bracket + 1, end !== -1 ? end : undefined)) };\n};\n\nconst extractExecutionFlags = (rawArgs: string): { args: string; bg: boolean; fork: boolean } => {\n\tlet args = rawArgs.trim();\n\tlet bg = false;\n\tlet fork = false;\n\n\twhile (true) {\n\t\tif (args.endsWith(\" --bg\") || args === \"--bg\") {\n\t\t\tbg = true;\n\t\t\targs = args === \"--bg\" ? \"\" : args.slice(0, -5).trim();\n\t\t\tcontinue;\n\t\t}\n\t\tif (args.endsWith(\" --fork\") || args === \"--fork\") {\n\t\t\tfork = true;\n\t\t\targs = args === \"--fork\" ? \"\" : args.slice(0, -7).trim();\n\t\t\tcontinue;\n\t\t}\n\t\tbreak;\n\t}\n\n\treturn { args, bg, fork };\n};\n\nconst makeAgentCompletions = (state: SubagentState, multiAgent: boolean) => (prefix: string) => {\n\tif (!state.baseCwd) return null;\n\tconst agents = discoverAgents(state.baseCwd, \"both\").agents;\n\tif (!multiAgent) {\n\t\tif (prefix.includes(\" \")) return null;\n\t\treturn agents.filter((a) => a.name.startsWith(prefix)).map((a) => ({ value: a.name, label: a.name }));\n\t}\n\n\t// Find the start of the current chain step: after the last top-level `->` arrow or `(`,\n\t// or after a `|` *inside* a group. A `|` at depth 0 is plain task text (only `(` opens a\n\t// group), so it must not restart agent completion — otherwise `scout -- do x | wr` would\n\t// wrongly resume suggesting agents past the `--` task. Quotes are tracked so separators\n\t// inside a task are ignored.\n\tlet inSingle = false, inDouble = false, depth = 0, segStart = 0;\n\tfor (let i = 0; i < prefix.length; i++) {\n\t\tconst ch = prefix[i]!;\n\t\tif (inSingle) { if (ch === \"'\") inSingle = false; continue; }\n\t\tif (inDouble) { if (ch === '\"') inDouble = false; continue; }\n\t\tif (ch === \"'\") { inSingle = true; continue; }\n\t\tif (ch === '\"') { inDouble = true; continue; }\n\t\tif (ch === \"(\") {\n\t\t\tif (!prefix.slice(segStart, i).includes(\" -- \")) {\n\t\t\t\tdepth++;\n\t\t\t\tsegStart = i + 1;\n\t\t\t}\n\t\t}\n\t\telse if (ch === \")\") {\n\t\t\tif (depth > 0) {\n\t\t\t\tdepth--;\n\t\t\t\tsegStart = i + 1;\n\t\t\t}\n\t\t}\n\t\telse if (ch === \"|\" && depth > 0) segStart = i + 1;\n\t\telse if (ch === \">\" && prefix[i - 1] === \"-\" && depth === 0) segStart = i + 1;\n\t}\n\t// Inside an open quote, or once the task has started (`--` / a quote), we are no\n\t// longer typing an agent name.\n\tif (inSingle || inDouble) return null;\n\tconst segment = prefix.slice(segStart);\n\tif (segment.includes(\" -- \") || segment.includes('\"') || segment.includes(\"'\")) return null;\n\n\tconst lastWord = (segment.match(/(\\S*)$/) || [\"\", \"\"])[1];\n\tlet beforeLastWord = prefix.slice(0, prefix.length - lastWord.length);\n\t// A bare `->` or `|` just typed (no trailing space) needs a separating space;\n\t// `(` glues naturally to the agent name.\n\tif (lastWord === \"\" && /[>|]$/.test(beforeLastWord)) beforeLastWord = `${beforeLastWord} `;\n\n\treturn agents.filter((a) => a.name.startsWith(lastWord)).map((a) => ({ value: `${beforeLastWord}${a.name}`, label: a.name }));\n};\n\nconst discoverSavedChains = (cwd: string): ChainConfig[] => {\n\tconst chainsByName = new Map<string, ChainConfig>();\n\tfor (const chain of discoverAgentsAll(cwd).chains) {\n\t\tchainsByName.set(chain.name, chain);\n\t}\n\treturn Array.from(chainsByName.values());\n};\n\nconst makeChainCompletions = (state: SubagentState) => (prefix: string) => {\n\tif (prefix.includes(\" \") || !state.baseCwd) return null;\n\treturn discoverSavedChains(state.baseCwd)\n\t\t.filter((chain) => chain.name.startsWith(prefix))\n\t\t.map((chain) => ({ value: chain.name, label: chain.name }));\n};\n\nconst makeBuiltinAgentNameCompletions = () => (prefix: string) => {\n\tif (prefix.includes(\" \")) return null;\n\treturn BUILTIN_AGENT_NAMES\n\t\t.filter((name) => name.startsWith(prefix))\n\t\t.map((name) => ({ value: name, label: name }));\n};\n\nconst makeProviderCompletions = (state: SubagentState) => (prefix: string) => {\n\tif (prefix.includes(\" \")) return null;\n\tconst available = state.lastUiContext?.modelRegistry?.getAvailable?.();\n\tif (!Array.isArray(available)) return null;\n\tconst providers = [...new Set(available\n\t\t.map((model) => typeof model?.provider === \"string\" ? model.provider : \"\")\n\t\t.filter(Boolean))]\n\t\t.sort((a, b) => a.localeCompare(b));\n\treturn providers\n\t\t.filter((provider) => provider.startsWith(prefix))\n\t\t.map((provider) => ({ value: provider, label: provider }));\n};\n\nfunction sendSlashText(pi: ExtensionAPI, text: string): void {\n\tpi.sendMessage({ customType: SLASH_TEXT_RESULT_TYPE, content: text, display: true });\n}\n\nasync function withSlashStatus<T>(\n\tctx: ExtensionContext,\n\ttext: string,\n\trun: () => Promise<T>,\n): Promise<T> {\n\tif (ctx.hasUI) ctx.ui.setStatus(\"subagent-slash-text\", text);\n\ttry {\n\t\treturn await run();\n\t} finally {\n\t\tif (ctx.hasUI) ctx.ui.setStatus(\"subagent-slash-text\", undefined);\n\t}\n}\n\ntype Theme = ExtensionContext[\"ui\"][\"theme\"];\n\ntype StopSelectorTarget = {\n\tkind: \"async\" | \"scheduled\";\n\tid: string;\n\tlabel: string;\n\tdetail: string;\n\tactionLabel: string;\n};\n\ntype StopSelectorResult = { confirmed: boolean; target?: StopSelectorTarget };\n\nfunction commandForTarget(target: StopSelectorTarget): string {\n\treturn target.kind === \"scheduled\"\n\t\t? `subagent({ action: \"schedule-cancel\", id: ${JSON.stringify(target.id)} })`\n\t\t: `subagent({ action: \"stop\", id: ${JSON.stringify(target.id)} })`;\n}\n\nfunction formatAsyncStopTarget(run: AsyncRunSummary): StopSelectorTarget {\n\tconst progress = formatAsyncRunProgressLabel(run);\n\tconst cwd = run.cwd ? shortenPath(run.cwd) : shortenPath(run.asyncDir);\n\treturn {\n\t\tkind: \"async\",\n\t\tid: run.id,\n\t\tlabel: `${run.id} · ${run.mode} · ${progress}`,\n\t\tdetail: `${run.state} · ${cwd}`,\n\t\tactionLabel: \"stop async run\",\n\t};\n}\n\nfunction scheduledStopTargets(ctx: ExtensionContext, state: SubagentState): StopSelectorTarget[] {\n\tconst sessionId = state.currentSessionId ?? ctx.sessionManager.getSessionId();\n\tif (!sessionId) return [];\n\tconst storePath = scheduledRunStorePath(ctx.cwd, sessionId);\n\tif (!fs.existsSync(storePath)) return [];\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(fs.readFileSync(storePath, \"utf-8\"));\n\t} catch {\n\t\treturn [];\n\t}\n\tconst jobs = parsed && typeof parsed === \"object\" && Array.isArray((parsed as { jobs?: unknown }).jobs)\n\t\t? (parsed as { jobs: Array<Record<string, unknown>> }).jobs\n\t\t: [];\n\treturn jobs\n\t\t.filter((job) => job.state === \"scheduled\" && typeof job.id === \"string\" && typeof job.name === \"string\" && typeof job.runAt === \"number\")\n\t\t.sort((left, right) => Number(left.runAt) - Number(right.runAt))\n\t\t.map((job) => ({\n\t\t\tkind: \"scheduled\" as const,\n\t\t\tid: String(job.id),\n\t\t\tlabel: `${String(job.id)} · ${String(job.name)}`,\n\t\t\tdetail: `scheduled · ${new Date(Number(job.runAt)).toISOString()}`,\n\t\t\tactionLabel: \"cancel scheduled run\",\n\t\t}));\n}\n\nfunction discoverStopTargets(ctx: ExtensionContext, state: SubagentState): StopSelectorTarget[] {\n\tconst sessionId = state.currentSessionId ?? ctx.sessionManager.getSessionId() ?? undefined;\n\tconst asyncTargets = listAsyncRuns(ASYNC_DIR, {\n\t\tstates: [\"queued\", \"running\"],\n\t\t...(sessionId ? { sessionId } : {}),\n\t}).map(formatAsyncStopTarget);\n\treturn [...asyncTargets, ...scheduledStopTargets(ctx, state)];\n}\n\nfunction stopFallbackText(targets: StopSelectorTarget[]): string {\n\tif (targets.length === 0) return \"No active current-session async runs or scheduled subagent runs to stop.\";\n\tconst lines = [\"Subagent stop targets:\", \"\"];\n\tfor (const target of targets) {\n\t\tlines.push(`- ${target.label}`);\n\t\tlines.push(` ${target.detail}`);\n\t\tlines.push(` ${target.actionLabel}: ${commandForTarget(target)}`);\n\t\tif (target.kind === \"async\") lines.push(` slash: /subagents-stop ${target.id}`);\n\t}\n\treturn lines.join(\"\\n\");\n}\n\nclass SubagentsStopSelector implements Component {\n\treadonly width = 84;\n\tprivate selected = 0;\n\tprivate confirming = false;\n\tprivate readonly tui: TUI;\n\tprivate readonly theme: Theme;\n\tprivate readonly targets: StopSelectorTarget[];\n\tprivate readonly done: (result: StopSelectorResult) => void;\n\n\tconstructor(tui: TUI, theme: Theme, targets: StopSelectorTarget[], done: (result: StopSelectorResult) => void) {\n\t\tthis.tui = tui;\n\t\tthis.theme = theme;\n\t\tthis.targets = targets;\n\t\tthis.done = done;\n\t}\n\n\thandleInput(data: string): void {\n\t\tif (matchesKey(data, \"escape\") || matchesKey(data, \"ctrl+c\")) {\n\t\t\tthis.done({ confirmed: false });\n\t\t\treturn;\n\t\t}\n\t\tif (this.confirming) {\n\t\t\tif (matchesKey(data, \"return\") || data.toLowerCase() === \"y\") {\n\t\t\t\tthis.done({ confirmed: true, target: this.targets[this.selected] });\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (data.toLowerCase() === \"n\" || matchesKey(data, \"backspace\")) {\n\t\t\t\tthis.confirming = false;\n\t\t\t\tthis.tui.requestRender();\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\tif (matchesKey(data, \"up\")) {\n\t\t\tthis.selected = Math.max(0, this.selected - 1);\n\t\t\tthis.tui.requestRender();\n\t\t\treturn;\n\t\t}\n\t\tif (matchesKey(data, \"down\")) {\n\t\t\tthis.selected = Math.min(this.targets.length - 1, this.selected + 1);\n\t\t\tthis.tui.requestRender();\n\t\t\treturn;\n\t\t}\n\t\tif (matchesKey(data, \"return\")) {\n\t\t\tthis.confirming = true;\n\t\t\tthis.tui.requestRender();\n\t\t}\n\t}\n\n\trender(width: number): string[] {\n\t\tconst contentWidth = Math.max(40, Math.min(this.width, width || this.width));\n\t\tconst lines = [this.theme.bold(\"Stop subagent run\"), this.theme.fg(\"dim\", \"Select a current-session async run to stop, or a scheduled run to cancel.\"), \"\"];\n\t\tconst maxRows = 10;\n\t\tconst start = Math.max(0, Math.min(this.selected - maxRows + 1, Math.max(0, this.targets.length - maxRows)));\n\t\tfor (let index = start; index < Math.min(this.targets.length, start + maxRows); index++) {\n\t\t\tconst target = this.targets[index]!;\n\t\t\tconst selected = index === this.selected;\n\t\t\tconst marker = selected ? \"›\" : \" \";\n\t\t\t\tconst actionLabel = target.actionLabel;\n\t\t\tconst action = target.kind === \"scheduled\" ? this.theme.fg(\"warning\", actionLabel) : this.theme.fg(\"accent\", actionLabel);\n\t\t\tconst labelWidth = Math.max(0, contentWidth - marker.length - actionLabel.length - 2);\n\t\t\tlines.push(`${marker} ${action} ${target.label.slice(0, labelWidth)}`);\n\t\t\tif (selected) lines.push(this.theme.fg(\"dim\", ` ${target.detail}`.slice(0, contentWidth)));\n\t\t}\n\t\tif (this.targets.length > maxRows) lines.push(this.theme.fg(\"dim\", `Showing ${start + 1}-${Math.min(this.targets.length, start + maxRows)} of ${this.targets.length}`));\n\t\tlines.push(\"\");\n\t\tif (this.confirming) {\n\t\t\tconst target = this.targets[this.selected]!;\n\t\t\tlines.push(this.theme.fg(\"warning\", `Confirm: ${target.actionLabel} ${target.id}?`));\n\t\t\tif (target.kind === \"async\") lines.push(this.theme.fg(\"dim\", \"Stop ends this run; use interrupt for a resumable pause.\"));\n\t\t\tlines.push(this.theme.fg(\"dim\", \"Enter/Y confirms · N returns · Esc cancels\"));\n\t\t} else {\n\t\t\tlines.push(this.theme.fg(\"dim\", \"↑/↓ select · Enter confirm · Esc cancel\"));\n\t\t}\n\t\treturn lines;\n\t}\n}\n\nfunction emptyUsage(): Usage {\n\treturn { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, turns: 0 };\n}\n\nfunction addUsage(target: Usage, source: Usage): void {\n\ttarget.input += source.input;\n\ttarget.output += source.output;\n\ttarget.cacheRead += source.cacheRead;\n\ttarget.cacheWrite += source.cacheWrite;\n\ttarget.cost += source.cost;\n\ttarget.turns += source.turns;\n}\n\nfunction usageHasValue(usage: Usage): boolean {\n\treturn usage.input !== 0 || usage.output !== 0 || usage.cacheRead !== 0 || usage.cacheWrite !== 0 || usage.cost !== 0 || usage.turns !== 0;\n}\n\nfunction assistantUsageFromMessage(message: unknown): Usage | undefined {\n\tif (!message || typeof message !== \"object\") return undefined;\n\tconst msg = message as { role?: unknown; usage?: unknown };\n\tif (msg.role !== \"assistant\" || !msg.usage || typeof msg.usage !== \"object\") return undefined;\n\tconst usage = msg.usage as {\n\t\tinput?: unknown;\n\t\toutput?: unknown;\n\t\tcacheRead?: unknown;\n\t\tcacheWrite?: unknown;\n\t\tcost?: { total?: unknown };\n\t};\n\treturn {\n\t\tinput: typeof usage.input === \"number\" ? usage.input : 0,\n\t\toutput: typeof usage.output === \"number\" ? usage.output : 0,\n\t\tcacheRead: typeof usage.cacheRead === \"number\" ? usage.cacheRead : 0,\n\t\tcacheWrite: typeof usage.cacheWrite === \"number\" ? usage.cacheWrite : 0,\n\t\tcost: typeof usage.cost?.total === \"number\" ? usage.cost.total : 0,\n\t\tturns: 1,\n\t};\n}\n\nfunction isSubagentDetails(value: unknown): value is Details {\n\tif (!value || typeof value !== \"object\") return false;\n\tconst details = value as { mode?: unknown; results?: unknown };\n\treturn typeof details.mode === \"string\" && Array.isArray(details.results);\n}\n\nfunction detailsFromSessionEntry(entry: unknown): Details | undefined {\n\tif (!entry || typeof entry !== \"object\") return undefined;\n\tconst record = entry as { type?: unknown; customType?: unknown; details?: unknown; message?: unknown };\n\tif (record.type === \"custom_message\" && record.customType === SLASH_RESULT_TYPE) {\n\t\tconst details = resolveSlashMessageDetails(record.details)?.result.details;\n\t\treturn isSubagentDetails(details) ? details : undefined;\n\t}\n\tif (record.type !== \"message\" || !record.message || typeof record.message !== \"object\") return undefined;\n\tconst message = record.message as { role?: unknown; toolName?: unknown; details?: unknown };\n\tif (message.role !== \"toolResult\" || message.toolName !== \"subagent\") return undefined;\n\treturn isSubagentDetails(message.details) ? message.details : undefined;\n}\n\nfunction formatCostUsage(label: string, usage: Usage): string {\n\tconst extras = [\n\t\tusage.cacheRead ? `cache read ${formatTokens(usage.cacheRead)}` : \"\",\n\t\tusage.cacheWrite ? `cache write ${formatTokens(usage.cacheWrite)}` : \"\",\n\t\tusage.turns ? `${usage.turns} turn${usage.turns === 1 ? \"\" : \"s\"}` : \"\",\n\t].filter(Boolean);\n\treturn `${label}: ↑${formatTokens(usage.input)} ↓${formatTokens(usage.output)} $${usage.cost.toFixed(4)}${extras.length ? ` (${extras.join(\", \")})` : \"\"}`;\n}\n\nfunction buildSubagentCostReport(ctx: ExtensionContext): string {\n\tconst parent = emptyUsage();\n\tconst childTotal = emptyUsage();\n\tconst total = emptyUsage();\n\tconst children: Array<{ label: string; usage: Usage; sessionFile?: string }> = [];\n\tfor (const entry of ctx.sessionManager.getBranch()) {\n\t\tconst message = entry.type === \"message\" ? (entry as { message?: unknown }).message : undefined;\n\t\tconst parentUsage = assistantUsageFromMessage(message);\n\t\tif (parentUsage) addUsage(parent, parentUsage);\n\t\tconst details = detailsFromSessionEntry(entry);\n\t\tif (!details) continue;\n\t\tfor (const result of details.results) {\n\t\t\tif (!usageHasValue(result.usage)) continue;\n\t\t\tconst usage = { ...result.usage };\n\t\t\tchildren.push({\n\t\t\t\tlabel: `Child ${children.length + 1} (${result.agent})`,\n\t\t\t\tusage,\n\t\t\t\t...(result.sessionFile ? { sessionFile: result.sessionFile } : {}),\n\t\t\t});\n\t\t\taddUsage(childTotal, usage);\n\t\t}\n\t}\n\taddUsage(total, parent);\n\taddUsage(total, childTotal);\n\tconst lines = [\n\t\t\"Subagent cost\",\n\t\t\"\",\n\t\tformatCostUsage(\"Parent\", parent),\n\t];\n\tif (children.length === 0) {\n\t\tlines.push(\"No subagent child usage found in this session.\");\n\t} else {\n\t\tfor (const child of children) {\n\t\t\tlines.push(formatCostUsage(child.label, child.usage));\n\t\t\tif (child.sessionFile) lines.push(` Session: ${child.sessionFile}`);\n\t\t}\n\t}\n\tlines.push(\"────────────────────────────\", formatCostUsage(\"Children\", childTotal), formatCostUsage(\"Total\", total));\n\treturn lines.join(\"\\n\");\n}\n\nfunction parseSingleRequiredArg(args: string, usage: string): { ok: true; value: string } | { ok: false; message: string } {\n\tconst parts = args.trim().split(/\\s+/).filter(Boolean);\n\tif (parts.length !== 1) return { ok: false, message: usage };\n\treturn { ok: true, value: parts[0]! };\n}\n\nfunction getProfileWorkerModel(profile: { subagents?: { agentOverrides?: Record<string, { model?: string }> } }): string | undefined {\n\tconst model = profile.subagents?.agentOverrides?.worker?.model;\n\treturn typeof model === \"string\" && model.trim() ? model.trim() : undefined;\n}\n\nfunction loadSavedOutputSchema(chain: ChainConfig, stepAgent: string, outputSchema: unknown): JsonSchemaObject | undefined {\n\tif (outputSchema === undefined) return undefined;\n\tif (typeof outputSchema === \"string\") {\n\t\tconst schemaPath = path.isAbsolute(outputSchema)\n\t\t\t? outputSchema\n\t\t\t: path.join(path.dirname(chain.filePath), outputSchema);\n\t\tconst parsed = JSON.parse(fs.readFileSync(schemaPath, \"utf-8\")) as unknown;\n\t\tassertJsonSchemaObject(parsed, `outputSchema for chain '${chain.name}' step '${stepAgent}' (${schemaPath})`);\n\t\treturn parsed;\n\t}\n\tassertJsonSchemaObject(outputSchema, `outputSchema for chain '${chain.name}' step '${stepAgent}'`);\n\treturn outputSchema;\n}\n\nconst mapSavedChainSteps = (chain: ChainConfig, worktree = false): ChainStep[] => {\n\treturn (chain.steps as unknown as Array<ChainStep & { skills?: string[] | false }>).map((step) => {\n\t\tif (isParallelStep(step)) {\n\t\t\tconst parallel = step.parallel.map((task) => {\n\t\t\t\tconst { outputSchema: rawOutputSchema, ...rest } = task as typeof task & { outputSchema?: unknown };\n\t\t\t\tconst outputSchema = loadSavedOutputSchema(chain, task.agent, rawOutputSchema);\n\t\t\t\treturn { ...rest, ...(outputSchema ? { outputSchema } : {}) };\n\t\t\t});\n\t\t\treturn { ...step, parallel, ...(worktree ? { worktree: true } : {}) };\n\t\t}\n\t\tif (isDynamicParallelStep(step)) {\n\t\t\tconst { outputSchema: rawOutputSchema, ...parallelRest } = step.parallel as typeof step.parallel & { outputSchema?: unknown };\n\t\t\tconst outputSchema = loadSavedOutputSchema(chain, step.parallel.agent, rawOutputSchema);\n\t\t\tconst collectSchema = loadSavedOutputSchema(chain, `${step.collect.as} collection`, step.collect.outputSchema);\n\t\t\treturn {\n\t\t\t\t...step,\n\t\t\t\tparallel: { ...parallelRest, ...(outputSchema ? { outputSchema } : {}) },\n\t\t\t\tcollect: { ...step.collect, ...(collectSchema ? { outputSchema: collectSchema } : {}) },\n\t\t\t};\n\t\t}\n\t\tconst outputSchema = loadSavedOutputSchema(chain, step.agent, (step as { outputSchema?: unknown }).outputSchema);\n\t\treturn {\n\t\t\tagent: step.agent,\n\t\t\ttask: step.task || undefined,\n\t\t\t...(step.phase ? { phase: step.phase } : {}),\n\t\t\t...(step.label ? { label: step.label } : {}),\n\t\t\t...(step.as ? { as: step.as } : {}),\n\t\t\t...(outputSchema ? { outputSchema } : {}),\n\t\t\t...((step as { acceptance?: unknown }).acceptance !== undefined ? { acceptance: (step as { acceptance?: unknown }).acceptance } : {}),\n\t\t\toutput: step.output,\n\t\t\toutputMode: step.outputMode,\n\t\t\treads: step.reads,\n\t\t\tprogress: step.progress,\n\t\t\tskill: step.skill ?? step.skills,\n\t\t\tmodel: step.model,\n\t\t};\n\t});\n};\n\nasync function requestSlashRun(\n\tpi: ExtensionAPI,\n\tctx: ExtensionContext,\n\trequestId: string,\n\tparams: SubagentParamsLike,\n): Promise<SlashSubagentResponse> {\n\treturn new Promise((resolve, reject) => {\n\t\tlet done = false;\n\t\tlet started = false;\n\n\t\tconst startTimeoutMs = 15_000;\n\t\tconst startTimeout = setTimeout(() => {\n\t\t\tfinish(() => reject(new Error(\n\t\t\t\t\"Slash subagent bridge did not start within 15s. Ensure the extension is loaded correctly.\",\n\t\t\t)));\n\t\t}, startTimeoutMs);\n\n\t\tconst onStarted = (data: unknown) => {\n\t\t\tif (done || !data || typeof data !== \"object\") return;\n\t\t\tif ((data as { requestId?: unknown }).requestId !== requestId) return;\n\t\t\tstarted = true;\n\t\t\tclearTimeout(startTimeout);\n\t\t\tif (ctx.hasUI) ctx.ui.setStatus(\"subagent-slash\", \"running...\");\n\t\t};\n\n\t\tconst onResponse = (data: unknown) => {\n\t\t\tif (done || !data || typeof data !== \"object\") return;\n\t\t\tconst response = data as Partial<SlashSubagentResponse>;\n\t\t\tif (response.requestId !== requestId) return;\n\t\t\tclearTimeout(startTimeout);\n\t\t\tfinish(() => resolve(response as SlashSubagentResponse));\n\t\t};\n\n\t\tconst onUpdate = (data: unknown) => {\n\t\t\tif (done || !data || typeof data !== \"object\") return;\n\t\t\tconst update = data as SlashSubagentUpdate;\n\t\t\tif (update.requestId !== requestId) return;\n\t\t\tapplySlashUpdate(requestId, update);\n\t\t\tif (!ctx.hasUI) return;\n\t\t\tconst tool = update.currentTool ? ` ${update.currentTool}` : \"\";\n\t\t\tconst count = update.toolCount ?? 0;\n\t\t\tconst liveDetailKey = keyText(\"app.tools.expand\");\n\t\t\tctx.ui.setStatus(\"subagent-slash\", `${count} tools${tool} | ${liveDetailKey} live detail`);\n\t\t};\n\n\t\tconst onTerminalInput = ctx.hasUI\n\t\t\t? ctx.ui.onTerminalInput((input) => {\n\t\t\t\tif (!matchesKey(input, Key.escape)) return undefined;\n\t\t\t\tpi.events.emit(SLASH_SUBAGENT_CANCEL_EVENT, { requestId });\n\t\t\t\tfinish(() => reject(new Error(\"Cancelled\")));\n\t\t\t\treturn { consume: true };\n\t\t\t})\n\t\t\t: undefined;\n\n\t\tconst unsubStarted = pi.events.on(SLASH_SUBAGENT_STARTED_EVENT, onStarted);\n\t\tconst unsubResponse = pi.events.on(SLASH_SUBAGENT_RESPONSE_EVENT, onResponse);\n\t\tconst unsubUpdate = pi.events.on(SLASH_SUBAGENT_UPDATE_EVENT, onUpdate);\n\n\t\tconst finish = (next: () => void) => {\n\t\t\tif (done) return;\n\t\t\tdone = true;\n\t\t\tclearTimeout(startTimeout);\n\t\t\tunsubStarted();\n\t\t\tunsubResponse();\n\t\t\tunsubUpdate();\n\t\t\tonTerminalInput?.();\n\t\t\tnext();\n\t\t};\n\n\t\tpi.events.emit(SLASH_SUBAGENT_REQUEST_EVENT, { requestId, params, ctx });\n\n\t\t// Bridge emits STARTED synchronously during REQUEST emit.\n\t\t// If not started, no bridge received the request.\n\t\tif (!started && done) return;\n\t\tif (!started) {\n\t\t\tfinish(() => reject(new Error(\n\t\t\t\t\"No slash subagent bridge responded. Ensure the subagent extension is loaded correctly.\",\n\t\t\t)));\n\t\t}\n\t});\n}\n\nfunction extractSlashMessageText(content: string | Array<{ type?: string; text?: string }>): string {\n\tif (typeof content === \"string\") return content;\n\tif (!Array.isArray(content)) return \"\";\n\treturn content\n\t\t.filter((part): part is { type: \"text\"; text: string } => part?.type === \"text\" && typeof part.text === \"string\")\n\t\t.map((part) => part.text)\n\t\t.join(\"\\n\");\n}\n\nfunction formatExportPathList(paths: string[]): string {\n\treturn paths.map((file) => `- \\`${file}\\``).join(\"\\n\");\n}\n\nfunction collectResultPaths(results: SingleResult[], getPath: (result: SingleResult) => string | undefined): string[] {\n\treturn results\n\t\t.map(getPath)\n\t\t.filter((file): file is string => typeof file === \"string\" && file.length > 0);\n}\n\nfunction buildSlashExportText(response: SlashSubagentResponse): string {\n\tconst output = extractSlashMessageText(response.result.content) || response.errorText || \"(no output)\";\n\tconst results = response.result.details?.results ?? [];\n\tconst sessionFiles = collectResultPaths(results, (result) => result.sessionFile);\n\tconst savedOutputs = collectResultPaths(results, (result) => result.savedOutputPath);\n\tconst artifactOutputs = collectResultPaths(results, (result) => result.artifactPaths?.outputPath);\n\tconst sections = [\"## Subagent result\", output];\n\tif (sessionFiles.length > 0) sections.push(\"## Child session exports\", formatExportPathList(sessionFiles));\n\tif (savedOutputs.length > 0) sections.push(\"## Saved outputs\", formatExportPathList(savedOutputs));\n\tif (artifactOutputs.length > 0) sections.push(\"## Artifact outputs\", formatExportPathList(artifactOutputs));\n\treturn sections.join(\"\\n\\n\");\n}\n\nfunction persistSlashSessionSnapshot(ctx: ExtensionContext): void {\n\ttry {\n\t\tif (!ctx.sessionManager) return;\n\t\tconst sessionManager = ctx.sessionManager as typeof ctx.sessionManager & {\n\t\t\t_rewriteFile?: () => void;\n\t\t\tflushed?: boolean;\n\t\t};\n\t\tconst sessionFile = sessionManager.getSessionFile();\n\t\tif (!sessionFile || typeof sessionManager._rewriteFile !== \"function\") return;\n\t\tfs.mkdirSync(path.dirname(sessionFile), { recursive: true });\n\t\tsessionManager._rewriteFile();\n\t\tsessionManager.flushed = true;\n\t} catch (error) {\n\t\tconsole.error(\"Failed to persist slash session snapshot for export:\", error);\n\t}\n}\n\nasync function runSlashSubagent(\n\tpi: ExtensionAPI,\n\tctx: ExtensionContext,\n\tparams: SubagentParamsLike,\n): Promise<void> {\n\tif (ctx.hasUI) ctx.ui.setToolsExpanded(false);\n\tconst requestId = randomUUID();\n\tconst initialDetails = buildSlashInitialResult(requestId, params);\n\tconst initialText = extractSlashMessageText(initialDetails.result.content) || \"Running subagent...\";\n\tpi.sendMessage({\n\t\tcustomType: SLASH_RESULT_TYPE,\n\t\tcontent: initialText,\n\t\tdisplay: true,\n\t\tdetails: initialDetails,\n\t});\n\tpersistSlashSessionSnapshot(ctx);\n\n\ttry {\n\t\tconst response = await requestSlashRun(pi, ctx, requestId, params);\n\t\tconst finalDetails = finalizeSlashResult(response);\n\t\tpi.sendMessage({\n\t\t\tcustomType: SLASH_RESULT_TYPE,\n\t\t\tcontent: buildSlashExportText(response),\n\t\t\tdisplay: !ctx.hasUI,\n\t\t\tdetails: finalDetails,\n\t\t});\n\t\tpersistSlashSessionSnapshot(ctx);\n\t\tif (ctx.hasUI) {\n\t\t\tctx.ui.setStatus(\"subagent-slash\", undefined);\n\t\t}\n\t\tif (response.isError && ctx.hasUI) {\n\t\t\tctx.ui.notify(response.errorText || \"Subagent failed\", \"error\");\n\t\t}\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\tconst failedDetails = failSlashResult(requestId, params, message);\n\t\tpi.sendMessage({\n\t\t\tcustomType: SLASH_RESULT_TYPE,\n\t\t\tcontent: `## Subagent result\\n\\n${message}`,\n\t\t\tdisplay: !ctx.hasUI,\n\t\t\tdetails: failedDetails,\n\t\t});\n\t\tpersistSlashSessionSnapshot(ctx);\n\t\tif (ctx.hasUI) {\n\t\t\tctx.ui.setStatus(\"subagent-slash\", undefined);\n\t\t}\n\t\tif (message === \"Cancelled\") {\n\t\t\tif (ctx.hasUI) ctx.ui.notify(\"Cancelled\", \"warning\");\n\t\t\treturn;\n\t\t}\n\t\tif (ctx.hasUI) ctx.ui.notify(message, \"error\");\n\t}\n}\n\n\nexport interface GroupConfig { concurrency?: number; failFast?: boolean; worktree?: boolean }\nexport interface ParsedStep { kind: \"step\"; name: string; config: InlineConfig; task?: string }\nexport interface ParsedGroup { kind: \"group\"; tasks: ParsedStep[]; config: GroupConfig }\nexport type ParsedGroupStep = ParsedStep | ParsedGroup;\n\nexport const PARALLEL_GROUP_USAGE =\n\t'Usage: /chain agent \"task\" -> (agent2 \"task\" | agent3 \"task\") -> agent4';\n\nexport class SlashParseError extends Error {}\n\n// Walk `input` tracking quote/paren state; returns true if parens are unbalanced.\nfunction findUnmatchedCloseParen(input: string): boolean {\n\tlet depth = 0, inSingle = false, inDouble = false;\n\tfor (let i = 0; i < input.length; i++) {\n\t\tconst ch = input[i]!;\n\t\tif (inSingle) { if (ch === \"'\") inSingle = false; continue; }\n\t\tif (inDouble) { if (ch === '\"') inDouble = false; continue; }\n\t\tif (ch === \"'\") { inSingle = true; continue; }\n\t\tif (ch === '\"') { inDouble = true; continue; }\n\t\tif (ch === \"(\") depth++;\n\t\telse if (ch === \")\") { depth--; if (depth < 0) return true; }\n\t}\n\treturn depth !== 0;\n}\n\n// Split on top-level \" -> \", ignoring arrows inside quotes or parentheses.\nfunction splitOnArrow(input: string): string[] {\n\tconst segments: string[] = [];\n\tlet depth = 0, inSingle = false, inDouble = false, start = 0;\n\tfor (let i = 0; i < input.length; i++) {\n\t\tconst ch = input[i]!;\n\t\tif (inSingle) { if (ch === \"'\") inSingle = false; continue; }\n\t\tif (inDouble) { if (ch === '\"') inDouble = false; continue; }\n\t\tif (ch === \"'\") { inSingle = true; continue; }\n\t\tif (ch === '\"') { inDouble = true; continue; }\n\t\tif (ch === \"(\") depth++;\n\t\telse if (ch === \")\") depth--;\n\t\telse if (depth === 0 && ch === \"-\" && input[i + 1] === \">\" && input[i + 2] === \" \") {\n\t\t\tsegments.push(input.slice(start, i));\n\t\t\ti += 2;\n\t\t\tstart = i + 1;\n\t\t}\n\t}\n\tsegments.push(input.slice(start));\n\treturn segments;\n}\n\n// Split a group's inner text on top-level \" | \", ignoring pipes inside quotes/parens.\nfunction splitGroupTasks(inner: string): string[] {\n\tconst parts: string[] = [];\n\tlet depth = 0, inSingle = false, inDouble = false, start = 0;\n\tfor (let i = 0; i < inner.length; i++) {\n\t\tconst ch = inner[i]!;\n\t\tif (inSingle) { if (ch === \"'\") inSingle = false; continue; }\n\t\tif (inDouble) { if (ch === '\"') inDouble = false; continue; }\n\t\tif (ch === \"'\") { inSingle = true; continue; }\n\t\tif (ch === '\"') { inDouble = true; continue; }\n\t\tif (ch === \"(\") depth++;\n\t\telse if (ch === \")\") depth--;\n\t\telse if (ch === \"|\" && depth === 0) {\n\t\t\tparts.push(inner.slice(start, i));\n\t\t\tstart = i + 1;\n\t\t}\n\t}\n\tparts.push(inner.slice(start));\n\treturn parts;\n}\n\nexport function parseSingleTaskToken(token: string): ParsedStep {\n\tlet agentPart: string;\n\tlet task: string | undefined;\n\tconst qMatch = token.match(/^(\\S+(?:\\[[^\\]]*\\])?)\\s+(?:\"([^\"]*)\"|'([^']*)')$/);\n\tif (qMatch) {\n\t\tagentPart = qMatch[1]!;\n\t\ttask = (qMatch[2] ?? qMatch[3]) || undefined;\n\t} else {\n\t\tconst dashIdx = token.indexOf(\" -- \");\n\t\tif (dashIdx !== -1) {\n\t\t\tagentPart = token.slice(0, dashIdx).trim();\n\t\t\ttask = token.slice(dashIdx + 4).trim() || undefined;\n\t\t} else {\n\t\t\tagentPart = token;\n\t\t}\n\t}\n\treturn { kind: \"step\", ...parseAgentToken(agentPart), task };\n}\n\nconst parseGroupConfig = (raw: string): GroupConfig => {\n\tconst config: GroupConfig = {};\n\tfor (const part of raw.split(\",\")) {\n\t\tconst trimmed = part.trim();\n\t\tif (!trimmed) continue;\n\t\tconst eq = trimmed.indexOf(\"=\");\n\t\tconst key = eq === -1 ? trimmed : trimmed.slice(0, eq).trim();\n\t\tconst val = eq === -1 ? \"\" : trimmed.slice(eq + 1).trim();\n\t\tswitch (key) {\n\t\t\tcase \"concurrency\": { const n = Number(val); if (Number.isInteger(n) && n > 0) config.concurrency = n; break; }\n\t\t\tcase \"failFast\": config.failFast = eq === -1 ? true : val !== \"false\"; break;\n\t\t\tcase \"worktree\": config.worktree = eq === -1 ? true : val !== \"false\"; break;\n\t\t}\n\t}\n\treturn config;\n};\n\n// Split `(...)` from an optional trailing `[...]` group-config suffix, respecting\n// quotes and nested parens. Returns the inner group text and the parsed config.\nconst splitGroupBody = (trimmed: string): { inner: string; config: GroupConfig } => {\n\tlet depth = 0, inSingle = false, inDouble = false, closeIdx = -1;\n\tfor (let i = 0; i < trimmed.length; i++) {\n\t\tconst ch = trimmed[i]!;\n\t\tif (inSingle) { if (ch === \"'\") inSingle = false; continue; }\n\t\tif (inDouble) { if (ch === '\"') inDouble = false; continue; }\n\t\tif (ch === \"'\") { inSingle = true; continue; }\n\t\tif (ch === '\"') { inDouble = true; continue; }\n\t\tif (ch === \"(\") depth++;\n\t\telse if (ch === \")\") { depth--; if (depth === 0) { closeIdx = i; break; } }\n\t}\n\tif (closeIdx === -1) throw new SlashParseError(`Unmatched parentheses in group: '${trimmed}'`);\n\tconst inner = trimmed.slice(1, closeIdx);\n\tconst suffix = trimmed.slice(closeIdx + 1).trim();\n\tif (!suffix) return { inner, config: {} };\n\tif (!suffix.startsWith(\"[\") || !suffix.endsWith(\"]\")) {\n\t\tthrow new SlashParseError(`Group options must be wrapped in [...]: '${suffix}'`);\n\t}\n\treturn { inner, config: parseGroupConfig(suffix.slice(1, -1)) };\n};\n\nexport function parseGroupSegment(segment: string): ParsedGroup {\n\tconst trimmed = segment.trim();\n\tif (!trimmed.startsWith(\"(\")) {\n\t\tthrow new SlashParseError(`Parallel group must be wrapped in parentheses: '${trimmed}'`);\n\t}\n\tconst { inner, config } = splitGroupBody(trimmed);\n\tconst rawParts = splitGroupTasks(inner).map((p) => p.trim()).filter((p) => p.length > 0);\n\tif (rawParts.length < 2) {\n\t\tthrow new SlashParseError(\"Parallel group must contain at least two tasks separated by ' | '\");\n\t}\n\treturn { kind: \"group\", tasks: rawParts.map((part) => parseSingleTaskToken(part)), config };\n}\n\n// True if `input` uses inline parallel-group syntax. A group is a *step* that begins\n// with `(` at the top level, so we split on top-level ` -> ` arrows and look for a\n// segment that opens with `(`. Parentheses appearing inside a task (e.g.\n// `scout -- inspect auth (backend)`) do not count, which keeps legacy shared-task\n// `-- task` parsing — including a bare `|` inside the task — on the single-agent path.\nexport function hasGroupSyntax(input: string): boolean {\n\treturn splitOnArrow(input).some((seg) => seg.trim().startsWith(\"(\"));\n}\n\nexport function parseChainExpression(input: string): { steps: ParsedGroupStep[] } {\n\tconst trimmed = input.trim();\n\tif (!trimmed.includes(\" -> \")) {\n\t\tthrow new SlashParseError('Parallel groups in /chain require \" -> \" between steps');\n\t}\n\tif (findUnmatchedCloseParen(trimmed)) {\n\t\tthrow new SlashParseError(\"Unmatched parentheses in /chain expression\");\n\t}\n\tconst steps: ParsedGroupStep[] = [];\n\tfor (const seg of splitOnArrow(trimmed)) {\n\t\tconst t = seg.trim();\n\t\tif (!t) continue;\n\t\tif (t.startsWith(\"(\")) {\n\t\t\tsteps.push(parseGroupSegment(t));\n\t\t\tcontinue;\n\t\t}\n\t\tif (findUnmatchedCloseParen(t)) {\n\t\t\tthrow new SlashParseError(`Unmatched parentheses in chain segment: '${t}'`);\n\t\t}\n\t\tsteps.push(parseSingleTaskToken(t));\n\t}\n\tif (steps.length === 0) {\n\t\tthrow new SlashParseError(\"/chain expression must include at least one step\");\n\t}\n\treturn { steps };\n}\n\nconst parseAgentArgs = (\n\tstate: SubagentState,\n\targs: string,\n\tcommand: string,\n\tctx: ExtensionContext,\n): { steps: ParsedStep[]; task: string } | null => {\n\tconst input = args.trim();\n\tconst usage = `Usage: /${command} agent1 \"task1\" -> agent2 \"task2\"`;\n\tlet steps: ParsedStep[];\n\tlet sharedTask: string;\n\tlet perStep = false;\n\n\tif (input.includes(\" -> \")) {\n\t\tperStep = true;\n\t\tconst segments = input.split(\" -> \");\n\t\tsteps = [];\n\t\tfor (const seg of segments) {\n\t\t\tconst trimmed = seg.trim();\n\t\t\tif (!trimmed) continue;\n\t\t\tsteps.push(parseSingleTaskToken(trimmed));\n\t\t}\n\t\tsharedTask = steps.find((s) => s.task)?.task ?? \"\";\n\t} else {\n\t\tconst delimiterIndex = input.indexOf(\" -- \");\n\t\tif (delimiterIndex === -1) {\n\t\t\tctx.ui.notify(usage, \"error\");\n\t\t\treturn null;\n\t\t}\n\t\tconst agentsPart = input.slice(0, delimiterIndex).trim();\n\t\tsharedTask = input.slice(delimiterIndex + 4).trim();\n\t\tif (!agentsPart || !sharedTask) {\n\t\t\tctx.ui.notify(usage, \"error\");\n\t\t\treturn null;\n\t\t}\n\t\tsteps = agentsPart.split(/\\s+/).filter(Boolean).map((t) => parseSingleTaskToken(t));\n\t}\n\n\tif (steps.length === 0) {\n\t\tctx.ui.notify(usage, \"error\");\n\t\treturn null;\n\t}\n\tif (!state.baseCwd) {\n\t\tctx.ui.notify(\"Subagent session cwd is not initialized yet\", \"error\");\n\t\treturn null;\n\t}\n\tconst agents = discoverAgents(state.baseCwd, \"both\").agents;\n\tfor (const step of steps) {\n\t\tif (!agents.find((a) => a.name === step.name)) {\n\t\t\tctx.ui.notify(`Unknown agent: ${step.name}`, \"error\");\n\t\t\treturn null;\n\t\t}\n\t}\n\tif (command === \"chain\" && !steps[0]?.task && (perStep || !sharedTask)) {\n\t\tctx.ui.notify(`First step must have a task: /chain agent \"task\" -> agent2`, \"error\");\n\t\treturn null;\n\t}\n\tif (command === \"parallel\" && !steps.some((s) => s.task) && !sharedTask) {\n\t\tctx.ui.notify(\"At least one step must have a task\", \"error\");\n\t\treturn null;\n\t}\n\treturn { steps, task: sharedTask };\n};\n\ntype ChainStepObject = {\n\tagent: string;\n\ttask?: string;\n\toutput?: string | false;\n\toutputMode?: \"inline\" | \"file-only\";\n\treads?: string[] | false;\n\tmodel?: string;\n\tskill?: string[] | false;\n\tprogress?: boolean;\n\tas?: string;\n\tlabel?: string;\n\tphase?: string;\n\tcwd?: string;\n\tcount?: number;\n\toutputSchema?: JsonSchemaObject;\n\tacceptance?: string;\n};\n\nconst INLINE_ACCEPTANCE_LEVELS = new Set([\"auto\", \"attested\", \"checked\"]);\n\nfunction validateInlineAcceptanceInput(value: string, agent: string): void {\n\tconst errors = validateAcceptanceInput(value, `acceptance for step '${agent}'`);\n\tif (errors.length > 0) throw new SlashParseError(errors[0]!);\n\tif (!INLINE_ACCEPTANCE_LEVELS.has(value)) {\n\t\tthrow new SlashParseError(`Inline acceptance for step '${agent}' supports auto, attested, or checked. Use the subagent tool API or a saved .chain.json file for none or verified acceptance contracts; reviewed is inferred-only.`);\n\t}\n}\n\n// Load an inline `outputSchema=<path>` JSON file, resolved against the session cwd.\n// Throws (SlashParseError / fs / JSON) on a missing or malformed schema.\nfunction loadInlineOutputSchema(baseCwd: string, agent: string, value: string): JsonSchemaObject {\n\tconst schemaPath = path.isAbsolute(value) ? value : path.join(baseCwd, value);\n\tconst label = `outputSchema for step '${agent}' (${schemaPath})`;\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(fs.readFileSync(schemaPath, \"utf-8\"));\n\t} catch (error) {\n\t\tthrow new SlashParseError(`Cannot read ${label}: ${error instanceof Error ? error.message : String(error)}`);\n\t}\n\tassertJsonSchemaObject(parsed, label);\n\treturn parsed;\n}\n\n// Build a ChainStep object from a parsed token. `inGroup` enables `count` (parallel-only).\n// May throw SlashParseError for an invalid acceptance level or outputSchema path.\nconst mapParsedTaskToStepObject = (\n\tstep: ParsedStep,\n\tfallbackTask: string | undefined,\n\tisFirst: boolean,\n\topts: { baseCwd: string; inGroup: boolean },\n): ChainStepObject => {\n\tconst { name, config, task: stepTask } = step;\n\tif (config.acceptance !== undefined) validateInlineAcceptanceInput(config.acceptance, name);\n\treturn {\n\t\tagent: name,\n\t\t...(stepTask ? { task: stepTask } : isFirst && fallbackTask ? { task: fallbackTask } : {}),\n\t\t...(config.output !== undefined ? { output: config.output } : {}),\n\t\t...(config.outputMode !== undefined ? { outputMode: config.outputMode } : {}),\n\t\t...(config.reads !== undefined ? { reads: config.reads } : {}),\n\t\t...(config.model ? { model: config.model } : {}),\n\t\t...(config.skill !== undefined ? { skill: config.skill } : {}),\n\t\t...(config.progress !== undefined ? { progress: config.progress } : {}),\n\t\t...(config.as ? { as: config.as } : {}),\n\t\t...(config.label ? { label: config.label } : {}),\n\t\t...(config.phase ? { phase: config.phase } : {}),\n\t\t...(config.cwd ? { cwd: config.cwd } : {}),\n\t\t...(opts.inGroup && config.count !== undefined ? { count: config.count } : {}),\n\t\t...(config.outputSchema ? { outputSchema: loadInlineOutputSchema(opts.baseCwd, name, config.outputSchema) } : {}),\n\t\t...(config.acceptance ? { acceptance: config.acceptance } : {}),\n\t};\n};\n\nexport function buildChainExpressionSteps(\n\tstate: SubagentState,\n\tinput: string,\n\tctx: ExtensionContext,\n): { chain: ChainStep[]; task: string } | null {\n\tconst notify = (message: string) => ctx.ui.notify(message, \"error\");\n\tif (!hasGroupSyntax(input)) {\n\t\tconst parsed = parseAgentArgs(state, input, \"chain\", ctx);\n\t\tif (!parsed) return null;\n\t\tconst baseCwd = state.baseCwd!; // parseAgentArgs already verified baseCwd is set\n\t\ttry {\n\t\t\tconst chain: ChainStep[] = parsed.steps.map((step, i) =>\n\t\t\t\tmapParsedTaskToStepObject(step, parsed.task || undefined, i === 0, { baseCwd, inGroup: false }),\n\t\t\t);\n\t\t\treturn { chain, task: parsed.task };\n\t\t} catch (error) {\n\t\t\tnotify(error instanceof Error ? error.message : String(error));\n\t\t\treturn null;\n\t\t}\n\t}\n\n\tlet expression: { steps: ParsedGroupStep[] };\n\ttry {\n\t\texpression = parseChainExpression(input);\n\t} catch (error) {\n\t\tnotify(error instanceof Error ? error.message : String(error));\n\t\treturn null;\n\t}\n\tif (!state.baseCwd) {\n\t\tnotify(\"Subagent session cwd is not initialized yet\");\n\t\treturn null;\n\t}\n\tconst agents = discoverAgents(state.baseCwd, \"both\").agents;\n\tconst stepAgentNames = expression.steps.flatMap((step) =>\n\t\tstep.kind === \"group\" ? step.tasks.map((t) => t.name) : [step.name],\n\t);\n\tfor (const name of stepAgentNames) {\n\t\tif (!agents.find((a) => a.name === name)) {\n\t\t\tnotify(`Unknown agent: ${name}`);\n\t\t\treturn null;\n\t\t}\n\t}\n\t// Every task inside a parallel group needs its own task; there is no shared-task fallback.\n\tfor (const step of expression.steps) {\n\t\tif (step.kind === \"group\" && step.tasks.some((t) => !t.task)) {\n\t\t\tnotify('Each task in a parallel group needs a task: (agent \"a\" | agent \"b\")');\n\t\t\treturn null;\n\t\t}\n\t}\n\tconst firstStep = expression.steps[0]!;\n\tconst firstHasTask =\n\t\tfirstStep.kind === \"group\"\n\t\t\t? firstStep.tasks.some((t) => Boolean(t.task))\n\t\t\t: Boolean(firstStep.task);\n\tif (!firstHasTask) {\n\t\tnotify('First step must have a task: /chain agent \"task\" -> agent2');\n\t\treturn null;\n\t}\n\tconst sharedTask =\n\t\tfirstStep.kind === \"group\"\n\t\t\t? (firstStep.tasks.find((t) => t.task)?.task ?? \"\")\n\t\t\t: (firstStep.task ?? \"\");\n\tconst baseCwd = state.baseCwd;\n\tlet chain: ChainStep[];\n\ttry {\n\t\tchain = expression.steps.map((step) => {\n\t\t\tif (step.kind === \"group\") {\n\t\t\t\tconst parallel = step.tasks.map((t) => mapParsedTaskToStepObject(t, undefined, false, { baseCwd, inGroup: true }));\n\t\t\t\treturn {\n\t\t\t\t\tparallel,\n\t\t\t\t\t...(step.config.concurrency !== undefined ? { concurrency: step.config.concurrency } : {}),\n\t\t\t\t\t...(step.config.failFast !== undefined ? { failFast: step.config.failFast } : {}),\n\t\t\t\t\t...(step.config.worktree !== undefined ? { worktree: step.config.worktree } : {}),\n\t\t\t\t};\n\t\t\t}\n\t\t\treturn mapParsedTaskToStepObject(step, sharedTask || undefined, false, { baseCwd, inGroup: false });\n\t\t});\n\t} catch (error) {\n\t\tnotify(error instanceof Error ? error.message : String(error));\n\t\treturn null;\n\t}\n\treturn { chain, task: sharedTask };\n}\n\nexport function registerSlashCommands(\n\tpi: ExtensionAPI,\n\tstate: SubagentState,\n): void {\n\tlet fleetOpen = false;\n\tconst showFleet = async (ctx: ExtensionContext) => {\n\t\tstate.lastUiContext = ctx;\n\t\tif (!ctx.hasUI) {\n\t\t\tawait runSlashSubagent(pi, ctx, { action: \"status\", view: \"fleet\" });\n\t\t\treturn;\n\t\t}\n\t\tif (fleetOpen) {\n\t\t\tctx.ui.notify(\"Subagent fleet inspector is already open.\", \"info\");\n\t\t\treturn;\n\t\t}\n\t\tfleetOpen = true;\n\t\ttry {\n\t\t\tawait openSubagentFleet(ctx, state);\n\t\t} finally {\n\t\t\tfleetOpen = false;\n\t\t}\n\t};\n\n\tpi.registerCommand(\"run\", {\n\t\tdescription: \"Run a subagent directly: /run agent[output=file] [task] [--bg]\",\n\t\tgetArgumentCompletions: makeAgentCompletions(state, false),\n\t\thandler: async (args, ctx) => {\n\t\t\tconst { args: cleanedArgs, bg } = extractExecutionFlags(args);\n\t\t\tconst input = cleanedArgs.trim();\n\t\t\tconst firstSpace = input.indexOf(\" \");\n\t\t\tif (!input) { ctx.ui.notify(\"Usage: /run <agent> [task] [--bg]\", \"error\"); return; }\n\t\t\tconst { name: agentName, config: inline } = parseAgentToken(firstSpace === -1 ? input : input.slice(0, firstSpace));\n\t\t\tconst task = firstSpace === -1 ? \"\" : input.slice(firstSpace + 1).trim();\n\n\t\t\tif (!state.baseCwd) { ctx.ui.notify(\"Subagent session cwd is not initialized yet\", \"error\"); return; }\n\t\t\tconst agents = discoverAgents(state.baseCwd, \"both\").agents;\n\t\t\tif (!agents.find((a) => a.name === agentName)) { ctx.ui.notify(`Unknown agent: ${agentName}`, \"error\"); return; }\n\n\t\t\tlet finalTask = task;\n\t\t\tif (inline.reads && Array.isArray(inline.reads) && inline.reads.length > 0) {\n\t\t\t\tfinalTask = `[Read from: ${inline.reads.join(\", \")}]\\n\\n${finalTask}`;\n\t\t\t}\n\t\t\tconst params: SubagentParamsLike = { agent: agentName, task: finalTask, clarify: false, agentScope: \"both\" };\n\t\t\tif (inline.output !== undefined) params.output = inline.output;\n\t\t\tif (inline.outputMode !== undefined) params.outputMode = inline.outputMode;\n\t\t\tif (inline.skill !== undefined) params.skill = inline.skill;\n\t\t\tif (inline.model) params.model = inline.model;\n\t\t\tif (bg) params.async = true;\n\t\t\tawait runSlashSubagent(pi, ctx, params);\n\t\t},\n\t});\n\n\tpi.registerCommand(\"chain\", {\n\t\tdescription: \"Run agents in sequence: /chain scout \\\"task\\\" -> planner [--bg]\",\n\t\tgetArgumentCompletions: makeAgentCompletions(state, true),\n\t\thandler: async (args, ctx) => {\n\t\t\tconst { args: cleanedArgs, bg } = extractExecutionFlags(args);\n\t\t\tconst built = buildChainExpressionSteps(state, cleanedArgs, ctx);\n\t\t\tif (!built) return;\n\t\t\tconst params: SubagentParamsLike = { chain: built.chain, task: built.task, clarify: false, agentScope: \"both\" };\n\t\t\tif (bg) params.async = true;\n\t\t\tawait runSlashSubagent(pi, ctx, params);\n\t\t},\n\t});\n\n\tpi.registerCommand(\"run-chain\", {\n\t\tdescription: \"Run a saved chain: /run-chain chainName -- task [--bg]\",\n\t\tgetArgumentCompletions: makeChainCompletions(state),\n\t\thandler: async (args, ctx) => {\n\t\t\tconst { args: cleanedArgs, bg } = extractExecutionFlags(args);\n\t\t\tconst delimiterIndex = cleanedArgs.indexOf(\" -- \");\n\t\t\tconst usage = \"Usage: /run-chain <chainName> -- <task> [--bg]\";\n\t\t\tif (delimiterIndex === -1) {\n\t\t\t\tctx.ui.notify(usage, \"error\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst chainName = cleanedArgs.slice(0, delimiterIndex).trim();\n\t\t\tconst task = cleanedArgs.slice(delimiterIndex + 4).trim();\n\t\t\tif (!chainName || !task) {\n\t\t\t\tctx.ui.notify(usage, \"error\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (!state.baseCwd) { ctx.ui.notify(\"Subagent session cwd is not initialized yet\", \"error\"); return; }\n\t\t\tconst chain = discoverSavedChains(state.baseCwd).find((candidate) => candidate.name === chainName);\n\t\t\tif (!chain) {\n\t\t\t\tctx.ui.notify(`Unknown chain: ${chainName}`, \"error\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst params: SubagentParamsLike = { chain: mapSavedChainSteps(chain), task, clarify: false, agentScope: \"both\" };\n\t\t\tif (bg) params.async = true;\n\t\t\tawait runSlashSubagent(pi, ctx, params);\n\t\t},\n\t});\n\n\tpi.registerCommand(\"parallel\", {\n\t\tdescription: \"Run agents in parallel: /parallel scout \\\"task1\\\" -> reviewer \\\"task2\\\" [--bg]\",\n\t\tgetArgumentCompletions: makeAgentCompletions(state, true),\n\t\thandler: async (args, ctx) => {\n\t\t\tconst { args: cleanedArgs, bg } = extractExecutionFlags(args);\n\t\t\tconst parsed = parseAgentArgs(state, cleanedArgs, \"parallel\", ctx);\n\t\t\tif (!parsed) return;\n\t\t\tconst tasks = parsed.steps.map(({ name, config, task: stepTask }) => ({\n\t\t\t\tagent: name,\n\t\t\t\ttask: stepTask ?? parsed.task,\n\t\t\t\t...(config.output !== undefined ? { output: config.output } : {}),\n\t\t\t\t...(config.outputMode !== undefined ? { outputMode: config.outputMode } : {}),\n\t\t\t\t...(config.reads !== undefined ? { reads: config.reads } : {}),\n\t\t\t\t...(config.model ? { model: config.model } : {}),\n\t\t\t\t...(config.skill !== undefined ? { skill: config.skill } : {}),\n\t\t\t\t...(config.progress !== undefined ? { progress: config.progress } : {}),\n\t\t\t}));\n\t\t\tconst params: SubagentParamsLike = { tasks, clarify: false, agentScope: \"both\" };\n\t\t\tif (bg) params.async = true;\n\t\t\tawait runSlashSubagent(pi, ctx, params);\n\t\t},\n\t});\n\n\tpi.registerCommand(\"subagent-cost\", {\n\t\tdescription: \"Show parent and subagent child usage cost for this session\",\n\t\thandler: async (_args, ctx) => {\n\t\t\tsendSlashText(pi, buildSubagentCostReport(ctx));\n\t\t},\n\t});\n\n\tpi.registerCommand(\"subagents-doctor\", {\n\t\tdescription: \"Show subagent diagnostics\",\n\t\thandler: async (_args, ctx) => {\n\t\t\tawait runSlashSubagent(pi, ctx, { action: \"doctor\" });\n\t\t},\n\t});\n\n\tpi.registerCommand(\"subagents-fleet\", {\n\t\tdescription: \"Open the live, inspection-only subagent fleet\",\n\t\thandler: async (_args, ctx) => showFleet(ctx),\n\t});\n\n\tpi.registerShortcut(Key.ctrlAlt(\"f\"), {\n\t\tdescription: \"Open subagent fleet inspector\",\n\t\thandler: async (ctx) => showFleet(ctx),\n\t});\n\n\tpi.registerCommand(\"subagents-stop\", {\n\t\tdescription: \"Stop a current-session async subagent run\",\n\t\thandler: async (args, ctx) => {\n\t\t\tconst id = args.trim();\n\t\t\tif (id) {\n\t\t\t\tawait runSlashSubagent(pi, ctx, { action: \"stop\", id });\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (process.env[SUBAGENT_FANOUT_CHILD_ENV] === \"1\") {\n\t\t\t\tsendSlashText(pi, \"Selector unavailable in child-safe fanout mode. Pass an explicit current-session top-level async run id, for example `/subagents-stop <run-id>` or `subagent({ action: \\\"stop\\\", id: \\\"<run-id>\\\" })`.\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tlet targets: StopSelectorTarget[];\n\t\t\ttry {\n\t\t\t\ttargets = discoverStopTargets(ctx, state);\n\t\t\t} catch (error) {\n\t\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\t\tctx.ui.notify(message, \"error\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (!ctx.hasUI) {\n\t\t\t\tsendSlashText(pi, stopFallbackText(targets));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (targets.length === 0) {\n\t\t\t\tctx.ui.notify(\"No active current-session async runs or scheduled subagent runs to stop.\", \"info\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst result = await ctx.ui.custom<StopSelectorResult>(\n\t\t\t\t(tui, theme, _kb, done) => new SubagentsStopSelector(tui, theme, targets, done),\n\t\t\t\t{ overlay: true, overlayOptions: { anchor: \"center\", width: 88, maxHeight: \"80%\" } },\n\t\t\t);\n\t\t\tif (!result?.confirmed || !result.target) return;\n\t\t\tif (result.target.kind === \"scheduled\") {\n\t\t\t\tawait runSlashSubagent(pi, ctx, { action: \"schedule-cancel\", id: result.target.id });\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tawait runSlashSubagent(pi, ctx, { action: \"stop\", id: result.target.id });\n\t\t},\n\t});\n\n\tregisterPromptWorkflowCommands({\n\t\tpi,\n\t\trun: (params, ctx) => runSlashSubagent(pi, ctx, params),\n\t});\n\n\tpi.registerCommand(\"subagents-models\", {\n\t\tdescription: \"Show runtime-loaded builtin subagent models\",\n\t\tgetArgumentCompletions: makeBuiltinAgentNameCompletions(),\n\t\thandler: async (args, ctx) => {\n\t\t\tconst trimmed = args.trim();\n\t\t\tif (!trimmed) {\n\t\t\t\tawait runSlashSubagent(pi, ctx, { action: \"models\" });\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst parts = trimmed.split(/\\s+/).filter(Boolean);\n\t\t\tif (parts.length !== 1) {\n\t\t\t\tctx.ui.notify(\"Usage: /subagents-models [builtin-agent-name]\", \"error\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst agent = parts[0]!;\n\t\t\tif (!(BUILTIN_AGENT_NAMES as readonly string[]).includes(agent)) {\n\t\t\t\tctx.ui.notify(`Unknown builtin agent: ${agent}`, \"error\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tawait runSlashSubagent(pi, ctx, { action: \"models\", agent });\n\t\t},\n\t});\n\n\tpi.registerCommand(\"subagents-profiles\", {\n\t\tdescription: \"List saved subagent profiles\",\n\t\thandler: async (_args, _ctx) => {\n\t\t\tconst profiles = listSubagentProfiles();\n\t\t\tif (profiles.length === 0) {\n\t\t\t\tsendSlashText(pi, \"Subagent profiles\\n\\nNo subagent profiles found in ~/.pi/agent/profiles/pi-subagents/\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tsendSlashText(pi, `Subagent profiles\\n\\n${profiles.join(\"\\n\")}`);\n\t\t},\n\t});\n\n\tpi.registerCommand(\"subagents-load-profile\", {\n\t\tdescription: \"Load a subagent profile into ~/.pi/agent/settings.json\",\n\t\tgetArgumentCompletions: (prefix) => {\n\t\t\tif (prefix.includes(\" \")) return null;\n\t\t\treturn listSubagentProfiles()\n\t\t\t\t.filter((name) => name.startsWith(prefix))\n\t\t\t\t.map((name) => ({ value: name, label: name }));\n\t\t},\n\t\thandler: async (args, ctx) => {\n\t\t\tconst parsed = parseSingleRequiredArg(args, \"Usage: /subagents-load-profile <name>\");\n\t\t\tif (!parsed.ok) {\n\t\t\t\tctx.ui.notify(parsed.message, \"error\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tawait withSlashStatus(ctx, `Loading profile ${parsed.value}…`, async () => {\n\t\t\t\t\tconst { profile } = readSubagentProfile(parsed.value);\n\t\t\t\t\tconst workerModel = getProfileWorkerModel(profile);\n\t\t\t\t\tconst result = applySubagentProfile(parsed.value);\n\t\t\t\t\tconst lines = [\n\t\t\t\t\t\t`Loaded subagent profile: ${parsed.value}`,\n\t\t\t\t\t\t`Profile: ${result.filePath}`,\n\t\t\t\t\t\t`Updated: ${result.settingsPath}`,\n\t\t\t\t\t];\n\n\t\t\t\t\tif (workerModel && typeof pi.setModel === \"function\" && typeof ctx.modelRegistry?.find === \"function\" && typeof ctx.modelRegistry?.getAvailable === \"function\") {\n\t\t\t\t\t\tconst shouldSwitch = await ctx.ui.confirm(\n\t\t\t\t\t\t\t\"\",\n\t\t\t\t\t\t\t`Profile loaded. Also switch this session to the profile worker model?\\n\\n${workerModel}`,\n\t\t\t\t\t\t);\n\t\t\t\t\t\tif (shouldSwitch) {\n\t\t\t\t\t\t\tconst modelInfo = findModelInfo(workerModel, ctx.modelRegistry.getAvailable().map(toModelInfo));\n\t\t\t\t\t\t\tconst model = modelInfo ? ctx.modelRegistry.find(modelInfo.provider, modelInfo.id) : undefined;\n\t\t\t\t\t\t\tif (!modelInfo || !model) {\n\t\t\t\t\t\t\t\tlines.push(`Could not switch current session model: '${workerModel}' is not available in the current model registry.`);\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tconst success = await pi.setModel(model);\n\t\t\t\t\t\t\t\tif (success) lines.push(`Current session model switched to: ${modelInfo.fullId}`);\n\t\t\t\t\t\t\t\telse lines.push(`Could not switch current session model to '${workerModel}': no API key or provider access is available.`);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (workerModel) {\n\t\t\t\t\t\tlines.push(`Profile worker model: ${workerModel}`);\n\t\t\t\t\t}\n\n\t\t\t\t\tsendSlashText(pi, lines.join(\"\\n\"));\n\t\t\t\t});\n\t\t\t} catch (error) {\n\t\t\t\tctx.ui.notify(error instanceof Error ? error.message : String(error), \"error\");\n\t\t\t}\n\t\t},\n\t});\n\n\tpi.registerCommand(\"subagents-refresh-provider-models\", {\n\t\tdescription: \"Refresh the cached model catalog for one provider\",\n\t\tgetArgumentCompletions: makeProviderCompletions(state),\n\t\thandler: async (args, ctx) => {\n\t\t\tconst trimmed = args.trim();\n\t\t\tconst force = /(?:^|\\s)--force$/.test(trimmed) || /(?:^|\\s)force$/.test(trimmed);\n\t\t\tconst withoutForce = trimmed.replace(/(?:^|\\s)(?:--force|force)$/, \"\").trim();\n\t\t\tconst parsed = parseSingleRequiredArg(withoutForce, \"Usage: /subagents-refresh-provider-models <provider> [--force]\");\n\t\t\tif (!parsed.ok) {\n\t\t\t\tctx.ui.notify(parsed.message, \"error\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tawait withSlashStatus(ctx, `Refreshing provider models for ${parsed.value}…`, async () => {\n\t\t\t\t\tconst result = await refreshProviderModelCatalog(pi, ctx, parsed.value, { force, maxAgeDays: DEFAULT_PROVIDER_MODELS_MAX_AGE_DAYS });\n\t\t\t\t\tconst lines = [\n\t\t\t\t\t\t\"Provider model catalog\",\n\t\t\t\t\t\t`Provider: ${parsed.value}`,\n\t\t\t\t\t\t`Status: ${result.reused ? \"fresh cache reused\" : \"refreshed\"}`,\n\t\t\t\t\t\t`File: ${result.filePath}`,\n\t\t\t\t\t\t`Models: ${result.catalog.models.length}`,\n\t\t\t\t\t\t`Refreshed at: ${result.catalog.refreshedAt}`,\n\t\t\t\t\t];\n\t\t\t\t\tif (result.heuristicFallbackCount > 0) {\n\t\t\t\t\t\tlines.push(`Warning: ${result.heuristicFallbackCount} model${result.heuristicFallbackCount === 1 ? \" was\" : \"s were\"} classified with name heuristics fallback.`);\n\t\t\t\t\t}\n\t\t\t\t\tsendSlashText(pi, lines.join(\"\\n\"));\n\t\t\t\t});\n\t\t\t} catch (error) {\n\t\t\t\tctx.ui.notify(error instanceof Error ? error.message : String(error), \"error\");\n\t\t\t}\n\t\t},\n\t});\n\n\tpi.registerCommand(\"subagents-generate-profiles\", {\n\t\tdescription: \"Generate <provider>.quota and <provider>.quality subagent profiles\",\n\t\tgetArgumentCompletions: makeProviderCompletions(state),\n\t\thandler: async (args, ctx) => {\n\t\t\tconst parsed = parseSingleRequiredArg(args, \"Usage: /subagents-generate-profiles <provider>\");\n\t\t\tif (!parsed.ok) {\n\t\t\t\tctx.ui.notify(parsed.message, \"error\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tawait withSlashStatus(ctx, `Generating profiles for ${parsed.value}…`, async () => {\n\t\t\t\t\tconst result = await generateProfilesForProvider(pi, ctx, parsed.value, { maxAgeDays: DEFAULT_PROVIDER_MODELS_MAX_AGE_DAYS });\n\t\t\t\t\tconst lines = [\n\t\t\t\t\t\t\"Generated subagent profiles\",\n\t\t\t\t\t\t`Provider: ${parsed.value}`,\n\t\t\t\t\t\t`Catalog: ${result.catalogPath}`,\n\t\t\t\t\t\t`Quota: ${result.quotaPath}`,\n\t\t\t\t\t\t` cheap=${result.quotaModels.cheap}`,\n\t\t\t\t\t\t` medium=${result.quotaModels.medium}`,\n\t\t\t\t\t\t` strong=${result.quotaModels.strong}`,\n\t\t\t\t\t\t`Quality: ${result.qualityPath}`,\n\t\t\t\t\t\t` cheap=${result.qualityModels.cheap}`,\n\t\t\t\t\t\t` medium=${result.qualityModels.medium}`,\n\t\t\t\t\t\t` strong=${result.qualityModels.strong}`,\n\t\t\t\t\t];\n\t\t\t\t\tif (result.selectedHeuristicFallbackCount > 0) {\n\t\t\t\t\t\tlines.push(`Warning: generated profiles depend on heuristic-only classification for ${result.selectedHeuristicFallbackCount} selected model${result.selectedHeuristicFallbackCount === 1 ? \"\" : \"s\"}.`);\n\t\t\t\t\t} else if (result.heuristicFallbackCount > 0) {\n\t\t\t\t\t\tlines.push(`Warning: provider catalog still contains ${result.heuristicFallbackCount} heuristic-classified model${result.heuristicFallbackCount === 1 ? \"\" : \"s\"}.`);\n\t\t\t\t\t}\n\t\t\t\t\tsendSlashText(pi, lines.join(\"\\n\"));\n\t\t\t\t});\n\t\t\t} catch (error) {\n\t\t\t\tctx.ui.notify(error instanceof Error ? error.message : String(error), \"error\");\n\t\t\t}\n\t\t},\n\t});\n\n\tpi.registerCommand(\"subagents-check-profile\", {\n\t\tdescription: \"Check whether a saved profile still points to usable models\",\n\t\tgetArgumentCompletions: (prefix) => {\n\t\t\tif (prefix.includes(\" \")) return null;\n\t\t\treturn listSubagentProfiles()\n\t\t\t\t.filter((name) => name.startsWith(prefix))\n\t\t\t\t.map((name) => ({ value: name, label: name }));\n\t\t},\n\t\thandler: async (args, ctx) => {\n\t\t\tconst parsed = parseSingleRequiredArg(args, \"Usage: /subagents-check-profile <name>\");\n\t\t\tif (!parsed.ok) {\n\t\t\t\tctx.ui.notify(parsed.message, \"error\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tawait withSlashStatus(ctx, `Checking profile ${parsed.value}…`, async () => {\n\t\t\t\t\tconst result = await checkSubagentProfile(pi, ctx, parsed.value);\n\t\t\t\t\tconst lines = [\n\t\t\t\t\t\t\"Subagent profile check\",\n\t\t\t\t\t\t`Profile: ${result.profileName}`,\n\t\t\t\t\t\t`File: ${result.filePath}`,\n\t\t\t\t\t\t\"\",\n\t\t\t\t\t\t...result.results.map((entry) => `${entry.agent} → ${entry.model} — registry ${entry.inRegistry ? \"ok\" : \"missing\"}; probe ${entry.probe.status}${entry.probe.message ? ` (${entry.probe.message.split(/\\r?\\n/, 1)[0]})` : \"\"}`),\n\t\t\t\t\t];\n\t\t\t\t\tsendSlashText(pi, lines.join(\"\\n\"));\n\t\t\t\t});\n\t\t\t} catch (error) {\n\t\t\t\tctx.ui.notify(error instanceof Error ? error.message : String(error), \"error\");\n\t\t\t}\n\t\t},\n\t});\n\n}\n"]}
@@ -1155,14 +1155,14 @@ export function registerSlashCommands(pi, state) {
1155
1155
  }
1156
1156
  };
1157
1157
  pi.registerCommand("run", {
1158
- description: "Run a subagent directly: /run agent[output=file] [task] [--bg] [--fork]",
1158
+ description: "Run a subagent directly: /run agent[output=file] [task] [--bg]",
1159
1159
  getArgumentCompletions: makeAgentCompletions(state, false),
1160
1160
  handler: async (args, ctx) => {
1161
- const { args: cleanedArgs, bg, fork } = extractExecutionFlags(args);
1161
+ const { args: cleanedArgs, bg } = extractExecutionFlags(args);
1162
1162
  const input = cleanedArgs.trim();
1163
1163
  const firstSpace = input.indexOf(" ");
1164
1164
  if (!input) {
1165
- ctx.ui.notify("Usage: /run <agent> [task] [--bg] [--fork]", "error");
1165
+ ctx.ui.notify("Usage: /run <agent> [task] [--bg]", "error");
1166
1166
  return;
1167
1167
  }
1168
1168
  const { name: agentName, config: inline } = parseAgentToken(firstSpace === -1 ? input : input.slice(0, firstSpace));
@@ -1191,34 +1191,30 @@ export function registerSlashCommands(pi, state) {
1191
1191
  params.model = inline.model;
1192
1192
  if (bg)
1193
1193
  params.async = true;
1194
- if (fork)
1195
- params.context = "fork";
1196
1194
  await runSlashSubagent(pi, ctx, params);
1197
1195
  },
1198
1196
  });
1199
1197
  pi.registerCommand("chain", {
1200
- description: "Run agents in sequence: /chain scout \"task\" -> planner [--bg] [--fork]",
1198
+ description: "Run agents in sequence: /chain scout \"task\" -> planner [--bg]",
1201
1199
  getArgumentCompletions: makeAgentCompletions(state, true),
1202
1200
  handler: async (args, ctx) => {
1203
- const { args: cleanedArgs, bg, fork } = extractExecutionFlags(args);
1201
+ const { args: cleanedArgs, bg } = extractExecutionFlags(args);
1204
1202
  const built = buildChainExpressionSteps(state, cleanedArgs, ctx);
1205
1203
  if (!built)
1206
1204
  return;
1207
1205
  const params = { chain: built.chain, task: built.task, clarify: false, agentScope: "both" };
1208
1206
  if (bg)
1209
1207
  params.async = true;
1210
- if (fork)
1211
- params.context = "fork";
1212
1208
  await runSlashSubagent(pi, ctx, params);
1213
1209
  },
1214
1210
  });
1215
1211
  pi.registerCommand("run-chain", {
1216
- description: "Run a saved chain: /run-chain chainName -- task [--bg] [--fork]",
1212
+ description: "Run a saved chain: /run-chain chainName -- task [--bg]",
1217
1213
  getArgumentCompletions: makeChainCompletions(state),
1218
1214
  handler: async (args, ctx) => {
1219
- const { args: cleanedArgs, bg, fork } = extractExecutionFlags(args);
1215
+ const { args: cleanedArgs, bg } = extractExecutionFlags(args);
1220
1216
  const delimiterIndex = cleanedArgs.indexOf(" -- ");
1221
- const usage = "Usage: /run-chain <chainName> -- <task> [--bg] [--fork]";
1217
+ const usage = "Usage: /run-chain <chainName> -- <task> [--bg]";
1222
1218
  if (delimiterIndex === -1) {
1223
1219
  ctx.ui.notify(usage, "error");
1224
1220
  return;
@@ -1241,16 +1237,14 @@ export function registerSlashCommands(pi, state) {
1241
1237
  const params = { chain: mapSavedChainSteps(chain), task, clarify: false, agentScope: "both" };
1242
1238
  if (bg)
1243
1239
  params.async = true;
1244
- if (fork)
1245
- params.context = "fork";
1246
1240
  await runSlashSubagent(pi, ctx, params);
1247
1241
  },
1248
1242
  });
1249
1243
  pi.registerCommand("parallel", {
1250
- description: "Run agents in parallel: /parallel scout \"task1\" -> reviewer \"task2\" [--bg] [--fork]",
1244
+ description: "Run agents in parallel: /parallel scout \"task1\" -> reviewer \"task2\" [--bg]",
1251
1245
  getArgumentCompletions: makeAgentCompletions(state, true),
1252
1246
  handler: async (args, ctx) => {
1253
- const { args: cleanedArgs, bg, fork } = extractExecutionFlags(args);
1247
+ const { args: cleanedArgs, bg } = extractExecutionFlags(args);
1254
1248
  const parsed = parseAgentArgs(state, cleanedArgs, "parallel", ctx);
1255
1249
  if (!parsed)
1256
1250
  return;
@@ -1267,8 +1261,6 @@ export function registerSlashCommands(pi, state) {
1267
1261
  const params = { tasks, clarify: false, agentScope: "both" };
1268
1262
  if (bg)
1269
1263
  params.async = true;
1270
- if (fork)
1271
- params.context = "fork";
1272
1264
  await runSlashSubagent(pi, ctx, params);
1273
1265
  },
1274
1266
  });