gsd-pi 2.66.1-dev.0df32ec → 2.66.1-dev.3cea7ac
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/extensions/ask-user-questions.js +79 -11
- package/dist/resources/extensions/claude-code-cli/partial-builder.js +4 -3
- package/dist/resources/extensions/claude-code-cli/stream-adapter.js +10 -3
- package/dist/resources/extensions/gsd/auto/loop.js +13 -1
- package/dist/resources/extensions/gsd/auto/phases.js +10 -4
- package/dist/resources/extensions/gsd/auto/run-unit.js +10 -2
- package/dist/resources/extensions/gsd/auto/session.js +1 -1
- package/dist/resources/extensions/gsd/auto-dashboard.js +65 -15
- package/dist/resources/extensions/gsd/auto-dispatch.js +30 -28
- package/dist/resources/extensions/gsd/auto-prompts.js +6 -6
- package/dist/resources/extensions/gsd/auto-recovery.js +11 -12
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +18 -6
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +59 -5
- package/dist/resources/extensions/gsd/bootstrap/register-shortcuts.js +8 -5
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +186 -14
- package/dist/resources/extensions/gsd/codebase-generator.js +4 -0
- package/dist/resources/extensions/gsd/commands/handlers/core.js +3 -3
- package/dist/resources/extensions/gsd/commands-prefs-wizard.js +10 -4
- package/dist/resources/extensions/gsd/custom-workflow-engine.js +3 -1
- package/dist/resources/extensions/gsd/detection.js +6 -0
- package/dist/resources/extensions/gsd/files.js +19 -2
- package/dist/resources/extensions/gsd/guided-flow.js +12 -8
- package/dist/resources/extensions/gsd/index.js +1 -1
- package/dist/resources/extensions/gsd/parallel-monitor-overlay.js +2 -0
- package/dist/resources/extensions/gsd/parsers-legacy.js +3 -1
- package/dist/resources/extensions/gsd/prompts/complete-milestone.md +1 -1
- package/dist/resources/extensions/gsd/prompts/discuss-prepared.md +7 -7
- package/dist/resources/extensions/gsd/prompts/discuss.md +3 -3
- package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +3 -3
- package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +3 -1
- package/dist/resources/extensions/gsd/prompts/rethink.md +6 -2
- package/dist/resources/extensions/gsd/prompts/system.md +1 -1
- package/dist/resources/extensions/gsd/prompts/triage-captures.md +1 -1
- package/dist/resources/extensions/gsd/prompts/validate-milestone.md +4 -4
- package/dist/resources/extensions/gsd/prompts/worktree-merge.md +3 -1
- package/dist/resources/extensions/gsd/safety/file-change-validator.js +2 -1
- package/dist/resources/extensions/gsd/state.js +2 -1
- package/dist/resources/extensions/gsd/visualizer-overlay.js +27 -26
- package/dist/resources/extensions/gsd/workflow-reconcile.js +46 -7
- package/dist/resources/extensions/remote-questions/manager.js +8 -0
- package/dist/resources/extensions/shared/interview-ui.js +10 -0
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +17 -17
- 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/required-server-files.json +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
- 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 +17 -17
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +2 -2
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/dist/web/standalone/server.js +1 -1
- package/package.json +1 -1
- package/packages/pi-ai/dist/providers/anthropic-shared.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic-shared.js +4 -3
- package/packages/pi-ai/dist/providers/anthropic-shared.js.map +1 -1
- package/packages/pi-ai/dist/utils/json-parse.d.ts.map +1 -1
- package/packages/pi-ai/dist/utils/json-parse.js +11 -1
- package/packages/pi-ai/dist/utils/json-parse.js.map +1 -1
- package/packages/pi-ai/dist/utils/repair-tool-json.d.ts.map +1 -1
- package/packages/pi-ai/dist/utils/repair-tool-json.js +60 -1
- package/packages/pi-ai/dist/utils/repair-tool-json.js.map +1 -1
- package/packages/pi-ai/dist/utils/tests/json-parse.test.d.ts +2 -0
- package/packages/pi-ai/dist/utils/tests/json-parse.test.d.ts.map +1 -0
- package/packages/pi-ai/dist/utils/tests/json-parse.test.js +14 -0
- package/packages/pi-ai/dist/utils/tests/json-parse.test.js.map +1 -0
- package/packages/pi-ai/dist/utils/tests/repair-tool-json.test.js +10 -0
- package/packages/pi-ai/dist/utils/tests/repair-tool-json.test.js.map +1 -1
- package/packages/pi-ai/src/providers/anthropic-shared.ts +4 -3
- package/packages/pi-ai/src/utils/json-parse.ts +11 -1
- package/packages/pi-ai/src/utils/repair-tool-json.ts +69 -1
- package/packages/pi-ai/src/utils/tests/json-parse.test.ts +17 -0
- package/packages/pi-ai/src/utils/tests/repair-tool-json.test.ts +13 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/provider-display-name.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/provider-display-name.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/provider-display-name.test.js +17 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/provider-display-name.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js +2 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.d.ts +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js +9 -2
- package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.js +2 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/scoped-models-selector.js +2 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/scoped-models-selector.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +2 -2
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/provider-display-name.test.ts +18 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/footer.ts +2 -1
- package/packages/pi-coding-agent/src/modes/interactive/components/model-selector.ts +11 -2
- package/packages/pi-coding-agent/src/modes/interactive/components/provider-manager.ts +2 -1
- package/packages/pi-coding-agent/src/modes/interactive/components/scoped-models-selector.ts +2 -1
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +2 -2
- package/packages/pi-tui/dist/__tests__/autocomplete.test.js +13 -0
- package/packages/pi-tui/dist/__tests__/autocomplete.test.js.map +1 -1
- package/packages/pi-tui/dist/__tests__/stdin-buffer.test.d.ts +2 -0
- package/packages/pi-tui/dist/__tests__/stdin-buffer.test.d.ts.map +1 -0
- package/packages/pi-tui/dist/__tests__/stdin-buffer.test.js +35 -0
- package/packages/pi-tui/dist/__tests__/stdin-buffer.test.js.map +1 -0
- package/packages/pi-tui/dist/__tests__/tui.test.d.ts +2 -0
- package/packages/pi-tui/dist/__tests__/tui.test.d.ts.map +1 -0
- package/packages/pi-tui/dist/__tests__/tui.test.js +43 -0
- package/packages/pi-tui/dist/__tests__/tui.test.js.map +1 -0
- package/packages/pi-tui/dist/autocomplete.d.ts.map +1 -1
- package/packages/pi-tui/dist/autocomplete.js +9 -7
- package/packages/pi-tui/dist/autocomplete.js.map +1 -1
- package/packages/pi-tui/dist/components/__tests__/editor.test.d.ts +2 -0
- package/packages/pi-tui/dist/components/__tests__/editor.test.d.ts.map +1 -0
- package/packages/pi-tui/dist/components/__tests__/editor.test.js +54 -0
- package/packages/pi-tui/dist/components/__tests__/editor.test.js.map +1 -0
- package/packages/pi-tui/dist/components/editor.d.ts +3 -1
- package/packages/pi-tui/dist/components/editor.d.ts.map +1 -1
- package/packages/pi-tui/dist/components/editor.js +14 -3
- package/packages/pi-tui/dist/components/editor.js.map +1 -1
- package/packages/pi-tui/dist/stdin-buffer.d.ts.map +1 -1
- package/packages/pi-tui/dist/stdin-buffer.js +6 -0
- package/packages/pi-tui/dist/stdin-buffer.js.map +1 -1
- package/packages/pi-tui/dist/tui.d.ts.map +1 -1
- package/packages/pi-tui/dist/tui.js +8 -0
- package/packages/pi-tui/dist/tui.js.map +1 -1
- package/packages/pi-tui/src/__tests__/autocomplete.test.ts +15 -0
- package/packages/pi-tui/src/__tests__/stdin-buffer.test.ts +43 -0
- package/packages/pi-tui/src/__tests__/tui.test.ts +50 -0
- package/packages/pi-tui/src/autocomplete.ts +9 -7
- package/packages/pi-tui/src/components/__tests__/editor.test.ts +64 -0
- package/packages/pi-tui/src/components/editor.ts +14 -3
- package/packages/pi-tui/src/stdin-buffer.ts +7 -0
- package/packages/pi-tui/src/tui.ts +9 -0
- package/src/resources/extensions/ask-user-questions.ts +103 -11
- package/src/resources/extensions/claude-code-cli/partial-builder.ts +4 -3
- package/src/resources/extensions/claude-code-cli/stream-adapter.ts +12 -3
- package/src/resources/extensions/claude-code-cli/tests/partial-builder.test.ts +17 -0
- package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +18 -0
- package/src/resources/extensions/gsd/auto/loop-deps.ts +2 -1
- package/src/resources/extensions/gsd/auto/loop.ts +14 -1
- package/src/resources/extensions/gsd/auto/phases.ts +10 -5
- package/src/resources/extensions/gsd/auto/run-unit.ts +14 -2
- package/src/resources/extensions/gsd/auto/session.ts +1 -1
- package/src/resources/extensions/gsd/auto-dashboard.ts +76 -16
- package/src/resources/extensions/gsd/auto-dispatch.ts +36 -35
- package/src/resources/extensions/gsd/auto-prompts.ts +5 -6
- package/src/resources/extensions/gsd/auto-recovery.ts +15 -15
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +27 -6
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +67 -6
- package/src/resources/extensions/gsd/bootstrap/register-shortcuts.ts +11 -8
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +209 -16
- package/src/resources/extensions/gsd/codebase-generator.ts +4 -0
- package/src/resources/extensions/gsd/commands/handlers/core.ts +6 -6
- package/src/resources/extensions/gsd/commands-prefs-wizard.ts +11 -4
- package/src/resources/extensions/gsd/custom-workflow-engine.ts +3 -1
- package/src/resources/extensions/gsd/detection.ts +6 -0
- package/src/resources/extensions/gsd/files.ts +21 -2
- package/src/resources/extensions/gsd/guided-flow.ts +15 -8
- package/src/resources/extensions/gsd/index.ts +6 -0
- package/src/resources/extensions/gsd/parallel-monitor-overlay.ts +2 -0
- package/src/resources/extensions/gsd/parsers-legacy.ts +3 -1
- package/src/resources/extensions/gsd/prompts/complete-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/discuss-prepared.md +7 -7
- package/src/resources/extensions/gsd/prompts/discuss.md +3 -3
- package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +3 -3
- package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +3 -1
- package/src/resources/extensions/gsd/prompts/rethink.md +6 -2
- package/src/resources/extensions/gsd/prompts/system.md +1 -1
- package/src/resources/extensions/gsd/prompts/triage-captures.md +1 -1
- package/src/resources/extensions/gsd/prompts/validate-milestone.md +4 -4
- package/src/resources/extensions/gsd/prompts/worktree-merge.md +3 -1
- package/src/resources/extensions/gsd/safety/file-change-validator.ts +4 -1
- package/src/resources/extensions/gsd/state.ts +2 -1
- package/src/resources/extensions/gsd/tests/auto-dashboard.test.ts +52 -1
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +50 -2
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +48 -0
- package/src/resources/extensions/gsd/tests/codebase-generator.test.ts +22 -0
- package/src/resources/extensions/gsd/tests/core-overlay-fallback.test.ts +44 -0
- package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +7 -1
- package/src/resources/extensions/gsd/tests/custom-workflow-engine.test.ts +31 -0
- package/src/resources/extensions/gsd/tests/detection.test.ts +37 -0
- package/src/resources/extensions/gsd/tests/file-change-validator.test.ts +50 -0
- package/src/resources/extensions/gsd/tests/gsd-tools.test.ts +35 -0
- package/src/resources/extensions/gsd/tests/guided-flow-session-isolation.test.ts +34 -0
- package/src/resources/extensions/gsd/tests/health-widget.test.ts +45 -0
- package/src/resources/extensions/gsd/tests/integration/auto-recovery.test.ts +53 -13
- package/src/resources/extensions/gsd/tests/integration/state-machine-runtime-failures.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/journal-integration.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +3 -4
- package/src/resources/extensions/gsd/tests/parallel-monitor-overlay.test.ts +21 -0
- package/src/resources/extensions/gsd/tests/parallel-research-dispatch.test.ts +71 -2
- package/src/resources/extensions/gsd/tests/parsers.test.ts +25 -0
- package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +8 -1
- package/src/resources/extensions/gsd/tests/queue-execution-guard.test.ts +9 -0
- package/src/resources/extensions/gsd/tests/reactive-graph.test.ts +19 -0
- package/src/resources/extensions/gsd/tests/register-shortcuts.test.ts +73 -0
- package/src/resources/extensions/gsd/tests/remote-questions.test.ts +98 -0
- package/src/resources/extensions/gsd/tests/smart-entry-complete.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +26 -0
- package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +59 -0
- package/src/resources/extensions/gsd/tests/workflow-reconcile.test.ts +91 -0
- package/src/resources/extensions/gsd/tests/write-gate.test.ts +210 -35
- package/src/resources/extensions/gsd/visualizer-overlay.ts +31 -27
- package/src/resources/extensions/gsd/workflow-reconcile.ts +59 -8
- package/src/resources/extensions/remote-questions/manager.ts +9 -0
- package/src/resources/extensions/shared/interview-ui.ts +13 -0
- /package/dist/web/standalone/.next/static/{Zw5aZFHFtOwjJSOsINh1m → HxFcJ8GrYNPsg9ARz7GPz}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{Zw5aZFHFtOwjJSOsINh1m → HxFcJ8GrYNPsg9ARz7GPz}/_ssgManifest.js +0 -0
|
@@ -159,6 +159,7 @@ export class CombinedAutocompleteProvider implements AutocompleteProvider {
|
|
|
159
159
|
): { items: AutocompleteItem[]; prefix: string } | null {
|
|
160
160
|
const currentLine = lines[cursorLine] || "";
|
|
161
161
|
const textBeforeCursor = currentLine.slice(0, cursorCol);
|
|
162
|
+
const trimmedBeforeCursor = textBeforeCursor.trimStart();
|
|
162
163
|
|
|
163
164
|
// Check for @ file reference (fuzzy search) - must be after a delimiter or at start
|
|
164
165
|
const atPrefix = this.extractAtPrefix(textBeforeCursor);
|
|
@@ -174,12 +175,12 @@ export class CombinedAutocompleteProvider implements AutocompleteProvider {
|
|
|
174
175
|
}
|
|
175
176
|
|
|
176
177
|
// Check for slash commands
|
|
177
|
-
if (
|
|
178
|
-
const spaceIndex =
|
|
178
|
+
if (trimmedBeforeCursor.startsWith("/")) {
|
|
179
|
+
const spaceIndex = trimmedBeforeCursor.indexOf(" ");
|
|
179
180
|
|
|
180
181
|
if (spaceIndex === -1) {
|
|
181
182
|
// No space yet - complete command names with fuzzy matching
|
|
182
|
-
const prefix =
|
|
183
|
+
const prefix = trimmedBeforeCursor.slice(1); // Remove the "/"
|
|
183
184
|
const commandItems = this.commands.map((cmd) => ({
|
|
184
185
|
name: "name" in cmd ? cmd.name : cmd.value,
|
|
185
186
|
label: "name" in cmd ? cmd.name : cmd.label,
|
|
@@ -196,12 +197,12 @@ export class CombinedAutocompleteProvider implements AutocompleteProvider {
|
|
|
196
197
|
|
|
197
198
|
return {
|
|
198
199
|
items: filtered,
|
|
199
|
-
prefix:
|
|
200
|
+
prefix: `/${prefix}`,
|
|
200
201
|
};
|
|
201
202
|
} else {
|
|
202
203
|
// Space found - complete command arguments
|
|
203
|
-
const commandName =
|
|
204
|
-
const argumentText =
|
|
204
|
+
const commandName = trimmedBeforeCursor.slice(1, spaceIndex); // Command without "/"
|
|
205
|
+
const argumentText = trimmedBeforeCursor.slice(spaceIndex + 1); // Text after space
|
|
205
206
|
|
|
206
207
|
const command = this.commands.find((cmd) => {
|
|
207
208
|
const name = "name" in cmd ? cmd.name : cmd.value;
|
|
@@ -269,7 +270,8 @@ export class CombinedAutocompleteProvider implements AutocompleteProvider {
|
|
|
269
270
|
|
|
270
271
|
// Check if we're completing a slash command (prefix starts with "/" but NOT a file path)
|
|
271
272
|
// Slash commands are at the start of the line and don't contain path separators after the first /
|
|
272
|
-
const
|
|
273
|
+
const trimmedPrefix = prefix.trimStart();
|
|
274
|
+
const isSlashCommand = trimmedPrefix.startsWith("/") && beforePrefix.trim() === "" && !trimmedPrefix.slice(1).includes("/");
|
|
273
275
|
if (isSlashCommand) {
|
|
274
276
|
// This is a command name completion
|
|
275
277
|
const newLine = `${beforePrefix}/${item.value} ${adjustedAfterCursor}`;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { describe, it } from "node:test";
|
|
3
|
+
|
|
4
|
+
import { Editor, type EditorTheme } from "../editor.js";
|
|
5
|
+
import { CURSOR_MARKER, TUI } from "../../tui.js";
|
|
6
|
+
import type { Terminal } from "../../terminal.js";
|
|
7
|
+
|
|
8
|
+
function makeTerminal(): Terminal {
|
|
9
|
+
return {
|
|
10
|
+
isTTY: true,
|
|
11
|
+
columns: 80,
|
|
12
|
+
rows: 24,
|
|
13
|
+
kittyProtocolActive: false,
|
|
14
|
+
start() {},
|
|
15
|
+
stop() {},
|
|
16
|
+
drainInput: async () => {},
|
|
17
|
+
write() {},
|
|
18
|
+
moveBy() {},
|
|
19
|
+
hideCursor() {},
|
|
20
|
+
showCursor() {},
|
|
21
|
+
clearLine() {},
|
|
22
|
+
clearFromCursor() {},
|
|
23
|
+
clearScreen() {},
|
|
24
|
+
setTitle() {},
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const theme: EditorTheme = {
|
|
29
|
+
borderColor: (text) => text,
|
|
30
|
+
selectList: {
|
|
31
|
+
selectedPrefix: (text) => text,
|
|
32
|
+
selectedText: (text) => text,
|
|
33
|
+
description: (text) => text,
|
|
34
|
+
scrollInfo: (text) => text,
|
|
35
|
+
noMatch: (text) => text,
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
describe("Editor", () => {
|
|
40
|
+
it("clears bracketed paste state when focus is lost", () => {
|
|
41
|
+
const editor = new Editor(new TUI(makeTerminal()), theme);
|
|
42
|
+
editor.focused = true;
|
|
43
|
+
|
|
44
|
+
editor.handleInput("\x1b[200~partial");
|
|
45
|
+
editor.focused = false;
|
|
46
|
+
editor.focused = true;
|
|
47
|
+
editor.handleInput("hello");
|
|
48
|
+
|
|
49
|
+
assert.equal(editor.getText(), "hello");
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("keeps the hardware cursor marker visible while autocomplete is open", () => {
|
|
53
|
+
const editor = new Editor(new TUI(makeTerminal()), theme);
|
|
54
|
+
editor.focused = true;
|
|
55
|
+
editor.setText("/se");
|
|
56
|
+
|
|
57
|
+
(editor as any).autocompleteState = "regular";
|
|
58
|
+
(editor as any).autocompleteList = { render: () => [] };
|
|
59
|
+
|
|
60
|
+
const rendered = editor.render(40).join("\n");
|
|
61
|
+
|
|
62
|
+
assert.ok(rendered.includes(CURSOR_MARKER));
|
|
63
|
+
});
|
|
64
|
+
});
|
|
@@ -128,7 +128,17 @@ export class Editor implements Component, Focusable {
|
|
|
128
128
|
};
|
|
129
129
|
|
|
130
130
|
/** Focusable interface - set by TUI when focus changes */
|
|
131
|
-
|
|
131
|
+
private _focused: boolean = false;
|
|
132
|
+
get focused(): boolean {
|
|
133
|
+
return this._focused;
|
|
134
|
+
}
|
|
135
|
+
set focused(value: boolean) {
|
|
136
|
+
this._focused = value;
|
|
137
|
+
if (!value) {
|
|
138
|
+
this.isInPaste = false;
|
|
139
|
+
this.pasteBuffer = "";
|
|
140
|
+
}
|
|
141
|
+
}
|
|
132
142
|
|
|
133
143
|
protected tui: TUI;
|
|
134
144
|
private theme: EditorTheme;
|
|
@@ -376,8 +386,9 @@ export class Editor implements Component, Focusable {
|
|
|
376
386
|
}
|
|
377
387
|
|
|
378
388
|
// Render each visible layout line
|
|
379
|
-
//
|
|
380
|
-
|
|
389
|
+
// Keep the hardware cursor anchored while autocomplete is open so IME
|
|
390
|
+
// candidate windows still attach to the editor caret.
|
|
391
|
+
const emitCursorMarker = this.focused;
|
|
381
392
|
|
|
382
393
|
for (const layoutLine of visibleLines) {
|
|
383
394
|
let displayText = layoutLine.text;
|
|
@@ -361,6 +361,13 @@ export class StdinBuffer extends EventEmitter<StdinBufferEventMap> {
|
|
|
361
361
|
return [];
|
|
362
362
|
}
|
|
363
363
|
|
|
364
|
+
// Keep incomplete escape prefixes buffered so split CSI/mouse/focus
|
|
365
|
+
// sequences do not get emitted as literal text on timeout.
|
|
366
|
+
// A lone ESC is still flushed so an actual Escape keypress is not lost.
|
|
367
|
+
if (this.buffer.length > 1 && this.buffer.startsWith(ESC) && isCompleteSequence(this.buffer) === "incomplete") {
|
|
368
|
+
return [];
|
|
369
|
+
}
|
|
370
|
+
|
|
364
371
|
const sequences = [this.buffer];
|
|
365
372
|
this.buffer = "";
|
|
366
373
|
return sequences;
|
|
@@ -590,6 +590,15 @@ export class TUI extends Container {
|
|
|
590
590
|
this.cellSizeQueryPending = false;
|
|
591
591
|
}
|
|
592
592
|
|
|
593
|
+
// Don't hold a bare Escape keypress hostage while waiting for the
|
|
594
|
+
// optional cell-size response. This is the most common early input race.
|
|
595
|
+
if (this.inputBuffer === "\x1b") {
|
|
596
|
+
const result = this.inputBuffer;
|
|
597
|
+
this.inputBuffer = "";
|
|
598
|
+
this.cellSizeQueryPending = false;
|
|
599
|
+
return result;
|
|
600
|
+
}
|
|
601
|
+
|
|
593
602
|
// Check if we have a partial cell size response starting (wait for more data)
|
|
594
603
|
// Patterns that could be incomplete cell size response: \x1b, \x1b[, \x1b[6, \x1b[6;...(no t yet)
|
|
595
604
|
const partialCellSizePattern = /\x1b(\[6?;?[\d;]*)?$/;
|
|
@@ -107,6 +107,65 @@ export function resetAskUserQuestionsCache(): void {
|
|
|
107
107
|
turnCache.clear();
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
+
// ─── Race helper ─────────────────────────────────────────────────────────────
|
|
111
|
+
|
|
112
|
+
interface RaceableResult {
|
|
113
|
+
content: { type: "text"; text: string }[];
|
|
114
|
+
details?: unknown;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Race a remote channel dispatch against the local TUI. The first to produce
|
|
119
|
+
* a valid (non-error, non-timeout) result wins. The loser is cancelled via
|
|
120
|
+
* the shared AbortController.
|
|
121
|
+
*
|
|
122
|
+
* If the local TUI responds first, the remote poll is aborted (the message
|
|
123
|
+
* stays in Discord/Slack but polling stops). If remote responds first, the
|
|
124
|
+
* local TUI prompt is cancelled.
|
|
125
|
+
*
|
|
126
|
+
* Returns null only when both sides fail or are cancelled.
|
|
127
|
+
*/
|
|
128
|
+
async function raceRemoteAndLocal(
|
|
129
|
+
startRemote: () => Promise<RaceableResult | null>,
|
|
130
|
+
startLocal: () => Promise<RoundResult | null | undefined>,
|
|
131
|
+
controller: AbortController,
|
|
132
|
+
questions: Question[],
|
|
133
|
+
): Promise<RaceableResult | null> {
|
|
134
|
+
// Wrap local TUI result into the same shape as remote results
|
|
135
|
+
const localPromise = startLocal().then((result): RaceableResult | null => {
|
|
136
|
+
if (!result || Object.keys(result.answers).length === 0) return null;
|
|
137
|
+
return {
|
|
138
|
+
content: [{ type: "text" as const, text: formatForLLM(result) }],
|
|
139
|
+
details: { questions, response: result, cancelled: false } satisfies LocalResultDetails,
|
|
140
|
+
};
|
|
141
|
+
}).catch(() => null);
|
|
142
|
+
|
|
143
|
+
const remotePromise = startRemote().then((result): RaceableResult | null => {
|
|
144
|
+
if (!result) return null;
|
|
145
|
+
const details = result.details as Record<string, unknown> | undefined;
|
|
146
|
+
// Treat timeouts and errors as non-wins — let the local TUI win instead
|
|
147
|
+
if (details?.timed_out || details?.error) return null;
|
|
148
|
+
return result;
|
|
149
|
+
}).catch(() => null);
|
|
150
|
+
|
|
151
|
+
// Race: first non-null result wins
|
|
152
|
+
const winner = await Promise.race([
|
|
153
|
+
localPromise.then((r) => r ? { source: "local" as const, result: r } : null),
|
|
154
|
+
remotePromise.then((r) => r ? { source: "remote" as const, result: r } : null),
|
|
155
|
+
]);
|
|
156
|
+
|
|
157
|
+
if (winner) {
|
|
158
|
+
// Cancel the loser
|
|
159
|
+
controller.abort();
|
|
160
|
+
return winner.result;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// First to resolve was null — wait for the other
|
|
164
|
+
const [localResult, remoteResult] = await Promise.all([localPromise, remotePromise]);
|
|
165
|
+
controller.abort();
|
|
166
|
+
return localResult ?? remoteResult;
|
|
167
|
+
}
|
|
168
|
+
|
|
110
169
|
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
111
170
|
|
|
112
171
|
const OTHER_OPTION_LABEL = "None of the above";
|
|
@@ -180,20 +239,53 @@ export default function AskUserQuestions(pi: ExtensionAPI) {
|
|
|
180
239
|
}
|
|
181
240
|
}
|
|
182
241
|
|
|
183
|
-
//
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
|
|
242
|
+
// ── Routing: race remote + local, remote-only, or local-only ────────
|
|
243
|
+
const { tryRemoteQuestions, isRemoteConfigured } = await import("./remote-questions/manager.js");
|
|
244
|
+
const hasRemote = isRemoteConfigured();
|
|
245
|
+
|
|
246
|
+
// Case 1: Both remote and local UI available — race them.
|
|
247
|
+
// The first response wins; the loser is cancelled via AbortController.
|
|
248
|
+
if (hasRemote && ctx.hasUI) {
|
|
249
|
+
const raceController = new AbortController();
|
|
250
|
+
// Merge the parent signal so external cancellation propagates.
|
|
251
|
+
const onParentAbort = () => raceController.abort();
|
|
252
|
+
signal?.addEventListener("abort", onParentAbort, { once: true });
|
|
253
|
+
const raceSignal = raceController.signal;
|
|
254
|
+
|
|
255
|
+
const raceResult = await raceRemoteAndLocal(
|
|
256
|
+
() => tryRemoteQuestions(params.questions, raceSignal),
|
|
257
|
+
() => showInterviewRound(params.questions, { signal: raceSignal }, ctx as any),
|
|
258
|
+
raceController,
|
|
259
|
+
params.questions,
|
|
260
|
+
);
|
|
261
|
+
|
|
262
|
+
signal?.removeEventListener("abort", onParentAbort);
|
|
263
|
+
|
|
264
|
+
if (raceResult) {
|
|
265
|
+
const details = raceResult.details as Record<string, unknown> | undefined;
|
|
266
|
+
if (details && !details.timed_out && !details.error && !details.cancelled) {
|
|
267
|
+
turnCache.set(sig, raceResult as unknown as CachedResult);
|
|
268
|
+
}
|
|
269
|
+
return { ...raceResult, details: raceResult.details as unknown };
|
|
270
|
+
}
|
|
271
|
+
// Both sides failed/cancelled — fall through to error
|
|
272
|
+
return errorResult("ask_user_questions: no response received from local UI or remote channel", params.questions);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Case 2: Remote configured but no local UI (headless) — remote only.
|
|
276
|
+
if (hasRemote && !ctx.hasUI) {
|
|
277
|
+
const remoteResult = await tryRemoteQuestions(params.questions, signal);
|
|
278
|
+
if (remoteResult) {
|
|
279
|
+
const remoteDetails = remoteResult.details as Record<string, unknown> | undefined;
|
|
280
|
+
if (remoteDetails && !remoteDetails.timed_out && !remoteDetails.error) {
|
|
281
|
+
turnCache.set(sig, remoteResult as unknown as CachedResult);
|
|
282
|
+
}
|
|
283
|
+
return { ...remoteResult, details: remoteResult.details as unknown };
|
|
193
284
|
}
|
|
194
|
-
return
|
|
285
|
+
return errorResult("Error: remote channel configured but returned no result", params.questions);
|
|
195
286
|
}
|
|
196
287
|
|
|
288
|
+
// Case 3: No remote — local UI only.
|
|
197
289
|
if (!ctx.hasUI) {
|
|
198
290
|
return errorResult("Error: UI not available (non-interactive mode)", params.questions);
|
|
199
291
|
}
|
|
@@ -16,7 +16,7 @@ import type {
|
|
|
16
16
|
Usage,
|
|
17
17
|
WebSearchResultContent,
|
|
18
18
|
} from "@gsd/pi-ai";
|
|
19
|
-
import { repairToolJson } from "@gsd/pi-ai";
|
|
19
|
+
import { hasXmlParameterTags, repairToolJson } from "@gsd/pi-ai";
|
|
20
20
|
import type { BetaContentBlock, BetaRawMessageStreamEvent, NonNullableUsage } from "./sdk-types.js";
|
|
21
21
|
|
|
22
22
|
// ---------------------------------------------------------------------------
|
|
@@ -242,13 +242,14 @@ export class PartialMessageBuilder {
|
|
|
242
242
|
}
|
|
243
243
|
if (block.type === "toolCall") {
|
|
244
244
|
const jsonStr = this.toolJsonAccum.get(streamIndex) ?? "{}";
|
|
245
|
+
const jsonForParse = hasXmlParameterTags(jsonStr) ? repairToolJson(jsonStr) : jsonStr;
|
|
245
246
|
try {
|
|
246
|
-
block.arguments = JSON.parse(
|
|
247
|
+
block.arguments = JSON.parse(jsonForParse);
|
|
247
248
|
} catch {
|
|
248
249
|
// JSON.parse failed — attempt repair for YAML-style bullet
|
|
249
250
|
// lists that LLMs copy from template formatting (#2660).
|
|
250
251
|
try {
|
|
251
|
-
block.arguments = JSON.parse(repairToolJson(
|
|
252
|
+
block.arguments = JSON.parse(repairToolJson(jsonForParse));
|
|
252
253
|
} catch {
|
|
253
254
|
// Repair also failed — stream was truncated or garbage.
|
|
254
255
|
// Preserve the raw string for diagnostics but signal the
|
|
@@ -50,6 +50,17 @@ function createAssistantStream(): AssistantMessageEventStream {
|
|
|
50
50
|
|
|
51
51
|
let cachedClaudePath: string | null = null;
|
|
52
52
|
|
|
53
|
+
export function getClaudeLookupCommand(platform: NodeJS.Platform = process.platform): string {
|
|
54
|
+
return platform === "win32" ? "where claude" : "which claude";
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function parseClaudeLookupOutput(output: Buffer | string): string {
|
|
58
|
+
return output
|
|
59
|
+
.toString()
|
|
60
|
+
.trim()
|
|
61
|
+
.split(/\r?\n/)[0] ?? "";
|
|
62
|
+
}
|
|
63
|
+
|
|
53
64
|
/**
|
|
54
65
|
* Resolve the path to the system-installed `claude` binary.
|
|
55
66
|
* The SDK defaults to a bundled cli.js which doesn't exist when
|
|
@@ -58,9 +69,7 @@ let cachedClaudePath: string | null = null;
|
|
|
58
69
|
function getClaudePath(): string {
|
|
59
70
|
if (cachedClaudePath) return cachedClaudePath;
|
|
60
71
|
try {
|
|
61
|
-
cachedClaudePath = execSync(
|
|
62
|
-
.toString()
|
|
63
|
-
.trim();
|
|
72
|
+
cachedClaudePath = parseClaudeLookupOutput(execSync(getClaudeLookupCommand(), { timeout: 5_000, stdio: "pipe" }));
|
|
64
73
|
} catch {
|
|
65
74
|
cachedClaudePath = "claude"; // fall back to PATH resolution
|
|
66
75
|
}
|
|
@@ -130,4 +130,21 @@ describe("PartialMessageBuilder — malformed tool arguments (#2574)", () => {
|
|
|
130
130
|
assert.equal(event!.toolCall.arguments.title, "done");
|
|
131
131
|
}
|
|
132
132
|
});
|
|
133
|
+
|
|
134
|
+
test("XML parameter tags trapped inside valid JSON strings are promoted (#3751)", () => {
|
|
135
|
+
const builder = new PartialMessageBuilder("claude-sonnet-4-20250514");
|
|
136
|
+
const malformedJson =
|
|
137
|
+
'{"narrative":"text.</narrative>\\n<parameter name=\\"verification\\">all tests pass</parameter>\\n<parameter name=\\"verificationEvidence\\">[\\"npm test\\"]</parameter>","oneLiner":"done"}';
|
|
138
|
+
const event = feedToolCall(builder, [malformedJson]);
|
|
139
|
+
|
|
140
|
+
assert.ok(event, "event should not be null");
|
|
141
|
+
assert.equal(event!.type, "toolcall_end");
|
|
142
|
+
assert.equal((event as any).malformedArguments, undefined);
|
|
143
|
+
if (event!.type === "toolcall_end") {
|
|
144
|
+
assert.equal(event.toolCall.arguments.narrative, "text.");
|
|
145
|
+
assert.equal(event.toolCall.arguments.verification, "all tests pass");
|
|
146
|
+
assert.deepEqual(event.toolCall.arguments.verificationEvidence, ["npm test"]);
|
|
147
|
+
assert.equal(event.toolCall.arguments.oneLiner, "done");
|
|
148
|
+
}
|
|
149
|
+
});
|
|
133
150
|
});
|
|
@@ -4,6 +4,8 @@ import {
|
|
|
4
4
|
makeStreamExhaustedErrorMessage,
|
|
5
5
|
buildPromptFromContext,
|
|
6
6
|
buildSdkOptions,
|
|
7
|
+
getClaudeLookupCommand,
|
|
8
|
+
parseClaudeLookupOutput,
|
|
7
9
|
} from "../stream-adapter.ts";
|
|
8
10
|
import type { Context, Message } from "@gsd/pi-ai";
|
|
9
11
|
|
|
@@ -126,3 +128,19 @@ describe("stream-adapter — session persistence (#2859)", () => {
|
|
|
126
128
|
);
|
|
127
129
|
});
|
|
128
130
|
});
|
|
131
|
+
|
|
132
|
+
describe("stream-adapter — Windows Claude path lookup (#3770)", () => {
|
|
133
|
+
test("getClaudeLookupCommand uses where on Windows", () => {
|
|
134
|
+
assert.equal(getClaudeLookupCommand("win32"), "where claude");
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test("getClaudeLookupCommand uses which on non-Windows platforms", () => {
|
|
138
|
+
assert.equal(getClaudeLookupCommand("darwin"), "which claude");
|
|
139
|
+
assert.equal(getClaudeLookupCommand("linux"), "which claude");
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test("parseClaudeLookupOutput keeps the first native path from multi-line lookup output", () => {
|
|
143
|
+
const output = "C:\\Users\\Binoy\\.local\\bin\\claude.exe\r\nC:\\Program Files\\Claude\\claude.exe\r\n";
|
|
144
|
+
assert.equal(parseClaudeLookupOutput(output), "C:\\Users\\Binoy\\.local\\bin\\claude.exe");
|
|
145
|
+
});
|
|
146
|
+
});
|
|
@@ -20,6 +20,7 @@ import type { DispatchAction } from "../auto-dispatch.js";
|
|
|
20
20
|
import type { WorktreeResolver } from "../worktree-resolver.js";
|
|
21
21
|
import type { CmuxLogLevel } from "../../cmux/index.js";
|
|
22
22
|
import type { JournalEntry } from "../journal.js";
|
|
23
|
+
import type { MergeReconcileResult } from "../auto-recovery.js";
|
|
23
24
|
|
|
24
25
|
/**
|
|
25
26
|
* Dependencies injected by the caller (auto.ts startAuto) so autoLoop
|
|
@@ -118,7 +119,7 @@ export interface LoopDeps {
|
|
|
118
119
|
milestoneId: string,
|
|
119
120
|
fileType: string,
|
|
120
121
|
) => string | null;
|
|
121
|
-
reconcileMergeState: (basePath: string, ctx: ExtensionContext) =>
|
|
122
|
+
reconcileMergeState: (basePath: string, ctx: ExtensionContext) => MergeReconcileResult;
|
|
122
123
|
|
|
123
124
|
// Budget/context/secrets
|
|
124
125
|
getLedger: () => unknown;
|
|
@@ -194,7 +194,7 @@ export async function autoLoop(
|
|
|
194
194
|
|
|
195
195
|
// Verification passed — mark step complete
|
|
196
196
|
debugLog("autoLoop", { phase: "custom-engine-reconcile", iteration, unitId: iterData.unitId });
|
|
197
|
-
await engine.reconcile(engineState, {
|
|
197
|
+
const reconcileResult = await engine.reconcile(engineState, {
|
|
198
198
|
unitType: iterData.unitType,
|
|
199
199
|
unitId: iterData.unitId,
|
|
200
200
|
startedAt: s.currentUnit?.startedAt ?? Date.now(),
|
|
@@ -206,6 +206,19 @@ export async function autoLoop(
|
|
|
206
206
|
recentErrorMessages.length = 0;
|
|
207
207
|
deps.emitJournalEvent({ ts: new Date().toISOString(), flowId, seq: nextSeq(), eventType: "iteration-end", data: { iteration } });
|
|
208
208
|
debugLog("autoLoop", { phase: "iteration-complete", iteration });
|
|
209
|
+
|
|
210
|
+
if (reconcileResult.outcome === "milestone-complete") {
|
|
211
|
+
await deps.stopAuto(ctx, pi, "Workflow complete");
|
|
212
|
+
break;
|
|
213
|
+
}
|
|
214
|
+
if (reconcileResult.outcome === "pause") {
|
|
215
|
+
await deps.pauseAuto(ctx, pi);
|
|
216
|
+
break;
|
|
217
|
+
}
|
|
218
|
+
if (reconcileResult.outcome === "stop") {
|
|
219
|
+
await deps.stopAuto(ctx, pi, reconcileResult.reason ?? "Engine stopped");
|
|
220
|
+
break;
|
|
221
|
+
}
|
|
209
222
|
continue;
|
|
210
223
|
}
|
|
211
224
|
|
|
@@ -507,7 +507,13 @@ export async function runPreDispatch(
|
|
|
507
507
|
}
|
|
508
508
|
|
|
509
509
|
// Mid-merge safety check
|
|
510
|
-
|
|
510
|
+
const mergeReconcileResult = deps.reconcileMergeState(s.basePath, ctx);
|
|
511
|
+
if (mergeReconcileResult === "blocked") {
|
|
512
|
+
await deps.pauseAuto(ctx, pi);
|
|
513
|
+
debugLog("autoLoop", { phase: "exit", reason: "merge-reconciliation-blocked" });
|
|
514
|
+
return { action: "break", reason: "merge-reconciliation-blocked" };
|
|
515
|
+
}
|
|
516
|
+
if (mergeReconcileResult === "reconciled") {
|
|
511
517
|
deps.invalidateAllCaches();
|
|
512
518
|
state = await deps.deriveState(s.basePath);
|
|
513
519
|
mid = state.activeMilestone?.id;
|
|
@@ -1303,8 +1309,8 @@ export async function runUnitPhase(
|
|
|
1303
1309
|
return { action: "break", reason: "provider-pause" };
|
|
1304
1310
|
}
|
|
1305
1311
|
// Session creation timeout (not a structural error): pause auto-mode
|
|
1306
|
-
// and let the provider-error-resume timer handle recovery. This
|
|
1307
|
-
// the provider-pause path — break out cleanly, don't hard-stop.
|
|
1312
|
+
// and let the provider-error-resume timer handle recovery (#3767). This
|
|
1313
|
+
// matches the provider-pause path — break out cleanly, don't hard-stop.
|
|
1308
1314
|
// Structural errors (TypeError, is not a function) are NOT transient
|
|
1309
1315
|
// and must hard-stop to avoid infinite retry loops.
|
|
1310
1316
|
if (
|
|
@@ -1312,7 +1318,7 @@ export async function runUnitPhase(
|
|
|
1312
1318
|
unitResult.errorContext?.category === "timeout"
|
|
1313
1319
|
) {
|
|
1314
1320
|
ctx.ui.notify(
|
|
1315
|
-
`Session creation timed out for ${unitType} ${unitId}.
|
|
1321
|
+
`Session creation timed out for ${unitType} ${unitId}. Pausing auto-mode (recoverable).`,
|
|
1316
1322
|
"warning",
|
|
1317
1323
|
);
|
|
1318
1324
|
debugLog("autoLoop", { phase: "session-timeout-pause", unitType, unitId });
|
|
@@ -1639,4 +1645,3 @@ export async function runFinalize(
|
|
|
1639
1645
|
|
|
1640
1646
|
return { action: "next", data: undefined as void };
|
|
1641
1647
|
}
|
|
1642
|
-
|
|
@@ -12,6 +12,11 @@ import type { UnitResult } from "./types.js";
|
|
|
12
12
|
import { _setCurrentResolve, _setSessionSwitchInFlight } from "./resolve.js";
|
|
13
13
|
import { debugLog } from "../debug-logger.js";
|
|
14
14
|
import { logWarning, logError } from "../workflow-logger.js";
|
|
15
|
+
import { resolveAutoSupervisorConfig } from "../preferences.js";
|
|
16
|
+
|
|
17
|
+
// Tracks the latest session-switch attempt so a late timeout settlement from an
|
|
18
|
+
// older runUnit() call cannot clear the guard for a newer one.
|
|
19
|
+
let sessionSwitchGeneration = 0;
|
|
15
20
|
|
|
16
21
|
/**
|
|
17
22
|
* Execute a single unit: create a new session, send the prompt, and await
|
|
@@ -36,10 +41,13 @@ export async function runUnit(
|
|
|
36
41
|
|
|
37
42
|
let sessionResult: { cancelled: boolean };
|
|
38
43
|
let sessionTimeoutHandle: ReturnType<typeof setTimeout> | undefined;
|
|
44
|
+
const mySessionSwitchGeneration = ++sessionSwitchGeneration;
|
|
39
45
|
_setSessionSwitchInFlight(true);
|
|
40
46
|
try {
|
|
41
47
|
const sessionPromise = s.cmdCtx!.newSession().finally(() => {
|
|
42
|
-
|
|
48
|
+
if (sessionSwitchGeneration === mySessionSwitchGeneration) {
|
|
49
|
+
_setSessionSwitchInFlight(false);
|
|
50
|
+
}
|
|
43
51
|
});
|
|
44
52
|
const timeoutPromise = new Promise<{ cancelled: true }>((resolve) => {
|
|
45
53
|
sessionTimeoutHandle = setTimeout(
|
|
@@ -112,7 +120,11 @@ export async function runUnit(
|
|
|
112
120
|
// If supervision fails to resolve unitPromise within 30s, treat as cancelled.
|
|
113
121
|
// Without this, a crashed agent that never emits agent_end hangs the loop (#3161).
|
|
114
122
|
debugLog("runUnit", { phase: "awaiting-agent-end", unitType, unitId });
|
|
115
|
-
const
|
|
123
|
+
const supervisor = resolveAutoSupervisorConfig();
|
|
124
|
+
const UNIT_HARD_TIMEOUT_MS = Math.max(
|
|
125
|
+
30_000,
|
|
126
|
+
((supervisor.hard_timeout_minutes ?? 30) * 60 * 1000) + 30_000,
|
|
127
|
+
);
|
|
116
128
|
let unitTimeoutHandle: ReturnType<typeof setTimeout> | undefined;
|
|
117
129
|
const timeoutResult = new Promise<UnitResult>((resolve) => {
|
|
118
130
|
unitTimeoutHandle = setTimeout(() => {
|
|
@@ -67,7 +67,7 @@ export interface SidecarItem {
|
|
|
67
67
|
export const MAX_UNIT_DISPATCHES = 3;
|
|
68
68
|
export const STUB_RECOVERY_THRESHOLD = 2;
|
|
69
69
|
export const MAX_LIFETIME_DISPATCHES = 6;
|
|
70
|
-
export const NEW_SESSION_TIMEOUT_MS =
|
|
70
|
+
export const NEW_SESSION_TIMEOUT_MS = 120_000;
|
|
71
71
|
|
|
72
72
|
// ─── AutoSession ─────────────────────────────────────────────────────────────
|
|
73
73
|
|