pi-crew 0.9.58 → 0.9.60

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 (51) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/dist/index.mjs +1390 -796
  3. package/docs/commands-reference.md +5 -0
  4. package/docs/resource-formats.md +7 -1
  5. package/package.json +1 -1
  6. package/skills/distill-software/SKILL.md +7 -1
  7. package/skills/real-test-pi-crew/REPORT-TEMPLATE.md +52 -0
  8. package/skills/real-test-pi-crew/SKILL.md +6 -4
  9. package/src/config/types.ts +29 -0
  10. package/src/extension/async-notifier.ts +10 -2
  11. package/src/extension/registration/context-builder.ts +5 -2
  12. package/src/extension/registration/crash-recovery-cache.ts +2 -2
  13. package/src/extension/registration/lazy-configurers.ts +1 -1
  14. package/src/extension/registration/lifecycle-handlers.ts +58 -10
  15. package/src/extension/registration/observability.ts +8 -4
  16. package/src/extension/registration/registration-types.ts +2 -2
  17. package/src/extension/registration/runtime-cleanup.ts +5 -2
  18. package/src/extension/registration/subagent-manager-setup.ts +9 -4
  19. package/src/extension/registration/subagent-tools.ts +15 -3
  20. package/src/extension/session-summary.ts +5 -0
  21. package/src/extension/team-tool/chain-dispatch.ts +20 -2
  22. package/src/extension/team-tool/chain-executor.ts +6 -1
  23. package/src/extension/team-tool/doctor.ts +43 -0
  24. package/src/extension/team-tool/run.ts +19 -3
  25. package/src/extension/team-tool.ts +2 -1
  26. package/src/runtime/background-runner.ts +26 -0
  27. package/src/runtime/child-pi/child-pi-spawn.ts +1 -0
  28. package/src/runtime/child-pi/child-pi.ts +2 -0
  29. package/src/runtime/delivery-coordinator.ts +24 -3
  30. package/src/runtime/live-session/live-session-runtime.ts +83 -16
  31. package/src/runtime/model/model-fallback.ts +412 -36
  32. package/src/runtime/model/model-scope.ts +2 -0
  33. package/src/runtime/model/pi-args.ts +7 -3
  34. package/src/runtime/model/provider-quota.ts +228 -0
  35. package/src/runtime/model/session-model.ts +135 -0
  36. package/src/runtime/recovery/crash-recovery.ts +34 -4
  37. package/src/runtime/subagent-manager.ts +7 -1
  38. package/src/runtime/task-runner/child-executor.ts +82 -4
  39. package/src/runtime/task-runner/live-executor.ts +12 -0
  40. package/src/runtime/task-runner.ts +5 -0
  41. package/src/runtime/team-runner.ts +18 -4
  42. package/src/schema/config-schema.ts +12 -0
  43. package/src/state/types.ts +27 -0
  44. package/src/teams/discover-teams.ts +16 -2
  45. package/src/teams/team-config.ts +4 -0
  46. package/src/ui/powerbar-publisher.ts +31 -6
  47. package/src/ui/run-dashboard.ts +7 -1
  48. package/src/ui/widget/index.ts +9 -1
  49. package/src/ui/widget/widget-model.ts +1 -1
  50. package/src/ui/widget/widget-types.ts +3 -0
  51. package/src/utils/session-utils.ts +42 -19
package/dist/index.mjs CHANGED
@@ -5867,16 +5867,16 @@ var init_assert = __esm({
5867
5867
  init_errors2();
5868
5868
  init_error();
5869
5869
  init_check();
5870
- __classPrivateFieldSet = function(receiver, state, value, kind, f) {
5870
+ __classPrivateFieldSet = function(receiver, state2, value, kind, f) {
5871
5871
  if (kind === "m") throw new TypeError("Private method is not writable");
5872
5872
  if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
5873
- if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
5874
- return kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value), value;
5873
+ if (typeof state2 === "function" ? receiver !== state2 || !f : !state2.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
5874
+ return kind === "a" ? f.call(receiver, value) : f ? f.value = value : state2.set(receiver, value), value;
5875
5875
  };
5876
- __classPrivateFieldGet = function(receiver, state, kind, f) {
5876
+ __classPrivateFieldGet = function(receiver, state2, kind, f) {
5877
5877
  if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
5878
- if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
5879
- return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
5878
+ if (typeof state2 === "function" ? receiver !== state2 || !f : !state2.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
5879
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state2.get(receiver);
5880
5880
  };
5881
5881
  AssertError = class extends TypeBoxError {
5882
5882
  constructor(iterator) {
@@ -8234,7 +8234,7 @@ var init_validation_types = __esm({
8234
8234
  });
8235
8235
 
8236
8236
  // src/schema/config-schema.ts
8237
- var PiTeamsAutonomyProfileSchema, PiTeamsAutonomousConfigSchema, PiTeamsLimitsConfigSchema, PiTeamsRuntimeConfigSchema, PiTeamsControlConfigSchema, PiTeamsWorktreeConfigSchema, GoalWrapWorkflowConfigSchema, PiTeamsGoalWrapConfigSchema, AgentOverrideSchema, PiTeamsAgentsConfigSchema, PiTeamsToolsConfigSchema, PiTeamsTelemetryConfigSchema, PiTeamsPolicyConfigSchema, PiTeamsNotificationsConfigSchema, PiTeamsObservabilityConfigSchema, PiTeamsReliabilityConfigSchema, PiTeamsOtlpConfigSchema, PiTeamsUiConfigSchema, CrewBrokerConfigSchema, PiTeamsConfigSchema;
8237
+ var PiTeamsAutonomyProfileSchema, PiTeamsAutonomousConfigSchema, PiTeamsLimitsConfigSchema, PiTeamsModelFallbackConfigSchema, PiTeamsRuntimeConfigSchema, PiTeamsControlConfigSchema, PiTeamsWorktreeConfigSchema, GoalWrapWorkflowConfigSchema, PiTeamsGoalWrapConfigSchema, AgentOverrideSchema, PiTeamsAgentsConfigSchema, PiTeamsToolsConfigSchema, PiTeamsTelemetryConfigSchema, PiTeamsPolicyConfigSchema, PiTeamsNotificationsConfigSchema, PiTeamsObservabilityConfigSchema, PiTeamsReliabilityConfigSchema, PiTeamsOtlpConfigSchema, PiTeamsUiConfigSchema, CrewBrokerConfigSchema, PiTeamsConfigSchema;
8238
8238
  var init_config_schema = __esm({
8239
8239
  "src/schema/config-schema.ts"() {
8240
8240
  "use strict";
@@ -8272,6 +8272,16 @@ var init_config_schema = __esm({
8272
8272
  },
8273
8273
  { additionalProperties: false }
8274
8274
  );
8275
+ PiTeamsModelFallbackConfigSchema = Type.Object(
8276
+ {
8277
+ maxAutoFallbacks: Type.Optional(Type.Integer({ minimum: 0 })),
8278
+ order: Type.Optional(Type.Union([Type.Literal("parentFirst"), Type.Literal("asIs")])),
8279
+ requireCredentials: Type.Optional(Type.Boolean()),
8280
+ quotaAwareOrdering: Type.Optional(Type.Boolean()),
8281
+ defaultSubagentModel: Type.Optional(Type.String({ minLength: 1 }))
8282
+ },
8283
+ { additionalProperties: false }
8284
+ );
8275
8285
  PiTeamsRuntimeConfigSchema = Type.Object(
8276
8286
  {
8277
8287
  mode: Type.Optional(
@@ -8311,7 +8321,8 @@ var init_config_schema = __esm({
8311
8321
  },
8312
8322
  { additionalProperties: false }
8313
8323
  )
8314
- )
8324
+ ),
8325
+ modelFallback: Type.Optional(PiTeamsModelFallbackConfigSchema)
8315
8326
  },
8316
8327
  { additionalProperties: false }
8317
8328
  );
@@ -12323,10 +12334,17 @@ function parseRoleLine(line4) {
12323
12334
  metadata.skills = raw.replace(/\s*,\s*/g, ",").trim();
12324
12335
  return "";
12325
12336
  });
12326
- descriptionSource = descriptionSource.replace(/\b(agent|model|maxConcurrency)\s*=\s*(\S+)/g, (_match, key, raw) => {
12327
- metadata[key] = raw.trim();
12337
+ descriptionSource = descriptionSource.replace(/\bfallbackModels\s*=\s*([\w/.:-]+(?:\s*,\s*[\w/.:-]+)*)/g, (_match, raw) => {
12338
+ metadata.fallbackModels = raw.replace(/\s*,\s*/g, ",").trim();
12328
12339
  return "";
12329
12340
  });
12341
+ descriptionSource = descriptionSource.replace(
12342
+ /\b(agent|model|thinking|maxConcurrency)\s*=\s*(\S+)/g,
12343
+ (_match, key, raw) => {
12344
+ metadata[key] = raw.trim();
12345
+ return "";
12346
+ }
12347
+ );
12330
12348
  const description = descriptionSource.replace(/\s+/g, " ").trim() || void 0;
12331
12349
  const maxConcurrency = metadata.maxConcurrency ? (() => {
12332
12350
  const p = Number.parseInt(metadata.maxConcurrency, 10);
@@ -12337,6 +12355,8 @@ function parseRoleLine(line4) {
12337
12355
  agent: metadata.agent ?? name,
12338
12356
  description,
12339
12357
  model: metadata.model,
12358
+ fallbackModels: metadata.fallbackModels ? metadata.fallbackModels.split(",").map((s) => s.trim()).filter(Boolean) : void 0,
12359
+ thinking: metadata.thinking?.trim() || void 0,
12340
12360
  skills: parseRoleSkills(metadata.skills),
12341
12361
  maxConcurrency: maxConcurrency && maxConcurrency > 0 ? maxConcurrency : void 0
12342
12362
  };
@@ -12871,14 +12891,23 @@ function toPiSessionId(runId) {
12871
12891
  }
12872
12892
  function extractSessionId(ctx) {
12873
12893
  if (typeof ctx !== "object" || ctx === null) return void 0;
12874
- let raw;
12875
12894
  try {
12876
- raw = Object.getOwnPropertyDescriptor(ctx, "sessionId")?.value;
12895
+ const sm = ctx.sessionManager;
12896
+ if (sm && typeof sm === "object") {
12897
+ const cached2 = sessionIdCache.get(sm);
12898
+ if (cached2) return cached2;
12899
+ const id = sm.getSessionId?.();
12900
+ if (typeof id === "string" && id.length > 0) {
12901
+ sessionIdCache.set(sm, id);
12902
+ return id;
12903
+ }
12904
+ }
12905
+ const direct = Object.getOwnPropertyDescriptor(ctx, "sessionId")?.value;
12906
+ if (typeof direct === "string" && direct.length > 0) return direct;
12877
12907
  } catch {
12878
12908
  return void 0;
12879
12909
  }
12880
- if (typeof raw !== "string" || raw.length === 0) return void 0;
12881
- return raw;
12910
+ return void 0;
12882
12911
  }
12883
12912
  function extractBrokerSessionId(ctx) {
12884
12913
  if (typeof ctx !== "object" || ctx === null) return void 0;
@@ -12893,9 +12922,11 @@ function extractBrokerSessionId(ctx) {
12893
12922
  return void 0;
12894
12923
  }
12895
12924
  }
12925
+ var sessionIdCache;
12896
12926
  var init_session_utils = __esm({
12897
12927
  "src/utils/session-utils.ts"() {
12898
12928
  "use strict";
12929
+ sessionIdCache = /* @__PURE__ */ new WeakMap();
12899
12930
  }
12900
12931
  });
12901
12932
 
@@ -13013,9 +13044,9 @@ var init_cancellation_token = __esm({
13013
13044
  this.throwIfCancelled();
13014
13045
  this.#lastHeartbeatAt = this.#now().toISOString();
13015
13046
  this.#lastHeartbeatStage = stage;
13016
- const state = this.state();
13017
- this.#onHeartbeat?.(state);
13018
- return state;
13047
+ const state2 = this.state();
13048
+ this.#onHeartbeat?.(state2);
13049
+ return state2;
13019
13050
  }
13020
13051
  throwIfCancelled() {
13021
13052
  if (this.aborted) throw new CrewCancellationError(this.#reason ?? cancellationReasonFromUnknown(this.#controller.signal.reason));
@@ -13943,12 +13974,13 @@ function buildPiWorkerArgs(input) {
13943
13974
  const args = ["--mode", "json", "-p"];
13944
13975
  if (input.sessionEnabled === false) args.push("--no-session");
13945
13976
  const resolvedModel = input.model ?? input.agent.model;
13977
+ const effectiveThinking = input.thinkingOverride ?? input.agent.thinking;
13946
13978
  if (resolvedModel) {
13947
- const modelWithThinking = applyThinkingSuffix(resolvedModel, input.agent.thinking);
13979
+ const modelWithThinking = applyThinkingSuffix(resolvedModel, effectiveThinking);
13948
13980
  if (modelWithThinking) args.push("--model", modelWithThinking);
13949
13981
  }
13950
- if (!resolvedModel && input.agent.thinking && input.agent.thinking !== "off" && isValidThinkingLevel(input.agent.thinking)) {
13951
- args.push("--thinking", input.agent.thinking);
13982
+ if (!resolvedModel && effectiveThinking && effectiveThinking !== "off" && isValidThinkingLevel(effectiveThinking)) {
13983
+ args.push("--thinking", effectiveThinking);
13952
13984
  }
13953
13985
  const policy = resolveToolPolicy(input.agent, input.role);
13954
13986
  const explicitTools = policy.tools;
@@ -14914,7 +14946,8 @@ function prepareSpawnContext(input, effectiveTask) {
14914
14946
  sessionEnabled: true,
14915
14947
  maxDepth: input.maxDepth,
14916
14948
  skillPaths: input.skillPaths,
14917
- role: input.role
14949
+ role: input.role,
14950
+ thinkingOverride: input.thinkingOverride
14918
14951
  });
14919
14952
  if (input.steeringFile) built.env.PI_CREW_STEERING_FILE = input.steeringFile;
14920
14953
  if (input.brokerSpawn?.socketPath && input.brokerSpawn.token) {
@@ -17832,45 +17865,45 @@ function readJsonlTail(filePath, tailBytes) {
17832
17865
  }
17833
17866
  }
17834
17867
  }
17835
- function readLinesSince(filePath, state) {
17868
+ function readLinesSince(filePath, state2) {
17836
17869
  let fd;
17837
17870
  try {
17838
17871
  fd = fs24.openSync(filePath, "r");
17839
17872
  } catch {
17840
17873
  return {
17841
17874
  lines: [],
17842
- state: { byteOffset: state.byteOffset, lineCount: state.lineCount },
17875
+ state: { byteOffset: state2.byteOffset, lineCount: state2.lineCount },
17843
17876
  eof: true
17844
17877
  };
17845
17878
  }
17846
17879
  try {
17847
17880
  const stat2 = fs24.fstatSync(fd);
17848
17881
  const fileSize = stat2.size;
17849
- if (fileSize <= state.byteOffset) {
17882
+ if (fileSize <= state2.byteOffset) {
17850
17883
  return {
17851
17884
  lines: [],
17852
- state: { byteOffset: fileSize, lineCount: state.lineCount },
17885
+ state: { byteOffset: fileSize, lineCount: state2.lineCount },
17853
17886
  eof: true
17854
17887
  };
17855
17888
  }
17856
- const bytesToRead = fileSize - state.byteOffset;
17889
+ const bytesToRead = fileSize - state2.byteOffset;
17857
17890
  const buf = Buffer.alloc(bytesToRead);
17858
17891
  let totalRead = 0;
17859
17892
  while (totalRead < bytesToRead) {
17860
17893
  const chunkSize = Math.min(CHUNK_SIZE, bytesToRead - totalRead);
17861
- const bytesRead = fs24.readSync(fd, buf, totalRead, chunkSize, state.byteOffset + totalRead);
17894
+ const bytesRead = fs24.readSync(fd, buf, totalRead, chunkSize, state2.byteOffset + totalRead);
17862
17895
  if (bytesRead === 0) break;
17863
17896
  totalRead += bytesRead;
17864
17897
  }
17865
17898
  const content = buf.toString("utf-8", 0, totalRead);
17866
17899
  const lines = [];
17867
- let lineCount = state.lineCount;
17868
- let committedOffset = state.byteOffset;
17900
+ let lineCount = state2.lineCount;
17901
+ let committedOffset = state2.byteOffset;
17869
17902
  let searchFrom = 0;
17870
17903
  let newlineIdx;
17871
17904
  while ((newlineIdx = content.indexOf("\n", searchFrom)) !== -1) {
17872
17905
  const lineText = content.slice(searchFrom, newlineIdx);
17873
- committedOffset = state.byteOffset + newlineIdx + 1;
17906
+ committedOffset = state2.byteOffset + newlineIdx + 1;
17874
17907
  searchFrom = newlineIdx + 1;
17875
17908
  if (lineText.length > 0) {
17876
17909
  lines.push(lineText);
@@ -17892,8 +17925,8 @@ function readLinesSince(filePath, state) {
17892
17925
  }
17893
17926
  }
17894
17927
  }
17895
- function readJsonlSince(filePath, state) {
17896
- const result4 = readLinesSince(filePath, state);
17928
+ function readJsonlSince(filePath, state2) {
17929
+ const result4 = readLinesSince(filePath, state2);
17897
17930
  const items = [];
17898
17931
  for (const line4 of result4.lines) {
17899
17932
  try {
@@ -20958,12 +20991,12 @@ function readDeliveryState(manifest) {
20958
20991
  if (obj.messages && typeof obj.messages === "object" && !Array.isArray(obj.messages)) {
20959
20992
  for (const [id, status] of Object.entries(obj.messages)) if (isStatus(status)) messages[id] = status;
20960
20993
  }
20961
- const state = {
20994
+ const state2 = {
20962
20995
  messages,
20963
20996
  updatedAt: typeof obj.updatedAt === "string" ? obj.updatedAt : (/* @__PURE__ */ new Date()).toISOString()
20964
20997
  };
20965
- setDeliveryCacheEntry(filePath, { mtimeMs: stat2.mtimeMs, state });
20966
- return state;
20998
+ setDeliveryCacheEntry(filePath, { mtimeMs: stat2.mtimeMs, state: state2 });
20999
+ return state2;
20967
21000
  } catch (error) {
20968
21001
  const quarantinePath = `${filePath}.corrupt-${Date.now()}`;
20969
21002
  try {
@@ -20978,25 +21011,25 @@ function readDeliveryState(manifest) {
20978
21011
  return { messages: {}, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
20979
21012
  }
20980
21013
  }
20981
- function writeDeliveryState(manifest, state, options) {
21014
+ function writeDeliveryState(manifest, state2, options) {
20982
21015
  ensureRunMailbox(manifest);
20983
21016
  const MAX_DELIVERY_MESSAGES = 1e4;
20984
- if (Object.keys(state.messages).length > MAX_DELIVERY_MESSAGES) {
20985
- const sorted = Object.entries(state.messages).sort(([, a], [, b]) => {
21017
+ if (Object.keys(state2.messages).length > MAX_DELIVERY_MESSAGES) {
21018
+ const sorted = Object.entries(state2.messages).sort(([, a], [, b]) => {
20986
21019
  const order = { queued: 0, delivered: 1, acknowledged: 2 };
20987
21020
  return (order[a] ?? 3) - (order[b] ?? 3);
20988
21021
  });
20989
21022
  const trimmed = sorted.slice(0, MAX_DELIVERY_MESSAGES);
20990
- state.messages = Object.fromEntries(trimmed);
21023
+ state2.messages = Object.fromEntries(trimmed);
20991
21024
  }
20992
21025
  const filePath = deliveryFile(manifest, true);
20993
- atomicWriteFile(filePath, `${JSON.stringify(redactSecrets(state), null, 2)}
21026
+ atomicWriteFile(filePath, `${JSON.stringify(redactSecrets(state2), null, 2)}
20994
21027
  `, {
20995
21028
  durability: options?.durability ?? "best-effort"
20996
21029
  });
20997
21030
  try {
20998
21031
  const postStat = fs29.statSync(filePath);
20999
- setDeliveryCacheEntry(filePath, { mtimeMs: postStat.mtimeMs, state });
21032
+ setDeliveryCacheEntry(filePath, { mtimeMs: postStat.mtimeMs, state: state2 });
21000
21033
  } catch {
21001
21034
  deliveryCache.delete(filePath);
21002
21035
  }
@@ -21611,6 +21644,713 @@ var init_runtime_resolver = __esm({
21611
21644
  }
21612
21645
  });
21613
21646
 
21647
+ // src/runtime/model/model-resolver.ts
21648
+ function fuzzyResolveModelId(input, models) {
21649
+ const query = input.toLowerCase();
21650
+ let bestMatch;
21651
+ let bestScore = 0;
21652
+ for (const m of models) {
21653
+ const id = m.id.toLowerCase();
21654
+ const name = (m.name ?? "").toLowerCase();
21655
+ const full = `${m.provider}/${m.id}`.toLowerCase();
21656
+ let score = 0;
21657
+ if (id === query || full === query) {
21658
+ score = 100;
21659
+ } else if (id.includes(query) || full.includes(query)) {
21660
+ score = 60 + query.length / id.length * 30;
21661
+ } else if (name.includes(query)) {
21662
+ score = 40 + query.length / (name.length || 1) * 20;
21663
+ } else if (query.split(/[\s\-/]+/).every((part) => id.includes(part) || name.includes(part) || m.provider.toLowerCase().includes(part))) {
21664
+ score = 20;
21665
+ }
21666
+ if (score > bestScore) {
21667
+ bestScore = score;
21668
+ bestMatch = m;
21669
+ }
21670
+ }
21671
+ return bestMatch && bestScore >= 20 ? `${bestMatch.provider}/${bestMatch.id}` : void 0;
21672
+ }
21673
+ var init_model_resolver = __esm({
21674
+ "src/runtime/model/model-resolver.ts"() {
21675
+ "use strict";
21676
+ }
21677
+ });
21678
+
21679
+ // src/runtime/model/model-scope.ts
21680
+ function patternToRegExp(pattern) {
21681
+ const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
21682
+ return new RegExp(`${escaped.replace(/\*/g, ".*")}`, "i");
21683
+ }
21684
+ function matchesModelPattern(modelId, pattern) {
21685
+ if (!modelId || !pattern) return false;
21686
+ const id = modelId.trim();
21687
+ const pat = pattern.trim();
21688
+ if (!id || !pat) return false;
21689
+ if (id.toLowerCase() === pat.toLowerCase()) return true;
21690
+ if (pat.includes("*")) {
21691
+ try {
21692
+ return patternToRegExp(pat).test(id);
21693
+ } catch {
21694
+ return false;
21695
+ }
21696
+ }
21697
+ return id.toLowerCase().includes(pat.toLowerCase());
21698
+ }
21699
+ function checkModelScope(modelId, patterns, source) {
21700
+ if (!modelId) {
21701
+ return {
21702
+ inScope: true,
21703
+ source,
21704
+ model: "",
21705
+ reason: "no model specified"
21706
+ };
21707
+ }
21708
+ if (!patterns || patterns.length === 0) {
21709
+ return { inScope: true, source, model: modelId };
21710
+ }
21711
+ for (const pattern of patterns) {
21712
+ if (matchesModelPattern(modelId, pattern)) {
21713
+ return {
21714
+ inScope: true,
21715
+ source,
21716
+ model: modelId,
21717
+ matchedPattern: pattern
21718
+ };
21719
+ }
21720
+ }
21721
+ return {
21722
+ inScope: false,
21723
+ source,
21724
+ model: modelId,
21725
+ reason: `model "${modelId}" is not in enabledModels allowlist (${patterns.join(", ")})`
21726
+ };
21727
+ }
21728
+ async function readEnabledModelsPatterns(cwd, agentDir) {
21729
+ try {
21730
+ const mod = await import("@earendil-works/pi-coding-agent").catch(() => null);
21731
+ if (!mod) return [];
21732
+ const SettingsManagerCtor = mod.SettingsManager;
21733
+ if (!SettingsManagerCtor?.create) return [];
21734
+ const sm = SettingsManagerCtor.create(cwd, agentDir);
21735
+ const patterns = sm.getEnabledModels?.();
21736
+ return Array.isArray(patterns) ? patterns : [];
21737
+ } catch {
21738
+ return [];
21739
+ }
21740
+ }
21741
+ var init_model_scope = __esm({
21742
+ "src/runtime/model/model-scope.ts"() {
21743
+ "use strict";
21744
+ }
21745
+ });
21746
+
21747
+ // src/runtime/model/provider-quota.ts
21748
+ function headerNumber(headers, ...names) {
21749
+ for (const name of names) {
21750
+ const raw = headers[name] ?? headers[name.toLowerCase()];
21751
+ if (raw === void 0) continue;
21752
+ const parsed = Number.parseInt(raw, 10);
21753
+ if (Number.isFinite(parsed) && parsed >= 0) return parsed;
21754
+ }
21755
+ return void 0;
21756
+ }
21757
+ function headerRaw(headers, ...names) {
21758
+ for (const name of names) {
21759
+ const raw = headers[name] ?? headers[name.toLowerCase()];
21760
+ if (raw !== void 0) return raw;
21761
+ }
21762
+ return void 0;
21763
+ }
21764
+ function parseResetValue(value, nowMs3) {
21765
+ const trimmed = value.trim();
21766
+ if (/^\d+$/.test(trimmed)) {
21767
+ const secs = Number.parseInt(trimmed, 10);
21768
+ if (Number.isFinite(secs) && secs >= 0) return nowMs3 + secs * 1e3;
21769
+ }
21770
+ if (GO_DURATION_RE.test(trimmed)) {
21771
+ let durationMs = 0;
21772
+ for (const seg of trimmed.matchAll(GO_DURATION_SEG_RE)) {
21773
+ const n = Number.parseInt(seg[1], 10);
21774
+ const unit = seg[2];
21775
+ durationMs += unit === "s" ? n * 1e3 : unit === "m" ? n * 6e4 : n * 36e5;
21776
+ }
21777
+ return nowMs3 + durationMs;
21778
+ }
21779
+ const parsed = Date.parse(trimmed);
21780
+ if (Number.isFinite(parsed) && parsed > 0) return parsed;
21781
+ return void 0;
21782
+ }
21783
+ function headerResetMs(headers, nowMs3) {
21784
+ const resetRaw = headerRaw(headers, "x-ratelimit-reset-requests", "x-ratelimit-reset");
21785
+ if (resetRaw !== void 0) {
21786
+ const parsed = parseResetValue(resetRaw, nowMs3);
21787
+ if (parsed !== void 0) return parsed;
21788
+ }
21789
+ const retryRaw = headerRaw(headers, "retry-after");
21790
+ if (retryRaw !== void 0) {
21791
+ return parseResetValue(retryRaw, nowMs3);
21792
+ }
21793
+ return void 0;
21794
+ }
21795
+ function noteProviderResponse(provider, status, headers, nowMs3 = Date.now()) {
21796
+ const key = provider.toLowerCase();
21797
+ const entry = {
21798
+ provider: key,
21799
+ remainingRequests: headerNumber(headers, "x-ratelimit-remaining-requests", "x-ratelimit-remaining"),
21800
+ remainingTokens: headerNumber(headers, "x-ratelimit-remaining-tokens"),
21801
+ resetAtMs: headerResetMs(headers, nowMs3),
21802
+ lastStatus: status,
21803
+ updatedAtMs: nowMs3
21804
+ };
21805
+ quotaCache.set(key, entry);
21806
+ const evictionCutoff = nowMs3 - 2 * QUOTA_TTL_MS;
21807
+ for (const [cacheKey2, cached2] of quotaCache) {
21808
+ if (cached2.updatedAtMs < evictionCutoff) quotaCache.delete(cacheKey2);
21809
+ }
21810
+ }
21811
+ function isProviderDeprioritized(provider, nowMs3 = Date.now()) {
21812
+ const entry = quotaCache.get(provider.toLowerCase());
21813
+ if (!entry) return false;
21814
+ if (nowMs3 - entry.updatedAtMs > QUOTA_TTL_MS) return false;
21815
+ if (entry.lastStatus === 429) return true;
21816
+ if (entry.remainingRequests === 0 || entry.remainingTokens === 0) return true;
21817
+ if (entry.resetAtMs !== void 0 && entry.resetAtMs > nowMs3) {
21818
+ if (entry.remainingRequests !== void 0 && entry.remainingRequests < 5) return true;
21819
+ if (entry.remainingTokens !== void 0 && entry.remainingTokens < 1e3) return true;
21820
+ }
21821
+ return false;
21822
+ }
21823
+ function deprioritizedProviders(providers, nowMs3 = Date.now()) {
21824
+ return providers.filter((p) => isProviderDeprioritized(p, nowMs3));
21825
+ }
21826
+ function providerRankFromQuota(providers, nowMs3 = Date.now()) {
21827
+ const rank = {};
21828
+ for (const provider of providers) {
21829
+ const key = provider.toLowerCase();
21830
+ const entry = quotaCache.get(key);
21831
+ if (!entry || nowMs3 - entry.updatedAtMs > QUOTA_TTL_MS) {
21832
+ rank[key] = Number.MAX_SAFE_INTEGER;
21833
+ continue;
21834
+ }
21835
+ if (isProviderDeprioritized(key, nowMs3)) {
21836
+ rank[key] = Number.MAX_SAFE_INTEGER - 1;
21837
+ continue;
21838
+ }
21839
+ const remaining = entry.remainingRequests ?? entry.remainingTokens;
21840
+ rank[key] = remaining !== void 0 ? Math.max(0, 1e3 - remaining) : Number.MAX_SAFE_INTEGER;
21841
+ }
21842
+ return rank;
21843
+ }
21844
+ function clearProviderQuotaCache() {
21845
+ quotaCache.clear();
21846
+ }
21847
+ var QUOTA_TTL_MS, quotaCache, GO_DURATION_RE, GO_DURATION_SEG_RE;
21848
+ var init_provider_quota = __esm({
21849
+ "src/runtime/model/provider-quota.ts"() {
21850
+ "use strict";
21851
+ QUOTA_TTL_MS = 5 * 60 * 1e3;
21852
+ quotaCache = /* @__PURE__ */ new Map();
21853
+ GO_DURATION_RE = /^(\d+[smh])+$/;
21854
+ GO_DURATION_SEG_RE = /(\d+)([smh])/g;
21855
+ }
21856
+ });
21857
+
21858
+ // src/runtime/model/model-fallback.ts
21859
+ import * as fs31 from "node:fs";
21860
+ import * as os9 from "node:os";
21861
+ import * as path27 from "node:path";
21862
+ function modelInfoFromUnknown(value) {
21863
+ if (typeof value === "string") {
21864
+ const raw = value.trim();
21865
+ if (!raw) return void 0;
21866
+ const slashIdx = raw.indexOf("/");
21867
+ if (slashIdx <= 0) return { provider: "", id: raw, fullId: raw };
21868
+ return {
21869
+ provider: raw.slice(0, slashIdx),
21870
+ id: raw.slice(slashIdx + 1),
21871
+ fullId: raw
21872
+ };
21873
+ }
21874
+ if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
21875
+ const record = value;
21876
+ if (typeof record.provider !== "string" || typeof record.id !== "string") return void 0;
21877
+ return {
21878
+ provider: record.provider,
21879
+ id: record.id,
21880
+ fullId: `${record.provider}/${record.id}`
21881
+ };
21882
+ }
21883
+ function availableModelInfosFromRegistry(registry2) {
21884
+ if (!registry2 || typeof registry2 !== "object" || Array.isArray(registry2)) return void 0;
21885
+ const candidate = registry2;
21886
+ const raw = typeof candidate.getAvailable === "function" ? candidate.getAvailable() : typeof candidate.getAll === "function" ? candidate.getAll() : void 0;
21887
+ if (!Array.isArray(raw)) return void 0;
21888
+ return raw.map(modelInfoFromUnknown).filter((entry) => entry !== void 0);
21889
+ }
21890
+ function modelStringFromUnknown(model) {
21891
+ return modelInfoFromUnknown(model)?.fullId;
21892
+ }
21893
+ function modelRefToString(model) {
21894
+ return modelInfoFromUnknown(model)?.fullId;
21895
+ }
21896
+ function providerOfModelRef(model) {
21897
+ if (!model) return void 0;
21898
+ const slashIdx = model.indexOf("/");
21899
+ return slashIdx > 0 ? model.slice(0, slashIdx) : void 0;
21900
+ }
21901
+ function uniqueModelInfos(models) {
21902
+ const seen = /* @__PURE__ */ new Set();
21903
+ return models.filter((model) => {
21904
+ if (seen.has(model.fullId)) return false;
21905
+ seen.add(model.fullId);
21906
+ return true;
21907
+ });
21908
+ }
21909
+ function readJsonObject(filePath) {
21910
+ try {
21911
+ const parsed = JSON.parse(fs31.readFileSync(filePath, "utf-8"));
21912
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : void 0;
21913
+ } catch {
21914
+ return void 0;
21915
+ }
21916
+ }
21917
+ function piAgentDir() {
21918
+ const envDir = process.env.PI_CODING_AGENT_DIR?.trim();
21919
+ if (envDir) {
21920
+ if (envDir === "~") return os9.homedir();
21921
+ if (envDir.startsWith("~/")) return path27.join(os9.homedir(), envDir.slice(2));
21922
+ return envDir;
21923
+ }
21924
+ return path27.join(os9.homedir(), ".pi", "agent");
21925
+ }
21926
+ function settingsModelInfo(settings) {
21927
+ if (typeof settings?.defaultProvider !== "string" || typeof settings.defaultModel !== "string") return void 0;
21928
+ return {
21929
+ provider: settings.defaultProvider,
21930
+ id: settings.defaultModel,
21931
+ fullId: `${settings.defaultProvider}/${settings.defaultModel}`
21932
+ };
21933
+ }
21934
+ function modelsJsonInfos(modelsJson) {
21935
+ if (!modelsJson?.providers || typeof modelsJson.providers !== "object" || Array.isArray(modelsJson.providers)) return [];
21936
+ const infos = [];
21937
+ for (const [provider, rawConfig] of Object.entries(modelsJson.providers)) {
21938
+ if (!rawConfig || typeof rawConfig !== "object" || Array.isArray(rawConfig)) continue;
21939
+ const config = rawConfig;
21940
+ if (Array.isArray(config.models)) {
21941
+ for (const rawModel of config.models) {
21942
+ if (!rawModel || typeof rawModel !== "object" || Array.isArray(rawModel)) continue;
21943
+ const id = rawModel.id;
21944
+ if (typeof id === "string") infos.push({ provider, id, fullId: `${provider}/${id}` });
21945
+ }
21946
+ }
21947
+ if (config.modelOverrides && typeof config.modelOverrides === "object" && !Array.isArray(config.modelOverrides)) {
21948
+ for (const id of Object.keys(config.modelOverrides)) infos.push({ provider, id, fullId: `${provider}/${id}` });
21949
+ }
21950
+ }
21951
+ return infos;
21952
+ }
21953
+ function providersWithCredentials(modelsJson, env = process.env) {
21954
+ const providers = /* @__PURE__ */ new Set();
21955
+ const auth = readJsonObject(path27.join(piAgentDir(), "auth.json"));
21956
+ for (const key of Object.keys(auth ?? {})) providers.add(key);
21957
+ if (modelsJson?.providers && typeof modelsJson.providers === "object" && !Array.isArray(modelsJson.providers)) {
21958
+ for (const [provider, rawConfig] of Object.entries(modelsJson.providers)) {
21959
+ if (!rawConfig || typeof rawConfig !== "object" || Array.isArray(rawConfig)) continue;
21960
+ const config = rawConfig;
21961
+ if (typeof config.apiKey === "string" && config.apiKey.trim()) providers.add(provider);
21962
+ if (typeof config.baseUrl === "string" && config.baseUrl.trim()) providers.add(provider);
21963
+ }
21964
+ }
21965
+ for (const key of Object.keys(env)) {
21966
+ const match = /^([A-Z0-9]+(?:_[A-Z0-9]+)*)_API_KEY$/.exec(key);
21967
+ if (match && env[key]?.trim()) providers.add(match[1].toLowerCase().replace(/_/g, "-"));
21968
+ }
21969
+ return providers;
21970
+ }
21971
+ function fileSignature(filePath) {
21972
+ try {
21973
+ const stat2 = fs31.statSync(filePath);
21974
+ return `${stat2.mtimeMs}:${stat2.size}`;
21975
+ } catch {
21976
+ return "-";
21977
+ }
21978
+ }
21979
+ function configuredModelInfosFromPiConfig(cwd, options) {
21980
+ const agentDir = piAgentDir();
21981
+ const globalSettingsPath = path27.join(agentDir, "settings.json");
21982
+ const modelsJsonPath = path27.join(agentDir, "models.json");
21983
+ const authPath = path27.join(agentDir, "auth.json");
21984
+ const projectSettingsPath = cwd ? path27.join(cwd, ".pi", "settings.json") : void 0;
21985
+ const cacheKey2 = `${agentDir}\0${cwd ?? ""}`;
21986
+ const signature = [globalSettingsPath, modelsJsonPath, authPath, ...projectSettingsPath ? [projectSettingsPath] : []].map(fileSignature).join("|");
21987
+ const cached2 = configuredModelCache.get(cacheKey2);
21988
+ if (cached2?.signature === signature) return options?.requireCredentials ? cached2.credentialed : cached2.all;
21989
+ const globalSettings = readJsonObject(globalSettingsPath);
21990
+ const projectSettings = projectSettingsPath ? readJsonObject(projectSettingsPath) : void 0;
21991
+ const effectiveSettings = {
21992
+ ...globalSettings ?? {},
21993
+ ...projectSettings ?? {}
21994
+ };
21995
+ const defaultModel = settingsModelInfo(effectiveSettings);
21996
+ const modelsJson = readJsonObject(modelsJsonPath);
21997
+ const all = uniqueModelInfos([...defaultModel ? [defaultModel] : [], ...modelsJsonInfos(modelsJson)]);
21998
+ const credentialedProviders = providersWithCredentials(modelsJson);
21999
+ const credentialed = all.filter(
22000
+ (info2) => info2.fullId === defaultModel?.fullId || credentialedProviders.has(info2.provider.toLowerCase())
22001
+ );
22002
+ configuredModelCache.set(cacheKey2, { signature, all, credentialed });
22003
+ if (configuredModelCache.size > 32) {
22004
+ const oldestKey = configuredModelCache.keys().next().value;
22005
+ if (oldestKey !== void 0) configuredModelCache.delete(oldestKey);
22006
+ }
22007
+ return options?.requireCredentials ? credentialed : all;
22008
+ }
22009
+ function splitThinkingSuffix(model) {
22010
+ const colonIdx = model.lastIndexOf(":");
22011
+ if (colonIdx === -1) return { baseModel: model, thinkingSuffix: "" };
22012
+ return {
22013
+ baseModel: model.substring(0, colonIdx),
22014
+ thinkingSuffix: model.substring(colonIdx)
22015
+ };
22016
+ }
22017
+ function resolveModelCandidate(model, availableModels, preferredProvider) {
22018
+ if (!model) return void 0;
22019
+ if (model.includes("/")) return model;
22020
+ if (!availableModels || availableModels.length === 0) return model;
22021
+ const { baseModel, thinkingSuffix } = splitThinkingSuffix(model);
22022
+ const matches = availableModels.filter((entry) => entry.id === baseModel);
22023
+ if (preferredProvider) {
22024
+ const preferredMatch = matches.find((entry) => entry.provider === preferredProvider);
22025
+ if (preferredMatch) return `${preferredMatch.fullId}${thinkingSuffix}`;
22026
+ }
22027
+ if (matches.length !== 1) {
22028
+ const fuzzy = fuzzyResolveModelId(baseModel, availableModels);
22029
+ if (fuzzy) return `${fuzzy}${thinkingSuffix}`;
22030
+ return model;
22031
+ }
22032
+ return `${matches[0].fullId}${thinkingSuffix}`;
22033
+ }
22034
+ function isRetryableModelFailure(error) {
22035
+ if (!error) return false;
22036
+ if (NON_RETRYABLE_MODEL_FAILURE_PATTERNS.some((pattern) => pattern.test(error))) return false;
22037
+ return RETRYABLE_MODEL_FAILURE_PATTERNS.some((pattern) => pattern.test(error));
22038
+ }
22039
+ function formatModelAttemptNote(attempt, nextModel) {
22040
+ const failure = attempt.error?.trim() || `exit ${attempt.exitCode ?? 1}`;
22041
+ return nextModel ? `[fallback] ${attempt.model} failed: ${failure}. Retrying with ${nextModel}.` : `[fallback] ${attempt.model} failed: ${failure}.`;
22042
+ }
22043
+ function buildModelCandidates(primaryModel, fallbackModels, availableModels, preferredProvider) {
22044
+ const seen = /* @__PURE__ */ new Set();
22045
+ const candidates = [];
22046
+ for (const raw of [primaryModel, ...fallbackModels ?? []]) {
22047
+ if (!raw) continue;
22048
+ const normalized = resolveModelCandidate(raw.trim(), availableModels, preferredProvider);
22049
+ if (!normalized || seen.has(normalized)) continue;
22050
+ seen.add(normalized);
22051
+ candidates.push(normalized);
22052
+ }
22053
+ return candidates;
22054
+ }
22055
+ function isAvailableModel(model, availableModels) {
22056
+ if (!availableModels || availableModels.length === 0) return true;
22057
+ const { baseModel } = splitThinkingSuffix(model);
22058
+ if (baseModel.includes("/")) return availableModels.some((entry) => entry.fullId === baseModel);
22059
+ if (availableModels.some((entry) => entry.id === baseModel)) return true;
22060
+ const fuzzy = fuzzyResolveModelId(baseModel, availableModels);
22061
+ return fuzzy !== void 0;
22062
+ }
22063
+ function orderAutoFallbacks(candidates, policy, anchorProvider) {
22064
+ if (!policy || policy.order === "asIs") return candidates;
22065
+ const deprioritized = new Set((policy.deprioritizedProviders ?? []).map((p) => p.toLowerCase()));
22066
+ const rank = policy.providerRank ?? {};
22067
+ const rankFor = (provider) => {
22068
+ if (!provider) return Number.MAX_SAFE_INTEGER;
22069
+ const value = rank[provider] ?? rank[provider.toLowerCase()];
22070
+ return typeof value === "number" ? value : Number.MAX_SAFE_INTEGER;
22071
+ };
22072
+ return candidates.map((model, index) => {
22073
+ const provider = providerOfModelRef(model);
22074
+ return {
22075
+ model,
22076
+ index,
22077
+ exhausted: provider && deprioritized.has(provider.toLowerCase()) ? 1 : 0,
22078
+ anchored: anchorProvider && provider === anchorProvider ? 0 : 1,
22079
+ rank: rankFor(provider)
22080
+ };
22081
+ }).sort((a, b) => a.exhausted - b.exhausted || a.anchored - b.anchored || a.rank - b.rank || a.index - b.index).map((entry) => entry.model);
22082
+ }
22083
+ function resolveModelFallbackPolicy(config, env = process.env) {
22084
+ const envMaxAuto = env.PI_CREW_MAX_AUTO_FALLBACKS;
22085
+ let maxAutoFallbacks;
22086
+ if (envMaxAuto) {
22087
+ const parsed = Number.parseInt(envMaxAuto, 10);
22088
+ if (Number.isFinite(parsed) && parsed >= 0) {
22089
+ maxAutoFallbacks = parsed;
22090
+ } else if (Number.isFinite(parsed)) {
22091
+ logInternalError(
22092
+ "model-fallback.max-auto-fallbacks-invalid",
22093
+ void 0,
22094
+ `PI_CREW_MAX_AUTO_FALLBACKS="${envMaxAuto}" is negative, clamped to 0`,
22095
+ "warn"
22096
+ );
22097
+ maxAutoFallbacks = 0;
22098
+ } else {
22099
+ logInternalError(
22100
+ "model-fallback.max-auto-fallbacks-invalid",
22101
+ void 0,
22102
+ `PI_CREW_MAX_AUTO_FALLBACKS="${envMaxAuto}" is invalid, falling back to config`,
22103
+ "warn"
22104
+ );
22105
+ maxAutoFallbacks = config?.maxAutoFallbacks;
22106
+ }
22107
+ } else {
22108
+ maxAutoFallbacks = config?.maxAutoFallbacks;
22109
+ }
22110
+ const order = env.PI_CREW_MODEL_FALLBACK_ORDER ?? config?.order;
22111
+ const requireCredentials = env.PI_CREW_MODEL_REQUIRE_CREDENTIALS === "1" ? true : env.PI_CREW_MODEL_REQUIRE_CREDENTIALS === "0" ? false : config?.requireCredentials;
22112
+ const quotaAware = config?.quotaAwareOrdering !== false;
22113
+ if (maxAutoFallbacks === void 0 && !order && requireCredentials === void 0 && config?.quotaAwareOrdering === void 0)
22114
+ return void 0;
22115
+ return {
22116
+ ...maxAutoFallbacks !== void 0 && Number.isFinite(maxAutoFallbacks) ? { maxAutoFallbacks } : {},
22117
+ ...order ? { order } : {},
22118
+ ...requireCredentials !== void 0 ? { requireCredentials } : {},
22119
+ quotaAwareOrdering: quotaAware
22120
+ };
22121
+ }
22122
+ function resolveDefaultSubagentModel(config, env = process.env) {
22123
+ return env.PI_CREW_MODEL?.trim() || config?.defaultSubagentModel?.trim() || void 0;
22124
+ }
22125
+ function buildConfiguredModelRouting(input) {
22126
+ const registryModels = availableModelInfosFromRegistry(input.modelRegistry);
22127
+ const configModels = configuredModelInfosFromPiConfig(input.cwd, {
22128
+ requireCredentials: input.policy?.requireCredentials
22129
+ });
22130
+ const availableModels = registryModels && registryModels.length > 0 ? registryModels : configModels.length > 0 ? configModels : registryModels;
22131
+ const parentModel = modelStringFromUnknown(input.parentModel);
22132
+ const preferredProvider = providerOfModelRef(parentModel) ?? availableModels?.[0]?.provider;
22133
+ const defaultSubagent = input.defaultSubagentModel?.trim() || void 0;
22134
+ const effectiveAgentModel = input.agentModel?.trim() ? input.agentModel : defaultSubagent ?? parentModel;
22135
+ const requested = [input.overrideModel, input.stepModel, input.teamRoleModel, effectiveAgentModel].find(
22136
+ (model) => Boolean(model?.trim())
22137
+ );
22138
+ if (availableModels && availableModels.length === 0)
22139
+ return {
22140
+ requested,
22141
+ candidates: [],
22142
+ reason: "no configured Pi models available"
22143
+ };
22144
+ const declaredRaw = [
22145
+ input.overrideModel,
22146
+ input.stepModel,
22147
+ input.teamRoleModel,
22148
+ effectiveAgentModel,
22149
+ // When defaultSubagentModel replaced parentModel as the effective agent
22150
+ // model, keep parentModel as an explicit fallback before the auto tail.
22151
+ ...defaultSubagent && !input.agentModel?.trim() ? [parentModel] : [],
22152
+ ...input.teamRoleFallbackModels ?? [],
22153
+ ...input.fallbackModels ?? []
22154
+ ];
22155
+ const parentModelRaw = effectiveAgentModel?.trim() || void 0;
22156
+ const parentModelFallback = defaultSubagent && !input.agentModel?.trim() ? parentModel : void 0;
22157
+ const declaredModels = declaredRaw.filter((model) => Boolean(model?.trim())).filter((model, idx) => {
22158
+ if (parentModelRaw && idx === 0 && model.trim() === parentModelRaw) return true;
22159
+ if (parentModelFallback && model.trim() === parentModelFallback) return true;
22160
+ return isAvailableModel(model.trim(), availableModels);
22161
+ });
22162
+ const declaredCandidates = buildModelCandidates(declaredModels[0], declaredModels.slice(1), availableModels, preferredProvider);
22163
+ const autoRaw = availableModels ? availableModels.map((model) => model.fullId) : parentModel ? [parentModel] : [];
22164
+ const declaredSet = new Set(declaredCandidates);
22165
+ const autoResolved = buildModelCandidates(void 0, autoRaw, availableModels, preferredProvider).filter(
22166
+ (candidate) => !declaredSet.has(candidate)
22167
+ );
22168
+ const anchorProvider = providerOfModelRef(declaredCandidates[0]) ?? providerOfModelRef(parentModel);
22169
+ let effectivePolicy = input.policy;
22170
+ if (effectivePolicy?.quotaAwareOrdering !== false && autoResolved.length > 0) {
22171
+ const tailProviders = [...new Set(autoResolved.map((m) => providerOfModelRef(m)).filter((p) => Boolean(p)))];
22172
+ const deprioritized = deprioritizedProviders(tailProviders);
22173
+ const rank = providerRankFromQuota(tailProviders);
22174
+ if (deprioritized.length > 0 || Object.keys(rank).length > 0) {
22175
+ effectivePolicy = {
22176
+ ...effectivePolicy,
22177
+ deprioritizedProviders: [...effectivePolicy?.deprioritizedProviders ?? [], ...deprioritized],
22178
+ providerRank: { ...rank, ...effectivePolicy?.providerRank ?? {} }
22179
+ };
22180
+ }
22181
+ }
22182
+ const autoOrdered = orderAutoFallbacks(autoResolved, effectivePolicy, anchorProvider);
22183
+ const autoCandidates = effectivePolicy?.maxAutoFallbacks === void 0 ? autoOrdered : autoOrdered.slice(0, Math.max(0, effectivePolicy.maxAutoFallbacks));
22184
+ const candidates = [...declaredCandidates, ...autoCandidates];
22185
+ const resolvedRequested = requested ? resolveModelCandidate(requested, availableModels, preferredProvider) : void 0;
22186
+ const droppedRequested = requested && candidates[0] && resolvedRequested !== candidates[0] ? requested : void 0;
22187
+ const reason = droppedRequested ? "requested model unavailable; selected configured Pi fallback" : candidates.length > 1 ? "configured Pi fallback chain" : void 0;
22188
+ let scopeVerdict;
22189
+ if (input.scopeModelsPatterns && input.scopeModelsPatterns.length > 0) {
22190
+ const resolved = candidates[0] ?? requested;
22191
+ const source = input.isFrontmatterOverride ? "frontmatter" : input.overrideModel ? "caller" : input.stepModel ? "caller" : input.teamRoleModel ? "caller" : input.agentModel?.trim() ? "frontmatter" : "resolved";
22192
+ scopeVerdict = checkModelScope(resolved, input.scopeModelsPatterns, source);
22193
+ if (!scopeVerdict.inScope && source === "caller" && !input.isFrontmatterOverride) {
22194
+ throw errors.modelOutOfScope(resolved ?? "", input.scopeModelsPatterns);
22195
+ }
22196
+ }
22197
+ return {
22198
+ requested,
22199
+ candidates,
22200
+ reason,
22201
+ droppedRequested,
22202
+ autoFallbackCount: autoCandidates.length,
22203
+ scopeVerdict
22204
+ };
22205
+ }
22206
+ function warnOutOfScopeSoft(verdict, scope, prefix = "Model") {
22207
+ if (!verdict || verdict.inScope || verdict.source === "caller") return;
22208
+ logInternalError(
22209
+ scope,
22210
+ void 0,
22211
+ `${prefix} "${verdict.model}" from source "${verdict.source}" is outside enabledModels scope: ${verdict.reason ?? "unknown"}. Running anyway (soft warn).`,
22212
+ "warn"
22213
+ );
22214
+ }
22215
+ var configuredModelCache, RETRYABLE_MODEL_FAILURE_PATTERNS, NON_RETRYABLE_MODEL_FAILURE_PATTERNS;
22216
+ var init_model_fallback = __esm({
22217
+ "src/runtime/model/model-fallback.ts"() {
22218
+ "use strict";
22219
+ init_errors3();
22220
+ init_internal_error();
22221
+ init_model_resolver();
22222
+ init_model_scope();
22223
+ init_provider_quota();
22224
+ configuredModelCache = /* @__PURE__ */ new Map();
22225
+ RETRYABLE_MODEL_FAILURE_PATTERNS = [
22226
+ /rate.?limit/i,
22227
+ /too many requests/i,
22228
+ /\b429\b/,
22229
+ /rate_limit_error/i,
22230
+ /quota/i,
22231
+ /provider.*unavailable/i,
22232
+ /model.*unavailable/i,
22233
+ /model.*disabled/i,
22234
+ /model.*not found/i,
22235
+ /unknown model/i,
22236
+ /overloaded/i,
22237
+ /service unavailable/i,
22238
+ /temporar(?:ily)? unavailable/i,
22239
+ /connection refused/i,
22240
+ /fetch failed/i,
22241
+ /network error/i,
22242
+ /socket hang up/i,
22243
+ /upstream/i,
22244
+ /timed? out/i,
22245
+ /timeout/i,
22246
+ /\b502\b/,
22247
+ /\b503\b/,
22248
+ /\b504\b/,
22249
+ //
22250
+ // Provider-side 5xx / generic api_error. The pi-core retry layer already
22251
+ // retries these (agent-session.ts matches `500|server error|internal error`),
22252
+ // but the pi-crew MODEL FALLBACK layer must ALSO treat them as retryable so
22253
+ // that when the provider is hard-down across all 3 provider retries, we fail
22254
+ // over to the next configured model instead of giving up. Reported case
22255
+ // (2026-06-17): `500 {"type":"error","error":{"type":"api_error",
22256
+ // "message":"unknown error, 999 (1000)"}}` — a transient provider outage that
22257
+ // should trigger the fallback chain, not abort.
22258
+ //
22259
+ // `api_error` is the OpenAI-compatible generic error type (vs rate_limit_error
22260
+ // / overloaded_error / etc.) and almost always means a transient server fault.
22261
+ //
22262
+ // `unknown error` is the body of the generic message; `internal`/`server`
22263
+ // catch the common phrasings. `\b500\b`/`\b501\b` catch the HTTP status in
22264
+ // the rendered error string.
22265
+ /\b500\b/,
22266
+ /\b501\b/,
22267
+ /api_error/i,
22268
+ /unknown error/i,
22269
+ /internal(?:_server)?[ _]error/i,
22270
+ /server error/i,
22271
+ /bad gateway/i,
22272
+ //
22273
+ // Broader retryable patterns (added 2026-06-25, FIX 2):
22274
+ // - `/provider[_ ]?error/i`: OpenAI-compatible "Provider error" generic fault.
22275
+ // - `/context[_ ]?length[_ ]?exceeded/i`: "context_length_exceeded" from
22276
+ // OpenAI/Anthropic — when the configured model is the bottleneck, a
22277
+ // different model in the fallback chain may have a larger window.
22278
+ // - `/safety/i`: Anthropic safety blocks — typically retryable on a
22279
+ // different model in the fallback chain.
22280
+ // - `/is[_ ]?overloaded/i`: alias to the existing `/overloaded/i` pattern
22281
+ // to catch phrasings like "upstream is overloaded".
22282
+ // - `/\b408\b/`: HTTP 408 Request Timeout — transient, provider-side.
22283
+ //
22284
+ // Intentionally NOT added: `/bad_request/` — can mean bad input (e.g.
22285
+ // invalid schema), which is non-retryable.
22286
+ /provider[_ ]?error/i,
22287
+ /context[_ ]?length[_ ]?exceeded/i,
22288
+ /safety/i,
22289
+ /is[_ ]?overloaded/i,
22290
+ /\b408\b/
22291
+ ];
22292
+ NON_RETRYABLE_MODEL_FAILURE_PATTERNS = [
22293
+ /auth(?:entication)?/i,
22294
+ /unauthori[sz]ed/i,
22295
+ /forbidden/i,
22296
+ /api key/i,
22297
+ /token expired/i,
22298
+ /invalid key/i,
22299
+ /billing/i,
22300
+ /credit/i
22301
+ ];
22302
+ }
22303
+ });
22304
+
22305
+ // src/runtime/model/session-model.ts
22306
+ function noteSessionModel(model, source = "model_select") {
22307
+ const normalized = modelRefToString(model);
22308
+ if (!normalized) return;
22309
+ if (source === "session_start" && state.source === "model_select") return;
22310
+ state.model = normalized;
22311
+ state.source = source;
22312
+ state.updatedAt = Date.now();
22313
+ }
22314
+ function noteSessionThinking(level) {
22315
+ if (typeof level !== "string") return;
22316
+ const value = level.trim();
22317
+ state.thinking = value && value !== "off" ? value : void 0;
22318
+ }
22319
+ function currentSessionModel() {
22320
+ return state.model;
22321
+ }
22322
+ function currentSessionThinking() {
22323
+ return state.thinking;
22324
+ }
22325
+ function resolveParentModel(ctxModel) {
22326
+ return state.model ?? modelRefToString(ctxModel);
22327
+ }
22328
+ function captureRunModelContext(ctx, override) {
22329
+ const parentModel = resolveParentModel(ctx.model);
22330
+ const registryModels = availableModelInfosFromRegistry(ctx.modelRegistry);
22331
+ const availableModels = registryModels && registryModels.length > 0 ? registryModels.map((model) => model.fullId) : void 0;
22332
+ const parentThinking = currentSessionThinking() ?? (typeof ctx.thinkingLevel === "string" ? ctx.thinkingLevel : void 0);
22333
+ const trimmedOverride = override?.trim() || void 0;
22334
+ if (!parentModel && !availableModels && !parentThinking && !trimmedOverride) return void 0;
22335
+ return {
22336
+ ...trimmedOverride ? { override: trimmedOverride } : {},
22337
+ ...parentModel ? { parentModel } : {},
22338
+ ...parentThinking && parentThinking !== "off" ? { parentThinking } : {},
22339
+ ...availableModels ? { availableModels } : {}
22340
+ };
22341
+ }
22342
+ function sessionModelSnapshot() {
22343
+ return { ...state };
22344
+ }
22345
+ var state;
22346
+ var init_session_model = __esm({
22347
+ "src/runtime/model/session-model.ts"() {
22348
+ "use strict";
22349
+ init_model_fallback();
22350
+ state = { source: "none" };
22351
+ }
22352
+ });
22353
+
21614
22354
  // src/extension/team-tool/config-patch.ts
21615
22355
  function sanitizeObject2(obj) {
21616
22356
  if (obj === null || obj === void 0 || typeof obj !== "object") return obj;
@@ -21914,14 +22654,14 @@ var init_i18n = __esm({
21914
22654
  });
21915
22655
 
21916
22656
  // src/runtime/async-marker.ts
21917
- import * as fs31 from "node:fs";
21918
- import * as path27 from "node:path";
22657
+ import * as fs32 from "node:fs";
22658
+ import * as path28 from "node:path";
21919
22659
  function asyncStartMarkerPath(manifest) {
21920
- return path27.join(manifest.stateRoot, "async.pid");
22660
+ return path28.join(manifest.stateRoot, "async.pid");
21921
22661
  }
21922
22662
  function hasAsyncStartMarker(manifest) {
21923
22663
  try {
21924
- const raw = JSON.parse(fs31.readFileSync(asyncStartMarkerPath(manifest), "utf-8"));
22664
+ const raw = JSON.parse(fs32.readFileSync(asyncStartMarkerPath(manifest), "utf-8"));
21925
22665
  return typeof raw.pid === "number" && Number.isInteger(raw.pid) && raw.pid > 0 && typeof raw.startedAt === "string" && raw.startedAt.length > 0;
21926
22666
  } catch {
21927
22667
  return false;
@@ -22034,8 +22774,8 @@ var init_process_status = __esm({
22034
22774
  });
22035
22775
 
22036
22776
  // src/runtime/run-tracker.ts
22037
- import * as fs32 from "node:fs";
22038
- import * as path28 from "node:path";
22777
+ import * as fs33 from "node:fs";
22778
+ import * as path29 from "node:path";
22039
22779
  function registerRunPromise(runId) {
22040
22780
  let resolve25;
22041
22781
  let reject;
@@ -22083,8 +22823,8 @@ async function waitForRun(runId, cwd, options = {}) {
22083
22823
  let attempt = 0;
22084
22824
  while (Date.now() < deadline) {
22085
22825
  if (attempt === 0) {
22086
- const runDir = path28.join(projectCrewRoot(cwd), "state", "runs", runId);
22087
- if (!fs32.existsSync(runDir)) {
22826
+ const runDir = path29.join(projectCrewRoot(cwd), "state", "runs", runId);
22827
+ if (!fs33.existsSync(runDir)) {
22088
22828
  throw new Error(`Run ${runId} not found. No run directory at ${runDir}`);
22089
22829
  }
22090
22830
  }
@@ -22234,13 +22974,13 @@ var init_crew_hooks = __esm({
22234
22974
  });
22235
22975
 
22236
22976
  // src/runtime/skill-effectiveness.ts
22237
- import { existsSync as existsSync23, mkdirSync as mkdirSync14, readFileSync as readFileSync22, writeFileSync as writeFileSync4 } from "node:fs";
22238
- import { dirname as dirname16, join as join28 } from "node:path";
22977
+ 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";
22239
22979
  function getSkillMetricsPath(cwd, runId) {
22240
- return join28(projectCrewRoot(cwd), `state/runs/${runId}/skill-metrics.jsonl`);
22980
+ return join29(projectCrewRoot(cwd), `state/runs/${runId}/skill-metrics.jsonl`);
22241
22981
  }
22242
22982
  function getSkillActivationsPath(cwd, runId) {
22243
- return join28(projectCrewRoot(cwd), `state/runs/${runId}/skill-activations.jsonl`);
22983
+ return join29(projectCrewRoot(cwd), `state/runs/${runId}/skill-activations.jsonl`);
22244
22984
  }
22245
22985
  function ensureSkillMetricsDir(cwd, runId) {
22246
22986
  const dir = dirname16(getSkillMetricsPath(cwd, runId));
@@ -22297,7 +23037,7 @@ function getSkillActivations(cwd, runId) {
22297
23037
  if (!existsSync23(path89)) {
22298
23038
  return [];
22299
23039
  }
22300
- const content = readFileSync22(path89, "utf-8");
23040
+ const content = readFileSync23(path89, "utf-8");
22301
23041
  if (!content.trim()) {
22302
23042
  return [];
22303
23043
  }
@@ -22475,10 +23215,10 @@ __export(skill_instructions_exports, {
22475
23215
  resetSkillCacheStats: () => resetSkillCacheStats,
22476
23216
  resolveTaskSkillNames: () => resolveTaskSkillNames
22477
23217
  });
22478
- import * as fs33 from "node:fs";
22479
- import * as path29 from "node:path";
23218
+ import * as fs34 from "node:fs";
23219
+ import * as path30 from "node:path";
22480
23220
  import { fileURLToPath as fileURLToPath4 } from "node:url";
22481
- import * as os9 from "node:os";
23221
+ import * as os10 from "node:os";
22482
23222
  function isValidSkillName(name) {
22483
23223
  return name.length > 0 && name.length <= MAX_SKILL_NAME_CHARS && isSafePathId(name);
22484
23224
  }
@@ -22528,18 +23268,18 @@ function candidateSkillDirs(cwd) {
22528
23268
  // F6 (v0.7.9): same five roots as discover-skills, in the same precedence
22529
23269
  // order. The first hit wins, so a project `.pi/skills/foo/SKILL.md`
22530
23270
  // overrides both the bundled `foo` and any legacy `<cwd>/skills/foo`.
22531
- { root: path29.resolve(cwd, ".pi", "skills"), source: "project-pi" },
23271
+ { root: path30.resolve(cwd, ".pi", "skills"), source: "project-pi" },
22532
23272
  {
22533
- root: path29.resolve(cwd, ".agents", "skills"),
23273
+ root: path30.resolve(cwd, ".agents", "skills"),
22534
23274
  source: "project-agents"
22535
23275
  },
22536
- { root: path29.resolve(cwd, "skills"), source: "project" },
22537
- { root: path29.join(getAgentDir(), "skills"), source: "user-pi" },
23276
+ { root: path30.resolve(cwd, "skills"), source: "project" },
23277
+ { root: path30.join(getAgentDir(), "skills"), source: "user-pi" },
22538
23278
  {
22539
- root: path29.join(os9.homedir(), ".agents", "skills"),
23279
+ root: path30.join(os10.homedir(), ".agents", "skills"),
22540
23280
  source: "user-agents"
22541
23281
  },
22542
- { root: path29.join(os9.homedir(), ".pi", "skills"), source: "user-pi" }
23282
+ { root: path30.join(os10.homedir(), ".pi", "skills"), source: "user-pi" }
22543
23283
  ];
22544
23284
  }
22545
23285
  function rememberSkill(key, value) {
@@ -22577,7 +23317,7 @@ function _setSkillCacheMaxEntriesForTesting(max) {
22577
23317
  }
22578
23318
  function cachedSkillFresh(value) {
22579
23319
  try {
22580
- const stat2 = fs33.statSync(value.path);
23320
+ const stat2 = fs34.statSync(value.path);
22581
23321
  return stat2.mtimeMs === value.mtimeMs && stat2.size === value.size;
22582
23322
  } catch {
22583
23323
  return false;
@@ -22585,7 +23325,7 @@ function cachedSkillFresh(value) {
22585
23325
  }
22586
23326
  function readSkillMarkdown(cwd, name) {
22587
23327
  if (!isValidSkillName(name)) return void 0;
22588
- const cacheKey2 = `${path29.resolve(cwd)}:${name}`;
23328
+ const cacheKey2 = `${path30.resolve(cwd)}:${name}`;
22589
23329
  const cached2 = skillReadCache.get(cacheKey2);
22590
23330
  if (cached2 && cachedSkillFresh(cached2)) {
22591
23331
  skillCacheStats.hits++;
@@ -22596,13 +23336,13 @@ function readSkillMarkdown(cwd, name) {
22596
23336
  skillCacheStats.currentSize = skillReadCache.size;
22597
23337
  for (const entry of candidateSkillDirs(cwd)) {
22598
23338
  try {
22599
- const relative9 = path29.join(name, "SKILL.md");
23339
+ const relative9 = path30.join(name, "SKILL.md");
22600
23340
  const contained = resolveRealContainedPath(entry.root, relative9);
22601
- if (!fs33.existsSync(contained)) continue;
22602
- if (fs33.lstatSync(contained).isSymbolicLink()) continue;
23341
+ if (!fs34.existsSync(contained)) continue;
23342
+ if (fs34.lstatSync(contained).isSymbolicLink()) continue;
22603
23343
  const filePath = resolveRealContainedPath(entry.root, relative9);
22604
- const stat2 = fs33.statSync(filePath);
22605
- const rawContent = fs33.readFileSync(filePath, "utf-8");
23344
+ const stat2 = fs34.statSync(filePath);
23345
+ const rawContent = fs34.readFileSync(filePath, "utf-8");
22606
23346
  return rememberSkill(cacheKey2, {
22607
23347
  path: filePath,
22608
23348
  source: entry.source,
@@ -22671,7 +23411,7 @@ Skill '${safeName}' was selected but no SKILL.md file was found. Continue with t
22671
23411
  if (!pushSection(missing)) omittedCount += 1;
22672
23412
  continue;
22673
23413
  }
22674
- skillPaths.push(path29.dirname(loaded.path));
23414
+ skillPaths.push(path30.dirname(loaded.path));
22675
23415
  const description = frontmatterDescription(loaded.content);
22676
23416
  const source = loaded.source === "project" ? `project:skills/${safeName}` : `package:skills/${safeName}`;
22677
23417
  const weighted = weightedSkills?.find((w) => w.skillId === name);
@@ -22686,7 +23426,7 @@ Skill '${safeName}' was selected but no SKILL.md file was found. Continue with t
22686
23426
  // spec "small instruction + large local reference" pattern, e.g.
22687
23427
  // effective-html's `references/html-effectiveness/`) leave the agent
22688
23428
  // guessing the skill dir. No behavior change for corpus-less skills.
22689
- `Path: ${path29.dirname(loaded.path)}`
23429
+ `Path: ${path30.dirname(loaded.path)}`
22690
23430
  ].filter(Boolean).join("\n");
22691
23431
  const rawContent = loaded.compacted;
22692
23432
  const wrappedContent = `<!-- skill: ${safeName} -->
@@ -22730,7 +23470,7 @@ var init_skill_instructions = __esm({
22730
23470
  init_safe_paths();
22731
23471
  init_skill_effectiveness();
22732
23472
  init_peer_dep();
22733
- PACKAGE_SKILLS_DIR = path29.resolve(path29.dirname(fileURLToPath4(import.meta.url)), "..", "..", "skills");
23473
+ PACKAGE_SKILLS_DIR = path30.resolve(path30.dirname(fileURLToPath4(import.meta.url)), "..", "..", "skills");
22734
23474
  MAX_SKILL_CHARS = 1500;
22735
23475
  MAX_TOTAL_CHARS = 6e3;
22736
23476
  MAX_SKILL_NAME_CHARS = 80;
@@ -23630,15 +24370,15 @@ var init_anchor = __esm({
23630
24370
  });
23631
24371
 
23632
24372
  // src/runtime/agent-observability.ts
23633
- import * as fs34 from "node:fs";
24373
+ import * as fs35 from "node:fs";
23634
24374
  function readTextTail(filePath, maxBytes = 64e3) {
23635
- if (!fs34.existsSync(filePath)) return { path: filePath, text: "", bytes: 0, truncated: false };
23636
- const stat2 = fs34.statSync(filePath);
24375
+ if (!fs35.existsSync(filePath)) return { path: filePath, text: "", bytes: 0, truncated: false };
24376
+ const stat2 = fs35.statSync(filePath);
23637
24377
  const bytesToRead = Math.min(stat2.size, Math.max(0, maxBytes));
23638
- const fd = fs34.openSync(filePath, "r");
24378
+ const fd = fs35.openSync(filePath, "r");
23639
24379
  try {
23640
24380
  const buffer = Buffer.alloc(bytesToRead);
23641
- fs34.readSync(fd, buffer, 0, bytesToRead, stat2.size - bytesToRead);
24381
+ fs35.readSync(fd, buffer, 0, bytesToRead, stat2.size - bytesToRead);
23642
24382
  return {
23643
24383
  path: filePath,
23644
24384
  text: buffer.toString("utf-8"),
@@ -23646,7 +24386,7 @@ function readTextTail(filePath, maxBytes = 64e3) {
23646
24386
  truncated: stat2.size > bytesToRead
23647
24387
  };
23648
24388
  } finally {
23649
- fs34.closeSync(fd);
24389
+ fs35.closeSync(fd);
23650
24390
  }
23651
24391
  }
23652
24392
  function compactDuration(ms) {
@@ -23685,8 +24425,8 @@ function outputWarning(manifest, agent) {
23685
24425
  if (agent.status !== "completed") return "";
23686
24426
  try {
23687
24427
  const outputPath = agentOutputPath(manifest, agent.taskId);
23688
- if (!fs34.existsSync(outputPath)) return " no-output";
23689
- return fs34.statSync(outputPath).size === 0 ? " no-output" : "";
24428
+ if (!fs35.existsSync(outputPath)) return " no-output";
24429
+ return fs35.statSync(outputPath).size === 0 ? " no-output" : "";
23690
24430
  } catch {
23691
24431
  return " no-output";
23692
24432
  }
@@ -23738,9 +24478,9 @@ var init_agent_observability = __esm({
23738
24478
  });
23739
24479
 
23740
24480
  // src/skills/validate.ts
23741
- import * as fs35 from "node:fs";
24481
+ import * as fs36 from "node:fs";
23742
24482
  import { createRequire as createRequire3 } from "node:module";
23743
- import * as path30 from "node:path";
24483
+ import * as path31 from "node:path";
23744
24484
  function getYaml() {
23745
24485
  if (!yamlModule) {
23746
24486
  const require4 = createRequire3(import.meta.url);
@@ -23776,11 +24516,11 @@ function warn(path89, field, reason) {
23776
24516
  }
23777
24517
  function validateSkillFrontmatter(skillDir) {
23778
24518
  const errors2 = [];
23779
- const skillMdPath = path30.join(skillDir, "SKILL.md");
23780
- const derivedName = path30.basename(skillDir);
24519
+ const skillMdPath = path31.join(skillDir, "SKILL.md");
24520
+ const derivedName = path31.basename(skillDir);
23781
24521
  let content;
23782
24522
  try {
23783
- content = fs35.readFileSync(skillMdPath, "utf-8");
24523
+ content = fs36.readFileSync(skillMdPath, "utf-8");
23784
24524
  } catch (e) {
23785
24525
  errors2.push(hard(skillDir, "SKILL.md", `Cannot read SKILL.md: ${e.message}`));
23786
24526
  return { ok: false, errors: errors2 };
@@ -23911,25 +24651,25 @@ var init_validate = __esm({
23911
24651
  });
23912
24652
 
23913
24653
  // src/skills/discover-skills.ts
23914
- import * as fs36 from "node:fs";
23915
- import * as os10 from "node:os";
23916
- import * as path31 from "node:path";
24654
+ import * as fs37 from "node:fs";
24655
+ import * as os11 from "node:os";
24656
+ import * as path32 from "node:path";
23917
24657
  import { fileURLToPath as fileURLToPath5 } from "node:url";
23918
24658
  function listSkillDirs(cwd) {
23919
24659
  return [
23920
24660
  { root: PACKAGE_SKILLS_DIR2, source: "package" },
23921
- { root: path31.resolve(cwd, ".pi", "skills"), source: "project-pi" },
24661
+ { root: path32.resolve(cwd, ".pi", "skills"), source: "project-pi" },
23922
24662
  {
23923
- root: path31.resolve(cwd, ".agents", "skills"),
24663
+ root: path32.resolve(cwd, ".agents", "skills"),
23924
24664
  source: "project-agents"
23925
24665
  },
23926
- { root: path31.resolve(cwd, "skills"), source: "project" },
23927
- { root: path31.join(getAgentDir(), "skills"), source: "user-pi" },
24666
+ { root: path32.resolve(cwd, "skills"), source: "project" },
24667
+ { root: path32.join(getAgentDir(), "skills"), source: "user-pi" },
23928
24668
  {
23929
- root: path31.join(os10.homedir(), ".agents", "skills"),
24669
+ root: path32.join(os11.homedir(), ".agents", "skills"),
23930
24670
  source: "user-agents"
23931
24671
  },
23932
- { root: path31.join(os10.homedir(), ".pi", "skills"), source: "user-pi" }
24672
+ { root: path32.join(os11.homedir(), ".pi", "skills"), source: "user-pi" }
23933
24673
  ];
23934
24674
  }
23935
24675
  function readDescription(content) {
@@ -23954,29 +24694,29 @@ function discoverSkills(cwd) {
23954
24694
  const results = [];
23955
24695
  const diagnostics = [];
23956
24696
  for (const dir of listSkillDirs(cwd)) {
23957
- if (!fs36.existsSync(dir.root)) continue;
24697
+ if (!fs37.existsSync(dir.root)) continue;
23958
24698
  try {
23959
- for (const entry of fs36.readdirSync(dir.root, {
24699
+ for (const entry of fs37.readdirSync(dir.root, {
23960
24700
  withFileTypes: true
23961
24701
  })) {
23962
24702
  if (!entry.isDirectory()) continue;
23963
24703
  if (!isSafePathId(entry.name)) continue;
23964
- const skillDirPath = path31.join(dir.root, entry.name);
24704
+ const skillDirPath = path32.join(dir.root, entry.name);
23965
24705
  try {
23966
- if (fs36.lstatSync(skillDirPath).isSymbolicLink()) continue;
24706
+ if (fs37.lstatSync(skillDirPath).isSymbolicLink()) continue;
23967
24707
  } catch {
23968
24708
  continue;
23969
24709
  }
23970
- const skillMdRelative = path31.join(entry.name, "SKILL.md");
24710
+ const skillMdRelative = path32.join(entry.name, "SKILL.md");
23971
24711
  let skillMdPath;
23972
24712
  try {
23973
24713
  skillMdPath = resolveContainedPath(dir.root, skillMdRelative);
23974
24714
  } catch {
23975
24715
  continue;
23976
24716
  }
23977
- if (!fs36.existsSync(skillMdPath)) continue;
24717
+ if (!fs37.existsSync(skillMdPath)) continue;
23978
24718
  try {
23979
- if (fs36.lstatSync(skillMdPath).isSymbolicLink()) continue;
24719
+ if (fs37.lstatSync(skillMdPath).isSymbolicLink()) continue;
23980
24720
  } catch {
23981
24721
  continue;
23982
24722
  }
@@ -23988,12 +24728,12 @@ function discoverSkills(cwd) {
23988
24728
  skillMdPath = readPath;
23989
24729
  } catch {
23990
24730
  }
23991
- const content = fs36.readFileSync(readPath, "utf-8");
24731
+ const content = fs37.readFileSync(readPath, "utf-8");
23992
24732
  const { description: desc, parseError } = readDescription(content);
23993
24733
  description = desc;
23994
24734
  if (parseError) {
23995
24735
  diagnostics.push({
23996
- path: path31.dirname(skillMdPath),
24736
+ path: path32.dirname(skillMdPath),
23997
24737
  field: "frontmatter",
23998
24738
  reason: parseError,
23999
24739
  severity: "error"
@@ -24015,7 +24755,7 @@ function discoverSkills(cwd) {
24015
24755
  }
24016
24756
  const filtered = [];
24017
24757
  for (const skill of results) {
24018
- const validation = validateSkillFrontmatter(path31.dirname(skill.path));
24758
+ const validation = validateSkillFrontmatter(path32.dirname(skill.path));
24019
24759
  if (validation.ok) {
24020
24760
  filtered.push(skill);
24021
24761
  } else {
@@ -24034,7 +24774,7 @@ var init_discover_skills = __esm({
24034
24774
  init_internal_error();
24035
24775
  init_safe_paths();
24036
24776
  init_validate();
24037
- PACKAGE_SKILLS_DIR2 = path31.resolve(path31.dirname(fileURLToPath5(import.meta.url)), "..", "..", "skills");
24777
+ PACKAGE_SKILLS_DIR2 = path32.resolve(path32.dirname(fileURLToPath5(import.meta.url)), "..", "..", "skills");
24038
24778
  CACHE_TTL_MS = 3e4;
24039
24779
  cache2 = null;
24040
24780
  lastDiagnostics = [];
@@ -24141,15 +24881,15 @@ var init_capability_inventory = __esm({
24141
24881
  });
24142
24882
 
24143
24883
  // src/runtime/foreground-control.ts
24144
- import * as fs37 from "node:fs";
24145
- import * as path32 from "node:path";
24884
+ import * as fs38 from "node:fs";
24885
+ import * as path33 from "node:path";
24146
24886
  function foregroundControlPath(manifest) {
24147
- return path32.join(manifest.stateRoot, "foreground-control.json");
24887
+ return path33.join(manifest.stateRoot, "foreground-control.json");
24148
24888
  }
24149
24889
  function readLastRequest(controlPath) {
24150
- if (!fs37.existsSync(controlPath)) return void 0;
24890
+ if (!fs38.existsSync(controlPath)) return void 0;
24151
24891
  try {
24152
- const parsed = JSON.parse(fs37.readFileSync(controlPath, "utf-8"));
24892
+ const parsed = JSON.parse(fs38.readFileSync(controlPath, "utf-8"));
24153
24893
  return parsed.requests?.at(-1);
24154
24894
  } catch {
24155
24895
  return void 0;
@@ -24173,7 +24913,7 @@ function readForegroundControlStatus(manifest, tasks) {
24173
24913
  function writeForegroundInterruptRequest(manifest, reason = "User requested foreground interrupt.") {
24174
24914
  const controlPath = foregroundControlPath(manifest);
24175
24915
  const lockDir = `${controlPath}.lock`;
24176
- const pidFile = path32.join(lockDir, "pid");
24916
+ const pidFile = path33.join(lockDir, "pid");
24177
24917
  let requests = [];
24178
24918
  const acquireLock = () => {
24179
24919
  const timeout = 5e3;
@@ -24181,7 +24921,7 @@ function writeForegroundInterruptRequest(manifest, reason = "User requested fore
24181
24921
  const start = Date.now();
24182
24922
  while (true) {
24183
24923
  try {
24184
- fs37.mkdirSync(lockDir, { recursive: true });
24924
+ fs38.mkdirSync(lockDir, { recursive: true });
24185
24925
  try {
24186
24926
  atomicWriteFile(pidFile, String(process.pid));
24187
24927
  } catch {
@@ -24190,7 +24930,7 @@ function writeForegroundInterruptRequest(manifest, reason = "User requested fore
24190
24930
  } catch {
24191
24931
  if (Date.now() - start > timeout) {
24192
24932
  try {
24193
- const raw = fs37.readFileSync(pidFile, "utf-8").trim();
24933
+ const raw = fs38.readFileSync(pidFile, "utf-8").trim();
24194
24934
  const ownerPid = Number.parseInt(raw, 10);
24195
24935
  if (!Number.isNaN(ownerPid) && ownerPid !== process.pid) {
24196
24936
  let alive = false;
@@ -24200,7 +24940,7 @@ function writeForegroundInterruptRequest(manifest, reason = "User requested fore
24200
24940
  } catch {
24201
24941
  }
24202
24942
  if (!alive) {
24203
- fs37.rmSync(lockDir, {
24943
+ fs38.rmSync(lockDir, {
24204
24944
  recursive: true,
24205
24945
  force: true
24206
24946
  });
@@ -24217,7 +24957,7 @@ function writeForegroundInterruptRequest(manifest, reason = "User requested fore
24217
24957
  throw err2;
24218
24958
  }
24219
24959
  try {
24220
- const raw = fs37.readFileSync(pidFile, "utf-8").trim();
24960
+ const raw = fs38.readFileSync(pidFile, "utf-8").trim();
24221
24961
  const ownerPid = Number.parseInt(raw, 10);
24222
24962
  if (!Number.isNaN(ownerPid) && ownerPid !== process.pid) {
24223
24963
  let alive = false;
@@ -24227,9 +24967,9 @@ function writeForegroundInterruptRequest(manifest, reason = "User requested fore
24227
24967
  } catch {
24228
24968
  }
24229
24969
  if (!alive) {
24230
- const stat2 = fs37.statSync(lockDir);
24970
+ const stat2 = fs38.statSync(lockDir);
24231
24971
  if (Date.now() - stat2.mtimeMs > staleMs) {
24232
- fs37.rmSync(lockDir, {
24972
+ fs38.rmSync(lockDir, {
24233
24973
  recursive: true,
24234
24974
  force: true
24235
24975
  });
@@ -24245,15 +24985,15 @@ function writeForegroundInterruptRequest(manifest, reason = "User requested fore
24245
24985
  };
24246
24986
  const releaseLock2 = () => {
24247
24987
  try {
24248
- fs37.rmSync(lockDir, { recursive: true, force: true });
24988
+ fs38.rmSync(lockDir, { recursive: true, force: true });
24249
24989
  } catch {
24250
24990
  }
24251
24991
  };
24252
24992
  acquireLock();
24253
24993
  try {
24254
- if (fs37.existsSync(controlPath)) {
24994
+ if (fs38.existsSync(controlPath)) {
24255
24995
  try {
24256
- const parsed = JSON.parse(fs37.readFileSync(controlPath, "utf-8"));
24996
+ const parsed = JSON.parse(fs38.readFileSync(controlPath, "utf-8"));
24257
24997
  requests = Array.isArray(parsed.requests) ? parsed.requests : [];
24258
24998
  } catch {
24259
24999
  requests = [];
@@ -24266,7 +25006,7 @@ function writeForegroundInterruptRequest(manifest, reason = "User requested fore
24266
25006
  reason,
24267
25007
  acknowledged: false
24268
25008
  };
24269
- fs37.mkdirSync(path32.dirname(controlPath), { recursive: true });
25009
+ fs38.mkdirSync(path33.dirname(controlPath), { recursive: true });
24270
25010
  atomicWriteFile(controlPath, `${JSON.stringify({ requests: [...requests, request] }, null, 2)}
24271
25011
  `);
24272
25012
  appendEvent(manifest.eventsPath, {
@@ -24309,7 +25049,7 @@ var init_worker_heartbeat = __esm({
24309
25049
  });
24310
25050
 
24311
25051
  // src/runtime/live-session/live-agent-control.ts
24312
- import * as fs38 from "node:fs";
25052
+ import * as fs39 from "node:fs";
24313
25053
  function liveAgentControlFile(manifest, taskId) {
24314
25054
  return agentStateFile(manifest, taskId, "live-control.jsonl");
24315
25055
  }
@@ -24327,7 +25067,7 @@ function appendLiveAgentControlRequest(manifest, input) {
24327
25067
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
24328
25068
  };
24329
25069
  const filePath = liveAgentControlFile(manifest, input.taskId);
24330
- fs38.appendFileSync(filePath, `${JSON.stringify(request)}
25070
+ fs39.appendFileSync(filePath, `${JSON.stringify(request)}
24331
25071
  `, "utf-8");
24332
25072
  return request;
24333
25073
  }
@@ -24338,8 +25078,8 @@ function readLiveAgentControlRequests(manifest, taskId, cursor = { offset: 0 })
24338
25078
  } catch {
24339
25079
  return { requests: [], cursor };
24340
25080
  }
24341
- if (!fs38.existsSync(filePath)) return { requests: [], cursor };
24342
- const text = fs38.readFileSync(filePath, "utf-8");
25081
+ if (!fs39.existsSync(filePath)) return { requests: [], cursor };
25082
+ const text = fs39.readFileSync(filePath, "utf-8");
24343
25083
  const lines = text.split(/\r?\n/).filter(Boolean);
24344
25084
  const requests = lines.slice(cursor.offset).flatMap((line4) => {
24345
25085
  try {
@@ -25134,379 +25874,6 @@ var init_mcp_proxy = __esm({
25134
25874
  }
25135
25875
  });
25136
25876
 
25137
- // src/runtime/model/model-resolver.ts
25138
- function fuzzyResolveModelId(input, models) {
25139
- const query = input.toLowerCase();
25140
- let bestMatch;
25141
- let bestScore = 0;
25142
- for (const m of models) {
25143
- const id = m.id.toLowerCase();
25144
- const name = (m.name ?? "").toLowerCase();
25145
- const full = `${m.provider}/${m.id}`.toLowerCase();
25146
- let score = 0;
25147
- if (id === query || full === query) {
25148
- score = 100;
25149
- } else if (id.includes(query) || full.includes(query)) {
25150
- score = 60 + query.length / id.length * 30;
25151
- } else if (name.includes(query)) {
25152
- score = 40 + query.length / (name.length || 1) * 20;
25153
- } else if (query.split(/[\s\-/]+/).every((part) => id.includes(part) || name.includes(part) || m.provider.toLowerCase().includes(part))) {
25154
- score = 20;
25155
- }
25156
- if (score > bestScore) {
25157
- bestScore = score;
25158
- bestMatch = m;
25159
- }
25160
- }
25161
- return bestMatch && bestScore >= 20 ? `${bestMatch.provider}/${bestMatch.id}` : void 0;
25162
- }
25163
- var init_model_resolver = __esm({
25164
- "src/runtime/model/model-resolver.ts"() {
25165
- "use strict";
25166
- }
25167
- });
25168
-
25169
- // src/runtime/model/model-scope.ts
25170
- function patternToRegExp(pattern) {
25171
- const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
25172
- return new RegExp(`${escaped.replace(/\*/g, ".*")}`, "i");
25173
- }
25174
- function matchesModelPattern(modelId, pattern) {
25175
- if (!modelId || !pattern) return false;
25176
- const id = modelId.trim();
25177
- const pat = pattern.trim();
25178
- if (!id || !pat) return false;
25179
- if (id.toLowerCase() === pat.toLowerCase()) return true;
25180
- if (pat.includes("*")) {
25181
- try {
25182
- return patternToRegExp(pat).test(id);
25183
- } catch {
25184
- return false;
25185
- }
25186
- }
25187
- return id.toLowerCase().includes(pat.toLowerCase());
25188
- }
25189
- function checkModelScope(modelId, patterns, source) {
25190
- if (!modelId) {
25191
- return {
25192
- inScope: true,
25193
- source,
25194
- model: "",
25195
- reason: "no model specified"
25196
- };
25197
- }
25198
- if (!patterns || patterns.length === 0) {
25199
- return { inScope: true, source, model: modelId };
25200
- }
25201
- for (const pattern of patterns) {
25202
- if (matchesModelPattern(modelId, pattern)) {
25203
- return {
25204
- inScope: true,
25205
- source,
25206
- model: modelId,
25207
- matchedPattern: pattern
25208
- };
25209
- }
25210
- }
25211
- return {
25212
- inScope: false,
25213
- source,
25214
- model: modelId,
25215
- reason: `model "${modelId}" is not in enabledModels allowlist (${patterns.join(", ")})`
25216
- };
25217
- }
25218
- async function readEnabledModelsPatterns(cwd, agentDir) {
25219
- try {
25220
- const mod = await import("@earendil-works/pi-coding-agent").catch(() => null);
25221
- if (!mod) return [];
25222
- const SettingsManagerCtor = mod.SettingsManager;
25223
- if (!SettingsManagerCtor?.create) return [];
25224
- const sm = SettingsManagerCtor.create(cwd, agentDir);
25225
- const patterns = sm.getEnabledModels?.();
25226
- return Array.isArray(patterns) ? patterns : [];
25227
- } catch {
25228
- return [];
25229
- }
25230
- }
25231
- var init_model_scope = __esm({
25232
- "src/runtime/model/model-scope.ts"() {
25233
- "use strict";
25234
- }
25235
- });
25236
-
25237
- // src/runtime/model/model-fallback.ts
25238
- import * as fs39 from "node:fs";
25239
- import * as os11 from "node:os";
25240
- import * as path33 from "node:path";
25241
- function modelInfoFromUnknown(value) {
25242
- if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
25243
- const record = value;
25244
- if (typeof record.provider !== "string" || typeof record.id !== "string") return void 0;
25245
- return {
25246
- provider: record.provider,
25247
- id: record.id,
25248
- fullId: `${record.provider}/${record.id}`
25249
- };
25250
- }
25251
- function availableModelInfosFromRegistry(registry2) {
25252
- if (!registry2 || typeof registry2 !== "object" || Array.isArray(registry2)) return void 0;
25253
- const candidate = registry2;
25254
- const raw = typeof candidate.getAvailable === "function" ? candidate.getAvailable() : typeof candidate.getAll === "function" ? candidate.getAll() : void 0;
25255
- if (!Array.isArray(raw)) return void 0;
25256
- return raw.map(modelInfoFromUnknown).filter((entry) => entry !== void 0);
25257
- }
25258
- function modelStringFromUnknown(model) {
25259
- return modelInfoFromUnknown(model)?.fullId;
25260
- }
25261
- function uniqueModelInfos(models) {
25262
- const seen = /* @__PURE__ */ new Set();
25263
- return models.filter((model) => {
25264
- if (seen.has(model.fullId)) return false;
25265
- seen.add(model.fullId);
25266
- return true;
25267
- });
25268
- }
25269
- function readJsonObject(filePath) {
25270
- try {
25271
- const parsed = JSON.parse(fs39.readFileSync(filePath, "utf-8"));
25272
- return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : void 0;
25273
- } catch {
25274
- return void 0;
25275
- }
25276
- }
25277
- function piAgentDir() {
25278
- const envDir = process.env.PI_CODING_AGENT_DIR?.trim();
25279
- if (envDir) {
25280
- if (envDir === "~") return os11.homedir();
25281
- if (envDir.startsWith("~/")) return path33.join(os11.homedir(), envDir.slice(2));
25282
- return envDir;
25283
- }
25284
- return path33.join(os11.homedir(), ".pi", "agent");
25285
- }
25286
- function settingsModelInfo(settings) {
25287
- if (typeof settings?.defaultProvider !== "string" || typeof settings.defaultModel !== "string") return void 0;
25288
- return {
25289
- provider: settings.defaultProvider,
25290
- id: settings.defaultModel,
25291
- fullId: `${settings.defaultProvider}/${settings.defaultModel}`
25292
- };
25293
- }
25294
- function modelsJsonInfos(modelsJson) {
25295
- if (!modelsJson?.providers || typeof modelsJson.providers !== "object" || Array.isArray(modelsJson.providers)) return [];
25296
- const infos = [];
25297
- for (const [provider, rawConfig] of Object.entries(modelsJson.providers)) {
25298
- if (!rawConfig || typeof rawConfig !== "object" || Array.isArray(rawConfig)) continue;
25299
- const config = rawConfig;
25300
- if (Array.isArray(config.models)) {
25301
- for (const rawModel of config.models) {
25302
- if (!rawModel || typeof rawModel !== "object" || Array.isArray(rawModel)) continue;
25303
- const id = rawModel.id;
25304
- if (typeof id === "string") infos.push({ provider, id, fullId: `${provider}/${id}` });
25305
- }
25306
- }
25307
- if (config.modelOverrides && typeof config.modelOverrides === "object" && !Array.isArray(config.modelOverrides)) {
25308
- for (const id of Object.keys(config.modelOverrides)) infos.push({ provider, id, fullId: `${provider}/${id}` });
25309
- }
25310
- }
25311
- return infos;
25312
- }
25313
- function configuredModelInfosFromPiConfig(cwd) {
25314
- const agentDir = piAgentDir();
25315
- const globalSettings = readJsonObject(path33.join(agentDir, "settings.json"));
25316
- const projectSettings = cwd ? readJsonObject(path33.join(cwd, ".pi", "settings.json")) : void 0;
25317
- const effectiveSettings = {
25318
- ...globalSettings ?? {},
25319
- ...projectSettings ?? {}
25320
- };
25321
- const defaultModel = settingsModelInfo(effectiveSettings);
25322
- return uniqueModelInfos([
25323
- ...defaultModel ? [defaultModel] : [],
25324
- ...modelsJsonInfos(readJsonObject(path33.join(agentDir, "models.json")))
25325
- ]);
25326
- }
25327
- function splitThinkingSuffix(model) {
25328
- const colonIdx = model.lastIndexOf(":");
25329
- if (colonIdx === -1) return { baseModel: model, thinkingSuffix: "" };
25330
- return {
25331
- baseModel: model.substring(0, colonIdx),
25332
- thinkingSuffix: model.substring(colonIdx)
25333
- };
25334
- }
25335
- function resolveModelCandidate(model, availableModels, preferredProvider) {
25336
- if (!model) return void 0;
25337
- if (model.includes("/")) return model;
25338
- if (!availableModels || availableModels.length === 0) return model;
25339
- const { baseModel, thinkingSuffix } = splitThinkingSuffix(model);
25340
- const matches = availableModels.filter((entry) => entry.id === baseModel);
25341
- if (preferredProvider) {
25342
- const preferredMatch = matches.find((entry) => entry.provider === preferredProvider);
25343
- if (preferredMatch) return `${preferredMatch.fullId}${thinkingSuffix}`;
25344
- }
25345
- if (matches.length !== 1) {
25346
- const fuzzy = fuzzyResolveModelId(baseModel, availableModels);
25347
- if (fuzzy) return `${fuzzy}${thinkingSuffix}`;
25348
- return model;
25349
- }
25350
- return `${matches[0].fullId}${thinkingSuffix}`;
25351
- }
25352
- function isRetryableModelFailure(error) {
25353
- if (!error) return false;
25354
- if (NON_RETRYABLE_MODEL_FAILURE_PATTERNS.some((pattern) => pattern.test(error))) return false;
25355
- return RETRYABLE_MODEL_FAILURE_PATTERNS.some((pattern) => pattern.test(error));
25356
- }
25357
- function formatModelAttemptNote(attempt, nextModel) {
25358
- const failure = attempt.error?.trim() || `exit ${attempt.exitCode ?? 1}`;
25359
- return nextModel ? `[fallback] ${attempt.model} failed: ${failure}. Retrying with ${nextModel}.` : `[fallback] ${attempt.model} failed: ${failure}.`;
25360
- }
25361
- function buildModelCandidates(primaryModel, fallbackModels, availableModels, preferredProvider) {
25362
- const seen = /* @__PURE__ */ new Set();
25363
- const candidates = [];
25364
- for (const raw of [primaryModel, ...fallbackModels ?? []]) {
25365
- if (!raw) continue;
25366
- const normalized = resolveModelCandidate(raw.trim(), availableModels, preferredProvider);
25367
- if (!normalized || seen.has(normalized)) continue;
25368
- seen.add(normalized);
25369
- candidates.push(normalized);
25370
- }
25371
- return candidates;
25372
- }
25373
- function isAvailableModel(model, availableModels) {
25374
- if (!availableModels || availableModels.length === 0) return true;
25375
- const { baseModel } = splitThinkingSuffix(model);
25376
- if (baseModel.includes("/")) return availableModels.some((entry) => entry.fullId === baseModel);
25377
- if (availableModels.some((entry) => entry.id === baseModel)) return true;
25378
- const fuzzy = fuzzyResolveModelId(baseModel, availableModels);
25379
- return fuzzy !== void 0;
25380
- }
25381
- function buildConfiguredModelRouting(input) {
25382
- const registryModels = availableModelInfosFromRegistry(input.modelRegistry);
25383
- const configModels = configuredModelInfosFromPiConfig(input.cwd);
25384
- const availableModels = registryModels && registryModels.length > 0 ? registryModels : configModels.length > 0 ? configModels : registryModels;
25385
- const parentModel = modelStringFromUnknown(input.parentModel);
25386
- const preferredProvider = parentModel?.split("/")[0] ?? availableModels?.[0]?.provider;
25387
- const effectiveAgentModel = input.agentModel?.trim() ? input.agentModel : parentModel;
25388
- const requested = [input.overrideModel, input.stepModel, input.teamRoleModel, effectiveAgentModel].find(
25389
- (model) => Boolean(model?.trim())
25390
- );
25391
- if (availableModels && availableModels.length === 0)
25392
- return {
25393
- requested,
25394
- candidates: [],
25395
- reason: "no configured Pi models available"
25396
- };
25397
- const rawModels = availableModels ? [
25398
- input.overrideModel,
25399
- input.stepModel,
25400
- input.teamRoleModel,
25401
- effectiveAgentModel,
25402
- ...input.fallbackModels ?? [],
25403
- ...availableModels.map((model) => model.fullId)
25404
- ] : [input.overrideModel, input.stepModel, input.teamRoleModel, effectiveAgentModel, ...input.fallbackModels ?? [], parentModel];
25405
- const parentModelRaw = effectiveAgentModel?.trim() || void 0;
25406
- const configuredModels = rawModels.filter((model) => Boolean(model?.trim())).filter((model, idx) => {
25407
- if (parentModelRaw && idx === 0 && model.trim() === parentModelRaw) return true;
25408
- return isAvailableModel(model.trim(), availableModels);
25409
- });
25410
- const candidates = buildModelCandidates(configuredModels[0], configuredModels.slice(1), availableModels, preferredProvider);
25411
- const reason = requested && candidates[0] && resolveModelCandidate(requested, availableModels, preferredProvider) !== candidates[0] ? "requested model unavailable; selected configured Pi fallback" : candidates.length > 1 ? "configured Pi fallback chain" : void 0;
25412
- let scopeVerdict;
25413
- if (input.scopeModelsPatterns && input.scopeModelsPatterns.length > 0) {
25414
- const resolved = candidates[0] ?? requested;
25415
- const source = input.overrideModel ? "caller" : input.agentModel ? "frontmatter" : "resolved";
25416
- scopeVerdict = checkModelScope(resolved, input.scopeModelsPatterns, source);
25417
- if (!scopeVerdict.inScope && source === "caller" && !input.isFrontmatterOverride) {
25418
- throw errors.modelOutOfScope(resolved ?? "", input.scopeModelsPatterns);
25419
- }
25420
- }
25421
- return { requested, candidates, reason, scopeVerdict };
25422
- }
25423
- var RETRYABLE_MODEL_FAILURE_PATTERNS, NON_RETRYABLE_MODEL_FAILURE_PATTERNS;
25424
- var init_model_fallback = __esm({
25425
- "src/runtime/model/model-fallback.ts"() {
25426
- "use strict";
25427
- init_errors3();
25428
- init_model_resolver();
25429
- init_model_scope();
25430
- RETRYABLE_MODEL_FAILURE_PATTERNS = [
25431
- /rate.?limit/i,
25432
- /too many requests/i,
25433
- /\b429\b/,
25434
- /rate_limit_error/i,
25435
- /quota/i,
25436
- /provider.*unavailable/i,
25437
- /model.*unavailable/i,
25438
- /model.*disabled/i,
25439
- /model.*not found/i,
25440
- /unknown model/i,
25441
- /overloaded/i,
25442
- /service unavailable/i,
25443
- /temporar(?:ily)? unavailable/i,
25444
- /connection refused/i,
25445
- /fetch failed/i,
25446
- /network error/i,
25447
- /socket hang up/i,
25448
- /upstream/i,
25449
- /timed? out/i,
25450
- /timeout/i,
25451
- /\b502\b/,
25452
- /\b503\b/,
25453
- /\b504\b/,
25454
- //
25455
- // Provider-side 5xx / generic api_error. The pi-core retry layer already
25456
- // retries these (agent-session.ts matches `500|server error|internal error`),
25457
- // but the pi-crew MODEL FALLBACK layer must ALSO treat them as retryable so
25458
- // that when the provider is hard-down across all 3 provider retries, we fail
25459
- // over to the next configured model instead of giving up. Reported case
25460
- // (2026-06-17): `500 {"type":"error","error":{"type":"api_error",
25461
- // "message":"unknown error, 999 (1000)"}}` — a transient provider outage that
25462
- // should trigger the fallback chain, not abort.
25463
- //
25464
- // `api_error` is the OpenAI-compatible generic error type (vs rate_limit_error
25465
- // / overloaded_error / etc.) and almost always means a transient server fault.
25466
- //
25467
- // `unknown error` is the body of the generic message; `internal`/`server`
25468
- // catch the common phrasings. `\b500\b`/`\b501\b` catch the HTTP status in
25469
- // the rendered error string.
25470
- /\b500\b/,
25471
- /\b501\b/,
25472
- /api_error/i,
25473
- /unknown error/i,
25474
- /internal(?:_server)?[ _]error/i,
25475
- /server error/i,
25476
- /bad gateway/i,
25477
- //
25478
- // Broader retryable patterns (added 2026-06-25, FIX 2):
25479
- // - `/provider[_ ]?error/i`: OpenAI-compatible "Provider error" generic fault.
25480
- // - `/context[_ ]?length[_ ]?exceeded/i`: "context_length_exceeded" from
25481
- // OpenAI/Anthropic — when the configured model is the bottleneck, a
25482
- // different model in the fallback chain may have a larger window.
25483
- // - `/safety/i`: Anthropic safety blocks — typically retryable on a
25484
- // different model in the fallback chain.
25485
- // - `/is[_ ]?overloaded/i`: alias to the existing `/overloaded/i` pattern
25486
- // to catch phrasings like "upstream is overloaded".
25487
- // - `/\b408\b/`: HTTP 408 Request Timeout — transient, provider-side.
25488
- //
25489
- // Intentionally NOT added: `/bad_request/` — can mean bad input (e.g.
25490
- // invalid schema), which is non-retryable.
25491
- /provider[_ ]?error/i,
25492
- /context[_ ]?length[_ ]?exceeded/i,
25493
- /safety/i,
25494
- /is[_ ]?overloaded/i,
25495
- /\b408\b/
25496
- ];
25497
- NON_RETRYABLE_MODEL_FAILURE_PATTERNS = [
25498
- /auth(?:entication)?/i,
25499
- /unauthori[sz]ed/i,
25500
- /forbidden/i,
25501
- /api key/i,
25502
- /token expired/i,
25503
- /invalid key/i,
25504
- /billing/i,
25505
- /credit/i
25506
- ];
25507
- }
25508
- });
25509
-
25510
25877
  // src/runtime/output/sidechain-output.ts
25511
25878
  import * as fs40 from "node:fs";
25512
25879
  import * as path34 from "node:path";
@@ -32556,6 +32923,20 @@ async function resolveScopeModelsPatterns(cwd, agentDir) {
32556
32923
  if (!scopeModels) return [];
32557
32924
  return readEnabledModelsPatterns(cwd, agentDir);
32558
32925
  }
32926
+ function resolveLiveModelFallbackPolicy(cwd) {
32927
+ try {
32928
+ return resolveModelFallbackPolicy(loadConfig(cwd).config.runtime?.modelFallback);
32929
+ } catch {
32930
+ return void 0;
32931
+ }
32932
+ }
32933
+ function resolveLiveDefaultSubagentModel(cwd) {
32934
+ try {
32935
+ return resolveDefaultSubagentModel(loadConfig(cwd).config.runtime?.modelFallback);
32936
+ } catch {
32937
+ return void 0;
32938
+ }
32939
+ }
32559
32940
  function modelFromRegistry(modelRegistry, modelId) {
32560
32941
  if (!modelId?.includes("/")) return void 0;
32561
32942
  const registry2 = asRecord6(modelRegistry);
@@ -32570,7 +32951,7 @@ function modelFromRegistry(modelRegistry, modelId) {
32570
32951
  }
32571
32952
  }
32572
32953
  function resolveParentModelFromRegistry(modelRegistry, rawParentModel) {
32573
- const raw = typeof rawParentModel === "string" ? rawParentModel.trim() : void 0;
32954
+ const raw = modelRefToString(rawParentModel);
32574
32955
  if (raw) {
32575
32956
  const candidate = raw.includes("/") ? raw : (() => {
32576
32957
  const m = modelFromRegistry(modelRegistry, raw);
@@ -32581,18 +32962,11 @@ function resolveParentModelFromRegistry(modelRegistry, rawParentModel) {
32581
32962
  })();
32582
32963
  if (candidate && modelFromRegistry(modelRegistry, candidate)) return candidate;
32583
32964
  }
32584
- const registry2 = modelRegistry;
32585
- if (registry2 && typeof registry2.getAvailable === "function") {
32586
- try {
32587
- const available = registry2.getAvailable();
32588
- if (Array.isArray(available) && available.length > 0) {
32589
- const first = available[0];
32590
- if (first && typeof first.provider === "string" && typeof first.id === "string") {
32591
- return `${first.provider}/${first.id}`;
32592
- }
32593
- }
32594
- } catch {
32595
- }
32965
+ const available = availableModelInfosFromRegistry(modelRegistry);
32966
+ if (available && available.length > 0) {
32967
+ const parentProvider = providerOfModelRef(raw);
32968
+ const sameProvider = parentProvider ? available.find((entry) => entry.provider === parentProvider) : void 0;
32969
+ return (sameProvider ?? available[0]).fullId;
32596
32970
  }
32597
32971
  return raw;
32598
32972
  }
@@ -32852,14 +33226,32 @@ async function runLiveSessionTask(input) {
32852
33226
  overrideModel: input.modelOverride,
32853
33227
  stepModel: input.step.model,
32854
33228
  teamRoleModel: input.teamRoleModel,
33229
+ teamRoleFallbackModels: input.teamRoleFallbackModels,
32855
33230
  agentModel: input.agent.model,
33231
+ defaultSubagentModel: resolveLiveDefaultSubagentModel(input.manifest.cwd),
32856
33232
  fallbackModels: input.agent.fallbackModels,
32857
33233
  parentModel: effectiveParentModel,
32858
33234
  modelRegistry: input.modelRegistry,
32859
33235
  cwd: input.manifest.cwd,
33236
+ policy: resolveLiveModelFallbackPolicy(input.manifest.cwd),
32860
33237
  scopeModelsPatterns: await resolveScopeModelsPatterns(input.manifest.cwd)
32861
33238
  });
32862
33239
  const resolvedModel = modelFromRegistry(input.modelRegistry, modelRouting.candidates[0] ?? modelRouting.requested) ?? input.parentModel;
33240
+ if (modelRouting.droppedRequested) {
33241
+ appendEventFireAndForget(input.manifest.eventsPath, {
33242
+ type: "task.model_dropped",
33243
+ runId: input.manifest.runId,
33244
+ taskId: input.task.id,
33245
+ message: `Requested model "${modelRouting.droppedRequested}" is not available; using "${modelRouting.candidates[0] ?? "default"}" instead.`,
33246
+ data: {
33247
+ requested: modelRouting.droppedRequested,
33248
+ resolved: modelRouting.candidates[0],
33249
+ fallbackChain: modelRouting.candidates
33250
+ }
33251
+ });
33252
+ }
33253
+ warnOutOfScopeSoft(modelRouting.scopeVerdict, "live-session.model-out-of-scope");
33254
+ const effectiveThinking = input.teamRoleThinking ?? input.agent.thinking;
32863
33255
  const mcpProxy = buildMcpProxyFromSession([], { shareMcp: true });
32864
33256
  const submitResultTool = createSubmitResultTool((result4) => {
32865
33257
  customToolYieldResult = result4;
@@ -32880,7 +33272,7 @@ async function runLiveSessionTask(input) {
32880
33272
  } : {},
32881
33273
  ...input.modelRegistry ? { modelRegistry: input.modelRegistry } : {},
32882
33274
  ...resolvedModel ? { model: resolvedModel } : {},
32883
- ...input.agent.thinking ? { thinkingLevel: input.agent.thinking } : {},
33275
+ ...effectiveThinking ? { thinkingLevel: effectiveThinking } : {},
32884
33276
  ...mcpProxy.enableMcp ? {} : { enableMCP: false },
32885
33277
  customTools
32886
33278
  });
@@ -33218,7 +33610,15 @@ ${input.prompt}` : input.prompt;
33218
33610
  stderr: created.modelFallbackMessage ?? "",
33219
33611
  jsonEvents,
33220
33612
  usage,
33221
- yieldResult
33613
+ yieldResult,
33614
+ modelRouting: {
33615
+ requested: modelRouting.requested,
33616
+ resolved: modelRefToString(resolvedModel) ?? modelRouting.candidates[0] ?? "default",
33617
+ fallbackChain: modelRouting.candidates,
33618
+ reason: modelRouting.reason,
33619
+ droppedRequested: modelRouting.droppedRequested,
33620
+ autoFallbackCount: modelRouting.autoFallbackCount
33621
+ }
33222
33622
  };
33223
33623
  } catch (error) {
33224
33624
  const message = error instanceof Error ? error.message : String(error);
@@ -36955,6 +37355,43 @@ function buildTeamDoctorReport(input) {
36955
37355
  }
36956
37356
  ];
36957
37357
  }),
37358
+ section("Model Routing", () => {
37359
+ const snapshot = sessionModelSnapshot();
37360
+ const liveModel = currentSessionModel();
37361
+ const policy = resolveModelFallbackPolicy(loadConfig(input.cwd).config.runtime?.modelFallback);
37362
+ const sampleRouting = buildConfiguredModelRouting({
37363
+ parentModel: liveModel,
37364
+ cwd: input.cwd,
37365
+ policy
37366
+ });
37367
+ return [
37368
+ {
37369
+ label: "session model (live)",
37370
+ ok: true,
37371
+ detail: liveModel ?? "not tracked yet"
37372
+ },
37373
+ {
37374
+ label: "session model (source)",
37375
+ ok: true,
37376
+ detail: `${snapshot.source}${snapshot.updatedAt ? ` @ ${new Date(snapshot.updatedAt).toISOString()}` : ""}`
37377
+ },
37378
+ {
37379
+ label: "fallback policy",
37380
+ ok: true,
37381
+ detail: policy ? `maxAuto=${policy.maxAutoFallbacks ?? "\u221E"} order=${policy.order ?? "parentFirst"} creds=${policy.requireCredentials ?? false} quota=${policy.quotaAwareOrdering ?? true}` : "legacy (unbounded, unordered)"
37382
+ },
37383
+ {
37384
+ label: "sample chain (no explicit model)",
37385
+ ok: true,
37386
+ detail: sampleRouting.candidates.length > 0 ? sampleRouting.candidates.join(" \u2192 ") : "(empty)"
37387
+ },
37388
+ {
37389
+ label: "auto tail size",
37390
+ ok: true,
37391
+ detail: `${sampleRouting.autoFallbackCount ?? 0} models`
37392
+ }
37393
+ ];
37394
+ }),
36958
37395
  section("Discovery", () => {
36959
37396
  const agentModelHints = discoveredAgentsAll.filter((agent) => agent.model || agent.fallbackModels?.length).length;
36960
37397
  return [
@@ -37139,7 +37576,9 @@ var init_doctor = __esm({
37139
37576
  init_config();
37140
37577
  init_defaults();
37141
37578
  init_drift_detector();
37579
+ init_model_fallback();
37142
37580
  init_runtime_warmup();
37581
+ init_session_model();
37143
37582
  init_pi_spawn();
37144
37583
  init_zombie_scanner();
37145
37584
  init_team_tool_schema();
@@ -41164,7 +41603,7 @@ var init_async_runner = __esm({
41164
41603
  });
41165
41604
 
41166
41605
  // src/runtime/goal-workflow/goal-state-store.ts
41167
- import { closeSync as closeSync10, existsSync as existsSync44, mkdirSync as mkdirSync26, openSync as openSync10, readdirSync as readdirSync20, readFileSync as readFileSync42, statSync as statSync32, unlinkSync as unlinkSync7 } from "node:fs";
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";
41168
41607
  import { dirname as dirname32 } from "node:path";
41169
41608
  function resolveGoalsRoot(cwd) {
41170
41609
  const crewRoot = projectCrewRoot(cwd) ?? userCrewRoot();
@@ -41207,22 +41646,22 @@ var init_goal_state_store = __esm({
41207
41646
  }
41208
41647
  }
41209
41648
  /** Atomically persist a goal state. Emits a goal.state_changed event if eventsPath given. */
41210
- save(state, eventsPath) {
41211
- assertSafePathId("goalId", state.goalId);
41212
- const path89 = goalFilePath(this.cwd, state.goalId);
41213
- const next = { ...state, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
41649
+ save(state2, eventsPath) {
41650
+ assertSafePathId("goalId", state2.goalId);
41651
+ const path89 = goalFilePath(this.cwd, state2.goalId);
41652
+ const next = { ...state2, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
41214
41653
  try {
41215
41654
  mkdirSync26(dirname32(path89), { recursive: true });
41216
41655
  atomicWriteJson(path89, next);
41217
41656
  if (eventsPath) {
41218
41657
  appendEvent(eventsPath, {
41219
41658
  type: "goal.state_changed",
41220
- runId: state.goalId,
41221
- data: { goalId: state.goalId, state: state.state }
41659
+ runId: state2.goalId,
41660
+ data: { goalId: state2.goalId, state: state2.state }
41222
41661
  });
41223
41662
  }
41224
41663
  } catch (error) {
41225
- logInternalError("goal-state-store.save", error, `goalId=${state.goalId}`);
41664
+ logInternalError("goal-state-store.save", error, `goalId=${state2.goalId}`);
41226
41665
  throw error;
41227
41666
  }
41228
41667
  }
@@ -41240,8 +41679,8 @@ var init_goal_state_store = __esm({
41240
41679
  return next;
41241
41680
  }
41242
41681
  /** Convenience: transition state with optional event emission. */
41243
- setStatus(goalId, state, eventsPath) {
41244
- return this.patch(goalId, { state }, eventsPath);
41682
+ setStatus(goalId, state2, eventsPath) {
41683
+ return this.patch(goalId, { state: state2 }, eventsPath);
41245
41684
  }
41246
41685
  /**
41247
41686
  * Compare-And-Set status for atomic stuck↔resume transitions (P1b, RFC v0.5 §P1b).
@@ -41295,7 +41734,7 @@ var init_goal_state_store = __esm({
41295
41734
  const code = error.code;
41296
41735
  if (code !== "EEXIST") return false;
41297
41736
  try {
41298
- const stat2 = statSync32(lockPath2);
41737
+ const stat2 = statSync33(lockPath2);
41299
41738
  if (Date.now() - stat2.mtimeMs > 5e3) {
41300
41739
  unlinkSync7(lockPath2);
41301
41740
  const fd = openSync10(lockPath2, "wx");
@@ -41388,7 +41827,7 @@ var init_verification_integrity = __esm({
41388
41827
 
41389
41828
  // src/runtime/workspace-lock.ts
41390
41829
  import { createHash as createHash7 } from "node:crypto";
41391
- import { closeSync as closeSync11, existsSync as existsSync45, mkdirSync as mkdirSync27, openSync as openSync11, readdirSync as readdirSync21, readFileSync as readFileSync44, statSync as statSync34, unlinkSync as unlinkSync8, writeFileSync as writeFileSync8 } from "node:fs";
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";
41392
41831
  import * as path50 from "node:path";
41393
41832
  function workspaceLockPath(cwd) {
41394
41833
  const absCwd = path50.resolve(cwd);
@@ -42694,8 +43133,8 @@ function registerPiCrewPowerbarSegments(events, config) {
42694
43133
  label: "pi-crew workflow steps"
42695
43134
  });
42696
43135
  }
42697
- function updatePiCrewPowerbar(events, cwd, config, manifestCache2, snapshotCache, ctx, notificationCount = 0, preloadedManifests) {
42698
- powerbarPublisher.update(events, cwd, config, manifestCache2, snapshotCache, ctx, notificationCount, preloadedManifests);
43136
+ function updatePiCrewPowerbar(events, cwd, config, manifestCache2, snapshotCache, ctx, notificationCount = 0, preloadedManifests, workspaceId) {
43137
+ powerbarPublisher.update(events, cwd, config, manifestCache2, snapshotCache, ctx, notificationCount, preloadedManifests, workspaceId);
42699
43138
  }
42700
43139
  function powerbarKey(payload) {
42701
43140
  return `${payload.text ?? ""}|${payload.suffix ?? ""}|${payload.bar ?? ""}|${payload.color ?? ""}|${payload.icon ?? ""}|${payload.barSegments ?? ""}`;
@@ -42739,8 +43178,8 @@ function buildStepsPayload(active, allTasks) {
42739
43178
  color
42740
43179
  };
42741
43180
  }
42742
- function requestPowerbarUpdate(events, cwd, config, manifestCache2, snapshotCache, ctx, notificationCount = 0, preloadedManifests) {
42743
- powerbarPublisher.request(events, cwd, config, manifestCache2, snapshotCache, ctx, notificationCount, preloadedManifests);
43181
+ function requestPowerbarUpdate(events, cwd, config, manifestCache2, snapshotCache, ctx, notificationCount = 0, preloadedManifests, workspaceId) {
43182
+ powerbarPublisher.request(events, cwd, config, manifestCache2, snapshotCache, ctx, notificationCount, preloadedManifests, workspaceId);
42744
43183
  }
42745
43184
  function disposePowerbarCoalescer() {
42746
43185
  powerbarPublisher.dispose();
@@ -42762,6 +43201,7 @@ var init_powerbar_publisher = __esm({
42762
43201
  init_usage();
42763
43202
  init_file_coalescer();
42764
43203
  init_internal_error();
43204
+ init_session_utils();
42765
43205
  init_discover_workflows();
42766
43206
  init_render_coalescer();
42767
43207
  init_widget_formatters();
@@ -42777,13 +43217,25 @@ var init_powerbar_publisher = __esm({
42777
43217
  const a = this.#latestArgs;
42778
43218
  this.#latestArgs = null;
42779
43219
  if (!a) return;
42780
- this.update(a.events, a.cwd, a.config, a.manifestCache, a.snapshotCache, a.ctx, a.notificationCount, a.preloadedManifests);
43220
+ this.update(
43221
+ a.events,
43222
+ a.cwd,
43223
+ a.config,
43224
+ a.manifestCache,
43225
+ a.snapshotCache,
43226
+ a.ctx,
43227
+ a.notificationCount,
43228
+ a.preloadedManifests,
43229
+ a.workspaceId
43230
+ );
42781
43231
  }, 200);
42782
43232
  }
42783
- update(events, cwd, config, manifestCache2, snapshotCache, ctx, notificationCount = 0, preloadedManifests) {
43233
+ update(events, cwd, config, manifestCache2, snapshotCache, ctx, notificationCount = 0, preloadedManifests, workspaceId) {
42784
43234
  if (config?.powerbar === false) return;
42785
43235
  const useStatusFallback = !hasPowerbarConsumer(events);
42786
- const runs = preloadedManifests ?? (manifestCache2 ? manifestCache2.list(20) : listRecentRuns(cwd, 20));
43236
+ const effectiveWorkspaceId = workspaceId ?? extractSessionId(ctx);
43237
+ const allRuns = preloadedManifests ?? (manifestCache2 ? manifestCache2.list(20) : listRecentRuns(cwd, 20));
43238
+ const runs = effectiveWorkspaceId ? allRuns.filter((run) => !run.ownerSessionId || run.ownerSessionId === effectiveWorkspaceId) : allRuns;
42787
43239
  const active = runs.map((run) => {
42788
43240
  let snapshot;
42789
43241
  try {
@@ -42839,7 +43291,8 @@ var init_powerbar_publisher = __esm({
42839
43291
  const tokenTotal = hasUsage ? (usage.input ?? 0) + (usage.output ?? 0) + (usage.cacheRead ?? 0) + (usage.cacheWrite ?? 0) : snapshotTokens;
42840
43292
  const model = config?.showModel === false ? void 0 : agents.find((agent) => agent.model)?.model?.split("/").at(-1);
42841
43293
  const tokenText = config?.showTokens === false || !tokenTotal ? void 0 : compactTokens(tokenTotal);
42842
- const liveRunning = listLiveAgents().filter((a) => a.status === "running").length;
43294
+ const liveAgents2 = effectiveWorkspaceId ? listLiveAgentsByWorkspace(effectiveWorkspaceId) : listLiveAgents();
43295
+ const liveRunning = liveAgents2.filter((a) => a.status === "running").length;
42843
43296
  const runningCount = agents.filter((a) => a.status === "running").length;
42844
43297
  const queuedCount = active.reduce(
42845
43298
  (sum, item) => sum + item.tasks.reduce((s, t2) => s + (t2.status === "queued" || t2.status === "waiting" ? 1 : 0), 0),
@@ -42893,7 +43346,7 @@ var init_powerbar_publisher = __esm({
42893
43346
  * into a single render pass within 200ms, preventing UI flicker from
42894
43347
  * event bursts.
42895
43348
  */
42896
- request(events, cwd, config, manifestCache2, snapshotCache, ctx, notificationCount = 0, preloadedManifests) {
43349
+ request(events, cwd, config, manifestCache2, snapshotCache, ctx, notificationCount = 0, preloadedManifests, workspaceId) {
42897
43350
  if (config?.powerbar === false) return;
42898
43351
  this.#latestArgs = {
42899
43352
  events,
@@ -42903,7 +43356,8 @@ var init_powerbar_publisher = __esm({
42903
43356
  snapshotCache,
42904
43357
  ctx,
42905
43358
  notificationCount,
42906
- preloadedManifests
43359
+ preloadedManifests,
43360
+ workspaceId
42907
43361
  };
42908
43362
  this.#coalescer.request();
42909
43363
  }
@@ -43945,19 +44399,19 @@ function ensureShared() {
43945
44399
  return shared;
43946
44400
  }
43947
44401
  function registerOverlayScheduler(render, onInvalidate) {
43948
- const state = ensureShared();
43949
- state.renderers.add(render);
43950
- if (onInvalidate) state.invalidators.add(onInvalidate);
44402
+ const state2 = ensureShared();
44403
+ state2.renderers.add(render);
44404
+ if (onInvalidate) state2.invalidators.add(onInvalidate);
43951
44405
  return {
43952
44406
  schedule: () => {
43953
- state.scheduler.schedule();
44407
+ state2.scheduler.schedule();
43954
44408
  },
43955
44409
  dispose: () => {
43956
- state.renderers.delete(render);
43957
- if (onInvalidate) state.invalidators.delete(onInvalidate);
43958
- if (state.renderers.size === 0) {
43959
- state.scheduler.dispose();
43960
- if (shared === state) shared = void 0;
44410
+ state2.renderers.delete(render);
44411
+ if (onInvalidate) state2.invalidators.delete(onInvalidate);
44412
+ if (state2.renderers.size === 0) {
44413
+ state2.scheduler.dispose();
44414
+ if (shared === state2) shared = void 0;
43961
44415
  }
43962
44416
  }
43963
44417
  };
@@ -44611,12 +45065,13 @@ function shouldRecoverTask(task, deadMs) {
44611
45065
  if (!task.heartbeat) return true;
44612
45066
  return task.heartbeat.alive === false || isWorkerHeartbeatStale(task.heartbeat, deadMs);
44613
45067
  }
44614
- function detectInterruptedRuns(cwd, manifestCache2, deadMs = 3e5) {
45068
+ function detectInterruptedRuns(cwd, manifestCache2, deadMs = 3e5, currentSessionId) {
44615
45069
  const plans = [];
44616
45070
  for (const manifest of manifestCache2.list(50)) {
44617
45071
  if (manifest.status !== "running" && manifest.status !== "blocked") continue;
44618
45072
  if (isPlanApprovalPending(manifest)) continue;
44619
45073
  if (manifest.async?.pid !== void 0 && checkProcessLiveness(manifest.async.pid).alive) continue;
45074
+ if (currentSessionId && manifest.ownerSessionId && manifest.ownerSessionId === currentSessionId) continue;
44620
45075
  const loaded = loadRunManifestById(cwd, manifest.runId);
44621
45076
  if (!loaded) continue;
44622
45077
  const resumableTasks = loaded.tasks.filter((task) => shouldRecoverTask(task, deadMs)).map((task) => task.id);
@@ -44788,7 +45243,7 @@ function hasRecentLifeEvidence(entry, manifestUpdatedAt, now, staleThresholdMs)
44788
45243
  if (Number.isFinite(hbAge) && hbAge <= staleThresholdMs) return true;
44789
45244
  return false;
44790
45245
  }
44791
- function purgeStaleActiveRunIndex(staleThresholdMs = 3e5, now = Date.now()) {
45246
+ function purgeStaleActiveRunIndex(staleThresholdMs = 3e5, now = Date.now(), currentSessionId) {
44792
45247
  const purged = [];
44793
45248
  const kept = [];
44794
45249
  const entries = readActiveRunRegistry();
@@ -44830,6 +45285,10 @@ function purgeStaleActiveRunIndex(staleThresholdMs = 3e5, now = Date.now()) {
44830
45285
  purged.push(entry.runId);
44831
45286
  continue;
44832
45287
  }
45288
+ if (currentSessionId && manifest?.ownerSessionId && manifest.ownerSessionId === currentSessionId) {
45289
+ kept.push(entry.runId);
45290
+ continue;
45291
+ }
44833
45292
  if (manifest?.status === "running" && manifest.async?.pid !== void 0) {
44834
45293
  const pidAlive = checkProcessLiveness(manifest.async.pid).alive;
44835
45294
  if (!pidAlive && !hasRecentLifeEvidence(entry, manifest.updatedAt, now, staleThresholdMs)) {
@@ -44930,9 +45389,13 @@ function purgeStaleActiveRunIndex(staleThresholdMs = 3e5, now = Date.now()) {
44930
45389
  }
44931
45390
  return { purged, kept };
44932
45391
  }
44933
- function reconcileAllStaleRuns(cwd, manifestCache2, now = Date.now()) {
45392
+ function reconcileAllStaleRuns(cwd, manifestCache2, now = Date.now(), currentSessionId) {
44934
45393
  const results = [];
44935
- const runIds = manifestCache2.list(50).filter((m) => m.status === "running" || m.status === "blocked").map((m) => m.runId);
45394
+ const runIds = manifestCache2.list(50).filter((m) => {
45395
+ if (m.status !== "running" && m.status !== "blocked") return false;
45396
+ if (currentSessionId && m.ownerSessionId && m.ownerSessionId === currentSessionId) return false;
45397
+ return true;
45398
+ }).map((m) => m.runId);
44936
45399
  for (const runId of runIds) {
44937
45400
  const cached2 = manifestCache2.get(runId);
44938
45401
  if (!cached2) continue;
@@ -45016,7 +45479,7 @@ function activeWidgetRuns(cwd, manifestCache2, snapshotCache, preloadedManifests
45016
45479
  if (now - lastStaleReconcileAt > STALE_RECONCILE_INTERVAL_MS && manifestCache2) {
45017
45480
  lastStaleReconcileAt = now;
45018
45481
  try {
45019
- reconcileAllStaleRuns(cwd, manifestCache2);
45482
+ reconcileAllStaleRuns(cwd, manifestCache2, Date.now(), workspaceId);
45020
45483
  } catch {
45021
45484
  }
45022
45485
  }
@@ -45417,9 +45880,9 @@ function uninstallResizeListener() {
45417
45880
  process.stdout.removeListener("resize", onResize);
45418
45881
  }
45419
45882
  }
45420
- function updateCrewWidget(ctx, state, config, manifestCache2, snapshotCache, preloadedManifests) {
45883
+ function updateCrewWidget(ctx, state2, config, manifestCache2, snapshotCache, preloadedManifests) {
45421
45884
  if (!ctx.hasUI) return;
45422
- state.frame += 1;
45885
+ state2.frame += 1;
45423
45886
  const maxLines = config?.widgetMaxLines ?? MAX_LINES_DEFAULT;
45424
45887
  let workspaceId = ctx.sessionManager?.getSessionId?.();
45425
45888
  if (!workspaceId && manifestCache2) {
@@ -45428,73 +45891,75 @@ function updateCrewWidget(ctx, state, config, manifestCache2, snapshotCache, pre
45428
45891
  if (active?.ownerSessionId) workspaceId = active.ownerSessionId;
45429
45892
  }
45430
45893
  const runs = activeWidgetRuns(ctx.cwd, manifestCache2, snapshotCache, preloadedManifests, workspaceId);
45431
- const lines = buildWidgetLines(ctx.cwd, state.frame, maxLines, runs, state.notificationCount ?? 0, getRenderWidth());
45894
+ const lines = buildWidgetLines(ctx.cwd, state2.frame, maxLines, runs, state2.notificationCount ?? 0, getRenderWidth());
45432
45895
  const placement = config?.widgetPlacement ?? DEFAULT_UI.widgetPlacement;
45433
45896
  ctx.ui.setStatus(STATUS_KEY, lines.length ? statusSummary(runs) : void 0);
45434
- const shouldClearLegacy = state.legacyCleared !== true || state.lastPlacement !== placement;
45897
+ const shouldClearLegacy = state2.legacyCleared !== true || state2.lastPlacement !== placement;
45435
45898
  if (shouldClearLegacy) {
45436
45899
  setExtensionWidget(ctx, LEGACY_WIDGET_KEY, void 0, { placement });
45437
- state.legacyCleared = true;
45900
+ state2.legacyCleared = true;
45438
45901
  }
45439
45902
  if (!lines.length) {
45440
- if (state.lastVisibility !== "hidden" || state.lastPlacement !== placement) {
45903
+ if (state2.lastVisibility !== "hidden" || state2.lastPlacement !== placement) {
45441
45904
  setExtensionWidget(ctx, WIDGET_KEY, void 0, { placement });
45442
- state.lastVisibility = "hidden";
45443
- state.lastPlacement = placement;
45444
- state.lastKey = WIDGET_KEY;
45445
- state.lastMaxLines = maxLines;
45446
- state.lastCwd = ctx.cwd;
45447
- state.model = void 0;
45905
+ state2.lastVisibility = "hidden";
45906
+ state2.lastPlacement = placement;
45907
+ state2.lastKey = WIDGET_KEY;
45908
+ state2.lastMaxLines = maxLines;
45909
+ state2.lastCwd = ctx.cwd;
45910
+ state2.model = void 0;
45448
45911
  }
45449
45912
  requestRender(ctx);
45450
45913
  return;
45451
45914
  }
45452
- const needsWidgetInstall = state.lastVisibility !== "visible" || state.lastPlacement !== placement || state.lastKey !== WIDGET_KEY || state.lastMaxLines !== maxLines || state.lastCwd !== ctx.cwd || !state.model;
45453
- if (!state.model)
45454
- state.model = {
45915
+ const needsWidgetInstall = state2.lastVisibility !== "visible" || state2.lastPlacement !== placement || state2.lastKey !== WIDGET_KEY || state2.lastMaxLines !== maxLines || state2.lastCwd !== ctx.cwd || !state2.model;
45916
+ if (!state2.model)
45917
+ state2.model = {
45455
45918
  cwd: ctx.cwd,
45456
- frame: state.frame,
45919
+ frame: state2.frame,
45457
45920
  maxLines,
45458
- notificationCount: state.notificationCount ?? 0,
45921
+ notificationCount: state2.notificationCount ?? 0,
45459
45922
  manifestCache: manifestCache2,
45460
45923
  snapshotCache,
45461
- preloadManifests: preloadedManifests
45924
+ preloadManifests: preloadedManifests,
45925
+ workspaceId
45462
45926
  };
45463
45927
  else {
45464
- state.model.cwd = ctx.cwd;
45465
- state.model.frame = state.frame;
45466
- state.model.maxLines = maxLines;
45467
- state.model.notificationCount = state.notificationCount ?? 0;
45468
- state.model.manifestCache = manifestCache2;
45469
- state.model.snapshotCache = snapshotCache;
45470
- state.model.preloadManifests = preloadedManifests;
45928
+ state2.model.cwd = ctx.cwd;
45929
+ state2.model.frame = state2.frame;
45930
+ state2.model.maxLines = maxLines;
45931
+ state2.model.notificationCount = state2.notificationCount ?? 0;
45932
+ state2.model.manifestCache = manifestCache2;
45933
+ state2.model.snapshotCache = snapshotCache;
45934
+ state2.model.preloadManifests = preloadedManifests;
45935
+ state2.model.workspaceId = workspaceId;
45471
45936
  }
45472
45937
  if (needsWidgetInstall) {
45473
- const model = state.model;
45938
+ const model = state2.model;
45474
45939
  setExtensionWidget(ctx, WIDGET_KEY, ((_tui, theme) => new CrewWidgetComponent(model, theme, _tui)), {
45475
45940
  placement,
45476
45941
  persist: true
45477
45942
  });
45478
- state.lastVisibility = "visible";
45479
- state.lastPlacement = placement;
45480
- state.lastKey = WIDGET_KEY;
45481
- state.lastMaxLines = maxLines;
45482
- state.lastCwd = ctx.cwd;
45943
+ state2.lastVisibility = "visible";
45944
+ state2.lastPlacement = placement;
45945
+ state2.lastKey = WIDGET_KEY;
45946
+ state2.lastMaxLines = maxLines;
45947
+ state2.lastCwd = ctx.cwd;
45483
45948
  }
45484
45949
  requestRender(ctx);
45485
45950
  }
45486
- function stopCrewWidget(ctx, state, config) {
45951
+ function stopCrewWidget(ctx, state2, config) {
45487
45952
  uninstallResizeListener();
45488
45953
  if (ctx?.hasUI) {
45489
45954
  const placement = config?.widgetPlacement ?? DEFAULT_UI.widgetPlacement;
45490
45955
  ctx.ui.setStatus(STATUS_KEY, void 0);
45491
45956
  setExtensionWidget(ctx, LEGACY_WIDGET_KEY, void 0, { placement });
45492
45957
  setExtensionWidget(ctx, WIDGET_KEY, void 0, { placement });
45493
- state.lastVisibility = "hidden";
45494
- state.lastPlacement = placement;
45495
- state.lastKey = WIDGET_KEY;
45496
- state.model = void 0;
45497
- state.legacyCleared = true;
45958
+ state2.lastVisibility = "hidden";
45959
+ state2.lastPlacement = placement;
45960
+ state2.lastKey = WIDGET_KEY;
45961
+ state2.model = void 0;
45962
+ state2.legacyCleared = true;
45498
45963
  requestRender(ctx);
45499
45964
  }
45500
45965
  }
@@ -45603,7 +46068,13 @@ var init_widget = __esm({
45603
46068
  if (activeResizeTarget === this) activeResizeTarget = void 0;
45604
46069
  }
45605
46070
  render(width) {
45606
- const runs = activeWidgetRuns(this.model.cwd, this.model.manifestCache, this.model.snapshotCache, this.model.preloadManifests);
46071
+ const runs = activeWidgetRuns(
46072
+ this.model.cwd,
46073
+ this.model.manifestCache,
46074
+ this.model.snapshotCache,
46075
+ this.model.preloadManifests,
46076
+ this.model.workspaceId
46077
+ );
45607
46078
  const now = Date.now();
45608
46079
  let sigBase;
45609
46080
  if (this.cachedBuildSignature !== "" && now - this.cachedBuildSignatureAt < SIGNATURE_CACHE_TTL_MS) {
@@ -45708,7 +46179,7 @@ var init_tool_progress_formatter = __esm({
45708
46179
  });
45709
46180
 
45710
46181
  // src/extension/registration/team-tool.ts
45711
- import { statSync as statSync37 } from "node:fs";
46182
+ import { statSync as statSync38 } from "node:fs";
45712
46183
  import { Text as Text3 } from "@earendil-works/pi-tui";
45713
46184
  async function handleTeamTool(params, ctx) {
45714
46185
  if (!_cachedHandleTeamTool) {
@@ -45731,7 +46202,7 @@ function resolveCwdOverride(baseCwd, override) {
45731
46202
  if (!override) return { ok: true, cwd: baseCwd };
45732
46203
  try {
45733
46204
  const resolved = resolveRealContainedPath(baseCwd, override);
45734
- const stat2 = statSync37(resolved);
46205
+ const stat2 = statSync38(resolved);
45735
46206
  if (!stat2.isDirectory())
45736
46207
  return {
45737
46208
  ok: false,
@@ -52641,6 +53112,20 @@ function resolveConfiguredMaxAttempts(cwd) {
52641
53112
  function computeSpawnBudgetMax(attemptModelsCount, configuredMaxAttempts) {
52642
53113
  return attemptModelsCount * (configuredMaxAttempts + 1);
52643
53114
  }
53115
+ function resolveTaskModelFallbackPolicy(cwd) {
53116
+ try {
53117
+ return resolveModelFallbackPolicy(loadConfig(cwd).config.runtime?.modelFallback);
53118
+ } catch {
53119
+ return void 0;
53120
+ }
53121
+ }
53122
+ function resolveTaskDefaultSubagentModel(cwd) {
53123
+ try {
53124
+ return resolveDefaultSubagentModel(loadConfig(cwd).config.runtime?.modelFallback);
53125
+ } catch {
53126
+ return void 0;
53127
+ }
53128
+ }
52644
53129
  function detectRetryableModelFailureFromOutput(parsed) {
52645
53130
  const messages = parsed.errorMessages;
52646
53131
  if (messages && messages.length > 0) {
@@ -52686,24 +53171,45 @@ async function runChildProcessTask(ctx) {
52686
53171
  let transcriptPath;
52687
53172
  let terminalEvidence = [];
52688
53173
  let startupEvidence = ctx.startupEvidence;
53174
+ const modelFallbackPolicy = resolveTaskModelFallbackPolicy(task.cwd);
53175
+ const defaultSubagentModel = resolveTaskDefaultSubagentModel(task.cwd);
52689
53176
  const modelRoutingPlan = buildConfiguredModelRouting({
52690
53177
  overrideModel: input.modelOverride,
52691
53178
  stepModel: input.step.model,
52692
53179
  teamRoleModel: input.teamRoleModel,
53180
+ teamRoleFallbackModels: input.teamRoleFallbackModels,
52693
53181
  agentModel: input.agent.model,
53182
+ defaultSubagentModel,
52694
53183
  fallbackModels: input.agent.fallbackModels,
52695
53184
  parentModel: input.parentModel,
52696
53185
  modelRegistry: input.modelRegistry,
52697
53186
  cwd: task.cwd,
53187
+ policy: modelFallbackPolicy,
52698
53188
  scopeModelsPatterns: await resolveTaskScopeModelsPatterns(task.cwd)
52699
53189
  });
52700
53190
  const candidates = modelRoutingPlan.candidates;
52701
- const attemptModels = candidates.length > 0 ? candidates : [void 0];
53191
+ if (modelRoutingPlan.droppedRequested) {
53192
+ void appendEventAsync(manifest.eventsPath, {
53193
+ type: "task.model_dropped",
53194
+ runId: manifest.runId,
53195
+ taskId: task.id,
53196
+ message: `Requested model "${modelRoutingPlan.droppedRequested}" is not available; using "${candidates[0] ?? "default"}" instead.`,
53197
+ data: {
53198
+ requested: modelRoutingPlan.droppedRequested,
53199
+ resolved: candidates[0],
53200
+ fallbackChain: candidates
53201
+ }
53202
+ }).catch(() => {
53203
+ });
53204
+ }
53205
+ warnOutOfScopeSoft(modelRoutingPlan.scopeVerdict, "child-executor.initial-out-of-scope");
53206
+ const attemptModels = candidates.length > 0 ? [...candidates] : [void 0];
52702
53207
  if (input.spawnBudget && input.spawnBudget.max === 0) {
52703
53208
  input.spawnBudget.max = computeSpawnBudgetMax(attemptModels.length, resolveConfiguredMaxAttempts(task.cwd));
52704
53209
  }
52705
53210
  const logs = [];
52706
53211
  let finalStderr = "";
53212
+ let reResolveUsed = false;
52707
53213
  modelAttempts = [];
52708
53214
  let finalCheckpointWritten = false;
52709
53215
  let lastAgentRecordPersistedAt = 0;
@@ -52824,6 +53330,7 @@ async function runChildProcessTask(ctx) {
52824
53330
  excludeContextBash: input.runtimeConfig?.excludeContextBash,
52825
53331
  sessionId: manifest.sessionId,
52826
53332
  role: task.role,
53333
+ thinkingOverride: input.teamRoleThinking,
52827
53334
  runId: manifest.runId,
52828
53335
  agentId: task.id,
52829
53336
  artifactsRoot: manifest.artifactsRoot,
@@ -52992,7 +53499,8 @@ async function runChildProcessTask(ctx) {
52992
53499
  );
52993
53500
  if (!error) break;
52994
53501
  let nextModel = attemptModels[i + 1];
52995
- if (!nextModel && isRetryableModelFailure(error)) {
53502
+ if (!nextModel && !reResolveUsed && isRetryableModelFailure(error)) {
53503
+ reResolveUsed = true;
52996
53504
  const reResolved = buildConfiguredModelRouting({
52997
53505
  overrideModel: void 0,
52998
53506
  stepModel: void 0,
@@ -53002,10 +53510,17 @@ async function runChildProcessTask(ctx) {
53002
53510
  parentModel: attempt.model,
53003
53511
  modelRegistry: input.modelRegistry,
53004
53512
  cwd: task.cwd,
53513
+ policy: modelFallbackPolicy,
53005
53514
  scopeModelsPatterns: await resolveTaskScopeModelsPatterns(task.cwd)
53006
53515
  });
53007
- const alt = reResolved.candidates.find((c) => c !== attempt.model);
53008
- if (alt) nextModel = alt;
53516
+ warnOutOfScopeSoft(reResolved.scopeVerdict, "child-executor.re-resolve-out-of-scope", "Re-resolved model");
53517
+ const tried = new Set(modelAttempts.map((a) => a.model));
53518
+ const alt = reResolved.candidates.find((candidate) => !tried.has(candidate));
53519
+ if (alt) {
53520
+ attemptModels.push(alt);
53521
+ nextModel = alt;
53522
+ if (input.spawnBudget) input.spawnBudget.max += 1;
53523
+ }
53009
53524
  }
53010
53525
  if (!nextModel || !isRetryableModelFailure(error)) break;
53011
53526
  logs.push(formatModelAttemptNote(attempt, nextModel), "");
@@ -53074,7 +53589,9 @@ async function runChildProcessTask(ctx) {
53074
53589
  resolved: resolvedModel,
53075
53590
  fallbackChain: candidates,
53076
53591
  reason: fallbackReason ?? modelRoutingPlan.reason,
53077
- usedAttempt
53592
+ usedAttempt,
53593
+ droppedRequested: modelRoutingPlan.droppedRequested,
53594
+ autoFallbackCount: modelRoutingPlan.autoFallbackCount
53078
53595
  }
53079
53596
  };
53080
53597
  tasks = updateTask(tasks, task);
@@ -54985,6 +55502,8 @@ async function runLiveTask(input) {
54985
55502
  modelRegistry: input.modelRegistry,
54986
55503
  modelOverride: input.modelOverride,
54987
55504
  teamRoleModel: input.teamRoleModel,
55505
+ teamRoleFallbackModels: input.teamRoleFallbackModels,
55506
+ teamRoleThinking: input.teamRoleThinking,
54988
55507
  isCurrent,
54989
55508
  workspaceId: input.workspaceId,
54990
55509
  // Phase 2: Pass output schema for yield validation
@@ -55028,6 +55547,14 @@ async function runLiveTask(input) {
55028
55547
  usage: liveResult.usage,
55029
55548
  agentProgress: applyUsageToProgress(task.agentProgress, liveResult.usage)
55030
55549
  };
55550
+ if (liveResult.modelRouting)
55551
+ task = {
55552
+ ...task,
55553
+ modelRouting: {
55554
+ ...liveResult.modelRouting,
55555
+ usedAttempt: 0
55556
+ }
55557
+ };
55031
55558
  persistLiveProgress({ type: "attempt_finished" }, true);
55032
55559
  const resultArtifact = writeArtifact(manifest.artifactsRoot, {
55033
55560
  kind: "result",
@@ -55154,6 +55681,8 @@ async function runTeamTask(input) {
55154
55681
  modelRegistry: input.modelRegistry,
55155
55682
  modelOverride: input.modelOverride,
55156
55683
  teamRoleModel: input.teamRoleModel,
55684
+ teamRoleFallbackModels: input.teamRoleFallbackModels,
55685
+ teamRoleThinking: input.teamRoleThinking,
55157
55686
  workspaceId: input.workspaceId
55158
55687
  });
55159
55688
  task = live.task;
@@ -56682,10 +57211,7 @@ async function cancelRunFromSignal(ctx) {
56682
57211
  return { kind: "return", result: { manifest: ctx.manifest, tasks: ctx.tasks } };
56683
57212
  }
56684
57213
  async function terminaliseRunWithDrain(ctx, opts) {
56685
- const inflightTaskIds = /* @__PURE__ */ new Set();
56686
- for (const unit of ctx.pendingUnits.values()) {
56687
- for (const id of unit.taskIds) inflightTaskIds.add(id);
56688
- }
57214
+ const inflightTaskIds = ctx.dispatchedTaskIds;
56689
57215
  const outcomes = await drainPendingUnits(ctx.pendingUnits, ctx.runController);
56690
57216
  const validResults = [];
56691
57217
  for (const outcome of outcomes) {
@@ -57006,6 +57532,8 @@ async function dispatchBatch(ctx, decision2) {
57006
57532
  modelRegistry: input.modelRegistry,
57007
57533
  modelOverride: input.modelOverride,
57008
57534
  teamRoleModel: teamRole?.model,
57535
+ teamRoleThinking: teamRole?.thinking,
57536
+ teamRoleFallbackModels: teamRole?.fallbackModels,
57009
57537
  teamRoleSkills: teamRole?.skills,
57010
57538
  skillOverride: input.skillOverride,
57011
57539
  limits: input.limits,
@@ -57191,6 +57719,7 @@ async function dispatchBatch(ctx, decision2) {
57191
57719
  promise: rawPromise,
57192
57720
  wrapped
57193
57721
  });
57722
+ for (const id of unitTaskIds) ctx.dispatchedTaskIds.add(id);
57194
57723
  }
57195
57724
  }
57196
57725
  async function mergeUnitResult(ctx) {
@@ -57551,6 +58080,7 @@ async function executeTeamRunCore(input, manifest, workflow) {
57551
58080
  queueIndex,
57552
58081
  wfMachine,
57553
58082
  pendingUnits,
58083
+ dispatchedTaskIds: /* @__PURE__ */ new Set(),
57554
58084
  runController,
57555
58085
  runtimeKind,
57556
58086
  adaptivePlanInjected,
@@ -59677,14 +60207,14 @@ var init_dwf_state_store = __esm({
59677
60207
  }
59678
60208
  }
59679
60209
  /** Atomically persist a checkpoint state. Stamps `updatedAt` (callers need not set it). */
59680
- save(state) {
60210
+ save(state2) {
59681
60211
  const path89 = this.path;
59682
- const next = { ...state, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
60212
+ const next = { ...state2, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
59683
60213
  try {
59684
60214
  mkdirSync39(dirname37(path89), { recursive: true });
59685
60215
  atomicWriteJson(path89, next);
59686
60216
  } catch (error) {
59687
- logInternalError("dwf-state-store.save", error, `runId=${state.runId}`);
60217
+ logInternalError("dwf-state-store.save", error, `runId=${state2.runId}`);
59688
60218
  throw error;
59689
60219
  }
59690
60220
  }
@@ -60583,9 +61113,9 @@ async function runDynamicWorkflow(input) {
60583
61113
  resumedState,
60584
61114
  // round-18 P2-3: checkpoint after each ctx.agent() call so a crash between calls
60585
61115
  // leaves durable state. onCheckpoint captures the closure values at call time.
60586
- onCheckpoint: (state) => {
61116
+ onCheckpoint: (state2) => {
60587
61117
  try {
60588
- dwfStore.save(state);
61118
+ dwfStore.save(state2);
60589
61119
  } catch (error) {
60590
61120
  logInternalError("dynamic-workflow-runner.checkpoint-save", error, `runId=${manifest.runId}`);
60591
61121
  }
@@ -61183,6 +61713,10 @@ ${dwfResult.manifest.summary ?? ""}`,
61183
61713
  ...updatedManifest,
61184
61714
  runtimeResolution,
61185
61715
  runConfig: executedConfig,
61716
+ // Background/async runs re-enter through background-runner in a detached
61717
+ // process with no ExtensionContext. Snapshot the model routing inputs so
61718
+ // they survive the hand-off instead of being rediscovered from models.json.
61719
+ modelContext: captureRunModelContext(ctx, params.model),
61186
61720
  // Persist budget config on the manifest so it's observable post-run
61187
61721
  // (events.jsonl, status reads, audits). The team-runner reads these
61188
61722
  // from the input, but persisting them means consumers can verify
@@ -61393,7 +61927,7 @@ ${dwfResult.manifest.summary ?? ""}`,
61393
61927
  runtime,
61394
61928
  runtimeConfig: executedConfig.runtime,
61395
61929
  parentContext: buildParentContext(ctx),
61396
- parentModel: ctx.model,
61930
+ parentModel: resolveParentModel(ctx.model),
61397
61931
  modelRegistry: ctx.modelRegistry,
61398
61932
  modelOverride: params.model,
61399
61933
  skillOverride,
@@ -61459,7 +61993,7 @@ ${dwfResult.manifest.summary ?? ""}`,
61459
61993
  runtime,
61460
61994
  runtimeConfig: executedConfig.runtime,
61461
61995
  parentContext: buildParentContext(ctx),
61462
- parentModel: ctx.model,
61996
+ parentModel: resolveParentModel(ctx.model),
61463
61997
  modelRegistry: ctx.modelRegistry,
61464
61998
  modelOverride: params.model,
61465
61999
  skillOverride,
@@ -61508,6 +62042,7 @@ var init_run2 = __esm({
61508
62042
  init_param_error();
61509
62043
  init_async_runner();
61510
62044
  init_runtime_resolver();
62045
+ init_session_model();
61511
62046
  init_event_log();
61512
62047
  init_i18n();
61513
62048
  init_async_marker();
@@ -61916,7 +62451,7 @@ async function handleResume2(params, ctx) {
61916
62451
  runtime: decision2.runtime,
61917
62452
  runtimeConfig: decision2.executedConfig.runtime,
61918
62453
  parentContext: buildParentContext(ctx),
61919
- parentModel: ctx.model,
62454
+ parentModel: resolveParentModel(ctx.model),
61920
62455
  modelRegistry: ctx.modelRegistry,
61921
62456
  modelOverride: params.model,
61922
62457
  skillOverride: decision2.resumeSkillOverride,
@@ -62163,6 +62698,7 @@ var init_team_tool2 = __esm({
62163
62698
  init_run_index();
62164
62699
  init_direct_run();
62165
62700
  init_runtime_resolver();
62701
+ init_session_model();
62166
62702
  init_pi_json_output();
62167
62703
  init_config_patch();
62168
62704
  init_context();
@@ -63194,16 +63730,16 @@ function readRunTranscript(manifest, taskId, options = {}) {
63194
63730
  readRunTranscriptCache.set(cacheKey2, { parsedAt: now, result: result4 });
63195
63731
  return result4;
63196
63732
  }
63197
- function renderViewerBase(state, width, lines, title, subtitle) {
63733
+ function renderViewerBase(state2, width, lines, title, subtitle) {
63198
63734
  const inner = Math.max(20, width - 4);
63199
63735
  const bodyText = lines.join("\n");
63200
- const { visualLines, skippedCount } = truncateToVisualLines(bodyText, state.lastHeight, inner);
63201
- const maxScroll = Math.max(0, visualLines.length - state.lastHeight);
63202
- if (state.autoScroll) state.scroll = maxScroll;
63203
- state.scroll = Math.min(state.scroll, maxScroll);
63204
- const visible = visualLines.slice(state.scroll, state.scroll + state.lastHeight);
63205
- const statusLine = `${visualLines.length} lines \xB7 ${visualLines.length ? Math.round((state.scroll + visible.length) / visualLines.length * 100) : 100}% \xB7 auto-scroll ${state.autoScroll ? "on" : "off"}`;
63206
- const fg = (color, text) => state.theme.fg(color, text);
63736
+ const { visualLines, skippedCount } = truncateToVisualLines(bodyText, state2.lastHeight, inner);
63737
+ const maxScroll = Math.max(0, visualLines.length - state2.lastHeight);
63738
+ if (state2.autoScroll) state2.scroll = maxScroll;
63739
+ state2.scroll = Math.min(state2.scroll, maxScroll);
63740
+ const visible = visualLines.slice(state2.scroll, state2.scroll + state2.lastHeight);
63741
+ const statusLine = `${visualLines.length} lines \xB7 ${visualLines.length ? Math.round((state2.scroll + visible.length) / visualLines.length * 100) : 100}% \xB7 auto-scroll ${state2.autoScroll ? "on" : "off"}`;
63742
+ const fg = (color, text) => state2.theme.fg(color, text);
63207
63743
  const row = (text) => `${fg("border", "\u2502")} ${pad(truncate(text, inner), inner)} ${fg("border", "\u2502")}`;
63208
63744
  const linesOut = [
63209
63745
  fg("border", `\u256D${"\u2500".repeat(inner + 2)}\u256E`),
@@ -63772,11 +64308,11 @@ function extractDwfPhaseState(events) {
63772
64308
  });
63773
64309
  return { phases, currentPhase };
63774
64310
  }
63775
- function renderDwfPhaseLines(state, options) {
64311
+ function renderDwfPhaseLines(state2, options) {
63776
64312
  const ascii = options?.ascii === true;
63777
64313
  const lines = [];
63778
- if (state.phases.length > 1) lines.push(DWF_PHASE_HEADER);
63779
- for (const entry of state.phases) {
64314
+ if (state2.phases.length > 1) lines.push(DWF_PHASE_HEADER);
64315
+ for (const entry of state2.phases) {
63780
64316
  lines.push(` ${markerFor(entry.status, ascii)} Phase: ${entry.name}`);
63781
64317
  }
63782
64318
  return lines;
@@ -64582,7 +65118,8 @@ var init_run_dashboard = __esm({
64582
65118
  if (!this.options.runProvider) return;
64583
65119
  const selectedRunId = this.selectedRunId();
64584
65120
  const next = this.options.runProvider();
64585
- this.runs = Array.isArray(next) ? next : this.runs;
65121
+ const unfiltered = Array.isArray(next) ? next : this.runs;
65122
+ this.runs = this.options.workspaceId ? unfiltered.filter((run) => !run.ownerSessionId || run.ownerSessionId === this.options.workspaceId) : unfiltered;
64586
65123
  if (selectedRunId) {
64587
65124
  const nextIndex = groupedRuns(this.runs, this.options.snapshotCache).filter((row) => row.run).findIndex((row) => row.run?.runId === selectedRunId);
64588
65125
  if (nextIndex >= 0) this.selected = nextIndex;
@@ -65703,24 +66240,24 @@ var init_mascot = __esm({
65703
66240
  }
65704
66241
  }
65705
66242
  tickTypewriter() {
65706
- const state = this.effectState;
65707
- if (state.pos === void 0) return true;
66243
+ const state2 = this.effectState;
66244
+ if (state2.pos === void 0) return true;
65708
66245
  for (let i = 0; i < 18; i++) {
65709
- const row = Math.floor(state.pos / ARMIN_WIDTH);
65710
- const x = state.pos % ARMIN_WIDTH;
66246
+ const row = Math.floor(state2.pos / ARMIN_WIDTH);
66247
+ const x = state2.pos % ARMIN_WIDTH;
65711
66248
  if (row >= ARMIN_DISPLAY_HEIGHT) return true;
65712
66249
  this.currentArminGrid[row][x] = this.finalArminGrid[row][x];
65713
- state.pos++;
66250
+ state2.pos++;
65714
66251
  }
65715
66252
  return false;
65716
66253
  }
65717
66254
  tickScanline() {
65718
- const state = this.effectState;
65719
- if (state.row === void 0) return true;
66255
+ const state2 = this.effectState;
66256
+ if (state2.row === void 0) return true;
65720
66257
  for (let step = 0; step < 3; step++) {
65721
- if (state.row >= ARMIN_DISPLAY_HEIGHT) return true;
65722
- for (let x = 0; x < ARMIN_WIDTH; x++) this.currentArminGrid[state.row][x] = this.finalArminGrid[state.row][x];
65723
- state.row++;
66258
+ if (state2.row >= ARMIN_DISPLAY_HEIGHT) return true;
66259
+ for (let x = 0; x < ARMIN_WIDTH; x++) this.currentArminGrid[state2.row][x] = this.finalArminGrid[state2.row][x];
66260
+ state2.row++;
65724
66261
  }
65725
66262
  return false;
65726
66263
  }
@@ -65756,33 +66293,33 @@ var init_mascot = __esm({
65756
66293
  return allSettled;
65757
66294
  }
65758
66295
  tickFade() {
65759
- const state = this.effectState;
65760
- if (!state.positions || state.idx === void 0) return true;
66296
+ const state2 = this.effectState;
66297
+ if (!state2.positions || state2.idx === void 0) return true;
65761
66298
  for (let i = 0; i < 54; i++) {
65762
- if (state.idx >= state.positions.length) return true;
65763
- const [row, x] = state.positions[state.idx];
66299
+ if (state2.idx >= state2.positions.length) return true;
66300
+ const [row, x] = state2.positions[state2.idx];
65764
66301
  this.currentArminGrid[row][x] = this.finalArminGrid[row][x];
65765
- state.idx++;
66302
+ state2.idx++;
65766
66303
  }
65767
66304
  return false;
65768
66305
  }
65769
66306
  tickCrt() {
65770
- const state = this.effectState;
65771
- if (state.expansion === void 0) return true;
66307
+ const state2 = this.effectState;
66308
+ if (state2.expansion === void 0) return true;
65772
66309
  const midRow = Math.floor(ARMIN_DISPLAY_HEIGHT / 2);
65773
66310
  this.currentArminGrid = emptyArminGrid();
65774
- const top = midRow - state.expansion;
65775
- const bottom = midRow + state.expansion;
66311
+ const top = midRow - state2.expansion;
66312
+ const bottom = midRow + state2.expansion;
65776
66313
  for (let row = Math.max(0, top); row <= Math.min(ARMIN_DISPLAY_HEIGHT - 1, bottom); row++) {
65777
66314
  for (let x = 0; x < ARMIN_WIDTH; x++) this.currentArminGrid[row][x] = this.finalArminGrid[row][x];
65778
66315
  }
65779
- state.expansion += 3;
65780
- return state.expansion > ARMIN_DISPLAY_HEIGHT;
66316
+ state2.expansion += 3;
66317
+ return state2.expansion > ARMIN_DISPLAY_HEIGHT;
65781
66318
  }
65782
66319
  tickGlitch() {
65783
- const state = this.effectState;
65784
- if (state.phase === void 0 || state.glitchFrames === void 0) return true;
65785
- if (state.phase < state.glitchFrames) {
66320
+ const state2 = this.effectState;
66321
+ if (state2.phase === void 0 || state2.glitchFrames === void 0) return true;
66322
+ if (state2.phase < state2.glitchFrames) {
65786
66323
  const offset = Math.floor(Math.random() * 7) - 3;
65787
66324
  const swapRow = Math.floor(Math.random() * ARMIN_DISPLAY_HEIGHT);
65788
66325
  const doShift = Math.random() < 0.3;
@@ -65797,7 +66334,7 @@ var init_mascot = __esm({
65797
66334
  }
65798
66335
  }
65799
66336
  }
65800
- state.phase += 3;
66337
+ state2.phase += 3;
65801
66338
  return false;
65802
66339
  }
65803
66340
  for (let row = 0; row < ARMIN_DISPLAY_HEIGHT; row++) {
@@ -65808,13 +66345,13 @@ var init_mascot = __esm({
65808
66345
  return true;
65809
66346
  }
65810
66347
  tickDissolve() {
65811
- const state = this.effectState;
65812
- if (!state.positions || state.idx === void 0) return true;
66348
+ const state2 = this.effectState;
66349
+ if (!state2.positions || state2.idx === void 0) return true;
65813
66350
  for (let i = 0; i < 66; i++) {
65814
- if (state.idx >= state.positions.length) return true;
65815
- const [row, x] = state.positions[state.idx];
66351
+ if (state2.idx >= state2.positions.length) return true;
66352
+ const [row, x] = state2.positions[state2.idx];
65816
66353
  this.currentArminGrid[row][x] = this.finalArminGrid[row][x];
65817
- state.idx++;
66354
+ state2.idx++;
65818
66355
  }
65819
66356
  return false;
65820
66357
  }
@@ -67808,6 +68345,7 @@ var init_commands = __esm({
67808
68345
  });
67809
68346
 
67810
68347
  // src/runtime/subagent-manager.ts
68348
+ import { randomUUID as randomUUID8 } from "node:crypto";
67811
68349
  import * as fs100 from "node:fs";
67812
68350
  import * as path81 from "node:path";
67813
68351
  function isValidSubagentId(id) {
@@ -67926,6 +68464,7 @@ var init_subagent_manager = __esm({
67926
68464
  "resultConsumed",
67927
68465
  "background",
67928
68466
  "ownerSessionGeneration",
68467
+ "ownerSessionId",
67929
68468
  "stuckNotified",
67930
68469
  "blockedAt",
67931
68470
  "turnCount",
@@ -67952,7 +68491,7 @@ var init_subagent_manager = __esm({
67952
68491
  }
67953
68492
  spawn(options, runner, signal) {
67954
68493
  const record = {
67955
- id: `agent_${Date.now().toString(36)}_${(++this.counter).toString(36)}`,
68494
+ id: `agent_${Date.now().toString(36)}_${randomUUID8().slice(0, 8)}_${(++this.counter).toString(36)}`,
67956
68495
  type: options.type,
67957
68496
  description: options.description,
67958
68497
  prompt: options.prompt,
@@ -67962,6 +68501,7 @@ var init_subagent_manager = __esm({
67962
68501
  skill: options.skill,
67963
68502
  background: options.background,
67964
68503
  ownerSessionGeneration: options.ownerSessionGeneration,
68504
+ ownerSessionId: options.ownerSessionId,
67965
68505
  batchId: options.batchId
67966
68506
  };
67967
68507
  this.records.set(record.id, record);
@@ -68214,7 +68754,8 @@ var init_subagent_manager = __esm({
68214
68754
  id: current.id,
68215
68755
  runId: current.runId,
68216
68756
  durationMs: Math.max(0, Date.now() - current.blockedAt),
68217
- ownerSessionGeneration: current.ownerSessionGeneration
68757
+ ownerSessionGeneration: current.ownerSessionGeneration,
68758
+ ownerSessionId: current.ownerSessionId
68218
68759
  });
68219
68760
  savePersistedSubagentRecord(cwd, current);
68220
68761
  };
@@ -68599,16 +69140,16 @@ async function importLiveRunSidebar() {
68599
69140
  }
68600
69141
  return _cachedLiveRunSidebar;
68601
69142
  }
68602
- async function installLiveSidebar(ctx, runId, state, deps) {
69143
+ async function installLiveSidebar(ctx, runId, state2, deps) {
68603
69144
  const uiConfig = loadConfig(ctx.cwd).config.ui;
68604
69145
  const autoOpen = uiConfig?.autoOpenDashboard === true;
68605
69146
  const foregroundAutoOpen = uiConfig?.autoOpenDashboardForForegroundRuns ?? DEFAULT_UI.autoOpenDashboardForForegroundRuns;
68606
69147
  if (!ctx.hasUI || !autoOpen || !foregroundAutoOpen || (uiConfig?.dashboardPlacement ?? DEFAULT_UI.dashboardPlacement) !== "right") {
68607
69148
  return false;
68608
69149
  }
68609
- if (state.liveSidebarRunId === runId) return true;
68610
- state.liveSidebarRunId = runId;
68611
- state.dashboardOpened = true;
69150
+ if (state2.liveSidebarRunId === runId) return true;
69151
+ state2.liveSidebarRunId = runId;
69152
+ state2.dashboardOpened = true;
68612
69153
  const widgetPlacement = uiConfig?.widgetPlacement ?? DEFAULT_UI.widgetPlacement;
68613
69154
  setExtensionWidget(ctx, "pi-crew", void 0, { placement: widgetPlacement });
68614
69155
  setExtensionWidget(ctx, "pi-crew-active", void 0, { placement: widgetPlacement });
@@ -68644,7 +69185,7 @@ async function installLiveSidebar(ctx, runId, state, deps) {
68644
69185
  }
68645
69186
  }
68646
69187
  ).finally(() => {
68647
- if (state.liveSidebarRunId === runId) state.liveSidebarRunId = void 0;
69188
+ if (state2.liveSidebarRunId === runId) state2.liveSidebarRunId = void 0;
68648
69189
  const c = deps.getCurrentCtx();
68649
69190
  if (!c) return;
68650
69191
  updateCrewWidget(
@@ -68661,12 +69202,12 @@ async function installLiveSidebar(ctx, runId, state, deps) {
68661
69202
  return false;
68662
69203
  }
68663
69204
  }
68664
- function clearDashboardPowerbar(state, deps) {
69205
+ function clearDashboardPowerbar(state2, deps) {
68665
69206
  const c = deps.getCurrentCtx();
68666
69207
  if (c) stopCrewWidget(c, deps.widgetState, loadConfig(c.cwd).config.ui);
68667
69208
  clearPiCrewPowerbar(deps.pi.events);
68668
69209
  resetPowerbarDedupState();
68669
- state.dashboardOpened = false;
69210
+ state2.dashboardOpened = false;
68670
69211
  }
68671
69212
  function registerPowerbarSegments(deps, uiConfig) {
68672
69213
  registerPiCrewPowerbarSegments(deps.pi.events, uiConfig);
@@ -68872,29 +69413,31 @@ function markDeadAsyncRunIfNeeded(run, now = Date.now(), quietMs = 3e4) {
68872
69413
  return failed;
68873
69414
  });
68874
69415
  }
68875
- function startAsyncRunNotifier(ctx, state, intervalMs = 5e3, options = {}) {
68876
- if (state.interval) clearInterval(state.interval);
68877
- const generation = options.generation ?? (state.generation ?? 0) + 1;
68878
- state.generation = generation;
69416
+ function startAsyncRunNotifier(ctx, state2, intervalMs = 5e3, options = {}) {
69417
+ if (state2.interval) clearInterval(state2.interval);
69418
+ const generation = options.generation ?? (state2.generation ?? 0) + 1;
69419
+ state2.generation = generation;
68879
69420
  const startedAtMs = Date.now();
68880
- const staleBeforeMs = state.lastStoppedAtMs ?? startedAtMs;
68881
- for (const run of listRuns(ctx.cwd)) {
69421
+ const staleBeforeMs = state2.lastStoppedAtMs ?? startedAtMs;
69422
+ const sid = extractSessionId(ctx);
69423
+ const ownsRun = (run) => !sid || !run.ownerSessionId || run.ownerSessionId === sid;
69424
+ for (const run of listRuns(ctx.cwd).filter(ownsRun)) {
68882
69425
  const updatedAtMs = timeMs(run.updatedAt) ?? 0;
68883
- if (isFinished2(run.status) && updatedAtMs < staleBeforeMs) state.seenFinishedRunIds.add(run.runId);
69426
+ if (isFinished2(run.status) && updatedAtMs < staleBeforeMs) state2.seenFinishedRunIds.add(run.runId);
68884
69427
  }
68885
69428
  let cachedRuns;
68886
- state.interval = setInterval(() => {
69429
+ state2.interval = setInterval(() => {
68887
69430
  try {
68888
69431
  if (options.isCurrent && !options.isCurrent(generation)) return;
68889
69432
  const nowMs3 = Date.now();
68890
- if (cachedRuns === void 0 || nowMs3 - (state.lastListRunsMs ?? 0) > LIST_RUNS_DEBOUNCE_MS) {
68891
- cachedRuns = listRuns(ctx.cwd).slice(0, 20);
68892
- state.lastListRunsMs = nowMs3;
69433
+ if (cachedRuns === void 0 || nowMs3 - (state2.lastListRunsMs ?? 0) > LIST_RUNS_DEBOUNCE_MS) {
69434
+ cachedRuns = listRuns(ctx.cwd).filter(ownsRun).slice(0, 20);
69435
+ state2.lastListRunsMs = nowMs3;
68893
69436
  }
68894
69437
  for (const run of cachedRuns) {
68895
69438
  const current = markDeadAsyncRunIfNeeded(run) ?? run;
68896
- if (!isFinished2(current.status) || state.seenFinishedRunIds.has(current.runId)) continue;
68897
- state.seenFinishedRunIds.add(current.runId);
69439
+ if (!isFinished2(current.status) || state2.seenFinishedRunIds.has(current.runId)) continue;
69440
+ state2.seenFinishedRunIds.add(current.runId);
68898
69441
  if (current.workflow === "goal-turn" && current.team.startsWith("goal-")) continue;
68899
69442
  const level = current.status === "completed" ? "info" : current.status === "cancelled" ? "warning" : "error";
68900
69443
  ctx.ui.notify(`pi-crew run ${current.status}: ${current.runId} (${current.team}/${current.workflow ?? "none"})`, level);
@@ -68907,13 +69450,13 @@ function startAsyncRunNotifier(ctx, state, intervalMs = 5e3, options = {}) {
68907
69450
  logInternalError("async-notifier", error, `interval=${intervalMs}`);
68908
69451
  }
68909
69452
  }, intervalMs);
68910
- if (typeof state.interval.unref === "function") state.interval.unref();
69453
+ if (typeof state2.interval.unref === "function") state2.interval.unref();
68911
69454
  }
68912
- function stopAsyncRunNotifier(state) {
68913
- if (state.interval) clearInterval(state.interval);
68914
- state.interval = void 0;
68915
- state.generation = (state.generation ?? 0) + 1;
68916
- state.lastStoppedAtMs = Date.now();
69455
+ function stopAsyncRunNotifier(state2) {
69456
+ if (state2.interval) clearInterval(state2.interval);
69457
+ state2.interval = void 0;
69458
+ state2.generation = (state2.generation ?? 0) + 1;
69459
+ state2.lastStoppedAtMs = Date.now();
68917
69460
  }
68918
69461
  var LIST_RUNS_DEBOUNCE_MS;
68919
69462
  var init_async_notifier = __esm({
@@ -68925,6 +69468,7 @@ var init_async_notifier = __esm({
68925
69468
  init_event_log();
68926
69469
  init_state_store();
68927
69470
  init_internal_error();
69471
+ init_session_utils();
68928
69472
  init_run_index();
68929
69473
  LIST_RUNS_DEBOUNCE_MS = 3e4;
68930
69474
  }
@@ -69137,11 +69681,15 @@ var init_delivery_coordinator = __esm({
69137
69681
  deps;
69138
69682
  ttlTimer;
69139
69683
  timerStarted = false;
69684
+ /** The session id passed to the most recent activate(); used by flushQueuedResults
69685
+ * to park deliveries owned by a different session (vector #12). */
69686
+ activeSessionId;
69140
69687
  constructor(deps) {
69141
69688
  this.deps = deps;
69142
69689
  }
69143
69690
  activate(sessionId) {
69144
69691
  this.active = true;
69692
+ this.activeSessionId = sessionId;
69145
69693
  this.flushQueuedResults();
69146
69694
  }
69147
69695
  deactivate() {
@@ -69154,7 +69702,7 @@ var init_delivery_coordinator = __esm({
69154
69702
  getPendingCount() {
69155
69703
  return this.pending.length;
69156
69704
  }
69157
- deliverResult(runId, result4) {
69705
+ deliverResult(runId, result4, ownerSessionId) {
69158
69706
  if (this.active && this.deps.emit) {
69159
69707
  try {
69160
69708
  this.deps.emit("pi-crew:run-result", result4);
@@ -69168,10 +69716,11 @@ var init_delivery_coordinator = __esm({
69168
69716
  runId,
69169
69717
  payload: result4,
69170
69718
  timestamp: Date.now(),
69171
- type: "result"
69719
+ type: "result",
69720
+ ownerSessionId
69172
69721
  });
69173
69722
  }
69174
- deliverNotification(notification) {
69723
+ deliverNotification(notification, ownerSessionId) {
69175
69724
  let delivered = false;
69176
69725
  if (this.active && this.deps.sendFollowUp) {
69177
69726
  try {
@@ -69195,10 +69744,11 @@ var init_delivery_coordinator = __esm({
69195
69744
  runId: notification.runId ?? "",
69196
69745
  payload: notification,
69197
69746
  timestamp: Date.now(),
69198
- type: "notification"
69747
+ type: "notification",
69748
+ ownerSessionId
69199
69749
  });
69200
69750
  }
69201
- deliverSteer(runId, message) {
69751
+ deliverSteer(runId, message, ownerSessionId) {
69202
69752
  if (this.active && this.deps.sendWakeUp) {
69203
69753
  try {
69204
69754
  this.deps.sendWakeUp(message);
@@ -69212,7 +69762,8 @@ var init_delivery_coordinator = __esm({
69212
69762
  runId,
69213
69763
  payload: message,
69214
69764
  timestamp: Date.now(),
69215
- type: "steer"
69765
+ type: "steer",
69766
+ ownerSessionId
69216
69767
  });
69217
69768
  }
69218
69769
  flushQueuedResults() {
@@ -69223,6 +69774,10 @@ var init_delivery_coordinator = __esm({
69223
69774
  try {
69224
69775
  const retryLater = [];
69225
69776
  for (const delivery of batch) {
69777
+ if (delivery.ownerSessionId && this.activeSessionId && delivery.ownerSessionId !== this.activeSessionId) {
69778
+ retryLater.push(delivery);
69779
+ continue;
69780
+ }
69226
69781
  if (delivery.type === "steer" && delivery.generation !== void 0 && delivery.generation !== this.generation) {
69227
69782
  logInternalError("delivery-coordinator.flush.stale", void 0, `runId=${delivery.runId} type=${delivery.type}`);
69228
69783
  continue;
@@ -69357,7 +69912,7 @@ var init_overflow_recovery = __esm({
69357
69912
  default:
69358
69913
  break;
69359
69914
  }
69360
- const state = {
69915
+ const state2 = {
69361
69916
  taskId,
69362
69917
  runId,
69363
69918
  phase,
@@ -69366,14 +69921,14 @@ var init_overflow_recovery = __esm({
69366
69921
  compactionCount,
69367
69922
  retryCount
69368
69923
  };
69369
- this.states.set(key, state);
69924
+ this.states.set(key, state2);
69370
69925
  this.resetTimeout(key);
69371
69926
  if (this.states.size > MAX_TRACKED_STATES) {
69372
69927
  this.evictOldestTerminalState();
69373
69928
  }
69374
69929
  if (previousPhase !== phase && this.callbacks.onPhaseChange) {
69375
69930
  try {
69376
- this.callbacks.onPhaseChange(state, previousPhase);
69931
+ this.callbacks.onPhaseChange(state2, previousPhase);
69377
69932
  } catch (error) {
69378
69933
  logInternalError("overflow-recovery.onPhaseChange", error, `taskId=${taskId}`);
69379
69934
  }
@@ -69382,13 +69937,13 @@ var init_overflow_recovery = __esm({
69382
69937
  }
69383
69938
  getState(taskId, runId) {
69384
69939
  if (runId) return this.states.get(this.keyFor(taskId, runId));
69385
- return [...this.states.values()].find((state) => state.taskId === taskId);
69940
+ return [...this.states.values()].find((state2) => state2.taskId === taskId);
69386
69941
  }
69387
69942
  getPhase(taskId, runId) {
69388
69943
  return this.getState(taskId, runId)?.phase ?? "none";
69389
69944
  }
69390
69945
  removeTask(taskId, runId) {
69391
- const keys = runId ? [this.keyFor(taskId, runId)] : [...this.states.entries()].filter(([, state]) => state.taskId === taskId).map(([key]) => key);
69946
+ const keys = runId ? [this.keyFor(taskId, runId)] : [...this.states.entries()].filter(([, state2]) => state2.taskId === taskId).map(([key]) => key);
69392
69947
  for (const key of keys) this.removeKey(key);
69393
69948
  }
69394
69949
  /**
@@ -69400,10 +69955,10 @@ var init_overflow_recovery = __esm({
69400
69955
  evictOldestTerminalState() {
69401
69956
  let oldestKey;
69402
69957
  let oldestTimestamp = Infinity;
69403
- for (const [key, state] of this.states) {
69404
- const isTerminal = state.phase === "recovered" || state.phase === "failed" || state.phase === "none";
69405
- if (isTerminal && state.lastEventAt < oldestTimestamp) {
69406
- oldestTimestamp = state.lastEventAt;
69958
+ for (const [key, state2] of this.states) {
69959
+ const isTerminal = state2.phase === "recovered" || state2.phase === "failed" || state2.phase === "none";
69960
+ if (isTerminal && state2.lastEventAt < oldestTimestamp) {
69961
+ oldestTimestamp = state2.lastEventAt;
69407
69962
  oldestKey = key;
69408
69963
  }
69409
69964
  }
@@ -69432,27 +69987,27 @@ var init_overflow_recovery = __esm({
69432
69987
  const timeoutMs = current?.phase === "recovered" || current?.phase === "failed" || current?.phase === "none" ? TERMINAL_STATE_TTL_MS : PHASE_TIMEOUT_MS;
69433
69988
  const timer = setTimeout(() => {
69434
69989
  this.timers.delete(key);
69435
- const state = this.states.get(key);
69436
- if (!state) return;
69437
- if (state.phase === "recovered" || state.phase === "failed" || state.phase === "none") {
69990
+ const state2 = this.states.get(key);
69991
+ if (!state2) return;
69992
+ if (state2.phase === "recovered" || state2.phase === "failed" || state2.phase === "none") {
69438
69993
  this.states.delete(key);
69439
69994
  return;
69440
69995
  }
69441
- const previousPhase = state.phase;
69442
- state.phase = "failed";
69443
- state.lastEventAt = Date.now();
69996
+ const previousPhase = state2.phase;
69997
+ state2.phase = "failed";
69998
+ state2.lastEventAt = Date.now();
69444
69999
  if (this.callbacks.onTimeout) {
69445
70000
  try {
69446
- this.callbacks.onTimeout(state);
70001
+ this.callbacks.onTimeout(state2);
69447
70002
  } catch (error) {
69448
- logInternalError("overflow-recovery.onTimeout", error, `taskId=${state.taskId}`);
70003
+ logInternalError("overflow-recovery.onTimeout", error, `taskId=${state2.taskId}`);
69449
70004
  }
69450
70005
  }
69451
70006
  if (this.callbacks.onPhaseChange) {
69452
70007
  try {
69453
- this.callbacks.onPhaseChange(state, previousPhase);
70008
+ this.callbacks.onPhaseChange(state2, previousPhase);
69454
70009
  } catch (error) {
69455
- logInternalError("overflow-recovery.onPhaseChange-timeout", error, `taskId=${state.taskId}`);
70010
+ logInternalError("overflow-recovery.onPhaseChange-timeout", error, `taskId=${state2.taskId}`);
69456
70011
  }
69457
70012
  }
69458
70013
  }, timeoutMs);
@@ -69473,10 +70028,10 @@ __export(lifecycle_exports, {
69473
70028
  startLifecycleWatchers: () => startLifecycleWatchers,
69474
70029
  stopLifecycleWatchers: () => stopLifecycleWatchers
69475
70030
  });
69476
- function startLifecycleWatchers(ctx, state, deps) {
69477
- if (state.notifierStarted) return false;
70031
+ function startLifecycleWatchers(ctx, state2, deps) {
70032
+ if (state2.notifierStarted) return false;
69478
70033
  const loadedConfig = loadConfig(ctx.cwd);
69479
- state.notifierStarted = true;
70034
+ state2.notifierStarted = true;
69480
70035
  try {
69481
70036
  void Promise.resolve().then(() => (init_async_notifier(), async_notifier_exports)).then(({ startAsyncRunNotifier: startAsyncRunNotifier2 }) => {
69482
70037
  if (deps.isCleanedUp()) return;
@@ -69487,14 +70042,14 @@ function startLifecycleWatchers(ctx, state, deps) {
69487
70042
  });
69488
70043
  return true;
69489
70044
  } catch (error) {
69490
- state.notifierStarted = false;
70045
+ state2.notifierStarted = false;
69491
70046
  logInternalError("register.startLifecycleWatchers", error);
69492
70047
  return false;
69493
70048
  }
69494
70049
  }
69495
- function stopLifecycleWatchers(state, deps) {
69496
- if (!state.notifierStarted) return;
69497
- state.notifierStarted = false;
70050
+ function stopLifecycleWatchers(state2, deps) {
70051
+ if (!state2.notifierStarted) return;
70052
+ state2.notifierStarted = false;
69498
70053
  try {
69499
70054
  void Promise.resolve().then(() => (init_async_notifier(), async_notifier_exports)).then(({ stopAsyncRunNotifier: stopAsyncRunNotifier2 }) => {
69500
70055
  stopAsyncRunNotifier2(deps.notifierState);
@@ -69503,11 +70058,11 @@ function stopLifecycleWatchers(state, deps) {
69503
70058
  logInternalError("register.stopLifecycleWatchers", error);
69504
70059
  }
69505
70060
  }
69506
- async function configureNotifications(ctx, state, deps) {
69507
- state.notificationRouter?.dispose();
69508
- state.notificationSink?.dispose();
69509
- state.notificationRouter = void 0;
69510
- state.notificationSink = void 0;
70061
+ async function configureNotifications(ctx, state2, deps) {
70062
+ state2.notificationRouter?.dispose();
70063
+ state2.notificationSink?.dispose();
70064
+ state2.notificationRouter = void 0;
70065
+ state2.notificationSink = void 0;
69511
70066
  const config = loadConfig(ctx.cwd).config;
69512
70067
  if (config.notifications?.enabled === false) return;
69513
70068
  const { NotificationRouter: NotificationRouter2 } = await Promise.resolve().then(() => (init_notification_router(), notification_router_exports));
@@ -69515,18 +70070,18 @@ async function configureNotifications(ctx, state, deps) {
69515
70070
  const { sendFollowUp: sendFollowUp2 } = await Promise.resolve().then(() => (init_subagent_helpers(), subagent_helpers_exports));
69516
70071
  const { updateCrewWidget: updateCrewWidget2 } = await Promise.resolve().then(() => (init_widget(), widget_exports));
69517
70072
  if (config.telemetry?.enabled !== false) {
69518
- state.notificationSink = createJsonlSink2(
70073
+ state2.notificationSink = createJsonlSink2(
69519
70074
  projectCrewRoot(ctx.cwd),
69520
70075
  config.notifications?.sinkRetentionDays ?? DEFAULT_NOTIFICATIONS.sinkRetentionDays
69521
70076
  );
69522
70077
  }
69523
- state.notificationRouter = new NotificationRouter2(
70078
+ state2.notificationRouter = new NotificationRouter2(
69524
70079
  {
69525
70080
  dedupWindowMs: config.notifications?.dedupWindowMs ?? DEFAULT_NOTIFICATIONS.dedupWindowMs,
69526
70081
  batchWindowMs: config.notifications?.batchWindowMs ?? DEFAULT_NOTIFICATIONS.batchWindowMs,
69527
70082
  quietHours: config.notifications?.quietHours,
69528
70083
  severityFilter: config.notifications?.severityFilter ?? [...DEFAULT_NOTIFICATIONS.severityFilter],
69529
- sink: (notification) => state.notificationSink?.write(notification)
70084
+ sink: (notification) => state2.notificationSink?.write(notification)
69530
70085
  },
69531
70086
  (notification) => {
69532
70087
  deps.widgetState.notificationCount = (deps.widgetState.notificationCount ?? 0) + 1;
@@ -69557,20 +70112,20 @@ async function configureNotifications(ctx, state, deps) {
69557
70112
  }
69558
70113
  );
69559
70114
  }
69560
- function disposeNotifications(state) {
69561
- state.notificationRouter?.dispose();
69562
- state.notificationRouter = void 0;
69563
- state.notificationSink?.dispose();
69564
- state.notificationSink = void 0;
69565
- }
69566
- async function configureDeliveryCoordinator(state, deps) {
69567
- state.deliveryCoordinator?.dispose();
69568
- state.deliveryCoordinator = void 0;
69569
- state.overflowTracker?.dispose();
69570
- state.overflowTracker = void 0;
70115
+ function disposeNotifications(state2) {
70116
+ state2.notificationRouter?.dispose();
70117
+ state2.notificationRouter = void 0;
70118
+ state2.notificationSink?.dispose();
70119
+ state2.notificationSink = void 0;
70120
+ }
70121
+ async function configureDeliveryCoordinator(state2, deps) {
70122
+ state2.deliveryCoordinator?.dispose();
70123
+ state2.deliveryCoordinator = void 0;
70124
+ state2.overflowTracker?.dispose();
70125
+ state2.overflowTracker = void 0;
69571
70126
  const { DeliveryCoordinator: DeliveryCoordinator2 } = await Promise.resolve().then(() => (init_delivery_coordinator(), delivery_coordinator_exports));
69572
70127
  const { OverflowRecoveryTracker: OverflowRecoveryTracker2 } = await Promise.resolve().then(() => (init_overflow_recovery(), overflow_recovery_exports));
69573
- state.deliveryCoordinator = new DeliveryCoordinator2({
70128
+ state2.deliveryCoordinator = new DeliveryCoordinator2({
69574
70129
  emit: (event, data) => {
69575
70130
  deps.pi.events?.emit?.(event, data);
69576
70131
  },
@@ -69581,7 +70136,7 @@ async function configureDeliveryCoordinator(state, deps) {
69581
70136
  deps.sendAgentWakeUp(deps.pi, message);
69582
70137
  }
69583
70138
  });
69584
- state.overflowTracker = new OverflowRecoveryTracker2({
70139
+ state2.overflowTracker = new OverflowRecoveryTracker2({
69585
70140
  onPhaseChange: (phaseState, previousPhase) => {
69586
70141
  if (deps.observabilityState.metricRegistry) {
69587
70142
  deps.observabilityState.metricRegistry.counter("crew.task.overflow_recovery_total", "Overflow recovery phase transitions").inc({
@@ -69608,11 +70163,11 @@ async function configureDeliveryCoordinator(state, deps) {
69608
70163
  }
69609
70164
  });
69610
70165
  }
69611
- function disposeDeliveryCoordinator(state) {
69612
- state.deliveryCoordinator?.dispose();
69613
- state.deliveryCoordinator = void 0;
69614
- state.overflowTracker?.dispose();
69615
- state.overflowTracker = void 0;
70166
+ function disposeDeliveryCoordinator(state2) {
70167
+ state2.deliveryCoordinator?.dispose();
70168
+ state2.deliveryCoordinator = void 0;
70169
+ state2.overflowTracker?.dispose();
70170
+ state2.overflowTracker = void 0;
69616
70171
  }
69617
70172
  var init_lifecycle = __esm({
69618
70173
  "src/extension/registration/lifecycle.ts"() {
@@ -70589,21 +71144,21 @@ async function importOTLPExporter() {
70589
71144
  }
70590
71145
  return _cachedOTLPExporter;
70591
71146
  }
70592
- async function configureObservability(ctx, state, deps) {
70593
- await disposeObservability(state, deps.isCleanedUp());
71147
+ async function configureObservability(ctx, state2, deps) {
71148
+ await disposeObservability(state2, deps.isCleanedUp());
70594
71149
  const config = loadConfig(ctx.cwd).config;
70595
71150
  if (config.observability?.enabled === false) return;
70596
71151
  const { createMetricRegistry: createMetricRegistry2 } = await Promise.resolve().then(() => (init_metric_registry(), metric_registry_exports));
70597
71152
  const { wireEventToMetrics: wireEventToMetrics2 } = await Promise.resolve().then(() => (init_event_to_metric(), event_to_metric_exports));
70598
71153
  const { createMetricFileSink: createMetricFileSink2 } = await Promise.resolve().then(() => (init_metric_sink(), metric_sink_exports));
70599
- state.metricRegistry = createMetricRegistry2();
71154
+ state2.metricRegistry = createMetricRegistry2();
70600
71155
  if (deps.pi.events) {
70601
- state.eventMetricSub = wireEventToMetrics2(deps.pi.events, state.metricRegistry);
71156
+ state2.eventMetricSub = wireEventToMetrics2(deps.pi.events, state2.metricRegistry);
70602
71157
  }
70603
71158
  if (config.telemetry?.enabled !== false) {
70604
- state.metricSink = createMetricFileSink2({
71159
+ state2.metricSink = createMetricFileSink2({
70605
71160
  crewRoot: projectCrewRoot(ctx.cwd),
70606
- registry: state.metricRegistry,
71161
+ registry: state2.metricRegistry,
70607
71162
  retentionDays: config.observability?.metricRetentionDays ?? 7
70608
71163
  });
70609
71164
  }
@@ -70611,10 +71166,10 @@ async function configureObservability(ctx, state, deps) {
70611
71166
  const otlpEndpoint = config.otlp.endpoint;
70612
71167
  const otlpHeaders = config.otlp.headers;
70613
71168
  const otlpInterval = config.otlp.intervalMs;
70614
- const owningRegistry = state.metricRegistry;
71169
+ const owningRegistry = state2.metricRegistry;
70615
71170
  void importOTLPExporter().then((Ctor) => {
70616
- if (deps.isCleanedUp() || state.metricRegistry !== owningRegistry || !owningRegistry) return;
70617
- state.otlpExporter = new Ctor(
71171
+ if (deps.isCleanedUp() || state2.metricRegistry !== owningRegistry || !owningRegistry) return;
71172
+ state2.otlpExporter = new Ctor(
70618
71173
  {
70619
71174
  endpoint: otlpEndpoint,
70620
71175
  headers: otlpHeaders,
@@ -70622,15 +71177,15 @@ async function configureObservability(ctx, state, deps) {
70622
71177
  },
70623
71178
  owningRegistry
70624
71179
  );
70625
- state.otlpExporter?.start();
71180
+ state2.otlpExporter?.start();
70626
71181
  }).catch((error) => logInternalError("register.otlp-lazy-import", error));
70627
71182
  }
70628
71183
  const { HeartbeatWatcher: HeartbeatWatcher2 } = await Promise.resolve().then(() => (init_heartbeat_watcher(), heartbeat_watcher_exports));
70629
- state.heartbeatWatcher = new HeartbeatWatcher2({
71184
+ state2.heartbeatWatcher = new HeartbeatWatcher2({
70630
71185
  cwd: ctx.cwd,
70631
71186
  pollIntervalMs: config.observability?.pollIntervalMs ?? 5e3,
70632
71187
  manifestCache: deps.getManifestCache(ctx.cwd),
70633
- registry: state.metricRegistry,
71188
+ registry: state2.metricRegistry,
70634
71189
  router: {
70635
71190
  enqueue: (notification) => {
70636
71191
  deps.notifyOperator(notification);
@@ -70646,7 +71201,7 @@ async function configureObservability(ctx, state, deps) {
70646
71201
  attempts: 0,
70647
71202
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
70648
71203
  });
70649
- state.metricRegistry?.counter("crew.task.deadletter_total", "Deadletter triggers by reason").inc({ reason: "heartbeat-dead" });
71204
+ state2.metricRegistry?.counter("crew.task.deadletter_total", "Deadletter triggers by reason").inc({ reason: "heartbeat-dead" });
70650
71205
  deps.pi.events?.emit?.("crew.task.deadletter", {
70651
71206
  runId: manifest.runId,
70652
71207
  taskId,
@@ -70654,12 +71209,12 @@ async function configureObservability(ctx, state, deps) {
70654
71209
  });
70655
71210
  }
70656
71211
  });
70657
- state.heartbeatWatcher.start();
71212
+ state2.heartbeatWatcher.start();
70658
71213
  try {
70659
71214
  deps.pi.on?.("before_agent_start", () => {
70660
71215
  if (deps.isCleanedUp()) return;
70661
71216
  try {
70662
- deps.reconcileStaleRuns(ctx.cwd, deps.getManifestCache(ctx.cwd));
71217
+ deps.reconcileStaleRuns(ctx.cwd, deps.getManifestCache(ctx.cwd), extractSessionId(ctx));
70663
71218
  } catch (error) {
70664
71219
  logInternalError("register.autoRepair.turnHook", error);
70665
71220
  }
@@ -70668,10 +71223,10 @@ async function configureObservability(ctx, state, deps) {
70668
71223
  }
70669
71224
  const autoRepairIntervalMs = config.reliability?.autoRepairIntervalMs ?? 3e5;
70670
71225
  if (autoRepairIntervalMs > 0) {
70671
- state.autoRepairTimer = setInterval(() => {
71226
+ state2.autoRepairTimer = setInterval(() => {
70672
71227
  if (deps.isCleanedUp()) return;
70673
71228
  try {
70674
- const staleResults = deps.reconcileStaleRuns(ctx.cwd, deps.getManifestCache(ctx.cwd));
71229
+ const staleResults = deps.reconcileStaleRuns(ctx.cwd, deps.getManifestCache(ctx.cwd), extractSessionId(ctx));
70675
71230
  if (Array.isArray(staleResults) && staleResults.length > 0) {
70676
71231
  for (const result4 of staleResults) {
70677
71232
  const repaired = result4.repaired;
@@ -70691,8 +71246,8 @@ async function configureObservability(ctx, state, deps) {
70691
71246
  logInternalError("register.autoRepair", error);
70692
71247
  }
70693
71248
  }, autoRepairIntervalMs);
70694
- state.autoRepairTimer.unref();
70695
- state.tempReconcileTimer = setInterval(() => {
71249
+ state2.autoRepairTimer.unref();
71250
+ state2.tempReconcileTimer = setInterval(() => {
70696
71251
  if (deps.isCleanedUp()) return;
70697
71252
  try {
70698
71253
  deps.reconcileOrphanedTempWorkspaces(Date.now(), {
@@ -70722,14 +71277,15 @@ async function configureObservability(ctx, state, deps) {
70722
71277
  logInternalError("register.tempAutoRepair", error);
70723
71278
  }
70724
71279
  }, autoRepairIntervalMs * 5);
70725
- state.tempReconcileTimer.unref();
71280
+ state2.tempReconcileTimer.unref();
70726
71281
  }
70727
71282
  if (config.reliability?.autoRecover === true) {
70728
71283
  const cwdSnapshot = ctx.cwd;
70729
71284
  const cacheSnapshot = deps.getManifestCache(cwdSnapshot);
70730
71285
  void deps.importCrashRecovery().then(({ detectInterruptedRuns: detectInterruptedRuns2 }) => {
70731
71286
  if (deps.isCleanedUp()) return;
70732
- for (const plan of detectInterruptedRuns2(cwdSnapshot, cacheSnapshot)) {
71287
+ const sid = extractSessionId(ctx);
71288
+ for (const plan of detectInterruptedRuns2(cwdSnapshot, cacheSnapshot, 3e5, sid)) {
70733
71289
  deps.notifyOperator({
70734
71290
  id: `recovery_prompt_${plan.runId}`,
70735
71291
  severity: "warning",
@@ -70742,25 +71298,25 @@ async function configureObservability(ctx, state, deps) {
70742
71298
  }).catch((error) => logInternalError("register.crash-recovery-lazy-import", error));
70743
71299
  }
70744
71300
  }
70745
- async function disposeObservability(state, _isCleanedUp) {
70746
- state.heartbeatWatcher?.dispose();
70747
- state.heartbeatWatcher = void 0;
70748
- if (state.autoRepairTimer) {
70749
- clearInterval(state.autoRepairTimer);
70750
- state.autoRepairTimer = void 0;
71301
+ async function disposeObservability(state2, _isCleanedUp) {
71302
+ state2.heartbeatWatcher?.dispose();
71303
+ state2.heartbeatWatcher = void 0;
71304
+ if (state2.autoRepairTimer) {
71305
+ clearInterval(state2.autoRepairTimer);
71306
+ state2.autoRepairTimer = void 0;
70751
71307
  }
70752
- if (state.tempReconcileTimer) {
70753
- clearInterval(state.tempReconcileTimer);
70754
- state.tempReconcileTimer = void 0;
71308
+ if (state2.tempReconcileTimer) {
71309
+ clearInterval(state2.tempReconcileTimer);
71310
+ state2.tempReconcileTimer = void 0;
70755
71311
  }
70756
- state.metricSink?.dispose();
70757
- state.metricSink = void 0;
70758
- state.eventMetricSub?.dispose();
70759
- state.eventMetricSub = void 0;
70760
- await state.otlpExporter?.dispose();
70761
- state.otlpExporter = void 0;
70762
- state.metricRegistry?.dispose();
70763
- state.metricRegistry = void 0;
71312
+ state2.metricSink?.dispose();
71313
+ state2.metricSink = void 0;
71314
+ state2.eventMetricSub?.dispose();
71315
+ state2.eventMetricSub = void 0;
71316
+ await state2.otlpExporter?.dispose();
71317
+ state2.otlpExporter = void 0;
71318
+ state2.metricRegistry?.dispose();
71319
+ state2.metricRegistry = void 0;
70764
71320
  }
70765
71321
  var _cachedOTLPExporter;
70766
71322
  var init_observability = __esm({
@@ -70769,6 +71325,7 @@ var init_observability = __esm({
70769
71325
  init_config();
70770
71326
  init_internal_error();
70771
71327
  init_paths();
71328
+ init_session_utils();
70772
71329
  }
70773
71330
  });
70774
71331
 
@@ -74346,7 +74903,10 @@ function buildRegistrationContext(pi) {
74346
74903
  globalStore: globalThis,
74347
74904
  runtimeCleanupStoreKey: RUNTIME_CLEANUP_STORE_KEY,
74348
74905
  captureSessionGeneration: () => ctx.sessionGeneration,
74349
- isOwnerSessionCurrent: (gen) => !ctx.cleanedUp && (gen === void 0 || gen === ctx.sessionGeneration),
74906
+ isOwnerSessionCurrent: (gen, oid) => {
74907
+ const currentSid = ctx.currentCtx?.sessionManager?.getSessionId?.();
74908
+ return !ctx.cleanedUp && (oid === void 0 || oid === currentSid) && (gen === void 0 || gen === ctx.sessionGeneration);
74909
+ },
74350
74910
  isContextCurrent: (c, gen) => !ctx.cleanedUp && ctx.currentCtx === c && ctx.sessionGeneration === gen,
74351
74911
  telemetryEnabled: () => loadConfig(ctx.currentCtx?.cwd ?? process.cwd()).config.telemetry?.enabled !== false,
74352
74912
  notifyOperator: void 0,
@@ -74358,7 +74918,7 @@ function buildRegistrationContext(pi) {
74358
74918
  configureObservability: () => void 0,
74359
74919
  configureDeliveryCoordinator: () => void 0,
74360
74920
  importCrashRecovery: void 0,
74361
- purgeStaleActiveRunIndexSyncIfLoaded: () => void 0,
74921
+ purgeStaleActiveRunIndexSyncIfLoaded: (_currentSessionId) => void 0,
74362
74922
  startForegroundRun: void 0,
74363
74923
  abortForegroundRun: () => false,
74364
74924
  openLiveSidebar: () => void 0,
@@ -74405,10 +74965,10 @@ async function importCrashRecovery() {
74405
74965
  }
74406
74966
  return _cachedCrashRecovery;
74407
74967
  }
74408
- function purgeStaleActiveRunIndexSyncIfLoaded() {
74968
+ function purgeStaleActiveRunIndexSyncIfLoaded(currentSessionId) {
74409
74969
  if (!_cachedCrashRecovery) return;
74410
74970
  try {
74411
- _cachedCrashRecovery.purgeStaleActiveRunIndex();
74971
+ _cachedCrashRecovery.purgeStaleActiveRunIndex(3e5, Date.now(), currentSessionId);
74412
74972
  } catch (error) {
74413
74973
  logInternalError("register.cleanupRuntime.purgeStale", error);
74414
74974
  }
@@ -74761,7 +75321,7 @@ async function configureObservabilityImpl(pi, ctx, extCtx) {
74761
75321
  getManifestCache: ctx.getManifestCache,
74762
75322
  notifyOperator: ctx.notifyOperator,
74763
75323
  isCleanedUp: () => ctx.cleanedUp,
74764
- reconcileStaleRuns: (cwd, cache3) => reconcileAllStaleRuns(cwd, cache3),
75324
+ reconcileStaleRuns: (cwd, cache3, currentSessionId) => reconcileAllStaleRuns(cwd, cache3, void 0, currentSessionId),
74765
75325
  reconcileOrphanedTempWorkspaces: (now, opts) => reconcileOrphanedTempWorkspaces(now, opts),
74766
75326
  cleanupOrphanTempDirs,
74767
75327
  cleanupLegacyOrphanTempDirs,
@@ -74805,9 +75365,9 @@ import * as fsp3 from "node:fs/promises";
74805
75365
  import * as net2 from "node:net";
74806
75366
 
74807
75367
  // src/runtime/broker/crew-broker-tokens.ts
74808
- import { randomUUID as randomUUID8, timingSafeEqual as timingSafeEqual3 } from "node:crypto";
75368
+ import { randomUUID as randomUUID9, timingSafeEqual as timingSafeEqual3 } from "node:crypto";
74809
75369
  function newBrokerToken() {
74810
- return randomUUID8();
75370
+ return randomUUID9();
74811
75371
  }
74812
75372
  var BrokerTokenRegistry = class _BrokerTokenRegistry {
74813
75373
  map = /* @__PURE__ */ new Map();
@@ -76096,7 +76656,10 @@ function safeStringify(value) {
76096
76656
  // src/extension/registration/lifecycle-handlers.ts
76097
76657
  init_child_pi();
76098
76658
  init_live_agent_manager();
76659
+ init_model_fallback();
76099
76660
  init_pi_args();
76661
+ init_provider_quota();
76662
+ init_session_model();
76100
76663
  init_orphan_worker_registry();
76101
76664
  init_crash_recovery();
76102
76665
  init_scheduler();
@@ -76312,9 +76875,12 @@ function registerCrewAutocomplete(ctx) {
76312
76875
  // src/extension/session-summary.ts
76313
76876
  init_crew_agent_records();
76314
76877
  init_process_status();
76878
+ init_session_utils();
76315
76879
  init_run_index();
76316
76880
  function notifyActiveRuns(ctx) {
76881
+ const sid = extractSessionId(ctx);
76317
76882
  const active = listRuns(ctx.cwd).filter((run) => {
76883
+ if (sid && run.ownerSessionId && run.ownerSessionId !== sid) return false;
76318
76884
  if (run.status !== "queued" && run.status !== "planning" && run.status !== "running") return false;
76319
76885
  const agents = readCrewAgents(run);
76320
76886
  return isDisplayActiveRun(run, agents);
@@ -76379,6 +76945,20 @@ function installSessionLifecycleHandlers(pi, ctx) {
76379
76945
  installSessionShutdownHandler(pi, ctx);
76380
76946
  installSessionStartHandler(pi, ctx);
76381
76947
  installSessionBeforeSwitchHandler(pi, ctx);
76948
+ installModelTrackingHandlers(pi);
76949
+ }
76950
+ function installModelTrackingHandlers(pi) {
76951
+ pi.on("model_select", (event) => {
76952
+ noteSessionModel(event.model);
76953
+ });
76954
+ pi.on("thinking_level_select", (event) => {
76955
+ noteSessionThinking(event.level);
76956
+ });
76957
+ pi.on("after_provider_response", (event) => {
76958
+ const model = currentSessionModel();
76959
+ const provider = model ? providerOfModelRef(model) : void 0;
76960
+ if (provider) noteProviderResponse(provider, event.status, event.headers);
76961
+ });
76382
76962
  }
76383
76963
  function installSessionShutdownHandler(pi, ctx) {
76384
76964
  pi.on("session_shutdown", (event) => {
@@ -76408,6 +76988,7 @@ function installSessionBeforeSwitchHandler(pi, ctx) {
76408
76988
  ctx.lifecycleState.deliveryCoordinator?.deactivate();
76409
76989
  resetPowerbarDedupState();
76410
76990
  stopAsyncRunNotifier(ctx.notifierState);
76991
+ clearProviderQuotaCache();
76411
76992
  ctx.stopSessionBoundSubagents();
76412
76993
  });
76413
76994
  }
@@ -76428,6 +77009,8 @@ function installSessionStartHandler(pi, ctx) {
76428
77009
  ctx.sessionGeneration++;
76429
77010
  const ownerGeneration = ctx.sessionGeneration;
76430
77011
  ctx.currentCtx = extensionCtx;
77012
+ noteSessionModel(extensionCtx.model, "session_start");
77013
+ noteSessionThinking(extensionCtx.thinkingLevel);
76431
77014
  if (!ctx.crewAutocompleteRegistered) {
76432
77015
  ctx.crewAutocompleteRegistered = true;
76433
77016
  registerCrewAutocomplete(extensionCtx);
@@ -76537,7 +77120,7 @@ async function runDeferredSessionCleanup(pi, ctx, ownerGeneration, currentSessio
76537
77120
  logInternalError("register.sessionStart.orphanWorkers", error);
76538
77121
  }
76539
77122
  try {
76540
- const { purged } = purgeStaleActiveRunIndexFn();
77123
+ const { purged } = purgeStaleActiveRunIndexFn(3e5, Date.now(), currentSessionId);
76541
77124
  if (purged.length > 0) {
76542
77125
  ctx.notifyOperator({
76543
77126
  id: `active_index_purge`,
@@ -76551,7 +77134,7 @@ async function runDeferredSessionCleanup(pi, ctx, ownerGeneration, currentSessio
76551
77134
  logInternalError("register.sessionStart.globalIndexPurge", error);
76552
77135
  }
76553
77136
  try {
76554
- const staleResults = reconcileAllStaleRuns(extensionCtx.cwd, ctx.getManifestCache(extensionCtx.cwd)) ?? [];
77137
+ const staleResults = reconcileAllStaleRuns(extensionCtx.cwd, ctx.getManifestCache(extensionCtx.cwd), Date.now(), currentSessionId) ?? [];
76555
77138
  if (staleResults.length > 0) {
76556
77139
  ctx.notifyOperator({
76557
77140
  id: "stale_reconcile",
@@ -76671,6 +77254,9 @@ function setupCrewScheduler(pi, ctx, extensionCtx, sessionId) {
76671
77254
  });
76672
77255
  return crewScheduler;
76673
77256
  }
77257
+ function filterManifestsForHealthNotifications(manifests, currentSessionId) {
77258
+ return manifests.filter((run) => !run.ownerSessionId || run.ownerSessionId === currentSessionId);
77259
+ }
76674
77260
  function setupRenderLoop(pi, ctx, extensionCtx, loadedConfig) {
76675
77261
  ctx.disposeRenderSchedulerSubscriptions();
76676
77262
  ctx.renderScheduler?.dispose();
@@ -76764,11 +77350,8 @@ function setupRenderLoop(pi, ctx, extensionCtx, loadedConfig) {
76764
77350
  ctx.widgetState.notificationCount ?? 0,
76765
77351
  manifests
76766
77352
  );
76767
- const currentSessionGen = ctx.sessionGeneration;
76768
- const currentSessionId = ctx.currentCtx ? ctx.currentCtx.sessionId : void 0;
76769
- const sessionManifests = manifests.filter(
76770
- (run) => !run.ownerSessionId || run.ownerSessionId === currentSessionId || run.ownerSessionGeneration === currentSessionGen
76771
- );
77353
+ const currentSessionId = ctx.currentCtx?.sessionManager?.getSessionId();
77354
+ const sessionManifests = filterManifestsForHealthNotifications(manifests, currentSessionId);
76772
77355
  const now = Date.now();
76773
77356
  for (const run of sessionManifests) {
76774
77357
  if (run.status !== "running") continue;
@@ -76998,6 +77581,7 @@ init_powerbar_publisher();
76998
77581
  init_widget();
76999
77582
  init_internal_error();
77000
77583
  init_paths();
77584
+ init_session_utils();
77001
77585
  init_async_notifier();
77002
77586
  init_team_tool2();
77003
77587
  init_lifecycle();
@@ -77021,6 +77605,7 @@ function buildCleanupSessionResourcesOnly(ctx) {
77021
77605
  return () => {
77022
77606
  if (ctx.cleanedUp) return;
77023
77607
  ctx.cleanedUp = true;
77608
+ const sid = extractSessionId(ctx.currentCtx);
77024
77609
  if (ctx.preloadTimer) {
77025
77610
  clearTimeout(ctx.preloadTimer);
77026
77611
  ctx.preloadTimer = void 0;
@@ -77032,7 +77617,7 @@ function buildCleanupSessionResourcesOnly(ctx) {
77032
77617
  ctx.stopSessionBoundSubagents();
77033
77618
  ctx.crewScheduler?.stop();
77034
77619
  stopAsyncRunNotifier(ctx.notifierState);
77035
- ctx.purgeStaleActiveRunIndexSyncIfLoaded();
77620
+ ctx.purgeStaleActiveRunIndexSyncIfLoaded(sid);
77036
77621
  stopCrewWidget(ctx.currentCtx, ctx.widgetState, ctx.currentCtx ? loadConfig(ctx.currentCtx.cwd).config.ui : void 0);
77037
77622
  clearPiCrewPowerbar(ctx.pi.events);
77038
77623
  disposePowerbarCoalescer();
@@ -77064,6 +77649,7 @@ function buildCleanupRuntime(ctx) {
77064
77649
  return () => {
77065
77650
  if (ctx.cleanedUp) return;
77066
77651
  ctx.cleanedUp = true;
77652
+ const sid = extractSessionId(ctx.currentCtx);
77067
77653
  if (ctx.preloadTimer) {
77068
77654
  clearTimeout(ctx.preloadTimer);
77069
77655
  ctx.preloadTimer = void 0;
@@ -77078,7 +77664,7 @@ function buildCleanupRuntime(ctx) {
77078
77664
  stopAllWatchdogs();
77079
77665
  ctx.crewScheduler?.stop();
77080
77666
  stopAsyncRunNotifier(ctx.notifierState);
77081
- ctx.purgeStaleActiveRunIndexSyncIfLoaded();
77667
+ ctx.purgeStaleActiveRunIndexSyncIfLoaded(sid);
77082
77668
  stopCrewWidget(ctx.currentCtx, ctx.widgetState, ctx.currentCtx ? loadConfig(ctx.currentCtx.cwd).config.ui : void 0);
77083
77669
  clearPiCrewPowerbar(ctx.pi.events);
77084
77670
  disposePowerbarCoalescer();
@@ -77155,7 +77741,7 @@ function createCompletionCoalescer(pi, ctx) {
77155
77741
  const f = ctx.subagentManager.getRecord(c.agentId);
77156
77742
  const p = ctx.currentCtx ? readPersistedSubagentRecord(ctx.currentCtx.cwd, c.agentId) : void 0;
77157
77743
  if (f?.resultConsumed || p?.resultConsumed) return false;
77158
- if (!ctx.isOwnerSessionCurrent(f?.ownerSessionGeneration ?? c.ownerGen)) return false;
77744
+ if (!ctx.isOwnerSessionCurrent(f?.ownerSessionGeneration ?? c.ownerGen, f?.ownerSessionId)) return false;
77159
77745
  return true;
77160
77746
  };
77161
77747
  const flush = () => {
@@ -77263,7 +77849,7 @@ function onTerminalStatus(pi, ctx, record, coalescer) {
77263
77849
  });
77264
77850
  }
77265
77851
  if (!record.background) return;
77266
- if (!ctx.isOwnerSessionCurrent(record.ownerSessionGeneration)) return;
77852
+ if (!ctx.isOwnerSessionCurrent(record.ownerSessionGeneration, record.ownerSessionId)) return;
77267
77853
  if (record.status !== "completed" && record.status !== "failed" && record.status !== "cancelled" && record.status !== "blocked" && record.status !== "error")
77268
77854
  return;
77269
77855
  const agentId = record.id;
@@ -77279,7 +77865,7 @@ function onTerminalStatus(pi, ctx, record, coalescer) {
77279
77865
  const fresh = ctx.subagentManager.getRecord(agentId);
77280
77866
  const persisted = ctx.currentCtx ? readPersistedSubagentRecord(ctx.currentCtx.cwd, agentId) : void 0;
77281
77867
  if (fresh?.resultConsumed || persisted?.resultConsumed) return;
77282
- if (!ctx.isOwnerSessionCurrent(fresh?.ownerSessionGeneration ?? ownerGen)) return;
77868
+ if (!ctx.isOwnerSessionCurrent(fresh?.ownerSessionGeneration ?? ownerGen, fresh?.ownerSessionId)) return;
77283
77869
  const member = {
77284
77870
  id: agentId,
77285
77871
  description: agentDescription,
@@ -77314,7 +77900,8 @@ function onTerminalStatus(pi, ctx, record, coalescer) {
77314
77900
  }
77315
77901
  function onInternalEvent(pi, ctx, event, payload) {
77316
77902
  const ownerGeneration = typeof payload?.ownerSessionGeneration === "number" ? payload.ownerSessionGeneration : void 0;
77317
- if (ownerGeneration !== void 0 && !ctx.isOwnerSessionCurrent(ownerGeneration)) return;
77903
+ const ownerSessionId = typeof payload?.ownerSessionId === "string" ? payload.ownerSessionId : void 0;
77904
+ if (ownerGeneration !== void 0 && !ctx.isOwnerSessionCurrent(ownerGeneration, ownerSessionId)) return;
77318
77905
  if (event === "subagent.stuck-blocked") {
77319
77906
  const p = payload;
77320
77907
  const id = typeof p.id === "string" ? p.id : "unknown";
@@ -77428,6 +78015,7 @@ function registerSubagentTools(pi, subagentManager, options = {}) {
77428
78015
  spawnOptions.ownerSessionGeneration = options.ownerSessionGeneration?.();
77429
78016
  if (!spawnOptions.prompt.trim()) return subagentToolResult(t("agent.requiresPrompt"), {}, true);
77430
78017
  const ctxWithSession = withSessionId(ctx);
78018
+ spawnOptions.ownerSessionId = ctxWithSession.sessionId;
77431
78019
  const runner = async (currentOptions, childSignal) => handleTeamTool6(
77432
78020
  {
77433
78021
  action: "run",
@@ -77549,6 +78137,10 @@ function registerSubagentTools(pi, subagentManager, options = {}) {
77549
78137
  const inMemory = subagentManager.getRecord(p.agent_id);
77550
78138
  const record = inMemory ?? readPersistedSubagentRecord(ctx.cwd, p.agent_id);
77551
78139
  if (!record) return subagentToolResult(t("result.notFound", { id: p.agent_id }), {}, true);
78140
+ const currentSessionId = withSessionId(ctx).sessionId;
78141
+ if (record.ownerSessionId && record.ownerSessionId !== currentSessionId) {
78142
+ return subagentToolResult("Agent belongs to another session.", {}, true);
78143
+ }
77552
78144
  let current = refreshPersistedSubagentRecord(ctx, record);
77553
78145
  if (inMemory && current !== inMemory) Object.assign(inMemory, current);
77554
78146
  if (!inMemory && !current.runId && (current.status === "running" || current.status === "queued")) {
@@ -77599,9 +78191,11 @@ function registerSubagentTools(pi, subagentManager, options = {}) {
77599
78191
  }
77600
78192
  const output = readSubagentRunResult(ctx, current);
77601
78193
  if (current.status !== "running" && current.status !== "queued" && current.status !== "blocked") {
77602
- current.resultConsumed = true;
77603
- if (inMemory) inMemory.resultConsumed = true;
77604
- savePersistedSubagentRecord(ctx.cwd, current);
78194
+ if (!current.ownerSessionId || current.ownerSessionId === currentSessionId) {
78195
+ current.resultConsumed = true;
78196
+ if (inMemory) inMemory.resultConsumed = true;
78197
+ savePersistedSubagentRecord(ctx.cwd, current);
78198
+ }
77605
78199
  }
77606
78200
  const text = [
77607
78201
  p.verbose ? formatSubagentRecord(current) : void 0,