pi-crew 0.11.11 → 0.11.13

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 (50) hide show
  1. package/CHANGELOG.md +102 -0
  2. package/dist/index.mjs +551 -519
  3. package/package.json +1 -1
  4. package/src/config/config-validation.ts +9 -0
  5. package/src/config/defaults.ts +7 -0
  6. package/src/config/env-vars.ts +24 -0
  7. package/src/config/types.ts +11 -0
  8. package/src/extension/registration/tool-registration.ts +7 -0
  9. package/src/extension/registration/verify-gate-tool.ts +263 -0
  10. package/src/extension/team-tool/run-deadline.ts +22 -1
  11. package/src/extension/team-tool/run.ts +38 -2
  12. package/src/extension/team-tool-types.ts +4 -0
  13. package/src/extension/team-tool.ts +31 -3
  14. package/src/prompt/inbox-poll.ts +141 -3
  15. package/src/runtime/broker/crew-broker.ts +48 -2
  16. package/src/runtime/broker/protocol/lock-busy.ts +47 -1
  17. package/src/runtime/broker/protocol/request-parsers.ts +8 -1
  18. package/src/runtime/budget-enforcement.ts +36 -20
  19. package/src/runtime/child-pi/child-pi-constants.ts +24 -0
  20. package/src/runtime/child-pi/child-pi-kill.ts +59 -2
  21. package/src/runtime/child-pi/child-pi-rpc.ts +1021 -0
  22. package/src/runtime/child-pi/child-pi-steering.ts +10 -8
  23. package/src/runtime/child-pi/child-pi-streams.ts +69 -19
  24. package/src/runtime/child-pi/child-pi.ts +29 -30
  25. package/src/runtime/child-pi/owned-process.ts +434 -0
  26. package/src/runtime/child-pi/postmortem-registry.ts +134 -0
  27. package/src/runtime/classifier/ambient-noise-gate.ts +126 -0
  28. package/src/runtime/classifier/classifier-service.ts +277 -61
  29. package/src/runtime/dispatch-batch.ts +32 -0
  30. package/src/runtime/live-session/live-agent-control.ts +2 -0
  31. package/src/runtime/live-session/live-agent-manager.ts +72 -2
  32. package/src/runtime/live-session/live-session-runtime.ts +435 -40
  33. package/src/runtime/merge-loop.ts +63 -35
  34. package/src/runtime/recovery/crash-recovery.ts +141 -3
  35. package/src/runtime/recovery/retry-executor.ts +29 -0
  36. package/src/runtime/run-tracker.ts +28 -0
  37. package/src/runtime/stale-reconciler.ts +36 -1
  38. package/src/runtime/task-runner.ts +103 -0
  39. package/src/runtime/verification/classifier-pre-gate.ts +262 -0
  40. package/src/runtime/verification/verification-gates.ts +5 -1
  41. package/src/runtime/verification/verify-gate.ts +394 -0
  42. package/src/schema/config-schema.ts +13 -0
  43. package/src/state/contracts.ts +18 -0
  44. package/src/state/coordination/mailbox.ts +203 -55
  45. package/src/state/stores/sqlite-run-state.ts +378 -0
  46. package/src/state/stores/state-store.ts +134 -6
  47. package/src/ui/agents-jobs-browser.ts +154 -8
  48. package/src/ui/events-state-source.ts +358 -0
  49. package/src/ui/run-snapshot-cache.ts +87 -3
  50. package/src/ui/widget/widget-model.ts +13 -2
@@ -12,6 +12,7 @@ import { loadPlanRecords, planFilePath } from "../state/stores/plan-store.ts";
12
12
  import { loadRunManifestById, loadRunManifestByIdAsync } from "../state/stores/state-store.ts";
13
13
  import type { TeamRunManifest, TeamTaskState } from "../state/types.ts";
14
14
  import { extractDwfPhaseState } from "./dwf-phase-display.ts";
15
+ import { type EventsStateSource, eventsStateSource } from "./events-state-source.ts";
15
16
  import { runEventBus } from "./run-event-bus.ts";
16
17
  import type {
17
18
  RunSnapshotCache as RunSnapshotCacheBase,
@@ -80,13 +81,38 @@ interface CacheEntry {
80
81
  lastAccessMs: number;
81
82
  }
82
83
 
84
+ /** U7: one attached events-state source + its frame unsubscribe handle. */
85
+ interface SourceSub {
86
+ source: EventsStateSource;
87
+ unsub: () => void;
88
+ }
89
+
83
90
  export interface RunSnapshotCacheOptions {
84
91
  ttlMs?: number;
85
92
  maxEntries?: number;
86
93
  recentEvents?: number;
87
94
  recentOutputLines?: number;
95
+ /**
96
+ * U7 (spec 2026-10-09): opt-in events-state layer. When set, each built
97
+ * entry gets a shared `EventsStateSource` (derived view over the run's
98
+ * events.jsonl — snapshot + tail-follow frames via the existing
99
+ * readEventsCursor watermark cursor) and ONE unref'd interval tail-follows
100
+ * every attached source; frames route through the SAME 80ms coalesced →
101
+ * async stamp-gated refresh pipeline as the run event bus (never a sync
102
+ * rebuild, never a delete — the flicker-fix contract holds). Slice
103
+ * computation is UNCHANGED (parity): recentEvents/cancellationReason still
104
+ * come from the cache's own tail read, because the cursor path only
105
+ * carries seq-stamped events. Default (unset): zero new I/O, zero timers —
106
+ * byte-identical behavior.
107
+ */
108
+ eventsState?: { pollMs?: number } | true;
88
109
  }
89
110
 
111
+ /** Default events-state tail-follow cadence (ms) — matches the legacy
112
+ * agents-jobs-browser POLL_INTERVAL_MS it replaces, at a fraction of the
113
+ * per-tick cost (stat + delta bytes vs full manifest+agents reparse). */
114
+ const EVENTS_STATE_POLL_MS_DEFAULT = 400;
115
+
90
116
  function zeroStamp(): FileStamp {
91
117
  return { mtimeMs: 0, size: 0 };
92
118
  }
@@ -888,10 +914,58 @@ export function createRunSnapshotCache(cwd: string, options: RunSnapshotCacheOpt
888
914
  }
889
915
  if (!key) key = entries.keys().next().value;
890
916
  if (!key) break;
891
- entries.delete(key);
917
+ dropEntry(key);
892
918
  }
893
919
  }
894
920
 
921
+ // ── U7: events-state layer (opt-in) ──────────────────────────────────
922
+ const eventsStateEnabled = options.eventsState !== undefined;
923
+ const eventsStatePollMs =
924
+ (typeof options.eventsState === "object" ? options.eventsState?.pollMs : undefined) ?? EVENTS_STATE_POLL_MS_DEFAULT;
925
+ const sourceSubs = new Map<string, SourceSub>();
926
+ function attachEventsSource(runId: string, eventsPath: string): void {
927
+ if (!eventsStateEnabled || sourceSubs.has(runId)) return;
928
+ const source = eventsStateSource(runId, eventsPath);
929
+ // Frames → the EXISTING coalesced async refresh (80ms batch → preloadStale
930
+ // → stamp check). A frame for a run whose entry was dropped is ignored;
931
+ // a stamp-equal refresh re-stamps in place (no rebuild), so a quiet run
932
+ // costs one async stat round per coalesce window.
933
+ const unsub = source.subscribe(() => {
934
+ if (entries.has(runId)) scheduleCoalescedRefresh(runId);
935
+ });
936
+ sourceSubs.set(runId, { source, unsub });
937
+ }
938
+ function detachEventsSource(runId: string): void {
939
+ const sub = sourceSubs.get(runId);
940
+ if (!sub) return;
941
+ sourceSubs.delete(runId);
942
+ sub.unsub();
943
+ sub.source.dispose();
944
+ }
945
+ function detachAllEventsSources(): void {
946
+ for (const runId of [...sourceSubs.keys()]) detachEventsSource(runId);
947
+ }
948
+ function dropEntry(runId: string): void {
949
+ entries.delete(runId);
950
+ detachEventsSource(runId);
951
+ }
952
+ let eventsStateTimer: ReturnType<typeof setInterval> | undefined;
953
+ if (eventsStateEnabled) {
954
+ eventsStateTimer = setInterval(() => {
955
+ for (const sub of sourceSubs.values()) {
956
+ try {
957
+ // Tail-follow every attached run's events.jsonl. Cheap on an
958
+ // unchanged log (stat + ring-served cursor read); discovered
959
+ // batches become frames → coalesced refresh above.
960
+ sub.source.poll();
961
+ } catch {
962
+ /* non-fatal — next tick retries */
963
+ }
964
+ }
965
+ }, eventsStatePollMs);
966
+ eventsStateTimer.unref();
967
+ }
968
+
895
969
  function build(runId: string, previous?: CacheEntry): CacheEntry {
896
970
  let loaded: ReturnType<typeof loadRunManifestById>;
897
971
  try {
@@ -944,6 +1018,7 @@ export function createRunSnapshotCache(cwd: string, options: RunSnapshotCacheOpt
944
1018
  ...(isPlanUiEnabled() ? { plans: loadPlanRecords(loaded.manifest) } : {}),
945
1019
  };
946
1020
  const stamps = stampsFor(loaded.manifest, agents);
1021
+ attachEventsSource(runId, loaded.manifest.eventsPath);
947
1022
  const sliceSignatures = computeSliceSignatures(base);
948
1023
  const snapshot: RunUiSnapshot = {
949
1024
  ...base,
@@ -1003,6 +1078,7 @@ export function createRunSnapshotCache(cwd: string, options: RunSnapshotCacheOpt
1003
1078
  ...(isPlanUiEnabled() ? { plans: loadPlanRecords(loaded.manifest) } : {}),
1004
1079
  };
1005
1080
  const stamps = await stampsForAsync(loaded.manifest, agents);
1081
+ attachEventsSource(runId, loaded.manifest.eventsPath);
1006
1082
  const sliceSignatures = computeSliceSignatures(base);
1007
1083
  const snapshot: RunUiSnapshot = {
1008
1084
  ...base,
@@ -1184,8 +1260,11 @@ export function createRunSnapshotCache(cwd: string, options: RunSnapshotCacheOpt
1184
1260
  preloadStale,
1185
1261
  preloadAllStale,
1186
1262
  invalidate(runId?: string): void {
1187
- if (runId) entries.delete(runId);
1188
- else entries.clear();
1263
+ if (runId) dropEntry(runId);
1264
+ else {
1265
+ entries.clear();
1266
+ detachAllEventsSources();
1267
+ }
1189
1268
  },
1190
1269
  snapshotsByKey(): Map<string, RunUiSnapshot> {
1191
1270
  return new Map([...entries.entries()].map(([key, entry]) => [key, entry.snapshot]));
@@ -1196,6 +1275,11 @@ export function createRunSnapshotCache(cwd: string, options: RunSnapshotCacheOpt
1196
1275
  if (disposed) return;
1197
1276
  disposed = true;
1198
1277
  unsubscribe();
1278
+ if (eventsStateTimer) {
1279
+ clearInterval(eventsStateTimer);
1280
+ eventsStateTimer = undefined;
1281
+ }
1282
+ detachAllEventsSources();
1199
1283
  inFlightRefreshes.clear();
1200
1284
  entries.clear();
1201
1285
  },
@@ -14,7 +14,7 @@ import { reconcileAllStaleRuns } from "../../runtime/recovery/crash-recovery.ts"
14
14
  import type { TeamRunManifest } from "../../state/types.ts";
15
15
  import { goalFlagSuffix } from "../goal-flag.ts";
16
16
  import { ACTIVE, RAIL, shortId } from "../rail.ts";
17
- import type { RunSnapshotCache } from "../snapshot-types.ts";
17
+ import type { RunSnapshotCache, RunUiSnapshot } from "../snapshot-types.ts";
18
18
  import type { WidgetRun } from "./widget-types.ts";
19
19
 
20
20
  let lastStaleReconcileAt = 0;
@@ -64,7 +64,18 @@ export function activeWidgetRuns(
64
64
  return runs
65
65
  .map((run) => {
66
66
  try {
67
- const snapshot = snapshotCache?.get(run.runId);
67
+ // U7 (spec 2026-10-09): prefer the committed-state read when the
68
+ // cache exposes it. `readForRender` returns the SAME cached
69
+ // snapshot `get` would (identical render — parity), but a lapsed
70
+ // TTL schedules the coalesced ASYNC refresh (80ms batch → stamp
71
+ // check → rebuild only when files changed) instead of waiting for
72
+ // an external poller to notice. Structural check: legacy callers
73
+ // and test doubles may implement only `get`.
74
+ const committed = snapshotCache as
75
+ | { readForRender?: (runId: string) => RunUiSnapshot | undefined; get?: (runId: string) => RunUiSnapshot | undefined }
76
+ | undefined;
77
+ const snapshot =
78
+ typeof committed?.readForRender === "function" ? committed.readForRender(run.runId) : snapshotCache?.get(run.runId);
68
79
  if (snapshot) {
69
80
  return {
70
81
  run: snapshot.manifest,