@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
|
@@ -238,6 +238,24 @@ export async function executeSummarySave(params, basePath = process.cwd()) {
|
|
|
238
238
|
};
|
|
239
239
|
}
|
|
240
240
|
}
|
|
241
|
+
function normalizeVerificationEvidence(evidence) {
|
|
242
|
+
return (evidence ?? []).map((entry) => typeof entry === "string"
|
|
243
|
+
? { command: entry, exitCode: -1, verdict: "unknown (coerced from string)", durationMs: 0 }
|
|
244
|
+
: entry);
|
|
245
|
+
}
|
|
246
|
+
function deriveVerificationSummary(evidence) {
|
|
247
|
+
if (evidence.length === 0)
|
|
248
|
+
return null;
|
|
249
|
+
const rendered = evidence.slice(0, 3).map((entry) => {
|
|
250
|
+
const command = entry.command.trim() || "(unspecified command)";
|
|
251
|
+
const verdict = entry.verdict.trim() || "recorded";
|
|
252
|
+
return `\`${command}\` exited ${entry.exitCode} (${verdict})`;
|
|
253
|
+
});
|
|
254
|
+
const suffix = evidence.length > rendered.length
|
|
255
|
+
? `; ${evidence.length - rendered.length} more check(s) recorded`
|
|
256
|
+
: "";
|
|
257
|
+
return `Verification evidence recorded: ${rendered.join("; ")}${suffix}.`;
|
|
258
|
+
}
|
|
241
259
|
export async function executeTaskComplete(params, basePath = process.cwd()) {
|
|
242
260
|
const dbAvailable = await ensureDbOpen(basePath);
|
|
243
261
|
if (!dbAvailable) {
|
|
@@ -249,7 +267,28 @@ export async function executeTaskComplete(params, basePath = process.cwd()) {
|
|
|
249
267
|
}
|
|
250
268
|
try {
|
|
251
269
|
const coerced = { ...params };
|
|
252
|
-
|
|
270
|
+
const verificationEvidence = normalizeVerificationEvidence(params.verificationEvidence);
|
|
271
|
+
coerced.verificationEvidence = verificationEvidence;
|
|
272
|
+
const verification = typeof params.verification === "string" ? params.verification.trim() : "";
|
|
273
|
+
if (verification.length === 0) {
|
|
274
|
+
const derived = deriveVerificationSummary(verificationEvidence);
|
|
275
|
+
if (derived) {
|
|
276
|
+
coerced.verification = derived;
|
|
277
|
+
}
|
|
278
|
+
else if (params.blockerDiscovered === true) {
|
|
279
|
+
coerced.verification = "Not run: blocker discovered before verification.";
|
|
280
|
+
}
|
|
281
|
+
else {
|
|
282
|
+
return {
|
|
283
|
+
content: [{
|
|
284
|
+
type: "text",
|
|
285
|
+
text: "Error completing task: verification is required unless verificationEvidence is provided or blockerDiscovered is true.",
|
|
286
|
+
}],
|
|
287
|
+
details: { operation: "complete_task", error: "verification_required" },
|
|
288
|
+
isError: true,
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
}
|
|
253
292
|
const result = await handleCompleteTask(coerced, basePath);
|
|
254
293
|
if ("error" in result) {
|
|
255
294
|
return {
|
|
@@ -105,3 +105,54 @@ export function renderFrame(theme, inner, width, options = {}) {
|
|
|
105
105
|
lines.push(border("╰" + "─".repeat(width - 2) + "╯"));
|
|
106
106
|
return lines.map((line) => safeLine(line, width, ""));
|
|
107
107
|
}
|
|
108
|
+
function renderTitledTopBorder(theme, title, width, border) {
|
|
109
|
+
const trimmedTitle = title.trim();
|
|
110
|
+
if (!trimmedTitle || width < 10) {
|
|
111
|
+
return border("╭" + "─".repeat(width - 2) + "╮");
|
|
112
|
+
}
|
|
113
|
+
const maxTitleWidth = Math.max(0, width - 7);
|
|
114
|
+
const safeTitle = safeLine(trimmedTitle, maxTitleWidth);
|
|
115
|
+
const fill = Math.max(0, width - visibleWidth(safeTitle) - 5);
|
|
116
|
+
return border("╭─ ") + theme.bold(theme.fg("accent", safeTitle)) + border(" " + "─".repeat(fill) + "╮");
|
|
117
|
+
}
|
|
118
|
+
export function renderDialogFrame(theme, title, inner, width, options = {}) {
|
|
119
|
+
if (width < 4)
|
|
120
|
+
return inner.map((line) => safeLine(line, width));
|
|
121
|
+
const borderColor = options.borderColor ?? "borderAccent";
|
|
122
|
+
const paddingX = Math.max(0, options.paddingX ?? 1);
|
|
123
|
+
const contentWidth = Math.max(0, width - 2 - paddingX * 2);
|
|
124
|
+
const border = (text) => theme.fg(borderColor, text);
|
|
125
|
+
const pad = " ".repeat(paddingX);
|
|
126
|
+
const lines = [renderTitledTopBorder(theme, title, width, border)];
|
|
127
|
+
const scroll = options.scroll;
|
|
128
|
+
const bodyRows = inner.length;
|
|
129
|
+
const trackOffset = Math.max(0, Math.min(scroll?.trackOffset ?? 0, bodyRows));
|
|
130
|
+
const trackRows = Math.max(0, Math.min(scroll?.trackRows ?? bodyRows, bodyRows - trackOffset));
|
|
131
|
+
const scrollable = !!scroll && scroll.totalRows > scroll.visibleRows && trackRows > 0;
|
|
132
|
+
const thumbLen = scrollable
|
|
133
|
+
? Math.max(1, Math.round((scroll.visibleRows / scroll.totalRows) * trackRows))
|
|
134
|
+
: 0;
|
|
135
|
+
const maxThumbStart = Math.max(0, trackRows - thumbLen);
|
|
136
|
+
const maxScrollOffset = scrollable ? Math.max(1, scroll.totalRows - scroll.visibleRows) : 1;
|
|
137
|
+
const thumbStart = scrollable
|
|
138
|
+
? trackOffset + Math.min(maxThumbStart, Math.round((scroll.offset / maxScrollOffset) * maxThumbStart))
|
|
139
|
+
: -1;
|
|
140
|
+
for (let i = 0; i < inner.length; i++) {
|
|
141
|
+
const line = inner[i] ?? "";
|
|
142
|
+
const rightBorder = scrollable && i >= thumbStart && i < thumbStart + thumbLen ? "┃" : "│";
|
|
143
|
+
lines.push(border("│") + pad + padRightVisible(line, contentWidth) + pad + border(rightBorder));
|
|
144
|
+
}
|
|
145
|
+
const footer = Array.isArray(options.footer)
|
|
146
|
+
? options.footer
|
|
147
|
+
: options.footer
|
|
148
|
+
? [options.footer]
|
|
149
|
+
: [];
|
|
150
|
+
if (footer.length > 0) {
|
|
151
|
+
lines.push(border("├" + "─".repeat(width - 2) + "┤"));
|
|
152
|
+
for (const line of footer) {
|
|
153
|
+
lines.push(border("│") + pad + padRightVisible(line, contentWidth) + pad + border("│"));
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
lines.push(border("╰" + "─".repeat(width - 2) + "╯"));
|
|
157
|
+
return lines.map((line) => safeLine(line, width, ""));
|
|
158
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Natural-language openers for milestone discussion.
|
|
3
|
+
*
|
|
4
|
+
* Keep these short and conversational. They are often the user's first prompt
|
|
5
|
+
* when GSD starts shaping a project or milestone, so they should feel like a
|
|
6
|
+
* collaborator starting a working session rather than a form field.
|
|
7
|
+
*/
|
|
8
|
+
import { randomInt } from "node:crypto";
|
|
9
|
+
export const VISION_ASK_VARIANTS = [
|
|
10
|
+
"What are we building?",
|
|
11
|
+
"What do you want to make next?",
|
|
12
|
+
"What should this become?",
|
|
13
|
+
"What are you picturing?",
|
|
14
|
+
"Where should we take this?",
|
|
15
|
+
"What should this milestone unlock?",
|
|
16
|
+
"Tell me what you want to build.",
|
|
17
|
+
"What should GSD help you shape?",
|
|
18
|
+
];
|
|
19
|
+
export function chooseVisionAskVariant(pickIndex = randomInt) {
|
|
20
|
+
const index = pickIndex(VISION_ASK_VARIANTS.length);
|
|
21
|
+
return VISION_ASK_VARIANTS[index] ?? VISION_ASK_VARIANTS[0];
|
|
22
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { visibleWidth, matchesKey, Key } from "@gsd/pi-tui";
|
|
2
2
|
import { loadVisualizerData } from "./visualizer-data.js";
|
|
3
3
|
import { renderProgressView, renderDepsView, renderMetricsView, renderTimelineView, renderAgentView, renderChangelogView, renderExportView, renderKnowledgeView, renderCapturesView, renderHealthView, } from "./visualizer-views.js";
|
|
4
4
|
import { writeFileSync, mkdirSync } from "node:fs";
|
|
@@ -6,6 +6,7 @@ import { join } from "node:path";
|
|
|
6
6
|
import { writeExportFile } from "./export.js";
|
|
7
7
|
import { gsdRoot } from "./paths.js";
|
|
8
8
|
import { stripAnsi } from "../shared/mod.js";
|
|
9
|
+
import { renderDialogFrame, renderKeyHints } from "./tui/render-kit.js";
|
|
9
10
|
export const TAB_COUNT = 10;
|
|
10
11
|
const TAB_LABELS = [
|
|
11
12
|
"1 Progress",
|
|
@@ -440,51 +441,22 @@ export class GSDVisualizerOverlay {
|
|
|
440
441
|
}
|
|
441
442
|
// Apply scroll
|
|
442
443
|
const viewportHeight = Math.max(5, process.stdout.rows ? process.stdout.rows - 8 : 24);
|
|
443
|
-
const
|
|
444
|
-
const visibleContentRows = Math.max(1, viewportHeight - chromeHeight);
|
|
444
|
+
const visibleContentRows = Math.max(1, viewportHeight - 4);
|
|
445
445
|
this.lastVisibleRows = visibleContentRows;
|
|
446
446
|
const totalLines = content.length;
|
|
447
447
|
const maxScroll = Math.max(0, content.length - visibleContentRows);
|
|
448
448
|
this.scrollOffsets[this.activeTab] = Math.min(this.scrollOffsets[this.activeTab], maxScroll);
|
|
449
449
|
const offset = this.scrollOffsets[this.activeTab];
|
|
450
450
|
const visibleContent = content.slice(offset, offset + visibleContentRows);
|
|
451
|
-
const
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
lines.push(" ".repeat(hintPad) + hint);
|
|
451
|
+
const footer = renderKeyHints(th, ["Tab/Shift+Tab/1-9,0 switch", "/ filter", "PgUp/PgDn scroll", "? help", "esc close"], Math.max(1, width - 4));
|
|
452
|
+
const lines = renderDialogFrame(th, "GSD Visualizer", visibleContent, width, {
|
|
453
|
+
footer,
|
|
454
|
+
scroll: { offset, visibleRows: visibleContentRows, totalRows: totalLines },
|
|
455
|
+
});
|
|
457
456
|
this.cachedWidth = width;
|
|
458
457
|
this.cachedLines = lines;
|
|
459
458
|
return lines;
|
|
460
459
|
}
|
|
461
|
-
wrapInBox(inner, width, offset, visibleRows, totalLines) {
|
|
462
|
-
const th = this.theme;
|
|
463
|
-
const border = (s) => th.fg("borderAccent", s);
|
|
464
|
-
const innerWidth = width - 4;
|
|
465
|
-
const lines = [];
|
|
466
|
-
lines.push(border("\u256d" + "\u2500".repeat(width - 2) + "\u256e"));
|
|
467
|
-
// Compute scroll indicator positions
|
|
468
|
-
const scrollable = totalLines !== undefined && visibleRows !== undefined && totalLines > visibleRows;
|
|
469
|
-
let thumbStart = -1;
|
|
470
|
-
let thumbLen = 0;
|
|
471
|
-
const innerRows = inner.length;
|
|
472
|
-
if (scrollable && innerRows > 0 && totalLines > 0) {
|
|
473
|
-
thumbStart = Math.round(((offset ?? 0) / totalLines) * innerRows);
|
|
474
|
-
thumbLen = Math.max(1, Math.round((visibleRows / totalLines) * innerRows));
|
|
475
|
-
}
|
|
476
|
-
for (let i = 0; i < inner.length; i++) {
|
|
477
|
-
const line = inner[i];
|
|
478
|
-
const truncated = truncateToWidth(line, innerWidth);
|
|
479
|
-
const padWidth = Math.max(0, innerWidth - visibleWidth(truncated));
|
|
480
|
-
const rightBorder = scrollable && i >= thumbStart && i < thumbStart + thumbLen
|
|
481
|
-
? border("\u2503")
|
|
482
|
-
: border("\u2502");
|
|
483
|
-
lines.push(border("\u2502") + " " + truncated + " ".repeat(padWidth) + " " + rightBorder);
|
|
484
|
-
}
|
|
485
|
-
lines.push(border("\u2570" + "\u2500".repeat(width - 2) + "\u256f"));
|
|
486
|
-
return lines;
|
|
487
|
-
}
|
|
488
460
|
invalidate() {
|
|
489
461
|
this.cachedWidth = undefined;
|
|
490
462
|
this.cachedLines = undefined;
|
|
@@ -203,7 +203,7 @@ function validateMilestoneId(milestoneId) {
|
|
|
203
203
|
* - emits worktree-created telemetry on successful entry
|
|
204
204
|
* - notifies the caller via `ctx.notify` for every user-visible outcome
|
|
205
205
|
*/
|
|
206
|
-
export function _enterMilestoneCore(s, deps, milestoneId, ctx) {
|
|
206
|
+
export function _enterMilestoneCore(s, deps, milestoneId, ctx, opts = {}) {
|
|
207
207
|
if (!isValidMilestoneId(milestoneId)) {
|
|
208
208
|
debugLog("WorktreeLifecycle", {
|
|
209
209
|
action: "enterMilestone",
|
|
@@ -305,7 +305,7 @@ export function _enterMilestoneCore(s, deps, milestoneId, ctx) {
|
|
|
305
305
|
// Handles the case where originalBasePath is falsy and basePath is itself
|
|
306
306
|
// a worktree path — prevents double-nested worktree paths (#3729).
|
|
307
307
|
const basePath = resolveWorktreeProjectRoot(s.basePath, s.originalBasePath);
|
|
308
|
-
const mode = getIsolationMode(basePath);
|
|
308
|
+
const mode = opts.modeOverride ?? getIsolationMode(basePath);
|
|
309
309
|
if (s.isolationDegraded) {
|
|
310
310
|
if (mode === "worktree") {
|
|
311
311
|
try {
|
|
@@ -841,7 +841,8 @@ export function mergeMilestoneStandalone(deps, mctx) {
|
|
|
841
841
|
pushed: false,
|
|
842
842
|
};
|
|
843
843
|
}
|
|
844
|
-
const mode =
|
|
844
|
+
const mode = mctx.isolationModeOverride ??
|
|
845
|
+
getIsolationMode(originalBasePath || worktreeBasePath);
|
|
845
846
|
debugLog("WorktreeLifecycle", {
|
|
846
847
|
action: "mergeAndExit",
|
|
847
848
|
milestoneId,
|
|
@@ -1137,6 +1138,7 @@ export class WorktreeLifecycle {
|
|
|
1137
1138
|
originalBasePath: this.s.originalBasePath,
|
|
1138
1139
|
worktreeBasePath: this.s.basePath,
|
|
1139
1140
|
milestoneId,
|
|
1141
|
+
isolationModeOverride: this.s.strandedRecoveryIsolationMode ?? undefined,
|
|
1140
1142
|
isolationDegraded: this.s.isolationDegraded,
|
|
1141
1143
|
notify: ctx.notify,
|
|
1142
1144
|
});
|
|
@@ -1223,6 +1225,7 @@ export class WorktreeLifecycle {
|
|
|
1223
1225
|
// Rebuild GitService after merge (branch HEAD changed)
|
|
1224
1226
|
rebuildGitService(this.s, this.deps);
|
|
1225
1227
|
}
|
|
1228
|
+
this.s.strandedRecoveryIsolationMode = null;
|
|
1226
1229
|
return result;
|
|
1227
1230
|
}
|
|
1228
1231
|
// ── Removed: _mergeWorktreeMode / _mergeBranchMode bodies ────────────
|
|
@@ -1345,6 +1348,24 @@ export class WorktreeLifecycle {
|
|
|
1345
1348
|
resumeFromPausedSession(base, persistedWorktreePath) {
|
|
1346
1349
|
this.s.basePath = resolvePausedResumeBasePath(base, persistedWorktreePath);
|
|
1347
1350
|
}
|
|
1351
|
+
/**
|
|
1352
|
+
* Adopt in-progress stranded work during bootstrap.
|
|
1353
|
+
*
|
|
1354
|
+
* Unlike completed-orphan recovery, this does not merge, delete, or commit.
|
|
1355
|
+
* It only moves the live session onto the branch/worktree proven by the
|
|
1356
|
+
* audit evidence, while preserving that mode for the eventual merge.
|
|
1357
|
+
*/
|
|
1358
|
+
adoptStrandedMilestone(milestoneId, base, ctx, opts) {
|
|
1359
|
+
this.adoptSessionRoot(base);
|
|
1360
|
+
this.s.strandedRecoveryIsolationMode = opts.mode;
|
|
1361
|
+
const result = _enterMilestoneCore(this.s, this.deps, milestoneId, ctx, {
|
|
1362
|
+
modeOverride: opts.mode,
|
|
1363
|
+
});
|
|
1364
|
+
if (!result.ok) {
|
|
1365
|
+
this.s.strandedRecoveryIsolationMode = null;
|
|
1366
|
+
}
|
|
1367
|
+
return result;
|
|
1368
|
+
}
|
|
1348
1369
|
/**
|
|
1349
1370
|
* Adopt an orphan worktree for a bootstrap-time merge (ADR-016 phase 2 / B4,
|
|
1350
1371
|
* issue #5622).
|
|
@@ -12,6 +12,45 @@ export const BRAVE_TOOL_NAMES = ["search-the-web", "search_and_read"];
|
|
|
12
12
|
export const CUSTOM_SEARCH_TOOL_NAMES = ["search-the-web", "search_and_read", "google_search"];
|
|
13
13
|
/** Thinking block types that require signature validation by the API */
|
|
14
14
|
const THINKING_TYPES = new Set(["thinking", "redacted_thinking"]);
|
|
15
|
+
const NATIVE_SERVER_TOOL_USE_TYPES = new Set([
|
|
16
|
+
"server_tool_use",
|
|
17
|
+
"serverToolUse",
|
|
18
|
+
]);
|
|
19
|
+
const NATIVE_WEB_SEARCH_RESULT_TYPES = new Set([
|
|
20
|
+
"web_search_tool_result",
|
|
21
|
+
"webSearchResult",
|
|
22
|
+
]);
|
|
23
|
+
function nativeServerToolId(block) {
|
|
24
|
+
if (!NATIVE_SERVER_TOOL_USE_TYPES.has(block?.type))
|
|
25
|
+
return undefined;
|
|
26
|
+
return typeof block.id === "string" ? block.id : undefined;
|
|
27
|
+
}
|
|
28
|
+
function nativeWebSearchResultId(block) {
|
|
29
|
+
if (!NATIVE_WEB_SEARCH_RESULT_TYPES.has(block?.type))
|
|
30
|
+
return undefined;
|
|
31
|
+
const id = block.type === "webSearchResult" ? block.toolUseId : block.tool_use_id;
|
|
32
|
+
return typeof id === "string" ? id : undefined;
|
|
33
|
+
}
|
|
34
|
+
function hasCompleteNativeServerToolReplay(content) {
|
|
35
|
+
const pendingToolUseIds = new Set();
|
|
36
|
+
let sawNativeServerToolUse = false;
|
|
37
|
+
for (const block of content) {
|
|
38
|
+
const toolUseId = nativeServerToolId(block);
|
|
39
|
+
if (toolUseId !== undefined) {
|
|
40
|
+
if (pendingToolUseIds.has(toolUseId))
|
|
41
|
+
return false;
|
|
42
|
+
sawNativeServerToolUse = true;
|
|
43
|
+
pendingToolUseIds.add(toolUseId);
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
const resultId = nativeWebSearchResultId(block);
|
|
47
|
+
if (resultId !== undefined) {
|
|
48
|
+
if (!pendingToolUseIds.delete(resultId))
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return sawNativeServerToolUse && pendingToolUseIds.size === 0;
|
|
53
|
+
}
|
|
15
54
|
/**
|
|
16
55
|
* Providers whose Anthropic-Messages endpoint is known to accept the native
|
|
17
56
|
* `web_search_20250305` server tool. Anthropic-shaped transports NOT in this
|
|
@@ -30,6 +69,10 @@ const NATIVE_WEB_SEARCH_PROVIDERS = new Set([
|
|
|
30
69
|
"anthropic-vertex",
|
|
31
70
|
"vercel-ai-gateway",
|
|
32
71
|
]);
|
|
72
|
+
function looksLikeAnthropicModelName(modelName) {
|
|
73
|
+
const normalized = modelName.trim().toLowerCase();
|
|
74
|
+
return normalized.startsWith("claude-") || normalized.startsWith("anthropic/claude-");
|
|
75
|
+
}
|
|
33
76
|
/**
|
|
34
77
|
* True when the model is an Anthropic-shaped transport AND the provider is
|
|
35
78
|
* known to accept the native `web_search_20250305` tool. Gate both on api
|
|
@@ -74,11 +117,10 @@ export function preferBraveSearch() {
|
|
|
74
117
|
* those blocks. The Anthropic API detects the modification and rejects the
|
|
75
118
|
* request with "thinking blocks cannot be modified."
|
|
76
119
|
*
|
|
77
|
-
* Fix: Remove thinking blocks from
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
* current turn regardless.
|
|
120
|
+
* Fix: Remove thinking blocks only from assistant messages that do not carry
|
|
121
|
+
* native server-tool blocks. Complete native server-tool histories can be
|
|
122
|
+
* replayed as-is; stripping thinking from those messages is itself a latest
|
|
123
|
+
* assistant message modification.
|
|
82
124
|
*/
|
|
83
125
|
export function stripThinkingFromHistory(messages) {
|
|
84
126
|
for (const msg of messages) {
|
|
@@ -87,6 +129,9 @@ export function stripThinkingFromHistory(messages) {
|
|
|
87
129
|
const content = msg.content;
|
|
88
130
|
if (!Array.isArray(content))
|
|
89
131
|
continue;
|
|
132
|
+
if (hasCompleteNativeServerToolReplay(content)) {
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
90
135
|
msg.content = content.filter((block) => !THINKING_TYPES.has(block?.type));
|
|
91
136
|
}
|
|
92
137
|
}
|
|
@@ -152,6 +197,8 @@ export function registerNativeSearchHooks(pi) {
|
|
|
152
197
|
// The model name heuristic is needed for session restores where
|
|
153
198
|
// modelsAreEqual suppresses model_select AND the SDK doesn't pass model.
|
|
154
199
|
const eventModel = event.model;
|
|
200
|
+
const payloadModelName = typeof payload.model === "string" ? payload.model : "";
|
|
201
|
+
const payloadLooksAnthropic = payloadModelName ? looksLikeAnthropicModelName(payloadModelName) : undefined;
|
|
155
202
|
let isAnthropic;
|
|
156
203
|
if (eventModel?.api || eventModel?.provider) {
|
|
157
204
|
// Preferred path: gate on api shape + provider allowlist. Both fields
|
|
@@ -161,13 +208,15 @@ export function registerNativeSearchHooks(pi) {
|
|
|
161
208
|
isAnthropic = supportsNativeWebSearch(eventModel);
|
|
162
209
|
}
|
|
163
210
|
else if (modelSelectFired) {
|
|
164
|
-
|
|
211
|
+
// The model_select flag can be stale if the next request omits event.model
|
|
212
|
+
// after a provider switch. A concrete non-Claude payload must win so an
|
|
213
|
+
// Anthropic-only tool never leaks into OpenAI Responses requests.
|
|
214
|
+
isAnthropic = isAnthropicProvider && payloadLooksAnthropic !== false;
|
|
165
215
|
}
|
|
166
216
|
else {
|
|
167
217
|
// Last resort: session-restore paths where the SDK doesn't pass model.
|
|
168
218
|
// The model-name prefix is best-effort and assumes direct Anthropic.
|
|
169
|
-
|
|
170
|
-
isAnthropic = modelName.startsWith("claude-");
|
|
219
|
+
isAnthropic = payloadLooksAnthropic === true;
|
|
171
220
|
}
|
|
172
221
|
if (!isAnthropic)
|
|
173
222
|
return;
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
* if (!confirmed) return textResult("Cancelled.");
|
|
16
16
|
*/
|
|
17
17
|
import { Key, matchesKey, truncateToWidth } from "@gsd/pi-tui";
|
|
18
|
+
import { renderSharedDialogFrame } from "./dialog-frame.js";
|
|
18
19
|
import { makeUI, GLYPH } from "./ui.js";
|
|
19
20
|
/**
|
|
20
21
|
* Show a themed yes/no confirmation dialog.
|
|
@@ -69,12 +70,13 @@ export async function showConfirm(ctx, opts) {
|
|
|
69
70
|
function render(width) {
|
|
70
71
|
if (cachedLines)
|
|
71
72
|
return cachedLines;
|
|
72
|
-
const
|
|
73
|
+
const contentWidth = Math.max(1, width - 4);
|
|
74
|
+
const ui = makeUI(theme, contentWidth);
|
|
73
75
|
const lines = [];
|
|
74
76
|
const push = (...rows) => { for (const r of rows)
|
|
75
77
|
lines.push(...r); };
|
|
76
|
-
push(ui.
|
|
77
|
-
const add = (s) => truncateToWidth(s,
|
|
78
|
+
push(ui.blank(), ui.subtitle(` ${opts.message}`), ui.blank());
|
|
79
|
+
const add = (s) => truncateToWidth(s, contentWidth);
|
|
78
80
|
const option = (num, label, selected) => {
|
|
79
81
|
if (selected) {
|
|
80
82
|
return add(` ${theme.fg("accent", GLYPH.cursor)} ${theme.fg("accent", `${num}. ${label}`)}`);
|
|
@@ -83,9 +85,10 @@ export async function showConfirm(ctx, opts) {
|
|
|
83
85
|
};
|
|
84
86
|
lines.push(option(1, yesLabel, cursor === 0));
|
|
85
87
|
lines.push(option(2, noLabel, cursor === 1));
|
|
86
|
-
push(ui.blank()
|
|
87
|
-
|
|
88
|
-
|
|
88
|
+
push(ui.blank());
|
|
89
|
+
const footer = ui.hints(["↑/↓ to choose", "y/n to quick-select", "enter to confirm"])[0] ?? "";
|
|
90
|
+
cachedLines = renderSharedDialogFrame(theme, opts.title, lines, width, { footer });
|
|
91
|
+
return cachedLines;
|
|
89
92
|
}
|
|
90
93
|
return {
|
|
91
94
|
render,
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { truncateToWidth, visibleWidth } from "@gsd/pi-tui";
|
|
2
|
+
function safeLine(text, width) {
|
|
3
|
+
return truncateToWidth(text, width, "");
|
|
4
|
+
}
|
|
5
|
+
function padVisible(text, width) {
|
|
6
|
+
const clipped = safeLine(text, width);
|
|
7
|
+
return clipped + " ".repeat(Math.max(0, width - visibleWidth(clipped)));
|
|
8
|
+
}
|
|
9
|
+
function renderTopBorder(theme, title, width, border) {
|
|
10
|
+
const trimmedTitle = title.trim();
|
|
11
|
+
if (!trimmedTitle || width < 10) {
|
|
12
|
+
return border("╭" + "─".repeat(width - 2) + "╮");
|
|
13
|
+
}
|
|
14
|
+
const safeTitle = safeLine(trimmedTitle, Math.max(0, width - 7));
|
|
15
|
+
const fill = Math.max(0, width - visibleWidth(safeTitle) - 5);
|
|
16
|
+
return border("╭─ ") + theme.bold(theme.fg("accent", safeTitle)) + border(" " + "─".repeat(fill) + "╮");
|
|
17
|
+
}
|
|
18
|
+
export function renderSharedDialogFrame(theme, title, inner, width, options = {}) {
|
|
19
|
+
if (width < 4)
|
|
20
|
+
return inner.map((line) => safeLine(line, width));
|
|
21
|
+
const paddingX = Math.max(0, options.paddingX ?? 1);
|
|
22
|
+
const contentWidth = Math.max(0, width - 2 - paddingX * 2);
|
|
23
|
+
const border = (text) => theme.fg(options.borderColor ?? "borderAccent", text);
|
|
24
|
+
const pad = " ".repeat(paddingX);
|
|
25
|
+
const lines = [renderTopBorder(theme, title, width, border)];
|
|
26
|
+
for (const line of inner) {
|
|
27
|
+
lines.push(border("│") + pad + padVisible(line, contentWidth) + pad + border("│"));
|
|
28
|
+
}
|
|
29
|
+
const footer = Array.isArray(options.footer)
|
|
30
|
+
? options.footer
|
|
31
|
+
: options.footer
|
|
32
|
+
? [options.footer]
|
|
33
|
+
: [];
|
|
34
|
+
if (footer.length > 0) {
|
|
35
|
+
lines.push(border("├" + "─".repeat(width - 2) + "┤"));
|
|
36
|
+
for (const line of footer) {
|
|
37
|
+
lines.push(border("│") + pad + padVisible(line, contentWidth) + pad + border("│"));
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
lines.push(border("╰" + "─".repeat(width - 2) + "╯"));
|
|
41
|
+
return lines;
|
|
42
|
+
}
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
*/
|
|
28
28
|
import { getMarkdownTheme } from "@gsd/pi-coding-agent";
|
|
29
29
|
import { Editor, Key, Markdown, matchesKey, truncateToWidth, } from "@gsd/pi-tui";
|
|
30
|
+
import { renderSharedDialogFrame } from "./dialog-frame.js";
|
|
30
31
|
import { mergeSideBySide } from "./layout-utils.js";
|
|
31
32
|
import { makeUI, INDENT } from "./ui.js";
|
|
32
33
|
// ─── Constants ────────────────────────────────────────────────────────────────
|
|
@@ -39,6 +40,9 @@ const PREVIEW_RATIO = 0.60; // preview gets the majority of the width
|
|
|
39
40
|
const DIVIDER_CHARS = " │ ";
|
|
40
41
|
const DIVIDER_WIDTH = 3;
|
|
41
42
|
const PREVIEW_MAX_LINES = 20; // hard cap — keeps total ≤ 24 rows for single-question
|
|
43
|
+
function dialogContentWidth(width) {
|
|
44
|
+
return width < 4 ? Math.max(1, width) : Math.max(1, width - 4);
|
|
45
|
+
}
|
|
42
46
|
// ─── Wrap-up screen ───────────────────────────────────────────────────────────
|
|
43
47
|
export async function showWrapUpScreen(opts, ctx) {
|
|
44
48
|
return ctx.ui.custom((tui, theme, _kb, done) => {
|
|
@@ -81,11 +85,12 @@ export async function showWrapUpScreen(opts, ctx) {
|
|
|
81
85
|
function render(width) {
|
|
82
86
|
if (cachedLines)
|
|
83
87
|
return cachedLines;
|
|
84
|
-
const
|
|
88
|
+
const contentWidth = dialogContentWidth(width);
|
|
89
|
+
const ui = makeUI(theme, contentWidth);
|
|
85
90
|
const lines = [];
|
|
86
91
|
const push = (...rows) => { for (const r of rows)
|
|
87
92
|
lines.push(...r); };
|
|
88
|
-
push(ui.
|
|
93
|
+
push(ui.blank());
|
|
89
94
|
if (opts.progress)
|
|
90
95
|
push(ui.meta(` ${opts.progress}`), ui.blank());
|
|
91
96
|
if (cursorIdx === 1) {
|
|
@@ -101,9 +106,10 @@ export async function showWrapUpScreen(opts, ctx) {
|
|
|
101
106
|
else {
|
|
102
107
|
push(ui.actionUnselected(2, opts.keepGoingLabel, "Continue with another batch of questions."));
|
|
103
108
|
}
|
|
104
|
-
push(ui.blank()
|
|
105
|
-
|
|
106
|
-
|
|
109
|
+
push(ui.blank());
|
|
110
|
+
const footer = ui.hints(["↑/↓ to choose", "1/2 to quick-select", "enter to confirm"])[0] ?? "";
|
|
111
|
+
cachedLines = renderSharedDialogFrame(theme, opts.headline, lines, width, { footer });
|
|
112
|
+
return cachedLines;
|
|
107
113
|
}
|
|
108
114
|
return {
|
|
109
115
|
render,
|
|
@@ -427,11 +433,13 @@ export async function showInterviewRound(questions, opts, ctx) {
|
|
|
427
433
|
}
|
|
428
434
|
// ── Review screen ────────────────────────────────────────────────
|
|
429
435
|
function renderReviewScreen(width) {
|
|
430
|
-
const
|
|
436
|
+
const contentWidth = dialogContentWidth(width);
|
|
437
|
+
const title = opts.reviewHeadline ?? "Review your answers";
|
|
438
|
+
const ui = makeUI(theme, contentWidth);
|
|
431
439
|
const lines = [];
|
|
432
440
|
const push = (...rows) => { for (const r of rows)
|
|
433
441
|
lines.push(...r); };
|
|
434
|
-
push(ui.
|
|
442
|
+
push(ui.blank());
|
|
435
443
|
for (let i = 0; i < questions.length; i++) {
|
|
436
444
|
const q = questions[i];
|
|
437
445
|
const st = states[i];
|
|
@@ -453,16 +461,19 @@ export async function showInterviewRound(questions, opts, ctx) {
|
|
|
453
461
|
push(ui.note(`${INDENT.note}note: ${st.notes}`));
|
|
454
462
|
push(ui.blank());
|
|
455
463
|
}
|
|
456
|
-
push(ui.actionSelected(0, "Submit answers"), ui.blank()
|
|
457
|
-
|
|
464
|
+
push(ui.actionSelected(0, "Submit answers"), ui.blank());
|
|
465
|
+
const footer = ui.hints(["← to go back and edit", "enter to submit", `esc to ${opts.exitLabel ?? "end interview"}`])[0] ?? "";
|
|
466
|
+
return renderSharedDialogFrame(theme, title, lines, width, { footer });
|
|
458
467
|
}
|
|
459
468
|
// ── Exit confirm screen ──────────────────────────────────────────
|
|
460
469
|
function renderExitConfirm(width) {
|
|
461
|
-
const
|
|
470
|
+
const contentWidth = dialogContentWidth(width);
|
|
471
|
+
const title = opts.exitHeadline ?? "End interview?";
|
|
472
|
+
const ui = makeUI(theme, contentWidth);
|
|
462
473
|
const lines = [];
|
|
463
474
|
const push = (...rows) => { for (const r of rows)
|
|
464
475
|
lines.push(...r); };
|
|
465
|
-
push(ui.
|
|
476
|
+
push(ui.blank(), ui.subtitle(" Answers from this batch won't be saved."), ui.blank());
|
|
466
477
|
const keepGoingLabel = "Keep going";
|
|
467
478
|
const exitActionLabel = opts.exitLabel
|
|
468
479
|
? opts.exitLabel.charAt(0).toUpperCase() + opts.exitLabel.slice(1)
|
|
@@ -480,8 +491,9 @@ export async function showInterviewRound(questions, opts, ctx) {
|
|
|
480
491
|
else {
|
|
481
492
|
push(ui.actionUnselected(2, exitActionLabel, "Exit and discard this batch of answers."));
|
|
482
493
|
}
|
|
483
|
-
push(ui.blank()
|
|
484
|
-
|
|
494
|
+
push(ui.blank());
|
|
495
|
+
const footer = ui.hints(["↑/↓ to choose", "1/2 to quick-select", "enter to confirm"])[0] ?? "";
|
|
496
|
+
return renderSharedDialogFrame(theme, title, lines, width, { footer });
|
|
485
497
|
}
|
|
486
498
|
// ── Preview helpers ──────────────────────────────────────────────
|
|
487
499
|
let mdThemeCache = null;
|
|
@@ -587,15 +599,16 @@ export async function showInterviewRound(questions, opts, ctx) {
|
|
|
587
599
|
cachedLines = renderReviewScreen(width);
|
|
588
600
|
return cachedLines;
|
|
589
601
|
}
|
|
602
|
+
const contentWidth = dialogContentWidth(width);
|
|
603
|
+
const title = questions[currentIdx]?.header || "GSD Interview";
|
|
590
604
|
const useSideBySide = questionHasAnyPreview()
|
|
591
|
-
&&
|
|
605
|
+
&& contentWidth >= (MIN_OPTIONS_WIDTH + MIN_PREVIEW_WIDTH + DIVIDER_WIDTH);
|
|
592
606
|
if (useSideBySide) {
|
|
593
607
|
// ── Preview path ──────────────────────────────────────
|
|
594
|
-
const ui = makeUI(theme,
|
|
608
|
+
const ui = makeUI(theme, contentWidth);
|
|
595
609
|
const lines = [];
|
|
596
610
|
const push = (...rows) => { for (const r of rows)
|
|
597
611
|
lines.push(...r); };
|
|
598
|
-
push(ui.bar());
|
|
599
612
|
if (isMultiQuestion) {
|
|
600
613
|
const unanswered = questions.filter((_, i) => !isQuestionAnswered(i)).length;
|
|
601
614
|
const answeredSet = new Set(questions.map((_, i) => i).filter(i => isQuestionAnswered(i)));
|
|
@@ -619,11 +632,11 @@ export async function showInterviewRound(questions, opts, ctx) {
|
|
|
619
632
|
// component: spinner/loader (1-2), status line (1), tool header (1),
|
|
620
633
|
// plus a safety margin for future additions.
|
|
621
634
|
const termRows = (typeof process !== "undefined" && process.stdout?.rows) || 24;
|
|
622
|
-
const footerLines =
|
|
635
|
+
const footerLines = 5; // body spacer + frame top/footer/bottom chrome
|
|
623
636
|
const tuiChrome = 5;
|
|
624
637
|
const maxBody = Math.min(PREVIEW_MAX_LINES, Math.max(6, termRows - lines.length - footerLines - tuiChrome));
|
|
625
|
-
const previewWidth = Math.max(MIN_PREVIEW_WIDTH, Math.floor(
|
|
626
|
-
const leftWidth = Math.max(MIN_OPTIONS_WIDTH,
|
|
638
|
+
const previewWidth = Math.max(MIN_PREVIEW_WIDTH, Math.min(contentWidth - MIN_OPTIONS_WIDTH - DIVIDER_WIDTH, Math.floor(contentWidth * PREVIEW_RATIO)));
|
|
639
|
+
const leftWidth = Math.max(MIN_OPTIONS_WIDTH, contentWidth - previewWidth - DIVIDER_WIDTH);
|
|
627
640
|
const fullLeft = renderOptionsColumn(leftWidth);
|
|
628
641
|
const leftLines = fullLeft.slice(0, maxBody);
|
|
629
642
|
if (fullLeft.length > maxBody) {
|
|
@@ -646,7 +659,7 @@ export async function showInterviewRound(questions, opts, ctx) {
|
|
|
646
659
|
while (rightLines.length < maxBody)
|
|
647
660
|
rightLines.push("");
|
|
648
661
|
const divider = theme.fg("dim", DIVIDER_CHARS);
|
|
649
|
-
lines.push(...mergeSideBySide(leftLines, rightLines, leftWidth, divider,
|
|
662
|
+
lines.push(...mergeSideBySide(leftLines, rightLines, leftWidth, divider, contentWidth));
|
|
650
663
|
// Footer
|
|
651
664
|
push(ui.blank());
|
|
652
665
|
const isLast = !isMultiQuestion || currentIdx === questions.length - 1;
|
|
@@ -669,19 +682,18 @@ export async function showInterviewRound(questions, opts, ctx) {
|
|
|
669
682
|
hints.push(isLast && allAnswered() ? "enter to review" : "enter to next");
|
|
670
683
|
}
|
|
671
684
|
hints.push("esc to exit");
|
|
672
|
-
|
|
673
|
-
cachedLines = lines;
|
|
674
|
-
return
|
|
685
|
+
const footer = ui.hints(hints)[0] ?? "";
|
|
686
|
+
cachedLines = renderSharedDialogFrame(theme, title, lines, width, { footer });
|
|
687
|
+
return cachedLines;
|
|
675
688
|
}
|
|
676
689
|
// ── Original path — no preview, untouched ────────────────
|
|
677
|
-
const ui = makeUI(theme,
|
|
690
|
+
const ui = makeUI(theme, contentWidth);
|
|
678
691
|
const lines = [];
|
|
679
692
|
const push = (...rows) => { for (const r of rows)
|
|
680
693
|
lines.push(...r); };
|
|
681
694
|
const q = questions[currentIdx];
|
|
682
695
|
const st = states[currentIdx];
|
|
683
696
|
const multiSel = isMultiSelect(currentIdx);
|
|
684
|
-
push(ui.bar());
|
|
685
697
|
// ── Progress header ────────────────────────────────────────────
|
|
686
698
|
if (isMultiQuestion) {
|
|
687
699
|
const unanswered = questions.filter((_, i) => !isQuestionAnswered(i)).length;
|
|
@@ -750,8 +762,8 @@ export async function showInterviewRound(questions, opts, ctx) {
|
|
|
750
762
|
if (st.notesVisible || focusNotes) {
|
|
751
763
|
push(ui.blank(), ui.notesLabel(focusNotes));
|
|
752
764
|
if (focusNotes) {
|
|
753
|
-
for (const line of getEditor().render(
|
|
754
|
-
lines.push(truncateToWidth(` ${line}`,
|
|
765
|
+
for (const line of getEditor().render(contentWidth - 2))
|
|
766
|
+
lines.push(truncateToWidth(` ${line}`, contentWidth));
|
|
755
767
|
}
|
|
756
768
|
else if (st.notes) {
|
|
757
769
|
push(ui.notesText(st.notes));
|
|
@@ -779,9 +791,9 @@ export async function showInterviewRound(questions, opts, ctx) {
|
|
|
779
791
|
hints.push(isLast && allAnswered() ? "enter to review" : "enter to next");
|
|
780
792
|
}
|
|
781
793
|
hints.push("esc to exit");
|
|
782
|
-
|
|
783
|
-
cachedLines = lines;
|
|
784
|
-
return
|
|
794
|
+
const footer = ui.hints(hints)[0] ?? "";
|
|
795
|
+
cachedLines = renderSharedDialogFrame(theme, title, lines, width, { footer });
|
|
796
|
+
return cachedLines;
|
|
785
797
|
}
|
|
786
798
|
return {
|
|
787
799
|
render,
|