@opengsd/gsd-pi 1.1.1-dev.9bb7453 → 1.1.1-dev.9f86580

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 (219) hide show
  1. package/dist/resources/.managed-resources-content-hash +1 -1
  2. package/dist/resources/extensions/browser-tools/engine/managed-gsd-browser.js +18 -2
  3. package/dist/resources/extensions/browser-tools/engine/selection.js +1 -1
  4. package/dist/resources/extensions/browser-tools/extension-manifest.json +1 -1
  5. package/dist/resources/extensions/browser-tools/index.js +29 -2
  6. package/dist/resources/extensions/browser-tools/web-app-detect.js +52 -0
  7. package/dist/resources/extensions/gsd/auto/phases.js +45 -3
  8. package/dist/resources/extensions/gsd/auto/session.js +2 -0
  9. package/dist/resources/extensions/gsd/auto-dispatch.js +21 -2
  10. package/dist/resources/extensions/gsd/auto-model-selection.js +26 -0
  11. package/dist/resources/extensions/gsd/auto-prompts.js +4 -0
  12. package/dist/resources/extensions/gsd/auto-recovery.js +3 -4
  13. package/dist/resources/extensions/gsd/auto-timers.js +24 -10
  14. package/dist/resources/extensions/gsd/auto-unit-tool-scope.js +18 -66
  15. package/dist/resources/extensions/gsd/auto-worktree.js +18 -5
  16. package/dist/resources/extensions/gsd/auto.js +26 -4
  17. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +16 -10
  18. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +48 -29
  19. package/dist/resources/extensions/gsd/bootstrap/system-context.js +1 -1
  20. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +18 -29
  21. package/dist/resources/extensions/gsd/closeout-consistency-gate.js +61 -0
  22. package/dist/resources/extensions/gsd/commands/handlers/auto.js +10 -0
  23. package/dist/resources/extensions/gsd/commands-mcp-status.js +1 -1
  24. package/dist/resources/extensions/gsd/config-overlay.js +1 -0
  25. package/dist/resources/extensions/gsd/context-masker.js +129 -5
  26. package/dist/resources/extensions/gsd/guided-flow.js +93 -108
  27. package/dist/resources/extensions/gsd/milestone-closeout.js +3 -1
  28. package/dist/resources/extensions/gsd/pending-auto-start.js +0 -1
  29. package/dist/resources/extensions/gsd/planner-handoff.js +98 -0
  30. package/dist/resources/extensions/gsd/preferences-models.js +1 -0
  31. package/dist/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
  32. package/dist/resources/extensions/gsd/prompts/run-uat.md +5 -19
  33. package/dist/resources/extensions/gsd/prompts/system.md +1 -1
  34. package/dist/resources/extensions/gsd/recovery-classification.js +20 -0
  35. package/dist/resources/extensions/gsd/skill-manifest.js +12 -0
  36. package/dist/resources/extensions/gsd/tool-contract.js +6 -1
  37. package/dist/resources/extensions/gsd/tool-presentation-plan.js +47 -7
  38. package/dist/resources/extensions/gsd/tools/complete-slice.js +28 -1
  39. package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +113 -8
  40. package/dist/resources/extensions/gsd/unit-tool-contracts.js +193 -0
  41. package/dist/resources/extensions/gsd/workflow-mcp.js +5 -78
  42. package/dist/resources/extensions/gsd/worktree-manager.js +26 -0
  43. package/dist/resources/extensions/gsd/worktree-reentry.js +96 -0
  44. package/dist/resources/extensions/shared/gsd-browser-cli.js +6 -0
  45. package/dist/web/standalone/.next/BUILD_ID +1 -1
  46. package/dist/web/standalone/.next/app-path-routes-manifest.json +5 -5
  47. package/dist/web/standalone/.next/build-manifest.json +2 -2
  48. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  49. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  50. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  51. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  52. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  53. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  54. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  55. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  56. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  57. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  58. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  59. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  60. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  61. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  62. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  63. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  64. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  65. package/dist/web/standalone/.next/server/app/index.html +1 -1
  66. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  67. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  68. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  69. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  70. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  71. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  72. package/dist/web/standalone/.next/server/app-paths-manifest.json +5 -5
  73. package/dist/web/standalone/.next/server/chunks/8357.js +1 -1
  74. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  75. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  76. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  77. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  78. package/package.json +1 -1
  79. package/packages/cloud-mcp-gateway/package.json +2 -2
  80. package/packages/contracts/package.json +1 -1
  81. package/packages/daemon/package.json +4 -4
  82. package/packages/gsd-agent-core/package.json +5 -5
  83. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  84. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js +5 -0
  85. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js.map +1 -1
  86. package/packages/gsd-agent-modes/package.json +7 -7
  87. package/packages/mcp-server/package.json +3 -3
  88. package/packages/native/package.json +1 -1
  89. package/packages/pi-agent-core/dist/agent-loop.js +4 -3
  90. package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
  91. package/packages/pi-agent-core/dist/harness/agent-harness.d.ts.map +1 -1
  92. package/packages/pi-agent-core/dist/harness/agent-harness.js +3 -1
  93. package/packages/pi-agent-core/dist/harness/agent-harness.js.map +1 -1
  94. package/packages/pi-agent-core/dist/harness/types.d.ts +1 -0
  95. package/packages/pi-agent-core/dist/harness/types.d.ts.map +1 -1
  96. package/packages/pi-agent-core/dist/harness/types.js.map +1 -1
  97. package/packages/pi-agent-core/dist/types.d.ts +3 -1
  98. package/packages/pi-agent-core/dist/types.d.ts.map +1 -1
  99. package/packages/pi-agent-core/dist/types.js.map +1 -1
  100. package/packages/pi-agent-core/package.json +1 -1
  101. package/packages/pi-ai/dist/models.generated.d.ts +157 -18
  102. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  103. package/packages/pi-ai/dist/models.generated.js +159 -36
  104. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  105. package/packages/pi-ai/dist/providers/transform-messages.d.ts.map +1 -1
  106. package/packages/pi-ai/dist/providers/transform-messages.js +8 -1
  107. package/packages/pi-ai/dist/providers/transform-messages.js.map +1 -1
  108. package/packages/pi-ai/package.json +1 -1
  109. package/packages/pi-coding-agent/dist/core/extensions/extension-upstream-types.d.ts +3 -0
  110. package/packages/pi-coding-agent/dist/core/extensions/extension-upstream-types.d.ts.map +1 -1
  111. package/packages/pi-coding-agent/dist/core/extensions/extension-upstream-types.js.map +1 -1
  112. package/packages/pi-coding-agent/dist/core/tools/bash.js +2 -2
  113. package/packages/pi-coding-agent/dist/core/tools/bash.js.map +1 -1
  114. package/packages/pi-coding-agent/dist/core/tools/edit.d.ts.map +1 -1
  115. package/packages/pi-coding-agent/dist/core/tools/edit.js +3 -2
  116. package/packages/pi-coding-agent/dist/core/tools/edit.js.map +1 -1
  117. package/packages/pi-coding-agent/dist/core/tools/render-utils.d.ts +1 -0
  118. package/packages/pi-coding-agent/dist/core/tools/render-utils.d.ts.map +1 -1
  119. package/packages/pi-coding-agent/dist/core/tools/render-utils.js +6 -0
  120. package/packages/pi-coding-agent/dist/core/tools/render-utils.js.map +1 -1
  121. package/packages/pi-coding-agent/dist/core/tools/write.d.ts.map +1 -1
  122. package/packages/pi-coding-agent/dist/core/tools/write.js +3 -2
  123. package/packages/pi-coding-agent/dist/core/tools/write.js.map +1 -1
  124. package/packages/pi-coding-agent/package.json +7 -7
  125. package/packages/pi-tui/package.json +1 -1
  126. package/packages/rpc-client/package.json +2 -2
  127. package/pkg/package.json +1 -1
  128. package/scripts/install/handoff.js +16 -3
  129. package/src/resources/extensions/browser-tools/engine/managed-gsd-browser.ts +21 -2
  130. package/src/resources/extensions/browser-tools/engine/selection.ts +1 -1
  131. package/src/resources/extensions/browser-tools/extension-manifest.json +1 -1
  132. package/src/resources/extensions/browser-tools/index.ts +36 -5
  133. package/src/resources/extensions/browser-tools/tests/browser-engine-selection.test.mjs +2 -2
  134. package/src/resources/extensions/browser-tools/tests/gsd-browser-launch-config.test.mjs +37 -0
  135. package/src/resources/extensions/browser-tools/tests/web-app-detect.test.mjs +68 -0
  136. package/src/resources/extensions/browser-tools/web-app-detect.ts +63 -0
  137. package/src/resources/extensions/gsd/auto/phases.ts +48 -6
  138. package/src/resources/extensions/gsd/auto/session.ts +2 -0
  139. package/src/resources/extensions/gsd/auto-dispatch.ts +48 -2
  140. package/src/resources/extensions/gsd/auto-model-selection.ts +26 -0
  141. package/src/resources/extensions/gsd/auto-prompts.ts +4 -0
  142. package/src/resources/extensions/gsd/auto-recovery.ts +3 -3
  143. package/src/resources/extensions/gsd/auto-timers.ts +25 -9
  144. package/src/resources/extensions/gsd/auto-unit-tool-scope.ts +43 -74
  145. package/src/resources/extensions/gsd/auto-worktree.ts +23 -5
  146. package/src/resources/extensions/gsd/auto.ts +28 -4
  147. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +16 -10
  148. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +63 -29
  149. package/src/resources/extensions/gsd/bootstrap/system-context.ts +1 -1
  150. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +50 -54
  151. package/src/resources/extensions/gsd/closeout-consistency-gate.ts +137 -0
  152. package/src/resources/extensions/gsd/commands/handlers/auto.ts +9 -0
  153. package/src/resources/extensions/gsd/commands-mcp-status.ts +1 -1
  154. package/src/resources/extensions/gsd/config-overlay.ts +1 -0
  155. package/src/resources/extensions/gsd/context-masker.ts +152 -5
  156. package/src/resources/extensions/gsd/guided-flow.ts +128 -135
  157. package/src/resources/extensions/gsd/milestone-closeout.ts +3 -1
  158. package/src/resources/extensions/gsd/pending-auto-start.ts +0 -2
  159. package/src/resources/extensions/gsd/planner-handoff.ts +149 -0
  160. package/src/resources/extensions/gsd/preferences-models.ts +1 -0
  161. package/src/resources/extensions/gsd/preferences-types.ts +8 -0
  162. package/src/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
  163. package/src/resources/extensions/gsd/prompts/run-uat.md +5 -19
  164. package/src/resources/extensions/gsd/prompts/system.md +1 -1
  165. package/src/resources/extensions/gsd/recovery-classification.ts +20 -0
  166. package/src/resources/extensions/gsd/skill-manifest.ts +12 -0
  167. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +99 -0
  168. package/src/resources/extensions/gsd/tests/auto-model-selection-tool-poisoning.test.ts +66 -4
  169. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +10 -2
  170. package/src/resources/extensions/gsd/tests/auto-start-bootstrap-await-3420.test.ts +4 -1
  171. package/src/resources/extensions/gsd/tests/auto-supervisor.test.mjs +4 -0
  172. package/src/resources/extensions/gsd/tests/auto-warning-noise-regression.test.ts +12 -2
  173. package/src/resources/extensions/gsd/tests/bundled-skill-triggers.test.ts +9 -0
  174. package/src/resources/extensions/gsd/tests/check-auto-start-pending-gate.test.ts +9 -15
  175. package/src/resources/extensions/gsd/tests/check-auto-start-ready-guard.test.ts +26 -16
  176. package/src/resources/extensions/gsd/tests/commands-dispatcher-unmerged-milestone.test.ts +21 -0
  177. package/src/resources/extensions/gsd/tests/complete-slice-verification-gate.test.ts +118 -0
  178. package/src/resources/extensions/gsd/tests/context-masker.test.ts +56 -1
  179. package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +1 -0
  180. package/src/resources/extensions/gsd/tests/dispatch-complete-milestone-guard.test.ts +40 -1
  181. package/src/resources/extensions/gsd/tests/dispatch-rule-coverage.test.ts +24 -0
  182. package/src/resources/extensions/gsd/tests/gate-1b-orphan-discrimination.test.ts +31 -79
  183. package/src/resources/extensions/gsd/tests/guided-flow-session-isolation.test.ts +5 -3
  184. package/src/resources/extensions/gsd/tests/guided-flow-state-rebuild.test.ts +40 -4
  185. package/src/resources/extensions/gsd/tests/integration/auto-worktree-milestone-merge.test.ts +8 -0
  186. package/src/resources/extensions/gsd/tests/integration/parallel-merge.test.ts +16 -0
  187. package/src/resources/extensions/gsd/tests/integration/run-uat.test.ts +7 -1
  188. package/src/resources/extensions/gsd/tests/interrupted-session-auto.test.ts +27 -0
  189. package/src/resources/extensions/gsd/tests/journal-integration.test.ts +1 -0
  190. package/src/resources/extensions/gsd/tests/mcp-project-config.test.ts +7 -1
  191. package/src/resources/extensions/gsd/tests/mcp-status.test.ts +1 -1
  192. package/src/resources/extensions/gsd/tests/merge-closeout-consistency-gate.test.ts +63 -0
  193. package/src/resources/extensions/gsd/tests/merge-db-cycle.test.ts +10 -1
  194. package/src/resources/extensions/gsd/tests/milestone-closeout.test.ts +9 -1
  195. package/src/resources/extensions/gsd/tests/planner-handoff.test.ts +100 -0
  196. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +147 -5
  197. package/src/resources/extensions/gsd/tests/provider-switch-observer.test.ts +55 -0
  198. package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +44 -0
  199. package/src/resources/extensions/gsd/tests/run-uat-composer.test.ts +4 -0
  200. package/src/resources/extensions/gsd/tests/runtime-invariant-modules.test.ts +56 -0
  201. package/src/resources/extensions/gsd/tests/skill-manifest.test.ts +4 -3
  202. package/src/resources/extensions/gsd/tests/token-tool-gating.test.ts +4 -4
  203. package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +77 -10
  204. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +409 -0
  205. package/src/resources/extensions/gsd/tests/worktree-reentry.test.ts +102 -0
  206. package/src/resources/extensions/gsd/tests/write-gate-planning-unit.test.ts +15 -0
  207. package/src/resources/extensions/gsd/tool-contract.ts +7 -1
  208. package/src/resources/extensions/gsd/tool-presentation-plan.ts +82 -7
  209. package/src/resources/extensions/gsd/tools/complete-slice.ts +29 -1
  210. package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +146 -9
  211. package/src/resources/extensions/gsd/unit-tool-contracts.ts +210 -0
  212. package/src/resources/extensions/gsd/workflow-mcp.ts +5 -78
  213. package/src/resources/extensions/gsd/worktree-manager.ts +32 -0
  214. package/src/resources/extensions/gsd/worktree-reentry.ts +103 -0
  215. package/src/resources/extensions/shared/gsd-browser-cli.ts +6 -0
  216. package/src/resources/extensions/gsd/tests/gate-1b-recovery-bound-corrections.test.ts +0 -246
  217. package/src/resources/extensions/gsd/tests/gate-1b-recovery-bound.test.ts +0 -218
  218. /package/dist/web/standalone/.next/static/{jBtwT9v1u2lUA3UEOy_ZH → zzYMrKpPGfRQRxSFO32Jr}/_buildManifest.js +0 -0
  219. /package/dist/web/standalone/.next/static/{jBtwT9v1u2lUA3UEOy_ZH → zzYMrKpPGfRQRxSFO32Jr}/_ssgManifest.js +0 -0
@@ -4,7 +4,7 @@ import { isAbsolute, join, relative, resolve, sep } from "node:path";
4
4
 
5
5
  import { minimatch } from "minimatch";
6
6
 
7
- import { shouldBlockAutoUnitToolCall } from "../auto-unit-tool-scope.js";
7
+ import { GSD_PHASE_SCOPE_DISPLAY_REASON, shouldBlockAutoUnitToolCall } from "../auto-unit-tool-scope.js";
8
8
  import { getIsolationMode } from "../preferences.js";
9
9
  import { compileSubagentPermissionContract, type ToolsPolicy } from "../unit-context-manifest.js";
10
10
  import { logWarning } from "../workflow-logger.js";
@@ -772,6 +772,20 @@ function blockReason(unitType: string, mode: string, what: string): string {
772
772
  ].join(" ");
773
773
  }
774
774
 
775
+ function planningBlock(unitType: string, mode: string, what: string): PlanningUnitBlockResult {
776
+ return {
777
+ block: true,
778
+ reason: blockReason(unitType, mode, what),
779
+ displayReason: GSD_PHASE_SCOPE_DISPLAY_REASON,
780
+ };
781
+ }
782
+
783
+ type PlanningUnitBlockResult = {
784
+ block: boolean;
785
+ reason?: string;
786
+ displayReason?: string;
787
+ };
788
+
775
789
  /**
776
790
  * Planning-unit tool-policy enforcement. Returns { block } per the policy
777
791
  * resolved from the active unit's manifest:
@@ -812,7 +826,7 @@ export function shouldBlockPlanningUnit(
812
826
  agentClasses?: readonly string[],
813
827
  toolInput?: unknown,
814
828
  unitId?: string,
815
- ): { block: boolean; reason?: string } {
829
+ ): PlanningUnitBlockResult {
816
830
  const tool = canonicalToolName(toolName);
817
831
  const autoScopeGuard = shouldBlockAutoUnitToolCall(unitType, toolName, toolInput, unitId);
818
832
  if (autoScopeGuard.block) return autoScopeGuard;
@@ -825,10 +839,10 @@ export function shouldBlockPlanningUnit(
825
839
  if (PLANNING_SAFE_TOOLS.has(tool)) return { block: false };
826
840
  if (tool.startsWith("gsd_")) return { block: false };
827
841
  if (PLANNING_WRITE_TOOLS.has(tool) || tool === "bash" || PLANNING_SUBAGENT_TOOLS.has(tool)) {
828
- return { block: true, reason: blockReason(unitType, policy.mode, `${tool} is not permitted (read-only)`) };
842
+ return planningBlock(unitType, policy.mode, `${tool} is not permitted (read-only)`);
829
843
  }
830
844
  // Unknown tool in read-only mode — block by default.
831
- return { block: true, reason: blockReason(unitType, policy.mode, `tool "${tool}" is not on the read-only allowlist`) };
845
+ return planningBlock(unitType, policy.mode, `tool "${tool}" is not on the read-only allowlist`);
832
846
  }
833
847
 
834
848
  // planning / planning-dispatch / docs / verification modes share the same surface for safe tools, bash, and subagent.
@@ -846,14 +860,11 @@ export function shouldBlockPlanningUnit(
846
860
  // instead of silently bypassing the gate.
847
861
  if (agentClasses === undefined) {
848
862
  warnMissingControlledDispatchAgentClasses(unitType, policy.mode, tool);
849
- return {
850
- block: true,
851
- reason: blockReason(
852
- unitType,
853
- policy.mode,
854
- `subagent dispatch blocked: stale caller did not supply agent identities for "${tool}"; update extractSubagentAgentClasses to handle this input shape`,
855
- ),
856
- };
863
+ return planningBlock(
864
+ unitType,
865
+ policy.mode,
866
+ `subagent dispatch blocked: stale caller did not supply agent identities for "${tool}"; update extractSubagentAgentClasses to handle this input shape`,
867
+ );
857
868
  }
858
869
  // agentClasses was explicitly provided but resolved to an empty list (for
859
870
  // example, a bare tool call with no agent field). Pass through; no agents
@@ -863,57 +874,45 @@ export function shouldBlockPlanningUnit(
863
874
  }
864
875
  const globallyDisallowed = requested.find(a => !isReadOnlySpecialist(a));
865
876
  if (globallyDisallowed) {
866
- return {
867
- block: true,
868
- reason: blockReason(
869
- unitType,
870
- policy.mode,
871
- `subagent dispatch of "${globallyDisallowed}" not permitted; only read-only specialists (${allowedPlanningDispatchAgentsList()}) may be dispatched from ${policy.mode} units`,
872
- ),
873
- };
877
+ return planningBlock(
878
+ unitType,
879
+ policy.mode,
880
+ `subagent dispatch of "${globallyDisallowed}" not permitted; only read-only specialists (${allowedPlanningDispatchAgentsList()}) may be dispatched from ${policy.mode} units`,
881
+ );
874
882
  }
875
883
  const disallowedByPolicy = requested.find(a => !allowed.has(a));
876
884
  if (disallowedByPolicy) {
877
- return {
878
- block: true,
879
- reason: blockReason(
880
- unitType,
881
- policy.mode,
882
- `subagent dispatch of "${disallowedByPolicy}" not permitted by ToolsPolicy.allowedSubagents; permitted agents for this unit: ${allowedSubagents.join(", ")}`,
883
- ),
884
- };
885
+ return planningBlock(
886
+ unitType,
887
+ policy.mode,
888
+ `subagent dispatch of "${disallowedByPolicy}" not permitted by ToolsPolicy.allowedSubagents; permitted agents for this unit: ${allowedSubagents.join(", ")}`,
889
+ );
885
890
  }
886
891
  return { block: false };
887
892
  }
888
- return { block: true, reason: blockReason(unitType, policy.mode, `subagent dispatch is not permitted in planning units`) };
893
+ return planningBlock(unitType, policy.mode, "subagent dispatch is not permitted in planning units");
889
894
  }
890
895
 
891
896
  if (tool === "bash") {
892
897
  if (policy.mode === "verification") {
893
898
  if (BASH_VERIFICATION_RE.test(pathOrCommand) || BASH_READ_ONLY_RE.test(pathOrCommand)) return { block: false };
894
- return {
895
- block: true,
896
- reason: blockReason(
897
- unitType,
898
- policy.mode,
899
- `bash is restricted to build/test verification commands (npm run build, npm test, etc.); cannot run "${pathOrCommand.slice(0, 80)}${pathOrCommand.length > 80 ? "…" : ""}"`,
900
- ),
901
- };
902
- }
903
- if (BASH_READ_ONLY_RE.test(pathOrCommand)) return { block: false };
904
- return {
905
- block: true,
906
- reason: blockReason(
899
+ return planningBlock(
907
900
  unitType,
908
901
  policy.mode,
909
- `bash is restricted to read-only commands (cat/grep/git log/etc); cannot run "${pathOrCommand.slice(0, 80)}${pathOrCommand.length > 80 ? "…" : ""}"`,
910
- ),
911
- };
902
+ `bash is restricted to build/test verification commands (npm run build, npm test, etc.); cannot run "${pathOrCommand.slice(0, 80)}${pathOrCommand.length > 80 ? "…" : ""}"`,
903
+ );
904
+ }
905
+ if (BASH_READ_ONLY_RE.test(pathOrCommand)) return { block: false };
906
+ return planningBlock(
907
+ unitType,
908
+ policy.mode,
909
+ `bash is restricted to read-only commands (cat/grep/git log/etc); cannot run "${pathOrCommand.slice(0, 80)}${pathOrCommand.length > 80 ? "…" : ""}"`,
910
+ );
912
911
  }
913
912
 
914
913
  if (PLANNING_WRITE_TOOLS.has(tool)) {
915
914
  if (!pathOrCommand) {
916
- return { block: true, reason: blockReason(unitType, policy.mode, `${tool} called with empty path`) };
915
+ return planningBlock(unitType, policy.mode, `${tool} called with empty path`);
917
916
  }
918
917
  const absPath = isAbsolute(pathOrCommand) ? pathOrCommand : resolve(basePath, pathOrCommand);
919
918
 
@@ -925,14 +924,11 @@ export function shouldBlockPlanningUnit(
925
924
  return { block: false };
926
925
  }
927
926
 
928
- return {
929
- block: true,
930
- reason: blockReason(
931
- unitType,
932
- policy.mode,
933
- `cannot ${tool} "${pathOrCommand}" — writes are restricted to .gsd/${policy.mode === "docs" ? " and " + policy.allowedPathGlobs.join(", ") : ""}`,
934
- ),
935
- };
927
+ return planningBlock(
928
+ unitType,
929
+ policy.mode,
930
+ `cannot ${tool} "${pathOrCommand}" — writes are restricted to .gsd/${policy.mode === "docs" ? " and " + policy.allowedPathGlobs.join(", ") : ""}`,
931
+ );
936
932
  }
937
933
 
938
934
  // Unknown tool name — pass through. Other layers (queue, pending-gate,
@@ -0,0 +1,137 @@
1
+ // Project/App: gsd-pi
2
+ // File Purpose: Shared DB-backed guard for milestone closeout finalization.
3
+
4
+ import {
5
+ getDbPath,
6
+ getLatestAssessmentByScope,
7
+ getMilestone,
8
+ getMilestoneSlices,
9
+ getPendingGates,
10
+ getSliceTasks,
11
+ isDbAvailable,
12
+ refreshOpenDatabaseFromDisk,
13
+ } from "./gsd-db.js";
14
+ import { isClosedStatus } from "./status-guards.js";
15
+
16
+ export const CLOSEOUT_CONSISTENCY_BLOCKED_REASON = "closeout-consistency-blocked";
17
+
18
+ export type CloseoutConsistencyFailureReason =
19
+ | "db-unavailable"
20
+ | "db-refresh-failed"
21
+ | "milestone-missing"
22
+ | "milestone-open"
23
+ | "validation-not-pass"
24
+ | "slice-missing"
25
+ | "slice-open"
26
+ | "task-open"
27
+ | "quality-gate-pending";
28
+
29
+ export type CloseoutConsistencyResult =
30
+ | { ok: true }
31
+ | {
32
+ ok: false;
33
+ reason: CloseoutConsistencyFailureReason;
34
+ recoveryReason: typeof CLOSEOUT_CONSISTENCY_BLOCKED_REASON;
35
+ message: string;
36
+ };
37
+
38
+ export interface CloseoutConsistencyOptions {
39
+ refreshFromDisk?: boolean;
40
+ }
41
+
42
+ function blocked(reason: CloseoutConsistencyFailureReason, message: string): CloseoutConsistencyResult {
43
+ return {
44
+ ok: false,
45
+ reason,
46
+ recoveryReason: CLOSEOUT_CONSISTENCY_BLOCKED_REASON,
47
+ message,
48
+ };
49
+ }
50
+
51
+ function isFileBackedDbPath(path: string | null): boolean {
52
+ return Boolean(path && path !== ":memory:");
53
+ }
54
+
55
+ export function checkCloseoutConsistencyGate(
56
+ milestoneId: string,
57
+ options: CloseoutConsistencyOptions = {},
58
+ ): CloseoutConsistencyResult {
59
+ if (!isDbAvailable()) {
60
+ return blocked(
61
+ "db-unavailable",
62
+ `Closeout consistency blocked for ${milestoneId}: canonical DB is unavailable.`,
63
+ );
64
+ }
65
+
66
+ if (options.refreshFromDisk && isFileBackedDbPath(getDbPath()) && !refreshOpenDatabaseFromDisk()) {
67
+ return blocked(
68
+ "db-refresh-failed",
69
+ `Closeout consistency blocked for ${milestoneId}: canonical DB refresh failed.`,
70
+ );
71
+ }
72
+
73
+ const milestone = getMilestone(milestoneId);
74
+ if (!milestone) {
75
+ return blocked(
76
+ "milestone-missing",
77
+ `Closeout consistency blocked for ${milestoneId}: milestone is missing from canonical DB.`,
78
+ );
79
+ }
80
+ if (!isClosedStatus(milestone.status)) {
81
+ return blocked(
82
+ "milestone-open",
83
+ `Closeout consistency blocked for ${milestoneId}: canonical DB milestone status is "${milestone.status}".`,
84
+ );
85
+ }
86
+
87
+ if (milestone.status !== "skipped") {
88
+ const validation = getLatestAssessmentByScope(milestoneId, "milestone-validation");
89
+ if (validation?.status !== "pass") {
90
+ return blocked(
91
+ "validation-not-pass",
92
+ `Closeout consistency blocked for ${milestoneId}: latest milestone validation is "${validation?.status ?? "absent"}".`,
93
+ );
94
+ }
95
+ }
96
+
97
+ const slices = getMilestoneSlices(milestoneId);
98
+ if (slices.length === 0 && milestone.status !== "skipped") {
99
+ return blocked(
100
+ "slice-missing",
101
+ `Closeout consistency blocked for ${milestoneId}: no slices exist in canonical DB.`,
102
+ );
103
+ }
104
+
105
+ for (const slice of slices) {
106
+ if (!isClosedStatus(slice.status)) {
107
+ return blocked(
108
+ "slice-open",
109
+ `Closeout consistency blocked for ${milestoneId}: slice ${slice.id} status is "${slice.status}".`,
110
+ );
111
+ }
112
+
113
+ for (const task of getSliceTasks(milestoneId, slice.id)) {
114
+ if (!isClosedStatus(task.status)) {
115
+ return blocked(
116
+ "task-open",
117
+ `Closeout consistency blocked for ${milestoneId}: task ${slice.id}/${task.id} status is "${task.status}".`,
118
+ );
119
+ }
120
+ }
121
+
122
+ const pendingGate = getPendingGates(milestoneId, slice.id)[0];
123
+ if (pendingGate) {
124
+ return blocked(
125
+ "quality-gate-pending",
126
+ `Closeout consistency blocked for ${milestoneId}: quality gate ${pendingGate.gate_id} is still pending for ${slice.id}.`,
127
+ );
128
+ }
129
+ }
130
+
131
+ return { ok: true };
132
+ }
133
+
134
+ export function formatCloseoutConsistencyBlock(result: CloseoutConsistencyResult): string {
135
+ if (result.ok) return "";
136
+ return `${result.message} Recovery reason: ${result.recoveryReason}. Resolve the canonical DB state and run /gsd auto to retry.`;
137
+ }
@@ -209,6 +209,15 @@ export async function handleAutoCommand(trimmed: string, ctx: ExtensionCommandCo
209
209
  if (trimmed === "") {
210
210
  if (!(await guardRemoteSession(ctx, pi))) return true;
211
211
  const basePath = projectRoot();
212
+ // Cold start after /quit lands at the project root, not the worktree. If the
213
+ // active milestone has a live worktree, chdir back into it now so the agent
214
+ // doesn't have to search for it. Best-effort; resolves to a no-op otherwise.
215
+ try {
216
+ const { reenterActiveWorktreeIfNeeded } = await import("../../worktree-reentry.js");
217
+ await reenterActiveWorktreeIfNeeded(basePath, {
218
+ notify: (message) => ctx.ui.notify(message, "info"),
219
+ });
220
+ } catch { /* non-fatal */ }
212
221
  const { hasGsdBootstrapArtifacts } = await import("../../detection.js");
213
222
  const { gsdRoot } = await import("../../paths.js");
214
223
  if (!hasGsdBootstrapArtifacts(gsdRoot(basePath))) {
@@ -73,7 +73,7 @@ export function formatMcpInitResult(
73
73
  `Config: ${configPath}`,
74
74
  "",
75
75
  "MCP-capable clients can now load the GSD workflow and gsd-browser MCP servers from this folder.",
76
- "Pi Providers use the managed gsd-browser engine directly; this project config is for External MCP Clients.",
76
+ "Pi Providers use built-in browser tools directly; this project config is for External MCP Clients.",
77
77
  "Restart or reconnect any client that already has this project open.",
78
78
  ].join("\n");
79
79
  }
@@ -139,6 +139,7 @@ function collectConfigSections(): ConfigSection[] {
139
139
  if (sup.model) supRows.push({ label: "Model", value: sup.model });
140
140
  supRows.push({ label: "Soft timeout", value: `${sup.soft_timeout_minutes}m` });
141
141
  supRows.push({ label: "Idle timeout", value: `${sup.idle_timeout_minutes}m` });
142
+ supRows.push({ label: "Stalled tool timeout", value: `${sup.stalled_tool_timeout_minutes}m` });
142
143
  supRows.push({ label: "Hard timeout", value: `${sup.hard_timeout_minutes}m` });
143
144
  sections.push({ title: "Auto Supervisor", rows: supRows });
144
145
  }
@@ -5,10 +5,17 @@
5
5
  * Reduces context bloat between compactions with zero LLM overhead.
6
6
  * Preserves message ordering, roles, and all assistant/user messages.
7
7
  *
8
- * Operates on the pi-ai Message[] format (post-convertToLlm, pre-provider):
8
+ * Operates on provider payloads after convertToLlm:
9
+ *
10
+ * pi-ai Message[] payloads:
9
11
  * - toolResult messages: { role: "toolResult", content: TextContent[] }
10
12
  * - bash results are already converted to: { role: "user", content: [{type:"text",text:"..."}] }
11
13
  * and start with "Ran `" from bashExecutionToText.
14
+ *
15
+ * OpenAI/Codex Responses payloads:
16
+ * - conversation items live in `input`, not `messages`
17
+ * - tool results are { type: "function_call_output", output: string | content[] }
18
+ * - bash results are user items with input_text content starting with "Ran `"
12
19
  */
13
20
 
14
21
  interface MaskableMessage {
@@ -20,6 +27,37 @@ interface MaskableMessage {
20
27
 
21
28
  const MASK_PLACEHOLDER = "[result masked — within summarized history]";
22
29
  const MASK_CONTENT_BLOCK = [{ type: "text" as const, text: MASK_PLACEHOLDER }];
30
+ const RESPONSES_MASK_CONTENT_BLOCK = [{ type: "input_text" as const, text: MASK_PLACEHOLDER }];
31
+ const TRUNCATION_MARKER = "\n…[truncated]";
32
+
33
+ type TextLikeBlock = {
34
+ type?: string;
35
+ text?: unknown;
36
+ [key: string]: unknown;
37
+ };
38
+
39
+ interface ResponsesInputItem {
40
+ role?: string;
41
+ type?: string;
42
+ content?: unknown;
43
+ output?: unknown;
44
+ [key: string]: unknown;
45
+ }
46
+
47
+ function isTextLikeBlock(block: unknown): block is TextLikeBlock {
48
+ return Boolean(block && typeof block === "object" && "text" in block);
49
+ }
50
+
51
+ function firstTextFromContent(content: unknown): string | undefined {
52
+ if (typeof content === "string") return content;
53
+ if (!Array.isArray(content)) return undefined;
54
+ const first = content.find(isTextLikeBlock);
55
+ return typeof first?.text === "string" ? first.text : undefined;
56
+ }
57
+
58
+ function isBashResultText(text: string | undefined): boolean {
59
+ return typeof text === "string" && text.startsWith("Ran `");
60
+ }
23
61
 
24
62
  function findTurnBoundary(messages: MaskableMessage[], keepRecentTurns: number): number {
25
63
  let turnsSeen = 0;
@@ -43,10 +81,8 @@ function findTurnBoundary(messages: MaskableMessage[], keepRecentTurns: number):
43
81
  * The bashExecutionToText format always starts with "Ran `".
44
82
  */
45
83
  function isBashResultUserMessage(m: MaskableMessage): boolean {
46
- if (m.role !== "user" || !Array.isArray(m.content)) return false;
47
- const first = m.content[0];
48
- return first && typeof first === "object" && "text" in first &&
49
- typeof first.text === "string" && first.text.startsWith("Ran `");
84
+ if (m.role !== "user") return false;
85
+ return isBashResultText(firstTextFromContent(m.content));
50
86
  }
51
87
 
52
88
  function isMaskableMessage(m: MaskableMessage): boolean {
@@ -72,3 +108,114 @@ export function createObservationMask(keepRecentTurns: number = 8) {
72
108
  });
73
109
  };
74
110
  }
111
+
112
+ function isResponsesBashResultUserItem(item: ResponsesInputItem): boolean {
113
+ if (item.role !== "user") return false;
114
+ return isBashResultText(firstTextFromContent(item.content));
115
+ }
116
+
117
+ function findResponsesTurnBoundary(items: ResponsesInputItem[], keepRecentTurns: number): number {
118
+ let turnsSeen = 0;
119
+ for (let i = items.length - 1; i >= 0; i--) {
120
+ const item = items[i];
121
+ if (item.role === "user" && !isResponsesBashResultUserItem(item)) {
122
+ turnsSeen++;
123
+ if (turnsSeen >= keepRecentTurns) return i;
124
+ }
125
+ }
126
+ return 0;
127
+ }
128
+
129
+ /**
130
+ * Observation masking for OpenAI/Codex Responses API payloads.
131
+ *
132
+ * Responses payloads store the conversation under `input` instead of
133
+ * `messages`, with tool results as `function_call_output` items. Keep this
134
+ * separate from createObservationMask so each payload shape stays explicit.
135
+ */
136
+ export function createResponsesInputObservationMask(keepRecentTurns: number = 8) {
137
+ return (items: ResponsesInputItem[]): ResponsesInputItem[] => {
138
+ const boundary = findResponsesTurnBoundary(items, keepRecentTurns);
139
+ if (boundary === 0) return items;
140
+
141
+ return items.map((item, i) => {
142
+ if (i >= boundary) return item;
143
+ if (item.type === "function_call_output") {
144
+ return { ...item, output: MASK_PLACEHOLDER };
145
+ }
146
+ if (isResponsesBashResultUserItem(item)) {
147
+ return { ...item, content: RESPONSES_MASK_CONTENT_BLOCK };
148
+ }
149
+ return item;
150
+ });
151
+ };
152
+ }
153
+
154
+ function truncateText(text: string, maxChars: number): string {
155
+ if (text.length <= maxChars) return text;
156
+ return text.slice(0, maxChars) + TRUNCATION_MARKER;
157
+ }
158
+
159
+ function truncateTextBlocks(content: unknown, maxChars: number): unknown {
160
+ if (typeof content === "string") {
161
+ return truncateText(content, maxChars);
162
+ }
163
+ if (!Array.isArray(content)) return content;
164
+
165
+ let remaining = maxChars;
166
+ let didTruncate = false;
167
+ const nextBlocks: unknown[] = [];
168
+
169
+ for (const block of content) {
170
+ if (!isTextLikeBlock(block) || typeof block.text !== "string") {
171
+ nextBlocks.push(block);
172
+ continue;
173
+ }
174
+
175
+ if (remaining <= 0) {
176
+ didTruncate = true;
177
+ continue;
178
+ }
179
+
180
+ const text = block.text;
181
+ if (text.length <= remaining) {
182
+ nextBlocks.push(block);
183
+ remaining -= text.length;
184
+ continue;
185
+ }
186
+
187
+ nextBlocks.push({ ...block, text: truncateText(text, remaining) });
188
+ remaining = 0;
189
+ didTruncate = true;
190
+ }
191
+
192
+ return didTruncate ? nextBlocks : content;
193
+ }
194
+
195
+ function normalizedMaxChars(maxChars: number): number {
196
+ return Number.isFinite(maxChars) && maxChars > 0 ? Math.floor(maxChars) : 800;
197
+ }
198
+
199
+ export function truncateContextResultMessages(messages: MaskableMessage[], maxChars: number = 800): MaskableMessage[] {
200
+ const limit = normalizedMaxChars(maxChars);
201
+ return messages.map((message) => {
202
+ if (!isMaskableMessage(message)) return message;
203
+ const content = truncateTextBlocks(message.content, limit);
204
+ return content === message.content ? message : { ...message, content };
205
+ });
206
+ }
207
+
208
+ export function truncateResponsesInputResultItems(items: ResponsesInputItem[], maxChars: number = 800): ResponsesInputItem[] {
209
+ const limit = normalizedMaxChars(maxChars);
210
+ return items.map((item) => {
211
+ if (item.type === "function_call_output") {
212
+ const output = truncateTextBlocks(item.output, limit);
213
+ return output === item.output ? item : { ...item, output };
214
+ }
215
+ if (isResponsesBashResultUserItem(item)) {
216
+ const content = truncateTextBlocks(item.content, limit);
217
+ return content === item.content ? item : { ...item, content };
218
+ }
219
+ return item;
220
+ });
221
+ }