@opengsd/gsd-pi 1.2.0-dev.d6c5343c → 1.2.0-dev.ddc97c10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/mcp-server.js +2 -1
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/gsd/auto/orchestrator.js +28 -10
- package/dist/resources/extensions/gsd/auto/phases.js +47 -4
- package/dist/resources/extensions/gsd/auto/session.js +3 -0
- package/dist/resources/extensions/gsd/auto-direct-dispatch.js +3 -2
- package/dist/resources/extensions/gsd/auto-dispatch.js +11 -2
- package/dist/resources/extensions/gsd/auto-model-selection.js +11 -7
- package/dist/resources/extensions/gsd/auto-post-unit.js +18 -6
- package/dist/resources/extensions/gsd/auto-unit-closeout.js +45 -21
- package/dist/resources/extensions/gsd/auto-verification.js +14 -2
- package/dist/resources/extensions/gsd/auto.js +37 -1
- package/dist/resources/extensions/gsd/blocked-models.js +28 -0
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +26 -6
- package/dist/resources/extensions/gsd/bootstrap/exec-tools.js +2 -2
- package/dist/resources/extensions/gsd/closeout-wizard.js +92 -0
- package/dist/resources/extensions/gsd/commands/context.js +16 -2
- package/dist/resources/extensions/gsd/commands-handlers.js +46 -3
- package/dist/resources/extensions/gsd/consent-question.js +16 -0
- package/dist/resources/extensions/gsd/crash-recovery.js +8 -3
- package/dist/resources/extensions/gsd/doctor-engine-checks.js +3 -3
- package/dist/resources/extensions/gsd/doctor-git-checks.js +2 -18
- package/dist/resources/extensions/gsd/gsd-command-home.js +22 -12
- package/dist/resources/extensions/gsd/gsd-db.js +2 -1
- package/dist/resources/extensions/gsd/guided-flow.js +6 -3
- package/dist/resources/extensions/gsd/milestone-closeout.js +73 -2
- package/dist/resources/extensions/gsd/milestone-planning-persistence.js +2 -2
- package/dist/resources/extensions/gsd/projection-flush.js +7 -0
- package/dist/resources/extensions/gsd/prompts/complete-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/execute-task.md +1 -1
- package/dist/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/quick-task.md +1 -1
- package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
- package/dist/resources/extensions/gsd/prompts/refine-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/replan-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/research-milestone.md +1 -1
- package/dist/resources/extensions/gsd/prompts/research-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/rewrite-docs.md +1 -1
- package/dist/resources/extensions/gsd/prompts/run-uat.md +1 -1
- package/dist/resources/extensions/gsd/prompts/triage-captures.md +1 -1
- package/dist/resources/extensions/gsd/prompts/validate-milestone.md +1 -1
- package/dist/resources/extensions/gsd/roadmap-slices.js +25 -3
- package/dist/resources/extensions/gsd/session-lock.js +1 -1
- package/dist/resources/extensions/gsd/tool-contract.js +14 -3
- package/dist/resources/extensions/gsd/tools/complete-milestone.js +3 -2
- package/dist/resources/extensions/gsd/tools/complete-slice.js +2 -2
- package/dist/resources/extensions/gsd/tools/complete-task.js +3 -2
- package/dist/resources/extensions/gsd/tools/plan-slice.js +2 -2
- package/dist/resources/extensions/gsd/tools/plan-task.js +2 -2
- package/dist/resources/extensions/gsd/tools/reassess-roadmap.js +2 -2
- package/dist/resources/extensions/gsd/tools/reopen-milestone.js +2 -2
- package/dist/resources/extensions/gsd/tools/reopen-slice.js +2 -2
- package/dist/resources/extensions/gsd/tools/reopen-task.js +2 -2
- package/dist/resources/extensions/gsd/tools/replan-slice.js +2 -2
- package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +67 -2
- package/dist/resources/extensions/gsd/verification-verdict.js +2 -1
- package/dist/resources/extensions/shared/gsd-browser-cli.js +21 -2
- package/dist/resources/shared/gsd-browser-path-sync.js +214 -0
- package/dist/resources/shared/package-manager-detection.js +1 -1
- package/dist/tsconfig.extensions.tsbuildinfo +1 -1
- package/dist/update-check.d.ts +2 -0
- package/dist/update-check.js +24 -1
- package/dist/update-cmd.js +20 -3
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +12 -12
- 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 +12 -12
- package/dist/web/standalone/.next/server/chunks/8357.js +2 -2
- 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/dist/web/standalone/node_modules/node-pty/build/Makefile +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/package.json +7 -7
- package/packages/mcp-server/dist/cli.js +10 -5
- package/packages/mcp-server/dist/cli.js.map +1 -1
- package/packages/mcp-server/dist/moonshot-tool-schema.d.ts +29 -0
- package/packages/mcp-server/dist/moonshot-tool-schema.d.ts.map +1 -0
- package/packages/mcp-server/dist/moonshot-tool-schema.js +50 -0
- package/packages/mcp-server/dist/moonshot-tool-schema.js.map +1 -0
- package/packages/mcp-server/dist/server.d.ts.map +1 -1
- package/packages/mcp-server/dist/server.js +4 -0
- package/packages/mcp-server/dist/server.js.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.d.ts +18 -18
- package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js +99 -38
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/package.json +5 -4
- package/packages/native/package.json +1 -1
- package/packages/pi-agent-core/package.json +1 -1
- package/packages/pi-ai/dist/index.d.ts +2 -0
- package/packages/pi-ai/dist/index.d.ts.map +1 -1
- package/packages/pi-ai/dist/index.js +2 -0
- package/packages/pi-ai/dist/index.js.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic.js +12 -7
- package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
- package/packages/pi-ai/dist/providers/google-shared.d.ts +5 -0
- package/packages/pi-ai/dist/providers/google-shared.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/google-shared.js +12 -3
- package/packages/pi-ai/dist/providers/google-shared.js.map +1 -1
- package/packages/pi-ai/dist/providers/openai-completions.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/openai-completions.js +7 -3
- package/packages/pi-ai/dist/providers/openai-completions.js.map +1 -1
- package/packages/pi-ai/dist/utils/moonshot-tool-schema.d.ts +9 -0
- package/packages/pi-ai/dist/utils/moonshot-tool-schema.d.ts.map +1 -0
- package/packages/pi-ai/dist/utils/moonshot-tool-schema.js +34 -0
- package/packages/pi-ai/dist/utils/moonshot-tool-schema.js.map +1 -0
- package/packages/pi-ai/dist/utils/oauth/github-copilot.d.ts.map +1 -1
- package/packages/pi-ai/dist/utils/oauth/github-copilot.js +6 -2
- package/packages/pi-ai/dist/utils/oauth/github-copilot.js.map +1 -1
- package/packages/pi-ai/package.json +1 -1
- package/packages/pi-coding-agent/package.json +7 -7
- package/packages/pi-tui/package.json +2 -2
- package/packages/rpc-client/package.json +2 -2
- package/pkg/package.json +1 -1
- package/src/resources/extensions/browser-tools/tests/gsd-browser-launch-config.test.mjs +11 -0
- package/src/resources/extensions/gsd/auto/orchestrator.ts +28 -10
- package/src/resources/extensions/gsd/auto/phases.ts +63 -24
- package/src/resources/extensions/gsd/auto/session.ts +3 -0
- package/src/resources/extensions/gsd/auto-direct-dispatch.ts +10 -16
- package/src/resources/extensions/gsd/auto-dispatch.ts +11 -10
- package/src/resources/extensions/gsd/auto-model-selection.ts +16 -7
- package/src/resources/extensions/gsd/auto-post-unit.ts +21 -6
- package/src/resources/extensions/gsd/auto-unit-closeout.ts +83 -28
- package/src/resources/extensions/gsd/auto-verification.ts +18 -2
- package/src/resources/extensions/gsd/auto.ts +44 -1
- package/src/resources/extensions/gsd/blocked-models.ts +49 -0
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +34 -5
- package/src/resources/extensions/gsd/bootstrap/exec-tools.ts +2 -2
- package/src/resources/extensions/gsd/closeout-wizard.ts +102 -0
- package/src/resources/extensions/gsd/commands/context.ts +16 -2
- package/src/resources/extensions/gsd/commands-handlers.ts +46 -3
- package/src/resources/extensions/gsd/consent-question.ts +15 -0
- package/src/resources/extensions/gsd/crash-recovery.ts +10 -2
- package/src/resources/extensions/gsd/doctor-engine-checks.ts +3 -3
- package/src/resources/extensions/gsd/doctor-git-checks.ts +2 -19
- package/src/resources/extensions/gsd/gsd-command-home.ts +13 -3
- package/src/resources/extensions/gsd/gsd-db.ts +4 -3
- package/src/resources/extensions/gsd/guided-flow.ts +21 -26
- package/src/resources/extensions/gsd/milestone-closeout.ts +97 -2
- package/src/resources/extensions/gsd/milestone-planning-persistence.ts +2 -2
- package/src/resources/extensions/gsd/projection-flush.ts +20 -0
- package/src/resources/extensions/gsd/prompts/complete-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/execute-task.md +1 -1
- package/src/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/quick-task.md +1 -1
- package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
- package/src/resources/extensions/gsd/prompts/refine-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/replan-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/research-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/research-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/rewrite-docs.md +1 -1
- package/src/resources/extensions/gsd/prompts/run-uat.md +1 -1
- package/src/resources/extensions/gsd/prompts/triage-captures.md +1 -1
- package/src/resources/extensions/gsd/prompts/validate-milestone.md +1 -1
- package/src/resources/extensions/gsd/roadmap-slices.ts +28 -3
- package/src/resources/extensions/gsd/session-lock.ts +1 -1
- package/src/resources/extensions/gsd/tests/auto-model-selection.test.ts +69 -0
- package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +97 -0
- package/src/resources/extensions/gsd/tests/auto-remote-session-lock-cleanup.test.ts +65 -3
- package/src/resources/extensions/gsd/tests/blocked-models.test.ts +19 -0
- package/src/resources/extensions/gsd/tests/consent-question.test.ts +15 -0
- package/src/resources/extensions/gsd/tests/doctor-git-checks-terminal.test.ts +73 -0
- package/src/resources/extensions/gsd/tests/gsd-command-home.test.ts +120 -0
- package/src/resources/extensions/gsd/tests/guided-dispatch-root.test.ts +2 -6
- package/src/resources/extensions/gsd/tests/milestone-closeout.test.ts +95 -4
- package/src/resources/extensions/gsd/tests/parsers-legacy-importers.test.ts +0 -1
- package/src/resources/extensions/gsd/tests/phases-terminal-complete-idempotent.test.ts +242 -0
- package/src/resources/extensions/gsd/tests/post-exec-retry-bypass.test.ts +63 -2
- package/src/resources/extensions/gsd/tests/roadmap-slices.test.ts +68 -0
- package/src/resources/extensions/gsd/tests/runtime-invariant-modules.test.ts +19 -1
- package/src/resources/extensions/gsd/tests/tool-unavailable-retry.test.ts +33 -0
- package/src/resources/extensions/gsd/tests/transport-gate-double-complete.test.ts +139 -0
- package/src/resources/extensions/gsd/tests/verification-verdict.test.ts +2 -0
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +273 -38
- package/src/resources/extensions/gsd/tool-contract.ts +38 -3
- package/src/resources/extensions/gsd/tools/complete-milestone.ts +3 -2
- package/src/resources/extensions/gsd/tools/complete-slice.ts +2 -2
- package/src/resources/extensions/gsd/tools/complete-task.ts +3 -2
- package/src/resources/extensions/gsd/tools/plan-slice.ts +2 -2
- package/src/resources/extensions/gsd/tools/plan-task.ts +2 -2
- package/src/resources/extensions/gsd/tools/reassess-roadmap.ts +2 -2
- package/src/resources/extensions/gsd/tools/reopen-milestone.ts +2 -2
- package/src/resources/extensions/gsd/tools/reopen-slice.ts +2 -2
- package/src/resources/extensions/gsd/tools/reopen-task.ts +2 -2
- package/src/resources/extensions/gsd/tools/replan-slice.ts +2 -2
- package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +81 -2
- package/src/resources/extensions/gsd/verification-verdict.ts +4 -2
- package/src/resources/extensions/shared/gsd-browser-cli.ts +23 -2
- package/src/resources/shared/gsd-browser-path-sync.ts +273 -0
- package/src/resources/shared/package-manager-detection.ts +1 -1
- /package/dist/web/standalone/.next/static/{jmTLg6xZmAuq_LIqKOxrH → McokybTayhff1xEVc-d3T}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{jmTLg6xZmAuq_LIqKOxrH → McokybTayhff1xEVc-d3T}/_ssgManifest.js +0 -0
package/dist/mcp-server.js
CHANGED
|
@@ -29,6 +29,7 @@ function isPlainObject(value) {
|
|
|
29
29
|
// built via a template string so TypeScript's NodeNext resolver treats them as
|
|
30
30
|
// `any` and skips static checking.
|
|
31
31
|
const MCP_PKG = '@modelcontextprotocol/sdk';
|
|
32
|
+
import { sanitizeSchemaForMoonshot } from '@gsd/pi-ai';
|
|
32
33
|
export function mcpSdkSpecifier(subpath) {
|
|
33
34
|
return `${MCP_PKG}/${subpath}.js`;
|
|
34
35
|
}
|
|
@@ -65,7 +66,7 @@ export async function startMcpServer(options) {
|
|
|
65
66
|
tools: tools.map((t) => ({
|
|
66
67
|
name: t.name,
|
|
67
68
|
description: t.description,
|
|
68
|
-
inputSchema: t.parameters,
|
|
69
|
+
inputSchema: sanitizeSchemaForMoonshot(t.parameters),
|
|
69
70
|
})),
|
|
70
71
|
}));
|
|
71
72
|
// tools/call — execute the requested tool and return content blocks.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
298c30be22e6452d
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
import { debugCount, debugLog, debugTime } from "../debug-logger.js";
|
|
13
13
|
import { reconcileBeforeDispatch } from "../state-reconciliation.js";
|
|
14
14
|
import { isLegalEdge, IllegalPhaseTransitionError } from "../state-transition-matrix.js";
|
|
15
|
-
import { resolveDispatch } from "../auto-dispatch.js";
|
|
15
|
+
import { hasPendingDeepStage, resolveDispatch } from "../auto-dispatch.js";
|
|
16
16
|
import { classifyFailure } from "../recovery-classification.js";
|
|
17
17
|
import { verifyExpectedArtifact, refreshRecoveryDbForArtifact } from "../auto-recovery.js";
|
|
18
18
|
import { invalidateAllCaches } from "../cache.js";
|
|
@@ -111,14 +111,25 @@ function shouldAdoptActiveMilestone(state, activeSession, activeDispatchBasePath
|
|
|
111
111
|
export async function decideOrchestratorDispatch(ctx, pi, dispatchBasePath, session, input) {
|
|
112
112
|
const state = input.stateSnapshot;
|
|
113
113
|
const active = state.activeMilestone;
|
|
114
|
-
if (!active)
|
|
115
|
-
return null;
|
|
116
114
|
const activeSession = input.session ?? session;
|
|
117
115
|
const activeDispatchBasePath = activeSession?.basePath || dispatchBasePath;
|
|
118
|
-
|
|
116
|
+
const prefs = loadEffectiveGSDPreferences(activeDispatchBasePath)?.preferences;
|
|
117
|
+
if (!active) {
|
|
118
|
+
if (state.phase !== "pre-planning")
|
|
119
|
+
return null;
|
|
120
|
+
if (!hasPendingDeepStage(prefs, activeDispatchBasePath)) {
|
|
121
|
+
return {
|
|
122
|
+
kind: "blocked",
|
|
123
|
+
reason: state.nextAction || "No active milestone. Run /gsd unpark <id> or create a new milestone.",
|
|
124
|
+
action: "stop",
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (active && activeSession && shouldAdoptActiveMilestone(state, activeSession, activeDispatchBasePath)) {
|
|
119
129
|
activeSession.currentMilestoneId = active.id;
|
|
120
130
|
}
|
|
121
|
-
const
|
|
131
|
+
const dispatchMid = active?.id ?? activeSession?.currentMilestoneId ?? "";
|
|
132
|
+
const dispatchMidTitle = active?.title ?? "";
|
|
122
133
|
// Derive session-derived dispatch inputs the same way phases.ts:runDispatch does
|
|
123
134
|
// (#5789). Prefer caller-supplied values when present so test harnesses and
|
|
124
135
|
// alternative wirings can inject deterministic snapshots; otherwise pull from
|
|
@@ -146,8 +157,15 @@ export async function decideOrchestratorDispatch(ctx, pi, dispatchBasePath, sess
|
|
|
146
157
|
})
|
|
147
158
|
? "true"
|
|
148
159
|
: "false");
|
|
160
|
+
// Only replay a milestone-scoped verification retry when a milestone is
|
|
161
|
+
// active. Pre-PR (#712 fix), `!active` returned null before reaching this
|
|
162
|
+
// block, so the retry was preserved for a future tick. The new
|
|
163
|
+
// pre-planning + deep-pending fall-through must keep that contract:
|
|
164
|
+
// otherwise a stale execute-task / complete-slice / complete-milestone
|
|
165
|
+
// retry whose target milestone has since been parked would preempt
|
|
166
|
+
// project-level deep rules like `discuss-project`.
|
|
149
167
|
const pendingRetry = session?.pendingVerificationRetryDispatch;
|
|
150
|
-
if (session && pendingRetry) {
|
|
168
|
+
if (session && pendingRetry && active) {
|
|
151
169
|
session.pendingVerificationRetryDispatch = null;
|
|
152
170
|
const alreadyClosedReason = getAlreadyClosedDispatchReason(pendingRetry.unitType, pendingRetry.unitId);
|
|
153
171
|
if (alreadyClosedReason) {
|
|
@@ -165,8 +183,8 @@ export async function decideOrchestratorDispatch(ctx, pi, dispatchBasePath, sess
|
|
|
165
183
|
}
|
|
166
184
|
const action = await resolveDispatch({
|
|
167
185
|
basePath: activeDispatchBasePath,
|
|
168
|
-
mid:
|
|
169
|
-
midTitle:
|
|
186
|
+
mid: dispatchMid,
|
|
187
|
+
midTitle: dispatchMidTitle,
|
|
170
188
|
state,
|
|
171
189
|
prefs,
|
|
172
190
|
session: activeSession,
|
|
@@ -211,8 +229,8 @@ export async function decideOrchestratorDispatch(ctx, pi, dispatchBasePath, sess
|
|
|
211
229
|
prompt: action.prompt,
|
|
212
230
|
pauseAfterUatDispatch: action.pauseAfterDispatch ?? false,
|
|
213
231
|
state,
|
|
214
|
-
mid:
|
|
215
|
-
midTitle:
|
|
232
|
+
mid: dispatchMid,
|
|
233
|
+
midTitle: dispatchMidTitle,
|
|
216
234
|
};
|
|
217
235
|
session.pendingOrchestrationDispatch = pending;
|
|
218
236
|
}
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
import { importExtensionModule } from "@gsd/pi-coding-agent";
|
|
12
12
|
import { USER_DRIVEN_DEEP_UNITS, isAwaitingUserInput, } from "../auto-post-unit.js";
|
|
13
13
|
import { lastAssistantText } from "../consent-question.js";
|
|
14
|
-
import { resolveEffectiveUnitIsolationMode } from "../preferences.js";
|
|
14
|
+
import { resolveEffectiveUnitIsolationMode, getIsolationMode } from "../preferences.js";
|
|
15
15
|
import { MAX_RECOVERY_CHARS, BUDGET_THRESHOLDS, MAX_FINALIZE_TIMEOUTS, } from "./types.js";
|
|
16
16
|
import { detectStuck } from "./detect-stuck.js";
|
|
17
17
|
import { STUCK_WINDOW_SIZE } from "./dispatch-history.js";
|
|
@@ -35,9 +35,10 @@ import { writeUnitRuntimeRecord } from "../unit-runtime.js";
|
|
|
35
35
|
import { withTimeout, FINALIZE_PRE_TIMEOUT_MS, FINALIZE_POST_TIMEOUT_MS } from "./finalize-timeout.js";
|
|
36
36
|
import { getEligibleSlices } from "../slice-parallel-eligibility.js";
|
|
37
37
|
import { isSliceParallelActive, startSliceParallel } from "../slice-parallel-orchestrator.js";
|
|
38
|
-
import { isDbAvailable, getMilestoneSlices, getSlice, getTask } from "../gsd-db.js";
|
|
38
|
+
import { isDbAvailable, getMilestone, getMilestoneSlices, getSlice, getTask } from "../gsd-db.js";
|
|
39
39
|
import { refreshWorkflowDatabaseFromDisk } from "../db-workspace.js";
|
|
40
40
|
import { isClosedStatus } from "../status-guards.js";
|
|
41
|
+
import { findUnmergedCompletedMilestones } from "../unmerged-milestone-guard.js";
|
|
41
42
|
import { setRuntimeKv } from "../db/runtime-kv.js";
|
|
42
43
|
import { getLatestForUnit } from "../db/unit-dispatches.js";
|
|
43
44
|
import { reconcileBeforeSpawn } from "../state-reconciliation.js";
|
|
@@ -50,7 +51,8 @@ import { parseUnitId } from "../unit-id.js";
|
|
|
50
51
|
import { createCheckpoint, cleanupCheckpoint, rollbackToCheckpoint } from "../safety/git-checkpoint.js";
|
|
51
52
|
import { resolveSafetyHarnessConfig } from "../safety/safety-harness.js";
|
|
52
53
|
import { getContextPauseAction } from "../auto-budget.js";
|
|
53
|
-
import {
|
|
54
|
+
import { supportsStructuredQuestions } from "../workflow-mcp.js";
|
|
55
|
+
import { getUnitWorkflowDispatchReadinessError } from "../tool-contract.js";
|
|
54
56
|
import { prepareWorkflowMcpForProject } from "../workflow-mcp-auto-prep.js";
|
|
55
57
|
import { applyThinkingLevelForModel, floorThinkingLevelForUnit, getRegisteredToolSnapshot, getToolBaselineSnapshot, } from "../auto-model-selection.js";
|
|
56
58
|
import { resolveManifest } from "../unit-context-manifest.js";
|
|
@@ -600,6 +602,36 @@ async function failClosedOnFinalizeTimeout(ic, iterData, loopState, stage, start
|
|
|
600
602
|
drainLogs();
|
|
601
603
|
return { action: "break", reason: progressKind };
|
|
602
604
|
}
|
|
605
|
+
export async function shouldSkipTerminalMilestoneCloseout(s, state, mid) {
|
|
606
|
+
const closeoutMilestoneId = mid ?? s.currentMilestoneId ?? state.lastCompletedMilestone?.id;
|
|
607
|
+
if (s.completionStopInProgress) {
|
|
608
|
+
return { skip: true, milestoneId: closeoutMilestoneId };
|
|
609
|
+
}
|
|
610
|
+
if (!closeoutMilestoneId) {
|
|
611
|
+
return { skip: false };
|
|
612
|
+
}
|
|
613
|
+
if (isDbAvailable())
|
|
614
|
+
refreshWorkflowDatabaseFromDisk();
|
|
615
|
+
const closeoutBasePath = s.originalBasePath || s.canonicalProjectRoot || s.basePath;
|
|
616
|
+
let closeoutMergePending = false;
|
|
617
|
+
if (getIsolationMode(closeoutBasePath) !== "none") {
|
|
618
|
+
try {
|
|
619
|
+
const blockers = await findUnmergedCompletedMilestones(closeoutBasePath);
|
|
620
|
+
closeoutMergePending = blockers.some((blocker) => blocker.milestoneId === closeoutMilestoneId);
|
|
621
|
+
}
|
|
622
|
+
catch {
|
|
623
|
+
// Fail open: without git/DB inspection we cannot safely treat closeout as done.
|
|
624
|
+
closeoutMergePending = true;
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
const milestoneAlreadyClosedOut = isDbAvailable()
|
|
628
|
+
&& isClosedStatus(getMilestone(closeoutMilestoneId)?.status ?? "")
|
|
629
|
+
&& !closeoutMergePending;
|
|
630
|
+
if (milestoneAlreadyClosedOut) {
|
|
631
|
+
return { skip: true, milestoneId: closeoutMilestoneId };
|
|
632
|
+
}
|
|
633
|
+
return { skip: false, milestoneId: closeoutMilestoneId };
|
|
634
|
+
}
|
|
603
635
|
// ─── runPreDispatch ───────────────────────────────────────────────────────────
|
|
604
636
|
/**
|
|
605
637
|
* Phase 1: Pre-dispatch — resource guard, health gate, state derivation,
|
|
@@ -962,6 +994,13 @@ export async function runPreDispatch(ic, loopState) {
|
|
|
962
994
|
deps.setActiveMilestoneId(s.basePath, mid);
|
|
963
995
|
}
|
|
964
996
|
// ── Terminal conditions ──────────────────────────────────────────────
|
|
997
|
+
if (state.phase === "complete") {
|
|
998
|
+
const closeoutSkip = await shouldSkipTerminalMilestoneCloseout(s, state, mid);
|
|
999
|
+
if (closeoutSkip.skip) {
|
|
1000
|
+
debugLog("autoLoop", { phase: "complete", reason: "milestone-already-closed", milestoneId: closeoutSkip.milestoneId });
|
|
1001
|
+
return { action: "break", reason: "milestone-complete" };
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
965
1004
|
if (!mid) {
|
|
966
1005
|
if (s.currentUnit) {
|
|
967
1006
|
await deps.closeoutUnit(ctx, s.basePath, s.currentUnit.type, s.currentUnit.id, s.currentUnit.startedAt, deps.buildSnapshotOpts(s.currentUnit.type, s.currentUnit.id));
|
|
@@ -1769,7 +1808,8 @@ export async function runUnitPhase(ic, iterData, loopState, sidecarItem) {
|
|
|
1769
1808
|
s.currentDispatchedModelId = s.currentUnitModel
|
|
1770
1809
|
? `${s.currentUnitModel.provider ?? ""}/${s.currentUnitModel.id ?? ""}`
|
|
1771
1810
|
: null;
|
|
1772
|
-
const compatibilityError =
|
|
1811
|
+
const compatibilityError = getUnitWorkflowDispatchReadinessError({
|
|
1812
|
+
provider: s.currentUnitModel?.provider ?? ctx.model?.provider,
|
|
1773
1813
|
projectRoot: s.basePath,
|
|
1774
1814
|
surface: "auto-mode",
|
|
1775
1815
|
unitType,
|
|
@@ -1848,6 +1888,9 @@ export async function runUnitPhase(ic, iterData, loopState, sidecarItem) {
|
|
|
1848
1888
|
causedBy: "unit-start",
|
|
1849
1889
|
});
|
|
1850
1890
|
s.lastToolInvocationError = null; // #2883: clear stale error from previous unit
|
|
1891
|
+
if (nextDispatchCount <= 1) {
|
|
1892
|
+
s.toolUnavailableRetries = 0;
|
|
1893
|
+
}
|
|
1851
1894
|
const unitStartSeq = ic.nextSeq();
|
|
1852
1895
|
deps.emitJournalEvent({ ts: new Date().toISOString(), flowId: ic.flowId, seq: unitStartSeq, eventType: "unit-start", data: { unitType, unitId } });
|
|
1853
1896
|
deps.captureAvailableSkills();
|
|
@@ -126,6 +126,8 @@ export class AutoSession {
|
|
|
126
126
|
/** Set when a GSD tool execution ends with isError due to malformed/truncated
|
|
127
127
|
* JSON arguments. Checked by postUnitPreVerification to break retry loops. */
|
|
128
128
|
lastToolInvocationError = null;
|
|
129
|
+
/** Consecutive tool-unavailable retries for the current unit (MCP startup race). */
|
|
130
|
+
toolUnavailableRetries = 0;
|
|
129
131
|
/** Agent-end messages from the just-finished unit, consumed during finalize. */
|
|
130
132
|
lastUnitAgentEndMessages = null;
|
|
131
133
|
/** Set when turn-level git action fails during closeout. */
|
|
@@ -306,6 +308,7 @@ export class AutoSession {
|
|
|
306
308
|
this.lastPreExecFailure = null;
|
|
307
309
|
this.preExecRetryCount.clear();
|
|
308
310
|
this.lastToolInvocationError = null;
|
|
311
|
+
this.toolUnavailableRetries = 0;
|
|
309
312
|
this.lastUnitAgentEndMessages = null;
|
|
310
313
|
this.lastGitActionFailure = null;
|
|
311
314
|
this.lastGitActionStatus = null;
|
|
@@ -10,7 +10,7 @@ import { buildResearchSlicePrompt, buildResearchMilestonePrompt, buildPlanSliceP
|
|
|
10
10
|
import { loadEffectiveGSDPreferences } from "./preferences.js";
|
|
11
11
|
import { pauseAuto } from "./auto.js";
|
|
12
12
|
import { resolveCanonicalMilestoneRoot } from "./worktree-manager.js";
|
|
13
|
-
import {
|
|
13
|
+
import { getUnitWorkflowDispatchReadinessError } from "./tool-contract.js";
|
|
14
14
|
export async function dispatchDirectPhase(ctx, pi, phase, base) {
|
|
15
15
|
const state = await deriveState(base);
|
|
16
16
|
const mid = state.activeMilestone?.id;
|
|
@@ -201,7 +201,8 @@ export async function dispatchDirectPhase(ctx, pi, phase, base) {
|
|
|
201
201
|
ctx.ui.notify(`Unknown phase "${phase}". Valid phases: research, plan, execute, complete, validate, reassess, uat, replan.`, "warning");
|
|
202
202
|
return;
|
|
203
203
|
}
|
|
204
|
-
const compatibilityError =
|
|
204
|
+
const compatibilityError = getUnitWorkflowDispatchReadinessError({
|
|
205
|
+
provider: ctx.model?.provider,
|
|
205
206
|
projectRoot,
|
|
206
207
|
surface: "direct phase dispatch",
|
|
207
208
|
unitType,
|
|
@@ -24,7 +24,8 @@ import { isAutoActive } from "./auto.js";
|
|
|
24
24
|
import { hostWriteGateAdapter } from "./bootstrap/write-gate.js";
|
|
25
25
|
import { ensureWorkflowPreferencesCaptured } from "./planning-depth.js";
|
|
26
26
|
import { MILESTONE_ID_RE } from "./milestone-ids.js";
|
|
27
|
-
import {
|
|
27
|
+
import { resolveWorkflowMcpProjectRoot } from "./workflow-mcp.js";
|
|
28
|
+
import { getUnitWorkflowDispatchReadinessError } from "./tool-contract.js";
|
|
28
29
|
import { prepareBrowserDaemonForUat } from "./browser-daemon-auto-prep.js";
|
|
29
30
|
import { PROJECT_RESEARCH_INFLIGHT_MARKER, } from "./project-research-policy.js";
|
|
30
31
|
import { isWorkflowPrefsCaptured, resolveDeepProjectSetupState, } from "./deep-project-setup-policy.js";
|
|
@@ -512,7 +513,15 @@ export const DISPATCH_RULES = [
|
|
|
512
513
|
// Transport preflight: verify required MCP tools are actually connected
|
|
513
514
|
// before consuming a retry attempt. Fixes tool-starved sessions burning
|
|
514
515
|
// all MAX_UAT_ATTEMPTS before stopping (#477).
|
|
515
|
-
const transportError =
|
|
516
|
+
const transportError = getUnitWorkflowDispatchReadinessError({
|
|
517
|
+
provider: sessionProvider,
|
|
518
|
+
projectRoot: basePath,
|
|
519
|
+
surface: "auto-mode",
|
|
520
|
+
unitType: "run-uat",
|
|
521
|
+
authMode: sessionAuthMode,
|
|
522
|
+
baseUrl: sessionBaseUrl,
|
|
523
|
+
activeTools,
|
|
524
|
+
});
|
|
516
525
|
if (transportError) {
|
|
517
526
|
return { action: "stop", reason: transportError, level: "warning" };
|
|
518
527
|
}
|
|
@@ -13,7 +13,7 @@ import { getSessionModelOverride } from "./session-model-override.js";
|
|
|
13
13
|
import { logWarning } from "./workflow-logger.js";
|
|
14
14
|
import { resolveUokFlags } from "./uok/flags.js";
|
|
15
15
|
import { applyModelPolicyFilter } from "./uok/model-policy.js";
|
|
16
|
-
import { isModelBlocked } from "./blocked-models.js";
|
|
16
|
+
import { isModelBlocked, isModelTemporarilyUnavailable } from "./blocked-models.js";
|
|
17
17
|
import { getRequiredWorkflowToolsForAutoUnit, isWorkflowMcpSurfaceTool } from "./workflow-mcp.js";
|
|
18
18
|
/**
|
|
19
19
|
* Thrown when the model-policy gate rejects every candidate model for a unit
|
|
@@ -218,6 +218,10 @@ function buildModelPolicyBlockReasons(policyDenyReasons, availableModels, routin
|
|
|
218
218
|
reason: `configured model(s) did not resolve against policy-eligible registry [${eligibleSummary}]`,
|
|
219
219
|
}];
|
|
220
220
|
}
|
|
221
|
+
function isModelUnavailable(basePath, provider, id) {
|
|
222
|
+
return isModelBlocked(basePath, provider, id) ||
|
|
223
|
+
isModelTemporarilyUnavailable(basePath, provider, id);
|
|
224
|
+
}
|
|
221
225
|
function restoreToolBaseline(pi) {
|
|
222
226
|
const key = pi;
|
|
223
227
|
const baseline = TOOL_BASELINE.get(key);
|
|
@@ -657,8 +661,8 @@ autoModeStartThinkingLevel) {
|
|
|
657
661
|
// (issue #4513). The block is persisted in .gsd/runtime/blocked-models.json
|
|
658
662
|
// so it survives /gsd auto restarts — without this, the same dead model
|
|
659
663
|
// gets reselected after every restart.
|
|
660
|
-
if (
|
|
661
|
-
ctx.ui.notify(`Skipping
|
|
664
|
+
if (isModelUnavailable(basePath, model.provider, model.id)) {
|
|
665
|
+
ctx.ui.notify(`Skipping unavailable model ${model.provider}/${model.id}.`, "warning");
|
|
662
666
|
continue;
|
|
663
667
|
}
|
|
664
668
|
// Warn if the ID is ambiguous across providers
|
|
@@ -724,7 +728,7 @@ autoModeStartThinkingLevel) {
|
|
|
724
728
|
const key = `${model.provider.toLowerCase()}/${model.id.toLowerCase()}`;
|
|
725
729
|
if (!policyAllowedModelKeys.has(key))
|
|
726
730
|
continue;
|
|
727
|
-
if (
|
|
731
|
+
if (isModelUnavailable(basePath, model.provider, model.id))
|
|
728
732
|
continue;
|
|
729
733
|
const ok = await pi.setModel(model, { persist: false });
|
|
730
734
|
if (!ok)
|
|
@@ -746,16 +750,16 @@ autoModeStartThinkingLevel) {
|
|
|
746
750
|
// No model preference for this unit type — re-apply the model captured
|
|
747
751
|
// at auto-mode start to prevent bleed from shared global settings.json (#650).
|
|
748
752
|
const availableModels = buildModelPolicyCandidates(ctx, autoModeStartModel, effectiveSessionModelOverride);
|
|
749
|
-
const startBlocked =
|
|
753
|
+
const startBlocked = isModelUnavailable(basePath, autoModeStartModel.provider, autoModeStartModel.id);
|
|
750
754
|
if (startBlocked) {
|
|
751
|
-
ctx.ui.notify(`Auto-mode start model ${autoModeStartModel.provider}/${autoModeStartModel.id} is
|
|
755
|
+
ctx.ui.notify(`Auto-mode start model ${autoModeStartModel.provider}/${autoModeStartModel.id} is unavailable. Using current session model instead.`, "warning");
|
|
752
756
|
}
|
|
753
757
|
else {
|
|
754
758
|
const startModel = availableModels.find(m => m.provider === autoModeStartModel.provider && m.id === autoModeStartModel.id);
|
|
755
759
|
if (startModel) {
|
|
756
760
|
const ok = await pi.setModel(startModel, { persist: false });
|
|
757
761
|
if (!ok) {
|
|
758
|
-
const byId = availableModels.find(m => m.id === autoModeStartModel.id && !
|
|
762
|
+
const byId = availableModels.find(m => m.id === autoModeStartModel.id && !isModelUnavailable(basePath, m.provider, m.id));
|
|
759
763
|
if (byId) {
|
|
760
764
|
const fallbackOk = await pi.setModel(byId, { persist: false });
|
|
761
765
|
if (fallbackOk) {
|
|
@@ -1704,13 +1704,24 @@ export async function postUnitPreVerification(pctx, opts) {
|
|
|
1704
1704
|
}
|
|
1705
1705
|
else if (!triggerArtifactVerified) {
|
|
1706
1706
|
if (s.lastToolInvocationError && isToolUnavailableError(s.lastToolInvocationError)) {
|
|
1707
|
-
// Tool-unavailable is the
|
|
1708
|
-
//
|
|
1709
|
-
//
|
|
1710
|
-
//
|
|
1711
|
-
|
|
1712
|
-
|
|
1707
|
+
// Tool-unavailable is transient: the workflow MCP server registers
|
|
1708
|
+
// its surface asynchronously, so a Unit's first call can race the
|
|
1709
|
+
// registration. Retry with escalating delay, bounded at 3 attempts.
|
|
1710
|
+
// ponytail: MAX constant so the guard, log, and display all agree
|
|
1711
|
+
const MAX_TOOL_UNAVAIL_RETRIES = 3;
|
|
1712
|
+
if (s.toolUnavailableRetries >= MAX_TOOL_UNAVAIL_RETRIES) {
|
|
1713
|
+
debugLog("postUnit", { phase: "tool-unavailable-exhausted", unitType: s.currentUnit.type, unitId: s.currentUnit.id, retries: s.toolUnavailableRetries });
|
|
1714
|
+
ctx.ui.notify(`Tool unavailable for ${s.currentUnit.type} after ${MAX_TOOL_UNAVAIL_RETRIES} retries: ${s.lastToolInvocationError}. MCP server may not be starting — pausing auto-mode.`, "error");
|
|
1715
|
+
s.lastToolInvocationError = null;
|
|
1716
|
+
await pauseAuto(ctx, pi);
|
|
1717
|
+
return "dispatched";
|
|
1718
|
+
}
|
|
1719
|
+
s.toolUnavailableRetries++;
|
|
1720
|
+
const delayMs = s.toolUnavailableRetries * 1000;
|
|
1721
|
+
debugLog("postUnit", { phase: "tool-unavailable-retry", unitType: s.currentUnit.type, unitId: s.currentUnit.id, error: s.lastToolInvocationError, attempt: s.toolUnavailableRetries, delayMs });
|
|
1722
|
+
ctx.ui.notify(`Tool unavailable for ${s.currentUnit.type}: ${s.lastToolInvocationError}. Waiting ${delayMs}ms for MCP server — retry ${s.toolUnavailableRetries}/${MAX_TOOL_UNAVAIL_RETRIES}.`, "warning");
|
|
1713
1723
|
s.lastToolInvocationError = null;
|
|
1724
|
+
await new Promise(r => setTimeout(r, delayMs));
|
|
1714
1725
|
}
|
|
1715
1726
|
else if (s.lastToolInvocationError) {
|
|
1716
1727
|
const isUserSkip = /queued user message/i.test(s.lastToolInvocationError);
|
|
@@ -1825,6 +1836,7 @@ export async function postUnitPreVerification(pctx, opts) {
|
|
|
1825
1836
|
if (s.pendingVerificationRetry?.unitId === s.currentUnit.id) {
|
|
1826
1837
|
s.pendingVerificationRetry = null;
|
|
1827
1838
|
}
|
|
1839
|
+
s.toolUnavailableRetries = 0;
|
|
1828
1840
|
s.verificationRetryCount.delete(retryKey);
|
|
1829
1841
|
s.verificationRetryFailureHashes.delete(retryKey);
|
|
1830
1842
|
s.exhaustedVerificationUnits.delete(retryKey);
|
|
@@ -36,17 +36,17 @@ export function isSuspiciousGhostCompletion(ctx, startedAt, maxElapsedMs = GHOST
|
|
|
36
36
|
activity.assistantMessages === 0);
|
|
37
37
|
}
|
|
38
38
|
/**
|
|
39
|
-
* Snapshot metrics, save activity log,
|
|
40
|
-
* for a completed unit.
|
|
39
|
+
* Snapshot metrics, save activity log, extract memories, and record the git
|
|
40
|
+
* transaction for a completed auto-mode unit.
|
|
41
41
|
*/
|
|
42
|
-
export async function
|
|
43
|
-
const modelId = ctx.model?.id ?? "unknown";
|
|
44
|
-
snapshotUnitMetrics(ctx, unitType, unitId, startedAt, modelId, opts);
|
|
45
|
-
const activityFile = saveActivityLog(ctx, basePath, unitType, unitId);
|
|
42
|
+
export async function closeoutAutoUnit(request) {
|
|
43
|
+
const modelId = request.ctx.model?.id ?? "unknown";
|
|
44
|
+
snapshotUnitMetrics(request.ctx, request.unitType, request.unitId, request.startedAt, modelId, request.opts);
|
|
45
|
+
const activityFile = saveActivityLog(request.ctx, request.basePath, request.unitType, request.unitId);
|
|
46
46
|
if (activityFile) {
|
|
47
47
|
try {
|
|
48
|
-
const { buildMemoryLLMCall, extractMemoriesFromUnit } = await import(
|
|
49
|
-
const llmCallFn = buildMemoryLLMCall(ctx);
|
|
48
|
+
const { buildMemoryLLMCall, extractMemoriesFromUnit } = await import("./memory-extractor.js");
|
|
49
|
+
const llmCallFn = buildMemoryLLMCall(request.ctx);
|
|
50
50
|
if (llmCallFn) {
|
|
51
51
|
// Awaited: a fire-and-forget here lets memory-extractor writes land in
|
|
52
52
|
// .gsd/ after closeoutUnit returns but before the milestone merge
|
|
@@ -55,10 +55,10 @@ export async function closeoutUnit(ctx, basePath, unitType, unitId, startedAt, o
|
|
|
55
55
|
// bounded by the extractor's LLM call, which is the acceptable price
|
|
56
56
|
// for not racing the merge boundary.
|
|
57
57
|
try {
|
|
58
|
-
await extractMemoriesFromUnit(activityFile, unitType, unitId, llmCallFn);
|
|
58
|
+
await extractMemoriesFromUnit(activityFile, request.unitType, request.unitId, llmCallFn);
|
|
59
59
|
}
|
|
60
60
|
catch (err) {
|
|
61
|
-
logWarning("engine", `memory extraction failed for ${unitType}/${unitId}: ${err.message}`);
|
|
61
|
+
logWarning("engine", `memory extraction failed for ${request.unitType}/${request.unitId}: ${err.message}`);
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
64
|
}
|
|
@@ -66,22 +66,46 @@ export async function closeoutUnit(ctx, basePath, unitType, unitId, startedAt, o
|
|
|
66
66
|
logWarning("engine", `operation failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
|
-
|
|
69
|
+
const gitTransaction = resolveGitTransactionOptions(request.opts);
|
|
70
|
+
if (gitTransaction) {
|
|
70
71
|
writeTurnGitTransaction({
|
|
71
|
-
basePath,
|
|
72
|
-
traceId:
|
|
73
|
-
turnId:
|
|
74
|
-
unitType,
|
|
75
|
-
unitId,
|
|
72
|
+
basePath: request.basePath,
|
|
73
|
+
traceId: gitTransaction.traceId,
|
|
74
|
+
turnId: gitTransaction.turnId,
|
|
75
|
+
unitType: request.unitType,
|
|
76
|
+
unitId: request.unitId,
|
|
76
77
|
stage: "record",
|
|
77
|
-
action:
|
|
78
|
-
push:
|
|
79
|
-
status:
|
|
80
|
-
error:
|
|
78
|
+
action: gitTransaction.gitAction,
|
|
79
|
+
push: gitTransaction.gitPush === true,
|
|
80
|
+
status: gitTransaction.gitStatus,
|
|
81
|
+
error: gitTransaction.gitError,
|
|
81
82
|
metadata: {
|
|
82
83
|
activityFile,
|
|
83
84
|
},
|
|
84
85
|
});
|
|
85
86
|
}
|
|
86
|
-
return
|
|
87
|
+
return {
|
|
88
|
+
...(activityFile ? { activityFile } : {}),
|
|
89
|
+
gitTransactionRecorded: Boolean(gitTransaction),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
function resolveGitTransactionOptions(opts) {
|
|
93
|
+
if (!opts?.traceId || !opts.turnId || !opts.gitAction || !opts.gitStatus)
|
|
94
|
+
return null;
|
|
95
|
+
return {
|
|
96
|
+
traceId: opts.traceId,
|
|
97
|
+
turnId: opts.turnId,
|
|
98
|
+
gitAction: opts.gitAction,
|
|
99
|
+
gitStatus: opts.gitStatus,
|
|
100
|
+
gitPush: opts.gitPush,
|
|
101
|
+
gitError: opts.gitError,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Compatibility wrapper for existing auto-loop callers. New code should prefer
|
|
106
|
+
* closeoutAutoUnit so the closeout request and result stay explicit.
|
|
107
|
+
*/
|
|
108
|
+
export async function closeoutUnit(ctx, basePath, unitType, unitId, startedAt, opts) {
|
|
109
|
+
const result = await closeoutAutoUnit({ ctx, basePath, unitType, unitId, startedAt, opts });
|
|
110
|
+
return result.activityFile;
|
|
87
111
|
}
|
|
@@ -23,6 +23,7 @@ import { getSlice } from "./gsd-db.js";
|
|
|
23
23
|
import { getLedger } from "./metrics.js";
|
|
24
24
|
import { getUnitCostSpikeAction, resolveUnitCostSpikeMultiplier } from "./auto-budget.js";
|
|
25
25
|
import { formatPostUnitStatusCard } from "./auto-status-message.js";
|
|
26
|
+
import { detectWebApp } from "./web-app-uat.js";
|
|
26
27
|
function getCurrentUnitCostStats(unitId) {
|
|
27
28
|
const ledger = getLedger();
|
|
28
29
|
if (!ledger || !Array.isArray(ledger.units) || ledger.units.length === 0) {
|
|
@@ -617,14 +618,25 @@ export async function runPostUnitVerification(vctx, pauseAuto) {
|
|
|
617
618
|
s.pendingVerificationRetry = null;
|
|
618
619
|
return "continue";
|
|
619
620
|
}
|
|
621
|
+
else if (verdict.reason === "no-host-checks" &&
|
|
622
|
+
taskAlreadyComplete &&
|
|
623
|
+
detectWebApp(s.basePath) &&
|
|
624
|
+
!result.runtimeErrors?.some((e) => e.blocking)) {
|
|
625
|
+
s.verificationRetryCount.delete(retryKey);
|
|
626
|
+
s.verificationRetryFailureHashes.delete(retryKey);
|
|
627
|
+
s.pendingVerificationRetry = null;
|
|
628
|
+
ctx.ui.notify("No task-level host verification command was found for a completed browser-facing task; continuing so slice UAT can verify the UI with browser tools.", "warning");
|
|
629
|
+
return "continue";
|
|
630
|
+
}
|
|
620
631
|
else if (verdict.reason === "no-host-checks") {
|
|
621
632
|
s.verificationRetryCount.delete(retryKey);
|
|
622
633
|
s.verificationRetryFailureHashes.delete(retryKey);
|
|
623
634
|
s.pendingVerificationRetry = null;
|
|
624
|
-
|
|
635
|
+
const pauseMessage = `Verification failed: ${verdict.failureContext}`;
|
|
636
|
+
ctx.ui.notify(`Verification gate FAILED — ${verdict.failureContext}`, "error");
|
|
625
637
|
process.stderr.write(`verification-gate: ${verdict.failureContext}\n`);
|
|
626
638
|
await pauseAuto(ctx, pi, {
|
|
627
|
-
message:
|
|
639
|
+
message: pauseMessage,
|
|
628
640
|
category: "unknown",
|
|
629
641
|
});
|
|
630
642
|
return "pause";
|
|
@@ -557,6 +557,13 @@ export function getAutoModeStartModel() {
|
|
|
557
557
|
export function setCurrentDispatchedModelId(model) {
|
|
558
558
|
s.currentDispatchedModelId = model ? `${model.provider}/${model.id}` : null;
|
|
559
559
|
}
|
|
560
|
+
/**
|
|
561
|
+
* Update the active unit model after runtime recovery switches models mid-unit.
|
|
562
|
+
* The next session restore path reads this field before dispatching again.
|
|
563
|
+
*/
|
|
564
|
+
export function setCurrentUnitModelForRecovery(model) {
|
|
565
|
+
s.currentUnitModel = model;
|
|
566
|
+
}
|
|
560
567
|
// Tool tracking — delegates to auto-tool-tracking.ts
|
|
561
568
|
export function markToolStart(toolCallId, toolName) {
|
|
562
569
|
_markToolStart(toolCallId, s.active, toolName);
|
|
@@ -623,6 +630,35 @@ export function stopAutoRemote(projectRoot) {
|
|
|
623
630
|
return { found: false, error: err.message };
|
|
624
631
|
}
|
|
625
632
|
}
|
|
633
|
+
/**
|
|
634
|
+
* Force-stop a remote auto-mode session before stealing its lock.
|
|
635
|
+
* The normal stop path stays SIGTERM-only so cooperative sessions can clean up;
|
|
636
|
+
* this path is only for the explicit "Force start" action.
|
|
637
|
+
*/
|
|
638
|
+
export function forceStopAutoRemote(projectRoot) {
|
|
639
|
+
const lock = readCrashLock(projectRoot);
|
|
640
|
+
if (!lock)
|
|
641
|
+
return { found: false };
|
|
642
|
+
if (lock.pid === process.pid) {
|
|
643
|
+
clearLock(projectRoot);
|
|
644
|
+
return { found: false };
|
|
645
|
+
}
|
|
646
|
+
if (!isLockProcessAlive(lock)) {
|
|
647
|
+
clearLock(projectRoot);
|
|
648
|
+
return { found: false };
|
|
649
|
+
}
|
|
650
|
+
try {
|
|
651
|
+
process.kill(lock.pid, "SIGTERM");
|
|
652
|
+
if (isLockProcessAlive(lock)) {
|
|
653
|
+
process.kill(lock.pid, "SIGKILL");
|
|
654
|
+
}
|
|
655
|
+
clearLock(projectRoot);
|
|
656
|
+
return { found: true, pid: lock.pid };
|
|
657
|
+
}
|
|
658
|
+
catch (err) {
|
|
659
|
+
return { found: false, error: err.message };
|
|
660
|
+
}
|
|
661
|
+
}
|
|
626
662
|
/**
|
|
627
663
|
* Check if a remote auto-mode session is running (from a different process).
|
|
628
664
|
* Reads the crash lock, checks PID liveness, and returns session details.
|
|
@@ -1824,7 +1860,7 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
|
|
|
1824
1860
|
if (freshStartAssessment.classification === "running") {
|
|
1825
1861
|
const pid = freshStartAssessment.lock?.pid;
|
|
1826
1862
|
ctx.ui.notify(pid
|
|
1827
|
-
? `Another auto-mode session (PID ${pid}) appears to be running.\
|
|
1863
|
+
? `Another auto-mode session (PID ${pid}) appears to be running.\nRun \`/gsd stop\` for graceful shutdown, or choose "Force start" from \`/gsd auto\` to terminate it.`
|
|
1828
1864
|
: "Another auto-mode session appears to be running.", "error");
|
|
1829
1865
|
return;
|
|
1830
1866
|
}
|
|
@@ -9,12 +9,16 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
|
9
9
|
import { dirname, join } from "node:path";
|
|
10
10
|
import { gsdRoot } from "./paths.js";
|
|
11
11
|
import { withFileLockSync } from "./file-lock.js";
|
|
12
|
+
const temporaryBlockedModels = new Map();
|
|
12
13
|
function blockedModelsPath(basePath) {
|
|
13
14
|
return join(gsdRoot(basePath), "runtime", "blocked-models.json");
|
|
14
15
|
}
|
|
15
16
|
function modelKey(provider, id) {
|
|
16
17
|
return `${provider.toLowerCase()}/${id.toLowerCase()}`;
|
|
17
18
|
}
|
|
19
|
+
function temporaryModelKey(basePath, provider, id) {
|
|
20
|
+
return `${basePath}:${modelKey(provider, id)}`;
|
|
21
|
+
}
|
|
18
22
|
function readFileSafe(path) {
|
|
19
23
|
if (!existsSync(path))
|
|
20
24
|
return { version: 1, blocked: [] };
|
|
@@ -41,6 +45,30 @@ export function isModelBlocked(basePath, provider, id) {
|
|
|
41
45
|
const target = modelKey(provider, id);
|
|
42
46
|
return loadBlockedModels(basePath).some((e) => modelKey(e.provider, e.id) === target);
|
|
43
47
|
}
|
|
48
|
+
export function blockModelUntil(basePath, provider, id, blockedUntil, reason) {
|
|
49
|
+
const key = temporaryModelKey(basePath, provider, id);
|
|
50
|
+
if (blockedUntil <= Date.now()) {
|
|
51
|
+
temporaryBlockedModels.delete(key);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
temporaryBlockedModels.set(key, { provider, id, reason, blockedUntil });
|
|
55
|
+
}
|
|
56
|
+
export function isModelTemporarilyUnavailable(basePath, provider, id, now = Date.now()) {
|
|
57
|
+
if (!provider || !id)
|
|
58
|
+
return false;
|
|
59
|
+
const key = temporaryModelKey(basePath, provider, id);
|
|
60
|
+
const entry = temporaryBlockedModels.get(key);
|
|
61
|
+
if (!entry)
|
|
62
|
+
return false;
|
|
63
|
+
if (entry.blockedUntil <= now) {
|
|
64
|
+
temporaryBlockedModels.delete(key);
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
export function clearTemporaryModelBlocksForTest() {
|
|
70
|
+
temporaryBlockedModels.clear();
|
|
71
|
+
}
|
|
44
72
|
export function blockModel(basePath, provider, id, reason) {
|
|
45
73
|
const path = blockedModelsPath(basePath);
|
|
46
74
|
mkdirSync(dirname(path), { recursive: true });
|