pi-crew 0.9.64 → 0.9.66

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 (43) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/README.md +46 -1
  3. package/dist/index.mjs +423 -330
  4. package/package.json +3 -2
  5. package/scripts/pty_probe.py +10 -8
  6. package/skills/real-test-pi-crew/SKILL.md +6 -6
  7. package/src/config/config.ts +19 -3
  8. package/src/config/types.ts +2 -0
  9. package/src/extension/team-tool/cancel.ts +34 -0
  10. package/src/extension/team-tool/dispatch/manage.ts +7 -4
  11. package/src/extension/team-tool/explain.ts +3 -1
  12. package/src/extension/team-tool/lifecycle-actions.ts +4 -1
  13. package/src/extension/team-tool-types.ts +2 -0
  14. package/src/observability/event-to-metric.ts +29 -0
  15. package/src/observability/metrics-primitives.ts +41 -3
  16. package/src/runtime/README.md +1 -1
  17. package/src/runtime/broker/crew-broker.ts +0 -16
  18. package/src/runtime/effectiveness.ts +23 -1
  19. package/src/runtime/merge-gate.ts +202 -0
  20. package/src/runtime/model/model-fallback.ts +11 -0
  21. package/src/runtime/model/provider-extensions.ts +31 -12
  22. package/src/runtime/output/output-validator.ts +34 -6
  23. package/src/runtime/output/progress-tracker.ts +3 -33
  24. package/src/runtime/scheduling/scheduler.ts +67 -19
  25. package/src/runtime/scratchpad/engine.ts +40 -2
  26. package/src/runtime/scratchpad/snapshot-hmac.ts +161 -0
  27. package/src/runtime/team-runner.ts +128 -203
  28. package/src/schema/team-tool-schema.ts +2 -0
  29. package/src/teams/discover-teams.ts +2 -0
  30. package/src/teams/team-config.ts +7 -0
  31. package/src/teams/team-serializer.ts +1 -0
  32. package/src/ui/mascot.ts +1 -14
  33. package/teams/default.team.md +1 -0
  34. package/teams/fast-fix.team.md +1 -0
  35. package/src/observability/event-bus.ts +0 -86
  36. package/src/plugins/plugin-define.ts +0 -6
  37. package/src/plugins/plugin-registry.ts +0 -32
  38. package/src/plugins/plugins/index.ts +0 -3
  39. package/src/plugins/plugins/nextjs.ts +0 -19
  40. package/src/plugins/plugins/vite.ts +0 -10
  41. package/src/plugins/plugins/vitest.ts +0 -9
  42. package/src/runtime/child-pi/child-pi-pool.ts +0 -68
  43. package/src/runtime/iteration-hooks.ts +0 -305
package/dist/index.mjs CHANGED
@@ -10934,11 +10934,15 @@ function updateConfig(patch, options = {}) {
10934
10934
  for (const unset of options.unsetPaths) unsetPath(raw, unset);
10935
10935
  merged = parseConfig(raw);
10936
10936
  }
10937
+ const normalizedCurrent = parseConfig(current);
10938
+ if (JSON.stringify(merged) === JSON.stringify(normalizedCurrent)) {
10939
+ return { path: filePath, config: merged, written: false };
10940
+ }
10937
10941
  fs5.mkdirSync(path5.dirname(filePath), { recursive: true });
10938
10942
  atomicWriteFile(filePath, `${JSON.stringify(merged, null, 2)}
10939
10943
  `);
10940
10944
  invalidateConfigCache();
10941
- return { path: filePath, config: merged };
10945
+ return { path: filePath, config: merged, written: true };
10942
10946
  });
10943
10947
  }
10944
10948
  function updateAutonomousConfig(patch) {
@@ -10953,11 +10957,15 @@ function updateAutonomousConfig(patch) {
10953
10957
  throw new Error(`Could not update pi-crew config: ${message}`);
10954
10958
  }
10955
10959
  const currentAutonomous = current.autonomous && typeof current.autonomous === "object" && !Array.isArray(current.autonomous) ? current.autonomous : {};
10956
- current.autonomous = { ...currentAutonomous, ...patch };
10960
+ const next = { ...current, autonomous: { ...currentAutonomous, ...patch } };
10961
+ if (JSON.stringify(next) === JSON.stringify(current)) {
10962
+ return { path: filePath, config: parseConfig(current), written: false };
10963
+ }
10964
+ current.autonomous = next.autonomous;
10957
10965
  atomicWriteFile(filePath, `${JSON.stringify(current, null, 2)}
10958
10966
  `);
10959
10967
  invalidateConfigCache();
10960
- return { path: filePath, config: parseConfig(current) };
10968
+ return { path: filePath, config: parseConfig(current), written: true };
10961
10969
  });
10962
10970
  }
10963
10971
  var CONFIG_CACHE_TTL_MS, configCache, configCacheTtlMsOverride, KNOWN_TOP_LEVEL_KEYS, LIMIT_CEILINGS, DANGEROUS_OBJECT_KEYS;
@@ -11513,10 +11521,16 @@ function discoverProviderExtensions(settingsPath2) {
11513
11521
  const npmBase = path9.join(baseDir, "npm", "node_modules");
11514
11522
  for (const spec of settings.packages ?? []) {
11515
11523
  if (typeof spec !== "string") continue;
11516
- if (!spec.startsWith("npm:")) continue;
11517
- const pkgName = spec.slice(4);
11518
- const pkgDir = path9.join(npmBase, pkgName);
11524
+ let pkgDir;
11525
+ if (spec.startsWith("npm:")) {
11526
+ pkgDir = path9.join(npmBase, spec.slice(4));
11527
+ } else if (spec.startsWith("./") || spec.startsWith("../") || path9.isAbsolute(spec)) {
11528
+ pkgDir = path9.resolve(baseDir, spec);
11529
+ } else {
11530
+ continue;
11531
+ }
11519
11532
  if (!fs9.existsSync(pkgDir)) continue;
11533
+ if (path9.resolve(pkgDir) === path9.resolve(packageRoot())) continue;
11520
11534
  const entryPath = resolvePackageEntry(pkgDir);
11521
11535
  if (entryPath) out.push({ spec, entryPath });
11522
11536
  }
@@ -12442,6 +12456,8 @@ function parseTeamFile(filePath, source) {
12442
12456
  defaultWorkflow: frontmatter.defaultWorkflow || frontmatter.workflow || void 0,
12443
12457
  workspaceMode: frontmatter.workspaceMode?.trim() === "worktree" ? "worktree" : "single",
12444
12458
  maxConcurrency: frontmatter.maxConcurrency ? Number.parseInt(frontmatter.maxConcurrency, 10) : void 0,
12459
+ // observability defaults ON ("luôn hoạt động"); explicit `observability: false` disables.
12460
+ observability: frontmatter.observability === void 0 ? true : frontmatter.observability !== "false",
12445
12461
  routing: triggers || useWhen || avoidWhen || cost || category ? { triggers, useWhen, avoidWhen, cost, category } : void 0
12446
12462
  };
12447
12463
  } catch {
@@ -22404,7 +22420,18 @@ var init_model_fallback = __esm({
22404
22420
  /context[_ ]?length[_ ]?exceeded/i,
22405
22421
  /safety/i,
22406
22422
  /is[_ ]?overloaded/i,
22407
- /\b408\b/
22423
+ /\b408\b/,
22424
+ //
22425
+ // EPIPE / broken-pipe. In the child-pi worker path this typically means
22426
+ // the child `pi` process exited (crash or early exit) while the parent
22427
+ // was still writing to its stdin — spawning a fresh child on the next
22428
+ // model in the fallback chain usually recovers. In the network path it
22429
+ // is a transient pipe close. Both are retryable on a different model.
22430
+ // See docs/failure-mode-inventory.md EPIPE gap; NON_RETRYABLE patterns
22431
+ // (auth/billing) are checked first, so an auth error mentioning EPIPE
22432
+ // stays non-retryable.
22433
+ /epipe/i,
22434
+ /broken pipe/i
22408
22435
  ];
22409
22436
  NON_RETRYABLE_MODEL_FAILURE_PATTERNS = [
22410
22437
  /auth(?:entication)?/i,
@@ -23842,10 +23869,12 @@ var init_team_tool_schema = __esm({
23842
23869
  })
23843
23870
  ),
23844
23871
  budgetTotal: Type.Optional(
23872
+ // Empty-string unset marker accepted (Tier-9: models emit "" when unset).
23845
23873
  // 0 accepted as "unset/disabled" (models emit 0 for off); still rejects 1-999
23846
23874
  // as the MISCONFIGURATION GUARD against typo'd silent-abort configs.
23847
23875
  Type.Union(
23848
23876
  [
23877
+ Type.Literal(""),
23849
23878
  Type.Literal(0),
23850
23879
  Type.Number({
23851
23880
  minimum: 1e3
@@ -35626,23 +35655,46 @@ function parseIntervalMs(s) {
35626
35655
  }
35627
35656
  return ms;
35628
35657
  }
35658
+ function cronFieldMatches(value, field, min, max, names) {
35659
+ let normalized = field.trim().toUpperCase();
35660
+ if (names) {
35661
+ for (const name of Object.keys(names).sort((a, b) => b.length - a.length)) {
35662
+ normalized = normalized.split(name).join(String(names[name]));
35663
+ }
35664
+ }
35665
+ if (min === 0 && max === 6) normalized = normalized.replace(/\b7\b/g, "0");
35666
+ const matched = /* @__PURE__ */ new Set();
35667
+ for (const rawPart of normalized.split(",")) {
35668
+ const part = rawPart.trim();
35669
+ if (part === "") return false;
35670
+ const stepMatch = part.match(/^(.*)\/(\d+)$/);
35671
+ const step = stepMatch ? Number.parseInt(stepMatch[2], 10) : 1;
35672
+ if (!Number.isFinite(step) || step < 1) return false;
35673
+ const rangeStr = stepMatch ? stepMatch[1] : part;
35674
+ let lo;
35675
+ let hi;
35676
+ if (rangeStr === "*") {
35677
+ lo = min;
35678
+ hi = max;
35679
+ } else if (/^\d+$/.test(rangeStr)) {
35680
+ lo = Number.parseInt(rangeStr, 10);
35681
+ hi = stepMatch ? max : lo;
35682
+ } else {
35683
+ const rm = rangeStr.match(/^(\d+)-(\d+)$/);
35684
+ if (!rm) return false;
35685
+ lo = Number.parseInt(rm[1], 10);
35686
+ hi = Number.parseInt(rm[2], 10);
35687
+ }
35688
+ for (let v = lo; v <= hi; v += step) {
35689
+ if (v >= min && v <= max) matched.add(v);
35690
+ }
35691
+ }
35692
+ return matched.has(value);
35693
+ }
35629
35694
  function nextCronDate(spec, from) {
35630
35695
  const parts = spec.split(/\s+/);
35631
35696
  if (parts.length < 5) return { error: "Invalid cron expression" };
35632
35697
  const [minStr, hourStr, domStr, monthStr, dowStr] = parts;
35633
- function matchField(value, str, min, max) {
35634
- if (str === "*") return true;
35635
- const n = parseInt(str, 10);
35636
- if (!Number.isNaN(n) && n >= min && n <= max && n === value) return true;
35637
- if (/^\d+-\d+$/.test(str)) {
35638
- const [a, b] = str.split("-").map(Number);
35639
- return value >= a && value <= b;
35640
- }
35641
- if (str.includes(",")) {
35642
- return str.split(",").some((part) => matchField(value, part.trim(), min, max));
35643
- }
35644
- return false;
35645
- }
35646
35698
  let cursor = new Date(from.getTime());
35647
35699
  cursor.setSeconds(0, 0);
35648
35700
  cursor = new Date(cursor.getTime() + 6e4);
@@ -35653,7 +35705,7 @@ function nextCronDate(spec, from) {
35653
35705
  const dom = cursor.getUTCDate();
35654
35706
  const month = cursor.getUTCMonth() + 1;
35655
35707
  const dow = cursor.getUTCDay();
35656
- if (matchField(min, minStr, 0, 59) && matchField(hour, hourStr, 0, 23) && matchField(dom, domStr, 1, 31) && matchField(month, monthStr, 1, 12) && matchField(dow, dowStr, 0, 6)) {
35708
+ if (cronFieldMatches(min, minStr, 0, 59) && cronFieldMatches(hour, hourStr, 0, 23) && cronFieldMatches(dom, domStr, 1, 31) && cronFieldMatches(month, monthStr, 1, 12, CRON_MONTH_NAMES) && cronFieldMatches(dow, dowStr, 0, 6, CRON_DOW_NAMES)) {
35657
35709
  return cursor;
35658
35710
  }
35659
35711
  cursor = new Date(cursor.getTime() + 6e4);
@@ -35731,7 +35783,7 @@ function humanizeSchedule(spec) {
35731
35783
  }
35732
35784
  return "unknown schedule";
35733
35785
  }
35734
- var CrewScheduler;
35786
+ var CrewScheduler, CRON_DOW_NAMES, CRON_MONTH_NAMES;
35735
35787
  var init_scheduler = __esm({
35736
35788
  "src/runtime/scheduling/scheduler.ts"() {
35737
35789
  "use strict";
@@ -35886,6 +35938,21 @@ var init_scheduler = __esm({
35886
35938
  throw new Error(`Invalid schedule "${s}". Use "5m", "+10m", ISO timestamp, or cron expression.`);
35887
35939
  }
35888
35940
  };
35941
+ CRON_DOW_NAMES = { SUN: 0, MON: 1, TUE: 2, WED: 3, THU: 4, FRI: 5, SAT: 6 };
35942
+ CRON_MONTH_NAMES = {
35943
+ JAN: 1,
35944
+ FEB: 2,
35945
+ MAR: 3,
35946
+ APR: 4,
35947
+ MAY: 5,
35948
+ JUN: 6,
35949
+ JUL: 7,
35950
+ AUG: 8,
35951
+ SEP: 9,
35952
+ OCT: 10,
35953
+ NOV: 11,
35954
+ DEC: 12
35955
+ };
35889
35956
  }
35890
35957
  });
35891
35958
 
@@ -36511,8 +36578,13 @@ var cancel_exports = {};
36511
36578
  __export(cancel_exports, {
36512
36579
  abortOwned: () => abortOwned,
36513
36580
  handleCancel: () => handleCancel,
36514
- handleRetry: () => handleRetry
36581
+ handleRetry: () => handleRetry,
36582
+ retryShortCircuitsCompleted: () => retryShortCircuitsCompleted
36515
36583
  });
36584
+ function retryShortCircuitsCompleted(runStatus, tasks, targetTaskId) {
36585
+ if (runStatus !== "completed") return false;
36586
+ return !tasks.some((task) => (targetTaskId ? task.id === targetTaskId : true) && RETRYABLE_STATUSES.has(task.status));
36587
+ }
36516
36588
  function abortOwned(runId, taskIds, ctx, force) {
36517
36589
  const runCwd = locateRunCwd(runId, ctx.cwd);
36518
36590
  if (!runCwd) return { abortedIds: [], missingIds: taskIds ?? [], foreignIds: [] };
@@ -36585,6 +36657,13 @@ async function handleRetry(params, ctx, deps) {
36585
36657
  );
36586
36658
  }
36587
36659
  const targetTaskId = typeof params.taskId === "string" ? params.taskId : void 0;
36660
+ if (retryShortCircuitsCompleted(loaded.manifest.status, loaded.tasks, targetTaskId)) {
36661
+ return result(
36662
+ `Run ${loaded.manifest.runId} is already completed; retry only applies to failed/cancelled runs.`,
36663
+ { action: "retry", status: "error", runId: loaded.manifest.runId },
36664
+ true
36665
+ );
36666
+ }
36588
36667
  return withRunLockSync(loaded.manifest, () => {
36589
36668
  const retryableStatuses = /* @__PURE__ */ new Set(["failed", "cancelled"]);
36590
36669
  const matchingTasks = loaded.tasks.filter((task) => {
@@ -36789,6 +36868,7 @@ async function handleCancel(params, ctx, deps) {
36789
36868
  });
36790
36869
  });
36791
36870
  }
36871
+ var RETRYABLE_STATUSES;
36792
36872
  var init_cancel = __esm({
36793
36873
  "src/extension/team-tool/cancel.ts"() {
36794
36874
  "use strict";
@@ -36808,6 +36888,7 @@ var init_cancel = __esm({
36808
36888
  init_intent_policy();
36809
36889
  init_param_error();
36810
36890
  init_run_not_found();
36891
+ RETRYABLE_STATUSES = /* @__PURE__ */ new Set(["failed", "cancelled"]);
36811
36892
  }
36812
36893
  });
36813
36894
 
@@ -38785,7 +38866,9 @@ function handleWorktrees(params, ctx) {
38785
38866
  { action: "worktrees", status: "error" },
38786
38867
  true
38787
38868
  );
38788
- const loaded = loadRunManifestById(ctx.cwd, params.runId);
38869
+ const runCwd = locateRunCwd(params.runId, ctx.cwd);
38870
+ if (!runCwd) return result(`Run '${params.runId}' not found.${RUN_NOT_FOUND_HINT}`, { action: "worktrees", status: "error" }, true);
38871
+ const loaded = loadRunManifestById(runCwd, params.runId);
38789
38872
  if (!loaded) return result(`Run '${params.runId}' not found.${RUN_NOT_FOUND_HINT}`, { action: "worktrees", status: "error" }, true);
38790
38873
  const withWorktrees = loaded.tasks.filter((task) => task.worktree);
38791
38874
  const lines = [
@@ -39310,6 +39393,7 @@ var init_lifecycle_actions = __esm({
39310
39393
  init_run_export();
39311
39394
  init_run_import();
39312
39395
  init_run_maintenance();
39396
+ init_team_tool2();
39313
39397
  init_context();
39314
39398
  init_intent_policy();
39315
39399
  init_param_error();
@@ -39565,6 +39649,7 @@ function serializeTeam(team) {
39565
39649
  team.defaultWorkflow ? `defaultWorkflow: ${team.defaultWorkflow}` : void 0,
39566
39650
  team.workspaceMode ? `workspaceMode: ${team.workspaceMode}` : void 0,
39567
39651
  team.maxConcurrency !== void 0 ? `maxConcurrency: ${team.maxConcurrency}` : void 0,
39652
+ team.observability !== void 0 ? `observability: ${team.observability}` : void 0,
39568
39653
  line2("triggers", team.routing?.triggers),
39569
39654
  line2("useWhen", team.routing?.useWhen),
39570
39655
  line2("avoidWhen", team.routing?.avoidWhen),
@@ -41145,10 +41230,13 @@ async function handleManageDomain(params, ctx) {
41145
41230
  unsetPaths
41146
41231
  });
41147
41232
  return result(
41148
- ["Updated pi-crew config.", `Path: ${saved.path}`, "Effective config:", JSON.stringify(saved.config, null, 2)].join(
41149
- "\n"
41150
- ),
41151
- { action: "config", status: "ok" }
41233
+ [
41234
+ saved.written ? "Updated pi-crew config." : "Config unchanged (no effective changes).",
41235
+ `Path: ${saved.path}`,
41236
+ "Effective config:",
41237
+ JSON.stringify(saved.config, null, 2)
41238
+ ].join("\n"),
41239
+ { action: "config", status: "ok", written: saved.written }
41152
41240
  );
41153
41241
  } catch (error) {
41154
41242
  const message = error instanceof Error ? error.message : String(error);
@@ -47774,7 +47862,8 @@ function handleExplain(params, cwd) {
47774
47862
  if (!params.runId) {
47775
47863
  return result3("explain requires runId", { action: "explain", status: "error" }, true);
47776
47864
  }
47777
- const loaded = loadRunManifestById(cwd, params.runId);
47865
+ const runCwd = locateRunCwd(params.runId, cwd);
47866
+ const loaded = runCwd ? loadRunManifestById(runCwd, params.runId) : void 0;
47778
47867
  if (!loaded) {
47779
47868
  return result3(`Run '${params.runId}' not found.${RUN_NOT_FOUND_HINT}`, { action: "explain", status: "error" }, true);
47780
47869
  }
@@ -47833,6 +47922,7 @@ var init_explain = __esm({
47833
47922
  "src/extension/team-tool/explain.ts"() {
47834
47923
  "use strict";
47835
47924
  init_state_store();
47925
+ init_team_tool2();
47836
47926
  init_run_not_found();
47837
47927
  }
47838
47928
  });
@@ -48455,6 +48545,9 @@ function taskHasObservableWorkerActivity(task) {
48455
48545
  (task.agentProgress?.toolCount ?? 0) > 0 || task.usage || task.transcriptArtifact || task.modelAttempts?.some((attempt) => attempt.success) || task.jsonEvents
48456
48546
  );
48457
48547
  }
48548
+ function taskHasEmptyResult(task) {
48549
+ return Boolean(task.resultArtifact && task.resultArtifact.sizeBytes === 0);
48550
+ }
48458
48551
  function resolveEffectivenessGuardMode(runtimeConfig, manifest) {
48459
48552
  const configured = runtimeConfig?.effectivenessGuard;
48460
48553
  if (configured === "off" || configured === "warn" || configured === "block" || configured === "fail") return configured;
@@ -48463,7 +48556,7 @@ function resolveEffectivenessGuardMode(runtimeConfig, manifest) {
48463
48556
  }
48464
48557
  function evaluateRunEffectiveness(input) {
48465
48558
  const completedTasks = input.tasks.filter((task) => task.status === "completed");
48466
- const noObservedWorkTasks = completedTasks.filter((task) => !taskHasObservableWorkerActivity(task));
48559
+ const noObservedWorkTasks = completedTasks.filter((task) => !taskHasObservableWorkerActivity(task) || taskHasEmptyResult(task));
48467
48560
  const needsAttentionTasks = input.tasks.filter((task) => task.agentProgress?.activityState === "needs_attention");
48468
48561
  const workerExecution = input.executeWorkers ? "enabled" : "disabled/scaffold";
48469
48562
  const guardMode = resolveEffectivenessGuardMode(input.runtimeConfig, input.manifest);
@@ -49325,112 +49418,6 @@ var init_correlation = __esm({
49325
49418
  }
49326
49419
  });
49327
49420
 
49328
- // src/plugins/plugin-registry.ts
49329
- var PluginRegistry;
49330
- var init_plugin_registry = __esm({
49331
- "src/plugins/plugin-registry.ts"() {
49332
- "use strict";
49333
- PluginRegistry = class {
49334
- plugins = [];
49335
- register(plugin) {
49336
- this.plugins.push(plugin);
49337
- }
49338
- activePlugins(allDeps) {
49339
- return this.plugins.filter(
49340
- (p) => p.enablers.some((enabler) => {
49341
- if (enabler.endsWith("/")) {
49342
- return allDeps.some((d) => d.startsWith(enabler));
49343
- }
49344
- return allDeps.includes(enabler);
49345
- })
49346
- );
49347
- }
49348
- allPlugins() {
49349
- return [...this.plugins];
49350
- }
49351
- };
49352
- }
49353
- });
49354
-
49355
- // src/plugins/plugin-define.ts
49356
- function definePlugin(spec) {
49357
- return spec;
49358
- }
49359
- var init_plugin_define = __esm({
49360
- "src/plugins/plugin-define.ts"() {
49361
- "use strict";
49362
- }
49363
- });
49364
-
49365
- // src/plugins/plugins/nextjs.ts
49366
- var NextJsPlugin;
49367
- var init_nextjs = __esm({
49368
- "src/plugins/plugins/nextjs.ts"() {
49369
- "use strict";
49370
- init_plugin_define();
49371
- NextJsPlugin = definePlugin({
49372
- name: "nextjs",
49373
- enablers: ["next"],
49374
- entryPatterns: [
49375
- "src/app/**/*.{ts,tsx}",
49376
- "src/pages/**/*.{ts,tsx}",
49377
- "src/app/**/page.{ts,tsx}",
49378
- "src/app/**/layout.{ts,tsx}",
49379
- "src/app/**/route.{ts,tsx}",
49380
- "middleware.{ts,js}",
49381
- "next.config.{ts,js,mjs}"
49382
- ],
49383
- configPatterns: ["next.config.{ts,js,mjs}"],
49384
- toolingDependencies: ["next", "@next/font", "@next/mdx"],
49385
- pathAliases: [["~", "src"]],
49386
- virtualModulePrefixes: ["next:"]
49387
- });
49388
- }
49389
- });
49390
-
49391
- // src/plugins/plugins/vite.ts
49392
- var VitePlugin;
49393
- var init_vite = __esm({
49394
- "src/plugins/plugins/vite.ts"() {
49395
- "use strict";
49396
- init_plugin_define();
49397
- VitePlugin = definePlugin({
49398
- name: "vite",
49399
- enablers: ["vite", "rolldown-vite"],
49400
- entryPatterns: ["src/main.{ts,tsx,js,jsx}", "src/index.{ts,tsx,js,jsx}", "index.html"],
49401
- configPatterns: ["vite.config.{ts,js,mts,mjs}"],
49402
- toolingDependencies: ["vite"],
49403
- virtualModulePrefixes: ["virtual:"]
49404
- });
49405
- }
49406
- });
49407
-
49408
- // src/plugins/plugins/vitest.ts
49409
- var VitestPlugin;
49410
- var init_vitest = __esm({
49411
- "src/plugins/plugins/vitest.ts"() {
49412
- "use strict";
49413
- init_plugin_define();
49414
- VitestPlugin = definePlugin({
49415
- name: "vitest",
49416
- enablers: ["vitest"],
49417
- entryPatterns: ["**/*.test.{ts,tsx}", "**/*.spec.{ts,tsx}", "src/**/*.test.{ts,tsx}", "src/**/*.spec.{ts,tsx}"],
49418
- configPatterns: ["vitest.config.{ts,js,mjs}", "vite.config.ts"],
49419
- toolingDependencies: ["vitest"]
49420
- });
49421
- }
49422
- });
49423
-
49424
- // src/plugins/plugins/index.ts
49425
- var init_plugins = __esm({
49426
- "src/plugins/plugins/index.ts"() {
49427
- "use strict";
49428
- init_nextjs();
49429
- init_vite();
49430
- init_vitest();
49431
- }
49432
- });
49433
-
49434
49421
  // src/runtime/task-health.ts
49435
49422
  function scoreToGrade(score) {
49436
49423
  if (score >= 90) return "A";
@@ -50470,6 +50457,166 @@ var init_group_join = __esm({
50470
50457
  }
50471
50458
  });
50472
50459
 
50460
+ // src/runtime/scheduling/task-graph-scheduler.ts
50461
+ function buildTaskGraphIndex(tasks) {
50462
+ const cached2 = taskGraphIndexCache.get(tasks);
50463
+ if (cached2) return cached2;
50464
+ const fresh = {
50465
+ doneSteps: new Set(
50466
+ tasks.filter((task) => task.status === "completed").map((task) => task.stepId).filter((id) => id !== void 0)
50467
+ ),
50468
+ idMap: new Map(tasks.map((task) => [task.id, task])),
50469
+ stepToTaskId: new Map(
50470
+ tasks.map((task) => [task.stepId, task.id]).filter((entry) => entry[0] !== void 0)
50471
+ )
50472
+ };
50473
+ taskGraphIndexCache.set(tasks, fresh);
50474
+ return fresh;
50475
+ }
50476
+ function dependencySatisfied(task, doneStepIds, idMap, stepMap) {
50477
+ return task.dependsOn.every((dependency) => {
50478
+ if (doneStepIds.has(dependency)) return true;
50479
+ const taskId = stepMap.get(dependency) ?? dependency;
50480
+ return idMap.get(taskId)?.status === "completed";
50481
+ });
50482
+ }
50483
+ function withQueue(task, index) {
50484
+ let resolvedQueue;
50485
+ if (task.status === "queued") {
50486
+ const isReady = dependencySatisfied(task, index.doneSteps, index.idMap, index.stepToTaskId);
50487
+ resolvedQueue = isReady ? "ready" : "blocked";
50488
+ } else if (task.status === "running") {
50489
+ resolvedQueue = "running";
50490
+ } else if (task.status === "completed" || task.status === "skipped" || task.status === "needs_attention") {
50491
+ resolvedQueue = "done";
50492
+ } else {
50493
+ resolvedQueue = "blocked";
50494
+ }
50495
+ if (task.graph && task.graph.queue === resolvedQueue) {
50496
+ return task;
50497
+ }
50498
+ return {
50499
+ ...task,
50500
+ graph: task.graph ? { ...task.graph, queue: resolvedQueue } : task.graph
50501
+ };
50502
+ }
50503
+ function ensureIndex(tasks, index) {
50504
+ return index ?? buildTaskGraphIndex(tasks);
50505
+ }
50506
+ function refreshTaskGraphQueues(tasks, index) {
50507
+ const resolved = ensureIndex(tasks, index);
50508
+ return tasks.map((task) => withQueue(task, resolved));
50509
+ }
50510
+ function taskGraphSnapshot(tasks, index) {
50511
+ const refreshed = refreshTaskGraphQueues(tasks, index);
50512
+ return {
50513
+ ready: refreshed.filter((task) => task.status === "queued" && task.graph?.queue === "ready").map((task) => task.id),
50514
+ blocked: refreshed.filter((task) => task.status === "queued" && task.graph?.queue === "blocked").map((task) => task.id),
50515
+ running: refreshed.filter((task) => task.status === "running").map((task) => task.id),
50516
+ done: refreshed.filter((task) => task.status === "completed" || task.status === "skipped").map((task) => task.id),
50517
+ failed: refreshed.filter((task) => task.status === "failed").map((task) => task.id),
50518
+ cancelled: refreshed.filter((task) => task.status === "cancelled").map((task) => task.id)
50519
+ };
50520
+ }
50521
+ var taskGraphIndexCache;
50522
+ var init_task_graph_scheduler = __esm({
50523
+ "src/runtime/scheduling/task-graph-scheduler.ts"() {
50524
+ "use strict";
50525
+ taskGraphIndexCache = /* @__PURE__ */ new WeakMap();
50526
+ }
50527
+ });
50528
+
50529
+ // src/runtime/merge-gate.ts
50530
+ function isNonTerminalTaskStatus(status) {
50531
+ return status === "queued" || status === "running" || status === "waiting";
50532
+ }
50533
+ function safeFinishedAt(task) {
50534
+ if (!task.finishedAt) return -Infinity;
50535
+ const ms = new Date(task.finishedAt).getTime();
50536
+ return Number.isNaN(ms) ? Infinity : ms;
50537
+ }
50538
+ function isMalformedFinishedAtReplacement(currentTime, updatedTime) {
50539
+ return !Number.isFinite(currentTime) && Number.isFinite(updatedTime);
50540
+ }
50541
+ function statusMergeKey(from, to) {
50542
+ return `${from}->${to}`;
50543
+ }
50544
+ function shouldMergeTaskUpdate(current, updated) {
50545
+ if (REJECTED_STATUS_MERGE_TRANSITIONS.has(statusMergeKey(current.status, updated.status))) return false;
50546
+ if (current.status === updated.status && updated.status === "running" && current.resultArtifact && !updated.resultArtifact)
50547
+ return false;
50548
+ if (current.status === updated.status && current.status === "completed" && current.resultArtifact && !updated.resultArtifact)
50549
+ return false;
50550
+ if (current.finishedAt !== void 0 && updated.finishedAt !== void 0) {
50551
+ const currentTime = safeFinishedAt(current);
50552
+ const updatedTime = safeFinishedAt(updated);
50553
+ if (!Number.isFinite(currentTime)) {
50554
+ console.warn(`[merge-gate] Task ${current.id} has malformed finishedAt: ${current.finishedAt}`);
50555
+ }
50556
+ if (isMalformedFinishedAtReplacement(currentTime, updatedTime)) {
50557
+ return true;
50558
+ }
50559
+ if (updatedTime < currentTime) return false;
50560
+ }
50561
+ if (!updated.finishedAt && !isNonTerminalTaskStatus(updated.status)) return false;
50562
+ const hasMeaningfulUpdate = updated.status !== current.status || updated.finishedAt !== current.finishedAt || updated.startedAt !== current.startedAt || Boolean(updated.resultArtifact) !== Boolean(current.resultArtifact) || Boolean(updated.resultArtifact) && updated.resultArtifact !== current.resultArtifact || Boolean(updated.error) || Boolean(updated.modelAttempts?.length) || Boolean(updated.usage) || Boolean(updated.attempts?.length) || updated.heartbeat?.lastSeenAt !== current.heartbeat?.lastSeenAt || updated.jsonEvents !== current.jsonEvents || updated.agentProgress?.lastActivityAt !== current.agentProgress?.lastActivityAt;
50563
+ return hasMeaningfulUpdate;
50564
+ }
50565
+ function mergeTaskUpdatesPreservingTerminal(base, results) {
50566
+ const indexById = /* @__PURE__ */ new Map();
50567
+ for (const task of base) indexById.set(task.id, task);
50568
+ let skipped = 0;
50569
+ for (const result4 of results) {
50570
+ for (const updated of result4.tasks) {
50571
+ const current = indexById.get(updated.id);
50572
+ if (!current) continue;
50573
+ if (!shouldMergeTaskUpdate(current, updated)) {
50574
+ console.debug("[merge-gate] Skipping stale merge for task", updated.id, {
50575
+ currentStatus: current.status,
50576
+ updatedStatus: updated.status,
50577
+ currentFinishedAt: current.finishedAt,
50578
+ updatedFinishedAt: updated.finishedAt
50579
+ });
50580
+ skipped += 1;
50581
+ continue;
50582
+ }
50583
+ indexById.set(updated.id, updated);
50584
+ }
50585
+ }
50586
+ const merged = base.map((task) => indexById.get(task.id) ?? task);
50587
+ void skipped;
50588
+ return refreshTaskGraphQueues(merged);
50589
+ }
50590
+ var REJECTED_STATUS_MERGE_TRANSITIONS, __test__shouldMergeTaskUpdate, __test__mergeTaskUpdates;
50591
+ var init_merge_gate = __esm({
50592
+ "src/runtime/merge-gate.ts"() {
50593
+ "use strict";
50594
+ init_contracts();
50595
+ init_task_graph_scheduler();
50596
+ REJECTED_STATUS_MERGE_TRANSITIONS = (() => {
50597
+ const rejected = /* @__PURE__ */ new Set();
50598
+ for (const from of TEAM_TASK_STATUSES) {
50599
+ if (!TEAM_TERMINAL_TASK_STATUSES.has(from)) continue;
50600
+ for (const to of TEAM_TASK_STATUSES) {
50601
+ if (!TEAM_TERMINAL_TASK_STATUSES.has(to)) rejected.add(statusMergeKey(from, to));
50602
+ }
50603
+ }
50604
+ rejected.add(statusMergeKey("waiting", "running"));
50605
+ const completedIntegrityFlips = [
50606
+ ["completed", "failed"],
50607
+ ["completed", "needs_attention"],
50608
+ ["failed", "completed"],
50609
+ ["cancelled", "completed"],
50610
+ ["needs_attention", "completed"]
50611
+ ];
50612
+ for (const [from, to] of completedIntegrityFlips) rejected.add(statusMergeKey(from, to));
50613
+ return rejected;
50614
+ })();
50615
+ __test__shouldMergeTaskUpdate = shouldMergeTaskUpdate;
50616
+ __test__mergeTaskUpdates = mergeTaskUpdatesPreservingTerminal;
50617
+ }
50618
+ });
50619
+
50473
50620
  // src/runtime/model/runtime-policy.ts
50474
50621
  function resolveTaskRuntimeKind(globalKind, role, isolationPolicy, env = process.env) {
50475
50622
  if (globalKind === "scaffold") return "scaffold";
@@ -51822,75 +51969,6 @@ var init_task_graph = __esm({
51822
51969
  }
51823
51970
  });
51824
51971
 
51825
- // src/runtime/scheduling/task-graph-scheduler.ts
51826
- function buildTaskGraphIndex(tasks) {
51827
- const cached2 = taskGraphIndexCache.get(tasks);
51828
- if (cached2) return cached2;
51829
- const fresh = {
51830
- doneSteps: new Set(
51831
- tasks.filter((task) => task.status === "completed").map((task) => task.stepId).filter((id) => id !== void 0)
51832
- ),
51833
- idMap: new Map(tasks.map((task) => [task.id, task])),
51834
- stepToTaskId: new Map(
51835
- tasks.map((task) => [task.stepId, task.id]).filter((entry) => entry[0] !== void 0)
51836
- )
51837
- };
51838
- taskGraphIndexCache.set(tasks, fresh);
51839
- return fresh;
51840
- }
51841
- function dependencySatisfied(task, doneStepIds, idMap, stepMap) {
51842
- return task.dependsOn.every((dependency) => {
51843
- if (doneStepIds.has(dependency)) return true;
51844
- const taskId = stepMap.get(dependency) ?? dependency;
51845
- return idMap.get(taskId)?.status === "completed";
51846
- });
51847
- }
51848
- function withQueue(task, index) {
51849
- let resolvedQueue;
51850
- if (task.status === "queued") {
51851
- const isReady = dependencySatisfied(task, index.doneSteps, index.idMap, index.stepToTaskId);
51852
- resolvedQueue = isReady ? "ready" : "blocked";
51853
- } else if (task.status === "running") {
51854
- resolvedQueue = "running";
51855
- } else if (task.status === "completed" || task.status === "skipped" || task.status === "needs_attention") {
51856
- resolvedQueue = "done";
51857
- } else {
51858
- resolvedQueue = "blocked";
51859
- }
51860
- if (task.graph && task.graph.queue === resolvedQueue) {
51861
- return task;
51862
- }
51863
- return {
51864
- ...task,
51865
- graph: task.graph ? { ...task.graph, queue: resolvedQueue } : task.graph
51866
- };
51867
- }
51868
- function ensureIndex(tasks, index) {
51869
- return index ?? buildTaskGraphIndex(tasks);
51870
- }
51871
- function refreshTaskGraphQueues(tasks, index) {
51872
- const resolved = ensureIndex(tasks, index);
51873
- return tasks.map((task) => withQueue(task, resolved));
51874
- }
51875
- function taskGraphSnapshot(tasks, index) {
51876
- const refreshed = refreshTaskGraphQueues(tasks, index);
51877
- return {
51878
- ready: refreshed.filter((task) => task.status === "queued" && task.graph?.queue === "ready").map((task) => task.id),
51879
- blocked: refreshed.filter((task) => task.status === "queued" && task.graph?.queue === "blocked").map((task) => task.id),
51880
- running: refreshed.filter((task) => task.status === "running").map((task) => task.id),
51881
- done: refreshed.filter((task) => task.status === "completed" || task.status === "skipped").map((task) => task.id),
51882
- failed: refreshed.filter((task) => task.status === "failed").map((task) => task.id),
51883
- cancelled: refreshed.filter((task) => task.status === "cancelled").map((task) => task.id)
51884
- };
51885
- }
51886
- var taskGraphIndexCache;
51887
- var init_task_graph_scheduler = __esm({
51888
- "src/runtime/scheduling/task-graph-scheduler.ts"() {
51889
- "use strict";
51890
- taskGraphIndexCache = /* @__PURE__ */ new WeakMap();
51891
- }
51892
- });
51893
-
51894
51972
  // src/extension/knowledge-injection.ts
51895
51973
  import * as fs75 from "node:fs";
51896
51974
  import * as path65 from "node:path";
@@ -54551,16 +54629,24 @@ function validateWorkerOutput(role, output) {
54551
54629
  issues
54552
54630
  };
54553
54631
  }
54554
- var ROLE_PATTERN_DEFS, makeUrlRe;
54632
+ var MARKDOWN_STRUCTURED, STRICT_ROLE_PATTERNS, ROLE_PATTERN_DEFS, makeUrlRe;
54555
54633
  var init_output_validator = __esm({
54556
54634
  "src/runtime/output/output-validator.ts"() {
54557
54635
  "use strict";
54636
+ MARKDOWN_STRUCTURED = /^(?:#{1,6}\s|\*\*|[-*]\s|\d+\.\s)/m;
54637
+ STRICT_ROLE_PATTERNS = {
54638
+ explorer: /^(\S+:\d+|Defs:|Refs:|Callers:|Tests:|Sites:|No match\.|totals:)/m,
54639
+ executor: /^(\S+:\d+(-\d+)? — .{1,80}\.|verified:|too-big\.|needs-confirm\.|ambiguous\.|regressed\.)/m,
54640
+ reviewer: new RegExp("^([^:\\s]+:\\d+:\\s+\\p{Emoji_Presentation}|No issues\\.|totals:)", "mu"),
54641
+ "security-reviewer": new RegExp("^([^:\\s]+:\\d+:\\s+\\p{Emoji_Presentation}|No issues\\.|totals:)", "mu"),
54642
+ verifier: /^(PASS:|FAIL:)/m
54643
+ };
54558
54644
  ROLE_PATTERN_DEFS = {
54559
- explorer: () => /^(\S+:\d+|Defs:|Refs:|Callers:|Tests:|Sites:|No match\.|totals:)/m,
54560
- executor: () => /^(\S+:\d+(-\d+)? — .{1,80}\.|verified:|too-big\.|needs-confirm\.|ambiguous\.|regressed\.)/m,
54561
- reviewer: () => new RegExp("^([^:\\s]+:\\d+:\\s+\\p{Emoji_Presentation}|No issues\\.|totals:)", "mu"),
54562
- "security-reviewer": () => new RegExp("^([^:\\s]+:\\d+:\\s+\\p{Emoji_Presentation}|No issues\\.|totals:)", "mu"),
54563
- verifier: () => /^(PASS:|FAIL:)/m
54645
+ explorer: () => new RegExp(`(?:${STRICT_ROLE_PATTERNS.explorer.source})|(?:${MARKDOWN_STRUCTURED.source})`, "m"),
54646
+ executor: () => new RegExp(`(?:${STRICT_ROLE_PATTERNS.executor.source})|(?:${MARKDOWN_STRUCTURED.source})`, "m"),
54647
+ reviewer: () => new RegExp(`(?:${STRICT_ROLE_PATTERNS.reviewer.source})|(?:${MARKDOWN_STRUCTURED.source})`, "mu"),
54648
+ "security-reviewer": () => new RegExp(`(?:${STRICT_ROLE_PATTERNS["security-reviewer"].source})|(?:${MARKDOWN_STRUCTURED.source})`, "mu"),
54649
+ verifier: () => new RegExp(`(?:${STRICT_ROLE_PATTERNS.verifier.source})|(?:${MARKDOWN_STRUCTURED.source})`, "m")
54564
54650
  };
54565
54651
  makeUrlRe = () => /\bhttps?:\/\/[^\s<>)\]"',;]+/gi;
54566
54652
  }
@@ -56787,12 +56873,13 @@ __export(team_runner_exports, {
56787
56873
  drainPendingUnits: () => drainPendingUnits,
56788
56874
  executeTeamRun: () => executeTeamRun,
56789
56875
  hasPendingMutatingTaskAtBoundary: () => hasPendingMutatingTaskAtBoundary,
56790
- mergeTaskUpdatesPreservingTerminal: () => mergeTaskUpdatesPreservingTerminal,
56791
56876
  setRunStatusRunning: () => setRunStatusRunning,
56792
56877
  shouldUseRetry: () => shouldUseRetry
56793
56878
  });
56879
+ import { spawn as spawn6 } from "node:child_process";
56794
56880
  import * as fs86 from "node:fs";
56795
56881
  import * as path72 from "node:path";
56882
+ import { fileURLToPath as fileURLToPath7 } from "node:url";
56796
56883
  function startTeamRunHeartbeat(stateRoot, runId) {
56797
56884
  const heartbeatPath = path72.join(stateRoot, "heartbeat.json");
56798
56885
  const writeHeartbeat = () => {
@@ -56816,6 +56903,87 @@ function startTeamRunHeartbeat(stateRoot, runId) {
56816
56903
  const interval = setInterval(writeHeartbeat, 6e4);
56817
56904
  return () => clearInterval(interval);
56818
56905
  }
56906
+ function perfScriptPath(scriptName) {
56907
+ try {
56908
+ const candidates = [
56909
+ fileURLToPath7(new URL(`../../scripts/${scriptName}`, import.meta.url)),
56910
+ fileURLToPath7(new URL(`../scripts/${scriptName}`, import.meta.url))
56911
+ ];
56912
+ return candidates.find((p) => fs86.existsSync(p));
56913
+ } catch {
56914
+ return void 0;
56915
+ }
56916
+ }
56917
+ function startPerfSampler(manifest, team) {
56918
+ const marker = (msg) => {
56919
+ try {
56920
+ fs86.appendFileSync(path72.join(manifest.artifactsRoot, "perf-obs.log"), `[${(/* @__PURE__ */ new Date()).toISOString()}] ${msg}
56921
+ `);
56922
+ } catch {
56923
+ }
56924
+ };
56925
+ marker(`startPerfSampler entered (team=${team.name} observability=${String(team.observability)} importMetaUrl=${import.meta.url})`);
56926
+ if (team.observability !== true) {
56927
+ marker(`SKIP: observability=${String(team.observability)} !== true`);
56928
+ return;
56929
+ }
56930
+ const samplerPath = perfScriptPath("resource-sampler.mjs");
56931
+ if (!samplerPath) {
56932
+ marker(`SKIP: resource-sampler.mjs not found (importMetaUrl=${import.meta.url})`);
56933
+ return;
56934
+ }
56935
+ marker(`spawning sampler from ${samplerPath}`);
56936
+ const crewRoot = path72.dirname(path72.dirname(path72.dirname(manifest.stateRoot)));
56937
+ const outPath = path72.join(manifest.artifactsRoot, "resources.jsonl");
56938
+ const logPath = path72.join(manifest.artifactsRoot, "perf-obs.log");
56939
+ try {
56940
+ const child = spawn6(
56941
+ process.execPath,
56942
+ [
56943
+ "--experimental-strip-types",
56944
+ samplerPath,
56945
+ "--watch-run",
56946
+ manifest.runId,
56947
+ "--crew-root",
56948
+ crewRoot,
56949
+ "--interval",
56950
+ String(OBSERVABILITY_INTERVAL_MS),
56951
+ "--out",
56952
+ outPath
56953
+ ],
56954
+ { detached: true, stdio: ["ignore", "ignore", "pipe"] }
56955
+ );
56956
+ child.stderr?.on("data", (d) => {
56957
+ try {
56958
+ fs86.appendFileSync(logPath, String(d));
56959
+ } catch {
56960
+ }
56961
+ });
56962
+ child.unref();
56963
+ } catch (err2) {
56964
+ console.warn(`[perf-obs] sampler spawn failed for ${manifest.runId}: ${String(err2)}`);
56965
+ }
56966
+ }
56967
+ function schedulePerfAnalyze(manifest, team) {
56968
+ if (team.observability !== true) return;
56969
+ const analyzePath = perfScriptPath("analyze-run.mjs");
56970
+ const resourcesPath = path72.join(manifest.artifactsRoot, "resources.jsonl");
56971
+ if (!analyzePath || !fs86.existsSync(resourcesPath)) return;
56972
+ const crewRoot = path72.dirname(path72.dirname(path72.dirname(manifest.stateRoot)));
56973
+ const timer = setTimeout(() => {
56974
+ try {
56975
+ const child = spawn6(
56976
+ process.execPath,
56977
+ ["--experimental-strip-types", analyzePath, manifest.runId, "--crew-root", crewRoot, "--resources", resourcesPath],
56978
+ { detached: true, stdio: "ignore" }
56979
+ );
56980
+ child.unref();
56981
+ } catch (err2) {
56982
+ console.warn(`[perf-obs] analyze spawn failed for ${manifest.runId}: ${String(err2)}`);
56983
+ }
56984
+ }, OBSERVABILITY_ANALYZE_DELAY_MS);
56985
+ timer.unref();
56986
+ }
56819
56987
  function checkPerTaskBudget(tasks, budgetTotal, budgetWarning, budgetAbort, fairShareFraction = 0.5) {
56820
56988
  const usage = aggregateUsage(tasks);
56821
56989
  const totalUsed = (usage?.input ?? 0) + (usage?.output ?? 0) + (usage?.cacheWrite ?? 0);
@@ -56859,9 +57027,6 @@ function markBlocked(tasks, reason) {
56859
57027
  } : task
56860
57028
  );
56861
57029
  }
56862
- function isNonTerminalTaskStatus(status) {
56863
- return status === "queued" || status === "running" || status === "waiting";
56864
- }
56865
57030
  function cancelNonTerminalTasks(tasks, status, reason, filter, transform) {
56866
57031
  const predicate = filter ?? ((task) => isNonTerminalTaskStatus(task.status));
56867
57032
  return tasks.map((task) => {
@@ -56870,63 +57035,6 @@ function cancelNonTerminalTasks(tasks, status, reason, filter, transform) {
56870
57035
  return transform ? transform(task, terminalised) : terminalised;
56871
57036
  });
56872
57037
  }
56873
- function safeFinishedAt(task) {
56874
- if (!task.finishedAt) return -Infinity;
56875
- const ms = new Date(task.finishedAt).getTime();
56876
- return Number.isNaN(ms) ? Infinity : ms;
56877
- }
56878
- function isMalformedFinishedAtReplacement(currentTime, updatedTime) {
56879
- return !Number.isFinite(currentTime) && Number.isFinite(updatedTime);
56880
- }
56881
- function statusMergeKey(from, to) {
56882
- return `${from}->${to}`;
56883
- }
56884
- function shouldMergeTaskUpdate(current, updated) {
56885
- if (REJECTED_STATUS_MERGE_TRANSITIONS.has(statusMergeKey(current.status, updated.status))) return false;
56886
- if (current.status === updated.status && updated.status === "running" && current.resultArtifact && !updated.resultArtifact)
56887
- return false;
56888
- if (current.status === updated.status && current.status === "completed" && current.resultArtifact && !updated.resultArtifact)
56889
- return false;
56890
- if (current.finishedAt !== void 0 && updated.finishedAt !== void 0) {
56891
- const currentTime = safeFinishedAt(current);
56892
- const updatedTime = safeFinishedAt(updated);
56893
- if (!Number.isFinite(currentTime)) {
56894
- console.warn(`[team-runner] Task ${current.id} has malformed finishedAt: ${current.finishedAt}`);
56895
- }
56896
- if (isMalformedFinishedAtReplacement(currentTime, updatedTime)) {
56897
- return true;
56898
- }
56899
- if (updatedTime < currentTime) return false;
56900
- }
56901
- if (!updated.finishedAt && !isNonTerminalTaskStatus(updated.status)) return false;
56902
- const hasMeaningfulUpdate = updated.status !== current.status || updated.finishedAt !== current.finishedAt || updated.startedAt !== current.startedAt || Boolean(updated.resultArtifact) !== Boolean(current.resultArtifact) || Boolean(updated.resultArtifact) && updated.resultArtifact !== current.resultArtifact || Boolean(updated.error) || Boolean(updated.modelAttempts?.length) || Boolean(updated.usage) || Boolean(updated.attempts?.length) || updated.heartbeat?.lastSeenAt !== current.heartbeat?.lastSeenAt || updated.jsonEvents !== current.jsonEvents || updated.agentProgress?.lastActivityAt !== current.agentProgress?.lastActivityAt;
56903
- return hasMeaningfulUpdate;
56904
- }
56905
- function mergeTaskUpdatesPreservingTerminal(base, results) {
56906
- const indexById = /* @__PURE__ */ new Map();
56907
- for (const task of base) indexById.set(task.id, task);
56908
- let skipped = 0;
56909
- for (const result4 of results) {
56910
- for (const updated of result4.tasks) {
56911
- const current = indexById.get(updated.id);
56912
- if (!current) continue;
56913
- if (!shouldMergeTaskUpdate(current, updated)) {
56914
- console.debug("[team-runner] Skipping stale merge for task", updated.id, {
56915
- currentStatus: current.status,
56916
- updatedStatus: updated.status,
56917
- currentFinishedAt: current.finishedAt,
56918
- updatedFinishedAt: updated.finishedAt
56919
- });
56920
- skipped += 1;
56921
- continue;
56922
- }
56923
- indexById.set(updated.id, updated);
56924
- }
56925
- }
56926
- const merged = base.map((task) => indexById.get(task.id) ?? task);
56927
- void skipped;
56928
- return refreshTaskGraphQueues(merged);
56929
- }
56930
57038
  function formatTaskProgress(task) {
56931
57039
  return `- ${task.id}: ${task.status} (${task.role} -> ${task.agent})${task.taskPacket ? ` scope=${task.taskPacket.scope}` : ""}${task.verification ? ` green=${task.verification.observedGreenLevel}/${task.verification.requiredGreenLevel}` : ""}${task.error ? ` - ${task.error}` : ""}`;
56932
57040
  }
@@ -57188,6 +57296,7 @@ async function executeTeamRun(input) {
57188
57296
  }
57189
57297
  void registerRunPromise(manifest.runId);
57190
57298
  const stopTeamHeartbeat = startTeamRunHeartbeat(manifest.stateRoot, manifest.runId);
57299
+ startPerfSampler(manifest, input.team);
57191
57300
  const cleanupUsage = () => {
57192
57301
  for (const task of input.tasks) clearTrackedTaskUsage(task.id);
57193
57302
  };
@@ -57253,6 +57362,7 @@ async function executeTeamRun(input) {
57253
57362
  );
57254
57363
  }
57255
57364
  await flushEventLogBuffer();
57365
+ schedulePerfAnalyze(manifest, input.team);
57256
57366
  return result4;
57257
57367
  } catch (error) {
57258
57368
  stopTeamHeartbeat();
@@ -58340,15 +58450,13 @@ async function executeTeamRunCore(input, manifest, workflow) {
58340
58450
  await drainPendingUnits(pendingUnits, runController);
58341
58451
  }
58342
58452
  }
58343
- var builtInRegistry, REJECTED_STATUS_MERGE_TRANSITIONS, __test__shouldMergeTaskUpdate, __test__mergeTaskUpdates, lastProgressContentHash, __test__lastProgressContentHash, __test__writeProgress, __test__cancelPlanTasks;
58453
+ var OBSERVABILITY_INTERVAL_MS, OBSERVABILITY_ANALYZE_DELAY_MS, lastProgressContentHash, __test__lastProgressContentHash, __test__writeProgress, __test__cancelPlanTasks;
58344
58454
  var init_team_runner = __esm({
58345
58455
  "src/runtime/team-runner.ts"() {
58346
58456
  "use strict";
58347
58457
  init_errors3();
58348
58458
  init_registry2();
58349
58459
  init_correlation();
58350
- init_plugin_registry();
58351
- init_plugins();
58352
58460
  init_atomic_write();
58353
58461
  init_contracts();
58354
58462
  init_locks();
@@ -58366,6 +58474,7 @@ var init_team_runner = __esm({
58366
58474
  init_goal_achievement();
58367
58475
  init_group_join();
58368
58476
  init_live_agent_manager();
58477
+ init_merge_gate();
58369
58478
  init_runtime_policy();
58370
58479
  init_path_overlap();
58371
58480
  init_policy_engine();
@@ -58387,32 +58496,10 @@ var init_team_runner = __esm({
58387
58496
  init_usage_tracker();
58388
58497
  init_workflow_state();
58389
58498
  init_adaptive_plan();
58499
+ init_merge_gate();
58390
58500
  init_adaptive_plan();
58391
- builtInRegistry = new PluginRegistry();
58392
- builtInRegistry.register(NextJsPlugin);
58393
- builtInRegistry.register(VitestPlugin);
58394
- builtInRegistry.register(VitePlugin);
58395
- REJECTED_STATUS_MERGE_TRANSITIONS = (() => {
58396
- const rejected = /* @__PURE__ */ new Set();
58397
- for (const from of TEAM_TASK_STATUSES) {
58398
- if (!TEAM_TERMINAL_TASK_STATUSES.has(from)) continue;
58399
- for (const to of TEAM_TASK_STATUSES) {
58400
- if (!TEAM_TERMINAL_TASK_STATUSES.has(to)) rejected.add(statusMergeKey(from, to));
58401
- }
58402
- }
58403
- rejected.add(statusMergeKey("waiting", "running"));
58404
- const completedIntegrityFlips = [
58405
- ["completed", "failed"],
58406
- ["completed", "needs_attention"],
58407
- ["failed", "completed"],
58408
- ["cancelled", "completed"],
58409
- ["needs_attention", "completed"]
58410
- ];
58411
- for (const [from, to] of completedIntegrityFlips) rejected.add(statusMergeKey(from, to));
58412
- return rejected;
58413
- })();
58414
- __test__shouldMergeTaskUpdate = shouldMergeTaskUpdate;
58415
- __test__mergeTaskUpdates = mergeTaskUpdatesPreservingTerminal;
58501
+ OBSERVABILITY_INTERVAL_MS = 2e3;
58502
+ OBSERVABILITY_ANALYZE_DELAY_MS = 3e3;
58416
58503
  lastProgressContentHash = /* @__PURE__ */ new Map();
58417
58504
  __test__lastProgressContentHash = lastProgressContentHash;
58418
58505
  __test__writeProgress = writeProgress;
@@ -66261,7 +66348,6 @@ var init_mascot = __esm({
66261
66348
  currentArminGrid;
66262
66349
  effectState = {};
66263
66350
  effectDone = false;
66264
- visible = true;
66265
66351
  frame = 0;
66266
66352
  effectPhase = 0;
66267
66353
  gridVersion = 0;
@@ -66357,7 +66443,7 @@ var init_mascot = __esm({
66357
66443
  this.gridVersion++;
66358
66444
  }
66359
66445
  this.invalidate();
66360
- if (this.visible) this.requestRender?.();
66446
+ this.requestRender?.();
66361
66447
  }
66362
66448
  tickArminEffect() {
66363
66449
  switch (this.effect) {
@@ -66564,14 +66650,6 @@ var init_mascot = __esm({
66564
66650
  this.close();
66565
66651
  }
66566
66652
  }
66567
- /**
66568
- * Set whether the mascot is currently visible (not obscured by another
66569
- * overlay). When invisible, tick() skips requestRender so the animation
66570
- * does not trigger needless repaints while hidden.
66571
- */
66572
- setVisible(visible) {
66573
- this.visible = visible;
66574
- }
66575
66653
  dispose() {
66576
66654
  this.doneGuard.called = true;
66577
66655
  if (this.interval) clearInterval(this.interval);
@@ -70620,10 +70698,18 @@ var init_otlp_exporter = __esm({
70620
70698
  });
70621
70699
 
70622
70700
  // src/observability/metrics-primitives.ts
70623
- function enforceLabelCap(map3, metricName) {
70701
+ function getCardinalityEvictions() {
70702
+ return cardinalityEvictions;
70703
+ }
70704
+ function enforceLabelCap(map3, _metricName) {
70624
70705
  while (map3.size > MAX_LABEL_COMBINATIONS) {
70625
70706
  const firstKey = map3.keys().next().value;
70626
- if (firstKey !== void 0) map3.delete(firstKey);
70707
+ if (firstKey !== void 0) {
70708
+ map3.delete(firstKey);
70709
+ cardinalityEvictions++;
70710
+ } else {
70711
+ break;
70712
+ }
70627
70713
  }
70628
70714
  }
70629
70715
  function normalizeLabels(labels = {}) {
@@ -70637,12 +70723,13 @@ function labelKey(labels = {}) {
70637
70723
  function cloneLabels(labels) {
70638
70724
  return { ...labels };
70639
70725
  }
70640
- var DEFAULT_HISTOGRAM_BUCKETS, MAX_LABEL_COMBINATIONS, Metric, Counter, Gauge, Histogram;
70726
+ var DEFAULT_HISTOGRAM_BUCKETS, MAX_LABEL_COMBINATIONS, cardinalityEvictions, Metric, Counter, Gauge, Histogram;
70641
70727
  var init_metrics_primitives = __esm({
70642
70728
  "src/observability/metrics-primitives.ts"() {
70643
70729
  "use strict";
70644
70730
  DEFAULT_HISTOGRAM_BUCKETS = [1, 2, 5, 10, 25, 50, 100, 250, 500, 1e3, 2500, 5e3, 1e4];
70645
70731
  MAX_LABEL_COMBINATIONS = 1e4;
70732
+ cardinalityEvictions = 0;
70646
70733
  Metric = class {
70647
70734
  name;
70648
70735
  description;
@@ -70907,6 +70994,14 @@ function wireEventToMetrics(events, registry2) {
70907
70994
  const deadletterCount = registry2.counter("crew.task.deadletter_total", "Deadletter triggers by reason");
70908
70995
  const overflowCount = registry2.counter("crew.task.overflow_phase_total", "Overflow recovery phase transitions");
70909
70996
  const supervisorContactCount = registry2.counter("crew.task.supervisor_contact_total", "Supervisor contact requests by reason");
70997
+ const unboundedConcurrencyCount = registry2.counter(
70998
+ "crew.limits.unbounded_total",
70999
+ "Runs that enabled allowUnboundedConcurrency (advisory; bypasses hard cap)"
71000
+ );
71001
+ const cardinalityEvictedGauge = registry2.gauge(
71002
+ "crew.metrics.cardinality_evicted",
71003
+ "Cumulative label-combination evictions (non-zero = unreliable aggregation)"
71004
+ );
70910
71005
  registry2.gauge("crew.heartbeat.staleness_ms", "Heartbeat elapsed since last seen, milliseconds");
70911
71006
  const runDuration = registry2.histogram(
70912
71007
  "crew.run.duration_ms",
@@ -71006,11 +71101,21 @@ function wireEventToMetrics(events, registry2) {
71006
71101
  direction: stringValue(item.direction, "unknown")
71007
71102
  });
71008
71103
  }
71104
+ ],
71105
+ [
71106
+ "crew.limits.unbounded",
71107
+ () => {
71108
+ unboundedConcurrencyCount.inc({});
71109
+ }
71009
71110
  ]
71010
71111
  ];
71011
71112
  const unsubscribers = [];
71012
71113
  for (const [event, handler] of handlers) {
71013
71114
  const unsubscribe = events?.on?.(event, (data) => {
71115
+ try {
71116
+ cardinalityEvictedGauge.set({}, getCardinalityEvictions());
71117
+ } catch {
71118
+ }
71014
71119
  try {
71015
71120
  handler(data);
71016
71121
  } catch {
@@ -71031,6 +71136,7 @@ var CANCELLATION_REASON_LABELS;
71031
71136
  var init_event_to_metric = __esm({
71032
71137
  "src/observability/event-to-metric.ts"() {
71033
71138
  "use strict";
71139
+ init_metrics_primitives();
71034
71140
  CANCELLATION_REASON_LABELS = /* @__PURE__ */ new Set([
71035
71141
  "caller_cancelled",
71036
71142
  "leader_interrupted",
@@ -72583,7 +72689,7 @@ function isDangerStage(index, levels) {
72583
72689
  init_pi_ui_compat();
72584
72690
  init_theme_adapter();
72585
72691
  init_visual();
72586
- import { isAbsolute as isAbsolute10, relative as relative8, resolve as resolve21, sep as sep9 } from "node:path";
72692
+ import { isAbsolute as isAbsolute11, relative as relative8, resolve as resolve21, sep as sep9 } from "node:path";
72587
72693
 
72588
72694
  // src/extension/crew-vibes/render.ts
72589
72695
  function formatCount(value) {
@@ -72720,7 +72826,7 @@ function formatCwdForFooter(cwd, home) {
72720
72826
  const resolvedCwd = resolve21(cwd);
72721
72827
  const resolvedHome = resolve21(home);
72722
72828
  const rel = relative8(resolvedHome, resolvedCwd);
72723
- const inside = rel === "" || rel !== ".." && !rel.startsWith(`..${sep9}`) && !isAbsolute10(rel);
72829
+ const inside = rel === "" || rel !== ".." && !rel.startsWith(`..${sep9}`) && !isAbsolute11(rel);
72724
72830
  if (!inside) return cwd;
72725
72831
  return rel === "" ? "~" : `~${sep9}${rel}`;
72726
72832
  }
@@ -75285,7 +75391,7 @@ function startForegroundRunImpl(pi, ctx, extensionCtx, runner, runId) {
75285
75391
  init_config();
75286
75392
  import * as fs103 from "node:fs";
75287
75393
  import * as path82 from "node:path";
75288
- import { fileURLToPath as fileURLToPath7 } from "node:url";
75394
+ import { fileURLToPath as fileURLToPath8 } from "node:url";
75289
75395
 
75290
75396
  // src/runtime/per-write-validator.ts
75291
75397
  import { readFileSync as readFileSync81 } from "node:fs";
@@ -75388,7 +75494,7 @@ function installResourcesDiscoverHook(pi, ctx) {
75388
75494
  pi.on("resources_discover", () => {
75389
75495
  const sessionCwd = ctx.currentCtx?.cwd ?? process.cwd();
75390
75496
  const skillDir = path82.resolve(sessionCwd, "skills");
75391
- const extSkillDir = path82.resolve(path82.dirname(fileURLToPath7(import.meta.url)), "..", "..", "skills");
75497
+ const extSkillDir = path82.resolve(path82.dirname(fileURLToPath8(import.meta.url)), "..", "..", "skills");
75392
75498
  const paths = [];
75393
75499
  if (fs103.existsSync(extSkillDir)) paths.push(extSkillDir);
75394
75500
  if (skillDir !== extSkillDir && fs103.existsSync(skillDir)) {
@@ -76011,19 +76117,6 @@ var CrewBroker = class {
76011
76117
  }
76012
76118
  this.resolvedSocketPath = null;
76013
76119
  }
76014
- /**
76015
- * Non-throwing enqueue entry point for the post-append mailbox observer
76016
- * (Phase 1) or any other in-process producer. Phase 0 accepts `notifyMessage`
76017
- * as a no-op shape so the lifecycle controller can install a single
76018
- * observer regardless of broker state.
76019
- *
76020
- * Fanout goes ONLY to authenticated connections matching the recipient.
76021
- * Phase 0 keeps this as a typed no-op (`not-implemented` would be
76022
- * inappropriate here — the caller is in-process and shouldn't be
76023
- * punished for testing the broker skeleton).
76024
- */
76025
- notifyMessage(_message) {
76026
- }
76027
76120
  // ------------------------------------------------------------------------
76028
76121
  // Connection lifecycle
76029
76122
  // ------------------------------------------------------------------------