glimmervoid 0.30.0 → 0.32.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 (68) hide show
  1. package/README.md +1 -1
  2. package/dist/bin/glimmervoid.js +144 -57
  3. package/dist/chunks/{adapters-Dho1VSY5.js → adapters-BU74M4QZ.js} +1 -1
  4. package/dist/chunks/{adapters-zPDx1Xox.js → adapters-CoC18rdt.js} +56 -31
  5. package/dist/chunks/agent-api-cli-V_BkYhBI.js +142 -0
  6. package/dist/chunks/agent-api-core-B3YI8SYs.js +192 -0
  7. package/dist/chunks/{agent-setup-cli-B5Atxr3F.js → agent-setup-cli-D4956BfN.js} +11 -28
  8. package/dist/chunks/backoff-DjVpfHk7.js +14 -0
  9. package/dist/chunks/child-process-safe-B3zcPdFM.js +2 -0
  10. package/dist/chunks/{child-process-safe-DRGsp0TE.js → child-process-safe-DDELk9XC.js} +3 -2
  11. package/dist/chunks/coerce-NSst8oRP.js +32 -0
  12. package/dist/chunks/{contracts-DGkKNh9W.js → config-CZ4H51D0.js} +374 -1684
  13. package/dist/chunks/{config-store-mm7MgzoL.js → config-store-BVtdHkDK.js} +594 -329
  14. package/dist/chunks/config-store-Cp62jN1O.js +2 -0
  15. package/dist/chunks/contracts-DXujp-Zn.js +2131 -0
  16. package/dist/chunks/git-version-CrLW4a6r.js +58 -0
  17. package/dist/chunks/{grok-DuU8xVLZ.js → grok-C4lzPFEg.js} +24 -21
  18. package/dist/chunks/{hook-relay-core-BHfYFnYV.js → hook-relay-core-DA_bz2KA.js} +10 -5
  19. package/dist/chunks/{hook-tools-pSJia7ks.js → hook-tools-UX2fmt81.js} +3 -5
  20. package/dist/chunks/{ingest-editor-core-BuI1gMF_.js → ingest-editor-core-vR6Wv6bP.js} +24 -13
  21. package/dist/chunks/json-core-dfa_7Fak.js +23 -0
  22. package/dist/chunks/json-file-oEH9vt-0.js +352 -0
  23. package/dist/chunks/knowledge-graph-cli-Bjy9_kv5.js +1354 -0
  24. package/dist/chunks/{loopback-post-C7C22eYi.js → loopback-post-DepQfdcM.js} +40 -26
  25. package/dist/chunks/{main-CuIZQPhZ.js → main-DSbpYn2Y.js} +27880 -19139
  26. package/dist/chunks/{node-pty-preflight-CK1knGIR.js → node-pty-preflight-C8QIlrtU.js} +4 -6
  27. package/dist/chunks/{pair-cli-w-gCAF-1.js → pair-cli-CKn-wUw9.js} +4 -3
  28. package/dist/chunks/paths-CeJl2Vcg.js +55 -0
  29. package/dist/chunks/{remote-config-C2ytQwrs.js → remote-config-DP44Ybt9.js} +37 -41
  30. package/dist/chunks/{rtk-hook-core-Bl6gYHPN.js → rtk-hook-core-BmEz2aq9.js} +3 -5
  31. package/dist/chunks/rtk-resolver-CIVLE_xk.js +2 -0
  32. package/dist/chunks/{rtk-resolver-CLo-gZzw.js → rtk-resolver-jJxy_Mrt.js} +2 -2
  33. package/dist/chunks/{runtime-paths-Ci_ksSkj.js → runtime-paths-BPoxENmK.js} +2 -6
  34. package/dist/chunks/runtime-paths-DU5jBEEO.js +2 -0
  35. package/dist/chunks/{sandbox-deps-Dd5-kZnf.js → sandbox-deps-DuaNFq7n.js} +16 -13
  36. package/dist/chunks/sandbox-proxy-core-hnQgKR0T.js +27 -0
  37. package/dist/chunks/{session-Cez8aIWg.js → session-B8fJb_Or.js} +25 -2
  38. package/dist/chunks/text-DoirglWV.js +21 -0
  39. package/dist/chunks/timer-deps-u2DAQLok.js +15 -0
  40. package/dist/chunks/{visions-cli-BXQFIBCX.js → visions-cli-BhFdym5n.js} +1 -1
  41. package/dist/chunks/{visions-setup-CvVnOe1r.js → visions-setup-CfgNHiG3.js} +20 -357
  42. package/dist/client/assets/index-C_XKsDFx.css +1 -0
  43. package/dist/client/assets/index-CoqWjYxP.js +254 -0
  44. package/dist/client/fonts/CommitMono-OFL.txt +92 -0
  45. package/dist/client/fonts/commit-mono-400-italic.woff2 +0 -0
  46. package/dist/client/fonts/commit-mono-400-regular.woff2 +0 -0
  47. package/dist/client/fonts/commit-mono-700-italic.woff2 +0 -0
  48. package/dist/client/fonts/commit-mono-700-regular.woff2 +0 -0
  49. package/dist/client/index.html +7 -24
  50. package/dist/client/octicons-LICENSE.txt +21 -0
  51. package/dist/scripts/postinstall-path-check.js +1 -1
  52. package/dist/server/index.js +3 -3
  53. package/dist/session/command-hook-relay.js +1 -1
  54. package/dist/session/hook-relay.js +3 -3
  55. package/dist/session/hook-tool-relay.js +2 -2
  56. package/dist/session/sandbox-open-launcher.js +79 -0
  57. package/dist/session/statusline-relay.js +8 -18
  58. package/dist/session/visions-relay.js +20 -11
  59. package/package.json +4 -1
  60. package/dist/chunks/agent-api-cli-D0MmtEdz.js +0 -71
  61. package/dist/chunks/agent-api-core-Dz0r21y9.js +0 -103
  62. package/dist/chunks/child-process-safe-DvQjXsgz.js +0 -2
  63. package/dist/chunks/config-store-CC8T3okC.js +0 -2
  64. package/dist/chunks/ingest-core-DWI1Mbu4.js +0 -342
  65. package/dist/chunks/rtk-resolver-CMQAidlI.js +0 -2
  66. package/dist/chunks/runtime-paths-JLrZ3wyi.js +0 -2
  67. package/dist/client/assets/index-B1MFijjb.css +0 -1
  68. package/dist/client/assets/index-CU1iPPdn.js +0 -249
@@ -1,50 +1,14 @@
1
+ import { n as errorLabel, r as errorMessage } from "./text-DoirglWV.js";
2
+ import { i as equalsIgnoringCaseOnWindows, t as canonicalizePath } from "./paths-CeJl2Vcg.js";
1
3
  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 { a as positiveIntOr, c as textOr, n as isRecord } from "./coerce-NSst8oRP.js";
5
+ import { d as writeJsonAtomicSync, f as writeTextAtomic, p as writeTextAtomicSync } from "./json-file-oEH9vt-0.js";
6
+ import { St as normalizeLegacyConfigNumbers, f as BranchGcFileSettings, i as Config, l as configIssueMessage, m as DEFAULT_FACTORY_PROTECTED_PATHS, n as BUILTIN_AGENT_IDS, p as DEFAULT_FACTORY_CHECKS, s as RUNTIME_CONFIG_SCALAR_KEYS, t as AGENT_ID_SHAPE_MESSAGE } from "./config-CZ4H51D0.js";
7
+ import { V as FeatureFlagsResponse, W as TELEMETRY_KILL_SWITCH_FLAG } from "./contracts-DXujp-Zn.js";
4
8
  import fs from "node:fs";
5
9
  import path from "node:path";
6
10
  import os from "node:os";
7
- import { promisify } from "node:util";
8
11
  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
12
  var DEFAULT_BRANCH_GC_PREFIXES = ["glimmervoid/session/", "worktree-agent-"];
49
13
  var DAY_MS = 864e5;
50
14
  var STALE_DELETION_REASON = "stale-orphan";
@@ -201,23 +165,6 @@ function planWorktreeGc({ worktrees, liveWorktreePaths, integrationTips, prefixe
201
165
  };
202
166
  }
203
167
  //#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
168
  //#region server/core/config-secrets-core.ts
222
169
  var ENV_SECRET_BINDINGS = Object.freeze([{
223
170
  blockName: "posthog",
@@ -242,7 +189,7 @@ function readEnvSecrets(env) {
242
189
  }
243
190
  function copyOfBlock(config, blockName) {
244
191
  const block = config[blockName];
245
- if (!isPlainObject(block)) return {};
192
+ if (!isRecord(block)) return {};
246
193
  return { ...block };
247
194
  }
248
195
  function withEnvSecrets(config, envSecrets) {
@@ -258,7 +205,7 @@ function withoutEnvSecrets(config, envSecrets) {
258
205
  if (envSecrets.length === 0) return config;
259
206
  const stripped = { ...config };
260
207
  for (const secret of envSecrets) {
261
- if (!isPlainObject(stripped[secret.blockName])) continue;
208
+ if (!isRecord(stripped[secret.blockName])) continue;
262
209
  const remaining = copyOfBlock(stripped, secret.blockName);
263
210
  delete remaining[secret.secretKey];
264
211
  if (Object.keys(remaining).length === 0) {
@@ -271,7 +218,7 @@ function withoutEnvSecrets(config, envSecrets) {
271
218
  }
272
219
  function withSortedKeys(value) {
273
220
  if (Array.isArray(value)) return value.map(withSortedKeys);
274
- if (!isPlainObject(value)) return value;
221
+ if (!isRecord(value)) return value;
275
222
  const sorted = {};
276
223
  for (const key of Object.keys(value).sort()) sorted[key] = withSortedKeys(value[key]);
277
224
  return sorted;
@@ -285,7 +232,7 @@ var POSTHOG_INGEST_HOST = "https://us.i.posthog.com";
285
232
  var TELEMETRY_BATCH_URL = `${POSTHOG_INGEST_HOST}/batch/`;
286
233
  var TELEMETRY_FLAGS_URL = `${POSTHOG_INGEST_HOST}/flags?v=2`;
287
234
  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");
235
+ 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, per session token counts, models and estimated cost, and factory outcomes as the kind of decision, how it came out, its duration and attempt number. No error messages, paths, repository names, prompts, objectives, model output or terminal output.", "Turn it off in Settings > Privacy, or set GLIMMERVOID_TELEMETRY=0 or DO_NOT_TRACK=1."].join("\n");
289
236
  var TRUTHY_FLAGS = /* @__PURE__ */ new Set([
290
237
  "1",
291
238
  "true",
@@ -594,11 +541,395 @@ function decideIsDevInstall({ installFlavor, isBundled, homeDir, defaultHomeDir
594
541
  if (installFlavor === "clone" || !isBundled) return true;
595
542
  return comparablePath(homeDir) !== comparablePath(defaultHomeDir);
596
543
  }
544
+ function buildFactoryDecisionEvent(decision) {
545
+ return {
546
+ kind: decision.kind,
547
+ outcome: decision.outcome,
548
+ failure_class: decision.failureClass,
549
+ duration_ms: decision.durationMs,
550
+ attempt: decision.attempt
551
+ };
552
+ }
597
553
  function nodeMajorVersion(nodeVersion) {
598
554
  const major = Number.parseInt(nodeVersion.replace(/^v/, ""), 10);
599
555
  return Number.isFinite(major) ? major : 0;
600
556
  }
601
557
  //#endregion
558
+ //#region shared/display-text.ts
559
+ var SECONDS_PER_MINUTE = 60;
560
+ var HOURS_PER_DAY = 24;
561
+ var UNIT_WORDS = {
562
+ compact: {
563
+ second: (count) => `${count}s`,
564
+ minute: (count) => `${count}m`,
565
+ hour: (count) => `${count}h`,
566
+ day: (count) => `${count}d`
567
+ },
568
+ abbreviated: {
569
+ second: (count) => `${count} sec`,
570
+ minute: (count) => `${count} min`,
571
+ hour: (count) => `${count} hr`,
572
+ day: (count) => `${count} ${count === 1 ? "day" : "days"}`
573
+ },
574
+ spelled: {
575
+ second: (count) => `${count} ${count === 1 ? "second" : "seconds"}`,
576
+ minute: (count) => `${count} ${count === 1 ? "minute" : "minutes"}`,
577
+ hour: (count) => `${count} ${count === 1 ? "hour" : "hours"}`,
578
+ day: (count) => `${count} ${count === 1 ? "day" : "days"}`
579
+ }
580
+ };
581
+ var COMPACT_FLOORED_AGO = {
582
+ rounding: "floor",
583
+ wording: "compact",
584
+ suffix: " ago",
585
+ justNowBelowSeconds: 0
586
+ };
587
+ function pad2(value) {
588
+ return String(value).padStart(2, "0");
589
+ }
590
+ function wholeSeconds(elapsedMs, rounding) {
591
+ return Math.max(0, Math[rounding](elapsedMs / 1e3));
592
+ }
593
+ function largestUnitText(totalSeconds, wording) {
594
+ const words = UNIT_WORDS[wording];
595
+ if (totalSeconds < SECONDS_PER_MINUTE) return words.second(totalSeconds);
596
+ const minutes = Math.floor(totalSeconds / SECONDS_PER_MINUTE);
597
+ if (minutes < 60) return words.minute(minutes);
598
+ const hours = Math.floor(minutes / 60);
599
+ if (hours < HOURS_PER_DAY) return words.hour(hours);
600
+ return words.day(Math.floor(hours / HOURS_PER_DAY));
601
+ }
602
+ function elapsedText(elapsedMs, options) {
603
+ const totalSeconds = wholeSeconds(elapsedMs, options.rounding);
604
+ if (totalSeconds < options.justNowBelowSeconds) return "just now";
605
+ return `${largestUnitText(totalSeconds, options.wording)}${options.suffix}`;
606
+ }
607
+ function localDayKey(moment) {
608
+ const date = new Date(moment);
609
+ return `${date.getFullYear()}-${pad2(date.getMonth() + 1)}-${pad2(date.getDate())}`;
610
+ }
611
+ //#endregion
612
+ //#region server/core/ingest-core.ts
613
+ var SOURCE_NAMES = [
614
+ "terminal",
615
+ "agentLogs",
616
+ "git",
617
+ "fs",
618
+ "shellHistory",
619
+ "editor"
620
+ ];
621
+ var SOURCE_DEFAULTS = Object.freeze({
622
+ terminal: Object.freeze({
623
+ maxEntries: 200,
624
+ maxBytes: 262144,
625
+ digestQuota: 8,
626
+ flushMs: 500,
627
+ accumulatorBytes: 8192,
628
+ windowBytes: 65536
629
+ }),
630
+ agentLogs: Object.freeze({
631
+ maxEntries: 200,
632
+ maxBytes: 131072,
633
+ digestQuota: 8,
634
+ pollMs: 2e3
635
+ }),
636
+ git: Object.freeze({
637
+ maxEntries: 100,
638
+ maxBytes: 65536,
639
+ digestQuota: 6,
640
+ debounceMs: 1e3,
641
+ pollMs: 6e4
642
+ }),
643
+ fs: Object.freeze({
644
+ maxEntries: 500,
645
+ maxBytes: 131072,
646
+ digestQuota: 8,
647
+ batchMs: 500
648
+ }),
649
+ shellHistory: Object.freeze({
650
+ maxEntries: 100,
651
+ maxBytes: 32768,
652
+ digestQuota: 6,
653
+ pollMs: 2e3
654
+ }),
655
+ editor: Object.freeze({
656
+ maxEntries: 100,
657
+ maxBytes: 32768,
658
+ digestQuota: 6
659
+ })
660
+ });
661
+ var KINDS_BY_SOURCE = Object.freeze({
662
+ terminal: Object.freeze(["output"]),
663
+ agentLogs: Object.freeze(["agent-turn", "agent-tool"]),
664
+ git: Object.freeze([
665
+ "commit",
666
+ "status-change",
667
+ "branch-change"
668
+ ]),
669
+ fs: Object.freeze(["file-change"]),
670
+ shellHistory: Object.freeze(["command"]),
671
+ editor: Object.freeze([
672
+ "doc-save",
673
+ "doc-open",
674
+ "doc-close"
675
+ ])
676
+ });
677
+ var MAX_DETAIL_CHARS = 1e3;
678
+ var MAX_DETAIL_KEYS = 12;
679
+ var DEFAULT_DIGEST_BUDGET_CHARS = 2e3;
680
+ var SCRUB_PLACEHOLDER = "[scrubbed]";
681
+ var DIGEST_HEADER = "Recent activity on this machine, newest first:";
682
+ var LIST_KEYS = Object.freeze({
683
+ fs: "roots",
684
+ shellHistory: "shells"
685
+ });
686
+ function stringList(raw) {
687
+ if (!Array.isArray(raw)) return [];
688
+ const values = [];
689
+ for (const entry of raw) {
690
+ if (typeof entry !== "string" || !entry.trim() || values.includes(entry.trim())) continue;
691
+ values.push(entry.trim());
692
+ }
693
+ return values;
694
+ }
695
+ function disabledSource(name) {
696
+ const source = {
697
+ enabled: false,
698
+ ...SOURCE_DEFAULTS[name]
699
+ };
700
+ const listKey = LIST_KEYS[name];
701
+ if (listKey) source[listKey] = [];
702
+ return source;
703
+ }
704
+ function disabledConfig() {
705
+ const sources = {};
706
+ for (const name of SOURCE_NAMES) sources[name] = disabledSource(name);
707
+ return {
708
+ enabled: false,
709
+ sources
710
+ };
711
+ }
712
+ function resolveSource(name, raw) {
713
+ const defaults = SOURCE_DEFAULTS[name];
714
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return disabledSource(name);
715
+ const rawSource = raw;
716
+ const resolved = { enabled: rawSource.enabled === true };
717
+ for (const [key, value] of Object.entries(defaults)) resolved[key] = positiveIntOr(rawSource[key], value);
718
+ const listKey = LIST_KEYS[name];
719
+ if (listKey) resolved[listKey] = stringList(rawSource[listKey]);
720
+ return resolved;
721
+ }
722
+ function resolveIngestConfig(raw) {
723
+ if (!isRecord(raw)) return disabledConfig();
724
+ if (raw.enabled !== true) return disabledConfig();
725
+ const rawSourcesValue = raw.sources;
726
+ const rawSources = isRecord(rawSourcesValue) ? rawSourcesValue : {};
727
+ const sources = {};
728
+ for (const name of SOURCE_NAMES) sources[name] = resolveSource(name, rawSources[name]);
729
+ return {
730
+ enabled: true,
731
+ sources
732
+ };
733
+ }
734
+ function enabledSourceNames(config) {
735
+ if (!config || config.enabled !== true) return [];
736
+ return SOURCE_NAMES.filter((name) => config.sources[name] && config.sources[name].enabled === true);
737
+ }
738
+ var SECRET_WORD = "(?:password|passwd|pwd|token|api[_-]?key|secret|credential|auth[_-]?token|access[_-]?key|secret[_-]?key|private[_-]?key|client[_-]?secret|connection[_-]?string)";
739
+ var QUOTED_OR_BARE = "(?:\"[^\"\\r\\n]*\"|'[^'\\r\\n]*'|[^\\s\\r\\n]+)";
740
+ var GAP = "[^\\S\\r\\n]";
741
+ var FLAG_REACH = "[^\\r\\n;&|]{0,200}?";
742
+ var SCRUB_PATTERNS = [
743
+ new RegExp(`(${SECRET_WORD}${GAP}*(?:=>|[:=])${GAP}*)${QUOTED_OR_BARE}`, "gi"),
744
+ new RegExp(`((?:^|\\s)--?${SECRET_WORD}${GAP}+)${QUOTED_OR_BARE}`, "gi"),
745
+ new RegExp(`((?:^|\\s)-(?:pwd|pw)(?![a-z])${GAP}*=?${GAP}*)${QUOTED_OR_BARE}`, "g"),
746
+ new RegExp(`(-AsPlainText${GAP}+)${QUOTED_OR_BARE}`, "gi"),
747
+ new RegExp(`((?:^|\\s)setx${GAP}+[^\\s\\r\\n]*${SECRET_WORD}[^\\s\\r\\n]*${GAP}+)${QUOTED_OR_BARE}`, "gi"),
748
+ new RegExp(`(\\bBearer${GAP}+)[A-Za-z0-9._~+/=-]{8,}`, "gi"),
749
+ new RegExp(`(\\bauthorization${GAP}*:${GAP}*Basic${GAP}+)[A-Za-z0-9+/=_-]{8,}`, "gi"),
750
+ new RegExp(`(\\bdocker${GAP}+login\\b${FLAG_REACH}${GAP}-p(?:${GAP}+|=))${QUOTED_OR_BARE}`, "gi"),
751
+ new RegExp(`(\\bcurl\\b${FLAG_REACH}${GAP}(?:--user|-u)(?:${GAP}+|=)[^\\s:\\r\\n]+:)${QUOTED_OR_BARE}`, "gi"),
752
+ new RegExp(`(\\b(?:mysql|mysqldump|mysqladmin|mysqlshow|mariadb|mariadb-dump)\\b${FLAG_REACH}${GAP}-p)${QUOTED_OR_BARE}`, "g"),
753
+ /\b([a-z][a-z0-9+.-]*:\/\/[^\s:/@]+:)[^\s@/]+(?=@)/gi
754
+ ];
755
+ var SECRET_SHAPE_PATTERNS = [
756
+ /\bAKIA[0-9A-Z]{16}\b/g,
757
+ /\bgh[pousr]_[A-Za-z0-9]{16,}\b/g,
758
+ /\bgithub_pat_[A-Za-z0-9_]{20,}\b/g,
759
+ /\bxox[abprs]-[A-Za-z0-9-]{10,}\b/g,
760
+ /\beyJ[A-Za-z0-9_=-]{6,}\.[A-Za-z0-9_=-]{6,}\.[A-Za-z0-9_=-]{4,}/g,
761
+ /-----BEGIN(?:[A-Z0-9 ]+)? PRIVATE KEY-----/g
762
+ ];
763
+ function scrubText(text) {
764
+ if (typeof text !== "string" || !text) return typeof text === "string" ? text : "";
765
+ let scrubbed = text;
766
+ for (const pattern of SCRUB_PATTERNS) {
767
+ pattern.lastIndex = 0;
768
+ scrubbed = scrubbed.replace(pattern, (_match, prefix) => `${prefix}${SCRUB_PLACEHOLDER}`);
769
+ }
770
+ for (const pattern of SECRET_SHAPE_PATTERNS) {
771
+ pattern.lastIndex = 0;
772
+ scrubbed = scrubbed.replace(pattern, SCRUB_PLACEHOLDER);
773
+ }
774
+ return scrubbed;
775
+ }
776
+ function scrubDetail(detail) {
777
+ if (!detail || typeof detail !== "object" || Array.isArray(detail)) return null;
778
+ const scrubbed = {};
779
+ let keys = 0;
780
+ for (const [key, value] of Object.entries(detail)) {
781
+ if (keys >= MAX_DETAIL_KEYS) break;
782
+ keys += 1;
783
+ if (typeof value === "string") {
784
+ scrubbed[key] = scrubText(value).slice(0, MAX_DETAIL_CHARS);
785
+ continue;
786
+ }
787
+ if (typeof value === "number" && Number.isFinite(value)) scrubbed[key] = value;
788
+ if (typeof value === "boolean") scrubbed[key] = value;
789
+ }
790
+ return scrubbed;
791
+ }
792
+ function normalizeScope(raw) {
793
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return {
794
+ root: null,
795
+ sessionId: null
796
+ };
797
+ const rawScope = raw;
798
+ return {
799
+ root: textOr(rawScope.root, null),
800
+ sessionId: textOr(rawScope.sessionId, null)
801
+ };
802
+ }
803
+ function normalizeEvent(raw, { seq, now }) {
804
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
805
+ const rawEvent = raw;
806
+ const source = textOr(rawEvent.source, null);
807
+ if (!source || !KINDS_BY_SOURCE[source]) return null;
808
+ const kind = textOr(rawEvent.kind, null);
809
+ if (!kind || !KINDS_BY_SOURCE[source].includes(kind)) return null;
810
+ const summary = scrubText(typeof rawEvent.summary === "string" ? rawEvent.summary : "").replace(/\s+/g, " ").trim().slice(0, 400);
811
+ if (!summary) return null;
812
+ const ts = Number(rawEvent.ts);
813
+ return {
814
+ source,
815
+ kind,
816
+ ts: Number.isFinite(ts) && ts > 0 ? Math.floor(ts) : now,
817
+ seq,
818
+ scope: normalizeScope(rawEvent.scope),
819
+ summary,
820
+ detail: scrubDetail(rawEvent.detail)
821
+ };
822
+ }
823
+ function eventBytes(event) {
824
+ return Buffer.byteLength(JSON.stringify(event), "utf8");
825
+ }
826
+ function createRing({ maxEntries, maxBytes }) {
827
+ return {
828
+ entries: [],
829
+ totalBytes: 0,
830
+ maxEntries,
831
+ maxBytes
832
+ };
833
+ }
834
+ function pushToRing(ring, event) {
835
+ const bytes = eventBytes(event);
836
+ ring.entries.push({
837
+ event,
838
+ bytes
839
+ });
840
+ ring.totalBytes += bytes;
841
+ while (ring.entries.length > 1 && (ring.entries.length > ring.maxEntries || ring.totalBytes > ring.maxBytes)) {
842
+ const evicted = ring.entries.shift();
843
+ ring.totalBytes -= evicted ? evicted.bytes : 0;
844
+ }
845
+ return event;
846
+ }
847
+ function createIngestStore(config) {
848
+ const resolved = config?.sources ? config : disabledConfig();
849
+ const rings = /* @__PURE__ */ new Map();
850
+ for (const name of enabledSourceNames(resolved)) rings.set(name, createRing(resolved.sources[name]));
851
+ return {
852
+ config: resolved,
853
+ rings,
854
+ seq: 0
855
+ };
856
+ }
857
+ function publishEvent(store, raw, now) {
858
+ const source = textOr((raw ?? null)?.source, null);
859
+ if (!source) return null;
860
+ const ring = store.rings.get(source);
861
+ if (!ring) return null;
862
+ const event = normalizeEvent(raw, {
863
+ seq: store.seq + 1,
864
+ now
865
+ });
866
+ if (!event) return null;
867
+ store.seq += 1;
868
+ return pushToRing(ring, event);
869
+ }
870
+ function latestSeq(store) {
871
+ const seq = Number(store?.seq);
872
+ if (!Number.isFinite(seq)) return 0;
873
+ return seq;
874
+ }
875
+ function snapshotEvents(store, { limit = 200 } = {}) {
876
+ const all = [];
877
+ for (const ring of store.rings.values()) for (const entry of ring.entries) all.push(entry.event);
878
+ all.sort((left, right) => right.seq - left.seq);
879
+ return all.slice(0, Math.max(0, Math.floor(limit)));
880
+ }
881
+ function ringStats(store) {
882
+ const stats = [];
883
+ for (const [source, ring] of store.rings) stats.push({
884
+ source,
885
+ events: ring.entries.length,
886
+ bytes: ring.totalBytes
887
+ });
888
+ return stats;
889
+ }
890
+ var SOURCE_LABELS = Object.freeze({
891
+ terminal: "terminal",
892
+ agentLogs: "agent",
893
+ git: "git",
894
+ fs: "files",
895
+ shellHistory: "shell",
896
+ editor: "editor"
897
+ });
898
+ function digestLine(event, now) {
899
+ const label = SOURCE_LABELS[event.source] || event.source;
900
+ const scope = event.scope.root ? "" : " (machine scope)";
901
+ return `- ${label} ${elapsedText(now - event.ts, COMPACT_FLOORED_AGO)}${scope}: ${event.summary}`;
902
+ }
903
+ function matchesScopes(event, scopes) {
904
+ if (!Array.isArray(scopes) || scopes.length === 0) return true;
905
+ if (!event.scope.root) return true;
906
+ return scopes.includes(event.scope.root);
907
+ }
908
+ function buildContextDigest(store, { scopes = null, budgetChars = DEFAULT_DIGEST_BUDGET_CHARS, now = NaN } = {}) {
909
+ const budget = positiveIntOr(budgetChars, DEFAULT_DIGEST_BUDGET_CHARS);
910
+ const candidates = [];
911
+ const takenBySource = /* @__PURE__ */ new Map();
912
+ for (const event of snapshotEvents(store, { limit: Number.MAX_SAFE_INTEGER })) {
913
+ if (!matchesScopes(event, scopes)) continue;
914
+ const quota = store.config.sources[event.source].digestQuota;
915
+ const taken = takenBySource.get(event.source) || 0;
916
+ if (taken >= quota) continue;
917
+ takenBySource.set(event.source, taken + 1);
918
+ candidates.push(event);
919
+ }
920
+ if (candidates.length === 0) return "";
921
+ const lines = [DIGEST_HEADER];
922
+ let used = 46;
923
+ for (const event of candidates) {
924
+ const line = scrubText(digestLine(event, now));
925
+ if (used + line.length + 1 > budget) break;
926
+ lines.push(line);
927
+ used += line.length + 1;
928
+ }
929
+ if (lines.length === 1) return "";
930
+ return lines.join("\n");
931
+ }
932
+ //#endregion
602
933
  //#region server/core/settings-block-core.ts
603
934
  var NO_BLOCKS = Object.freeze({});
604
935
  var INGEST_SOURCES_SPEC = Object.freeze({
@@ -617,7 +948,7 @@ var INGEST_SPEC = Object.freeze({
617
948
  });
618
949
  function validateSettingsBlock(block, spec) {
619
950
  if (block == null) return null;
620
- if (!isPlainObject(block)) return `${spec.name} must be an object`;
951
+ if (!isRecord(block)) return `${spec.name} must be an object`;
621
952
  for (const [key, value] of Object.entries(block)) {
622
953
  if (value == null) continue;
623
954
  if (spec.booleans.includes(key)) {
@@ -634,7 +965,7 @@ function validateSettingsBlock(block, spec) {
634
965
  return null;
635
966
  }
636
967
  function pickSettingsBlock(stored, spec) {
637
- if (!isPlainObject(stored)) return null;
968
+ if (!isRecord(stored)) return null;
638
969
  const fields = stored;
639
970
  const out = {};
640
971
  for (const key of spec.booleans) if (fields[key] != null) out[key] = !!fields[key];
@@ -645,8 +976,8 @@ function pickSettingsBlock(stored, spec) {
645
976
  return out;
646
977
  }
647
978
  function mergeSettingsBlock(stored, incoming, spec) {
648
- const out = isPlainObject(stored) ? { ...stored } : {};
649
- if (!isPlainObject(incoming)) return out;
979
+ const out = isRecord(stored) ? { ...stored } : {};
980
+ if (!isRecord(incoming)) return out;
650
981
  const fields = incoming;
651
982
  for (const key of spec.booleans) if (fields[key] != null) out[key] = !!fields[key];
652
983
  for (const [key, nested] of Object.entries(spec.blocks)) {
@@ -656,230 +987,89 @@ function mergeSettingsBlock(stored, incoming, spec) {
656
987
  return out;
657
988
  }
658
989
  //#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 };
990
+ //#region server/core/rtk-install-core.ts
991
+ var RTK_VERSION = "0.45.0";
992
+ var RTK_RELEASE_BASE_URL = "https://github.com/rtk-ai/rtk/releases/download/v0.45.0/";
993
+ var RTK_ASSETS = {
994
+ "linux-x64": {
995
+ file: "rtk-x86_64-unknown-linux-musl.tar.gz",
996
+ sha256: "c4c036fbf181fc55ef329786c8c17e0d427972b053b825944d968a6aafef1ba4"
997
+ },
998
+ "linux-arm64": {
999
+ file: "rtk-aarch64-unknown-linux-gnu.tar.gz",
1000
+ sha256: "80a746dd305ef944ff50ef011ae4ce3878dd5ba88dfe35d859d05498191637c3"
1001
+ },
1002
+ "win32-x64": {
1003
+ file: "rtk-x86_64-pc-windows-msvc.zip",
1004
+ sha256: "34cea9009a8099acdaf85147b971d95f65efabfa63fb3aea7d3e2b73e6f517c3"
1005
+ },
1006
+ "darwin-x64": {
1007
+ file: "rtk-x86_64-apple-darwin.tar.gz",
1008
+ sha256: "9ea02f889d5a2779e4fb700df4587824303c5a57cda22e903e30058079fca0ef"
1009
+ },
1010
+ "darwin-arm64": {
1011
+ file: "rtk-aarch64-apple-darwin.tar.gz",
1012
+ sha256: "064151cfc2d50b24d810b06a0af2e41b9c945e83534e4c438c3d3eae607fc3f4"
1013
+ }
1014
+ };
1015
+ var INSTALL_FAILURE_COOLDOWN_MS = 6e5;
1016
+ function assetForPlatform(platform, arch) {
1017
+ const entry = RTK_ASSETS[`${platform}-${arch}`];
1018
+ if (!entry) return null;
667
1019
  return {
668
- encoding,
669
- mode
1020
+ ...entry,
1021
+ version: RTK_VERSION,
1022
+ url: `${RTK_RELEASE_BASE_URL}${entry.file}`
670
1023
  };
671
1024
  }
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
1025
+ function decideRtkInstall({ rtkEnabled, resolvedPath, platform, arch, inFlight = false, lastFailureAt = null, nowMs = 0, cooldownMs = INSTALL_FAILURE_COOLDOWN_MS } = {}) {
1026
+ if (!rtkEnabled) return {
1027
+ action: "skip",
1028
+ reason: "rtk-disabled"
1029
+ };
1030
+ if (resolvedPath) return {
1031
+ action: "skip",
1032
+ reason: "already-resolved"
1033
+ };
1034
+ if (inFlight) return {
1035
+ action: "skip",
1036
+ reason: "install-in-flight"
1037
+ };
1038
+ const asset = assetForPlatform(String(platform), String(arch));
1039
+ if (!asset) return {
1040
+ action: "skip",
1041
+ reason: `unsupported-platform:${platform}-${arch}`
1042
+ };
1043
+ if (typeof lastFailureAt === "number" && nowMs - lastFailureAt < cooldownMs) return {
1044
+ action: "skip",
1045
+ reason: "failure-cooldown"
719
1046
  };
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
1047
  return {
730
- status: "quarantined",
731
- movedTo
1048
+ action: "install",
1049
+ reason: "missing-binary",
1050
+ asset
732
1051
  };
733
1052
  }
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
- }
1053
+ function verifyDigest(expectedHex, actualHex) {
1054
+ if (typeof expectedHex !== "string" || typeof actualHex !== "string") return false;
1055
+ if (expectedHex.length !== actualHex.length) return false;
1056
+ return crypto.timingSafeEqual(Buffer.from(expectedHex, "utf8"), Buffer.from(actualHex, "utf8"));
743
1057
  }
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
- }
1058
+ function installTargetPath(glimmervoidHome, platform) {
1059
+ const binaryName = platform === "win32" ? "rtk.exe" : "rtk";
1060
+ return path.join(glimmervoidHome, "bin", binaryName);
755
1061
  }
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
- }
1062
+ function isRtkBinaryName(name, platform) {
1063
+ const wanted = platform === "win32" ? "rtk.exe" : "rtk";
1064
+ return name.toLowerCase() === wanted;
766
1065
  }
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);
1066
+ function findEscapingArchiveMember(listingText) {
1067
+ const members = String(listingText || "").split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
1068
+ for (const member of members) {
1069
+ if (member.split(/[\\/]+/).includes("..")) return member;
1070
+ if (member.startsWith("/") || member.startsWith("\\") || /^[A-Za-z]:/.test(member)) return member;
877
1071
  }
878
- return {
879
- load,
880
- write,
881
- idle: () => writer ? writer.idle() : Promise.resolve()
882
- };
1072
+ return null;
883
1073
  }
884
1074
  //#endregion
885
1075
  //#region server/config-store.ts
@@ -911,13 +1101,30 @@ var DEFAULT_CONFIG = {
911
1101
  model: "",
912
1102
  timeoutSeconds: 90
913
1103
  } },
1104
+ factory: {
1105
+ enabled: false,
1106
+ maxRisk: "medium",
1107
+ maxLiveWorkers: 2,
1108
+ checks: DEFAULT_FACTORY_CHECKS,
1109
+ protectedPaths: DEFAULT_FACTORY_PROTECTED_PATHS,
1110
+ reviewerModel: null,
1111
+ watchWindowMinutes: 30,
1112
+ watchProjects: [],
1113
+ dailyBudgetUsd: null,
1114
+ verifierModel: null,
1115
+ mirrorHarnessRuns: false
1116
+ },
914
1117
  agentApi: { enabled: false },
1118
+ sandbox: {
1119
+ unconfinedRepos: [],
1120
+ writablePaths: ["~/.npm", "~/.cache"],
1121
+ unreadablePaths: []
1122
+ },
915
1123
  telemetry: { enabled: true },
916
1124
  checkForUpdates: true,
917
1125
  updateChannel: "release",
918
1126
  autoResume: true,
919
1127
  skipPermissionsByDefault: false,
920
- antiSlopPrompt: false,
921
1128
  rtk: false,
922
1129
  saneYolo: true,
923
1130
  telegramNotifications: false,
@@ -969,14 +1176,6 @@ var DEFAULT_CONFIG = {
969
1176
  };
970
1177
  var DEFAULT_CONFIG_BY_KEY = DEFAULT_CONFIG;
971
1178
  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
1179
  function glimmervoidHomeDir() {
981
1180
  return glimmervoidHomeDir$1(os.homedir(), process.env);
982
1181
  }
@@ -1002,11 +1201,10 @@ function resolveConfigPath() {
1002
1201
  mode: 448
1003
1202
  });
1004
1203
  restrictMode(homeDir, 448);
1005
- fs.writeFileSync(homeConfig, JSON.stringify(DEFAULT_CONFIG, null, 2), {
1006
- encoding: "utf8",
1007
- mode: 384
1204
+ writeJsonAtomicSync(homeConfig, DEFAULT_CONFIG, {
1205
+ mode: 384,
1206
+ enforceMode: true
1008
1207
  });
1009
- restrictMode(homeConfig, 384);
1010
1208
  console.log(`Created default config at ${homeConfig}`);
1011
1209
  return homeConfig;
1012
1210
  }
@@ -1014,7 +1212,7 @@ function generateProjectId() {
1014
1212
  return crypto.randomUUID();
1015
1213
  }
1016
1214
  function validateConfig(candidate) {
1017
- if (!isPlainObject(candidate)) return {
1215
+ if (!isRecord(candidate)) return {
1018
1216
  ok: false,
1019
1217
  errors: ["config must be a plain object"]
1020
1218
  };
@@ -1025,6 +1223,7 @@ function validateConfig(candidate) {
1025
1223
  if (issue.code === "custom") return issue.message;
1026
1224
  const [root, index, field] = issue.path;
1027
1225
  if (root === "port") return "port must be an integer from 0 to 65535";
1226
+ if (root === "replayBufferKB") return issue.message;
1028
1227
  if (root === "repoRoots" || root === "worktreeShare") return `${root} must be an array of strings`;
1029
1228
  if (root === "remote") return "remote must be a plain object";
1030
1229
  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`;
@@ -1042,8 +1241,8 @@ function validateConfig(candidate) {
1042
1241
  };
1043
1242
  }
1044
1243
  function normalizeConfigFile(candidate) {
1045
- if (!isPlainObject(candidate)) throw new Error("config must be a plain object");
1046
- const draft = candidate;
1244
+ if (!isRecord(candidate)) throw new Error("config must be a plain object");
1245
+ const draft = normalizeLegacyConfigNumbers(candidate);
1047
1246
  for (const [key, fallback] of Object.entries(DEFAULT_CONFIG_BY_KEY)) {
1048
1247
  if (fallback === null || typeof fallback === "object") continue;
1049
1248
  if (!Object.hasOwn(draft, key)) continue;
@@ -1058,13 +1257,12 @@ function normalizeConfigFile(candidate) {
1058
1257
  }
1059
1258
  function writeBackupContent(backupPath, content) {
1060
1259
  try {
1061
- fs.writeFileSync(backupPath, content, {
1062
- encoding: "utf8",
1063
- mode: 384
1260
+ writeTextAtomicSync(backupPath, content, {
1261
+ mode: 384,
1262
+ enforceMode: true
1064
1263
  });
1065
- restrictMode(backupPath, 384);
1066
1264
  } catch (err) {
1067
- console.warn(`[config] Failed to write backup ${backupPath}:`, errorCode(err));
1265
+ console.warn(`[config] Failed to write backup ${backupPath}:`, errorLabel(err));
1068
1266
  }
1069
1267
  }
1070
1268
  function loadConfigFile(configPath, { exitOnError = true } = {}) {
@@ -1077,13 +1275,12 @@ function loadConfigFile(configPath, { exitOnError = true } = {}) {
1077
1275
  } catch (err) {
1078
1276
  const invalidBackupPath = `${configPath}.invalid.bak`;
1079
1277
  try {
1080
- fs.writeFileSync(invalidBackupPath, loadedContent, {
1081
- encoding: "utf8",
1082
- mode: 384
1278
+ writeTextAtomicSync(invalidBackupPath, loadedContent, {
1279
+ mode: 384,
1280
+ enforceMode: true
1083
1281
  });
1084
- restrictMode(invalidBackupPath, 384);
1085
1282
  } catch (backupErr) {
1086
- console.warn(`[config] Failed to save invalid config copy ${invalidBackupPath}:`, errorCode(backupErr));
1283
+ console.warn(`[config] Failed to save invalid config copy ${invalidBackupPath}:`, errorLabel(backupErr));
1087
1284
  }
1088
1285
  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
1286
  if (!exitOnError) return {
@@ -1096,7 +1293,7 @@ function loadConfigFile(configPath, { exitOnError = true } = {}) {
1096
1293
  }
1097
1294
  }
1098
1295
  function topLevelKeyCount(candidate) {
1099
- if (!isPlainObject(candidate)) return 0;
1296
+ if (!isRecord(candidate)) return 0;
1100
1297
  return Object.keys(candidate).length;
1101
1298
  }
1102
1299
  function isSuspectedExternalWipe(candidate, currentConfig) {
@@ -1122,7 +1319,7 @@ function ensureProjectIds(projects) {
1122
1319
  return changed;
1123
1320
  }
1124
1321
  function collectBranchGcIssues(branchGc) {
1125
- if (!isPlainObject(branchGc)) return {
1322
+ if (!isRecord(branchGc)) return {
1126
1323
  block: { ...DEFAULT_CONFIG.branchGc },
1127
1324
  issues: []
1128
1325
  };
@@ -1183,7 +1380,7 @@ var TELEGRAM_SETTINGS_KEYS = Object.freeze(["chatId"]);
1183
1380
  var TELEGRAM_SECRET_KEYS = Object.freeze(["botToken"]);
1184
1381
  var SECRET_PRESENCE_SUFFIX = "Configured";
1185
1382
  function pickRedactedBlock(stored, allowedKeys, secretKeys) {
1186
- if (!isPlainObject(stored)) return null;
1383
+ if (!isRecord(stored)) return null;
1187
1384
  const source = stored;
1188
1385
  const redacted = {};
1189
1386
  for (const key of allowedKeys) if (source[key] !== void 0) redacted[key] = source[key];
@@ -1214,20 +1411,10 @@ function createConfigStore({ settingsDefaults } = {}) {
1214
1411
  }
1215
1412
  let _lastWrittenContent = null;
1216
1413
  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;
1414
+ let synchronousWriteRevision = 0;
1415
+ let lastSynchronousContent = null;
1416
+ let pendingSaves = Promise.resolve();
1417
+ function prepareSave(freshConfig, mutatorFn) {
1231
1418
  const freshValidation = validateConfig(freshConfig);
1232
1419
  if (!freshValidation.ok) {
1233
1420
  warnInvalidConfig("save config.json", freshValidation);
@@ -1238,23 +1425,86 @@ function createConfigStore({ settingsDefaults } = {}) {
1238
1425
  return null;
1239
1426
  }
1240
1427
  if (mutatorFn(freshConfig) === "abort-config-save") return null;
1241
- const effectiveConfig = withEnvSecrets(freshConfig, envSecrets);
1242
- const mutatedValidation = validateConfig(effectiveConfig);
1428
+ const mutatedValidation = validateConfig(withEnvSecrets(freshConfig, envSecrets));
1243
1429
  if (!mutatedValidation.ok) {
1244
1430
  warnInvalidConfig("save config.json", mutatedValidation);
1245
1431
  return null;
1246
1432
  }
1433
+ return mutatedValidation.config;
1434
+ }
1435
+ function save(mutatorFn) {
1436
+ let loaded;
1437
+ try {
1438
+ loaded = loadConfigFile(configPath, { exitOnError: false });
1439
+ } catch (err) {
1440
+ console.warn("[config] Failed to read config.json for save:", errorLabel(err));
1441
+ return null;
1442
+ }
1443
+ if ("error" in loaded) {
1444
+ console.warn(loaded.message);
1445
+ return null;
1446
+ }
1447
+ const freshConfig = loaded.config;
1448
+ const freshContent = loaded.loadedContent;
1449
+ const effectiveConfig = prepareSave(freshConfig, mutatorFn);
1450
+ if (!effectiveConfig) return null;
1247
1451
  try {
1248
1452
  const nextContent = JSON.stringify(withoutEnvSecrets(effectiveConfig, envSecrets), null, 2);
1249
1453
  if (freshContent !== nextContent) writeBackupContent(`${configPath}.bak`, freshContent);
1250
1454
  _lastWrittenContent = nextContent;
1251
1455
  _lastAppliedContent = null;
1252
1456
  writeTextAtomicSync(configPath, nextContent, { mode: 384 });
1457
+ lastSynchronousContent = nextContent;
1458
+ synchronousWriteRevision++;
1253
1459
  } catch (err) {
1254
- console.warn("[config] Failed to write config.json:", errorCode(err));
1460
+ console.warn("[config] Failed to write config.json:", errorLabel(err));
1255
1461
  return null;
1256
1462
  }
1257
- return mutatedValidation.config;
1463
+ return effectiveConfig;
1464
+ }
1465
+ function saveAsync(mutatorFn) {
1466
+ const scheduledSave = pendingSaves.then(async () => {
1467
+ let concurrentContent = null;
1468
+ for (;;) {
1469
+ const revision = synchronousWriteRevision;
1470
+ try {
1471
+ const diskContent = await fs.promises.readFile(configPath, "utf8");
1472
+ if (revision !== synchronousWriteRevision) {
1473
+ concurrentContent = lastSynchronousContent;
1474
+ continue;
1475
+ }
1476
+ const freshContent = concurrentContent ?? diskContent;
1477
+ const effectiveConfig = prepareSave(withEnvSecrets(normalizeConfigFile(JSON.parse(freshContent)), envSecrets), mutatorFn);
1478
+ if (!effectiveConfig) return null;
1479
+ const nextContent = JSON.stringify(withoutEnvSecrets(effectiveConfig, envSecrets), null, 2);
1480
+ if (freshContent !== nextContent) try {
1481
+ await writeTextAtomic(`${configPath}.bak`, freshContent, {
1482
+ mode: 384,
1483
+ enforceMode: process.platform !== "win32"
1484
+ });
1485
+ } catch (error) {
1486
+ console.warn(`[config] Failed to write backup ${configPath}.bak:`, errorLabel(error));
1487
+ }
1488
+ if (revision !== synchronousWriteRevision) {
1489
+ concurrentContent = lastSynchronousContent;
1490
+ continue;
1491
+ }
1492
+ await writeTextAtomic(configPath, nextContent, { mode: 384 });
1493
+ if (revision !== synchronousWriteRevision) {
1494
+ concurrentContent = lastSynchronousContent;
1495
+ continue;
1496
+ }
1497
+ _lastWrittenContent = nextContent;
1498
+ _lastAppliedContent = null;
1499
+ return effectiveConfig;
1500
+ } catch (error) {
1501
+ console.warn("[config] Failed to save config.json asynchronously:", errorMessage(error));
1502
+ return null;
1503
+ }
1504
+ }
1505
+ });
1506
+ pendingSaves = scheduledSave;
1507
+ return scheduledSave;
1258
1508
  }
1259
1509
  function getSettings() {
1260
1510
  return {
@@ -1272,10 +1522,14 @@ function createConfigStore({ settingsDefaults } = {}) {
1272
1522
  recordSignals: config.recordSignals ?? effectiveDefaults.recordSignals,
1273
1523
  trace: { enabled: config.trace?.enabled ?? DEFAULT_CONFIG.trace.enabled },
1274
1524
  agentApi: { enabled: config.agentApi?.enabled ?? DEFAULT_CONFIG.agentApi.enabled },
1525
+ sandbox: {
1526
+ ...DEFAULT_CONFIG.sandbox,
1527
+ ...config.sandbox
1528
+ },
1275
1529
  telemetry: { enabled: config.telemetry?.enabled ?? DEFAULT_CONFIG.telemetry.enabled },
1276
1530
  telemetryForcedOff: isTelemetryForcedOff(process.env),
1277
- antiSlopPrompt: config.antiSlopPrompt ?? effectiveDefaults.antiSlopPrompt,
1278
1531
  rtk: config.rtk ?? effectiveDefaults.rtk,
1532
+ rtkInstallSupported: assetForPlatform(process.platform, process.arch) !== null,
1279
1533
  saneYolo: config.saneYolo ?? effectiveDefaults.saneYolo,
1280
1534
  checkForUpdates: config.checkForUpdates ?? effectiveDefaults.checkForUpdates,
1281
1535
  updateChannel: config.updateChannel ?? effectiveDefaults.updateChannel,
@@ -1286,15 +1540,20 @@ function createConfigStore({ settingsDefaults } = {}) {
1286
1540
  worktreeRoot: config.worktreeRoot ?? effectiveDefaults.worktreeRoot,
1287
1541
  worktreeShare: config.worktreeShare ?? effectiveDefaults.worktreeShare,
1288
1542
  repoRoots: config.repoRoots,
1543
+ repositoryRootCaseInsensitive: equalsIgnoringCaseOnWindows("root", "ROOT"),
1289
1544
  taskTitle: config.taskTitle ? { ...config.taskTitle } : { ...DEFAULT_CONFIG.taskTitle },
1290
1545
  changeMap: config.changeMap ? { ...config.changeMap } : null,
1291
1546
  branchGc: { ...config.branchGc },
1292
1547
  postTurnChecks: config.postTurnChecks ? { ...config.postTurnChecks } : { ...DEFAULT_CONFIG.postTurnChecks },
1293
1548
  visions: config.visions ? { ...config.visions } : null,
1549
+ github: config.github ? { ...config.github } : null,
1294
1550
  teamReview: config.teamReview ? { ...config.teamReview } : null,
1295
1551
  benchmarks: config.benchmarks ? { ...config.benchmarks } : null,
1552
+ factory: config.factory ? { ...config.factory } : null,
1553
+ knowledgeGraph: config.knowledgeGraph ? { ...config.knowledgeGraph } : null,
1296
1554
  workflows: config.workflows ?? null,
1297
1555
  posthog: pickRedactedBlock(config.posthog, POSTHOG_SETTINGS_KEYS, POSTHOG_SECRET_KEYS),
1556
+ secretSources: Object.fromEntries(readEnvSecrets(process.env).map((secret) => [`${secret.blockName}.${secret.secretKey}`, secret.environmentVariable])),
1298
1557
  usage: config.usage ? { ...config.usage } : null,
1299
1558
  telegram: pickRedactedBlock(config.telegram, TELEGRAM_SETTINGS_KEYS, TELEGRAM_SECRET_KEYS),
1300
1559
  ingest: pickSettingsBlock(config.ingest, INGEST_SPEC),
@@ -1331,13 +1590,17 @@ function createConfigStore({ settingsDefaults } = {}) {
1331
1590
  if (newConfig.changeMap != null) config.changeMap = newConfig.changeMap;
1332
1591
  if (newConfig.branchGc != null) config.branchGc = resolveBranchGc(newConfig.branchGc);
1333
1592
  if (newConfig.visions != null) config.visions = newConfig.visions;
1593
+ if (newConfig.github != null) config.github = newConfig.github;
1334
1594
  if (newConfig.teamReview != null) config.teamReview = newConfig.teamReview;
1335
1595
  if (newConfig.benchmarks != null) config.benchmarks = newConfig.benchmarks;
1596
+ if (newConfig.factory != null) config.factory = newConfig.factory;
1597
+ if (newConfig.knowledgeGraph != null) config.knowledgeGraph = newConfig.knowledgeGraph;
1336
1598
  if (newConfig.posthog != null) config.posthog = newConfig.posthog;
1337
1599
  if (newConfig.usage != null) config.usage = newConfig.usage;
1338
1600
  if (newConfig.telegram != null) config.telegram = newConfig.telegram;
1339
1601
  if (newConfig.ingest != null) config.ingest = newConfig.ingest;
1340
1602
  if (newConfig.agentApi != null) config.agentApi = newConfig.agentApi;
1603
+ if (newConfig.sandbox != null) config.sandbox = newConfig.sandbox;
1341
1604
  if (newConfig.telemetry != null) config.telemetry = newConfig.telemetry;
1342
1605
  if (newConfig.workflows == null) delete config.workflows;
1343
1606
  if (newConfig.workflows != null) config.workflows = newConfig.workflows;
@@ -1401,6 +1664,8 @@ function createConfigStore({ settingsDefaults } = {}) {
1401
1664
  config,
1402
1665
  configPath,
1403
1666
  save,
1667
+ saveAsync,
1668
+ idle: () => pendingSaves,
1404
1669
  getSettings,
1405
1670
  applySettings,
1406
1671
  isUnchosenLaunchDefault,
@@ -1409,4 +1674,4 @@ function createConfigStore({ settingsDefaults } = {}) {
1409
1674
  };
1410
1675
  }
1411
1676
  //#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 };
1677
+ export { sessionIdFromBranch as $, snapshotEvents as A, classifySessionExit as B, createIngestStore as C, resolveIngestConfig as D, publishEvent as E, adapterBucket as F, nodeMajorVersion as G, decideRemoteTelemetryState as H, buildAiGenerationEvents as I, stableConfigKey as J, parsePosthogReleaseId as K, buildBrowserExceptionProperties as L, FIRST_RUN_NOTICE as M, TELEMETRY_BATCH_URL as N, ringStats as O, TELEMETRY_FLAGS_URL as P, planWorktreeGc as Q, buildExceptionProperties as R, buildContextDigest as S, latestSeq as T, decideTelemetryConsent as U, decideIsDevInstall as V, exceptionFingerprint as W, matchedPrefix as X, DEFAULT_BRANCH_GC_PREFIXES as Y, planBranchGc as Z, INGEST_SPEC as _, ensureProjectIds as a, DEFAULT_DIGEST_BUDGET_CHARS as b, loadConfigFile as c, assetForPlatform as d, usablePrefixes as et, decideRtkInstall as f, verifyDigest as g, isRtkBinaryName as h, createConfigStore as i, localDayKey as j, scrubText as k, resolveConfigPath as l, installTargetPath as m, DEFAULT_CONFIG as n, generateProjectId as o, findEscapingArchiveMember as p, resolveProjectToken as q, SECRET_PRESENCE_SUFFIX as r, glimmervoidHomeDir as s, ABORT_CONFIG_SAVE as t, worktreeIntegrationTips as tt, validateConfig as u, mergeSettingsBlock as v, enabledSourceNames as w, SOURCE_DEFAULTS as x, validateSettingsBlock as y, buildFactoryDecisionEvent as z };