@wrongstack/cli 0.297.0 → 0.298.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (505) hide show
  1. package/dist/{acp-A5OSIU2P.js → acp-S4LZQB66.js} +91 -23
  2. package/dist/{auth-CJWQJVRH.js → auth-NLLGEYQR.js} +3 -3
  3. package/dist/boot/governance-evidence-candidate.d.ts +3 -0
  4. package/dist/boot/governance-shadow-bridge.d.ts +19 -0
  5. package/dist/boot/governance-trace-context.d.ts +64 -0
  6. package/dist/boot/tui-session-resume.d.ts +10 -1
  7. package/dist/chimera-leader-wake.d.ts +57 -0
  8. package/dist/chimera-reviewer-policy.d.ts +89 -1
  9. package/dist/chimera-subagent-ladder.d.ts +72 -0
  10. package/dist/{chunk-AZESNNIC.js → chunk-4HH3YGG4.js} +3 -3
  11. package/dist/{chunk-IHBYBYGA.js → chunk-A6Q5KGLV.js} +51 -18
  12. package/dist/{chunk-ABXK4VVY.js → chunk-ARWGQUB3.js} +2 -1
  13. package/dist/{chunk-7JABKXET.js → chunk-C5GH4YBL.js} +1018 -175
  14. package/dist/{chunk-TADVX644.js → chunk-GO3TJICK.js} +11 -2
  15. package/dist/{chunk-JBTFWWKV.js → chunk-I2BM6FK6.js} +9 -4
  16. package/dist/{chunk-AXVZMEWG.js → chunk-I5MEUFDI.js} +2 -2
  17. package/dist/chunk-U3SS4NRH.js +10 -0
  18. package/dist/{chunk-QZKWRNIP.js → chunk-V3XH6XBJ.js} +12 -9
  19. package/dist/{cli-main-GOY5YGVL.js → cli-main-TJEU4OUY.js} +954 -268
  20. package/dist/cli-main-helpers.d.ts +42 -2
  21. package/dist/{execution-A4PGK4XV.js → execution-INOV5P7N.js} +1236 -652
  22. package/dist/execution-chimera-cascade.d.ts +7 -1
  23. package/dist/execution-chimera-review.d.ts +35 -0
  24. package/dist/fleet/host-types.d.ts +6 -1
  25. package/dist/fleet-statusline.d.ts +1 -0
  26. package/dist/governance-V2Z42RBE.js +65 -0
  27. package/dist/governance-hq-telemetry.d.ts +17 -0
  28. package/dist/{hq-GBPGXIR5.js → hq-WO6CLUSI.js} +2 -2
  29. package/dist/hq-server/auth.d.ts +33 -2
  30. package/dist/hq-server/login-attempt-store.d.ts +73 -0
  31. package/dist/hq-server/routes/auth-handlers.d.ts +73 -5
  32. package/dist/hq-server/routes.d.ts +5 -5
  33. package/dist/hq-server/types.d.ts +36 -2
  34. package/dist/hq-server/utils.d.ts +51 -6
  35. package/dist/{hq-server-YQUUMTPU.js → hq-server-6TSAORAC.js} +2 -2
  36. package/dist/hq-server.d.ts +10 -0
  37. package/dist/index.js +49 -21
  38. package/dist/live-settings-input.d.ts +1 -1
  39. package/dist/settings-menu.d.ts +1 -1
  40. package/dist/slash-commands/fallback.d.ts +2 -0
  41. package/dist/slash-commands/goal-refiner.d.ts +1 -1
  42. package/dist/subcommands/handlers/acp-connection-gate.d.ts +20 -0
  43. package/dist/subcommands/handlers/governance.d.ts +8 -0
  44. package/dist/{update-YC4TYW2Y.js → update-MV6TUACD.js} +3 -3
  45. package/dist/utils/win32-cmd.d.ts +9 -6
  46. package/dist/{version-help-DROHZRW6.js → version-help-ASKQYXDJ.js} +2 -2
  47. package/dist/{webui-server-XKFUOKMJ.js → webui-server-PCLSXX6L.js} +10 -3
  48. package/dist/wiring/brain-and-orchestration.d.ts +2 -0
  49. package/dist/wiring/provider-runtime-setup.d.ts +2 -2
  50. package/package.json +23 -22
  51. package/dist/acp-A5OSIU2P.js.map +0 -7
  52. package/dist/acp-registry-cache.d.ts.map +0 -1
  53. package/dist/acp-server-agent.d.ts.map +0 -1
  54. package/dist/arg-parser.d.ts.map +0 -1
  55. package/dist/audit-2ZSFGNIF.js.map +0 -7
  56. package/dist/auth-CJWQJVRH.js.map +0 -7
  57. package/dist/auth-menu/add-provider.d.ts.map +0 -1
  58. package/dist/auth-menu/anthropic-oauth.d.ts.map +0 -1
  59. package/dist/auth-menu/auth-menu-audit.d.ts.map +0 -1
  60. package/dist/auth-menu/direct.d.ts.map +0 -1
  61. package/dist/auth-menu/github-copilot-oauth.d.ts.map +0 -1
  62. package/dist/auth-menu/helpers.d.ts.map +0 -1
  63. package/dist/auth-menu/index.d.ts.map +0 -1
  64. package/dist/auth-menu/local-presets.d.ts.map +0 -1
  65. package/dist/auth-menu/local.d.ts.map +0 -1
  66. package/dist/auth-menu/loopback-server.d.ts.map +0 -1
  67. package/dist/auth-menu/oauth-menu.d.ts.map +0 -1
  68. package/dist/auth-menu/openai-codex-oauth.d.ts.map +0 -1
  69. package/dist/auth-menu/panel-service.d.ts.map +0 -1
  70. package/dist/auth-menu/provider-menu.d.ts.map +0 -1
  71. package/dist/auth-menu/shared.d.ts.map +0 -1
  72. package/dist/auth-menu/top-menu.d.ts.map +0 -1
  73. package/dist/auth-menu/types.d.ts.map +0 -1
  74. package/dist/auth-menu.d.ts.map +0 -1
  75. package/dist/bench-7ZDWSNEF.js.map +0 -7
  76. package/dist/boot/auto-discover-providers.d.ts.map +0 -1
  77. package/dist/boot/brain-decision-log.d.ts.map +0 -1
  78. package/dist/boot/config-backup-recovery.d.ts.map +0 -1
  79. package/dist/boot/container-wiring.d.ts.map +0 -1
  80. package/dist/boot/dispatch-singleshot.d.ts.map +0 -1
  81. package/dist/boot/dispatch-tui.d.ts.map +0 -1
  82. package/dist/boot/dispatch-webui.d.ts.map +0 -1
  83. package/dist/boot/event-wiring.d.ts.map +0 -1
  84. package/dist/boot/execution-mode.d.ts.map +0 -1
  85. package/dist/boot/launch-banner.d.ts.map +0 -1
  86. package/dist/boot/launch-menu.d.ts.map +0 -1
  87. package/dist/boot/short-circuit-desktop.d.ts.map +0 -1
  88. package/dist/boot/short-circuit-flags.d.ts.map +0 -1
  89. package/dist/boot/short-circuit-hq.d.ts.map +0 -1
  90. package/dist/boot/simpleui-full-auto.d.ts.map +0 -1
  91. package/dist/boot/system-prompt-builder.d.ts.map +0 -1
  92. package/dist/boot/system-prompt.d.ts.map +0 -1
  93. package/dist/boot/tool-registry.d.ts.map +0 -1
  94. package/dist/boot/tui-coordinator-callbacks.d.ts.map +0 -1
  95. package/dist/boot/tui-coordinator-setup.d.ts.map +0 -1
  96. package/dist/boot/tui-debug-stream.d.ts.map +0 -1
  97. package/dist/boot/tui-goal-wiring.d.ts.map +0 -1
  98. package/dist/boot/tui-live-sessions.d.ts.map +0 -1
  99. package/dist/boot/tui-project-picker-callback.d.ts.map +0 -1
  100. package/dist/boot/tui-project-spawn.d.ts.map +0 -1
  101. package/dist/boot/tui-project-switch.d.ts.map +0 -1
  102. package/dist/boot/tui-runtime-state.d.ts.map +0 -1
  103. package/dist/boot/tui-sdd-callback.d.ts.map +0 -1
  104. package/dist/boot/tui-session-resume.d.ts.map +0 -1
  105. package/dist/boot/tui-settings-adapter.d.ts.map +0 -1
  106. package/dist/boot-config.d.ts.map +0 -1
  107. package/dist/boot.d.ts.map +0 -1
  108. package/dist/brain-menu/panel-service.d.ts.map +0 -1
  109. package/dist/chimera-review-task.d.ts.map +0 -1
  110. package/dist/chimera-reviewer-policy.d.ts.map +0 -1
  111. package/dist/chronicle-QQEQFSSE.js.map +0 -7
  112. package/dist/chunk-5N34EWJR.js.map +0 -7
  113. package/dist/chunk-66T43Q4Y.js.map +0 -7
  114. package/dist/chunk-7JABKXET.js.map +0 -7
  115. package/dist/chunk-7OCVIDC7.js.map +0 -7
  116. package/dist/chunk-ABXK4VVY.js.map +0 -7
  117. package/dist/chunk-AXVZMEWG.js.map +0 -7
  118. package/dist/chunk-AZESNNIC.js.map +0 -7
  119. package/dist/chunk-BGFM3PD7.js.map +0 -7
  120. package/dist/chunk-DPJZQCVP.js.map +0 -7
  121. package/dist/chunk-F4PEGAJZ.js.map +0 -7
  122. package/dist/chunk-IHBYBYGA.js.map +0 -7
  123. package/dist/chunk-JBSQ7ONN.js.map +0 -7
  124. package/dist/chunk-JBTFWWKV.js.map +0 -7
  125. package/dist/chunk-JUHRI6OV.js.map +0 -7
  126. package/dist/chunk-JW75HY4F.js.map +0 -7
  127. package/dist/chunk-OP47B3IX.js.map +0 -7
  128. package/dist/chunk-PJHET45A.js.map +0 -7
  129. package/dist/chunk-Q5GTM25S.js.map +0 -7
  130. package/dist/chunk-QD544J2B.js.map +0 -7
  131. package/dist/chunk-QUNHS6O3.js.map +0 -7
  132. package/dist/chunk-QZKWRNIP.js.map +0 -7
  133. package/dist/chunk-TADVX644.js.map +0 -7
  134. package/dist/chunk-TGDHN4LM.js.map +0 -7
  135. package/dist/chunk-TYO2OVAD.js.map +0 -7
  136. package/dist/chunk-VCYZ3WZV.js.map +0 -7
  137. package/dist/chunk-VJUEZZYB.js +0 -29
  138. package/dist/chunk-VJUEZZYB.js.map +0 -7
  139. package/dist/chunk-WHOIR577.js.map +0 -7
  140. package/dist/chunk-XJXDOF63.js.map +0 -7
  141. package/dist/chunk-XTIXVJXP.js.map +0 -7
  142. package/dist/chunk-XWEGWYDU.js.map +0 -7
  143. package/dist/chunk-YWUPHRGB.js.map +0 -7
  144. package/dist/cli-bundled-prompts.d.ts.map +0 -1
  145. package/dist/cli-bundled-skills.d.ts.map +0 -1
  146. package/dist/cli-context.d.ts.map +0 -1
  147. package/dist/cli-entry-main.d.ts.map +0 -1
  148. package/dist/cli-entry-point.d.ts.map +0 -1
  149. package/dist/cli-eternal-flag.d.ts.map +0 -1
  150. package/dist/cli-main-GOY5YGVL.js.map +0 -7
  151. package/dist/cli-main-helpers.d.ts.map +0 -1
  152. package/dist/cli-main.d.ts.map +0 -1
  153. package/dist/cli-update-notice.d.ts.map +0 -1
  154. package/dist/config-doctor.d.ts.map +0 -1
  155. package/dist/config-history.d.ts.map +0 -1
  156. package/dist/context-limit.d.ts.map +0 -1
  157. package/dist/context-overflow-diagnostic.d.ts.map +0 -1
  158. package/dist/diag-doctor-OQ3AA63O.js.map +0 -7
  159. package/dist/diff-renderer.d.ts.map +0 -1
  160. package/dist/execute-deps.d.ts.map +0 -1
  161. package/dist/execution-A4PGK4XV.js.map +0 -7
  162. package/dist/execution-chimera-ask.d.ts.map +0 -1
  163. package/dist/execution-chimera-cascade.d.ts.map +0 -1
  164. package/dist/execution-cleanup.d.ts.map +0 -1
  165. package/dist/execution-kanban-dispatch.d.ts.map +0 -1
  166. package/dist/execution-repl-fleet-callbacks.d.ts.map +0 -1
  167. package/dist/execution-storage-observability.d.ts.map +0 -1
  168. package/dist/execution-tui-next-step-callbacks.d.ts.map +0 -1
  169. package/dist/execution.d.ts.map +0 -1
  170. package/dist/export-TPORYVRZ.js.map +0 -7
  171. package/dist/fleet/host-acp-runner-cache.d.ts.map +0 -1
  172. package/dist/fleet/host-acp.d.ts.map +0 -1
  173. package/dist/fleet/host-concurrency.d.ts.map +0 -1
  174. package/dist/fleet/host-context.d.ts.map +0 -1
  175. package/dist/fleet/host-director-builder.d.ts.map +0 -1
  176. package/dist/fleet/host-director-event-bridges.d.ts.map +0 -1
  177. package/dist/fleet/host-director-services.d.ts.map +0 -1
  178. package/dist/fleet/host-event-bridge.d.ts.map +0 -1
  179. package/dist/fleet/host-helpers.d.ts.map +0 -1
  180. package/dist/fleet/host-learning-tracker.d.ts.map +0 -1
  181. package/dist/fleet/host-learning.d.ts.map +0 -1
  182. package/dist/fleet/host-lifecycle-events.d.ts.map +0 -1
  183. package/dist/fleet/host-paths.d.ts.map +0 -1
  184. package/dist/fleet/host-provider.d.ts.map +0 -1
  185. package/dist/fleet/host-session-writer.d.ts.map +0 -1
  186. package/dist/fleet/host-shadow-pass.d.ts.map +0 -1
  187. package/dist/fleet/host-spawn-types.d.ts.map +0 -1
  188. package/dist/fleet/host-status.d.ts.map +0 -1
  189. package/dist/fleet/host-subagent-factory.d.ts.map +0 -1
  190. package/dist/fleet/host-supervisor.d.ts.map +0 -1
  191. package/dist/fleet/host-task-result-report.d.ts.map +0 -1
  192. package/dist/fleet/host-types.d.ts.map +0 -1
  193. package/dist/fleet/host.d.ts.map +0 -1
  194. package/dist/fleet/routing.d.ts.map +0 -1
  195. package/dist/fleet/status-broadcast.d.ts.map +0 -1
  196. package/dist/fleet/supervisor-registry.d.ts.map +0 -1
  197. package/dist/fleet-statusline.d.ts.map +0 -1
  198. package/dist/goal-host.d.ts.map +0 -1
  199. package/dist/hooks-wiring.d.ts.map +0 -1
  200. package/dist/hq-GBPGXIR5.js.map +0 -7
  201. package/dist/hq-command-controller.d.ts.map +0 -1
  202. package/dist/hq-publisher.d.ts.map +0 -1
  203. package/dist/hq-recovery-html.d.ts.map +0 -1
  204. package/dist/hq-server/audit-actor.d.ts.map +0 -1
  205. package/dist/hq-server/auth-state.d.ts.map +0 -1
  206. package/dist/hq-server/auth.d.ts.map +0 -1
  207. package/dist/hq-server/preflight.d.ts.map +0 -1
  208. package/dist/hq-server/project-root.d.ts.map +0 -1
  209. package/dist/hq-server/routes/auth-handlers.d.ts.map +0 -1
  210. package/dist/hq-server/routes/session-handlers.d.ts.map +0 -1
  211. package/dist/hq-server/routes/system-handlers.d.ts.map +0 -1
  212. package/dist/hq-server/routes.d.ts.map +0 -1
  213. package/dist/hq-server/snapshot.d.ts.map +0 -1
  214. package/dist/hq-server/startup.d.ts.map +0 -1
  215. package/dist/hq-server/trust-boundary.d.ts.map +0 -1
  216. package/dist/hq-server/types.d.ts.map +0 -1
  217. package/dist/hq-server/utils.d.ts.map +0 -1
  218. package/dist/hq-server/ws.d.ts.map +0 -1
  219. package/dist/hq-server-YQUUMTPU.js.map +0 -7
  220. package/dist/hq-server.d.ts.map +0 -1
  221. package/dist/hq-static-serve.d.ts.map +0 -1
  222. package/dist/hq-ttl-U2FAYFAT.js.map +0 -7
  223. package/dist/hq-tunnel-RFKGC4HI.js.map +0 -7
  224. package/dist/hq-tunnel.d.ts.map +0 -1
  225. package/dist/index.d.ts.map +0 -1
  226. package/dist/index.js.map +0 -7
  227. package/dist/init-E2NDDOHI.js.map +0 -7
  228. package/dist/input-reader.d.ts.map +0 -1
  229. package/dist/kanban-dispatch-route.d.ts.map +0 -1
  230. package/dist/kanban-hq-sync.d.ts.map +0 -1
  231. package/dist/kanban-run-mirror-EKVNGXWW.js.map +0 -7
  232. package/dist/launch-hints.d.ts.map +0 -1
  233. package/dist/live-settings-input.d.ts.map +0 -1
  234. package/dist/mailbox-bridge-bootstrap.d.ts.map +0 -1
  235. package/dist/mailbox-serve-DBOOOOWZ.js.map +0 -7
  236. package/dist/mcp-CYLPUEHC.js.map +0 -7
  237. package/dist/mcp-serve.d.ts.map +0 -1
  238. package/dist/modeldiag-HENYJ4N7.js.map +0 -7
  239. package/dist/multi-agent.d.ts.map +0 -1
  240. package/dist/next-task-predictor.d.ts.map +0 -1
  241. package/dist/permission-prompt.d.ts.map +0 -1
  242. package/dist/permissions-FNYLZIL4.js.map +0 -7
  243. package/dist/picker-key-state.d.ts.map +0 -1
  244. package/dist/picker-model-picker.d.ts.map +0 -1
  245. package/dist/picker-ui.d.ts.map +0 -1
  246. package/dist/picker.d.ts.map +0 -1
  247. package/dist/plugin-api-factory.d.ts.map +0 -1
  248. package/dist/plugin-management.d.ts.map +0 -1
  249. package/dist/plugin-usage-OU6X6LM3.js.map +0 -7
  250. package/dist/plugins-4LJ5IUBI.js.map +0 -7
  251. package/dist/pre-launch/launch-prompts.d.ts.map +0 -1
  252. package/dist/pre-launch/project-check.d.ts.map +0 -1
  253. package/dist/pre-launch.d.ts.map +0 -1
  254. package/dist/preflight.d.ts.map +0 -1
  255. package/dist/profile-config-path.d.ts.map +0 -1
  256. package/dist/project-BLQ3ALVL.js.map +0 -7
  257. package/dist/project-manifest-GE25PW4S.js.map +0 -7
  258. package/dist/project-picker-BOCWHBXP.js.map +0 -7
  259. package/dist/project-picker.d.ts.map +0 -1
  260. package/dist/projects-XE76NFNZ.js.map +0 -7
  261. package/dist/provider-config-utils.d.ts.map +0 -1
  262. package/dist/provider-helpers.d.ts.map +0 -1
  263. package/dist/provider-id.d.ts.map +0 -1
  264. package/dist/providers-models-GDELBHZK.js.map +0 -7
  265. package/dist/quick-VIO2BEHS.js.map +0 -7
  266. package/dist/renderer.d.ts.map +0 -1
  267. package/dist/repl-auto-proceed.d.ts.map +0 -1
  268. package/dist/repl-client-registration.d.ts.map +0 -1
  269. package/dist/repl-input.d.ts.map +0 -1
  270. package/dist/repl-options.d.ts.map +0 -1
  271. package/dist/repl-rendering.d.ts.map +0 -1
  272. package/dist/repl-suggestions.d.ts.map +0 -1
  273. package/dist/repl.d.ts.map +0 -1
  274. package/dist/replay-KCTXNMZQ.js.map +0 -7
  275. package/dist/rewind-EJMVFVLS.js.map +0 -7
  276. package/dist/sdd-runtime-AIJCJGWC.js.map +0 -7
  277. package/dist/services/autonomy-mode.d.ts.map +0 -1
  278. package/dist/services/commit-message.d.ts.map +0 -1
  279. package/dist/services/dispatch-classifier.d.ts.map +0 -1
  280. package/dist/services/mcp-management.d.ts.map +0 -1
  281. package/dist/services/project-facts.d.ts.map +0 -1
  282. package/dist/services/project-manifest.d.ts.map +0 -1
  283. package/dist/services/sdd/project-context.d.ts.map +0 -1
  284. package/dist/services/sdd/spec-detection.d.ts.map +0 -1
  285. package/dist/services/sdd/state.d.ts.map +0 -1
  286. package/dist/services/sdd/task-manager.d.ts.map +0 -1
  287. package/dist/services/sdd-runtime.d.ts.map +0 -1
  288. package/dist/services/statusline-config.d.ts.map +0 -1
  289. package/dist/services/suggestion-store.d.ts.map +0 -1
  290. package/dist/session-event-wiring.d.ts.map +0 -1
  291. package/dist/session-stats.d.ts.map +0 -1
  292. package/dist/sessions-config-NLCMK3BY.js.map +0 -7
  293. package/dist/settings-menu.d.ts.map +0 -1
  294. package/dist/shutdown-cleanup.d.ts.map +0 -1
  295. package/dist/simpleui-dist-JIFGUJO7.js.map +0 -7
  296. package/dist/simpleui-dist.d.ts.map +0 -1
  297. package/dist/slash-commands/acp.d.ts.map +0 -1
  298. package/dist/slash-commands/agent-improve.d.ts.map +0 -1
  299. package/dist/slash-commands/agents.d.ts.map +0 -1
  300. package/dist/slash-commands/audit.d.ts.map +0 -1
  301. package/dist/slash-commands/auth.d.ts.map +0 -1
  302. package/dist/slash-commands/autonomy.d.ts.map +0 -1
  303. package/dist/slash-commands/brain.d.ts.map +0 -1
  304. package/dist/slash-commands/btw.d.ts.map +0 -1
  305. package/dist/slash-commands/clear.d.ts.map +0 -1
  306. package/dist/slash-commands/codebase-reindex.d.ts.map +0 -1
  307. package/dist/slash-commands/collab.d.ts.map +0 -1
  308. package/dist/slash-commands/command-context.d.ts.map +0 -1
  309. package/dist/slash-commands/commit-llm.d.ts.map +0 -1
  310. package/dist/slash-commands/compact.d.ts.map +0 -1
  311. package/dist/slash-commands/context.d.ts.map +0 -1
  312. package/dist/slash-commands/coordinator.d.ts.map +0 -1
  313. package/dist/slash-commands/delegate.d.ts.map +0 -1
  314. package/dist/slash-commands/design.d.ts.map +0 -1
  315. package/dist/slash-commands/dev.d.ts.map +0 -1
  316. package/dist/slash-commands/diag-stats.d.ts.map +0 -1
  317. package/dist/slash-commands/dispatch-llm.d.ts.map +0 -1
  318. package/dist/slash-commands/doctor.d.ts.map +0 -1
  319. package/dist/slash-commands/enhance.d.ts.map +0 -1
  320. package/dist/slash-commands/ensemble.d.ts.map +0 -1
  321. package/dist/slash-commands/f-keys.d.ts.map +0 -1
  322. package/dist/slash-commands/fallback.d.ts.map +0 -1
  323. package/dist/slash-commands/fix-classifier.d.ts.map +0 -1
  324. package/dist/slash-commands/fix.d.ts.map +0 -1
  325. package/dist/slash-commands/fleet.d.ts.map +0 -1
  326. package/dist/slash-commands/git.d.ts.map +0 -1
  327. package/dist/slash-commands/gitid.d.ts.map +0 -1
  328. package/dist/slash-commands/goal-refiner.d.ts.map +0 -1
  329. package/dist/slash-commands/goal.d.ts.map +0 -1
  330. package/dist/slash-commands/health.d.ts.map +0 -1
  331. package/dist/slash-commands/help.d.ts.map +0 -1
  332. package/dist/slash-commands/helpers.d.ts.map +0 -1
  333. package/dist/slash-commands/hq.d.ts.map +0 -1
  334. package/dist/slash-commands/index.d.ts.map +0 -1
  335. package/dist/slash-commands/init.d.ts.map +0 -1
  336. package/dist/slash-commands/interrupt.d.ts.map +0 -1
  337. package/dist/slash-commands/kanban-agent-helpers.d.ts.map +0 -1
  338. package/dist/slash-commands/kanban-format.d.ts.map +0 -1
  339. package/dist/slash-commands/kanban-help.d.ts.map +0 -1
  340. package/dist/slash-commands/kanban.d.ts.map +0 -1
  341. package/dist/slash-commands/mailbox-demo.d.ts.map +0 -1
  342. package/dist/slash-commands/mailbox-serve.d.ts.map +0 -1
  343. package/dist/slash-commands/mailbox.d.ts.map +0 -1
  344. package/dist/slash-commands/mcp-utils.d.ts.map +0 -1
  345. package/dist/slash-commands/mcp.d.ts.map +0 -1
  346. package/dist/slash-commands/memory-compact.d.ts.map +0 -1
  347. package/dist/slash-commands/memory-flags.d.ts.map +0 -1
  348. package/dist/slash-commands/memory-formatters.d.ts.map +0 -1
  349. package/dist/slash-commands/memory.d.ts.map +0 -1
  350. package/dist/slash-commands/metrics.d.ts.map +0 -1
  351. package/dist/slash-commands/mode.d.ts.map +0 -1
  352. package/dist/slash-commands/modelcaps.d.ts.map +0 -1
  353. package/dist/slash-commands/models.d.ts.map +0 -1
  354. package/dist/slash-commands/mouse.d.ts.map +0 -1
  355. package/dist/slash-commands/next.d.ts.map +0 -1
  356. package/dist/slash-commands/plan.d.ts.map +0 -1
  357. package/dist/slash-commands/plugin.d.ts.map +0 -1
  358. package/dist/slash-commands/profile.d.ts.map +0 -1
  359. package/dist/slash-commands/project-utils.d.ts.map +0 -1
  360. package/dist/slash-commands/project.d.ts.map +0 -1
  361. package/dist/slash-commands/provider-status.d.ts.map +0 -1
  362. package/dist/slash-commands/prune.d.ts.map +0 -1
  363. package/dist/slash-commands/refiner.d.ts.map +0 -1
  364. package/dist/slash-commands/review.d.ts.map +0 -1
  365. package/dist/slash-commands/sdd/rendering.d.ts.map +0 -1
  366. package/dist/slash-commands/sdd-command-helpers.d.ts.map +0 -1
  367. package/dist/slash-commands/sdd-format.d.ts.map +0 -1
  368. package/dist/slash-commands/sdd.d.ts.map +0 -1
  369. package/dist/slash-commands/security.d.ts.map +0 -1
  370. package/dist/slash-commands/session.d.ts.map +0 -1
  371. package/dist/slash-commands/setmodel.d.ts.map +0 -1
  372. package/dist/slash-commands/settings.d.ts.map +0 -1
  373. package/dist/slash-commands/shadow.d.ts.map +0 -1
  374. package/dist/slash-commands/slash-deep-help.d.ts.map +0 -1
  375. package/dist/slash-commands/spawn-agents.d.ts.map +0 -1
  376. package/dist/slash-commands/statusline.d.ts.map +0 -1
  377. package/dist/slash-commands/suggest.d.ts.map +0 -1
  378. package/dist/slash-commands/suggestion-store.d.ts.map +0 -1
  379. package/dist/slash-commands/supervisor.d.ts.map +0 -1
  380. package/dist/slash-commands/surfaces.d.ts.map +0 -1
  381. package/dist/slash-commands/tasks.d.ts.map +0 -1
  382. package/dist/slash-commands/techstack.d.ts.map +0 -1
  383. package/dist/slash-commands/telegram-pairing.d.ts.map +0 -1
  384. package/dist/slash-commands/telegram-settings.d.ts.map +0 -1
  385. package/dist/slash-commands/telegram-setup.d.ts.map +0 -1
  386. package/dist/slash-commands/todos.d.ts.map +0 -1
  387. package/dist/slash-commands/tool.d.ts.map +0 -1
  388. package/dist/slash-commands/tools.d.ts.map +0 -1
  389. package/dist/slash-commands/tuneup.d.ts.map +0 -1
  390. package/dist/slash-commands/working-dir.d.ts.map +0 -1
  391. package/dist/slash-commands/worktree.d.ts.map +0 -1
  392. package/dist/slash-commands/yolo.d.ts.map +0 -1
  393. package/dist/spinner.d.ts.map +0 -1
  394. package/dist/subcommands/contracts.d.ts.map +0 -1
  395. package/dist/subcommands/handlers/acp.d.ts.map +0 -1
  396. package/dist/subcommands/handlers/audit.d.ts.map +0 -1
  397. package/dist/subcommands/handlers/auth-local-help.d.ts.map +0 -1
  398. package/dist/subcommands/handlers/auth.d.ts.map +0 -1
  399. package/dist/subcommands/handlers/bench-run-help.d.ts.map +0 -1
  400. package/dist/subcommands/handlers/bench.d.ts.map +0 -1
  401. package/dist/subcommands/handlers/chronicle.d.ts.map +0 -1
  402. package/dist/subcommands/handlers/config-history.d.ts.map +0 -1
  403. package/dist/subcommands/handlers/diag-doctor.d.ts.map +0 -1
  404. package/dist/subcommands/handlers/export.d.ts.map +0 -1
  405. package/dist/subcommands/handlers/helpers.d.ts.map +0 -1
  406. package/dist/subcommands/handlers/hq.d.ts.map +0 -1
  407. package/dist/subcommands/handlers/init.d.ts.map +0 -1
  408. package/dist/subcommands/handlers/mailbox-serve.d.ts.map +0 -1
  409. package/dist/subcommands/handlers/mcp.d.ts.map +0 -1
  410. package/dist/subcommands/handlers/model-smoke-test.d.ts.map +0 -1
  411. package/dist/subcommands/handlers/modeldiag-profiles.d.ts.map +0 -1
  412. package/dist/subcommands/handlers/modeldiag.d.ts.map +0 -1
  413. package/dist/subcommands/handlers/models-add-help.d.ts.map +0 -1
  414. package/dist/subcommands/handlers/per-subcommand-deep-help-table.d.ts.map +0 -1
  415. package/dist/subcommands/handlers/per-subcommand-help-data.d.ts.map +0 -1
  416. package/dist/subcommands/handlers/per-subcommand-help-table.d.ts.map +0 -1
  417. package/dist/subcommands/handlers/per-subcommand-help-types.d.ts.map +0 -1
  418. package/dist/subcommands/handlers/per-subcommand-help.d.ts.map +0 -1
  419. package/dist/subcommands/handlers/permissions.d.ts.map +0 -1
  420. package/dist/subcommands/handlers/plugin-usage.d.ts.map +0 -1
  421. package/dist/subcommands/handlers/project.d.ts.map +0 -1
  422. package/dist/subcommands/handlers/projects.d.ts.map +0 -1
  423. package/dist/subcommands/handlers/providers-models.d.ts.map +0 -1
  424. package/dist/subcommands/handlers/quick.d.ts.map +0 -1
  425. package/dist/subcommands/handlers/replay.d.ts.map +0 -1
  426. package/dist/subcommands/handlers/rewind.d.ts.map +0 -1
  427. package/dist/subcommands/handlers/sessions-config.d.ts.map +0 -1
  428. package/dist/subcommands/handlers/sessions-fleet.d.ts.map +0 -1
  429. package/dist/subcommands/handlers/tools-skills.d.ts.map +0 -1
  430. package/dist/subcommands/handlers/update.d.ts.map +0 -1
  431. package/dist/subcommands/handlers/version-help.d.ts.map +0 -1
  432. package/dist/subcommands/index.d.ts.map +0 -1
  433. package/dist/theme.d.ts.map +0 -1
  434. package/dist/tools-skills-UNKLOB7M.js.map +0 -7
  435. package/dist/tui-thinking-word.d.ts.map +0 -1
  436. package/dist/tuneup.d.ts.map +0 -1
  437. package/dist/update-YC4TYW2Y.js.map +0 -7
  438. package/dist/update-check-UMYTOH7Z.js.map +0 -7
  439. package/dist/update-check.d.ts.map +0 -1
  440. package/dist/utils/delay-format.d.ts.map +0 -1
  441. package/dist/utils/hq-ttl.d.ts.map +0 -1
  442. package/dist/utils/win32-cmd.d.ts.map +0 -1
  443. package/dist/utils.d.ts.map +0 -1
  444. package/dist/version-help-DROHZRW6.js.map +0 -7
  445. package/dist/version.d.ts.map +0 -1
  446. package/dist/webui-server/client-registration.d.ts.map +0 -1
  447. package/dist/webui-server/connection-handler.d.ts.map +0 -1
  448. package/dist/webui-server/context-breakdown.d.ts.map +0 -1
  449. package/dist/webui-server/contracts.d.ts.map +0 -1
  450. package/dist/webui-server/cost-helpers.d.ts.map +0 -1
  451. package/dist/webui-server/kanban-host-adapter.d.ts.map +0 -1
  452. package/dist/webui-server/kanban-run-mirror.d.ts.map +0 -1
  453. package/dist/webui-server/kanban-supervisor.d.ts.map +0 -1
  454. package/dist/webui-server/lifecycle.d.ts.map +0 -1
  455. package/dist/webui-server/listen-helpers.d.ts.map +0 -1
  456. package/dist/webui-server/logger-shim.d.ts.map +0 -1
  457. package/dist/webui-server/node-pty-loader.d.ts.map +0 -1
  458. package/dist/webui-server/prefs-seeding.d.ts.map +0 -1
  459. package/dist/webui-server/privileged-actions.d.ts.map +0 -1
  460. package/dist/webui-server/provider-config.d.ts.map +0 -1
  461. package/dist/webui-server/session-start-payload.d.ts.map +0 -1
  462. package/dist/webui-server/setup-events.d.ts.map +0 -1
  463. package/dist/webui-server/static-serve.d.ts.map +0 -1
  464. package/dist/webui-server/stream-coalescer.d.ts.map +0 -1
  465. package/dist/webui-server/terminal-log-view.d.ts.map +0 -1
  466. package/dist/webui-server-XKFUOKMJ.js.map +0 -7
  467. package/dist/webui-server-options.d.ts.map +0 -1
  468. package/dist/webui-server.d.ts.map +0 -1
  469. package/dist/wiring/brain-and-orchestration.d.ts.map +0 -1
  470. package/dist/wiring/codebase-index.d.ts.map +0 -1
  471. package/dist/wiring/command-host-adapters.d.ts.map +0 -1
  472. package/dist/wiring/command-host-state.d.ts.map +0 -1
  473. package/dist/wiring/controllers.d.ts.map +0 -1
  474. package/dist/wiring/dep-watcher-bridge.d.ts.map +0 -1
  475. package/dist/wiring/dep-watcher.d.ts.map +0 -1
  476. package/dist/wiring/design-studio.d.ts.map +0 -1
  477. package/dist/wiring/director-setup.d.ts.map +0 -1
  478. package/dist/wiring/eternal-command-handlers.d.ts.map +0 -1
  479. package/dist/wiring/fleet-command-handlers.d.ts.map +0 -1
  480. package/dist/wiring/hq-telemetry.d.ts.map +0 -1
  481. package/dist/wiring/lifecycle-plugins.d.ts.map +0 -1
  482. package/dist/wiring/mailbox-bridge-bootstrap.d.ts.map +0 -1
  483. package/dist/wiring/management-tools.d.ts.map +0 -1
  484. package/dist/wiring/metrics.d.ts.map +0 -1
  485. package/dist/wiring/pipeline.d.ts.map +0 -1
  486. package/dist/wiring/plugins.d.ts.map +0 -1
  487. package/dist/wiring/provider-runtime-setup.d.ts.map +0 -1
  488. package/dist/wiring/provider-runtime.d.ts.map +0 -1
  489. package/dist/wiring/provider-status.d.ts.map +0 -1
  490. package/dist/wiring/provider-utility-tools.d.ts.map +0 -1
  491. package/dist/wiring/provider.d.ts.map +0 -1
  492. package/dist/wiring/replay.d.ts.map +0 -1
  493. package/dist/wiring/runtime-controller-deps.d.ts.map +0 -1
  494. package/dist/wiring/runtime-dispatch-state.d.ts.map +0 -1
  495. package/dist/wiring/runtime-lifecycle-deps.d.ts.map +0 -1
  496. package/dist/wiring/runtime-picker-deps.d.ts.map +0 -1
  497. package/dist/wiring/sage.d.ts.map +0 -1
  498. package/dist/wiring/sdd-handlers.d.ts.map +0 -1
  499. package/dist/wiring/security-command.d.ts.map +0 -1
  500. package/dist/wiring/session-command-handlers.d.ts.map +0 -1
  501. package/dist/wiring/session-registry.d.ts.map +0 -1
  502. package/dist/wiring/session.d.ts.map +0 -1
  503. package/dist/wiring/slash-commands.d.ts.map +0 -1
  504. package/dist/wiring/to-execute-deps.d.ts.map +0 -1
  505. package/dist/wiring/tools.d.ts.map +0 -1
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../src/wiring/plugins.ts", "../src/plugin-api-factory.ts"],
4
- "sourcesContent": ["import { join } from 'node:path';\nimport type { AgentPipelines } from '@wrongstack/core/agent';\nimport type { Config, HealthRegistry, Logger, MetricsRuntimeStatus, MetricsSinkView, ModelsRegistry, Plugin, PromptLoader, SkillLoader } from '@wrongstack/core/types';\nimport type { ExtensionRegistry } from '@wrongstack/core/extension';\nimport type { PluginHostHandle } from '@wrongstack/core/plugin';\nimport type { ConfigStore, SessionWriter } from '@wrongstack/core/types';\nimport type { Container, EventBus } from '@wrongstack/core/kernel';\nimport type { ProviderRegistry, SlashCommandRegistry, ToolRegistry } from '@wrongstack/core/registry';\nimport { loadPlugins, resolvePluginConfig } from '@wrongstack/core/plugin';\nimport type { MCPRegistry } from '@wrongstack/mcp';\nimport { OFFICIAL_PLUGIN_FACTORIES } from '@wrongstack/plugins/factories';\nimport createApi from '../plugin-api-factory.js';\nimport { PLUGIN_AUDIT_ENTRIES } from '../plugin-management.js';\nimport { patchConfig } from '../utils.js';\n\n// ---------------------------------------------------------------------------\n// Deprecated plugin names \u2014 built-ins that have been merged into core\n// tools and no longer ship as separate plugins. We no longer auto-import\n// these factories, and if a user references one of these names in their\n// `config.plugins` we warn once and skip. Removal is split into two\n// phases:\n// 1. Remove the factory from BUILTIN_PLUGIN_FACTORIES (today).\n// 2. Drop the source files + subpath exports + tests from\n// @wrongstack/plugins in a follow-up commit.\n// Keeping the source files temporarily (phase 2) means user configs\n// that hard-code `@wrongstack/plugins/web-search` as a string spec\n// still resolve at runtime \u2014 the loader receives a no-op stub plugin\n// instead of an import error. Once the user removes the entry from\n// their config, the source can be safely deleted.\n// ---------------------------------------------------------------------------\nexport const DEPRECATED_PLUGIN_NAMES: Record<string, string> = {\n 'web-search': 'use the built-in `search` and `fetch` tools',\n 'json-path': 'use the built-in `json` tool with action: query | validate | transform | merge',\n};\n\n// Per-process dedupe so we don't spam the log if a user lists the\n// same deprecated name across multiple config entries (object form\n// + string form, etc.). Cleared on process restart by design \u2014\n// startup noise is fine, mid-session noise is not.\nconst deprecatedWarningsEmitted = new Set<string>();\n\nconst BUILTIN_PLUGIN_CONFIG_ALIASES: Record<string, string> = {\n lsp: '@wrongstack/plug-lsp',\n '@wrongstack/telegram': 'telegram',\n};\n\nexport const BUILTIN_PLUGIN_CONFIG_NAMES = new Set<string>([\n ...PLUGIN_AUDIT_ENTRIES.map((entry) => entry.name),\n ...PLUGIN_AUDIT_ENTRIES.map((entry) => `@wrongstack/plugins/${entry.name}`),\n ...Object.keys(BUILTIN_PLUGIN_CONFIG_ALIASES),\n]);\n\n/** Test helper: reset the dedupe set between test cases. */\nexport function _resetDeprecatedWarningsForTests(): void {\n deprecatedWarningsEmitted.clear();\n}\n\n/**\n * If `name` is in `DEPRECATED_PLUGIN_NAMES`, log a one-shot `warn`\n * describing the migration target and return true (caller should\n * skip the plugin). If the name is deprecated but already warned\n * about, return true WITHOUT logging again \u2014 the caller still needs\n * to know to skip the plugin. For unknown names, return false.\n */\nexport function warnIfDeprecatedPluginName(name: string, log: Logger): boolean {\n const replacement = DEPRECATED_PLUGIN_NAMES[name];\n if (!replacement) return false;\n if (deprecatedWarningsEmitted.has(name)) return true;\n deprecatedWarningsEmitted.add(name);\n log.warn(`[setupPlugins] plugin \"${name}\" is deprecated and no longer loaded \u2014 ${replacement}`);\n return true;\n}\n\n/**\n * Normalize a plugin spec (either a short name like `'web-search'` or a\n * fully-qualified import path like `'@wrongstack/plugins/web-search'`)\n * to its bare plugin name. Used to look the spec up in\n * `DEPRECATED_PLUGIN_NAMES` regardless of how the user spelled it.\n *\n * Returns null if the spec is not a string we can normalize (e.g.\n * relative paths, file URLs).\n */\nexport function pluginNameFromSpec(spec: string): string | null {\n if (spec.startsWith('.') || spec.startsWith('/') || spec.startsWith('file:')) {\n return null;\n }\n // `@scope/name/sub` \u2192 'name'; `@scope/name` \u2192 'name'; `name/sub` \u2192 'name'.\n const parts = spec.split('/');\n const last = parts[parts.length - 1];\n if (!last) return null;\n return last.split('?')[0] ?? null;\n}\n\nexport function builtinPluginNameFromSpec(spec: string): string | null {\n if (BUILTIN_PLUGIN_CONFIG_ALIASES[spec]) return BUILTIN_PLUGIN_CONFIG_ALIASES[spec];\n const bareName = pluginNameFromSpec(spec);\n if (BUILTIN_PLUGIN_CONFIG_NAMES.has(spec)) return bareName ?? spec;\n if (bareName && BUILTIN_PLUGIN_CONFIG_NAMES.has(bareName)) return bareName;\n return null;\n}\n\nexport interface PluginsWiringDeps {\n config: Config;\n container: Container;\n events: EventBus;\n pipelines: AgentPipelines;\n toolRegistry: ToolRegistry;\n providerRegistry: ProviderRegistry;\n slashCommandRegistry: SlashCommandRegistry;\n mcpRegistry: MCPRegistry;\n log: Logger;\n agent: { extensions?: ExtensionRegistry | undefined };\n /** Lifecycle hook registry \u2014 injected so plugins can register in-process hooks. */\n hookRegistry?: import('@wrongstack/core/hooks').HookRegistry | undefined;\n sessionWriter: SessionWriter;\n metricsSink?: MetricsSinkView | undefined;\n metricsStatus?: MetricsRuntimeStatus | undefined;\n /**\n * Models registry (models.dev-backed catalog of providers, models, and\n * per-token pricing). Forwarded to plugins that need model metadata\n * (cost-tracker, billing reports). Optional \u2014 minimal hosts may omit.\n */\n modelsRegistry?: ModelsRegistry | undefined;\n /**\n * Project-level mailbox (server-backed). Forwarded to plugins that\n * publish to other agents (todo-listener, session-recap). Optional \u2014\n * minimal hosts (tests, the LSP server) may omit.\n */\n mailbox?: import('@wrongstack/core/coordination').Mailbox | undefined;\n /**\n * Notification router \u2014 instantiated as a `NotifierImpl` by the host.\n * Plugins that deliver one-way notifications (Telegram, Slack, webhook)\n * register their `NotificationChannel` with this notifier during setup().\n * Optional \u2014 minimal hosts may omit.\n */\n notifier?: import('@wrongstack/core/notifications').Notifier | undefined;\n /**\n * LLM wiring for `api.llm` \u2014 the host session's live provider, its\n * default model, and a factory for named-provider overrides. Optional \u2014\n * minimal hosts omit it and plugins see `api.llm === undefined`.\n */\n llm?:\n | {\n provider: import('@wrongstack/core/types').Provider;\n model: string;\n getProvider?: (() => import('@wrongstack/core/types').Provider) | undefined;\n getModel?: (() => string) | undefined;\n createProvider?:\n | ((name: string, model?: string) => import('@wrongstack/core/types').Provider)\n | undefined;\n }\n | undefined;\n /** Health registry \u2014 injected so the observability built-in can run /health. */\n healthRegistry?: HealthRegistry | undefined;\n /** Skill loader \u2014 injected so the skills built-in can list/read skills. */\n skillLoader?: SkillLoader | undefined;\n /** Prompt loader \u2014 injected so the prompts built-in can list/search/save prompts. */\n promptLoader?: PromptLoader | undefined;\n configStore: ConfigStore;\n /** Secret vault \u2014 injected so sync plugin can encrypt the GitHub token. */\n vault?: { encrypt(plaintext: string): string; decrypt?(value: string): string };\n /** Resolved WstackPaths \u2014 injected so built-in plugins can init stores. */\n paths?: {\n globalRoot: string;\n globalConfig: string;\n globalSkills: string;\n globalPrompts: string;\n globalMemory: string;\n historyFile: string;\n syncConfig: string;\n /** ~/.wrongstack/profiles/<activeProfile> \u2014 used by sync plugin for its state path. */\n configDir: string;\n /**\n * Per-project root (`~/.wrongstack/projects/<slug>/`). Plugins that\n * need project-scoped state (todo-tracker, etc.) should put their\n * files here so they follow the same lifecycle as goals/SDD\n * boards/tasks.\n */\n projectDir?: string;\n /** Per-project goal.json path. Useful as a sibling anchor. */\n projectGoal?: string;\n };\n}\n\n/**\n * Built-in plugins loaded automatically for every WrongStack session.\n * Lazy (dynamic import) so they don't bloat consumers who never use them.\n *\n * Disable a built-in by adding `{ name: 'wstack-prompts', enabled: false }`\n * to config.plugins.\n *\n * Override for tests by mocking this module's `BUILTIN_PLUGIN_FACTORIES`.\n */\nexport const BUILTIN_PLUGIN_FACTORIES: (() => Promise<Plugin>)[] = [\n async () => {\n const { createPromptsPlugin } = await import('@wrongstack/core/plugin');\n return createPromptsPlugin();\n },\n async () => {\n const { createSyncPlugin } = await import('@wrongstack/core/plugin');\n return createSyncPlugin();\n },\n async () => {\n const { createChimeraPlugin } = await import('@wrongstack/core/plugin');\n return createChimeraPlugin();\n },\n async () => {\n const { createAutoReviewPlugin } = await import('@wrongstack/core/plugin');\n return createAutoReviewPlugin();\n },\n async () => {\n const { createSkillsPlugin } = await import('@wrongstack/core/plugin');\n return createSkillsPlugin();\n },\n // \u2500\u2500 Workspace plugins (@wrongstack/plugins subpath exports) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n ...OFFICIAL_PLUGIN_FACTORIES,\n // \u2500\u2500 LSP plugin \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n async () => (await import('@wrongstack/plug-lsp')).default,\n // \u2500\u2500 Telegram plugin \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n async () => (await import('@wrongstack/telegram')).default,\n];\n\nexport async function setupPlugins(\n params: PluginsWiringDeps,\n): Promise<PluginHostHandle | undefined> {\n const {\n config,\n container,\n events,\n toolRegistry,\n providerRegistry,\n slashCommandRegistry,\n mcpRegistry,\n log,\n agent,\n sessionWriter,\n metricsSink,\n metricsStatus,\n modelsRegistry,\n mailbox,\n healthRegistry,\n skillLoader,\n promptLoader,\n configStore,\n vault,\n pipelines,\n paths,\n hookRegistry,\n } = params;\n\n // \u2500\u2500 1. Load built-in plugins (prompts, sync, git, \u2026) only when paths are\n // available \u2014 they need WstackPaths to initialise their stores.\n //\n // Built-ins are ENABLED BY DEFAULT. A user can opt a specific one out by\n // adding `{ name: 'wstack-git', enabled: false }` to `config.plugins`\n // (or disable all plugins with `config.features.plugins === false`).\n const builtinPlugins: Plugin[] = [];\n const disabledBuiltins = new Set(\n (config.plugins ?? [])\n .filter(\n (p): p is { name: string; enabled?: boolean | undefined } =>\n typeof p === 'object' && p.enabled === false,\n )\n .flatMap((p) => {\n const builtinName = builtinPluginNameFromSpec(p.name);\n return builtinName && builtinName !== p.name ? [p.name, builtinName] : [p.name];\n }),\n );\n if (paths && config.features?.plugins !== false) {\n for (const factory of BUILTIN_PLUGIN_FACTORIES) {\n try {\n const plugin = await factory();\n if (!plugin) continue;\n const auditEntry = PLUGIN_AUDIT_ENTRIES.find((entry) => entry.name === plugin.name);\n const explicitlyEnabled = (config.plugins ?? []).some((entry) => {\n const spec = typeof entry === 'string' ? entry : entry.name;\n if (typeof entry === 'object' && entry.enabled === false) return false;\n return (builtinPluginNameFromSpec(spec) ?? spec) === plugin.name;\n });\n if (auditEntry?.defaultState === 'inactive' && !explicitlyEnabled) {\n continue;\n }\n if (disabledBuiltins.has(plugin.name)) {\n log.info(`[setupPlugins] built-in plugin \"${plugin.name}\" disabled by config`);\n continue;\n }\n // Defensive: if a future PR leaves a deprecated factory in\n // BUILTIN_PLUGIN_FACTORIES, the loader-level deprecation policy\n // still skips it (and warns once per name). Today this branch\n // is unreachable because we removed those factories \u2014 but the\n // check stays so a sloppy re-add doesn't silently re-enable a\n // retired plugin.\n if (warnIfDeprecatedPluginName(plugin.name, log)) continue;\n builtinPlugins.push(plugin);\n } catch (err) {\n log.warn('[setupPlugins] builtin plugin failed to load:', err);\n }\n }\n }\n\n // \u2500\u2500 2. Load user plugins from config.plugins \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n const userPlugins: Plugin[] = [];\n if (config.features?.plugins !== false) {\n for (const p of config.plugins ?? []) {\n if (typeof p === 'object' && p.enabled === false) continue;\n const spec = typeof p === 'string' ? p : p.name;\n // Deprecation policy: if the spec resolves to a deprecated plugin\n // name (either as `'web-search'` or `'@wrongstack/plugins/web-search'`),\n // warn once and skip the dynamic import. Today this means\n // web-search/json-path stub plugins silently load \u2014 after the\n // source files are deleted (phase 2), this branch becomes the\n // only line of defense.\n const bareName = pluginNameFromSpec(spec);\n if (bareName && warnIfDeprecatedPluginName(bareName, log)) {\n continue;\n }\n if (builtinPluginNameFromSpec(spec)) {\n continue;\n }\n try {\n const mod = (await import(spec)) as { default?: Plugin | undefined };\n if (mod.default) userPlugins.push(mod.default);\n } catch (err) {\n log.warn(`Plugin \"${spec}\" failed to load`, err);\n }\n }\n }\n\n // \u2500\u2500 3. Merge: builtins first (they set up infrastructure), then user plugins\n const allPlugins = [...builtinPlugins, ...userPlugins];\n if (allPlugins.length === 0) return;\n\n const pluginOptions = buildPluginOptions(config, allPlugins);\n\n // Workspace plugins that persist project-scoped state read ONLY their own\n // namespaced `config.extensions[name]` options \u2014 they never see the\n // top-level `paths` injected below. Bridge that gap for todo-tracker by\n // seeding a default `filePath` derived from `paths.projectDir` when the\n // user hasn't set one explicitly. This mirrors how goals/SDD boards/tasks\n // live under `~/.wrongstack/projects/<slug>/` and follows the intent\n // documented on `PluginsWiringDeps.paths.projectDir`.\n if (paths?.projectDir) {\n if (pluginOptions['todo-tracker'] === undefined) {\n pluginOptions['todo-tracker'] = {};\n }\n const todoTrackerOpts = pluginOptions['todo-tracker'];\n if (typeof todoTrackerOpts['filePath'] !== 'string' || todoTrackerOpts['filePath'] === '') {\n todoTrackerOpts['filePath'] = join(paths.projectDir, 'todo-tracker.json');\n }\n // context-pins persists pinned facts the same way \u2014 project-scoped,\n // next to goals/SDD boards/todo-tracker.\n const contextPinsOpts = pluginOptions['context-pins'] ?? {};\n pluginOptions['context-pins'] = contextPinsOpts;\n if (typeof contextPinsOpts['filePath'] !== 'string' || contextPinsOpts['filePath'] === '') {\n contextPinsOpts['filePath'] = join(paths.projectDir, 'context-pins.json');\n }\n }\n\n // Built-in plugins read their host dependencies off the TOP LEVEL of the\n // config object they receive (e.g. prompts/sync use `config.paths` /\n // `config.configStore`, observability uses `config.metricsSink` /\n // `config.healthRegistry`). Inject them here so each can wire up its store or\n // view without a circular import. User plugins never see these \u2014 they only\n // read their own namespaced `config.extensions[name]` options.\n const pluginConfig = patchConfig(config, {\n extensions: pluginOptions,\n paths,\n configStore,\n vault,\n metricsSink,\n metricsStatus,\n healthRegistry,\n skillLoader,\n promptLoader,\n } as Partial<Config>);\n\n const pluginHost = await loadPlugins(allPlugins, {\n log,\n pluginOptions,\n apiFactory: (plugin) =>\n createApi(plugin.name, {\n // First-party plugins come from BUILTIN_PLUGIN_FACTORIES \u2014 trust them\n // (\"official\") so they can claim bare slash command names (/prompts,\n // /sync) and override built-ins. User plugins stay namespaced.\n official: builtinPlugins.includes(plugin),\n container,\n events,\n pipelines: pipelines as never as Parameters<typeof createApi>[1]['pipelines'],\n toolRegistry,\n providerRegistry,\n slashCommandRegistry,\n mcpRegistry,\n config: pluginConfig,\n log,\n extensions: agent.extensions,\n hookRegistry,\n modelsRegistry,\n mailbox,\n notifier: params.notifier,\n llm: params.llm,\n sessionWriter: {\n transcriptPath: sessionWriter.transcriptPath,\n append: (e: Record<string, unknown> & { type: string; ts: string }) =>\n sessionWriter.append(e as Parameters<typeof sessionWriter.append>[0]),\n },\n metricsSink,\n configStore,\n }),\n });\n\n log.info(\n `[setupPlugins] loaded ${builtinPlugins.length} built-in, ${userPlugins.length} user plugin(s)`,\n );\n return pluginHost;\n}\n\nfunction buildPluginOptions(\n config: Config,\n plugins: readonly Plugin[],\n): Record<string, Record<string, unknown>> {\n const options: Record<string, Record<string, unknown>> = {};\n for (const [name, value] of Object.entries(config.extensions ?? {})) {\n options[name] = { ...value };\n }\n for (const plugin of plugins) {\n const resolved = resolvePluginConfig({\n name: plugin.name,\n aliases: plugin.configAliases,\n config,\n });\n if (resolved.configured) options[plugin.name] = resolved.options;\n }\n return options;\n}\n", "import { DefaultPluginAPI, type PluginAPI, type PluginAPIInit } from '@wrongstack/core/plugin';\n\nexport default function createApi(\n ownerName: string,\n base: Omit<PluginAPIInit, 'ownerName'>,\n): PluginAPI {\n return new DefaultPluginAPI({ ownerName, ...base });\n}\n"],
5
- "mappings": ";;;;;;;;;AAAA,SAAS,YAAY;AAQrB,SAAS,aAAa,2BAA2B;AAEjD,SAAS,iCAAiC;;;ACV1C,SAAS,wBAA4D;AAEtD,SAAR,UACL,WACA,MACW;AACX,SAAO,IAAI,iBAAiB,EAAE,WAAW,GAAG,KAAK,CAAC;AACpD;;;ADuBO,IAAM,0BAAkD;AAAA,EAC7D,cAAc;AAAA,EACd,aAAa;AACf;AAMA,IAAM,4BAA4B,oBAAI,IAAY;AAElD,IAAM,gCAAwD;AAAA,EAC5D,KAAK;AAAA,EACL,wBAAwB;AAC1B;AAEO,IAAM,8BAA8B,oBAAI,IAAY;AAAA,EACzD,GAAG,qBAAqB,IAAI,CAAC,UAAU,MAAM,IAAI;AAAA,EACjD,GAAG,qBAAqB,IAAI,CAAC,UAAU,uBAAuB,MAAM,IAAI,EAAE;AAAA,EAC1E,GAAG,OAAO,KAAK,6BAA6B;AAC9C,CAAC;AAGM,SAAS,mCAAyC;AACvD,4BAA0B,MAAM;AAClC;AASO,SAAS,2BAA2B,MAAc,KAAsB;AAC7E,QAAM,cAAc,wBAAwB,IAAI;AAChD,MAAI,CAAC,YAAa,QAAO;AACzB,MAAI,0BAA0B,IAAI,IAAI,EAAG,QAAO;AAChD,4BAA0B,IAAI,IAAI;AAClC,MAAI,KAAK,0BAA0B,IAAI,+CAA0C,WAAW,EAAE;AAC9F,SAAO;AACT;AAWO,SAAS,mBAAmB,MAA6B;AAC9D,MAAI,KAAK,WAAW,GAAG,KAAK,KAAK,WAAW,GAAG,KAAK,KAAK,WAAW,OAAO,GAAG;AAC5E,WAAO;AAAA,EACT;AAEA,QAAM,QAAQ,KAAK,MAAM,GAAG;AAC5B,QAAM,OAAO,MAAM,MAAM,SAAS,CAAC;AACnC,MAAI,CAAC,KAAM,QAAO;AAClB,SAAO,KAAK,MAAM,GAAG,EAAE,CAAC,KAAK;AAC/B;AAEO,SAAS,0BAA0B,MAA6B;AACrE,MAAI,8BAA8B,IAAI,EAAG,QAAO,8BAA8B,IAAI;AAClF,QAAM,WAAW,mBAAmB,IAAI;AACxC,MAAI,4BAA4B,IAAI,IAAI,EAAG,QAAO,YAAY;AAC9D,MAAI,YAAY,4BAA4B,IAAI,QAAQ,EAAG,QAAO;AAClE,SAAO;AACT;AA8FO,IAAM,2BAAsD;AAAA,EACjE,YAAY;AACV,UAAM,EAAE,oBAAoB,IAAI,MAAM,OAAO,yBAAyB;AACtE,WAAO,oBAAoB;AAAA,EAC7B;AAAA,EACA,YAAY;AACV,UAAM,EAAE,iBAAiB,IAAI,MAAM,OAAO,yBAAyB;AACnE,WAAO,iBAAiB;AAAA,EAC1B;AAAA,EACA,YAAY;AACV,UAAM,EAAE,oBAAoB,IAAI,MAAM,OAAO,yBAAyB;AACtE,WAAO,oBAAoB;AAAA,EAC7B;AAAA,EACA,YAAY;AACV,UAAM,EAAE,uBAAuB,IAAI,MAAM,OAAO,yBAAyB;AACzE,WAAO,uBAAuB;AAAA,EAChC;AAAA,EACA,YAAY;AACV,UAAM,EAAE,mBAAmB,IAAI,MAAM,OAAO,yBAAyB;AACrE,WAAO,mBAAmB;AAAA,EAC5B;AAAA;AAAA,EAEA,GAAG;AAAA;AAAA,EAEH,aAAa,MAAM,OAAO,sBAAsB,GAAG;AAAA;AAAA,EAEnD,aAAa,MAAM,OAAO,sBAAsB,GAAG;AACrD;AAEA,eAAsB,aACpB,QACuC;AACvC,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAI;AAQJ,QAAM,iBAA2B,CAAC;AAClC,QAAM,mBAAmB,IAAI;AAAA,KAC1B,OAAO,WAAW,CAAC,GACjB;AAAA,MACC,CAAC,MACC,OAAO,MAAM,YAAY,EAAE,YAAY;AAAA,IAC3C,EACC,QAAQ,CAAC,MAAM;AACd,YAAM,cAAc,0BAA0B,EAAE,IAAI;AACpD,aAAO,eAAe,gBAAgB,EAAE,OAAO,CAAC,EAAE,MAAM,WAAW,IAAI,CAAC,EAAE,IAAI;AAAA,IAChF,CAAC;AAAA,EACL;AACA,MAAI,SAAS,OAAO,UAAU,YAAY,OAAO;AAC/C,eAAW,WAAW,0BAA0B;AAC9C,UAAI;AACF,cAAM,SAAS,MAAM,QAAQ;AAC7B,YAAI,CAAC,OAAQ;AACb,cAAM,aAAa,qBAAqB,KAAK,CAAC,UAAU,MAAM,SAAS,OAAO,IAAI;AAClF,cAAM,qBAAqB,OAAO,WAAW,CAAC,GAAG,KAAK,CAAC,UAAU;AAC/D,gBAAM,OAAO,OAAO,UAAU,WAAW,QAAQ,MAAM;AACvD,cAAI,OAAO,UAAU,YAAY,MAAM,YAAY,MAAO,QAAO;AACjE,kBAAQ,0BAA0B,IAAI,KAAK,UAAU,OAAO;AAAA,QAC9D,CAAC;AACD,YAAI,YAAY,iBAAiB,cAAc,CAAC,mBAAmB;AACjE;AAAA,QACF;AACA,YAAI,iBAAiB,IAAI,OAAO,IAAI,GAAG;AACrC,cAAI,KAAK,mCAAmC,OAAO,IAAI,sBAAsB;AAC7E;AAAA,QACF;AAOA,YAAI,2BAA2B,OAAO,MAAM,GAAG,EAAG;AAClD,uBAAe,KAAK,MAAM;AAAA,MAC5B,SAAS,KAAK;AACZ,YAAI,KAAK,iDAAiD,GAAG;AAAA,MAC/D;AAAA,IACF;AAAA,EACF;AAGA,QAAM,cAAwB,CAAC;AAC/B,MAAI,OAAO,UAAU,YAAY,OAAO;AACtC,eAAW,KAAK,OAAO,WAAW,CAAC,GAAG;AACpC,UAAI,OAAO,MAAM,YAAY,EAAE,YAAY,MAAO;AAClD,YAAM,OAAO,OAAO,MAAM,WAAW,IAAI,EAAE;AAO3C,YAAM,WAAW,mBAAmB,IAAI;AACxC,UAAI,YAAY,2BAA2B,UAAU,GAAG,GAAG;AACzD;AAAA,MACF;AACA,UAAI,0BAA0B,IAAI,GAAG;AACnC;AAAA,MACF;AACA,UAAI;AACF,cAAM,MAAO,MAAM,OAAO;AAC1B,YAAI,IAAI,QAAS,aAAY,KAAK,IAAI,OAAO;AAAA,MAC/C,SAAS,KAAK;AACZ,YAAI,KAAK,WAAW,IAAI,oBAAoB,GAAG;AAAA,MACjD;AAAA,IACF;AAAA,EACF;AAGA,QAAM,aAAa,CAAC,GAAG,gBAAgB,GAAG,WAAW;AACrD,MAAI,WAAW,WAAW,EAAG;AAE7B,QAAM,gBAAgB,mBAAmB,QAAQ,UAAU;AAS3D,MAAI,OAAO,YAAY;AACrB,QAAI,cAAc,cAAc,MAAM,QAAW;AAC/C,oBAAc,cAAc,IAAI,CAAC;AAAA,IACnC;AACA,UAAM,kBAAkB,cAAc,cAAc;AACpD,QAAI,OAAO,gBAAgB,UAAU,MAAM,YAAY,gBAAgB,UAAU,MAAM,IAAI;AACzF,sBAAgB,UAAU,IAAI,KAAK,MAAM,YAAY,mBAAmB;AAAA,IAC1E;AAGA,UAAM,kBAAkB,cAAc,cAAc,KAAK,CAAC;AAC1D,kBAAc,cAAc,IAAI;AAChC,QAAI,OAAO,gBAAgB,UAAU,MAAM,YAAY,gBAAgB,UAAU,MAAM,IAAI;AACzF,sBAAgB,UAAU,IAAI,KAAK,MAAM,YAAY,mBAAmB;AAAA,IAC1E;AAAA,EACF;AAQA,QAAM,eAAe,YAAY,QAAQ;AAAA,IACvC,YAAY;AAAA,IACZ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAoB;AAEpB,QAAM,aAAa,MAAM,YAAY,YAAY;AAAA,IAC/C;AAAA,IACA;AAAA,IACA,YAAY,CAAC,WACX,UAAU,OAAO,MAAM;AAAA;AAAA;AAAA;AAAA,MAIrB,UAAU,eAAe,SAAS,MAAM;AAAA,MACxC;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,QAAQ;AAAA,MACR;AAAA,MACA,YAAY,MAAM;AAAA,MAClB;AAAA,MACA;AAAA,MACA;AAAA,MACA,UAAU,OAAO;AAAA,MACjB,KAAK,OAAO;AAAA,MACZ,eAAe;AAAA,QACb,gBAAgB,cAAc;AAAA,QAC9B,QAAQ,CAAC,MACP,cAAc,OAAO,CAA+C;AAAA,MACxE;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EACL,CAAC;AAED,MAAI;AAAA,IACF,yBAAyB,eAAe,MAAM,cAAc,YAAY,MAAM;AAAA,EAChF;AACA,SAAO;AACT;AAEA,SAAS,mBACP,QACA,SACyC;AACzC,QAAM,UAAmD,CAAC;AAC1D,aAAW,CAAC,MAAM,KAAK,KAAK,OAAO,QAAQ,OAAO,cAAc,CAAC,CAAC,GAAG;AACnE,YAAQ,IAAI,IAAI,EAAE,GAAG,MAAM;AAAA,EAC7B;AACA,aAAW,UAAU,SAAS;AAC5B,UAAM,WAAW,oBAAoB;AAAA,MACnC,MAAM,OAAO;AAAA,MACb,SAAS,OAAO;AAAA,MAChB;AAAA,IACF,CAAC;AACD,QAAI,SAAS,WAAY,SAAQ,OAAO,IAAI,IAAI,SAAS;AAAA,EAC3D;AACA,SAAO;AACT;",
6
- "names": []
7
- }
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../src/settings-menu.ts", "../src/services/suggestion-store.ts", "../src/tui-thinking-word.ts"],
4
- "sourcesContent": ["import * as fs from 'node:fs/promises';\nimport * as path from 'node:path';\nimport { atomicWrite, deepMerge } from '@wrongstack/core/utils';\nimport { ConfigError, ERROR_CODES, FsError, type SecretVault } from '@wrongstack/core/types';\nimport type { ConfigStore } from '@wrongstack/core/types';\nimport { decryptConfigSecrets, encryptConfigSecrets } from '@wrongstack/core/security';\n\n/** Configuration and storage dependencies needed to persist a setting.\n * This is safe to call from non-interactive surfaces such as the TUI,\n * headless runs, and the arg-driven `/settings` slash command. */\nexport interface PersistSettingDeps {\n configStore: ConfigStore;\n /** Path to the active profile config (~/.wrongstack/profiles/<name>/config.json). */\n profileConfigPath: string;\n /** Per-project config path (<project>/.wrongstack/config.json).\n * Used when configScope === 'project'. Lives inside the project\n * root so it can be gitignored or team-shared. */\n inProjectConfigPath?: string | undefined;\n vault: SecretVault;\n /** Force writes to the active profile config even when configScope is project. */\n forceGlobal?: boolean | undefined;\n /**\n * Optional resolver that recomputes the profile config path for a given\n * profile name. When set, {@link resolveActualTarget} uses this to\n * re-resolve the target path when `decrypted.activeProfile` differs from\n * the one used to compute `profileConfigPath`. This prevents the write\n * from landing in the wrong profile when the mutator changes activeProfile.\n */\n resolveProfilePath?: ((profileName: string) => string) | undefined;\n}\n\nexport function resolvePersistPath(deps: PersistSettingDeps): string {\n if (deps.forceGlobal) return deps.profileConfigPath;\n const scope = (deps.configStore.get() as { configScope?: string | undefined }).configScope;\n if (scope === 'project' && deps.inProjectConfigPath) {\n return deps.inProjectConfigPath;\n }\n // Default user scope always means the active profile, never the root bootstrap.\n return deps.profileConfigPath;\n}\n\n/**\n * Re-resolve the target path after a mutator may have changed configScope.\n * Handles the two settings targets: project config or active profile config.\n */\nexport function resolveActualTarget(\n deps: PersistSettingDeps,\n decrypted: Record<string, unknown>,\n fallbackPath: string,\n): string {\n if (deps.forceGlobal) return deps.profileConfigPath;\n const newScope = decrypted.configScope as string | undefined;\n // Recompute profile path when activeProfile changed in the mutation block.\n const newProfileName = decrypted.activeProfile as string | undefined;\n const effectiveProfilePath =\n newProfileName && deps.resolveProfilePath\n ? deps.resolveProfilePath(newProfileName)\n : deps.profileConfigPath;\n if (newScope === 'project' && deps.inProjectConfigPath) {\n return deps.inProjectConfigPath;\n }\n if (newScope === 'global') {\n return effectiveProfilePath;\n }\n return fallbackPath;\n}\n\n/**\n * Returns true when the target path is the trusted active-profile config, so\n * secrets are NOT stripped before writing.\n * Returns false for project-scoped paths where credentials must be filtered.\n */\nfunction isProfileOrGlobalTarget(actualTarget: string, deps: PersistSettingDeps): boolean {\n return actualTarget === deps.profileConfigPath;\n}\n\nasync function ensureProjectDir(filePath: string): Promise<void> {\n const dir = path.dirname(filePath);\n try {\n await fs.mkdir(dir, { recursive: true });\n } catch {\n // Directory already exists or is inaccessible \u2014 the write will surface the real error.\n }\n}\n\n/**\n * When configScope changed mid-save, the write target differs from the file\n * that was read. Read the *destination* file, decrypt it, and deep-merge the\n * mutated `source` on top (prefer-patch so the user's explicit changes win).\n * This prevents a sparse source (e.g. project config without credentials)\n * from clobbering credential-bearing fields in the destination (e.g. profile\n * config with apiKey / providers / mcpServers).\n *\n * Returns `source` unchanged when destination does not exist yet.\n */\nasync function mergeWithDestinationIfExists(\n destPath: string,\n source: Record<string, unknown>,\n vault: SecretVault,\n): Promise<Record<string, unknown>> {\n let destRaw: string;\n try {\n destRaw = await fs.readFile(destPath, 'utf8');\n } catch (err) {\n // Only swallow ENOENT (destination doesn't exist yet).\n // All other errors (EACCES, EIO, etc.) propagate as-is.\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') {\n return source;\n }\n throw err;\n }\n let destParsed: Record<string, unknown>;\n try {\n destParsed = JSON.parse(destRaw) as Record<string, unknown>;\n } catch (err) {\n // Corrupt destination \u2014 don't merge, just write our content.\n process.stderr.write(JSON.stringify({\n level: 'warn',\n event: 'settings_menu_merge_skipped',\n message: `Skipping merge into corrupt destination file: ${destPath}`,\n timestamp: new Date().toISOString(),\n }) + '\\n');\n void err;\n return source;\n }\n const destDecrypted = decryptConfigSecrets(destParsed, vault) as Record<string, unknown>;\n return deepMerge(destDecrypted, source) as Record<string, unknown>;\n}\n\n/**\n * Fields that are safe to persist in a per-project `.wrongstack/config.json`.\n * Credential-bearing fields (apiKey, providers, sync) MUST NOT appear here \u2014\n * they stay in the active profile config only. The active profile always gets the\n * full unfiltered object.\n *\n * When adding a field here, ask: \"Would I commit this to a shared repo?\"\n * If the answer is no, it doesn't belong on this list.\n */\nconst PROJECT_SAFE_FIELDS = new Set([\n 'provider',\n 'model',\n 'fallbackModels',\n // Kept in sync with core's IN_PROJECT_ALLOWED_KEYS (config-loader.ts): these\n // model-routing prefs are explicitly safe to READ from a per-project config,\n // so the writer must not strip them \u2014 otherwise a scope=project /fallback\n // profile/fav write (or a TUI full-config write under project scope) would be\n // silently dropped while the loader would happily accept them on next boot.\n 'fallbackProfiles',\n 'favoriteModels',\n 'favoriteModelsOnly',\n 'modelAvailabilitySchedule',\n 'fallbackAuto',\n 'models',\n 'modelMatrix',\n 'maxConcurrent',\n 'autonomy',\n 'hints',\n 'nextPrediction',\n 'debugStream',\n 'configScope',\n 'yolo',\n 'features',\n 'context',\n 'log',\n 'session',\n 'indexing',\n 'tools',\n 'launch',\n 'circuitBreaker',\n 'adaptiveConcurrency',\n 'modelRuntime',\n 'Sage',\n]);\n\n/**\n * Strip credential-bearing and machine-specific fields from a config object\n * so it is safe to write into a per-project `.wrongstack/config.json` file.\n * Returns a new object \u2014 the original is not mutated.\n */\nexport function filterSafeForProject(cfg: Record<string, unknown>): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(cfg)) {\n if (PROJECT_SAFE_FIELDS.has(key)) {\n out[key] = value;\n }\n }\n return out;\n}\n\n/**\n * Read the config file, apply `mutator` to its `autonomy` block, and write it\n * back atomically (then mirror into the in-memory config store). Pure I/O \u2014 no\n * terminal interaction \u2014 so it works identically under the plain REPL and the\n * Ink TUI.\n */\nexport async function persistAutonomySetting(\n deps: PersistSettingDeps,\n mutator: (autonomy: {\n autoProceedDelayMs?: number | undefined;\n defaultMode?: string | undefined;\n enhance?: boolean | undefined;\n enhanceDelayMs?: number | undefined;\n enhanceLanguage?: string | undefined;\n }) => void,\n): Promise<void> {\n const targetPath = resolvePersistPath(deps);\n await ensureProjectDir(targetPath);\n\n let raw: string;\n let fileExists = true;\n try {\n raw = await fs.readFile(targetPath, 'utf8');\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== 'ENOENT') {\n throw new FsError({\n message: `Could not read ${targetPath}`,\n code: ERROR_CODES.FS_READ_FAILED,\n path: targetPath,\n cause: err,\n });\n }\n fileExists = false;\n raw = '{}';\n }\n\n let parsed: Record<string, unknown>;\n try {\n parsed = JSON.parse(raw) as Record<string, unknown>;\n } catch (err) {\n if (fileExists) {\n throw new ConfigError({\n message: `Config at ${targetPath} is not valid JSON`,\n code: ERROR_CODES.CONFIG_PARSE_FAILED,\n context: { path: targetPath },\n cause: err,\n });\n }\n parsed = {};\n }\n\n const decrypted = decryptConfigSecrets(parsed, deps.vault) as Record<string, unknown>;\n const autonomy = (decrypted.autonomy as Record<string, unknown>) ?? {};\n mutator(\n autonomy as { autoProceedDelayMs?: number | undefined; defaultMode?: string | undefined },\n );\n decrypted.autonomy = autonomy;\n\n // Re-resolve path \u2014 the mutator might have changed configScope.\n const actualTarget = resolveActualTarget(deps, decrypted as Record<string, unknown>, targetPath);\n if (actualTarget !== targetPath) {\n await ensureProjectDir(actualTarget);\n }\n\n // When configScope changed mid-save, merge into the destination file's\n // existing content so credential-bearing fields are preserved.\n const effectiveConfig =\n actualTarget !== targetPath\n ? await mergeWithDestinationIfExists(actualTarget, decrypted, deps.vault)\n : decrypted;\n\n // When writing to the project-local config, strip credentials so\n // apiKey / providers / sync never leak into a per-project file.\n const toWrite =\n isProfileOrGlobalTarget(actualTarget, deps) ? effectiveConfig : filterSafeForProject(effectiveConfig);\n\n const encrypted = encryptConfigSecrets(toWrite, deps.vault);\n await atomicWrite(actualTarget, JSON.stringify(encrypted, null, 2), { mode: 0o600 });\n\n // Also update the in-memory config store so changes are immediately visible\n deps.configStore.update({\n autonomy: decrypted.autonomy as Parameters<typeof deps.configStore.update>[0]['autonomy'],\n });\n}\n\n/**\n * Read the config file, apply `mutator` to the full decrypted object, and\n * write it back atomically. This is used for small top-level settings that do\n * not warrant a dedicated helper.\n */\nexport async function persistConfigSetting(\n deps: PersistSettingDeps,\n mutator: (config: Record<string, unknown>) => void,\n): Promise<void> {\n const targetPath = resolvePersistPath(deps);\n await ensureProjectDir(targetPath);\n\n let raw: string;\n let fileExists = true;\n try {\n raw = await fs.readFile(targetPath, 'utf8');\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== 'ENOENT') {\n throw new FsError({\n message: `Could not read ${targetPath}`,\n code: ERROR_CODES.FS_READ_FAILED,\n path: targetPath,\n cause: err,\n });\n }\n fileExists = false;\n raw = '{}';\n }\n\n let parsed: Record<string, unknown>;\n try {\n parsed = JSON.parse(raw) as Record<string, unknown>;\n } catch (err) {\n if (fileExists) {\n throw new ConfigError({\n message: `Config at ${targetPath} is not valid JSON`,\n code: ERROR_CODES.CONFIG_PARSE_FAILED,\n context: { path: targetPath },\n cause: err,\n });\n }\n parsed = {};\n }\n\n const decrypted = decryptConfigSecrets(parsed, deps.vault) as Record<string, unknown>;\n mutator(decrypted);\n\n // If the mutator changed configScope, re-resolve the target path.\n // Without this, a scope change from 'project' \u2192 'global' would write\n // to the old project path instead of the new global one.\n const actualTarget = resolveActualTarget(deps, decrypted, targetPath);\n\n // Ensure the directory exists if we're writing to a new path\n if (actualTarget !== targetPath) {\n await ensureProjectDir(actualTarget);\n }\n\n // When configScope changed mid-save, merge into the destination file's\n // existing content so credential-bearing fields are preserved.\n const effectiveConfig =\n actualTarget !== targetPath\n ? await mergeWithDestinationIfExists(actualTarget, decrypted, deps.vault)\n : decrypted;\n\n // When writing to the project-local config, strip credentials so\n // apiKey / providers / sync never leak into a per-project file.\n const toWrite =\n isProfileOrGlobalTarget(actualTarget, deps) ? effectiveConfig : filterSafeForProject(effectiveConfig);\n\n const encrypted = encryptConfigSecrets(toWrite, deps.vault);\n await atomicWrite(actualTarget, JSON.stringify(encrypted, null, 2), { mode: 0o600 });\n deps.configStore.update(decrypted as Parameters<typeof deps.configStore.update>[0]);\n}\n\n/**\n * Persist Telegram plugin config to `extensions.telegram` in the active profile config\n * file. Mirrors `persistAutonomySetting` \u2014 reads the config, applies the\n * mutator, encrypts secrets, writes atomically, then updates ConfigStore.\n */\nexport async function persistTelegramConfig(\n deps: PersistSettingDeps,\n mutator: (telegram: Record<string, unknown>) => void,\n): Promise<void> {\n const targetPath = deps.profileConfigPath;\n let raw: string;\n let fileExists = true;\n try {\n raw = await fs.readFile(targetPath, 'utf8');\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== 'ENOENT') {\n throw new FsError({\n message: `Could not read ${targetPath}: ${(err as Error).message}`,\n code: 'FS_READ_FAILED',\n path: targetPath,\n context: { operation: 'readGlobalConfig', phase: 'read' },\n cause: err,\n });\n }\n fileExists = false;\n raw = '{}';\n }\n\n let parsed: Record<string, unknown>;\n try {\n parsed = JSON.parse(raw) as Record<string, unknown>;\n } catch (err) {\n if (fileExists) {\n throw new ConfigError({\n message: `Config at ${targetPath} is not valid JSON: ${(err as Error).message}`,\n code: 'CONFIG_PARSE_FAILED',\n context: { filePath: targetPath, operation: 'readProfileConfig' },\n cause: err,\n });\n }\n parsed = {};\n }\n\n const decrypted = decryptConfigSecrets(parsed, deps.vault) as Record<string, unknown>;\n const extensions = (decrypted.extensions as Record<string, Record<string, unknown>>) ?? {};\n const telegram = extensions.telegram ?? {};\n mutator(telegram);\n extensions.telegram = telegram;\n decrypted.extensions = extensions;\n\n const encrypted = encryptConfigSecrets(decrypted, deps.vault);\n await atomicWrite(targetPath, JSON.stringify(encrypted, null, 2), { mode: 0o600 });\n\n // Also update the in-memory config store so changes are immediately visible\n deps.configStore.update({\n extensions: extensions as NonNullable<\n Parameters<typeof deps.configStore.update>[0]['extensions']\n >,\n });\n}\n\n/**\n * Derive the filesystem-access pair (`features.allowOutsideProjectRoot` and\n * `tools.restrictToProjectRoot`) from a save input. The two are inverses\n * of each other and the codebase intentionally keeps both in sync for\n * backward compatibility with older readers that only know about\n * `tools.restrictToProjectRoot`.\n *\n * Single source of truth: `allowOutsideProjectRoot`. If the caller only\n * sets `restrictFsToRoot`, it is converted to its inverse. If both are\n * set (which the picker should not do, but defensive code paths may),\n * `allowOutsideProjectRoot` wins. Returns `undefined` if neither is set,\n * so the caller can skip the write.\n *\n * The input is duck-typed (only the two relevant fields are read) so any\n * caller can pass a partial shape: the TUI's full `LiveSettingsInput`,\n * a slash-command's `{ restrictFsToRoot: boolean }`, or any future\n * save-handler that only carries these two knobs.\n */\nexport function deriveFsAccessPair(input: {\n allowOutsideProjectRoot?: boolean | undefined;\n restrictFsToRoot?: boolean | undefined;\n}): { allowOutsideProjectRoot: boolean; restrictToProjectRoot: boolean } | undefined {\n if (input.allowOutsideProjectRoot !== undefined) {\n const allow = input.allowOutsideProjectRoot;\n return { allowOutsideProjectRoot: allow, restrictToProjectRoot: !allow };\n }\n if (input.restrictFsToRoot !== undefined) {\n const restrict = input.restrictFsToRoot;\n return { allowOutsideProjectRoot: !restrict, restrictToProjectRoot: restrict };\n }\n return undefined;\n}\n", "/**\n * Shared suggestion state used by command and runtime surfaces.\n *\n * Previously, suggestions were stored in a closure variable (`currentSuggestions`)\n * inside cli-main.ts, accessed via the `onSuggestions` callback on\n * `SlashCommandContext`. This closure-based approach works correctly in the REPL\n * but can fail in the TUI when the callback goes through multiple indirections\n * (SlashCommandRegistry \u2192 SlashCommand.run \u2192 closed-over opts).\n *\n * By using a module-level store, both /suggest (writer) and /next (reader)\n * access the same array directly, bypassing the callback indirection entirely.\n * The CLI's `onSuggestions` callback now reads/writes this shared store to\n * maintain backward compatibility with the REPL autonomy loop's\n * `getSuggestions`/`onSuggestionsParsed` flow.\n *\n * Auto suggestions (items with auto=\"true\" attribute) are stored separately\n * for YOLO+auto autonomy mode.\n */\n\nlet sharedSuggestions: string[] = [];\nlet sharedAutoSuggestions: string[] = [];\n\n/** Store suggestions (called by /suggest and onSuggestionsParsed). */\nexport function setSuggestions(suggestions: string[]): void {\n sharedSuggestions = suggestions;\n}\n\n/** Store auto suggestions (items with auto=\"true\" attribute). Called by onAutoSuggestionsParsed. */\nexport function setAutoSuggestions(suggestions: string[]): void {\n sharedAutoSuggestions = suggestions;\n}\n\n/** Retrieve current suggestions (called by /next selection and getSuggestions). */\nexport function getSuggestions(): string[] {\n return sharedSuggestions;\n}\n\n/** Retrieve current auto suggestions (called by YOLO+auto autonomy). */\nexport function getAutoSuggestions(): string[] {\n return sharedAutoSuggestions;\n}\n\n/**\n * Remove suggestions at the given 0-based indices.\n * Called by /next selection so selected prompts cannot be re-selected.\n * Also clears auto-suggestions since an explicit user selection should\n * override any auto-submit queue.\n */\nexport function removeSuggestions(indices: number[]): void {\n if (indices.length === 0) return;\n const set = new Set(indices);\n sharedSuggestions = sharedSuggestions.filter((_, i) => !set.has(i));\n sharedAutoSuggestions = [];\n}\n\n/** Clear suggestions (called by /next clear). */\nexport function clearSuggestions(): void {\n sharedSuggestions = [];\n sharedAutoSuggestions = [];\n}\n", "export const DEFAULT_TUI_THINKING_WORD = 'thinking';\nexport const MAX_TUI_THINKING_WORD_LENGTH = 16;\n\n/** Normalize autonomy.thinkingWord to a single short word for the TUI statusline. */\nexport function normalizeTuiThinkingWord(value: unknown): string {\n if (typeof value !== 'string') return DEFAULT_TUI_THINKING_WORD;\n const word = value.trim();\n if (word.length === 0 || word.length > MAX_TUI_THINKING_WORD_LENGTH) {\n return DEFAULT_TUI_THINKING_WORD;\n }\n if (!/^[\\p{L}\\p{N}_-]+$/u.test(word)) return DEFAULT_TUI_THINKING_WORD;\n return word;\n}\n"],
5
- "mappings": "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",
6
- "names": []
7
- }
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../src/auth-menu/openai-codex-oauth.ts", "../src/auth-menu/loopback-server.ts"],
4
- "sourcesContent": ["/**\n * OpenAI Codex \u2014 \"Sign in with ChatGPT\" OAuth (Authorization Code + PKCE).\n *\n * This is the ONLY login flow that actually works for using a ChatGPT\n * Plus/Pro/Team subscription programmatically. It mirrors the real Codex CLI:\n *\n * 1. Generate a PKCE verifier/challenge (S256) + CSRF state.\n * 2. Start a loopback HTTP server on http://localhost:1455/auth/callback.\n * 3. Open the browser to https://auth.openai.com/oauth/authorize.\n * 4. The browser redirects back to the loopback with `?code=...&state=...`.\n * 5. Exchange the code at https://auth.openai.com/oauth/token for an\n * access_token + refresh_token (and an id_token carrying the account id).\n * 6. Store the tokens in the vault as an `oauth` ProviderApiKey.\n *\n * The resulting access token is a JWT whose `https://api.openai.com/auth`\n * claim contains `chatgpt_account_id`. Requests then go to the ChatGPT\n * backend (`chatgpt.com/backend-api/codex/responses`) \u2014 NOT api.openai.com \u2014\n * which is what the `openai-codex` wire family handles. The standard\n * API-key flow (`family: openai` \u2192 api.openai.com/chat/completions) is left\n * completely untouched.\n *\n * NOTE: a ChatGPT web *session cookie* is NOT an API token. The old\n * device-code + cookie-scraping attempts could never produce a usable\n * credential; this module replaces them.\n */\n\nimport { createHash, randomBytes } from 'node:crypto';\nimport {\n type LoopbackServer,\n openBrowser,\n startLoopbackServer as startSharedLoopbackServer,\n} from './loopback-server.js';\nimport { CODEX_MODELS } from '@wrongstack/core/models';\nimport { color } from '@wrongstack/core/utils';\nimport { FetchError, ParseError, type ModelsRegistry, type ProviderApiKey, type ProviderConfig } from '@wrongstack/core/types';\nimport {\n mutateConfigProviders,\n normalizeKeys,\n nowIso,\n writeKeysBack,\n} from '../provider-config-utils.js';\nimport type { AuthMenuDeps } from './types.js';\n\n// \u2500\u2500 Codex OAuth constants (verified against the real Codex CLI) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst CLIENT_ID = 'app_EMoamEEZ73f0CkXaXp7hrann';\nconst AUTH_BASE_URL = 'https://auth.openai.com';\nconst AUTHORIZE_URL = `${AUTH_BASE_URL}/oauth/authorize`;\nconst TOKEN_URL = `${AUTH_BASE_URL}/oauth/token`;\nconst REDIRECT_PORT = 1455;\nconst FALLBACK_REDIRECT_PORT = 1457;\nconst REDIRECT_HOST = '127.0.0.1';\nconst REDIRECT_PATH = '/auth/callback';\nconst SCOPE = 'openid profile email offline_access api.connectors.read api.connectors.invoke';\n/** JWT claim that carries the ChatGPT account id. */\nconst JWT_CLAIM_PATH = 'https://api.openai.com/auth';\n/** Telemetry/branding tag sent to authorize + as a request header. Free-form. */\nconst ORIGINATOR = 'wrongstack';\n/** Canonical provider id under which ChatGPT-login credentials are stored. */\nexport const CODEX_PROVIDER_ID = 'openai-codex';\n/** Default ChatGPT backend base. The wire family appends `/codex/responses`. */\nexport const CODEX_BASE_URL = 'https://chatgpt.com/backend-api';\n\n/**\n * Codex-specific compatibility wrapper retained for callers that imported the\n * helper from this module before the shared OAuth loopback server extraction.\n */\nexport function startLoopbackServer(\n expectedState: string,\n signal?: AbortSignal,\n): Promise<LoopbackServer> {\n return startSharedLoopbackServer(\n expectedState,\n [REDIRECT_PORT, FALLBACK_REDIRECT_PORT],\n signal,\n REDIRECT_PATH,\n REDIRECT_HOST,\n );\n}\n\n/**\n * Fallback model list used when BOTH the live backend AND the models.dev\n * catalog are unreachable. This should never happen in practice \u2014 the\n * catalog is cached locally and the only scenario where both fail is a\n * fresh install with no network on first login.\n *\n * The primary resolution order in `resolveCodexModels()` is:\n * 1. Live backend (`GET /models`)\n * 2. models.dev catalog (`family: gpt-codex*` under `openai` provider)\n * 3. This inline fallback\n */\n// \u2500\u2500 Token shapes \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport interface CodexTokens {\n access: string;\n refresh: string;\n /** Absolute expiry in epoch milliseconds. */\n expires: number;\n idToken?: string | undefined;\n}\n\ninterface TokenEndpointResponse {\n access_token?: string;\n refresh_token?: string;\n expires_in?: number;\n id_token?: string;\n}\n\n// \u2500\u2500 PKCE \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction base64url(buf: Buffer): string {\n return buf.toString('base64').replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');\n}\n\nexport interface Pkce {\n verifier: string;\n challenge: string;\n}\n\n/** Generate a PKCE verifier + S256 challenge. */\nexport function generatePkce(): Pkce {\n const verifier = base64url(randomBytes(64));\n const challenge = base64url(createHash('sha256').update(verifier).digest());\n return { verifier, challenge };\n}\n\nfunction createState(): string {\n return base64url(randomBytes(32));\n}\n\nfunction redirectUri(port: number): string {\n return `http://localhost:${port}${REDIRECT_PATH}`;\n}\n\n/** Build the full authorize URL with all Codex-required query params. */\nexport function buildAuthorizeUrl(challenge: string, state: string, port = REDIRECT_PORT): string {\n const url = new URL(AUTHORIZE_URL);\n url.searchParams.set('response_type', 'code');\n url.searchParams.set('client_id', CLIENT_ID);\n url.searchParams.set('redirect_uri', redirectUri(port));\n url.searchParams.set('scope', SCOPE);\n url.searchParams.set('code_challenge', challenge);\n url.searchParams.set('code_challenge_method', 'S256');\n url.searchParams.set('state', state);\n url.searchParams.set('id_token_add_organizations', 'true');\n url.searchParams.set('codex_cli_simplified_flow', 'true');\n url.searchParams.set('originator', ORIGINATOR);\n return url.toString();\n}\n\n// \u2500\u2500 JWT account-id extraction \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\ninterface JwtAuthClaim {\n chatgpt_account_id?: string;\n}\n\nfunction decodeJwtPayload(token: string): Record<string, unknown> | null {\n try {\n const parts = token.split('.');\n if (parts.length !== 3) return null;\n const json = Buffer.from(parts[1]!, 'base64url').toString('utf8');\n return JSON.parse(json) as Record<string, unknown>;\n } catch {\n return null;\n }\n}\n\n/**\n * Extract `chatgpt_account_id` from an access/id token JWT. Returns null when\n * the token isn't a JWT or lacks the claim (the caller decides whether that\n * is fatal \u2014 for login it is; for a cached display value it isn't).\n */\nexport function extractAccountId(token: string): string | null {\n const payload = decodeJwtPayload(token);\n const auth = payload?.[JWT_CLAIM_PATH] as JwtAuthClaim | undefined;\n const id = auth?.chatgpt_account_id;\n return typeof id === 'string' && id.length > 0 ? id : null;\n}\n\n// \u2500\u2500 Model discovery \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Recommended Codex models for ChatGPT sign-in. Live backend discovery\n * wins; these values are only used when the backend/catalog cannot\n * provide a list. Derived from `CODEX_MODELS` in core (the single source of\n * truth for Codex id/name/description) \u2014 used both by OAuth login\n * (resolveCodexModels \u2192 tier 3) and by the provider-boot synthesis\n * path (wiring/provider.ts when the catalog is unavailable).\n */\nexport const FALLBACK_CODEX_MODELS: ReadonlyArray<{ id: string; name: string }> = CODEX_MODELS.map(\n (m) => ({ id: m.id, name: m.name }),\n);\n\n/** Families in the models.dev catalog that indicate Codex / Responses API compatibility. */\nexport const CODEX_CATALOG_FAMILIES = new Set(['gpt-codex', 'gpt-codex-spark']);\n\nexport function fallbackCodexModelIds(): string[] {\n return FALLBACK_CODEX_MODELS.map((m) => m.id);\n}\n\nexport function fallbackCodexProviderModels(): Array<{ id: string; name: string }> {\n return FALLBACK_CODEX_MODELS.map((m) => ({ id: m.id, name: m.name }));\n}\n\n/**\n * Filter a list of available model ids down to the ones that are still\n * current for ChatGPT sign-in (i.e. present in FALLBACK_CODEX_MODELS).\n * Used by both OAuth login (to drop deprecated ids from the live /models\n * response) and by the provider-boot synthesis (to pick current ids out\n * of the models.dev catalog).\n */\nexport function filterCurrentCodexModelIds(ids: Iterable<string>): string[] {\n const available = new Set(ids);\n return FALLBACK_CODEX_MODELS.map((m) => m.id).filter((id) => available.has(id));\n}\n\nexport function isCodexCatalogModel(model: { family?: string | undefined }): boolean {\n return typeof model.family === 'string' && CODEX_CATALOG_FAMILIES.has(model.family);\n}\n\n/**\n * Resolve the list of available Codex models using a 3-tier fallback chain:\n *\n * 1. **Live backend** \u2014 `fetchCodexModels()` hits `GET <baseUrl>/models`\n * and keeps only current Codex model ids.\n * 2. **models.dev catalog** \u2014 queries the `openai` provider in the cached\n * models.dev registry and picks models whose `family` is `gpt-codex` or\n * `gpt-codex-spark` and whose ids are current for ChatGPT sign-in.\n * 3. **Inline fallback** \u2014 a minimal list of documented mainstream models\n * (`gpt-5.6-sol`, `gpt-5.5`, `gpt-5.6-terra`, `gpt-5.6-luna`,\n * `gpt-5.4`, `gpt-5.4-mini`, `gpt-5.3-codex-spark`).\n *\n * The live/catalog tiers gate account availability, while the shared current\n * model list filters out deprecated ChatGPT sign-in ids before config is saved.\n */\nexport async function resolveCodexModels(\n modelsRegistry: ModelsRegistry,\n accessToken: Promise<string> | string,\n baseUrl?: string | undefined,\n signal?: AbortSignal,\n): Promise<string[]> {\n // Tier 1 \u2014 live backend\n const token = typeof accessToken === 'string' ? accessToken : await accessToken;\n const live = filterCurrentCodexModelIds(await fetchCodexModels(token, baseUrl, signal));\n if (live.length > 0) return live;\n\n // Tier 2 \u2014 models.dev catalog\n try {\n const openaiProvider = await modelsRegistry.getProvider('openai');\n if (openaiProvider) {\n const catalog = openaiProvider.models\n .filter(isCodexCatalogModel)\n .map((m) => m.id)\n .filter((id): id is string => typeof id === 'string' && id.length > 0);\n const currentCatalog = filterCurrentCodexModelIds(catalog);\n if (currentCatalog.length > 0) return currentCatalog;\n }\n } catch {\n // catalog unavailable \u2014 fall through to tier 3\n }\n\n // Tier 3 \u2014 inline fallback\n return fallbackCodexModelIds();\n}\n\n/**\n * Fetch the account's available Codex model ids live from the ChatGPT backend.\n *\n * Hits `GET <baseUrl>/models` \u2014 the standard OpenAI-compatible model listing\n * endpoint, which the Codex backend may expose. Best-effort: returns an empty\n * array on any failure so login still succeeds with the hardcoded fallback.\n */\nexport async function fetchCodexModels(\n accessToken: string,\n baseUrl?: string | undefined,\n signal?: AbortSignal,\n): Promise<string[]> {\n const url = `${(baseUrl ?? CODEX_BASE_URL).replace(/\\/+$/, '')}/models`;\n try {\n const res = await fetch(url, {\n headers: {\n accept: 'application/json',\n authorization: `Bearer ${accessToken}`,\n originator: 'wrongstack',\n 'OpenAI-Beta': 'responses=experimental',\n },\n signal: signal\n ? AbortSignal.any([signal, AbortSignal.timeout(8_000)])\n : AbortSignal.timeout(8_000),\n });\n if (!res.ok) return [];\n const json = (await res.json()) as\n | { data?: Array<{ id?: string }> }\n | { models?: Array<{ id?: string }> }\n | null;\n if (!json) return [];\n // Standard OpenAI-compatible: { data: [{ id: \"gpt-...\", ... }] }\n const rawList: unknown[] =\n 'data' in json && Array.isArray(json.data)\n ? (json.data as unknown[])\n : 'models' in json && Array.isArray(json.models)\n ? (json.models as unknown[])\n : [];\n const ids: string[] = [];\n for (const entry of rawList) {\n if (!entry || typeof entry !== 'object') continue;\n const id = (entry as Record<string, unknown>).id;\n if (typeof id === 'string' && id.length > 0) ids.push(id);\n }\n return ids;\n } catch {\n return [];\n }\n}\n\n// \u2500\u2500 Token endpoint calls \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nasync function readTokens(res: Response, op: string, url: string): Promise<CodexTokens> {\n if (!res.ok) {\n const text = await res.text().catch(() => '');\n throw new FetchError({\n message: `Codex token ${op} failed (${res.status}): ${text || res.statusText}`,\n status: res.status,\n context: { provider: 'openai-codex', op, url },\n });\n }\n const json = (await res.json()) as TokenEndpointResponse | null;\n if (!json?.access_token || !json.refresh_token || typeof json.expires_in !== 'number') {\n throw new ParseError({\n message: `Codex token ${op} response missing fields`,\n source: 'openai-codex-token-response',\n context: { op },\n });\n }\n return {\n access: json.access_token,\n refresh: json.refresh_token,\n expires: Date.now() + json.expires_in * 1000,\n idToken: json.id_token,\n };\n}\n\n/** Exchange an authorization code (+ PKCE verifier) for tokens. */\nexport async function exchangeAuthorizationCode(\n code: string,\n verifier: string,\n signal?: AbortSignal,\n port = REDIRECT_PORT,\n): Promise<CodexTokens> {\n const res = await fetch(TOKEN_URL, {\n method: 'POST',\n headers: { 'content-type': 'application/x-www-form-urlencoded' },\n body: new URLSearchParams({\n grant_type: 'authorization_code',\n client_id: CLIENT_ID,\n code,\n code_verifier: verifier,\n redirect_uri: redirectUri(port),\n }).toString(),\n signal: signal\n ? AbortSignal.any([signal, AbortSignal.timeout(30_000)])\n : AbortSignal.timeout(30_000),\n });\n return readTokens(res, 'exchange', TOKEN_URL);\n}\n\n/**\n * Refresh an expired access token using the stored refresh token. Exported for\n * the `openai-codex` provider, which calls it transparently before requests.\n */\nexport async function refreshCodexToken(\n refreshToken: string,\n signal?: AbortSignal,\n): Promise<CodexTokens> {\n const res = await fetch(TOKEN_URL, {\n method: 'POST',\n headers: { 'content-type': 'application/x-www-form-urlencoded' },\n body: new URLSearchParams({\n grant_type: 'refresh_token',\n client_id: CLIENT_ID,\n refresh_token: refreshToken,\n }).toString(),\n signal: signal\n ? AbortSignal.any([signal, AbortSignal.timeout(30_000)])\n : AbortSignal.timeout(30_000),\n });\n return readTokens(res, 'refresh', TOKEN_URL);\n}\n\n// \u2500\u2500 Loopback callback server \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// Loopback server, browser opener, and callback HTML are now in\n// `./loopback-server.js` \u2014 imported at the top of this file.\n\n// \u2500\u2500 Manual paste fallback \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/** Parse a pasted authorization code or full redirect URL into { code, state }. */\nexport function parseAuthorizationInput(input: string): {\n code?: string | undefined;\n state?: string | undefined;\n} {\n const value = input.trim();\n if (!value) return {};\n try {\n const url = new URL(value);\n return {\n code: url.searchParams.get('code') ?? undefined,\n state: url.searchParams.get('state') ?? undefined,\n };\n } catch {\n /* not a URL */\n }\n if (value.includes('code=')) {\n const params = new URLSearchParams(value);\n return {\n code: params.get('code') ?? undefined,\n state: params.get('state') ?? undefined,\n };\n }\n return { code: value };\n}\n\n// \u2500\u2500 Main login flow \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport interface CodexLoginOptions {\n /** Storage provider id. Defaults to the canonical `openai-codex`. */\n providerId?: string;\n /**\n * External cancellation signal (e.g. the TUI auth panel's Esc). When\n * provided, the flow does NOT install its own SIGINT handler \u2014 the\n * caller owns cancellation, and a raw `process.exit` from inside a\n * mounted Ink tree would corrupt the terminal.\n */\n signal?: AbortSignal | undefined;\n}\n\n/**\n * Run the \"Sign in with ChatGPT\" flow and persist the resulting OAuth tokens.\n * Returns 0 on success, 1 on cancel/error.\n */\nexport async function runCodexOAuthLogin(\n deps: AuthMenuDeps,\n opts: CodexLoginOptions = {},\n): Promise<number> {\n const providerId = opts.providerId ?? CODEX_PROVIDER_ID;\n const pkce = generatePkce();\n const state = createState();\n\n const ac = new AbortController();\n // First Ctrl+C cancels cleanly (aborts the controller, which unblocks the\n // loopback wait below); a second Ctrl+C is a hard escape hatch in case\n // anything downstream is still wedged. Skipped entirely when the caller\n // supplied an external signal (TUI) \u2014 the caller owns cancellation then.\n let sigintCount = 0;\n const onSig = () => {\n sigintCount += 1;\n if (sigintCount === 1) {\n deps.renderer.write(color.dim('\\n Cancelling sign-in\u2026 (press Ctrl+C again to force-quit)\\n'));\n ac.abort();\n } else {\n process.exit(130);\n }\n };\n const external = opts.signal;\n if (external) {\n if (external.aborted) ac.abort();\n else external.addEventListener('abort', () => ac.abort(), { once: true });\n } else {\n process.on('SIGINT', onSig);\n }\n\n const server = await startLoopbackServer(state, ac.signal);\n const authorizeUrl = buildAuthorizeUrl(pkce.challenge, state, server.port);\n // Wrap URL in OSC 8 terminal hyperlink sequences \u2014 makes it a clickable\n // link in modern terminals (Windows Terminal, iTerm2, Kitty, WezTerm, etc.).\n // The hyperlink is region-based: clicking any wrapped segment opens the URL.\n const osc8 = (url: string) => `\\x1b]8;;${url}\\x1b\\\\${url}\\x1b]8;;\\x1b\\\\`;\n\n deps.renderer.write(\n color.bold(`\\n Sign in with ChatGPT \u2014 ${color.cyan(providerId)}\\n`) +\n color.dim(' Uses your ChatGPT Plus/Pro/Team subscription (not an API key).\\n') +\n color.amber(' \u26A0 Using a subscription outside the official Codex client may violate\\n') +\n color.amber(' OpenAI\u2019s Terms \u2014 your account could be rate-limited or banned.\\n') +\n color.dim(' Sanctioned programmatic use = an API key: ') +\n color.bold('wstack auth openai') +\n color.dim('\\n\\n') +\n color.bold(` ${'\u2500'.repeat(56)}\\n`) +\n color.bold(' Open this URL in your browser to sign in:\\n') +\n color.cyan(` ${osc8(authorizeUrl)}\\n`) +\n color.bold(` ${'\u2500'.repeat(56)}\\n\\n`),\n );\n\n if (server.bound) {\n openBrowser(authorizeUrl);\n deps.renderer.write(\n color.dim(' A browser window should open. Waiting for you to finish signing in...\\n') +\n color.dim(` (Listening on ${redirectUri(server.port)} \u2014 press Ctrl+C to cancel.)\\n`),\n );\n } else {\n deps.renderer.write(\n color.amber(' \u26A0 Could not start the local callback listener (ports 1455 and 1457 in use).\\n') +\n color.dim(' After signing in, copy the full redirect URL from your browser\\n') +\n color.dim(' (it starts with http://localhost:1455/auth/callback or :1457) and paste it below.\\n'),\n );\n }\n\n let code: string | undefined;\n try {\n if (server.bound) {\n const callback = await server.waitForCode();\n // Ctrl+C while waiting on the loopback \u2192 cancel now rather than dropping\n // into the manual-paste prompt (which would block again).\n if (ac.signal.aborted) {\n deps.renderer.write(color.dim(' Cancelled.\\n'));\n return 1;\n }\n if (callback) code = callback.code;\n }\n\n // Manual paste fallback (port busy, or browser couldn't redirect back).\n if (!code) {\n const input = (\n await deps.reader.readLine(\n `\\n ${color.amber('?')} Paste the redirect URL or code ${color.dim('(or q to cancel)')}: `,\n )\n ).trim();\n if (input.toLowerCase() === 'q' || input === '') {\n deps.renderer.write(color.dim(' Cancelled.\\n'));\n return 1;\n }\n const parsed = parseAuthorizationInput(input);\n if (parsed.state && parsed.state !== state) {\n deps.renderer.writeError(' State mismatch \u2014 please restart the login flow.');\n return 1;\n }\n code = parsed.code;\n }\n\n if (!code) {\n deps.renderer.writeError(' No authorization code received.');\n return 1;\n }\n\n deps.renderer.write(color.dim('\\n Exchanging authorization code for tokens...\\n'));\n const tokens = await exchangeAuthorizationCode(code, pkce.verifier, ac.signal, server.port);\n const accountId = extractAccountId(tokens.access) ?? (tokens.idToken ? extractAccountId(tokens.idToken) : null);\n if (!accountId) {\n deps.renderer.writeError(\n ' Signed in, but the token has no ChatGPT account id.\\n' +\n ' This account may not have Codex/ChatGPT subscription access.',\n );\n return 1;\n }\n\n // Fetch available models from the Codex backend (best-effort).\n deps.renderer.write(color.dim(' Fetching available models...\\n'));\n const models = await resolveCodexModels(\n deps.modelsRegistry,\n tokens.access,\n CODEX_BASE_URL,\n ac.signal,\n );\n\n const saved = await saveCodexTokens(deps, providerId, tokens, accountId, models);\n if (!saved) return 1;\n\n const modelHint = models[0] ?? 'gpt-5.5';\n deps.renderer.write(color.green('\\n \u2713 Signed in with ChatGPT!\\n'));\n deps.renderer.writeInfo(\n ` Saved as provider ${color.bold(providerId)}${models.length > 0 ? ` (${models.length} models)` : ''}.\\n` +\n ` Use: ${color.bold(`wstack --provider ${providerId} --model ${modelHint}`)} \"<task>\"\\n` +\n color.dim(' Tokens refresh automatically before they expire.\\n'),\n );\n return 0;\n } catch (err) {\n const msg =\n err instanceof DOMException && err.name === 'AbortError'\n ? 'Login cancelled.'\n : (err as Error).message;\n deps.renderer.writeError(` Login failed: ${msg}`);\n return 1;\n } finally {\n server.close();\n if (!external) process.off('SIGINT', onSig);\n }\n}\n\n// \u2500\u2500 Persistence \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nasync function saveCodexTokens(\n deps: AuthMenuDeps,\n providerId: string,\n tokens: CodexTokens,\n accountId: string,\n models: string[],\n): Promise<boolean> {\n const entry: ProviderApiKey = {\n label: 'oauth-default',\n apiKey: tokens.access,\n createdAt: nowIso(),\n authMethod: 'oauth',\n expiresAt: new Date(tokens.expires).toISOString(),\n refreshToken: tokens.refresh,\n tokenType: 'bearer',\n scope: SCOPE,\n accountId,\n };\n\n try {\n await mutateConfigProviders(deps.profileConfigPath, deps.vault, (all) => {\n const existing = all[providerId];\n const p: ProviderConfig = existing ? { ...existing } : { type: providerId };\n p.family = 'openai-codex';\n if (!p.baseUrl) p.baseUrl = CODEX_BASE_URL;\n // The caller populates `models` from the live backend or the fallback\n // constant. Always overwrite \u2014 the backend is authoritative.\n p.models = [...models];\n\n const keys = normalizeKeys(p).filter((k) => k.label !== entry.label);\n keys.push(entry);\n writeKeysBack(p, keys);\n p.activeKey = entry.label;\n all[providerId] = p;\n }, deps.profileConfigPath);\n return true;\n } catch (err) {\n deps.renderer.writeError(` Failed to save tokens: ${(err as Error).message}`);\n return false;\n }\n}\n", "/**\n * Shared loopback OAuth server \u2014 HTTP callback listener for OAuth flows.\n *\n * Both the Codex (\"Sign in with ChatGPT\") and Anthropic (\"Sign in with Claude\")\n * OAuth flows start a local HTTP server on a loopback port to receive the\n * authorization code redirect from the provider's browser flow. Before this\n * module existed, each flow duplicated:\n *\n * - `escapeHtml` / `callbackHtml` \u2014 render success/error HTML pages\n * - `openBrowser` \u2014 best-effort platform browser opener\n * - `startLoopbackServer` \u2014 HTTP server with port fallback, abort handling,\n * state validation, and callback URL parsing\n *\n * Usage:\n * ```ts\n * import { startLoopbackServer, callbackHtml, openBrowser } from './loopback-server.js';\n *\n * const server = await startLoopbackServer(state, [1455, 1457], signal);\n * const code = await server.waitForCode();\n * ```\n *\n * @module auth-menu/loopback-server\n */\n\nimport { spawn } from 'node:child_process';\nimport { createServer, type Server } from 'node:http';\n\n// \u2500\u2500 HTML rendering \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst ESCAPE_HTML: Record<string, string> = {\n '&': '&amp;',\n '<': '&lt;',\n '>': '&gt;',\n '\"': '&quot;',\n \"'\": '&#39;',\n};\n\nfunction escapeHtml(s: string): string {\n return s.replace(/[&<>\"']/g, (ch) => ESCAPE_HTML[ch] ?? ch);\n}\n\n/**\n * Render a simple dark-themed HTML response for the browser callback page.\n * Shared by all loopback-based OAuth flows so the success/error page styling\n * is consistent and the HTML generation isn't duplicated.\n */\nexport function callbackHtml(ok: boolean, message: string): string {\n const heading = ok ? 'Authentication successful' : 'Authentication failed';\n return (\n `<!doctype html><html lang=\"en\"><head><meta charset=\"utf-8\"/>` +\n `<title>${escapeHtml(heading)}</title><style>body{margin:0;min-height:100vh;display:flex;` +\n `align-items:center;justify-content:center;background:#09090b;color:#fafafa;` +\n `font-family:ui-sans-serif,system-ui,sans-serif;text-align:center}` +\n `h1{font-size:26px;margin:0 0 8px}p{color:#a1a1aa}</style></head><body><main>` +\n `<h1>${escapeHtml(heading)}</h1><p>${escapeHtml(message)}</p></main></body></html>`\n );\n}\n\n// \u2500\u2500 Platform browser opener (best-effort) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Open a URL in the user's default browser. Best-effort \u2014 never throws.\n *\n * - Windows: `cmd /c start \"\" <url>`\n * - macOS: `open <url>`\n * - Linux: `xdg-open <url>`\n */\nexport function openBrowser(url: string): void {\n try {\n const platform = process.platform;\n const { command, args } =\n platform === 'win32'\n ? { command: 'cmd', args: ['/c', 'start', '', url] }\n : platform === 'darwin'\n ? { command: 'open', args: [url] }\n : { command: 'xdg-open', args: [url] };\n const child = spawn(command, args, { stdio: 'ignore', windowsHide: true });\n child.on('error', () => {});\n child.unref();\n } catch {\n // Best-effort \u2014 the URL is always printed in the terminal.\n }\n}\n\n// \u2500\u2500 Loopback callback server \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/** Result of waiting for the OAuth callback. */\nexport interface LoopbackCallback {\n /** The authorization code from the provider. */\n code: string;\n /** The OAuth state parameter from the callback (validated against expected). */\n state: string;\n}\n\nexport interface LoopbackServer {\n /**\n * Wait for the browser to redirect back with the authorization code.\n * Resolves with the code and state, or `null` if cancelled/failed.\n */\n waitForCode(): Promise<LoopbackCallback | null>;\n /** Close the HTTP server and release the port. */\n close(): void;\n /** True when the server bound to a registered callback port. */\n readonly bound: boolean;\n /** The actual port the server is listening on (matches the authorize URL redirect_uri). */\n readonly port: number;\n}\n\n/**\n * Start a loopback HTTP server for OAuth authorization code callback.\n *\n * The server listens on the first available port from `ports`. If all ports\n * are busy, `bound` is `false` and the caller should fall back to a manual\n * paste prompt.\n *\n * When `signal` aborts (user pressed Ctrl+C or TUI Esc), the pending\n * `waitForCode()` promise resolves to `null` and the server is torn down\n * so the login flow can return promptly.\n *\n * @param expectedState - The OAuth `state` value to validate against the callback\n * @param ports - Ordered list of TCP ports to try (first available wins)\n * @param signal - Optional external cancellation signal\n * @param callbackPath - The URL path the provider redirects to (default `/auth/callback`)\n * @param redirectHost - The hostname to listen on (default `127.0.0.1`)\n */\nexport function startLoopbackServer(\n expectedState: string,\n ports: readonly number[],\n signal?: AbortSignal,\n callbackPath = '/auth/callback',\n redirectHost = '127.0.0.1',\n): Promise<LoopbackServer> {\n let resolveCode: (v: LoopbackCallback | null) => void = () => {};\n const codePromise = new Promise<LoopbackCallback | null>((resolve) => {\n let settled = false;\n resolveCode = (v) => {\n if (settled) return;\n settled = true;\n resolve(v);\n };\n });\n\n const server: Server = createServer((req, res) => {\n let url: URL;\n try {\n url = new URL(req.url ?? '', `http://${redirectHost}`);\n } catch {\n res.statusCode = 400;\n res.end();\n return;\n }\n if (url.pathname !== callbackPath) {\n res.statusCode = 404;\n res.setHeader('content-type', 'text/html; charset=utf-8');\n res.end(callbackHtml(false, 'Callback route not found.'));\n return;\n }\n res.setHeader('content-type', 'text/html; charset=utf-8');\n const err = url.searchParams.get('error');\n if (err) {\n res.statusCode = 400;\n res.end(callbackHtml(false, `Authorization error: ${err}`));\n resolveCode(null);\n return;\n }\n const state = url.searchParams.get('state') ?? '';\n if (state !== expectedState) {\n res.statusCode = 400;\n res.end(callbackHtml(false, 'State mismatch \u2014 please restart the login.'));\n resolveCode(null);\n return;\n }\n const code = url.searchParams.get('code');\n if (!code) {\n res.statusCode = 400;\n res.end(callbackHtml(false, 'Missing authorization code.'));\n return;\n }\n res.statusCode = 200;\n res.end(callbackHtml(true, 'You can close this window and return to the terminal.'));\n resolveCode({ code, state });\n });\n\n // Abort (Ctrl+C / TUI Esc) \u2192 unblock the pending wait and close the server.\n const onAbort = (): void => {\n resolveCode(null);\n try {\n server.close();\n } catch {\n /* ignore */\n }\n };\n if (signal) {\n if (signal.aborted) onAbort();\n else signal.addEventListener('abort', onAbort, { once: true });\n }\n\n return new Promise<LoopbackServer>((resolve) => {\n const close = (): void => {\n resolveCode(null);\n try {\n server.close();\n } catch {\n /* ignore */\n }\n };\n\n const fail = (): void => {\n resolveCode(null);\n resolve({\n bound: false,\n port: ports[0] ?? 1455,\n waitForCode: () => Promise.resolve(null),\n close,\n });\n };\n\n let index = 0;\n server.on('error', () => {\n index += 1;\n const nextPort = ports[index];\n if (nextPort !== undefined) {\n server.listen(nextPort, redirectHost);\n return;\n }\n fail();\n });\n\n server.on('listening', () => {\n const address = server.address();\n const port = typeof address === 'object' && address ? address.port : ports[index] ?? 1455;\n resolve({\n bound: true,\n port,\n waitForCode: () => codePromise,\n close,\n });\n });\n\n server.listen(ports[0] ?? 1455, redirectHost);\n });\n}\n"],
5
- "mappings": "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",
6
- "names": ["startLoopbackServer", "startLoopbackServer"]
7
- }
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../src/boot/auto-discover-providers.ts"],
4
- "sourcesContent": ["import * as fs from 'node:fs/promises';\nimport * as path from 'node:path';\nimport type { Config, Logger, ModelsDevProvider, ProviderConfig } from '@wrongstack/core/types';\nimport type { DefaultModelsRegistry } from '@wrongstack/core/models';\nimport { COMPATIBLE_PRESETS, discoverOpenAICompatibleModels } from '@wrongstack/providers';\n\ninterface DiscoverCacheEntry {\n fetchedAt: string;\n provider: ModelsDevProvider;\n}\ntype DiscoverCache = Record<string, DiscoverCacheEntry>;\n\n/** Active API key from a ProviderConfig (mirrors the provider factory's resolver). */\nfunction resolveKey(cfg: ProviderConfig): string | undefined {\n if (Array.isArray(cfg.apiKeys) && cfg.apiKeys.length > 0) {\n const active = cfg.activeKey\n ? cfg.apiKeys.find((k) => k.label === cfg.activeKey)\n : undefined;\n return (active ?? cfg.apiKeys[0])?.apiKey;\n }\n return cfg.apiKey && cfg.apiKey.length > 0 ? cfg.apiKey : undefined;\n}\n\n/** Providers eligible for `/v1/models` auto-discovery and their resolved base URL. */\nfunction eligibleProviders(\n config: Config,\n): Array<{ id: string; cfg: ProviderConfig; baseUrl: string; apiKey?: string | undefined }> {\n const out: Array<{\n id: string;\n cfg: ProviderConfig;\n baseUrl: string;\n apiKey?: string | undefined;\n }> = [];\n for (const [id, cfg] of Object.entries(config.providers ?? {})) {\n const preset = COMPATIBLE_PRESETS[id];\n const enabled = cfg.autoDiscoverModels ?? preset?.autoDiscover ?? false;\n if (!enabled) continue;\n const baseUrl = cfg.baseUrl ?? preset?.defaultBaseUrl;\n if (!baseUrl) continue;\n out.push({ id, cfg, baseUrl, apiKey: resolveKey(cfg) });\n }\n return out;\n}\n\nasync function readCache(file: string): Promise<DiscoverCache> {\n try {\n return JSON.parse(await fs.readFile(file, 'utf8')) as DiscoverCache;\n } catch {\n return {};\n }\n}\n\n/**\n * Fetch model lists for every auto-discovery provider in the config and merge\n * them into the catalog. Best-effort end to end: a down server or missing key\n * falls back to the last cached list (any age \u2014 a local proxy being offline at\n * boot shouldn't wipe its models), and any failure is a logged no-op so boot\n * never breaks.\n *\n * Caches the most recent successful fetch per provider+baseUrl so the models\n * survive a restart while the server is briefly unavailable.\n */\nexport async function discoverAndMergeProviders(opts: {\n config: Config;\n registry: DefaultModelsRegistry;\n cacheDir: string;\n logger?: Logger | undefined;\n fetchImpl?: typeof fetch | undefined;\n}): Promise<void> {\n const targets = eligibleProviders(opts.config);\n if (targets.length === 0) return;\n\n const cacheFile = path.join(opts.cacheDir, 'discovered-models-cache.json');\n const cache = await readCache(cacheFile);\n let cacheDirty = false;\n\n await Promise.all(\n targets.map(async ({ id, cfg, baseUrl, apiKey }) => {\n const cacheKey = `${id}\u0000${baseUrl}`;\n const provider = await discoverOpenAICompatibleModels(id, {\n baseUrl,\n apiKey,\n headers: cfg.headers,\n providerName: id,\n fetchImpl: opts.fetchImpl,\n });\n if (provider) {\n cache[cacheKey] = { fetchedAt: new Date().toISOString(), provider };\n cacheDirty = true;\n opts.registry.mergeOverlay({ [id]: provider });\n opts.logger?.info(\n `auto-discovered ${Object.keys(provider.models).length} models for \"${id}\" from ${baseUrl}`,\n );\n return;\n }\n // Fetch failed \u2014 fall back to the last cached list, if any.\n const cached = cache[cacheKey];\n if (cached) {\n opts.registry.mergeOverlay({ [id]: cached.provider });\n opts.logger?.warn(\n `auto-discovery for \"${id}\" failed; using ${\n Object.keys(cached.provider.models).length\n } cached models from ${cached.fetchedAt}`,\n );\n } else {\n opts.logger?.warn(\n `auto-discovery for \"${id}\" failed and no cache available (server at ${baseUrl} unreachable?)`,\n );\n }\n }),\n );\n\n if (cacheDirty) {\n try {\n await fs.writeFile(cacheFile, JSON.stringify(cache), 'utf8');\n } catch {\n // best-effort cache write\n }\n }\n}\n"],
5
- "mappings": ";;;AAAA,YAAY,QAAQ;AACpB,YAAY,UAAU;AAGtB,SAAS,oBAAoB,sCAAsC;AASnE,SAAS,WAAW,KAAyC;AAC3D,MAAI,MAAM,QAAQ,IAAI,OAAO,KAAK,IAAI,QAAQ,SAAS,GAAG;AACxD,UAAM,SAAS,IAAI,YACf,IAAI,QAAQ,KAAK,CAAC,MAAM,EAAE,UAAU,IAAI,SAAS,IACjD;AACJ,YAAQ,UAAU,IAAI,QAAQ,CAAC,IAAI;AAAA,EACrC;AACA,SAAO,IAAI,UAAU,IAAI,OAAO,SAAS,IAAI,IAAI,SAAS;AAC5D;AAGA,SAAS,kBACP,QAC0F;AAC1F,QAAM,MAKD,CAAC;AACN,aAAW,CAAC,IAAI,GAAG,KAAK,OAAO,QAAQ,OAAO,aAAa,CAAC,CAAC,GAAG;AAC9D,UAAM,SAAS,mBAAmB,EAAE;AACpC,UAAM,UAAU,IAAI,sBAAsB,QAAQ,gBAAgB;AAClE,QAAI,CAAC,QAAS;AACd,UAAM,UAAU,IAAI,WAAW,QAAQ;AACvC,QAAI,CAAC,QAAS;AACd,QAAI,KAAK,EAAE,IAAI,KAAK,SAAS,QAAQ,WAAW,GAAG,EAAE,CAAC;AAAA,EACxD;AACA,SAAO;AACT;AAEA,eAAe,UAAU,MAAsC;AAC7D,MAAI;AACF,WAAO,KAAK,MAAM,MAAS,YAAS,MAAM,MAAM,CAAC;AAAA,EACnD,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACF;AAYA,eAAsB,0BAA0B,MAM9B;AAChB,QAAM,UAAU,kBAAkB,KAAK,MAAM;AAC7C,MAAI,QAAQ,WAAW,EAAG;AAE1B,QAAM,YAAiB,UAAK,KAAK,UAAU,8BAA8B;AACzE,QAAM,QAAQ,MAAM,UAAU,SAAS;AACvC,MAAI,aAAa;AAEjB,QAAM,QAAQ;AAAA,IACZ,QAAQ,IAAI,OAAO,EAAE,IAAI,KAAK,SAAS,OAAO,MAAM;AAClD,YAAM,WAAW,GAAG,EAAE,KAAI,OAAO;AACjC,YAAM,WAAW,MAAM,+BAA+B,IAAI;AAAA,QACxD;AAAA,QACA;AAAA,QACA,SAAS,IAAI;AAAA,QACb,cAAc;AAAA,QACd,WAAW,KAAK;AAAA,MAClB,CAAC;AACD,UAAI,UAAU;AACZ,cAAM,QAAQ,IAAI,EAAE,YAAW,oBAAI,KAAK,GAAE,YAAY,GAAG,SAAS;AAClE,qBAAa;AACb,aAAK,SAAS,aAAa,EAAE,CAAC,EAAE,GAAG,SAAS,CAAC;AAC7C,aAAK,QAAQ;AAAA,UACX,mBAAmB,OAAO,KAAK,SAAS,MAAM,EAAE,MAAM,gBAAgB,EAAE,UAAU,OAAO;AAAA,QAC3F;AACA;AAAA,MACF;AAEA,YAAM,SAAS,MAAM,QAAQ;AAC7B,UAAI,QAAQ;AACV,aAAK,SAAS,aAAa,EAAE,CAAC,EAAE,GAAG,OAAO,SAAS,CAAC;AACpD,aAAK,QAAQ;AAAA,UACX,uBAAuB,EAAE,mBACvB,OAAO,KAAK,OAAO,SAAS,MAAM,EAAE,MACtC,uBAAuB,OAAO,SAAS;AAAA,QACzC;AAAA,MACF,OAAO;AACL,aAAK,QAAQ;AAAA,UACX,uBAAuB,EAAE,8CAA8C,OAAO;AAAA,QAChF;AAAA,MACF;AAAA,IACF,CAAC;AAAA,EACH;AAEA,MAAI,YAAY;AACd,QAAI;AACF,YAAS,aAAU,WAAW,KAAK,UAAU,KAAK,GAAG,MAAM;AAAA,IAC7D,QAAQ;AAAA,IAER;AAAA,EACF;AACF;",
6
- "names": []
7
- }
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../src/utils.ts"],
4
- "sourcesContent": ["/**\n * Format a token count for human-readable display.\n * 999 \u2192 \"999\"\n * 1_200 \u2192 \"1.2k\"\n * 12_000 \u2192 \"12k\"\n * 1_500_000 \u2192 \"1.5M\"\n */\nexport function fmtTok(n: number): string {\n if (n < 1000) return String(n);\n if (n < 1_000_000) return `${(n / 1000).toFixed(n < 10_000 ? 1 : 0)}k`;\n return `${(n / 1_000_000).toFixed(1)}M`;\n}\n\n/**\n * Return a new frozen config object with the given patch applied.\n * Config objects are frozen by the config loader \u2014 direct mutation\n * silently fails at runtime. This helper spreads + re-freezes safely.\n */\nexport function patchConfig<T extends object>(base: T, patch: Partial<T>): T {\n return Object.freeze({ ...base, ...patch }) as T;\n}\n\n/**\n * Human-readable duration: 999 \u2192 \"999ms\", 12_500 \u2192 \"12.5s\",\n * 160_016 \u2192 \"2m40s\", 7_200_000 \u2192 \"2h0m\". Used by `/agents` and\n * `/fleet status` so users don't have to read \"160016ms\" and do the\n * conversion in their head.\n */\nexport function fmtDuration(ms: number): string {\n if (ms < 1000) return `${ms}ms`;\n const s = ms / 1000;\n if (s < 60) return `${s.toFixed(s < 10 ? 1 : 0)}s`;\n const m = Math.floor(s / 60);\n const remSec = Math.round(s - m * 60);\n if (m < 60) return remSec === 0 ? `${m}m` : `${m}m${remSec}s`;\n const h = Math.floor(m / 60);\n const remMin = m - h * 60;\n return `${h}h${remMin}m`;\n}\n\n/**\n * Render a completed TaskResult into a single line for `/agents` and\n * `/fleet status`. Distinguishes all four terminal statuses with\n * separate icons + colors so users can tell a timeout from a real\n * failure at a glance, and surfaces both the failure kind (e.g.\n * `provider_rate_limit`, `tool_failed`) and a truncated error tail\n * (previously dropped on the floor).\n *\n * Accepts either the structured `SubagentError` envelope (current) or\n * a legacy string error \u2014 the field shape is widened to keep older\n * callers compiling while migrations roll through.\n *\n * The caller passes the color helper (it lives in @wrongstack/core)\n * to avoid a circular import from this utility module.\n */\nexport function fmtTaskResultLine(\n r: {\n status: 'success' | 'failed' | 'timeout' | 'stopped';\n error?:\n | string\n | {\n kind?: string | undefined;\n message?: string | undefined;\n retryable?: boolean | undefined;\n backoffMs?: number | undefined;\n }\n | undefined;\n iterations: number;\n toolCalls: number;\n durationMs: number;\n },\n color: {\n green(s: string): string;\n red(s: string): string;\n yellow(s: string): string;\n dim(s: string): string;\n },\n): { mark: string; stats: string; tail: string } {\n const stats = `${r.iterations}it ${r.toolCalls}tc ${fmtDuration(r.durationMs)}`;\n // Error tails are unbounded provider strings \u2014 collapse whitespace and\n // truncate so a 2KB stack trace can't blow up the chat line. Lift the\n // structured `kind` chip in front of the tail so the user reads\n // `\u2717 failed [provider_rate_limit] \u2014 server overloaded` instead of\n // raw verbose body.\n const errMsg = typeof r.error === 'string' ? r.error : r.error?.message;\n const errKind = typeof r.error === 'object' ? r.error?.kind : undefined;\n const errTail = errMsg\n ? ` \u2014 ${errMsg.replace(/\\s+/g, ' ').slice(0, 80)}${errMsg.length > 80 ? '\u2026' : ''}`\n : '';\n const errKindChip = errKind ? color.dim(` [${errKind}]`) : '';\n const errSnip = errMsg || errKind ? `${errKindChip}${color.dim(errTail)}` : '';\n switch (r.status) {\n case 'success':\n return { mark: color.green('\u2713'), stats, tail: '' };\n case 'timeout':\n return {\n mark: color.yellow('\u23F1'),\n stats: `${color.yellow('timeout')} ${stats}`,\n tail: errSnip,\n };\n case 'stopped':\n return { mark: color.dim('\u2298'), stats: `${color.dim('stopped')} ${stats}`, tail: errSnip };\n case 'failed':\n return { mark: color.red('\u2717'), stats: `${color.red('failed')} ${stats}`, tail: errSnip };\n }\n}\n"],
5
- "mappings": ";;;AAOO,SAAS,OAAO,GAAmB;AACxC,MAAI,IAAI,IAAM,QAAO,OAAO,CAAC;AAC7B,MAAI,IAAI,IAAW,QAAO,IAAI,IAAI,KAAM,QAAQ,IAAI,MAAS,IAAI,CAAC,CAAC;AACnE,SAAO,IAAI,IAAI,KAAW,QAAQ,CAAC,CAAC;AACtC;AAOO,SAAS,YAA8B,MAAS,OAAsB;AAC3E,SAAO,OAAO,OAAO,EAAE,GAAG,MAAM,GAAG,MAAM,CAAC;AAC5C;AAQO,SAAS,YAAY,IAAoB;AAC9C,MAAI,KAAK,IAAM,QAAO,GAAG,EAAE;AAC3B,QAAM,IAAI,KAAK;AACf,MAAI,IAAI,GAAI,QAAO,GAAG,EAAE,QAAQ,IAAI,KAAK,IAAI,CAAC,CAAC;AAC/C,QAAM,IAAI,KAAK,MAAM,IAAI,EAAE;AAC3B,QAAM,SAAS,KAAK,MAAM,IAAI,IAAI,EAAE;AACpC,MAAI,IAAI,GAAI,QAAO,WAAW,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,MAAM;AAC1D,QAAM,IAAI,KAAK,MAAM,IAAI,EAAE;AAC3B,QAAM,SAAS,IAAI,IAAI;AACvB,SAAO,GAAG,CAAC,IAAI,MAAM;AACvB;AAiBO,SAAS,kBACd,GAeA,OAM+C;AAC/C,QAAM,QAAQ,GAAG,EAAE,UAAU,MAAM,EAAE,SAAS,MAAM,YAAY,EAAE,UAAU,CAAC;AAM7E,QAAM,SAAS,OAAO,EAAE,UAAU,WAAW,EAAE,QAAQ,EAAE,OAAO;AAChE,QAAM,UAAU,OAAO,EAAE,UAAU,WAAW,EAAE,OAAO,OAAO;AAC9D,QAAM,UAAU,SACZ,WAAM,OAAO,QAAQ,QAAQ,GAAG,EAAE,MAAM,GAAG,EAAE,CAAC,GAAG,OAAO,SAAS,KAAK,WAAM,EAAE,KAC9E;AACJ,QAAM,cAAc,UAAU,MAAM,IAAI,KAAK,OAAO,GAAG,IAAI;AAC3D,QAAM,UAAU,UAAU,UAAU,GAAG,WAAW,GAAG,MAAM,IAAI,OAAO,CAAC,KAAK;AAC5E,UAAQ,EAAE,QAAQ;AAAA,IAChB,KAAK;AACH,aAAO,EAAE,MAAM,MAAM,MAAM,QAAG,GAAG,OAAO,MAAM,GAAG;AAAA,IACnD,KAAK;AACH,aAAO;AAAA,QACL,MAAM,MAAM,OAAO,QAAG;AAAA,QACtB,OAAO,GAAG,MAAM,OAAO,SAAS,CAAC,IAAI,KAAK;AAAA,QAC1C,MAAM;AAAA,MACR;AAAA,IACF,KAAK;AACH,aAAO,EAAE,MAAM,MAAM,IAAI,QAAG,GAAG,OAAO,GAAG,MAAM,IAAI,SAAS,CAAC,IAAI,KAAK,IAAI,MAAM,QAAQ;AAAA,IAC1F,KAAK;AACH,aAAO,EAAE,MAAM,MAAM,IAAI,QAAG,GAAG,OAAO,GAAG,MAAM,IAAI,QAAQ,CAAC,IAAI,KAAK,IAAI,MAAM,QAAQ;AAAA,EAC3F;AACF;",
6
- "names": []
7
- }
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../src/webui-server/kanban-run-mirror.ts"],
4
- "sourcesContent": ["export {\n buildTaskGraphFromGoalPhase,\n buildTaskGraphFromSddSnapshot,\n createKanbanRunMirror,\n type KanbanRunMirror,\n type KanbanRunMirrorDeps,\n} from '@wrongstack/webui-server';\n"],
5
- "mappings": ";;;AAAA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAGK;",
6
- "names": []
7
- }
@@ -1,29 +0,0 @@
1
-
2
- // src/utils/win32-cmd.ts
3
- var WIN32_CMD_META = /[&|<>"\r\n\0]/;
4
- function buildWin32CmdShimInvocation(command, args = []) {
5
- assertSafeWin32CmdArgs([command, ...args]);
6
- const line = ["call", quoteWin32CmdArg(command), ...args.map(quoteWin32CmdArg)].join(" ");
7
- return {
8
- command: process.env["COMSPEC"] ?? "cmd.exe",
9
- args: ["/d", "/c", line],
10
- windowsVerbatimArguments: true
11
- };
12
- }
13
- function assertSafeWin32CmdArgs(args) {
14
- for (const arg of args) {
15
- if (typeof arg === "string" && WIN32_CMD_META.test(arg)) {
16
- throw new Error(
17
- 'win32 cmd shim spawn: argument contains a shell metacharacter (one of & | < > ", or a newline) that could enable command injection through the .cmd/.bat wrapper - refusing to run. Offending argument: ' + JSON.stringify(arg)
18
- );
19
- }
20
- }
21
- }
22
- function quoteWin32CmdArg(arg) {
23
- return `"${arg}"`;
24
- }
25
-
26
- export {
27
- buildWin32CmdShimInvocation
28
- };
29
- //# sourceMappingURL=chunk-VJUEZZYB.js.map
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../src/utils/win32-cmd.ts"],
4
- "sourcesContent": ["const WIN32_CMD_META = /[&|<>\"\\r\\n\\0]/;\n\nexport interface Win32CmdShimInvocation {\n command: string;\n args: string[];\n windowsVerbatimArguments: true;\n}\n\nexport function buildWin32CmdShimInvocation(\n command: string,\n args: readonly string[] = [],\n): Win32CmdShimInvocation {\n assertSafeWin32CmdArgs([command, ...args]);\n const line = ['call', quoteWin32CmdArg(command), ...args.map(quoteWin32CmdArg)].join(' ');\n return {\n command: process.env['COMSPEC'] ?? 'cmd.exe',\n args: ['/d', '/c', line],\n windowsVerbatimArguments: true,\n };\n}\n\nfunction assertSafeWin32CmdArgs(args: readonly unknown[]): void {\n for (const arg of args) {\n if (typeof arg === 'string' && WIN32_CMD_META.test(arg)) {\n throw new Error(\n 'win32 cmd shim spawn: argument contains a shell metacharacter ' +\n '(one of & | < > \", or a newline) that could enable command injection ' +\n 'through the .cmd/.bat wrapper - refusing to run. Offending argument: ' +\n JSON.stringify(arg),\n );\n }\n }\n}\n\nfunction quoteWin32CmdArg(arg: string): string {\n return `\"${arg}\"`;\n}\n"],
5
- "mappings": ";;;AAAA,IAAM,iBAAiB;AAQhB,SAAS,4BACd,SACA,OAA0B,CAAC,GACH;AACxB,yBAAuB,CAAC,SAAS,GAAG,IAAI,CAAC;AACzC,QAAM,OAAO,CAAC,QAAQ,iBAAiB,OAAO,GAAG,GAAG,KAAK,IAAI,gBAAgB,CAAC,EAAE,KAAK,GAAG;AACxF,SAAO;AAAA,IACL,SAAS,QAAQ,IAAI,SAAS,KAAK;AAAA,IACnC,MAAM,CAAC,MAAM,MAAM,IAAI;AAAA,IACvB,0BAA0B;AAAA,EAC5B;AACF;AAEA,SAAS,uBAAuB,MAAgC;AAC9D,aAAW,OAAO,MAAM;AACtB,QAAI,OAAO,QAAQ,YAAY,eAAe,KAAK,GAAG,GAAG;AACvD,YAAM,IAAI;AAAA,QACR,6MAGE,KAAK,UAAU,GAAG;AAAA,MACtB;AAAA,IACF;AAAA,EACF;AACF;AAEA,SAAS,iBAAiB,KAAqB;AAC7C,SAAO,IAAI,GAAG;AAChB;",
6
- "names": []
7
- }
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": [],
4
- "sourcesContent": [],
5
- "mappings": "",
6
- "names": []
7
- }
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../src/version.ts"],
4
- "sourcesContent": ["import { createRequire } from 'node:module';\n\nconst req = createRequire(import.meta.url);\n\nfunction readOwnVersion(): string {\n const candidates = ['../package.json', '../../package.json'];\n for (const rel of candidates) {\n try {\n const pkg = req(rel) as { version?: unknown | undefined };\n if (typeof pkg.version === 'string' && pkg.version.length > 0) return pkg.version;\n } catch {\n // try next\n }\n }\n return 'dev';\n}\n\nexport const CLI_VERSION = readOwnVersion();\n\nlet API_VERSION = '0.0.0';\ntry {\n const corePkg = req('@wrongstack/core/package.json') as {\n wrongstackApiVersion?: string | undefined;\n };\n if (corePkg.wrongstackApiVersion) API_VERSION = corePkg.wrongstackApiVersion;\n} catch {\n /* fallback */\n}\n\nexport { API_VERSION };\n"],
5
- "mappings": ";;;AAAA,SAAS,qBAAqB;AAE9B,IAAM,MAAM,cAAc,YAAY,GAAG;AAEzC,SAAS,iBAAyB;AAChC,QAAM,aAAa,CAAC,mBAAmB,oBAAoB;AAC3D,aAAW,OAAO,YAAY;AAC5B,QAAI;AACF,YAAM,MAAM,IAAI,GAAG;AACnB,UAAI,OAAO,IAAI,YAAY,YAAY,IAAI,QAAQ,SAAS,EAAG,QAAO,IAAI;AAAA,IAC5E,QAAQ;AAAA,IAER;AAAA,EACF;AACA,SAAO;AACT;AAEO,IAAM,cAAc,eAAe;AAE1C,IAAI,cAAc;AAClB,IAAI;AACF,QAAM,UAAU,IAAI,+BAA+B;AAGnD,MAAI,QAAQ,qBAAsB,eAAc,QAAQ;AAC1D,QAAQ;AAER;",
6
- "names": []
7
- }
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../src/services/sdd/state.ts", "../src/services/sdd/project-context.ts", "../src/services/sdd/spec-detection.ts", "../src/services/sdd/task-manager.ts"],
4
- "sourcesContent": ["import type { Context } from '@wrongstack/core/agent';\nimport {\n type AISpecBuilder,\n type AISpecPhase,\n SpecVersioning,\n type TaskStore,\n type TaskTracker,\n} from '@wrongstack/sdd';\n\n/** Key used to store SDD session state in ctx.meta for session isolation. */\nexport const SDD_META_KEY = 'sdd.state';\n\n/** Single shared SDD session state for the process lifetime. */\nexport class SDDState {\n private builder: AISpecBuilder | null = null;\n private taskStore: TaskStore | null = null;\n private taskTracker: TaskTracker | null = null;\n private taskGraphId: string | null = null;\n private sessionStartTime: number = Date.now();\n private phaseStartTime: number = Date.now();\n private versioning: SpecVersioning | null = null;\n\n getBuilder(): AISpecBuilder | null {\n return this.builder;\n }\n setBuilder(b: AISpecBuilder | null) {\n this.builder = b;\n }\n getTaskStore(): TaskStore | null {\n return this.taskStore;\n }\n setTaskStore(s: TaskStore | null) {\n this.taskStore = s;\n }\n getTaskTracker(): TaskTracker | null {\n return this.taskTracker;\n }\n setTaskTracker(t: TaskTracker | null) {\n this.taskTracker = t;\n }\n getTaskGraphId(): string | null {\n return this.taskGraphId;\n }\n setTaskGraphId(id: string | null) {\n this.taskGraphId = id;\n }\n getSessionStartTime(): number {\n return this.sessionStartTime;\n }\n setSessionStartTime(t: number) {\n this.sessionStartTime = t;\n }\n setPhaseStartTime(t: number) {\n this.phaseStartTime = t;\n }\n getPhaseStartTime(): number {\n return this.phaseStartTime;\n }\n getSessionElapsed(): number {\n return Date.now() - this.sessionStartTime;\n }\n getPhaseElapsed(): number {\n return Date.now() - this.phaseStartTime;\n }\n getVersioning(): SpecVersioning {\n if (this.versioning === null) this.versioning = new SpecVersioning();\n return this.versioning;\n }\n\n clearTaskState(): void {\n this.taskStore = null;\n this.taskTracker = null;\n this.taskGraphId = null;\n }\n\n getContext(): string | null {\n if (!this.builder) return null;\n const session = this.builder.getSession();\n if (session.phase === 'done') return null;\n return this.builder.getAIPrompt();\n }\n\n getPhase(): AISpecPhase | null {\n return this.builder?.getPhase() ?? null;\n }\n}\n\n/** Process-lifetime singleton \u2014 used when no Context is available (CLI single-session mode). */\nexport const sddState = new SDDState();\n\n/**\n * Get or create the SDD state for the current session.\n * Uses ctx.meta so each concurrent browser/REPL session has isolated state.\n */\nexport function getSessionState(ctx: Context | undefined): SDDState {\n if (!ctx) return sddState;\n let state = ctx.meta[SDD_META_KEY] as SDDState | undefined;\n if (!state) {\n state = new SDDState();\n ctx.meta[SDD_META_KEY] = state;\n }\n return state;\n}\n", "import type { AISpecBuilder, AISpecPhase, SpecIndexEntry, SpecStore } from '@wrongstack/sdd';\n// Shared with the WebUI wizard \u2014 single implementation in @wrongstack/sdd.\nexport { gatherProjectContext } from '@wrongstack/sdd';\nimport { sddState } from './state.js';\n\nexport function getActiveBuilder(): AISpecBuilder | null {\n return sddState.getBuilder();\n}\n\nexport function getActiveSDDContext(): string | null {\n return sddState.getContext();\n}\n\nexport function getActiveSDDPhase(): AISpecPhase | null {\n return sddState.getPhase();\n}\n\nexport async function findSpec(store: SpecStore, idOrTitle: string) {\n if (!idOrTitle) return null;\n const byId = await store.load(idOrTitle);\n if (byId) return byId;\n const all = await store.list();\n const match = all.find(\n (e: SpecIndexEntry) =>\n e.id.startsWith(idOrTitle) || e.title.toLowerCase().includes(idOrTitle.toLowerCase()),\n );\n if (match) return store.load(match.id);\n return null;\n}\n", "import { sddState } from './state.js';\n\n/**\n * Returns true if the text looks like conversational/explanatory output\n * rather than a structured implementation plan.\n */\nexport function isExplanatoryText(text: string): boolean {\n const lower = text.toLowerCase();\n return (\n lower.startsWith(\"i'\") ||\n lower.startsWith('i will') ||\n lower.startsWith('let me') ||\n lower.startsWith(\"here's my\") ||\n lower.startsWith('here is my') ||\n lower.startsWith(\"i'm going to\") ||\n lower.startsWith('first, let me') ||\n lower.startsWith('sure') ||\n lower.startsWith('of course') ||\n lower.startsWith('okay') ||\n lower.startsWith('ok,') ||\n lower.startsWith('sounds good') ||\n lower.startsWith('no problem') ||\n (text.split('\\n').length < 3 && !text.includes('.'))\n );\n}\n\n/**\n * Parse a spec from AI output text and save it to the active session.\n * Returns true if a spec was found and saved.\n */\nexport async function trySaveSpecFromAIOutput(aiOutput: string): Promise<boolean> {\n const builder = sddState.getBuilder();\n if (!builder) return false;\n const spec = builder.tryParseSpecFromOutput(aiOutput);\n if (!spec) return false;\n builder.setSpec(spec);\n return true;\n}\n\n/**\n * Try to save implementation plan from AI output during implementation phase.\n * Returns true if a plan was saved.\n */\nexport function trySaveImplementationPlan(aiOutput: string): boolean {\n const builder = sddState.getBuilder();\n if (!builder) return false;\n const session = builder.getSession();\n if (session.phase !== 'implementation') return false;\n\n const current = session.implementation ?? '';\n\n const jsonMatch = aiOutput.match(/```json\\s*\\[/);\n if (jsonMatch?.index && jsonMatch.index > 0) {\n const plan = aiOutput.substring(0, jsonMatch.index).trim();\n if (plan.length > 50 && plan !== current && !isExplanatoryText(plan)) {\n builder.setImplementation(plan);\n return true;\n }\n }\n\n if (\n aiOutput.length > 100 &&\n !aiOutput.includes('```json') &&\n aiOutput !== current &&\n !isExplanatoryText(aiOutput)\n ) {\n builder.setImplementation(aiOutput.trim());\n return true;\n }\n\n return false;\n}\n\n/**\n * Auto-detect task completion patterns in AI output and mark tasks.\n * Returns the number of tasks marked as completed.\n */\nexport function autoDetectTaskCompletion(aiOutput: string): number {\n const tracker = sddState.getTaskTracker();\n if (!tracker) return 0;\n const pending = tracker.getAllNodes({ status: ['pending', 'in_progress'] });\n if (pending.length === 0) return 0;\n\n let completed = 0;\n const lines = aiOutput.split('\\n');\n\n for (const line of lines) {\n const trimmed = line.trim();\n\n const sddDoneMatch = trimmed.match(/\\/sdd\\s+done\\s+(.+)/i);\n if (sddDoneMatch?.[1]) {\n const target = sddDoneMatch[1].trim();\n const num = Number(target);\n if (!Number.isNaN(num) && num >= 1 && num <= pending.length) {\n const node = pending[num - 1];\n if (node && node.status !== 'completed') {\n tracker.updateNodeStatus(node.id, 'completed');\n completed++;\n }\n } else {\n const match = pending.find(\n (n) =>\n n.title.toLowerCase().includes(target.toLowerCase()) ||\n target.toLowerCase().includes(n.title.toLowerCase()),\n );\n if (match && match.status !== 'completed') {\n tracker.updateNodeStatus(match.id, 'completed');\n completed++;\n }\n }\n continue;\n }\n\n const checkmarkMatch = trimmed.match(/^\u2705\\s*(?:Task:\\s*)?(.+)/i);\n if (checkmarkMatch?.[1]) {\n const title = checkmarkMatch[1].trim();\n const match = pending.find(\n (n) =>\n n.title.toLowerCase().includes(title.toLowerCase()) ||\n title.toLowerCase().includes(n.title.toLowerCase()),\n );\n if (match && match.status !== 'completed') {\n tracker.updateNodeStatus(match.id, 'completed');\n completed++;\n }\n continue;\n }\n\n const taskNumMatch = trimmed.match(/Task\\s+(\\d+)\\s*[:]\\s*(?:complete|done|finished)/i);\n if (taskNumMatch?.[1]) {\n const num = Number(taskNumMatch[1]);\n if (num >= 1 && num <= pending.length) {\n const node = pending[num - 1];\n if (node && node.status !== 'completed') {\n tracker.updateNodeStatus(node.id, 'completed');\n completed++;\n }\n }\n continue;\n }\n\n const completedMatch = trimmed.match(/^(?:Completed|Done|Finished)\\s*[:]\\s*(.+)/i);\n if (completedMatch?.[1]) {\n const title = completedMatch[1].trim();\n const match = pending.find(\n (n) =>\n n.title.toLowerCase().includes(title.toLowerCase()) ||\n title.toLowerCase().includes(n.title.toLowerCase()),\n );\n if (match && match.status !== 'completed') {\n tracker.updateNodeStatus(match.id, 'completed');\n completed++;\n }\n }\n }\n\n return completed;\n}\n", "import type { TaskProgress } from '@wrongstack/core/types';\nimport { expectDefined } from '@wrongstack/core/utils';\nimport { DefaultTaskStore, renderProgress, TaskTracker } from '@wrongstack/sdd';\nimport { sddState } from './state.js';\n\nexport type { TaskProgress };\nexport { renderProgress };\n\n/**\n * Format elapsed milliseconds as a human-readable string.\n */\nexport function formatElapsed(ms: number): string {\n if (ms < 1000) return `${ms}ms`;\n const s = Math.floor(ms / 1000);\n if (s < 60) return `${s}s`;\n const m = Math.floor(s / 60);\n const remS = s % 60;\n if (m < 60) return remS > 0 ? `${m}m ${remS}s` : `${m}m`;\n const h = Math.floor(m / 60);\n const remM = m % 60;\n return `${h}h ${remM}m`;\n}\n\nfunction addTaskToTracker(tracker: TaskTracker, task: Record<string, unknown>): void {\n tracker.addNode({\n title: String(task.title),\n description: String(task.description ?? ''),\n type: (['feature', 'bugfix', 'refactor', 'docs', 'test', 'chore'].includes(String(task.type))\n ? String(task.type)\n : 'feature') as 'feature' | 'bugfix' | 'refactor' | 'docs' | 'test' | 'chore',\n priority: (['critical', 'high', 'medium', 'low'].includes(String(task.priority))\n ? String(task.priority)\n : 'medium') as 'critical' | 'high' | 'medium' | 'low',\n status: 'pending',\n estimateHours: Number(task.estimateHours) || 2,\n tags: Array.isArray(task.tags) ? task.tags.map(String) : [],\n });\n}\n\nexport async function trySaveTasksFromAIOutput(aiOutput: string): Promise<boolean> {\n const builder = sddState.getBuilder();\n if (!builder) return false;\n const session = builder.getSession();\n if (!session.spec) return false;\n\n const json = builder.extractJSONArray(aiOutput);\n if (!json) return false;\n\n let tasks: Array<Record<string, unknown>>;\n try {\n tasks = JSON.parse(json);\n } catch {\n return false;\n }\n if (!Array.isArray(tasks) || tasks.length === 0) return false;\n\n const validTasks = tasks.filter(\n (t) => t && typeof t === 'object' && typeof t.title === 'string' && t.title.length > 0,\n );\n if (validTasks.length === 0) return false;\n\n const existingTracker = sddState.getTaskTracker();\n if (existingTracker) {\n for (const task of validTasks) addTaskToTracker(existingTracker, task);\n return true;\n }\n\n const store = sddState.getTaskStore() ?? new DefaultTaskStore();\n const tracker = new TaskTracker({ store });\n // `createGraph` now awaits `store.saveGraph` (real fs I/O for\n // TaskGraphStore). ENOSPC / EACCES on the on-disk store used to be\n // unreachable because the legacy DefaultTaskStore was a no-op; the\n // contract documented \"best-effort persistence\" so a failed write must\n // not reject through the caller. Fall back to tracking in-memory only\n // when the store rejects \u2014 the in-memory tracker still drives the run.\n let graph: Awaited<ReturnType<typeof tracker.createGraph>>;\n try {\n graph = await tracker.createGraph(session.spec.id, session.spec.title);\n } catch (error) {\n console.warn(\n JSON.stringify({\n level: 'warn',\n event: 'sdd.task_graph_create_failed',\n message: error instanceof Error ? error.message : String(error),\n timestamp: new Date().toISOString(),\n }),\n );\n return false;\n }\n for (const task of validTasks) addTaskToTracker(tracker, task);\n sddState.setTaskStore(store);\n sddState.setTaskTracker(tracker);\n sddState.setTaskGraphId(graph.id);\n // Fire-and-forget save is unsafe here \u2014 `setTaskGraphId` is awaited precisely\n // because the next mutation or caller's own `await saveSession()` must not\n // race a stale fire-and-forget rename that silently reverts the persisted\n // taskGraphId. The awaited save ensures the persisted snapshot matches\n // sddState.setTaskGraphId before this function returns.\n await builder.setTaskGraphId(graph.id);\n return true;\n}\n\nexport function getTaskProgress(): TaskProgress | null {\n const tracker = sddState.getTaskTracker();\n if (!tracker) return null;\n return tracker.getProgress();\n}\n\nexport function getCurrentTask(): {\n id: string;\n title: string;\n description: string;\n priority: string;\n estimateHours: number;\n tags: string[];\n startedAt: number | undefined;\n} | null {\n const tracker = sddState.getTaskTracker();\n if (!tracker) return null;\n const nodes = tracker.getAllNodes({ status: ['in_progress'] });\n if (nodes.length === 0) return null;\n const n = expectDefined(nodes[0]);\n return {\n id: n.id,\n title: n.title,\n description: n.description,\n priority: n.priority,\n estimateHours: n.estimateHours ?? 0,\n tags: n.tags ?? [],\n startedAt: n.startedAt,\n };\n}\n\nexport function advanceToNextTask(): boolean {\n const tracker = sddState.getTaskTracker();\n if (!tracker) return false;\n const pending = tracker.getAllNodes({ status: ['pending'] });\n for (const n of pending) {\n if (tracker.canStart(n.id)) {\n tracker.updateNodeStatus(n.id, 'in_progress');\n return true;\n }\n }\n return false;\n}\n\nexport function getTaskListText(): string | null {\n const tracker = sddState.getTaskTracker();\n if (!tracker) return null;\n const nodes = tracker.getAllNodes();\n if (nodes.length === 0) return null;\n return nodes\n .map((n, i) => {\n const status = n.status === 'completed' ? '\u2705' : n.status === 'in_progress' ? '\uD83D\uDD04' : '\u23F3';\n return `${i + 1}. ${status} [${n.priority}] ${n.title}`;\n })\n .join('\\n');\n}\n\nexport function renderTaskListWithProgress(): string | null {\n const tracker = sddState.getTaskTracker();\n if (!tracker) return null;\n const nodes = tracker.getAllNodes();\n if (nodes.length === 0) return null;\n\n const progress = tracker.getProgress();\n const phase = sddState.getPhase();\n const phaseLabel: Record<string, string> = {\n questioning: '\u2753 Questioning',\n spec_review: '\uD83D\uDCCB Spec Review',\n implementation: '\uD83C\uDFD7\uFE0F Implementation',\n task_review: '\uD83D\uDCDD Task Review',\n executing: '\u26A1 Executing',\n done: '\u2705 Done',\n };\n const lines = [\n `**${phaseLabel[phase ?? ''] ?? phase} \u2014 Task Status**`,\n '',\n renderProgress(progress),\n '',\n ];\n\n const sorted = [...nodes].sort((a, b) => {\n const order: Record<string, number> = {\n in_progress: 0,\n pending: 1,\n review: 2,\n blocked: 3,\n failed: 4,\n completed: 5,\n };\n return (order[a.status] ?? 6) - (order[b.status] ?? 6);\n });\n for (let i = 0; i < sorted.length; i++) {\n const n = expectDefined(sorted[i]);\n const status =\n n.status === 'completed'\n ? '\u2705'\n : n.status === 'in_progress'\n ? '\uD83D\uDD04'\n : n.status === 'failed'\n ? '\u274C'\n : n.status === 'blocked'\n ? '\uD83D\uDEAB'\n : n.status === 'review'\n ? '\uD83D\uDC41'\n : '\u23F3';\n const title = n.title.length > 50 ? n.title.slice(0, 49) + '\u2026' : n.title;\n let elapsed = '';\n if (n.status === 'in_progress' && n.startedAt)\n elapsed = ` \u00B7 ${formatElapsed(Date.now() - n.startedAt)}`;\n lines.push(`${i + 1}. ${status} ${title}${elapsed}`);\n }\n return lines.join('\\n');\n}\n\nexport function getCurrentExecutingContext(): string | null {\n const tracker = sddState.getTaskTracker();\n if (!tracker) return null;\n const nodes = tracker.getAllNodes({ status: ['in_progress'] });\n if (nodes.length === 0) return null;\n const n = expectDefined(nodes[0]);\n const elapsed = n.startedAt ? ` \u00B7 elapsed: ${formatElapsed(Date.now() - n.startedAt)}` : '';\n const progress = tracker.getProgress();\n return [\n `**NOW EXECUTING:** \"${n.title}\"${elapsed}`,\n `Description: ${n.description.split('\\n')[0] ?? '(none)'}`,\n `Priority: ${n.priority} \u00B7 Est: ${n.estimateHours ?? 0}h \u00B7 Tags: ${(n.tags ?? []).join(', ') || 'none'}`,\n `Progress: ${progress.completed}/${progress.total} tasks (${progress.percentComplete}%)`,\n ].join('\\n');\n}\n\n/**\n * Resolve a `/sdd done|skip|fail|review <N|title>` argument to a single task\n * node. Tries a 1-based index into the given (already filtered/sorted) node\n * list first, then a bidirectional case-insensitive title-substring match.\n * Centralizes the matching the four mutation subcommands used to duplicate.\n */\nexport function matchTaskNode<T extends { id: string; title: string }>(\n nodes: T[],\n query: string,\n): T | null {\n const trimmed = query.trim();\n if (!trimmed) return null;\n const num = Number(trimmed);\n if (!Number.isNaN(num) && num >= 1 && num <= nodes.length) {\n return nodes[num - 1] ?? null;\n }\n const q = trimmed.toLowerCase();\n return (\n nodes.find((n) => n.title.toLowerCase().includes(q) || q.includes(n.title.toLowerCase())) ??\n null\n );\n}\n\nexport function markTaskCompleted(taskTitle: string): boolean {\n const tracker = sddState.getTaskTracker();\n if (!tracker) return false;\n const nodes = tracker.getAllNodes({ status: ['pending', 'in_progress'] });\n const match = nodes.find(\n (n) =>\n n.title.toLowerCase().includes(taskTitle.toLowerCase()) ||\n taskTitle.toLowerCase().includes(n.title.toLowerCase()),\n );\n if (!match) return false;\n tracker.updateNodeStatus(match.id, 'completed');\n return true;\n}\n\nexport function getTaskGraphId(): string | null {\n return sddState.getTaskGraphId();\n}\nexport function getTaskTrackerExport(): TaskTracker | null {\n return sddState.getTaskTracker();\n}\n"],
5
- "mappings": "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",
6
- "names": []
7
- }
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../src/plugin-management.ts"],
4
- "sourcesContent": ["import * as fs from 'node:fs/promises';\nimport { atomicWrite } from '@wrongstack/core/utils';\nimport type { Config, PluginConfig, PluginManagerConfig } from '@wrongstack/core/types';\nimport { OFFICIAL_PLUGIN_AUDIT_ENTRIES } from '@wrongstack/plugins/audit';\n\nexport interface PluginAuditEntry {\n name: string;\n risk: 'low' | 'medium' | 'high';\n summary: string;\n defaultState: 'active' | 'inactive';\n canDisable: boolean;\n}\n\n/** Host-owned plugins that are not published by @wrongstack/plugins. */\nconst HOST_PLUGIN_AUDIT_ENTRIES: readonly PluginAuditEntry[] = [\n {\n name: 'wstack-prompts',\n risk: 'medium',\n summary: 'Prompt library and prompt authoring commands.',\n defaultState: 'active',\n canDisable: true,\n },\n {\n name: 'wstack-sync',\n risk: 'medium',\n summary: 'Cloud sync commands for prompts, skills, settings, memory, and history.',\n defaultState: 'active',\n canDisable: true,\n },\n {\n name: 'wstack-git',\n risk: 'high',\n summary: 'Git commands for commit, status checks, and push.',\n defaultState: 'active',\n canDisable: true,\n },\n {\n name: 'wstack-observability',\n risk: 'low',\n summary: 'Runtime metrics and health slash commands.',\n defaultState: 'active',\n canDisable: true,\n },\n {\n name: 'wstack-chimera',\n risk: 'medium',\n summary: 'Spawns a post-session code review subagent when explicitly enabled.',\n defaultState: 'inactive',\n canDisable: true,\n },\n {\n name: 'wstack-skills',\n risk: 'medium',\n summary: 'Skill library, authoring, install, update, and uninstall commands.',\n defaultState: 'active',\n canDisable: true,\n },\n {\n name: 'wstack-plan',\n risk: 'medium',\n summary: 'Strategic plan board slash command.',\n defaultState: 'active',\n canDisable: true,\n },\n {\n name: '@wrongstack/plug-lsp',\n risk: 'medium',\n summary: 'Language Server Protocol tools and slash commands.',\n defaultState: 'inactive',\n canDisable: true,\n },\n {\n name: 'telegram',\n risk: 'medium',\n summary: 'Telegram bridge for messages, approvals, and notifications.',\n defaultState: 'inactive',\n canDisable: true,\n },\n] as const;\n\nexport const PLUGIN_AUDIT_ENTRIES: readonly PluginAuditEntry[] = Object.freeze([\n ...HOST_PLUGIN_AUDIT_ENTRIES,\n ...OFFICIAL_PLUGIN_AUDIT_ENTRIES,\n]);\n\nexport const OFFICIAL_PLUGINS = [\n {\n alias: 'telegram',\n specifier: '@wrongstack/telegram',\n description: 'Telegram bridge for prompts, notifications, and slash commands.',\n },\n {\n alias: 'lsp',\n specifier: '@wrongstack/plug-lsp',\n description: 'Language Server Protocol tools for code intelligence.',\n },\n] as const;\n\nexport interface PluginManagementDeps {\n config: Config;\n configPath: string;\n}\n\nexport interface PluginManagementResult {\n code: number;\n level: 'output' | 'info' | 'error';\n message: string;\n patch?: {\n plugins?: (string | PluginConfig)[] | undefined;\n features?: Record<string, unknown>;\n pluginManager?: PluginManagerConfig | undefined;\n /**\n * Full replacement `extensions` map (patchConfig is a SHALLOW merge,\n * so per-plugin llm overrides must ship the whole merged object).\n */\n extensions?: Record<string, Record<string, unknown>> | undefined;\n };\n restartRequired?: boolean | undefined;\n}\n\nconst OFFICIAL_ALIASES = new Map<string, string>(\n OFFICIAL_PLUGINS.flatMap((p) => [\n [p.alias, p.specifier],\n [p.specifier, p.specifier],\n ]),\n);\n\nexport async function runPluginManagementCommand(\n args: string[],\n deps: PluginManagementDeps,\n): Promise<PluginManagementResult> {\n const sub = args[0];\n if (!sub || sub === 'list' || sub === 'status') {\n return {\n code: 0,\n level: 'output',\n message: renderConfiguredPlugins(deps.config),\n };\n }\n if (sub === 'official' || sub === 'officials') {\n return {\n code: 0,\n level: 'output',\n message: renderOfficialPlugins(deps.config),\n };\n }\n if (sub === 'report' || sub === 'audit' || sub === 'menu') {\n return {\n code: 0,\n level: 'output',\n message: renderPluginAuditReport(deps.config),\n };\n }\n if (sub === 'add' || sub === 'install') {\n const spec = args[1];\n if (!spec) {\n return errorResult('Usage: wstack plugin add <specifier|official-alias> [--disabled]');\n }\n return upsertPlugin(\n resolvePluginSpecifier(spec),\n { enabled: !args.includes('--disabled') },\n deps,\n 'Added',\n );\n }\n if (sub === 'remove' || sub === 'rm' || sub === 'uninstall') {\n const spec = args[1];\n if (!spec) {\n return errorResult('Usage: wstack plugin remove <specifier|official-alias>');\n }\n return removePlugin(resolvePluginSpecifier(spec), deps);\n }\n if (sub === 'enable' || sub === 'disable') {\n const spec = args[1];\n if (!spec) {\n return errorResult(`Usage: wstack plugin ${sub} <specifier|official-alias>`);\n }\n return upsertPlugin(\n resolvePluginSpecifier(spec),\n { enabled: sub === 'enable' },\n deps,\n sub === 'enable' ? 'Enabled' : 'Disabled',\n );\n }\n if (sub === 'toggle') {\n const spec = args[1];\n if (!spec) {\n return errorResult('Usage: wstack plugin toggle <plugin-name|official-alias>');\n }\n return togglePlugin(resolvePluginToggleSpecifier(spec), deps);\n }\n if (sub === 'manager') {\n return runPluginManagerPolicyCommand(args.slice(1), deps);\n }\n if (sub === 'llm') {\n return runPluginLlmCommand(args.slice(1), deps);\n }\n return errorResult(\n `Unknown plugin subcommand: ${sub}\\nUsage: wstack plugin [list|status|report|menu|official|add|install|remove|enable|disable|toggle|manager|llm]`,\n );\n}\n\nconst PLUGIN_MANAGER_POLICY_USAGE = [\n 'Usage:',\n ' wstack plugin manager List plugins locked against LLM state changes.',\n ' wstack plugin manager lock <plugin|*> Prevent plugin_manager from enabling/disabling it.',\n ' wstack plugin manager unlock <plugin|*> Restore plugin_manager enable/disable access.',\n '',\n 'This policy affects only the LLM-facing plugin_manager tool. Human /plugin and',\n 'wstack plugin enable/disable commands remain available.',\n].join('\\n');\n\nasync function runPluginManagerPolicyCommand(\n args: string[],\n deps: PluginManagementDeps,\n): Promise<PluginManagementResult> {\n const action = args[0] ?? 'list';\n if (action === 'list') {\n const locked = deps.config.pluginManager?.locked ?? [];\n return {\n code: 0,\n level: 'output',\n message:\n locked.length === 0\n ? 'LLM plugin-manager policy: no plugins are locked.'\n : `LLM plugin-manager policy \u2014 locked (${locked.length}):\\n${locked.map((name) => ` ${name}`).join('\\n')}`,\n };\n }\n\n if (action !== 'lock' && action !== 'unlock') {\n return errorResult(PLUGIN_MANAGER_POLICY_USAGE);\n }\n const rawName = args[1];\n if (!rawName) return errorResult(PLUGIN_MANAGER_POLICY_USAGE);\n const name = rawName === '*' ? '*' : resolvePluginToggleSpecifier(rawName);\n\n const existing = await readConfig(deps.configPath);\n const filePolicy = isRecord(existing.pluginManager) ? existing.pluginManager : undefined;\n const current = Array.isArray(filePolicy?.['locked'])\n ? filePolicy['locked'].filter((entry): entry is string => typeof entry === 'string')\n : [...(deps.config.pluginManager?.locked ?? [])];\n const canonical = canonicalPolicyName(name);\n const next =\n action === 'lock'\n ? current.some((entry) => canonicalPolicyName(entry) === canonical)\n ? current\n : [...current, name]\n : current.filter((entry) => canonicalPolicyName(entry) !== canonical);\n\n const pluginManager: PluginManagerConfig = { locked: next };\n existing.pluginManager = { ...(filePolicy ?? {}), ...pluginManager };\n await atomicWrite(deps.configPath, JSON.stringify(existing, null, 2), { mode: 0o600 });\n\n return {\n code: 0,\n level: 'info',\n message:\n action === 'lock'\n ? `Locked \"${name}\" against LLM enable/disable changes. Human plugin commands still work.`\n : `Unlocked \"${name}\" for LLM enable/disable changes.`,\n patch: { pluginManager },\n };\n}\n\nfunction canonicalPolicyName(name: string): string {\n if (name === '*') return name;\n return resolvePluginToggleSpecifier(name).trim().toLowerCase();\n}\n\nconst PLUGIN_LLM_USAGE = [\n 'Usage:',\n ' wstack plugin llm List every per-plugin LLM override.',\n \" wstack plugin llm <plugin> Show one plugin's LLM override.\",\n ' wstack plugin llm <plugin> <provider> [model] Route this plugin through a provider (and model).',\n ' wstack plugin llm <plugin> - <model> Keep the session provider, override only the model.',\n ' wstack plugin llm <plugin> --clear Remove the override (back to the session default).',\n '',\n 'The override is stored as config.extensions.<plugin>.llm and is used by',\n 'every api.llm call the plugin makes. Without an override, plugins follow',\n 'the active session provider/model (the chat leader).',\n].join('\\n');\n\n/**\n * Render every configured per-plugin LLM override. Reads\n * `config.extensions.<plugin>.llm` across all plugins so the user can\n * see, at a glance, which plugins deviate from the session default.\n */\nfunction renderPluginLlmOverviews(config: Config, sessionDefault: string): string {\n const rows: Array<{ name: string; provider: string; model: string }> = [];\n for (const [pluginName, ext] of Object.entries(config.extensions ?? {})) {\n const llm = (ext as Record<string, unknown>)?.['llm'];\n if (llm && typeof llm === 'object') {\n const o = llm as { provider?: string; model?: string };\n rows.push({\n name: pluginName,\n provider: o.provider ?? '(session)',\n model: o.model ?? '(session)',\n });\n }\n }\n const lines = [`Per-plugin LLM routing (session default: ${sessionDefault}):`];\n if (rows.length === 0) {\n lines.push(\n ' No plugin overrides \u2014 every plugin follows the session default.',\n ' Set one with: wstack plugin llm <plugin> <provider> [model]',\n );\n } else {\n rows.sort((a, b) => a.name.localeCompare(b.name));\n for (const r of rows) {\n lines.push(` ${r.name.padEnd(24)} provider=${r.provider.padEnd(16)} model=${r.model}`);\n }\n lines.push('', 'Clear one with: wstack plugin llm <plugin> --clear');\n }\n return lines.join('\\n');\n}\n\n/**\n * `wstack plugin llm \u2026` \u2014 per-plugin LLM routing. The chat leader's\n * provider/model stays the DEFAULT; this stores an override under\n * `config.extensions[<plugin>].llm = { provider?, model? }` which\n * `api.llm` resolves ahead of the session default on every call.\n */\nasync function runPluginLlmCommand(\n args: string[],\n deps: PluginManagementDeps,\n): Promise<PluginManagementResult> {\n const sessionDefault = `${deps.config.provider ?? '?'} / ${deps.config.model ?? '?'}`;\n\n // \u2500\u2500 List: `plugin llm` (no name) or `plugin llm list` \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n if (args.length === 0 || args[0] === 'list') {\n return {\n code: 0,\n level: 'output',\n message: renderPluginLlmOverviews(deps.config, sessionDefault),\n };\n }\n\n const name = args[0];\n if (!name || name.startsWith('-')) return errorResult(PLUGIN_LLM_USAGE);\n\n const currentOverride = deps.config.extensions?.[name]?.['llm'];\n\n // \u2500\u2500 Show \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n if (args.length === 1) {\n const lines = [`LLM routing for plugin \"${name}\":`];\n if (currentOverride && typeof currentOverride === 'object') {\n const o = currentOverride as { provider?: string; model?: string };\n lines.push(\n ` override: provider=${o.provider ?? '(session)'} model=${o.model ?? '(session)'}`,\n ` session default: ${sessionDefault}`,\n );\n } else {\n lines.push(\n ` no override \u2014 follows the session default (${sessionDefault}).`,\n ` Set one with: wstack plugin llm ${name} <provider> [model]`,\n );\n }\n return { code: 0, level: 'output', message: lines.join('\\n') };\n }\n\n const existing = await readConfig(deps.configPath);\n const fileExtensions = isRecord(existing.extensions)\n ? (existing.extensions as Record<string, unknown>)\n : {};\n const pluginExt = isRecord(fileExtensions[name])\n ? { ...(fileExtensions[name] as Record<string, unknown>) }\n : {};\n\n // \u2500\u2500 Clear \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n if (args[1] === '--clear' || args[1] === 'clear') {\n if (!('llm' in pluginExt) && !currentOverride) {\n return { code: 0, level: 'info', message: `Plugin \"${name}\" has no LLM override.` };\n }\n delete pluginExt['llm'];\n if (Object.keys(pluginExt).length === 0) delete fileExtensions[name];\n else fileExtensions[name] = pluginExt;\n existing.extensions = fileExtensions;\n await atomicWrite(deps.configPath, JSON.stringify(existing, null, 2), { mode: 0o600 });\n const merged = mergeExtensionsForPatch(deps.config, fileExtensions);\n // The in-memory config may still carry the old override \u2014 the merge\n // above unions both sources, so drop the key explicitly.\n const mergedEntry = merged[name];\n if (mergedEntry) {\n delete mergedEntry['llm'];\n if (Object.keys(mergedEntry).length === 0) delete merged[name];\n }\n return {\n code: 0,\n level: 'info',\n message:\n `Cleared LLM override for \"${name}\" \u2014 it now follows the session default (${sessionDefault}). ` +\n 'Takes effect immediately for api.llm calls.',\n patch: { extensions: merged },\n };\n }\n\n // \u2500\u2500 Set \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n const providerArg = args[1];\n const modelArg = args[2];\n if (!providerArg || providerArg.startsWith('--')) return errorResult(PLUGIN_LLM_USAGE);\n const provider = providerArg === '-' ? undefined : providerArg;\n const model = modelArg && modelArg !== '-' ? modelArg : undefined;\n if (!provider && !model) return errorResult(PLUGIN_LLM_USAGE);\n\n const llm: Record<string, unknown> = {};\n if (provider) llm['provider'] = provider;\n if (model) llm['model'] = model;\n pluginExt['llm'] = llm;\n fileExtensions[name] = pluginExt;\n existing.extensions = fileExtensions;\n await atomicWrite(deps.configPath, JSON.stringify(existing, null, 2), { mode: 0o600 });\n\n // Soft sanity note: an unknown provider name still works if the\n // models.dev catalog knows it, but flag likely typos against the\n // user's configured providers.\n const configured = Object.keys(deps.config.providers ?? {});\n const note =\n provider && configured.length > 0 && !configured.includes(provider)\n ? `\\nNote: \"${provider}\" is not in config.providers (configured: ${configured.join(', ')}). ` +\n 'Catalog providers (anthropic, openai, \u2026) still resolve if credentials are available.'\n : '';\n\n const merged = mergeExtensionsForPatch(deps.config, fileExtensions);\n return {\n code: 0,\n level: 'info',\n message:\n `Plugin \"${name}\" now routes api.llm calls via ` +\n `provider=${provider ?? '(session)'} model=${model ?? '(session)'}. ` +\n `Session default stays ${sessionDefault}. Takes effect immediately for api.llm calls.${note}`,\n patch: { extensions: merged },\n };\n}\n\n/**\n * patchConfig is a shallow merge \u2014 build the FULL extensions map:\n * in-memory config first, then the file's (just-written) entries win.\n */\nfunction mergeExtensionsForPatch(\n config: Config,\n fileExtensions: Record<string, unknown>,\n): Record<string, Record<string, unknown>> {\n const merged: Record<string, Record<string, unknown>> = {};\n for (const [k, v] of Object.entries(config.extensions ?? {})) {\n merged[k] = { ...v };\n }\n for (const [k, v] of Object.entries(fileExtensions)) {\n if (isRecord(v)) merged[k] = { ...(merged[k] ?? {}), ...v };\n }\n // A cleared plugin entry must disappear from the merged view too.\n for (const k of Object.keys(merged)) {\n if (!(k in fileExtensions) && !(k in (config.extensions ?? {}))) delete merged[k];\n }\n return merged;\n}\n\nexport function resolvePluginSpecifier(input: string): string {\n return OFFICIAL_ALIASES.get(input.toLowerCase()) ?? input;\n}\n\nfunction resolvePluginToggleSpecifier(input: string): string {\n if (input.toLowerCase() === '@wrongstack/telegram') return 'telegram';\n if (auditEntryFor(input)) return input;\n return resolvePluginSpecifier(input);\n}\n\nexport function renderOfficialPlugins(config?: Config): string {\n return [\n 'Official plugins:',\n ...OFFICIAL_PLUGINS.map((p) => {\n const state = config ? officialPluginState(config, p.specifier) : '';\n const status = state ? `${state.padEnd(14)} ` : '';\n return ` ${p.alias.padEnd(12)} ${status}${p.specifier.padEnd(24)} ${p.description}`;\n }),\n '',\n 'Use `wstack plugin add <alias>` or `/plugin install <alias>`.',\n ].join('\\n');\n}\n\nexport function renderConfiguredPlugins(config: Config): string {\n const plugins = config.plugins ?? [];\n if (plugins.length === 0) {\n return [\n 'No plugins configured.',\n 'Use `wstack plugin add <specifier>` or `/plugin install <official-alias>`.',\n ].join('\\n');\n }\n return plugins\n .map((p) => {\n const name = pluginName(p);\n const enabled = typeof p === 'object' && p.enabled === false ? 'disabled' : 'enabled';\n const official = OFFICIAL_PLUGINS.find((entry) => entry.specifier === name);\n const suffix = official ? ` (${official.alias})` : '';\n return ` ${`${name}${suffix}`.padEnd(44)} ${enabled}`;\n })\n .join('\\n');\n}\n\nexport function renderPluginAuditReport(config: Config): string {\n const configured = config.plugins ?? [];\n const lines = [\n 'Plugin audit report:',\n ' State shows the effective boot state. Default-active plugins run unless disabled in config.',\n ' Built-in audit rows are toggleable unless a future row explicitly marks itself locked.',\n '',\n ' Built-in audit entries:',\n ];\n\n for (const entry of PLUGIN_AUDIT_ENTRIES) {\n const { state, source } = effectiveAuditState(config, entry);\n const policy = entry.canDisable ? 'toggleable' : lockedReason(entry);\n lines.push(\n ` ${entry.name.padEnd(24)} ${state.padEnd(8)} ${source.padEnd(8)} risk=${entry.risk.padEnd(6)} ${policy.padEnd(24)} ${entry.summary}`,\n );\n }\n\n const extra = configured.filter(\n (plugin) => !PLUGIN_AUDIT_ENTRIES.some((entry) => pluginMatchesToggleSpec(plugin, entry.name)),\n );\n if (extra.length > 0) {\n lines.push('', ' Configured plugins outside the audit list:');\n for (const plugin of extra) {\n const name = pluginName(plugin);\n const state = typeof plugin === 'object' && plugin.enabled === false ? 'disabled' : 'enabled';\n lines.push(\n ` ${name.padEnd(24)} ${state.padEnd(8)} config risk=custom user-configured plugin`,\n );\n }\n }\n\n lines.push(\n '',\n 'Use `/plugin menu` in the TUI, or `wstack plugin toggle <name>` for toggleable rows.',\n );\n return lines.join('\\n');\n}\n\nasync function readConfig(file: string): Promise<Record<string, unknown>> {\n try {\n return JSON.parse(await fs.readFile(file, 'utf8')) as Record<string, unknown>;\n } catch {\n return {};\n }\n}\n\nfunction pluginName(p: string | PluginConfig): string {\n return typeof p === 'string' ? p : p.name;\n}\n\nfunction pluginEntry(spec: string, enabled: boolean): string | PluginConfig {\n return enabled ? spec : { name: spec, enabled: false };\n}\n\nfunction pluginMatchesToggleSpec(p: string | PluginConfig, spec: string): boolean {\n const name = pluginName(p);\n if (spec === 'telegram') return name === 'telegram' || name === '@wrongstack/telegram';\n return name === spec;\n}\n\nfunction effectiveAuditState(\n config: Config,\n entry: PluginAuditEntry,\n): { state: 'enabled' | 'disabled'; source: 'config' | 'default' } {\n const configured = (config.plugins ?? []).find((plugin) =>\n pluginMatchesToggleSpec(plugin, entry.name),\n );\n if (configured) {\n return {\n state:\n typeof configured === 'object' && configured.enabled === false ? 'disabled' : 'enabled',\n source: 'config',\n };\n }\n return {\n state: entry.defaultState === 'active' ? 'enabled' : 'disabled',\n source: 'default',\n };\n}\n\nfunction lockedReason(entry: PluginAuditEntry): string {\n if (entry.canDisable) return 'toggleable';\n if (entry.name === 'secret-scanner') {\n return 'locked: safety guard';\n }\n return 'locked: core surface';\n}\n\nfunction auditEntryFor(spec: string): PluginAuditEntry | undefined {\n return PLUGIN_AUDIT_ENTRIES.find((entry) => entry.name === spec);\n}\n\nfunction officialPluginState(\n config: Config,\n spec: string,\n): 'enabled' | 'disabled' | 'not configured' {\n const match = (config.plugins ?? []).find((p) => pluginName(p) === spec);\n if (!match) return 'not configured';\n return typeof match === 'object' && match.enabled === false ? 'disabled' : 'enabled';\n}\n\nasync function togglePlugin(\n spec: string,\n deps: PluginManagementDeps,\n): Promise<PluginManagementResult> {\n const auditEntry = auditEntryFor(spec);\n if (auditEntry?.canDisable === false) {\n return errorResult(`Plugin \"${spec}\" is locked and cannot be toggled from the plugin menu.`);\n }\n\n const existing = await readConfig(deps.configPath);\n const plugins = Array.isArray(existing.plugins)\n ? (existing.plugins as Array<string | PluginConfig>)\n : [];\n const idx = plugins.findIndex((p) => pluginMatchesToggleSpec(p, spec));\n const configured = idx >= 0 ? plugins[idx] : undefined;\n const enabled = configured\n ? !(typeof configured === 'object' && configured.enabled === false)\n : auditEntry?.defaultState === 'active';\n\n if (!configured && !auditEntry) {\n return errorResult(\n `Plugin \"${spec}\" is not configured. Use \"wstack plugin add ${spec}\" first.`,\n );\n }\n\n let nextPlugins: Array<string | PluginConfig>;\n let nextEnabled: boolean;\n if (enabled) {\n nextEnabled = false;\n nextPlugins = plugins.filter((p) => !pluginMatchesToggleSpec(p, spec));\n if (auditEntry?.defaultState !== 'inactive') {\n nextPlugins.push({ name: spec, enabled: false });\n }\n } else {\n nextEnabled = true;\n if (auditEntry?.defaultState === 'active') {\n nextPlugins = plugins.filter((p) => !pluginMatchesToggleSpec(p, spec));\n } else {\n nextPlugins = plugins.filter((p) => !pluginMatchesToggleSpec(p, spec));\n nextPlugins.push(spec);\n }\n }\n\n const features = {\n ...(isRecord(deps.config.features) ? deps.config.features : {}),\n ...(isRecord(existing.features) ? existing.features : {}),\n plugins: true,\n };\n existing.plugins = nextPlugins;\n existing.features = features;\n await atomicWrite(deps.configPath, JSON.stringify(existing, null, 2), { mode: 0o600 });\n const restartRequired = spec === 'branch-guard' && !nextEnabled ? undefined : true;\n return {\n code: 0,\n level: 'info',\n message:\n `${nextEnabled ? 'Enabled' : 'Disabled'} \"${spec}\". Config written to ${deps.configPath}.` +\n (spec === 'branch-guard' && !nextEnabled\n ? ' The running branch-guard hook will disable itself on config update.'\n : ''),\n patch: { plugins: nextPlugins, features },\n restartRequired,\n };\n}\n\nasync function upsertPlugin(\n spec: string,\n opts: { enabled: boolean },\n deps: PluginManagementDeps,\n verb: string,\n): Promise<PluginManagementResult> {\n const existing = await readConfig(deps.configPath);\n const plugins = Array.isArray(existing.plugins)\n ? (existing.plugins as Array<string | PluginConfig>)\n : [];\n const idx = plugins.findIndex((p) => pluginName(p) === spec);\n const nextEntry = pluginEntry(spec, opts.enabled);\n if (idx >= 0) plugins[idx] = nextEntry;\n else plugins.push(nextEntry);\n const features = {\n ...(isRecord(deps.config.features) ? deps.config.features : {}),\n ...(isRecord(existing.features) ? existing.features : {}),\n plugins: true,\n };\n existing.plugins = plugins;\n existing.features = features;\n await atomicWrite(deps.configPath, JSON.stringify(existing, null, 2), { mode: 0o600 });\n const restartRequired = spec === 'branch-guard' && !opts.enabled ? undefined : true;\n return {\n code: 0,\n level: 'info',\n message:\n `${verb} \"${spec}\" (${opts.enabled ? 'enabled' : 'disabled'}). Config written to ${deps.configPath}.` +\n (spec === 'branch-guard' && !opts.enabled\n ? ' The running branch-guard hook will disable itself on config update.'\n : ''),\n patch: { plugins, features },\n restartRequired,\n };\n}\n\nasync function removePlugin(\n spec: string,\n deps: PluginManagementDeps,\n): Promise<PluginManagementResult> {\n const existing = await readConfig(deps.configPath);\n const plugins = Array.isArray(existing.plugins)\n ? (existing.plugins as Array<string | PluginConfig>)\n : [];\n const next = plugins.filter((p) => pluginName(p) !== spec);\n if (next.length === plugins.length) {\n return errorResult(`Plugin \"${spec}\" not in config.`);\n }\n existing.plugins = next;\n await atomicWrite(deps.configPath, JSON.stringify(existing, null, 2), { mode: 0o600 });\n return {\n code: 0,\n level: 'info',\n message: `Removed \"${spec}\" from config.`,\n patch: { plugins: next },\n restartRequired: true,\n };\n}\n\nfunction errorResult(message: string): PluginManagementResult {\n return { code: 1, level: 'error', message };\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return Boolean(value) && typeof value === 'object' && !Array.isArray(value);\n}\n"],
5
- "mappings": "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",
6
- "names": ["name", "pluginName", "merged"]
7
- }