gsd-pi 2.76.0-dev.b072ebb73 → 2.76.0-dev.fe143342a

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 (200) hide show
  1. package/dist/mcp-server.d.ts +7 -0
  2. package/dist/mcp-server.js +35 -1
  3. package/dist/resource-loader.d.ts +1 -1
  4. package/dist/resource-loader.js +2 -8
  5. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +66 -4
  6. package/dist/resources/extensions/gsd/auto/phases.js +4 -1
  7. package/dist/resources/extensions/gsd/auto/session.js +4 -0
  8. package/dist/resources/extensions/gsd/auto-model-selection.js +39 -13
  9. package/dist/resources/extensions/gsd/auto-start.js +39 -21
  10. package/dist/resources/extensions/gsd/auto.js +15 -12
  11. package/dist/resources/extensions/gsd/blocked-models.js +68 -0
  12. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +76 -0
  13. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +39 -9
  14. package/dist/resources/extensions/gsd/bootstrap/exec-tools.js +93 -0
  15. package/dist/resources/extensions/gsd/bootstrap/register-extension.js +2 -0
  16. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +35 -0
  17. package/dist/resources/extensions/gsd/compaction-snapshot.js +121 -0
  18. package/dist/resources/extensions/gsd/complexity-classifier.js +5 -3
  19. package/dist/resources/extensions/gsd/error-classifier.js +31 -3
  20. package/dist/resources/extensions/gsd/exec-history.js +120 -0
  21. package/dist/resources/extensions/gsd/exec-sandbox.js +258 -0
  22. package/dist/resources/extensions/gsd/gsd-db.js +62 -4
  23. package/dist/resources/extensions/gsd/init-wizard.js +15 -1
  24. package/dist/resources/extensions/gsd/key-manager.js +6 -0
  25. package/dist/resources/extensions/gsd/pre-execution-checks.js +13 -3
  26. package/dist/resources/extensions/gsd/preferences-types.js +9 -0
  27. package/dist/resources/extensions/gsd/preferences-validation.js +83 -0
  28. package/dist/resources/extensions/gsd/preferences.js +17 -17
  29. package/dist/resources/extensions/gsd/prompt-loader.js +22 -7
  30. package/dist/resources/extensions/gsd/safety/file-change-validator.js +1 -1
  31. package/dist/resources/extensions/gsd/tools/exec-search-tool.js +59 -0
  32. package/dist/resources/extensions/gsd/tools/exec-tool.js +126 -0
  33. package/dist/resources/extensions/gsd/tools/resume-tool.js +23 -0
  34. package/dist/resources/extensions/gsd/workflow-mcp.js +3 -0
  35. package/dist/resources/extensions/search-the-web/command-search-provider.js +5 -4
  36. package/dist/resources/extensions/search-the-web/native-search.js +45 -13
  37. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  38. package/dist/web/standalone/.next/BUILD_ID +1 -1
  39. package/dist/web/standalone/.next/app-path-routes-manifest.json +8 -8
  40. package/dist/web/standalone/.next/build-manifest.json +2 -2
  41. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  42. package/dist/web/standalone/.next/required-server-files.json +1 -1
  43. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  44. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  45. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  46. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  47. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  48. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  49. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  50. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  51. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  52. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  53. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  54. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  55. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  56. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  57. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  58. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  59. package/dist/web/standalone/.next/server/app/index.html +1 -1
  60. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  61. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  62. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  63. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  64. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  65. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  66. package/dist/web/standalone/.next/server/app-paths-manifest.json +8 -8
  67. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  68. package/dist/web/standalone/.next/server/middleware-manifest.json +5 -5
  69. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  70. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  71. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  72. package/dist/web/standalone/server.js +1 -1
  73. package/package.json +1 -1
  74. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  75. package/packages/mcp-server/dist/workflow-tools.js +64 -25
  76. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  77. package/packages/mcp-server/src/workflow-tools.test.ts +146 -1
  78. package/packages/mcp-server/src/workflow-tools.ts +84 -43
  79. package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
  80. package/packages/pi-ai/dist/providers/openai-completions.d.ts.map +1 -1
  81. package/packages/pi-ai/dist/providers/openai-completions.js +60 -15
  82. package/packages/pi-ai/dist/providers/openai-completions.js.map +1 -1
  83. package/packages/pi-ai/dist/providers/think-tag-parser.d.ts +17 -0
  84. package/packages/pi-ai/dist/providers/think-tag-parser.d.ts.map +1 -0
  85. package/packages/pi-ai/dist/providers/think-tag-parser.js +75 -0
  86. package/packages/pi-ai/dist/providers/think-tag-parser.js.map +1 -0
  87. package/packages/pi-ai/dist/providers/think-tag-parser.test.d.ts +2 -0
  88. package/packages/pi-ai/dist/providers/think-tag-parser.test.d.ts.map +1 -0
  89. package/packages/pi-ai/dist/providers/think-tag-parser.test.js +41 -0
  90. package/packages/pi-ai/dist/providers/think-tag-parser.test.js.map +1 -0
  91. package/packages/pi-ai/src/providers/openai-completions.ts +57 -16
  92. package/packages/pi-ai/src/providers/think-tag-parser.test.ts +44 -0
  93. package/packages/pi-ai/src/providers/think-tag-parser.ts +94 -0
  94. package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
  95. package/packages/pi-coding-agent/dist/core/model-discovery.d.ts +3 -1
  96. package/packages/pi-coding-agent/dist/core/model-discovery.d.ts.map +1 -1
  97. package/packages/pi-coding-agent/dist/core/model-discovery.js +92 -12
  98. package/packages/pi-coding-agent/dist/core/model-discovery.js.map +1 -1
  99. package/packages/pi-coding-agent/dist/core/model-discovery.test.js +16 -1
  100. package/packages/pi-coding-agent/dist/core/model-discovery.test.js.map +1 -1
  101. package/packages/pi-coding-agent/dist/core/model-registry-discovery.test.js +61 -1
  102. package/packages/pi-coding-agent/dist/core/model-registry-discovery.test.js.map +1 -1
  103. package/packages/pi-coding-agent/dist/core/model-registry.d.ts +5 -0
  104. package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
  105. package/packages/pi-coding-agent/dist/core/model-registry.js +76 -10
  106. package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
  107. package/packages/pi-coding-agent/dist/core/redact-secrets.d.ts +2 -0
  108. package/packages/pi-coding-agent/dist/core/redact-secrets.d.ts.map +1 -0
  109. package/packages/pi-coding-agent/dist/core/redact-secrets.js +49 -0
  110. package/packages/pi-coding-agent/dist/core/redact-secrets.js.map +1 -0
  111. package/packages/pi-coding-agent/dist/core/redact-secrets.test.d.ts +2 -0
  112. package/packages/pi-coding-agent/dist/core/redact-secrets.test.d.ts.map +1 -0
  113. package/packages/pi-coding-agent/dist/core/redact-secrets.test.js +67 -0
  114. package/packages/pi-coding-agent/dist/core/redact-secrets.test.js.map +1 -0
  115. package/packages/pi-coding-agent/dist/core/session-manager.d.ts.map +1 -1
  116. package/packages/pi-coding-agent/dist/core/session-manager.js +9 -5
  117. package/packages/pi-coding-agent/dist/core/session-manager.js.map +1 -1
  118. package/packages/pi-coding-agent/dist/core/session-manager.test.js +25 -1
  119. package/packages/pi-coding-agent/dist/core/session-manager.test.js.map +1 -1
  120. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.d.ts +1 -1
  121. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.d.ts.map +1 -1
  122. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.js +5 -4
  123. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.js.map +1 -1
  124. package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.d.ts.map +1 -1
  125. package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.js +13 -7
  126. package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.js.map +1 -1
  127. package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.d.ts +7 -6
  128. package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.d.ts.map +1 -1
  129. package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.js +29 -21
  130. package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.js.map +1 -1
  131. package/packages/pi-coding-agent/src/core/model-discovery.test.ts +19 -0
  132. package/packages/pi-coding-agent/src/core/model-discovery.ts +99 -12
  133. package/packages/pi-coding-agent/src/core/model-registry-discovery.test.ts +75 -0
  134. package/packages/pi-coding-agent/src/core/model-registry.ts +86 -10
  135. package/packages/pi-coding-agent/src/core/redact-secrets.test.ts +86 -0
  136. package/packages/pi-coding-agent/src/core/redact-secrets.ts +58 -0
  137. package/packages/pi-coding-agent/src/core/session-manager.test.ts +36 -1
  138. package/packages/pi-coding-agent/src/core/session-manager.ts +9 -5
  139. package/packages/pi-coding-agent/src/modes/interactive/components/chat-frame.ts +6 -6
  140. package/packages/pi-coding-agent/src/modes/interactive/components/provider-manager.ts +16 -7
  141. package/packages/pi-coding-agent/src/modes/interactive/components/skill-invocation-message.ts +36 -22
  142. package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
  143. package/scripts/link-workspace-packages.cjs +1 -0
  144. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +67 -4
  145. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +137 -2
  146. package/src/resources/extensions/gsd/auto/loop-deps.ts +1 -0
  147. package/src/resources/extensions/gsd/auto/phases.ts +4 -0
  148. package/src/resources/extensions/gsd/auto/session.ts +7 -1
  149. package/src/resources/extensions/gsd/auto-model-selection.ts +50 -12
  150. package/src/resources/extensions/gsd/auto-start.ts +40 -22
  151. package/src/resources/extensions/gsd/auto.ts +15 -12
  152. package/src/resources/extensions/gsd/blocked-models.ts +98 -0
  153. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +97 -0
  154. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +40 -9
  155. package/src/resources/extensions/gsd/bootstrap/exec-tools.ts +109 -0
  156. package/src/resources/extensions/gsd/bootstrap/register-extension.ts +2 -0
  157. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +36 -0
  158. package/src/resources/extensions/gsd/compaction-snapshot.ts +165 -0
  159. package/src/resources/extensions/gsd/complexity-classifier.ts +5 -3
  160. package/src/resources/extensions/gsd/error-classifier.ts +36 -3
  161. package/src/resources/extensions/gsd/exec-history.ts +153 -0
  162. package/src/resources/extensions/gsd/exec-sandbox.ts +326 -0
  163. package/src/resources/extensions/gsd/gsd-db.ts +68 -4
  164. package/src/resources/extensions/gsd/init-wizard.ts +15 -1
  165. package/src/resources/extensions/gsd/key-manager.ts +6 -0
  166. package/src/resources/extensions/gsd/pre-execution-checks.ts +13 -3
  167. package/src/resources/extensions/gsd/preferences-types.ts +38 -0
  168. package/src/resources/extensions/gsd/preferences-validation.ts +79 -0
  169. package/src/resources/extensions/gsd/preferences.ts +17 -17
  170. package/src/resources/extensions/gsd/prompt-loader.ts +30 -7
  171. package/src/resources/extensions/gsd/safety/file-change-validator.ts +1 -1
  172. package/src/resources/extensions/gsd/tests/auto-model-selection.test.ts +12 -0
  173. package/src/resources/extensions/gsd/tests/auto-start-model-capture.test.ts +33 -3
  174. package/src/resources/extensions/gsd/tests/auto-thinking-restore.test.ts +38 -0
  175. package/src/resources/extensions/gsd/tests/blocked-models.test.ts +98 -0
  176. package/src/resources/extensions/gsd/tests/compaction-snapshot.test.ts +123 -0
  177. package/src/resources/extensions/gsd/tests/complexity-classifier.test.ts +3 -3
  178. package/src/resources/extensions/gsd/tests/exec-history.test.ts +124 -0
  179. package/src/resources/extensions/gsd/tests/exec-sandbox.test.ts +210 -0
  180. package/src/resources/extensions/gsd/tests/file-change-validator.test.ts +20 -0
  181. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +151 -0
  182. package/src/resources/extensions/gsd/tests/init-wizard.test.ts +27 -0
  183. package/src/resources/extensions/gsd/tests/isolation-none-branch-guard.test.ts +1 -1
  184. package/src/resources/extensions/gsd/tests/key-manager.test.ts +7 -0
  185. package/src/resources/extensions/gsd/tests/pre-exec-backtick-strip.test.ts +14 -0
  186. package/src/resources/extensions/gsd/tests/pre-execution-checks.test.ts +19 -0
  187. package/src/resources/extensions/gsd/tests/preferences.test.ts +110 -0
  188. package/src/resources/extensions/gsd/tests/prompt-loader-extension-dir.test.ts +49 -0
  189. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +91 -0
  190. package/src/resources/extensions/gsd/tests/save-gate-result-render.test.ts +95 -0
  191. package/src/resources/extensions/gsd/tests/zombie-gsd-state.test.ts +3 -1
  192. package/src/resources/extensions/gsd/tools/exec-search-tool.ts +81 -0
  193. package/src/resources/extensions/gsd/tools/exec-tool.ts +183 -0
  194. package/src/resources/extensions/gsd/tools/resume-tool.ts +40 -0
  195. package/src/resources/extensions/gsd/workflow-logger.ts +2 -1
  196. package/src/resources/extensions/gsd/workflow-mcp.ts +3 -0
  197. package/src/resources/extensions/search-the-web/command-search-provider.ts +5 -4
  198. package/src/resources/extensions/search-the-web/native-search.ts +48 -12
  199. /package/dist/web/standalone/.next/static/{pBwmOoye64ZrRp-_rf0v1 → n21VtX2hZlkpdEUO_nU4z}/_buildManifest.js +0 -0
  200. /package/dist/web/standalone/.next/static/{pBwmOoye64ZrRp-_rf0v1 → n21VtX2hZlkpdEUO_nU4z}/_ssgManifest.js +0 -0
@@ -0,0 +1,120 @@
1
+ // GSD Exec History — read-side helpers for the exec sandbox.
2
+ //
3
+ // Pure I/O: scans `.gsd/exec/*.meta.json` under a base directory and
4
+ // returns lightweight records. Used by the gsd_exec_search tool and
5
+ // any future compaction-snapshot enrichment.
6
+ import { closeSync, openSync, readdirSync, readFileSync, readSync, statSync } from "node:fs";
7
+ import { join, resolve } from "node:path";
8
+ function listMetaFiles(baseDir) {
9
+ const dir = resolve(baseDir, ".gsd", "exec");
10
+ try {
11
+ return readdirSync(dir)
12
+ .filter((name) => name.endsWith(".meta.json"))
13
+ .map((name) => join(dir, name));
14
+ }
15
+ catch {
16
+ return [];
17
+ }
18
+ }
19
+ function safeReadMeta(path) {
20
+ try {
21
+ const raw = readFileSync(path, "utf-8");
22
+ const parsed = JSON.parse(raw);
23
+ if (typeof parsed.id !== "string" || typeof parsed.runtime !== "string")
24
+ return null;
25
+ return {
26
+ id: parsed.id,
27
+ runtime: parsed.runtime,
28
+ purpose: typeof parsed.purpose === "string" ? parsed.purpose : null,
29
+ started_at: typeof parsed.started_at === "string" ? parsed.started_at : "",
30
+ finished_at: typeof parsed.finished_at === "string" ? parsed.finished_at : "",
31
+ duration_ms: typeof parsed.duration_ms === "number" ? parsed.duration_ms : 0,
32
+ exit_code: typeof parsed.exit_code === "number" ? parsed.exit_code : null,
33
+ signal: typeof parsed.signal === "string" ? parsed.signal : null,
34
+ timed_out: parsed.timed_out === true,
35
+ stdout_bytes: typeof parsed.stdout_bytes === "number" ? parsed.stdout_bytes : 0,
36
+ stderr_bytes: typeof parsed.stderr_bytes === "number" ? parsed.stderr_bytes : 0,
37
+ stdout_truncated: parsed.stdout_truncated === true,
38
+ stderr_truncated: parsed.stderr_truncated === true,
39
+ stdout_path: path.replace(/\.meta\.json$/, ".stdout"),
40
+ stderr_path: path.replace(/\.meta\.json$/, ".stderr"),
41
+ meta_path: path,
42
+ };
43
+ }
44
+ catch {
45
+ return null;
46
+ }
47
+ }
48
+ export function listExecHistory(baseDir) {
49
+ const metas = listMetaFiles(baseDir)
50
+ .map((path) => {
51
+ let mtime = 0;
52
+ try {
53
+ mtime = statSync(path).mtimeMs;
54
+ }
55
+ catch {
56
+ /* ignore */
57
+ }
58
+ const entry = safeReadMeta(path);
59
+ return entry ? { entry, mtime } : null;
60
+ })
61
+ .filter((value) => value !== null);
62
+ metas.sort((a, b) => b.mtime - a.mtime);
63
+ return metas.map((m) => m.entry);
64
+ }
65
+ function matchesFilters(entry, opts) {
66
+ if (opts.runtime && entry.runtime !== opts.runtime)
67
+ return false;
68
+ if (opts.failing_only) {
69
+ const failed = entry.timed_out || (entry.exit_code !== 0 && entry.exit_code !== null);
70
+ if (!failed)
71
+ return false;
72
+ }
73
+ const query = (opts.query ?? "").trim().toLowerCase();
74
+ if (!query)
75
+ return true;
76
+ const haystack = `${entry.id} ${entry.purpose ?? ""}`.toLowerCase();
77
+ return haystack.includes(query);
78
+ }
79
+ function readDigestPreview(entry, maxChars) {
80
+ if (!entry.stdout_path || maxChars <= 0)
81
+ return undefined;
82
+ try {
83
+ const size = statSync(entry.stdout_path).size;
84
+ if (size === 0)
85
+ return undefined;
86
+ const readBytes = Math.min(size, maxChars * 4); // 4 bytes/char upper bound for UTF-8
87
+ const buf = Buffer.allocUnsafe(readBytes);
88
+ const fd = openSync(entry.stdout_path, "r");
89
+ try {
90
+ const bytesRead = readSync(fd, buf, 0, readBytes, Math.max(0, size - readBytes));
91
+ const text = buf.subarray(0, bytesRead).toString("utf-8");
92
+ const trimmed = text.trimEnd();
93
+ return trimmed.length <= maxChars ? trimmed : trimmed.slice(trimmed.length - maxChars);
94
+ }
95
+ finally {
96
+ closeSync(fd);
97
+ }
98
+ }
99
+ catch {
100
+ return undefined;
101
+ }
102
+ }
103
+ export function searchExecHistory(baseDir, opts = {}) {
104
+ const limit = clampLimit(opts.limit, 20, 200);
105
+ const entries = listExecHistory(baseDir);
106
+ const filtered = entries.filter((entry) => matchesFilters(entry, opts));
107
+ return filtered.slice(0, limit).map((entry) => ({
108
+ entry,
109
+ digest_preview: readDigestPreview(entry, 300),
110
+ }));
111
+ }
112
+ function clampLimit(value, fallback, max) {
113
+ if (typeof value !== "number" || !Number.isFinite(value))
114
+ return fallback;
115
+ if (value < 1)
116
+ return 1;
117
+ if (value > max)
118
+ return max;
119
+ return Math.floor(value);
120
+ }
@@ -0,0 +1,258 @@
1
+ // GSD Exec Sandbox — tool-output sandboxing for sub-sessions.
2
+ //
3
+ // Runs a script in a subprocess and persists stdout/stderr to
4
+ // `.gsd/exec/<id>.{stdout,stderr,meta.json}`. Only a short digest is
5
+ // returned to the calling agent's context, keeping large outputs
6
+ // (e.g. Playwright snapshots, issue dumps) out of the window.
7
+ //
8
+ // Inspired by mksglu/context-mode (Elastic License 2.0). Independent
9
+ // implementation — no upstream code incorporated.
10
+ import { spawn } from "node:child_process";
11
+ import { existsSync, mkdirSync, writeFileSync } from "node:fs";
12
+ import { randomUUID } from "node:crypto";
13
+ import { resolve } from "node:path";
14
+ const ALWAYS_FORWARD_ENV = ["PATH", "HOME"];
15
+ export const EXEC_DEFAULTS = {
16
+ clampTimeoutMs: 600_000,
17
+ defaultTimeoutMs: 30_000,
18
+ stdoutCapBytes: 1_048_576,
19
+ stderrCapBytes: 262_144,
20
+ digestChars: 300,
21
+ envAllowlist: [
22
+ "LANG",
23
+ "LC_ALL",
24
+ "TERM",
25
+ "TZ",
26
+ "SHELL",
27
+ "USER",
28
+ "LOGNAME",
29
+ "TMPDIR",
30
+ "NODE_OPTIONS",
31
+ "PYTHONPATH",
32
+ "PYTHONIOENCODING",
33
+ ],
34
+ };
35
+ function buildChildEnv(opts) {
36
+ const source = opts.env ?? process.env;
37
+ const out = {};
38
+ const allowed = new Set([...ALWAYS_FORWARD_ENV, ...opts.env_allowlist]);
39
+ for (const key of allowed) {
40
+ const value = source[key];
41
+ if (typeof value === "string")
42
+ out[key] = value;
43
+ }
44
+ return out;
45
+ }
46
+ function clampTimeout(request, opts) {
47
+ const requested = typeof request.timeout_ms === "number" && Number.isFinite(request.timeout_ms)
48
+ ? Math.floor(request.timeout_ms)
49
+ : opts.default_timeout_ms;
50
+ if (requested < 1)
51
+ return 1;
52
+ if (requested > opts.clamp_timeout_ms)
53
+ return opts.clamp_timeout_ms;
54
+ return requested;
55
+ }
56
+ function resolveCommand(runtime) {
57
+ switch (runtime) {
58
+ case "bash":
59
+ return { cmd: "bash", args: ["-c"] };
60
+ case "node":
61
+ return { cmd: process.execPath, args: ["-e"] };
62
+ case "python":
63
+ return { cmd: "python3", args: ["-c"] };
64
+ }
65
+ }
66
+ function tail(buf, chars) {
67
+ if (chars <= 0)
68
+ return "";
69
+ const text = buf.toString("utf-8");
70
+ return text.length <= chars ? text : text.slice(text.length - chars);
71
+ }
72
+ /**
73
+ * Run a script in a subprocess, capture stdout/stderr to files under
74
+ * `.gsd/exec/<id>.{stdout,stderr,meta.json}`, and return an `ExecSandboxResult`
75
+ * containing the digest plus metadata.
76
+ *
77
+ * Errors from spawn failures resolve (not reject) with `exit_code=null`.
78
+ * The function is pure with respect to its inputs — no global state beyond
79
+ * filesystem writes under `baseDir`.
80
+ */
81
+ export function runExecSandbox(request, opts) {
82
+ return new Promise((resolveP) => {
83
+ const id = (opts.generateId ?? defaultGenerateId)();
84
+ const now = (opts.now ?? (() => new Date()))();
85
+ const execDir = resolve(opts.baseDir, ".gsd", "exec");
86
+ if (!existsSync(execDir))
87
+ mkdirSync(execDir, { recursive: true });
88
+ const stdoutPath = resolve(execDir, `${id}.stdout`);
89
+ const stderrPath = resolve(execDir, `${id}.stderr`);
90
+ const metaPath = resolve(execDir, `${id}.meta.json`);
91
+ const timeoutMs = clampTimeout(request, opts);
92
+ const { cmd, args } = resolveCommand(request.runtime);
93
+ const env = buildChildEnv(opts);
94
+ const useProcessGroup = process.platform !== "win32";
95
+ const started = Date.now();
96
+ let child;
97
+ try {
98
+ child = spawn(cmd, [...args, request.script], {
99
+ cwd: opts.baseDir,
100
+ env,
101
+ stdio: ["ignore", "pipe", "pipe"],
102
+ ...(useProcessGroup ? { detached: true } : {}),
103
+ });
104
+ }
105
+ catch (err) {
106
+ const duration = Date.now() - started;
107
+ const message = err instanceof Error ? err.message : String(err);
108
+ writeFileSync(stdoutPath, "");
109
+ writeFileSync(stderrPath, `spawn error: ${message}\n`);
110
+ const result = {
111
+ id,
112
+ runtime: request.runtime,
113
+ exit_code: null,
114
+ signal: null,
115
+ timed_out: false,
116
+ duration_ms: duration,
117
+ stdout_bytes: 0,
118
+ stderr_bytes: Buffer.byteLength(`spawn error: ${message}\n`),
119
+ stdout_truncated: false,
120
+ stderr_truncated: false,
121
+ stdout_path: stdoutPath,
122
+ stderr_path: stderrPath,
123
+ meta_path: metaPath,
124
+ digest: `[spawn error: ${message}]`,
125
+ };
126
+ writeMeta(metaPath, result, request, now);
127
+ resolveP(result);
128
+ return;
129
+ }
130
+ const stdoutChunks = [];
131
+ const stderrChunks = [];
132
+ let stdoutBytes = 0;
133
+ let stderrBytes = 0;
134
+ let stdoutTruncated = false;
135
+ let stderrTruncated = false;
136
+ child.stdout?.on("data", (chunk) => {
137
+ const remaining = opts.stdout_cap_bytes - stdoutBytes;
138
+ if (remaining <= 0) {
139
+ stdoutTruncated = true;
140
+ return;
141
+ }
142
+ if (chunk.length <= remaining) {
143
+ stdoutChunks.push(chunk);
144
+ stdoutBytes += chunk.length;
145
+ }
146
+ else {
147
+ stdoutChunks.push(chunk.subarray(0, remaining));
148
+ stdoutBytes += remaining;
149
+ stdoutTruncated = true;
150
+ }
151
+ });
152
+ child.stderr?.on("data", (chunk) => {
153
+ const remaining = opts.stderr_cap_bytes - stderrBytes;
154
+ if (remaining <= 0) {
155
+ stderrTruncated = true;
156
+ return;
157
+ }
158
+ if (chunk.length <= remaining) {
159
+ stderrChunks.push(chunk);
160
+ stderrBytes += chunk.length;
161
+ }
162
+ else {
163
+ stderrChunks.push(chunk.subarray(0, remaining));
164
+ stderrBytes += remaining;
165
+ stderrTruncated = true;
166
+ }
167
+ });
168
+ let timedOut = false;
169
+ const timer = setTimeout(() => {
170
+ timedOut = true;
171
+ if (useProcessGroup && child.pid != null) {
172
+ try {
173
+ process.kill(-child.pid, "SIGKILL");
174
+ }
175
+ catch {
176
+ child.kill("SIGKILL");
177
+ }
178
+ }
179
+ else {
180
+ child.kill("SIGKILL");
181
+ }
182
+ }, timeoutMs);
183
+ timer.unref?.();
184
+ const finalize = (exitCode, signal) => {
185
+ clearTimeout(timer);
186
+ const duration = Date.now() - started;
187
+ const stdoutBuf = Buffer.concat(stdoutChunks);
188
+ const stderrBuf = Buffer.concat(stderrChunks);
189
+ const stdoutSuffix = stdoutTruncated ? "\n[truncated: stdout cap reached]\n" : "";
190
+ const stderrSuffix = stderrTruncated ? "\n[truncated: stderr cap reached]\n" : "";
191
+ writeFileSync(stdoutPath, Buffer.concat([stdoutBuf, Buffer.from(stdoutSuffix, "utf-8")]));
192
+ writeFileSync(stderrPath, Buffer.concat([stderrBuf, Buffer.from(stderrSuffix, "utf-8")]));
193
+ const digestBody = tail(stdoutBuf, opts.digest_chars);
194
+ const digest = digestBody.length > 0
195
+ ? digestBody
196
+ : timedOut
197
+ ? "[no stdout — timed out]"
198
+ : stderrBuf.length > 0
199
+ ? `[no stdout — tail of stderr]\n${tail(stderrBuf, opts.digest_chars)}`
200
+ : "[no output]";
201
+ const result = {
202
+ id,
203
+ runtime: request.runtime,
204
+ exit_code: exitCode,
205
+ signal,
206
+ timed_out: timedOut,
207
+ duration_ms: duration,
208
+ stdout_bytes: stdoutBytes,
209
+ stderr_bytes: stderrBytes,
210
+ stdout_truncated: stdoutTruncated,
211
+ stderr_truncated: stderrTruncated,
212
+ stdout_path: stdoutPath,
213
+ stderr_path: stderrPath,
214
+ meta_path: metaPath,
215
+ digest,
216
+ };
217
+ writeMeta(metaPath, result, request, now);
218
+ resolveP(result);
219
+ };
220
+ child.on("error", (err) => {
221
+ const message = err instanceof Error ? err.message : String(err);
222
+ const line = `child error: ${message}\n`;
223
+ const remaining = opts.stderr_cap_bytes - stderrBytes;
224
+ if (remaining > 0) {
225
+ const chunk = Buffer.from(line, "utf-8").subarray(0, remaining);
226
+ stderrChunks.push(chunk);
227
+ stderrBytes += chunk.length;
228
+ if (chunk.length < Buffer.byteLength(line, "utf-8"))
229
+ stderrTruncated = true;
230
+ }
231
+ });
232
+ child.on("close", (code, signal) => finalize(code, signal));
233
+ });
234
+ }
235
+ function defaultGenerateId() {
236
+ return randomUUID();
237
+ }
238
+ function writeMeta(path, result, request, now) {
239
+ const meta = {
240
+ id: result.id,
241
+ runtime: result.runtime,
242
+ purpose: request.purpose ?? null,
243
+ script_chars: request.script.length,
244
+ started_at: now.toISOString(),
245
+ finished_at: new Date(now.getTime() + result.duration_ms).toISOString(),
246
+ exit_code: result.exit_code,
247
+ signal: result.signal,
248
+ timed_out: result.timed_out,
249
+ duration_ms: result.duration_ms,
250
+ stdout_bytes: result.stdout_bytes,
251
+ stderr_bytes: result.stderr_bytes,
252
+ stdout_truncated: result.stdout_truncated,
253
+ stderr_truncated: result.stderr_truncated,
254
+ stdout_path: result.stdout_path,
255
+ stderr_path: result.stderr_path,
256
+ };
257
+ writeFileSync(path, `${JSON.stringify(meta, null, 2)}\n`);
258
+ }
@@ -495,7 +495,9 @@ function initSchema(db, fileBacked) {
495
495
  // that fresh installs already have. Add only columns needed by bootstrap
496
496
  // indexes so old DBs can open far enough for the normal migration chain.
497
497
  ensureBootstrapIndexColumns(db);
498
- db.exec("CREATE INDEX IF NOT EXISTS idx_memories_scope ON memories(scope)");
498
+ if (columnExists(db, "memories", "scope")) {
499
+ db.exec("CREATE INDEX IF NOT EXISTS idx_memories_scope ON memories(scope)");
500
+ }
499
501
  db.exec("CREATE INDEX IF NOT EXISTS idx_memory_sources_kind ON memory_sources(kind)");
500
502
  db.exec("CREATE INDEX IF NOT EXISTS idx_memory_sources_scope ON memory_sources(scope)");
501
503
  db.exec("CREATE INDEX IF NOT EXISTS idx_memory_relations_from ON memory_relations(from_id)");
@@ -1097,6 +1099,8 @@ let currentPath = null;
1097
1099
  let currentPid = 0;
1098
1100
  let _exitHandlerRegistered = false;
1099
1101
  let _dbOpenAttempted = false;
1102
+ let _lastDbError = null;
1103
+ let _lastDbPhase = null;
1100
1104
  export function getDbProvider() {
1101
1105
  loadProvider();
1102
1106
  return providerName;
@@ -1113,13 +1117,54 @@ export function isDbAvailable() {
1113
1117
  export function wasDbOpenAttempted() {
1114
1118
  return _dbOpenAttempted;
1115
1119
  }
1120
+ export function getDbStatus() {
1121
+ loadProvider();
1122
+ return {
1123
+ available: currentDb !== null,
1124
+ provider: providerName,
1125
+ attempted: _dbOpenAttempted,
1126
+ lastError: _lastDbError,
1127
+ lastPhase: _lastDbPhase,
1128
+ };
1129
+ }
1116
1130
  export function openDatabase(path) {
1117
1131
  _dbOpenAttempted = true;
1118
1132
  if (currentDb && currentPath !== path)
1119
1133
  closeDatabase();
1120
1134
  if (currentDb && currentPath === path)
1121
1135
  return true;
1122
- const rawDb = openRawDb(path);
1136
+ // Reset error state only when a new open attempt is actually going to run.
1137
+ _lastDbError = null;
1138
+ _lastDbPhase = null;
1139
+ let rawDb;
1140
+ let fallbackProvider = null;
1141
+ let fallbackModule = null;
1142
+ try {
1143
+ rawDb = openRawDb(path);
1144
+ }
1145
+ catch (primaryErr) {
1146
+ _lastDbPhase = "open";
1147
+ _lastDbError = primaryErr instanceof Error ? primaryErr : new Error(String(primaryErr));
1148
+ // node:sqlite loaded but failed to open this file — try better-sqlite3 as fallback.
1149
+ if (providerName === "node:sqlite") {
1150
+ try {
1151
+ const mod = _require("better-sqlite3");
1152
+ const Db = (mod && mod.default) ? mod.default : mod;
1153
+ if (typeof Db === "function") {
1154
+ rawDb = new Db(path);
1155
+ fallbackProvider = "better-sqlite3";
1156
+ fallbackModule = Db;
1157
+ _lastDbError = null;
1158
+ _lastDbPhase = null;
1159
+ }
1160
+ }
1161
+ catch {
1162
+ // fallback unavailable; surface original error
1163
+ }
1164
+ }
1165
+ if (!rawDb)
1166
+ throw primaryErr;
1167
+ }
1123
1168
  if (!rawDb)
1124
1169
  return false;
1125
1170
  const adapter = createAdapter(rawDb);
@@ -1137,6 +1182,8 @@ export function openDatabase(path) {
1137
1182
  process.stderr.write("gsd-db: recovered corrupt database via VACUUM\n");
1138
1183
  }
1139
1184
  catch (retryErr) {
1185
+ _lastDbPhase = "vacuum-recovery";
1186
+ _lastDbError = retryErr instanceof Error ? retryErr : new Error(String(retryErr));
1140
1187
  try {
1141
1188
  adapter.close();
1142
1189
  }
@@ -1147,15 +1194,22 @@ export function openDatabase(path) {
1147
1194
  }
1148
1195
  }
1149
1196
  else {
1197
+ _lastDbPhase = "initSchema";
1198
+ _lastDbError = err instanceof Error ? err : new Error(String(err));
1150
1199
  try {
1151
1200
  adapter.close();
1152
1201
  }
1153
1202
  catch (e) {
1154
- logWarning("db", `close after VACUUM failed: ${e.message}`);
1203
+ logWarning("db", `close after initSchema failed: ${e.message}`);
1155
1204
  }
1156
1205
  throw err;
1157
1206
  }
1158
1207
  }
1208
+ // Commit fallback provider switch only after open + schema both succeeded.
1209
+ if (fallbackProvider) {
1210
+ providerName = fallbackProvider;
1211
+ providerModule = fallbackModule;
1212
+ }
1159
1213
  currentDb = adapter;
1160
1214
  currentPath = path;
1161
1215
  currentPid = process.pid;
@@ -1194,8 +1248,12 @@ export function closeDatabase() {
1194
1248
  currentDb = null;
1195
1249
  currentPath = null;
1196
1250
  currentPid = 0;
1197
- _dbOpenAttempted = false;
1198
1251
  }
1252
+ // Reset session-scoped state unconditionally so stale error info from a
1253
+ // failed open doesn't persist into the next open attempt or status check.
1254
+ _dbOpenAttempted = false;
1255
+ _lastDbError = null;
1256
+ _lastDbPhase = null;
1199
1257
  }
1200
1258
  /** Run a full VACUUM — call sparingly (e.g. after milestone completion). */
1201
1259
  export function vacuumDatabase() {
@@ -8,7 +8,7 @@
8
8
  import { existsSync, mkdirSync, writeFileSync, readFileSync } from "node:fs";
9
9
  import { join } from "node:path";
10
10
  import { showNextAction } from "../shared/tui.js";
11
- import { nativeInit } from "./native-git-bridge.js";
11
+ import { nativeInit, nativeAddAll, nativeCommit } from "./native-git-bridge.js";
12
12
  import { ensureGitignore, untrackRuntimeFiles } from "./gitignore.js";
13
13
  import { gsdRoot } from "./paths.js";
14
14
  import { assertSafeDirectory } from "./validate-directory.js";
@@ -40,6 +40,7 @@ export async function showProjectInit(ctx, pi, basePath, detection) {
40
40
  ctx.ui.notify(`Project detected:\n${detectionSummary.join("\n")}`, "info");
41
41
  }
42
42
  // ── Step 2: Git setup ──────────────────────────────────────────────────────
43
+ let didInitGit = false;
43
44
  if (!signals.isGitRepo) {
44
45
  const gitChoice = await showNextAction(ctx, {
45
46
  title: "GSD — Project Setup",
@@ -54,6 +55,7 @@ export async function showProjectInit(ctx, pi, basePath, detection) {
54
55
  return { completed: false, bootstrapped: false };
55
56
  if (gitChoice === "init_git") {
56
57
  nativeInit(basePath, prefs.mainBranch);
58
+ didInitGit = true;
57
59
  }
58
60
  }
59
61
  else {
@@ -244,6 +246,18 @@ export async function showProjectInit(ctx, pi, basePath, detection) {
244
246
  // Ensure .gitignore
245
247
  ensureGitignore(basePath);
246
248
  untrackRuntimeFiles(basePath);
249
+ // Create initial commit so git log and git worktree work immediately (#4530).
250
+ // Without this, the branch is "unborn" (zero commits) and downstream operations
251
+ // like `git log` and `git worktree add` fail.
252
+ if (didInitGit) {
253
+ try {
254
+ nativeAddAll(basePath);
255
+ nativeCommit(basePath, "chore: init project");
256
+ }
257
+ catch {
258
+ // Non-fatal — user can commit manually; don't block project init
259
+ }
260
+ }
247
261
  // Auto-generate codebase map for instant agent orientation
248
262
  try {
249
263
  const result = generateCodebaseMap(basePath);
@@ -12,6 +12,12 @@ import { getErrorMessage } from "./error-utils.js";
12
12
  export const PROVIDER_REGISTRY = [
13
13
  // LLM Providers
14
14
  { id: "anthropic", label: "Anthropic (Claude)", category: "llm", envVar: "ANTHROPIC_API_KEY", prefixes: ["sk-ant-"], hasOAuth: true, dashboardUrl: "console.anthropic.com" },
15
+ // Claude Code CLI: routes through the local `claude` binary — no API key,
16
+ // authentication is handled by the CLI's own OAuth flow.
17
+ // Referenced by doctor-providers.ts, auto-model-selection.ts, and others;
18
+ // must be in the canonical registry so all consumers see the same catalog.
19
+ // See: https://github.com/gsd-build/gsd-2/issues/4541
20
+ { id: "claude-code", label: "Claude Code CLI", category: "llm", hasOAuth: true },
15
21
  { id: "openai", label: "OpenAI", category: "llm", envVar: "OPENAI_API_KEY", prefixes: ["sk-"], dashboardUrl: "platform.openai.com/api-keys" },
16
22
  { id: "github-copilot", label: "GitHub Copilot", category: "llm", envVar: "GITHUB_TOKEN", hasOAuth: true },
17
23
  { id: "openai-codex", label: "ChatGPT Plus/Pro (Codex)", category: "llm", hasOAuth: true },
@@ -68,8 +68,13 @@ export function extractPackageReferences(description) {
68
68
  }
69
69
  }
70
70
  }
71
- // require('pkg') or import from 'pkg' in code blocks
72
- const importPattern = /(?:require\s*\(\s*['"]|from\s+['"])([a-zA-Z0-9@/_-]+)['"\)]/g;
71
+ // require('pkg') or `import ... from 'pkg'` in code blocks.
72
+ // The `from\s+['"]` branch MUST be preceded by an `import` keyword so that
73
+ // natural-language prose like `from "What's Next"` or `from 'master'` does
74
+ // not produce false package-existence failures. Requiring the leading import
75
+ // keyword anchors the match to JavaScript/TypeScript syntax.
76
+ // See: https://github.com/gsd-build/gsd-2/issues/4388
77
+ const importPattern = /(?:require\s*\(\s*['"]|import\b[\s\S]*?\bfrom\s+['"])([a-zA-Z0-9@/_-]+)['"\)]/g;
73
78
  let importMatch;
74
79
  while ((importMatch = importPattern.exec(description)) !== null) {
75
80
  // Skip relative imports and node builtins
@@ -278,7 +283,12 @@ function extractPathFromAnnotation(raw) {
278
283
  }
279
284
  const annotatedMatch = trimmed.match(/^(.+?)\s+[—–-]\s+.+$/);
280
285
  if (annotatedMatch) {
281
- return annotatedMatch[1].trim();
286
+ const prefix = annotatedMatch[1].trim();
287
+ const prefixBacktickMatch = prefix.match(/`([^`]+)`/);
288
+ if (prefixBacktickMatch && looksLikePathOrUrl(prefixBacktickMatch[1].trim())) {
289
+ return prefixBacktickMatch[1].trim();
290
+ }
291
+ return prefix.replace(/`/g, "").trim();
282
292
  }
283
293
  // Fallback: scan all backticked tokens and return the first one that looks
284
294
  // like a path or URL. Handles prose-annotated bullets such as:
@@ -5,6 +5,14 @@
5
5
  * both the validation and runtime modules can import them without pulling
6
6
  * in filesystem or loading logic.
7
7
  */
8
+ /**
9
+ * Resolve whether context-mode features (gsd_exec sandbox + compaction
10
+ * snapshot) should be active. Default is ON: missing config or missing
11
+ * `enabled` is treated as true. Only `enabled: false` disables.
12
+ */
13
+ export function isContextModeEnabled(prefs) {
14
+ return prefs?.context_mode?.enabled !== false;
15
+ }
8
16
  /** Default preference values for each workflow mode. */
9
17
  export const MODE_DEFAULTS = {
10
18
  solo: {
@@ -87,6 +95,7 @@ export const KNOWN_PREFERENCE_KEYS = new Set([
87
95
  "flat_rate_providers",
88
96
  "language",
89
97
  "context_window_override",
98
+ "context_mode",
90
99
  ]);
91
100
  /** Canonical list of all dispatch unit types. */
92
101
  export const KNOWN_UNIT_TYPES = [