@caupulican/pi-adaptative 0.81.38 → 0.81.40

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 (881) hide show
  1. package/CHANGELOG.md +131 -0
  2. package/README.md +2 -0
  3. package/dist/bundled-resources/extensions/tmux-agent-manager/README.md +18 -1
  4. package/dist/bundled-resources/extensions/tmux-agent-manager/dispatch-grant.d.ts +133 -0
  5. package/dist/bundled-resources/extensions/tmux-agent-manager/dispatch-grant.d.ts.map +1 -0
  6. package/dist/bundled-resources/extensions/tmux-agent-manager/dispatch-grant.js +198 -0
  7. package/dist/bundled-resources/extensions/tmux-agent-manager/dispatch-grant.js.map +1 -0
  8. package/dist/bundled-resources/extensions/tmux-agent-manager/dispatch-grant.ts +311 -0
  9. package/dist/bundled-resources/extensions/tmux-agent-manager/index.d.ts +31 -2
  10. package/dist/bundled-resources/extensions/tmux-agent-manager/index.d.ts.map +1 -1
  11. package/dist/bundled-resources/extensions/tmux-agent-manager/index.js +823 -79
  12. package/dist/bundled-resources/extensions/tmux-agent-manager/index.js.map +1 -1
  13. package/dist/bundled-resources/extensions/tmux-agent-manager/index.ts +1043 -86
  14. package/dist/bundled-resources/runtimes/pi-shell-engine/commands/__init__.py +43 -0
  15. package/dist/bundled-resources/runtimes/pi-shell-engine/commands/fs.py +270 -0
  16. package/dist/bundled-resources/runtimes/pi-shell-engine/commands/search.py +252 -0
  17. package/dist/bundled-resources/runtimes/pi-shell-engine/commands/strings.py +399 -0
  18. package/dist/bundled-resources/runtimes/pi-shell-engine/commands/text.py +575 -0
  19. package/dist/bundled-resources/runtimes/pi-shell-engine/context.py +52 -0
  20. package/dist/bundled-resources/runtimes/pi-shell-engine/errors.py +49 -0
  21. package/dist/bundled-resources/runtimes/pi-shell-engine/exec.py +734 -0
  22. package/dist/bundled-resources/runtimes/pi-shell-engine/expand.py +238 -0
  23. package/dist/bundled-resources/runtimes/pi-shell-engine/main.py +132 -0
  24. package/dist/bundled-resources/runtimes/pi-shell-engine/nodes.py +116 -0
  25. package/dist/bundled-resources/runtimes/pi-shell-engine/parser.py +287 -0
  26. package/dist/bundled-resources/runtimes/pi-shell-engine/proc.py +137 -0
  27. package/dist/bundled-resources/runtimes/pi-shell-engine/state.py +100 -0
  28. package/dist/bundled-resources/runtimes/pi-shell-engine/tokens.py +579 -0
  29. package/dist/bundled-resources/skills/tool-call-repair/SKILL.md +19 -9
  30. package/dist/bundled-resources/skills/tool-call-repair/references/failure-grammar.md +22 -5
  31. package/dist/bundled-resources/skills/tool-call-repair/references/repair-catalogue.md +7 -7
  32. package/dist/bundled-resources/skills/tool-call-repair/references/text-protocol-grammar.md +28 -7
  33. package/dist/cli/args.d.ts +5 -0
  34. package/dist/cli/args.d.ts.map +1 -1
  35. package/dist/cli/args.js +24 -0
  36. package/dist/cli/args.js.map +1 -1
  37. package/dist/core/agent-directory-layout.d.ts +15 -0
  38. package/dist/core/agent-directory-layout.d.ts.map +1 -0
  39. package/dist/core/agent-directory-layout.js +72 -0
  40. package/dist/core/agent-directory-layout.js.map +1 -0
  41. package/dist/core/agent-paths.d.ts +66 -0
  42. package/dist/core/agent-paths.d.ts.map +1 -0
  43. package/dist/core/agent-paths.js +170 -0
  44. package/dist/core/agent-paths.js.map +1 -0
  45. package/dist/core/agent-session-runtime.d.ts +52 -8
  46. package/dist/core/agent-session-runtime.d.ts.map +1 -1
  47. package/dist/core/agent-session-runtime.js +397 -79
  48. package/dist/core/agent-session-runtime.js.map +1 -1
  49. package/dist/core/agent-session-services.d.ts +1 -0
  50. package/dist/core/agent-session-services.d.ts.map +1 -1
  51. package/dist/core/agent-session-services.js +4 -3
  52. package/dist/core/agent-session-services.js.map +1 -1
  53. package/dist/core/agent-session.d.ts +255 -103
  54. package/dist/core/agent-session.d.ts.map +1 -1
  55. package/dist/core/agent-session.js +754 -1173
  56. package/dist/core/agent-session.js.map +1 -1
  57. package/dist/core/autonomy/approval-gate.d.ts +3 -2
  58. package/dist/core/autonomy/approval-gate.d.ts.map +1 -1
  59. package/dist/core/autonomy/approval-gate.js +6 -28
  60. package/dist/core/autonomy/approval-gate.js.map +1 -1
  61. package/dist/core/autonomy/bounded-completion.d.ts +1 -1
  62. package/dist/core/autonomy/bounded-completion.d.ts.map +1 -1
  63. package/dist/core/autonomy/bounded-completion.js +1 -1
  64. package/dist/core/autonomy/bounded-completion.js.map +1 -1
  65. package/dist/core/autonomy/contracts.d.ts +26 -9
  66. package/dist/core/autonomy/contracts.d.ts.map +1 -1
  67. package/dist/core/autonomy/contracts.js.map +1 -1
  68. package/dist/core/autonomy/foreground-envelope.d.ts +8 -2
  69. package/dist/core/autonomy/foreground-envelope.d.ts.map +1 -1
  70. package/dist/core/autonomy/foreground-envelope.js +8 -25
  71. package/dist/core/autonomy/foreground-envelope.js.map +1 -1
  72. package/dist/core/autonomy/gates.d.ts.map +1 -1
  73. package/dist/core/autonomy/gates.js +8 -10
  74. package/dist/core/autonomy/gates.js.map +1 -1
  75. package/dist/core/autonomy/lane-private-paths.d.ts +2 -0
  76. package/dist/core/autonomy/lane-private-paths.d.ts.map +1 -0
  77. package/dist/core/autonomy/lane-private-paths.js +16 -0
  78. package/dist/core/autonomy/lane-private-paths.js.map +1 -0
  79. package/dist/core/autonomy/lane-tool-surface.d.ts +10 -0
  80. package/dist/core/autonomy/lane-tool-surface.d.ts.map +1 -1
  81. package/dist/core/autonomy/lane-tool-surface.js +46 -8
  82. package/dist/core/autonomy/lane-tool-surface.js.map +1 -1
  83. package/dist/core/autonomy/lane-tracker.d.ts +24 -1
  84. package/dist/core/autonomy/lane-tracker.d.ts.map +1 -1
  85. package/dist/core/autonomy/lane-tracker.js +43 -1
  86. package/dist/core/autonomy/lane-tracker.js.map +1 -1
  87. package/dist/core/autonomy/risk-assessment.d.ts +1 -2
  88. package/dist/core/autonomy/risk-assessment.d.ts.map +1 -1
  89. package/dist/core/autonomy/risk-assessment.js +1 -123
  90. package/dist/core/autonomy/risk-assessment.js.map +1 -1
  91. package/dist/core/autonomy/session-lane-record.d.ts +4 -4
  92. package/dist/core/autonomy/session-lane-record.d.ts.map +1 -1
  93. package/dist/core/autonomy/session-lane-record.js +16 -29
  94. package/dist/core/autonomy/session-lane-record.js.map +1 -1
  95. package/dist/core/autonomy/telemetry-events.d.ts +1 -1
  96. package/dist/core/autonomy/telemetry-events.d.ts.map +1 -1
  97. package/dist/core/autonomy/telemetry-events.js +1 -1
  98. package/dist/core/autonomy/telemetry-events.js.map +1 -1
  99. package/dist/core/autonomy-telemetry.d.ts +7 -7
  100. package/dist/core/autonomy-telemetry.d.ts.map +1 -1
  101. package/dist/core/autonomy-telemetry.js +9 -9
  102. package/dist/core/autonomy-telemetry.js.map +1 -1
  103. package/dist/core/background-lane-controller.d.ts +90 -156
  104. package/dist/core/background-lane-controller.d.ts.map +1 -1
  105. package/dist/core/background-lane-controller.js +192 -968
  106. package/dist/core/background-lane-controller.js.map +1 -1
  107. package/dist/core/bash-execution-controller.d.ts +4 -0
  108. package/dist/core/bash-execution-controller.d.ts.map +1 -1
  109. package/dist/core/bash-execution-controller.js +7 -1
  110. package/dist/core/bash-execution-controller.js.map +1 -1
  111. package/dist/core/capability-contract.d.ts +4 -0
  112. package/dist/core/capability-contract.d.ts.map +1 -0
  113. package/dist/core/capability-contract.js +28 -0
  114. package/dist/core/capability-contract.js.map +1 -0
  115. package/dist/core/compaction-controller.d.ts +90 -0
  116. package/dist/core/compaction-controller.d.ts.map +1 -0
  117. package/dist/core/compaction-controller.js +452 -0
  118. package/dist/core/compaction-controller.js.map +1 -0
  119. package/dist/core/compaction-support.d.ts +24 -13
  120. package/dist/core/compaction-support.d.ts.map +1 -1
  121. package/dist/core/compaction-support.js +43 -7
  122. package/dist/core/compaction-support.js.map +1 -1
  123. package/dist/core/context/context-artifacts.d.ts +6 -2
  124. package/dist/core/context/context-artifacts.d.ts.map +1 -1
  125. package/dist/core/context/context-artifacts.js +34 -2
  126. package/dist/core/context/context-artifacts.js.map +1 -1
  127. package/dist/core/context/context-audit.d.ts +33 -1
  128. package/dist/core/context/context-audit.d.ts.map +1 -1
  129. package/dist/core/context/context-audit.js +31 -5
  130. package/dist/core/context/context-audit.js.map +1 -1
  131. package/dist/core/context/context-store-retention.d.ts +8 -2
  132. package/dist/core/context/context-store-retention.d.ts.map +1 -1
  133. package/dist/core/context/context-store-retention.js +158 -35
  134. package/dist/core/context/context-store-retention.js.map +1 -1
  135. package/dist/core/context/okf-memory.d.ts.map +1 -1
  136. package/dist/core/context/okf-memory.js +9 -11
  137. package/dist/core/context/okf-memory.js.map +1 -1
  138. package/dist/core/context/sqlite-runtime-index.d.ts.map +1 -1
  139. package/dist/core/context/sqlite-runtime-index.js +6 -8
  140. package/dist/core/context/sqlite-runtime-index.js.map +1 -1
  141. package/dist/core/context-gc.d.ts +3 -0
  142. package/dist/core/context-gc.d.ts.map +1 -1
  143. package/dist/core/context-gc.js +26 -6
  144. package/dist/core/context-gc.js.map +1 -1
  145. package/dist/core/context-pipeline.d.ts +26 -4
  146. package/dist/core/context-pipeline.d.ts.map +1 -1
  147. package/dist/core/context-pipeline.js +146 -18
  148. package/dist/core/context-pipeline.js.map +1 -1
  149. package/dist/core/cost-guard.d.ts +19 -3
  150. package/dist/core/cost-guard.d.ts.map +1 -1
  151. package/dist/core/cost-guard.js +18 -3
  152. package/dist/core/cost-guard.js.map +1 -1
  153. package/dist/core/default-tool-surface.d.ts.map +1 -1
  154. package/dist/core/default-tool-surface.js +1 -0
  155. package/dist/core/default-tool-surface.js.map +1 -1
  156. package/dist/core/delegation/managed-lane-controller.d.ts +34 -0
  157. package/dist/core/delegation/managed-lane-controller.d.ts.map +1 -0
  158. package/dist/core/delegation/managed-lane-controller.js +217 -0
  159. package/dist/core/delegation/managed-lane-controller.js.map +1 -0
  160. package/dist/core/delegation/session-worker-claim.d.ts +44 -0
  161. package/dist/core/delegation/session-worker-claim.d.ts.map +1 -0
  162. package/dist/core/delegation/session-worker-claim.js +98 -0
  163. package/dist/core/delegation/session-worker-claim.js.map +1 -0
  164. package/dist/core/delegation/worker-actions.d.ts +7 -5
  165. package/dist/core/delegation/worker-actions.d.ts.map +1 -1
  166. package/dist/core/delegation/worker-actions.js +20 -4
  167. package/dist/core/delegation/worker-actions.js.map +1 -1
  168. package/dist/core/delegation/worker-claim.d.ts +56 -0
  169. package/dist/core/delegation/worker-claim.d.ts.map +1 -0
  170. package/dist/core/delegation/worker-claim.js +239 -0
  171. package/dist/core/delegation/worker-claim.js.map +1 -0
  172. package/dist/core/delegation/worker-delegation-controller.d.ts +110 -0
  173. package/dist/core/delegation/worker-delegation-controller.d.ts.map +1 -0
  174. package/dist/core/delegation/worker-delegation-controller.js +861 -0
  175. package/dist/core/delegation/worker-delegation-controller.js.map +1 -0
  176. package/dist/core/delegation/worker-delegation-request.d.ts +11 -0
  177. package/dist/core/delegation/worker-delegation-request.d.ts.map +1 -0
  178. package/dist/core/delegation/worker-delegation-request.js +2 -0
  179. package/dist/core/delegation/worker-delegation-request.js.map +1 -0
  180. package/dist/core/delegation/worker-dispatch-scheduler.d.ts +40 -0
  181. package/dist/core/delegation/worker-dispatch-scheduler.d.ts.map +1 -0
  182. package/dist/core/delegation/worker-dispatch-scheduler.js +93 -0
  183. package/dist/core/delegation/worker-dispatch-scheduler.js.map +1 -0
  184. package/dist/core/delegation/worker-execution-policy.d.ts +62 -0
  185. package/dist/core/delegation/worker-execution-policy.d.ts.map +1 -0
  186. package/dist/core/delegation/worker-execution-policy.js +160 -0
  187. package/dist/core/delegation/worker-execution-policy.js.map +1 -0
  188. package/dist/core/delegation/worker-lane-projection.d.ts +14 -0
  189. package/dist/core/delegation/worker-lane-projection.d.ts.map +1 -0
  190. package/dist/core/delegation/worker-lane-projection.js +100 -0
  191. package/dist/core/delegation/worker-lane-projection.js.map +1 -0
  192. package/dist/core/delegation/worker-lifecycle.d.ts +97 -0
  193. package/dist/core/delegation/worker-lifecycle.d.ts.map +1 -0
  194. package/dist/core/delegation/worker-lifecycle.js +339 -0
  195. package/dist/core/delegation/worker-lifecycle.js.map +1 -0
  196. package/dist/core/delegation/worker-notification-coordinator.d.ts +36 -0
  197. package/dist/core/delegation/worker-notification-coordinator.d.ts.map +1 -0
  198. package/dist/core/delegation/worker-notification-coordinator.js +90 -0
  199. package/dist/core/delegation/worker-notification-coordinator.js.map +1 -0
  200. package/dist/core/delegation/worker-profile-resolver.d.ts +55 -0
  201. package/dist/core/delegation/worker-profile-resolver.d.ts.map +1 -0
  202. package/dist/core/delegation/worker-profile-resolver.js +113 -0
  203. package/dist/core/delegation/worker-profile-resolver.js.map +1 -0
  204. package/dist/core/delegation/worker-runner.d.ts +25 -14
  205. package/dist/core/delegation/worker-runner.d.ts.map +1 -1
  206. package/dist/core/delegation/worker-runner.js +122 -53
  207. package/dist/core/delegation/worker-runner.js.map +1 -1
  208. package/dist/core/delegation/worker-task-label.d.ts +3 -0
  209. package/dist/core/delegation/worker-task-label.d.ts.map +1 -0
  210. package/dist/core/delegation/worker-task-label.js +11 -0
  211. package/dist/core/delegation/worker-task-label.js.map +1 -0
  212. package/dist/core/doctor.d.ts +8 -0
  213. package/dist/core/doctor.d.ts.map +1 -1
  214. package/dist/core/doctor.js +32 -1
  215. package/dist/core/doctor.js.map +1 -1
  216. package/dist/core/extensions/index.d.ts +1 -1
  217. package/dist/core/extensions/index.d.ts.map +1 -1
  218. package/dist/core/extensions/index.js.map +1 -1
  219. package/dist/core/extensions/loader.d.ts +5 -1
  220. package/dist/core/extensions/loader.d.ts.map +1 -1
  221. package/dist/core/extensions/loader.js +68 -13
  222. package/dist/core/extensions/loader.js.map +1 -1
  223. package/dist/core/extensions/runner.d.ts.map +1 -1
  224. package/dist/core/extensions/runner.js +7 -0
  225. package/dist/core/extensions/runner.js.map +1 -1
  226. package/dist/core/extensions/storage.d.ts +8 -0
  227. package/dist/core/extensions/storage.d.ts.map +1 -0
  228. package/dist/core/extensions/storage.js +28 -0
  229. package/dist/core/extensions/storage.js.map +1 -0
  230. package/dist/core/extensions/types.d.ts +101 -1
  231. package/dist/core/extensions/types.d.ts.map +1 -1
  232. package/dist/core/extensions/types.js.map +1 -1
  233. package/dist/core/failure-corpus.d.ts +1 -15
  234. package/dist/core/failure-corpus.d.ts.map +1 -1
  235. package/dist/core/failure-corpus.js +9 -37
  236. package/dist/core/failure-corpus.js.map +1 -1
  237. package/dist/core/foreground-recovery-controller.d.ts +52 -0
  238. package/dist/core/foreground-recovery-controller.d.ts.map +1 -0
  239. package/dist/core/foreground-recovery-controller.js +111 -0
  240. package/dist/core/foreground-recovery-controller.js.map +1 -0
  241. package/dist/core/gateways/channel-provider.d.ts +3 -0
  242. package/dist/core/gateways/channel-provider.d.ts.map +1 -1
  243. package/dist/core/gateways/channel-provider.js +31 -6
  244. package/dist/core/gateways/channel-provider.js.map +1 -1
  245. package/dist/core/goal-loop-controller.d.ts +17 -1
  246. package/dist/core/goal-loop-controller.d.ts.map +1 -1
  247. package/dist/core/goal-loop-controller.js +79 -11
  248. package/dist/core/goal-loop-controller.js.map +1 -1
  249. package/dist/core/goals/goal-acceptance.d.ts +11 -0
  250. package/dist/core/goals/goal-acceptance.d.ts.map +1 -0
  251. package/dist/core/goals/goal-acceptance.js +25 -0
  252. package/dist/core/goals/goal-acceptance.js.map +1 -0
  253. package/dist/core/goals/goal-auto-continue-controller.d.ts +27 -0
  254. package/dist/core/goals/goal-auto-continue-controller.d.ts.map +1 -0
  255. package/dist/core/goals/goal-auto-continue-controller.js +93 -0
  256. package/dist/core/goals/goal-auto-continue-controller.js.map +1 -0
  257. package/dist/core/goals/goal-continuation-controller.d.ts +48 -2
  258. package/dist/core/goals/goal-continuation-controller.d.ts.map +1 -1
  259. package/dist/core/goals/goal-continuation-controller.js +87 -0
  260. package/dist/core/goals/goal-continuation-controller.js.map +1 -1
  261. package/dist/core/goals/goal-continuation-defaults.d.ts +39 -0
  262. package/dist/core/goals/goal-continuation-defaults.d.ts.map +1 -1
  263. package/dist/core/goals/goal-continuation-defaults.js +42 -0
  264. package/dist/core/goals/goal-continuation-defaults.js.map +1 -1
  265. package/dist/core/goals/goal-continuation-prompt.d.ts +5 -1
  266. package/dist/core/goals/goal-continuation-prompt.d.ts.map +1 -1
  267. package/dist/core/goals/goal-continuation-prompt.js +56 -15
  268. package/dist/core/goals/goal-continuation-prompt.js.map +1 -1
  269. package/dist/core/goals/goal-lifecycle.d.ts +20 -0
  270. package/dist/core/goals/goal-lifecycle.d.ts.map +1 -0
  271. package/dist/core/goals/goal-lifecycle.js +17 -0
  272. package/dist/core/goals/goal-lifecycle.js.map +1 -0
  273. package/dist/core/goals/goal-runtime-snapshot.d.ts +105 -4
  274. package/dist/core/goals/goal-runtime-snapshot.d.ts.map +1 -1
  275. package/dist/core/goals/goal-runtime-snapshot.js +91 -6
  276. package/dist/core/goals/goal-runtime-snapshot.js.map +1 -1
  277. package/dist/core/goals/goal-state.d.ts +90 -1
  278. package/dist/core/goals/goal-state.d.ts.map +1 -1
  279. package/dist/core/goals/goal-state.js +78 -15
  280. package/dist/core/goals/goal-state.js.map +1 -1
  281. package/dist/core/goals/goal-tool-core.d.ts +58 -2
  282. package/dist/core/goals/goal-tool-core.d.ts.map +1 -1
  283. package/dist/core/goals/goal-tool-core.js +82 -6
  284. package/dist/core/goals/goal-tool-core.js.map +1 -1
  285. package/dist/core/goals/session-goal-state.d.ts +13 -6
  286. package/dist/core/goals/session-goal-state.d.ts.map +1 -1
  287. package/dist/core/goals/session-goal-state.js +20 -28
  288. package/dist/core/goals/session-goal-state.js.map +1 -1
  289. package/dist/core/human-input.d.ts +100 -0
  290. package/dist/core/human-input.d.ts.map +1 -0
  291. package/dist/core/human-input.js +362 -0
  292. package/dist/core/human-input.js.map +1 -0
  293. package/dist/core/index.d.ts +9 -1
  294. package/dist/core/index.d.ts.map +1 -1
  295. package/dist/core/index.js +9 -1
  296. package/dist/core/index.js.map +1 -1
  297. package/dist/core/{telemetry.d.ts → install-telemetry.d.ts} +1 -1
  298. package/dist/core/install-telemetry.d.ts.map +1 -0
  299. package/dist/core/{telemetry.js → install-telemetry.js} +1 -1
  300. package/dist/core/install-telemetry.js.map +1 -0
  301. package/dist/core/keybindings.d.ts +27 -1
  302. package/dist/core/keybindings.d.ts.map +1 -1
  303. package/dist/core/keybindings.js +34 -8
  304. package/dist/core/keybindings.js.map +1 -1
  305. package/dist/core/learning/learning-audit.d.ts +2 -4
  306. package/dist/core/learning/learning-audit.d.ts.map +1 -1
  307. package/dist/core/learning/learning-audit.js +9 -29
  308. package/dist/core/learning/learning-audit.js.map +1 -1
  309. package/dist/core/learning/learning-gate.d.ts.map +1 -1
  310. package/dist/core/learning/learning-gate.js +1 -6
  311. package/dist/core/learning/learning-gate.js.map +1 -1
  312. package/dist/core/learning/observation-store.d.ts +12 -3
  313. package/dist/core/learning/observation-store.d.ts.map +1 -1
  314. package/dist/core/learning/observation-store.js +30 -15
  315. package/dist/core/learning/observation-store.js.map +1 -1
  316. package/dist/core/learning/session-learning-decision.d.ts +2 -4
  317. package/dist/core/learning/session-learning-decision.d.ts.map +1 -1
  318. package/dist/core/learning/session-learning-decision.js +9 -29
  319. package/dist/core/learning/session-learning-decision.js.map +1 -1
  320. package/dist/core/learning/skill-curator.d.ts +5 -1
  321. package/dist/core/learning/skill-curator.d.ts.map +1 -1
  322. package/dist/core/learning/skill-curator.js +21 -19
  323. package/dist/core/learning/skill-curator.js.map +1 -1
  324. package/dist/core/local-runtime-controller.d.ts +65 -3
  325. package/dist/core/local-runtime-controller.d.ts.map +1 -1
  326. package/dist/core/local-runtime-controller.js +186 -26
  327. package/dist/core/local-runtime-controller.js.map +1 -1
  328. package/dist/core/memory/providers/file-store.d.ts +1 -1
  329. package/dist/core/memory/providers/file-store.d.ts.map +1 -1
  330. package/dist/core/memory/providers/file-store.js +6 -6
  331. package/dist/core/memory/providers/file-store.js.map +1 -1
  332. package/dist/core/memory-controller.d.ts.map +1 -1
  333. package/dist/core/memory-controller.js +4 -4
  334. package/dist/core/memory-controller.js.map +1 -1
  335. package/dist/core/model-capability.d.ts +34 -0
  336. package/dist/core/model-capability.d.ts.map +1 -1
  337. package/dist/core/model-capability.js +43 -1
  338. package/dist/core/model-capability.js.map +1 -1
  339. package/dist/core/model-resolver.d.ts +1 -1
  340. package/dist/core/model-resolver.d.ts.map +1 -1
  341. package/dist/core/model-resolver.js +6 -9
  342. package/dist/core/model-resolver.js.map +1 -1
  343. package/dist/core/model-router/intent-classifier.d.ts +1 -4
  344. package/dist/core/model-router/intent-classifier.d.ts.map +1 -1
  345. package/dist/core/model-router/intent-classifier.js +1 -156
  346. package/dist/core/model-router/intent-classifier.js.map +1 -1
  347. package/dist/core/model-router/tool-escalation.d.ts +15 -0
  348. package/dist/core/model-router/tool-escalation.d.ts.map +1 -1
  349. package/dist/core/model-router/tool-escalation.js +23 -1
  350. package/dist/core/model-router/tool-escalation.js.map +1 -1
  351. package/dist/core/model-router-controller.d.ts +34 -7
  352. package/dist/core/model-router-controller.d.ts.map +1 -1
  353. package/dist/core/model-router-controller.js +95 -16
  354. package/dist/core/model-router-controller.js.map +1 -1
  355. package/dist/core/model-selection-controller.d.ts +4 -4
  356. package/dist/core/model-selection-controller.d.ts.map +1 -1
  357. package/dist/core/model-selection-controller.js.map +1 -1
  358. package/dist/core/model-tool-protocol.d.ts +23 -0
  359. package/dist/core/model-tool-protocol.d.ts.map +1 -0
  360. package/dist/core/model-tool-protocol.js +43 -0
  361. package/dist/core/model-tool-protocol.js.map +1 -0
  362. package/dist/core/models/adaptation-store.d.ts +10 -5
  363. package/dist/core/models/adaptation-store.d.ts.map +1 -1
  364. package/dist/core/models/adaptation-store.js +129 -81
  365. package/dist/core/models/adaptation-store.js.map +1 -1
  366. package/dist/core/models/default-model-suggestions.d.ts.map +1 -1
  367. package/dist/core/models/default-model-suggestions.js +17 -0
  368. package/dist/core/models/default-model-suggestions.js.map +1 -1
  369. package/dist/core/models/fitness-store.d.ts +4 -11
  370. package/dist/core/models/fitness-store.d.ts.map +1 -1
  371. package/dist/core/models/fitness-store.js +87 -51
  372. package/dist/core/models/fitness-store.js.map +1 -1
  373. package/dist/core/models/host-state-store.d.ts +34 -0
  374. package/dist/core/models/host-state-store.d.ts.map +1 -0
  375. package/dist/core/models/host-state-store.js +86 -0
  376. package/dist/core/models/host-state-store.js.map +1 -0
  377. package/dist/core/models/llamacpp-runtime.d.ts +180 -0
  378. package/dist/core/models/llamacpp-runtime.d.ts.map +1 -0
  379. package/dist/core/models/llamacpp-runtime.js +475 -0
  380. package/dist/core/models/llamacpp-runtime.js.map +1 -0
  381. package/dist/core/models/local-registration.d.ts +40 -0
  382. package/dist/core/models/local-registration.d.ts.map +1 -1
  383. package/dist/core/models/local-registration.js +94 -5
  384. package/dist/core/models/local-registration.js.map +1 -1
  385. package/dist/core/models/local-runtime.d.ts.map +1 -1
  386. package/dist/core/models/local-runtime.js +6 -5
  387. package/dist/core/models/local-runtime.js.map +1 -1
  388. package/dist/core/models/model-ref.d.ts +7 -0
  389. package/dist/core/models/model-ref.d.ts.map +1 -1
  390. package/dist/core/models/model-ref.js +26 -0
  391. package/dist/core/models/model-ref.js.map +1 -1
  392. package/dist/core/models/needle-runtime.d.ts +257 -0
  393. package/dist/core/models/needle-runtime.d.ts.map +1 -0
  394. package/dist/core/models/needle-runtime.js +519 -0
  395. package/dist/core/models/needle-runtime.js.map +1 -0
  396. package/dist/core/models/prism-llamacpp-lifecycle.d.ts +89 -0
  397. package/dist/core/models/prism-llamacpp-lifecycle.d.ts.map +1 -0
  398. package/dist/core/models/prism-llamacpp-lifecycle.js +121 -0
  399. package/dist/core/models/prism-llamacpp-lifecycle.js.map +1 -0
  400. package/dist/core/orchestration/agent-resume.d.ts +24 -0
  401. package/dist/core/orchestration/agent-resume.d.ts.map +1 -0
  402. package/dist/core/orchestration/agent-resume.js +90 -0
  403. package/dist/core/orchestration/agent-resume.js.map +1 -0
  404. package/dist/core/orchestration/capability-gateway.d.ts +61 -0
  405. package/dist/core/orchestration/capability-gateway.d.ts.map +1 -0
  406. package/dist/core/orchestration/capability-gateway.js +152 -0
  407. package/dist/core/orchestration/capability-gateway.js.map +1 -0
  408. package/dist/core/orchestration/contracts.d.ts +343 -0
  409. package/dist/core/orchestration/contracts.d.ts.map +1 -0
  410. package/dist/core/orchestration/contracts.js +135 -0
  411. package/dist/core/orchestration/contracts.js.map +1 -0
  412. package/dist/core/orchestration/delegation-ledger.d.ts +56 -0
  413. package/dist/core/orchestration/delegation-ledger.d.ts.map +1 -0
  414. package/dist/core/orchestration/delegation-ledger.js +197 -0
  415. package/dist/core/orchestration/delegation-ledger.js.map +1 -0
  416. package/dist/core/orchestration/event-store.d.ts +83 -0
  417. package/dist/core/orchestration/event-store.d.ts.map +1 -0
  418. package/dist/core/orchestration/event-store.js +560 -0
  419. package/dist/core/orchestration/event-store.js.map +1 -0
  420. package/dist/core/orchestration/lane-tool-manifests.d.ts +9 -0
  421. package/dist/core/orchestration/lane-tool-manifests.d.ts.map +1 -0
  422. package/dist/core/orchestration/lane-tool-manifests.js +37 -0
  423. package/dist/core/orchestration/lane-tool-manifests.js.map +1 -0
  424. package/dist/core/orchestration/model-binding.d.ts +15 -0
  425. package/dist/core/orchestration/model-binding.d.ts.map +1 -0
  426. package/dist/core/orchestration/model-binding.js +29 -0
  427. package/dist/core/orchestration/model-binding.js.map +1 -0
  428. package/dist/core/orchestration/policy-compiler.d.ts +49 -0
  429. package/dist/core/orchestration/policy-compiler.d.ts.map +1 -0
  430. package/dist/core/orchestration/policy-compiler.js +191 -0
  431. package/dist/core/orchestration/policy-compiler.js.map +1 -0
  432. package/dist/core/orchestration/policy-gate.d.ts +18 -0
  433. package/dist/core/orchestration/policy-gate.d.ts.map +1 -0
  434. package/dist/core/orchestration/policy-gate.js +24 -0
  435. package/dist/core/orchestration/policy-gate.js.map +1 -0
  436. package/dist/core/orchestration/profile-registry.d.ts +51 -0
  437. package/dist/core/orchestration/profile-registry.d.ts.map +1 -0
  438. package/dist/core/orchestration/profile-registry.js +393 -0
  439. package/dist/core/orchestration/profile-registry.js.map +1 -0
  440. package/dist/core/orchestration/profile-store.d.ts +31 -0
  441. package/dist/core/orchestration/profile-store.d.ts.map +1 -0
  442. package/dist/core/orchestration/profile-store.js +136 -0
  443. package/dist/core/orchestration/profile-store.js.map +1 -0
  444. package/dist/core/orchestration/risk-budget.d.ts +9 -0
  445. package/dist/core/orchestration/risk-budget.d.ts.map +1 -0
  446. package/dist/core/orchestration/risk-budget.js +48 -0
  447. package/dist/core/orchestration/risk-budget.js.map +1 -0
  448. package/dist/core/orchestration/task-runtime.d.ts +168 -0
  449. package/dist/core/orchestration/task-runtime.d.ts.map +1 -0
  450. package/dist/core/orchestration/task-runtime.js +1600 -0
  451. package/dist/core/orchestration/task-runtime.js.map +1 -0
  452. package/dist/core/orchestration/work-state-projection.d.ts +32 -0
  453. package/dist/core/orchestration/work-state-projection.d.ts.map +1 -0
  454. package/dist/core/orchestration/work-state-projection.js +116 -0
  455. package/dist/core/orchestration/work-state-projection.js.map +1 -0
  456. package/dist/core/orchestration/worker-execution-contract.d.ts +21 -0
  457. package/dist/core/orchestration/worker-execution-contract.d.ts.map +1 -0
  458. package/dist/core/orchestration/worker-execution-contract.js +179 -0
  459. package/dist/core/orchestration/worker-execution-contract.js.map +1 -0
  460. package/dist/core/orchestration/worker-result-adapter.d.ts +21 -0
  461. package/dist/core/orchestration/worker-result-adapter.d.ts.map +1 -0
  462. package/dist/core/orchestration/worker-result-adapter.js +109 -0
  463. package/dist/core/orchestration/worker-result-adapter.js.map +1 -0
  464. package/dist/core/package-manager.d.ts.map +1 -1
  465. package/dist/core/package-manager.js +11 -10
  466. package/dist/core/package-manager.js.map +1 -1
  467. package/dist/core/process-identity.d.ts +11 -0
  468. package/dist/core/process-identity.d.ts.map +1 -0
  469. package/dist/core/process-identity.js +26 -0
  470. package/dist/core/process-identity.js.map +1 -0
  471. package/dist/core/process-matrix/codes.d.ts +83 -0
  472. package/dist/core/process-matrix/codes.d.ts.map +1 -0
  473. package/dist/core/process-matrix/codes.js +15 -0
  474. package/dist/core/process-matrix/codes.js.map +1 -0
  475. package/dist/core/process-matrix/resume-launcher.d.ts +26 -0
  476. package/dist/core/process-matrix/resume-launcher.d.ts.map +1 -0
  477. package/dist/core/process-matrix/resume-launcher.js +62 -0
  478. package/dist/core/process-matrix/resume-launcher.js.map +1 -0
  479. package/dist/core/process-matrix/runtime.d.ts +78 -0
  480. package/dist/core/process-matrix/runtime.d.ts.map +1 -0
  481. package/dist/core/process-matrix/runtime.js +633 -0
  482. package/dist/core/process-matrix/runtime.js.map +1 -0
  483. package/dist/core/process-matrix/store.d.ts +25 -0
  484. package/dist/core/process-matrix/store.d.ts.map +1 -0
  485. package/dist/core/process-matrix/store.js +189 -0
  486. package/dist/core/process-matrix/store.js.map +1 -0
  487. package/dist/core/process-matrix/supervisor.d.ts +75 -0
  488. package/dist/core/process-matrix/supervisor.d.ts.map +1 -0
  489. package/dist/core/process-matrix/supervisor.js +161 -0
  490. package/dist/core/process-matrix/supervisor.js.map +1 -0
  491. package/dist/core/profile-registry.d.ts +7 -0
  492. package/dist/core/profile-registry.d.ts.map +1 -1
  493. package/dist/core/profile-registry.js +25 -7
  494. package/dist/core/profile-registry.js.map +1 -1
  495. package/dist/core/python-runtime.d.ts.map +1 -1
  496. package/dist/core/python-runtime.js +3 -3
  497. package/dist/core/python-runtime.js.map +1 -1
  498. package/dist/core/reflection-controller.d.ts +33 -9
  499. package/dist/core/reflection-controller.d.ts.map +1 -1
  500. package/dist/core/reflection-controller.js +217 -127
  501. package/dist/core/reflection-controller.js.map +1 -1
  502. package/dist/core/reload-blockers.d.ts +36 -0
  503. package/dist/core/reload-blockers.d.ts.map +1 -1
  504. package/dist/core/reload-blockers.js +50 -8
  505. package/dist/core/reload-blockers.js.map +1 -1
  506. package/dist/core/research/evidence-bundle.d.ts.map +1 -1
  507. package/dist/core/research/evidence-bundle.js +1 -6
  508. package/dist/core/research/evidence-bundle.js.map +1 -1
  509. package/dist/core/research/lane-model-resolver.d.ts +33 -0
  510. package/dist/core/research/lane-model-resolver.d.ts.map +1 -0
  511. package/dist/core/research/lane-model-resolver.js +65 -0
  512. package/dist/core/research/lane-model-resolver.js.map +1 -0
  513. package/dist/core/research/model-fitness-controller.d.ts +38 -0
  514. package/dist/core/research/model-fitness-controller.d.ts.map +1 -0
  515. package/dist/core/research/model-fitness-controller.js +106 -0
  516. package/dist/core/research/model-fitness-controller.js.map +1 -0
  517. package/dist/core/research/model-fitness.d.ts +1 -1
  518. package/dist/core/research/model-fitness.d.ts.map +1 -1
  519. package/dist/core/research/model-fitness.js +4 -4
  520. package/dist/core/research/model-fitness.js.map +1 -1
  521. package/dist/core/research/research-gate.d.ts.map +1 -1
  522. package/dist/core/research/research-gate.js +7 -7
  523. package/dist/core/research/research-gate.js.map +1 -1
  524. package/dist/core/research/research-lane-controller.d.ts +62 -0
  525. package/dist/core/research/research-lane-controller.d.ts.map +1 -0
  526. package/dist/core/research/research-lane-controller.js +292 -0
  527. package/dist/core/research/research-lane-controller.js.map +1 -0
  528. package/dist/core/research/session-evidence-bundle.d.ts +2 -4
  529. package/dist/core/research/session-evidence-bundle.d.ts.map +1 -1
  530. package/dist/core/research/session-evidence-bundle.js +10 -46
  531. package/dist/core/research/session-evidence-bundle.js.map +1 -1
  532. package/dist/core/resource-loader.d.ts.map +1 -1
  533. package/dist/core/resource-loader.js +15 -9
  534. package/dist/core/resource-loader.js.map +1 -1
  535. package/dist/core/risk-classifier.d.ts +6 -0
  536. package/dist/core/risk-classifier.d.ts.map +1 -0
  537. package/dist/core/risk-classifier.js +225 -0
  538. package/dist/core/risk-classifier.js.map +1 -0
  539. package/dist/core/runtime-builder.d.ts +76 -17
  540. package/dist/core/runtime-builder.d.ts.map +1 -1
  541. package/dist/core/runtime-builder.js +367 -124
  542. package/dist/core/runtime-builder.js.map +1 -1
  543. package/dist/core/scout-controller.d.ts +6 -0
  544. package/dist/core/scout-controller.d.ts.map +1 -1
  545. package/dist/core/scout-controller.js +66 -52
  546. package/dist/core/scout-controller.js.map +1 -1
  547. package/dist/core/sdk.d.ts +6 -3
  548. package/dist/core/sdk.d.ts.map +1 -1
  549. package/dist/core/sdk.js +64 -27
  550. package/dist/core/sdk.js.map +1 -1
  551. package/dist/core/session-analytics.d.ts.map +1 -1
  552. package/dist/core/session-analytics.js +28 -1
  553. package/dist/core/session-analytics.js.map +1 -1
  554. package/dist/core/session-image-store.d.ts +33 -0
  555. package/dist/core/session-image-store.d.ts.map +1 -0
  556. package/dist/core/session-image-store.js +283 -0
  557. package/dist/core/session-image-store.js.map +1 -0
  558. package/dist/core/session-role.d.ts +33 -0
  559. package/dist/core/session-role.d.ts.map +1 -0
  560. package/dist/core/session-role.js +58 -0
  561. package/dist/core/session-role.js.map +1 -0
  562. package/dist/core/session-snapshot.d.ts +34 -0
  563. package/dist/core/session-snapshot.d.ts.map +1 -0
  564. package/dist/core/session-snapshot.js +68 -0
  565. package/dist/core/session-snapshot.js.map +1 -0
  566. package/dist/core/session-supervision-runtime.d.ts +31 -0
  567. package/dist/core/session-supervision-runtime.d.ts.map +1 -0
  568. package/dist/core/session-supervision-runtime.js +97 -0
  569. package/dist/core/session-supervision-runtime.js.map +1 -0
  570. package/dist/core/settings-manager.d.ts +51 -7
  571. package/dist/core/settings-manager.d.ts.map +1 -1
  572. package/dist/core/settings-manager.js +168 -82
  573. package/dist/core/settings-manager.js.map +1 -1
  574. package/dist/core/system-prompt-builder.d.ts +12 -0
  575. package/dist/core/system-prompt-builder.d.ts.map +1 -1
  576. package/dist/core/system-prompt-builder.js +6 -0
  577. package/dist/core/system-prompt-builder.js.map +1 -1
  578. package/dist/core/system-prompt.d.ts +14 -1
  579. package/dist/core/system-prompt.d.ts.map +1 -1
  580. package/dist/core/system-prompt.js +20 -3
  581. package/dist/core/system-prompt.js.map +1 -1
  582. package/dist/core/tasks/session-task-state.d.ts +13 -6
  583. package/dist/core/tasks/session-task-state.d.ts.map +1 -1
  584. package/dist/core/tasks/session-task-state.js +20 -22
  585. package/dist/core/tasks/session-task-state.js.map +1 -1
  586. package/dist/core/tasks/task-contract-monitor.d.ts +45 -0
  587. package/dist/core/tasks/task-contract-monitor.d.ts.map +1 -0
  588. package/dist/core/tasks/task-contract-monitor.js +56 -0
  589. package/dist/core/tasks/task-contract-monitor.js.map +1 -0
  590. package/dist/core/tasks/task-projection.d.ts +20 -0
  591. package/dist/core/tasks/task-projection.d.ts.map +1 -0
  592. package/dist/core/tasks/task-projection.js +47 -0
  593. package/dist/core/tasks/task-projection.js.map +1 -0
  594. package/dist/core/tasks/task-state.d.ts +12 -0
  595. package/dist/core/tasks/task-state.d.ts.map +1 -1
  596. package/dist/core/tasks/task-state.js +56 -12
  597. package/dist/core/tasks/task-state.js.map +1 -1
  598. package/dist/core/tool-capability-policy.d.ts +13 -0
  599. package/dist/core/tool-capability-policy.d.ts.map +1 -0
  600. package/dist/core/tool-capability-policy.js +85 -0
  601. package/dist/core/tool-capability-policy.js.map +1 -0
  602. package/dist/core/tool-gate-controller.d.ts.map +1 -1
  603. package/dist/core/tool-gate-controller.js +5 -0
  604. package/dist/core/tool-gate-controller.js.map +1 -1
  605. package/dist/core/tool-protocol-controller.d.ts +91 -0
  606. package/dist/core/tool-protocol-controller.d.ts.map +1 -0
  607. package/dist/core/tool-protocol-controller.js +512 -0
  608. package/dist/core/tool-protocol-controller.js.map +1 -0
  609. package/dist/core/tool-recovery-log-records.d.ts +6 -0
  610. package/dist/core/tool-recovery-log-records.d.ts.map +1 -1
  611. package/dist/core/tool-recovery-log-records.js +13 -31
  612. package/dist/core/tool-recovery-log-records.js.map +1 -1
  613. package/dist/core/tool-selection/promotion.d.ts +54 -0
  614. package/dist/core/tool-selection/promotion.d.ts.map +1 -0
  615. package/dist/core/tool-selection/promotion.js +81 -0
  616. package/dist/core/tool-selection/promotion.js.map +1 -0
  617. package/dist/core/tool-selection/tool-performance-store.d.ts +39 -6
  618. package/dist/core/tool-selection/tool-performance-store.d.ts.map +1 -1
  619. package/dist/core/tool-selection/tool-performance-store.js +153 -110
  620. package/dist/core/tool-selection/tool-performance-store.js.map +1 -1
  621. package/dist/core/tool-selection/tool-selection-controller.d.ts +45 -0
  622. package/dist/core/tool-selection/tool-selection-controller.d.ts.map +1 -1
  623. package/dist/core/tool-selection/tool-selection-controller.js +96 -0
  624. package/dist/core/tool-selection/tool-selection-controller.js.map +1 -1
  625. package/dist/core/tools/ask-question.d.ts +124 -0
  626. package/dist/core/tools/ask-question.d.ts.map +1 -0
  627. package/dist/core/tools/ask-question.js +693 -0
  628. package/dist/core/tools/ask-question.js.map +1 -0
  629. package/dist/core/tools/bash.d.ts +18 -0
  630. package/dist/core/tools/bash.d.ts.map +1 -1
  631. package/dist/core/tools/bash.js +95 -16
  632. package/dist/core/tools/bash.js.map +1 -1
  633. package/dist/core/tools/delegate-status.d.ts +44 -2
  634. package/dist/core/tools/delegate-status.d.ts.map +1 -1
  635. package/dist/core/tools/delegate-status.js +214 -19
  636. package/dist/core/tools/delegate-status.js.map +1 -1
  637. package/dist/core/tools/delegate.d.ts +15 -13
  638. package/dist/core/tools/delegate.d.ts.map +1 -1
  639. package/dist/core/tools/delegate.js +124 -22
  640. package/dist/core/tools/delegate.js.map +1 -1
  641. package/dist/core/tools/extensionify.d.ts.map +1 -1
  642. package/dist/core/tools/extensionify.js +1 -0
  643. package/dist/core/tools/extensionify.js.map +1 -1
  644. package/dist/core/tools/goal.d.ts +94 -3
  645. package/dist/core/tools/goal.d.ts.map +1 -1
  646. package/dist/core/tools/goal.js +165 -15
  647. package/dist/core/tools/goal.js.map +1 -1
  648. package/dist/core/tools/model-fitness.d.ts +7 -0
  649. package/dist/core/tools/model-fitness.d.ts.map +1 -1
  650. package/dist/core/tools/model-fitness.js +4 -4
  651. package/dist/core/tools/model-fitness.js.map +1 -1
  652. package/dist/core/tools/orchestration-panel.d.ts +43 -0
  653. package/dist/core/tools/orchestration-panel.d.ts.map +1 -0
  654. package/dist/core/tools/orchestration-panel.js +168 -0
  655. package/dist/core/tools/orchestration-panel.js.map +1 -0
  656. package/dist/core/tools/run-process.d.ts +26 -0
  657. package/dist/core/tools/run-process.d.ts.map +1 -0
  658. package/dist/core/tools/run-process.js +138 -0
  659. package/dist/core/tools/run-process.js.map +1 -0
  660. package/dist/core/tools/shell-contract-router.d.ts +6 -1
  661. package/dist/core/tools/shell-contract-router.d.ts.map +1 -1
  662. package/dist/core/tools/shell-contract-router.js +23 -2
  663. package/dist/core/tools/shell-contract-router.js.map +1 -1
  664. package/dist/core/tools/shell-session.d.ts +89 -0
  665. package/dist/core/tools/shell-session.d.ts.map +1 -0
  666. package/dist/core/tools/shell-session.js +432 -0
  667. package/dist/core/tools/shell-session.js.map +1 -0
  668. package/dist/core/tools/task-steps.d.ts +6 -0
  669. package/dist/core/tools/task-steps.d.ts.map +1 -1
  670. package/dist/core/tools/task-steps.js +165 -8
  671. package/dist/core/tools/task-steps.js.map +1 -1
  672. package/dist/core/tools/tmux-dispatch.d.ts +84 -0
  673. package/dist/core/tools/tmux-dispatch.d.ts.map +1 -0
  674. package/dist/core/tools/tmux-dispatch.js +91 -0
  675. package/dist/core/tools/tmux-dispatch.js.map +1 -0
  676. package/dist/core/tools/windows-shell-engine.d.ts +42 -0
  677. package/dist/core/tools/windows-shell-engine.d.ts.map +1 -0
  678. package/dist/core/tools/windows-shell-engine.js +151 -0
  679. package/dist/core/tools/windows-shell-engine.js.map +1 -0
  680. package/dist/core/tools/windows-shell-state.d.ts +40 -0
  681. package/dist/core/tools/windows-shell-state.d.ts.map +1 -0
  682. package/dist/core/tools/windows-shell-state.js +59 -0
  683. package/dist/core/tools/windows-shell-state.js.map +1 -0
  684. package/dist/core/tools/worktree-sync.d.ts +24 -0
  685. package/dist/core/tools/worktree-sync.d.ts.map +1 -0
  686. package/dist/core/tools/worktree-sync.js +338 -0
  687. package/dist/core/tools/worktree-sync.js.map +1 -0
  688. package/dist/core/trust-manager.d.ts +4 -1
  689. package/dist/core/trust-manager.d.ts.map +1 -1
  690. package/dist/core/trust-manager.js +20 -2
  691. package/dist/core/trust-manager.js.map +1 -1
  692. package/dist/core/util/atomic-file.d.ts +55 -0
  693. package/dist/core/util/atomic-file.d.ts.map +1 -0
  694. package/dist/core/util/atomic-file.js +255 -0
  695. package/dist/core/util/atomic-file.js.map +1 -0
  696. package/dist/core/util/bounded-jsonl.d.ts +10 -0
  697. package/dist/core/util/bounded-jsonl.d.ts.map +1 -0
  698. package/dist/core/util/bounded-jsonl.js +75 -0
  699. package/dist/core/util/bounded-jsonl.js.map +1 -0
  700. package/dist/core/util/minimatch-cache.d.ts +33 -0
  701. package/dist/core/util/minimatch-cache.d.ts.map +1 -0
  702. package/dist/core/util/minimatch-cache.js +0 -0
  703. package/dist/core/util/minimatch-cache.js.map +1 -0
  704. package/dist/core/util/value-guards.d.ts +5 -0
  705. package/dist/core/util/value-guards.d.ts.map +1 -0
  706. package/dist/core/util/value-guards.js +12 -0
  707. package/dist/core/util/value-guards.js.map +1 -0
  708. package/dist/core/worktree-sync/codes.d.ts +227 -0
  709. package/dist/core/worktree-sync/codes.d.ts.map +1 -0
  710. package/dist/core/worktree-sync/codes.js +14 -0
  711. package/dist/core/worktree-sync/codes.js.map +1 -0
  712. package/dist/core/worktree-sync/git-engine.d.ts +156 -0
  713. package/dist/core/worktree-sync/git-engine.d.ts.map +1 -0
  714. package/dist/core/worktree-sync/git-engine.js +1191 -0
  715. package/dist/core/worktree-sync/git-engine.js.map +1 -0
  716. package/dist/core/worktree-sync/lane-gate.d.ts +75 -0
  717. package/dist/core/worktree-sync/lane-gate.d.ts.map +1 -0
  718. package/dist/core/worktree-sync/lane-gate.js +360 -0
  719. package/dist/core/worktree-sync/lane-gate.js.map +1 -0
  720. package/dist/core/worktree-sync/runtime.d.ts +47 -0
  721. package/dist/core/worktree-sync/runtime.d.ts.map +1 -0
  722. package/dist/core/worktree-sync/runtime.js +96 -0
  723. package/dist/core/worktree-sync/runtime.js.map +1 -0
  724. package/dist/core/worktree-sync/store.d.ts +69 -0
  725. package/dist/core/worktree-sync/store.d.ts.map +1 -0
  726. package/dist/core/worktree-sync/store.js +252 -0
  727. package/dist/core/worktree-sync/store.js.map +1 -0
  728. package/dist/core/worktree-sync/watcher.d.ts +29 -0
  729. package/dist/core/worktree-sync/watcher.d.ts.map +1 -0
  730. package/dist/core/worktree-sync/watcher.js +93 -0
  731. package/dist/core/worktree-sync/watcher.js.map +1 -0
  732. package/dist/index.d.ts +13 -3
  733. package/dist/index.d.ts.map +1 -1
  734. package/dist/index.js +12 -2
  735. package/dist/index.js.map +1 -1
  736. package/dist/main.d.ts.map +1 -1
  737. package/dist/main.js +179 -1
  738. package/dist/main.js.map +1 -1
  739. package/dist/migrations.d.ts +7 -0
  740. package/dist/migrations.d.ts.map +1 -1
  741. package/dist/migrations.js +197 -1
  742. package/dist/migrations.js.map +1 -1
  743. package/dist/modes/interactive/auto-learn-controller.d.ts +16 -1
  744. package/dist/modes/interactive/auto-learn-controller.d.ts.map +1 -1
  745. package/dist/modes/interactive/auto-learn-controller.js +50 -8
  746. package/dist/modes/interactive/auto-learn-controller.js.map +1 -1
  747. package/dist/modes/interactive/clipboard-input.d.ts +7 -1
  748. package/dist/modes/interactive/clipboard-input.d.ts.map +1 -1
  749. package/dist/modes/interactive/clipboard-input.js +44 -6
  750. package/dist/modes/interactive/clipboard-input.js.map +1 -1
  751. package/dist/modes/interactive/components/profile-resource-editor.d.ts.map +1 -1
  752. package/dist/modes/interactive/components/profile-resource-editor.js +23 -1
  753. package/dist/modes/interactive/components/profile-resource-editor.js.map +1 -1
  754. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  755. package/dist/modes/interactive/components/settings-selector.js +2 -2
  756. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  757. package/dist/modes/interactive/config-backup.d.ts.map +1 -1
  758. package/dist/modes/interactive/config-backup.js +5 -4
  759. package/dist/modes/interactive/config-backup.js.map +1 -1
  760. package/dist/modes/interactive/extension-ui-host.d.ts +14 -0
  761. package/dist/modes/interactive/extension-ui-host.d.ts.map +1 -1
  762. package/dist/modes/interactive/extension-ui-host.js +76 -15
  763. package/dist/modes/interactive/extension-ui-host.js.map +1 -1
  764. package/dist/modes/interactive/interactive-mode.d.ts +4 -0
  765. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  766. package/dist/modes/interactive/interactive-mode.js +67 -1
  767. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  768. package/dist/modes/interactive/key-handlers.d.ts.map +1 -1
  769. package/dist/modes/interactive/key-handlers.js +1 -1
  770. package/dist/modes/interactive/key-handlers.js.map +1 -1
  771. package/dist/modes/interactive/local-model-commands.d.ts +43 -0
  772. package/dist/modes/interactive/local-model-commands.d.ts.map +1 -1
  773. package/dist/modes/interactive/local-model-commands.js +290 -3
  774. package/dist/modes/interactive/local-model-commands.js.map +1 -1
  775. package/dist/modes/interactive/profile-menu-controller.d.ts.map +1 -1
  776. package/dist/modes/interactive/profile-menu-controller.js +7 -7
  777. package/dist/modes/interactive/profile-menu-controller.js.map +1 -1
  778. package/dist/modes/interactive/report-commands.d.ts.map +1 -1
  779. package/dist/modes/interactive/report-commands.js +21 -0
  780. package/dist/modes/interactive/report-commands.js.map +1 -1
  781. package/dist/modes/interactive/session-flow-commands.d.ts +17 -2
  782. package/dist/modes/interactive/session-flow-commands.d.ts.map +1 -1
  783. package/dist/modes/interactive/session-flow-commands.js +74 -21
  784. package/dist/modes/interactive/session-flow-commands.js.map +1 -1
  785. package/dist/modes/interactive/session-io-commands.d.ts.map +1 -1
  786. package/dist/modes/interactive/session-io-commands.js +16 -6
  787. package/dist/modes/interactive/session-io-commands.js.map +1 -1
  788. package/dist/modes/interactive/session-replacement-errors.d.ts +7 -0
  789. package/dist/modes/interactive/session-replacement-errors.d.ts.map +1 -0
  790. package/dist/modes/interactive/session-replacement-errors.js +15 -0
  791. package/dist/modes/interactive/session-replacement-errors.js.map +1 -0
  792. package/dist/modes/interactive/startup-checks.d.ts.map +1 -1
  793. package/dist/modes/interactive/startup-checks.js +1 -1
  794. package/dist/modes/interactive/startup-checks.js.map +1 -1
  795. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  796. package/dist/modes/rpc/rpc-mode.js +82 -23
  797. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  798. package/dist/modes/rpc/rpc-types.d.ts +15 -0
  799. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  800. package/dist/modes/rpc/rpc-types.js +138 -1
  801. package/dist/modes/rpc/rpc-types.js.map +1 -1
  802. package/dist/utils/clipboard-image.d.ts.map +1 -1
  803. package/dist/utils/clipboard-image.js +2 -13
  804. package/dist/utils/clipboard-image.js.map +1 -1
  805. package/dist/utils/clipboard-native.d.ts +1 -0
  806. package/dist/utils/clipboard-native.d.ts.map +1 -1
  807. package/dist/utils/clipboard-native.js.map +1 -1
  808. package/dist/utils/clipboard.d.ts +1 -0
  809. package/dist/utils/clipboard.d.ts.map +1 -1
  810. package/dist/utils/clipboard.js +52 -1
  811. package/dist/utils/clipboard.js.map +1 -1
  812. package/dist/utils/fs-watch.d.ts +11 -0
  813. package/dist/utils/fs-watch.d.ts.map +1 -1
  814. package/dist/utils/fs-watch.js +20 -2
  815. package/dist/utils/fs-watch.js.map +1 -1
  816. package/dist/utils/highlight-js-languages.d.ts +4 -0
  817. package/dist/utils/highlight-js-languages.d.ts.map +1 -0
  818. package/dist/utils/highlight-js-languages.js +573 -0
  819. package/dist/utils/highlight-js-languages.js.map +1 -0
  820. package/dist/utils/platform.d.ts +3 -0
  821. package/dist/utils/platform.d.ts.map +1 -0
  822. package/dist/utils/platform.js +8 -0
  823. package/dist/utils/platform.js.map +1 -0
  824. package/dist/utils/shell.d.ts +7 -1
  825. package/dist/utils/shell.d.ts.map +1 -1
  826. package/dist/utils/shell.js +21 -6
  827. package/dist/utils/shell.js.map +1 -1
  828. package/dist/utils/syntax-highlight.d.ts.map +1 -1
  829. package/dist/utils/syntax-highlight.js +53 -5
  830. package/dist/utils/syntax-highlight.js.map +1 -1
  831. package/dist/utils/tools-manager.d.ts.map +1 -1
  832. package/dist/utils/tools-manager.js +112 -1
  833. package/dist/utils/tools-manager.js.map +1 -1
  834. package/dist/utils/work-directory.d.ts +11 -0
  835. package/dist/utils/work-directory.d.ts.map +1 -1
  836. package/dist/utils/work-directory.js +30 -9
  837. package/dist/utils/work-directory.js.map +1 -1
  838. package/docs/UI_POLISH_ORCHESTRATION.md +44 -0
  839. package/docs/UI_QUALITY_ORCHESTRATION.md +30 -0
  840. package/docs/docs.json +4 -0
  841. package/docs/extensions.md +62 -2
  842. package/docs/harness-architecture.md +156 -0
  843. package/docs/keybindings.md +11 -1
  844. package/docs/process-matrix.md +153 -0
  845. package/docs/rpc.md +45 -2
  846. package/docs/sessions.md +15 -0
  847. package/docs/settings.md +128 -11
  848. package/docs/task-steps.md +8 -0
  849. package/docs/telemetry.md +19 -0
  850. package/docs/tmux-agent-manager.md +92 -2
  851. package/docs/ui-reference/README.md +9 -0
  852. package/docs/ui-reference/orchestration-after.txt +7 -0
  853. package/docs/ui-reference/orchestration-before.txt +7 -0
  854. package/docs/ui-reference/orchestration-reference.txt +6 -0
  855. package/docs/usage.md +14 -2
  856. package/docs/windows.md +52 -3
  857. package/docs/work-directory.md +42 -2
  858. package/docs/worktree-sync.md +250 -0
  859. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  860. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  861. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  862. package/examples/extensions/question.ts +34 -245
  863. package/examples/extensions/questionnaire.ts +41 -407
  864. package/examples/extensions/sandbox/package-lock.json +2 -2
  865. package/examples/extensions/sandbox/package.json +1 -1
  866. package/examples/extensions/with-deps/package-lock.json +2 -2
  867. package/examples/extensions/with-deps/package.json +1 -1
  868. package/npm-shrinkwrap.json +12 -12
  869. package/package.json +10 -4
  870. package/dist/core/delegation/session-worker-result.d.ts +0 -16
  871. package/dist/core/delegation/session-worker-result.d.ts.map +0 -1
  872. package/dist/core/delegation/session-worker-result.js +0 -53
  873. package/dist/core/delegation/session-worker-result.js.map +0 -1
  874. package/dist/core/delegation/worker-result.d.ts +0 -15
  875. package/dist/core/delegation/worker-result.d.ts.map +0 -1
  876. package/dist/core/delegation/worker-result.js +0 -161
  877. package/dist/core/delegation/worker-result.js.map +0 -1
  878. package/dist/core/telemetry.d.ts.map +0 -1
  879. package/dist/core/telemetry.js.map +0 -1
  880. package/docs/integration-sweep-builder-blueprint-2026-07-09.md +0 -365
  881. package/docs/integration-sweep-resume-2026-07-09.md +0 -407
@@ -1 +1 @@
1
- {"version":3,"file":"config-backup.d.ts","sourceRoot":"","sources":["../../../src/modes/interactive/config-backup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAEpD,OAAO,KAAK,EAMX,eAAe,EAEf,MAAM,gCAAgC,CAAC;AAGxC,MAAM,WAAW,gBAAgB;IAChC,QAAQ,CAAC,eAAe,EAAE,eAAe,CAAC;IAC1C,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,SAAS,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CACtC;AAED,MAAM,WAAW,iBAAkB,SAAQ,gBAAgB;IAC1D,YAAY,CACX,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,IAAI,KAAK;QAAE,SAAS,EAAE,SAAS,CAAC;QAAC,KAAK,EAAE,SAAS,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,IAAI,CAAA;KAAE,GACjG,IAAI,CAAC;IACR,mBAAmB,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;CACxC;AAqCD,MAAM,WAAW,oBAAoB;IACpC,gBAAgB,EAAE,OAAO,CAAC;IAC1B,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC3B;AAkDD,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,oBAAoB,CAU7E;AAsBD,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,oBAAoB,GAAG,IAAI,CAS7F;AAYD,wBAAsB,yBAAyB,CAAC,IAAI,EAAE,gBAAgB,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAmDvG;AAED,wBAAsB,0BAA0B,CAAC,IAAI,EAAE,iBAAiB,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAiIxG","sourcesContent":["/**\n * Config backup/restore commands extracted from interactive-mode.\n *\n * `handleConfigBackupCommand` snapshots the reusable-file profiles and the\n * resource-relevant settings to a JSON bundle; `handleConfigRestoreCommand`\n * reads such a bundle back, confirming with the user before clobbering local\n * state. Both operate through a narrow `host` seam (settings manager + the\n * status/error/selector callbacks) so the file IO and validation live outside\n * the god file while interactive-mode keeps only thin delegating wrappers.\n */\n\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport type { Component } from \"@caupulican/pi-tui\";\nimport { getAgentDir } from \"../../config.ts\";\nimport type {\n\tGlobalResourceProfileConfiguration,\n\tModelRouterSettings,\n\tProfileDefinitionInput,\n\tResourceProfileSettings,\n\tSettings,\n\tSettingsManager,\n\tThinkingLevel,\n} from \"../../core/settings-manager.ts\";\nimport { SelectSubmenu } from \"./components/settings-selector.ts\";\n\nexport interface ConfigBackupHost {\n\treadonly settingsManager: SettingsManager;\n\tshowStatus(message: string): void;\n\tshowError(errorMessage: string): void;\n}\n\nexport interface ConfigRestoreHost extends ConfigBackupHost {\n\tshowSelector(\n\t\tcreate: (done: () => void) => { component: Component; focus: Component; onSuperseded?: () => void },\n\t): void;\n\thandleReloadCommand(): Promise<boolean>;\n}\n\nconst PROFILE_THINKING_LEVELS = new Set<ThinkingLevel>([\n\t\"off\",\n\t\"minimal\",\n\t\"low\",\n\t\"medium\",\n\t\"high\",\n\t\"xhigh\",\n\t\"max\",\n\t\"ultra\",\n]);\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn Boolean(value) && typeof value === \"object\" && !Array.isArray(value);\n}\n\nfunction decodeBackupProfile(name: string, value: unknown): ProfileDefinitionInput | undefined {\n\tif (!isRecord(value)) return undefined;\n\tconst isWrapper = Object.hasOwn(value, \"resources\");\n\tconst resources = isWrapper ? value.resources : value;\n\tif (!isRecord(resources)) return undefined;\n\tconst thinking =\n\t\ttypeof value.thinking === \"string\" && PROFILE_THINKING_LEVELS.has(value.thinking as ThinkingLevel)\n\t\t\t? (value.thinking as ThinkingLevel)\n\t\t\t: undefined;\n\treturn {\n\t\tname,\n\t\tdescription: typeof value.description === \"string\" ? value.description : undefined,\n\t\tmodel: typeof value.model === \"string\" ? value.model : undefined,\n\t\tthinking,\n\t\tmodelRouter: isRecord(value.modelRouter) ? (value.modelRouter as ModelRouterSettings) : undefined,\n\t\tsoul: typeof value.soul === \"string\" ? value.soul : undefined,\n\t\tresources: resources as ResourceProfileSettings,\n\t};\n}\n\nexport interface ProfileFilesSnapshot {\n\tdirectoryExisted: boolean;\n\tfiles: Map<string, string>;\n}\n\nfunction canonicalActiveResourceProfiles(settings: Settings): string[] {\n\tconst values = settings.activeResourceProfiles ?? settings.activeResourceProfile ?? [];\n\tconst entries = Array.isArray(values) ? values : [values];\n\treturn [...new Set(entries.map((entry) => entry.trim()).filter((entry) => entry.length > 0))];\n}\n\nfunction decodeStringArray(label: string, value: unknown): string[] {\n\tif (!Array.isArray(value) || value.some((entry) => typeof entry !== \"string\")) {\n\t\tthrow new Error(`Invalid backup file: ${label} must be an array of strings`);\n\t}\n\treturn [...new Set(value.map((entry) => entry.trim()).filter((entry) => entry.length > 0))];\n}\n\nfunction decodeActiveResourceProfiles(settings: Record<string, unknown>): string[] {\n\tif (Object.hasOwn(settings, \"activeResourceProfiles\")) {\n\t\treturn decodeStringArray(\"settings.activeResourceProfiles\", settings.activeResourceProfiles);\n\t}\n\tconst legacy = settings.activeResourceProfile;\n\tif (legacy === undefined) return [];\n\tif (typeof legacy === \"string\") return legacy.trim() ? [legacy.trim()] : [];\n\treturn decodeStringArray(\"settings.activeResourceProfile\", legacy);\n}\n\nfunction decodeResourceProfiles(value: unknown): Record<string, ProfileDefinitionInput> {\n\tif (value === undefined) return {};\n\tif (!isRecord(value)) {\n\t\tthrow new Error(\"Invalid backup file: settings.resourceProfiles must be an object\");\n\t}\n\tconst profiles: Record<string, ProfileDefinitionInput> = {};\n\tfor (const [name, definition] of Object.entries(value)) {\n\t\tconst decoded = decodeBackupProfile(name, definition);\n\t\tif (!decoded) throw new Error(`Invalid backup file: malformed resource profile \"${name}\"`);\n\t\tprofiles[name] = decoded;\n\t}\n\treturn profiles;\n}\n\nfunction decodeProfileFiles(value: unknown): Record<string, unknown> {\n\tif (value === undefined) return {};\n\tif (!isRecord(value)) throw new Error(\"Invalid backup file: profiles must be an object\");\n\tfor (const filename of Object.keys(value)) {\n\t\tif (path.basename(filename) !== filename || !filename.endsWith(\".json\")) {\n\t\t\tthrow new Error(`Invalid backup file: unsafe profile filename \"${filename}\"`);\n\t\t}\n\t}\n\treturn value;\n}\n\nexport function captureProfileFiles(profilesDir: string): ProfileFilesSnapshot {\n\tconst files = new Map<string, string>();\n\tif (!fs.existsSync(profilesDir)) return { directoryExisted: false, files };\n\tfor (const filename of fs.readdirSync(profilesDir)) {\n\t\tif (!filename.endsWith(\".json\")) continue;\n\t\tconst profilePath = path.join(profilesDir, filename);\n\t\tif (!fs.lstatSync(profilePath).isFile()) continue;\n\t\tfiles.set(filename, fs.readFileSync(profilePath, \"utf-8\"));\n\t}\n\treturn { directoryExisted: true, files };\n}\n\nfunction clearProfileFiles(profilesDir: string): void {\n\tif (!fs.existsSync(profilesDir)) return;\n\tfor (const filename of fs.readdirSync(profilesDir)) {\n\t\tif (!filename.endsWith(\".json\")) continue;\n\t\tconst profilePath = path.join(profilesDir, filename);\n\t\tconst stat = fs.lstatSync(profilePath);\n\t\tif (stat.isFile() || stat.isSymbolicLink()) fs.rmSync(profilePath, { force: true });\n\t}\n}\n\nfunction replaceProfileFiles(profilesDir: string, profiles: Record<string, unknown>): void {\n\tfs.mkdirSync(profilesDir, { recursive: true });\n\tclearProfileFiles(profilesDir);\n\tfor (const [filename, content] of Object.entries(profiles)) {\n\t\tconst serialized = JSON.stringify(content, null, 2);\n\t\tif (serialized === undefined) throw new Error(`Invalid backup file: profile \"${filename}\" is not serializable`);\n\t\tfs.writeFileSync(path.join(profilesDir, filename), serialized, \"utf-8\");\n\t}\n}\n\nexport function restoreProfileFiles(profilesDir: string, snapshot: ProfileFilesSnapshot): void {\n\tclearProfileFiles(profilesDir);\n\tif (snapshot.files.size > 0) fs.mkdirSync(profilesDir, { recursive: true });\n\tfor (const [filename, content] of snapshot.files) {\n\t\tfs.writeFileSync(path.join(profilesDir, filename), content, \"utf-8\");\n\t}\n\tif (!snapshot.directoryExisted && fs.existsSync(profilesDir) && fs.readdirSync(profilesDir).length === 0) {\n\t\tfs.rmdirSync(profilesDir);\n\t}\n}\n\nfunction profileConfigurationFromSettings(settings: Settings): GlobalResourceProfileConfiguration {\n\treturn {\n\t\tresourceProfiles: settings.resourceProfiles,\n\t\tactiveResourceProfile: settings.activeResourceProfile,\n\t\tactiveResourceProfiles: settings.activeResourceProfiles,\n\t\texternalResourceRoots: settings.externalResourceRoots,\n\t\ttrustedResourceRoots: settings.trustedResourceRoots,\n\t};\n}\n\nexport async function handleConfigBackupCommand(host: ConfigBackupHost, fileArg?: string): Promise<void> {\n\ttry {\n\t\tconst profilesDir = path.join(getAgentDir(), \"profiles\");\n\t\tconst profiles: Record<string, unknown> = {};\n\t\tif (fs.existsSync(profilesDir)) {\n\t\t\tconst entries = fs.readdirSync(profilesDir);\n\t\t\tfor (const entry of entries) {\n\t\t\t\tif (entry.endsWith(\".json\")) {\n\t\t\t\t\tconst pPath = path.join(profilesDir, entry);\n\t\t\t\t\ttry {\n\t\t\t\t\t\tif (!fs.lstatSync(pPath).isFile()) continue;\n\t\t\t\t\t\tconst content = fs.readFileSync(pPath, \"utf-8\");\n\t\t\t\t\t\tprofiles[entry] = JSON.parse(content);\n\t\t\t\t\t} catch {\n\t\t\t\t\t\t// skip\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tconst globalSettings = host.settingsManager.getGlobalSettings();\n\t\tconst backupData = {\n\t\t\tversion: 2,\n\t\t\tprofiles,\n\t\t\tsettings: {\n\t\t\t\tresourceProfiles: globalSettings.resourceProfiles ?? {},\n\t\t\t\tactiveResourceProfiles: canonicalActiveResourceProfiles(globalSettings),\n\t\t\t\texternalResourceRoots: globalSettings.externalResourceRoots ?? [],\n\t\t\t\ttrustedResourceRoots: globalSettings.trustedResourceRoots ?? [],\n\t\t\t},\n\t\t};\n\n\t\tlet targetFile = fileArg;\n\t\tif (!targetFile) {\n\t\t\tconst backupsDir = path.join(getAgentDir(), \"backups\");\n\t\t\tfs.mkdirSync(backupsDir, { recursive: true });\n\t\t\tconst timestamp = new Date().toISOString().replace(/[:.]/g, \"-\");\n\t\t\ttargetFile = path.join(backupsDir, `config-${timestamp}.json`);\n\t\t} else {\n\t\t\tconst resolved = host.settingsManager.canonicalizePath(targetFile);\n\t\t\tif (resolved) {\n\t\t\t\ttargetFile = resolved;\n\t\t\t}\n\t\t}\n\n\t\tfs.mkdirSync(path.dirname(targetFile), { recursive: true });\n\t\tfs.writeFileSync(targetFile, JSON.stringify(backupData, null, 2), \"utf-8\");\n\t\thost.showStatus(`Configuration backup saved to ${targetFile}`);\n\t} catch (error) {\n\t\thost.showError(error instanceof Error ? error.message : String(error));\n\t}\n}\n\nexport async function handleConfigRestoreCommand(host: ConfigRestoreHost, fileArg: string): Promise<void> {\n\ttry {\n\t\tconst trimmed = fileArg.trim();\n\t\tif (!trimmed) {\n\t\t\thost.showError(\"Usage: /config-restore <file>\");\n\t\t\treturn;\n\t\t}\n\n\t\tconst resolved = host.settingsManager.canonicalizePath(trimmed);\n\t\tif (!resolved || !fs.existsSync(resolved)) {\n\t\t\thost.showError(`Backup file does not exist: ${trimmed}`);\n\t\t\treturn;\n\t\t}\n\n\t\tlet bundle: unknown;\n\t\ttry {\n\t\t\tconst content = fs.readFileSync(resolved, \"utf-8\");\n\t\t\tbundle = JSON.parse(content);\n\t\t} catch (error) {\n\t\t\thost.showError(`Failed to parse backup file: ${error instanceof Error ? error.message : String(error)}`);\n\t\t\treturn;\n\t\t}\n\n\t\tif (!isRecord(bundle)) {\n\t\t\thost.showError(\"Invalid backup file: must be a JSON object\");\n\t\t\treturn;\n\t\t}\n\n\t\tlet profileFiles: Record<string, unknown>;\n\t\tlet restoredResourceProfiles: Record<string, ProfileDefinitionInput>;\n\t\tlet restoredActiveProfiles: string[];\n\t\tlet restoredExternalRoots: string[];\n\t\ttry {\n\t\t\tprofileFiles = decodeProfileFiles(bundle.profiles);\n\t\t\tconst backupSettings = isRecord(bundle.settings) ? bundle.settings : {};\n\t\t\trestoredResourceProfiles = decodeResourceProfiles(backupSettings.resourceProfiles);\n\t\t\trestoredActiveProfiles = decodeActiveResourceProfiles(backupSettings);\n\t\t\trestoredExternalRoots = decodeStringArray(\n\t\t\t\t\"settings.externalResourceRoots\",\n\t\t\t\tbackupSettings.externalResourceRoots ?? [],\n\t\t\t);\n\t\t} catch (error) {\n\t\t\thost.showError(error instanceof Error ? error.message : String(error));\n\t\t\treturn;\n\t\t}\n\n\t\t// Confirm before clobbering\n\t\tconst confirm = await new Promise<boolean>((resolve) => {\n\t\t\thost.showSelector((done) => {\n\t\t\t\tlet settled = false;\n\t\t\t\tconst settle = (value: boolean) => {\n\t\t\t\t\tif (settled) return;\n\t\t\t\t\tsettled = true;\n\t\t\t\t\tresolve(value);\n\t\t\t\t};\n\t\t\t\tconst submenu = new SelectSubmenu(\n\t\t\t\t\t\"Restore configuration?\",\n\t\t\t\t\t\"This will overwrite existing local profiles and settings with the backup values. Do you want to continue?\",\n\t\t\t\t\t[\n\t\t\t\t\t\t{ value: \"yes\", label: \"Yes\", description: \"Proceed with restoration.\" },\n\t\t\t\t\t\t{ value: \"no\", label: \"No\", description: \"Cancel and abort.\" },\n\t\t\t\t\t],\n\t\t\t\t\t\"no\",\n\t\t\t\t\t(value) => {\n\t\t\t\t\t\tdone();\n\t\t\t\t\t\tsettle(value === \"yes\");\n\t\t\t\t\t},\n\t\t\t\t\t() => {\n\t\t\t\t\t\tdone();\n\t\t\t\t\t\tsettle(false);\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t\treturn {\n\t\t\t\t\tcomponent: submenu,\n\t\t\t\t\tfocus: submenu.getSelectList(),\n\t\t\t\t\tonSuperseded: () => settle(false),\n\t\t\t\t};\n\t\t\t});\n\t\t});\n\n\t\tif (!confirm) {\n\t\t\thost.showStatus(\"Restore aborted.\");\n\t\t\treturn;\n\t\t}\n\n\t\tconst profilesDir = path.join(getAgentDir(), \"profiles\");\n\t\tconst profileFilesSnapshot = captureProfileFiles(profilesDir);\n\t\tconst settingsSnapshot = host.settingsManager.createReloadSnapshot();\n\t\tlet restoreStarted = false;\n\t\ttry {\n\t\t\trestoreStarted = true;\n\t\t\treplaceProfileFiles(profilesDir, profileFiles);\n\t\t\tconst currentTrusted = settingsSnapshot.globalSettings.trustedResourceRoots ?? [];\n\t\t\thost.settingsManager.replaceGlobalResourceProfileConfiguration({\n\t\t\t\tresourceProfiles: Object.keys(restoredResourceProfiles).length > 0 ? restoredResourceProfiles : undefined,\n\t\t\t\tactiveResourceProfiles: restoredActiveProfiles,\n\t\t\t\texternalResourceRoots: restoredExternalRoots,\n\t\t\t\t// Security boundary: roots named by a backup never become trusted through restore.\n\t\t\t\ttrustedResourceRoots: currentTrusted.filter((root) => !restoredExternalRoots.includes(root)),\n\t\t\t});\n\t\t\tawait host.settingsManager.flush();\n\t\t\tif (!(await host.handleReloadCommand())) {\n\t\t\t\tthrow new Error(\"restored configuration failed runtime validation\");\n\t\t\t}\n\t\t\thost.showStatus(\"Configuration restored successfully.\");\n\t\t} catch (error) {\n\t\t\tlet rollbackError: unknown;\n\t\t\tif (restoreStarted) {\n\t\t\t\ttry {\n\t\t\t\t\trestoreProfileFiles(profilesDir, profileFilesSnapshot);\n\t\t\t\t\thost.settingsManager.replaceGlobalResourceProfileConfiguration(\n\t\t\t\t\t\tprofileConfigurationFromSettings(settingsSnapshot.globalSettings),\n\t\t\t\t\t);\n\t\t\t\t\tawait host.settingsManager.flush();\n\t\t\t\t\thost.settingsManager.restoreReloadSnapshot(settingsSnapshot);\n\t\t\t\t} catch (restoreError) {\n\t\t\t\t\trollbackError = restoreError;\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\thost.showError(\n\t\t\t\trollbackError\n\t\t\t\t\t? `Configuration restore failed: ${message}; rollback failed: ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)}`\n\t\t\t\t\t: `Configuration restore failed: ${message}; previous configuration restored`,\n\t\t\t);\n\t\t}\n\t} catch (error) {\n\t\thost.showError(error instanceof Error ? error.message : String(error));\n\t}\n}\n"]}
1
+ {"version":3,"file":"config-backup.d.ts","sourceRoot":"","sources":["../../../src/modes/interactive/config-backup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAGpD,OAAO,KAAK,EAMX,eAAe,EAEf,MAAM,gCAAgC,CAAC;AAGxC,MAAM,WAAW,gBAAgB;IAChC,QAAQ,CAAC,eAAe,EAAE,eAAe,CAAC;IAC1C,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,SAAS,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CACtC;AAED,MAAM,WAAW,iBAAkB,SAAQ,gBAAgB;IAC1D,YAAY,CACX,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,IAAI,KAAK;QAAE,SAAS,EAAE,SAAS,CAAC;QAAC,KAAK,EAAE,SAAS,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,IAAI,CAAA;KAAE,GACjG,IAAI,CAAC;IACR,mBAAmB,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;CACxC;AAqCD,MAAM,WAAW,oBAAoB;IACpC,gBAAgB,EAAE,OAAO,CAAC;IAC1B,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC3B;AAkDD,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,oBAAoB,CAU7E;AAsBD,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,oBAAoB,GAAG,IAAI,CAS7F;AAYD,wBAAsB,yBAAyB,CAAC,IAAI,EAAE,gBAAgB,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAmDvG;AAED,wBAAsB,0BAA0B,CAAC,IAAI,EAAE,iBAAiB,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAiIxG","sourcesContent":["/**\n * Config backup/restore commands extracted from interactive-mode.\n *\n * `handleConfigBackupCommand` snapshots the reusable-file profiles and the\n * resource-relevant settings to a JSON bundle; `handleConfigRestoreCommand`\n * reads such a bundle back, confirming with the user before clobbering local\n * state. Both operate through a narrow `host` seam (settings manager + the\n * status/error/selector callbacks) so the file IO and validation live outside\n * the god file while interactive-mode keeps only thin delegating wrappers.\n */\n\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport type { Component } from \"@caupulican/pi-tui\";\nimport { getAgentDir, getProfilesDir } from \"../../config.ts\";\nimport { configBackupsDir } from \"../../core/agent-paths.ts\";\nimport type {\n\tGlobalResourceProfileConfiguration,\n\tModelRouterSettings,\n\tProfileDefinitionInput,\n\tResourceProfileSettings,\n\tSettings,\n\tSettingsManager,\n\tThinkingLevel,\n} from \"../../core/settings-manager.ts\";\nimport { SelectSubmenu } from \"./components/settings-selector.ts\";\n\nexport interface ConfigBackupHost {\n\treadonly settingsManager: SettingsManager;\n\tshowStatus(message: string): void;\n\tshowError(errorMessage: string): void;\n}\n\nexport interface ConfigRestoreHost extends ConfigBackupHost {\n\tshowSelector(\n\t\tcreate: (done: () => void) => { component: Component; focus: Component; onSuperseded?: () => void },\n\t): void;\n\thandleReloadCommand(): Promise<boolean>;\n}\n\nconst PROFILE_THINKING_LEVELS = new Set<ThinkingLevel>([\n\t\"off\",\n\t\"minimal\",\n\t\"low\",\n\t\"medium\",\n\t\"high\",\n\t\"xhigh\",\n\t\"max\",\n\t\"ultra\",\n]);\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn Boolean(value) && typeof value === \"object\" && !Array.isArray(value);\n}\n\nfunction decodeBackupProfile(name: string, value: unknown): ProfileDefinitionInput | undefined {\n\tif (!isRecord(value)) return undefined;\n\tconst isWrapper = Object.hasOwn(value, \"resources\");\n\tconst resources = isWrapper ? value.resources : value;\n\tif (!isRecord(resources)) return undefined;\n\tconst thinking =\n\t\ttypeof value.thinking === \"string\" && PROFILE_THINKING_LEVELS.has(value.thinking as ThinkingLevel)\n\t\t\t? (value.thinking as ThinkingLevel)\n\t\t\t: undefined;\n\treturn {\n\t\tname,\n\t\tdescription: typeof value.description === \"string\" ? value.description : undefined,\n\t\tmodel: typeof value.model === \"string\" ? value.model : undefined,\n\t\tthinking,\n\t\tmodelRouter: isRecord(value.modelRouter) ? (value.modelRouter as ModelRouterSettings) : undefined,\n\t\tsoul: typeof value.soul === \"string\" ? value.soul : undefined,\n\t\tresources: resources as ResourceProfileSettings,\n\t};\n}\n\nexport interface ProfileFilesSnapshot {\n\tdirectoryExisted: boolean;\n\tfiles: Map<string, string>;\n}\n\nfunction canonicalActiveResourceProfiles(settings: Settings): string[] {\n\tconst values = settings.activeResourceProfiles ?? settings.activeResourceProfile ?? [];\n\tconst entries = Array.isArray(values) ? values : [values];\n\treturn [...new Set(entries.map((entry) => entry.trim()).filter((entry) => entry.length > 0))];\n}\n\nfunction decodeStringArray(label: string, value: unknown): string[] {\n\tif (!Array.isArray(value) || value.some((entry) => typeof entry !== \"string\")) {\n\t\tthrow new Error(`Invalid backup file: ${label} must be an array of strings`);\n\t}\n\treturn [...new Set(value.map((entry) => entry.trim()).filter((entry) => entry.length > 0))];\n}\n\nfunction decodeActiveResourceProfiles(settings: Record<string, unknown>): string[] {\n\tif (Object.hasOwn(settings, \"activeResourceProfiles\")) {\n\t\treturn decodeStringArray(\"settings.activeResourceProfiles\", settings.activeResourceProfiles);\n\t}\n\tconst legacy = settings.activeResourceProfile;\n\tif (legacy === undefined) return [];\n\tif (typeof legacy === \"string\") return legacy.trim() ? [legacy.trim()] : [];\n\treturn decodeStringArray(\"settings.activeResourceProfile\", legacy);\n}\n\nfunction decodeResourceProfiles(value: unknown): Record<string, ProfileDefinitionInput> {\n\tif (value === undefined) return {};\n\tif (!isRecord(value)) {\n\t\tthrow new Error(\"Invalid backup file: settings.resourceProfiles must be an object\");\n\t}\n\tconst profiles: Record<string, ProfileDefinitionInput> = {};\n\tfor (const [name, definition] of Object.entries(value)) {\n\t\tconst decoded = decodeBackupProfile(name, definition);\n\t\tif (!decoded) throw new Error(`Invalid backup file: malformed resource profile \"${name}\"`);\n\t\tprofiles[name] = decoded;\n\t}\n\treturn profiles;\n}\n\nfunction decodeProfileFiles(value: unknown): Record<string, unknown> {\n\tif (value === undefined) return {};\n\tif (!isRecord(value)) throw new Error(\"Invalid backup file: profiles must be an object\");\n\tfor (const filename of Object.keys(value)) {\n\t\tif (path.basename(filename) !== filename || !filename.endsWith(\".json\")) {\n\t\t\tthrow new Error(`Invalid backup file: unsafe profile filename \"${filename}\"`);\n\t\t}\n\t}\n\treturn value;\n}\n\nexport function captureProfileFiles(profilesDir: string): ProfileFilesSnapshot {\n\tconst files = new Map<string, string>();\n\tif (!fs.existsSync(profilesDir)) return { directoryExisted: false, files };\n\tfor (const filename of fs.readdirSync(profilesDir)) {\n\t\tif (!filename.endsWith(\".json\")) continue;\n\t\tconst profilePath = path.join(profilesDir, filename);\n\t\tif (!fs.lstatSync(profilePath).isFile()) continue;\n\t\tfiles.set(filename, fs.readFileSync(profilePath, \"utf-8\"));\n\t}\n\treturn { directoryExisted: true, files };\n}\n\nfunction clearProfileFiles(profilesDir: string): void {\n\tif (!fs.existsSync(profilesDir)) return;\n\tfor (const filename of fs.readdirSync(profilesDir)) {\n\t\tif (!filename.endsWith(\".json\")) continue;\n\t\tconst profilePath = path.join(profilesDir, filename);\n\t\tconst stat = fs.lstatSync(profilePath);\n\t\tif (stat.isFile() || stat.isSymbolicLink()) fs.rmSync(profilePath, { force: true });\n\t}\n}\n\nfunction replaceProfileFiles(profilesDir: string, profiles: Record<string, unknown>): void {\n\tfs.mkdirSync(profilesDir, { recursive: true });\n\tclearProfileFiles(profilesDir);\n\tfor (const [filename, content] of Object.entries(profiles)) {\n\t\tconst serialized = JSON.stringify(content, null, 2);\n\t\tif (serialized === undefined) throw new Error(`Invalid backup file: profile \"${filename}\" is not serializable`);\n\t\tfs.writeFileSync(path.join(profilesDir, filename), serialized, \"utf-8\");\n\t}\n}\n\nexport function restoreProfileFiles(profilesDir: string, snapshot: ProfileFilesSnapshot): void {\n\tclearProfileFiles(profilesDir);\n\tif (snapshot.files.size > 0) fs.mkdirSync(profilesDir, { recursive: true });\n\tfor (const [filename, content] of snapshot.files) {\n\t\tfs.writeFileSync(path.join(profilesDir, filename), content, \"utf-8\");\n\t}\n\tif (!snapshot.directoryExisted && fs.existsSync(profilesDir) && fs.readdirSync(profilesDir).length === 0) {\n\t\tfs.rmdirSync(profilesDir);\n\t}\n}\n\nfunction profileConfigurationFromSettings(settings: Settings): GlobalResourceProfileConfiguration {\n\treturn {\n\t\tresourceProfiles: settings.resourceProfiles,\n\t\tactiveResourceProfile: settings.activeResourceProfile,\n\t\tactiveResourceProfiles: settings.activeResourceProfiles,\n\t\texternalResourceRoots: settings.externalResourceRoots,\n\t\ttrustedResourceRoots: settings.trustedResourceRoots,\n\t};\n}\n\nexport async function handleConfigBackupCommand(host: ConfigBackupHost, fileArg?: string): Promise<void> {\n\ttry {\n\t\tconst profilesDir = getProfilesDir();\n\t\tconst profiles: Record<string, unknown> = {};\n\t\tif (fs.existsSync(profilesDir)) {\n\t\t\tconst entries = fs.readdirSync(profilesDir);\n\t\t\tfor (const entry of entries) {\n\t\t\t\tif (entry.endsWith(\".json\")) {\n\t\t\t\t\tconst pPath = path.join(profilesDir, entry);\n\t\t\t\t\ttry {\n\t\t\t\t\t\tif (!fs.lstatSync(pPath).isFile()) continue;\n\t\t\t\t\t\tconst content = fs.readFileSync(pPath, \"utf-8\");\n\t\t\t\t\t\tprofiles[entry] = JSON.parse(content);\n\t\t\t\t\t} catch {\n\t\t\t\t\t\t// skip\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tconst globalSettings = host.settingsManager.getGlobalSettings();\n\t\tconst backupData = {\n\t\t\tversion: 2,\n\t\t\tprofiles,\n\t\t\tsettings: {\n\t\t\t\tresourceProfiles: globalSettings.resourceProfiles ?? {},\n\t\t\t\tactiveResourceProfiles: canonicalActiveResourceProfiles(globalSettings),\n\t\t\t\texternalResourceRoots: globalSettings.externalResourceRoots ?? [],\n\t\t\t\ttrustedResourceRoots: globalSettings.trustedResourceRoots ?? [],\n\t\t\t},\n\t\t};\n\n\t\tlet targetFile = fileArg;\n\t\tif (!targetFile) {\n\t\t\tconst backupsDir = configBackupsDir(getAgentDir());\n\t\t\tfs.mkdirSync(backupsDir, { recursive: true });\n\t\t\tconst timestamp = new Date().toISOString().replace(/[:.]/g, \"-\");\n\t\t\ttargetFile = path.join(backupsDir, `config-${timestamp}.json`);\n\t\t} else {\n\t\t\tconst resolved = host.settingsManager.canonicalizePath(targetFile);\n\t\t\tif (resolved) {\n\t\t\t\ttargetFile = resolved;\n\t\t\t}\n\t\t}\n\n\t\tfs.mkdirSync(path.dirname(targetFile), { recursive: true });\n\t\tfs.writeFileSync(targetFile, JSON.stringify(backupData, null, 2), \"utf-8\");\n\t\thost.showStatus(`Configuration backup saved to ${targetFile}`);\n\t} catch (error) {\n\t\thost.showError(error instanceof Error ? error.message : String(error));\n\t}\n}\n\nexport async function handleConfigRestoreCommand(host: ConfigRestoreHost, fileArg: string): Promise<void> {\n\ttry {\n\t\tconst trimmed = fileArg.trim();\n\t\tif (!trimmed) {\n\t\t\thost.showError(\"Usage: /config-restore <file>\");\n\t\t\treturn;\n\t\t}\n\n\t\tconst resolved = host.settingsManager.canonicalizePath(trimmed);\n\t\tif (!resolved || !fs.existsSync(resolved)) {\n\t\t\thost.showError(`Backup file does not exist: ${trimmed}`);\n\t\t\treturn;\n\t\t}\n\n\t\tlet bundle: unknown;\n\t\ttry {\n\t\t\tconst content = fs.readFileSync(resolved, \"utf-8\");\n\t\t\tbundle = JSON.parse(content);\n\t\t} catch (error) {\n\t\t\thost.showError(`Failed to parse backup file: ${error instanceof Error ? error.message : String(error)}`);\n\t\t\treturn;\n\t\t}\n\n\t\tif (!isRecord(bundle)) {\n\t\t\thost.showError(\"Invalid backup file: must be a JSON object\");\n\t\t\treturn;\n\t\t}\n\n\t\tlet profileFiles: Record<string, unknown>;\n\t\tlet restoredResourceProfiles: Record<string, ProfileDefinitionInput>;\n\t\tlet restoredActiveProfiles: string[];\n\t\tlet restoredExternalRoots: string[];\n\t\ttry {\n\t\t\tprofileFiles = decodeProfileFiles(bundle.profiles);\n\t\t\tconst backupSettings = isRecord(bundle.settings) ? bundle.settings : {};\n\t\t\trestoredResourceProfiles = decodeResourceProfiles(backupSettings.resourceProfiles);\n\t\t\trestoredActiveProfiles = decodeActiveResourceProfiles(backupSettings);\n\t\t\trestoredExternalRoots = decodeStringArray(\n\t\t\t\t\"settings.externalResourceRoots\",\n\t\t\t\tbackupSettings.externalResourceRoots ?? [],\n\t\t\t);\n\t\t} catch (error) {\n\t\t\thost.showError(error instanceof Error ? error.message : String(error));\n\t\t\treturn;\n\t\t}\n\n\t\t// Confirm before clobbering\n\t\tconst confirm = await new Promise<boolean>((resolve) => {\n\t\t\thost.showSelector((done) => {\n\t\t\t\tlet settled = false;\n\t\t\t\tconst settle = (value: boolean) => {\n\t\t\t\t\tif (settled) return;\n\t\t\t\t\tsettled = true;\n\t\t\t\t\tresolve(value);\n\t\t\t\t};\n\t\t\t\tconst submenu = new SelectSubmenu(\n\t\t\t\t\t\"Restore configuration?\",\n\t\t\t\t\t\"This will overwrite existing local profiles and settings with the backup values. Do you want to continue?\",\n\t\t\t\t\t[\n\t\t\t\t\t\t{ value: \"yes\", label: \"Yes\", description: \"Proceed with restoration.\" },\n\t\t\t\t\t\t{ value: \"no\", label: \"No\", description: \"Cancel and abort.\" },\n\t\t\t\t\t],\n\t\t\t\t\t\"no\",\n\t\t\t\t\t(value) => {\n\t\t\t\t\t\tdone();\n\t\t\t\t\t\tsettle(value === \"yes\");\n\t\t\t\t\t},\n\t\t\t\t\t() => {\n\t\t\t\t\t\tdone();\n\t\t\t\t\t\tsettle(false);\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t\treturn {\n\t\t\t\t\tcomponent: submenu,\n\t\t\t\t\tfocus: submenu.getSelectList(),\n\t\t\t\t\tonSuperseded: () => settle(false),\n\t\t\t\t};\n\t\t\t});\n\t\t});\n\n\t\tif (!confirm) {\n\t\t\thost.showStatus(\"Restore aborted.\");\n\t\t\treturn;\n\t\t}\n\n\t\tconst profilesDir = getProfilesDir();\n\t\tconst profileFilesSnapshot = captureProfileFiles(profilesDir);\n\t\tconst settingsSnapshot = host.settingsManager.createReloadSnapshot();\n\t\tlet restoreStarted = false;\n\t\ttry {\n\t\t\trestoreStarted = true;\n\t\t\treplaceProfileFiles(profilesDir, profileFiles);\n\t\t\tconst currentTrusted = settingsSnapshot.globalSettings.trustedResourceRoots ?? [];\n\t\t\thost.settingsManager.replaceGlobalResourceProfileConfiguration({\n\t\t\t\tresourceProfiles: Object.keys(restoredResourceProfiles).length > 0 ? restoredResourceProfiles : undefined,\n\t\t\t\tactiveResourceProfiles: restoredActiveProfiles,\n\t\t\t\texternalResourceRoots: restoredExternalRoots,\n\t\t\t\t// Security boundary: roots named by a backup never become trusted through restore.\n\t\t\t\ttrustedResourceRoots: currentTrusted.filter((root) => !restoredExternalRoots.includes(root)),\n\t\t\t});\n\t\t\tawait host.settingsManager.flush();\n\t\t\tif (!(await host.handleReloadCommand())) {\n\t\t\t\tthrow new Error(\"restored configuration failed runtime validation\");\n\t\t\t}\n\t\t\thost.showStatus(\"Configuration restored successfully.\");\n\t\t} catch (error) {\n\t\t\tlet rollbackError: unknown;\n\t\t\tif (restoreStarted) {\n\t\t\t\ttry {\n\t\t\t\t\trestoreProfileFiles(profilesDir, profileFilesSnapshot);\n\t\t\t\t\thost.settingsManager.replaceGlobalResourceProfileConfiguration(\n\t\t\t\t\t\tprofileConfigurationFromSettings(settingsSnapshot.globalSettings),\n\t\t\t\t\t);\n\t\t\t\t\tawait host.settingsManager.flush();\n\t\t\t\t\thost.settingsManager.restoreReloadSnapshot(settingsSnapshot);\n\t\t\t\t} catch (restoreError) {\n\t\t\t\t\trollbackError = restoreError;\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\thost.showError(\n\t\t\t\trollbackError\n\t\t\t\t\t? `Configuration restore failed: ${message}; rollback failed: ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)}`\n\t\t\t\t\t: `Configuration restore failed: ${message}; previous configuration restored`,\n\t\t\t);\n\t\t}\n\t} catch (error) {\n\t\thost.showError(error instanceof Error ? error.message : String(error));\n\t}\n}\n"]}
@@ -10,7 +10,8 @@
10
10
  */
11
11
  import * as fs from "node:fs";
12
12
  import * as path from "node:path";
13
- import { getAgentDir } from "../../config.js";
13
+ import { getAgentDir, getProfilesDir } from "../../config.js";
14
+ import { configBackupsDir } from "../../core/agent-paths.js";
14
15
  import { SelectSubmenu } from "./components/settings-selector.js";
15
16
  const PROFILE_THINKING_LEVELS = new Set([
16
17
  "off",
@@ -152,7 +153,7 @@ function profileConfigurationFromSettings(settings) {
152
153
  }
153
154
  export async function handleConfigBackupCommand(host, fileArg) {
154
155
  try {
155
- const profilesDir = path.join(getAgentDir(), "profiles");
156
+ const profilesDir = getProfilesDir();
156
157
  const profiles = {};
157
158
  if (fs.existsSync(profilesDir)) {
158
159
  const entries = fs.readdirSync(profilesDir);
@@ -184,7 +185,7 @@ export async function handleConfigBackupCommand(host, fileArg) {
184
185
  };
185
186
  let targetFile = fileArg;
186
187
  if (!targetFile) {
187
- const backupsDir = path.join(getAgentDir(), "backups");
188
+ const backupsDir = configBackupsDir(getAgentDir());
188
189
  fs.mkdirSync(backupsDir, { recursive: true });
189
190
  const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
190
191
  targetFile = path.join(backupsDir, `config-${timestamp}.json`);
@@ -274,7 +275,7 @@ export async function handleConfigRestoreCommand(host, fileArg) {
274
275
  host.showStatus("Restore aborted.");
275
276
  return;
276
277
  }
277
- const profilesDir = path.join(getAgentDir(), "profiles");
278
+ const profilesDir = getProfilesDir();
278
279
  const profileFilesSnapshot = captureProfileFiles(profilesDir);
279
280
  const settingsSnapshot = host.settingsManager.createReloadSnapshot();
280
281
  let restoreStarted = false;
@@ -1 +1 @@
1
- {"version":3,"file":"config-backup.js","sourceRoot":"","sources":["../../../src/modes/interactive/config-backup.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Config backup/restore commands extracted from interactive-mode.\n *\n * `handleConfigBackupCommand` snapshots the reusable-file profiles and the\n * resource-relevant settings to a JSON bundle; `handleConfigRestoreCommand`\n * reads such a bundle back, confirming with the user before clobbering local\n * state. Both operate through a narrow `host` seam (settings manager + the\n * status/error/selector callbacks) so the file IO and validation live outside\n * the god file while interactive-mode keeps only thin delegating wrappers.\n */\n\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport type { Component } from \"@caupulican/pi-tui\";\nimport { getAgentDir } from \"../../config.ts\";\nimport type {\n\tGlobalResourceProfileConfiguration,\n\tModelRouterSettings,\n\tProfileDefinitionInput,\n\tResourceProfileSettings,\n\tSettings,\n\tSettingsManager,\n\tThinkingLevel,\n} from \"../../core/settings-manager.ts\";\nimport { SelectSubmenu } from \"./components/settings-selector.ts\";\n\nexport interface ConfigBackupHost {\n\treadonly settingsManager: SettingsManager;\n\tshowStatus(message: string): void;\n\tshowError(errorMessage: string): void;\n}\n\nexport interface ConfigRestoreHost extends ConfigBackupHost {\n\tshowSelector(\n\t\tcreate: (done: () => void) => { component: Component; focus: Component; onSuperseded?: () => void },\n\t): void;\n\thandleReloadCommand(): Promise<boolean>;\n}\n\nconst PROFILE_THINKING_LEVELS = new Set<ThinkingLevel>([\n\t\"off\",\n\t\"minimal\",\n\t\"low\",\n\t\"medium\",\n\t\"high\",\n\t\"xhigh\",\n\t\"max\",\n\t\"ultra\",\n]);\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn Boolean(value) && typeof value === \"object\" && !Array.isArray(value);\n}\n\nfunction decodeBackupProfile(name: string, value: unknown): ProfileDefinitionInput | undefined {\n\tif (!isRecord(value)) return undefined;\n\tconst isWrapper = Object.hasOwn(value, \"resources\");\n\tconst resources = isWrapper ? value.resources : value;\n\tif (!isRecord(resources)) return undefined;\n\tconst thinking =\n\t\ttypeof value.thinking === \"string\" && PROFILE_THINKING_LEVELS.has(value.thinking as ThinkingLevel)\n\t\t\t? (value.thinking as ThinkingLevel)\n\t\t\t: undefined;\n\treturn {\n\t\tname,\n\t\tdescription: typeof value.description === \"string\" ? value.description : undefined,\n\t\tmodel: typeof value.model === \"string\" ? value.model : undefined,\n\t\tthinking,\n\t\tmodelRouter: isRecord(value.modelRouter) ? (value.modelRouter as ModelRouterSettings) : undefined,\n\t\tsoul: typeof value.soul === \"string\" ? value.soul : undefined,\n\t\tresources: resources as ResourceProfileSettings,\n\t};\n}\n\nexport interface ProfileFilesSnapshot {\n\tdirectoryExisted: boolean;\n\tfiles: Map<string, string>;\n}\n\nfunction canonicalActiveResourceProfiles(settings: Settings): string[] {\n\tconst values = settings.activeResourceProfiles ?? settings.activeResourceProfile ?? [];\n\tconst entries = Array.isArray(values) ? values : [values];\n\treturn [...new Set(entries.map((entry) => entry.trim()).filter((entry) => entry.length > 0))];\n}\n\nfunction decodeStringArray(label: string, value: unknown): string[] {\n\tif (!Array.isArray(value) || value.some((entry) => typeof entry !== \"string\")) {\n\t\tthrow new Error(`Invalid backup file: ${label} must be an array of strings`);\n\t}\n\treturn [...new Set(value.map((entry) => entry.trim()).filter((entry) => entry.length > 0))];\n}\n\nfunction decodeActiveResourceProfiles(settings: Record<string, unknown>): string[] {\n\tif (Object.hasOwn(settings, \"activeResourceProfiles\")) {\n\t\treturn decodeStringArray(\"settings.activeResourceProfiles\", settings.activeResourceProfiles);\n\t}\n\tconst legacy = settings.activeResourceProfile;\n\tif (legacy === undefined) return [];\n\tif (typeof legacy === \"string\") return legacy.trim() ? [legacy.trim()] : [];\n\treturn decodeStringArray(\"settings.activeResourceProfile\", legacy);\n}\n\nfunction decodeResourceProfiles(value: unknown): Record<string, ProfileDefinitionInput> {\n\tif (value === undefined) return {};\n\tif (!isRecord(value)) {\n\t\tthrow new Error(\"Invalid backup file: settings.resourceProfiles must be an object\");\n\t}\n\tconst profiles: Record<string, ProfileDefinitionInput> = {};\n\tfor (const [name, definition] of Object.entries(value)) {\n\t\tconst decoded = decodeBackupProfile(name, definition);\n\t\tif (!decoded) throw new Error(`Invalid backup file: malformed resource profile \"${name}\"`);\n\t\tprofiles[name] = decoded;\n\t}\n\treturn profiles;\n}\n\nfunction decodeProfileFiles(value: unknown): Record<string, unknown> {\n\tif (value === undefined) return {};\n\tif (!isRecord(value)) throw new Error(\"Invalid backup file: profiles must be an object\");\n\tfor (const filename of Object.keys(value)) {\n\t\tif (path.basename(filename) !== filename || !filename.endsWith(\".json\")) {\n\t\t\tthrow new Error(`Invalid backup file: unsafe profile filename \"${filename}\"`);\n\t\t}\n\t}\n\treturn value;\n}\n\nexport function captureProfileFiles(profilesDir: string): ProfileFilesSnapshot {\n\tconst files = new Map<string, string>();\n\tif (!fs.existsSync(profilesDir)) return { directoryExisted: false, files };\n\tfor (const filename of fs.readdirSync(profilesDir)) {\n\t\tif (!filename.endsWith(\".json\")) continue;\n\t\tconst profilePath = path.join(profilesDir, filename);\n\t\tif (!fs.lstatSync(profilePath).isFile()) continue;\n\t\tfiles.set(filename, fs.readFileSync(profilePath, \"utf-8\"));\n\t}\n\treturn { directoryExisted: true, files };\n}\n\nfunction clearProfileFiles(profilesDir: string): void {\n\tif (!fs.existsSync(profilesDir)) return;\n\tfor (const filename of fs.readdirSync(profilesDir)) {\n\t\tif (!filename.endsWith(\".json\")) continue;\n\t\tconst profilePath = path.join(profilesDir, filename);\n\t\tconst stat = fs.lstatSync(profilePath);\n\t\tif (stat.isFile() || stat.isSymbolicLink()) fs.rmSync(profilePath, { force: true });\n\t}\n}\n\nfunction replaceProfileFiles(profilesDir: string, profiles: Record<string, unknown>): void {\n\tfs.mkdirSync(profilesDir, { recursive: true });\n\tclearProfileFiles(profilesDir);\n\tfor (const [filename, content] of Object.entries(profiles)) {\n\t\tconst serialized = JSON.stringify(content, null, 2);\n\t\tif (serialized === undefined) throw new Error(`Invalid backup file: profile \"${filename}\" is not serializable`);\n\t\tfs.writeFileSync(path.join(profilesDir, filename), serialized, \"utf-8\");\n\t}\n}\n\nexport function restoreProfileFiles(profilesDir: string, snapshot: ProfileFilesSnapshot): void {\n\tclearProfileFiles(profilesDir);\n\tif (snapshot.files.size > 0) fs.mkdirSync(profilesDir, { recursive: true });\n\tfor (const [filename, content] of snapshot.files) {\n\t\tfs.writeFileSync(path.join(profilesDir, filename), content, \"utf-8\");\n\t}\n\tif (!snapshot.directoryExisted && fs.existsSync(profilesDir) && fs.readdirSync(profilesDir).length === 0) {\n\t\tfs.rmdirSync(profilesDir);\n\t}\n}\n\nfunction profileConfigurationFromSettings(settings: Settings): GlobalResourceProfileConfiguration {\n\treturn {\n\t\tresourceProfiles: settings.resourceProfiles,\n\t\tactiveResourceProfile: settings.activeResourceProfile,\n\t\tactiveResourceProfiles: settings.activeResourceProfiles,\n\t\texternalResourceRoots: settings.externalResourceRoots,\n\t\ttrustedResourceRoots: settings.trustedResourceRoots,\n\t};\n}\n\nexport async function handleConfigBackupCommand(host: ConfigBackupHost, fileArg?: string): Promise<void> {\n\ttry {\n\t\tconst profilesDir = path.join(getAgentDir(), \"profiles\");\n\t\tconst profiles: Record<string, unknown> = {};\n\t\tif (fs.existsSync(profilesDir)) {\n\t\t\tconst entries = fs.readdirSync(profilesDir);\n\t\t\tfor (const entry of entries) {\n\t\t\t\tif (entry.endsWith(\".json\")) {\n\t\t\t\t\tconst pPath = path.join(profilesDir, entry);\n\t\t\t\t\ttry {\n\t\t\t\t\t\tif (!fs.lstatSync(pPath).isFile()) continue;\n\t\t\t\t\t\tconst content = fs.readFileSync(pPath, \"utf-8\");\n\t\t\t\t\t\tprofiles[entry] = JSON.parse(content);\n\t\t\t\t\t} catch {\n\t\t\t\t\t\t// skip\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tconst globalSettings = host.settingsManager.getGlobalSettings();\n\t\tconst backupData = {\n\t\t\tversion: 2,\n\t\t\tprofiles,\n\t\t\tsettings: {\n\t\t\t\tresourceProfiles: globalSettings.resourceProfiles ?? {},\n\t\t\t\tactiveResourceProfiles: canonicalActiveResourceProfiles(globalSettings),\n\t\t\t\texternalResourceRoots: globalSettings.externalResourceRoots ?? [],\n\t\t\t\ttrustedResourceRoots: globalSettings.trustedResourceRoots ?? [],\n\t\t\t},\n\t\t};\n\n\t\tlet targetFile = fileArg;\n\t\tif (!targetFile) {\n\t\t\tconst backupsDir = path.join(getAgentDir(), \"backups\");\n\t\t\tfs.mkdirSync(backupsDir, { recursive: true });\n\t\t\tconst timestamp = new Date().toISOString().replace(/[:.]/g, \"-\");\n\t\t\ttargetFile = path.join(backupsDir, `config-${timestamp}.json`);\n\t\t} else {\n\t\t\tconst resolved = host.settingsManager.canonicalizePath(targetFile);\n\t\t\tif (resolved) {\n\t\t\t\ttargetFile = resolved;\n\t\t\t}\n\t\t}\n\n\t\tfs.mkdirSync(path.dirname(targetFile), { recursive: true });\n\t\tfs.writeFileSync(targetFile, JSON.stringify(backupData, null, 2), \"utf-8\");\n\t\thost.showStatus(`Configuration backup saved to ${targetFile}`);\n\t} catch (error) {\n\t\thost.showError(error instanceof Error ? error.message : String(error));\n\t}\n}\n\nexport async function handleConfigRestoreCommand(host: ConfigRestoreHost, fileArg: string): Promise<void> {\n\ttry {\n\t\tconst trimmed = fileArg.trim();\n\t\tif (!trimmed) {\n\t\t\thost.showError(\"Usage: /config-restore <file>\");\n\t\t\treturn;\n\t\t}\n\n\t\tconst resolved = host.settingsManager.canonicalizePath(trimmed);\n\t\tif (!resolved || !fs.existsSync(resolved)) {\n\t\t\thost.showError(`Backup file does not exist: ${trimmed}`);\n\t\t\treturn;\n\t\t}\n\n\t\tlet bundle: unknown;\n\t\ttry {\n\t\t\tconst content = fs.readFileSync(resolved, \"utf-8\");\n\t\t\tbundle = JSON.parse(content);\n\t\t} catch (error) {\n\t\t\thost.showError(`Failed to parse backup file: ${error instanceof Error ? error.message : String(error)}`);\n\t\t\treturn;\n\t\t}\n\n\t\tif (!isRecord(bundle)) {\n\t\t\thost.showError(\"Invalid backup file: must be a JSON object\");\n\t\t\treturn;\n\t\t}\n\n\t\tlet profileFiles: Record<string, unknown>;\n\t\tlet restoredResourceProfiles: Record<string, ProfileDefinitionInput>;\n\t\tlet restoredActiveProfiles: string[];\n\t\tlet restoredExternalRoots: string[];\n\t\ttry {\n\t\t\tprofileFiles = decodeProfileFiles(bundle.profiles);\n\t\t\tconst backupSettings = isRecord(bundle.settings) ? bundle.settings : {};\n\t\t\trestoredResourceProfiles = decodeResourceProfiles(backupSettings.resourceProfiles);\n\t\t\trestoredActiveProfiles = decodeActiveResourceProfiles(backupSettings);\n\t\t\trestoredExternalRoots = decodeStringArray(\n\t\t\t\t\"settings.externalResourceRoots\",\n\t\t\t\tbackupSettings.externalResourceRoots ?? [],\n\t\t\t);\n\t\t} catch (error) {\n\t\t\thost.showError(error instanceof Error ? error.message : String(error));\n\t\t\treturn;\n\t\t}\n\n\t\t// Confirm before clobbering\n\t\tconst confirm = await new Promise<boolean>((resolve) => {\n\t\t\thost.showSelector((done) => {\n\t\t\t\tlet settled = false;\n\t\t\t\tconst settle = (value: boolean) => {\n\t\t\t\t\tif (settled) return;\n\t\t\t\t\tsettled = true;\n\t\t\t\t\tresolve(value);\n\t\t\t\t};\n\t\t\t\tconst submenu = new SelectSubmenu(\n\t\t\t\t\t\"Restore configuration?\",\n\t\t\t\t\t\"This will overwrite existing local profiles and settings with the backup values. Do you want to continue?\",\n\t\t\t\t\t[\n\t\t\t\t\t\t{ value: \"yes\", label: \"Yes\", description: \"Proceed with restoration.\" },\n\t\t\t\t\t\t{ value: \"no\", label: \"No\", description: \"Cancel and abort.\" },\n\t\t\t\t\t],\n\t\t\t\t\t\"no\",\n\t\t\t\t\t(value) => {\n\t\t\t\t\t\tdone();\n\t\t\t\t\t\tsettle(value === \"yes\");\n\t\t\t\t\t},\n\t\t\t\t\t() => {\n\t\t\t\t\t\tdone();\n\t\t\t\t\t\tsettle(false);\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t\treturn {\n\t\t\t\t\tcomponent: submenu,\n\t\t\t\t\tfocus: submenu.getSelectList(),\n\t\t\t\t\tonSuperseded: () => settle(false),\n\t\t\t\t};\n\t\t\t});\n\t\t});\n\n\t\tif (!confirm) {\n\t\t\thost.showStatus(\"Restore aborted.\");\n\t\t\treturn;\n\t\t}\n\n\t\tconst profilesDir = path.join(getAgentDir(), \"profiles\");\n\t\tconst profileFilesSnapshot = captureProfileFiles(profilesDir);\n\t\tconst settingsSnapshot = host.settingsManager.createReloadSnapshot();\n\t\tlet restoreStarted = false;\n\t\ttry {\n\t\t\trestoreStarted = true;\n\t\t\treplaceProfileFiles(profilesDir, profileFiles);\n\t\t\tconst currentTrusted = settingsSnapshot.globalSettings.trustedResourceRoots ?? [];\n\t\t\thost.settingsManager.replaceGlobalResourceProfileConfiguration({\n\t\t\t\tresourceProfiles: Object.keys(restoredResourceProfiles).length > 0 ? restoredResourceProfiles : undefined,\n\t\t\t\tactiveResourceProfiles: restoredActiveProfiles,\n\t\t\t\texternalResourceRoots: restoredExternalRoots,\n\t\t\t\t// Security boundary: roots named by a backup never become trusted through restore.\n\t\t\t\ttrustedResourceRoots: currentTrusted.filter((root) => !restoredExternalRoots.includes(root)),\n\t\t\t});\n\t\t\tawait host.settingsManager.flush();\n\t\t\tif (!(await host.handleReloadCommand())) {\n\t\t\t\tthrow new Error(\"restored configuration failed runtime validation\");\n\t\t\t}\n\t\t\thost.showStatus(\"Configuration restored successfully.\");\n\t\t} catch (error) {\n\t\t\tlet rollbackError: unknown;\n\t\t\tif (restoreStarted) {\n\t\t\t\ttry {\n\t\t\t\t\trestoreProfileFiles(profilesDir, profileFilesSnapshot);\n\t\t\t\t\thost.settingsManager.replaceGlobalResourceProfileConfiguration(\n\t\t\t\t\t\tprofileConfigurationFromSettings(settingsSnapshot.globalSettings),\n\t\t\t\t\t);\n\t\t\t\t\tawait host.settingsManager.flush();\n\t\t\t\t\thost.settingsManager.restoreReloadSnapshot(settingsSnapshot);\n\t\t\t\t} catch (restoreError) {\n\t\t\t\t\trollbackError = restoreError;\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\thost.showError(\n\t\t\t\trollbackError\n\t\t\t\t\t? `Configuration restore failed: ${message}; rollback failed: ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)}`\n\t\t\t\t\t: `Configuration restore failed: ${message}; previous configuration restored`,\n\t\t\t);\n\t\t}\n\t} catch (error) {\n\t\thost.showError(error instanceof Error ? error.message : String(error));\n\t}\n}\n"]}
1
+ {"version":3,"file":"config-backup.js","sourceRoot":"","sources":["../../../src/modes/interactive/config-backup.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Config backup/restore commands extracted from interactive-mode.\n *\n * `handleConfigBackupCommand` snapshots the reusable-file profiles and the\n * resource-relevant settings to a JSON bundle; `handleConfigRestoreCommand`\n * reads such a bundle back, confirming with the user before clobbering local\n * state. Both operate through a narrow `host` seam (settings manager + the\n * status/error/selector callbacks) so the file IO and validation live outside\n * the god file while interactive-mode keeps only thin delegating wrappers.\n */\n\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport type { Component } from \"@caupulican/pi-tui\";\nimport { getAgentDir, getProfilesDir } from \"../../config.ts\";\nimport { configBackupsDir } from \"../../core/agent-paths.ts\";\nimport type {\n\tGlobalResourceProfileConfiguration,\n\tModelRouterSettings,\n\tProfileDefinitionInput,\n\tResourceProfileSettings,\n\tSettings,\n\tSettingsManager,\n\tThinkingLevel,\n} from \"../../core/settings-manager.ts\";\nimport { SelectSubmenu } from \"./components/settings-selector.ts\";\n\nexport interface ConfigBackupHost {\n\treadonly settingsManager: SettingsManager;\n\tshowStatus(message: string): void;\n\tshowError(errorMessage: string): void;\n}\n\nexport interface ConfigRestoreHost extends ConfigBackupHost {\n\tshowSelector(\n\t\tcreate: (done: () => void) => { component: Component; focus: Component; onSuperseded?: () => void },\n\t): void;\n\thandleReloadCommand(): Promise<boolean>;\n}\n\nconst PROFILE_THINKING_LEVELS = new Set<ThinkingLevel>([\n\t\"off\",\n\t\"minimal\",\n\t\"low\",\n\t\"medium\",\n\t\"high\",\n\t\"xhigh\",\n\t\"max\",\n\t\"ultra\",\n]);\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn Boolean(value) && typeof value === \"object\" && !Array.isArray(value);\n}\n\nfunction decodeBackupProfile(name: string, value: unknown): ProfileDefinitionInput | undefined {\n\tif (!isRecord(value)) return undefined;\n\tconst isWrapper = Object.hasOwn(value, \"resources\");\n\tconst resources = isWrapper ? value.resources : value;\n\tif (!isRecord(resources)) return undefined;\n\tconst thinking =\n\t\ttypeof value.thinking === \"string\" && PROFILE_THINKING_LEVELS.has(value.thinking as ThinkingLevel)\n\t\t\t? (value.thinking as ThinkingLevel)\n\t\t\t: undefined;\n\treturn {\n\t\tname,\n\t\tdescription: typeof value.description === \"string\" ? value.description : undefined,\n\t\tmodel: typeof value.model === \"string\" ? value.model : undefined,\n\t\tthinking,\n\t\tmodelRouter: isRecord(value.modelRouter) ? (value.modelRouter as ModelRouterSettings) : undefined,\n\t\tsoul: typeof value.soul === \"string\" ? value.soul : undefined,\n\t\tresources: resources as ResourceProfileSettings,\n\t};\n}\n\nexport interface ProfileFilesSnapshot {\n\tdirectoryExisted: boolean;\n\tfiles: Map<string, string>;\n}\n\nfunction canonicalActiveResourceProfiles(settings: Settings): string[] {\n\tconst values = settings.activeResourceProfiles ?? settings.activeResourceProfile ?? [];\n\tconst entries = Array.isArray(values) ? values : [values];\n\treturn [...new Set(entries.map((entry) => entry.trim()).filter((entry) => entry.length > 0))];\n}\n\nfunction decodeStringArray(label: string, value: unknown): string[] {\n\tif (!Array.isArray(value) || value.some((entry) => typeof entry !== \"string\")) {\n\t\tthrow new Error(`Invalid backup file: ${label} must be an array of strings`);\n\t}\n\treturn [...new Set(value.map((entry) => entry.trim()).filter((entry) => entry.length > 0))];\n}\n\nfunction decodeActiveResourceProfiles(settings: Record<string, unknown>): string[] {\n\tif (Object.hasOwn(settings, \"activeResourceProfiles\")) {\n\t\treturn decodeStringArray(\"settings.activeResourceProfiles\", settings.activeResourceProfiles);\n\t}\n\tconst legacy = settings.activeResourceProfile;\n\tif (legacy === undefined) return [];\n\tif (typeof legacy === \"string\") return legacy.trim() ? [legacy.trim()] : [];\n\treturn decodeStringArray(\"settings.activeResourceProfile\", legacy);\n}\n\nfunction decodeResourceProfiles(value: unknown): Record<string, ProfileDefinitionInput> {\n\tif (value === undefined) return {};\n\tif (!isRecord(value)) {\n\t\tthrow new Error(\"Invalid backup file: settings.resourceProfiles must be an object\");\n\t}\n\tconst profiles: Record<string, ProfileDefinitionInput> = {};\n\tfor (const [name, definition] of Object.entries(value)) {\n\t\tconst decoded = decodeBackupProfile(name, definition);\n\t\tif (!decoded) throw new Error(`Invalid backup file: malformed resource profile \"${name}\"`);\n\t\tprofiles[name] = decoded;\n\t}\n\treturn profiles;\n}\n\nfunction decodeProfileFiles(value: unknown): Record<string, unknown> {\n\tif (value === undefined) return {};\n\tif (!isRecord(value)) throw new Error(\"Invalid backup file: profiles must be an object\");\n\tfor (const filename of Object.keys(value)) {\n\t\tif (path.basename(filename) !== filename || !filename.endsWith(\".json\")) {\n\t\t\tthrow new Error(`Invalid backup file: unsafe profile filename \"${filename}\"`);\n\t\t}\n\t}\n\treturn value;\n}\n\nexport function captureProfileFiles(profilesDir: string): ProfileFilesSnapshot {\n\tconst files = new Map<string, string>();\n\tif (!fs.existsSync(profilesDir)) return { directoryExisted: false, files };\n\tfor (const filename of fs.readdirSync(profilesDir)) {\n\t\tif (!filename.endsWith(\".json\")) continue;\n\t\tconst profilePath = path.join(profilesDir, filename);\n\t\tif (!fs.lstatSync(profilePath).isFile()) continue;\n\t\tfiles.set(filename, fs.readFileSync(profilePath, \"utf-8\"));\n\t}\n\treturn { directoryExisted: true, files };\n}\n\nfunction clearProfileFiles(profilesDir: string): void {\n\tif (!fs.existsSync(profilesDir)) return;\n\tfor (const filename of fs.readdirSync(profilesDir)) {\n\t\tif (!filename.endsWith(\".json\")) continue;\n\t\tconst profilePath = path.join(profilesDir, filename);\n\t\tconst stat = fs.lstatSync(profilePath);\n\t\tif (stat.isFile() || stat.isSymbolicLink()) fs.rmSync(profilePath, { force: true });\n\t}\n}\n\nfunction replaceProfileFiles(profilesDir: string, profiles: Record<string, unknown>): void {\n\tfs.mkdirSync(profilesDir, { recursive: true });\n\tclearProfileFiles(profilesDir);\n\tfor (const [filename, content] of Object.entries(profiles)) {\n\t\tconst serialized = JSON.stringify(content, null, 2);\n\t\tif (serialized === undefined) throw new Error(`Invalid backup file: profile \"${filename}\" is not serializable`);\n\t\tfs.writeFileSync(path.join(profilesDir, filename), serialized, \"utf-8\");\n\t}\n}\n\nexport function restoreProfileFiles(profilesDir: string, snapshot: ProfileFilesSnapshot): void {\n\tclearProfileFiles(profilesDir);\n\tif (snapshot.files.size > 0) fs.mkdirSync(profilesDir, { recursive: true });\n\tfor (const [filename, content] of snapshot.files) {\n\t\tfs.writeFileSync(path.join(profilesDir, filename), content, \"utf-8\");\n\t}\n\tif (!snapshot.directoryExisted && fs.existsSync(profilesDir) && fs.readdirSync(profilesDir).length === 0) {\n\t\tfs.rmdirSync(profilesDir);\n\t}\n}\n\nfunction profileConfigurationFromSettings(settings: Settings): GlobalResourceProfileConfiguration {\n\treturn {\n\t\tresourceProfiles: settings.resourceProfiles,\n\t\tactiveResourceProfile: settings.activeResourceProfile,\n\t\tactiveResourceProfiles: settings.activeResourceProfiles,\n\t\texternalResourceRoots: settings.externalResourceRoots,\n\t\ttrustedResourceRoots: settings.trustedResourceRoots,\n\t};\n}\n\nexport async function handleConfigBackupCommand(host: ConfigBackupHost, fileArg?: string): Promise<void> {\n\ttry {\n\t\tconst profilesDir = getProfilesDir();\n\t\tconst profiles: Record<string, unknown> = {};\n\t\tif (fs.existsSync(profilesDir)) {\n\t\t\tconst entries = fs.readdirSync(profilesDir);\n\t\t\tfor (const entry of entries) {\n\t\t\t\tif (entry.endsWith(\".json\")) {\n\t\t\t\t\tconst pPath = path.join(profilesDir, entry);\n\t\t\t\t\ttry {\n\t\t\t\t\t\tif (!fs.lstatSync(pPath).isFile()) continue;\n\t\t\t\t\t\tconst content = fs.readFileSync(pPath, \"utf-8\");\n\t\t\t\t\t\tprofiles[entry] = JSON.parse(content);\n\t\t\t\t\t} catch {\n\t\t\t\t\t\t// skip\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tconst globalSettings = host.settingsManager.getGlobalSettings();\n\t\tconst backupData = {\n\t\t\tversion: 2,\n\t\t\tprofiles,\n\t\t\tsettings: {\n\t\t\t\tresourceProfiles: globalSettings.resourceProfiles ?? {},\n\t\t\t\tactiveResourceProfiles: canonicalActiveResourceProfiles(globalSettings),\n\t\t\t\texternalResourceRoots: globalSettings.externalResourceRoots ?? [],\n\t\t\t\ttrustedResourceRoots: globalSettings.trustedResourceRoots ?? [],\n\t\t\t},\n\t\t};\n\n\t\tlet targetFile = fileArg;\n\t\tif (!targetFile) {\n\t\t\tconst backupsDir = configBackupsDir(getAgentDir());\n\t\t\tfs.mkdirSync(backupsDir, { recursive: true });\n\t\t\tconst timestamp = new Date().toISOString().replace(/[:.]/g, \"-\");\n\t\t\ttargetFile = path.join(backupsDir, `config-${timestamp}.json`);\n\t\t} else {\n\t\t\tconst resolved = host.settingsManager.canonicalizePath(targetFile);\n\t\t\tif (resolved) {\n\t\t\t\ttargetFile = resolved;\n\t\t\t}\n\t\t}\n\n\t\tfs.mkdirSync(path.dirname(targetFile), { recursive: true });\n\t\tfs.writeFileSync(targetFile, JSON.stringify(backupData, null, 2), \"utf-8\");\n\t\thost.showStatus(`Configuration backup saved to ${targetFile}`);\n\t} catch (error) {\n\t\thost.showError(error instanceof Error ? error.message : String(error));\n\t}\n}\n\nexport async function handleConfigRestoreCommand(host: ConfigRestoreHost, fileArg: string): Promise<void> {\n\ttry {\n\t\tconst trimmed = fileArg.trim();\n\t\tif (!trimmed) {\n\t\t\thost.showError(\"Usage: /config-restore <file>\");\n\t\t\treturn;\n\t\t}\n\n\t\tconst resolved = host.settingsManager.canonicalizePath(trimmed);\n\t\tif (!resolved || !fs.existsSync(resolved)) {\n\t\t\thost.showError(`Backup file does not exist: ${trimmed}`);\n\t\t\treturn;\n\t\t}\n\n\t\tlet bundle: unknown;\n\t\ttry {\n\t\t\tconst content = fs.readFileSync(resolved, \"utf-8\");\n\t\t\tbundle = JSON.parse(content);\n\t\t} catch (error) {\n\t\t\thost.showError(`Failed to parse backup file: ${error instanceof Error ? error.message : String(error)}`);\n\t\t\treturn;\n\t\t}\n\n\t\tif (!isRecord(bundle)) {\n\t\t\thost.showError(\"Invalid backup file: must be a JSON object\");\n\t\t\treturn;\n\t\t}\n\n\t\tlet profileFiles: Record<string, unknown>;\n\t\tlet restoredResourceProfiles: Record<string, ProfileDefinitionInput>;\n\t\tlet restoredActiveProfiles: string[];\n\t\tlet restoredExternalRoots: string[];\n\t\ttry {\n\t\t\tprofileFiles = decodeProfileFiles(bundle.profiles);\n\t\t\tconst backupSettings = isRecord(bundle.settings) ? bundle.settings : {};\n\t\t\trestoredResourceProfiles = decodeResourceProfiles(backupSettings.resourceProfiles);\n\t\t\trestoredActiveProfiles = decodeActiveResourceProfiles(backupSettings);\n\t\t\trestoredExternalRoots = decodeStringArray(\n\t\t\t\t\"settings.externalResourceRoots\",\n\t\t\t\tbackupSettings.externalResourceRoots ?? [],\n\t\t\t);\n\t\t} catch (error) {\n\t\t\thost.showError(error instanceof Error ? error.message : String(error));\n\t\t\treturn;\n\t\t}\n\n\t\t// Confirm before clobbering\n\t\tconst confirm = await new Promise<boolean>((resolve) => {\n\t\t\thost.showSelector((done) => {\n\t\t\t\tlet settled = false;\n\t\t\t\tconst settle = (value: boolean) => {\n\t\t\t\t\tif (settled) return;\n\t\t\t\t\tsettled = true;\n\t\t\t\t\tresolve(value);\n\t\t\t\t};\n\t\t\t\tconst submenu = new SelectSubmenu(\n\t\t\t\t\t\"Restore configuration?\",\n\t\t\t\t\t\"This will overwrite existing local profiles and settings with the backup values. Do you want to continue?\",\n\t\t\t\t\t[\n\t\t\t\t\t\t{ value: \"yes\", label: \"Yes\", description: \"Proceed with restoration.\" },\n\t\t\t\t\t\t{ value: \"no\", label: \"No\", description: \"Cancel and abort.\" },\n\t\t\t\t\t],\n\t\t\t\t\t\"no\",\n\t\t\t\t\t(value) => {\n\t\t\t\t\t\tdone();\n\t\t\t\t\t\tsettle(value === \"yes\");\n\t\t\t\t\t},\n\t\t\t\t\t() => {\n\t\t\t\t\t\tdone();\n\t\t\t\t\t\tsettle(false);\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t\treturn {\n\t\t\t\t\tcomponent: submenu,\n\t\t\t\t\tfocus: submenu.getSelectList(),\n\t\t\t\t\tonSuperseded: () => settle(false),\n\t\t\t\t};\n\t\t\t});\n\t\t});\n\n\t\tif (!confirm) {\n\t\t\thost.showStatus(\"Restore aborted.\");\n\t\t\treturn;\n\t\t}\n\n\t\tconst profilesDir = getProfilesDir();\n\t\tconst profileFilesSnapshot = captureProfileFiles(profilesDir);\n\t\tconst settingsSnapshot = host.settingsManager.createReloadSnapshot();\n\t\tlet restoreStarted = false;\n\t\ttry {\n\t\t\trestoreStarted = true;\n\t\t\treplaceProfileFiles(profilesDir, profileFiles);\n\t\t\tconst currentTrusted = settingsSnapshot.globalSettings.trustedResourceRoots ?? [];\n\t\t\thost.settingsManager.replaceGlobalResourceProfileConfiguration({\n\t\t\t\tresourceProfiles: Object.keys(restoredResourceProfiles).length > 0 ? restoredResourceProfiles : undefined,\n\t\t\t\tactiveResourceProfiles: restoredActiveProfiles,\n\t\t\t\texternalResourceRoots: restoredExternalRoots,\n\t\t\t\t// Security boundary: roots named by a backup never become trusted through restore.\n\t\t\t\ttrustedResourceRoots: currentTrusted.filter((root) => !restoredExternalRoots.includes(root)),\n\t\t\t});\n\t\t\tawait host.settingsManager.flush();\n\t\t\tif (!(await host.handleReloadCommand())) {\n\t\t\t\tthrow new Error(\"restored configuration failed runtime validation\");\n\t\t\t}\n\t\t\thost.showStatus(\"Configuration restored successfully.\");\n\t\t} catch (error) {\n\t\t\tlet rollbackError: unknown;\n\t\t\tif (restoreStarted) {\n\t\t\t\ttry {\n\t\t\t\t\trestoreProfileFiles(profilesDir, profileFilesSnapshot);\n\t\t\t\t\thost.settingsManager.replaceGlobalResourceProfileConfiguration(\n\t\t\t\t\t\tprofileConfigurationFromSettings(settingsSnapshot.globalSettings),\n\t\t\t\t\t);\n\t\t\t\t\tawait host.settingsManager.flush();\n\t\t\t\t\thost.settingsManager.restoreReloadSnapshot(settingsSnapshot);\n\t\t\t\t} catch (restoreError) {\n\t\t\t\t\trollbackError = restoreError;\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\thost.showError(\n\t\t\t\trollbackError\n\t\t\t\t\t? `Configuration restore failed: ${message}; rollback failed: ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)}`\n\t\t\t\t\t: `Configuration restore failed: ${message}; previous configuration restored`,\n\t\t\t);\n\t\t}\n\t} catch (error) {\n\t\thost.showError(error instanceof Error ? error.message : String(error));\n\t}\n}\n"]}
@@ -19,6 +19,7 @@ import type { AgentSession } from "../../core/agent-session.ts";
19
19
  import type { AutocompleteProviderFactory, ExtensionRunner, ExtensionUIContext, ExtensionUIDialogOptions } from "../../core/extensions/index.ts";
20
20
  import type { FooterDataProvider } from "../../core/footer-data-provider.ts";
21
21
  import type { KeybindingsManager } from "../../core/keybindings.ts";
22
+ import type { SessionImageStore } from "../../core/session-image-store.ts";
22
23
  import type { CustomEditor } from "./components/custom-editor.ts";
23
24
  import type { FooterComponent } from "./components/footer.ts";
24
25
  import type { EditorOverlayHost } from "./editor-overlay-host.ts";
@@ -43,6 +44,7 @@ export interface ExtensionUiHostUi {
43
44
  setEditor(editor: EditorComponent): void;
44
45
  getBuiltInHeader(): Component | undefined;
45
46
  getAutocompleteProvider(): AutocompleteProvider | undefined;
47
+ getClipboardImageStore(): Pick<SessionImageStore, "write"> | undefined;
46
48
  getToolsExpanded(): boolean;
47
49
  pushAutocompleteProviderWrapper(factory: AutocompleteProviderFactory): void;
48
50
  resetAutocompleteProviderWrappers(): void;
@@ -66,6 +68,10 @@ export interface ExtensionUiHostDeps {
66
68
  getSession(): AgentSession;
67
69
  ui: ExtensionUiHostUi;
68
70
  }
71
+ type DisposableComponent = Component & {
72
+ dispose?(): void;
73
+ };
74
+ type WidgetPlacement = "aboveEditor" | "belowEditor";
69
75
  export declare class ExtensionUiHost {
70
76
  private readonly deps;
71
77
  private extensionSelector;
@@ -76,6 +82,8 @@ export declare class ExtensionUiHost {
76
82
  private extensionTerminalInputUnsubscribers;
77
83
  private extensionWidgetsAbove;
78
84
  private extensionWidgetsBelow;
85
+ private hostWidgetsAbove;
86
+ private hostWidgetsBelow;
79
87
  private customFooter;
80
88
  private customHeader;
81
89
  private editorComponentFactory;
@@ -95,6 +103,10 @@ export declare class ExtensionUiHost {
95
103
  */
96
104
  private setExtensionStatus;
97
105
  private static readonly MAX_WIDGET_LINES;
106
+ private removeWidget;
107
+ /** Mount a native host widget without coupling it to extension reload lifecycle. */
108
+ setHostWidget(key: string, component: DisposableComponent | undefined, placement: WidgetPlacement): void;
109
+ clearHostWidgets(): void;
98
110
  /**
99
111
  * Set an extension widget (string array or custom component).
100
112
  */
@@ -120,6 +132,7 @@ export declare class ExtensionUiHost {
120
132
  * Create the ExtensionUIContext for extensions.
121
133
  */
122
134
  createExtensionUIContext(): ExtensionUIContext;
135
+ private showHumanInput;
123
136
  /**
124
137
  * Show a selector for extensions.
125
138
  */
@@ -163,4 +176,5 @@ export declare class ExtensionUiHost {
163
176
  */
164
177
  showExtensionError(extensionPath: string, error: string, stack?: string): void;
165
178
  }
179
+ export {};
166
180
  //# sourceMappingURL=extension-ui-host.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"extension-ui-host.d.ts","sourceRoot":"","sources":["../../../src/modes/interactive/extension-ui-host.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Extension UI host.\n *\n * Extracted verbatim from interactive-mode.ts (god-file decomposition). Owns the interactive-mode\n * surface of the extension UI API: extension-provided dialogs (selector / confirm / input / multi-line\n * editor / custom component), widget mounting above and below the editor, extension status text, custom\n * footer and header swaps, the custom editor-component swap, keyboard-shortcut wiring, terminal-input\n * listeners, and the `ExtensionUIContext` handed to extensions. It owns exactly the state this cluster\n * mutates (the live dialog components, the widget maps, the terminal-input unsubscribers, the custom\n * footer/header, and the current custom-editor factory) and reaches everything else — the mutable\n * `editor`, the render-core containers, the working-indicator, autocomplete wiring, theme/tools state —\n * through a narrow deps/ui surface rather than the whole InteractiveMode instance. Extensions must see\n * identical behavior, so the render-core operations (working indicator, hidden-thinking label,\n * autocomplete rebuild) stay host-side and are invoked here as delegations.\n */\n\nimport type {\n\tAutocompleteProvider,\n\tComponent,\n\tEditorComponent,\n\tKeyId,\n\tLoaderIndicatorOptions,\n\tOverlayHandle,\n\tOverlayOptions,\n} from \"@caupulican/pi-tui\";\nimport { Container, matchesKey, Spacer, Text, type TUI } from \"@caupulican/pi-tui\";\nimport type { AgentSession } from \"../../core/agent-session.ts\";\nimport type {\n\tAutocompleteProviderFactory,\n\tEditorFactory,\n\tExtensionContext,\n\tExtensionRunner,\n\tExtensionUIContext,\n\tExtensionUIDialogOptions,\n\tExtensionWidgetOptions,\n} from \"../../core/extensions/index.ts\";\nimport type { FooterDataProvider, ReadonlyFooterDataProvider } from \"../../core/footer-data-provider.ts\";\nimport type { KeybindingsManager } from \"../../core/keybindings.ts\";\nimport type { CustomEditor } from \"./components/custom-editor.ts\";\nimport { ExtensionEditorComponent } from \"./components/extension-editor.ts\";\nimport { ExtensionInputComponent } from \"./components/extension-input.ts\";\nimport { ExtensionSelectorComponent } from \"./components/extension-selector.ts\";\nimport type { FooterComponent } from \"./components/footer.ts\";\nimport type { EditorOverlayHost } from \"./editor-overlay-host.ts\";\nimport {\n\tgetAvailableThemesWithPaths,\n\tgetEditorTheme,\n\tgetThemeByName,\n\tsetTheme,\n\tsetThemeInstance,\n\tTheme,\n\ttheme,\n} from \"./theme/theme.ts\";\n\n/** Components that can be expanded/collapsed (custom headers may opt in). */\ninterface Expandable {\n\tsetExpanded(expanded: boolean): void;\n}\n\nfunction isExpandable(obj: unknown): obj is Expandable {\n\treturn typeof obj === \"object\" && obj !== null && \"setExpanded\" in obj && typeof obj.setExpanded === \"function\";\n}\n\n/**\n * The narrow slice of InteractiveMode the extension UI host drives. Stable containers and the TUI are\n * passed directly; mutable render-core state (the active editor, built-in header, loading animation,\n * autocomplete wrappers, tools-expansion) is read/written through accessors so InteractiveMode keeps\n * ownership; and render-core / status operations stay host-side and are exposed here as callbacks.\n */\nexport interface ExtensionUiHostUi {\n\treadonly tui: TUI;\n\treadonly overlayHost: EditorOverlayHost;\n\treadonly keybindings: KeybindingsManager;\n\treadonly footer: FooterComponent;\n\treadonly footerDataProvider: FooterDataProvider;\n\treadonly headerContainer: Container;\n\treadonly chatContainer: Container;\n\treadonly defaultEditor: CustomEditor;\n\treadonly widgetContainerAbove: Container;\n\treadonly widgetContainerBelow: Container;\n\n\tgetEditor(): EditorComponent;\n\tsetEditor(editor: EditorComponent): void;\n\tgetBuiltInHeader(): Component | undefined;\n\tgetAutocompleteProvider(): AutocompleteProvider | undefined;\n\tgetToolsExpanded(): boolean;\n\tpushAutocompleteProviderWrapper(factory: AutocompleteProviderFactory): void;\n\tresetAutocompleteProviderWrappers(): void;\n\n\tsetupAutocompleteProvider(): void;\n\tsetWorkingVisible(visible: boolean): void;\n\tsetWorkingIndicator(options?: LoaderIndicatorOptions): void;\n\tsetHiddenThinkingLabel(label?: string): void;\n\tsetWorkingMessage(message: string | undefined): void;\n\tresetWorkingIndicators(): void;\n\tsetToolsExpanded(expanded: boolean): void;\n\ttoggleToolsExpanded(): void;\n\tupdateTerminalTitle(): void;\n\tmarkShutdownRequested(): void;\n\tabort(): void;\n\treload(): Promise<void>;\n\tshowStatus(message: string): void;\n\tshowWarning(message: string): void;\n\tshowError(message: string): void;\n}\n\nexport interface ExtensionUiHostDeps {\n\tgetSession(): AgentSession;\n\tui: ExtensionUiHostUi;\n}\n\nexport class ExtensionUiHost {\n\tprivate readonly deps: ExtensionUiHostDeps;\n\n\t// Extension UI state\n\tprivate extensionSelector: ExtensionSelectorComponent | undefined = undefined;\n\tprivate extensionInput: ExtensionInputComponent | undefined = undefined;\n\tprivate extensionEditor: ExtensionEditorComponent | undefined = undefined;\n\t/** Settles the mounted extension dialog before another dialog can replace its overlay. */\n\tprivate activeExtensionDialogCancel: (() => void) | undefined;\n\tprivate extensionTerminalInputUnsubscribers = new Set<() => void>();\n\n\t// Extension widgets (components rendered above/below the editor)\n\tprivate extensionWidgetsAbove = new Map<string, Component & { dispose?(): void }>();\n\tprivate extensionWidgetsBelow = new Map<string, Component & { dispose?(): void }>();\n\n\t// Custom footer from extension (undefined = use built-in footer)\n\tprivate customFooter: (Component & { dispose?(): void }) | undefined = undefined;\n\n\t// Custom header from extension (undefined = use built-in header)\n\tprivate customHeader: (Component & { dispose?(): void }) | undefined = undefined;\n\n\t// Current custom editor-component factory (undefined = default editor)\n\tprivate editorComponentFactory: EditorFactory | undefined;\n\n\tconstructor(deps: ExtensionUiHostDeps) {\n\t\tthis.deps = deps;\n\t}\n\n\tprivate get session(): AgentSession {\n\t\treturn this.deps.getSession();\n\t}\n\tprivate get ui(): ExtensionUiHostUi {\n\t\treturn this.deps.ui;\n\t}\n\n\t/** The active custom header, if an extension has installed one. */\n\tgetCustomHeader(): (Component & { dispose?(): void }) | undefined {\n\t\treturn this.customHeader;\n\t}\n\n\t/**\n\t * Set up keyboard shortcuts registered by extensions.\n\t */\n\tsetupExtensionShortcuts(extensionRunner: ExtensionRunner): void {\n\t\tconst shortcuts = extensionRunner.getShortcuts(this.ui.keybindings.getEffectiveConfig());\n\t\tif (shortcuts.size === 0) return;\n\n\t\t// Create a context for shortcut handlers\n\t\tconst createContext = (): ExtensionContext => ({\n\t\t\tui: this.createExtensionUIContext(),\n\t\t\thasUI: true,\n\t\t\tmode: \"tui\",\n\t\t\tcwd: this.session.sessionManager.getCwd(),\n\t\t\tsessionManager: this.session.sessionManager,\n\t\t\tmodelRegistry: this.session.modelRegistry,\n\t\t\tmodel: this.session.model,\n\t\t\tisIdle: () => !this.session.isStreaming,\n\t\t\tsignal: this.session.agent.signal,\n\t\t\tabort: () => {\n\t\t\t\tthis.ui.abort();\n\t\t\t},\n\t\t\thasPendingMessages: () => this.session.pendingMessageCount > 0,\n\t\t\tshutdown: () => {\n\t\t\t\tthis.ui.markShutdownRequested();\n\t\t\t},\n\t\t\tgetContextUsage: () => this.session.getContextUsage(),\n\t\t\tcompact: (options) => {\n\t\t\t\tvoid (async () => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst result = await this.session.compact(options?.customInstructions);\n\t\t\t\t\t\toptions?.onComplete?.(result);\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\tconst err = error instanceof Error ? error : new Error(String(error));\n\t\t\t\t\t\toptions?.onError?.(err);\n\t\t\t\t\t}\n\t\t\t\t})();\n\t\t\t},\n\t\t\treload: async () => {\n\t\t\t\tawait this.ui.reload();\n\t\t\t},\n\t\t\tgetSystemPrompt: () => this.session.systemPrompt,\n\t\t});\n\n\t\t// Set up the extension shortcut handler on the default editor\n\t\tthis.ui.defaultEditor.onExtensionShortcut = (data: string) => {\n\t\t\tfor (const [shortcutStr, shortcut] of shortcuts) {\n\t\t\t\t// Cast to KeyId - extension shortcuts use the same format\n\t\t\t\tif (matchesKey(data, shortcutStr as KeyId)) {\n\t\t\t\t\t// Run handler async, don't block input\n\t\t\t\t\tPromise.resolve(shortcut.handler(createContext())).catch((err) => {\n\t\t\t\t\t\tthis.ui.showError(`Shortcut handler error: ${err instanceof Error ? err.message : String(err)}`);\n\t\t\t\t\t});\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn false;\n\t\t};\n\t}\n\n\t/**\n\t * Set extension status text in the footer.\n\t */\n\tprivate setExtensionStatus(key: string, text: string | undefined): void {\n\t\tthis.ui.footerDataProvider.setExtensionStatus(key, text);\n\t\tthis.ui.tui.requestRender();\n\t}\n\n\t// Maximum total widget lines to prevent viewport overflow\n\tprivate static readonly MAX_WIDGET_LINES = 10;\n\n\t/**\n\t * Set an extension widget (string array or custom component).\n\t */\n\tprivate setExtensionWidget(\n\t\tkey: string,\n\t\tcontent: string[] | ((tui: TUI, thm: Theme) => Component & { dispose?(): void }) | undefined,\n\t\toptions?: ExtensionWidgetOptions,\n\t): void {\n\t\tconst placement = options?.placement ?? \"aboveEditor\";\n\t\tconst removeExisting = (map: Map<string, Component & { dispose?(): void }>) => {\n\t\t\tconst existing = map.get(key);\n\t\t\tif (existing?.dispose) existing.dispose();\n\t\t\tmap.delete(key);\n\t\t};\n\n\t\tremoveExisting(this.extensionWidgetsAbove);\n\t\tremoveExisting(this.extensionWidgetsBelow);\n\n\t\tif (content === undefined) {\n\t\t\tthis.renderWidgets();\n\t\t\treturn;\n\t\t}\n\n\t\tlet component: Component & { dispose?(): void };\n\n\t\tif (Array.isArray(content)) {\n\t\t\t// Wrap string array in a Container with Text components\n\t\t\tconst container = new Container();\n\t\t\tfor (const line of content.slice(0, ExtensionUiHost.MAX_WIDGET_LINES)) {\n\t\t\t\tcontainer.addChild(new Text(line, 1, 0));\n\t\t\t}\n\t\t\tif (content.length > ExtensionUiHost.MAX_WIDGET_LINES) {\n\t\t\t\tcontainer.addChild(new Text(theme.fg(\"muted\", \"... (widget truncated)\"), 1, 0));\n\t\t\t}\n\t\t\tcomponent = container;\n\t\t} else {\n\t\t\t// Factory function - create component\n\t\t\tcomponent = content(this.ui.tui, theme);\n\t\t}\n\n\t\tconst targetMap = placement === \"belowEditor\" ? this.extensionWidgetsBelow : this.extensionWidgetsAbove;\n\t\ttargetMap.set(key, component);\n\t\tthis.renderWidgets();\n\t}\n\n\tprivate clearExtensionWidgets(): void {\n\t\tfor (const widget of this.extensionWidgetsAbove.values()) {\n\t\t\twidget.dispose?.();\n\t\t}\n\t\tfor (const widget of this.extensionWidgetsBelow.values()) {\n\t\t\twidget.dispose?.();\n\t\t}\n\t\tthis.extensionWidgetsAbove.clear();\n\t\tthis.extensionWidgetsBelow.clear();\n\t\tthis.renderWidgets();\n\t}\n\n\tresetExtensionUI(): void {\n\t\t// A reset/reload must settle the active dialog before disposing its component.\n\t\tthis.activeExtensionDialogCancel?.();\n\t\tthis.activeExtensionDialogCancel = undefined;\n\t\tif (this.extensionSelector) {\n\t\t\tthis.hideExtensionSelector();\n\t\t}\n\t\tif (this.extensionInput) {\n\t\t\tthis.hideExtensionInput();\n\t\t}\n\t\tif (this.extensionEditor) {\n\t\t\tthis.hideExtensionEditor();\n\t\t}\n\t\tthis.ui.tui.hideOverlay();\n\t\tthis.clearExtensionTerminalInputListeners();\n\t\tthis.setExtensionFooter(undefined);\n\t\tthis.setExtensionHeader(undefined);\n\t\tthis.clearExtensionWidgets();\n\t\tthis.ui.footerDataProvider.clearExtensionStatuses();\n\t\tthis.ui.footer.invalidate();\n\t\tthis.ui.resetAutocompleteProviderWrappers();\n\t\tthis.setCustomEditorComponent(undefined);\n\t\tthis.ui.setupAutocompleteProvider();\n\t\tthis.ui.defaultEditor.onExtensionShortcut = undefined;\n\t\tthis.ui.updateTerminalTitle();\n\t\tthis.ui.resetWorkingIndicators();\n\t}\n\n\t/**\n\t * Render all extension widgets to the widget container.\n\t */\n\trenderWidgets(): void {\n\t\tif (!this.ui.widgetContainerAbove || !this.ui.widgetContainerBelow) return;\n\t\tthis.renderWidgetContainer(this.ui.widgetContainerAbove, this.extensionWidgetsAbove, true, true);\n\t\tthis.renderWidgetContainer(this.ui.widgetContainerBelow, this.extensionWidgetsBelow, false, false);\n\t\tthis.ui.tui.requestRender();\n\t}\n\n\tprivate renderWidgetContainer(\n\t\tcontainer: Container,\n\t\twidgets: Map<string, Component & { dispose?(): void }>,\n\t\tspacerWhenEmpty: boolean,\n\t\tleadingSpacer: boolean,\n\t): void {\n\t\tcontainer.clear();\n\n\t\tif (widgets.size === 0) {\n\t\t\tif (spacerWhenEmpty) {\n\t\t\t\tcontainer.addChild(new Spacer(1));\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tif (leadingSpacer) {\n\t\t\tcontainer.addChild(new Spacer(1));\n\t\t}\n\t\tfor (const component of widgets.values()) {\n\t\t\tcontainer.addChild(component);\n\t\t}\n\t}\n\n\t/**\n\t * Set a custom footer component, or restore the built-in footer.\n\t */\n\tprivate setExtensionFooter(\n\t\tfactory:\n\t\t\t| ((tui: TUI, thm: Theme, footerData: ReadonlyFooterDataProvider) => Component & { dispose?(): void })\n\t\t\t| undefined,\n\t): void {\n\t\t// Dispose existing custom footer\n\t\tif (this.customFooter?.dispose) {\n\t\t\tthis.customFooter.dispose();\n\t\t}\n\n\t\t// Remove current footer from UI\n\t\tif (this.customFooter) {\n\t\t\tthis.ui.tui.removeChild(this.customFooter);\n\t\t} else {\n\t\t\tthis.ui.tui.removeChild(this.ui.footer);\n\t\t}\n\n\t\tif (factory) {\n\t\t\t// Create and add custom footer, passing the data provider\n\t\t\tthis.customFooter = factory(this.ui.tui, theme, this.ui.footerDataProvider);\n\t\t\tthis.ui.tui.addChild(this.customFooter);\n\t\t} else {\n\t\t\t// Restore built-in footer\n\t\t\tthis.customFooter = undefined;\n\t\t\tthis.ui.tui.addChild(this.ui.footer);\n\t\t}\n\n\t\tthis.ui.tui.requestRender();\n\t}\n\n\t/**\n\t * Set a custom header component, or restore the built-in header.\n\t */\n\tprivate setExtensionHeader(factory: ((tui: TUI, thm: Theme) => Component & { dispose?(): void }) | undefined): void {\n\t\t// Header may not be initialized yet if called during early initialization\n\t\tconst builtInHeader = this.ui.getBuiltInHeader();\n\t\tif (!builtInHeader) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Dispose existing custom header\n\t\tif (this.customHeader?.dispose) {\n\t\t\tthis.customHeader.dispose();\n\t\t}\n\n\t\t// Find the index of the current header in the header container\n\t\tconst currentHeader = this.customHeader || builtInHeader;\n\t\tconst index = this.ui.headerContainer.children.indexOf(currentHeader);\n\n\t\tif (factory) {\n\t\t\t// Create and add custom header\n\t\t\tthis.customHeader = factory(this.ui.tui, theme);\n\t\t\tif (isExpandable(this.customHeader)) {\n\t\t\t\tthis.customHeader.setExpanded(this.ui.getToolsExpanded());\n\t\t\t}\n\t\t\tif (index !== -1) {\n\t\t\t\tthis.ui.headerContainer.children[index] = this.customHeader;\n\t\t\t} else {\n\t\t\t\t// If not found (e.g. builtInHeader was never added), add at the top\n\t\t\t\tthis.ui.headerContainer.children.unshift(this.customHeader);\n\t\t\t}\n\t\t} else {\n\t\t\t// Restore built-in header\n\t\t\tthis.customHeader = undefined;\n\t\t\tif (isExpandable(builtInHeader)) {\n\t\t\t\tbuiltInHeader.setExpanded(this.ui.getToolsExpanded());\n\t\t\t}\n\t\t\tif (index !== -1) {\n\t\t\t\tthis.ui.headerContainer.children[index] = builtInHeader;\n\t\t\t}\n\t\t}\n\n\t\tthis.ui.tui.requestRender();\n\t}\n\n\tprivate addExtensionTerminalInputListener(\n\t\thandler: (data: string) => { consume?: boolean; data?: string } | undefined,\n\t): () => void {\n\t\tconst unsubscribe = this.ui.tui.addInputListener(handler);\n\t\tthis.extensionTerminalInputUnsubscribers.add(unsubscribe);\n\t\treturn () => {\n\t\t\tunsubscribe();\n\t\t\tthis.extensionTerminalInputUnsubscribers.delete(unsubscribe);\n\t\t};\n\t}\n\n\tclearExtensionTerminalInputListeners(): void {\n\t\tfor (const unsubscribe of this.extensionTerminalInputUnsubscribers) {\n\t\t\tunsubscribe();\n\t\t}\n\t\tthis.extensionTerminalInputUnsubscribers.clear();\n\t}\n\n\t/**\n\t * Create the ExtensionUIContext for extensions.\n\t */\n\tcreateExtensionUIContext(): ExtensionUIContext {\n\t\treturn {\n\t\t\tselect: (title, options, opts) => this.showExtensionSelector(title, options, opts),\n\t\t\tconfirm: (title, message, opts) => this.showExtensionConfirm(title, message, opts),\n\t\t\tinput: (title, placeholder, opts) => this.showExtensionInput(title, placeholder, opts),\n\t\t\tnotify: (message, type) => this.showExtensionNotify(message, type),\n\t\t\tonTerminalInput: (handler) => this.addExtensionTerminalInputListener(handler),\n\t\t\tsetStatus: (key, text) => this.setExtensionStatus(key, text),\n\t\t\tsetWorkingMessage: (message) => this.ui.setWorkingMessage(message),\n\t\t\tsetWorkingVisible: (visible) => this.ui.setWorkingVisible(visible),\n\t\t\tsetWorkingIndicator: (options) => this.ui.setWorkingIndicator(options),\n\t\t\tsetHiddenThinkingLabel: (label) => this.ui.setHiddenThinkingLabel(label),\n\t\t\tsetWidget: (key, content, options) => this.setExtensionWidget(key, content, options),\n\t\t\tsetFooter: (factory) => this.setExtensionFooter(factory),\n\t\t\tsetHeader: (factory) => this.setExtensionHeader(factory),\n\t\t\tsetTitle: (title) => this.ui.tui.terminal.setTitle(title),\n\t\t\tcustom: (factory, options) => this.showExtensionCustom(factory, options),\n\t\t\tpasteToEditor: (text) => this.ui.getEditor().handleInput(`\\x1b[200~${text}\\x1b[201~`),\n\t\t\tsetEditorText: (text) => this.ui.getEditor().setText(text),\n\t\t\tgetEditorText: () => this.ui.getEditor().getExpandedText?.() ?? this.ui.getEditor().getText(),\n\t\t\teditor: (title, prefill) => this.showExtensionEditor(title, prefill),\n\t\t\taddAutocompleteProvider: (factory) => {\n\t\t\t\tthis.ui.pushAutocompleteProviderWrapper(factory);\n\t\t\t\tthis.ui.setupAutocompleteProvider();\n\t\t\t},\n\t\t\tsetEditorComponent: (factory) => this.setCustomEditorComponent(factory),\n\t\t\tgetEditorComponent: () => this.editorComponentFactory,\n\t\t\tget theme() {\n\t\t\t\treturn theme;\n\t\t\t},\n\t\t\tgetAllThemes: () => getAvailableThemesWithPaths(),\n\t\t\tgetTheme: (name) => getThemeByName(name),\n\t\t\tsetTheme: (themeOrName) => {\n\t\t\t\tif (themeOrName instanceof Theme) {\n\t\t\t\t\tsetThemeInstance(themeOrName);\n\t\t\t\t\tthis.ui.tui.requestRender();\n\t\t\t\t\treturn { success: true };\n\t\t\t\t}\n\t\t\t\tconst result = setTheme(themeOrName, true);\n\t\t\t\tif (result.success) {\n\t\t\t\t\tif (this.session.settingsManager.getTheme() !== themeOrName) {\n\t\t\t\t\t\tthis.session.settingsManager.setTheme(themeOrName);\n\t\t\t\t\t}\n\t\t\t\t\tthis.ui.tui.requestRender();\n\t\t\t\t}\n\t\t\t\treturn result;\n\t\t\t},\n\t\t\tgetToolsExpanded: () => this.ui.getToolsExpanded(),\n\t\t\tsetToolsExpanded: (expanded) => this.ui.setToolsExpanded(expanded),\n\t\t};\n\t}\n\n\t/**\n\t * Show a selector for extensions.\n\t */\n\tshowExtensionSelector(\n\t\ttitle: string,\n\t\toptions: string[],\n\t\topts?: ExtensionUIDialogOptions,\n\t): Promise<string | undefined> {\n\t\tif (opts?.signal?.aborted) return Promise.resolve(undefined);\n\t\t// Replacing an overlay without settling its promise strands the extension handler forever.\n\t\tthis.activeExtensionDialogCancel?.();\n\t\treturn new Promise((resolve) => {\n\t\t\tlet settled = false;\n\t\t\tlet selector: ExtensionSelectorComponent;\n\t\t\tconst finish = (value: string | undefined, restoreEditor = true) => {\n\t\t\t\tif (settled) return;\n\t\t\t\tsettled = true;\n\t\t\t\topts?.signal?.removeEventListener(\"abort\", cancel);\n\t\t\t\tif (this.activeExtensionDialogCancel === cancel) {\n\t\t\t\t\tthis.activeExtensionDialogCancel = undefined;\n\t\t\t\t}\n\t\t\t\tthis.hideExtensionSelector(selector, restoreEditor);\n\t\t\t\tresolve(value);\n\t\t\t};\n\t\t\tconst cancel = () => finish(undefined);\n\n\t\t\tselector = new ExtensionSelectorComponent(title, options, (option) => finish(option), cancel, {\n\t\t\t\ttui: this.ui.tui,\n\t\t\t\ttimeout: opts?.timeout,\n\t\t\t\tonToggleToolsExpanded: () => this.ui.toggleToolsExpanded(),\n\t\t\t});\n\t\t\tthis.extensionSelector = selector;\n\t\t\tthis.activeExtensionDialogCancel = cancel;\n\t\t\tthis.ui.overlayHost.swap(selector, { onUnmount: () => finish(undefined, false) });\n\t\t\topts?.signal?.addEventListener(\"abort\", cancel, { once: true });\n\t\t});\n\t}\n\n\t/**\n\t * Hide the extension selector.\n\t */\n\tprivate hideExtensionSelector(selector = this.extensionSelector, restoreEditor = true): void {\n\t\tif (!selector) return;\n\t\tselector.dispose();\n\t\tif (this.extensionSelector !== selector) return;\n\t\tthis.extensionSelector = undefined;\n\t\tif (restoreEditor) this.ui.overlayHost.swap(this.ui.getEditor());\n\t}\n\n\t/**\n\t * Show a confirmation dialog for extensions.\n\t */\n\tasync showExtensionConfirm(title: string, message: string, opts?: ExtensionUIDialogOptions): Promise<boolean> {\n\t\tconst result = await this.showExtensionSelector(`${title}\\n${message}`, [\"Yes\", \"No\"], opts);\n\t\treturn result === \"Yes\";\n\t}\n\n\t/**\n\t * Show a text input for extensions.\n\t */\n\tprivate showExtensionInput(\n\t\ttitle: string,\n\t\tplaceholder?: string,\n\t\topts?: ExtensionUIDialogOptions,\n\t): Promise<string | undefined> {\n\t\tif (opts?.signal?.aborted) return Promise.resolve(undefined);\n\t\tthis.activeExtensionDialogCancel?.();\n\t\treturn new Promise((resolve) => {\n\t\t\tlet settled = false;\n\t\t\tlet input: ExtensionInputComponent;\n\t\t\tconst finish = (value: string | undefined, restoreEditor = true) => {\n\t\t\t\tif (settled) return;\n\t\t\t\tsettled = true;\n\t\t\t\topts?.signal?.removeEventListener(\"abort\", cancel);\n\t\t\t\tif (this.activeExtensionDialogCancel === cancel) {\n\t\t\t\t\tthis.activeExtensionDialogCancel = undefined;\n\t\t\t\t}\n\t\t\t\tthis.hideExtensionInput(input, restoreEditor);\n\t\t\t\tresolve(value);\n\t\t\t};\n\t\t\tconst cancel = () => finish(undefined);\n\n\t\t\tinput = new ExtensionInputComponent(title, placeholder, (value) => finish(value), cancel, {\n\t\t\t\ttui: this.ui.tui,\n\t\t\t\ttimeout: opts?.timeout,\n\t\t\t});\n\t\t\tthis.extensionInput = input;\n\t\t\tthis.activeExtensionDialogCancel = cancel;\n\t\t\tthis.ui.overlayHost.swap(input, { onUnmount: () => finish(undefined, false) });\n\t\t\topts?.signal?.addEventListener(\"abort\", cancel, { once: true });\n\t\t});\n\t}\n\n\t/**\n\t * Hide the extension input.\n\t */\n\tprivate hideExtensionInput(input = this.extensionInput, restoreEditor = true): void {\n\t\tif (!input) return;\n\t\tinput.dispose();\n\t\tif (this.extensionInput !== input) return;\n\t\tthis.extensionInput = undefined;\n\t\tif (restoreEditor) this.ui.overlayHost.swap(this.ui.getEditor());\n\t}\n\n\t/**\n\t * Show a multi-line editor for extensions (with Ctrl+G support).\n\t */\n\tshowExtensionEditor(title: string, prefill?: string): Promise<string | undefined> {\n\t\tthis.activeExtensionDialogCancel?.();\n\t\treturn new Promise((resolve) => {\n\t\t\tlet settled = false;\n\t\t\tlet editor: ExtensionEditorComponent;\n\t\t\tconst finish = (value: string | undefined, restoreEditor = true) => {\n\t\t\t\tif (settled) return;\n\t\t\t\tsettled = true;\n\t\t\t\tif (this.activeExtensionDialogCancel === cancel) {\n\t\t\t\t\tthis.activeExtensionDialogCancel = undefined;\n\t\t\t\t}\n\t\t\t\tthis.hideExtensionEditor(editor, restoreEditor);\n\t\t\t\tresolve(value);\n\t\t\t};\n\t\t\tconst cancel = () => finish(undefined);\n\n\t\t\teditor = new ExtensionEditorComponent(\n\t\t\t\tthis.ui.tui,\n\t\t\t\tthis.ui.keybindings,\n\t\t\t\ttitle,\n\t\t\t\tprefill,\n\t\t\t\t(value) => finish(value),\n\t\t\t\tcancel,\n\t\t\t);\n\t\t\tthis.extensionEditor = editor;\n\t\t\tthis.activeExtensionDialogCancel = cancel;\n\t\t\tthis.ui.overlayHost.swap(editor, { onUnmount: () => finish(undefined, false) });\n\t\t});\n\t}\n\n\t/**\n\t * Hide the extension editor.\n\t */\n\tprivate hideExtensionEditor(editor = this.extensionEditor, restoreEditor = true): void {\n\t\tif (!editor || this.extensionEditor !== editor) return;\n\t\tthis.extensionEditor = undefined;\n\t\tif (restoreEditor) this.ui.overlayHost.swap(this.ui.getEditor());\n\t}\n\n\t/**\n\t * Set a custom editor component from an extension.\n\t * Pass undefined to restore the default editor.\n\t */\n\tprivate setCustomEditorComponent(factory: EditorFactory | undefined): void {\n\t\tthis.editorComponentFactory = factory;\n\n\t\t// Save text from current editor before switching\n\t\tconst currentText = this.ui.getEditor().getText();\n\n\t\tif (factory) {\n\t\t\t// Create the custom editor with tui, theme, and keybindings\n\t\t\tconst newEditor = factory(this.ui.tui, getEditorTheme(), this.ui.keybindings);\n\n\t\t\t// Wire up callbacks from the default editor\n\t\t\tnewEditor.onSubmit = this.ui.defaultEditor.onSubmit;\n\t\t\tnewEditor.onChange = this.ui.defaultEditor.onChange;\n\n\t\t\t// Copy text from previous editor\n\t\t\tnewEditor.setText(currentText);\n\n\t\t\t// Copy appearance settings if supported\n\t\t\tif (newEditor.borderColor !== undefined) {\n\t\t\t\tnewEditor.borderColor = this.ui.defaultEditor.borderColor;\n\t\t\t}\n\t\t\tif (newEditor.setPaddingX !== undefined) {\n\t\t\t\tnewEditor.setPaddingX(this.ui.defaultEditor.getPaddingX());\n\t\t\t}\n\n\t\t\t// Set autocomplete if supported\n\t\t\tconst autocompleteProvider = this.ui.getAutocompleteProvider();\n\t\t\tif (newEditor.setAutocompleteProvider && autocompleteProvider) {\n\t\t\t\tnewEditor.setAutocompleteProvider(autocompleteProvider);\n\t\t\t}\n\n\t\t\t// If extending CustomEditor, copy app-level handlers\n\t\t\t// Use duck typing since instanceof fails across jiti module boundaries\n\t\t\tconst customEditor = newEditor as unknown as Record<string, unknown>;\n\t\t\tif (\"actionHandlers\" in customEditor && customEditor.actionHandlers instanceof Map) {\n\t\t\t\tif (!customEditor.onEscape) {\n\t\t\t\t\tcustomEditor.onEscape = () => this.ui.defaultEditor.onEscape?.();\n\t\t\t\t}\n\t\t\t\tif (!customEditor.onCtrlD) {\n\t\t\t\t\tcustomEditor.onCtrlD = () => this.ui.defaultEditor.onCtrlD?.();\n\t\t\t\t}\n\t\t\t\tif (!customEditor.onPasteImage) {\n\t\t\t\t\tcustomEditor.onPasteImage = () => this.ui.defaultEditor.onPasteImage?.();\n\t\t\t\t}\n\t\t\t\tif (!customEditor.onExtensionShortcut) {\n\t\t\t\t\tcustomEditor.onExtensionShortcut = (data: string) => this.ui.defaultEditor.onExtensionShortcut?.(data);\n\t\t\t\t}\n\t\t\t\t// Copy action handlers (clear, suspend, model switching, etc.)\n\t\t\t\tfor (const [action, handler] of this.ui.defaultEditor.actionHandlers) {\n\t\t\t\t\t(customEditor.actionHandlers as Map<string, () => void>).set(action, handler);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tthis.ui.setEditor(newEditor);\n\t\t} else {\n\t\t\t// Restore default editor with text from custom editor\n\t\t\tthis.ui.defaultEditor.setText(currentText);\n\t\t\tthis.ui.setEditor(this.ui.defaultEditor);\n\t\t}\n\n\t\tthis.ui.overlayHost.swap(this.ui.getEditor() as Component);\n\t}\n\n\t/**\n\t * Show a notification for extensions.\n\t */\n\tprivate showExtensionNotify(message: string, type?: \"info\" | \"warning\" | \"error\"): void {\n\t\tif (type === \"error\") {\n\t\t\tthis.ui.showError(message);\n\t\t} else if (type === \"warning\") {\n\t\t\tthis.ui.showWarning(message);\n\t\t} else {\n\t\t\tthis.ui.showStatus(message);\n\t\t}\n\t}\n\n\t/** Show a custom component with keyboard focus. Overlay mode renders on top of existing content. */\n\tprivate async showExtensionCustom<T>(\n\t\tfactory: (\n\t\t\ttui: TUI,\n\t\t\ttheme: Theme,\n\t\t\tkeybindings: KeybindingsManager,\n\t\t\tdone: (result: T) => void,\n\t\t) => (Component & { dispose?(): void }) | Promise<Component & { dispose?(): void }>,\n\t\toptions?: {\n\t\t\toverlay?: boolean;\n\t\t\toverlayOptions?: OverlayOptions | (() => OverlayOptions);\n\t\t\tonHandle?: (handle: OverlayHandle) => void;\n\t\t},\n\t): Promise<T> {\n\t\tthis.activeExtensionDialogCancel?.();\n\t\tconst savedText = this.ui.getEditor().getText();\n\t\tconst isOverlay = options?.overlay ?? false;\n\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tlet component: (Component & { dispose?(): void }) | undefined;\n\t\t\tlet overlayHandle: OverlayHandle | undefined;\n\t\t\tlet closed = false;\n\t\t\tlet cancel: () => void;\n\t\t\tconst dispose = (candidate: (Component & { dispose?(): void }) | undefined) => {\n\t\t\t\ttry {\n\t\t\t\t\tcandidate?.dispose?.();\n\t\t\t\t} catch {\n\t\t\t\t\t/* ignore dispose errors */\n\t\t\t\t}\n\t\t\t};\n\t\t\tconst restore = (restoreEditor = true) => {\n\t\t\t\tif (this.activeExtensionDialogCancel !== cancel) return;\n\t\t\t\tthis.activeExtensionDialogCancel = undefined;\n\t\t\t\tif (isOverlay) {\n\t\t\t\t\toverlayHandle?.hide();\n\t\t\t\t} else if (restoreEditor) {\n\t\t\t\t\tthis.ui.getEditor().setText(savedText);\n\t\t\t\t\tthis.ui.overlayHost.swap(this.ui.getEditor(), { focusMode: \"restore\" });\n\t\t\t\t}\n\t\t\t};\n\t\t\tconst close = (result: T) => {\n\t\t\t\tif (closed) return;\n\t\t\t\tclosed = true;\n\t\t\t\tdispose(component);\n\t\t\t\trestore();\n\t\t\t\tresolve(result);\n\t\t\t};\n\t\t\tconst fail = (error: unknown, restoreEditor = true) => {\n\t\t\t\tif (closed) return;\n\t\t\t\tclosed = true;\n\t\t\t\tdispose(component);\n\t\t\t\trestore(restoreEditor);\n\t\t\t\treject(error);\n\t\t\t};\n\t\t\tcancel = () => fail(new Error(\"Extension UI request was superseded or reset\"));\n\t\t\tthis.activeExtensionDialogCancel = cancel;\n\t\t\tlet mountingComponent = false;\n\t\t\tconst onUnmount = () => {\n\t\t\t\tif (!mountingComponent) fail(new Error(\"Extension UI request was superseded or reset\"), false);\n\t\t\t};\n\t\t\tif (!isOverlay) {\n\t\t\t\tthis.ui.overlayHost.swap(this.ui.getEditor(), {\n\t\t\t\t\tfocusMode: \"restore\",\n\t\t\t\t\trender: \"none\",\n\t\t\t\t\tonUnmount,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tPromise.resolve()\n\t\t\t\t.then(() => (closed ? undefined : factory(this.ui.tui, theme, this.ui.keybindings, close)))\n\t\t\t\t.then((createdComponent) => {\n\t\t\t\t\tif (!createdComponent) return;\n\t\t\t\t\tcomponent = createdComponent;\n\t\t\t\t\tif (closed) {\n\t\t\t\t\t\tdispose(component);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tif (isOverlay) {\n\t\t\t\t\t\tconst resolvedOptions =\n\t\t\t\t\t\t\ttypeof options?.overlayOptions === \"function\" ? options.overlayOptions() : options?.overlayOptions;\n\t\t\t\t\t\tconst width = (component as { width?: number }).width;\n\t\t\t\t\t\toverlayHandle = this.ui.tui.showOverlay(\n\t\t\t\t\t\t\tcomponent,\n\t\t\t\t\t\t\tresolvedOptions ?? (width ? { width } : undefined),\n\t\t\t\t\t\t);\n\t\t\t\t\t\toptions?.onHandle?.(overlayHandle);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tmountingComponent = true;\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tthis.ui.overlayHost.swap(component, { onUnmount });\n\t\t\t\t\t\t} finally {\n\t\t\t\t\t\t\tmountingComponent = false;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t\t.catch(fail);\n\t\t});\n\t}\n\n\t/**\n\t * Show an extension error in the UI.\n\t */\n\tshowExtensionError(extensionPath: string, error: string, stack?: string): void {\n\t\tconst errorMsg = `Extension \"${extensionPath}\" error: ${error}`;\n\t\tconst errorText = new Text(theme.fg(\"error\", errorMsg), 1, 0);\n\t\tthis.ui.chatContainer.addChild(errorText);\n\t\tif (stack) {\n\t\t\t// Show stack trace in dim color, indented\n\t\t\tconst stackLines = stack\n\t\t\t\t.split(\"\\n\")\n\t\t\t\t.slice(1) // Skip first line (duplicates error message)\n\t\t\t\t.map((line) => theme.fg(\"dim\", ` ${line.trim()}`))\n\t\t\t\t.join(\"\\n\");\n\t\t\tif (stackLines) {\n\t\t\t\tthis.ui.chatContainer.addChild(new Text(stackLines, 1, 0));\n\t\t\t}\n\t\t}\n\t\tthis.ui.tui.requestRender();\n\t}\n}\n"]}
1
+ {"version":3,"file":"extension-ui-host.d.ts","sourceRoot":"","sources":["../../../src/modes/interactive/extension-ui-host.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Extension UI host.\n *\n * Extracted verbatim from interactive-mode.ts (god-file decomposition). Owns the interactive-mode\n * surface of the extension UI API: extension-provided dialogs (selector / confirm / input / multi-line\n * editor / custom component), widget mounting above and below the editor, extension status text, custom\n * footer and header swaps, the custom editor-component swap, keyboard-shortcut wiring, terminal-input\n * listeners, and the `ExtensionUIContext` handed to extensions. It owns exactly the state this cluster\n * mutates (the live dialog components, the widget maps, the terminal-input unsubscribers, the custom\n * footer/header, and the current custom-editor factory) and reaches everything else — the mutable\n * `editor`, the render-core containers, the working-indicator, autocomplete wiring, theme/tools state —\n * through a narrow deps/ui surface rather than the whole InteractiveMode instance. Extensions must see\n * identical behavior, so the render-core operations (working indicator, hidden-thinking label,\n * autocomplete rebuild) stay host-side and are invoked here as delegations.\n */\n\nimport type {\n\tAutocompleteProvider,\n\tComponent,\n\tEditorComponent,\n\tKeyId,\n\tLoaderIndicatorOptions,\n\tOverlayHandle,\n\tOverlayOptions,\n} from \"@caupulican/pi-tui\";\nimport { Container, Editor, matchesKey, Spacer, Text, type TUI } from \"@caupulican/pi-tui\";\nimport type { AgentSession } from \"../../core/agent-session.ts\";\nimport type {\n\tAutocompleteProviderFactory,\n\tEditorFactory,\n\tExtensionContext,\n\tExtensionRunner,\n\tExtensionUIContext,\n\tExtensionUIDialogOptions,\n\tExtensionWidgetOptions,\n} from \"../../core/extensions/index.ts\";\nimport type { FooterDataProvider, ReadonlyFooterDataProvider } from \"../../core/footer-data-provider.ts\";\nimport type { HumanInputPresentationRequest, HumanInputPresentationResult } from \"../../core/human-input.ts\";\nimport type { KeybindingsManager } from \"../../core/keybindings.ts\";\nimport type { SessionImageStore } from \"../../core/session-image-store.ts\";\nimport { AskQuestionDialog } from \"../../core/tools/ask-question.ts\";\nimport { handleClipboardImagePaste } from \"./clipboard-input.ts\";\nimport type { CustomEditor } from \"./components/custom-editor.ts\";\nimport { ExtensionEditorComponent } from \"./components/extension-editor.ts\";\nimport { ExtensionInputComponent } from \"./components/extension-input.ts\";\nimport { ExtensionSelectorComponent } from \"./components/extension-selector.ts\";\nimport type { FooterComponent } from \"./components/footer.ts\";\nimport type { EditorOverlayHost } from \"./editor-overlay-host.ts\";\nimport {\n\tgetAvailableThemesWithPaths,\n\tgetEditorTheme,\n\tgetThemeByName,\n\tsetTheme,\n\tsetThemeInstance,\n\tTheme,\n\ttheme,\n} from \"./theme/theme.ts\";\n\n/** Components that can be expanded/collapsed (custom headers may opt in). */\ninterface Expandable {\n\tsetExpanded(expanded: boolean): void;\n}\n\nfunction isExpandable(obj: unknown): obj is Expandable {\n\treturn typeof obj === \"object\" && obj !== null && \"setExpanded\" in obj && typeof obj.setExpanded === \"function\";\n}\n\n/**\n * The narrow slice of InteractiveMode the extension UI host drives. Stable containers and the TUI are\n * passed directly; mutable render-core state (the active editor, built-in header, loading animation,\n * autocomplete wrappers, tools-expansion) is read/written through accessors so InteractiveMode keeps\n * ownership; and render-core / status operations stay host-side and are exposed here as callbacks.\n */\nexport interface ExtensionUiHostUi {\n\treadonly tui: TUI;\n\treadonly overlayHost: EditorOverlayHost;\n\treadonly keybindings: KeybindingsManager;\n\treadonly footer: FooterComponent;\n\treadonly footerDataProvider: FooterDataProvider;\n\treadonly headerContainer: Container;\n\treadonly chatContainer: Container;\n\treadonly defaultEditor: CustomEditor;\n\treadonly widgetContainerAbove: Container;\n\treadonly widgetContainerBelow: Container;\n\n\tgetEditor(): EditorComponent;\n\tsetEditor(editor: EditorComponent): void;\n\tgetBuiltInHeader(): Component | undefined;\n\tgetAutocompleteProvider(): AutocompleteProvider | undefined;\n\tgetClipboardImageStore(): Pick<SessionImageStore, \"write\"> | undefined;\n\tgetToolsExpanded(): boolean;\n\tpushAutocompleteProviderWrapper(factory: AutocompleteProviderFactory): void;\n\tresetAutocompleteProviderWrappers(): void;\n\n\tsetupAutocompleteProvider(): void;\n\tsetWorkingVisible(visible: boolean): void;\n\tsetWorkingIndicator(options?: LoaderIndicatorOptions): void;\n\tsetHiddenThinkingLabel(label?: string): void;\n\tsetWorkingMessage(message: string | undefined): void;\n\tresetWorkingIndicators(): void;\n\tsetToolsExpanded(expanded: boolean): void;\n\ttoggleToolsExpanded(): void;\n\tupdateTerminalTitle(): void;\n\tmarkShutdownRequested(): void;\n\tabort(): void;\n\treload(): Promise<void>;\n\tshowStatus(message: string): void;\n\tshowWarning(message: string): void;\n\tshowError(message: string): void;\n}\n\nexport interface ExtensionUiHostDeps {\n\tgetSession(): AgentSession;\n\tui: ExtensionUiHostUi;\n}\n\ntype DisposableComponent = Component & { dispose?(): void };\ntype WidgetPlacement = \"aboveEditor\" | \"belowEditor\";\n\nexport class ExtensionUiHost {\n\tprivate readonly deps: ExtensionUiHostDeps;\n\n\t// Extension UI state\n\tprivate extensionSelector: ExtensionSelectorComponent | undefined = undefined;\n\tprivate extensionInput: ExtensionInputComponent | undefined = undefined;\n\tprivate extensionEditor: ExtensionEditorComponent | undefined = undefined;\n\t/** Settles the mounted extension dialog before another dialog can replace its overlay. */\n\tprivate activeExtensionDialogCancel: (() => void) | undefined;\n\tprivate extensionTerminalInputUnsubscribers = new Set<() => void>();\n\n\t// Extension widgets (components rendered above/below the editor)\n\tprivate extensionWidgetsAbove = new Map<string, Component & { dispose?(): void }>();\n\tprivate extensionWidgetsBelow = new Map<string, Component & { dispose?(): void }>();\n\t// Host-owned widgets survive extension reloads and are cleared only by the owning interactive mode.\n\tprivate hostWidgetsAbove = new Map<string, DisposableComponent>();\n\tprivate hostWidgetsBelow = new Map<string, DisposableComponent>();\n\n\t// Custom footer from extension (undefined = use built-in footer)\n\tprivate customFooter: (Component & { dispose?(): void }) | undefined = undefined;\n\n\t// Custom header from extension (undefined = use built-in header)\n\tprivate customHeader: (Component & { dispose?(): void }) | undefined = undefined;\n\n\t// Current custom editor-component factory (undefined = default editor)\n\tprivate editorComponentFactory: EditorFactory | undefined;\n\n\tconstructor(deps: ExtensionUiHostDeps) {\n\t\tthis.deps = deps;\n\t}\n\n\tprivate get session(): AgentSession {\n\t\treturn this.deps.getSession();\n\t}\n\tprivate get ui(): ExtensionUiHostUi {\n\t\treturn this.deps.ui;\n\t}\n\n\t/** The active custom header, if an extension has installed one. */\n\tgetCustomHeader(): (Component & { dispose?(): void }) | undefined {\n\t\treturn this.customHeader;\n\t}\n\n\t/**\n\t * Set up keyboard shortcuts registered by extensions.\n\t */\n\tsetupExtensionShortcuts(extensionRunner: ExtensionRunner): void {\n\t\tconst shortcuts = extensionRunner.getShortcuts(this.ui.keybindings.getEffectiveConfig());\n\t\tif (shortcuts.size === 0) return;\n\n\t\t// Create a context for shortcut handlers\n\t\tconst createContext = (): ExtensionContext => ({\n\t\t\tui: this.createExtensionUIContext(),\n\t\t\thasUI: true,\n\t\t\tmode: \"tui\",\n\t\t\tcwd: this.session.sessionManager.getCwd(),\n\t\t\tsessionManager: this.session.sessionManager,\n\t\t\tmodelRegistry: this.session.modelRegistry,\n\t\t\tmodel: this.session.model,\n\t\t\tisIdle: () => !this.session.isStreaming,\n\t\t\tsignal: this.session.agent.signal,\n\t\t\tabort: () => {\n\t\t\t\tthis.ui.abort();\n\t\t\t},\n\t\t\thasPendingMessages: () => this.session.pendingMessageCount > 0,\n\t\t\tshutdown: () => {\n\t\t\t\tthis.ui.markShutdownRequested();\n\t\t\t},\n\t\t\tgetContextUsage: () => this.session.getContextUsage(),\n\t\t\tcompact: (options) => {\n\t\t\t\tvoid (async () => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst result = await this.session.compact(options?.customInstructions);\n\t\t\t\t\t\toptions?.onComplete?.(result);\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\tconst err = error instanceof Error ? error : new Error(String(error));\n\t\t\t\t\t\toptions?.onError?.(err);\n\t\t\t\t\t}\n\t\t\t\t})();\n\t\t\t},\n\t\t\treload: async () => {\n\t\t\t\tawait this.ui.reload();\n\t\t\t},\n\t\t\tgetSystemPrompt: () => this.session.systemPrompt,\n\t\t});\n\n\t\t// Set up the extension shortcut handler on the default editor\n\t\tthis.ui.defaultEditor.onExtensionShortcut = (data: string) => {\n\t\t\tfor (const [shortcutStr, shortcut] of shortcuts) {\n\t\t\t\t// Cast to KeyId - extension shortcuts use the same format\n\t\t\t\tif (matchesKey(data, shortcutStr as KeyId)) {\n\t\t\t\t\t// Run handler async, don't block input\n\t\t\t\t\tPromise.resolve(shortcut.handler(createContext())).catch((err) => {\n\t\t\t\t\t\tthis.ui.showError(`Shortcut handler error: ${err instanceof Error ? err.message : String(err)}`);\n\t\t\t\t\t});\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn false;\n\t\t};\n\t}\n\n\t/**\n\t * Set extension status text in the footer.\n\t */\n\tprivate setExtensionStatus(key: string, text: string | undefined): void {\n\t\tthis.ui.footerDataProvider.setExtensionStatus(key, text);\n\t\tthis.ui.tui.requestRender();\n\t}\n\n\t// Maximum total widget lines to prevent viewport overflow\n\tprivate static readonly MAX_WIDGET_LINES = 10;\n\n\tprivate removeWidget(map: Map<string, DisposableComponent>, key: string): void {\n\t\tconst existing = map.get(key);\n\t\texisting?.dispose?.();\n\t\tmap.delete(key);\n\t}\n\n\t/** Mount a native host widget without coupling it to extension reload lifecycle. */\n\tsetHostWidget(key: string, component: DisposableComponent | undefined, placement: WidgetPlacement): void {\n\t\tthis.removeWidget(this.hostWidgetsAbove, key);\n\t\tthis.removeWidget(this.hostWidgetsBelow, key);\n\t\tif (component) {\n\t\t\tconst target = placement === \"belowEditor\" ? this.hostWidgetsBelow : this.hostWidgetsAbove;\n\t\t\ttarget.set(key, component);\n\t\t}\n\t\tthis.renderWidgets();\n\t}\n\n\tclearHostWidgets(): void {\n\t\tfor (const widget of this.hostWidgetsAbove.values()) widget.dispose?.();\n\t\tfor (const widget of this.hostWidgetsBelow.values()) widget.dispose?.();\n\t\tthis.hostWidgetsAbove.clear();\n\t\tthis.hostWidgetsBelow.clear();\n\t\tthis.renderWidgets();\n\t}\n\n\t/**\n\t * Set an extension widget (string array or custom component).\n\t */\n\tprivate setExtensionWidget(\n\t\tkey: string,\n\t\tcontent: string[] | ((tui: TUI, thm: Theme) => Component & { dispose?(): void }) | undefined,\n\t\toptions?: ExtensionWidgetOptions,\n\t): void {\n\t\tconst placement = options?.placement ?? \"aboveEditor\";\n\t\tthis.removeWidget(this.extensionWidgetsAbove, key);\n\t\tthis.removeWidget(this.extensionWidgetsBelow, key);\n\n\t\tif (content === undefined) {\n\t\t\tthis.renderWidgets();\n\t\t\treturn;\n\t\t}\n\n\t\tlet component: Component & { dispose?(): void };\n\n\t\tif (Array.isArray(content)) {\n\t\t\t// Wrap string array in a Container with Text components\n\t\t\tconst container = new Container();\n\t\t\tfor (const line of content.slice(0, ExtensionUiHost.MAX_WIDGET_LINES)) {\n\t\t\t\tcontainer.addChild(new Text(line, 1, 0));\n\t\t\t}\n\t\t\tif (content.length > ExtensionUiHost.MAX_WIDGET_LINES) {\n\t\t\t\tcontainer.addChild(new Text(theme.fg(\"muted\", \"... (widget truncated)\"), 1, 0));\n\t\t\t}\n\t\t\tcomponent = container;\n\t\t} else {\n\t\t\t// Factory function - create component\n\t\t\tcomponent = content(this.ui.tui, theme);\n\t\t}\n\n\t\tconst targetMap = placement === \"belowEditor\" ? this.extensionWidgetsBelow : this.extensionWidgetsAbove;\n\t\ttargetMap.set(key, component);\n\t\tthis.renderWidgets();\n\t}\n\n\tprivate clearExtensionWidgets(): void {\n\t\tfor (const widget of this.extensionWidgetsAbove.values()) {\n\t\t\twidget.dispose?.();\n\t\t}\n\t\tfor (const widget of this.extensionWidgetsBelow.values()) {\n\t\t\twidget.dispose?.();\n\t\t}\n\t\tthis.extensionWidgetsAbove.clear();\n\t\tthis.extensionWidgetsBelow.clear();\n\t\tthis.renderWidgets();\n\t}\n\n\tresetExtensionUI(): void {\n\t\t// A reset/reload must settle the active dialog before disposing its component.\n\t\tthis.activeExtensionDialogCancel?.();\n\t\tthis.activeExtensionDialogCancel = undefined;\n\t\tif (this.extensionSelector) {\n\t\t\tthis.hideExtensionSelector();\n\t\t}\n\t\tif (this.extensionInput) {\n\t\t\tthis.hideExtensionInput();\n\t\t}\n\t\tif (this.extensionEditor) {\n\t\t\tthis.hideExtensionEditor();\n\t\t}\n\t\tthis.ui.tui.hideOverlay();\n\t\tthis.clearExtensionTerminalInputListeners();\n\t\tthis.setExtensionFooter(undefined);\n\t\tthis.setExtensionHeader(undefined);\n\t\tthis.clearExtensionWidgets();\n\t\tthis.ui.footerDataProvider.clearExtensionStatuses();\n\t\tthis.ui.footer.invalidate();\n\t\tthis.ui.resetAutocompleteProviderWrappers();\n\t\tthis.setCustomEditorComponent(undefined);\n\t\tthis.ui.setupAutocompleteProvider();\n\t\tthis.ui.defaultEditor.onExtensionShortcut = undefined;\n\t\tthis.ui.updateTerminalTitle();\n\t\tthis.ui.resetWorkingIndicators();\n\t}\n\n\t/**\n\t * Render all extension widgets to the widget container.\n\t */\n\trenderWidgets(): void {\n\t\tif (!this.ui.widgetContainerAbove || !this.ui.widgetContainerBelow) return;\n\t\tthis.renderWidgetContainer(\n\t\t\tthis.ui.widgetContainerAbove,\n\t\t\t[this.hostWidgetsAbove, this.extensionWidgetsAbove],\n\t\t\ttrue,\n\t\t\ttrue,\n\t\t);\n\t\tthis.renderWidgetContainer(\n\t\t\tthis.ui.widgetContainerBelow,\n\t\t\t[this.hostWidgetsBelow, this.extensionWidgetsBelow],\n\t\t\tfalse,\n\t\t\tfalse,\n\t\t);\n\t\tthis.ui.tui.requestRender();\n\t}\n\n\tprivate renderWidgetContainer(\n\t\tcontainer: Container,\n\t\twidgetGroups: readonly Map<string, DisposableComponent>[],\n\t\tspacerWhenEmpty: boolean,\n\t\tleadingSpacer: boolean,\n\t): void {\n\t\tcontainer.clear();\n\n\t\tif (widgetGroups.every((widgets) => widgets.size === 0)) {\n\t\t\tif (spacerWhenEmpty) {\n\t\t\t\tcontainer.addChild(new Spacer(1));\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tif (leadingSpacer) {\n\t\t\tcontainer.addChild(new Spacer(1));\n\t\t}\n\t\tfor (const widgets of widgetGroups) {\n\t\t\tfor (const component of widgets.values()) {\n\t\t\t\tcontainer.addChild(component);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Set a custom footer component, or restore the built-in footer.\n\t */\n\tprivate setExtensionFooter(\n\t\tfactory:\n\t\t\t| ((tui: TUI, thm: Theme, footerData: ReadonlyFooterDataProvider) => Component & { dispose?(): void })\n\t\t\t| undefined,\n\t): void {\n\t\t// Dispose existing custom footer\n\t\tif (this.customFooter?.dispose) {\n\t\t\tthis.customFooter.dispose();\n\t\t}\n\n\t\t// Remove current footer from UI\n\t\tif (this.customFooter) {\n\t\t\tthis.ui.tui.removeChild(this.customFooter);\n\t\t} else {\n\t\t\tthis.ui.tui.removeChild(this.ui.footer);\n\t\t}\n\n\t\tif (factory) {\n\t\t\t// Create and add custom footer, passing the data provider\n\t\t\tthis.customFooter = factory(this.ui.tui, theme, this.ui.footerDataProvider);\n\t\t\tthis.ui.tui.addChild(this.customFooter);\n\t\t} else {\n\t\t\t// Restore built-in footer\n\t\t\tthis.customFooter = undefined;\n\t\t\tthis.ui.tui.addChild(this.ui.footer);\n\t\t}\n\n\t\tthis.ui.tui.requestRender();\n\t}\n\n\t/**\n\t * Set a custom header component, or restore the built-in header.\n\t */\n\tprivate setExtensionHeader(factory: ((tui: TUI, thm: Theme) => Component & { dispose?(): void }) | undefined): void {\n\t\t// Header may not be initialized yet if called during early initialization\n\t\tconst builtInHeader = this.ui.getBuiltInHeader();\n\t\tif (!builtInHeader) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Dispose existing custom header\n\t\tif (this.customHeader?.dispose) {\n\t\t\tthis.customHeader.dispose();\n\t\t}\n\n\t\t// Find the index of the current header in the header container\n\t\tconst currentHeader = this.customHeader || builtInHeader;\n\t\tconst index = this.ui.headerContainer.children.indexOf(currentHeader);\n\n\t\tif (factory) {\n\t\t\t// Create and add custom header\n\t\t\tthis.customHeader = factory(this.ui.tui, theme);\n\t\t\tif (isExpandable(this.customHeader)) {\n\t\t\t\tthis.customHeader.setExpanded(this.ui.getToolsExpanded());\n\t\t\t}\n\t\t\tif (index !== -1) {\n\t\t\t\tthis.ui.headerContainer.children[index] = this.customHeader;\n\t\t\t} else {\n\t\t\t\t// If not found (e.g. builtInHeader was never added), add at the top\n\t\t\t\tthis.ui.headerContainer.children.unshift(this.customHeader);\n\t\t\t}\n\t\t} else {\n\t\t\t// Restore built-in header\n\t\t\tthis.customHeader = undefined;\n\t\t\tif (isExpandable(builtInHeader)) {\n\t\t\t\tbuiltInHeader.setExpanded(this.ui.getToolsExpanded());\n\t\t\t}\n\t\t\tif (index !== -1) {\n\t\t\t\tthis.ui.headerContainer.children[index] = builtInHeader;\n\t\t\t}\n\t\t}\n\n\t\tthis.ui.tui.requestRender();\n\t}\n\n\tprivate addExtensionTerminalInputListener(\n\t\thandler: (data: string) => { consume?: boolean; data?: string } | undefined,\n\t): () => void {\n\t\tconst unsubscribe = this.ui.tui.addInputListener(handler);\n\t\tthis.extensionTerminalInputUnsubscribers.add(unsubscribe);\n\t\treturn () => {\n\t\t\tunsubscribe();\n\t\t\tthis.extensionTerminalInputUnsubscribers.delete(unsubscribe);\n\t\t};\n\t}\n\n\tclearExtensionTerminalInputListeners(): void {\n\t\tfor (const unsubscribe of this.extensionTerminalInputUnsubscribers) {\n\t\t\tunsubscribe();\n\t\t}\n\t\tthis.extensionTerminalInputUnsubscribers.clear();\n\t}\n\n\t/**\n\t * Create the ExtensionUIContext for extensions.\n\t */\n\tcreateExtensionUIContext(): ExtensionUIContext {\n\t\treturn {\n\t\t\taskQuestions: (request, opts) => this.showHumanInput(request, opts),\n\t\t\tselect: (title, options, opts) => this.showExtensionSelector(title, options, opts),\n\t\t\tconfirm: (title, message, opts) => this.showExtensionConfirm(title, message, opts),\n\t\t\tinput: (title, placeholder, opts) => this.showExtensionInput(title, placeholder, opts),\n\t\t\tnotify: (message, type) => this.showExtensionNotify(message, type),\n\t\t\tonTerminalInput: (handler) => this.addExtensionTerminalInputListener(handler),\n\t\t\tsetStatus: (key, text) => this.setExtensionStatus(key, text),\n\t\t\tsetWorkingMessage: (message) => this.ui.setWorkingMessage(message),\n\t\t\tsetWorkingVisible: (visible) => this.ui.setWorkingVisible(visible),\n\t\t\tsetWorkingIndicator: (options) => this.ui.setWorkingIndicator(options),\n\t\t\tsetHiddenThinkingLabel: (label) => this.ui.setHiddenThinkingLabel(label),\n\t\t\tsetWidget: (key, content, options) => this.setExtensionWidget(key, content, options),\n\t\t\tsetFooter: (factory) => this.setExtensionFooter(factory),\n\t\t\tsetHeader: (factory) => this.setExtensionHeader(factory),\n\t\t\tsetTitle: (title) => this.ui.tui.terminal.setTitle(title),\n\t\t\tcustom: (factory, options) => this.showExtensionCustom(factory, options),\n\t\t\tpasteToEditor: (text) => this.ui.getEditor().handleInput(`\\x1b[200~${text}\\x1b[201~`),\n\t\t\tsetEditorText: (text) => this.ui.getEditor().setText(text),\n\t\t\tgetEditorText: () => this.ui.getEditor().getExpandedText?.() ?? this.ui.getEditor().getText(),\n\t\t\teditor: (title, prefill) => this.showExtensionEditor(title, prefill),\n\t\t\taddAutocompleteProvider: (factory) => {\n\t\t\t\tthis.ui.pushAutocompleteProviderWrapper(factory);\n\t\t\t\tthis.ui.setupAutocompleteProvider();\n\t\t\t},\n\t\t\tsetEditorComponent: (factory) => this.setCustomEditorComponent(factory),\n\t\t\tgetEditorComponent: () => this.editorComponentFactory,\n\t\t\tget theme() {\n\t\t\t\treturn theme;\n\t\t\t},\n\t\t\tgetAllThemes: () => getAvailableThemesWithPaths(),\n\t\t\tgetTheme: (name) => getThemeByName(name),\n\t\t\tsetTheme: (themeOrName) => {\n\t\t\t\tif (themeOrName instanceof Theme) {\n\t\t\t\t\tsetThemeInstance(themeOrName);\n\t\t\t\t\tthis.ui.tui.requestRender();\n\t\t\t\t\treturn { success: true };\n\t\t\t\t}\n\t\t\t\tconst result = setTheme(themeOrName, true);\n\t\t\t\tif (result.success) {\n\t\t\t\t\tif (this.session.settingsManager.getTheme() !== themeOrName) {\n\t\t\t\t\t\tthis.session.settingsManager.setTheme(themeOrName);\n\t\t\t\t\t}\n\t\t\t\t\tthis.ui.tui.requestRender();\n\t\t\t\t}\n\t\t\t\treturn result;\n\t\t\t},\n\t\t\tgetToolsExpanded: () => this.ui.getToolsExpanded(),\n\t\t\tsetToolsExpanded: (expanded) => this.ui.setToolsExpanded(expanded),\n\t\t};\n\t}\n\n\tprivate showHumanInput(\n\t\trequest: HumanInputPresentationRequest,\n\t\topts?: Pick<ExtensionUIDialogOptions, \"signal\">,\n\t): Promise<HumanInputPresentationResult> {\n\t\tif (opts?.signal?.aborted) {\n\t\t\treturn Promise.resolve({ answers: [], cancelled: true, reason: \"interrupted\", imageContents: [] });\n\t\t}\n\t\treturn this.showExtensionCustom<HumanInputPresentationResult>((tui, currentTheme, keybindings, done) => {\n\t\t\tlet onAbort: (() => void) | undefined;\n\t\t\tconst finish = (result: HumanInputPresentationResult) => {\n\t\t\t\tif (onAbort) opts?.signal?.removeEventListener(\"abort\", onAbort);\n\t\t\t\tdone(result);\n\t\t\t};\n\t\t\tconst settings = this.session.settingsManager;\n\t\t\tconst modelCannotSeeImages = !request.acceptsImages;\n\t\t\tconst dialog = new AskQuestionDialog({\n\t\t\t\tquestions: request.questions,\n\t\t\t\ttheme: currentTheme,\n\t\t\t\tkeybindings,\n\t\t\t\trequestRender: () => tui.requestRender(),\n\t\t\t\tfinish,\n\t\t\t\tcreateAnswerEditor: () => new Editor(tui, getEditorTheme()),\n\t\t\t\tclipboard: {\n\t\t\t\t\tautoResizeImages: settings.getImageAutoResize(),\n\t\t\t\t\tblockImages: settings.getBlockImages() || modelCannotSeeImages,\n\t\t\t\t\tblockImagesReason: modelCannotSeeImages\n\t\t\t\t\t\t? \"The selected model does not accept image input. Switch to an image-capable model or route this inspection to a vision worker.\"\n\t\t\t\t\t\t: undefined,\n\t\t\t\t\timageStore: this.ui.getClipboardImageStore(),\n\t\t\t\t},\n\t\t\t\tpasteClipboardImage: handleClipboardImagePaste,\n\t\t\t});\n\t\t\tonAbort = () => dialog.cancel(\"interrupted\");\n\t\t\topts?.signal?.addEventListener(\"abort\", onAbort, { once: true });\n\t\t\treturn dialog;\n\t\t}).catch(() => ({ answers: [], cancelled: true, reason: \"interrupted\", imageContents: [] }));\n\t}\n\n\t/**\n\t * Show a selector for extensions.\n\t */\n\tshowExtensionSelector(\n\t\ttitle: string,\n\t\toptions: string[],\n\t\topts?: ExtensionUIDialogOptions,\n\t): Promise<string | undefined> {\n\t\tif (opts?.signal?.aborted) return Promise.resolve(undefined);\n\t\t// Replacing an overlay without settling its promise strands the extension handler forever.\n\t\tthis.activeExtensionDialogCancel?.();\n\t\treturn new Promise((resolve) => {\n\t\t\tlet settled = false;\n\t\t\tlet selector: ExtensionSelectorComponent;\n\t\t\tconst finish = (value: string | undefined, restoreEditor = true) => {\n\t\t\t\tif (settled) return;\n\t\t\t\tsettled = true;\n\t\t\t\topts?.signal?.removeEventListener(\"abort\", cancel);\n\t\t\t\tif (this.activeExtensionDialogCancel === cancel) {\n\t\t\t\t\tthis.activeExtensionDialogCancel = undefined;\n\t\t\t\t}\n\t\t\t\tthis.hideExtensionSelector(selector, restoreEditor);\n\t\t\t\tresolve(value);\n\t\t\t};\n\t\t\tconst cancel = () => finish(undefined);\n\n\t\t\tselector = new ExtensionSelectorComponent(title, options, (option) => finish(option), cancel, {\n\t\t\t\ttui: this.ui.tui,\n\t\t\t\ttimeout: opts?.timeout,\n\t\t\t\tonToggleToolsExpanded: () => this.ui.toggleToolsExpanded(),\n\t\t\t});\n\t\t\tthis.extensionSelector = selector;\n\t\t\tthis.activeExtensionDialogCancel = cancel;\n\t\t\tthis.ui.overlayHost.swap(selector, { onUnmount: () => finish(undefined, false) });\n\t\t\topts?.signal?.addEventListener(\"abort\", cancel, { once: true });\n\t\t});\n\t}\n\n\t/**\n\t * Hide the extension selector.\n\t */\n\tprivate hideExtensionSelector(selector = this.extensionSelector, restoreEditor = true): void {\n\t\tif (!selector) return;\n\t\tselector.dispose();\n\t\tif (this.extensionSelector !== selector) return;\n\t\tthis.extensionSelector = undefined;\n\t\tif (restoreEditor) this.ui.overlayHost.swap(this.ui.getEditor());\n\t}\n\n\t/**\n\t * Show a confirmation dialog for extensions.\n\t */\n\tasync showExtensionConfirm(title: string, message: string, opts?: ExtensionUIDialogOptions): Promise<boolean> {\n\t\tconst result = await this.showExtensionSelector(`${title}\\n${message}`, [\"Yes\", \"No\"], opts);\n\t\treturn result === \"Yes\";\n\t}\n\n\t/**\n\t * Show a text input for extensions.\n\t */\n\tprivate showExtensionInput(\n\t\ttitle: string,\n\t\tplaceholder?: string,\n\t\topts?: ExtensionUIDialogOptions,\n\t): Promise<string | undefined> {\n\t\tif (opts?.signal?.aborted) return Promise.resolve(undefined);\n\t\tthis.activeExtensionDialogCancel?.();\n\t\treturn new Promise((resolve) => {\n\t\t\tlet settled = false;\n\t\t\tlet input: ExtensionInputComponent;\n\t\t\tconst finish = (value: string | undefined, restoreEditor = true) => {\n\t\t\t\tif (settled) return;\n\t\t\t\tsettled = true;\n\t\t\t\topts?.signal?.removeEventListener(\"abort\", cancel);\n\t\t\t\tif (this.activeExtensionDialogCancel === cancel) {\n\t\t\t\t\tthis.activeExtensionDialogCancel = undefined;\n\t\t\t\t}\n\t\t\t\tthis.hideExtensionInput(input, restoreEditor);\n\t\t\t\tresolve(value);\n\t\t\t};\n\t\t\tconst cancel = () => finish(undefined);\n\n\t\t\tinput = new ExtensionInputComponent(title, placeholder, (value) => finish(value), cancel, {\n\t\t\t\ttui: this.ui.tui,\n\t\t\t\ttimeout: opts?.timeout,\n\t\t\t});\n\t\t\tthis.extensionInput = input;\n\t\t\tthis.activeExtensionDialogCancel = cancel;\n\t\t\tthis.ui.overlayHost.swap(input, { onUnmount: () => finish(undefined, false) });\n\t\t\topts?.signal?.addEventListener(\"abort\", cancel, { once: true });\n\t\t});\n\t}\n\n\t/**\n\t * Hide the extension input.\n\t */\n\tprivate hideExtensionInput(input = this.extensionInput, restoreEditor = true): void {\n\t\tif (!input) return;\n\t\tinput.dispose();\n\t\tif (this.extensionInput !== input) return;\n\t\tthis.extensionInput = undefined;\n\t\tif (restoreEditor) this.ui.overlayHost.swap(this.ui.getEditor());\n\t}\n\n\t/**\n\t * Show a multi-line editor for extensions (with Ctrl+G support).\n\t */\n\tshowExtensionEditor(title: string, prefill?: string): Promise<string | undefined> {\n\t\tthis.activeExtensionDialogCancel?.();\n\t\treturn new Promise((resolve) => {\n\t\t\tlet settled = false;\n\t\t\tlet editor: ExtensionEditorComponent;\n\t\t\tconst finish = (value: string | undefined, restoreEditor = true) => {\n\t\t\t\tif (settled) return;\n\t\t\t\tsettled = true;\n\t\t\t\tif (this.activeExtensionDialogCancel === cancel) {\n\t\t\t\t\tthis.activeExtensionDialogCancel = undefined;\n\t\t\t\t}\n\t\t\t\tthis.hideExtensionEditor(editor, restoreEditor);\n\t\t\t\tresolve(value);\n\t\t\t};\n\t\t\tconst cancel = () => finish(undefined);\n\n\t\t\teditor = new ExtensionEditorComponent(\n\t\t\t\tthis.ui.tui,\n\t\t\t\tthis.ui.keybindings,\n\t\t\t\ttitle,\n\t\t\t\tprefill,\n\t\t\t\t(value) => finish(value),\n\t\t\t\tcancel,\n\t\t\t);\n\t\t\tthis.extensionEditor = editor;\n\t\t\tthis.activeExtensionDialogCancel = cancel;\n\t\t\tthis.ui.overlayHost.swap(editor, { onUnmount: () => finish(undefined, false) });\n\t\t});\n\t}\n\n\t/**\n\t * Hide the extension editor.\n\t */\n\tprivate hideExtensionEditor(editor = this.extensionEditor, restoreEditor = true): void {\n\t\tif (!editor || this.extensionEditor !== editor) return;\n\t\tthis.extensionEditor = undefined;\n\t\tif (restoreEditor) this.ui.overlayHost.swap(this.ui.getEditor());\n\t}\n\n\t/**\n\t * Set a custom editor component from an extension.\n\t * Pass undefined to restore the default editor.\n\t */\n\tprivate setCustomEditorComponent(factory: EditorFactory | undefined): void {\n\t\tthis.editorComponentFactory = factory;\n\n\t\t// Save text from current editor before switching\n\t\tconst currentText = this.ui.getEditor().getText();\n\n\t\tif (factory) {\n\t\t\t// Create the custom editor with tui, theme, and keybindings\n\t\t\tconst newEditor = factory(this.ui.tui, getEditorTheme(), this.ui.keybindings);\n\n\t\t\t// Wire up callbacks from the default editor\n\t\t\tnewEditor.onSubmit = this.ui.defaultEditor.onSubmit;\n\t\t\tnewEditor.onChange = this.ui.defaultEditor.onChange;\n\n\t\t\t// Copy text from previous editor\n\t\t\tnewEditor.setText(currentText);\n\n\t\t\t// Copy appearance settings if supported\n\t\t\tif (newEditor.borderColor !== undefined) {\n\t\t\t\tnewEditor.borderColor = this.ui.defaultEditor.borderColor;\n\t\t\t}\n\t\t\tif (newEditor.setPaddingX !== undefined) {\n\t\t\t\tnewEditor.setPaddingX(this.ui.defaultEditor.getPaddingX());\n\t\t\t}\n\n\t\t\t// Set autocomplete if supported\n\t\t\tconst autocompleteProvider = this.ui.getAutocompleteProvider();\n\t\t\tif (newEditor.setAutocompleteProvider && autocompleteProvider) {\n\t\t\t\tnewEditor.setAutocompleteProvider(autocompleteProvider);\n\t\t\t}\n\n\t\t\t// If extending CustomEditor, copy app-level handlers\n\t\t\t// Use duck typing since instanceof fails across jiti module boundaries\n\t\t\tconst customEditor = newEditor as unknown as Record<string, unknown>;\n\t\t\tif (\"actionHandlers\" in customEditor && customEditor.actionHandlers instanceof Map) {\n\t\t\t\tif (!customEditor.onEscape) {\n\t\t\t\t\tcustomEditor.onEscape = () => this.ui.defaultEditor.onEscape?.();\n\t\t\t\t}\n\t\t\t\tif (!customEditor.onCtrlD) {\n\t\t\t\t\tcustomEditor.onCtrlD = () => this.ui.defaultEditor.onCtrlD?.();\n\t\t\t\t}\n\t\t\t\tif (!customEditor.onPasteImage) {\n\t\t\t\t\tcustomEditor.onPasteImage = () => this.ui.defaultEditor.onPasteImage?.();\n\t\t\t\t}\n\t\t\t\tif (!customEditor.onExtensionShortcut) {\n\t\t\t\t\tcustomEditor.onExtensionShortcut = (data: string) => this.ui.defaultEditor.onExtensionShortcut?.(data);\n\t\t\t\t}\n\t\t\t\t// Copy action handlers (clear, suspend, model switching, etc.)\n\t\t\t\tfor (const [action, handler] of this.ui.defaultEditor.actionHandlers) {\n\t\t\t\t\t(customEditor.actionHandlers as Map<string, () => void>).set(action, handler);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tthis.ui.setEditor(newEditor);\n\t\t} else {\n\t\t\t// Restore default editor with text from custom editor\n\t\t\tthis.ui.defaultEditor.setText(currentText);\n\t\t\tthis.ui.setEditor(this.ui.defaultEditor);\n\t\t}\n\n\t\tthis.ui.overlayHost.swap(this.ui.getEditor() as Component);\n\t}\n\n\t/**\n\t * Show a notification for extensions.\n\t */\n\tprivate showExtensionNotify(message: string, type?: \"info\" | \"warning\" | \"error\"): void {\n\t\tif (type === \"error\") {\n\t\t\tthis.ui.showError(message);\n\t\t} else if (type === \"warning\") {\n\t\t\tthis.ui.showWarning(message);\n\t\t} else {\n\t\t\tthis.ui.showStatus(message);\n\t\t}\n\t}\n\n\t/** Show a custom component with keyboard focus. Overlay mode renders on top of existing content. */\n\tprivate async showExtensionCustom<T>(\n\t\tfactory: (\n\t\t\ttui: TUI,\n\t\t\ttheme: Theme,\n\t\t\tkeybindings: KeybindingsManager,\n\t\t\tdone: (result: T) => void,\n\t\t) => (Component & { dispose?(): void }) | Promise<Component & { dispose?(): void }>,\n\t\toptions?: {\n\t\t\toverlay?: boolean;\n\t\t\toverlayOptions?: OverlayOptions | (() => OverlayOptions);\n\t\t\tonHandle?: (handle: OverlayHandle) => void;\n\t\t},\n\t): Promise<T> {\n\t\tthis.activeExtensionDialogCancel?.();\n\t\tconst savedText = this.ui.getEditor().getText();\n\t\tconst isOverlay = options?.overlay ?? false;\n\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tlet component: (Component & { dispose?(): void }) | undefined;\n\t\t\tlet overlayHandle: OverlayHandle | undefined;\n\t\t\tlet closed = false;\n\t\t\tlet cancel: () => void;\n\t\t\tconst dispose = (candidate: (Component & { dispose?(): void }) | undefined) => {\n\t\t\t\ttry {\n\t\t\t\t\tcandidate?.dispose?.();\n\t\t\t\t} catch {\n\t\t\t\t\t/* ignore dispose errors */\n\t\t\t\t}\n\t\t\t};\n\t\t\tconst restore = (restoreEditor = true) => {\n\t\t\t\tif (this.activeExtensionDialogCancel !== cancel) return;\n\t\t\t\tthis.activeExtensionDialogCancel = undefined;\n\t\t\t\tif (isOverlay) {\n\t\t\t\t\toverlayHandle?.hide();\n\t\t\t\t} else if (restoreEditor) {\n\t\t\t\t\tthis.ui.getEditor().setText(savedText);\n\t\t\t\t\tthis.ui.overlayHost.swap(this.ui.getEditor(), { focusMode: \"restore\" });\n\t\t\t\t}\n\t\t\t};\n\t\t\tconst close = (result: T) => {\n\t\t\t\tif (closed) return;\n\t\t\t\tclosed = true;\n\t\t\t\tdispose(component);\n\t\t\t\trestore();\n\t\t\t\tresolve(result);\n\t\t\t};\n\t\t\tconst fail = (error: unknown, restoreEditor = true) => {\n\t\t\t\tif (closed) return;\n\t\t\t\tclosed = true;\n\t\t\t\tdispose(component);\n\t\t\t\trestore(restoreEditor);\n\t\t\t\treject(error);\n\t\t\t};\n\t\t\tcancel = () => fail(new Error(\"Extension UI request was superseded or reset\"));\n\t\t\tthis.activeExtensionDialogCancel = cancel;\n\t\t\tlet mountingComponent = false;\n\t\t\tconst onUnmount = () => {\n\t\t\t\tif (!mountingComponent) fail(new Error(\"Extension UI request was superseded or reset\"), false);\n\t\t\t};\n\t\t\tif (!isOverlay) {\n\t\t\t\tthis.ui.overlayHost.swap(this.ui.getEditor(), {\n\t\t\t\t\tfocusMode: \"restore\",\n\t\t\t\t\trender: \"none\",\n\t\t\t\t\tonUnmount,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tPromise.resolve()\n\t\t\t\t.then(() => (closed ? undefined : factory(this.ui.tui, theme, this.ui.keybindings, close)))\n\t\t\t\t.then((createdComponent) => {\n\t\t\t\t\tif (!createdComponent) return;\n\t\t\t\t\tcomponent = createdComponent;\n\t\t\t\t\tif (closed) {\n\t\t\t\t\t\tdispose(component);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tif (isOverlay) {\n\t\t\t\t\t\tconst resolvedOptions =\n\t\t\t\t\t\t\ttypeof options?.overlayOptions === \"function\" ? options.overlayOptions() : options?.overlayOptions;\n\t\t\t\t\t\tconst width = (component as { width?: number }).width;\n\t\t\t\t\t\toverlayHandle = this.ui.tui.showOverlay(\n\t\t\t\t\t\t\tcomponent,\n\t\t\t\t\t\t\tresolvedOptions ?? (width ? { width } : undefined),\n\t\t\t\t\t\t);\n\t\t\t\t\t\toptions?.onHandle?.(overlayHandle);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tmountingComponent = true;\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tthis.ui.overlayHost.swap(component, { onUnmount });\n\t\t\t\t\t\t} finally {\n\t\t\t\t\t\t\tmountingComponent = false;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t\t.catch(fail);\n\t\t});\n\t}\n\n\t/**\n\t * Show an extension error in the UI.\n\t */\n\tshowExtensionError(extensionPath: string, error: string, stack?: string): void {\n\t\tconst errorMsg = `Extension \"${extensionPath}\" error: ${error}`;\n\t\tconst errorText = new Text(theme.fg(\"error\", errorMsg), 1, 0);\n\t\tthis.ui.chatContainer.addChild(errorText);\n\t\tif (stack) {\n\t\t\t// Show stack trace in dim color, indented\n\t\t\tconst stackLines = stack\n\t\t\t\t.split(\"\\n\")\n\t\t\t\t.slice(1) // Skip first line (duplicates error message)\n\t\t\t\t.map((line) => theme.fg(\"dim\", ` ${line.trim()}`))\n\t\t\t\t.join(\"\\n\");\n\t\t\tif (stackLines) {\n\t\t\t\tthis.ui.chatContainer.addChild(new Text(stackLines, 1, 0));\n\t\t\t}\n\t\t}\n\t\tthis.ui.tui.requestRender();\n\t}\n}\n"]}