@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
|
@@ -33,6 +33,24 @@ export interface UnitActivitySnapshot {
|
|
|
33
33
|
assistantMessages: number;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
export interface AutoUnitCloseoutRequest {
|
|
37
|
+
ctx: ExtensionContext;
|
|
38
|
+
basePath: string;
|
|
39
|
+
unitType: string;
|
|
40
|
+
unitId: string;
|
|
41
|
+
startedAt: number;
|
|
42
|
+
opts?: CloseoutOptions;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface AutoUnitCloseoutResult {
|
|
46
|
+
activityFile?: string;
|
|
47
|
+
gitTransactionRecorded: boolean;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
type GitTransactionCloseoutOptions =
|
|
51
|
+
Required<Pick<CloseoutOptions, "traceId" | "turnId" | "gitAction" | "gitStatus">>
|
|
52
|
+
& Pick<CloseoutOptions, "gitPush" | "gitError">;
|
|
53
|
+
|
|
36
54
|
export const GHOST_COMPLETION_MAX_ELAPSED_MS = 500;
|
|
37
55
|
|
|
38
56
|
export function snapshotUnitActivity(
|
|
@@ -76,25 +94,27 @@ export function isSuspiciousGhostCompletion(
|
|
|
76
94
|
}
|
|
77
95
|
|
|
78
96
|
/**
|
|
79
|
-
* Snapshot metrics, save activity log,
|
|
80
|
-
* for a completed unit.
|
|
97
|
+
* Snapshot metrics, save activity log, extract memories, and record the git
|
|
98
|
+
* transaction for a completed auto-mode unit.
|
|
81
99
|
*/
|
|
82
|
-
export async function
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
100
|
+
export async function closeoutAutoUnit(
|
|
101
|
+
request: AutoUnitCloseoutRequest,
|
|
102
|
+
): Promise<AutoUnitCloseoutResult> {
|
|
103
|
+
const modelId = request.ctx.model?.id ?? "unknown";
|
|
104
|
+
snapshotUnitMetrics(
|
|
105
|
+
request.ctx,
|
|
106
|
+
request.unitType,
|
|
107
|
+
request.unitId,
|
|
108
|
+
request.startedAt,
|
|
109
|
+
modelId,
|
|
110
|
+
request.opts,
|
|
111
|
+
);
|
|
112
|
+
const activityFile = saveActivityLog(request.ctx, request.basePath, request.unitType, request.unitId);
|
|
93
113
|
|
|
94
114
|
if (activityFile) {
|
|
95
115
|
try {
|
|
96
|
-
const { buildMemoryLLMCall, extractMemoriesFromUnit } = await import(
|
|
97
|
-
const llmCallFn = buildMemoryLLMCall(ctx);
|
|
116
|
+
const { buildMemoryLLMCall, extractMemoriesFromUnit } = await import("./memory-extractor.js");
|
|
117
|
+
const llmCallFn = buildMemoryLLMCall(request.ctx);
|
|
98
118
|
if (llmCallFn) {
|
|
99
119
|
// Awaited: a fire-and-forget here lets memory-extractor writes land in
|
|
100
120
|
// .gsd/ after closeoutUnit returns but before the milestone merge
|
|
@@ -103,11 +123,11 @@ export async function closeoutUnit(
|
|
|
103
123
|
// bounded by the extractor's LLM call, which is the acceptable price
|
|
104
124
|
// for not racing the merge boundary.
|
|
105
125
|
try {
|
|
106
|
-
await extractMemoriesFromUnit(activityFile, unitType, unitId, llmCallFn);
|
|
126
|
+
await extractMemoriesFromUnit(activityFile, request.unitType, request.unitId, llmCallFn);
|
|
107
127
|
} catch (err) {
|
|
108
128
|
logWarning(
|
|
109
129
|
"engine",
|
|
110
|
-
`memory extraction failed for ${unitType}/${unitId}: ${(err as Error).message}`,
|
|
130
|
+
`memory extraction failed for ${request.unitType}/${request.unitId}: ${(err as Error).message}`,
|
|
111
131
|
);
|
|
112
132
|
}
|
|
113
133
|
}
|
|
@@ -116,23 +136,58 @@ export async function closeoutUnit(
|
|
|
116
136
|
}
|
|
117
137
|
}
|
|
118
138
|
|
|
119
|
-
|
|
139
|
+
const gitTransaction = resolveGitTransactionOptions(request.opts);
|
|
140
|
+
|
|
141
|
+
if (gitTransaction) {
|
|
120
142
|
writeTurnGitTransaction({
|
|
121
|
-
basePath,
|
|
122
|
-
traceId:
|
|
123
|
-
turnId:
|
|
124
|
-
unitType,
|
|
125
|
-
unitId,
|
|
143
|
+
basePath: request.basePath,
|
|
144
|
+
traceId: gitTransaction.traceId,
|
|
145
|
+
turnId: gitTransaction.turnId,
|
|
146
|
+
unitType: request.unitType,
|
|
147
|
+
unitId: request.unitId,
|
|
126
148
|
stage: "record",
|
|
127
|
-
action:
|
|
128
|
-
push:
|
|
129
|
-
status:
|
|
130
|
-
error:
|
|
149
|
+
action: gitTransaction.gitAction,
|
|
150
|
+
push: gitTransaction.gitPush === true,
|
|
151
|
+
status: gitTransaction.gitStatus,
|
|
152
|
+
error: gitTransaction.gitError,
|
|
131
153
|
metadata: {
|
|
132
154
|
activityFile,
|
|
133
155
|
},
|
|
134
156
|
});
|
|
135
157
|
}
|
|
136
158
|
|
|
137
|
-
return
|
|
159
|
+
return {
|
|
160
|
+
...(activityFile ? { activityFile } : {}),
|
|
161
|
+
gitTransactionRecorded: Boolean(gitTransaction),
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function resolveGitTransactionOptions(
|
|
166
|
+
opts: CloseoutOptions | undefined,
|
|
167
|
+
): GitTransactionCloseoutOptions | null {
|
|
168
|
+
if (!opts?.traceId || !opts.turnId || !opts.gitAction || !opts.gitStatus) return null;
|
|
169
|
+
return {
|
|
170
|
+
traceId: opts.traceId,
|
|
171
|
+
turnId: opts.turnId,
|
|
172
|
+
gitAction: opts.gitAction,
|
|
173
|
+
gitStatus: opts.gitStatus,
|
|
174
|
+
gitPush: opts.gitPush,
|
|
175
|
+
gitError: opts.gitError,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Compatibility wrapper for existing auto-loop callers. New code should prefer
|
|
181
|
+
* closeoutAutoUnit so the closeout request and result stay explicit.
|
|
182
|
+
*/
|
|
183
|
+
export async function closeoutUnit(
|
|
184
|
+
ctx: ExtensionContext,
|
|
185
|
+
basePath: string,
|
|
186
|
+
unitType: string,
|
|
187
|
+
unitId: string,
|
|
188
|
+
startedAt: number,
|
|
189
|
+
opts?: CloseoutOptions,
|
|
190
|
+
): Promise<string | undefined> {
|
|
191
|
+
const result = await closeoutAutoUnit({ ctx, basePath, unitType, unitId, startedAt, opts });
|
|
192
|
+
return result.activityFile;
|
|
138
193
|
}
|
|
@@ -50,6 +50,7 @@ import { getSlice } from "./gsd-db.js";
|
|
|
50
50
|
import { getLedger } from "./metrics.js";
|
|
51
51
|
import { getUnitCostSpikeAction, resolveUnitCostSpikeMultiplier } from "./auto-budget.js";
|
|
52
52
|
import { formatPostUnitStatusCard } from "./auto-status-message.js";
|
|
53
|
+
import { detectWebApp } from "./web-app-uat.js";
|
|
53
54
|
|
|
54
55
|
export interface VerificationContext {
|
|
55
56
|
s: AutoSession;
|
|
@@ -787,17 +788,32 @@ export async function runPostUnitVerification(
|
|
|
787
788
|
s.verificationRetryFailureHashes.delete(retryKey);
|
|
788
789
|
s.pendingVerificationRetry = null;
|
|
789
790
|
return "continue";
|
|
791
|
+
} else if (
|
|
792
|
+
verdict.reason === "no-host-checks" &&
|
|
793
|
+
taskAlreadyComplete &&
|
|
794
|
+
detectWebApp(s.basePath) &&
|
|
795
|
+
!result.runtimeErrors?.some((e) => e.blocking)
|
|
796
|
+
) {
|
|
797
|
+
s.verificationRetryCount.delete(retryKey);
|
|
798
|
+
s.verificationRetryFailureHashes.delete(retryKey);
|
|
799
|
+
s.pendingVerificationRetry = null;
|
|
800
|
+
ctx.ui.notify(
|
|
801
|
+
"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.",
|
|
802
|
+
"warning",
|
|
803
|
+
);
|
|
804
|
+
return "continue";
|
|
790
805
|
} else if (verdict.reason === "no-host-checks") {
|
|
791
806
|
s.verificationRetryCount.delete(retryKey);
|
|
792
807
|
s.verificationRetryFailureHashes.delete(retryKey);
|
|
793
808
|
s.pendingVerificationRetry = null;
|
|
809
|
+
const pauseMessage = `Verification failed: ${verdict.failureContext}`;
|
|
794
810
|
ctx.ui.notify(
|
|
795
|
-
|
|
811
|
+
`Verification gate FAILED — ${verdict.failureContext}`,
|
|
796
812
|
"error",
|
|
797
813
|
);
|
|
798
814
|
process.stderr.write(`verification-gate: ${verdict.failureContext}\n`);
|
|
799
815
|
await pauseAuto(ctx, pi, {
|
|
800
|
-
message:
|
|
816
|
+
message: pauseMessage,
|
|
801
817
|
category: "unknown",
|
|
802
818
|
});
|
|
803
819
|
return "pause";
|
|
@@ -924,6 +924,14 @@ export function setCurrentDispatchedModelId(model: { provider: string; id: strin
|
|
|
924
924
|
s.currentDispatchedModelId = model ? `${model.provider}/${model.id}` : null;
|
|
925
925
|
}
|
|
926
926
|
|
|
927
|
+
/**
|
|
928
|
+
* Update the active unit model after runtime recovery switches models mid-unit.
|
|
929
|
+
* The next session restore path reads this field before dispatching again.
|
|
930
|
+
*/
|
|
931
|
+
export function setCurrentUnitModelForRecovery(model: any | null): void {
|
|
932
|
+
s.currentUnitModel = model;
|
|
933
|
+
}
|
|
934
|
+
|
|
927
935
|
// Tool tracking — delegates to auto-tool-tracking.ts
|
|
928
936
|
export function markToolStart(toolCallId: string, toolName?: string): void {
|
|
929
937
|
_markToolStart(toolCallId, s.active, toolName);
|
|
@@ -1000,6 +1008,41 @@ export function stopAutoRemote(projectRoot: string): {
|
|
|
1000
1008
|
}
|
|
1001
1009
|
}
|
|
1002
1010
|
|
|
1011
|
+
/**
|
|
1012
|
+
* Force-stop a remote auto-mode session before stealing its lock.
|
|
1013
|
+
* The normal stop path stays SIGTERM-only so cooperative sessions can clean up;
|
|
1014
|
+
* this path is only for the explicit "Force start" action.
|
|
1015
|
+
*/
|
|
1016
|
+
export function forceStopAutoRemote(projectRoot: string): {
|
|
1017
|
+
found: boolean;
|
|
1018
|
+
pid?: number;
|
|
1019
|
+
error?: string;
|
|
1020
|
+
} {
|
|
1021
|
+
const lock = readCrashLock(projectRoot);
|
|
1022
|
+
if (!lock) return { found: false };
|
|
1023
|
+
|
|
1024
|
+
if (lock.pid === process.pid) {
|
|
1025
|
+
clearLock(projectRoot);
|
|
1026
|
+
return { found: false };
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
if (!isLockProcessAlive(lock)) {
|
|
1030
|
+
clearLock(projectRoot);
|
|
1031
|
+
return { found: false };
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
try {
|
|
1035
|
+
process.kill(lock.pid, "SIGTERM");
|
|
1036
|
+
if (isLockProcessAlive(lock)) {
|
|
1037
|
+
process.kill(lock.pid, "SIGKILL");
|
|
1038
|
+
}
|
|
1039
|
+
clearLock(projectRoot);
|
|
1040
|
+
return { found: true, pid: lock.pid };
|
|
1041
|
+
} catch (err) {
|
|
1042
|
+
return { found: false, error: (err as Error).message };
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1003
1046
|
/**
|
|
1004
1047
|
* Check if a remote auto-mode session is running (from a different process).
|
|
1005
1048
|
* Reads the crash lock, checks PID liveness, and returns session details.
|
|
@@ -2400,7 +2443,7 @@ export async function startAuto(
|
|
|
2400
2443
|
const pid = freshStartAssessment.lock?.pid;
|
|
2401
2444
|
ctx.ui.notify(
|
|
2402
2445
|
pid
|
|
2403
|
-
? `Another auto-mode session (PID ${pid}) appears to be running.\
|
|
2446
|
+
? `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.`
|
|
2404
2447
|
: "Another auto-mode session appears to be running.",
|
|
2405
2448
|
"error",
|
|
2406
2449
|
);
|
|
@@ -23,6 +23,15 @@ interface BlockedModelsFile {
|
|
|
23
23
|
blocked: BlockedModelEntry[];
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
interface TemporaryBlockedModelEntry {
|
|
27
|
+
provider: string;
|
|
28
|
+
id: string;
|
|
29
|
+
reason: string;
|
|
30
|
+
blockedUntil: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const temporaryBlockedModels = new Map<string, TemporaryBlockedModelEntry>();
|
|
34
|
+
|
|
26
35
|
function blockedModelsPath(basePath: string): string {
|
|
27
36
|
return join(gsdRoot(basePath), "runtime", "blocked-models.json");
|
|
28
37
|
}
|
|
@@ -31,6 +40,10 @@ function modelKey(provider: string, id: string): string {
|
|
|
31
40
|
return `${provider.toLowerCase()}/${id.toLowerCase()}`;
|
|
32
41
|
}
|
|
33
42
|
|
|
43
|
+
function temporaryModelKey(basePath: string, provider: string, id: string): string {
|
|
44
|
+
return `${basePath}:${modelKey(provider, id)}`;
|
|
45
|
+
}
|
|
46
|
+
|
|
34
47
|
function readFileSafe(path: string): BlockedModelsFile {
|
|
35
48
|
if (!existsSync(path)) return { version: 1, blocked: [] };
|
|
36
49
|
try {
|
|
@@ -66,6 +79,42 @@ export function isModelBlocked(
|
|
|
66
79
|
);
|
|
67
80
|
}
|
|
68
81
|
|
|
82
|
+
export function blockModelUntil(
|
|
83
|
+
basePath: string,
|
|
84
|
+
provider: string,
|
|
85
|
+
id: string,
|
|
86
|
+
blockedUntil: number,
|
|
87
|
+
reason: string,
|
|
88
|
+
): void {
|
|
89
|
+
const key = temporaryModelKey(basePath, provider, id);
|
|
90
|
+
if (blockedUntil <= Date.now()) {
|
|
91
|
+
temporaryBlockedModels.delete(key);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
temporaryBlockedModels.set(key, { provider, id, reason, blockedUntil });
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function isModelTemporarilyUnavailable(
|
|
98
|
+
basePath: string,
|
|
99
|
+
provider: string | undefined,
|
|
100
|
+
id: string | undefined,
|
|
101
|
+
now = Date.now(),
|
|
102
|
+
): boolean {
|
|
103
|
+
if (!provider || !id) return false;
|
|
104
|
+
const key = temporaryModelKey(basePath, provider, id);
|
|
105
|
+
const entry = temporaryBlockedModels.get(key);
|
|
106
|
+
if (!entry) return false;
|
|
107
|
+
if (entry.blockedUntil <= now) {
|
|
108
|
+
temporaryBlockedModels.delete(key);
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function clearTemporaryModelBlocksForTest(): void {
|
|
115
|
+
temporaryBlockedModels.clear();
|
|
116
|
+
}
|
|
117
|
+
|
|
69
118
|
export function blockModel(
|
|
70
119
|
basePath: string,
|
|
71
120
|
provider: string,
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
isAutoCompletionStopInProgress,
|
|
20
20
|
pauseAuto,
|
|
21
21
|
setCurrentDispatchedModelId,
|
|
22
|
+
setCurrentUnitModelForRecovery,
|
|
22
23
|
} from "../auto.js";
|
|
23
24
|
import { getNextFallbackModel, resolveModelWithFallbacksForUnit } from "../preferences.js";
|
|
24
25
|
import { pauseAutoForProviderError } from "../provider-error-pause.js";
|
|
@@ -41,7 +42,7 @@ import {
|
|
|
41
42
|
isTransient,
|
|
42
43
|
type ErrorClass,
|
|
43
44
|
} from "../error-classifier.js";
|
|
44
|
-
import { blockModel, isModelBlocked } from "../blocked-models.js";
|
|
45
|
+
import { blockModel, blockModelUntil, isModelBlocked, isModelTemporarilyUnavailable } from "../blocked-models.js";
|
|
45
46
|
import { getProjectGSDPreferencesPath } from "../preferences.js";
|
|
46
47
|
import { resolveProviderErrorGuidance } from "../provider-error-guidance.js";
|
|
47
48
|
import { formatGuidance } from "../guidance.js";
|
|
@@ -143,9 +144,14 @@ async function tryProviderModelFallback(params: ProviderModelFallbackParams): Pr
|
|
|
143
144
|
const nextModelId = getNextFallbackModel(cursorModelId, modelConfig);
|
|
144
145
|
if (!nextModelId) break;
|
|
145
146
|
const candidate = resolveModelId(nextModelId, availableModels, rejectedProvider);
|
|
146
|
-
if (
|
|
147
|
+
if (
|
|
148
|
+
candidate &&
|
|
149
|
+
!isModelBlocked(basePath, candidate.provider, candidate.id) &&
|
|
150
|
+
!isModelTemporarilyUnavailable(basePath, candidate.provider, candidate.id)
|
|
151
|
+
) {
|
|
147
152
|
const ok = await pi.setModel(candidate, { persist: false });
|
|
148
153
|
if (ok) {
|
|
154
|
+
setCurrentUnitModelForRecovery(candidate);
|
|
149
155
|
setCurrentDispatchedModelId({ provider: candidate.provider, id: candidate.id });
|
|
150
156
|
switchedNotify(`${candidate.provider}/${candidate.id}`);
|
|
151
157
|
pi.sendMessage(
|
|
@@ -163,7 +169,8 @@ async function tryProviderModelFallback(params: ProviderModelFallbackParams): Pr
|
|
|
163
169
|
if (
|
|
164
170
|
sessionModel &&
|
|
165
171
|
!(sessionModel.provider === rejectedProvider && sessionModel.id === rejectedId) &&
|
|
166
|
-
!isModelBlocked(basePath, sessionModel.provider, sessionModel.id)
|
|
172
|
+
!isModelBlocked(basePath, sessionModel.provider, sessionModel.id) &&
|
|
173
|
+
!isModelTemporarilyUnavailable(basePath, sessionModel.provider, sessionModel.id)
|
|
167
174
|
) {
|
|
168
175
|
const startModel = availableModels.find(
|
|
169
176
|
(m) => m.provider === sessionModel.provider && m.id === sessionModel.id,
|
|
@@ -171,6 +178,7 @@ async function tryProviderModelFallback(params: ProviderModelFallbackParams): Pr
|
|
|
171
178
|
if (startModel) {
|
|
172
179
|
const ok = await pi.setModel(startModel, { persist: false });
|
|
173
180
|
if (ok) {
|
|
181
|
+
setCurrentUnitModelForRecovery(startModel);
|
|
174
182
|
setCurrentDispatchedModelId({ provider: startModel.provider, id: startModel.id });
|
|
175
183
|
switchedNotify(`${startModel.provider}/${startModel.id}`);
|
|
176
184
|
pi.sendMessage(
|
|
@@ -676,6 +684,16 @@ export async function handleAgentEnd(
|
|
|
676
684
|
if (currentProvider === "openai-codex" || currentProvider === "google-gemini-cli") {
|
|
677
685
|
cls.retryAfterMs = Math.min(cls.retryAfterMs, 30_000);
|
|
678
686
|
}
|
|
687
|
+
const dash = getAutoDashboardData();
|
|
688
|
+
if (dash.basePath && ctx.model?.provider && ctx.model?.id) {
|
|
689
|
+
blockModelUntil(
|
|
690
|
+
dash.basePath,
|
|
691
|
+
ctx.model.provider,
|
|
692
|
+
ctx.model.id,
|
|
693
|
+
Date.now() + cls.retryAfterMs,
|
|
694
|
+
rawErrorMsg || displayMsg || "rate limit",
|
|
695
|
+
);
|
|
696
|
+
}
|
|
679
697
|
}
|
|
680
698
|
|
|
681
699
|
// ── 2. Decide & Act ──────────────────────────────────────────────────
|
|
@@ -721,9 +739,14 @@ export async function handleAgentEnd(
|
|
|
721
739
|
retryState.networkRetryCount = 0;
|
|
722
740
|
retryState.currentRetryModelId = undefined;
|
|
723
741
|
const modelToSet = resolveModelId(nextModelId, availableModels, ctx.model?.provider);
|
|
724
|
-
|
|
742
|
+
const modelUnavailable = dash.basePath && modelToSet
|
|
743
|
+
? isModelBlocked(dash.basePath, modelToSet.provider, modelToSet.id) ||
|
|
744
|
+
isModelTemporarilyUnavailable(dash.basePath, modelToSet.provider, modelToSet.id)
|
|
745
|
+
: false;
|
|
746
|
+
if (modelToSet && !modelUnavailable) {
|
|
725
747
|
const ok = await pi.setModel(modelToSet, { persist: false });
|
|
726
748
|
if (ok) {
|
|
749
|
+
setCurrentUnitModelForRecovery(modelToSet);
|
|
727
750
|
setCurrentDispatchedModelId({ provider: modelToSet.provider, id: modelToSet.id });
|
|
728
751
|
ctx.ui.notify(`Model error${errorDetail}. Switched to fallback: ${nextModelId} and resuming.`, "warning");
|
|
729
752
|
pi.sendMessage({ customType: "gsd-auto-timeout-recovery", content: "Continue execution.", display: false }, { triggerTurn: true });
|
|
@@ -737,11 +760,17 @@ export async function handleAgentEnd(
|
|
|
737
760
|
// Try restoring session model
|
|
738
761
|
const sessionModel = getAutoModeStartModel();
|
|
739
762
|
if (sessionModel) {
|
|
740
|
-
|
|
763
|
+
const dash = getAutoDashboardData();
|
|
764
|
+
const sessionModelUnavailable = dash.basePath
|
|
765
|
+
? isModelBlocked(dash.basePath, sessionModel.provider, sessionModel.id) ||
|
|
766
|
+
isModelTemporarilyUnavailable(dash.basePath, sessionModel.provider, sessionModel.id)
|
|
767
|
+
: false;
|
|
768
|
+
if (!sessionModelUnavailable && (ctx.model?.id !== sessionModel.id || ctx.model?.provider !== sessionModel.provider)) {
|
|
741
769
|
const startModel = ctx.modelRegistry.getAvailable().find((m) => m.provider === sessionModel.provider && m.id === sessionModel.id);
|
|
742
770
|
if (startModel) {
|
|
743
771
|
const ok = await pi.setModel(startModel, { persist: false });
|
|
744
772
|
if (ok) {
|
|
773
|
+
setCurrentUnitModelForRecovery(startModel);
|
|
745
774
|
setCurrentDispatchedModelId({ provider: startModel.provider, id: startModel.id });
|
|
746
775
|
retryState.networkRetryCount = 0;
|
|
747
776
|
retryState.currentRetryModelId = undefined;
|
|
@@ -137,8 +137,8 @@ export function registerExecTools(pi: ExtensionAPI): void {
|
|
|
137
137
|
parameters: Type.Object({
|
|
138
138
|
query: Type.Optional(Type.String({ description: "Substring matched against id and purpose (case-insensitive)." })),
|
|
139
139
|
runtime: Type.Optional(
|
|
140
|
-
Type.
|
|
141
|
-
description: "Restrict to one runtime.",
|
|
140
|
+
Type.String({
|
|
141
|
+
description: "Restrict to one runtime: bash, node, or python.",
|
|
142
142
|
}),
|
|
143
143
|
),
|
|
144
144
|
failing_only: Type.Optional(Type.Boolean({ description: "Only non-zero exit codes and timeouts." })),
|
|
@@ -1,25 +1,40 @@
|
|
|
1
1
|
// Project/App: gsd-pi
|
|
2
2
|
// File Purpose: Shared closeout detection and merge actions for /gsd home and smart entry.
|
|
3
3
|
|
|
4
|
+
import { existsSync, readdirSync, statSync } from "node:fs";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
|
|
4
7
|
import type { ExtensionCommandContext } from "@gsd/pi-coding-agent";
|
|
5
8
|
|
|
6
9
|
import type { NextAction } from "../shared/next-action-ui.js";
|
|
7
10
|
import type { GSDState } from "./types.js";
|
|
8
11
|
import { setAutoOutcomeWidget } from "./auto-dashboard.js";
|
|
9
12
|
import { invalidateAllCaches } from "./cache.js";
|
|
13
|
+
import { isDbAvailable } from "./db/engine.js";
|
|
14
|
+
import { getMilestone } from "./db/queries.js";
|
|
15
|
+
import { MILESTONE_ID_RE } from "./milestone-ids.js";
|
|
10
16
|
import { mergeCompletedMilestone } from "./parallel-merge.js";
|
|
11
17
|
import { cleanupQuickBranch, detectStrandedQuickBranch, type StrandedQuickBranch } from "./quick.js";
|
|
18
|
+
import { isClosedStatus } from "./status-guards.js";
|
|
12
19
|
import {
|
|
13
20
|
findUnmergedCompletedMilestones,
|
|
14
21
|
type UnmergedMilestoneBlocker,
|
|
15
22
|
} from "./unmerged-milestone-guard.js";
|
|
16
23
|
import { appendRequirementsBacklogToSummary } from "./requirements-backlog.js";
|
|
24
|
+
import { nativeBranchList, nativeIsRepo } from "./native-git-bridge.js";
|
|
25
|
+
import { allWorktreesDirs } from "./worktree-manager.js";
|
|
17
26
|
|
|
18
27
|
export type CloseoutActionId = "finish_quick" | "finish_milestone";
|
|
19
28
|
|
|
29
|
+
export interface IdleMilestoneResidueHint {
|
|
30
|
+
message: string;
|
|
31
|
+
milestoneIds: string[];
|
|
32
|
+
}
|
|
33
|
+
|
|
20
34
|
export interface CloseoutContext {
|
|
21
35
|
strandedQuick: StrandedQuickBranch | null;
|
|
22
36
|
unmergedMilestones: UnmergedMilestoneBlocker[];
|
|
37
|
+
idleResidueHint?: IdleMilestoneResidueHint | null;
|
|
23
38
|
}
|
|
24
39
|
|
|
25
40
|
const MILESTONE_MERGE_CLOSEOUT_COMMANDS = [
|
|
@@ -29,11 +44,90 @@ const MILESTONE_MERGE_CLOSEOUT_COMMANDS = [
|
|
|
29
44
|
"/gsd start for new work",
|
|
30
45
|
];
|
|
31
46
|
|
|
47
|
+
function listMilestoneWorktreeIds(basePath: string): string[] {
|
|
48
|
+
const ids = new Set<string>();
|
|
49
|
+
for (const wtDir of allWorktreesDirs(basePath)) {
|
|
50
|
+
if (!existsSync(wtDir)) continue;
|
|
51
|
+
for (const entry of readdirSync(wtDir)) {
|
|
52
|
+
if (!MILESTONE_ID_RE.test(entry)) continue;
|
|
53
|
+
try {
|
|
54
|
+
if (statSync(join(wtDir, entry)).isDirectory()) ids.add(entry);
|
|
55
|
+
} catch {
|
|
56
|
+
// skip unreadable entries
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return [...ids].sort();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function listMilestoneBranchIds(basePath: string): string[] {
|
|
64
|
+
try {
|
|
65
|
+
return nativeBranchList(basePath, "milestone/*")
|
|
66
|
+
.map((branch) => branch.replace(/^milestone\//, ""))
|
|
67
|
+
.filter((id) => MILESTONE_ID_RE.test(id))
|
|
68
|
+
.sort();
|
|
69
|
+
} catch {
|
|
70
|
+
return [];
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* A milestone ID is "stranded residue" only when its worktree/branch artifacts
|
|
76
|
+
* exist for a milestone the DB does not consider currently in flight — i.e. the
|
|
77
|
+
* row is closed (complete/done/skipped/closed) or absent. Active, pending,
|
|
78
|
+
* blocked, parked, queued, and deferred rows describe normal in-flight or
|
|
79
|
+
* intentionally-preserved state, never residue. Returning `false` skips the ID;
|
|
80
|
+
* returning `true` keeps it in the hint.
|
|
81
|
+
*/
|
|
82
|
+
function isStrandedMilestoneId(milestoneId: string): boolean {
|
|
83
|
+
if (!isDbAvailable()) return true;
|
|
84
|
+
const row = getMilestone(milestoneId);
|
|
85
|
+
if (!row) return true;
|
|
86
|
+
return isClosedStatus(row.status);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Surface stranded milestone git residue when closeout guards did not classify it. */
|
|
90
|
+
export function detectIdleMilestoneResidueHint(basePath: string): IdleMilestoneResidueHint | null {
|
|
91
|
+
if (!nativeIsRepo(basePath)) return null;
|
|
92
|
+
|
|
93
|
+
const gsdDir = join(basePath, ".gsd");
|
|
94
|
+
const dbPath = join(gsdDir, "gsd.db");
|
|
95
|
+
if (!existsSync(gsdDir) || !existsSync(dbPath)) {
|
|
96
|
+
return {
|
|
97
|
+
milestoneIds: [],
|
|
98
|
+
message:
|
|
99
|
+
"This git repo has no local GSD workflow database (.gsd/gsd.db). " +
|
|
100
|
+
"Workflow state may live in an external worktree, or run /gsd new-project to initialize here.",
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const worktreeIds = listMilestoneWorktreeIds(basePath);
|
|
105
|
+
const branchIds = listMilestoneBranchIds(basePath);
|
|
106
|
+
const candidateIds = [...new Set([...worktreeIds, ...branchIds])].sort();
|
|
107
|
+
const milestoneIds = candidateIds.filter(isStrandedMilestoneId);
|
|
108
|
+
if (milestoneIds.length === 0) return null;
|
|
109
|
+
|
|
110
|
+
const listed = milestoneIds.join(", ");
|
|
111
|
+
const recovery =
|
|
112
|
+
milestoneIds.length === 1
|
|
113
|
+
? `/gsd dispatch complete-milestone ${milestoneIds[0]}`
|
|
114
|
+
: "/gsd doctor --fix";
|
|
115
|
+
return {
|
|
116
|
+
milestoneIds,
|
|
117
|
+
message:
|
|
118
|
+
`Stranded milestone git residue detected (${listed}: worktree dir and/or milestone/* branch). ` +
|
|
119
|
+
`Run ${recovery} or /gsd status to recover closeout before starting new work.`,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
32
123
|
export async function loadCloseoutContext(basePath: string): Promise<CloseoutContext> {
|
|
33
124
|
const unmergedMilestones = await findUnmergedCompletedMilestones(basePath);
|
|
125
|
+
const idleResidueHint =
|
|
126
|
+
unmergedMilestones.length === 0 ? detectIdleMilestoneResidueHint(basePath) : null;
|
|
34
127
|
return {
|
|
35
128
|
strandedQuick: detectStrandedQuickBranch(basePath),
|
|
36
129
|
unmergedMilestones,
|
|
130
|
+
idleResidueHint,
|
|
37
131
|
};
|
|
38
132
|
}
|
|
39
133
|
|
|
@@ -87,6 +181,14 @@ export function buildIdleMenuSummary(state: GSDState, closeout: CloseoutContext)
|
|
|
87
181
|
];
|
|
88
182
|
}
|
|
89
183
|
|
|
184
|
+
// Surface idle residue before the completion summary so smart entry shows
|
|
185
|
+
// the same recovery text /gsd home would: a closed/unknown milestone with
|
|
186
|
+
// lingering worktree/branch artifacts must not be hidden behind the
|
|
187
|
+
// "all milestones complete" message.
|
|
188
|
+
if (closeout.idleResidueHint) {
|
|
189
|
+
return [closeout.idleResidueHint.message];
|
|
190
|
+
}
|
|
191
|
+
|
|
90
192
|
if (state.phase === "complete") {
|
|
91
193
|
const last = state.lastCompletedMilestone;
|
|
92
194
|
return appendRequirementsBacklogToSummary(state, [
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ExtensionAPI, ExtensionCommandContext } from "@gsd/pi-coding-agent";
|
|
2
2
|
|
|
3
|
-
import { checkRemoteAutoSession, isAutoActive, isAutoPaused, stopAutoRemote } from "../auto.js";
|
|
3
|
+
import { checkRemoteAutoSession, forceStopAutoRemote, isAutoActive, isAutoPaused, stopAutoRemote } from "../auto.js";
|
|
4
4
|
import { validateDirectory } from "../validate-directory.js";
|
|
5
5
|
import { resolveProjectRoot } from "../worktree.js";
|
|
6
6
|
import { showNextAction } from "../../shared/tui.js";
|
|
@@ -155,5 +155,19 @@ export async function guardRemoteSession(
|
|
|
155
155
|
return false;
|
|
156
156
|
}
|
|
157
157
|
|
|
158
|
-
|
|
158
|
+
if (choice === "force") {
|
|
159
|
+
const result = forceStopAutoRemote(projectRoot());
|
|
160
|
+
if (result.error) {
|
|
161
|
+
ctx.ui.notify(`Failed to force-stop remote auto-mode: ${result.error}`, "error");
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
if (result.found) {
|
|
165
|
+
ctx.ui.notify(`Force-stopped auto-mode session (PID ${result.pid}). Starting a new session.`, "warning");
|
|
166
|
+
} else {
|
|
167
|
+
ctx.ui.notify("Remote session is no longer running. Starting a new session.", "info");
|
|
168
|
+
}
|
|
169
|
+
return true;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return false;
|
|
159
173
|
}
|