pi-crew 0.9.68 → 0.10.2

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 (253) hide show
  1. package/CHANGELOG.md +222 -0
  2. package/NOTICE.md +21 -0
  3. package/README.md +44 -2
  4. package/agents/analyst.md +1 -1
  5. package/agents/cold-verifier.md +3 -1
  6. package/agents/critic.md +1 -1
  7. package/agents/executor.md +1 -1
  8. package/agents/explorer.md +1 -1
  9. package/agents/planner.md +1 -1
  10. package/agents/reviewer.md +1 -1
  11. package/agents/security-reviewer.md +1 -1
  12. package/agents/test-engineer.md +1 -1
  13. package/agents/verifier.md +1 -1
  14. package/agents/writer.md +1 -1
  15. package/dist/index.mjs +68113 -60774
  16. package/docs/README.md +2 -0
  17. package/docs/actions-reference.md +31 -0
  18. package/docs/commands-reference.md +17 -6
  19. package/docs/resource-formats.md +13 -0
  20. package/package.json +4 -2
  21. package/schema.json +503 -91
  22. package/scripts/resource-sampler.mjs +36 -2
  23. package/skills/requirements-to-task-packet/SKILL.md +26 -0
  24. package/skills/widget-rendering/SKILL.md +7 -7
  25. package/src/agents/agent-config.ts +2 -1
  26. package/src/agents/discover-agents.ts +23 -14
  27. package/src/config/config-merge.ts +183 -0
  28. package/src/config/config-validation.ts +687 -0
  29. package/src/config/config.ts +22 -864
  30. package/src/config/defaults.ts +43 -2
  31. package/src/config/drift-detector.ts +1 -1
  32. package/src/config/env-vars.ts +691 -0
  33. package/src/config/role-tools.ts +11 -9
  34. package/src/config/sanitize-project-config.ts +172 -0
  35. package/src/config/types.ts +49 -1
  36. package/src/extension/async-notifier.ts +25 -2
  37. package/src/extension/crew-cleanup.ts +13 -0
  38. package/src/extension/crew-vibes/config.ts +2 -1
  39. package/src/extension/crew-vibes/footer.ts +19 -0
  40. package/src/extension/crew-vibes/index.ts +11 -1
  41. package/src/extension/plan-orchestrate.ts +132 -0
  42. package/src/extension/register.ts +8 -0
  43. package/src/extension/registration/command-registration.ts +1 -0
  44. package/src/extension/registration/commands/dashboard.ts +158 -0
  45. package/src/extension/registration/commands/index.ts +35 -0
  46. package/src/extension/registration/commands/manage.ts +303 -0
  47. package/src/extension/registration/commands/run.ts +228 -0
  48. package/src/extension/registration/commands/shared.ts +639 -0
  49. package/src/extension/registration/commands/status.ts +60 -0
  50. package/src/extension/registration/commands.ts +13 -1224
  51. package/src/extension/registration/foreground-run-controller.ts +10 -2
  52. package/src/extension/registration/lifecycle-handlers.ts +178 -17
  53. package/src/extension/registration/runtime-cleanup.ts +23 -5
  54. package/src/extension/registration/subagent-tools.ts +218 -9
  55. package/src/extension/registration/team-tool.ts +5 -1
  56. package/src/extension/registration/ui.ts +5 -4
  57. package/src/extension/rpc-hmac.ts +5 -3
  58. package/src/extension/team-tool/api/heartbeat.ts +47 -10
  59. package/src/extension/team-tool/api/plan-approval.ts +9 -0
  60. package/src/extension/team-tool/api/task-claims.ts +109 -40
  61. package/src/extension/team-tool/cancel.ts +84 -50
  62. package/src/extension/team-tool/dispatch/index.ts +1 -0
  63. package/src/extension/team-tool/dispatch/run.ts +4 -1
  64. package/src/extension/team-tool/doctor.ts +103 -1
  65. package/src/extension/team-tool/orchestrate.ts +66 -1
  66. package/src/extension/team-tool/plans.ts +192 -0
  67. package/src/extension/team-tool/respond.ts +197 -65
  68. package/src/extension/team-tool/run-deadline.ts +35 -3
  69. package/src/extension/team-tool/run-intent.ts +63 -0
  70. package/src/extension/team-tool/run.ts +74 -20
  71. package/src/extension/team-tool/status.ts +84 -26
  72. package/src/extension/team-tool.ts +11 -2
  73. package/src/hooks/registry.ts +1 -6
  74. package/src/i18n.ts +9 -0
  75. package/src/prompt/prompt-runtime.ts +521 -2
  76. package/src/prompt/worker-events-channel.ts +173 -0
  77. package/src/runtime/README.md +8 -8
  78. package/src/runtime/async-runner.ts +7 -3
  79. package/src/runtime/background-runner.ts +42 -14
  80. package/src/runtime/broker/broker-issuer.ts +9 -2
  81. package/src/runtime/broker/crew-broker-tokens.ts +43 -6
  82. package/src/runtime/broker/crew-broker.ts +838 -10
  83. package/src/runtime/broker/wait-status-cache.ts +157 -0
  84. package/src/runtime/budget-enforcement.ts +281 -0
  85. package/src/runtime/child-pi/child-pi-constants.ts +8 -0
  86. package/src/runtime/child-pi/child-pi-spawn.ts +60 -14
  87. package/src/runtime/child-pi/child-pi-streams.ts +21 -1
  88. package/src/runtime/child-pi/child-pi-timers.ts +324 -0
  89. package/src/runtime/child-pi/child-pi.ts +97 -201
  90. package/src/runtime/child-pi/mock-fixtures.ts +16 -2
  91. package/src/runtime/crew-agent-records.ts +259 -14
  92. package/src/runtime/delegate-spawn.ts +148 -0
  93. package/src/runtime/detached-run-results.ts +90 -0
  94. package/src/runtime/deterministic-ast.ts +2 -1
  95. package/src/runtime/dispatch-batch.ts +945 -0
  96. package/src/runtime/finalize-run.ts +557 -0
  97. package/src/runtime/goal-workflow/adaptive-plan.ts +116 -15
  98. package/src/runtime/goal-workflow/dynamic-workflow-runner.ts +8 -3
  99. package/src/runtime/goal-workflow/goal-state-store.ts +1 -1
  100. package/src/runtime/group-join.ts +11 -125
  101. package/src/runtime/live-session/live-session-runtime.ts +26 -1
  102. package/src/runtime/merge-gate.ts +32 -10
  103. package/src/runtime/merge-loop.ts +130 -0
  104. package/src/runtime/model/model-budget-summary.ts +53 -0
  105. package/src/runtime/model/model-fallback.ts +36 -2
  106. package/src/runtime/model/pi-args.ts +10 -0
  107. package/src/runtime/model/provider-extensions.ts +10 -0
  108. package/src/runtime/orphan-worker-registry.ts +1 -1
  109. package/src/runtime/output/output-validator.ts +45 -0
  110. package/src/runtime/parent-guard.ts +3 -1
  111. package/src/runtime/peer-dep.ts +2 -1
  112. package/src/runtime/per-write-validator.ts +0 -5
  113. package/src/runtime/pi-spawn.ts +61 -15
  114. package/src/runtime/plan-approval.ts +125 -0
  115. package/src/runtime/plan-replan.ts +151 -0
  116. package/src/runtime/process-status.ts +16 -1
  117. package/src/runtime/recovery/checkpoint.ts +0 -18
  118. package/src/runtime/recovery/crash-recovery.ts +111 -46
  119. package/src/runtime/run-tracker.ts +77 -10
  120. package/src/runtime/scheduler-context.ts +98 -0
  121. package/src/runtime/scheduling/coalesce-tasks.ts +5 -0
  122. package/src/runtime/scheduling/global-worker-cap.ts +2 -1
  123. package/src/runtime/scheduling/nested-slots.ts +70 -0
  124. package/src/runtime/scheduling/run-coalesced-task-group.ts +64 -13
  125. package/src/runtime/scheduling/task-graph-scheduler.ts +0 -10
  126. package/src/runtime/settings-store.ts +219 -0
  127. package/src/runtime/spawn-policy.ts +217 -0
  128. package/src/runtime/stale-reconciler.ts +87 -6
  129. package/src/runtime/subagent-manager.ts +25 -1
  130. package/src/runtime/task-output-context.ts +230 -9
  131. package/src/runtime/task-packet.ts +23 -1
  132. package/src/runtime/task-runner/child-executor.ts +106 -7
  133. package/src/runtime/task-runner/post-execution.ts +125 -1
  134. package/src/runtime/task-runner/pre-execution.ts +39 -1
  135. package/src/runtime/task-runner/prompt-builder.ts +51 -1
  136. package/src/runtime/task-runner/retrieval-orchestrator.ts +72 -18
  137. package/src/runtime/task-runner/spec-evidence.ts +403 -0
  138. package/src/runtime/task-runner/state-helpers.ts +26 -24
  139. package/src/runtime/task-runner.ts +11 -0
  140. package/src/runtime/team-runner.ts +132 -1673
  141. package/src/runtime/verification/spec-sandbox.ts +255 -0
  142. package/src/runtime/verification/verification-gates.ts +3 -2
  143. package/src/runtime/verification/verification-worktree.ts +2 -1
  144. package/src/runtime/workflow-phase-advance.ts +100 -0
  145. package/src/runtime/workspace-tree.ts +9 -0
  146. package/src/schema/config-schema.ts +66 -26
  147. package/src/schema/sensitive-config-paths.ts +64 -0
  148. package/src/schema/team-tool-schema.ts +13 -3
  149. package/src/state/README.md +4 -10
  150. package/src/state/atomic-write.ts +20 -3
  151. package/src/state/contracts.ts +38 -0
  152. package/src/state/coordination/mailbox.ts +12 -2
  153. package/src/state/event-log/cursor.ts +223 -0
  154. package/src/state/event-log/event-log-rotation.ts +12 -4
  155. package/src/state/event-log/event-log.ts +152 -369
  156. package/src/state/event-log/sequence-cache.ts +373 -0
  157. package/src/state/event-log/worker-atomic-writer.ts +2 -1
  158. package/src/state/stores/active-run-registry.ts +3 -2
  159. package/src/state/stores/manifest-io.ts +237 -0
  160. package/src/state/stores/ownership-map.ts +162 -0
  161. package/src/state/stores/plan-store.ts +241 -0
  162. package/src/state/stores/run-cache.ts +0 -90
  163. package/src/state/stores/spec-store.ts +189 -0
  164. package/src/state/stores/state-store.ts +139 -232
  165. package/src/state/types.ts +199 -0
  166. package/src/ui/dashboard-panes/plan-pane.ts +136 -0
  167. package/src/ui/dashboard-panes/progress-pane.ts +6 -0
  168. package/src/ui/dashboard-panes/transcript-pane.ts +31 -0
  169. package/src/ui/dock-footer.ts +49 -0
  170. package/src/ui/heartbeat-aggregator.ts +9 -1
  171. package/src/ui/inline-panel/agent-pane.ts +375 -0
  172. package/src/ui/inline-panel/agent-transcript.ts +338 -0
  173. package/src/ui/inline-panel/agent-view-overlay.ts +225 -0
  174. package/src/ui/inline-panel/crew-editor.ts +192 -0
  175. package/src/ui/inline-panel/index.ts +290 -0
  176. package/src/ui/inline-panel/panel-rows.ts +37 -0
  177. package/src/ui/inline-panel/panel-selection.ts +157 -0
  178. package/src/ui/inline-panel/panel-store.ts +111 -0
  179. package/src/ui/inline-panel/view-session-store.ts +36 -0
  180. package/src/ui/keybinding-map.ts +54 -13
  181. package/src/ui/pi-ui-compat.ts +9 -0
  182. package/src/ui/powerbar-publisher.ts +52 -1
  183. package/src/ui/run-dashboard.ts +31 -5
  184. package/src/ui/run-snapshot-cache.ts +57 -30
  185. package/src/ui/snapshot-types.ts +6 -1
  186. package/src/ui/widget/index.ts +176 -22
  187. package/src/ui/widget/task-list.ts +198 -0
  188. package/src/ui/widget/widget-formatters.ts +240 -4
  189. package/src/ui/widget/widget-renderer.ts +243 -38
  190. package/src/ui/widget/widget-types.ts +11 -0
  191. package/src/utils/child-process-shield.ts +106 -0
  192. package/src/utils/file-coalescer.ts +0 -4
  193. package/src/utils/fs-errno.ts +66 -0
  194. package/src/utils/fs-watch.ts +1 -1
  195. package/src/utils/internal-error.ts +3 -1
  196. package/src/utils/paths.ts +11 -3
  197. package/src/utils/redaction.ts +7 -0
  198. package/src/utils/safe-abort.ts +45 -0
  199. package/src/utils/task-name-generator.ts +1 -8
  200. package/src/workflows/discover-workflows.ts +20 -2
  201. package/src/workflows/validate-workflow.ts +7 -1
  202. package/src/workflows/workflow-config.ts +17 -0
  203. package/src/workflows/workflow-serializer.ts +3 -0
  204. package/src/worktree/worktree-manager.ts +22 -0
  205. package/workflows/default.workflow.md +36 -26
  206. package/workflows/strict-fast-fix.workflow.md +26 -0
  207. package/src/agents/agent-search.ts +0 -98
  208. package/src/benchmark/benchmark-runner.ts +0 -313
  209. package/src/benchmark/feedback-loop.ts +0 -73
  210. package/src/config/resilient-parser.ts +0 -117
  211. package/src/extension/crew-vibes/cat-frames.ts +0 -18
  212. package/src/extension/result-watcher.ts +0 -139
  213. package/src/observability/exporters/prometheus-exporter.ts +0 -54
  214. package/src/observability/metric-retention.ts +0 -64
  215. package/src/runtime/compaction/compaction-summary.ts +0 -278
  216. package/src/runtime/errors/crew-errors.ts +0 -162
  217. package/src/runtime/live-session/intercom-bridge.ts +0 -187
  218. package/src/runtime/loop-gates.ts +0 -128
  219. package/src/runtime/metric-parser.ts +0 -36
  220. package/src/runtime/output/stream-preview.ts +0 -184
  221. package/src/runtime/output/tool-progress.ts +0 -278
  222. package/src/runtime/phase-tracker.ts +0 -385
  223. package/src/runtime/pipeline-runner.ts +0 -523
  224. package/src/runtime/process/process-lifecycle.ts +0 -491
  225. package/src/runtime/recovery/retry-runner.ts +0 -330
  226. package/src/runtime/run-drift.ts +0 -219
  227. package/src/runtime/task-quality.ts +0 -199
  228. package/src/runtime/task-runner/run-projection.ts +0 -128
  229. package/src/runtime/verification/post-checks.ts +0 -142
  230. package/src/state/coordination/schedule.ts +0 -166
  231. package/src/state/event-log/jsonl-writer.ts +0 -115
  232. package/src/state/hook-instinct-bridge.ts +0 -94
  233. package/src/state/hook-integrations.ts +0 -51
  234. package/src/state/session-state-map.ts +0 -51
  235. package/src/state/stores/blob-store.ts +0 -308
  236. package/src/state/stores/instinct-store.ts +0 -275
  237. package/src/state/stores/observation-store.ts +0 -176
  238. package/src/state/tiered-eval.ts +0 -480
  239. package/src/state/types-eval.ts +0 -58
  240. package/src/tools/safe-bash-extension.ts +0 -54
  241. package/src/tools/safe-bash.ts +0 -505
  242. package/src/ui/agent-management-overlay.ts +0 -160
  243. package/src/ui/crew-footer.ts +0 -102
  244. package/src/ui/crew-select-list.ts +0 -114
  245. package/src/ui/dashboard-panes/capability-pane.ts +0 -77
  246. package/src/ui/transcript-entries.ts +0 -256
  247. package/src/utils/conflict-detect.ts +0 -721
  248. package/src/utils/fingerprint.ts +0 -180
  249. package/src/utils/gh-protocol.ts +0 -556
  250. package/src/utils/project-detector.ts +0 -160
  251. package/src/utils/sse-parser.ts +0 -131
  252. package/src/workflows/cost-estimator.ts +0 -34
  253. package/src/workflows/intermediate-store.ts +0 -166
@@ -1,330 +0,0 @@
1
- /**
2
- * RetryRunner - Execute tasks with retry support and summary accumulation.
3
- *
4
- * Based on pi-boomerang's --rethrow pattern:
5
- * - Retries failed tasks up to maxAttempts
6
- * - Generates handoffs between attempts
7
- * - Accumulates context from previous attempts
8
- * - Supports exponential backoff
9
- * - Limits handoff accumulation to prevent memory leaks
10
- */
11
-
12
- import type { HandoffManager, HandoffSummary, TaskPacket, TaskResult } from "../handoff-manager.ts";
13
-
14
- /**
15
- * Retry configuration.
16
- */
17
- export interface RetryConfig {
18
- /** Maximum number of retry attempts */
19
- maxAttempts: number;
20
- /** Generate summary between attempts for context accumulation */
21
- summaryBetweenAttempts?: boolean;
22
- /** Stop retrying if task succeeds */
23
- stopOnSuccess?: boolean;
24
- /** Base backoff delay in milliseconds (multiplied by attempt number) */
25
- backoffMs?: number;
26
- /** Backoff multiplier (default: 1) */
27
- backoffMultiplier?: number;
28
- /** Maximum backoff delay cap in milliseconds */
29
- maxBackoffMs?: number;
30
- /** Custom retry condition (return true to retry) */
31
- retryCondition?: (result: TaskResult, attempt: number) => boolean;
32
- /** Maximum handoffs to retain (default: 100) - prevents memory leaks */
33
- maxHandoffs?: number;
34
- }
35
-
36
- /**
37
- * Result of a single attempt.
38
- */
39
- export interface AttemptResult {
40
- attempt: number;
41
- result: TaskResult;
42
- summary?: HandoffSummary;
43
- duration: number;
44
- error?: string;
45
- timestamp: number;
46
- }
47
-
48
- /**
49
- * Final retry result.
50
- */
51
- export interface RetryResult {
52
- success: boolean;
53
- attempts: AttemptResult[];
54
- finalResult?: TaskResult;
55
- totalHandoffs: HandoffSummary[];
56
- totalDuration: number;
57
- }
58
-
59
- /**
60
- * Task runner interface (minimal for retry functionality).
61
- */
62
- export interface TaskRunnerLike {
63
- runTask(packet: TaskPacket): Promise<TaskResult>;
64
- }
65
-
66
- /**
67
- * RetryRunner handles task execution with automatic retry and summary accumulation.
68
- */
69
- export class RetryRunner {
70
- private _disposed = false;
71
-
72
- private taskRunner: TaskRunnerLike;
73
- private handoffManager: HandoffManager;
74
-
75
- constructor(taskRunner: TaskRunnerLike, handoffManager: HandoffManager) {
76
- this.taskRunner = taskRunner;
77
- this.handoffManager = handoffManager;
78
- }
79
-
80
- /**
81
- * Check if this runner has been disposed.
82
- */
83
- get isDisposed(): boolean {
84
- return this._disposed;
85
- }
86
-
87
- /**
88
- * Dispose of resources held by this runner.
89
- * Clears any accumulated state and prevents further operations.
90
- */
91
- dispose(): void {
92
- this._disposed = true;
93
- }
94
-
95
- /**
96
- * Clear accumulated handoffs to free memory.
97
- * Useful when you want to reset state without disposing.
98
- */
99
- clearHandoffs(): void {
100
- /* no-op: _handoffs field removed (was never read) */
101
- }
102
-
103
- /**
104
- * Get the effective max handoffs limit from config or default.
105
- */
106
- private getMaxHandoffs(config: RetryConfig): number {
107
- return config.maxHandoffs ?? 100;
108
- }
109
-
110
- /**
111
- * Trim handoffs array to max size, keeping most recent.
112
- */
113
- private trimHandoffs(handoffs: HandoffSummary[], maxSize: number): HandoffSummary[] {
114
- if (handoffs.length <= maxSize) {
115
- return handoffs;
116
- }
117
- // Keep the most recent handoffs (last maxSize items)
118
- return handoffs.slice(-maxSize);
119
- }
120
-
121
- /**
122
- * Execute task with retry support.
123
- * Summaries accumulate between attempts for better context.
124
- *
125
- * @param packet - Task packet to execute
126
- * @param config - Retry configuration (stopOnSuccess defaults to true)
127
- * @returns Final retry result with all attempts
128
- */
129
- async runWithRetry(packet: TaskPacket, config: RetryConfig): Promise<RetryResult> {
130
- if (this._disposed) {
131
- throw new Error("RetryRunner has been disposed");
132
- }
133
-
134
- const attempts: AttemptResult[] = [];
135
- const handoffs: HandoffSummary[] = [];
136
- const startTime = Date.now();
137
- const maxHandoffs = this.getMaxHandoffs(config);
138
-
139
- for (let attempt = 1; attempt <= config.maxAttempts; attempt++) {
140
- if (this._disposed) {
141
- throw new Error("RetryRunner was disposed during retry");
142
- }
143
- const attemptStart = Date.now();
144
-
145
- try {
146
- // Inject accumulated handoffs into context
147
- const enrichedPacket = this.enrichPacketWithHandoffs(packet, handoffs, attempt);
148
-
149
- // Execute task
150
- const result = await this.taskRunner.runTask(enrichedPacket);
151
-
152
- const attemptResult: AttemptResult = {
153
- attempt,
154
- result,
155
- duration: Date.now() - attemptStart,
156
- timestamp: Date.now(),
157
- };
158
-
159
- // Generate summary between attempts
160
- if (config.summaryBetweenAttempts !== false) {
161
- const summary = await this.handoffManager.generateSummary(packet, result);
162
- attemptResult.summary = summary;
163
- handoffs.push(summary);
164
-
165
- // Trim handoffs to prevent memory leak
166
- if (handoffs.length > maxHandoffs) {
167
- handoffs.splice(0, handoffs.length - maxHandoffs);
168
- }
169
- }
170
-
171
- attempts.push(attemptResult);
172
-
173
- // Stop on success if configured (default to true when undefined)
174
- if ((config.stopOnSuccess ?? true) && result.outcome === "success") {
175
- return {
176
- success: true,
177
- attempts,
178
- finalResult: result,
179
- totalHandoffs: handoffs,
180
- totalDuration: Date.now() - startTime,
181
- };
182
- }
183
-
184
- // Check custom retry condition
185
- if (config.retryCondition && !config.retryCondition(result, attempt)) {
186
- break;
187
- }
188
-
189
- // Check default retry condition (retry on failure)
190
- if (result.outcome !== "success" && attempt < config.maxAttempts) {
191
- // Apply backoff before retry
192
- const backoffDelay = this.calculateBackoff(
193
- config.backoffMs ?? 1000,
194
- attempt,
195
- config.backoffMultiplier ?? 1,
196
- config.maxBackoffMs,
197
- );
198
- if (backoffDelay > 0) {
199
- await this.sleep(backoffDelay);
200
- }
201
- }
202
- } catch (error) {
203
- attempts.push({
204
- attempt,
205
- result: {
206
- outcome: "failure",
207
- error: error instanceof Error ? error.message : String(error),
208
- },
209
- duration: Date.now() - attemptStart,
210
- timestamp: Date.now(),
211
- error: error instanceof Error ? error.message : String(error),
212
- });
213
-
214
- // Apply backoff before retry on error
215
- if (config.backoffMs && attempt < config.maxAttempts) {
216
- const backoffDelay = this.calculateBackoff(
217
- config.backoffMs,
218
- attempt,
219
- config.backoffMultiplier ?? 1,
220
- config.maxBackoffMs,
221
- );
222
- if (backoffDelay > 0) {
223
- await this.sleep(backoffDelay);
224
- }
225
- }
226
- }
227
- }
228
-
229
- const finalAttempt = attempts[attempts.length - 1];
230
- return {
231
- success: finalAttempt?.result.outcome === "success",
232
- attempts,
233
- finalResult: finalAttempt?.result,
234
- totalHandoffs: this.trimHandoffs(handoffs, maxHandoffs),
235
- totalDuration: Date.now() - startTime,
236
- };
237
- }
238
-
239
- /**
240
- * Enrich packet with accumulated handoffs from previous attempts.
241
- */
242
- private enrichPacketWithHandoffs(packet: TaskPacket, handoffs: HandoffSummary[], attempt: number): TaskPacket {
243
- if (handoffs.length === 0) {
244
- return packet;
245
- }
246
-
247
- // Build accumulated context from previous attempts
248
- const accumulatedContext = handoffs
249
- .map(
250
- (h, index) =>
251
- `## Attempt ${index + 1}: ${h.task}\n` +
252
- `Outcome: ${h.outcome}\n` +
253
- `Files: created=${h.filesCreated.join(", ")}, modified=${h.filesModified.join(", ")}\n` +
254
- `Decisions: ${h.decisions.map((d) => d.rationale).join("; ")}\n` +
255
- `Blockers: ${h.blockers.join(", ")}\n` +
256
- `Next Steps: ${h.nextSteps.join(", ")}\n`,
257
- )
258
- .join("\n---\n");
259
-
260
- return {
261
- ...packet,
262
- context: {
263
- ...packet.context,
264
- __boomerangAttempts: attempt,
265
- __boomerangHandoffs: handoffs,
266
- __boomerangContext: `Previous attempts summary:\n${accumulatedContext}`,
267
- },
268
- };
269
- }
270
-
271
- /**
272
- * Calculate backoff delay with optional capping.
273
- */
274
- private calculateBackoff(baseMs: number, attempt: number, multiplier: number, maxBackoffMs?: number): number {
275
- let delay = baseMs * multiplier ** (attempt - 1);
276
- if (maxBackoffMs !== undefined) {
277
- delay = Math.min(delay, maxBackoffMs);
278
- }
279
- return delay;
280
- }
281
-
282
- /**
283
- * Sleep helper.
284
- */
285
- private sleep(ms: number): Promise<void> {
286
- return new Promise((resolve) => setTimeout(resolve, ms));
287
- }
288
- }
289
-
290
- /**
291
- * Create a RetryRunner with default dependencies.
292
- */
293
- export function createRetryRunner(taskRunner: TaskRunnerLike, handoffManager: HandoffManager): RetryRunner {
294
- return new RetryRunner(taskRunner, handoffManager);
295
- }
296
-
297
- /**
298
- * Default retry config for common scenarios.
299
- */
300
- export const DEFAULT_RETRY_CONFIG: RetryConfig = {
301
- maxAttempts: 3,
302
- summaryBetweenAttempts: true,
303
- stopOnSuccess: true,
304
- backoffMs: 1000,
305
- backoffMultiplier: 2,
306
- maxBackoffMs: 30000,
307
- };
308
-
309
- /**
310
- * Retry config for transient failures (quick retries).
311
- */
312
- export const TRANSIENT_FAILURE_RETRY_CONFIG: RetryConfig = {
313
- maxAttempts: 2,
314
- summaryBetweenAttempts: false,
315
- stopOnSuccess: true,
316
- backoffMs: 500,
317
- backoffMultiplier: 1,
318
- };
319
-
320
- /**
321
- * Retry config for persistent failures (exponential backoff).
322
- */
323
- export const PERSISTENT_FAILURE_RETRY_CONFIG: RetryConfig = {
324
- maxAttempts: 5,
325
- summaryBetweenAttempts: true,
326
- stopOnSuccess: true,
327
- backoffMs: 2000,
328
- backoffMultiplier: 2,
329
- maxBackoffMs: 60000,
330
- };
@@ -1,219 +0,0 @@
1
- /**
2
- * Runtime drift detectors — detect state anomalies in pi-crew runs.
3
- *
4
- * Pattern origin: GSD-2 ADR-017 drift detection & state reconciliation.
5
- * Each detector checks for a specific anomaly and returns a report.
6
- * Repair handlers are idempotent — safe to run multiple times.
7
- */
8
-
9
- import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
10
- import path from "node:path";
11
- import { logInternalError } from "../utils/internal-error.ts";
12
-
13
- // ── Types ────────────────────────────────────────────────────────────────
14
-
15
- export type DriftKind =
16
- | "stale-process" // Heartbeat timeout (existing)
17
- | "orphaned-claim" // Task claim without task
18
- | "orphaned-worktree" // Worktree dir without active run
19
- | "missing-timestamps" // State files without timestamps
20
- | "status-divergence" // Manifest status ≠ status file
21
- | "unregistered-run"; // State dir but no manifest
22
-
23
- export interface DriftReport {
24
- kind: DriftKind;
25
- runId: string;
26
- details: string;
27
- repaired: boolean;
28
- repairResult?: string;
29
- }
30
-
31
- export interface DriftContext {
32
- /** Root directory for crew state (.crew/) */
33
- crewRoot: string;
34
- /** Active run IDs (from registry) */
35
- activeRunIds: Set<string>;
36
- /** Manifest content if available */
37
- manifest?: {
38
- runId: string;
39
- status: string;
40
- cwd: string;
41
- [k: string]: unknown;
42
- };
43
- }
44
-
45
- // ── Detectors ────────────────────────────────────────────────────────────
46
-
47
- /**
48
- * Detect task claims that reference tasks not in the manifest.
49
- */
50
- export function detectOrphanedClaim(ctx: DriftContext): DriftReport | null {
51
- if (!ctx.manifest) return null;
52
- const claimsDir = path.join(ctx.crewRoot, "state", "task-claims");
53
- if (!existsSync(claimsDir)) return null;
54
-
55
- const claimFiles = readdirSync(claimsDir).filter((f) => f.endsWith(".json"));
56
- for (const file of claimFiles) {
57
- try {
58
- const claim = JSON.parse(readFileSync(path.join(claimsDir, file), "utf-8"));
59
- if (claim.runId === ctx.manifest.runId && claim.taskId) {
60
- // Check if task exists in manifest tasks array
61
- const tasks = (ctx.manifest as Record<string, unknown>).tasks;
62
- if (Array.isArray(tasks) && !tasks.some((t: Record<string, unknown>) => t.id === claim.taskId)) {
63
- return {
64
- kind: "orphaned-claim",
65
- runId: ctx.manifest.runId,
66
- details: `Task claim '${claim.taskId}' references non-existent task`,
67
- repaired: false,
68
- };
69
- }
70
- }
71
- } catch {
72
- // Malformed claim file — skip
73
- }
74
- }
75
- return null;
76
- }
77
-
78
- /**
79
- * Detect worktree directories that don't belong to any active run.
80
- */
81
- export function detectOrphanedWorktree(ctx: DriftContext): DriftReport | null {
82
- const worktreesDir = path.join(ctx.crewRoot, "worktrees");
83
- if (!existsSync(worktreesDir)) return null;
84
-
85
- const dirs = readdirSync(worktreesDir, { withFileTypes: true })
86
- .filter((d) => d.isDirectory())
87
- .map((d) => d.name);
88
-
89
- for (const dir of dirs) {
90
- // Extract run ID from worktree dir name (format: <runId>-<taskId> or <runId>)
91
- const runId = dir.split("-").slice(0, 5).join("-"); // heuristic: run IDs are timestamp-based
92
- if (!ctx.activeRunIds.has(runId) && !ctx.activeRunIds.has(dir)) {
93
- return {
94
- kind: "orphaned-worktree",
95
- runId: dir,
96
- details: `Worktree '${dir}' has no active run`,
97
- repaired: false,
98
- };
99
- }
100
- }
101
- return null;
102
- }
103
-
104
- /**
105
- * Detect state files missing required timestamps.
106
- */
107
- export function detectMissingTimestamps(ctx: DriftContext): DriftReport | null {
108
- if (!ctx.manifest) return null;
109
- const stateDir = path.join(ctx.crewRoot, "state");
110
- if (!existsSync(stateDir)) return null;
111
-
112
- // Check manifest has createdAt/updatedAt
113
- const m = ctx.manifest as Record<string, unknown>;
114
- if (!m.createdAt && !m.updatedAt) {
115
- return {
116
- kind: "missing-timestamps",
117
- runId: ctx.manifest.runId,
118
- details: "Manifest missing createdAt/updatedAt timestamps",
119
- repaired: false,
120
- };
121
- }
122
- return null;
123
- }
124
-
125
- /**
126
- * Detect divergence between manifest status and individual task status files.
127
- */
128
- export function detectStatusDivergence(ctx: DriftContext): DriftReport | null {
129
- if (!ctx.manifest) return null;
130
- const statusPath = path.join(ctx.crewRoot, "state", `${ctx.manifest.runId}.status`);
131
- if (!existsSync(statusPath)) return null;
132
-
133
- try {
134
- const status = readFileSync(statusPath, "utf-8").trim();
135
- if (status !== ctx.manifest.status) {
136
- return {
137
- kind: "status-divergence",
138
- runId: ctx.manifest.runId,
139
- details: `Manifest says '${ctx.manifest.status}' but status file says '${status}'`,
140
- repaired: false,
141
- };
142
- }
143
- } catch {
144
- // Can't read status file — not drift, might be permissions
145
- }
146
- return null;
147
- }
148
-
149
- /**
150
- * Detect state directories that have no corresponding manifest.
151
- */
152
- export function detectUnregisteredRun(ctx: DriftContext): DriftReport | null {
153
- const runsDir = path.join(ctx.crewRoot, "runs");
154
- if (!existsSync(runsDir)) return null;
155
-
156
- const runDirs = readdirSync(runsDir, { withFileTypes: true })
157
- .filter((d) => d.isDirectory())
158
- .map((d) => d.name);
159
-
160
- for (const runId of runDirs) {
161
- if (!ctx.activeRunIds.has(runId)) {
162
- // Check if it has state files (manifest exists)
163
- const manifestPath = path.join(runsDir, runId, "manifest.json");
164
- if (existsSync(manifestPath)) {
165
- try {
166
- const stat = statSync(manifestPath);
167
- const ageMs = Date.now() - stat.mtimeMs;
168
- // Only flag if older than 1 hour (might be in-progress)
169
- if (ageMs > 60 * 60 * 1000) {
170
- return {
171
- kind: "unregistered-run",
172
- runId,
173
- details: `Run '${runId}' has manifest but is not in active registry (age: ${Math.round(ageMs / 60000)}m)`,
174
- repaired: false,
175
- };
176
- }
177
- } catch {
178
- // Can't stat — skip
179
- }
180
- }
181
- }
182
- }
183
- return null;
184
- }
185
-
186
- // ── Reconciliation Loop ─────────────────────────────────────────────────
187
-
188
- const ALL_DETECTORS = [detectOrphanedClaim, detectOrphanedWorktree, detectMissingTimestamps, detectStatusDivergence, detectUnregisteredRun];
189
-
190
- /**
191
- * Run all drift detectors and collect reports.
192
- * Capped at maxPasses repair attempts.
193
- *
194
- * Pattern origin: GSD-2 ADR-017 — capped at 2 retry passes.
195
- */
196
- export function runDriftDetection(ctx: DriftContext, maxPasses = 2): DriftReport[] {
197
- const reports: DriftReport[] = [];
198
-
199
- for (let pass = 0; pass < maxPasses; pass++) {
200
- let newFindings = 0;
201
-
202
- for (const detector of ALL_DETECTORS) {
203
- try {
204
- const report = detector(ctx);
205
- if (report) {
206
- reports.push(report);
207
- newFindings++;
208
- }
209
- } catch (error) {
210
- logInternalError("run-drift", error, `detector=${detector.name} runId=${ctx.manifest?.runId}`);
211
- }
212
- }
213
-
214
- // If no new findings, stop early
215
- if (newFindings === 0) break;
216
- }
217
-
218
- return reports;
219
- }