gsd-pi 2.35.0-dev.cd3b7ea → 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
|
@@ -22,7 +22,6 @@ import {
|
|
|
22
22
|
READINESS_PATTERNS,
|
|
23
23
|
BUILD_COMPLETE_PATTERNS,
|
|
24
24
|
TEST_RESULT_PATTERNS,
|
|
25
|
-
LINE_DEDUP_MAX,
|
|
26
25
|
} from "./types.js";
|
|
27
26
|
import { addEvent, pushAlert } from "./process-manager.js";
|
|
28
27
|
import { transitionToReady } from "./readiness-detector.js";
|
|
@@ -106,22 +105,6 @@ export function analyzeLine(bg: BgProcess, line: string, stream: "stdout" | "std
|
|
|
106
105
|
}
|
|
107
106
|
}
|
|
108
107
|
|
|
109
|
-
// Dedup tracking — evict oldest entry when map exceeds LINE_DEDUP_MAX (LRU via Map insertion order)
|
|
110
|
-
bg.totalRawLines++;
|
|
111
|
-
const lineHash = line.trim().slice(0, 100);
|
|
112
|
-
const existing = bg.lineDedup.get(lineHash);
|
|
113
|
-
if (existing !== undefined) {
|
|
114
|
-
// Re-insert to update insertion order (move to tail = most recent)
|
|
115
|
-
bg.lineDedup.delete(lineHash);
|
|
116
|
-
bg.lineDedup.set(lineHash, existing + 1);
|
|
117
|
-
} else {
|
|
118
|
-
if (bg.lineDedup.size >= LINE_DEDUP_MAX) {
|
|
119
|
-
// Evict oldest entry (Map iteration order = insertion order = LRU at head)
|
|
120
|
-
const oldest = bg.lineDedup.keys().next().value;
|
|
121
|
-
if (oldest !== undefined) bg.lineDedup.delete(oldest);
|
|
122
|
-
}
|
|
123
|
-
bg.lineDedup.set(lineHash, 1);
|
|
124
|
-
}
|
|
125
108
|
}
|
|
126
109
|
|
|
127
110
|
// ── Digest Generation ──────────────────────────────────────────────────────
|
|
@@ -162,12 +162,8 @@ export function startProcess(opts: StartOptions): BgProcess {
|
|
|
162
162
|
group: opts.group || null,
|
|
163
163
|
lastErrorCount: 0,
|
|
164
164
|
lastWarningCount: 0,
|
|
165
|
-
commandHistory: [],
|
|
166
|
-
lineDedup: new Map(),
|
|
167
|
-
totalRawLines: 0,
|
|
168
165
|
stdoutLineCount: 0,
|
|
169
166
|
stderrLineCount: 0,
|
|
170
|
-
envKeys: Object.keys(opts.env || {}),
|
|
171
167
|
restartCount: 0,
|
|
172
168
|
startConfig: {
|
|
173
169
|
command,
|
|
@@ -21,9 +21,7 @@ export interface ProcessEvent {
|
|
|
21
21
|
| "recovered"
|
|
22
22
|
| "exited"
|
|
23
23
|
| "crashed"
|
|
24
|
-
| "output"
|
|
25
24
|
| "port_open"
|
|
26
|
-
| "pattern_match"
|
|
27
25
|
| "port_timeout";
|
|
28
26
|
timestamp: number;
|
|
29
27
|
detail: string;
|
|
@@ -92,18 +90,10 @@ export interface BgProcess {
|
|
|
92
90
|
lastErrorCount: number;
|
|
93
91
|
/** Last warning count snapshot for diff detection */
|
|
94
92
|
lastWarningCount: number;
|
|
95
|
-
/** Command history for shell-type sessions */
|
|
96
|
-
commandHistory: string[];
|
|
97
|
-
/** Dedup tracker: hash → count of repeated lines (capped at LINE_DEDUP_MAX entries) */
|
|
98
|
-
lineDedup: Map<string, number>;
|
|
99
|
-
/** Total raw lines (before dedup) for token savings calc */
|
|
100
|
-
totalRawLines: number;
|
|
101
93
|
/** Tracked stdout line count (incremented in addOutputLine, avoids O(n) filter) */
|
|
102
94
|
stdoutLineCount: number;
|
|
103
95
|
/** Tracked stderr line count (incremented in addOutputLine, avoids O(n) filter) */
|
|
104
96
|
stderrLineCount: number;
|
|
105
|
-
/** Env snapshot (keys only, no values for security) */
|
|
106
|
-
envKeys: string[];
|
|
107
97
|
/** Restart count */
|
|
108
98
|
restartCount: number;
|
|
109
99
|
/** Original start config for restart */
|
|
@@ -187,8 +177,6 @@ export interface ProcessManifest {
|
|
|
187
177
|
export const MAX_BUFFER_LINES = 5000;
|
|
188
178
|
export const MAX_EVENTS = 200;
|
|
189
179
|
export const DEAD_PROCESS_TTL = 10 * 60 * 1000;
|
|
190
|
-
/** Maximum unique entries in the per-process lineDedup Map before LRU eviction. */
|
|
191
|
-
export const LINE_DEDUP_MAX = 500;
|
|
192
180
|
export const PORT_PROBE_TIMEOUT = 500;
|
|
193
181
|
export const READY_POLL_INTERVAL = 250;
|
|
194
182
|
export const DEFAULT_READY_TIMEOUT = 30000;
|
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
import { execFile, execFileSync } from "node:child_process";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { promisify } from "node:util";
|
|
4
|
+
import type { GSDPreferences } from "../gsd/preferences.js";
|
|
5
|
+
import type { GSDState, Phase } from "../gsd/types.js";
|
|
6
|
+
|
|
7
|
+
const execFileAsync = promisify(execFile);
|
|
8
|
+
const DEFAULT_SOCKET_PATH = "/tmp/cmux.sock";
|
|
9
|
+
const STATUS_KEY = "gsd";
|
|
10
|
+
const lastSidebarSnapshots = new Map<string, string>();
|
|
11
|
+
let cmuxPromptedThisSession = false;
|
|
12
|
+
let cachedCliAvailability: boolean | null = null;
|
|
13
|
+
|
|
14
|
+
export interface CmuxEnvironment {
|
|
15
|
+
available: boolean;
|
|
16
|
+
cliAvailable: boolean;
|
|
17
|
+
socketPath: string;
|
|
18
|
+
workspaceId?: string;
|
|
19
|
+
surfaceId?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface ResolvedCmuxConfig extends CmuxEnvironment {
|
|
23
|
+
enabled: boolean;
|
|
24
|
+
notifications: boolean;
|
|
25
|
+
sidebar: boolean;
|
|
26
|
+
splits: boolean;
|
|
27
|
+
browser: boolean;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface CmuxSidebarProgress {
|
|
31
|
+
value: number;
|
|
32
|
+
label: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export type CmuxLogLevel = "info" | "progress" | "success" | "warning" | "error";
|
|
36
|
+
|
|
37
|
+
export function detectCmuxEnvironment(
|
|
38
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
39
|
+
socketExists: (path: string) => boolean = existsSync,
|
|
40
|
+
cliAvailable: () => boolean = isCmuxCliAvailable,
|
|
41
|
+
): CmuxEnvironment {
|
|
42
|
+
const socketPath = env.CMUX_SOCKET_PATH ?? DEFAULT_SOCKET_PATH;
|
|
43
|
+
const workspaceId = env.CMUX_WORKSPACE_ID?.trim() || undefined;
|
|
44
|
+
const surfaceId = env.CMUX_SURFACE_ID?.trim() || undefined;
|
|
45
|
+
const available = Boolean(workspaceId && surfaceId && socketExists(socketPath));
|
|
46
|
+
return {
|
|
47
|
+
available,
|
|
48
|
+
cliAvailable: cliAvailable(),
|
|
49
|
+
socketPath,
|
|
50
|
+
workspaceId,
|
|
51
|
+
surfaceId,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function resolveCmuxConfig(
|
|
56
|
+
preferences: GSDPreferences | undefined,
|
|
57
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
58
|
+
socketExists: (path: string) => boolean = existsSync,
|
|
59
|
+
cliAvailable: () => boolean = isCmuxCliAvailable,
|
|
60
|
+
): ResolvedCmuxConfig {
|
|
61
|
+
const detected = detectCmuxEnvironment(env, socketExists, cliAvailable);
|
|
62
|
+
const cmux = preferences?.cmux ?? {};
|
|
63
|
+
const enabled = detected.available && cmux.enabled === true;
|
|
64
|
+
return {
|
|
65
|
+
...detected,
|
|
66
|
+
enabled,
|
|
67
|
+
notifications: enabled && cmux.notifications !== false,
|
|
68
|
+
sidebar: enabled && cmux.sidebar !== false,
|
|
69
|
+
splits: enabled && cmux.splits === true,
|
|
70
|
+
browser: enabled && cmux.browser === true,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function shouldPromptToEnableCmux(
|
|
75
|
+
preferences: GSDPreferences | undefined,
|
|
76
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
77
|
+
socketExists: (path: string) => boolean = existsSync,
|
|
78
|
+
cliAvailable: () => boolean = isCmuxCliAvailable,
|
|
79
|
+
): boolean {
|
|
80
|
+
if (cmuxPromptedThisSession) return false;
|
|
81
|
+
const detected = detectCmuxEnvironment(env, socketExists, cliAvailable);
|
|
82
|
+
if (!detected.available) return false;
|
|
83
|
+
return preferences?.cmux?.enabled === undefined;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function markCmuxPromptShown(): void {
|
|
87
|
+
cmuxPromptedThisSession = true;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function resetCmuxPromptState(): void {
|
|
91
|
+
cmuxPromptedThisSession = false;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function isCmuxCliAvailable(): boolean {
|
|
95
|
+
if (cachedCliAvailability !== null) return cachedCliAvailability;
|
|
96
|
+
try {
|
|
97
|
+
execFileSync("cmux", ["--help"], { stdio: "ignore", timeout: 1000 });
|
|
98
|
+
cachedCliAvailability = true;
|
|
99
|
+
} catch {
|
|
100
|
+
cachedCliAvailability = false;
|
|
101
|
+
}
|
|
102
|
+
return cachedCliAvailability;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function supportsOsc777Notifications(env: NodeJS.ProcessEnv = process.env): boolean {
|
|
106
|
+
const termProgram = env.TERM_PROGRAM?.toLowerCase() ?? "";
|
|
107
|
+
return termProgram === "ghostty" || termProgram === "wezterm" || termProgram === "iterm.app";
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function emitOsc777Notification(title: string, body: string): void {
|
|
111
|
+
if (!supportsOsc777Notifications()) return;
|
|
112
|
+
const safeTitle = normalizeNotificationText(title).replace(/;/g, ",");
|
|
113
|
+
const safeBody = normalizeNotificationText(body).replace(/;/g, ",");
|
|
114
|
+
process.stdout.write(`\x1b]777;notify;${safeTitle};${safeBody}\x07`);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function buildCmuxStatusLabel(state: GSDState): string {
|
|
118
|
+
const parts: string[] = [];
|
|
119
|
+
if (state.activeMilestone) parts.push(state.activeMilestone.id);
|
|
120
|
+
if (state.activeSlice) parts.push(state.activeSlice.id);
|
|
121
|
+
if (state.activeTask) {
|
|
122
|
+
const prev = parts.pop();
|
|
123
|
+
parts.push(prev ? `${prev}/${state.activeTask.id}` : state.activeTask.id);
|
|
124
|
+
}
|
|
125
|
+
if (parts.length === 0) return state.phase;
|
|
126
|
+
return `${parts.join(" ")} · ${state.phase}`;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function buildCmuxProgress(state: GSDState): CmuxSidebarProgress | null {
|
|
130
|
+
const progress = state.progress;
|
|
131
|
+
if (!progress) return null;
|
|
132
|
+
|
|
133
|
+
const choose = (done: number, total: number, label: string): CmuxSidebarProgress | null => {
|
|
134
|
+
if (total <= 0) return null;
|
|
135
|
+
return { value: Math.max(0, Math.min(1, done / total)), label: `${done}/${total} ${label}` };
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
return choose(progress.tasks?.done ?? 0, progress.tasks?.total ?? 0, "tasks")
|
|
139
|
+
?? choose(progress.slices?.done ?? 0, progress.slices?.total ?? 0, "slices")
|
|
140
|
+
?? choose(progress.milestones.done, progress.milestones.total, "milestones");
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function phaseVisuals(phase: Phase): { icon: string; color: string } {
|
|
144
|
+
switch (phase) {
|
|
145
|
+
case "blocked":
|
|
146
|
+
return { icon: "triangle-alert", color: "#ef4444" };
|
|
147
|
+
case "paused":
|
|
148
|
+
return { icon: "pause", color: "#f59e0b" };
|
|
149
|
+
case "complete":
|
|
150
|
+
case "completing-milestone":
|
|
151
|
+
return { icon: "check", color: "#22c55e" };
|
|
152
|
+
case "planning":
|
|
153
|
+
case "researching":
|
|
154
|
+
case "replanning-slice":
|
|
155
|
+
return { icon: "compass", color: "#3b82f6" };
|
|
156
|
+
case "validating-milestone":
|
|
157
|
+
case "verifying":
|
|
158
|
+
return { icon: "shield-check", color: "#06b6d4" };
|
|
159
|
+
default:
|
|
160
|
+
return { icon: "rocket", color: "#4ade80" };
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function sidebarSnapshotKey(config: ResolvedCmuxConfig): string {
|
|
165
|
+
return config.workspaceId ?? "default";
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export class CmuxClient {
|
|
169
|
+
private readonly config: ResolvedCmuxConfig;
|
|
170
|
+
|
|
171
|
+
constructor(config: ResolvedCmuxConfig) {
|
|
172
|
+
this.config = config;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
static fromPreferences(preferences: GSDPreferences | undefined): CmuxClient {
|
|
176
|
+
return new CmuxClient(resolveCmuxConfig(preferences));
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
getConfig(): ResolvedCmuxConfig {
|
|
180
|
+
return this.config;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
private canRun(): boolean {
|
|
184
|
+
return this.config.available && this.config.cliAvailable;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
private appendWorkspace(args: string[]): string[] {
|
|
188
|
+
return this.config.workspaceId ? [...args, "--workspace", this.config.workspaceId] : args;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
private appendSurface(args: string[], surfaceId?: string): string[] {
|
|
192
|
+
return surfaceId ? [...args, "--surface", surfaceId] : args;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
private runSync(args: string[]): string | null {
|
|
196
|
+
if (!this.canRun()) return null;
|
|
197
|
+
try {
|
|
198
|
+
return execFileSync("cmux", args, {
|
|
199
|
+
encoding: "utf-8",
|
|
200
|
+
timeout: 3000,
|
|
201
|
+
env: process.env,
|
|
202
|
+
});
|
|
203
|
+
} catch {
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
private async runAsync(args: string[]): Promise<string | null> {
|
|
209
|
+
if (!this.canRun()) return null;
|
|
210
|
+
try {
|
|
211
|
+
const result = await execFileAsync("cmux", args, {
|
|
212
|
+
encoding: "utf-8",
|
|
213
|
+
timeout: 5000,
|
|
214
|
+
env: process.env,
|
|
215
|
+
});
|
|
216
|
+
return result.stdout;
|
|
217
|
+
} catch {
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
getCapabilities(): unknown | null {
|
|
223
|
+
const stdout = this.runSync(["capabilities", "--json"]);
|
|
224
|
+
return stdout ? parseJson(stdout) : null;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
identify(): unknown | null {
|
|
228
|
+
const stdout = this.runSync(["identify", "--json"]);
|
|
229
|
+
return stdout ? parseJson(stdout) : null;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
setStatus(label: string, phase: Phase): void {
|
|
233
|
+
if (!this.config.sidebar) return;
|
|
234
|
+
const visuals = phaseVisuals(phase);
|
|
235
|
+
this.runSync(this.appendWorkspace([
|
|
236
|
+
"set-status",
|
|
237
|
+
STATUS_KEY,
|
|
238
|
+
label,
|
|
239
|
+
"--icon",
|
|
240
|
+
visuals.icon,
|
|
241
|
+
"--color",
|
|
242
|
+
visuals.color,
|
|
243
|
+
]));
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
clearStatus(): void {
|
|
247
|
+
if (!this.config.sidebar) return;
|
|
248
|
+
this.runSync(this.appendWorkspace(["clear-status", STATUS_KEY]));
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
setProgress(progress: CmuxSidebarProgress | null): void {
|
|
252
|
+
if (!this.config.sidebar) return;
|
|
253
|
+
if (!progress) {
|
|
254
|
+
this.runSync(this.appendWorkspace(["clear-progress"]));
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
this.runSync(this.appendWorkspace([
|
|
258
|
+
"set-progress",
|
|
259
|
+
progress.value.toFixed(3),
|
|
260
|
+
"--label",
|
|
261
|
+
progress.label,
|
|
262
|
+
]));
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
log(message: string, level: CmuxLogLevel = "info", source = "gsd"): void {
|
|
266
|
+
if (!this.config.sidebar) return;
|
|
267
|
+
this.runSync(this.appendWorkspace([
|
|
268
|
+
"log",
|
|
269
|
+
"--level",
|
|
270
|
+
level,
|
|
271
|
+
"--source",
|
|
272
|
+
source,
|
|
273
|
+
"--",
|
|
274
|
+
message,
|
|
275
|
+
]));
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
notify(title: string, body: string, subtitle?: string): boolean {
|
|
279
|
+
if (!this.config.notifications) return false;
|
|
280
|
+
const args = ["notify", "--title", title, "--body", body];
|
|
281
|
+
if (subtitle) args.push("--subtitle", subtitle);
|
|
282
|
+
return this.runSync(args) !== null;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
async listSurfaceIds(): Promise<string[]> {
|
|
286
|
+
const stdout = await this.runAsync(this.appendWorkspace(["list-surfaces", "--json", "--id-format", "both"]));
|
|
287
|
+
const parsed = stdout ? parseJson(stdout) : null;
|
|
288
|
+
return extractSurfaceIds(parsed);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
async createSplit(direction: "right" | "down" | "left" | "up"): Promise<string | null> {
|
|
292
|
+
if (!this.config.splits) return null;
|
|
293
|
+
const before = new Set(await this.listSurfaceIds());
|
|
294
|
+
const args = ["new-split", direction];
|
|
295
|
+
const scopedArgs = this.appendSurface(this.appendWorkspace(args), this.config.surfaceId);
|
|
296
|
+
await this.runAsync(scopedArgs);
|
|
297
|
+
const after = await this.listSurfaceIds();
|
|
298
|
+
for (const id of after) {
|
|
299
|
+
if (!before.has(id)) return id;
|
|
300
|
+
}
|
|
301
|
+
return null;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
async sendSurface(surfaceId: string, text: string): Promise<boolean> {
|
|
305
|
+
const payload = text.endsWith("\n") ? text : `${text}\n`;
|
|
306
|
+
const stdout = await this.runAsync(["send-surface", "--surface", surfaceId, payload]);
|
|
307
|
+
return stdout !== null;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
export function syncCmuxSidebar(preferences: GSDPreferences | undefined, state: GSDState): void {
|
|
312
|
+
const client = CmuxClient.fromPreferences(preferences);
|
|
313
|
+
const config = client.getConfig();
|
|
314
|
+
if (!config.sidebar) return;
|
|
315
|
+
|
|
316
|
+
const label = buildCmuxStatusLabel(state);
|
|
317
|
+
const progress = buildCmuxProgress(state);
|
|
318
|
+
const snapshot = JSON.stringify({ label, progress, phase: state.phase });
|
|
319
|
+
const key = sidebarSnapshotKey(config);
|
|
320
|
+
if (lastSidebarSnapshots.get(key) === snapshot) return;
|
|
321
|
+
|
|
322
|
+
client.setStatus(label, state.phase);
|
|
323
|
+
client.setProgress(progress);
|
|
324
|
+
lastSidebarSnapshots.set(key, snapshot);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
export function clearCmuxSidebar(preferences: GSDPreferences | undefined): void {
|
|
328
|
+
const config = resolveCmuxConfig(preferences);
|
|
329
|
+
if (!config.available || !config.cliAvailable) return;
|
|
330
|
+
const client = new CmuxClient({ ...config, enabled: true, sidebar: true });
|
|
331
|
+
const key = sidebarSnapshotKey(config);
|
|
332
|
+
client.clearStatus();
|
|
333
|
+
client.setProgress(null);
|
|
334
|
+
lastSidebarSnapshots.delete(key);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
export function logCmuxEvent(
|
|
338
|
+
preferences: GSDPreferences | undefined,
|
|
339
|
+
message: string,
|
|
340
|
+
level: CmuxLogLevel = "info",
|
|
341
|
+
): void {
|
|
342
|
+
CmuxClient.fromPreferences(preferences).log(message, level);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
export function shellEscape(value: string): string {
|
|
346
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function normalizeNotificationText(value: string): string {
|
|
350
|
+
return value.replace(/\r?\n/g, " ").trim();
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function parseJson(text: string): unknown {
|
|
354
|
+
try {
|
|
355
|
+
return JSON.parse(text);
|
|
356
|
+
} catch {
|
|
357
|
+
return null;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function extractSurfaceIds(value: unknown): string[] {
|
|
362
|
+
const found = new Set<string>();
|
|
363
|
+
|
|
364
|
+
const visit = (node: unknown): void => {
|
|
365
|
+
if (Array.isArray(node)) {
|
|
366
|
+
for (const item of node) visit(item);
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
if (!node || typeof node !== "object") return;
|
|
370
|
+
|
|
371
|
+
for (const [key, child] of Object.entries(node as Record<string, unknown>)) {
|
|
372
|
+
if (
|
|
373
|
+
typeof child === "string"
|
|
374
|
+
&& (key === "surface_id" || key === "surface" || (key === "id" && child.includes("surface")))
|
|
375
|
+
) {
|
|
376
|
+
found.add(child);
|
|
377
|
+
}
|
|
378
|
+
visit(child);
|
|
379
|
+
}
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
visit(value);
|
|
383
|
+
return Array.from(found);
|
|
384
|
+
}
|
|
@@ -414,6 +414,13 @@ export default function (pi: ExtensionAPI) {
|
|
|
414
414
|
},
|
|
415
415
|
});
|
|
416
416
|
|
|
417
|
+
// ── Session cleanup ─────────────────────────────────────────────────────
|
|
418
|
+
|
|
419
|
+
pi.on("session_shutdown", async () => {
|
|
420
|
+
searchCache.clear();
|
|
421
|
+
docCache.clear();
|
|
422
|
+
});
|
|
423
|
+
|
|
417
424
|
// ── Startup notification ─────────────────────────────────────────────────
|
|
418
425
|
|
|
419
426
|
pi.on("session_start", async (_event, ctx) => {
|
|
@@ -11,9 +11,9 @@ import { existsSync, statSync } from "node:fs";
|
|
|
11
11
|
import { resolve } from "node:path";
|
|
12
12
|
|
|
13
13
|
import type { ExtensionAPI, Theme } from "@gsd/pi-coding-agent";
|
|
14
|
-
import {
|
|
14
|
+
import { Editor, type EditorTheme, Key, matchesKey, Text, truncateToWidth, wrapTextWithAnsi } from "@gsd/pi-tui";
|
|
15
15
|
import { Type } from "@sinclair/typebox";
|
|
16
|
-
import { makeUI, type ProgressStatus } from "./shared/mod.js";
|
|
16
|
+
import { makeUI, maskEditorLine, type ProgressStatus } from "./shared/mod.js";
|
|
17
17
|
import { parseSecretsManifest, formatSecretsManifest } from "./gsd/files.js";
|
|
18
18
|
import { resolveMilestoneFile } from "./gsd/paths.js";
|
|
19
19
|
import type { SecretsManifestEntry } from "./gsd/types.js";
|
|
@@ -42,39 +42,6 @@ function maskPreview(value: string): string {
|
|
|
42
42
|
return `${value.slice(0, 4)}${"*".repeat(Math.max(4, value.length - 8))}${value.slice(-4)}`;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
/**
|
|
46
|
-
* Replace editor visible text with masked characters while preserving ANSI cursor/sequencer codes.
|
|
47
|
-
*/
|
|
48
|
-
function maskEditorLine(line: string): string {
|
|
49
|
-
// Keep border / metadata lines readable.
|
|
50
|
-
if (line.startsWith("─")) {
|
|
51
|
-
return line;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
let output = "";
|
|
55
|
-
let i = 0;
|
|
56
|
-
while (i < line.length) {
|
|
57
|
-
if (line.startsWith(CURSOR_MARKER, i)) {
|
|
58
|
-
output += CURSOR_MARKER;
|
|
59
|
-
i += CURSOR_MARKER.length;
|
|
60
|
-
continue;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const ansiMatch = /^\x1b\[[0-9;]*m/.exec(line.slice(i));
|
|
64
|
-
if (ansiMatch) {
|
|
65
|
-
output += ansiMatch[0];
|
|
66
|
-
i += ansiMatch[0].length;
|
|
67
|
-
continue;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const ch = line[i] as string;
|
|
71
|
-
output += ch === " " ? " " : "*";
|
|
72
|
-
i += 1;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
return output;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
45
|
function shellEscapeSingle(value: string): string {
|
|
79
46
|
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
80
47
|
}
|
|
@@ -411,6 +411,13 @@ export default function (pi: ExtensionAPI) {
|
|
|
411
411
|
},
|
|
412
412
|
});
|
|
413
413
|
|
|
414
|
+
// ── Session cleanup ─────────────────────────────────────────────────────
|
|
415
|
+
|
|
416
|
+
pi.on("session_shutdown", async () => {
|
|
417
|
+
resultCache.clear();
|
|
418
|
+
client = null;
|
|
419
|
+
});
|
|
420
|
+
|
|
414
421
|
// ── Startup notification ─────────────────────────────────────────────────
|
|
415
422
|
|
|
416
423
|
pi.on("session_start", async (_event, ctx) => {
|