pi-crew 0.1.41 → 0.1.44

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 (191) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/README.md +51 -0
  3. package/agents/analyst.md +11 -11
  4. package/agents/critic.md +11 -11
  5. package/agents/executor.md +11 -11
  6. package/agents/explorer.md +11 -11
  7. package/agents/planner.md +11 -11
  8. package/agents/reviewer.md +11 -11
  9. package/agents/security-reviewer.md +11 -11
  10. package/agents/test-engineer.md +11 -11
  11. package/agents/verifier.md +11 -11
  12. package/agents/writer.md +11 -11
  13. package/docs/refactor-tasks-phase3.md +394 -394
  14. package/docs/refactor-tasks-phase4.md +564 -564
  15. package/docs/refactor-tasks-phase5.md +402 -402
  16. package/docs/refactor-tasks-phase6.md +662 -662
  17. package/docs/research-extension-examples.md +297 -297
  18. package/docs/research-extension-system.md +324 -324
  19. package/docs/research-optimization-plan.md +548 -548
  20. package/docs/research-phase10-distillation.md +199 -0
  21. package/docs/research-phase11-distillation.md +201 -0
  22. package/docs/research-pi-coding-agent.md +357 -357
  23. package/docs/research-source-pi-crew-reference.md +174 -174
  24. package/docs/runtime-flow.md +148 -148
  25. package/docs/source-runtime-refactor-map.md +83 -83
  26. package/index.ts +6 -6
  27. package/package.json +1 -1
  28. package/src/agents/agent-serializer.ts +34 -34
  29. package/src/agents/discover-agents.ts +5 -4
  30. package/src/config/config.ts +28 -4
  31. package/src/extension/cross-extension-rpc.ts +82 -82
  32. package/src/extension/management.ts +37 -8
  33. package/src/extension/notification-router.ts +2 -2
  34. package/src/extension/register.ts +130 -8
  35. package/src/extension/registration/commands.ts +11 -9
  36. package/src/extension/registration/compaction-guard.ts +125 -125
  37. package/src/extension/registration/subagent-tools.ts +28 -19
  38. package/src/extension/registration/team-tool.ts +2 -1
  39. package/src/extension/result-watcher.ts +4 -4
  40. package/src/extension/run-bundle-schema.ts +8 -4
  41. package/src/extension/run-import.ts +4 -0
  42. package/src/extension/run-index.ts +23 -1
  43. package/src/extension/run-maintenance.ts +43 -24
  44. package/src/extension/team-tool/api.ts +2 -2
  45. package/src/extension/team-tool/cancel.ts +76 -4
  46. package/src/extension/team-tool/context.ts +1 -0
  47. package/src/extension/team-tool/doctor.ts +8 -1
  48. package/src/extension/team-tool/handle-settings.ts +188 -0
  49. package/src/extension/team-tool/inspect.ts +41 -41
  50. package/src/extension/team-tool/lifecycle-actions.ts +79 -79
  51. package/src/extension/team-tool/plan.ts +19 -19
  52. package/src/extension/team-tool/respond.ts +67 -0
  53. package/src/extension/team-tool/run.ts +6 -4
  54. package/src/extension/team-tool/status.ts +99 -93
  55. package/src/extension/team-tool-types.ts +4 -0
  56. package/src/extension/team-tool.ts +5 -1
  57. package/src/i18n.ts +184 -0
  58. package/src/observability/correlation.ts +2 -2
  59. package/src/observability/event-to-metric.ts +10 -3
  60. package/src/observability/exporters/adapter.ts +7 -1
  61. package/src/observability/exporters/otlp-exporter.ts +14 -2
  62. package/src/observability/exporters/prometheus-exporter.ts +9 -2
  63. package/src/observability/metric-registry.ts +18 -3
  64. package/src/observability/metric-retention.ts +11 -3
  65. package/src/observability/metric-sink.ts +9 -4
  66. package/src/observability/metrics-primitives.ts +4 -3
  67. package/src/prompt/prompt-runtime.ts +72 -68
  68. package/src/runtime/agent-control.ts +63 -63
  69. package/src/runtime/agent-memory.ts +72 -72
  70. package/src/runtime/agent-observability.ts +114 -114
  71. package/src/runtime/async-marker.ts +26 -26
  72. package/src/runtime/attention-events.ts +28 -23
  73. package/src/runtime/background-runner.ts +53 -53
  74. package/src/runtime/child-pi.ts +4 -4
  75. package/src/runtime/completion-guard.ts +95 -4
  76. package/src/runtime/concurrency.ts +1 -1
  77. package/src/runtime/crash-recovery.ts +32 -1
  78. package/src/runtime/crew-agent-runtime.ts +59 -58
  79. package/src/runtime/deadletter.ts +14 -4
  80. package/src/runtime/delivery-coordinator.ts +143 -0
  81. package/src/runtime/direct-run.ts +35 -35
  82. package/src/runtime/foreground-control.ts +82 -82
  83. package/src/runtime/green-contract.ts +46 -46
  84. package/src/runtime/group-join.ts +106 -106
  85. package/src/runtime/heartbeat-gradient.ts +28 -28
  86. package/src/runtime/heartbeat-watcher.ts +48 -4
  87. package/src/runtime/live-agent-control.ts +87 -87
  88. package/src/runtime/live-agent-manager.ts +85 -85
  89. package/src/runtime/live-control-realtime.ts +36 -36
  90. package/src/runtime/live-session-runtime.ts +305 -305
  91. package/src/runtime/manifest-cache.ts +2 -2
  92. package/src/runtime/model-fallback.ts +272 -261
  93. package/src/runtime/overflow-recovery.ts +157 -0
  94. package/src/runtime/parallel-research.ts +44 -44
  95. package/src/runtime/parallel-utils.ts +1 -1
  96. package/src/runtime/pi-json-output.ts +111 -111
  97. package/src/runtime/policy-engine.ts +79 -78
  98. package/src/runtime/post-exit-stdio-guard.ts +2 -2
  99. package/src/runtime/process-status.ts +56 -56
  100. package/src/runtime/progress-event-coalescer.ts +43 -43
  101. package/src/runtime/recovery-recipes.ts +74 -74
  102. package/src/runtime/retry-executor.ts +5 -0
  103. package/src/runtime/role-permission.ts +39 -39
  104. package/src/runtime/runtime-resolver.ts +1 -1
  105. package/src/runtime/session-resources.ts +25 -0
  106. package/src/runtime/session-snapshot.ts +59 -0
  107. package/src/runtime/session-usage.ts +79 -79
  108. package/src/runtime/sidechain-output.ts +29 -29
  109. package/src/runtime/stale-reconciler.ts +179 -0
  110. package/src/runtime/subagent-manager.ts +3 -3
  111. package/src/runtime/supervisor-contact.ts +59 -0
  112. package/src/runtime/task-display.ts +38 -38
  113. package/src/runtime/task-output-context.ts +127 -127
  114. package/src/runtime/task-runner/live-executor.ts +101 -101
  115. package/src/runtime/task-runner/progress.ts +119 -111
  116. package/src/runtime/task-runner/result-utils.ts +14 -14
  117. package/src/runtime/task-runner/state-helpers.ts +22 -22
  118. package/src/runtime/task-runner.ts +14 -0
  119. package/src/runtime/team-runner.ts +9 -10
  120. package/src/runtime/worker-heartbeat.ts +21 -21
  121. package/src/runtime/worker-startup.ts +57 -57
  122. package/src/schema/config-schema.ts +2 -1
  123. package/src/schema/team-tool-schema.ts +115 -109
  124. package/src/state/artifact-store.ts +4 -2
  125. package/src/state/atomic-write.ts +12 -4
  126. package/src/state/contracts.ts +109 -105
  127. package/src/state/event-log.ts +3 -4
  128. package/src/state/jsonl-writer.ts +4 -1
  129. package/src/state/locks.ts +9 -1
  130. package/src/state/task-claims.ts +44 -42
  131. package/src/state/usage.ts +29 -29
  132. package/src/subagents/async-entry.ts +1 -1
  133. package/src/subagents/index.ts +3 -3
  134. package/src/subagents/live/control.ts +1 -1
  135. package/src/subagents/live/manager.ts +1 -1
  136. package/src/subagents/live/realtime.ts +1 -1
  137. package/src/subagents/live/session-runtime.ts +1 -1
  138. package/src/subagents/manager.ts +1 -1
  139. package/src/subagents/spawn.ts +1 -1
  140. package/src/teams/discover-teams.ts +2 -2
  141. package/src/teams/team-serializer.ts +38 -38
  142. package/src/types/diff.d.ts +18 -18
  143. package/src/ui/crew-footer.ts +101 -101
  144. package/src/ui/crew-select-list.ts +111 -111
  145. package/src/ui/crew-widget.ts +5 -4
  146. package/src/ui/dashboard-panes/metrics-pane.ts +34 -34
  147. package/src/ui/dynamic-border.ts +25 -25
  148. package/src/ui/layout-primitives.ts +106 -106
  149. package/src/ui/live-run-sidebar.ts +1 -1
  150. package/src/ui/loaders.ts +158 -158
  151. package/src/ui/mascot.ts +3 -2
  152. package/src/ui/powerbar-publisher.ts +7 -6
  153. package/src/ui/render-diff.ts +119 -119
  154. package/src/ui/render-scheduler.ts +54 -14
  155. package/src/ui/run-dashboard.ts +39 -11
  156. package/src/ui/run-snapshot-cache.ts +336 -36
  157. package/src/ui/spinner.ts +17 -17
  158. package/src/ui/status-colors.ts +58 -54
  159. package/src/ui/syntax-highlight.ts +116 -116
  160. package/src/ui/theme-adapter.ts +1 -1
  161. package/src/ui/transcript-viewer.ts +7 -2
  162. package/src/utils/atomic-write.ts +33 -0
  163. package/src/utils/completion-dedupe.ts +63 -63
  164. package/src/utils/file-coalescer.ts +5 -3
  165. package/src/utils/frontmatter.ts +68 -36
  166. package/src/utils/git.ts +262 -262
  167. package/src/utils/ids.ts +12 -12
  168. package/src/utils/internal-error.ts +1 -1
  169. package/src/utils/names.ts +27 -26
  170. package/src/utils/paths.ts +1 -1
  171. package/src/utils/redaction.ts +44 -41
  172. package/src/utils/safe-paths.ts +47 -34
  173. package/src/utils/sleep.ts +2 -2
  174. package/src/utils/timings.ts +2 -0
  175. package/src/utils/visual.ts +9 -1
  176. package/src/workflows/discover-workflows.ts +4 -1
  177. package/src/workflows/validate-workflow.ts +40 -40
  178. package/src/worktree/branch-freshness.ts +45 -45
  179. package/src/worktree/worktree-manager.ts +6 -1
  180. package/teams/default.team.md +12 -12
  181. package/teams/fast-fix.team.md +11 -11
  182. package/teams/implementation.team.md +18 -18
  183. package/teams/parallel-research.team.md +14 -14
  184. package/teams/research.team.md +11 -11
  185. package/teams/review.team.md +12 -12
  186. package/workflows/default.workflow.md +29 -29
  187. package/workflows/fast-fix.workflow.md +22 -22
  188. package/workflows/implementation.workflow.md +38 -38
  189. package/workflows/parallel-research.workflow.md +46 -46
  190. package/workflows/research.workflow.md +22 -22
  191. package/workflows/review.workflow.md +30 -30
@@ -22,15 +22,26 @@ const DEFAULT_EVENTS = [
22
22
  "crew.mailbox.message",
23
23
  ];
24
24
 
25
+ /**
26
+ * Coordinates UI renders with debounce + fallback polling.
27
+ *
28
+ * Critical: uses recursive setTimeout instead of setInterval + a rendering
29
+ * guard (`rendering` / `pendingRender`) so that when render() takes longer
30
+ * than the fallback interval, callbacks do NOT pile up and storm the event
31
+ * loop. Instead, overlapping schedules are collapsed into a single deferred
32
+ * re-render.
33
+ */
25
34
  export class RenderScheduler {
26
35
  private readonly render: () => void;
27
36
  private readonly onInvalidate?: (payload: unknown) => void;
28
37
  private readonly debounceMs: number;
29
38
  private readonly fallbackMs: number;
30
39
  private debounceTimer: ReturnType<typeof setTimeout> | undefined;
31
- private fallbackTimer: ReturnType<typeof setInterval> | undefined;
40
+ private fallbackTimer: ReturnType<typeof setTimeout> | undefined;
32
41
  private disposed = false;
33
42
  private lastEventAt = 0;
43
+ private rendering = false;
44
+ private pendingRender = false;
34
45
  private readonly unsubs: Array<() => void> = [];
35
46
 
36
47
  constructor(events: RenderSchedulerEventBus | undefined, render: () => void, options: RenderSchedulerOptions = {}) {
@@ -39,8 +50,8 @@ export class RenderScheduler {
39
50
  this.debounceMs = options.debounceMs ?? 75;
40
51
  this.fallbackMs = options.fallbackMs ?? 750;
41
52
  for (const event of options.events ?? DEFAULT_EVENTS) this.subscribe(events, event);
42
- this.fallbackTimer = setInterval(() => this.fallback(), this.fallbackMs);
43
- this.fallbackTimer.unref?.();
53
+ this.fallbackTimer = setTimeout(() => this.fallbackLoop(), this.fallbackMs);
54
+ this.fallbackTimer.unref();
44
55
  }
45
56
 
46
57
  private subscribe(events: RenderSchedulerEventBus | undefined, event: string): void {
@@ -54,10 +65,19 @@ export class RenderScheduler {
54
65
  }
55
66
  }
56
67
 
57
- private fallback(): void {
68
+ /** Recursive setTimeout — avoids setInterval timer storms. */
69
+ private fallbackLoop(): void {
58
70
  if (this.disposed) return;
59
- if (Date.now() - this.lastEventAt < this.fallbackMs) return;
60
- this.flush();
71
+ if (Date.now() - this.lastEventAt < this.fallbackMs) {
72
+ if (this.disposed) return;
73
+ this.fallbackTimer = setTimeout(() => this.fallbackLoop(), this.fallbackMs);
74
+ this.fallbackTimer.unref();
75
+ return;
76
+ }
77
+ this.schedule();
78
+ if (this.disposed) return;
79
+ this.fallbackTimer = setTimeout(() => this.fallbackLoop(), this.fallbackMs);
80
+ this.fallbackTimer.unref();
61
81
  }
62
82
 
63
83
  schedule(payload?: unknown): void {
@@ -73,15 +93,39 @@ export class RenderScheduler {
73
93
  this.debounceTimer = undefined;
74
94
  this.flush();
75
95
  }, this.debounceMs);
76
- this.debounceTimer.unref?.();
96
+ this.debounceTimer.unref();
77
97
  }
78
98
 
99
+ /**
100
+ * Flush a render. If a render is already in progress the request is
101
+ * collapsed: `pendingRender` is set and the caller that holds
102
+ * `rendering==true` will loop one more time after finishing.
103
+ */
79
104
  flush(): void {
80
105
  if (this.disposed) return;
106
+ if (this.rendering) {
107
+ this.pendingRender = true;
108
+ return;
109
+ }
110
+ this.rendering = true;
111
+ this.pendingRender = false;
112
+ let iterations = 0;
81
113
  try {
82
- this.render();
114
+ do {
115
+ this.pendingRender = false;
116
+ this.render();
117
+ iterations += 1;
118
+ // Safety valve: 5 re-renders max per flush to prevent infinite loops
119
+ // if render() itself calls flush() synchronously.
120
+ } while (this.pendingRender && !this.disposed && iterations < 5);
83
121
  } catch (error) {
84
122
  logInternalError("render-scheduler.render", error);
123
+ } finally {
124
+ this.rendering = false;
125
+ // If we hit the iteration cap, schedule one more render to drain.
126
+ if (iterations >= 5 && this.pendingRender && !this.disposed) {
127
+ this.schedule();
128
+ }
85
129
  }
86
130
  }
87
131
 
@@ -89,15 +133,11 @@ export class RenderScheduler {
89
133
  if (this.disposed) return;
90
134
  this.disposed = true;
91
135
  if (this.debounceTimer) clearTimeout(this.debounceTimer);
92
- if (this.fallbackTimer) clearInterval(this.fallbackTimer);
136
+ if (this.fallbackTimer) clearTimeout(this.fallbackTimer);
93
137
  this.debounceTimer = undefined;
94
138
  this.fallbackTimer = undefined;
95
139
  for (const unsub of this.unsubs.splice(0)) {
96
- try {
97
- unsub();
98
- } catch (error) {
99
- logInternalError("render-scheduler.unsubscribe", error);
100
- }
140
+ try { unsub(); } catch (error) { logInternalError("render-scheduler.unsubscribe", error); }
101
141
  }
102
142
  }
103
143
  }
@@ -12,6 +12,7 @@ import { Box, Text } from "./layout-primitives.ts";
12
12
  import { DynamicCrewBorder } from "./dynamic-border.ts";
13
13
  import { CrewFooter } from "./crew-footer.ts";
14
14
  import { aggregateUsage } from "../state/usage.ts";
15
+ import { logInternalError } from "../utils/internal-error.ts";
15
16
  import { renderAgentsPane } from "./dashboard-panes/agents-pane.ts";
16
17
  import { renderMailboxPane } from "./dashboard-panes/mailbox-pane.ts";
17
18
  import { renderProgressPane } from "./dashboard-panes/progress-pane.ts";
@@ -194,18 +195,38 @@ function runLabel(run: TeamRunManifest, selected: boolean, snapshotCache?: RunSn
194
195
  const stale = isLikelyOrphanedActiveRun(run, agents);
195
196
  const running = agents.find((agent) => agent.status === "running");
196
197
  const queued = agents.find((agent) => agent.status === "queued");
197
- const step = stale ? "orphaned queued run" : running ? `step ${running.taskId}` : queued ? `queued ${queued.taskId}` : `agents ${agents.length}`;
198
+ const step = stale ? "orphaned queued run" : running ? `step ${running.taskId.replace(/[\x00-\x1f\x7f-\x9f]/g, "")}` : queued ? `queued ${queued.taskId.replace(/[\x00-\x1f\x7f-\x9f]/g, "")}` : `agents ${agents.length}`;
198
199
  const status: RunStatus = stale ? "stale" : (run.status as RunStatus);
199
200
  const marker = selected ? "›" : " ";
200
201
  return `${marker} ${iconForStatus(status, { runningGlyph: spinnerFrame(run.runId) })} ${run.runId.slice(-8)} ${status} | ${run.team}/${run.workflow ?? "none"} | ${step} | ${run.goal}`;
201
202
  }
202
203
 
204
+ interface ResolvedRun {
205
+ manifest: TeamRunManifest;
206
+ snapshot: RunUiSnapshot | undefined;
207
+ agents: CrewAgentRecord[];
208
+ status: RunStatus;
209
+ }
210
+
211
+ function resolveRuns(runs: TeamRunManifest[], snapshotCache?: RunSnapshotCache): Map<string, ResolvedRun> {
212
+ const map = new Map<string, ResolvedRun>();
213
+ for (const run of runs) {
214
+ const snapshot = snapshotFor(run, snapshotCache);
215
+ const agents = snapshot?.agents ?? agentsFor(run, snapshotCache);
216
+ const displayRun = snapshot?.manifest ?? run;
217
+ const status: RunStatus = isLikelyOrphanedActiveRun(displayRun, agents) ? "stale" : (displayRun.status as RunStatus);
218
+ map.set(run.runId, { manifest: run, snapshot, agents, status });
219
+ }
220
+ return map;
221
+ }
222
+
203
223
  function groupedRuns(runs: TeamRunManifest[], snapshotCache?: RunSnapshotCache): Array<{ label: string; run?: TeamRunManifest }> {
204
- const active = runs.filter((run) => isDisplayActiveRun(snapshotFor(run, snapshotCache)?.manifest ?? run, agentsFor(run, snapshotCache)));
205
- const recent = runs.filter((run) => !isDisplayActiveRun(snapshotFor(run, snapshotCache)?.manifest ?? run, agentsFor(run, snapshotCache)));
224
+ const resolved = resolveRuns(runs, snapshotCache);
206
225
  const rows: Array<{ label: string; run?: TeamRunManifest }> = [];
226
+ const active = runs.filter((run) => isDisplayActiveRun(resolved.get(run.runId)?.snapshot?.manifest ?? run, resolved.get(run.runId)?.agents ?? []));
227
+ const rest = runs.filter((run) => !isDisplayActiveRun(resolved.get(run.runId)?.snapshot?.manifest ?? run, resolved.get(run.runId)?.agents ?? []));
207
228
  if (active.length) rows.push({ label: "Active" }, ...active.map((run) => ({ label: run.runId, run })));
208
- if (recent.length) rows.push({ label: "Recent" }, ...recent.map((run) => ({ label: run.runId, run })));
229
+ if (rest.length) rows.push({ label: "Recent" }, ...rest.map((run) => ({ label: run.runId, run })));
209
230
  return rows;
210
231
  }
211
232
 
@@ -214,13 +235,9 @@ function selectedRunFromGrouped(runs: TeamRunManifest[], selected: number, snaps
214
235
  }
215
236
 
216
237
  function countByStatus(runs: TeamRunManifest[], snapshotCache?: RunSnapshotCache): string {
238
+ const resolved = resolveRuns(runs, snapshotCache);
217
239
  const counts = new Map<RunStatus, number>();
218
- for (const run of runs) {
219
- const snapshot = snapshotFor(run, snapshotCache);
220
- const displayRun = snapshot?.manifest ?? run;
221
- const status: RunStatus = isLikelyOrphanedActiveRun(displayRun, snapshot?.agents ?? agentsFor(run, snapshotCache)) ? "stale" : (displayRun.status as RunStatus);
222
- counts.set(status, (counts.get(status) ?? 0) + 1);
223
- }
240
+ for (const r of resolved.values()) counts.set(r.status, (counts.get(r.status) ?? 0) + 1);
224
241
  return [...counts.entries()].map(([status, count]) => `${status}=${count}`).join(", ") || "none";
225
242
  }
226
243
 
@@ -253,10 +270,12 @@ export class RunDashboard implements DashboardComponent {
253
270
  private refreshRuns(): void {
254
271
  if (!this.options.runProvider) return;
255
272
  const selectedRunId = this.selectedRunId();
256
- this.runs = this.options.runProvider();
273
+ const next = this.options.runProvider();
274
+ this.runs = Array.isArray(next) ? next : this.runs;
257
275
  if (selectedRunId) {
258
276
  const nextIndex = groupedRuns(this.runs, this.options.snapshotCache).filter((row) => row.run).findIndex((row) => row.run?.runId === selectedRunId);
259
277
  if (nextIndex >= 0) this.selected = nextIndex;
278
+ else this.selected = 0;
260
279
  }
261
280
  }
262
281
 
@@ -289,6 +308,15 @@ export class RunDashboard implements DashboardComponent {
289
308
  }
290
309
 
291
310
  render(width: number): string[] {
311
+ try {
312
+ return this.renderUnsafe(width);
313
+ } catch (error) {
314
+ logInternalError("run-dashboard.render", error);
315
+ return renderLines(["Dashboard error — see logs for details."], width);
316
+ }
317
+ }
318
+
319
+ private renderUnsafe(width: number): string[] {
292
320
  this.refreshRuns();
293
321
  const signature = this.buildSignature();
294
322
  if (signature !== this.cachedVersion || this.cachedWidth !== width) {