@opengsd/gsd-pi 1.3.0-dev.65546769 → 1.3.0-dev.72e3af2a

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 (357) hide show
  1. package/dist/resources/.managed-resources-content-hash +1 -1
  2. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +11 -2
  3. package/dist/resources/extensions/google-cli/stream-adapter.js +82 -15
  4. package/dist/resources/extensions/gsd/artifact-verification.js +427 -0
  5. package/dist/resources/extensions/gsd/auto/orchestrator.js +12 -3
  6. package/dist/resources/extensions/gsd/auto/session.js +3 -0
  7. package/dist/resources/extensions/gsd/auto-artifact-paths.js +28 -1
  8. package/dist/resources/extensions/gsd/auto-dispatch.js +20 -19
  9. package/dist/resources/extensions/gsd/auto-prompts.js +26 -11
  10. package/dist/resources/extensions/gsd/auto-recovery.js +6 -507
  11. package/dist/resources/extensions/gsd/auto-runtime-state.js +4 -5
  12. package/dist/resources/extensions/gsd/auto-timeout-recovery.js +3 -3
  13. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +103 -13
  14. package/dist/resources/extensions/gsd/bootstrap/core-session-tools.js +38 -0
  15. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +6 -1
  16. package/dist/resources/extensions/gsd/bootstrap/exec-tools.js +2 -0
  17. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +10 -19
  18. package/dist/resources/extensions/gsd/bootstrap/system-context.js +46 -19
  19. package/dist/resources/extensions/gsd/bootstrap/tool-call-loop-guard.js +68 -10
  20. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +1 -1
  21. package/dist/resources/extensions/gsd/commands-context.js +19 -1
  22. package/dist/resources/extensions/gsd/commands-prefs-wizard.js +16 -10
  23. package/dist/resources/extensions/gsd/commands-worktree.js +12 -10
  24. package/dist/resources/extensions/gsd/dashboard-overlay.js +32 -3
  25. package/dist/resources/extensions/gsd/db/queries.js +60 -0
  26. package/dist/resources/extensions/gsd/db-workspace.js +55 -3
  27. package/dist/resources/extensions/gsd/doctor-providers.js +92 -8
  28. package/dist/resources/extensions/gsd/exec-sandbox.js +45 -9
  29. package/dist/resources/extensions/gsd/forensics.js +2 -32
  30. package/dist/resources/extensions/gsd/git-service.js +4 -4
  31. package/dist/resources/extensions/gsd/guided-flow-queue.js +66 -5
  32. package/dist/resources/extensions/gsd/health-widget.js +55 -29
  33. package/dist/resources/extensions/gsd/layout-policy.js +3 -1
  34. package/dist/resources/extensions/gsd/markdown-renderer.js +8 -9
  35. package/dist/resources/extensions/gsd/memory-consolidation-scanner.js +44 -21
  36. package/dist/resources/extensions/gsd/migration-auto-check.js +22 -0
  37. package/dist/resources/extensions/gsd/milestone-ids.js +32 -2
  38. package/dist/resources/extensions/gsd/milestone-implementation-evidence.js +26 -20
  39. package/dist/resources/extensions/gsd/prompts/code-review.md +6 -4
  40. package/dist/resources/extensions/gsd/quick.js +45 -2
  41. package/dist/resources/extensions/gsd/session-forensics.js +11 -1
  42. package/dist/resources/extensions/gsd/skills/gsd-headless/references/commands.md +1 -1
  43. package/dist/resources/extensions/gsd/state/derive/cache.js +28 -0
  44. package/dist/resources/extensions/gsd/state/derive/db-open.js +39 -0
  45. package/dist/resources/extensions/gsd/state/derive/from-db.js +452 -0
  46. package/dist/resources/extensions/gsd/state/derive/index.js +75 -0
  47. package/dist/resources/extensions/gsd/state/derive/interrupted-work.js +21 -0
  48. package/dist/resources/extensions/gsd/state-reconciliation/drift/stale-render.js +45 -2
  49. package/dist/resources/extensions/gsd/state-reconciliation/index.js +48 -23
  50. package/dist/resources/extensions/gsd/state-reconciliation/registry.js +32 -28
  51. package/dist/resources/extensions/gsd/state.js +12 -611
  52. package/dist/resources/extensions/gsd/tools/complete-slice.js +2 -2
  53. package/dist/resources/extensions/gsd/tools/complete-task.js +43 -14
  54. package/dist/resources/extensions/gsd/tools/exec-tool.js +7 -2
  55. package/dist/resources/extensions/gsd/unit-context-composer.js +23 -7
  56. package/dist/resources/extensions/gsd/unit-registry.js +32 -4
  57. package/dist/resources/extensions/gsd/unmerged-milestone-guard.js +33 -3
  58. package/dist/resources/extensions/gsd/validation-block-guard.js +9 -4
  59. package/dist/resources/extensions/gsd/workflow-projections.js +19 -14
  60. package/dist/resources/extensions/gsd/workspace-git-preflight.js +30 -1
  61. package/dist/resources/extensions/gsd/worktree-manager.js +44 -2
  62. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  63. package/dist/web/standalone/.next/BUILD_ID +1 -1
  64. package/dist/web/standalone/.next/app-path-routes-manifest.json +10 -10
  65. package/dist/web/standalone/.next/build-manifest.json +3 -3
  66. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  67. package/dist/web/standalone/.next/react-loadable-manifest.json +9 -9
  68. package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  69. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  70. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  71. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  72. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  73. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  74. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  75. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  76. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  77. package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  78. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  79. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  80. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  81. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  82. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  83. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  84. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  85. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  86. package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
  87. package/dist/web/standalone/.next/server/app/index.html +1 -1
  88. package/dist/web/standalone/.next/server/app/index.rsc +2 -2
  89. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  90. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +2 -2
  91. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  92. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  93. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  94. package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  95. package/dist/web/standalone/.next/server/app-paths-manifest.json +10 -10
  96. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  97. package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
  98. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  99. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  100. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  101. package/dist/web/standalone/.next/static/chunks/{2659.b7b129ee6a769448.js → 2659.58e950899a9bb82f.js} +1 -1
  102. package/dist/web/standalone/.next/static/chunks/2772.a7c1fcc69a4685ef.js +1 -0
  103. package/dist/web/standalone/.next/static/chunks/{3616.3c60753b8ffcbd2e.js → 3616.61a2af74bb8833c8.js} +1 -1
  104. package/dist/web/standalone/.next/static/chunks/{4283.8e446784528ed9dc.js → 4283.d0d9e0a955e441cb.js} +1 -1
  105. package/dist/web/standalone/.next/static/chunks/{5826.a46ecdd1cfe8dabc.js → 5826.5421d66c72b9f34e.js} +1 -1
  106. package/dist/web/standalone/.next/static/chunks/{8785.481aa5869991b760.js → 8785.e29b3134cab1d153.js} +1 -1
  107. package/dist/web/standalone/.next/static/chunks/8937.640dc9c2aaa1dfad.js +10 -0
  108. package/dist/web/standalone/.next/static/chunks/app/{page-6644fc6ee8ca1247.js → page-72a856634ad14c10.js} +1 -1
  109. package/dist/web/standalone/.next/static/chunks/webpack-9c401904f87ded16.js +1 -0
  110. package/dist/web/standalone/node_modules/node-pty/build/Makefile +1 -1
  111. package/package.json +1 -1
  112. package/packages/cloud-mcp-gateway/package.json +2 -2
  113. package/packages/contracts/dist/workflow.d.ts +1 -0
  114. package/packages/contracts/dist/workflow.d.ts.map +1 -1
  115. package/packages/contracts/dist/workflow.js +2 -0
  116. package/packages/contracts/dist/workflow.js.map +1 -1
  117. package/packages/contracts/package.json +1 -1
  118. package/packages/daemon/package.json +4 -4
  119. package/packages/gsd-agent-core/dist/agent-session.d.ts +1 -0
  120. package/packages/gsd-agent-core/dist/agent-session.d.ts.map +1 -1
  121. package/packages/gsd-agent-core/dist/agent-session.js +3 -0
  122. package/packages/gsd-agent-core/dist/agent-session.js.map +1 -1
  123. package/packages/gsd-agent-core/dist/extension-ui-snapshot.d.ts +41 -0
  124. package/packages/gsd-agent-core/dist/extension-ui-snapshot.d.ts.map +1 -0
  125. package/packages/gsd-agent-core/dist/extension-ui-snapshot.js +62 -0
  126. package/packages/gsd-agent-core/dist/extension-ui-snapshot.js.map +1 -0
  127. package/packages/gsd-agent-core/dist/index.d.ts +2 -0
  128. package/packages/gsd-agent-core/dist/index.d.ts.map +1 -1
  129. package/packages/gsd-agent-core/dist/index.js +2 -0
  130. package/packages/gsd-agent-core/dist/index.js.map +1 -1
  131. package/packages/gsd-agent-core/dist/session/agent-session-events.js +1 -1
  132. package/packages/gsd-agent-core/dist/session/agent-session-events.js.map +1 -1
  133. package/packages/gsd-agent-core/dist/session/agent-session-host.d.ts +1 -0
  134. package/packages/gsd-agent-core/dist/session/agent-session-host.d.ts.map +1 -1
  135. package/packages/gsd-agent-core/dist/session/agent-session-host.js.map +1 -1
  136. package/packages/gsd-agent-core/dist/session/agent-session-prompt.d.ts +5 -0
  137. package/packages/gsd-agent-core/dist/session/agent-session-prompt.d.ts.map +1 -1
  138. package/packages/gsd-agent-core/dist/session/agent-session-prompt.js +60 -3
  139. package/packages/gsd-agent-core/dist/session/agent-session-prompt.js.map +1 -1
  140. package/packages/gsd-agent-core/dist/transcript-store.d.ts +58 -0
  141. package/packages/gsd-agent-core/dist/transcript-store.d.ts.map +1 -0
  142. package/packages/gsd-agent-core/dist/transcript-store.js +132 -0
  143. package/packages/gsd-agent-core/dist/transcript-store.js.map +1 -0
  144. package/packages/gsd-agent-core/package.json +5 -5
  145. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.d.ts +2 -0
  146. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  147. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js +25 -11
  148. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js.map +1 -1
  149. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller-latency.d.ts +4 -0
  150. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller-latency.d.ts.map +1 -0
  151. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller-latency.js +7 -0
  152. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller-latency.js.map +1 -0
  153. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.d.ts +3 -24
  154. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  155. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js +26 -829
  156. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  157. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-handoff-filter.d.ts +58 -0
  158. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-handoff-filter.d.ts.map +1 -0
  159. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-handoff-filter.js +312 -0
  160. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-handoff-filter.js.map +1 -0
  161. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-pinned-zone.d.ts +31 -0
  162. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-pinned-zone.d.ts.map +1 -0
  163. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-pinned-zone.js +130 -0
  164. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-pinned-zone.js.map +1 -0
  165. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-segment-walker.d.ts +15 -0
  166. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-segment-walker.d.ts.map +1 -0
  167. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-segment-walker.js +258 -0
  168. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-segment-walker.js.map +1 -0
  169. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-tool-rollup.d.ts +13 -0
  170. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-tool-rollup.d.ts.map +1 -0
  171. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-tool-rollup.js +118 -0
  172. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-tool-rollup.js.map +1 -0
  173. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-state.d.ts +9 -0
  174. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-state.d.ts.map +1 -1
  175. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-state.js +1 -1
  176. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-state.js.map +1 -1
  177. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-ui-state.d.ts +54 -0
  178. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-ui-state.d.ts.map +1 -0
  179. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-ui-state.js +20 -0
  180. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-ui-state.js.map +1 -0
  181. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.d.ts +4 -1
  182. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  183. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.js +9 -0
  184. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.js.map +1 -1
  185. package/packages/gsd-agent-modes/dist/modes/interactive/streaming-render-state.d.ts +56 -0
  186. package/packages/gsd-agent-modes/dist/modes/interactive/streaming-render-state.d.ts.map +1 -0
  187. package/packages/gsd-agent-modes/dist/modes/interactive/streaming-render-state.js +44 -0
  188. package/packages/gsd-agent-modes/dist/modes/interactive/streaming-render-state.js.map +1 -0
  189. package/packages/gsd-agent-modes/dist/modes/interactive/tui-transcript-tracker.d.ts +5 -0
  190. package/packages/gsd-agent-modes/dist/modes/interactive/tui-transcript-tracker.d.ts.map +1 -0
  191. package/packages/gsd-agent-modes/dist/modes/interactive/tui-transcript-tracker.js +77 -0
  192. package/packages/gsd-agent-modes/dist/modes/interactive/tui-transcript-tracker.js.map +1 -0
  193. package/packages/gsd-agent-modes/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  194. package/packages/gsd-agent-modes/dist/modes/rpc/rpc-mode.js +18 -0
  195. package/packages/gsd-agent-modes/dist/modes/rpc/rpc-mode.js.map +1 -1
  196. package/packages/gsd-agent-modes/package.json +7 -7
  197. package/packages/mcp-server/README.md +1 -1
  198. package/packages/mcp-server/dist/server.d.ts +1 -1
  199. package/packages/mcp-server/dist/server.d.ts.map +1 -1
  200. package/packages/mcp-server/dist/server.js +3 -3
  201. package/packages/mcp-server/dist/server.js.map +1 -1
  202. package/packages/mcp-server/dist/workflow-tools.d.ts +13 -1
  203. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  204. package/packages/mcp-server/dist/workflow-tools.js +34 -20
  205. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  206. package/packages/mcp-server/package.json +4 -4
  207. package/packages/native/package.json +1 -1
  208. package/packages/pi-agent-core/package.json +1 -1
  209. package/packages/pi-ai/package.json +1 -1
  210. package/packages/pi-coding-agent/README.md +3 -2
  211. package/packages/pi-coding-agent/dist/core/session-manager-context.d.ts +9 -0
  212. package/packages/pi-coding-agent/dist/core/session-manager-context.d.ts.map +1 -0
  213. package/packages/pi-coding-agent/dist/core/session-manager-context.js +94 -0
  214. package/packages/pi-coding-agent/dist/core/session-manager-context.js.map +1 -0
  215. package/packages/pi-coding-agent/dist/core/session-manager-list.d.ts +8 -0
  216. package/packages/pi-coding-agent/dist/core/session-manager-list.d.ts.map +1 -0
  217. package/packages/pi-coding-agent/dist/core/session-manager-list.js +244 -0
  218. package/packages/pi-coding-agent/dist/core/session-manager-list.js.map +1 -0
  219. package/packages/pi-coding-agent/dist/core/session-manager-migration.d.ts +12 -0
  220. package/packages/pi-coding-agent/dist/core/session-manager-migration.d.ts.map +1 -0
  221. package/packages/pi-coding-agent/dist/core/session-manager-migration.js +84 -0
  222. package/packages/pi-coding-agent/dist/core/session-manager-migration.js.map +1 -0
  223. package/packages/pi-coding-agent/dist/core/session-manager-types.d.ts +135 -0
  224. package/packages/pi-coding-agent/dist/core/session-manager-types.d.ts.map +1 -0
  225. package/packages/pi-coding-agent/dist/core/session-manager-types.js +2 -0
  226. package/packages/pi-coding-agent/dist/core/session-manager-types.js.map +1 -0
  227. package/packages/pi-coding-agent/dist/core/session-manager.d.ts +6 -154
  228. package/packages/pi-coding-agent/dist/core/session-manager.d.ts.map +1 -1
  229. package/packages/pi-coding-agent/dist/core/session-manager.js +22 -459
  230. package/packages/pi-coding-agent/dist/core/session-manager.js.map +1 -1
  231. package/packages/pi-coding-agent/dist/theme/theme-schema.d.ts +75 -75
  232. package/packages/pi-coding-agent/dist/theme/theme-schema.d.ts.map +1 -1
  233. package/packages/pi-coding-agent/dist/theme/theme-schema.js +1 -1
  234. package/packages/pi-coding-agent/dist/theme/theme-schema.js.map +1 -1
  235. package/packages/pi-coding-agent/dist/theme/theme.d.ts.map +1 -1
  236. package/packages/pi-coding-agent/dist/theme/theme.js +11 -7
  237. package/packages/pi-coding-agent/dist/theme/theme.js.map +1 -1
  238. package/packages/pi-coding-agent/package.json +7 -7
  239. package/packages/pi-tui/package.json +2 -2
  240. package/packages/rpc-client/package.json +2 -2
  241. package/pkg/dist/theme/theme-schema.d.ts +75 -75
  242. package/pkg/dist/theme/theme-schema.d.ts.map +1 -1
  243. package/pkg/dist/theme/theme-schema.js +1 -1
  244. package/pkg/dist/theme/theme-schema.js.map +1 -1
  245. package/pkg/dist/theme/theme.d.ts.map +1 -1
  246. package/pkg/dist/theme/theme.js +11 -7
  247. package/pkg/dist/theme/theme.js.map +1 -1
  248. package/pkg/package.json +1 -1
  249. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +20 -2
  250. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +80 -0
  251. package/src/resources/extensions/google-cli/stream-adapter.ts +106 -19
  252. package/src/resources/extensions/gsd/artifact-verification.ts +464 -0
  253. package/src/resources/extensions/gsd/auto/orchestrator.ts +25 -11
  254. package/src/resources/extensions/gsd/auto/session.ts +5 -0
  255. package/src/resources/extensions/gsd/auto-artifact-paths.ts +47 -1
  256. package/src/resources/extensions/gsd/auto-dispatch.ts +21 -23
  257. package/src/resources/extensions/gsd/auto-prompts.ts +38 -12
  258. package/src/resources/extensions/gsd/auto-recovery.ts +10 -508
  259. package/src/resources/extensions/gsd/auto-runtime-state.ts +4 -5
  260. package/src/resources/extensions/gsd/auto-timeout-recovery.ts +3 -2
  261. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +125 -12
  262. package/src/resources/extensions/gsd/bootstrap/core-session-tools.ts +43 -0
  263. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +6 -1
  264. package/src/resources/extensions/gsd/bootstrap/exec-tools.ts +2 -0
  265. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +11 -19
  266. package/src/resources/extensions/gsd/bootstrap/system-context.ts +52 -18
  267. package/src/resources/extensions/gsd/bootstrap/tool-call-loop-guard.ts +74 -10
  268. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +1 -1
  269. package/src/resources/extensions/gsd/commands-context.ts +18 -1
  270. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +14 -9
  271. package/src/resources/extensions/gsd/commands-worktree.ts +12 -10
  272. package/src/resources/extensions/gsd/dashboard-overlay.ts +32 -3
  273. package/src/resources/extensions/gsd/db/queries.ts +79 -0
  274. package/src/resources/extensions/gsd/db-workspace.ts +61 -3
  275. package/src/resources/extensions/gsd/doctor-providers.ts +103 -9
  276. package/src/resources/extensions/gsd/exec-sandbox.ts +49 -9
  277. package/src/resources/extensions/gsd/forensics.ts +2 -33
  278. package/src/resources/extensions/gsd/git-service.ts +5 -5
  279. package/src/resources/extensions/gsd/guided-flow-queue.ts +89 -4
  280. package/src/resources/extensions/gsd/health-widget.ts +69 -32
  281. package/src/resources/extensions/gsd/layout-policy.ts +2 -1
  282. package/src/resources/extensions/gsd/markdown-renderer.ts +8 -11
  283. package/src/resources/extensions/gsd/memory-consolidation-scanner.ts +51 -19
  284. package/src/resources/extensions/gsd/migration-auto-check.ts +23 -0
  285. package/src/resources/extensions/gsd/milestone-ids.ts +31 -2
  286. package/src/resources/extensions/gsd/milestone-implementation-evidence.ts +35 -21
  287. package/src/resources/extensions/gsd/prompts/code-review.md +6 -4
  288. package/src/resources/extensions/gsd/quick.ts +43 -2
  289. package/src/resources/extensions/gsd/session-forensics.ts +11 -1
  290. package/src/resources/extensions/gsd/skills/gsd-headless/references/commands.md +1 -1
  291. package/src/resources/extensions/gsd/state/derive/cache.ts +46 -0
  292. package/src/resources/extensions/gsd/state/derive/db-open.ts +45 -0
  293. package/src/resources/extensions/gsd/state/derive/from-db.ts +561 -0
  294. package/src/resources/extensions/gsd/state/derive/index.ts +104 -0
  295. package/src/resources/extensions/gsd/state/derive/interrupted-work.ts +31 -0
  296. package/src/resources/extensions/gsd/state-reconciliation/drift/stale-render.ts +81 -7
  297. package/src/resources/extensions/gsd/state-reconciliation/index.ts +50 -24
  298. package/src/resources/extensions/gsd/state-reconciliation/registry.ts +43 -28
  299. package/src/resources/extensions/gsd/state.ts +32 -732
  300. package/src/resources/extensions/gsd/tests/auto-artifact-paths.test.ts +98 -1
  301. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +111 -1
  302. package/src/resources/extensions/gsd/tests/commands-context.test.ts +26 -0
  303. package/src/resources/extensions/gsd/tests/commands-gsd-core.test.ts +1 -0
  304. package/src/resources/extensions/gsd/tests/commands-worktree-clean.test.ts +80 -0
  305. package/src/resources/extensions/gsd/tests/complete-slice.test.ts +11 -0
  306. package/src/resources/extensions/gsd/tests/complete-task-rollback-evidence.test.ts +48 -8
  307. package/src/resources/extensions/gsd/tests/complete-task.test.ts +75 -0
  308. package/src/resources/extensions/gsd/tests/dashboard-overlay.test.ts +55 -2
  309. package/src/resources/extensions/gsd/tests/dispatch-rule-coverage.test.ts +26 -1
  310. package/src/resources/extensions/gsd/tests/doctor-forensics-db-open-regression.test.ts +70 -2
  311. package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +107 -0
  312. package/src/resources/extensions/gsd/tests/exec-graceful-kill.test.ts +38 -0
  313. package/src/resources/extensions/gsd/tests/exec-tool.test.ts +45 -1
  314. package/src/resources/extensions/gsd/tests/forensics-error-filter.test.ts +88 -0
  315. package/src/resources/extensions/gsd/tests/guided-discuss-milestone-prompt-rendering.test.ts +42 -0
  316. package/src/resources/extensions/gsd/tests/health-widget.test.ts +268 -3
  317. package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +119 -1
  318. package/src/resources/extensions/gsd/tests/integration/queue-active-milestone-context-budget.test.ts +93 -0
  319. package/src/resources/extensions/gsd/tests/integration/quick-branch-lifecycle.test.ts +56 -9
  320. package/src/resources/extensions/gsd/tests/knowledge-cold-start.test.ts +14 -0
  321. package/src/resources/extensions/gsd/tests/memory-consolidation-scanner.test.ts +78 -0
  322. package/src/resources/extensions/gsd/tests/migration-auto-check.test.ts +70 -0
  323. package/src/resources/extensions/gsd/tests/orchestrator-logs.test.ts +43 -1
  324. package/src/resources/extensions/gsd/tests/parallel-research-dispatch.test.ts +26 -0
  325. package/src/resources/extensions/gsd/tests/parsers-legacy-importers.test.ts +2 -1
  326. package/src/resources/extensions/gsd/tests/pipeline-variant-dispatch.test.ts +1 -1
  327. package/src/resources/extensions/gsd/tests/prefs-wizard-coverage.test.ts +54 -1
  328. package/src/resources/extensions/gsd/tests/progressive-planning.test.ts +50 -14
  329. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +195 -1
  330. package/src/resources/extensions/gsd/tests/read-uat-gate-verdict.test.ts +185 -0
  331. package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +87 -0
  332. package/src/resources/extensions/gsd/tests/runtime-invariant-modules.test.ts +7 -0
  333. package/src/resources/extensions/gsd/tests/stalled-tool-recovery.test.ts +56 -0
  334. package/src/resources/extensions/gsd/tests/state-reconciliation-drift.test.ts +191 -0
  335. package/src/resources/extensions/gsd/tests/tool-call-loop-guard.test.ts +151 -0
  336. package/src/resources/extensions/gsd/tests/tool-param-optionality.test.ts +26 -0
  337. package/src/resources/extensions/gsd/tests/unit-context-composer.test.ts +193 -14
  338. package/src/resources/extensions/gsd/tests/unmerged-milestone-guard.test.ts +25 -0
  339. package/src/resources/extensions/gsd/tests/validation-block-guard.test.ts +79 -0
  340. package/src/resources/extensions/gsd/tests/verify-artifact-tightened.test.ts +66 -0
  341. package/src/resources/extensions/gsd/tests/workspace-git-preflight.test.ts +151 -2
  342. package/src/resources/extensions/gsd/tests/worktree-manager.test.ts +39 -0
  343. package/src/resources/extensions/gsd/tools/complete-slice.ts +2 -2
  344. package/src/resources/extensions/gsd/tools/complete-task.ts +53 -15
  345. package/src/resources/extensions/gsd/tools/exec-tool.ts +7 -3
  346. package/src/resources/extensions/gsd/unit-context-composer.ts +33 -7
  347. package/src/resources/extensions/gsd/unit-registry.ts +32 -4
  348. package/src/resources/extensions/gsd/unmerged-milestone-guard.ts +41 -5
  349. package/src/resources/extensions/gsd/validation-block-guard.ts +13 -7
  350. package/src/resources/extensions/gsd/workflow-projections.ts +20 -14
  351. package/src/resources/extensions/gsd/workspace-git-preflight.ts +31 -0
  352. package/src/resources/extensions/gsd/worktree-manager.ts +41 -1
  353. package/dist/web/standalone/.next/static/chunks/2772.bfa657f49f955239.js +0 -1
  354. package/dist/web/standalone/.next/static/chunks/796.e0bdc932325d7e03.js +0 -10
  355. package/dist/web/standalone/.next/static/chunks/webpack-f46ea08200a0227e.js +0 -1
  356. /package/dist/web/standalone/.next/static/{BTKtGFF1Y-hvVJEGhBRo9 → O7xDYXO0r4zFhIzY1hrWV}/_buildManifest.js +0 -0
  357. /package/dist/web/standalone/.next/static/{BTKtGFF1Y-hvVJEGhBRo9 → O7xDYXO0r4zFhIzY1hrWV}/_ssgManifest.js +0 -0
@@ -1,6 +1,8 @@
1
1
  // gsd-pi + src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts - Handles provider and agent-end recovery for GSD auto-mode.
2
2
 
3
3
  import type { ExtensionAPI, ExtensionContext } from "@gsd/pi-coding-agent";
4
+ import { mkdirSync, readdirSync, writeFileSync } from "node:fs";
5
+ import { join } from "node:path";
4
6
 
5
7
  import type { AgentEndEvent, ErrorContext } from "../auto/types.js";
6
8
  import { logWarning } from "../workflow-logger.js";
@@ -11,7 +13,7 @@ import {
11
13
  maybeHandleEmptyIntentTurn,
12
14
  resetEmptyTurnCounter,
13
15
  } from "../guided-flow.js";
14
- import { clearPathCache } from "../paths.js";
16
+ import { clearPathCache, gsdRoot } from "../paths.js";
15
17
  import {
16
18
  getAutoDashboardData,
17
19
  getAutoModeStartModel,
@@ -34,7 +36,7 @@ import { resolveModelId } from "../auto-model-selection.js";
34
36
  import { resolveProjectRoot } from "../worktree.js";
35
37
  import { clearDiscussionFlowState } from "./write-gate.js";
36
38
  import { scheduleFallbackContinuation } from "./fallback-continuation.js";
37
- import { clearGuidedUnitContext } from "../guided-unit-context.js";
39
+ import { clearGuidedUnitContext, getGuidedUnitContext, type GuidedUnitContext } from "../guided-unit-context.js";
38
40
  import { resumeAutoAfterProviderDelay } from "./provider-error-resume.js";
39
41
  import {
40
42
  classifyError,
@@ -116,9 +118,14 @@ export function isUserInitiatedAbortMessage(message: string | undefined | null):
116
118
  export function shouldDeferTransientErrorToCoreRetry(
117
119
  cls: ErrorClass,
118
120
  rawErrorMsg: string,
121
+ deferCheckMsg: string = rawErrorMsg,
119
122
  ): boolean {
120
123
  if (!isTransient(cls) || cls.kind === "rate-limit") return false;
121
- return !/retry failed after \d+ attempts:/i.test(rawErrorMsg);
124
+ // Empty rawErrorMsg means the SDK terminated the session without providing an
125
+ // error string — core is done, not mid-retry. GSD must schedule its own
126
+ // retry rather than silently deferring to a core that has already exited.
127
+ if (!rawErrorMsg) return false;
128
+ return !/retry failed after \d+ attempts:/i.test(deferCheckMsg);
122
129
  }
123
130
 
124
131
  type ProviderModelFallbackParams = {
@@ -388,6 +395,108 @@ export function suppressTerminalDeletedWorktreeMessageEnd(event: MessageEndLike)
388
395
  return true;
389
396
  }
390
397
 
398
+ function modelLabel(ctx: ExtensionContext): string {
399
+ const provider = ctx.model?.provider;
400
+ const id = ctx.model?.id;
401
+ return provider && id ? `${provider}/${id}` : "unknown model";
402
+ }
403
+
404
+ function isFatalManualGuidedTerminalFailure(lastMsg: unknown): boolean {
405
+ if (!isObjectRecord(lastMsg) || !("stopReason" in lastMsg)) return false;
406
+ if (lastMsg.stopReason === "error") {
407
+ const rawErrorMsg = ("errorMessage" in lastMsg && lastMsg.errorMessage) ? String(lastMsg.errorMessage) : "";
408
+ if (isUserInitiatedAbortMessage(rawErrorMsg)) return false;
409
+ return true;
410
+ }
411
+ if (lastMsg.stopReason !== "aborted") return false;
412
+
413
+ const content = "content" in lastMsg ? lastMsg.content : undefined;
414
+ const hasErrorMessage = "errorMessage" in lastMsg && !!lastMsg.errorMessage;
415
+ return hasErrorMessage || !_hasEmptyAgentEndContent(content);
416
+ }
417
+
418
+ function terminalFailureDetail(lastMsg: unknown): string {
419
+ if (!isObjectRecord(lastMsg)) return "Provider turn ended with an unknown terminal error.";
420
+ const rawErrorMsg = ("errorMessage" in lastMsg && lastMsg.errorMessage) ? String(lastMsg.errorMessage) : "";
421
+ const content = "content" in lastMsg ? lastMsg.content : undefined;
422
+ const displayMsg = resolveAgentEndErrorDisplay(rawErrorMsg, content).replace(/\s+/g, " ").trim();
423
+ if (displayMsg) return displayMsg.length > 300 ? `${displayMsg.slice(0, 300)}...` : displayMsg;
424
+ return lastMsg.stopReason === "aborted"
425
+ ? "Provider turn aborted with error context."
426
+ : "Provider stream ended with stopReason=error.";
427
+ }
428
+
429
+ function nextManualActivitySequence(activityDir: string): string {
430
+ let maxSeq = 0;
431
+ try {
432
+ for (const file of readdirSync(activityDir)) {
433
+ const match = /^(\d+)-/.exec(file);
434
+ if (match) maxSeq = Math.max(maxSeq, Number.parseInt(match[1]!, 10));
435
+ }
436
+ } catch {
437
+ return "001";
438
+ }
439
+ return String(maxSeq + 1).padStart(3, "0");
440
+ }
441
+
442
+ function writeManualGuidedTerminalErrorActivity(
443
+ basePath: string,
444
+ unitType: string,
445
+ model: string,
446
+ detail: string,
447
+ stopReason: unknown,
448
+ ): void {
449
+ const activityDir = join(gsdRoot(basePath), "activity");
450
+ mkdirSync(activityDir, { recursive: true });
451
+ const seq = nextManualActivitySequence(activityDir);
452
+ const safeUnitType = unitType.replace(/[^a-z0-9_.-]+/gi, "-");
453
+ const markerPath = join(activityDir, `${seq}-${safeUnitType}-manual-terminal-provider-error.jsonl`);
454
+ const message = `Manual guided ${unitType} turn ended with provider ${String(stopReason)} on ${model}: ${detail}`;
455
+ writeFileSync(
456
+ markerPath,
457
+ JSON.stringify({
458
+ type: "message",
459
+ message: {
460
+ role: "toolResult",
461
+ toolCallId: "manual-guided-terminal-provider-error",
462
+ toolName: "provider",
463
+ isError: true,
464
+ content: [{ type: "text", text: message }],
465
+ },
466
+ }) + "\n",
467
+ "utf-8",
468
+ );
469
+ }
470
+
471
+ function observeManualDiscussTerminalError(
472
+ ctx: ExtensionContext,
473
+ lastMsg: unknown,
474
+ guidedUnit: GuidedUnitContext | null,
475
+ ): void {
476
+ if (!guidedUnit?.unitType.startsWith("discuss-")) return;
477
+ if (!isFatalManualGuidedTerminalFailure(lastMsg)) return;
478
+
479
+ const model = modelLabel(ctx);
480
+ const detail = terminalFailureDetail(lastMsg);
481
+ ctx.ui.notify(
482
+ `Manual /gsd discuss ${guidedUnit.unitType} ended with a provider error on ${model}: ${detail}`,
483
+ "warning",
484
+ );
485
+
486
+ try {
487
+ writeManualGuidedTerminalErrorActivity(
488
+ guidedUnit.basePath,
489
+ guidedUnit.unitType,
490
+ model,
491
+ detail,
492
+ isObjectRecord(lastMsg) ? lastMsg.stopReason : "unknown",
493
+ );
494
+ } catch (err) {
495
+ const message = err instanceof Error ? err.message : String(err);
496
+ logWarning("bootstrap", `Failed to write manual guided terminal-error activity marker: ${message}`);
497
+ }
498
+ }
499
+
391
500
  async function pauseTransientWithBackoff(
392
501
  cls: ErrorClass,
393
502
  pi: ExtensionAPI,
@@ -437,7 +546,9 @@ export async function handleAgentEnd(
437
546
  // rejected" loop even though the files are on disk.
438
547
  clearPathCache();
439
548
  const basePath = resolveAgentEndBasePath();
440
- clearGuidedUnitContext(basePath);
549
+ const lastMsg = event.messages[event.messages.length - 1];
550
+ const guidedUnit = basePath ? getGuidedUnitContext(basePath) ?? getGuidedUnitContext() : getGuidedUnitContext();
551
+ clearGuidedUnitContext(guidedUnit?.basePath ?? basePath);
441
552
 
442
553
  try {
443
554
  if (await checkDeepProjectSetupAfterTurn(event, ctx, basePath)) {
@@ -466,13 +577,15 @@ export async function handleAgentEnd(
466
577
  // discussions (where isAutoActive may be false) still get recovered.
467
578
  if (maybeHandleEmptyIntentTurn(event, isAutoActive(), basePath)) return;
468
579
 
469
- if (!isAutoActive()) return;
580
+ if (!isAutoActive()) {
581
+ observeManualDiscussTerminalError(ctx, lastMsg, guidedUnit);
582
+ return;
583
+ }
470
584
 
471
585
  if (shouldIgnoreAgentEndForActiveUnit(event)) {
472
586
  return;
473
587
  }
474
588
 
475
- const lastMsg = event.messages[event.messages.length - 1];
476
589
  if (isSessionSwitchInFlight()) {
477
590
  _handleSessionSwitchAgentEnd(lastMsg, resolveAgentEndCancelled);
478
591
  return;
@@ -555,9 +668,9 @@ export async function handleAgentEnd(
555
668
  });
556
669
  return;
557
670
  }
558
- // #3588: When errorMessage is uninformative, extract the real error from
559
- // the assistant message text content for display purposes only.
560
- // Classification still uses rawErrorMsg to avoid false positives from prose.
671
+ // #3588/#956: When errorMessage is uninformative, extract the real error
672
+ // from assistant text. Prefer rawErrorMsg for classification to avoid
673
+ // prose false-positives, but use display text when rawErrorMsg is empty.
561
674
  const displayMsg = resolveAgentEndErrorDisplay(
562
675
  rawErrorMsg,
563
676
  "content" in lastMsg ? lastMsg.content : undefined,
@@ -576,8 +689,8 @@ export async function handleAgentEnd(
576
689
  const errorDetail = displayMsg ? `: ${displayMsg}` : "";
577
690
  const explicitRetryAfterMs = ("retryAfterMs" in lastMsg && typeof lastMsg.retryAfterMs === "number") ? lastMsg.retryAfterMs : undefined;
578
691
 
579
- // ── 1. Classify using rawErrorMsg to avoid prose false-positives ────
580
- const cls = classifyError(rawErrorMsg, explicitRetryAfterMs);
692
+ // ── 1. Classify, preserving non-empty errorMessage precedence ──────
693
+ const cls = classifyError(rawErrorMsg || displayMsg, explicitRetryAfterMs);
581
694
 
582
695
  // ── 1a. Unsupported-model: provider rejected this model for the current
583
696
  // account/plan at request time (#4513). Persist a block so the
@@ -655,7 +768,7 @@ export async function handleAgentEnd(
655
768
  // Core retries transient failures in-session after this handler.
656
769
  // Keep that behavior for non-rate-limit classes to avoid pause/retry races,
657
770
  // but let rate-limit continue into model fallback logic below (#4373).
658
- if (shouldDeferTransientErrorToCoreRetry(cls, rawErrorMsg)) {
771
+ if (shouldDeferTransientErrorToCoreRetry(cls, rawErrorMsg, rawErrorMsg || displayMsg)) {
659
772
  return;
660
773
  }
661
774
 
@@ -0,0 +1,43 @@
1
+ // Project/App: gsd-pi
2
+ // File Purpose: Canonical core session tool surface shared by auto-mode scoping and loop guards.
3
+
4
+ /** Base tools available during auto-mode execution before unit-specific GSD tools are added. */
5
+ export const MINIMAL_AUTO_BASE_TOOL_NAMES = [
6
+ "ask_user_questions",
7
+ "bash",
8
+ "bg_shell",
9
+ "edit",
10
+ "find",
11
+ "glob",
12
+ "grep",
13
+ "fetch_page",
14
+ "search-the-web",
15
+ "ls",
16
+ "read",
17
+ "subagent",
18
+ "write",
19
+ "ToolSearch",
20
+ ] as const;
21
+
22
+ /** Tools excluded from the high per-turn loop-guard cap (strict or orchestration). */
23
+ const NON_REPEATABLE_FROM_MINIMAL_BASE = new Set<string>([
24
+ "ask_user_questions",
25
+ "subagent",
26
+ "ToolSearch",
27
+ ]);
28
+
29
+ /** Additional core tools not in MINIMAL_AUTO_BASE but routinely multi-called per turn. */
30
+ const EXTRA_INHERENTLY_REPEATABLE = [
31
+ "multi_edit",
32
+ "todo_write",
33
+ "notebook_edit",
34
+ "search_and_read",
35
+ ] as const;
36
+
37
+ /** Core session tools that may be invoked many times per agent turn. */
38
+ export const INHERENTLY_REPEATABLE_TOOL_NAMES = [
39
+ ...MINIMAL_AUTO_BASE_TOOL_NAMES.filter((name) => !NON_REPEATABLE_FROM_MINIMAL_BASE.has(name)),
40
+ ...EXTRA_INHERENTLY_REPEATABLE,
41
+ ] as const;
42
+
43
+ export const INHERENTLY_REPEATABLE_TOOL_SET = new Set<string>(INHERENTLY_REPEATABLE_TOOL_NAMES);
@@ -3,6 +3,7 @@
3
3
  import { Type, StringEnum } from "@gsd/pi-ai";
4
4
  import type { ExtensionAPI } from "@gsd/pi-coding-agent";
5
5
  import { Text } from "@gsd/pi-tui";
6
+ import { SUMMARY_SAVE_CONTENT_MAX_LENGTH } from "@opengsd/contracts";
6
7
 
7
8
  import { loadEffectiveGSDPreferences } from "../preferences.js";
8
9
  import { ensureDbOpen, resolveCtxCwd, resolveWorkflowToolBasePath } from "./dynamic-tools.js";
@@ -392,13 +393,17 @@ export function registerDbTools(pi: ExtensionAPI): void {
392
393
  "Root-level artifact paths are PROJECT.md, PROJECT-DRAFT.md, REQUIREMENTS.md, and REQUIREMENTS-DRAFT.md.",
393
394
  "artifact_type must be one of: SUMMARY, RESEARCH, CONTEXT, ASSESSMENT, CONTEXT-DRAFT, PROJECT, PROJECT-DRAFT, REQUIREMENTS, REQUIREMENTS-DRAFT.",
394
395
  "Use CONTEXT-DRAFT for incremental draft persistence; use CONTEXT for the final milestone context after depth verification.",
396
+ `Keep each content payload under ${SUMMARY_SAVE_CONTENT_MAX_LENGTH} characters; save large context incrementally with CONTEXT-DRAFT/PROJECT-DRAFT/REQUIREMENTS-DRAFT instead of one oversized call.`,
395
397
  ],
396
398
  parameters: Type.Object({
397
399
  milestone_id: Type.Optional(Type.String({ description: "Milestone ID (e.g. M001). Omit only for root-level PROJECT/PROJECT-DRAFT/REQUIREMENTS/REQUIREMENTS-DRAFT artifacts." })),
398
400
  slice_id: Type.Optional(Type.String({ description: "Slice ID (e.g. S01)" })),
399
401
  task_id: Type.Optional(Type.String({ description: "Task ID (e.g. T01)" })),
400
402
  artifact_type: StringEnum(["SUMMARY", "RESEARCH", "CONTEXT", "ASSESSMENT", "CONTEXT-DRAFT", "PROJECT", "PROJECT-DRAFT", "REQUIREMENTS", "REQUIREMENTS-DRAFT"], { description: "Artifact type to save" }),
401
- content: Type.String({ description: "The full markdown content of the artifact" }),
403
+ content: Type.String({
404
+ description: `The full markdown content of the artifact. Maximum ${SUMMARY_SAVE_CONTENT_MAX_LENGTH} characters per save.`,
405
+ maxLength: SUMMARY_SAVE_CONTENT_MAX_LENGTH,
406
+ }),
402
407
  }),
403
408
  execute: summarySaveExecute,
404
409
  renderCall(args: any, theme: any) {
@@ -72,6 +72,7 @@ export function registerExecTools(pi: ExtensionAPI): void {
72
72
  return executeUatExec(params as Parameters<typeof executeUatExec>[0], {
73
73
  baseDir,
74
74
  preferences: await loadContextModePreferences(baseDir),
75
+ signal: _signal,
75
76
  });
76
77
  },
77
78
  });
@@ -119,6 +120,7 @@ export function registerExecTools(pi: ExtensionAPI): void {
119
120
  return executeGsdExec(params as Parameters<typeof executeGsdExec>[0], {
120
121
  baseDir,
121
122
  preferences: await loadContextModePreferences(baseDir),
123
+ signal: _signal,
122
124
  });
123
125
  },
124
126
  });
@@ -13,7 +13,7 @@ import type { GSDEcosystemBeforeAgentStartHandler } from "../ecosystem/gsd-exten
13
13
  import { updateSnapshot } from "../ecosystem/gsd-extension-api.js";
14
14
 
15
15
  import { buildMilestoneFileName, canonicalPhaseDirName, clearPathCache, milestonesDir, legacyMilestonesDir, resolveMilestonePath, resolveSliceFile, resolveSlicePath } from "../paths.js";
16
- import { applyAskUserQuestionsGateResult, clearDiscussionFlowState, formatPendingAskUserQuestionsGateMessage, formatTimedOutAskUserQuestionsGateMessage, hostWriteGateAdapter, isApprovalGateVerifiedInSnapshot, isDepthConfirmationAnswer, isMilestoneDepthVerified, isMilestoneDepthVerifiedInSnapshot, isQueuePhaseActive, resetWriteGateState, shouldBlockContextWrite, shouldBlockPlanningUnit, shouldBlockQueueExecution, shouldBlockWorktreeBash, shouldBlockWorktreeWrite, isGateQuestionId, getPendingGate, shouldBlockPendingGate, shouldBlockPendingGateBash, extractDepthVerificationMilestoneId } from "./write-gate.js";
16
+ import { applyAskUserQuestionsGateResult, clearDiscussionFlowState, currentWriteGateSnapshot, formatPendingAskUserQuestionsGateMessage, formatTimedOutAskUserQuestionsGateMessage, hostWriteGateAdapter, isApprovalGateVerifiedInSnapshot, isDepthConfirmationAnswer, isMilestoneDepthVerifiedInSnapshot, isQueuePhaseActive, resetWriteGateState, shouldBlockContextWrite, shouldBlockPlanningUnit, shouldBlockQueueExecution, shouldBlockWorktreeBash, shouldBlockWorktreeWrite, isGateQuestionId, getPendingGate, shouldBlockPendingGate, shouldBlockPendingGateBash, extractDepthVerificationMilestoneId, type WriteGateSnapshot } from "./write-gate.js";
17
17
  import { canonicalToolName } from "../engine-hook-contract.js";
18
18
  import { resolveManifest } from "../unit-context-manifest.js";
19
19
  import { isBlockedStateFile, isBashWriteToStateFile, BLOCKED_WRITE_ERROR } from "../write-intercept.js";
@@ -35,6 +35,7 @@ import {
35
35
  import { applyProviderPayloadPolicy } from "../provider-payload-policy.js";
36
36
 
37
37
  import { checkToolCallLoop, resetToolCallLoopGuard } from "./tool-call-loop-guard.js";
38
+ import { MINIMAL_AUTO_BASE_TOOL_NAMES } from "./core-session-tools.js";
38
39
  import { maybePauseAutoForApprovalGate, resetPendingGatePauseGuard } from "./pending-gate-pause.js";
39
40
  import { saveActivityLog } from "../activity-log.js";
40
41
  import { recordToolCall as safetyRecordToolCall, recordToolResult as safetyRecordToolResult, saveEvidenceToDisk } from "../safety/evidence-collector.js";
@@ -178,22 +179,7 @@ export const MINIMAL_GSD_TOOL_NAMES = [
178
179
  "gsd_capture_thought",
179
180
  ] as const;
180
181
 
181
- export const MINIMAL_AUTO_BASE_TOOL_NAMES = [
182
- "ask_user_questions",
183
- "bash",
184
- "bg_shell",
185
- "edit",
186
- "find",
187
- "glob",
188
- "grep",
189
- "fetch_page",
190
- "search-the-web",
191
- "ls",
192
- "read",
193
- "subagent",
194
- "write",
195
- "ToolSearch",
196
- ] as const;
182
+ export { MINIMAL_AUTO_BASE_TOOL_NAMES } from "./core-session-tools.js";
197
183
 
198
184
  function withPreservedShimTools(toolNames: readonly string[]): string[] {
199
185
  return [...new Set([...toolNames, ...ALWAYS_PRESERVED_SHIM_TOOL_NAMES])];
@@ -583,6 +569,10 @@ function deferApprovalGate(gateId: string, basePath: string): void {
583
569
  // workflow MCP child already verified this gate, deferring would block
584
570
  // tools for a gate that can never legitimately arm.
585
571
  const snapshot = hostWriteGateAdapter.readState(basePath);
572
+ deferApprovalGateFromSnapshot(gateId, basePath, snapshot);
573
+ }
574
+
575
+ function deferApprovalGateFromSnapshot(gateId: string, basePath: string, snapshot: WriteGateSnapshot): void {
586
576
  if (isApprovalGateVerifiedInSnapshot(snapshot, gateId)) return;
587
577
  const milestoneId = extractDepthVerificationMilestoneId(gateId);
588
578
  if (milestoneId && isMilestoneDepthVerifiedInSnapshot(snapshot, milestoneId)) return;
@@ -1233,13 +1223,15 @@ export function registerHooks(
1233
1223
 
1234
1224
  const gateId = approvalGateIdForUnit(unitType, unitId);
1235
1225
  if (gateId) {
1226
+ const basePath = contextBasePath(ctx);
1227
+ const gateSnapshot = currentWriteGateSnapshot(basePath);
1236
1228
  // Skip the gate if this milestone is already depth-verified — the approval
1237
1229
  // pattern matched again on post-verification text (a false-positive re-trigger).
1238
1230
  // Without this guard, the second firing blocks gsd_plan_milestone in the same
1239
1231
  // turn and leaves CONTEXT.md on disk with no DB row (#discuss-milestone-no-db).
1240
1232
  const gateMilestoneId = extractDepthVerificationMilestoneId(gateId);
1241
- if (gateMilestoneId && isMilestoneDepthVerified(gateMilestoneId, contextBasePath(ctx))) return;
1242
- deferApprovalGate(gateId, contextBasePath(ctx));
1233
+ if (gateMilestoneId && isMilestoneDepthVerifiedInSnapshot(gateSnapshot, gateMilestoneId)) return;
1234
+ deferApprovalGateFromSnapshot(gateId, basePath, gateSnapshot);
1243
1235
  }
1244
1236
 
1245
1237
  approvalQuestionAbortInFlight = true;
@@ -28,9 +28,10 @@ const DEFAULT_CODEBASE_MAX_CHARS = 8_000;
28
28
  const MIN_CONTEXT_MESSAGE_MAX_CHARS = 1_000;
29
29
  const MIN_KNOWLEDGE_MAX_CHARS = 1_000;
30
30
 
31
- const contextMaintenanceCompletedForBasePath = new Set<string>();
32
- const contextMaintenanceInFlightByBasePath = new Map<string, Promise<boolean>>();
33
- const deferredContextMaintenanceByBasePath = new Map<string, Promise<void>>();
31
+ const CONTEXT_MAINTENANCE_KEY_SEPARATOR = "\0";
32
+ const contextMaintenanceCompletedForSession = new Set<string>();
33
+ const contextMaintenanceInFlightBySession = new Map<string, Promise<boolean>>();
34
+ const deferredContextMaintenanceBySession = new Map<string, Promise<void>>();
34
35
 
35
36
  /**
36
37
  * Bundled skill triggers — resolved dynamically at runtime instead of
@@ -114,7 +115,8 @@ async function runSessionStartupMaintenanceOnce(
114
115
  basePath: string,
115
116
  ctx: ExtensionContext,
116
117
  ): Promise<boolean> {
117
- if (contextMaintenanceCompletedForBasePath.has(basePath)) {
118
+ const maintenanceKey = getContextMaintenanceKey(basePath, ctx);
119
+ if (contextMaintenanceCompletedForSession.has(maintenanceKey)) {
118
120
  // Backfills are session-once, but memory queries and other DB-backed
119
121
  // prompt assembly still need an active adapter on every turn.
120
122
  try {
@@ -126,16 +128,16 @@ async function runSessionStartupMaintenanceOnce(
126
128
  return false;
127
129
  }
128
130
 
129
- const existing = contextMaintenanceInFlightByBasePath.get(basePath);
131
+ const existing = contextMaintenanceInFlightBySession.get(maintenanceKey);
130
132
  const isInitiator = !existing;
131
133
  // Use a definite Promise<boolean> so `await inFlight` has a known return type.
132
134
  let inFlight: Promise<boolean>;
133
135
  if (isInitiator) {
134
- inFlight = performSessionStartupMaintenance(basePath, ctx);
135
- contextMaintenanceInFlightByBasePath.set(basePath, inFlight);
136
+ inFlight = performSessionStartupMaintenance(basePath, ctx, maintenanceKey);
137
+ contextMaintenanceInFlightBySession.set(maintenanceKey, inFlight);
136
138
  void inFlight.finally(() => {
137
- if (contextMaintenanceInFlightByBasePath.get(basePath) === inFlight) {
138
- contextMaintenanceInFlightByBasePath.delete(basePath);
139
+ if (contextMaintenanceInFlightBySession.get(maintenanceKey) === inFlight) {
140
+ contextMaintenanceInFlightBySession.delete(maintenanceKey);
139
141
  }
140
142
  });
141
143
  } else {
@@ -149,6 +151,7 @@ async function runSessionStartupMaintenanceOnce(
149
151
  async function performSessionStartupMaintenance(
150
152
  basePath: string,
151
153
  ctx: ExtensionContext,
154
+ maintenanceKey: string,
152
155
  ): Promise<boolean> {
153
156
  // DB-backed memory backfills run below. On a cold session the database file
154
157
  // may exist without an active in-process adapter, so open the canonical
@@ -172,11 +175,40 @@ async function performSessionStartupMaintenance(
172
175
 
173
176
  // Mark session complete before scheduling deferred work so any concurrent
174
177
  // caller that observes the completed state does not re-enter maintenance.
175
- contextMaintenanceCompletedForBasePath.add(basePath);
176
- scheduleDeferredContextMaintenance(basePath);
178
+ contextMaintenanceCompletedForSession.add(maintenanceKey);
179
+ scheduleDeferredContextMaintenance(basePath, maintenanceKey);
177
180
  return true;
178
181
  }
179
182
 
183
+ function getContextMaintenanceKey(basePath: string, ctx: ExtensionContext): string {
184
+ return `${basePath}${CONTEXT_MAINTENANCE_KEY_SEPARATOR}${getContextSessionPart(ctx)}`;
185
+ }
186
+
187
+ function getContextSessionPart(ctx: ExtensionContext): string {
188
+ const sessionManager = (ctx as {
189
+ sessionManager?: {
190
+ getSessionId?: () => string | undefined;
191
+ getSessionFile?: () => string | undefined;
192
+ };
193
+ }).sessionManager;
194
+
195
+ try {
196
+ const sessionId = sessionManager?.getSessionId?.();
197
+ if (typeof sessionId === "string" && sessionId.length > 0) return `id:${sessionId}`;
198
+ } catch (e) {
199
+ logWarning("bootstrap", `session-id fetch failed: ${(e as Error).message}`);
200
+ }
201
+
202
+ try {
203
+ const sessionFile = sessionManager?.getSessionFile?.();
204
+ if (typeof sessionFile === "string" && sessionFile.length > 0) return `file:${sessionFile}`;
205
+ } catch (e) {
206
+ logWarning("bootstrap", `session-file fetch failed: ${(e as Error).message}`);
207
+ }
208
+
209
+ return "process";
210
+ }
211
+
180
212
  async function runDecisionsMemoryBackfill(ctx: ExtensionContext): Promise<void> {
181
213
  // ADR-013 step 5: opportunistic decisions->memories backfill. Idempotent
182
214
  // and best-effort — first run absorbs the existing decisions table into
@@ -210,8 +242,8 @@ async function runKnowledgeMemoryBackfill(
210
242
  }
211
243
  }
212
244
 
213
- function scheduleDeferredContextMaintenance(basePath: string): void {
214
- if (deferredContextMaintenanceByBasePath.has(basePath)) return;
245
+ function scheduleDeferredContextMaintenance(basePath: string, maintenanceKey: string): void {
246
+ if (deferredContextMaintenanceBySession.has(maintenanceKey)) return;
215
247
 
216
248
  const task = new Promise<void>((resolve) => {
217
249
  setTimeout(() => {
@@ -219,10 +251,10 @@ function scheduleDeferredContextMaintenance(basePath: string): void {
219
251
  }, 0);
220
252
  });
221
253
 
222
- deferredContextMaintenanceByBasePath.set(basePath, task);
254
+ deferredContextMaintenanceBySession.set(maintenanceKey, task);
223
255
  void task.finally(() => {
224
- if (deferredContextMaintenanceByBasePath.get(basePath) === task) {
225
- deferredContextMaintenanceByBasePath.delete(basePath);
256
+ if (deferredContextMaintenanceBySession.get(maintenanceKey) === task) {
257
+ deferredContextMaintenanceBySession.delete(maintenanceKey);
226
258
  }
227
259
  });
228
260
  }
@@ -257,8 +289,10 @@ async function reportConsolidationGapsDeferred(basePath: string): Promise<void>
257
289
 
258
290
  export async function _flushDeferredContextMaintenanceForTest(basePath?: string): Promise<void> {
259
291
  const tasks = basePath
260
- ? [deferredContextMaintenanceByBasePath.get(basePath)].filter((task): task is Promise<void> => Boolean(task))
261
- : [...deferredContextMaintenanceByBasePath.values()];
292
+ ? [...deferredContextMaintenanceBySession.entries()]
293
+ .filter(([key]) => key.startsWith(`${basePath}${CONTEXT_MAINTENANCE_KEY_SEPARATOR}`))
294
+ .map(([, task]) => task)
295
+ : [...deferredContextMaintenanceBySession.values()];
262
296
  await Promise.allSettled(tasks);
263
297
  }
264
298
 
@@ -1,18 +1,28 @@
1
1
  /**
2
2
  * Tool-call loop guard.
3
3
  *
4
- * Detects when a model calls the same tool with identical arguments
5
- * repeatedly within a single agent turn. Works in both auto-mode and
6
- * interactive sessions by hooking into the `tool_call` event, which
7
- * fires before execution and can block the call.
4
+ * Detects when a model repeats tool calls within a single Agent Turn.
5
+ * Works in both auto-mode and interactive sessions by hooking into the
6
+ * native engine's `tool_call` event, which fires before execution and can
7
+ * block the call.
8
8
  *
9
- * The guard uses a sliding window: it tracks the last N tool signatures
10
- * and blocks when the same signature appears more than MAX_CONSECUTIVE
11
- * times in a row. Resets on each agent turn (session_start, agent_end)
12
- * and when a different tool call breaks the streak.
9
+ * The guard has two independent checks: a sliding window for identical
10
+ * tool signatures, and a per-tool-name cap for repeated calls with varied
11
+ * arguments. State resets at Agent Turn boundaries (session_start,
12
+ * agent_end) and the identical-signature streak also resets when a
13
+ * different tool call breaks the streak. Block messages instruct the model
14
+ * to stop tooling for the rest of that turn and answer in text.
15
+ *
16
+ * The per-tool-name check (#783 Brief C) tracks call counts within a
17
+ * turn regardless of args. This catches improvisation
18
+ * loops where the model attempts the same missing workflow tool through
19
+ * varied surfaces (bash → `node -e` → CLI), each with a different
20
+ * signature, so the identical-args streak never trips. Whichever guard
21
+ * trips first blocks.
13
22
  */
14
23
 
15
24
  import { createHash } from "node:crypto";
25
+ import { INHERENTLY_REPEATABLE_TOOL_SET } from "./core-session-tools.js";
16
26
 
17
27
  const MAX_CONSECUTIVE_IDENTICAL_CALLS = 4;
18
28
 
@@ -20,11 +30,27 @@ const MAX_CONSECUTIVE_IDENTICAL_CALLS = 4;
20
30
  const STRICT_LOOP_TOOLS = new Set(["ask_user_questions"]);
21
31
  const MAX_CONSECUTIVE_STRICT = 1;
22
32
 
33
+ /**
34
+ * Per-turn cap on calls to the SAME tool name, regardless of args (#783).
35
+ *
36
+ * General-purpose execution tools are routinely called many times per turn
37
+ * (touching multiple files, running several commands), so they get a higher
38
+ * ceiling. Everything else — workflow one-shot tools (e.g. gsd_complete_milestone)
39
+ * and any non-allowlisted tool — gets the default cap. The default is generous
40
+ * enough to absorb legitimate retries but catches the reported improvisation
41
+ * loop (~51 calls) well before a cost spike.
42
+ */
43
+ const PER_TOOL_DEFAULT_CAP = 6;
44
+ const PER_TOOL_REPEATABLE_CAP = 15;
45
+
23
46
  let consecutiveCount = 0;
24
47
  let lastSignature = "";
25
48
  let lastToolName = "";
26
49
  let enabled = true;
27
50
 
51
+ /** Per-tool-name call counts within the current turn (#783 Brief C). */
52
+ const perToolCounts = new Map<string, number>();
53
+
28
54
  /** Hash tool name + args into a compact signature for comparison. */
29
55
  function hashToolCall(toolName: string, args: Record<string, unknown>): string {
30
56
  const h = createHash("sha256");
@@ -46,6 +72,12 @@ function hashToolCall(toolName: string, args: Record<string, unknown>): string {
46
72
  *
47
73
  * Returns `{ block: false }` for allowed calls.
48
74
  * Returns `{ block: true, reason }` when the loop threshold is exceeded.
75
+ *
76
+ * Two independent guards run; whichever trips first blocks:
77
+ * 1. Identical-signature streak (MAX_CONSECUTIVE_IDENTICAL_CALLS, strict for
78
+ * ask_user_questions).
79
+ * 2. Per-tool-name cap (PER_TOOL_DEFAULT_CAP / PER_TOOL_REPEATABLE_CAP),
80
+ * independent of args — catches improvisation loops (#783).
49
81
  */
50
82
  export function checkToolCallLoop(
51
83
  toolName: string,
@@ -63,6 +95,7 @@ export function checkToolCallLoop(
63
95
  lastToolName = toolName;
64
96
  }
65
97
 
98
+ // ── Guard 1: identical-signature streak ──
66
99
  const threshold = STRICT_LOOP_TOOLS.has(toolName)
67
100
  ? MAX_CONSECUTIVE_STRICT
68
101
  : MAX_CONSECUTIVE_IDENTICAL_CALLS;
@@ -71,13 +104,34 @@ export function checkToolCallLoop(
71
104
  return {
72
105
  block: true,
73
106
  reason:
74
- `Tool loop detected: ${toolName} called ${consecutiveCount} times ` +
107
+ `Tool loop detected (identical args): ${toolName} called ${consecutiveCount} times ` +
75
108
  `with identical arguments. Blocking to prevent infinite loop. ` +
76
- `Try a different approach or modify your arguments.`,
109
+ `Do not retry this tool or call other tools this turn — stop and respond to the user in text.`,
77
110
  count: consecutiveCount,
78
111
  };
79
112
  }
80
113
 
114
+ // ── Guard 2: per-tool-name cap, independent of args (#783 Brief C) ──
115
+ // Catches improvisation loops where the same tool is invoked many times with
116
+ // varied args (e.g. retrying a missing workflow tool via bash/node -e/CLI).
117
+ const perToolCount = (perToolCounts.get(toolName) ?? 0) + 1;
118
+ perToolCounts.set(toolName, perToolCount);
119
+ const perToolCap = INHERENTLY_REPEATABLE_TOOL_SET.has(toolName)
120
+ ? PER_TOOL_REPEATABLE_CAP
121
+ : PER_TOOL_DEFAULT_CAP;
122
+
123
+ if (perToolCount > perToolCap) {
124
+ return {
125
+ block: true,
126
+ reason:
127
+ `Tool loop detected (repeated tool): ${toolName} called ${perToolCount} times ` +
128
+ `this turn (cap ${perToolCap}). Blocking to prevent infinite loop. ` +
129
+ `The tool may be unavailable or failing repeatedly. ` +
130
+ `Do not retry this tool or pivot to other tools this turn — stop and respond to the user in text.`,
131
+ count: perToolCount,
132
+ };
133
+ }
134
+
81
135
  return { block: false, count: consecutiveCount };
82
136
  }
83
137
 
@@ -87,6 +141,7 @@ export function resetToolCallLoopGuard(): void {
87
141
  lastSignature = "";
88
142
  lastToolName = "";
89
143
  enabled = true;
144
+ perToolCounts.clear();
90
145
  }
91
146
 
92
147
  /** Disable the guard (e.g. during shutdown). */
@@ -95,9 +150,18 @@ export function disableToolCallLoopGuard(): void {
95
150
  consecutiveCount = 0;
96
151
  lastSignature = "";
97
152
  lastToolName = "";
153
+ perToolCounts.clear();
98
154
  }
99
155
 
100
156
  /** Get current consecutive count for diagnostics. */
101
157
  export function getToolCallLoopCount(): number {
102
158
  return consecutiveCount;
103
159
  }
160
+
161
+ /**
162
+ * Get the per-tool-name call count for the current turn (#783 Brief C).
163
+ * Returns 0 for tools not yet called. Diagnostic only.
164
+ */
165
+ export function getToolCallCountForTool(toolName: string): number {
166
+ return perToolCounts.get(toolName) ?? 0;
167
+ }
@@ -180,7 +180,7 @@ function ensureWriteGateSnapshotDirectory(basePath: string): void {
180
180
  mkdirSync(join(gsdPath, "runtime"), { recursive: true });
181
181
  }
182
182
 
183
- function currentWriteGateSnapshot(basePath: string = process.cwd()): WriteGateSnapshot {
183
+ export function currentWriteGateSnapshot(basePath: string = process.cwd()): WriteGateSnapshot {
184
184
  const state = getWriteGateState(basePath);
185
185
  return {
186
186
  verifiedDepthMilestones: [...state.verifiedDepthMilestones].sort(),