gsd-pi 2.79.0-dev.ece5fd8ba → 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 (193) hide show
  1. package/dist/loader.js +0 -0
  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 +55 -6
  6. package/dist/resources/extensions/gsd/auto/session.js +8 -0
  7. package/dist/resources/extensions/gsd/auto-recovery.js +45 -52
  8. package/dist/resources/extensions/gsd/auto-runtime-state.js +4 -0
  9. package/dist/resources/extensions/gsd/auto.js +159 -2
  10. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +9 -1
  11. package/dist/resources/extensions/gsd/gsd-db.js +34 -1
  12. package/dist/resources/extensions/gsd/post-execution-checks.js +25 -6
  13. package/dist/resources/extensions/shared/interview-ui.js +15 -4
  14. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  15. package/dist/web/standalone/.next/BUILD_ID +1 -1
  16. package/dist/web/standalone/.next/app-path-routes-manifest.json +15 -15
  17. package/dist/web/standalone/.next/build-manifest.json +3 -3
  18. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  19. package/dist/web/standalone/.next/required-server-files.json +3 -3
  20. package/dist/web/standalone/.next/server/app/_global-error/page.js +3 -3
  21. package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  22. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  23. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  24. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  25. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  26. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  27. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  28. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  29. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  30. package/dist/web/standalone/.next/server/app/_not-found/page.js +2 -2
  31. package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  32. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  33. package/dist/web/standalone/.next/server/app/_not-found.rsc +3 -3
  34. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
  35. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  36. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +3 -3
  37. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  38. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  39. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  40. package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
  41. package/dist/web/standalone/.next/server/app/api/boot/route_client-reference-manifest.js +1 -1
  42. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
  43. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route_client-reference-manifest.js +1 -1
  44. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
  45. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route_client-reference-manifest.js +1 -1
  46. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
  47. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route_client-reference-manifest.js +1 -1
  48. package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
  49. package/dist/web/standalone/.next/server/app/api/browse-directories/route_client-reference-manifest.js +1 -1
  50. package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
  51. package/dist/web/standalone/.next/server/app/api/captures/route_client-reference-manifest.js +1 -1
  52. package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
  53. package/dist/web/standalone/.next/server/app/api/cleanup/route_client-reference-manifest.js +1 -1
  54. package/dist/web/standalone/.next/server/app/api/dev-mode/route.js +1 -1
  55. package/dist/web/standalone/.next/server/app/api/dev-mode/route_client-reference-manifest.js +1 -1
  56. package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
  57. package/dist/web/standalone/.next/server/app/api/doctor/route_client-reference-manifest.js +1 -1
  58. package/dist/web/standalone/.next/server/app/api/experimental/route.js +2 -2
  59. package/dist/web/standalone/.next/server/app/api/experimental/route_client-reference-manifest.js +1 -1
  60. package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
  61. package/dist/web/standalone/.next/server/app/api/export-data/route_client-reference-manifest.js +1 -1
  62. package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
  63. package/dist/web/standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
  64. package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
  65. package/dist/web/standalone/.next/server/app/api/forensics/route_client-reference-manifest.js +1 -1
  66. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  67. package/dist/web/standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
  68. package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
  69. package/dist/web/standalone/.next/server/app/api/history/route_client-reference-manifest.js +1 -1
  70. package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
  71. package/dist/web/standalone/.next/server/app/api/hooks/route_client-reference-manifest.js +1 -1
  72. package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
  73. package/dist/web/standalone/.next/server/app/api/inspect/route_client-reference-manifest.js +1 -1
  74. package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
  75. package/dist/web/standalone/.next/server/app/api/knowledge/route_client-reference-manifest.js +1 -1
  76. package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
  77. package/dist/web/standalone/.next/server/app/api/live-state/route_client-reference-manifest.js +1 -1
  78. package/dist/web/standalone/.next/server/app/api/notifications/route.js +2 -2
  79. package/dist/web/standalone/.next/server/app/api/notifications/route_client-reference-manifest.js +1 -1
  80. package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
  81. package/dist/web/standalone/.next/server/app/api/onboarding/route_client-reference-manifest.js +1 -1
  82. package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
  83. package/dist/web/standalone/.next/server/app/api/preferences/route_client-reference-manifest.js +1 -1
  84. package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
  85. package/dist/web/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
  86. package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
  87. package/dist/web/standalone/.next/server/app/api/recovery/route_client-reference-manifest.js +1 -1
  88. package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +2 -2
  89. package/dist/web/standalone/.next/server/app/api/remote-questions/route_client-reference-manifest.js +1 -1
  90. package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
  91. package/dist/web/standalone/.next/server/app/api/session/browser/route_client-reference-manifest.js +1 -1
  92. package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
  93. package/dist/web/standalone/.next/server/app/api/session/command/route_client-reference-manifest.js +1 -1
  94. package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
  95. package/dist/web/standalone/.next/server/app/api/session/events/route_client-reference-manifest.js +1 -1
  96. package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
  97. package/dist/web/standalone/.next/server/app/api/session/manage/route_client-reference-manifest.js +1 -1
  98. package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
  99. package/dist/web/standalone/.next/server/app/api/settings-data/route_client-reference-manifest.js +1 -1
  100. package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
  101. package/dist/web/standalone/.next/server/app/api/shutdown/route_client-reference-manifest.js +1 -1
  102. package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
  103. package/dist/web/standalone/.next/server/app/api/skill-health/route_client-reference-manifest.js +1 -1
  104. package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
  105. package/dist/web/standalone/.next/server/app/api/steer/route_client-reference-manifest.js +1 -1
  106. package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
  107. package/dist/web/standalone/.next/server/app/api/switch-root/route_client-reference-manifest.js +1 -1
  108. package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +1 -1
  109. package/dist/web/standalone/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -1
  110. package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
  111. package/dist/web/standalone/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +1 -1
  112. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +1 -1
  113. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route_client-reference-manifest.js +1 -1
  114. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +2 -2
  115. package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -1
  116. package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
  117. package/dist/web/standalone/.next/server/app/api/terminal/upload/route_client-reference-manifest.js +1 -1
  118. package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
  119. package/dist/web/standalone/.next/server/app/api/undo/route_client-reference-manifest.js +1 -1
  120. package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
  121. package/dist/web/standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
  122. package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
  123. package/dist/web/standalone/.next/server/app/api/visualizer/route_client-reference-manifest.js +1 -1
  124. package/dist/web/standalone/.next/server/app/index.html +1 -1
  125. package/dist/web/standalone/.next/server/app/index.rsc +4 -4
  126. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  127. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +4 -4
  128. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  129. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +3 -3
  130. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  131. package/dist/web/standalone/.next/server/app/page.js +2 -2
  132. package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  133. package/dist/web/standalone/.next/server/app-paths-manifest.json +15 -15
  134. package/dist/web/standalone/.next/server/chunks/63.js +3 -3
  135. package/dist/web/standalone/.next/server/chunks/6897.js +1 -1
  136. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  137. package/dist/web/standalone/.next/server/middleware.js +2 -2
  138. package/dist/web/standalone/.next/server/next-font-manifest.js +1 -1
  139. package/dist/web/standalone/.next/server/next-font-manifest.json +1 -1
  140. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  141. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  142. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  143. package/dist/web/standalone/.next/static/chunks/app/_not-found/{page-f2a7482d42a5614b.js → page-2f24283c162b6ab3.js} +1 -1
  144. package/dist/web/standalone/.next/static/chunks/app/{layout-a16c7a7ecdf0c2cf.js → layout-9ecfd95f343793f0.js} +1 -1
  145. package/dist/web/standalone/.next/static/chunks/app/page-ff639266d978f2a0.js +1 -0
  146. package/dist/web/standalone/.next/static/chunks/main-app-d3d4c336195465f9.js +1 -0
  147. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-ab5a8926e07ec673.js +1 -0
  148. package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
  149. package/dist/web/standalone/node_modules/node-pty/build/Release/pty.node +0 -0
  150. package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
  151. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api.target.mk +14 -14
  152. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_except.target.mk +14 -14
  153. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_maybe.target.mk +14 -14
  154. package/dist/web/standalone/server.js +1 -1
  155. package/package.json +1 -1
  156. package/packages/daemon/package.json +2 -2
  157. package/packages/mcp-server/package.json +2 -2
  158. package/packages/native/package.json +1 -1
  159. package/packages/pi-agent-core/package.json +1 -1
  160. package/packages/pi-ai/package.json +1 -1
  161. package/packages/pi-coding-agent/package.json +1 -1
  162. package/packages/pi-tui/package.json +1 -1
  163. package/packages/rpc-client/package.json +1 -1
  164. package/pkg/package.json +1 -1
  165. package/src/resources/extensions/gsd/auto/contracts.ts +87 -0
  166. package/src/resources/extensions/gsd/auto/loop-deps.ts +10 -3
  167. package/src/resources/extensions/gsd/auto/orchestrator.ts +161 -0
  168. package/src/resources/extensions/gsd/auto/phases.ts +82 -8
  169. package/src/resources/extensions/gsd/auto/session.ts +11 -0
  170. package/src/resources/extensions/gsd/auto-recovery.ts +42 -50
  171. package/src/resources/extensions/gsd/auto-runtime-state.ts +7 -0
  172. package/src/resources/extensions/gsd/auto.ts +167 -1
  173. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +14 -1
  174. package/src/resources/extensions/gsd/gsd-db.ts +35 -1
  175. package/src/resources/extensions/gsd/interrupted-session.ts +1 -0
  176. package/src/resources/extensions/gsd/post-execution-checks.ts +31 -6
  177. package/src/resources/extensions/gsd/tests/auto-abort-pause-regression.test.ts +32 -0
  178. package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +353 -0
  179. package/src/resources/extensions/gsd/tests/auto-runtime-state.test.ts +39 -0
  180. package/src/resources/extensions/gsd/tests/auto-session-encapsulation.test.ts +3 -0
  181. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +95 -0
  182. package/src/resources/extensions/gsd/tests/integration/auto-recovery.test.ts +79 -0
  183. package/src/resources/extensions/gsd/tests/journal-integration.test.ts +134 -0
  184. package/src/resources/extensions/gsd/tests/paused-session-via-db.test.ts +2 -0
  185. package/src/resources/extensions/gsd/tests/plan-slice.test.ts +27 -0
  186. package/src/resources/extensions/gsd/tests/post-execution-checks.test.ts +46 -0
  187. package/src/resources/extensions/shared/interview-ui.ts +18 -5
  188. package/src/resources/extensions/shared/tests/interview-notes-loop.test.ts +41 -0
  189. package/dist/web/standalone/.next/static/chunks/app/page-fab3ebb85b006001.js +0 -1
  190. package/dist/web/standalone/.next/static/chunks/main-app-fdab67f7802d7832.js +0 -1
  191. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-459824ffb8c323dd.js +0 -1
  192. /package/dist/web/standalone/.next/static/{TzEVJ1Lh8vbez4n4Q9TqQ → V-3Ehy4B24f9FCGiLPWIM}/_buildManifest.js +0 -0
  193. /package/dist/web/standalone/.next/static/{TzEVJ1Lh8vbez4n4Q9TqQ → V-3Ehy4B24f9FCGiLPWIM}/_ssgManifest.js +0 -0
package/dist/loader.js CHANGED
File without changes
@@ -1 +1 @@
1
- 76e1a0d39c0a27d8
1
+ 63159c1114dd0d48
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,146 @@
1
+ function now() {
2
+ return Date.now();
3
+ }
4
+ export class AutoOrchestrator {
5
+ status = {
6
+ phase: "idle",
7
+ transitionCount: 0,
8
+ };
9
+ deps;
10
+ lastAdvanceKey = null;
11
+ constructor(deps) {
12
+ this.deps = deps;
13
+ }
14
+ async start(_sessionContext) {
15
+ this.lastAdvanceKey = null;
16
+ this.status.phase = "running";
17
+ this.bumpTransition();
18
+ await this.deps.runtime.journalTransition({ name: "start" });
19
+ await this.deps.notifications.notifyLifecycle({ name: "start" });
20
+ return this.advance();
21
+ }
22
+ async advance() {
23
+ try {
24
+ await this.deps.runtime.ensureLockOwnership();
25
+ const gate = await this.deps.health.preAdvanceGate();
26
+ if (!gate.allow) {
27
+ const blocked = { kind: "blocked", reason: gate.reason ?? "health gate blocked" };
28
+ await this.deps.runtime.journalTransition({ name: "advance-blocked", reason: blocked.reason });
29
+ await this.deps.health.postAdvanceRecord(blocked);
30
+ return blocked;
31
+ }
32
+ const decision = await this.deps.dispatch.decideNextUnit();
33
+ if (!decision) {
34
+ const stopped = { kind: "stopped", reason: "no remaining units" };
35
+ this.status.phase = "stopped";
36
+ this.status.activeUnit = undefined;
37
+ this.lastAdvanceKey = null;
38
+ this.bumpTransition();
39
+ await this.deps.runtime.journalTransition({ name: "advance-stopped", reason: stopped.reason });
40
+ await this.deps.health.postAdvanceRecord(stopped);
41
+ return stopped;
42
+ }
43
+ const nextKey = `${decision.unitType}:${decision.unitId}`;
44
+ if (this.lastAdvanceKey === nextKey) {
45
+ const blocked = { kind: "blocked", reason: "idempotent advance: unit already active" };
46
+ await this.deps.runtime.journalTransition({
47
+ name: "advance-blocked",
48
+ reason: blocked.reason,
49
+ unitType: decision.unitType,
50
+ unitId: decision.unitId,
51
+ });
52
+ await this.deps.health.postAdvanceRecord(blocked);
53
+ return blocked;
54
+ }
55
+ this.status.activeUnit = { unitType: decision.unitType, unitId: decision.unitId };
56
+ this.status.phase = "running";
57
+ this.lastAdvanceKey = nextKey;
58
+ this.bumpTransition();
59
+ await this.deps.runtime.journalTransition({
60
+ name: "advance",
61
+ reason: decision.reason,
62
+ unitType: decision.unitType,
63
+ unitId: decision.unitId,
64
+ });
65
+ await this.deps.worktree.prepareForUnit(decision.unitType, decision.unitId);
66
+ await this.deps.worktree.syncAfterUnit(decision.unitType, decision.unitId);
67
+ const advanced = { kind: "advanced" };
68
+ await this.deps.health.postAdvanceRecord(advanced);
69
+ return advanced;
70
+ }
71
+ catch (error) {
72
+ const recovery = await this.deps.recovery.classifyAndRecover({
73
+ error,
74
+ unitType: this.status.activeUnit?.unitType,
75
+ unitId: this.status.activeUnit?.unitId,
76
+ });
77
+ const result = recovery.action === "retry"
78
+ ? { kind: "paused", reason: recovery.reason }
79
+ : recovery.action === "escalate"
80
+ ? { kind: "error", reason: recovery.reason }
81
+ : { kind: "stopped", reason: recovery.reason };
82
+ if (result.kind === "paused") {
83
+ this.status.phase = "paused";
84
+ }
85
+ else if (result.kind === "stopped") {
86
+ this.status.phase = "stopped";
87
+ }
88
+ else {
89
+ this.status.phase = "error";
90
+ }
91
+ if (result.kind === "stopped") {
92
+ this.lastAdvanceKey = null;
93
+ this.status.activeUnit = undefined;
94
+ }
95
+ this.bumpTransition();
96
+ const journalName = result.kind === "paused"
97
+ ? "advance-paused"
98
+ : result.kind === "stopped"
99
+ ? "advance-stopped"
100
+ : "advance-error";
101
+ await this.deps.runtime.journalTransition({ name: journalName, reason: recovery.reason });
102
+ if (result.kind === "paused") {
103
+ await this.deps.notifications.notifyLifecycle({ name: "pause", detail: recovery.reason });
104
+ }
105
+ else if (result.kind === "stopped") {
106
+ await this.deps.notifications.notifyLifecycle({ name: "stopped", detail: recovery.reason });
107
+ }
108
+ else if (result.kind === "error") {
109
+ await this.deps.notifications.notifyLifecycle({ name: "error", detail: recovery.reason });
110
+ }
111
+ await this.deps.health.postAdvanceRecord(result);
112
+ return result;
113
+ }
114
+ }
115
+ async resume() {
116
+ this.lastAdvanceKey = null;
117
+ this.status.phase = "running";
118
+ this.bumpTransition();
119
+ await this.deps.runtime.journalTransition({ name: "resume" });
120
+ await this.deps.notifications.notifyLifecycle({ name: "resume" });
121
+ return this.advance();
122
+ }
123
+ async stop(reason) {
124
+ if (this.status.phase === "stopped") {
125
+ return { kind: "stopped", reason };
126
+ }
127
+ await this.deps.worktree.cleanupOnStop(reason);
128
+ this.status.phase = "stopped";
129
+ this.status.activeUnit = undefined;
130
+ this.lastAdvanceKey = null;
131
+ this.bumpTransition();
132
+ await this.deps.runtime.journalTransition({ name: "stop", reason });
133
+ await this.deps.notifications.notifyLifecycle({ name: "stop", detail: reason });
134
+ return { kind: "stopped", reason };
135
+ }
136
+ getStatus() {
137
+ return { ...this.status, activeUnit: this.status.activeUnit ? { ...this.status.activeUnit } : undefined };
138
+ }
139
+ bumpTransition() {
140
+ this.status.transitionCount += 1;
141
+ this.status.lastTransitionAt = now();
142
+ }
143
+ }
144
+ export function createAutoOrchestrator(deps) {
145
+ return new AutoOrchestrator(deps);
146
+ }
@@ -28,7 +28,7 @@ import { writeUnitRuntimeRecord } from "../unit-runtime.js";
28
28
  import { withTimeout, FINALIZE_PRE_TIMEOUT_MS, FINALIZE_POST_TIMEOUT_MS } from "./finalize-timeout.js";
29
29
  import { getEligibleSlices } from "../slice-parallel-eligibility.js";
30
30
  import { startSliceParallel } from "../slice-parallel-orchestrator.js";
31
- import { isDbAvailable, getMilestoneSlices } from "../gsd-db.js";
31
+ import { isDbAvailable, getMilestoneSlices, refreshOpenDatabaseFromDisk } from "../gsd-db.js";
32
32
  import { ensurePlanV2Graph, isEmptyPlanV2GraphResult, isMissingFinalizedContextResult } from "../uok/plan-v2.js";
33
33
  import { resolveUokFlags } from "../uok/flags.js";
34
34
  import { UokGateRunner } from "../uok/gate-runner.js";
@@ -42,6 +42,13 @@ import { getWorkflowTransportSupportError, getRequiredWorkflowToolsForAutoUnit,
42
42
  function isSamePathLocal(a, b) {
43
43
  return normalizeWorktreePathForCompare(a) === normalizeWorktreePathForCompare(b);
44
44
  }
45
+ function refreshPlanSliceRecoveryDbIfNeeded(unitType) {
46
+ if (unitType !== "plan-slice")
47
+ return true;
48
+ if (!isDbAvailable())
49
+ return true;
50
+ return refreshOpenDatabaseFromDisk();
51
+ }
45
52
  // ─── Session timeout auto-resume state ────────────────────────────────────────
46
53
  let consecutiveSessionTimeouts = 0;
47
54
  const MAX_SESSION_TIMEOUT_AUTO_RESUMES = 3;
@@ -145,6 +152,22 @@ async function emitCancelledUnitEnd(ic, unitType, unitId, unitStartSeq, errorCon
145
152
  causedBy: { flowId: ic.flowId, seq: unitStartSeq },
146
153
  });
147
154
  }
155
+ export function _buildCancelledUnitStopReason(unitType, unitId, errorContext) {
156
+ const cancellationMessage = errorContext?.message ?? "unknown";
157
+ const isSessionCreationFailure = errorContext?.category === "session-failed";
158
+ if (isSessionCreationFailure) {
159
+ return {
160
+ notifyMessage: `Session creation failed for ${unitType} ${unitId}: ${cancellationMessage}. Stopping auto-mode.`,
161
+ stopReason: `Session creation failed: ${cancellationMessage}`,
162
+ loopReason: "session-failed",
163
+ };
164
+ }
165
+ return {
166
+ notifyMessage: `Unit ${unitType} ${unitId} aborted after dispatch: ${cancellationMessage}. Stopping auto-mode.`,
167
+ stopReason: `Unit aborted: ${cancellationMessage}`,
168
+ loopReason: "unit-aborted",
169
+ };
170
+ }
148
171
  async function failClosedOnFinalizeTimeout(ic, iterData, loopState, stage, startedAt) {
149
172
  const { ctx, pi, s, deps } = ic;
150
173
  const now = Date.now();
@@ -710,7 +733,10 @@ export async function runDispatch(ic, preData, loopState) {
710
733
  // See: https://github.com/gsd-build/gsd-2/issues/2474
711
734
  if (dispatchResult.level === "warning") {
712
735
  ctx.ui.notify(dispatchResult.reason, "warning");
713
- await deps.pauseAuto(ctx, pi);
736
+ await deps.pauseAuto(ctx, pi, {
737
+ message: dispatchResult.reason,
738
+ category: "unknown",
739
+ });
714
740
  }
715
741
  else {
716
742
  await closeoutAndStop(ctx, pi, s, deps, dispatchResult.reason);
@@ -770,7 +796,13 @@ export async function runDispatch(ic, preData, loopState) {
770
796
  action: "artifact-found",
771
797
  });
772
798
  ctx.ui.notify(`Stuck recovery: artifact for ${unitType} ${unitId} found on disk. Invalidating caches.`, "info");
799
+ if (!refreshPlanSliceRecoveryDbIfNeeded(unitType)) {
800
+ ctx.ui.notify(`Stuck recovery found ${unitType} ${unitId} artifacts, but the DB refresh failed. Keeping stuck state for retry.`, "warning");
801
+ return { action: "continue" };
802
+ }
773
803
  deps.invalidateAllCaches();
804
+ loopState.recentUnits.length = 0;
805
+ loopState.stuckRecoveryAttempts = 0;
774
806
  return { action: "continue" };
775
807
  }
776
808
  ctx.ui.notify(`Stuck on ${unitType} ${unitId} (${stuckSignal.reason}). Invalidating caches and retrying.`, "warning");
@@ -778,6 +810,22 @@ export async function runDispatch(ic, preData, loopState) {
778
810
  }
779
811
  else {
780
812
  // Level 2: hard stop — genuinely stuck
813
+ deps.invalidateAllCaches();
814
+ const artifactExists = verifyExpectedArtifact(unitType, unitId, s.basePath);
815
+ if (artifactExists && unitType !== "complete-milestone") {
816
+ debugLog("autoLoop", {
817
+ phase: "stuck-recovery",
818
+ level: 2,
819
+ action: "artifact-found",
820
+ });
821
+ ctx.ui.notify(`Stuck recovery: artifact for ${unitType} ${unitId} found on disk after cache invalidation. Continuing.`, "info");
822
+ if (refreshPlanSliceRecoveryDbIfNeeded(unitType)) {
823
+ loopState.recentUnits.length = 0;
824
+ loopState.stuckRecoveryAttempts = 0;
825
+ return { action: "continue" };
826
+ }
827
+ ctx.ui.notify(`Stuck recovery found ${unitType} ${unitId} artifacts, but the DB refresh failed. Stopping for manual recovery.`, "warning");
828
+ }
781
829
  debugLog("autoLoop", {
782
830
  phase: "stuck-detected",
783
831
  unitType,
@@ -1384,10 +1432,11 @@ export async function runUnitPhase(ic, iterData, loopState, sidecarItem) {
1384
1432
  }
1385
1433
  await deps.autoCommitUnit?.(s.basePath, unitType, unitId, ctx);
1386
1434
  await emitCancelledUnitEnd(ic, unitType, unitId, unitStartSeq, unitResult.errorContext);
1387
- ctx.ui.notify(`Session creation failed for ${unitType} ${unitId}: ${unitResult.errorContext?.message ?? "unknown"}. Stopping auto-mode.`, "warning");
1388
- await deps.stopAuto(ctx, pi, `Session creation failed: ${unitResult.errorContext?.message ?? "unknown"}`);
1389
- debugLog("autoLoop", { phase: "exit", reason: "session-failed" });
1390
- return { action: "break", reason: "session-failed" };
1435
+ const cancelledStop = _buildCancelledUnitStopReason(unitType, unitId, unitResult.errorContext);
1436
+ ctx.ui.notify(cancelledStop.notifyMessage, "warning");
1437
+ await deps.stopAuto(ctx, pi, cancelledStop.stopReason);
1438
+ debugLog("autoLoop", { phase: "exit", reason: cancelledStop.loopReason });
1439
+ return { action: "break", reason: cancelledStop.loopReason };
1391
1440
  }
1392
1441
  // ── Immediate unit closeout (metrics, activity log, memory) ────────
1393
1442
  // Run right after runUnit() returns so telemetry is never lost to a
@@ -148,6 +148,8 @@ export class AutoSession {
148
148
  // ── Remote command polling ───────────────────────────────────────────────
149
149
  /** Cleanup function returned by startCommandPolling(); null when not running. */
150
150
  commandPollingCleanup = null;
151
+ // ── Orchestration seam ───────────────────────────────────────────────────
152
+ orchestration = null;
151
153
  // ── Loop promise state ──────────────────────────────────────────────────
152
154
  // Per-unit resolve function and session-switch guard live at module level
153
155
  // in auto-loop.ts (_currentResolve, _sessionSwitchInFlight).
@@ -268,9 +270,12 @@ export class AutoSession {
268
270
  this.sigtermHandler = null;
269
271
  // Remote command polling — cleanup must be called before reset (auto.ts stopAuto)
270
272
  this.commandPollingCleanup = null;
273
+ // Orchestration seam
274
+ this.orchestration = null;
271
275
  // Loop promise state lives in auto-loop.ts module scope
272
276
  }
273
277
  toJSON() {
278
+ const orchestrationStatus = this.orchestration?.getStatus();
274
279
  return {
275
280
  active: this.active,
276
281
  paused: this.paused,
@@ -280,6 +285,9 @@ export class AutoSession {
280
285
  activeRunDir: this.activeRunDir,
281
286
  currentMilestoneId: this.currentMilestoneId,
282
287
  currentUnit: this.currentUnit,
288
+ orchestrationPhase: orchestrationStatus?.phase,
289
+ orchestrationTransitionCount: orchestrationStatus?.transitionCount,
290
+ orchestrationLastTransitionAt: orchestrationStatus?.lastTransitionAt,
283
291
  unitDispatchCount: Object.fromEntries(this.unitDispatchCount),
284
292
  };
285
293
  }
@@ -12,7 +12,7 @@ import { appendEvent } from "./workflow-events.js";
12
12
  import { atomicWriteSync } from "./atomic-write.js";
13
13
  import { clearParseCache } from "./files.js";
14
14
  import { parseRoadmap as parseLegacyRoadmap, parsePlan as parseLegacyPlan } from "./parsers-legacy.js";
15
- import { isDbAvailable, getTask, getSlice, getSliceTasks, getPendingGates, updateTaskStatus, updateSliceStatus, insertSlice, getMilestone } from "./gsd-db.js";
15
+ import { isDbAvailable, getTask, getSlice, getSliceTasks, getPendingGates, updateTaskStatus, updateSliceStatus, insertSlice, getMilestone, refreshOpenDatabaseFromDisk } from "./gsd-db.js";
16
16
  import { isValidationTerminal } from "./state.js";
17
17
  import { getErrorMessage } from "./error-utils.js";
18
18
  import { logWarning, logError } from "./workflow-logger.js";
@@ -510,66 +510,32 @@ export function verifyExpectedArtifact(unitType, unitId, base) {
510
510
  return false;
511
511
  }
512
512
  }
513
- // plan-slice must produce a plan with actual task entries, not just a scaffold.
514
- // The plan file may exist from a prior discussion/context step with only headings
515
- // but no tasks. Without this check the artifact is considered "complete" and the
516
- // unit gets skipped — but deriveState still returns phase:"planning" because the
517
- // plan has no tasks, creating an infinite skip loop (#699).
518
- if (unitType === "plan-slice") {
519
- const planContent = readFileSync(absPath, "utf-8");
520
- // Accept checkbox-style (- [x] **T01: ...) or heading-style (### T01 -- / ### T01: / ### T01 —)
521
- const hasCheckboxTask = /^- \[[xX ]\] \*\*T\d+:/m.test(planContent);
522
- const hasHeadingTask = /^#{2,4}\s+T\d+\s*(?:--|—|:)/m.test(planContent);
523
- if (!hasCheckboxTask && !hasHeadingTask) {
524
- logWarning("recovery", `verify-fail ${unitType} ${unitId}: plan has no task checkbox/heading (len=${planContent.length}) at ${absPath}`);
525
- return false;
526
- }
527
- }
528
- // execute-task: DB status is authoritative. Fall back to checked-checkbox
529
- // detection when the DB is unavailable (unmigrated projects), or when the
530
- // disk artifacts already reflect completion but the DB replay is one beat
531
- // behind the completion write.
532
- if (unitType === "execute-task") {
533
- const { milestone: mid, slice: sid, task: tid } = parseUnitId(unitId);
534
- if (mid && sid && tid) {
535
- const dbTask = getTask(mid, sid, tid);
536
- if (dbTask) {
537
- if (dbTask.status !== "complete" && dbTask.status !== "done" && !hasCheckedTaskCompletionOnDisk(base, mid, sid, tid)) {
538
- return false;
539
- }
540
- }
541
- else if (!isDbAvailable()) {
542
- // LEGACY: Pre-migration fallback for projects without DB.
543
- // Require a CHECKED checkbox — a bare heading or unchecked checkbox
544
- // does not prove gsd_complete_task ran. Summary file on disk alone
545
- // is not sufficient evidence (could be a rogue write) (#3607).
546
- if (!hasCheckedTaskCompletionOnDisk(base, mid, sid, tid))
547
- return false;
548
- }
549
- else {
550
- // DB available but task row not found — completion tool never ran (#3607)
551
- return false;
552
- }
553
- }
554
- }
555
- // plan-slice must also produce individual task plan files for every task listed
556
- // in the slice plan. Without this check, a plan-slice that wrote S{sid}-PLAN.md
557
- // but omitted T{tid}-PLAN.md files would be marked complete, causing execute-task
558
- // to dispatch with a missing task plan (see issue #739).
513
+ // plan-slice verification is DB-primary. The slice plan is a projection, so
514
+ // DB task rows prove the slice was planned even if the rendered markdown no
515
+ // longer uses legacy checkbox/heading syntax.
559
516
  if (unitType === "plan-slice") {
560
517
  const { milestone: mid, slice: sid } = parseUnitId(unitId);
561
518
  if (mid && sid) {
562
519
  try {
563
- // DB primary path — get task IDs to verify task plan files exist
564
520
  let taskIds = null;
565
521
  if (isDbAvailable()) {
566
- const tasks = getSliceTasks(mid, sid);
567
- if (tasks.length > 0)
568
- taskIds = tasks.map(t => t.id);
522
+ const refreshed = refreshOpenDatabaseFromDisk();
523
+ if (refreshed) {
524
+ const tasks = getSliceTasks(mid, sid);
525
+ if (tasks.length > 0)
526
+ taskIds = tasks.map(t => t.id);
527
+ }
569
528
  }
570
529
  if (!taskIds) {
571
- // LEGACY: DB unavailable or no tasks in DB parse plan file for task IDs
530
+ // LEGACY: DB unavailable or no tasks in DB. Require actual task
531
+ // entries so an empty scaffold cannot advance the pipeline (#699).
572
532
  const planContent = readFileSync(absPath, "utf-8");
533
+ const hasCheckboxTask = /^\s*- \[[xX ]\] \*\*T\d+:/m.test(planContent);
534
+ const hasHeadingTask = /^\s*#{2,4}\s+T\d+\s*(?:--|—|:)/m.test(planContent);
535
+ if (!hasCheckboxTask && !hasHeadingTask) {
536
+ logWarning("recovery", `verify-fail ${unitType} ${unitId}: plan has no task checkbox/heading (len=${planContent.length}) at ${absPath}`);
537
+ return false;
538
+ }
573
539
  const plan = parseLegacyPlan(planContent);
574
540
  if (plan.tasks.length > 0)
575
541
  taskIds = plan.tasks.map((t) => t.id);
@@ -595,6 +561,33 @@ export function verifyExpectedArtifact(unitType, unitId, base) {
595
561
  }
596
562
  }
597
563
  }
564
+ // execute-task: DB status is authoritative. Fall back to checked-checkbox
565
+ // detection when the DB is unavailable (unmigrated projects), or when the
566
+ // disk artifacts already reflect completion but the DB replay is one beat
567
+ // behind the completion write.
568
+ if (unitType === "execute-task") {
569
+ const { milestone: mid, slice: sid, task: tid } = parseUnitId(unitId);
570
+ if (mid && sid && tid) {
571
+ const dbTask = getTask(mid, sid, tid);
572
+ if (dbTask) {
573
+ if (dbTask.status !== "complete" && dbTask.status !== "done" && !hasCheckedTaskCompletionOnDisk(base, mid, sid, tid)) {
574
+ return false;
575
+ }
576
+ }
577
+ else if (!isDbAvailable()) {
578
+ // LEGACY: Pre-migration fallback for projects without DB.
579
+ // Require a CHECKED checkbox — a bare heading or unchecked checkbox
580
+ // does not prove gsd_complete_task ran. Summary file on disk alone
581
+ // is not sufficient evidence (could be a rogue write) (#3607).
582
+ if (!hasCheckedTaskCompletionOnDisk(base, mid, sid, tid))
583
+ return false;
584
+ }
585
+ else {
586
+ // DB available but task row not found — completion tool never ran (#3607)
587
+ return false;
588
+ }
589
+ }
590
+ }
598
591
  // complete-slice: DB status is authoritative for whether the slice is done.
599
592
  // Fall back to file-based check (roadmap [x]) when DB is unavailable.
600
593
  if (unitType === "complete-slice") {
@@ -3,11 +3,15 @@ import { AutoSession } from "./auto/session.js";
3
3
  import { isDeterministicPolicyError, isQueuedUserMessageSkip, isToolInvocationError, markToolEnd as markTrackedToolEnd, markToolStart as markTrackedToolStart, } from "./auto-tool-tracking.js";
4
4
  export const autoSession = new AutoSession();
5
5
  export function getAutoRuntimeSnapshot() {
6
+ const orchestrationStatus = autoSession.orchestration?.getStatus();
6
7
  return {
7
8
  active: autoSession.active,
8
9
  paused: autoSession.paused,
9
10
  currentUnit: autoSession.currentUnit ? { ...autoSession.currentUnit } : null,
10
11
  basePath: autoSession.basePath,
12
+ orchestrationPhase: orchestrationStatus?.phase,
13
+ orchestrationTransitionCount: orchestrationStatus?.transitionCount,
14
+ orchestrationLastTransitionAt: orchestrationStatus?.lastTransitionAt,
11
15
  };
12
16
  }
13
17
  export function isAutoActive() {