@opengsd/gsd-pi 1.0.2-dev.5961fbf → 1.0.2-dev.5f7864c
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/README.md +63 -12
- package/dist/onboarding.js +22 -3
- package/dist/resource-loader.d.ts +2 -0
- package/dist/resource-loader.js +18 -1
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/context7/index.js +12 -2
- package/dist/resources/extensions/get-secrets-from-user.js +16 -16
- package/dist/resources/extensions/google-cli/index.js +30 -0
- package/dist/resources/extensions/google-cli/models.js +55 -0
- package/dist/resources/extensions/google-cli/package.json +11 -0
- package/dist/resources/extensions/google-cli/readiness.js +12 -0
- package/dist/resources/extensions/google-cli/stream-adapter.js +191 -0
- package/dist/resources/extensions/gsd/auto/session.js +3 -0
- package/dist/resources/extensions/gsd/auto-start.js +232 -49
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +4 -3
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +17 -15
- package/dist/resources/extensions/gsd/closeout-recovery.js +7 -1
- package/dist/resources/extensions/gsd/commands/handlers/auto.js +9 -1
- package/dist/resources/extensions/gsd/commands-handlers.js +3 -0
- package/dist/resources/extensions/gsd/commands-usage.js +105 -1
- package/dist/resources/extensions/gsd/config-overlay.js +20 -14
- package/dist/resources/extensions/gsd/context-overlay.js +22 -16
- package/dist/resources/extensions/gsd/dashboard-overlay.js +10 -23
- package/dist/resources/extensions/gsd/doctor-providers.js +54 -24
- package/dist/resources/extensions/gsd/git-conflict-state.js +26 -1
- package/dist/resources/extensions/gsd/guided-flow.js +1 -1
- package/dist/resources/extensions/gsd/key-manager.js +45 -13
- package/dist/resources/extensions/gsd/notification-overlay.js +8 -9
- package/dist/resources/extensions/gsd/parallel-monitor-overlay.js +15 -13
- package/dist/resources/extensions/gsd/prompt-loader.js +2 -0
- package/dist/resources/extensions/gsd/prompts/discuss.md +4 -2
- package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +2 -0
- package/dist/resources/extensions/gsd/queue-reorder-ui.js +28 -18
- package/dist/resources/extensions/gsd/tools/complete-task.js +9 -0
- package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +40 -1
- package/dist/resources/extensions/gsd/tui/render-kit.js +51 -0
- package/dist/resources/extensions/gsd/vision-ask.js +22 -0
- package/dist/resources/extensions/gsd/visualizer-overlay.js +8 -36
- package/dist/resources/extensions/gsd/worktree-lifecycle.js +24 -3
- package/dist/resources/extensions/search-the-web/native-search.js +57 -8
- package/dist/resources/extensions/shared/confirm-ui.js +9 -6
- package/dist/resources/extensions/shared/dialog-frame.js +42 -0
- package/dist/resources/extensions/shared/interview-ui.js +42 -30
- package/dist/resources/extensions/shared/next-action-ui.js +6 -6
- package/dist/resources/shared/package-manager-detection.js +36 -0
- package/dist/update-check.d.ts +6 -2
- package/dist/update-check.js +7 -3
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +6 -6
- 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/api/update/route.js +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +6 -6
- package/dist/web/standalone/.next/server/chunks/1834.js +2 -2
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/package.json +1 -1
- package/packages/cloud-mcp-gateway/package.json +2 -2
- package/packages/contracts/package.json +1 -1
- package/packages/daemon/package.json +4 -4
- package/packages/gsd-agent-core/package.json +5 -5
- package/packages/gsd-agent-modes/dist/modes/interactive/components/dialog-container.d.ts +12 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/dialog-container.d.ts.map +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/dialog-container.js +45 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/dialog-container.js.map +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-editor.d.ts +3 -2
- package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-editor.js +11 -11
- package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-editor.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-input.d.ts +3 -3
- package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-input.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-input.js +13 -11
- package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-input.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-selector.d.ts +3 -3
- package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-selector.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-selector.js +12 -10
- package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-selector.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/index.d.ts +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/index.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/index.js +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/index.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/login-dialog.d.ts +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/login-dialog.js +2 -2
- package/packages/gsd-agent-modes/dist/modes/interactive/components/login-dialog.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/oauth-selector.d.ts +6 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/oauth-selector.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/oauth-selector.js +9 -6
- package/packages/gsd-agent-modes/dist/modes/interactive/components/oauth-selector.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.js +0 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-auth.d.ts +3 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-auth.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-auth.js +144 -2
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-auth.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-session.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-session.js +2 -14
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-session.js.map +1 -1
- package/packages/gsd-agent-modes/package.json +7 -7
- package/packages/mcp-server/dist/workflow-tools.js +1 -1
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/package.json +3 -3
- package/packages/native/package.json +1 -1
- package/packages/pi-agent-core/dist/agent-loop.js +13 -13
- package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
- package/packages/pi-agent-core/package.json +1 -1
- package/packages/pi-ai/dist/models.generated.d.ts +57 -17
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +64 -28
- package/packages/pi-ai/dist/models.generated.js.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic.js +50 -0
- package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
- package/packages/pi-ai/dist/types.d.ts +2 -0
- package/packages/pi-ai/dist/types.d.ts.map +1 -1
- package/packages/pi-ai/dist/types.js.map +1 -1
- package/packages/pi-ai/package.json +1 -1
- package/packages/pi-coding-agent/package.json +7 -7
- package/packages/pi-tui/package.json +1 -1
- package/packages/rpc-client/package.json +2 -2
- package/pkg/package.json +1 -1
- package/scripts/install/detect-existing.js +17 -3
- package/scripts/install/npm-global.js +103 -33
- package/scripts/install.js +1 -0
- package/src/resources/extensions/context7/index.ts +15 -2
- package/src/resources/extensions/get-secrets-from-user.ts +17 -16
- package/src/resources/extensions/google-cli/index.ts +34 -0
- package/src/resources/extensions/google-cli/models.ts +57 -0
- package/src/resources/extensions/google-cli/package.json +11 -0
- package/src/resources/extensions/google-cli/readiness.ts +15 -0
- package/src/resources/extensions/google-cli/stream-adapter.ts +245 -0
- package/src/resources/extensions/gsd/auto/session.ts +3 -0
- package/src/resources/extensions/gsd/auto-start.ts +307 -56
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +4 -3
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +22 -15
- package/src/resources/extensions/gsd/closeout-recovery.ts +6 -1
- package/src/resources/extensions/gsd/commands/handlers/auto.ts +9 -1
- package/src/resources/extensions/gsd/commands-handlers.ts +2 -0
- package/src/resources/extensions/gsd/commands-usage.ts +110 -5
- package/src/resources/extensions/gsd/config-overlay.ts +19 -16
- package/src/resources/extensions/gsd/context-overlay.ts +24 -19
- package/src/resources/extensions/gsd/dashboard-overlay.ts +14 -27
- package/src/resources/extensions/gsd/doctor-providers.ts +55 -27
- package/src/resources/extensions/gsd/git-conflict-state.ts +25 -1
- package/src/resources/extensions/gsd/guided-flow.ts +1 -1
- package/src/resources/extensions/gsd/key-manager.ts +57 -14
- package/src/resources/extensions/gsd/notification-overlay.ts +12 -11
- package/src/resources/extensions/gsd/parallel-monitor-overlay.ts +16 -12
- package/src/resources/extensions/gsd/prompt-loader.ts +2 -0
- package/src/resources/extensions/gsd/prompts/discuss.md +4 -2
- package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +2 -0
- package/src/resources/extensions/gsd/queue-reorder-ui.ts +29 -20
- package/src/resources/extensions/gsd/tests/auto-start-orphan-bootstrap.test.ts +436 -0
- package/src/resources/extensions/gsd/tests/closeout-recovery.test.ts +15 -0
- package/src/resources/extensions/gsd/tests/collect-from-manifest.test.ts +31 -0
- package/src/resources/extensions/gsd/tests/commands-context.test.ts +5 -3
- package/src/resources/extensions/gsd/tests/commands-dispatcher-workspace-git.test.ts +15 -2
- package/src/resources/extensions/gsd/tests/commands-usage.test.ts +97 -0
- package/src/resources/extensions/gsd/tests/context-chart.test.ts +9 -0
- package/src/resources/extensions/gsd/tests/dashboard-overlay.test.ts +25 -0
- package/src/resources/extensions/gsd/tests/discuss-prompt.test.ts +4 -2
- package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +105 -0
- package/src/resources/extensions/gsd/tests/guided-discuss-milestone-prompt-rendering.test.ts +6 -0
- package/src/resources/extensions/gsd/tests/key-manager.test.ts +23 -4
- package/src/resources/extensions/gsd/tests/notification-overlay.test.ts +6 -1
- package/src/resources/extensions/gsd/tests/orphaned-worktree-audit.test.ts +70 -10
- package/src/resources/extensions/gsd/tests/parallel-monitor-overlay.test.ts +7 -1
- package/src/resources/extensions/gsd/tests/queue-reorder-ui.test.ts +46 -0
- package/src/resources/extensions/gsd/tests/show-config-command.test.ts +4 -0
- package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +13 -2
- package/src/resources/extensions/gsd/tests/tool-param-optionality.test.ts +24 -1
- package/src/resources/extensions/gsd/tests/tui-border-assertions.ts +28 -0
- package/src/resources/extensions/gsd/tests/tui-render-kit.test.ts +14 -0
- package/src/resources/extensions/gsd/tests/vision-ask.test.ts +23 -0
- package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +6 -1
- package/src/resources/extensions/gsd/tests/workflow-mcp-auto-prep.test.ts +60 -0
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +54 -0
- package/src/resources/extensions/gsd/tests/workspace-git-preflight.test.ts +16 -1
- package/src/resources/extensions/gsd/tests/worktree-lifecycle.test.ts +28 -0
- package/src/resources/extensions/gsd/tests/zombie-gsd-state.test.ts +45 -1
- package/src/resources/extensions/gsd/tools/complete-task.ts +9 -0
- package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +56 -4
- package/src/resources/extensions/gsd/tui/render-kit.ts +82 -0
- package/src/resources/extensions/gsd/vision-ask.ts +28 -0
- package/src/resources/extensions/gsd/visualizer-overlay.ts +12 -40
- package/src/resources/extensions/gsd/worktree-lifecycle.ts +37 -2
- package/src/resources/extensions/search-the-web/native-search.ts +60 -8
- package/src/resources/extensions/shared/confirm-ui.ts +8 -12
- package/src/resources/extensions/shared/dialog-frame.ts +71 -0
- package/src/resources/extensions/shared/interview-ui.ts +43 -42
- package/src/resources/extensions/shared/next-action-ui.ts +6 -6
- package/src/resources/extensions/shared/tests/confirm-ui.test.ts +57 -0
- package/src/resources/extensions/shared/tests/interview-ui-border.test.ts +163 -0
- package/src/resources/extensions/shared/tests/next-action-ui-hasui.test.ts +55 -0
- package/src/resources/shared/package-manager-detection.ts +39 -0
- /package/dist/web/standalone/.next/static/{spUYLkQXoHJyxYOMH9VQy → IjxvcC7sl_MHNKXsUZrAy}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{spUYLkQXoHJyxYOMH9VQy → IjxvcC7sl_MHNKXsUZrAy}/_ssgManifest.js +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Theme } from "@gsd/pi-coding-agent";
|
|
2
|
-
import {
|
|
2
|
+
import { visibleWidth, matchesKey, Key } from "@gsd/pi-tui";
|
|
3
3
|
import { loadVisualizerData, type VisualizerData } from "./visualizer-data.js";
|
|
4
4
|
import {
|
|
5
5
|
renderProgressView,
|
|
@@ -19,6 +19,7 @@ import { join } from "node:path";
|
|
|
19
19
|
import { writeExportFile } from "./export.js";
|
|
20
20
|
import { gsdRoot } from "./paths.js";
|
|
21
21
|
import { stripAnsi } from "../shared/mod.js";
|
|
22
|
+
import { renderDialogFrame, renderKeyHints } from "./tui/render-kit.js";
|
|
22
23
|
|
|
23
24
|
export const TAB_COUNT = 10;
|
|
24
25
|
const TAB_LABELS = [
|
|
@@ -500,8 +501,7 @@ export class GSDVisualizerOverlay {
|
|
|
500
501
|
|
|
501
502
|
// Apply scroll
|
|
502
503
|
const viewportHeight = Math.max(5, process.stdout.rows ? process.stdout.rows - 8 : 24);
|
|
503
|
-
const
|
|
504
|
-
const visibleContentRows = Math.max(1, viewportHeight - chromeHeight);
|
|
504
|
+
const visibleContentRows = Math.max(1, viewportHeight - 4);
|
|
505
505
|
this.lastVisibleRows = visibleContentRows;
|
|
506
506
|
const totalLines = content.length;
|
|
507
507
|
const maxScroll = Math.max(0, content.length - visibleContentRows);
|
|
@@ -509,49 +509,21 @@ export class GSDVisualizerOverlay {
|
|
|
509
509
|
const offset = this.scrollOffsets[this.activeTab];
|
|
510
510
|
const visibleContent = content.slice(offset, offset + visibleContentRows);
|
|
511
511
|
|
|
512
|
-
const
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
const
|
|
518
|
-
|
|
512
|
+
const footer = renderKeyHints(
|
|
513
|
+
th,
|
|
514
|
+
["Tab/Shift+Tab/1-9,0 switch", "/ filter", "PgUp/PgDn scroll", "? help", "esc close"],
|
|
515
|
+
Math.max(1, width - 4),
|
|
516
|
+
);
|
|
517
|
+
const lines = renderDialogFrame(th, "GSD Visualizer", visibleContent, width, {
|
|
518
|
+
footer,
|
|
519
|
+
scroll: { offset, visibleRows: visibleContentRows, totalRows: totalLines },
|
|
520
|
+
});
|
|
519
521
|
|
|
520
522
|
this.cachedWidth = width;
|
|
521
523
|
this.cachedLines = lines;
|
|
522
524
|
return lines;
|
|
523
525
|
}
|
|
524
526
|
|
|
525
|
-
private wrapInBox(inner: string[], width: number, offset?: number, visibleRows?: number, totalLines?: number): string[] {
|
|
526
|
-
const th = this.theme;
|
|
527
|
-
const border = (s: string) => th.fg("borderAccent", s);
|
|
528
|
-
const innerWidth = width - 4;
|
|
529
|
-
const lines: string[] = [];
|
|
530
|
-
lines.push(border("\u256d" + "\u2500".repeat(width - 2) + "\u256e"));
|
|
531
|
-
|
|
532
|
-
// Compute scroll indicator positions
|
|
533
|
-
const scrollable = totalLines !== undefined && visibleRows !== undefined && totalLines > visibleRows;
|
|
534
|
-
let thumbStart = -1;
|
|
535
|
-
let thumbLen = 0;
|
|
536
|
-
const innerRows = inner.length;
|
|
537
|
-
if (scrollable && innerRows > 0 && totalLines! > 0) {
|
|
538
|
-
thumbStart = Math.round(((offset ?? 0) / totalLines!) * innerRows);
|
|
539
|
-
thumbLen = Math.max(1, Math.round((visibleRows! / totalLines!) * innerRows));
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
for (let i = 0; i < inner.length; i++) {
|
|
543
|
-
const line = inner[i];
|
|
544
|
-
const truncated = truncateToWidth(line, innerWidth);
|
|
545
|
-
const padWidth = Math.max(0, innerWidth - visibleWidth(truncated));
|
|
546
|
-
const rightBorder = scrollable && i >= thumbStart && i < thumbStart + thumbLen
|
|
547
|
-
? border("\u2503")
|
|
548
|
-
: border("\u2502");
|
|
549
|
-
lines.push(border("\u2502") + " " + truncated + " ".repeat(padWidth) + " " + rightBorder);
|
|
550
|
-
}
|
|
551
|
-
lines.push(border("\u2570" + "\u2500".repeat(width - 2) + "\u256f"));
|
|
552
|
-
return lines;
|
|
553
|
-
}
|
|
554
|
-
|
|
555
527
|
invalidate(): void {
|
|
556
528
|
this.cachedWidth = undefined;
|
|
557
529
|
this.cachedLines = undefined;
|
|
@@ -216,6 +216,10 @@ export type EnterResult =
|
|
|
216
216
|
cause?: unknown;
|
|
217
217
|
};
|
|
218
218
|
|
|
219
|
+
export interface StrandedMilestoneAdoptionOptions {
|
|
220
|
+
mode: "worktree" | "branch";
|
|
221
|
+
}
|
|
222
|
+
|
|
219
223
|
export type ExitResult =
|
|
220
224
|
| { ok: true; merged: boolean; codeFilesChanged: boolean }
|
|
221
225
|
| { ok: false; reason: "merge-conflict" | "teardown-failed"; cause?: unknown };
|
|
@@ -237,6 +241,8 @@ export interface MergeContext {
|
|
|
237
241
|
*/
|
|
238
242
|
worktreeBasePath: string;
|
|
239
243
|
milestoneId: string;
|
|
244
|
+
/** Temporary override used while recovering stranded work. */
|
|
245
|
+
isolationModeOverride?: "worktree" | "branch" | "none";
|
|
240
246
|
/**
|
|
241
247
|
* When true, `mergeMilestoneStandalone` returns `{ merged: false,
|
|
242
248
|
* mode: "skipped" }` immediately (mirrors the single-loop guard). Default
|
|
@@ -533,6 +539,7 @@ export function _enterMilestoneCore(
|
|
|
533
539
|
deps: WorktreeLifecycleDeps,
|
|
534
540
|
milestoneId: string,
|
|
535
541
|
ctx: NotifyCtx,
|
|
542
|
+
opts: { modeOverride?: "worktree" | "branch" } = {},
|
|
536
543
|
): EnterResult {
|
|
537
544
|
if (!isValidMilestoneId(milestoneId)) {
|
|
538
545
|
debugLog("WorktreeLifecycle", {
|
|
@@ -653,7 +660,7 @@ export function _enterMilestoneCore(
|
|
|
653
660
|
// Handles the case where originalBasePath is falsy and basePath is itself
|
|
654
661
|
// a worktree path — prevents double-nested worktree paths (#3729).
|
|
655
662
|
const basePath = resolveWorktreeProjectRoot(s.basePath, s.originalBasePath);
|
|
656
|
-
const mode = getIsolationMode(basePath);
|
|
663
|
+
const mode = opts.modeOverride ?? getIsolationMode(basePath);
|
|
657
664
|
|
|
658
665
|
if (s.isolationDegraded) {
|
|
659
666
|
if (mode === "worktree") {
|
|
@@ -1298,7 +1305,9 @@ export function mergeMilestoneStandalone(
|
|
|
1298
1305
|
};
|
|
1299
1306
|
}
|
|
1300
1307
|
|
|
1301
|
-
const mode =
|
|
1308
|
+
const mode =
|
|
1309
|
+
mctx.isolationModeOverride ??
|
|
1310
|
+
getIsolationMode(originalBasePath || worktreeBasePath);
|
|
1302
1311
|
debugLog("WorktreeLifecycle", {
|
|
1303
1312
|
action: "mergeAndExit",
|
|
1304
1313
|
milestoneId,
|
|
@@ -1637,6 +1646,7 @@ export class WorktreeLifecycle {
|
|
|
1637
1646
|
originalBasePath: this.s.originalBasePath,
|
|
1638
1647
|
worktreeBasePath: this.s.basePath,
|
|
1639
1648
|
milestoneId,
|
|
1649
|
+
isolationModeOverride: this.s.strandedRecoveryIsolationMode ?? undefined,
|
|
1640
1650
|
isolationDegraded: this.s.isolationDegraded,
|
|
1641
1651
|
notify: ctx.notify,
|
|
1642
1652
|
});
|
|
@@ -1740,6 +1750,7 @@ export class WorktreeLifecycle {
|
|
|
1740
1750
|
// Rebuild GitService after merge (branch HEAD changed)
|
|
1741
1751
|
rebuildGitService(this.s, this.deps);
|
|
1742
1752
|
}
|
|
1753
|
+
this.s.strandedRecoveryIsolationMode = null;
|
|
1743
1754
|
return result;
|
|
1744
1755
|
}
|
|
1745
1756
|
|
|
@@ -1876,6 +1887,30 @@ export class WorktreeLifecycle {
|
|
|
1876
1887
|
this.s.basePath = resolvePausedResumeBasePath(base, persistedWorktreePath);
|
|
1877
1888
|
}
|
|
1878
1889
|
|
|
1890
|
+
/**
|
|
1891
|
+
* Adopt in-progress stranded work during bootstrap.
|
|
1892
|
+
*
|
|
1893
|
+
* Unlike completed-orphan recovery, this does not merge, delete, or commit.
|
|
1894
|
+
* It only moves the live session onto the branch/worktree proven by the
|
|
1895
|
+
* audit evidence, while preserving that mode for the eventual merge.
|
|
1896
|
+
*/
|
|
1897
|
+
adoptStrandedMilestone(
|
|
1898
|
+
milestoneId: string,
|
|
1899
|
+
base: string,
|
|
1900
|
+
ctx: NotifyCtx,
|
|
1901
|
+
opts: StrandedMilestoneAdoptionOptions,
|
|
1902
|
+
): EnterResult {
|
|
1903
|
+
this.adoptSessionRoot(base);
|
|
1904
|
+
this.s.strandedRecoveryIsolationMode = opts.mode;
|
|
1905
|
+
const result = _enterMilestoneCore(this.s, this.deps, milestoneId, ctx, {
|
|
1906
|
+
modeOverride: opts.mode,
|
|
1907
|
+
});
|
|
1908
|
+
if (!result.ok) {
|
|
1909
|
+
this.s.strandedRecoveryIsolationMode = null;
|
|
1910
|
+
}
|
|
1911
|
+
return result;
|
|
1912
|
+
}
|
|
1913
|
+
|
|
1879
1914
|
/**
|
|
1880
1915
|
* Adopt an orphan worktree for a bootstrap-time merge (ADR-016 phase 2 / B4,
|
|
1881
1916
|
* issue #5622).
|
|
@@ -16,6 +16,47 @@ export const CUSTOM_SEARCH_TOOL_NAMES = ["search-the-web", "search_and_read", "g
|
|
|
16
16
|
|
|
17
17
|
/** Thinking block types that require signature validation by the API */
|
|
18
18
|
const THINKING_TYPES = new Set(["thinking", "redacted_thinking"]);
|
|
19
|
+
const NATIVE_SERVER_TOOL_USE_TYPES = new Set([
|
|
20
|
+
"server_tool_use",
|
|
21
|
+
"serverToolUse",
|
|
22
|
+
]);
|
|
23
|
+
const NATIVE_WEB_SEARCH_RESULT_TYPES = new Set([
|
|
24
|
+
"web_search_tool_result",
|
|
25
|
+
"webSearchResult",
|
|
26
|
+
]);
|
|
27
|
+
|
|
28
|
+
function nativeServerToolId(block: any): string | undefined {
|
|
29
|
+
if (!NATIVE_SERVER_TOOL_USE_TYPES.has(block?.type)) return undefined;
|
|
30
|
+
return typeof block.id === "string" ? block.id : undefined;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function nativeWebSearchResultId(block: any): string | undefined {
|
|
34
|
+
if (!NATIVE_WEB_SEARCH_RESULT_TYPES.has(block?.type)) return undefined;
|
|
35
|
+
const id = block.type === "webSearchResult" ? block.toolUseId : block.tool_use_id;
|
|
36
|
+
return typeof id === "string" ? id : undefined;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function hasCompleteNativeServerToolReplay(content: any[]): boolean {
|
|
40
|
+
const pendingToolUseIds = new Set<string>();
|
|
41
|
+
let sawNativeServerToolUse = false;
|
|
42
|
+
|
|
43
|
+
for (const block of content) {
|
|
44
|
+
const toolUseId = nativeServerToolId(block);
|
|
45
|
+
if (toolUseId !== undefined) {
|
|
46
|
+
if (pendingToolUseIds.has(toolUseId)) return false;
|
|
47
|
+
sawNativeServerToolUse = true;
|
|
48
|
+
pendingToolUseIds.add(toolUseId);
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const resultId = nativeWebSearchResultId(block);
|
|
53
|
+
if (resultId !== undefined) {
|
|
54
|
+
if (!pendingToolUseIds.delete(resultId)) return false;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return sawNativeServerToolUse && pendingToolUseIds.size === 0;
|
|
59
|
+
}
|
|
19
60
|
|
|
20
61
|
/**
|
|
21
62
|
* Providers whose Anthropic-Messages endpoint is known to accept the native
|
|
@@ -36,6 +77,11 @@ const NATIVE_WEB_SEARCH_PROVIDERS = new Set([
|
|
|
36
77
|
"vercel-ai-gateway",
|
|
37
78
|
]);
|
|
38
79
|
|
|
80
|
+
function looksLikeAnthropicModelName(modelName: string): boolean {
|
|
81
|
+
const normalized = modelName.trim().toLowerCase();
|
|
82
|
+
return normalized.startsWith("claude-") || normalized.startsWith("anthropic/claude-");
|
|
83
|
+
}
|
|
84
|
+
|
|
39
85
|
/**
|
|
40
86
|
* True when the model is an Anthropic-shaped transport AND the provider is
|
|
41
87
|
* known to accept the native `web_search_20250305` tool. Gate both on api
|
|
@@ -89,11 +135,10 @@ export interface NativeSearchPI {
|
|
|
89
135
|
* those blocks. The Anthropic API detects the modification and rejects the
|
|
90
136
|
* request with "thinking blocks cannot be modified."
|
|
91
137
|
*
|
|
92
|
-
* Fix: Remove thinking blocks from
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
* current turn regardless.
|
|
138
|
+
* Fix: Remove thinking blocks only from assistant messages that do not carry
|
|
139
|
+
* native server-tool blocks. Complete native server-tool histories can be
|
|
140
|
+
* replayed as-is; stripping thinking from those messages is itself a latest
|
|
141
|
+
* assistant message modification.
|
|
97
142
|
*/
|
|
98
143
|
export function stripThinkingFromHistory(
|
|
99
144
|
messages: Array<Record<string, unknown>>
|
|
@@ -103,6 +148,9 @@ export function stripThinkingFromHistory(
|
|
|
103
148
|
|
|
104
149
|
const content = msg.content;
|
|
105
150
|
if (!Array.isArray(content)) continue;
|
|
151
|
+
if (hasCompleteNativeServerToolReplay(content)) {
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
106
154
|
|
|
107
155
|
msg.content = content.filter(
|
|
108
156
|
(block: any) => !THINKING_TYPES.has(block?.type)
|
|
@@ -180,6 +228,8 @@ export function registerNativeSearchHooks(pi: NativeSearchPI): { getIsAnthropic:
|
|
|
180
228
|
// The model name heuristic is needed for session restores where
|
|
181
229
|
// modelsAreEqual suppresses model_select AND the SDK doesn't pass model.
|
|
182
230
|
const eventModel = event.model as { provider?: string; api?: string } | undefined;
|
|
231
|
+
const payloadModelName = typeof payload.model === "string" ? payload.model : "";
|
|
232
|
+
const payloadLooksAnthropic = payloadModelName ? looksLikeAnthropicModelName(payloadModelName) : undefined;
|
|
183
233
|
let isAnthropic: boolean;
|
|
184
234
|
if (eventModel?.api || eventModel?.provider) {
|
|
185
235
|
// Preferred path: gate on api shape + provider allowlist. Both fields
|
|
@@ -188,12 +238,14 @@ export function registerNativeSearchHooks(pi: NativeSearchPI): { getIsAnthropic:
|
|
|
188
238
|
// (#444 regression) or minimax-served Claude-compat as Anthropic (#4492).
|
|
189
239
|
isAnthropic = supportsNativeWebSearch(eventModel);
|
|
190
240
|
} else if (modelSelectFired) {
|
|
191
|
-
|
|
241
|
+
// The model_select flag can be stale if the next request omits event.model
|
|
242
|
+
// after a provider switch. A concrete non-Claude payload must win so an
|
|
243
|
+
// Anthropic-only tool never leaks into OpenAI Responses requests.
|
|
244
|
+
isAnthropic = isAnthropicProvider && payloadLooksAnthropic !== false;
|
|
192
245
|
} else {
|
|
193
246
|
// Last resort: session-restore paths where the SDK doesn't pass model.
|
|
194
247
|
// The model-name prefix is best-effort and assumes direct Anthropic.
|
|
195
|
-
|
|
196
|
-
isAnthropic = modelName.startsWith("claude-");
|
|
248
|
+
isAnthropic = payloadLooksAnthropic === true;
|
|
197
249
|
}
|
|
198
250
|
if (!isAnthropic) return;
|
|
199
251
|
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
import type { ExtensionContext } from "@gsd/pi-coding-agent";
|
|
19
19
|
import { type Theme } from "@gsd/pi-coding-agent";
|
|
20
20
|
import { Key, matchesKey, truncateToWidth, type TUI } from "@gsd/pi-tui";
|
|
21
|
+
import { renderSharedDialogFrame } from "./dialog-frame.js";
|
|
21
22
|
import { makeUI, GLYPH } from "./ui.js";
|
|
22
23
|
|
|
23
24
|
export interface ConfirmOptions {
|
|
@@ -83,20 +84,18 @@ export async function showConfirm(
|
|
|
83
84
|
function render(width: number): string[] {
|
|
84
85
|
if (cachedLines) return cachedLines;
|
|
85
86
|
|
|
86
|
-
const
|
|
87
|
+
const contentWidth = Math.max(1, width - 4);
|
|
88
|
+
const ui = makeUI(theme, contentWidth);
|
|
87
89
|
const lines: string[] = [];
|
|
88
90
|
const push = (...rows: string[][]) => { for (const r of rows) lines.push(...r); };
|
|
89
91
|
|
|
90
92
|
push(
|
|
91
|
-
ui.bar(),
|
|
92
|
-
ui.blank(),
|
|
93
|
-
ui.header(` ${opts.title}`),
|
|
94
93
|
ui.blank(),
|
|
95
94
|
ui.subtitle(` ${opts.message}`),
|
|
96
95
|
ui.blank(),
|
|
97
96
|
);
|
|
98
97
|
|
|
99
|
-
const add = (s: string) => truncateToWidth(s,
|
|
98
|
+
const add = (s: string) => truncateToWidth(s, contentWidth);
|
|
100
99
|
const option = (num: number, label: string, selected: boolean) => {
|
|
101
100
|
if (selected) {
|
|
102
101
|
return add(` ${theme.fg("accent", GLYPH.cursor)} ${theme.fg("accent", `${num}. ${label}`)}`);
|
|
@@ -107,14 +106,11 @@ export async function showConfirm(
|
|
|
107
106
|
lines.push(option(1, yesLabel, cursor === 0));
|
|
108
107
|
lines.push(option(2, noLabel, cursor === 1));
|
|
109
108
|
|
|
110
|
-
push(
|
|
111
|
-
|
|
112
|
-
ui.hints(["↑/↓ to choose", "y/n to quick-select", "enter to confirm"]),
|
|
113
|
-
ui.bar(),
|
|
114
|
-
);
|
|
109
|
+
push(ui.blank());
|
|
110
|
+
const footer = ui.hints(["↑/↓ to choose", "y/n to quick-select", "enter to confirm"])[0] ?? "";
|
|
115
111
|
|
|
116
|
-
cachedLines = lines;
|
|
117
|
-
return
|
|
112
|
+
cachedLines = renderSharedDialogFrame(theme, opts.title, lines, width, { footer });
|
|
113
|
+
return cachedLines;
|
|
118
114
|
}
|
|
119
115
|
|
|
120
116
|
return {
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { type Theme } from "@gsd/pi-coding-agent";
|
|
2
|
+
import { truncateToWidth, visibleWidth } from "@gsd/pi-tui";
|
|
3
|
+
|
|
4
|
+
type ThemeColor = Parameters<Theme["fg"]>[0];
|
|
5
|
+
|
|
6
|
+
export interface SharedDialogFrameOptions {
|
|
7
|
+
borderColor?: ThemeColor;
|
|
8
|
+
footer?: string | string[];
|
|
9
|
+
paddingX?: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function safeLine(text: string, width: number): string {
|
|
13
|
+
return truncateToWidth(text, width, "");
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function padVisible(text: string, width: number): string {
|
|
17
|
+
const clipped = safeLine(text, width);
|
|
18
|
+
return clipped + " ".repeat(Math.max(0, width - visibleWidth(clipped)));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function renderTopBorder(
|
|
22
|
+
theme: Theme,
|
|
23
|
+
title: string,
|
|
24
|
+
width: number,
|
|
25
|
+
border: (text: string) => string,
|
|
26
|
+
): string {
|
|
27
|
+
const trimmedTitle = title.trim();
|
|
28
|
+
if (!trimmedTitle || width < 10) {
|
|
29
|
+
return border("╭" + "─".repeat(width - 2) + "╮");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const safeTitle = safeLine(trimmedTitle, Math.max(0, width - 7));
|
|
33
|
+
const fill = Math.max(0, width - visibleWidth(safeTitle) - 5);
|
|
34
|
+
return border("╭─ ") + theme.bold(theme.fg("accent", safeTitle)) + border(" " + "─".repeat(fill) + "╮");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function renderSharedDialogFrame(
|
|
38
|
+
theme: Theme,
|
|
39
|
+
title: string,
|
|
40
|
+
inner: string[],
|
|
41
|
+
width: number,
|
|
42
|
+
options: SharedDialogFrameOptions = {},
|
|
43
|
+
): string[] {
|
|
44
|
+
if (width < 4) return inner.map((line) => safeLine(line, width));
|
|
45
|
+
|
|
46
|
+
const paddingX = Math.max(0, options.paddingX ?? 1);
|
|
47
|
+
const contentWidth = Math.max(0, width - 2 - paddingX * 2);
|
|
48
|
+
const border = (text: string) => theme.fg(options.borderColor ?? "borderAccent", text);
|
|
49
|
+
const pad = " ".repeat(paddingX);
|
|
50
|
+
const lines = [renderTopBorder(theme, title, width, border)];
|
|
51
|
+
|
|
52
|
+
for (const line of inner) {
|
|
53
|
+
lines.push(border("│") + pad + padVisible(line, contentWidth) + pad + border("│"));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const footer = Array.isArray(options.footer)
|
|
57
|
+
? options.footer
|
|
58
|
+
: options.footer
|
|
59
|
+
? [options.footer]
|
|
60
|
+
: [];
|
|
61
|
+
if (footer.length > 0) {
|
|
62
|
+
lines.push(border("├" + "─".repeat(width - 2) + "┤"));
|
|
63
|
+
for (const line of footer) {
|
|
64
|
+
lines.push(border("│") + pad + padVisible(line, contentWidth) + pad + border("│"));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
lines.push(border("╰" + "─".repeat(width - 2) + "╯"));
|
|
69
|
+
return lines;
|
|
70
|
+
}
|
|
71
|
+
|
|
@@ -36,6 +36,7 @@ import {
|
|
|
36
36
|
truncateToWidth,
|
|
37
37
|
type TUI,
|
|
38
38
|
} from "@gsd/pi-tui";
|
|
39
|
+
import { renderSharedDialogFrame } from "./dialog-frame.js";
|
|
39
40
|
import { mergeSideBySide } from "./layout-utils.js";
|
|
40
41
|
import { makeUI, INDENT } from "./ui.js";
|
|
41
42
|
|
|
@@ -126,6 +127,10 @@ const DIVIDER_CHARS = " │ ";
|
|
|
126
127
|
const DIVIDER_WIDTH = 3;
|
|
127
128
|
const PREVIEW_MAX_LINES = 20; // hard cap — keeps total ≤ 24 rows for single-question
|
|
128
129
|
|
|
130
|
+
function dialogContentWidth(width: number): number {
|
|
131
|
+
return width < 4 ? Math.max(1, width) : Math.max(1, width - 4);
|
|
132
|
+
}
|
|
133
|
+
|
|
129
134
|
// ─── Wrap-up screen ───────────────────────────────────────────────────────────
|
|
130
135
|
|
|
131
136
|
export async function showWrapUpScreen(
|
|
@@ -157,11 +162,12 @@ export async function showWrapUpScreen(
|
|
|
157
162
|
|
|
158
163
|
function render(width: number): string[] {
|
|
159
164
|
if (cachedLines) return cachedLines;
|
|
160
|
-
const
|
|
165
|
+
const contentWidth = dialogContentWidth(width);
|
|
166
|
+
const ui = makeUI(theme, contentWidth);
|
|
161
167
|
const lines: string[] = [];
|
|
162
168
|
const push = (...rows: string[][]) => { for (const r of rows) lines.push(...r); };
|
|
163
169
|
|
|
164
|
-
push(ui.
|
|
170
|
+
push(ui.blank());
|
|
165
171
|
if (opts.progress) push(ui.meta(` ${opts.progress}`), ui.blank());
|
|
166
172
|
|
|
167
173
|
if (cursorIdx === 1) {
|
|
@@ -175,14 +181,11 @@ export async function showWrapUpScreen(
|
|
|
175
181
|
} else {
|
|
176
182
|
push(ui.actionUnselected(2, opts.keepGoingLabel, "Continue with another batch of questions."));
|
|
177
183
|
}
|
|
178
|
-
push(
|
|
179
|
-
ui.blank(),
|
|
180
|
-
ui.hints(["↑/↓ to choose", "1/2 to quick-select", "enter to confirm"]),
|
|
181
|
-
ui.bar(),
|
|
182
|
-
);
|
|
184
|
+
push(ui.blank());
|
|
183
185
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
+
const footer = ui.hints(["↑/↓ to choose", "1/2 to quick-select", "enter to confirm"])[0] ?? "";
|
|
187
|
+
cachedLines = renderSharedDialogFrame(theme, opts.headline, lines, width, { footer });
|
|
188
|
+
return cachedLines;
|
|
186
189
|
}
|
|
187
190
|
|
|
188
191
|
return {
|
|
@@ -469,11 +472,13 @@ export async function showInterviewRound(
|
|
|
469
472
|
// ── Review screen ────────────────────────────────────────────────
|
|
470
473
|
|
|
471
474
|
function renderReviewScreen(width: number): string[] {
|
|
472
|
-
const
|
|
475
|
+
const contentWidth = dialogContentWidth(width);
|
|
476
|
+
const title = opts.reviewHeadline ?? "Review your answers";
|
|
477
|
+
const ui = makeUI(theme, contentWidth);
|
|
473
478
|
const lines: string[] = [];
|
|
474
479
|
const push = (...rows: string[][]) => { for (const r of rows) lines.push(...r); };
|
|
475
480
|
|
|
476
|
-
push(ui.
|
|
481
|
+
push(ui.blank());
|
|
477
482
|
|
|
478
483
|
for (let i = 0; i < questions.length; i++) {
|
|
479
484
|
const q = questions[i];
|
|
@@ -500,24 +505,22 @@ export async function showInterviewRound(
|
|
|
500
505
|
push(
|
|
501
506
|
ui.actionSelected(0, "Submit answers"),
|
|
502
507
|
ui.blank(),
|
|
503
|
-
ui.hints(["← to go back and edit", "enter to submit", `esc to ${opts.exitLabel ?? "end interview"}`]),
|
|
504
|
-
ui.bar(),
|
|
505
508
|
);
|
|
506
509
|
|
|
507
|
-
|
|
510
|
+
const footer = ui.hints(["← to go back and edit", "enter to submit", `esc to ${opts.exitLabel ?? "end interview"}`])[0] ?? "";
|
|
511
|
+
return renderSharedDialogFrame(theme, title, lines, width, { footer });
|
|
508
512
|
}
|
|
509
513
|
|
|
510
514
|
// ── Exit confirm screen ──────────────────────────────────────────
|
|
511
515
|
|
|
512
516
|
function renderExitConfirm(width: number): string[] {
|
|
513
|
-
const
|
|
517
|
+
const contentWidth = dialogContentWidth(width);
|
|
518
|
+
const title = opts.exitHeadline ?? "End interview?";
|
|
519
|
+
const ui = makeUI(theme, contentWidth);
|
|
514
520
|
const lines: string[] = [];
|
|
515
521
|
const push = (...rows: string[][]) => { for (const r of rows) lines.push(...r); };
|
|
516
522
|
|
|
517
523
|
push(
|
|
518
|
-
ui.bar(),
|
|
519
|
-
ui.blank(),
|
|
520
|
-
ui.header(` ${opts.exitHeadline ?? "End interview?"}`),
|
|
521
524
|
ui.blank(),
|
|
522
525
|
ui.subtitle(" Answers from this batch won't be saved."),
|
|
523
526
|
ui.blank(),
|
|
@@ -538,13 +541,10 @@ export async function showInterviewRound(
|
|
|
538
541
|
} else {
|
|
539
542
|
push(ui.actionUnselected(2, exitActionLabel, "Exit and discard this batch of answers."));
|
|
540
543
|
}
|
|
541
|
-
push(
|
|
542
|
-
ui.blank(),
|
|
543
|
-
ui.hints(["↑/↓ to choose", "1/2 to quick-select", "enter to confirm"]),
|
|
544
|
-
ui.bar(),
|
|
545
|
-
);
|
|
544
|
+
push(ui.blank());
|
|
546
545
|
|
|
547
|
-
|
|
546
|
+
const footer = ui.hints(["↑/↓ to choose", "1/2 to quick-select", "enter to confirm"])[0] ?? "";
|
|
547
|
+
return renderSharedDialogFrame(theme, title, lines, width, { footer });
|
|
548
548
|
}
|
|
549
549
|
|
|
550
550
|
// ── Preview helpers ──────────────────────────────────────────────
|
|
@@ -648,17 +648,17 @@ export async function showInterviewRound(
|
|
|
648
648
|
if (showingExitConfirm) { cachedLines = renderExitConfirm(width); return cachedLines; }
|
|
649
649
|
if (showingReview) { cachedLines = renderReviewScreen(width); return cachedLines; }
|
|
650
650
|
|
|
651
|
+
const contentWidth = dialogContentWidth(width);
|
|
652
|
+
const title = questions[currentIdx]?.header || "GSD Interview";
|
|
651
653
|
const useSideBySide = questionHasAnyPreview()
|
|
652
|
-
&&
|
|
654
|
+
&& contentWidth >= (MIN_OPTIONS_WIDTH + MIN_PREVIEW_WIDTH + DIVIDER_WIDTH);
|
|
653
655
|
|
|
654
656
|
if (useSideBySide) {
|
|
655
657
|
// ── Preview path ──────────────────────────────────────
|
|
656
|
-
const ui = makeUI(theme,
|
|
658
|
+
const ui = makeUI(theme, contentWidth);
|
|
657
659
|
const lines: string[] = [];
|
|
658
660
|
const push = (...rows: string[][]) => { for (const r of rows) lines.push(...r); };
|
|
659
661
|
|
|
660
|
-
push(ui.bar());
|
|
661
|
-
|
|
662
662
|
if (isMultiQuestion) {
|
|
663
663
|
const unanswered = questions.filter((_, i) => !isQuestionAnswered(i)).length;
|
|
664
664
|
const answeredSet = new Set(questions.map((_, i) => i).filter(i => isQuestionAnswered(i)));
|
|
@@ -680,12 +680,15 @@ export async function showInterviewRound(
|
|
|
680
680
|
// component: spinner/loader (1-2), status line (1), tool header (1),
|
|
681
681
|
// plus a safety margin for future additions.
|
|
682
682
|
const termRows = (typeof process !== "undefined" && process.stdout?.rows) || 24;
|
|
683
|
-
const footerLines =
|
|
683
|
+
const footerLines = 5; // body spacer + frame top/footer/bottom chrome
|
|
684
684
|
const tuiChrome = 5;
|
|
685
685
|
const maxBody = Math.min(PREVIEW_MAX_LINES, Math.max(6, termRows - lines.length - footerLines - tuiChrome));
|
|
686
686
|
|
|
687
|
-
const previewWidth = Math.max(
|
|
688
|
-
|
|
687
|
+
const previewWidth = Math.max(
|
|
688
|
+
MIN_PREVIEW_WIDTH,
|
|
689
|
+
Math.min(contentWidth - MIN_OPTIONS_WIDTH - DIVIDER_WIDTH, Math.floor(contentWidth * PREVIEW_RATIO)),
|
|
690
|
+
);
|
|
691
|
+
const leftWidth = Math.max(MIN_OPTIONS_WIDTH, contentWidth - previewWidth - DIVIDER_WIDTH);
|
|
689
692
|
|
|
690
693
|
const fullLeft = renderOptionsColumn(leftWidth);
|
|
691
694
|
const leftLines = fullLeft.slice(0, maxBody);
|
|
@@ -709,7 +712,7 @@ export async function showInterviewRound(
|
|
|
709
712
|
while (leftLines.length < maxBody) leftLines.push("");
|
|
710
713
|
while (rightLines.length < maxBody) rightLines.push("");
|
|
711
714
|
const divider = theme.fg("dim", DIVIDER_CHARS);
|
|
712
|
-
lines.push(...mergeSideBySide(leftLines, rightLines, leftWidth, divider,
|
|
715
|
+
lines.push(...mergeSideBySide(leftLines, rightLines, leftWidth, divider, contentWidth));
|
|
713
716
|
|
|
714
717
|
// Footer
|
|
715
718
|
push(ui.blank());
|
|
@@ -729,15 +732,15 @@ export async function showInterviewRound(
|
|
|
729
732
|
hints.push(isLast && allAnswered() ? "enter to review" : "enter to next");
|
|
730
733
|
}
|
|
731
734
|
hints.push("esc to exit");
|
|
732
|
-
|
|
735
|
+
const footer = ui.hints(hints)[0] ?? "";
|
|
733
736
|
|
|
734
|
-
cachedLines = lines;
|
|
735
|
-
return
|
|
737
|
+
cachedLines = renderSharedDialogFrame(theme, title, lines, width, { footer });
|
|
738
|
+
return cachedLines;
|
|
736
739
|
}
|
|
737
740
|
|
|
738
741
|
// ── Original path — no preview, untouched ────────────────
|
|
739
742
|
|
|
740
|
-
const ui = makeUI(theme,
|
|
743
|
+
const ui = makeUI(theme, contentWidth);
|
|
741
744
|
const lines: string[] = [];
|
|
742
745
|
const push = (...rows: string[][]) => { for (const r of rows) lines.push(...r); };
|
|
743
746
|
|
|
@@ -745,8 +748,6 @@ export async function showInterviewRound(
|
|
|
745
748
|
const st = states[currentIdx];
|
|
746
749
|
const multiSel = isMultiSelect(currentIdx);
|
|
747
750
|
|
|
748
|
-
push(ui.bar());
|
|
749
|
-
|
|
750
751
|
// ── Progress header ────────────────────────────────────────────
|
|
751
752
|
if (isMultiQuestion) {
|
|
752
753
|
const unanswered = questions.filter((_, i) => !isQuestionAnswered(i)).length;
|
|
@@ -809,7 +810,7 @@ export async function showInterviewRound(
|
|
|
809
810
|
if (st.notesVisible || focusNotes) {
|
|
810
811
|
push(ui.blank(), ui.notesLabel(focusNotes));
|
|
811
812
|
if (focusNotes) {
|
|
812
|
-
for (const line of getEditor().render(
|
|
813
|
+
for (const line of getEditor().render(contentWidth - 2)) lines.push(truncateToWidth(` ${line}`, contentWidth));
|
|
813
814
|
} else if (st.notes) {
|
|
814
815
|
push(ui.notesText(st.notes));
|
|
815
816
|
}
|
|
@@ -833,10 +834,10 @@ export async function showInterviewRound(
|
|
|
833
834
|
hints.push(isLast && allAnswered() ? "enter to review" : "enter to next");
|
|
834
835
|
}
|
|
835
836
|
hints.push("esc to exit");
|
|
836
|
-
|
|
837
|
+
const footer = ui.hints(hints)[0] ?? "";
|
|
837
838
|
|
|
838
|
-
cachedLines = lines;
|
|
839
|
-
return
|
|
839
|
+
cachedLines = renderSharedDialogFrame(theme, title, lines, width, { footer });
|
|
840
|
+
return cachedLines;
|
|
840
841
|
}
|
|
841
842
|
|
|
842
843
|
return {
|
|
@@ -44,6 +44,7 @@
|
|
|
44
44
|
import type { ExtensionCommandContext } from "@gsd/pi-coding-agent";
|
|
45
45
|
import { type Theme } from "@gsd/pi-coding-agent";
|
|
46
46
|
import { Key, matchesKey, type TUI } from "@gsd/pi-tui";
|
|
47
|
+
import { renderSharedDialogFrame } from "./dialog-frame.js";
|
|
47
48
|
import { makeUI } from "./ui.js";
|
|
48
49
|
|
|
49
50
|
// ─── Public API ───────────────────────────────────────────────────────────────
|
|
@@ -142,14 +143,14 @@ export async function showNextAction(
|
|
|
142
143
|
|
|
143
144
|
function render(width: number): string[] {
|
|
144
145
|
if (cachedLines) return cachedLines;
|
|
145
|
-
const
|
|
146
|
+
const contentWidth = Math.max(1, width - 4);
|
|
147
|
+
const ui = makeUI(theme, contentWidth);
|
|
146
148
|
const lines: string[] = [];
|
|
147
149
|
const push = (...rows: string[][]) => { for (const r of rows) lines.push(...r); };
|
|
148
150
|
|
|
149
151
|
// ── Header — uses success colour to signal completion ────────────
|
|
150
152
|
// Note: next-action intentionally uses "success" for its bar/title
|
|
151
153
|
// to distinguish it from regular accent-coloured screens.
|
|
152
|
-
push(ui.bar());
|
|
153
154
|
push(ui.blank());
|
|
154
155
|
push(ui.header(` ✓ ${opts.title}`));
|
|
155
156
|
|
|
@@ -192,11 +193,10 @@ export async function showNextAction(
|
|
|
192
193
|
|
|
193
194
|
// ── Footer ────────────────────────────────────────────────────────
|
|
194
195
|
const numHint = allActions.map((_, i) => `${i + 1}`).join("/");
|
|
195
|
-
|
|
196
|
-
push(ui.bar());
|
|
196
|
+
const footer = ui.hints([`↑/↓ to choose`, `${numHint} to quick-select`, `enter to confirm`])[0] ?? "";
|
|
197
197
|
|
|
198
|
-
cachedLines = lines;
|
|
199
|
-
return
|
|
198
|
+
cachedLines = renderSharedDialogFrame(theme, "GSD Next Action", lines, width, { footer });
|
|
199
|
+
return cachedLines;
|
|
200
200
|
}
|
|
201
201
|
|
|
202
202
|
return { render, invalidate: () => { cachedLines = undefined; }, handleInput };
|