@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.
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/browser-tools/engine/managed-gsd-browser.js +18 -2
- package/dist/resources/extensions/browser-tools/engine/selection.js +1 -1
- package/dist/resources/extensions/browser-tools/extension-manifest.json +1 -1
- package/dist/resources/extensions/browser-tools/index.js +29 -2
- package/dist/resources/extensions/browser-tools/web-app-detect.js +52 -0
- package/dist/resources/extensions/gsd/auto/phases.js +45 -3
- package/dist/resources/extensions/gsd/auto/session.js +2 -0
- package/dist/resources/extensions/gsd/auto-dispatch.js +21 -2
- package/dist/resources/extensions/gsd/auto-model-selection.js +26 -0
- package/dist/resources/extensions/gsd/auto-prompts.js +4 -0
- package/dist/resources/extensions/gsd/auto-recovery.js +3 -4
- package/dist/resources/extensions/gsd/auto-timers.js +24 -10
- package/dist/resources/extensions/gsd/auto-unit-tool-scope.js +18 -66
- package/dist/resources/extensions/gsd/auto-worktree.js +18 -5
- package/dist/resources/extensions/gsd/auto.js +26 -4
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +16 -10
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +48 -29
- package/dist/resources/extensions/gsd/bootstrap/system-context.js +1 -1
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +18 -29
- package/dist/resources/extensions/gsd/closeout-consistency-gate.js +61 -0
- package/dist/resources/extensions/gsd/commands/handlers/auto.js +10 -0
- package/dist/resources/extensions/gsd/commands-mcp-status.js +1 -1
- package/dist/resources/extensions/gsd/config-overlay.js +1 -0
- package/dist/resources/extensions/gsd/context-masker.js +129 -5
- package/dist/resources/extensions/gsd/guided-flow.js +93 -108
- package/dist/resources/extensions/gsd/milestone-closeout.js +3 -1
- package/dist/resources/extensions/gsd/pending-auto-start.js +0 -1
- package/dist/resources/extensions/gsd/planner-handoff.js +98 -0
- package/dist/resources/extensions/gsd/preferences-models.js +1 -0
- package/dist/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
- package/dist/resources/extensions/gsd/prompts/run-uat.md +5 -19
- package/dist/resources/extensions/gsd/prompts/system.md +1 -1
- package/dist/resources/extensions/gsd/recovery-classification.js +20 -0
- package/dist/resources/extensions/gsd/skill-manifest.js +12 -0
- package/dist/resources/extensions/gsd/tool-contract.js +6 -1
- package/dist/resources/extensions/gsd/tool-presentation-plan.js +47 -7
- package/dist/resources/extensions/gsd/tools/complete-slice.js +28 -1
- package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +113 -8
- package/dist/resources/extensions/gsd/unit-tool-contracts.js +193 -0
- package/dist/resources/extensions/gsd/workflow-mcp.js +5 -78
- package/dist/resources/extensions/gsd/worktree-manager.js +26 -0
- package/dist/resources/extensions/gsd/worktree-reentry.js +96 -0
- package/dist/resources/extensions/shared/gsd-browser-cli.js +6 -0
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +5 -5
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +5 -5
- package/dist/web/standalone/.next/server/chunks/8357.js +1 -1
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/package.json +1 -1
- package/packages/cloud-mcp-gateway/package.json +2 -2
- package/packages/contracts/package.json +1 -1
- package/packages/daemon/package.json +4 -4
- package/packages/gsd-agent-core/package.json +5 -5
- package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js +5 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/packages/gsd-agent-modes/package.json +7 -7
- package/packages/mcp-server/package.json +3 -3
- package/packages/native/package.json +1 -1
- package/packages/pi-agent-core/dist/agent-loop.js +4 -3
- package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
- package/packages/pi-agent-core/dist/harness/agent-harness.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/harness/agent-harness.js +3 -1
- package/packages/pi-agent-core/dist/harness/agent-harness.js.map +1 -1
- package/packages/pi-agent-core/dist/harness/types.d.ts +1 -0
- package/packages/pi-agent-core/dist/harness/types.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/harness/types.js.map +1 -1
- package/packages/pi-agent-core/dist/types.d.ts +3 -1
- package/packages/pi-agent-core/dist/types.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/types.js.map +1 -1
- package/packages/pi-agent-core/package.json +1 -1
- package/packages/pi-ai/dist/models.generated.d.ts +157 -18
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +159 -36
- package/packages/pi-ai/dist/models.generated.js.map +1 -1
- package/packages/pi-ai/dist/providers/transform-messages.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/transform-messages.js +8 -1
- package/packages/pi-ai/dist/providers/transform-messages.js.map +1 -1
- package/packages/pi-ai/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/extension-upstream-types.d.ts +3 -0
- package/packages/pi-coding-agent/dist/core/extensions/extension-upstream-types.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/extension-upstream-types.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/bash.js +2 -2
- package/packages/pi-coding-agent/dist/core/tools/bash.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/edit.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/edit.js +3 -2
- package/packages/pi-coding-agent/dist/core/tools/edit.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/render-utils.d.ts +1 -0
- package/packages/pi-coding-agent/dist/core/tools/render-utils.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/render-utils.js +6 -0
- package/packages/pi-coding-agent/dist/core/tools/render-utils.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/write.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/write.js +3 -2
- package/packages/pi-coding-agent/dist/core/tools/write.js.map +1 -1
- package/packages/pi-coding-agent/package.json +7 -7
- package/packages/pi-tui/package.json +1 -1
- package/packages/rpc-client/package.json +2 -2
- package/pkg/package.json +1 -1
- package/scripts/install/handoff.js +16 -3
- package/src/resources/extensions/browser-tools/engine/managed-gsd-browser.ts +21 -2
- package/src/resources/extensions/browser-tools/engine/selection.ts +1 -1
- package/src/resources/extensions/browser-tools/extension-manifest.json +1 -1
- package/src/resources/extensions/browser-tools/index.ts +36 -5
- package/src/resources/extensions/browser-tools/tests/browser-engine-selection.test.mjs +2 -2
- package/src/resources/extensions/browser-tools/tests/gsd-browser-launch-config.test.mjs +37 -0
- package/src/resources/extensions/browser-tools/tests/web-app-detect.test.mjs +68 -0
- package/src/resources/extensions/browser-tools/web-app-detect.ts +63 -0
- package/src/resources/extensions/gsd/auto/phases.ts +48 -6
- package/src/resources/extensions/gsd/auto/session.ts +2 -0
- package/src/resources/extensions/gsd/auto-dispatch.ts +48 -2
- package/src/resources/extensions/gsd/auto-model-selection.ts +26 -0
- package/src/resources/extensions/gsd/auto-prompts.ts +4 -0
- package/src/resources/extensions/gsd/auto-recovery.ts +3 -3
- package/src/resources/extensions/gsd/auto-timers.ts +25 -9
- package/src/resources/extensions/gsd/auto-unit-tool-scope.ts +43 -74
- package/src/resources/extensions/gsd/auto-worktree.ts +23 -5
- package/src/resources/extensions/gsd/auto.ts +28 -4
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +16 -10
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +63 -29
- package/src/resources/extensions/gsd/bootstrap/system-context.ts +1 -1
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +50 -54
- package/src/resources/extensions/gsd/closeout-consistency-gate.ts +137 -0
- package/src/resources/extensions/gsd/commands/handlers/auto.ts +9 -0
- package/src/resources/extensions/gsd/commands-mcp-status.ts +1 -1
- package/src/resources/extensions/gsd/config-overlay.ts +1 -0
- package/src/resources/extensions/gsd/context-masker.ts +152 -5
- package/src/resources/extensions/gsd/guided-flow.ts +128 -135
- package/src/resources/extensions/gsd/milestone-closeout.ts +3 -1
- package/src/resources/extensions/gsd/pending-auto-start.ts +0 -2
- package/src/resources/extensions/gsd/planner-handoff.ts +149 -0
- package/src/resources/extensions/gsd/preferences-models.ts +1 -0
- package/src/resources/extensions/gsd/preferences-types.ts +8 -0
- package/src/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/run-uat.md +5 -19
- package/src/resources/extensions/gsd/prompts/system.md +1 -1
- package/src/resources/extensions/gsd/recovery-classification.ts +20 -0
- package/src/resources/extensions/gsd/skill-manifest.ts +12 -0
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +99 -0
- package/src/resources/extensions/gsd/tests/auto-model-selection-tool-poisoning.test.ts +66 -4
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +10 -2
- package/src/resources/extensions/gsd/tests/auto-start-bootstrap-await-3420.test.ts +4 -1
- package/src/resources/extensions/gsd/tests/auto-supervisor.test.mjs +4 -0
- package/src/resources/extensions/gsd/tests/auto-warning-noise-regression.test.ts +12 -2
- package/src/resources/extensions/gsd/tests/bundled-skill-triggers.test.ts +9 -0
- package/src/resources/extensions/gsd/tests/check-auto-start-pending-gate.test.ts +9 -15
- package/src/resources/extensions/gsd/tests/check-auto-start-ready-guard.test.ts +26 -16
- package/src/resources/extensions/gsd/tests/commands-dispatcher-unmerged-milestone.test.ts +21 -0
- package/src/resources/extensions/gsd/tests/complete-slice-verification-gate.test.ts +118 -0
- package/src/resources/extensions/gsd/tests/context-masker.test.ts +56 -1
- package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/dispatch-complete-milestone-guard.test.ts +40 -1
- package/src/resources/extensions/gsd/tests/dispatch-rule-coverage.test.ts +24 -0
- package/src/resources/extensions/gsd/tests/gate-1b-orphan-discrimination.test.ts +31 -79
- package/src/resources/extensions/gsd/tests/guided-flow-session-isolation.test.ts +5 -3
- package/src/resources/extensions/gsd/tests/guided-flow-state-rebuild.test.ts +40 -4
- package/src/resources/extensions/gsd/tests/integration/auto-worktree-milestone-merge.test.ts +8 -0
- package/src/resources/extensions/gsd/tests/integration/parallel-merge.test.ts +16 -0
- package/src/resources/extensions/gsd/tests/integration/run-uat.test.ts +7 -1
- package/src/resources/extensions/gsd/tests/interrupted-session-auto.test.ts +27 -0
- package/src/resources/extensions/gsd/tests/journal-integration.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/mcp-project-config.test.ts +7 -1
- package/src/resources/extensions/gsd/tests/mcp-status.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/merge-closeout-consistency-gate.test.ts +63 -0
- package/src/resources/extensions/gsd/tests/merge-db-cycle.test.ts +10 -1
- package/src/resources/extensions/gsd/tests/milestone-closeout.test.ts +9 -1
- package/src/resources/extensions/gsd/tests/planner-handoff.test.ts +100 -0
- package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +147 -5
- package/src/resources/extensions/gsd/tests/provider-switch-observer.test.ts +55 -0
- package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +44 -0
- package/src/resources/extensions/gsd/tests/run-uat-composer.test.ts +4 -0
- package/src/resources/extensions/gsd/tests/runtime-invariant-modules.test.ts +56 -0
- package/src/resources/extensions/gsd/tests/skill-manifest.test.ts +4 -3
- package/src/resources/extensions/gsd/tests/token-tool-gating.test.ts +4 -4
- package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +77 -10
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +409 -0
- package/src/resources/extensions/gsd/tests/worktree-reentry.test.ts +102 -0
- package/src/resources/extensions/gsd/tests/write-gate-planning-unit.test.ts +15 -0
- package/src/resources/extensions/gsd/tool-contract.ts +7 -1
- package/src/resources/extensions/gsd/tool-presentation-plan.ts +82 -7
- package/src/resources/extensions/gsd/tools/complete-slice.ts +29 -1
- package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +146 -9
- package/src/resources/extensions/gsd/unit-tool-contracts.ts +210 -0
- package/src/resources/extensions/gsd/workflow-mcp.ts +5 -78
- package/src/resources/extensions/gsd/worktree-manager.ts +32 -0
- package/src/resources/extensions/gsd/worktree-reentry.ts +103 -0
- package/src/resources/extensions/shared/gsd-browser-cli.ts +6 -0
- package/src/resources/extensions/gsd/tests/gate-1b-recovery-bound-corrections.test.ts +0 -246
- package/src/resources/extensions/gsd/tests/gate-1b-recovery-bound.test.ts +0 -218
- /package/dist/web/standalone/.next/static/{jBtwT9v1u2lUA3UEOy_ZH → zzYMrKpPGfRQRxSFO32Jr}/_buildManifest.js +0 -0
- /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
|
-
):
|
|
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
|
|
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
|
|
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
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
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
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
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
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
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
|
|
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
|
|
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"
|
|
47
|
-
|
|
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
|
+
}
|