@ashx-j/lunr 0.2.11 → 0.2.13

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 (342) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +211 -236
  3. package/dist/builtin-extensions/ashxj-tui.d.ts +3 -0
  4. package/dist/builtin-extensions/ashxj-tui.d.ts.map +1 -1
  5. package/dist/builtin-extensions/ashxj-tui.js +28 -11
  6. package/dist/builtin-extensions/ashxj-tui.js.map +1 -1
  7. package/dist/builtin-extensions/index.d.ts.map +1 -1
  8. package/dist/builtin-extensions/index.js +0 -2
  9. package/dist/builtin-extensions/index.js.map +1 -1
  10. package/dist/builtin-extensions/lunr-cron.d.ts +1 -1
  11. package/dist/builtin-extensions/lunr-cron.d.ts.map +1 -1
  12. package/dist/builtin-extensions/lunr-cron.js +1 -1
  13. package/dist/builtin-extensions/lunr-cron.js.map +1 -1
  14. package/dist/builtin-extensions/pi-subagents/src/extension/chain-validation.d.ts.map +1 -1
  15. package/dist/builtin-extensions/pi-subagents/src/extension/chain-validation.js +3 -3
  16. package/dist/builtin-extensions/pi-subagents/src/extension/chain-validation.js.map +1 -1
  17. package/dist/builtin-extensions/pi-subagents/src/extension/doctor.d.ts +0 -2
  18. package/dist/builtin-extensions/pi-subagents/src/extension/doctor.d.ts.map +1 -1
  19. package/dist/builtin-extensions/pi-subagents/src/extension/doctor.js +1 -23
  20. package/dist/builtin-extensions/pi-subagents/src/extension/doctor.js.map +1 -1
  21. package/dist/builtin-extensions/pi-subagents/src/extension/fanout-child.d.ts.map +1 -1
  22. package/dist/builtin-extensions/pi-subagents/src/extension/fanout-child.js +6 -7
  23. package/dist/builtin-extensions/pi-subagents/src/extension/fanout-child.js.map +1 -1
  24. package/dist/builtin-extensions/pi-subagents/src/extension/index.d.ts +1 -1
  25. package/dist/builtin-extensions/pi-subagents/src/extension/index.d.ts.map +1 -1
  26. package/dist/builtin-extensions/pi-subagents/src/extension/index.js +7 -14
  27. package/dist/builtin-extensions/pi-subagents/src/extension/index.js.map +1 -1
  28. package/dist/builtin-extensions/pi-subagents/src/extension/schemas.d.ts +12 -9
  29. package/dist/builtin-extensions/pi-subagents/src/extension/schemas.d.ts.map +1 -1
  30. package/dist/builtin-extensions/pi-subagents/src/extension/schemas.js +38 -34
  31. package/dist/builtin-extensions/pi-subagents/src/extension/schemas.js.map +1 -1
  32. package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.d.ts +3 -3
  33. package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.d.ts.map +1 -1
  34. package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.js +32 -47
  35. package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.js.map +1 -1
  36. package/dist/builtin-extensions/pi-subagents/src/intercom/intercom-bridge.d.ts +2 -2
  37. package/dist/builtin-extensions/pi-subagents/src/intercom/intercom-bridge.d.ts.map +1 -1
  38. package/dist/builtin-extensions/pi-subagents/src/intercom/intercom-bridge.js.map +1 -1
  39. package/dist/builtin-extensions/pi-subagents/src/runs/background/async-execution.d.ts +7 -10
  40. package/dist/builtin-extensions/pi-subagents/src/runs/background/async-execution.d.ts.map +1 -1
  41. package/dist/builtin-extensions/pi-subagents/src/runs/background/async-execution.js +90 -109
  42. package/dist/builtin-extensions/pi-subagents/src/runs/background/async-execution.js.map +1 -1
  43. package/dist/builtin-extensions/pi-subagents/src/runs/background/async-resume.d.ts +3 -2
  44. package/dist/builtin-extensions/pi-subagents/src/runs/background/async-resume.d.ts.map +1 -1
  45. package/dist/builtin-extensions/pi-subagents/src/runs/background/async-resume.js +37 -50
  46. package/dist/builtin-extensions/pi-subagents/src/runs/background/async-resume.js.map +1 -1
  47. package/dist/builtin-extensions/pi-subagents/src/runs/background/run-status.d.ts.map +1 -1
  48. package/dist/builtin-extensions/pi-subagents/src/runs/background/run-status.js +1 -1
  49. package/dist/builtin-extensions/pi-subagents/src/runs/background/run-status.js.map +1 -1
  50. package/dist/builtin-extensions/pi-subagents/src/runs/background/scheduled-runs.d.ts +4 -0
  51. package/dist/builtin-extensions/pi-subagents/src/runs/background/scheduled-runs.d.ts.map +1 -1
  52. package/dist/builtin-extensions/pi-subagents/src/runs/background/scheduled-runs.js +5 -5
  53. package/dist/builtin-extensions/pi-subagents/src/runs/background/scheduled-runs.js.map +1 -1
  54. package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-runner.d.ts.map +1 -1
  55. package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-runner.js +31 -13
  56. package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-runner.js.map +1 -1
  57. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/chain-clarify.d.ts +2 -2
  58. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/chain-clarify.d.ts.map +1 -1
  59. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/chain-clarify.js.map +1 -1
  60. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/chain-execution.d.ts +4 -3
  61. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/chain-execution.d.ts.map +1 -1
  62. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/chain-execution.js +48 -20
  63. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/chain-execution.js.map +1 -1
  64. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/execution.d.ts +2 -2
  65. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/execution.d.ts.map +1 -1
  66. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/execution.js +90 -64
  67. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/execution.js.map +1 -1
  68. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.d.ts +6 -8
  69. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.d.ts.map +1 -1
  70. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.js +262 -161
  71. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.js.map +1 -1
  72. package/dist/builtin-extensions/pi-subagents/src/runs/shared/acceptance.d.ts +2 -1
  73. package/dist/builtin-extensions/pi-subagents/src/runs/shared/acceptance.d.ts.map +1 -1
  74. package/dist/builtin-extensions/pi-subagents/src/runs/shared/acceptance.js +24 -33
  75. package/dist/builtin-extensions/pi-subagents/src/runs/shared/acceptance.js.map +1 -1
  76. package/dist/builtin-extensions/pi-subagents/src/runs/shared/child-tools.d.ts +25 -0
  77. package/dist/builtin-extensions/pi-subagents/src/runs/shared/child-tools.d.ts.map +1 -0
  78. package/dist/builtin-extensions/pi-subagents/src/runs/shared/child-tools.js +51 -0
  79. package/dist/builtin-extensions/pi-subagents/src/runs/shared/child-tools.js.map +1 -0
  80. package/dist/builtin-extensions/pi-subagents/src/runs/shared/dynamic-fanout.d.ts.map +1 -1
  81. package/dist/builtin-extensions/pi-subagents/src/runs/shared/dynamic-fanout.js +20 -3
  82. package/dist/builtin-extensions/pi-subagents/src/runs/shared/dynamic-fanout.js.map +1 -1
  83. package/dist/builtin-extensions/pi-subagents/src/runs/shared/parallel-utils.d.ts +4 -1
  84. package/dist/builtin-extensions/pi-subagents/src/runs/shared/parallel-utils.d.ts.map +1 -1
  85. package/dist/builtin-extensions/pi-subagents/src/runs/shared/parallel-utils.js.map +1 -1
  86. package/dist/builtin-extensions/pi-subagents/src/runs/shared/pi-args.d.ts +5 -0
  87. package/dist/builtin-extensions/pi-subagents/src/runs/shared/pi-args.d.ts.map +1 -1
  88. package/dist/builtin-extensions/pi-subagents/src/runs/shared/pi-args.js +9 -3
  89. package/dist/builtin-extensions/pi-subagents/src/runs/shared/pi-args.js.map +1 -1
  90. package/dist/builtin-extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.d.ts.map +1 -1
  91. package/dist/builtin-extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.js +2 -2
  92. package/dist/builtin-extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.js.map +1 -1
  93. package/dist/builtin-extensions/pi-subagents/src/runs/shared/task-intent.d.ts +2 -2
  94. package/dist/builtin-extensions/pi-subagents/src/runs/shared/task-intent.d.ts.map +1 -1
  95. package/dist/builtin-extensions/pi-subagents/src/runs/shared/task-intent.js +20 -17
  96. package/dist/builtin-extensions/pi-subagents/src/runs/shared/task-intent.js.map +1 -1
  97. package/dist/builtin-extensions/pi-subagents/src/shared/child-spec.d.ts +70 -0
  98. package/dist/builtin-extensions/pi-subagents/src/shared/child-spec.d.ts.map +1 -0
  99. package/dist/builtin-extensions/pi-subagents/src/shared/child-spec.js +153 -0
  100. package/dist/builtin-extensions/pi-subagents/src/shared/child-spec.js.map +1 -0
  101. package/dist/builtin-extensions/pi-subagents/src/shared/settings.d.ts +13 -8
  102. package/dist/builtin-extensions/pi-subagents/src/shared/settings.d.ts.map +1 -1
  103. package/dist/builtin-extensions/pi-subagents/src/shared/settings.js +20 -51
  104. package/dist/builtin-extensions/pi-subagents/src/shared/settings.js.map +1 -1
  105. package/dist/builtin-extensions/pi-subagents/src/shared/types.d.ts +80 -20
  106. package/dist/builtin-extensions/pi-subagents/src/shared/types.d.ts.map +1 -1
  107. package/dist/builtin-extensions/pi-subagents/src/shared/types.js +10 -2
  108. package/dist/builtin-extensions/pi-subagents/src/shared/types.js.map +1 -1
  109. package/dist/builtin-extensions/pi-subagents/src/slash/prompt-workflows.d.ts +2 -2
  110. package/dist/builtin-extensions/pi-subagents/src/slash/prompt-workflows.d.ts.map +1 -1
  111. package/dist/builtin-extensions/pi-subagents/src/slash/prompt-workflows.js +20 -32
  112. package/dist/builtin-extensions/pi-subagents/src/slash/prompt-workflows.js.map +1 -1
  113. package/dist/builtin-extensions/pi-subagents/src/slash/slash-commands.d.ts +2 -1
  114. package/dist/builtin-extensions/pi-subagents/src/slash/slash-commands.d.ts.map +1 -1
  115. package/dist/builtin-extensions/pi-subagents/src/slash/slash-commands.js +23 -449
  116. package/dist/builtin-extensions/pi-subagents/src/slash/slash-commands.js.map +1 -1
  117. package/dist/builtin-extensions/pi-subagents/src/tui/render.d.ts +2 -1
  118. package/dist/builtin-extensions/pi-subagents/src/tui/render.d.ts.map +1 -1
  119. package/dist/builtin-extensions/pi-subagents/src/tui/render.js +13 -11
  120. package/dist/builtin-extensions/pi-subagents/src/tui/render.js.map +1 -1
  121. package/dist/builtin-extensions/pi-subagents/src/watchdog/runtime.d.ts +2 -1
  122. package/dist/builtin-extensions/pi-subagents/src/watchdog/runtime.d.ts.map +1 -1
  123. package/dist/builtin-extensions/pi-subagents/src/watchdog/runtime.js +18 -17
  124. package/dist/builtin-extensions/pi-subagents/src/watchdog/runtime.js.map +1 -1
  125. package/dist/builtin-extensions/simple-pi-memory.d.ts +2 -2
  126. package/dist/builtin-extensions/simple-pi-memory.d.ts.map +1 -1
  127. package/dist/builtin-extensions/simple-pi-memory.js +41 -17
  128. package/dist/builtin-extensions/simple-pi-memory.js.map +1 -1
  129. package/dist/catalog/models.json +1 -1
  130. package/dist/catalog/providers/amazon-bedrock.json +1 -1
  131. package/dist/catalog/providers/azure-openai-responses.json +1 -1
  132. package/dist/catalog/providers/cloudflare-ai-gateway.json +1 -1
  133. package/dist/catalog/providers/cloudflare-workers-ai.json +1 -1
  134. package/dist/catalog/providers/fireworks.json +1 -1
  135. package/dist/catalog/providers/github-copilot.json +1 -1
  136. package/dist/catalog/providers/huggingface.json +1 -1
  137. package/dist/catalog/providers/minimax-cn.json +1 -1
  138. package/dist/catalog/providers/minimax.json +1 -1
  139. package/dist/catalog/providers/mistral.json +1 -1
  140. package/dist/catalog/providers/nvidia.json +1 -1
  141. package/dist/catalog/providers/openai.json +1 -1
  142. package/dist/catalog/providers/opencode-go.json +1 -1
  143. package/dist/catalog/providers/opencode.json +1 -1
  144. package/dist/catalog/providers/openrouter.json +1 -1
  145. package/dist/catalog/providers/qwen-cloud-cn.json +1 -1
  146. package/dist/catalog/providers/qwen-cloud.json +1 -1
  147. package/dist/catalog/providers/qwen-token-plan-cn.json +1 -1
  148. package/dist/catalog/providers/qwen-token-plan.json +1 -1
  149. package/dist/catalog/providers/together.json +1 -1
  150. package/dist/catalog/providers/vercel-ai-gateway.json +1 -1
  151. package/dist/catalog/providers/zai-coding-cn.json +1 -1
  152. package/dist/catalog/providers/zai.json +1 -1
  153. package/dist/catalog/publication.json +3 -3
  154. package/dist/core/agent-session.d.ts +7 -1
  155. package/dist/core/agent-session.d.ts.map +1 -1
  156. package/dist/core/agent-session.js +34 -4
  157. package/dist/core/agent-session.js.map +1 -1
  158. package/dist/core/context-breakdown.d.ts +3 -1
  159. package/dist/core/context-breakdown.d.ts.map +1 -1
  160. package/dist/core/context-breakdown.js +5 -2
  161. package/dist/core/context-breakdown.js.map +1 -1
  162. package/dist/core/customize.d.ts +2 -0
  163. package/dist/core/customize.d.ts.map +1 -1
  164. package/dist/core/customize.js +6 -0
  165. package/dist/core/customize.js.map +1 -1
  166. package/dist/core/memory-cap.d.ts +4 -8
  167. package/dist/core/memory-cap.d.ts.map +1 -1
  168. package/dist/core/memory-cap.js +8 -8
  169. package/dist/core/memory-cap.js.map +1 -1
  170. package/dist/core/permissions.d.ts +2 -0
  171. package/dist/core/permissions.d.ts.map +1 -1
  172. package/dist/core/permissions.js +30 -14
  173. package/dist/core/permissions.js.map +1 -1
  174. package/dist/core/plan-mode.d.ts.map +1 -1
  175. package/dist/core/plan-mode.js +1 -9
  176. package/dist/core/plan-mode.js.map +1 -1
  177. package/dist/core/rollback.d.ts.map +1 -1
  178. package/dist/core/rollback.js +1 -1
  179. package/dist/core/rollback.js.map +1 -1
  180. package/dist/core/settings-manager.d.ts +16 -5
  181. package/dist/core/settings-manager.d.ts.map +1 -1
  182. package/dist/core/settings-manager.js +37 -10
  183. package/dist/core/settings-manager.js.map +1 -1
  184. package/dist/core/slash-commands.d.ts.map +1 -1
  185. package/dist/core/slash-commands.js +1 -5
  186. package/dist/core/slash-commands.js.map +1 -1
  187. package/dist/core/subagent-permission-inherit.d.ts +34 -22
  188. package/dist/core/subagent-permission-inherit.d.ts.map +1 -1
  189. package/dist/core/subagent-permission-inherit.js +37 -80
  190. package/dist/core/subagent-permission-inherit.js.map +1 -1
  191. package/dist/core/swarm.d.ts.map +1 -1
  192. package/dist/core/swarm.js +13 -7
  193. package/dist/core/swarm.js.map +1 -1
  194. package/dist/core/system-prompt.d.ts +6 -4
  195. package/dist/core/system-prompt.d.ts.map +1 -1
  196. package/dist/core/system-prompt.js +38 -48
  197. package/dist/core/system-prompt.js.map +1 -1
  198. package/dist/core/usage-adapters/openai-codex.d.ts +1 -1
  199. package/dist/core/usage-adapters/openai-codex.d.ts.map +1 -1
  200. package/dist/core/usage-adapters/openai-codex.js +22 -17
  201. package/dist/core/usage-adapters/openai-codex.js.map +1 -1
  202. package/dist/core/usage-service.d.ts +10 -1
  203. package/dist/core/usage-service.d.ts.map +1 -1
  204. package/dist/core/usage-service.js +17 -6
  205. package/dist/core/usage-service.js.map +1 -1
  206. package/dist/gateway/agent-bridge.d.ts.map +1 -1
  207. package/dist/gateway/agent-bridge.js +1 -4
  208. package/dist/gateway/agent-bridge.js.map +1 -1
  209. package/dist/gateway/cron.d.ts.map +1 -1
  210. package/dist/gateway/cron.js +1 -4
  211. package/dist/gateway/cron.js.map +1 -1
  212. package/dist/index.d.ts +1 -1
  213. package/dist/index.d.ts.map +1 -1
  214. package/dist/index.js.map +1 -1
  215. package/dist/main.d.ts.map +1 -1
  216. package/dist/main.js +0 -5
  217. package/dist/main.js.map +1 -1
  218. package/dist/migrations.d.ts +2 -0
  219. package/dist/migrations.d.ts.map +1 -1
  220. package/dist/migrations.js +21 -0
  221. package/dist/migrations.js.map +1 -1
  222. package/dist/modes/interactive/components/settings-selector.d.ts +7 -4
  223. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  224. package/dist/modes/interactive/components/settings-selector.js +122 -162
  225. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  226. package/dist/modes/interactive/components/usage-view.d.ts +1 -1
  227. package/dist/modes/interactive/components/usage-view.d.ts.map +1 -1
  228. package/dist/modes/interactive/components/usage-view.js +7 -4
  229. package/dist/modes/interactive/components/usage-view.js.map +1 -1
  230. package/dist/modes/interactive/interactive-mode.d.ts +1 -2
  231. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  232. package/dist/modes/interactive/interactive-mode.js +55 -96
  233. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  234. package/dist/modes/interactive/skill-tag-autocomplete.d.ts +13 -0
  235. package/dist/modes/interactive/skill-tag-autocomplete.d.ts.map +1 -0
  236. package/dist/modes/interactive/skill-tag-autocomplete.js +72 -0
  237. package/dist/modes/interactive/skill-tag-autocomplete.js.map +1 -0
  238. package/docs/compaction.md +23 -23
  239. package/docs/containerization.md +24 -24
  240. package/docs/custom-provider.md +18 -18
  241. package/docs/development.md +16 -15
  242. package/docs/docs.json +6 -2
  243. package/docs/extensions.md +82 -80
  244. package/docs/features.md +102 -0
  245. package/docs/index.md +25 -19
  246. package/docs/json.md +7 -7
  247. package/docs/keybindings.md +4 -4
  248. package/docs/models.md +2 -2
  249. package/docs/packages.md +38 -42
  250. package/docs/prompt-templates.md +4 -4
  251. package/docs/providers.md +22 -10
  252. package/docs/quickstart.md +81 -46
  253. package/docs/rpc.md +7 -13
  254. package/docs/sdk.md +43 -41
  255. package/docs/security.md +18 -18
  256. package/docs/session-format.md +9 -9
  257. package/docs/sessions.md +13 -12
  258. package/docs/settings.md +63 -24
  259. package/docs/shell-aliases.md +2 -2
  260. package/docs/skills.md +21 -11
  261. package/docs/terminal-setup.md +5 -5
  262. package/docs/termux.md +7 -7
  263. package/docs/themes.md +12 -14
  264. package/docs/tmux.md +4 -4
  265. package/docs/tui.md +23 -23
  266. package/docs/usage.md +112 -87
  267. package/docs/windows.md +10 -2
  268. package/examples/README.md +3 -1
  269. package/examples/extensions/README.md +6 -6
  270. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  271. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  272. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  273. package/examples/extensions/doom-overlay/README.md +2 -2
  274. package/examples/extensions/gondolin/package-lock.json +2 -2
  275. package/examples/extensions/gondolin/package.json +1 -1
  276. package/examples/extensions/plan-mode/README.md +3 -1
  277. package/examples/extensions/sandbox/package-lock.json +2 -2
  278. package/examples/extensions/sandbox/package.json +1 -1
  279. package/examples/extensions/subagent/README.md +13 -11
  280. package/examples/extensions/todo.ts +2 -1
  281. package/examples/extensions/with-deps/package-lock.json +2 -2
  282. package/examples/extensions/with-deps/package.json +1 -1
  283. package/examples/sdk/README.md +9 -5
  284. package/package.json +6 -7
  285. package/dist/builtin-extensions/lunr-behavior.d.ts +0 -44
  286. package/dist/builtin-extensions/lunr-behavior.d.ts.map +0 -1
  287. package/dist/builtin-extensions/lunr-behavior.js +0 -213
  288. package/dist/builtin-extensions/lunr-behavior.js.map +0 -1
  289. package/dist/builtin-extensions/pi-subagents/agents/context-builder.md +0 -46
  290. package/dist/builtin-extensions/pi-subagents/agents/deep-researcher.md +0 -36
  291. package/dist/builtin-extensions/pi-subagents/agents/delegate.md +0 -12
  292. package/dist/builtin-extensions/pi-subagents/agents/oracle.md +0 -73
  293. package/dist/builtin-extensions/pi-subagents/agents/planner.md +0 -55
  294. package/dist/builtin-extensions/pi-subagents/agents/research-writer.md +0 -37
  295. package/dist/builtin-extensions/pi-subagents/agents/researcher.md +0 -52
  296. package/dist/builtin-extensions/pi-subagents/agents/reviewer.md +0 -79
  297. package/dist/builtin-extensions/pi-subagents/agents/scout.md +0 -50
  298. package/dist/builtin-extensions/pi-subagents/agents/worker.md +0 -55
  299. package/dist/builtin-extensions/pi-subagents/src/agents/agent-management.d.ts +0 -20
  300. package/dist/builtin-extensions/pi-subagents/src/agents/agent-management.d.ts.map +0 -1
  301. package/dist/builtin-extensions/pi-subagents/src/agents/agent-management.js +0 -1273
  302. package/dist/builtin-extensions/pi-subagents/src/agents/agent-management.js.map +0 -1
  303. package/dist/builtin-extensions/pi-subagents/src/agents/agent-memory.d.ts +0 -49
  304. package/dist/builtin-extensions/pi-subagents/src/agents/agent-memory.d.ts.map +0 -1
  305. package/dist/builtin-extensions/pi-subagents/src/agents/agent-memory.js +0 -252
  306. package/dist/builtin-extensions/pi-subagents/src/agents/agent-memory.js.map +0 -1
  307. package/dist/builtin-extensions/pi-subagents/src/agents/agent-scope.d.ts +0 -3
  308. package/dist/builtin-extensions/pi-subagents/src/agents/agent-scope.d.ts.map +0 -1
  309. package/dist/builtin-extensions/pi-subagents/src/agents/agent-scope.js +0 -6
  310. package/dist/builtin-extensions/pi-subagents/src/agents/agent-scope.js.map +0 -1
  311. package/dist/builtin-extensions/pi-subagents/src/agents/agent-selection.d.ts +0 -3
  312. package/dist/builtin-extensions/pi-subagents/src/agents/agent-selection.d.ts.map +0 -1
  313. package/dist/builtin-extensions/pi-subagents/src/agents/agent-selection.js +0 -23
  314. package/dist/builtin-extensions/pi-subagents/src/agents/agent-selection.js.map +0 -1
  315. package/dist/builtin-extensions/pi-subagents/src/agents/agent-serializer.d.ts +0 -8
  316. package/dist/builtin-extensions/pi-subagents/src/agents/agent-serializer.d.ts.map +0 -1
  317. package/dist/builtin-extensions/pi-subagents/src/agents/agent-serializer.js +0 -143
  318. package/dist/builtin-extensions/pi-subagents/src/agents/agent-serializer.js.map +0 -1
  319. package/dist/builtin-extensions/pi-subagents/src/agents/agents.d.ts +0 -176
  320. package/dist/builtin-extensions/pi-subagents/src/agents/agents.d.ts.map +0 -1
  321. package/dist/builtin-extensions/pi-subagents/src/agents/agents.js +0 -1326
  322. package/dist/builtin-extensions/pi-subagents/src/agents/agents.js.map +0 -1
  323. package/dist/builtin-extensions/pi-subagents/src/agents/chain-serializer.d.ts +0 -7
  324. package/dist/builtin-extensions/pi-subagents/src/agents/chain-serializer.d.ts.map +0 -1
  325. package/dist/builtin-extensions/pi-subagents/src/agents/chain-serializer.js +0 -307
  326. package/dist/builtin-extensions/pi-subagents/src/agents/chain-serializer.js.map +0 -1
  327. package/dist/builtin-extensions/pi-subagents/src/agents/identity.d.ts +0 -8
  328. package/dist/builtin-extensions/pi-subagents/src/agents/identity.d.ts.map +0 -1
  329. package/dist/builtin-extensions/pi-subagents/src/agents/identity.js +0 -30
  330. package/dist/builtin-extensions/pi-subagents/src/agents/identity.js.map +0 -1
  331. package/dist/builtin-extensions/pi-subagents/src/agents/proactive-skills.d.ts +0 -35
  332. package/dist/builtin-extensions/pi-subagents/src/agents/proactive-skills.d.ts.map +0 -1
  333. package/dist/builtin-extensions/pi-subagents/src/agents/proactive-skills.js +0 -151
  334. package/dist/builtin-extensions/pi-subagents/src/agents/proactive-skills.js.map +0 -1
  335. package/dist/builtin-extensions/pi-subagents/src/profiles/profiles.d.ts +0 -131
  336. package/dist/builtin-extensions/pi-subagents/src/profiles/profiles.d.ts.map +0 -1
  337. package/dist/builtin-extensions/pi-subagents/src/profiles/profiles.js +0 -491
  338. package/dist/builtin-extensions/pi-subagents/src/profiles/profiles.js.map +0 -1
  339. package/dist/core/behavior-preset.d.ts +0 -60
  340. package/dist/core/behavior-preset.d.ts.map +0 -1
  341. package/dist/core/behavior-preset.js +0 -257
  342. package/dist/core/behavior-preset.js.map +0 -1
@@ -6,7 +6,7 @@
6
6
  */
7
7
  import type { Theme } from "@ashx-j/lunr";
8
8
  import type { Component, TUI } from "@ashx-j/lunr-tui";
9
- import type { AgentConfig } from "../../agents/agents.ts";
9
+ import type { ChildRuntimeConfig } from "../../shared/types.ts";
10
10
  import type { ResolvedStepBehavior } from "../../shared/settings.ts";
11
11
  import { type ModelInfo } from "../../shared/model-info.ts";
12
12
  type ClarifyMode = 'single' | 'parallel' | 'chain';
@@ -60,7 +60,7 @@ export declare class ChainClarifyComponent implements Component {
60
60
  private availableSkills;
61
61
  private done;
62
62
  private mode;
63
- constructor(tui: TUI, theme: Theme, agentConfigs: AgentConfig[], templates: string[], originalTask: string, chainDir: string | undefined, resolvedBehaviors: ResolvedStepBehavior[], availableModels: ModelInfo[], preferredProvider: string | undefined, availableSkills: Array<{
63
+ constructor(tui: TUI, theme: Theme, agentConfigs: ChildRuntimeConfig[], templates: string[], originalTask: string, chainDir: string | undefined, resolvedBehaviors: ResolvedStepBehavior[], availableModels: ModelInfo[], preferredProvider: string | undefined, availableSkills: Array<{
64
64
  name: string;
65
65
  source: string;
66
66
  description?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"chain-clarify.d.ts","sourceRoot":"","sources":["../../../../../../src/builtin-extensions/pi-subagents/src/runs/foreground/chain-clarify.ts"],"names":[],"mappings":"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","sourcesContent":["// @ts-nocheck\n/**\n * Chain Clarification TUI Component\n *\n * Shows templates and resolved behaviors for each step in a chain.\n * Supports runtime editing of templates, output paths, reads lists, and progress toggle.\n */\n\nimport type { Theme } from \"@ashx-j/lunr\";\nimport type { Component, TUI } from \"@ashx-j/lunr-tui\";\nimport { matchesKey, visibleWidth, truncateToWidth } from \"@ashx-j/lunr-tui\";\nimport type { AgentConfig } from \"../../agents/agents.ts\";\nimport type { ResolvedStepBehavior } from \"../../shared/settings.ts\";\nimport { resolveModelCandidate, splitThinkingSuffix } from \"../shared/model-fallback.ts\";\nimport { findModelInfo, getSupportedThinkingLevels, type ModelInfo, type ThinkingLevel } from \"../../shared/model-info.ts\";\n\ntype ClarifyMode = 'single' | 'parallel' | 'chain';\n\nexport interface BehaviorOverride {\n\toutput?: string | false;\n\treads?: string[] | false;\n\tprogress?: boolean;\n\tmodel?: string;\n\tskills?: string[] | false;\n}\n\nexport interface ChainClarifyResult {\n\tconfirmed: boolean;\n\ttemplates: string[];\n\tbehaviorOverrides: (BehaviorOverride | undefined)[];\n\trunInBackground?: boolean;\n}\n\ntype EditMode = \"template\" | \"output\" | \"reads\" | \"model\" | \"thinking\" | \"skills\";\n\n\ninterface TextEditorState {\n\tbuffer: string;\n\tcursor: number;\n\tviewportOffset: number;\n}\n\nfunction createEditorState(initial = \"\"): TextEditorState {\n\treturn { buffer: initial, cursor: 0, viewportOffset: 0 };\n}\n\nfunction wrapText(text: string, width: number): { lines: string[]; starts: number[] } {\n\tif (width <= 0) return { lines: [text], starts: [0] };\n\tif (text.length === 0) return { lines: [\"\"], starts: [0] };\n\n\tconst lines: string[] = [];\n\tconst starts: number[] = [];\n\tlet offset = 0;\n\tconst segments = text.split(\"\\n\");\n\tfor (const [index, segment] of segments.entries()) {\n\t\tif (segment.length === 0) {\n\t\t\tstarts.push(offset);\n\t\t\tlines.push(\"\");\n\t\t} else {\n\t\t\tlet lineStart = 0;\n\t\t\tlet pos = 0;\n\t\t\tlet lineWidth = 0;\n\t\t\twhile (pos < segment.length) {\n\t\t\t\tconst char = String.fromCodePoint(segment.codePointAt(pos)!);\n\t\t\t\tconst charWidth = visibleWidth(char);\n\t\t\t\tif (lineWidth > 0 && lineWidth + charWidth > width) {\n\t\t\t\t\tstarts.push(offset + lineStart);\n\t\t\t\t\tlines.push(segment.slice(lineStart, pos));\n\t\t\t\t\tlineStart = pos;\n\t\t\t\t\tlineWidth = 0;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tpos += char.length;\n\t\t\t\tlineWidth += charWidth;\n\t\t\t}\n\t\t\tstarts.push(offset + lineStart);\n\t\t\tlines.push(segment.slice(lineStart));\n\t\t}\n\t\toffset += segment.length + (index < segments.length - 1 ? 1 : 0);\n\t}\n\tif (!text.endsWith(\"\\n\") && text.length > 0 && visibleWidth(lines[lines.length - 1] ?? \"\") === width) {\n\t\tstarts.push(text.length);\n\t\tlines.push(\"\");\n\t}\n\treturn { lines, starts };\n}\n\nfunction getCursorDisplayPos(cursor: number, starts: number[]): { line: number; col: number } {\n\tfor (let i = starts.length - 1; i >= 0; i--) {\n\t\tif (cursor >= starts[i]!) return { line: i, col: cursor - starts[i]! };\n\t}\n\treturn { line: 0, col: 0 };\n}\n\nfunction ensureCursorVisible(cursorLine: number, viewportHeight: number, currentOffset: number): number {\n\tif (cursorLine < currentOffset) return Math.max(0, cursorLine);\n\tif (cursorLine >= currentOffset + viewportHeight) return Math.max(0, cursorLine - viewportHeight + 1);\n\treturn Math.max(0, currentOffset);\n}\n\nfunction isWordChar(ch: string): boolean {\n\tconst code = ch.charCodeAt(0);\n\treturn (code >= 48 && code <= 57) || (code >= 65 && code <= 90) || (code >= 97 && code <= 122) || code === 95;\n}\n\nfunction wordBackward(buffer: string, cursor: number): number {\n\tlet pos = cursor;\n\twhile (pos > 0 && !isWordChar(buffer[pos - 1]!)) pos--;\n\twhile (pos > 0 && isWordChar(buffer[pos - 1]!)) pos--;\n\treturn pos;\n}\n\nfunction wordForward(buffer: string, cursor: number): number {\n\tlet pos = cursor;\n\twhile (pos < buffer.length && isWordChar(buffer[pos]!)) pos++;\n\twhile (pos < buffer.length && !isWordChar(buffer[pos]!)) pos++;\n\treturn pos;\n}\n\nfunction normalizeInsertText(data: string): string | null {\n\tlet text = data.split(\"\\x1b[200~\").join(\"\").split(\"\\x1b[201~\").join(\"\");\n\ttext = text.replace(/\\r\\n/g, \"\\n\").replace(/\\r/g, \"\\n\");\n\tconst newline = text.indexOf(\"\\n\");\n\tif (newline !== -1) text = text.slice(0, newline);\n\ttext = text.replace(/\\t/g, \" \");\n\tif (text.length === 0) return null;\n\tfor (let i = 0; i < text.length; i++) {\n\t\tif (text.charCodeAt(i) < 32) return null;\n\t}\n\treturn text;\n}\n\nfunction handleEditorInput(state: TextEditorState, data: string, textWidth: number): TextEditorState | null {\n\tif (matchesKey(data, \"escape\") || matchesKey(data, \"ctrl+c\") || matchesKey(data, \"return\")) return null;\n\n\tconst { lines: wrapped, starts } = wrapText(state.buffer, textWidth);\n\tconst cursorPos = getCursorDisplayPos(state.cursor, starts);\n\n\tif (matchesKey(data, \"alt+left\") || matchesKey(data, \"ctrl+left\")) return { ...state, cursor: wordBackward(state.buffer, state.cursor) };\n\tif (matchesKey(data, \"alt+right\") || matchesKey(data, \"ctrl+right\")) return { ...state, cursor: wordForward(state.buffer, state.cursor) };\n\tif (matchesKey(data, \"left\")) return state.cursor > 0 ? { ...state, cursor: state.cursor - 1 } : state;\n\tif (matchesKey(data, \"right\")) return state.cursor < state.buffer.length ? { ...state, cursor: state.cursor + 1 } : state;\n\tif (matchesKey(data, \"up\") && cursorPos.line > 0) {\n\t\tconst targetLine = cursorPos.line - 1;\n\t\treturn { ...state, cursor: starts[targetLine]! + Math.min(cursorPos.col, wrapped[targetLine]?.length ?? 0) };\n\t}\n\tif (matchesKey(data, \"down\") && cursorPos.line < wrapped.length - 1) {\n\t\tconst targetLine = cursorPos.line + 1;\n\t\treturn { ...state, cursor: starts[targetLine]! + Math.min(cursorPos.col, wrapped[targetLine]?.length ?? 0) };\n\t}\n\tif (matchesKey(data, \"home\")) return { ...state, cursor: starts[cursorPos.line]! };\n\tif (matchesKey(data, \"end\")) return { ...state, cursor: starts[cursorPos.line]! + (wrapped[cursorPos.line]?.length ?? 0) };\n\tif (matchesKey(data, \"ctrl+home\")) return { ...state, cursor: 0 };\n\tif (matchesKey(data, \"ctrl+end\")) return { ...state, cursor: state.buffer.length };\n\tif (matchesKey(data, \"alt+backspace\")) {\n\t\tconst target = wordBackward(state.buffer, state.cursor);\n\t\treturn target === state.cursor ? state : { ...state, buffer: state.buffer.slice(0, target) + state.buffer.slice(state.cursor), cursor: target };\n\t}\n\tif (matchesKey(data, \"backspace\")) {\n\t\treturn state.cursor > 0\n\t\t\t? { ...state, buffer: state.buffer.slice(0, state.cursor - 1) + state.buffer.slice(state.cursor), cursor: state.cursor - 1 }\n\t\t\t: state;\n\t}\n\tif (matchesKey(data, \"delete\")) {\n\t\treturn state.cursor < state.buffer.length\n\t\t\t? { ...state, buffer: state.buffer.slice(0, state.cursor) + state.buffer.slice(state.cursor + 1) }\n\t\t\t: state;\n\t}\n\n\tconst insert = normalizeInsertText(data);\n\treturn insert\n\t\t? { ...state, buffer: state.buffer.slice(0, state.cursor) + insert + state.buffer.slice(state.cursor), cursor: state.cursor + insert.length }\n\t\t: null;\n}\n\nfunction renderWithCursor(text: string, cursorPos: number): string {\n\tconst before = text.slice(0, cursorPos);\n\tconst cursorChar = text[cursorPos] ?? \" \";\n\tconst after = text.slice(cursorPos + 1);\n\treturn `${before}\\x1b[7m${cursorChar}\\x1b[27m${after}`;\n}\n\nfunction renderEditor(state: TextEditorState, width: number, viewportHeight: number): string[] {\n\tconst { lines: wrapped, starts } = wrapText(state.buffer, width);\n\tconst cursorPos = getCursorDisplayPos(state.cursor, starts);\n\tconst lines: string[] = [];\n\tfor (let i = 0; i < viewportHeight; i++) {\n\t\tconst lineIdx = state.viewportOffset + i;\n\t\tlet content = lineIdx < wrapped.length ? wrapped[lineIdx] ?? \"\" : \"\";\n\t\tif (lineIdx === cursorPos.line) content = renderWithCursor(content, cursorPos.col);\n\t\tlines.push(content);\n\t}\n\treturn lines;\n}\n\n/**\n * TUI component for chain clarification.\n * Factory signature matches ctx.ui.custom: (tui, theme, kb, done) => Component\n */\nexport class ChainClarifyComponent implements Component {\n\treadonly width = 84;\n\n\tprivate selectedStep = 0;\n\tprivate editingStep: number | null = null;\n\tprivate editMode: EditMode = \"template\";\n\tprivate editState: TextEditorState = createEditorState();\n\n\tprivate readonly EDIT_VIEWPORT_HEIGHT = 12;\n\tprivate behaviorOverrides: Map<number, BehaviorOverride> = new Map();\n\tprivate modelSearchQuery: string = \"\";\n\tprivate modelSelectedIndex: number = 0;\n\tprivate filteredModels: ModelInfo[] = [];\n\tprivate readonly MODEL_SELECTOR_HEIGHT = 10;\n\tprivate thinkingSelectedIndex: number = 0;\n\tprivate skillSearchQuery: string = \"\";\n\tprivate skillSelectedNames: Set<string> = new Set();\n\tprivate skillCursorIndex: number = 0;\n\tprivate filteredSkills: Array<{ name: string; source: string; description?: string }> = [];\n\tprivate noticeMessage: { text: string; type: \"info\" | \"error\" } | null = null;\n\tprivate noticeMessageTimer: ReturnType<typeof setTimeout> | null = null;\n\t/** Run in background (async) mode */\n\tprivate runInBackground = false;\n\tprivate tui: TUI;\n\tprivate theme: Theme;\n\tprivate agentConfigs: AgentConfig[];\n\tprivate templates: string[];\n\tprivate originalTask: string;\n\tprivate chainDir: string | undefined;\n\tprivate resolvedBehaviors: ResolvedStepBehavior[];\n\tprivate availableModels: ModelInfo[];\n\tprivate preferredProvider: string | undefined;\n\tprivate availableSkills: Array<{ name: string; source: string; description?: string }>;\n\tprivate done: (result: ChainClarifyResult) => void;\n\tprivate mode: ClarifyMode;\n\n\tconstructor(\n\t\ttui: TUI,\n\t\ttheme: Theme,\n\t\tagentConfigs: AgentConfig[],\n\t\ttemplates: string[],\n\t\toriginalTask: string,\n\t\tchainDir: string | undefined,\n\t\tresolvedBehaviors: ResolvedStepBehavior[],\n\t\tavailableModels: ModelInfo[],\n\t\tpreferredProvider: string | undefined,\n\t\tavailableSkills: Array<{ name: string; source: string; description?: string }>,\n\t\tdone: (result: ChainClarifyResult) => void,\n\t\tmode: ClarifyMode = 'chain',\n\t) {\n\t\tthis.tui = tui;\n\t\tthis.theme = theme;\n\t\tthis.agentConfigs = agentConfigs;\n\t\tthis.templates = templates;\n\t\tthis.originalTask = originalTask;\n\t\tthis.chainDir = chainDir;\n\t\tthis.resolvedBehaviors = resolvedBehaviors;\n\t\tthis.availableModels = availableModels;\n\t\tthis.preferredProvider = preferredProvider;\n\t\tthis.availableSkills = availableSkills;\n\t\tthis.done = done;\n\t\tthis.mode = mode;\n\t\tthis.filteredModels = [...availableModels];\n\t\tthis.filteredSkills = [...availableSkills];\n\t}\n\n\t// ─────────────────────────────────────────────────────────────────────────────\n\t// Helper methods for rendering\n\t// ─────────────────────────────────────────────────────────────────────────────\n\n\t/** Pad string to specified visible width */\n\tprivate pad(s: string, len: number): string {\n\t\tconst vis = visibleWidth(s);\n\t\treturn s + \" \".repeat(Math.max(0, len - vis));\n\t}\n\n\t/** Create a row with border characters */\n\tprivate row(content: string): string {\n\t\tconst innerW = this.width - 2;\n\t\treturn this.theme.fg(\"border\", \"│\") + this.pad(content, innerW) + this.theme.fg(\"border\", \"│\");\n\t}\n\n\t/** Render centered header line with border */\n\tprivate renderHeader(text: string): string {\n\t\tconst innerW = this.width - 2;\n\t\tconst padLen = Math.max(0, innerW - visibleWidth(text));\n\t\tconst padLeft = Math.floor(padLen / 2);\n\t\tconst padRight = padLen - padLeft;\n\t\treturn (\n\t\t\tthis.theme.fg(\"border\", \"╭\" + \"─\".repeat(padLeft)) +\n\t\t\tthis.theme.fg(\"accent\", text) +\n\t\t\tthis.theme.fg(\"border\", \"─\".repeat(padRight) + \"╮\")\n\t\t);\n\t}\n\n\t/** Render centered footer line with border */\n\tprivate renderFooter(text: string): string {\n\t\tconst innerW = this.width - 2;\n\t\tconst padLen = Math.max(0, innerW - visibleWidth(text));\n\t\tconst padLeft = Math.floor(padLen / 2);\n\t\tconst padRight = padLen - padLeft;\n\t\treturn (\n\t\t\tthis.theme.fg(\"border\", \"╰\" + \"─\".repeat(padLeft)) +\n\t\t\tthis.theme.fg(\"dim\", text) +\n\t\t\tthis.theme.fg(\"border\", \"─\".repeat(padRight) + \"╯\")\n\t\t);\n\t}\n\n\t/** Exit edit mode and reset state */\n\tprivate exitEditMode(): void {\n\t\tthis.editingStep = null;\n\t\tthis.editState = createEditorState();\n\t\tthis.tui.requestRender();\n\t}\n\n\t// ─────────────────────────────────────────────────────────────────────────────\n\t// Full edit mode methods\n\t// ─────────────────────────────────────────────────────────────────────────────\n\n\t/** Render the full-edit takeover view */\n\tprivate renderFullEditMode(): string[] {\n\t\tconst innerW = this.width - 2;\n\t\tconst textWidth = innerW - 2; // 1 char padding on each side\n\t\tconst lines: string[] = [];\n\n\t\tconst { lines: wrapped, starts } = wrapText(this.editState.buffer, textWidth);\n\t\tconst cursorPos = getCursorDisplayPos(this.editState.cursor, starts);\n\t\tthis.editState = {\n\t\t\t...this.editState,\n\t\t\tviewportOffset: ensureCursorVisible(\n\t\t\t\tcursorPos.line,\n\t\t\t\tthis.EDIT_VIEWPORT_HEIGHT,\n\t\t\t\tthis.editState.viewportOffset,\n\t\t\t),\n\t\t};\n\n\t\t// Header (truncate agent name to prevent overflow)\n\t\tconst fieldName = this.editMode === \"template\" ? \"task\" : this.editMode;\n\t\tconst rawAgentName = this.agentConfigs[this.editingStep!]?.name ?? \"unknown\";\n\t\tconst maxAgentLen = innerW - 30; // Reserve space for \" Editing X (Step/Task N: ) \"\n\t\tconst agentName = rawAgentName.length > maxAgentLen\n\t\t\t? rawAgentName.slice(0, maxAgentLen - 1) + \"…\"\n\t\t\t: rawAgentName;\n\t\t// Use mode-appropriate terminology\n\t\tconst stepLabel = this.mode === 'single' \n\t\t\t? agentName \n\t\t\t: this.mode === 'parallel' \n\t\t\t\t? `Task ${this.editingStep! + 1}: ${agentName}` \n\t\t\t\t: `Step ${this.editingStep! + 1}: ${agentName}`;\n\t\tconst headerText = ` Editing ${fieldName} (${stepLabel}) `;\n\t\tlines.push(this.renderHeader(headerText));\n\t\tlines.push(this.row(\"\"));\n\n\t\tconst editorLines = renderEditor(this.editState, textWidth, this.EDIT_VIEWPORT_HEIGHT);\n\t\tfor (const line of editorLines) {\n\t\t\tlines.push(this.row(` ${line}`));\n\t\t}\n\n\t\tconst linesBelow = wrapped.length - this.editState.viewportOffset - this.EDIT_VIEWPORT_HEIGHT;\n\t\tconst hasMore = linesBelow > 0;\n\t\tconst hasLess = this.editState.viewportOffset > 0;\n\t\tlet scrollInfo = \"\";\n\t\tif (hasLess) scrollInfo += \"↑\";\n\t\tif (hasMore) scrollInfo += `↓ ${linesBelow}+`;\n\n\t\tlines.push(this.row(\"\"));\n\n\t\tconst footerText = scrollInfo\n\t\t\t? ` [Esc] Done • [Ctrl+C] Discard • ${scrollInfo} `\n\t\t\t: \" [Esc] Done • [Ctrl+C] Discard \";\n\t\tlines.push(this.renderFooter(footerText));\n\n\t\treturn lines;\n\t}\n\n\t// ─────────────────────────────────────────────────────────────────────────────\n\t// Behavior helpers\n\t// ─────────────────────────────────────────────────────────────────────────────\n\n\t/** Get effective behavior for a step (with user overrides applied) */\n\tprivate getEffectiveBehavior(stepIndex: number): ResolvedStepBehavior {\n\t\tconst base = this.resolvedBehaviors[stepIndex]!;\n\t\tconst override = this.behaviorOverrides.get(stepIndex);\n\t\tif (!override) return base;\n\n\t\treturn {\n\t\t\toutput: override.output !== undefined ? override.output : base.output,\n\t\t\toutputMode: base.outputMode,\n\t\t\treads: override.reads !== undefined ? override.reads : base.reads,\n\t\t\tprogress: override.progress !== undefined ? override.progress : base.progress,\n\t\t\tskills: override.skills !== undefined ? override.skills : base.skills,\n\t\t\tmodel: override.model !== undefined ? override.model : base.model,\n\t\t};\n\t}\n\n\t/** Get the effective model for a step (override or agent default) */\n\tprivate getEffectiveModel(stepIndex: number): string {\n\t\tconst override = this.behaviorOverrides.get(stepIndex);\n\t\tif (override?.model) return this.resolveModelFullId(override.model);\n\n\t\tconst baseModel = this.resolvedBehaviors[stepIndex]?.model;\n\t\tif (baseModel) return this.resolveModelFullId(baseModel);\n\t\treturn \"default\";\n\t}\n\n\t/** Resolve a model name to its full provider/model format */\n\tprivate resolveModelFullId(modelName: string): string {\n\t\treturn resolveModelCandidate(modelName, this.availableModels, this.preferredProvider) ?? modelName;\n\t}\n\n\t/** Update a behavior override for a step */\n\tprivate updateBehavior(stepIndex: number, field: keyof BehaviorOverride, value: string | boolean | string[] | false): void {\n\t\tconst existing = this.behaviorOverrides.get(stepIndex) ?? {};\n\t\tthis.behaviorOverrides.set(stepIndex, { ...existing, [field]: value });\n\t}\n\n\tprivate showNotice(text: string, type: \"info\" | \"error\"): void {\n\t\tthis.noticeMessage = { text, type };\n\t\tif (this.noticeMessageTimer) clearTimeout(this.noticeMessageTimer);\n\t\tthis.noticeMessageTimer = setTimeout(() => {\n\t\t\tthis.noticeMessage = null;\n\t\t\tthis.noticeMessageTimer = null;\n\t\t\tthis.tui.requestRender();\n\t\t}, 2000);\n\t\tthis.tui.requestRender();\n\t}\n\n\thandleInput(data: string): void {\n\t\tif (this.editingStep !== null) {\n\t\t\tif (this.editMode === \"model\") {\n\t\t\t\tthis.handleModelSelectorInput(data);\n\t\t\t} else if (this.editMode === \"thinking\") {\n\t\t\t\tthis.handleThinkingSelectorInput(data);\n\t\t\t} else if (this.editMode === \"skills\") {\n\t\t\t\tthis.handleSkillSelectorInput(data);\n\t\t\t} else {\n\t\t\t\tthis.handleEditInput(data);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tif (matchesKey(data, \"escape\") || matchesKey(data, \"ctrl+c\")) {\n\t\t\tthis.done({ confirmed: false, templates: [], behaviorOverrides: [] });\n\t\t\treturn;\n\t\t}\n\n\t\tif (matchesKey(data, \"return\")) {\n\t\t\tconst overrides: (BehaviorOverride | undefined)[] = [];\n\t\t\tfor (let i = 0; i < this.agentConfigs.length; i++) {\n\t\t\t\toverrides.push(this.behaviorOverrides.get(i));\n\t\t\t}\n\t\t\tthis.done({ confirmed: true, templates: this.templates, behaviorOverrides: overrides, runInBackground: this.runInBackground });\n\t\t\treturn;\n\t\t}\n\n\t\tif (matchesKey(data, \"up\")) {\n\t\t\tthis.selectedStep = Math.max(0, this.selectedStep - 1);\n\t\t\tthis.tui.requestRender();\n\t\t\treturn;\n\t\t}\n\n\t\tif (matchesKey(data, \"down\")) {\n\t\t\tconst maxStep = Math.max(0, this.agentConfigs.length - 1);\n\t\t\tthis.selectedStep = Math.min(maxStep, this.selectedStep + 1);\n\t\t\tthis.tui.requestRender();\n\t\t\treturn;\n\t\t}\n\n\t\tif (data === \"e\") {\n\t\t\tthis.enterEditMode(\"template\");\n\t\t\treturn;\n\t\t}\n\n\t\tif (data === \"m\") {\n\t\t\tthis.enterModelSelector();\n\t\t\treturn;\n\t\t}\n\n\t\tif (data === \"t\") {\n\t\t\tthis.enterThinkingSelector();\n\t\t\treturn;\n\t\t}\n\n\t\tif (data === \"s\") {\n\t\t\tthis.editingStep = this.selectedStep;\n\t\t\tthis.editMode = \"skills\";\n\t\t\tthis.skillSearchQuery = \"\";\n\t\t\tthis.skillCursorIndex = 0;\n\t\t\tthis.filteredSkills = [...this.availableSkills];\n\t\t\tconst current = this.getEffectiveBehavior(this.selectedStep).skills;\n\t\t\tthis.skillSelectedNames.clear();\n\t\t\tif (current !== false && current.length > 0) {\n\t\t\t\tcurrent.forEach((skillName) => this.skillSelectedNames.add(skillName));\n\t\t\t}\n\t\t\tthis.tui.requestRender();\n\t\t\treturn;\n\t\t}\n\n\t\tif (data === \"w\" && this.mode !== 'parallel') {\n\t\t\tthis.enterEditMode(\"output\");\n\t\t\treturn;\n\t\t}\n\n\t\tif (data === \"r\" && this.mode === 'chain') {\n\t\t\tthis.enterEditMode(\"reads\");\n\t\t\treturn;\n\t\t}\n\n\t\tif (data === \"p\" && this.mode === 'chain') {\n\t\t\tconst anyEnabled = this.agentConfigs.some((_, i) => this.getEffectiveBehavior(i).progress);\n\t\t\tconst newState = !anyEnabled;\n\t\t\tfor (let i = 0; i < this.agentConfigs.length; i++) {\n\t\t\t\tthis.updateBehavior(i, \"progress\", newState);\n\t\t\t}\n\t\t\tthis.tui.requestRender();\n\t\t\treturn;\n\t\t}\n\n\t\tif (data === \"b\") {\n\t\t\tthis.runInBackground = !this.runInBackground;\n\t\t\tthis.tui.requestRender();\n\t\t\treturn;\n\t\t}\n\n\t}\n\n\tprivate enterEditMode(mode: EditMode): void {\n\t\tthis.editingStep = this.selectedStep;\n\t\tthis.editMode = mode;\n\t\tlet buffer = \"\";\n\n\t\tif (mode === \"template\") {\n\t\t\tconst template = this.templates[this.selectedStep] ?? \"\";\n\t\t\tbuffer = template.split(\"\\n\")[0] ?? \"\";\n\t\t} else if (mode === \"output\") {\n\t\t\tconst behavior = this.getEffectiveBehavior(this.selectedStep);\n\t\t\tbuffer = behavior.output === false ? \"\" : (behavior.output || \"\");\n\t\t} else if (mode === \"reads\") {\n\t\t\tconst behavior = this.getEffectiveBehavior(this.selectedStep);\n\t\t\tbuffer = behavior.reads === false ? \"\" : (behavior.reads?.join(\", \") || \"\");\n\t\t}\n\n\t\tthis.editState = createEditorState(buffer);\n\t\tthis.tui.requestRender();\n\t}\n\n\t/** Enter model selector mode */\n\tprivate enterModelSelector(): void {\n\t\tthis.editingStep = this.selectedStep;\n\t\tthis.editMode = \"model\";\n\t\tthis.modelSearchQuery = \"\";\n\t\tthis.modelSelectedIndex = 0;\n\t\tthis.filteredModels = [...this.availableModels];\n\t\tconst currentModel = splitThinkingSuffix(this.getEffectiveModel(this.selectedStep)).baseModel;\n\t\tconst currentIndex = this.filteredModels.findIndex((m) => m.fullId === currentModel || m.id === currentModel);\n\t\tif (currentIndex >= 0) {\n\t\t\tthis.modelSelectedIndex = currentIndex;\n\t\t}\n\n\t\tthis.tui.requestRender();\n\t}\n\n\t/** Filter models based on search query */\n\tprivate filterModels(): void {\n\t\tconst query = this.modelSearchQuery.toLowerCase();\n\t\tif (!query) {\n\t\t\tthis.filteredModels = [...this.availableModels];\n\t\t} else {\n\t\t\tthis.filteredModels = this.availableModels.filter((m) =>\n\t\t\t\tm.fullId.toLowerCase().includes(query) ||\n\t\t\t\tm.id.toLowerCase().includes(query) ||\n\t\t\t\tm.provider.toLowerCase().includes(query)\n\t\t\t);\n\t\t}\n\t\tthis.modelSelectedIndex = Math.min(this.modelSelectedIndex, Math.max(0, this.filteredModels.length - 1));\n\t}\n\n\tprivate handleModelSelectorInput(data: string): void {\n\t\tif (matchesKey(data, \"escape\") || matchesKey(data, \"ctrl+c\")) {\n\t\t\tthis.exitEditMode();\n\t\t\treturn;\n\t\t}\n\n\t\tif (matchesKey(data, \"return\")) {\n\t\t\tconst selected = this.filteredModels[this.modelSelectedIndex];\n\t\t\tif (selected) {\n\t\t\t\tconst { thinkingSuffix } = splitThinkingSuffix(this.getEffectiveModel(this.editingStep!));\n\t\t\t\tconst requestedLevel = thinkingSuffix.slice(1);\n\t\t\t\tconst selectedModel = findModelInfo(selected.fullId, this.availableModels, this.preferredProvider);\n\t\t\t\tconst suffix = getSupportedThinkingLevels(selectedModel).some((level) => level === requestedLevel) ? thinkingSuffix : \"\";\n\t\t\t\tthis.updateBehavior(this.editingStep!, \"model\", `${selected.fullId}${suffix}`);\n\t\t\t}\n\t\t\tthis.exitEditMode();\n\t\t\treturn;\n\t\t}\n\n\t\tif (matchesKey(data, \"up\")) {\n\t\t\tif (this.filteredModels.length > 0) {\n\t\t\t\tthis.modelSelectedIndex = this.modelSelectedIndex === 0\n\t\t\t\t\t? this.filteredModels.length - 1\n\t\t\t\t\t: this.modelSelectedIndex - 1;\n\t\t\t}\n\t\t\tthis.tui.requestRender();\n\t\t\treturn;\n\t\t}\n\n\t\tif (matchesKey(data, \"down\")) {\n\t\t\tif (this.filteredModels.length > 0) {\n\t\t\t\tthis.modelSelectedIndex = this.modelSelectedIndex === this.filteredModels.length - 1\n\t\t\t\t\t? 0\n\t\t\t\t\t: this.modelSelectedIndex + 1;\n\t\t\t}\n\t\t\tthis.tui.requestRender();\n\t\t\treturn;\n\t\t}\n\n\t\tif (matchesKey(data, \"backspace\")) {\n\t\t\tif (this.modelSearchQuery.length > 0) {\n\t\t\t\tthis.modelSearchQuery = this.modelSearchQuery.slice(0, -1);\n\t\t\t\tthis.filterModels();\n\t\t\t}\n\t\t\tthis.tui.requestRender();\n\t\t\treturn;\n\t\t}\n\n\t\tif (data.length === 1 && data.charCodeAt(0) >= 32) {\n\t\t\tthis.modelSearchQuery += data;\n\t\t\tthis.filterModels();\n\t\t\tthis.tui.requestRender();\n\t\t\treturn;\n\t\t}\n\t}\n\n\tprivate getAvailableThinkingLevels(stepIndex: number): ThinkingLevel[] {\n\t\treturn getSupportedThinkingLevels(findModelInfo(this.getEffectiveModel(stepIndex), this.availableModels, this.preferredProvider));\n\t}\n\n\t/** Enter thinking level selector mode */\n\tprivate enterThinkingSelector(): void {\n\t\tif (!this.getEffectiveBehavior(this.selectedStep).model) {\n\t\t\tthis.showNotice(\"Select a model first\", \"error\");\n\t\t\treturn;\n\t\t}\n\t\tthis.editingStep = this.selectedStep;\n\t\tthis.editMode = \"thinking\";\n\n\t\tconst levels = this.getAvailableThinkingLevels(this.selectedStep);\n\t\tconst { thinkingSuffix } = splitThinkingSuffix(this.getEffectiveModel(this.selectedStep));\n\t\tconst suffix = thinkingSuffix.slice(1);\n\t\tconst levelIdx = levels.findIndex((level) => level === suffix);\n\t\tthis.thinkingSelectedIndex = levelIdx >= 0 ? levelIdx : Math.max(0, levels.indexOf(\"off\"));\n\n\t\tthis.tui.requestRender();\n\t}\n\n\tprivate handleThinkingSelectorInput(data: string): void {\n\t\tif (matchesKey(data, \"escape\") || matchesKey(data, \"ctrl+c\")) {\n\t\t\tthis.exitEditMode();\n\t\t\treturn;\n\t\t}\n\n\t\tconst levels = this.getAvailableThinkingLevels(this.editingStep!);\n\t\tif (levels.length === 0) return;\n\n\t\tif (matchesKey(data, \"return\")) {\n\t\t\tconst selectedLevel = levels[this.thinkingSelectedIndex] ?? \"off\";\n\t\t\tthis.applyThinkingLevel(selectedLevel);\n\t\t\tthis.exitEditMode();\n\t\t\treturn;\n\t\t}\n\n\t\tif (matchesKey(data, \"up\")) {\n\t\t\tthis.thinkingSelectedIndex = this.thinkingSelectedIndex === 0\n\t\t\t\t? levels.length - 1\n\t\t\t\t: this.thinkingSelectedIndex - 1;\n\t\t\tthis.tui.requestRender();\n\t\t\treturn;\n\t\t}\n\n\t\tif (matchesKey(data, \"down\")) {\n\t\t\tthis.thinkingSelectedIndex = this.thinkingSelectedIndex === levels.length - 1\n\t\t\t\t? 0\n\t\t\t\t: this.thinkingSelectedIndex + 1;\n\t\t\tthis.tui.requestRender();\n\t\t\treturn;\n\t\t}\n\t}\n\n\t/** Apply thinking level to the current step's model */\n\tprivate applyThinkingLevel(level: ThinkingLevel): void {\n\t\tconst stepIndex = this.editingStep!;\n\t\tconst currentModel = this.getEffectiveBehavior(stepIndex).model;\n\t\tif (!currentModel) return;\n\n\t\tconst { baseModel } = splitThinkingSuffix(currentModel);\n\t\tconst newModel = level === \"off\" ? baseModel : `${baseModel}:${level}`;\n\t\tthis.updateBehavior(stepIndex, \"model\", newModel);\n\t}\n\n\tprivate filterSkills(): void {\n\t\tconst query = this.skillSearchQuery.toLowerCase();\n\t\tif (!query) {\n\t\t\tthis.filteredSkills = [...this.availableSkills];\n\t\t} else {\n\t\t\tthis.filteredSkills = this.availableSkills.filter((s) =>\n\t\t\t\ts.name.toLowerCase().includes(query) ||\n\t\t\t\t(s.description?.toLowerCase().includes(query) ?? false),\n\t\t\t);\n\t\t}\n\t\tthis.skillCursorIndex = Math.min(this.skillCursorIndex, Math.max(0, this.filteredSkills.length - 1));\n\t}\n\n\tprivate handleSkillSelectorInput(data: string): void {\n\t\tif (matchesKey(data, \"escape\") || matchesKey(data, \"ctrl+c\")) {\n\t\t\tthis.exitEditMode();\n\t\t\treturn;\n\t\t}\n\n\t\tif (matchesKey(data, \"return\")) {\n\t\t\tconst selected = [...this.skillSelectedNames];\n\t\t\tthis.updateBehavior(this.editingStep!, \"skills\", selected);\n\t\t\tthis.exitEditMode();\n\t\t\treturn;\n\t\t}\n\n\t\tif (data === \" \") {\n\t\t\tif (this.filteredSkills.length > 0) {\n\t\t\t\tconst skill = this.filteredSkills[this.skillCursorIndex];\n\t\t\t\tif (skill) {\n\t\t\t\t\tif (this.skillSelectedNames.has(skill.name)) {\n\t\t\t\t\t\tthis.skillSelectedNames.delete(skill.name);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthis.skillSelectedNames.add(skill.name);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tthis.tui.requestRender();\n\t\t\treturn;\n\t\t}\n\n\t\tif (matchesKey(data, \"up\")) {\n\t\t\tif (this.filteredSkills.length > 0) {\n\t\t\t\tthis.skillCursorIndex = this.skillCursorIndex === 0\n\t\t\t\t\t? this.filteredSkills.length - 1\n\t\t\t\t\t: this.skillCursorIndex - 1;\n\t\t\t}\n\t\t\tthis.tui.requestRender();\n\t\t\treturn;\n\t\t}\n\n\t\tif (matchesKey(data, \"down\")) {\n\t\t\tif (this.filteredSkills.length > 0) {\n\t\t\t\tthis.skillCursorIndex = this.skillCursorIndex === this.filteredSkills.length - 1\n\t\t\t\t\t? 0\n\t\t\t\t\t: this.skillCursorIndex + 1;\n\t\t\t}\n\t\t\tthis.tui.requestRender();\n\t\t\treturn;\n\t\t}\n\n\t\tif (matchesKey(data, \"backspace\")) {\n\t\t\tif (this.skillSearchQuery.length > 0) {\n\t\t\t\tthis.skillSearchQuery = this.skillSearchQuery.slice(0, -1);\n\t\t\t\tthis.filterSkills();\n\t\t\t}\n\t\t\tthis.tui.requestRender();\n\t\t\treturn;\n\t\t}\n\n\t\tif (data.length === 1 && data.charCodeAt(0) >= 32) {\n\t\t\tthis.skillSearchQuery += data;\n\t\t\tthis.filterSkills();\n\t\t\tthis.tui.requestRender();\n\t\t\treturn;\n\t\t}\n\t}\n\n\tprivate handleEditInput(data: string): void {\n\t\tconst textWidth = this.width - 4; // Must match render: innerW - 2 = (width - 2) - 2\n\t\tif (matchesKey(data, \"shift+up\") || matchesKey(data, \"pageup\")) {\n\t\t\tconst { lines: wrapped, starts } = wrapText(this.editState.buffer, textWidth);\n\t\t\tconst cursorPos = getCursorDisplayPos(this.editState.cursor, starts);\n\t\t\tconst targetLine = Math.max(0, cursorPos.line - this.EDIT_VIEWPORT_HEIGHT);\n\t\t\tconst targetCol = Math.min(cursorPos.col, wrapped[targetLine]?.length ?? 0);\n\t\t\tthis.editState = { ...this.editState, cursor: starts[targetLine] + targetCol };\n\t\t\tthis.tui.requestRender();\n\t\t\treturn;\n\t\t}\n\n\t\tif (matchesKey(data, \"shift+down\") || matchesKey(data, \"pagedown\")) {\n\t\t\tconst { lines: wrapped, starts } = wrapText(this.editState.buffer, textWidth);\n\t\t\tconst cursorPos = getCursorDisplayPos(this.editState.cursor, starts);\n\t\t\tconst targetLine = Math.min(wrapped.length - 1, cursorPos.line + this.EDIT_VIEWPORT_HEIGHT);\n\t\t\tconst targetCol = Math.min(cursorPos.col, wrapped[targetLine]?.length ?? 0);\n\t\t\tthis.editState = { ...this.editState, cursor: starts[targetLine] + targetCol };\n\t\t\tthis.tui.requestRender();\n\t\t\treturn;\n\t\t}\n\n\t\tif (matchesKey(data, \"tab\")) return;\n\n\t\tconst nextState = handleEditorInput(this.editState, data, textWidth);\n\t\tif (nextState) {\n\t\t\tthis.editState = nextState;\n\t\t\tthis.tui.requestRender();\n\t\t\treturn;\n\t\t}\n\n\t\tif (matchesKey(data, \"escape\")) {\n\t\t\tthis.saveEdit();\n\t\t\tthis.exitEditMode();\n\t\t\treturn;\n\t\t}\n\n\t\tif (matchesKey(data, \"ctrl+c\")) {\n\t\t\tthis.exitEditMode();\n\t\t\treturn;\n\t\t}\n\t}\n\n\tprivate saveEdit(): void {\n\t\tconst stepIndex = this.editingStep!;\n\n\t\tif (this.editMode === \"template\") {\n\t\t\t// For template, preserve other lines if they existed\n\t\t\tconst original = this.templates[stepIndex] ?? \"\";\n\t\t\tconst originalLines = original.split(\"\\n\");\n\t\t\toriginalLines[0] = this.editState.buffer;\n\t\t\tthis.templates[stepIndex] = originalLines.join(\"\\n\");\n\t\t} else if (this.editMode === \"output\") {\n\t\t\t// Capture OLD output before updating (for downstream propagation)\n\t\t\tconst oldBehavior = this.getEffectiveBehavior(stepIndex);\n\t\t\tconst oldOutput = typeof oldBehavior.output === \"string\" ? oldBehavior.output : null;\n\n\t\t\t// Empty string or whitespace means disable output\n\t\t\tconst trimmed = this.editState.buffer.trim();\n\t\t\tconst newOutput = trimmed === \"\" ? false : trimmed;\n\t\t\tthis.updateBehavior(stepIndex, \"output\", newOutput);\n\n\t\t\t// Propagate output filename change to downstream steps' reads\n\t\t\tif (oldOutput && typeof newOutput === \"string\" && oldOutput !== newOutput) {\n\t\t\t\tthis.propagateOutputChange(stepIndex, oldOutput, newOutput);\n\t\t\t}\n\t\t} else if (this.editMode === \"reads\") {\n\t\t\t// Parse comma-separated list, empty means disable reads\n\t\t\tconst trimmed = this.editState.buffer.trim();\n\t\t\tif (trimmed === \"\") {\n\t\t\t\tthis.updateBehavior(stepIndex, \"reads\", false);\n\t\t\t} else {\n\t\t\t\tconst files = trimmed.split(\",\").map(f => f.trim()).filter(f => f !== \"\");\n\t\t\t\tthis.updateBehavior(stepIndex, \"reads\", files.length > 0 ? files : false);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * When a step's output filename changes, update downstream steps that read from it.\n\t * This maintains the chain dependency automatically.\n\t */\n\tprivate propagateOutputChange(changedStepIndex: number, oldOutput: string, newOutput: string): void {\n\t\t// Check all downstream steps (steps that come after the changed step)\n\t\tfor (let i = changedStepIndex + 1; i < this.agentConfigs.length; i++) {\n\t\t\tconst behavior = this.getEffectiveBehavior(i);\n\t\t\t\n\t\t\t// Skip if reads is disabled or empty\n\t\t\tif (behavior.reads === false || !behavior.reads || behavior.reads.length === 0) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Check if this step reads the old output file\n\t\t\tconst readsArray = behavior.reads;\n\t\t\tconst oldIndex = readsArray.indexOf(oldOutput);\n\t\t\t\n\t\t\tif (oldIndex !== -1) {\n\t\t\t\t// Replace old filename with new filename in reads\n\t\t\t\tconst newReads = [...readsArray];\n\t\t\t\tnewReads[oldIndex] = newOutput;\n\t\t\t\tthis.updateBehavior(i, \"reads\", newReads);\n\t\t\t}\n\t\t}\n\t}\n\n\trender(_width: number): string[] {\n\t\tif (this.editingStep !== null) {\n\t\t\tif (this.editMode === \"model\") {\n\t\t\t\treturn this.renderModelSelector();\n\t\t\t}\n\t\t\tif (this.editMode === \"thinking\") {\n\t\t\t\treturn this.renderThinkingSelector();\n\t\t\t}\n\t\t\tif (this.editMode === \"skills\") {\n\t\t\t\treturn this.renderSkillSelector();\n\t\t\t}\n\t\t\treturn this.renderFullEditMode();\n\t\t}\n\t\t// Mode-based navigation rendering\n\t\tswitch (this.mode) {\n\t\t\tcase 'single': return this.renderSingleMode();\n\t\t\tcase 'parallel': return this.renderParallelMode();\n\t\t\tcase 'chain': return this.renderChainMode();\n\t\t}\n\t}\n\n\t/** Render the model selector view */\n\tprivate renderModelSelector(): string[] {\n\t\tconst th = this.theme;\n\t\tconst lines: string[] = [];\n\n\t\t// Header (mode-aware terminology)\n\t\tconst agentName = this.agentConfigs[this.editingStep!]?.name ?? \"unknown\";\n\t\tconst stepLabel = this.mode === 'single' \n\t\t\t? agentName \n\t\t\t: this.mode === 'parallel' \n\t\t\t\t? `Task ${this.editingStep! + 1}: ${agentName}` \n\t\t\t\t: `Step ${this.editingStep! + 1}: ${agentName}`;\n\t\tconst headerText = ` Select Model (${stepLabel}) `;\n\t\tlines.push(this.renderHeader(headerText));\n\t\tlines.push(this.row(\"\"));\n\n\t\tconst searchPrefix = th.fg(\"dim\", \"Search: \");\n\t\tconst cursor = \"\\x1b[7m \\x1b[27m\"; // Reverse video space for cursor\n\t\tconst searchDisplay = this.modelSearchQuery + cursor;\n\t\tlines.push(this.row(` ${searchPrefix}${searchDisplay}`));\n\t\tlines.push(this.row(\"\"));\n\n\t\tconst currentModel = this.getEffectiveModel(this.editingStep!);\n\t\tconst currentModelBase = splitThinkingSuffix(currentModel).baseModel;\n\t\tconst currentLabel = th.fg(\"dim\", \"Current: \");\n\t\tlines.push(this.row(` ${currentLabel}${th.fg(\"warning\", currentModel)}`));\n\t\tlines.push(this.row(\"\"));\n\n\t\tif (this.filteredModels.length === 0) {\n\t\t\tlines.push(this.row(` ${th.fg(\"dim\", \"No matching models\")}`));\n\t\t} else {\n\t\t\tconst maxVisible = this.MODEL_SELECTOR_HEIGHT;\n\t\t\tlet startIdx = 0;\n\n\t\t\tif (this.filteredModels.length > maxVisible) {\n\t\t\t\tstartIdx = Math.max(0, this.modelSelectedIndex - Math.floor(maxVisible / 2));\n\t\t\t\tstartIdx = Math.min(startIdx, this.filteredModels.length - maxVisible);\n\t\t\t}\n\n\t\t\tconst endIdx = Math.min(startIdx + maxVisible, this.filteredModels.length);\n\n\t\t\tif (startIdx > 0) {\n\t\t\t\tlines.push(this.row(` ${th.fg(\"dim\", ` ↑ ${startIdx} more`)}`));\n\t\t\t}\n\n\t\t\tfor (let i = startIdx; i < endIdx; i++) {\n\t\t\t\tconst model = this.filteredModels[i]!;\n\t\t\t\tconst isSelected = i === this.modelSelectedIndex;\n\t\t\t\tconst isCurrent = model.fullId === currentModelBase || model.id === currentModelBase;\n\t\t\t\tconst prefix = isSelected ? th.fg(\"accent\", \"→ \") : \" \";\n\t\t\t\tconst modelText = isSelected ? th.fg(\"accent\", model.id) : model.id;\n\t\t\t\tconst providerBadge = th.fg(\"dim\", ` [${model.provider}]`);\n\t\t\t\tconst currentBadge = isCurrent ? th.fg(\"success\", \" current\") : \"\";\n\n\t\t\t\tlines.push(this.row(` ${prefix}${modelText}${providerBadge}${currentBadge}`));\n\t\t\t}\n\n\t\t\tconst remaining = this.filteredModels.length - endIdx;\n\t\t\tif (remaining > 0) {\n\t\t\t\tlines.push(this.row(` ${th.fg(\"dim\", ` ↓ ${remaining} more`)}`));\n\t\t\t}\n\t\t}\n\n\t\tconst contentLines = lines.length;\n\t\tconst targetHeight = 18;\n\t\tfor (let i = contentLines; i < targetHeight; i++) {\n\t\t\tlines.push(this.row(\"\"));\n\t\t}\n\n\t\tconst footerText = \" [Enter] Select • [Esc] Cancel • Type to search \";\n\t\tlines.push(this.renderFooter(footerText));\n\n\t\treturn lines;\n\t}\n\n\t/** Render the thinking level selector view */\n\tprivate renderThinkingSelector(): string[] {\n\t\tconst th = this.theme;\n\t\tconst lines: string[] = [];\n\n\t\tconst agentName = this.agentConfigs[this.editingStep!]?.name ?? \"unknown\";\n\t\tconst stepLabel = this.mode === 'single' \n\t\t\t? agentName \n\t\t\t: this.mode === 'parallel' \n\t\t\t\t? `Task ${this.editingStep! + 1}: ${agentName}` \n\t\t\t\t: `Step ${this.editingStep! + 1}: ${agentName}`;\n\t\tconst headerText = ` Thinking Level (${stepLabel}) `;\n\t\tlines.push(this.renderHeader(headerText));\n\t\tlines.push(this.row(\"\"));\n\n\t\tconst currentModel = this.getEffectiveModel(this.editingStep!);\n\t\tconst currentLabel = th.fg(\"dim\", \"Model: \");\n\t\tlines.push(this.row(` ${currentLabel}${th.fg(\"accent\", currentModel)}`));\n\t\tlines.push(this.row(\"\"));\n\n\t\tlines.push(this.row(` ${th.fg(\"dim\", \"Select thinking level (extended thinking budget):\")}`));\n\t\tlines.push(this.row(\"\"));\n\n\t\tconst levelDescriptions: Record<ThinkingLevel, string> = {\n\t\t\t\"off\": \"No extended thinking\",\n\t\t\t\"minimal\": \"Brief reasoning\",\n\t\t\t\"low\": \"Light reasoning\",\n\t\t\t\"medium\": \"Moderate reasoning\",\n\t\t\t\"high\": \"Deep reasoning\",\n\t\t\t\"xhigh\": \"Maximum reasoning (ultrathink)\",\n\t\t\t\"max\": \"Maximum available reasoning\",\n\t\t};\n\n\t\tconst levels = this.getAvailableThinkingLevels(this.editingStep!);\n\t\tif (levels.length === 0) {\n\t\t\tlines.push(this.row(` ${th.fg(\"dim\", \"No supported thinking levels\")}`));\n\t\t} else {\n\t\t\tfor (let i = 0; i < levels.length; i++) {\n\t\t\t\tconst level = levels[i]!;\n\t\t\t\tconst isSelected = i === this.thinkingSelectedIndex;\n\t\t\t\tconst prefix = isSelected ? th.fg(\"accent\", \"→ \") : \" \";\n\t\t\t\tconst levelText = isSelected ? th.fg(\"accent\", level) : level;\n\t\t\t\tconst desc = th.fg(\"dim\", ` - ${levelDescriptions[level]}`);\n\t\t\t\tlines.push(this.row(` ${prefix}${levelText}${desc}`));\n\t\t\t}\n\t\t}\n\n\t\tconst contentLines = lines.length;\n\t\tconst targetHeight = 16;\n\t\tfor (let i = contentLines; i < targetHeight; i++) {\n\t\t\tlines.push(this.row(\"\"));\n\t\t}\n\n\t\tconst footerText = levels.length === 0\n\t\t\t? \" [Esc] Cancel \"\n\t\t\t: \" [Enter] Select • [Esc] Cancel • ↑↓ Navigate \";\n\t\tlines.push(this.renderFooter(footerText));\n\n\t\treturn lines;\n\t}\n\n\tprivate renderSkillSelector(): string[] {\n\t\tconst innerW = this.width - 2;\n\t\tconst th = this.theme;\n\t\tconst lines: string[] = [];\n\n\t\tconst agentName = this.agentConfigs[this.editingStep!]?.name ?? \"unknown\";\n\t\tconst stepLabel = this.mode === 'single'\n\t\t\t? agentName\n\t\t\t: this.mode === 'parallel'\n\t\t\t\t? `Task ${this.editingStep! + 1}: ${agentName}`\n\t\t\t\t: `Step ${this.editingStep! + 1}: ${agentName}`;\n\t\tlines.push(this.renderHeader(` Select Skills (${stepLabel}) `));\n\t\tlines.push(this.row(\"\"));\n\n\t\tconst cursor = \"\\x1b[7m \\x1b[27m\";\n\t\tlines.push(this.row(` ${th.fg(\"dim\", \"Search: \")}${this.skillSearchQuery}${cursor}`));\n\t\tlines.push(this.row(\"\"));\n\n\t\tconst selected = [...this.skillSelectedNames].join(\", \") || th.fg(\"dim\", \"(none)\");\n\t\tlines.push(this.row(` ${th.fg(\"dim\", \"Selected: \")}${truncateToWidth(selected, innerW - 12)}`));\n\t\tlines.push(this.row(\"\"));\n\n\t\tconst selectorHeight = 10;\n\t\tif (this.filteredSkills.length === 0) {\n\t\t\tlines.push(this.row(` ${th.fg(\"dim\", \"No matching skills\")}`));\n\t\t} else {\n\t\t\tlet startIdx = 0;\n\t\t\tif (this.filteredSkills.length > selectorHeight) {\n\t\t\t\tstartIdx = Math.max(0, this.skillCursorIndex - Math.floor(selectorHeight / 2));\n\t\t\t\tstartIdx = Math.min(startIdx, this.filteredSkills.length - selectorHeight);\n\t\t\t}\n\t\t\tconst endIdx = Math.min(startIdx + selectorHeight, this.filteredSkills.length);\n\n\t\t\tif (startIdx > 0) {\n\t\t\t\tlines.push(this.row(` ${th.fg(\"dim\", ` ↑ ${startIdx} more`)}`));\n\t\t\t}\n\n\t\t\tfor (let i = startIdx; i < endIdx; i++) {\n\t\t\t\tconst skill = this.filteredSkills[i]!;\n\t\t\t\tconst isCursor = i === this.skillCursorIndex;\n\t\t\t\tconst isSelected = this.skillSelectedNames.has(skill.name);\n\n\t\t\t\tconst prefix = isCursor ? th.fg(\"accent\", \"→ \") : \" \";\n\t\t\t\tconst checkbox = isSelected ? th.fg(\"success\", \"[x]\") : \"[ ]\";\n\t\t\t\tconst nameText = isCursor ? th.fg(\"accent\", skill.name) : skill.name;\n\t\t\t\tconst sourceBadge = th.fg(\"dim\", ` [${skill.source}]`);\n\t\t\t\tconst desc = skill.description\n\t\t\t\t\t? th.fg(\"dim\", ` - ${truncateToWidth(skill.description, 25)}`)\n\t\t\t\t\t: \"\";\n\n\t\t\t\tlines.push(this.row(` ${prefix}${checkbox} ${nameText}${sourceBadge}${desc}`));\n\t\t\t}\n\n\t\t\tconst remaining = this.filteredSkills.length - endIdx;\n\t\t\tif (remaining > 0) {\n\t\t\t\tlines.push(this.row(` ${th.fg(\"dim\", ` ↓ ${remaining} more`)}`));\n\t\t\t}\n\t\t}\n\n\t\tconst targetHeight = 18;\n\t\tfor (let i = lines.length; i < targetHeight; i++) {\n\t\t\tlines.push(this.row(\"\"));\n\t\t}\n\n\t\tlines.push(this.renderFooter(\" [Enter] Confirm • [Space] Toggle • [Esc] Cancel \"));\n\t\treturn lines;\n\t}\n\n\tprivate getShortcutHelpText(): string {\n\t\tconst th = this.theme;\n\t\tconst shortcut = (key: string, label: string) =>\n\t\t\tth.fg(\"accent\", `[${key}]`) + th.fg(\"dim\", label);\n\t\tconst backgroundState = this.runInBackground ? \"ON\" : \"OFF\";\n\t\tconst background = shortcut(\"b\", `Background:${backgroundState}`);\n\n\t\tswitch (this.mode) {\n\t\t\tcase 'single':\n\t\t\t\treturn ` ${shortcut(\"e\", \"Task\")} ${shortcut(\"m\", \"Model\")} ${shortcut(\"t\", \"Think\")} ${shortcut(\"w\", \"Output\")} ${shortcut(\"s\", \"Skills\")} ${background}`;\n\t\t\tcase 'parallel':\n\t\t\t\treturn ` ${shortcut(\"e\", \"Task\")} ${shortcut(\"m\", \"Model\")} ${shortcut(\"t\", \"Think\")} ${shortcut(\"s\", \"Skills\")} ${background}`;\n\t\t\tcase 'chain':\n\t\t\t\treturn ` ${shortcut(\"e\", \"Task\")} ${shortcut(\"m\", \"Model\")} ${shortcut(\"t\", \"Think\")} ${shortcut(\"w\", \"Output\")} ${shortcut(\"r\", \"Reads\")} ${shortcut(\"p\", \"Prog\")} ${shortcut(\"s\", \"Skills\")} ${background}`;\n\t\t}\n\t}\n\n\tprivate getFooterText(): string {\n\t\treturn this.mode === 'single'\n\t\t\t? \" [Enter] Run • [Esc] Cancel \"\n\t\t\t: \" [Enter] Run • [Esc] Cancel • [↑↓] Navigate \";\n\t}\n\n\tprivate appendNotice(lines: string[]): void {\n\t\tif (!this.noticeMessage) return;\n\t\tconst color = this.noticeMessage.type === \"error\" ? \"error\" : \"success\";\n\t\tlines.push(this.row(` ${this.theme.fg(color, this.noticeMessage.text)}`));\n\t}\n\n\tprivate renderSingleMode(): string[] {\n\t\tconst innerW = this.width - 2;\n\t\tconst th = this.theme;\n\t\tconst lines: string[] = [];\n\n\t\tconst agentName = this.agentConfigs[0]?.name ?? \"unknown\";\n\t\tconst maxHeaderLen = innerW - 4;\n\t\tconst headerText = ` Agent: ${truncateToWidth(agentName, maxHeaderLen - 9)} `;\n\t\tlines.push(this.renderHeader(headerText));\n\t\tlines.push(this.row(\"\"));\n\n\t\tconst config = this.agentConfigs[0]!;\n\t\tconst behavior = this.getEffectiveBehavior(0);\n\n\t\tconst stepLabel = config.name;\n\t\tlines.push(this.row(` ${th.fg(\"accent\", \"▶ \" + stepLabel)}`));\n\n\t\tconst template = (this.templates[0] ?? \"\").split(\"\\n\")[0] ?? \"\";\n\t\tconst taskLabel = th.fg(\"dim\", \"task: \");\n\t\tlines.push(this.row(` ${taskLabel}${truncateToWidth(template, innerW - 12)}`));\n\n\t\tconst effectiveModel = this.getEffectiveModel(0);\n\t\tconst override = this.behaviorOverrides.get(0);\n\t\tconst isOverridden = override?.model !== undefined;\n\t\tconst modelValue = isOverridden\n\t\t\t? th.fg(\"warning\", effectiveModel) + th.fg(\"dim\", \" ✎\")\n\t\t\t: effectiveModel;\n\t\tconst modelLabel = th.fg(\"dim\", \"model: \");\n\t\tlines.push(this.row(` ${modelLabel}${truncateToWidth(modelValue, innerW - 13)}`));\n\n\t\tconst writesValue = behavior.output === false\n\t\t\t? th.fg(\"dim\", \"(disabled)\")\n\t\t\t: (behavior.output || th.fg(\"dim\", \"(none)\"));\n\t\tconst writesLabel = th.fg(\"dim\", \"writes: \");\n\t\tlines.push(this.row(` ${writesLabel}${truncateToWidth(writesValue, innerW - 14)}`));\n\n\t\tconst skillsValue = behavior.skills === false\n\t\t\t? th.fg(\"dim\", \"(disabled)\")\n\t\t\t: (behavior.skills?.length ? behavior.skills.join(\", \") : th.fg(\"dim\", \"(none)\"));\n\t\tconst skillsLabel = th.fg(\"dim\", \"skills: \");\n\t\tlines.push(this.row(` ${skillsLabel}${truncateToWidth(skillsValue, innerW - 14)}`));\n\n\t\tlines.push(this.row(this.getShortcutHelpText()));\n\n\t\tthis.appendNotice(lines);\n\t\tlines.push(this.renderFooter(this.getFooterText()));\n\n\t\treturn lines;\n\t}\n\n\tprivate renderParallelMode(): string[] {\n\t\tconst innerW = this.width - 2;\n\t\tconst th = this.theme;\n\t\tconst lines: string[] = [];\n\n\t\tconst headerText = ` Parallel Tasks (${this.agentConfigs.length}) `;\n\t\tlines.push(this.renderHeader(headerText));\n\t\tlines.push(this.row(\"\"));\n\n\t\tfor (let i = 0; i < this.agentConfigs.length; i++) {\n\t\t\tconst config = this.agentConfigs[i]!;\n\t\t\tconst isSelected = i === this.selectedStep;\n\n\t\t\tconst color = isSelected ? \"accent\" : \"dim\";\n\t\t\tconst prefix = isSelected ? \"▶ \" : \" \";\n\t\t\tconst taskPrefix = `Task ${i + 1}: `;\n\t\t\tconst maxNameLen = innerW - 4 - prefix.length - taskPrefix.length;\n\t\t\tconst agentName = config.name.length > maxNameLen\n\t\t\t\t? config.name.slice(0, maxNameLen - 1) + \"…\"\n\t\t\t\t: config.name;\n\t\t\tconst taskLabel = `${taskPrefix}${agentName}`;\n\t\t\tlines.push(this.row(` ${th.fg(color, prefix + taskLabel)}`));\n\n\t\t\tconst template = (this.templates[i] ?? \"\").split(\"\\n\")[0] ?? \"\";\n\t\t\tconst taskTextLabel = th.fg(\"dim\", \"task: \");\n\t\t\tlines.push(this.row(` ${taskTextLabel}${truncateToWidth(template, innerW - 12)}`));\n\n\t\t\tconst effectiveModel = this.getEffectiveModel(i);\n\t\t\tconst override = this.behaviorOverrides.get(i);\n\t\t\tconst isOverridden = override?.model !== undefined;\n\t\t\tconst modelValue = isOverridden\n\t\t\t\t? th.fg(\"warning\", effectiveModel) + th.fg(\"dim\", \" ✎\")\n\t\t\t\t: effectiveModel;\n\t\t\tconst modelLabel = th.fg(\"dim\", \"model: \");\n\t\t\tlines.push(this.row(` ${modelLabel}${truncateToWidth(modelValue, innerW - 13)}`));\n\n\t\t\tconst behavior = this.getEffectiveBehavior(i);\n\t\t\tconst skillsValue = behavior.skills === false\n\t\t\t\t? th.fg(\"dim\", \"(disabled)\")\n\t\t\t\t: (behavior.skills?.length ? behavior.skills.join(\", \") : th.fg(\"dim\", \"(none)\"));\n\t\t\tconst skillsLabel = th.fg(\"dim\", \"skills: \");\n\t\t\tlines.push(this.row(` ${skillsLabel}${truncateToWidth(skillsValue, innerW - 14)}`));\n\n\t\t\tlines.push(this.row(\"\"));\n\t\t}\n\n\t\tif (this.agentConfigs.length > 0) lines.pop();\n\t\tlines.push(this.row(this.getShortcutHelpText()));\n\t\tthis.appendNotice(lines);\n\t\tlines.push(this.renderFooter(this.getFooterText()));\n\n\t\treturn lines;\n\t}\n\n\tprivate renderChainMode(): string[] {\n\t\tconst innerW = this.width - 2;\n\t\tconst th = this.theme;\n\t\tconst lines: string[] = [];\n\n\t\tconst chainLabel = this.agentConfigs.map((c) => c.name).join(\" → \");\n\t\tconst maxHeaderLen = innerW - 4;\n\t\tconst headerText = ` Chain: ${truncateToWidth(chainLabel, maxHeaderLen - 9)} `;\n\t\tlines.push(this.renderHeader(headerText));\n\n\t\tlines.push(this.row(\"\"));\n\n\t\tconst taskPreview = truncateToWidth(this.originalTask, innerW - 16);\n\t\tlines.push(this.row(` Original Task: ${taskPreview}`));\n\t\tconst chainDirPreview = truncateToWidth(this.chainDir ?? \"\", innerW - 12);\n\t\tlines.push(this.row(` Chain Dir: ${th.fg(\"dim\", chainDirPreview)}`));\n\n\t\tconst progressEnabled = this.agentConfigs.some((_, i) => this.getEffectiveBehavior(i).progress);\n\t\tconst progressValue = progressEnabled ? th.fg(\"success\", \"enabled\") : th.fg(\"dim\", \"disabled\");\n\t\tlines.push(this.row(` Progress: ${progressValue} ${th.fg(\"dim\", \"(press [p] to toggle)\")}`));\n\t\tlines.push(this.row(\"\"));\n\n\t\tfor (let i = 0; i < this.agentConfigs.length; i++) {\n\t\t\tconst config = this.agentConfigs[i]!;\n\t\t\tconst isSelected = i === this.selectedStep;\n\t\t\tconst behavior = this.getEffectiveBehavior(i);\n\n\t\t\tconst color = isSelected ? \"accent\" : \"dim\";\n\t\t\tconst prefix = isSelected ? \"▶ \" : \" \";\n\t\t\tconst stepPrefix = `Step ${i + 1}: `;\n\t\t\tconst maxNameLen = innerW - 4 - prefix.length - stepPrefix.length;\n\t\t\tconst agentName = config.name.length > maxNameLen\n\t\t\t\t? config.name.slice(0, maxNameLen - 1) + \"…\"\n\t\t\t\t: config.name;\n\t\t\tconst stepLabel = `${stepPrefix}${agentName}`;\n\t\t\tlines.push(\n\t\t\t\tthis.row(` ${th.fg(color, prefix + stepLabel)}`),\n\t\t\t);\n\n\t\t\tconst template = (this.templates[i] ?? \"\").split(\"\\n\")[0] ?? \"\";\n\t\t\tconst highlighted = template\n\t\t\t\t.replace(/\\{task\\}/g, th.fg(\"success\", \"{task}\"))\n\t\t\t\t.replace(/\\{previous\\}/g, th.fg(\"warning\", \"{previous}\"))\n\t\t\t\t.replace(/\\{chain_dir\\}/g, th.fg(\"accent\", \"{chain_dir}\"));\n\n\t\t\tconst templateLabel = th.fg(\"dim\", \"task: \");\n\t\t\tlines.push(this.row(` ${templateLabel}${truncateToWidth(highlighted, innerW - 12)}`));\n\n\t\t\tconst effectiveModel = this.getEffectiveModel(i);\n\t\t\tconst override = this.behaviorOverrides.get(i);\n\t\t\tconst isOverridden = override?.model !== undefined;\n\t\t\tconst modelValue = isOverridden\n\t\t\t\t? th.fg(\"warning\", effectiveModel) + th.fg(\"dim\", \" ✎\")\n\t\t\t\t: effectiveModel;\n\t\t\tconst modelLabel = th.fg(\"dim\", \"model: \");\n\t\t\tlines.push(this.row(` ${modelLabel}${truncateToWidth(modelValue, innerW - 13)}`));\n\n\t\t\tconst writesValue = behavior.output === false\n\t\t\t\t? th.fg(\"dim\", \"(disabled)\")\n\t\t\t\t: (behavior.output || th.fg(\"dim\", \"(none)\"));\n\t\t\tconst writesLabel = th.fg(\"dim\", \"writes: \");\n\t\t\tlines.push(this.row(` ${writesLabel}${truncateToWidth(writesValue, innerW - 14)}`));\n\n\t\t\tconst readsValue = behavior.reads === false\n\t\t\t\t? th.fg(\"dim\", \"(disabled)\")\n\t\t\t\t: (behavior.reads && behavior.reads.length > 0\n\t\t\t\t\t? behavior.reads.join(\", \")\n\t\t\t\t\t: th.fg(\"dim\", \"(none)\"));\n\t\t\tconst readsLabel = th.fg(\"dim\", \"reads: \");\n\t\t\tlines.push(this.row(` ${readsLabel}${truncateToWidth(readsValue, innerW - 13)}`));\n\n\t\t\tconst skillsValue = behavior.skills === false\n\t\t\t\t? th.fg(\"dim\", \"(disabled)\")\n\t\t\t\t: (behavior.skills?.length ? behavior.skills.join(\", \") : th.fg(\"dim\", \"(none)\"));\n\t\t\tconst skillsLabel = th.fg(\"dim\", \"skills: \");\n\t\t\tlines.push(this.row(` ${skillsLabel}${truncateToWidth(skillsValue, innerW - 14)}`));\n\n\t\t\tif (progressEnabled) {\n\t\t\t\tconst isFirstStep = i === 0;\n\t\t\t\tconst progressAction = isFirstStep \n\t\t\t\t\t? th.fg(\"success\", \"writes progress.md\")\n\t\t\t\t\t: th.fg(\"accent\", \"reads progress.md\");\n\t\t\t\tconst progressLabel = th.fg(\"dim\", \"progress: \");\n\t\t\t\tlines.push(this.row(` ${progressLabel}${progressAction}`));\n\t\t\t}\n\n\t\t\tif (i < this.agentConfigs.length - 1) {\n\t\t\t\tconst nextStepUsePrevious = (this.templates[i + 1] ?? \"\").includes(\"{previous}\");\n\t\t\t\tif (nextStepUsePrevious) {\n\t\t\t\t\tconst indicator = th.fg(\"dim\", \" ↳ response → \") + th.fg(\"warning\", \"{previous}\");\n\t\t\t\t\tlines.push(this.row(indicator));\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tlines.push(this.row(\"\"));\n\t\t}\n\n\t\tif (this.agentConfigs.length > 0) lines.pop();\n\t\tlines.push(this.row(this.getShortcutHelpText()));\n\t\tthis.appendNotice(lines);\n\t\tlines.push(this.renderFooter(this.getFooterText()));\n\n\t\treturn lines;\n\t}\n\n\tinvalidate(): void {}\n\tdispose(): void {\n\t\tif (this.noticeMessageTimer) clearTimeout(this.noticeMessageTimer);\n\t\tthis.noticeMessageTimer = null;\n\t}\n}\n"]}
1
+ {"version":3,"file":"chain-clarify.d.ts","sourceRoot":"","sources":["../../../../../../src/builtin-extensions/pi-subagents/src/runs/foreground/chain-clarify.ts"],"names":[],"mappings":"AACA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,iCAAiC,CAAC;AAC7D,OAAO,KAAK,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,wBAAwB,CAAC;AAE7D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAErE,OAAO,EAA6C,KAAK,SAAS,EAAsB,MAAM,4BAA4B,CAAC;AAE3H,KAAK,WAAW,GAAG,QAAQ,GAAG,UAAU,GAAG,OAAO,CAAC;AAEnD,MAAM,WAAW,gBAAgB;IAChC,MAAM,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,KAAK,CAAC;IACzB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,EAAE,GAAG,KAAK,CAAC;CAC1B;AAED,MAAM,WAAW,kBAAkB;IAClC,SAAS,EAAE,OAAO,CAAC;IACnB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,iBAAiB,EAAE,CAAC,gBAAgB,GAAG,SAAS,CAAC,EAAE,CAAC;IACpD,eAAe,CAAC,EAAE,OAAO,CAAC;CAC1B;AAoKD;;;GAGG;AACH,qBAAa,qBAAsB,YAAW,SAAS;IACtD,QAAQ,CAAC,KAAK,MAAM;IAEpB,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,WAAW,CAAuB;IAC1C,OAAO,CAAC,QAAQ,CAAwB;IACxC,OAAO,CAAC,SAAS,CAAwC;IAEzD,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAAM;IAC3C,OAAO,CAAC,iBAAiB,CAA4C;IACrE,OAAO,CAAC,gBAAgB,CAAc;IACtC,OAAO,CAAC,kBAAkB,CAAa;IACvC,OAAO,CAAC,cAAc,CAAmB;IACzC,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAAM;IAC5C,OAAO,CAAC,qBAAqB,CAAa;IAC1C,OAAO,CAAC,gBAAgB,CAAc;IACtC,OAAO,CAAC,kBAAkB,CAA0B;IACpD,OAAO,CAAC,gBAAgB,CAAa;IACrC,OAAO,CAAC,cAAc,CAAqE;IAC3F,OAAO,CAAC,aAAa,CAAyD;IAC9E,OAAO,CAAC,kBAAkB,CAA8C;IACxE,qCAAqC;IACrC,OAAO,CAAC,eAAe,CAAS;IAChC,OAAO,CAAC,GAAG,CAAM;IACjB,OAAO,CAAC,KAAK,CAAQ;IACrB,OAAO,CAAC,YAAY,CAAuB;IAC3C,OAAO,CAAC,SAAS,CAAW;IAC5B,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,QAAQ,CAAqB;IACrC,OAAO,CAAC,iBAAiB,CAAyB;IAClD,OAAO,CAAC,eAAe,CAAc;IACrC,OAAO,CAAC,iBAAiB,CAAqB;IAC9C,OAAO,CAAC,eAAe,CAAgE;IACvF,OAAO,CAAC,IAAI,CAAuC;IACnD,OAAO,CAAC,IAAI,CAAc;IAE1B,YACC,GAAG,EAAE,GAAG,EACR,KAAK,EAAE,KAAK,EACZ,YAAY,EAAE,kBAAkB,EAAE,EAClC,SAAS,EAAE,MAAM,EAAE,EACnB,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,iBAAiB,EAAE,oBAAoB,EAAE,EACzC,eAAe,EAAE,SAAS,EAAE,EAC5B,iBAAiB,EAAE,MAAM,GAAG,SAAS,EACrC,eAAe,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,EAC9E,IAAI,EAAE,CAAC,MAAM,EAAE,kBAAkB,KAAK,IAAI,EAC1C,IAAI,GAAE,WAAqB,EAgB3B;IAMD,4CAA4C;IAC5C,OAAO,CAAC,GAAG;IAKX,0CAA0C;IAC1C,OAAO,CAAC,GAAG;IAKX,8CAA8C;IAC9C,OAAO,CAAC,YAAY;IAYpB,8CAA8C;IAC9C,OAAO,CAAC,YAAY;IAYpB,qCAAqC;IACrC,OAAO,CAAC,YAAY;IAUpB,yCAAyC;IACzC,OAAO,CAAC,kBAAkB;IA2D1B,sEAAsE;IACtE,OAAO,CAAC,oBAAoB;IAe5B,qEAAqE;IACrE,OAAO,CAAC,iBAAiB;IASzB,6DAA6D;IAC7D,OAAO,CAAC,kBAAkB;IAI1B,4CAA4C;IAC5C,OAAO,CAAC,cAAc;IAKtB,OAAO,CAAC,UAAU;IAWlB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAiG9B;IAED,OAAO,CAAC,aAAa;IAoBrB,gCAAgC;IAChC,OAAO,CAAC,kBAAkB;IAe1B,0CAA0C;IAC1C,OAAO,CAAC,YAAY;IAcpB,OAAO,CAAC,wBAAwB;IAwDhC,OAAO,CAAC,0BAA0B;IAIlC,yCAAyC;IACzC,OAAO,CAAC,qBAAqB;IAiB7B,OAAO,CAAC,2BAA2B;IAiCnC,uDAAuD;IACvD,OAAO,CAAC,kBAAkB;IAU1B,OAAO,CAAC,YAAY;IAapB,OAAO,CAAC,wBAAwB;IAiEhC,OAAO,CAAC,eAAe;IA2CvB,OAAO,CAAC,QAAQ;IAmChB;;;OAGG;IACH,OAAO,CAAC,qBAAqB;IAuB7B,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAmB/B;IAED,qCAAqC;IACrC,OAAO,CAAC,mBAAmB;IA0E3B,8CAA8C;IAC9C,OAAO,CAAC,sBAAsB;IA4D9B,OAAO,CAAC,mBAAmB;IAoE3B,OAAO,CAAC,mBAAmB;IAiB3B,OAAO,CAAC,aAAa;IAMrB,OAAO,CAAC,YAAY;IAMpB,OAAO,CAAC,gBAAgB;IAkDxB,OAAO,CAAC,kBAAkB;IAsD1B,OAAO,CAAC,eAAe;IAyGvB,UAAU,IAAI,IAAI,CAAG;IACrB,OAAO,IAAI,IAAI,CAGd;CACD","sourcesContent":["// @ts-nocheck\n/**\n * Chain Clarification TUI Component\n *\n * Shows templates and resolved behaviors for each step in a chain.\n * Supports runtime editing of templates, output paths, reads lists, and progress toggle.\n */\n\nimport type { Theme } from \"@ashx-j/lunr\";\nimport type { Component, TUI } from \"@ashx-j/lunr-tui\";\nimport { matchesKey, visibleWidth, truncateToWidth } from \"@ashx-j/lunr-tui\";\nimport type { ChildRuntimeConfig } from \"../../shared/types.ts\";\nimport type { ResolvedStepBehavior } from \"../../shared/settings.ts\";\nimport { resolveModelCandidate, splitThinkingSuffix } from \"../shared/model-fallback.ts\";\nimport { findModelInfo, getSupportedThinkingLevels, type ModelInfo, type ThinkingLevel } from \"../../shared/model-info.ts\";\n\ntype ClarifyMode = 'single' | 'parallel' | 'chain';\n\nexport interface BehaviorOverride {\n\toutput?: string | false;\n\treads?: string[] | false;\n\tprogress?: boolean;\n\tmodel?: string;\n\tskills?: string[] | false;\n}\n\nexport interface ChainClarifyResult {\n\tconfirmed: boolean;\n\ttemplates: string[];\n\tbehaviorOverrides: (BehaviorOverride | undefined)[];\n\trunInBackground?: boolean;\n}\n\ntype EditMode = \"template\" | \"output\" | \"reads\" | \"model\" | \"thinking\" | \"skills\";\n\n\ninterface TextEditorState {\n\tbuffer: string;\n\tcursor: number;\n\tviewportOffset: number;\n}\n\nfunction createEditorState(initial = \"\"): TextEditorState {\n\treturn { buffer: initial, cursor: 0, viewportOffset: 0 };\n}\n\nfunction wrapText(text: string, width: number): { lines: string[]; starts: number[] } {\n\tif (width <= 0) return { lines: [text], starts: [0] };\n\tif (text.length === 0) return { lines: [\"\"], starts: [0] };\n\n\tconst lines: string[] = [];\n\tconst starts: number[] = [];\n\tlet offset = 0;\n\tconst segments = text.split(\"\\n\");\n\tfor (const [index, segment] of segments.entries()) {\n\t\tif (segment.length === 0) {\n\t\t\tstarts.push(offset);\n\t\t\tlines.push(\"\");\n\t\t} else {\n\t\t\tlet lineStart = 0;\n\t\t\tlet pos = 0;\n\t\t\tlet lineWidth = 0;\n\t\t\twhile (pos < segment.length) {\n\t\t\t\tconst char = String.fromCodePoint(segment.codePointAt(pos)!);\n\t\t\t\tconst charWidth = visibleWidth(char);\n\t\t\t\tif (lineWidth > 0 && lineWidth + charWidth > width) {\n\t\t\t\t\tstarts.push(offset + lineStart);\n\t\t\t\t\tlines.push(segment.slice(lineStart, pos));\n\t\t\t\t\tlineStart = pos;\n\t\t\t\t\tlineWidth = 0;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tpos += char.length;\n\t\t\t\tlineWidth += charWidth;\n\t\t\t}\n\t\t\tstarts.push(offset + lineStart);\n\t\t\tlines.push(segment.slice(lineStart));\n\t\t}\n\t\toffset += segment.length + (index < segments.length - 1 ? 1 : 0);\n\t}\n\tif (!text.endsWith(\"\\n\") && text.length > 0 && visibleWidth(lines[lines.length - 1] ?? \"\") === width) {\n\t\tstarts.push(text.length);\n\t\tlines.push(\"\");\n\t}\n\treturn { lines, starts };\n}\n\nfunction getCursorDisplayPos(cursor: number, starts: number[]): { line: number; col: number } {\n\tfor (let i = starts.length - 1; i >= 0; i--) {\n\t\tif (cursor >= starts[i]!) return { line: i, col: cursor - starts[i]! };\n\t}\n\treturn { line: 0, col: 0 };\n}\n\nfunction ensureCursorVisible(cursorLine: number, viewportHeight: number, currentOffset: number): number {\n\tif (cursorLine < currentOffset) return Math.max(0, cursorLine);\n\tif (cursorLine >= currentOffset + viewportHeight) return Math.max(0, cursorLine - viewportHeight + 1);\n\treturn Math.max(0, currentOffset);\n}\n\nfunction isWordChar(ch: string): boolean {\n\tconst code = ch.charCodeAt(0);\n\treturn (code >= 48 && code <= 57) || (code >= 65 && code <= 90) || (code >= 97 && code <= 122) || code === 95;\n}\n\nfunction wordBackward(buffer: string, cursor: number): number {\n\tlet pos = cursor;\n\twhile (pos > 0 && !isWordChar(buffer[pos - 1]!)) pos--;\n\twhile (pos > 0 && isWordChar(buffer[pos - 1]!)) pos--;\n\treturn pos;\n}\n\nfunction wordForward(buffer: string, cursor: number): number {\n\tlet pos = cursor;\n\twhile (pos < buffer.length && isWordChar(buffer[pos]!)) pos++;\n\twhile (pos < buffer.length && !isWordChar(buffer[pos]!)) pos++;\n\treturn pos;\n}\n\nfunction normalizeInsertText(data: string): string | null {\n\tlet text = data.split(\"\\x1b[200~\").join(\"\").split(\"\\x1b[201~\").join(\"\");\n\ttext = text.replace(/\\r\\n/g, \"\\n\").replace(/\\r/g, \"\\n\");\n\tconst newline = text.indexOf(\"\\n\");\n\tif (newline !== -1) text = text.slice(0, newline);\n\ttext = text.replace(/\\t/g, \" \");\n\tif (text.length === 0) return null;\n\tfor (let i = 0; i < text.length; i++) {\n\t\tif (text.charCodeAt(i) < 32) return null;\n\t}\n\treturn text;\n}\n\nfunction handleEditorInput(state: TextEditorState, data: string, textWidth: number): TextEditorState | null {\n\tif (matchesKey(data, \"escape\") || matchesKey(data, \"ctrl+c\") || matchesKey(data, \"return\")) return null;\n\n\tconst { lines: wrapped, starts } = wrapText(state.buffer, textWidth);\n\tconst cursorPos = getCursorDisplayPos(state.cursor, starts);\n\n\tif (matchesKey(data, \"alt+left\") || matchesKey(data, \"ctrl+left\")) return { ...state, cursor: wordBackward(state.buffer, state.cursor) };\n\tif (matchesKey(data, \"alt+right\") || matchesKey(data, \"ctrl+right\")) return { ...state, cursor: wordForward(state.buffer, state.cursor) };\n\tif (matchesKey(data, \"left\")) return state.cursor > 0 ? { ...state, cursor: state.cursor - 1 } : state;\n\tif (matchesKey(data, \"right\")) return state.cursor < state.buffer.length ? { ...state, cursor: state.cursor + 1 } : state;\n\tif (matchesKey(data, \"up\") && cursorPos.line > 0) {\n\t\tconst targetLine = cursorPos.line - 1;\n\t\treturn { ...state, cursor: starts[targetLine]! + Math.min(cursorPos.col, wrapped[targetLine]?.length ?? 0) };\n\t}\n\tif (matchesKey(data, \"down\") && cursorPos.line < wrapped.length - 1) {\n\t\tconst targetLine = cursorPos.line + 1;\n\t\treturn { ...state, cursor: starts[targetLine]! + Math.min(cursorPos.col, wrapped[targetLine]?.length ?? 0) };\n\t}\n\tif (matchesKey(data, \"home\")) return { ...state, cursor: starts[cursorPos.line]! };\n\tif (matchesKey(data, \"end\")) return { ...state, cursor: starts[cursorPos.line]! + (wrapped[cursorPos.line]?.length ?? 0) };\n\tif (matchesKey(data, \"ctrl+home\")) return { ...state, cursor: 0 };\n\tif (matchesKey(data, \"ctrl+end\")) return { ...state, cursor: state.buffer.length };\n\tif (matchesKey(data, \"alt+backspace\")) {\n\t\tconst target = wordBackward(state.buffer, state.cursor);\n\t\treturn target === state.cursor ? state : { ...state, buffer: state.buffer.slice(0, target) + state.buffer.slice(state.cursor), cursor: target };\n\t}\n\tif (matchesKey(data, \"backspace\")) {\n\t\treturn state.cursor > 0\n\t\t\t? { ...state, buffer: state.buffer.slice(0, state.cursor - 1) + state.buffer.slice(state.cursor), cursor: state.cursor - 1 }\n\t\t\t: state;\n\t}\n\tif (matchesKey(data, \"delete\")) {\n\t\treturn state.cursor < state.buffer.length\n\t\t\t? { ...state, buffer: state.buffer.slice(0, state.cursor) + state.buffer.slice(state.cursor + 1) }\n\t\t\t: state;\n\t}\n\n\tconst insert = normalizeInsertText(data);\n\treturn insert\n\t\t? { ...state, buffer: state.buffer.slice(0, state.cursor) + insert + state.buffer.slice(state.cursor), cursor: state.cursor + insert.length }\n\t\t: null;\n}\n\nfunction renderWithCursor(text: string, cursorPos: number): string {\n\tconst before = text.slice(0, cursorPos);\n\tconst cursorChar = text[cursorPos] ?? \" \";\n\tconst after = text.slice(cursorPos + 1);\n\treturn `${before}\\x1b[7m${cursorChar}\\x1b[27m${after}`;\n}\n\nfunction renderEditor(state: TextEditorState, width: number, viewportHeight: number): string[] {\n\tconst { lines: wrapped, starts } = wrapText(state.buffer, width);\n\tconst cursorPos = getCursorDisplayPos(state.cursor, starts);\n\tconst lines: string[] = [];\n\tfor (let i = 0; i < viewportHeight; i++) {\n\t\tconst lineIdx = state.viewportOffset + i;\n\t\tlet content = lineIdx < wrapped.length ? wrapped[lineIdx] ?? \"\" : \"\";\n\t\tif (lineIdx === cursorPos.line) content = renderWithCursor(content, cursorPos.col);\n\t\tlines.push(content);\n\t}\n\treturn lines;\n}\n\n/**\n * TUI component for chain clarification.\n * Factory signature matches ctx.ui.custom: (tui, theme, kb, done) => Component\n */\nexport class ChainClarifyComponent implements Component {\n\treadonly width = 84;\n\n\tprivate selectedStep = 0;\n\tprivate editingStep: number | null = null;\n\tprivate editMode: EditMode = \"template\";\n\tprivate editState: TextEditorState = createEditorState();\n\n\tprivate readonly EDIT_VIEWPORT_HEIGHT = 12;\n\tprivate behaviorOverrides: Map<number, BehaviorOverride> = new Map();\n\tprivate modelSearchQuery: string = \"\";\n\tprivate modelSelectedIndex: number = 0;\n\tprivate filteredModels: ModelInfo[] = [];\n\tprivate readonly MODEL_SELECTOR_HEIGHT = 10;\n\tprivate thinkingSelectedIndex: number = 0;\n\tprivate skillSearchQuery: string = \"\";\n\tprivate skillSelectedNames: Set<string> = new Set();\n\tprivate skillCursorIndex: number = 0;\n\tprivate filteredSkills: Array<{ name: string; source: string; description?: string }> = [];\n\tprivate noticeMessage: { text: string; type: \"info\" | \"error\" } | null = null;\n\tprivate noticeMessageTimer: ReturnType<typeof setTimeout> | null = null;\n\t/** Run in background (async) mode */\n\tprivate runInBackground = false;\n\tprivate tui: TUI;\n\tprivate theme: Theme;\n\tprivate agentConfigs: ChildRuntimeConfig[];\n\tprivate templates: string[];\n\tprivate originalTask: string;\n\tprivate chainDir: string | undefined;\n\tprivate resolvedBehaviors: ResolvedStepBehavior[];\n\tprivate availableModels: ModelInfo[];\n\tprivate preferredProvider: string | undefined;\n\tprivate availableSkills: Array<{ name: string; source: string; description?: string }>;\n\tprivate done: (result: ChainClarifyResult) => void;\n\tprivate mode: ClarifyMode;\n\n\tconstructor(\n\t\ttui: TUI,\n\t\ttheme: Theme,\n\t\tagentConfigs: ChildRuntimeConfig[],\n\t\ttemplates: string[],\n\t\toriginalTask: string,\n\t\tchainDir: string | undefined,\n\t\tresolvedBehaviors: ResolvedStepBehavior[],\n\t\tavailableModels: ModelInfo[],\n\t\tpreferredProvider: string | undefined,\n\t\tavailableSkills: Array<{ name: string; source: string; description?: string }>,\n\t\tdone: (result: ChainClarifyResult) => void,\n\t\tmode: ClarifyMode = 'chain',\n\t) {\n\t\tthis.tui = tui;\n\t\tthis.theme = theme;\n\t\tthis.agentConfigs = agentConfigs;\n\t\tthis.templates = templates;\n\t\tthis.originalTask = originalTask;\n\t\tthis.chainDir = chainDir;\n\t\tthis.resolvedBehaviors = resolvedBehaviors;\n\t\tthis.availableModels = availableModels;\n\t\tthis.preferredProvider = preferredProvider;\n\t\tthis.availableSkills = availableSkills;\n\t\tthis.done = done;\n\t\tthis.mode = mode;\n\t\tthis.filteredModels = [...availableModels];\n\t\tthis.filteredSkills = [...availableSkills];\n\t}\n\n\t// ─────────────────────────────────────────────────────────────────────────────\n\t// Helper methods for rendering\n\t// ─────────────────────────────────────────────────────────────────────────────\n\n\t/** Pad string to specified visible width */\n\tprivate pad(s: string, len: number): string {\n\t\tconst vis = visibleWidth(s);\n\t\treturn s + \" \".repeat(Math.max(0, len - vis));\n\t}\n\n\t/** Create a row with border characters */\n\tprivate row(content: string): string {\n\t\tconst innerW = this.width - 2;\n\t\treturn this.theme.fg(\"border\", \"│\") + this.pad(content, innerW) + this.theme.fg(\"border\", \"│\");\n\t}\n\n\t/** Render centered header line with border */\n\tprivate renderHeader(text: string): string {\n\t\tconst innerW = this.width - 2;\n\t\tconst padLen = Math.max(0, innerW - visibleWidth(text));\n\t\tconst padLeft = Math.floor(padLen / 2);\n\t\tconst padRight = padLen - padLeft;\n\t\treturn (\n\t\t\tthis.theme.fg(\"border\", \"╭\" + \"─\".repeat(padLeft)) +\n\t\t\tthis.theme.fg(\"accent\", text) +\n\t\t\tthis.theme.fg(\"border\", \"─\".repeat(padRight) + \"╮\")\n\t\t);\n\t}\n\n\t/** Render centered footer line with border */\n\tprivate renderFooter(text: string): string {\n\t\tconst innerW = this.width - 2;\n\t\tconst padLen = Math.max(0, innerW - visibleWidth(text));\n\t\tconst padLeft = Math.floor(padLen / 2);\n\t\tconst padRight = padLen - padLeft;\n\t\treturn (\n\t\t\tthis.theme.fg(\"border\", \"╰\" + \"─\".repeat(padLeft)) +\n\t\t\tthis.theme.fg(\"dim\", text) +\n\t\t\tthis.theme.fg(\"border\", \"─\".repeat(padRight) + \"╯\")\n\t\t);\n\t}\n\n\t/** Exit edit mode and reset state */\n\tprivate exitEditMode(): void {\n\t\tthis.editingStep = null;\n\t\tthis.editState = createEditorState();\n\t\tthis.tui.requestRender();\n\t}\n\n\t// ─────────────────────────────────────────────────────────────────────────────\n\t// Full edit mode methods\n\t// ─────────────────────────────────────────────────────────────────────────────\n\n\t/** Render the full-edit takeover view */\n\tprivate renderFullEditMode(): string[] {\n\t\tconst innerW = this.width - 2;\n\t\tconst textWidth = innerW - 2; // 1 char padding on each side\n\t\tconst lines: string[] = [];\n\n\t\tconst { lines: wrapped, starts } = wrapText(this.editState.buffer, textWidth);\n\t\tconst cursorPos = getCursorDisplayPos(this.editState.cursor, starts);\n\t\tthis.editState = {\n\t\t\t...this.editState,\n\t\t\tviewportOffset: ensureCursorVisible(\n\t\t\t\tcursorPos.line,\n\t\t\t\tthis.EDIT_VIEWPORT_HEIGHT,\n\t\t\t\tthis.editState.viewportOffset,\n\t\t\t),\n\t\t};\n\n\t\t// Header (truncate agent name to prevent overflow)\n\t\tconst fieldName = this.editMode === \"template\" ? \"task\" : this.editMode;\n\t\tconst rawAgentName = this.agentConfigs[this.editingStep!]?.name ?? \"unknown\";\n\t\tconst maxAgentLen = innerW - 30; // Reserve space for \" Editing X (Step/Task N: ) \"\n\t\tconst agentName = rawAgentName.length > maxAgentLen\n\t\t\t? rawAgentName.slice(0, maxAgentLen - 1) + \"…\"\n\t\t\t: rawAgentName;\n\t\t// Use mode-appropriate terminology\n\t\tconst stepLabel = this.mode === 'single' \n\t\t\t? agentName \n\t\t\t: this.mode === 'parallel' \n\t\t\t\t? `Task ${this.editingStep! + 1}: ${agentName}` \n\t\t\t\t: `Step ${this.editingStep! + 1}: ${agentName}`;\n\t\tconst headerText = ` Editing ${fieldName} (${stepLabel}) `;\n\t\tlines.push(this.renderHeader(headerText));\n\t\tlines.push(this.row(\"\"));\n\n\t\tconst editorLines = renderEditor(this.editState, textWidth, this.EDIT_VIEWPORT_HEIGHT);\n\t\tfor (const line of editorLines) {\n\t\t\tlines.push(this.row(` ${line}`));\n\t\t}\n\n\t\tconst linesBelow = wrapped.length - this.editState.viewportOffset - this.EDIT_VIEWPORT_HEIGHT;\n\t\tconst hasMore = linesBelow > 0;\n\t\tconst hasLess = this.editState.viewportOffset > 0;\n\t\tlet scrollInfo = \"\";\n\t\tif (hasLess) scrollInfo += \"↑\";\n\t\tif (hasMore) scrollInfo += `↓ ${linesBelow}+`;\n\n\t\tlines.push(this.row(\"\"));\n\n\t\tconst footerText = scrollInfo\n\t\t\t? ` [Esc] Done • [Ctrl+C] Discard • ${scrollInfo} `\n\t\t\t: \" [Esc] Done • [Ctrl+C] Discard \";\n\t\tlines.push(this.renderFooter(footerText));\n\n\t\treturn lines;\n\t}\n\n\t// ─────────────────────────────────────────────────────────────────────────────\n\t// Behavior helpers\n\t// ─────────────────────────────────────────────────────────────────────────────\n\n\t/** Get effective behavior for a step (with user overrides applied) */\n\tprivate getEffectiveBehavior(stepIndex: number): ResolvedStepBehavior {\n\t\tconst base = this.resolvedBehaviors[stepIndex]!;\n\t\tconst override = this.behaviorOverrides.get(stepIndex);\n\t\tif (!override) return base;\n\n\t\treturn {\n\t\t\toutput: override.output !== undefined ? override.output : base.output,\n\t\t\toutputMode: base.outputMode,\n\t\t\treads: override.reads !== undefined ? override.reads : base.reads,\n\t\t\tprogress: override.progress !== undefined ? override.progress : base.progress,\n\t\t\tskills: override.skills !== undefined ? override.skills : base.skills,\n\t\t\tmodel: override.model !== undefined ? override.model : base.model,\n\t\t};\n\t}\n\n\t/** Get the effective model for a step (override or agent default) */\n\tprivate getEffectiveModel(stepIndex: number): string {\n\t\tconst override = this.behaviorOverrides.get(stepIndex);\n\t\tif (override?.model) return this.resolveModelFullId(override.model);\n\n\t\tconst baseModel = this.resolvedBehaviors[stepIndex]?.model;\n\t\tif (baseModel) return this.resolveModelFullId(baseModel);\n\t\treturn \"default\";\n\t}\n\n\t/** Resolve a model name to its full provider/model format */\n\tprivate resolveModelFullId(modelName: string): string {\n\t\treturn resolveModelCandidate(modelName, this.availableModels, this.preferredProvider) ?? modelName;\n\t}\n\n\t/** Update a behavior override for a step */\n\tprivate updateBehavior(stepIndex: number, field: keyof BehaviorOverride, value: string | boolean | string[] | false): void {\n\t\tconst existing = this.behaviorOverrides.get(stepIndex) ?? {};\n\t\tthis.behaviorOverrides.set(stepIndex, { ...existing, [field]: value });\n\t}\n\n\tprivate showNotice(text: string, type: \"info\" | \"error\"): void {\n\t\tthis.noticeMessage = { text, type };\n\t\tif (this.noticeMessageTimer) clearTimeout(this.noticeMessageTimer);\n\t\tthis.noticeMessageTimer = setTimeout(() => {\n\t\t\tthis.noticeMessage = null;\n\t\t\tthis.noticeMessageTimer = null;\n\t\t\tthis.tui.requestRender();\n\t\t}, 2000);\n\t\tthis.tui.requestRender();\n\t}\n\n\thandleInput(data: string): void {\n\t\tif (this.editingStep !== null) {\n\t\t\tif (this.editMode === \"model\") {\n\t\t\t\tthis.handleModelSelectorInput(data);\n\t\t\t} else if (this.editMode === \"thinking\") {\n\t\t\t\tthis.handleThinkingSelectorInput(data);\n\t\t\t} else if (this.editMode === \"skills\") {\n\t\t\t\tthis.handleSkillSelectorInput(data);\n\t\t\t} else {\n\t\t\t\tthis.handleEditInput(data);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tif (matchesKey(data, \"escape\") || matchesKey(data, \"ctrl+c\")) {\n\t\t\tthis.done({ confirmed: false, templates: [], behaviorOverrides: [] });\n\t\t\treturn;\n\t\t}\n\n\t\tif (matchesKey(data, \"return\")) {\n\t\t\tconst overrides: (BehaviorOverride | undefined)[] = [];\n\t\t\tfor (let i = 0; i < this.agentConfigs.length; i++) {\n\t\t\t\toverrides.push(this.behaviorOverrides.get(i));\n\t\t\t}\n\t\t\tthis.done({ confirmed: true, templates: this.templates, behaviorOverrides: overrides, runInBackground: this.runInBackground });\n\t\t\treturn;\n\t\t}\n\n\t\tif (matchesKey(data, \"up\")) {\n\t\t\tthis.selectedStep = Math.max(0, this.selectedStep - 1);\n\t\t\tthis.tui.requestRender();\n\t\t\treturn;\n\t\t}\n\n\t\tif (matchesKey(data, \"down\")) {\n\t\t\tconst maxStep = Math.max(0, this.agentConfigs.length - 1);\n\t\t\tthis.selectedStep = Math.min(maxStep, this.selectedStep + 1);\n\t\t\tthis.tui.requestRender();\n\t\t\treturn;\n\t\t}\n\n\t\tif (data === \"e\") {\n\t\t\tthis.enterEditMode(\"template\");\n\t\t\treturn;\n\t\t}\n\n\t\tif (data === \"m\") {\n\t\t\tthis.enterModelSelector();\n\t\t\treturn;\n\t\t}\n\n\t\tif (data === \"t\") {\n\t\t\tthis.enterThinkingSelector();\n\t\t\treturn;\n\t\t}\n\n\t\tif (data === \"s\") {\n\t\t\tthis.editingStep = this.selectedStep;\n\t\t\tthis.editMode = \"skills\";\n\t\t\tthis.skillSearchQuery = \"\";\n\t\t\tthis.skillCursorIndex = 0;\n\t\t\tthis.filteredSkills = [...this.availableSkills];\n\t\t\tconst current = this.getEffectiveBehavior(this.selectedStep).skills;\n\t\t\tthis.skillSelectedNames.clear();\n\t\t\tif (current !== false && current.length > 0) {\n\t\t\t\tcurrent.forEach((skillName) => this.skillSelectedNames.add(skillName));\n\t\t\t}\n\t\t\tthis.tui.requestRender();\n\t\t\treturn;\n\t\t}\n\n\t\tif (data === \"w\" && this.mode !== 'parallel') {\n\t\t\tthis.enterEditMode(\"output\");\n\t\t\treturn;\n\t\t}\n\n\t\tif (data === \"r\" && this.mode === 'chain') {\n\t\t\tthis.enterEditMode(\"reads\");\n\t\t\treturn;\n\t\t}\n\n\t\tif (data === \"p\" && this.mode === 'chain') {\n\t\t\tconst anyEnabled = this.agentConfigs.some((_, i) => this.getEffectiveBehavior(i).progress);\n\t\t\tconst newState = !anyEnabled;\n\t\t\tfor (let i = 0; i < this.agentConfigs.length; i++) {\n\t\t\t\tthis.updateBehavior(i, \"progress\", newState);\n\t\t\t}\n\t\t\tthis.tui.requestRender();\n\t\t\treturn;\n\t\t}\n\n\t\tif (data === \"b\") {\n\t\t\tthis.runInBackground = !this.runInBackground;\n\t\t\tthis.tui.requestRender();\n\t\t\treturn;\n\t\t}\n\n\t}\n\n\tprivate enterEditMode(mode: EditMode): void {\n\t\tthis.editingStep = this.selectedStep;\n\t\tthis.editMode = mode;\n\t\tlet buffer = \"\";\n\n\t\tif (mode === \"template\") {\n\t\t\tconst template = this.templates[this.selectedStep] ?? \"\";\n\t\t\tbuffer = template.split(\"\\n\")[0] ?? \"\";\n\t\t} else if (mode === \"output\") {\n\t\t\tconst behavior = this.getEffectiveBehavior(this.selectedStep);\n\t\t\tbuffer = behavior.output === false ? \"\" : (behavior.output || \"\");\n\t\t} else if (mode === \"reads\") {\n\t\t\tconst behavior = this.getEffectiveBehavior(this.selectedStep);\n\t\t\tbuffer = behavior.reads === false ? \"\" : (behavior.reads?.join(\", \") || \"\");\n\t\t}\n\n\t\tthis.editState = createEditorState(buffer);\n\t\tthis.tui.requestRender();\n\t}\n\n\t/** Enter model selector mode */\n\tprivate enterModelSelector(): void {\n\t\tthis.editingStep = this.selectedStep;\n\t\tthis.editMode = \"model\";\n\t\tthis.modelSearchQuery = \"\";\n\t\tthis.modelSelectedIndex = 0;\n\t\tthis.filteredModels = [...this.availableModels];\n\t\tconst currentModel = splitThinkingSuffix(this.getEffectiveModel(this.selectedStep)).baseModel;\n\t\tconst currentIndex = this.filteredModels.findIndex((m) => m.fullId === currentModel || m.id === currentModel);\n\t\tif (currentIndex >= 0) {\n\t\t\tthis.modelSelectedIndex = currentIndex;\n\t\t}\n\n\t\tthis.tui.requestRender();\n\t}\n\n\t/** Filter models based on search query */\n\tprivate filterModels(): void {\n\t\tconst query = this.modelSearchQuery.toLowerCase();\n\t\tif (!query) {\n\t\t\tthis.filteredModels = [...this.availableModels];\n\t\t} else {\n\t\t\tthis.filteredModels = this.availableModels.filter((m) =>\n\t\t\t\tm.fullId.toLowerCase().includes(query) ||\n\t\t\t\tm.id.toLowerCase().includes(query) ||\n\t\t\t\tm.provider.toLowerCase().includes(query)\n\t\t\t);\n\t\t}\n\t\tthis.modelSelectedIndex = Math.min(this.modelSelectedIndex, Math.max(0, this.filteredModels.length - 1));\n\t}\n\n\tprivate handleModelSelectorInput(data: string): void {\n\t\tif (matchesKey(data, \"escape\") || matchesKey(data, \"ctrl+c\")) {\n\t\t\tthis.exitEditMode();\n\t\t\treturn;\n\t\t}\n\n\t\tif (matchesKey(data, \"return\")) {\n\t\t\tconst selected = this.filteredModels[this.modelSelectedIndex];\n\t\t\tif (selected) {\n\t\t\t\tconst { thinkingSuffix } = splitThinkingSuffix(this.getEffectiveModel(this.editingStep!));\n\t\t\t\tconst requestedLevel = thinkingSuffix.slice(1);\n\t\t\t\tconst selectedModel = findModelInfo(selected.fullId, this.availableModels, this.preferredProvider);\n\t\t\t\tconst suffix = getSupportedThinkingLevels(selectedModel).some((level) => level === requestedLevel) ? thinkingSuffix : \"\";\n\t\t\t\tthis.updateBehavior(this.editingStep!, \"model\", `${selected.fullId}${suffix}`);\n\t\t\t}\n\t\t\tthis.exitEditMode();\n\t\t\treturn;\n\t\t}\n\n\t\tif (matchesKey(data, \"up\")) {\n\t\t\tif (this.filteredModels.length > 0) {\n\t\t\t\tthis.modelSelectedIndex = this.modelSelectedIndex === 0\n\t\t\t\t\t? this.filteredModels.length - 1\n\t\t\t\t\t: this.modelSelectedIndex - 1;\n\t\t\t}\n\t\t\tthis.tui.requestRender();\n\t\t\treturn;\n\t\t}\n\n\t\tif (matchesKey(data, \"down\")) {\n\t\t\tif (this.filteredModels.length > 0) {\n\t\t\t\tthis.modelSelectedIndex = this.modelSelectedIndex === this.filteredModels.length - 1\n\t\t\t\t\t? 0\n\t\t\t\t\t: this.modelSelectedIndex + 1;\n\t\t\t}\n\t\t\tthis.tui.requestRender();\n\t\t\treturn;\n\t\t}\n\n\t\tif (matchesKey(data, \"backspace\")) {\n\t\t\tif (this.modelSearchQuery.length > 0) {\n\t\t\t\tthis.modelSearchQuery = this.modelSearchQuery.slice(0, -1);\n\t\t\t\tthis.filterModels();\n\t\t\t}\n\t\t\tthis.tui.requestRender();\n\t\t\treturn;\n\t\t}\n\n\t\tif (data.length === 1 && data.charCodeAt(0) >= 32) {\n\t\t\tthis.modelSearchQuery += data;\n\t\t\tthis.filterModels();\n\t\t\tthis.tui.requestRender();\n\t\t\treturn;\n\t\t}\n\t}\n\n\tprivate getAvailableThinkingLevels(stepIndex: number): ThinkingLevel[] {\n\t\treturn getSupportedThinkingLevels(findModelInfo(this.getEffectiveModel(stepIndex), this.availableModels, this.preferredProvider));\n\t}\n\n\t/** Enter thinking level selector mode */\n\tprivate enterThinkingSelector(): void {\n\t\tif (!this.getEffectiveBehavior(this.selectedStep).model) {\n\t\t\tthis.showNotice(\"Select a model first\", \"error\");\n\t\t\treturn;\n\t\t}\n\t\tthis.editingStep = this.selectedStep;\n\t\tthis.editMode = \"thinking\";\n\n\t\tconst levels = this.getAvailableThinkingLevels(this.selectedStep);\n\t\tconst { thinkingSuffix } = splitThinkingSuffix(this.getEffectiveModel(this.selectedStep));\n\t\tconst suffix = thinkingSuffix.slice(1);\n\t\tconst levelIdx = levels.findIndex((level) => level === suffix);\n\t\tthis.thinkingSelectedIndex = levelIdx >= 0 ? levelIdx : Math.max(0, levels.indexOf(\"off\"));\n\n\t\tthis.tui.requestRender();\n\t}\n\n\tprivate handleThinkingSelectorInput(data: string): void {\n\t\tif (matchesKey(data, \"escape\") || matchesKey(data, \"ctrl+c\")) {\n\t\t\tthis.exitEditMode();\n\t\t\treturn;\n\t\t}\n\n\t\tconst levels = this.getAvailableThinkingLevels(this.editingStep!);\n\t\tif (levels.length === 0) return;\n\n\t\tif (matchesKey(data, \"return\")) {\n\t\t\tconst selectedLevel = levels[this.thinkingSelectedIndex] ?? \"off\";\n\t\t\tthis.applyThinkingLevel(selectedLevel);\n\t\t\tthis.exitEditMode();\n\t\t\treturn;\n\t\t}\n\n\t\tif (matchesKey(data, \"up\")) {\n\t\t\tthis.thinkingSelectedIndex = this.thinkingSelectedIndex === 0\n\t\t\t\t? levels.length - 1\n\t\t\t\t: this.thinkingSelectedIndex - 1;\n\t\t\tthis.tui.requestRender();\n\t\t\treturn;\n\t\t}\n\n\t\tif (matchesKey(data, \"down\")) {\n\t\t\tthis.thinkingSelectedIndex = this.thinkingSelectedIndex === levels.length - 1\n\t\t\t\t? 0\n\t\t\t\t: this.thinkingSelectedIndex + 1;\n\t\t\tthis.tui.requestRender();\n\t\t\treturn;\n\t\t}\n\t}\n\n\t/** Apply thinking level to the current step's model */\n\tprivate applyThinkingLevel(level: ThinkingLevel): void {\n\t\tconst stepIndex = this.editingStep!;\n\t\tconst currentModel = this.getEffectiveBehavior(stepIndex).model;\n\t\tif (!currentModel) return;\n\n\t\tconst { baseModel } = splitThinkingSuffix(currentModel);\n\t\tconst newModel = level === \"off\" ? baseModel : `${baseModel}:${level}`;\n\t\tthis.updateBehavior(stepIndex, \"model\", newModel);\n\t}\n\n\tprivate filterSkills(): void {\n\t\tconst query = this.skillSearchQuery.toLowerCase();\n\t\tif (!query) {\n\t\t\tthis.filteredSkills = [...this.availableSkills];\n\t\t} else {\n\t\t\tthis.filteredSkills = this.availableSkills.filter((s) =>\n\t\t\t\ts.name.toLowerCase().includes(query) ||\n\t\t\t\t(s.description?.toLowerCase().includes(query) ?? false),\n\t\t\t);\n\t\t}\n\t\tthis.skillCursorIndex = Math.min(this.skillCursorIndex, Math.max(0, this.filteredSkills.length - 1));\n\t}\n\n\tprivate handleSkillSelectorInput(data: string): void {\n\t\tif (matchesKey(data, \"escape\") || matchesKey(data, \"ctrl+c\")) {\n\t\t\tthis.exitEditMode();\n\t\t\treturn;\n\t\t}\n\n\t\tif (matchesKey(data, \"return\")) {\n\t\t\tconst selected = [...this.skillSelectedNames];\n\t\t\tthis.updateBehavior(this.editingStep!, \"skills\", selected);\n\t\t\tthis.exitEditMode();\n\t\t\treturn;\n\t\t}\n\n\t\tif (data === \" \") {\n\t\t\tif (this.filteredSkills.length > 0) {\n\t\t\t\tconst skill = this.filteredSkills[this.skillCursorIndex];\n\t\t\t\tif (skill) {\n\t\t\t\t\tif (this.skillSelectedNames.has(skill.name)) {\n\t\t\t\t\t\tthis.skillSelectedNames.delete(skill.name);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthis.skillSelectedNames.add(skill.name);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tthis.tui.requestRender();\n\t\t\treturn;\n\t\t}\n\n\t\tif (matchesKey(data, \"up\")) {\n\t\t\tif (this.filteredSkills.length > 0) {\n\t\t\t\tthis.skillCursorIndex = this.skillCursorIndex === 0\n\t\t\t\t\t? this.filteredSkills.length - 1\n\t\t\t\t\t: this.skillCursorIndex - 1;\n\t\t\t}\n\t\t\tthis.tui.requestRender();\n\t\t\treturn;\n\t\t}\n\n\t\tif (matchesKey(data, \"down\")) {\n\t\t\tif (this.filteredSkills.length > 0) {\n\t\t\t\tthis.skillCursorIndex = this.skillCursorIndex === this.filteredSkills.length - 1\n\t\t\t\t\t? 0\n\t\t\t\t\t: this.skillCursorIndex + 1;\n\t\t\t}\n\t\t\tthis.tui.requestRender();\n\t\t\treturn;\n\t\t}\n\n\t\tif (matchesKey(data, \"backspace\")) {\n\t\t\tif (this.skillSearchQuery.length > 0) {\n\t\t\t\tthis.skillSearchQuery = this.skillSearchQuery.slice(0, -1);\n\t\t\t\tthis.filterSkills();\n\t\t\t}\n\t\t\tthis.tui.requestRender();\n\t\t\treturn;\n\t\t}\n\n\t\tif (data.length === 1 && data.charCodeAt(0) >= 32) {\n\t\t\tthis.skillSearchQuery += data;\n\t\t\tthis.filterSkills();\n\t\t\tthis.tui.requestRender();\n\t\t\treturn;\n\t\t}\n\t}\n\n\tprivate handleEditInput(data: string): void {\n\t\tconst textWidth = this.width - 4; // Must match render: innerW - 2 = (width - 2) - 2\n\t\tif (matchesKey(data, \"shift+up\") || matchesKey(data, \"pageup\")) {\n\t\t\tconst { lines: wrapped, starts } = wrapText(this.editState.buffer, textWidth);\n\t\t\tconst cursorPos = getCursorDisplayPos(this.editState.cursor, starts);\n\t\t\tconst targetLine = Math.max(0, cursorPos.line - this.EDIT_VIEWPORT_HEIGHT);\n\t\t\tconst targetCol = Math.min(cursorPos.col, wrapped[targetLine]?.length ?? 0);\n\t\t\tthis.editState = { ...this.editState, cursor: starts[targetLine] + targetCol };\n\t\t\tthis.tui.requestRender();\n\t\t\treturn;\n\t\t}\n\n\t\tif (matchesKey(data, \"shift+down\") || matchesKey(data, \"pagedown\")) {\n\t\t\tconst { lines: wrapped, starts } = wrapText(this.editState.buffer, textWidth);\n\t\t\tconst cursorPos = getCursorDisplayPos(this.editState.cursor, starts);\n\t\t\tconst targetLine = Math.min(wrapped.length - 1, cursorPos.line + this.EDIT_VIEWPORT_HEIGHT);\n\t\t\tconst targetCol = Math.min(cursorPos.col, wrapped[targetLine]?.length ?? 0);\n\t\t\tthis.editState = { ...this.editState, cursor: starts[targetLine] + targetCol };\n\t\t\tthis.tui.requestRender();\n\t\t\treturn;\n\t\t}\n\n\t\tif (matchesKey(data, \"tab\")) return;\n\n\t\tconst nextState = handleEditorInput(this.editState, data, textWidth);\n\t\tif (nextState) {\n\t\t\tthis.editState = nextState;\n\t\t\tthis.tui.requestRender();\n\t\t\treturn;\n\t\t}\n\n\t\tif (matchesKey(data, \"escape\")) {\n\t\t\tthis.saveEdit();\n\t\t\tthis.exitEditMode();\n\t\t\treturn;\n\t\t}\n\n\t\tif (matchesKey(data, \"ctrl+c\")) {\n\t\t\tthis.exitEditMode();\n\t\t\treturn;\n\t\t}\n\t}\n\n\tprivate saveEdit(): void {\n\t\tconst stepIndex = this.editingStep!;\n\n\t\tif (this.editMode === \"template\") {\n\t\t\t// For template, preserve other lines if they existed\n\t\t\tconst original = this.templates[stepIndex] ?? \"\";\n\t\t\tconst originalLines = original.split(\"\\n\");\n\t\t\toriginalLines[0] = this.editState.buffer;\n\t\t\tthis.templates[stepIndex] = originalLines.join(\"\\n\");\n\t\t} else if (this.editMode === \"output\") {\n\t\t\t// Capture OLD output before updating (for downstream propagation)\n\t\t\tconst oldBehavior = this.getEffectiveBehavior(stepIndex);\n\t\t\tconst oldOutput = typeof oldBehavior.output === \"string\" ? oldBehavior.output : null;\n\n\t\t\t// Empty string or whitespace means disable output\n\t\t\tconst trimmed = this.editState.buffer.trim();\n\t\t\tconst newOutput = trimmed === \"\" ? false : trimmed;\n\t\t\tthis.updateBehavior(stepIndex, \"output\", newOutput);\n\n\t\t\t// Propagate output filename change to downstream steps' reads\n\t\t\tif (oldOutput && typeof newOutput === \"string\" && oldOutput !== newOutput) {\n\t\t\t\tthis.propagateOutputChange(stepIndex, oldOutput, newOutput);\n\t\t\t}\n\t\t} else if (this.editMode === \"reads\") {\n\t\t\t// Parse comma-separated list, empty means disable reads\n\t\t\tconst trimmed = this.editState.buffer.trim();\n\t\t\tif (trimmed === \"\") {\n\t\t\t\tthis.updateBehavior(stepIndex, \"reads\", false);\n\t\t\t} else {\n\t\t\t\tconst files = trimmed.split(\",\").map(f => f.trim()).filter(f => f !== \"\");\n\t\t\t\tthis.updateBehavior(stepIndex, \"reads\", files.length > 0 ? files : false);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * When a step's output filename changes, update downstream steps that read from it.\n\t * This maintains the chain dependency automatically.\n\t */\n\tprivate propagateOutputChange(changedStepIndex: number, oldOutput: string, newOutput: string): void {\n\t\t// Check all downstream steps (steps that come after the changed step)\n\t\tfor (let i = changedStepIndex + 1; i < this.agentConfigs.length; i++) {\n\t\t\tconst behavior = this.getEffectiveBehavior(i);\n\t\t\t\n\t\t\t// Skip if reads is disabled or empty\n\t\t\tif (behavior.reads === false || !behavior.reads || behavior.reads.length === 0) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Check if this step reads the old output file\n\t\t\tconst readsArray = behavior.reads;\n\t\t\tconst oldIndex = readsArray.indexOf(oldOutput);\n\t\t\t\n\t\t\tif (oldIndex !== -1) {\n\t\t\t\t// Replace old filename with new filename in reads\n\t\t\t\tconst newReads = [...readsArray];\n\t\t\t\tnewReads[oldIndex] = newOutput;\n\t\t\t\tthis.updateBehavior(i, \"reads\", newReads);\n\t\t\t}\n\t\t}\n\t}\n\n\trender(_width: number): string[] {\n\t\tif (this.editingStep !== null) {\n\t\t\tif (this.editMode === \"model\") {\n\t\t\t\treturn this.renderModelSelector();\n\t\t\t}\n\t\t\tif (this.editMode === \"thinking\") {\n\t\t\t\treturn this.renderThinkingSelector();\n\t\t\t}\n\t\t\tif (this.editMode === \"skills\") {\n\t\t\t\treturn this.renderSkillSelector();\n\t\t\t}\n\t\t\treturn this.renderFullEditMode();\n\t\t}\n\t\t// Mode-based navigation rendering\n\t\tswitch (this.mode) {\n\t\t\tcase 'single': return this.renderSingleMode();\n\t\t\tcase 'parallel': return this.renderParallelMode();\n\t\t\tcase 'chain': return this.renderChainMode();\n\t\t}\n\t}\n\n\t/** Render the model selector view */\n\tprivate renderModelSelector(): string[] {\n\t\tconst th = this.theme;\n\t\tconst lines: string[] = [];\n\n\t\t// Header (mode-aware terminology)\n\t\tconst agentName = this.agentConfigs[this.editingStep!]?.name ?? \"unknown\";\n\t\tconst stepLabel = this.mode === 'single' \n\t\t\t? agentName \n\t\t\t: this.mode === 'parallel' \n\t\t\t\t? `Task ${this.editingStep! + 1}: ${agentName}` \n\t\t\t\t: `Step ${this.editingStep! + 1}: ${agentName}`;\n\t\tconst headerText = ` Select Model (${stepLabel}) `;\n\t\tlines.push(this.renderHeader(headerText));\n\t\tlines.push(this.row(\"\"));\n\n\t\tconst searchPrefix = th.fg(\"dim\", \"Search: \");\n\t\tconst cursor = \"\\x1b[7m \\x1b[27m\"; // Reverse video space for cursor\n\t\tconst searchDisplay = this.modelSearchQuery + cursor;\n\t\tlines.push(this.row(` ${searchPrefix}${searchDisplay}`));\n\t\tlines.push(this.row(\"\"));\n\n\t\tconst currentModel = this.getEffectiveModel(this.editingStep!);\n\t\tconst currentModelBase = splitThinkingSuffix(currentModel).baseModel;\n\t\tconst currentLabel = th.fg(\"dim\", \"Current: \");\n\t\tlines.push(this.row(` ${currentLabel}${th.fg(\"warning\", currentModel)}`));\n\t\tlines.push(this.row(\"\"));\n\n\t\tif (this.filteredModels.length === 0) {\n\t\t\tlines.push(this.row(` ${th.fg(\"dim\", \"No matching models\")}`));\n\t\t} else {\n\t\t\tconst maxVisible = this.MODEL_SELECTOR_HEIGHT;\n\t\t\tlet startIdx = 0;\n\n\t\t\tif (this.filteredModels.length > maxVisible) {\n\t\t\t\tstartIdx = Math.max(0, this.modelSelectedIndex - Math.floor(maxVisible / 2));\n\t\t\t\tstartIdx = Math.min(startIdx, this.filteredModels.length - maxVisible);\n\t\t\t}\n\n\t\t\tconst endIdx = Math.min(startIdx + maxVisible, this.filteredModels.length);\n\n\t\t\tif (startIdx > 0) {\n\t\t\t\tlines.push(this.row(` ${th.fg(\"dim\", ` ↑ ${startIdx} more`)}`));\n\t\t\t}\n\n\t\t\tfor (let i = startIdx; i < endIdx; i++) {\n\t\t\t\tconst model = this.filteredModels[i]!;\n\t\t\t\tconst isSelected = i === this.modelSelectedIndex;\n\t\t\t\tconst isCurrent = model.fullId === currentModelBase || model.id === currentModelBase;\n\t\t\t\tconst prefix = isSelected ? th.fg(\"accent\", \"→ \") : \" \";\n\t\t\t\tconst modelText = isSelected ? th.fg(\"accent\", model.id) : model.id;\n\t\t\t\tconst providerBadge = th.fg(\"dim\", ` [${model.provider}]`);\n\t\t\t\tconst currentBadge = isCurrent ? th.fg(\"success\", \" current\") : \"\";\n\n\t\t\t\tlines.push(this.row(` ${prefix}${modelText}${providerBadge}${currentBadge}`));\n\t\t\t}\n\n\t\t\tconst remaining = this.filteredModels.length - endIdx;\n\t\t\tif (remaining > 0) {\n\t\t\t\tlines.push(this.row(` ${th.fg(\"dim\", ` ↓ ${remaining} more`)}`));\n\t\t\t}\n\t\t}\n\n\t\tconst contentLines = lines.length;\n\t\tconst targetHeight = 18;\n\t\tfor (let i = contentLines; i < targetHeight; i++) {\n\t\t\tlines.push(this.row(\"\"));\n\t\t}\n\n\t\tconst footerText = \" [Enter] Select • [Esc] Cancel • Type to search \";\n\t\tlines.push(this.renderFooter(footerText));\n\n\t\treturn lines;\n\t}\n\n\t/** Render the thinking level selector view */\n\tprivate renderThinkingSelector(): string[] {\n\t\tconst th = this.theme;\n\t\tconst lines: string[] = [];\n\n\t\tconst agentName = this.agentConfigs[this.editingStep!]?.name ?? \"unknown\";\n\t\tconst stepLabel = this.mode === 'single' \n\t\t\t? agentName \n\t\t\t: this.mode === 'parallel' \n\t\t\t\t? `Task ${this.editingStep! + 1}: ${agentName}` \n\t\t\t\t: `Step ${this.editingStep! + 1}: ${agentName}`;\n\t\tconst headerText = ` Thinking Level (${stepLabel}) `;\n\t\tlines.push(this.renderHeader(headerText));\n\t\tlines.push(this.row(\"\"));\n\n\t\tconst currentModel = this.getEffectiveModel(this.editingStep!);\n\t\tconst currentLabel = th.fg(\"dim\", \"Model: \");\n\t\tlines.push(this.row(` ${currentLabel}${th.fg(\"accent\", currentModel)}`));\n\t\tlines.push(this.row(\"\"));\n\n\t\tlines.push(this.row(` ${th.fg(\"dim\", \"Select thinking level (extended thinking budget):\")}`));\n\t\tlines.push(this.row(\"\"));\n\n\t\tconst levelDescriptions: Record<ThinkingLevel, string> = {\n\t\t\t\"off\": \"No extended thinking\",\n\t\t\t\"minimal\": \"Brief reasoning\",\n\t\t\t\"low\": \"Light reasoning\",\n\t\t\t\"medium\": \"Moderate reasoning\",\n\t\t\t\"high\": \"Deep reasoning\",\n\t\t\t\"xhigh\": \"Maximum reasoning (ultrathink)\",\n\t\t\t\"max\": \"Maximum available reasoning\",\n\t\t};\n\n\t\tconst levels = this.getAvailableThinkingLevels(this.editingStep!);\n\t\tif (levels.length === 0) {\n\t\t\tlines.push(this.row(` ${th.fg(\"dim\", \"No supported thinking levels\")}`));\n\t\t} else {\n\t\t\tfor (let i = 0; i < levels.length; i++) {\n\t\t\t\tconst level = levels[i]!;\n\t\t\t\tconst isSelected = i === this.thinkingSelectedIndex;\n\t\t\t\tconst prefix = isSelected ? th.fg(\"accent\", \"→ \") : \" \";\n\t\t\t\tconst levelText = isSelected ? th.fg(\"accent\", level) : level;\n\t\t\t\tconst desc = th.fg(\"dim\", ` - ${levelDescriptions[level]}`);\n\t\t\t\tlines.push(this.row(` ${prefix}${levelText}${desc}`));\n\t\t\t}\n\t\t}\n\n\t\tconst contentLines = lines.length;\n\t\tconst targetHeight = 16;\n\t\tfor (let i = contentLines; i < targetHeight; i++) {\n\t\t\tlines.push(this.row(\"\"));\n\t\t}\n\n\t\tconst footerText = levels.length === 0\n\t\t\t? \" [Esc] Cancel \"\n\t\t\t: \" [Enter] Select • [Esc] Cancel • ↑↓ Navigate \";\n\t\tlines.push(this.renderFooter(footerText));\n\n\t\treturn lines;\n\t}\n\n\tprivate renderSkillSelector(): string[] {\n\t\tconst innerW = this.width - 2;\n\t\tconst th = this.theme;\n\t\tconst lines: string[] = [];\n\n\t\tconst agentName = this.agentConfigs[this.editingStep!]?.name ?? \"unknown\";\n\t\tconst stepLabel = this.mode === 'single'\n\t\t\t? agentName\n\t\t\t: this.mode === 'parallel'\n\t\t\t\t? `Task ${this.editingStep! + 1}: ${agentName}`\n\t\t\t\t: `Step ${this.editingStep! + 1}: ${agentName}`;\n\t\tlines.push(this.renderHeader(` Select Skills (${stepLabel}) `));\n\t\tlines.push(this.row(\"\"));\n\n\t\tconst cursor = \"\\x1b[7m \\x1b[27m\";\n\t\tlines.push(this.row(` ${th.fg(\"dim\", \"Search: \")}${this.skillSearchQuery}${cursor}`));\n\t\tlines.push(this.row(\"\"));\n\n\t\tconst selected = [...this.skillSelectedNames].join(\", \") || th.fg(\"dim\", \"(none)\");\n\t\tlines.push(this.row(` ${th.fg(\"dim\", \"Selected: \")}${truncateToWidth(selected, innerW - 12)}`));\n\t\tlines.push(this.row(\"\"));\n\n\t\tconst selectorHeight = 10;\n\t\tif (this.filteredSkills.length === 0) {\n\t\t\tlines.push(this.row(` ${th.fg(\"dim\", \"No matching skills\")}`));\n\t\t} else {\n\t\t\tlet startIdx = 0;\n\t\t\tif (this.filteredSkills.length > selectorHeight) {\n\t\t\t\tstartIdx = Math.max(0, this.skillCursorIndex - Math.floor(selectorHeight / 2));\n\t\t\t\tstartIdx = Math.min(startIdx, this.filteredSkills.length - selectorHeight);\n\t\t\t}\n\t\t\tconst endIdx = Math.min(startIdx + selectorHeight, this.filteredSkills.length);\n\n\t\t\tif (startIdx > 0) {\n\t\t\t\tlines.push(this.row(` ${th.fg(\"dim\", ` ↑ ${startIdx} more`)}`));\n\t\t\t}\n\n\t\t\tfor (let i = startIdx; i < endIdx; i++) {\n\t\t\t\tconst skill = this.filteredSkills[i]!;\n\t\t\t\tconst isCursor = i === this.skillCursorIndex;\n\t\t\t\tconst isSelected = this.skillSelectedNames.has(skill.name);\n\n\t\t\t\tconst prefix = isCursor ? th.fg(\"accent\", \"→ \") : \" \";\n\t\t\t\tconst checkbox = isSelected ? th.fg(\"success\", \"[x]\") : \"[ ]\";\n\t\t\t\tconst nameText = isCursor ? th.fg(\"accent\", skill.name) : skill.name;\n\t\t\t\tconst sourceBadge = th.fg(\"dim\", ` [${skill.source}]`);\n\t\t\t\tconst desc = skill.description\n\t\t\t\t\t? th.fg(\"dim\", ` - ${truncateToWidth(skill.description, 25)}`)\n\t\t\t\t\t: \"\";\n\n\t\t\t\tlines.push(this.row(` ${prefix}${checkbox} ${nameText}${sourceBadge}${desc}`));\n\t\t\t}\n\n\t\t\tconst remaining = this.filteredSkills.length - endIdx;\n\t\t\tif (remaining > 0) {\n\t\t\t\tlines.push(this.row(` ${th.fg(\"dim\", ` ↓ ${remaining} more`)}`));\n\t\t\t}\n\t\t}\n\n\t\tconst targetHeight = 18;\n\t\tfor (let i = lines.length; i < targetHeight; i++) {\n\t\t\tlines.push(this.row(\"\"));\n\t\t}\n\n\t\tlines.push(this.renderFooter(\" [Enter] Confirm • [Space] Toggle • [Esc] Cancel \"));\n\t\treturn lines;\n\t}\n\n\tprivate getShortcutHelpText(): string {\n\t\tconst th = this.theme;\n\t\tconst shortcut = (key: string, label: string) =>\n\t\t\tth.fg(\"accent\", `[${key}]`) + th.fg(\"dim\", label);\n\t\tconst backgroundState = this.runInBackground ? \"ON\" : \"OFF\";\n\t\tconst background = shortcut(\"b\", `Background:${backgroundState}`);\n\n\t\tswitch (this.mode) {\n\t\t\tcase 'single':\n\t\t\t\treturn ` ${shortcut(\"e\", \"Task\")} ${shortcut(\"m\", \"Model\")} ${shortcut(\"t\", \"Think\")} ${shortcut(\"w\", \"Output\")} ${shortcut(\"s\", \"Skills\")} ${background}`;\n\t\t\tcase 'parallel':\n\t\t\t\treturn ` ${shortcut(\"e\", \"Task\")} ${shortcut(\"m\", \"Model\")} ${shortcut(\"t\", \"Think\")} ${shortcut(\"s\", \"Skills\")} ${background}`;\n\t\t\tcase 'chain':\n\t\t\t\treturn ` ${shortcut(\"e\", \"Task\")} ${shortcut(\"m\", \"Model\")} ${shortcut(\"t\", \"Think\")} ${shortcut(\"w\", \"Output\")} ${shortcut(\"r\", \"Reads\")} ${shortcut(\"p\", \"Prog\")} ${shortcut(\"s\", \"Skills\")} ${background}`;\n\t\t}\n\t}\n\n\tprivate getFooterText(): string {\n\t\treturn this.mode === 'single'\n\t\t\t? \" [Enter] Run • [Esc] Cancel \"\n\t\t\t: \" [Enter] Run • [Esc] Cancel • [↑↓] Navigate \";\n\t}\n\n\tprivate appendNotice(lines: string[]): void {\n\t\tif (!this.noticeMessage) return;\n\t\tconst color = this.noticeMessage.type === \"error\" ? \"error\" : \"success\";\n\t\tlines.push(this.row(` ${this.theme.fg(color, this.noticeMessage.text)}`));\n\t}\n\n\tprivate renderSingleMode(): string[] {\n\t\tconst innerW = this.width - 2;\n\t\tconst th = this.theme;\n\t\tconst lines: string[] = [];\n\n\t\tconst agentName = this.agentConfigs[0]?.name ?? \"unknown\";\n\t\tconst maxHeaderLen = innerW - 4;\n\t\tconst headerText = ` Agent: ${truncateToWidth(agentName, maxHeaderLen - 9)} `;\n\t\tlines.push(this.renderHeader(headerText));\n\t\tlines.push(this.row(\"\"));\n\n\t\tconst config = this.agentConfigs[0]!;\n\t\tconst behavior = this.getEffectiveBehavior(0);\n\n\t\tconst stepLabel = config.name;\n\t\tlines.push(this.row(` ${th.fg(\"accent\", \"▶ \" + stepLabel)}`));\n\n\t\tconst template = (this.templates[0] ?? \"\").split(\"\\n\")[0] ?? \"\";\n\t\tconst taskLabel = th.fg(\"dim\", \"task: \");\n\t\tlines.push(this.row(` ${taskLabel}${truncateToWidth(template, innerW - 12)}`));\n\n\t\tconst effectiveModel = this.getEffectiveModel(0);\n\t\tconst override = this.behaviorOverrides.get(0);\n\t\tconst isOverridden = override?.model !== undefined;\n\t\tconst modelValue = isOverridden\n\t\t\t? th.fg(\"warning\", effectiveModel) + th.fg(\"dim\", \" ✎\")\n\t\t\t: effectiveModel;\n\t\tconst modelLabel = th.fg(\"dim\", \"model: \");\n\t\tlines.push(this.row(` ${modelLabel}${truncateToWidth(modelValue, innerW - 13)}`));\n\n\t\tconst writesValue = behavior.output === false\n\t\t\t? th.fg(\"dim\", \"(disabled)\")\n\t\t\t: (behavior.output || th.fg(\"dim\", \"(none)\"));\n\t\tconst writesLabel = th.fg(\"dim\", \"writes: \");\n\t\tlines.push(this.row(` ${writesLabel}${truncateToWidth(writesValue, innerW - 14)}`));\n\n\t\tconst skillsValue = behavior.skills === false\n\t\t\t? th.fg(\"dim\", \"(disabled)\")\n\t\t\t: (behavior.skills?.length ? behavior.skills.join(\", \") : th.fg(\"dim\", \"(none)\"));\n\t\tconst skillsLabel = th.fg(\"dim\", \"skills: \");\n\t\tlines.push(this.row(` ${skillsLabel}${truncateToWidth(skillsValue, innerW - 14)}`));\n\n\t\tlines.push(this.row(this.getShortcutHelpText()));\n\n\t\tthis.appendNotice(lines);\n\t\tlines.push(this.renderFooter(this.getFooterText()));\n\n\t\treturn lines;\n\t}\n\n\tprivate renderParallelMode(): string[] {\n\t\tconst innerW = this.width - 2;\n\t\tconst th = this.theme;\n\t\tconst lines: string[] = [];\n\n\t\tconst headerText = ` Parallel Tasks (${this.agentConfigs.length}) `;\n\t\tlines.push(this.renderHeader(headerText));\n\t\tlines.push(this.row(\"\"));\n\n\t\tfor (let i = 0; i < this.agentConfigs.length; i++) {\n\t\t\tconst config = this.agentConfigs[i]!;\n\t\t\tconst isSelected = i === this.selectedStep;\n\n\t\t\tconst color = isSelected ? \"accent\" : \"dim\";\n\t\t\tconst prefix = isSelected ? \"▶ \" : \" \";\n\t\t\tconst taskPrefix = `Task ${i + 1}: `;\n\t\t\tconst maxNameLen = innerW - 4 - prefix.length - taskPrefix.length;\n\t\t\tconst agentName = config.name.length > maxNameLen\n\t\t\t\t? config.name.slice(0, maxNameLen - 1) + \"…\"\n\t\t\t\t: config.name;\n\t\t\tconst taskLabel = `${taskPrefix}${agentName}`;\n\t\t\tlines.push(this.row(` ${th.fg(color, prefix + taskLabel)}`));\n\n\t\t\tconst template = (this.templates[i] ?? \"\").split(\"\\n\")[0] ?? \"\";\n\t\t\tconst taskTextLabel = th.fg(\"dim\", \"task: \");\n\t\t\tlines.push(this.row(` ${taskTextLabel}${truncateToWidth(template, innerW - 12)}`));\n\n\t\t\tconst effectiveModel = this.getEffectiveModel(i);\n\t\t\tconst override = this.behaviorOverrides.get(i);\n\t\t\tconst isOverridden = override?.model !== undefined;\n\t\t\tconst modelValue = isOverridden\n\t\t\t\t? th.fg(\"warning\", effectiveModel) + th.fg(\"dim\", \" ✎\")\n\t\t\t\t: effectiveModel;\n\t\t\tconst modelLabel = th.fg(\"dim\", \"model: \");\n\t\t\tlines.push(this.row(` ${modelLabel}${truncateToWidth(modelValue, innerW - 13)}`));\n\n\t\t\tconst behavior = this.getEffectiveBehavior(i);\n\t\t\tconst skillsValue = behavior.skills === false\n\t\t\t\t? th.fg(\"dim\", \"(disabled)\")\n\t\t\t\t: (behavior.skills?.length ? behavior.skills.join(\", \") : th.fg(\"dim\", \"(none)\"));\n\t\t\tconst skillsLabel = th.fg(\"dim\", \"skills: \");\n\t\t\tlines.push(this.row(` ${skillsLabel}${truncateToWidth(skillsValue, innerW - 14)}`));\n\n\t\t\tlines.push(this.row(\"\"));\n\t\t}\n\n\t\tif (this.agentConfigs.length > 0) lines.pop();\n\t\tlines.push(this.row(this.getShortcutHelpText()));\n\t\tthis.appendNotice(lines);\n\t\tlines.push(this.renderFooter(this.getFooterText()));\n\n\t\treturn lines;\n\t}\n\n\tprivate renderChainMode(): string[] {\n\t\tconst innerW = this.width - 2;\n\t\tconst th = this.theme;\n\t\tconst lines: string[] = [];\n\n\t\tconst chainLabel = this.agentConfigs.map((c) => c.name).join(\" → \");\n\t\tconst maxHeaderLen = innerW - 4;\n\t\tconst headerText = ` Chain: ${truncateToWidth(chainLabel, maxHeaderLen - 9)} `;\n\t\tlines.push(this.renderHeader(headerText));\n\n\t\tlines.push(this.row(\"\"));\n\n\t\tconst taskPreview = truncateToWidth(this.originalTask, innerW - 16);\n\t\tlines.push(this.row(` Original Task: ${taskPreview}`));\n\t\tconst chainDirPreview = truncateToWidth(this.chainDir ?? \"\", innerW - 12);\n\t\tlines.push(this.row(` Chain Dir: ${th.fg(\"dim\", chainDirPreview)}`));\n\n\t\tconst progressEnabled = this.agentConfigs.some((_, i) => this.getEffectiveBehavior(i).progress);\n\t\tconst progressValue = progressEnabled ? th.fg(\"success\", \"enabled\") : th.fg(\"dim\", \"disabled\");\n\t\tlines.push(this.row(` Progress: ${progressValue} ${th.fg(\"dim\", \"(press [p] to toggle)\")}`));\n\t\tlines.push(this.row(\"\"));\n\n\t\tfor (let i = 0; i < this.agentConfigs.length; i++) {\n\t\t\tconst config = this.agentConfigs[i]!;\n\t\t\tconst isSelected = i === this.selectedStep;\n\t\t\tconst behavior = this.getEffectiveBehavior(i);\n\n\t\t\tconst color = isSelected ? \"accent\" : \"dim\";\n\t\t\tconst prefix = isSelected ? \"▶ \" : \" \";\n\t\t\tconst stepPrefix = `Step ${i + 1}: `;\n\t\t\tconst maxNameLen = innerW - 4 - prefix.length - stepPrefix.length;\n\t\t\tconst agentName = config.name.length > maxNameLen\n\t\t\t\t? config.name.slice(0, maxNameLen - 1) + \"…\"\n\t\t\t\t: config.name;\n\t\t\tconst stepLabel = `${stepPrefix}${agentName}`;\n\t\t\tlines.push(\n\t\t\t\tthis.row(` ${th.fg(color, prefix + stepLabel)}`),\n\t\t\t);\n\n\t\t\tconst template = (this.templates[i] ?? \"\").split(\"\\n\")[0] ?? \"\";\n\t\t\tconst highlighted = template\n\t\t\t\t.replace(/\\{task\\}/g, th.fg(\"success\", \"{task}\"))\n\t\t\t\t.replace(/\\{previous\\}/g, th.fg(\"warning\", \"{previous}\"))\n\t\t\t\t.replace(/\\{chain_dir\\}/g, th.fg(\"accent\", \"{chain_dir}\"));\n\n\t\t\tconst templateLabel = th.fg(\"dim\", \"task: \");\n\t\t\tlines.push(this.row(` ${templateLabel}${truncateToWidth(highlighted, innerW - 12)}`));\n\n\t\t\tconst effectiveModel = this.getEffectiveModel(i);\n\t\t\tconst override = this.behaviorOverrides.get(i);\n\t\t\tconst isOverridden = override?.model !== undefined;\n\t\t\tconst modelValue = isOverridden\n\t\t\t\t? th.fg(\"warning\", effectiveModel) + th.fg(\"dim\", \" ✎\")\n\t\t\t\t: effectiveModel;\n\t\t\tconst modelLabel = th.fg(\"dim\", \"model: \");\n\t\t\tlines.push(this.row(` ${modelLabel}${truncateToWidth(modelValue, innerW - 13)}`));\n\n\t\t\tconst writesValue = behavior.output === false\n\t\t\t\t? th.fg(\"dim\", \"(disabled)\")\n\t\t\t\t: (behavior.output || th.fg(\"dim\", \"(none)\"));\n\t\t\tconst writesLabel = th.fg(\"dim\", \"writes: \");\n\t\t\tlines.push(this.row(` ${writesLabel}${truncateToWidth(writesValue, innerW - 14)}`));\n\n\t\t\tconst readsValue = behavior.reads === false\n\t\t\t\t? th.fg(\"dim\", \"(disabled)\")\n\t\t\t\t: (behavior.reads && behavior.reads.length > 0\n\t\t\t\t\t? behavior.reads.join(\", \")\n\t\t\t\t\t: th.fg(\"dim\", \"(none)\"));\n\t\t\tconst readsLabel = th.fg(\"dim\", \"reads: \");\n\t\t\tlines.push(this.row(` ${readsLabel}${truncateToWidth(readsValue, innerW - 13)}`));\n\n\t\t\tconst skillsValue = behavior.skills === false\n\t\t\t\t? th.fg(\"dim\", \"(disabled)\")\n\t\t\t\t: (behavior.skills?.length ? behavior.skills.join(\", \") : th.fg(\"dim\", \"(none)\"));\n\t\t\tconst skillsLabel = th.fg(\"dim\", \"skills: \");\n\t\t\tlines.push(this.row(` ${skillsLabel}${truncateToWidth(skillsValue, innerW - 14)}`));\n\n\t\t\tif (progressEnabled) {\n\t\t\t\tconst isFirstStep = i === 0;\n\t\t\t\tconst progressAction = isFirstStep \n\t\t\t\t\t? th.fg(\"success\", \"writes progress.md\")\n\t\t\t\t\t: th.fg(\"accent\", \"reads progress.md\");\n\t\t\t\tconst progressLabel = th.fg(\"dim\", \"progress: \");\n\t\t\t\tlines.push(this.row(` ${progressLabel}${progressAction}`));\n\t\t\t}\n\n\t\t\tif (i < this.agentConfigs.length - 1) {\n\t\t\t\tconst nextStepUsePrevious = (this.templates[i + 1] ?? \"\").includes(\"{previous}\");\n\t\t\t\tif (nextStepUsePrevious) {\n\t\t\t\t\tconst indicator = th.fg(\"dim\", \" ↳ response → \") + th.fg(\"warning\", \"{previous}\");\n\t\t\t\t\tlines.push(this.row(indicator));\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tlines.push(this.row(\"\"));\n\t\t}\n\n\t\tif (this.agentConfigs.length > 0) lines.pop();\n\t\tlines.push(this.row(this.getShortcutHelpText()));\n\t\tthis.appendNotice(lines);\n\t\tlines.push(this.renderFooter(this.getFooterText()));\n\n\t\treturn lines;\n\t}\n\n\tinvalidate(): void {}\n\tdispose(): void {\n\t\tif (this.noticeMessageTimer) clearTimeout(this.noticeMessageTimer);\n\t\tthis.noticeMessageTimer = null;\n\t}\n}\n"]}