pi-crew 0.9.62 → 0.9.65

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 (60) hide show
  1. package/CHANGELOG.md +105 -0
  2. package/README.md +47 -1
  3. package/agents/critic.md +1 -1
  4. package/agents/explorer.md +1 -1
  5. package/agents/planner.md +1 -1
  6. package/agents/reviewer.md +1 -1
  7. package/agents/security-reviewer.md +1 -1
  8. package/agents/test-engineer.md +1 -1
  9. package/agents/writer.md +1 -1
  10. package/dist/index.mjs +478 -366
  11. package/package.json +7 -3
  12. package/scripts/analyze-run.mjs +1333 -0
  13. package/scripts/pty_probe.py +10 -8
  14. package/scripts/resource-sampler.mjs +482 -0
  15. package/skills/real-test-pi-crew/SKILL.md +6 -6
  16. package/src/agents/agent-config.ts +4 -0
  17. package/src/agents/agent-serializer.ts +1 -0
  18. package/src/agents/discover-agents.ts +8 -0
  19. package/src/config/role-tools.ts +49 -1
  20. package/src/observability/event-to-metric.ts +29 -0
  21. package/src/observability/metrics-primitives.ts +41 -3
  22. package/src/prompt/prompt-runtime.ts +6 -0
  23. package/src/prompt/scratchpad-lifecycle.ts +605 -0
  24. package/src/runtime/README.md +1 -1
  25. package/src/runtime/broker/crew-broker.ts +0 -16
  26. package/src/runtime/child-pi/child-pi-spawn.ts +42 -1
  27. package/src/runtime/child-pi/child-pi.ts +5 -0
  28. package/src/runtime/effectiveness.ts +23 -1
  29. package/src/runtime/merge-gate.ts +202 -0
  30. package/src/runtime/model/model-fallback.ts +11 -0
  31. package/src/runtime/model/pi-args.ts +1 -1
  32. package/src/runtime/model/provider-extensions.ts +31 -12
  33. package/src/runtime/output/progress-tracker.ts +3 -33
  34. package/src/runtime/recovery/crash-recovery.ts +1 -1
  35. package/src/runtime/scratchpad/README.md +184 -0
  36. package/src/runtime/scratchpad/engine.ts +648 -0
  37. package/src/runtime/scratchpad/guest.ts +360 -0
  38. package/src/runtime/scratchpad/index.ts +22 -0
  39. package/src/runtime/scratchpad/protocol.ts +88 -0
  40. package/src/runtime/scratchpad/snapshot-hmac.ts +161 -0
  41. package/src/runtime/scratchpad/snapshot-lookup.ts +74 -0
  42. package/src/runtime/scratchpad/transform.ts +363 -0
  43. package/src/runtime/task-runner/child-executor.ts +48 -31
  44. package/src/runtime/team-runner.ts +128 -203
  45. package/src/schema/team-tool-schema.ts +2 -0
  46. package/src/teams/discover-teams.ts +2 -0
  47. package/src/teams/team-config.ts +7 -0
  48. package/src/teams/team-serializer.ts +1 -0
  49. package/src/ui/mascot.ts +1 -14
  50. package/teams/default.team.md +1 -0
  51. package/teams/fast-fix.team.md +1 -0
  52. package/src/observability/event-bus.ts +0 -86
  53. package/src/plugins/plugin-define.ts +0 -6
  54. package/src/plugins/plugin-registry.ts +0 -32
  55. package/src/plugins/plugins/index.ts +0 -3
  56. package/src/plugins/plugins/nextjs.ts +0 -19
  57. package/src/plugins/plugins/vite.ts +0 -10
  58. package/src/plugins/plugins/vitest.ts +0 -9
  59. package/src/runtime/child-pi/child-pi-pool.ts +0 -68
  60. package/src/runtime/iteration-hooks.ts +0 -305
package/dist/index.mjs CHANGED
@@ -11513,10 +11513,16 @@ function discoverProviderExtensions(settingsPath2) {
11513
11513
  const npmBase = path9.join(baseDir, "npm", "node_modules");
11514
11514
  for (const spec of settings.packages ?? []) {
11515
11515
  if (typeof spec !== "string") continue;
11516
- if (!spec.startsWith("npm:")) continue;
11517
- const pkgName = spec.slice(4);
11518
- const pkgDir = path9.join(npmBase, pkgName);
11516
+ let pkgDir;
11517
+ if (spec.startsWith("npm:")) {
11518
+ pkgDir = path9.join(npmBase, spec.slice(4));
11519
+ } else if (spec.startsWith("./") || spec.startsWith("../") || path9.isAbsolute(spec)) {
11520
+ pkgDir = path9.resolve(baseDir, spec);
11521
+ } else {
11522
+ continue;
11523
+ }
11519
11524
  if (!fs9.existsSync(pkgDir)) continue;
11525
+ if (path9.resolve(pkgDir) === path9.resolve(packageRoot())) continue;
11520
11526
  const entryPath = resolvePackageEntry(pkgDir);
11521
11527
  if (entryPath) out.push({ spec, entryPath });
11522
11528
  }
@@ -11602,15 +11608,51 @@ var init_frontmatter = __esm({
11602
11608
  }
11603
11609
  });
11604
11610
 
11611
+ // src/runtime/role-permission.ts
11612
+ function permissionForRole(role) {
11613
+ if (READ_ONLY_ROLES.has(role)) return "read_only";
11614
+ if (WRITE_ROLES.has(role)) return "workspace_write";
11615
+ return "read_only";
11616
+ }
11617
+ function currentCrewRole(env = process.env) {
11618
+ return env.PI_CREW_ROLE?.trim() || env.PI_TEAMS_ROLE?.trim() || void 0;
11619
+ }
11620
+ function checkSubagentSpawnPermission(role) {
11621
+ if (!role) return { allowed: true, mode: "workspace_write" };
11622
+ const mode = permissionForRole(role);
11623
+ if (mode === "read_only")
11624
+ return {
11625
+ allowed: false,
11626
+ mode,
11627
+ reason: `Role '${role}' is read-only and cannot spawn additional subagents.`
11628
+ };
11629
+ return { allowed: true, mode };
11630
+ }
11631
+ var READ_ONLY_ROLES, WRITE_ROLES;
11632
+ var init_role_permission = __esm({
11633
+ "src/runtime/role-permission.ts"() {
11634
+ "use strict";
11635
+ READ_ONLY_ROLES = /* @__PURE__ */ new Set(["explorer", "reviewer", "security-reviewer", "analyst", "critic", "planner"]);
11636
+ WRITE_ROLES = /* @__PURE__ */ new Set(["executor", "test-engineer", "writer", "verifier", "agent", "cold-verifier", "chain-executor", "worker"]);
11637
+ }
11638
+ });
11639
+
11605
11640
  // src/config/role-tools.ts
11606
11641
  function getToolConfig(role) {
11607
11642
  const key = role.includes("_") ? role.replaceAll("_", "-") : role;
11608
11643
  return ROLE_TOOL_CONFIGS[key] ?? ROLE_TOOL_CONFIGS[role] ?? {};
11609
11644
  }
11645
+ function isScratchpadEnabledForRole(role, agent) {
11646
+ const normalized = role.includes("_") ? role.replaceAll("_", "-") : role;
11647
+ if (permissionForRole(normalized) === "read_only") return false;
11648
+ if (agent?.scratchpad === false) return false;
11649
+ return agent?.scratchpad === true || getToolConfig(normalized).scratchpad === true;
11650
+ }
11610
11651
  var ROLE_TOOL_CONFIGS;
11611
11652
  var init_role_tools = __esm({
11612
11653
  "src/config/role-tools.ts"() {
11613
11654
  "use strict";
11655
+ init_role_permission();
11614
11656
  ROLE_TOOL_CONFIGS = {
11615
11657
  // Explorer - Read-only exploration; bash is included for git log/show
11616
11658
  // (decisions stream needs commit-history mining) but edit/write stay
@@ -11641,9 +11683,11 @@ var init_role_tools = __esm({
11641
11683
  tools: ["read", "grep", "find", "ls", "glob"],
11642
11684
  excludeTools: ["edit", "write", "bash", "web"]
11643
11685
  },
11644
- // Executor - Full access (default)
11686
+ // Executor - Full access (default). Phase 1 scratchpad-enabled (stateful
11687
+ // evaluator compounds intermediate results across execute calls).
11645
11688
  executor: {
11646
11689
  // No restrictions - full tool access
11690
+ scratchpad: true
11647
11691
  },
11648
11692
  // Reviewer - Read and review, no write
11649
11693
  reviewer: {
@@ -11670,12 +11714,16 @@ var init_role_tools = __esm({
11670
11714
  // integrity is preserved during verification. Mirrors cold-verifier behavior.
11671
11715
  verifier: {
11672
11716
  tools: ["read", "grep", "find", "ls", "bash"],
11673
- excludeTools: ["edit", "write", "web"]
11717
+ excludeTools: ["edit", "write", "web"],
11718
+ // Phase 1 scratchpad: multi-cell test/verify flows reuse parsed state.
11719
+ scratchpad: true
11674
11720
  },
11675
11721
  // Test Engineer - Can write tests (F1: hyphenated key)
11676
11722
  "test-engineer": {
11677
11723
  tools: ["read", "edit", "write", "bash", "ls"],
11678
- excludeTools: ["web"]
11724
+ excludeTools: ["web"],
11725
+ // Phase 1 scratchpad: build/run test suites with state across cells.
11726
+ scratchpad: true
11679
11727
  }
11680
11728
  };
11681
11729
  }
@@ -11891,6 +11939,14 @@ function parseAgentFile(filePath, source) {
11891
11939
  fallbackModels: parseCsv(frontmatter.fallbackModels),
11892
11940
  thinking: frontmatter.thinking === "false" ? void 0 : frontmatter.thinking || void 0,
11893
11941
  tools: parseToolsField(frontmatter.tools),
11942
+ // Phase 1 scratchpad opt-in (Q3: pi ignores unknown frontmatter keys; pi-crew
11943
+ // is the sole consumer in the worker path — task arrives via -p, agent file
11944
+ // is not re-read by pi). 3-STATE parse (NOT `=== "true"` like
11945
+ // inheritProjectContext): omitted/malformed → undefined so the F6 kill-switch
11946
+ // (`agent.scratchpad === false`) only fires on an EXPLICIT `scratchpad: false`,
11947
+ // not on every agent without the key (which would wrongly kill role
11948
+ // default-on). Only the literal "true"/"false" are honored.
11949
+ scratchpad: frontmatter.scratchpad === "true" ? true : frontmatter.scratchpad === "false" ? false : void 0,
11894
11950
  // SEC-1: Strip extensions/excludeExtensions for untrusted project-sourced
11895
11951
  // agents (RCE prevention). Both `project` (.crew/agents/) and
11896
11952
  // `project-pi` (.pi/agents/) are repo-adjacent / untrusted sources —
@@ -12392,6 +12448,8 @@ function parseTeamFile(filePath, source) {
12392
12448
  defaultWorkflow: frontmatter.defaultWorkflow || frontmatter.workflow || void 0,
12393
12449
  workspaceMode: frontmatter.workspaceMode?.trim() === "worktree" ? "worktree" : "single",
12394
12450
  maxConcurrency: frontmatter.maxConcurrency ? Number.parseInt(frontmatter.maxConcurrency, 10) : void 0,
12451
+ // observability defaults ON ("luôn hoạt động"); explicit `observability: false` disables.
12452
+ observability: frontmatter.observability === void 0 ? true : frontmatter.observability !== "false",
12395
12453
  routing: triggers || useWhen || avoidWhen || cost || category ? { triggers, useWhen, avoidWhen, cost, category } : void 0
12396
12454
  };
12397
12455
  } catch {
@@ -14860,6 +14918,55 @@ var init_env_filter = __esm({
14860
14918
  }
14861
14919
  });
14862
14920
 
14921
+ // src/runtime/scratchpad/snapshot-lookup.ts
14922
+ import { lstatSync as lstatSync5, readdirSync as readdirSync8 } from "node:fs";
14923
+ import { join as join18 } from "node:path";
14924
+ function findLatestScratchpadSnapshot(artifactsRoot, agentId) {
14925
+ const scratchpadDir = join18(artifactsRoot, "scratchpad");
14926
+ let dirStat;
14927
+ try {
14928
+ dirStat = lstatSync5(scratchpadDir);
14929
+ } catch {
14930
+ return null;
14931
+ }
14932
+ if (dirStat.isSymbolicLink() || !dirStat.isDirectory()) return null;
14933
+ let entries;
14934
+ try {
14935
+ entries = readdirSync8(scratchpadDir, { withFileTypes: true });
14936
+ } catch {
14937
+ return null;
14938
+ }
14939
+ const prefix = `${agentId}.attempt-`;
14940
+ let best = null;
14941
+ for (const dirent of entries) {
14942
+ if (dirent.isSymbolicLink() || !dirent.isFile()) continue;
14943
+ const name = dirent.name;
14944
+ if (!name.startsWith(prefix) || !name.endsWith(SNAPSHOT_SUFFIX)) continue;
14945
+ const attemptPart = name.slice(prefix.length, name.length - SNAPSHOT_SUFFIX.length);
14946
+ if (!/^\d+$/.test(attemptPart)) continue;
14947
+ const attempt = Number.parseInt(attemptPart, 10);
14948
+ let stat2;
14949
+ try {
14950
+ stat2 = lstatSync5(join18(scratchpadDir, name));
14951
+ } catch {
14952
+ continue;
14953
+ }
14954
+ if (!stat2.isFile()) continue;
14955
+ const hit = { path: join18(scratchpadDir, name), attempt, mtimeMs: stat2.mtimeMs };
14956
+ if (best === null || hit.mtimeMs > best.mtimeMs || hit.mtimeMs === best.mtimeMs && hit.attempt < best.attempt) {
14957
+ best = hit;
14958
+ }
14959
+ }
14960
+ return best;
14961
+ }
14962
+ var SNAPSHOT_SUFFIX;
14963
+ var init_snapshot_lookup = __esm({
14964
+ "src/runtime/scratchpad/snapshot-lookup.ts"() {
14965
+ "use strict";
14966
+ SNAPSHOT_SUFFIX = ".snapshot.json";
14967
+ }
14968
+ });
14969
+
14863
14970
  // src/runtime/child-pi/child-pi-spawn.ts
14864
14971
  import * as fs18 from "node:fs";
14865
14972
  import * as path18 from "node:path";
@@ -14956,6 +15063,21 @@ function prepareSpawnContext(input, effectiveTask) {
14956
15063
  if (input.runId) built.env.PI_CREW_BROKER_RUN_ID = input.runId;
14957
15064
  if (input.agentId) built.env.PI_CREW_BROKER_TASK_ID = input.agentId;
14958
15065
  }
15066
+ if (input.agentId && isScratchpadEnabledForRole(input.role ?? input.agent.name, input.agent)) {
15067
+ built.env.PI_CREW_SCRATCHPAD = "1";
15068
+ built.env.PI_CREW_TASK_ID = input.agentId;
15069
+ built.env.PI_CREW_ATTEMPT = String(input.attempt ?? 0);
15070
+ if (input.artifactsRoot) {
15071
+ built.env.PI_CREW_ARTIFACTS_ROOT = input.artifactsRoot;
15072
+ }
15073
+ const scratchTempDir = built.tempDir ?? createSafeTempDir(getPiTempBase(), "pi-crew-scratchpad-");
15074
+ built.env.PI_CREW_SCRATCHPAD_SNAPSHOT = resolveRealContainedPath(scratchTempDir, `${input.agentId}.snapshot.json`);
15075
+ const restoreHit = input.artifactsRoot ? findLatestScratchpadSnapshot(input.artifactsRoot, input.agentId) : null;
15076
+ if (restoreHit) {
15077
+ built.env.PI_CREW_SCRATCHPAD_RESTORE = restoreHit.path;
15078
+ built.env.PI_CREW_SCRATCHPAD_RESTORE_MTIME = String(restoreHit.mtimeMs);
15079
+ }
15080
+ }
14959
15081
  if (input.signal?.aborted) {
14960
15082
  return {
14961
15083
  kind: "aborted",
@@ -14983,11 +15105,14 @@ var BASE_ALLOWLIST;
14983
15105
  var init_child_pi_spawn = __esm({
14984
15106
  "src/runtime/child-pi/child-pi-spawn.ts"() {
14985
15107
  "use strict";
15108
+ init_role_tools();
14986
15109
  init_env_allowlist();
14987
15110
  init_env_filter();
14988
15111
  init_internal_error();
15112
+ init_safe_paths();
14989
15113
  init_pi_args();
14990
15114
  init_pi_spawn();
15115
+ init_snapshot_lookup();
14991
15116
  BASE_ALLOWLIST = [
14992
15117
  "PATH",
14993
15118
  "HOME",
@@ -22287,7 +22412,18 @@ var init_model_fallback = __esm({
22287
22412
  /context[_ ]?length[_ ]?exceeded/i,
22288
22413
  /safety/i,
22289
22414
  /is[_ ]?overloaded/i,
22290
- /\b408\b/
22415
+ /\b408\b/,
22416
+ //
22417
+ // EPIPE / broken-pipe. In the child-pi worker path this typically means
22418
+ // the child `pi` process exited (crash or early exit) while the parent
22419
+ // was still writing to its stdin — spawning a fresh child on the next
22420
+ // model in the fallback chain usually recovers. In the network path it
22421
+ // is a transient pipe close. Both are retryable on a different model.
22422
+ // See docs/failure-mode-inventory.md EPIPE gap; NON_RETRYABLE patterns
22423
+ // (auth/billing) are checked first, so an auth error mentioning EPIPE
22424
+ // stays non-retryable.
22425
+ /epipe/i,
22426
+ /broken pipe/i
22291
22427
  ];
22292
22428
  NON_RETRYABLE_MODEL_FAILURE_PATTERNS = [
22293
22429
  /auth(?:entication)?/i,
@@ -23005,12 +23141,12 @@ var init_crew_hooks = __esm({
23005
23141
 
23006
23142
  // src/runtime/skill-effectiveness.ts
23007
23143
  import { existsSync as existsSync23, mkdirSync as mkdirSync14, readFileSync as readFileSync23, writeFileSync as writeFileSync4 } from "node:fs";
23008
- import { dirname as dirname16, join as join29 } from "node:path";
23144
+ import { dirname as dirname16, join as join30 } from "node:path";
23009
23145
  function getSkillMetricsPath(cwd, runId) {
23010
- return join29(projectCrewRoot(cwd), `state/runs/${runId}/skill-metrics.jsonl`);
23146
+ return join30(projectCrewRoot(cwd), `state/runs/${runId}/skill-metrics.jsonl`);
23011
23147
  }
23012
23148
  function getSkillActivationsPath(cwd, runId) {
23013
- return join29(projectCrewRoot(cwd), `state/runs/${runId}/skill-activations.jsonl`);
23149
+ return join30(projectCrewRoot(cwd), `state/runs/${runId}/skill-activations.jsonl`);
23014
23150
  }
23015
23151
  function ensureSkillMetricsDir(cwd, runId) {
23016
23152
  const dir = dirname16(getSkillMetricsPath(cwd, runId));
@@ -23725,10 +23861,12 @@ var init_team_tool_schema = __esm({
23725
23861
  })
23726
23862
  ),
23727
23863
  budgetTotal: Type.Optional(
23864
+ // Empty-string unset marker accepted (Tier-9: models emit "" when unset).
23728
23865
  // 0 accepted as "unset/disabled" (models emit 0 for off); still rejects 1-999
23729
23866
  // as the MISCONFIGURATION GUARD against typo'd silent-abort configs.
23730
23867
  Type.Union(
23731
23868
  [
23869
+ Type.Literal(""),
23732
23870
  Type.Literal(0),
23733
23871
  Type.Number({
23734
23872
  minimum: 1e3
@@ -33743,35 +33881,6 @@ var init_live_session_runtime = __esm({
33743
33881
  }
33744
33882
  });
33745
33883
 
33746
- // src/runtime/role-permission.ts
33747
- function permissionForRole(role) {
33748
- if (READ_ONLY_ROLES.has(role)) return "read_only";
33749
- if (WRITE_ROLES.has(role)) return "workspace_write";
33750
- return "read_only";
33751
- }
33752
- function currentCrewRole(env = process.env) {
33753
- return env.PI_CREW_ROLE?.trim() || env.PI_TEAMS_ROLE?.trim() || void 0;
33754
- }
33755
- function checkSubagentSpawnPermission(role) {
33756
- if (!role) return { allowed: true, mode: "workspace_write" };
33757
- const mode = permissionForRole(role);
33758
- if (mode === "read_only")
33759
- return {
33760
- allowed: false,
33761
- mode,
33762
- reason: `Role '${role}' is read-only and cannot spawn additional subagents.`
33763
- };
33764
- return { allowed: true, mode };
33765
- }
33766
- var READ_ONLY_ROLES, WRITE_ROLES;
33767
- var init_role_permission = __esm({
33768
- "src/runtime/role-permission.ts"() {
33769
- "use strict";
33770
- READ_ONLY_ROLES = /* @__PURE__ */ new Set(["explorer", "reviewer", "security-reviewer", "analyst", "critic", "planner"]);
33771
- WRITE_ROLES = /* @__PURE__ */ new Set(["executor", "test-engineer", "writer", "verifier", "agent", "cold-verifier", "chain-executor", "worker"]);
33772
- }
33773
- });
33774
-
33775
33884
  // src/state/coordination/task-claims.ts
33776
33885
  import { randomUUID as randomUUID4, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
33777
33886
  function createTaskClaim(owner, leaseMs = 5 * 6e4, now = /* @__PURE__ */ new Date()) {
@@ -39426,6 +39535,7 @@ function serializeAgent(agent) {
39426
39535
  line("fallbackModels", agent.fallbackModels),
39427
39536
  line("thinking", agent.thinking),
39428
39537
  line("tools", agent.tools),
39538
+ line("scratchpad", agent.scratchpad),
39429
39539
  agent.extensions !== void 0 ? line("extensions", agent.extensions) ?? "extensions:" : void 0,
39430
39540
  line("skills", agent.skills),
39431
39541
  line("systemPromptMode", agent.systemPromptMode),
@@ -39476,6 +39586,7 @@ function serializeTeam(team) {
39476
39586
  team.defaultWorkflow ? `defaultWorkflow: ${team.defaultWorkflow}` : void 0,
39477
39587
  team.workspaceMode ? `workspaceMode: ${team.workspaceMode}` : void 0,
39478
39588
  team.maxConcurrency !== void 0 ? `maxConcurrency: ${team.maxConcurrency}` : void 0,
39589
+ team.observability !== void 0 ? `observability: ${team.observability}` : void 0,
39479
39590
  line2("triggers", team.routing?.triggers),
39480
39591
  line2("useWhen", team.routing?.useWhen),
39481
39592
  line2("avoidWhen", team.routing?.avoidWhen),
@@ -40772,9 +40883,9 @@ var init_handle_settings = __esm({
40772
40883
 
40773
40884
  // src/extension/team-tool/workflow-manage.ts
40774
40885
  import { existsSync as existsSync41, readFileSync as readFileSync40, rmSync as rmSync14, writeFileSync as writeFileSync7 } from "node:fs";
40775
- import { dirname as dirname29, join as join45 } from "node:path";
40886
+ import { dirname as dirname29, join as join46 } from "node:path";
40776
40887
  function allowedWorkflowDirs(cwd) {
40777
- return [join45(projectCrewRoot(cwd), "workflows"), join45(userPiRoot(), "workflows"), join45(packageRoot(), "workflows")];
40888
+ return [join46(projectCrewRoot(cwd), "workflows"), join46(userPiRoot(), "workflows"), join46(packageRoot(), "workflows")];
40778
40889
  }
40779
40890
  function validateScriptContent(content) {
40780
40891
  for (const pattern of FORBIDDEN_PATTERNS) {
@@ -40786,7 +40897,7 @@ function validateScriptContent(content) {
40786
40897
  }
40787
40898
  function resolveWorkflowWritePath(cwd, name, scope = "project") {
40788
40899
  assertSafePathId("workflowName", name);
40789
- const base = scope === "user" ? join45(userPiRoot(), "workflows") : join45(projectCrewRoot(cwd), "workflows");
40900
+ const base = scope === "user" ? join46(userPiRoot(), "workflows") : join46(projectCrewRoot(cwd), "workflows");
40790
40901
  return resolveRealContainedPath(base, `${name}.dwf.ts`);
40791
40902
  }
40792
40903
  function handleWorkflowCreate(params, ctx) {
@@ -41640,7 +41751,7 @@ var init_async_runner = __esm({
41640
41751
  });
41641
41752
 
41642
41753
  // src/runtime/goal-workflow/goal-state-store.ts
41643
- import { closeSync as closeSync10, existsSync as existsSync44, mkdirSync as mkdirSync26, openSync as openSync10, readdirSync as readdirSync20, readFileSync as readFileSync42, statSync as statSync33, unlinkSync as unlinkSync7 } from "node:fs";
41754
+ import { closeSync as closeSync10, existsSync as existsSync44, mkdirSync as mkdirSync26, openSync as openSync10, readdirSync as readdirSync21, readFileSync as readFileSync42, statSync as statSync33, unlinkSync as unlinkSync7 } from "node:fs";
41644
41755
  import { dirname as dirname32 } from "node:path";
41645
41756
  function resolveGoalsRoot(cwd) {
41646
41757
  const crewRoot = projectCrewRoot(cwd) ?? userCrewRoot();
@@ -41800,7 +41911,7 @@ var init_goal_state_store = __esm({
41800
41911
  try {
41801
41912
  const root = resolveGoalsRoot(this.cwd);
41802
41913
  if (!existsSync44(root)) return [];
41803
- const entries = readdirSync20(root);
41914
+ const entries = readdirSync21(root);
41804
41915
  const goals = [];
41805
41916
  for (const entry of entries) {
41806
41917
  if (!entry.endsWith(".json")) continue;
@@ -41864,7 +41975,7 @@ var init_verification_integrity = __esm({
41864
41975
 
41865
41976
  // src/runtime/workspace-lock.ts
41866
41977
  import { createHash as createHash7 } from "node:crypto";
41867
- import { closeSync as closeSync11, existsSync as existsSync45, mkdirSync as mkdirSync27, openSync as openSync11, readdirSync as readdirSync21, readFileSync as readFileSync44, statSync as statSync35, unlinkSync as unlinkSync8, writeFileSync as writeFileSync8 } from "node:fs";
41978
+ import { closeSync as closeSync11, existsSync as existsSync45, mkdirSync as mkdirSync27, openSync as openSync11, readdirSync as readdirSync22, readFileSync as readFileSync44, statSync as statSync35, unlinkSync as unlinkSync8, writeFileSync as writeFileSync8 } from "node:fs";
41868
41979
  import * as path50 from "node:path";
41869
41980
  function workspaceLockPath(cwd) {
41870
41981
  const absCwd = path50.resolve(cwd);
@@ -45069,7 +45180,8 @@ __export(crash_recovery_exports, {
45069
45180
  detectInterruptedRuns: () => detectInterruptedRuns,
45070
45181
  purgeStaleActiveRunIndex: () => purgeStaleActiveRunIndex,
45071
45182
  readManifestWithTransientRetry: () => readManifestWithTransientRetry,
45072
- reconcileAllStaleRuns: () => reconcileAllStaleRuns
45183
+ reconcileAllStaleRuns: () => reconcileAllStaleRuns,
45184
+ shouldRecoverTask: () => shouldRecoverTask
45073
45185
  });
45074
45186
  import * as fs63 from "node:fs";
45075
45187
  import * as path52 from "node:path";
@@ -48365,6 +48477,9 @@ function taskHasObservableWorkerActivity(task) {
48365
48477
  (task.agentProgress?.toolCount ?? 0) > 0 || task.usage || task.transcriptArtifact || task.modelAttempts?.some((attempt) => attempt.success) || task.jsonEvents
48366
48478
  );
48367
48479
  }
48480
+ function taskHasEmptyResult(task) {
48481
+ return Boolean(task.resultArtifact && task.resultArtifact.sizeBytes === 0);
48482
+ }
48368
48483
  function resolveEffectivenessGuardMode(runtimeConfig, manifest) {
48369
48484
  const configured = runtimeConfig?.effectivenessGuard;
48370
48485
  if (configured === "off" || configured === "warn" || configured === "block" || configured === "fail") return configured;
@@ -48373,7 +48488,7 @@ function resolveEffectivenessGuardMode(runtimeConfig, manifest) {
48373
48488
  }
48374
48489
  function evaluateRunEffectiveness(input) {
48375
48490
  const completedTasks = input.tasks.filter((task) => task.status === "completed");
48376
- const noObservedWorkTasks = completedTasks.filter((task) => !taskHasObservableWorkerActivity(task));
48491
+ const noObservedWorkTasks = completedTasks.filter((task) => !taskHasObservableWorkerActivity(task) || taskHasEmptyResult(task));
48377
48492
  const needsAttentionTasks = input.tasks.filter((task) => task.agentProgress?.activityState === "needs_attention");
48378
48493
  const workerExecution = input.executeWorkers ? "enabled" : "disabled/scaffold";
48379
48494
  const guardMode = resolveEffectivenessGuardMode(input.runtimeConfig, input.manifest);
@@ -49235,112 +49350,6 @@ var init_correlation = __esm({
49235
49350
  }
49236
49351
  });
49237
49352
 
49238
- // src/plugins/plugin-registry.ts
49239
- var PluginRegistry;
49240
- var init_plugin_registry = __esm({
49241
- "src/plugins/plugin-registry.ts"() {
49242
- "use strict";
49243
- PluginRegistry = class {
49244
- plugins = [];
49245
- register(plugin) {
49246
- this.plugins.push(plugin);
49247
- }
49248
- activePlugins(allDeps) {
49249
- return this.plugins.filter(
49250
- (p) => p.enablers.some((enabler) => {
49251
- if (enabler.endsWith("/")) {
49252
- return allDeps.some((d) => d.startsWith(enabler));
49253
- }
49254
- return allDeps.includes(enabler);
49255
- })
49256
- );
49257
- }
49258
- allPlugins() {
49259
- return [...this.plugins];
49260
- }
49261
- };
49262
- }
49263
- });
49264
-
49265
- // src/plugins/plugin-define.ts
49266
- function definePlugin(spec) {
49267
- return spec;
49268
- }
49269
- var init_plugin_define = __esm({
49270
- "src/plugins/plugin-define.ts"() {
49271
- "use strict";
49272
- }
49273
- });
49274
-
49275
- // src/plugins/plugins/nextjs.ts
49276
- var NextJsPlugin;
49277
- var init_nextjs = __esm({
49278
- "src/plugins/plugins/nextjs.ts"() {
49279
- "use strict";
49280
- init_plugin_define();
49281
- NextJsPlugin = definePlugin({
49282
- name: "nextjs",
49283
- enablers: ["next"],
49284
- entryPatterns: [
49285
- "src/app/**/*.{ts,tsx}",
49286
- "src/pages/**/*.{ts,tsx}",
49287
- "src/app/**/page.{ts,tsx}",
49288
- "src/app/**/layout.{ts,tsx}",
49289
- "src/app/**/route.{ts,tsx}",
49290
- "middleware.{ts,js}",
49291
- "next.config.{ts,js,mjs}"
49292
- ],
49293
- configPatterns: ["next.config.{ts,js,mjs}"],
49294
- toolingDependencies: ["next", "@next/font", "@next/mdx"],
49295
- pathAliases: [["~", "src"]],
49296
- virtualModulePrefixes: ["next:"]
49297
- });
49298
- }
49299
- });
49300
-
49301
- // src/plugins/plugins/vite.ts
49302
- var VitePlugin;
49303
- var init_vite = __esm({
49304
- "src/plugins/plugins/vite.ts"() {
49305
- "use strict";
49306
- init_plugin_define();
49307
- VitePlugin = definePlugin({
49308
- name: "vite",
49309
- enablers: ["vite", "rolldown-vite"],
49310
- entryPatterns: ["src/main.{ts,tsx,js,jsx}", "src/index.{ts,tsx,js,jsx}", "index.html"],
49311
- configPatterns: ["vite.config.{ts,js,mts,mjs}"],
49312
- toolingDependencies: ["vite"],
49313
- virtualModulePrefixes: ["virtual:"]
49314
- });
49315
- }
49316
- });
49317
-
49318
- // src/plugins/plugins/vitest.ts
49319
- var VitestPlugin;
49320
- var init_vitest = __esm({
49321
- "src/plugins/plugins/vitest.ts"() {
49322
- "use strict";
49323
- init_plugin_define();
49324
- VitestPlugin = definePlugin({
49325
- name: "vitest",
49326
- enablers: ["vitest"],
49327
- entryPatterns: ["**/*.test.{ts,tsx}", "**/*.spec.{ts,tsx}", "src/**/*.test.{ts,tsx}", "src/**/*.spec.{ts,tsx}"],
49328
- configPatterns: ["vitest.config.{ts,js,mjs}", "vite.config.ts"],
49329
- toolingDependencies: ["vitest"]
49330
- });
49331
- }
49332
- });
49333
-
49334
- // src/plugins/plugins/index.ts
49335
- var init_plugins = __esm({
49336
- "src/plugins/plugins/index.ts"() {
49337
- "use strict";
49338
- init_nextjs();
49339
- init_vite();
49340
- init_vitest();
49341
- }
49342
- });
49343
-
49344
49353
  // src/runtime/task-health.ts
49345
49354
  function scoreToGrade(score) {
49346
49355
  if (score >= 90) return "A";
@@ -50380,6 +50389,166 @@ var init_group_join = __esm({
50380
50389
  }
50381
50390
  });
50382
50391
 
50392
+ // src/runtime/scheduling/task-graph-scheduler.ts
50393
+ function buildTaskGraphIndex(tasks) {
50394
+ const cached2 = taskGraphIndexCache.get(tasks);
50395
+ if (cached2) return cached2;
50396
+ const fresh = {
50397
+ doneSteps: new Set(
50398
+ tasks.filter((task) => task.status === "completed").map((task) => task.stepId).filter((id) => id !== void 0)
50399
+ ),
50400
+ idMap: new Map(tasks.map((task) => [task.id, task])),
50401
+ stepToTaskId: new Map(
50402
+ tasks.map((task) => [task.stepId, task.id]).filter((entry) => entry[0] !== void 0)
50403
+ )
50404
+ };
50405
+ taskGraphIndexCache.set(tasks, fresh);
50406
+ return fresh;
50407
+ }
50408
+ function dependencySatisfied(task, doneStepIds, idMap, stepMap) {
50409
+ return task.dependsOn.every((dependency) => {
50410
+ if (doneStepIds.has(dependency)) return true;
50411
+ const taskId = stepMap.get(dependency) ?? dependency;
50412
+ return idMap.get(taskId)?.status === "completed";
50413
+ });
50414
+ }
50415
+ function withQueue(task, index) {
50416
+ let resolvedQueue;
50417
+ if (task.status === "queued") {
50418
+ const isReady = dependencySatisfied(task, index.doneSteps, index.idMap, index.stepToTaskId);
50419
+ resolvedQueue = isReady ? "ready" : "blocked";
50420
+ } else if (task.status === "running") {
50421
+ resolvedQueue = "running";
50422
+ } else if (task.status === "completed" || task.status === "skipped" || task.status === "needs_attention") {
50423
+ resolvedQueue = "done";
50424
+ } else {
50425
+ resolvedQueue = "blocked";
50426
+ }
50427
+ if (task.graph && task.graph.queue === resolvedQueue) {
50428
+ return task;
50429
+ }
50430
+ return {
50431
+ ...task,
50432
+ graph: task.graph ? { ...task.graph, queue: resolvedQueue } : task.graph
50433
+ };
50434
+ }
50435
+ function ensureIndex(tasks, index) {
50436
+ return index ?? buildTaskGraphIndex(tasks);
50437
+ }
50438
+ function refreshTaskGraphQueues(tasks, index) {
50439
+ const resolved = ensureIndex(tasks, index);
50440
+ return tasks.map((task) => withQueue(task, resolved));
50441
+ }
50442
+ function taskGraphSnapshot(tasks, index) {
50443
+ const refreshed = refreshTaskGraphQueues(tasks, index);
50444
+ return {
50445
+ ready: refreshed.filter((task) => task.status === "queued" && task.graph?.queue === "ready").map((task) => task.id),
50446
+ blocked: refreshed.filter((task) => task.status === "queued" && task.graph?.queue === "blocked").map((task) => task.id),
50447
+ running: refreshed.filter((task) => task.status === "running").map((task) => task.id),
50448
+ done: refreshed.filter((task) => task.status === "completed" || task.status === "skipped").map((task) => task.id),
50449
+ failed: refreshed.filter((task) => task.status === "failed").map((task) => task.id),
50450
+ cancelled: refreshed.filter((task) => task.status === "cancelled").map((task) => task.id)
50451
+ };
50452
+ }
50453
+ var taskGraphIndexCache;
50454
+ var init_task_graph_scheduler = __esm({
50455
+ "src/runtime/scheduling/task-graph-scheduler.ts"() {
50456
+ "use strict";
50457
+ taskGraphIndexCache = /* @__PURE__ */ new WeakMap();
50458
+ }
50459
+ });
50460
+
50461
+ // src/runtime/merge-gate.ts
50462
+ function isNonTerminalTaskStatus(status) {
50463
+ return status === "queued" || status === "running" || status === "waiting";
50464
+ }
50465
+ function safeFinishedAt(task) {
50466
+ if (!task.finishedAt) return -Infinity;
50467
+ const ms = new Date(task.finishedAt).getTime();
50468
+ return Number.isNaN(ms) ? Infinity : ms;
50469
+ }
50470
+ function isMalformedFinishedAtReplacement(currentTime, updatedTime) {
50471
+ return !Number.isFinite(currentTime) && Number.isFinite(updatedTime);
50472
+ }
50473
+ function statusMergeKey(from, to) {
50474
+ return `${from}->${to}`;
50475
+ }
50476
+ function shouldMergeTaskUpdate(current, updated) {
50477
+ if (REJECTED_STATUS_MERGE_TRANSITIONS.has(statusMergeKey(current.status, updated.status))) return false;
50478
+ if (current.status === updated.status && updated.status === "running" && current.resultArtifact && !updated.resultArtifact)
50479
+ return false;
50480
+ if (current.status === updated.status && current.status === "completed" && current.resultArtifact && !updated.resultArtifact)
50481
+ return false;
50482
+ if (current.finishedAt !== void 0 && updated.finishedAt !== void 0) {
50483
+ const currentTime = safeFinishedAt(current);
50484
+ const updatedTime = safeFinishedAt(updated);
50485
+ if (!Number.isFinite(currentTime)) {
50486
+ console.warn(`[merge-gate] Task ${current.id} has malformed finishedAt: ${current.finishedAt}`);
50487
+ }
50488
+ if (isMalformedFinishedAtReplacement(currentTime, updatedTime)) {
50489
+ return true;
50490
+ }
50491
+ if (updatedTime < currentTime) return false;
50492
+ }
50493
+ if (!updated.finishedAt && !isNonTerminalTaskStatus(updated.status)) return false;
50494
+ 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;
50495
+ return hasMeaningfulUpdate;
50496
+ }
50497
+ function mergeTaskUpdatesPreservingTerminal(base, results) {
50498
+ const indexById = /* @__PURE__ */ new Map();
50499
+ for (const task of base) indexById.set(task.id, task);
50500
+ let skipped = 0;
50501
+ for (const result4 of results) {
50502
+ for (const updated of result4.tasks) {
50503
+ const current = indexById.get(updated.id);
50504
+ if (!current) continue;
50505
+ if (!shouldMergeTaskUpdate(current, updated)) {
50506
+ console.debug("[merge-gate] Skipping stale merge for task", updated.id, {
50507
+ currentStatus: current.status,
50508
+ updatedStatus: updated.status,
50509
+ currentFinishedAt: current.finishedAt,
50510
+ updatedFinishedAt: updated.finishedAt
50511
+ });
50512
+ skipped += 1;
50513
+ continue;
50514
+ }
50515
+ indexById.set(updated.id, updated);
50516
+ }
50517
+ }
50518
+ const merged = base.map((task) => indexById.get(task.id) ?? task);
50519
+ void skipped;
50520
+ return refreshTaskGraphQueues(merged);
50521
+ }
50522
+ var REJECTED_STATUS_MERGE_TRANSITIONS, __test__shouldMergeTaskUpdate, __test__mergeTaskUpdates;
50523
+ var init_merge_gate = __esm({
50524
+ "src/runtime/merge-gate.ts"() {
50525
+ "use strict";
50526
+ init_contracts();
50527
+ init_task_graph_scheduler();
50528
+ REJECTED_STATUS_MERGE_TRANSITIONS = (() => {
50529
+ const rejected = /* @__PURE__ */ new Set();
50530
+ for (const from of TEAM_TASK_STATUSES) {
50531
+ if (!TEAM_TERMINAL_TASK_STATUSES.has(from)) continue;
50532
+ for (const to of TEAM_TASK_STATUSES) {
50533
+ if (!TEAM_TERMINAL_TASK_STATUSES.has(to)) rejected.add(statusMergeKey(from, to));
50534
+ }
50535
+ }
50536
+ rejected.add(statusMergeKey("waiting", "running"));
50537
+ const completedIntegrityFlips = [
50538
+ ["completed", "failed"],
50539
+ ["completed", "needs_attention"],
50540
+ ["failed", "completed"],
50541
+ ["cancelled", "completed"],
50542
+ ["needs_attention", "completed"]
50543
+ ];
50544
+ for (const [from, to] of completedIntegrityFlips) rejected.add(statusMergeKey(from, to));
50545
+ return rejected;
50546
+ })();
50547
+ __test__shouldMergeTaskUpdate = shouldMergeTaskUpdate;
50548
+ __test__mergeTaskUpdates = mergeTaskUpdatesPreservingTerminal;
50549
+ }
50550
+ });
50551
+
50383
50552
  // src/runtime/model/runtime-policy.ts
50384
50553
  function resolveTaskRuntimeKind(globalKind, role, isolationPolicy, env = process.env) {
50385
50554
  if (globalKind === "scaffold") return "scaffold";
@@ -51732,75 +51901,6 @@ var init_task_graph = __esm({
51732
51901
  }
51733
51902
  });
51734
51903
 
51735
- // src/runtime/scheduling/task-graph-scheduler.ts
51736
- function buildTaskGraphIndex(tasks) {
51737
- const cached2 = taskGraphIndexCache.get(tasks);
51738
- if (cached2) return cached2;
51739
- const fresh = {
51740
- doneSteps: new Set(
51741
- tasks.filter((task) => task.status === "completed").map((task) => task.stepId).filter((id) => id !== void 0)
51742
- ),
51743
- idMap: new Map(tasks.map((task) => [task.id, task])),
51744
- stepToTaskId: new Map(
51745
- tasks.map((task) => [task.stepId, task.id]).filter((entry) => entry[0] !== void 0)
51746
- )
51747
- };
51748
- taskGraphIndexCache.set(tasks, fresh);
51749
- return fresh;
51750
- }
51751
- function dependencySatisfied(task, doneStepIds, idMap, stepMap) {
51752
- return task.dependsOn.every((dependency) => {
51753
- if (doneStepIds.has(dependency)) return true;
51754
- const taskId = stepMap.get(dependency) ?? dependency;
51755
- return idMap.get(taskId)?.status === "completed";
51756
- });
51757
- }
51758
- function withQueue(task, index) {
51759
- let resolvedQueue;
51760
- if (task.status === "queued") {
51761
- const isReady = dependencySatisfied(task, index.doneSteps, index.idMap, index.stepToTaskId);
51762
- resolvedQueue = isReady ? "ready" : "blocked";
51763
- } else if (task.status === "running") {
51764
- resolvedQueue = "running";
51765
- } else if (task.status === "completed" || task.status === "skipped" || task.status === "needs_attention") {
51766
- resolvedQueue = "done";
51767
- } else {
51768
- resolvedQueue = "blocked";
51769
- }
51770
- if (task.graph && task.graph.queue === resolvedQueue) {
51771
- return task;
51772
- }
51773
- return {
51774
- ...task,
51775
- graph: task.graph ? { ...task.graph, queue: resolvedQueue } : task.graph
51776
- };
51777
- }
51778
- function ensureIndex(tasks, index) {
51779
- return index ?? buildTaskGraphIndex(tasks);
51780
- }
51781
- function refreshTaskGraphQueues(tasks, index) {
51782
- const resolved = ensureIndex(tasks, index);
51783
- return tasks.map((task) => withQueue(task, resolved));
51784
- }
51785
- function taskGraphSnapshot(tasks, index) {
51786
- const refreshed = refreshTaskGraphQueues(tasks, index);
51787
- return {
51788
- ready: refreshed.filter((task) => task.status === "queued" && task.graph?.queue === "ready").map((task) => task.id),
51789
- blocked: refreshed.filter((task) => task.status === "queued" && task.graph?.queue === "blocked").map((task) => task.id),
51790
- running: refreshed.filter((task) => task.status === "running").map((task) => task.id),
51791
- done: refreshed.filter((task) => task.status === "completed" || task.status === "skipped").map((task) => task.id),
51792
- failed: refreshed.filter((task) => task.status === "failed").map((task) => task.id),
51793
- cancelled: refreshed.filter((task) => task.status === "cancelled").map((task) => task.id)
51794
- };
51795
- }
51796
- var taskGraphIndexCache;
51797
- var init_task_graph_scheduler = __esm({
51798
- "src/runtime/scheduling/task-graph-scheduler.ts"() {
51799
- "use strict";
51800
- taskGraphIndexCache = /* @__PURE__ */ new WeakMap();
51801
- }
51802
- });
51803
-
51804
51904
  // src/extension/knowledge-injection.ts
51805
51905
  import * as fs75 from "node:fs";
51806
51906
  import * as path65 from "node:path";
@@ -53188,6 +53288,20 @@ function detectRetryableModelFailureFromOutput(parsed) {
53188
53288
  }
53189
53289
  return void 0;
53190
53290
  }
53291
+ function evidenceStatusFor(childResult) {
53292
+ return childResult.exitStatus?.cancelled ? "cancelled" : childResult.error || childResult.exitCode && childResult.exitCode !== 0 ? "failed" : "completed";
53293
+ }
53294
+ function attemptErrorFor(childResult, parsedOutput, taskId) {
53295
+ let err2 = childResult.error || (childResult.exitCode && childResult.exitCode !== 0 ? childResult.stderr || `Child Pi exited with ${childResult.exitCode}` : void 0);
53296
+ if (childResult.exitStatus?.timedOut) {
53297
+ err2 = errors.childTimeout({ taskId, stderr: childResult.stderr }).message;
53298
+ }
53299
+ if (!err2 && parsedOutput) {
53300
+ const rateLimitErr = detectRetryableModelFailureFromOutput(parsedOutput);
53301
+ if (rateLimitErr) err2 = rateLimitErr;
53302
+ }
53303
+ return err2;
53304
+ }
53191
53305
  async function runChildProcessTask(ctx) {
53192
53306
  const input = ctx.input;
53193
53307
  const manifest = ctx.manifest;
@@ -53371,6 +53485,7 @@ async function runChildProcessTask(ctx) {
53371
53485
  runId: manifest.runId,
53372
53486
  agentId: task.id,
53373
53487
  artifactsRoot: manifest.artifactsRoot,
53488
+ attempt: i,
53374
53489
  steeringFile: resolveRealContainedPath(`${manifest.artifactsRoot}/steering`, `${task.id}.jsonl`),
53375
53490
  onSpawn: (pid) => {
53376
53491
  try {
@@ -53459,7 +53574,7 @@ async function runChildProcessTask(ctx) {
53459
53574
  input.signal.removeEventListener("abort", externalAbortListener);
53460
53575
  }
53461
53576
  }
53462
- const evidenceStatus = childResult.exitStatus?.cancelled ? "cancelled" : childResult.error || childResult.exitCode && childResult.exitCode !== 0 ? "failed" : "completed";
53577
+ const evidenceStatus = evidenceStatusFor(childResult);
53463
53578
  terminalEvidence = [
53464
53579
  ...terminalEvidence,
53465
53580
  {
@@ -53505,17 +53620,7 @@ async function runChildProcessTask(ctx) {
53505
53620
  parsedOutput = parsePiJsonOutput(transcriptText2);
53506
53621
  rawFinalText = childResult.rawFinalText;
53507
53622
  intermediateFindings = childResult.intermediateFindings;
53508
- error = childResult.error || (childResult.exitCode && childResult.exitCode !== 0 ? childResult.stderr || `Child Pi exited with ${childResult.exitCode}` : void 0);
53509
- if (childResult.exitStatus?.timedOut) {
53510
- error = errors.childTimeout({
53511
- taskId: task.id,
53512
- stderr: childResult.stderr
53513
- }).message;
53514
- }
53515
- if (!error && parsedOutput) {
53516
- const rateLimitErr = detectRetryableModelFailureFromOutput(parsedOutput);
53517
- if (rateLimitErr) error = rateLimitErr;
53518
- }
53623
+ error = attemptErrorFor(childResult, parsedOutput, task.id);
53519
53624
  persistHeartbeat(true);
53520
53625
  persistChildProgress({ type: "attempt_finished" }, true);
53521
53626
  const attempt = {
@@ -56692,12 +56797,13 @@ __export(team_runner_exports, {
56692
56797
  drainPendingUnits: () => drainPendingUnits,
56693
56798
  executeTeamRun: () => executeTeamRun,
56694
56799
  hasPendingMutatingTaskAtBoundary: () => hasPendingMutatingTaskAtBoundary,
56695
- mergeTaskUpdatesPreservingTerminal: () => mergeTaskUpdatesPreservingTerminal,
56696
56800
  setRunStatusRunning: () => setRunStatusRunning,
56697
56801
  shouldUseRetry: () => shouldUseRetry
56698
56802
  });
56803
+ import { spawn as spawn6 } from "node:child_process";
56699
56804
  import * as fs86 from "node:fs";
56700
56805
  import * as path72 from "node:path";
56806
+ import { fileURLToPath as fileURLToPath7 } from "node:url";
56701
56807
  function startTeamRunHeartbeat(stateRoot, runId) {
56702
56808
  const heartbeatPath = path72.join(stateRoot, "heartbeat.json");
56703
56809
  const writeHeartbeat = () => {
@@ -56721,6 +56827,87 @@ function startTeamRunHeartbeat(stateRoot, runId) {
56721
56827
  const interval = setInterval(writeHeartbeat, 6e4);
56722
56828
  return () => clearInterval(interval);
56723
56829
  }
56830
+ function perfScriptPath(scriptName) {
56831
+ try {
56832
+ const candidates = [
56833
+ fileURLToPath7(new URL(`../../scripts/${scriptName}`, import.meta.url)),
56834
+ fileURLToPath7(new URL(`../scripts/${scriptName}`, import.meta.url))
56835
+ ];
56836
+ return candidates.find((p) => fs86.existsSync(p));
56837
+ } catch {
56838
+ return void 0;
56839
+ }
56840
+ }
56841
+ function startPerfSampler(manifest, team) {
56842
+ const marker = (msg) => {
56843
+ try {
56844
+ fs86.appendFileSync(path72.join(manifest.artifactsRoot, "perf-obs.log"), `[${(/* @__PURE__ */ new Date()).toISOString()}] ${msg}
56845
+ `);
56846
+ } catch {
56847
+ }
56848
+ };
56849
+ marker(`startPerfSampler entered (team=${team.name} observability=${String(team.observability)} importMetaUrl=${import.meta.url})`);
56850
+ if (team.observability !== true) {
56851
+ marker(`SKIP: observability=${String(team.observability)} !== true`);
56852
+ return;
56853
+ }
56854
+ const samplerPath = perfScriptPath("resource-sampler.mjs");
56855
+ if (!samplerPath) {
56856
+ marker(`SKIP: resource-sampler.mjs not found (importMetaUrl=${import.meta.url})`);
56857
+ return;
56858
+ }
56859
+ marker(`spawning sampler from ${samplerPath}`);
56860
+ const crewRoot = path72.dirname(path72.dirname(path72.dirname(manifest.stateRoot)));
56861
+ const outPath = path72.join(manifest.artifactsRoot, "resources.jsonl");
56862
+ const logPath = path72.join(manifest.artifactsRoot, "perf-obs.log");
56863
+ try {
56864
+ const child = spawn6(
56865
+ process.execPath,
56866
+ [
56867
+ "--experimental-strip-types",
56868
+ samplerPath,
56869
+ "--watch-run",
56870
+ manifest.runId,
56871
+ "--crew-root",
56872
+ crewRoot,
56873
+ "--interval",
56874
+ String(OBSERVABILITY_INTERVAL_MS),
56875
+ "--out",
56876
+ outPath
56877
+ ],
56878
+ { detached: true, stdio: ["ignore", "ignore", "pipe"] }
56879
+ );
56880
+ child.stderr?.on("data", (d) => {
56881
+ try {
56882
+ fs86.appendFileSync(logPath, String(d));
56883
+ } catch {
56884
+ }
56885
+ });
56886
+ child.unref();
56887
+ } catch (err2) {
56888
+ console.warn(`[perf-obs] sampler spawn failed for ${manifest.runId}: ${String(err2)}`);
56889
+ }
56890
+ }
56891
+ function schedulePerfAnalyze(manifest, team) {
56892
+ if (team.observability !== true) return;
56893
+ const analyzePath = perfScriptPath("analyze-run.mjs");
56894
+ const resourcesPath = path72.join(manifest.artifactsRoot, "resources.jsonl");
56895
+ if (!analyzePath || !fs86.existsSync(resourcesPath)) return;
56896
+ const crewRoot = path72.dirname(path72.dirname(path72.dirname(manifest.stateRoot)));
56897
+ const timer = setTimeout(() => {
56898
+ try {
56899
+ const child = spawn6(
56900
+ process.execPath,
56901
+ ["--experimental-strip-types", analyzePath, manifest.runId, "--crew-root", crewRoot, "--resources", resourcesPath],
56902
+ { detached: true, stdio: "ignore" }
56903
+ );
56904
+ child.unref();
56905
+ } catch (err2) {
56906
+ console.warn(`[perf-obs] analyze spawn failed for ${manifest.runId}: ${String(err2)}`);
56907
+ }
56908
+ }, OBSERVABILITY_ANALYZE_DELAY_MS);
56909
+ timer.unref();
56910
+ }
56724
56911
  function checkPerTaskBudget(tasks, budgetTotal, budgetWarning, budgetAbort, fairShareFraction = 0.5) {
56725
56912
  const usage = aggregateUsage(tasks);
56726
56913
  const totalUsed = (usage?.input ?? 0) + (usage?.output ?? 0) + (usage?.cacheWrite ?? 0);
@@ -56764,9 +56951,6 @@ function markBlocked(tasks, reason) {
56764
56951
  } : task
56765
56952
  );
56766
56953
  }
56767
- function isNonTerminalTaskStatus(status) {
56768
- return status === "queued" || status === "running" || status === "waiting";
56769
- }
56770
56954
  function cancelNonTerminalTasks(tasks, status, reason, filter, transform) {
56771
56955
  const predicate = filter ?? ((task) => isNonTerminalTaskStatus(task.status));
56772
56956
  return tasks.map((task) => {
@@ -56775,63 +56959,6 @@ function cancelNonTerminalTasks(tasks, status, reason, filter, transform) {
56775
56959
  return transform ? transform(task, terminalised) : terminalised;
56776
56960
  });
56777
56961
  }
56778
- function safeFinishedAt(task) {
56779
- if (!task.finishedAt) return -Infinity;
56780
- const ms = new Date(task.finishedAt).getTime();
56781
- return Number.isNaN(ms) ? Infinity : ms;
56782
- }
56783
- function isMalformedFinishedAtReplacement(currentTime, updatedTime) {
56784
- return !Number.isFinite(currentTime) && Number.isFinite(updatedTime);
56785
- }
56786
- function statusMergeKey(from, to) {
56787
- return `${from}->${to}`;
56788
- }
56789
- function shouldMergeTaskUpdate(current, updated) {
56790
- if (REJECTED_STATUS_MERGE_TRANSITIONS.has(statusMergeKey(current.status, updated.status))) return false;
56791
- if (current.status === updated.status && updated.status === "running" && current.resultArtifact && !updated.resultArtifact)
56792
- return false;
56793
- if (current.status === updated.status && current.status === "completed" && current.resultArtifact && !updated.resultArtifact)
56794
- return false;
56795
- if (current.finishedAt !== void 0 && updated.finishedAt !== void 0) {
56796
- const currentTime = safeFinishedAt(current);
56797
- const updatedTime = safeFinishedAt(updated);
56798
- if (!Number.isFinite(currentTime)) {
56799
- console.warn(`[team-runner] Task ${current.id} has malformed finishedAt: ${current.finishedAt}`);
56800
- }
56801
- if (isMalformedFinishedAtReplacement(currentTime, updatedTime)) {
56802
- return true;
56803
- }
56804
- if (updatedTime < currentTime) return false;
56805
- }
56806
- if (!updated.finishedAt && !isNonTerminalTaskStatus(updated.status)) return false;
56807
- 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;
56808
- return hasMeaningfulUpdate;
56809
- }
56810
- function mergeTaskUpdatesPreservingTerminal(base, results) {
56811
- const indexById = /* @__PURE__ */ new Map();
56812
- for (const task of base) indexById.set(task.id, task);
56813
- let skipped = 0;
56814
- for (const result4 of results) {
56815
- for (const updated of result4.tasks) {
56816
- const current = indexById.get(updated.id);
56817
- if (!current) continue;
56818
- if (!shouldMergeTaskUpdate(current, updated)) {
56819
- console.debug("[team-runner] Skipping stale merge for task", updated.id, {
56820
- currentStatus: current.status,
56821
- updatedStatus: updated.status,
56822
- currentFinishedAt: current.finishedAt,
56823
- updatedFinishedAt: updated.finishedAt
56824
- });
56825
- skipped += 1;
56826
- continue;
56827
- }
56828
- indexById.set(updated.id, updated);
56829
- }
56830
- }
56831
- const merged = base.map((task) => indexById.get(task.id) ?? task);
56832
- void skipped;
56833
- return refreshTaskGraphQueues(merged);
56834
- }
56835
56962
  function formatTaskProgress(task) {
56836
56963
  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}` : ""}`;
56837
56964
  }
@@ -57093,6 +57220,7 @@ async function executeTeamRun(input) {
57093
57220
  }
57094
57221
  void registerRunPromise(manifest.runId);
57095
57222
  const stopTeamHeartbeat = startTeamRunHeartbeat(manifest.stateRoot, manifest.runId);
57223
+ startPerfSampler(manifest, input.team);
57096
57224
  const cleanupUsage = () => {
57097
57225
  for (const task of input.tasks) clearTrackedTaskUsage(task.id);
57098
57226
  };
@@ -57158,6 +57286,7 @@ async function executeTeamRun(input) {
57158
57286
  );
57159
57287
  }
57160
57288
  await flushEventLogBuffer();
57289
+ schedulePerfAnalyze(manifest, input.team);
57161
57290
  return result4;
57162
57291
  } catch (error) {
57163
57292
  stopTeamHeartbeat();
@@ -58245,15 +58374,13 @@ async function executeTeamRunCore(input, manifest, workflow) {
58245
58374
  await drainPendingUnits(pendingUnits, runController);
58246
58375
  }
58247
58376
  }
58248
- var builtInRegistry, REJECTED_STATUS_MERGE_TRANSITIONS, __test__shouldMergeTaskUpdate, __test__mergeTaskUpdates, lastProgressContentHash, __test__lastProgressContentHash, __test__writeProgress, __test__cancelPlanTasks;
58377
+ var OBSERVABILITY_INTERVAL_MS, OBSERVABILITY_ANALYZE_DELAY_MS, lastProgressContentHash, __test__lastProgressContentHash, __test__writeProgress, __test__cancelPlanTasks;
58249
58378
  var init_team_runner = __esm({
58250
58379
  "src/runtime/team-runner.ts"() {
58251
58380
  "use strict";
58252
58381
  init_errors3();
58253
58382
  init_registry2();
58254
58383
  init_correlation();
58255
- init_plugin_registry();
58256
- init_plugins();
58257
58384
  init_atomic_write();
58258
58385
  init_contracts();
58259
58386
  init_locks();
@@ -58271,6 +58398,7 @@ var init_team_runner = __esm({
58271
58398
  init_goal_achievement();
58272
58399
  init_group_join();
58273
58400
  init_live_agent_manager();
58401
+ init_merge_gate();
58274
58402
  init_runtime_policy();
58275
58403
  init_path_overlap();
58276
58404
  init_policy_engine();
@@ -58292,32 +58420,10 @@ var init_team_runner = __esm({
58292
58420
  init_usage_tracker();
58293
58421
  init_workflow_state();
58294
58422
  init_adaptive_plan();
58423
+ init_merge_gate();
58295
58424
  init_adaptive_plan();
58296
- builtInRegistry = new PluginRegistry();
58297
- builtInRegistry.register(NextJsPlugin);
58298
- builtInRegistry.register(VitestPlugin);
58299
- builtInRegistry.register(VitePlugin);
58300
- REJECTED_STATUS_MERGE_TRANSITIONS = (() => {
58301
- const rejected = /* @__PURE__ */ new Set();
58302
- for (const from of TEAM_TASK_STATUSES) {
58303
- if (!TEAM_TERMINAL_TASK_STATUSES.has(from)) continue;
58304
- for (const to of TEAM_TASK_STATUSES) {
58305
- if (!TEAM_TERMINAL_TASK_STATUSES.has(to)) rejected.add(statusMergeKey(from, to));
58306
- }
58307
- }
58308
- rejected.add(statusMergeKey("waiting", "running"));
58309
- const completedIntegrityFlips = [
58310
- ["completed", "failed"],
58311
- ["completed", "needs_attention"],
58312
- ["failed", "completed"],
58313
- ["cancelled", "completed"],
58314
- ["needs_attention", "completed"]
58315
- ];
58316
- for (const [from, to] of completedIntegrityFlips) rejected.add(statusMergeKey(from, to));
58317
- return rejected;
58318
- })();
58319
- __test__shouldMergeTaskUpdate = shouldMergeTaskUpdate;
58320
- __test__mergeTaskUpdates = mergeTaskUpdatesPreservingTerminal;
58425
+ OBSERVABILITY_INTERVAL_MS = 2e3;
58426
+ OBSERVABILITY_ANALYZE_DELAY_MS = 3e3;
58321
58427
  lastProgressContentHash = /* @__PURE__ */ new Map();
58322
58428
  __test__lastProgressContentHash = lastProgressContentHash;
58323
58429
  __test__writeProgress = writeProgress;
@@ -61073,7 +61179,7 @@ __export(dynamic_workflow_runner_exports, {
61073
61179
  runDynamicWorkflow: () => runDynamicWorkflow
61074
61180
  });
61075
61181
  import { readFileSync as readFileSync67 } from "node:fs";
61076
- import { join as join71 } from "node:path";
61182
+ import { join as join72 } from "node:path";
61077
61183
  import { transformSync } from "esbuild";
61078
61184
  function assertStructuredCloneable(value, name) {
61079
61185
  try {
@@ -61085,7 +61191,7 @@ function assertStructuredCloneable(value, name) {
61085
61191
  }
61086
61192
  function resolveScriptPath(workflow, cwd) {
61087
61193
  const crewRoot = projectCrewRoot(cwd);
61088
- const allowedBases = [join71(projectCrewRoot(cwd), "workflows"), join71(userPiRoot(), "workflows"), join71(packageRoot(), "workflows")];
61194
+ const allowedBases = [join72(projectCrewRoot(cwd), "workflows"), join72(userPiRoot(), "workflows"), join72(packageRoot(), "workflows")];
61089
61195
  for (const base of allowedBases) {
61090
61196
  try {
61091
61197
  const real = resolveRealContainedPath(base, workflow.filePath);
@@ -66166,7 +66272,6 @@ var init_mascot = __esm({
66166
66272
  currentArminGrid;
66167
66273
  effectState = {};
66168
66274
  effectDone = false;
66169
- visible = true;
66170
66275
  frame = 0;
66171
66276
  effectPhase = 0;
66172
66277
  gridVersion = 0;
@@ -66262,7 +66367,7 @@ var init_mascot = __esm({
66262
66367
  this.gridVersion++;
66263
66368
  }
66264
66369
  this.invalidate();
66265
- if (this.visible) this.requestRender?.();
66370
+ this.requestRender?.();
66266
66371
  }
66267
66372
  tickArminEffect() {
66268
66373
  switch (this.effect) {
@@ -66469,14 +66574,6 @@ var init_mascot = __esm({
66469
66574
  this.close();
66470
66575
  }
66471
66576
  }
66472
- /**
66473
- * Set whether the mascot is currently visible (not obscured by another
66474
- * overlay). When invisible, tick() skips requestRender so the animation
66475
- * does not trigger needless repaints while hidden.
66476
- */
66477
- setVisible(visible) {
66478
- this.visible = visible;
66479
- }
66480
66577
  dispose() {
66481
66578
  this.doneGuard.called = true;
66482
66579
  if (this.interval) clearInterval(this.interval);
@@ -70525,10 +70622,18 @@ var init_otlp_exporter = __esm({
70525
70622
  });
70526
70623
 
70527
70624
  // src/observability/metrics-primitives.ts
70528
- function enforceLabelCap(map3, metricName) {
70625
+ function getCardinalityEvictions() {
70626
+ return cardinalityEvictions;
70627
+ }
70628
+ function enforceLabelCap(map3, _metricName) {
70529
70629
  while (map3.size > MAX_LABEL_COMBINATIONS) {
70530
70630
  const firstKey = map3.keys().next().value;
70531
- if (firstKey !== void 0) map3.delete(firstKey);
70631
+ if (firstKey !== void 0) {
70632
+ map3.delete(firstKey);
70633
+ cardinalityEvictions++;
70634
+ } else {
70635
+ break;
70636
+ }
70532
70637
  }
70533
70638
  }
70534
70639
  function normalizeLabels(labels = {}) {
@@ -70542,12 +70647,13 @@ function labelKey(labels = {}) {
70542
70647
  function cloneLabels(labels) {
70543
70648
  return { ...labels };
70544
70649
  }
70545
- var DEFAULT_HISTOGRAM_BUCKETS, MAX_LABEL_COMBINATIONS, Metric, Counter, Gauge, Histogram;
70650
+ var DEFAULT_HISTOGRAM_BUCKETS, MAX_LABEL_COMBINATIONS, cardinalityEvictions, Metric, Counter, Gauge, Histogram;
70546
70651
  var init_metrics_primitives = __esm({
70547
70652
  "src/observability/metrics-primitives.ts"() {
70548
70653
  "use strict";
70549
70654
  DEFAULT_HISTOGRAM_BUCKETS = [1, 2, 5, 10, 25, 50, 100, 250, 500, 1e3, 2500, 5e3, 1e4];
70550
70655
  MAX_LABEL_COMBINATIONS = 1e4;
70656
+ cardinalityEvictions = 0;
70551
70657
  Metric = class {
70552
70658
  name;
70553
70659
  description;
@@ -70812,6 +70918,14 @@ function wireEventToMetrics(events, registry2) {
70812
70918
  const deadletterCount = registry2.counter("crew.task.deadletter_total", "Deadletter triggers by reason");
70813
70919
  const overflowCount = registry2.counter("crew.task.overflow_phase_total", "Overflow recovery phase transitions");
70814
70920
  const supervisorContactCount = registry2.counter("crew.task.supervisor_contact_total", "Supervisor contact requests by reason");
70921
+ const unboundedConcurrencyCount = registry2.counter(
70922
+ "crew.limits.unbounded_total",
70923
+ "Runs that enabled allowUnboundedConcurrency (advisory; bypasses hard cap)"
70924
+ );
70925
+ const cardinalityEvictedGauge = registry2.gauge(
70926
+ "crew.metrics.cardinality_evicted",
70927
+ "Cumulative label-combination evictions (non-zero = unreliable aggregation)"
70928
+ );
70815
70929
  registry2.gauge("crew.heartbeat.staleness_ms", "Heartbeat elapsed since last seen, milliseconds");
70816
70930
  const runDuration = registry2.histogram(
70817
70931
  "crew.run.duration_ms",
@@ -70911,11 +71025,21 @@ function wireEventToMetrics(events, registry2) {
70911
71025
  direction: stringValue(item.direction, "unknown")
70912
71026
  });
70913
71027
  }
71028
+ ],
71029
+ [
71030
+ "crew.limits.unbounded",
71031
+ () => {
71032
+ unboundedConcurrencyCount.inc({});
71033
+ }
70914
71034
  ]
70915
71035
  ];
70916
71036
  const unsubscribers = [];
70917
71037
  for (const [event, handler] of handlers) {
70918
71038
  const unsubscribe = events?.on?.(event, (data) => {
71039
+ try {
71040
+ cardinalityEvictedGauge.set({}, getCardinalityEvictions());
71041
+ } catch {
71042
+ }
70919
71043
  try {
70920
71044
  handler(data);
70921
71045
  } catch {
@@ -70936,6 +71060,7 @@ var CANCELLATION_REASON_LABELS;
70936
71060
  var init_event_to_metric = __esm({
70937
71061
  "src/observability/event-to-metric.ts"() {
70938
71062
  "use strict";
71063
+ init_metrics_primitives();
70939
71064
  CANCELLATION_REASON_LABELS = /* @__PURE__ */ new Set([
70940
71065
  "caller_cancelled",
70941
71066
  "leader_interrupted",
@@ -72238,20 +72363,20 @@ init_internal_error();
72238
72363
 
72239
72364
  // src/extension/crew-vibes/config.ts
72240
72365
  import { existsSync as existsSync75, mkdirSync as mkdirSync42, readFileSync as readFileSync74, writeFileSync as writeFileSync9 } from "node:fs";
72241
- import { dirname as dirname39, join as join77 } from "node:path";
72366
+ import { dirname as dirname39, join as join78 } from "node:path";
72242
72367
 
72243
72368
  // src/extension/crew-vibes/font-detect.ts
72244
72369
  import { existsSync as existsSync74, readFileSync as readFileSync73 } from "node:fs";
72245
72370
  import { homedir as homedir11, platform } from "node:os";
72246
- import { join as join76 } from "node:path";
72371
+ import { join as join77 } from "node:path";
72247
72372
  function fontPath() {
72248
72373
  const os18 = platform();
72249
72374
  const home = homedir11();
72250
- if (os18 === "darwin") return join76(home, "Library", "Fonts", "crew-vibes.ttf");
72251
- if (os18 === "linux") return join76(home, ".local", "share", "fonts", "crew-vibes.ttf");
72375
+ if (os18 === "darwin") return join77(home, "Library", "Fonts", "crew-vibes.ttf");
72376
+ if (os18 === "linux") return join77(home, ".local", "share", "fonts", "crew-vibes.ttf");
72252
72377
  if (os18 === "win32") {
72253
- const local = process.env.LOCALAPPDATA ?? join76(home, "AppData", "Local");
72254
- return join76(local, "Microsoft", "Windows", "Fonts", "crew-vibes.ttf");
72378
+ const local = process.env.LOCALAPPDATA ?? join77(home, "AppData", "Local");
72379
+ return join77(local, "Microsoft", "Windows", "Fonts", "crew-vibes.ttf");
72255
72380
  }
72256
72381
  return "";
72257
72382
  }
@@ -72300,7 +72425,7 @@ function resolveHome() {
72300
72425
  return (process.env.PI_TEAMS_HOME ?? process.env.PI_CREW_HOME)?.trim() || process.env.HOME || process.env.USERPROFILE || "";
72301
72426
  }
72302
72427
  function configPath2() {
72303
- return join77(resolveHome(), ".pi", "agent", "pi-crew-vibes.json");
72428
+ return join78(resolveHome(), ".pi", "agent", "pi-crew-vibes.json");
72304
72429
  }
72305
72430
  var DEFAULT_CONFIG2 = {
72306
72431
  enabled: true,
@@ -72488,7 +72613,7 @@ function isDangerStage(index, levels) {
72488
72613
  init_pi_ui_compat();
72489
72614
  init_theme_adapter();
72490
72615
  init_visual();
72491
- import { isAbsolute as isAbsolute10, relative as relative8, resolve as resolve21, sep as sep9 } from "node:path";
72616
+ import { isAbsolute as isAbsolute11, relative as relative8, resolve as resolve21, sep as sep9 } from "node:path";
72492
72617
 
72493
72618
  // src/extension/crew-vibes/render.ts
72494
72619
  function formatCount(value) {
@@ -72625,7 +72750,7 @@ function formatCwdForFooter(cwd, home) {
72625
72750
  const resolvedCwd = resolve21(cwd);
72626
72751
  const resolvedHome = resolve21(home);
72627
72752
  const rel = relative8(resolvedHome, resolvedCwd);
72628
- const inside = rel === "" || rel !== ".." && !rel.startsWith(`..${sep9}`) && !isAbsolute10(rel);
72753
+ const inside = rel === "" || rel !== ".." && !rel.startsWith(`..${sep9}`) && !isAbsolute11(rel);
72629
72754
  if (!inside) return cwd;
72630
72755
  return rel === "" ? "~" : `~${sep9}${rel}`;
72631
72756
  }
@@ -72800,14 +72925,14 @@ function createCrewVibesFooter(deps) {
72800
72925
  // src/extension/crew-vibes/provider-usage.ts
72801
72926
  import { readFileSync as readFileSync75 } from "node:fs";
72802
72927
  import { homedir as homedir12 } from "node:os";
72803
- import { join as join78 } from "node:path";
72928
+ import { join as join79 } from "node:path";
72804
72929
  function withTimeout(ms, fn) {
72805
72930
  const controller = new AbortController();
72806
72931
  const timeoutId = setTimeout(() => controller.abort(), ms);
72807
72932
  return fn(controller.signal).finally(() => clearTimeout(timeoutId));
72808
72933
  }
72809
72934
  function piAuthPath() {
72810
- return join78(homedir12(), ".pi", "agent", "auth.json");
72935
+ return join79(homedir12(), ".pi", "agent", "auth.json");
72811
72936
  }
72812
72937
  function loadAnthropicToken() {
72813
72938
  const envToken = process.env.ANTHROPIC_OAUTH_TOKEN?.trim();
@@ -72852,8 +72977,8 @@ function tokenFromHostEntry(entry) {
72852
72977
  return void 0;
72853
72978
  }
72854
72979
  function loadLegacyCopilotToken() {
72855
- const configHome = process.env.XDG_CONFIG_HOME?.trim() || join78(homedir12(), ".config");
72856
- const candidates = [join78(configHome, "github-copilot", "hosts.json"), join78(homedir12(), ".github-copilot", "hosts.json")];
72980
+ const configHome = process.env.XDG_CONFIG_HOME?.trim() || join79(homedir12(), ".config");
72981
+ const candidates = [join79(configHome, "github-copilot", "hosts.json"), join79(homedir12(), ".github-copilot", "hosts.json")];
72857
72982
  for (const hostsPath of candidates) {
72858
72983
  try {
72859
72984
  const data = JSON.parse(readFileSync75(hostsPath, "utf8"));
@@ -75190,7 +75315,7 @@ function startForegroundRunImpl(pi, ctx, extensionCtx, runner, runId) {
75190
75315
  init_config();
75191
75316
  import * as fs103 from "node:fs";
75192
75317
  import * as path82 from "node:path";
75193
- import { fileURLToPath as fileURLToPath7 } from "node:url";
75318
+ import { fileURLToPath as fileURLToPath8 } from "node:url";
75194
75319
 
75195
75320
  // src/runtime/per-write-validator.ts
75196
75321
  import { readFileSync as readFileSync81 } from "node:fs";
@@ -75293,7 +75418,7 @@ function installResourcesDiscoverHook(pi, ctx) {
75293
75418
  pi.on("resources_discover", () => {
75294
75419
  const sessionCwd = ctx.currentCtx?.cwd ?? process.cwd();
75295
75420
  const skillDir = path82.resolve(sessionCwd, "skills");
75296
- const extSkillDir = path82.resolve(path82.dirname(fileURLToPath7(import.meta.url)), "..", "..", "skills");
75421
+ const extSkillDir = path82.resolve(path82.dirname(fileURLToPath8(import.meta.url)), "..", "..", "skills");
75297
75422
  const paths = [];
75298
75423
  if (fs103.existsSync(extSkillDir)) paths.push(extSkillDir);
75299
75424
  if (skillDir !== extSkillDir && fs103.existsSync(skillDir)) {
@@ -75916,19 +76041,6 @@ var CrewBroker = class {
75916
76041
  }
75917
76042
  this.resolvedSocketPath = null;
75918
76043
  }
75919
- /**
75920
- * Non-throwing enqueue entry point for the post-append mailbox observer
75921
- * (Phase 1) or any other in-process producer. Phase 0 accepts `notifyMessage`
75922
- * as a no-op shape so the lifecycle controller can install a single
75923
- * observer regardless of broker state.
75924
- *
75925
- * Fanout goes ONLY to authenticated connections matching the recipient.
75926
- * Phase 0 keeps this as a typed no-op (`not-implemented` would be
75927
- * inappropriate here — the caller is in-process and shouldn't be
75928
- * punished for testing the broker skeleton).
75929
- */
75930
- notifyMessage(_message) {
75931
- }
75932
76044
  // ------------------------------------------------------------------------
75933
76045
  // Connection lifecycle
75934
76046
  // ------------------------------------------------------------------------