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