gsd-pi 2.35.0 → 2.36.0-dev.d612764
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 +3 -1
- package/dist/cli.js +7 -2
- package/dist/resource-loader.d.ts +1 -1
- package/dist/resource-loader.js +13 -1
- package/dist/resources/extensions/async-jobs/await-tool.js +0 -2
- package/dist/resources/extensions/async-jobs/job-manager.js +0 -6
- package/dist/resources/extensions/bg-shell/output-formatter.js +1 -19
- package/dist/resources/extensions/bg-shell/process-manager.js +0 -4
- package/dist/resources/extensions/bg-shell/types.js +0 -2
- package/dist/resources/extensions/cmux/index.js +321 -0
- package/dist/resources/extensions/context7/index.js +5 -0
- package/dist/resources/extensions/get-secrets-from-user.js +2 -30
- package/dist/resources/extensions/google-search/index.js +5 -0
- package/dist/resources/extensions/gsd/auto-dashboard.js +334 -104
- package/dist/resources/extensions/gsd/auto-dispatch.js +43 -1
- package/dist/resources/extensions/gsd/auto-loop.js +28 -3
- package/dist/resources/extensions/gsd/auto-model-selection.js +15 -3
- package/dist/resources/extensions/gsd/auto-recovery.js +35 -0
- package/dist/resources/extensions/gsd/auto-start.js +35 -2
- package/dist/resources/extensions/gsd/auto.js +75 -4
- package/dist/resources/extensions/gsd/commands-cmux.js +120 -0
- package/dist/resources/extensions/gsd/commands-handlers.js +2 -2
- package/dist/resources/extensions/gsd/commands-inspect.js +10 -3
- package/dist/resources/extensions/gsd/commands-prefs-wizard.js +1 -1
- package/dist/resources/extensions/gsd/commands-rate.js +31 -0
- package/dist/resources/extensions/gsd/commands.js +94 -2
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +25 -0
- package/dist/resources/extensions/gsd/doctor-environment.js +26 -17
- package/dist/resources/extensions/gsd/files.js +11 -2
- package/dist/resources/extensions/gsd/gitignore.js +54 -7
- package/dist/resources/extensions/gsd/guided-flow.js +8 -2
- package/dist/resources/extensions/gsd/health-widget-core.js +96 -0
- package/dist/resources/extensions/gsd/health-widget.js +97 -46
- package/dist/resources/extensions/gsd/index.js +31 -33
- package/dist/resources/extensions/gsd/migrate-external.js +55 -2
- package/dist/resources/extensions/gsd/milestone-ids.js +3 -2
- package/dist/resources/extensions/gsd/notifications.js +10 -1
- package/dist/resources/extensions/gsd/paths.js +74 -7
- package/dist/resources/extensions/gsd/post-unit-hooks.js +4 -1
- package/dist/resources/extensions/gsd/preferences-types.js +2 -0
- package/dist/resources/extensions/gsd/preferences-validation.js +45 -1
- package/dist/resources/extensions/gsd/preferences.js +15 -0
- package/dist/resources/extensions/gsd/prompts/complete-milestone.md +2 -0
- package/dist/resources/extensions/gsd/prompts/research-milestone.md +4 -3
- package/dist/resources/extensions/gsd/prompts/research-slice.md +3 -2
- package/dist/resources/extensions/gsd/prompts/validate-milestone.md +2 -0
- package/dist/resources/extensions/gsd/roadmap-mutations.js +55 -0
- package/dist/resources/extensions/gsd/session-lock.js +53 -2
- package/dist/resources/extensions/gsd/state.js +2 -1
- package/dist/resources/extensions/gsd/templates/plan.md +8 -0
- package/dist/resources/extensions/gsd/templates/preferences.md +6 -0
- package/dist/resources/extensions/gsd/worktree-resolver.js +12 -0
- package/dist/resources/extensions/remote-questions/remote-command.js +2 -22
- package/dist/resources/extensions/search-the-web/native-search.js +45 -4
- package/dist/resources/extensions/shared/mod.js +1 -1
- package/dist/resources/extensions/shared/sanitize.js +30 -0
- package/dist/resources/extensions/shared/terminal.js +5 -0
- package/dist/resources/extensions/subagent/index.js +186 -74
- package/dist/resources/skills/core-web-vitals/SKILL.md +1 -1
- package/dist/resources/skills/create-gsd-extension/workflows/debug-extension.md +1 -1
- package/dist/resources/skills/github-workflows/SKILL.md +0 -2
- package/dist/resources/skills/web-quality-audit/SKILL.md +0 -2
- package/package.json +2 -1
- package/packages/pi-agent-core/dist/agent.d.ts +10 -2
- package/packages/pi-agent-core/dist/agent.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/agent.js +19 -8
- package/packages/pi-agent-core/dist/agent.js.map +1 -1
- package/packages/pi-agent-core/src/agent.ts +31 -10
- package/packages/pi-ai/dist/providers/openai-responses.js +1 -1
- package/packages/pi-ai/dist/providers/openai-responses.js.map +1 -1
- package/packages/pi-ai/src/providers/openai-responses.ts +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js +20 -4
- package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/resource-loader.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/resource-loader.js +13 -2
- package/packages/pi-coding-agent/dist/core/resource-loader.js.map +1 -1
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-coding-agent/src/core/agent-session.ts +36 -12
- package/packages/pi-coding-agent/src/core/resource-loader.ts +13 -2
- package/packages/pi-tui/dist/terminal-image.d.ts.map +1 -1
- package/packages/pi-tui/dist/terminal-image.js +4 -0
- package/packages/pi-tui/dist/terminal-image.js.map +1 -1
- package/packages/pi-tui/src/terminal-image.ts +5 -0
- package/pkg/package.json +1 -1
- package/src/resources/extensions/async-jobs/await-tool.ts +0 -2
- package/src/resources/extensions/async-jobs/job-manager.ts +0 -7
- package/src/resources/extensions/bg-shell/output-formatter.ts +0 -17
- package/src/resources/extensions/bg-shell/process-manager.ts +0 -4
- package/src/resources/extensions/bg-shell/types.ts +0 -12
- package/src/resources/extensions/cmux/index.ts +384 -0
- package/src/resources/extensions/context7/index.ts +7 -0
- package/src/resources/extensions/get-secrets-from-user.ts +2 -35
- package/src/resources/extensions/google-search/index.ts +7 -0
- package/src/resources/extensions/gsd/auto-dashboard.ts +363 -116
- package/src/resources/extensions/gsd/auto-dispatch.ts +49 -1
- package/src/resources/extensions/gsd/auto-loop.ts +64 -2
- package/src/resources/extensions/gsd/auto-model-selection.ts +23 -2
- package/src/resources/extensions/gsd/auto-recovery.ts +39 -0
- package/src/resources/extensions/gsd/auto-start.ts +42 -2
- package/src/resources/extensions/gsd/auto.ts +82 -3
- package/src/resources/extensions/gsd/commands-cmux.ts +143 -0
- package/src/resources/extensions/gsd/commands-handlers.ts +2 -2
- package/src/resources/extensions/gsd/commands-inspect.ts +10 -3
- package/src/resources/extensions/gsd/commands-prefs-wizard.ts +1 -1
- package/src/resources/extensions/gsd/commands-rate.ts +55 -0
- package/src/resources/extensions/gsd/commands.ts +97 -2
- package/src/resources/extensions/gsd/docs/preferences-reference.md +25 -0
- package/src/resources/extensions/gsd/doctor-environment.ts +26 -16
- package/src/resources/extensions/gsd/files.ts +12 -2
- package/src/resources/extensions/gsd/gitignore.ts +54 -7
- package/src/resources/extensions/gsd/guided-flow.ts +8 -2
- package/src/resources/extensions/gsd/health-widget-core.ts +129 -0
- package/src/resources/extensions/gsd/health-widget.ts +103 -59
- package/src/resources/extensions/gsd/index.ts +37 -32
- package/src/resources/extensions/gsd/migrate-external.ts +47 -2
- package/src/resources/extensions/gsd/milestone-ids.ts +3 -2
- package/src/resources/extensions/gsd/notifications.ts +10 -1
- package/src/resources/extensions/gsd/paths.ts +73 -7
- package/src/resources/extensions/gsd/post-unit-hooks.ts +5 -1
- package/src/resources/extensions/gsd/preferences-types.ts +13 -0
- package/src/resources/extensions/gsd/preferences-validation.ts +42 -1
- package/src/resources/extensions/gsd/preferences.ts +18 -1
- package/src/resources/extensions/gsd/prompts/complete-milestone.md +2 -0
- package/src/resources/extensions/gsd/prompts/research-milestone.md +4 -3
- package/src/resources/extensions/gsd/prompts/research-slice.md +3 -2
- package/src/resources/extensions/gsd/prompts/validate-milestone.md +2 -0
- package/src/resources/extensions/gsd/roadmap-mutations.ts +66 -0
- package/src/resources/extensions/gsd/session-lock.ts +59 -2
- package/src/resources/extensions/gsd/state.ts +2 -1
- package/src/resources/extensions/gsd/templates/plan.md +8 -0
- package/src/resources/extensions/gsd/templates/preferences.md +6 -0
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +2 -0
- package/src/resources/extensions/gsd/tests/cmux.test.ts +98 -0
- package/src/resources/extensions/gsd/tests/commands-inspect-open-db.test.ts +46 -0
- package/src/resources/extensions/gsd/tests/files-loadfile-eisdir.test.ts +20 -0
- package/src/resources/extensions/gsd/tests/gitignore-tracked-gsd.test.ts +214 -0
- package/src/resources/extensions/gsd/tests/health-widget.test.ts +158 -0
- package/src/resources/extensions/gsd/tests/paths.test.ts +113 -0
- package/src/resources/extensions/gsd/tests/preferences.test.ts +35 -2
- package/src/resources/extensions/gsd/tests/queue-reorder-e2e.test.ts +26 -0
- package/src/resources/extensions/gsd/tests/test-utils.ts +165 -0
- package/src/resources/extensions/gsd/tests/validate-directory.test.ts +15 -0
- package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +7 -0
- package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +32 -0
- package/src/resources/extensions/gsd/worktree-resolver.ts +11 -0
- package/src/resources/extensions/remote-questions/remote-command.ts +2 -23
- package/src/resources/extensions/search-the-web/native-search.ts +50 -4
- package/src/resources/extensions/shared/mod.ts +1 -1
- package/src/resources/extensions/shared/sanitize.ts +36 -0
- package/src/resources/extensions/shared/terminal.ts +5 -0
- package/src/resources/extensions/subagent/index.ts +242 -91
- package/src/resources/skills/core-web-vitals/SKILL.md +1 -1
- package/src/resources/skills/create-gsd-extension/workflows/debug-extension.md +1 -1
- package/src/resources/skills/github-workflows/SKILL.md +0 -2
- package/src/resources/skills/web-quality-audit/SKILL.md +0 -2
- package/dist/resources/extensions/shared/wizard-ui.js +0 -478
- package/dist/resources/skills/swiftui/SKILL.md +0 -208
- package/dist/resources/skills/swiftui/references/animations.md +0 -921
- package/dist/resources/skills/swiftui/references/architecture.md +0 -1561
- package/dist/resources/skills/swiftui/references/layout-system.md +0 -1186
- package/dist/resources/skills/swiftui/references/navigation.md +0 -1492
- package/dist/resources/skills/swiftui/references/networking-async.md +0 -214
- package/dist/resources/skills/swiftui/references/performance.md +0 -1706
- package/dist/resources/skills/swiftui/references/platform-integration.md +0 -204
- package/dist/resources/skills/swiftui/references/state-management.md +0 -1443
- package/dist/resources/skills/swiftui/references/swiftdata.md +0 -297
- package/dist/resources/skills/swiftui/references/testing-debugging.md +0 -247
- package/dist/resources/skills/swiftui/references/uikit-appkit-interop.md +0 -218
- package/dist/resources/skills/swiftui/workflows/add-feature.md +0 -191
- package/dist/resources/skills/swiftui/workflows/build-new-app.md +0 -311
- package/dist/resources/skills/swiftui/workflows/debug-swiftui.md +0 -192
- package/dist/resources/skills/swiftui/workflows/optimize-performance.md +0 -197
- package/dist/resources/skills/swiftui/workflows/ship-app.md +0 -203
- package/dist/resources/skills/swiftui/workflows/write-tests.md +0 -235
- package/src/resources/extensions/shared/wizard-ui.ts +0 -551
- package/src/resources/skills/swiftui/SKILL.md +0 -208
- package/src/resources/skills/swiftui/references/animations.md +0 -921
- package/src/resources/skills/swiftui/references/architecture.md +0 -1561
- package/src/resources/skills/swiftui/references/layout-system.md +0 -1186
- package/src/resources/skills/swiftui/references/navigation.md +0 -1492
- package/src/resources/skills/swiftui/references/networking-async.md +0 -214
- package/src/resources/skills/swiftui/references/performance.md +0 -1706
- package/src/resources/skills/swiftui/references/platform-integration.md +0 -204
- package/src/resources/skills/swiftui/references/state-management.md +0 -1443
- package/src/resources/skills/swiftui/references/swiftdata.md +0 -297
- package/src/resources/skills/swiftui/references/testing-debugging.md +0 -247
- package/src/resources/skills/swiftui/references/uikit-appkit-interop.md +0 -218
- package/src/resources/skills/swiftui/workflows/add-feature.md +0 -191
- package/src/resources/skills/swiftui/workflows/build-new-app.md +0 -311
- package/src/resources/skills/swiftui/workflows/debug-swiftui.md +0 -192
- package/src/resources/skills/swiftui/workflows/optimize-performance.md +0 -197
- package/src/resources/skills/swiftui/workflows/ship-app.md +0 -203
- package/src/resources/skills/swiftui/workflows/write-tests.md +0 -235
package/README.md
CHANGED
|
@@ -455,7 +455,9 @@ auto_report: true
|
|
|
455
455
|
|
|
456
456
|
### Agent Instructions
|
|
457
457
|
|
|
458
|
-
|
|
458
|
+
Place an `AGENTS.md` file in any directory to provide persistent behavioral guidance for that scope. Pi core loads `AGENTS.md` automatically (with `CLAUDE.md` as a fallback) at both user and project levels. Use these files for coding standards, architectural decisions, domain terminology, or workflow preferences.
|
|
459
|
+
|
|
460
|
+
> **Note:** The legacy `agent-instructions.md` format (`~/.gsd/agent-instructions.md` and `.gsd/agent-instructions.md`) is deprecated and no longer loaded. Migrate any existing instructions to `AGENTS.md` or `CLAUDE.md`.
|
|
459
461
|
|
|
460
462
|
### Debug Mode
|
|
461
463
|
|
package/dist/cli.js
CHANGED
|
@@ -327,7 +327,10 @@ if (isPrintMode) {
|
|
|
327
327
|
markStartup('createAgentSession');
|
|
328
328
|
if (extensionsResult.errors.length > 0) {
|
|
329
329
|
for (const err of extensionsResult.errors) {
|
|
330
|
-
|
|
330
|
+
// Downgrade conflicts with built-in tools to warnings (#1347)
|
|
331
|
+
const isSuperseded = err.error.includes("supersedes");
|
|
332
|
+
const prefix = isSuperseded ? "Extension conflict" : "Extension load error";
|
|
333
|
+
process.stderr.write(`[gsd] ${prefix}: ${err.error}\n`);
|
|
331
334
|
}
|
|
332
335
|
}
|
|
333
336
|
// Apply --model override if specified
|
|
@@ -456,7 +459,9 @@ const { session, extensionsResult } = await createAgentSession({
|
|
|
456
459
|
markStartup('createAgentSession');
|
|
457
460
|
if (extensionsResult.errors.length > 0) {
|
|
458
461
|
for (const err of extensionsResult.errors) {
|
|
459
|
-
|
|
462
|
+
const isSuperseded = err.error.includes("supersedes");
|
|
463
|
+
const prefix = isSuperseded ? "Extension conflict" : "Extension load error";
|
|
464
|
+
process.stderr.write(`[gsd] ${prefix}: ${err.error}\n`);
|
|
460
465
|
}
|
|
461
466
|
}
|
|
462
467
|
// Restore scoped models from settings on startup.
|
|
@@ -9,7 +9,7 @@ export declare function getNewerManagedResourceVersion(agentDir: string, current
|
|
|
9
9
|
* - extensions/ → ~/.gsd/agent/extensions/ (overwrite when version changes)
|
|
10
10
|
* - agents/ → ~/.gsd/agent/agents/ (overwrite when version changes)
|
|
11
11
|
* - skills/ → ~/.gsd/agent/skills/ (overwrite when version changes)
|
|
12
|
-
* - GSD-WORKFLOW.md
|
|
12
|
+
* - GSD-WORKFLOW.md → ~/.gsd/agent/GSD-WORKFLOW.md (fallback for env var miss)
|
|
13
13
|
*
|
|
14
14
|
* Skips the copy when the managed-resources.json version matches the current
|
|
15
15
|
* GSD version, avoiding ~128ms of synchronous cpSync on every startup.
|
package/dist/resource-loader.js
CHANGED
|
@@ -18,6 +18,9 @@ import { loadRegistry, readManifestFromEntryPath, isExtensionEnabled, ensureRegi
|
|
|
18
18
|
const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
19
19
|
const distResources = join(packageRoot, 'dist', 'resources');
|
|
20
20
|
const srcResources = join(packageRoot, 'src', 'resources');
|
|
21
|
+
// Use dist/resources only if it has the full expected structure.
|
|
22
|
+
// A partial build (tsc without copy-resources) creates dist/resources/extensions/
|
|
23
|
+
// but not agents/ or skills/, causing initResources to sync from an incomplete source.
|
|
21
24
|
const resourcesDir = (existsSync(distResources) && existsSync(join(distResources, 'agents')))
|
|
22
25
|
? distResources
|
|
23
26
|
: srcResources;
|
|
@@ -220,7 +223,7 @@ function copyDirRecursive(src, dest) {
|
|
|
220
223
|
* - extensions/ → ~/.gsd/agent/extensions/ (overwrite when version changes)
|
|
221
224
|
* - agents/ → ~/.gsd/agent/agents/ (overwrite when version changes)
|
|
222
225
|
* - skills/ → ~/.gsd/agent/skills/ (overwrite when version changes)
|
|
223
|
-
* - GSD-WORKFLOW.md
|
|
226
|
+
* - GSD-WORKFLOW.md → ~/.gsd/agent/GSD-WORKFLOW.md (fallback for env var miss)
|
|
224
227
|
*
|
|
225
228
|
* Skips the copy when the managed-resources.json version matches the current
|
|
226
229
|
* GSD version, avoiding ~128ms of synchronous cpSync on every startup.
|
|
@@ -247,6 +250,15 @@ export function initResources(agentDir) {
|
|
|
247
250
|
syncResourceDir(bundledExtensionsDir, join(agentDir, 'extensions'));
|
|
248
251
|
syncResourceDir(join(resourcesDir, 'agents'), join(agentDir, 'agents'));
|
|
249
252
|
syncResourceDir(join(resourcesDir, 'skills'), join(agentDir, 'skills'));
|
|
253
|
+
// Sync GSD-WORKFLOW.md to agentDir as a fallback for when GSD_WORKFLOW_PATH
|
|
254
|
+
// env var is not set (e.g. fork/dev builds, alternative entry points).
|
|
255
|
+
const workflowSrc = join(resourcesDir, 'GSD-WORKFLOW.md');
|
|
256
|
+
if (existsSync(workflowSrc)) {
|
|
257
|
+
try {
|
|
258
|
+
copyFileSync(workflowSrc, join(agentDir, 'GSD-WORKFLOW.md'));
|
|
259
|
+
}
|
|
260
|
+
catch { /* non-fatal */ }
|
|
261
|
+
}
|
|
250
262
|
// Ensure all newly copied files are owner-writable so the next run can
|
|
251
263
|
// overwrite them (covers extensions, agents, and skills in one walk).
|
|
252
264
|
makeTreeWritable(agentDir);
|
|
@@ -52,14 +52,12 @@ export function createAwaitTool(getManager) {
|
|
|
52
52
|
const running = watched.filter((j) => j.status === "running");
|
|
53
53
|
if (running.length === 0) {
|
|
54
54
|
const result = formatResults(watched);
|
|
55
|
-
manager.acknowledgeDeliveries(watched.map((j) => j.id));
|
|
56
55
|
return { content: [{ type: "text", text: result }], details: undefined };
|
|
57
56
|
}
|
|
58
57
|
// Wait for at least one to complete
|
|
59
58
|
await Promise.race(running.map((j) => j.promise));
|
|
60
59
|
// Collect all completed results (more may have finished while waiting)
|
|
61
60
|
const completed = watched.filter((j) => j.status !== "running");
|
|
62
|
-
manager.acknowledgeDeliveries(completed.map((j) => j.id));
|
|
63
61
|
const stillRunning = watched.filter((j) => j.status === "running");
|
|
64
62
|
let result = formatResults(completed);
|
|
65
63
|
if (stillRunning.length > 0) {
|
|
@@ -101,12 +101,6 @@ export class AsyncJobManager {
|
|
|
101
101
|
getAllJobs() {
|
|
102
102
|
return [...this.jobs.values()];
|
|
103
103
|
}
|
|
104
|
-
/**
|
|
105
|
-
* No-op. Retained for API compatibility with await_job tool.
|
|
106
|
-
*/
|
|
107
|
-
acknowledgeDeliveries(_jobIds) {
|
|
108
|
-
// Delivery is fire-once; no retries to cancel.
|
|
109
|
-
}
|
|
110
104
|
/**
|
|
111
105
|
* Cleanup all timers and resources.
|
|
112
106
|
*/
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Output analysis, digest generation, highlights extraction, and output retrieval.
|
|
3
3
|
*/
|
|
4
4
|
import { truncateHead, DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, } from "@gsd/pi-coding-agent";
|
|
5
|
-
import { ERROR_PATTERN_UNION, WARNING_PATTERN_UNION, READINESS_PATTERN_UNION, BUILD_COMPLETE_PATTERN_UNION, TEST_RESULT_PATTERN_UNION, URL_PATTERN, PORT_PATTERN_SOURCE,
|
|
5
|
+
import { ERROR_PATTERN_UNION, WARNING_PATTERN_UNION, READINESS_PATTERN_UNION, BUILD_COMPLETE_PATTERN_UNION, TEST_RESULT_PATTERN_UNION, URL_PATTERN, PORT_PATTERN_SOURCE, } from "./types.js";
|
|
6
6
|
import { addEvent, pushAlert } from "./process-manager.js";
|
|
7
7
|
import { transitionToReady } from "./readiness-detector.js";
|
|
8
8
|
import { formatUptime, formatTimeAgo } from "./utilities.js";
|
|
@@ -78,24 +78,6 @@ export function analyzeLine(bg, line, stream) {
|
|
|
78
78
|
pushAlert(bg, "recovered — errors cleared");
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
|
-
// Dedup tracking — evict oldest entry when map exceeds LINE_DEDUP_MAX (LRU via Map insertion order)
|
|
82
|
-
bg.totalRawLines++;
|
|
83
|
-
const lineHash = line.trim().slice(0, 100);
|
|
84
|
-
const existing = bg.lineDedup.get(lineHash);
|
|
85
|
-
if (existing !== undefined) {
|
|
86
|
-
// Re-insert to update insertion order (move to tail = most recent)
|
|
87
|
-
bg.lineDedup.delete(lineHash);
|
|
88
|
-
bg.lineDedup.set(lineHash, existing + 1);
|
|
89
|
-
}
|
|
90
|
-
else {
|
|
91
|
-
if (bg.lineDedup.size >= LINE_DEDUP_MAX) {
|
|
92
|
-
// Evict oldest entry (Map iteration order = insertion order = LRU at head)
|
|
93
|
-
const oldest = bg.lineDedup.keys().next().value;
|
|
94
|
-
if (oldest !== undefined)
|
|
95
|
-
bg.lineDedup.delete(oldest);
|
|
96
|
-
}
|
|
97
|
-
bg.lineDedup.set(lineHash, 1);
|
|
98
|
-
}
|
|
99
81
|
}
|
|
100
82
|
// ── Digest Generation ──────────────────────────────────────────────────────
|
|
101
83
|
export function generateDigest(bg, mutate = false) {
|
|
@@ -135,12 +135,8 @@ export function startProcess(opts) {
|
|
|
135
135
|
group: opts.group || null,
|
|
136
136
|
lastErrorCount: 0,
|
|
137
137
|
lastWarningCount: 0,
|
|
138
|
-
commandHistory: [],
|
|
139
|
-
lineDedup: new Map(),
|
|
140
|
-
totalRawLines: 0,
|
|
141
138
|
stdoutLineCount: 0,
|
|
142
139
|
stderrLineCount: 0,
|
|
143
|
-
envKeys: Object.keys(opts.env || {}),
|
|
144
140
|
restartCount: 0,
|
|
145
141
|
startConfig: {
|
|
146
142
|
command,
|
|
@@ -5,8 +5,6 @@
|
|
|
5
5
|
export const MAX_BUFFER_LINES = 5000;
|
|
6
6
|
export const MAX_EVENTS = 200;
|
|
7
7
|
export const DEAD_PROCESS_TTL = 10 * 60 * 1000;
|
|
8
|
-
/** Maximum unique entries in the per-process lineDedup Map before LRU eviction. */
|
|
9
|
-
export const LINE_DEDUP_MAX = 500;
|
|
10
8
|
export const PORT_PROBE_TIMEOUT = 500;
|
|
11
9
|
export const READY_POLL_INTERVAL = 250;
|
|
12
10
|
export const DEFAULT_READY_TIMEOUT = 30000;
|
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
import { execFile, execFileSync } from "node:child_process";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { promisify } from "node:util";
|
|
4
|
+
const execFileAsync = promisify(execFile);
|
|
5
|
+
const DEFAULT_SOCKET_PATH = "/tmp/cmux.sock";
|
|
6
|
+
const STATUS_KEY = "gsd";
|
|
7
|
+
const lastSidebarSnapshots = new Map();
|
|
8
|
+
let cmuxPromptedThisSession = false;
|
|
9
|
+
let cachedCliAvailability = null;
|
|
10
|
+
export function detectCmuxEnvironment(env = process.env, socketExists = existsSync, cliAvailable = isCmuxCliAvailable) {
|
|
11
|
+
const socketPath = env.CMUX_SOCKET_PATH ?? DEFAULT_SOCKET_PATH;
|
|
12
|
+
const workspaceId = env.CMUX_WORKSPACE_ID?.trim() || undefined;
|
|
13
|
+
const surfaceId = env.CMUX_SURFACE_ID?.trim() || undefined;
|
|
14
|
+
const available = Boolean(workspaceId && surfaceId && socketExists(socketPath));
|
|
15
|
+
return {
|
|
16
|
+
available,
|
|
17
|
+
cliAvailable: cliAvailable(),
|
|
18
|
+
socketPath,
|
|
19
|
+
workspaceId,
|
|
20
|
+
surfaceId,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
export function resolveCmuxConfig(preferences, env = process.env, socketExists = existsSync, cliAvailable = isCmuxCliAvailable) {
|
|
24
|
+
const detected = detectCmuxEnvironment(env, socketExists, cliAvailable);
|
|
25
|
+
const cmux = preferences?.cmux ?? {};
|
|
26
|
+
const enabled = detected.available && cmux.enabled === true;
|
|
27
|
+
return {
|
|
28
|
+
...detected,
|
|
29
|
+
enabled,
|
|
30
|
+
notifications: enabled && cmux.notifications !== false,
|
|
31
|
+
sidebar: enabled && cmux.sidebar !== false,
|
|
32
|
+
splits: enabled && cmux.splits === true,
|
|
33
|
+
browser: enabled && cmux.browser === true,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
export function shouldPromptToEnableCmux(preferences, env = process.env, socketExists = existsSync, cliAvailable = isCmuxCliAvailable) {
|
|
37
|
+
if (cmuxPromptedThisSession)
|
|
38
|
+
return false;
|
|
39
|
+
const detected = detectCmuxEnvironment(env, socketExists, cliAvailable);
|
|
40
|
+
if (!detected.available)
|
|
41
|
+
return false;
|
|
42
|
+
return preferences?.cmux?.enabled === undefined;
|
|
43
|
+
}
|
|
44
|
+
export function markCmuxPromptShown() {
|
|
45
|
+
cmuxPromptedThisSession = true;
|
|
46
|
+
}
|
|
47
|
+
export function resetCmuxPromptState() {
|
|
48
|
+
cmuxPromptedThisSession = false;
|
|
49
|
+
}
|
|
50
|
+
export function isCmuxCliAvailable() {
|
|
51
|
+
if (cachedCliAvailability !== null)
|
|
52
|
+
return cachedCliAvailability;
|
|
53
|
+
try {
|
|
54
|
+
execFileSync("cmux", ["--help"], { stdio: "ignore", timeout: 1000 });
|
|
55
|
+
cachedCliAvailability = true;
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
cachedCliAvailability = false;
|
|
59
|
+
}
|
|
60
|
+
return cachedCliAvailability;
|
|
61
|
+
}
|
|
62
|
+
export function supportsOsc777Notifications(env = process.env) {
|
|
63
|
+
const termProgram = env.TERM_PROGRAM?.toLowerCase() ?? "";
|
|
64
|
+
return termProgram === "ghostty" || termProgram === "wezterm" || termProgram === "iterm.app";
|
|
65
|
+
}
|
|
66
|
+
export function emitOsc777Notification(title, body) {
|
|
67
|
+
if (!supportsOsc777Notifications())
|
|
68
|
+
return;
|
|
69
|
+
const safeTitle = normalizeNotificationText(title).replace(/;/g, ",");
|
|
70
|
+
const safeBody = normalizeNotificationText(body).replace(/;/g, ",");
|
|
71
|
+
process.stdout.write(`\x1b]777;notify;${safeTitle};${safeBody}\x07`);
|
|
72
|
+
}
|
|
73
|
+
export function buildCmuxStatusLabel(state) {
|
|
74
|
+
const parts = [];
|
|
75
|
+
if (state.activeMilestone)
|
|
76
|
+
parts.push(state.activeMilestone.id);
|
|
77
|
+
if (state.activeSlice)
|
|
78
|
+
parts.push(state.activeSlice.id);
|
|
79
|
+
if (state.activeTask) {
|
|
80
|
+
const prev = parts.pop();
|
|
81
|
+
parts.push(prev ? `${prev}/${state.activeTask.id}` : state.activeTask.id);
|
|
82
|
+
}
|
|
83
|
+
if (parts.length === 0)
|
|
84
|
+
return state.phase;
|
|
85
|
+
return `${parts.join(" ")} · ${state.phase}`;
|
|
86
|
+
}
|
|
87
|
+
export function buildCmuxProgress(state) {
|
|
88
|
+
const progress = state.progress;
|
|
89
|
+
if (!progress)
|
|
90
|
+
return null;
|
|
91
|
+
const choose = (done, total, label) => {
|
|
92
|
+
if (total <= 0)
|
|
93
|
+
return null;
|
|
94
|
+
return { value: Math.max(0, Math.min(1, done / total)), label: `${done}/${total} ${label}` };
|
|
95
|
+
};
|
|
96
|
+
return choose(progress.tasks?.done ?? 0, progress.tasks?.total ?? 0, "tasks")
|
|
97
|
+
?? choose(progress.slices?.done ?? 0, progress.slices?.total ?? 0, "slices")
|
|
98
|
+
?? choose(progress.milestones.done, progress.milestones.total, "milestones");
|
|
99
|
+
}
|
|
100
|
+
function phaseVisuals(phase) {
|
|
101
|
+
switch (phase) {
|
|
102
|
+
case "blocked":
|
|
103
|
+
return { icon: "triangle-alert", color: "#ef4444" };
|
|
104
|
+
case "paused":
|
|
105
|
+
return { icon: "pause", color: "#f59e0b" };
|
|
106
|
+
case "complete":
|
|
107
|
+
case "completing-milestone":
|
|
108
|
+
return { icon: "check", color: "#22c55e" };
|
|
109
|
+
case "planning":
|
|
110
|
+
case "researching":
|
|
111
|
+
case "replanning-slice":
|
|
112
|
+
return { icon: "compass", color: "#3b82f6" };
|
|
113
|
+
case "validating-milestone":
|
|
114
|
+
case "verifying":
|
|
115
|
+
return { icon: "shield-check", color: "#06b6d4" };
|
|
116
|
+
default:
|
|
117
|
+
return { icon: "rocket", color: "#4ade80" };
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
function sidebarSnapshotKey(config) {
|
|
121
|
+
return config.workspaceId ?? "default";
|
|
122
|
+
}
|
|
123
|
+
export class CmuxClient {
|
|
124
|
+
config;
|
|
125
|
+
constructor(config) {
|
|
126
|
+
this.config = config;
|
|
127
|
+
}
|
|
128
|
+
static fromPreferences(preferences) {
|
|
129
|
+
return new CmuxClient(resolveCmuxConfig(preferences));
|
|
130
|
+
}
|
|
131
|
+
getConfig() {
|
|
132
|
+
return this.config;
|
|
133
|
+
}
|
|
134
|
+
canRun() {
|
|
135
|
+
return this.config.available && this.config.cliAvailable;
|
|
136
|
+
}
|
|
137
|
+
appendWorkspace(args) {
|
|
138
|
+
return this.config.workspaceId ? [...args, "--workspace", this.config.workspaceId] : args;
|
|
139
|
+
}
|
|
140
|
+
appendSurface(args, surfaceId) {
|
|
141
|
+
return surfaceId ? [...args, "--surface", surfaceId] : args;
|
|
142
|
+
}
|
|
143
|
+
runSync(args) {
|
|
144
|
+
if (!this.canRun())
|
|
145
|
+
return null;
|
|
146
|
+
try {
|
|
147
|
+
return execFileSync("cmux", args, {
|
|
148
|
+
encoding: "utf-8",
|
|
149
|
+
timeout: 3000,
|
|
150
|
+
env: process.env,
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
async runAsync(args) {
|
|
158
|
+
if (!this.canRun())
|
|
159
|
+
return null;
|
|
160
|
+
try {
|
|
161
|
+
const result = await execFileAsync("cmux", args, {
|
|
162
|
+
encoding: "utf-8",
|
|
163
|
+
timeout: 5000,
|
|
164
|
+
env: process.env,
|
|
165
|
+
});
|
|
166
|
+
return result.stdout;
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
getCapabilities() {
|
|
173
|
+
const stdout = this.runSync(["capabilities", "--json"]);
|
|
174
|
+
return stdout ? parseJson(stdout) : null;
|
|
175
|
+
}
|
|
176
|
+
identify() {
|
|
177
|
+
const stdout = this.runSync(["identify", "--json"]);
|
|
178
|
+
return stdout ? parseJson(stdout) : null;
|
|
179
|
+
}
|
|
180
|
+
setStatus(label, phase) {
|
|
181
|
+
if (!this.config.sidebar)
|
|
182
|
+
return;
|
|
183
|
+
const visuals = phaseVisuals(phase);
|
|
184
|
+
this.runSync(this.appendWorkspace([
|
|
185
|
+
"set-status",
|
|
186
|
+
STATUS_KEY,
|
|
187
|
+
label,
|
|
188
|
+
"--icon",
|
|
189
|
+
visuals.icon,
|
|
190
|
+
"--color",
|
|
191
|
+
visuals.color,
|
|
192
|
+
]));
|
|
193
|
+
}
|
|
194
|
+
clearStatus() {
|
|
195
|
+
if (!this.config.sidebar)
|
|
196
|
+
return;
|
|
197
|
+
this.runSync(this.appendWorkspace(["clear-status", STATUS_KEY]));
|
|
198
|
+
}
|
|
199
|
+
setProgress(progress) {
|
|
200
|
+
if (!this.config.sidebar)
|
|
201
|
+
return;
|
|
202
|
+
if (!progress) {
|
|
203
|
+
this.runSync(this.appendWorkspace(["clear-progress"]));
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
this.runSync(this.appendWorkspace([
|
|
207
|
+
"set-progress",
|
|
208
|
+
progress.value.toFixed(3),
|
|
209
|
+
"--label",
|
|
210
|
+
progress.label,
|
|
211
|
+
]));
|
|
212
|
+
}
|
|
213
|
+
log(message, level = "info", source = "gsd") {
|
|
214
|
+
if (!this.config.sidebar)
|
|
215
|
+
return;
|
|
216
|
+
this.runSync(this.appendWorkspace([
|
|
217
|
+
"log",
|
|
218
|
+
"--level",
|
|
219
|
+
level,
|
|
220
|
+
"--source",
|
|
221
|
+
source,
|
|
222
|
+
"--",
|
|
223
|
+
message,
|
|
224
|
+
]));
|
|
225
|
+
}
|
|
226
|
+
notify(title, body, subtitle) {
|
|
227
|
+
if (!this.config.notifications)
|
|
228
|
+
return false;
|
|
229
|
+
const args = ["notify", "--title", title, "--body", body];
|
|
230
|
+
if (subtitle)
|
|
231
|
+
args.push("--subtitle", subtitle);
|
|
232
|
+
return this.runSync(args) !== null;
|
|
233
|
+
}
|
|
234
|
+
async listSurfaceIds() {
|
|
235
|
+
const stdout = await this.runAsync(this.appendWorkspace(["list-surfaces", "--json", "--id-format", "both"]));
|
|
236
|
+
const parsed = stdout ? parseJson(stdout) : null;
|
|
237
|
+
return extractSurfaceIds(parsed);
|
|
238
|
+
}
|
|
239
|
+
async createSplit(direction) {
|
|
240
|
+
if (!this.config.splits)
|
|
241
|
+
return null;
|
|
242
|
+
const before = new Set(await this.listSurfaceIds());
|
|
243
|
+
const args = ["new-split", direction];
|
|
244
|
+
const scopedArgs = this.appendSurface(this.appendWorkspace(args), this.config.surfaceId);
|
|
245
|
+
await this.runAsync(scopedArgs);
|
|
246
|
+
const after = await this.listSurfaceIds();
|
|
247
|
+
for (const id of after) {
|
|
248
|
+
if (!before.has(id))
|
|
249
|
+
return id;
|
|
250
|
+
}
|
|
251
|
+
return null;
|
|
252
|
+
}
|
|
253
|
+
async sendSurface(surfaceId, text) {
|
|
254
|
+
const payload = text.endsWith("\n") ? text : `${text}\n`;
|
|
255
|
+
const stdout = await this.runAsync(["send-surface", "--surface", surfaceId, payload]);
|
|
256
|
+
return stdout !== null;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
export function syncCmuxSidebar(preferences, state) {
|
|
260
|
+
const client = CmuxClient.fromPreferences(preferences);
|
|
261
|
+
const config = client.getConfig();
|
|
262
|
+
if (!config.sidebar)
|
|
263
|
+
return;
|
|
264
|
+
const label = buildCmuxStatusLabel(state);
|
|
265
|
+
const progress = buildCmuxProgress(state);
|
|
266
|
+
const snapshot = JSON.stringify({ label, progress, phase: state.phase });
|
|
267
|
+
const key = sidebarSnapshotKey(config);
|
|
268
|
+
if (lastSidebarSnapshots.get(key) === snapshot)
|
|
269
|
+
return;
|
|
270
|
+
client.setStatus(label, state.phase);
|
|
271
|
+
client.setProgress(progress);
|
|
272
|
+
lastSidebarSnapshots.set(key, snapshot);
|
|
273
|
+
}
|
|
274
|
+
export function clearCmuxSidebar(preferences) {
|
|
275
|
+
const config = resolveCmuxConfig(preferences);
|
|
276
|
+
if (!config.available || !config.cliAvailable)
|
|
277
|
+
return;
|
|
278
|
+
const client = new CmuxClient({ ...config, enabled: true, sidebar: true });
|
|
279
|
+
const key = sidebarSnapshotKey(config);
|
|
280
|
+
client.clearStatus();
|
|
281
|
+
client.setProgress(null);
|
|
282
|
+
lastSidebarSnapshots.delete(key);
|
|
283
|
+
}
|
|
284
|
+
export function logCmuxEvent(preferences, message, level = "info") {
|
|
285
|
+
CmuxClient.fromPreferences(preferences).log(message, level);
|
|
286
|
+
}
|
|
287
|
+
export function shellEscape(value) {
|
|
288
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
289
|
+
}
|
|
290
|
+
function normalizeNotificationText(value) {
|
|
291
|
+
return value.replace(/\r?\n/g, " ").trim();
|
|
292
|
+
}
|
|
293
|
+
function parseJson(text) {
|
|
294
|
+
try {
|
|
295
|
+
return JSON.parse(text);
|
|
296
|
+
}
|
|
297
|
+
catch {
|
|
298
|
+
return null;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
function extractSurfaceIds(value) {
|
|
302
|
+
const found = new Set();
|
|
303
|
+
const visit = (node) => {
|
|
304
|
+
if (Array.isArray(node)) {
|
|
305
|
+
for (const item of node)
|
|
306
|
+
visit(item);
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
if (!node || typeof node !== "object")
|
|
310
|
+
return;
|
|
311
|
+
for (const [key, child] of Object.entries(node)) {
|
|
312
|
+
if (typeof child === "string"
|
|
313
|
+
&& (key === "surface_id" || key === "surface" || (key === "id" && child.includes("surface")))) {
|
|
314
|
+
found.add(child);
|
|
315
|
+
}
|
|
316
|
+
visit(child);
|
|
317
|
+
}
|
|
318
|
+
};
|
|
319
|
+
visit(value);
|
|
320
|
+
return Array.from(found);
|
|
321
|
+
}
|
|
@@ -327,6 +327,11 @@ export default function (pi) {
|
|
|
327
327
|
return new Text(text, 0, 0);
|
|
328
328
|
},
|
|
329
329
|
});
|
|
330
|
+
// ── Session cleanup ─────────────────────────────────────────────────────
|
|
331
|
+
pi.on("session_shutdown", async () => {
|
|
332
|
+
searchCache.clear();
|
|
333
|
+
docCache.clear();
|
|
334
|
+
});
|
|
330
335
|
// ── Startup notification ─────────────────────────────────────────────────
|
|
331
336
|
pi.on("session_start", async (_event, ctx) => {
|
|
332
337
|
if (!getApiKey()) {
|
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
import { readFile, writeFile } from "node:fs/promises";
|
|
9
9
|
import { existsSync, statSync } from "node:fs";
|
|
10
10
|
import { resolve } from "node:path";
|
|
11
|
-
import {
|
|
11
|
+
import { Editor, Key, matchesKey, Text, truncateToWidth, wrapTextWithAnsi } from "@gsd/pi-tui";
|
|
12
12
|
import { Type } from "@sinclair/typebox";
|
|
13
|
-
import { makeUI } from "./shared/mod.js";
|
|
13
|
+
import { makeUI, maskEditorLine } from "./shared/mod.js";
|
|
14
14
|
import { parseSecretsManifest, formatSecretsManifest } from "./gsd/files.js";
|
|
15
15
|
import { resolveMilestoneFile } from "./gsd/paths.js";
|
|
16
16
|
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
@@ -21,34 +21,6 @@ function maskPreview(value) {
|
|
|
21
21
|
return "*".repeat(value.length);
|
|
22
22
|
return `${value.slice(0, 4)}${"*".repeat(Math.max(4, value.length - 8))}${value.slice(-4)}`;
|
|
23
23
|
}
|
|
24
|
-
/**
|
|
25
|
-
* Replace editor visible text with masked characters while preserving ANSI cursor/sequencer codes.
|
|
26
|
-
*/
|
|
27
|
-
function maskEditorLine(line) {
|
|
28
|
-
// Keep border / metadata lines readable.
|
|
29
|
-
if (line.startsWith("─")) {
|
|
30
|
-
return line;
|
|
31
|
-
}
|
|
32
|
-
let output = "";
|
|
33
|
-
let i = 0;
|
|
34
|
-
while (i < line.length) {
|
|
35
|
-
if (line.startsWith(CURSOR_MARKER, i)) {
|
|
36
|
-
output += CURSOR_MARKER;
|
|
37
|
-
i += CURSOR_MARKER.length;
|
|
38
|
-
continue;
|
|
39
|
-
}
|
|
40
|
-
const ansiMatch = /^\x1b\[[0-9;]*m/.exec(line.slice(i));
|
|
41
|
-
if (ansiMatch) {
|
|
42
|
-
output += ansiMatch[0];
|
|
43
|
-
i += ansiMatch[0].length;
|
|
44
|
-
continue;
|
|
45
|
-
}
|
|
46
|
-
const ch = line[i];
|
|
47
|
-
output += ch === " " ? " " : "*";
|
|
48
|
-
i += 1;
|
|
49
|
-
}
|
|
50
|
-
return output;
|
|
51
|
-
}
|
|
52
24
|
function shellEscapeSingle(value) {
|
|
53
25
|
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
54
26
|
}
|
|
@@ -326,6 +326,11 @@ export default function (pi) {
|
|
|
326
326
|
return new Text(text, 0, 0);
|
|
327
327
|
},
|
|
328
328
|
});
|
|
329
|
+
// ── Session cleanup ─────────────────────────────────────────────────────
|
|
330
|
+
pi.on("session_shutdown", async () => {
|
|
331
|
+
resultCache.clear();
|
|
332
|
+
client = null;
|
|
333
|
+
});
|
|
329
334
|
// ── Startup notification ─────────────────────────────────────────────────
|
|
330
335
|
pi.on("session_start", async (_event, ctx) => {
|
|
331
336
|
if (process.env.GEMINI_API_KEY)
|