pi-crew 0.9.25 → 0.9.27
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 +87 -0
- package/NOTICE.md +14 -0
- package/assets/crew-vibes.ttf +0 -0
- package/assets/runner-spritesheet.png +0 -0
- package/dist/build-meta.json +255 -40
- package/dist/index.mjs +1564 -241
- package/dist/index.mjs.map +4 -4
- package/docs/perf/optimization-plan-2026-07-verified.md +396 -0
- package/package.json +10 -3
- package/scripts/bench-check.mjs +96 -0
- package/scripts/bench-cold-start.mjs +216 -0
- package/scripts/build-bundle.mjs +84 -0
- package/scripts/build-crew-vibes-font.py +328 -0
- package/scripts/check-all-skills.ts +294 -0
- package/scripts/check-bundle-staleness.mjs +97 -0
- package/scripts/check-conflict-markers.mjs +91 -0
- package/scripts/check-lazy-imports.mjs +28 -0
- package/scripts/install-crew-vibes-font.mjs +91 -0
- package/scripts/postinstall.mjs +47 -0
- package/scripts/profile-startup.mjs +125 -0
- package/scripts/release-smoke.mjs +74 -0
- package/scripts/run-bench.mjs +47 -0
- package/scripts/run-real-chain.ts +41 -0
- package/scripts/test-issue-29-crash.ts +111 -0
- package/scripts/test-issue-29-e2e.ts +183 -0
- package/scripts/test-issue-29-real-runtime.ts +330 -0
- package/scripts/test-issue-29-real-tasks.ts +387 -0
- package/scripts/test-issue-29-team-tool.ts +105 -0
- package/scripts/test-runner.mjs +74 -0
- package/scripts/verify-flicker-fix.ts +109 -0
- package/scripts/verify-skill.ts +550 -0
- package/scripts/watch-bundle.mjs +259 -0
- package/scripts/watchdog-harness.ts +119 -0
- package/src/agents/discover-agents.ts +6 -1
- package/src/config/defaults.ts +6 -0
- package/src/extension/crew-vibes/cat-frames.ts +18 -0
- package/src/extension/crew-vibes/config.ts +195 -0
- package/src/extension/crew-vibes/figures.ts +94 -0
- package/src/extension/crew-vibes/font-detect.ts +58 -0
- package/src/extension/crew-vibes/index.ts +396 -0
- package/src/extension/crew-vibes/provider-usage.ts +330 -0
- package/src/extension/crew-vibes/render.ts +206 -0
- package/src/extension/crew-vibes/speed.ts +286 -0
- package/src/extension/knowledge-injection.ts +12 -3
- package/src/extension/management.ts +23 -3
- package/src/extension/register.ts +7 -0
- package/src/runtime/child-pi.ts +117 -10
- package/src/runtime/crew-agent-records.ts +10 -3
- package/src/runtime/retry-executor.ts +4 -1
- package/src/runtime/task-runner/state-helpers.ts +9 -30
- package/src/runtime/team-runner.ts +5 -3
- package/src/state/atomic-write.ts +153 -49
- package/src/state/event-log.ts +28 -19
- package/src/state/locks.ts +7 -8
- package/src/state/mailbox.ts +15 -4
- package/src/state/state-store.ts +39 -10
- package/src/teams/discover-teams.ts +56 -1
- package/src/utils/safe-paths.ts +2 -1
- package/src/workflows/discover-workflows.ts +72 -1
|
@@ -114,12 +114,67 @@ function readTeamDir(dir: string, source: ResourceSource): TeamConfig[] {
|
|
|
114
114
|
.sort((a, b) => a.name.localeCompare(b.name));
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
+
// ─── Team Discovery Cache (F15) ──────────────────────────────────────────
|
|
118
|
+
// Mirrors the Workflow Discovery Cache (see workflows/discover-workflows.ts).
|
|
119
|
+
// `discoverTeams(cwd)` is called from recommend/scheduler paths and previously
|
|
120
|
+
// re-scanned 3 roots on every call. 5 s TTL + dir-stamp invalidation keeps
|
|
121
|
+
// changes (e.g. user edits a `.team.md`) visible within a few seconds without
|
|
122
|
+
// paying a full re-scan per call.
|
|
123
|
+
const TEAM_DISCOVERY_TTL_MS = 5000;
|
|
124
|
+
const TEAM_DISCOVERY_MAX_ENTRIES = 32;
|
|
125
|
+
interface CachedTeamEntry {
|
|
126
|
+
result: TeamDiscoveryResult;
|
|
127
|
+
expiresAt: number;
|
|
128
|
+
dirStamp: string;
|
|
129
|
+
}
|
|
130
|
+
const teamCache = new Map<string, CachedTeamEntry>();
|
|
131
|
+
|
|
132
|
+
function teamDirStamp(cwd: string): string {
|
|
133
|
+
const dirs = [path.join(packageRoot(), "teams"), path.join(userPiRoot(), "teams"), path.join(projectCrewRoot(cwd), "teams")];
|
|
134
|
+
let out = "";
|
|
135
|
+
for (const d of dirs) {
|
|
136
|
+
try {
|
|
137
|
+
out += `${fs.statSync(d).mtimeMs}|`;
|
|
138
|
+
} catch {
|
|
139
|
+
out += "0|";
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return out;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** Drop one or all cached entries. Called from management actions and tests. */
|
|
146
|
+
export function invalidateTeamDiscoveryCache(cwd?: string): void {
|
|
147
|
+
if (cwd) {
|
|
148
|
+
teamCache.delete(cwd);
|
|
149
|
+
} else {
|
|
150
|
+
teamCache.clear();
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
117
154
|
export function discoverTeams(cwd: string): TeamDiscoveryResult {
|
|
118
|
-
|
|
155
|
+
// F15: serve from cache when both TTL is fresh AND dir-stamp is unchanged.
|
|
156
|
+
const now = Date.now();
|
|
157
|
+
const stamp = teamDirStamp(cwd);
|
|
158
|
+
const cached = teamCache.get(cwd);
|
|
159
|
+
if (cached && cached.expiresAt > now && cached.dirStamp === stamp) {
|
|
160
|
+
return cached.result;
|
|
161
|
+
}
|
|
162
|
+
const result: TeamDiscoveryResult = {
|
|
119
163
|
builtin: readTeamDir(path.join(packageRoot(), "teams"), "builtin"),
|
|
120
164
|
user: readTeamDir(path.join(userPiRoot(), "teams"), "user"),
|
|
121
165
|
project: readTeamDir(path.join(projectCrewRoot(cwd), "teams"), "project"),
|
|
122
166
|
};
|
|
167
|
+
teamCache.set(cwd, {
|
|
168
|
+
result,
|
|
169
|
+
expiresAt: now + TEAM_DISCOVERY_TTL_MS,
|
|
170
|
+
dirStamp: stamp,
|
|
171
|
+
});
|
|
172
|
+
while (teamCache.size > TEAM_DISCOVERY_MAX_ENTRIES) {
|
|
173
|
+
const oldest = teamCache.keys().next().value;
|
|
174
|
+
if (oldest === undefined) break;
|
|
175
|
+
teamCache.delete(oldest);
|
|
176
|
+
}
|
|
177
|
+
return result;
|
|
123
178
|
}
|
|
124
179
|
|
|
125
180
|
export function allTeams(discovery: TeamDiscoveryResult | undefined): TeamConfig[] {
|
package/src/utils/safe-paths.ts
CHANGED
|
@@ -255,7 +255,8 @@ export function resolveRealContainedPath(baseDir: string, targetPath: string): s
|
|
|
255
255
|
if (process.platform === "darwin") {
|
|
256
256
|
const resolvedSymlink = resolvedAccumulated;
|
|
257
257
|
const knownDarwinSymlinks = ["/var", "/tmp", "/etc", "/private/var", "/private/tmp", "/private/etc"];
|
|
258
|
-
|
|
258
|
+
// Use prefix match to handle subdirectories like /var/folders/...
|
|
259
|
+
if (knownDarwinSymlinks.some((s) => resolvedSymlink === s || resolvedSymlink.startsWith(s + "/"))) continue;
|
|
259
260
|
}
|
|
260
261
|
throw new Error("Refusing to resolve: target path ancestor is a symlink: " + resolvedAccumulated);
|
|
261
262
|
}
|
|
@@ -198,15 +198,86 @@ function parseDynamicWorkflowFile(filePath: string, source: ResourceSource): Wor
|
|
|
198
198
|
}
|
|
199
199
|
}
|
|
200
200
|
|
|
201
|
+
// ─── Workflow Discovery Cache (F17) ──────────────────────────────────────
|
|
202
|
+
// Mirrors the Agent Discovery Cache pattern (see discover-agents.ts:493-556).
|
|
203
|
+
// `discoverWorkflows(cwd)` is called on the powerbar hot path (≤200 ms coalesce =
|
|
204
|
+
// up to ~5 Hz when a run is active + emitting events). Without caching, each call
|
|
205
|
+
// walks 3 roots, each requiring readdirSync × 2 + readFileSync + regex-parse per
|
|
206
|
+
// `.workflow.md`. We use TTL + dir-stamp invalidation so the cache survives
|
|
207
|
+
// steady-state rendering yet still picks up file changes within a few seconds.
|
|
208
|
+
//
|
|
209
|
+
// SECURITY: ResourceIdentity preserved — same precedence + same parsers as before.
|
|
210
|
+
// Reviewers: there is no path-component concern; this only memoises a pure function
|
|
211
|
+
// of the filesystem state into a process-local Map.
|
|
212
|
+
const WORKFLOW_DISCOVERY_TTL_MS = 5000;
|
|
213
|
+
const WORKFLOW_DISCOVERY_MAX_ENTRIES = 32;
|
|
214
|
+
interface CachedWorkflowEntry {
|
|
215
|
+
result: WorkflowDiscoveryResult;
|
|
216
|
+
expiresAt: number;
|
|
217
|
+
/** mtime tuple of the 3 workflow source dirs; change ⇒ invalidate early. */
|
|
218
|
+
dirStamp: string;
|
|
219
|
+
}
|
|
220
|
+
const workflowCache = new Map<string, CachedWorkflowEntry>();
|
|
221
|
+
|
|
222
|
+
/** Compact mtime signature for the 3 workflow source dirs. Cheap (3 statSync). */
|
|
223
|
+
function workflowDirStamp(cwd: string): string {
|
|
224
|
+
const dirs = [
|
|
225
|
+
path.join(packageRoot(), "workflows"),
|
|
226
|
+
path.join(userPiRoot(), "workflows"),
|
|
227
|
+
path.join(projectCrewRoot(cwd), "workflows"),
|
|
228
|
+
];
|
|
229
|
+
let out = "";
|
|
230
|
+
for (const d of dirs) {
|
|
231
|
+
try {
|
|
232
|
+
const st = fs.statSync(d);
|
|
233
|
+
out += `${st.mtimeMs}|`;
|
|
234
|
+
} catch {
|
|
235
|
+
out += "0|";
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return out;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/** Drop one or all cached entries. Called from management actions and tests. */
|
|
242
|
+
export function invalidateWorkflowDiscoveryCache(cwd?: string): void {
|
|
243
|
+
if (cwd) {
|
|
244
|
+
workflowCache.delete(cwd);
|
|
245
|
+
} else {
|
|
246
|
+
workflowCache.clear();
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
201
250
|
export function discoverWorkflows(cwd: string): WorkflowDiscoveryResult {
|
|
202
251
|
if (!cwd || typeof cwd !== "string") {
|
|
203
252
|
return { builtin: [], user: [], project: [] };
|
|
204
253
|
}
|
|
205
|
-
|
|
254
|
+
// F17: serve from cache when both TTL is fresh AND dir-stamp is unchanged.
|
|
255
|
+
// `dirStamp` is a 3-stat pulse we run on every cached call — it is much
|
|
256
|
+
// cheaper than the disk scan it protects against (3 statSync vs 2 readdirSync
|
|
257
|
+
// + N readFileSync + regex parse per `.workflow.md`).
|
|
258
|
+
const now = Date.now();
|
|
259
|
+
const stamp = workflowDirStamp(cwd);
|
|
260
|
+
const cached = workflowCache.get(cwd);
|
|
261
|
+
if (cached && cached.expiresAt > now && cached.dirStamp === stamp) {
|
|
262
|
+
return cached.result;
|
|
263
|
+
}
|
|
264
|
+
const result: WorkflowDiscoveryResult = {
|
|
206
265
|
builtin: readWorkflowDir(path.join(packageRoot(), "workflows"), "builtin"),
|
|
207
266
|
user: readWorkflowDir(path.join(userPiRoot(), "workflows"), "user"),
|
|
208
267
|
project: readWorkflowDir(path.join(projectCrewRoot(cwd), "workflows"), "project"),
|
|
209
268
|
};
|
|
269
|
+
workflowCache.set(cwd, {
|
|
270
|
+
result,
|
|
271
|
+
expiresAt: now + WORKFLOW_DISCOVERY_TTL_MS,
|
|
272
|
+
dirStamp: stamp,
|
|
273
|
+
});
|
|
274
|
+
// Bounded LRU-ish eviction by insertion order (Map iteration order).
|
|
275
|
+
while (workflowCache.size > WORKFLOW_DISCOVERY_MAX_ENTRIES) {
|
|
276
|
+
const oldest = workflowCache.keys().next().value;
|
|
277
|
+
if (oldest === undefined) break;
|
|
278
|
+
workflowCache.delete(oldest);
|
|
279
|
+
}
|
|
280
|
+
return result;
|
|
210
281
|
}
|
|
211
282
|
|
|
212
283
|
export function allWorkflows(discovery: WorkflowDiscoveryResult | undefined): WorkflowConfig[] {
|