pi-crew 0.9.61 → 0.9.64

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 (34) hide show
  1. package/CHANGELOG.md +99 -0
  2. package/README.md +1 -0
  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 +256 -81
  11. package/package.json +5 -2
  12. package/src/agents/agent-config.ts +4 -0
  13. package/src/agents/agent-serializer.ts +1 -0
  14. package/src/agents/discover-agents.ts +8 -0
  15. package/src/config/role-tools.ts +49 -1
  16. package/src/extension/notification-router.ts +25 -0
  17. package/src/extension/registration/lifecycle-handlers.ts +47 -6
  18. package/src/extension/registration/lifecycle.ts +13 -7
  19. package/src/prompt/prompt-runtime.ts +6 -0
  20. package/src/prompt/scratchpad-lifecycle.ts +605 -0
  21. package/src/runtime/child-pi/child-pi-spawn.ts +42 -1
  22. package/src/runtime/child-pi/child-pi.ts +5 -0
  23. package/src/runtime/live-session/live-session-runtime.ts +12 -1
  24. package/src/runtime/model/pi-args.ts +1 -1
  25. package/src/runtime/model/session-model.ts +76 -1
  26. package/src/runtime/recovery/crash-recovery.ts +1 -1
  27. package/src/runtime/scratchpad/README.md +184 -0
  28. package/src/runtime/scratchpad/engine.ts +610 -0
  29. package/src/runtime/scratchpad/guest.ts +360 -0
  30. package/src/runtime/scratchpad/index.ts +22 -0
  31. package/src/runtime/scratchpad/protocol.ts +88 -0
  32. package/src/runtime/scratchpad/snapshot-lookup.ts +74 -0
  33. package/src/runtime/scratchpad/transform.ts +363 -0
  34. package/src/runtime/task-runner/child-executor.ts +48 -31
package/dist/index.mjs CHANGED
@@ -11602,15 +11602,51 @@ var init_frontmatter = __esm({
11602
11602
  }
11603
11603
  });
11604
11604
 
11605
+ // src/runtime/role-permission.ts
11606
+ function permissionForRole(role) {
11607
+ if (READ_ONLY_ROLES.has(role)) return "read_only";
11608
+ if (WRITE_ROLES.has(role)) return "workspace_write";
11609
+ return "read_only";
11610
+ }
11611
+ function currentCrewRole(env = process.env) {
11612
+ return env.PI_CREW_ROLE?.trim() || env.PI_TEAMS_ROLE?.trim() || void 0;
11613
+ }
11614
+ function checkSubagentSpawnPermission(role) {
11615
+ if (!role) return { allowed: true, mode: "workspace_write" };
11616
+ const mode = permissionForRole(role);
11617
+ if (mode === "read_only")
11618
+ return {
11619
+ allowed: false,
11620
+ mode,
11621
+ reason: `Role '${role}' is read-only and cannot spawn additional subagents.`
11622
+ };
11623
+ return { allowed: true, mode };
11624
+ }
11625
+ var READ_ONLY_ROLES, WRITE_ROLES;
11626
+ var init_role_permission = __esm({
11627
+ "src/runtime/role-permission.ts"() {
11628
+ "use strict";
11629
+ READ_ONLY_ROLES = /* @__PURE__ */ new Set(["explorer", "reviewer", "security-reviewer", "analyst", "critic", "planner"]);
11630
+ WRITE_ROLES = /* @__PURE__ */ new Set(["executor", "test-engineer", "writer", "verifier", "agent", "cold-verifier", "chain-executor", "worker"]);
11631
+ }
11632
+ });
11633
+
11605
11634
  // src/config/role-tools.ts
11606
11635
  function getToolConfig(role) {
11607
11636
  const key = role.includes("_") ? role.replaceAll("_", "-") : role;
11608
11637
  return ROLE_TOOL_CONFIGS[key] ?? ROLE_TOOL_CONFIGS[role] ?? {};
11609
11638
  }
11639
+ function isScratchpadEnabledForRole(role, agent) {
11640
+ const normalized = role.includes("_") ? role.replaceAll("_", "-") : role;
11641
+ if (permissionForRole(normalized) === "read_only") return false;
11642
+ if (agent?.scratchpad === false) return false;
11643
+ return agent?.scratchpad === true || getToolConfig(normalized).scratchpad === true;
11644
+ }
11610
11645
  var ROLE_TOOL_CONFIGS;
11611
11646
  var init_role_tools = __esm({
11612
11647
  "src/config/role-tools.ts"() {
11613
11648
  "use strict";
11649
+ init_role_permission();
11614
11650
  ROLE_TOOL_CONFIGS = {
11615
11651
  // Explorer - Read-only exploration; bash is included for git log/show
11616
11652
  // (decisions stream needs commit-history mining) but edit/write stay
@@ -11641,9 +11677,11 @@ var init_role_tools = __esm({
11641
11677
  tools: ["read", "grep", "find", "ls", "glob"],
11642
11678
  excludeTools: ["edit", "write", "bash", "web"]
11643
11679
  },
11644
- // Executor - Full access (default)
11680
+ // Executor - Full access (default). Phase 1 scratchpad-enabled (stateful
11681
+ // evaluator compounds intermediate results across execute calls).
11645
11682
  executor: {
11646
11683
  // No restrictions - full tool access
11684
+ scratchpad: true
11647
11685
  },
11648
11686
  // Reviewer - Read and review, no write
11649
11687
  reviewer: {
@@ -11670,12 +11708,16 @@ var init_role_tools = __esm({
11670
11708
  // integrity is preserved during verification. Mirrors cold-verifier behavior.
11671
11709
  verifier: {
11672
11710
  tools: ["read", "grep", "find", "ls", "bash"],
11673
- excludeTools: ["edit", "write", "web"]
11711
+ excludeTools: ["edit", "write", "web"],
11712
+ // Phase 1 scratchpad: multi-cell test/verify flows reuse parsed state.
11713
+ scratchpad: true
11674
11714
  },
11675
11715
  // Test Engineer - Can write tests (F1: hyphenated key)
11676
11716
  "test-engineer": {
11677
11717
  tools: ["read", "edit", "write", "bash", "ls"],
11678
- excludeTools: ["web"]
11718
+ excludeTools: ["web"],
11719
+ // Phase 1 scratchpad: build/run test suites with state across cells.
11720
+ scratchpad: true
11679
11721
  }
11680
11722
  };
11681
11723
  }
@@ -11891,6 +11933,14 @@ function parseAgentFile(filePath, source) {
11891
11933
  fallbackModels: parseCsv(frontmatter.fallbackModels),
11892
11934
  thinking: frontmatter.thinking === "false" ? void 0 : frontmatter.thinking || void 0,
11893
11935
  tools: parseToolsField(frontmatter.tools),
11936
+ // Phase 1 scratchpad opt-in (Q3: pi ignores unknown frontmatter keys; pi-crew
11937
+ // is the sole consumer in the worker path — task arrives via -p, agent file
11938
+ // is not re-read by pi). 3-STATE parse (NOT `=== "true"` like
11939
+ // inheritProjectContext): omitted/malformed → undefined so the F6 kill-switch
11940
+ // (`agent.scratchpad === false`) only fires on an EXPLICIT `scratchpad: false`,
11941
+ // not on every agent without the key (which would wrongly kill role
11942
+ // default-on). Only the literal "true"/"false" are honored.
11943
+ scratchpad: frontmatter.scratchpad === "true" ? true : frontmatter.scratchpad === "false" ? false : void 0,
11894
11944
  // SEC-1: Strip extensions/excludeExtensions for untrusted project-sourced
11895
11945
  // agents (RCE prevention). Both `project` (.crew/agents/) and
11896
11946
  // `project-pi` (.pi/agents/) are repo-adjacent / untrusted sources —
@@ -14860,6 +14910,55 @@ var init_env_filter = __esm({
14860
14910
  }
14861
14911
  });
14862
14912
 
14913
+ // src/runtime/scratchpad/snapshot-lookup.ts
14914
+ import { lstatSync as lstatSync5, readdirSync as readdirSync8 } from "node:fs";
14915
+ import { join as join18 } from "node:path";
14916
+ function findLatestScratchpadSnapshot(artifactsRoot, agentId) {
14917
+ const scratchpadDir = join18(artifactsRoot, "scratchpad");
14918
+ let dirStat;
14919
+ try {
14920
+ dirStat = lstatSync5(scratchpadDir);
14921
+ } catch {
14922
+ return null;
14923
+ }
14924
+ if (dirStat.isSymbolicLink() || !dirStat.isDirectory()) return null;
14925
+ let entries;
14926
+ try {
14927
+ entries = readdirSync8(scratchpadDir, { withFileTypes: true });
14928
+ } catch {
14929
+ return null;
14930
+ }
14931
+ const prefix = `${agentId}.attempt-`;
14932
+ let best = null;
14933
+ for (const dirent of entries) {
14934
+ if (dirent.isSymbolicLink() || !dirent.isFile()) continue;
14935
+ const name = dirent.name;
14936
+ if (!name.startsWith(prefix) || !name.endsWith(SNAPSHOT_SUFFIX)) continue;
14937
+ const attemptPart = name.slice(prefix.length, name.length - SNAPSHOT_SUFFIX.length);
14938
+ if (!/^\d+$/.test(attemptPart)) continue;
14939
+ const attempt = Number.parseInt(attemptPart, 10);
14940
+ let stat2;
14941
+ try {
14942
+ stat2 = lstatSync5(join18(scratchpadDir, name));
14943
+ } catch {
14944
+ continue;
14945
+ }
14946
+ if (!stat2.isFile()) continue;
14947
+ const hit = { path: join18(scratchpadDir, name), attempt, mtimeMs: stat2.mtimeMs };
14948
+ if (best === null || hit.mtimeMs > best.mtimeMs || hit.mtimeMs === best.mtimeMs && hit.attempt < best.attempt) {
14949
+ best = hit;
14950
+ }
14951
+ }
14952
+ return best;
14953
+ }
14954
+ var SNAPSHOT_SUFFIX;
14955
+ var init_snapshot_lookup = __esm({
14956
+ "src/runtime/scratchpad/snapshot-lookup.ts"() {
14957
+ "use strict";
14958
+ SNAPSHOT_SUFFIX = ".snapshot.json";
14959
+ }
14960
+ });
14961
+
14863
14962
  // src/runtime/child-pi/child-pi-spawn.ts
14864
14963
  import * as fs18 from "node:fs";
14865
14964
  import * as path18 from "node:path";
@@ -14956,6 +15055,21 @@ function prepareSpawnContext(input, effectiveTask) {
14956
15055
  if (input.runId) built.env.PI_CREW_BROKER_RUN_ID = input.runId;
14957
15056
  if (input.agentId) built.env.PI_CREW_BROKER_TASK_ID = input.agentId;
14958
15057
  }
15058
+ if (input.agentId && isScratchpadEnabledForRole(input.role ?? input.agent.name, input.agent)) {
15059
+ built.env.PI_CREW_SCRATCHPAD = "1";
15060
+ built.env.PI_CREW_TASK_ID = input.agentId;
15061
+ built.env.PI_CREW_ATTEMPT = String(input.attempt ?? 0);
15062
+ if (input.artifactsRoot) {
15063
+ built.env.PI_CREW_ARTIFACTS_ROOT = input.artifactsRoot;
15064
+ }
15065
+ const scratchTempDir = built.tempDir ?? createSafeTempDir(getPiTempBase(), "pi-crew-scratchpad-");
15066
+ built.env.PI_CREW_SCRATCHPAD_SNAPSHOT = resolveRealContainedPath(scratchTempDir, `${input.agentId}.snapshot.json`);
15067
+ const restoreHit = input.artifactsRoot ? findLatestScratchpadSnapshot(input.artifactsRoot, input.agentId) : null;
15068
+ if (restoreHit) {
15069
+ built.env.PI_CREW_SCRATCHPAD_RESTORE = restoreHit.path;
15070
+ built.env.PI_CREW_SCRATCHPAD_RESTORE_MTIME = String(restoreHit.mtimeMs);
15071
+ }
15072
+ }
14959
15073
  if (input.signal?.aborted) {
14960
15074
  return {
14961
15075
  kind: "aborted",
@@ -14983,11 +15097,14 @@ var BASE_ALLOWLIST;
14983
15097
  var init_child_pi_spawn = __esm({
14984
15098
  "src/runtime/child-pi/child-pi-spawn.ts"() {
14985
15099
  "use strict";
15100
+ init_role_tools();
14986
15101
  init_env_allowlist();
14987
15102
  init_env_filter();
14988
15103
  init_internal_error();
15104
+ init_safe_paths();
14989
15105
  init_pi_args();
14990
15106
  init_pi_spawn();
15107
+ init_snapshot_lookup();
14991
15108
  BASE_ALLOWLIST = [
14992
15109
  "PATH",
14993
15110
  "HOME",
@@ -22303,6 +22420,32 @@ var init_model_fallback = __esm({
22303
22420
  });
22304
22421
 
22305
22422
  // src/runtime/model/session-model.ts
22423
+ import { AsyncLocalStorage as AsyncLocalStorage2 } from "node:async_hooks";
22424
+ function registerLiveAgentModel(agentId, model) {
22425
+ if (liveAgentModels.size >= MAX_LIVE_AGENT_MODELS && !liveAgentModels.has(agentId)) {
22426
+ const oldestKey = liveAgentModels.keys().next().value;
22427
+ if (oldestKey !== void 0) {
22428
+ logInternalError(
22429
+ "session-model.liveAgentModels.cap",
22430
+ new Error(`liveAgentModels at cap ${MAX_LIVE_AGENT_MODELS}; evicting oldest ${oldestKey}`)
22431
+ );
22432
+ liveAgentModels.delete(oldestKey);
22433
+ }
22434
+ }
22435
+ liveAgentModels.set(agentId, model);
22436
+ }
22437
+ function unregisterLiveAgentModel(agentId) {
22438
+ liveAgentModels.delete(agentId);
22439
+ }
22440
+ function hasActiveLiveAgents() {
22441
+ return liveAgentModels.size > 0;
22442
+ }
22443
+ function resolveProviderForResponse() {
22444
+ const ctx = liveAgentContext.getStore();
22445
+ if (ctx) return providerOfModelRef(ctx.modelRef);
22446
+ if (hasActiveLiveAgents()) return void 0;
22447
+ return providerOfModelRef(currentSessionModel());
22448
+ }
22306
22449
  function noteSessionModel(model, source = "model_select") {
22307
22450
  const normalized = modelRefToString(model);
22308
22451
  if (!normalized) return;
@@ -22342,12 +22485,16 @@ function captureRunModelContext(ctx, override) {
22342
22485
  function sessionModelSnapshot() {
22343
22486
  return { ...state };
22344
22487
  }
22345
- var state;
22488
+ var state, liveAgentContext, liveAgentModels, MAX_LIVE_AGENT_MODELS;
22346
22489
  var init_session_model = __esm({
22347
22490
  "src/runtime/model/session-model.ts"() {
22348
22491
  "use strict";
22492
+ init_internal_error();
22349
22493
  init_model_fallback();
22350
22494
  state = { source: "none" };
22495
+ liveAgentContext = new AsyncLocalStorage2();
22496
+ liveAgentModels = /* @__PURE__ */ new Map();
22497
+ MAX_LIVE_AGENT_MODELS = 5e3;
22351
22498
  }
22352
22499
  });
22353
22500
 
@@ -22975,12 +23122,12 @@ var init_crew_hooks = __esm({
22975
23122
 
22976
23123
  // src/runtime/skill-effectiveness.ts
22977
23124
  import { existsSync as existsSync23, mkdirSync as mkdirSync14, readFileSync as readFileSync23, writeFileSync as writeFileSync4 } from "node:fs";
22978
- import { dirname as dirname16, join as join29 } from "node:path";
23125
+ import { dirname as dirname16, join as join30 } from "node:path";
22979
23126
  function getSkillMetricsPath(cwd, runId) {
22980
- return join29(projectCrewRoot(cwd), `state/runs/${runId}/skill-metrics.jsonl`);
23127
+ return join30(projectCrewRoot(cwd), `state/runs/${runId}/skill-metrics.jsonl`);
22981
23128
  }
22982
23129
  function getSkillActivationsPath(cwd, runId) {
22983
- return join29(projectCrewRoot(cwd), `state/runs/${runId}/skill-activations.jsonl`);
23130
+ return join30(projectCrewRoot(cwd), `state/runs/${runId}/skill-activations.jsonl`);
22984
23131
  }
22985
23132
  function ensureSkillMetricsDir(cwd, runId) {
22986
23133
  const dir = dirname16(getSkillMetricsPath(cwd, runId));
@@ -33237,6 +33384,7 @@ async function runLiveSessionTask(input) {
33237
33384
  scopeModelsPatterns: await resolveScopeModelsPatterns(input.manifest.cwd)
33238
33385
  });
33239
33386
  const resolvedModel = modelFromRegistry(input.modelRegistry, modelRouting.candidates[0] ?? modelRouting.requested) ?? input.parentModel;
33387
+ const resolvedModelRef = modelRefToString(resolvedModel) ?? modelRouting.candidates[0];
33240
33388
  if (modelRouting.droppedRequested) {
33241
33389
  appendEventFireAndForget(input.manifest.eventsPath, {
33242
33390
  type: "task.model_dropped",
@@ -33335,6 +33483,7 @@ async function runLiveSessionTask(input) {
33335
33483
  appendEvent,
33336
33484
  input.manifest.eventsPath
33337
33485
  );
33486
+ registerLiveAgentModel(agentId, resolvedModelRef ?? "");
33338
33487
  streamOut = createStreamingOutput(input.manifest, input.task.id);
33339
33488
  let controlCursor = { offset: 0 };
33340
33489
  const seenControlRequestIds = /* @__PURE__ */ new Set();
@@ -33470,7 +33619,10 @@ ${input.prompt}` : input.prompt;
33470
33619
  });
33471
33620
  const sessionTimeoutMs = DEFAULT_LIVE_SESSION.responseTimeoutMs;
33472
33621
  try {
33473
- await promptWithTimeout(session, effectivePrompt, sessionTimeoutMs, "Live-session");
33622
+ await liveAgentContext.run(
33623
+ { agentId, modelRef: resolvedModelRef ?? "" },
33624
+ () => promptWithTimeout(session, effectivePrompt, sessionTimeoutMs, "Live-session")
33625
+ );
33474
33626
  } catch (promptError) {
33475
33627
  const msg = promptError instanceof Error ? promptError.message : String(promptError);
33476
33628
  appendEventFireAndForget(input.manifest.eventsPath, {
@@ -33643,6 +33795,7 @@ ${input.prompt}` : input.prompt;
33643
33795
  error: message
33644
33796
  };
33645
33797
  } finally {
33798
+ unregisterLiveAgentModel(agentId);
33646
33799
  unsubscribe?.();
33647
33800
  unsubscribeControlRealtime?.();
33648
33801
  if (onSignalAbort) input.signal?.removeEventListener("abort", onSignalAbort);
@@ -33681,6 +33834,7 @@ var init_live_session_runtime = __esm({
33681
33834
  init_model_scope();
33682
33835
  init_runtime_resolver();
33683
33836
  init_runtime_warmup();
33837
+ init_session_model();
33684
33838
  init_sidechain_output();
33685
33839
  init_streaming_output();
33686
33840
  init_sensitive_paths();
@@ -33706,35 +33860,6 @@ var init_live_session_runtime = __esm({
33706
33860
  }
33707
33861
  });
33708
33862
 
33709
- // src/runtime/role-permission.ts
33710
- function permissionForRole(role) {
33711
- if (READ_ONLY_ROLES.has(role)) return "read_only";
33712
- if (WRITE_ROLES.has(role)) return "workspace_write";
33713
- return "read_only";
33714
- }
33715
- function currentCrewRole(env = process.env) {
33716
- return env.PI_CREW_ROLE?.trim() || env.PI_TEAMS_ROLE?.trim() || void 0;
33717
- }
33718
- function checkSubagentSpawnPermission(role) {
33719
- if (!role) return { allowed: true, mode: "workspace_write" };
33720
- const mode = permissionForRole(role);
33721
- if (mode === "read_only")
33722
- return {
33723
- allowed: false,
33724
- mode,
33725
- reason: `Role '${role}' is read-only and cannot spawn additional subagents.`
33726
- };
33727
- return { allowed: true, mode };
33728
- }
33729
- var READ_ONLY_ROLES, WRITE_ROLES;
33730
- var init_role_permission = __esm({
33731
- "src/runtime/role-permission.ts"() {
33732
- "use strict";
33733
- READ_ONLY_ROLES = /* @__PURE__ */ new Set(["explorer", "reviewer", "security-reviewer", "analyst", "critic", "planner"]);
33734
- WRITE_ROLES = /* @__PURE__ */ new Set(["executor", "test-engineer", "writer", "verifier", "agent", "cold-verifier", "chain-executor", "worker"]);
33735
- }
33736
- });
33737
-
33738
33863
  // src/state/coordination/task-claims.ts
33739
33864
  import { randomUUID as randomUUID4, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
33740
33865
  function createTaskClaim(owner, leaseMs = 5 * 6e4, now = /* @__PURE__ */ new Date()) {
@@ -39389,6 +39514,7 @@ function serializeAgent(agent) {
39389
39514
  line("fallbackModels", agent.fallbackModels),
39390
39515
  line("thinking", agent.thinking),
39391
39516
  line("tools", agent.tools),
39517
+ line("scratchpad", agent.scratchpad),
39392
39518
  agent.extensions !== void 0 ? line("extensions", agent.extensions) ?? "extensions:" : void 0,
39393
39519
  line("skills", agent.skills),
39394
39520
  line("systemPromptMode", agent.systemPromptMode),
@@ -40735,9 +40861,9 @@ var init_handle_settings = __esm({
40735
40861
 
40736
40862
  // src/extension/team-tool/workflow-manage.ts
40737
40863
  import { existsSync as existsSync41, readFileSync as readFileSync40, rmSync as rmSync14, writeFileSync as writeFileSync7 } from "node:fs";
40738
- import { dirname as dirname29, join as join45 } from "node:path";
40864
+ import { dirname as dirname29, join as join46 } from "node:path";
40739
40865
  function allowedWorkflowDirs(cwd) {
40740
- return [join45(projectCrewRoot(cwd), "workflows"), join45(userPiRoot(), "workflows"), join45(packageRoot(), "workflows")];
40866
+ return [join46(projectCrewRoot(cwd), "workflows"), join46(userPiRoot(), "workflows"), join46(packageRoot(), "workflows")];
40741
40867
  }
40742
40868
  function validateScriptContent(content) {
40743
40869
  for (const pattern of FORBIDDEN_PATTERNS) {
@@ -40749,7 +40875,7 @@ function validateScriptContent(content) {
40749
40875
  }
40750
40876
  function resolveWorkflowWritePath(cwd, name, scope = "project") {
40751
40877
  assertSafePathId("workflowName", name);
40752
- const base = scope === "user" ? join45(userPiRoot(), "workflows") : join45(projectCrewRoot(cwd), "workflows");
40878
+ const base = scope === "user" ? join46(userPiRoot(), "workflows") : join46(projectCrewRoot(cwd), "workflows");
40753
40879
  return resolveRealContainedPath(base, `${name}.dwf.ts`);
40754
40880
  }
40755
40881
  function handleWorkflowCreate(params, ctx) {
@@ -41603,7 +41729,7 @@ var init_async_runner = __esm({
41603
41729
  });
41604
41730
 
41605
41731
  // src/runtime/goal-workflow/goal-state-store.ts
41606
- 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";
41732
+ 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";
41607
41733
  import { dirname as dirname32 } from "node:path";
41608
41734
  function resolveGoalsRoot(cwd) {
41609
41735
  const crewRoot = projectCrewRoot(cwd) ?? userCrewRoot();
@@ -41763,7 +41889,7 @@ var init_goal_state_store = __esm({
41763
41889
  try {
41764
41890
  const root = resolveGoalsRoot(this.cwd);
41765
41891
  if (!existsSync44(root)) return [];
41766
- const entries = readdirSync20(root);
41892
+ const entries = readdirSync21(root);
41767
41893
  const goals = [];
41768
41894
  for (const entry of entries) {
41769
41895
  if (!entry.endsWith(".json")) continue;
@@ -41827,7 +41953,7 @@ var init_verification_integrity = __esm({
41827
41953
 
41828
41954
  // src/runtime/workspace-lock.ts
41829
41955
  import { createHash as createHash7 } from "node:crypto";
41830
- 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";
41956
+ 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";
41831
41957
  import * as path50 from "node:path";
41832
41958
  function workspaceLockPath(cwd) {
41833
41959
  const absCwd = path50.resolve(cwd);
@@ -45032,7 +45158,8 @@ __export(crash_recovery_exports, {
45032
45158
  detectInterruptedRuns: () => detectInterruptedRuns,
45033
45159
  purgeStaleActiveRunIndex: () => purgeStaleActiveRunIndex,
45034
45160
  readManifestWithTransientRetry: () => readManifestWithTransientRetry,
45035
- reconcileAllStaleRuns: () => reconcileAllStaleRuns
45161
+ reconcileAllStaleRuns: () => reconcileAllStaleRuns,
45162
+ shouldRecoverTask: () => shouldRecoverTask
45036
45163
  });
45037
45164
  import * as fs63 from "node:fs";
45038
45165
  import * as path52 from "node:path";
@@ -49170,7 +49297,7 @@ var init_dispatch = __esm({
49170
49297
  });
49171
49298
 
49172
49299
  // src/observability/correlation.ts
49173
- import { AsyncLocalStorage as AsyncLocalStorage2 } from "node:async_hooks";
49300
+ import { AsyncLocalStorage as AsyncLocalStorage3 } from "node:async_hooks";
49174
49301
  import { randomBytes as randomBytes2 } from "node:crypto";
49175
49302
  function withCorrelation(ctx, fn) {
49176
49303
  return storage.run(ctx, fn);
@@ -49194,7 +49321,7 @@ var storage;
49194
49321
  var init_correlation = __esm({
49195
49322
  "src/observability/correlation.ts"() {
49196
49323
  "use strict";
49197
- storage = new AsyncLocalStorage2();
49324
+ storage = new AsyncLocalStorage3();
49198
49325
  }
49199
49326
  });
49200
49327
 
@@ -53151,6 +53278,20 @@ function detectRetryableModelFailureFromOutput(parsed) {
53151
53278
  }
53152
53279
  return void 0;
53153
53280
  }
53281
+ function evidenceStatusFor(childResult) {
53282
+ return childResult.exitStatus?.cancelled ? "cancelled" : childResult.error || childResult.exitCode && childResult.exitCode !== 0 ? "failed" : "completed";
53283
+ }
53284
+ function attemptErrorFor(childResult, parsedOutput, taskId) {
53285
+ let err2 = childResult.error || (childResult.exitCode && childResult.exitCode !== 0 ? childResult.stderr || `Child Pi exited with ${childResult.exitCode}` : void 0);
53286
+ if (childResult.exitStatus?.timedOut) {
53287
+ err2 = errors.childTimeout({ taskId, stderr: childResult.stderr }).message;
53288
+ }
53289
+ if (!err2 && parsedOutput) {
53290
+ const rateLimitErr = detectRetryableModelFailureFromOutput(parsedOutput);
53291
+ if (rateLimitErr) err2 = rateLimitErr;
53292
+ }
53293
+ return err2;
53294
+ }
53154
53295
  async function runChildProcessTask(ctx) {
53155
53296
  const input = ctx.input;
53156
53297
  const manifest = ctx.manifest;
@@ -53334,6 +53475,7 @@ async function runChildProcessTask(ctx) {
53334
53475
  runId: manifest.runId,
53335
53476
  agentId: task.id,
53336
53477
  artifactsRoot: manifest.artifactsRoot,
53478
+ attempt: i,
53337
53479
  steeringFile: resolveRealContainedPath(`${manifest.artifactsRoot}/steering`, `${task.id}.jsonl`),
53338
53480
  onSpawn: (pid) => {
53339
53481
  try {
@@ -53422,7 +53564,7 @@ async function runChildProcessTask(ctx) {
53422
53564
  input.signal.removeEventListener("abort", externalAbortListener);
53423
53565
  }
53424
53566
  }
53425
- const evidenceStatus = childResult.exitStatus?.cancelled ? "cancelled" : childResult.error || childResult.exitCode && childResult.exitCode !== 0 ? "failed" : "completed";
53567
+ const evidenceStatus = evidenceStatusFor(childResult);
53426
53568
  terminalEvidence = [
53427
53569
  ...terminalEvidence,
53428
53570
  {
@@ -53468,17 +53610,7 @@ async function runChildProcessTask(ctx) {
53468
53610
  parsedOutput = parsePiJsonOutput(transcriptText2);
53469
53611
  rawFinalText = childResult.rawFinalText;
53470
53612
  intermediateFindings = childResult.intermediateFindings;
53471
- error = childResult.error || (childResult.exitCode && childResult.exitCode !== 0 ? childResult.stderr || `Child Pi exited with ${childResult.exitCode}` : void 0);
53472
- if (childResult.exitStatus?.timedOut) {
53473
- error = errors.childTimeout({
53474
- taskId: task.id,
53475
- stderr: childResult.stderr
53476
- }).message;
53477
- }
53478
- if (!error && parsedOutput) {
53479
- const rateLimitErr = detectRetryableModelFailureFromOutput(parsedOutput);
53480
- if (rateLimitErr) error = rateLimitErr;
53481
- }
53613
+ error = attemptErrorFor(childResult, parsedOutput, task.id);
53482
53614
  persistHeartbeat(true);
53483
53615
  persistChildProgress({ type: "attempt_finished" }, true);
53484
53616
  const attempt = {
@@ -61036,7 +61168,7 @@ __export(dynamic_workflow_runner_exports, {
61036
61168
  runDynamicWorkflow: () => runDynamicWorkflow
61037
61169
  });
61038
61170
  import { readFileSync as readFileSync67 } from "node:fs";
61039
- import { join as join71 } from "node:path";
61171
+ import { join as join72 } from "node:path";
61040
61172
  import { transformSync } from "esbuild";
61041
61173
  function assertStructuredCloneable(value, name) {
61042
61174
  try {
@@ -61048,7 +61180,7 @@ function assertStructuredCloneable(value, name) {
61048
61180
  }
61049
61181
  function resolveScriptPath(workflow, cwd) {
61050
61182
  const crewRoot = projectCrewRoot(cwd);
61051
- const allowedBases = [join71(projectCrewRoot(cwd), "workflows"), join71(userPiRoot(), "workflows"), join71(packageRoot(), "workflows")];
61183
+ const allowedBases = [join72(projectCrewRoot(cwd), "workflows"), join72(userPiRoot(), "workflows"), join72(packageRoot(), "workflows")];
61052
61184
  for (const base of allowedBases) {
61053
61185
  try {
61054
61186
  const real = resolveRealContainedPath(base, workflow.filePath);
@@ -69560,6 +69692,19 @@ var init_notification_router = __esm({
69560
69692
  ...notification,
69561
69693
  timestamp: notification.timestamp ?? now
69562
69694
  };
69695
+ if (withTime.clear) {
69696
+ const clearKey = notificationKey(withTime);
69697
+ const wasInSeen = this.seen.delete(clearKey);
69698
+ if (wasInSeen) {
69699
+ try {
69700
+ this.opts.sink?.(withTime);
69701
+ } catch (sinkError) {
69702
+ logInternalError("notification-sink", sinkError);
69703
+ }
69704
+ this.deliver(withTime);
69705
+ }
69706
+ return true;
69707
+ }
69563
69708
  try {
69564
69709
  this.opts.sink?.(withTime);
69565
69710
  } catch (sinkError) {
@@ -70092,11 +70237,15 @@ async function configureNotifications(ctx, state2, deps) {
70092
70237
  sink: (notification) => state2.notificationSink?.write(notification)
70093
70238
  },
70094
70239
  (notification) => {
70095
- deps.widgetState.notificationCount = (deps.widgetState.notificationCount ?? 0) + 1;
70096
- sendFollowUp2(
70097
- deps.pi,
70098
- [notification.title, notification.body, notification.runId ? `Run: ${notification.runId}` : void 0].filter((line4) => Boolean(line4)).join("\n")
70099
- );
70240
+ if (notification.clear) {
70241
+ deps.widgetState.notificationCount = Math.max(0, (deps.widgetState.notificationCount ?? 0) - 1);
70242
+ } else {
70243
+ deps.widgetState.notificationCount = (deps.widgetState.notificationCount ?? 0) + 1;
70244
+ sendFollowUp2(
70245
+ deps.pi,
70246
+ [notification.title, notification.body, notification.runId ? `Run: ${notification.runId}` : void 0].filter((line4) => Boolean(line4)).join("\n")
70247
+ );
70248
+ }
70100
70249
  const currentCtx = deps.getCurrentCtx();
70101
70250
  if (currentCtx) {
70102
70251
  const uiConfig = loadConfig(currentCtx.cwd).config.ui;
@@ -72184,20 +72333,20 @@ init_internal_error();
72184
72333
 
72185
72334
  // src/extension/crew-vibes/config.ts
72186
72335
  import { existsSync as existsSync75, mkdirSync as mkdirSync42, readFileSync as readFileSync74, writeFileSync as writeFileSync9 } from "node:fs";
72187
- import { dirname as dirname39, join as join77 } from "node:path";
72336
+ import { dirname as dirname39, join as join78 } from "node:path";
72188
72337
 
72189
72338
  // src/extension/crew-vibes/font-detect.ts
72190
72339
  import { existsSync as existsSync74, readFileSync as readFileSync73 } from "node:fs";
72191
72340
  import { homedir as homedir11, platform } from "node:os";
72192
- import { join as join76 } from "node:path";
72341
+ import { join as join77 } from "node:path";
72193
72342
  function fontPath() {
72194
72343
  const os18 = platform();
72195
72344
  const home = homedir11();
72196
- if (os18 === "darwin") return join76(home, "Library", "Fonts", "crew-vibes.ttf");
72197
- if (os18 === "linux") return join76(home, ".local", "share", "fonts", "crew-vibes.ttf");
72345
+ if (os18 === "darwin") return join77(home, "Library", "Fonts", "crew-vibes.ttf");
72346
+ if (os18 === "linux") return join77(home, ".local", "share", "fonts", "crew-vibes.ttf");
72198
72347
  if (os18 === "win32") {
72199
- const local = process.env.LOCALAPPDATA ?? join76(home, "AppData", "Local");
72200
- return join76(local, "Microsoft", "Windows", "Fonts", "crew-vibes.ttf");
72348
+ const local = process.env.LOCALAPPDATA ?? join77(home, "AppData", "Local");
72349
+ return join77(local, "Microsoft", "Windows", "Fonts", "crew-vibes.ttf");
72201
72350
  }
72202
72351
  return "";
72203
72352
  }
@@ -72246,7 +72395,7 @@ function resolveHome() {
72246
72395
  return (process.env.PI_TEAMS_HOME ?? process.env.PI_CREW_HOME)?.trim() || process.env.HOME || process.env.USERPROFILE || "";
72247
72396
  }
72248
72397
  function configPath2() {
72249
- return join77(resolveHome(), ".pi", "agent", "pi-crew-vibes.json");
72398
+ return join78(resolveHome(), ".pi", "agent", "pi-crew-vibes.json");
72250
72399
  }
72251
72400
  var DEFAULT_CONFIG2 = {
72252
72401
  enabled: true,
@@ -72746,14 +72895,14 @@ function createCrewVibesFooter(deps) {
72746
72895
  // src/extension/crew-vibes/provider-usage.ts
72747
72896
  import { readFileSync as readFileSync75 } from "node:fs";
72748
72897
  import { homedir as homedir12 } from "node:os";
72749
- import { join as join78 } from "node:path";
72898
+ import { join as join79 } from "node:path";
72750
72899
  function withTimeout(ms, fn) {
72751
72900
  const controller = new AbortController();
72752
72901
  const timeoutId = setTimeout(() => controller.abort(), ms);
72753
72902
  return fn(controller.signal).finally(() => clearTimeout(timeoutId));
72754
72903
  }
72755
72904
  function piAuthPath() {
72756
- return join78(homedir12(), ".pi", "agent", "auth.json");
72905
+ return join79(homedir12(), ".pi", "agent", "auth.json");
72757
72906
  }
72758
72907
  function loadAnthropicToken() {
72759
72908
  const envToken = process.env.ANTHROPIC_OAUTH_TOKEN?.trim();
@@ -72798,8 +72947,8 @@ function tokenFromHostEntry(entry) {
72798
72947
  return void 0;
72799
72948
  }
72800
72949
  function loadLegacyCopilotToken() {
72801
- const configHome = process.env.XDG_CONFIG_HOME?.trim() || join78(homedir12(), ".config");
72802
- const candidates = [join78(configHome, "github-copilot", "hosts.json"), join78(homedir12(), ".github-copilot", "hosts.json")];
72950
+ const configHome = process.env.XDG_CONFIG_HOME?.trim() || join79(homedir12(), ".config");
72951
+ const candidates = [join79(configHome, "github-copilot", "hosts.json"), join79(homedir12(), ".github-copilot", "hosts.json")];
72803
72952
  for (const hostsPath of candidates) {
72804
72953
  try {
72805
72954
  const data = JSON.parse(readFileSync75(hostsPath, "utf8"));
@@ -76664,7 +76813,6 @@ function safeStringify(value) {
76664
76813
  // src/extension/registration/lifecycle-handlers.ts
76665
76814
  init_child_pi();
76666
76815
  init_live_agent_manager();
76667
- init_model_fallback();
76668
76816
  init_pi_args();
76669
76817
  init_provider_quota();
76670
76818
  init_session_model();
@@ -76963,8 +77111,7 @@ function installModelTrackingHandlers(pi) {
76963
77111
  noteSessionThinking(event.level);
76964
77112
  });
76965
77113
  pi.on("after_provider_response", (event) => {
76966
- const model = currentSessionModel();
76967
- const provider = model ? providerOfModelRef(model) : void 0;
77114
+ const provider = resolveProviderForResponse();
76968
77115
  if (provider) noteProviderResponse(provider, event.status, event.headers);
76969
77116
  });
76970
77117
  }
@@ -77361,12 +77508,40 @@ function setupRenderLoop(pi, ctx, extensionCtx, loadedConfig) {
77361
77508
  const currentSessionId = ctx.currentCtx?.sessionManager?.getSessionId();
77362
77509
  const sessionManifests = filterManifestsForHealthNotifications(manifests, currentSessionId);
77363
77510
  const now = Date.now();
77511
+ const clearHealthNotifications = (runId) => {
77512
+ for (const kind of ["recovery_dead_workers", "recovery_missing_heartbeat"]) {
77513
+ const key = `${kind}_${runId}`;
77514
+ ctx.autoRecoveryLast.delete(key);
77515
+ ctx.notifyOperator({
77516
+ id: key,
77517
+ clear: true,
77518
+ severity: "info",
77519
+ source: "health",
77520
+ runId,
77521
+ title: `Cleared ${kind} for ${runId}`
77522
+ });
77523
+ }
77524
+ };
77364
77525
  for (const run of sessionManifests) {
77365
- if (run.status !== "running") continue;
77526
+ if (run.status !== "running") {
77527
+ snapshotCache.invalidate(run.runId);
77528
+ clearHealthNotifications(run.runId);
77529
+ continue;
77530
+ }
77366
77531
  try {
77532
+ const freshManifest = ctx.getManifestCache(extensionCtx.cwd).get(run.runId);
77533
+ if (freshManifest?.status !== "running") {
77534
+ snapshotCache.invalidate(run.runId);
77535
+ clearHealthNotifications(run.runId);
77536
+ continue;
77537
+ }
77367
77538
  const snapshot = snapshotCache.get(run.runId);
77368
77539
  if (!snapshot) continue;
77369
- if (snapshot.manifest.status !== "running") continue;
77540
+ if (snapshot.manifest.status !== "running") {
77541
+ snapshotCache.invalidate(run.runId);
77542
+ clearHealthNotifications(run.runId);
77543
+ continue;
77544
+ }
77370
77545
  const summary = summarizeHeartbeats(snapshot, { now });
77371
77546
  const maybeNotifyHealth = (kind, count2, title, body) => {
77372
77547
  if (count2 <= 0) return;