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.
- package/CHANGELOG.md +102 -0
- package/dist/index.mjs +551 -519
- package/package.json +1 -1
- package/src/config/config-validation.ts +9 -0
- package/src/config/defaults.ts +7 -0
- package/src/config/env-vars.ts +24 -0
- package/src/config/types.ts +11 -0
- package/src/extension/registration/tool-registration.ts +7 -0
- package/src/extension/registration/verify-gate-tool.ts +263 -0
- package/src/extension/team-tool/run-deadline.ts +22 -1
- package/src/extension/team-tool/run.ts +38 -2
- package/src/extension/team-tool-types.ts +4 -0
- package/src/extension/team-tool.ts +31 -3
- package/src/prompt/inbox-poll.ts +141 -3
- package/src/runtime/broker/crew-broker.ts +48 -2
- package/src/runtime/broker/protocol/lock-busy.ts +47 -1
- package/src/runtime/broker/protocol/request-parsers.ts +8 -1
- package/src/runtime/budget-enforcement.ts +36 -20
- package/src/runtime/child-pi/child-pi-constants.ts +24 -0
- package/src/runtime/child-pi/child-pi-kill.ts +59 -2
- package/src/runtime/child-pi/child-pi-rpc.ts +1021 -0
- package/src/runtime/child-pi/child-pi-steering.ts +10 -8
- package/src/runtime/child-pi/child-pi-streams.ts +69 -19
- package/src/runtime/child-pi/child-pi.ts +29 -30
- package/src/runtime/child-pi/owned-process.ts +434 -0
- package/src/runtime/child-pi/postmortem-registry.ts +134 -0
- package/src/runtime/classifier/ambient-noise-gate.ts +126 -0
- package/src/runtime/classifier/classifier-service.ts +277 -61
- package/src/runtime/dispatch-batch.ts +32 -0
- package/src/runtime/live-session/live-agent-control.ts +2 -0
- package/src/runtime/live-session/live-agent-manager.ts +72 -2
- package/src/runtime/live-session/live-session-runtime.ts +435 -40
- package/src/runtime/merge-loop.ts +63 -35
- package/src/runtime/recovery/crash-recovery.ts +141 -3
- package/src/runtime/recovery/retry-executor.ts +29 -0
- package/src/runtime/run-tracker.ts +28 -0
- package/src/runtime/stale-reconciler.ts +36 -1
- package/src/runtime/task-runner.ts +103 -0
- package/src/runtime/verification/classifier-pre-gate.ts +262 -0
- package/src/runtime/verification/verification-gates.ts +5 -1
- package/src/runtime/verification/verify-gate.ts +394 -0
- package/src/schema/config-schema.ts +13 -0
- package/src/state/contracts.ts +18 -0
- package/src/state/coordination/mailbox.ts +203 -55
- package/src/state/stores/sqlite-run-state.ts +378 -0
- package/src/state/stores/state-store.ts +134 -6
- package/src/ui/agents-jobs-browser.ts +154 -8
- package/src/ui/events-state-source.ts +358 -0
- package/src/ui/run-snapshot-cache.ts +87 -3
- 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
|
-
|
|
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)
|
|
1188
|
-
else
|
|
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
|
-
|
|
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,
|