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.
- package/dist/mcp-server.d.ts +7 -0
- package/dist/mcp-server.js +35 -1
- package/dist/resource-loader.d.ts +1 -1
- package/dist/resource-loader.js +2 -8
- package/dist/resources/extensions/claude-code-cli/stream-adapter.js +66 -4
- package/dist/resources/extensions/gsd/auto/phases.js +4 -1
- package/dist/resources/extensions/gsd/auto/session.js +4 -0
- package/dist/resources/extensions/gsd/auto-model-selection.js +39 -13
- package/dist/resources/extensions/gsd/auto-start.js +39 -21
- package/dist/resources/extensions/gsd/auto.js +15 -12
- package/dist/resources/extensions/gsd/blocked-models.js +68 -0
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +76 -0
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +39 -9
- package/dist/resources/extensions/gsd/bootstrap/exec-tools.js +93 -0
- package/dist/resources/extensions/gsd/bootstrap/register-extension.js +2 -0
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +35 -0
- package/dist/resources/extensions/gsd/compaction-snapshot.js +121 -0
- package/dist/resources/extensions/gsd/complexity-classifier.js +5 -3
- package/dist/resources/extensions/gsd/error-classifier.js +31 -3
- package/dist/resources/extensions/gsd/exec-history.js +120 -0
- package/dist/resources/extensions/gsd/exec-sandbox.js +258 -0
- package/dist/resources/extensions/gsd/gsd-db.js +62 -4
- package/dist/resources/extensions/gsd/init-wizard.js +15 -1
- package/dist/resources/extensions/gsd/key-manager.js +6 -0
- package/dist/resources/extensions/gsd/pre-execution-checks.js +13 -3
- package/dist/resources/extensions/gsd/preferences-types.js +9 -0
- package/dist/resources/extensions/gsd/preferences-validation.js +83 -0
- package/dist/resources/extensions/gsd/preferences.js +17 -17
- package/dist/resources/extensions/gsd/prompt-loader.js +22 -7
- package/dist/resources/extensions/gsd/safety/file-change-validator.js +1 -1
- package/dist/resources/extensions/gsd/tools/exec-search-tool.js +59 -0
- package/dist/resources/extensions/gsd/tools/exec-tool.js +126 -0
- package/dist/resources/extensions/gsd/tools/resume-tool.js +23 -0
- package/dist/resources/extensions/gsd/workflow-mcp.js +3 -0
- package/dist/resources/extensions/search-the-web/command-search-provider.js +5 -4
- package/dist/resources/extensions/search-the-web/native-search.js +45 -13
- package/dist/tsconfig.extensions.tsbuildinfo +1 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +8 -8
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/required-server-files.json +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +8 -8
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware-manifest.json +5 -5
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/dist/web/standalone/server.js +1 -1
- package/package.json +1 -1
- package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js +64 -25
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/src/workflow-tools.test.ts +146 -1
- package/packages/mcp-server/src/workflow-tools.ts +84 -43
- package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-ai/dist/providers/openai-completions.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/openai-completions.js +60 -15
- package/packages/pi-ai/dist/providers/openai-completions.js.map +1 -1
- package/packages/pi-ai/dist/providers/think-tag-parser.d.ts +17 -0
- package/packages/pi-ai/dist/providers/think-tag-parser.d.ts.map +1 -0
- package/packages/pi-ai/dist/providers/think-tag-parser.js +75 -0
- package/packages/pi-ai/dist/providers/think-tag-parser.js.map +1 -0
- package/packages/pi-ai/dist/providers/think-tag-parser.test.d.ts +2 -0
- package/packages/pi-ai/dist/providers/think-tag-parser.test.d.ts.map +1 -0
- package/packages/pi-ai/dist/providers/think-tag-parser.test.js +41 -0
- package/packages/pi-ai/dist/providers/think-tag-parser.test.js.map +1 -0
- package/packages/pi-ai/src/providers/openai-completions.ts +57 -16
- package/packages/pi-ai/src/providers/think-tag-parser.test.ts +44 -0
- package/packages/pi-ai/src/providers/think-tag-parser.ts +94 -0
- package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-coding-agent/dist/core/model-discovery.d.ts +3 -1
- package/packages/pi-coding-agent/dist/core/model-discovery.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-discovery.js +92 -12
- package/packages/pi-coding-agent/dist/core/model-discovery.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-discovery.test.js +16 -1
- package/packages/pi-coding-agent/dist/core/model-discovery.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry-discovery.test.js +61 -1
- package/packages/pi-coding-agent/dist/core/model-registry-discovery.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry.d.ts +5 -0
- package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry.js +76 -10
- package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/redact-secrets.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/redact-secrets.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/redact-secrets.js +49 -0
- package/packages/pi-coding-agent/dist/core/redact-secrets.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/redact-secrets.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/redact-secrets.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/redact-secrets.test.js +67 -0
- package/packages/pi-coding-agent/dist/core/redact-secrets.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/session-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/session-manager.js +9 -5
- package/packages/pi-coding-agent/dist/core/session-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/session-manager.test.js +25 -1
- package/packages/pi-coding-agent/dist/core/session-manager.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.d.ts +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.js +5 -4
- package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.js +13 -7
- package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.d.ts +7 -6
- package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.js +29 -21
- package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.js.map +1 -1
- package/packages/pi-coding-agent/src/core/model-discovery.test.ts +19 -0
- package/packages/pi-coding-agent/src/core/model-discovery.ts +99 -12
- package/packages/pi-coding-agent/src/core/model-registry-discovery.test.ts +75 -0
- package/packages/pi-coding-agent/src/core/model-registry.ts +86 -10
- package/packages/pi-coding-agent/src/core/redact-secrets.test.ts +86 -0
- package/packages/pi-coding-agent/src/core/redact-secrets.ts +58 -0
- package/packages/pi-coding-agent/src/core/session-manager.test.ts +36 -1
- package/packages/pi-coding-agent/src/core/session-manager.ts +9 -5
- package/packages/pi-coding-agent/src/modes/interactive/components/chat-frame.ts +6 -6
- package/packages/pi-coding-agent/src/modes/interactive/components/provider-manager.ts +16 -7
- package/packages/pi-coding-agent/src/modes/interactive/components/skill-invocation-message.ts +36 -22
- package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
- package/scripts/link-workspace-packages.cjs +1 -0
- package/src/resources/extensions/claude-code-cli/stream-adapter.ts +67 -4
- package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +137 -2
- package/src/resources/extensions/gsd/auto/loop-deps.ts +1 -0
- package/src/resources/extensions/gsd/auto/phases.ts +4 -0
- package/src/resources/extensions/gsd/auto/session.ts +7 -1
- package/src/resources/extensions/gsd/auto-model-selection.ts +50 -12
- package/src/resources/extensions/gsd/auto-start.ts +40 -22
- package/src/resources/extensions/gsd/auto.ts +15 -12
- package/src/resources/extensions/gsd/blocked-models.ts +98 -0
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +97 -0
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +40 -9
- package/src/resources/extensions/gsd/bootstrap/exec-tools.ts +109 -0
- package/src/resources/extensions/gsd/bootstrap/register-extension.ts +2 -0
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +36 -0
- package/src/resources/extensions/gsd/compaction-snapshot.ts +165 -0
- package/src/resources/extensions/gsd/complexity-classifier.ts +5 -3
- package/src/resources/extensions/gsd/error-classifier.ts +36 -3
- package/src/resources/extensions/gsd/exec-history.ts +153 -0
- package/src/resources/extensions/gsd/exec-sandbox.ts +326 -0
- package/src/resources/extensions/gsd/gsd-db.ts +68 -4
- package/src/resources/extensions/gsd/init-wizard.ts +15 -1
- package/src/resources/extensions/gsd/key-manager.ts +6 -0
- package/src/resources/extensions/gsd/pre-execution-checks.ts +13 -3
- package/src/resources/extensions/gsd/preferences-types.ts +38 -0
- package/src/resources/extensions/gsd/preferences-validation.ts +79 -0
- package/src/resources/extensions/gsd/preferences.ts +17 -17
- package/src/resources/extensions/gsd/prompt-loader.ts +30 -7
- package/src/resources/extensions/gsd/safety/file-change-validator.ts +1 -1
- package/src/resources/extensions/gsd/tests/auto-model-selection.test.ts +12 -0
- package/src/resources/extensions/gsd/tests/auto-start-model-capture.test.ts +33 -3
- package/src/resources/extensions/gsd/tests/auto-thinking-restore.test.ts +38 -0
- package/src/resources/extensions/gsd/tests/blocked-models.test.ts +98 -0
- package/src/resources/extensions/gsd/tests/compaction-snapshot.test.ts +123 -0
- package/src/resources/extensions/gsd/tests/complexity-classifier.test.ts +3 -3
- package/src/resources/extensions/gsd/tests/exec-history.test.ts +124 -0
- package/src/resources/extensions/gsd/tests/exec-sandbox.test.ts +210 -0
- package/src/resources/extensions/gsd/tests/file-change-validator.test.ts +20 -0
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +151 -0
- package/src/resources/extensions/gsd/tests/init-wizard.test.ts +27 -0
- package/src/resources/extensions/gsd/tests/isolation-none-branch-guard.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/key-manager.test.ts +7 -0
- package/src/resources/extensions/gsd/tests/pre-exec-backtick-strip.test.ts +14 -0
- package/src/resources/extensions/gsd/tests/pre-execution-checks.test.ts +19 -0
- package/src/resources/extensions/gsd/tests/preferences.test.ts +110 -0
- package/src/resources/extensions/gsd/tests/prompt-loader-extension-dir.test.ts +49 -0
- package/src/resources/extensions/gsd/tests/provider-errors.test.ts +91 -0
- package/src/resources/extensions/gsd/tests/save-gate-result-render.test.ts +95 -0
- package/src/resources/extensions/gsd/tests/zombie-gsd-state.test.ts +3 -1
- package/src/resources/extensions/gsd/tools/exec-search-tool.ts +81 -0
- package/src/resources/extensions/gsd/tools/exec-tool.ts +183 -0
- package/src/resources/extensions/gsd/tools/resume-tool.ts +40 -0
- package/src/resources/extensions/gsd/workflow-logger.ts +2 -1
- package/src/resources/extensions/gsd/workflow-mcp.ts +3 -0
- package/src/resources/extensions/search-the-web/command-search-provider.ts +5 -4
- package/src/resources/extensions/search-the-web/native-search.ts +48 -12
- /package/dist/web/standalone/.next/static/{pBwmOoye64ZrRp-_rf0v1 → n21VtX2hZlkpdEUO_nU4z}/_buildManifest.js +0 -0
- /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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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 = [
|