gsd-pi 2.76.0-dev.82e249f7b → 2.76.0-dev.97807402

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 (236) hide show
  1. package/dist/claude-cli-check.js +32 -3
  2. package/dist/mcp-server.d.ts +7 -0
  3. package/dist/mcp-server.js +35 -1
  4. package/dist/resource-loader.d.ts +1 -1
  5. package/dist/resource-loader.js +2 -8
  6. package/dist/resources/extensions/claude-code-cli/readiness.js +4 -3
  7. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +77 -17
  8. package/dist/resources/extensions/gsd/auto/phases.js +14 -0
  9. package/dist/resources/extensions/gsd/auto/run-unit.js +27 -0
  10. package/dist/resources/extensions/gsd/auto-model-selection.js +1 -1
  11. package/dist/resources/extensions/gsd/auto-post-unit.js +1 -1
  12. package/dist/resources/extensions/gsd/auto-recovery.js +13 -0
  13. package/dist/resources/extensions/gsd/auto-start.js +27 -18
  14. package/dist/resources/extensions/gsd/auto-worktree.js +30 -48
  15. package/dist/resources/extensions/gsd/auto.js +13 -17
  16. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +17 -1
  17. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +39 -9
  18. package/dist/resources/extensions/gsd/bootstrap/exec-tools.js +93 -0
  19. package/dist/resources/extensions/gsd/bootstrap/register-extension.js +2 -0
  20. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +40 -4
  21. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +12 -1
  22. package/dist/resources/extensions/gsd/commands-prefs-wizard.js +968 -23
  23. package/dist/resources/extensions/gsd/compaction-snapshot.js +121 -0
  24. package/dist/resources/extensions/gsd/error-classifier.js +10 -3
  25. package/dist/resources/extensions/gsd/exec-history.js +120 -0
  26. package/dist/resources/extensions/gsd/exec-sandbox.js +258 -0
  27. package/dist/resources/extensions/gsd/gsd-db.js +115 -7
  28. package/dist/resources/extensions/gsd/guided-flow.js +189 -0
  29. package/dist/resources/extensions/gsd/health-widget.js +4 -1
  30. package/dist/resources/extensions/gsd/init-wizard.js +15 -1
  31. package/dist/resources/extensions/gsd/key-manager.js +6 -0
  32. package/dist/resources/extensions/gsd/model-router.js +36 -3
  33. package/dist/resources/extensions/gsd/pre-execution-checks.js +35 -9
  34. package/dist/resources/extensions/gsd/preferences-types.js +9 -0
  35. package/dist/resources/extensions/gsd/preferences-validation.js +83 -0
  36. package/dist/resources/extensions/gsd/preferences.js +17 -17
  37. package/dist/resources/extensions/gsd/prompts/discuss-headless.md +8 -0
  38. package/dist/resources/extensions/gsd/prompts/discuss.md +29 -2
  39. package/dist/resources/extensions/gsd/prompts/parallel-research-slices.md +5 -2
  40. package/dist/resources/extensions/gsd/safety/file-change-validator.js +10 -4
  41. package/dist/resources/extensions/gsd/safety/safety-harness.js +4 -0
  42. package/dist/resources/extensions/gsd/token-counter.js +22 -5
  43. package/dist/resources/extensions/gsd/tools/exec-search-tool.js +59 -0
  44. package/dist/resources/extensions/gsd/tools/exec-tool.js +126 -0
  45. package/dist/resources/extensions/gsd/tools/resume-tool.js +23 -0
  46. package/dist/resources/extensions/gsd/workflow-mcp.js +3 -0
  47. package/dist/resources/skills/verify-before-complete/SKILL.md +2 -1
  48. package/dist/resources/skills/write-docs/SKILL.md +2 -1
  49. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  50. package/dist/web/standalone/.next/BUILD_ID +1 -1
  51. package/dist/web/standalone/.next/app-path-routes-manifest.json +7 -7
  52. package/dist/web/standalone/.next/build-manifest.json +2 -2
  53. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  54. package/dist/web/standalone/.next/required-server-files.json +1 -1
  55. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  56. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  57. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  58. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  59. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  60. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  61. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  62. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  63. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  64. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  65. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  66. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  67. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  68. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  69. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  70. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  71. package/dist/web/standalone/.next/server/app/index.html +1 -1
  72. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  73. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  74. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  75. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  76. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  77. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  78. package/dist/web/standalone/.next/server/app-paths-manifest.json +7 -7
  79. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  80. package/dist/web/standalone/.next/server/middleware-manifest.json +5 -5
  81. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  82. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  83. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  84. package/dist/web/standalone/server.js +1 -1
  85. package/package.json +1 -1
  86. package/packages/mcp-server/dist/remote-questions.d.ts +45 -0
  87. package/packages/mcp-server/dist/remote-questions.d.ts.map +1 -0
  88. package/packages/mcp-server/dist/remote-questions.js +732 -0
  89. package/packages/mcp-server/dist/remote-questions.js.map +1 -0
  90. package/packages/mcp-server/dist/server.d.ts.map +1 -1
  91. package/packages/mcp-server/dist/server.js +18 -1
  92. package/packages/mcp-server/dist/server.js.map +1 -1
  93. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  94. package/packages/mcp-server/dist/workflow-tools.js +64 -25
  95. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  96. package/packages/mcp-server/package.json +2 -1
  97. package/packages/mcp-server/src/remote-questions.test.ts +294 -0
  98. package/packages/mcp-server/src/remote-questions.ts +916 -0
  99. package/packages/mcp-server/src/server.ts +19 -1
  100. package/packages/mcp-server/src/workflow-tools.test.ts +146 -1
  101. package/packages/mcp-server/src/workflow-tools.ts +84 -43
  102. package/packages/mcp-server/tsconfig.test.json +19 -0
  103. package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
  104. package/packages/pi-ai/dist/providers/anthropic-shared.d.ts.map +1 -1
  105. package/packages/pi-ai/dist/providers/anthropic-shared.js +2 -0
  106. package/packages/pi-ai/dist/providers/anthropic-shared.js.map +1 -1
  107. package/packages/pi-ai/dist/providers/simple-options.d.ts +10 -0
  108. package/packages/pi-ai/dist/providers/simple-options.d.ts.map +1 -1
  109. package/packages/pi-ai/dist/providers/simple-options.js +16 -1
  110. package/packages/pi-ai/dist/providers/simple-options.js.map +1 -1
  111. package/packages/pi-ai/src/providers/anthropic-shared.ts +3 -1
  112. package/packages/pi-ai/src/providers/simple-options.ts +17 -1
  113. package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
  114. package/packages/pi-coding-agent/dist/core/model-registry-custom-caps.test.d.ts +2 -0
  115. package/packages/pi-coding-agent/dist/core/model-registry-custom-caps.test.d.ts.map +1 -0
  116. package/packages/pi-coding-agent/dist/core/model-registry-custom-caps.test.js +203 -0
  117. package/packages/pi-coding-agent/dist/core/model-registry-custom-caps.test.js.map +1 -0
  118. package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
  119. package/packages/pi-coding-agent/dist/core/model-registry.js +14 -0
  120. package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
  121. package/packages/pi-coding-agent/dist/core/redact-secrets.d.ts +2 -0
  122. package/packages/pi-coding-agent/dist/core/redact-secrets.d.ts.map +1 -0
  123. package/packages/pi-coding-agent/dist/core/redact-secrets.js +49 -0
  124. package/packages/pi-coding-agent/dist/core/redact-secrets.js.map +1 -0
  125. package/packages/pi-coding-agent/dist/core/redact-secrets.test.d.ts +2 -0
  126. package/packages/pi-coding-agent/dist/core/redact-secrets.test.d.ts.map +1 -0
  127. package/packages/pi-coding-agent/dist/core/redact-secrets.test.js +67 -0
  128. package/packages/pi-coding-agent/dist/core/redact-secrets.test.js.map +1 -0
  129. package/packages/pi-coding-agent/dist/core/session-manager.d.ts.map +1 -1
  130. package/packages/pi-coding-agent/dist/core/session-manager.js +9 -5
  131. package/packages/pi-coding-agent/dist/core/session-manager.js.map +1 -1
  132. package/packages/pi-coding-agent/dist/core/session-manager.test.js +25 -1
  133. package/packages/pi-coding-agent/dist/core/session-manager.test.js.map +1 -1
  134. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.d.ts +1 -1
  135. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.d.ts.map +1 -1
  136. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.js +5 -4
  137. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.js.map +1 -1
  138. package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.d.ts +7 -6
  139. package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.d.ts.map +1 -1
  140. package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.js +29 -21
  141. package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.js.map +1 -1
  142. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  143. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +13 -1
  144. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  145. package/packages/pi-coding-agent/src/core/model-registry-custom-caps.test.ts +245 -0
  146. package/packages/pi-coding-agent/src/core/model-registry.ts +16 -0
  147. package/packages/pi-coding-agent/src/core/redact-secrets.test.ts +86 -0
  148. package/packages/pi-coding-agent/src/core/redact-secrets.ts +58 -0
  149. package/packages/pi-coding-agent/src/core/session-manager.test.ts +36 -1
  150. package/packages/pi-coding-agent/src/core/session-manager.ts +9 -5
  151. package/packages/pi-coding-agent/src/modes/interactive/components/chat-frame.ts +6 -6
  152. package/packages/pi-coding-agent/src/modes/interactive/components/skill-invocation-message.ts +36 -22
  153. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +13 -1
  154. package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
  155. package/src/resources/extensions/claude-code-cli/readiness.ts +4 -3
  156. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +78 -17
  157. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +149 -5
  158. package/src/resources/extensions/gsd/auto/phases.ts +14 -0
  159. package/src/resources/extensions/gsd/auto/run-unit.ts +29 -0
  160. package/src/resources/extensions/gsd/auto-model-selection.ts +1 -1
  161. package/src/resources/extensions/gsd/auto-post-unit.ts +1 -2
  162. package/src/resources/extensions/gsd/auto-recovery.ts +15 -0
  163. package/src/resources/extensions/gsd/auto-start.ts +29 -19
  164. package/src/resources/extensions/gsd/auto-worktree.ts +34 -52
  165. package/src/resources/extensions/gsd/auto.ts +12 -17
  166. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +23 -1
  167. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +40 -9
  168. package/src/resources/extensions/gsd/bootstrap/exec-tools.ts +109 -0
  169. package/src/resources/extensions/gsd/bootstrap/register-extension.ts +2 -0
  170. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +42 -4
  171. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +13 -1
  172. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +898 -32
  173. package/src/resources/extensions/gsd/compaction-snapshot.ts +165 -0
  174. package/src/resources/extensions/gsd/error-classifier.ts +10 -3
  175. package/src/resources/extensions/gsd/exec-history.ts +153 -0
  176. package/src/resources/extensions/gsd/exec-sandbox.ts +326 -0
  177. package/src/resources/extensions/gsd/gsd-db.ts +122 -7
  178. package/src/resources/extensions/gsd/guided-flow.ts +221 -0
  179. package/src/resources/extensions/gsd/health-widget.ts +3 -1
  180. package/src/resources/extensions/gsd/init-wizard.ts +15 -1
  181. package/src/resources/extensions/gsd/journal.ts +2 -1
  182. package/src/resources/extensions/gsd/key-manager.ts +6 -0
  183. package/src/resources/extensions/gsd/model-router.ts +42 -1
  184. package/src/resources/extensions/gsd/pre-execution-checks.ts +36 -10
  185. package/src/resources/extensions/gsd/preferences-types.ts +46 -0
  186. package/src/resources/extensions/gsd/preferences-validation.ts +79 -0
  187. package/src/resources/extensions/gsd/preferences.ts +17 -17
  188. package/src/resources/extensions/gsd/prompts/discuss-headless.md +8 -0
  189. package/src/resources/extensions/gsd/prompts/discuss.md +29 -2
  190. package/src/resources/extensions/gsd/prompts/parallel-research-slices.md +5 -2
  191. package/src/resources/extensions/gsd/safety/file-change-validator.ts +14 -3
  192. package/src/resources/extensions/gsd/safety/safety-harness.ts +6 -0
  193. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +116 -0
  194. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +49 -0
  195. package/src/resources/extensions/gsd/tests/compaction-snapshot.test.ts +123 -0
  196. package/src/resources/extensions/gsd/tests/complete-slice.test.ts +2 -2
  197. package/src/resources/extensions/gsd/tests/complete-task.test.ts +2 -2
  198. package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +31 -0
  199. package/src/resources/extensions/gsd/tests/ensure-db-open.test.ts +1 -1
  200. package/src/resources/extensions/gsd/tests/escalation.test.ts +1 -1
  201. package/src/resources/extensions/gsd/tests/exec-history.test.ts +124 -0
  202. package/src/resources/extensions/gsd/tests/exec-sandbox.test.ts +210 -0
  203. package/src/resources/extensions/gsd/tests/file-change-validator.test.ts +58 -0
  204. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +152 -1
  205. package/src/resources/extensions/gsd/tests/init-wizard.test.ts +27 -0
  206. package/src/resources/extensions/gsd/tests/isolation-none-branch-guard.test.ts +1 -1
  207. package/src/resources/extensions/gsd/tests/issue-4540-regressions.test.ts +288 -0
  208. package/src/resources/extensions/gsd/tests/key-manager.test.ts +7 -0
  209. package/src/resources/extensions/gsd/tests/md-importer.test.ts +1 -1
  210. package/src/resources/extensions/gsd/tests/memory-store.test.ts +2 -2
  211. package/src/resources/extensions/gsd/tests/parallel-research-dispatch.test.ts +19 -0
  212. package/src/resources/extensions/gsd/tests/pre-exec-backtick-strip.test.ts +14 -0
  213. package/src/resources/extensions/gsd/tests/pre-execution-checks.test.ts +234 -0
  214. package/src/resources/extensions/gsd/tests/preferences.test.ts +110 -0
  215. package/src/resources/extensions/gsd/tests/prefs-wizard-coverage.test.ts +44 -0
  216. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +48 -0
  217. package/src/resources/extensions/gsd/tests/ready-phrase-no-files-4573.test.ts +388 -0
  218. package/src/resources/extensions/gsd/tests/restore-tools-after-discuss.test.ts +9 -3
  219. package/src/resources/extensions/gsd/tests/save-gate-result-render.test.ts +95 -0
  220. package/src/resources/extensions/gsd/tests/session-start-footer.test.ts +32 -40
  221. package/src/resources/extensions/gsd/tests/stash-queued-context-files.test.ts +56 -0
  222. package/src/resources/extensions/gsd/tests/token-counter.test.ts +105 -1
  223. package/src/resources/extensions/gsd/tests/tool-compatibility.test.ts +107 -0
  224. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +65 -2
  225. package/src/resources/extensions/gsd/tests/write-gate.test.ts +64 -0
  226. package/src/resources/extensions/gsd/tests/zombie-gsd-state.test.ts +3 -1
  227. package/src/resources/extensions/gsd/token-counter.ts +22 -5
  228. package/src/resources/extensions/gsd/tools/exec-search-tool.ts +81 -0
  229. package/src/resources/extensions/gsd/tools/exec-tool.ts +183 -0
  230. package/src/resources/extensions/gsd/tools/resume-tool.ts +40 -0
  231. package/src/resources/extensions/gsd/workflow-logger.ts +2 -1
  232. package/src/resources/extensions/gsd/workflow-mcp.ts +3 -0
  233. package/src/resources/skills/verify-before-complete/SKILL.md +2 -1
  234. package/src/resources/skills/write-docs/SKILL.md +2 -1
  235. /package/dist/web/standalone/.next/static/{ecSsu49rxxcpbNmVP4mLD → pI48IF3dgfs0CBrYi2bh_}/_buildManifest.js +0 -0
  236. /package/dist/web/standalone/.next/static/{ecSsu49rxxcpbNmVP4mLD → pI48IF3dgfs0CBrYi2bh_}/_ssgManifest.js +0 -0
@@ -0,0 +1,121 @@
1
+ // GSD Compaction Snapshot — writes a ≤2 KB markdown digest of durable
2
+ // project state before the session context is compacted. On resume, an
3
+ // agent can `gsd_resume` (or Read .gsd/last-snapshot.md) to re-orient
4
+ // without re-deriving the same memories.
5
+ //
6
+ // Inspired by mksglu/context-mode. Independent implementation.
7
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
8
+ import { resolve } from "node:path";
9
+ import { getActiveMemoriesRanked } from "./memory-store.js";
10
+ import { listExecHistory } from "./exec-history.js";
11
+ export const DEFAULT_SNAPSHOT_BYTES = 2048;
12
+ export const SNAPSHOT_FILENAME = "last-snapshot.md";
13
+ /**
14
+ * Build a priority-tiered markdown snapshot. Pure — no I/O. Tiers:
15
+ * 1. Active context (if any)
16
+ * 2. Top memories by rank
17
+ * 3. Recent exec runs (failures highlighted)
18
+ * The result is guaranteed to be <= opts.maxBytes (truncated with an
19
+ * ellipsis marker if necessary).
20
+ */
21
+ export function buildSnapshot(sources, opts = {}) {
22
+ const maxBytes = opts.maxBytes ?? DEFAULT_SNAPSHOT_BYTES;
23
+ const maxMemories = opts.maxMemories ?? 6;
24
+ const maxExec = opts.maxExec ?? 5;
25
+ const lines = [];
26
+ lines.push(`# GSD context snapshot (${sources.generatedAt.toISOString()})`);
27
+ lines.push("");
28
+ if (sources.activeContext && sources.activeContext.trim().length > 0) {
29
+ lines.push("## Active context");
30
+ lines.push(sources.activeContext.trim());
31
+ lines.push("");
32
+ }
33
+ const memories = sources.memories.slice(0, maxMemories);
34
+ if (memories.length > 0) {
35
+ lines.push("## Top project memories");
36
+ for (const memory of memories) {
37
+ lines.push(`- [${memory.id}] (${memory.category}) ${memory.content.trim()}`);
38
+ }
39
+ lines.push("");
40
+ }
41
+ const exec = sources.execHistory.slice(0, maxExec);
42
+ if (exec.length > 0) {
43
+ lines.push("## Recent gsd_exec runs");
44
+ for (const entry of exec) {
45
+ const status = entry.timed_out
46
+ ? "timeout"
47
+ : entry.exit_code === null
48
+ ? "exit:null"
49
+ : `exit:${entry.exit_code}`;
50
+ const purpose = entry.purpose ? ` — ${entry.purpose}` : "";
51
+ lines.push(`- [${entry.id}] ${entry.runtime} ${status}${purpose}`);
52
+ }
53
+ lines.push("");
54
+ }
55
+ if (memories.length === 0 && exec.length === 0 && !sources.activeContext) {
56
+ lines.push("_No durable memories, active context, or exec history to surface._");
57
+ }
58
+ return enforceByteCap(lines.join("\n").trimEnd(), maxBytes);
59
+ }
60
+ function enforceByteCap(input, maxBytes) {
61
+ if (Buffer.byteLength(input, "utf-8") <= maxBytes)
62
+ return input;
63
+ const marker = "\n…[truncated]";
64
+ const markerBytes = Buffer.byteLength(marker, "utf-8");
65
+ const budget = Math.max(0, maxBytes - markerBytes);
66
+ // Walk backwards until the trimmed string fits. utf-8 is variable-width;
67
+ // naive char slicing is safe for ASCII but may split a multi-byte char.
68
+ // Guard by decoding the trimmed Buffer and relying on the replacement-char
69
+ // fallback in TextDecoder (implicit via toString).
70
+ const buf = Buffer.from(input, "utf-8").subarray(0, budget);
71
+ return `${buf.toString("utf-8")}${marker}`;
72
+ }
73
+ export function writeCompactionSnapshot(baseDir, opts = {}) {
74
+ const memories = safeGetMemories();
75
+ const execHistory = safeListExec(baseDir);
76
+ const content = buildSnapshot({
77
+ memories,
78
+ execHistory,
79
+ generatedAt: (opts.now ?? (() => new Date()))(),
80
+ activeContext: opts.activeContext ?? null,
81
+ }, opts);
82
+ const gsdDir = resolve(baseDir, ".gsd");
83
+ if (!existsSync(gsdDir))
84
+ mkdirSync(gsdDir, { recursive: true });
85
+ const path = resolve(gsdDir, SNAPSHOT_FILENAME);
86
+ const finalContent = `${content}\n`;
87
+ writeFileSync(path, finalContent, "utf-8");
88
+ return {
89
+ path,
90
+ bytes: Buffer.byteLength(finalContent, "utf-8"),
91
+ memories: memories.length,
92
+ execRuns: execHistory.length,
93
+ };
94
+ }
95
+ export function readCompactionSnapshot(baseDir) {
96
+ const path = resolve(baseDir, ".gsd", SNAPSHOT_FILENAME);
97
+ if (!existsSync(path))
98
+ return null;
99
+ try {
100
+ return readFileSync(path, "utf-8");
101
+ }
102
+ catch {
103
+ return null;
104
+ }
105
+ }
106
+ function safeGetMemories() {
107
+ try {
108
+ return getActiveMemoriesRanked(12);
109
+ }
110
+ catch {
111
+ return [];
112
+ }
113
+ }
114
+ function safeListExec(baseDir) {
115
+ try {
116
+ return listExecHistory(baseDir);
117
+ }
118
+ catch {
119
+ return [];
120
+ }
121
+ }
@@ -22,12 +22,19 @@ const PERMANENT_RE = /auth|unauthorized|forbidden|invalid.*key|invalid.*api|bill
22
22
  // Include provider-specific quota-window phrasing like:
23
23
  // - "You've hit your limit"
24
24
  // - "usage limit" / "quota reached"
25
- const RATE_LIMIT_RE = /rate.?limit|too many requests|429|hit your limit|usage limit|quota (?:reached|hit)|limit.*resets?/i;
25
+ // - "out of extra usage"
26
+ const RATE_LIMIT_RE = /rate.?limit|too many requests|429|hit your limit|usage limit|out of extra usage|quota (?:reached|hit)|limit.*resets?/i;
26
27
  // OpenRouter affordability-style quota errors should be treated as transient
27
28
  // so core retry logic can lower maxTokens and continue in-session.
28
29
  const AFFORDABILITY_RE = /requires more credits|can only afford|insufficient credits|not enough credits|fewer max_tokens/i;
29
- const NETWORK_RE = /network|ECONNRESET|ETIMEDOUT|ECONNREFUSED|socket hang up|fetch failed|connection.*reset|dns|unexpected eof/i;
30
- const SERVER_RE = /internal server error|500|502|503|overloaded|server_error|api_error|service.?unavailable/i;
30
+ // "Stream idle timeout" and "partial response received" are emitted by the SDK/harness
31
+ // for mid-stream disconnects. Both indicate a transient network-level interruption.
32
+ // See: https://github.com/gsd-build/gsd-2/issues/4558
33
+ const NETWORK_RE = /network|ECONNRESET|ETIMEDOUT|ECONNREFUSED|socket hang up|fetch failed|connection.*reset|dns|unexpected eof|stream idle timeout|partial response received/i;
34
+ // Context overflow errors (context window/length exceeded) should be treated as server-class
35
+ // transient errors so auto-mode can retry with reduced budget or fall back to a larger-context model.
36
+ // See: https://github.com/gsd-build/gsd-2/issues/4528
37
+ const SERVER_RE = /internal server error|500|502|503|overloaded|server_error|api_error|service.?unavailable|context (?:window|length) exceed|context window exceed/i;
31
38
  // ECONNRESET/ECONNREFUSED are in NETWORK_RE (same-model retry first).
32
39
  const CONNECTION_RE = /terminated|connection.?(?:refused|error)|other side closed|EPIPE|network.?(?:is\s+)?unavailable|stream_exhausted(?:_without_result)?/i;
33
40
  // Catch-all for V8 JSON.parse errors: all modern variants end with "in JSON at position \d+".
@@ -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
+ }