@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
|
@@ -15,16 +15,15 @@ import { delimiter, join } from "node:path";
|
|
|
15
15
|
import { AuthStorage } from "@gsd/pi-coding-agent";
|
|
16
16
|
import { getEnvApiKey } from "@gsd/pi-ai";
|
|
17
17
|
import { loadEffectiveGSDPreferences } from "./preferences.js";
|
|
18
|
-
import { getAuthPath, PROVIDER_REGISTRY } from "./key-manager.js";
|
|
18
|
+
import { getAuthPath, PROVIDER_REGISTRY, supportsBrowserOAuth } from "./key-manager.js";
|
|
19
19
|
import { homedir } from "node:os";
|
|
20
20
|
// ── Provider routing constants ────────────────────────────────────────────────
|
|
21
21
|
/**
|
|
22
22
|
* Providers that use external CLI authentication (not API keys).
|
|
23
|
-
*
|
|
23
|
+
* When explicitly selected, the provider's own CLI/session owns auth.
|
|
24
24
|
*/
|
|
25
25
|
const CLI_AUTH_PROVIDERS = new Set([
|
|
26
26
|
"claude-code",
|
|
27
|
-
"openai-codex",
|
|
28
27
|
"google-gemini-cli",
|
|
29
28
|
"google-antigravity",
|
|
30
29
|
]);
|
|
@@ -126,23 +125,26 @@ function collectConfiguredModelProviders() {
|
|
|
126
125
|
* Used for lightweight binary-presence checks (PATH scan, no subprocess).
|
|
127
126
|
*/
|
|
128
127
|
const CLI_BINARY_MAP = {
|
|
129
|
-
"claude-code": "claude",
|
|
130
|
-
"
|
|
131
|
-
"google-
|
|
132
|
-
"google-antigravity": "antigravity",
|
|
128
|
+
"claude-code": ["claude", "claude-code"],
|
|
129
|
+
"google-gemini-cli": ["gemini"],
|
|
130
|
+
"google-antigravity": ["agy"],
|
|
133
131
|
};
|
|
132
|
+
const CLI_AUTH_PATH_CHECK_PROVIDERS = new Set([
|
|
133
|
+
"google-gemini-cli",
|
|
134
|
+
"google-antigravity",
|
|
135
|
+
]);
|
|
134
136
|
/**
|
|
135
137
|
* Check if a CLI provider's binary exists anywhere in PATH.
|
|
136
138
|
* Fast filesystem scan — no subprocess, no network, sub-1ms.
|
|
137
139
|
*/
|
|
138
140
|
function isCliBinaryInPath(providerId) {
|
|
139
|
-
const
|
|
140
|
-
if (!
|
|
141
|
+
const binaries = CLI_BINARY_MAP[providerId];
|
|
142
|
+
if (!binaries)
|
|
141
143
|
return false;
|
|
142
144
|
const pathDirs = (process.env.PATH ?? "").split(delimiter).filter(Boolean);
|
|
143
145
|
// On Windows, command shims are commonly installed as .cmd/.exe/.bat/.com.
|
|
144
146
|
// Scan PATHEXT candidates in addition to the bare binary name.
|
|
145
|
-
const executableNames = [
|
|
147
|
+
const executableNames = [...binaries];
|
|
146
148
|
if (process.platform === "win32") {
|
|
147
149
|
const rawPathExt = process.env.PATHEXT
|
|
148
150
|
?.split(";")
|
|
@@ -150,10 +152,12 @@ function isCliBinaryInPath(providerId) {
|
|
|
150
152
|
.filter(Boolean) ?? [];
|
|
151
153
|
const normalizedPathExt = rawPathExt.map(ext => ext.startsWith(".") ? ext.toLowerCase() : `.${ext.toLowerCase()}`);
|
|
152
154
|
const defaultExt = [".exe", ".cmd", ".bat", ".com"];
|
|
153
|
-
for (const
|
|
154
|
-
const
|
|
155
|
-
|
|
156
|
-
executableNames.
|
|
155
|
+
for (const binary of binaries) {
|
|
156
|
+
for (const ext of [...normalizedPathExt, ...defaultExt]) {
|
|
157
|
+
const candidate = `${binary}${ext}`;
|
|
158
|
+
if (!executableNames.includes(candidate))
|
|
159
|
+
executableNames.push(candidate);
|
|
160
|
+
}
|
|
157
161
|
}
|
|
158
162
|
}
|
|
159
163
|
return pathDirs.some(dir => executableNames.some(name => existsSync(join(dir, name))));
|
|
@@ -185,11 +189,6 @@ function hasModelsJsonApiKey(providerId) {
|
|
|
185
189
|
}
|
|
186
190
|
function resolveKey(providerId) {
|
|
187
191
|
const info = PROVIDER_REGISTRY.find(p => p.id === providerId);
|
|
188
|
-
// claude-code never stores credentials in auth.json — GSD delegates entirely to
|
|
189
|
-
// the local CLI binary. Presence of the binary in PATH is the only signal.
|
|
190
|
-
if (providerId === "claude-code") {
|
|
191
|
-
return { found: isCliBinaryInPath("claude-code"), source: "env", backedOff: false };
|
|
192
|
-
}
|
|
193
192
|
if (providerId === "anthropic-vertex" && process.env.ANTHROPIC_VERTEX_PROJECT_ID) {
|
|
194
193
|
return { found: true, source: "env", backedOff: false };
|
|
195
194
|
}
|
|
@@ -201,7 +200,16 @@ function resolveKey(providerId) {
|
|
|
201
200
|
const creds = auth.getCredentialsForProvider(providerId);
|
|
202
201
|
if (creds.length > 0) {
|
|
203
202
|
// Filter out empty placeholder keys (from skipped onboarding)
|
|
204
|
-
const hasRealKey = creds.some(c =>
|
|
203
|
+
const hasRealKey = creds.some(c => {
|
|
204
|
+
if (c.type === "oauth")
|
|
205
|
+
return true;
|
|
206
|
+
if (c.type !== "api_key")
|
|
207
|
+
return false;
|
|
208
|
+
const key = c.key?.trim();
|
|
209
|
+
if (!key)
|
|
210
|
+
return false;
|
|
211
|
+
return !(CLI_AUTH_PROVIDERS.has(providerId) && key === "cli");
|
|
212
|
+
});
|
|
205
213
|
if (hasRealKey) {
|
|
206
214
|
return {
|
|
207
215
|
found: true,
|
|
@@ -229,6 +237,11 @@ function resolveKey(providerId) {
|
|
|
229
237
|
if (hasModelsJsonApiKey(providerId)) {
|
|
230
238
|
return { found: true, source: "models.json", backedOff: false };
|
|
231
239
|
}
|
|
240
|
+
// Cross-provider routes can use a local CLI when it is installed. Explicit
|
|
241
|
+
// external CLI provider selections are handled in checkLlmProviders() below.
|
|
242
|
+
if (CLI_AUTH_PROVIDERS.has(providerId) && isCliBinaryInPath(providerId)) {
|
|
243
|
+
return { found: true, source: "env", backedOff: false };
|
|
244
|
+
}
|
|
232
245
|
return { found: false, source: "none", backedOff: false };
|
|
233
246
|
}
|
|
234
247
|
// ── Individual check groups ────────────────────────────────────────────────────
|
|
@@ -236,15 +249,32 @@ function checkLlmProviders() {
|
|
|
236
249
|
const required = collectConfiguredModelProviders();
|
|
237
250
|
const results = [];
|
|
238
251
|
for (const providerId of required) {
|
|
239
|
-
// CLI-authenticated providers don't need API keys
|
|
252
|
+
// CLI-authenticated providers don't need API keys. The provider's own
|
|
253
|
+
// request path validates CLI sessions when it is used.
|
|
240
254
|
if (CLI_AUTH_PROVIDERS.has(providerId)) {
|
|
241
255
|
const info = PROVIDER_REGISTRY.find(p => p.id === providerId);
|
|
256
|
+
const label = info?.label ?? providerId;
|
|
257
|
+
if (CLI_AUTH_PATH_CHECK_PROVIDERS.has(providerId) && !isCliBinaryInPath(providerId)) {
|
|
258
|
+
const binaries = CLI_BINARY_MAP[providerId]?.map(binary => `\`${binary}\``).join(" or ");
|
|
259
|
+
results.push({
|
|
260
|
+
name: providerId,
|
|
261
|
+
label,
|
|
262
|
+
category: "llm",
|
|
263
|
+
status: "error",
|
|
264
|
+
message: `${label} — CLI not found`,
|
|
265
|
+
detail: binaries
|
|
266
|
+
? `Install ${label} and ensure ${binaries} is on PATH`
|
|
267
|
+
: `Install ${label} and ensure its CLI is on PATH`,
|
|
268
|
+
required: true,
|
|
269
|
+
});
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
242
272
|
results.push({
|
|
243
273
|
name: providerId,
|
|
244
|
-
label
|
|
274
|
+
label,
|
|
245
275
|
category: "llm",
|
|
246
276
|
status: "ok",
|
|
247
|
-
message: `${
|
|
277
|
+
message: `${label} — CLI auth (no key needed)`,
|
|
248
278
|
required: true,
|
|
249
279
|
});
|
|
250
280
|
continue;
|
|
@@ -282,7 +312,7 @@ function checkLlmProviders() {
|
|
|
282
312
|
message: `${label} — not configured`,
|
|
283
313
|
detail: providerId === "anthropic-vertex"
|
|
284
314
|
? "Set ANTHROPIC_VERTEX_PROJECT_ID and authenticate with Google ADC"
|
|
285
|
-
: info
|
|
315
|
+
: info && supportsBrowserOAuth(info)
|
|
286
316
|
? `Run /gsd keys to authenticate`
|
|
287
317
|
: `Set ${envVar} or run /gsd keys`,
|
|
288
318
|
required: true,
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// File Purpose: Detect and reconcile unresolved Git conflict state before automation runs.
|
|
3
3
|
import { spawnSync } from "node:child_process";
|
|
4
4
|
import { existsSync } from "node:fs";
|
|
5
|
-
import { join } from "node:path";
|
|
5
|
+
import { dirname, join, resolve } from "node:path";
|
|
6
6
|
import { autoResolveSafeConflictPaths } from "./git-conflict-resolve.js";
|
|
7
7
|
import { GIT_NO_PROMPT_ENV } from "./git-constants.js";
|
|
8
8
|
import { abortAndReset } from "./git-self-heal.js";
|
|
@@ -10,6 +10,23 @@ import { logWarning } from "./workflow-logger.js";
|
|
|
10
10
|
function splitZeroDelimited(output) {
|
|
11
11
|
return output.split("\0").filter(Boolean);
|
|
12
12
|
}
|
|
13
|
+
function hasGitMarker(basePath) {
|
|
14
|
+
try {
|
|
15
|
+
let dir = resolve(basePath);
|
|
16
|
+
for (let i = 0; i < 30; i++) {
|
|
17
|
+
if (existsSync(join(dir, ".git")))
|
|
18
|
+
return true;
|
|
19
|
+
const parent = dirname(dir);
|
|
20
|
+
if (parent === dir)
|
|
21
|
+
break;
|
|
22
|
+
dir = parent;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
// Fall through to the git probes, which will report unknown on failure.
|
|
27
|
+
}
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
13
30
|
export function listUnmergedGitPaths(basePath) {
|
|
14
31
|
try {
|
|
15
32
|
const output = spawnSync("git", ["diff", "--name-only", "--diff-filter=U", "-z"], {
|
|
@@ -57,6 +74,14 @@ export function listMergeStateBlockers(basePath) {
|
|
|
57
74
|
return MERGE_STATE_MARKERS.filter((marker) => existsSync(join(gitDir, marker)));
|
|
58
75
|
}
|
|
59
76
|
export function probeGitConflictState(basePath) {
|
|
77
|
+
if (!hasGitMarker(basePath)) {
|
|
78
|
+
return {
|
|
79
|
+
status: "clean",
|
|
80
|
+
unmerged: [],
|
|
81
|
+
checkFailures: [],
|
|
82
|
+
mergeStateBlockers: [],
|
|
83
|
+
};
|
|
84
|
+
}
|
|
60
85
|
const unmerged = listUnmergedGitPaths(basePath);
|
|
61
86
|
if (unmerged === null) {
|
|
62
87
|
return {
|
|
@@ -1045,7 +1045,7 @@ function buildHeadlessDiscussPrompt(nextId, seedContext, _basePath) {
|
|
|
1045
1045
|
* Run preparation phase if enabled, then build the discuss prompt.
|
|
1046
1046
|
* Preparation analyzes the codebase and prior context, injecting the results
|
|
1047
1047
|
* as supplementary context into the standard discuss template. The discuss
|
|
1048
|
-
* template drives the conversation
|
|
1048
|
+
* template drives the conversation with a variable vision opener, while
|
|
1049
1049
|
* the preparation briefs give the agent grounding in the existing codebase.
|
|
1050
1050
|
*
|
|
1051
1051
|
* @param ctx - Extension command context with UI for progress notifications
|
|
@@ -12,18 +12,18 @@ import { getErrorMessage } from "./error-utils.js";
|
|
|
12
12
|
import { gsdHome } from "./gsd-home.js";
|
|
13
13
|
export const PROVIDER_REGISTRY = [
|
|
14
14
|
// LLM Providers
|
|
15
|
-
{ id: "anthropic", label: "Anthropic (Claude)", category: "llm", envVar: "ANTHROPIC_API_KEY", prefixes: ["sk-ant-"],
|
|
15
|
+
{ id: "anthropic", label: "Anthropic (Claude)", category: "llm", envVar: "ANTHROPIC_API_KEY", prefixes: ["sk-ant-"], authMode: "apiKey", dashboardUrl: "console.anthropic.com" },
|
|
16
16
|
// Claude Code CLI: routes through the local `claude` binary — no API key,
|
|
17
17
|
// authentication is handled by the CLI's own OAuth flow.
|
|
18
18
|
// Referenced by doctor-providers.ts, auto-model-selection.ts, and others;
|
|
19
19
|
// must be in the canonical registry so all consumers see the same catalog.
|
|
20
20
|
// See: https://github.com/open-gsd/gsd-pi/issues/4541
|
|
21
|
-
{ id: "claude-code", label: "Claude Code CLI", category: "llm",
|
|
21
|
+
{ id: "claude-code", label: "Claude Code CLI", category: "llm", authMode: "externalCli" },
|
|
22
22
|
{ id: "openai", label: "OpenAI", category: "llm", envVar: "OPENAI_API_KEY", prefixes: ["sk-"], dashboardUrl: "platform.openai.com/api-keys" },
|
|
23
|
-
{ id: "github-copilot", label: "GitHub Copilot", category: "llm", envVar: "GITHUB_TOKEN",
|
|
24
|
-
{ id: "openai-codex", label: "ChatGPT Plus/Pro (Codex)", category: "llm",
|
|
25
|
-
{ id: "google-gemini-cli", label: "Google Gemini CLI", category: "llm",
|
|
26
|
-
{ id: "google-antigravity", label: "Antigravity", category: "llm",
|
|
23
|
+
{ id: "github-copilot", label: "GitHub Copilot", category: "llm", envVar: "GITHUB_TOKEN", authMode: "browserOAuth" },
|
|
24
|
+
{ id: "openai-codex", label: "ChatGPT Plus/Pro (Codex)", category: "llm", authMode: "browserOAuth" },
|
|
25
|
+
{ id: "google-gemini-cli", label: "Google Gemini CLI", category: "llm", authMode: "externalCli" },
|
|
26
|
+
{ id: "google-antigravity", label: "Antigravity", category: "llm", authMode: "externalCli" },
|
|
27
27
|
{ id: "google", label: "Google (Gemini)", category: "llm", envVar: "GEMINI_API_KEY", dashboardUrl: "aistudio.google.com/apikey" },
|
|
28
28
|
{ id: "groq", label: "Groq", category: "llm", envVar: "GROQ_API_KEY", dashboardUrl: "console.groq.com" },
|
|
29
29
|
{ id: "xai", label: "xAI (Grok)", category: "llm", envVar: "XAI_API_KEY", dashboardUrl: "console.x.ai" },
|
|
@@ -49,6 +49,20 @@ export const PROVIDER_REGISTRY = [
|
|
|
49
49
|
{ id: "telegram_bot", label: "Telegram Bot", category: "remote", envVar: "TELEGRAM_BOT_TOKEN" },
|
|
50
50
|
];
|
|
51
51
|
// ─── Utilities ──────────────────────────────────────────────────────────────────
|
|
52
|
+
export function getProviderAuthMode(provider) {
|
|
53
|
+
return provider.authMode ?? "apiKey";
|
|
54
|
+
}
|
|
55
|
+
export function supportsBrowserOAuth(provider) {
|
|
56
|
+
return getProviderAuthMode(provider) === "browserOAuth";
|
|
57
|
+
}
|
|
58
|
+
export function supportsStoredApiKey(provider) {
|
|
59
|
+
const mode = getProviderAuthMode(provider);
|
|
60
|
+
if (mode === "externalCli" || mode === "cloudIdentity" || mode === "none")
|
|
61
|
+
return false;
|
|
62
|
+
if (mode === "browserOAuth")
|
|
63
|
+
return Boolean(provider.envVar || provider.prefixes?.length);
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
52
66
|
/**
|
|
53
67
|
* Mask an API key for display: show first 4 + last 4 chars.
|
|
54
68
|
* Keys shorter than 12 chars show only first 2 + last 2.
|
|
@@ -79,11 +93,14 @@ export function formatDuration(ms) {
|
|
|
79
93
|
/**
|
|
80
94
|
* Describe a credential's type and status.
|
|
81
95
|
*/
|
|
82
|
-
export function describeCredential(cred) {
|
|
96
|
+
export function describeCredential(cred, provider) {
|
|
83
97
|
if (cred.type === "api_key") {
|
|
84
98
|
const apiCred = cred;
|
|
85
99
|
if (!apiCred.key)
|
|
86
100
|
return "empty key";
|
|
101
|
+
if (apiCred.key === "cli" && provider && getProviderAuthMode(provider) === "externalCli") {
|
|
102
|
+
return "external CLI";
|
|
103
|
+
}
|
|
87
104
|
return `API key (${maskKey(apiCred.key)})`;
|
|
88
105
|
}
|
|
89
106
|
if (cred.type === "oauth") {
|
|
@@ -129,7 +146,7 @@ export function getAllKeyStatuses(auth) {
|
|
|
129
146
|
const firstCred = creds[0];
|
|
130
147
|
const desc = creds.length > 1
|
|
131
148
|
? `${creds.length} keys (round-robin)`
|
|
132
|
-
: describeCredential(firstCred);
|
|
149
|
+
: describeCredential(firstCred, provider);
|
|
133
150
|
return {
|
|
134
151
|
provider,
|
|
135
152
|
configured: true,
|
|
@@ -236,9 +253,20 @@ export async function handleAddKey(providerArg, ctx, auth) {
|
|
|
236
253
|
return false;
|
|
237
254
|
provider = PROVIDER_REGISTRY[idx];
|
|
238
255
|
}
|
|
239
|
-
|
|
240
|
-
if (
|
|
241
|
-
|
|
256
|
+
const authMode = getProviderAuthMode(provider);
|
|
257
|
+
if (authMode === "externalCli") {
|
|
258
|
+
ctx.ui.notify(`${provider.label} is authenticated by its own local CLI. ` +
|
|
259
|
+
`Sign in with that CLI first, then run /login to activate it in GSD.`, "info");
|
|
260
|
+
return false;
|
|
261
|
+
}
|
|
262
|
+
if (authMode === "cloudIdentity") {
|
|
263
|
+
ctx.ui.notify(`${provider.label} uses cloud identity credentials. Configure the provider's cloud CLI or environment, then restart GSD.`, "info");
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
if (supportsBrowserOAuth(provider)) {
|
|
267
|
+
const methods = supportsStoredApiKey(provider)
|
|
268
|
+
? ["API token/key", "Browser login (OAuth)"]
|
|
269
|
+
: ["Browser login (OAuth)"];
|
|
242
270
|
const method = await ctx.ui.select(`${provider.label} — how do you want to authenticate?`, methods);
|
|
243
271
|
if (!method || typeof method !== "string")
|
|
244
272
|
return false;
|
|
@@ -248,6 +276,10 @@ export async function handleAddKey(providerArg, ctx, auth) {
|
|
|
248
276
|
return false;
|
|
249
277
|
}
|
|
250
278
|
}
|
|
279
|
+
if (!supportsStoredApiKey(provider)) {
|
|
280
|
+
ctx.ui.notify(`${provider.label} does not accept a GSD-stored API key. Use /login.`, "info");
|
|
281
|
+
return false;
|
|
282
|
+
}
|
|
251
283
|
// API key input
|
|
252
284
|
const input = await ctx.ui.input(`API key for ${provider.label}:`, provider.envVar ? `or set ${provider.envVar} env var` : "paste your key here");
|
|
253
285
|
if (input === null || input === undefined)
|
|
@@ -309,7 +341,7 @@ export async function handleRemoveKey(providerArg, ctx, auth) {
|
|
|
309
341
|
}
|
|
310
342
|
// Multi-key handling
|
|
311
343
|
if (creds.length > 1) {
|
|
312
|
-
const options = creds.map((c, i) => `[${i + 1}] ${describeCredential(c)}`);
|
|
344
|
+
const options = creds.map((c, i) => `[${i + 1}] ${describeCredential(c, provider)}`);
|
|
313
345
|
options.push("Remove all");
|
|
314
346
|
const choice = await ctx.ui.select(`${provider.label} has ${creds.length} keys. Remove which?`, options);
|
|
315
347
|
if (!choice || typeof choice !== "string")
|
|
@@ -330,7 +362,7 @@ export async function handleRemoveKey(providerArg, ctx, auth) {
|
|
|
330
362
|
}
|
|
331
363
|
}
|
|
332
364
|
else {
|
|
333
|
-
const confirmed = await ctx.ui.confirm("Remove key?", `Remove ${describeCredential(creds[0])} for ${provider.label}?`);
|
|
365
|
+
const confirmed = await ctx.ui.confirm("Remove key?", `Remove ${describeCredential(creds[0], provider)} for ${provider.label}?`);
|
|
334
366
|
if (!confirmed)
|
|
335
367
|
return false;
|
|
336
368
|
auth.remove(provider.id);
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import { truncateToWidth, visibleWidth, matchesKey, Key } from "@gsd/pi-tui";
|
|
4
4
|
import { readNotifications, markAllRead, clearNotifications, onNotificationStoreChange, } from "./notification-store.js";
|
|
5
5
|
import { formattedShortcutPair } from "./shortcut-defs.js";
|
|
6
|
-
import { padRightVisible,
|
|
6
|
+
import { padRightVisible, renderDialogFrame, renderKeyHints, rightAlign, wrapVisibleText, } from "./tui/render-kit.js";
|
|
7
7
|
const FILTER_CYCLE = ["all", "error", "warning", "success", "info"];
|
|
8
8
|
const OVERLAY_WIDTH = "58%";
|
|
9
9
|
const OVERLAY_MIN_WIDTH = 68;
|
|
@@ -163,12 +163,16 @@ export class GSDNotificationOverlay {
|
|
|
163
163
|
const terminalRows = process.stdout.rows || 32;
|
|
164
164
|
const availableRows = Math.max(1, terminalRows - OVERLAY_MARGIN.top - OVERLAY_MARGIN.bottom);
|
|
165
165
|
const overlayRows = Math.min(availableRows, Math.max(1, Math.floor((terminalRows * OVERLAY_MAX_HEIGHT_PERCENT) / 100)));
|
|
166
|
-
const maxVisibleRows = Math.max(5, overlayRows -
|
|
166
|
+
const maxVisibleRows = Math.max(5, overlayRows - 4);
|
|
167
167
|
const visibleContentRows = Math.min(content.length, maxVisibleRows);
|
|
168
168
|
const maxScroll = Math.max(0, content.length - visibleContentRows);
|
|
169
169
|
this.scrollOffset = Math.min(this.scrollOffset, maxScroll);
|
|
170
170
|
const visibleContent = content.slice(this.scrollOffset, this.scrollOffset + visibleContentRows);
|
|
171
|
-
const
|
|
171
|
+
const footer = renderKeyHints(this.theme, ["↑/↓ scroll", "f filter", "c clear", `Esc/${formattedShortcutPair("notifications")} close`], Math.max(1, width - 4));
|
|
172
|
+
const lines = renderDialogFrame(this.theme, "Notifications", visibleContent, width, {
|
|
173
|
+
footer,
|
|
174
|
+
scroll: { offset: this.scrollOffset, visibleRows: visibleContentRows, totalRows: content.length },
|
|
175
|
+
});
|
|
172
176
|
this.cachedWidth = width;
|
|
173
177
|
this.cachedLines = lines;
|
|
174
178
|
return lines;
|
|
@@ -211,8 +215,6 @@ export class GSDNotificationOverlay {
|
|
|
211
215
|
};
|
|
212
216
|
const blank = () => row("");
|
|
213
217
|
const hr = () => row(th.fg("dim", "─".repeat(contentWidth)));
|
|
214
|
-
// Header
|
|
215
|
-
const title = th.fg("accent", th.bold("Notifications"));
|
|
216
218
|
const filterLabel = this.filter === "all"
|
|
217
219
|
? th.fg("dim", "all")
|
|
218
220
|
: th.fg(this.filter === "error" ? "error"
|
|
@@ -220,11 +222,8 @@ export class GSDNotificationOverlay {
|
|
|
220
222
|
: this.filter === "success" ? "success"
|
|
221
223
|
: "dim", this.filter);
|
|
222
224
|
const count = `${this.filteredEntries.length} entries`;
|
|
223
|
-
lines.push(row(rightAlign(`${
|
|
225
|
+
lines.push(row(rightAlign(`${th.fg("dim", "filter:")} ${filterLabel}`, th.fg("dim", count), contentWidth)));
|
|
224
226
|
lines.push(hr());
|
|
225
|
-
// Controls
|
|
226
|
-
const closeShortcut = formattedShortcutPair("notifications");
|
|
227
|
-
lines.push(row(renderKeyHints(th, ["↑/↓ scroll", "f filter", "c clear", `Esc/${closeShortcut} close`], contentWidth)));
|
|
228
227
|
lines.push(blank());
|
|
229
228
|
// Entries
|
|
230
229
|
const filtered = this.filteredEntries;
|
|
@@ -7,7 +7,7 @@ import { matchesKey, Key } from "@gsd/pi-tui";
|
|
|
7
7
|
import { formatDuration } from "../shared/mod.js";
|
|
8
8
|
import { formattedShortcutPair } from "./shortcut-defs.js";
|
|
9
9
|
import { resolveGsdPathContract } from "./paths.js";
|
|
10
|
-
import { renderBar, renderKeyHints, renderProgressBar, safeLine, statusGlyph, } from "./tui/render-kit.js";
|
|
10
|
+
import { renderBar, renderDialogFrame, renderKeyHints, renderProgressBar, safeLine, statusGlyph, } from "./tui/render-kit.js";
|
|
11
11
|
// ─── Data Helpers ─────────────────────────────────────────────────────────
|
|
12
12
|
function readJsonSafe(filePath) {
|
|
13
13
|
try {
|
|
@@ -306,15 +306,14 @@ export class ParallelMonitorOverlay {
|
|
|
306
306
|
const t = this.theme;
|
|
307
307
|
const lines = [];
|
|
308
308
|
const w = Math.max(1, width);
|
|
309
|
-
|
|
309
|
+
const contentWidth = Math.max(1, w - 4);
|
|
310
310
|
const totalCost = this.workers.reduce((s, wk) => s + wk.cost, 0);
|
|
311
311
|
const aliveCount = this.workers.filter((wk) => wk.alive).length;
|
|
312
312
|
const now = new Date().toLocaleTimeString();
|
|
313
|
-
lines.push(t.bold(t.fg("accent", " GSD Parallel Monitor ")));
|
|
314
313
|
lines.push(t.fg("muted", ` ${now} │ ${aliveCount}/${this.workers.length} alive │ Total: `) +
|
|
315
314
|
t.bold(`$${totalCost.toFixed(2)}`) +
|
|
316
315
|
t.fg("muted", " │ 5s refresh"));
|
|
317
|
-
lines.push(renderBar(t,
|
|
316
|
+
lines.push(renderBar(t, contentWidth));
|
|
318
317
|
if (this.workers.length === 0) {
|
|
319
318
|
lines.push("");
|
|
320
319
|
lines.push(t.fg("warning", " No parallel workers found."));
|
|
@@ -360,7 +359,7 @@ export class ParallelMonitorOverlay {
|
|
|
360
359
|
});
|
|
361
360
|
lines.push(` ${t.fg("muted", "slices")} ${chips.join(" ")}`);
|
|
362
361
|
// Task progress bar
|
|
363
|
-
const barWidth = Math.max(6, Math.min(25,
|
|
362
|
+
const barWidth = Math.max(6, Math.min(25, contentWidth - 32));
|
|
364
363
|
const bar = renderProgressBar(t, wk.doneTasks, wk.totalTasks, barWidth, {
|
|
365
364
|
filledChar: "█",
|
|
366
365
|
emptyChar: "░",
|
|
@@ -378,7 +377,7 @@ export class ParallelMonitorOverlay {
|
|
|
378
377
|
}
|
|
379
378
|
// Event feed
|
|
380
379
|
lines.push("");
|
|
381
|
-
lines.push(renderBar(t,
|
|
380
|
+
lines.push(renderBar(t, contentWidth));
|
|
382
381
|
lines.push(` ${t.bold("Recent Events")}`);
|
|
383
382
|
if (this.events.length === 0) {
|
|
384
383
|
lines.push(t.fg("muted", " No events yet..."));
|
|
@@ -389,7 +388,6 @@ export class ParallelMonitorOverlay {
|
|
|
389
388
|
lines.push(` ${t.fg("muted", "│")} ${t.fg("accent", mid)} ${evt.replace(/^✓ M\d+\//, "")}`);
|
|
390
389
|
}
|
|
391
390
|
}
|
|
392
|
-
// Footer
|
|
393
391
|
lines.push("");
|
|
394
392
|
const allDone = this.workers.length > 0 && this.workers.every((wk) => !wk.alive);
|
|
395
393
|
if (allDone) {
|
|
@@ -400,16 +398,20 @@ export class ParallelMonitorOverlay {
|
|
|
400
398
|
}
|
|
401
399
|
lines.push(` ${t.bold("Total: $" + this.workers.reduce((s, wk) => s + wk.cost, 0).toFixed(2))}`);
|
|
402
400
|
}
|
|
403
|
-
lines.push(renderKeyHints(t, [`ESC/q/${formattedShortcutPair("parallel")} close`, "↑↓ scroll"], w));
|
|
404
401
|
// Apply scroll — use terminal rows as height estimate
|
|
405
402
|
const termHeight = process.stdout.rows || 40;
|
|
406
|
-
const
|
|
403
|
+
const maxBodyRows = Math.max(1, Math.min(lines.length, termHeight - 12));
|
|
404
|
+
const maxScroll = Math.max(0, lines.length - maxBodyRows);
|
|
407
405
|
this.scrollOffset = Math.min(Math.max(this.scrollOffset, 0), maxScroll);
|
|
408
406
|
const visible = lines
|
|
409
|
-
.slice(this.scrollOffset, this.scrollOffset +
|
|
410
|
-
.map((line) => safeLine(line,
|
|
411
|
-
|
|
407
|
+
.slice(this.scrollOffset, this.scrollOffset + maxBodyRows)
|
|
408
|
+
.map((line) => safeLine(line, contentWidth));
|
|
409
|
+
const footer = renderKeyHints(t, [`ESC/q/${formattedShortcutPair("parallel")} close`, "↑↓ scroll"], contentWidth);
|
|
410
|
+
this.cachedLines = renderDialogFrame(t, "GSD Parallel Monitor", visible, w, {
|
|
411
|
+
footer,
|
|
412
|
+
scroll: { offset: this.scrollOffset, visibleRows: maxBodyRows, totalRows: lines.length },
|
|
413
|
+
});
|
|
412
414
|
this.cachedWidth = width;
|
|
413
|
-
return
|
|
415
|
+
return this.cachedLines;
|
|
414
416
|
}
|
|
415
417
|
}
|
|
@@ -21,6 +21,7 @@ import { join, dirname } from "node:path";
|
|
|
21
21
|
import { fileURLToPath } from "node:url";
|
|
22
22
|
import { logWarning } from "./workflow-logger.js";
|
|
23
23
|
import { gsdHome } from "./gsd-home.js";
|
|
24
|
+
import { chooseVisionAskVariant } from "./vision-ask.js";
|
|
24
25
|
function hasRequiredExtensionAssets(rootDir, exists = existsSync) {
|
|
25
26
|
return (exists(join(rootDir, "prompts")) &&
|
|
26
27
|
exists(join(rootDir, "templates", "task-summary.md")));
|
|
@@ -168,6 +169,7 @@ export function loadPrompt(name, vars = {}) {
|
|
|
168
169
|
planTemplatePath: join(getTemplatesDir(), "plan.md"),
|
|
169
170
|
taskPlanTemplatePath: join(getTemplatesDir(), "task-plan.md"),
|
|
170
171
|
taskSummaryTemplatePath: join(getTemplatesDir(), "task-summary.md"),
|
|
172
|
+
visionAsk: chooseVisionAskVariant(),
|
|
171
173
|
skillActivation: "If a `GSD Skill Preferences` block is present in system context, use it and the `<available_skills>` catalog in your system prompt to decide which skills to load and follow for this unit, without relaxing required verification or artifact rules.",
|
|
172
174
|
...vars,
|
|
173
175
|
};
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
{{preamble}}
|
|
2
2
|
|
|
3
|
-
Ask
|
|
3
|
+
Ask exactly this once: "{{visionAsk}}" Then use the user's reply as vision input.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
The opener is intentionally variable so GSD feels alive across project starts. Keep it natural, easy to answer, and assistant/developer shaped: plain language, light guidance, no corporate wording, no roleplay.
|
|
6
|
+
|
|
7
|
+
**Special handling:** If the **user's** message is status, branch state, clarification, or any other non-project-description, treat it as vision input and proceed instead of repeating the opener. Do **not** treat the system preamble above (e.g. "New milestone M00X.") as vision input — wait for the user.
|
|
6
8
|
|
|
7
9
|
## Reflection Step
|
|
8
10
|
|
|
@@ -4,6 +4,8 @@ Discuss milestone {{milestoneId}} ("{{milestoneTitle}}"). Identify real gray are
|
|
|
4
4
|
|
|
5
5
|
**Structured questions available: {{structuredQuestionsAvailable}}**
|
|
6
6
|
|
|
7
|
+
**Conversation opener:** For the first user-facing question round, if you need a broad starting question, use: "{{visionAsk}}" Pair it with investigation-shaped follow-ups. Keep the tone natural and easy to answer, like an assistant/developer helping the user shape the next piece of work.
|
|
8
|
+
|
|
7
9
|
{{inlinedTemplates}}
|
|
8
10
|
|
|
9
11
|
---
|
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
* Conflicting depends_on entries are auto-removed on confirm.
|
|
9
9
|
*/
|
|
10
10
|
import { Key, matchesKey, truncateToWidth } from "@gsd/pi-tui";
|
|
11
|
-
import { makeUI } from "../shared/tui.js";
|
|
12
11
|
import { GLYPH } from "../shared/mod.js";
|
|
13
12
|
import { validateQueueOrder } from "./queue-order.js";
|
|
13
|
+
import { renderDialogFrame, renderKeyHints } from "./tui/render-kit.js";
|
|
14
14
|
/**
|
|
15
15
|
* Show the queue reorder overlay.
|
|
16
16
|
* Returns the new order + deps to remove, or null if cancelled.
|
|
@@ -26,6 +26,7 @@ export async function showQueueReorder(ctx, completed, pending) {
|
|
|
26
26
|
let grabbed = false;
|
|
27
27
|
let scrollOffset = 0;
|
|
28
28
|
let cachedLines;
|
|
29
|
+
let cachedWidth;
|
|
29
30
|
let validation;
|
|
30
31
|
// Mutable deps map — tracks removals during this session
|
|
31
32
|
const liveDeps = new Map();
|
|
@@ -42,6 +43,7 @@ export async function showQueueReorder(ctx, completed, pending) {
|
|
|
42
43
|
revalidate();
|
|
43
44
|
function refresh() {
|
|
44
45
|
cachedLines = undefined;
|
|
46
|
+
cachedWidth = undefined;
|
|
45
47
|
tui.requestRender();
|
|
46
48
|
}
|
|
47
49
|
function swapItems(fromIdx, toIdx) {
|
|
@@ -125,17 +127,14 @@ export async function showQueueReorder(ctx, completed, pending) {
|
|
|
125
127
|
}
|
|
126
128
|
}
|
|
127
129
|
function render(width) {
|
|
128
|
-
if (cachedLines)
|
|
130
|
+
if (cachedLines && cachedWidth === width)
|
|
129
131
|
return cachedLines;
|
|
130
|
-
const
|
|
132
|
+
const contentWidth = Math.max(1, width - 4);
|
|
131
133
|
const lines = [];
|
|
132
134
|
const queueRows = [];
|
|
133
|
-
const
|
|
134
|
-
lines.push(...r); };
|
|
135
|
-
const add = (s) => truncateToWidth(s, width);
|
|
135
|
+
const add = (s) => truncateToWidth(s, contentWidth);
|
|
136
136
|
let cursorQueueRow = 0;
|
|
137
|
-
const headerText = grabbed ? "
|
|
138
|
-
push(ui.bar(), ui.blank(), ui.header(headerText), ui.blank());
|
|
137
|
+
const headerText = grabbed ? "Queue Reorder - Moving Item" : "Queue Reorder";
|
|
139
138
|
// Completed milestones (dimmed)
|
|
140
139
|
if (completed.length > 0) {
|
|
141
140
|
lines.push(add(theme.fg("dim", " Completed:")));
|
|
@@ -143,7 +142,7 @@ export async function showQueueReorder(ctx, completed, pending) {
|
|
|
143
142
|
const label = m.title && m.title !== m.id ? `${m.id} ${m.title}` : m.id;
|
|
144
143
|
lines.push(add(` ${theme.fg("dim", `${GLYPH.statusDone} ${label}`)}`));
|
|
145
144
|
}
|
|
146
|
-
push(
|
|
145
|
+
lines.push("");
|
|
147
146
|
}
|
|
148
147
|
// Pending milestones
|
|
149
148
|
const queueLabel = grabbed ? " Queue (space to release, ↑/↓ to move):" : " Queue (space to grab, ↑/↓ to navigate):";
|
|
@@ -190,7 +189,7 @@ export async function showQueueReorder(ctx, completed, pending) {
|
|
|
190
189
|
// Removed deps feedback
|
|
191
190
|
const trailingLines = [];
|
|
192
191
|
if (removedDeps.length > 0) {
|
|
193
|
-
trailingLines.push(
|
|
192
|
+
trailingLines.push("");
|
|
194
193
|
for (const r of removedDeps) {
|
|
195
194
|
trailingLines.push(add(` ${theme.fg("success", `${GLYPH.statusDone} Removed: ${r.milestone} depends_on ${r.dep}`)}`));
|
|
196
195
|
}
|
|
@@ -198,10 +197,9 @@ export async function showQueueReorder(ctx, completed, pending) {
|
|
|
198
197
|
// Circular warning
|
|
199
198
|
const circ = validation.violations.find(v => v.type === 'circular');
|
|
200
199
|
if (circ) {
|
|
201
|
-
trailingLines.push(
|
|
200
|
+
trailingLines.push("");
|
|
202
201
|
trailingLines.push(add(` ${theme.fg("error", `${GLYPH.statusWarning} ${circ.message}`)}`));
|
|
203
202
|
}
|
|
204
|
-
trailingLines.push(...ui.blank());
|
|
205
203
|
// Hints — context-sensitive based on grab state
|
|
206
204
|
const hints = [];
|
|
207
205
|
if (grabbed) {
|
|
@@ -221,9 +219,9 @@ export async function showQueueReorder(ctx, completed, pending) {
|
|
|
221
219
|
hints.push("enter ok");
|
|
222
220
|
}
|
|
223
221
|
hints.push("esc");
|
|
224
|
-
trailingLines.push(...ui.hints(hints), ...ui.bar());
|
|
225
222
|
const maxOverlayRows = Math.max(10, process.stdout.rows ? Math.floor(process.stdout.rows * 0.8) : 24);
|
|
226
|
-
const
|
|
223
|
+
const frameRows = 4;
|
|
224
|
+
const availableQueueRows = Math.max(1, maxOverlayRows - frameRows - lines.length - trailingLines.length);
|
|
227
225
|
const maxScroll = Math.max(0, queueRows.length - availableQueueRows);
|
|
228
226
|
if (cursorQueueRow < scrollOffset) {
|
|
229
227
|
scrollOffset = cursorQueueRow;
|
|
@@ -232,11 +230,23 @@ export async function showQueueReorder(ctx, completed, pending) {
|
|
|
232
230
|
scrollOffset = cursorQueueRow - availableQueueRows + 1;
|
|
233
231
|
}
|
|
234
232
|
scrollOffset = Math.min(Math.max(scrollOffset, 0), maxScroll);
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
233
|
+
const queueStartRow = lines.length;
|
|
234
|
+
const visibleQueueRows = queueRows.slice(scrollOffset, scrollOffset + availableQueueRows);
|
|
235
|
+
lines.push(...visibleQueueRows, ...trailingLines);
|
|
236
|
+
cachedLines = renderDialogFrame(theme, headerText, lines, width, {
|
|
237
|
+
footer: renderKeyHints(theme, hints, contentWidth),
|
|
238
|
+
scroll: {
|
|
239
|
+
offset: scrollOffset,
|
|
240
|
+
visibleRows: availableQueueRows,
|
|
241
|
+
totalRows: queueRows.length,
|
|
242
|
+
trackOffset: queueStartRow,
|
|
243
|
+
trackRows: visibleQueueRows.length,
|
|
244
|
+
},
|
|
245
|
+
});
|
|
246
|
+
cachedWidth = width;
|
|
247
|
+
return cachedLines;
|
|
238
248
|
}
|
|
239
|
-
return { render, invalidate: () => { cachedLines = undefined; }, handleInput };
|
|
249
|
+
return { render, invalidate: () => { cachedLines = undefined; cachedWidth = undefined; }, handleInput };
|
|
240
250
|
}, {
|
|
241
251
|
overlay: true,
|
|
242
252
|
overlayOptions: { width: "70%", minWidth: 50, maxHeight: "80%", anchor: "center" },
|
|
@@ -116,6 +116,15 @@ export async function handleCompleteTask(params, basePath) {
|
|
|
116
116
|
if (!params.milestoneId || typeof params.milestoneId !== "string" || params.milestoneId.trim() === "") {
|
|
117
117
|
return { error: "milestoneId is required and must be a non-empty string" };
|
|
118
118
|
}
|
|
119
|
+
if (!params.oneLiner || typeof params.oneLiner !== "string" || params.oneLiner.trim() === "") {
|
|
120
|
+
return { error: "oneLiner is required and must be a non-empty string" };
|
|
121
|
+
}
|
|
122
|
+
if (!params.narrative || typeof params.narrative !== "string" || params.narrative.trim() === "") {
|
|
123
|
+
return { error: "narrative is required and must be a non-empty string" };
|
|
124
|
+
}
|
|
125
|
+
if (!params.verification || typeof params.verification !== "string" || params.verification.trim() === "") {
|
|
126
|
+
return { error: "verification is required and must be a non-empty string" };
|
|
127
|
+
}
|
|
119
128
|
const artifactBasePath = resolveCanonicalMilestoneRoot(basePath, params.milestoneId);
|
|
120
129
|
// ── Ownership check (opt-in: only enforced when claim file exists) ──────
|
|
121
130
|
const ownershipErr = checkOwnership(artifactBasePath, taskUnitKey(params.milestoneId, params.sliceId, params.taskId), params.actorName);
|