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,6 +15,7 @@ import type { ExtensionAPI, ExtensionContext } from "@gsd/pi-coding-agent";
|
|
|
15
15
|
import type { AutoSession } from "./auto/session.js";
|
|
16
16
|
import { NEW_SESSION_TIMEOUT_MS } from "./auto/session.js";
|
|
17
17
|
import type { GSDPreferences } from "./preferences.js";
|
|
18
|
+
import type { SessionLockStatus } from "./session-lock.js";
|
|
18
19
|
import type { GSDState } from "./types.js";
|
|
19
20
|
import type { CloseoutOptions } from "./auto-unit-closeout.js";
|
|
20
21
|
import type { PostUnitContext } from "./auto-post-unit.js";
|
|
@@ -25,6 +26,7 @@ import type {
|
|
|
25
26
|
import type { DispatchAction } from "./auto-dispatch.js";
|
|
26
27
|
import type { WorktreeResolver } from "./worktree-resolver.js";
|
|
27
28
|
import { debugLog } from "./debug-logger.js";
|
|
29
|
+
import type { CmuxLogLevel } from "../cmux/index.js";
|
|
28
30
|
|
|
29
31
|
/**
|
|
30
32
|
* Maximum total loop iterations before forced stop. Prevents runaway loops
|
|
@@ -276,6 +278,12 @@ export interface LoopDeps {
|
|
|
276
278
|
unitId: string,
|
|
277
279
|
state: GSDState,
|
|
278
280
|
) => void;
|
|
281
|
+
syncCmuxSidebar: (preferences: GSDPreferences | undefined, state: GSDState) => void;
|
|
282
|
+
logCmuxEvent: (
|
|
283
|
+
preferences: GSDPreferences | undefined,
|
|
284
|
+
message: string,
|
|
285
|
+
level?: CmuxLogLevel,
|
|
286
|
+
) => void;
|
|
279
287
|
|
|
280
288
|
// State and cache functions
|
|
281
289
|
invalidateAllCaches: () => void;
|
|
@@ -300,7 +308,7 @@ export interface LoopDeps {
|
|
|
300
308
|
checkResourcesStale: (version: string | null) => string | null;
|
|
301
309
|
|
|
302
310
|
// Session lock
|
|
303
|
-
validateSessionLock: (basePath: string) =>
|
|
311
|
+
validateSessionLock: (basePath: string) => SessionLockStatus;
|
|
304
312
|
updateSessionLock: (
|
|
305
313
|
basePath: string,
|
|
306
314
|
unitType: string,
|
|
@@ -308,7 +316,10 @@ export interface LoopDeps {
|
|
|
308
316
|
completedUnits: number,
|
|
309
317
|
sessionFile?: string,
|
|
310
318
|
) => void;
|
|
311
|
-
handleLostSessionLock: (
|
|
319
|
+
handleLostSessionLock: (
|
|
320
|
+
ctx?: ExtensionContext,
|
|
321
|
+
lockStatus?: SessionLockStatus,
|
|
322
|
+
) => void;
|
|
312
323
|
|
|
313
324
|
// Milestone transition functions
|
|
314
325
|
sendDesktopNotification: (
|
|
@@ -552,10 +563,24 @@ export async function autoLoop(
|
|
|
552
563
|
try {
|
|
553
564
|
// ── Blanket try/catch: one bad iteration must not kill the session
|
|
554
565
|
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
566
|
+
const sessionLockBase = deps.lockBase();
|
|
567
|
+
if (sessionLockBase) {
|
|
568
|
+
const lockStatus = deps.validateSessionLock(sessionLockBase);
|
|
569
|
+
if (!lockStatus.valid) {
|
|
570
|
+
debugLog("autoLoop", {
|
|
571
|
+
phase: "session-lock-invalid",
|
|
572
|
+
reason: lockStatus.failureReason ?? "unknown",
|
|
573
|
+
existingPid: lockStatus.existingPid,
|
|
574
|
+
expectedPid: lockStatus.expectedPid,
|
|
575
|
+
});
|
|
576
|
+
deps.handleLostSessionLock(ctx, lockStatus);
|
|
577
|
+
debugLog("autoLoop", {
|
|
578
|
+
phase: "exit",
|
|
579
|
+
reason: "session-lock-lost",
|
|
580
|
+
detail: lockStatus.failureReason ?? "unknown",
|
|
581
|
+
});
|
|
582
|
+
break;
|
|
583
|
+
}
|
|
559
584
|
}
|
|
560
585
|
|
|
561
586
|
// ── Phase 1: Pre-dispatch ───────────────────────────────────────────
|
|
@@ -609,6 +634,7 @@ export async function autoLoop(
|
|
|
609
634
|
|
|
610
635
|
// Derive state
|
|
611
636
|
let state = await deps.deriveState(s.basePath);
|
|
637
|
+
deps.syncCmuxSidebar(deps.loadEffectiveGSDPreferences()?.preferences, state);
|
|
612
638
|
let mid = state.activeMilestone?.id;
|
|
613
639
|
let midTitle = state.activeMilestone?.title;
|
|
614
640
|
debugLog("autoLoop", {
|
|
@@ -630,6 +656,11 @@ export async function autoLoop(
|
|
|
630
656
|
"success",
|
|
631
657
|
"milestone",
|
|
632
658
|
);
|
|
659
|
+
deps.logCmuxEvent(
|
|
660
|
+
deps.loadEffectiveGSDPreferences()?.preferences,
|
|
661
|
+
`Milestone ${s.currentMilestoneId} complete. Advancing to ${mid}.`,
|
|
662
|
+
"success",
|
|
663
|
+
);
|
|
633
664
|
|
|
634
665
|
const vizPrefs = deps.loadEffectiveGSDPreferences()?.preferences;
|
|
635
666
|
if (vizPrefs?.auto_visualize) {
|
|
@@ -767,12 +798,18 @@ export async function autoLoop(
|
|
|
767
798
|
"success",
|
|
768
799
|
"milestone",
|
|
769
800
|
);
|
|
801
|
+
deps.logCmuxEvent(
|
|
802
|
+
deps.loadEffectiveGSDPreferences()?.preferences,
|
|
803
|
+
"All milestones complete.",
|
|
804
|
+
"success",
|
|
805
|
+
);
|
|
770
806
|
await deps.stopAuto(ctx, pi, "All milestones complete");
|
|
771
807
|
} else if (state.phase === "blocked") {
|
|
772
808
|
const blockerMsg = `Blocked: ${state.blockers.join(", ")}`;
|
|
773
809
|
await deps.stopAuto(ctx, pi, blockerMsg);
|
|
774
810
|
ctx.ui.notify(`${blockerMsg}. Fix and run /gsd auto.`, "warning");
|
|
775
811
|
deps.sendDesktopNotification("GSD", blockerMsg, "error", "attention");
|
|
812
|
+
deps.logCmuxEvent(deps.loadEffectiveGSDPreferences()?.preferences, blockerMsg, "error");
|
|
776
813
|
} else {
|
|
777
814
|
const ids = incomplete.map((m: { id: string }) => m.id).join(", ");
|
|
778
815
|
const diag = `basePath=${s.basePath}, milestones=[${state.registry.map((m: { id: string; status: string }) => `${m.id}:${m.status}`).join(", ")}], phase=${state.phase}`;
|
|
@@ -850,6 +887,11 @@ export async function autoLoop(
|
|
|
850
887
|
"success",
|
|
851
888
|
"milestone",
|
|
852
889
|
);
|
|
890
|
+
deps.logCmuxEvent(
|
|
891
|
+
deps.loadEffectiveGSDPreferences()?.preferences,
|
|
892
|
+
`Milestone ${mid} complete.`,
|
|
893
|
+
"success",
|
|
894
|
+
);
|
|
853
895
|
await deps.stopAuto(ctx, pi, `Milestone ${mid} complete`);
|
|
854
896
|
debugLog("autoLoop", { phase: "exit", reason: "milestone-complete" });
|
|
855
897
|
break;
|
|
@@ -871,6 +913,7 @@ export async function autoLoop(
|
|
|
871
913
|
await deps.stopAuto(ctx, pi, blockerMsg);
|
|
872
914
|
ctx.ui.notify(`${blockerMsg}. Fix and run /gsd auto.`, "warning");
|
|
873
915
|
deps.sendDesktopNotification("GSD", blockerMsg, "error", "attention");
|
|
916
|
+
deps.logCmuxEvent(deps.loadEffectiveGSDPreferences()?.preferences, blockerMsg, "error");
|
|
874
917
|
debugLog("autoLoop", { phase: "exit", reason: "blocked" });
|
|
875
918
|
break;
|
|
876
919
|
}
|
|
@@ -914,12 +957,14 @@ export async function autoLoop(
|
|
|
914
957
|
"warning",
|
|
915
958
|
);
|
|
916
959
|
deps.sendDesktopNotification("GSD", msg, "warning", "budget");
|
|
960
|
+
deps.logCmuxEvent(prefs, msg, "warning");
|
|
917
961
|
await deps.pauseAuto(ctx, pi);
|
|
918
962
|
debugLog("autoLoop", { phase: "exit", reason: "budget-pause" });
|
|
919
963
|
break;
|
|
920
964
|
}
|
|
921
965
|
ctx.ui.notify(`${msg} Continuing (enforcement: warn).`, "warning");
|
|
922
966
|
deps.sendDesktopNotification("GSD", msg, "warning", "budget");
|
|
967
|
+
deps.logCmuxEvent(prefs, msg, "warning");
|
|
923
968
|
} else if (newBudgetAlertLevel === 90) {
|
|
924
969
|
s.lastBudgetAlertLevel =
|
|
925
970
|
newBudgetAlertLevel as AutoSession["lastBudgetAlertLevel"];
|
|
@@ -933,6 +978,11 @@ export async function autoLoop(
|
|
|
933
978
|
"warning",
|
|
934
979
|
"budget",
|
|
935
980
|
);
|
|
981
|
+
deps.logCmuxEvent(
|
|
982
|
+
prefs,
|
|
983
|
+
`Budget 90%: ${deps.formatCost(totalCost)} / ${deps.formatCost(budgetCeiling)}`,
|
|
984
|
+
"warning",
|
|
985
|
+
);
|
|
936
986
|
} else if (newBudgetAlertLevel === 80) {
|
|
937
987
|
s.lastBudgetAlertLevel =
|
|
938
988
|
newBudgetAlertLevel as AutoSession["lastBudgetAlertLevel"];
|
|
@@ -946,6 +996,11 @@ export async function autoLoop(
|
|
|
946
996
|
"warning",
|
|
947
997
|
"budget",
|
|
948
998
|
);
|
|
999
|
+
deps.logCmuxEvent(
|
|
1000
|
+
prefs,
|
|
1001
|
+
`Budget 80%: ${deps.formatCost(totalCost)} / ${deps.formatCost(budgetCeiling)}`,
|
|
1002
|
+
"warning",
|
|
1003
|
+
);
|
|
949
1004
|
} else if (newBudgetAlertLevel === 75) {
|
|
950
1005
|
s.lastBudgetAlertLevel =
|
|
951
1006
|
newBudgetAlertLevel as AutoSession["lastBudgetAlertLevel"];
|
|
@@ -959,6 +1014,11 @@ export async function autoLoop(
|
|
|
959
1014
|
"info",
|
|
960
1015
|
"budget",
|
|
961
1016
|
);
|
|
1017
|
+
deps.logCmuxEvent(
|
|
1018
|
+
prefs,
|
|
1019
|
+
`Budget 75%: ${deps.formatCost(totalCost)} / ${deps.formatCost(budgetCeiling)}`,
|
|
1020
|
+
"progress",
|
|
1021
|
+
);
|
|
962
1022
|
} else if (budgetAlertLevel === 0) {
|
|
963
1023
|
s.lastBudgetAlertLevel = 0;
|
|
964
1024
|
}
|
|
@@ -47,10 +47,11 @@ import {
|
|
|
47
47
|
} from "./crash-recovery.js";
|
|
48
48
|
import {
|
|
49
49
|
acquireSessionLock,
|
|
50
|
-
|
|
50
|
+
getSessionLockStatus,
|
|
51
51
|
releaseSessionLock,
|
|
52
52
|
updateSessionLock,
|
|
53
53
|
} from "./session-lock.js";
|
|
54
|
+
import type { SessionLockStatus } from "./session-lock.js";
|
|
54
55
|
import {
|
|
55
56
|
clearUnitRuntimeRecord,
|
|
56
57
|
inspectExecuteTaskDurability,
|
|
@@ -184,6 +185,7 @@ import {
|
|
|
184
185
|
} from "./auto-supervisor.js";
|
|
185
186
|
import { isDbAvailable } from "./gsd-db.js";
|
|
186
187
|
import { countPendingCaptures } from "./captures.js";
|
|
188
|
+
import { clearCmuxSidebar, logCmuxEvent, syncCmuxSidebar } from "../cmux/index.js";
|
|
187
189
|
|
|
188
190
|
// ── Extracted modules ──────────────────────────────────────────────────────
|
|
189
191
|
import { startUnitSupervision } from "./auto-timers.js";
|
|
@@ -416,6 +418,38 @@ export function stopAutoRemote(projectRoot: string): {
|
|
|
416
418
|
}
|
|
417
419
|
}
|
|
418
420
|
|
|
421
|
+
/**
|
|
422
|
+
* Check if a remote auto-mode session is running (from a different process).
|
|
423
|
+
* Reads the crash lock, checks PID liveness, and returns session details.
|
|
424
|
+
* Used by the guard in commands.ts to prevent bare /gsd, /gsd next, and
|
|
425
|
+
* /gsd auto from stealing the session lock.
|
|
426
|
+
*/
|
|
427
|
+
export function checkRemoteAutoSession(projectRoot: string): {
|
|
428
|
+
running: boolean;
|
|
429
|
+
pid?: number;
|
|
430
|
+
unitType?: string;
|
|
431
|
+
unitId?: string;
|
|
432
|
+
startedAt?: string;
|
|
433
|
+
completedUnits?: number;
|
|
434
|
+
} {
|
|
435
|
+
const lock = readCrashLock(projectRoot);
|
|
436
|
+
if (!lock) return { running: false };
|
|
437
|
+
|
|
438
|
+
if (!isLockProcessAlive(lock)) {
|
|
439
|
+
// Stale lock from a dead process — not a live remote session
|
|
440
|
+
return { running: false };
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
return {
|
|
444
|
+
running: true,
|
|
445
|
+
pid: lock.pid,
|
|
446
|
+
unitType: lock.unitType,
|
|
447
|
+
unitId: lock.unitId,
|
|
448
|
+
startedAt: lock.startedAt,
|
|
449
|
+
completedUnits: lock.completedUnits,
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
|
|
419
453
|
export function isStepMode(): boolean {
|
|
420
454
|
return s.stepMode;
|
|
421
455
|
}
|
|
@@ -460,14 +494,33 @@ function buildSnapshotOpts(
|
|
|
460
494
|
};
|
|
461
495
|
}
|
|
462
496
|
|
|
463
|
-
function handleLostSessionLock(
|
|
464
|
-
|
|
497
|
+
function handleLostSessionLock(
|
|
498
|
+
ctx?: ExtensionContext,
|
|
499
|
+
lockStatus?: SessionLockStatus,
|
|
500
|
+
): void {
|
|
501
|
+
debugLog("session-lock-lost", {
|
|
502
|
+
lockBase: lockBase(),
|
|
503
|
+
reason: lockStatus?.failureReason,
|
|
504
|
+
existingPid: lockStatus?.existingPid,
|
|
505
|
+
expectedPid: lockStatus?.expectedPid,
|
|
506
|
+
});
|
|
465
507
|
s.active = false;
|
|
466
508
|
s.paused = false;
|
|
467
509
|
clearUnitTimeout();
|
|
468
510
|
deregisterSigtermHandler();
|
|
511
|
+
clearCmuxSidebar(loadEffectiveGSDPreferences()?.preferences);
|
|
512
|
+
const message =
|
|
513
|
+
lockStatus?.failureReason === "pid-mismatch"
|
|
514
|
+
? lockStatus.existingPid
|
|
515
|
+
? `Session lock moved to PID ${lockStatus.existingPid} — another GSD process appears to have taken over. Stopping gracefully.`
|
|
516
|
+
: "Session lock moved to a different process — another GSD process appears to have taken over. Stopping gracefully."
|
|
517
|
+
: lockStatus?.failureReason === "missing-metadata"
|
|
518
|
+
? "Session lock metadata disappeared, so ownership could not be confirmed. Stopping gracefully."
|
|
519
|
+
: lockStatus?.failureReason === "compromised"
|
|
520
|
+
? "Session lock was compromised or invalidated during heartbeat checks; takeover was not confirmed. Stopping gracefully."
|
|
521
|
+
: "Session lock lost. Stopping gracefully.";
|
|
469
522
|
ctx?.ui.notify(
|
|
470
|
-
|
|
523
|
+
message,
|
|
471
524
|
"error",
|
|
472
525
|
);
|
|
473
526
|
ctx?.ui.setStatus("gsd-auto", undefined);
|
|
@@ -481,6 +534,7 @@ export async function stopAuto(
|
|
|
481
534
|
reason?: string,
|
|
482
535
|
): Promise<void> {
|
|
483
536
|
if (!s.active && !s.paused) return;
|
|
537
|
+
const loadedPreferences = loadEffectiveGSDPreferences()?.preferences;
|
|
484
538
|
const reasonSuffix = reason ? ` — ${reason}` : "";
|
|
485
539
|
clearUnitTimeout();
|
|
486
540
|
if (lockBase()) clearLock(lockBase());
|
|
@@ -543,6 +597,13 @@ export async function stopAuto(
|
|
|
543
597
|
}
|
|
544
598
|
}
|
|
545
599
|
|
|
600
|
+
clearCmuxSidebar(loadedPreferences);
|
|
601
|
+
logCmuxEvent(
|
|
602
|
+
loadedPreferences,
|
|
603
|
+
`Auto-mode stopped${reasonSuffix || ""}.`,
|
|
604
|
+
reason?.startsWith("Blocked:") ? "warning" : "info",
|
|
605
|
+
);
|
|
606
|
+
|
|
546
607
|
if (isDebugEnabled()) {
|
|
547
608
|
const logPath = writeDebugSummary();
|
|
548
609
|
if (logPath) {
|
|
@@ -708,6 +769,8 @@ function buildLoopDeps(): LoopDeps {
|
|
|
708
769
|
pauseAuto,
|
|
709
770
|
clearUnitTimeout,
|
|
710
771
|
updateProgressWidget,
|
|
772
|
+
syncCmuxSidebar,
|
|
773
|
+
logCmuxEvent,
|
|
711
774
|
|
|
712
775
|
// State and cache
|
|
713
776
|
invalidateAllCaches,
|
|
@@ -724,7 +787,7 @@ function buildLoopDeps(): LoopDeps {
|
|
|
724
787
|
checkResourcesStale,
|
|
725
788
|
|
|
726
789
|
// Session lock
|
|
727
|
-
validateSessionLock,
|
|
790
|
+
validateSessionLock: getSessionLockStatus,
|
|
728
791
|
updateSessionLock,
|
|
729
792
|
handleLostSessionLock,
|
|
730
793
|
|
|
@@ -890,6 +953,7 @@ export async function startAuto(
|
|
|
890
953
|
restoreHookState(s.basePath);
|
|
891
954
|
try {
|
|
892
955
|
await rebuildState(s.basePath);
|
|
956
|
+
syncCmuxSidebar(loadEffectiveGSDPreferences()?.preferences, await deriveState(s.basePath));
|
|
893
957
|
} catch (e) {
|
|
894
958
|
debugLog("resume-rebuild-state-failed", {
|
|
895
959
|
error: e instanceof Error ? e.message : String(e),
|
|
@@ -941,6 +1005,7 @@ export async function startAuto(
|
|
|
941
1005
|
s.currentMilestoneId ?? "unknown",
|
|
942
1006
|
s.completedUnits.length,
|
|
943
1007
|
);
|
|
1008
|
+
logCmuxEvent(loadEffectiveGSDPreferences()?.preferences, s.stepMode ? "Step-mode resumed." : "Auto-mode resumed.", "progress");
|
|
944
1009
|
|
|
945
1010
|
await autoLoop(ctx, pi, s, buildLoopDeps());
|
|
946
1011
|
return;
|
|
@@ -965,6 +1030,13 @@ export async function startAuto(
|
|
|
965
1030
|
);
|
|
966
1031
|
if (!ready) return;
|
|
967
1032
|
|
|
1033
|
+
try {
|
|
1034
|
+
syncCmuxSidebar(loadEffectiveGSDPreferences()?.preferences, await deriveState(s.basePath));
|
|
1035
|
+
} catch {
|
|
1036
|
+
// Best-effort only — sidebar sync must never block auto-mode startup
|
|
1037
|
+
}
|
|
1038
|
+
logCmuxEvent(loadEffectiveGSDPreferences()?.preferences, requestedStepMode ? "Step-mode started." : "Auto-mode started.", "progress");
|
|
1039
|
+
|
|
968
1040
|
// Dispatch the first unit
|
|
969
1041
|
await autoLoop(ctx, pi, s, buildLoopDeps());
|
|
970
1042
|
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import type { ExtensionCommandContext } from "@gsd/pi-coding-agent";
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
+
import { clearCmuxSidebar, CmuxClient, detectCmuxEnvironment, resolveCmuxConfig } from "../cmux/index.js";
|
|
4
|
+
import { saveFile } from "./files.js";
|
|
5
|
+
import {
|
|
6
|
+
getProjectGSDPreferencesPath,
|
|
7
|
+
loadEffectiveGSDPreferences,
|
|
8
|
+
loadProjectGSDPreferences,
|
|
9
|
+
} from "./preferences.js";
|
|
10
|
+
import { ensurePreferencesFile, serializePreferencesToFrontmatter } from "./commands-prefs-wizard.js";
|
|
11
|
+
|
|
12
|
+
function extractBodyAfterFrontmatter(content: string): string | null {
|
|
13
|
+
const start = content.startsWith("---\n") ? 4 : content.startsWith("---\r\n") ? 5 : -1;
|
|
14
|
+
if (start === -1) return null;
|
|
15
|
+
const closingIdx = content.indexOf("\n---", start);
|
|
16
|
+
if (closingIdx === -1) return null;
|
|
17
|
+
const after = content.slice(closingIdx + 4);
|
|
18
|
+
return after.trim() ? after : null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function writeProjectCmuxPreferences(
|
|
22
|
+
ctx: ExtensionCommandContext,
|
|
23
|
+
updater: (prefs: Record<string, unknown>) => void,
|
|
24
|
+
): Promise<void> {
|
|
25
|
+
const path = getProjectGSDPreferencesPath();
|
|
26
|
+
await ensurePreferencesFile(path, ctx, "project");
|
|
27
|
+
|
|
28
|
+
const existing = loadProjectGSDPreferences();
|
|
29
|
+
const prefs: Record<string, unknown> = existing?.preferences ? { ...existing.preferences } : { version: 1 };
|
|
30
|
+
updater(prefs);
|
|
31
|
+
prefs.version = prefs.version || 1;
|
|
32
|
+
|
|
33
|
+
const frontmatter = serializePreferencesToFrontmatter(prefs);
|
|
34
|
+
let body = "\n# GSD Skill Preferences\n\nSee `~/.gsd/agent/extensions/gsd/docs/preferences-reference.md` for full field documentation and examples.\n";
|
|
35
|
+
if (existsSync(path)) {
|
|
36
|
+
const preserved = extractBodyAfterFrontmatter(readFileSync(path, "utf-8"));
|
|
37
|
+
if (preserved) body = preserved;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
await saveFile(path, `---\n${frontmatter}---${body}`);
|
|
41
|
+
await ctx.waitForIdle();
|
|
42
|
+
await ctx.reload();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function formatCmuxStatus(): string {
|
|
46
|
+
const loaded = loadEffectiveGSDPreferences();
|
|
47
|
+
const detected = detectCmuxEnvironment();
|
|
48
|
+
const resolved = resolveCmuxConfig(loaded?.preferences);
|
|
49
|
+
const capabilities = new CmuxClient(resolved).getCapabilities() as Record<string, unknown> | null;
|
|
50
|
+
const accessMode = typeof capabilities?.mode === "string"
|
|
51
|
+
? capabilities.mode
|
|
52
|
+
: typeof capabilities?.access_mode === "string"
|
|
53
|
+
? capabilities.access_mode
|
|
54
|
+
: "unknown";
|
|
55
|
+
const methods = Array.isArray(capabilities?.methods) ? capabilities.methods.length : 0;
|
|
56
|
+
|
|
57
|
+
return [
|
|
58
|
+
"cmux status",
|
|
59
|
+
"",
|
|
60
|
+
`Detected: ${detected.available ? "yes" : "no"}`,
|
|
61
|
+
`Enabled: ${resolved.enabled ? "yes" : "no"}`,
|
|
62
|
+
`CLI available: ${detected.cliAvailable ? "yes" : "no"}`,
|
|
63
|
+
`Socket: ${detected.socketPath}`,
|
|
64
|
+
`Workspace: ${detected.workspaceId ?? "(none)"}`,
|
|
65
|
+
`Surface: ${detected.surfaceId ?? "(none)"}`,
|
|
66
|
+
`Features: notifications=${resolved.notifications ? "on" : "off"}, sidebar=${resolved.sidebar ? "on" : "off"}, splits=${resolved.splits ? "on" : "off"}, browser=${resolved.browser ? "on" : "off"}`,
|
|
67
|
+
`Capabilities: access=${accessMode}, methods=${methods}`,
|
|
68
|
+
].join("\n");
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function ensureCmuxAvailableForEnable(ctx: ExtensionCommandContext): boolean {
|
|
72
|
+
const detected = detectCmuxEnvironment();
|
|
73
|
+
if (detected.available) return true;
|
|
74
|
+
ctx.ui.notify(
|
|
75
|
+
"cmux not detected. Install it from https://cmux.com and run gsd inside a cmux terminal.",
|
|
76
|
+
"warning",
|
|
77
|
+
);
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export async function handleCmux(args: string, ctx: ExtensionCommandContext): Promise<void> {
|
|
82
|
+
const trimmed = args.trim();
|
|
83
|
+
if (!trimmed || trimmed === "status") {
|
|
84
|
+
ctx.ui.notify(formatCmuxStatus(), "info");
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (trimmed === "on") {
|
|
89
|
+
if (!ensureCmuxAvailableForEnable(ctx)) return;
|
|
90
|
+
await writeProjectCmuxPreferences(ctx, (prefs) => {
|
|
91
|
+
prefs.cmux = {
|
|
92
|
+
enabled: true,
|
|
93
|
+
notifications: true,
|
|
94
|
+
sidebar: true,
|
|
95
|
+
splits: false,
|
|
96
|
+
browser: false,
|
|
97
|
+
...((prefs.cmux as Record<string, unknown> | undefined) ?? {}),
|
|
98
|
+
};
|
|
99
|
+
(prefs.cmux as Record<string, unknown>).enabled = true;
|
|
100
|
+
});
|
|
101
|
+
ctx.ui.notify("cmux integration enabled in project preferences.", "info");
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (trimmed === "off") {
|
|
106
|
+
const effective = loadEffectiveGSDPreferences()?.preferences;
|
|
107
|
+
await writeProjectCmuxPreferences(ctx, (prefs) => {
|
|
108
|
+
prefs.cmux = { ...((prefs.cmux as Record<string, unknown> | undefined) ?? {}), enabled: false };
|
|
109
|
+
});
|
|
110
|
+
clearCmuxSidebar(effective);
|
|
111
|
+
ctx.ui.notify("cmux integration disabled in project preferences.", "info");
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const parts = trimmed.split(/\s+/);
|
|
116
|
+
if (parts.length === 2 && ["notifications", "sidebar", "splits", "browser"].includes(parts[0]) && ["on", "off"].includes(parts[1])) {
|
|
117
|
+
const feature = parts[0] as "notifications" | "sidebar" | "splits" | "browser";
|
|
118
|
+
const enabled = parts[1] === "on";
|
|
119
|
+
if (enabled && !ensureCmuxAvailableForEnable(ctx)) return;
|
|
120
|
+
|
|
121
|
+
await writeProjectCmuxPreferences(ctx, (prefs) => {
|
|
122
|
+
const next = { ...((prefs.cmux as Record<string, unknown> | undefined) ?? {}) };
|
|
123
|
+
next[feature] = enabled;
|
|
124
|
+
if (enabled) next.enabled = true;
|
|
125
|
+
prefs.cmux = next;
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
if (!enabled && feature === "sidebar") {
|
|
129
|
+
clearCmuxSidebar(loadEffectiveGSDPreferences()?.preferences);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const note = feature === "browser" && enabled
|
|
133
|
+
? " Browser surfaces are still a follow-up path."
|
|
134
|
+
: "";
|
|
135
|
+
ctx.ui.notify(`cmux ${feature} ${enabled ? "enabled" : "disabled"}.${note}`, "info");
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
ctx.ui.notify(
|
|
140
|
+
"Usage: /gsd cmux <status|on|off|notifications on|notifications off|sidebar on|sidebar off|splits on|splits off|browser on|browser off>",
|
|
141
|
+
"info",
|
|
142
|
+
);
|
|
143
|
+
}
|
|
@@ -740,7 +740,7 @@ export function serializePreferencesToFrontmatter(prefs: Record<string, unknown>
|
|
|
740
740
|
"skill_rules", "custom_instructions", "models", "skill_discovery",
|
|
741
741
|
"skill_staleness_days", "auto_supervisor", "uat_dispatch", "unique_milestone_ids",
|
|
742
742
|
"budget_ceiling", "budget_enforcement", "context_pause_threshold",
|
|
743
|
-
"notifications", "remote_questions", "git",
|
|
743
|
+
"notifications", "cmux", "remote_questions", "git",
|
|
744
744
|
"post_unit_hooks", "pre_dispatch_hooks",
|
|
745
745
|
"dynamic_routing", "token_profile", "phases", "parallel",
|
|
746
746
|
"auto_visualize", "auto_report",
|