0sec-cli 0.22.1 → 0.22.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/0.js +56 -45
- package/README.md +12 -0
- package/chunks/{adapt-loop-J6QCZZTB.js → adapt-loop-3CCOWE43.js} +4 -4
- package/chunks/{artifact-scraper-MFKT6OI4.js → artifact-scraper-Z4UUIOH3.js} +2 -2
- package/chunks/{assumption-mining-646LYSJY.js → assumption-mining-2UAKQB6V.js} +18 -14
- package/chunks/{chunk-OHHCGDJ2.js → chunk-2WWAPKAW.js} +126 -1089
- package/chunks/{chunk-XDKB2P5X.js → chunk-3BPX75OR.js} +1 -1
- package/chunks/chunk-4ZYYX5GY.js +74 -0
- package/chunks/{chunk-3E2KALZ5.js → chunk-5JNTMWPK.js} +2 -2
- package/chunks/{chunk-CPWA6RWE.js → chunk-5WAJGLCW.js} +3 -3
- package/chunks/{chunk-KAU6NLGP.js → chunk-6KFB6AYN.js} +515 -20
- package/chunks/{chunk-XA2O4WXA.js → chunk-6WQDXWQC.js} +169 -15
- package/chunks/chunk-6YZE552K.js +5603 -0
- package/chunks/chunk-BGX53365.js +309 -0
- package/chunks/{chunk-X7LAEEUW.js → chunk-C6JPA32H.js} +1 -1
- package/chunks/chunk-CF3MNS3O.js +990 -0
- package/chunks/{chunk-4VGLO2YS.js → chunk-CPCHLL6A.js} +5 -2
- package/chunks/{chunk-ZKQ2I3S2.js → chunk-DPUXXMJW.js} +1 -1
- package/chunks/{chunk-XCISCI4T.js → chunk-E3OOBVOK.js} +30 -12
- package/chunks/{chunk-4G7OVYJX.js → chunk-E74LXQFY.js} +1 -1
- package/chunks/{chunk-OSBPGWTE.js → chunk-ELI3BVIK.js} +3 -3
- package/chunks/chunk-FFHRANYC.js +547 -0
- package/chunks/{chunk-WSDDGFA6.js → chunk-G2BE2FC7.js} +1 -1
- package/chunks/chunk-GQ3DGHWE.js +1659 -0
- package/chunks/{chunk-V36HVTUL.js → chunk-GVY3S5E5.js} +21 -18
- package/chunks/{chunk-5BTJMINS.js → chunk-H4DH7COV.js} +192 -40
- package/chunks/{chunk-HHPWRY2M.js → chunk-H6Q2QAIY.js} +23475 -22747
- package/chunks/{chunk-7XCKYNLO.js → chunk-IHPTOTLL.js} +1 -1
- package/chunks/chunk-IXAHQICR.js +31 -0
- package/chunks/{chunk-TH2LW437.js → chunk-KCA35IL3.js} +2 -190
- package/chunks/{chunk-VWITLWXB.js → chunk-L2CCO4KE.js} +282 -34
- package/chunks/{chunk-6YNKRUMC.js → chunk-NJLKFC66.js} +321 -5
- package/chunks/{chunk-W6UWZFKL.js → chunk-O6KPVYOI.js} +100 -5
- package/chunks/{chunk-LGCASF2C.js → chunk-OAS5SAVZ.js} +6767 -2100
- package/chunks/chunk-PS5E7LUQ.js +277 -0
- package/chunks/{chunk-AV2LG2L7.js → chunk-QVIRWJ45.js} +257 -145
- package/chunks/{chunk-TV27OWIW.js → chunk-QWK4R3DB.js} +9 -9
- package/chunks/{chunk-WZRVS7SB.js → chunk-RCRTSKQN.js} +1 -1
- package/chunks/chunk-RS4MAN3Q.js +73 -0
- package/chunks/{chunk-V6GHX34G.js → chunk-RTBZ36BD.js} +8 -274
- package/chunks/{chunk-YL6YAGLL.js → chunk-SCWWLABR.js} +1 -1
- package/chunks/chunk-VDOWARMJ.js +63 -0
- package/chunks/{chunk-IFME676H.js → chunk-WBRGXCU2.js} +79 -18
- package/chunks/{chunk-IVQV574Y.js → chunk-WHUAUFSX.js} +1 -1
- package/chunks/{chunk-GOEA3NW6.js → chunk-XODDLNBI.js} +1534 -1305
- package/chunks/chunk-YAZARMJY.js +355 -0
- package/chunks/{chunk-NLH4LMWH.js → chunk-YED25PA4.js} +1 -1
- package/chunks/{codex-models-NHQW45SO.js → codex-models-Z43V3427.js} +9 -3
- package/chunks/{commands-N6GN5SBF.js → commands-EUGF4FUZ.js} +5842 -4093
- package/chunks/{db-5IWEJUYV.js → db-NVAW4WWR.js} +3 -3
- package/chunks/{dist-L3XJWWZ6.js → dist-27BNG6WD.js} +193 -112
- package/chunks/{dist-FSS7MBDF.js → dist-7RUOKPRJ.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-NG2HT5XU.js} +27 -1
- package/chunks/{eval-runner-EMEGF24P.js → eval-runner-6Q3H52SP.js} +13 -9
- package/chunks/{exploit-agent-KW4EB7BE.js → exploit-agent-FCCR2HYX.js} +1 -1
- package/chunks/{exploit-autoclimb-CXJ2ERRO.js → exploit-autoclimb-X5ZS5FAO.js} +1 -1
- package/chunks/{exploit-climb-TJWLY4XC.js → exploit-climb-3JHGEO36.js} +2 -2
- package/chunks/{fix-UUKPXCFB.js → fix-NSNPU33B.js} +12 -6
- package/chunks/{harness-C5HOKIEV.js → harness-KXQOVIDQ.js} +3 -3
- package/chunks/{hunt-scan-KZZ444V2.js → hunt-scan-ELTKGKC4.js} +16 -12
- package/chunks/{kernel-primitive-Z77IR7YS.js → kernel-primitive-MT7YZSIG.js} +2 -2
- package/chunks/{kernel-vm-runner-JW77P276.js → kernel-vm-runner-YFRHYC6G.js} +2 -2
- package/chunks/{native-loop-P6P5GYVJ.js → native-loop-OFJDRHPI.js} +18 -8
- package/chunks/orchestrate-DRZ2SVCP.js +62 -0
- package/chunks/{prepare-KYHGFWCJ.js → prepare-72GWTRVI.js} +6 -2
- package/chunks/{process-N245QIQG.js → process-MJYJ2K5E.js} +2 -1
- package/chunks/{run-GAFKBCTN.js → run-WTJCZZHV.js} +1901 -4816
- package/chunks/{runtime-H7YBR5IZ.js → runtime-FCKOUB7V.js} +10 -4
- package/chunks/{runtime-XFWJTBUO.js → runtime-LCJ4XSKE.js} +5 -1
- package/chunks/{scan-stream-BMOCPKQX.js → scan-stream-GT6GKQ3U.js} +7 -1
- package/chunks/{scope-QWY4VQZ7.js → scope-E5QZUOOH.js} +2 -2
- package/chunks/service-plugins-MSK2EQP6.js +62 -0
- package/chunks/{session-store-XIDFGG7K.js → session-store-H3Q7KCYG.js} +6 -4
- package/chunks/{variant-candidates-7D4F5E4H.js → variant-candidates-SZKDCOLK.js} +9 -3
- package/chunks/{web-recon-prepass-VXXBPDAV.js → web-recon-prepass-IO3GXQMW.js} +11 -6
- package/dashboard/apple-touch-icon.png +0 -0
- package/dashboard/assets/operations-BvB5Qpmk.css +1 -0
- package/dashboard/assets/operations-D8Ii01SV.js +114 -0
- package/dashboard/assets/zero-peek-Dzm3MT3X.png +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-6GHSR47F.js +0 -147
- package/chunks/chunk-BCSRRQG5.js +0 -3514
- package/chunks/chunk-BOUGKEKA.js +0 -193
- package/chunks/chunk-GHWODZMR.js +0 -16
- 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,122 +1,987 @@
|
|
|
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
|
+
createConsoleRuntime,
|
|
6
|
+
createWorkbenchProviderBroker,
|
|
7
|
+
runSmolvmWorkbench,
|
|
8
|
+
snapshotSmolvmWorkspace
|
|
9
|
+
} from "./chunk-OAS5SAVZ.js";
|
|
10
|
+
import {
|
|
11
|
+
TOOL_DEFINITIONS
|
|
12
|
+
} from "./chunk-H6Q2QAIY.js";
|
|
4
13
|
import {
|
|
5
14
|
ANALYTICS_LEVEL_ENV,
|
|
6
|
-
|
|
15
|
+
getSmolvmWorkbenchStatus,
|
|
16
|
+
isAdmittedSmolvmWorkbench,
|
|
7
17
|
levelAtLeast,
|
|
8
18
|
loadCloudCredentials,
|
|
9
19
|
resolveAnalyticsLevel
|
|
10
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-2WWAPKAW.js";
|
|
21
|
+
import {
|
|
22
|
+
ScopePolicy,
|
|
23
|
+
aggregateCapabilities,
|
|
24
|
+
emptyEnablement,
|
|
25
|
+
getScopeEnforcementState,
|
|
26
|
+
listInstalledPluginIds,
|
|
27
|
+
loadableIds,
|
|
28
|
+
pluginsRootDir,
|
|
29
|
+
readEnablement,
|
|
30
|
+
readInstalledPlugin,
|
|
31
|
+
reconcile,
|
|
32
|
+
writeEnablement
|
|
33
|
+
} from "./chunk-C6JPA32H.js";
|
|
11
34
|
import {
|
|
12
35
|
DEFAULT_ALLOW_MODEL_SELF_EXTENSION,
|
|
13
|
-
|
|
36
|
+
SERVICE_PLUGIN_CATALOG,
|
|
37
|
+
VERSION,
|
|
38
|
+
findingSchema,
|
|
14
39
|
homeStateDir
|
|
15
|
-
} from "./chunk-
|
|
40
|
+
} from "./chunk-6KFB6AYN.js";
|
|
16
41
|
|
|
17
|
-
// packages/cli/src/
|
|
18
|
-
import {
|
|
42
|
+
// packages/cli/src/workbench.ts
|
|
43
|
+
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";
|
|
44
|
+
import { homedir as homedir2 } from "node:os";
|
|
45
|
+
import { dirname as dirname5, isAbsolute as isAbsolute2, join as join8 } from "node:path";
|
|
19
46
|
|
|
20
|
-
// packages/cli/src/
|
|
21
|
-
import {
|
|
47
|
+
// packages/cli/src/workbench-assets.ts
|
|
48
|
+
import { existsSync, lstatSync, realpathSync } from "node:fs";
|
|
49
|
+
import { dirname, join } from "node:path";
|
|
50
|
+
import { fileURLToPath } from "node:url";
|
|
51
|
+
function currentWorkbenchAssets(moduleUrl = import.meta.url) {
|
|
52
|
+
let directory = dirname(fileURLToPath(moduleUrl));
|
|
53
|
+
for (let depth = 0; depth < 5; depth++) {
|
|
54
|
+
for (const candidate of [directory, join(directory, "dist")]) {
|
|
55
|
+
const entry = join(candidate, "0.js");
|
|
56
|
+
if (existsSync(entry) && lstatSync(entry).isFile() && !lstatSync(entry).isSymbolicLink()) return { cliDist: realpathSync(candidate) };
|
|
57
|
+
}
|
|
58
|
+
const parent = dirname(directory);
|
|
59
|
+
if (parent === directory) break;
|
|
60
|
+
directory = parent;
|
|
61
|
+
}
|
|
62
|
+
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.");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// packages/cli/src/workbench-console-protocol.ts
|
|
66
|
+
import { isAbsolute, relative, resolve, sep } from "node:path";
|
|
67
|
+
var WORKBENCH_FRAME_BYTES = 8 * 1024 * 1024;
|
|
68
|
+
var WORKBENCH_MAX_PENDING = 64;
|
|
69
|
+
function encodeWorkbenchFrame(frame) {
|
|
70
|
+
const encoded = JSON.stringify(frame);
|
|
71
|
+
if (Buffer.byteLength(encoded) > WORKBENCH_FRAME_BYTES) throw new Error("Workbench protocol frame exceeds its byte limit");
|
|
72
|
+
return encoded + "\n";
|
|
73
|
+
}
|
|
74
|
+
var WorkbenchFrameReader = class {
|
|
75
|
+
#pending = "";
|
|
76
|
+
push(data, receive) {
|
|
77
|
+
this.#pending += data;
|
|
78
|
+
let newline;
|
|
79
|
+
while ((newline = this.#pending.indexOf("\n")) !== -1) {
|
|
80
|
+
const line = this.#pending.slice(0, newline);
|
|
81
|
+
this.#pending = this.#pending.slice(newline + 1);
|
|
82
|
+
if (!line || Buffer.byteLength(line) > WORKBENCH_FRAME_BYTES) throw new Error("Invalid workbench protocol frame size");
|
|
83
|
+
const frame = JSON.parse(line);
|
|
84
|
+
if (!frame || typeof frame !== "object" || Array.isArray(frame) || typeof frame.type !== "string") throw new Error("Invalid workbench protocol frame");
|
|
85
|
+
const parsed = frame;
|
|
86
|
+
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");
|
|
87
|
+
receive(parsed);
|
|
88
|
+
}
|
|
89
|
+
if (Buffer.byteLength(this.#pending) > WORKBENCH_FRAME_BYTES) throw new Error("Workbench protocol input exceeds its byte limit");
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
function guestWorkspacePath(path, workspace) {
|
|
93
|
+
if (path === "/workspace" || path.startsWith("/workspace/")) {
|
|
94
|
+
const normalized = resolve(path);
|
|
95
|
+
if (normalized !== "/workspace" && !normalized.startsWith("/workspace/")) throw new Error("Invalid guest workspace path");
|
|
96
|
+
return normalized;
|
|
97
|
+
}
|
|
98
|
+
if (!isAbsolute(path)) throw new Error("Workbench scope requires an absolute host path");
|
|
99
|
+
const suffix = relative(workspace, path);
|
|
100
|
+
if (isAbsolute(suffix) || suffix === ".." || suffix.startsWith(`..${sep}`)) throw new Error("Requested host path is outside the granted workbench workspace");
|
|
101
|
+
return suffix ? `/workspace/${suffix.split(sep).join("/")}` : "/workspace";
|
|
102
|
+
}
|
|
103
|
+
function hostWorkspacePath(path, workspace) {
|
|
104
|
+
if (path === "/workspace" || path.startsWith("/workspace/")) {
|
|
105
|
+
const normalized = resolve(path);
|
|
106
|
+
if (normalized !== "/workspace" && !normalized.startsWith("/workspace/")) throw new Error("Invalid guest workspace path");
|
|
107
|
+
return resolve(workspace, relative("/workspace", normalized));
|
|
108
|
+
}
|
|
109
|
+
throw new Error("Guest local scope is outside the granted workspace");
|
|
110
|
+
}
|
|
111
|
+
function mapWorkbenchTarget(target, workspace, host = false) {
|
|
112
|
+
const map = host ? hostWorkspacePath : guestWorkspacePath;
|
|
113
|
+
if (isAbsolute(target)) return map(target, workspace);
|
|
114
|
+
if (target.startsWith("source:") && isAbsolute(target.slice(7))) return `source:${map(target.slice(7), workspace)}`;
|
|
115
|
+
return target;
|
|
116
|
+
}
|
|
117
|
+
function mapWorkbenchCheckpoint(checkpoint, workspace, host) {
|
|
118
|
+
const result = structuredClone(checkpoint);
|
|
119
|
+
const map = host ? hostWorkspacePath : guestWorkspacePath;
|
|
120
|
+
result.workspaceRoot = map(result.workspaceRoot, workspace);
|
|
121
|
+
result.target = mapWorkbenchTarget(result.target, workspace, host);
|
|
122
|
+
if (result.localScopePath) result.localScopePath = map(result.localScopePath, workspace);
|
|
123
|
+
result.deniedLocalPaths = result.deniedLocalPaths.map((path) => {
|
|
124
|
+
try {
|
|
125
|
+
return map(path, workspace);
|
|
126
|
+
} catch {
|
|
127
|
+
return path;
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
if (result.harnessRoot) {
|
|
131
|
+
throw new Error("Workbench checkpoint handoff with live executable resources is not supported");
|
|
132
|
+
}
|
|
133
|
+
return result;
|
|
134
|
+
}
|
|
135
|
+
function serializeWorkbenchConfig(config, workspace) {
|
|
136
|
+
for (const key of ["pluginHost", "mcpHost", "jevRuntime", "contribution", "developmentSourceRoot", "executablePlugins", "executableEvolutionProfiles"]) {
|
|
137
|
+
if (config[key] !== void 0) throw new Error(`Host ${key} cannot execute through the workbench controller`);
|
|
138
|
+
}
|
|
139
|
+
const result = { workspaceRoot: "/workspace", target: mapWorkbenchTarget(config.target ?? "", workspace) };
|
|
140
|
+
for (const key of ["role", "tools", "scanId", "maxToolIterations", "allowScanners", "systemPrompt", "autonomyMode", "refineObjective", "allowModelSelfExtension", "costModel", "contextWindowTokens", "compaction", "initialMessages"]) {
|
|
141
|
+
if (config[key] !== void 0) result[key] = structuredClone(config[key]);
|
|
142
|
+
}
|
|
143
|
+
if (config.maxTurnTokens !== void 0 && Number.isFinite(config.maxTurnTokens)) result.maxTurnTokens = config.maxTurnTokens;
|
|
144
|
+
if (config.scope) result.scope = config.scope.raw;
|
|
145
|
+
if (config.initialCheckpoint) result.initialCheckpoint = mapWorkbenchCheckpoint(config.initialCheckpoint, workspace, false);
|
|
146
|
+
if (config.agentMessaging && typeof config.agentMessaging === "object") {
|
|
147
|
+
const messaging = config.agentMessaging;
|
|
148
|
+
result.agentMessaging = {
|
|
149
|
+
selfId: messaging.selfId,
|
|
150
|
+
selfRole: messaging.selfRole,
|
|
151
|
+
parentId: messaging.parentId,
|
|
152
|
+
operatorId: messaging.operatorId,
|
|
153
|
+
siblingPrefix: messaging.siblingPrefix,
|
|
154
|
+
siblingChannelEnabled: messaging.siblingChannelEnabled,
|
|
155
|
+
operatorChannelEnabled: messaging.operatorChannelEnabled,
|
|
156
|
+
projectPath: "/workspace",
|
|
157
|
+
homeDir: "/home/zero"
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
return result;
|
|
161
|
+
}
|
|
162
|
+
function mapWorkbenchCliArguments(args, workspace) {
|
|
163
|
+
const targetFlags = /* @__PURE__ */ new Set(["--target", "-t", "--repo"]);
|
|
164
|
+
const pathFlags = /* @__PURE__ */ new Set(["--scope", "--db-path", "--workspace", "--workspace-root", "--cwd", "--output", "-o", "--config"]);
|
|
165
|
+
const result = [...args];
|
|
166
|
+
for (let index = 0; index < result.length; index++) {
|
|
167
|
+
const argument = result[index];
|
|
168
|
+
const equal = argument.indexOf("=");
|
|
169
|
+
const flag = equal < 0 ? argument : argument.slice(0, equal);
|
|
170
|
+
if (!targetFlags.has(flag) && !pathFlags.has(flag)) continue;
|
|
171
|
+
const valueIndex = equal < 0 ? index + 1 : index;
|
|
172
|
+
const value = equal < 0 ? result[valueIndex] : argument.slice(equal + 1);
|
|
173
|
+
if (value === void 0) throw new Error(`Missing workbench argument for ${flag}`);
|
|
174
|
+
const mapped = targetFlags.has(flag) ? mapWorkbenchTarget(value, workspace) : isAbsolute(value) ? guestWorkspacePath(value, workspace) : value;
|
|
175
|
+
result[valueIndex] = equal < 0 ? mapped : `${flag}=${mapped}`;
|
|
176
|
+
if (equal < 0) index++;
|
|
177
|
+
}
|
|
178
|
+
return result;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// packages/cli/src/workbench-console-session.ts
|
|
22
182
|
import { randomUUID } from "node:crypto";
|
|
23
|
-
import {
|
|
183
|
+
import { realpathSync as realpathSync2 } from "node:fs";
|
|
184
|
+
import { mkdir, mkdtemp as mkdtemp2, realpath as realpath2 } from "node:fs/promises";
|
|
185
|
+
import { join as join3 } from "node:path";
|
|
24
186
|
|
|
25
|
-
// packages/cli/src/
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
187
|
+
// packages/cli/src/workbench-plugins.ts
|
|
188
|
+
import { lstat, mkdtemp, realpath, rm } from "node:fs/promises";
|
|
189
|
+
import { join as join2 } from "node:path";
|
|
190
|
+
var GUEST_PLUGIN_ASSETS = "/opt/0-approved-plugins";
|
|
191
|
+
var reservedToolNames = Object.values(TOOL_DEFINITIONS).map((tool) => tool.name);
|
|
192
|
+
var bounds = { maxBytes: 64 * 1024 * 1024, maxFiles: 4096 };
|
|
193
|
+
async function prepareWorkbenchPlugins(project, homeDir) {
|
|
194
|
+
const root = pluginsRootDir(homeDir);
|
|
195
|
+
const record = readEnablement(project, homeDir);
|
|
196
|
+
const views = listInstalledPluginIds(root).flatMap((id) => {
|
|
197
|
+
const discovered = readInstalledPlugin(root, id, { reservedToolNames });
|
|
198
|
+
return discovered.ok ? [{ id, version: discovered.plugin.manifest.version, capabilities: aggregateCapabilities(discovered.plugin.manifest) }] : [];
|
|
199
|
+
});
|
|
200
|
+
const ids = loadableIds(reconcile(record, views));
|
|
201
|
+
const approvals = emptyEnablement("/workspace");
|
|
202
|
+
if (!ids.length) return { approvals, cleanup: async () => {
|
|
203
|
+
} };
|
|
204
|
+
const directory = await realpath(await mkdtemp("/tmp/0-guest-plugins-"));
|
|
205
|
+
const cleanup = () => rm(directory, { recursive: true, force: true });
|
|
206
|
+
try {
|
|
207
|
+
let bytes = 0, files = 0;
|
|
208
|
+
for (const id of ids) {
|
|
209
|
+
if ((await lstat(join2(root, id))).isSymbolicLink()) throw new Error("Workbench plugins cannot use linked source directories");
|
|
210
|
+
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);
|
|
211
|
+
bytes += snapshot.bytes;
|
|
212
|
+
files += snapshot.files.length;
|
|
213
|
+
if (bytes > bounds.maxBytes || files > bounds.maxFiles) throw new Error("Approved plugins exceed the workbench plugin limits");
|
|
214
|
+
const copied = readInstalledPlugin(directory, id, { reservedToolNames });
|
|
215
|
+
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`);
|
|
216
|
+
approvals.enabled[id] = record.enabled[id];
|
|
217
|
+
}
|
|
218
|
+
return { directory, approvals, cleanup };
|
|
219
|
+
} catch (error) {
|
|
220
|
+
await cleanup();
|
|
221
|
+
throw error;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
async function installWorkbenchPlugins(approvals) {
|
|
225
|
+
if (!isAdmittedSmolvmWorkbench()) throw new Error("Plugin installation requires admitted VM execution");
|
|
226
|
+
const ids = Object.keys(approvals.enabled);
|
|
227
|
+
for (const id of ids) {
|
|
228
|
+
const discovered = readInstalledPlugin(GUEST_PLUGIN_ASSETS, id, { reservedToolNames });
|
|
229
|
+
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}`);
|
|
230
|
+
await snapshotSmolvmWorkspace(join2(GUEST_PLUGIN_ASSETS, id), join2(pluginsRootDir(), id), bounds, void 0, false, false);
|
|
231
|
+
}
|
|
232
|
+
if (ids.length && !writeEnablement("/workspace", { ...approvals, project: "/workspace" })) throw new Error("Could not save guest plugin approvals");
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// packages/cli/src/workbench-service-plugins.ts
|
|
236
|
+
function validateGuestServicePluginConnections(raw, networkGranted) {
|
|
237
|
+
if (raw === void 0) return [];
|
|
238
|
+
if (!Array.isArray(raw) || raw.length > SERVICE_PLUGIN_CATALOG.length) throw new Error("Invalid guest service plugin grants");
|
|
239
|
+
const ids = /* @__PURE__ */ new Set();
|
|
240
|
+
const connections = raw.map((value) => {
|
|
241
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("Invalid guest service plugin grant");
|
|
242
|
+
const row = value;
|
|
243
|
+
const plugin = SERVICE_PLUGIN_CATALOG.find((item) => item.id === row.id);
|
|
244
|
+
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");
|
|
245
|
+
ids.add(plugin.id);
|
|
246
|
+
const fields = row.fields;
|
|
247
|
+
if (Object.keys(fields).some((key) => !plugin.fields.some((field) => field.key === key))) throw new Error("Invalid guest service plugin field");
|
|
248
|
+
const output = {};
|
|
249
|
+
for (const field of plugin.fields) {
|
|
250
|
+
const value2 = fields[field.key];
|
|
251
|
+
if (value2 === void 0 && !field.required) continue;
|
|
252
|
+
if (typeof value2 !== "string" || !value2.trim() || value2.length > 8192 || /[\x00-\x1f\x7f]/.test(value2)) throw new Error("Invalid guest service plugin field");
|
|
253
|
+
if (field.type === "url") {
|
|
254
|
+
let url;
|
|
255
|
+
try {
|
|
256
|
+
url = new URL(value2);
|
|
257
|
+
} catch {
|
|
258
|
+
throw new Error("Invalid guest service plugin URL");
|
|
259
|
+
}
|
|
260
|
+
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");
|
|
261
|
+
}
|
|
262
|
+
output[field.key] = value2;
|
|
263
|
+
}
|
|
264
|
+
return { id: plugin.id, enabled: true, fields: output };
|
|
265
|
+
});
|
|
266
|
+
if (connections.length && networkGranted !== true) throw new Error("Service plugins require the existing SmolVM network grant. Enable workbench networking before using connected plugins.");
|
|
267
|
+
return connections;
|
|
268
|
+
}
|
|
269
|
+
function servicePluginSecretRedactor(connections) {
|
|
270
|
+
const secrets = connections.flatMap((connection) => {
|
|
271
|
+
const plugin = SERVICE_PLUGIN_CATALOG.find((item) => item.id === connection.id);
|
|
272
|
+
const values = plugin.fields.filter((field) => field.type === "secret").map((field) => connection.fields[field.key]).filter((value) => !!value);
|
|
273
|
+
if (connection.id === "jira" && connection.fields.email && connection.fields.token) values.push(Buffer.from(`${connection.fields.email}:${connection.fields.token}`).toString("base64"));
|
|
274
|
+
return values.flatMap((value) => [value, encodeURIComponent(value), JSON.stringify(value).slice(1, -1)]);
|
|
275
|
+
}).sort((a, b) => b.length - a.length);
|
|
276
|
+
return (value) => secrets.reduce((text, secret) => text.split(secret).join("[redacted]"), value);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// packages/cli/src/workbench-console-session.ts
|
|
280
|
+
var EVENTS = /* @__PURE__ */ new Set(["onHarnessUpdate", "onAssistantDelta", "onReasoningDelta", "onToolStart", "onToolResult", "onUsage", "onNotice", "onCompaction"]);
|
|
281
|
+
var DECISIONS = /* @__PURE__ */ new Set(["requestScope", "requestLocalScope", "approveTool", "escalateScopedAudit", "askOperator", "historyList", "historyRead"]);
|
|
282
|
+
function validateOptions(options) {
|
|
283
|
+
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");
|
|
284
|
+
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");
|
|
285
|
+
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}`);
|
|
286
|
+
}
|
|
287
|
+
function guestCommand(options, cli) {
|
|
288
|
+
if (options.guestCommand) return { command: options.guestCommand };
|
|
289
|
+
const entry = cli ? "run-agent" : "console-agent";
|
|
290
|
+
if (!options.assets) return { command: ["/usr/local/bin/0", "--workbench-inner", "workbench", entry] };
|
|
291
|
+
const dependencies = options.assets.dependencies ? "/opt/0-controller-deps/node_modules" : "/opt/0/node_modules";
|
|
292
|
+
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));`;
|
|
293
|
+
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" }] : []] };
|
|
294
|
+
}
|
|
295
|
+
var Controller = class {
|
|
296
|
+
constructor(options, init, cli) {
|
|
297
|
+
this.options = options;
|
|
298
|
+
this.init = init;
|
|
299
|
+
this.cli = cli;
|
|
300
|
+
validateOptions(options);
|
|
301
|
+
this.workspace = realpathSync2(options.workbench.workspaceRoot ?? process.cwd());
|
|
302
|
+
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 };
|
|
303
|
+
this.ready = new Promise((resolve2, reject) => {
|
|
304
|
+
this.resolveReady = resolve2;
|
|
305
|
+
this.rejectReady = reject;
|
|
306
|
+
});
|
|
307
|
+
void this.ready.catch(() => {
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
options;
|
|
311
|
+
init;
|
|
312
|
+
cli;
|
|
313
|
+
abort = new AbortController();
|
|
314
|
+
runId = randomUUID();
|
|
315
|
+
workspace;
|
|
316
|
+
execution;
|
|
317
|
+
ready;
|
|
318
|
+
done;
|
|
319
|
+
resolveReady;
|
|
320
|
+
rejectReady;
|
|
321
|
+
input;
|
|
322
|
+
pending = /* @__PURE__ */ new Map();
|
|
323
|
+
providers = /* @__PURE__ */ new Map();
|
|
324
|
+
idle;
|
|
325
|
+
lifetime;
|
|
326
|
+
started = false;
|
|
327
|
+
guestReady = false;
|
|
328
|
+
startupStderr = "";
|
|
329
|
+
redact = (value) => value;
|
|
330
|
+
receive = () => {
|
|
331
|
+
};
|
|
332
|
+
decision = async () => null;
|
|
333
|
+
status(status, message) {
|
|
334
|
+
const { message: _previous, ...snapshot } = this.execution;
|
|
335
|
+
this.execution = { ...snapshot, status, ...message ? { message } : {} };
|
|
336
|
+
this.options.onExecution?.(this.execution);
|
|
337
|
+
}
|
|
338
|
+
write(frame) {
|
|
339
|
+
if (!this.input || this.abort.signal.aborted) throw new Error("Workbench VM is unavailable");
|
|
340
|
+
this.input.write(encodeWorkbenchFrame(frame));
|
|
341
|
+
}
|
|
342
|
+
touch() {
|
|
343
|
+
clearTimeout(this.idle);
|
|
344
|
+
this.idle = setTimeout(() => {
|
|
345
|
+
this.abort.abort(new Error("Workbench idle deadline expired"));
|
|
346
|
+
}, this.options.idleMs ?? 5 * 6e4);
|
|
347
|
+
this.idle.unref();
|
|
348
|
+
}
|
|
349
|
+
async start() {
|
|
350
|
+
if (this.started) return this.ready;
|
|
351
|
+
this.started = true;
|
|
352
|
+
this.status("pending", "Starting isolated workbench");
|
|
353
|
+
let stagedPlugins;
|
|
354
|
+
this.lifetime = setTimeout(() => this.abort.abort(new Error("Workbench lifetime expired")), this.options.lifetimeMs ?? 30 * 6e4);
|
|
355
|
+
this.lifetime.unref();
|
|
356
|
+
try {
|
|
357
|
+
const root = join3(this.options.workbench.stateRoot, "controller-results");
|
|
358
|
+
await mkdir(root, { recursive: true, mode: 448 });
|
|
359
|
+
const artifacts = this.options.artifactDirectory ?? await realpath2(await mkdtemp2(join3(root, "run-")));
|
|
360
|
+
const reader = new WorkbenchFrameReader();
|
|
361
|
+
const launch = guestCommand(this.options, this.cli);
|
|
362
|
+
const connections = this.cli ? [] : (await import("./service-plugins-MSK2EQP6.js")).loadServicePluginConnections(this.options.pluginHomeDir).filter((item) => item.enabled);
|
|
363
|
+
const servicePluginConnections = validateGuestServicePluginConnections(connections, this.options.network);
|
|
364
|
+
this.redact = servicePluginSecretRedactor(servicePluginConnections);
|
|
365
|
+
const plugins = stagedPlugins = await prepareWorkbenchPlugins(this.workspace, this.options.pluginHomeDir);
|
|
366
|
+
this.done = runSmolvmWorkbench({
|
|
367
|
+
image: this.options.workbench.image,
|
|
368
|
+
stateRoot: this.options.workbench.stateRoot,
|
|
369
|
+
workspaceRoot: this.workspace,
|
|
370
|
+
command: launch.command,
|
|
371
|
+
environment: { ZERO_PROVIDER: "chatgpt-codex", ZERO_NO_TELEMETRY: "1", DO_NOT_TRACK: "1" },
|
|
372
|
+
network: this.options.network,
|
|
373
|
+
tty: false,
|
|
374
|
+
cpus: this.options.workbench.cpus,
|
|
375
|
+
memoryMb: this.options.workbench.memoryMb,
|
|
376
|
+
storageGb: this.options.workbench.storageGb,
|
|
377
|
+
approvedImages: this.options.workbench.approvedImages,
|
|
378
|
+
signal: this.abort.signal,
|
|
379
|
+
workspaceMode: "snapshot",
|
|
380
|
+
artifactDirectory: artifacts,
|
|
381
|
+
readOnlyMounts: [...launch.mounts ?? [], ...plugins.directory ? [{ source: plugins.directory, target: GUEST_PLUGIN_ASSETS }] : []],
|
|
382
|
+
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) => {
|
|
383
|
+
this.input = input;
|
|
384
|
+
}, onStdout: (data) => reader.push(data, (frame) => this.dispatch(frame)), onStderr: (data) => {
|
|
385
|
+
this.startupStderr = this.redact(this.startupStderr + data).slice(-4096);
|
|
386
|
+
} }
|
|
387
|
+
});
|
|
388
|
+
this.done = this.done.finally(() => plugins.cleanup());
|
|
389
|
+
void this.done.then(async (result) => {
|
|
390
|
+
const error = new Error(this.redact(result.error ?? "Workbench VM exited") + (!this.guestReady && this.startupStderr.trim() ? `: ${this.startupStderr.trim()}` : ""));
|
|
391
|
+
if (!this.guestReady) this.rejectReady(error);
|
|
392
|
+
for (const pending of this.pending.values()) pending.reject(error);
|
|
393
|
+
this.pending.clear();
|
|
394
|
+
clearTimeout(this.idle);
|
|
395
|
+
clearTimeout(this.lifetime);
|
|
396
|
+
for (const pending of this.providers.values()) pending.abort();
|
|
397
|
+
this.providers.clear();
|
|
398
|
+
await this.options.provider.close?.();
|
|
399
|
+
this.status(result.cleanupFailed || result.exitCode !== 0 && !this.abort.signal.aborted ? "failed" : "stopped", result.error ? this.redact(result.error) : void 0);
|
|
400
|
+
});
|
|
401
|
+
this.touch();
|
|
402
|
+
await this.ready;
|
|
403
|
+
} catch (error) {
|
|
404
|
+
if (!this.done) {
|
|
405
|
+
await stagedPlugins?.cleanup();
|
|
406
|
+
await this.options.provider.close?.();
|
|
407
|
+
}
|
|
408
|
+
clearTimeout(this.idle);
|
|
409
|
+
clearTimeout(this.lifetime);
|
|
410
|
+
this.abort.abort();
|
|
411
|
+
this.rejectReady(new Error(this.redact(error instanceof Error ? error.message : "Workbench startup failed")));
|
|
412
|
+
this.status("failed", this.redact(error instanceof Error ? error.message : "Workbench startup failed"));
|
|
413
|
+
throw new Error(this.redact(error instanceof Error ? error.message : "Workbench startup failed"));
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
dispatch(frame) {
|
|
417
|
+
if (frame.type === "ready") {
|
|
418
|
+
if (this.guestReady || frame.platform !== "linux" || frame.workspace !== "/workspace") throw new Error("Invalid workbench guest readiness");
|
|
419
|
+
this.guestReady = true;
|
|
420
|
+
this.status("ready");
|
|
421
|
+
this.resolveReady();
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
if (frame.type === "provider") {
|
|
425
|
+
void this.provider(frame);
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
if (frame.type === "provider-cancel") {
|
|
429
|
+
this.providers.get(frame.id)?.abort();
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
if (frame.type === "decision") {
|
|
433
|
+
if (!DECISIONS.has(String(frame.name)) || !Array.isArray(frame.args)) throw new Error("Invalid guest decision callback");
|
|
434
|
+
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(() => {
|
|
435
|
+
});
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
if (frame.type === "result" || frame.type === "error") {
|
|
439
|
+
const pending = this.pending.get(frame.id);
|
|
440
|
+
this.pending.delete(frame.id);
|
|
441
|
+
if (frame.type === "error") {
|
|
442
|
+
const error = new Error(typeof frame.error === "string" ? this.redact(frame.error) : "Guest operation failed");
|
|
443
|
+
if (!this.guestReady) this.rejectReady(error);
|
|
444
|
+
pending?.reject(error);
|
|
445
|
+
} else pending?.resolve(frame.value);
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
this.receive(frame);
|
|
449
|
+
}
|
|
450
|
+
async provider(frame) {
|
|
451
|
+
if (!frame.id || this.providers.size >= 4 || this.providers.has(frame.id)) {
|
|
452
|
+
this.abort.abort(new Error("Invalid guest provider request"));
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
const envelope = frame.envelope;
|
|
456
|
+
const abort = new AbortController();
|
|
457
|
+
this.providers.set(frame.id, abort);
|
|
458
|
+
const cancel = () => abort.abort();
|
|
459
|
+
this.abort.signal.addEventListener("abort", cancel, { once: true });
|
|
460
|
+
try {
|
|
461
|
+
if (!envelope || envelope.provider !== "chatgpt-codex" || !this.options.provider.models.includes(envelope.model) || typeof envelope.body !== "string") throw new Error("Invalid guest provider envelope");
|
|
462
|
+
const response = await this.options.provider.request({ provider: envelope.provider, model: envelope.model, body: envelope.body }, abort.signal);
|
|
463
|
+
this.write({ type: "provider-headers", id: frame.id, status: response.status, contentType: response.headers.get("content-type") ?? "text/event-stream" });
|
|
464
|
+
let bytes = 0;
|
|
465
|
+
if (response.body) {
|
|
466
|
+
const reader = response.body.getReader();
|
|
467
|
+
try {
|
|
468
|
+
while (true) {
|
|
469
|
+
const next = await reader.read();
|
|
470
|
+
if (next.done) break;
|
|
471
|
+
const chunk = next.value;
|
|
472
|
+
bytes += chunk.length;
|
|
473
|
+
if (bytes > 16 * 1024 * 1024) throw new Error("Provider response exceeds controller limit");
|
|
474
|
+
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") });
|
|
475
|
+
}
|
|
476
|
+
} finally {
|
|
477
|
+
await reader.cancel().catch(() => {
|
|
478
|
+
});
|
|
479
|
+
reader.releaseLock();
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
this.write({ type: "provider-end", id: frame.id });
|
|
483
|
+
} catch {
|
|
484
|
+
try {
|
|
485
|
+
this.write({ type: "provider-error", id: frame.id });
|
|
486
|
+
} catch {
|
|
487
|
+
}
|
|
488
|
+
} finally {
|
|
489
|
+
this.providers.delete(frame.id);
|
|
490
|
+
this.abort.signal.removeEventListener("abort", cancel);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
async request(op, values = {}) {
|
|
494
|
+
await this.start();
|
|
495
|
+
if (this.pending.size >= WORKBENCH_MAX_PENDING) throw new Error("Workbench request limit reached");
|
|
496
|
+
this.touch();
|
|
497
|
+
const id = randomUUID();
|
|
498
|
+
const result = new Promise((resolve2, reject) => this.pending.set(id, { resolve: resolve2, reject }));
|
|
499
|
+
try {
|
|
500
|
+
this.write({ type: "request", id, op, ...values });
|
|
501
|
+
} catch (error) {
|
|
502
|
+
this.pending.delete(id);
|
|
503
|
+
throw error;
|
|
504
|
+
}
|
|
505
|
+
return result;
|
|
506
|
+
}
|
|
507
|
+
async close() {
|
|
508
|
+
if (!this.started) {
|
|
509
|
+
await this.options.provider.close?.();
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
let exited = false;
|
|
513
|
+
try {
|
|
514
|
+
await Promise.race([this.request("close"), new Promise((resolve2) => setTimeout(resolve2, 1e3))]);
|
|
515
|
+
await Promise.race([this.done?.then(() => {
|
|
516
|
+
exited = true;
|
|
517
|
+
}), new Promise((resolve2) => setTimeout(resolve2, 1e3))]);
|
|
518
|
+
} catch {
|
|
519
|
+
} finally {
|
|
520
|
+
if (!exited) this.abort.abort();
|
|
521
|
+
const result = await this.done;
|
|
522
|
+
if (result?.cleanupFailed) throw new Error(result.error ?? "Workbench teardown could not be confirmed");
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
};
|
|
526
|
+
function createWorkbenchConsoleSession(options) {
|
|
527
|
+
const workspace = realpathSync2(options.workbench.workspaceRoot ?? options.config.workspaceRoot ?? process.cwd());
|
|
528
|
+
const config = { ...options.config, scanId: options.config.scanId ?? `console-${randomUUID()}` };
|
|
529
|
+
const serial = serializeWorkbenchConfig(config, workspace);
|
|
530
|
+
const callbacks = [...DECISIONS].filter((name) => name === "historyList" || name === "historyRead" ? !!config.conversationHistory : typeof config[name] === "function");
|
|
531
|
+
const scopeEnforcement = getScopeEnforcementState(workspace);
|
|
532
|
+
const controller = new Controller({ ...options, workbench: { ...options.workbench, workspaceRoot: workspace } }, { config: serial, callbacks, scopeEnforcement }, false);
|
|
533
|
+
let messages = structuredClone(config.initialMessages ?? config.initialCheckpoint?.messages ?? []);
|
|
534
|
+
let checkpoint = config.initialCheckpoint;
|
|
535
|
+
let target = config.target ?? "";
|
|
536
|
+
let scope = config.scope;
|
|
537
|
+
let localScopePath = config.initialCheckpoint?.localScopePath ?? void 0;
|
|
538
|
+
let autonomyMode = config.autonomyMode ?? "standard";
|
|
539
|
+
let systemPrompt = config.systemPrompt ?? "";
|
|
540
|
+
let tools = config.tools ?? [];
|
|
541
|
+
let render;
|
|
542
|
+
let active = false;
|
|
543
|
+
let findings = [];
|
|
544
|
+
let engineWorkStarted = false;
|
|
545
|
+
let lastOutcome;
|
|
546
|
+
let cleanupPromise;
|
|
547
|
+
const deferred = [];
|
|
548
|
+
const queue = (op, value) => {
|
|
549
|
+
if (!controller.done) {
|
|
550
|
+
serial[op === "autonomy" ? "autonomyMode" : op] = value;
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
553
|
+
const pending = controller.request(op, { value });
|
|
554
|
+
deferred.push(pending);
|
|
555
|
+
void pending.catch(() => {
|
|
556
|
+
});
|
|
557
|
+
};
|
|
558
|
+
controller.decision = async (name, args) => {
|
|
559
|
+
if (name === "historyList") return config.conversationHistory.list(...args);
|
|
560
|
+
if (name === "historyRead") return config.conversationHistory.read(...args);
|
|
561
|
+
const callback = config[name];
|
|
562
|
+
if (!callback || !callbacks.includes(name)) return null;
|
|
563
|
+
if (name === "requestLocalScope") {
|
|
564
|
+
const request = structuredClone(args[0]);
|
|
565
|
+
for (const key of ["requestedPath", "currentScopePath"]) if (typeof request[key] === "string") request[key] = hostWorkspacePath(request[key], workspace);
|
|
566
|
+
const value = await callback(request);
|
|
567
|
+
if (!value) return null;
|
|
568
|
+
const resolution = value;
|
|
569
|
+
return { scopePath: guestWorkspacePath(resolution.scopePath, workspace) };
|
|
570
|
+
}
|
|
571
|
+
if (name === "requestScope") {
|
|
572
|
+
const request = args[0];
|
|
573
|
+
const value = await callback({ ...request, target: mapWorkbenchTarget(String(request.target ?? ""), workspace, true), ...request.currentScope ? { currentScope: new ScopePolicy(request.currentScope.raw) } : {} });
|
|
574
|
+
if (!value) return null;
|
|
575
|
+
const resolution = value;
|
|
576
|
+
return { target: mapWorkbenchTarget(resolution.target, workspace), scope: resolution.scope.raw };
|
|
577
|
+
}
|
|
578
|
+
return callback(...args);
|
|
579
|
+
};
|
|
580
|
+
controller.receive = (frame) => {
|
|
581
|
+
if (frame.type === "state") {
|
|
582
|
+
const state = frame.snapshot;
|
|
583
|
+
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");
|
|
584
|
+
messages = state.messages;
|
|
585
|
+
systemPrompt = String(state.systemPrompt ?? "");
|
|
586
|
+
tools = state.tools;
|
|
587
|
+
target = mapWorkbenchTarget(String(state.target ?? ""), workspace, true);
|
|
588
|
+
scope = state.scope ? new ScopePolicy(state.scope) : void 0;
|
|
589
|
+
localScopePath = state.localScopePath ? hostWorkspacePath(String(state.localScopePath), workspace) : void 0;
|
|
590
|
+
autonomyMode = state.autonomyMode;
|
|
591
|
+
checkpoint = state.checkpoint ? mapWorkbenchCheckpoint(state.checkpoint, workspace, true) : void 0;
|
|
592
|
+
} else if (frame.type === "event") {
|
|
593
|
+
if (!EVENTS.has(String(frame.name)) || !Array.isArray(frame.args)) throw new Error("Invalid guest render callback");
|
|
594
|
+
const callback = render?.[frame.name];
|
|
595
|
+
callback?.(...frame.args);
|
|
596
|
+
} else if (frame.type === "findings") {
|
|
597
|
+
if (!Array.isArray(frame.findings) || frame.findings.length > 1e3) throw new Error("Invalid guest findings");
|
|
598
|
+
findings = frame.findings.map((finding) => findingSchema.parse(finding));
|
|
599
|
+
} else throw new Error("Unsupported guest controller event");
|
|
600
|
+
};
|
|
601
|
+
return {
|
|
602
|
+
scanId: config.scanId,
|
|
603
|
+
ready: Promise.resolve(),
|
|
604
|
+
get execution() {
|
|
605
|
+
return controller.execution;
|
|
606
|
+
},
|
|
607
|
+
get systemPrompt() {
|
|
608
|
+
return systemPrompt;
|
|
609
|
+
},
|
|
610
|
+
get tools() {
|
|
611
|
+
return tools;
|
|
612
|
+
},
|
|
613
|
+
get messages() {
|
|
614
|
+
return messages;
|
|
615
|
+
},
|
|
616
|
+
get target() {
|
|
617
|
+
return target;
|
|
618
|
+
},
|
|
619
|
+
get scope() {
|
|
620
|
+
return scope;
|
|
621
|
+
},
|
|
622
|
+
get localScopePath() {
|
|
623
|
+
return localScopePath;
|
|
624
|
+
},
|
|
625
|
+
get autonomyMode() {
|
|
626
|
+
return autonomyMode;
|
|
627
|
+
},
|
|
628
|
+
scopeEnforcement,
|
|
629
|
+
setAutonomyMode(mode) {
|
|
630
|
+
autonomyMode = mode;
|
|
631
|
+
queue("autonomy", mode);
|
|
632
|
+
},
|
|
633
|
+
configureEngagement(selection) {
|
|
634
|
+
if (selection.target !== void 0) target = selection.target;
|
|
635
|
+
if (selection.scope !== void 0) scope = selection.scope ?? void 0;
|
|
636
|
+
const value = { ...selection.target === void 0 ? {} : { target: mapWorkbenchTarget(selection.target, workspace) }, ...selection.scope === void 0 ? {} : { scope: selection.scope?.raw ?? null } };
|
|
637
|
+
if (!controller.done) Object.assign(serial, value);
|
|
638
|
+
else queue("configure", value);
|
|
639
|
+
},
|
|
640
|
+
configureWorkspace(path) {
|
|
641
|
+
if (realpathSync2(path) !== workspace) throw new Error("The SmolVM workspace grant is fixed for this chat; start a new chat to change it");
|
|
642
|
+
},
|
|
643
|
+
reconfigureRuntime(selection) {
|
|
644
|
+
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");
|
|
645
|
+
Object.assign(options.selection, selection);
|
|
646
|
+
if (controller.done) queue("reconfigure", selection);
|
|
647
|
+
},
|
|
648
|
+
clearConversation() {
|
|
649
|
+
messages = [];
|
|
650
|
+
checkpoint = void 0;
|
|
651
|
+
if (!controller.done) {
|
|
652
|
+
serial.initialMessages = [];
|
|
653
|
+
delete serial.initialCheckpoint;
|
|
654
|
+
} else queue("clear");
|
|
655
|
+
},
|
|
656
|
+
async send(text, callbacks2, sendOptions) {
|
|
657
|
+
if (active) throw new Error("Workbench turn already active");
|
|
658
|
+
active = true;
|
|
659
|
+
render = callbacks2;
|
|
660
|
+
let cancelTimer;
|
|
661
|
+
const cancel = () => {
|
|
662
|
+
try {
|
|
663
|
+
controller.write({ type: "cancel" });
|
|
664
|
+
} catch {
|
|
665
|
+
}
|
|
666
|
+
cancelTimer = setTimeout(() => controller.abort.abort(new Error("Operator cancelled workbench turn")), 2e3);
|
|
667
|
+
};
|
|
668
|
+
sendOptions?.signal?.addEventListener("abort", cancel, { once: true });
|
|
669
|
+
try {
|
|
670
|
+
await Promise.all(deferred.splice(0));
|
|
671
|
+
await controller.start();
|
|
672
|
+
if (sendOptions?.signal?.aborted) cancel();
|
|
673
|
+
controller.status("running");
|
|
674
|
+
engineWorkStarted = true;
|
|
675
|
+
lastOutcome = await controller.request("send", { text, generateTitle: sendOptions?.generateTitle });
|
|
676
|
+
return lastOutcome;
|
|
677
|
+
} finally {
|
|
678
|
+
clearTimeout(cancelTimer);
|
|
679
|
+
active = false;
|
|
680
|
+
render = void 0;
|
|
681
|
+
sendOptions?.signal?.removeEventListener("abort", cancel);
|
|
682
|
+
if (!controller.abort.signal.aborted && controller.execution.status !== "stopped" && controller.execution.status !== "failed") controller.status("ready");
|
|
683
|
+
}
|
|
684
|
+
},
|
|
685
|
+
async stopPersistentAgent(agentId) {
|
|
686
|
+
return await controller.request("stopWorker", { agentId });
|
|
687
|
+
},
|
|
688
|
+
async stopPersistentAgents() {
|
|
689
|
+
if (controller.done) await controller.request("stopWorkers");
|
|
690
|
+
},
|
|
691
|
+
exportCheckpoint() {
|
|
692
|
+
if (active || !checkpoint) throw new Error("No quiescent VM checkpoint is available");
|
|
693
|
+
return structuredClone(checkpoint);
|
|
694
|
+
},
|
|
695
|
+
async prepareHandoff() {
|
|
696
|
+
if (active) throw new Error("Cannot hand off active VM turn");
|
|
697
|
+
if (!controller.done) return {};
|
|
698
|
+
return await controller.request("handoff");
|
|
699
|
+
},
|
|
700
|
+
cleanup() {
|
|
701
|
+
return cleanupPromise ??= (async () => {
|
|
702
|
+
await controller.close();
|
|
703
|
+
if (engineWorkStarted && controller.done) await options.onFindings?.(findings, { outcome: lastOutcome });
|
|
704
|
+
})();
|
|
705
|
+
}
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
async function runWorkbenchCli(options) {
|
|
709
|
+
if (options.args.length > 512 || options.args.some((arg) => typeof arg !== "string" || arg.includes("\0"))) throw new Error("Invalid workbench CLI argv");
|
|
710
|
+
const controller = new Controller(options, { args: options.args }, true);
|
|
711
|
+
let exitCode;
|
|
712
|
+
controller.receive = (frame) => {
|
|
713
|
+
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"));
|
|
714
|
+
else if (frame.type === "exit" && Number.isSafeInteger(frame.code)) {
|
|
715
|
+
exitCode = frame.code;
|
|
716
|
+
} else throw new Error("Invalid CLI guest event");
|
|
717
|
+
};
|
|
718
|
+
const cancel = () => controller.abort.abort();
|
|
719
|
+
options.signal?.addEventListener("abort", cancel, { once: true });
|
|
720
|
+
try {
|
|
721
|
+
if (options.signal?.aborted) cancel();
|
|
722
|
+
await controller.start();
|
|
723
|
+
controller.status("running");
|
|
724
|
+
const result = await controller.done;
|
|
725
|
+
return { ...result, exitCode: exitCode ?? result.exitCode };
|
|
726
|
+
} finally {
|
|
727
|
+
options.signal?.removeEventListener("abort", cancel);
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
// packages/cli/src/tui/provider-status.ts
|
|
732
|
+
var PROVIDER_DEFS = [
|
|
38
733
|
{
|
|
39
|
-
id: "
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
734
|
+
id: "chatgpt-codex",
|
|
735
|
+
label: "ChatGPT Codex",
|
|
736
|
+
methods: ["oauth"],
|
|
737
|
+
// OAuth, not an API key. Both tokens are accepted and the access token is
|
|
738
|
+
// read first (llm-api.ts L874-875, L1386-1394), so it leads the list.
|
|
739
|
+
envVars: ["ZERO_CHATGPT_ACCESS_TOKEN", "ZERO_CHATGPT_OAUTH_REFRESH_TOKEN"],
|
|
740
|
+
fileSource: "~/.codex/auth.json (override with ZERO_CHATGPT_AUTH_FILE)",
|
|
741
|
+
hint: "run `codex login` to write ~/.codex/auth.json, or invoke 0 with env ZERO_CHATGPT_OAUTH_REFRESH_TOKEN=..."
|
|
47
742
|
},
|
|
48
743
|
{
|
|
49
|
-
id: "
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
category: "Composer",
|
|
55
|
-
lockReason: "text entry",
|
|
56
|
-
handler: 'if (key.name === "tab") setComposerText(completionFor(selectedSlashCommand, \u2026))'
|
|
744
|
+
id: "deepseek",
|
|
745
|
+
label: "DeepSeek",
|
|
746
|
+
methods: ["api-key"],
|
|
747
|
+
envVars: ["DEEPSEEK_API_KEY"],
|
|
748
|
+
hint: "set DEEPSEEK_API_KEY (endpoint override: DEEPSEEK_BASE_URL)"
|
|
57
749
|
},
|
|
58
750
|
{
|
|
59
|
-
id: "
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
751
|
+
id: "openrouter",
|
|
752
|
+
label: "OpenRouter",
|
|
753
|
+
// OAuth (PKCE browser sign-in) is preferred; a pasted OPENROUTER_API_KEY
|
|
754
|
+
// remains a secondary path. The browser flow provisions a durable
|
|
755
|
+
// `sk-or-...` key and stores it as an api_key record written to
|
|
756
|
+
// OPENROUTER_API_KEY (envVars[0], which llm-api already reads), so — unlike
|
|
757
|
+
// the xai/kimi device flows — the credential is a key, not OAuth tokens.
|
|
758
|
+
methods: ["oauth", "api-key"],
|
|
759
|
+
envVars: ["OPENROUTER_API_KEY"],
|
|
760
|
+
hint: "sign in with your OpenRouter account, or set OPENROUTER_API_KEY=sk-or-... from openrouter.ai/keys"
|
|
66
761
|
},
|
|
67
762
|
{
|
|
68
|
-
id: "
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
763
|
+
id: "azure",
|
|
764
|
+
label: "Azure OpenAI",
|
|
765
|
+
methods: ["api-key"],
|
|
766
|
+
// Only the Azure key authenticates; the deployment URL comes from
|
|
767
|
+
// AZURE_OPENAI_BASE_URL / OPENAI_BASE_URL / ~/.codex/config.toml
|
|
768
|
+
// (L1435-1445). A key with no reachable base URL still counts as
|
|
769
|
+
// configured here because detectProvider selects azure on the key alone.
|
|
770
|
+
envVars: ["AZURE_OPENAI_API_KEY"],
|
|
771
|
+
hint: "set AZURE_OPENAI_API_KEY plus AZURE_OPENAI_BASE_URL (or [model_providers.azure] in ~/.codex/config.toml)"
|
|
76
772
|
},
|
|
77
773
|
{
|
|
78
|
-
id: "
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
category: "Composer",
|
|
84
|
-
lockReason: "text entry",
|
|
85
|
-
handler: 'if (key.ctrl && key.name === "u") \u2192 deleteToLineStart'
|
|
774
|
+
id: "openai",
|
|
775
|
+
label: "OpenAI",
|
|
776
|
+
methods: ["api-key"],
|
|
777
|
+
envVars: ["OPENAI_API_KEY"],
|
|
778
|
+
hint: "set OPENAI_API_KEY=sk-... from platform.openai.com/api-keys"
|
|
86
779
|
},
|
|
87
780
|
{
|
|
88
|
-
id: "
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
category: "Composer",
|
|
94
|
-
lockReason: "text entry",
|
|
95
|
-
handler: 'if (key.name === "right") { const suffix = suggestCompletion(...); if (suffix) setComposerText(`${\u2026}${suffix}`) }'
|
|
781
|
+
id: "z-ai",
|
|
782
|
+
label: "Z.ai GLM",
|
|
783
|
+
methods: ["api-key"],
|
|
784
|
+
envVars: ["Z_AI_API_KEY"],
|
|
785
|
+
hint: "set Z_AI_API_KEY from your Z.ai Coding Plan (endpoint override: Z_AI_BASE_URL)"
|
|
96
786
|
},
|
|
97
|
-
// ── Navigation ──────────────────────────────────────────────────────────────
|
|
98
787
|
{
|
|
99
|
-
id: "
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
788
|
+
id: "kimi",
|
|
789
|
+
label: "Moonshot Kimi",
|
|
790
|
+
// OAuth (device sign-in) is preferred; a pasted KIMI_API_KEY remains a
|
|
791
|
+
// secondary path. The OAuth access token is written to KIMI_API_KEY as a
|
|
792
|
+
// Bearer (llm-api.ts reads it there, no change needed), and the refresh
|
|
793
|
+
// token to the 0-owned var so the store can round-trip it.
|
|
794
|
+
methods: ["oauth", "api-key"],
|
|
795
|
+
envVars: ["KIMI_API_KEY", "ZERO_KIMI_OAUTH_REFRESH_TOKEN"],
|
|
796
|
+
hint: "sign in with your Kimi account, or set KIMI_API_KEY from your Kimi coding plan (endpoint override: KIMI_BASE_URL)"
|
|
106
797
|
},
|
|
107
798
|
{
|
|
108
|
-
id: "
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
category: "Navigation",
|
|
114
|
-
lockReason: "modal",
|
|
115
|
-
handler: 'if (key.name === "up") recallComposerHistory("up")'
|
|
799
|
+
id: "qwen",
|
|
800
|
+
label: "Alibaba Qwen",
|
|
801
|
+
methods: ["api-key"],
|
|
802
|
+
envVars: ["QWEN_API_KEY"],
|
|
803
|
+
hint: "set QWEN_API_KEY from Alibaba Model Studio (endpoint override: QWEN_BASE_URL)"
|
|
116
804
|
},
|
|
117
805
|
{
|
|
118
|
-
id: "
|
|
119
|
-
|
|
806
|
+
id: "xai",
|
|
807
|
+
label: "xAI Grok",
|
|
808
|
+
// OAuth (device sign-in) is preferred; a pasted XAI_API_KEY remains a
|
|
809
|
+
// secondary path. The OAuth access token is written to XAI_API_KEY as a
|
|
810
|
+
// Bearer (llm-api.ts reads it there, no change needed), and the refresh
|
|
811
|
+
// token to the 0-owned var so the store can round-trip it.
|
|
812
|
+
methods: ["oauth", "api-key"],
|
|
813
|
+
envVars: ["XAI_API_KEY", "ZERO_XAI_OAUTH_REFRESH_TOKEN"],
|
|
814
|
+
hint: "sign in with your xAI account, or set XAI_API_KEY from console.x.ai (endpoint override: XAI_BASE_URL)"
|
|
815
|
+
},
|
|
816
|
+
{
|
|
817
|
+
id: "opencode",
|
|
818
|
+
label: "OpenCode Zen",
|
|
819
|
+
methods: ["api-key"],
|
|
820
|
+
envVars: ["OPENCODE_API_KEY"],
|
|
821
|
+
hint: "set OPENCODE_API_KEY from opencode.ai/auth (endpoint override: OPENCODE_BASE_URL)"
|
|
822
|
+
},
|
|
823
|
+
{
|
|
824
|
+
id: "copilot",
|
|
825
|
+
label: "GitHub Copilot",
|
|
826
|
+
// OAuth only (device-code sign-in). The GitHub device-flow access token is
|
|
827
|
+
// written to ZERO_COPILOT_GITHUB_TOKEN (envVars[0]) and sent directly as a
|
|
828
|
+
// Bearer to api.githubcopilot.com — no secondary exchange, no refresh, so
|
|
829
|
+
// there is no refresh-token env var.
|
|
830
|
+
methods: ["oauth"],
|
|
831
|
+
envVars: ["ZERO_COPILOT_GITHUB_TOKEN"],
|
|
832
|
+
hint: "sign in with your GitHub Copilot account (device sign-in), or set ZERO_COPILOT_GITHUB_TOKEN=... (endpoint override: COPILOT_BASE_URL)"
|
|
833
|
+
},
|
|
834
|
+
{
|
|
835
|
+
id: "google",
|
|
836
|
+
label: "Google Gemini (Code Assist)",
|
|
837
|
+
// OAuth only (PKCE browser sign-in — the Gemini CLI flow). The minted
|
|
838
|
+
// access token is written to ZERO_GEMINI_ACCESS_TOKEN (envVars[0]) and the
|
|
839
|
+
// refresh token to ZERO_GEMINI_OAUTH_REFRESH_TOKEN (the /REFRESH/i var); the
|
|
840
|
+
// runtime refreshes on demand against oauth2.googleapis.com. There is no
|
|
841
|
+
// pasted-key equivalent — Code Assist authenticates only via OAuth.
|
|
842
|
+
methods: ["oauth"],
|
|
843
|
+
envVars: ["ZERO_GEMINI_ACCESS_TOKEN", "ZERO_GEMINI_OAUTH_REFRESH_TOKEN"],
|
|
844
|
+
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)"
|
|
845
|
+
},
|
|
846
|
+
{
|
|
847
|
+
id: "anthropic",
|
|
848
|
+
label: "Anthropic",
|
|
849
|
+
// API key only for now. The schema supports OAuth, but Anthropic/Claude
|
|
850
|
+
// subscription OAuth is deliberately deferred — do not add "oauth" here
|
|
851
|
+
// until that workstream lands.
|
|
852
|
+
methods: ["api-key"],
|
|
853
|
+
envVars: ["ANTHROPIC_API_KEY"],
|
|
854
|
+
hint: "set ANTHROPIC_API_KEY=sk-ant-... from console.anthropic.com"
|
|
855
|
+
}
|
|
856
|
+
];
|
|
857
|
+
function primaryMethod(methods) {
|
|
858
|
+
return methods[0] ?? "api-key";
|
|
859
|
+
}
|
|
860
|
+
var PROVIDERS = PROVIDER_DEFS.map((def) => ({
|
|
861
|
+
...def,
|
|
862
|
+
auth: primaryMethod(def.methods)
|
|
863
|
+
}));
|
|
864
|
+
function providerSupportsMethod(providerId, method) {
|
|
865
|
+
const info = PROVIDERS.find((candidate) => candidate.id === providerId);
|
|
866
|
+
return info !== void 0 && info.methods.includes(method);
|
|
867
|
+
}
|
|
868
|
+
function hasCredential(value) {
|
|
869
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
870
|
+
}
|
|
871
|
+
function satisfyingVar(info, env) {
|
|
872
|
+
return info.envVars.find((name) => hasCredential(env[name]));
|
|
873
|
+
}
|
|
874
|
+
function providerStates(env) {
|
|
875
|
+
return PROVIDERS.map((info) => {
|
|
876
|
+
const via = satisfyingVar(info, env);
|
|
877
|
+
return via === void 0 ? { ...info, configured: false } : { ...info, configured: true, via };
|
|
878
|
+
});
|
|
879
|
+
}
|
|
880
|
+
function isProviderConfigured(providerId, env) {
|
|
881
|
+
const info = PROVIDERS.find((candidate) => candidate.id === providerId);
|
|
882
|
+
return info !== void 0 && satisfyingVar(info, env) !== void 0;
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
// packages/cli/src/tui/settings.ts
|
|
886
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, renameSync, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
887
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
888
|
+
import { dirname as dirname3, join as join6 } from "node:path";
|
|
889
|
+
|
|
890
|
+
// packages/cli/src/tui/keybindings.ts
|
|
891
|
+
var KEYBINDINGS = [
|
|
892
|
+
// ── Composer ──────────────────────────────────────────────────────────────
|
|
893
|
+
{
|
|
894
|
+
id: "composer.send",
|
|
895
|
+
keys: "Enter",
|
|
896
|
+
defaultChords: ["return"],
|
|
897
|
+
rebindable: false,
|
|
898
|
+
description: "Send the message or run the highlighted slash command; queues the line when a turn is already in flight.",
|
|
899
|
+
category: "Composer",
|
|
900
|
+
lockReason: "text entry",
|
|
901
|
+
handler: 'if (key.name === "return") { \u2026 send / queue / dequeue }'
|
|
902
|
+
},
|
|
903
|
+
{
|
|
904
|
+
id: "composer.newline",
|
|
905
|
+
keys: "Shift+Enter",
|
|
906
|
+
defaultChords: ["shift+return"],
|
|
907
|
+
rebindable: false,
|
|
908
|
+
description: "Insert a newline instead of sending (terminals without the kitty protocol fall through to send).",
|
|
909
|
+
category: "Composer",
|
|
910
|
+
lockReason: "text entry",
|
|
911
|
+
handler: 'if (key.name === "return" && key.shift) setComposerText(`${\u2026}\\n`)'
|
|
912
|
+
},
|
|
913
|
+
{
|
|
914
|
+
id: "composer.complete-command",
|
|
915
|
+
keys: "Tab",
|
|
916
|
+
defaultChords: ["tab"],
|
|
917
|
+
rebindable: false,
|
|
918
|
+
description: "Complete the highlighted slash command in the command menu.",
|
|
919
|
+
category: "Composer",
|
|
920
|
+
lockReason: "text entry",
|
|
921
|
+
handler: 'if (key.name === "tab") setComposerText(completionFor(selectedSlashCommand, \u2026))'
|
|
922
|
+
},
|
|
923
|
+
{
|
|
924
|
+
id: "composer.edit-queued",
|
|
925
|
+
keys: "Ctrl+Y",
|
|
926
|
+
defaultChords: ["ctrl+y"],
|
|
927
|
+
rebindable: true,
|
|
928
|
+
description: "Pull the most recently queued message back into the composer to edit, re-send, or drop.",
|
|
929
|
+
category: "Composer",
|
|
930
|
+
handler: 'if (matchesBinding(key, "composer.edit-queued", \u2026) && queuedRef.current.length > 0) \u2026'
|
|
931
|
+
},
|
|
932
|
+
{
|
|
933
|
+
id: "composer.delete-word",
|
|
934
|
+
keys: "Ctrl+W / Alt+Backspace",
|
|
935
|
+
defaultChords: ["ctrl+w", "option+backspace"],
|
|
936
|
+
rebindable: false,
|
|
937
|
+
description: "Delete the word before the cursor.",
|
|
938
|
+
category: "Composer",
|
|
939
|
+
lockReason: "text entry",
|
|
940
|
+
handler: 'if (key.ctrl && key.name === "w") / if (key.name === "backspace" && (key.meta || key.option || key.ctrl)) \u2192 deletePreviousWord'
|
|
941
|
+
},
|
|
942
|
+
{
|
|
943
|
+
id: "composer.delete-to-start",
|
|
944
|
+
keys: "Ctrl+U",
|
|
945
|
+
defaultChords: ["ctrl+u"],
|
|
946
|
+
rebindable: false,
|
|
947
|
+
description: "Delete from the cursor to the start of the line.",
|
|
948
|
+
category: "Composer",
|
|
949
|
+
lockReason: "text entry",
|
|
950
|
+
handler: 'if (key.ctrl && key.name === "u") \u2192 deleteToLineStart'
|
|
951
|
+
},
|
|
952
|
+
{
|
|
953
|
+
id: "composer.accept-suggestion",
|
|
954
|
+
keys: "Right",
|
|
955
|
+
defaultChords: ["right"],
|
|
956
|
+
rebindable: false,
|
|
957
|
+
description: "Accept the inline autosuggestion, filling the ghost suffix into the draft without sending.",
|
|
958
|
+
category: "Composer",
|
|
959
|
+
lockReason: "text entry",
|
|
960
|
+
handler: 'if (key.name === "right") { const suffix = suggestCompletion(...); if (suffix) setComposerText(`${\u2026}${suffix}`) }'
|
|
961
|
+
},
|
|
962
|
+
// ── Navigation ──────────────────────────────────────────────────────────────
|
|
963
|
+
{
|
|
964
|
+
id: "nav.palette",
|
|
965
|
+
keys: "Ctrl+P / Ctrl+K",
|
|
966
|
+
defaultChords: ["ctrl+p", "ctrl+k"],
|
|
967
|
+
rebindable: true,
|
|
968
|
+
description: "Open the slash-command palette.",
|
|
969
|
+
category: "Navigation",
|
|
970
|
+
handler: 'if (matchesBinding(key, "nav.palette", \u2026)) setComposerText("/")'
|
|
971
|
+
},
|
|
972
|
+
{
|
|
973
|
+
id: "nav.history-prev",
|
|
974
|
+
keys: "Up",
|
|
975
|
+
defaultChords: ["up"],
|
|
976
|
+
rebindable: false,
|
|
977
|
+
description: "Recall the previous submitted message into the composer (also moves the selection in menus and overlays).",
|
|
978
|
+
category: "Navigation",
|
|
979
|
+
lockReason: "modal",
|
|
980
|
+
handler: 'if (key.name === "up") recallComposerHistory("up")'
|
|
981
|
+
},
|
|
982
|
+
{
|
|
983
|
+
id: "nav.history-next",
|
|
984
|
+
keys: "Down",
|
|
120
985
|
defaultChords: ["down"],
|
|
121
986
|
rebindable: false,
|
|
122
987
|
description: "Recall the next message, or \u2014 on an empty composer with workers running \u2014 drop into the active-subagents list.",
|
|
@@ -416,11 +1281,11 @@ function sanitizeKeybindingOverrides(raw, bindings = KEYBINDINGS) {
|
|
|
416
1281
|
|
|
417
1282
|
// packages/cli/src/tui/themes.ts
|
|
418
1283
|
import { mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
419
|
-
import { dirname, join as
|
|
1284
|
+
import { dirname as dirname2, join as join5 } from "node:path";
|
|
420
1285
|
|
|
421
1286
|
// packages/cli/src/tui/user-themes.ts
|
|
422
1287
|
import { readdirSync, readFileSync } from "node:fs";
|
|
423
|
-
import { join } from "node:path";
|
|
1288
|
+
import { join as join4 } from "node:path";
|
|
424
1289
|
function coerceUserTheme(id, raw) {
|
|
425
1290
|
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
426
1291
|
return { ok: false, reason: "file is not a JSON object" };
|
|
@@ -468,7 +1333,7 @@ function loadUserThemes(dir) {
|
|
|
468
1333
|
}
|
|
469
1334
|
let parsed;
|
|
470
1335
|
try {
|
|
471
|
-
parsed = JSON.parse(readFileSync(
|
|
1336
|
+
parsed = JSON.parse(readFileSync(join4(dir, file), "utf8"));
|
|
472
1337
|
} catch (err) {
|
|
473
1338
|
rejected.push({
|
|
474
1339
|
name: id,
|
|
@@ -1221,11 +2086,11 @@ function degradePalette(palette, depth) {
|
|
|
1221
2086
|
return out;
|
|
1222
2087
|
}
|
|
1223
2088
|
var INSTALLED_THEMES_DIRNAME = "themes";
|
|
1224
|
-
function installedThemesDir(
|
|
1225
|
-
return
|
|
2089
|
+
function installedThemesDir(homeDir) {
|
|
2090
|
+
return join5(homeStateDir(homeDir), INSTALLED_THEMES_DIRNAME);
|
|
1226
2091
|
}
|
|
1227
|
-
function installedThemeFilePath(id,
|
|
1228
|
-
return
|
|
2092
|
+
function installedThemeFilePath(id, homeDir) {
|
|
2093
|
+
return join5(installedThemesDir(homeDir), `${id}.json`);
|
|
1229
2094
|
}
|
|
1230
2095
|
var THEME_ID_RE = /^[a-z][a-z0-9]*([._-][a-z0-9]+)*$/;
|
|
1231
2096
|
var THEME_ID_MAX = 64;
|
|
@@ -1234,36 +2099,36 @@ function isSafeThemeId(id) {
|
|
|
1234
2099
|
}
|
|
1235
2100
|
var installedThemes = null;
|
|
1236
2101
|
var installedFromHome;
|
|
1237
|
-
function readInstalledThemes(
|
|
2102
|
+
function readInstalledThemes(homeDir) {
|
|
1238
2103
|
const out = /* @__PURE__ */ new Map();
|
|
1239
|
-
for (const entry of loadUserThemes(installedThemesDir(
|
|
2104
|
+
for (const entry of loadUserThemes(installedThemesDir(homeDir)).valid) {
|
|
1240
2105
|
out.set(entry.name, entry);
|
|
1241
2106
|
}
|
|
1242
2107
|
return out;
|
|
1243
2108
|
}
|
|
1244
|
-
function ensureInstalledThemesLoaded(
|
|
1245
|
-
if (installedThemes === null || installedFromHome !==
|
|
1246
|
-
installedThemes = readInstalledThemes(
|
|
1247
|
-
installedFromHome =
|
|
2109
|
+
function ensureInstalledThemesLoaded(homeDir) {
|
|
2110
|
+
if (installedThemes === null || installedFromHome !== homeDir) {
|
|
2111
|
+
installedThemes = readInstalledThemes(homeDir);
|
|
2112
|
+
installedFromHome = homeDir;
|
|
1248
2113
|
}
|
|
1249
2114
|
return installedThemes;
|
|
1250
2115
|
}
|
|
1251
|
-
function reloadInstalledThemes(
|
|
1252
|
-
installedThemes = readInstalledThemes(
|
|
1253
|
-
installedFromHome =
|
|
2116
|
+
function reloadInstalledThemes(homeDir) {
|
|
2117
|
+
installedThemes = readInstalledThemes(homeDir);
|
|
2118
|
+
installedFromHome = homeDir;
|
|
1254
2119
|
return installedThemes;
|
|
1255
2120
|
}
|
|
1256
|
-
function installedThemeEntries(
|
|
1257
|
-
return [...ensureInstalledThemesLoaded(
|
|
2121
|
+
function installedThemeEntries(homeDir) {
|
|
2122
|
+
return [...ensureInstalledThemesLoaded(homeDir).values()];
|
|
1258
2123
|
}
|
|
1259
|
-
function allThemeNames(
|
|
1260
|
-
return [...THEME_NAMES, ...ensureInstalledThemesLoaded(
|
|
2124
|
+
function allThemeNames(homeDir) {
|
|
2125
|
+
return [...THEME_NAMES, ...ensureInstalledThemesLoaded(homeDir).keys()];
|
|
1261
2126
|
}
|
|
1262
2127
|
function isKnownTheme(value) {
|
|
1263
2128
|
if (isThemeName(value)) return true;
|
|
1264
2129
|
return typeof value === "string" && installedThemes !== null && installedThemes.has(value);
|
|
1265
2130
|
}
|
|
1266
|
-
function writeInstalledTheme(file,
|
|
2131
|
+
function writeInstalledTheme(file, homeDir) {
|
|
1267
2132
|
if (!isSafeThemeId(file.id)) return { ok: false, error: `unsafe theme id ${JSON.stringify(file.id)}` };
|
|
1268
2133
|
if (isThemeName(file.id)) return { ok: false, error: `"${file.id}" is a built-in theme name and cannot be overridden` };
|
|
1269
2134
|
const issues = validateTheme(file.palette);
|
|
@@ -1271,8 +2136,8 @@ function writeInstalledTheme(file, homeDir2) {
|
|
|
1271
2136
|
return { ok: false, error: `invalid palette: ${issues.map((i) => i.message).join("; ")}` };
|
|
1272
2137
|
}
|
|
1273
2138
|
try {
|
|
1274
|
-
const path = installedThemeFilePath(file.id,
|
|
1275
|
-
mkdirSync(
|
|
2139
|
+
const path = installedThemeFilePath(file.id, homeDir);
|
|
2140
|
+
mkdirSync(dirname2(path), { recursive: true });
|
|
1276
2141
|
const body = {
|
|
1277
2142
|
id: file.id,
|
|
1278
2143
|
...file.label !== void 0 ? { label: file.label } : {},
|
|
@@ -1287,13 +2152,13 @@ function writeInstalledTheme(file, homeDir2) {
|
|
|
1287
2152
|
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
1288
2153
|
}
|
|
1289
2154
|
}
|
|
1290
|
-
function removeInstalledTheme(id,
|
|
2155
|
+
function removeInstalledTheme(id, homeDir) {
|
|
1291
2156
|
if (!isSafeThemeId(id)) return { ok: false, error: `unsafe theme id ${JSON.stringify(id)}` };
|
|
1292
2157
|
if (isThemeName(id)) return { ok: false, error: `"${id}" is a built-in theme and cannot be removed` };
|
|
1293
|
-
const cache = ensureInstalledThemesLoaded(
|
|
2158
|
+
const cache = ensureInstalledThemesLoaded(homeDir);
|
|
1294
2159
|
if (!cache.has(id)) return { ok: false, error: `theme "${id}" is not installed` };
|
|
1295
2160
|
try {
|
|
1296
|
-
rmSync(installedThemeFilePath(id,
|
|
2161
|
+
rmSync(installedThemeFilePath(id, homeDir), { force: true });
|
|
1297
2162
|
return { ok: true };
|
|
1298
2163
|
} catch (err) {
|
|
1299
2164
|
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
@@ -1302,15 +2167,15 @@ function removeInstalledTheme(id, homeDir2) {
|
|
|
1302
2167
|
|
|
1303
2168
|
// packages/cli/src/tui/settings.ts
|
|
1304
2169
|
var THEME_CHOICES = [...THEME_NAMES];
|
|
1305
|
-
function syncThemeChoices(
|
|
1306
|
-
ensureInstalledThemesLoaded(
|
|
1307
|
-
THEME_CHOICES.splice(0, THEME_CHOICES.length, ...allThemeNames(
|
|
2170
|
+
function syncThemeChoices(homeDir) {
|
|
2171
|
+
ensureInstalledThemesLoaded(homeDir);
|
|
2172
|
+
THEME_CHOICES.splice(0, THEME_CHOICES.length, ...allThemeNames(homeDir));
|
|
1308
2173
|
}
|
|
1309
2174
|
var DEFS = [
|
|
1310
2175
|
{
|
|
1311
2176
|
key: "showStatusBar",
|
|
1312
2177
|
label: "Status bar",
|
|
1313
|
-
description: "
|
|
2178
|
+
description: "Bottom bar with model, folder, git and token counts. Permission mode and turn timer always show.",
|
|
1314
2179
|
kind: "boolean",
|
|
1315
2180
|
default: true,
|
|
1316
2181
|
group: "Display"
|
|
@@ -1326,7 +2191,7 @@ var DEFS = [
|
|
|
1326
2191
|
{
|
|
1327
2192
|
key: "composerSuggestions",
|
|
1328
2193
|
label: "Composer autosuggestions",
|
|
1329
|
-
description: "
|
|
2194
|
+
description: "Ghost-text suggestions from your past messages. Right arrow accepts.",
|
|
1330
2195
|
kind: "boolean",
|
|
1331
2196
|
default: true,
|
|
1332
2197
|
group: "Display"
|
|
@@ -1334,7 +2199,7 @@ var DEFS = [
|
|
|
1334
2199
|
{
|
|
1335
2200
|
key: "mouseSupport",
|
|
1336
2201
|
label: "Mouse support",
|
|
1337
|
-
description: "Scroll
|
|
2202
|
+
description: "Scroll, click rows and hints. Turn off to use your terminal's own text selection.",
|
|
1338
2203
|
kind: "boolean",
|
|
1339
2204
|
default: true,
|
|
1340
2205
|
group: "Display"
|
|
@@ -1350,7 +2215,7 @@ var DEFS = [
|
|
|
1350
2215
|
{
|
|
1351
2216
|
key: "showRuntimeNotices",
|
|
1352
2217
|
label: "Runtime notices",
|
|
1353
|
-
description: "
|
|
2218
|
+
description: "Show engine output (stdout/stderr) in the conversation.",
|
|
1354
2219
|
kind: "boolean",
|
|
1355
2220
|
default: true,
|
|
1356
2221
|
group: "Transcript"
|
|
@@ -1382,7 +2247,7 @@ var DEFS = [
|
|
|
1382
2247
|
{
|
|
1383
2248
|
key: "showObjective",
|
|
1384
2249
|
label: "Objective",
|
|
1385
|
-
description:
|
|
2250
|
+
description: "Short title for the current session in the bottom bar.",
|
|
1386
2251
|
kind: "boolean",
|
|
1387
2252
|
default: true,
|
|
1388
2253
|
group: "Display"
|
|
@@ -1390,7 +2255,7 @@ var DEFS = [
|
|
|
1390
2255
|
{
|
|
1391
2256
|
key: "showScope",
|
|
1392
2257
|
label: "Scope",
|
|
1393
|
-
description: "Show the
|
|
2258
|
+
description: "Show the target scope and exclusions. No scope is different from an empty scope.",
|
|
1394
2259
|
kind: "boolean",
|
|
1395
2260
|
default: true,
|
|
1396
2261
|
group: "Display"
|
|
@@ -1441,7 +2306,7 @@ var DEFS = [
|
|
|
1441
2306
|
{
|
|
1442
2307
|
key: "transcriptStyle",
|
|
1443
2308
|
label: "Transcript style",
|
|
1444
|
-
description: "
|
|
2309
|
+
description: "How the conversation looks. Minimal (default) is flat text; Bubble and Balanced add message bubbles; Rail, plain, compact and document are alternatives.",
|
|
1445
2310
|
kind: "enum",
|
|
1446
2311
|
default: "minimal",
|
|
1447
2312
|
choices: ["minimal", "rail", "bubble", "balanced", "plain", "compact", "document"],
|
|
@@ -1450,7 +2315,7 @@ var DEFS = [
|
|
|
1450
2315
|
{
|
|
1451
2316
|
key: "roleLabelStyle",
|
|
1452
2317
|
label: "Role label",
|
|
1453
|
-
description: '
|
|
2318
|
+
description: 'Label your messages as "You". Full and short add the age, glyph shows the name only, off hides it.',
|
|
1454
2319
|
kind: "enum",
|
|
1455
2320
|
default: "off",
|
|
1456
2321
|
choices: ["full", "short", "glyph", "off"],
|
|
@@ -1468,7 +2333,7 @@ var DEFS = [
|
|
|
1468
2333
|
{
|
|
1469
2334
|
key: "richToolCards",
|
|
1470
2335
|
label: "Rich tool cards",
|
|
1471
|
-
description: "
|
|
2336
|
+
description: "Show command output and file edits as cards instead of one line.",
|
|
1472
2337
|
kind: "boolean",
|
|
1473
2338
|
default: true,
|
|
1474
2339
|
group: "Transcript"
|
|
@@ -1476,7 +2341,7 @@ var DEFS = [
|
|
|
1476
2341
|
{
|
|
1477
2342
|
key: "transcriptDetail",
|
|
1478
2343
|
label: "Transcript detail",
|
|
1479
|
-
description: "Fold tool and reasoning steps.
|
|
2344
|
+
description: "Fold tool and reasoning steps. Click a card to see more.",
|
|
1480
2345
|
kind: "enum",
|
|
1481
2346
|
default: "collapsed",
|
|
1482
2347
|
choices: ["collapsed", "expanded"],
|
|
@@ -1485,7 +2350,7 @@ var DEFS = [
|
|
|
1485
2350
|
{
|
|
1486
2351
|
key: "autoCompaction",
|
|
1487
2352
|
label: "Auto-compaction",
|
|
1488
|
-
description: "
|
|
2353
|
+
description: "Summarise older turns when the context window gets full.",
|
|
1489
2354
|
kind: "boolean",
|
|
1490
2355
|
default: true,
|
|
1491
2356
|
group: "Context"
|
|
@@ -1502,7 +2367,7 @@ var DEFS = [
|
|
|
1502
2367
|
{
|
|
1503
2368
|
key: "theme",
|
|
1504
2369
|
label: "Theme",
|
|
1505
|
-
description: "Colour
|
|
2370
|
+
description: "Colour theme. Contrast, Mono Dim and ANSI 16 suit 16-colour terminals. Add your own in ~/.0/themes.",
|
|
1506
2371
|
kind: "enum",
|
|
1507
2372
|
default: DEFAULT_THEME_NAME,
|
|
1508
2373
|
choices: THEME_CHOICES,
|
|
@@ -1511,7 +2376,7 @@ var DEFS = [
|
|
|
1511
2376
|
{
|
|
1512
2377
|
key: "allowModelSelfExtension",
|
|
1513
2378
|
label: "Model self-extension",
|
|
1514
|
-
description: "
|
|
2379
|
+
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
2380
|
kind: "boolean",
|
|
1516
2381
|
default: DEFAULT_ALLOW_MODEL_SELF_EXTENSION,
|
|
1517
2382
|
group: "Security"
|
|
@@ -1519,7 +2384,7 @@ var DEFS = [
|
|
|
1519
2384
|
{
|
|
1520
2385
|
key: "allowDevSourceUpdates",
|
|
1521
2386
|
label: "Development engine updates",
|
|
1522
|
-
description: "0dev only. Reload changed
|
|
2387
|
+
description: "0dev only. Reload changed engine code between turns. Runs with full host access, including your credentials; separate from sandboxed tools.",
|
|
1523
2388
|
kind: "boolean",
|
|
1524
2389
|
default: false,
|
|
1525
2390
|
group: "Security"
|
|
@@ -1527,7 +2392,7 @@ var DEFS = [
|
|
|
1527
2392
|
{
|
|
1528
2393
|
key: "autoEvolveFinderLenses",
|
|
1529
2394
|
label: "Auto-evolve finder lenses",
|
|
1530
|
-
description: "
|
|
2395
|
+
description: "Watch ~/.0/lens-synthesis/miss-input.json (or OSEC_TUI_LENS_SYNTH_INPUT) and let the model draft new finder lenses.",
|
|
1531
2396
|
kind: "boolean",
|
|
1532
2397
|
default: false,
|
|
1533
2398
|
group: "Security"
|
|
@@ -1535,7 +2400,7 @@ var DEFS = [
|
|
|
1535
2400
|
{
|
|
1536
2401
|
key: "autoPromoteFinderLenses",
|
|
1537
2402
|
label: "Auto-promote validated lenses",
|
|
1538
|
-
description: "
|
|
2403
|
+
description: "Save a drafted lens only after it passes its positive and negative test cases. Otherwise evaluation is dry-run.",
|
|
1539
2404
|
kind: "boolean",
|
|
1540
2405
|
default: false,
|
|
1541
2406
|
group: "Security"
|
|
@@ -1576,7 +2441,7 @@ var DEFS = [
|
|
|
1576
2441
|
{
|
|
1577
2442
|
key: "elapsedTimer",
|
|
1578
2443
|
label: "Elapsed timer",
|
|
1579
|
-
description: "Show
|
|
2444
|
+
description: "Show how long the current turn has been running.",
|
|
1580
2445
|
kind: "enum",
|
|
1581
2446
|
default: "left",
|
|
1582
2447
|
choices: ["left", "off"],
|
|
@@ -1585,7 +2450,7 @@ var DEFS = [
|
|
|
1585
2450
|
{
|
|
1586
2451
|
key: "logoAnimation",
|
|
1587
2452
|
label: "Logo animation",
|
|
1588
|
-
description:
|
|
2453
|
+
description: "Intro animation for the 0 logo, or off.",
|
|
1589
2454
|
kind: "enum",
|
|
1590
2455
|
default: "glitch",
|
|
1591
2456
|
choices: [
|
|
@@ -1609,7 +2474,7 @@ var DEFS = [
|
|
|
1609
2474
|
{
|
|
1610
2475
|
key: "reduceMotion",
|
|
1611
2476
|
label: "Reduce motion",
|
|
1612
|
-
description: "
|
|
2477
|
+
description: "Turn off decorative animations (logo intro, shimmers, sweeps).",
|
|
1613
2478
|
kind: "boolean",
|
|
1614
2479
|
default: false,
|
|
1615
2480
|
group: "Motion"
|
|
@@ -1617,16 +2482,16 @@ var DEFS = [
|
|
|
1617
2482
|
{
|
|
1618
2483
|
key: "analyticsLevel",
|
|
1619
2484
|
label: "Data sharing",
|
|
1620
|
-
description: "Usage
|
|
2485
|
+
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
2486
|
kind: "enum",
|
|
1622
2487
|
default: "off",
|
|
1623
|
-
choices: ["off", "usage"
|
|
2488
|
+
choices: ["off", "usage"],
|
|
1624
2489
|
group: "Privacy"
|
|
1625
2490
|
},
|
|
1626
2491
|
{
|
|
1627
2492
|
key: "diagnosticReporting",
|
|
1628
2493
|
label: "Problem reports",
|
|
1629
|
-
description: "Review
|
|
2494
|
+
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
2495
|
kind: "enum",
|
|
1631
2496
|
default: "ask",
|
|
1632
2497
|
choices: ["off", "ask", "automatic"],
|
|
@@ -1635,7 +2500,7 @@ var DEFS = [
|
|
|
1635
2500
|
{
|
|
1636
2501
|
key: "updatePolicy",
|
|
1637
2502
|
label: "Updates",
|
|
1638
|
-
description: "Off, notify about releases, or install updates
|
|
2503
|
+
description: "Off, notify about new releases, or install updates at startup. Applies to this computer; project settings can't turn on installing.",
|
|
1639
2504
|
kind: "enum",
|
|
1640
2505
|
default: "automatic",
|
|
1641
2506
|
choices: ["off", "notify", "automatic"],
|
|
@@ -1644,7 +2509,7 @@ var DEFS = [
|
|
|
1644
2509
|
{
|
|
1645
2510
|
key: "executionProfile",
|
|
1646
2511
|
label: "Execution profile",
|
|
1647
|
-
description: "SmolVM runs
|
|
2512
|
+
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
2513
|
kind: "enum",
|
|
1649
2514
|
default: "local",
|
|
1650
2515
|
choices: ["local", "smolvm"],
|
|
@@ -1653,7 +2518,7 @@ var DEFS = [
|
|
|
1653
2518
|
{
|
|
1654
2519
|
key: "symbolPreset",
|
|
1655
2520
|
label: "Symbols",
|
|
1656
|
-
description: "
|
|
2521
|
+
description: "Icon set: Unicode (default), Nerd Font (needs a Nerd Font terminal) or ASCII.",
|
|
1657
2522
|
kind: "enum",
|
|
1658
2523
|
default: "unicode",
|
|
1659
2524
|
choices: ["unicode", "nerd", "ascii"],
|
|
@@ -1662,7 +2527,7 @@ var DEFS = [
|
|
|
1662
2527
|
{
|
|
1663
2528
|
key: "rosterSort",
|
|
1664
2529
|
label: "Roster order",
|
|
1665
|
-
description: "
|
|
2530
|
+
description: "Sort agents by attention (blocked and working first, default) or by status.",
|
|
1666
2531
|
kind: "enum",
|
|
1667
2532
|
default: "attention",
|
|
1668
2533
|
choices: ["attention", "status"],
|
|
@@ -1671,7 +2536,7 @@ var DEFS = [
|
|
|
1671
2536
|
{
|
|
1672
2537
|
key: "leaderKey",
|
|
1673
2538
|
label: "Leader key",
|
|
1674
|
-
description: "Optional
|
|
2539
|
+
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
2540
|
kind: "enum",
|
|
1676
2541
|
default: "off",
|
|
1677
2542
|
choices: ["off", "ctrl+a", "ctrl+b", "ctrl+space"],
|
|
@@ -1706,1068 +2571,246 @@ var DEFAULT_SETTINGS = {
|
|
|
1706
2571
|
transcriptDetail: "collapsed",
|
|
1707
2572
|
autoCompaction: true,
|
|
1708
2573
|
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
|
-
});
|
|
2574
|
+
theme: DEFAULT_THEME_NAME,
|
|
2575
|
+
allowModelSelfExtension: DEFAULT_ALLOW_MODEL_SELF_EXTENSION,
|
|
2576
|
+
allowDevSourceUpdates: false,
|
|
2577
|
+
autoEvolveFinderLenses: false,
|
|
2578
|
+
autoPromoteFinderLenses: false,
|
|
2579
|
+
showTokenUsage: false,
|
|
2580
|
+
showCost: false,
|
|
2581
|
+
showContextMeter: true,
|
|
2582
|
+
modelDisplay: "statusbar",
|
|
2583
|
+
elapsedTimer: "left",
|
|
2584
|
+
logoAnimation: "glitch",
|
|
2585
|
+
reduceMotion: false,
|
|
2586
|
+
analyticsLevel: "off",
|
|
2587
|
+
diagnosticReporting: "ask",
|
|
2588
|
+
diagnosticReportingPrompted: false,
|
|
2589
|
+
updatePolicy: "automatic",
|
|
2590
|
+
symbolPreset: "unicode",
|
|
2591
|
+
rosterSort: "attention",
|
|
2592
|
+
leaderKey: "off",
|
|
2593
|
+
keybindings: {},
|
|
2594
|
+
modelPreference: null
|
|
2595
|
+
};
|
|
2596
|
+
var SETTINGS_FILENAME = "tui-settings.json";
|
|
2597
|
+
function settingsFilePath(homeDir) {
|
|
2598
|
+
return join6(homeStateDir(homeDir), SETTINGS_FILENAME);
|
|
2493
2599
|
}
|
|
2494
|
-
function
|
|
2495
|
-
|
|
2496
|
-
return info !== void 0 && satisfyingVar(info, env) !== void 0;
|
|
2600
|
+
function projectStateDir(projectDir = process.cwd()) {
|
|
2601
|
+
return join6(projectDir, ".0");
|
|
2497
2602
|
}
|
|
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;
|
|
2603
|
+
function projectSettingsFilePath(projectDir = process.cwd()) {
|
|
2604
|
+
return join6(projectStateDir(projectDir), SETTINGS_FILENAME);
|
|
2513
2605
|
}
|
|
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;
|
|
2606
|
+
function readRawSettingsFile(path) {
|
|
2607
|
+
try {
|
|
2608
|
+
return JSON.parse(readFileSync2(path, "utf8"));
|
|
2609
|
+
} catch {
|
|
2610
|
+
return void 0;
|
|
2523
2611
|
}
|
|
2524
|
-
return out;
|
|
2525
2612
|
}
|
|
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);
|
|
2613
|
+
function projectSettingsExist(projectDir = process.cwd()) {
|
|
2614
|
+
const raw = readRawSettingsFile(projectSettingsFilePath(projectDir));
|
|
2615
|
+
return typeof raw === "object" && raw !== null && !Array.isArray(raw);
|
|
2533
2616
|
}
|
|
2534
|
-
function
|
|
2535
|
-
const
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2617
|
+
function strictValueAt(raw, key) {
|
|
2618
|
+
const value = rawValue(raw, key);
|
|
2619
|
+
if (value === void 0) return void 0;
|
|
2620
|
+
if (key === "onboardingCompleted" || key === "diagnosticReportingPrompted") {
|
|
2621
|
+
return typeof value === "boolean" ? value : void 0;
|
|
2539
2622
|
}
|
|
2540
|
-
|
|
2623
|
+
if (value === false && (key === "diagnosticReporting" || key === "updatePolicy")) {
|
|
2624
|
+
return "off";
|
|
2625
|
+
}
|
|
2626
|
+
if (key === "theme") {
|
|
2627
|
+
return typeof value === "string" && isKnownTheme(value) ? value : void 0;
|
|
2628
|
+
}
|
|
2629
|
+
if (key === "keybindings") {
|
|
2630
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? sanitizeKeybindingOverrides(value) : void 0;
|
|
2631
|
+
}
|
|
2632
|
+
if (key === "modelPreference") {
|
|
2633
|
+
return normalizeModelPreference(value);
|
|
2634
|
+
}
|
|
2635
|
+
const def = DEF_BY_KEY.get(key);
|
|
2636
|
+
if (def?.kind === "boolean") {
|
|
2637
|
+
return typeof value === "boolean" ? value : void 0;
|
|
2638
|
+
}
|
|
2639
|
+
const choices = def?.choices ?? [];
|
|
2640
|
+
return typeof value === "string" && choices.includes(value) ? value : void 0;
|
|
2641
|
+
}
|
|
2642
|
+
function isOperatorSetting(key) {
|
|
2643
|
+
return key === "onboardingCompleted" || key === "analyticsLevel" || key === "diagnosticReporting" || key === "diagnosticReportingPrompted" || key === "updatePolicy" || key === "allowDevSourceUpdates" || key === "executionProfile" || key === "modelPreference";
|
|
2541
2644
|
}
|
|
2542
|
-
function
|
|
2543
|
-
const
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2645
|
+
function resolveLayeredSettings(globalRaw, projectRaw) {
|
|
2646
|
+
const settings = {};
|
|
2647
|
+
const sources = {};
|
|
2648
|
+
for (const key of Object.keys(DEFAULT_SETTINGS)) {
|
|
2649
|
+
const projectValue = isOperatorSetting(key) ? void 0 : strictValueAt(projectRaw, key);
|
|
2650
|
+
if (projectValue !== void 0) {
|
|
2651
|
+
settings[key] = projectValue;
|
|
2652
|
+
sources[key] = "project";
|
|
2653
|
+
continue;
|
|
2548
2654
|
}
|
|
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;
|
|
2655
|
+
const globalValue = strictValueAt(globalRaw, key);
|
|
2656
|
+
if (globalValue !== void 0) {
|
|
2657
|
+
settings[key] = globalValue;
|
|
2658
|
+
sources[key] = "global";
|
|
2659
|
+
continue;
|
|
2670
2660
|
}
|
|
2671
|
-
|
|
2661
|
+
settings[key] = DEFAULT_SETTINGS[key];
|
|
2662
|
+
sources[key] = "default";
|
|
2672
2663
|
}
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2664
|
+
return { settings, sources };
|
|
2665
|
+
}
|
|
2666
|
+
function loadLayeredSettings(opts = {}) {
|
|
2667
|
+
syncThemeChoices(opts.homeDir);
|
|
2668
|
+
const globalRaw = readRawSettingsFile(settingsFilePath(opts.homeDir));
|
|
2669
|
+
const projectRaw = readRawSettingsFile(projectSettingsFilePath(opts.projectDir));
|
|
2670
|
+
return resolveLayeredSettings(globalRaw, projectRaw);
|
|
2671
|
+
}
|
|
2672
|
+
function rawValue(raw, key) {
|
|
2673
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return void 0;
|
|
2674
|
+
const value = raw[key];
|
|
2675
|
+
if (key === "analyticsLevel" && value !== void 0) {
|
|
2676
|
+
return value === "usage" || value === "commands" || value === "full" ? "usage" : "off";
|
|
2678
2677
|
}
|
|
2679
|
-
return
|
|
2678
|
+
return key === "transcriptStyle" && value === "messenger" ? "bubble" : value;
|
|
2680
2679
|
}
|
|
2681
|
-
function
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2680
|
+
function booleanAt(raw, key) {
|
|
2681
|
+
const value = rawValue(raw, key);
|
|
2682
|
+
return typeof value === "boolean" ? value : DEFAULT_SETTINGS[key];
|
|
2683
|
+
}
|
|
2684
|
+
function enumAt(raw, key) {
|
|
2685
|
+
const def = DEF_BY_KEY.get(key);
|
|
2686
|
+
const value = rawValue(raw, key);
|
|
2687
|
+
const choices = def?.choices ?? [];
|
|
2688
|
+
return typeof value === "string" && choices.includes(value) ? value : DEFAULT_SETTINGS[key];
|
|
2689
|
+
}
|
|
2690
|
+
function themeAt(raw) {
|
|
2691
|
+
const value = rawValue(raw, "theme");
|
|
2692
|
+
return typeof value === "string" && isKnownTheme(value) ? value : DEFAULT_SETTINGS.theme;
|
|
2693
|
+
}
|
|
2694
|
+
function keybindingsAt(raw) {
|
|
2695
|
+
return sanitizeKeybindingOverrides(rawValue(raw, "keybindings"));
|
|
2696
|
+
}
|
|
2697
|
+
function normalizeModelPreference(raw) {
|
|
2698
|
+
if (raw === null) return null;
|
|
2699
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return void 0;
|
|
2700
|
+
const value = raw;
|
|
2701
|
+
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;
|
|
2702
|
+
return { providerId: value.providerId, model: value.model, connectionIdentity: value.connectionIdentity };
|
|
2703
|
+
}
|
|
2704
|
+
function normalizeSettings(raw) {
|
|
2705
|
+
return {
|
|
2706
|
+
showStatusBar: booleanAt(raw, "showStatusBar"),
|
|
2707
|
+
showComposerHints: booleanAt(raw, "showComposerHints"),
|
|
2708
|
+
composerSuggestions: booleanAt(raw, "composerSuggestions"),
|
|
2709
|
+
mouseSupport: booleanAt(raw, "mouseSupport"),
|
|
2710
|
+
showLogo: booleanAt(raw, "showLogo"),
|
|
2711
|
+
showRuntimeNotices: booleanAt(raw, "showRuntimeNotices"),
|
|
2712
|
+
showTurnSummary: booleanAt(raw, "showTurnSummary"),
|
|
2713
|
+
showSubagents: booleanAt(raw, "showSubagents"),
|
|
2714
|
+
showTimestamps: booleanAt(raw, "showTimestamps"),
|
|
2715
|
+
showObjective: booleanAt(raw, "showObjective"),
|
|
2716
|
+
showScope: booleanAt(raw, "showScope"),
|
|
2717
|
+
density: enumAt(raw, "density"),
|
|
2718
|
+
composerStyle: enumAt(raw, "composerStyle"),
|
|
2719
|
+
busyInputMode: enumAt(raw, "busyInputMode"),
|
|
2720
|
+
onboardingCompleted: booleanAt(raw, "onboardingCompleted"),
|
|
2721
|
+
executionProfile: enumAt(raw, "executionProfile"),
|
|
2722
|
+
allowSubagentPeerMessaging: booleanAt(raw, "allowSubagentPeerMessaging"),
|
|
2723
|
+
allowSubagentOperatorMessaging: booleanAt(raw, "allowSubagentOperatorMessaging"),
|
|
2724
|
+
transcriptStyle: enumAt(raw, "transcriptStyle"),
|
|
2725
|
+
roleLabelStyle: enumAt(raw, "roleLabelStyle"),
|
|
2726
|
+
toolCardStyle: enumAt(raw, "toolCardStyle"),
|
|
2727
|
+
richToolCards: booleanAt(raw, "richToolCards"),
|
|
2728
|
+
transcriptDetail: enumAt(raw, "transcriptDetail"),
|
|
2729
|
+
autoCompaction: booleanAt(raw, "autoCompaction"),
|
|
2730
|
+
compactionThreshold: enumAt(raw, "compactionThreshold"),
|
|
2731
|
+
theme: themeAt(raw),
|
|
2732
|
+
allowModelSelfExtension: booleanAt(raw, "allowModelSelfExtension"),
|
|
2733
|
+
allowDevSourceUpdates: booleanAt(raw, "allowDevSourceUpdates"),
|
|
2734
|
+
autoEvolveFinderLenses: booleanAt(raw, "autoEvolveFinderLenses"),
|
|
2735
|
+
autoPromoteFinderLenses: booleanAt(raw, "autoPromoteFinderLenses"),
|
|
2736
|
+
showTokenUsage: booleanAt(raw, "showTokenUsage"),
|
|
2737
|
+
showCost: booleanAt(raw, "showCost"),
|
|
2738
|
+
showContextMeter: booleanAt(raw, "showContextMeter"),
|
|
2739
|
+
modelDisplay: enumAt(raw, "modelDisplay"),
|
|
2740
|
+
elapsedTimer: enumAt(raw, "elapsedTimer"),
|
|
2741
|
+
logoAnimation: enumAt(raw, "logoAnimation"),
|
|
2742
|
+
reduceMotion: booleanAt(raw, "reduceMotion"),
|
|
2743
|
+
analyticsLevel: enumAt(raw, "analyticsLevel"),
|
|
2744
|
+
diagnosticReporting: strictValueAt(raw, "diagnosticReporting") ?? DEFAULT_SETTINGS.diagnosticReporting,
|
|
2745
|
+
diagnosticReportingPrompted: booleanAt(raw, "diagnosticReportingPrompted"),
|
|
2746
|
+
updatePolicy: strictValueAt(raw, "updatePolicy") ?? DEFAULT_SETTINGS.updatePolicy,
|
|
2747
|
+
symbolPreset: enumAt(raw, "symbolPreset"),
|
|
2748
|
+
rosterSort: enumAt(raw, "rosterSort"),
|
|
2749
|
+
leaderKey: enumAt(raw, "leaderKey"),
|
|
2750
|
+
keybindings: keybindingsAt(raw),
|
|
2751
|
+
modelPreference: normalizeModelPreference(rawValue(raw, "modelPreference")) ?? null
|
|
2752
|
+
};
|
|
2753
|
+
}
|
|
2754
|
+
function loadSettings(homeDir, projectDir) {
|
|
2755
|
+
return loadLayeredSettings({ homeDir, projectDir }).settings;
|
|
2756
|
+
}
|
|
2757
|
+
function loadGlobalSettings(homeDir, options = {}) {
|
|
2758
|
+
syncThemeChoices(homeDir);
|
|
2759
|
+
const raw = readRawSettingsFile(settingsFilePath(homeDir));
|
|
2760
|
+
if (options.requireExecutionProfile && strictValueAt(raw, "executionProfile") === void 0) {
|
|
2761
|
+
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
2762
|
}
|
|
2763
|
+
return normalizeSettings(raw);
|
|
2688
2764
|
}
|
|
2689
|
-
function
|
|
2765
|
+
function saveSettings(settings, homeDir) {
|
|
2766
|
+
const path = settingsFilePath(homeDir);
|
|
2767
|
+
const temporary = `${path}.${randomUUID2()}.tmp`;
|
|
2690
2768
|
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);
|
|
2769
|
+
mkdirSync2(dirname3(path), { recursive: true });
|
|
2770
|
+
writeFileSync2(temporary, `${JSON.stringify(normalizeSettings(settings), null, 2)}
|
|
2771
|
+
`, { encoding: "utf8", flag: "wx", mode: 384 });
|
|
2772
|
+
renameSync(temporary, path);
|
|
2701
2773
|
return true;
|
|
2702
2774
|
} catch {
|
|
2703
2775
|
return false;
|
|
2776
|
+
} finally {
|
|
2777
|
+
try {
|
|
2778
|
+
rmSync2(temporary, { force: true });
|
|
2779
|
+
} catch {
|
|
2780
|
+
}
|
|
2704
2781
|
}
|
|
2705
2782
|
}
|
|
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
|
-
};
|
|
2783
|
+
function sanitizeOverrides(raw) {
|
|
2784
|
+
const out = {};
|
|
2785
|
+
for (const key of Object.keys(DEFAULT_SETTINGS)) {
|
|
2786
|
+
if (isOperatorSetting(key)) continue;
|
|
2787
|
+
const value = strictValueAt(raw, key);
|
|
2788
|
+
if (value !== void 0) out[key] = value;
|
|
2789
|
+
}
|
|
2790
|
+
return out;
|
|
2740
2791
|
}
|
|
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
|
-
}
|
|
2792
|
+
function readProjectOverrides(projectDir) {
|
|
2793
|
+
return sanitizeOverrides(readRawSettingsFile(projectSettingsFilePath(projectDir)));
|
|
2794
|
+
}
|
|
2795
|
+
function saveProjectOverrides(patch, projectDir) {
|
|
2796
|
+
try {
|
|
2797
|
+
const path = projectSettingsFilePath(projectDir);
|
|
2798
|
+
mkdirSync2(dirname3(path), { recursive: true });
|
|
2799
|
+
writeFileSync2(path, `${JSON.stringify(sanitizeOverrides(patch), null, 2)}
|
|
2800
|
+
`, "utf8");
|
|
2801
|
+
return true;
|
|
2802
|
+
} catch {
|
|
2803
|
+
return false;
|
|
2761
2804
|
}
|
|
2762
|
-
return patch;
|
|
2763
2805
|
}
|
|
2764
2806
|
|
|
2765
2807
|
// packages/cli/src/tui/feedback.ts
|
|
2766
|
-
import { appendFileSync, mkdirSync as
|
|
2767
|
-
import {
|
|
2768
|
-
import {
|
|
2769
|
-
|
|
2770
|
-
|
|
2808
|
+
import { appendFileSync, mkdirSync as mkdirSync3 } from "node:fs";
|
|
2809
|
+
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
2810
|
+
import { dirname as dirname4, join as join7 } from "node:path";
|
|
2811
|
+
import { homedir } from "node:os";
|
|
2812
|
+
function feedbackFilePath(homeDir) {
|
|
2813
|
+
return join7(homeDir ?? homedir(), ".0", "feedback.md");
|
|
2771
2814
|
}
|
|
2772
2815
|
function formatFeedbackEntry(entry) {
|
|
2773
2816
|
const context = [
|
|
@@ -2781,10 +2824,10 @@ function formatFeedbackEntry(entry) {
|
|
|
2781
2824
|
return `${lines.join("\n")}
|
|
2782
2825
|
`;
|
|
2783
2826
|
}
|
|
2784
|
-
function appendFeedback(entry,
|
|
2785
|
-
const path = feedbackFilePath(
|
|
2827
|
+
function appendFeedback(entry, homeDir) {
|
|
2828
|
+
const path = feedbackFilePath(homeDir);
|
|
2786
2829
|
try {
|
|
2787
|
-
|
|
2830
|
+
mkdirSync3(dirname4(path), { recursive: true });
|
|
2788
2831
|
appendFileSync(path, formatFeedbackEntry(entry), "utf8");
|
|
2789
2832
|
return { ok: true, path };
|
|
2790
2833
|
} catch (error) {
|
|
@@ -2808,6 +2851,7 @@ function parseFeedbackCommand(raw) {
|
|
|
2808
2851
|
}
|
|
2809
2852
|
var DEFAULT_FEEDBACK_URL = "";
|
|
2810
2853
|
var FEEDBACK_URL_ENV = "ZERO_FEEDBACK_URL";
|
|
2854
|
+
var SENTRY_DSN_ENV = "ZERO_SENTRY_DSN";
|
|
2811
2855
|
var CLOUD_FEEDBACK_PATH = "/api/cli-feedback";
|
|
2812
2856
|
function defaultCloudCredentials(env) {
|
|
2813
2857
|
try {
|
|
@@ -2833,6 +2877,24 @@ function cloudFeedbackUrl(host) {
|
|
|
2833
2877
|
return null;
|
|
2834
2878
|
}
|
|
2835
2879
|
}
|
|
2880
|
+
function resolveSentryTarget(dsn) {
|
|
2881
|
+
try {
|
|
2882
|
+
const parsed = new URL(dsn);
|
|
2883
|
+
const key = parsed.username;
|
|
2884
|
+
const segments = parsed.pathname.split("/").filter(Boolean);
|
|
2885
|
+
const project = segments.pop();
|
|
2886
|
+
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: "" };
|
|
2887
|
+
parsed.username = "";
|
|
2888
|
+
parsed.pathname = `/${[...segments, "api", project, "envelope"].join("/")}/`;
|
|
2889
|
+
return { url: parsed.toString(), sentryKey: key };
|
|
2890
|
+
} catch {
|
|
2891
|
+
return { url: "" };
|
|
2892
|
+
}
|
|
2893
|
+
}
|
|
2894
|
+
function resolveSubmissionTarget(payload, env, options) {
|
|
2895
|
+
const dsn = env[SENTRY_DSN_ENV]?.trim();
|
|
2896
|
+
return payload.diagnostic && dsn ? resolveSentryTarget(dsn) : resolveFeedbackTarget(env, options);
|
|
2897
|
+
}
|
|
2836
2898
|
function resolveFeedbackTarget(env = process.env, options = {}) {
|
|
2837
2899
|
const configured = env[FEEDBACK_URL_ENV]?.trim();
|
|
2838
2900
|
if (configured) return { url: configured };
|
|
@@ -2852,11 +2914,11 @@ function isOptOutSet(value) {
|
|
|
2852
2914
|
if (normalized === "") return false;
|
|
2853
2915
|
return normalized !== "0" && normalized !== "false" && normalized !== "no";
|
|
2854
2916
|
}
|
|
2855
|
-
function submissionBlockedReason(env = process.env, options = {}) {
|
|
2917
|
+
function submissionBlockedReason(env = process.env, options = {}, payload) {
|
|
2856
2918
|
for (const name of FEEDBACK_OPT_OUT_ENV) {
|
|
2857
2919
|
if (isOptOutSet(env[name])) return "opt-out";
|
|
2858
2920
|
}
|
|
2859
|
-
return targetBlockedReason(resolveFeedbackTarget(env, options));
|
|
2921
|
+
return targetBlockedReason(payload ? resolveSubmissionTarget(payload, env, options) : resolveFeedbackTarget(env, options));
|
|
2860
2922
|
}
|
|
2861
2923
|
function targetBlockedReason(target) {
|
|
2862
2924
|
if (target === null) return "no-endpoint";
|
|
@@ -2866,7 +2928,7 @@ function targetBlockedReason(target) {
|
|
|
2866
2928
|
} catch {
|
|
2867
2929
|
return "insecure-endpoint";
|
|
2868
2930
|
}
|
|
2869
|
-
if (parsed.protocol !== "https:") return "insecure-endpoint";
|
|
2931
|
+
if (parsed.protocol !== "https:" || parsed.username || parsed.password || parsed.hash) return "insecure-endpoint";
|
|
2870
2932
|
return null;
|
|
2871
2933
|
}
|
|
2872
2934
|
function describeSkip(reason) {
|
|
@@ -2876,7 +2938,9 @@ function describeSkip(reason) {
|
|
|
2876
2938
|
case "no-endpoint":
|
|
2877
2939
|
return `No feedback endpoint configured (set ${FEEDBACK_URL_ENV}). Saved locally only.`;
|
|
2878
2940
|
case "insecure-endpoint":
|
|
2879
|
-
return
|
|
2941
|
+
return "Refusing an invalid or non-HTTPS telemetry endpoint. Saved locally only.";
|
|
2942
|
+
case "no-consent":
|
|
2943
|
+
return "Problem report requires confirmation or an automatic-reporting preference. Saved locally only.";
|
|
2880
2944
|
}
|
|
2881
2945
|
}
|
|
2882
2946
|
var SECRET_RULES = [
|
|
@@ -2924,30 +2988,38 @@ function serializePayload(payload) {
|
|
|
2924
2988
|
}
|
|
2925
2989
|
function requestHeaders(target, redactAuthorization = false) {
|
|
2926
2990
|
const headers = { "content-type": "application/json" };
|
|
2991
|
+
if (target.sentryKey) {
|
|
2992
|
+
headers["content-type"] = "application/x-sentry-envelope";
|
|
2993
|
+
headers["x-sentry-auth"] = `Sentry sentry_version=7,sentry_key=${redactAuthorization ? "<redacted>" : target.sentryKey},sentry_client=0-cli/${VERSION}`;
|
|
2994
|
+
}
|
|
2927
2995
|
if (target.authorization) {
|
|
2928
2996
|
headers.authorization = redactAuthorization ? "Bearer <redacted>" : target.authorization;
|
|
2929
2997
|
}
|
|
2930
2998
|
return headers;
|
|
2931
2999
|
}
|
|
2932
3000
|
function buildSubmitPreview(payload, env = process.env, options = {}) {
|
|
2933
|
-
if (
|
|
2934
|
-
const target =
|
|
2935
|
-
if (target === null) return null;
|
|
3001
|
+
if (FEEDBACK_OPT_OUT_ENV.some((name) => isOptOutSet(env[name]))) return null;
|
|
3002
|
+
const target = resolveSubmissionTarget(payload, env, options);
|
|
3003
|
+
if (target === null || targetBlockedReason(target) !== null) return null;
|
|
2936
3004
|
return {
|
|
2937
3005
|
url: target.url,
|
|
2938
|
-
body:
|
|
3006
|
+
body: serializeSubmission(payload, target, env),
|
|
2939
3007
|
headers: requestHeaders(target, true),
|
|
2940
|
-
warnings: scanForSecrets(payload.message)
|
|
3008
|
+
warnings: target.sentryKey ? [] : scanForSecrets(payload.message)
|
|
2941
3009
|
};
|
|
2942
3010
|
}
|
|
2943
3011
|
async function submitFeedback(payload, env = process.env, opts = {}) {
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
3012
|
+
if (FEEDBACK_OPT_OUT_ENV.some((name) => isOptOutSet(env[name]))) {
|
|
3013
|
+
return { ok: false, skipped: "opt-out", error: describeSkip("opt-out") };
|
|
3014
|
+
}
|
|
3015
|
+
if (payload.diagnostic && opts.diagnosticConsent?.confirmed !== true && opts.diagnosticConsent?.policy !== "automatic") {
|
|
3016
|
+
return { ok: false, skipped: "no-consent", error: describeSkip("no-consent") };
|
|
3017
|
+
}
|
|
3018
|
+
const target = resolveSubmissionTarget(payload, env, opts);
|
|
2947
3019
|
const targetBlocked = targetBlockedReason(target);
|
|
2948
3020
|
if (targetBlocked !== null) return { ok: false, skipped: targetBlocked, error: describeSkip(targetBlocked) };
|
|
2949
3021
|
if (target === null) return { ok: false, skipped: "no-endpoint", error: describeSkip("no-endpoint") };
|
|
2950
|
-
const body =
|
|
3022
|
+
const body = serializeSubmission(payload, target, env);
|
|
2951
3023
|
if (Buffer.byteLength(body, "utf8") > MAX_BODY_BYTES) {
|
|
2952
3024
|
return { ok: false, error: `Message too large to submit (limit ${MAX_BODY_BYTES} bytes).` };
|
|
2953
3025
|
}
|
|
@@ -2957,7 +3029,7 @@ async function submitFeedback(payload, env = process.env, opts = {}) {
|
|
|
2957
3029
|
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
3030
|
return {
|
|
2959
3031
|
ok: false,
|
|
2960
|
-
error: "Reviewed
|
|
3032
|
+
error: "Reviewed telemetry destination has changed. Preview the updated submission before sending again."
|
|
2961
3033
|
};
|
|
2962
3034
|
}
|
|
2963
3035
|
}
|
|
@@ -2965,10 +3037,10 @@ async function submitFeedback(payload, env = process.env, opts = {}) {
|
|
|
2965
3037
|
const timeoutMs = opts.timeoutMs ?? FEEDBACK_TIMEOUT_MS;
|
|
2966
3038
|
const controller = new AbortController();
|
|
2967
3039
|
let timer;
|
|
2968
|
-
const timeout = new Promise((
|
|
3040
|
+
const timeout = new Promise((resolve2) => {
|
|
2969
3041
|
timer = setTimeout(() => {
|
|
2970
3042
|
controller.abort();
|
|
2971
|
-
|
|
3043
|
+
resolve2({ ok: false, error: `Feedback submission timed out after ${timeoutMs}ms.` });
|
|
2972
3044
|
}, timeoutMs);
|
|
2973
3045
|
timer.unref?.();
|
|
2974
3046
|
});
|
|
@@ -2982,8 +3054,8 @@ async function submitFeedback(payload, env = process.env, opts = {}) {
|
|
|
2982
3054
|
redirect: "error"
|
|
2983
3055
|
});
|
|
2984
3056
|
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:
|
|
3057
|
+
} catch {
|
|
3058
|
+
return { ok: false, error: "Feedback submission failed." };
|
|
2987
3059
|
}
|
|
2988
3060
|
})();
|
|
2989
3061
|
try {
|
|
@@ -3031,7 +3103,7 @@ function diagnosticVersion(raw) {
|
|
|
3031
3103
|
if (typeof raw !== "string" || raw.length > 128) return "unknown";
|
|
3032
3104
|
return /^v?(\d{1,6}\.\d{1,6}\.\d{1,6})(?:[-+][a-zA-Z0-9.+-]+)?$/.exec(raw)?.[1] ?? "unknown";
|
|
3033
3105
|
}
|
|
3034
|
-
function buildDiagnosticFeedback(info
|
|
3106
|
+
function buildDiagnosticFeedback(info) {
|
|
3035
3107
|
const kind = info.kind === "tool" ? "tool" : info.kind === "runtime" ? "runtime" : "unknown";
|
|
3036
3108
|
const platform = DIAGNOSTIC_PLATFORMS.has(info.platform) ? info.platform : "unknown";
|
|
3037
3109
|
const arch = DIAGNOSTIC_ARCHS.has(info.arch) ? info.arch : "unknown";
|
|
@@ -3039,90 +3111,251 @@ function buildDiagnosticFeedback(info, env = process.env) {
|
|
|
3039
3111
|
const runtimeVersion = diagnosticVersion(info.runtimeVersion);
|
|
3040
3112
|
const version = diagnosticVersion(info.version);
|
|
3041
3113
|
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
|
-
|
|
3114
|
+
const category = diagnosticError(info.error);
|
|
3115
|
+
const message = `Diagnostic: ${kind} error \u2014 ${platform}/${arch} on ${runtime} ${runtimeVersion}
|
|
3043
3116
|
Version: ${version}
|
|
3044
|
-
Error: ${
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3117
|
+
Error: ${category}`;
|
|
3118
|
+
return {
|
|
3119
|
+
message,
|
|
3120
|
+
timestamp,
|
|
3121
|
+
version,
|
|
3122
|
+
diagnostic: {
|
|
3123
|
+
eventId: randomUUID3().replaceAll("-", ""),
|
|
3124
|
+
kind,
|
|
3125
|
+
category,
|
|
3126
|
+
platform,
|
|
3127
|
+
arch,
|
|
3128
|
+
runtime,
|
|
3129
|
+
runtimeVersion,
|
|
3130
|
+
frames: diagnosticFrames(info.error)
|
|
3131
|
+
}
|
|
3132
|
+
};
|
|
3058
3133
|
}
|
|
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 "";
|
|
3134
|
+
function diagnosticFrames(error) {
|
|
3098
3135
|
try {
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3136
|
+
if (!(error instanceof Error) || typeof error.stack !== "string") return [];
|
|
3137
|
+
const frames = [];
|
|
3138
|
+
for (const line of error.stack.slice(0, 16384).split("\n").slice(1, 33)) {
|
|
3139
|
+
const match = /(?:^|[/\\])(packages[/\\](?:cli|core|shared)[/\\](?:src|dist)[/\\][A-Za-z0-9_./\\-]+\.[cm]?[jt]sx?):(\d{1,7}):(\d{1,7})\)?$/.exec(line.trim());
|
|
3140
|
+
if (!match || match[1].split(/[/\\]/).some((part) => part === "..")) continue;
|
|
3141
|
+
const lineno = Number(match[2]);
|
|
3142
|
+
const colno = Number(match[3]);
|
|
3143
|
+
if (lineno > 0 && colno > 0) {
|
|
3144
|
+
frames.push({ filename: `app:///${match[1].replaceAll("\\", "/")}`, lineno, colno });
|
|
3145
|
+
}
|
|
3146
|
+
}
|
|
3147
|
+
return frames.reverse();
|
|
3102
3148
|
} catch {
|
|
3103
|
-
return
|
|
3104
|
-
}
|
|
3149
|
+
return [];
|
|
3150
|
+
}
|
|
3151
|
+
}
|
|
3152
|
+
function serializeSubmission(payload, target, env) {
|
|
3153
|
+
if (!target.sentryKey || !payload.diagnostic) return serializePayload(payload);
|
|
3154
|
+
const diagnostic = payload.diagnostic;
|
|
3155
|
+
const cliVersion = VERSION;
|
|
3156
|
+
const commit = /^[0-9a-f]{40}$/i.test("531d93617a529a487a9d94e6c20886903ffc40ec") ? "531d93617a529a487a9d94e6c20886903ffc40ec".toLowerCase() : void 0;
|
|
3157
|
+
const environment = env["NODE_ENV"] === "development" ? "development" : env["NODE_ENV"] === "production" || true ? "production" : "development";
|
|
3158
|
+
const event = JSON.stringify({
|
|
3159
|
+
event_id: diagnostic.eventId,
|
|
3160
|
+
timestamp: Date.parse(payload.timestamp) / 1e3,
|
|
3161
|
+
platform: "node",
|
|
3162
|
+
level: "error",
|
|
3163
|
+
logger: "0.cli",
|
|
3164
|
+
release: `0-cli@${cliVersion}${commit ? `+${commit}` : ""}`,
|
|
3165
|
+
environment,
|
|
3166
|
+
tags: {
|
|
3167
|
+
cli_version: cliVersion,
|
|
3168
|
+
...commit ? { build_commit: commit } : {},
|
|
3169
|
+
kind: diagnostic.kind,
|
|
3170
|
+
platform: diagnostic.platform,
|
|
3171
|
+
arch: diagnostic.arch,
|
|
3172
|
+
runtime: diagnostic.runtime
|
|
3173
|
+
},
|
|
3174
|
+
contexts: { runtime: { name: diagnostic.runtime, version: diagnostic.runtimeVersion } },
|
|
3175
|
+
exception: {
|
|
3176
|
+
values: [{
|
|
3177
|
+
type: "CLIError",
|
|
3178
|
+
value: diagnostic.category,
|
|
3179
|
+
...diagnostic.frames.length ? { stacktrace: { frames: diagnostic.frames } } : {}
|
|
3180
|
+
}]
|
|
3181
|
+
}
|
|
3182
|
+
});
|
|
3183
|
+
return `${JSON.stringify({ event_id: diagnostic.eventId })}
|
|
3184
|
+
${JSON.stringify({ type: "event", length: Buffer.byteLength(event, "utf8") })}
|
|
3185
|
+
${event}
|
|
3186
|
+
`;
|
|
3105
3187
|
}
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3188
|
+
|
|
3189
|
+
// packages/cli/src/workbench.ts
|
|
3190
|
+
var WORKBENCH_INNER_ARGUMENT = "--workbench-inner";
|
|
3191
|
+
function workbenchConfigPath(home = process.env.HOME || homedir2()) {
|
|
3192
|
+
return join8(homeStateDir(home), "workbench.json");
|
|
3193
|
+
}
|
|
3194
|
+
function defaultWorkbenchStateRoot(home = process.env.HOME || homedir2()) {
|
|
3195
|
+
return join8(homeStateDir(home), "workbench");
|
|
3196
|
+
}
|
|
3197
|
+
function positiveInteger(value, maximum) {
|
|
3198
|
+
return typeof value === "number" && Number.isInteger(value) && value > 0 && value <= maximum;
|
|
3199
|
+
}
|
|
3200
|
+
function isPinnedWorkbenchImageReference(value) {
|
|
3201
|
+
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(":");
|
|
3202
|
+
}
|
|
3203
|
+
function normalizeWorkbenchConfig(raw) {
|
|
3204
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) throw new Error("Invalid workbench operator choices.");
|
|
3205
|
+
const config = raw;
|
|
3206
|
+
if (config.github === true) throw new Error("GitHub credential forwarding is not supported by the isolated workbench. Revoke the GitHub grant before launch.");
|
|
3207
|
+
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.");
|
|
3208
|
+
const approvedImages = [];
|
|
3209
|
+
if (config.approvedImages !== void 0) {
|
|
3210
|
+
if (!Array.isArray(config.approvedImages)) throw new Error("Invalid workbench sandbox image catalog.");
|
|
3211
|
+
const references = /* @__PURE__ */ new Set();
|
|
3212
|
+
for (const rawImage of config.approvedImages) {
|
|
3213
|
+
if (typeof rawImage !== "object" || rawImage === null || Array.isArray(rawImage)) throw new Error("Invalid workbench sandbox image catalog.");
|
|
3214
|
+
const image = rawImage;
|
|
3215
|
+
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.");
|
|
3216
|
+
references.add(image.reference);
|
|
3217
|
+
approvedImages.push({ reference: image.reference, archive: image.archive, digest: image.digest });
|
|
3218
|
+
}
|
|
3113
3219
|
}
|
|
3114
|
-
|
|
3220
|
+
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)) {
|
|
3221
|
+
throw new Error("Invalid workbench operator choices.");
|
|
3222
|
+
}
|
|
3223
|
+
return {
|
|
3224
|
+
schemaVersion: 1,
|
|
3225
|
+
image: config.image,
|
|
3226
|
+
imageDigest: config.imageDigest,
|
|
3227
|
+
stateRoot: config.stateRoot,
|
|
3228
|
+
...config.workspaceRoot === void 0 ? {} : { workspaceRoot: config.workspaceRoot },
|
|
3229
|
+
providers: [...new Set(config.providers)],
|
|
3230
|
+
github: config.github,
|
|
3231
|
+
cpus: config.cpus,
|
|
3232
|
+
memoryMb: config.memoryMb,
|
|
3233
|
+
storageGb: config.storageGb,
|
|
3234
|
+
...approvedImages.length ? { approvedImages } : {}
|
|
3235
|
+
};
|
|
3115
3236
|
}
|
|
3116
|
-
function
|
|
3117
|
-
|
|
3118
|
-
if (
|
|
3119
|
-
if (error === null || error === void 0) return "";
|
|
3237
|
+
function loadWorkbenchConfig(home) {
|
|
3238
|
+
const file = workbenchConfigPath(home);
|
|
3239
|
+
if (!existsSync2(file)) return void 0;
|
|
3120
3240
|
try {
|
|
3121
|
-
const
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3241
|
+
const stat = lstatSync2(file);
|
|
3242
|
+
if (!stat.isFile() || stat.isSymbolicLink() || (stat.mode & 63) !== 0 || stat.uid !== process.getuid?.()) {
|
|
3243
|
+
throw new Error("Workbench configuration must be an operator-owned, private regular file (mode 0600).");
|
|
3244
|
+
}
|
|
3245
|
+
return normalizeWorkbenchConfig(JSON.parse(readFileSync3(file, "utf8")));
|
|
3246
|
+
} catch (error) {
|
|
3247
|
+
if (error instanceof Error && error.message.startsWith("Workbench configuration must")) throw error;
|
|
3248
|
+
throw new Error(`Unreadable or invalid workbench configuration at ${file}. Repair the operator choices before setup or launch; no profile fallback is allowed.`);
|
|
3249
|
+
}
|
|
3250
|
+
}
|
|
3251
|
+
function saveWorkbenchConfig(config, home) {
|
|
3252
|
+
const path = workbenchConfigPath(home);
|
|
3253
|
+
mkdirSync4(dirname5(path), { recursive: true, mode: 448 });
|
|
3254
|
+
const temporary = `${path}.${process.pid}.tmp`;
|
|
3255
|
+
writeFileSync3(temporary, `${JSON.stringify(normalizeWorkbenchConfig(config), null, 2)}
|
|
3256
|
+
`, { encoding: "utf8", mode: 384, flag: "wx" });
|
|
3257
|
+
renameSync2(temporary, path);
|
|
3258
|
+
chmodSync(path, 384);
|
|
3259
|
+
}
|
|
3260
|
+
function resolveWorkbenchGuestSettings(settings, env = process.env) {
|
|
3261
|
+
const privacy = { [ANALYTICS_LEVEL_ENV]: env[ANALYTICS_LEVEL_ENV] ?? settings.analyticsLevel };
|
|
3262
|
+
for (const name of FEEDBACK_OPT_OUT_ENV) privacy[name] = env[name];
|
|
3263
|
+
const environmentLevel = resolveAnalyticsLevel(privacy);
|
|
3264
|
+
return normalizeSettings({
|
|
3265
|
+
...settings,
|
|
3266
|
+
executionProfile: "local",
|
|
3267
|
+
updatePolicy: "off",
|
|
3268
|
+
analyticsLevel: levelAtLeast(environmentLevel, settings.analyticsLevel) ? settings.analyticsLevel : environmentLevel,
|
|
3269
|
+
diagnosticReporting: submissionBlockedReason(privacy, { allowCloud: false }) === "opt-out" ? "off" : settings.diagnosticReporting
|
|
3270
|
+
});
|
|
3271
|
+
}
|
|
3272
|
+
function workbenchNetworkEnabled(env = process.env) {
|
|
3273
|
+
return submissionBlockedReason({ ZERO_OFFLINE: env.ZERO_OFFLINE }, { allowCloud: false }) !== "opt-out";
|
|
3274
|
+
}
|
|
3275
|
+
async function launchConfiguredWorkbench(args) {
|
|
3276
|
+
const home = process.env.HOME || homedir2();
|
|
3277
|
+
if (args[0] === WORKBENCH_INNER_ARGUMENT) {
|
|
3278
|
+
if (process.platform !== "linux" || !isAdmittedSmolvmWorkbench()) {
|
|
3279
|
+
throw new Error("Missing authenticated SmolVM broker admission; refusing internal invocation.");
|
|
3280
|
+
}
|
|
3281
|
+
process.argv = [process.argv[0], process.argv[1], ...args.slice(1)];
|
|
3282
|
+
return void 0;
|
|
3283
|
+
}
|
|
3284
|
+
if (process.platform === "linux" && isAdmittedSmolvmWorkbench()) return void 0;
|
|
3285
|
+
if (["workbench", "config", "web", "dashboard", "console", "tui", "doctor", "help", "--help", "-h", "--version", "-v"].includes(args[0] ?? "") || args.length === 0) return void 0;
|
|
3286
|
+
const hostSettings = loadGlobalSettings(home, { requireExecutionProfile: existsSync2(workbenchConfigPath(home)) });
|
|
3287
|
+
if (hostSettings.executionProfile !== "smolvm") return void 0;
|
|
3288
|
+
const config = loadWorkbenchConfig(home);
|
|
3289
|
+
if (!config) throw new Error("SmolVM profile is selected but no workbench is configured. Run 0 workbench setup --image <approved-archive>.");
|
|
3290
|
+
const workspaceRoot = realpathSync3(config.workspaceRoot ?? process.cwd());
|
|
3291
|
+
if (!lstatSync2(workspaceRoot).isDirectory()) throw new Error("The configured workbench workspace is not a directory.");
|
|
3292
|
+
const status = await getSmolvmWorkbenchStatus({ stateRoot: config.stateRoot, image: config.image });
|
|
3293
|
+
if (!status.platformSupported || !status.runtimeReady || !status.imageApproved) {
|
|
3294
|
+
throw new Error(status.error ?? "SmolVM runtime or approved image is unavailable. Run 0 workbench setup; host fallback is refused.");
|
|
3295
|
+
}
|
|
3296
|
+
const modelIndex = args.findIndex((argument) => argument === "--model" || argument === "-m");
|
|
3297
|
+
const modelArgument = modelIndex >= 0 ? args[modelIndex + 1] : args.find((argument) => argument.startsWith("--model="))?.slice("--model=".length);
|
|
3298
|
+
const runtime = createConsoleRuntime({ model: modelArgument });
|
|
3299
|
+
const providerId = runtime.resolvedProvider();
|
|
3300
|
+
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.");
|
|
3301
|
+
const model = runtime.resolvedModel();
|
|
3302
|
+
const routing = runtime.modelSelection();
|
|
3303
|
+
const broker = createWorkbenchProviderBroker({
|
|
3304
|
+
provider: providerId,
|
|
3305
|
+
models: [.../* @__PURE__ */ new Set([model, ...Object.values(routing.agentModels).filter((value) => value !== "auto")])],
|
|
3306
|
+
resolveCredentials: runtime.workbenchCredentialResolver()
|
|
3307
|
+
});
|
|
3308
|
+
const network = workbenchNetworkEnabled();
|
|
3309
|
+
const controller = new AbortController();
|
|
3310
|
+
let signalCode;
|
|
3311
|
+
const interrupt = () => {
|
|
3312
|
+
signalCode = 130;
|
|
3313
|
+
controller.abort();
|
|
3314
|
+
};
|
|
3315
|
+
const terminate = () => {
|
|
3316
|
+
signalCode = 143;
|
|
3317
|
+
controller.abort();
|
|
3318
|
+
};
|
|
3319
|
+
const hangup = () => {
|
|
3320
|
+
signalCode = 129;
|
|
3321
|
+
controller.abort();
|
|
3322
|
+
};
|
|
3323
|
+
process.once("SIGINT", interrupt);
|
|
3324
|
+
process.once("SIGTERM", terminate);
|
|
3325
|
+
process.once("SIGHUP", hangup);
|
|
3326
|
+
let result;
|
|
3327
|
+
try {
|
|
3328
|
+
result = await runWorkbenchCli({
|
|
3329
|
+
workbench: config,
|
|
3330
|
+
selection: { provider: providerId, model, ...routing },
|
|
3331
|
+
provider: { ...broker.grant, request: broker.request, close: broker.close },
|
|
3332
|
+
assets: currentWorkbenchAssets(),
|
|
3333
|
+
guestSettings: resolveWorkbenchGuestSettings(hostSettings),
|
|
3334
|
+
network,
|
|
3335
|
+
args: mapWorkbenchCliArguments(args, workspaceRoot),
|
|
3336
|
+
signal: controller.signal,
|
|
3337
|
+
onStdout: (data) => process.stdout.write(data),
|
|
3338
|
+
onStderr: (data) => process.stderr.write(data)
|
|
3339
|
+
});
|
|
3340
|
+
} finally {
|
|
3341
|
+
await broker.close();
|
|
3342
|
+
process.removeListener("SIGINT", interrupt);
|
|
3343
|
+
process.removeListener("SIGTERM", terminate);
|
|
3344
|
+
process.removeListener("SIGHUP", hangup);
|
|
3125
3345
|
}
|
|
3346
|
+
if (result.cleanupFailed) {
|
|
3347
|
+
process.stderr.write("[0] SmolVM cleanup could not be proven. Workbench admission/state is retained; host execution is refused.\n");
|
|
3348
|
+
return 125;
|
|
3349
|
+
}
|
|
3350
|
+
if (result.artifacts) process.stderr.write(`[0] Guest workspace and results saved at ${result.artifacts.directory}
|
|
3351
|
+
`);
|
|
3352
|
+
if (result.error) process.stderr.write(`[0] ${result.error}
|
|
3353
|
+
`);
|
|
3354
|
+
if (result.timedOut) return 124;
|
|
3355
|
+
return signalCode ?? result.exitCode ?? 125;
|
|
3356
|
+
}
|
|
3357
|
+
function selectWorkbenchProfile(profile, home) {
|
|
3358
|
+
if (!saveSettings({ ...loadGlobalSettings(home), executionProfile: profile }, home)) throw new Error("Could not persist the operator execution profile.");
|
|
3126
3359
|
}
|
|
3127
3360
|
|
|
3128
3361
|
export {
|
|
@@ -3150,13 +3383,18 @@ export {
|
|
|
3150
3383
|
isSafeThemeId,
|
|
3151
3384
|
reloadInstalledThemes,
|
|
3152
3385
|
installedThemeEntries,
|
|
3386
|
+
allThemeNames,
|
|
3153
3387
|
isKnownTheme,
|
|
3154
3388
|
writeInstalledTheme,
|
|
3155
3389
|
removeInstalledTheme,
|
|
3390
|
+
syncThemeChoices,
|
|
3156
3391
|
SETTING_DEFS,
|
|
3157
3392
|
DEFAULT_SETTINGS,
|
|
3158
3393
|
settingsFilePath,
|
|
3159
3394
|
projectSettingsFilePath,
|
|
3395
|
+
projectSettingsExist,
|
|
3396
|
+
isOperatorSetting,
|
|
3397
|
+
resolveLayeredSettings,
|
|
3160
3398
|
loadLayeredSettings,
|
|
3161
3399
|
normalizeSettings,
|
|
3162
3400
|
loadSettings,
|
|
@@ -3165,42 +3403,33 @@ export {
|
|
|
3165
3403
|
sanitizeOverrides,
|
|
3166
3404
|
readProjectOverrides,
|
|
3167
3405
|
saveProjectOverrides,
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
maybeLoadCodexAuth,
|
|
3178
|
-
presentationEventBus,
|
|
3179
|
-
createPresentationEmitter,
|
|
3180
|
-
consolePresentationOutput,
|
|
3181
|
-
installConsolePresentationBridge,
|
|
3182
|
-
installProcessPresentationStreamBridge,
|
|
3183
|
-
suspendProcessPresentationStreamBridge,
|
|
3184
|
-
resumeProcessPresentationStreamBridge,
|
|
3185
|
-
processPresentationOutput,
|
|
3186
|
-
writePresentationLine,
|
|
3406
|
+
currentWorkbenchAssets,
|
|
3407
|
+
WORKBENCH_FRAME_BYTES,
|
|
3408
|
+
WORKBENCH_MAX_PENDING,
|
|
3409
|
+
encodeWorkbenchFrame,
|
|
3410
|
+
WorkbenchFrameReader,
|
|
3411
|
+
installWorkbenchPlugins,
|
|
3412
|
+
validateGuestServicePluginConnections,
|
|
3413
|
+
servicePluginSecretRedactor,
|
|
3414
|
+
createWorkbenchConsoleSession,
|
|
3187
3415
|
PROVIDERS,
|
|
3416
|
+
providerSupportsMethod,
|
|
3188
3417
|
providerStates,
|
|
3189
3418
|
isProviderConfigured,
|
|
3190
|
-
loadCredentials,
|
|
3191
|
-
saveCredentials,
|
|
3192
|
-
credentialEnvPatch,
|
|
3193
|
-
redactSecret,
|
|
3194
|
-
loadAccountStore,
|
|
3195
|
-
saveAccountStore,
|
|
3196
|
-
addAccount,
|
|
3197
|
-
accountEnvPatch,
|
|
3198
3419
|
appendFeedback,
|
|
3199
3420
|
parseFeedbackCommand,
|
|
3200
|
-
FEEDBACK_OPT_OUT_ENV,
|
|
3201
3421
|
submissionBlockedReason,
|
|
3202
3422
|
describeSkip,
|
|
3203
3423
|
buildSubmitPreview,
|
|
3204
3424
|
submitFeedback,
|
|
3205
|
-
buildDiagnosticFeedback
|
|
3425
|
+
buildDiagnosticFeedback,
|
|
3426
|
+
workbenchConfigPath,
|
|
3427
|
+
defaultWorkbenchStateRoot,
|
|
3428
|
+
isPinnedWorkbenchImageReference,
|
|
3429
|
+
loadWorkbenchConfig,
|
|
3430
|
+
saveWorkbenchConfig,
|
|
3431
|
+
resolveWorkbenchGuestSettings,
|
|
3432
|
+
workbenchNetworkEnabled,
|
|
3433
|
+
launchConfiguredWorkbench,
|
|
3434
|
+
selectWorkbenchProfile
|
|
3206
3435
|
};
|