gsd-pi 2.71.0-dev.06b86c6 → 2.71.0-dev.7a61d89

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.
Files changed (151) hide show
  1. package/dist/headless-events.d.ts +2 -0
  2. package/dist/headless-events.js +7 -0
  3. package/dist/headless.js +16 -3
  4. package/dist/resource-loader.js +6 -3
  5. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +10 -4
  6. package/dist/resources/extensions/gsd/auto/infra-errors.js +34 -0
  7. package/dist/resources/extensions/gsd/auto/loop.js +32 -1
  8. package/dist/resources/extensions/gsd/auto/session.js +8 -0
  9. package/dist/resources/extensions/gsd/auto-dashboard.js +22 -16
  10. package/dist/resources/extensions/gsd/auto.js +52 -0
  11. package/dist/resources/extensions/gsd/bootstrap/register-shortcuts.js +66 -51
  12. package/dist/resources/extensions/gsd/commands/handlers/auto.js +10 -33
  13. package/dist/resources/extensions/gsd/commands/handlers/core.js +45 -11
  14. package/dist/resources/extensions/gsd/commands/handlers/notifications-handler.js +15 -6
  15. package/dist/resources/extensions/gsd/commands/handlers/workflow.js +4 -10
  16. package/dist/resources/extensions/gsd/dashboard-overlay.js +8 -3
  17. package/dist/resources/extensions/gsd/forensics.js +19 -6
  18. package/dist/resources/extensions/gsd/guided-flow.js +5 -10
  19. package/dist/resources/extensions/gsd/metrics.js +1 -0
  20. package/dist/resources/extensions/gsd/milestone-actions.js +10 -4
  21. package/dist/resources/extensions/gsd/notification-overlay.js +20 -5
  22. package/dist/resources/extensions/gsd/notification-store.js +30 -0
  23. package/dist/resources/extensions/gsd/notification-widget.js +5 -13
  24. package/dist/resources/extensions/gsd/parallel-monitor-overlay.js +8 -3
  25. package/dist/resources/extensions/gsd/shortcut-defs.js +34 -0
  26. package/dist/web/standalone/.next/BUILD_ID +1 -1
  27. package/dist/web/standalone/.next/app-path-routes-manifest.json +11 -11
  28. package/dist/web/standalone/.next/build-manifest.json +2 -2
  29. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  30. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  31. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  32. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  33. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  34. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  35. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  36. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  37. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  38. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  39. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  40. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  41. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  42. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  43. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  44. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  45. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  46. package/dist/web/standalone/.next/server/app/index.html +1 -1
  47. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  48. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  49. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  50. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  51. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  52. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  53. package/dist/web/standalone/.next/server/app-paths-manifest.json +11 -11
  54. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  55. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  56. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  57. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  58. package/package.json +1 -1
  59. package/packages/pi-ai/dist/providers/anthropic-auth.test.d.ts +2 -0
  60. package/packages/pi-ai/dist/providers/anthropic-auth.test.d.ts.map +1 -0
  61. package/packages/pi-ai/dist/providers/anthropic-auth.test.js +20 -0
  62. package/packages/pi-ai/dist/providers/anthropic-auth.test.js.map +1 -0
  63. package/packages/pi-ai/dist/providers/anthropic.d.ts +2 -1
  64. package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
  65. package/packages/pi-ai/dist/providers/anthropic.js +7 -4
  66. package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
  67. package/packages/pi-ai/src/providers/anthropic-auth.test.ts +32 -0
  68. package/packages/pi-ai/src/providers/anthropic.ts +8 -4
  69. package/packages/pi-coding-agent/dist/core/agent-session-renderable-tools.test.d.ts +2 -0
  70. package/packages/pi-coding-agent/dist/core/agent-session-renderable-tools.test.d.ts.map +1 -0
  71. package/packages/pi-coding-agent/dist/core/agent-session-renderable-tools.test.js +61 -0
  72. package/packages/pi-coding-agent/dist/core/agent-session-renderable-tools.test.js.map +1 -0
  73. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  74. package/packages/pi-coding-agent/dist/core/agent-session.js +2 -1
  75. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  76. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts +10 -0
  77. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts.map +1 -1
  78. package/packages/pi-coding-agent/dist/core/auth-storage.js +27 -0
  79. package/packages/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
  80. package/packages/pi-coding-agent/dist/core/auth-storage.test.js +85 -0
  81. package/packages/pi-coding-agent/dist/core/auth-storage.test.js.map +1 -1
  82. package/packages/pi-coding-agent/dist/core/sdk.d.ts +11 -0
  83. package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
  84. package/packages/pi-coding-agent/dist/core/sdk.js +38 -5
  85. package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
  86. package/packages/pi-coding-agent/dist/core/sdk.test.d.ts +2 -0
  87. package/packages/pi-coding-agent/dist/core/sdk.test.d.ts.map +1 -0
  88. package/packages/pi-coding-agent/dist/core/sdk.test.js +71 -0
  89. package/packages/pi-coding-agent/dist/core/sdk.test.js.map +1 -0
  90. package/packages/pi-coding-agent/dist/index.d.ts +1 -1
  91. package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
  92. package/packages/pi-coding-agent/dist/index.js +1 -1
  93. package/packages/pi-coding-agent/dist/index.js.map +1 -1
  94. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +4 -0
  95. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  96. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +43 -0
  97. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  98. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  99. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +7 -2
  100. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  101. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  102. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +4 -3
  103. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  104. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js +4 -2
  105. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js.map +1 -1
  106. package/packages/pi-coding-agent/src/core/agent-session-renderable-tools.test.ts +70 -0
  107. package/packages/pi-coding-agent/src/core/agent-session.ts +2 -1
  108. package/packages/pi-coding-agent/src/core/auth-storage.test.ts +108 -0
  109. package/packages/pi-coding-agent/src/core/auth-storage.ts +30 -0
  110. package/packages/pi-coding-agent/src/core/sdk.test.ts +89 -0
  111. package/packages/pi-coding-agent/src/core/sdk.ts +45 -9
  112. package/packages/pi-coding-agent/src/index.ts +1 -0
  113. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +47 -0
  114. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +7 -2
  115. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +4 -3
  116. package/packages/pi-coding-agent/src/modes/interactive/slash-command-handlers.ts +4 -2
  117. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +13 -5
  118. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +56 -4
  119. package/src/resources/extensions/gsd/auto/infra-errors.ts +38 -0
  120. package/src/resources/extensions/gsd/auto/loop.ts +45 -1
  121. package/src/resources/extensions/gsd/auto/session.ts +8 -0
  122. package/src/resources/extensions/gsd/auto-dashboard.ts +29 -18
  123. package/src/resources/extensions/gsd/auto.ts +68 -0
  124. package/src/resources/extensions/gsd/bootstrap/register-shortcuts.ts +82 -60
  125. package/src/resources/extensions/gsd/commands/handlers/auto.ts +10 -36
  126. package/src/resources/extensions/gsd/commands/handlers/core.ts +46 -11
  127. package/src/resources/extensions/gsd/commands/handlers/notifications-handler.ts +17 -7
  128. package/src/resources/extensions/gsd/commands/handlers/workflow.ts +4 -10
  129. package/src/resources/extensions/gsd/dashboard-overlay.ts +10 -3
  130. package/src/resources/extensions/gsd/forensics.ts +23 -7
  131. package/src/resources/extensions/gsd/guided-flow.ts +5 -10
  132. package/src/resources/extensions/gsd/interrupted-session.ts +1 -0
  133. package/src/resources/extensions/gsd/metrics.ts +12 -1
  134. package/src/resources/extensions/gsd/milestone-actions.ts +10 -3
  135. package/src/resources/extensions/gsd/notification-overlay.ts +24 -7
  136. package/src/resources/extensions/gsd/notification-store.ts +30 -0
  137. package/src/resources/extensions/gsd/notification-widget.ts +5 -14
  138. package/src/resources/extensions/gsd/parallel-monitor-overlay.ts +10 -3
  139. package/src/resources/extensions/gsd/shortcut-defs.ts +49 -0
  140. package/src/resources/extensions/gsd/tests/forensics-stuck-loops.test.ts +62 -0
  141. package/src/resources/extensions/gsd/tests/format-shortcut.test.ts +15 -0
  142. package/src/resources/extensions/gsd/tests/infra-errors-cooldown.test.ts +180 -0
  143. package/src/resources/extensions/gsd/tests/notification-store.test.ts +18 -0
  144. package/src/resources/extensions/gsd/tests/notification-widget.test.ts +3 -2
  145. package/src/resources/extensions/gsd/tests/notifications-handler.test.ts +90 -0
  146. package/src/resources/extensions/gsd/tests/parallel-monitor-overlay.test.ts +1 -0
  147. package/src/resources/extensions/gsd/tests/park-db-sync.test.ts +18 -0
  148. package/src/resources/extensions/gsd/tests/register-shortcuts.test.ts +62 -5
  149. package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +90 -0
  150. /package/dist/web/standalone/.next/static/{dYVdRaunb2ZSEA8fjkT-V → ug91LJa0m7OdzrTVaz_48}/_buildManifest.js +0 -0
  151. /package/dist/web/standalone/.next/static/{dYVdRaunb2ZSEA8fjkT-V → ug91LJa0m7OdzrTVaz_48}/_ssgManifest.js +0 -0
@@ -165,6 +165,7 @@ import {
165
165
  reconcileMergeState,
166
166
  } from "./auto-recovery.js";
167
167
  import { resolveDispatch, DISPATCH_RULES } from "./auto-dispatch.js";
168
+ import { getErrorMessage } from "./error-utils.js";
168
169
  import { initRegistry, convertDispatchRules } from "./rule-registry.js";
169
170
  import { emitJournalEvent as _emitJournalEvent, type JournalEntry } from "./journal.js";
170
171
  import {
@@ -272,6 +273,53 @@ function restoreProjectRootEnv(): void {
272
273
  s.projectRootEnvCaptured = false;
273
274
  }
274
275
 
276
+ function captureMilestoneLockEnv(milestoneId: string | null): void {
277
+ if (!s.milestoneLockEnvCaptured) {
278
+ s.hadMilestoneLockEnv = Object.prototype.hasOwnProperty.call(process.env, "GSD_MILESTONE_LOCK");
279
+ s.previousMilestoneLockEnv = process.env.GSD_MILESTONE_LOCK ?? null;
280
+ s.milestoneLockEnvCaptured = true;
281
+ }
282
+
283
+ if (milestoneId) {
284
+ process.env.GSD_MILESTONE_LOCK = milestoneId;
285
+ } else {
286
+ delete process.env.GSD_MILESTONE_LOCK;
287
+ }
288
+ }
289
+
290
+ function restoreMilestoneLockEnv(): void {
291
+ if (!s.milestoneLockEnvCaptured) return;
292
+
293
+ if (s.hadMilestoneLockEnv && s.previousMilestoneLockEnv !== null) {
294
+ process.env.GSD_MILESTONE_LOCK = s.previousMilestoneLockEnv;
295
+ } else {
296
+ delete process.env.GSD_MILESTONE_LOCK;
297
+ }
298
+
299
+ s.previousMilestoneLockEnv = null;
300
+ s.hadMilestoneLockEnv = false;
301
+ s.milestoneLockEnvCaptured = false;
302
+ }
303
+
304
+ export function startAutoDetached(
305
+ ctx: ExtensionCommandContext,
306
+ pi: ExtensionAPI,
307
+ base: string,
308
+ verboseMode: boolean,
309
+ options?: {
310
+ step?: boolean;
311
+ interrupted?: InterruptedSessionAssessment;
312
+ milestoneLock?: string | null;
313
+ },
314
+ ): void {
315
+ void startAuto(ctx, pi, base, verboseMode, options).catch((err) => {
316
+ const message = getErrorMessage(err);
317
+ ctx.ui.notify(`Auto-start failed: ${message}`, "error");
318
+ logWarning("engine", `auto start error: ${message}`, { file: "auto.ts" });
319
+ debugLog("auto-start-failed", { error: message });
320
+ });
321
+ }
322
+
275
323
  export function shouldUseWorktreeIsolation(): boolean {
276
324
  const prefs = loadEffectiveGSDPreferences()?.preferences?.git;
277
325
  if (prefs?.isolation === "worktree") return true;
@@ -549,11 +597,13 @@ function buildSnapshotOpts(
549
597
  _unitType: string,
550
598
  _unitId: string,
551
599
  ): {
600
+ autoSessionKey?: string;
552
601
  continueHereFired?: boolean;
553
602
  promptCharCount?: number;
554
603
  baselineCharCount?: number;
555
604
  } & Record<string, unknown> {
556
605
  return {
606
+ ...(s.autoStartTime > 0 ? { autoSessionKey: String(s.autoStartTime) } : {}),
557
607
  promptCharCount: s.lastPromptCharCount,
558
608
  baselineCharCount: s.lastBaselineCharCount,
559
609
  ...(s.currentUnitRouting ?? {}),
@@ -574,6 +624,7 @@ function handleLostSessionLock(
574
624
  s.paused = false;
575
625
  clearUnitTimeout();
576
626
  restoreProjectRootEnv();
627
+ restoreMilestoneLockEnv();
577
628
  deregisterSigtermHandler();
578
629
  clearCmuxSidebar(loadEffectiveGSDPreferences()?.preferences);
579
630
  const base = lockBase();
@@ -610,6 +661,7 @@ function cleanupAfterLoopExit(ctx: ExtensionContext): void {
610
661
  s.active = false;
611
662
  clearUnitTimeout();
612
663
  restoreProjectRootEnv();
664
+ restoreMilestoneLockEnv();
613
665
 
614
666
  // Clear crash lock and release session lock so the next `/gsd next` does
615
667
  // not see a stale lock with the current PID and treat it as a "remote"
@@ -880,6 +932,7 @@ export async function stopAuto(
880
932
  ctx?.ui.setWidget("gsd-progress", undefined);
881
933
  ctx?.ui.setFooter(undefined);
882
934
  restoreProjectRootEnv();
935
+ restoreMilestoneLockEnv();
883
936
 
884
937
  // Reset all session state in one call
885
938
  s.reset();
@@ -933,6 +986,7 @@ export async function pauseAuto(
933
986
  activeEngineId: s.activeEngineId,
934
987
  activeRunDir: s.activeRunDir,
935
988
  autoStartTime: s.autoStartTime,
989
+ milestoneLock: s.sessionMilestoneLock ?? undefined,
936
990
  };
937
991
  const runtimeDir = join(gsdRoot(s.originalBasePath || s.basePath), "runtime");
938
992
  mkdirSync(runtimeDir, { recursive: true });
@@ -971,6 +1025,7 @@ export async function pauseAuto(
971
1025
  s.active = false;
972
1026
  s.paused = true;
973
1027
  restoreProjectRootEnv();
1028
+ restoreMilestoneLockEnv();
974
1029
  s.pendingVerificationRetry = null;
975
1030
  s.verificationRetryCount.clear();
976
1031
  ctx?.ui.setStatus("gsd-auto", "paused");
@@ -1154,6 +1209,7 @@ export async function startAuto(
1154
1209
  options?: {
1155
1210
  step?: boolean;
1156
1211
  interrupted?: InterruptedSessionAssessment;
1212
+ milestoneLock?: string | null;
1157
1213
  },
1158
1214
  ): Promise<void> {
1159
1215
  if (s.active) {
@@ -1163,6 +1219,12 @@ export async function startAuto(
1163
1219
 
1164
1220
  const requestedStepMode = options?.step ?? false;
1165
1221
  const interruptedAssessment = options?.interrupted ?? null;
1222
+ if (options?.milestoneLock !== undefined) {
1223
+ s.sessionMilestoneLock = options.milestoneLock ?? null;
1224
+ }
1225
+ if (s.sessionMilestoneLock) {
1226
+ captureMilestoneLockEnv(s.sessionMilestoneLock);
1227
+ }
1166
1228
 
1167
1229
  // Escape stale worktree cwd from a previous milestone (#608).
1168
1230
  base = escapeStaleWorktree(base);
@@ -1194,6 +1256,7 @@ export async function startAuto(
1194
1256
  s.originalBasePath = meta.originalBasePath || base;
1195
1257
  s.stepMode = meta.stepMode ?? requestedStepMode;
1196
1258
  s.autoStartTime = meta.autoStartTime || Date.now();
1259
+ s.sessionMilestoneLock = meta.milestoneLock ?? null;
1197
1260
  s.paused = true;
1198
1261
  try { unlinkSync(pausedPath); } catch (e) { logWarning("session", `pause file cleanup failed: ${e instanceof Error ? e.message : String(e)}`, { file: "auto.ts" }); }
1199
1262
  ctx.ui.notify(
@@ -1228,6 +1291,7 @@ export async function startAuto(
1228
1291
  s.pausedUnitType = meta.unitType ?? null;
1229
1292
  s.pausedUnitId = meta.unitId ?? null;
1230
1293
  s.autoStartTime = meta.autoStartTime || Date.now();
1294
+ s.sessionMilestoneLock = meta.milestoneLock ?? null;
1231
1295
  s.paused = true;
1232
1296
  try { unlinkSync(pausedPath); } catch (e) { logWarning("session", `pause file cleanup failed: ${e instanceof Error ? e.message : String(e)}`, { file: "auto.ts" }); }
1233
1297
  ctx.ui.notify(
@@ -1247,6 +1311,10 @@ export async function startAuto(
1247
1311
  if (!s.autoStartTime || s.autoStartTime <= 0) s.autoStartTime = Date.now();
1248
1312
  }
1249
1313
 
1314
+ if (s.sessionMilestoneLock) {
1315
+ captureMilestoneLockEnv(s.sessionMilestoneLock);
1316
+ }
1317
+
1250
1318
  if (!s.paused) {
1251
1319
  s.stepMode = requestedStepMode;
1252
1320
  }
@@ -1,79 +1,101 @@
1
1
  import { existsSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
 
4
- import type { ExtensionAPI } from "@gsd/pi-coding-agent";
4
+ import type { ExtensionAPI, ExtensionContext } from "@gsd/pi-coding-agent";
5
5
  import { Key } from "@gsd/pi-tui";
6
6
 
7
7
  import { GSDDashboardOverlay } from "../dashboard-overlay.js";
8
8
  import { GSDNotificationOverlay } from "../notification-overlay.js";
9
9
  import { ParallelMonitorOverlay } from "../parallel-monitor-overlay.js";
10
+ import { GSD_SHORTCUTS } from "../shortcut-defs.js";
10
11
  import { projectRoot } from "../commands/context.js";
11
12
  import { shortcutDesc } from "../../shared/mod.js";
12
13
 
13
14
  export function registerShortcuts(pi: ExtensionAPI): void {
14
- pi.registerShortcut(Key.ctrlAlt("g"), {
15
- description: shortcutDesc("Open GSD dashboard", "/gsd status"),
16
- handler: async (ctx) => {
17
- const basePath = projectRoot();
18
- if (!existsSync(join(basePath, ".gsd"))) {
19
- ctx.ui.notify("No .gsd/ directory found. Run /gsd to start.", "info");
20
- return;
21
- }
22
- await ctx.ui.custom<boolean>(
23
- (tui, theme, _kb, done) => new GSDDashboardOverlay(tui, theme, () => done(true)),
24
- {
25
- overlay: true,
26
- overlayOptions: {
27
- width: "90%",
28
- minWidth: 80,
29
- maxHeight: "92%",
30
- anchor: "center",
31
- },
15
+ const overlayOptions = {
16
+ width: "90%",
17
+ minWidth: 80,
18
+ maxHeight: "92%",
19
+ anchor: "center",
20
+ } as const;
21
+
22
+ const openDashboardOverlay = async (ctx: ExtensionContext) => {
23
+ const basePath = projectRoot();
24
+ if (!existsSync(join(basePath, ".gsd"))) {
25
+ ctx.ui.notify("No .gsd/ directory found. Run /gsd to start.", "info");
26
+ return;
27
+ }
28
+ await ctx.ui.custom<boolean>(
29
+ (tui, theme, _kb, done) => new GSDDashboardOverlay(tui, theme, () => done(true)),
30
+ {
31
+ overlay: true,
32
+ overlayOptions,
33
+ },
34
+ );
35
+ };
36
+
37
+ const openNotificationsOverlay = async (ctx: ExtensionContext) => {
38
+ await ctx.ui.custom<boolean>(
39
+ (tui, theme, _kb, done) => new GSDNotificationOverlay(tui, theme, () => done(true)),
40
+ {
41
+ overlay: true,
42
+ overlayOptions: {
43
+ width: "80%",
44
+ minWidth: 60,
45
+ maxHeight: "88%",
46
+ anchor: "center",
47
+ backdrop: true,
32
48
  },
33
- );
34
- },
49
+ },
50
+ );
51
+ };
52
+
53
+ const openParallelOverlay = async (ctx: ExtensionContext) => {
54
+ const basePath = projectRoot();
55
+ const parallelDir = join(basePath, ".gsd", "parallel");
56
+ if (!existsSync(parallelDir)) {
57
+ ctx.ui.notify("No parallel workers found. Run /gsd parallel start first.", "info");
58
+ return;
59
+ }
60
+ await ctx.ui.custom<boolean>(
61
+ (tui, theme, _kb, done) => new ParallelMonitorOverlay(tui, theme, () => done(true), basePath),
62
+ {
63
+ overlay: true,
64
+ overlayOptions,
65
+ },
66
+ );
67
+ };
68
+
69
+ pi.registerShortcut(Key.ctrlAlt(GSD_SHORTCUTS.dashboard.key), {
70
+ description: shortcutDesc(GSD_SHORTCUTS.dashboard.action, GSD_SHORTCUTS.dashboard.command),
71
+ handler: openDashboardOverlay,
35
72
  });
36
73
 
37
- pi.registerShortcut(Key.ctrlAlt("n"), {
38
- description: shortcutDesc("Open notification history", "/gsd notifications"),
39
- handler: async (ctx) => {
40
- await ctx.ui.custom<boolean>(
41
- (tui, theme, _kb, done) => new GSDNotificationOverlay(tui, theme, () => done(true)),
42
- {
43
- overlay: true,
44
- overlayOptions: {
45
- width: "80%",
46
- minWidth: 60,
47
- maxHeight: "88%",
48
- anchor: "center",
49
- backdrop: true,
50
- },
51
- },
52
- );
53
- },
74
+ // Fallback for terminals where Ctrl+Alt letter chords are not forwarded reliably.
75
+ pi.registerShortcut(Key.ctrlShift(GSD_SHORTCUTS.dashboard.key), {
76
+ description: shortcutDesc(`${GSD_SHORTCUTS.dashboard.action} (fallback)`, GSD_SHORTCUTS.dashboard.command),
77
+ handler: openDashboardOverlay,
54
78
  });
55
79
 
56
- pi.registerShortcut(Key.ctrlAlt("p"), {
57
- description: shortcutDesc("Open parallel worker monitor", "/gsd parallel watch"),
58
- handler: async (ctx) => {
59
- const basePath = projectRoot();
60
- const parallelDir = join(basePath, ".gsd", "parallel");
61
- if (!existsSync(parallelDir)) {
62
- ctx.ui.notify("No parallel workers found. Run /gsd parallel start first.", "info");
63
- return;
64
- }
65
- await ctx.ui.custom<boolean>(
66
- (tui, theme, _kb, done) => new ParallelMonitorOverlay(tui, theme, () => done(true)),
67
- {
68
- overlay: true,
69
- overlayOptions: {
70
- width: "90%",
71
- minWidth: 80,
72
- maxHeight: "92%",
73
- anchor: "center",
74
- },
75
- },
76
- );
77
- },
80
+ pi.registerShortcut(Key.ctrlAlt(GSD_SHORTCUTS.notifications.key), {
81
+ description: shortcutDesc(GSD_SHORTCUTS.notifications.action, GSD_SHORTCUTS.notifications.command),
82
+ handler: openNotificationsOverlay,
83
+ });
84
+
85
+ // Fallback for terminals where Ctrl+Alt letter chords are not forwarded reliably.
86
+ pi.registerShortcut(Key.ctrlShift(GSD_SHORTCUTS.notifications.key), {
87
+ description: shortcutDesc(`${GSD_SHORTCUTS.notifications.action} (fallback)`, GSD_SHORTCUTS.notifications.command),
88
+ handler: openNotificationsOverlay,
89
+ });
90
+
91
+ pi.registerShortcut(Key.ctrlAlt(GSD_SHORTCUTS.parallel.key), {
92
+ description: shortcutDesc(GSD_SHORTCUTS.parallel.action, GSD_SHORTCUTS.parallel.command),
93
+ handler: openParallelOverlay,
94
+ });
95
+
96
+ // Fallback for terminals where Ctrl+Alt letter chords are not forwarded reliably.
97
+ pi.registerShortcut(Key.ctrlShift(GSD_SHORTCUTS.parallel.key), {
98
+ description: shortcutDesc(`${GSD_SHORTCUTS.parallel.action} (fallback)`, GSD_SHORTCUTS.parallel.command),
99
+ handler: openParallelOverlay,
78
100
  });
79
101
  }
@@ -4,7 +4,7 @@ import { existsSync, readFileSync } from "node:fs";
4
4
  import { resolve } from "node:path";
5
5
 
6
6
  import { enableDebug } from "../../debug-logger.js";
7
- import { getAutoDashboardData, isAutoActive, isAutoPaused, pauseAuto, startAuto, stopAuto, stopAutoRemote } from "../../auto.js";
7
+ import { getAutoDashboardData, isAutoActive, isAutoPaused, pauseAuto, startAutoDetached, stopAuto, stopAutoRemote } from "../../auto.js";
8
8
  import { handleRate } from "../../commands-rate.js";
9
9
  import { guardRemoteSession, projectRoot } from "../context.js";
10
10
  import { findMilestoneIds } from "../../milestone-id-utils.js";
@@ -42,26 +42,6 @@ export function parseMilestoneTarget(input: string): { milestoneId: string | nul
42
42
  return { milestoneId: match[1], rest };
43
43
  }
44
44
 
45
- /**
46
- * Set GSD_MILESTONE_LOCK to target a specific milestone, then run `fn`.
47
- * Clears the env var when `fn` resolves or rejects, so the lock does not
48
- * leak into subsequent commands in the same process.
49
- */
50
- async function withMilestoneLock(milestoneId: string, fn: () => Promise<void>): Promise<void> {
51
- const previous = process.env.GSD_MILESTONE_LOCK;
52
- process.env.GSD_MILESTONE_LOCK = milestoneId;
53
- try {
54
- await fn();
55
- } finally {
56
- // Restore previous value (undefined → delete, else restore).
57
- if (previous === undefined) {
58
- delete process.env.GSD_MILESTONE_LOCK;
59
- } else {
60
- process.env.GSD_MILESTONE_LOCK = previous;
61
- }
62
- }
63
- }
64
-
65
45
  export async function handleAutoCommand(trimmed: string, ctx: ExtensionCommandContext, pi: ExtensionAPI): Promise<boolean> {
66
46
  if (trimmed === "next" || trimmed.startsWith("next ")) {
67
47
  if (trimmed.includes("--dry-run")) {
@@ -84,13 +64,10 @@ export async function handleAutoCommand(trimmed: string, ctx: ExtensionCommandCo
84
64
  }
85
65
  }
86
66
 
87
- if (milestoneId) {
88
- await withMilestoneLock(milestoneId, () =>
89
- startAuto(ctx, pi, projectRoot(), verboseMode, { step: true }),
90
- );
91
- } else {
92
- await startAuto(ctx, pi, projectRoot(), verboseMode, { step: true });
93
- }
67
+ startAutoDetached(ctx, pi, projectRoot(), verboseMode, {
68
+ step: true,
69
+ milestoneLock: milestoneId,
70
+ });
94
71
  return true;
95
72
  }
96
73
 
@@ -128,13 +105,11 @@ export async function handleAutoCommand(trimmed: string, ctx: ExtensionCommandCo
128
105
  const { showHeadlessMilestoneCreation } = await import("../../guided-flow.js");
129
106
  await showHeadlessMilestoneCreation(ctx, pi, projectRoot(), seedContent);
130
107
  } else if (milestoneId) {
131
- // Target a specific milestone — use GSD_MILESTONE_LOCK so state
132
- // derivation only sees this milestone (#2521).
133
- await withMilestoneLock(milestoneId, () =>
134
- startAuto(ctx, pi, projectRoot(), verboseMode),
135
- );
108
+ startAutoDetached(ctx, pi, projectRoot(), verboseMode, {
109
+ milestoneLock: milestoneId,
110
+ });
136
111
  } else {
137
- await startAuto(ctx, pi, projectRoot(), verboseMode);
112
+ startAutoDetached(ctx, pi, projectRoot(), verboseMode);
138
113
  }
139
114
  return true;
140
115
  }
@@ -175,10 +150,9 @@ export async function handleAutoCommand(trimmed: string, ctx: ExtensionCommandCo
175
150
 
176
151
  if (trimmed === "") {
177
152
  if (!(await guardRemoteSession(ctx, pi))) return true;
178
- await startAuto(ctx, pi, projectRoot(), false, { step: true });
153
+ startAutoDetached(ctx, pi, projectRoot(), false, { step: true });
179
154
  return true;
180
155
  }
181
156
 
182
157
  return false;
183
158
  }
184
-
@@ -9,10 +9,43 @@ import { runEnvironmentChecks } from "../../doctor-environment.js";
9
9
  import { deriveState } from "../../state.js";
10
10
  import { handleCmux } from "../../commands-cmux.js";
11
11
  import { projectRoot } from "../context.js";
12
- import { formatShortcut } from "../../files.js";
12
+ import { formattedShortcutPair } from "../../shortcut-defs.js";
13
13
 
14
- export function showHelp(ctx: ExtensionCommandContext): void {
15
- const lines = [
14
+ export function showHelp(ctx: ExtensionCommandContext, args = ""): void {
15
+ const summaryLines = [
16
+ "GSD — Get Shit Done\n",
17
+ "QUICK START",
18
+ " /gsd start <tpl> Start a workflow template",
19
+ " /gsd Run next unit (same as /gsd next)",
20
+ " /gsd auto Run all queued units continuously",
21
+ " /gsd pause Pause auto-mode",
22
+ " /gsd stop Stop auto-mode gracefully",
23
+ "",
24
+ "VISIBILITY",
25
+ ` /gsd status Dashboard (${formattedShortcutPair("dashboard")})`,
26
+ ` /gsd parallel watch Parallel monitor (${formattedShortcutPair("parallel")})`,
27
+ ` /gsd notifications Notification history (${formattedShortcutPair("notifications")})`,
28
+ " /gsd visualize Interactive 10-tab TUI",
29
+ " /gsd queue Show queued/dispatched units",
30
+ "",
31
+ "COURSE CORRECTION",
32
+ " /gsd steer <desc> Apply user override to active work",
33
+ " /gsd capture <text> Quick-capture a thought to CAPTURES.md",
34
+ " /gsd triage Classify and route pending captures",
35
+ " /gsd undo Revert last completed unit [--force]",
36
+ " /gsd rethink Conversational project reorganization",
37
+ "",
38
+ "SETUP",
39
+ " /gsd init Project init wizard",
40
+ " /gsd setup Global setup status [llm|search|remote|keys|prefs]",
41
+ " /gsd model Switch active session model",
42
+ " /gsd prefs Manage preferences",
43
+ " /gsd doctor Diagnose and repair .gsd/ state",
44
+ "",
45
+ "Use /gsd help full for the complete command reference.",
46
+ ];
47
+
48
+ const fullLines = [
16
49
  "GSD — Get Shit Done\n",
17
50
  "WORKFLOW",
18
51
  " /gsd start <tpl> Start a workflow template (bugfix, spike, feature, hotfix, etc.)",
@@ -26,12 +59,13 @@ export function showHelp(ctx: ExtensionCommandContext): void {
26
59
  " /gsd new-milestone Create milestone from headless context (used by gsd headless)",
27
60
  "",
28
61
  "VISIBILITY",
29
- ` /gsd status Show progress dashboard (${formatShortcut("Ctrl+Alt+G")})`,
62
+ ` /gsd status Show progress dashboard (${formattedShortcutPair("dashboard")})`,
63
+ ` /gsd parallel watch Open parallel worker monitor (${formattedShortcutPair("parallel")})`,
30
64
  " /gsd visualize Interactive 10-tab TUI (progress, timeline, deps, metrics, health, agent, changes, knowledge, captures, export)",
31
65
  " /gsd queue Show queued/dispatched units and execution order",
32
66
  " /gsd history View execution history [--cost] [--phase] [--model] [N]",
33
67
  " /gsd changelog Show categorized release notes [version]",
34
- ` /gsd notifications View persistent notification history [clear|tail|filter] (${formatShortcut("Ctrl+Alt+N")})`,
68
+ ` /gsd notifications View persistent notification history [clear|tail|filter] (${formattedShortcutPair("notifications")})`,
35
69
  "",
36
70
  "COURSE CORRECTION",
37
71
  " /gsd steer <desc> Apply user override to active work",
@@ -71,7 +105,8 @@ export function showHelp(ctx: ExtensionCommandContext): void {
71
105
  " /gsd inspect Show SQLite DB diagnostics (schema, row counts, recent entries)",
72
106
  " /gsd update Update GSD to the latest version via npm",
73
107
  ];
74
- ctx.ui.notify(lines.join("\n"), "info");
108
+ const full = ["full", "--full", "all"].includes(args.trim().toLowerCase());
109
+ ctx.ui.notify((full ? fullLines : summaryLines).join("\n"), "info");
75
110
  }
76
111
 
77
112
  export async function handleStatus(ctx: ExtensionCommandContext): Promise<void> {
@@ -92,9 +127,9 @@ export async function handleStatus(ctx: ExtensionCommandContext): Promise<void>
92
127
  {
93
128
  overlay: true,
94
129
  overlayOptions: {
95
- width: "70%",
96
- minWidth: 60,
97
- maxHeight: "90%",
130
+ width: "90%",
131
+ minWidth: 80,
132
+ maxHeight: "92%",
98
133
  anchor: "center",
99
134
  },
100
135
  },
@@ -309,8 +344,8 @@ export async function handleCoreCommand(
309
344
  ctx: ExtensionCommandContext,
310
345
  pi?: ExtensionAPI,
311
346
  ): Promise<boolean> {
312
- if (trimmed === "help" || trimmed === "h" || trimmed === "?") {
313
- showHelp(ctx);
347
+ if (trimmed === "help" || trimmed === "h" || trimmed === "?" || trimmed.startsWith("help ")) {
348
+ showHelp(ctx, trimmed.startsWith("help ") ? trimmed.slice(5).trim() : "");
314
349
  return true;
315
350
  }
316
351
  if (trimmed === "status") {
@@ -13,6 +13,8 @@ import {
13
13
  } from "../../notification-store.js";
14
14
  import { GSDNotificationOverlay } from "../../notification-overlay.js";
15
15
 
16
+ const MAX_INLINE_ENTRIES = 40;
17
+
16
18
  function severityIcon(severity: NotifySeverity): string {
17
19
  switch (severity) {
18
20
  case "error": return "✗";
@@ -54,8 +56,9 @@ export async function handleNotificationsCommand(
54
56
  if (args === "tail" || args.startsWith("tail ")) {
55
57
  const countStr = args.replace(/^tail\s*/, "").trim();
56
58
  const count = countStr ? parseInt(countStr, 10) : 20;
57
- const n = isNaN(count) || count < 1 ? 20 : Math.min(count, 100);
58
- const entries = readNotifications().slice(0, n);
59
+ const all = readNotifications();
60
+ const n = isNaN(count) || count < 1 ? 20 : Math.min(count, MAX_INLINE_ENTRIES);
61
+ const entries = all.slice(0, n);
59
62
 
60
63
  if (entries.length === 0) {
61
64
  ctx.ui.notify("No notifications.", "info");
@@ -65,7 +68,10 @@ export async function handleNotificationsCommand(
65
68
  const lines = entries.map((e) =>
66
69
  `${severityIcon(e.severity)} [${formatTimestamp(e.ts)}] ${e.message}`,
67
70
  );
68
- ctx.ui.notify(`Last ${entries.length} notification(s):\n${lines.join("\n")}`, "info");
71
+ const suffix = all.length > entries.length
72
+ ? `\n... and ${all.length - entries.length} more (open /gsd notifications to browse all)`
73
+ : "";
74
+ ctx.ui.notify(`Last ${entries.length} notification(s):\n${lines.join("\n")}${suffix}`, "info");
69
75
  return true;
70
76
  }
71
77
 
@@ -86,7 +92,9 @@ export async function handleNotificationsCommand(
86
92
  const lines = entries.slice(0, 20).map((e) =>
87
93
  `${severityIcon(e.severity)} [${formatTimestamp(e.ts)}] ${e.message}`,
88
94
  );
89
- const suffix = entries.length > 20 ? `\n... and ${entries.length - 20} more` : "";
95
+ const suffix = entries.length > 20
96
+ ? `\n... and ${entries.length - 20} more (open /gsd notifications to browse all)`
97
+ : "";
90
98
  ctx.ui.notify(`${severity} notifications (${entries.length}):\n${lines.join("\n")}${suffix}`, "info");
91
99
  return true;
92
100
  }
@@ -96,8 +104,8 @@ export async function handleNotificationsCommand(
96
104
  // Try overlay first (TUI mode)
97
105
  if (ctx.hasUI) {
98
106
  try {
99
- await ctx.ui.custom<void>(
100
- (tui, theme, _kb, done) => new GSDNotificationOverlay(tui, theme, () => done()),
107
+ const result = await ctx.ui.custom<boolean>(
108
+ (tui, theme, _kb, done) => new GSDNotificationOverlay(tui, theme, () => done(true)),
101
109
  {
102
110
  overlay: true,
103
111
  overlayOptions: {
@@ -109,7 +117,9 @@ export async function handleNotificationsCommand(
109
117
  },
110
118
  },
111
119
  );
112
- return true;
120
+ if (result !== undefined) {
121
+ return true;
122
+ }
113
123
  } catch {
114
124
  // Fall through to text output if overlay fails
115
125
  }
@@ -18,7 +18,7 @@ import { createRun, listRuns } from "../../run-manager.js";
18
18
  import {
19
19
  setActiveEngineId,
20
20
  setActiveRunDir,
21
- startAuto,
21
+ startAutoDetached,
22
22
  pauseAuto,
23
23
  isAutoActive,
24
24
  getActiveEngineId,
@@ -77,7 +77,7 @@ async function handleCustomWorkflow(
77
77
  setActiveEngineId("custom");
78
78
  setActiveRunDir(runDir);
79
79
  ctx.ui.notify(`Created workflow run: ${defName}\nRun dir: ${runDir}`, "info");
80
- await startAuto(ctx, pi, base, false);
80
+ startAutoDetached(ctx, pi, base, false);
81
81
  } catch (err) {
82
82
  // Clean up engine state so a failed workflow run doesn't pollute the next /gsd auto
83
83
  setActiveEngineId(null);
@@ -157,13 +157,8 @@ async function handleCustomWorkflow(
157
157
  ctx.ui.notify("No custom workflow to resume. Use /gsd auto for dev workflow.", "warning");
158
158
  return true;
159
159
  }
160
- try {
161
- await startAuto(ctx, pi, projectRoot(), false);
162
- ctx.ui.notify("Custom workflow resumed.", "info");
163
- } catch (err) {
164
- const msg = err instanceof Error ? err.message : String(err);
165
- ctx.ui.notify(`Failed to resume workflow: ${msg}`, "error");
166
- }
160
+ startAutoDetached(ctx, pi, projectRoot(), false);
161
+ ctx.ui.notify("Custom workflow resumed.", "info");
167
162
  return true;
168
163
  }
169
164
 
@@ -278,4 +273,3 @@ export function getNextMilestoneId(basePath: string): string {
278
273
  const uniqueIds = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
279
274
  return nextMilestoneId(milestoneIds, uniqueIds);
280
275
  }
281
-
@@ -3,7 +3,8 @@
3
3
  *
4
4
  * Full-screen overlay showing auto-mode progress: milestone/slice/task
5
5
  * breakdown, current unit, completed units, timing, and activity log.
6
- * Toggled with Ctrl+Alt+G (⌃⌥G on macOS) or opened from /gsd status.
6
+ * Toggled with Ctrl+Alt+G (⌃⌥G on macOS), Ctrl+Shift+G fallback,
7
+ * or opened from /gsd status.
7
8
  */
8
9
 
9
10
  import type { Theme } from "@gsd/pi-coding-agent";
@@ -26,6 +27,7 @@ import { formatDuration, padRight, joinColumns, centerLine, fitColumns, STATUS_G
26
27
  import { estimateTimeRemaining } from "./auto-dashboard.js";
27
28
  import { computeProgressScore, formatProgressLine } from "./progress-score.js";
28
29
  import { runEnvironmentChecks, type EnvironmentCheckResult } from "./doctor-environment.js";
30
+ import { formattedShortcutPair } from "./shortcut-defs.js";
29
31
 
30
32
  function unitLabel(type: string): string {
31
33
  switch (type) {
@@ -203,7 +205,12 @@ export class GSDDashboardOverlay {
203
205
  }
204
206
 
205
207
  handleInput(data: string): void {
206
- if (matchesKey(data, Key.escape) || matchesKey(data, Key.ctrl("c")) || matchesKey(data, Key.ctrlAlt("g"))) {
208
+ if (
209
+ matchesKey(data, Key.escape) ||
210
+ matchesKey(data, Key.ctrl("c")) ||
211
+ matchesKey(data, Key.ctrlAlt("g")) ||
212
+ matchesKey(data, Key.ctrlShift("g"))
213
+ ) {
207
214
  this.dispose();
208
215
  this.onClose();
209
216
  return;
@@ -587,7 +594,7 @@ export class GSDDashboardOverlay {
587
594
 
588
595
  lines.push(blank());
589
596
  lines.push(hr());
590
- lines.push(centered(th.fg("dim", "↑↓ scroll · g/G top/end · esc close")));
597
+ lines.push(centered(th.fg("dim", `↑↓ scroll · g/G top/end · Esc/${formattedShortcutPair("dashboard")} close`)));
591
598
 
592
599
  return lines;
593
600
  }