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.
Files changed (59) hide show
  1. package/CHANGELOG.md +87 -0
  2. package/NOTICE.md +14 -0
  3. package/assets/crew-vibes.ttf +0 -0
  4. package/assets/runner-spritesheet.png +0 -0
  5. package/dist/build-meta.json +255 -40
  6. package/dist/index.mjs +1564 -241
  7. package/dist/index.mjs.map +4 -4
  8. package/docs/perf/optimization-plan-2026-07-verified.md +396 -0
  9. package/package.json +10 -3
  10. package/scripts/bench-check.mjs +96 -0
  11. package/scripts/bench-cold-start.mjs +216 -0
  12. package/scripts/build-bundle.mjs +84 -0
  13. package/scripts/build-crew-vibes-font.py +328 -0
  14. package/scripts/check-all-skills.ts +294 -0
  15. package/scripts/check-bundle-staleness.mjs +97 -0
  16. package/scripts/check-conflict-markers.mjs +91 -0
  17. package/scripts/check-lazy-imports.mjs +28 -0
  18. package/scripts/install-crew-vibes-font.mjs +91 -0
  19. package/scripts/postinstall.mjs +47 -0
  20. package/scripts/profile-startup.mjs +125 -0
  21. package/scripts/release-smoke.mjs +74 -0
  22. package/scripts/run-bench.mjs +47 -0
  23. package/scripts/run-real-chain.ts +41 -0
  24. package/scripts/test-issue-29-crash.ts +111 -0
  25. package/scripts/test-issue-29-e2e.ts +183 -0
  26. package/scripts/test-issue-29-real-runtime.ts +330 -0
  27. package/scripts/test-issue-29-real-tasks.ts +387 -0
  28. package/scripts/test-issue-29-team-tool.ts +105 -0
  29. package/scripts/test-runner.mjs +74 -0
  30. package/scripts/verify-flicker-fix.ts +109 -0
  31. package/scripts/verify-skill.ts +550 -0
  32. package/scripts/watch-bundle.mjs +259 -0
  33. package/scripts/watchdog-harness.ts +119 -0
  34. package/src/agents/discover-agents.ts +6 -1
  35. package/src/config/defaults.ts +6 -0
  36. package/src/extension/crew-vibes/cat-frames.ts +18 -0
  37. package/src/extension/crew-vibes/config.ts +195 -0
  38. package/src/extension/crew-vibes/figures.ts +94 -0
  39. package/src/extension/crew-vibes/font-detect.ts +58 -0
  40. package/src/extension/crew-vibes/index.ts +396 -0
  41. package/src/extension/crew-vibes/provider-usage.ts +330 -0
  42. package/src/extension/crew-vibes/render.ts +206 -0
  43. package/src/extension/crew-vibes/speed.ts +286 -0
  44. package/src/extension/knowledge-injection.ts +12 -3
  45. package/src/extension/management.ts +23 -3
  46. package/src/extension/register.ts +7 -0
  47. package/src/runtime/child-pi.ts +117 -10
  48. package/src/runtime/crew-agent-records.ts +10 -3
  49. package/src/runtime/retry-executor.ts +4 -1
  50. package/src/runtime/task-runner/state-helpers.ts +9 -30
  51. package/src/runtime/team-runner.ts +5 -3
  52. package/src/state/atomic-write.ts +153 -49
  53. package/src/state/event-log.ts +28 -19
  54. package/src/state/locks.ts +7 -8
  55. package/src/state/mailbox.ts +15 -4
  56. package/src/state/state-store.ts +39 -10
  57. package/src/teams/discover-teams.ts +56 -1
  58. package/src/utils/safe-paths.ts +2 -1
  59. package/src/workflows/discover-workflows.ts +72 -1
package/dist/index.mjs CHANGED
@@ -8693,6 +8693,7 @@ __export(atomic_write_exports, {
8693
8693
  atomicWriteJsonAsync: () => atomicWriteJsonAsync,
8694
8694
  atomicWriteJsonCoalesced: () => atomicWriteJsonCoalesced,
8695
8695
  flushPendingAtomicWrites: () => flushPendingAtomicWrites,
8696
+ invalidateSymlinkSafeCache: () => invalidateSymlinkSafeCache,
8696
8697
  isSymlinkSafePath: () => isSymlinkSafePath,
8697
8698
  readJsonFile: () => readJsonFile,
8698
8699
  renameWithRetry: () => renameWithRetry,
@@ -8774,6 +8775,32 @@ function isSymlinkSafePath(filePath) {
8774
8775
  return false;
8775
8776
  }
8776
8777
  }
8778
+ function invalidateSymlinkSafeCache(dir) {
8779
+ if (dir) symlinkSafeCache.delete(dir);
8780
+ else symlinkSafeCache.clear();
8781
+ }
8782
+ function isTargetNotSymlink(filePath) {
8783
+ try {
8784
+ if (fs2.lstatSync(filePath).isSymbolicLink()) return false;
8785
+ } catch {
8786
+ }
8787
+ return true;
8788
+ }
8789
+ function isSymlinkSafeDirCached(filePath) {
8790
+ const now = Date.now();
8791
+ const dir = path2.dirname(filePath);
8792
+ if (!isTargetNotSymlink(filePath)) return false;
8793
+ const hit = symlinkSafeCache.get(dir);
8794
+ if (hit && now - hit.at < SYMLINK_SAFE_TTL_MS) return hit.safe;
8795
+ const verdict = isSymlinkSafePath(path2.join(dir, ".symlink-safe-check"));
8796
+ symlinkSafeCache.set(dir, { safe: verdict, at: now });
8797
+ while (symlinkSafeCache.size > SYMLINK_SAFE_MAX_ENTRIES) {
8798
+ const oldest = symlinkSafeCache.keys().next().value;
8799
+ if (oldest === void 0) break;
8800
+ symlinkSafeCache.delete(oldest);
8801
+ }
8802
+ return verdict;
8803
+ }
8777
8804
  function sleep2(ms) {
8778
8805
  return new Promise((resolve21) => setTimeout(resolve21, ms));
8779
8806
  }
@@ -8889,8 +8916,19 @@ async function renameWithRetryAsync(tempPath, filePath, retries = 8, rename = (s
8889
8916
  }
8890
8917
  throw lastError;
8891
8918
  }
8892
- function atomicWriteFile(filePath, content, expectedHash) {
8893
- if (!isSymlinkSafePath(filePath)) throw new Error(`Refusing to write: target is a symlink or inside untrusted directory: ${filePath}`);
8919
+ function normalizeOptions(arg) {
8920
+ if (typeof arg === "string") return { expectedHash: arg, durability: "full" };
8921
+ if (arg && typeof arg === "object") {
8922
+ const o = arg;
8923
+ const durability = o.durability === "best-effort" ? "best-effort" : "full";
8924
+ return { expectedHash: o.expectedHash, durability };
8925
+ }
8926
+ return { durability: "full" };
8927
+ }
8928
+ function atomicWriteFile(filePath, content, options) {
8929
+ const { durability } = normalizeOptions(options);
8930
+ if (!isSymlinkSafeDirCached(filePath))
8931
+ throw new Error(`Refusing to write: target is a symlink or inside untrusted directory: ${filePath}`);
8894
8932
  const canonicalize = (p) => {
8895
8933
  try {
8896
8934
  const r = fs2.realpathSync.native(p);
@@ -8925,14 +8963,14 @@ function atomicWriteFile(filePath, content, expectedHash) {
8925
8963
  throw new Error(`Refusing to write: opened path is not a regular file: ${tempPath}`);
8926
8964
  }
8927
8965
  fs2.writeSync(fd, content, void 0, "utf-8");
8928
- fs2.fsyncSync(fd);
8966
+ if (durability === "full") fs2.fsyncSync(fd);
8929
8967
  fs2.closeSync(fd);
8930
8968
  try {
8931
- if (!isSymlinkSafePath(filePath)) {
8969
+ if (!isSymlinkSafeDirCached(filePath)) {
8932
8970
  throw new Error(`Refusing to rename: target became a symlink or inside untrusted directory: ${filePath}`);
8933
8971
  }
8934
8972
  renameWithLinkSync(tempPath, filePath);
8935
- if (process.platform !== "win32") {
8973
+ if (durability === "full" && process.platform !== "win32") {
8936
8974
  try {
8937
8975
  const dirFd = fs2.openSync(path2.dirname(filePath), "r");
8938
8976
  fs2.fsyncSync(dirFd);
@@ -8963,11 +9001,13 @@ function atomicWriteFile(filePath, content, expectedHash) {
8963
9001
  }
8964
9002
  }
8965
9003
  }
8966
- async function atomicWriteFileAsync(filePath, content) {
9004
+ async function atomicWriteFileAsync(filePath, content, options) {
9005
+ const { durability } = normalizeOptions(options);
8967
9006
  if (isWorkerAtomicWriterEnabled()) {
8968
9007
  return atomicWriteFileViaWorker(filePath, content);
8969
9008
  }
8970
- if (!isSymlinkSafePath(filePath)) throw new Error(`Refusing to write: target is a symlink or inside untrusted directory: ${filePath}`);
9009
+ if (!isSymlinkSafeDirCached(filePath))
9010
+ throw new Error(`Refusing to write: target is a symlink or inside untrusted directory: ${filePath}`);
8971
9011
  await fs2.promises.mkdir(path2.dirname(filePath), { recursive: true });
8972
9012
  const tempPath = `${filePath}.${crypto.randomUUID()}.tmp`;
8973
9013
  try {
@@ -8979,33 +9019,12 @@ async function atomicWriteFileAsync(filePath, content) {
8979
9019
  throw new Error(`Refusing to write: opened path is not a regular file: ${tempPath}`);
8980
9020
  }
8981
9021
  await fd.writeFile(content, "utf-8");
9022
+ if (durability === "full") await fd.sync();
8982
9023
  await fd.close();
8983
- try {
8984
- if (!isSymlinkSafePath(filePath)) {
8985
- try {
8986
- await fs2.promises.rm(tempPath, { force: true });
8987
- } catch {
8988
- }
8989
- throw new Error(`Refusing to rename: target became a symlink or inside untrusted directory: ${filePath}`);
8990
- }
8991
- await renameWithLinkAsync(tempPath, filePath);
8992
- } catch (renameError) {
8993
- let matches = false;
8994
- try {
8995
- const existing = await fs2.promises.readFile(filePath, "utf-8");
8996
- matches = existing === content;
8997
- } catch {
8998
- }
8999
- if (matches) {
9000
- try {
9001
- await fs2.promises.rm(tempPath, { force: true });
9002
- } catch (cleanupError) {
9003
- logInternalError("atomic-write.cleanupAsync", cleanupError, `tempPath=${tempPath}`);
9004
- }
9005
- return;
9006
- }
9007
- throw renameError;
9024
+ if (!isSymlinkSafeDirCached(filePath)) {
9025
+ throw new Error(`Refusing to rename: target became a symlink or inside untrusted directory: ${filePath}`);
9008
9026
  }
9027
+ await renameWithLinkAsync(tempPath, filePath);
9009
9028
  } catch (error) {
9010
9029
  try {
9011
9030
  await fs2.promises.rm(tempPath, { force: true });
@@ -9015,15 +9034,15 @@ async function atomicWriteFileAsync(filePath, content) {
9015
9034
  throw error;
9016
9035
  }
9017
9036
  }
9018
- function atomicWriteJson(filePath, value) {
9037
+ function atomicWriteJson(filePath, value, options) {
9019
9038
  atomicWriteFile(filePath, `${JSON.stringify(value, null, 2)}
9020
- `);
9039
+ `, options);
9021
9040
  }
9022
- async function atomicWriteJsonAsync(filePath, value) {
9041
+ async function atomicWriteJsonAsync(filePath, value, options) {
9023
9042
  await atomicWriteFileAsync(filePath, `${JSON.stringify(value, null, 2)}
9024
- `);
9043
+ `, options);
9025
9044
  }
9026
- function atomicWriteJsonCoalesced(filePath, value, coalesceMs = DEFAULT_ATOMIC_COALESCE_MS) {
9045
+ function atomicWriteJsonCoalesced(filePath, value, coalesceMs = DEFAULT_ATOMIC_COALESCE_MS, options) {
9027
9046
  const content = `${JSON.stringify(value, null, 2)}
9028
9047
  `;
9029
9048
  const previous = pendingAtomicWrites.get(filePath);
@@ -9031,12 +9050,14 @@ function atomicWriteJsonCoalesced(filePath, value, coalesceMs = DEFAULT_ATOMIC_C
9031
9050
  const timer = setTimeout(() => flushOnePendingAtomicWrite(filePath), coalesceMs);
9032
9051
  timer.unref();
9033
9052
  const generation = ++writeGeneration;
9053
+ const normalized = normalizeOptions(options);
9034
9054
  pendingAtomicWrites.set(filePath, {
9035
9055
  content,
9036
9056
  timer,
9037
9057
  coalesceMs,
9038
9058
  retryCount: 0,
9039
- generation
9059
+ generation,
9060
+ durability: normalized.durability
9040
9061
  });
9041
9062
  }
9042
9063
  function flushOnePendingAtomicWrite(filePath) {
@@ -9045,7 +9066,7 @@ function flushOnePendingAtomicWrite(filePath) {
9045
9066
  const savedGeneration = entry.generation;
9046
9067
  clearTimeout(entry.timer);
9047
9068
  try {
9048
- atomicWriteFile(filePath, entry.content);
9069
+ atomicWriteFile(filePath, entry.content, { durability: entry.durability });
9049
9070
  if (pendingAtomicWrites.get(filePath)?.generation === savedGeneration) {
9050
9071
  pendingAtomicWrites.delete(filePath);
9051
9072
  }
@@ -9090,7 +9111,7 @@ function readJsonFile(filePath) {
9090
9111
  }
9091
9112
  }
9092
9113
  }
9093
- var RETRYABLE_RENAME_CODES, RETRYABLE_LINK_CODES, __test__renameWithRetry, __test__renameWithRetryAsync, MAX_FLUSH_RETRIES, pendingAtomicWrites, DEFAULT_ATOMIC_COALESCE_MS, writeGeneration, flushInProgress;
9114
+ var RETRYABLE_RENAME_CODES, RETRYABLE_LINK_CODES, SYMLINK_SAFE_TTL_MS, SYMLINK_SAFE_MAX_ENTRIES, symlinkSafeCache, __test__renameWithRetry, __test__renameWithRetryAsync, MAX_FLUSH_RETRIES, pendingAtomicWrites, DEFAULT_ATOMIC_COALESCE_MS, writeGeneration, flushInProgress;
9094
9115
  var init_atomic_write = __esm({
9095
9116
  "src/state/atomic-write.ts"() {
9096
9117
  "use strict";
@@ -9099,6 +9120,9 @@ var init_atomic_write = __esm({
9099
9120
  init_worker_atomic_writer();
9100
9121
  RETRYABLE_RENAME_CODES = /* @__PURE__ */ new Set(["EPERM", "EBUSY", "EACCES"]);
9101
9122
  RETRYABLE_LINK_CODES = /* @__PURE__ */ new Set(["EPERM", "EBUSY", "EACCES", "ENOENT"]);
9123
+ SYMLINK_SAFE_TTL_MS = 3e4;
9124
+ SYMLINK_SAFE_MAX_ENTRIES = 128;
9125
+ symlinkSafeCache = /* @__PURE__ */ new Map();
9102
9126
  __test__renameWithRetry = renameWithRetry;
9103
9127
  __test__renameWithRetryAsync = renameWithRetryAsync;
9104
9128
  MAX_FLUSH_RETRIES = 5;
@@ -9120,6 +9144,7 @@ var init_defaults = __esm({
9120
9144
  DEFAULT_CHILD_PI = {
9121
9145
  postExitStdioGuardMs: 3e3,
9122
9146
  finalDrainMs: 5e3,
9147
+ finalDrainQuietMs: 800,
9123
9148
  hardKillMs: 3e3,
9124
9149
  // Child workers can spend more than a few seconds in provider calls or long-running tools without emitting stdout.
9125
9150
  // Keep this as a coarse stuck-worker guard rather than a short per-message latency budget.
@@ -9325,14 +9350,10 @@ function readLockToken(filePath) {
9325
9350
  }
9326
9351
  }
9327
9352
  function timingSafeTokenMatch(a, b) {
9328
- const bufA = Buffer.from(String(a));
9329
- const bufB = Buffer.from(String(b));
9330
- const len = Math.max(bufA.length, bufB.length);
9331
- const safeA = Buffer.alloc(len);
9332
- const safeB = Buffer.alloc(len);
9333
- bufA.copy(safeA);
9334
- bufB.copy(safeB);
9335
- return timingSafeEqual(safeA, safeB);
9353
+ if (a.length !== b.length) return false;
9354
+ const bufA = Buffer.from(a);
9355
+ const bufB = Buffer.from(b);
9356
+ return timingSafeEqual(bufA, bufB);
9336
9357
  }
9337
9358
  function releaseLock(filePath, token) {
9338
9359
  let isSymlink2 = false;
@@ -11703,9 +11724,6 @@ function resetEventLogMode() {
11703
11724
  asyncQueues.clear();
11704
11725
  }
11705
11726
  async function appendEventAsync(eventsPath, event) {
11706
- if (!TERMINAL_EVENT_TYPES.has(event.type)) {
11707
- return appendEventBuffered(eventsPath, event);
11708
- }
11709
11727
  const queueKey = eventsPath;
11710
11728
  const prev = asyncQueues.get(queueKey) ?? Promise.resolve();
11711
11729
  const next = prev.then(async () => {
@@ -12014,12 +12032,14 @@ function appendEventInsideLock(eventsPath, event) {
12014
12032
  if (!skippedDueToSize) {
12015
12033
  fs8.appendFileSync(eventsPath, `${JSON.stringify(redactSecrets(fullEvent))}
12016
12034
  `, "utf-8");
12017
- const fd = fs8.openSync(eventsPath, "r+");
12018
- try {
12019
- fs8.fsyncSync(fd);
12020
- } catch {
12021
- } finally {
12022
- fs8.closeSync(fd);
12035
+ if (isTerminal) {
12036
+ const fd = fs8.openSync(eventsPath, "r+");
12037
+ try {
12038
+ fs8.fsyncSync(fd);
12039
+ } catch {
12040
+ } finally {
12041
+ fs8.closeSync(fd);
12042
+ }
12023
12043
  }
12024
12044
  persistSequence(eventsPath, seq);
12025
12045
  try {
@@ -12212,6 +12232,20 @@ var init_event_log = __esm({
12212
12232
  }
12213
12233
  asyncQueues.clear();
12214
12234
  });
12235
+ process.on("beforeExit", async () => {
12236
+ if (bufferedQueues.size > 0) {
12237
+ try {
12238
+ await flushEventLogBuffer();
12239
+ } catch {
12240
+ }
12241
+ }
12242
+ if (asyncQueues.size > 0) {
12243
+ try {
12244
+ await drainAsyncQueues();
12245
+ } catch {
12246
+ }
12247
+ }
12248
+ });
12215
12249
  process.on("SIGTERM", () => setImmediate(() => flushEventLogBuffer()));
12216
12250
  process.on("SIGINT", () => setImmediate(() => flushEventLogBuffer()));
12217
12251
  process.on("uncaughtException", (error) => {
@@ -12514,7 +12548,7 @@ function resolveRealContainedPath(baseDir, targetPath2) {
12514
12548
  if (process.platform === "darwin") {
12515
12549
  const resolvedSymlink = resolvedAccumulated;
12516
12550
  const knownDarwinSymlinks = ["/var", "/tmp", "/etc", "/private/var", "/private/tmp", "/private/etc"];
12517
- if (knownDarwinSymlinks.includes(resolvedSymlink)) continue;
12551
+ if (knownDarwinSymlinks.some((s) => resolvedSymlink === s || resolvedSymlink.startsWith(s + "/"))) continue;
12518
12552
  }
12519
12553
  throw new Error("Refusing to resolve: target path ancestor is a symlink: " + resolvedAccumulated);
12520
12554
  }
@@ -13155,6 +13189,9 @@ function statManifestWithWindowsRetry(manifestPath) {
13155
13189
  }
13156
13190
  return fs12.statSync(manifestPath);
13157
13191
  }
13192
+ function genOf(stateRoot) {
13193
+ return manifestCacheGeneration.get(stateRoot) ?? 0;
13194
+ }
13158
13195
  function __test__setManifestCache(stateRoot, entry) {
13159
13196
  setManifestCache(stateRoot, entry);
13160
13197
  }
@@ -13164,7 +13201,7 @@ function __test__getManifestCacheEntry(stateRoot) {
13164
13201
  function setManifestCache(stateRoot, entry) {
13165
13202
  if (manifestCache.has(stateRoot)) manifestCache.delete(stateRoot);
13166
13203
  entry.cachedAt = Date.now();
13167
- entry.generation = manifestCacheGeneration;
13204
+ entry.generation = genOf(stateRoot);
13168
13205
  const now = Date.now();
13169
13206
  for (const [key, val] of manifestCache.entries()) {
13170
13207
  if (val.cachedAt && now - val.cachedAt > MANIFEST_CACHE_TTL_MS) {
@@ -13191,7 +13228,7 @@ function useProjectState(cwd) {
13191
13228
  }
13192
13229
  function invalidateRunCache(stateRoot) {
13193
13230
  manifestCache.delete(stateRoot);
13194
- manifestCacheGeneration++;
13231
+ manifestCacheGeneration.set(stateRoot, genOf(stateRoot) + 1);
13195
13232
  }
13196
13233
  function scopeBaseRoot(cwd) {
13197
13234
  return useProjectState(cwd) ? projectCrewRoot(cwd) : userCrewRoot();
@@ -13332,13 +13369,24 @@ function saveRunManifest(manifest) {
13332
13369
  const manifestPath = path10.join(manifest.stateRoot, "manifest.json");
13333
13370
  atomicWriteJson(manifestPath, manifest);
13334
13371
  const manifestStat = fs12.statSync(manifestPath);
13372
+ let tasks = [];
13373
+ let tasksMtimeMs = 0;
13374
+ let tasksSize = 0;
13375
+ try {
13376
+ const tasksPath = path10.join(manifest.stateRoot, "tasks.json");
13377
+ const tasksStat = fs12.statSync(tasksPath);
13378
+ tasks = JSON.parse(fs12.readFileSync(tasksPath, "utf-8"));
13379
+ tasksMtimeMs = tasksStat.mtimeMs;
13380
+ tasksSize = tasksStat.size;
13381
+ } catch {
13382
+ }
13335
13383
  setManifestCache(manifest.stateRoot, {
13336
13384
  manifest,
13337
- tasks: [],
13385
+ tasks,
13338
13386
  manifestMtimeMs: manifestStat.mtimeMs,
13339
13387
  manifestSize: manifestStat.size,
13340
- tasksMtimeMs: 0,
13341
- tasksSize: 0
13388
+ tasksMtimeMs,
13389
+ tasksSize
13342
13390
  });
13343
13391
  }
13344
13392
  async function saveRunManifestAsync(manifest) {
@@ -13506,7 +13554,7 @@ function loadRunManifestById(cwd, runId) {
13506
13554
  tasksStat = void 0;
13507
13555
  }
13508
13556
  const tasksMtimeMs = tasksStat?.mtimeMs ?? 0;
13509
- if (cached && cached.manifestMtimeMs === manifestStat.mtimeMs && cached.manifestSize === manifestStat.size && cached.tasksMtimeMs === tasksMtimeMs && cached.tasksSize === (tasksStat?.size ?? 0) && cached.generation === manifestCacheGeneration) {
13557
+ if (cached && cached.manifestMtimeMs === manifestStat.mtimeMs && cached.manifestSize === manifestStat.size && cached.tasksMtimeMs === tasksMtimeMs && cached.tasksSize === (tasksStat?.size ?? 0) && cached.generation === genOf(stateRoot)) {
13510
13558
  if (!cached.cachedAt || Date.now() - cached.cachedAt > MANIFEST_CACHE_TTL_MS) {
13511
13559
  manifestCache.delete(stateRoot);
13512
13560
  } else if (!validateRunManifestPaths(cwd, runId, cached.manifest, stateRoot, tasksPath)) {
@@ -13569,7 +13617,7 @@ async function loadRunManifestByIdAsync(cwd, runId) {
13569
13617
  tasksStat = void 0;
13570
13618
  }
13571
13619
  const tasksMtimeMs = tasksStat?.mtimeMs ?? 0;
13572
- if (cached && cached.manifestMtimeMs === manifestStat.mtimeMs && cached.manifestSize === manifestStat.size && cached.tasksMtimeMs === tasksMtimeMs && cached.tasksSize === (tasksStat?.size ?? 0) && cached.generation === manifestCacheGeneration) {
13620
+ if (cached && cached.manifestMtimeMs === manifestStat.mtimeMs && cached.manifestSize === manifestStat.size && cached.tasksMtimeMs === tasksMtimeMs && cached.tasksSize === (tasksStat?.size ?? 0) && cached.generation === genOf(stateRoot)) {
13573
13621
  if (!cached.cachedAt || Date.now() - cached.cachedAt > MANIFEST_CACHE_TTL_MS) {
13574
13622
  manifestCache.delete(stateRoot);
13575
13623
  } else if (!validateRunManifestPaths(cwd, runId, cached.manifest, stateRoot, tasksPath)) {
@@ -13629,7 +13677,7 @@ var init_state_store = __esm({
13629
13677
  init_contracts();
13630
13678
  init_event_log();
13631
13679
  init_locks();
13632
- manifestCacheGeneration = 0;
13680
+ manifestCacheGeneration = /* @__PURE__ */ new Map();
13633
13681
  MANIFEST_CACHE_TTL_MS = 15 * 1e3;
13634
13682
  LOAD_MANIFEST_RETRY_LIMIT = 5;
13635
13683
  manifestCache = /* @__PURE__ */ new Map();
@@ -13950,18 +13998,20 @@ function upsertCrewAgent(manifest, record) {
13950
13998
  }
13951
13999
  function writeCrewAgentStatus(manifest, record) {
13952
14000
  ensureAgentStateDir(manifest, record.taskId);
13953
- atomicWriteJson(agentStatusPath(manifest, record.taskId), redactSecrets(record));
14001
+ atomicWriteJson(agentStatusPath(manifest, record.taskId), redactSecrets(record), { durability: "full" });
13954
14002
  }
13955
14003
  function saveCrewAgentsCoalesced(manifest, records) {
13956
14004
  const filePath = agentsPath(manifest);
13957
14005
  fs14.mkdirSync(manifest.stateRoot, { recursive: true });
13958
- atomicWriteJsonCoalesced(filePath, redactSecrets(records), AGENT_COALESCE_MS);
14006
+ atomicWriteJsonCoalesced(filePath, redactSecrets(records), AGENT_COALESCE_MS, { durability: "best-effort" });
13959
14007
  asyncAgentReaderCache.delete(filePath);
13960
14008
  for (const record of records) writeCrewAgentStatusCoalesced(manifest, record);
13961
14009
  }
13962
14010
  function writeCrewAgentStatusCoalesced(manifest, record) {
13963
14011
  ensureAgentStateDir(manifest, record.taskId);
13964
- atomicWriteJsonCoalesced(agentStatusPath(manifest, record.taskId), redactSecrets(record), AGENT_COALESCE_MS);
14012
+ atomicWriteJsonCoalesced(agentStatusPath(manifest, record.taskId), redactSecrets(record), AGENT_COALESCE_MS, {
14013
+ durability: "best-effort"
14014
+ });
13965
14015
  }
13966
14016
  function readCrewAgentStatus(manifest, taskOrAgentId) {
13967
14017
  try {
@@ -15549,12 +15599,12 @@ function getProcessStartTime2(pid) {
15549
15599
  } catch {
15550
15600
  return void 0;
15551
15601
  }
15552
- const platform = process.platform;
15553
- if (platform === "linux") {
15602
+ const platform2 = process.platform;
15603
+ if (platform2 === "linux") {
15554
15604
  return getProcessStartTimeLinux(pid);
15555
- } else if (platform === "darwin") {
15605
+ } else if (platform2 === "darwin") {
15556
15606
  return getProcessStartTimeMacOS(pid);
15557
- } else if (platform === "win32") {
15607
+ } else if (platform2 === "win32") {
15558
15608
  return getProcessStartTimeWindows(pid);
15559
15609
  }
15560
15610
  return void 0;
@@ -18714,6 +18764,7 @@ ${JSON.stringify({ type: "message_end", usage: { input: 10, output: 5, cost: 1e-
18714
18764
  const finalDrainMs = input.finalDrainMs ?? FINAL_DRAIN_MS;
18715
18765
  const hardKillMs = input.hardKillMs ?? HARD_KILL_MS;
18716
18766
  let finalDrainArmed = false;
18767
+ let lastStdoutActivityMonotonicMs = performance.now();
18717
18768
  let finalDrainFiredMonotonicMs;
18718
18769
  const spawnMonotonicMs = performance.now();
18719
18770
  let finalAssistantEventMonotonicMs;
@@ -18872,10 +18923,59 @@ ${JSON.stringify({ type: "message_end", usage: { input: 10, output: 5, cost: 1e-
18872
18923
  completeOperation(eventOpId);
18873
18924
  throw err2;
18874
18925
  }
18926
+ lastStdoutActivityMonotonicMs = performance.now();
18875
18927
  input.onJsonEvent?.(event);
18876
18928
  if (!isFinalAssistantEvent(event) || childExited || settled || finalDrainTimer) return;
18877
18929
  finalAssistantEventMonotonicMs = performance.now();
18878
18930
  finalDrainArmed = true;
18931
+ const quietMs = input.finalDrainQuietMs ?? DEFAULT_CHILD_PI.finalDrainQuietMs;
18932
+ if (quietMs < (input.finalDrainMs ?? DEFAULT_CHILD_PI.finalDrainMs)) {
18933
+ const pollHandle = setInterval(() => {
18934
+ if (settled || childExited) {
18935
+ clearInterval(pollHandle);
18936
+ pollHandle.unref();
18937
+ return;
18938
+ }
18939
+ const sinceLast = performance.now() - lastStdoutActivityMonotonicMs;
18940
+ if (sinceLast >= quietMs) {
18941
+ clearInterval(pollHandle);
18942
+ pollHandle.unref();
18943
+ forcedFinalDrain = true;
18944
+ finalDrainFiredMonotonicMs = performance.now();
18945
+ input.onLifecycleEvent?.({
18946
+ type: "final_drain",
18947
+ pid: child.pid,
18948
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
18949
+ reason: "stdout-quiet"
18950
+ });
18951
+ try {
18952
+ child.kill(process.platform === "win32" ? void 0 : "SIGTERM");
18953
+ } catch (error) {
18954
+ logInternalError("child-pi.quiet-drain-term", error, `pid=${child.pid}`);
18955
+ }
18956
+ hardKillTimer = setTimeout(() => {
18957
+ if (settled || childExited) return;
18958
+ try {
18959
+ hardKilled = true;
18960
+ input.onLifecycleEvent?.({
18961
+ type: "hard_kill",
18962
+ pid: child.pid,
18963
+ ts: (/* @__PURE__ */ new Date()).toISOString()
18964
+ });
18965
+ child.kill(process.platform === "win32" ? void 0 : "SIGKILL");
18966
+ } catch (error) {
18967
+ logInternalError("child-pi.quiet-drain-hard-kill", error, `pid=${child.pid}`);
18968
+ }
18969
+ }, hardKillMs);
18970
+ hardKillTimer.unref();
18971
+ if (finalDrainTimer) {
18972
+ clearTimeout(finalDrainTimer);
18973
+ finalDrainTimer = void 0;
18974
+ }
18975
+ }
18976
+ }, 200);
18977
+ pollHandle.unref();
18978
+ }
18879
18979
  finalDrainTimer = setTimeout(() => {
18880
18980
  if (settled || childExited) return;
18881
18981
  forcedFinalDrain = true;
@@ -19199,6 +19299,15 @@ var init_child_pi = __esm({
19199
19299
  MAX_COMPACT_CONTENT_CHARS = DEFAULT_CHILD_PI.maxCompactContentChars;
19200
19300
  activeChildProcesses = /* @__PURE__ */ new Map();
19201
19301
  childHardKillTimers = /* @__PURE__ */ new Map();
19302
+ setInterval(() => {
19303
+ for (const [pid, child] of activeChildProcesses) {
19304
+ try {
19305
+ process.kill(pid, 0);
19306
+ } catch {
19307
+ activeChildProcesses.delete(pid);
19308
+ }
19309
+ }
19310
+ }, 6e4).unref();
19202
19311
  BASE_ALLOWLIST = [
19203
19312
  "PATH",
19204
19313
  "HOME",
@@ -19241,20 +19350,25 @@ var init_child_pi = __esm({
19241
19350
  "PI_TEAMS_MOCK_CHILD_PI",
19242
19351
  "PI_CREW_ALLOW_MOCK"
19243
19352
  ];
19244
- ChildPiLineObserver = class {
19353
+ ChildPiLineObserver = class _ChildPiLineObserver {
19245
19354
  buffer = "";
19246
19355
  input;
19247
- /** RAW (uncapped) assistant-text fragments, accumulated in arrival order.
19248
- * Mirrors {@link parsePiJsonOutput}'s textEvents/finalText extraction but
19249
- * operates on the RAW event stream instead of the 16K-compacted transcript.
19250
- * This is the source of the AUTHORITATIVE result.txt; the transcript stays
19251
- * compacted (memory bound). */
19356
+ /** F9: bounded ring buffer for RAW assistant-text fragments. Consumers
19357
+ * (getRawFinalText) only read the last element, but the legacy implementation
19358
+ * accumulated every fragment unconditionally, which let a verbose/long-running
19359
+ * worker grow this array linearly with output. We retain the last 2 entries:
19360
+ * the consumer needs the last; we keep the second-to-last only as a defensive
19361
+ * fence against a race where a final event arrives just after the consumer
19362
+ * read (the previous "last" is still the most-recent pre-final text in that
19363
+ * window). 2 is well below any plausible consumer's "tail-only" need while
19364
+ * bounding memory. */
19365
+ static MAX_RAW_TEXT_EVENTS = 2;
19252
19366
  rawTextEvents = [];
19253
- /** #7 hardening: bounded digest of intermediate findings. When a worker spends
19254
- * its entire budget on tool calls (never emits a final assistant text),
19255
- * getRawFinalText() returns undefined but this digest captures the last
19256
- * display lines (tool results, stdout fragments) before budget exhaustion.
19257
- * Capped at MAX_INTERMEDIATE_DIGEST_LINES so result artifacts stay bounded. */
19367
+ /** F9: bounded ring buffer for intermediate findings. The downstream digest
19368
+ * (getIntermediateFindings) slices the last 20, but the array previously grew
19369
+ * to 1000s of entries. We keep MAX_INTERMEDIATE_DIGEST_LINES + headroom so
19370
+ * the public API behaviour is preserved (still returns "last 20 lines"). */
19371
+ static MAX_INTERMEDIATE_FINDINGS = 32;
19258
19372
  intermediateFindings = [];
19259
19373
  constructor(input) {
19260
19374
  this.input = input;
@@ -19298,9 +19412,13 @@ var init_child_pi = __esm({
19298
19412
  const rawTexts = extractText(rawParsed);
19299
19413
  if (rawTexts.length > 0) {
19300
19414
  this.rawTextEvents.push(...rawTexts);
19415
+ const rawOverflow = this.rawTextEvents.length - _ChildPiLineObserver.MAX_RAW_TEXT_EVENTS;
19416
+ if (rawOverflow > 0) this.rawTextEvents.splice(0, rawOverflow);
19301
19417
  const last = rawTexts[rawTexts.length - 1];
19302
19418
  if (last.trim().length > 0) {
19303
19419
  this.intermediateFindings.push(last.trim());
19420
+ const findingsOverflow = this.intermediateFindings.length - _ChildPiLineObserver.MAX_INTERMEDIATE_FINDINGS;
19421
+ if (findingsOverflow > 0) this.intermediateFindings.splice(0, findingsOverflow);
19304
19422
  }
19305
19423
  }
19306
19424
  } catch {
@@ -19321,6 +19439,8 @@ var init_child_pi = __esm({
19321
19439
  logInternalError("child-pi.on-stdout-line", error, `line=${compact.displayLine}`);
19322
19440
  }
19323
19441
  this.intermediateFindings.push(compact.displayLine.trim());
19442
+ const findingsOverflow = this.intermediateFindings.length - _ChildPiLineObserver.MAX_INTERMEDIATE_FINDINGS;
19443
+ if (findingsOverflow > 0) this.intermediateFindings.splice(0, findingsOverflow);
19324
19444
  }
19325
19445
  }
19326
19446
  };
@@ -19970,15 +20090,56 @@ function parseDynamicWorkflowFile(filePath, source) {
19970
20090
  return void 0;
19971
20091
  }
19972
20092
  }
20093
+ function workflowDirStamp(cwd) {
20094
+ const dirs = [
20095
+ path26.join(packageRoot(), "workflows"),
20096
+ path26.join(userPiRoot(), "workflows"),
20097
+ path26.join(projectCrewRoot(cwd), "workflows")
20098
+ ];
20099
+ let out = "";
20100
+ for (const d of dirs) {
20101
+ try {
20102
+ const st = fs30.statSync(d);
20103
+ out += `${st.mtimeMs}|`;
20104
+ } catch {
20105
+ out += "0|";
20106
+ }
20107
+ }
20108
+ return out;
20109
+ }
20110
+ function invalidateWorkflowDiscoveryCache(cwd) {
20111
+ if (cwd) {
20112
+ workflowCache.delete(cwd);
20113
+ } else {
20114
+ workflowCache.clear();
20115
+ }
20116
+ }
19973
20117
  function discoverWorkflows(cwd) {
19974
20118
  if (!cwd || typeof cwd !== "string") {
19975
20119
  return { builtin: [], user: [], project: [] };
19976
20120
  }
19977
- return {
20121
+ const now = Date.now();
20122
+ const stamp = workflowDirStamp(cwd);
20123
+ const cached = workflowCache.get(cwd);
20124
+ if (cached && cached.expiresAt > now && cached.dirStamp === stamp) {
20125
+ return cached.result;
20126
+ }
20127
+ const result4 = {
19978
20128
  builtin: readWorkflowDir(path26.join(packageRoot(), "workflows"), "builtin"),
19979
20129
  user: readWorkflowDir(path26.join(userPiRoot(), "workflows"), "user"),
19980
20130
  project: readWorkflowDir(path26.join(projectCrewRoot(cwd), "workflows"), "project")
19981
20131
  };
20132
+ workflowCache.set(cwd, {
20133
+ result: result4,
20134
+ expiresAt: now + WORKFLOW_DISCOVERY_TTL_MS,
20135
+ dirStamp: stamp
20136
+ });
20137
+ while (workflowCache.size > WORKFLOW_DISCOVERY_MAX_ENTRIES) {
20138
+ const oldest = workflowCache.keys().next().value;
20139
+ if (oldest === void 0) break;
20140
+ workflowCache.delete(oldest);
20141
+ }
20142
+ return result4;
19982
20143
  }
19983
20144
  function allWorkflows(discovery) {
19984
20145
  if (!discovery) return [];
@@ -19988,7 +20149,7 @@ function allWorkflows(discovery) {
19988
20149
  }
19989
20150
  return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
19990
20151
  }
19991
- var STEP_CONFIG_KEYS, parseOptionalInteger, parseOptionalBoolean, parseTopology;
20152
+ var STEP_CONFIG_KEYS, parseOptionalInteger, parseOptionalBoolean, parseTopology, WORKFLOW_DISCOVERY_TTL_MS, WORKFLOW_DISCOVERY_MAX_ENTRIES, workflowCache;
19992
20153
  var init_discover_workflows = __esm({
19993
20154
  "src/workflows/discover-workflows.ts"() {
19994
20155
  "use strict";
@@ -20033,6 +20194,9 @@ var init_discover_workflows = __esm({
20033
20194
  }
20034
20195
  return void 0;
20035
20196
  };
20197
+ WORKFLOW_DISCOVERY_TTL_MS = 5e3;
20198
+ WORKFLOW_DISCOVERY_MAX_ENTRIES = 32;
20199
+ workflowCache = /* @__PURE__ */ new Map();
20036
20200
  }
20037
20201
  });
20038
20202
 
@@ -22165,7 +22329,7 @@ var init_discover_agents = __esm({
22165
22329
  MAX_SECURITY_LOG_ENTRIES = 1e3;
22166
22330
  securityEventLog = [];
22167
22331
  warnedForkAgents = /* @__PURE__ */ new Set();
22168
- DISCOVERY_CACHE_TTL_MS = 500;
22332
+ DISCOVERY_CACHE_TTL_MS = 5e3;
22169
22333
  discoveryCache = /* @__PURE__ */ new Map();
22170
22334
  DISCOVERY_CACHE_MAX_ENTRIES = 32;
22171
22335
  dynamicAgents = /* @__PURE__ */ new Map();
@@ -22362,7 +22526,8 @@ var init_git = __esm({
22362
22526
  var discover_teams_exports = {};
22363
22527
  __export(discover_teams_exports, {
22364
22528
  allTeams: () => allTeams,
22365
- discoverTeams: () => discoverTeams
22529
+ discoverTeams: () => discoverTeams,
22530
+ invalidateTeamDiscoveryCache: () => invalidateTeamDiscoveryCache
22366
22531
  });
22367
22532
  import * as fs34 from "node:fs";
22368
22533
  import * as path29 from "node:path";
@@ -22446,12 +22611,48 @@ function readTeamDir(dir, source) {
22446
22611
  if (!fs34.existsSync(dir)) return [];
22447
22612
  return fs34.readdirSync(dir).filter((entry) => entry.endsWith(".team.md")).map((entry) => parseTeamFile(path29.join(dir, entry), source)).filter((team) => team !== void 0).sort((a, b) => a.name.localeCompare(b.name));
22448
22613
  }
22614
+ function teamDirStamp(cwd) {
22615
+ const dirs = [path29.join(packageRoot(), "teams"), path29.join(userPiRoot(), "teams"), path29.join(projectCrewRoot(cwd), "teams")];
22616
+ let out = "";
22617
+ for (const d of dirs) {
22618
+ try {
22619
+ out += `${fs34.statSync(d).mtimeMs}|`;
22620
+ } catch {
22621
+ out += "0|";
22622
+ }
22623
+ }
22624
+ return out;
22625
+ }
22626
+ function invalidateTeamDiscoveryCache(cwd) {
22627
+ if (cwd) {
22628
+ teamCache.delete(cwd);
22629
+ } else {
22630
+ teamCache.clear();
22631
+ }
22632
+ }
22449
22633
  function discoverTeams(cwd) {
22450
- return {
22634
+ const now = Date.now();
22635
+ const stamp = teamDirStamp(cwd);
22636
+ const cached = teamCache.get(cwd);
22637
+ if (cached && cached.expiresAt > now && cached.dirStamp === stamp) {
22638
+ return cached.result;
22639
+ }
22640
+ const result4 = {
22451
22641
  builtin: readTeamDir(path29.join(packageRoot(), "teams"), "builtin"),
22452
22642
  user: readTeamDir(path29.join(userPiRoot(), "teams"), "user"),
22453
22643
  project: readTeamDir(path29.join(projectCrewRoot(cwd), "teams"), "project")
22454
22644
  };
22645
+ teamCache.set(cwd, {
22646
+ result: result4,
22647
+ expiresAt: now + TEAM_DISCOVERY_TTL_MS,
22648
+ dirStamp: stamp
22649
+ });
22650
+ while (teamCache.size > TEAM_DISCOVERY_MAX_ENTRIES) {
22651
+ const oldest = teamCache.keys().next().value;
22652
+ if (oldest === void 0) break;
22653
+ teamCache.delete(oldest);
22654
+ }
22655
+ return result4;
22455
22656
  }
22456
22657
  function allTeams(discovery) {
22457
22658
  if (!discovery) return [];
@@ -22461,12 +22662,16 @@ function allTeams(discovery) {
22461
22662
  }
22462
22663
  return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
22463
22664
  }
22665
+ var TEAM_DISCOVERY_TTL_MS, TEAM_DISCOVERY_MAX_ENTRIES, teamCache;
22464
22666
  var init_discover_teams = __esm({
22465
22667
  "src/teams/discover-teams.ts"() {
22466
22668
  "use strict";
22467
22669
  init_frontmatter();
22468
22670
  init_git();
22469
22671
  init_paths();
22672
+ TEAM_DISCOVERY_TTL_MS = 5e3;
22673
+ TEAM_DISCOVERY_MAX_ENTRIES = 32;
22674
+ teamCache = /* @__PURE__ */ new Map();
22470
22675
  }
22471
22676
  });
22472
22677
 
@@ -23615,7 +23820,7 @@ function readDeliveryState(manifest) {
23615
23820
  return { messages: {}, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
23616
23821
  }
23617
23822
  }
23618
- function writeDeliveryState(manifest, state) {
23823
+ function writeDeliveryState(manifest, state, options) {
23619
23824
  ensureRunMailbox(manifest);
23620
23825
  const MAX_DELIVERY_MESSAGES = 1e4;
23621
23826
  if (Object.keys(state.messages).length > MAX_DELIVERY_MESSAGES) {
@@ -23627,7 +23832,9 @@ function writeDeliveryState(manifest, state) {
23627
23832
  state.messages = Object.fromEntries(trimmed);
23628
23833
  }
23629
23834
  atomicWriteFile(deliveryFile(manifest, true), `${JSON.stringify(redactSecrets(state), null, 2)}
23630
- `);
23835
+ `, {
23836
+ durability: options?.durability ?? "best-effort"
23837
+ });
23631
23838
  }
23632
23839
  function appendMailboxMessage(manifest, message) {
23633
23840
  if (message.taskId) ensureTaskMailbox(manifest, message.taskId);
@@ -23666,7 +23873,7 @@ function appendMailboxMessage(manifest, message) {
23666
23873
  const delivery = readDeliveryState(manifest);
23667
23874
  delivery.messages[complete.id] = complete.status;
23668
23875
  delivery.updatedAt = createdAt;
23669
- writeDeliveryState(manifest, delivery);
23876
+ writeDeliveryState(manifest, delivery, { durability: "full" });
23670
23877
  });
23671
23878
  return complete;
23672
23879
  }
@@ -23710,7 +23917,7 @@ function acknowledgeMailboxMessage(manifest, messageId) {
23710
23917
  if (!delivery.messages[messageId]) throw new Error(`Mailbox message '${messageId}' not found.`);
23711
23918
  delivery.messages[messageId] = "acknowledged";
23712
23919
  delivery.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
23713
- writeDeliveryState(manifest, delivery);
23920
+ writeDeliveryState(manifest, delivery, { durability: "full" });
23714
23921
  return delivery;
23715
23922
  });
23716
23923
  }
@@ -24164,6 +24371,11 @@ var init_intent_policy = __esm({
24164
24371
  import * as crypto3 from "node:crypto";
24165
24372
  import * as fs38 from "node:fs";
24166
24373
  import * as path33 from "node:path";
24374
+ function invalidateResourceCaches() {
24375
+ invalidateAgentDiscoveryCache();
24376
+ invalidateTeamDiscoveryCache();
24377
+ invalidateWorkflowDiscoveryCache();
24378
+ }
24167
24379
  function result2(text, status = "ok", isError = false) {
24168
24380
  return toolResult(text, { action: "management", status }, isError);
24169
24381
  }
@@ -24461,6 +24673,7 @@ ${content}`);
24461
24673
  true
24462
24674
  );
24463
24675
  }
24676
+ invalidateResourceCaches();
24464
24677
  return result2(`Created ${params.resource} '${name}' at ${filePath}.`);
24465
24678
  }
24466
24679
  function handleUpdate(params, ctx) {
@@ -24565,6 +24778,7 @@ function handleUpdate(params, ctx) {
24565
24778
  );
24566
24779
  }
24567
24780
  const updatedRefs = params.updateReferences ? updateReferencesForRename(ctx, params.resource, current2.name, nextName, source, false) : [];
24781
+ invalidateResourceCaches();
24568
24782
  return result2(
24569
24783
  [
24570
24784
  `Updated ${params.resource} at ${nextPath}. Backup: ${backupPath}.`,
@@ -24603,6 +24817,7 @@ ${refs.map((ref2) => `- ${ref2}`).join("\n")}` : ""}`
24603
24817
  true
24604
24818
  );
24605
24819
  }
24820
+ invalidateResourceCaches();
24606
24821
  return result2(`Deleted ${params.resource} at ${resolved.resource.filePath}. Backup: ${backupPath}.`);
24607
24822
  }
24608
24823
  var init_management = __esm({
@@ -24658,18 +24873,18 @@ function initializeProject(cwd, options = {}) {
24658
24873
  const teamsDir = path34.join(crewRoot, "teams");
24659
24874
  const workflowsDir = path34.join(crewRoot, "workflows");
24660
24875
  const configScope = options.configScope ?? "global";
24661
- const configPath2 = configScope === "project" ? path34.join(projectPiRoot(cwd), "pi-crew.json") : configScope === "global" ? configPath() : "";
24876
+ const configPath3 = configScope === "project" ? path34.join(projectPiRoot(cwd), "pi-crew.json") : configScope === "global" ? configPath() : "";
24662
24877
  ensureDir(agentsDir, createdDirs);
24663
24878
  ensureDir(teamsDir, createdDirs);
24664
24879
  ensureDir(workflowsDir, createdDirs);
24665
24880
  ensureDir(path34.join(crewRoot, "imports"), createdDirs);
24666
24881
  let configCreated = false;
24667
24882
  let configSkipped = false;
24668
- if (configPath2) {
24669
- if (configScope === "project") ensureDir(path34.dirname(configPath2), createdDirs);
24670
- else fs39.mkdirSync(path34.dirname(configPath2), { recursive: true });
24671
- if (!fs39.existsSync(configPath2) || options.overwrite === true) {
24672
- fs39.writeFileSync(configPath2, `${JSON.stringify(DEFAULT_PI_CREW_CONFIG, null, 2)}
24883
+ if (configPath3) {
24884
+ if (configScope === "project") ensureDir(path34.dirname(configPath3), createdDirs);
24885
+ else fs39.mkdirSync(path34.dirname(configPath3), { recursive: true });
24886
+ if (!fs39.existsSync(configPath3) || options.overwrite === true) {
24887
+ fs39.writeFileSync(configPath3, `${JSON.stringify(DEFAULT_PI_CREW_CONFIG, null, 2)}
24673
24888
  `, "utf-8");
24674
24889
  configCreated = true;
24675
24890
  } else {
@@ -24708,7 +24923,7 @@ ${missing.join("\n")}
24708
24923
  skippedFiles,
24709
24924
  gitignorePath,
24710
24925
  gitignoreUpdated,
24711
- configPath: configPath2,
24926
+ configPath: configPath3,
24712
24927
  configScope,
24713
24928
  configCreated,
24714
24929
  configSkipped
@@ -44086,7 +44301,7 @@ var init_crew_hooks = __esm({
44086
44301
  });
44087
44302
 
44088
44303
  // src/runtime/skill-effectiveness.ts
44089
- import { existsSync as existsSync38, mkdirSync as mkdirSync26, readFileSync as readFileSync40, writeFileSync as writeFileSync18 } from "fs";
44304
+ import { existsSync as existsSync38, mkdirSync as mkdirSync26, readFileSync as readFileSync41, writeFileSync as writeFileSync18 } from "fs";
44090
44305
  import { dirname as dirname24, join as join43 } from "path";
44091
44306
  function getSkillMetricsPath(cwd, runId) {
44092
44307
  return join43(projectCrewRoot(cwd), `state/runs/${runId}/skill-metrics.jsonl`);
@@ -44149,7 +44364,7 @@ function getSkillActivations(cwd, runId) {
44149
44364
  if (!existsSync38(path81)) {
44150
44365
  return [];
44151
44366
  }
44152
- const content = readFileSync40(path81, "utf-8");
44367
+ const content = readFileSync41(path81, "utf-8");
44153
44368
  if (!content.trim()) {
44154
44369
  return [];
44155
44370
  }
@@ -47311,7 +47526,7 @@ var init_explain = __esm({
47311
47526
  });
47312
47527
 
47313
47528
  // src/runtime/goal-state-store.ts
47314
- import { closeSync as closeSync13, existsSync as existsSync45, mkdirSync as mkdirSync29, openSync as openSync13, readdirSync as readdirSync23, readFileSync as readFileSync46, statSync as statSync29, unlinkSync as unlinkSync9 } from "node:fs";
47529
+ import { closeSync as closeSync13, existsSync as existsSync45, mkdirSync as mkdirSync29, openSync as openSync13, readdirSync as readdirSync23, readFileSync as readFileSync47, statSync as statSync31, unlinkSync as unlinkSync9 } from "node:fs";
47315
47530
  import { dirname as dirname26 } from "node:path";
47316
47531
  function resolveGoalsRoot(cwd) {
47317
47532
  const crewRoot = projectCrewRoot(cwd) ?? userCrewRoot();
@@ -47345,7 +47560,7 @@ var init_goal_state_store = __esm({
47345
47560
  const path81 = goalFilePath(this.cwd, goalId);
47346
47561
  try {
47347
47562
  if (!existsSync45(path81)) return void 0;
47348
- const raw = readFileSync46(path81, "utf-8");
47563
+ const raw = readFileSync47(path81, "utf-8");
47349
47564
  const parsed = JSON.parse(raw);
47350
47565
  if (!parsed || typeof parsed !== "object" || typeof parsed.goalId !== "string") return void 0;
47351
47566
  return parsed;
@@ -47442,7 +47657,7 @@ var init_goal_state_store = __esm({
47442
47657
  const code = error.code;
47443
47658
  if (code !== "EEXIST") return false;
47444
47659
  try {
47445
- const stat2 = statSync29(lockPath2);
47660
+ const stat2 = statSync31(lockPath2);
47446
47661
  if (Date.now() - stat2.mtimeMs > 5e3) {
47447
47662
  unlinkSync9(lockPath2);
47448
47663
  const fd = openSync13(lockPath2, "wx");
@@ -47533,7 +47748,7 @@ var init_verification_integrity = __esm({
47533
47748
 
47534
47749
  // src/runtime/workspace-lock.ts
47535
47750
  import { createHash as createHash7 } from "node:crypto";
47536
- import { closeSync as closeSync14, existsSync as existsSync46, mkdirSync as mkdirSync30, openSync as openSync14, readdirSync as readdirSync24, readFileSync as readFileSync48, statSync as statSync31, unlinkSync as unlinkSync10, writeFileSync as writeFileSync23 } from "node:fs";
47751
+ import { closeSync as closeSync14, existsSync as existsSync46, mkdirSync as mkdirSync30, openSync as openSync14, readdirSync as readdirSync24, readFileSync as readFileSync49, statSync as statSync33, unlinkSync as unlinkSync10, writeFileSync as writeFileSync23 } from "node:fs";
47537
47752
  import * as path51 from "node:path";
47538
47753
  function workspaceLockPath(cwd) {
47539
47754
  const absCwd = path51.resolve(cwd);
@@ -47545,7 +47760,7 @@ function workspaceLockPath(cwd) {
47545
47760
  function readLock(lockPath2) {
47546
47761
  if (!existsSync46(lockPath2)) return void 0;
47547
47762
  try {
47548
- const parsed = JSON.parse(readFileSync48(lockPath2, "utf-8"));
47763
+ const parsed = JSON.parse(readFileSync49(lockPath2, "utf-8"));
47549
47764
  if (!parsed || typeof parsed !== "object") return void 0;
47550
47765
  return parsed;
47551
47766
  } catch {
@@ -47584,7 +47799,7 @@ var init_workspace_lock = __esm({
47584
47799
  DEFAULT_HEARTBEAT_STALE_MS = 6e4;
47585
47800
  defaultStartTimeResolver = (pid) => {
47586
47801
  try {
47587
- const stat2 = readFileSync48(`/proc/${pid}/stat`, "utf-8");
47802
+ const stat2 = readFileSync49(`/proc/${pid}/stat`, "utf-8");
47588
47803
  const lastParen = stat2.lastIndexOf(")");
47589
47804
  if (lastParen === -1) return void 0;
47590
47805
  const fieldsAfterComm = stat2.slice(lastParen + 1).trim().split(/\s+/);
@@ -51508,7 +51723,7 @@ var init_tool_progress_formatter = __esm({
51508
51723
  });
51509
51724
 
51510
51725
  // src/extension/registration/team-tool.ts
51511
- import { statSync as statSync34 } from "node:fs";
51726
+ import { statSync as statSync36 } from "node:fs";
51512
51727
  import { Text as Text4 } from "@earendil-works/pi-tui";
51513
51728
  async function handleTeamTool(params, ctx) {
51514
51729
  if (!_cachedHandleTeamTool) {
@@ -51521,7 +51736,7 @@ function resolveCwdOverride(baseCwd, override) {
51521
51736
  if (!override) return { ok: true, cwd: baseCwd };
51522
51737
  try {
51523
51738
  const resolved = resolveRealContainedPath(baseCwd, override);
51524
- const stat2 = statSync34(resolved);
51739
+ const stat2 = statSync36(resolved);
51525
51740
  if (!stat2.isDirectory())
51526
51741
  return {
51527
51742
  ok: false,
@@ -52795,7 +53010,7 @@ var init_status = __esm({
52795
53010
  });
52796
53011
 
52797
53012
  // src/extension/team-tool/workflow-manage.ts
52798
- import { existsSync as existsSync58, readFileSync as readFileSync57, rmSync as rmSync16, writeFileSync as writeFileSync26 } from "node:fs";
53013
+ import { existsSync as existsSync58, readFileSync as readFileSync58, rmSync as rmSync16, writeFileSync as writeFileSync26 } from "node:fs";
52799
53014
  import { dirname as dirname33, join as join59 } from "node:path";
52800
53015
  function allowedWorkflowDirs(cwd) {
52801
53016
  return [join59(projectCrewRoot(cwd), "workflows"), join59(userPiRoot(), "workflows"), join59(packageRoot(), "workflows")];
@@ -52867,7 +53082,7 @@ function handleWorkflowGet(params, ctx) {
52867
53082
  let source = "(static workflow \u2014 no script source)";
52868
53083
  if (isDynamic && wf.filePath && existsSync58(wf.filePath)) {
52869
53084
  try {
52870
- source = readFileSync57(wf.filePath, "utf-8").slice(0, 8e3);
53085
+ source = readFileSync58(wf.filePath, "utf-8").slice(0, 8e3);
52871
53086
  } catch (error) {
52872
53087
  logInternalError("workflow-manage.get", error, `filePath=${wf.filePath}`);
52873
53088
  }
@@ -54413,7 +54628,7 @@ function asError(error) {
54413
54628
  return error instanceof Error ? error : new Error(String(error));
54414
54629
  }
54415
54630
  function globToRegex(pattern) {
54416
- const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
54631
+ const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
54417
54632
  return new RegExp(`^${escaped}$`, "i");
54418
54633
  }
54419
54634
  function isRetryable(error, policy) {
@@ -56385,6 +56600,10 @@ function readKnowledge(cwd, query) {
56385
56600
  knowledgeCache.delete(p);
56386
56601
  return "";
56387
56602
  }
56603
+ if (stat2.isSymbolicLink) {
56604
+ logInternalError("knowledge-injection.symlink", new Error(`Refusing to read symlink: ${p}`));
56605
+ return "";
56606
+ }
56388
56607
  const cacheKey2 = `${stat2.mtimeMs}:${stat2.size}`;
56389
56608
  if (!query || !query.goal && !query.taskText) {
56390
56609
  const cached = knowledgeCache.get(p);
@@ -56434,8 +56653,8 @@ Full file: ${p} -->`
56434
56653
  }
56435
56654
  function tryStat(p) {
56436
56655
  try {
56437
- const s = fs77.statSync(p);
56438
- return { mtimeMs: s.mtimeMs, size: s.size };
56656
+ const s = fs77.lstatSync(p);
56657
+ return { mtimeMs: s.mtimeMs, size: s.size, isSymbolicLink: s.isSymbolicLink() };
56439
56658
  } catch {
56440
56659
  return void 0;
56441
56660
  }
@@ -56466,6 +56685,7 @@ var KNOWLEDGE_FILENAME, MAX_SESSION_LOG_BYTES, CONVENTION_HEADERS, STOPWORDS, kn
56466
56685
  var init_knowledge_injection = __esm({
56467
56686
  "src/extension/knowledge-injection.ts"() {
56468
56687
  "use strict";
56688
+ init_internal_error();
56469
56689
  init_paths();
56470
56690
  KNOWLEDGE_FILENAME = "knowledge.md";
56471
56691
  MAX_SESSION_LOG_BYTES = 5e3;
@@ -57087,32 +57307,12 @@ function persistSingleTaskUpdate(manifest, fallbackTasks, updated, checkpointPha
57087
57307
  try {
57088
57308
  currentMtime = fs80.statSync(manifest.tasksPath).mtimeMs;
57089
57309
  } catch {
57090
- currentMtime = 0;
57310
+ return fallbackTasks;
57091
57311
  }
57092
57312
  if (currentMtime !== baseMtime) {
57093
57313
  baseMtime = currentMtime;
57094
57314
  continue;
57095
57315
  }
57096
- let recheckMtime;
57097
- try {
57098
- recheckMtime = fs80.statSync(manifest.tasksPath).mtimeMs;
57099
- } catch {
57100
- return fallbackTasks;
57101
- }
57102
- if (recheckMtime !== baseMtime) {
57103
- baseMtime = recheckMtime;
57104
- continue;
57105
- }
57106
- let preWriteMtime;
57107
- try {
57108
- preWriteMtime = fs80.statSync(manifest.tasksPath).mtimeMs;
57109
- } catch {
57110
- preWriteMtime = 0;
57111
- }
57112
- if (preWriteMtime !== baseMtime) {
57113
- baseMtime = preWriteMtime;
57114
- continue;
57115
- }
57116
57316
  break;
57117
57317
  }
57118
57318
  if (merged === void 0) {
@@ -59593,8 +59793,7 @@ function checkPerTaskBudget(tasks, budgetTotal, budgetWarning, budgetAbort, fair
59593
59793
  const totalUsed = (usage?.input ?? 0) + (usage?.output ?? 0) + (usage?.cacheWrite ?? 0);
59594
59794
  const abort = totalUsed >= budgetAbort * budgetTotal;
59595
59795
  const warning = !abort && totalUsed >= budgetWarning * budgetTotal;
59596
- const remainingBudget = Math.max(0, budgetTotal - totalUsed);
59597
- const fairShareThreshold = remainingBudget * fairShareFraction;
59796
+ const fairShareThreshold = budgetTotal * fairShareFraction;
59598
59797
  const fairShareViolators = [];
59599
59798
  for (const task of tasks) {
59600
59799
  if (!task.usage) continue;
@@ -60031,6 +60230,8 @@ async function executeTeamRun(input) {
60031
60230
  cleanupUsage();
60032
60231
  await flushEventLogBuffer();
60033
60232
  return result4;
60233
+ } finally {
60234
+ await flushEventLogBuffer();
60034
60235
  }
60035
60236
  }
60036
60237
  async function executeTeamRunCore(input, manifest, workflow) {
@@ -60906,122 +61107,126 @@ var init_pipeline_runner = __esm({
60906
61107
  const stages = [];
60907
61108
  let previousResults = [];
60908
61109
  const startTime = Date.now();
60909
- await appendEventAsync(eventsPath, {
60910
- type: "pipeline:started",
60911
- runId,
60912
- message: `Pipeline '${workflow.name}' started`,
60913
- data: { stages: workflow.stages.map((s) => s.name) }
60914
- });
60915
- for (let i2 = 0; i2 < workflow.stages.length; i2++) {
60916
- const stage = workflow.stages[i2];
60917
- const stageStartTime = Date.now();
60918
- const effectiveStopOnError = stage.stopOnError ?? workflow.stopOnError ?? this.stopOnError;
61110
+ try {
60919
61111
  await appendEventAsync(eventsPath, {
60920
- type: "pipeline:stage_started",
61112
+ type: "pipeline:started",
60921
61113
  runId,
60922
- message: `Stage '${stage.name}' started`,
60923
- data: { stageIndex: i2, stageName: stage.name }
61114
+ message: `Pipeline '${workflow.name}' started`,
61115
+ data: { stages: workflow.stages.map((s) => s.name) }
60924
61116
  });
60925
- try {
60926
- const stageContext = {
60927
- stageIndex: i2,
60928
- stageName: stage.name,
60929
- previousResults,
60930
- totalStages: workflow.stages.length,
60931
- runId
60932
- };
60933
- const inputs = this.resolveInputs(stage.inputs, previousResults, context);
60934
- const results = await this.executeStageInternal(stage, inputs, stageContext, executeStage);
60935
- const duration = Date.now() - stageStartTime;
60936
- stages.push({
60937
- name: stage.name,
60938
- status: "completed",
60939
- results,
60940
- duration,
60941
- fanOutItems: Array.isArray(inputs) ? inputs.length : void 0
60942
- });
60943
- previousResults = results;
61117
+ for (let i2 = 0; i2 < workflow.stages.length; i2++) {
61118
+ const stage = workflow.stages[i2];
61119
+ const stageStartTime = Date.now();
61120
+ const effectiveStopOnError = stage.stopOnError ?? workflow.stopOnError ?? this.stopOnError;
60944
61121
  await appendEventAsync(eventsPath, {
60945
- type: "pipeline:stage_completed",
61122
+ type: "pipeline:stage_started",
60946
61123
  runId,
60947
- message: `Stage '${stage.name}' completed`,
60948
- data: {
61124
+ message: `Stage '${stage.name}' started`,
61125
+ data: { stageIndex: i2, stageName: stage.name }
61126
+ });
61127
+ try {
61128
+ const stageContext = {
60949
61129
  stageIndex: i2,
60950
61130
  stageName: stage.name,
60951
- duration,
60952
- resultCount: results.length
60953
- }
60954
- });
60955
- } catch (error) {
60956
- const duration = Date.now() - stageStartTime;
60957
- const errorMessage = error instanceof Error ? error.message : String(error);
60958
- if (effectiveStopOnError) {
60959
- stages.push({
60960
- name: stage.name,
60961
- status: "failed",
60962
- results: [],
60963
- error: errorMessage,
60964
- duration
60965
- });
60966
- await appendEventAsync(eventsPath, {
60967
- type: "pipeline:stage_failed",
60968
- runId,
60969
- message: `Stage '${stage.name}' failed: ${errorMessage}`,
60970
- data: {
60971
- stageIndex: i2,
60972
- stageName: stage.name,
60973
- duration,
60974
- error: errorMessage
60975
- }
60976
- });
60977
- await appendEventAsync(eventsPath, {
60978
- type: "pipeline:failed",
60979
- runId,
60980
- message: `Pipeline '${workflow.name}' failed at stage '${stage.name}'`,
60981
- data: { failedStage: stage.name, error: errorMessage }
60982
- });
60983
- return {
60984
- stages,
60985
- totalDuration: Date.now() - startTime,
60986
- finalResults: previousResults,
60987
- status: "failed"
61131
+ previousResults,
61132
+ totalStages: workflow.stages.length,
61133
+ runId
60988
61134
  };
60989
- } else {
61135
+ const inputs = this.resolveInputs(stage.inputs, previousResults, context);
61136
+ const results = await this.executeStageInternal(stage, inputs, stageContext, executeStage);
61137
+ const duration = Date.now() - stageStartTime;
60990
61138
  stages.push({
60991
61139
  name: stage.name,
60992
- status: "failed",
60993
- results: [],
60994
- error: errorMessage,
60995
- duration
61140
+ status: "completed",
61141
+ results,
61142
+ duration,
61143
+ fanOutItems: Array.isArray(inputs) ? inputs.length : void 0
60996
61144
  });
61145
+ previousResults = results;
60997
61146
  await appendEventAsync(eventsPath, {
60998
- type: "pipeline:stage_skipped",
61147
+ type: "pipeline:stage_completed",
60999
61148
  runId,
61000
- message: `Stage '${stage.name}' skipped due to error`,
61149
+ message: `Stage '${stage.name}' completed`,
61001
61150
  data: {
61002
61151
  stageIndex: i2,
61003
61152
  stageName: stage.name,
61004
61153
  duration,
61005
- error: errorMessage
61154
+ resultCount: results.length
61006
61155
  }
61007
61156
  });
61157
+ } catch (error) {
61158
+ const duration = Date.now() - stageStartTime;
61159
+ const errorMessage = error instanceof Error ? error.message : String(error);
61160
+ if (effectiveStopOnError) {
61161
+ stages.push({
61162
+ name: stage.name,
61163
+ status: "failed",
61164
+ results: [],
61165
+ error: errorMessage,
61166
+ duration
61167
+ });
61168
+ await appendEventAsync(eventsPath, {
61169
+ type: "pipeline:stage_failed",
61170
+ runId,
61171
+ message: `Stage '${stage.name}' failed: ${errorMessage}`,
61172
+ data: {
61173
+ stageIndex: i2,
61174
+ stageName: stage.name,
61175
+ duration,
61176
+ error: errorMessage
61177
+ }
61178
+ });
61179
+ await appendEventAsync(eventsPath, {
61180
+ type: "pipeline:failed",
61181
+ runId,
61182
+ message: `Pipeline '${workflow.name}' failed at stage '${stage.name}'`,
61183
+ data: { failedStage: stage.name, error: errorMessage }
61184
+ });
61185
+ return {
61186
+ stages,
61187
+ totalDuration: Date.now() - startTime,
61188
+ finalResults: previousResults,
61189
+ status: "failed"
61190
+ };
61191
+ } else {
61192
+ stages.push({
61193
+ name: stage.name,
61194
+ status: "failed",
61195
+ results: [],
61196
+ error: errorMessage,
61197
+ duration
61198
+ });
61199
+ await appendEventAsync(eventsPath, {
61200
+ type: "pipeline:stage_skipped",
61201
+ runId,
61202
+ message: `Stage '${stage.name}' skipped due to error`,
61203
+ data: {
61204
+ stageIndex: i2,
61205
+ stageName: stage.name,
61206
+ duration,
61207
+ error: errorMessage
61208
+ }
61209
+ });
61210
+ }
61008
61211
  }
61009
61212
  }
61213
+ await appendEventAsync(eventsPath, {
61214
+ type: "pipeline:completed",
61215
+ runId,
61216
+ message: `Pipeline '${workflow.name}' completed`,
61217
+ data: {
61218
+ stages: stages.map((s) => ({ name: s.name, status: s.status }))
61219
+ }
61220
+ });
61221
+ return {
61222
+ stages,
61223
+ totalDuration: Date.now() - startTime,
61224
+ finalResults: previousResults,
61225
+ status: stages.some((s) => s.status === "failed") ? "partial" : "completed"
61226
+ };
61227
+ } finally {
61228
+ await flushEventLogBuffer();
61010
61229
  }
61011
- await appendEventAsync(eventsPath, {
61012
- type: "pipeline:completed",
61013
- runId,
61014
- message: `Pipeline '${workflow.name}' completed`,
61015
- data: {
61016
- stages: stages.map((s) => ({ name: s.name, status: s.status }))
61017
- }
61018
- });
61019
- return {
61020
- stages,
61021
- totalDuration: Date.now() - startTime,
61022
- finalResults: previousResults,
61023
- status: stages.some((s) => s.status === "failed") ? "partial" : "completed"
61024
- };
61025
61230
  }
61026
61231
  /**
61027
61232
  * Execute a single stage, handling fan-out for array inputs.
@@ -68717,7 +68922,7 @@ var init_deterministic_ast = __esm({
68717
68922
  });
68718
68923
 
68719
68924
  // src/runtime/dwf-state-store.ts
68720
- import { existsSync as existsSync70, mkdirSync as mkdirSync42, readFileSync as readFileSync67, unlinkSync as unlinkSync11 } from "node:fs";
68925
+ import { existsSync as existsSync70, mkdirSync as mkdirSync42, readFileSync as readFileSync68, unlinkSync as unlinkSync11 } from "node:fs";
68721
68926
  import { dirname as dirname37 } from "node:path";
68722
68927
  var DwfStore;
68723
68928
  var init_dwf_state_store = __esm({
@@ -68738,7 +68943,7 @@ var init_dwf_state_store = __esm({
68738
68943
  const path81 = this.path;
68739
68944
  try {
68740
68945
  if (!existsSync70(path81)) return void 0;
68741
- const raw = readFileSync67(path81, "utf-8");
68946
+ const raw = readFileSync68(path81, "utf-8");
68742
68947
  const parsed = JSON.parse(raw);
68743
68948
  if (!parsed || typeof parsed !== "object" || typeof parsed.runId !== "string") return void 0;
68744
68949
  return parsed;
@@ -69560,7 +69765,7 @@ var dynamic_workflow_runner_exports = {};
69560
69765
  __export(dynamic_workflow_runner_exports, {
69561
69766
  runDynamicWorkflow: () => runDynamicWorkflow
69562
69767
  });
69563
- import { readFileSync as readFileSync68 } from "node:fs";
69768
+ import { readFileSync as readFileSync69 } from "node:fs";
69564
69769
  import { join as join71 } from "node:path";
69565
69770
  function assertStructuredCloneable(value, name) {
69566
69771
  try {
@@ -69585,7 +69790,7 @@ function resolveScriptPath(workflow, cwd) {
69585
69790
  );
69586
69791
  }
69587
69792
  async function loadWorkflowModule(scriptPath) {
69588
- const scriptSource = readFileSync68(scriptPath, "utf-8");
69793
+ const scriptSource = readFileSync69(scriptPath, "utf-8");
69589
69794
  if (isDeterminismCheckEnabled()) {
69590
69795
  assertDeterministicScript(scriptSource);
69591
69796
  }
@@ -69708,7 +69913,7 @@ async function runDynamicWorkflow(input) {
69708
69913
  }
69709
69914
  function readFinalArtifact(artifactPath) {
69710
69915
  try {
69711
- return readFileSync68(artifactPath, "utf-8");
69916
+ return readFileSync69(artifactPath, "utf-8");
69712
69917
  } catch (error) {
69713
69918
  logInternalError("dynamic-workflow-runner.readFinal", error, `artifactPath=${artifactPath}`);
69714
69919
  return `(failed to read final artifact ${artifactPath})`;
@@ -75827,7 +76032,7 @@ function teamCommandContext(ctx) {
75827
76032
  }
75828
76033
  async function openTeamSettingsOverlay(ctx) {
75829
76034
  if (!ctx.hasUI) return;
75830
- const [{ updateConfig: updateConfig2, parseConfig: parseConfig2 }, { asCrewTheme: asCrewTheme2 }, { createSettingsOverlay: createSettingsOverlay2 }] = await Promise.all([
76035
+ const [{ updateConfig: updateConfig2, parseConfig: parseConfig2 }, { asCrewTheme: asCrewTheme3 }, { createSettingsOverlay: createSettingsOverlay2 }] = await Promise.all([
75831
76036
  Promise.resolve().then(() => (init_config(), config_exports)),
75832
76037
  Promise.resolve().then(() => (init_theme_adapter(), theme_adapter_exports)),
75833
76038
  Promise.resolve().then(() => (init_settings_overlay(), settings_overlay_exports))
@@ -75836,7 +76041,7 @@ async function openTeamSettingsOverlay(ctx) {
75836
76041
  const config = loaded.config;
75837
76042
  await ctx.ui.custom(
75838
76043
  (_tui, _theme, _keybindings, done) => {
75839
- const theme = asCrewTheme2(_theme);
76044
+ const theme = asCrewTheme3(_theme);
75840
76045
  const { overlay } = createSettingsOverlay2(
75841
76046
  config,
75842
76047
  theme,
@@ -79776,7 +79981,7 @@ init_orphan_worker_registry();
79776
79981
  init_peer_dep();
79777
79982
 
79778
79983
  // src/runtime/per-write-validator.ts
79779
- import { readFileSync as readFileSync17 } from "node:fs";
79984
+ import { readFileSync as readFileSync18 } from "node:fs";
79780
79985
  import { extname as pathExtname } from "node:path";
79781
79986
  function validateJson(content, _filePath) {
79782
79987
  if (content.trim() === "") return { ok: true };
@@ -79820,7 +80025,7 @@ function validateWrittenFile(filePath) {
79820
80025
  if (!validator) return null;
79821
80026
  let content;
79822
80027
  try {
79823
- content = readFileSync17(filePath, "utf-8");
80028
+ content = readFileSync18(filePath, "utf-8");
79824
80029
  } catch {
79825
80030
  return null;
79826
80031
  }
@@ -81478,6 +81683,1119 @@ function registerCrewShortcuts(pi) {
81478
81683
  }
81479
81684
  var CREW_SHORTCUT_KEYS = CREW_SHORTCUTS.map((s) => s.key);
81480
81685
 
81686
+ // src/extension/crew-vibes/config.ts
81687
+ import { existsSync as existsSync76, mkdirSync as mkdirSync44, readFileSync as readFileSync75, writeFileSync as writeFileSync33 } from "node:fs";
81688
+ import { dirname as dirname39, join as join76 } from "node:path";
81689
+
81690
+ // src/extension/crew-vibes/font-detect.ts
81691
+ import { existsSync as existsSync75, readFileSync as readFileSync74 } from "node:fs";
81692
+ import { homedir as homedir11, platform } from "node:os";
81693
+ import { join as join75 } from "node:path";
81694
+ function fontPath() {
81695
+ const os16 = platform();
81696
+ const home = homedir11();
81697
+ if (os16 === "darwin") return join75(home, "Library", "Fonts", "crew-vibes.ttf");
81698
+ if (os16 === "linux") return join75(home, ".local", "share", "fonts", "crew-vibes.ttf");
81699
+ if (os16 === "win32") {
81700
+ const local = process.env.LOCALAPPDATA ?? join75(home, "AppData", "Local");
81701
+ return join75(local, "Microsoft", "Windows", "Fonts", "crew-vibes.ttf");
81702
+ }
81703
+ return "";
81704
+ }
81705
+ var _hasFontFile = null;
81706
+ function hasCrewFontFile() {
81707
+ if (_hasFontFile !== null) return _hasFontFile;
81708
+ const p = fontPath();
81709
+ _hasFontFile = p !== "" && existsSync75(p);
81710
+ return _hasFontFile;
81711
+ }
81712
+ function isWebTerminal() {
81713
+ if (process.env.GOTTY || process.env.WEBTERM) return true;
81714
+ if (process.env.TERM === "dumb") return true;
81715
+ try {
81716
+ let pid = process.pid;
81717
+ for (let i2 = 0; i2 < 6 && pid > 1; i2++) {
81718
+ const cgroup = readFileSync74(`/proc/${pid}/cgroup`, "utf8");
81719
+ if (cgroup.includes("gotty") || cgroup.includes("wetty")) return true;
81720
+ const match = cgroup.match(/\d+:.*:(.*)/);
81721
+ const status = readFileSync74(`/proc/${pid}/status`, "utf8");
81722
+ const ppid = status.match(/^PPid:\s+(\d+)/m);
81723
+ pid = ppid ? Number.parseInt(ppid[1], 10) : 1;
81724
+ }
81725
+ } catch {
81726
+ }
81727
+ return false;
81728
+ }
81729
+
81730
+ // src/extension/crew-vibes/config.ts
81731
+ var SPEED_STATUS_ID = "pi-crew-speed";
81732
+ var CAPACITY_STATUS_ID = "pi-crew-bar";
81733
+ var PROVIDER_STATUS_ID = "pi-crew-bar";
81734
+ function resolveHome() {
81735
+ return process.env.PI_TEAMS_HOME?.trim() || process.env.HOME || process.env.USERPROFILE || "";
81736
+ }
81737
+ function configPath2() {
81738
+ return join76(resolveHome(), ".pi", "agent", "pi-crew-vibes.json");
81739
+ }
81740
+ var DEFAULT_CONFIG2 = {
81741
+ enabled: true,
81742
+ speed: {
81743
+ enabled: true,
81744
+ footer: false,
81745
+ indicator: true,
81746
+ label: "tok/s",
81747
+ renderIntervalMs: 250,
81748
+ slidingWindowMs: 1e3,
81749
+ minReliableDurationMs: 1e3,
81750
+ maxDisplayTokS: 500,
81751
+ defaultIntervalMs: 167,
81752
+ minIntervalMs: 50,
81753
+ maxIntervalMs: 250,
81754
+ scale: 6e3
81755
+ },
81756
+ capacity: {
81757
+ enabled: true,
81758
+ tokenDisplay: "tokens",
81759
+ showLabel: true,
81760
+ refreshIntervalMs: 2e3,
81761
+ labels: ["Orbit", "Cruise", "Warp", "Black Hole", "Supernova", "Big Bang"],
81762
+ icons: ["\uE710", "\uE711", "\uE712", "\uE713", "\uE714", "\uE715"],
81763
+ providerUsage: true,
81764
+ providerRefreshMs: 3e5
81765
+ }
81766
+ };
81767
+ var FALLBACK_CAPACITY_ICONS = [
81768
+ "\u25CB ",
81769
+ // ○ empty circle (lean)
81770
+ "\u25D4 ",
81771
+ // ◔ circle with dot (chonking)
81772
+ "\u25D1 ",
81773
+ // ◑ circle half filled (chonky)
81774
+ "\u25CF ",
81775
+ // ● filled circle (big chonk)
81776
+ "\u2B24 ",
81777
+ // ⬤ large filled circle (mega chonk)
81778
+ "\u2B22 "
81779
+ // ⬢ filled hexagon (oh lawd)
81780
+ ];
81781
+ function capacityIcons() {
81782
+ if (isWebTerminal()) return FALLBACK_CAPACITY_ICONS;
81783
+ return hasCrewFontFile() ? DEFAULT_CONFIG2.capacity.icons : FALLBACK_CAPACITY_ICONS;
81784
+ }
81785
+ function asRecord10(value) {
81786
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
81787
+ }
81788
+ function boolFrom(raw, fallback2) {
81789
+ return typeof raw === "boolean" ? raw : fallback2;
81790
+ }
81791
+ function stringFrom(raw, fallback2) {
81792
+ return typeof raw === "string" ? raw : fallback2;
81793
+ }
81794
+ function positiveFrom(raw, fallback2) {
81795
+ return typeof raw === "number" && Number.isFinite(raw) && raw > 0 ? raw : fallback2;
81796
+ }
81797
+ function sextet(raw, fallback2) {
81798
+ if (Array.isArray(raw) && raw.length === 6 && raw.every((entry) => typeof entry === "string")) {
81799
+ return raw;
81800
+ }
81801
+ return fallback2;
81802
+ }
81803
+ function tokenDisplayFrom(raw, fallback2) {
81804
+ return raw === "off" || raw === "tokens" || raw === "percentage" ? raw : fallback2;
81805
+ }
81806
+ function normalizeSpeed(raw) {
81807
+ const input = asRecord10(raw);
81808
+ const speed = {
81809
+ enabled: boolFrom(input.enabled, DEFAULT_CONFIG2.speed.enabled),
81810
+ footer: boolFrom(input.footer, DEFAULT_CONFIG2.speed.footer),
81811
+ indicator: boolFrom(input.indicator, DEFAULT_CONFIG2.speed.indicator),
81812
+ label: stringFrom(input.label, DEFAULT_CONFIG2.speed.label),
81813
+ renderIntervalMs: positiveFrom(input.renderIntervalMs, DEFAULT_CONFIG2.speed.renderIntervalMs),
81814
+ slidingWindowMs: positiveFrom(input.slidingWindowMs, DEFAULT_CONFIG2.speed.slidingWindowMs),
81815
+ minReliableDurationMs: positiveFrom(input.minReliableDurationMs, DEFAULT_CONFIG2.speed.minReliableDurationMs),
81816
+ maxDisplayTokS: positiveFrom(input.maxDisplayTokS, DEFAULT_CONFIG2.speed.maxDisplayTokS),
81817
+ defaultIntervalMs: positiveFrom(input.defaultIntervalMs, DEFAULT_CONFIG2.speed.defaultIntervalMs),
81818
+ minIntervalMs: positiveFrom(input.minIntervalMs, DEFAULT_CONFIG2.speed.minIntervalMs),
81819
+ maxIntervalMs: positiveFrom(input.maxIntervalMs, DEFAULT_CONFIG2.speed.maxIntervalMs),
81820
+ scale: positiveFrom(input.scale, DEFAULT_CONFIG2.speed.scale)
81821
+ };
81822
+ if (speed.minIntervalMs > speed.maxIntervalMs) {
81823
+ speed.minIntervalMs = DEFAULT_CONFIG2.speed.minIntervalMs;
81824
+ speed.maxIntervalMs = DEFAULT_CONFIG2.speed.maxIntervalMs;
81825
+ }
81826
+ return speed;
81827
+ }
81828
+ function normalizeCapacity(raw) {
81829
+ const input = asRecord10(raw);
81830
+ return {
81831
+ enabled: boolFrom(input.enabled, DEFAULT_CONFIG2.capacity.enabled),
81832
+ tokenDisplay: tokenDisplayFrom(input.tokenDisplay, DEFAULT_CONFIG2.capacity.tokenDisplay),
81833
+ showLabel: boolFrom(input.showLabel, DEFAULT_CONFIG2.capacity.showLabel),
81834
+ refreshIntervalMs: positiveFrom(input.refreshIntervalMs, DEFAULT_CONFIG2.capacity.refreshIntervalMs),
81835
+ labels: sextet(input.labels, DEFAULT_CONFIG2.capacity.labels),
81836
+ icons: sextet(input.icons, DEFAULT_CONFIG2.capacity.icons),
81837
+ providerUsage: boolFrom(input.providerUsage, DEFAULT_CONFIG2.capacity.providerUsage),
81838
+ providerRefreshMs: positiveFrom(input.providerRefreshMs, DEFAULT_CONFIG2.capacity.providerRefreshMs)
81839
+ };
81840
+ }
81841
+ function normalizeConfig(raw) {
81842
+ const input = asRecord10(raw);
81843
+ return {
81844
+ enabled: boolFrom(input.enabled, DEFAULT_CONFIG2.enabled),
81845
+ speed: normalizeSpeed(input.speed),
81846
+ capacity: normalizeCapacity(input.capacity)
81847
+ };
81848
+ }
81849
+ function loadConfig2() {
81850
+ try {
81851
+ const path81 = configPath2();
81852
+ if (!existsSync76(path81)) return normalizeConfig(void 0);
81853
+ return normalizeConfig(JSON.parse(readFileSync75(path81, "utf8")));
81854
+ } catch {
81855
+ return normalizeConfig(void 0);
81856
+ }
81857
+ }
81858
+ function saveConfig(config) {
81859
+ const path81 = configPath2();
81860
+ mkdirSync44(dirname39(path81), { recursive: true });
81861
+ writeFileSync33(path81, `${JSON.stringify(normalizeConfig(config), null, 2)}
81862
+ `);
81863
+ }
81864
+
81865
+ // src/extension/crew-vibes/provider-usage.ts
81866
+ import { readFileSync as readFileSync76 } from "node:fs";
81867
+ import { homedir as homedir12 } from "node:os";
81868
+ import { join as join77 } from "node:path";
81869
+ function withTimeout(ms, fn) {
81870
+ const controller = new AbortController();
81871
+ const timeoutId = setTimeout(() => controller.abort(), ms);
81872
+ return fn(controller.signal).finally(() => clearTimeout(timeoutId));
81873
+ }
81874
+ function piAuthPath() {
81875
+ return join77(homedir12(), ".pi", "agent", "auth.json");
81876
+ }
81877
+ function loadAnthropicToken() {
81878
+ const envToken = process.env.ANTHROPIC_OAUTH_TOKEN?.trim();
81879
+ if (envToken) return envToken;
81880
+ try {
81881
+ const data2 = JSON.parse(readFileSync76(piAuthPath(), "utf8"));
81882
+ const token = data2.anthropic?.access;
81883
+ return typeof token === "string" && token.length > 0 ? token : void 0;
81884
+ } catch {
81885
+ return void 0;
81886
+ }
81887
+ }
81888
+ function loadZaiToken() {
81889
+ const envKey = process.env.ZAI_API_KEY?.trim() || process.env.Z_AI_API_KEY?.trim();
81890
+ if (envKey) return envKey;
81891
+ try {
81892
+ const data2 = JSON.parse(readFileSync76(piAuthPath(), "utf8"));
81893
+ const key = data2["z-ai"]?.access || data2["z-ai"]?.key || data2.zai?.access || data2.zai?.key;
81894
+ return typeof key === "string" && key.length > 0 ? key : void 0;
81895
+ } catch {
81896
+ return void 0;
81897
+ }
81898
+ }
81899
+ var COPILOT_TOKEN_KEYS = ["oauth_token", "user_token", "github_token", "token"];
81900
+ function tokenFromHostEntry(entry) {
81901
+ if (!entry) return void 0;
81902
+ for (const key of COPILOT_TOKEN_KEYS) {
81903
+ const value = entry[key];
81904
+ if (typeof value === "string" && value.length > 0) return value;
81905
+ }
81906
+ return void 0;
81907
+ }
81908
+ function loadLegacyCopilotToken() {
81909
+ const configHome = process.env.XDG_CONFIG_HOME?.trim() || join77(homedir12(), ".config");
81910
+ const candidates = [join77(configHome, "github-copilot", "hosts.json"), join77(homedir12(), ".github-copilot", "hosts.json")];
81911
+ for (const hostsPath of candidates) {
81912
+ try {
81913
+ const data2 = JSON.parse(readFileSync76(hostsPath, "utf8"));
81914
+ if (!data2 || typeof data2 !== "object") continue;
81915
+ const normalized = {};
81916
+ for (const [host, entry] of Object.entries(data2)) {
81917
+ normalized[host.toLowerCase()] = entry;
81918
+ }
81919
+ const preferred = tokenFromHostEntry(normalized["github.com"]) ?? tokenFromHostEntry(normalized["api.github.com"]);
81920
+ if (preferred) return preferred;
81921
+ for (const entry of Object.values(normalized)) {
81922
+ const token = tokenFromHostEntry(entry);
81923
+ if (token) return token;
81924
+ }
81925
+ } catch {
81926
+ }
81927
+ }
81928
+ return void 0;
81929
+ }
81930
+ function loadCopilotToken() {
81931
+ const envToken = (process.env.COPILOT_GITHUB_TOKEN || process.env.GH_TOKEN || process.env.GITHUB_TOKEN || "").trim();
81932
+ if (envToken) return envToken;
81933
+ try {
81934
+ const data2 = JSON.parse(readFileSync76(piAuthPath(), "utf8"));
81935
+ const piToken = data2["github-copilot"]?.refresh || data2["github-copilot"]?.access;
81936
+ if (typeof piToken === "string" && piToken.length > 0) return piToken;
81937
+ } catch {
81938
+ }
81939
+ return loadLegacyCopilotToken();
81940
+ }
81941
+ async function fetchAnthropicUsage(token) {
81942
+ const data2 = await withTimeout(1e4, async (signal) => {
81943
+ const res = await fetch("https://api.anthropic.com/api/oauth/usage", {
81944
+ headers: {
81945
+ Authorization: `Bearer ${token}`,
81946
+ "anthropic-beta": "oauth-2025-04-20"
81947
+ },
81948
+ signal
81949
+ });
81950
+ if (!res.ok) throw new Error(`anthropic usage HTTP ${res.status}`);
81951
+ return await res.json();
81952
+ });
81953
+ return {
81954
+ fiveHourPercent: data2.five_hour?.utilization ?? 0,
81955
+ weeklyPercent: data2.seven_day?.utilization ?? 0,
81956
+ fiveHourResetAt: data2.five_hour?.resets_at ?? null,
81957
+ weeklyResetAt: data2.seven_day?.resets_at ?? null
81958
+ };
81959
+ }
81960
+ async function fetchCopilotMonthlyPercent(token) {
81961
+ const data2 = await withTimeout(1e4, async (signal) => {
81962
+ const res = await fetch("https://api.github.com/copilot_internal/user", {
81963
+ headers: {
81964
+ Authorization: `token ${token}`,
81965
+ "Editor-Version": "vscode/1.96.2",
81966
+ "User-Agent": "GitHubCopilotChat/0.26.7",
81967
+ "X-Github-Api-Version": "2025-04-01",
81968
+ Accept: "application/json"
81969
+ },
81970
+ signal
81971
+ });
81972
+ if (!res.ok) throw new Error(`copilot user HTTP ${res.status}`);
81973
+ return await res.json();
81974
+ });
81975
+ const percentRemaining = data2.quota_snapshots?.premium_interactions?.percent_remaining;
81976
+ if (typeof percentRemaining !== "number") return void 0;
81977
+ return Math.max(0, 100 - percentRemaining);
81978
+ }
81979
+ async function fetchZaiUsage(token) {
81980
+ const data2 = await withTimeout(1e4, async (signal) => {
81981
+ const res = await fetch("https://api.z.ai/api/monitor/usage/quota/limit", {
81982
+ method: "GET",
81983
+ headers: {
81984
+ Authorization: `Bearer ${token}`,
81985
+ Accept: "application/json"
81986
+ },
81987
+ signal
81988
+ });
81989
+ if (!res.ok) throw new Error(`z.ai usage HTTP ${res.status}`);
81990
+ return await res.json();
81991
+ });
81992
+ if (!data2.success || data2.code !== 200) throw new Error(data2.msg || "z.ai API error");
81993
+ const limits = data2.data?.limits ?? [];
81994
+ let tokensPercent = 0;
81995
+ let monthlyPercent = 0;
81996
+ let tokensResetAt = null;
81997
+ let monthlyResetAt = null;
81998
+ for (const limit of limits) {
81999
+ const pct = limit.percentage ?? 0;
82000
+ const resetIso = typeof limit.nextResetTime === "number" ? new Date(limit.nextResetTime).toISOString() : typeof limit.nextResetTime === "string" ? limit.nextResetTime : null;
82001
+ if (limit.type === "TOKENS_LIMIT") {
82002
+ tokensPercent = pct;
82003
+ tokensResetAt = resetIso;
82004
+ } else if (limit.type === "TIME_LIMIT") {
82005
+ monthlyPercent = pct;
82006
+ monthlyResetAt = resetIso;
82007
+ }
82008
+ }
82009
+ return {
82010
+ providerName: "z.ai",
82011
+ fiveHourPercent: tokensPercent,
82012
+ fiveHourResetAt: tokensResetAt,
82013
+ weeklyPercent: monthlyPercent,
82014
+ weeklyResetAt: monthlyResetAt
82015
+ };
82016
+ }
82017
+ var cachedUsage = null;
82018
+ var cachedAt = 0;
82019
+ function clearProviderUsageCache() {
82020
+ cachedUsage = null;
82021
+ cachedAt = 0;
82022
+ }
82023
+ async function fetchProviderUsage(maxAgeMs = 3e5) {
82024
+ if (cachedUsage !== null && Date.now() - cachedAt < maxAgeMs) {
82025
+ return cachedUsage;
82026
+ }
82027
+ try {
82028
+ const anthropicToken = loadAnthropicToken();
82029
+ if (anthropicToken) {
82030
+ const base = await fetchAnthropicUsage(anthropicToken);
82031
+ const usage = {
82032
+ providerName: "Claude",
82033
+ fiveHourPercent: base.fiveHourPercent,
82034
+ fiveHourResetAt: base.fiveHourResetAt,
82035
+ weeklyPercent: base.weeklyPercent,
82036
+ weeklyResetAt: base.weeklyResetAt
82037
+ };
82038
+ cachedUsage = usage;
82039
+ cachedAt = Date.now();
82040
+ return usage;
82041
+ }
82042
+ const zaiToken = loadZaiToken();
82043
+ if (zaiToken) {
82044
+ const usage = await fetchZaiUsage(zaiToken);
82045
+ usage.providerName = "z.ai";
82046
+ cachedUsage = usage;
82047
+ cachedAt = Date.now();
82048
+ return usage;
82049
+ }
82050
+ const copilotToken = loadCopilotToken();
82051
+ if (copilotToken) {
82052
+ const monthlyPercent = await fetchCopilotMonthlyPercent(copilotToken);
82053
+ if (monthlyPercent !== void 0) {
82054
+ const usage = {
82055
+ providerName: "Copilot",
82056
+ fiveHourPercent: 0,
82057
+ fiveHourResetAt: null,
82058
+ weeklyPercent: monthlyPercent,
82059
+ weeklyResetAt: null,
82060
+ copilotMonthlyPercent: monthlyPercent
82061
+ };
82062
+ cachedUsage = usage;
82063
+ cachedAt = Date.now();
82064
+ return usage;
82065
+ }
82066
+ }
82067
+ return null;
82068
+ } catch {
82069
+ return null;
82070
+ }
82071
+ }
82072
+
82073
+ // src/extension/crew-vibes/figures.ts
82074
+ var BRAILLE_FRAMES = [
82075
+ "\u280B ",
82076
+ // ⠋
82077
+ "\u2819 ",
82078
+ // ⠙
82079
+ "\u2839 ",
82080
+ // ⠹
82081
+ "\u2838 ",
82082
+ // ⠸
82083
+ "\u283C ",
82084
+ // ⠼
82085
+ "\u2834 ",
82086
+ // ⠴
82087
+ "\u2826 ",
82088
+ // ⠦
82089
+ "\u2827 ",
82090
+ // ⠧
82091
+ "\u2807 ",
82092
+ // ⠇
82093
+ "\u280F "
82094
+ // ⠏
82095
+ ];
82096
+ var PUA_CREW_FRAMES = [
82097
+ "\uE700 ",
82098
+ "\uE701 ",
82099
+ "\uE702 ",
82100
+ "\uE703 ",
82101
+ "\uE704 ",
82102
+ "\uE705 ",
82103
+ "\uE706 ",
82104
+ "\uE707 ",
82105
+ "\uE708 ",
82106
+ "\uE709 ",
82107
+ "\uE70A ",
82108
+ "\uE70B ",
82109
+ "\uE70C ",
82110
+ "\uE70D ",
82111
+ "\uE70E ",
82112
+ "\uE70F "
82113
+ ];
82114
+ function crewFrames(style = "pua") {
82115
+ if (style === "pua" && isWebTerminal()) return BRAILLE_FRAMES;
82116
+ return style === "pua" ? PUA_CREW_FRAMES : BRAILLE_FRAMES;
82117
+ }
82118
+ function intervalForSpeed(config, speed) {
82119
+ if (speed === null || !Number.isFinite(speed) || speed <= 0) return config.defaultIntervalMs;
82120
+ return Math.max(config.minIntervalMs, Math.min(config.maxIntervalMs, Math.round(config.scale / speed)));
82121
+ }
82122
+ function capacityIndex(percent, levels = 6) {
82123
+ if (percent === null || percent === void 0 || !Number.isFinite(percent)) return 0;
82124
+ return Math.max(0, Math.min(levels - 1, Math.floor(Math.max(0, Math.min(100, percent)) / 100 * levels)));
82125
+ }
82126
+ function isDangerStage(index, levels) {
82127
+ return index >= Math.max(0, levels - 2);
82128
+ }
82129
+
82130
+ // src/extension/crew-vibes/render.ts
82131
+ function formatCount(value) {
82132
+ if (value < 1e3) return value.toString();
82133
+ if (value < 1e4) return `${(value / 1e3).toFixed(1)}k`;
82134
+ if (value < 1e6) return `${Math.round(value / 1e3)}k`;
82135
+ if (value < 1e7) return `${(value / 1e6).toFixed(1)}M`;
82136
+ return `${Math.round(value / 1e6)}M`;
82137
+ }
82138
+ function asCrewTheme2(theme) {
82139
+ if (theme && typeof theme === "object" && typeof theme.fg === "function") {
82140
+ return theme;
82141
+ }
82142
+ return void 0;
82143
+ }
82144
+ function getCapacityUsage(ctx) {
82145
+ const fn = ctx.getContextUsage;
82146
+ const usage = typeof fn === "function" ? fn.call(ctx) : null;
82147
+ return {
82148
+ tokens: typeof usage?.tokens === "number" && Number.isFinite(usage.tokens) ? usage.tokens : null,
82149
+ percent: typeof usage?.percent === "number" && Number.isFinite(usage.percent) ? usage.percent : null
82150
+ };
82151
+ }
82152
+ function formatSpeed(config, speed) {
82153
+ return speed === null ? `-- ${config.label}` : `${speed.toFixed(1)} ${config.label}`;
82154
+ }
82155
+ function renderSpeedFooter(theme, config, speed) {
82156
+ const value = speed === null ? "--" : speed.toFixed(1);
82157
+ const valueTone = speed === null ? "dim" : "accent";
82158
+ const styled = theme ? `${theme.fg(valueTone, value)} ${theme.fg("dim", config.label)}` : `${value} ${config.label}`;
82159
+ return styled;
82160
+ }
82161
+ function renderWorkingMessage(theme, config, speed) {
82162
+ const left = "Working";
82163
+ const speedText = theme ? `${theme.fg(speed === null ? "dim" : "accent", speed === null ? "--" : speed.toFixed(1))} ${theme.fg("dim", config.label)}` : `${speed === null ? "--" : speed.toFixed(1)} ${config.label}`;
82164
+ return theme ? `${theme.fg("muted", left)} ${speedText}` : `${left} ${speedText}`;
82165
+ }
82166
+ function crewIndicatorFrames(theme) {
82167
+ const frames = crewFrames();
82168
+ if (!theme) return [...frames];
82169
+ return frames.map((frame) => theme.fg("accent", frame));
82170
+ }
82171
+ function formatCapacityPrefix(config, usage) {
82172
+ const display = config.tokenDisplay;
82173
+ if (display === "off") return "";
82174
+ if (display === "percentage") {
82175
+ return `${usage.percent === null ? "?" : Math.round(Math.max(0, Math.min(999, usage.percent)))}% `;
82176
+ }
82177
+ return `${usage.tokens === null ? "?" : formatCount(usage.tokens)} `;
82178
+ }
82179
+ function colorStage(theme, index, levels, text) {
82180
+ if (!theme || text.length === 0) return text;
82181
+ return theme.fg(isDangerStage(index, levels) ? "error" : "success", text);
82182
+ }
82183
+ function renderCapacity(theme, config, usage) {
82184
+ const icons = capacityIcons();
82185
+ const levels = icons.length;
82186
+ const index = capacityIndex(usage.percent, levels);
82187
+ const icon = icons[index] ?? icons[0];
82188
+ const label = config.labels[index] ?? config.labels[0];
82189
+ const prefix = theme ? theme.fg("muted", formatCapacityPrefix(config, usage)) : formatCapacityPrefix(config, usage);
82190
+ const coloredIcon = colorStage(theme, index, levels, icon);
82191
+ const afterIcon = config.showLabel ? ` ${colorStage(theme, index, levels, label)}` : " ";
82192
+ return `${prefix}${coloredIcon}${afterIcon}`;
82193
+ }
82194
+ function setSpeedStatus(ctx, config, text) {
82195
+ if (!ctx?.hasUI) return;
82196
+ if (!config.enabled || !config.speed.enabled || !config.speed.footer) {
82197
+ ctx.ui.setStatus(SPEED_STATUS_ID, void 0);
82198
+ return;
82199
+ }
82200
+ ctx.ui.setStatus(SPEED_STATUS_ID, text);
82201
+ }
82202
+ function setCapacityStatus(ctx, config, text) {
82203
+ if (!ctx?.hasUI) return;
82204
+ if (!config.enabled || !config.capacity.enabled) {
82205
+ ctx.ui.setStatus(CAPACITY_STATUS_ID, void 0);
82206
+ return;
82207
+ }
82208
+ ctx.ui.setStatus(CAPACITY_STATUS_ID, text);
82209
+ }
82210
+ function clearVibesStatus(ctx) {
82211
+ if (!ctx?.hasUI) return;
82212
+ ctx.ui.setStatus(SPEED_STATUS_ID, void 0);
82213
+ ctx.ui.setStatus(CAPACITY_STATUS_ID, void 0);
82214
+ ctx.ui.setStatus(PROVIDER_STATUS_ID, void 0);
82215
+ if (ctx.ui.setWorkingIndicator) ctx.ui.setWorkingIndicator();
82216
+ if (ctx.ui.setWorkingMessage) ctx.ui.setWorkingMessage();
82217
+ }
82218
+ function formatResetTimer(resetAt) {
82219
+ if (!resetAt) return null;
82220
+ const diffMs = new Date(resetAt).getTime() - Date.now();
82221
+ if (diffMs < 0) return null;
82222
+ const mins = Math.floor(diffMs / 6e4);
82223
+ if (mins < 60) return `${mins}m`;
82224
+ const hours = Math.floor(mins / 60);
82225
+ if (hours < 48) {
82226
+ const remMins = mins % 60;
82227
+ return remMins > 0 ? `${hours}h${remMins}m` : `${hours}h`;
82228
+ }
82229
+ const days = Math.floor(hours / 24);
82230
+ const remHours = hours % 24;
82231
+ return remHours > 0 ? `${days}d${remHours}h` : `${days}d`;
82232
+ }
82233
+ function renderBar(percent, width = 8) {
82234
+ const clamped = Math.max(0, Math.min(100, percent));
82235
+ const filled = Math.round(clamped / 100 * width);
82236
+ return `${"\u2501".repeat(filled)}${"\u2504".repeat(width - filled)}`;
82237
+ }
82238
+ function renderProviderUsage(theme, usage) {
82239
+ if (!usage) return void 0;
82240
+ const parts = [];
82241
+ if (usage.providerName) {
82242
+ const nameText = usage.providerName;
82243
+ parts.push(theme ? theme.fg("muted", nameText) : nameText);
82244
+ }
82245
+ const fiveHourBar = renderBar(usage.fiveHourPercent);
82246
+ const fiveHourRounded = Math.round(usage.fiveHourPercent);
82247
+ const fiveHourReset = formatResetTimer(usage.fiveHourResetAt);
82248
+ const fiveHourText = `5h ${fiveHourBar} ${fiveHourRounded}%${fiveHourReset ? " " + fiveHourReset : ""}`;
82249
+ const fiveHourColor = usage.fiveHourPercent >= 80 ? "error" : "accent";
82250
+ parts.push(theme ? theme.fg(fiveHourColor, fiveHourText) : fiveHourText);
82251
+ const weeklyBar = renderBar(usage.weeklyPercent);
82252
+ const weeklyRounded = Math.round(usage.weeklyPercent);
82253
+ const weeklyReset = formatResetTimer(usage.weeklyResetAt);
82254
+ const weeklyText = `Wk ${weeklyBar} ${weeklyRounded}%${weeklyReset ? " " + weeklyReset : ""}`;
82255
+ parts.push(theme ? theme.fg("dim", weeklyText) : weeklyText);
82256
+ if (typeof usage.copilotMonthlyPercent === "number" && Number.isFinite(usage.copilotMonthlyPercent)) {
82257
+ const monthlyRounded = Math.round(usage.copilotMonthlyPercent);
82258
+ const monthlyText = `Mo: ${monthlyRounded}%`;
82259
+ parts.push(theme ? theme.fg("dim", monthlyText) : monthlyText);
82260
+ }
82261
+ return parts.join(" ");
82262
+ }
82263
+
82264
+ // src/extension/crew-vibes/speed.ts
82265
+ var COMPACTION_THRESHOLD = 5e3;
82266
+ function estimateTokensFromDelta(text) {
82267
+ if (!text) return 0;
82268
+ const matches = text.match(/\w+|[^\s\w]/g);
82269
+ return matches ? matches.length : 0;
82270
+ }
82271
+ var TokenSpeedEngine = class {
82272
+ _isStreaming = false;
82273
+ _tokenCount = 0;
82274
+ _startTime = 0;
82275
+ _endTime = 0;
82276
+ _events = [];
82277
+ _windowStartIndex = 0;
82278
+ _lastStableTokS = 0;
82279
+ _lastUsageOutput = 0;
82280
+ _config;
82281
+ constructor(config) {
82282
+ this._config = config;
82283
+ }
82284
+ updateConfig(config) {
82285
+ this._config = config;
82286
+ }
82287
+ get isStreaming() {
82288
+ return this._isStreaming;
82289
+ }
82290
+ get tokenCount() {
82291
+ return this._tokenCount;
82292
+ }
82293
+ get elapsedMs() {
82294
+ if (this._startTime === 0) return 0;
82295
+ return this._isStreaming ? Date.now() - this._startTime : this._endTime - this._startTime;
82296
+ }
82297
+ get avgTokS() {
82298
+ const elapsedSec = this.elapsedMs / 1e3;
82299
+ if (elapsedSec <= 0) return 0;
82300
+ return this._tokenCount / elapsedSec;
82301
+ }
82302
+ sanitizeTokS(value, durationMs = this.elapsedMs) {
82303
+ if (value === null || !Number.isFinite(value) || value <= 0) return null;
82304
+ if (durationMs < this._config.minReliableDurationMs) return null;
82305
+ if (value > this._config.maxDisplayTokS) return null;
82306
+ return value;
82307
+ }
82308
+ get tokS() {
82309
+ const candidate = this.rawTokS;
82310
+ const stable = this.sanitizeTokS(candidate);
82311
+ if (stable !== null) this._lastStableTokS = stable;
82312
+ return this._lastStableTokS;
82313
+ }
82314
+ get rawTokS() {
82315
+ if (this.elapsedMs < this._config.slidingWindowMs) return this.avgTokS;
82316
+ if (!this._isStreaming) return this.avgTokS;
82317
+ const now = Date.now();
82318
+ const windowStart = now - this._config.slidingWindowMs;
82319
+ while (this._windowStartIndex < this._events.length && this._events[this._windowStartIndex].time < windowStart) {
82320
+ this._windowStartIndex++;
82321
+ }
82322
+ if (this._windowStartIndex >= this._events.length) return this.avgTokS;
82323
+ let windowTokenCount = 0;
82324
+ for (let i2 = this._windowStartIndex; i2 < this._events.length; i2++) {
82325
+ windowTokenCount += this._events[i2].tokens;
82326
+ }
82327
+ if (windowTokenCount === 0) return this.avgTokS;
82328
+ const windowDuration = (now - this._events[this._windowStartIndex].time) / 1e3;
82329
+ if (windowDuration <= 0) return 0;
82330
+ return windowTokenCount / windowDuration;
82331
+ }
82332
+ start() {
82333
+ this._tokenCount = 0;
82334
+ this._isStreaming = true;
82335
+ this._startTime = Date.now();
82336
+ this._endTime = this._startTime;
82337
+ this._events = [];
82338
+ this._windowStartIndex = 0;
82339
+ this._lastStableTokS = 0;
82340
+ this._lastUsageOutput = 0;
82341
+ }
82342
+ stop() {
82343
+ this._isStreaming = false;
82344
+ this._endTime = Date.now();
82345
+ this._events = [];
82346
+ this._windowStartIndex = 0;
82347
+ }
82348
+ recordDelta(delta, usageOutput) {
82349
+ if (!this._isStreaming) return;
82350
+ if (usageOutput !== void 0 && usageOutput > 0) {
82351
+ const increment = usageOutput - this._lastUsageOutput;
82352
+ this._lastUsageOutput = usageOutput;
82353
+ this.recordTokens(Math.max(0, increment));
82354
+ return;
82355
+ }
82356
+ this.recordTokens(estimateTokensFromDelta(delta));
82357
+ }
82358
+ reconcileTotal(tokens) {
82359
+ if (tokens > 0) this._tokenCount = tokens;
82360
+ }
82361
+ recordTokens(tokens) {
82362
+ if (!this._isStreaming || tokens <= 0) return;
82363
+ this._tokenCount += tokens;
82364
+ this._events.push({ time: Date.now(), tokens });
82365
+ if (this._windowStartIndex >= COMPACTION_THRESHOLD) this.compact();
82366
+ }
82367
+ compact() {
82368
+ if (this._windowStartIndex === 0) return;
82369
+ this._events = this._events.slice(this._windowStartIndex);
82370
+ this._windowStartIndex = 0;
82371
+ }
82372
+ };
82373
+ function isSuccessfulStop(stopReason) {
82374
+ return stopReason !== "error" && stopReason !== "aborted";
82375
+ }
82376
+ var SpeedTracker = class {
82377
+ engine;
82378
+ lastStableTokS = null;
82379
+ sessionOutputTokens = 0;
82380
+ sessionDurationMs = 0;
82381
+ constructor(config) {
82382
+ this.engine = new TokenSpeedEngine(config);
82383
+ }
82384
+ updateConfig(config) {
82385
+ this.engine.updateConfig(config);
82386
+ }
82387
+ get isStreaming() {
82388
+ return this.engine.isStreaming;
82389
+ }
82390
+ get lastTokS() {
82391
+ return this.lastStableTokS;
82392
+ }
82393
+ resetSession() {
82394
+ this.sessionOutputTokens = 0;
82395
+ this.sessionDurationMs = 0;
82396
+ }
82397
+ startMessage() {
82398
+ this.engine.start();
82399
+ }
82400
+ recordDelta(delta, usageOutput) {
82401
+ this.engine.recordDelta(delta, usageOutput);
82402
+ }
82403
+ stopMessage() {
82404
+ if (this.engine.isStreaming) this.engine.stop();
82405
+ }
82406
+ liveTokS() {
82407
+ const speed = this.engine.tokS;
82408
+ return speed > 0 ? speed : this.lastStableTokS;
82409
+ }
82410
+ sessionAvgTokS() {
82411
+ return this.sessionDurationMs > 0 ? this.sessionOutputTokens / (this.sessionDurationMs / 1e3) : null;
82412
+ }
82413
+ finishMessage(outputTokens, stopReason) {
82414
+ if (!this.engine.isStreaming) return null;
82415
+ this.engine.reconcileTotal(outputTokens);
82416
+ const durationMs = this.engine.elapsedMs;
82417
+ const tokens = this.engine.tokenCount;
82418
+ const rawAvgTokS = durationMs > 0 ? tokens / (durationMs / 1e3) : null;
82419
+ const tokS = this.engine.sanitizeTokS(rawAvgTokS, durationMs);
82420
+ this.lastStableTokS = tokS;
82421
+ this.engine.stop();
82422
+ if (tokS !== null && isSuccessfulStop(stopReason)) {
82423
+ this.sessionOutputTokens += tokens;
82424
+ this.sessionDurationMs += durationMs;
82425
+ }
82426
+ return { outputTokens: tokens, durationMs, tokS };
82427
+ }
82428
+ };
82429
+ var SpeedAnimator = class {
82430
+ from = null;
82431
+ target = null;
82432
+ startedAt = 0;
82433
+ durationMs;
82434
+ constructor(durationMs) {
82435
+ this.durationMs = durationMs;
82436
+ }
82437
+ updateDuration(durationMs) {
82438
+ this.durationMs = durationMs;
82439
+ }
82440
+ reset(value = null, now = Date.now()) {
82441
+ this.from = value;
82442
+ this.target = value;
82443
+ this.startedAt = now;
82444
+ }
82445
+ setTarget(target, now = Date.now()) {
82446
+ if (target === null) {
82447
+ this.reset(null, now);
82448
+ return null;
82449
+ }
82450
+ const current2 = this.value(now);
82451
+ if (current2 === null) {
82452
+ this.reset(target, now);
82453
+ return target;
82454
+ }
82455
+ if (this.target !== null && Math.abs(target - this.target) < 0.05) return current2;
82456
+ this.from = current2;
82457
+ this.target = target;
82458
+ this.startedAt = now;
82459
+ return current2;
82460
+ }
82461
+ value(now = Date.now()) {
82462
+ if (this.target === null) return null;
82463
+ if (this.from === null || this.durationMs <= 0) return this.target;
82464
+ const progress = Math.max(0, Math.min(1, (now - this.startedAt) / this.durationMs));
82465
+ if (progress >= 1) {
82466
+ this.from = this.target;
82467
+ return this.target;
82468
+ }
82469
+ return this.from + (this.target - this.from) * progress;
82470
+ }
82471
+ isAnimating(now = Date.now()) {
82472
+ return this.target !== null && this.from !== null && Math.abs(this.target - this.from) >= 0.05 && now - this.startedAt < this.durationMs;
82473
+ }
82474
+ };
82475
+
82476
+ // src/extension/crew-vibes/cat-frames.ts
82477
+ var CAT_FRAMES2 = [
82478
+ // Frame 0 — standing
82479
+ ["\u2584\u2584 \u2588\u2588\u2588\u2584 ", "\u2580\u2580\u2588\u2588\u2588\u2588\u2588\u2584", " \u2588\u2588\u2580\u2588\u2588 ", " \u2580\u2580 \u2580\u2580"],
82480
+ // Frame 1 — step 1
82481
+ [" \u2588\u2588\u2588\u2588 ", " \u2588\u2588\u2588\u2588\u2588\u2584", "\u2584\u2588\u2588\u2588\u2588\u2588\u2588\u2580", " \u2588\u2588 \u2580\u2588\u2584"],
82482
+ // Frame 2 — step 2
82483
+ [" \u2584\u2588\u2588\u2588\u2588\u2588\u2588", " \u2588\u2588\u2588\u2588\u2588\u2580 ", "\u2588\u2588\u2588\u2588\u2588\u2588\u2588 ", "\u2580\u2588\u2588\u2588\u2588\u2588\u2588 "],
82484
+ // Frame 3 — step 3
82485
+ [" \u2588\u2588\u2588\u2588 ", "\u2584\u2588\u2588\u2588\u2588\u2588\u2584 ", "\u2580\u2588\u2588\u2588\u2588\u2588\u2588\u2584", "\u2584\u2588\u2580 \u2588\u2588 "]
82486
+ ];
82487
+ var CAT_FRAME_COUNT = CAT_FRAMES2.length;
82488
+
82489
+ // src/extension/crew-vibes/index.ts
82490
+ function isAssistantMessage(message) {
82491
+ return typeof message === "object" && message !== null && message.role === "assistant";
82492
+ }
82493
+ function assistantUsageOutput(message) {
82494
+ const usage = message.usage;
82495
+ const output = usage?.output;
82496
+ return typeof output === "number" && Number.isFinite(output) ? output : void 0;
82497
+ }
82498
+ function assistantStopReason(message) {
82499
+ const reason = message.stopReason;
82500
+ return typeof reason === "string" ? reason : void 0;
82501
+ }
82502
+ function assistantEventType(event) {
82503
+ return typeof event.type === "string" ? event.type : void 0;
82504
+ }
82505
+ function registerCrewVibes(pi) {
82506
+ let config = loadConfig2();
82507
+ let lastRenderedAt = 0;
82508
+ let currentIntervalMs = 0;
82509
+ const speedTracker = new SpeedTracker(config.speed);
82510
+ const footerAnimator = new SpeedAnimator(config.speed.renderIntervalMs);
82511
+ let liveTimer;
82512
+ let footerTimer;
82513
+ let capacityTimer;
82514
+ let providerTimer;
82515
+ let lastProviderText;
82516
+ let catFrameIndex = 0;
82517
+ function visibleLen(text) {
82518
+ return text.replace(/\x1b\[[0-9;]*m/g, "").length;
82519
+ }
82520
+ function spreadLine(left, right) {
82521
+ const cols = process.stdout.columns || 120;
82522
+ const padding = Math.max(2, cols - visibleLen(left) - visibleLen(right));
82523
+ return left + "\xA0".repeat(padding) + right;
82524
+ }
82525
+ function themeOf(ctx) {
82526
+ return asCrewTheme2(ctx.hasUI ? ctx.ui.theme : void 0);
82527
+ }
82528
+ function publishCapacity(ctx) {
82529
+ if (!ctx?.hasUI) return;
82530
+ if (!config.enabled || !config.capacity.enabled) {
82531
+ setCapacityStatus(ctx, config, void 0);
82532
+ return;
82533
+ }
82534
+ const capText = renderCapacity(themeOf(ctx), config.capacity, getCapacityUsage(ctx));
82535
+ const combined = lastProviderText ? spreadLine(capText, lastProviderText) : capText;
82536
+ setCapacityStatus(ctx, config, combined);
82537
+ }
82538
+ function publishSpeedFooter(ctx, speed = footerAnimator.value()) {
82539
+ if (!config.enabled || !config.speed.enabled || !config.speed.footer) {
82540
+ setSpeedStatus(ctx, config, void 0);
82541
+ return;
82542
+ }
82543
+ setSpeedStatus(ctx, config, renderSpeedFooter(themeOf(ctx), config.speed, speed));
82544
+ }
82545
+ function applyIndicator(ctx, speed, force = false) {
82546
+ if (!ctx.hasUI || !ctx.ui.setWorkingIndicator) return;
82547
+ if (!config.enabled || !config.speed.enabled || !config.speed.indicator) {
82548
+ ctx.ui.setWorkingIndicator();
82549
+ return;
82550
+ }
82551
+ const next = intervalForSpeed(config.speed, speed);
82552
+ if (!force && Math.abs(next - currentIntervalMs) < 10) return;
82553
+ ctx.ui.setWorkingIndicator({
82554
+ frames: crewIndicatorFrames(themeOf(ctx)),
82555
+ intervalMs: next
82556
+ });
82557
+ currentIntervalMs = next;
82558
+ }
82559
+ function renderWorking(ctx, speed) {
82560
+ if (!config.enabled || !config.speed.enabled || !ctx.hasUI) return;
82561
+ ctx.ui.setWorkingMessage(renderWorkingMessage(themeOf(ctx), config.speed, speed));
82562
+ }
82563
+ function stopLiveTimer() {
82564
+ if (!liveTimer) return;
82565
+ clearInterval(liveTimer);
82566
+ liveTimer = void 0;
82567
+ }
82568
+ function stopFooterTimer() {
82569
+ if (!footerTimer) return;
82570
+ clearInterval(footerTimer);
82571
+ footerTimer = void 0;
82572
+ }
82573
+ function stopCapacityTimer() {
82574
+ if (!capacityTimer) return;
82575
+ clearInterval(capacityTimer);
82576
+ capacityTimer = void 0;
82577
+ }
82578
+ function stopProviderTimer() {
82579
+ if (!providerTimer) return;
82580
+ clearInterval(providerTimer);
82581
+ providerTimer = void 0;
82582
+ }
82583
+ function startLiveTimer(ctx) {
82584
+ if (liveTimer || !ctx.hasUI) return;
82585
+ liveTimer = setInterval(() => {
82586
+ if (!config.enabled || !config.speed.enabled || !speedTracker.isStreaming) {
82587
+ stopLiveTimer();
82588
+ return;
82589
+ }
82590
+ const speed = speedTracker.liveTokS();
82591
+ applyIndicator(ctx, speed);
82592
+ renderWorking(ctx, speed);
82593
+ if (isWebTerminal()) {
82594
+ catFrameIndex = (catFrameIndex + 1) % CAT_FRAMES2.length;
82595
+ ctx.ui.setWidget("crew-vibes-cat", [...CAT_FRAMES2[catFrameIndex]], { placement: "aboveEditor" });
82596
+ }
82597
+ }, config.speed.renderIntervalMs);
82598
+ liveTimer.unref?.();
82599
+ }
82600
+ function startFooterTimer(ctx) {
82601
+ if (footerTimer || !ctx.hasUI) return;
82602
+ footerTimer = setInterval(() => {
82603
+ if (!config.enabled || !config.speed.enabled) {
82604
+ stopFooterTimer();
82605
+ return;
82606
+ }
82607
+ publishSpeedFooter(ctx);
82608
+ if (!footerAnimator.isAnimating()) stopFooterTimer();
82609
+ }, config.speed.renderIntervalMs);
82610
+ footerTimer.unref?.();
82611
+ }
82612
+ function startCapacityTimer(ctx) {
82613
+ if (capacityTimer) return;
82614
+ const interval = Math.max(250, config.capacity.refreshIntervalMs);
82615
+ capacityTimer = setInterval(() => publishCapacity(ctx), interval);
82616
+ capacityTimer.unref?.();
82617
+ }
82618
+ function startProviderTimer(ctx) {
82619
+ if (providerTimer) return;
82620
+ if (!config.capacity.providerUsage) return;
82621
+ const interval = Math.max(1e4, config.capacity.providerRefreshMs);
82622
+ async function tick() {
82623
+ if (!config.enabled || !config.capacity.providerUsage) {
82624
+ stopProviderTimer();
82625
+ return;
82626
+ }
82627
+ try {
82628
+ const usage = await fetchProviderUsage(config.capacity.providerRefreshMs);
82629
+ lastProviderText = renderProviderUsage(themeOf(ctx), usage);
82630
+ publishCapacity(ctx);
82631
+ } catch {
82632
+ lastProviderText = void 0;
82633
+ publishCapacity(ctx);
82634
+ }
82635
+ }
82636
+ tick();
82637
+ providerTimer = setInterval(tick, interval);
82638
+ providerTimer.unref?.();
82639
+ }
82640
+ function resetWorking(ctx) {
82641
+ applyIndicator(ctx, null, true);
82642
+ renderWorking(ctx, speedTracker.lastTokS);
82643
+ }
82644
+ function applyConfig(ctx) {
82645
+ saveConfig(config);
82646
+ speedTracker.updateConfig(config.speed);
82647
+ footerAnimator.updateDuration(config.speed.renderIntervalMs);
82648
+ if (!config.enabled) {
82649
+ stopLiveTimer();
82650
+ stopFooterTimer();
82651
+ stopCapacityTimer();
82652
+ stopProviderTimer();
82653
+ clearVibesStatus(ctx);
82654
+ return;
82655
+ }
82656
+ publishCapacity(ctx);
82657
+ publishSpeedFooter(ctx);
82658
+ if (config.capacity.providerUsage) startProviderTimer(ctx);
82659
+ }
82660
+ pi.on("session_start", (_event, ctx) => {
82661
+ stopLiveTimer();
82662
+ stopFooterTimer();
82663
+ stopCapacityTimer();
82664
+ stopProviderTimer();
82665
+ config = loadConfig2();
82666
+ speedTracker.updateConfig(config.speed);
82667
+ footerAnimator.updateDuration(config.speed.renderIntervalMs);
82668
+ speedTracker.resetSession();
82669
+ footerAnimator.reset(null);
82670
+ clearProviderUsageCache();
82671
+ if (!config.enabled) {
82672
+ clearVibesStatus(ctx);
82673
+ return;
82674
+ }
82675
+ publishCapacity(ctx);
82676
+ publishSpeedFooter(ctx);
82677
+ startCapacityTimer(ctx);
82678
+ startProviderTimer(ctx);
82679
+ applyIndicator(ctx, null, true);
82680
+ });
82681
+ pi.on("agent_start", (_event, ctx) => {
82682
+ if (!config.enabled) return;
82683
+ resetWorking(ctx);
82684
+ });
82685
+ pi.on("turn_start", (_event, ctx) => {
82686
+ if (!config.enabled) return;
82687
+ resetWorking(ctx);
82688
+ });
82689
+ pi.on("message_start", (event, ctx) => {
82690
+ if (!config.enabled || !config.speed.enabled || !isAssistantMessage(event.message)) return;
82691
+ speedTracker.startMessage();
82692
+ footerAnimator.reset(speedTracker.lastTokS);
82693
+ startLiveTimer(ctx);
82694
+ lastRenderedAt = 0;
82695
+ if (isWebTerminal() && ctx.hasUI) {
82696
+ ctx.ui.setWidget("crew-vibes-cat", [...CAT_FRAMES2[0]], { placement: "aboveEditor" });
82697
+ }
82698
+ });
82699
+ pi.on("message_update", (event, ctx) => {
82700
+ if (!config.enabled || !config.speed.enabled || !isAssistantMessage(event.message) || !speedTracker.isStreaming) return;
82701
+ const ev = event.assistantMessageEvent;
82702
+ const type = assistantEventType(ev);
82703
+ if (type === "text_delta" || type === "thinking_delta") {
82704
+ const delta = ev.delta ?? "";
82705
+ speedTracker.recordDelta(delta, assistantUsageOutput(event.message));
82706
+ }
82707
+ if (type === "start") resetWorking(ctx);
82708
+ const now = Date.now();
82709
+ if (now - lastRenderedAt < config.speed.renderIntervalMs && type !== "done") return;
82710
+ lastRenderedAt = now;
82711
+ const speed = speedTracker.liveTokS();
82712
+ applyIndicator(ctx, speed);
82713
+ renderWorking(ctx, speed);
82714
+ });
82715
+ pi.on("message_end", (event, ctx) => {
82716
+ if (!isAssistantMessage(event.message)) return;
82717
+ publishCapacity(ctx);
82718
+ if (!config.enabled || !config.speed.enabled || !speedTracker.isStreaming) return;
82719
+ const completed = speedTracker.finishMessage(assistantUsageOutput(event.message) ?? 0, assistantStopReason(event.message));
82720
+ if (!completed) return;
82721
+ footerAnimator.setTarget(speedTracker.sessionAvgTokS());
82722
+ publishSpeedFooter(ctx);
82723
+ startFooterTimer(ctx);
82724
+ applyIndicator(ctx, speedTracker.lastTokS);
82725
+ if (isWebTerminal() && ctx.hasUI) {
82726
+ ctx.ui.setWidget("crew-vibes-cat", void 0);
82727
+ }
82728
+ });
82729
+ pi.on("turn_end", () => {
82730
+ speedTracker.stopMessage();
82731
+ stopLiveTimer();
82732
+ });
82733
+ pi.on("agent_end", (_event, ctx) => {
82734
+ speedTracker.stopMessage();
82735
+ stopLiveTimer();
82736
+ if (ctx && config.enabled && ctx.hasUI) {
82737
+ applyIndicator(ctx, speedTracker.lastTokS);
82738
+ ctx.ui.setWorkingMessage();
82739
+ if (isWebTerminal()) ctx.ui.setWidget("crew-vibes-cat", void 0);
82740
+ }
82741
+ });
82742
+ pi.on("model_select", (_event, ctx) => publishCapacity(ctx));
82743
+ pi.on("session_compact", (_event, ctx) => publishCapacity(ctx));
82744
+ pi.on("session_tree", (_event, ctx) => publishCapacity(ctx));
82745
+ pi.on("session_shutdown", (_event, ctx) => {
82746
+ stopLiveTimer();
82747
+ stopFooterTimer();
82748
+ stopCapacityTimer();
82749
+ stopProviderTimer();
82750
+ clearVibesStatus(ctx);
82751
+ if (ctx.hasUI) ctx.ui.setWidget("crew-vibes-cat", void 0);
82752
+ });
82753
+ async function handleCommand(args, ctx) {
82754
+ const tokens = args.trim().split(/\s+/).filter(Boolean);
82755
+ const [first, second] = tokens;
82756
+ const mutate = (next) => {
82757
+ config = next;
82758
+ applyConfig(ctx);
82759
+ };
82760
+ if (!first) {
82761
+ const speed = speedTracker.liveTokS();
82762
+ const usage = getCapacityUsage(ctx);
82763
+ const stage = config.capacity.icons.length ? config.capacity.labels[Math.max(
82764
+ 0,
82765
+ Math.min(
82766
+ config.capacity.labels.length - 1,
82767
+ Math.floor((usage.percent ?? 0) / 100 * config.capacity.labels.length)
82768
+ )
82769
+ )] : "?";
82770
+ ctx.ui.notify(
82771
+ `crew-vibes: ${config.enabled ? "on" : "off"} \xB7 speed ${config.speed.enabled ? "on" : "off"} (${formatSpeed(config.speed, speed)}) \xB7 capacity ${config.capacity.enabled ? "on" : "off"} (${stage})`,
82772
+ "info"
82773
+ );
82774
+ return;
82775
+ }
82776
+ if (first === "on" || first === "off") {
82777
+ mutate({ ...config, enabled: first === "on" });
82778
+ ctx.ui.notify(`crew-vibes ${first === "on" ? "enabled" : "disabled"}`, "info");
82779
+ return;
82780
+ }
82781
+ if (first === "speed" && (second === "on" || second === "off")) {
82782
+ mutate({ ...config, speed: { ...config.speed, enabled: second === "on" } });
82783
+ ctx.ui.notify(`crew-vibes speed ${second === "on" ? "enabled" : "disabled"}`, "info");
82784
+ return;
82785
+ }
82786
+ if (first === "capacity" && (second === "on" || second === "off")) {
82787
+ mutate({ ...config, capacity: { ...config.capacity, enabled: second === "on" } });
82788
+ ctx.ui.notify(`crew-vibes capacity ${second === "on" ? "enabled" : "disabled"}`, "info");
82789
+ return;
82790
+ }
82791
+ ctx.ui.notify("Usage: /team-vibes [on|off|speed on|off|capacity on|off]", "error");
82792
+ }
82793
+ pi.registerCommand("team-vibes", {
82794
+ description: "Toggle crew-vibes speed + context meters (on/off, speed, capacity)",
82795
+ handler: handleCommand
82796
+ });
82797
+ }
82798
+
81481
82799
  // src/extension/cross-extension-rpc.ts
81482
82800
  init_safe_paths();
81483
82801
 
@@ -83617,6 +84935,11 @@ Subagent may need manual intervention.`
83617
84935
  registerContextStatusInjection(pi, {
83618
84936
  enabled: loadConfig(process.cwd()).config.reliability?.ambientStatusInjection !== false
83619
84937
  });
84938
+ try {
84939
+ registerCrewVibes(pi);
84940
+ } catch (err2) {
84941
+ console.warn("[pi-crew] crew-vibes initialization failed:", err2 instanceof Error ? err2.message : err2);
84942
+ }
83620
84943
  }
83621
84944
 
83622
84945
  // index.bundle.ts