pi-crew 0.8.6 → 0.8.8
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/package.json
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { listRecentRuns } from "../run-index.ts";
|
|
3
|
+
import { findRepoRoot } from "../../utils/paths.ts";
|
|
3
4
|
import { extractSessionId } from "../../utils/session-utils.ts";
|
|
4
5
|
import type { ArtifactDescriptor, TeamRunManifest } from "../../state/types.ts";
|
|
5
6
|
|
|
@@ -69,6 +70,29 @@ function formatCrewArtifactIndex(entries: CrewArtifactIndexEntry[]): string {
|
|
|
69
70
|
return lines.join("\n");
|
|
70
71
|
}
|
|
71
72
|
|
|
73
|
+
/**
|
|
74
|
+
* Project-scope filter: keep `run` only if it belongs to the SAME repo as
|
|
75
|
+
* `queryCwd` (or is a user-level / legacy run with no repo). This is the
|
|
76
|
+
* version-independent, reliable barrier against cross-project leaks: even
|
|
77
|
+
* when the session-id filter below cannot fire (ctx.sessionId is absent on
|
|
78
|
+
* some pi versions — observed on pi 0.79.6 ExtensionContext), the cwd filter
|
|
79
|
+
* stops another project's in-flight runs (e.g. edge-ai-agent) from bleeding
|
|
80
|
+
* into this project's ambient status or compaction-resume directive.
|
|
81
|
+
*
|
|
82
|
+
* Note: listRecentRuns already scopes its filesystem scan via scopedRunRoots,
|
|
83
|
+
* BUT it ALSO merges the GLOBAL activeRunEntries() registry (the cross-
|
|
84
|
+
* project dashboard view). That global merge is intentional for the
|
|
85
|
+
* dashboard but wrong for "what should THIS project's session do" — hence
|
|
86
|
+
* this filter at the consumption site.
|
|
87
|
+
*/
|
|
88
|
+
function isInProjectScope(run: TeamRunManifest, queryCwd: string): boolean {
|
|
89
|
+
const queryRepo = findRepoRoot(queryCwd);
|
|
90
|
+
if (queryRepo === undefined) return true; // viewer not in a repo → user-level view
|
|
91
|
+
const runRepo = typeof run.cwd === "string" && run.cwd.length > 0 ? findRepoRoot(run.cwd) : undefined;
|
|
92
|
+
if (runRepo === undefined) return true; // run is user-level / legacy / not a repo → include
|
|
93
|
+
return runRepo === queryRepo; // same project only
|
|
94
|
+
}
|
|
95
|
+
|
|
72
96
|
/**
|
|
73
97
|
* Collect in-flight (non-terminal) crew runs that must be resumable after
|
|
74
98
|
* compaction. These are runs the agent was actively working on or awaiting.
|
|
@@ -88,7 +112,13 @@ function formatCrewArtifactIndex(entries: CrewArtifactIndexEntry[]): string {
|
|
|
88
112
|
export function collectInFlightRuns(cwd: string, currentSessionId?: string): TeamRunManifest[] {
|
|
89
113
|
return listRecentRuns(cwd, MAX_ARTIFACT_INDEX_RUNS).filter((run) => {
|
|
90
114
|
if (!IN_FLIGHT_RUN_STATUSES.has(run.status)) return false;
|
|
91
|
-
|
|
115
|
+
// Reliable barrier (2026-06-17): never leak another project's runs into
|
|
116
|
+
// THIS project's resume directive / ambient status, regardless of
|
|
117
|
+
// whether the session-id filter is available. This fixes the live
|
|
118
|
+
// cross-session leak that persisted after 4bd6f5b because ctx.sessionId
|
|
119
|
+
// is absent on pi 0.79.6.
|
|
120
|
+
if (!isInProjectScope(run, cwd)) return false;
|
|
121
|
+
if (currentSessionId === undefined) return true; // no session filter → back-compat (still project-scoped)
|
|
92
122
|
return run.ownerSessionId === currentSessionId; // strict: legacy ownerless runs excluded
|
|
93
123
|
});
|
|
94
124
|
}
|
|
@@ -76,7 +76,7 @@ function scopedRunRoots(cwd: string): string[] {
|
|
|
76
76
|
return [...roots];
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
function collectActiveRuns(): TeamRunManifest[] {
|
|
79
|
+
function collectActiveRuns(cwd?: string): TeamRunManifest[] {
|
|
80
80
|
return activeRunEntries()
|
|
81
81
|
.map((entry) => readManifest(entry.manifestPath))
|
|
82
82
|
.filter((manifest): manifest is TeamRunManifest => manifest !== undefined);
|
|
@@ -9,6 +9,7 @@ import { projectCrewRoot, userCrewRoot } from "../../utils/paths.ts";
|
|
|
9
9
|
import { DEFAULT_PATHS } from "../../config/defaults.ts";
|
|
10
10
|
import type { TeamToolParamsValue } from "../../schema/team-tool-schema.ts";
|
|
11
11
|
import { getPiSpawnCommand } from "../../runtime/pi-spawn.ts";
|
|
12
|
+
import { getRuntimeWarmupStatus } from "../../runtime/runtime-warmup.ts";
|
|
12
13
|
import { validateResources } from "../validate-resources.ts";
|
|
13
14
|
import { detectDrift, formatDriftReport, type DriftReport } from "../../config/drift-detector.ts";
|
|
14
15
|
import { TeamToolParams } from "../../schema/team-tool-schema.ts";
|
|
@@ -188,6 +189,37 @@ export function buildTeamDoctorReport(input: TeamDoctorReportInput): TeamDoctorR
|
|
|
188
189
|
{ label: "leader repository", ok: true, detail: input.cwd },
|
|
189
190
|
{ label: "cleanup policy", ok: true, detail: "dirty worktrees preserved unless force is set" },
|
|
190
191
|
]),
|
|
192
|
+
section("Runtime warmup (cold-start fix v0.8.6)", () => {
|
|
193
|
+
// Surface whether the general cold-start-race fix is active + how long
|
|
194
|
+
// the graph warmup took, so a session can confirm the fix loaded
|
|
195
|
+
// (post-restart) and isn't pathologically slow. An UNWARMED graph is
|
|
196
|
+
// the documented cause of `Cannot read properties of undefined
|
|
197
|
+
// (reading '<binding>')` under concurrent subagent spawn.
|
|
198
|
+
//
|
|
199
|
+
// "Not started" is NOT a doctor error: it is the normal state in unit
|
|
200
|
+
// tests and in any caller that invokes buildTeamDoctorReport directly
|
|
201
|
+
// without going through registerPiTeams. Only a STARTED-but-FAILED
|
|
202
|
+
// warmup is an error (something genuinely went wrong during pre-warm).
|
|
203
|
+
const status = getRuntimeWarmupStatus();
|
|
204
|
+
const checks: DoctorCheck[] = [
|
|
205
|
+
{
|
|
206
|
+
label: "warmup started",
|
|
207
|
+
ok: true, // informational — "not started" is not a failure
|
|
208
|
+
detail: status.started ? "module graph pre-warmed at registration" : "not started in this process (normal for direct unit-test calls; in a live Pi session, started at extension load)",
|
|
209
|
+
},
|
|
210
|
+
];
|
|
211
|
+
if (status.started) {
|
|
212
|
+
checks.push({
|
|
213
|
+
label: "warmup completed",
|
|
214
|
+
ok: status.completed,
|
|
215
|
+
detail: status.completed ? (status.durationMs !== undefined ? `graph warm in ${status.durationMs}ms` : "completed") : "in progress",
|
|
216
|
+
});
|
|
217
|
+
if (status.error) {
|
|
218
|
+
checks.push({ label: "warmup error", ok: false, detail: status.error });
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return checks;
|
|
222
|
+
}),
|
|
191
223
|
];
|
|
192
224
|
if (input.smokeChildPi) {
|
|
193
225
|
sections.push([`Child check`, `- ${input.smokeChildPi.ok ? "OK" : "FAIL"} child Pi smoke: ${input.smokeChildPi.detail}`]);
|
|
@@ -61,6 +61,9 @@ const HOT_PEER_DEPS = ["@earendil-works/pi-coding-agent"] as const;
|
|
|
61
61
|
|
|
62
62
|
let warmupPromise: Promise<void> | undefined;
|
|
63
63
|
let warmupStarted = false;
|
|
64
|
+
let warmupCompleted = false;
|
|
65
|
+
let warmupDurationMs: number | undefined;
|
|
66
|
+
let warmupError: string | undefined;
|
|
64
67
|
|
|
65
68
|
/**
|
|
66
69
|
* Start the runtime warmup (idempotent). Fires eager `import()` of the hot
|
|
@@ -74,6 +77,7 @@ let warmupStarted = false;
|
|
|
74
77
|
export function startRuntimeWarmup(): void {
|
|
75
78
|
if (warmupStarted) return;
|
|
76
79
|
warmupStarted = true;
|
|
80
|
+
const startedAt = Date.now();
|
|
77
81
|
warmupPromise = (async (): Promise<void> => {
|
|
78
82
|
const imports: Array<Promise<unknown>> = [];
|
|
79
83
|
for (const spec of HOT_MODULE_SPECIFIERS) {
|
|
@@ -91,9 +95,15 @@ export function startRuntimeWarmup(): void {
|
|
|
91
95
|
);
|
|
92
96
|
}
|
|
93
97
|
await Promise.all(imports);
|
|
94
|
-
})()
|
|
95
|
-
|
|
96
|
-
|
|
98
|
+
})()
|
|
99
|
+
.then(() => {
|
|
100
|
+
warmupCompleted = true;
|
|
101
|
+
warmupDurationMs = Date.now() - startedAt;
|
|
102
|
+
})
|
|
103
|
+
.catch((err: unknown) => {
|
|
104
|
+
// final safety net — warmup must never reject. Record for diagnostics.
|
|
105
|
+
warmupError = err instanceof Error ? err.message : String(err ?? "unknown");
|
|
106
|
+
});
|
|
97
107
|
}
|
|
98
108
|
|
|
99
109
|
/**
|
|
@@ -113,9 +123,34 @@ export async function awaitRuntimeWarmup(): Promise<void> {
|
|
|
113
123
|
export function resetRuntimeWarmupForTest(): void {
|
|
114
124
|
warmupPromise = undefined;
|
|
115
125
|
warmupStarted = false;
|
|
126
|
+
warmupCompleted = false;
|
|
127
|
+
warmupDurationMs = undefined;
|
|
128
|
+
warmupError = undefined;
|
|
116
129
|
}
|
|
117
130
|
|
|
118
131
|
/** Test seam: has startRuntimeWarmup() been called? */
|
|
119
132
|
export function isRuntimeWarmupStarted(): boolean {
|
|
120
133
|
return warmupStarted;
|
|
121
134
|
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Diagnostic snapshot of warmup state for `team doctor`. Surfaces whether the
|
|
138
|
+
* v0.8.6 cold-start fix is active and how long the graph warmup took, so a
|
|
139
|
+
* session can confirm the fix loaded (post-restart) and isn't pathologically
|
|
140
|
+
* slow.
|
|
141
|
+
*/
|
|
142
|
+
export interface RuntimeWarmupStatus {
|
|
143
|
+
started: boolean;
|
|
144
|
+
completed: boolean;
|
|
145
|
+
durationMs: number | undefined;
|
|
146
|
+
error: string | undefined;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function getRuntimeWarmupStatus(): RuntimeWarmupStatus {
|
|
150
|
+
return {
|
|
151
|
+
started: warmupStarted,
|
|
152
|
+
completed: warmupCompleted,
|
|
153
|
+
durationMs: warmupDurationMs,
|
|
154
|
+
error: warmupError,
|
|
155
|
+
};
|
|
156
|
+
}
|