@fitchmultz/pi-subagents 0.44.7 → 0.46.0

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 (955) hide show
  1. package/CHANGELOG.md +264 -7
  2. package/LICENSE +1 -0
  3. package/README.md +58 -1483
  4. package/agents/context-builder.md +19 -1
  5. package/agents/debugger.md +10 -3
  6. package/agents/delegate.md +1 -0
  7. package/agents/fixer.md +4 -1
  8. package/agents/oracle.md +13 -2
  9. package/agents/planner.md +14 -3
  10. package/agents/researcher.md +11 -2
  11. package/agents/reviewer-claude.md +11 -2
  12. package/agents/reviewer-gpt.md +12 -2
  13. package/agents/reviewer-ponytail.md +11 -1
  14. package/agents/reviewer-security.md +13 -2
  15. package/agents/reviewer.md +12 -3
  16. package/agents/scout.md +12 -1
  17. package/agents/ui-designer.md +10 -3
  18. package/agents/watcher.md +1 -0
  19. package/agents/worker.md +8 -2
  20. package/agents/writer.md +6 -3
  21. package/dist/agents/agent-defaults.js +9 -0
  22. package/dist/agents/agent-definition.js +196 -0
  23. package/dist/agents/agent-management.js +64 -905
  24. package/dist/agents/agent-scope.js +4 -2
  25. package/dist/agents/agent-selection.js +10 -5
  26. package/dist/agents/agent-serializer.js +55 -67
  27. package/dist/agents/agents.js +75 -716
  28. package/dist/agents/builtin-overrides.js +218 -0
  29. package/dist/agents/chain-serializer.js +3 -367
  30. package/dist/agents/config-values.js +52 -0
  31. package/dist/agents/discovery-paths.js +91 -0
  32. package/dist/agents/frontmatter.js +15 -5
  33. package/dist/agents/identity.js +25 -8
  34. package/dist/agents/json-chain.js +254 -0
  35. package/dist/agents/management-config.js +332 -0
  36. package/dist/agents/management-format.js +231 -0
  37. package/dist/agents/management-persistence.js +287 -0
  38. package/dist/agents/management-targets.js +136 -0
  39. package/dist/agents/management-warnings.js +81 -0
  40. package/dist/agents/markdown-chain.js +210 -0
  41. package/dist/agents/package-agents.js +154 -0
  42. package/dist/agents/skill-files.js +157 -0
  43. package/dist/agents/skill-search-paths.js +246 -0
  44. package/dist/agents/skill-types.js +1 -0
  45. package/dist/agents/skills.js +54 -499
  46. package/dist/extension/acceptance-schema.js +55 -0
  47. package/dist/extension/build-info.js +1 -1
  48. package/dist/extension/chain-schema.js +196 -0
  49. package/dist/extension/child-runtime.js +237 -0
  50. package/dist/extension/compact-tools.js +122 -51
  51. package/dist/extension/config-schema.js +29 -0
  52. package/dist/extension/config.js +27 -11
  53. package/dist/extension/control-notice-schema.js +37 -0
  54. package/dist/extension/control-notices.js +13 -6
  55. package/dist/extension/delegation-bridges.js +43 -0
  56. package/dist/extension/doctor.js +48 -25
  57. package/dist/extension/everyday-schemas.js +207 -0
  58. package/dist/extension/fanout-child.js +18 -319
  59. package/dist/extension/index.js +8 -563
  60. package/dist/extension/message-renderers.js +225 -0
  61. package/dist/extension/nested-control-inbox.js +154 -0
  62. package/dist/extension/parent-runtime.js +339 -0
  63. package/dist/extension/primary-tool.js +69 -0
  64. package/dist/extension/result-snapshot.js +22 -0
  65. package/dist/extension/runtime-reload.js +62 -0
  66. package/dist/extension/runtime-storage.js +43 -0
  67. package/dist/extension/schema-overrides.js +75 -0
  68. package/dist/extension/schemas.js +265 -336
  69. package/dist/extension/tool-input.js +27 -12
  70. package/dist/extension/tool-result.js +19 -4
  71. package/dist/history/answer-texts.js +57 -0
  72. package/dist/history/canonical-result.js +76 -43
  73. package/dist/history/canonical-selection.js +40 -0
  74. package/dist/history/database-generation.js +82 -0
  75. package/dist/history/database.js +57 -33
  76. package/dist/history/entry-ingestion.js +61 -0
  77. package/dist/history/index.js +242 -97
  78. package/dist/history/ingest.js +168 -331
  79. package/dist/history/output-digests.js +40 -0
  80. package/dist/history/preview.js +210 -0
  81. package/dist/history/prune.js +33 -0
  82. package/dist/history/queries.js +60 -349
  83. package/dist/history/query-scope.js +190 -0
  84. package/dist/history/rows.js +77 -0
  85. package/dist/history/run-projection.js +137 -0
  86. package/dist/history/run-queries.js +153 -0
  87. package/dist/history/schema.js +31 -0
  88. package/dist/history/search-queries.js +163 -0
  89. package/dist/history/selected-record.js +108 -0
  90. package/dist/history/source-file.js +45 -0
  91. package/dist/history/source-watches.js +141 -0
  92. package/dist/history/store.js +164 -153
  93. package/dist/history/text-chunks.js +169 -0
  94. package/dist/history/text.js +35 -0
  95. package/dist/history/transcript-queries.js +177 -0
  96. package/dist/history/types.js +5 -1
  97. package/dist/history/values.js +36 -0
  98. package/dist/history/wire-inputs.js +122 -0
  99. package/dist/history/wire-models.js +38 -0
  100. package/dist/history/worker-runtime.js +441 -0
  101. package/dist/history/worker.js +66 -462
  102. package/dist/intercom/intercom-bridge.js +48 -20
  103. package/dist/intercom/live-intercom.js +80 -23
  104. package/dist/intercom/nested-result-tree.js +163 -0
  105. package/dist/intercom/result-delivery.js +40 -0
  106. package/dist/intercom/result-intercom.js +178 -261
  107. package/dist/pi-intercom/agent-dir.js +7 -3
  108. package/dist/pi-intercom/async.js +27 -0
  109. package/dist/pi-intercom/bridge-ask.js +57 -0
  110. package/dist/pi-intercom/bridge-config.js +89 -0
  111. package/dist/pi-intercom/bridge-http.js +86 -0
  112. package/dist/pi-intercom/bridge-launch-policy.js +88 -0
  113. package/dist/pi-intercom/bridge-launch.js +217 -0
  114. package/dist/pi-intercom/bridge-protocol.js +106 -0
  115. package/dist/pi-intercom/bridge-routes.js +72 -0
  116. package/dist/pi-intercom/bridge-rpc-process.js +264 -0
  117. package/dist/pi-intercom/bridge-session.js +420 -0
  118. package/dist/pi-intercom/bridge.js +221 -606
  119. package/dist/pi-intercom/broker/broker.js +236 -244
  120. package/dist/pi-intercom/broker/client.js +189 -304
  121. package/dist/pi-intercom/broker/connection.js +165 -0
  122. package/dist/pi-intercom/broker/framing.js +5 -3
  123. package/dist/pi-intercom/broker/paths.js +4 -1
  124. package/dist/pi-intercom/broker/pid.js +53 -25
  125. package/dist/pi-intercom/broker/snapshot-reader.js +78 -0
  126. package/dist/pi-intercom/broker/snapshots.js +188 -0
  127. package/dist/pi-intercom/broker/spawn.js +36 -25
  128. package/dist/pi-intercom/config.js +85 -71
  129. package/dist/pi-intercom/connection.js +297 -0
  130. package/dist/pi-intercom/identity.js +58 -0
  131. package/dist/pi-intercom/inbound-delivery.js +356 -0
  132. package/dist/pi-intercom/inbound-journal.js +303 -0
  133. package/dist/pi-intercom/inbound-record.js +113 -0
  134. package/dist/pi-intercom/inbound.js +179 -0
  135. package/dist/pi-intercom/index.js +79 -2933
  136. package/dist/pi-intercom/inspection.js +62 -0
  137. package/dist/pi-intercom/interview.js +236 -0
  138. package/dist/pi-intercom/lifecycle.js +168 -0
  139. package/dist/pi-intercom/message-format.js +69 -0
  140. package/dist/pi-intercom/outbound.js +304 -0
  141. package/dist/pi-intercom/peer-awareness.js +66 -0
  142. package/dist/pi-intercom/reply-tracker.js +62 -43
  143. package/dist/pi-intercom/reply-wait.js +201 -0
  144. package/dist/pi-intercom/runtime-types.js +3 -0
  145. package/dist/pi-intercom/session-events.js +180 -0
  146. package/dist/pi-intercom/session-targets.js +42 -34
  147. package/dist/pi-intercom/subagent-events.js +238 -0
  148. package/dist/pi-intercom/subagent-live-events.js +129 -86
  149. package/dist/pi-intercom/supervisor.js +282 -0
  150. package/dist/pi-intercom/tool-arguments.js +74 -0
  151. package/dist/pi-intercom/tool-registration.js +95 -0
  152. package/dist/pi-intercom/tool-results.js +38 -0
  153. package/dist/pi-intercom/tool-schemas.js +82 -0
  154. package/dist/pi-intercom/tools.js +51 -0
  155. package/dist/pi-intercom/topic-actions.js +177 -0
  156. package/dist/pi-intercom/topics.js +177 -92
  157. package/dist/pi-intercom/transport.js +1 -0
  158. package/dist/pi-intercom/types.js +151 -114
  159. package/dist/pi-intercom/ui/compose-paste.js +93 -0
  160. package/dist/pi-intercom/ui/compose.js +121 -143
  161. package/dist/pi-intercom/ui/inline-message.js +59 -59
  162. package/dist/pi-intercom/ui/message-renderers.js +147 -0
  163. package/dist/pi-intercom/ui/overlay.js +181 -0
  164. package/dist/pi-intercom/ui/session-format.js +124 -0
  165. package/dist/pi-intercom/ui/session-list.js +76 -43
  166. package/dist/pi-intercom/ui/tool-renderers.js +110 -0
  167. package/dist/pi-intercom/ui/topic-view.js +88 -0
  168. package/dist/pi-intercom/validation.js +59 -0
  169. package/dist/runs/background/async-chain-plan.js +265 -0
  170. package/dist/runs/background/async-control.js +134 -58
  171. package/dist/runs/background/async-execution.js +136 -633
  172. package/dist/runs/background/async-job-admission.js +40 -0
  173. package/dist/runs/background/async-job-projection.js +169 -0
  174. package/dist/runs/background/async-job-tracker.js +297 -327
  175. package/dist/runs/background/async-launch.js +216 -0
  176. package/dist/runs/background/async-plan.js +116 -0
  177. package/dist/runs/background/async-result-file.js +47 -23
  178. package/dist/runs/background/async-resume.js +195 -253
  179. package/dist/runs/background/async-run-discovery.js +213 -0
  180. package/dist/runs/background/async-run-location.js +135 -0
  181. package/dist/runs/background/async-run-record.js +1 -0
  182. package/dist/runs/background/async-run-repair.js +223 -0
  183. package/dist/runs/background/async-run-summary.js +1 -0
  184. package/dist/runs/background/async-single-plan.js +105 -0
  185. package/dist/runs/background/async-status-projection.js +155 -0
  186. package/dist/runs/background/async-status-render.js +117 -0
  187. package/dist/runs/background/async-status-validation.js +181 -0
  188. package/dist/runs/background/async-status.js +3 -442
  189. package/dist/runs/background/async-value.js +21 -0
  190. package/dist/runs/background/completion-dedupe.js +36 -12
  191. package/dist/runs/background/completion-delivery.js +211 -205
  192. package/dist/runs/background/completion-receipts.js +237 -0
  193. package/dist/runs/background/legacy-status-acceptance.js +34 -0
  194. package/dist/runs/background/notify.js +96 -48
  195. package/dist/runs/background/parallel-groups.js +36 -19
  196. package/dist/runs/background/result-payload.js +178 -0
  197. package/dist/runs/background/result-watcher.js +348 -339
  198. package/dist/runs/background/run-id-resolver.js +53 -33
  199. package/dist/runs/background/run-schemas.js +189 -0
  200. package/dist/runs/background/run-status-contracts.js +14 -0
  201. package/dist/runs/background/run-status-render.js +270 -0
  202. package/dist/runs/background/run-status.js +231 -339
  203. package/dist/runs/background/runner-attempt-output.js +155 -0
  204. package/dist/runs/background/runner-attempt.js +263 -0
  205. package/dist/runs/background/runner-child-executor.js +166 -0
  206. package/dist/runs/background/runner-child-observer.js +192 -0
  207. package/dist/runs/background/runner-completion-summary.js +71 -0
  208. package/dist/runs/background/runner-completion.js +211 -0
  209. package/dist/runs/background/runner-config.js +1 -0
  210. package/dist/runs/background/runner-contract.js +1 -0
  211. package/dist/runs/background/runner-dynamic.js +121 -0
  212. package/dist/runs/background/runner-finalization.js +149 -0
  213. package/dist/runs/background/runner-graph.js +68 -0
  214. package/dist/runs/background/runner-initial-status.js +60 -0
  215. package/dist/runs/background/runner-lifecycle.js +142 -0
  216. package/dist/runs/background/runner-monitor.js +309 -0
  217. package/dist/runs/background/runner-parallel.js +204 -0
  218. package/dist/runs/background/runner-prepare-step.js +86 -0
  219. package/dist/runs/background/runner-reporting.js +153 -0
  220. package/dist/runs/background/runner-status.js +148 -0
  221. package/dist/runs/background/runner-step-output.js +92 -0
  222. package/dist/runs/background/runner-step-record.js +45 -0
  223. package/dist/runs/background/runner-step.js +112 -0
  224. package/dist/runs/background/runner-streaming.js +173 -0
  225. package/dist/runs/background/runner-workflow.js +213 -0
  226. package/dist/runs/background/stale-run-reconciler.js +155 -292
  227. package/dist/runs/background/subagent-runner-launcher.js +33 -13
  228. package/dist/runs/background/subagent-runner.js +51 -1842
  229. package/dist/runs/background/top-level-async.js +2 -1
  230. package/dist/runs/foreground/chain-clarify.js +293 -1017
  231. package/dist/runs/foreground/clarification-draft.js +87 -0
  232. package/dist/runs/foreground/clarify-contracts.js +1 -0
  233. package/dist/runs/foreground/clarify-invocation.js +129 -38
  234. package/dist/runs/foreground/clarify-text-editor.js +210 -0
  235. package/dist/runs/foreground/clarify-view.js +167 -0
  236. package/dist/runs/foreground/execution-input.js +226 -353
  237. package/dist/runs/foreground/execution-routing.js +33 -0
  238. package/dist/runs/foreground/execution-timeout.js +81 -0
  239. package/dist/runs/foreground/foreground-control.js +222 -763
  240. package/dist/runs/foreground/foreground-memory.js +99 -0
  241. package/dist/runs/foreground/invocation-context.js +55 -0
  242. package/dist/runs/foreground/invocation-execution.js +331 -0
  243. package/dist/runs/foreground/invocation-expansion.js +48 -0
  244. package/dist/runs/foreground/management-actions.js +295 -0
  245. package/dist/runs/foreground/nested-control.js +179 -0
  246. package/dist/runs/foreground/question-continuation.js +78 -0
  247. package/dist/runs/foreground/question-control.js +155 -50
  248. package/dist/runs/foreground/resume-target.js +221 -0
  249. package/dist/runs/foreground/revival-launch-policy.js +58 -0
  250. package/dist/runs/foreground/run-async-path.js +190 -178
  251. package/dist/runs/foreground/run-interrupt.js +164 -0
  252. package/dist/runs/foreground/run-nudge.js +278 -0
  253. package/dist/runs/foreground/saved-revival.js +251 -0
  254. package/dist/runs/foreground/subagent-executor.js +135 -424
  255. package/dist/runs/foreground/subagent-params.js +67 -30
  256. package/dist/runs/foreground/wait-observation.js +104 -0
  257. package/dist/runs/foreground/wait-registration.js +17 -0
  258. package/dist/runs/foreground/wait-run.js +283 -123
  259. package/dist/runs/shared/acceptance-contract.js +193 -99
  260. package/dist/runs/shared/acceptance-defaults.js +1 -0
  261. package/dist/runs/shared/acceptance-evaluation.js +201 -181
  262. package/dist/runs/shared/acceptance-finalization.js +256 -180
  263. package/dist/runs/shared/acceptance-outcome.js +53 -0
  264. package/dist/runs/shared/acceptance-reports.js +137 -72
  265. package/dist/runs/shared/acceptance-submission.js +167 -0
  266. package/dist/runs/shared/acceptance-verification.js +100 -0
  267. package/dist/runs/shared/chain-outputs.js +80 -52
  268. package/dist/runs/shared/child-attempt-types.js +1 -0
  269. package/dist/runs/shared/child-attempt.js +314 -667
  270. package/dist/runs/shared/child-boundaries.js +173 -0
  271. package/dist/runs/shared/child-context-filter.js +44 -0
  272. package/dist/runs/shared/child-event-observer.js +390 -0
  273. package/dist/runs/shared/child-execution-cwd.js +73 -43
  274. package/dist/runs/shared/child-execution-outcome.js +72 -0
  275. package/dist/runs/shared/child-invocation.js +54 -0
  276. package/dist/runs/shared/child-message-validation.js +215 -0
  277. package/dist/runs/shared/child-native-accounting.js +106 -0
  278. package/dist/runs/shared/child-native-observation.js +69 -0
  279. package/dist/runs/shared/child-observation-spool.js +164 -0
  280. package/dist/runs/shared/child-observations.js +157 -76
  281. package/dist/runs/shared/child-presence.js +3 -0
  282. package/dist/runs/shared/child-route-env.js +119 -0
  283. package/dist/runs/shared/child-stream-observer.js +221 -0
  284. package/dist/runs/shared/child-stream-projection.js +137 -0
  285. package/dist/runs/shared/child-structured-tool.js +27 -0
  286. package/dist/runs/shared/claude-code.js +121 -158
  287. package/dist/runs/shared/claude-model.js +42 -0
  288. package/dist/runs/shared/claude-session.js +83 -0
  289. package/dist/runs/shared/claude-types.js +1 -0
  290. package/dist/runs/shared/completion-guard.js +18 -18
  291. package/dist/runs/shared/control-event-policy.js +8 -0
  292. package/dist/runs/shared/control-notification-owner.js +13 -0
  293. package/dist/runs/shared/dynamic-fanout.js +188 -173
  294. package/dist/runs/shared/dynamic-item.js +102 -0
  295. package/dist/runs/shared/history-index.js +62 -36
  296. package/dist/runs/shared/legacy-async-recovery.js +114 -0
  297. package/dist/runs/shared/legacy-output-recovery.js +148 -0
  298. package/dist/runs/shared/mcp-config-schema.js +36 -0
  299. package/dist/runs/shared/mcp-direct-tool-allowlist.js +147 -116
  300. package/dist/runs/shared/model-fallback.js +97 -38
  301. package/dist/runs/shared/mutating-tool-guard.js +128 -88
  302. package/dist/runs/shared/native-finalization-types.js +1 -0
  303. package/dist/runs/shared/native-finalization.js +155 -76
  304. package/dist/runs/shared/native-usage.js +238 -98
  305. package/dist/runs/shared/nested-control-records.js +128 -0
  306. package/dist/runs/shared/nested-events.js +8 -700
  307. package/dist/runs/shared/nested-path.js +32 -28
  308. package/dist/runs/shared/nested-projection.js +155 -0
  309. package/dist/runs/shared/nested-protocol.js +18 -0
  310. package/dist/runs/shared/nested-registry.js +191 -0
  311. package/dist/runs/shared/nested-render.js +106 -58
  312. package/dist/runs/shared/nested-route.js +220 -0
  313. package/dist/runs/shared/nested-validation.js +216 -0
  314. package/dist/runs/shared/owned-child-evidence.js +125 -0
  315. package/dist/runs/shared/owned-run-list.js +146 -0
  316. package/dist/runs/shared/owned-run-observation.js +267 -0
  317. package/dist/runs/shared/owned-run-progress.js +109 -0
  318. package/dist/runs/shared/owned-run-read-state.js +1 -0
  319. package/dist/runs/shared/owned-run-results.js +202 -0
  320. package/dist/runs/shared/owned-run-status.js +180 -0
  321. package/dist/runs/shared/owned-run-view.js +51 -0
  322. package/dist/runs/shared/owned-workflow-details.js +17 -0
  323. package/dist/runs/shared/owner-output.js +43 -0
  324. package/dist/runs/shared/parallel-utils.js +36 -28
  325. package/dist/runs/shared/parent-receipts.js +90 -32
  326. package/dist/runs/shared/parent-usage.js +169 -57
  327. package/dist/runs/shared/pi-args.js +108 -201
  328. package/dist/runs/shared/pi-launch-input.js +1 -0
  329. package/dist/runs/shared/pi-session-launch.js +86 -0
  330. package/dist/runs/shared/pi-spawn.js +35 -21
  331. package/dist/runs/shared/run-history-queries.js +126 -35
  332. package/dist/runs/shared/run-history.js +101 -35
  333. package/dist/runs/shared/run-metadata-paths.js +14 -0
  334. package/dist/runs/shared/run-metadata.js +158 -0
  335. package/dist/runs/shared/run-persistence.js +131 -0
  336. package/dist/runs/shared/run-records.js +9 -679
  337. package/dist/runs/shared/run-restoration.js +338 -0
  338. package/dist/runs/shared/run-state-owner.js +26 -0
  339. package/dist/runs/shared/session-cwd-preload.js +27 -18
  340. package/dist/runs/shared/single-output.js +148 -70
  341. package/dist/runs/shared/stable-value.js +15 -0
  342. package/dist/runs/shared/streaming-text.js +23 -12
  343. package/dist/runs/shared/structured-output.js +25 -13
  344. package/dist/runs/shared/subagent-control.js +167 -100
  345. package/dist/runs/shared/subagent-prompt-runtime.js +27 -130
  346. package/dist/runs/shared/subagent-tool-loop-guard.js +81 -52
  347. package/dist/runs/shared/supervisor-question-state.js +145 -0
  348. package/dist/runs/shared/supervisor-questions.js +185 -254
  349. package/dist/runs/shared/workflow-graph.js +259 -167
  350. package/dist/runs/shared/workflow-policy.js +120 -48
  351. package/dist/runs/shared/worktree-contract.js +1 -0
  352. package/dist/runs/shared/worktree-hook.js +138 -0
  353. package/dist/runs/shared/worktree-location.js +47 -0
  354. package/dist/runs/shared/worktree-preservation.js +8 -0
  355. package/dist/runs/shared/worktree-process.js +107 -0
  356. package/dist/runs/shared/worktree.js +183 -322
  357. package/dist/shared/agent-context-policy.js +34 -44
  358. package/dist/shared/agent-dir.js +14 -0
  359. package/dist/shared/artifacts.js +30 -12
  360. package/dist/shared/atomic-json.js +4 -1
  361. package/dist/shared/chain-files.js +72 -0
  362. package/dist/shared/concurrency.js +18 -0
  363. package/dist/shared/content-preview.js +115 -0
  364. package/dist/shared/cooperative.js +5 -2
  365. package/dist/shared/execution-cwd.js +13 -5
  366. package/dist/shared/file-coalescer.js +2 -1
  367. package/dist/shared/fork-context.js +10 -12
  368. package/dist/shared/formatters.js +45 -19
  369. package/dist/shared/journal-frames.js +164 -0
  370. package/dist/shared/journal-projections.js +121 -0
  371. package/dist/shared/journal-reader.js +6 -407
  372. package/dist/shared/json-projection.js +191 -0
  373. package/dist/shared/lazy-tools.js +19 -8
  374. package/dist/shared/message-output.js +116 -0
  375. package/dist/shared/model-info.js +31 -14
  376. package/dist/shared/native-import.js +20 -0
  377. package/dist/shared/native-journal.js +140 -0
  378. package/dist/shared/native-session-loader.js +17 -0
  379. package/dist/shared/native-session.js +2 -13
  380. package/dist/shared/native-tui.js +7 -2
  381. package/dist/shared/native-typebox.js +15 -3
  382. package/dist/shared/output-page.js +63 -0
  383. package/dist/shared/post-exit-stdio-guard.js +128 -88
  384. package/dist/shared/prompt-sections.js +2 -0
  385. package/dist/shared/session-entries.js +70 -35
  386. package/dist/shared/session-identity.js +5 -3
  387. package/dist/shared/settings.js +62 -192
  388. package/dist/shared/status-format.js +97 -37
  389. package/dist/shared/task-file-policy.js +24 -0
  390. package/dist/shared/temp-root.js +44 -15
  391. package/dist/shared/types/acceptance.js +1 -0
  392. package/dist/shared/types/async.js +1 -0
  393. package/dist/shared/types/config.js +1 -0
  394. package/dist/shared/types/details.js +1 -0
  395. package/dist/shared/types/history.js +1 -0
  396. package/dist/shared/types/inputs.js +1 -0
  397. package/dist/shared/types/intercom.js +1 -0
  398. package/dist/shared/types/launch.js +1 -0
  399. package/dist/shared/types/messages.js +1 -0
  400. package/dist/shared/types/nested.js +1 -0
  401. package/dist/shared/types/owned-runs.js +1 -0
  402. package/dist/shared/types/progress.js +1 -0
  403. package/dist/shared/types/questions.js +1 -0
  404. package/dist/shared/types/results.js +1 -0
  405. package/dist/shared/types/state.js +1 -0
  406. package/dist/shared/types/usage.js +1 -0
  407. package/dist/shared/types/workflow.js +1 -0
  408. package/dist/shared/types.js +75 -30
  409. package/dist/shared/unknown.js +52 -0
  410. package/dist/shared/utils.js +54 -260
  411. package/dist/slash/command-discovery.js +82 -0
  412. package/dist/slash/command-input.js +190 -0
  413. package/dist/slash/prompt-template-bridge.js +94 -270
  414. package/dist/slash/prompt-template-contract.js +77 -0
  415. package/dist/slash/prompt-template-results.js +125 -0
  416. package/dist/slash/request-protocol.js +68 -0
  417. package/dist/slash/result-delivery.js +237 -0
  418. package/dist/slash/saved-chain.js +78 -0
  419. package/dist/slash/slash-bridge.js +123 -89
  420. package/dist/slash/slash-commands.js +101 -572
  421. package/dist/slash/slash-live-state.js +68 -47
  422. package/dist/tui/action-hints.js +50 -22
  423. package/dist/tui/activity-lines.js +32 -0
  424. package/dist/tui/agent-browser.js +290 -0
  425. package/dist/tui/agent-controls.js +289 -0
  426. package/dist/tui/agent-conversation.js +444 -0
  427. package/dist/tui/agent-dock.js +171 -0
  428. package/dist/tui/agent-history.js +50 -183
  429. package/dist/tui/agent-model.js +62 -0
  430. package/dist/tui/agent-picker.js +262 -0
  431. package/dist/tui/agent-view.js +232 -1564
  432. package/dist/tui/compact-results.js +193 -0
  433. package/dist/tui/conversation-details.js +190 -0
  434. package/dist/tui/conversation-frame.js +135 -0
  435. package/dist/tui/conversation-history.js +201 -0
  436. package/dist/tui/conversation-menu.js +78 -0
  437. package/dist/tui/conversation-notices.js +79 -0
  438. package/dist/tui/conversation-selection.js +58 -0
  439. package/dist/tui/conversation-viewport.js +129 -0
  440. package/dist/tui/direction-message.js +27 -0
  441. package/dist/tui/display.js +282 -0
  442. package/dist/tui/editor-viewport.js +18 -0
  443. package/dist/tui/expanded-content.js +87 -0
  444. package/dist/tui/expanded-multi.js +170 -0
  445. package/dist/tui/expanded-single.js +65 -0
  446. package/dist/tui/history-cards.js +216 -0
  447. package/dist/tui/history-display.js +238 -0
  448. package/dist/tui/history-json.js +16 -0
  449. package/dist/tui/history-metadata.js +152 -0
  450. package/dist/tui/history-record.js +251 -0
  451. package/dist/tui/history-text.js +34 -0
  452. package/dist/tui/indexed-history.js +116 -0
  453. package/dist/tui/nested-widget.js +120 -0
  454. package/dist/tui/picker-page.js +59 -0
  455. package/dist/tui/render.js +58 -1501
  456. package/dist/tui/result-facts.js +64 -0
  457. package/dist/tui/task-observation.js +59 -0
  458. package/dist/tui/task-store.js +366 -0
  459. package/dist/tui/text-values.js +16 -0
  460. package/dist/tui/view-connections.js +47 -0
  461. package/dist/tui/view-model.js +94 -0
  462. package/dist/tui/view-persistence.js +51 -0
  463. package/dist/tui/view-ports.js +1 -0
  464. package/dist/tui/view-session.js +37 -0
  465. package/dist/tui/widget-details.js +144 -0
  466. package/dist/tui/widget-status.js +245 -0
  467. package/dist/tui/widget.js +145 -0
  468. package/dist/tui/workflow-labels.js +223 -0
  469. package/dist/tui/workflow-outcomes.js +124 -0
  470. package/docs/code-quality.md +38 -0
  471. package/docs/development.md +99 -0
  472. package/docs/intercom-bridge.md +77 -22
  473. package/docs/intercom.md +132 -106
  474. package/docs/performance.md +19 -17
  475. package/docs/reference.md +1472 -0
  476. package/generated/RunValidators.d.ts +28 -0
  477. package/generated/RunValidators.js +2 -0
  478. package/package.json +27 -6
  479. package/prompts/parallel-cleanup.md +3 -0
  480. package/prompts/parallel-context-build.md +3 -0
  481. package/prompts/parallel-handoff-plan.md +6 -0
  482. package/prompts/parallel-research.md +4 -0
  483. package/prompts/parallel-review.md +2 -0
  484. package/prompts/review-loop.md +2 -0
  485. package/scripts/build.mjs +129 -85
  486. package/scripts/check-compat.mjs +48 -11
  487. package/scripts/compat-host.mjs +35 -7
  488. package/scripts/generate-run-schemas.mjs +210 -0
  489. package/scripts/intercom-bridge-credentials.mjs +158 -29
  490. package/scripts/intercom-remote.mjs +213 -52
  491. package/scripts/local-install-smoke.mjs +47 -37
  492. package/scripts/native-package-smoke.mjs +135 -25
  493. package/scripts/package-smoke.mjs +360 -140
  494. package/scripts/prepare.mjs +54 -34
  495. package/scripts/real-pi-processes.mjs +261 -0
  496. package/scripts/real-pi-smoke.mjs +432 -351
  497. package/scripts/run-tests.mjs +29 -15
  498. package/skills/pi-intercom/SKILL.md +11 -11
  499. package/skills/pi-intercom/references/peer-sessions.md +7 -2
  500. package/skills/pi-subagents/SKILL.md +1 -1
  501. package/skills/pi-subagents/references/full-orchestration-guide.md +254 -112
  502. package/src/agents/agent-defaults.ts +13 -0
  503. package/src/agents/agent-definition.ts +253 -0
  504. package/src/agents/agent-management.ts +111 -808
  505. package/src/agents/agent-scope.ts +7 -3
  506. package/src/agents/agent-selection.ts +26 -16
  507. package/src/agents/agent-serializer.ts +84 -93
  508. package/src/agents/agents.ts +199 -906
  509. package/src/agents/builtin-overrides.ts +297 -0
  510. package/src/agents/chain-serializer.ts +3 -329
  511. package/src/agents/config-values.ts +57 -0
  512. package/src/agents/discovery-paths.ts +134 -0
  513. package/src/agents/frontmatter.ts +35 -17
  514. package/src/agents/identity.ts +49 -18
  515. package/src/agents/json-chain.ts +305 -0
  516. package/src/agents/management-config.ts +342 -0
  517. package/src/agents/management-format.ts +243 -0
  518. package/src/agents/management-persistence.ts +408 -0
  519. package/src/agents/management-targets.ts +260 -0
  520. package/src/agents/management-warnings.ts +115 -0
  521. package/src/agents/markdown-chain.ts +237 -0
  522. package/src/agents/package-agents.ts +231 -0
  523. package/src/agents/skill-files.ts +182 -0
  524. package/src/agents/skill-search-paths.ts +289 -0
  525. package/src/agents/skill-types.ts +31 -0
  526. package/src/agents/skills.ts +152 -610
  527. package/src/extension/acceptance-schema.ts +88 -0
  528. package/src/extension/chain-schema.ts +276 -0
  529. package/src/extension/child-runtime.ts +302 -0
  530. package/src/extension/compact-tools.ts +259 -102
  531. package/src/extension/config-schema.ts +59 -0
  532. package/src/extension/config.ts +39 -17
  533. package/src/extension/control-notice-schema.ts +55 -0
  534. package/src/extension/control-notices.ts +57 -33
  535. package/src/extension/delegation-bridges.ts +59 -0
  536. package/src/extension/doctor.ts +171 -135
  537. package/src/extension/everyday-schemas.ts +252 -0
  538. package/src/extension/fanout-child.ts +29 -308
  539. package/src/extension/index.ts +17 -642
  540. package/src/extension/message-renderers.ts +334 -0
  541. package/src/extension/nested-control-inbox.ts +196 -0
  542. package/src/extension/parent-runtime.ts +405 -0
  543. package/src/extension/primary-tool.ts +130 -0
  544. package/src/extension/result-snapshot.ts +35 -0
  545. package/src/extension/runtime-reload.ts +72 -0
  546. package/src/extension/runtime-storage.ts +49 -0
  547. package/src/extension/schema-overrides.ts +114 -0
  548. package/src/extension/schemas.ts +451 -418
  549. package/src/extension/tool-input.ts +40 -13
  550. package/src/extension/tool-result.ts +45 -14
  551. package/src/history/answer-texts.ts +74 -0
  552. package/src/history/canonical-result.ts +127 -51
  553. package/src/history/canonical-selection.ts +54 -0
  554. package/src/history/database-generation.ts +86 -0
  555. package/src/history/database.ts +86 -48
  556. package/src/history/entry-ingestion.ts +129 -0
  557. package/src/history/index.ts +426 -148
  558. package/src/history/ingest.ts +372 -342
  559. package/src/history/output-digests.ts +41 -0
  560. package/src/history/preview.ts +235 -0
  561. package/src/history/prune.ts +48 -0
  562. package/src/history/queries.ts +122 -256
  563. package/src/history/query-scope.ts +278 -0
  564. package/src/history/rows.ts +118 -0
  565. package/src/history/run-projection.ts +164 -0
  566. package/src/history/run-queries.ts +199 -0
  567. package/src/history/schema.ts +34 -0
  568. package/src/history/search-queries.ts +216 -0
  569. package/src/history/selected-record.ts +140 -0
  570. package/src/history/source-file.ts +51 -0
  571. package/src/history/source-watches.ts +156 -0
  572. package/src/history/store.ts +310 -197
  573. package/src/history/text-chunks.ts +181 -0
  574. package/src/history/text.ts +43 -0
  575. package/src/history/transcript-queries.ts +251 -0
  576. package/src/history/types.ts +27 -164
  577. package/src/history/values.ts +36 -0
  578. package/src/history/wire-inputs.ts +141 -0
  579. package/src/history/wire-models.ts +51 -0
  580. package/src/history/worker-runtime.ts +502 -0
  581. package/src/history/worker.ts +94 -336
  582. package/src/intercom/intercom-bridge.ts +136 -69
  583. package/src/intercom/live-intercom.ts +197 -92
  584. package/src/intercom/nested-result-tree.ts +213 -0
  585. package/src/intercom/result-delivery.ts +56 -0
  586. package/src/intercom/result-intercom.ts +364 -382
  587. package/src/pi-intercom/agent-dir.ts +9 -3
  588. package/src/pi-intercom/async.ts +33 -0
  589. package/src/pi-intercom/bridge-ask.ts +64 -0
  590. package/src/pi-intercom/bridge-config.ts +120 -0
  591. package/src/pi-intercom/bridge-http.ts +97 -0
  592. package/src/pi-intercom/bridge-launch-policy.ts +109 -0
  593. package/src/pi-intercom/bridge-launch.ts +317 -0
  594. package/src/pi-intercom/bridge-protocol.ts +123 -0
  595. package/src/pi-intercom/bridge-routes.ts +105 -0
  596. package/src/pi-intercom/bridge-rpc-process.ts +293 -0
  597. package/src/pi-intercom/bridge-session.ts +510 -0
  598. package/src/pi-intercom/bridge.ts +305 -549
  599. package/src/pi-intercom/broker/broker.ts +326 -263
  600. package/src/pi-intercom/broker/client.ts +273 -354
  601. package/src/pi-intercom/broker/connection.ts +192 -0
  602. package/src/pi-intercom/broker/framing.ts +11 -6
  603. package/src/pi-intercom/broker/paths.ts +28 -25
  604. package/src/pi-intercom/broker/pid.ts +68 -24
  605. package/src/pi-intercom/broker/snapshot-reader.ts +91 -0
  606. package/src/pi-intercom/broker/snapshots.ts +241 -0
  607. package/src/pi-intercom/broker/spawn.ts +58 -34
  608. package/src/pi-intercom/config.ts +113 -108
  609. package/src/pi-intercom/connection.ts +346 -0
  610. package/src/pi-intercom/identity.ts +71 -0
  611. package/src/pi-intercom/inbound-delivery.ts +450 -0
  612. package/src/pi-intercom/inbound-journal.ts +350 -0
  613. package/src/pi-intercom/inbound-record.ts +141 -0
  614. package/src/pi-intercom/inbound.ts +237 -0
  615. package/src/pi-intercom/index.ts +86 -2923
  616. package/src/pi-intercom/inspection.ts +82 -0
  617. package/src/pi-intercom/interview.ts +295 -0
  618. package/src/pi-intercom/lifecycle.ts +183 -0
  619. package/src/pi-intercom/message-format.ts +77 -0
  620. package/src/pi-intercom/outbound.ts +418 -0
  621. package/src/pi-intercom/peer-awareness.ts +84 -0
  622. package/src/pi-intercom/reply-tracker.ts +121 -59
  623. package/src/pi-intercom/reply-wait.ts +247 -0
  624. package/src/pi-intercom/runtime-types.ts +97 -0
  625. package/src/pi-intercom/session-events.ts +247 -0
  626. package/src/pi-intercom/session-targets.ts +124 -72
  627. package/src/pi-intercom/subagent-events.ts +299 -0
  628. package/src/pi-intercom/subagent-live-events.ts +212 -109
  629. package/src/pi-intercom/supervisor.ts +362 -0
  630. package/src/pi-intercom/tool-arguments.ts +120 -0
  631. package/src/pi-intercom/tool-registration.ts +121 -0
  632. package/src/pi-intercom/tool-results.ts +54 -0
  633. package/src/pi-intercom/tool-schemas.ts +134 -0
  634. package/src/pi-intercom/tools.ts +80 -0
  635. package/src/pi-intercom/topic-actions.ts +243 -0
  636. package/src/pi-intercom/topics.ts +267 -121
  637. package/src/pi-intercom/transport.ts +14 -0
  638. package/src/pi-intercom/types.ts +288 -190
  639. package/src/pi-intercom/ui/compose-paste.ts +109 -0
  640. package/src/pi-intercom/ui/compose.ts +173 -157
  641. package/src/pi-intercom/ui/inline-message.ts +80 -62
  642. package/src/pi-intercom/ui/message-renderers.ts +182 -0
  643. package/src/pi-intercom/ui/overlay.ts +238 -0
  644. package/src/pi-intercom/ui/session-format.ts +146 -0
  645. package/src/pi-intercom/ui/session-list.ts +145 -62
  646. package/src/pi-intercom/ui/tool-renderers.ts +148 -0
  647. package/src/pi-intercom/ui/topic-view.ts +107 -0
  648. package/src/pi-intercom/validation.ts +69 -0
  649. package/src/runs/background/async-chain-plan.ts +451 -0
  650. package/src/runs/background/async-control.ts +201 -71
  651. package/src/runs/background/async-execution.ts +206 -829
  652. package/src/runs/background/async-job-admission.ts +54 -0
  653. package/src/runs/background/async-job-projection.ts +225 -0
  654. package/src/runs/background/async-job-tracker.ts +416 -360
  655. package/src/runs/background/async-launch.ts +319 -0
  656. package/src/runs/background/async-plan.ts +285 -0
  657. package/src/runs/background/async-result-file.ts +79 -37
  658. package/src/runs/background/async-resume.ts +338 -285
  659. package/src/runs/background/async-run-discovery.ts +270 -0
  660. package/src/runs/background/async-run-location.ts +194 -0
  661. package/src/runs/background/async-run-record.ts +14 -0
  662. package/src/runs/background/async-run-repair.ts +307 -0
  663. package/src/runs/background/async-run-summary.ts +71 -0
  664. package/src/runs/background/async-single-plan.ts +168 -0
  665. package/src/runs/background/async-status-projection.ts +188 -0
  666. package/src/runs/background/async-status-render.ts +164 -0
  667. package/src/runs/background/async-status-validation.ts +238 -0
  668. package/src/runs/background/async-status.ts +9 -481
  669. package/src/runs/background/async-value.ts +29 -0
  670. package/src/runs/background/completion-dedupe.ts +103 -54
  671. package/src/runs/background/completion-delivery.ts +313 -187
  672. package/src/runs/background/completion-receipts.ts +309 -0
  673. package/src/runs/background/legacy-status-acceptance.ts +38 -0
  674. package/src/runs/background/notify.ts +167 -106
  675. package/src/runs/background/parallel-groups.ts +95 -46
  676. package/src/runs/background/result-payload.ts +268 -0
  677. package/src/runs/background/result-watcher.ts +484 -387
  678. package/src/runs/background/run-id-resolver.ts +126 -57
  679. package/src/runs/background/run-schemas.ts +240 -0
  680. package/src/runs/background/run-status-contracts.ts +46 -0
  681. package/src/runs/background/run-status-render.ts +364 -0
  682. package/src/runs/background/run-status.ts +338 -356
  683. package/src/runs/background/runner-attempt-output.ts +281 -0
  684. package/src/runs/background/runner-attempt.ts +354 -0
  685. package/src/runs/background/runner-child-executor.ts +232 -0
  686. package/src/runs/background/runner-child-observer.ts +270 -0
  687. package/src/runs/background/runner-completion-summary.ts +106 -0
  688. package/src/runs/background/runner-completion.ts +274 -0
  689. package/src/runs/background/runner-config.ts +52 -0
  690. package/src/runs/background/runner-contract.ts +51 -0
  691. package/src/runs/background/runner-dynamic.ts +185 -0
  692. package/src/runs/background/runner-finalization.ts +203 -0
  693. package/src/runs/background/runner-graph.ts +91 -0
  694. package/src/runs/background/runner-initial-status.ts +72 -0
  695. package/src/runs/background/runner-lifecycle.ts +159 -0
  696. package/src/runs/background/runner-monitor.ts +399 -0
  697. package/src/runs/background/runner-parallel.ts +280 -0
  698. package/src/runs/background/runner-prepare-step.ts +128 -0
  699. package/src/runs/background/runner-reporting.ts +192 -0
  700. package/src/runs/background/runner-status.ts +220 -0
  701. package/src/runs/background/runner-step-output.ts +158 -0
  702. package/src/runs/background/runner-step-record.ts +76 -0
  703. package/src/runs/background/runner-step.ts +150 -0
  704. package/src/runs/background/runner-streaming.ts +220 -0
  705. package/src/runs/background/runner-workflow.ts +284 -0
  706. package/src/runs/background/stale-run-reconciler.ts +279 -336
  707. package/src/runs/background/subagent-runner-launcher.ts +67 -35
  708. package/src/runs/background/subagent-runner.ts +80 -2048
  709. package/src/runs/background/top-level-async.ts +9 -7
  710. package/src/runs/foreground/chain-clarify.ts +499 -1334
  711. package/src/runs/foreground/clarification-draft.ts +92 -0
  712. package/src/runs/foreground/clarify-contracts.ts +33 -0
  713. package/src/runs/foreground/clarify-invocation.ts +192 -65
  714. package/src/runs/foreground/clarify-text-editor.ts +229 -0
  715. package/src/runs/foreground/clarify-view.ts +222 -0
  716. package/src/runs/foreground/execution-input.ts +358 -424
  717. package/src/runs/foreground/execution-routing.ts +41 -0
  718. package/src/runs/foreground/execution-timeout.ts +92 -0
  719. package/src/runs/foreground/foreground-control.ts +333 -812
  720. package/src/runs/foreground/foreground-memory.ts +132 -0
  721. package/src/runs/foreground/invocation-context.ts +88 -0
  722. package/src/runs/foreground/invocation-execution.ts +465 -0
  723. package/src/runs/foreground/invocation-expansion.ts +58 -0
  724. package/src/runs/foreground/management-actions.ts +360 -0
  725. package/src/runs/foreground/nested-control.ts +248 -0
  726. package/src/runs/foreground/question-continuation.ts +112 -0
  727. package/src/runs/foreground/question-control.ts +297 -70
  728. package/src/runs/foreground/resume-target.ts +330 -0
  729. package/src/runs/foreground/revival-launch-policy.ts +112 -0
  730. package/src/runs/foreground/run-async-path.ts +307 -223
  731. package/src/runs/foreground/run-interrupt.ts +202 -0
  732. package/src/runs/foreground/run-nudge.ts +355 -0
  733. package/src/runs/foreground/saved-revival.ts +356 -0
  734. package/src/runs/foreground/subagent-executor.ts +195 -493
  735. package/src/runs/foreground/subagent-params.ts +314 -242
  736. package/src/runs/foreground/wait-observation.ts +160 -0
  737. package/src/runs/foreground/wait-registration.ts +18 -0
  738. package/src/runs/foreground/wait-run.ts +361 -119
  739. package/src/runs/shared/acceptance-contract.ts +410 -209
  740. package/src/runs/shared/acceptance-defaults.ts +1 -0
  741. package/src/runs/shared/acceptance-evaluation.ts +314 -240
  742. package/src/runs/shared/acceptance-finalization.ts +447 -305
  743. package/src/runs/shared/acceptance-outcome.ts +69 -0
  744. package/src/runs/shared/acceptance-reports.ts +220 -126
  745. package/src/runs/shared/acceptance-submission.ts +251 -0
  746. package/src/runs/shared/acceptance-verification.ts +118 -0
  747. package/src/runs/shared/acceptance.ts +25 -25
  748. package/src/runs/shared/chain-outputs.ts +190 -98
  749. package/src/runs/shared/child-attempt-types.ts +118 -0
  750. package/src/runs/shared/child-attempt.ts +372 -608
  751. package/src/runs/shared/child-boundaries.ts +217 -0
  752. package/src/runs/shared/child-context-filter.ts +53 -0
  753. package/src/runs/shared/child-event-observer.ts +496 -0
  754. package/src/runs/shared/child-execution-cwd.ts +139 -68
  755. package/src/runs/shared/child-execution-outcome.ts +103 -0
  756. package/src/runs/shared/child-invocation.ts +74 -0
  757. package/src/runs/shared/child-message-validation.ts +233 -0
  758. package/src/runs/shared/child-native-accounting.ts +154 -0
  759. package/src/runs/shared/child-native-observation.ts +79 -0
  760. package/src/runs/shared/child-observation-spool.ts +188 -0
  761. package/src/runs/shared/child-observations.ts +219 -89
  762. package/src/runs/shared/child-presence.ts +6 -0
  763. package/src/runs/shared/child-route-env.ts +142 -0
  764. package/src/runs/shared/child-stream-observer.ts +302 -0
  765. package/src/runs/shared/child-stream-projection.ts +160 -0
  766. package/src/runs/shared/child-structured-tool.ts +38 -0
  767. package/src/runs/shared/claude-code.ts +201 -228
  768. package/src/runs/shared/claude-model.ts +67 -0
  769. package/src/runs/shared/claude-session.ts +124 -0
  770. package/src/runs/shared/claude-types.ts +31 -0
  771. package/src/runs/shared/completion-guard.ts +35 -26
  772. package/src/runs/shared/control-event-policy.ts +15 -0
  773. package/src/runs/shared/control-notification-owner.ts +20 -0
  774. package/src/runs/shared/dynamic-fanout.ts +402 -238
  775. package/src/runs/shared/dynamic-item.ts +114 -0
  776. package/src/runs/shared/history-index.ts +112 -51
  777. package/src/runs/shared/legacy-async-recovery.ts +153 -0
  778. package/src/runs/shared/legacy-output-recovery.ts +214 -0
  779. package/src/runs/shared/mcp-config-schema.ts +46 -0
  780. package/src/runs/shared/mcp-direct-tool-allowlist.ts +396 -307
  781. package/src/runs/shared/model-fallback.ts +302 -180
  782. package/src/runs/shared/mutating-tool-guard.ts +259 -158
  783. package/src/runs/shared/native-finalization-types.ts +29 -0
  784. package/src/runs/shared/native-finalization.ts +233 -109
  785. package/src/runs/shared/native-usage.ts +379 -96
  786. package/src/runs/shared/nested-control-records.ts +178 -0
  787. package/src/runs/shared/nested-events.ts +45 -752
  788. package/src/runs/shared/nested-path.ts +47 -34
  789. package/src/runs/shared/nested-projection.ts +230 -0
  790. package/src/runs/shared/nested-protocol.ts +67 -0
  791. package/src/runs/shared/nested-registry.ts +273 -0
  792. package/src/runs/shared/nested-render.ts +198 -100
  793. package/src/runs/shared/nested-route.ts +292 -0
  794. package/src/runs/shared/nested-validation.ts +291 -0
  795. package/src/runs/shared/owned-child-evidence.ts +184 -0
  796. package/src/runs/shared/owned-run-list.ts +187 -0
  797. package/src/runs/shared/owned-run-observation.ts +368 -0
  798. package/src/runs/shared/owned-run-progress.ts +172 -0
  799. package/src/runs/shared/owned-run-read-state.ts +7 -0
  800. package/src/runs/shared/owned-run-results.ts +289 -0
  801. package/src/runs/shared/owned-run-status.ts +239 -0
  802. package/src/runs/shared/owned-run-view.ts +65 -0
  803. package/src/runs/shared/owned-workflow-details.ts +24 -0
  804. package/src/runs/shared/owner-output.ts +73 -0
  805. package/src/runs/shared/parallel-utils.ts +93 -129
  806. package/src/runs/shared/parent-receipts.ts +177 -50
  807. package/src/runs/shared/parent-usage.ts +314 -91
  808. package/src/runs/shared/pi-args.ts +241 -311
  809. package/src/runs/shared/pi-launch-input.ts +48 -0
  810. package/src/runs/shared/pi-session-launch.ts +94 -0
  811. package/src/runs/shared/pi-spawn.ts +57 -33
  812. package/src/runs/shared/run-history-queries.ts +189 -38
  813. package/src/runs/shared/run-history.ts +197 -76
  814. package/src/runs/shared/run-id-ambiguity.ts +7 -3
  815. package/src/runs/shared/run-metadata-paths.ts +17 -0
  816. package/src/runs/shared/run-metadata.ts +240 -0
  817. package/src/runs/shared/run-persistence.ts +181 -0
  818. package/src/runs/shared/run-records.ts +19 -607
  819. package/src/runs/shared/run-restoration.ts +455 -0
  820. package/src/runs/shared/run-state-owner.ts +31 -0
  821. package/src/runs/shared/session-cwd-preload.ts +39 -19
  822. package/src/runs/shared/single-output.ts +358 -205
  823. package/src/runs/shared/stable-value.ts +15 -0
  824. package/src/runs/shared/streaming-text.ts +39 -12
  825. package/src/runs/shared/structured-output.ts +76 -46
  826. package/src/runs/shared/subagent-control.ts +330 -197
  827. package/src/runs/shared/subagent-prompt-runtime.ts +92 -174
  828. package/src/runs/shared/subagent-tool-loop-guard.ts +118 -68
  829. package/src/runs/shared/supervisor-question-state.ts +221 -0
  830. package/src/runs/shared/supervisor-questions.ts +396 -401
  831. package/src/runs/shared/workflow-graph.ts +400 -198
  832. package/src/runs/shared/workflow-policy.ts +245 -110
  833. package/src/runs/shared/worktree-contract.ts +29 -0
  834. package/src/runs/shared/worktree-hook.ts +181 -0
  835. package/src/runs/shared/worktree-location.ts +61 -0
  836. package/src/runs/shared/worktree-preservation.ts +10 -0
  837. package/src/runs/shared/worktree-process.ts +132 -0
  838. package/src/runs/shared/worktree.ts +459 -587
  839. package/src/shared/agent-context-policy.ts +176 -145
  840. package/src/shared/agent-dir.ts +15 -0
  841. package/src/shared/artifacts.ts +99 -68
  842. package/src/shared/atomic-json.ts +14 -11
  843. package/src/shared/chain-files.ts +72 -0
  844. package/src/shared/concurrency.ts +27 -0
  845. package/src/shared/content-preview.ts +117 -0
  846. package/src/shared/cooperative.ts +17 -13
  847. package/src/shared/execution-cwd.ts +34 -9
  848. package/src/shared/file-coalescer.ts +24 -22
  849. package/src/shared/fork-context.ts +52 -48
  850. package/src/shared/formatters.ts +88 -47
  851. package/src/shared/journal-frames.ts +197 -0
  852. package/src/shared/journal-projections.ts +133 -0
  853. package/src/shared/journal-reader.ts +18 -333
  854. package/src/shared/json-projection.ts +224 -0
  855. package/src/shared/lazy-tools.ts +76 -27
  856. package/src/shared/message-output.ts +146 -0
  857. package/src/shared/model-info.ts +94 -58
  858. package/src/shared/native-import.ts +25 -0
  859. package/src/shared/native-journal.ts +172 -0
  860. package/src/shared/native-session-loader.ts +13 -0
  861. package/src/shared/native-session.ts +3 -5
  862. package/src/shared/native-tui.ts +13 -3
  863. package/src/shared/native-typebox.ts +18 -3
  864. package/src/shared/output-page.ts +76 -0
  865. package/src/shared/post-exit-stdio-guard.ts +232 -172
  866. package/src/shared/prompt-sections.ts +13 -7
  867. package/src/shared/session-entries.ts +103 -44
  868. package/src/shared/session-identity.ts +17 -10
  869. package/src/shared/settings.ts +182 -367
  870. package/src/shared/status-format.ts +207 -98
  871. package/src/shared/stream-json.d.ts +10 -0
  872. package/src/shared/task-file-policy.ts +35 -0
  873. package/src/shared/temp-root.ts +122 -71
  874. package/src/shared/types/acceptance.ts +149 -0
  875. package/src/shared/types/async.ts +247 -0
  876. package/src/shared/types/config.ts +139 -0
  877. package/src/shared/types/details.ts +104 -0
  878. package/src/shared/types/history.ts +217 -0
  879. package/src/shared/types/inputs.ts +4 -0
  880. package/src/shared/types/intercom.ts +57 -0
  881. package/src/shared/types/launch.ts +39 -0
  882. package/src/shared/types/messages.ts +104 -0
  883. package/src/shared/types/nested.ts +153 -0
  884. package/src/shared/types/owned-runs.ts +148 -0
  885. package/src/shared/types/progress.ts +106 -0
  886. package/src/shared/types/questions.ts +103 -0
  887. package/src/shared/types/results.ts +119 -0
  888. package/src/shared/types/state.ts +56 -0
  889. package/src/shared/types/usage.ts +36 -0
  890. package/src/shared/types/workflow.ts +187 -0
  891. package/src/shared/types.ts +222 -1161
  892. package/src/shared/ui-mode.ts +1 -1
  893. package/src/shared/unknown.ts +59 -0
  894. package/src/shared/utils.ts +111 -305
  895. package/src/slash/command-discovery.ts +109 -0
  896. package/src/slash/command-input.ts +232 -0
  897. package/src/slash/prompt-template-bridge.ts +153 -400
  898. package/src/slash/prompt-template-contract.ts +152 -0
  899. package/src/slash/prompt-template-results.ts +189 -0
  900. package/src/slash/request-protocol.ts +87 -0
  901. package/src/slash/result-delivery.ts +312 -0
  902. package/src/slash/saved-chain.ts +115 -0
  903. package/src/slash/slash-bridge.ts +202 -156
  904. package/src/slash/slash-commands.ts +176 -598
  905. package/src/slash/slash-live-state.ts +287 -229
  906. package/src/tui/action-hints.ts +147 -61
  907. package/src/tui/activity-lines.ts +40 -0
  908. package/src/tui/agent-browser.ts +354 -0
  909. package/src/tui/agent-controls.ts +355 -0
  910. package/src/tui/agent-conversation.ts +514 -0
  911. package/src/tui/agent-dock.ts +250 -0
  912. package/src/tui/agent-history.ts +78 -203
  913. package/src/tui/agent-model.ts +78 -0
  914. package/src/tui/agent-picker.ts +325 -0
  915. package/src/tui/agent-view.ts +391 -1333
  916. package/src/tui/compact-results.ts +342 -0
  917. package/src/tui/conversation-details.ts +206 -0
  918. package/src/tui/conversation-frame.ts +184 -0
  919. package/src/tui/conversation-history.ts +246 -0
  920. package/src/tui/conversation-menu.ts +119 -0
  921. package/src/tui/conversation-notices.ts +116 -0
  922. package/src/tui/conversation-selection.ts +66 -0
  923. package/src/tui/conversation-viewport.ts +169 -0
  924. package/src/tui/direction-message.ts +35 -0
  925. package/src/tui/display.ts +409 -0
  926. package/src/tui/editor-viewport.ts +19 -0
  927. package/src/tui/expanded-content.ts +134 -0
  928. package/src/tui/expanded-multi.ts +264 -0
  929. package/src/tui/expanded-single.ts +92 -0
  930. package/src/tui/history-cards.ts +316 -0
  931. package/src/tui/history-display.ts +334 -0
  932. package/src/tui/history-json.ts +17 -0
  933. package/src/tui/history-metadata.ts +177 -0
  934. package/src/tui/history-record.ts +321 -0
  935. package/src/tui/history-text.ts +34 -0
  936. package/src/tui/indexed-history.ts +149 -0
  937. package/src/tui/nested-widget.ts +192 -0
  938. package/src/tui/picker-page.ts +80 -0
  939. package/src/tui/render.ts +100 -1546
  940. package/src/tui/result-facts.ts +99 -0
  941. package/src/tui/task-observation.ts +96 -0
  942. package/src/tui/task-store.ts +432 -0
  943. package/src/tui/text-values.ts +20 -0
  944. package/src/tui/view-connections.ts +64 -0
  945. package/src/tui/view-model.ts +181 -0
  946. package/src/tui/view-persistence.ts +57 -0
  947. package/src/tui/view-ports.ts +55 -0
  948. package/src/tui/view-session.ts +42 -0
  949. package/src/tui/widget-details.ts +220 -0
  950. package/src/tui/widget-status.ts +331 -0
  951. package/src/tui/widget.ts +208 -0
  952. package/src/tui/workflow-labels.ts +340 -0
  953. package/src/tui/workflow-outcomes.ts +150 -0
  954. package/tsconfig.build.json +11 -10
  955. package/tsconfig.json +30 -17
@@ -1,1340 +1,505 @@
1
- /**
2
- * Chain Clarification TUI Component
3
- *
4
- * Shows templates and resolved behaviors for each step in a chain.
5
- * Supports runtime editing of templates, output paths, reads lists, and progress toggle.
6
- */
7
-
8
1
  import type { Theme } from "@earendil-works/pi-coding-agent";
9
- import type { Component, TUI } from "@earendil-works/pi-tui";
10
- import { matchesKey, visibleWidth, truncateToWidth } from "@earendil-works/pi-tui";
11
- import type { AgentConfig } from "../../agents/agents.ts";
12
- import type { ResolvedStepBehavior } from "../../shared/settings.ts";
2
+ import { type Component, type TUI, matchesKey, truncateToWidth } from "@earendil-works/pi-tui";
3
+ import { ClarifyView } from "./clarify-view.ts";
4
+ import {
5
+ findModelInfo,
6
+ getSupportedThinkingLevels,
7
+ type ModelInfo,
8
+ type ThinkingLevel,
9
+ } from "../../shared/model-info.ts";
13
10
  import { resolveModelCandidate, splitThinkingSuffix } from "../shared/model-fallback.ts";
14
- import { findModelInfo, getSupportedThinkingLevels, type ModelInfo, type ThinkingLevel } from "../../shared/model-info.ts";
15
-
16
- type ClarifyMode = 'single' | 'parallel' | 'chain';
17
-
18
- export interface BehaviorOverride {
19
- output?: string | false;
20
- reads?: string[] | false;
21
- progress?: boolean;
22
- model?: string;
23
- skills?: string[] | false;
24
- }
25
-
26
- export interface ChainClarifyResult {
27
- confirmed: boolean;
28
- templates: string[];
29
- behaviorOverrides: (BehaviorOverride | undefined)[];
30
- runInBackground?: boolean;
31
- }
32
-
33
- type EditMode = "template" | "output" | "reads" | "model" | "thinking" | "skills";
34
-
35
-
36
- interface TextEditorState {
37
- buffer: string;
38
- cursor: number;
39
- viewportOffset: number;
40
- }
41
-
42
- function createEditorState(initial = ""): TextEditorState {
43
- return { buffer: initial, cursor: 0, viewportOffset: 0 };
44
- }
45
-
46
- function wrapText(text: string, width: number): { lines: string[]; starts: number[] } {
47
- if (width <= 0) return { lines: [text], starts: [0] };
48
- if (text.length === 0) return { lines: [""], starts: [0] };
49
-
50
- const lines: string[] = [];
51
- const starts: number[] = [];
52
- let offset = 0;
53
- const segments = text.split("\n");
54
- for (const [index, segment] of segments.entries()) {
55
- if (segment.length === 0) {
56
- starts.push(offset);
57
- lines.push("");
58
- } else {
59
- let lineStart = 0;
60
- let pos = 0;
61
- let lineWidth = 0;
62
- while (pos < segment.length) {
63
- const char = String.fromCodePoint(segment.codePointAt(pos)!);
64
- const charWidth = visibleWidth(char);
65
- if (lineWidth > 0 && lineWidth + charWidth > width) {
66
- starts.push(offset + lineStart);
67
- lines.push(segment.slice(lineStart, pos));
68
- lineStart = pos;
69
- lineWidth = 0;
70
- continue;
71
- }
72
- pos += char.length;
73
- lineWidth += charWidth;
74
- }
75
- starts.push(offset + lineStart);
76
- lines.push(segment.slice(lineStart));
77
- }
78
- offset += segment.length + (index < segments.length - 1 ? 1 : 0);
79
- }
80
- if (!text.endsWith("\n") && text.length > 0 && visibleWidth(lines[lines.length - 1] ?? "") === width) {
81
- starts.push(text.length);
82
- lines.push("");
83
- }
84
- return { lines, starts };
85
- }
86
-
87
- function getCursorDisplayPos(cursor: number, starts: number[]): { line: number; col: number } {
88
- for (let i = starts.length - 1; i >= 0; i--) {
89
- if (cursor >= starts[i]!) return { line: i, col: cursor - starts[i]! };
90
- }
91
- return { line: 0, col: 0 };
92
- }
93
-
94
- function ensureCursorVisible(cursorLine: number, viewportHeight: number, currentOffset: number): number {
95
- if (cursorLine < currentOffset) return Math.max(0, cursorLine);
96
- if (cursorLine >= currentOffset + viewportHeight) return Math.max(0, cursorLine - viewportHeight + 1);
97
- return Math.max(0, currentOffset);
98
- }
99
-
100
- function isWordChar(ch: string): boolean {
101
- const code = ch.charCodeAt(0);
102
- return (code >= 48 && code <= 57) || (code >= 65 && code <= 90) || (code >= 97 && code <= 122) || code === 95;
103
- }
104
-
105
- function wordBackward(buffer: string, cursor: number): number {
106
- let pos = cursor;
107
- while (pos > 0 && !isWordChar(buffer[pos - 1]!)) pos--;
108
- while (pos > 0 && isWordChar(buffer[pos - 1]!)) pos--;
109
- return pos;
110
- }
111
-
112
- function wordForward(buffer: string, cursor: number): number {
113
- let pos = cursor;
114
- while (pos < buffer.length && isWordChar(buffer[pos]!)) pos++;
115
- while (pos < buffer.length && !isWordChar(buffer[pos]!)) pos++;
116
- return pos;
117
- }
118
-
119
- function normalizeInsertText(data: string): string | null {
120
- let text = data.split("\x1b[200~").join("").split("\x1b[201~").join("");
121
- text = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
122
- const newline = text.indexOf("\n");
123
- if (newline !== -1) text = text.slice(0, newline);
124
- text = text.replace(/\t/g, " ");
125
- if (text.length === 0) return null;
126
- for (let i = 0; i < text.length; i++) {
127
- if (text.charCodeAt(i) < 32) return null;
128
- }
129
- return text;
11
+ import { ClarifyTextEditor } from "./clarify-text-editor.ts";
12
+ import { ClarificationDraft } from "./clarification-draft.ts";
13
+
14
+ import type {
15
+ ChainClarifyResult,
16
+ ChainClarifyOptions,
17
+ ClarifyMode,
18
+ EditMode,
19
+ } from "./clarify-contracts.ts";
20
+ export type {
21
+ BehaviorOverride,
22
+ ChainClarifyResult,
23
+ ChainClarifyOptions,
24
+ } from "./clarify-contracts.ts";
25
+ type SkillChoice = ChainClarifyOptions["availableSkills"][number];
26
+
27
+ /** Owns one preview interaction. Edits replace local values; launch inputs are never mutated. */
28
+ export class ChainClarifyComponent implements Component {
29
+ private renderWidth = 84;
30
+ private selectedStep = 0;
31
+ private editingStep: number | null = null;
32
+ private editMode: EditMode = "template";
33
+ private readonly editor = new ClarifyTextEditor();
34
+ private readonly draft: ClarificationDraft;
35
+ private readonly tui: TUI;
36
+ private readonly theme: Theme;
37
+ private readonly options: ChainClarifyOptions;
38
+ private readonly done: (result: ChainClarifyResult) => void;
39
+ private modelSearchQuery = "";
40
+ private modelSelectedIndex = 0;
41
+ private filteredModels: readonly ModelInfo[];
42
+ private thinkingSelectedIndex = 0;
43
+ private skillSearchQuery = "";
44
+ private readonly skillSelectedNames = new Set<string>();
45
+ private skillCursorIndex = 0;
46
+ private filteredSkills: readonly SkillChoice[];
47
+ private noticeMessage: string | undefined;
48
+ private noticeMessageTimer: ReturnType<typeof setTimeout> | undefined;
49
+ private runInBackground = false;
50
+ private readonly mode: ClarifyMode;
51
+
52
+ constructor(
53
+ tui: TUI,
54
+ theme: Theme,
55
+ options: ChainClarifyOptions,
56
+ done: (result: ChainClarifyResult) => void,
57
+ ) {
58
+ this.tui = tui;
59
+ this.theme = theme;
60
+ this.options = options;
61
+ this.done = done;
62
+ this.draft = new ClarificationDraft(options.templates, options.resolvedBehaviors);
63
+ this.filteredModels = options.availableModels;
64
+ this.filteredSkills = options.availableSkills;
65
+ this.mode = options.mode ?? "chain";
66
+ }
67
+ private getEffectiveModel(index: number): string {
68
+ const model = this.draft.behavior(index).model;
69
+ if (model === undefined || model.length === 0) {
70
+ return "default";
71
+ }
72
+ return (
73
+ resolveModelCandidate(model, this.options.availableModels, this.options.preferredProvider) ??
74
+ model
75
+ );
76
+ }
77
+ private exitEditMode(): void {
78
+ this.editingStep = null;
79
+ this.editor.setText("");
80
+ this.tui.requestRender();
81
+ }
82
+ private showNotice(text: string): void {
83
+ this.noticeMessage = text;
84
+ if (this.noticeMessageTimer !== undefined) {
85
+ clearTimeout(this.noticeMessageTimer);
86
+ }
87
+ this.noticeMessageTimer = setTimeout(() => {
88
+ this.noticeMessage = undefined;
89
+ this.noticeMessageTimer = undefined;
90
+ this.tui.requestRender();
91
+ }, 2000);
92
+ this.tui.requestRender();
93
+ }
94
+ handleInput(data: string): void {
95
+ if (this.editingStep !== null) {
96
+ this.handleEditingInput(data);
97
+ return;
98
+ }
99
+ if (matchesKey(data, "escape") || matchesKey(data, "ctrl+c")) {
100
+ this.done({ confirmed: false, templates: [], behaviorOverrides: [] });
101
+ return;
102
+ }
103
+ if (matchesKey(data, "return")) {
104
+ this.done({
105
+ confirmed: true,
106
+ templates: [...this.draft.templates],
107
+ behaviorOverrides: this.options.agentConfigs.map((_, index) => this.draft.override(index)),
108
+ runInBackground: this.runInBackground,
109
+ });
110
+ return;
111
+ }
112
+ if (matchesKey(data, "up") || matchesKey(data, "down")) {
113
+ const delta = matchesKey(data, "up") ? -1 : 1;
114
+ this.selectedStep = Math.max(
115
+ 0,
116
+ Math.min(this.options.agentConfigs.length - 1, this.selectedStep + delta),
117
+ );
118
+ } else {
119
+ this.handleShortcut(data);
120
+ }
121
+ this.tui.requestRender();
122
+ }
123
+ private handleShortcut(data: string): void {
124
+ switch (data) {
125
+ case "e":
126
+ this.enterEditMode("template");
127
+ break;
128
+ case "m":
129
+ this.enterModelSelector();
130
+ break;
131
+ case "t":
132
+ this.enterThinkingSelector();
133
+ break;
134
+ case "s":
135
+ this.enterSkillSelector();
136
+ break;
137
+ case "w":
138
+ if (this.mode !== "parallel") {
139
+ this.enterEditMode("output");
140
+ }
141
+ break;
142
+ case "r":
143
+ if (this.mode === "chain") {
144
+ this.enterEditMode("reads");
145
+ }
146
+ break;
147
+ case "p":
148
+ if (this.mode === "chain") {
149
+ this.draft.toggleProgress();
150
+ }
151
+ break;
152
+ case "b":
153
+ this.runInBackground = !this.runInBackground;
154
+ break;
155
+ }
156
+ }
157
+ private enterEditMode(mode: "template" | "output" | "reads"): void {
158
+ this.editingStep = this.selectedStep;
159
+ this.editMode = mode;
160
+ const behavior = this.draft.behavior(this.selectedStep);
161
+ let buffer = (this.draft.templates[this.selectedStep] ?? "").split("\n")[0] ?? "";
162
+ if (mode === "output") {
163
+ buffer = behavior.output === false ? "" : behavior.output;
164
+ }
165
+ if (mode === "reads") {
166
+ buffer = behavior.reads === false ? "" : behavior.reads.join(", ");
167
+ }
168
+ this.editor.setText(buffer);
169
+ }
170
+ private enterModelSelector(): void {
171
+ this.editingStep = this.selectedStep;
172
+ this.editMode = "model";
173
+ this.modelSearchQuery = "";
174
+ this.filteredModels = this.options.availableModels;
175
+ const current = splitThinkingSuffix(this.getEffectiveModel(this.selectedStep)).baseModel;
176
+ this.modelSelectedIndex = Math.max(
177
+ 0,
178
+ this.filteredModels.findIndex((model) => model.fullId === current || model.id === current),
179
+ );
180
+ this.tui.requestRender();
181
+ }
182
+ private getAvailableThinkingLevels(index: number): ThinkingLevel[] {
183
+ return getSupportedThinkingLevels(
184
+ findModelInfo(
185
+ this.getEffectiveModel(index),
186
+ this.options.availableModels,
187
+ this.options.preferredProvider,
188
+ ),
189
+ );
190
+ }
191
+ private enterThinkingSelector(): void {
192
+ const model = this.draft.behavior(this.selectedStep).model;
193
+ if (model === undefined || model.length === 0) {
194
+ this.showNotice("Select a model first");
195
+ return;
196
+ }
197
+ this.editingStep = this.selectedStep;
198
+ this.editMode = "thinking";
199
+ const levels = this.getAvailableThinkingLevels(this.selectedStep);
200
+ const suffix = splitThinkingSuffix(
201
+ this.getEffectiveModel(this.selectedStep),
202
+ ).thinkingSuffix.slice(1);
203
+ const index = levels.findIndex((level) => level === suffix);
204
+ this.thinkingSelectedIndex = index >= 0 ? index : Math.max(0, levels.indexOf("off"));
205
+ this.tui.requestRender();
206
+ }
207
+ private applyThinkingLevel(level: ThinkingLevel): void {
208
+ if (this.editingStep === null) {
209
+ return;
210
+ }
211
+ const current = this.draft.behavior(this.editingStep).model;
212
+ if (current === undefined || current.length === 0) {
213
+ return;
214
+ }
215
+ const { baseModel } = splitThinkingSuffix(current);
216
+ this.draft.update(this.editingStep, {
217
+ model: level === "off" ? baseModel : `${baseModel}:${level}`,
218
+ });
219
+ }
220
+ private enterSkillSelector(): void {
221
+ this.editingStep = this.selectedStep;
222
+ this.editMode = "skills";
223
+ this.skillSearchQuery = "";
224
+ this.skillCursorIndex = 0;
225
+ this.filteredSkills = this.options.availableSkills;
226
+ this.skillSelectedNames.clear();
227
+ const skills = this.draft.behavior(this.selectedStep).skills;
228
+ if (skills !== false) {
229
+ skills.forEach((skill) => {
230
+ this.skillSelectedNames.add(skill);
231
+ });
232
+ }
233
+ }
234
+ private handleEditingInput(data: string): void {
235
+ if (this.editMode === "model") {
236
+ this.handleModelSelectorInput(data);
237
+ return;
238
+ }
239
+ if (this.editMode === "thinking") {
240
+ this.handleThinkingSelectorInput(data);
241
+ return;
242
+ }
243
+ if (this.editMode === "skills") {
244
+ this.handleSkillSelectorInput(data);
245
+ return;
246
+ }
247
+ if (matchesKey(data, "escape")) {
248
+ this.saveEdit();
249
+ this.exitEditMode();
250
+ return;
251
+ }
252
+ if (matchesKey(data, "ctrl+c")) {
253
+ this.exitEditMode();
254
+ return;
255
+ }
256
+ this.editor.handleInput(data, this.renderWidth - 4);
257
+ this.tui.requestRender();
258
+ }
259
+ private handleModelSelectorInput(data: string): void {
260
+ if (matchesKey(data, "escape") || matchesKey(data, "ctrl+c")) {
261
+ this.exitEditMode();
262
+ return;
263
+ }
264
+ if (matchesKey(data, "return")) {
265
+ this.selectModel();
266
+ this.exitEditMode();
267
+ return;
268
+ }
269
+ const direction = navigationDirection(data);
270
+ if (direction !== 0) {
271
+ this.modelSelectedIndex = moveSelection(
272
+ this.modelSelectedIndex,
273
+ direction,
274
+ this.filteredModels.length,
275
+ );
276
+ } else {
277
+ this.modelSearchQuery = searchInput(this.modelSearchQuery, data);
278
+ const query = this.modelSearchQuery.toLowerCase();
279
+ this.filteredModels = this.options.availableModels.filter((model) =>
280
+ `${model.fullId} ${model.id} ${model.provider}`.toLowerCase().includes(query),
281
+ );
282
+ this.modelSelectedIndex = Math.min(
283
+ this.modelSelectedIndex,
284
+ Math.max(0, this.filteredModels.length - 1),
285
+ );
286
+ }
287
+ this.tui.requestRender();
288
+ }
289
+ private selectModel(): void {
290
+ const selected = this.filteredModels.at(this.modelSelectedIndex);
291
+ if (!selected || this.editingStep === null) {
292
+ return;
293
+ }
294
+ const { thinkingSuffix } = splitThinkingSuffix(this.getEffectiveModel(this.editingStep));
295
+ const info = findModelInfo(
296
+ selected.fullId,
297
+ this.options.availableModels,
298
+ this.options.preferredProvider,
299
+ );
300
+ const suffix = getSupportedThinkingLevels(info).some(
301
+ (level) => level === thinkingSuffix.slice(1),
302
+ )
303
+ ? thinkingSuffix
304
+ : "";
305
+ this.draft.update(this.editingStep, { model: `${selected.fullId}${suffix}` });
306
+ }
307
+ private handleThinkingSelectorInput(data: string): void {
308
+ if (matchesKey(data, "escape") || matchesKey(data, "ctrl+c")) {
309
+ this.exitEditMode();
310
+ return;
311
+ }
312
+ if (this.editingStep === null) {
313
+ return;
314
+ }
315
+ const levels = this.getAvailableThinkingLevels(this.editingStep);
316
+ if (levels.length === 0) {
317
+ return;
318
+ }
319
+ if (matchesKey(data, "return")) {
320
+ this.applyThinkingLevel(levels[this.thinkingSelectedIndex] ?? "off");
321
+ this.exitEditMode();
322
+ return;
323
+ }
324
+ this.thinkingSelectedIndex = moveSelection(
325
+ this.thinkingSelectedIndex,
326
+ navigationDirection(data),
327
+ levels.length,
328
+ );
329
+ this.tui.requestRender();
330
+ }
331
+ private handleSkillSelectorInput(data: string): void {
332
+ if (matchesKey(data, "escape") || matchesKey(data, "ctrl+c")) {
333
+ this.exitEditMode();
334
+ return;
335
+ }
336
+ if (matchesKey(data, "return") && this.editingStep !== null) {
337
+ this.draft.update(this.editingStep, { skills: [...this.skillSelectedNames] });
338
+ this.exitEditMode();
339
+ return;
340
+ }
341
+ if (data === " ") {
342
+ this.toggleSkill();
343
+ } else {
344
+ this.searchSkills(data);
345
+ }
346
+ this.tui.requestRender();
347
+ }
348
+ private toggleSkill(): void {
349
+ const skill = this.filteredSkills.at(this.skillCursorIndex);
350
+ if (!skill) {
351
+ return;
352
+ }
353
+ if (this.skillSelectedNames.has(skill.name)) {
354
+ this.skillSelectedNames.delete(skill.name);
355
+ } else {
356
+ this.skillSelectedNames.add(skill.name);
357
+ }
358
+ }
359
+ private searchSkills(data: string): void {
360
+ const direction = navigationDirection(data);
361
+ if (direction !== 0) {
362
+ this.skillCursorIndex = moveSelection(
363
+ this.skillCursorIndex,
364
+ direction,
365
+ this.filteredSkills.length,
366
+ );
367
+ return;
368
+ }
369
+ this.skillSearchQuery = searchInput(this.skillSearchQuery, data);
370
+ const query = this.skillSearchQuery.toLowerCase();
371
+ this.filteredSkills = this.options.availableSkills.filter((skill) =>
372
+ `${skill.name} ${skill.description ?? ""}`.toLowerCase().includes(query),
373
+ );
374
+ this.skillCursorIndex = Math.min(
375
+ this.skillCursorIndex,
376
+ Math.max(0, this.filteredSkills.length - 1),
377
+ );
378
+ }
379
+ private saveEdit(): void {
380
+ if (this.editingStep !== null) {
381
+ this.draft.save(this.editingStep, this.editMode, this.editor.getText());
382
+ }
383
+ }
384
+ private stepLabel(index: number): string {
385
+ const agent = this.options.agentConfigs[index]?.name ?? "unknown";
386
+ if (this.mode === "single") {
387
+ return agent;
388
+ }
389
+ return `${this.mode === "parallel" ? "Task" : "Step"} ${index + 1}: ${agent}`;
390
+ }
391
+ render(width: number): string[] {
392
+ if (width < 40) {
393
+ return [truncateToWidth("Subagent clarify: widen terminal", width)];
394
+ }
395
+ this.renderWidth = Math.min(84, width);
396
+ if (this.editingStep !== null) {
397
+ return this.renderEditing();
398
+ }
399
+ return this.renderOverview();
400
+ }
401
+ private renderEditing(): string[] {
402
+ if (this.editMode === "model") {
403
+ return this.renderModelSelector();
404
+ }
405
+ if (this.editMode === "thinking") {
406
+ return this.renderThinkingSelector();
407
+ }
408
+ if (this.editMode === "skills") {
409
+ return this.renderSkillSelector();
410
+ }
411
+ const name = this.editMode === "template" ? "task" : this.editMode;
412
+ const view = new ClarifyView(this.theme, this.renderWidth);
413
+ return view.editor(
414
+ `Editing ${name} (${this.stepLabel(this.editingStep ?? 0)})`,
415
+ this.editor.render(this.renderWidth - 4, 12),
416
+ );
417
+ }
418
+ private renderModelSelector(): string[] {
419
+ const current = this.getEffectiveModel(this.editingStep ?? 0);
420
+ const base = splitThinkingSuffix(current).baseModel;
421
+ const choices = this.filteredModels.map(
422
+ (model) =>
423
+ `${model.id} [${model.provider}]${model.fullId === base || model.id === base ? " current" : ""}`,
424
+ );
425
+ return new ClarifyView(this.theme, this.renderWidth).selector({
426
+ title: `Select Model (${this.stepLabel(this.editingStep ?? 0)})`,
427
+ subtitles: [`Search: ${this.modelSearchQuery}\x1b[7m \x1b[27m`, `Current: ${current}`],
428
+ choices,
429
+ selected: this.modelSelectedIndex,
430
+ footer: "[Enter] Select • [Esc] Cancel • Type to search",
431
+ });
432
+ }
433
+ private renderThinkingSelector(): string[] {
434
+ return new ClarifyView(this.theme, this.renderWidth).thinking({
435
+ step: this.stepLabel(this.editingStep ?? 0),
436
+ model: this.getEffectiveModel(this.editingStep ?? 0),
437
+ levels: this.getAvailableThinkingLevels(this.editingStep ?? 0),
438
+ selected: this.thinkingSelectedIndex,
439
+ });
440
+ }
441
+ private renderSkillSelector(): string[] {
442
+ const selected = [...this.skillSelectedNames].join(", ");
443
+ const choices = this.filteredSkills.map(
444
+ (skill) =>
445
+ `${this.skillSelectedNames.has(skill.name) ? "[x]" : "[ ]"} ${skill.name} [${skill.source}] - ${truncateToWidth(skill.description ?? "", 25)}`,
446
+ );
447
+ return new ClarifyView(this.theme, this.renderWidth).selector({
448
+ title: `Select Skills (${this.stepLabel(this.editingStep ?? 0)})`,
449
+ subtitles: [
450
+ `Search: ${this.skillSearchQuery}\x1b[7m \x1b[27m`,
451
+ `Selected: ${selected.length > 0 ? selected : "(none)"}`,
452
+ ],
453
+ choices,
454
+ selected: this.skillCursorIndex,
455
+ footer: "[Enter] Confirm • [Space] Toggle • [Esc] Cancel",
456
+ });
457
+ }
458
+ private renderOverview(): string[] {
459
+ const progress = this.options.agentConfigs.some(
460
+ (_, index) => this.draft.behavior(index).progress,
461
+ );
462
+ return new ClarifyView(this.theme, this.renderWidth).overview({
463
+ mode: this.mode,
464
+ selected: this.selectedStep,
465
+ background: this.runInBackground,
466
+ originalTask: this.options.originalTask,
467
+ chainDir: this.options.chainDir,
468
+ progress,
469
+ notice: this.noticeMessage,
470
+ steps: this.options.agentConfigs.map((agent, index) => ({
471
+ name: agent.name,
472
+ label: this.stepLabel(index),
473
+ template: (this.draft.templates[index] ?? "").split("\n")[0] ?? "",
474
+ model: this.getEffectiveModel(index),
475
+ modelOverridden: this.draft.override(index)?.model !== undefined,
476
+ behavior: this.draft.behavior(index),
477
+ nextUsesPrevious: (this.draft.templates[index + 1] ?? "").includes("{previous}"),
478
+ })),
479
+ });
480
+ }
481
+ invalidate(): void {
482
+ /* Rendering reads the current theme and owns no cache. */
483
+ }
484
+ dispose(): void {
485
+ if (this.noticeMessageTimer !== undefined) {
486
+ clearTimeout(this.noticeMessageTimer);
487
+ }
488
+ this.noticeMessageTimer = undefined;
489
+ }
130
490
  }
131
-
132
- function handleEditorInput(state: TextEditorState, data: string, textWidth: number): TextEditorState | null {
133
- if (matchesKey(data, "escape") || matchesKey(data, "ctrl+c") || matchesKey(data, "return")) return null;
134
-
135
- const { lines: wrapped, starts } = wrapText(state.buffer, textWidth);
136
- const cursorPos = getCursorDisplayPos(state.cursor, starts);
137
-
138
- if (matchesKey(data, "alt+left") || matchesKey(data, "ctrl+left")) return { ...state, cursor: wordBackward(state.buffer, state.cursor) };
139
- if (matchesKey(data, "alt+right") || matchesKey(data, "ctrl+right")) return { ...state, cursor: wordForward(state.buffer, state.cursor) };
140
- if (matchesKey(data, "left")) return state.cursor > 0 ? { ...state, cursor: state.cursor - 1 } : state;
141
- if (matchesKey(data, "right")) return state.cursor < state.buffer.length ? { ...state, cursor: state.cursor + 1 } : state;
142
- if (matchesKey(data, "up") && cursorPos.line > 0) {
143
- const targetLine = cursorPos.line - 1;
144
- return { ...state, cursor: starts[targetLine]! + Math.min(cursorPos.col, wrapped[targetLine]?.length ?? 0) };
145
- }
146
- if (matchesKey(data, "down") && cursorPos.line < wrapped.length - 1) {
147
- const targetLine = cursorPos.line + 1;
148
- return { ...state, cursor: starts[targetLine]! + Math.min(cursorPos.col, wrapped[targetLine]?.length ?? 0) };
149
- }
150
- if (matchesKey(data, "home")) return { ...state, cursor: starts[cursorPos.line]! };
151
- if (matchesKey(data, "end")) return { ...state, cursor: starts[cursorPos.line]! + (wrapped[cursorPos.line]?.length ?? 0) };
152
- if (matchesKey(data, "ctrl+home")) return { ...state, cursor: 0 };
153
- if (matchesKey(data, "ctrl+end")) return { ...state, cursor: state.buffer.length };
154
- if (matchesKey(data, "alt+backspace")) {
155
- const target = wordBackward(state.buffer, state.cursor);
156
- return target === state.cursor ? state : { ...state, buffer: state.buffer.slice(0, target) + state.buffer.slice(state.cursor), cursor: target };
157
- }
158
- if (matchesKey(data, "backspace")) {
159
- return state.cursor > 0
160
- ? { ...state, buffer: state.buffer.slice(0, state.cursor - 1) + state.buffer.slice(state.cursor), cursor: state.cursor - 1 }
161
- : state;
162
- }
163
- if (matchesKey(data, "delete")) {
164
- return state.cursor < state.buffer.length
165
- ? { ...state, buffer: state.buffer.slice(0, state.cursor) + state.buffer.slice(state.cursor + 1) }
166
- : state;
167
- }
168
-
169
- const insert = normalizeInsertText(data);
170
- return insert
171
- ? { ...state, buffer: state.buffer.slice(0, state.cursor) + insert + state.buffer.slice(state.cursor), cursor: state.cursor + insert.length }
172
- : null;
491
+ function navigationDirection(data: string): number {
492
+ if (matchesKey(data, "up")) {
493
+ return -1;
494
+ }
495
+ return matchesKey(data, "down") ? 1 : 0;
173
496
  }
174
-
175
- function renderWithCursor(text: string, cursorPos: number): string {
176
- const before = text.slice(0, cursorPos);
177
- const cursorChar = text[cursorPos] ?? " ";
178
- const after = text.slice(cursorPos + 1);
179
- return `${before}\x1b[7m${cursorChar}\x1b[27m${after}`;
497
+ function moveSelection(index: number, direction: number, count: number): number {
498
+ return count === 0 ? 0 : (index + direction + count) % count;
180
499
  }
181
-
182
- function renderEditor(state: TextEditorState, width: number, viewportHeight: number): string[] {
183
- const { lines: wrapped, starts } = wrapText(state.buffer, width);
184
- const cursorPos = getCursorDisplayPos(state.cursor, starts);
185
- const lines: string[] = [];
186
- for (let i = 0; i < viewportHeight; i++) {
187
- const lineIdx = state.viewportOffset + i;
188
- let content = lineIdx < wrapped.length ? wrapped[lineIdx] ?? "" : "";
189
- if (lineIdx === cursorPos.line) content = renderWithCursor(content, cursorPos.col);
190
- lines.push(content);
191
- }
192
- return lines;
193
- }
194
-
195
- /**
196
- * TUI component for chain clarification.
197
- * Factory signature matches ctx.ui.custom: (tui, theme, kb, done) => Component
198
- */
199
- export class ChainClarifyComponent implements Component {
200
- private renderWidth = 84;
201
- private get width(): number { return this.renderWidth; }
202
-
203
- private selectedStep = 0;
204
- private editingStep: number | null = null;
205
- private editMode: EditMode = "template";
206
- private editState: TextEditorState = createEditorState();
207
-
208
- private readonly EDIT_VIEWPORT_HEIGHT = 12;
209
- private behaviorOverrides: Map<number, BehaviorOverride> = new Map();
210
- private modelSearchQuery: string = "";
211
- private modelSelectedIndex: number = 0;
212
- private filteredModels: ModelInfo[] = [];
213
- private readonly MODEL_SELECTOR_HEIGHT = 10;
214
- private thinkingSelectedIndex: number = 0;
215
- private skillSearchQuery: string = "";
216
- private skillSelectedNames: Set<string> = new Set();
217
- private skillCursorIndex: number = 0;
218
- private filteredSkills: Array<{ name: string; source: string; description?: string }> = [];
219
- private noticeMessage: { text: string; type: "info" | "error" } | null = null;
220
- private noticeMessageTimer: ReturnType<typeof setTimeout> | null = null;
221
- /** Run in background (async) mode */
222
- private runInBackground = false;
223
- private tui: TUI;
224
- private theme: Theme;
225
- private agentConfigs: AgentConfig[];
226
- private templates: string[];
227
- private originalTask: string;
228
- private chainDir: string | undefined;
229
- private resolvedBehaviors: ResolvedStepBehavior[];
230
- private availableModels: ModelInfo[];
231
- private preferredProvider: string | undefined;
232
- private availableSkills: Array<{ name: string; source: string; description?: string }>;
233
- private done: (result: ChainClarifyResult) => void;
234
- private mode: ClarifyMode;
235
-
236
- constructor(
237
- tui: TUI,
238
- theme: Theme,
239
- agentConfigs: AgentConfig[],
240
- templates: string[],
241
- originalTask: string,
242
- chainDir: string | undefined,
243
- resolvedBehaviors: ResolvedStepBehavior[],
244
- availableModels: ModelInfo[],
245
- preferredProvider: string | undefined,
246
- availableSkills: Array<{ name: string; source: string; description?: string }>,
247
- done: (result: ChainClarifyResult) => void,
248
- mode: ClarifyMode = 'chain',
249
- ) {
250
- this.tui = tui;
251
- this.theme = theme;
252
- this.agentConfigs = agentConfigs;
253
- this.templates = templates;
254
- this.originalTask = originalTask;
255
- this.chainDir = chainDir;
256
- this.resolvedBehaviors = resolvedBehaviors;
257
- this.availableModels = availableModels;
258
- this.preferredProvider = preferredProvider;
259
- this.availableSkills = availableSkills;
260
- this.done = done;
261
- this.mode = mode;
262
- this.filteredModels = [...availableModels];
263
- this.filteredSkills = [...availableSkills];
264
- }
265
-
266
- // ─────────────────────────────────────────────────────────────────────────────
267
- // Helper methods for rendering
268
- // ─────────────────────────────────────────────────────────────────────────────
269
-
270
- /** Pad string to specified visible width */
271
- private pad(s: string, len: number): string {
272
- const vis = visibleWidth(s);
273
- return s + " ".repeat(Math.max(0, len - vis));
274
- }
275
-
276
- /** Create a row with border characters */
277
- private row(content: string): string {
278
- const innerW = this.width - 2;
279
- const clipped = truncateToWidth(content, innerW, "", true);
280
- return this.theme.fg("border", "│") + this.pad(clipped, innerW) + this.theme.fg("border", "│");
281
- }
282
-
283
- /** Render centered header line with border */
284
- private renderHeader(text: string): string {
285
- const innerW = this.width - 2;
286
- const clipped = truncateToWidth(text, innerW, "", true);
287
- const padLen = Math.max(0, innerW - visibleWidth(clipped));
288
- const padLeft = Math.floor(padLen / 2);
289
- const padRight = padLen - padLeft;
290
- return (
291
- this.theme.fg("border", "╭" + "─".repeat(padLeft)) +
292
- this.theme.fg("accent", clipped) +
293
- this.theme.fg("border", "─".repeat(padRight) + "╮")
294
- );
295
- }
296
-
297
- /** Render centered footer line with border */
298
- private renderFooter(text: string): string {
299
- const innerW = this.width - 2;
300
- const clipped = truncateToWidth(text, innerW, "", true);
301
- const padLen = Math.max(0, innerW - visibleWidth(clipped));
302
- const padLeft = Math.floor(padLen / 2);
303
- const padRight = padLen - padLeft;
304
- return (
305
- this.theme.fg("border", "╰" + "─".repeat(padLeft)) +
306
- this.theme.fg("dim", clipped) +
307
- this.theme.fg("border", "─".repeat(padRight) + "╯")
308
- );
309
- }
310
-
311
- /** Exit edit mode and reset state */
312
- private exitEditMode(): void {
313
- this.editingStep = null;
314
- this.editState = createEditorState();
315
- this.tui.requestRender();
316
- }
317
-
318
- // ─────────────────────────────────────────────────────────────────────────────
319
- // Full edit mode methods
320
- // ─────────────────────────────────────────────────────────────────────────────
321
-
322
- /** Render the full-edit takeover view */
323
- private renderFullEditMode(): string[] {
324
- const innerW = this.width - 2;
325
- const textWidth = innerW - 2; // 1 char padding on each side
326
- const lines: string[] = [];
327
-
328
- const { lines: wrapped, starts } = wrapText(this.editState.buffer, textWidth);
329
- const cursorPos = getCursorDisplayPos(this.editState.cursor, starts);
330
- this.editState = {
331
- ...this.editState,
332
- viewportOffset: ensureCursorVisible(
333
- cursorPos.line,
334
- this.EDIT_VIEWPORT_HEIGHT,
335
- this.editState.viewportOffset,
336
- ),
337
- };
338
-
339
- // Header (truncate agent name to prevent overflow)
340
- const fieldName = this.editMode === "template" ? "task" : this.editMode;
341
- const rawAgentName = this.agentConfigs[this.editingStep!]?.name ?? "unknown";
342
- const maxAgentLen = innerW - 30; // Reserve space for " Editing X (Step/Task N: ) "
343
- const agentName = rawAgentName.length > maxAgentLen
344
- ? rawAgentName.slice(0, maxAgentLen - 1) + "…"
345
- : rawAgentName;
346
- // Use mode-appropriate terminology
347
- const stepLabel = this.mode === 'single'
348
- ? agentName
349
- : this.mode === 'parallel'
350
- ? `Task ${this.editingStep! + 1}: ${agentName}`
351
- : `Step ${this.editingStep! + 1}: ${agentName}`;
352
- const headerText = ` Editing ${fieldName} (${stepLabel}) `;
353
- lines.push(this.renderHeader(headerText));
354
- lines.push(this.row(""));
355
-
356
- const editorLines = renderEditor(this.editState, textWidth, this.EDIT_VIEWPORT_HEIGHT);
357
- for (const line of editorLines) {
358
- lines.push(this.row(` ${line}`));
359
- }
360
-
361
- const linesBelow = wrapped.length - this.editState.viewportOffset - this.EDIT_VIEWPORT_HEIGHT;
362
- const hasMore = linesBelow > 0;
363
- const hasLess = this.editState.viewportOffset > 0;
364
- let scrollInfo = "";
365
- if (hasLess) scrollInfo += "↑";
366
- if (hasMore) scrollInfo += `↓ ${linesBelow}+`;
367
-
368
- lines.push(this.row(""));
369
-
370
- const footerText = scrollInfo
371
- ? ` [Esc] Done • [Ctrl+C] Discard • ${scrollInfo} `
372
- : " [Esc] Done • [Ctrl+C] Discard ";
373
- lines.push(this.renderFooter(footerText));
374
-
375
- return lines;
376
- }
377
-
378
- // ─────────────────────────────────────────────────────────────────────────────
379
- // Behavior helpers
380
- // ─────────────────────────────────────────────────────────────────────────────
381
-
382
- /** Get effective behavior for a step (with user overrides applied) */
383
- private getEffectiveBehavior(stepIndex: number): ResolvedStepBehavior {
384
- const base = this.resolvedBehaviors[stepIndex]!;
385
- const override = this.behaviorOverrides.get(stepIndex);
386
- if (!override) return base;
387
-
388
- return {
389
- output: override.output !== undefined ? override.output : base.output,
390
- outputMode: base.outputMode,
391
- reads: override.reads !== undefined ? override.reads : base.reads,
392
- progress: override.progress !== undefined ? override.progress : base.progress,
393
- skills: override.skills !== undefined ? override.skills : base.skills,
394
- model: override.model !== undefined ? override.model : base.model,
395
- };
396
- }
397
-
398
- /** Get the effective model for a step (override or agent default) */
399
- private getEffectiveModel(stepIndex: number): string {
400
- const override = this.behaviorOverrides.get(stepIndex);
401
- if (override?.model) return this.resolveModelFullId(override.model);
402
-
403
- const baseModel = this.resolvedBehaviors[stepIndex]?.model;
404
- if (baseModel) return this.resolveModelFullId(baseModel);
405
- return "default";
406
- }
407
-
408
- /** Resolve a model name to its full provider/model format */
409
- private resolveModelFullId(modelName: string): string {
410
- return resolveModelCandidate(modelName, this.availableModels, this.preferredProvider) ?? modelName;
411
- }
412
-
413
- /** Update a behavior override for a step */
414
- private updateBehavior(stepIndex: number, field: keyof BehaviorOverride, value: string | boolean | string[] | false): void {
415
- const existing = this.behaviorOverrides.get(stepIndex) ?? {};
416
- this.behaviorOverrides.set(stepIndex, { ...existing, [field]: value });
417
- }
418
-
419
- private showNotice(text: string, type: "info" | "error"): void {
420
- this.noticeMessage = { text, type };
421
- if (this.noticeMessageTimer) clearTimeout(this.noticeMessageTimer);
422
- this.noticeMessageTimer = setTimeout(() => {
423
- this.noticeMessage = null;
424
- this.noticeMessageTimer = null;
425
- this.tui.requestRender();
426
- }, 2000);
427
- this.tui.requestRender();
428
- }
429
-
430
- handleInput(data: string): void {
431
- if (this.editingStep !== null) {
432
- if (this.editMode === "model") {
433
- this.handleModelSelectorInput(data);
434
- } else if (this.editMode === "thinking") {
435
- this.handleThinkingSelectorInput(data);
436
- } else if (this.editMode === "skills") {
437
- this.handleSkillSelectorInput(data);
438
- } else {
439
- this.handleEditInput(data);
440
- }
441
- return;
442
- }
443
-
444
- if (matchesKey(data, "escape") || matchesKey(data, "ctrl+c")) {
445
- this.done({ confirmed: false, templates: [], behaviorOverrides: [] });
446
- return;
447
- }
448
-
449
- if (matchesKey(data, "return")) {
450
- const overrides: (BehaviorOverride | undefined)[] = [];
451
- for (let i = 0; i < this.agentConfigs.length; i++) {
452
- overrides.push(this.behaviorOverrides.get(i));
453
- }
454
- this.done({ confirmed: true, templates: this.templates, behaviorOverrides: overrides, runInBackground: this.runInBackground });
455
- return;
456
- }
457
-
458
- if (matchesKey(data, "up")) {
459
- this.selectedStep = Math.max(0, this.selectedStep - 1);
460
- this.tui.requestRender();
461
- return;
462
- }
463
-
464
- if (matchesKey(data, "down")) {
465
- const maxStep = Math.max(0, this.agentConfigs.length - 1);
466
- this.selectedStep = Math.min(maxStep, this.selectedStep + 1);
467
- this.tui.requestRender();
468
- return;
469
- }
470
-
471
- if (data === "e") {
472
- this.enterEditMode("template");
473
- return;
474
- }
475
-
476
- if (data === "m") {
477
- this.enterModelSelector();
478
- return;
479
- }
480
-
481
- if (data === "t") {
482
- this.enterThinkingSelector();
483
- return;
484
- }
485
-
486
- if (data === "s") {
487
- this.editingStep = this.selectedStep;
488
- this.editMode = "skills";
489
- this.skillSearchQuery = "";
490
- this.skillCursorIndex = 0;
491
- this.filteredSkills = [...this.availableSkills];
492
- const current = this.getEffectiveBehavior(this.selectedStep).skills;
493
- this.skillSelectedNames.clear();
494
- if (current !== false && current.length > 0) {
495
- current.forEach((skillName) => this.skillSelectedNames.add(skillName));
496
- }
497
- this.tui.requestRender();
498
- return;
499
- }
500
-
501
- if (data === "w" && this.mode !== 'parallel') {
502
- this.enterEditMode("output");
503
- return;
504
- }
505
-
506
- if (data === "r" && this.mode === 'chain') {
507
- this.enterEditMode("reads");
508
- return;
509
- }
510
-
511
- if (data === "p" && this.mode === 'chain') {
512
- const anyEnabled = this.agentConfigs.some((_, i) => this.getEffectiveBehavior(i).progress);
513
- const newState = !anyEnabled;
514
- for (let i = 0; i < this.agentConfigs.length; i++) {
515
- this.updateBehavior(i, "progress", newState);
516
- }
517
- this.tui.requestRender();
518
- return;
519
- }
520
-
521
- if (data === "b") {
522
- this.runInBackground = !this.runInBackground;
523
- this.tui.requestRender();
524
- return;
525
- }
526
-
527
- }
528
-
529
- private enterEditMode(mode: EditMode): void {
530
- this.editingStep = this.selectedStep;
531
- this.editMode = mode;
532
- let buffer = "";
533
-
534
- if (mode === "template") {
535
- const template = this.templates[this.selectedStep] ?? "";
536
- buffer = template.split("\n")[0] ?? "";
537
- } else if (mode === "output") {
538
- const behavior = this.getEffectiveBehavior(this.selectedStep);
539
- buffer = behavior.output === false ? "" : (behavior.output || "");
540
- } else if (mode === "reads") {
541
- const behavior = this.getEffectiveBehavior(this.selectedStep);
542
- buffer = behavior.reads === false ? "" : (behavior.reads?.join(", ") || "");
543
- }
544
-
545
- this.editState = createEditorState(buffer);
546
- this.tui.requestRender();
547
- }
548
-
549
- /** Enter model selector mode */
550
- private enterModelSelector(): void {
551
- this.editingStep = this.selectedStep;
552
- this.editMode = "model";
553
- this.modelSearchQuery = "";
554
- this.modelSelectedIndex = 0;
555
- this.filteredModels = [...this.availableModels];
556
- const currentModel = splitThinkingSuffix(this.getEffectiveModel(this.selectedStep)).baseModel;
557
- const currentIndex = this.filteredModels.findIndex((m) => m.fullId === currentModel || m.id === currentModel);
558
- if (currentIndex >= 0) {
559
- this.modelSelectedIndex = currentIndex;
560
- }
561
-
562
- this.tui.requestRender();
563
- }
564
-
565
- /** Filter models based on search query */
566
- private filterModels(): void {
567
- const query = this.modelSearchQuery.toLowerCase();
568
- if (!query) {
569
- this.filteredModels = [...this.availableModels];
570
- } else {
571
- this.filteredModels = this.availableModels.filter((m) =>
572
- m.fullId.toLowerCase().includes(query) ||
573
- m.id.toLowerCase().includes(query) ||
574
- m.provider.toLowerCase().includes(query)
575
- );
576
- }
577
- this.modelSelectedIndex = Math.min(this.modelSelectedIndex, Math.max(0, this.filteredModels.length - 1));
578
- }
579
-
580
- private handleModelSelectorInput(data: string): void {
581
- if (matchesKey(data, "escape") || matchesKey(data, "ctrl+c")) {
582
- this.exitEditMode();
583
- return;
584
- }
585
-
586
- if (matchesKey(data, "return")) {
587
- const selected = this.filteredModels[this.modelSelectedIndex];
588
- if (selected) {
589
- const { thinkingSuffix } = splitThinkingSuffix(this.getEffectiveModel(this.editingStep!));
590
- const requestedLevel = thinkingSuffix.slice(1);
591
- const selectedModel = findModelInfo(selected.fullId, this.availableModels, this.preferredProvider);
592
- const suffix = getSupportedThinkingLevels(selectedModel).some((level) => level === requestedLevel) ? thinkingSuffix : "";
593
- this.updateBehavior(this.editingStep!, "model", `${selected.fullId}${suffix}`);
594
- }
595
- this.exitEditMode();
596
- return;
597
- }
598
-
599
- if (matchesKey(data, "up")) {
600
- if (this.filteredModels.length > 0) {
601
- this.modelSelectedIndex = this.modelSelectedIndex === 0
602
- ? this.filteredModels.length - 1
603
- : this.modelSelectedIndex - 1;
604
- }
605
- this.tui.requestRender();
606
- return;
607
- }
608
-
609
- if (matchesKey(data, "down")) {
610
- if (this.filteredModels.length > 0) {
611
- this.modelSelectedIndex = this.modelSelectedIndex === this.filteredModels.length - 1
612
- ? 0
613
- : this.modelSelectedIndex + 1;
614
- }
615
- this.tui.requestRender();
616
- return;
617
- }
618
-
619
- if (matchesKey(data, "backspace")) {
620
- if (this.modelSearchQuery.length > 0) {
621
- this.modelSearchQuery = this.modelSearchQuery.slice(0, -1);
622
- this.filterModels();
623
- }
624
- this.tui.requestRender();
625
- return;
626
- }
627
-
628
- if (data.length === 1 && data.charCodeAt(0) >= 32) {
629
- this.modelSearchQuery += data;
630
- this.filterModels();
631
- this.tui.requestRender();
632
- return;
633
- }
634
- }
635
-
636
- private getAvailableThinkingLevels(stepIndex: number): ThinkingLevel[] {
637
- return getSupportedThinkingLevels(findModelInfo(this.getEffectiveModel(stepIndex), this.availableModels, this.preferredProvider));
638
- }
639
-
640
- /** Enter thinking level selector mode */
641
- private enterThinkingSelector(): void {
642
- if (!this.getEffectiveBehavior(this.selectedStep).model) {
643
- this.showNotice("Select a model first", "error");
644
- return;
645
- }
646
- this.editingStep = this.selectedStep;
647
- this.editMode = "thinking";
648
-
649
- const levels = this.getAvailableThinkingLevels(this.selectedStep);
650
- const { thinkingSuffix } = splitThinkingSuffix(this.getEffectiveModel(this.selectedStep));
651
- const suffix = thinkingSuffix.slice(1);
652
- const levelIdx = levels.findIndex((level) => level === suffix);
653
- this.thinkingSelectedIndex = levelIdx >= 0 ? levelIdx : Math.max(0, levels.indexOf("off"));
654
-
655
- this.tui.requestRender();
656
- }
657
-
658
- private handleThinkingSelectorInput(data: string): void {
659
- if (matchesKey(data, "escape") || matchesKey(data, "ctrl+c")) {
660
- this.exitEditMode();
661
- return;
662
- }
663
-
664
- const levels = this.getAvailableThinkingLevels(this.editingStep!);
665
- if (levels.length === 0) return;
666
-
667
- if (matchesKey(data, "return")) {
668
- const selectedLevel = levels[this.thinkingSelectedIndex] ?? "off";
669
- this.applyThinkingLevel(selectedLevel);
670
- this.exitEditMode();
671
- return;
672
- }
673
-
674
- if (matchesKey(data, "up")) {
675
- this.thinkingSelectedIndex = this.thinkingSelectedIndex === 0
676
- ? levels.length - 1
677
- : this.thinkingSelectedIndex - 1;
678
- this.tui.requestRender();
679
- return;
680
- }
681
-
682
- if (matchesKey(data, "down")) {
683
- this.thinkingSelectedIndex = this.thinkingSelectedIndex === levels.length - 1
684
- ? 0
685
- : this.thinkingSelectedIndex + 1;
686
- this.tui.requestRender();
687
- return;
688
- }
689
- }
690
-
691
- /** Apply thinking level to the current step's model */
692
- private applyThinkingLevel(level: ThinkingLevel): void {
693
- const stepIndex = this.editingStep!;
694
- const currentModel = this.getEffectiveBehavior(stepIndex).model;
695
- if (!currentModel) return;
696
-
697
- const { baseModel } = splitThinkingSuffix(currentModel);
698
- const newModel = level === "off" ? baseModel : `${baseModel}:${level}`;
699
- this.updateBehavior(stepIndex, "model", newModel);
700
- }
701
-
702
- private filterSkills(): void {
703
- const query = this.skillSearchQuery.toLowerCase();
704
- if (!query) {
705
- this.filteredSkills = [...this.availableSkills];
706
- } else {
707
- this.filteredSkills = this.availableSkills.filter((s) =>
708
- s.name.toLowerCase().includes(query) ||
709
- (s.description?.toLowerCase().includes(query) ?? false),
710
- );
711
- }
712
- this.skillCursorIndex = Math.min(this.skillCursorIndex, Math.max(0, this.filteredSkills.length - 1));
713
- }
714
-
715
- private handleSkillSelectorInput(data: string): void {
716
- if (matchesKey(data, "escape") || matchesKey(data, "ctrl+c")) {
717
- this.exitEditMode();
718
- return;
719
- }
720
-
721
- if (matchesKey(data, "return")) {
722
- const selected = [...this.skillSelectedNames];
723
- this.updateBehavior(this.editingStep!, "skills", selected);
724
- this.exitEditMode();
725
- return;
726
- }
727
-
728
- if (data === " ") {
729
- if (this.filteredSkills.length > 0) {
730
- const skill = this.filteredSkills[this.skillCursorIndex];
731
- if (skill) {
732
- if (this.skillSelectedNames.has(skill.name)) {
733
- this.skillSelectedNames.delete(skill.name);
734
- } else {
735
- this.skillSelectedNames.add(skill.name);
736
- }
737
- }
738
- }
739
- this.tui.requestRender();
740
- return;
741
- }
742
-
743
- if (matchesKey(data, "up")) {
744
- if (this.filteredSkills.length > 0) {
745
- this.skillCursorIndex = this.skillCursorIndex === 0
746
- ? this.filteredSkills.length - 1
747
- : this.skillCursorIndex - 1;
748
- }
749
- this.tui.requestRender();
750
- return;
751
- }
752
-
753
- if (matchesKey(data, "down")) {
754
- if (this.filteredSkills.length > 0) {
755
- this.skillCursorIndex = this.skillCursorIndex === this.filteredSkills.length - 1
756
- ? 0
757
- : this.skillCursorIndex + 1;
758
- }
759
- this.tui.requestRender();
760
- return;
761
- }
762
-
763
- if (matchesKey(data, "backspace")) {
764
- if (this.skillSearchQuery.length > 0) {
765
- this.skillSearchQuery = this.skillSearchQuery.slice(0, -1);
766
- this.filterSkills();
767
- }
768
- this.tui.requestRender();
769
- return;
770
- }
771
-
772
- if (data.length === 1 && data.charCodeAt(0) >= 32) {
773
- this.skillSearchQuery += data;
774
- this.filterSkills();
775
- this.tui.requestRender();
776
- return;
777
- }
778
- }
779
-
780
- private handleEditInput(data: string): void {
781
- const textWidth = this.width - 4; // Must match render: innerW - 2 = (width - 2) - 2
782
- if (matchesKey(data, "shift+up") || matchesKey(data, "pageUp")) {
783
- const { lines: wrapped, starts } = wrapText(this.editState.buffer, textWidth);
784
- const cursorPos = getCursorDisplayPos(this.editState.cursor, starts);
785
- const targetLine = Math.max(0, cursorPos.line - this.EDIT_VIEWPORT_HEIGHT);
786
- const targetCol = Math.min(cursorPos.col, wrapped[targetLine]?.length ?? 0);
787
- this.editState = { ...this.editState, cursor: starts[targetLine] + targetCol };
788
- this.tui.requestRender();
789
- return;
790
- }
791
-
792
- if (matchesKey(data, "shift+down") || matchesKey(data, "pageDown")) {
793
- const { lines: wrapped, starts } = wrapText(this.editState.buffer, textWidth);
794
- const cursorPos = getCursorDisplayPos(this.editState.cursor, starts);
795
- const targetLine = Math.min(wrapped.length - 1, cursorPos.line + this.EDIT_VIEWPORT_HEIGHT);
796
- const targetCol = Math.min(cursorPos.col, wrapped[targetLine]?.length ?? 0);
797
- this.editState = { ...this.editState, cursor: starts[targetLine] + targetCol };
798
- this.tui.requestRender();
799
- return;
800
- }
801
-
802
- if (matchesKey(data, "tab")) return;
803
-
804
- const nextState = handleEditorInput(this.editState, data, textWidth);
805
- if (nextState) {
806
- this.editState = nextState;
807
- this.tui.requestRender();
808
- return;
809
- }
810
-
811
- if (matchesKey(data, "escape")) {
812
- this.saveEdit();
813
- this.exitEditMode();
814
- return;
815
- }
816
-
817
- if (matchesKey(data, "ctrl+c")) {
818
- this.exitEditMode();
819
- return;
820
- }
821
- }
822
-
823
- private saveEdit(): void {
824
- const stepIndex = this.editingStep!;
825
-
826
- if (this.editMode === "template") {
827
- // For template, preserve other lines if they existed
828
- const original = this.templates[stepIndex] ?? "";
829
- const originalLines = original.split("\n");
830
- originalLines[0] = this.editState.buffer;
831
- this.templates[stepIndex] = originalLines.join("\n");
832
- } else if (this.editMode === "output") {
833
- // Capture OLD output before updating (for downstream propagation)
834
- const oldBehavior = this.getEffectiveBehavior(stepIndex);
835
- const oldOutput = typeof oldBehavior.output === "string" ? oldBehavior.output : null;
836
-
837
- // Empty string or whitespace means disable output
838
- const trimmed = this.editState.buffer.trim();
839
- const newOutput = trimmed === "" ? false : trimmed;
840
- this.updateBehavior(stepIndex, "output", newOutput);
841
-
842
- // Propagate output filename change to downstream steps' reads
843
- if (oldOutput && typeof newOutput === "string" && oldOutput !== newOutput) {
844
- this.propagateOutputChange(stepIndex, oldOutput, newOutput);
845
- }
846
- } else if (this.editMode === "reads") {
847
- // Parse comma-separated list, empty means disable reads
848
- const trimmed = this.editState.buffer.trim();
849
- if (trimmed === "") {
850
- this.updateBehavior(stepIndex, "reads", false);
851
- } else {
852
- const files = trimmed.split(",").map(f => f.trim()).filter(f => f !== "");
853
- this.updateBehavior(stepIndex, "reads", files.length > 0 ? files : false);
854
- }
855
- }
856
- }
857
-
858
- /**
859
- * When a step's output filename changes, update downstream steps that read from it.
860
- * This maintains the chain dependency automatically.
861
- */
862
- private propagateOutputChange(changedStepIndex: number, oldOutput: string, newOutput: string): void {
863
- // Check all downstream steps (steps that come after the changed step)
864
- for (let i = changedStepIndex + 1; i < this.agentConfigs.length; i++) {
865
- const behavior = this.getEffectiveBehavior(i);
866
-
867
- // Skip if reads is disabled or empty
868
- if (behavior.reads === false || !behavior.reads || behavior.reads.length === 0) {
869
- continue;
870
- }
871
-
872
- // Check if this step reads the old output file
873
- const readsArray = behavior.reads;
874
- const oldIndex = readsArray.indexOf(oldOutput);
875
-
876
- if (oldIndex !== -1) {
877
- // Replace old filename with new filename in reads
878
- const newReads = [...readsArray];
879
- newReads[oldIndex] = newOutput;
880
- this.updateBehavior(i, "reads", newReads);
881
- }
882
- }
883
- }
884
-
885
- render(width: number): string[] {
886
- if (width < 40) return [truncateToWidth("Subagent clarify: widen terminal", width)];
887
- this.renderWidth = Math.min(84, width);
888
- if (this.editingStep !== null) {
889
- if (this.editMode === "model") {
890
- return this.renderModelSelector();
891
- }
892
- if (this.editMode === "thinking") {
893
- return this.renderThinkingSelector();
894
- }
895
- if (this.editMode === "skills") {
896
- return this.renderSkillSelector();
897
- }
898
- return this.renderFullEditMode();
899
- }
900
- // Mode-based navigation rendering
901
- switch (this.mode) {
902
- case 'single': return this.renderSingleMode();
903
- case 'parallel': return this.renderParallelMode();
904
- case 'chain': return this.renderChainMode();
905
- }
906
- }
907
-
908
- /** Render the model selector view */
909
- private renderModelSelector(): string[] {
910
- const th = this.theme;
911
- const lines: string[] = [];
912
-
913
- // Header (mode-aware terminology)
914
- const agentName = this.agentConfigs[this.editingStep!]?.name ?? "unknown";
915
- const stepLabel = this.mode === 'single'
916
- ? agentName
917
- : this.mode === 'parallel'
918
- ? `Task ${this.editingStep! + 1}: ${agentName}`
919
- : `Step ${this.editingStep! + 1}: ${agentName}`;
920
- const headerText = ` Select Model (${stepLabel}) `;
921
- lines.push(this.renderHeader(headerText));
922
- lines.push(this.row(""));
923
-
924
- const searchPrefix = th.fg("dim", "Search: ");
925
- const cursor = "\x1b[7m \x1b[27m"; // Reverse video space for cursor
926
- const searchDisplay = this.modelSearchQuery + cursor;
927
- lines.push(this.row(` ${searchPrefix}${searchDisplay}`));
928
- lines.push(this.row(""));
929
-
930
- const currentModel = this.getEffectiveModel(this.editingStep!);
931
- const currentModelBase = splitThinkingSuffix(currentModel).baseModel;
932
- const currentLabel = th.fg("dim", "Current: ");
933
- lines.push(this.row(` ${currentLabel}${th.fg("warning", currentModel)}`));
934
- lines.push(this.row(""));
935
-
936
- if (this.filteredModels.length === 0) {
937
- lines.push(this.row(` ${th.fg("dim", "No matching models")}`));
938
- } else {
939
- const maxVisible = this.MODEL_SELECTOR_HEIGHT;
940
- let startIdx = 0;
941
-
942
- if (this.filteredModels.length > maxVisible) {
943
- startIdx = Math.max(0, this.modelSelectedIndex - Math.floor(maxVisible / 2));
944
- startIdx = Math.min(startIdx, this.filteredModels.length - maxVisible);
945
- }
946
-
947
- const endIdx = Math.min(startIdx + maxVisible, this.filteredModels.length);
948
-
949
- if (startIdx > 0) {
950
- lines.push(this.row(` ${th.fg("dim", ` ↑ ${startIdx} more`)}`));
951
- }
952
-
953
- for (let i = startIdx; i < endIdx; i++) {
954
- const model = this.filteredModels[i]!;
955
- const isSelected = i === this.modelSelectedIndex;
956
- const isCurrent = model.fullId === currentModelBase || model.id === currentModelBase;
957
- const prefix = isSelected ? th.fg("accent", "→ ") : " ";
958
- const modelText = isSelected ? th.fg("accent", model.id) : model.id;
959
- const providerBadge = th.fg("dim", ` [${model.provider}]`);
960
- const currentBadge = isCurrent ? th.fg("success", " current") : "";
961
-
962
- lines.push(this.row(` ${prefix}${modelText}${providerBadge}${currentBadge}`));
963
- }
964
-
965
- const remaining = this.filteredModels.length - endIdx;
966
- if (remaining > 0) {
967
- lines.push(this.row(` ${th.fg("dim", ` ↓ ${remaining} more`)}`));
968
- }
969
- }
970
-
971
- const contentLines = lines.length;
972
- const targetHeight = 18;
973
- for (let i = contentLines; i < targetHeight; i++) {
974
- lines.push(this.row(""));
975
- }
976
-
977
- const footerText = " [Enter] Select • [Esc] Cancel • Type to search ";
978
- lines.push(this.renderFooter(footerText));
979
-
980
- return lines;
981
- }
982
-
983
- /** Render the thinking level selector view */
984
- private renderThinkingSelector(): string[] {
985
- const th = this.theme;
986
- const lines: string[] = [];
987
-
988
- const agentName = this.agentConfigs[this.editingStep!]?.name ?? "unknown";
989
- const stepLabel = this.mode === 'single'
990
- ? agentName
991
- : this.mode === 'parallel'
992
- ? `Task ${this.editingStep! + 1}: ${agentName}`
993
- : `Step ${this.editingStep! + 1}: ${agentName}`;
994
- const headerText = ` Thinking Level (${stepLabel}) `;
995
- lines.push(this.renderHeader(headerText));
996
- lines.push(this.row(""));
997
-
998
- const currentModel = this.getEffectiveModel(this.editingStep!);
999
- const currentLabel = th.fg("dim", "Model: ");
1000
- lines.push(this.row(` ${currentLabel}${th.fg("accent", currentModel)}`));
1001
- lines.push(this.row(""));
1002
-
1003
- lines.push(this.row(` ${th.fg("dim", "Select thinking level (extended thinking budget):")}`));
1004
- lines.push(this.row(""));
1005
-
1006
- const levelDescriptions: Record<ThinkingLevel, string> = {
1007
- "off": "No extended thinking",
1008
- "minimal": "Brief reasoning",
1009
- "low": "Light reasoning",
1010
- "medium": "Moderate reasoning",
1011
- "high": "Deep reasoning",
1012
- "xhigh": "Extra-high reasoning (ultrathink)",
1013
- "max": "Maximum reasoning",
1014
- };
1015
-
1016
- const levels = this.getAvailableThinkingLevels(this.editingStep!);
1017
- if (levels.length === 0) {
1018
- lines.push(this.row(` ${th.fg("dim", "No supported thinking levels")}`));
1019
- } else {
1020
- for (let i = 0; i < levels.length; i++) {
1021
- const level = levels[i]!;
1022
- const isSelected = i === this.thinkingSelectedIndex;
1023
- const prefix = isSelected ? th.fg("accent", "→ ") : " ";
1024
- const levelText = isSelected ? th.fg("accent", level) : level;
1025
- const desc = th.fg("dim", ` - ${levelDescriptions[level]}`);
1026
- lines.push(this.row(` ${prefix}${levelText}${desc}`));
1027
- }
1028
- }
1029
-
1030
- const contentLines = lines.length;
1031
- const targetHeight = 16;
1032
- for (let i = contentLines; i < targetHeight; i++) {
1033
- lines.push(this.row(""));
1034
- }
1035
-
1036
- const footerText = levels.length === 0
1037
- ? " [Esc] Cancel "
1038
- : " [Enter] Select • [Esc] Cancel • ↑↓ Navigate ";
1039
- lines.push(this.renderFooter(footerText));
1040
-
1041
- return lines;
1042
- }
1043
-
1044
- private renderSkillSelector(): string[] {
1045
- const innerW = this.width - 2;
1046
- const th = this.theme;
1047
- const lines: string[] = [];
1048
-
1049
- const agentName = this.agentConfigs[this.editingStep!]?.name ?? "unknown";
1050
- const stepLabel = this.mode === 'single'
1051
- ? agentName
1052
- : this.mode === 'parallel'
1053
- ? `Task ${this.editingStep! + 1}: ${agentName}`
1054
- : `Step ${this.editingStep! + 1}: ${agentName}`;
1055
- lines.push(this.renderHeader(` Select Skills (${stepLabel}) `));
1056
- lines.push(this.row(""));
1057
-
1058
- const cursor = "\x1b[7m \x1b[27m";
1059
- lines.push(this.row(` ${th.fg("dim", "Search: ")}${this.skillSearchQuery}${cursor}`));
1060
- lines.push(this.row(""));
1061
-
1062
- const selected = [...this.skillSelectedNames].join(", ") || th.fg("dim", "(none)");
1063
- lines.push(this.row(` ${th.fg("dim", "Selected: ")}${truncateToWidth(selected, innerW - 12)}`));
1064
- lines.push(this.row(""));
1065
-
1066
- const selectorHeight = 10;
1067
- if (this.filteredSkills.length === 0) {
1068
- lines.push(this.row(` ${th.fg("dim", "No matching skills")}`));
1069
- } else {
1070
- let startIdx = 0;
1071
- if (this.filteredSkills.length > selectorHeight) {
1072
- startIdx = Math.max(0, this.skillCursorIndex - Math.floor(selectorHeight / 2));
1073
- startIdx = Math.min(startIdx, this.filteredSkills.length - selectorHeight);
1074
- }
1075
- const endIdx = Math.min(startIdx + selectorHeight, this.filteredSkills.length);
1076
-
1077
- if (startIdx > 0) {
1078
- lines.push(this.row(` ${th.fg("dim", ` ↑ ${startIdx} more`)}`));
1079
- }
1080
-
1081
- for (let i = startIdx; i < endIdx; i++) {
1082
- const skill = this.filteredSkills[i]!;
1083
- const isCursor = i === this.skillCursorIndex;
1084
- const isSelected = this.skillSelectedNames.has(skill.name);
1085
-
1086
- const prefix = isCursor ? th.fg("accent", "→ ") : " ";
1087
- const checkbox = isSelected ? th.fg("success", "[x]") : "[ ]";
1088
- const nameText = isCursor ? th.fg("accent", skill.name) : skill.name;
1089
- const sourceBadge = th.fg("dim", ` [${skill.source}]`);
1090
- const desc = skill.description
1091
- ? th.fg("dim", ` - ${truncateToWidth(skill.description, 25)}`)
1092
- : "";
1093
-
1094
- lines.push(this.row(` ${prefix}${checkbox} ${nameText}${sourceBadge}${desc}`));
1095
- }
1096
-
1097
- const remaining = this.filteredSkills.length - endIdx;
1098
- if (remaining > 0) {
1099
- lines.push(this.row(` ${th.fg("dim", ` ↓ ${remaining} more`)}`));
1100
- }
1101
- }
1102
-
1103
- const targetHeight = 18;
1104
- for (let i = lines.length; i < targetHeight; i++) {
1105
- lines.push(this.row(""));
1106
- }
1107
-
1108
- lines.push(this.renderFooter(" [Enter] Confirm • [Space] Toggle • [Esc] Cancel "));
1109
- return lines;
1110
- }
1111
-
1112
- private getFooterText(): string {
1113
- const bgLabel = this.runInBackground ? '[b]g:ON' : '[b]g';
1114
- switch (this.mode) {
1115
- case 'single':
1116
- return ` [Enter] Run • [Esc] Cancel • e m t w s ${bgLabel} `;
1117
- case 'parallel':
1118
- return ` [Enter] Run • [Esc] Cancel • e m t s ${bgLabel} • ↑↓ Nav `;
1119
- case 'chain':
1120
- return ` [Enter] Run • [Esc] Cancel • e m t w r p s ${bgLabel} • ↑↓ Nav `;
1121
- }
1122
- }
1123
-
1124
- private appendNotice(lines: string[]): void {
1125
- if (!this.noticeMessage) return;
1126
- const color = this.noticeMessage.type === "error" ? "error" : "success";
1127
- lines.push(this.row(` ${this.theme.fg(color, this.noticeMessage.text)}`));
1128
- }
1129
-
1130
- private renderSingleMode(): string[] {
1131
- const innerW = this.width - 2;
1132
- const th = this.theme;
1133
- const lines: string[] = [];
1134
-
1135
- const agentName = this.agentConfigs[0]?.name ?? "unknown";
1136
- const maxHeaderLen = innerW - 4;
1137
- const headerText = ` Agent: ${truncateToWidth(agentName, maxHeaderLen - 9)} `;
1138
- lines.push(this.renderHeader(headerText));
1139
- lines.push(this.row(""));
1140
-
1141
- const config = this.agentConfigs[0]!;
1142
- const behavior = this.getEffectiveBehavior(0);
1143
-
1144
- const stepLabel = config.name;
1145
- lines.push(this.row(` ${th.fg("accent", "▶ " + stepLabel)}`));
1146
-
1147
- const template = (this.templates[0] ?? "").split("\n")[0] ?? "";
1148
- const taskLabel = th.fg("dim", "task: ");
1149
- lines.push(this.row(` ${taskLabel}${truncateToWidth(template, innerW - 12)}`));
1150
-
1151
- const effectiveModel = this.getEffectiveModel(0);
1152
- const override = this.behaviorOverrides.get(0);
1153
- const isOverridden = override?.model !== undefined;
1154
- const modelValue = isOverridden
1155
- ? th.fg("warning", effectiveModel) + th.fg("dim", " ✎")
1156
- : effectiveModel;
1157
- const modelLabel = th.fg("dim", "model: ");
1158
- lines.push(this.row(` ${modelLabel}${truncateToWidth(modelValue, innerW - 13)}`));
1159
-
1160
- const writesValue = behavior.output === false
1161
- ? th.fg("dim", "(disabled)")
1162
- : (behavior.output || th.fg("dim", "(none)"));
1163
- const writesLabel = th.fg("dim", "writes: ");
1164
- lines.push(this.row(` ${writesLabel}${truncateToWidth(writesValue, innerW - 14)}`));
1165
-
1166
- const skillsValue = behavior.skills === false
1167
- ? th.fg("dim", "(disabled)")
1168
- : (behavior.skills?.length ? behavior.skills.join(", ") : th.fg("dim", "(none)"));
1169
- const skillsLabel = th.fg("dim", "skills: ");
1170
- lines.push(this.row(` ${skillsLabel}${truncateToWidth(skillsValue, innerW - 14)}`));
1171
-
1172
- lines.push(this.row(""));
1173
-
1174
- this.appendNotice(lines);
1175
- lines.push(this.renderFooter(this.getFooterText()));
1176
-
1177
- return lines;
1178
- }
1179
-
1180
- private renderParallelMode(): string[] {
1181
- const innerW = this.width - 2;
1182
- const th = this.theme;
1183
- const lines: string[] = [];
1184
-
1185
- const headerText = ` Parallel Tasks (${this.agentConfigs.length}) `;
1186
- lines.push(this.renderHeader(headerText));
1187
- lines.push(this.row(""));
1188
-
1189
- for (let i = 0; i < this.agentConfigs.length; i++) {
1190
- const config = this.agentConfigs[i]!;
1191
- const isSelected = i === this.selectedStep;
1192
-
1193
- const color = isSelected ? "accent" : "dim";
1194
- const prefix = isSelected ? "▶ " : " ";
1195
- const taskPrefix = `Task ${i + 1}: `;
1196
- const maxNameLen = innerW - 4 - prefix.length - taskPrefix.length;
1197
- const agentName = config.name.length > maxNameLen
1198
- ? config.name.slice(0, maxNameLen - 1) + "…"
1199
- : config.name;
1200
- const taskLabel = `${taskPrefix}${agentName}`;
1201
- lines.push(this.row(` ${th.fg(color, prefix + taskLabel)}`));
1202
-
1203
- const template = (this.templates[i] ?? "").split("\n")[0] ?? "";
1204
- const taskTextLabel = th.fg("dim", "task: ");
1205
- lines.push(this.row(` ${taskTextLabel}${truncateToWidth(template, innerW - 12)}`));
1206
-
1207
- const effectiveModel = this.getEffectiveModel(i);
1208
- const override = this.behaviorOverrides.get(i);
1209
- const isOverridden = override?.model !== undefined;
1210
- const modelValue = isOverridden
1211
- ? th.fg("warning", effectiveModel) + th.fg("dim", " ✎")
1212
- : effectiveModel;
1213
- const modelLabel = th.fg("dim", "model: ");
1214
- lines.push(this.row(` ${modelLabel}${truncateToWidth(modelValue, innerW - 13)}`));
1215
-
1216
- const behavior = this.getEffectiveBehavior(i);
1217
- const skillsValue = behavior.skills === false
1218
- ? th.fg("dim", "(disabled)")
1219
- : (behavior.skills?.length ? behavior.skills.join(", ") : th.fg("dim", "(none)"));
1220
- const skillsLabel = th.fg("dim", "skills: ");
1221
- lines.push(this.row(` ${skillsLabel}${truncateToWidth(skillsValue, innerW - 14)}`));
1222
-
1223
- lines.push(this.row(""));
1224
- }
1225
-
1226
- this.appendNotice(lines);
1227
- lines.push(this.renderFooter(this.getFooterText()));
1228
-
1229
- return lines;
1230
- }
1231
-
1232
- private renderChainMode(): string[] {
1233
- const innerW = this.width - 2;
1234
- const th = this.theme;
1235
- const lines: string[] = [];
1236
-
1237
- const chainLabel = this.agentConfigs.map((c) => c.name).join(" → ");
1238
- const maxHeaderLen = innerW - 4;
1239
- const headerText = ` Chain: ${truncateToWidth(chainLabel, maxHeaderLen - 9)} `;
1240
- lines.push(this.renderHeader(headerText));
1241
-
1242
- lines.push(this.row(""));
1243
-
1244
- const taskPreview = truncateToWidth(this.originalTask, innerW - 16);
1245
- lines.push(this.row(` Original Task: ${taskPreview}`));
1246
- const chainDirPreview = truncateToWidth(this.chainDir ?? "", innerW - 12);
1247
- lines.push(this.row(` Chain Dir: ${th.fg("dim", chainDirPreview)}`));
1248
-
1249
- const progressEnabled = this.agentConfigs.some((_, i) => this.getEffectiveBehavior(i).progress);
1250
- const progressValue = progressEnabled ? th.fg("success", "enabled") : th.fg("dim", "disabled");
1251
- lines.push(this.row(` Progress: ${progressValue} ${th.fg("dim", "(press [p] to toggle)")}`));
1252
- lines.push(this.row(""));
1253
-
1254
- for (let i = 0; i < this.agentConfigs.length; i++) {
1255
- const config = this.agentConfigs[i]!;
1256
- const isSelected = i === this.selectedStep;
1257
- const behavior = this.getEffectiveBehavior(i);
1258
-
1259
- const color = isSelected ? "accent" : "dim";
1260
- const prefix = isSelected ? "▶ " : " ";
1261
- const stepPrefix = `Step ${i + 1}: `;
1262
- const maxNameLen = innerW - 4 - prefix.length - stepPrefix.length;
1263
- const agentName = config.name.length > maxNameLen
1264
- ? config.name.slice(0, maxNameLen - 1) + "…"
1265
- : config.name;
1266
- const stepLabel = `${stepPrefix}${agentName}`;
1267
- lines.push(
1268
- this.row(` ${th.fg(color, prefix + stepLabel)}`),
1269
- );
1270
-
1271
- const template = (this.templates[i] ?? "").split("\n")[0] ?? "";
1272
- const highlighted = template
1273
- .replace(/\{task\}/g, th.fg("success", "{task}"))
1274
- .replace(/\{previous\}/g, th.fg("warning", "{previous}"))
1275
- .replace(/\{chain_dir\}/g, th.fg("accent", "{chain_dir}"));
1276
-
1277
- const templateLabel = th.fg("dim", "task: ");
1278
- lines.push(this.row(` ${templateLabel}${truncateToWidth(highlighted, innerW - 12)}`));
1279
-
1280
- const effectiveModel = this.getEffectiveModel(i);
1281
- const override = this.behaviorOverrides.get(i);
1282
- const isOverridden = override?.model !== undefined;
1283
- const modelValue = isOverridden
1284
- ? th.fg("warning", effectiveModel) + th.fg("dim", " ✎")
1285
- : effectiveModel;
1286
- const modelLabel = th.fg("dim", "model: ");
1287
- lines.push(this.row(` ${modelLabel}${truncateToWidth(modelValue, innerW - 13)}`));
1288
-
1289
- const writesValue = behavior.output === false
1290
- ? th.fg("dim", "(disabled)")
1291
- : (behavior.output || th.fg("dim", "(none)"));
1292
- const writesLabel = th.fg("dim", "writes: ");
1293
- lines.push(this.row(` ${writesLabel}${truncateToWidth(writesValue, innerW - 14)}`));
1294
-
1295
- const readsValue = behavior.reads === false
1296
- ? th.fg("dim", "(disabled)")
1297
- : (behavior.reads && behavior.reads.length > 0
1298
- ? behavior.reads.join(", ")
1299
- : th.fg("dim", "(none)"));
1300
- const readsLabel = th.fg("dim", "reads: ");
1301
- lines.push(this.row(` ${readsLabel}${truncateToWidth(readsValue, innerW - 13)}`));
1302
-
1303
- const skillsValue = behavior.skills === false
1304
- ? th.fg("dim", "(disabled)")
1305
- : (behavior.skills?.length ? behavior.skills.join(", ") : th.fg("dim", "(none)"));
1306
- const skillsLabel = th.fg("dim", "skills: ");
1307
- lines.push(this.row(` ${skillsLabel}${truncateToWidth(skillsValue, innerW - 14)}`));
1308
-
1309
- if (progressEnabled) {
1310
- const isFirstStep = i === 0;
1311
- const progressAction = isFirstStep
1312
- ? th.fg("success", "writes progress.md")
1313
- : th.fg("accent", "reads progress.md");
1314
- const progressLabel = th.fg("dim", "progress: ");
1315
- lines.push(this.row(` ${progressLabel}${progressAction}`));
1316
- }
1317
-
1318
- if (i < this.agentConfigs.length - 1) {
1319
- const nextStepUsePrevious = (this.templates[i + 1] ?? "").includes("{previous}");
1320
- if (nextStepUsePrevious) {
1321
- const indicator = th.fg("dim", " ↳ response → ") + th.fg("warning", "{previous}");
1322
- lines.push(this.row(indicator));
1323
- }
1324
- }
1325
-
1326
- lines.push(this.row(""));
1327
- }
1328
-
1329
- this.appendNotice(lines);
1330
- lines.push(this.renderFooter(this.getFooterText()));
1331
-
1332
- return lines;
1333
- }
1334
-
1335
- invalidate(): void {}
1336
- dispose(): void {
1337
- if (this.noticeMessageTimer) clearTimeout(this.noticeMessageTimer);
1338
- this.noticeMessageTimer = null;
1339
- }
500
+ function searchInput(query: string, data: string): string {
501
+ if (matchesKey(data, "backspace")) {
502
+ return query.slice(0, -1);
503
+ }
504
+ return data.length === 1 && data.charCodeAt(0) >= 32 ? query + data : query;
1340
505
  }