gsd-pi 2.71.0-dev.06b86c6 → 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/resource-loader.js +6 -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 +11 -11
- 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 +11 -11
- 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/{dYVdRaunb2ZSEA8fjkT-V → ug91LJa0m7OdzrTVaz_48}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{dYVdRaunb2ZSEA8fjkT-V → ug91LJa0m7OdzrTVaz_48}/_ssgManifest.js +0 -0
|
@@ -1,4 +1,24 @@
|
|
|
1
1
|
import { join } from "node:path";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Structured error thrown when all credentials for a provider are in a
|
|
5
|
+
* backoff window. Carries typed metadata so callers (e.g. the auto-loop)
|
|
6
|
+
* can make informed retry decisions instead of string-matching the message.
|
|
7
|
+
*/
|
|
8
|
+
export class CredentialCooldownError extends Error {
|
|
9
|
+
readonly code = "AUTH_COOLDOWN" as const;
|
|
10
|
+
/** Milliseconds until the earliest credential becomes available, or undefined if unknown. */
|
|
11
|
+
readonly retryAfterMs: number | undefined;
|
|
12
|
+
|
|
13
|
+
constructor(provider: string, retryAfterMs?: number) {
|
|
14
|
+
super(
|
|
15
|
+
`All credentials for "${provider}" are in a cooldown window. ` +
|
|
16
|
+
`Please wait a moment and try again, or switch to a different provider.`,
|
|
17
|
+
);
|
|
18
|
+
this.name = "CredentialCooldownError";
|
|
19
|
+
this.retryAfterMs = retryAfterMs;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
2
22
|
import { Agent, type AgentMessage, type ThinkingLevel } from "@gsd/pi-agent-core";
|
|
3
23
|
import type { Message, Model } from "@gsd/pi-ai";
|
|
4
24
|
import { getAgentDir, getDocsPath } from "../config.js";
|
|
@@ -363,8 +383,12 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
363
383
|
|
|
364
384
|
// Retry key resolution with backoff to handle transient network failures
|
|
365
385
|
// (e.g., OAuth token refresh failing due to brief connectivity loss).
|
|
386
|
+
// When credentials are in a cooldown window (e.g., after a 429), wait
|
|
387
|
+
// for the backoff to expire instead of using fixed delays that are
|
|
388
|
+
// shorter than the cooldown duration.
|
|
366
389
|
const maxAttempts = 3;
|
|
367
390
|
const baseDelayMs = 2000;
|
|
391
|
+
const maxCooldownWaitMs = 60_000; // Don't wait longer than 60s (skip quota-exhausted 30min backoffs)
|
|
368
392
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
369
393
|
const key = await modelRegistry.getApiKeyForProvider(resolvedProvider);
|
|
370
394
|
if (key) return key;
|
|
@@ -379,7 +403,21 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
379
403
|
const isOAuth = model && modelRegistry.isUsingOAuth(model);
|
|
380
404
|
if (!hasAuth && !isOAuth) break;
|
|
381
405
|
|
|
382
|
-
//
|
|
406
|
+
// If credentials are in a cooldown window, wait for the earliest
|
|
407
|
+
// one to expire rather than using a fixed delay that's too short.
|
|
408
|
+
const backoffExpiry = modelRegistry.authStorage.getEarliestBackoffExpiry(resolvedProvider);
|
|
409
|
+
if (backoffExpiry !== undefined) {
|
|
410
|
+
const waitMs = backoffExpiry - Date.now() + 500; // 500ms buffer
|
|
411
|
+
if (waitMs > 0 && waitMs <= maxCooldownWaitMs) {
|
|
412
|
+
await new Promise(resolve => setTimeout(resolve, waitMs));
|
|
413
|
+
continue; // Retry immediately after cooldown clears
|
|
414
|
+
}
|
|
415
|
+
if (waitMs > maxCooldownWaitMs) {
|
|
416
|
+
break; // Quota-exhausted or very long backoff — don't block
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// Standard exponential backoff for non-cooldown transient failures
|
|
383
421
|
await new Promise(resolve => setTimeout(resolve, baseDelayMs * attempt));
|
|
384
422
|
}
|
|
385
423
|
|
|
@@ -390,10 +428,9 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
390
428
|
// the retry handler and creating cascading error entries (#3429).
|
|
391
429
|
const hasAuth = modelRegistry.authStorage.hasAuth(resolvedProvider);
|
|
392
430
|
if (hasAuth) {
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
);
|
|
431
|
+
const expiry = modelRegistry.authStorage.getEarliestBackoffExpiry(resolvedProvider);
|
|
432
|
+
const retryAfterMs = expiry !== undefined ? Math.max(0, expiry - Date.now()) : undefined;
|
|
433
|
+
throw new CredentialCooldownError(resolvedProvider, retryAfterMs);
|
|
397
434
|
}
|
|
398
435
|
const model = agent.state.model;
|
|
399
436
|
const isOAuth = model && modelRegistry.isUsingOAuth(model);
|
|
@@ -401,10 +438,9 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
401
438
|
// If credentials exist but are all in a backoff window (quota / rate-limit),
|
|
402
439
|
// surface a specific message instead of the misleading "Authentication failed".
|
|
403
440
|
if (modelRegistry.authStorage.areAllCredentialsBackedOff(resolvedProvider)) {
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
);
|
|
441
|
+
const expiry = modelRegistry.authStorage.getEarliestBackoffExpiry(resolvedProvider);
|
|
442
|
+
const retryAfterMs = expiry !== undefined ? Math.max(0, expiry - Date.now()) : undefined;
|
|
443
|
+
throw new CredentialCooldownError(resolvedProvider, retryAfterMs);
|
|
408
444
|
}
|
|
409
445
|
throw new Error(
|
|
410
446
|
`Authentication failed for "${resolvedProvider}". ` +
|
|
@@ -325,6 +325,29 @@ export class ToolExecutionComponent extends Container {
|
|
|
325
325
|
this.maybeConvertImagesForKitty();
|
|
326
326
|
}
|
|
327
327
|
|
|
328
|
+
/**
|
|
329
|
+
* Finalize a pending tool call as failed/interrupted while preserving any streamed partial output.
|
|
330
|
+
*/
|
|
331
|
+
completeWithError(message?: string): void {
|
|
332
|
+
this.isPartial = false;
|
|
333
|
+
if (this.result) {
|
|
334
|
+
let content = this.result.content;
|
|
335
|
+
if (message) {
|
|
336
|
+
const alreadyHasMessage = content.some((block) => block.type === "text" && block.text === message);
|
|
337
|
+
if (!alreadyHasMessage) {
|
|
338
|
+
content = [...content, { type: "text", text: message }];
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
this.result = { ...this.result, content, isError: true };
|
|
342
|
+
} else {
|
|
343
|
+
this.result = {
|
|
344
|
+
content: message ? [{ type: "text", text: message }] : [],
|
|
345
|
+
isError: true,
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
this.updateDisplay();
|
|
349
|
+
}
|
|
350
|
+
|
|
328
351
|
/**
|
|
329
352
|
* Convert non-PNG images to PNG for Kitty graphics protocol.
|
|
330
353
|
* Kitty requires PNG format (f=100), so JPEG/GIF/WebP won't display.
|
|
@@ -652,6 +675,12 @@ export class ToolExecutionComponent extends Container {
|
|
|
652
675
|
text = `${theme.fg("toolTitle", theme.bold("read"))} ${pathDisplay}`;
|
|
653
676
|
|
|
654
677
|
if (this.result) {
|
|
678
|
+
if (this.result.isError) {
|
|
679
|
+
const errorText = this.getTextOutput().trim() || "read failed";
|
|
680
|
+
text += `\n\n${theme.fg("error", errorText)}`;
|
|
681
|
+
return text;
|
|
682
|
+
}
|
|
683
|
+
|
|
655
684
|
const rawOutput = this.getTextOutput();
|
|
656
685
|
// Strip hashline prefixes (e.g. "1#BQ:content") for TUI display
|
|
657
686
|
const output = rawOutput.replace(/^(\s*)\d+#[ZPMQVRWSNKTXJBYH]{2}:/gm, "$1");
|
|
@@ -804,6 +833,12 @@ export class ToolExecutionComponent extends Container {
|
|
|
804
833
|
}
|
|
805
834
|
|
|
806
835
|
if (this.result) {
|
|
836
|
+
if (this.result.isError) {
|
|
837
|
+
const errorText = this.getTextOutput().trim() || "ls failed";
|
|
838
|
+
text += `\n\n${theme.fg("error", errorText)}`;
|
|
839
|
+
return text;
|
|
840
|
+
}
|
|
841
|
+
|
|
807
842
|
const output = this.getTextOutput().trim();
|
|
808
843
|
if (output) {
|
|
809
844
|
const lines = output.split("\n");
|
|
@@ -846,6 +881,12 @@ export class ToolExecutionComponent extends Container {
|
|
|
846
881
|
}
|
|
847
882
|
|
|
848
883
|
if (this.result) {
|
|
884
|
+
if (this.result.isError) {
|
|
885
|
+
const errorText = this.getTextOutput().trim() || "find failed";
|
|
886
|
+
text += `\n\n${theme.fg("error", errorText)}`;
|
|
887
|
+
return text;
|
|
888
|
+
}
|
|
889
|
+
|
|
849
890
|
const output = this.getTextOutput().trim();
|
|
850
891
|
if (output) {
|
|
851
892
|
const lines = output.split("\n");
|
|
@@ -892,6 +933,12 @@ export class ToolExecutionComponent extends Container {
|
|
|
892
933
|
}
|
|
893
934
|
|
|
894
935
|
if (this.result) {
|
|
936
|
+
if (this.result.isError) {
|
|
937
|
+
const errorText = this.getTextOutput().trim() || "grep failed";
|
|
938
|
+
text += `\n\n${theme.fg("error", errorText)}`;
|
|
939
|
+
return text;
|
|
940
|
+
}
|
|
941
|
+
|
|
895
942
|
const output = this.getTextOutput().trim();
|
|
896
943
|
if (output) {
|
|
897
944
|
const lines = output.split("\n");
|
|
@@ -369,8 +369,13 @@ export async function handleAgentEvent(host: InteractiveModeStateHost & {
|
|
|
369
369
|
if (!errorMessage) {
|
|
370
370
|
errorMessage = host.streamingMessage.errorMessage || "Error";
|
|
371
371
|
}
|
|
372
|
-
|
|
373
|
-
|
|
372
|
+
const pendingComponents = Array.from(host.pendingTools.values());
|
|
373
|
+
if (pendingComponents.length > 0) {
|
|
374
|
+
const [first, ...rest] = pendingComponents;
|
|
375
|
+
first.completeWithError(errorMessage);
|
|
376
|
+
for (const component of rest) {
|
|
377
|
+
component.completeWithError();
|
|
378
|
+
}
|
|
374
379
|
}
|
|
375
380
|
host.pendingTools.clear();
|
|
376
381
|
} else {
|
|
@@ -1785,7 +1785,7 @@ export class InteractiveMode {
|
|
|
1785
1785
|
} else if (type === "warning") {
|
|
1786
1786
|
this.showWarning(message);
|
|
1787
1787
|
} else {
|
|
1788
|
-
this.showStatus(message);
|
|
1788
|
+
this.showStatus(message, { append: true });
|
|
1789
1789
|
}
|
|
1790
1790
|
}
|
|
1791
1791
|
|
|
@@ -2052,12 +2052,13 @@ export class InteractiveMode {
|
|
|
2052
2052
|
* If multiple status messages are emitted back-to-back (without anything else being added to the chat),
|
|
2053
2053
|
* we update the previous status line instead of appending new ones to avoid log spam.
|
|
2054
2054
|
*/
|
|
2055
|
-
private showStatus(message: string): void {
|
|
2055
|
+
private showStatus(message: string, options?: { append?: boolean }): void {
|
|
2056
|
+
const append = options?.append ?? false;
|
|
2056
2057
|
const children = this.chatContainer.children;
|
|
2057
2058
|
const last = children.length > 0 ? children[children.length - 1] : undefined;
|
|
2058
2059
|
const secondLast = children.length > 1 ? children[children.length - 2] : undefined;
|
|
2059
2060
|
|
|
2060
|
-
if (last && secondLast && last === this.lastStatusText && secondLast === this.lastStatusSpacer) {
|
|
2061
|
+
if (!append && last && secondLast && last === this.lastStatusText && secondLast === this.lastStatusSpacer) {
|
|
2061
2062
|
this.lastStatusText.setText(theme.fg("dim", message));
|
|
2062
2063
|
this.ui.requestRender();
|
|
2063
2064
|
return;
|
|
@@ -499,12 +499,14 @@ function handleHotkeysCommand(ctx: SlashCommandContext): void {
|
|
|
499
499
|
const suspend = getAppKeyDisplay(ctx.keybindings, "suspend");
|
|
500
500
|
const cycleThinkingLevel = getAppKeyDisplay(ctx.keybindings, "cycleThinkingLevel");
|
|
501
501
|
const cycleModelForward = getAppKeyDisplay(ctx.keybindings, "cycleModelForward");
|
|
502
|
+
const cycleModelBackward = getAppKeyDisplay(ctx.keybindings, "cycleModelBackward");
|
|
502
503
|
const selectModel = getAppKeyDisplay(ctx.keybindings, "selectModel");
|
|
503
504
|
const expandTools = getAppKeyDisplay(ctx.keybindings, "expandTools");
|
|
504
505
|
const toggleThinking = getAppKeyDisplay(ctx.keybindings, "toggleThinking");
|
|
505
506
|
const externalEditor = getAppKeyDisplay(ctx.keybindings, "externalEditor");
|
|
506
507
|
const followUp = getAppKeyDisplay(ctx.keybindings, "followUp");
|
|
507
508
|
const dequeue = getAppKeyDisplay(ctx.keybindings, "dequeue");
|
|
509
|
+
const pasteImage = getAppKeyDisplay(ctx.keybindings, "pasteImage");
|
|
508
510
|
|
|
509
511
|
let hotkeys = `
|
|
510
512
|
**Navigation**
|
|
@@ -540,14 +542,14 @@ function handleHotkeysCommand(ctx: SlashCommandContext): void {
|
|
|
540
542
|
| \`${exit}\` | Exit (when editor is empty) |
|
|
541
543
|
| \`${suspend}\` | Suspend to background |
|
|
542
544
|
| \`${cycleThinkingLevel}\` | Cycle thinking level |
|
|
543
|
-
| \`${cycleModelForward}\` | Cycle models |
|
|
545
|
+
| \`${cycleModelForward}\` / \`${cycleModelBackward}\` | Cycle models |
|
|
544
546
|
| \`${selectModel}\` | Open model selector |
|
|
545
547
|
| \`${expandTools}\` | Toggle tool output expansion |
|
|
546
548
|
| \`${toggleThinking}\` | Toggle thinking block visibility |
|
|
547
549
|
| \`${externalEditor}\` | Edit message in external editor |
|
|
548
550
|
| \`${followUp}\` | Queue follow-up message |
|
|
549
551
|
| \`${dequeue}\` | Restore queued messages |
|
|
550
|
-
| \`
|
|
552
|
+
| \`${pasteImage}\` | Paste image from clipboard |
|
|
551
553
|
| \`/\` | Slash commands |
|
|
552
554
|
| \`!\` | Run bash command |
|
|
553
555
|
| \`!!\` | Run bash command (excluded from context) |
|
|
@@ -118,6 +118,18 @@ function createAssistantStream(): AssistantMessageEventStream {
|
|
|
118
118
|
) as AssistantMessageEventStream;
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
+
export function getResultErrorMessage(result: SDKResultMessage): string {
|
|
122
|
+
if ("errors" in result && Array.isArray(result.errors) && result.errors.length > 0) {
|
|
123
|
+
return result.errors.join("; ");
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if ("result" in result && typeof result.result === "string" && result.result.trim().length > 0) {
|
|
127
|
+
return result.result.trim();
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return result.subtype === "success" ? "claude_code_request_failed" : result.subtype;
|
|
131
|
+
}
|
|
132
|
+
|
|
121
133
|
// ---------------------------------------------------------------------------
|
|
122
134
|
// Claude binary resolution
|
|
123
135
|
// ---------------------------------------------------------------------------
|
|
@@ -882,11 +894,7 @@ async function pumpSdkMessages(
|
|
|
882
894
|
};
|
|
883
895
|
|
|
884
896
|
if (result.is_error) {
|
|
885
|
-
|
|
886
|
-
"errors" in result
|
|
887
|
-
? (result as any).errors?.join("; ")
|
|
888
|
-
: result.subtype;
|
|
889
|
-
finalMessage.errorMessage = errText;
|
|
897
|
+
finalMessage.errorMessage = getResultErrorMessage(result);
|
|
890
898
|
stream.push({ type: "error", reason: "error", error: finalMessage });
|
|
891
899
|
} else {
|
|
892
900
|
stream.push({ type: "done", reason: "stop", message: finalMessage });
|
|
@@ -5,6 +5,7 @@ import { join, resolve } from "node:path";
|
|
|
5
5
|
import { tmpdir } from "node:os";
|
|
6
6
|
import {
|
|
7
7
|
makeStreamExhaustedErrorMessage,
|
|
8
|
+
getResultErrorMessage,
|
|
8
9
|
buildPromptFromContext,
|
|
9
10
|
buildSdkOptions,
|
|
10
11
|
createClaudeCodeElicitationHandler,
|
|
@@ -40,6 +41,57 @@ describe("stream-adapter — exhausted stream fallback (#2575)", () => {
|
|
|
40
41
|
});
|
|
41
42
|
});
|
|
42
43
|
|
|
44
|
+
describe("stream-adapter — result error text (#3776)", () => {
|
|
45
|
+
test("prefers SDK result text when an error arrives with subtype success", () => {
|
|
46
|
+
const message = getResultErrorMessage({
|
|
47
|
+
type: "result",
|
|
48
|
+
subtype: "success",
|
|
49
|
+
uuid: "uuid-1",
|
|
50
|
+
session_id: "session-1",
|
|
51
|
+
duration_ms: 1,
|
|
52
|
+
duration_api_ms: 1,
|
|
53
|
+
is_error: true,
|
|
54
|
+
num_turns: 1,
|
|
55
|
+
result: 'API Error: 529 {"type":"error","error":{"type":"overloaded_error","message":"Overloaded"}}',
|
|
56
|
+
stop_reason: null,
|
|
57
|
+
total_cost_usd: 0,
|
|
58
|
+
usage: {
|
|
59
|
+
input_tokens: 0,
|
|
60
|
+
output_tokens: 0,
|
|
61
|
+
cache_read_input_tokens: 0,
|
|
62
|
+
cache_creation_input_tokens: 0,
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
assert.match(message, /API Error: 529/);
|
|
67
|
+
assert.doesNotMatch(message, /^success$/i);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test("falls back to a stable classifier when success errors have no text", () => {
|
|
71
|
+
const message = getResultErrorMessage({
|
|
72
|
+
type: "result",
|
|
73
|
+
subtype: "success",
|
|
74
|
+
uuid: "uuid-2",
|
|
75
|
+
session_id: "session-2",
|
|
76
|
+
duration_ms: 1,
|
|
77
|
+
duration_api_ms: 1,
|
|
78
|
+
is_error: true,
|
|
79
|
+
num_turns: 1,
|
|
80
|
+
result: " ",
|
|
81
|
+
stop_reason: null,
|
|
82
|
+
total_cost_usd: 0,
|
|
83
|
+
usage: {
|
|
84
|
+
input_tokens: 0,
|
|
85
|
+
output_tokens: 0,
|
|
86
|
+
cache_read_input_tokens: 0,
|
|
87
|
+
cache_creation_input_tokens: 0,
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
assert.equal(message, "claude_code_request_failed");
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
43
95
|
// ---------------------------------------------------------------------------
|
|
44
96
|
// Bug #2859 — stateless provider regression tests
|
|
45
97
|
// ---------------------------------------------------------------------------
|
|
@@ -598,9 +650,9 @@ describe("stream-adapter — MCP elicitation bridge", () => {
|
|
|
598
650
|
requestedSchema: {
|
|
599
651
|
type: "object" as const,
|
|
600
652
|
properties: {
|
|
601
|
-
|
|
653
|
+
TEST_SECURE_FIELD: {
|
|
602
654
|
type: "string",
|
|
603
|
-
title: "
|
|
655
|
+
title: "TEST_SECURE_FIELD",
|
|
604
656
|
description: "Format: Your secure testing password\nLeave empty to skip.",
|
|
605
657
|
},
|
|
606
658
|
},
|
|
@@ -611,7 +663,7 @@ describe("stream-adapter — MCP elicitation bridge", () => {
|
|
|
611
663
|
const handler = createClaudeCodeElicitationHandler({
|
|
612
664
|
input: async (_title: string, _placeholder?: string, opts?: { secure?: boolean }) => {
|
|
613
665
|
inputCalls.push({ opts });
|
|
614
|
-
return "
|
|
666
|
+
return "example-secure-input";
|
|
615
667
|
},
|
|
616
668
|
} as any);
|
|
617
669
|
assert.ok(handler);
|
|
@@ -620,7 +672,7 @@ describe("stream-adapter — MCP elicitation bridge", () => {
|
|
|
620
672
|
assert.deepEqual(result, {
|
|
621
673
|
action: "accept",
|
|
622
674
|
content: {
|
|
623
|
-
|
|
675
|
+
TEST_SECURE_FIELD: "example-secure-input",
|
|
624
676
|
},
|
|
625
677
|
});
|
|
626
678
|
assert.equal(inputCalls.length, 1);
|
|
@@ -46,3 +46,41 @@ export function isInfrastructureError(err: unknown): string | null {
|
|
|
46
46
|
if (msg.includes("database disk image is malformed")) return "SQLITE_CORRUPT";
|
|
47
47
|
return null;
|
|
48
48
|
}
|
|
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
|
+
|
|
57
|
+
/** Maximum consecutive cooldown retries before the auto-loop gives up. */
|
|
58
|
+
export const MAX_COOLDOWN_RETRIES = 5;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Detect whether an error is a transient credential cooldown that should
|
|
62
|
+
* be waited out rather than counted as a consecutive failure.
|
|
63
|
+
*
|
|
64
|
+
* Prefers the structured `CredentialCooldownError` (code: AUTH_COOLDOWN)
|
|
65
|
+
* thrown by sdk.ts. Falls back to message matching for errors that
|
|
66
|
+
* propagated across process boundaries without the typed class.
|
|
67
|
+
*/
|
|
68
|
+
export function isTransientCooldownError(err: unknown): boolean {
|
|
69
|
+
if (err && typeof err === "object" && (err as Record<string, unknown>).code === "AUTH_COOLDOWN") {
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
// Fallback: message match for cross-process error propagation
|
|
73
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
74
|
+
return /in a cooldown window/i.test(msg);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Extract retryAfterMs from a CredentialCooldownError, if available.
|
|
79
|
+
* Returns undefined for unstructured errors or when no retry hint exists.
|
|
80
|
+
*/
|
|
81
|
+
export function getCooldownRetryAfterMs(err: unknown): number | undefined {
|
|
82
|
+
if (err && typeof err === "object" && (err as Record<string, unknown>).code === "AUTH_COOLDOWN") {
|
|
83
|
+
return (err as Record<string, unknown>).retryAfterMs as number | undefined;
|
|
84
|
+
}
|
|
85
|
+
return undefined;
|
|
86
|
+
}
|
|
@@ -27,7 +27,7 @@ import {
|
|
|
27
27
|
runFinalize,
|
|
28
28
|
} from "./phases.js";
|
|
29
29
|
import { debugLog } from "../debug-logger.js";
|
|
30
|
-
import { isInfrastructureError } from "./infra-errors.js";
|
|
30
|
+
import { isInfrastructureError, isTransientCooldownError, getCooldownRetryAfterMs, COOLDOWN_FALLBACK_WAIT_MS, MAX_COOLDOWN_RETRIES } from "./infra-errors.js";
|
|
31
31
|
import { resolveEngine } from "../engine-resolver.js";
|
|
32
32
|
|
|
33
33
|
/**
|
|
@@ -48,6 +48,7 @@ export async function autoLoop(
|
|
|
48
48
|
let iteration = 0;
|
|
49
49
|
const loopState: LoopState = { recentUnits: [], stuckRecoveryAttempts: 0, consecutiveFinalizeTimeouts: 0 };
|
|
50
50
|
let consecutiveErrors = 0;
|
|
51
|
+
let consecutiveCooldowns = 0;
|
|
51
52
|
const recentErrorMessages: string[] = [];
|
|
52
53
|
|
|
53
54
|
while (s.active) {
|
|
@@ -203,6 +204,7 @@ export async function autoLoop(
|
|
|
203
204
|
|
|
204
205
|
deps.clearUnitTimeout();
|
|
205
206
|
consecutiveErrors = 0;
|
|
207
|
+
consecutiveCooldowns = 0;
|
|
206
208
|
recentErrorMessages.length = 0;
|
|
207
209
|
deps.emitJournalEvent({ ts: new Date().toISOString(), flowId, seq: nextSeq(), eventType: "iteration-end", data: { iteration } });
|
|
208
210
|
debugLog("autoLoop", { phase: "iteration-complete", iteration });
|
|
@@ -265,6 +267,7 @@ export async function autoLoop(
|
|
|
265
267
|
if (finalizeResult.action === "continue") continue;
|
|
266
268
|
|
|
267
269
|
consecutiveErrors = 0; // Iteration completed successfully
|
|
270
|
+
consecutiveCooldowns = 0;
|
|
268
271
|
recentErrorMessages.length = 0;
|
|
269
272
|
deps.emitJournalEvent({ ts: new Date().toISOString(), flowId, seq: nextSeq(), eventType: "iteration-end", data: { iteration } });
|
|
270
273
|
debugLog("autoLoop", { phase: "iteration-complete", iteration });
|
|
@@ -300,6 +303,47 @@ export async function autoLoop(
|
|
|
300
303
|
break;
|
|
301
304
|
}
|
|
302
305
|
|
|
306
|
+
// ── Credential cooldown: wait and retry with bounded budget ──
|
|
307
|
+
// A 429 triggers a 30s credential backoff in AuthStorage. If the SDK's
|
|
308
|
+
// getApiKey() retries couldn't outlast the window, the error surfaces
|
|
309
|
+
// here. Wait for the cooldown to clear rather than counting it as a
|
|
310
|
+
// consecutive failure — but cap retries so we don't spin for hours
|
|
311
|
+
// on persistent quota exhaustion.
|
|
312
|
+
if (isTransientCooldownError(loopErr)) {
|
|
313
|
+
consecutiveCooldowns++;
|
|
314
|
+
const retryAfterMs = getCooldownRetryAfterMs(loopErr);
|
|
315
|
+
debugLog("autoLoop", {
|
|
316
|
+
phase: "cooldown-wait",
|
|
317
|
+
iteration,
|
|
318
|
+
consecutiveCooldowns,
|
|
319
|
+
retryAfterMs,
|
|
320
|
+
error: msg,
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
if (consecutiveCooldowns > MAX_COOLDOWN_RETRIES) {
|
|
324
|
+
ctx.ui.notify(
|
|
325
|
+
`Auto-mode stopped: ${consecutiveCooldowns} consecutive credential cooldowns — rate limit or quota may be persistently exhausted.`,
|
|
326
|
+
"error",
|
|
327
|
+
);
|
|
328
|
+
await deps.stopAuto(
|
|
329
|
+
ctx,
|
|
330
|
+
pi,
|
|
331
|
+
`${consecutiveCooldowns} consecutive credential cooldowns exceeded retry budget`,
|
|
332
|
+
);
|
|
333
|
+
break;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const waitMs = (retryAfterMs !== undefined && retryAfterMs > 0 && retryAfterMs <= 60_000)
|
|
337
|
+
? retryAfterMs + 500 // Use structured hint + small buffer
|
|
338
|
+
: COOLDOWN_FALLBACK_WAIT_MS;
|
|
339
|
+
ctx.ui.notify(
|
|
340
|
+
`Credentials in cooldown (${consecutiveCooldowns}/${MAX_COOLDOWN_RETRIES}) — waiting ${Math.round(waitMs / 1000)}s before retrying.`,
|
|
341
|
+
"warning",
|
|
342
|
+
);
|
|
343
|
+
await new Promise(resolve => setTimeout(resolve, waitMs));
|
|
344
|
+
continue; // Retry iteration without incrementing consecutiveErrors
|
|
345
|
+
}
|
|
346
|
+
|
|
303
347
|
consecutiveErrors++;
|
|
304
348
|
recentErrorMessages.push(msg.length > 120 ? msg.slice(0, 120) + "..." : msg);
|
|
305
349
|
debugLog("autoLoop", {
|
|
@@ -87,6 +87,10 @@ export class AutoSession {
|
|
|
87
87
|
previousProjectRootEnv: string | null = null;
|
|
88
88
|
hadProjectRootEnv = false;
|
|
89
89
|
projectRootEnvCaptured = false;
|
|
90
|
+
previousMilestoneLockEnv: string | null = null;
|
|
91
|
+
hadMilestoneLockEnv = false;
|
|
92
|
+
milestoneLockEnvCaptured = false;
|
|
93
|
+
sessionMilestoneLock: string | null = null;
|
|
90
94
|
gitService: GitServiceImpl | null = null;
|
|
91
95
|
|
|
92
96
|
// ── Dispatch counters ────────────────────────────────────────────────────
|
|
@@ -200,6 +204,10 @@ export class AutoSession {
|
|
|
200
204
|
this.previousProjectRootEnv = null;
|
|
201
205
|
this.hadProjectRootEnv = false;
|
|
202
206
|
this.projectRootEnvCaptured = false;
|
|
207
|
+
this.previousMilestoneLockEnv = null;
|
|
208
|
+
this.hadMilestoneLockEnv = false;
|
|
209
|
+
this.milestoneLockEnvCaptured = false;
|
|
210
|
+
this.sessionMilestoneLock = null;
|
|
203
211
|
this.gitService = null;
|
|
204
212
|
|
|
205
213
|
// Dispatch
|
|
@@ -6,7 +6,13 @@
|
|
|
6
6
|
* or AutoContext dependency. State accessors are passed as callbacks.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import type {
|
|
9
|
+
import type {
|
|
10
|
+
ExtensionContext,
|
|
11
|
+
ExtensionCommandContext,
|
|
12
|
+
SessionMessageEntry,
|
|
13
|
+
ReadonlyFooterDataProvider,
|
|
14
|
+
Theme,
|
|
15
|
+
} from "@gsd/pi-coding-agent";
|
|
10
16
|
import type { GSDState } from "./types.js";
|
|
11
17
|
import { getCurrentBranch } from "./worktree.js";
|
|
12
18
|
import { getActiveHook } from "./post-unit-hooks.js";
|
|
@@ -17,7 +23,6 @@ import {
|
|
|
17
23
|
resolveSliceFile,
|
|
18
24
|
} from "./paths.js";
|
|
19
25
|
import { isDbAvailable, getMilestoneSlices, getSliceTasks } from "./gsd-db.js";
|
|
20
|
-
import { formatShortcut } from "./files.js";
|
|
21
26
|
import { readFileSync, writeFileSync, existsSync } from "node:fs";
|
|
22
27
|
import { execFileSync } from "node:child_process";
|
|
23
28
|
import { truncateToWidth, visibleWidth } from "@gsd/pi-tui";
|
|
@@ -38,6 +43,7 @@ import {
|
|
|
38
43
|
type RtkSessionSavings,
|
|
39
44
|
} from "../shared/rtk-session-stats.js";
|
|
40
45
|
import { logWarning } from "./workflow-logger.js";
|
|
46
|
+
import { formattedShortcutPair } from "./shortcut-defs.js";
|
|
41
47
|
|
|
42
48
|
// ─── UAT Slice Extraction ─────────────────────────────────────────────────────
|
|
43
49
|
|
|
@@ -358,12 +364,23 @@ function getLastCommit(basePath: string): { timeAgo: string; message: string } |
|
|
|
358
364
|
// ─── Footer Factory ───────────────────────────────────────────────────────────
|
|
359
365
|
|
|
360
366
|
/**
|
|
361
|
-
* Footer factory
|
|
362
|
-
*
|
|
363
|
-
* progress widget instead, so there's no gap or redundancy.
|
|
367
|
+
* Footer factory used by auto-mode.
|
|
368
|
+
* Keep footer minimal but preserve extension status context from setStatus().
|
|
364
369
|
*/
|
|
365
|
-
|
|
366
|
-
|
|
370
|
+
function sanitizeFooterStatus(text: string): string {
|
|
371
|
+
return text.replace(/\s+/g, " ").trim();
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
export const hideFooter = (_tui: unknown, theme: Theme, footerData: ReadonlyFooterDataProvider) => ({
|
|
375
|
+
render(width: number): string[] {
|
|
376
|
+
const extensionStatuses = footerData.getExtensionStatuses();
|
|
377
|
+
if (extensionStatuses.size === 0) return [];
|
|
378
|
+
const statusLine = Array.from(extensionStatuses.entries())
|
|
379
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
380
|
+
.map(([, text]) => sanitizeFooterStatus(text))
|
|
381
|
+
.join(" ");
|
|
382
|
+
return [truncateToWidth(theme.fg("dim", statusLine), width, theme.fg("dim", "..."))];
|
|
383
|
+
},
|
|
367
384
|
invalidate() {},
|
|
368
385
|
dispose() {},
|
|
369
386
|
});
|
|
@@ -646,14 +663,6 @@ export function updateProgressWidget(
|
|
|
646
663
|
: "";
|
|
647
664
|
lines.push(rightAlign(headerLeft, headerRight, width));
|
|
648
665
|
|
|
649
|
-
// Worktree/branch right-aligned below header
|
|
650
|
-
const branchLabel = worktreeName && cachedBranch
|
|
651
|
-
? `${worktreeName} (${cachedBranch})`
|
|
652
|
-
: cachedBranch ?? "";
|
|
653
|
-
if (branchLabel) {
|
|
654
|
-
lines.push(rightAlign("", theme.fg("dim", branchLabel), width));
|
|
655
|
-
}
|
|
656
|
-
|
|
657
666
|
// Show health signal details when degraded (yellow/red)
|
|
658
667
|
if (score.level !== "green" && score.signals.length > 0 && widgetMode !== "min") {
|
|
659
668
|
// Show up to 3 most relevant signals in compact form
|
|
@@ -917,15 +926,17 @@ export function updateProgressWidget(
|
|
|
917
926
|
// Hints line
|
|
918
927
|
const hintParts: string[] = [];
|
|
919
928
|
hintParts.push("esc pause");
|
|
920
|
-
hintParts.push(`${
|
|
929
|
+
hintParts.push(`${formattedShortcutPair("dashboard")} dashboard`);
|
|
930
|
+
hintParts.push(`${formattedShortcutPair("parallel")} parallel`);
|
|
921
931
|
const hintStr = theme.fg("dim", hintParts.join(" | "));
|
|
922
932
|
const commitStr = lastCommit
|
|
923
933
|
? theme.fg("dim", `${lastCommit.timeAgo} ago: ${commitMsg}`)
|
|
924
934
|
: "";
|
|
935
|
+
const locationStr = theme.fg("dim", widgetPwd);
|
|
925
936
|
if (commitStr) {
|
|
926
|
-
lines.push(rightAlign(`${pad}${commitStr}`, hintStr, width));
|
|
937
|
+
lines.push(rightAlign(`${pad}${locationStr} · ${commitStr}`, hintStr, width));
|
|
927
938
|
} else {
|
|
928
|
-
lines.push(rightAlign(
|
|
939
|
+
lines.push(rightAlign(`${pad}${locationStr}`, hintStr, width));
|
|
929
940
|
}
|
|
930
941
|
|
|
931
942
|
lines.push(...ui.bar());
|