@opengsd/gsd-pi 1.3.0-dev.65546769 → 1.3.0-dev.eed73bea
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/claude-code-cli/stream-adapter.js +11 -2
- package/dist/resources/extensions/google-cli/stream-adapter.js +82 -15
- package/dist/resources/extensions/gsd/auto/orchestrator.js +12 -3
- package/dist/resources/extensions/gsd/auto-dispatch.js +17 -14
- package/dist/resources/extensions/gsd/auto-prompts.js +43 -12
- package/dist/resources/extensions/gsd/auto-recovery.js +13 -6
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +103 -13
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +6 -1
- package/dist/resources/extensions/gsd/bootstrap/exec-tools.js +2 -0
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +8 -3
- package/dist/resources/extensions/gsd/bootstrap/system-context.js +46 -19
- package/dist/resources/extensions/gsd/bootstrap/tool-call-loop-guard.js +75 -1
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +1 -1
- package/dist/resources/extensions/gsd/commands-context.js +19 -1
- package/dist/resources/extensions/gsd/commands-prefs-wizard.js +16 -10
- package/dist/resources/extensions/gsd/commands-worktree.js +12 -10
- package/dist/resources/extensions/gsd/dashboard-overlay.js +32 -3
- package/dist/resources/extensions/gsd/db/queries.js +60 -0
- package/dist/resources/extensions/gsd/doctor-providers.js +92 -8
- package/dist/resources/extensions/gsd/exec-sandbox.js +45 -9
- package/dist/resources/extensions/gsd/forensics.js +2 -32
- package/dist/resources/extensions/gsd/git-service.js +4 -4
- package/dist/resources/extensions/gsd/guided-flow-queue.js +59 -5
- package/dist/resources/extensions/gsd/health-widget.js +55 -29
- package/dist/resources/extensions/gsd/markdown-renderer.js +6 -2
- package/dist/resources/extensions/gsd/memory-consolidation-scanner.js +44 -21
- package/dist/resources/extensions/gsd/milestone-implementation-evidence.js +26 -20
- package/dist/resources/extensions/gsd/quick.js +45 -2
- package/dist/resources/extensions/gsd/session-forensics.js +11 -1
- package/dist/resources/extensions/gsd/state-reconciliation/drift/stale-render.js +52 -3
- package/dist/resources/extensions/gsd/tools/complete-slice.js +34 -3
- package/dist/resources/extensions/gsd/tools/complete-task.js +78 -16
- package/dist/resources/extensions/gsd/tools/exec-tool.js +7 -2
- package/dist/resources/extensions/gsd/unit-context-composer.js +23 -7
- package/dist/resources/extensions/gsd/unit-registry.js +25 -3
- package/dist/resources/extensions/gsd/unmerged-milestone-guard.js +33 -3
- package/dist/resources/extensions/gsd/validation-block-guard.js +9 -4
- package/dist/resources/extensions/gsd/workspace-git-preflight.js +30 -1
- package/dist/tsconfig.extensions.tsbuildinfo +1 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +14 -14
- package/dist/web/standalone/.next/build-manifest.json +3 -3
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/react-loadable-manifest.json +1 -1
- 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/api/visualizer/route.js +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 +14 -14
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware-react-loadable-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/.next/static/chunks/{796.e0bdc932325d7e03.js → 796.3976108148518f7d.js} +3 -3
- package/dist/web/standalone/.next/static/chunks/{webpack-f46ea08200a0227e.js → webpack-7c1d97e39be2da11.js} +1 -1
- package/package.json +1 -1
- package/packages/cloud-mcp-gateway/package.json +2 -2
- package/packages/contracts/dist/workflow.d.ts +1 -0
- package/packages/contracts/dist/workflow.d.ts.map +1 -1
- package/packages/contracts/dist/workflow.js +2 -0
- package/packages/contracts/dist/workflow.js.map +1 -1
- package/packages/contracts/package.json +1 -1
- package/packages/daemon/package.json +4 -4
- package/packages/gsd-agent-core/package.json +5 -5
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js +21 -9
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
- package/packages/gsd-agent-modes/package.json +7 -7
- package/packages/mcp-server/README.md +1 -1
- package/packages/mcp-server/dist/server.d.ts +1 -1
- package/packages/mcp-server/dist/server.d.ts.map +1 -1
- package/packages/mcp-server/dist/server.js +3 -3
- package/packages/mcp-server/dist/server.js.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.d.ts +13 -1
- package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js +34 -20
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/package.json +4 -4
- package/packages/native/package.json +1 -1
- package/packages/pi-agent-core/package.json +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/claude-code-cli/stream-adapter.ts +20 -2
- package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +80 -0
- package/src/resources/extensions/google-cli/stream-adapter.ts +106 -19
- package/src/resources/extensions/gsd/auto/orchestrator.ts +25 -11
- package/src/resources/extensions/gsd/auto-dispatch.ts +18 -17
- package/src/resources/extensions/gsd/auto-prompts.ts +54 -12
- package/src/resources/extensions/gsd/auto-recovery.ts +13 -6
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +125 -12
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +6 -1
- package/src/resources/extensions/gsd/bootstrap/exec-tools.ts +2 -0
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +9 -3
- package/src/resources/extensions/gsd/bootstrap/system-context.ts +52 -18
- package/src/resources/extensions/gsd/bootstrap/tool-call-loop-guard.ts +82 -1
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +1 -1
- package/src/resources/extensions/gsd/commands-context.ts +18 -1
- package/src/resources/extensions/gsd/commands-prefs-wizard.ts +14 -9
- package/src/resources/extensions/gsd/commands-worktree.ts +12 -10
- package/src/resources/extensions/gsd/dashboard-overlay.ts +32 -3
- package/src/resources/extensions/gsd/db/queries.ts +79 -0
- package/src/resources/extensions/gsd/doctor-providers.ts +103 -9
- package/src/resources/extensions/gsd/exec-sandbox.ts +49 -9
- package/src/resources/extensions/gsd/forensics.ts +2 -33
- package/src/resources/extensions/gsd/git-service.ts +5 -5
- package/src/resources/extensions/gsd/guided-flow-queue.ts +82 -4
- package/src/resources/extensions/gsd/health-widget.ts +69 -32
- package/src/resources/extensions/gsd/markdown-renderer.ts +6 -1
- package/src/resources/extensions/gsd/memory-consolidation-scanner.ts +51 -19
- package/src/resources/extensions/gsd/milestone-implementation-evidence.ts +35 -21
- package/src/resources/extensions/gsd/quick.ts +43 -2
- package/src/resources/extensions/gsd/session-forensics.ts +11 -1
- package/src/resources/extensions/gsd/state-reconciliation/drift/stale-render.ts +76 -8
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +111 -1
- package/src/resources/extensions/gsd/tests/commands-context.test.ts +26 -0
- package/src/resources/extensions/gsd/tests/commands-worktree-clean.test.ts +80 -0
- package/src/resources/extensions/gsd/tests/complete-slice.test.ts +11 -0
- package/src/resources/extensions/gsd/tests/complete-task-rollback-evidence.test.ts +48 -8
- package/src/resources/extensions/gsd/tests/complete-task.test.ts +75 -0
- package/src/resources/extensions/gsd/tests/dashboard-overlay.test.ts +55 -2
- package/src/resources/extensions/gsd/tests/dispatch-rule-coverage.test.ts +26 -1
- package/src/resources/extensions/gsd/tests/doctor-forensics-db-open-regression.test.ts +70 -2
- package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +107 -0
- package/src/resources/extensions/gsd/tests/exec-graceful-kill.test.ts +38 -0
- package/src/resources/extensions/gsd/tests/exec-tool.test.ts +45 -1
- package/src/resources/extensions/gsd/tests/forensics-error-filter.test.ts +88 -0
- package/src/resources/extensions/gsd/tests/guided-discuss-milestone-prompt-rendering.test.ts +42 -0
- package/src/resources/extensions/gsd/tests/health-widget.test.ts +268 -3
- package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +119 -1
- package/src/resources/extensions/gsd/tests/integration/queue-active-milestone-context-budget.test.ts +93 -0
- package/src/resources/extensions/gsd/tests/integration/quick-branch-lifecycle.test.ts +56 -9
- package/src/resources/extensions/gsd/tests/knowledge-cold-start.test.ts +14 -0
- package/src/resources/extensions/gsd/tests/memory-consolidation-scanner.test.ts +78 -0
- package/src/resources/extensions/gsd/tests/orchestrator-logs.test.ts +43 -1
- package/src/resources/extensions/gsd/tests/parallel-research-dispatch.test.ts +26 -0
- package/src/resources/extensions/gsd/tests/pipeline-variant-dispatch.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/prefs-wizard-coverage.test.ts +54 -1
- package/src/resources/extensions/gsd/tests/provider-errors.test.ts +195 -1
- package/src/resources/extensions/gsd/tests/read-uat-gate-verdict.test.ts +185 -0
- package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +87 -0
- package/src/resources/extensions/gsd/tests/state-reconciliation-drift.test.ts +76 -0
- package/src/resources/extensions/gsd/tests/tool-call-loop-guard.test.ts +68 -0
- package/src/resources/extensions/gsd/tests/tool-param-optionality.test.ts +26 -0
- package/src/resources/extensions/gsd/tests/unit-context-composer.test.ts +193 -14
- package/src/resources/extensions/gsd/tests/unmerged-milestone-guard.test.ts +25 -0
- package/src/resources/extensions/gsd/tests/validation-block-guard.test.ts +79 -0
- package/src/resources/extensions/gsd/tests/verify-artifact-tightened.test.ts +66 -0
- package/src/resources/extensions/gsd/tests/workspace-git-preflight.test.ts +151 -2
- package/src/resources/extensions/gsd/tools/complete-slice.ts +30 -3
- package/src/resources/extensions/gsd/tools/complete-task.ts +86 -16
- package/src/resources/extensions/gsd/tools/exec-tool.ts +7 -3
- package/src/resources/extensions/gsd/unit-context-composer.ts +33 -7
- package/src/resources/extensions/gsd/unit-registry.ts +25 -3
- package/src/resources/extensions/gsd/unmerged-milestone-guard.ts +41 -5
- package/src/resources/extensions/gsd/validation-block-guard.ts +13 -7
- package/src/resources/extensions/gsd/workspace-git-preflight.ts +31 -0
- /package/dist/web/standalone/.next/static/{BTKtGFF1Y-hvVJEGhBRo9 → SzEuqWX37DR9MEpEuQjP1}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{BTKtGFF1Y-hvVJEGhBRo9 → SzEuqWX37DR9MEpEuQjP1}/_ssgManifest.js +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
3da89e4dca62c288
|
|
@@ -285,8 +285,10 @@ export function buildPromptFromContext(context, toolContext = {}) {
|
|
|
285
285
|
? "- GSD workflow tools (gsd_exec, gsd_slice_complete, gsd_task_complete, gsd_plan_slice, gsd_save_gate_result, etc.) " +
|
|
286
286
|
`are MCP tools — call them as mcp__${toolContext.workflowMcpServerName}__<tool_name> ` +
|
|
287
287
|
`(e.g. mcp__${toolContext.workflowMcpServerName}__gsd_exec, mcp__${toolContext.workflowMcpServerName}__gsd_save_gate_result)\n` +
|
|
288
|
-
|
|
289
|
-
|
|
288
|
+
(toolContext.questionToolAvailable !== false
|
|
289
|
+
? `- Structured user input: call mcp__${toolContext.workflowMcpServerName}__ask_user_questions. ` +
|
|
290
|
+
"Do not call bare ask_user_questions. Do not call native AskUserQuestion.\n"
|
|
291
|
+
: "")
|
|
290
292
|
: "- GSD workflow MCP tools are unavailable in this Claude Code run.\n";
|
|
291
293
|
const toolSearchLine = toolContext.workflowMcpServerName
|
|
292
294
|
? "- ToolSearch is available only for Claude Code deferred workflow MCP hydration. " +
|
|
@@ -1413,6 +1415,12 @@ function workflowMcpServerNameFromAllowedTools(allowedTools) {
|
|
|
1413
1415
|
}
|
|
1414
1416
|
return undefined;
|
|
1415
1417
|
}
|
|
1418
|
+
function workflowQuestionToolAvailableFromAllowedTools(allowedTools, workflowMcpServerName, gsdPhase) {
|
|
1419
|
+
if (!workflowMcpServerName || !gsdPhase)
|
|
1420
|
+
return undefined;
|
|
1421
|
+
return Array.isArray(allowedTools)
|
|
1422
|
+
&& allowedTools.includes(`mcp__${workflowMcpServerName}__ask_user_questions`);
|
|
1423
|
+
}
|
|
1416
1424
|
function isRecord(value) {
|
|
1417
1425
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
1418
1426
|
}
|
|
@@ -1821,6 +1829,7 @@ async function pumpSdkMessages(model, context, options, stream) {
|
|
|
1821
1829
|
const prompt = buildPromptFromContext(context, {
|
|
1822
1830
|
workflowMcpServerName,
|
|
1823
1831
|
browserMcpServerName: browserMcpServerNameFromAllowedTools(sdkOpts.allowedTools),
|
|
1832
|
+
questionToolAvailable: workflowQuestionToolAvailableFromAllowedTools(sdkOpts.allowedTools, workflowMcpServerName, gsdPhase),
|
|
1824
1833
|
});
|
|
1825
1834
|
const queryPrompt = buildSdkQueryPrompt(context, prompt);
|
|
1826
1835
|
// Emit start with an empty partial
|
|
@@ -8,6 +8,64 @@ const ZERO_USAGE = {
|
|
|
8
8
|
totalTokens: 0,
|
|
9
9
|
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
10
10
|
};
|
|
11
|
+
const WINDOWS_CHILD_ENV_KEYS = new Set([
|
|
12
|
+
"ALLUSERSPROFILE",
|
|
13
|
+
"APPDATA",
|
|
14
|
+
"COMSPEC",
|
|
15
|
+
"COMMONPROGRAMFILES",
|
|
16
|
+
"COMMONPROGRAMFILES(X86)",
|
|
17
|
+
"FORCE_COLOR",
|
|
18
|
+
"HOME",
|
|
19
|
+
"HOMEDRIVE",
|
|
20
|
+
"HOMEPATH",
|
|
21
|
+
"LANG",
|
|
22
|
+
"LC_ALL",
|
|
23
|
+
"LOCALAPPDATA",
|
|
24
|
+
"NODE_EXTRA_CA_CERTS",
|
|
25
|
+
"NO_COLOR",
|
|
26
|
+
"NO_PROXY",
|
|
27
|
+
"PATHEXT",
|
|
28
|
+
"PATH",
|
|
29
|
+
"PROGRAMDATA",
|
|
30
|
+
"PROGRAMFILES",
|
|
31
|
+
"PROGRAMFILES(X86)",
|
|
32
|
+
"SSL_CERT_FILE",
|
|
33
|
+
"SYSTEMROOT",
|
|
34
|
+
"TEMP",
|
|
35
|
+
"TERM",
|
|
36
|
+
"TMP",
|
|
37
|
+
"TMPDIR",
|
|
38
|
+
"USER",
|
|
39
|
+
"USERNAME",
|
|
40
|
+
"USERPROFILE",
|
|
41
|
+
"WINDIR",
|
|
42
|
+
"XDG_CACHE_HOME",
|
|
43
|
+
"XDG_CONFIG_HOME",
|
|
44
|
+
"HTTP_PROXY",
|
|
45
|
+
"HTTPS_PROXY",
|
|
46
|
+
]);
|
|
47
|
+
const WINDOWS_CHILD_ENV_PREFIXES = [
|
|
48
|
+
"AGY_",
|
|
49
|
+
"ANTIGRAVITY_",
|
|
50
|
+
"CLOUDSDK_",
|
|
51
|
+
"GEMINI_",
|
|
52
|
+
"GOOGLE_",
|
|
53
|
+
];
|
|
54
|
+
export function buildGoogleCliChildEnv(env = process.env, platform = process.platform) {
|
|
55
|
+
if (platform !== "win32")
|
|
56
|
+
return env;
|
|
57
|
+
const childEnv = {};
|
|
58
|
+
for (const [key, value] of Object.entries(env)) {
|
|
59
|
+
if (typeof value !== "string")
|
|
60
|
+
continue;
|
|
61
|
+
const upperKey = key.toUpperCase();
|
|
62
|
+
if (WINDOWS_CHILD_ENV_KEYS.has(upperKey) ||
|
|
63
|
+
WINDOWS_CHILD_ENV_PREFIXES.some((prefix) => upperKey.startsWith(prefix))) {
|
|
64
|
+
childEnv[key] = value;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return childEnv;
|
|
68
|
+
}
|
|
11
69
|
function textBlocks(content) {
|
|
12
70
|
return content
|
|
13
71
|
.map((block) => block.type === "text" ? block.text : `[${block.type} omitted]`)
|
|
@@ -87,16 +145,16 @@ function extractGeminiJsonResponse(raw) {
|
|
|
87
145
|
function commandForProvider(provider) {
|
|
88
146
|
return provider === "google-gemini-cli" ? "gemini" : "agy";
|
|
89
147
|
}
|
|
90
|
-
function argsForProvider(provider,
|
|
148
|
+
function argsForProvider(provider, modelId, prompt) {
|
|
91
149
|
if (provider === "google-gemini-cli") {
|
|
92
|
-
const args = ["-p", prompt, "--output-format", "json"];
|
|
93
|
-
if (
|
|
94
|
-
args.unshift("-m",
|
|
150
|
+
const args = prompt === undefined ? ["--output-format", "json"] : ["-p", prompt, "--output-format", "json"];
|
|
151
|
+
if (modelId !== "default")
|
|
152
|
+
args.unshift("-m", modelId);
|
|
95
153
|
return args;
|
|
96
154
|
}
|
|
97
|
-
const args = ["-p", prompt];
|
|
98
|
-
if (
|
|
99
|
-
args.unshift("-m",
|
|
155
|
+
const args = prompt === undefined ? [] : ["-p", prompt];
|
|
156
|
+
if (modelId !== "default")
|
|
157
|
+
args.unshift("-m", modelId);
|
|
100
158
|
return args;
|
|
101
159
|
}
|
|
102
160
|
export function buildGoogleCliSpawnInvocation(command, args, platform = process.platform) {
|
|
@@ -105,13 +163,20 @@ export function buildGoogleCliSpawnInvocation(command, args, platform = process.
|
|
|
105
163
|
}
|
|
106
164
|
return { command, args };
|
|
107
165
|
}
|
|
108
|
-
function
|
|
166
|
+
export function buildGoogleCliRunPlan(provider, modelId, prompt, platform = process.platform) {
|
|
167
|
+
const pipePrompt = platform === "win32";
|
|
168
|
+
const args = argsForProvider(provider, modelId, pipePrompt ? undefined : prompt);
|
|
169
|
+
return {
|
|
170
|
+
...buildGoogleCliSpawnInvocation(commandForProvider(provider), args, platform),
|
|
171
|
+
...(pipePrompt ? { stdin: prompt } : {}),
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
function runCli(plan, options) {
|
|
109
175
|
return new Promise((resolve, reject) => {
|
|
110
|
-
const
|
|
111
|
-
const child = spawn(invocation.command, invocation.args, {
|
|
176
|
+
const child = spawn(plan.command, plan.args, {
|
|
112
177
|
cwd: options?.cwd || process.cwd(),
|
|
113
|
-
env:
|
|
114
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
178
|
+
env: buildGoogleCliChildEnv(),
|
|
179
|
+
stdio: [plan.stdin === undefined ? "ignore" : "pipe", "pipe", "pipe"],
|
|
115
180
|
});
|
|
116
181
|
let stdout = "";
|
|
117
182
|
let stderr = "";
|
|
@@ -132,6 +197,10 @@ function runCli(command, args, options) {
|
|
|
132
197
|
return;
|
|
133
198
|
}
|
|
134
199
|
options?.signal?.addEventListener("abort", onAbort);
|
|
200
|
+
if (plan.stdin !== undefined) {
|
|
201
|
+
child.stdin?.on("error", () => { });
|
|
202
|
+
child.stdin?.end(plan.stdin);
|
|
203
|
+
}
|
|
135
204
|
child.stdout.setEncoding("utf8");
|
|
136
205
|
child.stderr.setEncoding("utf8");
|
|
137
206
|
child.stdout.on("data", (chunk) => {
|
|
@@ -185,9 +254,7 @@ export function streamViaGoogleCli(model, context, options) {
|
|
|
185
254
|
queueMicrotask(async () => {
|
|
186
255
|
try {
|
|
187
256
|
const prompt = buildGoogleCliPrompt(context);
|
|
188
|
-
const
|
|
189
|
-
const args = argsForProvider(provider, model, prompt);
|
|
190
|
-
const result = await runCli(command, args, options);
|
|
257
|
+
const result = await runCli(buildGoogleCliRunPlan(provider, model.id, prompt), options);
|
|
191
258
|
if (result.code !== 0) {
|
|
192
259
|
const detail = (result.stderr || result.stdout || `CLI exited with code ${result.code}`).trim();
|
|
193
260
|
throw new Error(formatGoogleCliError(detail, provider));
|
|
@@ -396,14 +396,17 @@ export class AutoOrchestrator {
|
|
|
396
396
|
}
|
|
397
397
|
}
|
|
398
398
|
// ── UokGateAdapter (folded) ──────────────────────────────────────────────
|
|
399
|
-
|
|
399
|
+
resolveUokGateContext() {
|
|
400
400
|
const activeBasePath = this.getLiveDispatchBasePath();
|
|
401
401
|
const prefs = loadEffectiveGSDPreferencesWithRegistry(this.ctx.modelRegistry, activeBasePath, resolveProfileAnchorProvider(this.ctx.model?.provider, this.s.autoModeStartModel?.provider), this.s.autoModeStartModel
|
|
402
402
|
? `${this.s.autoModeStartModel.provider}/${this.s.autoModeStartModel.id}`
|
|
403
403
|
: undefined)?.preferences;
|
|
404
|
-
|
|
405
|
-
|
|
404
|
+
return { activeBasePath, uokFlags: resolveUokFlags(prefs) };
|
|
405
|
+
}
|
|
406
|
+
async emitUokGate(input) {
|
|
407
|
+
if (!input.uokFlags.gates)
|
|
406
408
|
return;
|
|
409
|
+
const activeBasePath = input.activeBasePath;
|
|
407
410
|
const milestoneId = input.milestoneId ?? this.s.currentMilestoneId ?? undefined;
|
|
408
411
|
try {
|
|
409
412
|
const { UokGateRunner } = await import("../uok/gate-runner.js");
|
|
@@ -760,9 +763,11 @@ export class AutoOrchestrator {
|
|
|
760
763
|
const stopAdvanceTimer = debugTime("orchestrator-advance");
|
|
761
764
|
try {
|
|
762
765
|
this.ensureLockOwnership();
|
|
766
|
+
const uokGateContext = this.resolveUokGateContext();
|
|
763
767
|
const staleMsg = this.checkResourcesStale();
|
|
764
768
|
if (staleMsg) {
|
|
765
769
|
await this.emitUokGate({
|
|
770
|
+
...uokGateContext,
|
|
766
771
|
gateId: "resource-version-guard",
|
|
767
772
|
gateType: "policy",
|
|
768
773
|
outcome: "fail",
|
|
@@ -776,6 +781,7 @@ export class AutoOrchestrator {
|
|
|
776
781
|
return blocked;
|
|
777
782
|
}
|
|
778
783
|
await this.emitUokGate({
|
|
784
|
+
...uokGateContext,
|
|
779
785
|
gateId: "resource-version-guard",
|
|
780
786
|
gateType: "policy",
|
|
781
787
|
outcome: "pass",
|
|
@@ -785,6 +791,7 @@ export class AutoOrchestrator {
|
|
|
785
791
|
const gate = await this.preAdvanceGate();
|
|
786
792
|
if (gate.kind === "fail") {
|
|
787
793
|
await this.emitUokGate({
|
|
794
|
+
...uokGateContext,
|
|
788
795
|
gateId: "pre-dispatch-health-gate",
|
|
789
796
|
gateType: "execution",
|
|
790
797
|
outcome: "manual-attention",
|
|
@@ -803,6 +810,7 @@ export class AutoOrchestrator {
|
|
|
803
810
|
}
|
|
804
811
|
if (gate.kind === "threw") {
|
|
805
812
|
await this.emitUokGate({
|
|
813
|
+
...uokGateContext,
|
|
806
814
|
gateId: "pre-dispatch-health-gate",
|
|
807
815
|
gateType: "execution",
|
|
808
816
|
outcome: "manual-attention",
|
|
@@ -814,6 +822,7 @@ export class AutoOrchestrator {
|
|
|
814
822
|
}
|
|
815
823
|
else {
|
|
816
824
|
await this.emitUokGate({
|
|
825
|
+
...uokGateContext,
|
|
817
826
|
gateId: "pre-dispatch-health-gate",
|
|
818
827
|
gateType: "execution",
|
|
819
828
|
outcome: "pass",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// File Purpose: Declarative auto-mode dispatch rules and dispatch resolver.
|
|
3
3
|
import { loadFile, extractUatType, loadActiveOverrides } from "./files.js";
|
|
4
4
|
import { getUatBrowserToolSupportError } from "./uat-policy.js";
|
|
5
|
-
import { isDbAvailable, getMilestoneSlices, getMilestoneSliceSummaries, getClosedSliceIds, getPendingGatesForTurn, markPendingGatesOmittedForTurn, getMilestone, insertArtifact, insertAssessment, setSliceSketchFlag, transaction, getAssessment, } from "./gsd-db.js";
|
|
5
|
+
import { isDbAvailable, getMilestoneSlices, getMilestoneSliceSummaries, getClosedSliceIds, getPendingGatesForTurn, markPendingGatesOmittedForTurn, getMilestone, insertArtifact, insertAssessment, setSliceSketchFlag, transaction, getAssessment, getSliceRunUatAssessment, } from "./gsd-db.js";
|
|
6
6
|
import { isClosedStatus } from "./status-guards.js";
|
|
7
7
|
import { extractVerdict, isAcceptableUatVerdict } from "./verdict-parser.js";
|
|
8
8
|
import { gsdRoot, resolveGsdPathContract, resolveMilestoneFile, resolveMilestonePath, resolveSliceFile, resolveTaskFile, relTaskFile, relSliceFile, relMilestoneFile, buildTaskFileName, gsdProjectionRoot, } from "./paths.js";
|
|
@@ -155,6 +155,18 @@ export async function readUatGateVerdict(basePath, mid, sliceId) {
|
|
|
155
155
|
return { verdict: legacyUatVerdict, uatType };
|
|
156
156
|
}
|
|
157
157
|
}
|
|
158
|
+
// ADR-017 DB fallback: when the ASSESSMENT markdown is missing or orphaned
|
|
159
|
+
// from its canonical path (e.g. after a milestone artifact-layout migration
|
|
160
|
+
// moves slice artifacts from `phases/…` to `milestones/…`), consult the
|
|
161
|
+
// authoritative assessments table by (mid, slice) identity instead of path.
|
|
162
|
+
// `gsd_uat_result_save` always writes this row, so it is the source of truth.
|
|
163
|
+
const runUatAssessment = getSliceRunUatAssessment(mid, sliceId);
|
|
164
|
+
if (runUatAssessment?.status) {
|
|
165
|
+
return {
|
|
166
|
+
verdict: runUatAssessment.status,
|
|
167
|
+
uatType: uatType ?? extractUatType(runUatAssessment.fullContent),
|
|
168
|
+
};
|
|
169
|
+
}
|
|
158
170
|
return null;
|
|
159
171
|
}
|
|
160
172
|
/**
|
|
@@ -919,16 +931,13 @@ export const DISPATCH_RULES = [
|
|
|
919
931
|
const dbSlices = getMilestoneSliceSummaries(mid);
|
|
920
932
|
if (dbSlices.length === 0)
|
|
921
933
|
return null;
|
|
922
|
-
// Find slices that need research (no RESEARCH file, dependencies done)
|
|
923
|
-
|
|
924
|
-
|
|
934
|
+
// Find slices that need research (no RESEARCH file, dependencies done).
|
|
935
|
+
// Milestone research informs slice research; it does not satisfy the
|
|
936
|
+
// per-slice RESEARCH artifact contract.
|
|
925
937
|
const researchReadySlices = [];
|
|
926
938
|
for (const slice of dbSlices) {
|
|
927
939
|
if (slice.done)
|
|
928
940
|
continue;
|
|
929
|
-
// Skip S01 when milestone research exists
|
|
930
|
-
if (milestoneResearchFile && slice.id === "S01")
|
|
931
|
-
continue;
|
|
932
941
|
// Skip if already has research
|
|
933
942
|
if (resolveExistingExpectedArtifact("research-slice", `${mid}/${slice.id}`, basePath))
|
|
934
943
|
continue;
|
|
@@ -957,7 +966,7 @@ export const DISPATCH_RULES = [
|
|
|
957
966
|
},
|
|
958
967
|
},
|
|
959
968
|
{
|
|
960
|
-
name: "planning (no research
|
|
969
|
+
name: "planning (no research) → research-slice",
|
|
961
970
|
match: async ({ state, mid, midTitle, basePath, prefs }) => {
|
|
962
971
|
if (state.phase !== "planning")
|
|
963
972
|
return null;
|
|
@@ -975,12 +984,6 @@ export const DISPATCH_RULES = [
|
|
|
975
984
|
resolveSliceFile(basePath, mid, sid, "RESEARCH");
|
|
976
985
|
if (researchFile)
|
|
977
986
|
return null; // has research, fall through
|
|
978
|
-
// Skip slice research for S01 when milestone research already exists —
|
|
979
|
-
// the milestone research already covers the same ground for the first slice.
|
|
980
|
-
const milestoneResearchFile = resolveExistingExpectedArtifact("research-milestone", mid, basePath) ??
|
|
981
|
-
resolveMilestoneFile(basePath, mid, "RESEARCH");
|
|
982
|
-
if (milestoneResearchFile && sid === "S01")
|
|
983
|
-
return null; // fall through to plan-slice
|
|
984
987
|
return {
|
|
985
988
|
action: "dispatch",
|
|
986
989
|
unitType: "research-slice",
|
|
@@ -14,7 +14,7 @@ import { resolveMilestoneFile, resolveSliceFile, resolveSlicePath, resolveTasksD
|
|
|
14
14
|
import { resolveInlineLevel, loadEffectiveGSDPreferences } from "./preferences.js";
|
|
15
15
|
import { isContextModeEnabled } from "./preferences-types.js";
|
|
16
16
|
import { parseRoadmap } from "./parsers-legacy.js";
|
|
17
|
-
import { join } from "node:path";
|
|
17
|
+
import { join, relative } from "node:path";
|
|
18
18
|
import { existsSync } from "node:fs";
|
|
19
19
|
import { computeBudgets, resolveExecutorContextWindow, truncateAtSectionBoundary } from "./context-budget.js";
|
|
20
20
|
import { getPendingGatesForTurn } from "./gsd-db.js";
|
|
@@ -23,6 +23,7 @@ import { formatDecisionsCompact, formatRequirementsCompact } from "./structured-
|
|
|
23
23
|
import { readPhaseAnchor, formatAnchorForPrompt } from "./phase-anchor.js";
|
|
24
24
|
import { composeContextModeInstructions, composeContractedUnitContext, composeInlinedContext, composeToolSurfaceInstructions, composeUnitContext, } from "./unit-context-composer.js";
|
|
25
25
|
import { resolveManifest } from "./unit-context-manifest.js";
|
|
26
|
+
import { resolveExpectedArtifactPath } from "./auto-artifact-paths.js";
|
|
26
27
|
import { compileUnitContextContract } from "./tool-contract.js";
|
|
27
28
|
import { readCompactionSnapshot } from "./compaction-snapshot.js";
|
|
28
29
|
import { logWarning } from "./workflow-logger.js";
|
|
@@ -247,14 +248,37 @@ function requireComposedArtifactBlock(blocks, unitType, key) {
|
|
|
247
248
|
return block.body;
|
|
248
249
|
}
|
|
249
250
|
function renderExecuteTaskOnDemandContext(base, mid, sid, artifacts) {
|
|
250
|
-
if (!artifacts.includes("slice-research"))
|
|
251
|
-
return "";
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
251
|
+
if (!artifacts.includes("slice-research")) {
|
|
252
|
+
return { text: "", skipReason: "not declared by contract" };
|
|
253
|
+
}
|
|
254
|
+
// Mirror dispatch's dual-resolution logic: worktree projection path first,
|
|
255
|
+
// then the authoritative project-root path via resolveExpectedArtifactPath.
|
|
256
|
+
// In worktree layouts the RESEARCH file may live under the project-root .gsd
|
|
257
|
+
// and not have been copied into the worktree projection, so resolveSliceFile
|
|
258
|
+
// (which looks only at gsdProjectionRoot) would miss it while dispatch would
|
|
259
|
+
// still treat research as satisfied.
|
|
260
|
+
const projectedFile = resolveSliceFile(base, mid, sid, "RESEARCH");
|
|
261
|
+
const researchFile = projectedFile ?? (() => {
|
|
262
|
+
const p = resolveExpectedArtifactPath("research-slice", `${mid}/${sid}`, base);
|
|
263
|
+
return p && existsSync(p) ? p : null;
|
|
264
|
+
})();
|
|
265
|
+
if (!researchFile) {
|
|
266
|
+
return { text: "", skipReason: "missing" };
|
|
267
|
+
}
|
|
268
|
+
// Use the layout-aware relative path when the file is in the worktree
|
|
269
|
+
// projection (relSliceFile), or fall back to node:path relative() when the
|
|
270
|
+
// file was only found via the project-root resolution path.
|
|
271
|
+
const researchPath = projectedFile
|
|
272
|
+
? relSliceFile(base, mid, sid, "RESEARCH")
|
|
273
|
+
: relative(base, researchFile);
|
|
274
|
+
return {
|
|
275
|
+
text: [
|
|
276
|
+
"## On-demand Context",
|
|
277
|
+
"",
|
|
278
|
+
`Slice research is available at \`${researchPath}\`. Read it only if the inlined task plan, slice plan excerpt, and carry-forward context do not explain a required implementation detail.`,
|
|
279
|
+
].join("\n"),
|
|
280
|
+
skipReason: null,
|
|
281
|
+
};
|
|
258
282
|
}
|
|
259
283
|
// ─── Executor Constraints ─────────────────────────────────────────────────────
|
|
260
284
|
/**
|
|
@@ -1459,7 +1483,13 @@ export async function buildDiscussMilestonePrompt(mid, midTitle, base, structure
|
|
|
1459
1483
|
const draftPath = resolveMilestoneFile(base, mid, "CONTEXT-DRAFT");
|
|
1460
1484
|
const draftContent = draftPath ? await loadFile(draftPath) : null;
|
|
1461
1485
|
if (includeDraftSeed && draftContent) {
|
|
1462
|
-
|
|
1486
|
+
const draftRelPath = relMilestoneFile(base, mid, "CONTEXT-DRAFT");
|
|
1487
|
+
const draftSeed = `### Prior Discussion Draft\nSource: \`${draftRelPath}\`\n\n${draftContent.trim()}`;
|
|
1488
|
+
const cappedDraftSeed = capPreamble(draftSeed);
|
|
1489
|
+
const truncationNote = cappedDraftSeed !== draftSeed
|
|
1490
|
+
? `\n\n_(Draft seed truncated; read the full draft at \`${draftRelPath}\` if needed.)_`
|
|
1491
|
+
: "";
|
|
1492
|
+
return `${promptWithContextMode}\n\n## Prior Discussion (Draft Seed)\n\nThe following draft was captured from a prior multi-milestone discussion. Use it as seed material — the user has already provided this context. Start with a brief reflection on what the draft covers, then probe for any gaps or open questions before writing the full CONTEXT.md.\n\n${cappedDraftSeed}${truncationNote}`;
|
|
1463
1493
|
}
|
|
1464
1494
|
return promptWithContextMode;
|
|
1465
1495
|
}
|
|
@@ -2405,8 +2435,9 @@ export async function buildExecuteTaskPrompt(mid, sid, sTitle, tid, tTitle, base
|
|
|
2405
2435
|
const slicePlanExcerpt = requireComposedArtifactBlock(contractedContext.blocks, "execute-task", "slice-plan");
|
|
2406
2436
|
const contractedCarryForward = requireComposedArtifactBlock(contractedContext.blocks, "execute-task", "prior-task-summaries");
|
|
2407
2437
|
const contractedTemplates = requireComposedArtifactBlock(contractedContext.blocks, "execute-task", "templates");
|
|
2408
|
-
const
|
|
2409
|
-
|
|
2438
|
+
const onDemandResult = renderExecuteTaskOnDemandContext(base, mid, sid, contractedContext.onDemand);
|
|
2439
|
+
const onDemandContext = onDemandResult.text;
|
|
2440
|
+
trackPromptContext(contextTelemetry, "slice-research", onDemandContext ? "on-demand" : "skipped", onDemandContext, onDemandContext ? undefined : onDemandResult.skipReason ?? undefined);
|
|
2410
2441
|
const prompt = loadPrompt("execute-task", {
|
|
2411
2442
|
overridesSection,
|
|
2412
2443
|
runtimeContext,
|
|
@@ -452,14 +452,21 @@ export function verifyExpectedArtifact(unitType, unitId, base) {
|
|
|
452
452
|
// discuss-milestone unit with CONTEXT at phases/NN-slug/ in the project root
|
|
453
453
|
// but not in the worktree resolves to null → "resolveExpectedArtifactPath
|
|
454
454
|
// returned null" → finalize-retry loop (#852).
|
|
455
|
-
|
|
455
|
+
//
|
|
456
|
+
// #870: the fallback must fire whenever the artifact is missing at
|
|
457
|
+
// `artifactBase`, NOT only when `artifactBase !== base`. When the real call
|
|
458
|
+
// site passes the worktree path as `base` (auto-post-unit.ts:1726 uses
|
|
459
|
+
// `s.currentUnit.workspaceRoot ?? s.basePath`), resolveCanonicalMilestoneRoot
|
|
460
|
+
// round-trips the worktree path back to itself, so `artifactBase === base`
|
|
461
|
+
// while still being a worktree path that lacks the projected artifact. The
|
|
462
|
+
// old `if (artifactBase !== base)` guard skipped the fallback in exactly that
|
|
463
|
+
// case, stranding CONTEXT at the project root and producing a stuck-loop.
|
|
464
|
+
if (!absPath || !existsSync(absPath)) {
|
|
456
465
|
const projectRoot = resolve(resolveWorktreeProjectRoot(artifactBase));
|
|
457
466
|
if (projectRoot && projectRoot !== artifactBase) {
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
absPath = projectPath;
|
|
462
|
-
}
|
|
467
|
+
const projectPath = resolveExpectedArtifactPath(unitType, unitId, projectRoot);
|
|
468
|
+
if (projectPath && existsSync(projectPath)) {
|
|
469
|
+
absPath = projectPath;
|
|
463
470
|
}
|
|
464
471
|
}
|
|
465
472
|
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
// gsd-pi + src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts - Handles provider and agent-end recovery for GSD auto-mode.
|
|
2
|
+
import { mkdirSync, readdirSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
2
4
|
import { logWarning } from "../workflow-logger.js";
|
|
3
5
|
import { checkDeepProjectSetupAfterTurn, checkAutoStartAfterDiscuss, maybeHandleReadyPhraseWithoutFiles, maybeHandleEmptyIntentTurn, resetEmptyTurnCounter, } from "../guided-flow.js";
|
|
4
|
-
import { clearPathCache } from "../paths.js";
|
|
6
|
+
import { clearPathCache, gsdRoot } from "../paths.js";
|
|
5
7
|
import { getAutoDashboardData, getAutoModeStartModel, isAutoActive, isAutoCompletionStopInProgress, pauseAuto, setCurrentDispatchedModelId, setCurrentUnitModelForRecovery, } from "../auto.js";
|
|
6
8
|
import { getNextFallbackModel, resolveModelWithFallbacksForUnit } from "../preferences.js";
|
|
7
9
|
import { pauseAutoForProviderError } from "../provider-error-pause.js";
|
|
@@ -11,7 +13,7 @@ import { resolveModelId } from "../auto-model-selection.js";
|
|
|
11
13
|
import { resolveProjectRoot } from "../worktree.js";
|
|
12
14
|
import { clearDiscussionFlowState } from "./write-gate.js";
|
|
13
15
|
import { scheduleFallbackContinuation } from "./fallback-continuation.js";
|
|
14
|
-
import { clearGuidedUnitContext } from "../guided-unit-context.js";
|
|
16
|
+
import { clearGuidedUnitContext, getGuidedUnitContext } from "../guided-unit-context.js";
|
|
15
17
|
import { resumeAutoAfterProviderDelay } from "./provider-error-resume.js";
|
|
16
18
|
import { classifyError, createRetryState, resetRetryState, isTransient, } from "../error-classifier.js";
|
|
17
19
|
import { blockModel, blockModelUntil, isModelBlocked, isModelTemporarilyUnavailable } from "../blocked-models.js";
|
|
@@ -78,10 +80,15 @@ export function isUserInitiatedAbortMessage(message) {
|
|
|
78
80
|
return false;
|
|
79
81
|
return /\b(?:claude code process aborted by user|request aborted by user|process aborted by user)\b/i.test(message);
|
|
80
82
|
}
|
|
81
|
-
export function shouldDeferTransientErrorToCoreRetry(cls, rawErrorMsg) {
|
|
83
|
+
export function shouldDeferTransientErrorToCoreRetry(cls, rawErrorMsg, deferCheckMsg = rawErrorMsg) {
|
|
82
84
|
if (!isTransient(cls) || cls.kind === "rate-limit")
|
|
83
85
|
return false;
|
|
84
|
-
|
|
86
|
+
// Empty rawErrorMsg means the SDK terminated the session without providing an
|
|
87
|
+
// error string — core is done, not mid-retry. GSD must schedule its own
|
|
88
|
+
// retry rather than silently deferring to a core that has already exited.
|
|
89
|
+
if (!rawErrorMsg)
|
|
90
|
+
return false;
|
|
91
|
+
return !/retry failed after \d+ attempts:/i.test(deferCheckMsg);
|
|
85
92
|
}
|
|
86
93
|
/** Try configured fallbacks, then the auto-mode start model. Returns true if switched. */
|
|
87
94
|
async function tryProviderModelFallback(params) {
|
|
@@ -277,6 +284,86 @@ export function suppressTerminalDeletedWorktreeMessageEnd(event) {
|
|
|
277
284
|
logWarning("bootstrap", `Suppressing stale deleted-worktree provider error during terminal completion reroot: ${displayMsg || rawErrorMsg}`);
|
|
278
285
|
return true;
|
|
279
286
|
}
|
|
287
|
+
function modelLabel(ctx) {
|
|
288
|
+
const provider = ctx.model?.provider;
|
|
289
|
+
const id = ctx.model?.id;
|
|
290
|
+
return provider && id ? `${provider}/${id}` : "unknown model";
|
|
291
|
+
}
|
|
292
|
+
function isFatalManualGuidedTerminalFailure(lastMsg) {
|
|
293
|
+
if (!isObjectRecord(lastMsg) || !("stopReason" in lastMsg))
|
|
294
|
+
return false;
|
|
295
|
+
if (lastMsg.stopReason === "error") {
|
|
296
|
+
const rawErrorMsg = ("errorMessage" in lastMsg && lastMsg.errorMessage) ? String(lastMsg.errorMessage) : "";
|
|
297
|
+
if (isUserInitiatedAbortMessage(rawErrorMsg))
|
|
298
|
+
return false;
|
|
299
|
+
return true;
|
|
300
|
+
}
|
|
301
|
+
if (lastMsg.stopReason !== "aborted")
|
|
302
|
+
return false;
|
|
303
|
+
const content = "content" in lastMsg ? lastMsg.content : undefined;
|
|
304
|
+
const hasErrorMessage = "errorMessage" in lastMsg && !!lastMsg.errorMessage;
|
|
305
|
+
return hasErrorMessage || !_hasEmptyAgentEndContent(content);
|
|
306
|
+
}
|
|
307
|
+
function terminalFailureDetail(lastMsg) {
|
|
308
|
+
if (!isObjectRecord(lastMsg))
|
|
309
|
+
return "Provider turn ended with an unknown terminal error.";
|
|
310
|
+
const rawErrorMsg = ("errorMessage" in lastMsg && lastMsg.errorMessage) ? String(lastMsg.errorMessage) : "";
|
|
311
|
+
const content = "content" in lastMsg ? lastMsg.content : undefined;
|
|
312
|
+
const displayMsg = resolveAgentEndErrorDisplay(rawErrorMsg, content).replace(/\s+/g, " ").trim();
|
|
313
|
+
if (displayMsg)
|
|
314
|
+
return displayMsg.length > 300 ? `${displayMsg.slice(0, 300)}...` : displayMsg;
|
|
315
|
+
return lastMsg.stopReason === "aborted"
|
|
316
|
+
? "Provider turn aborted with error context."
|
|
317
|
+
: "Provider stream ended with stopReason=error.";
|
|
318
|
+
}
|
|
319
|
+
function nextManualActivitySequence(activityDir) {
|
|
320
|
+
let maxSeq = 0;
|
|
321
|
+
try {
|
|
322
|
+
for (const file of readdirSync(activityDir)) {
|
|
323
|
+
const match = /^(\d+)-/.exec(file);
|
|
324
|
+
if (match)
|
|
325
|
+
maxSeq = Math.max(maxSeq, Number.parseInt(match[1], 10));
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
catch {
|
|
329
|
+
return "001";
|
|
330
|
+
}
|
|
331
|
+
return String(maxSeq + 1).padStart(3, "0");
|
|
332
|
+
}
|
|
333
|
+
function writeManualGuidedTerminalErrorActivity(basePath, unitType, model, detail, stopReason) {
|
|
334
|
+
const activityDir = join(gsdRoot(basePath), "activity");
|
|
335
|
+
mkdirSync(activityDir, { recursive: true });
|
|
336
|
+
const seq = nextManualActivitySequence(activityDir);
|
|
337
|
+
const safeUnitType = unitType.replace(/[^a-z0-9_.-]+/gi, "-");
|
|
338
|
+
const markerPath = join(activityDir, `${seq}-${safeUnitType}-manual-terminal-provider-error.jsonl`);
|
|
339
|
+
const message = `Manual guided ${unitType} turn ended with provider ${String(stopReason)} on ${model}: ${detail}`;
|
|
340
|
+
writeFileSync(markerPath, JSON.stringify({
|
|
341
|
+
type: "message",
|
|
342
|
+
message: {
|
|
343
|
+
role: "toolResult",
|
|
344
|
+
toolCallId: "manual-guided-terminal-provider-error",
|
|
345
|
+
toolName: "provider",
|
|
346
|
+
isError: true,
|
|
347
|
+
content: [{ type: "text", text: message }],
|
|
348
|
+
},
|
|
349
|
+
}) + "\n", "utf-8");
|
|
350
|
+
}
|
|
351
|
+
function observeManualDiscussTerminalError(ctx, lastMsg, guidedUnit) {
|
|
352
|
+
if (!guidedUnit?.unitType.startsWith("discuss-"))
|
|
353
|
+
return;
|
|
354
|
+
if (!isFatalManualGuidedTerminalFailure(lastMsg))
|
|
355
|
+
return;
|
|
356
|
+
const model = modelLabel(ctx);
|
|
357
|
+
const detail = terminalFailureDetail(lastMsg);
|
|
358
|
+
ctx.ui.notify(`Manual /gsd discuss ${guidedUnit.unitType} ended with a provider error on ${model}: ${detail}`, "warning");
|
|
359
|
+
try {
|
|
360
|
+
writeManualGuidedTerminalErrorActivity(guidedUnit.basePath, guidedUnit.unitType, model, detail, isObjectRecord(lastMsg) ? lastMsg.stopReason : "unknown");
|
|
361
|
+
}
|
|
362
|
+
catch (err) {
|
|
363
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
364
|
+
logWarning("bootstrap", `Failed to write manual guided terminal-error activity marker: ${message}`);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
280
367
|
async function pauseTransientWithBackoff(cls, pi, ctx, errorDetail, isRateLimit) {
|
|
281
368
|
retryState.consecutiveTransientCount += 1;
|
|
282
369
|
const baseRetryAfterMs = "retryAfterMs" in cls ? cls.retryAfterMs : 15_000;
|
|
@@ -315,7 +402,9 @@ export async function handleAgentEnd(pi, event, ctx) {
|
|
|
315
402
|
// rejected" loop even though the files are on disk.
|
|
316
403
|
clearPathCache();
|
|
317
404
|
const basePath = resolveAgentEndBasePath();
|
|
318
|
-
|
|
405
|
+
const lastMsg = event.messages[event.messages.length - 1];
|
|
406
|
+
const guidedUnit = basePath ? getGuidedUnitContext(basePath) ?? getGuidedUnitContext() : getGuidedUnitContext();
|
|
407
|
+
clearGuidedUnitContext(guidedUnit?.basePath ?? basePath);
|
|
319
408
|
try {
|
|
320
409
|
if (await checkDeepProjectSetupAfterTurn(event, ctx, basePath)) {
|
|
321
410
|
return;
|
|
@@ -342,12 +431,13 @@ export async function handleAgentEnd(pi, event, ctx) {
|
|
|
342
431
|
// discussions (where isAutoActive may be false) still get recovered.
|
|
343
432
|
if (maybeHandleEmptyIntentTurn(event, isAutoActive(), basePath))
|
|
344
433
|
return;
|
|
345
|
-
if (!isAutoActive())
|
|
434
|
+
if (!isAutoActive()) {
|
|
435
|
+
observeManualDiscussTerminalError(ctx, lastMsg, guidedUnit);
|
|
346
436
|
return;
|
|
437
|
+
}
|
|
347
438
|
if (shouldIgnoreAgentEndForActiveUnit(event)) {
|
|
348
439
|
return;
|
|
349
440
|
}
|
|
350
|
-
const lastMsg = event.messages[event.messages.length - 1];
|
|
351
441
|
if (isSessionSwitchInFlight()) {
|
|
352
442
|
_handleSessionSwitchAgentEnd(lastMsg, resolveAgentEndCancelled);
|
|
353
443
|
return;
|
|
@@ -435,9 +525,9 @@ export async function handleAgentEnd(pi, event, ctx) {
|
|
|
435
525
|
});
|
|
436
526
|
return;
|
|
437
527
|
}
|
|
438
|
-
// #3588: When errorMessage is uninformative, extract the real error
|
|
439
|
-
//
|
|
440
|
-
//
|
|
528
|
+
// #3588/#956: When errorMessage is uninformative, extract the real error
|
|
529
|
+
// from assistant text. Prefer rawErrorMsg for classification to avoid
|
|
530
|
+
// prose false-positives, but use display text when rawErrorMsg is empty.
|
|
441
531
|
const displayMsg = resolveAgentEndErrorDisplay(rawErrorMsg, "content" in lastMsg ? lastMsg.content : undefined);
|
|
442
532
|
if (isAutoCompletionStopInProgress() &&
|
|
443
533
|
isTerminalDeletedWorktreeProviderError(`${rawErrorMsg}\n${displayMsg}`)) {
|
|
@@ -447,8 +537,8 @@ export async function handleAgentEnd(pi, event, ctx) {
|
|
|
447
537
|
}
|
|
448
538
|
const errorDetail = displayMsg ? `: ${displayMsg}` : "";
|
|
449
539
|
const explicitRetryAfterMs = ("retryAfterMs" in lastMsg && typeof lastMsg.retryAfterMs === "number") ? lastMsg.retryAfterMs : undefined;
|
|
450
|
-
// ── 1. Classify
|
|
451
|
-
const cls = classifyError(rawErrorMsg, explicitRetryAfterMs);
|
|
540
|
+
// ── 1. Classify, preserving non-empty errorMessage precedence ──────
|
|
541
|
+
const cls = classifyError(rawErrorMsg || displayMsg, explicitRetryAfterMs);
|
|
452
542
|
// ── 1a. Unsupported-model: provider rejected this model for the current
|
|
453
543
|
// account/plan at request time (#4513). Persist a block so the
|
|
454
544
|
// same dead model isn't reselected on the next /gsd auto restart,
|
|
@@ -517,7 +607,7 @@ export async function handleAgentEnd(pi, event, ctx) {
|
|
|
517
607
|
// Core retries transient failures in-session after this handler.
|
|
518
608
|
// Keep that behavior for non-rate-limit classes to avoid pause/retry races,
|
|
519
609
|
// but let rate-limit continue into model fallback logic below (#4373).
|
|
520
|
-
if (shouldDeferTransientErrorToCoreRetry(cls, rawErrorMsg)) {
|
|
610
|
+
if (shouldDeferTransientErrorToCoreRetry(cls, rawErrorMsg, rawErrorMsg || displayMsg)) {
|
|
521
611
|
return;
|
|
522
612
|
}
|
|
523
613
|
// ── Tool-schema overload: the active model repeatedly emitted tool-call
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// File Purpose: Registers DB-backed GSD workflow tools and compatibility aliases.
|
|
3
3
|
import { Type, StringEnum } from "@gsd/pi-ai";
|
|
4
4
|
import { Text } from "@gsd/pi-tui";
|
|
5
|
+
import { SUMMARY_SAVE_CONTENT_MAX_LENGTH } from "@opengsd/contracts";
|
|
5
6
|
import { loadEffectiveGSDPreferences } from "../preferences.js";
|
|
6
7
|
import { ensureDbOpen, resolveCtxCwd, resolveWorkflowToolBasePath } from "./dynamic-tools.js";
|
|
7
8
|
import { importWorkflowExecutorsModule } from "../workflow-mcp.js";
|
|
@@ -373,13 +374,17 @@ export function registerDbTools(pi) {
|
|
|
373
374
|
"Root-level artifact paths are PROJECT.md, PROJECT-DRAFT.md, REQUIREMENTS.md, and REQUIREMENTS-DRAFT.md.",
|
|
374
375
|
"artifact_type must be one of: SUMMARY, RESEARCH, CONTEXT, ASSESSMENT, CONTEXT-DRAFT, PROJECT, PROJECT-DRAFT, REQUIREMENTS, REQUIREMENTS-DRAFT.",
|
|
375
376
|
"Use CONTEXT-DRAFT for incremental draft persistence; use CONTEXT for the final milestone context after depth verification.",
|
|
377
|
+
`Keep each content payload under ${SUMMARY_SAVE_CONTENT_MAX_LENGTH} characters; save large context incrementally with CONTEXT-DRAFT/PROJECT-DRAFT/REQUIREMENTS-DRAFT instead of one oversized call.`,
|
|
376
378
|
],
|
|
377
379
|
parameters: Type.Object({
|
|
378
380
|
milestone_id: Type.Optional(Type.String({ description: "Milestone ID (e.g. M001). Omit only for root-level PROJECT/PROJECT-DRAFT/REQUIREMENTS/REQUIREMENTS-DRAFT artifacts." })),
|
|
379
381
|
slice_id: Type.Optional(Type.String({ description: "Slice ID (e.g. S01)" })),
|
|
380
382
|
task_id: Type.Optional(Type.String({ description: "Task ID (e.g. T01)" })),
|
|
381
383
|
artifact_type: StringEnum(["SUMMARY", "RESEARCH", "CONTEXT", "ASSESSMENT", "CONTEXT-DRAFT", "PROJECT", "PROJECT-DRAFT", "REQUIREMENTS", "REQUIREMENTS-DRAFT"], { description: "Artifact type to save" }),
|
|
382
|
-
content: Type.String({
|
|
384
|
+
content: Type.String({
|
|
385
|
+
description: `The full markdown content of the artifact. Maximum ${SUMMARY_SAVE_CONTENT_MAX_LENGTH} characters per save.`,
|
|
386
|
+
maxLength: SUMMARY_SAVE_CONTENT_MAX_LENGTH,
|
|
387
|
+
}),
|
|
383
388
|
}),
|
|
384
389
|
execute: summarySaveExecute,
|
|
385
390
|
renderCall(args, theme) {
|