0sec-cli 0.22.1 → 0.23.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 (219) hide show
  1. package/0.js +64 -47
  2. package/README.md +73 -53
  3. package/chunks/{adapt-loop-J6QCZZTB.js → adapt-loop-V3JFWOKC.js} +4 -4
  4. package/chunks/{artifact-scraper-MFKT6OI4.js → artifact-scraper-NYKUF2M5.js} +2 -2
  5. package/chunks/{assumption-mining-646LYSJY.js → assumption-mining-ZX3YVWEH.js} +19 -15
  6. package/chunks/backend-connections-H52NIVH2.js +23 -0
  7. package/chunks/{chunk-NLH4LMWH.js → chunk-3E54JYS4.js} +1 -1
  8. package/chunks/chunk-3P3XXSIJ.js +463 -0
  9. package/chunks/{chunk-CPWA6RWE.js → chunk-3ZNI7F42.js} +3 -3
  10. package/chunks/{chunk-LGCASF2C.js → chunk-4HGGNBKK.js} +7405 -2225
  11. package/chunks/{chunk-TM74SSHC.js → chunk-62ENS6XO.js} +1 -1
  12. package/chunks/{chunk-4VGLO2YS.js → chunk-65H3MRBF.js} +5 -2
  13. package/chunks/chunk-A2ADXAC5.js +5329 -0
  14. package/chunks/chunk-A5QO7N3S.js +74 -0
  15. package/chunks/chunk-AAA4PRWK.js +990 -0
  16. package/chunks/{chunk-AV2LG2L7.js → chunk-BF5FIK7H.js} +257 -145
  17. package/chunks/chunk-CP6HANBE.js +355 -0
  18. package/chunks/{chunk-HHPWRY2M.js → chunk-EH2WTNY7.js} +23474 -22746
  19. package/chunks/{chunk-OSBPGWTE.js → chunk-EJH2TNVH.js} +3 -3
  20. package/chunks/chunk-EWXOG6D3.js +1695 -0
  21. package/chunks/{chunk-WZRVS7SB.js → chunk-F344NYUN.js} +1 -1
  22. package/chunks/chunk-FXR3BT2G.js +226 -0
  23. package/chunks/{chunk-GOEA3NW6.js → chunk-HFDSIHTE.js} +1603 -1295
  24. package/chunks/chunk-I3OCJ2OA.js +309 -0
  25. package/chunks/{chunk-X7LAEEUW.js → chunk-ITES7BCY.js} +1 -1
  26. package/chunks/{chunk-YL6YAGLL.js → chunk-JEYIWMGL.js} +1 -1
  27. package/chunks/{chunk-WSDDGFA6.js → chunk-K55B4OH4.js} +1 -1
  28. package/chunks/{chunk-TH2LW437.js → chunk-KCA35IL3.js} +2 -190
  29. package/chunks/chunk-KHPOSLCF.js +1659 -0
  30. package/chunks/chunk-LT7BFGXA.js +733 -0
  31. package/chunks/{chunk-KAU6NLGP.js → chunk-LTEE2JWK.js} +786 -20
  32. package/chunks/{chunk-ZKQ2I3S2.js → chunk-LVGZGG63.js} +1 -1
  33. package/chunks/chunk-MQJVU2GZ.js +277 -0
  34. package/chunks/{chunk-OHHCGDJ2.js → chunk-NI6QYQSK.js} +126 -1089
  35. package/chunks/{chunk-6YNKRUMC.js → chunk-NJLKFC66.js} +321 -5
  36. package/chunks/{chunk-3E2KALZ5.js → chunk-NX4H6L4M.js} +2 -2
  37. package/chunks/{chunk-W6UWZFKL.js → chunk-O6KPVYOI.js} +100 -5
  38. package/chunks/{chunk-IVQV574Y.js → chunk-O6Z4XPOM.js} +1 -1
  39. package/chunks/{chunk-V6GHX34G.js → chunk-OLSKXTQZ.js} +8 -274
  40. package/chunks/{chunk-5BTJMINS.js → chunk-P7KDN5PK.js} +208 -46
  41. package/chunks/chunk-PIR6BNE7.js +83 -0
  42. package/chunks/{chunk-XCISCI4T.js → chunk-QMICC5O2.js} +30 -12
  43. package/chunks/{chunk-6GHSR47F.js → chunk-R35KKJA4.js} +1 -58
  44. package/chunks/chunk-RS4MAN3Q.js +73 -0
  45. package/chunks/{chunk-VWITLWXB.js → chunk-T7ZEVY5G.js} +307 -34
  46. package/chunks/chunk-TPUJZ74W.js +36 -0
  47. package/chunks/{chunk-IFME676H.js → chunk-UZUELPFW.js} +79 -18
  48. package/chunks/{chunk-TV27OWIW.js → chunk-V2BWXPZP.js} +9 -9
  49. package/chunks/chunk-VDOWARMJ.js +63 -0
  50. package/chunks/{chunk-XDKB2P5X.js → chunk-W5VNCB57.js} +1 -1
  51. package/chunks/{chunk-4G7OVYJX.js → chunk-WIYNYNL2.js} +1 -1
  52. package/chunks/chunk-WYNZFNMF.js +457 -0
  53. package/chunks/{chunk-7XCKYNLO.js → chunk-XORZUR2H.js} +1 -1
  54. package/chunks/{chunk-2OQOQ2FZ.js → chunk-Y4HJRUFE.js} +20 -4
  55. package/chunks/{chunk-V36HVTUL.js → chunk-YAEJAZYW.js} +21 -18
  56. package/chunks/{chunk-XA2O4WXA.js → chunk-YYS2H33A.js} +169 -15
  57. package/chunks/{codex-models-NHQW45SO.js → codex-models-Y457ND6Q.js} +9 -3
  58. package/chunks/{commands-N6GN5SBF.js → commands-PBDAOZKA.js} +7019 -5937
  59. package/chunks/{db-5IWEJUYV.js → db-TPSI552V.js} +3 -3
  60. package/chunks/deep-review-DKED6EN5.js +81 -0
  61. package/chunks/{dist-L3XJWWZ6.js → dist-4LGRIBOC.js} +209 -114
  62. package/chunks/{dist-FSS7MBDF.js → dist-7Q5A6TQM.js} +12 -2
  63. package/chunks/{dist-KEULE5IQ.js → dist-LNXGATR2.js} +2 -2
  64. package/chunks/{dist-XZB53PS5.js → dist-M2X367VW.js} +2 -2
  65. package/chunks/{dist-BS4CY4RG.js → dist-RLPNLRZD.js} +79 -1
  66. package/chunks/eval-runner-RZKDOQ2G.js +30 -0
  67. package/chunks/{exploit-agent-KW4EB7BE.js → exploit-agent-RMFYNG7H.js} +1 -1
  68. package/chunks/{exploit-autoclimb-CXJ2ERRO.js → exploit-autoclimb-2A2PQI4E.js} +1 -1
  69. package/chunks/{exploit-climb-TJWLY4XC.js → exploit-climb-DEJ232QN.js} +2 -2
  70. package/chunks/{fix-UUKPXCFB.js → fix-JGBW7XUN.js} +12 -6
  71. package/chunks/{harness-C5HOKIEV.js → harness-CBOLMUHB.js} +3 -3
  72. package/chunks/{hunt-scan-KZZ444V2.js → hunt-scan-MIRMABY4.js} +17 -13
  73. package/chunks/{kernel-primitive-Z77IR7YS.js → kernel-primitive-NAPDMJC7.js} +2 -2
  74. package/chunks/{kernel-vm-runner-JW77P276.js → kernel-vm-runner-PR5OSDQ2.js} +2 -2
  75. package/chunks/managed-operation-YNQQEA5V.js +59 -0
  76. package/chunks/{memsafety-scan-FCPAXIZE.js → memsafety-scan-RPS446CD.js} +2 -2
  77. package/chunks/{native-loop-P6P5GYVJ.js → native-loop-CGOJMNHV.js} +19 -9
  78. package/chunks/orchestrate-5NK34H3W.js +62 -0
  79. package/chunks/{prepare-KYHGFWCJ.js → prepare-YVNENPNL.js} +6 -2
  80. package/chunks/{process-N245QIQG.js → process-MJYJ2K5E.js} +2 -1
  81. package/chunks/remote-workflow-runtime-RGQ5ECMN.js +96 -0
  82. package/chunks/{run-GAFKBCTN.js → run-VH4OIDKF.js} +1919 -4817
  83. package/chunks/{runtime-XFWJTBUO.js → runtime-4TAJFVF7.js} +7 -1
  84. package/chunks/{runtime-H7YBR5IZ.js → runtime-HWL6HG7A.js} +10 -4
  85. package/chunks/{scan-stream-BMOCPKQX.js → scan-stream-HJAZJONE.js} +7 -1
  86. package/chunks/{scope-QWY4VQZ7.js → scope-Q5PIX2IS.js} +2 -2
  87. package/chunks/service-plugins-QYFITODK.js +63 -0
  88. package/chunks/{session-store-XIDFGG7K.js → session-store-2FEIZGSV.js} +6 -4
  89. package/chunks/{variant-candidates-7D4F5E4H.js → variant-candidates-TX5V6XOT.js} +9 -3
  90. package/chunks/{web-recon-prepass-VXXBPDAV.js → web-recon-prepass-6HOUGF7Q.js} +11 -6
  91. package/chunks/workflow-runtime-ZEEBJFQG.js +64 -0
  92. package/dashboard/apple-touch-icon.png +0 -0
  93. package/dashboard/assets/abnfDiagram-VCTEODGH-BuvumFYR.js +1 -0
  94. package/dashboard/assets/arc-DZzWDuCG.js +1 -0
  95. package/dashboard/assets/architecture-7GRP2DOG-DK9bv1Md.js +1 -0
  96. package/dashboard/assets/architectureDiagram-5GKGNRK7-Bw5el_EL.js +36 -0
  97. package/dashboard/assets/array-BifhSqXX.js +1 -0
  98. package/dashboard/assets/blockDiagram-I7D4REHJ-BjVLEApp.js +129 -0
  99. package/dashboard/assets/c4Diagram-7LVT6UL2-B9nNzx2J.js +38 -0
  100. package/dashboard/assets/channel-Ck17eKdX.js +1 -0
  101. package/dashboard/assets/chunk-2Q5K7J3B-C1jixKkw.js +1 -0
  102. package/dashboard/assets/chunk-4HAMMTFA-y72Q95nW.js +62 -0
  103. package/dashboard/assets/chunk-5VM5RSS4-ZNzvKenW.js +15 -0
  104. package/dashboard/assets/chunk-75Z2AOVW-BOhrOGMO.js +2 -0
  105. package/dashboard/assets/chunk-DU6HZSFF-B0EhAaNb.js +127 -0
  106. package/dashboard/assets/chunk-F27PBJKO-BR-hiDvM.js +1 -0
  107. package/dashboard/assets/chunk-FOHPRMQF-DHwB1DNv.js +161 -0
  108. package/dashboard/assets/chunk-GMAD6QVW-H15vJYXg.js +72 -0
  109. package/dashboard/assets/chunk-GVQU2GXP-BRndps0o.js +1 -0
  110. package/dashboard/assets/chunk-IMKFNOWR-CIzC_OWx.js +231 -0
  111. package/dashboard/assets/chunk-JWPE2WC7-DVXcaiue.js +1 -0
  112. package/dashboard/assets/chunk-L3NEJ4N5-NR4P1Vko.js +1 -0
  113. package/dashboard/assets/chunk-OSK3NFVY-DtjTDf8S.js +10 -0
  114. package/dashboard/assets/chunk-P2QGCYS3-CYT11Qjx.js +1 -0
  115. package/dashboard/assets/chunk-POPQ4Y6H-Duj2Ygwu.js +1 -0
  116. package/dashboard/assets/chunk-PWAF6VOD-z4p5Tf_y.js +1 -0
  117. package/dashboard/assets/chunk-SHT3W25Y-DYeLYLS3.js +168 -0
  118. package/dashboard/assets/chunk-SVP7TREG-BKowxCVI.js +88 -0
  119. package/dashboard/assets/chunk-TICWLB2K-GDAwEFlH.js +206 -0
  120. package/dashboard/assets/chunk-XXDRQBXY-DRD79Mjx.js +1 -0
  121. package/dashboard/assets/chunk-Y2CYZVJY-DsF7k-Jl.js +1 -0
  122. package/dashboard/assets/classDiagram-ZZMXUADV-BaeZA6LE.js +1 -0
  123. package/dashboard/assets/classDiagram-v2-VYDZK3BY-BaeZA6LE.js +1 -0
  124. package/dashboard/assets/common-vnzd7UOF.js +5 -0
  125. package/dashboard/assets/cose-bilkent-JH36ORCC-GqCLsRyf.js +1 -0
  126. package/dashboard/assets/cynefin-OW5HDTMX-CJN9YwWN.js +1 -0
  127. package/dashboard/assets/cynefinDiagram-5FMLGOSQ-DVJHCYQY.js +62 -0
  128. package/dashboard/assets/cytoscape.esm-CGd-uY3x.js +321 -0
  129. package/dashboard/assets/dagre-GXQ25YYZ-BMCrrJJR.js +4 -0
  130. package/dashboard/assets/dagre-PrKaheQc.js +1 -0
  131. package/dashboard/assets/defaultLocale-BFoDCU3G.js +1 -0
  132. package/dashboard/assets/diagram-S7CK7UJ4-CLYDdOEm.js +30 -0
  133. package/dashboard/assets/diagram-UQ7AKVKN-CnPuAZK7.js +41 -0
  134. package/dashboard/assets/diagram-VSXAHHWV-Cr2I2DnG.js +3 -0
  135. package/dashboard/assets/diagram-VX7I27RA-_mGW1wpp.js +24 -0
  136. package/dashboard/assets/diagram-Z3DM3KII-igybB0l9.js +24 -0
  137. package/dashboard/assets/dist-Bb7_-x5y.js +1 -0
  138. package/dashboard/assets/ebnfDiagram-PWID7BFC-CVCICf2R.js +1 -0
  139. package/dashboard/assets/erDiagram-RLTQ6QDP-CRjnfgxj.js +99 -0
  140. package/dashboard/assets/eventmodeling-NTZA5JFV-po5oDydv.js +1 -0
  141. package/dashboard/assets/flowDiagram-HODETNUW-TSV9s7q4.js +1 -0
  142. package/dashboard/assets/ganttDiagram-EL5Y4UJY-DppD7sD7.js +292 -0
  143. package/dashboard/assets/gitGraph-4MIJSDKK-Tw0vNnQc.js +1 -0
  144. package/dashboard/assets/gitGraphDiagram-WWUBYQGX-Cpf96-z3.js +106 -0
  145. package/dashboard/assets/graphlib-DS17s2tU.js +1 -0
  146. package/dashboard/assets/info-A6RAGUB7-DLyt9arx.js +1 -0
  147. package/dashboard/assets/infoDiagram-27XIBGKW-B6_piSNS.js +2 -0
  148. package/dashboard/assets/init-C-OQMol4.js +1 -0
  149. package/dashboard/assets/ishikawaDiagram-5VMMS53U-DvMO0B0X.js +70 -0
  150. package/dashboard/assets/journeyDiagram-3NMN7TZE-f7ZK98b6.js +139 -0
  151. package/dashboard/assets/kanban-definition-UXKFOSKX-Dso0nAxL.js +89 -0
  152. package/dashboard/assets/katex-CXMH3UgJ.js +257 -0
  153. package/dashboard/assets/line-VcKaFG0G.js +1 -0
  154. package/dashboard/assets/linear-D7YnpiWh.js +1 -0
  155. package/dashboard/assets/mermaid-parser.core-DriL2hrV.js +7 -0
  156. package/dashboard/assets/mermaid.core-DJWYDh_P.js +44 -0
  157. package/dashboard/assets/mindmap-definition-YA3MSWOX-DZsoIeac.js +96 -0
  158. package/dashboard/assets/operations-BIaUSnYv.js +121 -0
  159. package/dashboard/assets/operations-BeCdkT4S.css +1 -0
  160. package/dashboard/assets/ordinal-BDEzSJ7C.js +1 -0
  161. package/dashboard/assets/packet-AYTQ26CC-BQBIXKxk.js +1 -0
  162. package/dashboard/assets/path-fybaL0A-.js +1 -0
  163. package/dashboard/assets/pegDiagram-XKGWAZYB-B0-lslgW.js +1 -0
  164. package/dashboard/assets/pie-WAS4IAKB-DoY6dwQs.js +1 -0
  165. package/dashboard/assets/pieDiagram-E7YTZNPT-ElmdRjp5.js +39 -0
  166. package/dashboard/assets/quadrantDiagram-AXDQQJYC-Ci1h98Q2.js +7 -0
  167. package/dashboard/assets/radar-RG4KPBEZ-DHod2WgB.js +1 -0
  168. package/dashboard/assets/railroad-74A4TZTK-90JxB4xV.js +1 -0
  169. package/dashboard/assets/railroad-abnf-HS5TGJTU-BkXGl114.js +1 -0
  170. package/dashboard/assets/railroad-ebnf-LZEXJU2U-QVAERn2C.js +1 -0
  171. package/dashboard/assets/railroad-peg-WCYAUIDC-B1-PM01z.js +1 -0
  172. package/dashboard/assets/railroadDiagram-O6MQD6OU-k3190A6L.js +1 -0
  173. package/dashboard/assets/requirementDiagram-BXWQKSXE-C3-eR7I5.js +84 -0
  174. package/dashboard/assets/rough.esm-Dy-Kn_BL.js +1 -0
  175. package/dashboard/assets/sankeyDiagram-P5KCCOFB-Bc86pi8w.js +40 -0
  176. package/dashboard/assets/sequenceDiagram-WJ2MYXX4-Bv_MvEmz.js +162 -0
  177. package/dashboard/assets/sizeCapture-INFHLROL-B0uUizjq.js +1 -0
  178. package/dashboard/assets/src-D7Dqq5yP.js +1 -0
  179. package/dashboard/assets/stateDiagram-D77RDMKH-Bq_GVmeW.js +1 -0
  180. package/dashboard/assets/stateDiagram-v2-MP3YSRHH-z1XRaftz.js +1 -0
  181. package/dashboard/assets/swimlanes-42K2YHIH-q0iK-e0P.js +1 -0
  182. package/dashboard/assets/swimlanesDiagram-VR7AAH4N-C9L6XBQj.js +8 -0
  183. package/dashboard/assets/timeline-definition-24CTP7MA-CNfF07hM.js +120 -0
  184. package/dashboard/assets/treeView-Q6P3EWNA-m629ePnS.js +1 -0
  185. package/dashboard/assets/treemap-WGGIJYW6-D9Cb2ETv.js +1 -0
  186. package/dashboard/assets/vennDiagram-4TSXK5OY-v4dKF-FE.js +34 -0
  187. package/dashboard/assets/wardley-WFR3VGLG-DkwH_nRe.js +1 -0
  188. package/dashboard/assets/wardleyDiagram-VM6X3IG4-3ImohVu4.js +78 -0
  189. package/dashboard/assets/xychartDiagram-S5SC5T6Z-QJu3dm7K.js +7 -0
  190. package/dashboard/assets/zero-connect-BGDY0PMZ.webp +0 -0
  191. package/dashboard/assets/zero-curious-DovTLOs8.webp +0 -0
  192. package/dashboard/assets/zero-peek-Dzm3MT3X.png +0 -0
  193. package/dashboard/assets/zero-planning-CBeHDrG_.webp +0 -0
  194. package/dashboard/favicon.ico +0 -0
  195. package/dashboard/favicon.svg +10 -0
  196. package/dashboard/index.html +5 -3
  197. package/package.json +1 -1
  198. package/chunks/chunk-BCSRRQG5.js +0 -3514
  199. package/chunks/chunk-BOUGKEKA.js +0 -193
  200. package/chunks/chunk-GHWODZMR.js +0 -16
  201. package/chunks/eval-runner-EMEGF24P.js +0 -26
  202. package/chunks/orchestrate-DWWUT5RC.js +0 -58
  203. package/dashboard/assets/bot-Bg1Tp9_d.js +0 -1
  204. package/dashboard/assets/dist-B_dDCPbu.js +0 -1
  205. package/dashboard/assets/findings-page-mayGhNx6.js +0 -5
  206. package/dashboard/assets/format--susmHX7.js +0 -1
  207. package/dashboard/assets/input-TuVPR3Zl.js +0 -1
  208. package/dashboard/assets/jsx-runtime-MGseXjj5.js +0 -3
  209. package/dashboard/assets/live-page-HWWHX99U.js +0 -1
  210. package/dashboard/assets/meta-tile-ckHo-w7k.js +0 -1
  211. package/dashboard/assets/operations-BxZ0OoXO.js +0 -10
  212. package/dashboard/assets/operations-CMFVrXEe.css +0 -2
  213. package/dashboard/assets/operations-app-D3d35rgo.js +0 -46
  214. package/dashboard/assets/overview-page-W_4IteEr.js +0 -1
  215. package/dashboard/assets/page-header-Bw9AJgBV.js +0 -1
  216. package/dashboard/assets/scans-page-CgzVXHnn.js +0 -1
  217. package/dashboard/assets/siren-KQj8QAnJ.js +0 -1
  218. package/dashboard/assets/table-8EDklulN.js +0 -1
  219. package/dashboard/assets/tabs-CFHPOwg8.js +0 -1
@@ -1,112 +1,1049 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire as __0CreateRequire } from "node:module";
3
3
  const require = __0CreateRequire(import.meta.url);
4
+ import {
5
+ DEFAULT_MAX_TOOL_ITERATIONS,
6
+ createConsoleRuntime,
7
+ createWorkbenchProviderBroker,
8
+ runSmolvmWorkbench,
9
+ snapshotSmolvmWorkspace
10
+ } from "./chunk-4HGGNBKK.js";
11
+ import {
12
+ getReleaseChannel
13
+ } from "./chunk-TPUJZ74W.js";
14
+ import {
15
+ TOOL_DEFINITIONS
16
+ } from "./chunk-EH2WTNY7.js";
4
17
  import {
5
18
  ANALYTICS_LEVEL_ENV,
6
- analyticsPipeline,
19
+ getSmolvmWorkbenchStatus,
20
+ isAdmittedSmolvmWorkbench,
7
21
  levelAtLeast,
8
22
  loadCloudCredentials,
9
23
  resolveAnalyticsLevel
10
- } from "./chunk-OHHCGDJ2.js";
24
+ } from "./chunk-NI6QYQSK.js";
25
+ import {
26
+ ScopePolicy,
27
+ aggregateCapabilities,
28
+ emptyEnablement,
29
+ getScopeEnforcementState,
30
+ listInstalledPluginIds,
31
+ loadableIds,
32
+ pluginsRootDir,
33
+ readEnablement,
34
+ readInstalledPlugin,
35
+ reconcile,
36
+ writeEnablement
37
+ } from "./chunk-ITES7BCY.js";
11
38
  import {
39
+ BackendIdSchema,
12
40
  DEFAULT_ALLOW_MODEL_SELF_EXTENSION,
13
- createPresentationEvent,
41
+ SERVICE_PLUGIN_CATALOG,
42
+ VERSION,
43
+ findingSchema,
14
44
  homeStateDir
15
- } from "./chunk-KAU6NLGP.js";
45
+ } from "./chunk-LTEE2JWK.js";
46
+
47
+ // packages/cli/src/backend-client-mode.ts
48
+ function remoteBackendClientId(args) {
49
+ if (!["workflow", "runs", "mcp-server"].includes(args[0] ?? "")) return void 0;
50
+ const selections = [];
51
+ const valueFlags = /* @__PURE__ */ new Set(["--template", "--revision", "--target", "--workspace", "--scope", "--model", "--inputs", "--time-cap", "--cost-cap", "--db-path", "--format", "--backends-config", "--tools", "--scan-id", "--timeout", "--rate-limit", "--engagement-profile"]);
52
+ for (let index = 1; index < args.length; index++) {
53
+ const argument = args[index];
54
+ if (argument === "--") break;
55
+ if (argument === "--backend") {
56
+ selections.push(args[++index]);
57
+ continue;
58
+ }
59
+ if (argument.startsWith("--backend=")) {
60
+ selections.push(argument.slice(10));
61
+ continue;
62
+ }
63
+ if (valueFlags.has(argument)) index++;
64
+ }
65
+ if (!selections.length) return void 0;
66
+ if (selections.length !== 1) throw new Error("Select exactly one registered remote backend.");
67
+ const selected = BackendIdSchema.parse(selections[0]);
68
+ if (selected === "local") throw new Error("--backend local is not a remote selection. Omit --backend to preserve configured local execution.");
69
+ return selected;
70
+ }
16
71
 
17
- // packages/cli/src/tui/settings-store.ts
18
- import { useSyncExternalStore } from "react";
72
+ // packages/cli/src/workbench.ts
73
+ import { chmodSync, existsSync as existsSync2, lstatSync as lstatSync2, mkdirSync as mkdirSync4, readFileSync as readFileSync3, realpathSync as realpathSync3, renameSync as renameSync2, writeFileSync as writeFileSync3 } from "node:fs";
74
+ import { homedir as homedir2 } from "node:os";
75
+ import { dirname as dirname5, isAbsolute as isAbsolute2, join as join8 } from "node:path";
19
76
 
20
- // packages/cli/src/tui/settings.ts
21
- import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, renameSync, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "node:fs";
77
+ // packages/cli/src/workbench-assets.ts
78
+ import { existsSync, lstatSync, realpathSync } from "node:fs";
79
+ import { dirname, join } from "node:path";
80
+ import { fileURLToPath } from "node:url";
81
+ function currentWorkbenchAssets(moduleUrl = import.meta.url, executablePath = process.execPath) {
82
+ let directory = dirname(fileURLToPath(moduleUrl));
83
+ for (let depth = 0; depth < 5; depth++) {
84
+ for (const candidate of [directory, join(directory, "dist")]) {
85
+ const entry = join(candidate, "0.js");
86
+ if (existsSync(entry) && lstatSync(entry).isFile() && !lstatSync(entry).isSymbolicLink()) return { cliDist: realpathSync(candidate) };
87
+ }
88
+ const parent = dirname(directory);
89
+ if (parent === directory) break;
90
+ directory = parent;
91
+ }
92
+ if (moduleUrl.includes("/$bunfs/")) {
93
+ const candidate = join(dirname(executablePath), "..", "lib", "workbench-cli");
94
+ const entry = join(candidate, "0.js");
95
+ if (existsSync(entry) && lstatSync(entry).isFile() && !lstatSync(entry).isSymbolicLink()) return { cliDist: realpathSync(candidate) };
96
+ }
97
+ throw new Error("Current workbench CLI distribution is unavailable. Build/install the current CLI bundle before isolated execution; an older image CLI is not used.");
98
+ }
99
+
100
+ // packages/cli/src/workbench-console-protocol.ts
101
+ import { isAbsolute, relative, resolve, sep } from "node:path";
102
+ var WORKBENCH_FRAME_BYTES = 8 * 1024 * 1024;
103
+ var WORKBENCH_MAX_PENDING = 64;
104
+ function encodeWorkbenchFrame(frame) {
105
+ const encoded = JSON.stringify(frame);
106
+ if (Buffer.byteLength(encoded) > WORKBENCH_FRAME_BYTES) throw new Error("Workbench protocol frame exceeds its byte limit");
107
+ return encoded + "\n";
108
+ }
109
+ var WorkbenchFrameReader = class {
110
+ #pending = "";
111
+ push(data, receive) {
112
+ this.#pending += data;
113
+ let newline;
114
+ while ((newline = this.#pending.indexOf("\n")) !== -1) {
115
+ const line = this.#pending.slice(0, newline);
116
+ this.#pending = this.#pending.slice(newline + 1);
117
+ if (!line || Buffer.byteLength(line) > WORKBENCH_FRAME_BYTES) throw new Error("Invalid workbench protocol frame size");
118
+ const frame = JSON.parse(line);
119
+ if (!frame || typeof frame !== "object" || Array.isArray(frame) || typeof frame.type !== "string") throw new Error("Invalid workbench protocol frame");
120
+ const parsed = frame;
121
+ if (parsed.id !== void 0 && (typeof parsed.id !== "string" || !/^[A-Za-z0-9_-]{1,80}$/.test(parsed.id))) throw new Error("Invalid workbench protocol request id");
122
+ receive(parsed);
123
+ }
124
+ if (Buffer.byteLength(this.#pending) > WORKBENCH_FRAME_BYTES) throw new Error("Workbench protocol input exceeds its byte limit");
125
+ }
126
+ };
127
+ function guestWorkspacePath(path, workspace) {
128
+ if (path === "/workspace" || path.startsWith("/workspace/")) {
129
+ const normalized = resolve(path);
130
+ if (normalized !== "/workspace" && !normalized.startsWith("/workspace/")) throw new Error("Invalid guest workspace path");
131
+ return normalized;
132
+ }
133
+ if (!isAbsolute(path)) throw new Error("Workbench scope requires an absolute host path");
134
+ const suffix = relative(workspace, path);
135
+ if (isAbsolute(suffix) || suffix === ".." || suffix.startsWith(`..${sep}`)) throw new Error("Requested host path is outside the granted workbench workspace");
136
+ return suffix ? `/workspace/${suffix.split(sep).join("/")}` : "/workspace";
137
+ }
138
+ function hostWorkspacePath(path, workspace) {
139
+ if (path === "/workspace" || path.startsWith("/workspace/")) {
140
+ const normalized = resolve(path);
141
+ if (normalized !== "/workspace" && !normalized.startsWith("/workspace/")) throw new Error("Invalid guest workspace path");
142
+ return resolve(workspace, relative("/workspace", normalized));
143
+ }
144
+ throw new Error("Guest local scope is outside the granted workspace");
145
+ }
146
+ function mapWorkbenchTarget(target, workspace, host = false) {
147
+ const map = host ? hostWorkspacePath : guestWorkspacePath;
148
+ if (isAbsolute(target)) return map(target, workspace);
149
+ if (target.startsWith("source:") && isAbsolute(target.slice(7))) return `source:${map(target.slice(7), workspace)}`;
150
+ return target;
151
+ }
152
+ function mapWorkbenchCheckpoint(checkpoint, workspace, host) {
153
+ const result = structuredClone(checkpoint);
154
+ const map = host ? hostWorkspacePath : guestWorkspacePath;
155
+ result.workspaceRoot = map(result.workspaceRoot, workspace);
156
+ result.target = mapWorkbenchTarget(result.target, workspace, host);
157
+ if (result.localScopePath) result.localScopePath = map(result.localScopePath, workspace);
158
+ result.deniedLocalPaths = result.deniedLocalPaths.map((path) => {
159
+ try {
160
+ return map(path, workspace);
161
+ } catch {
162
+ return path;
163
+ }
164
+ });
165
+ if (result.harnessRoot) {
166
+ throw new Error("Workbench checkpoint handoff with live executable resources is not supported");
167
+ }
168
+ return result;
169
+ }
170
+ function serializeWorkbenchConfig(config, workspace) {
171
+ for (const key of ["pluginHost", "mcpHost", "jevRuntime", "contribution", "developmentSourceRoot", "executablePlugins", "executableEvolutionProfiles"]) {
172
+ if (config[key] !== void 0) throw new Error(`Host ${key} cannot execute through the workbench controller`);
173
+ }
174
+ const result = { workspaceRoot: "/workspace", target: mapWorkbenchTarget(config.target ?? "", workspace) };
175
+ for (const key of ["role", "tools", "scanId", "maxToolIterations", "allowScanners", "systemPrompt", "autonomyMode", "refineObjective", "allowModelSelfExtension", "costModel", "contextWindowTokens", "compaction", "initialMessages"]) {
176
+ if (config[key] !== void 0) result[key] = structuredClone(config[key]);
177
+ }
178
+ if (config.maxTurnTokens !== void 0 && Number.isFinite(config.maxTurnTokens)) result.maxTurnTokens = config.maxTurnTokens;
179
+ if (config.scope) result.scope = config.scope.raw;
180
+ if (config.initialCheckpoint) result.initialCheckpoint = mapWorkbenchCheckpoint(config.initialCheckpoint, workspace, false);
181
+ if (config.agentMessaging && typeof config.agentMessaging === "object") {
182
+ const messaging = config.agentMessaging;
183
+ result.agentMessaging = {
184
+ selfId: messaging.selfId,
185
+ selfRole: messaging.selfRole,
186
+ parentId: messaging.parentId,
187
+ operatorId: messaging.operatorId,
188
+ siblingPrefix: messaging.siblingPrefix,
189
+ siblingChannelEnabled: messaging.siblingChannelEnabled,
190
+ operatorChannelEnabled: messaging.operatorChannelEnabled,
191
+ projectPath: "/workspace",
192
+ homeDir: "/home/zero"
193
+ };
194
+ }
195
+ return result;
196
+ }
197
+ function mapWorkbenchCliArguments(args, workspace) {
198
+ const targetFlags = /* @__PURE__ */ new Set(["--target", "-t", "--repo"]);
199
+ const pathFlags = /* @__PURE__ */ new Set(["--scope", "--db-path", "--workspace", "--workspace-root", "--cwd", "--output", "-o", "--config", "--inputs"]);
200
+ const result = [...args];
201
+ for (let index = 0; index < result.length; index++) {
202
+ const argument = result[index];
203
+ const equal = argument.indexOf("=");
204
+ const flag = equal < 0 ? argument : argument.slice(0, equal);
205
+ if (!targetFlags.has(flag) && !pathFlags.has(flag)) continue;
206
+ const valueIndex = equal < 0 ? index + 1 : index;
207
+ const value = equal < 0 ? result[valueIndex] : argument.slice(equal + 1);
208
+ if (value === void 0) throw new Error(`Missing workbench argument for ${flag}`);
209
+ const mapped = targetFlags.has(flag) ? mapWorkbenchTarget(value, workspace) : isAbsolute(value) ? guestWorkspacePath(value, workspace) : value;
210
+ result[valueIndex] = equal < 0 ? mapped : `${flag}=${mapped}`;
211
+ if (equal < 0) index++;
212
+ }
213
+ return result;
214
+ }
215
+
216
+ // packages/cli/src/workbench-console-session.ts
22
217
  import { randomUUID } from "node:crypto";
23
- import { dirname as dirname2, join as join3 } from "node:path";
218
+ import { realpathSync as realpathSync2 } from "node:fs";
219
+ import { mkdir, mkdtemp as mkdtemp2, realpath as realpath2 } from "node:fs/promises";
220
+ import { join as join3 } from "node:path";
24
221
 
25
- // packages/cli/src/tui/keybindings.ts
26
- var KEYBINDINGS = [
27
- // ── Composer ──────────────────────────────────────────────────────────────
222
+ // packages/cli/src/workbench-plugins.ts
223
+ import { lstat, mkdtemp, realpath, rm } from "node:fs/promises";
224
+ import { join as join2 } from "node:path";
225
+ var GUEST_PLUGIN_ASSETS = "/opt/0-approved-plugins";
226
+ var reservedToolNames = Object.values(TOOL_DEFINITIONS).map((tool) => tool.name);
227
+ var bounds = { maxBytes: 64 * 1024 * 1024, maxFiles: 4096 };
228
+ async function prepareWorkbenchPlugins(project, homeDir) {
229
+ const root = pluginsRootDir(homeDir);
230
+ const record = readEnablement(project, homeDir);
231
+ const views = listInstalledPluginIds(root).flatMap((id) => {
232
+ const discovered = readInstalledPlugin(root, id, { reservedToolNames });
233
+ return discovered.ok ? [{ id, version: discovered.plugin.manifest.version, capabilities: aggregateCapabilities(discovered.plugin.manifest) }] : [];
234
+ });
235
+ const ids = loadableIds(reconcile(record, views));
236
+ const approvals = emptyEnablement("/workspace");
237
+ if (!ids.length) return { approvals, cleanup: async () => {
238
+ } };
239
+ const directory = await realpath(await mkdtemp("/tmp/0-guest-plugins-"));
240
+ const cleanup = () => rm(directory, { recursive: true, force: true });
241
+ try {
242
+ let bytes = 0, files = 0;
243
+ for (const id of ids) {
244
+ if ((await lstat(join2(root, id))).isSymbolicLink()) throw new Error("Workbench plugins cannot use linked source directories");
245
+ const snapshot = await snapshotSmolvmWorkspace(join2(root, id), join2(directory, id), { maxBytes: Math.max(1, bounds.maxBytes - bytes), maxFiles: Math.max(1, bounds.maxFiles - files) }, void 0, false, false);
246
+ bytes += snapshot.bytes;
247
+ files += snapshot.files.length;
248
+ if (bytes > bounds.maxBytes || files > bounds.maxFiles) throw new Error("Approved plugins exceed the workbench plugin limits");
249
+ const copied = readInstalledPlugin(directory, id, { reservedToolNames });
250
+ if (!copied.ok || !loadableIds(reconcile(record, [{ id, version: copied.plugin.manifest.version, capabilities: aggregateCapabilities(copied.plugin.manifest) }])).includes(id)) throw new Error(`Plugin ${id} changed during workbench preparation; approve its current permissions first`);
251
+ approvals.enabled[id] = record.enabled[id];
252
+ }
253
+ return { directory, approvals, cleanup };
254
+ } catch (error) {
255
+ await cleanup();
256
+ throw error;
257
+ }
258
+ }
259
+ async function installWorkbenchPlugins(approvals) {
260
+ if (!isAdmittedSmolvmWorkbench()) throw new Error("Plugin installation requires admitted VM execution");
261
+ const ids = Object.keys(approvals.enabled);
262
+ for (const id of ids) {
263
+ const discovered = readInstalledPlugin(GUEST_PLUGIN_ASSETS, id, { reservedToolNames });
264
+ if (!discovered.ok || !loadableIds(reconcile(approvals, [{ id, version: discovered.plugin.manifest.version, capabilities: aggregateCapabilities(discovered.plugin.manifest) }])).includes(id)) throw new Error(`Invalid approved guest plugin: ${id}`);
265
+ await snapshotSmolvmWorkspace(join2(GUEST_PLUGIN_ASSETS, id), join2(pluginsRootDir(), id), bounds, void 0, false, false);
266
+ }
267
+ if (ids.length && !writeEnablement("/workspace", { ...approvals, project: "/workspace" })) throw new Error("Could not save guest plugin approvals");
268
+ }
269
+
270
+ // packages/cli/src/workbench-service-plugins.ts
271
+ function validateGuestServicePluginConnections(raw, networkGranted) {
272
+ if (raw === void 0) return [];
273
+ if (!Array.isArray(raw) || raw.length > SERVICE_PLUGIN_CATALOG.length) throw new Error("Invalid guest service plugin grants");
274
+ const ids = /* @__PURE__ */ new Set();
275
+ const connections = raw.map((value) => {
276
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("Invalid guest service plugin grant");
277
+ const row = value;
278
+ const plugin = SERVICE_PLUGIN_CATALOG.find((item) => item.id === row.id);
279
+ if (!plugin || ids.has(plugin.id) || row.enabled !== true || !row.fields || typeof row.fields !== "object" || Array.isArray(row.fields)) throw new Error("Invalid guest service plugin grant");
280
+ ids.add(plugin.id);
281
+ const fields = row.fields;
282
+ if (Object.keys(fields).some((key) => !plugin.fields.some((field) => field.key === key))) throw new Error("Invalid guest service plugin field");
283
+ const output = {};
284
+ for (const field of plugin.fields) {
285
+ const value2 = fields[field.key];
286
+ if (value2 === void 0 && !field.required) continue;
287
+ if (typeof value2 !== "string" || !value2.trim() || value2.length > 8192 || /[\x00-\x1f\x7f]/.test(value2)) throw new Error("Invalid guest service plugin field");
288
+ if (field.type === "url") {
289
+ let url;
290
+ try {
291
+ url = new URL(value2);
292
+ } catch {
293
+ throw new Error("Invalid guest service plugin URL");
294
+ }
295
+ if (url.username || url.password || url.search || url.hash || url.protocol !== "https:" && !(url.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname))) throw new Error("Invalid guest service plugin URL");
296
+ }
297
+ output[field.key] = value2;
298
+ }
299
+ return { id: plugin.id, enabled: true, fields: output };
300
+ });
301
+ if (connections.length && networkGranted !== true) throw new Error("Service plugins require the existing SmolVM network grant. Enable workbench networking before using connected plugins.");
302
+ return connections;
303
+ }
304
+ function servicePluginSecretRedactor(connections) {
305
+ const secrets = connections.flatMap((connection) => {
306
+ const plugin = SERVICE_PLUGIN_CATALOG.find((item) => item.id === connection.id);
307
+ const values = plugin.fields.filter((field) => field.type === "secret").map((field) => connection.fields[field.key]).filter((value) => !!value);
308
+ if (connection.id === "jira" && connection.fields.email && connection.fields.token) values.push(Buffer.from(`${connection.fields.email}:${connection.fields.token}`).toString("base64"));
309
+ return values.flatMap((value) => [value, encodeURIComponent(value), JSON.stringify(value).slice(1, -1)]);
310
+ }).sort((a, b) => b.length - a.length);
311
+ return (value) => secrets.reduce((text, secret) => text.split(secret).join("[redacted]"), value);
312
+ }
313
+
314
+ // packages/cli/src/workbench-console-session.ts
315
+ var EVENTS = /* @__PURE__ */ new Set(["onHarnessUpdate", "onAssistantDelta", "onReasoningDelta", "onToolStart", "onToolResult", "onUsage", "onNotice", "onCompaction"]);
316
+ var DECISIONS = /* @__PURE__ */ new Set(["requestScope", "requestLocalScope", "approveTool", "escalateScopedAudit", "askOperator", "historyList", "historyRead"]);
317
+ function validateOptions(options) {
318
+ if (options.provider.provider !== "chatgpt-codex" || options.selection.provider && options.selection.provider !== "chatgpt-codex" || !options.provider.models.includes(options.selection.model)) throw new Error("Workbench requires an explicit host provider/model grant");
319
+ for (const model of Object.values(options.selection.agentModels ?? {})) if (model !== "auto" && !options.provider.models.includes(model)) throw new Error("Workbench worker model is outside the host provider grant");
320
+ for (const [name, value, max] of [["lifetimeMs", options.lifetimeMs ?? 30 * 6e4, 60 * 6e4], ["idleMs", options.idleMs ?? 5 * 6e4, 15 * 6e4]]) if (!Number.isSafeInteger(value) || value < 1e3 || value > max) throw new Error(`Invalid workbench ${name}`);
321
+ }
322
+ function guestCommand(options, cli) {
323
+ if (options.guestCommand) return { command: options.guestCommand };
324
+ const entry = cli ? "run-agent" : "console-agent";
325
+ if (!options.assets) return { command: ["/usr/local/bin/0", "--workbench-inner", "workbench", entry] };
326
+ const dependencies = options.assets.dependencies ? "/opt/0-controller-deps/node_modules" : "/opt/0/node_modules";
327
+ const script = `const fs=require('node:fs');const path='/tmp/0-controller-cli';fs.cpSync('/opt/0-controller-cli',path,{recursive:true});fs.symlinkSync(${JSON.stringify(dependencies)},path+'/node_modules');const child=require('node:child_process').spawn(process.execPath,[path+'/0.js','--workbench-inner','workbench',${JSON.stringify(entry)}],{stdio:'inherit',env:process.env});child.once('error',e=>{console.error(e.message);process.exit(1)});child.once('exit',code=>process.exit(code??1));`;
328
+ return { command: ["/usr/local/bin/node", "--eval", script], mounts: [{ source: options.assets.cliDist, target: "/opt/0-controller-cli" }, ...options.assets.dependencies ? [{ source: options.assets.dependencies, target: "/opt/0-controller-deps/node_modules" }] : []] };
329
+ }
330
+ var Controller = class {
331
+ constructor(options, init, cli) {
332
+ this.options = options;
333
+ this.init = init;
334
+ this.cli = cli;
335
+ validateOptions(options);
336
+ this.workspace = realpathSync2(options.workbench.workspaceRoot ?? process.cwd());
337
+ this.execution = { backend: "smolvm", status: "pending", runId: this.runId, workspacePath: this.workspace, guestWorkspacePath: "/workspace", imageDigest: options.workbench.imageDigest, cpus: options.workbench.cpus, memoryMb: options.workbench.memoryMb };
338
+ this.ready = new Promise((resolve2, reject) => {
339
+ this.resolveReady = resolve2;
340
+ this.rejectReady = reject;
341
+ });
342
+ void this.ready.catch(() => {
343
+ });
344
+ }
345
+ options;
346
+ init;
347
+ cli;
348
+ abort = new AbortController();
349
+ runId = randomUUID();
350
+ workspace;
351
+ execution;
352
+ ready;
353
+ done;
354
+ resolveReady;
355
+ rejectReady;
356
+ input;
357
+ pending = /* @__PURE__ */ new Map();
358
+ providers = /* @__PURE__ */ new Map();
359
+ idle;
360
+ lifetime;
361
+ started = false;
362
+ guestReady = false;
363
+ startupStderr = "";
364
+ redact = (value) => value;
365
+ receive = () => {
366
+ };
367
+ decision = async () => null;
368
+ status(status, message) {
369
+ const { message: _previous, ...snapshot } = this.execution;
370
+ this.execution = { ...snapshot, status, ...message ? { message } : {} };
371
+ this.options.onExecution?.(this.execution);
372
+ }
373
+ write(frame) {
374
+ if (!this.input || this.abort.signal.aborted) throw new Error("Workbench VM is unavailable");
375
+ this.input.write(encodeWorkbenchFrame(frame));
376
+ }
377
+ touch() {
378
+ clearTimeout(this.idle);
379
+ this.idle = setTimeout(() => {
380
+ this.abort.abort(new Error("Workbench idle deadline expired"));
381
+ }, this.options.idleMs ?? 5 * 6e4);
382
+ this.idle.unref();
383
+ }
384
+ async start() {
385
+ if (this.started) return this.ready;
386
+ this.started = true;
387
+ this.status("pending", "Starting isolated workbench");
388
+ let stagedPlugins;
389
+ this.lifetime = setTimeout(() => this.abort.abort(new Error("Workbench lifetime expired")), this.options.lifetimeMs ?? 30 * 6e4);
390
+ this.lifetime.unref();
391
+ try {
392
+ const root = join3(this.options.workbench.stateRoot, "controller-results");
393
+ await mkdir(root, { recursive: true, mode: 448 });
394
+ const artifacts = this.options.artifactDirectory ?? await realpath2(await mkdtemp2(join3(root, "run-")));
395
+ const reader = new WorkbenchFrameReader();
396
+ const launch = guestCommand(this.options, this.cli);
397
+ const connections = this.cli ? [] : (await import("./service-plugins-QYFITODK.js")).loadServicePluginConnections(this.options.pluginHomeDir).filter((item) => item.enabled);
398
+ const servicePluginConnections = validateGuestServicePluginConnections(connections, this.options.network);
399
+ this.redact = servicePluginSecretRedactor(servicePluginConnections);
400
+ const plugins = stagedPlugins = await prepareWorkbenchPlugins(this.workspace, this.options.pluginHomeDir);
401
+ this.done = runSmolvmWorkbench({
402
+ image: this.options.workbench.image,
403
+ stateRoot: this.options.workbench.stateRoot,
404
+ workspaceRoot: this.workspace,
405
+ command: launch.command,
406
+ environment: { ZERO_PROVIDER: "chatgpt-codex", ZERO_NO_TELEMETRY: "1", DO_NOT_TRACK: "1" },
407
+ network: this.options.network,
408
+ tty: false,
409
+ cpus: this.options.workbench.cpus,
410
+ memoryMb: this.options.workbench.memoryMb,
411
+ storageGb: this.options.workbench.storageGb,
412
+ approvedImages: this.options.workbench.approvedImages,
413
+ signal: this.abort.signal,
414
+ workspaceMode: "snapshot",
415
+ artifactDirectory: artifacts,
416
+ readOnlyMounts: [...launch.mounts ?? [], ...plugins.directory ? [{ source: plugins.directory, target: GUEST_PLUGIN_ASSETS }] : []],
417
+ transport: { initialInput: encodeWorkbenchFrame({ type: "init", ...this.init, servicePluginConnections, servicePluginNetworkGranted: this.options.network, pluginApprovals: plugins.approvals, guestSettings: this.options.guestSettings, selection: { ...this.options.selection, provider: "chatgpt-codex" } }), onReady: (input) => {
418
+ this.input = input;
419
+ }, onStdout: (data) => reader.push(data, (frame) => this.dispatch(frame)), onStderr: (data) => {
420
+ this.startupStderr = this.redact(this.startupStderr + data).slice(-4096);
421
+ } }
422
+ });
423
+ this.done = this.done.finally(() => plugins.cleanup());
424
+ void this.done.then(async (result) => {
425
+ const error = new Error(this.redact(result.error ?? "Workbench VM exited") + (!this.guestReady && this.startupStderr.trim() ? `: ${this.startupStderr.trim()}` : ""));
426
+ if (!this.guestReady) this.rejectReady(error);
427
+ for (const pending of this.pending.values()) pending.reject(error);
428
+ this.pending.clear();
429
+ clearTimeout(this.idle);
430
+ clearTimeout(this.lifetime);
431
+ for (const pending of this.providers.values()) pending.abort();
432
+ this.providers.clear();
433
+ await this.options.provider.close?.();
434
+ this.status(result.cleanupFailed || result.exitCode !== 0 && !this.abort.signal.aborted ? "failed" : "stopped", result.error ? this.redact(result.error) : void 0);
435
+ });
436
+ this.touch();
437
+ await this.ready;
438
+ } catch (error) {
439
+ if (!this.done) {
440
+ await stagedPlugins?.cleanup();
441
+ await this.options.provider.close?.();
442
+ }
443
+ clearTimeout(this.idle);
444
+ clearTimeout(this.lifetime);
445
+ this.abort.abort();
446
+ this.rejectReady(new Error(this.redact(error instanceof Error ? error.message : "Workbench startup failed")));
447
+ this.status("failed", this.redact(error instanceof Error ? error.message : "Workbench startup failed"));
448
+ throw new Error(this.redact(error instanceof Error ? error.message : "Workbench startup failed"));
449
+ }
450
+ }
451
+ dispatch(frame) {
452
+ if (frame.type === "ready") {
453
+ if (this.guestReady || frame.platform !== "linux" || frame.workspace !== "/workspace") throw new Error("Invalid workbench guest readiness");
454
+ this.guestReady = true;
455
+ this.status("ready");
456
+ this.resolveReady();
457
+ return;
458
+ }
459
+ if (frame.type === "provider") {
460
+ void this.provider(frame);
461
+ return;
462
+ }
463
+ if (frame.type === "provider-cancel") {
464
+ this.providers.get(frame.id)?.abort();
465
+ return;
466
+ }
467
+ if (frame.type === "decision") {
468
+ if (!DECISIONS.has(String(frame.name)) || !Array.isArray(frame.args)) throw new Error("Invalid guest decision callback");
469
+ void this.decision(String(frame.name), frame.args).then((value) => this.write({ type: "decision-result", id: frame.id, value }), () => this.write({ type: "decision-result", id: frame.id, error: "Host decision unavailable" })).catch(() => {
470
+ });
471
+ return;
472
+ }
473
+ if (frame.type === "result" || frame.type === "error") {
474
+ const pending = this.pending.get(frame.id);
475
+ this.pending.delete(frame.id);
476
+ if (frame.type === "error") {
477
+ const error = new Error(typeof frame.error === "string" ? this.redact(frame.error) : "Guest operation failed");
478
+ if (!this.guestReady) this.rejectReady(error);
479
+ pending?.reject(error);
480
+ } else pending?.resolve(frame.value);
481
+ return;
482
+ }
483
+ this.receive(frame);
484
+ }
485
+ async provider(frame) {
486
+ if (!frame.id || this.providers.size >= 4 || this.providers.has(frame.id)) {
487
+ this.abort.abort(new Error("Invalid guest provider request"));
488
+ return;
489
+ }
490
+ const envelope = frame.envelope;
491
+ const abort = new AbortController();
492
+ this.providers.set(frame.id, abort);
493
+ const cancel = () => abort.abort();
494
+ this.abort.signal.addEventListener("abort", cancel, { once: true });
495
+ try {
496
+ if (!envelope || envelope.provider !== "chatgpt-codex" || !this.options.provider.models.includes(envelope.model) || typeof envelope.body !== "string") throw new Error("Invalid guest provider envelope");
497
+ const response = await this.options.provider.request({ provider: envelope.provider, model: envelope.model, body: envelope.body }, abort.signal);
498
+ this.write({ type: "provider-headers", id: frame.id, status: response.status, contentType: response.headers.get("content-type") ?? "text/event-stream" });
499
+ let bytes = 0;
500
+ if (response.body) {
501
+ const reader = response.body.getReader();
502
+ try {
503
+ while (true) {
504
+ const next = await reader.read();
505
+ if (next.done) break;
506
+ const chunk = next.value;
507
+ bytes += chunk.length;
508
+ if (bytes > 16 * 1024 * 1024) throw new Error("Provider response exceeds controller limit");
509
+ for (let offset = 0; offset < chunk.length; offset += 49152) this.write({ type: "provider-chunk", id: frame.id, data: Buffer.from(chunk.subarray(offset, offset + 49152)).toString("base64") });
510
+ }
511
+ } finally {
512
+ await reader.cancel().catch(() => {
513
+ });
514
+ reader.releaseLock();
515
+ }
516
+ }
517
+ this.write({ type: "provider-end", id: frame.id });
518
+ } catch {
519
+ try {
520
+ this.write({ type: "provider-error", id: frame.id });
521
+ } catch {
522
+ }
523
+ } finally {
524
+ this.providers.delete(frame.id);
525
+ this.abort.signal.removeEventListener("abort", cancel);
526
+ }
527
+ }
528
+ async request(op, values = {}) {
529
+ await this.start();
530
+ if (this.pending.size >= WORKBENCH_MAX_PENDING) throw new Error("Workbench request limit reached");
531
+ this.touch();
532
+ const id = randomUUID();
533
+ const result = new Promise((resolve2, reject) => this.pending.set(id, { resolve: resolve2, reject }));
534
+ try {
535
+ this.write({ type: "request", id, op, ...values });
536
+ } catch (error) {
537
+ this.pending.delete(id);
538
+ throw error;
539
+ }
540
+ return result;
541
+ }
542
+ async close() {
543
+ if (!this.started) {
544
+ await this.options.provider.close?.();
545
+ return;
546
+ }
547
+ let exited = false;
548
+ try {
549
+ await Promise.race([this.request("close"), new Promise((resolve2) => setTimeout(resolve2, 1e3))]);
550
+ await Promise.race([this.done?.then(() => {
551
+ exited = true;
552
+ }), new Promise((resolve2) => setTimeout(resolve2, 1e3))]);
553
+ } catch {
554
+ } finally {
555
+ if (!exited) this.abort.abort();
556
+ const result = await this.done;
557
+ if (result?.cleanupFailed) throw new Error(result.error ?? "Workbench teardown could not be confirmed");
558
+ }
559
+ }
560
+ };
561
+ function createWorkbenchConsoleSession(options) {
562
+ const workspace = realpathSync2(options.workbench.workspaceRoot ?? options.config.workspaceRoot ?? process.cwd());
563
+ const config = { ...options.config, scanId: options.config.scanId ?? `console-${randomUUID()}` };
564
+ const serial = serializeWorkbenchConfig(config, workspace);
565
+ const callbacks = [...DECISIONS].filter((name) => name === "historyList" || name === "historyRead" ? !!config.conversationHistory : typeof config[name] === "function");
566
+ const scopeEnforcement = getScopeEnforcementState(workspace);
567
+ const controller = new Controller({ ...options, workbench: { ...options.workbench, workspaceRoot: workspace } }, { config: serial, callbacks, scopeEnforcement }, false);
568
+ let messages = structuredClone(config.initialMessages ?? config.initialCheckpoint?.messages ?? []);
569
+ let checkpoint = config.initialCheckpoint;
570
+ let target = config.target ?? "";
571
+ let scope = config.scope;
572
+ let localScopePath = config.initialCheckpoint?.localScopePath ?? void 0;
573
+ let autonomyMode = config.autonomyMode ?? "standard";
574
+ let systemPrompt = config.systemPrompt ?? "";
575
+ let tools = config.tools ?? [];
576
+ let render;
577
+ let active = false;
578
+ let findings = [];
579
+ let engineWorkStarted = false;
580
+ let lastOutcome;
581
+ let cleanupPromise;
582
+ const deferred = [];
583
+ const queue = (op, value) => {
584
+ if (!controller.done) {
585
+ serial[op === "autonomy" ? "autonomyMode" : op] = value;
586
+ return;
587
+ }
588
+ const pending = controller.request(op, { value });
589
+ deferred.push(pending);
590
+ void pending.catch(() => {
591
+ });
592
+ };
593
+ controller.decision = async (name, args) => {
594
+ if (name === "historyList") return config.conversationHistory.list(...args);
595
+ if (name === "historyRead") return config.conversationHistory.read(...args);
596
+ const callback = config[name];
597
+ if (!callback || !callbacks.includes(name)) return null;
598
+ if (name === "requestLocalScope") {
599
+ const request = structuredClone(args[0]);
600
+ for (const key of ["requestedPath", "currentScopePath"]) if (typeof request[key] === "string") request[key] = hostWorkspacePath(request[key], workspace);
601
+ const value = await callback(request);
602
+ if (!value) return null;
603
+ const resolution = value;
604
+ return { scopePath: guestWorkspacePath(resolution.scopePath, workspace) };
605
+ }
606
+ if (name === "requestScope") {
607
+ const request = args[0];
608
+ const value = await callback({ ...request, target: mapWorkbenchTarget(String(request.target ?? ""), workspace, true), ...request.currentScope ? { currentScope: new ScopePolicy(request.currentScope.raw) } : {} });
609
+ if (!value) return null;
610
+ const resolution = value;
611
+ return { target: mapWorkbenchTarget(resolution.target, workspace), scope: resolution.scope.raw };
612
+ }
613
+ return callback(...args);
614
+ };
615
+ controller.receive = (frame) => {
616
+ if (frame.type === "state") {
617
+ const state = frame.snapshot;
618
+ if (!state || state.scanId !== config.scanId || !Array.isArray(state.messages) || state.messages.length > 1e4 || !Array.isArray(state.tools)) throw new Error("Invalid guest session state");
619
+ messages = state.messages;
620
+ systemPrompt = String(state.systemPrompt ?? "");
621
+ tools = state.tools;
622
+ target = mapWorkbenchTarget(String(state.target ?? ""), workspace, true);
623
+ scope = state.scope ? new ScopePolicy(state.scope) : void 0;
624
+ localScopePath = state.localScopePath ? hostWorkspacePath(String(state.localScopePath), workspace) : void 0;
625
+ autonomyMode = state.autonomyMode;
626
+ checkpoint = state.checkpoint ? mapWorkbenchCheckpoint(state.checkpoint, workspace, true) : void 0;
627
+ } else if (frame.type === "event") {
628
+ if (!EVENTS.has(String(frame.name)) || !Array.isArray(frame.args)) throw new Error("Invalid guest render callback");
629
+ const callback = render?.[frame.name];
630
+ callback?.(...frame.args);
631
+ } else if (frame.type === "findings") {
632
+ if (!Array.isArray(frame.findings) || frame.findings.length > 1e3) throw new Error("Invalid guest findings");
633
+ findings = frame.findings.map((finding) => findingSchema.parse(finding));
634
+ } else throw new Error("Unsupported guest controller event");
635
+ };
636
+ return {
637
+ scanId: config.scanId,
638
+ ready: Promise.resolve(),
639
+ get execution() {
640
+ return controller.execution;
641
+ },
642
+ get systemPrompt() {
643
+ return systemPrompt;
644
+ },
645
+ get tools() {
646
+ return tools;
647
+ },
648
+ get messages() {
649
+ return messages;
650
+ },
651
+ get target() {
652
+ return target;
653
+ },
654
+ get scope() {
655
+ return scope;
656
+ },
657
+ get localScopePath() {
658
+ return localScopePath;
659
+ },
660
+ get autonomyMode() {
661
+ return autonomyMode;
662
+ },
663
+ scopeEnforcement,
664
+ setAutonomyMode(mode) {
665
+ autonomyMode = mode;
666
+ queue("autonomy", mode);
667
+ },
668
+ configureEngagement(selection) {
669
+ if (selection.target !== void 0) target = selection.target;
670
+ if (selection.scope !== void 0) scope = selection.scope ?? void 0;
671
+ const value = { ...selection.target === void 0 ? {} : { target: mapWorkbenchTarget(selection.target, workspace) }, ...selection.scope === void 0 ? {} : { scope: selection.scope?.raw ?? null } };
672
+ if (!controller.done) Object.assign(serial, value);
673
+ else queue("configure", value);
674
+ },
675
+ configureWorkspace(path) {
676
+ if (realpathSync2(path) !== workspace) throw new Error("The SmolVM workspace grant is fixed for this chat; start a new chat to change it");
677
+ },
678
+ reconfigureRuntime(selection) {
679
+ if (selection.env !== void 0 || selection.provider && selection.provider !== "chatgpt-codex" || selection.model && !options.provider.models.includes(selection.model) || Object.values(selection.agentModels ?? {}).some((model) => model !== "auto" && !options.provider.models.includes(model))) throw new Error("Runtime selection is outside workbench provider grant");
680
+ Object.assign(options.selection, selection);
681
+ if (controller.done) queue("reconfigure", selection);
682
+ },
683
+ clearConversation() {
684
+ messages = [];
685
+ checkpoint = void 0;
686
+ if (!controller.done) {
687
+ serial.initialMessages = [];
688
+ delete serial.initialCheckpoint;
689
+ } else queue("clear");
690
+ },
691
+ async send(text, callbacks2, sendOptions) {
692
+ if (active) throw new Error("Workbench turn already active");
693
+ active = true;
694
+ let partialText = "";
695
+ const partialTools = [];
696
+ const partialUsage = { inputTokens: 0, outputTokens: 0 };
697
+ let usageReport;
698
+ render = {
699
+ ...callbacks2,
700
+ onAssistantDelta: (text2) => {
701
+ partialText += text2;
702
+ callbacks2?.onAssistantDelta?.(text2);
703
+ },
704
+ onToolResult: (call, result) => {
705
+ partialTools.push({ call, result });
706
+ callbacks2?.onToolResult?.(call, result);
707
+ },
708
+ onUsage: (usage) => {
709
+ partialUsage.inputTokens += usage.inputTokens;
710
+ partialUsage.outputTokens += usage.outputTokens;
711
+ usageReport = usage;
712
+ callbacks2?.onUsage?.(usage);
713
+ }
714
+ };
715
+ const messageCountBeforeTurn = messages.length;
716
+ let cancelTimer;
717
+ const cancel = () => {
718
+ if (controller.execution.status === "pending") {
719
+ controller.abort.abort(new Error("Operator cancelled workbench startup"));
720
+ return;
721
+ }
722
+ try {
723
+ controller.write({ type: "cancel" });
724
+ } catch {
725
+ }
726
+ cancelTimer ??= setTimeout(() => controller.abort.abort(new Error("Operator cancelled workbench turn")), 2e3);
727
+ };
728
+ sendOptions?.signal?.addEventListener("abort", cancel, { once: true });
729
+ try {
730
+ if (sendOptions?.signal?.aborted) cancel();
731
+ await Promise.all(deferred.splice(0));
732
+ await controller.start();
733
+ sendOptions?.signal?.throwIfAborted();
734
+ controller.status("running");
735
+ engineWorkStarted = true;
736
+ lastOutcome = await controller.request("send", { text, generateTitle: sendOptions?.generateTitle });
737
+ return lastOutcome;
738
+ } catch (error) {
739
+ if (sendOptions?.signal?.aborted) {
740
+ const result = await controller.done;
741
+ if ((!result || !result.cleanupFailed) && controller.abort.signal.aborted) {
742
+ controller.status("stopped");
743
+ if (messages.length === messageCountBeforeTurn) messages.push({ role: "user", content: [{ type: "text", text }] });
744
+ if (partialText && messages.at(-1)?.role !== "assistant") messages.push({ role: "assistant", content: [{ type: "text", text: partialText }] });
745
+ return lastOutcome = { assistantText: partialText, toolCalls: partialTools, usage: partialUsage, budget: { tokensUsed: usageReport?.turnTokensUsed ?? partialUsage.inputTokens + partialUsage.outputTokens, tokenBudget: usageReport?.turnTokenBudget ?? options.config.maxTurnTokens ?? Infinity, iterations: usageReport?.iterations ?? 0, maxToolIterations: usageReport?.maxToolIterations ?? options.config.maxToolIterations ?? DEFAULT_MAX_TOOL_ITERATIONS }, stopReason: "cancelled" };
746
+ }
747
+ }
748
+ throw error;
749
+ } finally {
750
+ clearTimeout(cancelTimer);
751
+ active = false;
752
+ render = void 0;
753
+ sendOptions?.signal?.removeEventListener("abort", cancel);
754
+ if (!controller.abort.signal.aborted && controller.execution.status !== "stopped" && controller.execution.status !== "failed") controller.status("ready");
755
+ }
756
+ },
757
+ async stopPersistentAgent(agentId) {
758
+ return await controller.request("stopWorker", { agentId });
759
+ },
760
+ async stopPersistentAgents() {
761
+ if (controller.done) await controller.request("stopWorkers");
762
+ },
763
+ exportCheckpoint() {
764
+ if (active || !checkpoint) throw new Error("No quiescent VM checkpoint is available");
765
+ return structuredClone(checkpoint);
766
+ },
767
+ async prepareHandoff() {
768
+ if (active) throw new Error("Cannot hand off active VM turn");
769
+ if (!controller.done) return {};
770
+ return await controller.request("handoff");
771
+ },
772
+ cleanup() {
773
+ return cleanupPromise ??= (async () => {
774
+ await controller.close();
775
+ if (engineWorkStarted && controller.done) await options.onFindings?.(findings, { outcome: lastOutcome });
776
+ })();
777
+ }
778
+ };
779
+ }
780
+ async function runWorkbenchCli(options) {
781
+ if (options.args.length > 512 || options.args.some((arg) => typeof arg !== "string" || arg.includes("\0"))) throw new Error("Invalid workbench CLI argv");
782
+ const controller = new Controller(options, { args: options.args }, true);
783
+ let exitCode;
784
+ controller.receive = (frame) => {
785
+ if ((frame.type === "stdout" || frame.type === "stderr") && typeof frame.data === "string") (frame.type === "stdout" ? options.onStdout : options.onStderr)?.(Buffer.from(frame.data, "base64").toString("utf8"));
786
+ else if (frame.type === "exit" && Number.isSafeInteger(frame.code)) {
787
+ exitCode = frame.code;
788
+ } else throw new Error("Invalid CLI guest event");
789
+ };
790
+ const cancel = () => controller.abort.abort();
791
+ options.signal?.addEventListener("abort", cancel, { once: true });
792
+ try {
793
+ if (options.signal?.aborted) cancel();
794
+ await controller.start();
795
+ controller.status("running");
796
+ const result = await controller.done;
797
+ return { ...result, exitCode: exitCode ?? result.exitCode };
798
+ } finally {
799
+ options.signal?.removeEventListener("abort", cancel);
800
+ }
801
+ }
802
+
803
+ // packages/cli/src/tui/provider-status.ts
804
+ var PROVIDER_DEFS = [
28
805
  {
29
- id: "composer.send",
30
- keys: "Enter",
31
- defaultChords: ["return"],
32
- rebindable: false,
33
- description: "Send the message or run the highlighted slash command; queues the line when a turn is already in flight.",
34
- category: "Composer",
35
- lockReason: "text entry",
36
- handler: 'if (key.name === "return") { \u2026 send / queue / dequeue }'
806
+ id: "chatgpt-codex",
807
+ label: "ChatGPT Codex",
808
+ methods: ["oauth"],
809
+ // OAuth, not an API key. Both tokens are accepted and the access token is
810
+ // read first (llm-api.ts L874-875, L1386-1394), so it leads the list.
811
+ envVars: ["ZERO_CHATGPT_ACCESS_TOKEN", "ZERO_CHATGPT_OAUTH_REFRESH_TOKEN"],
812
+ fileSource: "~/.codex/auth.json (override with ZERO_CHATGPT_AUTH_FILE)",
813
+ hint: "run `codex login` to write ~/.codex/auth.json, or invoke 0 with env ZERO_CHATGPT_OAUTH_REFRESH_TOKEN=..."
37
814
  },
38
815
  {
39
- id: "composer.newline",
40
- keys: "Shift+Enter",
41
- defaultChords: ["shift+return"],
42
- rebindable: false,
43
- description: "Insert a newline instead of sending (terminals without the kitty protocol fall through to send).",
44
- category: "Composer",
45
- lockReason: "text entry",
46
- handler: 'if (key.name === "return" && key.shift) setComposerText(`${\u2026}\\n`)'
816
+ id: "deepseek",
817
+ label: "DeepSeek",
818
+ methods: ["api-key"],
819
+ envVars: ["DEEPSEEK_API_KEY"],
820
+ hint: "set DEEPSEEK_API_KEY (endpoint override: DEEPSEEK_BASE_URL)"
47
821
  },
48
822
  {
49
- id: "composer.complete-command",
50
- keys: "Tab",
51
- defaultChords: ["tab"],
52
- rebindable: false,
53
- description: "Complete the highlighted slash command in the command menu.",
54
- category: "Composer",
55
- lockReason: "text entry",
56
- handler: 'if (key.name === "tab") setComposerText(completionFor(selectedSlashCommand, \u2026))'
823
+ id: "openrouter",
824
+ label: "OpenRouter",
825
+ // OAuth (PKCE browser sign-in) is preferred; a pasted OPENROUTER_API_KEY
826
+ // remains a secondary path. The browser flow provisions a durable
827
+ // `sk-or-...` key and stores it as an api_key record written to
828
+ // OPENROUTER_API_KEY (envVars[0], which llm-api already reads), so — unlike
829
+ // the xai/kimi device flows — the credential is a key, not OAuth tokens.
830
+ methods: ["oauth", "api-key"],
831
+ envVars: ["OPENROUTER_API_KEY"],
832
+ hint: "sign in with your OpenRouter account, or set OPENROUTER_API_KEY=sk-or-... from openrouter.ai/keys"
57
833
  },
58
834
  {
59
- id: "composer.edit-queued",
60
- keys: "Ctrl+Y",
61
- defaultChords: ["ctrl+y"],
62
- rebindable: true,
63
- description: "Pull the most recently queued message back into the composer to edit, re-send, or drop.",
64
- category: "Composer",
65
- handler: 'if (matchesBinding(key, "composer.edit-queued", \u2026) && queuedRef.current.length > 0) \u2026'
835
+ id: "azure",
836
+ label: "Azure OpenAI",
837
+ methods: ["api-key"],
838
+ // Only the Azure key authenticates; the deployment URL comes from
839
+ // AZURE_OPENAI_BASE_URL / OPENAI_BASE_URL / ~/.codex/config.toml
840
+ // (L1435-1445). A key with no reachable base URL still counts as
841
+ // configured here because detectProvider selects azure on the key alone.
842
+ envVars: ["AZURE_OPENAI_API_KEY"],
843
+ hint: "set AZURE_OPENAI_API_KEY plus AZURE_OPENAI_BASE_URL (or [model_providers.azure] in ~/.codex/config.toml)"
66
844
  },
67
845
  {
68
- id: "composer.delete-word",
69
- keys: "Ctrl+W / Alt+Backspace",
70
- defaultChords: ["ctrl+w", "option+backspace"],
71
- rebindable: false,
72
- description: "Delete the word before the cursor.",
73
- category: "Composer",
74
- lockReason: "text entry",
75
- handler: 'if (key.ctrl && key.name === "w") / if (key.name === "backspace" && (key.meta || key.option || key.ctrl)) \u2192 deletePreviousWord'
846
+ id: "openai",
847
+ label: "OpenAI",
848
+ methods: ["api-key"],
849
+ envVars: ["OPENAI_API_KEY"],
850
+ hint: "set OPENAI_API_KEY=sk-... from platform.openai.com/api-keys"
76
851
  },
77
852
  {
78
- id: "composer.delete-to-start",
79
- keys: "Ctrl+U",
80
- defaultChords: ["ctrl+u"],
81
- rebindable: false,
82
- description: "Delete from the cursor to the start of the line.",
83
- category: "Composer",
84
- lockReason: "text entry",
85
- handler: 'if (key.ctrl && key.name === "u") \u2192 deleteToLineStart'
853
+ id: "z-ai",
854
+ label: "Z.ai GLM",
855
+ methods: ["api-key"],
856
+ envVars: ["Z_AI_API_KEY"],
857
+ hint: "set Z_AI_API_KEY from your Z.ai Coding Plan (endpoint override: Z_AI_BASE_URL)"
86
858
  },
87
859
  {
88
- id: "composer.accept-suggestion",
89
- keys: "Right",
90
- defaultChords: ["right"],
91
- rebindable: false,
92
- description: "Accept the inline autosuggestion, filling the ghost suffix into the draft without sending.",
93
- category: "Composer",
94
- lockReason: "text entry",
95
- handler: 'if (key.name === "right") { const suffix = suggestCompletion(...); if (suffix) setComposerText(`${\u2026}${suffix}`) }'
860
+ id: "kimi",
861
+ label: "Moonshot Kimi",
862
+ // OAuth (device sign-in) is preferred; a pasted KIMI_API_KEY remains a
863
+ // secondary path. The OAuth access token is written to KIMI_API_KEY as a
864
+ // Bearer (llm-api.ts reads it there, no change needed), and the refresh
865
+ // token to the 0-owned var so the store can round-trip it.
866
+ methods: ["oauth", "api-key"],
867
+ envVars: ["KIMI_API_KEY", "ZERO_KIMI_OAUTH_REFRESH_TOKEN"],
868
+ hint: "sign in with your Kimi account, or set KIMI_API_KEY from your Kimi coding plan (endpoint override: KIMI_BASE_URL)"
96
869
  },
97
- // ── Navigation ──────────────────────────────────────────────────────────────
98
870
  {
99
- id: "nav.palette",
100
- keys: "Ctrl+P / Ctrl+K",
101
- defaultChords: ["ctrl+p", "ctrl+k"],
102
- rebindable: true,
103
- description: "Open the slash-command palette.",
104
- category: "Navigation",
105
- handler: 'if (matchesBinding(key, "nav.palette", \u2026)) setComposerText("/")'
871
+ id: "qwen",
872
+ label: "Alibaba Qwen",
873
+ methods: ["api-key"],
874
+ envVars: ["QWEN_API_KEY"],
875
+ hint: "set QWEN_API_KEY from Alibaba Model Studio (endpoint override: QWEN_BASE_URL)"
106
876
  },
107
877
  {
108
- id: "nav.history-prev",
109
- keys: "Up",
878
+ id: "xai",
879
+ label: "xAI Grok",
880
+ // OAuth (device sign-in) is preferred; a pasted XAI_API_KEY remains a
881
+ // secondary path. The OAuth access token is written to XAI_API_KEY as a
882
+ // Bearer (llm-api.ts reads it there, no change needed), and the refresh
883
+ // token to the 0-owned var so the store can round-trip it.
884
+ methods: ["oauth", "api-key"],
885
+ envVars: ["XAI_API_KEY", "ZERO_XAI_OAUTH_REFRESH_TOKEN"],
886
+ hint: "sign in with your xAI account, or set XAI_API_KEY from console.x.ai (endpoint override: XAI_BASE_URL)"
887
+ },
888
+ {
889
+ id: "opencode",
890
+ label: "OpenCode Zen",
891
+ methods: ["api-key"],
892
+ envVars: ["OPENCODE_API_KEY"],
893
+ hint: "set OPENCODE_API_KEY from opencode.ai/auth (endpoint override: OPENCODE_BASE_URL)"
894
+ },
895
+ {
896
+ id: "copilot",
897
+ label: "GitHub Copilot",
898
+ // OAuth only (device-code sign-in). The GitHub device-flow access token is
899
+ // written to ZERO_COPILOT_GITHUB_TOKEN (envVars[0]) and sent directly as a
900
+ // Bearer to api.githubcopilot.com — no secondary exchange, no refresh, so
901
+ // there is no refresh-token env var.
902
+ methods: ["oauth"],
903
+ envVars: ["ZERO_COPILOT_GITHUB_TOKEN"],
904
+ hint: "sign in with your GitHub Copilot account (device sign-in), or set ZERO_COPILOT_GITHUB_TOKEN=... (endpoint override: COPILOT_BASE_URL)"
905
+ },
906
+ {
907
+ id: "google",
908
+ label: "Google Gemini (Code Assist)",
909
+ // OAuth only (PKCE browser sign-in — the Gemini CLI flow). The minted
910
+ // access token is written to ZERO_GEMINI_ACCESS_TOKEN (envVars[0]) and the
911
+ // refresh token to ZERO_GEMINI_OAUTH_REFRESH_TOKEN (the /REFRESH/i var); the
912
+ // runtime refreshes on demand against oauth2.googleapis.com. There is no
913
+ // pasted-key equivalent — Code Assist authenticates only via OAuth.
914
+ methods: ["oauth"],
915
+ envVars: ["ZERO_GEMINI_ACCESS_TOKEN", "ZERO_GEMINI_OAUTH_REFRESH_TOKEN"],
916
+ hint: "sign in with your Google account (browser sign-in), or set ZERO_GEMINI_OAUTH_REFRESH_TOKEN=... for Gemini Code Assist (project override: GOOGLE_CLOUD_PROJECT / ZERO_GEMINI_PROJECT)"
917
+ },
918
+ {
919
+ id: "anthropic",
920
+ label: "Anthropic",
921
+ // API key only for now. The schema supports OAuth, but Anthropic/Claude
922
+ // subscription OAuth is deliberately deferred — do not add "oauth" here
923
+ // until that workstream lands.
924
+ methods: ["api-key"],
925
+ envVars: ["ANTHROPIC_API_KEY"],
926
+ hint: "set ANTHROPIC_API_KEY=sk-ant-... from console.anthropic.com"
927
+ }
928
+ ];
929
+ function primaryMethod(methods) {
930
+ return methods[0] ?? "api-key";
931
+ }
932
+ var PROVIDERS = PROVIDER_DEFS.map((def) => ({
933
+ ...def,
934
+ auth: primaryMethod(def.methods)
935
+ }));
936
+ function providerSupportsMethod(providerId, method) {
937
+ const info = PROVIDERS.find((candidate) => candidate.id === providerId);
938
+ return info !== void 0 && info.methods.includes(method);
939
+ }
940
+ function hasCredential(value) {
941
+ return typeof value === "string" && value.trim().length > 0;
942
+ }
943
+ function satisfyingVar(info, env) {
944
+ return info.envVars.find((name) => hasCredential(env[name]));
945
+ }
946
+ function providerStates(env) {
947
+ return PROVIDERS.map((info) => {
948
+ const via = satisfyingVar(info, env);
949
+ return via === void 0 ? { ...info, configured: false } : { ...info, configured: true, via };
950
+ });
951
+ }
952
+ function isProviderConfigured(providerId, env) {
953
+ const info = PROVIDERS.find((candidate) => candidate.id === providerId);
954
+ return info !== void 0 && satisfyingVar(info, env) !== void 0;
955
+ }
956
+
957
+ // packages/cli/src/tui/settings.ts
958
+ import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, renameSync, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "node:fs";
959
+ import { randomUUID as randomUUID2 } from "node:crypto";
960
+ import { dirname as dirname3, join as join6 } from "node:path";
961
+
962
+ // packages/cli/src/tui/keybindings.ts
963
+ var KEYBINDINGS = [
964
+ // ── Composer ──────────────────────────────────────────────────────────────
965
+ {
966
+ id: "composer.send",
967
+ keys: "Enter",
968
+ defaultChords: ["return"],
969
+ rebindable: false,
970
+ description: "Send the message or run the highlighted slash command; queues the line when a turn is already in flight.",
971
+ category: "Composer",
972
+ lockReason: "text entry",
973
+ handler: 'if (key.name === "return") { \u2026 send / queue / dequeue }'
974
+ },
975
+ {
976
+ id: "composer.newline",
977
+ keys: "Shift+Enter",
978
+ defaultChords: ["shift+return"],
979
+ rebindable: false,
980
+ description: "Insert a newline instead of sending (terminals without the kitty protocol fall through to send).",
981
+ category: "Composer",
982
+ lockReason: "text entry",
983
+ handler: 'if (key.name === "return" && key.shift) setComposerText(`${\u2026}\\n`)'
984
+ },
985
+ {
986
+ id: "composer.complete-command",
987
+ keys: "Tab",
988
+ defaultChords: ["tab"],
989
+ rebindable: false,
990
+ description: "Complete the highlighted slash command in the command menu.",
991
+ category: "Composer",
992
+ lockReason: "text entry",
993
+ handler: 'if (key.name === "tab") setComposerText(completionFor(selectedSlashCommand, \u2026))'
994
+ },
995
+ {
996
+ id: "composer.edit-queued",
997
+ keys: "Ctrl+Y",
998
+ defaultChords: ["ctrl+y"],
999
+ rebindable: true,
1000
+ description: "Pull the most recently queued message back into the composer to edit, re-send, or drop.",
1001
+ category: "Composer",
1002
+ handler: 'if (matchesBinding(key, "composer.edit-queued", \u2026) && queuedRef.current.length > 0) \u2026'
1003
+ },
1004
+ {
1005
+ id: "composer.delete-word",
1006
+ keys: "Ctrl+W / Alt+Backspace",
1007
+ defaultChords: ["ctrl+w", "option+backspace"],
1008
+ rebindable: false,
1009
+ description: "Delete the word before the cursor.",
1010
+ category: "Composer",
1011
+ lockReason: "text entry",
1012
+ handler: 'if (key.ctrl && key.name === "w") / if (key.name === "backspace" && (key.meta || key.option || key.ctrl)) \u2192 deletePreviousWord'
1013
+ },
1014
+ {
1015
+ id: "composer.delete-to-start",
1016
+ keys: "Ctrl+U",
1017
+ defaultChords: ["ctrl+u"],
1018
+ rebindable: false,
1019
+ description: "Delete from the cursor to the start of the line.",
1020
+ category: "Composer",
1021
+ lockReason: "text entry",
1022
+ handler: 'if (key.ctrl && key.name === "u") \u2192 deleteToLineStart'
1023
+ },
1024
+ {
1025
+ id: "composer.accept-suggestion",
1026
+ keys: "Right",
1027
+ defaultChords: ["right"],
1028
+ rebindable: false,
1029
+ description: "Accept the inline autosuggestion, filling the ghost suffix into the draft without sending.",
1030
+ category: "Composer",
1031
+ lockReason: "text entry",
1032
+ handler: 'if (key.name === "right") { const suffix = suggestCompletion(...); if (suffix) setComposerText(`${\u2026}${suffix}`) }'
1033
+ },
1034
+ // ── Navigation ──────────────────────────────────────────────────────────────
1035
+ {
1036
+ id: "nav.palette",
1037
+ keys: "Ctrl+P / Ctrl+K",
1038
+ defaultChords: ["ctrl+p", "ctrl+k"],
1039
+ rebindable: true,
1040
+ description: "Open the slash-command palette.",
1041
+ category: "Navigation",
1042
+ handler: 'if (matchesBinding(key, "nav.palette", \u2026)) setComposerText("/")'
1043
+ },
1044
+ {
1045
+ id: "nav.history-prev",
1046
+ keys: "Up",
110
1047
  defaultChords: ["up"],
111
1048
  rebindable: false,
112
1049
  description: "Recall the previous submitted message into the composer (also moves the selection in menus and overlays).",
@@ -416,11 +1353,11 @@ function sanitizeKeybindingOverrides(raw, bindings = KEYBINDINGS) {
416
1353
 
417
1354
  // packages/cli/src/tui/themes.ts
418
1355
  import { mkdirSync, rmSync, writeFileSync } from "node:fs";
419
- import { dirname, join as join2 } from "node:path";
1356
+ import { dirname as dirname2, join as join5 } from "node:path";
420
1357
 
421
1358
  // packages/cli/src/tui/user-themes.ts
422
1359
  import { readdirSync, readFileSync } from "node:fs";
423
- import { join } from "node:path";
1360
+ import { join as join4 } from "node:path";
424
1361
  function coerceUserTheme(id, raw) {
425
1362
  if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
426
1363
  return { ok: false, reason: "file is not a JSON object" };
@@ -468,7 +1405,7 @@ function loadUserThemes(dir) {
468
1405
  }
469
1406
  let parsed;
470
1407
  try {
471
- parsed = JSON.parse(readFileSync(join(dir, file), "utf8"));
1408
+ parsed = JSON.parse(readFileSync(join4(dir, file), "utf8"));
472
1409
  } catch (err) {
473
1410
  rejected.push({
474
1411
  name: id,
@@ -1221,11 +2158,11 @@ function degradePalette(palette, depth) {
1221
2158
  return out;
1222
2159
  }
1223
2160
  var INSTALLED_THEMES_DIRNAME = "themes";
1224
- function installedThemesDir(homeDir2) {
1225
- return join2(homeStateDir(homeDir2), INSTALLED_THEMES_DIRNAME);
2161
+ function installedThemesDir(homeDir) {
2162
+ return join5(homeStateDir(homeDir), INSTALLED_THEMES_DIRNAME);
1226
2163
  }
1227
- function installedThemeFilePath(id, homeDir2) {
1228
- return join2(installedThemesDir(homeDir2), `${id}.json`);
2164
+ function installedThemeFilePath(id, homeDir) {
2165
+ return join5(installedThemesDir(homeDir), `${id}.json`);
1229
2166
  }
1230
2167
  var THEME_ID_RE = /^[a-z][a-z0-9]*([._-][a-z0-9]+)*$/;
1231
2168
  var THEME_ID_MAX = 64;
@@ -1234,36 +2171,36 @@ function isSafeThemeId(id) {
1234
2171
  }
1235
2172
  var installedThemes = null;
1236
2173
  var installedFromHome;
1237
- function readInstalledThemes(homeDir2) {
2174
+ function readInstalledThemes(homeDir) {
1238
2175
  const out = /* @__PURE__ */ new Map();
1239
- for (const entry of loadUserThemes(installedThemesDir(homeDir2)).valid) {
2176
+ for (const entry of loadUserThemes(installedThemesDir(homeDir)).valid) {
1240
2177
  out.set(entry.name, entry);
1241
2178
  }
1242
2179
  return out;
1243
2180
  }
1244
- function ensureInstalledThemesLoaded(homeDir2) {
1245
- if (installedThemes === null || installedFromHome !== homeDir2) {
1246
- installedThemes = readInstalledThemes(homeDir2);
1247
- installedFromHome = homeDir2;
2181
+ function ensureInstalledThemesLoaded(homeDir) {
2182
+ if (installedThemes === null || installedFromHome !== homeDir) {
2183
+ installedThemes = readInstalledThemes(homeDir);
2184
+ installedFromHome = homeDir;
1248
2185
  }
1249
2186
  return installedThemes;
1250
2187
  }
1251
- function reloadInstalledThemes(homeDir2) {
1252
- installedThemes = readInstalledThemes(homeDir2);
1253
- installedFromHome = homeDir2;
2188
+ function reloadInstalledThemes(homeDir) {
2189
+ installedThemes = readInstalledThemes(homeDir);
2190
+ installedFromHome = homeDir;
1254
2191
  return installedThemes;
1255
2192
  }
1256
- function installedThemeEntries(homeDir2) {
1257
- return [...ensureInstalledThemesLoaded(homeDir2).values()];
2193
+ function installedThemeEntries(homeDir) {
2194
+ return [...ensureInstalledThemesLoaded(homeDir).values()];
1258
2195
  }
1259
- function allThemeNames(homeDir2) {
1260
- return [...THEME_NAMES, ...ensureInstalledThemesLoaded(homeDir2).keys()];
2196
+ function allThemeNames(homeDir) {
2197
+ return [...THEME_NAMES, ...ensureInstalledThemesLoaded(homeDir).keys()];
1261
2198
  }
1262
2199
  function isKnownTheme(value) {
1263
2200
  if (isThemeName(value)) return true;
1264
2201
  return typeof value === "string" && installedThemes !== null && installedThemes.has(value);
1265
2202
  }
1266
- function writeInstalledTheme(file, homeDir2) {
2203
+ function writeInstalledTheme(file, homeDir) {
1267
2204
  if (!isSafeThemeId(file.id)) return { ok: false, error: `unsafe theme id ${JSON.stringify(file.id)}` };
1268
2205
  if (isThemeName(file.id)) return { ok: false, error: `"${file.id}" is a built-in theme name and cannot be overridden` };
1269
2206
  const issues = validateTheme(file.palette);
@@ -1271,8 +2208,8 @@ function writeInstalledTheme(file, homeDir2) {
1271
2208
  return { ok: false, error: `invalid palette: ${issues.map((i) => i.message).join("; ")}` };
1272
2209
  }
1273
2210
  try {
1274
- const path = installedThemeFilePath(file.id, homeDir2);
1275
- mkdirSync(dirname(path), { recursive: true });
2211
+ const path = installedThemeFilePath(file.id, homeDir);
2212
+ mkdirSync(dirname2(path), { recursive: true });
1276
2213
  const body = {
1277
2214
  id: file.id,
1278
2215
  ...file.label !== void 0 ? { label: file.label } : {},
@@ -1287,13 +2224,13 @@ function writeInstalledTheme(file, homeDir2) {
1287
2224
  return { ok: false, error: err instanceof Error ? err.message : String(err) };
1288
2225
  }
1289
2226
  }
1290
- function removeInstalledTheme(id, homeDir2) {
2227
+ function removeInstalledTheme(id, homeDir) {
1291
2228
  if (!isSafeThemeId(id)) return { ok: false, error: `unsafe theme id ${JSON.stringify(id)}` };
1292
2229
  if (isThemeName(id)) return { ok: false, error: `"${id}" is a built-in theme and cannot be removed` };
1293
- const cache = ensureInstalledThemesLoaded(homeDir2);
2230
+ const cache = ensureInstalledThemesLoaded(homeDir);
1294
2231
  if (!cache.has(id)) return { ok: false, error: `theme "${id}" is not installed` };
1295
2232
  try {
1296
- rmSync(installedThemeFilePath(id, homeDir2), { force: true });
2233
+ rmSync(installedThemeFilePath(id, homeDir), { force: true });
1297
2234
  return { ok: true };
1298
2235
  } catch (err) {
1299
2236
  return { ok: false, error: err instanceof Error ? err.message : String(err) };
@@ -1302,15 +2239,15 @@ function removeInstalledTheme(id, homeDir2) {
1302
2239
 
1303
2240
  // packages/cli/src/tui/settings.ts
1304
2241
  var THEME_CHOICES = [...THEME_NAMES];
1305
- function syncThemeChoices(homeDir2) {
1306
- ensureInstalledThemesLoaded(homeDir2);
1307
- THEME_CHOICES.splice(0, THEME_CHOICES.length, ...allThemeNames(homeDir2));
2242
+ function syncThemeChoices(homeDir) {
2243
+ ensureInstalledThemesLoaded(homeDir);
2244
+ THEME_CHOICES.splice(0, THEME_CHOICES.length, ...allThemeNames(homeDir));
1308
2245
  }
1309
2246
  var DEFS = [
1310
2247
  {
1311
2248
  key: "showStatusBar",
1312
2249
  label: "Status bar",
1313
- description: "Optional bottom-bar telemetry: model, working directory, git state and token counters. Permission mode and the turn timer share that row and stay visible with this off.",
2250
+ description: "Bottom bar with model, folder, git and token counts. Permission mode and turn timer always show.",
1314
2251
  kind: "boolean",
1315
2252
  default: true,
1316
2253
  group: "Display"
@@ -1326,7 +2263,7 @@ var DEFS = [
1326
2263
  {
1327
2264
  key: "composerSuggestions",
1328
2265
  label: "Composer autosuggestions",
1329
- description: "Inline ghost-text continuation from your submitted-message history, accepted with the Right arrow at end-of-input.",
2266
+ description: "Ghost-text suggestions from your past messages. Right arrow accepts.",
1330
2267
  kind: "boolean",
1331
2268
  default: true,
1332
2269
  group: "Display"
@@ -1334,7 +2271,7 @@ var DEFS = [
1334
2271
  {
1335
2272
  key: "mouseSupport",
1336
2273
  label: "Mouse support",
1337
- description: "Scroll-wheel scrolling, click-to-select on list rows, and clickable hints. Turn off for a keyboard-only console where the terminal's own text selection works.",
2274
+ description: "Scroll, click rows and hints. Turn off to use your terminal's own text selection.",
1338
2275
  kind: "boolean",
1339
2276
  default: true,
1340
2277
  group: "Display"
@@ -1350,7 +2287,7 @@ var DEFS = [
1350
2287
  {
1351
2288
  key: "showRuntimeNotices",
1352
2289
  label: "Runtime notices",
1353
- description: "Surface runtime stdout/stderr as transcript notices.",
2290
+ description: "Show engine output (stdout/stderr) in the conversation.",
1354
2291
  kind: "boolean",
1355
2292
  default: true,
1356
2293
  group: "Transcript"
@@ -1382,7 +2319,7 @@ var DEFS = [
1382
2319
  {
1383
2320
  key: "showObjective",
1384
2321
  label: "Objective",
1385
- description: `Bottom-bar "objective" pill: a short "what am I working on" title derived from the session's first message.`,
2322
+ description: "Short title for the current session in the bottom bar.",
1386
2323
  kind: "boolean",
1387
2324
  default: true,
1388
2325
  group: "Display"
@@ -1390,7 +2327,7 @@ var DEFS = [
1390
2327
  {
1391
2328
  key: "showScope",
1392
2329
  label: "Scope",
1393
- description: "Show the configured multi-host scope and exclusions. An absent scope is distinct from an explicit empty scope.",
2330
+ description: "Show the target scope and exclusions. No scope is different from an empty scope.",
1394
2331
  kind: "boolean",
1395
2332
  default: true,
1396
2333
  group: "Display"
@@ -1441,7 +2378,7 @@ var DEFS = [
1441
2378
  {
1442
2379
  key: "transcriptStyle",
1443
2380
  label: "Transcript style",
1444
- description: "Minimal (the default) drops every bubble and box: your turns carry a thin coloured accent rail and the answer flows as plain body text, the OpenCode / oh-my-pi flat look. Bubble right-aligns your messages against left-aligned answers and titles each card on its border. Balanced: only your own messages are bubbled; everything else is flat. Rail, plain, compact and document offer alternative transcript layouts.",
2381
+ description: "How the conversation looks. Minimal (default) is flat text; Bubble and Balanced add message bubbles; Rail, plain, compact and document are alternatives.",
1445
2382
  kind: "enum",
1446
2383
  default: "minimal",
1447
2384
  choices: ["minimal", "rail", "bubble", "balanced", "plain", "compact", "document"],
@@ -1450,7 +2387,7 @@ var DEFS = [
1450
2387
  {
1451
2388
  key: "roleLabelStyle",
1452
2389
  label: "Role label",
1453
- description: 'Labels your messages as "You"; assistant replies stay unlabeled. Full and short include the optional age, glyph shows the name alone, and off hides the label.',
2390
+ description: 'Label your messages as "You". Full and short add the age, glyph shows the name only, off hides it.',
1454
2391
  kind: "enum",
1455
2392
  default: "off",
1456
2393
  choices: ["full", "short", "glyph", "off"],
@@ -1468,7 +2405,7 @@ var DEFS = [
1468
2405
  {
1469
2406
  key: "richToolCards",
1470
2407
  label: "Rich tool cards",
1471
- description: "Draw bash/run_command output and apply_patch edits as bordered cards (command + output + wall/exit footer, or an edit header + diff) instead of a plain line.",
2408
+ description: "Show command output and file edits as cards instead of one line.",
1472
2409
  kind: "boolean",
1473
2410
  default: true,
1474
2411
  group: "Transcript"
@@ -1476,7 +2413,7 @@ var DEFS = [
1476
2413
  {
1477
2414
  key: "transcriptDetail",
1478
2415
  label: "Transcript detail",
1479
- description: "Fold tool and reasoning steps. Output previews stay within 20 lines; click a card for retained detail.",
2416
+ description: "Fold tool and reasoning steps. Click a card to see more.",
1480
2417
  kind: "enum",
1481
2418
  default: "collapsed",
1482
2419
  choices: ["collapsed", "expanded"],
@@ -1485,7 +2422,7 @@ var DEFS = [
1485
2422
  {
1486
2423
  key: "autoCompaction",
1487
2424
  label: "Auto-compaction",
1488
- description: "Automatically summarise older turns when the conversation nears the model's context window.",
2425
+ description: "Summarise older turns when the context window gets full.",
1489
2426
  kind: "boolean",
1490
2427
  default: true,
1491
2428
  group: "Context"
@@ -1502,7 +2439,7 @@ var DEFS = [
1502
2439
  {
1503
2440
  key: "theme",
1504
2441
  label: "Theme",
1505
- description: "Colour palette. Slate (neutral grey, default) and Midnight (deep blue-black) and Carbon (warm dark), Standard/Paper (light), plus Contrast, Mono Dim and ANSI 16 for 16-colour terminals. Drop validated palettes in ~/.0/themes to add your own.",
2442
+ description: "Colour theme. Contrast, Mono Dim and ANSI 16 suit 16-colour terminals. Add your own in ~/.0/themes.",
1506
2443
  kind: "enum",
1507
2444
  default: DEFAULT_THEME_NAME,
1508
2445
  choices: THEME_CHOICES,
@@ -1511,7 +2448,7 @@ var DEFS = [
1511
2448
  {
1512
2449
  key: "allowModelSelfExtension",
1513
2450
  label: "Model self-extension",
1514
- description: "Allow new sessions to add sandboxed tools and live harness generations. Existing disabled sessions stay disabled. Trusted harness code requires separate workspace trust; development engine updates are controlled independently.",
2451
+ description: "Let new sessions add sandboxed tools. Sessions started with this off stay off. Trusted tool code also needs workspace trust; engine updates are a separate setting.",
1515
2452
  kind: "boolean",
1516
2453
  default: DEFAULT_ALLOW_MODEL_SELF_EXTENSION,
1517
2454
  group: "Security"
@@ -1519,7 +2456,7 @@ var DEFS = [
1519
2456
  {
1520
2457
  key: "allowDevSourceUpdates",
1521
2458
  label: "Development engine updates",
1522
- description: "0dev only. Reload changed core engine code between turns without losing the session. Runs with host privileges, including credential access; independent of sandboxed tool extension.",
2459
+ description: "0dev only. Reload changed engine code between turns. Runs with full host access, including your credentials; separate from sandboxed tools.",
1523
2460
  kind: "boolean",
1524
2461
  default: false,
1525
2462
  group: "Security"
@@ -1527,7 +2464,7 @@ var DEFS = [
1527
2464
  {
1528
2465
  key: "autoEvolveFinderLenses",
1529
2466
  label: "Auto-evolve finder lenses",
1530
- description: "Start the TUI watcher for ~/.0/lens-synthesis/miss-input.json (or OSEC_TUI_LENS_SYNTH_INPUT) so each new curated revision can invoke the configured model.",
2467
+ description: "Watch ~/.0/lens-synthesis/miss-input.json (or OSEC_TUI_LENS_SYNTH_INPUT) and let the model draft new finder lenses.",
1531
2468
  kind: "boolean",
1532
2469
  default: false,
1533
2470
  group: "Security"
@@ -1535,7 +2472,7 @@ var DEFS = [
1535
2472
  {
1536
2473
  key: "autoPromoteFinderLenses",
1537
2474
  label: "Auto-promote validated lenses",
1538
- description: "Permit the TUI watcher to persist a candidate only after its positive and negative-control corpus gate passes, otherwise automatic evaluation stays dry-run.",
2475
+ description: "Save a drafted lens only after it passes its positive and negative test cases. Otherwise evaluation is dry-run.",
1539
2476
  kind: "boolean",
1540
2477
  default: false,
1541
2478
  group: "Security"
@@ -1576,7 +2513,7 @@ var DEFS = [
1576
2513
  {
1577
2514
  key: "elapsedTimer",
1578
2515
  label: "Elapsed timer",
1579
- description: "Show the running-turn elapsed time as a compact clock-glyphed pill next to the left status icon, or hide it.",
2516
+ description: "Show how long the current turn has been running.",
1580
2517
  kind: "enum",
1581
2518
  default: "left",
1582
2519
  choices: ["left", "off"],
@@ -1585,7 +2522,7 @@ var DEFS = [
1585
2522
  {
1586
2523
  key: "logoAnimation",
1587
2524
  label: "Logo animation",
1588
- description: 'Intro animation for the "0.SECURITY" wordmark: glitch (a neon-flecked scramble that resolves \u2014 the default), rainbow (a looping hue sweep), matrix (a green matrix-rain cascade), wave (a rippling cyan wavefront), neon (a neon-sign warm-up flicker), shimmer (a bright comet with a gradient tail), pulse (the slash breathes), strike (an orange slash strikes through the 0), draw (letters draw in behind a pen tip), fade (a centre-out bloom), typein (per-cell reveal), sweep (a bright bar wipes across) or off (static).',
2525
+ description: "Intro animation for the 0 logo, or off.",
1589
2526
  kind: "enum",
1590
2527
  default: "glitch",
1591
2528
  choices: [
@@ -1609,7 +2546,7 @@ var DEFS = [
1609
2546
  {
1610
2547
  key: "reduceMotion",
1611
2548
  label: "Reduce motion",
1612
- description: "Master reduce-motion: the console keeps essential feedback but disables decorative animations like the logo intro, shimmers and sweeps.",
2549
+ description: "Turn off decorative animations (logo intro, shimmers, sweeps).",
1613
2550
  kind: "boolean",
1614
2551
  default: false,
1615
2552
  group: "Motion"
@@ -1617,16 +2554,16 @@ var DEFS = [
1617
2554
  {
1618
2555
  key: "analyticsLevel",
1619
2556
  label: "Data sharing",
1620
- description: "Usage sends counts, timing, cost and error categories. Commands adds tool inputs/outputs and code; Full adds scope and findings. Credentials are scrubbed, but identifying content may remain. Uploads require an endpoint and are not anonymous. Environment opt-outs take precedence. Applies to this computer.",
2557
+ description: "Usage shares pseudonymous counts, timing, cost and error types, never code or tool content. Off sends nothing. Environment opt-outs win. Applies to this computer.",
1621
2558
  kind: "enum",
1622
2559
  default: "off",
1623
- choices: ["off", "usage", "commands", "full"],
2560
+ choices: ["off", "usage"],
1624
2561
  group: "Privacy"
1625
2562
  },
1626
2563
  {
1627
2564
  key: "diagnosticReporting",
1628
2565
  label: "Problem reports",
1629
- description: "Review diagnostics before sending, send automatically, or keep reports local. Requires a configured endpoint. Off/Usage sharing sends categories only; Commands/Full can include scrubbed errors, stacks and failure output. Applies to this computer.",
2566
+ description: "Review error reports before sending, send automatically, or keep them local. Needs a configured destination; separate from usage sharing. Applies to this computer.",
1630
2567
  kind: "enum",
1631
2568
  default: "ask",
1632
2569
  choices: ["off", "ask", "automatic"],
@@ -1635,7 +2572,7 @@ var DEFS = [
1635
2572
  {
1636
2573
  key: "updatePolicy",
1637
2574
  label: "Updates",
1638
- description: "Off, notify about releases, or install updates before the console starts. Applies to this computer; project settings cannot enable installation.",
2575
+ description: "Off, notify about new releases, or install updates at startup. Applies to this computer; project settings can't turn on installing.",
1639
2576
  kind: "enum",
1640
2577
  default: "automatic",
1641
2578
  choices: ["off", "notify", "automatic"],
@@ -1644,7 +2581,7 @@ var DEFS = [
1644
2581
  {
1645
2582
  key: "executionProfile",
1646
2583
  label: "Execution profile",
1647
- description: "SmolVM runs the whole console, agents, browser and shell in the online Kali workbench. Prepare its approved image and explicit credential grants with 0 workbench setup. Applies to this computer on the next launch; failures never fall back to host execution.",
2584
+ description: "SmolVM runs everything (agents, browser, shell) in the online Kali workbench. Set it up with 0 workbench setup. Applies to this computer on next launch; failures never fall back to running on your machine.",
1648
2585
  kind: "enum",
1649
2586
  default: "local",
1650
2587
  choices: ["local", "smolvm"],
@@ -1653,7 +2590,7 @@ var DEFS = [
1653
2590
  {
1654
2591
  key: "symbolPreset",
1655
2592
  label: "Symbols",
1656
- description: "Glyph set for icons, status marks and checkboxes: Unicode (default, works everywhere), Nerd Font (crisp patched-font icons \u2014 requires a Nerd Font terminal) or ASCII (plain, single-cell).",
2593
+ description: "Icon set: Unicode (default), Nerd Font (needs a Nerd Font terminal) or ASCII.",
1657
2594
  kind: "enum",
1658
2595
  default: "unicode",
1659
2596
  choices: ["unicode", "nerd", "ascii"],
@@ -1662,7 +2599,7 @@ var DEFS = [
1662
2599
  {
1663
2600
  key: "rosterSort",
1664
2601
  label: "Roster order",
1665
- description: "Order the agent herd and comms fleet by attention (blocked and working agents float to the top \u2014 the default) or by fixed lifecycle status.",
2602
+ description: "Sort agents by attention (blocked and working first, default) or by status.",
1666
2603
  kind: "enum",
1667
2604
  default: "attention",
1668
2605
  choices: ["attention", "status"],
@@ -1671,7 +2608,7 @@ var DEFS = [
1671
2608
  {
1672
2609
  key: "leaderKey",
1673
2610
  label: "Leader key",
1674
- description: "Optional tmux-style prefix chord for the roster views: press it, then a number to focus that agent, or n/p to step. Off disables it (the direct number keys work regardless).",
2611
+ description: "Optional prefix key for agent views: press it, then a number to focus an agent or n/p to step. Number keys work either way.",
1675
2612
  kind: "enum",
1676
2613
  default: "off",
1677
2614
  choices: ["off", "ctrl+a", "ctrl+b", "ctrl+space"],
@@ -1706,1068 +2643,246 @@ var DEFAULT_SETTINGS = {
1706
2643
  transcriptDetail: "collapsed",
1707
2644
  autoCompaction: true,
1708
2645
  compactionThreshold: "80%",
1709
- theme: DEFAULT_THEME_NAME,
1710
- allowModelSelfExtension: DEFAULT_ALLOW_MODEL_SELF_EXTENSION,
1711
- allowDevSourceUpdates: false,
1712
- autoEvolveFinderLenses: false,
1713
- autoPromoteFinderLenses: false,
1714
- showTokenUsage: false,
1715
- showCost: false,
1716
- showContextMeter: true,
1717
- modelDisplay: "statusbar",
1718
- elapsedTimer: "left",
1719
- logoAnimation: "glitch",
1720
- reduceMotion: false,
1721
- analyticsLevel: "off",
1722
- diagnosticReporting: "ask",
1723
- diagnosticReportingPrompted: false,
1724
- updatePolicy: "automatic",
1725
- symbolPreset: "unicode",
1726
- rosterSort: "attention",
1727
- leaderKey: "off",
1728
- keybindings: {},
1729
- modelPreference: null
1730
- };
1731
- var SETTINGS_FILENAME = "tui-settings.json";
1732
- function settingsFilePath(homeDir2) {
1733
- return join3(homeStateDir(homeDir2), SETTINGS_FILENAME);
1734
- }
1735
- function projectStateDir(projectDir2 = process.cwd()) {
1736
- return join3(projectDir2, ".0");
1737
- }
1738
- function projectSettingsFilePath(projectDir2 = process.cwd()) {
1739
- return join3(projectStateDir(projectDir2), SETTINGS_FILENAME);
1740
- }
1741
- function readRawSettingsFile(path) {
1742
- try {
1743
- return JSON.parse(readFileSync2(path, "utf8"));
1744
- } catch {
1745
- return void 0;
1746
- }
1747
- }
1748
- function projectSettingsExist(projectDir2 = process.cwd()) {
1749
- const raw = readRawSettingsFile(projectSettingsFilePath(projectDir2));
1750
- return typeof raw === "object" && raw !== null && !Array.isArray(raw);
1751
- }
1752
- function strictValueAt(raw, key) {
1753
- const value = rawValue(raw, key);
1754
- if (value === void 0) return void 0;
1755
- if (key === "onboardingCompleted" || key === "diagnosticReportingPrompted") {
1756
- return typeof value === "boolean" ? value : void 0;
1757
- }
1758
- if (value === false && (key === "diagnosticReporting" || key === "updatePolicy")) {
1759
- return "off";
1760
- }
1761
- if (key === "theme") {
1762
- return typeof value === "string" && isKnownTheme(value) ? value : void 0;
1763
- }
1764
- if (key === "keybindings") {
1765
- return typeof value === "object" && value !== null && !Array.isArray(value) ? sanitizeKeybindingOverrides(value) : void 0;
1766
- }
1767
- if (key === "modelPreference") {
1768
- return normalizeModelPreference(value);
1769
- }
1770
- const def = DEF_BY_KEY.get(key);
1771
- if (def?.kind === "boolean") {
1772
- return typeof value === "boolean" ? value : void 0;
1773
- }
1774
- const choices = def?.choices ?? [];
1775
- return typeof value === "string" && choices.includes(value) ? value : void 0;
1776
- }
1777
- function isOperatorSetting(key) {
1778
- return key === "onboardingCompleted" || key === "analyticsLevel" || key === "diagnosticReporting" || key === "diagnosticReportingPrompted" || key === "updatePolicy" || key === "allowDevSourceUpdates" || key === "executionProfile" || key === "modelPreference";
1779
- }
1780
- function resolveLayeredSettings(globalRaw, projectRaw) {
1781
- const settings = {};
1782
- const sources = {};
1783
- for (const key of Object.keys(DEFAULT_SETTINGS)) {
1784
- const projectValue = isOperatorSetting(key) ? void 0 : strictValueAt(projectRaw, key);
1785
- if (projectValue !== void 0) {
1786
- settings[key] = projectValue;
1787
- sources[key] = "project";
1788
- continue;
1789
- }
1790
- const globalValue = strictValueAt(globalRaw, key);
1791
- if (globalValue !== void 0) {
1792
- settings[key] = globalValue;
1793
- sources[key] = "global";
1794
- continue;
1795
- }
1796
- settings[key] = DEFAULT_SETTINGS[key];
1797
- sources[key] = "default";
1798
- }
1799
- return { settings, sources };
1800
- }
1801
- function loadLayeredSettings(opts = {}) {
1802
- syncThemeChoices(opts.homeDir);
1803
- const globalRaw = readRawSettingsFile(settingsFilePath(opts.homeDir));
1804
- const projectRaw = readRawSettingsFile(projectSettingsFilePath(opts.projectDir));
1805
- return resolveLayeredSettings(globalRaw, projectRaw);
1806
- }
1807
- function rawValue(raw, key) {
1808
- if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return void 0;
1809
- const value = raw[key];
1810
- return key === "transcriptStyle" && value === "messenger" ? "bubble" : value;
1811
- }
1812
- function booleanAt(raw, key) {
1813
- const value = rawValue(raw, key);
1814
- return typeof value === "boolean" ? value : DEFAULT_SETTINGS[key];
1815
- }
1816
- function enumAt(raw, key) {
1817
- const def = DEF_BY_KEY.get(key);
1818
- const value = rawValue(raw, key);
1819
- const choices = def?.choices ?? [];
1820
- return typeof value === "string" && choices.includes(value) ? value : DEFAULT_SETTINGS[key];
1821
- }
1822
- function themeAt(raw) {
1823
- const value = rawValue(raw, "theme");
1824
- return typeof value === "string" && isKnownTheme(value) ? value : DEFAULT_SETTINGS.theme;
1825
- }
1826
- function keybindingsAt(raw) {
1827
- return sanitizeKeybindingOverrides(rawValue(raw, "keybindings"));
1828
- }
1829
- function normalizeModelPreference(raw) {
1830
- if (raw === null) return null;
1831
- if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return void 0;
1832
- const value = raw;
1833
- if (typeof value.providerId !== "string" || !value.providerId.trim() || typeof value.model !== "string" || !value.model.trim() || typeof value.connectionIdentity !== "string" || !/^[a-f0-9]{64}$/.test(value.connectionIdentity)) return void 0;
1834
- return { providerId: value.providerId, model: value.model, connectionIdentity: value.connectionIdentity };
1835
- }
1836
- function normalizeSettings(raw) {
1837
- return {
1838
- showStatusBar: booleanAt(raw, "showStatusBar"),
1839
- showComposerHints: booleanAt(raw, "showComposerHints"),
1840
- composerSuggestions: booleanAt(raw, "composerSuggestions"),
1841
- mouseSupport: booleanAt(raw, "mouseSupport"),
1842
- showLogo: booleanAt(raw, "showLogo"),
1843
- showRuntimeNotices: booleanAt(raw, "showRuntimeNotices"),
1844
- showTurnSummary: booleanAt(raw, "showTurnSummary"),
1845
- showSubagents: booleanAt(raw, "showSubagents"),
1846
- showTimestamps: booleanAt(raw, "showTimestamps"),
1847
- showObjective: booleanAt(raw, "showObjective"),
1848
- showScope: booleanAt(raw, "showScope"),
1849
- density: enumAt(raw, "density"),
1850
- composerStyle: enumAt(raw, "composerStyle"),
1851
- busyInputMode: enumAt(raw, "busyInputMode"),
1852
- onboardingCompleted: booleanAt(raw, "onboardingCompleted"),
1853
- executionProfile: enumAt(raw, "executionProfile"),
1854
- allowSubagentPeerMessaging: booleanAt(raw, "allowSubagentPeerMessaging"),
1855
- allowSubagentOperatorMessaging: booleanAt(raw, "allowSubagentOperatorMessaging"),
1856
- transcriptStyle: enumAt(raw, "transcriptStyle"),
1857
- roleLabelStyle: enumAt(raw, "roleLabelStyle"),
1858
- toolCardStyle: enumAt(raw, "toolCardStyle"),
1859
- richToolCards: booleanAt(raw, "richToolCards"),
1860
- transcriptDetail: enumAt(raw, "transcriptDetail"),
1861
- autoCompaction: booleanAt(raw, "autoCompaction"),
1862
- compactionThreshold: enumAt(raw, "compactionThreshold"),
1863
- theme: themeAt(raw),
1864
- allowModelSelfExtension: booleanAt(raw, "allowModelSelfExtension"),
1865
- allowDevSourceUpdates: booleanAt(raw, "allowDevSourceUpdates"),
1866
- autoEvolveFinderLenses: booleanAt(raw, "autoEvolveFinderLenses"),
1867
- autoPromoteFinderLenses: booleanAt(raw, "autoPromoteFinderLenses"),
1868
- showTokenUsage: booleanAt(raw, "showTokenUsage"),
1869
- showCost: booleanAt(raw, "showCost"),
1870
- showContextMeter: booleanAt(raw, "showContextMeter"),
1871
- modelDisplay: enumAt(raw, "modelDisplay"),
1872
- elapsedTimer: enumAt(raw, "elapsedTimer"),
1873
- logoAnimation: enumAt(raw, "logoAnimation"),
1874
- reduceMotion: booleanAt(raw, "reduceMotion"),
1875
- analyticsLevel: enumAt(raw, "analyticsLevel"),
1876
- diagnosticReporting: strictValueAt(raw, "diagnosticReporting") ?? DEFAULT_SETTINGS.diagnosticReporting,
1877
- diagnosticReportingPrompted: booleanAt(raw, "diagnosticReportingPrompted"),
1878
- updatePolicy: strictValueAt(raw, "updatePolicy") ?? DEFAULT_SETTINGS.updatePolicy,
1879
- symbolPreset: enumAt(raw, "symbolPreset"),
1880
- rosterSort: enumAt(raw, "rosterSort"),
1881
- leaderKey: enumAt(raw, "leaderKey"),
1882
- keybindings: keybindingsAt(raw),
1883
- modelPreference: normalizeModelPreference(rawValue(raw, "modelPreference")) ?? null
1884
- };
1885
- }
1886
- function loadSettings(homeDir2, projectDir2) {
1887
- return loadLayeredSettings({ homeDir: homeDir2, projectDir: projectDir2 }).settings;
1888
- }
1889
- function loadGlobalSettings(homeDir2, options = {}) {
1890
- syncThemeChoices(homeDir2);
1891
- const raw = readRawSettingsFile(settingsFilePath(homeDir2));
1892
- if (options.requireExecutionProfile && strictValueAt(raw, "executionProfile") === void 0) {
1893
- throw new Error("Configured workbench execution profile is missing or invalid. Run 0 workbench setup or 0 workbench disable to make an explicit operator choice; host fallback is refused.");
1894
- }
1895
- return normalizeSettings(raw);
1896
- }
1897
- function saveSettings(settings, homeDir2) {
1898
- const path = settingsFilePath(homeDir2);
1899
- const temporary = `${path}.${randomUUID()}.tmp`;
1900
- try {
1901
- mkdirSync2(dirname2(path), { recursive: true });
1902
- writeFileSync2(temporary, `${JSON.stringify(normalizeSettings(settings), null, 2)}
1903
- `, { encoding: "utf8", flag: "wx", mode: 384 });
1904
- renameSync(temporary, path);
1905
- return true;
1906
- } catch {
1907
- return false;
1908
- } finally {
1909
- try {
1910
- rmSync2(temporary, { force: true });
1911
- } catch {
1912
- }
1913
- }
1914
- }
1915
- function sanitizeOverrides(raw) {
1916
- const out = {};
1917
- for (const key of Object.keys(DEFAULT_SETTINGS)) {
1918
- if (isOperatorSetting(key)) continue;
1919
- const value = strictValueAt(raw, key);
1920
- if (value !== void 0) out[key] = value;
1921
- }
1922
- return out;
1923
- }
1924
- function readProjectOverrides(projectDir2) {
1925
- return sanitizeOverrides(readRawSettingsFile(projectSettingsFilePath(projectDir2)));
1926
- }
1927
- function saveProjectOverrides(patch, projectDir2) {
1928
- try {
1929
- const path = projectSettingsFilePath(projectDir2);
1930
- mkdirSync2(dirname2(path), { recursive: true });
1931
- writeFileSync2(path, `${JSON.stringify(sanitizeOverrides(patch), null, 2)}
1932
- `, "utf8");
1933
- return true;
1934
- } catch {
1935
- return false;
1936
- }
1937
- }
1938
-
1939
- // packages/cli/src/tui/settings-store.ts
1940
- var cached = null;
1941
- var globalDraft = null;
1942
- var projectDraft = null;
1943
- var homeDir;
1944
- var projectDir;
1945
- var subscribers = /* @__PURE__ */ new Set();
1946
- function notify(settings) {
1947
- bridgeAnalyticsLevel(settings);
1948
- for (const fn of [...subscribers]) {
1949
- try {
1950
- fn(settings);
1951
- } catch {
1952
- }
1953
- }
1954
- }
1955
- function loadLayered() {
1956
- return globalDraft === null && projectDraft === null ? loadLayeredSettings({ homeDir, projectDir }) : resolveLayeredSettings(globalDraft ?? loadGlobalSettings(homeDir), projectDraft ?? readProjectOverrides(projectDir));
1957
- }
1958
- function getSettings() {
1959
- if (cached === null) {
1960
- cached = loadLayered();
1961
- bridgeAnalyticsLevel(cached.settings);
1962
- }
1963
- return cached.settings;
1964
- }
1965
- function getSettingSources() {
1966
- getSettings();
1967
- return cached.sources;
1968
- }
1969
- function setSettings(next) {
1970
- const value = normalizeSettings(next);
1971
- const saved = saveSettings(value, homeDir);
1972
- globalDraft = saved ? null : value;
1973
- cached = loadLayered();
1974
- notify(cached.settings);
1975
- return saved;
1976
- }
1977
- function defaultWriteLayer() {
1978
- return projectSettingsExist(projectDir) ? "project" : "global";
1979
- }
1980
- function updateSetting(key, value, opts = {}) {
1981
- const operatorOnly = isOperatorSetting(key);
1982
- if (operatorOnly && opts.scope === "project") return false;
1983
- const layer = operatorOnly ? "global" : opts.scope ?? defaultWriteLayer();
1984
- if (layer === "global") {
1985
- return setSettings({ ...globalDraft ?? loadGlobalSettings(homeDir), [key]: value });
1986
- }
1987
- const patch = { ...projectDraft ?? readProjectOverrides(projectDir), [key]: value };
1988
- const saved = saveProjectOverrides(patch, projectDir);
1989
- projectDraft = saved ? null : patch;
1990
- cached = loadLayered();
1991
- notify(cached.settings);
1992
- return saved;
1993
- }
1994
- function resetSettings(next, keys) {
1995
- const selected = keys.filter((key) => SETTING_DEFS.some((def) => def.key === key));
1996
- if (selected.length === 0) return true;
1997
- const global = { ...globalDraft ?? loadGlobalSettings(homeDir) };
1998
- const project = { ...projectDraft ?? readProjectOverrides(projectDir) };
1999
- let projectChanged = false;
2000
- for (const key of selected) {
2001
- global[key] = next[key];
2002
- if (Object.hasOwn(project, key)) {
2003
- delete project[key];
2004
- projectChanged = true;
2005
- }
2006
- }
2007
- const value = normalizeSettings(global);
2008
- const globalSaved = saveSettings(value, homeDir);
2009
- globalDraft = globalSaved ? null : value;
2010
- const projectSaved = !projectChanged || saveProjectOverrides(project, projectDir);
2011
- if (projectChanged) projectDraft = projectSaved ? null : project;
2012
- cached = loadLayered();
2013
- notify(cached.settings);
2014
- return globalSaved && projectSaved;
2015
- }
2016
- function previewSetting(key, value) {
2017
- if (cached === null) cached = loadLayered();
2018
- cached = { settings: { ...cached.settings, [key]: value }, sources: cached.sources };
2019
- notify(cached.settings);
2020
- }
2021
- function subscribeSettings(fn) {
2022
- subscribers.add(fn);
2023
- return () => {
2024
- subscribers.delete(fn);
2025
- };
2026
- }
2027
- var bridgedAnalyticsEnv;
2028
- function bridgeAnalyticsLevel(settings) {
2029
- try {
2030
- const current = process.env[ANALYTICS_LEVEL_ENV];
2031
- if (current === void 0 || current === bridgedAnalyticsEnv) {
2032
- process.env[ANALYTICS_LEVEL_ENV] = settings.analyticsLevel;
2033
- bridgedAnalyticsEnv = settings.analyticsLevel;
2034
- } else {
2035
- bridgedAnalyticsEnv = void 0;
2036
- }
2037
- const environmentLevel = resolveAnalyticsLevel();
2038
- analyticsPipeline.setLevel(levelAtLeast(environmentLevel, settings.analyticsLevel) ? settings.analyticsLevel : environmentLevel);
2039
- } catch {
2040
- }
2041
- }
2042
- function reloadSettings() {
2043
- globalDraft = null;
2044
- projectDraft = null;
2045
- cached = loadLayered();
2046
- notify(cached.settings);
2047
- return cached.settings;
2048
- }
2049
- function configureSettingsStore(options) {
2050
- homeDir = options.homeDir;
2051
- projectDir = options.projectDir;
2052
- reloadSettings();
2053
- }
2054
- function useSettings() {
2055
- return useSyncExternalStore(subscribeSettings, getSettings, getSettings);
2056
- }
2057
-
2058
- // packages/cli/src/codex-auth.ts
2059
- import { readFileSync as readFileSync3, existsSync } from "node:fs";
2060
- import { homedir } from "node:os";
2061
- import { join as join4 } from "node:path";
2062
- var AUTH_FILE_ENV = "ZERO_CHATGPT_AUTH_FILE";
2063
- var LEGACY_AUTH_FILE_ENV = "ZERO_CODEX_AUTH_JSON_PATH";
2064
- function maybeLoadCodexAuth(options = {}) {
2065
- const env = options.env ?? process.env;
2066
- if (!options.force && (env["ZERO_CHATGPT_ACCESS_TOKEN"] || env["ZERO_CHATGPT_OAUTH_REFRESH_TOKEN"])) {
2067
- return;
2068
- }
2069
- const home = options.home ?? homedir();
2070
- const override = nonEmpty(env[AUTH_FILE_ENV]) ?? nonEmpty(env[LEGACY_AUTH_FILE_ENV]);
2071
- const path = override ?? join4(home, ".codex", "auth.json");
2072
- if (!existsSync(path)) return;
2073
- try {
2074
- const raw = JSON.parse(readFileSync3(path, "utf8"));
2075
- const accessToken = nonEmpty(asString(raw.tokens?.access_token));
2076
- const refreshToken = nonEmpty(asString(raw.tokens?.refresh_token));
2077
- if (!accessToken && !refreshToken) return;
2078
- if (options.force) {
2079
- delete env["ZERO_CHATGPT_ACCESS_TOKEN"];
2080
- delete env["ZERO_CHATGPT_OAUTH_REFRESH_TOKEN"];
2081
- }
2082
- if (accessToken) {
2083
- env["ZERO_CHATGPT_ACCESS_TOKEN"] = accessToken;
2084
- }
2085
- if (refreshToken) {
2086
- env["ZERO_CHATGPT_OAUTH_REFRESH_TOKEN"] = refreshToken;
2087
- }
2088
- } catch {
2089
- }
2090
- }
2091
- function nonEmpty(value) {
2092
- return typeof value === "string" && value.length > 0 ? value : void 0;
2093
- }
2094
- function asString(value) {
2095
- return typeof value === "string" ? value : void 0;
2096
- }
2097
-
2098
- // packages/cli/src/presentation/event-bus.ts
2099
- var PresentationEventBus = class {
2100
- listeners = [];
2101
- subscribe(listener) {
2102
- this.listeners.push(listener);
2103
- return () => {
2104
- const index = this.listeners.indexOf(listener);
2105
- if (index >= 0) this.listeners.splice(index, 1);
2106
- };
2107
- }
2108
- emit(event) {
2109
- for (const listener of [...this.listeners]) {
2110
- try {
2111
- listener.emit(event);
2112
- } catch {
2113
- }
2114
- }
2115
- }
2116
- get size() {
2117
- return this.listeners.length;
2118
- }
2119
- };
2120
- var presentationEventBus = new PresentationEventBus();
2121
- function createPresentationEmitter(options = {}) {
2122
- const source = options.source ?? "cli";
2123
- const now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
2124
- const bus = options.bus ?? presentationEventBus;
2125
- let sequence = 0;
2126
- return {
2127
- emit(eventType, payload, correlation = {}) {
2128
- const event = createPresentationEvent({
2129
- source,
2130
- sequence: ++sequence,
2131
- at: now(),
2132
- eventType,
2133
- payload,
2134
- ...correlation.scanId ? { scanId: correlation.scanId } : {},
2135
- ...correlation.sessionId ? { sessionId: correlation.sessionId } : {}
2136
- });
2137
- bus.emit(event);
2138
- return event;
2139
- }
2140
- };
2141
- }
2142
-
2143
- // packages/cli/src/presentation/process-output.ts
2144
- import { format } from "node:util";
2145
- var streamBridgeSuppressionDepth = 0;
2146
- var activeStreamBridge = null;
2147
- function currentPresentationTimestamp() {
2148
- return (/* @__PURE__ */ new Date()).toISOString();
2149
- }
2150
- function createProcessPresentationOutput(options = {}) {
2151
- const stdout = options.stdout ?? { write: process.stdout.write.bind(process.stdout) };
2152
- const stderr = options.stderr ?? { write: process.stderr.write.bind(process.stderr) };
2153
- const source = options.source ?? "cli";
2154
- const now = options.now ?? currentPresentationTimestamp;
2155
- let sequence = 0;
2156
- const write = (channel, target, text, eventType) => {
2157
- const event = createPresentationEvent({
2158
- source,
2159
- sequence: ++sequence,
2160
- at: now(),
2161
- eventType,
2162
- payload: { channel, text }
2163
- });
2164
- streamBridgeSuppressionDepth += 1;
2165
- try {
2166
- target.write(text);
2167
- } finally {
2168
- streamBridgeSuppressionDepth -= 1;
2169
- }
2170
- options.onEvent?.(event);
2171
- return event;
2172
- };
2173
- return {
2174
- stdout(text, eventType = "output.stdout") {
2175
- return write("stdout", stdout, text, eventType);
2176
- },
2177
- stderr(text, eventType = "output.stderr") {
2178
- return write("stderr", stderr, text, eventType);
2179
- }
2180
- };
2181
- }
2182
- function createConsolePresentationOutput(options = {}) {
2183
- const source = options.source ?? "cli";
2184
- const now = options.now ?? currentPresentationTimestamp;
2185
- let sequence = 0;
2186
- const emit = (channel, text, eventType) => {
2187
- const event = createPresentationEvent({
2188
- source,
2189
- sequence: ++sequence,
2190
- at: now(),
2191
- eventType,
2192
- payload: { channel, text }
2193
- });
2194
- streamBridgeSuppressionDepth += 1;
2195
- try {
2196
- if (channel === "stdout") {
2197
- (options.log ?? console.log)(text);
2198
- } else {
2199
- (options.error ?? console.error)(text);
2200
- }
2201
- } finally {
2202
- streamBridgeSuppressionDepth -= 1;
2203
- }
2204
- options.onEvent?.(event);
2205
- return event;
2206
- };
2207
- return {
2208
- stdout(text, eventType = "output.stdout") {
2209
- return emit("stdout", text, eventType);
2210
- },
2211
- stderr(text, eventType = "output.stderr") {
2212
- return emit("stderr", text, eventType);
2213
- }
2214
- };
2215
- }
2216
- var consolePresentationOutput = createConsolePresentationOutput({
2217
- onEvent(event) {
2218
- presentationEventBus.emit(event);
2219
- }
2220
- });
2221
- function installConsolePresentationBridge(options = {}) {
2222
- const target = options.target ?? console;
2223
- const originals = {
2224
- log: target.log,
2225
- info: target.info,
2226
- warn: target.warn,
2227
- error: target.error
2228
- };
2229
- const source = options.source ?? "cli";
2230
- const now = options.now ?? currentPresentationTimestamp;
2231
- let sequence = 0;
2232
- const emit = (method, channel, args, eventType) => {
2233
- const text = format(...args);
2234
- if (streamBridgeSuppressionDepth > 0) {
2235
- originals[method].call(target, text);
2236
- return;
2237
- }
2238
- const event = createPresentationEvent({
2239
- source,
2240
- sequence: ++sequence,
2241
- at: now(),
2242
- eventType,
2243
- payload: { channel, text }
2244
- });
2245
- streamBridgeSuppressionDepth += 1;
2246
- try {
2247
- originals[method].call(target, text);
2248
- } finally {
2249
- streamBridgeSuppressionDepth -= 1;
2250
- }
2251
- presentationEventBus.emit(event);
2252
- options.onEvent?.(event);
2253
- };
2254
- const bridgedLog = (...args) => emit("log", "stdout", args, "output.console.log");
2255
- const bridgedInfo = (...args) => emit("info", "stdout", args, "output.console.info");
2256
- const bridgedWarn = (...args) => emit("warn", "stderr", args, "output.console.warn");
2257
- const bridgedError = (...args) => emit("error", "stderr", args, "output.console.error");
2258
- target.log = bridgedLog;
2259
- target.info = bridgedInfo;
2260
- target.warn = bridgedWarn;
2261
- target.error = bridgedError;
2262
- return {
2263
- restore() {
2264
- if (target.log === bridgedLog) target.log = originals.log;
2265
- if (target.info === bridgedInfo) target.info = originals.info;
2266
- if (target.warn === bridgedWarn) target.warn = originals.warn;
2267
- if (target.error === bridgedError) target.error = originals.error;
2268
- }
2269
- };
2270
- }
2271
- function installProcessPresentationStreamBridge(options = {}) {
2272
- if (activeStreamBridge) return activeStreamBridge;
2273
- const source = options.source ?? "cli";
2274
- const now = options.now ?? currentPresentationTimestamp;
2275
- let sequence = 0;
2276
- let suspended = false;
2277
- const streams = [];
2278
- const emit = (channel, chunk, encoding) => {
2279
- const text = typeof chunk === "string" ? chunk : chunk instanceof Uint8Array ? Buffer.from(chunk).toString(typeof encoding === "string" ? encoding : "utf8") : String(chunk ?? "");
2280
- if (channel === "stdout" && text.startsWith("ZERO_EVENT_")) return;
2281
- const event = createPresentationEvent({
2282
- source,
2283
- sequence: ++sequence,
2284
- at: now(),
2285
- eventType: channel === "stdout" ? "output.stream.stdout" : "output.stream.stderr",
2286
- payload: { channel, text }
2287
- });
2288
- presentationEventBus.emit(event);
2289
- options.onEvent?.(event);
2290
- };
2291
- for (const [channel, stream] of [
2292
- ["stdout", options.stdout ?? process.stdout],
2293
- ["stderr", options.stderr ?? process.stderr]
2294
- ]) {
2295
- const original = stream.write;
2296
- const patched = ((chunk, encoding, callback) => {
2297
- const result = original.call(stream, chunk, encoding, callback);
2298
- if (!suspended && streamBridgeSuppressionDepth === 0) emit(channel, chunk, encoding);
2299
- return result;
2300
- });
2301
- streams.push({ channel, stream, original, patched });
2302
- }
2303
- const bridge = {
2304
- suspend() {
2305
- if (suspended) return;
2306
- suspended = true;
2307
- for (const item of streams) {
2308
- if (item.stream.write === item.patched) item.stream.write = item.original;
2309
- }
2310
- },
2311
- resume() {
2312
- if (!suspended) return;
2313
- suspended = false;
2314
- for (const item of streams) {
2315
- if (item.stream.write === item.original) item.stream.write = item.patched;
2316
- }
2317
- },
2318
- restore() {
2319
- for (const item of streams) {
2320
- if (item.stream.write === item.patched) item.stream.write = item.original;
2321
- }
2322
- if (activeStreamBridge === bridge) activeStreamBridge = null;
2323
- }
2324
- };
2325
- for (const item of streams) item.stream.write = item.patched;
2326
- activeStreamBridge = bridge;
2327
- return bridge;
2328
- }
2329
- function suspendProcessPresentationStreamBridge() {
2330
- activeStreamBridge?.suspend();
2331
- }
2332
- function resumeProcessPresentationStreamBridge() {
2333
- activeStreamBridge?.resume();
2334
- }
2335
- var processPresentationOutput = createProcessPresentationOutput({
2336
- onEvent(event) {
2337
- presentationEventBus.emit(event);
2338
- }
2339
- });
2340
- function writePresentationLine(text, eventType = "output.stdout") {
2341
- return processPresentationOutput.stdout(`${text}
2342
- `, eventType);
2343
- }
2344
-
2345
- // packages/cli/src/tui/provider-status.ts
2346
- var PROVIDER_DEFS = [
2347
- {
2348
- id: "chatgpt-codex",
2349
- label: "ChatGPT Codex",
2350
- methods: ["oauth"],
2351
- // OAuth, not an API key. Both tokens are accepted and the access token is
2352
- // read first (llm-api.ts L874-875, L1386-1394), so it leads the list.
2353
- envVars: ["ZERO_CHATGPT_ACCESS_TOKEN", "ZERO_CHATGPT_OAUTH_REFRESH_TOKEN"],
2354
- fileSource: "~/.codex/auth.json (override with ZERO_CHATGPT_AUTH_FILE)",
2355
- hint: "run `codex login` to write ~/.codex/auth.json, or invoke 0 with env ZERO_CHATGPT_OAUTH_REFRESH_TOKEN=..."
2356
- },
2357
- {
2358
- id: "deepseek",
2359
- label: "DeepSeek",
2360
- methods: ["api-key"],
2361
- envVars: ["DEEPSEEK_API_KEY"],
2362
- hint: "set DEEPSEEK_API_KEY (endpoint override: DEEPSEEK_BASE_URL)"
2363
- },
2364
- {
2365
- id: "openrouter",
2366
- label: "OpenRouter",
2367
- // OAuth (PKCE browser sign-in) is preferred; a pasted OPENROUTER_API_KEY
2368
- // remains a secondary path. The browser flow provisions a durable
2369
- // `sk-or-...` key and stores it as an api_key record written to
2370
- // OPENROUTER_API_KEY (envVars[0], which llm-api already reads), so — unlike
2371
- // the xai/kimi device flows — the credential is a key, not OAuth tokens.
2372
- methods: ["oauth", "api-key"],
2373
- envVars: ["OPENROUTER_API_KEY"],
2374
- hint: "sign in with your OpenRouter account, or set OPENROUTER_API_KEY=sk-or-... from openrouter.ai/keys"
2375
- },
2376
- {
2377
- id: "azure",
2378
- label: "Azure OpenAI",
2379
- methods: ["api-key"],
2380
- // Only the Azure key authenticates; the deployment URL comes from
2381
- // AZURE_OPENAI_BASE_URL / OPENAI_BASE_URL / ~/.codex/config.toml
2382
- // (L1435-1445). A key with no reachable base URL still counts as
2383
- // configured here because detectProvider selects azure on the key alone.
2384
- envVars: ["AZURE_OPENAI_API_KEY"],
2385
- hint: "set AZURE_OPENAI_API_KEY plus AZURE_OPENAI_BASE_URL (or [model_providers.azure] in ~/.codex/config.toml)"
2386
- },
2387
- {
2388
- id: "openai",
2389
- label: "OpenAI",
2390
- methods: ["api-key"],
2391
- envVars: ["OPENAI_API_KEY"],
2392
- hint: "set OPENAI_API_KEY=sk-... from platform.openai.com/api-keys"
2393
- },
2394
- {
2395
- id: "z-ai",
2396
- label: "Z.ai GLM",
2397
- methods: ["api-key"],
2398
- envVars: ["Z_AI_API_KEY"],
2399
- hint: "set Z_AI_API_KEY from your Z.ai Coding Plan (endpoint override: Z_AI_BASE_URL)"
2400
- },
2401
- {
2402
- id: "kimi",
2403
- label: "Moonshot Kimi",
2404
- // OAuth (device sign-in) is preferred; a pasted KIMI_API_KEY remains a
2405
- // secondary path. The OAuth access token is written to KIMI_API_KEY as a
2406
- // Bearer (llm-api.ts reads it there, no change needed), and the refresh
2407
- // token to the 0-owned var so the store can round-trip it.
2408
- methods: ["oauth", "api-key"],
2409
- envVars: ["KIMI_API_KEY", "ZERO_KIMI_OAUTH_REFRESH_TOKEN"],
2410
- hint: "sign in with your Kimi account, or set KIMI_API_KEY from your Kimi coding plan (endpoint override: KIMI_BASE_URL)"
2411
- },
2412
- {
2413
- id: "qwen",
2414
- label: "Alibaba Qwen",
2415
- methods: ["api-key"],
2416
- envVars: ["QWEN_API_KEY"],
2417
- hint: "set QWEN_API_KEY from Alibaba Model Studio (endpoint override: QWEN_BASE_URL)"
2418
- },
2419
- {
2420
- id: "xai",
2421
- label: "xAI Grok",
2422
- // OAuth (device sign-in) is preferred; a pasted XAI_API_KEY remains a
2423
- // secondary path. The OAuth access token is written to XAI_API_KEY as a
2424
- // Bearer (llm-api.ts reads it there, no change needed), and the refresh
2425
- // token to the 0-owned var so the store can round-trip it.
2426
- methods: ["oauth", "api-key"],
2427
- envVars: ["XAI_API_KEY", "ZERO_XAI_OAUTH_REFRESH_TOKEN"],
2428
- hint: "sign in with your xAI account, or set XAI_API_KEY from console.x.ai (endpoint override: XAI_BASE_URL)"
2429
- },
2430
- {
2431
- id: "opencode",
2432
- label: "OpenCode Zen",
2433
- methods: ["api-key"],
2434
- envVars: ["OPENCODE_API_KEY"],
2435
- hint: "set OPENCODE_API_KEY from opencode.ai/auth (endpoint override: OPENCODE_BASE_URL)"
2436
- },
2437
- {
2438
- id: "copilot",
2439
- label: "GitHub Copilot",
2440
- // OAuth only (device-code sign-in). The GitHub device-flow access token is
2441
- // written to ZERO_COPILOT_GITHUB_TOKEN (envVars[0]) and sent directly as a
2442
- // Bearer to api.githubcopilot.com — no secondary exchange, no refresh, so
2443
- // there is no refresh-token env var.
2444
- methods: ["oauth"],
2445
- envVars: ["ZERO_COPILOT_GITHUB_TOKEN"],
2446
- hint: "sign in with your GitHub Copilot account (device sign-in), or set ZERO_COPILOT_GITHUB_TOKEN=... (endpoint override: COPILOT_BASE_URL)"
2447
- },
2448
- {
2449
- id: "google",
2450
- label: "Google Gemini (Code Assist)",
2451
- // OAuth only (PKCE browser sign-in — the Gemini CLI flow). The minted
2452
- // access token is written to ZERO_GEMINI_ACCESS_TOKEN (envVars[0]) and the
2453
- // refresh token to ZERO_GEMINI_OAUTH_REFRESH_TOKEN (the /REFRESH/i var); the
2454
- // runtime refreshes on demand against oauth2.googleapis.com. There is no
2455
- // pasted-key equivalent — Code Assist authenticates only via OAuth.
2456
- methods: ["oauth"],
2457
- envVars: ["ZERO_GEMINI_ACCESS_TOKEN", "ZERO_GEMINI_OAUTH_REFRESH_TOKEN"],
2458
- hint: "sign in with your Google account (browser sign-in), or set ZERO_GEMINI_OAUTH_REFRESH_TOKEN=... for Gemini Code Assist (project override: GOOGLE_CLOUD_PROJECT / ZERO_GEMINI_PROJECT)"
2459
- },
2460
- {
2461
- id: "anthropic",
2462
- label: "Anthropic",
2463
- // API key only for now. The schema supports OAuth, but Anthropic/Claude
2464
- // subscription OAuth is deliberately deferred — do not add "oauth" here
2465
- // until that workstream lands.
2466
- methods: ["api-key"],
2467
- envVars: ["ANTHROPIC_API_KEY"],
2468
- hint: "set ANTHROPIC_API_KEY=sk-ant-... from console.anthropic.com"
2469
- }
2470
- ];
2471
- function primaryMethod(methods) {
2472
- return methods[0] ?? "api-key";
2473
- }
2474
- var PROVIDERS = PROVIDER_DEFS.map((def) => ({
2475
- ...def,
2476
- auth: primaryMethod(def.methods)
2477
- }));
2478
- function providerSupportsMethod(providerId, method) {
2479
- const info = PROVIDERS.find((candidate) => candidate.id === providerId);
2480
- return info !== void 0 && info.methods.includes(method);
2481
- }
2482
- function hasCredential(value) {
2483
- return typeof value === "string" && value.trim().length > 0;
2484
- }
2485
- function satisfyingVar(info, env) {
2486
- return info.envVars.find((name) => hasCredential(env[name]));
2487
- }
2488
- function providerStates(env) {
2489
- return PROVIDERS.map((info) => {
2490
- const via = satisfyingVar(info, env);
2491
- return via === void 0 ? { ...info, configured: false } : { ...info, configured: true, via };
2492
- });
2646
+ theme: DEFAULT_THEME_NAME,
2647
+ allowModelSelfExtension: DEFAULT_ALLOW_MODEL_SELF_EXTENSION,
2648
+ allowDevSourceUpdates: false,
2649
+ autoEvolveFinderLenses: false,
2650
+ autoPromoteFinderLenses: false,
2651
+ showTokenUsage: false,
2652
+ showCost: false,
2653
+ showContextMeter: true,
2654
+ modelDisplay: "statusbar",
2655
+ elapsedTimer: "left",
2656
+ logoAnimation: "glitch",
2657
+ reduceMotion: false,
2658
+ analyticsLevel: "off",
2659
+ diagnosticReporting: "ask",
2660
+ diagnosticReportingPrompted: false,
2661
+ updatePolicy: "automatic",
2662
+ symbolPreset: "unicode",
2663
+ rosterSort: "attention",
2664
+ leaderKey: "off",
2665
+ keybindings: {},
2666
+ modelPreference: null
2667
+ };
2668
+ var SETTINGS_FILENAME = "tui-settings.json";
2669
+ function settingsFilePath(homeDir) {
2670
+ return join6(homeStateDir(homeDir), SETTINGS_FILENAME);
2493
2671
  }
2494
- function isProviderConfigured(providerId, env) {
2495
- const info = PROVIDERS.find((candidate) => candidate.id === providerId);
2496
- return info !== void 0 && satisfyingVar(info, env) !== void 0;
2672
+ function projectStateDir(projectDir = process.cwd()) {
2673
+ return join6(projectDir, ".0");
2497
2674
  }
2498
-
2499
- // packages/cli/src/tui/credential-store.ts
2500
- import { chmodSync, mkdirSync as mkdirSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "node:fs";
2501
- import { dirname as dirname3, join as join5 } from "node:path";
2502
- var CREDENTIALS_FILENAME = "credentials.json";
2503
- var FILE_MODE = 384;
2504
- var DIR_MODE = 448;
2505
- function credentialsFilePath(homeDir2) {
2506
- return join5(homeStateDir(homeDir2), CREDENTIALS_FILENAME);
2507
- }
2508
- var STORABLE_PROVIDER_IDS = new Set(
2509
- PROVIDERS.filter((provider) => provider.methods.includes("api-key")).map((provider) => provider.id)
2510
- );
2511
- function hasCredential2(value) {
2512
- return typeof value === "string" && value.trim().length > 0;
2675
+ function projectSettingsFilePath(projectDir = process.cwd()) {
2676
+ return join6(projectStateDir(projectDir), SETTINGS_FILENAME);
2513
2677
  }
2514
- function normalizeCredentials(raw) {
2515
- const out = {};
2516
- if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return out;
2517
- for (const [providerId, value] of Object.entries(raw)) {
2518
- if (!STORABLE_PROVIDER_IDS.has(providerId)) continue;
2519
- if (typeof value !== "string") continue;
2520
- const secret = value.trim();
2521
- if (secret.length === 0) continue;
2522
- out[providerId] = secret;
2678
+ function readRawSettingsFile(path) {
2679
+ try {
2680
+ return JSON.parse(readFileSync2(path, "utf8"));
2681
+ } catch {
2682
+ return void 0;
2523
2683
  }
2524
- return out;
2525
2684
  }
2526
- function loadCredentials(homeDir2) {
2527
- return projectApiKeys(loadAccountStore(homeDir2));
2528
- }
2529
- function saveCredentials(creds, homeDir2) {
2530
- const store = loadAccountStore(homeDir2);
2531
- const desired = normalizeCredentials(creds);
2532
- return saveAccountStore(replaceApiKeyLayer(store, desired), homeDir2);
2685
+ function projectSettingsExist(projectDir = process.cwd()) {
2686
+ const raw = readRawSettingsFile(projectSettingsFilePath(projectDir));
2687
+ return typeof raw === "object" && raw !== null && !Array.isArray(raw);
2533
2688
  }
2534
- function projectApiKeys(store) {
2535
- const out = {};
2536
- for (const providerId of Object.keys(store.providers)) {
2537
- const record = getActiveAccount(store, providerId);
2538
- if (record?.kind === "api_key") out[providerId] = record.secret;
2689
+ function strictValueAt(raw, key) {
2690
+ const value = rawValue(raw, key);
2691
+ if (value === void 0) return void 0;
2692
+ if (key === "onboardingCompleted" || key === "diagnosticReportingPrompted") {
2693
+ return typeof value === "boolean" ? value : void 0;
2539
2694
  }
2540
- return out;
2695
+ if (value === false && (key === "diagnosticReporting" || key === "updatePolicy")) {
2696
+ return "off";
2697
+ }
2698
+ if (key === "theme") {
2699
+ return typeof value === "string" && isKnownTheme(value) ? value : void 0;
2700
+ }
2701
+ if (key === "keybindings") {
2702
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? sanitizeKeybindingOverrides(value) : void 0;
2703
+ }
2704
+ if (key === "modelPreference") {
2705
+ return normalizeModelPreference(value);
2706
+ }
2707
+ const def = DEF_BY_KEY.get(key);
2708
+ if (def?.kind === "boolean") {
2709
+ return typeof value === "boolean" ? value : void 0;
2710
+ }
2711
+ const choices = def?.choices ?? [];
2712
+ return typeof value === "string" && choices.includes(value) ? value : void 0;
2713
+ }
2714
+ function isOperatorSetting(key) {
2715
+ return key === "onboardingCompleted" || key === "analyticsLevel" || key === "diagnosticReporting" || key === "diagnosticReportingPrompted" || key === "updatePolicy" || key === "allowDevSourceUpdates" || key === "executionProfile" || key === "modelPreference";
2541
2716
  }
2542
- function replaceApiKeyLayer(store, desired) {
2543
- const providers = {};
2544
- for (const [providerId, entry] of Object.entries(store.providers)) {
2545
- const kept = {};
2546
- for (const [accountId, record] of Object.entries(entry.accounts)) {
2547
- if (record.kind !== "api_key") kept[accountId] = record;
2717
+ function resolveLayeredSettings(globalRaw, projectRaw) {
2718
+ const settings = {};
2719
+ const sources = {};
2720
+ for (const key of Object.keys(DEFAULT_SETTINGS)) {
2721
+ const projectValue = isOperatorSetting(key) ? void 0 : strictValueAt(projectRaw, key);
2722
+ if (projectValue !== void 0) {
2723
+ settings[key] = projectValue;
2724
+ sources[key] = "project";
2725
+ continue;
2548
2726
  }
2549
- const keptIds = Object.keys(kept);
2550
- if (keptIds.length === 0) continue;
2551
- const active = entry.activeAccountId !== void 0 && kept[entry.activeAccountId] !== void 0 ? entry.activeAccountId : keptIds[0];
2552
- providers[providerId] = { activeAccountId: active, accounts: kept };
2553
- }
2554
- for (const [providerId, secret] of Object.entries(desired)) {
2555
- const existing = providers[providerId];
2556
- const accounts = existing ? { ...existing.accounts } : {};
2557
- accounts[DEFAULT_ACCOUNT_ID] = { kind: "api_key", secret };
2558
- providers[providerId] = { activeAccountId: DEFAULT_ACCOUNT_ID, accounts };
2559
- }
2560
- return { version: STORE_VERSION, providers };
2561
- }
2562
- function credentialEnvPatch(creds, env) {
2563
- const stored = normalizeCredentials(creds);
2564
- const patch = {};
2565
- for (const info of PROVIDERS) {
2566
- const secret = stored[info.id];
2567
- if (secret === void 0) continue;
2568
- if (info.envVars.some((name) => hasCredential2(env[name]))) continue;
2569
- const target = info.envVars[0];
2570
- if (target === void 0) continue;
2571
- patch[target] = secret;
2572
- }
2573
- return patch;
2574
- }
2575
- var MASK = "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022";
2576
- var TAIL = 4;
2577
- var PREFIX = 6;
2578
- var MIN_LENGTH_FOR_TAIL = 12;
2579
- var MIN_LENGTH_FOR_PREFIX = 20;
2580
- function redactSecret(secret) {
2581
- const value = typeof secret === "string" ? secret.trim() : "";
2582
- if (value.length < MIN_LENGTH_FOR_TAIL) return MASK;
2583
- const tail = value.slice(-TAIL);
2584
- if (value.length < MIN_LENGTH_FOR_PREFIX) return `${MASK}${tail}`;
2585
- return `${value.slice(0, PREFIX)}\u2026${tail}`;
2586
- }
2587
- var STORE_VERSION = 2;
2588
- var DEFAULT_ACCOUNT_ID = "default";
2589
- var KNOWN_PROVIDER_IDS = new Set(PROVIDERS.map((provider) => provider.id));
2590
- function emptyStore() {
2591
- return { version: STORE_VERSION, providers: {} };
2592
- }
2593
- function isPlainObject(value) {
2594
- return typeof value === "object" && value !== null && !Array.isArray(value);
2595
- }
2596
- function isSafeKey(key) {
2597
- return key.length > 0 && key !== "__proto__" && key !== "constructor" && key !== "prototype";
2598
- }
2599
- function trimmedString(value) {
2600
- if (typeof value !== "string") return void 0;
2601
- const trimmed = value.trim();
2602
- return trimmed.length > 0 ? trimmed : void 0;
2603
- }
2604
- function normalizeTokens(raw) {
2605
- if (!isPlainObject(raw)) return void 0;
2606
- const tokens = {};
2607
- const accessToken = trimmedString(raw.accessToken);
2608
- const refreshToken = trimmedString(raw.refreshToken);
2609
- if (accessToken !== void 0) tokens.accessToken = accessToken;
2610
- if (refreshToken !== void 0) tokens.refreshToken = refreshToken;
2611
- if (tokens.accessToken === void 0 && tokens.refreshToken === void 0) return void 0;
2612
- if (typeof raw.expiresAt === "number" && Number.isFinite(raw.expiresAt)) tokens.expiresAt = raw.expiresAt;
2613
- const tokenType = trimmedString(raw.tokenType);
2614
- if (tokenType !== void 0) tokens.tokenType = tokenType;
2615
- const scope = trimmedString(raw.scope);
2616
- if (scope !== void 0) tokens.scope = scope;
2617
- return tokens;
2618
- }
2619
- function normalizeIdentity(raw) {
2620
- if (!isPlainObject(raw)) return void 0;
2621
- const identity = {};
2622
- const label = trimmedString(raw.label);
2623
- const accountId = trimmedString(raw.accountId);
2624
- const email = trimmedString(raw.email);
2625
- if (label !== void 0) identity.label = label;
2626
- if (accountId !== void 0) identity.accountId = accountId;
2627
- if (email !== void 0) identity.email = email;
2628
- return Object.keys(identity).length > 0 ? identity : void 0;
2629
- }
2630
- function normalizeRecord(providerId, raw) {
2631
- if (!isPlainObject(raw)) return void 0;
2632
- if (raw.kind === "api_key") {
2633
- if (!providerSupportsMethod(providerId, "api-key")) return void 0;
2634
- const secret = trimmedString(raw.secret);
2635
- return secret === void 0 ? void 0 : { kind: "api_key", secret };
2636
- }
2637
- if (raw.kind === "oauth") {
2638
- if (!providerSupportsMethod(providerId, "oauth")) return void 0;
2639
- const tokens = normalizeTokens(raw.tokens);
2640
- if (tokens === void 0) return void 0;
2641
- const identity = normalizeIdentity(raw.identity);
2642
- return identity === void 0 ? { kind: "oauth", tokens } : { kind: "oauth", tokens, identity };
2643
- }
2644
- return void 0;
2645
- }
2646
- function normalizeProviderAccounts(providerId, raw) {
2647
- if (!isPlainObject(raw)) return void 0;
2648
- const accountsRaw = isPlainObject(raw.accounts) ? raw.accounts : {};
2649
- const accounts = {};
2650
- for (const [accountId, recordRaw] of Object.entries(accountsRaw)) {
2651
- if (!isSafeKey(accountId)) continue;
2652
- const record = normalizeRecord(providerId, recordRaw);
2653
- if (record !== void 0) accounts[accountId] = record;
2654
- }
2655
- const ids = Object.keys(accounts);
2656
- if (ids.length === 0) return void 0;
2657
- const active = typeof raw.activeAccountId === "string" && accounts[raw.activeAccountId] !== void 0 ? raw.activeAccountId : ids[0];
2658
- return { activeAccountId: active, accounts };
2659
- }
2660
- function normalizeAccountStore(raw) {
2661
- if (!isPlainObject(raw)) return emptyStore();
2662
- const looksV2 = isPlainObject(raw.providers) || raw.version !== void 0;
2663
- const store = emptyStore();
2664
- if (looksV2) {
2665
- const providersRaw = isPlainObject(raw.providers) ? raw.providers : {};
2666
- for (const [providerId, entry] of Object.entries(providersRaw)) {
2667
- if (!isSafeKey(providerId) || !KNOWN_PROVIDER_IDS.has(providerId)) continue;
2668
- const accounts = normalizeProviderAccounts(providerId, entry);
2669
- if (accounts !== void 0) store.providers[providerId] = accounts;
2727
+ const globalValue = strictValueAt(globalRaw, key);
2728
+ if (globalValue !== void 0) {
2729
+ settings[key] = globalValue;
2730
+ sources[key] = "global";
2731
+ continue;
2670
2732
  }
2671
- return store;
2733
+ settings[key] = DEFAULT_SETTINGS[key];
2734
+ sources[key] = "default";
2672
2735
  }
2673
- for (const [providerId, secret] of Object.entries(normalizeCredentials(raw))) {
2674
- store.providers[providerId] = {
2675
- activeAccountId: DEFAULT_ACCOUNT_ID,
2676
- accounts: { [DEFAULT_ACCOUNT_ID]: { kind: "api_key", secret } }
2677
- };
2736
+ return { settings, sources };
2737
+ }
2738
+ function loadLayeredSettings(opts = {}) {
2739
+ syncThemeChoices(opts.homeDir);
2740
+ const globalRaw = readRawSettingsFile(settingsFilePath(opts.homeDir));
2741
+ const projectRaw = readRawSettingsFile(projectSettingsFilePath(opts.projectDir));
2742
+ return resolveLayeredSettings(globalRaw, projectRaw);
2743
+ }
2744
+ function rawValue(raw, key) {
2745
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return void 0;
2746
+ const value = raw[key];
2747
+ if (key === "analyticsLevel" && value !== void 0) {
2748
+ return value === "usage" || value === "commands" || value === "full" ? "usage" : "off";
2678
2749
  }
2679
- return store;
2750
+ return key === "transcriptStyle" && value === "messenger" ? "bubble" : value;
2680
2751
  }
2681
- function loadAccountStore(homeDir2) {
2682
- try {
2683
- const text = readFileSync4(credentialsFilePath(homeDir2), "utf8");
2684
- return normalizeAccountStore(JSON.parse(text));
2685
- } catch {
2686
- return emptyStore();
2752
+ function booleanAt(raw, key) {
2753
+ const value = rawValue(raw, key);
2754
+ return typeof value === "boolean" ? value : DEFAULT_SETTINGS[key];
2755
+ }
2756
+ function enumAt(raw, key) {
2757
+ const def = DEF_BY_KEY.get(key);
2758
+ const value = rawValue(raw, key);
2759
+ const choices = def?.choices ?? [];
2760
+ return typeof value === "string" && choices.includes(value) ? value : DEFAULT_SETTINGS[key];
2761
+ }
2762
+ function themeAt(raw) {
2763
+ const value = rawValue(raw, "theme");
2764
+ return typeof value === "string" && isKnownTheme(value) ? value : DEFAULT_SETTINGS.theme;
2765
+ }
2766
+ function keybindingsAt(raw) {
2767
+ return sanitizeKeybindingOverrides(rawValue(raw, "keybindings"));
2768
+ }
2769
+ function normalizeModelPreference(raw) {
2770
+ if (raw === null) return null;
2771
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return void 0;
2772
+ const value = raw;
2773
+ if (typeof value.providerId !== "string" || !value.providerId.trim() || typeof value.model !== "string" || !value.model.trim() || typeof value.connectionIdentity !== "string" || !/^[a-f0-9]{64}$/.test(value.connectionIdentity)) return void 0;
2774
+ return { providerId: value.providerId, model: value.model, connectionIdentity: value.connectionIdentity };
2775
+ }
2776
+ function normalizeSettings(raw) {
2777
+ return {
2778
+ showStatusBar: booleanAt(raw, "showStatusBar"),
2779
+ showComposerHints: booleanAt(raw, "showComposerHints"),
2780
+ composerSuggestions: booleanAt(raw, "composerSuggestions"),
2781
+ mouseSupport: booleanAt(raw, "mouseSupport"),
2782
+ showLogo: booleanAt(raw, "showLogo"),
2783
+ showRuntimeNotices: booleanAt(raw, "showRuntimeNotices"),
2784
+ showTurnSummary: booleanAt(raw, "showTurnSummary"),
2785
+ showSubagents: booleanAt(raw, "showSubagents"),
2786
+ showTimestamps: booleanAt(raw, "showTimestamps"),
2787
+ showObjective: booleanAt(raw, "showObjective"),
2788
+ showScope: booleanAt(raw, "showScope"),
2789
+ density: enumAt(raw, "density"),
2790
+ composerStyle: enumAt(raw, "composerStyle"),
2791
+ busyInputMode: enumAt(raw, "busyInputMode"),
2792
+ onboardingCompleted: booleanAt(raw, "onboardingCompleted"),
2793
+ executionProfile: enumAt(raw, "executionProfile"),
2794
+ allowSubagentPeerMessaging: booleanAt(raw, "allowSubagentPeerMessaging"),
2795
+ allowSubagentOperatorMessaging: booleanAt(raw, "allowSubagentOperatorMessaging"),
2796
+ transcriptStyle: enumAt(raw, "transcriptStyle"),
2797
+ roleLabelStyle: enumAt(raw, "roleLabelStyle"),
2798
+ toolCardStyle: enumAt(raw, "toolCardStyle"),
2799
+ richToolCards: booleanAt(raw, "richToolCards"),
2800
+ transcriptDetail: enumAt(raw, "transcriptDetail"),
2801
+ autoCompaction: booleanAt(raw, "autoCompaction"),
2802
+ compactionThreshold: enumAt(raw, "compactionThreshold"),
2803
+ theme: themeAt(raw),
2804
+ allowModelSelfExtension: booleanAt(raw, "allowModelSelfExtension"),
2805
+ allowDevSourceUpdates: booleanAt(raw, "allowDevSourceUpdates"),
2806
+ autoEvolveFinderLenses: booleanAt(raw, "autoEvolveFinderLenses"),
2807
+ autoPromoteFinderLenses: booleanAt(raw, "autoPromoteFinderLenses"),
2808
+ showTokenUsage: booleanAt(raw, "showTokenUsage"),
2809
+ showCost: booleanAt(raw, "showCost"),
2810
+ showContextMeter: booleanAt(raw, "showContextMeter"),
2811
+ modelDisplay: enumAt(raw, "modelDisplay"),
2812
+ elapsedTimer: enumAt(raw, "elapsedTimer"),
2813
+ logoAnimation: enumAt(raw, "logoAnimation"),
2814
+ reduceMotion: booleanAt(raw, "reduceMotion"),
2815
+ analyticsLevel: enumAt(raw, "analyticsLevel"),
2816
+ diagnosticReporting: strictValueAt(raw, "diagnosticReporting") ?? DEFAULT_SETTINGS.diagnosticReporting,
2817
+ diagnosticReportingPrompted: booleanAt(raw, "diagnosticReportingPrompted"),
2818
+ updatePolicy: strictValueAt(raw, "updatePolicy") ?? DEFAULT_SETTINGS.updatePolicy,
2819
+ symbolPreset: enumAt(raw, "symbolPreset"),
2820
+ rosterSort: enumAt(raw, "rosterSort"),
2821
+ leaderKey: enumAt(raw, "leaderKey"),
2822
+ keybindings: keybindingsAt(raw),
2823
+ modelPreference: normalizeModelPreference(rawValue(raw, "modelPreference")) ?? null
2824
+ };
2825
+ }
2826
+ function loadSettings(homeDir, projectDir) {
2827
+ return loadLayeredSettings({ homeDir, projectDir }).settings;
2828
+ }
2829
+ function loadGlobalSettings(homeDir, options = {}) {
2830
+ syncThemeChoices(homeDir);
2831
+ const raw = readRawSettingsFile(settingsFilePath(homeDir));
2832
+ if (options.requireExecutionProfile && strictValueAt(raw, "executionProfile") === void 0) {
2833
+ throw new Error("Configured workbench execution profile is missing or invalid. Run 0 workbench setup or 0 workbench disable to make an explicit operator choice; host fallback is refused.");
2687
2834
  }
2835
+ return normalizeSettings(raw);
2688
2836
  }
2689
- function saveAccountStore(store, homeDir2) {
2837
+ function saveSettings(settings, homeDir) {
2838
+ const path = settingsFilePath(homeDir);
2839
+ const temporary = `${path}.${randomUUID2()}.tmp`;
2690
2840
  try {
2691
- const path = credentialsFilePath(homeDir2);
2692
- const dir = dirname3(path);
2693
- mkdirSync3(dir, { recursive: true, mode: DIR_MODE });
2694
- chmodSync(dir, DIR_MODE);
2695
- writeFileSync3(path, `${JSON.stringify(normalizeAccountStore(store), null, 2)}
2696
- `, {
2697
- encoding: "utf8",
2698
- mode: FILE_MODE
2699
- });
2700
- chmodSync(path, FILE_MODE);
2841
+ mkdirSync2(dirname3(path), { recursive: true });
2842
+ writeFileSync2(temporary, `${JSON.stringify(normalizeSettings(settings), null, 2)}
2843
+ `, { encoding: "utf8", flag: "wx", mode: 384 });
2844
+ renameSync(temporary, path);
2701
2845
  return true;
2702
2846
  } catch {
2703
2847
  return false;
2848
+ } finally {
2849
+ try {
2850
+ rmSync2(temporary, { force: true });
2851
+ } catch {
2852
+ }
2704
2853
  }
2705
2854
  }
2706
- function getActiveAccount(store, providerId) {
2707
- const entry = store.providers[providerId];
2708
- if (entry === void 0) return void 0;
2709
- const id = entry.activeAccountId;
2710
- if (id !== void 0 && entry.accounts[id] !== void 0) return entry.accounts[id];
2711
- const first = Object.keys(entry.accounts)[0];
2712
- return first === void 0 ? void 0 : entry.accounts[first];
2713
- }
2714
- function chooseAccountId(record, requested) {
2715
- if (requested !== void 0 && isSafeKey(requested)) return requested;
2716
- if (record.kind === "oauth" && record.identity?.accountId !== void 0 && isSafeKey(record.identity.accountId)) {
2717
- return record.identity.accountId;
2718
- }
2719
- if (record.kind === "api_key") return DEFAULT_ACCOUNT_ID;
2720
- return `account-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
2721
- }
2722
- function addAccount(store, providerId, record, options) {
2723
- if (!KNOWN_PROVIDER_IDS.has(providerId)) {
2724
- throw new Error(`unknown provider "${providerId}"`);
2725
- }
2726
- const method = record.kind === "api_key" ? "api-key" : "oauth";
2727
- if (!providerSupportsMethod(providerId, method)) {
2728
- throw new Error(`provider "${providerId}" does not support ${record.kind} accounts`);
2729
- }
2730
- const accountId = chooseAccountId(record, options?.accountId);
2731
- const existing = store.providers[providerId];
2732
- const accounts = { ...existing?.accounts ?? {} };
2733
- accounts[accountId] = record;
2734
- const makeActive = options?.makeActive ?? true;
2735
- const activeAccountId = makeActive ? accountId : existing?.activeAccountId !== void 0 && accounts[existing.activeAccountId] !== void 0 ? existing.activeAccountId : accountId;
2736
- return {
2737
- store: { version: STORE_VERSION, providers: { ...store.providers, [providerId]: { activeAccountId, accounts } } },
2738
- accountId
2739
- };
2855
+ function sanitizeOverrides(raw) {
2856
+ const out = {};
2857
+ for (const key of Object.keys(DEFAULT_SETTINGS)) {
2858
+ if (isOperatorSetting(key)) continue;
2859
+ const value = strictValueAt(raw, key);
2860
+ if (value !== void 0) out[key] = value;
2861
+ }
2862
+ return out;
2740
2863
  }
2741
- function accountEnvPatch(store, env) {
2742
- const normalized = normalizeAccountStore(store);
2743
- const patch = {};
2744
- for (const info of PROVIDERS) {
2745
- const record = getActiveAccount(normalized, info.id);
2746
- if (record === void 0) continue;
2747
- if (info.envVars.some((name) => hasCredential2(env[name]))) continue;
2748
- if (record.kind === "api_key") {
2749
- const target = info.envVars[0];
2750
- if (target !== void 0) patch[target] = record.secret;
2751
- continue;
2752
- }
2753
- const accessVar = info.envVars[0];
2754
- if (accessVar !== void 0 && record.tokens.accessToken !== void 0) {
2755
- patch[accessVar] = record.tokens.accessToken;
2756
- }
2757
- if (record.tokens.refreshToken !== void 0) {
2758
- const refreshVar = info.envVars.find((name) => /REFRESH/i.test(name));
2759
- if (refreshVar !== void 0) patch[refreshVar] = record.tokens.refreshToken;
2760
- }
2864
+ function readProjectOverrides(projectDir) {
2865
+ return sanitizeOverrides(readRawSettingsFile(projectSettingsFilePath(projectDir)));
2866
+ }
2867
+ function saveProjectOverrides(patch, projectDir) {
2868
+ try {
2869
+ const path = projectSettingsFilePath(projectDir);
2870
+ mkdirSync2(dirname3(path), { recursive: true });
2871
+ writeFileSync2(path, `${JSON.stringify(sanitizeOverrides(patch), null, 2)}
2872
+ `, "utf8");
2873
+ return true;
2874
+ } catch {
2875
+ return false;
2761
2876
  }
2762
- return patch;
2763
2877
  }
2764
2878
 
2765
2879
  // packages/cli/src/tui/feedback.ts
2766
- import { appendFileSync, mkdirSync as mkdirSync4 } from "node:fs";
2767
- import { dirname as dirname4, join as join6 } from "node:path";
2768
- import { homedir as homedir2 } from "node:os";
2769
- function feedbackFilePath(homeDir2) {
2770
- return join6(homeDir2 ?? homedir2(), ".0", "feedback.md");
2880
+ import { appendFileSync, mkdirSync as mkdirSync3 } from "node:fs";
2881
+ import { randomUUID as randomUUID3 } from "node:crypto";
2882
+ import { dirname as dirname4, join as join7 } from "node:path";
2883
+ import { homedir } from "node:os";
2884
+ function feedbackFilePath(homeDir) {
2885
+ return join7(homeDir ?? homedir(), ".0", "feedback.md");
2771
2886
  }
2772
2887
  function formatFeedbackEntry(entry) {
2773
2888
  const context = [
@@ -2781,10 +2896,10 @@ function formatFeedbackEntry(entry) {
2781
2896
  return `${lines.join("\n")}
2782
2897
  `;
2783
2898
  }
2784
- function appendFeedback(entry, homeDir2) {
2785
- const path = feedbackFilePath(homeDir2);
2899
+ function appendFeedback(entry, homeDir) {
2900
+ const path = feedbackFilePath(homeDir);
2786
2901
  try {
2787
- mkdirSync4(dirname4(path), { recursive: true });
2902
+ mkdirSync3(dirname4(path), { recursive: true });
2788
2903
  appendFileSync(path, formatFeedbackEntry(entry), "utf8");
2789
2904
  return { ok: true, path };
2790
2905
  } catch (error) {
@@ -2808,6 +2923,7 @@ function parseFeedbackCommand(raw) {
2808
2923
  }
2809
2924
  var DEFAULT_FEEDBACK_URL = "";
2810
2925
  var FEEDBACK_URL_ENV = "ZERO_FEEDBACK_URL";
2926
+ var SENTRY_DSN_ENV = "ZERO_SENTRY_DSN";
2811
2927
  var CLOUD_FEEDBACK_PATH = "/api/cli-feedback";
2812
2928
  function defaultCloudCredentials(env) {
2813
2929
  try {
@@ -2833,6 +2949,24 @@ function cloudFeedbackUrl(host) {
2833
2949
  return null;
2834
2950
  }
2835
2951
  }
2952
+ function resolveSentryTarget(dsn) {
2953
+ try {
2954
+ const parsed = new URL(dsn);
2955
+ const key = parsed.username;
2956
+ const segments = parsed.pathname.split("/").filter(Boolean);
2957
+ const project = segments.pop();
2958
+ if (parsed.protocol !== "https:" || parsed.password || parsed.search || parsed.hash || !/^[A-Za-z0-9_-]+$/.test(key) || !project || !/^\d+$/.test(project) || segments.some((segment) => !/^[A-Za-z0-9_-]+$/.test(segment))) return { url: "" };
2959
+ parsed.username = "";
2960
+ parsed.pathname = `/${[...segments, "api", project, "envelope"].join("/")}/`;
2961
+ return { url: parsed.toString(), sentryKey: key };
2962
+ } catch {
2963
+ return { url: "" };
2964
+ }
2965
+ }
2966
+ function resolveSubmissionTarget(payload, env, options) {
2967
+ const dsn = env[SENTRY_DSN_ENV]?.trim();
2968
+ return payload.diagnostic && dsn ? resolveSentryTarget(dsn) : resolveFeedbackTarget(env, options);
2969
+ }
2836
2970
  function resolveFeedbackTarget(env = process.env, options = {}) {
2837
2971
  const configured = env[FEEDBACK_URL_ENV]?.trim();
2838
2972
  if (configured) return { url: configured };
@@ -2852,11 +2986,11 @@ function isOptOutSet(value) {
2852
2986
  if (normalized === "") return false;
2853
2987
  return normalized !== "0" && normalized !== "false" && normalized !== "no";
2854
2988
  }
2855
- function submissionBlockedReason(env = process.env, options = {}) {
2989
+ function submissionBlockedReason(env = process.env, options = {}, payload) {
2856
2990
  for (const name of FEEDBACK_OPT_OUT_ENV) {
2857
2991
  if (isOptOutSet(env[name])) return "opt-out";
2858
2992
  }
2859
- return targetBlockedReason(resolveFeedbackTarget(env, options));
2993
+ return targetBlockedReason(payload ? resolveSubmissionTarget(payload, env, options) : resolveFeedbackTarget(env, options));
2860
2994
  }
2861
2995
  function targetBlockedReason(target) {
2862
2996
  if (target === null) return "no-endpoint";
@@ -2866,7 +3000,7 @@ function targetBlockedReason(target) {
2866
3000
  } catch {
2867
3001
  return "insecure-endpoint";
2868
3002
  }
2869
- if (parsed.protocol !== "https:") return "insecure-endpoint";
3003
+ if (parsed.protocol !== "https:" || parsed.username || parsed.password || parsed.hash) return "insecure-endpoint";
2870
3004
  return null;
2871
3005
  }
2872
3006
  function describeSkip(reason) {
@@ -2876,7 +3010,9 @@ function describeSkip(reason) {
2876
3010
  case "no-endpoint":
2877
3011
  return `No feedback endpoint configured (set ${FEEDBACK_URL_ENV}). Saved locally only.`;
2878
3012
  case "insecure-endpoint":
2879
- return `Refusing a non-HTTPS ${FEEDBACK_URL_ENV}. Saved locally only.`;
3013
+ return "Refusing an invalid or non-HTTPS telemetry endpoint. Saved locally only.";
3014
+ case "no-consent":
3015
+ return "Problem report requires confirmation or an automatic-reporting preference. Saved locally only.";
2880
3016
  }
2881
3017
  }
2882
3018
  var SECRET_RULES = [
@@ -2924,30 +3060,38 @@ function serializePayload(payload) {
2924
3060
  }
2925
3061
  function requestHeaders(target, redactAuthorization = false) {
2926
3062
  const headers = { "content-type": "application/json" };
3063
+ if (target.sentryKey) {
3064
+ headers["content-type"] = "application/x-sentry-envelope";
3065
+ headers["x-sentry-auth"] = `Sentry sentry_version=7,sentry_key=${redactAuthorization ? "<redacted>" : target.sentryKey},sentry_client=0-cli/${VERSION}`;
3066
+ }
2927
3067
  if (target.authorization) {
2928
3068
  headers.authorization = redactAuthorization ? "Bearer <redacted>" : target.authorization;
2929
3069
  }
2930
3070
  return headers;
2931
3071
  }
2932
3072
  function buildSubmitPreview(payload, env = process.env, options = {}) {
2933
- if (submissionBlockedReason(env, options) !== null) return null;
2934
- const target = resolveFeedbackTarget(env, options);
2935
- if (target === null) return null;
3073
+ if (FEEDBACK_OPT_OUT_ENV.some((name) => isOptOutSet(env[name]))) return null;
3074
+ const target = resolveSubmissionTarget(payload, env, options);
3075
+ if (target === null || targetBlockedReason(target) !== null) return null;
2936
3076
  return {
2937
3077
  url: target.url,
2938
- body: serializePayload(payload),
3078
+ body: serializeSubmission(payload, target, env),
2939
3079
  headers: requestHeaders(target, true),
2940
- warnings: scanForSecrets(payload.message)
3080
+ warnings: target.sentryKey ? [] : scanForSecrets(payload.message)
2941
3081
  };
2942
3082
  }
2943
3083
  async function submitFeedback(payload, env = process.env, opts = {}) {
2944
- const blocked = submissionBlockedReason(env, opts);
2945
- if (blocked !== null) return { ok: false, skipped: blocked, error: describeSkip(blocked) };
2946
- const target = resolveFeedbackTarget(env, opts);
3084
+ if (FEEDBACK_OPT_OUT_ENV.some((name) => isOptOutSet(env[name]))) {
3085
+ return { ok: false, skipped: "opt-out", error: describeSkip("opt-out") };
3086
+ }
3087
+ if (payload.diagnostic && opts.diagnosticConsent?.confirmed !== true && opts.diagnosticConsent?.policy !== "automatic") {
3088
+ return { ok: false, skipped: "no-consent", error: describeSkip("no-consent") };
3089
+ }
3090
+ const target = resolveSubmissionTarget(payload, env, opts);
2947
3091
  const targetBlocked = targetBlockedReason(target);
2948
3092
  if (targetBlocked !== null) return { ok: false, skipped: targetBlocked, error: describeSkip(targetBlocked) };
2949
3093
  if (target === null) return { ok: false, skipped: "no-endpoint", error: describeSkip("no-endpoint") };
2950
- const body = serializePayload(payload);
3094
+ const body = serializeSubmission(payload, target, env);
2951
3095
  if (Buffer.byteLength(body, "utf8") > MAX_BODY_BYTES) {
2952
3096
  return { ok: false, error: `Message too large to submit (limit ${MAX_BODY_BYTES} bytes).` };
2953
3097
  }
@@ -2957,7 +3101,7 @@ async function submitFeedback(payload, env = process.env, opts = {}) {
2957
3101
  if (target.url !== reviewed.url || body !== reviewed.body || Object.keys(currentHeaders).length !== Object.keys(reviewed.headers).length || !Object.keys(currentHeaders).every((k) => currentHeaders[k] === reviewed.headers[k])) {
2958
3102
  return {
2959
3103
  ok: false,
2960
- error: "Reviewed feedback destination has changed. Preview the updated submission before sending again."
3104
+ error: "Reviewed telemetry destination has changed. Preview the updated submission before sending again."
2961
3105
  };
2962
3106
  }
2963
3107
  }
@@ -2965,10 +3109,10 @@ async function submitFeedback(payload, env = process.env, opts = {}) {
2965
3109
  const timeoutMs = opts.timeoutMs ?? FEEDBACK_TIMEOUT_MS;
2966
3110
  const controller = new AbortController();
2967
3111
  let timer;
2968
- const timeout = new Promise((resolve) => {
3112
+ const timeout = new Promise((resolve2) => {
2969
3113
  timer = setTimeout(() => {
2970
3114
  controller.abort();
2971
- resolve({ ok: false, error: `Feedback submission timed out after ${timeoutMs}ms.` });
3115
+ resolve2({ ok: false, error: `Feedback submission timed out after ${timeoutMs}ms.` });
2972
3116
  }, timeoutMs);
2973
3117
  timer.unref?.();
2974
3118
  });
@@ -2982,8 +3126,8 @@ async function submitFeedback(payload, env = process.env, opts = {}) {
2982
3126
  redirect: "error"
2983
3127
  });
2984
3128
  return response.ok ? { ok: true, status: response.status } : { ok: false, status: response.status, error: `Endpoint returned ${response.status}.` };
2985
- } catch (error) {
2986
- return { ok: false, error: error instanceof Error ? error.message : String(error) };
3129
+ } catch {
3130
+ return { ok: false, error: "Feedback submission failed." };
2987
3131
  }
2988
3132
  })();
2989
3133
  try {
@@ -3031,7 +3175,7 @@ function diagnosticVersion(raw) {
3031
3175
  if (typeof raw !== "string" || raw.length > 128) return "unknown";
3032
3176
  return /^v?(\d{1,6}\.\d{1,6}\.\d{1,6})(?:[-+][a-zA-Z0-9.+-]+)?$/.exec(raw)?.[1] ?? "unknown";
3033
3177
  }
3034
- function buildDiagnosticFeedback(info, env = process.env) {
3178
+ function buildDiagnosticFeedback(info) {
3035
3179
  const kind = info.kind === "tool" ? "tool" : info.kind === "runtime" ? "runtime" : "unknown";
3036
3180
  const platform = DIAGNOSTIC_PLATFORMS.has(info.platform) ? info.platform : "unknown";
3037
3181
  const arch = DIAGNOSTIC_ARCHS.has(info.arch) ? info.arch : "unknown";
@@ -3039,90 +3183,257 @@ function buildDiagnosticFeedback(info, env = process.env) {
3039
3183
  const runtimeVersion = diagnosticVersion(info.runtimeVersion);
3040
3184
  const version = diagnosticVersion(info.version);
3041
3185
  const timestamp = typeof info.timestamp === "string" && /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/.test(info.timestamp) && Number.isFinite(Date.parse(info.timestamp)) ? info.timestamp : (/* @__PURE__ */ new Date()).toISOString();
3042
- let message = `Diagnostic: ${kind} error \u2014 ${platform}/${arch} on ${runtime} ${runtimeVersion}
3186
+ const category = diagnosticError(info.error);
3187
+ const message = `Diagnostic: ${kind} error \u2014 ${platform}/${arch} on ${runtime} ${runtimeVersion}
3043
3188
  Version: ${version}
3044
- Error: ${diagnosticError(info.error)}`;
3045
- if (diagnosticDetailAllowed(env)) {
3046
- const detail = redactDiagnosticDetail(buildDiagnosticDetailText(info));
3047
- if (detail) message = capUtf8Bytes(`${message}
3048
-
3049
- ${detail}`, MAX_DIAGNOSTIC_DETAIL_MESSAGE_BYTES);
3050
- }
3051
- return { message, timestamp, version };
3052
- }
3053
- var MAX_DIAGNOSTIC_DETAIL_MESSAGE_BYTES = 8192;
3054
- var MAX_DIAGNOSTIC_DETAIL_CHARS = 6e3;
3055
- function diagnosticDetailAllowed(env) {
3056
- const level = (env["ZERO_ANALYTICS_LEVEL"] ?? "").trim().toLowerCase();
3057
- return level === "commands" || level === "full";
3189
+ Error: ${category}`;
3190
+ return {
3191
+ message,
3192
+ timestamp,
3193
+ version,
3194
+ diagnostic: {
3195
+ eventId: randomUUID3().replaceAll("-", ""),
3196
+ kind,
3197
+ category,
3198
+ platform,
3199
+ arch,
3200
+ runtime,
3201
+ runtimeVersion,
3202
+ frames: diagnosticFrames(info.error)
3203
+ }
3204
+ };
3058
3205
  }
3059
- function buildDiagnosticDetailText(info) {
3060
- const parts = [];
3061
- if (info.toolName) parts.push(`Tool: ${info.toolName}`);
3062
- const err = info.error;
3063
- if (err instanceof Error) {
3064
- parts.push(`${err.name}: ${err.message || "(no message)"}`);
3065
- if (typeof err.stack === "string" && err.stack.trim()) parts.push(err.stack.trim());
3066
- } else {
3067
- const text = diagnosticErrorText(err);
3068
- if (text) parts.push(text);
3069
- }
3070
- const exitOutput = typeof info.exitOutput === "string" ? info.exitOutput.trim() : "";
3071
- if (exitOutput && !parts.some((p) => p.includes(exitOutput))) parts.push(exitOutput);
3072
- let detail = parts.join("\n").trim();
3073
- if (detail.length > MAX_DIAGNOSTIC_DETAIL_CHARS) detail = detail.slice(0, MAX_DIAGNOSTIC_DETAIL_CHARS - 1) + "\u2026";
3074
- return detail;
3075
- }
3076
- var DETAIL_REDACTIONS = [
3077
- { re: /-----BEGIN (?:[A-Z0-9 ]+ )?PRIVATE KEY-----[\s\S]*?-----END (?:[A-Z0-9 ]+ )?PRIVATE KEY-----/g, to: "\u2039redacted:private-key\u203A" },
3078
- { re: /\bsk-[A-Za-z0-9_-]{16,}/g, to: "\u2039redacted:key\u203A" },
3079
- { re: /\bgh[pousr]_[A-Za-z0-9]{20,}/g, to: "\u2039redacted:gh-token\u203A" },
3080
- { re: /\b(?:AKIA|ASIA|ABIA|ACCA)[0-9A-Z]{16}\b/g, to: "\u2039redacted:aws-key\u203A" },
3081
- { re: /\bAIza[0-9A-Za-z_-]{35}\b/g, to: "\u2039redacted:gcp-key\u203A" },
3082
- { re: /\bxox[abprs]-[A-Za-z0-9-]{10,}/g, to: "\u2039redacted:slack-token\u203A" },
3083
- { re: /\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]+/g, to: "\u2039redacted:jwt\u203A" },
3084
- { re: /authorization\s*[:=]\s*(?:bearer|basic|token|digest)\s+\S+/gi, to: "authorization: \u2039redacted\u203A" },
3085
- { re: /\b((?:pass(?:word|wd)?|api[_-]?key|secret|token|credentials?)\s*[:=]\s*)\S{6,}/gi, to: "$1\u2039redacted\u203A" },
3086
- // Connection strings: keep the scheme/host shape, drop the inline credentials.
3087
- // MUST run before the email rule so `user:pass@host` is not mis-read as an
3088
- // email (which would swallow the host too).
3089
- { re: /([a-z][a-z0-9+.-]*:\/\/)[^\s/@:]+:[^\s/@]+@/gi, to: "$1\u2039redacted\u203A@" },
3090
- { re: /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/g, to: "\u2039redacted:email\u203A" },
3091
- // Home-dir usernames in file paths — keep the path shape, anonymise the user.
3092
- { re: /(\/home\/)[^/\s]+/g, to: "$1\u2039user\u203A" },
3093
- { re: /(\/Users\/)[^/\s]+/g, to: "$1\u2039user\u203A" },
3094
- { re: /([A-Za-z]:\\Users\\)[^\\\s]+/g, to: "$1\u2039user\u203A" }
3095
- ];
3096
- function redactDiagnosticDetail(text) {
3097
- if (typeof text !== "string" || text.length === 0) return "";
3206
+ function diagnosticFrames(error) {
3098
3207
  try {
3099
- let out = text;
3100
- for (const { re, to } of DETAIL_REDACTIONS) out = out.replace(re, to);
3101
- return out;
3208
+ if (!(error instanceof Error) || typeof error.stack !== "string") return [];
3209
+ const frames = [];
3210
+ for (const line of error.stack.slice(0, 16384).split("\n").slice(1, 33)) {
3211
+ const match = /(?:^|[/\\])(packages[/\\](?:cli|core|shared)[/\\](?:src|dist)[/\\][A-Za-z0-9_./\\-]+\.[cm]?[jt]sx?):(\d{1,7}):(\d{1,7})\)?$/.exec(line.trim());
3212
+ if (!match || match[1].split(/[/\\]/).some((part) => part === "..")) continue;
3213
+ const lineno = Number(match[2]);
3214
+ const colno = Number(match[3]);
3215
+ if (lineno > 0 && colno > 0) {
3216
+ frames.push({ filename: `app:///${match[1].replaceAll("\\", "/")}`, lineno, colno });
3217
+ }
3218
+ }
3219
+ return frames.reverse();
3102
3220
  } catch {
3103
- return "";
3104
- }
3221
+ return [];
3222
+ }
3223
+ }
3224
+ function serializeSubmission(payload, target, env) {
3225
+ if (!target.sentryKey || !payload.diagnostic) return serializePayload(payload);
3226
+ const diagnostic = payload.diagnostic;
3227
+ const cliVersion = VERSION;
3228
+ const commit = /^[0-9a-f]{40}$/i.test("8f4d9ec0535d64def1bc2c3fc9c917d4ec220cb4") ? "8f4d9ec0535d64def1bc2c3fc9c917d4ec220cb4".toLowerCase() : void 0;
3229
+ const releaseChannel = getReleaseChannel(env);
3230
+ const environment = releaseChannel === "beta" ? "production" : "development";
3231
+ const event = JSON.stringify({
3232
+ event_id: diagnostic.eventId,
3233
+ timestamp: Date.parse(payload.timestamp) / 1e3,
3234
+ platform: "node",
3235
+ level: "error",
3236
+ logger: "0.cli",
3237
+ release: `0-cli@${cliVersion}${commit ? `+${commit}` : ""}`,
3238
+ environment,
3239
+ tags: {
3240
+ cli_version: cliVersion,
3241
+ release_channel: releaseChannel,
3242
+ ...commit ? { build_commit: commit } : {},
3243
+ kind: diagnostic.kind,
3244
+ platform: diagnostic.platform,
3245
+ arch: diagnostic.arch,
3246
+ runtime: diagnostic.runtime
3247
+ },
3248
+ contexts: { runtime: { name: diagnostic.runtime, version: diagnostic.runtimeVersion } },
3249
+ exception: {
3250
+ values: [{
3251
+ type: "CLIError",
3252
+ value: diagnostic.category,
3253
+ ...diagnostic.frames.length ? { stacktrace: { frames: diagnostic.frames } } : {}
3254
+ }]
3255
+ }
3256
+ });
3257
+ return `${JSON.stringify({ event_id: diagnostic.eventId })}
3258
+ ${JSON.stringify({ type: "event", length: Buffer.byteLength(event, "utf8") })}
3259
+ ${event}
3260
+ `;
3105
3261
  }
3106
- function capUtf8Bytes(text, maxBytes) {
3107
- if (Buffer.byteLength(text, "utf8") <= maxBytes) return text;
3108
- let lo = 0, hi = text.length;
3109
- while (lo < hi) {
3110
- const mid = Math.ceil((lo + hi) / 2);
3111
- if (Buffer.byteLength(text.slice(0, mid), "utf8") <= maxBytes - 1) lo = mid;
3112
- else hi = mid - 1;
3262
+
3263
+ // packages/cli/src/workbench.ts
3264
+ var WORKBENCH_INNER_ARGUMENT = "--workbench-inner";
3265
+ function workbenchConfigPath(home = process.env.HOME || homedir2()) {
3266
+ return join8(homeStateDir(home), "workbench.json");
3267
+ }
3268
+ function defaultWorkbenchStateRoot(home = process.env.HOME || homedir2()) {
3269
+ return join8(homeStateDir(home), "workbench");
3270
+ }
3271
+ function positiveInteger(value, maximum) {
3272
+ return typeof value === "number" && Number.isInteger(value) && value > 0 && value <= maximum;
3273
+ }
3274
+ function isPinnedWorkbenchImageReference(value) {
3275
+ return typeof value === "string" && /^[a-z0-9][a-z0-9._:/-]*@sha256:[a-f0-9]{64}$/.test(value) && !value.slice(0, value.indexOf("@")).slice(value.lastIndexOf("/") + 1).includes(":");
3276
+ }
3277
+ function normalizeWorkbenchConfig(raw) {
3278
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) throw new Error("Invalid workbench operator choices.");
3279
+ const config = raw;
3280
+ if (config.github === true) throw new Error("GitHub credential forwarding is not supported by the isolated workbench. Revoke the GitHub grant before launch.");
3281
+ if (Array.isArray(config.providers) && config.providers.some((provider) => provider !== "chatgpt-codex")) throw new Error("The isolated workbench currently supports only host-brokered chatgpt-codex grants.");
3282
+ const approvedImages = [];
3283
+ if (config.approvedImages !== void 0) {
3284
+ if (!Array.isArray(config.approvedImages)) throw new Error("Invalid workbench sandbox image catalog.");
3285
+ const references = /* @__PURE__ */ new Set();
3286
+ for (const rawImage of config.approvedImages) {
3287
+ if (typeof rawImage !== "object" || rawImage === null || Array.isArray(rawImage)) throw new Error("Invalid workbench sandbox image catalog.");
3288
+ const image = rawImage;
3289
+ if (!isPinnedWorkbenchImageReference(image.reference) || typeof image.archive !== "string" || !isAbsolute2(image.archive) || typeof image.digest !== "string" || !/^sha256:[a-f0-9]{64}$/.test(image.digest) || references.has(image.reference)) throw new Error("Sandbox image grants require unique immutable repository@sha256 references and approved local archives.");
3290
+ references.add(image.reference);
3291
+ approvedImages.push({ reference: image.reference, archive: image.archive, digest: image.digest });
3292
+ }
3113
3293
  }
3114
- return text.slice(0, lo) + "\u2026";
3294
+ if (config.schemaVersion !== 1 || typeof config.image !== "string" || !isAbsolute2(config.image) || typeof config.imageDigest !== "string" || !/^sha256:[a-f0-9]{64}$/.test(config.imageDigest) || typeof config.stateRoot !== "string" || !isAbsolute2(config.stateRoot) || config.workspaceRoot !== void 0 && (typeof config.workspaceRoot !== "string" || !isAbsolute2(config.workspaceRoot)) || !Array.isArray(config.providers) || config.providers.some((id) => typeof id !== "string" || !PROVIDERS.some((provider) => provider.id === id)) || typeof config.github !== "boolean" || !positiveInteger(config.cpus, 64) || !positiveInteger(config.memoryMb, 262144) || !positiveInteger(config.storageGb, 1024)) {
3295
+ throw new Error("Invalid workbench operator choices.");
3296
+ }
3297
+ return {
3298
+ schemaVersion: 1,
3299
+ image: config.image,
3300
+ imageDigest: config.imageDigest,
3301
+ stateRoot: config.stateRoot,
3302
+ ...config.workspaceRoot === void 0 ? {} : { workspaceRoot: config.workspaceRoot },
3303
+ providers: [...new Set(config.providers)],
3304
+ github: config.github,
3305
+ cpus: config.cpus,
3306
+ memoryMb: config.memoryMb,
3307
+ storageGb: config.storageGb,
3308
+ ...approvedImages.length ? { approvedImages } : {}
3309
+ };
3115
3310
  }
3116
- function diagnosticErrorText(error) {
3117
- if (typeof error === "string") return error.trim();
3118
- if (error instanceof Error) return (error.message || error.name || "Error").trim();
3119
- if (error === null || error === void 0) return "";
3311
+ function loadWorkbenchConfig(home) {
3312
+ const file = workbenchConfigPath(home);
3313
+ if (!existsSync2(file)) return void 0;
3120
3314
  try {
3121
- const text = String(error).trim();
3122
- return text === "[object Object]" ? "" : text;
3123
- } catch {
3124
- return "";
3315
+ const stat = lstatSync2(file);
3316
+ if (!stat.isFile() || stat.isSymbolicLink() || (stat.mode & 63) !== 0 || stat.uid !== process.getuid?.()) {
3317
+ throw new Error("Workbench configuration must be an operator-owned, private regular file (mode 0600).");
3318
+ }
3319
+ return normalizeWorkbenchConfig(JSON.parse(readFileSync3(file, "utf8")));
3320
+ } catch (error) {
3321
+ if (error instanceof Error && error.message.startsWith("Workbench configuration must")) throw error;
3322
+ throw new Error(`Unreadable or invalid workbench configuration at ${file}. Repair the operator choices before setup or launch; no profile fallback is allowed.`);
3323
+ }
3324
+ }
3325
+ function saveWorkbenchConfig(config, home) {
3326
+ const path = workbenchConfigPath(home);
3327
+ mkdirSync4(dirname5(path), { recursive: true, mode: 448 });
3328
+ const temporary = `${path}.${process.pid}.tmp`;
3329
+ writeFileSync3(temporary, `${JSON.stringify(normalizeWorkbenchConfig(config), null, 2)}
3330
+ `, { encoding: "utf8", mode: 384, flag: "wx" });
3331
+ renameSync2(temporary, path);
3332
+ chmodSync(path, 384);
3333
+ }
3334
+ function resolveWorkbenchGuestSettings(settings, env = process.env) {
3335
+ const privacy = { [ANALYTICS_LEVEL_ENV]: env[ANALYTICS_LEVEL_ENV] ?? settings.analyticsLevel };
3336
+ for (const name of FEEDBACK_OPT_OUT_ENV) privacy[name] = env[name];
3337
+ const environmentLevel = resolveAnalyticsLevel(privacy);
3338
+ return normalizeSettings({
3339
+ ...settings,
3340
+ executionProfile: "local",
3341
+ updatePolicy: "off",
3342
+ analyticsLevel: levelAtLeast(environmentLevel, settings.analyticsLevel) ? settings.analyticsLevel : environmentLevel,
3343
+ diagnosticReporting: submissionBlockedReason(privacy, { allowCloud: false }) === "opt-out" ? "off" : settings.diagnosticReporting
3344
+ });
3345
+ }
3346
+ function workbenchNetworkEnabled(env = process.env) {
3347
+ return submissionBlockedReason({ ZERO_OFFLINE: env.ZERO_OFFLINE }, { allowCloud: false }) !== "opt-out";
3348
+ }
3349
+ async function launchConfiguredWorkbench(args) {
3350
+ const home = process.env.HOME || homedir2();
3351
+ if (args[0] === WORKBENCH_INNER_ARGUMENT) {
3352
+ if (process.platform !== "linux" || !isAdmittedSmolvmWorkbench()) {
3353
+ throw new Error("Missing authenticated SmolVM broker admission; refusing internal invocation.");
3354
+ }
3355
+ process.argv = [process.argv[0], process.argv[1], ...args.slice(1)];
3356
+ return void 0;
3357
+ }
3358
+ if (process.platform === "linux" && isAdmittedSmolvmWorkbench()) return void 0;
3359
+ if (["workbench", "config", "web", "dashboard", "console", "tui", "doctor", "help", "--help", "-h", "--version", "-v"].includes(args[0] ?? "") || args.length === 0) return void 0;
3360
+ if (remoteBackendClientId(args)) return void 0;
3361
+ const hostSettings = loadGlobalSettings(home, { requireExecutionProfile: existsSync2(workbenchConfigPath(home)) });
3362
+ if (hostSettings.executionProfile !== "smolvm") return void 0;
3363
+ if (args[0] === "mcp-server") {
3364
+ throw new Error("MCP requires bidirectional stdio, which the SmolVM CLI bridge does not support. The selected SmolVM profile is unchanged; host fallback is refused. Use `0 workflow` commands in SmolVM. MCP currently requires explicitly selected host-local execution; only the operator should choose `0 workbench disable` if host execution is intended.");
3365
+ }
3366
+ const config = loadWorkbenchConfig(home);
3367
+ if (!config) throw new Error("SmolVM profile is selected but no workbench is configured. Run 0 workbench setup --image <approved-archive>.");
3368
+ const workspaceRoot = realpathSync3(config.workspaceRoot ?? process.cwd());
3369
+ if (!lstatSync2(workspaceRoot).isDirectory()) throw new Error("The configured workbench workspace is not a directory.");
3370
+ const status = await getSmolvmWorkbenchStatus({ stateRoot: config.stateRoot, image: config.image });
3371
+ if (!status.platformSupported || !status.runtimeReady || !status.imageApproved) {
3372
+ throw new Error(status.error ?? "SmolVM runtime or approved image is unavailable. Run 0 workbench setup; host fallback is refused.");
3373
+ }
3374
+ const modelIndex = args.findIndex((argument) => argument === "--model" || argument === "-m");
3375
+ const modelArgument = modelIndex >= 0 ? args[modelIndex + 1] : args.find((argument) => argument.startsWith("--model="))?.slice("--model=".length);
3376
+ const runtime = createConsoleRuntime({ model: modelArgument });
3377
+ const providerId = runtime.resolvedProvider();
3378
+ if (providerId !== "chatgpt-codex" || !config.providers.includes(providerId)) throw new Error("Isolated CLI execution requires an explicitly granted brokered chatgpt-codex provider. Host fallback is refused.");
3379
+ const model = runtime.resolvedModel();
3380
+ const routing = runtime.modelSelection();
3381
+ const broker = createWorkbenchProviderBroker({
3382
+ provider: providerId,
3383
+ models: [.../* @__PURE__ */ new Set([model, ...Object.values(routing.agentModels).filter((value) => value !== "auto")])],
3384
+ resolveCredentials: runtime.workbenchCredentialResolver()
3385
+ });
3386
+ const network = workbenchNetworkEnabled();
3387
+ const controller = new AbortController();
3388
+ let signalCode;
3389
+ const interrupt = () => {
3390
+ signalCode = 130;
3391
+ controller.abort();
3392
+ };
3393
+ const terminate = () => {
3394
+ signalCode = 143;
3395
+ controller.abort();
3396
+ };
3397
+ const hangup = () => {
3398
+ signalCode = 129;
3399
+ controller.abort();
3400
+ };
3401
+ process.once("SIGINT", interrupt);
3402
+ process.once("SIGTERM", terminate);
3403
+ process.once("SIGHUP", hangup);
3404
+ let result;
3405
+ try {
3406
+ result = await runWorkbenchCli({
3407
+ workbench: config,
3408
+ selection: { provider: providerId, model, ...routing },
3409
+ provider: { ...broker.grant, request: broker.request, close: broker.close },
3410
+ assets: currentWorkbenchAssets(),
3411
+ guestSettings: resolveWorkbenchGuestSettings(hostSettings),
3412
+ network,
3413
+ args: mapWorkbenchCliArguments(args, workspaceRoot),
3414
+ signal: controller.signal,
3415
+ onStdout: (data) => process.stdout.write(data),
3416
+ onStderr: (data) => process.stderr.write(data)
3417
+ });
3418
+ } finally {
3419
+ await broker.close();
3420
+ process.removeListener("SIGINT", interrupt);
3421
+ process.removeListener("SIGTERM", terminate);
3422
+ process.removeListener("SIGHUP", hangup);
3125
3423
  }
3424
+ if (result.cleanupFailed) {
3425
+ process.stderr.write("[0] SmolVM cleanup could not be proven. Workbench admission/state is retained; host execution is refused.\n");
3426
+ return 125;
3427
+ }
3428
+ if (result.artifacts) process.stderr.write(`[0] Guest workspace and results saved at ${result.artifacts.directory}
3429
+ `);
3430
+ if (result.error) process.stderr.write(`[0] ${result.error}
3431
+ `);
3432
+ if (result.timedOut) return 124;
3433
+ return signalCode ?? result.exitCode ?? 125;
3434
+ }
3435
+ function selectWorkbenchProfile(profile, home) {
3436
+ if (!saveSettings({ ...loadGlobalSettings(home), executionProfile: profile }, home)) throw new Error("Could not persist the operator execution profile.");
3126
3437
  }
3127
3438
 
3128
3439
  export {
@@ -3150,13 +3461,18 @@ export {
3150
3461
  isSafeThemeId,
3151
3462
  reloadInstalledThemes,
3152
3463
  installedThemeEntries,
3464
+ allThemeNames,
3153
3465
  isKnownTheme,
3154
3466
  writeInstalledTheme,
3155
3467
  removeInstalledTheme,
3468
+ syncThemeChoices,
3156
3469
  SETTING_DEFS,
3157
3470
  DEFAULT_SETTINGS,
3158
3471
  settingsFilePath,
3159
3472
  projectSettingsFilePath,
3473
+ projectSettingsExist,
3474
+ isOperatorSetting,
3475
+ resolveLayeredSettings,
3160
3476
  loadLayeredSettings,
3161
3477
  normalizeSettings,
3162
3478
  loadSettings,
@@ -3165,42 +3481,34 @@ export {
3165
3481
  sanitizeOverrides,
3166
3482
  readProjectOverrides,
3167
3483
  saveProjectOverrides,
3168
- getSettings,
3169
- getSettingSources,
3170
- updateSetting,
3171
- resetSettings,
3172
- previewSetting,
3173
- subscribeSettings,
3174
- reloadSettings,
3175
- configureSettingsStore,
3176
- useSettings,
3177
- maybeLoadCodexAuth,
3178
- presentationEventBus,
3179
- createPresentationEmitter,
3180
- consolePresentationOutput,
3181
- installConsolePresentationBridge,
3182
- installProcessPresentationStreamBridge,
3183
- suspendProcessPresentationStreamBridge,
3184
- resumeProcessPresentationStreamBridge,
3185
- processPresentationOutput,
3186
- writePresentationLine,
3484
+ currentWorkbenchAssets,
3485
+ WORKBENCH_FRAME_BYTES,
3486
+ WORKBENCH_MAX_PENDING,
3487
+ encodeWorkbenchFrame,
3488
+ WorkbenchFrameReader,
3489
+ installWorkbenchPlugins,
3490
+ validateGuestServicePluginConnections,
3491
+ servicePluginSecretRedactor,
3492
+ createWorkbenchConsoleSession,
3493
+ remoteBackendClientId,
3187
3494
  PROVIDERS,
3495
+ providerSupportsMethod,
3188
3496
  providerStates,
3189
3497
  isProviderConfigured,
3190
- loadCredentials,
3191
- saveCredentials,
3192
- credentialEnvPatch,
3193
- redactSecret,
3194
- loadAccountStore,
3195
- saveAccountStore,
3196
- addAccount,
3197
- accountEnvPatch,
3198
3498
  appendFeedback,
3199
3499
  parseFeedbackCommand,
3200
- FEEDBACK_OPT_OUT_ENV,
3201
3500
  submissionBlockedReason,
3202
3501
  describeSkip,
3203
3502
  buildSubmitPreview,
3204
3503
  submitFeedback,
3205
- buildDiagnosticFeedback
3504
+ buildDiagnosticFeedback,
3505
+ workbenchConfigPath,
3506
+ defaultWorkbenchStateRoot,
3507
+ isPinnedWorkbenchImageReference,
3508
+ loadWorkbenchConfig,
3509
+ saveWorkbenchConfig,
3510
+ resolveWorkbenchGuestSettings,
3511
+ workbenchNetworkEnabled,
3512
+ launchConfiguredWorkbench,
3513
+ selectWorkbenchProfile
3206
3514
  };