gsd-pi 2.36.0 → 2.37.0-dev.68605cd
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/dist/resources/extensions/cmux/index.js +321 -0
- package/dist/resources/extensions/cmux/package.json +7 -0
- package/dist/resources/extensions/gsd/auto-dashboard.js +334 -104
- package/dist/resources/extensions/gsd/auto-loop.js +29 -4
- package/dist/resources/extensions/gsd/auto.js +58 -5
- package/dist/resources/extensions/gsd/commands-cmux.js +120 -0
- package/dist/resources/extensions/gsd/commands-prefs-wizard.js +1 -1
- package/dist/resources/extensions/gsd/commands.js +131 -34
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +25 -0
- package/dist/resources/extensions/gsd/git-service.js +9 -1
- package/dist/resources/extensions/gsd/history.js +2 -1
- package/dist/resources/extensions/gsd/index.js +5 -0
- package/dist/resources/extensions/gsd/metrics.js +4 -2
- package/dist/resources/extensions/gsd/notifications.js +10 -1
- package/dist/resources/extensions/gsd/preferences-types.js +2 -0
- package/dist/resources/extensions/gsd/preferences-validation.js +29 -0
- package/dist/resources/extensions/gsd/preferences.js +3 -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/session-lock.js +26 -6
- package/dist/resources/extensions/gsd/templates/preferences.md +6 -0
- package/dist/resources/extensions/search-the-web/native-search.js +45 -4
- package/dist/resources/extensions/shared/format-utils.js +5 -41
- package/dist/resources/extensions/shared/layout-utils.js +46 -0
- package/dist/resources/extensions/shared/mod.js +2 -1
- package/dist/resources/extensions/shared/terminal.js +5 -0
- package/dist/resources/extensions/subagent/index.js +180 -60
- package/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.js +8 -4
- package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-coding-agent/src/core/extensions/loader.ts +8 -4
- 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/cmux/index.ts +384 -0
- package/src/resources/extensions/cmux/package.json +7 -0
- package/src/resources/extensions/gsd/auto-dashboard.ts +363 -116
- package/src/resources/extensions/gsd/auto-loop.ts +66 -6
- package/src/resources/extensions/gsd/auto.ts +77 -5
- package/src/resources/extensions/gsd/commands-cmux.ts +143 -0
- package/src/resources/extensions/gsd/commands-prefs-wizard.ts +1 -1
- package/src/resources/extensions/gsd/commands.ts +139 -32
- package/src/resources/extensions/gsd/docs/preferences-reference.md +25 -0
- package/src/resources/extensions/gsd/git-service.ts +12 -1
- package/src/resources/extensions/gsd/history.ts +2 -1
- package/src/resources/extensions/gsd/index.ts +8 -0
- package/src/resources/extensions/gsd/metrics.ts +4 -2
- package/src/resources/extensions/gsd/notifications.ts +10 -1
- package/src/resources/extensions/gsd/preferences-types.ts +13 -0
- package/src/resources/extensions/gsd/preferences-validation.ts +26 -0
- package/src/resources/extensions/gsd/preferences.ts +4 -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/session-lock.ts +41 -6
- package/src/resources/extensions/gsd/templates/preferences.md +6 -0
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +39 -1
- package/src/resources/extensions/gsd/tests/auto-worktree.test.ts +19 -0
- package/src/resources/extensions/gsd/tests/cmux.test.ts +122 -0
- package/src/resources/extensions/gsd/tests/preferences.test.ts +23 -0
- package/src/resources/extensions/gsd/tests/session-lock-regression.test.ts +45 -0
- package/src/resources/extensions/search-the-web/native-search.ts +50 -4
- package/src/resources/extensions/shared/format-utils.ts +5 -44
- package/src/resources/extensions/shared/layout-utils.ts +49 -0
- package/src/resources/extensions/shared/mod.ts +7 -4
- package/src/resources/extensions/shared/terminal.ts +5 -0
- package/src/resources/extensions/shared/tests/format-utils.test.ts +5 -3
- package/src/resources/extensions/subagent/index.ts +236 -79
|
@@ -15,7 +15,7 @@ import { deriveState } from "./state.js";
|
|
|
15
15
|
import { GSDDashboardOverlay } from "./dashboard-overlay.js";
|
|
16
16
|
import { GSDVisualizerOverlay } from "./visualizer-overlay.js";
|
|
17
17
|
import { showQueue, showDiscuss, showHeadlessMilestoneCreation } from "./guided-flow.js";
|
|
18
|
-
import { startAuto, stopAuto, pauseAuto, isAutoActive, isAutoPaused, isStepMode, stopAutoRemote } from "./auto.js";
|
|
18
|
+
import { startAuto, stopAuto, pauseAuto, isAutoActive, isAutoPaused, isStepMode, stopAutoRemote, checkRemoteAutoSession } from "./auto.js";
|
|
19
19
|
import { dispatchDirectPhase } from "./auto-direct-dispatch.js";
|
|
20
20
|
import { resolveProjectRoot } from "./worktree.js";
|
|
21
21
|
import { assertSafeDirectory } from "./validate-directory.js";
|
|
@@ -49,6 +49,8 @@ import { runEnvironmentChecks } from "./doctor-environment.js";
|
|
|
49
49
|
import { handleLogs } from "./commands-logs.js";
|
|
50
50
|
import { handleStart, handleTemplates, getTemplateCompletions } from "./commands-workflow-templates.js";
|
|
51
51
|
import { readSessionLockData, isSessionLockProcessAlive } from "./session-lock.js";
|
|
52
|
+
import { handleCmux } from "./commands-cmux.js";
|
|
53
|
+
import { showNextAction } from "../shared/mod.js";
|
|
52
54
|
|
|
53
55
|
|
|
54
56
|
/** Resolve the effective project root, accounting for worktree paths. */
|
|
@@ -71,41 +73,93 @@ export function projectRoot(): string {
|
|
|
71
73
|
}
|
|
72
74
|
|
|
73
75
|
/**
|
|
74
|
-
*
|
|
75
|
-
* Returns
|
|
76
|
+
* Guard against starting auto-mode when a remote session is already running.
|
|
77
|
+
* Returns true if the caller should proceed with startAuto, false if handled.
|
|
76
78
|
*/
|
|
77
|
-
function
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
79
|
+
async function guardRemoteSession(
|
|
80
|
+
ctx: ExtensionCommandContext,
|
|
81
|
+
pi: ExtensionAPI,
|
|
82
|
+
): Promise<boolean> {
|
|
83
|
+
// Local session already active — proceed (startAuto handles re-entrant calls)
|
|
84
|
+
if (isAutoActive() || isAutoPaused()) return true;
|
|
85
|
+
|
|
86
|
+
const remote = checkRemoteAutoSession(projectRoot());
|
|
87
|
+
if (!remote.running || !remote.pid) return true;
|
|
88
|
+
|
|
89
|
+
const unitLabel = remote.unitType && remote.unitId
|
|
90
|
+
? `${remote.unitType} (${remote.unitId})`
|
|
91
|
+
: "unknown unit";
|
|
92
|
+
const unitsMsg = remote.completedUnits != null
|
|
93
|
+
? `${remote.completedUnits} units completed`
|
|
94
|
+
: "";
|
|
95
|
+
|
|
96
|
+
const choice = await showNextAction(ctx, {
|
|
97
|
+
title: `Auto-mode is running in another terminal (PID ${remote.pid})`,
|
|
98
|
+
summary: [
|
|
99
|
+
`Currently executing: ${unitLabel}`,
|
|
100
|
+
...(unitsMsg ? [unitsMsg] : []),
|
|
101
|
+
...(remote.startedAt ? [`Started: ${remote.startedAt}`] : []),
|
|
102
|
+
],
|
|
103
|
+
actions: [
|
|
104
|
+
{
|
|
105
|
+
id: "status",
|
|
106
|
+
label: "View status",
|
|
107
|
+
description: "Show the current GSD progress dashboard.",
|
|
108
|
+
recommended: true,
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
id: "steer",
|
|
112
|
+
label: "Steer the session",
|
|
113
|
+
description: "Use /gsd steer <instruction> to redirect the running session.",
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
id: "stop",
|
|
117
|
+
label: "Stop remote session",
|
|
118
|
+
description: `Send SIGTERM to PID ${remote.pid} to stop it gracefully.`,
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
id: "force",
|
|
122
|
+
label: "Force start (steal lock)",
|
|
123
|
+
description: "Start a new session, terminating the existing one.",
|
|
124
|
+
},
|
|
125
|
+
],
|
|
126
|
+
notYetMessage: "Run /gsd when ready.",
|
|
127
|
+
});
|
|
84
128
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
129
|
+
if (choice === "status") {
|
|
130
|
+
await handleStatus(ctx);
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
if (choice === "steer") {
|
|
134
|
+
ctx.ui.notify(
|
|
135
|
+
"Use /gsd steer <instruction> to redirect the running auto-mode session.\n" +
|
|
136
|
+
"Example: /gsd steer Use Postgres instead of SQLite",
|
|
137
|
+
"info",
|
|
138
|
+
);
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
if (choice === "stop") {
|
|
142
|
+
const result = stopAutoRemote(projectRoot());
|
|
143
|
+
if (result.found) {
|
|
144
|
+
ctx.ui.notify(`Sent stop signal to auto-mode session (PID ${result.pid}). It will shut down gracefully.`, "info");
|
|
145
|
+
} else if (result.error) {
|
|
146
|
+
ctx.ui.notify(`Failed to stop remote auto-mode: ${result.error}`, "error");
|
|
147
|
+
} else {
|
|
148
|
+
ctx.ui.notify("Remote session is no longer running.", "info");
|
|
149
|
+
}
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
if (choice === "force") {
|
|
153
|
+
return true; // Proceed — startAuto will steal the lock
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// "not_yet" or escape
|
|
157
|
+
return false;
|
|
104
158
|
}
|
|
105
159
|
|
|
106
160
|
export function registerGSDCommand(pi: ExtensionAPI): void {
|
|
107
161
|
pi.registerCommand("gsd", {
|
|
108
|
-
description: "GSD — Get Shit Done: /gsd help|start|templates|next|auto|stop|pause|status|visualize|queue|quick|capture|triage|dispatch|history|undo|skip|export|cleanup|mode|prefs|config|keys|hooks|run-hook|skill-health|doctor|forensics|changelog|migrate|remote|steer|knowledge|new-milestone|parallel|update",
|
|
162
|
+
description: "GSD — Get Shit Done: /gsd help|start|templates|next|auto|stop|pause|status|visualize|queue|quick|capture|triage|dispatch|history|undo|skip|export|cleanup|mode|prefs|config|keys|hooks|run-hook|skill-health|doctor|forensics|changelog|migrate|remote|steer|knowledge|new-milestone|parallel|cmux|update",
|
|
109
163
|
getArgumentCompletions: (prefix: string) => {
|
|
110
164
|
const subcommands = [
|
|
111
165
|
{ cmd: "help", desc: "Categorized command reference with descriptions" },
|
|
@@ -114,6 +168,7 @@ export function registerGSDCommand(pi: ExtensionAPI): void {
|
|
|
114
168
|
{ cmd: "stop", desc: "Stop auto mode gracefully" },
|
|
115
169
|
{ cmd: "pause", desc: "Pause auto-mode (preserves state, /gsd auto to resume)" },
|
|
116
170
|
{ cmd: "status", desc: "Progress dashboard" },
|
|
171
|
+
{ cmd: "widget", desc: "Cycle widget: full → small → min → off" },
|
|
117
172
|
{ cmd: "visualize", desc: "Open 10-tab workflow visualizer (progress, timeline, deps, metrics, health, agent, changes, knowledge, captures, export)" },
|
|
118
173
|
{ cmd: "queue", desc: "Queue and reorder future milestones" },
|
|
119
174
|
{ cmd: "quick", desc: "Execute a quick task without full planning overhead" },
|
|
@@ -147,6 +202,7 @@ export function registerGSDCommand(pi: ExtensionAPI): void {
|
|
|
147
202
|
{ cmd: "knowledge", desc: "Add persistent project knowledge (rule, pattern, or lesson)" },
|
|
148
203
|
{ cmd: "new-milestone", desc: "Create a milestone from a specification document (headless)" },
|
|
149
204
|
{ cmd: "parallel", desc: "Parallel milestone orchestration (start, status, stop, merge)" },
|
|
205
|
+
{ cmd: "cmux", desc: "Manage cmux integration (status, sidebar, notifications, splits)" },
|
|
150
206
|
{ cmd: "park", desc: "Park a milestone — skip without deleting" },
|
|
151
207
|
{ cmd: "unpark", desc: "Reactivate a parked milestone" },
|
|
152
208
|
{ cmd: "update", desc: "Update GSD to the latest version" },
|
|
@@ -203,6 +259,38 @@ export function registerGSDCommand(pi: ExtensionAPI): void {
|
|
|
203
259
|
.map((s) => ({ value: `parallel ${s.cmd}`, label: s.cmd, description: s.desc }));
|
|
204
260
|
}
|
|
205
261
|
|
|
262
|
+
if (parts[0] === "cmux") {
|
|
263
|
+
if (parts.length <= 2) {
|
|
264
|
+
const subPrefix = parts[1] ?? "";
|
|
265
|
+
const subs = [
|
|
266
|
+
{ cmd: "status", desc: "Show cmux detection, prefs, and capabilities" },
|
|
267
|
+
{ cmd: "on", desc: "Enable cmux integration" },
|
|
268
|
+
{ cmd: "off", desc: "Disable cmux integration" },
|
|
269
|
+
{ cmd: "notifications", desc: "Toggle cmux desktop notifications" },
|
|
270
|
+
{ cmd: "sidebar", desc: "Toggle cmux sidebar metadata" },
|
|
271
|
+
{ cmd: "splits", desc: "Toggle cmux visual subagent splits" },
|
|
272
|
+
{ cmd: "browser", desc: "Toggle future browser integration flag" },
|
|
273
|
+
];
|
|
274
|
+
return subs
|
|
275
|
+
.filter((s) => s.cmd.startsWith(subPrefix))
|
|
276
|
+
.map((s) => ({ value: `cmux ${s.cmd}`, label: s.cmd, description: s.desc }));
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (parts.length <= 3 && ["notifications", "sidebar", "splits", "browser"].includes(parts[1])) {
|
|
280
|
+
const togglePrefix = parts[2] ?? "";
|
|
281
|
+
return [
|
|
282
|
+
{ cmd: "on", desc: "Enable this cmux area" },
|
|
283
|
+
{ cmd: "off", desc: "Disable this cmux area" },
|
|
284
|
+
]
|
|
285
|
+
.filter((item) => item.cmd.startsWith(togglePrefix))
|
|
286
|
+
.map((item) => ({
|
|
287
|
+
value: `cmux ${parts[1]} ${item.cmd}`,
|
|
288
|
+
label: item.cmd,
|
|
289
|
+
description: item.desc,
|
|
290
|
+
}));
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
206
294
|
if (parts[0] === "setup" && parts.length <= 2) {
|
|
207
295
|
const subPrefix = parts[1] ?? "";
|
|
208
296
|
const subs = [
|
|
@@ -474,6 +562,18 @@ export async function handleGSDCommand(
|
|
|
474
562
|
return;
|
|
475
563
|
}
|
|
476
564
|
|
|
565
|
+
if (trimmed === "widget" || trimmed.startsWith("widget ")) {
|
|
566
|
+
const { cycleWidgetMode, setWidgetMode, getWidgetMode } = await import("./auto-dashboard.js");
|
|
567
|
+
const arg = trimmed.replace(/^widget\s*/, "").trim();
|
|
568
|
+
if (arg === "full" || arg === "small" || arg === "min" || arg === "off") {
|
|
569
|
+
setWidgetMode(arg);
|
|
570
|
+
} else {
|
|
571
|
+
cycleWidgetMode();
|
|
572
|
+
}
|
|
573
|
+
ctx.ui.notify(`Widget: ${getWidgetMode()}`, "info");
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
576
|
+
|
|
477
577
|
if (trimmed === "visualize") {
|
|
478
578
|
await handleVisualize(ctx);
|
|
479
579
|
return;
|
|
@@ -493,6 +593,11 @@ export async function handleGSDCommand(
|
|
|
493
593
|
return;
|
|
494
594
|
}
|
|
495
595
|
|
|
596
|
+
if (trimmed === "cmux" || trimmed.startsWith("cmux ")) {
|
|
597
|
+
await handleCmux(trimmed.replace(/^cmux\s*/, "").trim(), ctx);
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
600
|
+
|
|
496
601
|
if (trimmed === "init") {
|
|
497
602
|
const { detectProjectState } = await import("./detection.js");
|
|
498
603
|
const { showProjectInit, handleReinit } = await import("./init-wizard.js");
|
|
@@ -546,10 +651,10 @@ export async function handleGSDCommand(
|
|
|
546
651
|
await handleDryRun(ctx, projectRoot());
|
|
547
652
|
return;
|
|
548
653
|
}
|
|
549
|
-
if (notifyRemoteAutoActive(ctx, projectRoot())) return;
|
|
550
654
|
const verboseMode = trimmed.includes("--verbose");
|
|
551
655
|
const debugMode = trimmed.includes("--debug");
|
|
552
656
|
if (debugMode) enableDebug(projectRoot());
|
|
657
|
+
if (!(await guardRemoteSession(ctx, pi))) return;
|
|
553
658
|
await startAuto(ctx, pi, projectRoot(), verboseMode, { step: true });
|
|
554
659
|
return;
|
|
555
660
|
}
|
|
@@ -558,6 +663,7 @@ export async function handleGSDCommand(
|
|
|
558
663
|
const verboseMode = trimmed.includes("--verbose");
|
|
559
664
|
const debugMode = trimmed.includes("--debug");
|
|
560
665
|
if (debugMode) enableDebug(projectRoot());
|
|
666
|
+
if (!(await guardRemoteSession(ctx, pi))) return;
|
|
561
667
|
await startAuto(ctx, pi, projectRoot(), verboseMode);
|
|
562
668
|
return;
|
|
563
669
|
}
|
|
@@ -941,7 +1047,7 @@ Examples:
|
|
|
941
1047
|
}
|
|
942
1048
|
|
|
943
1049
|
if (trimmed === "") {
|
|
944
|
-
if (
|
|
1050
|
+
if (!(await guardRemoteSession(ctx, pi))) return;
|
|
945
1051
|
await startAuto(ctx, pi, projectRoot(), false, { step: true });
|
|
946
1052
|
return;
|
|
947
1053
|
}
|
|
@@ -996,6 +1102,7 @@ function showHelp(ctx: ExtensionCommandContext): void {
|
|
|
996
1102
|
" /gsd setup Global setup status [llm|search|remote|keys|prefs]",
|
|
997
1103
|
" /gsd mode Set workflow mode (solo/team) [global|project]",
|
|
998
1104
|
" /gsd prefs Manage preferences [global|project|status|wizard|setup|import-claude]",
|
|
1105
|
+
" /gsd cmux Manage cmux integration [status|on|off|notifications|sidebar|splits|browser]",
|
|
999
1106
|
" /gsd config Set API keys for external tools",
|
|
1000
1107
|
" /gsd keys API key manager [list|add|remove|test|rotate|doctor]",
|
|
1001
1108
|
" /gsd hooks Show post-unit hook configuration",
|
|
@@ -173,6 +173,13 @@ Setting `prefer_skills: []` does **not** disable skill discovery — it just mea
|
|
|
173
173
|
- `on_milestone`: boolean — notify when a milestone finishes. Default: `true`.
|
|
174
174
|
- `on_attention`: boolean — notify when manual attention is needed. Default: `true`.
|
|
175
175
|
|
|
176
|
+
- `cmux`: configures cmux terminal integration when GSD is running inside a cmux workspace. Keys:
|
|
177
|
+
- `enabled`: boolean — master toggle for cmux integration. Default: `false`.
|
|
178
|
+
- `notifications`: boolean — route desktop notifications through cmux. Default: `true` when enabled.
|
|
179
|
+
- `sidebar`: boolean — publish status, progress, and log metadata to the cmux sidebar. Default: `true` when enabled.
|
|
180
|
+
- `splits`: boolean — run supported subagent work in visible cmux splits. Default: `false`.
|
|
181
|
+
- `browser`: boolean — reserve the future browser integration flag. Default: `false`.
|
|
182
|
+
|
|
176
183
|
- `dynamic_routing`: configures the dynamic model router that adjusts model selection based on task complexity. Keys:
|
|
177
184
|
- `enabled`: boolean — enable dynamic routing. Default: `false`.
|
|
178
185
|
- `tier_models`: object — model overrides per complexity tier. Keys: `light`, `standard`, `heavy`. Values are model ID strings.
|
|
@@ -477,6 +484,24 @@ Disables per-unit completion notifications (noisy in long runs) while keeping er
|
|
|
477
484
|
|
|
478
485
|
---
|
|
479
486
|
|
|
487
|
+
## cmux Example
|
|
488
|
+
|
|
489
|
+
```yaml
|
|
490
|
+
---
|
|
491
|
+
version: 1
|
|
492
|
+
cmux:
|
|
493
|
+
enabled: true
|
|
494
|
+
notifications: true
|
|
495
|
+
sidebar: true
|
|
496
|
+
splits: true
|
|
497
|
+
browser: false
|
|
498
|
+
---
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
Enables cmux-aware notifications, sidebar metadata, and visible subagent splits when GSD is running inside a cmux terminal.
|
|
502
|
+
|
|
503
|
+
---
|
|
504
|
+
|
|
480
505
|
## Post-Unit Hooks Example
|
|
481
506
|
|
|
482
507
|
```yaml
|
|
@@ -479,9 +479,20 @@ export class GitServiceImpl {
|
|
|
479
479
|
|
|
480
480
|
const wtName = detectWorktreeName(this.basePath);
|
|
481
481
|
if (wtName) {
|
|
482
|
+
// Auto-mode worktrees use milestone/<MID> branches (wtName = milestone ID)
|
|
483
|
+
const milestoneBranch = `milestone/${wtName}`;
|
|
484
|
+
const currentBranch = nativeGetCurrentBranch(this.basePath);
|
|
485
|
+
|
|
486
|
+
// If we're on a milestone/<MID> branch, use it (auto-mode case)
|
|
487
|
+
if (currentBranch.startsWith("milestone/")) {
|
|
488
|
+
return currentBranch;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// Otherwise check for manual worktree branch (worktree/<name>)
|
|
482
492
|
const wtBranch = `worktree/${wtName}`;
|
|
483
493
|
if (nativeBranchExists(this.basePath, wtBranch)) return wtBranch;
|
|
484
|
-
|
|
494
|
+
|
|
495
|
+
return currentBranch;
|
|
485
496
|
}
|
|
486
497
|
|
|
487
498
|
// Repo-level default detection: origin/HEAD → main → master → current branch.
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
// Human-readable display of past auto-mode unit executions.
|
|
3
3
|
|
|
4
4
|
import type { ExtensionCommandContext } from "@gsd/pi-coding-agent";
|
|
5
|
-
import { formatDuration,
|
|
5
|
+
import { formatDuration, truncateWithEllipsis } from "../shared/format-utils.js";
|
|
6
|
+
import { padRight } from "../shared/layout-utils.js";
|
|
6
7
|
import {
|
|
7
8
|
getLedger, getProjectTotals, formatCost, formatTokenCount,
|
|
8
9
|
aggregateBySlice, aggregateByPhase, aggregateByModel, loadLedgerFromDisk,
|
|
@@ -65,6 +65,7 @@ import { pauseAutoForProviderError, classifyProviderError } from "./provider-err
|
|
|
65
65
|
import { toPosixPath } from "../shared/mod.js";
|
|
66
66
|
import { isParallelActive, shutdownParallel } from "./parallel-orchestrator.js";
|
|
67
67
|
import { DEFAULT_BASH_TIMEOUT_SECS } from "./constants.js";
|
|
68
|
+
import { markCmuxPromptShown, shouldPromptToEnableCmux } from "../cmux/index.js";
|
|
68
69
|
|
|
69
70
|
// ── Agent Instructions (DEPRECATED) ──────────────────────────────────────
|
|
70
71
|
// agent-instructions.md is deprecated. Use AGENTS.md or CLAUDE.md instead.
|
|
@@ -623,6 +624,13 @@ export default function (pi: ExtensionAPI) {
|
|
|
623
624
|
const stopContextTimer = debugTime("context-inject");
|
|
624
625
|
const systemContent = loadPrompt("system");
|
|
625
626
|
const loadedPreferences = loadEffectiveGSDPreferences();
|
|
627
|
+
if (shouldPromptToEnableCmux(loadedPreferences?.preferences)) {
|
|
628
|
+
markCmuxPromptShown();
|
|
629
|
+
ctx.ui.notify(
|
|
630
|
+
"cmux detected. Run /gsd cmux on to enable sidebar metadata, notifications, and visual subagent splits for this project.",
|
|
631
|
+
"info",
|
|
632
|
+
);
|
|
633
|
+
}
|
|
626
634
|
let preferenceBlock = "";
|
|
627
635
|
if (loadedPreferences) {
|
|
628
636
|
const cwd = process.cwd();
|
|
@@ -20,8 +20,10 @@ import { getAndClearSkills } from "./skill-telemetry.js";
|
|
|
20
20
|
import { loadJsonFile, loadJsonFileOrNull, saveJsonFile } from "./json-persistence.js";
|
|
21
21
|
import { parseUnitId } from "./unit-id.js";
|
|
22
22
|
|
|
23
|
-
// Re-export from shared —
|
|
24
|
-
|
|
23
|
+
// Re-export from shared — import directly from format-utils to avoid pulling
|
|
24
|
+
// in the full barrel (mod.js → ui.js → @gsd/pi-tui) which breaks when loaded
|
|
25
|
+
// outside jiti's alias resolution (e.g. dynamic import in auto-loop reports).
|
|
26
|
+
export { formatTokenCount } from "../shared/format-utils.js";
|
|
25
27
|
|
|
26
28
|
// ─── Types ────────────────────────────────────────────────────────────────────
|
|
27
29
|
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { execFileSync } from "node:child_process";
|
|
5
5
|
import type { NotificationPreferences } from "./types.js";
|
|
6
6
|
import { loadEffectiveGSDPreferences } from "./preferences.js";
|
|
7
|
+
import { CmuxClient, emitOsc777Notification, resolveCmuxConfig } from "../cmux/index.js";
|
|
7
8
|
|
|
8
9
|
export type NotifyLevel = "info" | "success" | "warning" | "error";
|
|
9
10
|
export type NotificationKind = "complete" | "error" | "budget" | "milestone" | "attention";
|
|
@@ -23,7 +24,15 @@ export function sendDesktopNotification(
|
|
|
23
24
|
level: NotifyLevel = "info",
|
|
24
25
|
kind: NotificationKind = "complete",
|
|
25
26
|
): void {
|
|
26
|
-
|
|
27
|
+
const loaded = loadEffectiveGSDPreferences()?.preferences;
|
|
28
|
+
if (!shouldSendDesktopNotification(kind, loaded?.notifications)) return;
|
|
29
|
+
|
|
30
|
+
const cmux = resolveCmuxConfig(loaded);
|
|
31
|
+
if (cmux.notifications) {
|
|
32
|
+
const delivered = CmuxClient.fromPreferences(loaded).notify(title, message);
|
|
33
|
+
if (delivered) return;
|
|
34
|
+
emitOsc777Notification(title, message);
|
|
35
|
+
}
|
|
27
36
|
|
|
28
37
|
try {
|
|
29
38
|
const command = buildDesktopNotificationCommand(process.platform, title, message, level);
|
|
@@ -68,6 +68,7 @@ export const KNOWN_PREFERENCE_KEYS = new Set<string>([
|
|
|
68
68
|
"budget_enforcement",
|
|
69
69
|
"context_pause_threshold",
|
|
70
70
|
"notifications",
|
|
71
|
+
"cmux",
|
|
71
72
|
"remote_questions",
|
|
72
73
|
"git",
|
|
73
74
|
"post_unit_hooks",
|
|
@@ -84,6 +85,7 @@ export const KNOWN_PREFERENCE_KEYS = new Set<string>([
|
|
|
84
85
|
"search_provider",
|
|
85
86
|
"compression_strategy",
|
|
86
87
|
"context_selection",
|
|
88
|
+
"widget_mode",
|
|
87
89
|
]);
|
|
88
90
|
|
|
89
91
|
/** Canonical list of all dispatch unit types. */
|
|
@@ -164,6 +166,14 @@ export interface RemoteQuestionsConfig {
|
|
|
164
166
|
poll_interval_seconds?: number; // clamped to 2-30
|
|
165
167
|
}
|
|
166
168
|
|
|
169
|
+
export interface CmuxPreferences {
|
|
170
|
+
enabled?: boolean;
|
|
171
|
+
notifications?: boolean;
|
|
172
|
+
sidebar?: boolean;
|
|
173
|
+
splits?: boolean;
|
|
174
|
+
browser?: boolean;
|
|
175
|
+
}
|
|
176
|
+
|
|
167
177
|
export interface GSDPreferences {
|
|
168
178
|
version?: number;
|
|
169
179
|
mode?: WorkflowMode;
|
|
@@ -182,6 +192,7 @@ export interface GSDPreferences {
|
|
|
182
192
|
budget_enforcement?: BudgetEnforcementMode;
|
|
183
193
|
context_pause_threshold?: number;
|
|
184
194
|
notifications?: NotificationPreferences;
|
|
195
|
+
cmux?: CmuxPreferences;
|
|
185
196
|
remote_questions?: RemoteQuestionsConfig;
|
|
186
197
|
git?: GitPreferences;
|
|
187
198
|
post_unit_hooks?: PostUnitHookConfig[];
|
|
@@ -202,6 +213,8 @@ export interface GSDPreferences {
|
|
|
202
213
|
compression_strategy?: CompressionStrategy;
|
|
203
214
|
/** Context selection mode for file inlining. "full" inlines entire files, "smart" uses semantic chunking. Default derived from token profile. */
|
|
204
215
|
context_selection?: ContextSelectionMode;
|
|
216
|
+
/** Default widget display mode for auto-mode dashboard. "full" | "small" | "min" | "off". Default: "full". */
|
|
217
|
+
widget_mode?: "full" | "small" | "min" | "off";
|
|
205
218
|
}
|
|
206
219
|
|
|
207
220
|
export interface LoadedGSDPreferences {
|
|
@@ -242,6 +242,32 @@ export function validatePreferences(preferences: GSDPreferences): {
|
|
|
242
242
|
}
|
|
243
243
|
}
|
|
244
244
|
|
|
245
|
+
// ─── Cmux ───────────────────────────────────────────────────────────────
|
|
246
|
+
if (preferences.cmux !== undefined) {
|
|
247
|
+
if (preferences.cmux && typeof preferences.cmux === "object") {
|
|
248
|
+
const cmux = preferences.cmux as Record<string, unknown>;
|
|
249
|
+
const validatedCmux: NonNullable<GSDPreferences["cmux"]> = {};
|
|
250
|
+
if (cmux.enabled !== undefined) validatedCmux.enabled = !!cmux.enabled;
|
|
251
|
+
if (cmux.notifications !== undefined) validatedCmux.notifications = !!cmux.notifications;
|
|
252
|
+
if (cmux.sidebar !== undefined) validatedCmux.sidebar = !!cmux.sidebar;
|
|
253
|
+
if (cmux.splits !== undefined) validatedCmux.splits = !!cmux.splits;
|
|
254
|
+
if (cmux.browser !== undefined) validatedCmux.browser = !!cmux.browser;
|
|
255
|
+
|
|
256
|
+
const knownCmuxKeys = new Set(["enabled", "notifications", "sidebar", "splits", "browser"]);
|
|
257
|
+
for (const key of Object.keys(cmux)) {
|
|
258
|
+
if (!knownCmuxKeys.has(key)) {
|
|
259
|
+
warnings.push(`unknown cmux key "${key}" — ignored`);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (Object.keys(validatedCmux).length > 0) {
|
|
264
|
+
validated.cmux = validatedCmux;
|
|
265
|
+
}
|
|
266
|
+
} else {
|
|
267
|
+
errors.push("cmux must be an object");
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
245
271
|
// ─── Remote Questions ───────────────────────────────────────────────
|
|
246
272
|
if (preferences.remote_questions !== undefined) {
|
|
247
273
|
if (preferences.remote_questions && typeof preferences.remote_questions === "object") {
|
|
@@ -45,6 +45,7 @@ export type {
|
|
|
45
45
|
SkillDiscoveryMode,
|
|
46
46
|
AutoSupervisorConfig,
|
|
47
47
|
RemoteQuestionsConfig,
|
|
48
|
+
CmuxPreferences,
|
|
48
49
|
GSDPreferences,
|
|
49
50
|
LoadedGSDPreferences,
|
|
50
51
|
SkillResolution,
|
|
@@ -241,6 +242,9 @@ function mergePreferences(base: GSDPreferences, override: GSDPreferences): GSDPr
|
|
|
241
242
|
notifications: (base.notifications || override.notifications)
|
|
242
243
|
? { ...(base.notifications ?? {}), ...(override.notifications ?? {}) }
|
|
243
244
|
: undefined,
|
|
245
|
+
cmux: (base.cmux || override.cmux)
|
|
246
|
+
? { ...(base.cmux ?? {}), ...(override.cmux ?? {}) }
|
|
247
|
+
: undefined,
|
|
244
248
|
remote_questions: override.remote_questions
|
|
245
249
|
? { ...(base.remote_questions ?? {}), ...override.remote_questions }
|
|
246
250
|
: base.remote_questions,
|
|
@@ -25,9 +25,10 @@ Then research the codebase and relevant technologies. Narrate key findings and s
|
|
|
25
25
|
2. **Skill Discovery ({{skillDiscoveryMode}}):**{{skillDiscoveryInstructions}}
|
|
26
26
|
3. Explore relevant code. For small/familiar codebases, use `rg`, `find`, and targeted reads. For large or unfamiliar codebases, use `scout` to build a broad map efficiently before diving in.
|
|
27
27
|
4. Use `resolve_library` / `get_library_docs` for unfamiliar libraries — skip this for libraries already used in the codebase
|
|
28
|
-
5. Use
|
|
29
|
-
6.
|
|
30
|
-
7.
|
|
28
|
+
5. **Web search budget:** You have a limited budget of web searches (max ~15 per session). Use them strategically — prefer `resolve_library` / `get_library_docs` for library documentation. Do NOT repeat the same or similar queries. If a search didn't find what you need, rephrase once or move on. Target 3-5 total web searches for a typical research unit.
|
|
29
|
+
6. Use the **Research** output template from the inlined context above — include only sections that have real content
|
|
30
|
+
7. If `.gsd/REQUIREMENTS.md` exists, research against it. Identify which Active requirements are table stakes, likely omissions, overbuilt risks, or domain-standard behaviors the user may or may not want.
|
|
31
|
+
8. Write `{{outputPath}}`
|
|
31
32
|
|
|
32
33
|
## Strategic Questions to Answer
|
|
33
34
|
|
|
@@ -46,8 +46,9 @@ Research what this slice needs. Narrate key findings and surprises as you go —
|
|
|
46
46
|
2. **Skill Discovery ({{skillDiscoveryMode}}):**{{skillDiscoveryInstructions}}
|
|
47
47
|
3. Explore relevant code for this slice's scope. For targeted exploration, use `rg`, `find`, and reads. For broad or unfamiliar subsystems, use `scout` to map the relevant area first.
|
|
48
48
|
4. Use `resolve_library` / `get_library_docs` for unfamiliar libraries — skip this for libraries already used in the codebase
|
|
49
|
-
5.
|
|
50
|
-
6.
|
|
49
|
+
5. **Web search budget:** You have a limited budget of web searches (max ~15 per session). Use them strategically — prefer `resolve_library` / `get_library_docs` for library documentation. Do NOT repeat the same or similar queries. If a search didn't find what you need, rephrase once or move on. Target 3-5 total web searches for a typical research unit.
|
|
50
|
+
6. Use the **Research** output template from the inlined context above — include only sections that have real content. The template is already inlined above; do NOT attempt to read any template file from disk (there is no `templates/SLICE-RESEARCH.md` — the correct template is already present in this prompt).
|
|
51
|
+
7. Write `{{outputPath}}`
|
|
51
52
|
|
|
52
53
|
The slice directory already exists at `{{slicePath}}/`. Do NOT mkdir — just write the file.
|
|
53
54
|
|
|
@@ -40,6 +40,19 @@ export type SessionLockResult =
|
|
|
40
40
|
| { acquired: true }
|
|
41
41
|
| { acquired: false; reason: string; existingPid?: number };
|
|
42
42
|
|
|
43
|
+
export type SessionLockFailureReason =
|
|
44
|
+
| "compromised"
|
|
45
|
+
| "missing-metadata"
|
|
46
|
+
| "pid-mismatch";
|
|
47
|
+
|
|
48
|
+
export interface SessionLockStatus {
|
|
49
|
+
valid: boolean;
|
|
50
|
+
failureReason?: SessionLockFailureReason;
|
|
51
|
+
existingPid?: number;
|
|
52
|
+
expectedPid?: number;
|
|
53
|
+
recovered?: boolean;
|
|
54
|
+
}
|
|
55
|
+
|
|
43
56
|
// ─── Module State ───────────────────────────────────────────────────────────
|
|
44
57
|
|
|
45
58
|
/** Release function from proper-lockfile — calling it releases the OS lock. */
|
|
@@ -368,7 +381,7 @@ export function updateSessionLock(
|
|
|
368
381
|
*
|
|
369
382
|
* This is called periodically during the dispatch loop.
|
|
370
383
|
*/
|
|
371
|
-
export function
|
|
384
|
+
export function getSessionLockStatus(basePath: string): SessionLockStatus {
|
|
372
385
|
// Lock was compromised by proper-lockfile (mtime drift from sleep, stall, etc.)
|
|
373
386
|
if (_lockCompromised) {
|
|
374
387
|
// Recovery gate (#1512): Before declaring the lock lost, check if the lock
|
|
@@ -385,18 +398,23 @@ export function validateSessionLock(basePath: string): boolean {
|
|
|
385
398
|
process.stderr.write(
|
|
386
399
|
`[gsd] Lock recovered after onCompromised — lock file PID matched, re-acquired.\n`,
|
|
387
400
|
);
|
|
388
|
-
return true;
|
|
401
|
+
return { valid: true, recovered: true };
|
|
389
402
|
}
|
|
390
403
|
} catch {
|
|
391
404
|
// Re-acquisition failed — fall through to return false
|
|
392
405
|
}
|
|
393
406
|
}
|
|
394
|
-
return
|
|
407
|
+
return {
|
|
408
|
+
valid: false,
|
|
409
|
+
failureReason: "compromised",
|
|
410
|
+
existingPid: existing?.pid,
|
|
411
|
+
expectedPid: process.pid,
|
|
412
|
+
};
|
|
395
413
|
}
|
|
396
414
|
|
|
397
415
|
// If we have an OS-level lock, we're still the owner
|
|
398
416
|
if (_releaseFunction && _lockedPath === basePath) {
|
|
399
|
-
return true;
|
|
417
|
+
return { valid: true };
|
|
400
418
|
}
|
|
401
419
|
|
|
402
420
|
// Fallback: check the lock file PID
|
|
@@ -404,10 +422,27 @@ export function validateSessionLock(basePath: string): boolean {
|
|
|
404
422
|
const existing = readExistingLockData(lp);
|
|
405
423
|
if (!existing) {
|
|
406
424
|
// Lock file was deleted — we lost ownership
|
|
407
|
-
return
|
|
425
|
+
return {
|
|
426
|
+
valid: false,
|
|
427
|
+
failureReason: "missing-metadata",
|
|
428
|
+
expectedPid: process.pid,
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
if (existing.pid !== process.pid) {
|
|
433
|
+
return {
|
|
434
|
+
valid: false,
|
|
435
|
+
failureReason: "pid-mismatch",
|
|
436
|
+
existingPid: existing.pid,
|
|
437
|
+
expectedPid: process.pid,
|
|
438
|
+
};
|
|
408
439
|
}
|
|
409
440
|
|
|
410
|
-
return
|
|
441
|
+
return { valid: true };
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
export function validateSessionLock(basePath: string): boolean {
|
|
445
|
+
return getSessionLockStatus(basePath).valid;
|
|
411
446
|
}
|
|
412
447
|
|
|
413
448
|
/**
|