pi-crew 0.11.21 → 0.11.23

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 (89) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/dist/index.mjs +542 -531
  3. package/package.json +2 -1
  4. package/src/errors.ts +14 -1
  5. package/src/extension/async-notifier.ts +42 -17
  6. package/src/extension/cross-extension-rpc.ts +42 -5
  7. package/src/extension/notification-router.ts +15 -2
  8. package/src/extension/registration/lifecycle-handlers.ts +36 -13
  9. package/src/extension/registration/runtime-cleanup.ts +17 -0
  10. package/src/extension/registration/wire-cross-extension.ts +26 -0
  11. package/src/extension/rpc-hmac.ts +53 -2
  12. package/src/extension/team-tool/cancel.ts +34 -21
  13. package/src/extension/team-tool/dispatch/index.ts +6 -5
  14. package/src/extension/team-tool/dispatch/status.ts +42 -2
  15. package/src/extension/team-tool/respond.ts +8 -6
  16. package/src/extension/team-tool/run.ts +4 -4
  17. package/src/extension/team-tool.ts +24 -2
  18. package/src/prompt/inbox-poll.ts +77 -25
  19. package/src/prompt/message-tool.ts +62 -4
  20. package/src/prompt/prompt-runtime.ts +82 -32
  21. package/src/prompt/surface-worker.ts +13 -1
  22. package/src/runtime/broker/crew-broker-client.ts +74 -16
  23. package/src/runtime/broker/crew-broker.ts +120 -97
  24. package/src/runtime/broker/protocol/msg-send-targets.ts +74 -0
  25. package/src/runtime/broker/protocol/request-parsers.ts +68 -0
  26. package/src/runtime/broker/wait-status-cache.ts +14 -5
  27. package/src/runtime/child-pi/child-pi-spawn.ts +47 -9
  28. package/src/runtime/child-pi/child-pi.ts +20 -2
  29. package/src/runtime/child-pi/mock-fixtures.ts +23 -0
  30. package/src/runtime/compaction/compact-stages/truncation-stage.ts +45 -3
  31. package/src/runtime/compaction/important-line-classifier.ts +85 -11
  32. package/src/runtime/compaction/tool-output-pruner.ts +35 -11
  33. package/src/runtime/crew-agent-records.ts +32 -14
  34. package/src/runtime/delivery-coordinator.ts +30 -0
  35. package/src/runtime/dispatch-batch.ts +9 -6
  36. package/src/runtime/dwf-state-store.ts +41 -7
  37. package/src/runtime/finalize-run.ts +13 -2
  38. package/src/runtime/goal-workflow/dynamic-workflow-context.ts +192 -64
  39. package/src/runtime/goal-workflow/goal-loop-runner.ts +130 -3
  40. package/src/runtime/heartbeat/heartbeat-watcher.ts +7 -0
  41. package/src/runtime/live-session/embedded-tier.ts +3 -1
  42. package/src/runtime/live-session/live-session-runtime.ts +54 -1
  43. package/src/runtime/model/model-fallback.ts +23 -1
  44. package/src/runtime/output/pi-json-output.ts +26 -1
  45. package/src/runtime/policy-engine.ts +18 -0
  46. package/src/runtime/process/zombie-scanner.ts +84 -16
  47. package/src/runtime/process-status.ts +6 -1
  48. package/src/runtime/recovery/crash-recovery.ts +170 -28
  49. package/src/runtime/rpc/rpc-worker.ts +44 -1
  50. package/src/runtime/scheduling/run-coalesced-task-group.ts +73 -6
  51. package/src/runtime/scheduling/scheduler.ts +74 -11
  52. package/src/runtime/scratchpad/engine.ts +26 -3
  53. package/src/runtime/settings-store.ts +22 -2
  54. package/src/runtime/stale-reconciler.ts +4 -1
  55. package/src/runtime/surface/tmux-provider.ts +24 -3
  56. package/src/runtime/task-health.ts +16 -1
  57. package/src/runtime/task-output-context.ts +143 -71
  58. package/src/runtime/task-packet.ts +70 -17
  59. package/src/runtime/task-runner/child-executor.ts +27 -0
  60. package/src/runtime/task-runner/output-splitter.ts +79 -16
  61. package/src/runtime/task-runner/post-execution.ts +75 -15
  62. package/src/runtime/task-runner/state-helpers.ts +92 -2
  63. package/src/runtime/usage-tracker.ts +11 -0
  64. package/src/runtime/verification/green-contract.ts +17 -0
  65. package/src/runtime/verification/verification-gates.ts +24 -6
  66. package/src/runtime/workspace-lock.ts +53 -0
  67. package/src/schema/team-tool-schema.ts +10 -4
  68. package/src/state/coordination/mailbox.ts +133 -38
  69. package/src/state/decision-ledger.ts +56 -8
  70. package/src/state/event-log/cursor.ts +14 -2
  71. package/src/state/event-log/event-log-rotation.ts +83 -5
  72. package/src/state/event-log/event-log.ts +181 -12
  73. package/src/state/stores/active-run-registry.ts +10 -3
  74. package/src/state/types.ts +9 -1
  75. package/src/ui/agents-jobs-browser.ts +22 -8
  76. package/src/ui/dashboard-panes/agents-pane.ts +5 -2
  77. package/src/ui/events-state-source.ts +67 -28
  78. package/src/ui/inline-panel/agent-pane.ts +13 -6
  79. package/src/ui/inline-panel/agent-transcript.ts +33 -21
  80. package/src/ui/inline-panel/agent-view-overlay.ts +18 -3
  81. package/src/ui/inline-panel/index.ts +21 -1
  82. package/src/ui/live-run-sidebar.ts +14 -3
  83. package/src/ui/overlays/mailbox-compose-overlay.ts +10 -4
  84. package/src/ui/powerbar-publisher.ts +6 -3
  85. package/src/ui/rail.ts +7 -1
  86. package/src/ui/render-scheduler.ts +53 -1
  87. package/src/ui/run-action-dispatcher.ts +68 -20
  88. package/src/ui/status-colors.ts +9 -0
  89. package/src/utils/env-filter.ts +8 -0
@@ -13,11 +13,14 @@ async function handleTeamTool(
13
13
  }
14
14
 
15
15
  import { isToolError, textFromToolResult } from "../extension/tool-result.ts";
16
+ import { isRunLockBusyError } from "../runtime/broker/protocol/lock-busy.ts";
16
17
  import { readCrewAgents } from "../runtime/crew-agent-records.ts";
17
18
  import { exportDiagnostic } from "../runtime/diagnostic-export.ts";
19
+ import { withRunLockSync } from "../state/coordination/locks.ts";
18
20
  import type { MailboxDirection, MailboxMessage } from "../state/coordination/mailbox.ts";
19
21
  import { appendEventBuffered } from "../state/event-log/event-log.ts";
20
22
  import { loadRunManifestById, saveRunTasks } from "../state/stores/state-store.ts";
23
+ import type { TeamTaskState } from "../state/types.ts";
21
24
  import { logInternalError } from "../utils/internal-error.ts";
22
25
  import { formatCount } from "./format-helpers.ts";
23
26
 
@@ -120,35 +123,80 @@ export function dispatchHealthRecovery(ctx: ExtensionContext, runId: string): Pr
120
123
  });
121
124
  }
122
125
 
126
+ /** Staleness window for the kill-stale sweep (R4-6). */
127
+ const KILL_STALE_HEARTBEAT_MS = 60_000;
128
+
129
+ /**
130
+ * R4-6 (DR-02 Wave 2 Cluster C): pure RMW core of the kill-stale sweep —
131
+ * mark running/queued tasks whose heartbeat is older than `staleMs` dead.
132
+ * Exported so the locked dispatcher body stays unit-testable without a
133
+ * harness (repo seam pattern; see state-helpers-cas-contention.test.ts).
134
+ * Predicate is byte-identical to the previous inline map.
135
+ */
136
+ export function markStaleWorkerHeartbeatsDead(
137
+ tasks: readonly TeamTaskState[],
138
+ currentMs: number,
139
+ staleMs: number = KILL_STALE_HEARTBEAT_MS,
140
+ ): { tasks: TeamTaskState[]; count: number } {
141
+ const now = new Date(currentMs).toISOString();
142
+ let count = 0;
143
+ const updated = tasks.map((task) => {
144
+ if ((task.status !== "running" && task.status !== "queued") || !task.heartbeat || task.heartbeat.alive === false) return task;
145
+ const lastSeenMs = Date.parse(task.heartbeat.lastSeenAt);
146
+ if (!Number.isFinite(lastSeenMs) || currentMs - lastSeenMs <= staleMs) return task;
147
+ count += 1;
148
+ return {
149
+ ...task,
150
+ heartbeat: { ...task.heartbeat, alive: false, lastSeenAt: now },
151
+ };
152
+ });
153
+ return { tasks: updated, count };
154
+ }
155
+
123
156
  export async function dispatchKillStaleWorkers(ctx: ExtensionContext, runId: string): Promise<RunActionResult> {
124
157
  try {
125
- const loaded = loadRunManifestById(ctx.cwd, runId); // NOTE: no withRunLock - best-effort only; concurrent writes may cause inconsistency;
158
+ const loaded = loadRunManifestById(ctx.cwd, runId);
126
159
  if (!loaded) return { ok: false, message: `Run '${runId}' not found.` };
127
- const currentMs = Date.now();
128
- const staleMs = 60_000;
129
- const now = new Date(currentMs).toISOString();
130
- let count = 0;
131
- const tasks = loaded.tasks.map((task) => {
132
- if ((task.status !== "running" && task.status !== "queued") || !task.heartbeat || task.heartbeat.alive === false) return task;
133
- const lastSeenMs = Date.parse(task.heartbeat.lastSeenAt);
134
- if (!Number.isFinite(lastSeenMs) || currentMs - lastSeenMs <= staleMs) return task;
135
- count += 1;
136
- return {
137
- ...task,
138
- heartbeat: { ...task.heartbeat, alive: false, lastSeenAt: now },
139
- };
140
- });
141
- saveRunTasks(loaded.manifest, tasks);
160
+ // R4-6 (DR-02 Wave 2 Cluster C): this sweep used to be an UNLOCKED
161
+ // load→map→save (":125 no withRunLock - best-effort") — a cross-process
162
+ // writer holding the run lock (leader heartbeats, task claims, delegate
163
+ // flushes) could land a fresh tasks.json between our read and write and
164
+ // get clobbered by the STALE array. Now a run-locked RMW with the fresh
165
+ // re-read INSIDE the lock (R13-S1 canonical pattern, team-tool/api/heartbeat.ts).
166
+ // Best-effort semantics are preserved on contention: the UI never blocks —
167
+ // the sweep is skipped with a warn, never a stale write.
168
+ let applied: { tasks: TeamTaskState[]; count: number } | undefined;
169
+ try {
170
+ applied = withRunLockSync(loaded.manifest, () => {
171
+ const fresh = loadRunManifestById(loaded.manifest.cwd, loaded.manifest.runId);
172
+ if (!fresh) return undefined;
173
+ const result = markStaleWorkerHeartbeatsDead(fresh.tasks, Date.now());
174
+ // count=0 → nothing changed; skip the identical-content rewrite.
175
+ if (result.count > 0) saveRunTasks(fresh.manifest, result.tasks);
176
+ return result;
177
+ });
178
+ } catch (error) {
179
+ if (isRunLockBusyError(error)) {
180
+ logInternalError("run-action-dispatcher.killStale", error, `runId=${runId} skipped: run lock busy`, "warn");
181
+ return {
182
+ ok: false,
183
+ message: `Skipped kill-stale for run '${runId}': another operation holds the run lock. Try again in a moment.`,
184
+ };
185
+ }
186
+ throw error;
187
+ }
188
+ if (!applied) return { ok: false, message: `Run '${runId}' not found.` };
189
+ const message = `Marked ${applied.count} stale worker heartbeat(s) dead.`;
142
190
  appendEventBuffered(loaded.manifest.eventsPath, {
143
191
  type: "worker.kill_stale",
144
192
  runId,
145
- message: `Marked ${count} stale worker heartbeat(s) dead.`,
146
- data: { count },
193
+ message,
194
+ data: { count: applied.count },
147
195
  }).catch((e) => logInternalError("run_action_dispatcher.buffered", e, "type=worker.kill_stale"));
148
196
  return {
149
197
  ok: true,
150
- message: `Marked ${count} stale worker heartbeat(s) dead.`,
151
- data: { count },
198
+ message,
199
+ data: { count: applied.count },
152
200
  };
153
201
  } catch (error) {
154
202
  return err(error);
@@ -19,6 +19,11 @@ export function colorForStatus(status: RunStatus): CrewThemeColor {
19
19
  return "accent";
20
20
  case "waiting":
21
21
  return "muted";
22
+ // F4 (DR-02 wave 2): "planning" was missing from BOTH palettes — it
23
+ // fell through to this default, which was correct-by-luck but unsigned.
24
+ // Informational/dim, same tier as queued's default fall-through.
25
+ case "planning":
26
+ return "dim";
22
27
  case "completed":
23
28
  return "success";
24
29
  case "failed":
@@ -52,6 +57,10 @@ export function iconForStatus(status: RunStatus, options?: { runningGlyph?: stri
52
57
  return "⏳";
53
58
  case "queued":
54
59
  return "◦";
60
+ case "planning":
61
+ // F4: informational — the dim dot already used for unknown-but-known
62
+ // states; kept in the colorizeStatusGlyphs vocabulary (dim tier).
63
+ return "·";
55
64
  case "blocked":
56
65
  return "⏸";
57
66
  case "needs_attention":
@@ -60,6 +60,14 @@ export function providerEnvKeys(modelId: string | undefined): string[] {
60
60
  * Build a scoped allowlist that includes system vars + only the provider keys
61
61
  * relevant to the assigned model (and its fallback chain).
62
62
  *
63
+ * R6-8 (DR-03 round-6) contract note: callers SHOULD pass the task's full
64
+ * candidate chain (primary + fallbacks) — child-pi-spawn.ts threads it from
65
+ * the executor's attempt chain. Bare model ids (no "provider/" prefix)
66
+ * contribute ZERO keys here (providerEnvKeys returns [] for them); scoping is
67
+ * never widened to all-providers to compensate — instead the spawn layer warns
68
+ * when a truly-bare single model resolves zero provider keys (see
69
+ * child-pi-spawn.ts R6-8 warn; auth.json users are unaffected).
70
+ *
63
71
  * @param baseAllowList - Non-provider system vars to always include
64
72
  * @param models - Model IDs to include provider keys for (primary + fallbacks)
65
73
  * @returns Combined allowlist with scoped provider keys