gsd-pi 2.71.0-dev.72557e1 → 2.71.0-dev.7a61d89
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/headless-events.d.ts +2 -0
- package/dist/headless-events.js +7 -0
- package/dist/headless.js +16 -3
- package/dist/resources/extensions/claude-code-cli/stream-adapter.js +10 -4
- package/dist/resources/extensions/gsd/auto/infra-errors.js +34 -0
- package/dist/resources/extensions/gsd/auto/loop.js +32 -1
- package/dist/resources/extensions/gsd/auto/session.js +8 -0
- package/dist/resources/extensions/gsd/auto-dashboard.js +22 -16
- package/dist/resources/extensions/gsd/auto.js +52 -0
- package/dist/resources/extensions/gsd/bootstrap/register-shortcuts.js +66 -51
- package/dist/resources/extensions/gsd/commands/handlers/auto.js +10 -33
- package/dist/resources/extensions/gsd/commands/handlers/core.js +45 -11
- package/dist/resources/extensions/gsd/commands/handlers/notifications-handler.js +15 -6
- package/dist/resources/extensions/gsd/commands/handlers/workflow.js +4 -10
- package/dist/resources/extensions/gsd/dashboard-overlay.js +8 -3
- package/dist/resources/extensions/gsd/forensics.js +19 -6
- package/dist/resources/extensions/gsd/guided-flow.js +5 -10
- package/dist/resources/extensions/gsd/metrics.js +1 -0
- package/dist/resources/extensions/gsd/milestone-actions.js +10 -4
- package/dist/resources/extensions/gsd/notification-overlay.js +20 -5
- package/dist/resources/extensions/gsd/notification-store.js +30 -0
- package/dist/resources/extensions/gsd/notification-widget.js +5 -13
- package/dist/resources/extensions/gsd/parallel-monitor-overlay.js +8 -3
- package/dist/resources/extensions/gsd/shortcut-defs.js +34 -0
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +12 -12
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +12 -12
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/package.json +1 -1
- package/packages/pi-ai/dist/providers/anthropic-auth.test.d.ts +2 -0
- package/packages/pi-ai/dist/providers/anthropic-auth.test.d.ts.map +1 -0
- package/packages/pi-ai/dist/providers/anthropic-auth.test.js +20 -0
- package/packages/pi-ai/dist/providers/anthropic-auth.test.js.map +1 -0
- package/packages/pi-ai/dist/providers/anthropic.d.ts +2 -1
- package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic.js +7 -4
- package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
- package/packages/pi-ai/src/providers/anthropic-auth.test.ts +32 -0
- package/packages/pi-ai/src/providers/anthropic.ts +8 -4
- package/packages/pi-coding-agent/dist/core/agent-session-renderable-tools.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/agent-session-renderable-tools.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/agent-session-renderable-tools.test.js +61 -0
- package/packages/pi-coding-agent/dist/core/agent-session-renderable-tools.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js +2 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.d.ts +10 -0
- package/packages/pi-coding-agent/dist/core/auth-storage.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.js +27 -0
- package/packages/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.test.js +85 -0
- package/packages/pi-coding-agent/dist/core/auth-storage.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.d.ts +11 -0
- package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.js +38 -5
- package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/sdk.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/sdk.test.js +71 -0
- package/packages/pi-coding-agent/dist/core/sdk.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/index.d.ts +1 -1
- package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/index.js +1 -1
- package/packages/pi-coding-agent/dist/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +4 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +43 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +7 -2
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +4 -3
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js +4 -2
- package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js.map +1 -1
- package/packages/pi-coding-agent/src/core/agent-session-renderable-tools.test.ts +70 -0
- package/packages/pi-coding-agent/src/core/agent-session.ts +2 -1
- package/packages/pi-coding-agent/src/core/auth-storage.test.ts +108 -0
- package/packages/pi-coding-agent/src/core/auth-storage.ts +30 -0
- package/packages/pi-coding-agent/src/core/sdk.test.ts +89 -0
- package/packages/pi-coding-agent/src/core/sdk.ts +45 -9
- package/packages/pi-coding-agent/src/index.ts +1 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +47 -0
- package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +7 -2
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +4 -3
- package/packages/pi-coding-agent/src/modes/interactive/slash-command-handlers.ts +4 -2
- package/src/resources/extensions/claude-code-cli/stream-adapter.ts +13 -5
- package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +56 -4
- package/src/resources/extensions/gsd/auto/infra-errors.ts +38 -0
- package/src/resources/extensions/gsd/auto/loop.ts +45 -1
- package/src/resources/extensions/gsd/auto/session.ts +8 -0
- package/src/resources/extensions/gsd/auto-dashboard.ts +29 -18
- package/src/resources/extensions/gsd/auto.ts +68 -0
- package/src/resources/extensions/gsd/bootstrap/register-shortcuts.ts +82 -60
- package/src/resources/extensions/gsd/commands/handlers/auto.ts +10 -36
- package/src/resources/extensions/gsd/commands/handlers/core.ts +46 -11
- package/src/resources/extensions/gsd/commands/handlers/notifications-handler.ts +17 -7
- package/src/resources/extensions/gsd/commands/handlers/workflow.ts +4 -10
- package/src/resources/extensions/gsd/dashboard-overlay.ts +10 -3
- package/src/resources/extensions/gsd/forensics.ts +23 -7
- package/src/resources/extensions/gsd/guided-flow.ts +5 -10
- package/src/resources/extensions/gsd/interrupted-session.ts +1 -0
- package/src/resources/extensions/gsd/metrics.ts +12 -1
- package/src/resources/extensions/gsd/milestone-actions.ts +10 -3
- package/src/resources/extensions/gsd/notification-overlay.ts +24 -7
- package/src/resources/extensions/gsd/notification-store.ts +30 -0
- package/src/resources/extensions/gsd/notification-widget.ts +5 -14
- package/src/resources/extensions/gsd/parallel-monitor-overlay.ts +10 -3
- package/src/resources/extensions/gsd/shortcut-defs.ts +49 -0
- package/src/resources/extensions/gsd/tests/forensics-stuck-loops.test.ts +62 -0
- package/src/resources/extensions/gsd/tests/format-shortcut.test.ts +15 -0
- package/src/resources/extensions/gsd/tests/infra-errors-cooldown.test.ts +180 -0
- package/src/resources/extensions/gsd/tests/notification-store.test.ts +18 -0
- package/src/resources/extensions/gsd/tests/notification-widget.test.ts +3 -2
- package/src/resources/extensions/gsd/tests/notifications-handler.test.ts +90 -0
- package/src/resources/extensions/gsd/tests/parallel-monitor-overlay.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/park-db-sync.test.ts +18 -0
- package/src/resources/extensions/gsd/tests/register-shortcuts.test.ts +62 -5
- package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +90 -0
- /package/dist/web/standalone/.next/static/{DpE_M0QWUMtGDPkq_Dvfr → ug91LJa0m7OdzrTVaz_48}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{DpE_M0QWUMtGDPkq_Dvfr → ug91LJa0m7OdzrTVaz_48}/_ssgManifest.js +0 -0
|
@@ -43,6 +43,8 @@ export declare const NEW_MILESTONE_IDLE_TIMEOUT_MS = 120000;
|
|
|
43
43
|
export declare function isTerminalNotification(event: Record<string, unknown>): boolean;
|
|
44
44
|
export declare function isBlockedNotification(event: Record<string, unknown>): boolean;
|
|
45
45
|
export declare function isMilestoneReadyNotification(event: Record<string, unknown>): boolean;
|
|
46
|
+
export declare function isInteractiveHeadlessTool(toolName: string | undefined): boolean;
|
|
47
|
+
export declare function shouldArmHeadlessIdleTimeout(toolCallCount: number, interactiveToolCount: number): boolean;
|
|
46
48
|
export declare const FIRE_AND_FORGET_METHODS: Set<string>;
|
|
47
49
|
export declare const QUICK_COMMANDS: Set<string>;
|
|
48
50
|
export declare function isQuickCommand(command: string): boolean;
|
package/dist/headless-events.js
CHANGED
|
@@ -65,6 +65,7 @@ export const IDLE_TIMEOUT_MS = 15_000;
|
|
|
65
65
|
// between tool calls (e.g. after mkdir, before writing files). Use a
|
|
66
66
|
// longer idle timeout to avoid killing the session prematurely (#808).
|
|
67
67
|
export const NEW_MILESTONE_IDLE_TIMEOUT_MS = 120_000;
|
|
68
|
+
const INTERACTIVE_HEADLESS_TOOLS = new Set(['ask_user_questions', 'secure_env_collect']);
|
|
68
69
|
export function isTerminalNotification(event) {
|
|
69
70
|
if (event.type !== 'extension_ui_request' || event.method !== 'notify')
|
|
70
71
|
return false;
|
|
@@ -83,6 +84,12 @@ export function isMilestoneReadyNotification(event) {
|
|
|
83
84
|
return false;
|
|
84
85
|
return /milestone\s+m\d+.*ready/i.test(String(event.message ?? ''));
|
|
85
86
|
}
|
|
87
|
+
export function isInteractiveHeadlessTool(toolName) {
|
|
88
|
+
return INTERACTIVE_HEADLESS_TOOLS.has(String(toolName ?? ''));
|
|
89
|
+
}
|
|
90
|
+
export function shouldArmHeadlessIdleTimeout(toolCallCount, interactiveToolCount) {
|
|
91
|
+
return toolCallCount > 0 && interactiveToolCount === 0;
|
|
92
|
+
}
|
|
86
93
|
// ---------------------------------------------------------------------------
|
|
87
94
|
// Quick Command Detection
|
|
88
95
|
// ---------------------------------------------------------------------------
|
package/dist/headless.js
CHANGED
|
@@ -17,7 +17,7 @@ import { resolve } from 'node:path';
|
|
|
17
17
|
import { RpcClient, SessionManager } from '@gsd/pi-coding-agent';
|
|
18
18
|
import { getProjectSessionsDir } from './project-sessions.js';
|
|
19
19
|
import { loadAndValidateAnswerFile, AnswerInjector } from './headless-answers.js';
|
|
20
|
-
import { isTerminalNotification, isBlockedNotification, isMilestoneReadyNotification, isQuickCommand, FIRE_AND_FORGET_METHODS, IDLE_TIMEOUT_MS, NEW_MILESTONE_IDLE_TIMEOUT_MS, EXIT_SUCCESS, EXIT_ERROR, EXIT_BLOCKED, EXIT_CANCELLED, mapStatusToExitCode, } from './headless-events.js';
|
|
20
|
+
import { isTerminalNotification, isBlockedNotification, isMilestoneReadyNotification, isQuickCommand, FIRE_AND_FORGET_METHODS, IDLE_TIMEOUT_MS, NEW_MILESTONE_IDLE_TIMEOUT_MS, isInteractiveHeadlessTool, shouldArmHeadlessIdleTimeout, EXIT_SUCCESS, EXIT_ERROR, EXIT_BLOCKED, EXIT_CANCELLED, mapStatusToExitCode, } from './headless-events.js';
|
|
21
21
|
import { VALID_OUTPUT_FORMATS } from './headless-types.js';
|
|
22
22
|
import { handleExtensionUIRequest, formatProgress, formatThinkingLine, formatTextStart, formatTextEnd, formatThinkingStart, formatThinkingEnd, startSupervisedStdinReader, } from './headless-ui.js';
|
|
23
23
|
import { loadContext, bootstrapGsdProject, } from './headless-context.js';
|
|
@@ -282,6 +282,7 @@ async function runHeadlessOnce(options, restartCount) {
|
|
|
282
282
|
let exitCode = 0;
|
|
283
283
|
let milestoneReady = false; // tracks "Milestone X ready." for auto-chaining
|
|
284
284
|
const recentEvents = [];
|
|
285
|
+
const interactiveToolCallIds = new Set();
|
|
285
286
|
// JSON batch mode: cost aggregation (cumulative-max pattern per K004)
|
|
286
287
|
let cumulativeCostUsd = 0;
|
|
287
288
|
let cumulativeInputTokens = 0;
|
|
@@ -365,7 +366,7 @@ async function runHeadlessOnce(options, restartCount) {
|
|
|
365
366
|
function resetIdleTimer() {
|
|
366
367
|
if (idleTimer)
|
|
367
368
|
clearTimeout(idleTimer);
|
|
368
|
-
if (toolCallCount
|
|
369
|
+
if (shouldArmHeadlessIdleTimeout(toolCallCount, interactiveToolCallIds.size)) {
|
|
369
370
|
idleTimer = setTimeout(() => {
|
|
370
371
|
completed = true;
|
|
371
372
|
resolveCompletion();
|
|
@@ -386,13 +387,25 @@ async function runHeadlessOnce(options, restartCount) {
|
|
|
386
387
|
client.onEvent((event) => {
|
|
387
388
|
const eventObj = event;
|
|
388
389
|
trackEvent(eventObj);
|
|
390
|
+
const eventType = String(eventObj.type ?? '');
|
|
391
|
+
if (eventType === 'tool_execution_start') {
|
|
392
|
+
const toolCallId = String(eventObj.toolCallId ?? eventObj.id ?? '');
|
|
393
|
+
if (toolCallId && isInteractiveHeadlessTool(String(eventObj.toolName ?? ''))) {
|
|
394
|
+
interactiveToolCallIds.add(toolCallId);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
else if (eventType === 'tool_execution_end') {
|
|
398
|
+
const toolCallId = String(eventObj.toolCallId ?? eventObj.id ?? '');
|
|
399
|
+
if (toolCallId) {
|
|
400
|
+
interactiveToolCallIds.delete(toolCallId);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
389
403
|
resetIdleTimer();
|
|
390
404
|
// Answer injector: observe events for question metadata
|
|
391
405
|
injector?.observeEvent(eventObj);
|
|
392
406
|
// --json / --output-format stream-json: forward events as JSONL to stdout (filtered if --events)
|
|
393
407
|
// --output-format json (batch mode): suppress streaming, track cost for final result
|
|
394
408
|
if (options.json && options.outputFormat === 'stream-json') {
|
|
395
|
-
const eventType = String(eventObj.type ?? '');
|
|
396
409
|
if (!options.eventFilter || options.eventFilter.has(eventType)) {
|
|
397
410
|
process.stdout.write(JSON.stringify(eventObj) + '\n');
|
|
398
411
|
}
|
|
@@ -29,6 +29,15 @@ function createAssistantStream() {
|
|
|
29
29
|
throw new Error("Unexpected event type for final result");
|
|
30
30
|
});
|
|
31
31
|
}
|
|
32
|
+
export function getResultErrorMessage(result) {
|
|
33
|
+
if ("errors" in result && Array.isArray(result.errors) && result.errors.length > 0) {
|
|
34
|
+
return result.errors.join("; ");
|
|
35
|
+
}
|
|
36
|
+
if ("result" in result && typeof result.result === "string" && result.result.trim().length > 0) {
|
|
37
|
+
return result.result.trim();
|
|
38
|
+
}
|
|
39
|
+
return result.subtype === "success" ? "claude_code_request_failed" : result.subtype;
|
|
40
|
+
}
|
|
32
41
|
// ---------------------------------------------------------------------------
|
|
33
42
|
// Claude binary resolution
|
|
34
43
|
// ---------------------------------------------------------------------------
|
|
@@ -666,10 +675,7 @@ async function pumpSdkMessages(model, context, options, stream) {
|
|
|
666
675
|
timestamp: Date.now(),
|
|
667
676
|
};
|
|
668
677
|
if (result.is_error) {
|
|
669
|
-
|
|
670
|
-
? result.errors?.join("; ")
|
|
671
|
-
: result.subtype;
|
|
672
|
-
finalMessage.errorMessage = errText;
|
|
678
|
+
finalMessage.errorMessage = getResultErrorMessage(result);
|
|
673
679
|
stream.push({ type: "error", reason: "error", error: finalMessage });
|
|
674
680
|
}
|
|
675
681
|
else {
|
|
@@ -47,3 +47,37 @@ export function isInfrastructureError(err) {
|
|
|
47
47
|
return "SQLITE_CORRUPT";
|
|
48
48
|
return null;
|
|
49
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* Default wait duration when a cooldown error is detected but no specific
|
|
52
|
+
* expiry is available from AuthStorage (e.g., error propagated across
|
|
53
|
+
* process boundary without structured backoff data).
|
|
54
|
+
*/
|
|
55
|
+
export const COOLDOWN_FALLBACK_WAIT_MS = 35_000; // 35s — slightly longer than the 30s rate-limit backoff
|
|
56
|
+
/** Maximum consecutive cooldown retries before the auto-loop gives up. */
|
|
57
|
+
export const MAX_COOLDOWN_RETRIES = 5;
|
|
58
|
+
/**
|
|
59
|
+
* Detect whether an error is a transient credential cooldown that should
|
|
60
|
+
* be waited out rather than counted as a consecutive failure.
|
|
61
|
+
*
|
|
62
|
+
* Prefers the structured `CredentialCooldownError` (code: AUTH_COOLDOWN)
|
|
63
|
+
* thrown by sdk.ts. Falls back to message matching for errors that
|
|
64
|
+
* propagated across process boundaries without the typed class.
|
|
65
|
+
*/
|
|
66
|
+
export function isTransientCooldownError(err) {
|
|
67
|
+
if (err && typeof err === "object" && err.code === "AUTH_COOLDOWN") {
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
// Fallback: message match for cross-process error propagation
|
|
71
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
72
|
+
return /in a cooldown window/i.test(msg);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Extract retryAfterMs from a CredentialCooldownError, if available.
|
|
76
|
+
* Returns undefined for unstructured errors or when no retry hint exists.
|
|
77
|
+
*/
|
|
78
|
+
export function getCooldownRetryAfterMs(err) {
|
|
79
|
+
if (err && typeof err === "object" && err.code === "AUTH_COOLDOWN") {
|
|
80
|
+
return err.retryAfterMs;
|
|
81
|
+
}
|
|
82
|
+
return undefined;
|
|
83
|
+
}
|
|
@@ -11,7 +11,7 @@ import { MAX_LOOP_ITERATIONS, } from "./types.js";
|
|
|
11
11
|
import { _clearCurrentResolve } from "./resolve.js";
|
|
12
12
|
import { runPreDispatch, runDispatch, runGuards, runUnitPhase, runFinalize, } from "./phases.js";
|
|
13
13
|
import { debugLog } from "../debug-logger.js";
|
|
14
|
-
import { isInfrastructureError } from "./infra-errors.js";
|
|
14
|
+
import { isInfrastructureError, isTransientCooldownError, getCooldownRetryAfterMs, COOLDOWN_FALLBACK_WAIT_MS, MAX_COOLDOWN_RETRIES } from "./infra-errors.js";
|
|
15
15
|
import { resolveEngine } from "../engine-resolver.js";
|
|
16
16
|
/**
|
|
17
17
|
* Main auto-mode execution loop. Iterates: derive → dispatch → guards →
|
|
@@ -26,6 +26,7 @@ export async function autoLoop(ctx, pi, s, deps) {
|
|
|
26
26
|
let iteration = 0;
|
|
27
27
|
const loopState = { recentUnits: [], stuckRecoveryAttempts: 0, consecutiveFinalizeTimeouts: 0 };
|
|
28
28
|
let consecutiveErrors = 0;
|
|
29
|
+
let consecutiveCooldowns = 0;
|
|
29
30
|
const recentErrorMessages = [];
|
|
30
31
|
while (s.active) {
|
|
31
32
|
iteration++;
|
|
@@ -158,6 +159,7 @@ export async function autoLoop(ctx, pi, s, deps) {
|
|
|
158
159
|
});
|
|
159
160
|
deps.clearUnitTimeout();
|
|
160
161
|
consecutiveErrors = 0;
|
|
162
|
+
consecutiveCooldowns = 0;
|
|
161
163
|
recentErrorMessages.length = 0;
|
|
162
164
|
deps.emitJournalEvent({ ts: new Date().toISOString(), flowId, seq: nextSeq(), eventType: "iteration-end", data: { iteration } });
|
|
163
165
|
debugLog("autoLoop", { phase: "iteration-complete", iteration });
|
|
@@ -220,6 +222,7 @@ export async function autoLoop(ctx, pi, s, deps) {
|
|
|
220
222
|
if (finalizeResult.action === "continue")
|
|
221
223
|
continue;
|
|
222
224
|
consecutiveErrors = 0; // Iteration completed successfully
|
|
225
|
+
consecutiveCooldowns = 0;
|
|
223
226
|
recentErrorMessages.length = 0;
|
|
224
227
|
deps.emitJournalEvent({ ts: new Date().toISOString(), flowId, seq: nextSeq(), eventType: "iteration-end", data: { iteration } });
|
|
225
228
|
debugLog("autoLoop", { phase: "iteration-complete", iteration });
|
|
@@ -246,6 +249,34 @@ export async function autoLoop(ctx, pi, s, deps) {
|
|
|
246
249
|
await deps.stopAuto(ctx, pi, `Infrastructure error (${infraCode}): not recoverable by retry`);
|
|
247
250
|
break;
|
|
248
251
|
}
|
|
252
|
+
// ── Credential cooldown: wait and retry with bounded budget ──
|
|
253
|
+
// A 429 triggers a 30s credential backoff in AuthStorage. If the SDK's
|
|
254
|
+
// getApiKey() retries couldn't outlast the window, the error surfaces
|
|
255
|
+
// here. Wait for the cooldown to clear rather than counting it as a
|
|
256
|
+
// consecutive failure — but cap retries so we don't spin for hours
|
|
257
|
+
// on persistent quota exhaustion.
|
|
258
|
+
if (isTransientCooldownError(loopErr)) {
|
|
259
|
+
consecutiveCooldowns++;
|
|
260
|
+
const retryAfterMs = getCooldownRetryAfterMs(loopErr);
|
|
261
|
+
debugLog("autoLoop", {
|
|
262
|
+
phase: "cooldown-wait",
|
|
263
|
+
iteration,
|
|
264
|
+
consecutiveCooldowns,
|
|
265
|
+
retryAfterMs,
|
|
266
|
+
error: msg,
|
|
267
|
+
});
|
|
268
|
+
if (consecutiveCooldowns > MAX_COOLDOWN_RETRIES) {
|
|
269
|
+
ctx.ui.notify(`Auto-mode stopped: ${consecutiveCooldowns} consecutive credential cooldowns — rate limit or quota may be persistently exhausted.`, "error");
|
|
270
|
+
await deps.stopAuto(ctx, pi, `${consecutiveCooldowns} consecutive credential cooldowns exceeded retry budget`);
|
|
271
|
+
break;
|
|
272
|
+
}
|
|
273
|
+
const waitMs = (retryAfterMs !== undefined && retryAfterMs > 0 && retryAfterMs <= 60_000)
|
|
274
|
+
? retryAfterMs + 500 // Use structured hint + small buffer
|
|
275
|
+
: COOLDOWN_FALLBACK_WAIT_MS;
|
|
276
|
+
ctx.ui.notify(`Credentials in cooldown (${consecutiveCooldowns}/${MAX_COOLDOWN_RETRIES}) — waiting ${Math.round(waitMs / 1000)}s before retrying.`, "warning");
|
|
277
|
+
await new Promise(resolve => setTimeout(resolve, waitMs));
|
|
278
|
+
continue; // Retry iteration without incrementing consecutiveErrors
|
|
279
|
+
}
|
|
249
280
|
consecutiveErrors++;
|
|
250
281
|
recentErrorMessages.push(msg.length > 120 ? msg.slice(0, 120) + "..." : msg);
|
|
251
282
|
debugLog("autoLoop", {
|
|
@@ -36,6 +36,10 @@ export class AutoSession {
|
|
|
36
36
|
previousProjectRootEnv = null;
|
|
37
37
|
hadProjectRootEnv = false;
|
|
38
38
|
projectRootEnvCaptured = false;
|
|
39
|
+
previousMilestoneLockEnv = null;
|
|
40
|
+
hadMilestoneLockEnv = false;
|
|
41
|
+
milestoneLockEnvCaptured = false;
|
|
42
|
+
sessionMilestoneLock = null;
|
|
39
43
|
gitService = null;
|
|
40
44
|
// ── Dispatch counters ────────────────────────────────────────────────────
|
|
41
45
|
unitDispatchCount = new Map();
|
|
@@ -140,6 +144,10 @@ export class AutoSession {
|
|
|
140
144
|
this.previousProjectRootEnv = null;
|
|
141
145
|
this.hadProjectRootEnv = false;
|
|
142
146
|
this.projectRootEnvCaptured = false;
|
|
147
|
+
this.previousMilestoneLockEnv = null;
|
|
148
|
+
this.hadMilestoneLockEnv = false;
|
|
149
|
+
this.milestoneLockEnvCaptured = false;
|
|
150
|
+
this.sessionMilestoneLock = null;
|
|
143
151
|
this.gitService = null;
|
|
144
152
|
// Dispatch
|
|
145
153
|
this.unitDispatchCount.clear();
|
|
@@ -10,7 +10,6 @@ import { getActiveHook } from "./post-unit-hooks.js";
|
|
|
10
10
|
import { getLedger, getProjectTotals } from "./metrics.js";
|
|
11
11
|
import { getErrorMessage } from "./error-utils.js";
|
|
12
12
|
import { isDbAvailable, getMilestoneSlices, getSliceTasks } from "./gsd-db.js";
|
|
13
|
-
import { formatShortcut } from "./files.js";
|
|
14
13
|
import { readFileSync, writeFileSync, existsSync } from "node:fs";
|
|
15
14
|
import { execFileSync } from "node:child_process";
|
|
16
15
|
import { truncateToWidth, visibleWidth } from "@gsd/pi-tui";
|
|
@@ -23,6 +22,7 @@ import { resolveServiceTierIcon, getEffectiveServiceTier } from "./service-tier.
|
|
|
23
22
|
import { parseUnitId } from "./unit-id.js";
|
|
24
23
|
import { formatRtkSavingsLabel, getRtkSessionSavings, } from "../shared/rtk-session-stats.js";
|
|
25
24
|
import { logWarning } from "./workflow-logger.js";
|
|
25
|
+
import { formattedShortcutPair } from "./shortcut-defs.js";
|
|
26
26
|
// ─── UAT Slice Extraction ─────────────────────────────────────────────────────
|
|
27
27
|
/**
|
|
28
28
|
* Extract the target slice ID from a run-uat unit ID (e.g. "M001/S01" → "S01").
|
|
@@ -282,12 +282,23 @@ function getLastCommit(basePath) {
|
|
|
282
282
|
}
|
|
283
283
|
// ─── Footer Factory ───────────────────────────────────────────────────────────
|
|
284
284
|
/**
|
|
285
|
-
* Footer factory
|
|
286
|
-
*
|
|
287
|
-
* progress widget instead, so there's no gap or redundancy.
|
|
285
|
+
* Footer factory used by auto-mode.
|
|
286
|
+
* Keep footer minimal but preserve extension status context from setStatus().
|
|
288
287
|
*/
|
|
289
|
-
|
|
290
|
-
|
|
288
|
+
function sanitizeFooterStatus(text) {
|
|
289
|
+
return text.replace(/\s+/g, " ").trim();
|
|
290
|
+
}
|
|
291
|
+
export const hideFooter = (_tui, theme, footerData) => ({
|
|
292
|
+
render(width) {
|
|
293
|
+
const extensionStatuses = footerData.getExtensionStatuses();
|
|
294
|
+
if (extensionStatuses.size === 0)
|
|
295
|
+
return [];
|
|
296
|
+
const statusLine = Array.from(extensionStatuses.entries())
|
|
297
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
298
|
+
.map(([, text]) => sanitizeFooterStatus(text))
|
|
299
|
+
.join(" ");
|
|
300
|
+
return [truncateToWidth(theme.fg("dim", statusLine), width, theme.fg("dim", "..."))];
|
|
301
|
+
},
|
|
291
302
|
invalidate() { },
|
|
292
303
|
dispose() { },
|
|
293
304
|
});
|
|
@@ -522,13 +533,6 @@ export function updateProgressWidget(ctx, unitType, unitId, state, accessors, ti
|
|
|
522
533
|
: theme.fg("dim", elapsed))
|
|
523
534
|
: "";
|
|
524
535
|
lines.push(rightAlign(headerLeft, headerRight, width));
|
|
525
|
-
// Worktree/branch right-aligned below header
|
|
526
|
-
const branchLabel = worktreeName && cachedBranch
|
|
527
|
-
? `${worktreeName} (${cachedBranch})`
|
|
528
|
-
: cachedBranch ?? "";
|
|
529
|
-
if (branchLabel) {
|
|
530
|
-
lines.push(rightAlign("", theme.fg("dim", branchLabel), width));
|
|
531
|
-
}
|
|
532
536
|
// Show health signal details when degraded (yellow/red)
|
|
533
537
|
if (score.level !== "green" && score.signals.length > 0 && widgetMode !== "min") {
|
|
534
538
|
// Show up to 3 most relevant signals in compact form
|
|
@@ -776,16 +780,18 @@ export function updateProgressWidget(ctx, unitType, unitId, state, accessors, ti
|
|
|
776
780
|
// Hints line
|
|
777
781
|
const hintParts = [];
|
|
778
782
|
hintParts.push("esc pause");
|
|
779
|
-
hintParts.push(`${
|
|
783
|
+
hintParts.push(`${formattedShortcutPair("dashboard")} dashboard`);
|
|
784
|
+
hintParts.push(`${formattedShortcutPair("parallel")} parallel`);
|
|
780
785
|
const hintStr = theme.fg("dim", hintParts.join(" | "));
|
|
781
786
|
const commitStr = lastCommit
|
|
782
787
|
? theme.fg("dim", `${lastCommit.timeAgo} ago: ${commitMsg}`)
|
|
783
788
|
: "";
|
|
789
|
+
const locationStr = theme.fg("dim", widgetPwd);
|
|
784
790
|
if (commitStr) {
|
|
785
|
-
lines.push(rightAlign(`${pad}${commitStr}`, hintStr, width));
|
|
791
|
+
lines.push(rightAlign(`${pad}${locationStr} · ${commitStr}`, hintStr, width));
|
|
786
792
|
}
|
|
787
793
|
else {
|
|
788
|
-
lines.push(rightAlign(
|
|
794
|
+
lines.push(rightAlign(`${pad}${locationStr}`, hintStr, width));
|
|
789
795
|
}
|
|
790
796
|
lines.push(...ui.bar());
|
|
791
797
|
cachedLines = lines;
|
|
@@ -49,6 +49,7 @@ import { pruneQueueOrder } from "./queue-order.js";
|
|
|
49
49
|
import { debugLog, isDebugEnabled, writeDebugSummary } from "./debug-logger.js";
|
|
50
50
|
import { reconcileMergeState, } from "./auto-recovery.js";
|
|
51
51
|
import { resolveDispatch, DISPATCH_RULES } from "./auto-dispatch.js";
|
|
52
|
+
import { getErrorMessage } from "./error-utils.js";
|
|
52
53
|
import { initRegistry, convertDispatchRules } from "./rule-registry.js";
|
|
53
54
|
import { emitJournalEvent as _emitJournalEvent } from "./journal.js";
|
|
54
55
|
import { updateProgressWidget as _updateProgressWidget, updateSliceProgressCache, clearSliceProgressCache, hideFooter, } from "./auto-dashboard.js";
|
|
@@ -103,6 +104,40 @@ function restoreProjectRootEnv() {
|
|
|
103
104
|
s.hadProjectRootEnv = false;
|
|
104
105
|
s.projectRootEnvCaptured = false;
|
|
105
106
|
}
|
|
107
|
+
function captureMilestoneLockEnv(milestoneId) {
|
|
108
|
+
if (!s.milestoneLockEnvCaptured) {
|
|
109
|
+
s.hadMilestoneLockEnv = Object.prototype.hasOwnProperty.call(process.env, "GSD_MILESTONE_LOCK");
|
|
110
|
+
s.previousMilestoneLockEnv = process.env.GSD_MILESTONE_LOCK ?? null;
|
|
111
|
+
s.milestoneLockEnvCaptured = true;
|
|
112
|
+
}
|
|
113
|
+
if (milestoneId) {
|
|
114
|
+
process.env.GSD_MILESTONE_LOCK = milestoneId;
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
delete process.env.GSD_MILESTONE_LOCK;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
function restoreMilestoneLockEnv() {
|
|
121
|
+
if (!s.milestoneLockEnvCaptured)
|
|
122
|
+
return;
|
|
123
|
+
if (s.hadMilestoneLockEnv && s.previousMilestoneLockEnv !== null) {
|
|
124
|
+
process.env.GSD_MILESTONE_LOCK = s.previousMilestoneLockEnv;
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
delete process.env.GSD_MILESTONE_LOCK;
|
|
128
|
+
}
|
|
129
|
+
s.previousMilestoneLockEnv = null;
|
|
130
|
+
s.hadMilestoneLockEnv = false;
|
|
131
|
+
s.milestoneLockEnvCaptured = false;
|
|
132
|
+
}
|
|
133
|
+
export function startAutoDetached(ctx, pi, base, verboseMode, options) {
|
|
134
|
+
void startAuto(ctx, pi, base, verboseMode, options).catch((err) => {
|
|
135
|
+
const message = getErrorMessage(err);
|
|
136
|
+
ctx.ui.notify(`Auto-start failed: ${message}`, "error");
|
|
137
|
+
logWarning("engine", `auto start error: ${message}`, { file: "auto.ts" });
|
|
138
|
+
debugLog("auto-start-failed", { error: message });
|
|
139
|
+
});
|
|
140
|
+
}
|
|
106
141
|
export function shouldUseWorktreeIsolation() {
|
|
107
142
|
const prefs = loadEffectiveGSDPreferences()?.preferences?.git;
|
|
108
143
|
if (prefs?.isolation === "worktree")
|
|
@@ -324,6 +359,7 @@ function clearUnitTimeout() {
|
|
|
324
359
|
/** Build snapshot metric opts. */
|
|
325
360
|
function buildSnapshotOpts(_unitType, _unitId) {
|
|
326
361
|
return {
|
|
362
|
+
...(s.autoStartTime > 0 ? { autoSessionKey: String(s.autoStartTime) } : {}),
|
|
327
363
|
promptCharCount: s.lastPromptCharCount,
|
|
328
364
|
baselineCharCount: s.lastBaselineCharCount,
|
|
329
365
|
...(s.currentUnitRouting ?? {}),
|
|
@@ -340,6 +376,7 @@ function handleLostSessionLock(ctx, lockStatus) {
|
|
|
340
376
|
s.paused = false;
|
|
341
377
|
clearUnitTimeout();
|
|
342
378
|
restoreProjectRootEnv();
|
|
379
|
+
restoreMilestoneLockEnv();
|
|
343
380
|
deregisterSigtermHandler();
|
|
344
381
|
clearCmuxSidebar(loadEffectiveGSDPreferences()?.preferences);
|
|
345
382
|
const base = lockBase();
|
|
@@ -371,6 +408,7 @@ function cleanupAfterLoopExit(ctx) {
|
|
|
371
408
|
s.active = false;
|
|
372
409
|
clearUnitTimeout();
|
|
373
410
|
restoreProjectRootEnv();
|
|
411
|
+
restoreMilestoneLockEnv();
|
|
374
412
|
// Clear crash lock and release session lock so the next `/gsd next` does
|
|
375
413
|
// not see a stale lock with the current PID and treat it as a "remote"
|
|
376
414
|
// session (which would cause it to SIGTERM itself). (#2730)
|
|
@@ -628,6 +666,7 @@ export async function stopAuto(ctx, pi, reason) {
|
|
|
628
666
|
ctx?.ui.setWidget("gsd-progress", undefined);
|
|
629
667
|
ctx?.ui.setFooter(undefined);
|
|
630
668
|
restoreProjectRootEnv();
|
|
669
|
+
restoreMilestoneLockEnv();
|
|
631
670
|
// Reset all session state in one call
|
|
632
671
|
s.reset();
|
|
633
672
|
}
|
|
@@ -673,6 +712,7 @@ export async function pauseAuto(ctx, _pi, _errorContext) {
|
|
|
673
712
|
activeEngineId: s.activeEngineId,
|
|
674
713
|
activeRunDir: s.activeRunDir,
|
|
675
714
|
autoStartTime: s.autoStartTime,
|
|
715
|
+
milestoneLock: s.sessionMilestoneLock ?? undefined,
|
|
676
716
|
};
|
|
677
717
|
const runtimeDir = join(gsdRoot(s.originalBasePath || s.basePath), "runtime");
|
|
678
718
|
mkdirSync(runtimeDir, { recursive: true });
|
|
@@ -704,6 +744,7 @@ export async function pauseAuto(ctx, _pi, _errorContext) {
|
|
|
704
744
|
s.active = false;
|
|
705
745
|
s.paused = true;
|
|
706
746
|
restoreProjectRootEnv();
|
|
747
|
+
restoreMilestoneLockEnv();
|
|
707
748
|
s.pendingVerificationRetry = null;
|
|
708
749
|
s.verificationRetryCount.clear();
|
|
709
750
|
ctx?.ui.setStatus("gsd-auto", "paused");
|
|
@@ -859,6 +900,12 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
|
|
|
859
900
|
}
|
|
860
901
|
const requestedStepMode = options?.step ?? false;
|
|
861
902
|
const interruptedAssessment = options?.interrupted ?? null;
|
|
903
|
+
if (options?.milestoneLock !== undefined) {
|
|
904
|
+
s.sessionMilestoneLock = options.milestoneLock ?? null;
|
|
905
|
+
}
|
|
906
|
+
if (s.sessionMilestoneLock) {
|
|
907
|
+
captureMilestoneLockEnv(s.sessionMilestoneLock);
|
|
908
|
+
}
|
|
862
909
|
// Escape stale worktree cwd from a previous milestone (#608).
|
|
863
910
|
base = escapeStaleWorktree(base);
|
|
864
911
|
const freshStartAssessment = interruptedAssessment
|
|
@@ -883,6 +930,7 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
|
|
|
883
930
|
s.originalBasePath = meta.originalBasePath || base;
|
|
884
931
|
s.stepMode = meta.stepMode ?? requestedStepMode;
|
|
885
932
|
s.autoStartTime = meta.autoStartTime || Date.now();
|
|
933
|
+
s.sessionMilestoneLock = meta.milestoneLock ?? null;
|
|
886
934
|
s.paused = true;
|
|
887
935
|
try {
|
|
888
936
|
unlinkSync(pausedPath);
|
|
@@ -918,6 +966,7 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
|
|
|
918
966
|
s.pausedUnitType = meta.unitType ?? null;
|
|
919
967
|
s.pausedUnitId = meta.unitId ?? null;
|
|
920
968
|
s.autoStartTime = meta.autoStartTime || Date.now();
|
|
969
|
+
s.sessionMilestoneLock = meta.milestoneLock ?? null;
|
|
921
970
|
s.paused = true;
|
|
922
971
|
try {
|
|
923
972
|
unlinkSync(pausedPath);
|
|
@@ -946,6 +995,9 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
|
|
|
946
995
|
if (!s.autoStartTime || s.autoStartTime <= 0)
|
|
947
996
|
s.autoStartTime = Date.now();
|
|
948
997
|
}
|
|
998
|
+
if (s.sessionMilestoneLock) {
|
|
999
|
+
captureMilestoneLockEnv(s.sessionMilestoneLock);
|
|
1000
|
+
}
|
|
949
1001
|
if (!s.paused) {
|
|
950
1002
|
s.stepMode = requestedStepMode;
|
|
951
1003
|
}
|
|
@@ -4,61 +4,76 @@ import { Key } from "@gsd/pi-tui";
|
|
|
4
4
|
import { GSDDashboardOverlay } from "../dashboard-overlay.js";
|
|
5
5
|
import { GSDNotificationOverlay } from "../notification-overlay.js";
|
|
6
6
|
import { ParallelMonitorOverlay } from "../parallel-monitor-overlay.js";
|
|
7
|
+
import { GSD_SHORTCUTS } from "../shortcut-defs.js";
|
|
7
8
|
import { projectRoot } from "../commands/context.js";
|
|
8
9
|
import { shortcutDesc } from "../../shared/mod.js";
|
|
9
10
|
export function registerShortcuts(pi) {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
11
|
+
const overlayOptions = {
|
|
12
|
+
width: "90%",
|
|
13
|
+
minWidth: 80,
|
|
14
|
+
maxHeight: "92%",
|
|
15
|
+
anchor: "center",
|
|
16
|
+
};
|
|
17
|
+
const openDashboardOverlay = async (ctx) => {
|
|
18
|
+
const basePath = projectRoot();
|
|
19
|
+
if (!existsSync(join(basePath, ".gsd"))) {
|
|
20
|
+
ctx.ui.notify("No .gsd/ directory found. Run /gsd to start.", "info");
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
await ctx.ui.custom((tui, theme, _kb, done) => new GSDDashboardOverlay(tui, theme, () => done(true)), {
|
|
24
|
+
overlay: true,
|
|
25
|
+
overlayOptions,
|
|
26
|
+
});
|
|
27
|
+
};
|
|
28
|
+
const openNotificationsOverlay = async (ctx) => {
|
|
29
|
+
await ctx.ui.custom((tui, theme, _kb, done) => new GSDNotificationOverlay(tui, theme, () => done(true)), {
|
|
30
|
+
overlay: true,
|
|
31
|
+
overlayOptions: {
|
|
32
|
+
width: "80%",
|
|
33
|
+
minWidth: 60,
|
|
34
|
+
maxHeight: "88%",
|
|
35
|
+
anchor: "center",
|
|
36
|
+
backdrop: true,
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
};
|
|
40
|
+
const openParallelOverlay = async (ctx) => {
|
|
41
|
+
const basePath = projectRoot();
|
|
42
|
+
const parallelDir = join(basePath, ".gsd", "parallel");
|
|
43
|
+
if (!existsSync(parallelDir)) {
|
|
44
|
+
ctx.ui.notify("No parallel workers found. Run /gsd parallel start first.", "info");
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
await ctx.ui.custom((tui, theme, _kb, done) => new ParallelMonitorOverlay(tui, theme, () => done(true), basePath), {
|
|
48
|
+
overlay: true,
|
|
49
|
+
overlayOptions,
|
|
50
|
+
});
|
|
51
|
+
};
|
|
52
|
+
pi.registerShortcut(Key.ctrlAlt(GSD_SHORTCUTS.dashboard.key), {
|
|
53
|
+
description: shortcutDesc(GSD_SHORTCUTS.dashboard.action, GSD_SHORTCUTS.dashboard.command),
|
|
54
|
+
handler: openDashboardOverlay,
|
|
28
55
|
});
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
overlay: true,
|
|
34
|
-
overlayOptions: {
|
|
35
|
-
width: "80%",
|
|
36
|
-
minWidth: 60,
|
|
37
|
-
maxHeight: "88%",
|
|
38
|
-
anchor: "center",
|
|
39
|
-
backdrop: true,
|
|
40
|
-
},
|
|
41
|
-
});
|
|
42
|
-
},
|
|
56
|
+
// Fallback for terminals where Ctrl+Alt letter chords are not forwarded reliably.
|
|
57
|
+
pi.registerShortcut(Key.ctrlShift(GSD_SHORTCUTS.dashboard.key), {
|
|
58
|
+
description: shortcutDesc(`${GSD_SHORTCUTS.dashboard.action} (fallback)`, GSD_SHORTCUTS.dashboard.command),
|
|
59
|
+
handler: openDashboardOverlay,
|
|
43
60
|
});
|
|
44
|
-
pi.registerShortcut(Key.ctrlAlt(
|
|
45
|
-
description: shortcutDesc(
|
|
46
|
-
handler:
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
});
|
|
62
|
-
},
|
|
61
|
+
pi.registerShortcut(Key.ctrlAlt(GSD_SHORTCUTS.notifications.key), {
|
|
62
|
+
description: shortcutDesc(GSD_SHORTCUTS.notifications.action, GSD_SHORTCUTS.notifications.command),
|
|
63
|
+
handler: openNotificationsOverlay,
|
|
64
|
+
});
|
|
65
|
+
// Fallback for terminals where Ctrl+Alt letter chords are not forwarded reliably.
|
|
66
|
+
pi.registerShortcut(Key.ctrlShift(GSD_SHORTCUTS.notifications.key), {
|
|
67
|
+
description: shortcutDesc(`${GSD_SHORTCUTS.notifications.action} (fallback)`, GSD_SHORTCUTS.notifications.command),
|
|
68
|
+
handler: openNotificationsOverlay,
|
|
69
|
+
});
|
|
70
|
+
pi.registerShortcut(Key.ctrlAlt(GSD_SHORTCUTS.parallel.key), {
|
|
71
|
+
description: shortcutDesc(GSD_SHORTCUTS.parallel.action, GSD_SHORTCUTS.parallel.command),
|
|
72
|
+
handler: openParallelOverlay,
|
|
73
|
+
});
|
|
74
|
+
// Fallback for terminals where Ctrl+Alt letter chords are not forwarded reliably.
|
|
75
|
+
pi.registerShortcut(Key.ctrlShift(GSD_SHORTCUTS.parallel.key), {
|
|
76
|
+
description: shortcutDesc(`${GSD_SHORTCUTS.parallel.action} (fallback)`, GSD_SHORTCUTS.parallel.command),
|
|
77
|
+
handler: openParallelOverlay,
|
|
63
78
|
});
|
|
64
79
|
}
|