gsd-pi 2.79.0 → 2.80.0

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/README.md +94 -47
  2. package/dist/resources/.managed-resources-content-hash +1 -1
  3. package/dist/resources/extensions/gsd/auto/contracts.js +1 -0
  4. package/dist/resources/extensions/gsd/auto/orchestrator.js +146 -0
  5. package/dist/resources/extensions/gsd/auto/phases.js +61 -7
  6. package/dist/resources/extensions/gsd/auto/session.js +8 -0
  7. package/dist/resources/extensions/gsd/auto-artifact-paths.js +2 -2
  8. package/dist/resources/extensions/gsd/auto-dispatch.js +2 -0
  9. package/dist/resources/extensions/gsd/auto-prompts.js +52 -29
  10. package/dist/resources/extensions/gsd/auto-recovery.js +63 -55
  11. package/dist/resources/extensions/gsd/auto-runtime-state.js +4 -0
  12. package/dist/resources/extensions/gsd/auto-start.js +3 -2
  13. package/dist/resources/extensions/gsd/auto.js +159 -2
  14. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +9 -1
  15. package/dist/resources/extensions/gsd/bootstrap/exec-tools.js +2 -2
  16. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +41 -45
  17. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +8 -8
  18. package/dist/resources/extensions/gsd/commands/context.js +1 -1
  19. package/dist/resources/extensions/gsd/gsd-db.js +34 -1
  20. package/dist/resources/extensions/gsd/guided-flow.js +40 -0
  21. package/dist/resources/extensions/gsd/paths.js +5 -1
  22. package/dist/resources/extensions/gsd/post-execution-checks.js +25 -6
  23. package/dist/resources/extensions/gsd/preferences-types.js +20 -2
  24. package/dist/resources/extensions/gsd/preferences-validation.js +3 -3
  25. package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +82 -2
  26. package/dist/resources/extensions/gsd/unit-context-composer.js +32 -0
  27. package/dist/resources/extensions/gsd/unit-context-manifest.js +21 -0
  28. package/dist/resources/extensions/gsd/uok/audit.js +23 -9
  29. package/dist/resources/extensions/gsd/uok/contracts.js +69 -1
  30. package/dist/resources/extensions/gsd/uok/dispatch-envelope.js +3 -0
  31. package/dist/resources/extensions/gsd/uok/loop-adapter.js +48 -33
  32. package/dist/resources/extensions/gsd/uok/timeline.js +125 -0
  33. package/dist/resources/extensions/shared/gsd-phase-state.js +45 -3
  34. package/dist/resources/extensions/shared/interview-ui.js +15 -4
  35. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  36. package/dist/web/standalone/.next/BUILD_ID +1 -1
  37. package/dist/web/standalone/.next/app-path-routes-manifest.json +9 -9
  38. package/dist/web/standalone/.next/build-manifest.json +2 -2
  39. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  40. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  41. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  42. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  43. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  44. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  45. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  46. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  47. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  48. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  49. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  50. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  51. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  52. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  53. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  54. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  55. package/dist/web/standalone/.next/server/app/index.html +1 -1
  56. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  57. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  58. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  59. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  60. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  61. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  62. package/dist/web/standalone/.next/server/app-paths-manifest.json +9 -9
  63. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  64. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  65. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  66. package/package.json +1 -1
  67. package/packages/daemon/package.json +2 -2
  68. package/packages/mcp-server/dist/workflow-tools.d.ts +1 -1
  69. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  70. package/packages/mcp-server/dist/workflow-tools.js +53 -0
  71. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  72. package/packages/mcp-server/package.json +2 -2
  73. package/packages/mcp-server/src/workflow-tools.test.ts +129 -2
  74. package/packages/mcp-server/src/workflow-tools.ts +81 -0
  75. package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
  76. package/packages/native/package.json +1 -1
  77. package/packages/pi-agent-core/package.json +1 -1
  78. package/packages/pi-ai/package.json +1 -1
  79. package/packages/pi-coding-agent/package.json +1 -1
  80. package/packages/pi-tui/package.json +1 -1
  81. package/packages/rpc-client/package.json +1 -1
  82. package/pkg/package.json +1 -1
  83. package/src/resources/extensions/gsd/auto/contracts.ts +87 -0
  84. package/src/resources/extensions/gsd/auto/loop-deps.ts +10 -3
  85. package/src/resources/extensions/gsd/auto/orchestrator.ts +161 -0
  86. package/src/resources/extensions/gsd/auto/phases.ts +88 -9
  87. package/src/resources/extensions/gsd/auto/session.ts +11 -0
  88. package/src/resources/extensions/gsd/auto-artifact-paths.ts +2 -2
  89. package/src/resources/extensions/gsd/auto-dispatch.ts +1 -0
  90. package/src/resources/extensions/gsd/auto-prompts.ts +106 -28
  91. package/src/resources/extensions/gsd/auto-recovery.ts +59 -53
  92. package/src/resources/extensions/gsd/auto-runtime-state.ts +7 -0
  93. package/src/resources/extensions/gsd/auto-start.ts +3 -2
  94. package/src/resources/extensions/gsd/auto.ts +167 -1
  95. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +14 -1
  96. package/src/resources/extensions/gsd/bootstrap/exec-tools.ts +2 -2
  97. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +49 -46
  98. package/src/resources/extensions/gsd/bootstrap/tests/write-gate-shouldblock-basepath.test.ts +97 -0
  99. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +8 -4
  100. package/src/resources/extensions/gsd/commands/context.ts +1 -1
  101. package/src/resources/extensions/gsd/gsd-db.ts +35 -1
  102. package/src/resources/extensions/gsd/guided-flow.ts +47 -0
  103. package/src/resources/extensions/gsd/interrupted-session.ts +1 -0
  104. package/src/resources/extensions/gsd/paths.ts +6 -1
  105. package/src/resources/extensions/gsd/post-execution-checks.ts +31 -6
  106. package/src/resources/extensions/gsd/preferences-types.ts +23 -4
  107. package/src/resources/extensions/gsd/preferences-validation.ts +3 -3
  108. package/src/resources/extensions/gsd/tests/auto-abort-pause-regression.test.ts +32 -0
  109. package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +353 -0
  110. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +108 -1
  111. package/src/resources/extensions/gsd/tests/auto-runtime-state.test.ts +39 -0
  112. package/src/resources/extensions/gsd/tests/auto-session-encapsulation.test.ts +3 -0
  113. package/src/resources/extensions/gsd/tests/bootstrap-derive-state-db-open.test.ts +2 -2
  114. package/src/resources/extensions/gsd/tests/check-auto-start-pending-gate.test.ts +203 -0
  115. package/src/resources/extensions/gsd/tests/check-auto-start-ready-guard.test.ts +148 -0
  116. package/src/resources/extensions/gsd/tests/current-directory-root-homedir-fallback.test.ts +63 -0
  117. package/src/resources/extensions/gsd/tests/deep-planning-mode-dispatch.test.ts +42 -0
  118. package/src/resources/extensions/gsd/tests/deep-project-auto-loop.test.ts +63 -2
  119. package/src/resources/extensions/gsd/tests/execute-summary-save-empty-project.test.ts +109 -0
  120. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +95 -0
  121. package/src/resources/extensions/gsd/tests/guided-flow-prompt-consolidation.test.ts +14 -0
  122. package/src/resources/extensions/gsd/tests/integration/auto-recovery.test.ts +79 -0
  123. package/src/resources/extensions/gsd/tests/journal-integration.test.ts +134 -0
  124. package/src/resources/extensions/gsd/tests/parallel-skill-prompt-integration.test.ts +8 -0
  125. package/src/resources/extensions/gsd/tests/paused-session-via-db.test.ts +2 -0
  126. package/src/resources/extensions/gsd/tests/plan-slice.test.ts +27 -0
  127. package/src/resources/extensions/gsd/tests/post-execution-checks.test.ts +46 -0
  128. package/src/resources/extensions/gsd/tests/pre-exec-gate-loop.test.ts +3 -0
  129. package/src/resources/extensions/gsd/tests/register-hooks-compaction-checkpoint.test.ts +85 -0
  130. package/src/resources/extensions/gsd/tests/run-uat-composer.test.ts +2 -0
  131. package/src/resources/extensions/gsd/tests/subagent-model-dispatch.test.ts +59 -0
  132. package/src/resources/extensions/gsd/tests/unit-context-composer.test.ts +38 -0
  133. package/src/resources/extensions/gsd/tests/unit-context-manifest.test.ts +32 -0
  134. package/src/resources/extensions/gsd/tests/uok-contracts.test.ts +109 -1
  135. package/src/resources/extensions/gsd/tests/uok-loop-adapter-writer.test.ts +98 -0
  136. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +132 -3
  137. package/src/resources/extensions/gsd/tests/worktree-path-injection.test.ts +3 -0
  138. package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +84 -1
  139. package/src/resources/extensions/gsd/unit-context-composer.ts +49 -0
  140. package/src/resources/extensions/gsd/unit-context-manifest.ts +34 -0
  141. package/src/resources/extensions/gsd/uok/audit.ts +25 -9
  142. package/src/resources/extensions/gsd/uok/contracts.ts +105 -0
  143. package/src/resources/extensions/gsd/uok/dispatch-envelope.ts +4 -0
  144. package/src/resources/extensions/gsd/uok/loop-adapter.ts +60 -45
  145. package/src/resources/extensions/gsd/uok/timeline.ts +158 -0
  146. package/src/resources/extensions/shared/gsd-phase-state.ts +56 -3
  147. package/src/resources/extensions/shared/interview-ui.ts +18 -5
  148. package/src/resources/extensions/shared/tests/gsd-phase-state.test.ts +43 -1
  149. package/src/resources/extensions/shared/tests/interview-notes-loop.test.ts +41 -0
  150. /package/dist/web/standalone/.next/static/{J-CU-p_sp45CJHT3R9TJS → V-3Ehy4B24f9FCGiLPWIM}/_buildManifest.js +0 -0
  151. /package/dist/web/standalone/.next/static/{J-CU-p_sp45CJHT3R9TJS → V-3Ehy4B24f9FCGiLPWIM}/_ssgManifest.js +0 -0
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gsd/pi-coding-agent",
3
- "version": "2.79.0",
3
+ "version": "2.80.0",
4
4
  "description": "Coding agent CLI (vendored from pi-mono)",
5
5
  "type": "module",
6
6
  "gsd": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gsd/pi-tui",
3
- "version": "2.79.0",
3
+ "version": "2.80.0",
4
4
  "description": "Terminal User Interface library (vendored from pi-mono)",
5
5
  "type": "module",
6
6
  "gsd": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gsd-build/rpc-client",
3
- "version": "2.79.0",
3
+ "version": "2.80.0",
4
4
  "description": "Standalone RPC client SDK for GSD — zero internal dependencies",
5
5
  "license": "MIT",
6
6
  "gsd": {
package/pkg/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glittercowboy/gsd",
3
- "version": "2.79.0",
3
+ "version": "2.80.0",
4
4
  "piConfig": {
5
5
  "name": "gsd",
6
6
  "configDir": ".gsd"
@@ -0,0 +1,87 @@
1
+ import type { GSDState } from "../types.js";
2
+
3
+ export interface AutoSessionContext {
4
+ basePath: string;
5
+ trigger: "guided-flow" | "resume" | "auto-loop" | "manual";
6
+ }
7
+
8
+ export interface AutoStatus {
9
+ phase: "idle" | "running" | "paused" | "stopped" | "error";
10
+ activeUnit?: {
11
+ unitType: string;
12
+ unitId: string;
13
+ };
14
+ lastTransitionAt?: number;
15
+ transitionCount: number;
16
+ }
17
+
18
+ export interface AutoAdvanceResult {
19
+ kind: "advanced" | "blocked" | "paused" | "stopped" | "error";
20
+ reason?: string;
21
+ stateSnapshot?: GSDState;
22
+ }
23
+
24
+ export interface AutoOrchestrationModule {
25
+ start(sessionContext: AutoSessionContext): Promise<AutoAdvanceResult>;
26
+ advance(): Promise<AutoAdvanceResult>;
27
+ resume(): Promise<AutoAdvanceResult>;
28
+ stop(reason: string): Promise<AutoAdvanceResult>;
29
+ getStatus(): AutoStatus;
30
+ }
31
+
32
+ export interface DispatchAdapter {
33
+ decideNextUnit(): Promise<{
34
+ unitType: string;
35
+ unitId: string;
36
+ reason: string;
37
+ preconditions: string[];
38
+ } | null>;
39
+ }
40
+
41
+ export interface RecoveryAdapter {
42
+ classifyAndRecover(input: {
43
+ error: unknown;
44
+ unitType?: string;
45
+ unitId?: string;
46
+ }): Promise<{
47
+ action: "retry" | "escalate" | "stop";
48
+ reason: string;
49
+ }>;
50
+ }
51
+
52
+ export interface WorktreeAdapter {
53
+ prepareForUnit(unitType: string, unitId: string): Promise<void>;
54
+ syncAfterUnit(unitType: string, unitId: string): Promise<void>;
55
+ cleanupOnStop(reason: string): Promise<void>;
56
+ }
57
+
58
+ export interface HealthAdapter {
59
+ preAdvanceGate(): Promise<{ allow: boolean; reason?: string }>;
60
+ postAdvanceRecord(result: AutoAdvanceResult): Promise<void>;
61
+ }
62
+
63
+ export interface RuntimePersistenceAdapter {
64
+ ensureLockOwnership(): Promise<void>;
65
+ journalTransition(event: {
66
+ name: string;
67
+ reason?: string;
68
+ unitType?: string;
69
+ unitId?: string;
70
+ }): Promise<void>;
71
+ }
72
+
73
+ export interface NotificationAdapter {
74
+ notifyLifecycle(event: {
75
+ name: string;
76
+ detail?: string;
77
+ }): Promise<void>;
78
+ }
79
+
80
+ export interface AutoOrchestratorDeps {
81
+ dispatch: DispatchAdapter;
82
+ recovery: RecoveryAdapter;
83
+ worktree: WorktreeAdapter;
84
+ health: HealthAdapter;
85
+ runtime: RuntimePersistenceAdapter;
86
+ notifications: NotificationAdapter;
87
+ }
@@ -7,6 +7,7 @@
7
7
  import type { ExtensionAPI, ExtensionContext } from "@gsd/pi-coding-agent";
8
8
 
9
9
  import type { AutoSession } from "./session.js";
10
+ import type { ErrorContext } from "./types.js";
10
11
  import type { GSDPreferences } from "../preferences.js";
11
12
  import type { GSDState } from "../types.js";
12
13
  import type { SessionLockStatus } from "../session-lock.js";
@@ -24,6 +25,12 @@ import type { MergeReconcileResult } from "../auto-recovery.js";
24
25
  import type { UokTurnObserver } from "../uok/contracts.js";
25
26
  import type { PreflightResult } from "../clean-root-preflight.js";
26
27
 
28
+ type PauseAutoFn = (
29
+ ctx?: ExtensionContext,
30
+ pi?: ExtensionAPI,
31
+ errorContext?: ErrorContext,
32
+ ) => Promise<void>;
33
+
27
34
  /**
28
35
  * Dependencies injected by the caller (auto.ts startAuto) so autoLoop
29
36
  * can access private functions from auto.ts without exporting them.
@@ -39,7 +46,7 @@ export interface LoopDeps {
39
46
  pi?: ExtensionAPI,
40
47
  reason?: string,
41
48
  ) => Promise<void>;
42
- pauseAuto: (ctx?: ExtensionContext, pi?: ExtensionAPI) => Promise<void>;
49
+ pauseAuto: PauseAutoFn;
43
50
  clearUnitTimeout: () => void;
44
51
  updateProgressWidget: (
45
52
  ctx: ExtensionContext,
@@ -245,7 +252,7 @@ export interface LoopDeps {
245
252
  prefs: GSDPreferences | undefined;
246
253
  buildSnapshotOpts: () => CloseoutOptions & Record<string, unknown>;
247
254
  buildRecoveryContext: () => unknown;
248
- pauseAuto: (ctx?: ExtensionContext, pi?: ExtensionAPI) => Promise<void>;
255
+ pauseAuto: PauseAutoFn;
249
256
  }) => void;
250
257
 
251
258
  // Prompt helpers
@@ -271,7 +278,7 @@ export interface LoopDeps {
271
278
  ) => Promise<"dispatched" | "continue" | "retry">;
272
279
  runPostUnitVerification: (
273
280
  vctx: VerificationContext,
274
- pauseAuto: (ctx?: ExtensionContext, pi?: ExtensionAPI) => Promise<void>,
281
+ pauseAuto: PauseAutoFn,
275
282
  ) => Promise<VerificationResult>;
276
283
  postUnitPostVerification: (
277
284
  pctx: PostUnitContext,
@@ -0,0 +1,161 @@
1
+ import type { AutoAdvanceResult, AutoOrchestrationModule, AutoOrchestratorDeps, AutoSessionContext, AutoStatus } from "./contracts.js";
2
+
3
+ function now(): number {
4
+ return Date.now();
5
+ }
6
+
7
+ export class AutoOrchestrator implements AutoOrchestrationModule {
8
+ private status: AutoStatus = {
9
+ phase: "idle",
10
+ transitionCount: 0,
11
+ };
12
+ private readonly deps: AutoOrchestratorDeps;
13
+ private lastAdvanceKey: string | null = null;
14
+
15
+ public constructor(deps: AutoOrchestratorDeps) {
16
+ this.deps = deps;
17
+ }
18
+
19
+ public async start(_sessionContext: AutoSessionContext): Promise<AutoAdvanceResult> {
20
+ this.lastAdvanceKey = null;
21
+ this.status.phase = "running";
22
+ this.bumpTransition();
23
+ await this.deps.runtime.journalTransition({ name: "start" });
24
+ await this.deps.notifications.notifyLifecycle({ name: "start" });
25
+ return this.advance();
26
+ }
27
+
28
+ public async advance(): Promise<AutoAdvanceResult> {
29
+ try {
30
+ await this.deps.runtime.ensureLockOwnership();
31
+ const gate = await this.deps.health.preAdvanceGate();
32
+ if (!gate.allow) {
33
+ const blocked: AutoAdvanceResult = { kind: "blocked", reason: gate.reason ?? "health gate blocked" };
34
+ await this.deps.runtime.journalTransition({ name: "advance-blocked", reason: blocked.reason });
35
+ await this.deps.health.postAdvanceRecord(blocked);
36
+ return blocked;
37
+ }
38
+
39
+ const decision = await this.deps.dispatch.decideNextUnit();
40
+ if (!decision) {
41
+ const stopped: AutoAdvanceResult = { kind: "stopped", reason: "no remaining units" };
42
+ this.status.phase = "stopped";
43
+ this.status.activeUnit = undefined;
44
+ this.lastAdvanceKey = null;
45
+ this.bumpTransition();
46
+ await this.deps.runtime.journalTransition({ name: "advance-stopped", reason: stopped.reason });
47
+ await this.deps.health.postAdvanceRecord(stopped);
48
+ return stopped;
49
+ }
50
+
51
+ const nextKey = `${decision.unitType}:${decision.unitId}`;
52
+ if (this.lastAdvanceKey === nextKey) {
53
+ const blocked: AutoAdvanceResult = { kind: "blocked", reason: "idempotent advance: unit already active" };
54
+ await this.deps.runtime.journalTransition({
55
+ name: "advance-blocked",
56
+ reason: blocked.reason,
57
+ unitType: decision.unitType,
58
+ unitId: decision.unitId,
59
+ });
60
+ await this.deps.health.postAdvanceRecord(blocked);
61
+ return blocked;
62
+ }
63
+
64
+ this.status.activeUnit = { unitType: decision.unitType, unitId: decision.unitId };
65
+ this.status.phase = "running";
66
+ this.lastAdvanceKey = nextKey;
67
+ this.bumpTransition();
68
+
69
+ await this.deps.runtime.journalTransition({
70
+ name: "advance",
71
+ reason: decision.reason,
72
+ unitType: decision.unitType,
73
+ unitId: decision.unitId,
74
+ });
75
+ await this.deps.worktree.prepareForUnit(decision.unitType, decision.unitId);
76
+ await this.deps.worktree.syncAfterUnit(decision.unitType, decision.unitId);
77
+
78
+ const advanced: AutoAdvanceResult = { kind: "advanced" };
79
+ await this.deps.health.postAdvanceRecord(advanced);
80
+ return advanced;
81
+ } catch (error) {
82
+ const recovery = await this.deps.recovery.classifyAndRecover({
83
+ error,
84
+ unitType: this.status.activeUnit?.unitType,
85
+ unitId: this.status.activeUnit?.unitId,
86
+ });
87
+ const result: AutoAdvanceResult = recovery.action === "retry"
88
+ ? { kind: "paused", reason: recovery.reason }
89
+ : recovery.action === "escalate"
90
+ ? { kind: "error", reason: recovery.reason }
91
+ : { kind: "stopped", reason: recovery.reason };
92
+
93
+ if (result.kind === "paused") {
94
+ this.status.phase = "paused";
95
+ } else if (result.kind === "stopped") {
96
+ this.status.phase = "stopped";
97
+ } else {
98
+ this.status.phase = "error";
99
+ }
100
+
101
+ if (result.kind === "stopped") {
102
+ this.lastAdvanceKey = null;
103
+ this.status.activeUnit = undefined;
104
+ }
105
+ this.bumpTransition();
106
+
107
+ const journalName = result.kind === "paused"
108
+ ? "advance-paused"
109
+ : result.kind === "stopped"
110
+ ? "advance-stopped"
111
+ : "advance-error";
112
+ await this.deps.runtime.journalTransition({ name: journalName, reason: recovery.reason });
113
+
114
+ if (result.kind === "paused") {
115
+ await this.deps.notifications.notifyLifecycle({ name: "pause", detail: recovery.reason });
116
+ } else if (result.kind === "stopped") {
117
+ await this.deps.notifications.notifyLifecycle({ name: "stopped", detail: recovery.reason });
118
+ } else if (result.kind === "error") {
119
+ await this.deps.notifications.notifyLifecycle({ name: "error", detail: recovery.reason });
120
+ }
121
+ await this.deps.health.postAdvanceRecord(result);
122
+ return result;
123
+ }
124
+ }
125
+
126
+ public async resume(): Promise<AutoAdvanceResult> {
127
+ this.lastAdvanceKey = null;
128
+ this.status.phase = "running";
129
+ this.bumpTransition();
130
+ await this.deps.runtime.journalTransition({ name: "resume" });
131
+ await this.deps.notifications.notifyLifecycle({ name: "resume" });
132
+ return this.advance();
133
+ }
134
+
135
+ public async stop(reason: string): Promise<AutoAdvanceResult> {
136
+ if (this.status.phase === "stopped") {
137
+ return { kind: "stopped", reason };
138
+ }
139
+ await this.deps.worktree.cleanupOnStop(reason);
140
+ this.status.phase = "stopped";
141
+ this.status.activeUnit = undefined;
142
+ this.lastAdvanceKey = null;
143
+ this.bumpTransition();
144
+ await this.deps.runtime.journalTransition({ name: "stop", reason });
145
+ await this.deps.notifications.notifyLifecycle({ name: "stop", detail: reason });
146
+ return { kind: "stopped", reason };
147
+ }
148
+
149
+ public getStatus(): AutoStatus {
150
+ return { ...this.status, activeUnit: this.status.activeUnit ? { ...this.status.activeUnit } : undefined };
151
+ }
152
+
153
+ private bumpTransition(): void {
154
+ this.status.transitionCount += 1;
155
+ this.status.lastTransitionAt = now();
156
+ }
157
+ }
158
+
159
+ export function createAutoOrchestrator(deps: AutoOrchestratorDeps): AutoOrchestrationModule {
160
+ return new AutoOrchestrator(deps);
161
+ }
@@ -55,7 +55,7 @@ import { writeUnitRuntimeRecord } from "../unit-runtime.js";
55
55
  import { withTimeout, FINALIZE_PRE_TIMEOUT_MS, FINALIZE_POST_TIMEOUT_MS } from "./finalize-timeout.js";
56
56
  import { getEligibleSlices } from "../slice-parallel-eligibility.js";
57
57
  import { startSliceParallel } from "../slice-parallel-orchestrator.js";
58
- import { isDbAvailable, getMilestoneSlices } from "../gsd-db.js";
58
+ import { isDbAvailable, getMilestoneSlices, refreshOpenDatabaseFromDisk } from "../gsd-db.js";
59
59
  import type { MinimalModelRegistry } from "../context-budget.js";
60
60
  import { ensurePlanV2Graph, isEmptyPlanV2GraphResult, isMissingFinalizedContextResult } from "../uok/plan-v2.js";
61
61
  import { resolveUokFlags } from "../uok/flags.js";
@@ -76,6 +76,12 @@ function isSamePathLocal(a: string, b: string): boolean {
76
76
  return normalizeWorktreePathForCompare(a) === normalizeWorktreePathForCompare(b);
77
77
  }
78
78
 
79
+ function refreshPlanSliceRecoveryDbIfNeeded(unitType: string): boolean {
80
+ if (unitType !== "plan-slice") return true;
81
+ if (!isDbAvailable()) return true;
82
+ return refreshOpenDatabaseFromDisk();
83
+ }
84
+
79
85
  // ─── Session timeout auto-resume state ────────────────────────────────────────
80
86
 
81
87
  let consecutiveSessionTimeouts = 0;
@@ -232,6 +238,33 @@ async function emitCancelledUnitEnd(
232
238
  });
233
239
  }
234
240
 
241
+ export function _buildCancelledUnitStopReason(
242
+ unitType: string,
243
+ unitId: string,
244
+ errorContext?: { message: string; category: string },
245
+ ): {
246
+ notifyMessage: string;
247
+ stopReason: string;
248
+ loopReason: "session-failed" | "unit-aborted";
249
+ } {
250
+ const cancellationMessage = errorContext?.message ?? "unknown";
251
+ const isSessionCreationFailure = errorContext?.category === "session-failed";
252
+
253
+ if (isSessionCreationFailure) {
254
+ return {
255
+ notifyMessage: `Session creation failed for ${unitType} ${unitId}: ${cancellationMessage}. Stopping auto-mode.`,
256
+ stopReason: `Session creation failed: ${cancellationMessage}`,
257
+ loopReason: "session-failed",
258
+ };
259
+ }
260
+
261
+ return {
262
+ notifyMessage: `Unit ${unitType} ${unitId} aborted after dispatch: ${cancellationMessage}. Stopping auto-mode.`,
263
+ stopReason: `Unit aborted: ${cancellationMessage}`,
264
+ loopReason: "unit-aborted",
265
+ };
266
+ }
267
+
235
268
  async function failClosedOnFinalizeTimeout(
236
269
  ic: IterationContext,
237
270
  iterData: IterationData,
@@ -994,7 +1027,10 @@ export async function runDispatch(
994
1027
  // See: https://github.com/gsd-build/gsd-2/issues/2474
995
1028
  if (dispatchResult.level === "warning") {
996
1029
  ctx.ui.notify(dispatchResult.reason, "warning");
997
- await deps.pauseAuto(ctx, pi);
1030
+ await deps.pauseAuto(ctx, pi, {
1031
+ message: dispatchResult.reason,
1032
+ category: "unknown",
1033
+ });
998
1034
  } else {
999
1035
  await closeoutAndStop(ctx, pi, s, deps, dispatchResult.reason);
1000
1036
  }
@@ -1065,7 +1101,16 @@ export async function runDispatch(
1065
1101
  `Stuck recovery: artifact for ${unitType} ${unitId} found on disk. Invalidating caches.`,
1066
1102
  "info",
1067
1103
  );
1104
+ if (!refreshPlanSliceRecoveryDbIfNeeded(unitType)) {
1105
+ ctx.ui.notify(
1106
+ `Stuck recovery found ${unitType} ${unitId} artifacts, but the DB refresh failed. Keeping stuck state for retry.`,
1107
+ "warning",
1108
+ );
1109
+ return { action: "continue" };
1110
+ }
1068
1111
  deps.invalidateAllCaches();
1112
+ loopState.recentUnits.length = 0;
1113
+ loopState.stuckRecoveryAttempts = 0;
1069
1114
  return { action: "continue" };
1070
1115
  }
1071
1116
  ctx.ui.notify(
@@ -1075,6 +1120,32 @@ export async function runDispatch(
1075
1120
  deps.invalidateAllCaches();
1076
1121
  } else {
1077
1122
  // Level 2: hard stop — genuinely stuck
1123
+ deps.invalidateAllCaches();
1124
+ const artifactExists = verifyExpectedArtifact(
1125
+ unitType,
1126
+ unitId,
1127
+ s.basePath,
1128
+ );
1129
+ if (artifactExists && unitType !== "complete-milestone") {
1130
+ debugLog("autoLoop", {
1131
+ phase: "stuck-recovery",
1132
+ level: 2,
1133
+ action: "artifact-found",
1134
+ });
1135
+ ctx.ui.notify(
1136
+ `Stuck recovery: artifact for ${unitType} ${unitId} found on disk after cache invalidation. Continuing.`,
1137
+ "info",
1138
+ );
1139
+ if (refreshPlanSliceRecoveryDbIfNeeded(unitType)) {
1140
+ loopState.recentUnits.length = 0;
1141
+ loopState.stuckRecoveryAttempts = 0;
1142
+ return { action: "continue" };
1143
+ }
1144
+ ctx.ui.notify(
1145
+ `Stuck recovery found ${unitType} ${unitId} artifacts, but the DB refresh failed. Stopping for manual recovery.`,
1146
+ "warning",
1147
+ );
1148
+ }
1078
1149
  debugLog("autoLoop", {
1079
1150
  phase: "stuck-detected",
1080
1151
  unitType,
@@ -1470,7 +1541,12 @@ export async function runUnitPhase(
1470
1541
  s.lastGitActionFailure = null;
1471
1542
  s.lastGitActionStatus = null;
1472
1543
  s.lastUnitAgentEndMessages = null;
1473
- setCurrentPhase(unitType);
1544
+ setCurrentPhase(unitType, {
1545
+ basePath: s.basePath,
1546
+ traceId: ic.flowId,
1547
+ turnId: `iter-${ic.iteration}`,
1548
+ causedBy: "unit-start",
1549
+ });
1474
1550
  s.lastToolInvocationError = null; // #2883: clear stale error from previous unit
1475
1551
  const unitStartSeq = ic.nextSeq();
1476
1552
  deps.emitJournalEvent({ ts: new Date().toISOString(), flowId: ic.flowId, seq: unitStartSeq, eventType: "unit-start", data: { unitType, unitId } });
@@ -1866,13 +1942,16 @@ export async function runUnitPhase(
1866
1942
  }
1867
1943
  await deps.autoCommitUnit?.(s.basePath, unitType, unitId, ctx);
1868
1944
  await emitCancelledUnitEnd(ic, unitType, unitId, unitStartSeq, unitResult.errorContext);
1869
- ctx.ui.notify(
1870
- `Session creation failed for ${unitType} ${unitId}: ${unitResult.errorContext?.message ?? "unknown"}. Stopping auto-mode.`,
1871
- "warning",
1945
+
1946
+ const cancelledStop = _buildCancelledUnitStopReason(
1947
+ unitType,
1948
+ unitId,
1949
+ unitResult.errorContext,
1872
1950
  );
1873
- await deps.stopAuto(ctx, pi, `Session creation failed: ${unitResult.errorContext?.message ?? "unknown"}`);
1874
- debugLog("autoLoop", { phase: "exit", reason: "session-failed" });
1875
- return { action: "break", reason: "session-failed" };
1951
+ ctx.ui.notify(cancelledStop.notifyMessage, "warning");
1952
+ await deps.stopAuto(ctx, pi, cancelledStop.stopReason);
1953
+ debugLog("autoLoop", { phase: "exit", reason: cancelledStop.loopReason });
1954
+ return { action: "break", reason: cancelledStop.loopReason };
1876
1955
  }
1877
1956
 
1878
1957
  // ── Immediate unit closeout (metrics, activity log, memory) ────────
@@ -21,6 +21,7 @@ import type { ExtensionAPI, ExtensionCommandContext } from "@gsd/pi-coding-agent
21
21
  import type { GitServiceImpl } from "../git-service.js";
22
22
  import type { CaptureEntry } from "../captures.js";
23
23
  import type { BudgetAlertLevel } from "../auto-budget.js";
24
+ import type { AutoOrchestrationModule } from "./contracts.js";
24
25
  import { resolveWorktreeProjectRoot } from "../worktree-root.js";
25
26
  import { normalizeRealPath } from "../paths.js";
26
27
  import type { MilestoneScope } from "../workspace.js";
@@ -229,6 +230,9 @@ export class AutoSession {
229
230
  /** Cleanup function returned by startCommandPolling(); null when not running. */
230
231
  commandPollingCleanup: (() => void) | null = null;
231
232
 
233
+ // ── Orchestration seam ───────────────────────────────────────────────────
234
+ orchestration: AutoOrchestrationModule | null = null;
235
+
232
236
  // ── Loop promise state ──────────────────────────────────────────────────
233
237
  // Per-unit resolve function and session-switch guard live at module level
234
238
  // in auto-loop.ts (_currentResolve, _sessionSwitchInFlight).
@@ -354,10 +358,14 @@ export class AutoSession {
354
358
  // Remote command polling — cleanup must be called before reset (auto.ts stopAuto)
355
359
  this.commandPollingCleanup = null;
356
360
 
361
+ // Orchestration seam
362
+ this.orchestration = null;
363
+
357
364
  // Loop promise state lives in auto-loop.ts module scope
358
365
  }
359
366
 
360
367
  toJSON(): Record<string, unknown> {
368
+ const orchestrationStatus = this.orchestration?.getStatus();
361
369
  return {
362
370
  active: this.active,
363
371
  paused: this.paused,
@@ -367,6 +375,9 @@ export class AutoSession {
367
375
  activeRunDir: this.activeRunDir,
368
376
  currentMilestoneId: this.currentMilestoneId,
369
377
  currentUnit: this.currentUnit,
378
+ orchestrationPhase: orchestrationStatus?.phase,
379
+ orchestrationTransitionCount: orchestrationStatus?.transitionCount,
380
+ orchestrationLastTransitionAt: orchestrationStatus?.lastTransitionAt,
370
381
  unitDispatchCount: Object.fromEntries(this.unitDispatchCount),
371
382
  };
372
383
  }
@@ -159,9 +159,9 @@ export function diagnoseExpectedArtifact(
159
159
  }
160
160
  return `${relSliceFile(base, mid, sid!, "RESEARCH")} (slice research)`;
161
161
  case "plan-slice":
162
- return `${relSliceFile(base, mid, sid!, "PLAN")} (slice plan)`;
162
+ return `${relSliceFile(base, mid, sid!, "PLAN")} plus tasks/T##-PLAN.md files (slice plan and task plans)`;
163
163
  case "refine-slice":
164
- return `${relSliceFile(base, mid, sid!, "PLAN")} (refined slice plan from sketch)`;
164
+ return `${relSliceFile(base, mid, sid!, "PLAN")} plus tasks/T##-PLAN.md files (refined slice plan and task plans)`;
165
165
  case "execute-task": {
166
166
  return `Task ${tid} marked [x] in ${relSliceFile(base, mid, sid!, "PLAN")} + summary written`;
167
167
  }
@@ -692,6 +692,7 @@ export const DISPATCH_RULES: DispatchRule[] = [
692
692
  const contextFile = resolveMilestoneFile(basePath, mid, "CONTEXT");
693
693
  const hasContext = !!(contextFile && (await loadFile(contextFile)));
694
694
  if (hasContext) return null; // fall through to next rule
695
+ if (prefs?.planning_depth === "deep") return null;
695
696
  // H6 fix (#4973): keep the non-deep auto-mode bypass, but do not
696
697
  // pre-verify deep planning's user-facing milestone approval gate.
697
698
  if (shouldBypassMilestoneDepthGateInAuto(prefs)) {