glimmervoid 0.0.0-stage → 0.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/LICENSE +48 -0
  2. package/README.md +195 -2
  3. package/dist/bin/glimmervoid.js +275 -0
  4. package/dist/chunks/adapters-Dho1VSY5.js +2 -0
  5. package/dist/chunks/adapters-zPDx1Xox.js +575 -0
  6. package/dist/chunks/agent-api-cli-D0MmtEdz.js +71 -0
  7. package/dist/chunks/agent-api-core-Dz0r21y9.js +103 -0
  8. package/dist/chunks/agent-setup-cli-B5Atxr3F.js +139 -0
  9. package/dist/chunks/ascii-figure-core-pDAwtlKO.js +45 -0
  10. package/dist/chunks/child-process-safe-DRGsp0TE.js +69 -0
  11. package/dist/chunks/child-process-safe-DvQjXsgz.js +2 -0
  12. package/dist/chunks/config-path-core-DFt-TemS.js +38 -0
  13. package/dist/chunks/config-store-CC8T3okC.js +2 -0
  14. package/dist/chunks/config-store-mm7MgzoL.js +1412 -0
  15. package/dist/chunks/contracts-DGkKNh9W.js +3020 -0
  16. package/dist/chunks/grok-DuU8xVLZ.js +436 -0
  17. package/dist/chunks/hook-command-core-DclEoE9e.js +14 -0
  18. package/dist/chunks/hook-relay-core-BHfYFnYV.js +88 -0
  19. package/dist/chunks/hook-tools-pSJia7ks.js +74 -0
  20. package/dist/chunks/ingest-core-DWI1Mbu4.js +342 -0
  21. package/dist/chunks/ingest-editor-core-BuI1gMF_.js +445 -0
  22. package/dist/chunks/loopback-post-C7C22eYi.js +79 -0
  23. package/dist/chunks/main-CuIZQPhZ.js +43559 -0
  24. package/dist/chunks/node-pty-preflight-CK1knGIR.js +162 -0
  25. package/dist/chunks/pair-cli-w-gCAF-1.js +110 -0
  26. package/dist/chunks/path-doctor-C23VAqeu.js +63 -0
  27. package/dist/chunks/refocus-core-BKQKllq7.js +38 -0
  28. package/dist/chunks/relay-stdin-CpHvLu6M.js +20 -0
  29. package/dist/chunks/remote-config-C2ytQwrs.js +525 -0
  30. package/dist/chunks/rtk-hook-core-Bl6gYHPN.js +133 -0
  31. package/dist/chunks/rtk-resolver-CLo-gZzw.js +21 -0
  32. package/dist/chunks/rtk-resolver-CMQAidlI.js +2 -0
  33. package/dist/chunks/runtime-paths-Ci_ksSkj.js +75 -0
  34. package/dist/chunks/runtime-paths-JLrZ3wyi.js +2 -0
  35. package/dist/chunks/sandbox-deps-Dd5-kZnf.js +67 -0
  36. package/dist/chunks/session-Cez8aIWg.js +164 -0
  37. package/dist/chunks/spawn-command-Cc40FiZI.js +134 -0
  38. package/dist/chunks/visions-cli-BXQFIBCX.js +98 -0
  39. package/dist/chunks/visions-setup-CvVnOe1r.js +1753 -0
  40. package/dist/client/assets/favicon-CKygeLwW.svg +7 -0
  41. package/dist/client/assets/index-B1MFijjb.css +1 -0
  42. package/dist/client/assets/index-CU1iPPdn.js +249 -0
  43. package/dist/client/index.html +108 -0
  44. package/dist/package.json +3 -0
  45. package/dist/scripts/postinstall-path-check.js +36 -0
  46. package/dist/server/index.js +92 -0
  47. package/dist/session/command-hook-relay.js +16 -0
  48. package/dist/session/hook-relay.js +24 -0
  49. package/dist/session/hook-tool-relay.js +107 -0
  50. package/dist/session/statusline-relay.js +82 -0
  51. package/dist/session/visions-relay.js +627 -0
  52. package/dist/tools/vscode-visions/extension.js +289 -0
  53. package/dist/tools/vscode-visions/lsp-convert.js +80 -0
  54. package/dist/tools/vscode-visions/package.json +32 -0
  55. package/dist/tools/vscode-visions/visions-lsp-core.js +112 -0
  56. package/package.json +96 -4
  57. package/scripts/postinstall.mjs +4 -0
  58. package/scripts/prepare-build.js +38 -0
  59. package/scripts/recover-handoff.mjs +82 -0
@@ -0,0 +1,1412 @@
1
+ import { n as glimmervoidHomeDir$1, t as decideConfigPath } from "./config-path-core-DFt-TemS.js";
2
+ import { I as TELEMETRY_KILL_SWITCH_FLAG, N as FeatureFlagsResponse, R as AGENT_ID_SHAPE_MESSAGE, U as RUNTIME_CONFIG_SCALAR_KEYS, V as Config, W as configIssueMessage, q as BranchGcFileSettings, z as BUILTIN_AGENT_IDS } from "./contracts-DGkKNh9W.js";
3
+ import { r as SOURCE_NAMES } from "./ingest-core-DWI1Mbu4.js";
4
+ import fs from "node:fs";
5
+ import path from "node:path";
6
+ import os from "node:os";
7
+ import { promisify } from "node:util";
8
+ import crypto, { createHash } from "node:crypto";
9
+ //#region shared/paths.ts
10
+ var realpathNative = promisify(fs.realpath.native);
11
+ function canonicalizePath(p) {
12
+ try {
13
+ return fs.realpathSync.native(p);
14
+ } catch {
15
+ return p;
16
+ }
17
+ }
18
+ function caseFoldedPathKey(value) {
19
+ return process.platform === "win32" ? value.toLowerCase() : value;
20
+ }
21
+ function equalsIgnoringCaseOnWindows(a, b) {
22
+ return caseFoldedPathKey(a) === caseFoldedPathKey(b);
23
+ }
24
+ function isSameDirectoryPath(a, b) {
25
+ const resolvedA = path.resolve(String(a || ""));
26
+ const resolvedB = path.resolve(String(b || ""));
27
+ if (equalsIgnoringCaseOnWindows(resolvedA, resolvedB)) return true;
28
+ if (process.platform !== "win32" && process.platform !== "darwin") return false;
29
+ return equalsIgnoringCaseOnWindows(canonicalizePath(resolvedA), canonicalizePath(resolvedB));
30
+ }
31
+ async function comparableDirectoryPath(candidatePath) {
32
+ const resolvedPath = path.resolve(String(candidatePath || ""));
33
+ let canonicalPath;
34
+ try {
35
+ canonicalPath = await realpathNative(resolvedPath);
36
+ } catch {
37
+ canonicalPath = resolvedPath;
38
+ }
39
+ if (process.platform !== "win32") return canonicalPath;
40
+ return canonicalPath.toLowerCase();
41
+ }
42
+ function safePathSegment(value) {
43
+ return String(value).replace(/[<>:"/\\|?*\x00-\x1f]/g, "-").replace(/[. ]+$/, "") || "_";
44
+ }
45
+ function sanitizeWorktreeName(value) {
46
+ return String(value || "").replace(/[^\w.-]+/g, "-").replace(/^-+|-+$/g, "");
47
+ }
48
+ var DEFAULT_BRANCH_GC_PREFIXES = ["glimmervoid/session/", "worktree-agent-"];
49
+ var DAY_MS = 864e5;
50
+ var STALE_DELETION_REASON = "stale-orphan";
51
+ var UNMERGED_KEPT_REASON = "not-merged-and-unmerged-deletion-off";
52
+ function sessionIdFromBranch(branchName) {
53
+ if (!branchName.startsWith("glimmervoid/session/")) return null;
54
+ return branchName.slice(20);
55
+ }
56
+ function usablePrefixes(prefixes) {
57
+ return prefixes.filter((prefix) => prefix.length > 0);
58
+ }
59
+ function matchedPrefix(name, prefixes) {
60
+ for (const prefix of usablePrefixes(prefixes)) if (name.startsWith(prefix)) return prefix;
61
+ return null;
62
+ }
63
+ function protectedBranchNames(integrationTips) {
64
+ return /* @__PURE__ */ new Set([
65
+ "main",
66
+ "master",
67
+ ...integrationTips.map((tip) => tip.branch)
68
+ ]);
69
+ }
70
+ function planBranchGc({ remoteBranches = [], integrationTips = [], liveSessionIds = /* @__PURE__ */ new Set(), prefixes = DEFAULT_BRANCH_GC_PREFIXES, nowMs, staleDays = 14, deleteUnmerged = false }) {
71
+ const deletions = [];
72
+ const kept = [];
73
+ const protectedNames = protectedBranchNames(integrationTips);
74
+ const staleBeforeMs = nowMs - staleDays * DAY_MS;
75
+ for (const remoteBranch of remoteBranches) {
76
+ const { name, tipSha } = remoteBranch;
77
+ if (matchedPrefix(name, prefixes) === null) {
78
+ kept.push({
79
+ name,
80
+ reason: "foreign-prefix"
81
+ });
82
+ continue;
83
+ }
84
+ if (protectedNames.has(name)) {
85
+ kept.push({
86
+ name,
87
+ reason: "not-merged-and-fresh"
88
+ });
89
+ continue;
90
+ }
91
+ const sessionId = sessionIdFromBranch(name);
92
+ if (sessionId !== null && liveSessionIds.has(sessionId)) {
93
+ kept.push({
94
+ name,
95
+ reason: "live-session"
96
+ });
97
+ continue;
98
+ }
99
+ if (remoteBranch.mergedIntoIntegration === true) {
100
+ deletions.push({
101
+ name,
102
+ reason: remoteBranch.mergedReason ?? "merged-into-integration",
103
+ tipSha
104
+ });
105
+ continue;
106
+ }
107
+ if (!deleteUnmerged) {
108
+ kept.push({
109
+ name,
110
+ reason: UNMERGED_KEPT_REASON
111
+ });
112
+ continue;
113
+ }
114
+ if (Number.isFinite(remoteBranch.tipCommitTimeMs) && remoteBranch.tipCommitTimeMs < staleBeforeMs) {
115
+ deletions.push({
116
+ name,
117
+ reason: STALE_DELETION_REASON,
118
+ tipSha
119
+ });
120
+ continue;
121
+ }
122
+ kept.push({
123
+ name,
124
+ reason: "not-merged-and-fresh"
125
+ });
126
+ }
127
+ return {
128
+ deletions,
129
+ kept
130
+ };
131
+ }
132
+ function worktreeIntegrationTips({ projectTips, worktreeOwnTips }) {
133
+ const tipBySha = /* @__PURE__ */ new Map();
134
+ for (const tip of [...projectTips, ...worktreeOwnTips]) {
135
+ if (!tip.sha) continue;
136
+ if (tipBySha.has(tip.sha)) continue;
137
+ tipBySha.set(tip.sha, tip);
138
+ }
139
+ return [...tipBySha.values()];
140
+ }
141
+ function protectedWorktreeBranchNames(integrationTips, worktrees) {
142
+ const ownIntegrationBranches = worktrees.map((worktree) => worktree.integrationBranch).filter((branch) => Boolean(branch)).map((branch) => ({ branch }));
143
+ return protectedBranchNames([...integrationTips, ...ownIntegrationBranches]);
144
+ }
145
+ function planWorktreeGc({ worktrees, liveWorktreePaths, integrationTips, prefixes }) {
146
+ const removals = [];
147
+ const kept = [];
148
+ const protectedNames = protectedWorktreeBranchNames(integrationTips, worktrees);
149
+ for (const worktree of worktrees) {
150
+ const decision = (disposition, reason) => ({
151
+ cwd: worktree.cwd,
152
+ branch: worktree.branch,
153
+ disposition,
154
+ reason
155
+ });
156
+ if (worktree.prunable === true) {
157
+ kept.push(decision("keep", "missing-directory"));
158
+ continue;
159
+ }
160
+ if (worktree.locked) {
161
+ kept.push(decision("keep", "locked"));
162
+ continue;
163
+ }
164
+ if (liveWorktreePaths.has(worktree.cwd)) {
165
+ kept.push(decision("keep", "live-session"));
166
+ continue;
167
+ }
168
+ if (protectedNames.has(worktree.branch)) {
169
+ kept.push(decision("keep", "protected"));
170
+ continue;
171
+ }
172
+ if (matchedPrefix(worktree.branch, prefixes) === null) {
173
+ kept.push(decision("keep", "foreign-prefix"));
174
+ continue;
175
+ }
176
+ if (worktree.dirty === null) {
177
+ kept.push(decision("keep", "status-probe-failed"));
178
+ continue;
179
+ }
180
+ if (worktree.dirty) {
181
+ kept.push(decision("keep", "uncommitted-changes"));
182
+ continue;
183
+ }
184
+ if (worktree.atIntegrationTip === true) {
185
+ kept.push(decision("keep", "no-commits"));
186
+ continue;
187
+ }
188
+ if (worktree.merged === null) {
189
+ kept.push(decision("keep", "merge-proof-failed"));
190
+ continue;
191
+ }
192
+ if (worktree.merged !== true) {
193
+ kept.push(decision("keep", "unmerged"));
194
+ continue;
195
+ }
196
+ removals.push(decision("remove", "merged"));
197
+ }
198
+ return {
199
+ removals,
200
+ kept
201
+ };
202
+ }
203
+ //#endregion
204
+ //#region server/core/usage-number-core.ts
205
+ function safeNumber(value) {
206
+ if (typeof value !== "number" || !Number.isFinite(value)) return 0;
207
+ return value;
208
+ }
209
+ function numberOrNull(value) {
210
+ if (typeof value !== "number" || !Number.isFinite(value)) return null;
211
+ return value;
212
+ }
213
+ function stringOrNull(value) {
214
+ if (typeof value !== "string" || value.length === 0) return null;
215
+ return value;
216
+ }
217
+ function isPlainObject(value) {
218
+ return value != null && typeof value === "object" && !Array.isArray(value);
219
+ }
220
+ //#endregion
221
+ //#region server/core/config-secrets-core.ts
222
+ var ENV_SECRET_BINDINGS = Object.freeze([{
223
+ blockName: "posthog",
224
+ secretKey: "apiKey",
225
+ environmentVariable: "GLIMMERVOID_POSTHOG_API_KEY"
226
+ }, {
227
+ blockName: "telegram",
228
+ secretKey: "botToken",
229
+ environmentVariable: "GLIMMERVOID_TELEGRAM_BOT_TOKEN"
230
+ }]);
231
+ function readEnvSecrets(env) {
232
+ const resolvedSecrets = [];
233
+ for (const binding of ENV_SECRET_BINDINGS) {
234
+ const value = env[binding.environmentVariable];
235
+ if (typeof value !== "string" || value.length === 0) continue;
236
+ resolvedSecrets.push({
237
+ ...binding,
238
+ value
239
+ });
240
+ }
241
+ return resolvedSecrets;
242
+ }
243
+ function copyOfBlock(config, blockName) {
244
+ const block = config[blockName];
245
+ if (!isPlainObject(block)) return {};
246
+ return { ...block };
247
+ }
248
+ function withEnvSecrets(config, envSecrets) {
249
+ if (envSecrets.length === 0) return config;
250
+ const overlaid = { ...config };
251
+ for (const secret of envSecrets) overlaid[secret.blockName] = {
252
+ ...copyOfBlock(overlaid, secret.blockName),
253
+ [secret.secretKey]: secret.value
254
+ };
255
+ return overlaid;
256
+ }
257
+ function withoutEnvSecrets(config, envSecrets) {
258
+ if (envSecrets.length === 0) return config;
259
+ const stripped = { ...config };
260
+ for (const secret of envSecrets) {
261
+ if (!isPlainObject(stripped[secret.blockName])) continue;
262
+ const remaining = copyOfBlock(stripped, secret.blockName);
263
+ delete remaining[secret.secretKey];
264
+ if (Object.keys(remaining).length === 0) {
265
+ delete stripped[secret.blockName];
266
+ continue;
267
+ }
268
+ stripped[secret.blockName] = remaining;
269
+ }
270
+ return stripped;
271
+ }
272
+ function withSortedKeys(value) {
273
+ if (Array.isArray(value)) return value.map(withSortedKeys);
274
+ if (!isPlainObject(value)) return value;
275
+ const sorted = {};
276
+ for (const key of Object.keys(value).sort()) sorted[key] = withSortedKeys(value[key]);
277
+ return sorted;
278
+ }
279
+ function stableConfigKey(value) {
280
+ return JSON.stringify(withSortedKeys(value));
281
+ }
282
+ //#endregion
283
+ //#region server/core/telemetry-core.ts
284
+ var POSTHOG_INGEST_HOST = "https://us.i.posthog.com";
285
+ var TELEMETRY_BATCH_URL = `${POSTHOG_INGEST_HOST}/batch/`;
286
+ var TELEMETRY_FLAGS_URL = `${POSTHOG_INGEST_HOST}/flags?v=2`;
287
+ var MAX_STACK_LENGTH = 16384;
288
+ var FIRST_RUN_NOTICE = ["Glimmervoid sends anonymous usage and error data to help improve it: app starts, daily activity, session starts and ends with the agent kind, exit kind and duration, errors as their type, error code and scrubbed stack frames, and per session token counts, models and estimated cost. No error messages, paths, repository names, prompts, model output or terminal output.", "Turn it off in Settings > Privacy, or set GLIMMERVOID_TELEMETRY=0 or DO_NOT_TRACK=1."].join("\n");
289
+ var TRUTHY_FLAGS = /* @__PURE__ */ new Set([
290
+ "1",
291
+ "true",
292
+ "yes"
293
+ ]);
294
+ var FALSY_FLAGS = /* @__PURE__ */ new Set([
295
+ "0",
296
+ "false",
297
+ "no",
298
+ "off"
299
+ ]);
300
+ function normalizedFlag(value) {
301
+ return (value ?? "").trim().toLowerCase();
302
+ }
303
+ function decideTelemetryConsent(env, config) {
304
+ if (TRUTHY_FLAGS.has(normalizedFlag(env.DO_NOT_TRACK))) return {
305
+ isEnabled: false,
306
+ source: "do-not-track"
307
+ };
308
+ if (FALSY_FLAGS.has(normalizedFlag(env.GLIMMERVOID_TELEMETRY))) return {
309
+ isEnabled: false,
310
+ source: "environment"
311
+ };
312
+ if (TRUTHY_FLAGS.has(normalizedFlag(env.CI))) return {
313
+ isEnabled: false,
314
+ source: "ci"
315
+ };
316
+ if (normalizedFlag(env.NODE_TEST_CONTEXT) !== "") return {
317
+ isEnabled: false,
318
+ source: "test-runner"
319
+ };
320
+ const configured = config.telemetry?.enabled;
321
+ if (typeof configured === "boolean") return {
322
+ isEnabled: configured,
323
+ source: "config"
324
+ };
325
+ return {
326
+ isEnabled: true,
327
+ source: "default"
328
+ };
329
+ }
330
+ function isTelemetryForcedOff(env) {
331
+ return decideTelemetryConsent(env, { telemetry: { enabled: true } }).isEnabled === false;
332
+ }
333
+ function resolveProjectToken(env) {
334
+ return env.GLIMMERVOID_TELEMETRY_PROJECT_TOKEN?.trim() || "phc_s68t9vWmeGcrWkx4NkLQaEiVZ9sMBxv8GjQBbAcix2Sf";
335
+ }
336
+ function toForwardSlashes(filePath) {
337
+ return filePath.replaceAll("\\", "/");
338
+ }
339
+ function isWindowsStylePath(filePath) {
340
+ return /^[A-Za-z]:\//.test(filePath);
341
+ }
342
+ function comparablePath(filePath) {
343
+ const forward = toForwardSlashes(filePath).replace(/\/+$/, "");
344
+ return isWindowsStylePath(forward) ? forward.toLowerCase() : forward;
345
+ }
346
+ function decodedFileUrlPath(fileUrl) {
347
+ const urlPath = fileUrl.slice(7).replace(/^\/([A-Za-z]:)/, "$1");
348
+ try {
349
+ return decodeURIComponent(urlPath);
350
+ } catch {
351
+ return urlPath;
352
+ }
353
+ }
354
+ function stripFileScheme(rawPath) {
355
+ if (!rawPath.startsWith("file://")) return toForwardSlashes(rawPath);
356
+ return toForwardSlashes(decodedFileUrlPath(rawPath));
357
+ }
358
+ function relativeUnder(filePath, root) {
359
+ if (!root) return null;
360
+ const comparableRoot = comparablePath(root);
361
+ if (!comparableRoot) return null;
362
+ if (!comparablePath(filePath).startsWith(`${comparableRoot}/`)) return null;
363
+ return toForwardSlashes(filePath).slice(comparableRoot.length + 1);
364
+ }
365
+ function scrubLocalPath(rawPath, packageRoot) {
366
+ const filePath = stripFileScheme(rawPath);
367
+ if (filePath.startsWith("node:")) return filePath;
368
+ const underPackage = relativeUnder(filePath, packageRoot);
369
+ if (underPackage !== null) return underPackage;
370
+ const nodeModulesAt = filePath.lastIndexOf("/node_modules/");
371
+ if (nodeModulesAt >= 0) return filePath.slice(nodeModulesAt + 1);
372
+ return filePath.slice(filePath.lastIndexOf("/") + 1);
373
+ }
374
+ function isInApp(rawPath, packageRoot) {
375
+ const filePath = stripFileScheme(rawPath);
376
+ if (filePath.startsWith("node:")) return false;
377
+ const underPackage = relativeUnder(filePath, packageRoot);
378
+ if (underPackage === null) return false;
379
+ return !underPackage.split("/").includes("node_modules");
380
+ }
381
+ var V8_FRAME_WITH_FUNCTION = /^\s*at (.+?) \((.+?):(\d+):(\d+)\)$/;
382
+ var V8_FRAME_ANONYMOUS = /^\s*at (.+?):(\d+):(\d+)$/;
383
+ function matchV8Frame(stackLine) {
384
+ const named = V8_FRAME_WITH_FUNCTION.exec(stackLine);
385
+ if (named) return {
386
+ functionName: named[1].replace(/^async /, ""),
387
+ filePath: named[2],
388
+ line: named[3],
389
+ column: named[4]
390
+ };
391
+ const anonymous = V8_FRAME_ANONYMOUS.exec(stackLine);
392
+ if (!anonymous) return null;
393
+ return {
394
+ functionName: "?",
395
+ filePath: anonymous[1].replace(/^async /, ""),
396
+ line: anonymous[2],
397
+ column: anonymous[3]
398
+ };
399
+ }
400
+ function parseV8StackFrames(stack, packageRoot, platform = "node:javascript") {
401
+ const frames = [];
402
+ for (const stackLine of stack.slice(0, MAX_STACK_LENGTH).split("\n")) {
403
+ const rawFrame = matchV8Frame(stackLine);
404
+ if (!rawFrame) continue;
405
+ frames.push({
406
+ platform,
407
+ function: rawFrame.functionName.slice(0, 512),
408
+ filename: scrubLocalPath(rawFrame.filePath, packageRoot).slice(0, 512),
409
+ lineno: Number(rawFrame.line),
410
+ colno: Number(rawFrame.column),
411
+ in_app: isInApp(rawFrame.filePath, packageRoot)
412
+ });
413
+ if (frames.length >= 64) break;
414
+ }
415
+ return frames.reverse();
416
+ }
417
+ var ERROR_CODE_SHAPE = /^[A-Z][A-Z0-9_]{0,63}$/;
418
+ function safeErrorCode(error) {
419
+ const code = Reflect.get(error, "code");
420
+ if (typeof code !== "string") return "";
421
+ return ERROR_CODE_SHAPE.test(code) ? code : "";
422
+ }
423
+ function errorParts(error) {
424
+ if (error instanceof Error) return {
425
+ type: error.name || "Error",
426
+ code: safeErrorCode(error),
427
+ stack: error.stack ?? ""
428
+ };
429
+ return {
430
+ type: "NonError",
431
+ code: "",
432
+ stack: ""
433
+ };
434
+ }
435
+ function exceptionPropertiesOf({ type, code, handled, level, frames }) {
436
+ return {
437
+ $exception_list: [{
438
+ type: type.slice(0, 512),
439
+ value: code,
440
+ mechanism: {
441
+ handled,
442
+ synthetic: false,
443
+ type: "generic"
444
+ },
445
+ stacktrace: {
446
+ type: "raw",
447
+ frames
448
+ }
449
+ }],
450
+ $exception_level: level
451
+ };
452
+ }
453
+ function buildExceptionProperties(error, { handled, packageRoot }) {
454
+ const parts = errorParts(error);
455
+ return exceptionPropertiesOf({
456
+ type: parts.type,
457
+ code: parts.code,
458
+ handled,
459
+ level: handled ? "error" : "fatal",
460
+ frames: parseV8StackFrames(parts.stack, packageRoot)
461
+ });
462
+ }
463
+ var BROWSER_FRAME_GECKO = /^\s*([^@]*)@(.+?):(\d+):(\d+)$/;
464
+ var URL_ORIGIN = /^[A-Za-z][A-Za-z0-9+.-]*:\/\/[^/]*/;
465
+ var SAFE_ERROR_TYPE_NAME = /^[A-Za-z_$][\w$]{0,127}$/;
466
+ function isWebUrl(rawUrl) {
467
+ return /^https?:\/\//i.test(rawUrl);
468
+ }
469
+ function lastPathSegment(urlPath) {
470
+ return urlPath.slice(urlPath.lastIndexOf("/") + 1);
471
+ }
472
+ function scrubWebPath(webPath) {
473
+ const nodeModulesAt = webPath.lastIndexOf("/node_modules/");
474
+ if (nodeModulesAt >= 0) return webPath.slice(nodeModulesAt + 1);
475
+ if (webPath.startsWith("/@fs/")) return lastPathSegment(webPath);
476
+ return webPath;
477
+ }
478
+ function urlPathOnly(rawUrl) {
479
+ const [withoutQueryOrHash] = rawUrl.split(/[?#]/, 1);
480
+ if (isWebUrl(withoutQueryOrHash)) return scrubWebPath(withoutQueryOrHash.replace(URL_ORIGIN, "") || "/");
481
+ return lastPathSegment(withoutQueryOrHash);
482
+ }
483
+ function matchBrowserFrame(stackLine) {
484
+ const v8Frame = matchV8Frame(stackLine);
485
+ if (v8Frame) return v8Frame;
486
+ const gecko = BROWSER_FRAME_GECKO.exec(stackLine);
487
+ if (!gecko) return null;
488
+ return {
489
+ functionName: gecko[1] || "?",
490
+ filePath: gecko[2],
491
+ line: gecko[3],
492
+ column: gecko[4]
493
+ };
494
+ }
495
+ function parseBrowserStackFrames(stack) {
496
+ const frames = [];
497
+ for (const stackLine of stack.slice(0, MAX_STACK_LENGTH).split("\n")) {
498
+ const rawFrame = matchBrowserFrame(stackLine);
499
+ if (!rawFrame) continue;
500
+ frames.push({
501
+ platform: "web:javascript",
502
+ function: rawFrame.functionName.slice(0, 512),
503
+ filename: urlPathOnly(rawFrame.filePath).slice(0, 512),
504
+ lineno: Number(rawFrame.line),
505
+ colno: Number(rawFrame.column),
506
+ in_app: isWebUrl(rawFrame.filePath)
507
+ });
508
+ if (frames.length >= 64) break;
509
+ }
510
+ return frames.reverse();
511
+ }
512
+ function safeErrorTypeName(name) {
513
+ return SAFE_ERROR_TYPE_NAME.test(name) ? name : "Error";
514
+ }
515
+ function buildBrowserExceptionProperties({ name, stack }) {
516
+ return exceptionPropertiesOf({
517
+ type: safeErrorTypeName(name),
518
+ code: "",
519
+ handled: false,
520
+ level: "error",
521
+ frames: parseBrowserStackFrames(stack)
522
+ });
523
+ }
524
+ function exceptionFingerprint(properties) {
525
+ const [exception] = properties.$exception_list;
526
+ const inAppFrames = exception.stacktrace.frames.filter((frame) => frame.in_app).map((frame) => `${frame.function}@${frame.filename}`);
527
+ return [exception.type, ...inAppFrames].join("|");
528
+ }
529
+ function isBuiltinAgentId(agentId) {
530
+ return BUILTIN_AGENT_IDS.some((builtinAgentId) => builtinAgentId === agentId);
531
+ }
532
+ function adapterBucket(agentId) {
533
+ if (isBuiltinAgentId(agentId)) return agentId;
534
+ return "custom";
535
+ }
536
+ function classifySessionExit({ exitCode, signal, reason }) {
537
+ if (reason === "no_output_before_exit") return "no_output";
538
+ if (signal) return "signal";
539
+ if (exitCode === 0) return "clean";
540
+ return "error";
541
+ }
542
+ var AI_PROVIDER_BY_VENDOR = Object.freeze({
543
+ claude: "anthropic",
544
+ codex: "openai",
545
+ grok: "xai"
546
+ });
547
+ var AGENT_ID_BY_VENDOR = Object.freeze({
548
+ claude: "claude-code",
549
+ codex: "codex",
550
+ grok: "grok"
551
+ });
552
+ function aiTraceId(installId, sessionId) {
553
+ return createHash("sha256").update(`${installId}${sessionId}`).digest("hex");
554
+ }
555
+ function wholeTokenCount(tokens) {
556
+ return Math.max(0, Math.round(tokens));
557
+ }
558
+ function buildAiGenerationEvent(row, installId) {
559
+ const properties = {
560
+ $ai_trace_id: aiTraceId(installId, row.sessionId),
561
+ $ai_provider: (AI_PROVIDER_BY_VENDOR[row.vendor] ?? row.vendor).slice(0, 64),
562
+ $ai_model: row.isModelKnown ? row.model.slice(0, 128) : "unknown",
563
+ $ai_input_tokens: wholeTokenCount(row.input),
564
+ $ai_output_tokens: wholeTokenCount(row.output),
565
+ $ai_cache_read_input_tokens: wholeTokenCount(row.cacheRead),
566
+ $ai_cache_creation_input_tokens: wholeTokenCount(row.cacheCreate),
567
+ agent_adapter: adapterBucket(AGENT_ID_BY_VENDOR[row.vendor])
568
+ };
569
+ if (!row.hasKnownCost) return properties;
570
+ return {
571
+ ...properties,
572
+ $ai_total_cost_usd: Math.max(0, row.costUSD)
573
+ };
574
+ }
575
+ function hasAnyTokens(row) {
576
+ return row.input + row.output + row.cacheRead + row.cacheCreate > 0;
577
+ }
578
+ function buildAiGenerationEvents(rows, installId) {
579
+ return rows.filter(hasAnyTokens).map((row) => buildAiGenerationEvent(row, installId));
580
+ }
581
+ function decideRemoteTelemetryState(previous, response) {
582
+ const parsed = FeatureFlagsResponse.safeParse(response);
583
+ if (!parsed.success || parsed.data.errorsWhileComputingFlags === true) return previous;
584
+ const killSwitchFlag = parsed.data.flags[TELEMETRY_KILL_SWITCH_FLAG];
585
+ if (!killSwitchFlag) return previous;
586
+ return killSwitchFlag.enabled ? "enabled" : "disabled";
587
+ }
588
+ var POSTHOG_RELEASE_ID_SHAPE = /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/;
589
+ function parsePosthogReleaseId(value) {
590
+ if (typeof value !== "string") return null;
591
+ return POSTHOG_RELEASE_ID_SHAPE.test(value) ? value : null;
592
+ }
593
+ function decideIsDevInstall({ installFlavor, isBundled, homeDir, defaultHomeDir }) {
594
+ if (installFlavor === "clone" || !isBundled) return true;
595
+ return comparablePath(homeDir) !== comparablePath(defaultHomeDir);
596
+ }
597
+ function nodeMajorVersion(nodeVersion) {
598
+ const major = Number.parseInt(nodeVersion.replace(/^v/, ""), 10);
599
+ return Number.isFinite(major) ? major : 0;
600
+ }
601
+ //#endregion
602
+ //#region server/core/settings-block-core.ts
603
+ var NO_BLOCKS = Object.freeze({});
604
+ var INGEST_SOURCES_SPEC = Object.freeze({
605
+ name: "ingest.sources",
606
+ booleans: Object.freeze([]),
607
+ blocks: Object.freeze(Object.fromEntries(SOURCE_NAMES.map((source) => [source, Object.freeze({
608
+ name: `ingest.sources.${source}`,
609
+ booleans: Object.freeze(["enabled"]),
610
+ blocks: NO_BLOCKS
611
+ })])))
612
+ });
613
+ var INGEST_SPEC = Object.freeze({
614
+ name: "ingest",
615
+ booleans: Object.freeze(["enabled"]),
616
+ blocks: Object.freeze({ sources: INGEST_SOURCES_SPEC })
617
+ });
618
+ function validateSettingsBlock(block, spec) {
619
+ if (block == null) return null;
620
+ if (!isPlainObject(block)) return `${spec.name} must be an object`;
621
+ for (const [key, value] of Object.entries(block)) {
622
+ if (value == null) continue;
623
+ if (spec.booleans.includes(key)) {
624
+ if (typeof value !== "boolean") return `${spec.name}.${key} must be a boolean`;
625
+ continue;
626
+ }
627
+ if (Object.hasOwn(spec.blocks, key)) {
628
+ const error = validateSettingsBlock(value, spec.blocks[key]);
629
+ if (error) return error;
630
+ continue;
631
+ }
632
+ return `${spec.name}.${key} is not settable from the dashboard`;
633
+ }
634
+ return null;
635
+ }
636
+ function pickSettingsBlock(stored, spec) {
637
+ if (!isPlainObject(stored)) return null;
638
+ const fields = stored;
639
+ const out = {};
640
+ for (const key of spec.booleans) if (fields[key] != null) out[key] = !!fields[key];
641
+ for (const [key, nested] of Object.entries(spec.blocks)) {
642
+ const picked = pickSettingsBlock(fields[key], nested);
643
+ if (picked) out[key] = picked;
644
+ }
645
+ return out;
646
+ }
647
+ function mergeSettingsBlock(stored, incoming, spec) {
648
+ const out = isPlainObject(stored) ? { ...stored } : {};
649
+ if (!isPlainObject(incoming)) return out;
650
+ const fields = incoming;
651
+ for (const key of spec.booleans) if (fields[key] != null) out[key] = !!fields[key];
652
+ for (const [key, nested] of Object.entries(spec.blocks)) {
653
+ if (fields[key] == null) continue;
654
+ out[key] = mergeSettingsBlock(out[key], fields[key], nested);
655
+ }
656
+ return out;
657
+ }
658
+ //#endregion
659
+ //#region server/json-file.ts
660
+ var tmpCounter = 0;
661
+ function tmpPathFor(filePath) {
662
+ tmpCounter += 1;
663
+ return `${filePath}.tmp.${process.pid}.${tmpCounter}`;
664
+ }
665
+ function writeOptions(mode, encoding) {
666
+ if (mode == null) return { encoding };
667
+ return {
668
+ encoding,
669
+ mode
670
+ };
671
+ }
672
+ var RENAME_RETRY_CODES = /* @__PURE__ */ new Set([
673
+ "EPERM",
674
+ "EACCES",
675
+ "EBUSY"
676
+ ]);
677
+ function renameRetryDelayMs(attempt) {
678
+ return Math.min(10 * 2 ** attempt, 50);
679
+ }
680
+ function isRetryableRename(error, attempt) {
681
+ if (attempt >= 4) return false;
682
+ const code = error?.code;
683
+ return typeof code === "string" && RENAME_RETRY_CODES.has(code);
684
+ }
685
+ function sleepSync(ms) {
686
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
687
+ }
688
+ function renameRetryPlan(error, attempt) {
689
+ if (!isRetryableRename(error, attempt)) return null;
690
+ return renameRetryDelayMs(attempt);
691
+ }
692
+ function isMissingFileError(error) {
693
+ const code = error?.code;
694
+ return code === "ENOENT" || code === "ENOTDIR";
695
+ }
696
+ function errorText(error) {
697
+ return error instanceof Error ? error.message : String(error);
698
+ }
699
+ async function loadJsonStateFile({ filePath, fsPromises, parse, nowMs }) {
700
+ let text;
701
+ try {
702
+ text = await fsPromises.readFile(filePath, "utf8");
703
+ } catch (error) {
704
+ if (isMissingFileError(error)) return { status: "missing" };
705
+ return {
706
+ status: "unreadable",
707
+ error
708
+ };
709
+ }
710
+ let value;
711
+ try {
712
+ value = parse(JSON.parse(text));
713
+ } catch {
714
+ value = null;
715
+ }
716
+ if (value !== null) return {
717
+ status: "loaded",
718
+ value
719
+ };
720
+ const movedTo = `${filePath}.corrupt-${nowMs()}`;
721
+ try {
722
+ await fsPromises.rename(filePath, movedTo);
723
+ } catch (error) {
724
+ return {
725
+ status: "unreadable",
726
+ error
727
+ };
728
+ }
729
+ return {
730
+ status: "quarantined",
731
+ movedTo
732
+ };
733
+ }
734
+ function renameWithRetrySync(fsSync, tmpPath, filePath) {
735
+ for (let attempt = 0;; attempt += 1) try {
736
+ fsSync.renameSync(tmpPath, filePath);
737
+ return;
738
+ } catch (error) {
739
+ const delayMs = renameRetryPlan(error, attempt);
740
+ if (delayMs === null) throw error;
741
+ sleepSync(delayMs);
742
+ }
743
+ }
744
+ async function renameWithRetry(fsPromises, tmpPath, filePath) {
745
+ for (let attempt = 0;; attempt += 1) try {
746
+ await fsPromises.rename(tmpPath, filePath);
747
+ return;
748
+ } catch (error) {
749
+ const delayMs = renameRetryPlan(error, attempt);
750
+ if (delayMs === null) throw error;
751
+ await new Promise((resolve) => {
752
+ setTimeout(resolve, delayMs);
753
+ });
754
+ }
755
+ }
756
+ function writeTextAtomicSync(filePath, content, { mode, encoding = "utf8", mkdir = false, fsSync = fs } = {}) {
757
+ if (mkdir) fsSync.mkdirSync(path.dirname(filePath), { recursive: true });
758
+ const tmpPath = tmpPathFor(filePath);
759
+ fsSync.writeFileSync(tmpPath, content, writeOptions(mode, encoding));
760
+ try {
761
+ renameWithRetrySync(fsSync, tmpPath, filePath);
762
+ } catch (error) {
763
+ fsSync.rmSync(tmpPath, { force: true });
764
+ throw error;
765
+ }
766
+ }
767
+ function writeJsonAtomicSync(filePath, value, options) {
768
+ writeTextAtomicSync(filePath, JSON.stringify(value, null, 2), options);
769
+ }
770
+ async function writeTextAtomic(filePath, content, { mode, encoding = "utf8", mkdir = false, fsPromises = fs.promises } = {}) {
771
+ if (mkdir) await fsPromises.mkdir(path.dirname(filePath), { recursive: true });
772
+ const tmpPath = tmpPathFor(filePath);
773
+ await fsPromises.writeFile(tmpPath, content, writeOptions(mode, encoding));
774
+ try {
775
+ await renameWithRetry(fsPromises, tmpPath, filePath);
776
+ } catch (error) {
777
+ try {
778
+ await fsPromises.rm(tmpPath, { force: true });
779
+ } catch {}
780
+ throw error;
781
+ }
782
+ }
783
+ async function writeJsonAtomic(filePath, value, options) {
784
+ await writeTextAtomic(filePath, JSON.stringify(value, null, 2), options);
785
+ }
786
+ var appendChains = /* @__PURE__ */ new Map();
787
+ function appendChained(filePath, payload, { fsPromises = fs.promises, mkdir = false, encoding = "utf8", mode } = {}) {
788
+ const next = (appendChains.get(filePath) || Promise.resolve()).then(async () => {
789
+ if (mkdir) await fsPromises.mkdir(path.dirname(filePath), { recursive: true });
790
+ await fsPromises.appendFile(filePath, payload, writeOptions(mode, encoding));
791
+ });
792
+ const settled = next.then(() => {}, () => {});
793
+ appendChains.set(filePath, settled);
794
+ settled.then(() => {
795
+ if (appendChains.get(filePath) === settled) appendChains.delete(filePath);
796
+ });
797
+ return next;
798
+ }
799
+ function appendJsonLine(filePath, value, options) {
800
+ return appendChained(filePath, `${JSON.stringify(value)}\n`, options);
801
+ }
802
+ function appendJsonLines(filePath, values, options) {
803
+ if (values.length === 0) return Promise.resolve();
804
+ return appendChained(filePath, values.map((value) => `${JSON.stringify(value)}\n`).join(""), options);
805
+ }
806
+ function createJsonStateWriter({ filePath, fsPromises = fs.promises, warn = () => {} }) {
807
+ let signature = null;
808
+ let writeChain = Promise.resolve();
809
+ async function commit(payload) {
810
+ try {
811
+ await writeTextAtomic(filePath, payload, {
812
+ fsPromises,
813
+ mkdir: true
814
+ });
815
+ } catch (error) {
816
+ warn(error);
817
+ signature = null;
818
+ }
819
+ }
820
+ async function write(subject, buildPayload) {
821
+ const next = JSON.stringify(subject);
822
+ if (next === signature) return;
823
+ signature = next;
824
+ writeChain = writeChain.then(() => commit(buildPayload())).catch(() => {});
825
+ await writeChain;
826
+ }
827
+ function reset() {
828
+ signature = null;
829
+ }
830
+ return {
831
+ write,
832
+ reset,
833
+ idle: () => writeChain
834
+ };
835
+ }
836
+ function createJsonStateStore({ name, filePath, fsPromises = fs.promises, parse, adopt, nowMs = Date.now, warn = () => {} }) {
837
+ const writer = filePath ? createJsonStateWriter({
838
+ filePath,
839
+ fsPromises,
840
+ warn: (error) => warn(`${name} write failed`, { error: errorText(error) })
841
+ }) : null;
842
+ let isFileReadable = true;
843
+ let loadPromise = null;
844
+ function load() {
845
+ const statePath = filePath;
846
+ if (!statePath) return Promise.resolve();
847
+ if (loadPromise) return loadPromise;
848
+ loadPromise = (async () => {
849
+ const outcome = await loadJsonStateFile({
850
+ filePath: statePath,
851
+ fsPromises,
852
+ parse,
853
+ nowMs
854
+ });
855
+ if (outcome.status === "unreadable") {
856
+ isFileReadable = false;
857
+ loadPromise = null;
858
+ warn(`${name} unreadable`, {
859
+ path: statePath,
860
+ error: errorText(outcome.error)
861
+ });
862
+ return;
863
+ }
864
+ if (outcome.status === "quarantined") warn(`${name} quarantined`, {
865
+ path: statePath,
866
+ movedTo: outcome.movedTo
867
+ });
868
+ isFileReadable = true;
869
+ adopt(outcome.status === "loaded" ? outcome.value : null);
870
+ writer?.reset();
871
+ })();
872
+ return loadPromise;
873
+ }
874
+ async function write(subject, buildPayload) {
875
+ if (!writer || !isFileReadable) return;
876
+ await writer.write(subject, buildPayload);
877
+ }
878
+ return {
879
+ load,
880
+ write,
881
+ idle: () => writer ? writer.idle() : Promise.resolve()
882
+ };
883
+ }
884
+ //#endregion
885
+ //#region server/config-store.ts
886
+ var DEFAULT_CONFIG = {
887
+ port: 3e3,
888
+ autoRecoverSeconds: 3,
889
+ inputGraceSeconds: 5,
890
+ promptDetectionMs: 1500,
891
+ notifyDebounceMs: 3e3,
892
+ phoneEscalationMs: 3e5,
893
+ cursorBlink: false,
894
+ debugMode: false,
895
+ calmLayout: false,
896
+ detectBackgroundAgents: true,
897
+ detectScheduledWakeups: true,
898
+ replayBufferKB: 512,
899
+ recordSignals: true,
900
+ trace: { enabled: true },
901
+ planReview: { enabled: true },
902
+ taskTitle: { refiner: {
903
+ enabled: true,
904
+ model: "haiku",
905
+ minIntervalSeconds: 60,
906
+ timeoutSeconds: 60
907
+ } },
908
+ changeMap: { narrator: {
909
+ enabled: false,
910
+ engine: "claude",
911
+ model: "",
912
+ timeoutSeconds: 90
913
+ } },
914
+ agentApi: { enabled: false },
915
+ telemetry: { enabled: true },
916
+ checkForUpdates: true,
917
+ updateChannel: "release",
918
+ autoResume: true,
919
+ skipPermissionsByDefault: false,
920
+ antiSlopPrompt: false,
921
+ rtk: false,
922
+ saneYolo: true,
923
+ telegramNotifications: false,
924
+ integrationBranch: null,
925
+ worktreeRoot: "",
926
+ worktreeShare: [
927
+ "node_modules",
928
+ ".env",
929
+ ".env.local",
930
+ ".claude"
931
+ ],
932
+ worktreeAutoRebase: true,
933
+ worktreeSyncOnStart: true,
934
+ worktreeRerere: true,
935
+ branchGc: {
936
+ enabled: true,
937
+ worktrees: true,
938
+ prefixes: [...DEFAULT_BRANCH_GC_PREFIXES],
939
+ dryRun: false,
940
+ staleDays: 14,
941
+ deleteUnmerged: false,
942
+ intervalMs: 216e5
943
+ },
944
+ repoRoots: [],
945
+ customAgents: [],
946
+ postTurnChecks: {
947
+ enabled: true,
948
+ mode: "report",
949
+ rules: {
950
+ trailingWs: true,
951
+ finalNewline: true,
952
+ bom: true,
953
+ slop: false
954
+ }
955
+ },
956
+ remote: {
957
+ enabled: false,
958
+ port: null,
959
+ publicHost: "",
960
+ allowedOrigins: []
961
+ },
962
+ workflows: {
963
+ enabled: true,
964
+ maxConcurrentSessions: 2,
965
+ maxActionsPerPoll: 20,
966
+ rules: []
967
+ },
968
+ projects: []
969
+ };
970
+ var DEFAULT_CONFIG_BY_KEY = DEFAULT_CONFIG;
971
+ var ABORT_CONFIG_SAVE = "abort-config-save";
972
+ function errorCode(error) {
973
+ const source = error ?? {};
974
+ if (typeof source.code === "string") return source.code;
975
+ return typeof source.message === "string" ? source.message : String(error);
976
+ }
977
+ function errorMessage(error) {
978
+ return error instanceof Error ? error.message : String(error);
979
+ }
980
+ function glimmervoidHomeDir() {
981
+ return glimmervoidHomeDir$1(os.homedir(), process.env);
982
+ }
983
+ function restrictMode(target, mode) {
984
+ try {
985
+ fs.chmodSync(target, mode);
986
+ } catch {}
987
+ }
988
+ function resolveConfigPath() {
989
+ const homeDir = glimmervoidHomeDir();
990
+ const decided = decideConfigPath({
991
+ env: process.env,
992
+ homeDir
993
+ }, (candidate) => fs.existsSync(candidate));
994
+ if (decided.path) return decided.path;
995
+ if (decided.source === "env") {
996
+ console.error(`Config file not found: ${decided.envPath}`);
997
+ process.exit(1);
998
+ }
999
+ const homeConfig = decided.homePath;
1000
+ fs.mkdirSync(homeDir, {
1001
+ recursive: true,
1002
+ mode: 448
1003
+ });
1004
+ restrictMode(homeDir, 448);
1005
+ fs.writeFileSync(homeConfig, JSON.stringify(DEFAULT_CONFIG, null, 2), {
1006
+ encoding: "utf8",
1007
+ mode: 384
1008
+ });
1009
+ restrictMode(homeConfig, 384);
1010
+ console.log(`Created default config at ${homeConfig}`);
1011
+ return homeConfig;
1012
+ }
1013
+ function generateProjectId() {
1014
+ return crypto.randomUUID();
1015
+ }
1016
+ function validateConfig(candidate) {
1017
+ if (!isPlainObject(candidate)) return {
1018
+ ok: false,
1019
+ errors: ["config must be a plain object"]
1020
+ };
1021
+ const parsedConfig = Config.safeParse(candidate);
1022
+ if (!parsedConfig.success) return {
1023
+ ok: false,
1024
+ errors: parsedConfig.error.issues.map((issue) => {
1025
+ if (issue.code === "custom") return issue.message;
1026
+ const [root, index, field] = issue.path;
1027
+ if (root === "port") return "port must be an integer from 0 to 65535";
1028
+ if (root === "repoRoots" || root === "worktreeShare") return `${root} must be an array of strings`;
1029
+ if (root === "remote") return "remote must be a plain object";
1030
+ if (root !== "projects" && typeof DEFAULT_CONFIG_BY_KEY[String(root)] === "number") return `${String(root)} must be a finite number greater than or equal to 0`;
1031
+ if (root !== "projects") return issue.message;
1032
+ if (typeof index !== "number") return "projects must be an array";
1033
+ if (!field) return `projects[${index}] must be a plain object`;
1034
+ if (field === "agent") return `projects[${index}].agent must be ${AGENT_ID_SHAPE_MESSAGE}`;
1035
+ if (field === "codexBypassHookTrust") return `projects[${index}].codexBypassHookTrust must be a boolean`;
1036
+ return `projects[${index}].${String(field)} must be a string`;
1037
+ })
1038
+ };
1039
+ return {
1040
+ ok: true,
1041
+ config: parsedConfig.data
1042
+ };
1043
+ }
1044
+ function normalizeConfigFile(candidate) {
1045
+ if (!isPlainObject(candidate)) throw new Error("config must be a plain object");
1046
+ const draft = candidate;
1047
+ for (const [key, fallback] of Object.entries(DEFAULT_CONFIG_BY_KEY)) {
1048
+ if (fallback === null || typeof fallback === "object") continue;
1049
+ if (!Object.hasOwn(draft, key)) continue;
1050
+ const fieldSchema = Config.shape[key];
1051
+ if (!fieldSchema || fieldSchema.safeParse(draft[key]).success) continue;
1052
+ console.warn(`[config] ${key} value ${JSON.stringify(draft[key])} is invalid; using ${JSON.stringify(fallback)}`);
1053
+ draft[key] = fallback;
1054
+ }
1055
+ const validation = validateConfig(draft);
1056
+ if (!validation.ok) throw new Error(`validation failed: ${validation.errors.join("; ")}`);
1057
+ return validation.config;
1058
+ }
1059
+ function writeBackupContent(backupPath, content) {
1060
+ try {
1061
+ fs.writeFileSync(backupPath, content, {
1062
+ encoding: "utf8",
1063
+ mode: 384
1064
+ });
1065
+ restrictMode(backupPath, 384);
1066
+ } catch (err) {
1067
+ console.warn(`[config] Failed to write backup ${backupPath}:`, errorCode(err));
1068
+ }
1069
+ }
1070
+ function loadConfigFile(configPath, { exitOnError = true } = {}) {
1071
+ const loadedContent = fs.readFileSync(configPath, "utf8");
1072
+ try {
1073
+ return {
1074
+ config: withEnvSecrets(normalizeConfigFile(JSON.parse(loadedContent)), readEnvSecrets(process.env)),
1075
+ loadedContent
1076
+ };
1077
+ } catch (err) {
1078
+ const invalidBackupPath = `${configPath}.invalid.bak`;
1079
+ try {
1080
+ fs.writeFileSync(invalidBackupPath, loadedContent, {
1081
+ encoding: "utf8",
1082
+ mode: 384
1083
+ });
1084
+ restrictMode(invalidBackupPath, 384);
1085
+ } catch (backupErr) {
1086
+ console.warn(`[config] Failed to save invalid config copy ${invalidBackupPath}:`, errorCode(backupErr));
1087
+ }
1088
+ const message = `[config] Could not load ${configPath}: ${errorMessage(err)}. The broken file was copied to ${invalidBackupPath} when possible. Restore from ${configPath}.boot.bak or ${configPath}.bak, then restart Glimmervoid.`;
1089
+ if (!exitOnError) return {
1090
+ error: err,
1091
+ message,
1092
+ invalidBackupPath
1093
+ };
1094
+ console.error(message);
1095
+ process.exit(1);
1096
+ }
1097
+ }
1098
+ function topLevelKeyCount(candidate) {
1099
+ if (!isPlainObject(candidate)) return 0;
1100
+ return Object.keys(candidate).length;
1101
+ }
1102
+ function isSuspectedExternalWipe(candidate, currentConfig) {
1103
+ const currentKeyCount = topLevelKeyCount(currentConfig);
1104
+ if (currentKeyCount === 0) return false;
1105
+ return topLevelKeyCount({
1106
+ ...candidate,
1107
+ branchGc: resolveBranchGc(candidate.branchGc)
1108
+ }) * 2 < currentKeyCount;
1109
+ }
1110
+ function warnInvalidConfig(action, validation) {
1111
+ console.warn(`[config] Refusing to ${action}; validation failed: ${validation.errors.join("; ")}. Recovery sources: config.json.bak and config.json.boot.bak.`);
1112
+ }
1113
+ function warnSuspectedWipe(action) {
1114
+ console.warn(`[config] Refusing to ${action}; config.json has fewer than half the top-level keys of the in-memory config. This looks like an external wipe. Recovery sources: config.json.bak and config.json.boot.bak.`);
1115
+ }
1116
+ function ensureProjectIds(projects) {
1117
+ let changed = false;
1118
+ for (const p of projects) if (!p.id) {
1119
+ p.id = generateProjectId();
1120
+ changed = true;
1121
+ }
1122
+ return changed;
1123
+ }
1124
+ function collectBranchGcIssues(branchGc) {
1125
+ if (!isPlainObject(branchGc)) return {
1126
+ block: { ...DEFAULT_CONFIG.branchGc },
1127
+ issues: []
1128
+ };
1129
+ const accepted = {};
1130
+ const issues = [];
1131
+ for (const [field, value] of Object.entries(branchGc)) {
1132
+ if (!Object.hasOwn(BranchGcFileSettings.shape, field)) {
1133
+ issues.push(`[config] Ignoring unknown branchGc.${field}; it is not a branchGc setting, so the default block applies.`);
1134
+ continue;
1135
+ }
1136
+ const parsed = BranchGcFileSettings.safeParse({ [field]: value });
1137
+ if (!parsed.success) {
1138
+ issues.push(`[config] Ignoring invalid branchGc.${field}; using the default instead: ${configIssueMessage(parsed.error)}`);
1139
+ continue;
1140
+ }
1141
+ accepted[field] = value;
1142
+ }
1143
+ return {
1144
+ block: {
1145
+ ...DEFAULT_CONFIG.branchGc,
1146
+ ...accepted
1147
+ },
1148
+ issues
1149
+ };
1150
+ }
1151
+ function resolveBranchGc(branchGc) {
1152
+ return collectBranchGcIssues(branchGc).block;
1153
+ }
1154
+ function resolveBranchGcAndWarn(branchGc) {
1155
+ const resolved = collectBranchGcIssues(branchGc);
1156
+ for (const issue of resolved.issues) console.warn(issue);
1157
+ return resolved.block;
1158
+ }
1159
+ var POSTHOG_SETTINGS_KEYS = Object.freeze([
1160
+ "enabled",
1161
+ "recurrenceDedupe",
1162
+ "trafficSpikeEnabled",
1163
+ "autoFix",
1164
+ "host",
1165
+ "repoPath",
1166
+ "projects",
1167
+ "projectMap",
1168
+ "intervalMinutes",
1169
+ "maxConcurrentInvestigations",
1170
+ "investigationTimeoutSeconds",
1171
+ "fixTimeoutSeconds",
1172
+ "minUsersToInvestigate",
1173
+ "userEscalationThreshold",
1174
+ "recurrenceWindowDays",
1175
+ "transientRecurrenceLimit",
1176
+ "trafficSpikeMultiplier",
1177
+ "trafficSpikeMinUsers",
1178
+ "trafficSpikeCooldownMinutes",
1179
+ "trafficSpikeBaselineDays"
1180
+ ]);
1181
+ var POSTHOG_SECRET_KEYS = Object.freeze(["apiKey"]);
1182
+ var TELEGRAM_SETTINGS_KEYS = Object.freeze(["chatId"]);
1183
+ var TELEGRAM_SECRET_KEYS = Object.freeze(["botToken"]);
1184
+ var SECRET_PRESENCE_SUFFIX = "Configured";
1185
+ function pickRedactedBlock(stored, allowedKeys, secretKeys) {
1186
+ if (!isPlainObject(stored)) return null;
1187
+ const source = stored;
1188
+ const redacted = {};
1189
+ for (const key of allowedKeys) if (source[key] !== void 0) redacted[key] = source[key];
1190
+ for (const secretKey of secretKeys) {
1191
+ const secret = source[secretKey];
1192
+ redacted[`${secretKey}${SECRET_PRESENCE_SUFFIX}`] = typeof secret === "string" && secret.length > 0;
1193
+ }
1194
+ return redacted;
1195
+ }
1196
+ function createConfigStore({ settingsDefaults } = {}) {
1197
+ const configPath = resolveConfigPath();
1198
+ const effectiveDefaults = {
1199
+ ...DEFAULT_CONFIG,
1200
+ ...settingsDefaults || {}
1201
+ };
1202
+ const launchDefaultKeys = new Set(Object.keys(settingsDefaults || {}));
1203
+ const envSecrets = readEnvSecrets(process.env);
1204
+ const loadedConfig = loadConfigFile(configPath);
1205
+ const config = loadedConfig.config;
1206
+ writeBackupContent(`${configPath}.boot.bak`, loadedConfig.loadedContent);
1207
+ config.repoRoots = config.repoRoots || [];
1208
+ config.branchGc = resolveBranchGcAndWarn(config.branchGc);
1209
+ if (Array.isArray(config.projects) && ensureProjectIds(config.projects)) try {
1210
+ writeJsonAtomicSync(configPath, withoutEnvSecrets(config, envSecrets), { mode: 384 });
1211
+ console.log("[config] Auto-assigned IDs to projects missing them");
1212
+ } catch (err) {
1213
+ console.warn("[config] Failed to persist auto-assigned project IDs:", errorMessage(err));
1214
+ }
1215
+ let _lastWrittenContent = null;
1216
+ let _lastAppliedContent = loadedConfig.loadedContent;
1217
+ function save(mutatorFn) {
1218
+ let loaded;
1219
+ try {
1220
+ loaded = loadConfigFile(configPath, { exitOnError: false });
1221
+ } catch (err) {
1222
+ console.warn("[config] Failed to read config.json for save:", errorCode(err));
1223
+ return null;
1224
+ }
1225
+ if ("error" in loaded) {
1226
+ console.warn(loaded.message);
1227
+ return null;
1228
+ }
1229
+ const freshConfig = loaded.config;
1230
+ const freshContent = loaded.loadedContent;
1231
+ const freshValidation = validateConfig(freshConfig);
1232
+ if (!freshValidation.ok) {
1233
+ warnInvalidConfig("save config.json", freshValidation);
1234
+ return null;
1235
+ }
1236
+ if (isSuspectedExternalWipe(freshConfig, config)) {
1237
+ warnSuspectedWipe("save config.json");
1238
+ return null;
1239
+ }
1240
+ if (mutatorFn(freshConfig) === "abort-config-save") return null;
1241
+ const effectiveConfig = withEnvSecrets(freshConfig, envSecrets);
1242
+ const mutatedValidation = validateConfig(effectiveConfig);
1243
+ if (!mutatedValidation.ok) {
1244
+ warnInvalidConfig("save config.json", mutatedValidation);
1245
+ return null;
1246
+ }
1247
+ try {
1248
+ const nextContent = JSON.stringify(withoutEnvSecrets(effectiveConfig, envSecrets), null, 2);
1249
+ if (freshContent !== nextContent) writeBackupContent(`${configPath}.bak`, freshContent);
1250
+ _lastWrittenContent = nextContent;
1251
+ _lastAppliedContent = null;
1252
+ writeTextAtomicSync(configPath, nextContent, { mode: 384 });
1253
+ } catch (err) {
1254
+ console.warn("[config] Failed to write config.json:", errorCode(err));
1255
+ return null;
1256
+ }
1257
+ return mutatedValidation.config;
1258
+ }
1259
+ function getSettings() {
1260
+ return {
1261
+ port: config.port,
1262
+ autoRecoverSeconds: config.autoRecoverSeconds,
1263
+ inputGraceSeconds: config.inputGraceSeconds,
1264
+ promptDetectionMs: config.promptDetectionMs,
1265
+ notifyDebounceMs: config.notifyDebounceMs,
1266
+ phoneEscalationMs: config.phoneEscalationMs ?? DEFAULT_CONFIG.phoneEscalationMs,
1267
+ replayBufferKB: config.replayBufferKB,
1268
+ cursorBlink: config.cursorBlink ?? effectiveDefaults.cursorBlink,
1269
+ debugMode: config.debugMode ?? effectiveDefaults.debugMode,
1270
+ calmLayout: config.calmLayout ?? effectiveDefaults.calmLayout,
1271
+ detectBackgroundAgents: config.detectBackgroundAgents ?? effectiveDefaults.detectBackgroundAgents,
1272
+ recordSignals: config.recordSignals ?? effectiveDefaults.recordSignals,
1273
+ trace: { enabled: config.trace?.enabled ?? DEFAULT_CONFIG.trace.enabled },
1274
+ agentApi: { enabled: config.agentApi?.enabled ?? DEFAULT_CONFIG.agentApi.enabled },
1275
+ telemetry: { enabled: config.telemetry?.enabled ?? DEFAULT_CONFIG.telemetry.enabled },
1276
+ telemetryForcedOff: isTelemetryForcedOff(process.env),
1277
+ antiSlopPrompt: config.antiSlopPrompt ?? effectiveDefaults.antiSlopPrompt,
1278
+ rtk: config.rtk ?? effectiveDefaults.rtk,
1279
+ saneYolo: config.saneYolo ?? effectiveDefaults.saneYolo,
1280
+ checkForUpdates: config.checkForUpdates ?? effectiveDefaults.checkForUpdates,
1281
+ updateChannel: config.updateChannel ?? effectiveDefaults.updateChannel,
1282
+ autoResume: config.autoResume ?? effectiveDefaults.autoResume,
1283
+ skipPermissionsByDefault: config.skipPermissionsByDefault ?? effectiveDefaults.skipPermissionsByDefault,
1284
+ telegramNotifications: config.telegramNotifications ?? effectiveDefaults.telegramNotifications,
1285
+ integrationBranch: config.integrationBranch === void 0 ? effectiveDefaults.integrationBranch : config.integrationBranch,
1286
+ worktreeRoot: config.worktreeRoot ?? effectiveDefaults.worktreeRoot,
1287
+ worktreeShare: config.worktreeShare ?? effectiveDefaults.worktreeShare,
1288
+ repoRoots: config.repoRoots,
1289
+ taskTitle: config.taskTitle ? { ...config.taskTitle } : { ...DEFAULT_CONFIG.taskTitle },
1290
+ changeMap: config.changeMap ? { ...config.changeMap } : null,
1291
+ branchGc: { ...config.branchGc },
1292
+ postTurnChecks: config.postTurnChecks ? { ...config.postTurnChecks } : { ...DEFAULT_CONFIG.postTurnChecks },
1293
+ visions: config.visions ? { ...config.visions } : null,
1294
+ teamReview: config.teamReview ? { ...config.teamReview } : null,
1295
+ benchmarks: config.benchmarks ? { ...config.benchmarks } : null,
1296
+ workflows: config.workflows ?? null,
1297
+ posthog: pickRedactedBlock(config.posthog, POSTHOG_SETTINGS_KEYS, POSTHOG_SECRET_KEYS),
1298
+ usage: config.usage ? { ...config.usage } : null,
1299
+ telegram: pickRedactedBlock(config.telegram, TELEGRAM_SETTINGS_KEYS, TELEGRAM_SECRET_KEYS),
1300
+ ingest: pickSettingsBlock(config.ingest, INGEST_SPEC),
1301
+ projectChoices: (config.projects || []).map((p) => ({
1302
+ id: p.id,
1303
+ name: p.name,
1304
+ path: p.path
1305
+ }))
1306
+ };
1307
+ }
1308
+ function isUnchosenLaunchDefault(target, key, value) {
1309
+ if (!launchDefaultKeys.has(key)) return false;
1310
+ if (key in target) return false;
1311
+ return value === effectiveDefaults[key];
1312
+ }
1313
+ function applySettings(newConfig) {
1314
+ for (const key of RUNTIME_CONFIG_SCALAR_KEYS) {
1315
+ if (key === "integrationBranch") {
1316
+ if (newConfig[key] === void 0) continue;
1317
+ config[key] = newConfig[key] === null || String(newConfig[key]) === "" ? null : String(newConfig[key]);
1318
+ continue;
1319
+ }
1320
+ if (newConfig[key] == null) continue;
1321
+ let nextValue = newConfig[key];
1322
+ if (typeof DEFAULT_CONFIG_BY_KEY[key] === "boolean") nextValue = !!nextValue;
1323
+ if (typeof DEFAULT_CONFIG_BY_KEY[key] === "string") nextValue = String(nextValue);
1324
+ if (typeof nextValue === "boolean" && isUnchosenLaunchDefault(config, key, nextValue)) continue;
1325
+ config[key] = nextValue;
1326
+ }
1327
+ config.repoRoots = newConfig.repoRoots || [];
1328
+ if (newConfig.postTurnChecks != null) config.postTurnChecks = newConfig.postTurnChecks;
1329
+ if (newConfig.worktreeShare != null) config.worktreeShare = newConfig.worktreeShare;
1330
+ if (newConfig.taskTitle != null) config.taskTitle = newConfig.taskTitle;
1331
+ if (newConfig.changeMap != null) config.changeMap = newConfig.changeMap;
1332
+ if (newConfig.branchGc != null) config.branchGc = resolveBranchGc(newConfig.branchGc);
1333
+ if (newConfig.visions != null) config.visions = newConfig.visions;
1334
+ if (newConfig.teamReview != null) config.teamReview = newConfig.teamReview;
1335
+ if (newConfig.benchmarks != null) config.benchmarks = newConfig.benchmarks;
1336
+ if (newConfig.posthog != null) config.posthog = newConfig.posthog;
1337
+ if (newConfig.usage != null) config.usage = newConfig.usage;
1338
+ if (newConfig.telegram != null) config.telegram = newConfig.telegram;
1339
+ if (newConfig.ingest != null) config.ingest = newConfig.ingest;
1340
+ if (newConfig.agentApi != null) config.agentApi = newConfig.agentApi;
1341
+ if (newConfig.telemetry != null) config.telemetry = newConfig.telemetry;
1342
+ if (newConfig.workflows == null) delete config.workflows;
1343
+ if (newConfig.workflows != null) config.workflows = newConfig.workflows;
1344
+ if (newConfig.coder == null) delete config.coder;
1345
+ if (newConfig.coder != null) config.coder = newConfig.coder;
1346
+ config.customAgents = newConfig.customAgents ?? [];
1347
+ config.hooks = Array.isArray(newConfig.hooks) ? newConfig.hooks : [];
1348
+ if (newConfig.port != null && newConfig.port !== config.port) console.log(`[settings] Port changed to ${newConfig.port} - restart required to take effect`);
1349
+ }
1350
+ function watchForChanges(callback) {
1351
+ let reloadTimer = null;
1352
+ let watcher = null;
1353
+ function handleConfigChange(err, data) {
1354
+ if (err) {
1355
+ console.warn("[config] Failed to read config.json:", err.code);
1356
+ return;
1357
+ }
1358
+ if (_lastWrittenContent !== null && data === _lastWrittenContent) return;
1359
+ if (_lastAppliedContent !== null && data === _lastAppliedContent) return;
1360
+ let newConfig;
1361
+ try {
1362
+ newConfig = withEnvSecrets(normalizeConfigFile(JSON.parse(data)), envSecrets);
1363
+ } catch (parseErr) {
1364
+ console.warn("[config] Invalid config.json:", errorMessage(parseErr));
1365
+ return;
1366
+ }
1367
+ newConfig.branchGc = resolveBranchGcAndWarn(newConfig.branchGc);
1368
+ if (isSuspectedExternalWipe(newConfig, config)) {
1369
+ warnSuspectedWipe("reload config.json");
1370
+ return;
1371
+ }
1372
+ _lastAppliedContent = data;
1373
+ _lastWrittenContent = null;
1374
+ callback(newConfig);
1375
+ console.log("[config] Reloaded config.json");
1376
+ }
1377
+ try {
1378
+ const canonicalConfigPath = canonicalizePath(configPath);
1379
+ const watchDir = path.dirname(canonicalConfigPath);
1380
+ const targetName = path.basename(canonicalConfigPath);
1381
+ watcher = fs.watch(watchDir, (_event, filename) => {
1382
+ if (filename != null && !equalsIgnoringCaseOnWindows(path.basename(String(filename)), targetName)) return;
1383
+ if (reloadTimer) clearTimeout(reloadTimer);
1384
+ reloadTimer = setTimeout(() => {
1385
+ fs.readFile(configPath, "utf8", handleConfigChange);
1386
+ }, 500);
1387
+ });
1388
+ console.log("[config] Watching config.json for changes");
1389
+ } catch (watchErr) {
1390
+ console.warn("[config] Failed to watch config.json:", errorMessage(watchErr));
1391
+ }
1392
+ return function stop() {
1393
+ if (reloadTimer) clearTimeout(reloadTimer);
1394
+ if (watcher) try {
1395
+ watcher.close();
1396
+ } catch {}
1397
+ watcher = null;
1398
+ };
1399
+ }
1400
+ return {
1401
+ config,
1402
+ configPath,
1403
+ save,
1404
+ getSettings,
1405
+ applySettings,
1406
+ isUnchosenLaunchDefault,
1407
+ watchForChanges,
1408
+ DEFAULT_CONFIG
1409
+ };
1410
+ }
1411
+ //#endregion
1412
+ export { sanitizeWorktreeName as $, decideTelemetryConsent as A, DEFAULT_BRANCH_GC_PREFIXES as B, adapterBucket as C, classifySessionExit as D, buildExceptionProperties as E, stableConfigKey as F, usablePrefixes as G, planBranchGc as H, isPlainObject as I, caseFoldedPathKey as J, worktreeIntegrationTips as K, numberOrNull as L, nodeMajorVersion as M, parsePosthogReleaseId as N, decideIsDevInstall as O, resolveProjectToken as P, safePathSegment as Q, safeNumber as R, TELEMETRY_FLAGS_URL as S, buildBrowserExceptionProperties as T, planWorktreeGc as U, matchedPrefix as V, sessionIdFromBranch as W, equalsIgnoringCaseOnWindows as X, comparableDirectoryPath as Y, isSameDirectoryPath as Z, INGEST_SPEC as _, ensureProjectIds as a, FIRST_RUN_NOTICE as b, loadConfigFile as c, appendJsonLine as d, appendJsonLines as f, writeJsonAtomicSync as g, writeJsonAtomic as h, createConfigStore as i, exceptionFingerprint as j, decideRemoteTelemetryState as k, resolveConfigPath as l, createJsonStateWriter as m, DEFAULT_CONFIG as n, generateProjectId as o, createJsonStateStore as p, canonicalizePath as q, SECRET_PRESENCE_SUFFIX as r, glimmervoidHomeDir as s, ABORT_CONFIG_SAVE as t, validateConfig as u, mergeSettingsBlock as v, buildAiGenerationEvents as w, TELEMETRY_BATCH_URL as x, validateSettingsBlock as y, stringOrNull as z };