gsd-pi 2.37.1 → 2.38.0-dev.63ad7e5
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 +1 -1
- package/dist/app-paths.js +1 -1
- package/dist/cli.js +9 -0
- package/dist/extension-discovery.d.ts +5 -3
- package/dist/extension-discovery.js +14 -9
- package/dist/extension-registry.js +2 -2
- package/dist/onboarding.js +1 -0
- package/dist/remote-questions-config.js +2 -2
- package/dist/resources/extensions/browser-tools/package.json +3 -1
- package/dist/resources/extensions/cmux/index.js +55 -1
- package/dist/resources/extensions/context7/package.json +1 -1
- package/dist/resources/extensions/env-utils.js +29 -0
- package/dist/resources/extensions/get-secrets-from-user.js +5 -24
- package/dist/resources/extensions/google-search/package.json +3 -1
- package/dist/resources/extensions/gsd/auto/session.js +6 -23
- package/dist/resources/extensions/gsd/auto-dispatch.js +74 -9
- package/dist/resources/extensions/gsd/auto-loop.js +68 -97
- package/dist/resources/extensions/gsd/auto-post-unit.js +87 -69
- package/dist/resources/extensions/gsd/auto-prompts.js +98 -33
- package/dist/resources/extensions/gsd/auto-recovery.js +37 -1
- package/dist/resources/extensions/gsd/auto-start.js +13 -2
- package/dist/resources/extensions/gsd/auto-worktree-sync.js +13 -5
- package/dist/resources/extensions/gsd/auto.js +143 -96
- package/dist/resources/extensions/gsd/captures.js +9 -1
- package/dist/resources/extensions/gsd/commands-extensions.js +3 -2
- package/dist/resources/extensions/gsd/commands-handlers.js +16 -3
- package/dist/resources/extensions/gsd/commands-prefs-wizard.js +1 -1
- package/dist/resources/extensions/gsd/commands.js +22 -2
- package/dist/resources/extensions/gsd/context-budget.js +2 -10
- package/dist/resources/extensions/gsd/detection.js +1 -2
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +0 -2
- package/dist/resources/extensions/gsd/doctor-checks.js +82 -0
- package/dist/resources/extensions/gsd/doctor-environment.js +78 -0
- package/dist/resources/extensions/gsd/doctor-format.js +15 -0
- package/dist/resources/extensions/gsd/doctor-providers.js +62 -12
- package/dist/resources/extensions/gsd/doctor.js +184 -11
- package/dist/resources/extensions/gsd/export.js +1 -1
- package/dist/resources/extensions/gsd/files.js +43 -2
- package/dist/resources/extensions/gsd/forensics.js +1 -1
- package/dist/resources/extensions/gsd/index.js +2 -1
- package/dist/resources/extensions/gsd/migrate/parsers.js +1 -1
- package/dist/resources/extensions/gsd/observability-validator.js +24 -0
- package/dist/resources/extensions/gsd/package.json +1 -1
- package/dist/resources/extensions/gsd/preferences-models.js +0 -12
- package/dist/resources/extensions/gsd/preferences-types.js +2 -2
- package/dist/resources/extensions/gsd/preferences-validation.js +43 -11
- package/dist/resources/extensions/gsd/preferences.js +5 -5
- package/dist/resources/extensions/gsd/prompts/discuss.md +11 -14
- package/dist/resources/extensions/gsd/prompts/execute-task.md +2 -2
- package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +11 -12
- package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -10
- package/dist/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +2 -1
- package/dist/resources/extensions/gsd/prompts/queue.md +4 -8
- package/dist/resources/extensions/gsd/prompts/reactive-execute.md +44 -0
- package/dist/resources/extensions/gsd/prompts/run-uat.md +25 -10
- package/dist/resources/extensions/gsd/prompts/workflow-start.md +2 -2
- package/dist/resources/extensions/gsd/reactive-graph.js +227 -0
- package/dist/resources/extensions/gsd/repo-identity.js +21 -4
- package/dist/resources/extensions/gsd/resource-version.js +2 -1
- package/dist/resources/extensions/gsd/state.js +1 -1
- package/dist/resources/extensions/gsd/templates/task-plan.md +11 -3
- package/dist/resources/extensions/gsd/visualizer-data.js +1 -1
- package/dist/resources/extensions/gsd/worktree.js +35 -16
- package/dist/resources/extensions/remote-questions/status.js +2 -1
- package/dist/resources/extensions/remote-questions/store.js +2 -1
- package/dist/resources/extensions/search-the-web/provider.js +2 -1
- package/dist/resources/extensions/subagent/index.js +12 -3
- package/dist/resources/extensions/subagent/isolation.js +2 -1
- package/dist/resources/extensions/ttsr/rule-loader.js +2 -1
- package/dist/resources/extensions/universal-config/package.json +1 -1
- package/dist/welcome-screen.d.ts +12 -0
- package/dist/welcome-screen.js +53 -0
- package/package.json +2 -1
- package/packages/pi-ai/dist/env-api-keys.js +13 -0
- package/packages/pi-ai/dist/env-api-keys.js.map +1 -1
- package/packages/pi-ai/dist/models.generated.d.ts +172 -0
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +172 -0
- package/packages/pi-ai/dist/models.generated.js.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic-shared.d.ts +64 -0
- package/packages/pi-ai/dist/providers/anthropic-shared.d.ts.map +1 -0
- package/packages/pi-ai/dist/providers/anthropic-shared.js +668 -0
- package/packages/pi-ai/dist/providers/anthropic-shared.js.map +1 -0
- package/packages/pi-ai/dist/providers/anthropic-vertex.d.ts +5 -0
- package/packages/pi-ai/dist/providers/anthropic-vertex.d.ts.map +1 -0
- package/packages/pi-ai/dist/providers/anthropic-vertex.js +85 -0
- package/packages/pi-ai/dist/providers/anthropic-vertex.js.map +1 -0
- package/packages/pi-ai/dist/providers/anthropic.d.ts +4 -30
- package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic.js +47 -764
- package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
- package/packages/pi-ai/dist/providers/register-builtins.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/register-builtins.js +6 -0
- package/packages/pi-ai/dist/providers/register-builtins.js.map +1 -1
- package/packages/pi-ai/dist/types.d.ts +2 -2
- 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 -0
- package/packages/pi-ai/src/env-api-keys.ts +14 -0
- package/packages/pi-ai/src/models.generated.ts +172 -0
- package/packages/pi-ai/src/providers/anthropic-shared.ts +761 -0
- package/packages/pi-ai/src/providers/anthropic-vertex.ts +130 -0
- package/packages/pi-ai/src/providers/anthropic.ts +76 -868
- package/packages/pi-ai/src/providers/register-builtins.ts +7 -0
- package/packages/pi-ai/src/types.ts +2 -0
- package/packages/pi-coding-agent/dist/core/model-resolver.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-resolver.js +1 -0
- package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/package-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/package-manager.js +8 -4
- package/packages/pi-coding-agent/dist/core/package-manager.js.map +1 -1
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-coding-agent/src/core/model-resolver.ts +1 -0
- package/packages/pi-coding-agent/src/core/package-manager.ts +8 -4
- package/pkg/package.json +1 -1
- package/src/resources/extensions/cmux/index.ts +57 -1
- package/src/resources/extensions/env-utils.ts +31 -0
- package/src/resources/extensions/get-secrets-from-user.ts +5 -24
- package/src/resources/extensions/gsd/auto/session.ts +7 -25
- package/src/resources/extensions/gsd/auto-dispatch.ts +99 -8
- package/src/resources/extensions/gsd/auto-loop.ts +88 -133
- package/src/resources/extensions/gsd/auto-post-unit.ts +64 -40
- package/src/resources/extensions/gsd/auto-prompts.ts +132 -36
- package/src/resources/extensions/gsd/auto-recovery.ts +42 -0
- package/src/resources/extensions/gsd/auto-start.ts +18 -2
- package/src/resources/extensions/gsd/auto-worktree-sync.ts +15 -4
- package/src/resources/extensions/gsd/auto.ts +139 -101
- package/src/resources/extensions/gsd/captures.ts +10 -1
- package/src/resources/extensions/gsd/commands-extensions.ts +4 -2
- package/src/resources/extensions/gsd/commands-handlers.ts +17 -2
- package/src/resources/extensions/gsd/commands-prefs-wizard.ts +1 -1
- package/src/resources/extensions/gsd/commands.ts +24 -2
- package/src/resources/extensions/gsd/context-budget.ts +2 -12
- package/src/resources/extensions/gsd/detection.ts +2 -2
- package/src/resources/extensions/gsd/docs/preferences-reference.md +0 -2
- package/src/resources/extensions/gsd/doctor-checks.ts +75 -0
- package/src/resources/extensions/gsd/doctor-environment.ts +82 -1
- package/src/resources/extensions/gsd/doctor-format.ts +20 -0
- package/src/resources/extensions/gsd/doctor-providers.ts +64 -10
- package/src/resources/extensions/gsd/doctor-types.ts +16 -1
- package/src/resources/extensions/gsd/doctor.ts +177 -13
- package/src/resources/extensions/gsd/export.ts +1 -1
- package/src/resources/extensions/gsd/files.ts +47 -2
- package/src/resources/extensions/gsd/forensics.ts +1 -1
- package/src/resources/extensions/gsd/index.ts +3 -1
- package/src/resources/extensions/gsd/migrate/parsers.ts +1 -1
- package/src/resources/extensions/gsd/observability-validator.ts +27 -0
- package/src/resources/extensions/gsd/preferences-models.ts +0 -12
- package/src/resources/extensions/gsd/preferences-types.ts +5 -5
- package/src/resources/extensions/gsd/preferences-validation.ts +42 -11
- package/src/resources/extensions/gsd/preferences.ts +5 -5
- package/src/resources/extensions/gsd/prompts/discuss.md +11 -14
- package/src/resources/extensions/gsd/prompts/execute-task.md +2 -2
- package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +11 -12
- package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -10
- package/src/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
- package/src/resources/extensions/gsd/prompts/plan-slice.md +2 -1
- package/src/resources/extensions/gsd/prompts/queue.md +4 -8
- package/src/resources/extensions/gsd/prompts/reactive-execute.md +44 -0
- package/src/resources/extensions/gsd/prompts/run-uat.md +25 -10
- package/src/resources/extensions/gsd/prompts/workflow-start.md +2 -2
- package/src/resources/extensions/gsd/reactive-graph.ts +289 -0
- package/src/resources/extensions/gsd/repo-identity.ts +23 -4
- package/src/resources/extensions/gsd/resource-version.ts +3 -1
- package/src/resources/extensions/gsd/state.ts +1 -1
- package/src/resources/extensions/gsd/templates/task-plan.md +11 -3
- package/src/resources/extensions/gsd/tests/agent-end-retry.test.ts +21 -18
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +11 -31
- package/src/resources/extensions/gsd/tests/cmux.test.ts +93 -0
- package/src/resources/extensions/gsd/tests/doctor-enhancements.test.ts +266 -0
- package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +191 -3
- package/src/resources/extensions/gsd/tests/plan-quality-validator.test.ts +111 -0
- package/src/resources/extensions/gsd/tests/preferences.test.ts +2 -7
- package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +59 -0
- package/src/resources/extensions/gsd/tests/reactive-executor.test.ts +511 -0
- package/src/resources/extensions/gsd/tests/reactive-graph.test.ts +299 -0
- package/src/resources/extensions/gsd/tests/repo-identity-worktree.test.ts +21 -1
- package/src/resources/extensions/gsd/tests/run-uat.test.ts +11 -3
- package/src/resources/extensions/gsd/tests/worktree.test.ts +47 -0
- package/src/resources/extensions/gsd/types.ts +43 -1
- package/src/resources/extensions/gsd/visualizer-data.ts +1 -1
- package/src/resources/extensions/gsd/worktree.ts +35 -15
- package/src/resources/extensions/remote-questions/status.ts +3 -1
- package/src/resources/extensions/remote-questions/store.ts +3 -1
- package/src/resources/extensions/search-the-web/provider.ts +2 -1
- package/src/resources/extensions/subagent/index.ts +12 -3
- package/src/resources/extensions/subagent/isolation.ts +3 -1
- package/src/resources/extensions/ttsr/rule-loader.ts +3 -1
- package/dist/resources/extensions/gsd/prompt-compressor.js +0 -393
- package/dist/resources/extensions/gsd/semantic-chunker.js +0 -254
- package/dist/resources/extensions/gsd/summary-distiller.js +0 -212
- package/src/resources/extensions/gsd/prompt-compressor.ts +0 -508
- package/src/resources/extensions/gsd/semantic-chunker.ts +0 -336
- package/src/resources/extensions/gsd/summary-distiller.ts +0 -258
- package/src/resources/extensions/gsd/tests/context-compression.test.ts +0 -193
- package/src/resources/extensions/gsd/tests/prompt-compressor.test.ts +0 -529
- package/src/resources/extensions/gsd/tests/semantic-chunker.test.ts +0 -426
- package/src/resources/extensions/gsd/tests/summary-distiller.test.ts +0 -323
- package/src/resources/extensions/gsd/tests/token-optimization-benchmark.test.ts +0 -1272
- package/src/resources/extensions/gsd/tests/token-optimization-prefs.test.ts +0 -164
package/README.md
CHANGED
|
@@ -629,7 +629,7 @@ GSD isn't locked to one provider. It runs on the [Pi SDK](https://github.com/bad
|
|
|
629
629
|
|
|
630
630
|
### Built-in Providers
|
|
631
631
|
|
|
632
|
-
Anthropic, OpenAI, Google (Gemini), OpenRouter, GitHub Copilot, Amazon Bedrock, Azure OpenAI, Google Vertex, Groq, Cerebras, Mistral, xAI, HuggingFace, Vercel AI Gateway, and more.
|
|
632
|
+
Anthropic, Anthropic (Vertex AI), OpenAI, Google (Gemini), OpenRouter, GitHub Copilot, Amazon Bedrock, Azure OpenAI, Google Vertex, Groq, Cerebras, Mistral, xAI, HuggingFace, Vercel AI Gateway, and more.
|
|
633
633
|
|
|
634
634
|
### OAuth / Max Plans
|
|
635
635
|
|
package/dist/app-paths.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { homedir } from 'os';
|
|
2
2
|
import { join } from 'path';
|
|
3
|
-
export const appRoot = join(homedir(), '.gsd');
|
|
3
|
+
export const appRoot = process.env.GSD_HOME || join(homedir(), '.gsd');
|
|
4
4
|
export const agentDir = join(appRoot, 'agent');
|
|
5
5
|
export const sessionsDir = join(appRoot, 'sessions');
|
|
6
6
|
export const authFilePath = join(agentDir, 'auth.json');
|
package/dist/cli.js
CHANGED
|
@@ -505,6 +505,15 @@ if (enabledModelPatterns && enabledModelPatterns.length > 0) {
|
|
|
505
505
|
session.setScopedModels(scopedModels);
|
|
506
506
|
}
|
|
507
507
|
}
|
|
508
|
+
// Welcome screen — shown on every fresh interactive session before TUI takes over
|
|
509
|
+
{
|
|
510
|
+
const { printWelcomeScreen } = await import('./welcome-screen.js');
|
|
511
|
+
printWelcomeScreen({
|
|
512
|
+
version: process.env.GSD_VERSION || '0.0.0',
|
|
513
|
+
modelName: settingsManager.getDefaultModel() || undefined,
|
|
514
|
+
provider: settingsManager.getDefaultProvider() || undefined,
|
|
515
|
+
});
|
|
516
|
+
}
|
|
508
517
|
const interactiveMode = new InteractiveMode(session);
|
|
509
518
|
markStartup('InteractiveMode');
|
|
510
519
|
printStartupTimings();
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Resolves the entry-point file(s) for a single extension directory.
|
|
3
3
|
*
|
|
4
|
-
* 1. If the directory contains a package.json with a `pi
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* 1. If the directory contains a package.json with a `pi` manifest object,
|
|
5
|
+
* the manifest is authoritative:
|
|
6
|
+
* - `pi.extensions` array → resolve each entry relative to the directory.
|
|
7
|
+
* - `pi: {}` (no extensions) → return empty (library opt-out, e.g. cmux).
|
|
8
|
+
* 2. Only when no `pi` manifest exists does it fall back to `index.ts` → `index.js`.
|
|
7
9
|
*/
|
|
8
10
|
export declare function resolveExtensionEntries(dir: string): string[];
|
|
9
11
|
/**
|
|
@@ -6,24 +6,29 @@ function isExtensionFile(name) {
|
|
|
6
6
|
/**
|
|
7
7
|
* Resolves the entry-point file(s) for a single extension directory.
|
|
8
8
|
*
|
|
9
|
-
* 1. If the directory contains a package.json with a `pi
|
|
10
|
-
*
|
|
11
|
-
*
|
|
9
|
+
* 1. If the directory contains a package.json with a `pi` manifest object,
|
|
10
|
+
* the manifest is authoritative:
|
|
11
|
+
* - `pi.extensions` array → resolve each entry relative to the directory.
|
|
12
|
+
* - `pi: {}` (no extensions) → return empty (library opt-out, e.g. cmux).
|
|
13
|
+
* 2. Only when no `pi` manifest exists does it fall back to `index.ts` → `index.js`.
|
|
12
14
|
*/
|
|
13
15
|
export function resolveExtensionEntries(dir) {
|
|
14
16
|
const packageJsonPath = join(dir, 'package.json');
|
|
15
17
|
if (existsSync(packageJsonPath)) {
|
|
16
18
|
try {
|
|
17
19
|
const pkg = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
20
|
+
if (pkg?.pi && typeof pkg.pi === 'object') {
|
|
21
|
+
// When a pi manifest exists, it is authoritative — don't fall through
|
|
22
|
+
// to index.ts/index.js auto-detection. This allows library directories
|
|
23
|
+
// (like cmux) to opt out by declaring "pi": {} with no extensions.
|
|
24
|
+
const declared = pkg.pi.extensions;
|
|
25
|
+
if (!Array.isArray(declared) || declared.length === 0) {
|
|
26
|
+
return [];
|
|
27
|
+
}
|
|
28
|
+
return declared
|
|
21
29
|
.filter((entry) => typeof entry === 'string')
|
|
22
30
|
.map((entry) => resolve(dir, entry))
|
|
23
31
|
.filter((entry) => existsSync(entry));
|
|
24
|
-
if (resolved.length > 0) {
|
|
25
|
-
return resolved;
|
|
26
|
-
}
|
|
27
32
|
}
|
|
28
33
|
}
|
|
29
34
|
catch {
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* The only way an extension stops loading is an explicit `gsd extensions disable <id>`.
|
|
7
7
|
*/
|
|
8
8
|
import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, writeFileSync } from "node:fs";
|
|
9
|
-
import {
|
|
9
|
+
import { appRoot } from "./app-paths.js";
|
|
10
10
|
import { dirname, join } from "node:path";
|
|
11
11
|
// ─── Validation ─────────────────────────────────────────────────────────────
|
|
12
12
|
function isRegistry(data) {
|
|
@@ -26,7 +26,7 @@ function isManifest(data) {
|
|
|
26
26
|
}
|
|
27
27
|
// ─── Registry Path ──────────────────────────────────────────────────────────
|
|
28
28
|
export function getRegistryPath() {
|
|
29
|
-
return join(
|
|
29
|
+
return join(appRoot, "extensions", "registry.json");
|
|
30
30
|
}
|
|
31
31
|
// ─── Registry I/O ───────────────────────────────────────────────────────────
|
|
32
32
|
function defaultRegistry() {
|
package/dist/onboarding.js
CHANGED
|
@@ -9,12 +9,12 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
11
11
|
import { dirname, join } from "node:path";
|
|
12
|
-
import {
|
|
12
|
+
import { appRoot } from "./app-paths.js";
|
|
13
13
|
// Inlined from preferences.ts to avoid crossing the compiled/uncompiled
|
|
14
14
|
// boundary — this file is compiled by tsc, but preferences.ts is loaded
|
|
15
15
|
// via jiti at runtime. Importing it as .js fails because no .js exists
|
|
16
16
|
// in dist/. See #592, #1110.
|
|
17
|
-
const GLOBAL_PREFERENCES_PATH = join(
|
|
17
|
+
const GLOBAL_PREFERENCES_PATH = join(appRoot, "preferences.md");
|
|
18
18
|
export function saveRemoteQuestionsConfig(channel, channelId) {
|
|
19
19
|
const prefsPath = GLOBAL_PREFERENCES_PATH;
|
|
20
20
|
const block = [
|
|
@@ -237,11 +237,14 @@ export class CmuxClient {
|
|
|
237
237
|
return extractSurfaceIds(parsed);
|
|
238
238
|
}
|
|
239
239
|
async createSplit(direction) {
|
|
240
|
+
return this.createSplitFrom(this.config.surfaceId, direction);
|
|
241
|
+
}
|
|
242
|
+
async createSplitFrom(sourceSurfaceId, direction) {
|
|
240
243
|
if (!this.config.splits)
|
|
241
244
|
return null;
|
|
242
245
|
const before = new Set(await this.listSurfaceIds());
|
|
243
246
|
const args = ["new-split", direction];
|
|
244
|
-
const scopedArgs = this.appendSurface(this.appendWorkspace(args),
|
|
247
|
+
const scopedArgs = this.appendSurface(this.appendWorkspace(args), sourceSurfaceId);
|
|
245
248
|
await this.runAsync(scopedArgs);
|
|
246
249
|
const after = await this.listSurfaceIds();
|
|
247
250
|
for (const id of after) {
|
|
@@ -250,6 +253,57 @@ export class CmuxClient {
|
|
|
250
253
|
}
|
|
251
254
|
return null;
|
|
252
255
|
}
|
|
256
|
+
/**
|
|
257
|
+
* Create a grid of surfaces for parallel agent execution.
|
|
258
|
+
*
|
|
259
|
+
* Layout strategy (gsd stays in the original surface):
|
|
260
|
+
* 1 agent: [gsd | A]
|
|
261
|
+
* 2 agents: [gsd | A]
|
|
262
|
+
* [ | B]
|
|
263
|
+
* 3 agents: [gsd | A]
|
|
264
|
+
* [ C | B]
|
|
265
|
+
* 4 agents: [gsd | A]
|
|
266
|
+
* [ C | B] (D splits from B downward)
|
|
267
|
+
* [ | D]
|
|
268
|
+
*
|
|
269
|
+
* Returns surface IDs in order, or empty array on failure.
|
|
270
|
+
*/
|
|
271
|
+
async createGridLayout(count) {
|
|
272
|
+
if (!this.config.splits || count <= 0)
|
|
273
|
+
return [];
|
|
274
|
+
const surfaces = [];
|
|
275
|
+
// First split: create right column from the gsd surface
|
|
276
|
+
const rightCol = await this.createSplitFrom(this.config.surfaceId, "right");
|
|
277
|
+
if (!rightCol)
|
|
278
|
+
return [];
|
|
279
|
+
surfaces.push(rightCol);
|
|
280
|
+
if (count === 1)
|
|
281
|
+
return surfaces;
|
|
282
|
+
// Second split: split right column down → bottom-right
|
|
283
|
+
const bottomRight = await this.createSplitFrom(rightCol, "down");
|
|
284
|
+
if (!bottomRight)
|
|
285
|
+
return surfaces;
|
|
286
|
+
surfaces.push(bottomRight);
|
|
287
|
+
if (count === 2)
|
|
288
|
+
return surfaces;
|
|
289
|
+
// Third split: split gsd surface down → bottom-left
|
|
290
|
+
const bottomLeft = await this.createSplitFrom(this.config.surfaceId, "down");
|
|
291
|
+
if (!bottomLeft)
|
|
292
|
+
return surfaces;
|
|
293
|
+
surfaces.push(bottomLeft);
|
|
294
|
+
if (count === 3)
|
|
295
|
+
return surfaces;
|
|
296
|
+
// Fourth+: split subsequent surfaces down from the last created
|
|
297
|
+
let lastSurface = bottomRight;
|
|
298
|
+
for (let i = 3; i < count; i++) {
|
|
299
|
+
const next = await this.createSplitFrom(lastSurface, "down");
|
|
300
|
+
if (!next)
|
|
301
|
+
break;
|
|
302
|
+
surfaces.push(next);
|
|
303
|
+
lastSurface = next;
|
|
304
|
+
}
|
|
305
|
+
return surfaces;
|
|
306
|
+
}
|
|
253
307
|
async sendSurface(surfaceId, text) {
|
|
254
308
|
const payload = text.endsWith("\n") ? text : `${text}\n`;
|
|
255
309
|
const stdout = await this.runAsync(["send-surface", "--surface", surfaceId, payload]);
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// GSD Extension — Environment variable utilities
|
|
2
|
+
// Copyright (c) 2026 Jeremy McSpadden <jeremy@fluxlabs.net>
|
|
3
|
+
//
|
|
4
|
+
// Pure utility for checking existing env keys in .env files and process.env.
|
|
5
|
+
// Extracted from get-secrets-from-user.ts to avoid pulling in @gsd/pi-tui
|
|
6
|
+
// when only env-checking is needed (e.g. from files.ts during report generation).
|
|
7
|
+
import { readFile } from "node:fs/promises";
|
|
8
|
+
/**
|
|
9
|
+
* Check which keys already exist in a .env file or process.env.
|
|
10
|
+
* Returns the subset of `keys` that are already set.
|
|
11
|
+
*/
|
|
12
|
+
export async function checkExistingEnvKeys(keys, envFilePath) {
|
|
13
|
+
let fileContent = "";
|
|
14
|
+
try {
|
|
15
|
+
fileContent = await readFile(envFilePath, "utf8");
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
// ENOENT or other read error — proceed with empty content
|
|
19
|
+
}
|
|
20
|
+
const existing = [];
|
|
21
|
+
for (const key of keys) {
|
|
22
|
+
const escaped = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
23
|
+
const regex = new RegExp(`^${escaped}\\s*=`, "m");
|
|
24
|
+
if (regex.test(fileContent) || key in process.env) {
|
|
25
|
+
existing.push(key);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return existing;
|
|
29
|
+
}
|
|
@@ -46,30 +46,11 @@ async function writeEnvKey(filePath, key, value) {
|
|
|
46
46
|
await writeFile(filePath, content, "utf8");
|
|
47
47
|
}
|
|
48
48
|
// ─── Exported utilities ───────────────────────────────────────────────────────
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
*/
|
|
55
|
-
export async function checkExistingEnvKeys(keys, envFilePath) {
|
|
56
|
-
let fileContent = "";
|
|
57
|
-
try {
|
|
58
|
-
fileContent = await readFile(envFilePath, "utf8");
|
|
59
|
-
}
|
|
60
|
-
catch {
|
|
61
|
-
// ENOENT or other read error — proceed with empty content
|
|
62
|
-
}
|
|
63
|
-
const existing = [];
|
|
64
|
-
for (const key of keys) {
|
|
65
|
-
const escaped = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
66
|
-
const regex = new RegExp(`^${escaped}\\s*=`, "m");
|
|
67
|
-
if (regex.test(fileContent) || key in process.env) {
|
|
68
|
-
existing.push(key);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
return existing;
|
|
72
|
-
}
|
|
49
|
+
// Re-export from env-utils.ts so existing consumers still work.
|
|
50
|
+
// The implementation lives in env-utils.ts to avoid pulling @gsd/pi-tui
|
|
51
|
+
// into modules that only need env-checking (e.g. files.ts during reports).
|
|
52
|
+
import { checkExistingEnvKeys } from "./env-utils.js";
|
|
53
|
+
export { checkExistingEnvKeys };
|
|
73
54
|
/**
|
|
74
55
|
* Detect the write destination based on project files in basePath.
|
|
75
56
|
* Priority: vercel.json → convex/ dir → fallback "dotenv".
|
|
@@ -60,6 +60,8 @@ export class AutoSession {
|
|
|
60
60
|
lastStateRebuildAt = 0;
|
|
61
61
|
// ── Sidecar queue ─────────────────────────────────────────────────────
|
|
62
62
|
sidecarQueue = [];
|
|
63
|
+
// ── Dispatch circuit breakers ──────────────────────────────────────
|
|
64
|
+
rewriteAttemptCount = 0;
|
|
63
65
|
// ── Metrics ──────────────────────────────────────────────────────────────
|
|
64
66
|
autoStartTime = 0;
|
|
65
67
|
lastPromptCharCount;
|
|
@@ -68,25 +70,8 @@ export class AutoSession {
|
|
|
68
70
|
// ── Signal handler ───────────────────────────────────────────────────────
|
|
69
71
|
sigtermHandler = null;
|
|
70
72
|
// ── Loop promise state ──────────────────────────────────────────────────
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
* emitted from the previous session's abort during this window must be
|
|
74
|
-
* ignored; they do not belong to the new unit.
|
|
75
|
-
*/
|
|
76
|
-
sessionSwitchInFlight = false;
|
|
77
|
-
/**
|
|
78
|
-
* One-shot resolver for the current unit's agent_end promise.
|
|
79
|
-
* Non-null only while a unit is in-flight (between sendMessage and agent_end).
|
|
80
|
-
* Scoped to the session to prevent concurrent session corruption.
|
|
81
|
-
*/
|
|
82
|
-
pendingResolve = null;
|
|
83
|
-
/**
|
|
84
|
-
* Queue for agent_end events that arrive when no pendingResolve exists.
|
|
85
|
-
* This happens when error-recovery sendMessage retries produce agent_end
|
|
86
|
-
* events between loop iterations. The next runUnit drains this queue
|
|
87
|
-
* instead of waiting for a new event.
|
|
88
|
-
*/
|
|
89
|
-
pendingAgentEndQueue = [];
|
|
73
|
+
// Per-unit resolve function and session-switch guard live at module level
|
|
74
|
+
// in auto-loop.ts (_currentResolve, _sessionSwitchInFlight).
|
|
90
75
|
// ── Methods ──────────────────────────────────────────────────────────────
|
|
91
76
|
clearTimers() {
|
|
92
77
|
if (this.unitTimeoutHandle) {
|
|
@@ -160,12 +145,10 @@ export class AutoSession {
|
|
|
160
145
|
this.lastBaselineCharCount = undefined;
|
|
161
146
|
this.pendingQuickTasks = [];
|
|
162
147
|
this.sidecarQueue = [];
|
|
148
|
+
this.rewriteAttemptCount = 0;
|
|
163
149
|
// Signal handler
|
|
164
150
|
this.sigtermHandler = null;
|
|
165
|
-
// Loop promise state
|
|
166
|
-
this.sessionSwitchInFlight = false;
|
|
167
|
-
this.pendingResolve = null;
|
|
168
|
-
this.pendingAgentEndQueue = [];
|
|
151
|
+
// Loop promise state lives in auto-loop.ts module scope
|
|
169
152
|
}
|
|
170
153
|
toJSON() {
|
|
171
154
|
return {
|
|
@@ -12,7 +12,7 @@ import { loadFile, loadActiveOverrides, parseRoadmap } from "./files.js";
|
|
|
12
12
|
import { resolveMilestoneFile, resolveMilestonePath, resolveSliceFile, resolveTaskFile, relSliceFile, buildMilestoneFileName, } from "./paths.js";
|
|
13
13
|
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
14
14
|
import { join } from "node:path";
|
|
15
|
-
import { buildResearchMilestonePrompt, buildPlanMilestonePrompt, buildResearchSlicePrompt, buildPlanSlicePrompt, buildExecuteTaskPrompt, buildCompleteSlicePrompt, buildCompleteMilestonePrompt, buildValidateMilestonePrompt, buildReplanSlicePrompt, buildRunUatPrompt, buildReassessRoadmapPrompt, buildRewriteDocsPrompt, checkNeedsReassessment, checkNeedsRunUat, } from "./auto-prompts.js";
|
|
15
|
+
import { buildResearchMilestonePrompt, buildPlanMilestonePrompt, buildResearchSlicePrompt, buildPlanSlicePrompt, buildExecuteTaskPrompt, buildCompleteSlicePrompt, buildCompleteMilestonePrompt, buildValidateMilestonePrompt, buildReplanSlicePrompt, buildRunUatPrompt, buildReassessRoadmapPrompt, buildRewriteDocsPrompt, buildReactiveExecutePrompt, checkNeedsReassessment, checkNeedsRunUat, } from "./auto-prompts.js";
|
|
16
16
|
function missingSliceStop(mid, phase) {
|
|
17
17
|
return {
|
|
18
18
|
action: "stop",
|
|
@@ -22,25 +22,24 @@ function missingSliceStop(mid, phase) {
|
|
|
22
22
|
}
|
|
23
23
|
// ─── Rewrite Circuit Breaker ──────────────────────────────────────────────
|
|
24
24
|
const MAX_REWRITE_ATTEMPTS = 3;
|
|
25
|
-
let rewriteAttemptCount = 0;
|
|
26
|
-
export function resetRewriteCircuitBreaker() {
|
|
27
|
-
rewriteAttemptCount = 0;
|
|
28
|
-
}
|
|
29
25
|
// ─── Rules ────────────────────────────────────────────────────────────────
|
|
30
26
|
const DISPATCH_RULES = [
|
|
31
27
|
{
|
|
32
28
|
name: "rewrite-docs (override gate)",
|
|
33
|
-
match: async ({ mid, midTitle, state, basePath }) => {
|
|
29
|
+
match: async ({ mid, midTitle, state, basePath, session }) => {
|
|
34
30
|
const pendingOverrides = await loadActiveOverrides(basePath);
|
|
35
31
|
if (pendingOverrides.length === 0)
|
|
36
32
|
return null;
|
|
37
|
-
|
|
33
|
+
const count = session?.rewriteAttemptCount ?? 0;
|
|
34
|
+
if (count >= MAX_REWRITE_ATTEMPTS) {
|
|
38
35
|
const { resolveAllOverrides } = await import("./files.js");
|
|
39
36
|
await resolveAllOverrides(basePath);
|
|
40
|
-
|
|
37
|
+
if (session)
|
|
38
|
+
session.rewriteAttemptCount = 0;
|
|
41
39
|
return null;
|
|
42
40
|
}
|
|
43
|
-
|
|
41
|
+
if (session)
|
|
42
|
+
session.rewriteAttemptCount++;
|
|
44
43
|
const unitId = state.activeSlice ? `${mid}/${state.activeSlice.id}` : mid;
|
|
45
44
|
return {
|
|
46
45
|
action: "dispatch",
|
|
@@ -223,6 +222,72 @@ const DISPATCH_RULES = [
|
|
|
223
222
|
};
|
|
224
223
|
},
|
|
225
224
|
},
|
|
225
|
+
{
|
|
226
|
+
name: "executing → reactive-execute (parallel dispatch)",
|
|
227
|
+
match: async ({ state, mid, midTitle, basePath, prefs }) => {
|
|
228
|
+
if (state.phase !== "executing" || !state.activeTask)
|
|
229
|
+
return null;
|
|
230
|
+
if (!state.activeSlice)
|
|
231
|
+
return null; // fall through
|
|
232
|
+
// Only activate when reactive_execution is explicitly enabled
|
|
233
|
+
const reactiveConfig = prefs?.reactive_execution;
|
|
234
|
+
if (!reactiveConfig?.enabled)
|
|
235
|
+
return null;
|
|
236
|
+
const sid = state.activeSlice.id;
|
|
237
|
+
const sTitle = state.activeSlice.title;
|
|
238
|
+
const maxParallel = reactiveConfig.max_parallel ?? 2;
|
|
239
|
+
// Dry-run mode: max_parallel=1 means graph is derived and logged but
|
|
240
|
+
// execution remains sequential
|
|
241
|
+
if (maxParallel <= 1)
|
|
242
|
+
return null;
|
|
243
|
+
try {
|
|
244
|
+
const { loadSliceTaskIO, deriveTaskGraph, isGraphAmbiguous, getReadyTasks, chooseNonConflictingSubset, graphMetrics, } = await import("./reactive-graph.js");
|
|
245
|
+
const taskIO = await loadSliceTaskIO(basePath, mid, sid);
|
|
246
|
+
if (taskIO.length < 2)
|
|
247
|
+
return null; // single task, no point
|
|
248
|
+
const graph = deriveTaskGraph(taskIO);
|
|
249
|
+
// Ambiguous graph → fall through to sequential
|
|
250
|
+
if (isGraphAmbiguous(graph))
|
|
251
|
+
return null;
|
|
252
|
+
const completed = new Set(graph.filter((n) => n.done).map((n) => n.id));
|
|
253
|
+
const readyIds = getReadyTasks(graph, completed, new Set());
|
|
254
|
+
// Only activate reactive dispatch when >1 task is ready
|
|
255
|
+
if (readyIds.length <= 1)
|
|
256
|
+
return null;
|
|
257
|
+
const selected = chooseNonConflictingSubset(readyIds, graph, maxParallel, new Set());
|
|
258
|
+
if (selected.length <= 1)
|
|
259
|
+
return null;
|
|
260
|
+
// Log graph metrics for observability
|
|
261
|
+
const metrics = graphMetrics(graph);
|
|
262
|
+
process.stderr.write(`gsd-reactive: ${mid}/${sid} graph — tasks:${metrics.taskCount} edges:${metrics.edgeCount} ` +
|
|
263
|
+
`ready:${metrics.readySetSize} dispatching:${selected.length} ambiguous:${metrics.ambiguous}\n`);
|
|
264
|
+
// Persist dispatched batch so verification and recovery can check
|
|
265
|
+
// exactly which tasks were sent.
|
|
266
|
+
const { saveReactiveState } = await import("./reactive-graph.js");
|
|
267
|
+
saveReactiveState(basePath, mid, sid, {
|
|
268
|
+
sliceId: sid,
|
|
269
|
+
completed: [...completed],
|
|
270
|
+
dispatched: selected,
|
|
271
|
+
graphSnapshot: metrics,
|
|
272
|
+
updatedAt: new Date().toISOString(),
|
|
273
|
+
});
|
|
274
|
+
// Encode selected task IDs in unitId for artifact verification.
|
|
275
|
+
// Format: M001/S01/reactive+T02,T03
|
|
276
|
+
const batchSuffix = selected.join(",");
|
|
277
|
+
return {
|
|
278
|
+
action: "dispatch",
|
|
279
|
+
unitType: "reactive-execute",
|
|
280
|
+
unitId: `${mid}/${sid}/reactive+${batchSuffix}`,
|
|
281
|
+
prompt: await buildReactiveExecutePrompt(mid, midTitle, sid, sTitle, selected, basePath),
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
catch (err) {
|
|
285
|
+
// Non-fatal — fall through to sequential execution
|
|
286
|
+
process.stderr.write(`gsd-reactive: graph derivation failed: ${err.message}\n`);
|
|
287
|
+
return null;
|
|
288
|
+
}
|
|
289
|
+
},
|
|
290
|
+
},
|
|
226
291
|
{
|
|
227
292
|
name: "executing → execute-task (recover missing task plan → plan-slice)",
|
|
228
293
|
match: async ({ state, mid, midTitle, basePath }) => {
|