0sec-cli 0.17.0 → 0.21.4

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 (171) hide show
  1. package/{0sec.js → 0.js} +92 -79
  2. package/LICENSE +1 -1
  3. package/README.md +77 -37
  4. package/chunks/adapt-loop-IGBRQAXO.js +18 -0
  5. package/chunks/{adgraph-JLGA6RYI.js → adgraph-AUDQTBYA.js} +5 -5
  6. package/chunks/agent/skills/frameworks/entra-id.yaml +2 -2
  7. package/chunks/agent/skills/techniques/assumption-mining.yaml +2 -2
  8. package/chunks/agent/skills/techniques/cve-poc-adaptation.yaml +2 -2
  9. package/chunks/agent/skills/techniques/entra-attack-paths.yaml +1 -1
  10. package/chunks/agent/skills/techniques/kernel-weaponization.yaml +1 -1
  11. package/chunks/agent/skills/techniques/llm-prompt-injection.yaml +2 -2
  12. package/chunks/agent/skills/techniques/npm-ecosystem.yaml +1 -1
  13. package/chunks/agent/skills/techniques/poc-verification.yaml +1 -1
  14. package/chunks/agent/skills/techniques/seedless-depth-review.yaml +20 -38
  15. package/chunks/{artifact-scraper-GJEZMOHF.js → artifact-scraper-LNQJUORS.js} +6 -6
  16. package/chunks/{assumption-mining-BVMMUZHB.js → assumption-mining-4MJTP6H5.js} +24 -26
  17. package/chunks/{chunk-P6WKNFWX.js → chunk-2OQOQ2FZ.js} +8 -8
  18. package/chunks/{chunk-5G3ZXFBW.js → chunk-2WNCI664.js} +3 -3
  19. package/chunks/{chunk-HDWV7PGI.js → chunk-2WSXFFJZ.js} +4 -4
  20. package/chunks/{chunk-DW5UWPFY.js → chunk-4VGLO2YS.js} +6 -5
  21. package/chunks/{chunk-SNKTC4BP.js → chunk-5T6D5BVY.js} +88 -88
  22. package/chunks/{chunk-OEFNRYI2.js → chunk-6GHSR47F.js} +3 -6
  23. package/chunks/chunk-6YNKRUMC.js +4474 -0
  24. package/chunks/{chunk-O462Y7P2.js → chunk-ANDKY54G.js} +22317 -19616
  25. package/chunks/{chunk-53G27VPS.js → chunk-AZ7L3HFD.js} +7 -2
  26. package/chunks/{chunk-2SANI5RH.js → chunk-BACXRTQ4.js} +691 -318
  27. package/chunks/{chunk-SOG2U7B3.js → chunk-CAJJRUTV.js} +4 -4
  28. package/chunks/{chunk-57ZENEX2.js → chunk-CGHCEN7W.js} +3 -3
  29. package/chunks/{chunk-VGRDNSHA.js → chunk-CL5KAUM6.js} +17 -16
  30. package/chunks/{chunk-QOKTUOU2.js → chunk-CXKDWFNO.js} +6 -6
  31. package/chunks/{chunk-RWONANDA.js → chunk-DQTNI3KY.js} +5 -5
  32. package/chunks/{chunk-K26SZ37E.js → chunk-E2UOY5PJ.js} +288 -100
  33. package/chunks/{chunk-CLHCDHP4.js → chunk-ER3SUMYJ.js} +40 -18
  34. package/chunks/{chunk-IORC6MQX.js → chunk-FAH6V4QM.js} +31964 -32279
  35. package/chunks/{chunk-6LKRLK2R.js → chunk-FHC2B7LN.js} +3 -3
  36. package/chunks/{chunk-4Y4KQJXE.js → chunk-FWAYJ2IV.js} +16 -16
  37. package/chunks/{chunk-WVTBZEQO.js → chunk-G34QP2XW.js} +8 -8
  38. package/chunks/{chunk-MYVT64FN.js → chunk-GHWODZMR.js} +2 -2
  39. package/chunks/{chunk-F3WBKITT.js → chunk-GIQKESMN.js} +10 -10
  40. package/chunks/{chunk-2RMLOJVB.js → chunk-GVD6SJGH.js} +2 -2
  41. package/chunks/{chunk-RDZYQQLW.js → chunk-HBKCGJAV.js} +7 -7
  42. package/chunks/{chunk-SF4KZ4O3.js → chunk-HLFS6WV2.js} +2 -2
  43. package/chunks/chunk-HPQZHOIU.js +67 -0
  44. package/chunks/{chunk-3QFDYBZQ.js → chunk-IMGWPNQM.js} +231 -1308
  45. package/chunks/{chunk-3MOLBTLS.js → chunk-K2PWUPUI.js} +2 -2
  46. package/chunks/{chunk-MMLDQR4H.js → chunk-NDJU5ZZ7.js} +14 -14
  47. package/chunks/{chunk-H44E2CRN.js → chunk-NOPBXZUV.js} +3 -3
  48. package/chunks/{chunk-HE7LCA7G.js → chunk-O6SD4A36.js} +3 -3
  49. package/chunks/{chunk-SAFFWQW4.js → chunk-P6YG6TEZ.js} +6 -6
  50. package/chunks/{chunk-KKGE5RQE.js → chunk-PC6RCKQV.js} +198 -11
  51. package/chunks/{chunk-LP3HHYQU.js → chunk-PD2BBBKT.js} +2 -2
  52. package/chunks/{chunk-BKZFDZ23.js → chunk-QCUFBVIZ.js} +3 -3
  53. package/chunks/{chunk-KLTNTE2Z.js → chunk-QZVG3BG4.js} +14 -14
  54. package/chunks/{chunk-D6S3IIPG.js → chunk-RF5QGKLR.js} +9 -9
  55. package/chunks/{chunk-LOHTE223.js → chunk-SBMB4OL3.js} +9 -9
  56. package/chunks/{chunk-IOL7D5YV.js → chunk-SQCBHFWN.js} +3 -3
  57. package/chunks/{chunk-A6CLR72I.js → chunk-TF46UWFU.js} +3 -3
  58. package/chunks/chunk-TH2LW437.js +7829 -0
  59. package/chunks/chunk-TRH2PJSN.js +547 -0
  60. package/chunks/{chunk-RJWOYEOG.js → chunk-UEX7PFSE.js} +5 -5
  61. package/chunks/{chunk-VQG4FT5D.js → chunk-UFHN6RIU.js} +2 -2
  62. package/chunks/{chunk-WU6AFRAZ.js → chunk-V36HVTUL.js} +12 -12
  63. package/chunks/{chunk-IR537GON.js → chunk-VCSFCHAJ.js} +2 -2
  64. package/chunks/{chunk-YLMN3N25.js → chunk-VIT5ALXF.js} +3 -3
  65. package/chunks/{chunk-UJ4IK5PO.js → chunk-VOAXLO4A.js} +10 -10
  66. package/chunks/{chunk-UM3ZNQIM.js → chunk-W4JOXE4N.js} +100 -84
  67. package/chunks/{chunk-DN25OQQA.js → chunk-WF5W75WY.js} +25 -25
  68. package/chunks/{chunk-QKICO43A.js → chunk-WPQLBN4S.js} +5 -5
  69. package/chunks/{chunk-HT6P7RY3.js → chunk-WZISK5VC.js} +37 -35
  70. package/chunks/{chunk-QJQKHO7G.js → chunk-Y5H6E24H.js} +3 -3
  71. package/chunks/{chunk-KTQDLNSR.js → chunk-YA4PUM4H.js} +41 -41
  72. package/chunks/{chunk-47TQJWDH.js → chunk-YHWWF4QY.js} +9 -9
  73. package/chunks/{chunk-2CJ776PV.js → chunk-YPOA3W4O.js} +48 -39
  74. package/chunks/{chunk-7DQEV5QI.js → chunk-Z3IKZX3X.js} +4 -4
  75. package/chunks/codex-models-TYYV4JNN.js +15 -0
  76. package/chunks/{commands-U3AMV5ZU.js → commands-WF5RG4I2.js} +5620 -2513
  77. package/chunks/corpus-v1.json +2 -2
  78. package/chunks/data/appsec-archetypes.json +2 -2
  79. package/chunks/data/chromium-archetypes.json +1 -1
  80. package/chunks/data/freebsd-archetypes.json +1 -1
  81. package/chunks/data/kernel-archetypes.json +2 -2
  82. package/chunks/db-U3WQZVAO.js +16 -0
  83. package/chunks/{disclose-A6AEIEDA.js → disclose-67GZCDBO.js} +5 -5
  84. package/chunks/{dist-JR67XKYO.js → dist-ACC3DZUZ.js} +11 -5
  85. package/chunks/{dist-GL66KMUX.js → dist-EVTN3UTT.js} +6 -6
  86. package/chunks/{dist-FDKALV4R.js → dist-F6X62TQK.js} +290 -327
  87. package/chunks/dist-KEULE5IQ.js +21914 -0
  88. package/chunks/dist-XZB53PS5.js +53 -0
  89. package/chunks/eval-runner-SA6OUGHZ.js +26 -0
  90. package/chunks/example-manifest.json +2 -2
  91. package/chunks/{exploit-agent-XP23ISN7.js → exploit-agent-36Q7XQFP.js} +4 -4
  92. package/chunks/{exploit-autoclimb-E6T7RRSC.js → exploit-autoclimb-J4OTSTZ6.js} +6 -6
  93. package/chunks/{exploit-climb-2FAUP6XU.js → exploit-climb-PYEJDIC4.js} +12 -12
  94. package/chunks/fix-J2KBRJDV.js +12 -0
  95. package/chunks/{github-issues-MJ6OYOOU.js → github-issues-IGTTWM7V.js} +7 -7
  96. package/chunks/harness-GDUMKGSE.js +22 -0
  97. package/chunks/http-conformance-U5F6GKYG.js +11 -0
  98. package/chunks/http-sender-I5XFFY35.js +10 -0
  99. package/chunks/hunt-scan-UDJSCISU.js +36 -0
  100. package/chunks/{identity-6ZAIIWOR.js → identity-JTIVJSV7.js} +5 -5
  101. package/chunks/{kernel-primitive-TENE3R7T.js → kernel-primitive-F2IXUFAX.js} +6 -6
  102. package/chunks/{kernel-vm-runner-4F6QSNFY.js → kernel-vm-runner-276LHIOV.js} +6 -6
  103. package/chunks/memsafety-scan-NHUOGTEX.js +16 -0
  104. package/chunks/{native-loop-NTONUDP7.js → native-loop-3BSYVDUI.js} +17 -18
  105. package/chunks/{npm-detectors-KB5Y5ZFX.js → npm-detectors-VIYJXKFQ.js} +6 -6
  106. package/chunks/npm-dynamic-discovery-5J7FT3XM.js +13 -0
  107. package/chunks/orchestrate-DHBFRTSI.js +59 -0
  108. package/chunks/{pre-recon-cve-66EB6G4M.js → pre-recon-cve-X2K2P5YQ.js} +4 -4
  109. package/chunks/prepare-KYHGFWCJ.js +13 -0
  110. package/chunks/process-PNMC36JI.js +15 -0
  111. package/chunks/{replay-runner-RRR4E2A7.js → replay-runner-LK2XFIVG.js} +7 -7
  112. package/chunks/{run-ESNN4V5W.js → run-CBGE6DRQ.js} +2401 -1810
  113. package/chunks/{runtime-62ZQAH7H.js → runtime-PXO5G6UV.js} +11 -11
  114. package/chunks/runtime-XFWJTBUO.js +12 -0
  115. package/chunks/{scan-stream-FHI2FYZE.js → scan-stream-BMOCPKQX.js} +7 -7
  116. package/chunks/{scope-BI7BF4ZY.js → scope-NJHIDDTN.js} +4 -4
  117. package/chunks/{session-store-BCFQYDCE.js → session-store-46WBZM22.js} +6 -6
  118. package/chunks/source-files-REHOM44I.js +12 -0
  119. package/chunks/{specdrift-WCLH6TTQ.js → specdrift-AFS54WEJ.js} +5 -5
  120. package/chunks/token-KRWP5JYA.js +72 -0
  121. package/chunks/token-util-DWTL33S3.js +8 -0
  122. package/chunks/variant-candidates-UH2WC2HA.js +13 -0
  123. package/chunks/{web-recon-prepass-DPVCBRZA.js → web-recon-prepass-SFXU44SS.js} +9 -9
  124. package/dashboard/assets/desktop-BT7RkC4q.js +32 -0
  125. package/dashboard/assets/desktop-OaBI0E0e.css +1 -0
  126. package/dashboard/assets/{findings-page-CACW9nw2.js → findings-page-B4rwF6fZ.js} +2 -2
  127. package/dashboard/assets/{format-PoISqsES.js → format-LNQSSpn6.js} +1 -1
  128. package/dashboard/assets/live-page-DoNwpPAO.js +1 -0
  129. package/dashboard/assets/{meta-tile-CcskIV_o.js → meta-tile-BMtBC-Qq.js} +1 -1
  130. package/dashboard/assets/operations-CMFVrXEe.css +2 -0
  131. package/dashboard/assets/operations-app-ER0gg7GN.js +2 -0
  132. package/dashboard/assets/{operations-D8nUP5_m.js → operations-e2CGYKB1.js} +2 -2
  133. package/dashboard/assets/{overview-page-DTGS8dPo.js → overview-page-CmeWH17A.js} +1 -1
  134. package/dashboard/assets/{page-header-MFVvEtZW.js → page-header-CIJyycfz.js} +1 -1
  135. package/dashboard/assets/scans-page-CuhzWhHx.js +1 -0
  136. package/dashboard/assets/{table-BYS-nIVA.js → table-BgsSBdke.js} +1 -1
  137. package/dashboard/assets/{tabs-DivxbhQy.js → tabs-vn6v7Bk6.js} +1 -1
  138. package/dashboard/desktop.html +3 -3
  139. package/dashboard/index.html +3 -3
  140. package/package.json +11 -11
  141. package/chunks/adapt-loop-ECQG7454.js +0 -18
  142. package/chunks/appsec-catalog-CBWHGTEL.js +0 -24
  143. package/chunks/chunk-H2FFLZNK.js +0 -3
  144. package/chunks/chunk-QI233I24.js +0 -333
  145. package/chunks/chunk-RRMJC3ZE.js +0 -105
  146. package/chunks/chunk-SZJCPG2I.js +0 -110
  147. package/chunks/chunk-WKNNZVJS.js +0 -2798
  148. package/chunks/cost-ledger-ZCKMBJEN.js +0 -13
  149. package/chunks/db-26NGQFKO.js +0 -16
  150. package/chunks/eval-runner-DDLE5RQ3.js +0 -27
  151. package/chunks/fix-IG7LYXH3.js +0 -12
  152. package/chunks/harness-HWOYFKFY.js +0 -22
  153. package/chunks/http-conformance-DU66MZIU.js +0 -11
  154. package/chunks/http-sender-GWH2IYEA.js +0 -10
  155. package/chunks/hunt-scan-XPELSPQQ.js +0 -38
  156. package/chunks/memsafety-scan-J7SECV5W.js +0 -16
  157. package/chunks/npm-dynamic-discovery-AHROOZDB.js +0 -13
  158. package/chunks/orchestrate-ZUWAUWBC.js +0 -62
  159. package/chunks/pipeline-FCARFZU3.js +0 -14
  160. package/chunks/prepare-MYY743TK.js +0 -13
  161. package/chunks/process-3Q7QJIOZ.js +0 -13
  162. package/chunks/runtime-J7PLXZNM.js +0 -12
  163. package/chunks/source-files-PWRQR6LY.js +0 -12
  164. package/chunks/variant-candidates-6KM6F6MD.js +0 -13
  165. package/dashboard/assets/0sec-icon-66SreztZ.gif +0 -0
  166. package/dashboard/assets/desktop-CXTQonNm.js +0 -32
  167. package/dashboard/assets/desktop-NOekk4QS.css +0 -1
  168. package/dashboard/assets/live-page-DrgQqQK5.js +0 -1
  169. package/dashboard/assets/operations-BKE8T-vY.css +0 -2
  170. package/dashboard/assets/operations-app-CQQhG54n.js +0 -2
  171. package/dashboard/assets/scans-page-ChVtq8Us.js +0 -1
@@ -1,19 +1,19 @@
1
1
  #!/usr/bin/env node
2
- import { createRequire as __0secCreateRequire } from "node:module";
3
- const require = __0secCreateRequire(import.meta.url);
2
+ import { createRequire as __0CreateRequire } from "node:module";
3
+ const require = __0CreateRequire(import.meta.url);
4
4
  import {
5
5
  VERSION,
6
- homeStateDir
7
- } from "./chunk-KKGE5RQE.js";
6
+ cloudStateDir
7
+ } from "./chunk-PC6RCKQV.js";
8
8
 
9
9
  // packages/core/dist/agent/feature-presets.js
10
10
  var FP_MOAT_FLAGS = [
11
- "0SEC_FEATURE_REACHABILITY_GATE",
12
- "0SEC_FEATURE_MULTIMODAL",
13
- "0SEC_FEATURE_PUBLISHABILITY_GATE",
14
- "0SEC_FEATURE_POV_GATE",
15
- "0SEC_FEATURE_POC_GEN_STATIC",
16
- "0SEC_FEATURE_CONSENSUS_VERIFY"
11
+ "ZERO_FEATURE_REACHABILITY_GATE",
12
+ "ZERO_FEATURE_MULTIMODAL",
13
+ "ZERO_FEATURE_PUBLISHABILITY_GATE",
14
+ "ZERO_FEATURE_POV_GATE",
15
+ "ZERO_FEATURE_POC_GEN_STATIC",
16
+ "ZERO_FEATURE_CONSENSUS_VERIFY"
17
17
  ];
18
18
  var FEATURE_PRESETS = Object.freeze({
19
19
  "fp-moat": FP_MOAT_FLAGS
@@ -41,7 +41,7 @@ function applyFeaturePreset(preset, env2 = process.env) {
41
41
  return { preset, applied, preserved };
42
42
  }
43
43
  function applyFeaturePresetFromEnv(env2 = process.env) {
44
- const raw = env2["0SEC_FEATURE_PRESET"];
44
+ const raw = env2["ZERO_FEATURE_PRESET"];
45
45
  if (!raw)
46
46
  return void 0;
47
47
  const preset = resolveFeaturePreset(raw);
@@ -63,73 +63,73 @@ var features = {
63
63
  *
64
64
  * Turn count is also a cost PROXY, and cost is already bounded directly by
65
65
  * the token budget, so this never was the control that kept spend in check.
66
- * Enable with `0SEC_FEATURE_EARLY_STOP=1` for benchmark or A/B runs where a
66
+ * Enable with `ZERO_FEATURE_EARLY_STOP=1` for benchmark or A/B runs where a
67
67
  * fixed turn budget per attempt is the point.
68
68
  */
69
69
  get earlyStopRetry() {
70
- return env("0SEC_FEATURE_EARLY_STOP", false);
70
+ return env("ZERO_FEATURE_EARLY_STOP", false);
71
71
  },
72
72
  /** Detect A-A-A and A-B-A-B loop patterns, inject warning */
73
73
  get loopDetection() {
74
- return env("0SEC_FEATURE_LOOP_DETECTION", true);
74
+ return env("ZERO_FEATURE_LOOP_DETECTION", true);
75
75
  },
76
76
  /** Compress middle messages when context exceeds 30k tokens */
77
77
  get contextCompaction() {
78
- return env("0SEC_FEATURE_CONTEXT_COMPACTION", true);
78
+ return env("ZERO_FEATURE_CONTEXT_COMPACTION", true);
79
79
  },
80
80
  /**
81
81
  * Re-send the opaque, model-bound Responses output item array on the next
82
82
  * turn. Default ON; set to 0 only for matched retained-reasoning A/B runs.
83
83
  */
84
84
  get retainedReasoning() {
85
- return env("0SEC_FEATURE_RETAINED_REASONING", true);
85
+ return env("ZERO_FEATURE_RETAINED_REASONING", true);
86
86
  },
87
87
  /** Exploit script templates in shell prompt (blind SQLi, SSTI, auth chain) */
88
88
  get scriptTemplates() {
89
- return env("0SEC_FEATURE_SCRIPT_TEMPLATES", true);
89
+ return env("ZERO_FEATURE_SCRIPT_TEMPLATES", true);
90
90
  },
91
91
  /** Dynamic vulnerability playbooks injected after recon phase */
92
92
  get dynamicPlaybooks() {
93
- return env("0SEC_FEATURE_DYNAMIC_PLAYBOOKS", false);
93
+ return env("ZERO_FEATURE_DYNAMIC_PLAYBOOKS", false);
94
94
  },
95
95
  /** Just-in-time atomic DO/DON'T rules injected on a matching tool action */
96
96
  get ruleInjection() {
97
- return env("0SEC_FEATURE_RULE_INJECTION", false);
97
+ return env("ZERO_FEATURE_RULE_INJECTION", false);
98
98
  },
99
99
  /** Agent writes plan/creds to disk, injected at reflection checkpoints */
100
100
  get externalMemory() {
101
- return env("0SEC_FEATURE_EXTERNAL_MEMORY", false);
101
+ return env("ZERO_FEATURE_EXTERNAL_MEMORY", false);
102
102
  },
103
103
  /** Inject prior attempt findings when retrying (LLM-summarized progress handoff) */
104
104
  get progressHandoff() {
105
- return env("0SEC_FEATURE_PROGRESS_HANDOFF", true);
105
+ return env("ZERO_FEATURE_PROGRESS_HANDOFF", true);
106
106
  },
107
107
  /** Allow the agent to search the web for CVE details, docs, and technique references */
108
108
  get webSearch() {
109
- return env("0SEC_FEATURE_WEB_SEARCH", false);
109
+ return env("ZERO_FEATURE_WEB_SEARCH", false);
110
110
  },
111
111
  /** Interactive PTY sessions for exploits requiring interactivity (reverse shells, DB clients, SSH) */
112
112
  get ptySession() {
113
- return env("0SEC_FEATURE_PTY_SESSION", false);
113
+ return env("ZERO_FEATURE_PTY_SESSION", false);
114
114
  },
115
115
  /**
116
116
  * Persistent, COMPUTE-ONLY Python REPL (`python_exec`, Phase-0). A framed
117
117
  * python3 kernel keeps state across calls for payload/parse/crypto/encode
118
118
  * work; networking is blocked at the socket source whenever an engagement is
119
- * active. Default OFF — opt in via 0SEC_FEATURE_PYTHON_EXEC=1. Getter so
119
+ * active. Default OFF — opt in via ZERO_FEATURE_PYTHON_EXEC=1. Getter so
120
120
  * the CLI `--features` flag (set after this module is imported) is honored at
121
121
  * tool-dispatch time.
122
122
  */
123
123
  get pythonExec() {
124
- return env("0SEC_FEATURE_PYTHON_EXEC", false);
124
+ return env("ZERO_FEATURE_PYTHON_EXEC", false);
125
125
  },
126
126
  /**
127
127
  * Expose the path-confined `analyze_binary` bridge to 0verse. Default OFF:
128
128
  * a model may request a long-running binary analysis only after an operator
129
- * opts in with 0SEC_FEATURE_ZEROVERSE=1.
129
+ * opts in with ZERO_FEATURE_ZEROVERSE=1.
130
130
  */
131
131
  get zeroverse() {
132
- return env("0SEC_FEATURE_ZEROVERSE", false);
132
+ return env("ZERO_FEATURE_ZEROVERSE", false);
133
133
  },
134
134
  /**
135
135
  * EGATS specialist routing (#557, HPTSA-inspired). When ON, an EGATS branch
@@ -146,22 +146,22 @@ var features = {
146
146
  * benchmark harness. Implemented as a getter so the CLI `--features` flag
147
147
  * (which sets the env var inside the command action, AFTER this module has
148
148
  * been imported) is honored at routing time. Enable via
149
- * 0SEC_FEATURE_SPECIALIST_ROUTING=1.
149
+ * ZERO_FEATURE_SPECIALIST_ROUTING=1.
150
150
  */
151
151
  get specialistRouting() {
152
- return env("0SEC_FEATURE_SPECIALIST_ROUTING", false);
152
+ return env("ZERO_FEATURE_SPECIALIST_ROUTING", false);
153
153
  },
154
154
  /** Self-consistency voting: run the structured verify pipeline N times and take the majority vote */
155
155
  get selfConsistencyVerify() {
156
- return env("0SEC_FEATURE_CONSENSUS_VERIFY", false);
156
+ return env("ZERO_FEATURE_CONSENSUS_VERIFY", false);
157
157
  },
158
158
  /** Multi-modal agreement: cross-validate findings against foxguard (Rust pattern scanner) */
159
159
  get multiModalAgreement() {
160
- return env("0SEC_FEATURE_MULTIMODAL", false);
160
+ return env("ZERO_FEATURE_MULTIMODAL", false);
161
161
  },
162
162
  /** Reachability gate: suppress findings whose sink is not reachable from an application entry point */
163
163
  get reachabilityGate() {
164
- return env("0SEC_FEATURE_REACHABILITY_GATE", false);
164
+ return env("ZERO_FEATURE_REACHABILITY_GATE", false);
165
165
  },
166
166
  /**
167
167
  * Publishability / in-scope gate (issue #537 / #539). Decides
@@ -173,14 +173,14 @@ var features = {
173
173
  *
174
174
  * Default OFF: this gate can suppress reproducible findings, so it must be
175
175
  * explicitly opted into before any A/B claim. Disable/enable via
176
- * 0SEC_FEATURE_PUBLISHABILITY_GATE.
176
+ * ZERO_FEATURE_PUBLISHABILITY_GATE.
177
177
  */
178
178
  get publishabilityGate() {
179
- return env("0SEC_FEATURE_PUBLISHABILITY_GATE", false);
179
+ return env("ZERO_FEATURE_PUBLISHABILITY_GATE", false);
180
180
  },
181
181
  /** PoV gate: require a working, executable PoC per finding or downgrade to info */
182
182
  get povGate() {
183
- return env("0SEC_FEATURE_POV_GATE", false);
183
+ return env("ZERO_FEATURE_POV_GATE", false);
184
184
  },
185
185
  /**
186
186
  * Intra-scan semantic dedupe post-pass (anchored incremental LLM
@@ -188,10 +188,10 @@ var features = {
188
188
  * Marks duplicates with a canonical mapping + cluster reason instead of
189
189
  * dropping them. Default OFF: it spends an LLM call per ≤50-finding batch
190
190
  * after the scan, so it must be explicitly opted into before any A/B
191
- * claim. Toggle via 0SEC_FEATURE_SEMANTIC_DEDUPE.
191
+ * claim. Toggle via ZERO_FEATURE_SEMANTIC_DEDUPE.
192
192
  */
193
193
  get semanticDedupe() {
194
- return env("0SEC_FEATURE_SEMANTIC_DEDUPE", false);
194
+ return env("ZERO_FEATURE_SEMANTIC_DEDUPE", false);
195
195
  },
196
196
  /**
197
197
  * Finding-specific remediation written by the model
@@ -207,33 +207,17 @@ var features = {
207
207
  * degrade cost, never correctness.
208
208
  */
209
209
  get llmRemediation() {
210
- return env("0SEC_FEATURE_LLM_REMEDIATION", false);
211
- },
212
- /**
213
- * Per-finding impact assessment (`assessImpact`, `triage/impact-assessment.ts`)
214
- * written by the model: reachability tier, weaponizability, blast radius,
215
- * business-impact tier. Default OFF — one extra LLM call per non-false-positive
216
- * finding at report time.
217
- *
218
- * When on, the assessment feeds three things it is otherwise absent from:
219
- * a real CVSS exploitability vector (AV/PR/UI from the reachability tier
220
- * rather than the AV:N/severity-floor guess), the advisory's Impact +
221
- * attack-prerequisites section, and the vendor-notification impact line. When
222
- * off, all three fall back to today's category/severity heuristics — so this
223
- * flag strictly adds fidelity, never changes the no-assessment output.
224
- */
225
- get impactAssessment() {
226
- return env("0SEC_FEATURE_IMPACT_ASSESSMENT", false);
210
+ return env("ZERO_FEATURE_LLM_REMEDIATION", false);
227
211
  },
228
212
  /**
229
213
  * Incremental finding ranking post-pass (decimal-insertion between ranked
230
214
  * anchors, `triage/incremental-rank.ts`). Orders the report by comparative
231
215
  * promise (exploitability × impact × evidence strength). Default OFF: it
232
216
  * spends an LLM call per ≤50-finding batch; opt in before any A/B claim.
233
- * Toggle via 0SEC_FEATURE_INCREMENTAL_RANK.
217
+ * Toggle via ZERO_FEATURE_INCREMENTAL_RANK.
234
218
  */
235
219
  get incrementalRank() {
236
- return env("0SEC_FEATURE_INCREMENTAL_RANK", false);
220
+ return env("ZERO_FEATURE_INCREMENTAL_RANK", false);
237
221
  },
238
222
  /**
239
223
  * Static-finding PoC generation (#666 / EPIC #674 Part A). For findings that
@@ -247,10 +231,10 @@ var features = {
247
231
  *
248
232
  * Default OFF: it spends LLM + execution budget per static finding and must
249
233
  * be explicitly opted into before any A/B claim (A/B-able via the #656
250
- * harness). Toggle via 0SEC_FEATURE_POC_GEN_STATIC.
234
+ * harness). Toggle via ZERO_FEATURE_POC_GEN_STATIC.
251
235
  */
252
236
  get pocGenStatic() {
253
- return env("0SEC_FEATURE_POC_GEN_STATIC", false);
237
+ return env("ZERO_FEATURE_POC_GEN_STATIC", false);
254
238
  },
255
239
  /**
256
240
  * Inline validation / validate-on-save (#554). When ON, the native attack
@@ -268,10 +252,10 @@ var features = {
268
252
  * cost_per_flag claim. Implemented as a getter so the CLI `--features` flag
269
253
  * (which sets the env var inside the command action, AFTER this module is
270
254
  * imported) is honored at loop time. Enable via
271
- * 0SEC_FEATURE_INLINE_VALIDATION=1.
255
+ * ZERO_FEATURE_INLINE_VALIDATION=1.
272
256
  */
273
257
  get inlineValidation() {
274
- return env("0SEC_FEATURE_INLINE_VALIDATION", false);
258
+ return env("ZERO_FEATURE_INLINE_VALIDATION", false);
275
259
  },
276
260
  /**
277
261
  * WordPress plugin/theme fingerprinter + OSV CVE lookup.
@@ -286,7 +270,7 @@ var features = {
286
270
  * still honored at tool-dispatch time.
287
271
  */
288
272
  get wpFingerprint() {
289
- return env("0SEC_FEATURE_WP_FINGERPRINT", true);
273
+ return env("ZERO_FEATURE_WP_FINGERPRINT", true);
290
274
  },
291
275
  /**
292
276
  * MongoDB ObjectID forge tool. Exposes the `mongo_objectid` tool to the
@@ -296,23 +280,23 @@ var features = {
296
280
  *
297
281
  * Default ON — this is a pure-computation utility with no network or
298
282
  * filesystem side effects, so there's no reason to gate it off. Disable
299
- * via 0SEC_FEATURE_MONGO_OBJECTID_FORGE=0 or `--no-mongo-objectid-forge`
283
+ * via ZERO_FEATURE_MONGO_OBJECTID_FORGE=0 or `--no-mongo-objectid-forge`
300
284
  * for ablation. Implemented as a getter so the CLI `--features` flag
301
285
  * (which sets the env var inside the command action, AFTER this module
302
286
  * has been imported) is still honored at tool-dispatch time. Matches
303
287
  * the wpFingerprint pattern above. See packages/core/src/agent/objectid-forge.ts.
304
288
  */
305
289
  get mongoObjectIdForge() {
306
- return env("0SEC_FEATURE_MONGO_OBJECTID_FORGE", true);
290
+ return env("ZERO_FEATURE_MONGO_OBJECTID_FORGE", true);
307
291
  },
308
292
  /**
309
- * Live cloud-surface testing (0sec#925). Exposes `cloud_s3_probe` and
293
+ * Live cloud-surface testing (0#925). Exposes `cloud_s3_probe` and
310
294
  * `cloud_validate_credentials` to the attack agent so it can test S3 buckets
311
295
  * for public access + orphaned-bucket takeover and safely validate harvested
312
296
  * AWS credentials (read-only). All probes are anonymous or read/verify-only —
313
297
  * no writes, no data exfiltration beyond minimal proof.
314
298
  *
315
- * Default OFF (opt-in via 0SEC_FEATURE_CLOUD_SURFACE=1). Probing a target
299
+ * Default OFF (opt-in via ZERO_FEATURE_CLOUD_SURFACE=1). Probing a target
316
300
  * org's bucket-name space or validating its harvested credentials is recon
317
301
  * AGAINST THAT ORG, so it is deny-by-default at two layers: this enablement
318
302
  * flag, AND an engagement-scope check in the tool handlers (a configured
@@ -323,7 +307,7 @@ var features = {
323
307
  * mongoObjectIdForge pattern above. See packages/core/src/agent/cloud-surface.ts.
324
308
  */
325
309
  get cloudSurface() {
326
- return env("0SEC_FEATURE_CLOUD_SURFACE", false);
310
+ return env("ZERO_FEATURE_CLOUD_SURFACE", false);
327
311
  },
328
312
  /**
329
313
  * #978 (ADR-060) — agent fan-out. When ON, the agent gets the `start_scan`
@@ -331,11 +315,11 @@ var features = {
331
315
  * that run independently and report up the scan tree — the recursive
332
316
  * sub-agent orchestration. Default OFF: fan-out multiplies scans/cost, so it
333
317
  * stays opt-in even though the orchestrator enforces budget + a tree-level
334
- * cap (max children/depth). Enable with 0SEC_FEATURE_AGENT_FANOUT=1.
318
+ * cap (max children/depth). Enable with ZERO_FEATURE_AGENT_FANOUT=1.
335
319
  * Getter so the CLI `--features` flag is honored at dispatch time.
336
320
  */
337
321
  get agentFanout() {
338
- return env("0SEC_FEATURE_AGENT_FANOUT", false);
322
+ return env("ZERO_FEATURE_AGENT_FANOUT", false);
339
323
  },
340
324
  // ── Phase-2 offensive-engine feature flags (dev-live-engine-recovery) ──
341
325
  // Each gates a tool that RUNS/BUILDS untrusted code or WEAPONIZES. They are
@@ -347,21 +331,21 @@ var features = {
347
331
  /**
348
332
  * `memsafety_fuzz` — clones/builds/fuzzes a source tree (sanitizer builds +
349
333
  * a fuzz harness), executing attacker-adjacent build scripts and native
350
- * fuzz targets. Default OFF; opt in via 0SEC_FEATURE_MEMSAFETY=1. Building
334
+ * fuzz targets. Default OFF; opt in via ZERO_FEATURE_MEMSAFETY=1. Building
351
335
  * and running an untrusted tree is code execution, so it is deny-by-default
352
336
  * behind this flag AND an engagement scope.
353
337
  */
354
338
  get memsafetyFuzz() {
355
- return env("0SEC_FEATURE_MEMSAFETY", false);
339
+ return env("ZERO_FEATURE_MEMSAFETY", false);
356
340
  },
357
341
  /**
358
342
  * `npm_dynamic_discovery` — installs and RUNS untrusted npm packages under
359
343
  * instrumentation to observe malicious install/runtime behaviour. Executing
360
344
  * arbitrary package code is the whole point, so it is deny-by-default behind
361
- * this flag AND an engagement scope. Opt in via 0SEC_FEATURE_NPM_DISCOVERY=1.
345
+ * this flag AND an engagement scope. Opt in via ZERO_FEATURE_NPM_DISCOVERY=1.
362
346
  */
363
347
  get npmDynamicDiscovery() {
364
- return env("0SEC_FEATURE_NPM_DISCOVERY", false);
348
+ return env("ZERO_FEATURE_NPM_DISCOVERY", false);
365
349
  },
366
350
  /**
367
351
  * `weaponize_kernel` — the kernel-exploit weaponization ladder. It only ever
@@ -369,19 +353,19 @@ var features = {
369
353
  * present. Highest-caution capability: deny-by-default behind this flag AND
370
354
  * an engagement scope AND a runtime artifact-presence check (the handler
371
355
  * refuses when the kernel-VM assets are absent). Opt in via
372
- * 0SEC_FEATURE_KERNEL_WEAPONIZE=1.
356
+ * ZERO_FEATURE_KERNEL_WEAPONIZE=1.
373
357
  */
374
358
  get kernelWeaponize() {
375
- return env("0SEC_FEATURE_KERNEL_WEAPONIZE", false);
359
+ return env("ZERO_FEATURE_KERNEL_WEAPONIZE", false);
376
360
  },
377
361
  /**
378
362
  * `cve_adapt` — adapts a public CVE PoC to the target and RUNS it to confirm
379
363
  * exploitability. Running an adapted exploit is code execution against the
380
364
  * target, so it is deny-by-default behind this flag AND an engagement scope.
381
- * Opt in via 0SEC_FEATURE_CVE_ADAPT=1.
365
+ * Opt in via ZERO_FEATURE_CVE_ADAPT=1.
382
366
  */
383
367
  get cveAdapt() {
384
- return env("0SEC_FEATURE_CVE_ADAPT", false);
368
+ return env("ZERO_FEATURE_CVE_ADAPT", false);
385
369
  },
386
370
  /**
387
371
  * Anti-honeypot flag-shape validator. When the agent calls the `done`
@@ -392,7 +376,7 @@ var features = {
392
376
  *
393
377
  * Default ON because legitimate flags pass the shape check trivially
394
378
  * and the false-positive rate on real flags should be near zero. Turn
395
- * off via `0SEC_FEATURE_DECOY_DETECTION=0` or the CLI flag
379
+ * off via `ZERO_FEATURE_DECOY_DETECTION=0` or the CLI flag
396
380
  * `--no-decoy-detection` for ablation/testing.
397
381
  *
398
382
  * Implemented as a getter so the CLI flag (which flips the env var
@@ -402,7 +386,7 @@ var features = {
402
386
  * packages/core/src/agent/flag-validator.ts.
403
387
  */
404
388
  get decoyDetection() {
405
- return env("0SEC_FEATURE_DECOY_DETECTION", true);
389
+ return env("ZERO_FEATURE_DECOY_DETECTION", true);
406
390
  },
407
391
  // ── Always-on triage filters (default ON, ablatable for A/B testing) ──
408
392
  /**
@@ -411,13 +395,13 @@ var features = {
411
395
  * sink names and rejects findings that look like "the function did its job".
412
396
  *
413
397
  * Default ON because that's the existing v0.6.0 behavior. Can be disabled
414
- * via 0SEC_FEATURE_HOLDING_IT_WRONG=0 to test whether this filter is
398
+ * via ZERO_FEATURE_HOLDING_IT_WRONG=0 to test whether this filter is
415
399
  * suppressing real signal — the ceiling-analysis from 2026-04-06 identified
416
400
  * this as the strongest candidate for the unexplained XBOW finding-density
417
401
  * collapse from 14 → 4 between `features=none` and `features=all`.
418
402
  */
419
403
  get holdingItWrong() {
420
- return env("0SEC_FEATURE_HOLDING_IT_WRONG", true);
404
+ return env("ZERO_FEATURE_HOLDING_IT_WRONG", true);
421
405
  },
422
406
  /**
423
407
  * `evidence_completeness <= 0.5` reject (`packages/core/src/agentic-scanner.ts:591`).
@@ -425,10 +409,10 @@ var features = {
425
409
  * gather enough cross-source evidence (request + response + analysis + ...).
426
410
  *
427
411
  * Default ON because that's the existing v0.6.0 behavior. Can be disabled
428
- * via 0SEC_FEATURE_EVIDENCE_GATE=0 for ablation.
412
+ * via ZERO_FEATURE_EVIDENCE_GATE=0 for ablation.
429
413
  */
430
414
  get evidenceGate() {
431
- return env("0SEC_FEATURE_EVIDENCE_GATE", true);
415
+ return env("ZERO_FEATURE_EVIDENCE_GATE", true);
432
416
  },
433
417
  /**
434
418
  * Learned per-finding triage router (`packages/core/src/triage/learned-router.ts`).
@@ -439,17 +423,17 @@ var features = {
439
423
  * the scan's slice type (xbow-wb, xbow-bb, npm).
440
424
  *
441
425
  * Default OFF until the router is validated via A/B testing on xbow-bench
442
- * and npm-bench. See 0sec#113 for the design doc.
426
+ * and npm-bench. See 0#113 for the design doc.
443
427
  */
444
428
  get learnedRouter() {
445
- return env("0SEC_FEATURE_LEARNED_ROUTER", false);
429
+ return env("ZERO_FEATURE_LEARNED_ROUTER", false);
446
430
  },
447
431
  /**
448
432
  * Dynamic per-finding triage routing (`packages/core/src/triage/router/`).
449
433
  * When enabled, every finding is sent through a `RouterModel` that
450
434
  * decides which subset of the 11 triage layers to invoke for that
451
435
  * specific finding. v0 ships an explicit-rule router encoded from the
452
- * 0sec#72 per-profile ablation; a learned classifier replaces the
436
+ * 0#72 per-profile ablation; a learned classifier replaces the
453
437
  * rules in a follow-up PR without touching the dispatch site.
454
438
  *
455
439
  * Distinct from `learnedRouter` above: `learnedRouter` is the XGBoost
@@ -458,25 +442,25 @@ var features = {
458
442
  * the dispatch router gates which layers run AFTER the TP/FP score
459
443
  * model has spoken.
460
444
  *
461
- * Default OFF — opt in via 0SEC_FEATURE_DYNAMIC_TRIAGE=1. See
462
- * 0sec#113 for the design doc and 0sec#67 for the joint paper plan.
445
+ * Default OFF — opt in via ZERO_FEATURE_DYNAMIC_TRIAGE=1. See
446
+ * 0#113 for the design doc and 0#67 for the joint paper plan.
463
447
  */
464
448
  get dynamicTriageRouting() {
465
- return env("0SEC_FEATURE_DYNAMIC_TRIAGE", false);
449
+ return env("ZERO_FEATURE_DYNAMIC_TRIAGE", false);
466
450
  },
467
451
  /**
468
452
  * Opt-in cloud-sink webhook integration (`packages/core/src/cloud-sink.ts`).
469
- * When enabled AND the user has set 0SEC_CLOUD_SINK + 0SEC_CLOUD_SCAN_ID,
453
+ * When enabled AND the user has set ZERO_CLOUD_SINK + ZERO_CLOUD_SCAN_ID,
470
454
  * every finding and the final scan report are POSTed to the configured
471
455
  * remote endpoint in real time.
472
456
  *
473
457
  * Default ON so the env-var trio is sufficient to enable streaming, but the
474
458
  * flag exists so operators can force-disable the integration in environments
475
459
  * where outbound HTTP from the scanner is not desired (e.g. air-gapped CI).
476
- * Disable via 0SEC_FEATURE_CLOUD_SINK=0.
460
+ * Disable via ZERO_FEATURE_CLOUD_SINK=0.
477
461
  */
478
462
  get cloudSink() {
479
- return env("0SEC_FEATURE_CLOUD_SINK", true);
463
+ return env("ZERO_FEATURE_CLOUD_SINK", true);
480
464
  },
481
465
  /**
482
466
  * Pre-recon CVE check (`packages/core/src/pre-recon-cve.ts`).
@@ -487,10 +471,10 @@ var features = {
487
471
  * where the agent has source access but no concrete leads.
488
472
  *
489
473
  * Default ON in white-box mode (no-op in black-box). Disable via
490
- * 0SEC_FEATURE_PRE_RECON_CVE=0 for ablation.
474
+ * ZERO_FEATURE_PRE_RECON_CVE=0 for ablation.
491
475
  */
492
476
  get preReconCve() {
493
- return env("0SEC_FEATURE_PRE_RECON_CVE", true);
477
+ return env("ZERO_FEATURE_PRE_RECON_CVE", true);
494
478
  },
495
479
  /**
496
480
  * Deterministic web-recon pre-pass (`packages/core/src/stages/web-recon-prepass.ts`).
@@ -501,25 +485,25 @@ var features = {
501
485
  * checks. It EMITS findings directly for what it can prove and injects a
502
486
  * "pursue these leads" block into the system prompt for what it can only hint.
503
487
  *
504
- * Default ON (no-op in non-web modes). Gated behind 0SEC_FEATURE_WEB_RECON
488
+ * Default ON (no-op in non-web modes). Gated behind ZERO_FEATURE_WEB_RECON
505
489
  * so it can be disabled for ablation or offline runs. Implemented as a getter
506
490
  * so the CLI `--features` flag (which sets the env var inside the command
507
491
  * action, AFTER this module has been imported) is honored at stage time.
508
492
  */
509
493
  get webRecon() {
510
- return env("0SEC_FEATURE_WEB_RECON", true);
494
+ return env("ZERO_FEATURE_WEB_RECON", true);
511
495
  },
512
496
  /**
513
497
  * Best-effort target-history preflight for source review. When a local repo
514
- * path is known, 0sec infers repository/package/product hints, queries live
498
+ * path is known, 0 infers repository/package/product hints, queries live
515
499
  * prior-vulnerability intel, and injects a compact audit-graph summary into
516
500
  * the review prompt before the agent starts.
517
501
  *
518
502
  * Default ON for white-box/source-review modes. Disable via
519
- * 0SEC_FEATURE_TARGET_HISTORY_PRESEED=0 for offline or ablation runs.
503
+ * ZERO_FEATURE_TARGET_HISTORY_PRESEED=0 for offline or ablation runs.
520
504
  */
521
505
  get targetHistoryPreseed() {
522
- return env("0SEC_FEATURE_TARGET_HISTORY_PRESEED", true);
506
+ return env("ZERO_FEATURE_TARGET_HISTORY_PRESEED", true);
523
507
  },
524
508
  /**
525
509
  * Preserve credential / exploit-bearing messages verbatim during
@@ -534,15 +518,15 @@ var features = {
534
518
  * (a handful of extra messages preserved verbatim in the user
535
519
  * compaction-summary block) is small. BoxPwnr-inspired: see
536
520
  * `src/boxpwnr/solvers/single_loop_compactation.py` in 0ca/BoxPwnr,
537
- * and 0sec#229 for the design discussion.
521
+ * and 0#229 for the design discussion.
538
522
  *
539
523
  * Implemented as a getter so the CLI `--features` flag — which sets
540
524
  * the env var inside the command action AFTER this module is imported
541
525
  * — is still honored at compaction time. Disable via
542
- * 0SEC_FEATURE_PRESERVE_CRITICAL_MESSAGES=0 for ablation.
526
+ * ZERO_FEATURE_PRESERVE_CRITICAL_MESSAGES=0 for ablation.
543
527
  */
544
528
  get preserveCriticalMessages() {
545
- return env("0SEC_FEATURE_PRESERVE_CRITICAL_MESSAGES", true);
529
+ return env("ZERO_FEATURE_PRESERVE_CRITICAL_MESSAGES", true);
546
530
  },
547
531
  /**
548
532
  * Two-stage budget-warning injection in the agent loop (#408).
@@ -558,14 +542,14 @@ var features = {
558
542
  * single short user-message injection at two specific turn boundaries,
559
543
  * and the win on long benchmarks (clean handoff instead of stray
560
544
  * exploration on the last turn) is well-documented in Strix's
561
- * implementation. Disable via 0SEC_FEATURE_BUDGET_WARNINGS=0 for
545
+ * implementation. Disable via ZERO_FEATURE_BUDGET_WARNINGS=0 for
562
546
  * ablation. Implemented as a getter so the CLI `--features` flag —
563
547
  * which sets the env var inside the command action AFTER this module
564
548
  * is imported — is still honored at injection time (matches the
565
549
  * wpFingerprint / preserveCriticalMessages pattern).
566
550
  */
567
551
  get budgetWarnings() {
568
- return env("0SEC_FEATURE_BUDGET_WARNINGS", true);
552
+ return env("ZERO_FEATURE_BUDGET_WARNINGS", true);
569
553
  },
570
554
  /**
571
555
  * Per-file orchestration for the research and audit stages (#285).
@@ -579,14 +563,14 @@ var features = {
579
563
  * Trade-off: total token spend grows roughly N × per-file budget instead
580
564
  * of capped at a single session's budget. For a 50-file package, that
581
565
  * could be a 5-10× cost increase on research. Disable via
582
- * `0SEC_FEATURE_PER_ITEM_ORCHESTRATION=0` to revert to the shared-session
566
+ * `ZERO_FEATURE_PER_ITEM_ORCHESTRATION=0` to revert to the shared-session
583
567
  * behavior — useful for cost-bounded benchmarks.
584
568
  *
585
569
  * Implemented as a getter so the env var is honored at orchestration time
586
570
  * (matches the wpFingerprint / mongoObjectIdForge pattern).
587
571
  */
588
572
  get perItemOrchestration() {
589
- return env("0SEC_FEATURE_PER_ITEM_ORCHESTRATION", true);
573
+ return env("ZERO_FEATURE_PER_ITEM_ORCHESTRATION", true);
590
574
  },
591
575
  /**
592
576
  * JIT skill loading (`packages/core/src/agent/skills/`).
@@ -595,20 +579,21 @@ var features = {
595
579
  * them into working context mid-scan. Skills replace the monolithic
596
580
  * playbook injection with targeted, on-demand knowledge (#410, #457).
597
581
  *
598
- * Default OFF until the skill registry is validated via A/B testing.
582
+ * Default OFF unless an assigned cloud methodology manifest opts this run in.
583
+ * An explicit feature flag still takes precedence over that default.
599
584
  * Implemented as a getter so the CLI `--features` flag — which sets
600
585
  * the env var inside the command action, AFTER this module has been
601
586
  * imported — is still honored at tool-dispatch time.
602
587
  */
603
588
  get jitSkills() {
604
- return env("0SEC_FEATURE_JIT_SKILLS", false);
589
+ return env("ZERO_FEATURE_JIT_SKILLS", Boolean(process.env["ZERO_AUDIT_SKILLS_MANIFEST"]?.trim()));
605
590
  },
606
591
  /**
607
592
  * Execution-journal shadow mode (#494, first additive slice).
608
593
  *
609
594
  * When ON, the live agent loop ALSO writes append-only journal entries
610
595
  * (`tool_call`, `tool_result`, `finding`, `done`) to
611
- * `~/.0sec/runs/<scanId>/journal.jsonl` as it runs — a durable,
596
+ * `~/.0/runs/<scanId>/journal.jsonl` as it runs — a durable,
612
597
  * replayable trace alongside the existing in-memory conversation window.
613
598
  * This is strictly additive: the loop continues to drive off its own
614
599
  * conversation state, the journal is write-only here, and a failed
@@ -621,11 +606,11 @@ var features = {
621
606
  * moat-ablation harness before any A/B claim. Implemented as a getter so
622
607
  * the CLI `--features` flag (which sets the env var inside the command
623
608
  * action, AFTER this module has been imported) is honored at loop time.
624
- * Enable via 0SEC_FEATURE_EXECUTION_JOURNAL=1 or `--features
609
+ * Enable via ZERO_FEATURE_EXECUTION_JOURNAL=1 or `--features
625
610
  * execution-journal`.
626
611
  */
627
612
  get executionJournal() {
628
- return env("0SEC_FEATURE_EXECUTION_JOURNAL", false);
613
+ return env("ZERO_FEATURE_EXECUTION_JOURNAL", false);
629
614
  },
630
615
  /**
631
616
  * Execution-journal context routing (#494, slice 2).
@@ -641,7 +626,7 @@ var features = {
641
626
  * Independent of `executionJournal` (the shadow-WRITE flag) on purpose so
642
627
  * the moat-ablation harness can toggle write and route separately for a
643
628
  * clean A/B. Rehydrate is a READER, though, so it only does anything when
644
- * a journal was written for the run — it reads `~/.0sec/runs/<scanId>/
629
+ * a journal was written for the run — it reads `~/.0/runs/<scanId>/
645
630
  * journal.jsonl` regardless of how it got there (shadow mode this slice,
646
631
  * or specialists in a later slice). When the journal is missing, empty, or
647
632
  * corrupt the loop falls back to the existing DB-blob / fresh-prompt
@@ -654,11 +639,11 @@ var features = {
654
639
  * must be explicitly opted into before any A/B claim. Implemented as a
655
640
  * getter so the CLI `--features` flag (which sets the env var inside the
656
641
  * command action, AFTER this module has been imported) is honored at loop
657
- * time. Enable via 0SEC_FEATURE_JOURNAL_REHYDRATE=1 or `--features
642
+ * time. Enable via ZERO_FEATURE_JOURNAL_REHYDRATE=1 or `--features
658
643
  * journal-rehydrate`.
659
644
  */
660
645
  get journalRehydrate() {
661
- return env("0SEC_FEATURE_JOURNAL_REHYDRATE", false);
646
+ return env("ZERO_FEATURE_JOURNAL_REHYDRATE", false);
662
647
  },
663
648
  /**
664
649
  * Loot / foothold ledger for opportunistic exploit chaining (#567).
@@ -678,14 +663,14 @@ var features = {
678
663
  * tool), matches the `preserveCriticalMessages` rationale — recovering a
679
664
  * credential in turn 12 that's needed in turn 38 is a large win on long-tail
680
665
  * challenges — and the cost (a short, size-capped block per turn) is small.
681
- * Disable via 0SEC_FEATURE_LOOT_LEDGER=0 or `--no-loot-ledger` for
666
+ * Disable via ZERO_FEATURE_LOOT_LEDGER=0 or `--no-loot-ledger` for
682
667
  * ablation. Implemented as a getter so the CLI `--features` flag (which sets
683
668
  * the env var inside the command action, AFTER this module has been
684
669
  * imported) is honored at tool-dispatch / injection time — matches the
685
670
  * wpFingerprint / preserveCriticalMessages pattern.
686
671
  */
687
672
  get lootLedger() {
688
- return env("0SEC_FEATURE_LOOT_LEDGER", true);
673
+ return env("ZERO_FEATURE_LOOT_LEDGER", true);
689
674
  },
690
675
  /**
691
676
  * Typed TODO / plan ledger (`packages/core/src/agent/task-ledger.ts`).
@@ -704,13 +689,13 @@ var features = {
704
689
  * failure mode of an unused tool is a few hundred wasted schema tokens
705
690
  * rather than wrong behavior. Note for whoever publishes benchmark numbers
706
691
  * next: this DOES change the default tool list, so re-baseline before
707
- * quoting a figure across this change. Disable via 0SEC_FEATURE_AGENT_PLAN=0
692
+ * quoting a figure across this change. Disable via ZERO_FEATURE_AGENT_PLAN=0
708
693
  * or `--features no-agent-plan` for ablation. Getter so the CLI `--features`
709
694
  * flag (which sets the env var AFTER this module is imported) is honored at
710
695
  * tool-dispatch time.
711
696
  */
712
697
  get agentPlan() {
713
- return env("0SEC_FEATURE_AGENT_PLAN", false);
698
+ return env("ZERO_FEATURE_AGENT_PLAN", false);
714
699
  },
715
700
  /**
716
701
  * Task-drift detection (`packages/core/src/agent/drift.ts`).
@@ -733,10 +718,10 @@ var features = {
733
718
  * to a newly-discovered lead is lexically indistinguishable from a derail.
734
719
  * Repo convention is explicit that behavior-steering features stay opt-in
735
720
  * until A/B'd, and this is squarely one. Enable via
736
- * 0SEC_FEATURE_DRIFT_DETECTION=1 or `--features drift-detection`.
721
+ * ZERO_FEATURE_DRIFT_DETECTION=1 or `--features drift-detection`.
737
722
  */
738
723
  get driftDetection() {
739
- return env("0SEC_FEATURE_DRIFT_DETECTION", false);
724
+ return env("ZERO_FEATURE_DRIFT_DETECTION", false);
740
725
  },
741
726
  /**
742
727
  * OAST out-of-band interaction collaborator + oracle (#659).
@@ -748,12 +733,12 @@ var features = {
748
733
  * evidence and feeds the loot ledger.
749
734
  *
750
735
  * Default OFF — the tools are inert without a deployed collaborator. Enable
751
- * with 0SEC_FEATURE_OAST=1 AND point 0SEC_OAST_URL at the self-hosted
736
+ * with ZERO_FEATURE_OAST=1 AND point ZERO_OAST_URL at the self-hosted
752
737
  * collaborator server (see packages/core/src/oast/server.ts). Getter (not a
753
738
  * const) so the CLI `--features` flag is honored at tool-dispatch time.
754
739
  */
755
740
  get oastCollaborator() {
756
- return env("0SEC_FEATURE_OAST", false);
741
+ return env("ZERO_FEATURE_OAST", false);
757
742
  },
758
743
  /**
759
744
  * Anthropic prompt caching (`cache_control: {type: "ephemeral"}`) over the
@@ -776,17 +761,17 @@ var features = {
776
761
  * never see an Anthropic-shaped field regardless of this flag (see
777
762
  * `providerSupportsPromptCache`).
778
763
  *
779
- * Disable via 0SEC_FEATURE_PROMPT_CACHE=0 — worth doing only to isolate a
764
+ * Disable via ZERO_FEATURE_PROMPT_CACHE=0 — worth doing only to isolate a
780
765
  * suspected provider-side caching bug, or to measure the uncached baseline.
781
766
  * Implemented as a getter so a late env mutation (CLI `--features`, which
782
767
  * runs after this module is imported) is honoured at request-build time.
783
768
  */
784
769
  get promptCache() {
785
- return env("0SEC_FEATURE_PROMPT_CACHE", true);
770
+ return env("ZERO_FEATURE_PROMPT_CACHE", true);
786
771
  }
787
772
  };
788
773
  function presetRaisesDefault(key) {
789
- const raw = process.env["0SEC_FEATURE_PRESET"];
774
+ const raw = process.env["ZERO_FEATURE_PRESET"];
790
775
  if (!raw)
791
776
  return false;
792
777
  const preset = resolveFeaturePreset(raw);
@@ -884,7 +869,7 @@ function sanitizeFields(input) {
884
869
  var LEVEL_RANK = { info: 10, warn: 20, error: 30 };
885
870
  var OFF_RANK = Number.POSITIVE_INFINITY;
886
871
  function minimumRank() {
887
- const raw = process.env["0SEC_DIAG_LEVEL"];
872
+ const raw = process.env["ZERO_DIAG_LEVEL"];
888
873
  if (!raw)
889
874
  return LEVEL_RANK.info;
890
875
  switch (raw.trim().toLowerCase()) {
@@ -904,9 +889,9 @@ function minimumRank() {
904
889
  function formatDiagnosticLine(event) {
905
890
  const keys = Object.keys(event.fields);
906
891
  if (keys.length === 0)
907
- return `[0sec] ${event.message}`;
892
+ return `[0] ${event.message}`;
908
893
  const rendered = keys.map((k) => `${k}=${event.fields[k]}`).join(" ");
909
- return `[0sec] ${event.message} (${rendered})`;
894
+ return `[0] ${event.message} (${rendered})`;
910
895
  }
911
896
  var stderrDiagnosticSink = {
912
897
  emit(event) {
@@ -1087,19 +1072,22 @@ function loadCloudCredentials(opts = {}) {
1087
1072
  const env2 = opts.env ?? process.env;
1088
1073
  const warn = opts.warn ?? ((m) => process.stderr.write(`${m}
1089
1074
  `));
1090
- const envTok = env2["0SEC_CLOUD_TOKEN"]?.trim();
1075
+ const envTok = env2["ZERO_CLOUD_TOKEN"]?.trim() || env2["0SEC_CLOUD_TOKEN"]?.trim();
1091
1076
  if (envTok) {
1092
- const envHost = normaliseHost(env2["0SEC_CLOUD_HOST"]?.trim() ?? DEFAULT_CLOUD_HOST);
1077
+ const envHost = normaliseHost(env2["ZERO_CLOUD_HOST"]?.trim() ?? env2["0SEC_CLOUD_HOST"]?.trim() ?? DEFAULT_CLOUD_HOST);
1078
+ if (!env2["ZERO_CLOUD_TOKEN"]?.trim()) {
1079
+ warn("[0 cloud] using legacy 0SEC_CLOUD_* credentials; re-run `0 auth login` to migrate to ZERO_CLOUD_*.");
1080
+ }
1093
1081
  return { host: envHost, token: envTok, source: "env" };
1094
1082
  }
1095
- const path = join(homeStateDir(opts.homeDir), "cloud.env");
1083
+ const path = join(cloudStateDir(opts.homeDir, env2), "cloud.env");
1096
1084
  let raw;
1097
1085
  try {
1098
1086
  raw = readFileSync(path, "utf-8");
1099
1087
  } catch (err) {
1100
1088
  const code = err.code;
1101
1089
  if (code === "ENOENT") {
1102
- throw new CloudAuthMissingError(`0sec-cloud credentials not found. Run \`0sec auth login\` or set 0SEC_CLOUD_TOKEN in env, or create ${path} (chmod 600) with 0SEC_CLOUD_TOKEN=\u2026 (optionally 0SEC_CLOUD_HOST=\u2026).`);
1090
+ throw new CloudAuthMissingError(`0-cloud credentials not found. Run \`${env2["ZERO_DEV_SOURCE_ROOT"]?.trim() ? "0dev" : "0"} auth login\`.`);
1103
1091
  }
1104
1092
  throw err;
1105
1093
  }
@@ -1107,22 +1095,25 @@ function loadCloudCredentials(opts = {}) {
1107
1095
  const st = statSync(path);
1108
1096
  const mode = st.mode & 511;
1109
1097
  if (mode !== 384) {
1110
- warn(`[0sec cloud] WARNING: ${path} mode is ${mode.toString(8).padStart(3, "0")} (expected 600). Run: chmod 600 ${path}`);
1098
+ warn(`[0 cloud] WARNING: ${path} mode is ${mode.toString(8).padStart(3, "0")} (expected 600). Run: chmod 600 ${path}`);
1111
1099
  }
1112
1100
  } catch {
1113
1101
  }
1114
1102
  const parsed = parseEnvFile(raw);
1115
- const fileTok = parsed["0SEC_CLOUD_TOKEN"]?.trim();
1103
+ const fileTok = parsed["ZERO_CLOUD_TOKEN"]?.trim() || parsed["0SEC_CLOUD_TOKEN"]?.trim();
1116
1104
  if (!fileTok) {
1117
- throw new CloudAuthMissingError(`0sec-cloud credentials in ${path} are incomplete: 0SEC_CLOUD_TOKEN is required.`);
1105
+ throw new CloudAuthMissingError(`0-cloud credentials in ${path} are incomplete: ZERO_CLOUD_TOKEN is required.`);
1106
+ }
1107
+ if (!parsed["ZERO_CLOUD_TOKEN"]?.trim()) {
1108
+ warn("[0 cloud] using legacy 0SEC_CLOUD_* credentials; re-run `0 auth login` to migrate to ZERO_CLOUD_*.");
1118
1109
  }
1119
- const fileHost = normaliseHost(parsed["0SEC_CLOUD_HOST"]?.trim() ?? DEFAULT_CLOUD_HOST);
1110
+ const fileHost = normaliseHost(parsed["ZERO_CLOUD_HOST"]?.trim() ?? parsed["0SEC_CLOUD_HOST"]?.trim() ?? env2["ZERO_CLOUD_HOST"]?.trim() ?? env2["0SEC_CLOUD_HOST"]?.trim() ?? DEFAULT_CLOUD_HOST);
1120
1111
  return { host: fileHost, token: fileTok, source: "file" };
1121
1112
  }
1122
1113
  function normaliseHost(host) {
1123
1114
  let h = host;
1124
1115
  if (!/^https?:\/\//.test(h)) {
1125
- throw new CloudAuthMissingError(`0SEC_CLOUD_HOST must be an http(s) URL (got ${JSON.stringify(host)}).`);
1116
+ throw new CloudAuthMissingError(`ZERO_CLOUD_HOST must be an http(s) URL (got ${JSON.stringify(host)}).`);
1126
1117
  }
1127
1118
  while (h.endsWith("/"))
1128
1119
  h = h.slice(0, -1);
@@ -1167,26 +1158,92 @@ var CloudError = class extends Error {
1167
1158
  };
1168
1159
  var CloudUnauthorizedError = class extends CloudError {
1169
1160
  constructor(path) {
1170
- super(`0sec-cloud auth rejected (HTTP 401) on ${path}. Run \`0sec auth login\` to refresh.`, 401, path);
1161
+ super(`0-cloud auth rejected (HTTP 401) on ${path}. Run \`0 auth login\` to refresh.`, 401, path);
1171
1162
  this.name = "CloudUnauthorizedError";
1172
1163
  }
1173
1164
  };
1174
1165
  var CloudForbiddenError = class extends CloudError {
1175
1166
  constructor(path) {
1176
- super(`0sec-cloud forbidden (HTTP 403) on ${path}. Token lacks scope for this resource.`, 403, path);
1167
+ super(`0-cloud forbidden (HTTP 403) on ${path}. Token lacks scope for this resource.`, 403, path);
1177
1168
  this.name = "CloudForbiddenError";
1178
1169
  }
1179
1170
  };
1180
1171
  var CloudNetworkError = class extends CloudError {
1181
1172
  constructor(message, path) {
1182
- super(`0sec-cloud network error on ${path}: ${message}`, void 0, path);
1173
+ super(`0-cloud network error on ${path}: ${message}`, void 0, path);
1183
1174
  this.name = "CloudNetworkError";
1184
1175
  }
1185
1176
  };
1177
+ function isUsageAccount(raw) {
1178
+ if (!raw || typeof raw !== "object")
1179
+ return false;
1180
+ const obj = raw;
1181
+ if (obj.schemaVersion !== "usage-v2")
1182
+ return false;
1183
+ if (!isUtcDate(obj.snapshotAt))
1184
+ return false;
1185
+ if (!obj.scope || typeof obj.scope !== "object")
1186
+ return false;
1187
+ if (typeof obj.scope.orgId !== "string")
1188
+ return false;
1189
+ if (typeof obj.state !== "string")
1190
+ return false;
1191
+ if (!["ready", "disabled", "unavailable", "restricted"].includes(obj.state))
1192
+ return false;
1193
+ if (obj.reason !== null && typeof obj.reason !== "string")
1194
+ return false;
1195
+ const plan = obj.plan;
1196
+ if (!plan || typeof plan !== "object")
1197
+ return false;
1198
+ if (typeof plan.id !== "string" && plan.id !== null)
1199
+ return false;
1200
+ if (plan.id !== null && !["pro", "gold", "enterprise"].includes(plan.id))
1201
+ return false;
1202
+ if (typeof plan.name !== "string" && plan.name !== null)
1203
+ return false;
1204
+ if (!isUsd(plan.monthlyPriceUsd))
1205
+ return false;
1206
+ const included = obj.included;
1207
+ if (!included || typeof included !== "object")
1208
+ return false;
1209
+ if (typeof included.state !== "string")
1210
+ return false;
1211
+ if (!["active", "exhausted", "none", "unavailable"].includes(included.state))
1212
+ return false;
1213
+ if (included.usedPercent !== null && typeof included.usedPercent !== "number")
1214
+ return false;
1215
+ if (included.usedPercent !== null && (!Number.isFinite(included.usedPercent) || included.usedPercent < 0 || included.usedPercent > 100))
1216
+ return false;
1217
+ if (included.resetsAt !== null && !isUtcDate(included.resetsAt))
1218
+ return false;
1219
+ const prepaid = obj.prepaid;
1220
+ if (!prepaid || typeof prepaid !== "object")
1221
+ return false;
1222
+ if (!isUsd(prepaid.balanceUsd))
1223
+ return false;
1224
+ if (typeof prepaid.fallbackEnabled !== "boolean")
1225
+ return false;
1226
+ if (typeof obj.canManageBilling !== "boolean")
1227
+ return false;
1228
+ const admission = obj.admission;
1229
+ if (!admission || typeof admission !== "object")
1230
+ return false;
1231
+ if (typeof admission.eligible !== "boolean")
1232
+ return false;
1233
+ if (admission.reason !== null && typeof admission.reason !== "string")
1234
+ return false;
1235
+ return true;
1236
+ }
1237
+ function isUsd(value) {
1238
+ return value === null || typeof value === "string" && /^\d+(?:\.\d{1,9})?$/.test(value);
1239
+ }
1240
+ function isUtcDate(value) {
1241
+ return typeof value === "string" && /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z$/.test(value) && Number.isFinite(Date.parse(value));
1242
+ }
1186
1243
  function healthPath(host) {
1187
1244
  try {
1188
1245
  const hostname = new URL(host).hostname.toLowerCase();
1189
- if (hostname === "cloud.0sec.ai" || hostname === "cloud.0.security") {
1246
+ if (hostname === "cloud.0.ai" || hostname === "cloud.0.security") {
1190
1247
  return "/api/health";
1191
1248
  }
1192
1249
  } catch {
@@ -1219,25 +1276,46 @@ var CloudClient = class {
1219
1276
  return this.getJson("/api/inference/v1/models");
1220
1277
  }
1221
1278
  /**
1222
- * Fetch the organization's hosted inference credit availability. The service
1223
- * calculates the percentage from Autumn's current pool; holds reduce availability.
1224
- * Older gateways without percentage metadata remain explicitly unavailable.
1279
+ * Fetch the organization's credit account — usage-v2 shape including
1280
+ * plan, included allowance, prepaid balance, and admission status.
1281
+ *
1282
+ * Returns `null` when the response is a recognised HTTP 200 (customer is
1283
+ * authenticated) but the payload is missing, legacy, or structurally
1284
+ * unrecognised — not an auth failure. HTTP 401/403 still throw the
1285
+ * existing typed errors so the caller can distinguish a credential
1286
+ * problem from unsupported credit data.
1287
+ *
1288
+ * Monetary amounts are decimal strings (no Number coercion); the
1289
+ * caller preserves them for exact display.
1225
1290
  */
1226
1291
  async getInferenceAccount() {
1227
- const account = await this.getJson("/api/inference/account");
1228
- const credits = account.credits;
1229
- if (!credits || typeof credits !== "object" || typeof credits.featureId !== "string" || !credits.featureId.trim() || typeof credits.remaining !== "number" || !Number.isFinite(credits.remaining) || credits.remaining < 0 || credits.granted !== null && (typeof credits.granted !== "number" || !Number.isFinite(credits.granted) || credits.granted < 0)) {
1230
- return { ...account, credits: null };
1231
- }
1232
- const remainingPercent = typeof credits.remainingPercent === "number" && Number.isFinite(credits.remainingPercent) && credits.remainingPercent >= 0 && credits.remainingPercent <= 100 && credits.granted !== null && credits.granted > 0 && credits.remaining <= credits.granted ? credits.remainingPercent : null;
1233
- const nextResetAt = typeof credits.nextResetAt === "number" && Number.isSafeInteger(credits.nextResetAt) && credits.nextResetAt > 0 && credits.nextResetAt <= 864e13 ? credits.nextResetAt : null;
1234
- return { ...account, credits: {
1235
- featureId: credits.featureId,
1236
- granted: credits.granted,
1237
- remaining: credits.remaining,
1238
- remainingPercent,
1239
- nextResetAt
1240
- } };
1292
+ const raw = await this.getJson("/api/inference/account");
1293
+ if (!isUsageAccount(raw))
1294
+ return null;
1295
+ const { plan, included, prepaid, admission } = raw;
1296
+ return {
1297
+ schemaVersion: raw.schemaVersion,
1298
+ snapshotAt: raw.snapshotAt,
1299
+ scope: { orgId: raw.scope.orgId },
1300
+ state: raw.state,
1301
+ reason: raw.reason,
1302
+ plan: {
1303
+ id: plan.id,
1304
+ name: plan.name,
1305
+ monthlyPriceUsd: plan.monthlyPriceUsd
1306
+ },
1307
+ included: {
1308
+ state: included.state,
1309
+ usedPercent: included.usedPercent,
1310
+ resetsAt: included.resetsAt
1311
+ },
1312
+ prepaid: {
1313
+ balanceUsd: prepaid.balanceUsd,
1314
+ fallbackEnabled: prepaid.fallbackEnabled
1315
+ },
1316
+ canManageBilling: raw.canManageBilling,
1317
+ admission: { eligible: admission.eligible, reason: admission.reason }
1318
+ };
1241
1319
  }
1242
1320
  /**
1243
1321
  * Fetch request-level usage metadata for the operator's hosted
@@ -1247,6 +1325,114 @@ var CloudClient = class {
1247
1325
  async getInferenceUsage() {
1248
1326
  return this.getJson("/api/inference/usage");
1249
1327
  }
1328
+ // ── Audit-skills helpers (#audit-skills) ──
1329
+ /** List all audit skills for the authenticated organization. */
1330
+ async listAuditSkills() {
1331
+ return this.getJson("/api/audit-skills");
1332
+ }
1333
+ /**
1334
+ * Get a single audit skill with its revision history and project
1335
+ * assignments.
1336
+ */
1337
+ async getAuditSkill(id) {
1338
+ return this.getJson(`/api/audit-skills/${encodeURIComponent(id)}`);
1339
+ }
1340
+ /** Create a new markdown-based audit skill. */
1341
+ async createAuditSkill(input) {
1342
+ return this.postJson("/api/audit-skills", input);
1343
+ }
1344
+ /** Import an audit skill from a GitHub repository. */
1345
+ async importAuditSkillFromGithub(input) {
1346
+ return this.postJson("/api/audit-skills/import", input);
1347
+ }
1348
+ /** Create a new revision of an audit skill (CAS — 409 on stale expectedRevision). */
1349
+ async createAuditSkillRevision(id, input) {
1350
+ return this.postJson(`/api/audit-skills/${encodeURIComponent(id)}/revisions`, input);
1351
+ }
1352
+ /** Re-fetch the skill from its original GitHub source. */
1353
+ async syncAuditSkill(id, expectedRevision) {
1354
+ return this.postJson(`/api/audit-skills/${encodeURIComponent(id)}/sync`, { expectedRevision });
1355
+ }
1356
+ /** Pin an audit skill revision to a project for future scans. */
1357
+ async assignAuditSkill(id, projectId, revisionId) {
1358
+ return this.postJson(`/api/audit-skills/${encodeURIComponent(id)}/projects/${encodeURIComponent(projectId)}`, { revisionId });
1359
+ }
1360
+ /** Unpin an audit skill from a project (future scans no longer use it). */
1361
+ async unassignAuditSkill(id, projectId) {
1362
+ return this.deleteJson(`/api/audit-skills/${encodeURIComponent(id)}/projects/${encodeURIComponent(projectId)}`);
1363
+ }
1364
+ /** Archive an audit skill (disables future bindings, preserves history). */
1365
+ async archiveAuditSkill(id) {
1366
+ await this.deleteJson(`/api/audit-skills/${encodeURIComponent(id)}`);
1367
+ }
1368
+ /**
1369
+ * List audit skills available to a specific project, along with current
1370
+ * assignments for that project. The project must belong to the caller's org.
1371
+ */
1372
+ async listAuditSkillsByProject(projectId) {
1373
+ return this.getJson(`/api/audit-skills/by-project/${encodeURIComponent(projectId)}`);
1374
+ }
1375
+ /**
1376
+ * Generic JSON DELETE helper with the same error mapping as getJson/postJson.
1377
+ * Used by `0 service disconnect` to remove scan schedules.
1378
+ */
1379
+ async deleteJson(path) {
1380
+ const url = `${this.host}${path}`;
1381
+ let res;
1382
+ try {
1383
+ res = await this.fetchImpl(url, {
1384
+ method: "DELETE",
1385
+ headers: { ...this.headers(), "Content-Type": "application/json" }
1386
+ });
1387
+ } catch (err) {
1388
+ const msg = err instanceof Error ? err.message : String(err);
1389
+ throw new CloudNetworkError(this.scrub(msg), path);
1390
+ }
1391
+ if (!res.ok) {
1392
+ let code;
1393
+ try {
1394
+ const parsed = await res.json();
1395
+ const raw = typeof parsed?.error === "object" ? parsed.error?.code : void 0;
1396
+ if (typeof raw === "string" && raw.length > 0)
1397
+ code = raw;
1398
+ } catch {
1399
+ }
1400
+ this.throwForStatus(res.status, path, code);
1401
+ }
1402
+ if (res.status === 204)
1403
+ return void 0;
1404
+ return await res.json();
1405
+ }
1406
+ /**
1407
+ * Generic JSON POST helper with the same error mapping as getJson.
1408
+ * Used by `0 connect` to enqueue scans and schedules.
1409
+ */
1410
+ async postJson(path, body) {
1411
+ const url = `${this.host}${path}`;
1412
+ let res;
1413
+ try {
1414
+ res = await this.fetchImpl(url, {
1415
+ method: "POST",
1416
+ headers: { ...this.headers(), "Content-Type": "application/json" },
1417
+ body: JSON.stringify(body)
1418
+ });
1419
+ } catch (err) {
1420
+ const msg = err instanceof Error ? err.message : String(err);
1421
+ throw new CloudNetworkError(this.scrub(msg), path);
1422
+ }
1423
+ if (!res.ok) {
1424
+ let code;
1425
+ try {
1426
+ const parsed = await res.json();
1427
+ const raw = typeof parsed?.error === "object" ? parsed.error?.code : void 0;
1428
+ if (typeof raw === "string" && raw.length > 0)
1429
+ code = raw;
1430
+ } catch {
1431
+ }
1432
+ this.throwForStatus(res.status, path, code);
1433
+ }
1434
+ return await res.json();
1435
+ }
1250
1436
  /**
1251
1437
  * Generic JSON GET helper. Public so future modules (scans, findings)
1252
1438
  * can reuse the same error mapping without duplicating it. Not exported
@@ -1283,7 +1469,7 @@ var CloudClient = class {
1283
1469
  throw new CloudUnauthorizedError(path);
1284
1470
  if (status === 403)
1285
1471
  throw new CloudForbiddenError(path);
1286
- throw new CloudError(`0sec-cloud request failed (HTTP ${status}${code ? ` ${code}` : ""}) on ${path}.`, status, path, code);
1472
+ throw new CloudError(`0-cloud request failed (HTTP ${status}${code ? ` ${code}` : ""}) on ${path}.`, status, path, code);
1287
1473
  }
1288
1474
  /**
1289
1475
  * Throw a typed error for non-2xx responses. Public so direct callers
@@ -1296,14 +1482,14 @@ var CloudClient = class {
1296
1482
  throw new CloudUnauthorizedError(path);
1297
1483
  if (res.status === 403)
1298
1484
  throw new CloudForbiddenError(path);
1299
- throw new CloudError(`0sec-cloud request failed (HTTP ${res.status}) on ${path}.`, res.status, path);
1485
+ throw new CloudError(`0-cloud request failed (HTTP ${res.status}) on ${path}.`, res.status, path);
1300
1486
  }
1301
1487
  // ── internals ──
1302
1488
  headers() {
1303
1489
  return {
1304
1490
  Authorization: `Bearer ${this.token}`,
1305
1491
  Accept: "application/json",
1306
- "User-Agent": `0sec-cli/${VERSION}`
1492
+ "User-Agent": `@0/cli/${VERSION}`
1307
1493
  };
1308
1494
  }
1309
1495
  /**
@@ -1325,6 +1511,54 @@ import { appendFileSync, existsSync, readFileSync as readFileSync2, renameSync,
1325
1511
  import { homedir } from "node:os";
1326
1512
  import { join as join2 } from "node:path";
1327
1513
 
1514
+ // packages/core/dist/runtime/hosted-request-queue.js
1515
+ var MAX_IN_FLIGHT = 4;
1516
+ var endpoints = /* @__PURE__ */ new Map();
1517
+ function acquireHostedRequestSlot(endpoint, token, signal) {
1518
+ signal?.throwIfAborted();
1519
+ let accounts = endpoints.get(endpoint);
1520
+ if (!accounts)
1521
+ endpoints.set(endpoint, accounts = /* @__PURE__ */ new Map());
1522
+ let queue = accounts.get(token);
1523
+ if (!queue)
1524
+ accounts.set(token, queue = { active: 0, waiting: [] });
1525
+ const accountQueues = accounts;
1526
+ const current = queue;
1527
+ return new Promise((resolve, reject) => {
1528
+ const abort = () => {
1529
+ const index = current.waiting.indexOf(waiter);
1530
+ if (index !== -1)
1531
+ current.waiting.splice(index, 1);
1532
+ reject(signal.reason);
1533
+ };
1534
+ const waiter = { grant: () => {
1535
+ signal?.removeEventListener("abort", abort);
1536
+ current.active++;
1537
+ let released = false;
1538
+ resolve(() => {
1539
+ if (released)
1540
+ return;
1541
+ released = true;
1542
+ current.active--;
1543
+ const next = current.waiting.shift();
1544
+ if (next)
1545
+ next.grant();
1546
+ else if (current.active === 0) {
1547
+ accountQueues.delete(token);
1548
+ if (accountQueues.size === 0)
1549
+ endpoints.delete(endpoint);
1550
+ }
1551
+ });
1552
+ } };
1553
+ if (current.active < MAX_IN_FLIGHT)
1554
+ waiter.grant();
1555
+ else {
1556
+ current.waiting.push(waiter);
1557
+ signal?.addEventListener("abort", abort, { once: true });
1558
+ }
1559
+ });
1560
+ }
1561
+
1328
1562
  // packages/core/dist/runtime/prompt-cache.js
1329
1563
  var MAX_CACHE_BREAKPOINTS = 4;
1330
1564
  var MESSAGE_CACHE_BREAKPOINTS = MAX_CACHE_BREAKPOINTS - 1;
@@ -1339,7 +1573,7 @@ function providerSupportsPromptCache(provider) {
1339
1573
  return readExtraCacheProviders().has(provider);
1340
1574
  }
1341
1575
  function readExtraCacheProviders() {
1342
- const raw = process.env["0SEC_PROMPT_CACHE_EXTRA_PROVIDERS"];
1576
+ const raw = process.env["ZERO_PROMPT_CACHE_EXTRA_PROVIDERS"];
1343
1577
  if (!raw)
1344
1578
  return /* @__PURE__ */ new Set();
1345
1579
  return new Set(raw.split(",").map((entry) => entry.trim().toLowerCase()).filter((entry) => entry.length > 0));
@@ -1408,7 +1642,7 @@ function isWireBlockArray(blocks) {
1408
1642
  }
1409
1643
  var azureRegionCache = /* @__PURE__ */ new Map();
1410
1644
  async function probeAzureRegion(baseUrl, apiKey, fetchImpl = fetch) {
1411
- const override = process.env["0SEC_REGION_OVERRIDE"];
1645
+ const override = process.env["ZERO_REGION_OVERRIDE"];
1412
1646
  if (override && override.trim().length > 0) {
1413
1647
  return override.trim();
1414
1648
  }
@@ -1470,7 +1704,7 @@ function prettyRegion(code) {
1470
1704
  };
1471
1705
  return map[code.toLowerCase()] ?? code;
1472
1706
  }
1473
- var PROVIDER_BANNER_KEY = /* @__PURE__ */ Symbol.for("0sec.core.loggedProviderStartup");
1707
+ var PROVIDER_BANNER_KEY = /* @__PURE__ */ Symbol.for("0.core.loggedProviderStartup");
1474
1708
  var loggedProviderStartup = (() => {
1475
1709
  const g = globalThis;
1476
1710
  if (!g[PROVIDER_BANNER_KEY])
@@ -1478,7 +1712,7 @@ var loggedProviderStartup = (() => {
1478
1712
  return g[PROVIDER_BANNER_KEY];
1479
1713
  })();
1480
1714
  function appendNativeTrace(record) {
1481
- const file = process.env["0SEC_TRACE_NATIVE_RESPONSES"];
1715
+ const file = process.env["ZERO_TRACE_NATIVE_RESPONSES"];
1482
1716
  if (!file)
1483
1717
  return;
1484
1718
  try {
@@ -1488,7 +1722,7 @@ function appendNativeTrace(record) {
1488
1722
  }
1489
1723
  }
1490
1724
  function shouldLogProviderStartup() {
1491
- return process.env["0SEC_SUPPRESS_PROVIDER_STARTUP_LOG"] !== "1";
1725
+ return process.env["ZERO_SUPPRESS_PROVIDER_STARTUP_LOG"] !== "1";
1492
1726
  }
1493
1727
  function isRetryableHttpStatus(status) {
1494
1728
  return status === 429 || status === 500 || status === 502 || status === 503 || status === 504;
@@ -1498,7 +1732,7 @@ var TRANSIENT_STREAM_ERROR_PATTERNS = [
1498
1732
  "response stream failed"
1499
1733
  ];
1500
1734
  function llmStreamMaxAttempts() {
1501
- const raw = process.env["0SEC_LLM_STREAM_MAX_ATTEMPTS"];
1735
+ const raw = process.env["ZERO_LLM_STREAM_MAX_ATTEMPTS"];
1502
1736
  if (raw == null || raw.trim() === "")
1503
1737
  return 3;
1504
1738
  const n = Number.parseInt(raw, 10);
@@ -1545,28 +1779,28 @@ function isRetryableTransportCode(code) {
1545
1779
  ].includes(code);
1546
1780
  }
1547
1781
  function llmMaxRetries() {
1548
- const raw = process.env["0SEC_LLM_MAX_RETRIES"];
1782
+ const raw = process.env["ZERO_LLM_MAX_RETRIES"];
1549
1783
  if (raw == null || raw.trim() === "")
1550
1784
  return 6;
1551
1785
  const n = Number.parseInt(raw, 10);
1552
1786
  return Number.isFinite(n) && n >= 0 ? n : 6;
1553
1787
  }
1554
1788
  function llmMaxRetryWaitMs() {
1555
- const raw = process.env["0SEC_LLM_MAX_RETRY_WAIT_MS"];
1789
+ const raw = process.env["ZERO_LLM_MAX_RETRY_WAIT_MS"];
1556
1790
  if (raw == null || raw.trim() === "")
1557
1791
  return 6e4;
1558
1792
  const n = Number.parseInt(raw, 10);
1559
1793
  return Number.isFinite(n) && n > 0 ? n : 6e4;
1560
1794
  }
1561
1795
  function llm429MaxRetries() {
1562
- const raw = process.env["0SEC_LLM_429_MAX_RETRIES"] ?? process.env["0SEC_LLM_MAX_RETRIES"];
1796
+ const raw = process.env["ZERO_LLM_429_MAX_RETRIES"] ?? process.env["ZERO_LLM_MAX_RETRIES"];
1563
1797
  if (raw == null || raw.trim() === "")
1564
1798
  return 12;
1565
1799
  const n = Number.parseInt(raw, 10);
1566
1800
  return Number.isFinite(n) && n >= 0 ? n : 12;
1567
1801
  }
1568
1802
  function llm429MaxRetryWaitMs() {
1569
- const raw = process.env["0SEC_LLM_429_MAX_RETRY_WAIT_MS"] ?? process.env["0SEC_LLM_MAX_RETRY_WAIT_MS"];
1803
+ const raw = process.env["ZERO_LLM_429_MAX_RETRY_WAIT_MS"] ?? process.env["ZERO_LLM_MAX_RETRY_WAIT_MS"];
1570
1804
  if (raw == null || raw.trim() === "")
1571
1805
  return 3e5;
1572
1806
  const n = Number.parseInt(raw, 10);
@@ -1677,12 +1911,19 @@ function parseUsageLimitReached(body) {
1677
1911
  return details;
1678
1912
  }
1679
1913
  function llmStreamIdleTimeoutMs() {
1680
- const raw = process.env["0SEC_LLM_STREAM_IDLE_TIMEOUT_MS"];
1914
+ const raw = process.env["ZERO_LLM_STREAM_IDLE_TIMEOUT_MS"];
1681
1915
  if (raw == null || raw.trim() === "")
1682
1916
  return 12e4;
1683
1917
  const n = Number.parseInt(raw, 10);
1684
1918
  return Number.isFinite(n) && n > 0 ? n : 12e4;
1685
1919
  }
1920
+ function llmStreamEventIdleTimeoutMs() {
1921
+ const raw = process.env["ZERO_LLM_STREAM_EVENT_IDLE_TIMEOUT_MS"];
1922
+ if (raw == null || raw.trim() === "")
1923
+ return 24e4;
1924
+ const n = Number.parseInt(raw, 10);
1925
+ return Number.isFinite(n) && n > 0 ? n : 24e4;
1926
+ }
1686
1927
  function parseRetryAfterMs(headerValue) {
1687
1928
  if (!headerValue)
1688
1929
  return void 0;
@@ -1827,7 +2068,7 @@ var AZURE_FOUNDRY_DEPLOYMENT_IDS = {
1827
2068
  "gpt-5.6-terra": true
1828
2069
  };
1829
2070
  function parseLlmFallbackChain(env2 = process.env) {
1830
- const raw = env2["0SEC_LLM_FALLBACK"];
2071
+ const raw = env2["ZERO_LLM_FALLBACK"];
1831
2072
  if (!raw || raw.trim().length === 0)
1832
2073
  return [];
1833
2074
  const entries = [];
@@ -1853,17 +2094,17 @@ function parseLlmFallbackChain(env2 = process.env) {
1853
2094
  continue;
1854
2095
  const colonIdx = trimmed.indexOf(":");
1855
2096
  if (colonIdx < 1 || colonIdx === trimmed.length - 1) {
1856
- diag.warn("fallback_chain_malformed_entry", `0SEC_LLM_FALLBACK: malformed entry "${trimmed}" (expected provider:model)`, { entry: trimmed, expected: "provider:model" });
2097
+ diag.warn("fallback_chain_malformed_entry", `ZERO_LLM_FALLBACK: malformed entry "${trimmed}" (expected provider:model)`, { entry: trimmed, expected: "provider:model" });
1857
2098
  continue;
1858
2099
  }
1859
2100
  const provider = trimmed.slice(0, colonIdx);
1860
2101
  const model = trimmed.slice(colonIdx + 1).trim();
1861
2102
  if (!VALID_PROVIDERS[provider]) {
1862
- diag.warn("fallback_chain_unknown_provider", `0SEC_LLM_FALLBACK: unknown provider "${provider}" in "${trimmed}"`, { entry: trimmed, provider });
2103
+ diag.warn("fallback_chain_unknown_provider", `ZERO_LLM_FALLBACK: unknown provider "${provider}" in "${trimmed}"`, { entry: trimmed, provider });
1863
2104
  continue;
1864
2105
  }
1865
2106
  if (!model) {
1866
- diag.warn("fallback_chain_empty_model", `0SEC_LLM_FALLBACK: empty model in "${trimmed}"`, { entry: trimmed, provider });
2107
+ diag.warn("fallback_chain_empty_model", `ZERO_LLM_FALLBACK: empty model in "${trimmed}"`, { entry: trimmed, provider });
1867
2108
  continue;
1868
2109
  }
1869
2110
  entries.push({ provider, model });
@@ -1906,7 +2147,7 @@ function resolveFailoverProvider(provider, model, env2 = process.env, apiKey) {
1906
2147
  return { apiKey: key, baseUrl: env2.ANTHROPIC_BASE_URL ?? "https://api.anthropic.com", wireApi: "chat_completions" };
1907
2148
  }
1908
2149
  case "chatgpt-codex": {
1909
- if (!env2["0SEC_CHATGPT_ACCESS_TOKEN"] && !env2["0SEC_CHATGPT_OAUTH_REFRESH_TOKEN"] && !readChatGptCodexAuthFile(env2))
2150
+ if (!env2["ZERO_CHATGPT_ACCESS_TOKEN"] && !env2["ZERO_CHATGPT_OAUTH_REFRESH_TOKEN"] && !readChatGptCodexAuthFile(env2))
1910
2151
  return void 0;
1911
2152
  return { apiKey: "", baseUrl: CODEX_API_ENDPOINT, wireApi: "responses" };
1912
2153
  }
@@ -1941,13 +2182,13 @@ function resolveFailoverProvider(provider, model, env2 = process.env, apiKey) {
1941
2182
  return { apiKey: key, baseUrl: env2.OPENCODE_BASE_URL ?? OPENCODE_DEFAULT_BASE_URL, wireApi: opencodeWireApiForModel(model) };
1942
2183
  }
1943
2184
  case "copilot": {
1944
- const key = apiKey ?? env2["0SEC_COPILOT_GITHUB_TOKEN"];
2185
+ const key = apiKey ?? env2["ZERO_COPILOT_GITHUB_TOKEN"];
1945
2186
  if (!key)
1946
2187
  return void 0;
1947
2188
  return { apiKey: key, baseUrl: env2.COPILOT_BASE_URL ?? COPILOT_API_BASE, wireApi: "chat_completions" };
1948
2189
  }
1949
2190
  case "google": {
1950
- if (!env2["0SEC_GEMINI_ACCESS_TOKEN"] && !env2["0SEC_GEMINI_OAUTH_REFRESH_TOKEN"])
2191
+ if (!env2["ZERO_GEMINI_ACCESS_TOKEN"] && !env2["ZERO_GEMINI_OAUTH_REFRESH_TOKEN"])
1951
2192
  return void 0;
1952
2193
  return { apiKey: "", baseUrl: CODE_ASSIST_ENDPOINT, wireApi: "google_generate_content" };
1953
2194
  }
@@ -1973,7 +2214,7 @@ function resolveFailoverProvider(provider, model, env2 = process.env, apiKey) {
1973
2214
  }
1974
2215
  var fallbackChainCache;
1975
2216
  function getFallbackChain(env2) {
1976
- const raw = env2["0SEC_LLM_FALLBACK"];
2217
+ const raw = env2["ZERO_LLM_FALLBACK"];
1977
2218
  if (!fallbackChainCache || fallbackChainCache.raw !== raw) {
1978
2219
  fallbackChainCache = { raw, entries: parseLlmFallbackChain(env2) };
1979
2220
  }
@@ -1983,7 +2224,7 @@ var ZAI_DEFAULT_BASE_URL = "https://api.z.ai/api/anthropic";
1983
2224
  var ZAI_DEFAULT_MODEL = "glm-5.3";
1984
2225
  var ZAI_DEFAULT_THINKING_BUDGET = 2048;
1985
2226
  function zaiThinkingBudget() {
1986
- const raw = process.env["0SEC_ZAI_THINKING_BUDGET"];
2227
+ const raw = process.env["ZERO_ZAI_THINKING_BUDGET"];
1987
2228
  if (raw == null || raw.trim().length === 0)
1988
2229
  return ZAI_DEFAULT_THINKING_BUDGET;
1989
2230
  const n = Number.parseInt(raw, 10);
@@ -1998,18 +2239,18 @@ var CODEX_OAUTH_ISSUER = "https://auth.openai.com";
1998
2239
  var CODEX_OAUTH_CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
1999
2240
  var CODEX_DEFAULT_MODEL = "gpt-5.5";
2000
2241
  var LOOP_SERVER_COMPACTION_TOKENS = 15e4;
2001
- var PROCESS_SESSION_ID = `0sec-${Math.random().toString(36).slice(2, 10)}-${Date.now().toString(36)}`;
2242
+ var PROCESS_SESSION_ID = `0-${Math.random().toString(36).slice(2, 10)}-${Date.now().toString(36)}`;
2002
2243
  var chatGptCodexAuthStates = /* @__PURE__ */ new Map();
2003
2244
  function codexAuthStateKey(state) {
2004
2245
  return JSON.stringify([state.authFilePath, state.accountId, state.refreshToken || state.accessToken]);
2005
2246
  }
2006
2247
  function readChatGptCodexEnv(env2 = process.env) {
2007
- const access = env2["0SEC_CHATGPT_ACCESS_TOKEN"];
2008
- const refresh = env2["0SEC_CHATGPT_OAUTH_REFRESH_TOKEN"];
2248
+ const access = env2["ZERO_CHATGPT_ACCESS_TOKEN"];
2249
+ const refresh = env2["ZERO_CHATGPT_OAUTH_REFRESH_TOKEN"];
2009
2250
  if ((!access || access.length === 0) && (!refresh || refresh.length === 0)) {
2010
2251
  return void 0;
2011
2252
  }
2012
- const accountId = env2["0SEC_CHATGPT_ACCOUNT_ID"];
2253
+ const accountId = env2["ZERO_CHATGPT_ACCOUNT_ID"];
2013
2254
  return {
2014
2255
  accessToken: access && access.length > 0 ? access : void 0,
2015
2256
  refreshToken: refresh && refresh.length > 0 ? refresh : void 0,
@@ -2017,7 +2258,7 @@ function readChatGptCodexEnv(env2 = process.env) {
2017
2258
  };
2018
2259
  }
2019
2260
  function resolveChatGptCodexAuthPath(env2 = process.env) {
2020
- return env2["0SEC_CHATGPT_AUTH_FILE"] ?? join2(env2.HOME ?? homedir(), ".codex", "auth.json");
2261
+ return env2["ZERO_CHATGPT_AUTH_FILE"] ?? join2(env2.HOME ?? homedir(), ".codex", "auth.json");
2021
2262
  }
2022
2263
  function persistChatGptCodexAuthFile(authPath, tokens, usedRefreshToken) {
2023
2264
  try {
@@ -2046,7 +2287,7 @@ function persistChatGptCodexAuthFile(authPath, tokens, usedRefreshToken) {
2046
2287
  `, { mode: 384 });
2047
2288
  renameSync(tmp, authPath);
2048
2289
  } catch (err) {
2049
- process.stderr.write(`[0sec] warning: could not persist rotated Codex refresh token to ${authPath}: ${err instanceof Error ? err.message : String(err)}
2290
+ process.stderr.write(`[0] warning: could not persist rotated Codex refresh token to ${authPath}: ${err instanceof Error ? err.message : String(err)}
2050
2291
  `);
2051
2292
  }
2052
2293
  }
@@ -2082,6 +2323,7 @@ function accessTokenExpiryMs(accessToken) {
2082
2323
  }
2083
2324
  async function refreshChatGptCodexAccessToken(refreshToken) {
2084
2325
  const res = await fetch(`${CODEX_OAUTH_ISSUER}/oauth/token`, {
2326
+ signal: AbortSignal.timeout(3e4),
2085
2327
  method: "POST",
2086
2328
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
2087
2329
  body: new URLSearchParams({
@@ -2133,12 +2375,15 @@ function extractChatGptAccountId(tokens) {
2133
2375
  }
2134
2376
  return void 0;
2135
2377
  }
2378
+ async function getChatGptCodexAccessToken(env2 = process.env) {
2379
+ return refreshChatGptCodexAuthState(resolveChatGptCodexAuthState(env2));
2380
+ }
2136
2381
  function resolveChatGptCodexAuthState(env2) {
2137
2382
  const fromEnvOnly = readChatGptCodexEnv(env2);
2138
2383
  const fromFile = fromEnvOnly ? void 0 : readChatGptCodexAuthFile(env2);
2139
2384
  const tokens = fromEnvOnly ?? fromFile;
2140
2385
  if (!tokens) {
2141
- throw new Error("ChatGPT Codex auth: neither 0SEC_CHATGPT_ACCESS_TOKEN nor 0SEC_CHATGPT_OAUTH_REFRESH_TOKEN is set. Run `codex login` and either forward the access token via worker-controller (preferred for multi-sandbox dispatch \u2014 avoids the OAuth refresh-token rotation race) or keep a valid ~/.codex/auth.json on this host.");
2386
+ throw new Error("ChatGPT Codex auth: neither ZERO_CHATGPT_ACCESS_TOKEN nor ZERO_CHATGPT_OAUTH_REFRESH_TOKEN is set. Run `codex login` and either forward the access token via worker-controller (preferred for multi-sandbox dispatch \u2014 avoids the OAuth refresh-token rotation race) or keep a valid ~/.codex/auth.json on this host.");
2142
2387
  }
2143
2388
  const identity = {
2144
2389
  refreshToken: tokens.refreshToken ?? "",
@@ -2198,8 +2443,8 @@ function geminiAuthStateKey(refreshToken, accessToken) {
2198
2443
  return JSON.stringify([refreshToken, refreshToken ? void 0 : accessToken]);
2199
2444
  }
2200
2445
  function readGeminiCodeAssistEnv(env2 = process.env) {
2201
- const access = env2["0SEC_GEMINI_ACCESS_TOKEN"];
2202
- const refresh = env2["0SEC_GEMINI_OAUTH_REFRESH_TOKEN"];
2446
+ const access = env2["ZERO_GEMINI_ACCESS_TOKEN"];
2447
+ const refresh = env2["ZERO_GEMINI_OAUTH_REFRESH_TOKEN"];
2203
2448
  if ((!access || access.length === 0) && (!refresh || refresh.length === 0))
2204
2449
  return void 0;
2205
2450
  return {
@@ -2210,7 +2455,7 @@ function readGeminiCodeAssistEnv(env2 = process.env) {
2210
2455
  function resolveGeminiCodeAssistAuthState(env2) {
2211
2456
  const tokens = readGeminiCodeAssistEnv(env2);
2212
2457
  if (!tokens) {
2213
- throw new Error("Google Gemini Code Assist auth: neither 0SEC_GEMINI_ACCESS_TOKEN nor 0SEC_GEMINI_OAUTH_REFRESH_TOKEN is set. Sign in with your Google account (0sec connect) or forward a fresh access token.");
2458
+ throw new Error("Google Gemini Code Assist auth: neither ZERO_GEMINI_ACCESS_TOKEN nor ZERO_GEMINI_OAUTH_REFRESH_TOKEN is set. Sign in with your Google account (0 connect) or forward a fresh access token.");
2214
2459
  }
2215
2460
  const key = geminiAuthStateKey(tokens.refreshToken ?? "", tokens.accessToken);
2216
2461
  const existing = geminiCodeAssistAuthStates.get(key);
@@ -2323,7 +2568,7 @@ async function resolveGeminiCodeAssistProject(state, env2, sleep = (ms) => new P
2323
2568
  return state.inflightProjectResolve;
2324
2569
  state.inflightProjectResolve = (async () => {
2325
2570
  try {
2326
- const override = firstNonEmptyEnv(env2, "GOOGLE_CLOUD_PROJECT", "0SEC_GEMINI_PROJECT");
2571
+ const override = firstNonEmptyEnv(env2, "GOOGLE_CLOUD_PROJECT", "ZERO_GEMINI_PROJECT");
2327
2572
  const accessToken = await refreshGeminiCodeAssistAuthState(state);
2328
2573
  let load;
2329
2574
  try {
@@ -2335,7 +2580,7 @@ async function resolveGeminiCodeAssistProject(state, env2, sleep = (ms) => new P
2335
2580
  if (err.securityPolicyViolated) {
2336
2581
  if (override)
2337
2582
  return override;
2338
- throw new Error("Google Gemini Code Assist: this account is behind a VPC Service Controls perimeter \u2014 set GOOGLE_CLOUD_PROJECT (or 0SEC_GEMINI_PROJECT).");
2583
+ throw new Error("Google Gemini Code Assist: this account is behind a VPC Service Controls perimeter \u2014 set GOOGLE_CLOUD_PROJECT (or ZERO_GEMINI_PROJECT).");
2339
2584
  }
2340
2585
  throw err;
2341
2586
  }
@@ -2447,13 +2692,13 @@ function providerForModel(model, env2) {
2447
2692
  return env2.OPENCODE_API_KEY ? "opencode" : void 0;
2448
2693
  }
2449
2694
  if (m.startsWith("copilot/")) {
2450
- return env2["0SEC_COPILOT_GITHUB_TOKEN"] ? "copilot" : void 0;
2695
+ return env2["ZERO_COPILOT_GITHUB_TOKEN"] ? "copilot" : void 0;
2451
2696
  }
2452
2697
  if (m.startsWith("gemini") || m.startsWith("google/")) {
2453
- return env2["0SEC_GEMINI_ACCESS_TOKEN"] || env2["0SEC_GEMINI_OAUTH_REFRESH_TOKEN"] ? "google" : void 0;
2698
+ return env2["ZERO_GEMINI_ACCESS_TOKEN"] || env2["ZERO_GEMINI_OAUTH_REFRESH_TOKEN"] ? "google" : void 0;
2454
2699
  }
2455
2700
  if (/^gpt-|^o[1-4](?:[-_]|$)/.test(m)) {
2456
- if (env2["0SEC_CHATGPT_ACCESS_TOKEN"] || env2["0SEC_CHATGPT_OAUTH_REFRESH_TOKEN"])
2701
+ if (env2["ZERO_CHATGPT_ACCESS_TOKEN"] || env2["ZERO_CHATGPT_OAUTH_REFRESH_TOKEN"])
2457
2702
  return "chatgpt-codex";
2458
2703
  if (env2.OPENAI_API_KEY)
2459
2704
  return "openai";
@@ -2489,21 +2734,21 @@ function detectProvider(configApiKey, preferredModel, env2, configProvider) {
2489
2734
  if (configProvider !== void 0 && !Object.hasOwn(DEFAULT_PROVIDER_MODELS, configProvider)) {
2490
2735
  throw new Error(`RuntimeConfig.provider is unsupported: ${configProvider}`);
2491
2736
  }
2492
- const selectedProviderRaw = configProvider ?? env2["0SEC_SELECTED_PROVIDER"]?.trim();
2493
- const forcedProviderRaw = env2["0SEC_FORCE_PROVIDER"]?.trim() || void 0;
2737
+ const selectedProviderRaw = configProvider ?? env2["ZERO_SELECTED_PROVIDER"]?.trim();
2738
+ const forcedProviderRaw = env2["ZERO_FORCE_PROVIDER"]?.trim() || void 0;
2494
2739
  if (selectedProviderRaw && forcedProviderRaw && selectedProviderRaw !== forcedProviderRaw) {
2495
- throw new Error(`${configProvider !== void 0 ? "RuntimeConfig.provider" : "0SEC_SELECTED_PROVIDER"} conflicts with 0SEC_FORCE_PROVIDER`);
2740
+ throw new Error(`${configProvider !== void 0 ? "RuntimeConfig.provider" : "ZERO_SELECTED_PROVIDER"} conflicts with ZERO_FORCE_PROVIDER`);
2496
2741
  }
2497
- const primaryModel = env2["0SEC_MODEL"]?.trim();
2742
+ const primaryModel = env2["ZERO_MODEL"]?.trim();
2498
2743
  const selectedProviderApplies = configProvider !== void 0 || !preferredModel || !primaryModel || preferredModel === primaryModel;
2499
2744
  const pinnedProviderRaw = forcedProviderRaw ?? (selectedProviderApplies ? selectedProviderRaw : void 0);
2500
2745
  if (pinnedProviderRaw) {
2501
- const source = pinnedProviderRaw === forcedProviderRaw ? "0SEC_FORCE_PROVIDER" : configProvider !== void 0 ? "RuntimeConfig.provider" : "0SEC_SELECTED_PROVIDER";
2746
+ const source = pinnedProviderRaw === forcedProviderRaw ? "ZERO_FORCE_PROVIDER" : configProvider !== void 0 ? "RuntimeConfig.provider" : "ZERO_SELECTED_PROVIDER";
2502
2747
  if (!Object.hasOwn(DEFAULT_PROVIDER_MODELS, pinnedProviderRaw)) {
2503
2748
  throw new Error(`${source} is unsupported: ${pinnedProviderRaw}`);
2504
2749
  }
2505
2750
  const provider = pinnedProviderRaw;
2506
- const model = preferredModel ?? env2["0SEC_MODEL"] ?? (configProvider !== void 0 || provider === "hosted" ? DEFAULT_PROVIDER_MODELS[provider] : void 0);
2751
+ const model = preferredModel ?? env2["ZERO_MODEL"] ?? (configProvider !== void 0 || provider === "hosted" ? DEFAULT_PROVIDER_MODELS[provider] : void 0);
2507
2752
  if (model === void 0 || model === "" && provider !== "hosted") {
2508
2753
  throw new Error(`${source} requires an explicit model`);
2509
2754
  }
@@ -2613,7 +2858,7 @@ function detectProvider(configApiKey, preferredModel, env2, configProvider) {
2613
2858
  case "copilot":
2614
2859
  return {
2615
2860
  provider: "copilot",
2616
- apiKey: env2["0SEC_COPILOT_GITHUB_TOKEN"],
2861
+ apiKey: env2["ZERO_COPILOT_GITHUB_TOKEN"],
2617
2862
  baseUrl: env2.COPILOT_BASE_URL ?? COPILOT_API_BASE,
2618
2863
  defaultModel: preferredModel ?? COPILOT_DEFAULT_MODEL,
2619
2864
  wireApi: "chat_completions"
@@ -2631,7 +2876,7 @@ function detectProvider(configApiKey, preferredModel, env2, configProvider) {
2631
2876
  provider: "chatgpt-codex",
2632
2877
  apiKey: "",
2633
2878
  baseUrl: CODEX_API_ENDPOINT,
2634
- defaultModel: env2["0SEC_MODEL"] ?? CODEX_DEFAULT_MODEL,
2879
+ defaultModel: env2["ZERO_MODEL"] ?? CODEX_DEFAULT_MODEL,
2635
2880
  wireApi: "responses"
2636
2881
  };
2637
2882
  case "anthropic":
@@ -2661,8 +2906,23 @@ function detectProvider(configApiKey, preferredModel, env2, configProvider) {
2661
2906
  default:
2662
2907
  break;
2663
2908
  }
2664
- const chatGptAccess = env2["0SEC_CHATGPT_ACCESS_TOKEN"];
2665
- const chatGptRefresh = env2["0SEC_CHATGPT_OAUTH_REFRESH_TOKEN"];
2909
+ try {
2910
+ const hostedCreds = loadCloudCredentials({
2911
+ env: env2,
2912
+ warn: () => {
2913
+ }
2914
+ });
2915
+ return {
2916
+ provider: "hosted",
2917
+ apiKey: hostedCreds.token,
2918
+ baseUrl: `${hostedCreds.host}/api/inference/v1`,
2919
+ defaultModel: "",
2920
+ wireApi: "chat_completions"
2921
+ };
2922
+ } catch {
2923
+ }
2924
+ const chatGptAccess = env2["ZERO_CHATGPT_ACCESS_TOKEN"];
2925
+ const chatGptRefresh = env2["ZERO_CHATGPT_OAUTH_REFRESH_TOKEN"];
2666
2926
  const chatGptAuthFile = !chatGptAccess && !chatGptRefresh ? readChatGptCodexAuthFile(env2) : void 0;
2667
2927
  if (chatGptAccess && chatGptAccess.length > 0 || chatGptRefresh && chatGptRefresh.length > 0 || !!chatGptAuthFile) {
2668
2928
  return {
@@ -2675,7 +2935,7 @@ function detectProvider(configApiKey, preferredModel, env2, configProvider) {
2675
2935
  // baseUrl is informational only — the runtime hardcodes
2676
2936
  // CODEX_API_ENDPOINT for this provider.
2677
2937
  baseUrl: CODEX_API_ENDPOINT,
2678
- defaultModel: env2["0SEC_MODEL"] ?? CODEX_DEFAULT_MODEL,
2938
+ defaultModel: env2["ZERO_MODEL"] ?? CODEX_DEFAULT_MODEL,
2679
2939
  wireApi: "responses"
2680
2940
  };
2681
2941
  }
@@ -2771,7 +3031,7 @@ function detectProvider(configApiKey, preferredModel, env2, configProvider) {
2771
3031
  wireApi: opencodeWireApiForModel(preferredModel)
2772
3032
  };
2773
3033
  }
2774
- const copilotToken = env2["0SEC_COPILOT_GITHUB_TOKEN"];
3034
+ const copilotToken = env2["ZERO_COPILOT_GITHUB_TOKEN"];
2775
3035
  if (copilotToken) {
2776
3036
  return {
2777
3037
  provider: "copilot",
@@ -2781,14 +3041,14 @@ function detectProvider(configApiKey, preferredModel, env2, configProvider) {
2781
3041
  wireApi: "chat_completions"
2782
3042
  };
2783
3043
  }
2784
- const geminiAccess = env2["0SEC_GEMINI_ACCESS_TOKEN"];
2785
- const geminiRefresh = env2["0SEC_GEMINI_OAUTH_REFRESH_TOKEN"];
3044
+ const geminiAccess = env2["ZERO_GEMINI_ACCESS_TOKEN"];
3045
+ const geminiRefresh = env2["ZERO_GEMINI_OAUTH_REFRESH_TOKEN"];
2786
3046
  if (geminiAccess && geminiAccess.length > 0 || geminiRefresh && geminiRefresh.length > 0) {
2787
3047
  return {
2788
3048
  provider: "google",
2789
3049
  apiKey: "",
2790
3050
  baseUrl: CODE_ASSIST_ENDPOINT,
2791
- defaultModel: env2["0SEC_MODEL"] ?? GEMINI_DEFAULT_MODEL,
3051
+ defaultModel: env2["ZERO_MODEL"] ?? GEMINI_DEFAULT_MODEL,
2792
3052
  wireApi: "google_generate_content"
2793
3053
  };
2794
3054
  }
@@ -2802,21 +3062,6 @@ function detectProvider(configApiKey, preferredModel, env2, configProvider) {
2802
3062
  wireApi: "chat_completions"
2803
3063
  };
2804
3064
  }
2805
- try {
2806
- const hostedCreds = loadCloudCredentials({
2807
- env: env2,
2808
- warn: () => {
2809
- }
2810
- });
2811
- return {
2812
- provider: "hosted",
2813
- apiKey: hostedCreds.token,
2814
- baseUrl: `${hostedCreds.host}/api/inference/v1`,
2815
- defaultModel: "",
2816
- wireApi: "chat_completions"
2817
- };
2818
- } catch {
2819
- }
2820
3065
  return {
2821
3066
  provider: "anthropic",
2822
3067
  apiKey: "",
@@ -2843,7 +3088,7 @@ var LlmApiRuntime = class _LlmApiRuntime {
2843
3088
  reasoningEffort;
2844
3089
  azureConfig;
2845
3090
  serverCompactionTokens;
2846
- /** Ordered fallback chain (0SEC_LLM_FALLBACK). Empty = no failover. */
3091
+ /** Ordered fallback chain (ZERO_LLM_FALLBACK). Empty = no failover. */
2847
3092
  fallbackChain;
2848
3093
  /** Index into fallbackChain — which entry to try next. */
2849
3094
  fallbackIndex;
@@ -2919,7 +3164,7 @@ var LlmApiRuntime = class _LlmApiRuntime {
2919
3164
  credentials: resolveFailoverProvider(entry.provider, entry.model, this.env)
2920
3165
  }));
2921
3166
  this.fallbackIndex = 0;
2922
- const detected = detectProvider(config.apiKey, config.model ?? this.env["0SEC_MODEL"], this.env, config.provider);
3167
+ const detected = detectProvider(config.apiKey, config.model ?? this.env["ZERO_MODEL"], this.env, config.provider);
2923
3168
  this.provider = detected.provider;
2924
3169
  this.apiKey = detected.apiKey;
2925
3170
  this.baseUrl = detected.baseUrl;
@@ -2936,9 +3181,9 @@ var LlmApiRuntime = class _LlmApiRuntime {
2936
3181
  this.geminiAuthState = resolveGeminiCodeAssistAuthState(this.env);
2937
3182
  }
2938
3183
  }
2939
- this.reasoningEffort = this.env["0SEC_REASONING_EFFORT"] ?? detected.reasoningEffort;
3184
+ this.reasoningEffort = this.env["ZERO_REASONING_EFFORT"] ?? detected.reasoningEffort;
2940
3185
  this.serverCompactionTokens = config.serverCompactionTokens !== void 0 ? Math.max(1e3, config.serverCompactionTokens) : void 0;
2941
- const requestedModel = config.model ?? this.env["0SEC_MODEL"];
3186
+ const requestedModel = config.model ?? this.env["ZERO_MODEL"];
2942
3187
  if (requestedModel === "free" && this.provider === "openrouter") {
2943
3188
  this.model = FREE_OPENROUTER_MODEL;
2944
3189
  } else {
@@ -2951,11 +3196,50 @@ var LlmApiRuntime = class _LlmApiRuntime {
2951
3196
  this.model = copilotModelId(this.model);
2952
3197
  }
2953
3198
  this.applyModelWireApi();
2954
- if (this.apiKey && !this.env["0SEC_SKIP_PROVIDER_BANNER"]) {
3199
+ if (this.apiKey && !this.env["ZERO_SKIP_PROVIDER_BANNER"]) {
2955
3200
  void logProviderStartup(this.provider, this.providerLabel, this.baseUrl, this.model, this.wireApi, this.apiKey).catch(() => {
2956
3201
  });
2957
3202
  }
2958
3203
  }
3204
+ /** Discover models using this runtime's captured account, including after a separate login changes. */
3205
+ async codexModelCatalog(signal) {
3206
+ const state = this.codexAuthState;
3207
+ if (this.provider !== "chatgpt-codex" || !state)
3208
+ throw new Error("No active Codex subscription");
3209
+ const { loadCodexModelCatalog } = await import("./codex-models-TYYV4JNN.js");
3210
+ return loadCodexModelCatalog({ signal, resolveCredentials: () => refreshChatGptCodexAuthState(state) });
3211
+ }
3212
+ /** Check account admission and resolve the service model without inference.
3213
+ * Each explicit check refreshes admission; failed discovery is never cached.
3214
+ */
3215
+ async prepare() {
3216
+ while (this.provider === "hosted") {
3217
+ const config = this.config;
3218
+ const client = new CloudClient({
3219
+ host: this.baseUrl.replace(/\/api\/inference\/v1$/, ""),
3220
+ token: this.apiKey
3221
+ });
3222
+ try {
3223
+ const account = await client.getInferenceAccount();
3224
+ if (this.config !== config)
3225
+ continue;
3226
+ if (!account) {
3227
+ throw new CloudError("0cloud account availability could not be read. Check again or review your account in /connect.", void 0, "/api/inference/account", "unsupported_account_data");
3228
+ }
3229
+ if (!account.admission.eligible) {
3230
+ const reason = account.admission.reason ?? account.reason ?? "account_restricted";
3231
+ throw new CloudError(account.state === "unavailable" ? `0cloud account availability could not be checked (${reason}). Check again or review your account in /connect.` : `0cloud account access is restricted (${reason}). Review your account in /connect or contact your organization owner.`, void 0, "/api/inference/account", reason);
3232
+ }
3233
+ await this.ensureHostedModel();
3234
+ if (this.config === config)
3235
+ return;
3236
+ } catch (error) {
3237
+ if (this.config !== config)
3238
+ continue;
3239
+ throw error;
3240
+ }
3241
+ }
3242
+ }
2959
3243
  /**
2960
3244
  * Mutate the live selection in place so the NEXT turn (the engine reads
2961
3245
  * `config.runtime` per turn) and the NEXT `forkForSubagent` pick up the new
@@ -2964,11 +3248,12 @@ var LlmApiRuntime = class _LlmApiRuntime {
2964
3248
  */
2965
3249
  reconfigure(sel) {
2966
3250
  const providerChanged = sel.provider !== void 0 && sel.provider !== this.provider;
2967
- if (providerChanged) {
3251
+ if (providerChanged || this.provider === "hosted" && (sel.env !== void 0 || sel.provider === "hosted")) {
2968
3252
  const merged = {
2969
3253
  ...this.config,
3254
+ ...providerChanged && sel.provider === "hosted" && sel.model === void 0 ? { model: "" } : {},
2970
3255
  apiKey: void 0,
2971
- provider: sel.provider,
3256
+ provider: sel.provider ?? this.provider,
2972
3257
  ...sel.model !== void 0 ? { model: sel.model } : {},
2973
3258
  ...sel.agentModels !== void 0 ? { agentModels: sel.agentModels } : {},
2974
3259
  ...sel.singleModel !== void 0 ? { singleModel: sel.singleModel } : {},
@@ -2998,6 +3283,8 @@ var LlmApiRuntime = class _LlmApiRuntime {
2998
3283
  this.wireApi = openAICompatibleWireApi(this.env, "AZURE_OPENAI_WIRE_API", this.azureConfig.wireApi);
2999
3284
  this.applyModelWireApi();
3000
3285
  this.reasoningEffort = void 0;
3286
+ this.hostedCatalogPromise = null;
3287
+ this.hostedMaxOutputTokens = void 0;
3001
3288
  }
3002
3289
  }
3003
3290
  }
@@ -3082,25 +3369,50 @@ var LlmApiRuntime = class _LlmApiRuntime {
3082
3369
  }
3083
3370
  /** The server catalog is authoritative even when a model was selected explicitly. */
3084
3371
  async ensureHostedModel() {
3085
- if (this.provider !== "hosted")
3086
- return;
3087
- if (!this.hostedCatalogPromise) {
3088
- this.hostedCatalogPromise = (async () => {
3372
+ while (this.provider === "hosted") {
3373
+ let pending = this.hostedCatalogPromise;
3374
+ if (!pending) {
3375
+ const requestedModel = this.model;
3089
3376
  const client = new CloudClient({
3090
3377
  host: this.baseUrl.replace(/\/api\/inference\/v1$/, ""),
3091
3378
  token: this.apiKey
3092
3379
  });
3093
- const catalog = await client.getInferenceModels();
3094
- const selected = this.model ? catalog.data.find((model) => model.id === this.model) : catalog.data[0];
3095
- if (!selected) {
3096
- throw new Error(this.model ? `Hosted model "${this.model}" is unavailable. Run \`0sec models\` for available models.` : "No hosted models are available. Run `0sec models` to check service availability.");
3097
- }
3098
- this.model = selected.id;
3099
- this.wireApi = selected.wire_api;
3100
- this.hostedMaxOutputTokens = selected.max_output_tokens;
3101
- })();
3380
+ const request = client.getInferenceModels().then((catalog) => {
3381
+ if (this.hostedCatalogPromise !== request)
3382
+ return;
3383
+ const selected = requestedModel ? catalog.data.find((model) => model.id === requestedModel) : catalog.data[0];
3384
+ if (!selected) {
3385
+ throw new Error(requestedModel ? `Hosted model "${requestedModel}" is unavailable. Run \`0 models\` for available models.` : "No hosted models are available. Run `0 models` to check service availability.");
3386
+ }
3387
+ this.model = selected.id;
3388
+ this.wireApi = selected.wire_api;
3389
+ this.hostedMaxOutputTokens = selected.max_output_tokens;
3390
+ });
3391
+ this.hostedCatalogPromise = pending = request;
3392
+ }
3393
+ try {
3394
+ await pending;
3395
+ } catch (error) {
3396
+ if (this.hostedCatalogPromise !== pending)
3397
+ continue;
3398
+ this.hostedCatalogPromise = null;
3399
+ throw error;
3400
+ }
3401
+ if (this.hostedCatalogPromise === pending)
3402
+ return;
3403
+ }
3404
+ }
3405
+ /** All audits and nested workers on this hosted credential share admission. */
3406
+ async acquireHostedSlot(signal) {
3407
+ while (this.provider === "hosted") {
3408
+ const config = this.config;
3409
+ const release = await acquireHostedRequestSlot(this.baseUrl, this.apiKey, signal);
3410
+ if (this.config === config)
3411
+ return release;
3412
+ release();
3413
+ await this.ensureHostedModel();
3102
3414
  }
3103
- await this.hostedCatalogPromise;
3415
+ return void 0;
3104
3416
  }
3105
3417
  /**
3106
3418
  * A hard dollar ceiling needs a provider-enforced bound on the next response.
@@ -3178,8 +3490,8 @@ var LlmApiRuntime = class _LlmApiRuntime {
3178
3490
  if (this.provider === "chatgpt-codex") {
3179
3491
  return {
3180
3492
  "Content-Type": "application/json",
3181
- originator: "0sec",
3182
- "User-Agent": `0sec/${VERSION}`
3493
+ originator: "0",
3494
+ "User-Agent": `0/${VERSION}`
3183
3495
  };
3184
3496
  }
3185
3497
  if (this.isGeminiCodeAssist) {
@@ -3211,8 +3523,8 @@ var LlmApiRuntime = class _LlmApiRuntime {
3211
3523
  headers["Authorization"] = `Bearer ${this.apiKey}`;
3212
3524
  }
3213
3525
  if (this.provider === "openrouter") {
3214
- headers["HTTP-Referer"] = "https://0sec.ai";
3215
- headers["X-Title"] = "0sec Security Scanner";
3526
+ headers["HTTP-Referer"] = "https://0.security";
3527
+ headers["X-Title"] = "0 Security Scanner";
3216
3528
  }
3217
3529
  return headers;
3218
3530
  }
@@ -3236,7 +3548,7 @@ var LlmApiRuntime = class _LlmApiRuntime {
3236
3548
  * happens.
3237
3549
  *
3238
3550
  * session_id is process-stable (PROCESS_SESSION_ID, randomised
3239
- * once at module load). A 0sec-cli invocation = one scan = one
3551
+ * once at module load). A @0/cli invocation = one scan = one
3240
3552
  * session, so the process-lifetime constant is the right
3241
3553
  * granularity. If we ever want per-scan ids inside a long-lived
3242
3554
  * controller process, add a setter on the runtime; for now this
@@ -3390,7 +3702,7 @@ var LlmApiRuntime = class _LlmApiRuntime {
3390
3702
  /**
3391
3703
  * Per-turn prompt-cache accounting line, so a run can be shown to actually
3392
3704
  * be hitting cache rather than assumed to be. Off unless
3393
- * `0SEC_DEBUG_PROMPT_CACHE` is set — this fires once per agent turn, and an
3705
+ * `ZERO_DEBUG_PROMPT_CACHE` is set — this fires once per agent turn, and an
3394
3706
  * unconditional line would interleave with the TUI on every scan.
3395
3707
  *
3396
3708
  * The same numbers reach the cloud without this flag: `cachedInputTokens`
@@ -3398,7 +3710,7 @@ var LlmApiRuntime = class _LlmApiRuntime {
3398
3710
  * is the durable, queryable proof. This is the local fast path.
3399
3711
  */
3400
3712
  logCacheUsage(usage) {
3401
- if (!usage || !process.env["0SEC_DEBUG_PROMPT_CACHE"])
3713
+ if (!usage || !process.env["ZERO_DEBUG_PROMPT_CACHE"])
3402
3714
  return;
3403
3715
  const read = usage.cachedInputTokens ?? 0;
3404
3716
  const write = usage.cacheWriteTokens ?? 0;
@@ -3442,11 +3754,11 @@ var LlmApiRuntime = class _LlmApiRuntime {
3442
3754
  case "google":
3443
3755
  return "Google Gemini (Code Assist)";
3444
3756
  case "hosted":
3445
- return "0sec Cloud";
3757
+ return "0.security Cloud";
3446
3758
  }
3447
3759
  }
3448
3760
  noKeyError() {
3449
- return "No provider credential found. Set one of:\n env 0SEC_CHATGPT_OAUTH_REFRESH_TOKEN=... 0sec <command> (ChatGPT Codex subscription auth)\n export OPENROUTER_API_KEY=sk-or-... (OpenRouter \u2014 many models, one key)\n export DEEPSEEK_API_KEY=... (DeepSeek \u2014 direct Flash 0731 inference)\n export ANTHROPIC_API_KEY=sk-ant-... (Anthropic \u2014 direct Claude access)\n export AZURE_OPENAI_API_KEY=... (Azure OpenAI \u2014 reuse your Codex Azure provider)\n export OPENAI_API_KEY=sk-... (OpenAI \u2014 direct GPT access)\n export Z_AI_API_KEY=... (Z.ai GLM \u2014 flat-rate Coding Plan, Anthropic-compatible)\n export KIMI_API_KEY=... (Moonshot Kimi K3 \u2014 flat-rate coding, Anthropic-compatible)\n export QWEN_API_KEY=... (Alibaba Qwen \u2014 Token Plan sub, OpenAI-compatible)\n export XAI_API_KEY=... (xAI Grok \u2014 OpenAI-compatible)\n export OPENCODE_API_KEY=... (OpenCode Zen \u2014 multi-wire gateway)\n export 0SEC_COPILOT_GITHUB_TOKEN=... (GitHub Copilot \u2014 device-code OAuth token)\n Run `0sec login` (0sec hosted inference)";
3761
+ return "No provider credential found. Set one of:\n env ZERO_CHATGPT_OAUTH_REFRESH_TOKEN=... 0 <command> (ChatGPT Codex subscription auth)\n export OPENROUTER_API_KEY=sk-or-... (OpenRouter \u2014 many models, one key)\n export DEEPSEEK_API_KEY=... (DeepSeek \u2014 direct Flash 0731 inference)\n export ANTHROPIC_API_KEY=sk-ant-... (Anthropic \u2014 direct Claude access)\n export AZURE_OPENAI_API_KEY=... (Azure OpenAI \u2014 reuse your Codex Azure provider)\n export OPENAI_API_KEY=sk-... (OpenAI \u2014 direct GPT access)\n export Z_AI_API_KEY=... (Z.ai GLM \u2014 flat-rate Coding Plan, Anthropic-compatible)\n export KIMI_API_KEY=... (Moonshot Kimi K3 \u2014 flat-rate coding, Anthropic-compatible)\n export QWEN_API_KEY=... (Alibaba Qwen \u2014 Token Plan sub, OpenAI-compatible)\n export XAI_API_KEY=... (xAI Grok \u2014 OpenAI-compatible)\n export OPENCODE_API_KEY=... (OpenCode Zen \u2014 multi-wire gateway)\n export ZERO_COPILOT_GITHUB_TOKEN=... (GitHub Copilot \u2014 device-code OAuth token)\n Run `0 login` (0 hosted inference)";
3450
3762
  }
3451
3763
  getConfigurationDiagnostics() {
3452
3764
  if (!this.apiKey && this.provider !== "chatgpt-codex") {
@@ -3466,7 +3778,7 @@ var LlmApiRuntime = class _LlmApiRuntime {
3466
3778
  };
3467
3779
  }
3468
3780
  const hasConfiguredBaseUrl = !!(this.env.AZURE_OPENAI_BASE_URL || this.env.OPENAI_BASE_URL || this.azureConfig.baseUrl);
3469
- const hasConfiguredModel = !!(this.config.model || this.env["0SEC_MODEL"] || this.env.AZURE_OPENAI_MODEL || this.azureConfig.model);
3781
+ const hasConfiguredModel = !!(this.config.model || this.env["ZERO_MODEL"] || this.env.AZURE_OPENAI_MODEL || this.azureConfig.model);
3470
3782
  const missing = [];
3471
3783
  if (!hasConfiguredBaseUrl) {
3472
3784
  missing.push("AZURE_OPENAI_BASE_URL (or [model_providers.azure].base_url in ~/.codex/config.toml)");
@@ -3482,7 +3794,7 @@ var LlmApiRuntime = class _LlmApiRuntime {
3482
3794
  reason: "invalid_config",
3483
3795
  fatalError: `Azure OpenAI runtime is selected, but the configuration is incomplete.
3484
3796
  Missing: ${missing.join("; ")}
3485
- 0sec will not guess Azure defaults because that can silently route to the wrong endpoint or deployment.`
3797
+ 0 will not guess Azure defaults because that can silently route to the wrong endpoint or deployment.`
3486
3798
  };
3487
3799
  }
3488
3800
  return {
@@ -3500,15 +3812,15 @@ Missing: ${missing.join("; ")}
3500
3812
  *
3501
3813
  * Two 429 classes are handled differently:
3502
3814
  * - per-minute rate limit → retry with the wider 429 budget
3503
- * (0SEC_LLM_429_MAX_RETRIES attempts / 0SEC_LLM_429_MAX_RETRY_WAIT_MS
3815
+ * (ZERO_LLM_429_MAX_RETRIES attempts / ZERO_LLM_429_MAX_RETRY_WAIT_MS
3504
3816
  * cumulative, defaults 12 / 5min) since the limiter resets every ~60s;
3505
3817
  * `Retry-After` / `retry-after-ms` headers are honored up to a 120s cap.
3506
3818
  * - plan-quota exhaustion (`usage_limit_reached`, resets in hours/days) →
3507
- * skips retries and immediately advances `0SEC_LLM_FALLBACK`; if no
3819
+ * skips retries and immediately advances `ZERO_LLM_FALLBACK`; if no
3508
3820
  * configured fallback has credentials, it throws QuotaExhaustedError.
3509
3821
  *
3510
3822
  * Other retryable statuses (transient 5xx) keep the generic budget:
3511
- * 0SEC_LLM_MAX_RETRIES (attempts) and 0SEC_LLM_MAX_RETRY_WAIT_MS
3823
+ * ZERO_LLM_MAX_RETRIES (attempts) and ZERO_LLM_MAX_RETRY_WAIT_MS
3512
3824
  * (cumulative backoff). On exhaustion it returns the last still-failing
3513
3825
  * Response with its body intact, so the caller's existing `!res.ok` branch
3514
3826
  * surfaces the clear "API error <status>" message — a rate-limit never
@@ -3519,7 +3831,7 @@ Missing: ${missing.join("; ")}
3519
3831
  * is fixed across attempts.
3520
3832
  */
3521
3833
  /**
3522
- * Try the next fallback provider in the chain (0SEC_LLM_FALLBACK).
3834
+ * Try the next fallback provider in the chain (ZERO_LLM_FALLBACK).
3523
3835
  * Updates `this.provider`, `this.model`, `this.apiKey`, `this.baseUrl`,
3524
3836
  * `this.wireApi` to match the next valid provider. Returns `true` when a
3525
3837
  * valid next provider was found and switched to, `false` when the chain is
@@ -3531,7 +3843,7 @@ Missing: ${missing.join("; ")}
3531
3843
  this.fallbackIndex++;
3532
3844
  const cfg = entry.credentials;
3533
3845
  if (!cfg) {
3534
- diag.warn("failover_provider_skipped", `0SEC_LLM_FALLBACK: skipping ${entry.provider} (auth env missing)`, { provider: entry.provider, model: entry.model, cause: "auth-env-missing" });
3846
+ diag.warn("failover_provider_skipped", `ZERO_LLM_FALLBACK: skipping ${entry.provider} (auth env missing)`, { provider: entry.provider, model: entry.model, cause: "auth-env-missing" });
3535
3847
  continue;
3536
3848
  }
3537
3849
  this.provider = entry.provider;
@@ -3577,7 +3889,7 @@ Missing: ${missing.join("; ")}
3577
3889
  } catch (error) {
3578
3890
  abort?.throwIfCancelled();
3579
3891
  if (this.provider === "hosted") {
3580
- throw new Error("0sec hosted request outcome is unknown. Automatic replay is disabled; check your inference usage before retrying.", { cause: error });
3892
+ throw new Error("0 hosted request outcome is unknown. Automatic replay is disabled; check your inference usage before retrying.", { cause: error });
3581
3893
  }
3582
3894
  const cause = error instanceof Error ? error.cause : void 0;
3583
3895
  const causeCode = cause && typeof cause === "object" && "code" in cause && typeof cause.code === "string" ? cause.code : "unknown";
@@ -3605,12 +3917,12 @@ Missing: ${missing.join("; ")}
3605
3917
  if (res.ok || !isRetryableHttpStatus(res.status)) {
3606
3918
  return res;
3607
3919
  }
3608
- if (this.provider === "hosted" && res.status === 429 && res.headers.get("x-0sec-retry-safe") !== "1") {
3920
+ if (this.provider === "hosted" && res.status === 429 && res.headers.get("x-0-retry-safe") !== "1") {
3609
3921
  return res;
3610
3922
  }
3611
3923
  if (this.provider === "hosted" && res.status >= 500) {
3612
3924
  await res.body?.cancel();
3613
- throw new Error(`0sec hosted request returned HTTP ${res.status}; its outcome may be unknown. Automatic replay is disabled; check your inference usage before retrying.`);
3925
+ throw new Error(`0 hosted request returned HTTP ${res.status}; its outcome may be unknown. Automatic replay is disabled; check your inference usage before retrying.`);
3614
3926
  }
3615
3927
  abort?.throwIfCancelled();
3616
3928
  const is429 = res.status === 429;
@@ -3714,11 +4026,15 @@ Missing: ${missing.join("; ")}
3714
4026
  };
3715
4027
  }
3716
4028
  const systemPrompt = context?.systemPrompt ?? "";
4029
+ let releaseHostedSlot = this.provider === "hosted" ? await this.acquireHostedSlot() : void 0;
3717
4030
  const controller = new AbortController();
3718
4031
  const timer = setTimeout(() => controller.abort(), this.config.timeout || 12e4);
3719
4032
  try {
3720
4033
  let res;
3721
4034
  do {
4035
+ if (this.provider === "hosted" && !releaseHostedSlot) {
4036
+ releaseHostedSlot = await this.acquireHostedSlot(controller.signal);
4037
+ }
3722
4038
  if (this.isOpenAICompat && this.wireApi === "chat_completions") {
3723
4039
  const messages = [];
3724
4040
  if (systemPrompt) {
@@ -3846,6 +4162,8 @@ Missing: ${missing.join("; ")}
3846
4162
  durationMs: Date.now() - start,
3847
4163
  error: timedOut ? `${this.providerLabel} API request timed out` : `${this.providerLabel} API error: ${msg}`
3848
4164
  };
4165
+ } finally {
4166
+ releaseHostedSlot?.();
3849
4167
  }
3850
4168
  }
3851
4169
  // ── Native Runtime interface (structured messages + tool_use) ──
@@ -3899,12 +4217,25 @@ Missing: ${missing.join("; ")}
3899
4217
  }
3900
4218
  if (signal?.aborted)
3901
4219
  return this.cancelledResult(start);
4220
+ let releaseHostedSlot;
4221
+ if (this.provider === "hosted") {
4222
+ try {
4223
+ releaseHostedSlot = await this.acquireHostedSlot(signal);
4224
+ } catch (error) {
4225
+ if (signal?.aborted)
4226
+ return this.cancelledResult(start);
4227
+ throw error;
4228
+ }
4229
+ }
3902
4230
  const controller = new AbortController();
3903
4231
  const timer = setTimeout(() => controller.abort(), this.config.timeout || 12e4);
3904
4232
  const call = composeCallAbort(controller.signal, signal);
3905
4233
  try {
3906
4234
  let res;
3907
4235
  do {
4236
+ if (this.provider === "hosted" && !releaseHostedSlot) {
4237
+ releaseHostedSlot = await this.acquireHostedSlot(call.signal);
4238
+ }
3908
4239
  if (this.isOpenAICompat && this.wireApi === "chat_completions") {
3909
4240
  const chatMessages = [];
3910
4241
  chatMessages.push({ role: "system", content: system });
@@ -4095,6 +4426,7 @@ Missing: ${missing.join("; ")}
4095
4426
  }
4096
4427
  const streamed = await this.consumeResponsesStream(res, start, callbacks, {
4097
4428
  idleTimeoutMs: llmStreamIdleTimeoutMs(),
4429
+ eventIdleTimeoutMs: llmStreamEventIdleTimeoutMs(),
4098
4430
  abort: call
4099
4431
  });
4100
4432
  clearTimeout(timer);
@@ -4402,6 +4734,7 @@ Missing: ${missing.join("; ")}
4402
4734
  error: timedOut ? `${this.providerLabel} API request timed out` : `${this.providerLabel} API error: ${msg}`
4403
4735
  };
4404
4736
  } finally {
4737
+ releaseHostedSlot?.();
4405
4738
  call.dispose();
4406
4739
  }
4407
4740
  }
@@ -4416,10 +4749,14 @@ Missing: ${missing.join("; ")}
4416
4749
  };
4417
4750
  }
4418
4751
  const idleTimeoutMs = opts?.idleTimeoutMs ?? llmStreamIdleTimeoutMs();
4752
+ const eventIdleTimeoutMs = opts?.eventIdleTimeoutMs ?? llmStreamEventIdleTimeoutMs();
4753
+ let lastEventAt = Date.now();
4419
4754
  const operatorSignal = opts?.abort?.operator;
4420
4755
  let stalled = false;
4756
+ let stallIdleMs = idleTimeoutMs;
4421
4757
  const readBounded = async () => {
4422
4758
  let timer;
4759
+ let eventTimer;
4423
4760
  const detach = operatorSignal ? new AbortController() : void 0;
4424
4761
  try {
4425
4762
  return await Promise.race([
@@ -4427,9 +4764,22 @@ Missing: ${missing.join("; ")}
4427
4764
  new Promise((_resolve, reject) => {
4428
4765
  timer = setTimeout(() => {
4429
4766
  stalled = true;
4767
+ stallIdleMs = idleTimeoutMs;
4430
4768
  reject(new Error("stream stalled"));
4431
4769
  }, idleTimeoutMs);
4432
4770
  }),
4771
+ // Event-level racer: fires when no MEANINGFUL SSE event has arrived
4772
+ // for `eventIdleTimeoutMs`, even if keep-alive bytes keep resetting
4773
+ // the byte-level timer above. Recomputed per read, so an event that
4774
+ // landed while the previous chunk was being parsed re-arms it.
4775
+ new Promise((_resolve, reject) => {
4776
+ const remainingMs = eventIdleTimeoutMs - (Date.now() - lastEventAt);
4777
+ eventTimer = setTimeout(() => {
4778
+ stalled = true;
4779
+ stallIdleMs = eventIdleTimeoutMs;
4780
+ reject(new Error("stream stalled"));
4781
+ }, Math.max(remainingMs, 0));
4782
+ }),
4433
4783
  // A real aborted `fetch` also errors the body stream, so `read()`
4434
4784
  // would reject on its own — but only for a live socket. This racer
4435
4785
  // is what makes cancellation immediate and unconditional, including
@@ -4445,16 +4795,21 @@ Missing: ${missing.join("; ")}
4445
4795
  ] : []
4446
4796
  ]);
4447
4797
  } finally {
4448
- if (timer)
4449
- clearTimeout(timer);
4798
+ clearTimeout(timer);
4799
+ clearTimeout(eventTimer);
4450
4800
  detach?.abort();
4451
4801
  }
4452
4802
  };
4453
4803
  const decoder = new TextDecoder();
4454
4804
  let buffer = "";
4805
+ let trailingCR = false;
4806
+ let receivedBytes = 0;
4807
+ let malformedEvents = 0;
4808
+ const eventTypes = /* @__PURE__ */ new Set();
4809
+ const identifier = (value) => typeof value === "string" && /^[A-Za-z0-9_.:-]{1,96}$/.test(value) ? value : null;
4455
4810
  let completedResponse = null;
4456
- let openRouterStreamFailed = false;
4457
- let openRouterUsage;
4811
+ let streamFailure;
4812
+ let responseUsage;
4458
4813
  const streamedOutputItems = [];
4459
4814
  let thinkingText = "";
4460
4815
  let lastThinkingEmit = 0;
@@ -4477,7 +4832,7 @@ Missing: ${missing.join("; ")}
4477
4832
  lastThinkingLength = thinkingText.length;
4478
4833
  callbacks.onThinking(thinkingText);
4479
4834
  };
4480
- while (true) {
4835
+ responses: while (true) {
4481
4836
  let chunk;
4482
4837
  try {
4483
4838
  chunk = await readBounded();
@@ -4494,10 +4849,10 @@ Missing: ${missing.join("; ")}
4494
4849
  await reader.cancel();
4495
4850
  } catch {
4496
4851
  }
4497
- const secs = Math.round(idleTimeoutMs / 1e3);
4852
+ const secs = Math.round(stallIdleMs / 1e3);
4498
4853
  diag.warn("stream_stalled", `${this.providerLabel} stream stalled \u2014 no SSE events for ${secs}s (server hold; aborting call)`, {
4499
4854
  provider: this.providerLabel,
4500
- idle_timeout_ms: idleTimeoutMs,
4855
+ idle_timeout_ms: stallIdleMs,
4501
4856
  idle_timeout_s: secs
4502
4857
  });
4503
4858
  return {
@@ -4510,9 +4865,18 @@ Missing: ${missing.join("; ")}
4510
4865
  throw err;
4511
4866
  }
4512
4867
  const { done, value } = chunk;
4513
- if (done)
4868
+ if (done) {
4869
+ buffer += decoder.decode();
4514
4870
  break;
4515
- buffer += decoder.decode(value, { stream: true });
4871
+ }
4872
+ receivedBytes += value.byteLength;
4873
+ let decoded = decoder.decode(value, { stream: true });
4874
+ if (decoded) {
4875
+ if (trailingCR && decoded.startsWith("\n"))
4876
+ decoded = decoded.slice(1);
4877
+ trailingCR = decoded.endsWith("\r");
4878
+ buffer += decoded.replace(/\r\n?/g, "\n");
4879
+ }
4516
4880
  let boundary = buffer.indexOf("\n\n");
4517
4881
  while (boundary >= 0) {
4518
4882
  const rawChunk = buffer.slice(0, boundary);
@@ -4521,27 +4885,46 @@ Missing: ${missing.join("; ")}
4521
4885
  const payload = rawChunk.split("\n").filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trim()).join("\n");
4522
4886
  if (!payload || payload === "[DONE]")
4523
4887
  continue;
4888
+ lastEventAt = Date.now();
4524
4889
  let event;
4525
4890
  try {
4526
4891
  event = JSON.parse(payload);
4892
+ if (!event || typeof event !== "object" || Array.isArray(event)) {
4893
+ malformedEvents++;
4894
+ continue;
4895
+ }
4527
4896
  } catch {
4897
+ malformedEvents++;
4528
4898
  continue;
4529
4899
  }
4530
4900
  const type = String(event.type ?? "");
4531
- if (this.provider === "openrouter" && (type === "response.done" || type === "response.failed" || type === "response.completed" || type === "response.incomplete")) {
4532
- const response = event.response;
4533
- const usage2 = response?.usage;
4534
- if (usage2 && typeof usage2.input_tokens === "number" && Number.isFinite(usage2.input_tokens) && usage2.input_tokens >= 0 && typeof usage2.output_tokens === "number" && Number.isFinite(usage2.output_tokens) && usage2.output_tokens >= 0) {
4535
- openRouterUsage = {
4536
- inputTokens: usage2.input_tokens,
4537
- outputTokens: usage2.output_tokens,
4538
- ...readResponsesCachedTokens(usage2)
4901
+ if (eventTypes.size < 32)
4902
+ eventTypes.add(identifier(type) ?? "unrecognized");
4903
+ const terminal = type === "response.completed" || this.provider === "openrouter" && type === "response.done";
4904
+ if (terminal || type === "response.failed" || type === "response.incomplete" || type === "error") {
4905
+ const response = event.response && typeof event.response === "object" && !Array.isArray(event.response) ? event.response : void 0;
4906
+ const usage = response?.usage;
4907
+ if (usage && typeof usage.input_tokens === "number" && Number.isFinite(usage.input_tokens) && usage.input_tokens >= 0 && typeof usage.output_tokens === "number" && Number.isFinite(usage.output_tokens) && usage.output_tokens >= 0) {
4908
+ responseUsage = {
4909
+ inputTokens: usage.input_tokens,
4910
+ outputTokens: usage.output_tokens,
4911
+ ...readResponsesCachedTokens(usage)
4539
4912
  };
4540
- callbacks?.onUsage?.(openRouterUsage);
4913
+ callbacks?.onUsage?.(responseUsage);
4541
4914
  }
4542
- }
4543
- if (this.provider === "openrouter" && (type === "error" || type === "response.failed" || type === "response.incomplete")) {
4544
- openRouterStreamFailed = true;
4915
+ if (!terminal || !response || response.error != null || (type === "response.done" || response.status !== void 0) && response.status !== "completed") {
4916
+ const error = response?.error ?? event.error;
4917
+ const incomplete = response?.incomplete_details;
4918
+ streamFailure = {
4919
+ event: type,
4920
+ status: identifier(response?.status),
4921
+ code: identifier(error?.code ?? event.code),
4922
+ errorType: identifier(error?.type),
4923
+ reason: identifier(incomplete?.reason)
4924
+ };
4925
+ break responses;
4926
+ }
4927
+ completedResponse = response;
4545
4928
  continue;
4546
4929
  }
4547
4930
  if (type === "response.output_text.delta" || this.provider === "openrouter" && type === "response.content_part.delta") {
@@ -4575,33 +4958,33 @@ Missing: ${missing.join("; ")}
4575
4958
  }
4576
4959
  continue;
4577
4960
  }
4578
- if (type === "response.completed" || type === "response.incomplete" || this.provider === "openrouter" && type === "response.done") {
4579
- const response = event.response;
4580
- if (response) {
4581
- if (this.provider === "openrouter" && (openRouterStreamFailed || response.error != null || (type === "response.done" || response.status !== void 0) && response.status !== "completed")) {
4582
- openRouterStreamFailed = true;
4583
- continue;
4584
- }
4585
- completedResponse = response;
4586
- const usage2 = response.usage;
4587
- if (usage2 && this.provider !== "openrouter") {
4588
- callbacks?.onUsage?.({
4589
- inputTokens: Number(usage2.input_tokens ?? 0),
4590
- outputTokens: Number(usage2.output_tokens ?? 0)
4591
- });
4592
- }
4593
- }
4594
- }
4595
4961
  }
4596
4962
  }
4597
4963
  emitThinking(true);
4598
- if (!completedResponse || openRouterStreamFailed) {
4964
+ if (!completedResponse || streamFailure) {
4965
+ if (streamFailure) {
4966
+ void reader.cancel().catch(() => {
4967
+ });
4968
+ }
4969
+ appendNativeTrace({
4970
+ kind: "native-response-stream-error",
4971
+ provider: this.providerLabel,
4972
+ wireApi: this.wireApi,
4973
+ httpStatus: res.status,
4974
+ eventStreamContentType: /^text\/event-stream(?:\s*;|$)/i.test(res.headers.get("content-type") ?? ""),
4975
+ terminalFailure: streamFailure ?? null,
4976
+ eventTypes: [...eventTypes],
4977
+ receivedBytes,
4978
+ malformedEvents,
4979
+ trailingCharacters: buffer.length,
4980
+ usage: responseUsage ?? null
4981
+ });
4599
4982
  return {
4600
4983
  content: thinkingText ? [{ type: "text", text: thinkingText }] : [{ type: "text", text: "" }],
4601
4984
  stopReason: "error",
4602
4985
  durationMs: Date.now() - start,
4603
- ...openRouterUsage ? { usage: openRouterUsage } : {},
4604
- error: `${this.providerLabel} API error: ${openRouterStreamFailed ? "response stream failed" : "stream completed without final response"}`
4986
+ ...responseUsage ? { usage: responseUsage } : {},
4987
+ error: `${this.providerLabel} API error: ${streamFailure ? `Responses terminal failure ${JSON.stringify(streamFailure)}` : `stream completed without final response (HTTP ${res.status}; events=${[...eventTypes].join(",") || "none"}; malformed=${malformedEvents}; trailing=${buffer.length})`}`
4605
4988
  };
4606
4989
  }
4607
4990
  appendNativeTrace({
@@ -4654,21 +5037,10 @@ Missing: ${missing.join("; ")}
4654
5037
  thinkingText = reasoningSummaries.join("\n");
4655
5038
  emitThinking(true);
4656
5039
  }
4657
- const usageRecord = completedResponse.usage;
4658
- const usage = usageRecord ? {
4659
- inputTokens: Number(usageRecord.input_tokens ?? 0),
4660
- outputTokens: Number(usageRecord.output_tokens ?? 0),
4661
- // Responses `input_tokens` already INCLUDES the cached span (unlike
4662
- // Anthropic, which subtracts it), so no normalisation is needed —
4663
- // this is purely so cache behaviour becomes observable. Without it
4664
- // the Codex cache hit rate is unmeasurable: `prompt-cache.ts`
4665
- // instruments the Anthropic path only.
4666
- ...readResponsesCachedTokens(usageRecord)
4667
- } : void 0;
4668
5040
  return {
4669
5041
  content,
4670
5042
  stopReason: content.some((item) => item.type === "tool_use") ? "tool_use" : "end_turn",
4671
- usage,
5043
+ usage: responseUsage,
4672
5044
  durationMs: Date.now() - start,
4673
5045
  // `outputItems` is the complete, correctly-ordered response array —
4674
5046
  // reasoning items with their `encrypted_content` still attached, each
@@ -4724,5 +5096,6 @@ export {
4724
5096
  OperatorAbortError,
4725
5097
  parseUsageLimitReached,
4726
5098
  LOOP_SERVER_COMPACTION_TOKENS,
5099
+ getChatGptCodexAccessToken,
4727
5100
  LlmApiRuntime
4728
5101
  };