pi-crew 0.9.59 → 0.9.61

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.
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
  };
@@ -13024,9 +13044,9 @@ var init_cancellation_token = __esm({
13024
13044
  this.throwIfCancelled();
13025
13045
  this.#lastHeartbeatAt = this.#now().toISOString();
13026
13046
  this.#lastHeartbeatStage = stage;
13027
- const state = this.state();
13028
- this.#onHeartbeat?.(state);
13029
- return state;
13047
+ const state2 = this.state();
13048
+ this.#onHeartbeat?.(state2);
13049
+ return state2;
13030
13050
  }
13031
13051
  throwIfCancelled() {
13032
13052
  if (this.aborted) throw new CrewCancellationError(this.#reason ?? cancellationReasonFromUnknown(this.#controller.signal.reason));
@@ -13954,12 +13974,13 @@ function buildPiWorkerArgs(input) {
13954
13974
  const args = ["--mode", "json", "-p"];
13955
13975
  if (input.sessionEnabled === false) args.push("--no-session");
13956
13976
  const resolvedModel = input.model ?? input.agent.model;
13977
+ const effectiveThinking = input.thinkingOverride ?? input.agent.thinking;
13957
13978
  if (resolvedModel) {
13958
- const modelWithThinking = applyThinkingSuffix(resolvedModel, input.agent.thinking);
13979
+ const modelWithThinking = applyThinkingSuffix(resolvedModel, effectiveThinking);
13959
13980
  if (modelWithThinking) args.push("--model", modelWithThinking);
13960
13981
  }
13961
- if (!resolvedModel && input.agent.thinking && input.agent.thinking !== "off" && isValidThinkingLevel(input.agent.thinking)) {
13962
- args.push("--thinking", input.agent.thinking);
13982
+ if (!resolvedModel && effectiveThinking && effectiveThinking !== "off" && isValidThinkingLevel(effectiveThinking)) {
13983
+ args.push("--thinking", effectiveThinking);
13963
13984
  }
13964
13985
  const policy = resolveToolPolicy(input.agent, input.role);
13965
13986
  const explicitTools = policy.tools;
@@ -14925,7 +14946,8 @@ function prepareSpawnContext(input, effectiveTask) {
14925
14946
  sessionEnabled: true,
14926
14947
  maxDepth: input.maxDepth,
14927
14948
  skillPaths: input.skillPaths,
14928
- role: input.role
14949
+ role: input.role,
14950
+ thinkingOverride: input.thinkingOverride
14929
14951
  });
14930
14952
  if (input.steeringFile) built.env.PI_CREW_STEERING_FILE = input.steeringFile;
14931
14953
  if (input.brokerSpawn?.socketPath && input.brokerSpawn.token) {
@@ -17843,45 +17865,45 @@ function readJsonlTail(filePath, tailBytes) {
17843
17865
  }
17844
17866
  }
17845
17867
  }
17846
- function readLinesSince(filePath, state) {
17868
+ function readLinesSince(filePath, state2) {
17847
17869
  let fd;
17848
17870
  try {
17849
17871
  fd = fs24.openSync(filePath, "r");
17850
17872
  } catch {
17851
17873
  return {
17852
17874
  lines: [],
17853
- state: { byteOffset: state.byteOffset, lineCount: state.lineCount },
17875
+ state: { byteOffset: state2.byteOffset, lineCount: state2.lineCount },
17854
17876
  eof: true
17855
17877
  };
17856
17878
  }
17857
17879
  try {
17858
17880
  const stat2 = fs24.fstatSync(fd);
17859
17881
  const fileSize = stat2.size;
17860
- if (fileSize <= state.byteOffset) {
17882
+ if (fileSize <= state2.byteOffset) {
17861
17883
  return {
17862
17884
  lines: [],
17863
- state: { byteOffset: fileSize, lineCount: state.lineCount },
17885
+ state: { byteOffset: fileSize, lineCount: state2.lineCount },
17864
17886
  eof: true
17865
17887
  };
17866
17888
  }
17867
- const bytesToRead = fileSize - state.byteOffset;
17889
+ const bytesToRead = fileSize - state2.byteOffset;
17868
17890
  const buf = Buffer.alloc(bytesToRead);
17869
17891
  let totalRead = 0;
17870
17892
  while (totalRead < bytesToRead) {
17871
17893
  const chunkSize = Math.min(CHUNK_SIZE, bytesToRead - totalRead);
17872
- const bytesRead = fs24.readSync(fd, buf, totalRead, chunkSize, state.byteOffset + totalRead);
17894
+ const bytesRead = fs24.readSync(fd, buf, totalRead, chunkSize, state2.byteOffset + totalRead);
17873
17895
  if (bytesRead === 0) break;
17874
17896
  totalRead += bytesRead;
17875
17897
  }
17876
17898
  const content = buf.toString("utf-8", 0, totalRead);
17877
17899
  const lines = [];
17878
- let lineCount = state.lineCount;
17879
- let committedOffset = state.byteOffset;
17900
+ let lineCount = state2.lineCount;
17901
+ let committedOffset = state2.byteOffset;
17880
17902
  let searchFrom = 0;
17881
17903
  let newlineIdx;
17882
17904
  while ((newlineIdx = content.indexOf("\n", searchFrom)) !== -1) {
17883
17905
  const lineText = content.slice(searchFrom, newlineIdx);
17884
- committedOffset = state.byteOffset + newlineIdx + 1;
17906
+ committedOffset = state2.byteOffset + newlineIdx + 1;
17885
17907
  searchFrom = newlineIdx + 1;
17886
17908
  if (lineText.length > 0) {
17887
17909
  lines.push(lineText);
@@ -17903,8 +17925,8 @@ function readLinesSince(filePath, state) {
17903
17925
  }
17904
17926
  }
17905
17927
  }
17906
- function readJsonlSince(filePath, state) {
17907
- const result4 = readLinesSince(filePath, state);
17928
+ function readJsonlSince(filePath, state2) {
17929
+ const result4 = readLinesSince(filePath, state2);
17908
17930
  const items = [];
17909
17931
  for (const line4 of result4.lines) {
17910
17932
  try {
@@ -20969,12 +20991,12 @@ function readDeliveryState(manifest) {
20969
20991
  if (obj.messages && typeof obj.messages === "object" && !Array.isArray(obj.messages)) {
20970
20992
  for (const [id, status] of Object.entries(obj.messages)) if (isStatus(status)) messages[id] = status;
20971
20993
  }
20972
- const state = {
20994
+ const state2 = {
20973
20995
  messages,
20974
20996
  updatedAt: typeof obj.updatedAt === "string" ? obj.updatedAt : (/* @__PURE__ */ new Date()).toISOString()
20975
20997
  };
20976
- setDeliveryCacheEntry(filePath, { mtimeMs: stat2.mtimeMs, state });
20977
- return state;
20998
+ setDeliveryCacheEntry(filePath, { mtimeMs: stat2.mtimeMs, state: state2 });
20999
+ return state2;
20978
21000
  } catch (error) {
20979
21001
  const quarantinePath = `${filePath}.corrupt-${Date.now()}`;
20980
21002
  try {
@@ -20989,25 +21011,25 @@ function readDeliveryState(manifest) {
20989
21011
  return { messages: {}, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
20990
21012
  }
20991
21013
  }
20992
- function writeDeliveryState(manifest, state, options) {
21014
+ function writeDeliveryState(manifest, state2, options) {
20993
21015
  ensureRunMailbox(manifest);
20994
21016
  const MAX_DELIVERY_MESSAGES = 1e4;
20995
- if (Object.keys(state.messages).length > MAX_DELIVERY_MESSAGES) {
20996
- 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]) => {
20997
21019
  const order = { queued: 0, delivered: 1, acknowledged: 2 };
20998
21020
  return (order[a] ?? 3) - (order[b] ?? 3);
20999
21021
  });
21000
21022
  const trimmed = sorted.slice(0, MAX_DELIVERY_MESSAGES);
21001
- state.messages = Object.fromEntries(trimmed);
21023
+ state2.messages = Object.fromEntries(trimmed);
21002
21024
  }
21003
21025
  const filePath = deliveryFile(manifest, true);
21004
- atomicWriteFile(filePath, `${JSON.stringify(redactSecrets(state), null, 2)}
21026
+ atomicWriteFile(filePath, `${JSON.stringify(redactSecrets(state2), null, 2)}
21005
21027
  `, {
21006
21028
  durability: options?.durability ?? "best-effort"
21007
21029
  });
21008
21030
  try {
21009
21031
  const postStat = fs29.statSync(filePath);
21010
- setDeliveryCacheEntry(filePath, { mtimeMs: postStat.mtimeMs, state });
21032
+ setDeliveryCacheEntry(filePath, { mtimeMs: postStat.mtimeMs, state: state2 });
21011
21033
  } catch {
21012
21034
  deliveryCache.delete(filePath);
21013
21035
  }
@@ -21622,6 +21644,713 @@ var init_runtime_resolver = __esm({
21622
21644
  }
21623
21645
  });
21624
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
+
21625
22354
  // src/extension/team-tool/config-patch.ts
21626
22355
  function sanitizeObject2(obj) {
21627
22356
  if (obj === null || obj === void 0 || typeof obj !== "object") return obj;
@@ -21925,14 +22654,14 @@ var init_i18n = __esm({
21925
22654
  });
21926
22655
 
21927
22656
  // src/runtime/async-marker.ts
21928
- import * as fs31 from "node:fs";
21929
- import * as path27 from "node:path";
22657
+ import * as fs32 from "node:fs";
22658
+ import * as path28 from "node:path";
21930
22659
  function asyncStartMarkerPath(manifest) {
21931
- return path27.join(manifest.stateRoot, "async.pid");
22660
+ return path28.join(manifest.stateRoot, "async.pid");
21932
22661
  }
21933
22662
  function hasAsyncStartMarker(manifest) {
21934
22663
  try {
21935
- const raw = JSON.parse(fs31.readFileSync(asyncStartMarkerPath(manifest), "utf-8"));
22664
+ const raw = JSON.parse(fs32.readFileSync(asyncStartMarkerPath(manifest), "utf-8"));
21936
22665
  return typeof raw.pid === "number" && Number.isInteger(raw.pid) && raw.pid > 0 && typeof raw.startedAt === "string" && raw.startedAt.length > 0;
21937
22666
  } catch {
21938
22667
  return false;
@@ -22045,8 +22774,8 @@ var init_process_status = __esm({
22045
22774
  });
22046
22775
 
22047
22776
  // src/runtime/run-tracker.ts
22048
- import * as fs32 from "node:fs";
22049
- import * as path28 from "node:path";
22777
+ import * as fs33 from "node:fs";
22778
+ import * as path29 from "node:path";
22050
22779
  function registerRunPromise(runId) {
22051
22780
  let resolve25;
22052
22781
  let reject;
@@ -22094,8 +22823,8 @@ async function waitForRun(runId, cwd, options = {}) {
22094
22823
  let attempt = 0;
22095
22824
  while (Date.now() < deadline) {
22096
22825
  if (attempt === 0) {
22097
- const runDir = path28.join(projectCrewRoot(cwd), "state", "runs", runId);
22098
- if (!fs32.existsSync(runDir)) {
22826
+ const runDir = path29.join(projectCrewRoot(cwd), "state", "runs", runId);
22827
+ if (!fs33.existsSync(runDir)) {
22099
22828
  throw new Error(`Run ${runId} not found. No run directory at ${runDir}`);
22100
22829
  }
22101
22830
  }
@@ -22245,13 +22974,13 @@ var init_crew_hooks = __esm({
22245
22974
  });
22246
22975
 
22247
22976
  // src/runtime/skill-effectiveness.ts
22248
- import { existsSync as existsSync23, mkdirSync as mkdirSync14, readFileSync as readFileSync22, writeFileSync as writeFileSync4 } from "node:fs";
22249
- 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";
22250
22979
  function getSkillMetricsPath(cwd, runId) {
22251
- return join28(projectCrewRoot(cwd), `state/runs/${runId}/skill-metrics.jsonl`);
22980
+ return join29(projectCrewRoot(cwd), `state/runs/${runId}/skill-metrics.jsonl`);
22252
22981
  }
22253
22982
  function getSkillActivationsPath(cwd, runId) {
22254
- return join28(projectCrewRoot(cwd), `state/runs/${runId}/skill-activations.jsonl`);
22983
+ return join29(projectCrewRoot(cwd), `state/runs/${runId}/skill-activations.jsonl`);
22255
22984
  }
22256
22985
  function ensureSkillMetricsDir(cwd, runId) {
22257
22986
  const dir = dirname16(getSkillMetricsPath(cwd, runId));
@@ -22308,7 +23037,7 @@ function getSkillActivations(cwd, runId) {
22308
23037
  if (!existsSync23(path89)) {
22309
23038
  return [];
22310
23039
  }
22311
- const content = readFileSync22(path89, "utf-8");
23040
+ const content = readFileSync23(path89, "utf-8");
22312
23041
  if (!content.trim()) {
22313
23042
  return [];
22314
23043
  }
@@ -22486,10 +23215,10 @@ __export(skill_instructions_exports, {
22486
23215
  resetSkillCacheStats: () => resetSkillCacheStats,
22487
23216
  resolveTaskSkillNames: () => resolveTaskSkillNames
22488
23217
  });
22489
- import * as fs33 from "node:fs";
22490
- import * as path29 from "node:path";
23218
+ import * as fs34 from "node:fs";
23219
+ import * as path30 from "node:path";
22491
23220
  import { fileURLToPath as fileURLToPath4 } from "node:url";
22492
- import * as os9 from "node:os";
23221
+ import * as os10 from "node:os";
22493
23222
  function isValidSkillName(name) {
22494
23223
  return name.length > 0 && name.length <= MAX_SKILL_NAME_CHARS && isSafePathId(name);
22495
23224
  }
@@ -22539,18 +23268,18 @@ function candidateSkillDirs(cwd) {
22539
23268
  // F6 (v0.7.9): same five roots as discover-skills, in the same precedence
22540
23269
  // order. The first hit wins, so a project `.pi/skills/foo/SKILL.md`
22541
23270
  // overrides both the bundled `foo` and any legacy `<cwd>/skills/foo`.
22542
- { root: path29.resolve(cwd, ".pi", "skills"), source: "project-pi" },
23271
+ { root: path30.resolve(cwd, ".pi", "skills"), source: "project-pi" },
22543
23272
  {
22544
- root: path29.resolve(cwd, ".agents", "skills"),
23273
+ root: path30.resolve(cwd, ".agents", "skills"),
22545
23274
  source: "project-agents"
22546
23275
  },
22547
- { root: path29.resolve(cwd, "skills"), source: "project" },
22548
- { 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" },
22549
23278
  {
22550
- root: path29.join(os9.homedir(), ".agents", "skills"),
23279
+ root: path30.join(os10.homedir(), ".agents", "skills"),
22551
23280
  source: "user-agents"
22552
23281
  },
22553
- { root: path29.join(os9.homedir(), ".pi", "skills"), source: "user-pi" }
23282
+ { root: path30.join(os10.homedir(), ".pi", "skills"), source: "user-pi" }
22554
23283
  ];
22555
23284
  }
22556
23285
  function rememberSkill(key, value) {
@@ -22588,7 +23317,7 @@ function _setSkillCacheMaxEntriesForTesting(max) {
22588
23317
  }
22589
23318
  function cachedSkillFresh(value) {
22590
23319
  try {
22591
- const stat2 = fs33.statSync(value.path);
23320
+ const stat2 = fs34.statSync(value.path);
22592
23321
  return stat2.mtimeMs === value.mtimeMs && stat2.size === value.size;
22593
23322
  } catch {
22594
23323
  return false;
@@ -22596,7 +23325,7 @@ function cachedSkillFresh(value) {
22596
23325
  }
22597
23326
  function readSkillMarkdown(cwd, name) {
22598
23327
  if (!isValidSkillName(name)) return void 0;
22599
- const cacheKey2 = `${path29.resolve(cwd)}:${name}`;
23328
+ const cacheKey2 = `${path30.resolve(cwd)}:${name}`;
22600
23329
  const cached2 = skillReadCache.get(cacheKey2);
22601
23330
  if (cached2 && cachedSkillFresh(cached2)) {
22602
23331
  skillCacheStats.hits++;
@@ -22607,13 +23336,13 @@ function readSkillMarkdown(cwd, name) {
22607
23336
  skillCacheStats.currentSize = skillReadCache.size;
22608
23337
  for (const entry of candidateSkillDirs(cwd)) {
22609
23338
  try {
22610
- const relative9 = path29.join(name, "SKILL.md");
23339
+ const relative9 = path30.join(name, "SKILL.md");
22611
23340
  const contained = resolveRealContainedPath(entry.root, relative9);
22612
- if (!fs33.existsSync(contained)) continue;
22613
- if (fs33.lstatSync(contained).isSymbolicLink()) continue;
23341
+ if (!fs34.existsSync(contained)) continue;
23342
+ if (fs34.lstatSync(contained).isSymbolicLink()) continue;
22614
23343
  const filePath = resolveRealContainedPath(entry.root, relative9);
22615
- const stat2 = fs33.statSync(filePath);
22616
- const rawContent = fs33.readFileSync(filePath, "utf-8");
23344
+ const stat2 = fs34.statSync(filePath);
23345
+ const rawContent = fs34.readFileSync(filePath, "utf-8");
22617
23346
  return rememberSkill(cacheKey2, {
22618
23347
  path: filePath,
22619
23348
  source: entry.source,
@@ -22682,7 +23411,7 @@ Skill '${safeName}' was selected but no SKILL.md file was found. Continue with t
22682
23411
  if (!pushSection(missing)) omittedCount += 1;
22683
23412
  continue;
22684
23413
  }
22685
- skillPaths.push(path29.dirname(loaded.path));
23414
+ skillPaths.push(path30.dirname(loaded.path));
22686
23415
  const description = frontmatterDescription(loaded.content);
22687
23416
  const source = loaded.source === "project" ? `project:skills/${safeName}` : `package:skills/${safeName}`;
22688
23417
  const weighted = weightedSkills?.find((w) => w.skillId === name);
@@ -22697,7 +23426,7 @@ Skill '${safeName}' was selected but no SKILL.md file was found. Continue with t
22697
23426
  // spec "small instruction + large local reference" pattern, e.g.
22698
23427
  // effective-html's `references/html-effectiveness/`) leave the agent
22699
23428
  // guessing the skill dir. No behavior change for corpus-less skills.
22700
- `Path: ${path29.dirname(loaded.path)}`
23429
+ `Path: ${path30.dirname(loaded.path)}`
22701
23430
  ].filter(Boolean).join("\n");
22702
23431
  const rawContent = loaded.compacted;
22703
23432
  const wrappedContent = `<!-- skill: ${safeName} -->
@@ -22741,7 +23470,7 @@ var init_skill_instructions = __esm({
22741
23470
  init_safe_paths();
22742
23471
  init_skill_effectiveness();
22743
23472
  init_peer_dep();
22744
- 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");
22745
23474
  MAX_SKILL_CHARS = 1500;
22746
23475
  MAX_TOTAL_CHARS = 6e3;
22747
23476
  MAX_SKILL_NAME_CHARS = 80;
@@ -23641,15 +24370,15 @@ var init_anchor = __esm({
23641
24370
  });
23642
24371
 
23643
24372
  // src/runtime/agent-observability.ts
23644
- import * as fs34 from "node:fs";
24373
+ import * as fs35 from "node:fs";
23645
24374
  function readTextTail(filePath, maxBytes = 64e3) {
23646
- if (!fs34.existsSync(filePath)) return { path: filePath, text: "", bytes: 0, truncated: false };
23647
- const stat2 = fs34.statSync(filePath);
24375
+ if (!fs35.existsSync(filePath)) return { path: filePath, text: "", bytes: 0, truncated: false };
24376
+ const stat2 = fs35.statSync(filePath);
23648
24377
  const bytesToRead = Math.min(stat2.size, Math.max(0, maxBytes));
23649
- const fd = fs34.openSync(filePath, "r");
24378
+ const fd = fs35.openSync(filePath, "r");
23650
24379
  try {
23651
24380
  const buffer = Buffer.alloc(bytesToRead);
23652
- fs34.readSync(fd, buffer, 0, bytesToRead, stat2.size - bytesToRead);
24381
+ fs35.readSync(fd, buffer, 0, bytesToRead, stat2.size - bytesToRead);
23653
24382
  return {
23654
24383
  path: filePath,
23655
24384
  text: buffer.toString("utf-8"),
@@ -23657,7 +24386,7 @@ function readTextTail(filePath, maxBytes = 64e3) {
23657
24386
  truncated: stat2.size > bytesToRead
23658
24387
  };
23659
24388
  } finally {
23660
- fs34.closeSync(fd);
24389
+ fs35.closeSync(fd);
23661
24390
  }
23662
24391
  }
23663
24392
  function compactDuration(ms) {
@@ -23696,8 +24425,8 @@ function outputWarning(manifest, agent) {
23696
24425
  if (agent.status !== "completed") return "";
23697
24426
  try {
23698
24427
  const outputPath = agentOutputPath(manifest, agent.taskId);
23699
- if (!fs34.existsSync(outputPath)) return " no-output";
23700
- 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" : "";
23701
24430
  } catch {
23702
24431
  return " no-output";
23703
24432
  }
@@ -23749,9 +24478,9 @@ var init_agent_observability = __esm({
23749
24478
  });
23750
24479
 
23751
24480
  // src/skills/validate.ts
23752
- import * as fs35 from "node:fs";
24481
+ import * as fs36 from "node:fs";
23753
24482
  import { createRequire as createRequire3 } from "node:module";
23754
- import * as path30 from "node:path";
24483
+ import * as path31 from "node:path";
23755
24484
  function getYaml() {
23756
24485
  if (!yamlModule) {
23757
24486
  const require4 = createRequire3(import.meta.url);
@@ -23787,11 +24516,11 @@ function warn(path89, field, reason) {
23787
24516
  }
23788
24517
  function validateSkillFrontmatter(skillDir) {
23789
24518
  const errors2 = [];
23790
- const skillMdPath = path30.join(skillDir, "SKILL.md");
23791
- const derivedName = path30.basename(skillDir);
24519
+ const skillMdPath = path31.join(skillDir, "SKILL.md");
24520
+ const derivedName = path31.basename(skillDir);
23792
24521
  let content;
23793
24522
  try {
23794
- content = fs35.readFileSync(skillMdPath, "utf-8");
24523
+ content = fs36.readFileSync(skillMdPath, "utf-8");
23795
24524
  } catch (e) {
23796
24525
  errors2.push(hard(skillDir, "SKILL.md", `Cannot read SKILL.md: ${e.message}`));
23797
24526
  return { ok: false, errors: errors2 };
@@ -23922,25 +24651,25 @@ var init_validate = __esm({
23922
24651
  });
23923
24652
 
23924
24653
  // src/skills/discover-skills.ts
23925
- import * as fs36 from "node:fs";
23926
- import * as os10 from "node:os";
23927
- 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";
23928
24657
  import { fileURLToPath as fileURLToPath5 } from "node:url";
23929
24658
  function listSkillDirs(cwd) {
23930
24659
  return [
23931
24660
  { root: PACKAGE_SKILLS_DIR2, source: "package" },
23932
- { root: path31.resolve(cwd, ".pi", "skills"), source: "project-pi" },
24661
+ { root: path32.resolve(cwd, ".pi", "skills"), source: "project-pi" },
23933
24662
  {
23934
- root: path31.resolve(cwd, ".agents", "skills"),
24663
+ root: path32.resolve(cwd, ".agents", "skills"),
23935
24664
  source: "project-agents"
23936
24665
  },
23937
- { root: path31.resolve(cwd, "skills"), source: "project" },
23938
- { 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" },
23939
24668
  {
23940
- root: path31.join(os10.homedir(), ".agents", "skills"),
24669
+ root: path32.join(os11.homedir(), ".agents", "skills"),
23941
24670
  source: "user-agents"
23942
24671
  },
23943
- { root: path31.join(os10.homedir(), ".pi", "skills"), source: "user-pi" }
24672
+ { root: path32.join(os11.homedir(), ".pi", "skills"), source: "user-pi" }
23944
24673
  ];
23945
24674
  }
23946
24675
  function readDescription(content) {
@@ -23965,29 +24694,29 @@ function discoverSkills(cwd) {
23965
24694
  const results = [];
23966
24695
  const diagnostics = [];
23967
24696
  for (const dir of listSkillDirs(cwd)) {
23968
- if (!fs36.existsSync(dir.root)) continue;
24697
+ if (!fs37.existsSync(dir.root)) continue;
23969
24698
  try {
23970
- for (const entry of fs36.readdirSync(dir.root, {
24699
+ for (const entry of fs37.readdirSync(dir.root, {
23971
24700
  withFileTypes: true
23972
24701
  })) {
23973
24702
  if (!entry.isDirectory()) continue;
23974
24703
  if (!isSafePathId(entry.name)) continue;
23975
- const skillDirPath = path31.join(dir.root, entry.name);
24704
+ const skillDirPath = path32.join(dir.root, entry.name);
23976
24705
  try {
23977
- if (fs36.lstatSync(skillDirPath).isSymbolicLink()) continue;
24706
+ if (fs37.lstatSync(skillDirPath).isSymbolicLink()) continue;
23978
24707
  } catch {
23979
24708
  continue;
23980
24709
  }
23981
- const skillMdRelative = path31.join(entry.name, "SKILL.md");
24710
+ const skillMdRelative = path32.join(entry.name, "SKILL.md");
23982
24711
  let skillMdPath;
23983
24712
  try {
23984
24713
  skillMdPath = resolveContainedPath(dir.root, skillMdRelative);
23985
24714
  } catch {
23986
24715
  continue;
23987
24716
  }
23988
- if (!fs36.existsSync(skillMdPath)) continue;
24717
+ if (!fs37.existsSync(skillMdPath)) continue;
23989
24718
  try {
23990
- if (fs36.lstatSync(skillMdPath).isSymbolicLink()) continue;
24719
+ if (fs37.lstatSync(skillMdPath).isSymbolicLink()) continue;
23991
24720
  } catch {
23992
24721
  continue;
23993
24722
  }
@@ -23999,12 +24728,12 @@ function discoverSkills(cwd) {
23999
24728
  skillMdPath = readPath;
24000
24729
  } catch {
24001
24730
  }
24002
- const content = fs36.readFileSync(readPath, "utf-8");
24731
+ const content = fs37.readFileSync(readPath, "utf-8");
24003
24732
  const { description: desc, parseError } = readDescription(content);
24004
24733
  description = desc;
24005
24734
  if (parseError) {
24006
24735
  diagnostics.push({
24007
- path: path31.dirname(skillMdPath),
24736
+ path: path32.dirname(skillMdPath),
24008
24737
  field: "frontmatter",
24009
24738
  reason: parseError,
24010
24739
  severity: "error"
@@ -24026,7 +24755,7 @@ function discoverSkills(cwd) {
24026
24755
  }
24027
24756
  const filtered = [];
24028
24757
  for (const skill of results) {
24029
- const validation = validateSkillFrontmatter(path31.dirname(skill.path));
24758
+ const validation = validateSkillFrontmatter(path32.dirname(skill.path));
24030
24759
  if (validation.ok) {
24031
24760
  filtered.push(skill);
24032
24761
  } else {
@@ -24045,7 +24774,7 @@ var init_discover_skills = __esm({
24045
24774
  init_internal_error();
24046
24775
  init_safe_paths();
24047
24776
  init_validate();
24048
- 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");
24049
24778
  CACHE_TTL_MS = 3e4;
24050
24779
  cache2 = null;
24051
24780
  lastDiagnostics = [];
@@ -24152,15 +24881,15 @@ var init_capability_inventory = __esm({
24152
24881
  });
24153
24882
 
24154
24883
  // src/runtime/foreground-control.ts
24155
- import * as fs37 from "node:fs";
24156
- import * as path32 from "node:path";
24884
+ import * as fs38 from "node:fs";
24885
+ import * as path33 from "node:path";
24157
24886
  function foregroundControlPath(manifest) {
24158
- return path32.join(manifest.stateRoot, "foreground-control.json");
24887
+ return path33.join(manifest.stateRoot, "foreground-control.json");
24159
24888
  }
24160
24889
  function readLastRequest(controlPath) {
24161
- if (!fs37.existsSync(controlPath)) return void 0;
24890
+ if (!fs38.existsSync(controlPath)) return void 0;
24162
24891
  try {
24163
- const parsed = JSON.parse(fs37.readFileSync(controlPath, "utf-8"));
24892
+ const parsed = JSON.parse(fs38.readFileSync(controlPath, "utf-8"));
24164
24893
  return parsed.requests?.at(-1);
24165
24894
  } catch {
24166
24895
  return void 0;
@@ -24184,7 +24913,7 @@ function readForegroundControlStatus(manifest, tasks) {
24184
24913
  function writeForegroundInterruptRequest(manifest, reason = "User requested foreground interrupt.") {
24185
24914
  const controlPath = foregroundControlPath(manifest);
24186
24915
  const lockDir = `${controlPath}.lock`;
24187
- const pidFile = path32.join(lockDir, "pid");
24916
+ const pidFile = path33.join(lockDir, "pid");
24188
24917
  let requests = [];
24189
24918
  const acquireLock = () => {
24190
24919
  const timeout = 5e3;
@@ -24192,7 +24921,7 @@ function writeForegroundInterruptRequest(manifest, reason = "User requested fore
24192
24921
  const start = Date.now();
24193
24922
  while (true) {
24194
24923
  try {
24195
- fs37.mkdirSync(lockDir, { recursive: true });
24924
+ fs38.mkdirSync(lockDir, { recursive: true });
24196
24925
  try {
24197
24926
  atomicWriteFile(pidFile, String(process.pid));
24198
24927
  } catch {
@@ -24201,7 +24930,7 @@ function writeForegroundInterruptRequest(manifest, reason = "User requested fore
24201
24930
  } catch {
24202
24931
  if (Date.now() - start > timeout) {
24203
24932
  try {
24204
- const raw = fs37.readFileSync(pidFile, "utf-8").trim();
24933
+ const raw = fs38.readFileSync(pidFile, "utf-8").trim();
24205
24934
  const ownerPid = Number.parseInt(raw, 10);
24206
24935
  if (!Number.isNaN(ownerPid) && ownerPid !== process.pid) {
24207
24936
  let alive = false;
@@ -24211,7 +24940,7 @@ function writeForegroundInterruptRequest(manifest, reason = "User requested fore
24211
24940
  } catch {
24212
24941
  }
24213
24942
  if (!alive) {
24214
- fs37.rmSync(lockDir, {
24943
+ fs38.rmSync(lockDir, {
24215
24944
  recursive: true,
24216
24945
  force: true
24217
24946
  });
@@ -24228,7 +24957,7 @@ function writeForegroundInterruptRequest(manifest, reason = "User requested fore
24228
24957
  throw err2;
24229
24958
  }
24230
24959
  try {
24231
- const raw = fs37.readFileSync(pidFile, "utf-8").trim();
24960
+ const raw = fs38.readFileSync(pidFile, "utf-8").trim();
24232
24961
  const ownerPid = Number.parseInt(raw, 10);
24233
24962
  if (!Number.isNaN(ownerPid) && ownerPid !== process.pid) {
24234
24963
  let alive = false;
@@ -24238,9 +24967,9 @@ function writeForegroundInterruptRequest(manifest, reason = "User requested fore
24238
24967
  } catch {
24239
24968
  }
24240
24969
  if (!alive) {
24241
- const stat2 = fs37.statSync(lockDir);
24970
+ const stat2 = fs38.statSync(lockDir);
24242
24971
  if (Date.now() - stat2.mtimeMs > staleMs) {
24243
- fs37.rmSync(lockDir, {
24972
+ fs38.rmSync(lockDir, {
24244
24973
  recursive: true,
24245
24974
  force: true
24246
24975
  });
@@ -24256,15 +24985,15 @@ function writeForegroundInterruptRequest(manifest, reason = "User requested fore
24256
24985
  };
24257
24986
  const releaseLock2 = () => {
24258
24987
  try {
24259
- fs37.rmSync(lockDir, { recursive: true, force: true });
24988
+ fs38.rmSync(lockDir, { recursive: true, force: true });
24260
24989
  } catch {
24261
24990
  }
24262
24991
  };
24263
24992
  acquireLock();
24264
24993
  try {
24265
- if (fs37.existsSync(controlPath)) {
24994
+ if (fs38.existsSync(controlPath)) {
24266
24995
  try {
24267
- const parsed = JSON.parse(fs37.readFileSync(controlPath, "utf-8"));
24996
+ const parsed = JSON.parse(fs38.readFileSync(controlPath, "utf-8"));
24268
24997
  requests = Array.isArray(parsed.requests) ? parsed.requests : [];
24269
24998
  } catch {
24270
24999
  requests = [];
@@ -24277,7 +25006,7 @@ function writeForegroundInterruptRequest(manifest, reason = "User requested fore
24277
25006
  reason,
24278
25007
  acknowledged: false
24279
25008
  };
24280
- fs37.mkdirSync(path32.dirname(controlPath), { recursive: true });
25009
+ fs38.mkdirSync(path33.dirname(controlPath), { recursive: true });
24281
25010
  atomicWriteFile(controlPath, `${JSON.stringify({ requests: [...requests, request] }, null, 2)}
24282
25011
  `);
24283
25012
  appendEvent(manifest.eventsPath, {
@@ -24320,7 +25049,7 @@ var init_worker_heartbeat = __esm({
24320
25049
  });
24321
25050
 
24322
25051
  // src/runtime/live-session/live-agent-control.ts
24323
- import * as fs38 from "node:fs";
25052
+ import * as fs39 from "node:fs";
24324
25053
  function liveAgentControlFile(manifest, taskId) {
24325
25054
  return agentStateFile(manifest, taskId, "live-control.jsonl");
24326
25055
  }
@@ -24338,7 +25067,7 @@ function appendLiveAgentControlRequest(manifest, input) {
24338
25067
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
24339
25068
  };
24340
25069
  const filePath = liveAgentControlFile(manifest, input.taskId);
24341
- fs38.appendFileSync(filePath, `${JSON.stringify(request)}
25070
+ fs39.appendFileSync(filePath, `${JSON.stringify(request)}
24342
25071
  `, "utf-8");
24343
25072
  return request;
24344
25073
  }
@@ -24349,8 +25078,8 @@ function readLiveAgentControlRequests(manifest, taskId, cursor = { offset: 0 })
24349
25078
  } catch {
24350
25079
  return { requests: [], cursor };
24351
25080
  }
24352
- if (!fs38.existsSync(filePath)) return { requests: [], cursor };
24353
- const text = fs38.readFileSync(filePath, "utf-8");
25081
+ if (!fs39.existsSync(filePath)) return { requests: [], cursor };
25082
+ const text = fs39.readFileSync(filePath, "utf-8");
24354
25083
  const lines = text.split(/\r?\n/).filter(Boolean);
24355
25084
  const requests = lines.slice(cursor.offset).flatMap((line4) => {
24356
25085
  try {
@@ -25145,379 +25874,6 @@ var init_mcp_proxy = __esm({
25145
25874
  }
25146
25875
  });
25147
25876
 
25148
- // src/runtime/model/model-resolver.ts
25149
- function fuzzyResolveModelId(input, models) {
25150
- const query = input.toLowerCase();
25151
- let bestMatch;
25152
- let bestScore = 0;
25153
- for (const m of models) {
25154
- const id = m.id.toLowerCase();
25155
- const name = (m.name ?? "").toLowerCase();
25156
- const full = `${m.provider}/${m.id}`.toLowerCase();
25157
- let score = 0;
25158
- if (id === query || full === query) {
25159
- score = 100;
25160
- } else if (id.includes(query) || full.includes(query)) {
25161
- score = 60 + query.length / id.length * 30;
25162
- } else if (name.includes(query)) {
25163
- score = 40 + query.length / (name.length || 1) * 20;
25164
- } else if (query.split(/[\s\-/]+/).every((part) => id.includes(part) || name.includes(part) || m.provider.toLowerCase().includes(part))) {
25165
- score = 20;
25166
- }
25167
- if (score > bestScore) {
25168
- bestScore = score;
25169
- bestMatch = m;
25170
- }
25171
- }
25172
- return bestMatch && bestScore >= 20 ? `${bestMatch.provider}/${bestMatch.id}` : void 0;
25173
- }
25174
- var init_model_resolver = __esm({
25175
- "src/runtime/model/model-resolver.ts"() {
25176
- "use strict";
25177
- }
25178
- });
25179
-
25180
- // src/runtime/model/model-scope.ts
25181
- function patternToRegExp(pattern) {
25182
- const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
25183
- return new RegExp(`${escaped.replace(/\*/g, ".*")}`, "i");
25184
- }
25185
- function matchesModelPattern(modelId, pattern) {
25186
- if (!modelId || !pattern) return false;
25187
- const id = modelId.trim();
25188
- const pat = pattern.trim();
25189
- if (!id || !pat) return false;
25190
- if (id.toLowerCase() === pat.toLowerCase()) return true;
25191
- if (pat.includes("*")) {
25192
- try {
25193
- return patternToRegExp(pat).test(id);
25194
- } catch {
25195
- return false;
25196
- }
25197
- }
25198
- return id.toLowerCase().includes(pat.toLowerCase());
25199
- }
25200
- function checkModelScope(modelId, patterns, source) {
25201
- if (!modelId) {
25202
- return {
25203
- inScope: true,
25204
- source,
25205
- model: "",
25206
- reason: "no model specified"
25207
- };
25208
- }
25209
- if (!patterns || patterns.length === 0) {
25210
- return { inScope: true, source, model: modelId };
25211
- }
25212
- for (const pattern of patterns) {
25213
- if (matchesModelPattern(modelId, pattern)) {
25214
- return {
25215
- inScope: true,
25216
- source,
25217
- model: modelId,
25218
- matchedPattern: pattern
25219
- };
25220
- }
25221
- }
25222
- return {
25223
- inScope: false,
25224
- source,
25225
- model: modelId,
25226
- reason: `model "${modelId}" is not in enabledModels allowlist (${patterns.join(", ")})`
25227
- };
25228
- }
25229
- async function readEnabledModelsPatterns(cwd, agentDir) {
25230
- try {
25231
- const mod = await import("@earendil-works/pi-coding-agent").catch(() => null);
25232
- if (!mod) return [];
25233
- const SettingsManagerCtor = mod.SettingsManager;
25234
- if (!SettingsManagerCtor?.create) return [];
25235
- const sm = SettingsManagerCtor.create(cwd, agentDir);
25236
- const patterns = sm.getEnabledModels?.();
25237
- return Array.isArray(patterns) ? patterns : [];
25238
- } catch {
25239
- return [];
25240
- }
25241
- }
25242
- var init_model_scope = __esm({
25243
- "src/runtime/model/model-scope.ts"() {
25244
- "use strict";
25245
- }
25246
- });
25247
-
25248
- // src/runtime/model/model-fallback.ts
25249
- import * as fs39 from "node:fs";
25250
- import * as os11 from "node:os";
25251
- import * as path33 from "node:path";
25252
- function modelInfoFromUnknown(value) {
25253
- if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
25254
- const record = value;
25255
- if (typeof record.provider !== "string" || typeof record.id !== "string") return void 0;
25256
- return {
25257
- provider: record.provider,
25258
- id: record.id,
25259
- fullId: `${record.provider}/${record.id}`
25260
- };
25261
- }
25262
- function availableModelInfosFromRegistry(registry2) {
25263
- if (!registry2 || typeof registry2 !== "object" || Array.isArray(registry2)) return void 0;
25264
- const candidate = registry2;
25265
- const raw = typeof candidate.getAvailable === "function" ? candidate.getAvailable() : typeof candidate.getAll === "function" ? candidate.getAll() : void 0;
25266
- if (!Array.isArray(raw)) return void 0;
25267
- return raw.map(modelInfoFromUnknown).filter((entry) => entry !== void 0);
25268
- }
25269
- function modelStringFromUnknown(model) {
25270
- return modelInfoFromUnknown(model)?.fullId;
25271
- }
25272
- function uniqueModelInfos(models) {
25273
- const seen = /* @__PURE__ */ new Set();
25274
- return models.filter((model) => {
25275
- if (seen.has(model.fullId)) return false;
25276
- seen.add(model.fullId);
25277
- return true;
25278
- });
25279
- }
25280
- function readJsonObject(filePath) {
25281
- try {
25282
- const parsed = JSON.parse(fs39.readFileSync(filePath, "utf-8"));
25283
- return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : void 0;
25284
- } catch {
25285
- return void 0;
25286
- }
25287
- }
25288
- function piAgentDir() {
25289
- const envDir = process.env.PI_CODING_AGENT_DIR?.trim();
25290
- if (envDir) {
25291
- if (envDir === "~") return os11.homedir();
25292
- if (envDir.startsWith("~/")) return path33.join(os11.homedir(), envDir.slice(2));
25293
- return envDir;
25294
- }
25295
- return path33.join(os11.homedir(), ".pi", "agent");
25296
- }
25297
- function settingsModelInfo(settings) {
25298
- if (typeof settings?.defaultProvider !== "string" || typeof settings.defaultModel !== "string") return void 0;
25299
- return {
25300
- provider: settings.defaultProvider,
25301
- id: settings.defaultModel,
25302
- fullId: `${settings.defaultProvider}/${settings.defaultModel}`
25303
- };
25304
- }
25305
- function modelsJsonInfos(modelsJson) {
25306
- if (!modelsJson?.providers || typeof modelsJson.providers !== "object" || Array.isArray(modelsJson.providers)) return [];
25307
- const infos = [];
25308
- for (const [provider, rawConfig] of Object.entries(modelsJson.providers)) {
25309
- if (!rawConfig || typeof rawConfig !== "object" || Array.isArray(rawConfig)) continue;
25310
- const config = rawConfig;
25311
- if (Array.isArray(config.models)) {
25312
- for (const rawModel of config.models) {
25313
- if (!rawModel || typeof rawModel !== "object" || Array.isArray(rawModel)) continue;
25314
- const id = rawModel.id;
25315
- if (typeof id === "string") infos.push({ provider, id, fullId: `${provider}/${id}` });
25316
- }
25317
- }
25318
- if (config.modelOverrides && typeof config.modelOverrides === "object" && !Array.isArray(config.modelOverrides)) {
25319
- for (const id of Object.keys(config.modelOverrides)) infos.push({ provider, id, fullId: `${provider}/${id}` });
25320
- }
25321
- }
25322
- return infos;
25323
- }
25324
- function configuredModelInfosFromPiConfig(cwd) {
25325
- const agentDir = piAgentDir();
25326
- const globalSettings = readJsonObject(path33.join(agentDir, "settings.json"));
25327
- const projectSettings = cwd ? readJsonObject(path33.join(cwd, ".pi", "settings.json")) : void 0;
25328
- const effectiveSettings = {
25329
- ...globalSettings ?? {},
25330
- ...projectSettings ?? {}
25331
- };
25332
- const defaultModel = settingsModelInfo(effectiveSettings);
25333
- return uniqueModelInfos([
25334
- ...defaultModel ? [defaultModel] : [],
25335
- ...modelsJsonInfos(readJsonObject(path33.join(agentDir, "models.json")))
25336
- ]);
25337
- }
25338
- function splitThinkingSuffix(model) {
25339
- const colonIdx = model.lastIndexOf(":");
25340
- if (colonIdx === -1) return { baseModel: model, thinkingSuffix: "" };
25341
- return {
25342
- baseModel: model.substring(0, colonIdx),
25343
- thinkingSuffix: model.substring(colonIdx)
25344
- };
25345
- }
25346
- function resolveModelCandidate(model, availableModels, preferredProvider) {
25347
- if (!model) return void 0;
25348
- if (model.includes("/")) return model;
25349
- if (!availableModels || availableModels.length === 0) return model;
25350
- const { baseModel, thinkingSuffix } = splitThinkingSuffix(model);
25351
- const matches = availableModels.filter((entry) => entry.id === baseModel);
25352
- if (preferredProvider) {
25353
- const preferredMatch = matches.find((entry) => entry.provider === preferredProvider);
25354
- if (preferredMatch) return `${preferredMatch.fullId}${thinkingSuffix}`;
25355
- }
25356
- if (matches.length !== 1) {
25357
- const fuzzy = fuzzyResolveModelId(baseModel, availableModels);
25358
- if (fuzzy) return `${fuzzy}${thinkingSuffix}`;
25359
- return model;
25360
- }
25361
- return `${matches[0].fullId}${thinkingSuffix}`;
25362
- }
25363
- function isRetryableModelFailure(error) {
25364
- if (!error) return false;
25365
- if (NON_RETRYABLE_MODEL_FAILURE_PATTERNS.some((pattern) => pattern.test(error))) return false;
25366
- return RETRYABLE_MODEL_FAILURE_PATTERNS.some((pattern) => pattern.test(error));
25367
- }
25368
- function formatModelAttemptNote(attempt, nextModel) {
25369
- const failure = attempt.error?.trim() || `exit ${attempt.exitCode ?? 1}`;
25370
- return nextModel ? `[fallback] ${attempt.model} failed: ${failure}. Retrying with ${nextModel}.` : `[fallback] ${attempt.model} failed: ${failure}.`;
25371
- }
25372
- function buildModelCandidates(primaryModel, fallbackModels, availableModels, preferredProvider) {
25373
- const seen = /* @__PURE__ */ new Set();
25374
- const candidates = [];
25375
- for (const raw of [primaryModel, ...fallbackModels ?? []]) {
25376
- if (!raw) continue;
25377
- const normalized = resolveModelCandidate(raw.trim(), availableModels, preferredProvider);
25378
- if (!normalized || seen.has(normalized)) continue;
25379
- seen.add(normalized);
25380
- candidates.push(normalized);
25381
- }
25382
- return candidates;
25383
- }
25384
- function isAvailableModel(model, availableModels) {
25385
- if (!availableModels || availableModels.length === 0) return true;
25386
- const { baseModel } = splitThinkingSuffix(model);
25387
- if (baseModel.includes("/")) return availableModels.some((entry) => entry.fullId === baseModel);
25388
- if (availableModels.some((entry) => entry.id === baseModel)) return true;
25389
- const fuzzy = fuzzyResolveModelId(baseModel, availableModels);
25390
- return fuzzy !== void 0;
25391
- }
25392
- function buildConfiguredModelRouting(input) {
25393
- const registryModels = availableModelInfosFromRegistry(input.modelRegistry);
25394
- const configModels = configuredModelInfosFromPiConfig(input.cwd);
25395
- const availableModels = registryModels && registryModels.length > 0 ? registryModels : configModels.length > 0 ? configModels : registryModels;
25396
- const parentModel = modelStringFromUnknown(input.parentModel);
25397
- const preferredProvider = parentModel?.split("/")[0] ?? availableModels?.[0]?.provider;
25398
- const effectiveAgentModel = input.agentModel?.trim() ? input.agentModel : parentModel;
25399
- const requested = [input.overrideModel, input.stepModel, input.teamRoleModel, effectiveAgentModel].find(
25400
- (model) => Boolean(model?.trim())
25401
- );
25402
- if (availableModels && availableModels.length === 0)
25403
- return {
25404
- requested,
25405
- candidates: [],
25406
- reason: "no configured Pi models available"
25407
- };
25408
- const rawModels = availableModels ? [
25409
- input.overrideModel,
25410
- input.stepModel,
25411
- input.teamRoleModel,
25412
- effectiveAgentModel,
25413
- ...input.fallbackModels ?? [],
25414
- ...availableModels.map((model) => model.fullId)
25415
- ] : [input.overrideModel, input.stepModel, input.teamRoleModel, effectiveAgentModel, ...input.fallbackModels ?? [], parentModel];
25416
- const parentModelRaw = effectiveAgentModel?.trim() || void 0;
25417
- const configuredModels = rawModels.filter((model) => Boolean(model?.trim())).filter((model, idx) => {
25418
- if (parentModelRaw && idx === 0 && model.trim() === parentModelRaw) return true;
25419
- return isAvailableModel(model.trim(), availableModels);
25420
- });
25421
- const candidates = buildModelCandidates(configuredModels[0], configuredModels.slice(1), availableModels, preferredProvider);
25422
- 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;
25423
- let scopeVerdict;
25424
- if (input.scopeModelsPatterns && input.scopeModelsPatterns.length > 0) {
25425
- const resolved = candidates[0] ?? requested;
25426
- const source = input.overrideModel ? "caller" : input.agentModel ? "frontmatter" : "resolved";
25427
- scopeVerdict = checkModelScope(resolved, input.scopeModelsPatterns, source);
25428
- if (!scopeVerdict.inScope && source === "caller" && !input.isFrontmatterOverride) {
25429
- throw errors.modelOutOfScope(resolved ?? "", input.scopeModelsPatterns);
25430
- }
25431
- }
25432
- return { requested, candidates, reason, scopeVerdict };
25433
- }
25434
- var RETRYABLE_MODEL_FAILURE_PATTERNS, NON_RETRYABLE_MODEL_FAILURE_PATTERNS;
25435
- var init_model_fallback = __esm({
25436
- "src/runtime/model/model-fallback.ts"() {
25437
- "use strict";
25438
- init_errors3();
25439
- init_model_resolver();
25440
- init_model_scope();
25441
- RETRYABLE_MODEL_FAILURE_PATTERNS = [
25442
- /rate.?limit/i,
25443
- /too many requests/i,
25444
- /\b429\b/,
25445
- /rate_limit_error/i,
25446
- /quota/i,
25447
- /provider.*unavailable/i,
25448
- /model.*unavailable/i,
25449
- /model.*disabled/i,
25450
- /model.*not found/i,
25451
- /unknown model/i,
25452
- /overloaded/i,
25453
- /service unavailable/i,
25454
- /temporar(?:ily)? unavailable/i,
25455
- /connection refused/i,
25456
- /fetch failed/i,
25457
- /network error/i,
25458
- /socket hang up/i,
25459
- /upstream/i,
25460
- /timed? out/i,
25461
- /timeout/i,
25462
- /\b502\b/,
25463
- /\b503\b/,
25464
- /\b504\b/,
25465
- //
25466
- // Provider-side 5xx / generic api_error. The pi-core retry layer already
25467
- // retries these (agent-session.ts matches `500|server error|internal error`),
25468
- // but the pi-crew MODEL FALLBACK layer must ALSO treat them as retryable so
25469
- // that when the provider is hard-down across all 3 provider retries, we fail
25470
- // over to the next configured model instead of giving up. Reported case
25471
- // (2026-06-17): `500 {"type":"error","error":{"type":"api_error",
25472
- // "message":"unknown error, 999 (1000)"}}` — a transient provider outage that
25473
- // should trigger the fallback chain, not abort.
25474
- //
25475
- // `api_error` is the OpenAI-compatible generic error type (vs rate_limit_error
25476
- // / overloaded_error / etc.) and almost always means a transient server fault.
25477
- //
25478
- // `unknown error` is the body of the generic message; `internal`/`server`
25479
- // catch the common phrasings. `\b500\b`/`\b501\b` catch the HTTP status in
25480
- // the rendered error string.
25481
- /\b500\b/,
25482
- /\b501\b/,
25483
- /api_error/i,
25484
- /unknown error/i,
25485
- /internal(?:_server)?[ _]error/i,
25486
- /server error/i,
25487
- /bad gateway/i,
25488
- //
25489
- // Broader retryable patterns (added 2026-06-25, FIX 2):
25490
- // - `/provider[_ ]?error/i`: OpenAI-compatible "Provider error" generic fault.
25491
- // - `/context[_ ]?length[_ ]?exceeded/i`: "context_length_exceeded" from
25492
- // OpenAI/Anthropic — when the configured model is the bottleneck, a
25493
- // different model in the fallback chain may have a larger window.
25494
- // - `/safety/i`: Anthropic safety blocks — typically retryable on a
25495
- // different model in the fallback chain.
25496
- // - `/is[_ ]?overloaded/i`: alias to the existing `/overloaded/i` pattern
25497
- // to catch phrasings like "upstream is overloaded".
25498
- // - `/\b408\b/`: HTTP 408 Request Timeout — transient, provider-side.
25499
- //
25500
- // Intentionally NOT added: `/bad_request/` — can mean bad input (e.g.
25501
- // invalid schema), which is non-retryable.
25502
- /provider[_ ]?error/i,
25503
- /context[_ ]?length[_ ]?exceeded/i,
25504
- /safety/i,
25505
- /is[_ ]?overloaded/i,
25506
- /\b408\b/
25507
- ];
25508
- NON_RETRYABLE_MODEL_FAILURE_PATTERNS = [
25509
- /auth(?:entication)?/i,
25510
- /unauthori[sz]ed/i,
25511
- /forbidden/i,
25512
- /api key/i,
25513
- /token expired/i,
25514
- /invalid key/i,
25515
- /billing/i,
25516
- /credit/i
25517
- ];
25518
- }
25519
- });
25520
-
25521
25877
  // src/runtime/output/sidechain-output.ts
25522
25878
  import * as fs40 from "node:fs";
25523
25879
  import * as path34 from "node:path";
@@ -32567,6 +32923,20 @@ async function resolveScopeModelsPatterns(cwd, agentDir) {
32567
32923
  if (!scopeModels) return [];
32568
32924
  return readEnabledModelsPatterns(cwd, agentDir);
32569
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
+ }
32570
32940
  function modelFromRegistry(modelRegistry, modelId) {
32571
32941
  if (!modelId?.includes("/")) return void 0;
32572
32942
  const registry2 = asRecord6(modelRegistry);
@@ -32581,7 +32951,7 @@ function modelFromRegistry(modelRegistry, modelId) {
32581
32951
  }
32582
32952
  }
32583
32953
  function resolveParentModelFromRegistry(modelRegistry, rawParentModel) {
32584
- const raw = typeof rawParentModel === "string" ? rawParentModel.trim() : void 0;
32954
+ const raw = modelRefToString(rawParentModel);
32585
32955
  if (raw) {
32586
32956
  const candidate = raw.includes("/") ? raw : (() => {
32587
32957
  const m = modelFromRegistry(modelRegistry, raw);
@@ -32592,18 +32962,11 @@ function resolveParentModelFromRegistry(modelRegistry, rawParentModel) {
32592
32962
  })();
32593
32963
  if (candidate && modelFromRegistry(modelRegistry, candidate)) return candidate;
32594
32964
  }
32595
- const registry2 = modelRegistry;
32596
- if (registry2 && typeof registry2.getAvailable === "function") {
32597
- try {
32598
- const available = registry2.getAvailable();
32599
- if (Array.isArray(available) && available.length > 0) {
32600
- const first = available[0];
32601
- if (first && typeof first.provider === "string" && typeof first.id === "string") {
32602
- return `${first.provider}/${first.id}`;
32603
- }
32604
- }
32605
- } catch {
32606
- }
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;
32607
32970
  }
32608
32971
  return raw;
32609
32972
  }
@@ -32863,14 +33226,32 @@ async function runLiveSessionTask(input) {
32863
33226
  overrideModel: input.modelOverride,
32864
33227
  stepModel: input.step.model,
32865
33228
  teamRoleModel: input.teamRoleModel,
33229
+ teamRoleFallbackModels: input.teamRoleFallbackModels,
32866
33230
  agentModel: input.agent.model,
33231
+ defaultSubagentModel: resolveLiveDefaultSubagentModel(input.manifest.cwd),
32867
33232
  fallbackModels: input.agent.fallbackModels,
32868
33233
  parentModel: effectiveParentModel,
32869
33234
  modelRegistry: input.modelRegistry,
32870
33235
  cwd: input.manifest.cwd,
33236
+ policy: resolveLiveModelFallbackPolicy(input.manifest.cwd),
32871
33237
  scopeModelsPatterns: await resolveScopeModelsPatterns(input.manifest.cwd)
32872
33238
  });
32873
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;
32874
33255
  const mcpProxy = buildMcpProxyFromSession([], { shareMcp: true });
32875
33256
  const submitResultTool = createSubmitResultTool((result4) => {
32876
33257
  customToolYieldResult = result4;
@@ -32891,7 +33272,7 @@ async function runLiveSessionTask(input) {
32891
33272
  } : {},
32892
33273
  ...input.modelRegistry ? { modelRegistry: input.modelRegistry } : {},
32893
33274
  ...resolvedModel ? { model: resolvedModel } : {},
32894
- ...input.agent.thinking ? { thinkingLevel: input.agent.thinking } : {},
33275
+ ...effectiveThinking ? { thinkingLevel: effectiveThinking } : {},
32895
33276
  ...mcpProxy.enableMcp ? {} : { enableMCP: false },
32896
33277
  customTools
32897
33278
  });
@@ -33229,7 +33610,15 @@ ${input.prompt}` : input.prompt;
33229
33610
  stderr: created.modelFallbackMessage ?? "",
33230
33611
  jsonEvents,
33231
33612
  usage,
33232
- 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
+ }
33233
33622
  };
33234
33623
  } catch (error) {
33235
33624
  const message = error instanceof Error ? error.message : String(error);
@@ -36966,6 +37355,43 @@ function buildTeamDoctorReport(input) {
36966
37355
  }
36967
37356
  ];
36968
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
+ }),
36969
37395
  section("Discovery", () => {
36970
37396
  const agentModelHints = discoveredAgentsAll.filter((agent) => agent.model || agent.fallbackModels?.length).length;
36971
37397
  return [
@@ -37150,7 +37576,9 @@ var init_doctor = __esm({
37150
37576
  init_config();
37151
37577
  init_defaults();
37152
37578
  init_drift_detector();
37579
+ init_model_fallback();
37153
37580
  init_runtime_warmup();
37581
+ init_session_model();
37154
37582
  init_pi_spawn();
37155
37583
  init_zombie_scanner();
37156
37584
  init_team_tool_schema();
@@ -41175,7 +41603,7 @@ var init_async_runner = __esm({
41175
41603
  });
41176
41604
 
41177
41605
  // src/runtime/goal-workflow/goal-state-store.ts
41178
- 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";
41179
41607
  import { dirname as dirname32 } from "node:path";
41180
41608
  function resolveGoalsRoot(cwd) {
41181
41609
  const crewRoot = projectCrewRoot(cwd) ?? userCrewRoot();
@@ -41218,22 +41646,22 @@ var init_goal_state_store = __esm({
41218
41646
  }
41219
41647
  }
41220
41648
  /** Atomically persist a goal state. Emits a goal.state_changed event if eventsPath given. */
41221
- save(state, eventsPath) {
41222
- assertSafePathId("goalId", state.goalId);
41223
- const path89 = goalFilePath(this.cwd, state.goalId);
41224
- 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() };
41225
41653
  try {
41226
41654
  mkdirSync26(dirname32(path89), { recursive: true });
41227
41655
  atomicWriteJson(path89, next);
41228
41656
  if (eventsPath) {
41229
41657
  appendEvent(eventsPath, {
41230
41658
  type: "goal.state_changed",
41231
- runId: state.goalId,
41232
- data: { goalId: state.goalId, state: state.state }
41659
+ runId: state2.goalId,
41660
+ data: { goalId: state2.goalId, state: state2.state }
41233
41661
  });
41234
41662
  }
41235
41663
  } catch (error) {
41236
- logInternalError("goal-state-store.save", error, `goalId=${state.goalId}`);
41664
+ logInternalError("goal-state-store.save", error, `goalId=${state2.goalId}`);
41237
41665
  throw error;
41238
41666
  }
41239
41667
  }
@@ -41251,8 +41679,8 @@ var init_goal_state_store = __esm({
41251
41679
  return next;
41252
41680
  }
41253
41681
  /** Convenience: transition state with optional event emission. */
41254
- setStatus(goalId, state, eventsPath) {
41255
- return this.patch(goalId, { state }, eventsPath);
41682
+ setStatus(goalId, state2, eventsPath) {
41683
+ return this.patch(goalId, { state: state2 }, eventsPath);
41256
41684
  }
41257
41685
  /**
41258
41686
  * Compare-And-Set status for atomic stuck↔resume transitions (P1b, RFC v0.5 §P1b).
@@ -41306,7 +41734,7 @@ var init_goal_state_store = __esm({
41306
41734
  const code = error.code;
41307
41735
  if (code !== "EEXIST") return false;
41308
41736
  try {
41309
- const stat2 = statSync32(lockPath2);
41737
+ const stat2 = statSync33(lockPath2);
41310
41738
  if (Date.now() - stat2.mtimeMs > 5e3) {
41311
41739
  unlinkSync7(lockPath2);
41312
41740
  const fd = openSync10(lockPath2, "wx");
@@ -41399,7 +41827,7 @@ var init_verification_integrity = __esm({
41399
41827
 
41400
41828
  // src/runtime/workspace-lock.ts
41401
41829
  import { createHash as createHash7 } from "node:crypto";
41402
- 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";
41403
41831
  import * as path50 from "node:path";
41404
41832
  function workspaceLockPath(cwd) {
41405
41833
  const absCwd = path50.resolve(cwd);
@@ -43971,19 +44399,19 @@ function ensureShared() {
43971
44399
  return shared;
43972
44400
  }
43973
44401
  function registerOverlayScheduler(render, onInvalidate) {
43974
- const state = ensureShared();
43975
- state.renderers.add(render);
43976
- if (onInvalidate) state.invalidators.add(onInvalidate);
44402
+ const state2 = ensureShared();
44403
+ state2.renderers.add(render);
44404
+ if (onInvalidate) state2.invalidators.add(onInvalidate);
43977
44405
  return {
43978
44406
  schedule: () => {
43979
- state.scheduler.schedule();
44407
+ state2.scheduler.schedule();
43980
44408
  },
43981
44409
  dispose: () => {
43982
- state.renderers.delete(render);
43983
- if (onInvalidate) state.invalidators.delete(onInvalidate);
43984
- if (state.renderers.size === 0) {
43985
- state.scheduler.dispose();
43986
- 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;
43987
44415
  }
43988
44416
  }
43989
44417
  };
@@ -45452,9 +45880,9 @@ function uninstallResizeListener() {
45452
45880
  process.stdout.removeListener("resize", onResize);
45453
45881
  }
45454
45882
  }
45455
- function updateCrewWidget(ctx, state, config, manifestCache2, snapshotCache, preloadedManifests) {
45883
+ function updateCrewWidget(ctx, state2, config, manifestCache2, snapshotCache, preloadedManifests) {
45456
45884
  if (!ctx.hasUI) return;
45457
- state.frame += 1;
45885
+ state2.frame += 1;
45458
45886
  const maxLines = config?.widgetMaxLines ?? MAX_LINES_DEFAULT;
45459
45887
  let workspaceId = ctx.sessionManager?.getSessionId?.();
45460
45888
  if (!workspaceId && manifestCache2) {
@@ -45463,75 +45891,75 @@ function updateCrewWidget(ctx, state, config, manifestCache2, snapshotCache, pre
45463
45891
  if (active?.ownerSessionId) workspaceId = active.ownerSessionId;
45464
45892
  }
45465
45893
  const runs = activeWidgetRuns(ctx.cwd, manifestCache2, snapshotCache, preloadedManifests, workspaceId);
45466
- 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());
45467
45895
  const placement = config?.widgetPlacement ?? DEFAULT_UI.widgetPlacement;
45468
45896
  ctx.ui.setStatus(STATUS_KEY, lines.length ? statusSummary(runs) : void 0);
45469
- const shouldClearLegacy = state.legacyCleared !== true || state.lastPlacement !== placement;
45897
+ const shouldClearLegacy = state2.legacyCleared !== true || state2.lastPlacement !== placement;
45470
45898
  if (shouldClearLegacy) {
45471
45899
  setExtensionWidget(ctx, LEGACY_WIDGET_KEY, void 0, { placement });
45472
- state.legacyCleared = true;
45900
+ state2.legacyCleared = true;
45473
45901
  }
45474
45902
  if (!lines.length) {
45475
- if (state.lastVisibility !== "hidden" || state.lastPlacement !== placement) {
45903
+ if (state2.lastVisibility !== "hidden" || state2.lastPlacement !== placement) {
45476
45904
  setExtensionWidget(ctx, WIDGET_KEY, void 0, { placement });
45477
- state.lastVisibility = "hidden";
45478
- state.lastPlacement = placement;
45479
- state.lastKey = WIDGET_KEY;
45480
- state.lastMaxLines = maxLines;
45481
- state.lastCwd = ctx.cwd;
45482
- 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;
45483
45911
  }
45484
45912
  requestRender(ctx);
45485
45913
  return;
45486
45914
  }
45487
- const needsWidgetInstall = state.lastVisibility !== "visible" || state.lastPlacement !== placement || state.lastKey !== WIDGET_KEY || state.lastMaxLines !== maxLines || state.lastCwd !== ctx.cwd || !state.model;
45488
- if (!state.model)
45489
- 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 = {
45490
45918
  cwd: ctx.cwd,
45491
- frame: state.frame,
45919
+ frame: state2.frame,
45492
45920
  maxLines,
45493
- notificationCount: state.notificationCount ?? 0,
45921
+ notificationCount: state2.notificationCount ?? 0,
45494
45922
  manifestCache: manifestCache2,
45495
45923
  snapshotCache,
45496
45924
  preloadManifests: preloadedManifests,
45497
45925
  workspaceId
45498
45926
  };
45499
45927
  else {
45500
- state.model.cwd = ctx.cwd;
45501
- state.model.frame = state.frame;
45502
- state.model.maxLines = maxLines;
45503
- state.model.notificationCount = state.notificationCount ?? 0;
45504
- state.model.manifestCache = manifestCache2;
45505
- state.model.snapshotCache = snapshotCache;
45506
- state.model.preloadManifests = preloadedManifests;
45507
- state.model.workspaceId = workspaceId;
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;
45508
45936
  }
45509
45937
  if (needsWidgetInstall) {
45510
- const model = state.model;
45938
+ const model = state2.model;
45511
45939
  setExtensionWidget(ctx, WIDGET_KEY, ((_tui, theme) => new CrewWidgetComponent(model, theme, _tui)), {
45512
45940
  placement,
45513
45941
  persist: true
45514
45942
  });
45515
- state.lastVisibility = "visible";
45516
- state.lastPlacement = placement;
45517
- state.lastKey = WIDGET_KEY;
45518
- state.lastMaxLines = maxLines;
45519
- 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;
45520
45948
  }
45521
45949
  requestRender(ctx);
45522
45950
  }
45523
- function stopCrewWidget(ctx, state, config) {
45951
+ function stopCrewWidget(ctx, state2, config) {
45524
45952
  uninstallResizeListener();
45525
45953
  if (ctx?.hasUI) {
45526
45954
  const placement = config?.widgetPlacement ?? DEFAULT_UI.widgetPlacement;
45527
45955
  ctx.ui.setStatus(STATUS_KEY, void 0);
45528
45956
  setExtensionWidget(ctx, LEGACY_WIDGET_KEY, void 0, { placement });
45529
45957
  setExtensionWidget(ctx, WIDGET_KEY, void 0, { placement });
45530
- state.lastVisibility = "hidden";
45531
- state.lastPlacement = placement;
45532
- state.lastKey = WIDGET_KEY;
45533
- state.model = void 0;
45534
- 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;
45535
45963
  requestRender(ctx);
45536
45964
  }
45537
45965
  }
@@ -45751,7 +46179,7 @@ var init_tool_progress_formatter = __esm({
45751
46179
  });
45752
46180
 
45753
46181
  // src/extension/registration/team-tool.ts
45754
- import { statSync as statSync37 } from "node:fs";
46182
+ import { statSync as statSync38 } from "node:fs";
45755
46183
  import { Text as Text3 } from "@earendil-works/pi-tui";
45756
46184
  async function handleTeamTool(params, ctx) {
45757
46185
  if (!_cachedHandleTeamTool) {
@@ -45774,7 +46202,7 @@ function resolveCwdOverride(baseCwd, override) {
45774
46202
  if (!override) return { ok: true, cwd: baseCwd };
45775
46203
  try {
45776
46204
  const resolved = resolveRealContainedPath(baseCwd, override);
45777
- const stat2 = statSync37(resolved);
46205
+ const stat2 = statSync38(resolved);
45778
46206
  if (!stat2.isDirectory())
45779
46207
  return {
45780
46208
  ok: false,
@@ -52684,6 +53112,20 @@ function resolveConfiguredMaxAttempts(cwd) {
52684
53112
  function computeSpawnBudgetMax(attemptModelsCount, configuredMaxAttempts) {
52685
53113
  return attemptModelsCount * (configuredMaxAttempts + 1);
52686
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
+ }
52687
53129
  function detectRetryableModelFailureFromOutput(parsed) {
52688
53130
  const messages = parsed.errorMessages;
52689
53131
  if (messages && messages.length > 0) {
@@ -52729,24 +53171,45 @@ async function runChildProcessTask(ctx) {
52729
53171
  let transcriptPath;
52730
53172
  let terminalEvidence = [];
52731
53173
  let startupEvidence = ctx.startupEvidence;
53174
+ const modelFallbackPolicy = resolveTaskModelFallbackPolicy(task.cwd);
53175
+ const defaultSubagentModel = resolveTaskDefaultSubagentModel(task.cwd);
52732
53176
  const modelRoutingPlan = buildConfiguredModelRouting({
52733
53177
  overrideModel: input.modelOverride,
52734
53178
  stepModel: input.step.model,
52735
53179
  teamRoleModel: input.teamRoleModel,
53180
+ teamRoleFallbackModels: input.teamRoleFallbackModels,
52736
53181
  agentModel: input.agent.model,
53182
+ defaultSubagentModel,
52737
53183
  fallbackModels: input.agent.fallbackModels,
52738
53184
  parentModel: input.parentModel,
52739
53185
  modelRegistry: input.modelRegistry,
52740
53186
  cwd: task.cwd,
53187
+ policy: modelFallbackPolicy,
52741
53188
  scopeModelsPatterns: await resolveTaskScopeModelsPatterns(task.cwd)
52742
53189
  });
52743
53190
  const candidates = modelRoutingPlan.candidates;
52744
- 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];
52745
53207
  if (input.spawnBudget && input.spawnBudget.max === 0) {
52746
53208
  input.spawnBudget.max = computeSpawnBudgetMax(attemptModels.length, resolveConfiguredMaxAttempts(task.cwd));
52747
53209
  }
52748
53210
  const logs = [];
52749
53211
  let finalStderr = "";
53212
+ let reResolveUsed = false;
52750
53213
  modelAttempts = [];
52751
53214
  let finalCheckpointWritten = false;
52752
53215
  let lastAgentRecordPersistedAt = 0;
@@ -52867,6 +53330,7 @@ async function runChildProcessTask(ctx) {
52867
53330
  excludeContextBash: input.runtimeConfig?.excludeContextBash,
52868
53331
  sessionId: manifest.sessionId,
52869
53332
  role: task.role,
53333
+ thinkingOverride: input.teamRoleThinking,
52870
53334
  runId: manifest.runId,
52871
53335
  agentId: task.id,
52872
53336
  artifactsRoot: manifest.artifactsRoot,
@@ -53035,7 +53499,8 @@ async function runChildProcessTask(ctx) {
53035
53499
  );
53036
53500
  if (!error) break;
53037
53501
  let nextModel = attemptModels[i + 1];
53038
- if (!nextModel && isRetryableModelFailure(error)) {
53502
+ if (!nextModel && !reResolveUsed && isRetryableModelFailure(error)) {
53503
+ reResolveUsed = true;
53039
53504
  const reResolved = buildConfiguredModelRouting({
53040
53505
  overrideModel: void 0,
53041
53506
  stepModel: void 0,
@@ -53045,10 +53510,17 @@ async function runChildProcessTask(ctx) {
53045
53510
  parentModel: attempt.model,
53046
53511
  modelRegistry: input.modelRegistry,
53047
53512
  cwd: task.cwd,
53513
+ policy: modelFallbackPolicy,
53048
53514
  scopeModelsPatterns: await resolveTaskScopeModelsPatterns(task.cwd)
53049
53515
  });
53050
- const alt = reResolved.candidates.find((c) => c !== attempt.model);
53051
- 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
+ }
53052
53524
  }
53053
53525
  if (!nextModel || !isRetryableModelFailure(error)) break;
53054
53526
  logs.push(formatModelAttemptNote(attempt, nextModel), "");
@@ -53117,7 +53589,9 @@ async function runChildProcessTask(ctx) {
53117
53589
  resolved: resolvedModel,
53118
53590
  fallbackChain: candidates,
53119
53591
  reason: fallbackReason ?? modelRoutingPlan.reason,
53120
- usedAttempt
53592
+ usedAttempt,
53593
+ droppedRequested: modelRoutingPlan.droppedRequested,
53594
+ autoFallbackCount: modelRoutingPlan.autoFallbackCount
53121
53595
  }
53122
53596
  };
53123
53597
  tasks = updateTask(tasks, task);
@@ -55028,6 +55502,8 @@ async function runLiveTask(input) {
55028
55502
  modelRegistry: input.modelRegistry,
55029
55503
  modelOverride: input.modelOverride,
55030
55504
  teamRoleModel: input.teamRoleModel,
55505
+ teamRoleFallbackModels: input.teamRoleFallbackModels,
55506
+ teamRoleThinking: input.teamRoleThinking,
55031
55507
  isCurrent,
55032
55508
  workspaceId: input.workspaceId,
55033
55509
  // Phase 2: Pass output schema for yield validation
@@ -55071,6 +55547,14 @@ async function runLiveTask(input) {
55071
55547
  usage: liveResult.usage,
55072
55548
  agentProgress: applyUsageToProgress(task.agentProgress, liveResult.usage)
55073
55549
  };
55550
+ if (liveResult.modelRouting)
55551
+ task = {
55552
+ ...task,
55553
+ modelRouting: {
55554
+ ...liveResult.modelRouting,
55555
+ usedAttempt: 0
55556
+ }
55557
+ };
55074
55558
  persistLiveProgress({ type: "attempt_finished" }, true);
55075
55559
  const resultArtifact = writeArtifact(manifest.artifactsRoot, {
55076
55560
  kind: "result",
@@ -55197,6 +55681,8 @@ async function runTeamTask(input) {
55197
55681
  modelRegistry: input.modelRegistry,
55198
55682
  modelOverride: input.modelOverride,
55199
55683
  teamRoleModel: input.teamRoleModel,
55684
+ teamRoleFallbackModels: input.teamRoleFallbackModels,
55685
+ teamRoleThinking: input.teamRoleThinking,
55200
55686
  workspaceId: input.workspaceId
55201
55687
  });
55202
55688
  task = live.task;
@@ -56725,10 +57211,7 @@ async function cancelRunFromSignal(ctx) {
56725
57211
  return { kind: "return", result: { manifest: ctx.manifest, tasks: ctx.tasks } };
56726
57212
  }
56727
57213
  async function terminaliseRunWithDrain(ctx, opts) {
56728
- const inflightTaskIds = /* @__PURE__ */ new Set();
56729
- for (const unit of ctx.pendingUnits.values()) {
56730
- for (const id of unit.taskIds) inflightTaskIds.add(id);
56731
- }
57214
+ const inflightTaskIds = ctx.dispatchedTaskIds;
56732
57215
  const outcomes = await drainPendingUnits(ctx.pendingUnits, ctx.runController);
56733
57216
  const validResults = [];
56734
57217
  for (const outcome of outcomes) {
@@ -57049,6 +57532,8 @@ async function dispatchBatch(ctx, decision2) {
57049
57532
  modelRegistry: input.modelRegistry,
57050
57533
  modelOverride: input.modelOverride,
57051
57534
  teamRoleModel: teamRole?.model,
57535
+ teamRoleThinking: teamRole?.thinking,
57536
+ teamRoleFallbackModels: teamRole?.fallbackModels,
57052
57537
  teamRoleSkills: teamRole?.skills,
57053
57538
  skillOverride: input.skillOverride,
57054
57539
  limits: input.limits,
@@ -57234,6 +57719,7 @@ async function dispatchBatch(ctx, decision2) {
57234
57719
  promise: rawPromise,
57235
57720
  wrapped
57236
57721
  });
57722
+ for (const id of unitTaskIds) ctx.dispatchedTaskIds.add(id);
57237
57723
  }
57238
57724
  }
57239
57725
  async function mergeUnitResult(ctx) {
@@ -57594,6 +58080,7 @@ async function executeTeamRunCore(input, manifest, workflow) {
57594
58080
  queueIndex,
57595
58081
  wfMachine,
57596
58082
  pendingUnits,
58083
+ dispatchedTaskIds: /* @__PURE__ */ new Set(),
57597
58084
  runController,
57598
58085
  runtimeKind,
57599
58086
  adaptivePlanInjected,
@@ -59396,7 +59883,8 @@ var init_chain_executor = __esm({
59396
59883
  # Current Chain Step
59397
59884
  ${packet.goal}` : packet.goal;
59398
59885
  const stepTeam = context.__chainStepTeam ?? this.overrides.team ?? "default";
59399
- const stepWorkflow = context.__chainStepWorkflow ?? this.overrides.workflow;
59886
+ const rawWorkflow = context.__chainStepWorkflow ?? this.overrides.workflow;
59887
+ const stepWorkflow = rawWorkflow === "chain" ? void 0 : rawWorkflow;
59400
59888
  const stepModel = context.__chainStepModel ?? this.overrides.model;
59401
59889
  const runParams = {
59402
59890
  action: "run",
@@ -59441,6 +59929,13 @@ async function handleChainRun(params, ctx, handleRun3) {
59441
59929
  if (!chainString || chainString.trim().length === 0) {
59442
59930
  return result("Chain expression is empty.", { action: "run", status: "error" }, true);
59443
59931
  }
59932
+ if (params.workflow === "chain") {
59933
+ return result(
59934
+ "Workflow 'chain' cannot be combined with a chain run: the chain runner owns step execution and has no per-step workflow. Omit `workflow` (steps use the team's defaultWorkflow) or use @team references in the chain expression.",
59935
+ { action: "run", status: "error" },
59936
+ true
59937
+ );
59938
+ }
59444
59939
  const spec = parseChainString(chainString);
59445
59940
  if (spec.steps.length === 0) {
59446
59941
  return result(
@@ -59454,8 +59949,8 @@ async function handleChainRun(params, ctx, handleRun3) {
59454
59949
  ctx,
59455
59950
  overrides: {
59456
59951
  team: params.team,
59457
- workflow: params.workflow,
59458
- model: params.model
59952
+ // workflow intentionally omitted — chain runner owns step execution (bug-44)
59953
+ ...params.model ? { model: params.model } : {}
59459
59954
  }
59460
59955
  });
59461
59956
  const runner = new ChainRunner(executor, new HandoffManager());
@@ -59720,14 +60215,14 @@ var init_dwf_state_store = __esm({
59720
60215
  }
59721
60216
  }
59722
60217
  /** Atomically persist a checkpoint state. Stamps `updatedAt` (callers need not set it). */
59723
- save(state) {
60218
+ save(state2) {
59724
60219
  const path89 = this.path;
59725
- const next = { ...state, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
60220
+ const next = { ...state2, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
59726
60221
  try {
59727
60222
  mkdirSync39(dirname37(path89), { recursive: true });
59728
60223
  atomicWriteJson(path89, next);
59729
60224
  } catch (error) {
59730
- logInternalError("dwf-state-store.save", error, `runId=${state.runId}`);
60225
+ logInternalError("dwf-state-store.save", error, `runId=${state2.runId}`);
59731
60226
  throw error;
59732
60227
  }
59733
60228
  }
@@ -60626,9 +61121,9 @@ async function runDynamicWorkflow(input) {
60626
61121
  resumedState,
60627
61122
  // round-18 P2-3: checkpoint after each ctx.agent() call so a crash between calls
60628
61123
  // leaves durable state. onCheckpoint captures the closure values at call time.
60629
- onCheckpoint: (state) => {
61124
+ onCheckpoint: (state2) => {
60630
61125
  try {
60631
- dwfStore.save(state);
61126
+ dwfStore.save(state2);
60632
61127
  } catch (error) {
60633
61128
  logInternalError("dynamic-workflow-runner.checkpoint-save", error, `runId=${manifest.runId}`);
60634
61129
  }
@@ -61012,7 +61507,7 @@ Commit or stash changes before using worktree mode, or use workspaceMode: 'singl
61012
61507
  workspaceMode: params.workspaceMode
61013
61508
  } : teams.find((item) => item.name === teamName);
61014
61509
  if (!team) return result(`Team '${teamName}' not found.`, { action: "run", status: "error" }, true);
61015
- const workflowName = directAgent ? "direct-agent" : params.workflow ?? team.defaultWorkflow ?? "default";
61510
+ const workflowName = directAgent ? "direct-agent" : params.workflow === "chain" && !params.chain ? team.defaultWorkflow ?? "default" : params.workflow ?? team.defaultWorkflow ?? "default";
61016
61511
  const baseWorkflow = directAgent ? {
61017
61512
  name: "direct-agent",
61018
61513
  description: `Direct task for ${directAgent.name}`,
@@ -61226,6 +61721,10 @@ ${dwfResult.manifest.summary ?? ""}`,
61226
61721
  ...updatedManifest,
61227
61722
  runtimeResolution,
61228
61723
  runConfig: executedConfig,
61724
+ // Background/async runs re-enter through background-runner in a detached
61725
+ // process with no ExtensionContext. Snapshot the model routing inputs so
61726
+ // they survive the hand-off instead of being rediscovered from models.json.
61727
+ modelContext: captureRunModelContext(ctx, params.model),
61229
61728
  // Persist budget config on the manifest so it's observable post-run
61230
61729
  // (events.jsonl, status reads, audits). The team-runner reads these
61231
61730
  // from the input, but persisting them means consumers can verify
@@ -61436,7 +61935,7 @@ ${dwfResult.manifest.summary ?? ""}`,
61436
61935
  runtime,
61437
61936
  runtimeConfig: executedConfig.runtime,
61438
61937
  parentContext: buildParentContext(ctx),
61439
- parentModel: ctx.model,
61938
+ parentModel: resolveParentModel(ctx.model),
61440
61939
  modelRegistry: ctx.modelRegistry,
61441
61940
  modelOverride: params.model,
61442
61941
  skillOverride,
@@ -61502,7 +62001,7 @@ ${dwfResult.manifest.summary ?? ""}`,
61502
62001
  runtime,
61503
62002
  runtimeConfig: executedConfig.runtime,
61504
62003
  parentContext: buildParentContext(ctx),
61505
- parentModel: ctx.model,
62004
+ parentModel: resolveParentModel(ctx.model),
61506
62005
  modelRegistry: ctx.modelRegistry,
61507
62006
  modelOverride: params.model,
61508
62007
  skillOverride,
@@ -61551,6 +62050,7 @@ var init_run2 = __esm({
61551
62050
  init_param_error();
61552
62051
  init_async_runner();
61553
62052
  init_runtime_resolver();
62053
+ init_session_model();
61554
62054
  init_event_log();
61555
62055
  init_i18n();
61556
62056
  init_async_marker();
@@ -61959,7 +62459,7 @@ async function handleResume2(params, ctx) {
61959
62459
  runtime: decision2.runtime,
61960
62460
  runtimeConfig: decision2.executedConfig.runtime,
61961
62461
  parentContext: buildParentContext(ctx),
61962
- parentModel: ctx.model,
62462
+ parentModel: resolveParentModel(ctx.model),
61963
62463
  modelRegistry: ctx.modelRegistry,
61964
62464
  modelOverride: params.model,
61965
62465
  skillOverride: decision2.resumeSkillOverride,
@@ -62206,6 +62706,7 @@ var init_team_tool2 = __esm({
62206
62706
  init_run_index();
62207
62707
  init_direct_run();
62208
62708
  init_runtime_resolver();
62709
+ init_session_model();
62209
62710
  init_pi_json_output();
62210
62711
  init_config_patch();
62211
62712
  init_context();
@@ -63237,16 +63738,16 @@ function readRunTranscript(manifest, taskId, options = {}) {
63237
63738
  readRunTranscriptCache.set(cacheKey2, { parsedAt: now, result: result4 });
63238
63739
  return result4;
63239
63740
  }
63240
- function renderViewerBase(state, width, lines, title, subtitle) {
63741
+ function renderViewerBase(state2, width, lines, title, subtitle) {
63241
63742
  const inner = Math.max(20, width - 4);
63242
63743
  const bodyText = lines.join("\n");
63243
- const { visualLines, skippedCount } = truncateToVisualLines(bodyText, state.lastHeight, inner);
63244
- const maxScroll = Math.max(0, visualLines.length - state.lastHeight);
63245
- if (state.autoScroll) state.scroll = maxScroll;
63246
- state.scroll = Math.min(state.scroll, maxScroll);
63247
- const visible = visualLines.slice(state.scroll, state.scroll + state.lastHeight);
63248
- 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"}`;
63249
- const fg = (color, text) => state.theme.fg(color, text);
63744
+ const { visualLines, skippedCount } = truncateToVisualLines(bodyText, state2.lastHeight, inner);
63745
+ const maxScroll = Math.max(0, visualLines.length - state2.lastHeight);
63746
+ if (state2.autoScroll) state2.scroll = maxScroll;
63747
+ state2.scroll = Math.min(state2.scroll, maxScroll);
63748
+ const visible = visualLines.slice(state2.scroll, state2.scroll + state2.lastHeight);
63749
+ 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"}`;
63750
+ const fg = (color, text) => state2.theme.fg(color, text);
63250
63751
  const row = (text) => `${fg("border", "\u2502")} ${pad(truncate(text, inner), inner)} ${fg("border", "\u2502")}`;
63251
63752
  const linesOut = [
63252
63753
  fg("border", `\u256D${"\u2500".repeat(inner + 2)}\u256E`),
@@ -63815,11 +64316,11 @@ function extractDwfPhaseState(events) {
63815
64316
  });
63816
64317
  return { phases, currentPhase };
63817
64318
  }
63818
- function renderDwfPhaseLines(state, options) {
64319
+ function renderDwfPhaseLines(state2, options) {
63819
64320
  const ascii = options?.ascii === true;
63820
64321
  const lines = [];
63821
- if (state.phases.length > 1) lines.push(DWF_PHASE_HEADER);
63822
- for (const entry of state.phases) {
64322
+ if (state2.phases.length > 1) lines.push(DWF_PHASE_HEADER);
64323
+ for (const entry of state2.phases) {
63823
64324
  lines.push(` ${markerFor(entry.status, ascii)} Phase: ${entry.name}`);
63824
64325
  }
63825
64326
  return lines;
@@ -65747,24 +66248,24 @@ var init_mascot = __esm({
65747
66248
  }
65748
66249
  }
65749
66250
  tickTypewriter() {
65750
- const state = this.effectState;
65751
- if (state.pos === void 0) return true;
66251
+ const state2 = this.effectState;
66252
+ if (state2.pos === void 0) return true;
65752
66253
  for (let i = 0; i < 18; i++) {
65753
- const row = Math.floor(state.pos / ARMIN_WIDTH);
65754
- const x = state.pos % ARMIN_WIDTH;
66254
+ const row = Math.floor(state2.pos / ARMIN_WIDTH);
66255
+ const x = state2.pos % ARMIN_WIDTH;
65755
66256
  if (row >= ARMIN_DISPLAY_HEIGHT) return true;
65756
66257
  this.currentArminGrid[row][x] = this.finalArminGrid[row][x];
65757
- state.pos++;
66258
+ state2.pos++;
65758
66259
  }
65759
66260
  return false;
65760
66261
  }
65761
66262
  tickScanline() {
65762
- const state = this.effectState;
65763
- if (state.row === void 0) return true;
66263
+ const state2 = this.effectState;
66264
+ if (state2.row === void 0) return true;
65764
66265
  for (let step = 0; step < 3; step++) {
65765
- if (state.row >= ARMIN_DISPLAY_HEIGHT) return true;
65766
- for (let x = 0; x < ARMIN_WIDTH; x++) this.currentArminGrid[state.row][x] = this.finalArminGrid[state.row][x];
65767
- state.row++;
66266
+ if (state2.row >= ARMIN_DISPLAY_HEIGHT) return true;
66267
+ for (let x = 0; x < ARMIN_WIDTH; x++) this.currentArminGrid[state2.row][x] = this.finalArminGrid[state2.row][x];
66268
+ state2.row++;
65768
66269
  }
65769
66270
  return false;
65770
66271
  }
@@ -65800,33 +66301,33 @@ var init_mascot = __esm({
65800
66301
  return allSettled;
65801
66302
  }
65802
66303
  tickFade() {
65803
- const state = this.effectState;
65804
- if (!state.positions || state.idx === void 0) return true;
66304
+ const state2 = this.effectState;
66305
+ if (!state2.positions || state2.idx === void 0) return true;
65805
66306
  for (let i = 0; i < 54; i++) {
65806
- if (state.idx >= state.positions.length) return true;
65807
- const [row, x] = state.positions[state.idx];
66307
+ if (state2.idx >= state2.positions.length) return true;
66308
+ const [row, x] = state2.positions[state2.idx];
65808
66309
  this.currentArminGrid[row][x] = this.finalArminGrid[row][x];
65809
- state.idx++;
66310
+ state2.idx++;
65810
66311
  }
65811
66312
  return false;
65812
66313
  }
65813
66314
  tickCrt() {
65814
- const state = this.effectState;
65815
- if (state.expansion === void 0) return true;
66315
+ const state2 = this.effectState;
66316
+ if (state2.expansion === void 0) return true;
65816
66317
  const midRow = Math.floor(ARMIN_DISPLAY_HEIGHT / 2);
65817
66318
  this.currentArminGrid = emptyArminGrid();
65818
- const top = midRow - state.expansion;
65819
- const bottom = midRow + state.expansion;
66319
+ const top = midRow - state2.expansion;
66320
+ const bottom = midRow + state2.expansion;
65820
66321
  for (let row = Math.max(0, top); row <= Math.min(ARMIN_DISPLAY_HEIGHT - 1, bottom); row++) {
65821
66322
  for (let x = 0; x < ARMIN_WIDTH; x++) this.currentArminGrid[row][x] = this.finalArminGrid[row][x];
65822
66323
  }
65823
- state.expansion += 3;
65824
- return state.expansion > ARMIN_DISPLAY_HEIGHT;
66324
+ state2.expansion += 3;
66325
+ return state2.expansion > ARMIN_DISPLAY_HEIGHT;
65825
66326
  }
65826
66327
  tickGlitch() {
65827
- const state = this.effectState;
65828
- if (state.phase === void 0 || state.glitchFrames === void 0) return true;
65829
- if (state.phase < state.glitchFrames) {
66328
+ const state2 = this.effectState;
66329
+ if (state2.phase === void 0 || state2.glitchFrames === void 0) return true;
66330
+ if (state2.phase < state2.glitchFrames) {
65830
66331
  const offset = Math.floor(Math.random() * 7) - 3;
65831
66332
  const swapRow = Math.floor(Math.random() * ARMIN_DISPLAY_HEIGHT);
65832
66333
  const doShift = Math.random() < 0.3;
@@ -65841,7 +66342,7 @@ var init_mascot = __esm({
65841
66342
  }
65842
66343
  }
65843
66344
  }
65844
- state.phase += 3;
66345
+ state2.phase += 3;
65845
66346
  return false;
65846
66347
  }
65847
66348
  for (let row = 0; row < ARMIN_DISPLAY_HEIGHT; row++) {
@@ -65852,13 +66353,13 @@ var init_mascot = __esm({
65852
66353
  return true;
65853
66354
  }
65854
66355
  tickDissolve() {
65855
- const state = this.effectState;
65856
- if (!state.positions || state.idx === void 0) return true;
66356
+ const state2 = this.effectState;
66357
+ if (!state2.positions || state2.idx === void 0) return true;
65857
66358
  for (let i = 0; i < 66; i++) {
65858
- if (state.idx >= state.positions.length) return true;
65859
- const [row, x] = state.positions[state.idx];
66359
+ if (state2.idx >= state2.positions.length) return true;
66360
+ const [row, x] = state2.positions[state2.idx];
65860
66361
  this.currentArminGrid[row][x] = this.finalArminGrid[row][x];
65861
- state.idx++;
66362
+ state2.idx++;
65862
66363
  }
65863
66364
  return false;
65864
66365
  }
@@ -68647,16 +69148,16 @@ async function importLiveRunSidebar() {
68647
69148
  }
68648
69149
  return _cachedLiveRunSidebar;
68649
69150
  }
68650
- async function installLiveSidebar(ctx, runId, state, deps) {
69151
+ async function installLiveSidebar(ctx, runId, state2, deps) {
68651
69152
  const uiConfig = loadConfig(ctx.cwd).config.ui;
68652
69153
  const autoOpen = uiConfig?.autoOpenDashboard === true;
68653
69154
  const foregroundAutoOpen = uiConfig?.autoOpenDashboardForForegroundRuns ?? DEFAULT_UI.autoOpenDashboardForForegroundRuns;
68654
69155
  if (!ctx.hasUI || !autoOpen || !foregroundAutoOpen || (uiConfig?.dashboardPlacement ?? DEFAULT_UI.dashboardPlacement) !== "right") {
68655
69156
  return false;
68656
69157
  }
68657
- if (state.liveSidebarRunId === runId) return true;
68658
- state.liveSidebarRunId = runId;
68659
- state.dashboardOpened = true;
69158
+ if (state2.liveSidebarRunId === runId) return true;
69159
+ state2.liveSidebarRunId = runId;
69160
+ state2.dashboardOpened = true;
68660
69161
  const widgetPlacement = uiConfig?.widgetPlacement ?? DEFAULT_UI.widgetPlacement;
68661
69162
  setExtensionWidget(ctx, "pi-crew", void 0, { placement: widgetPlacement });
68662
69163
  setExtensionWidget(ctx, "pi-crew-active", void 0, { placement: widgetPlacement });
@@ -68692,7 +69193,7 @@ async function installLiveSidebar(ctx, runId, state, deps) {
68692
69193
  }
68693
69194
  }
68694
69195
  ).finally(() => {
68695
- if (state.liveSidebarRunId === runId) state.liveSidebarRunId = void 0;
69196
+ if (state2.liveSidebarRunId === runId) state2.liveSidebarRunId = void 0;
68696
69197
  const c = deps.getCurrentCtx();
68697
69198
  if (!c) return;
68698
69199
  updateCrewWidget(
@@ -68709,12 +69210,12 @@ async function installLiveSidebar(ctx, runId, state, deps) {
68709
69210
  return false;
68710
69211
  }
68711
69212
  }
68712
- function clearDashboardPowerbar(state, deps) {
69213
+ function clearDashboardPowerbar(state2, deps) {
68713
69214
  const c = deps.getCurrentCtx();
68714
69215
  if (c) stopCrewWidget(c, deps.widgetState, loadConfig(c.cwd).config.ui);
68715
69216
  clearPiCrewPowerbar(deps.pi.events);
68716
69217
  resetPowerbarDedupState();
68717
- state.dashboardOpened = false;
69218
+ state2.dashboardOpened = false;
68718
69219
  }
68719
69220
  function registerPowerbarSegments(deps, uiConfig) {
68720
69221
  registerPiCrewPowerbarSegments(deps.pi.events, uiConfig);
@@ -68920,31 +69421,31 @@ function markDeadAsyncRunIfNeeded(run, now = Date.now(), quietMs = 3e4) {
68920
69421
  return failed;
68921
69422
  });
68922
69423
  }
68923
- function startAsyncRunNotifier(ctx, state, intervalMs = 5e3, options = {}) {
68924
- if (state.interval) clearInterval(state.interval);
68925
- const generation = options.generation ?? (state.generation ?? 0) + 1;
68926
- state.generation = generation;
69424
+ function startAsyncRunNotifier(ctx, state2, intervalMs = 5e3, options = {}) {
69425
+ if (state2.interval) clearInterval(state2.interval);
69426
+ const generation = options.generation ?? (state2.generation ?? 0) + 1;
69427
+ state2.generation = generation;
68927
69428
  const startedAtMs = Date.now();
68928
- const staleBeforeMs = state.lastStoppedAtMs ?? startedAtMs;
69429
+ const staleBeforeMs = state2.lastStoppedAtMs ?? startedAtMs;
68929
69430
  const sid = extractSessionId(ctx);
68930
69431
  const ownsRun = (run) => !sid || !run.ownerSessionId || run.ownerSessionId === sid;
68931
69432
  for (const run of listRuns(ctx.cwd).filter(ownsRun)) {
68932
69433
  const updatedAtMs = timeMs(run.updatedAt) ?? 0;
68933
- if (isFinished2(run.status) && updatedAtMs < staleBeforeMs) state.seenFinishedRunIds.add(run.runId);
69434
+ if (isFinished2(run.status) && updatedAtMs < staleBeforeMs) state2.seenFinishedRunIds.add(run.runId);
68934
69435
  }
68935
69436
  let cachedRuns;
68936
- state.interval = setInterval(() => {
69437
+ state2.interval = setInterval(() => {
68937
69438
  try {
68938
69439
  if (options.isCurrent && !options.isCurrent(generation)) return;
68939
69440
  const nowMs3 = Date.now();
68940
- if (cachedRuns === void 0 || nowMs3 - (state.lastListRunsMs ?? 0) > LIST_RUNS_DEBOUNCE_MS) {
69441
+ if (cachedRuns === void 0 || nowMs3 - (state2.lastListRunsMs ?? 0) > LIST_RUNS_DEBOUNCE_MS) {
68941
69442
  cachedRuns = listRuns(ctx.cwd).filter(ownsRun).slice(0, 20);
68942
- state.lastListRunsMs = nowMs3;
69443
+ state2.lastListRunsMs = nowMs3;
68943
69444
  }
68944
69445
  for (const run of cachedRuns) {
68945
69446
  const current = markDeadAsyncRunIfNeeded(run) ?? run;
68946
- if (!isFinished2(current.status) || state.seenFinishedRunIds.has(current.runId)) continue;
68947
- state.seenFinishedRunIds.add(current.runId);
69447
+ if (!isFinished2(current.status) || state2.seenFinishedRunIds.has(current.runId)) continue;
69448
+ state2.seenFinishedRunIds.add(current.runId);
68948
69449
  if (current.workflow === "goal-turn" && current.team.startsWith("goal-")) continue;
68949
69450
  const level = current.status === "completed" ? "info" : current.status === "cancelled" ? "warning" : "error";
68950
69451
  ctx.ui.notify(`pi-crew run ${current.status}: ${current.runId} (${current.team}/${current.workflow ?? "none"})`, level);
@@ -68957,13 +69458,13 @@ function startAsyncRunNotifier(ctx, state, intervalMs = 5e3, options = {}) {
68957
69458
  logInternalError("async-notifier", error, `interval=${intervalMs}`);
68958
69459
  }
68959
69460
  }, intervalMs);
68960
- if (typeof state.interval.unref === "function") state.interval.unref();
69461
+ if (typeof state2.interval.unref === "function") state2.interval.unref();
68961
69462
  }
68962
- function stopAsyncRunNotifier(state) {
68963
- if (state.interval) clearInterval(state.interval);
68964
- state.interval = void 0;
68965
- state.generation = (state.generation ?? 0) + 1;
68966
- state.lastStoppedAtMs = Date.now();
69463
+ function stopAsyncRunNotifier(state2) {
69464
+ if (state2.interval) clearInterval(state2.interval);
69465
+ state2.interval = void 0;
69466
+ state2.generation = (state2.generation ?? 0) + 1;
69467
+ state2.lastStoppedAtMs = Date.now();
68967
69468
  }
68968
69469
  var LIST_RUNS_DEBOUNCE_MS;
68969
69470
  var init_async_notifier = __esm({
@@ -69419,7 +69920,7 @@ var init_overflow_recovery = __esm({
69419
69920
  default:
69420
69921
  break;
69421
69922
  }
69422
- const state = {
69923
+ const state2 = {
69423
69924
  taskId,
69424
69925
  runId,
69425
69926
  phase,
@@ -69428,14 +69929,14 @@ var init_overflow_recovery = __esm({
69428
69929
  compactionCount,
69429
69930
  retryCount
69430
69931
  };
69431
- this.states.set(key, state);
69932
+ this.states.set(key, state2);
69432
69933
  this.resetTimeout(key);
69433
69934
  if (this.states.size > MAX_TRACKED_STATES) {
69434
69935
  this.evictOldestTerminalState();
69435
69936
  }
69436
69937
  if (previousPhase !== phase && this.callbacks.onPhaseChange) {
69437
69938
  try {
69438
- this.callbacks.onPhaseChange(state, previousPhase);
69939
+ this.callbacks.onPhaseChange(state2, previousPhase);
69439
69940
  } catch (error) {
69440
69941
  logInternalError("overflow-recovery.onPhaseChange", error, `taskId=${taskId}`);
69441
69942
  }
@@ -69444,13 +69945,13 @@ var init_overflow_recovery = __esm({
69444
69945
  }
69445
69946
  getState(taskId, runId) {
69446
69947
  if (runId) return this.states.get(this.keyFor(taskId, runId));
69447
- return [...this.states.values()].find((state) => state.taskId === taskId);
69948
+ return [...this.states.values()].find((state2) => state2.taskId === taskId);
69448
69949
  }
69449
69950
  getPhase(taskId, runId) {
69450
69951
  return this.getState(taskId, runId)?.phase ?? "none";
69451
69952
  }
69452
69953
  removeTask(taskId, runId) {
69453
- const keys = runId ? [this.keyFor(taskId, runId)] : [...this.states.entries()].filter(([, state]) => state.taskId === taskId).map(([key]) => key);
69954
+ const keys = runId ? [this.keyFor(taskId, runId)] : [...this.states.entries()].filter(([, state2]) => state2.taskId === taskId).map(([key]) => key);
69454
69955
  for (const key of keys) this.removeKey(key);
69455
69956
  }
69456
69957
  /**
@@ -69462,10 +69963,10 @@ var init_overflow_recovery = __esm({
69462
69963
  evictOldestTerminalState() {
69463
69964
  let oldestKey;
69464
69965
  let oldestTimestamp = Infinity;
69465
- for (const [key, state] of this.states) {
69466
- const isTerminal = state.phase === "recovered" || state.phase === "failed" || state.phase === "none";
69467
- if (isTerminal && state.lastEventAt < oldestTimestamp) {
69468
- oldestTimestamp = state.lastEventAt;
69966
+ for (const [key, state2] of this.states) {
69967
+ const isTerminal = state2.phase === "recovered" || state2.phase === "failed" || state2.phase === "none";
69968
+ if (isTerminal && state2.lastEventAt < oldestTimestamp) {
69969
+ oldestTimestamp = state2.lastEventAt;
69469
69970
  oldestKey = key;
69470
69971
  }
69471
69972
  }
@@ -69494,27 +69995,27 @@ var init_overflow_recovery = __esm({
69494
69995
  const timeoutMs = current?.phase === "recovered" || current?.phase === "failed" || current?.phase === "none" ? TERMINAL_STATE_TTL_MS : PHASE_TIMEOUT_MS;
69495
69996
  const timer = setTimeout(() => {
69496
69997
  this.timers.delete(key);
69497
- const state = this.states.get(key);
69498
- if (!state) return;
69499
- if (state.phase === "recovered" || state.phase === "failed" || state.phase === "none") {
69998
+ const state2 = this.states.get(key);
69999
+ if (!state2) return;
70000
+ if (state2.phase === "recovered" || state2.phase === "failed" || state2.phase === "none") {
69500
70001
  this.states.delete(key);
69501
70002
  return;
69502
70003
  }
69503
- const previousPhase = state.phase;
69504
- state.phase = "failed";
69505
- state.lastEventAt = Date.now();
70004
+ const previousPhase = state2.phase;
70005
+ state2.phase = "failed";
70006
+ state2.lastEventAt = Date.now();
69506
70007
  if (this.callbacks.onTimeout) {
69507
70008
  try {
69508
- this.callbacks.onTimeout(state);
70009
+ this.callbacks.onTimeout(state2);
69509
70010
  } catch (error) {
69510
- logInternalError("overflow-recovery.onTimeout", error, `taskId=${state.taskId}`);
70011
+ logInternalError("overflow-recovery.onTimeout", error, `taskId=${state2.taskId}`);
69511
70012
  }
69512
70013
  }
69513
70014
  if (this.callbacks.onPhaseChange) {
69514
70015
  try {
69515
- this.callbacks.onPhaseChange(state, previousPhase);
70016
+ this.callbacks.onPhaseChange(state2, previousPhase);
69516
70017
  } catch (error) {
69517
- logInternalError("overflow-recovery.onPhaseChange-timeout", error, `taskId=${state.taskId}`);
70018
+ logInternalError("overflow-recovery.onPhaseChange-timeout", error, `taskId=${state2.taskId}`);
69518
70019
  }
69519
70020
  }
69520
70021
  }, timeoutMs);
@@ -69535,10 +70036,10 @@ __export(lifecycle_exports, {
69535
70036
  startLifecycleWatchers: () => startLifecycleWatchers,
69536
70037
  stopLifecycleWatchers: () => stopLifecycleWatchers
69537
70038
  });
69538
- function startLifecycleWatchers(ctx, state, deps) {
69539
- if (state.notifierStarted) return false;
70039
+ function startLifecycleWatchers(ctx, state2, deps) {
70040
+ if (state2.notifierStarted) return false;
69540
70041
  const loadedConfig = loadConfig(ctx.cwd);
69541
- state.notifierStarted = true;
70042
+ state2.notifierStarted = true;
69542
70043
  try {
69543
70044
  void Promise.resolve().then(() => (init_async_notifier(), async_notifier_exports)).then(({ startAsyncRunNotifier: startAsyncRunNotifier2 }) => {
69544
70045
  if (deps.isCleanedUp()) return;
@@ -69549,14 +70050,14 @@ function startLifecycleWatchers(ctx, state, deps) {
69549
70050
  });
69550
70051
  return true;
69551
70052
  } catch (error) {
69552
- state.notifierStarted = false;
70053
+ state2.notifierStarted = false;
69553
70054
  logInternalError("register.startLifecycleWatchers", error);
69554
70055
  return false;
69555
70056
  }
69556
70057
  }
69557
- function stopLifecycleWatchers(state, deps) {
69558
- if (!state.notifierStarted) return;
69559
- state.notifierStarted = false;
70058
+ function stopLifecycleWatchers(state2, deps) {
70059
+ if (!state2.notifierStarted) return;
70060
+ state2.notifierStarted = false;
69560
70061
  try {
69561
70062
  void Promise.resolve().then(() => (init_async_notifier(), async_notifier_exports)).then(({ stopAsyncRunNotifier: stopAsyncRunNotifier2 }) => {
69562
70063
  stopAsyncRunNotifier2(deps.notifierState);
@@ -69565,11 +70066,11 @@ function stopLifecycleWatchers(state, deps) {
69565
70066
  logInternalError("register.stopLifecycleWatchers", error);
69566
70067
  }
69567
70068
  }
69568
- async function configureNotifications(ctx, state, deps) {
69569
- state.notificationRouter?.dispose();
69570
- state.notificationSink?.dispose();
69571
- state.notificationRouter = void 0;
69572
- state.notificationSink = void 0;
70069
+ async function configureNotifications(ctx, state2, deps) {
70070
+ state2.notificationRouter?.dispose();
70071
+ state2.notificationSink?.dispose();
70072
+ state2.notificationRouter = void 0;
70073
+ state2.notificationSink = void 0;
69573
70074
  const config = loadConfig(ctx.cwd).config;
69574
70075
  if (config.notifications?.enabled === false) return;
69575
70076
  const { NotificationRouter: NotificationRouter2 } = await Promise.resolve().then(() => (init_notification_router(), notification_router_exports));
@@ -69577,18 +70078,18 @@ async function configureNotifications(ctx, state, deps) {
69577
70078
  const { sendFollowUp: sendFollowUp2 } = await Promise.resolve().then(() => (init_subagent_helpers(), subagent_helpers_exports));
69578
70079
  const { updateCrewWidget: updateCrewWidget2 } = await Promise.resolve().then(() => (init_widget(), widget_exports));
69579
70080
  if (config.telemetry?.enabled !== false) {
69580
- state.notificationSink = createJsonlSink2(
70081
+ state2.notificationSink = createJsonlSink2(
69581
70082
  projectCrewRoot(ctx.cwd),
69582
70083
  config.notifications?.sinkRetentionDays ?? DEFAULT_NOTIFICATIONS.sinkRetentionDays
69583
70084
  );
69584
70085
  }
69585
- state.notificationRouter = new NotificationRouter2(
70086
+ state2.notificationRouter = new NotificationRouter2(
69586
70087
  {
69587
70088
  dedupWindowMs: config.notifications?.dedupWindowMs ?? DEFAULT_NOTIFICATIONS.dedupWindowMs,
69588
70089
  batchWindowMs: config.notifications?.batchWindowMs ?? DEFAULT_NOTIFICATIONS.batchWindowMs,
69589
70090
  quietHours: config.notifications?.quietHours,
69590
70091
  severityFilter: config.notifications?.severityFilter ?? [...DEFAULT_NOTIFICATIONS.severityFilter],
69591
- sink: (notification) => state.notificationSink?.write(notification)
70092
+ sink: (notification) => state2.notificationSink?.write(notification)
69592
70093
  },
69593
70094
  (notification) => {
69594
70095
  deps.widgetState.notificationCount = (deps.widgetState.notificationCount ?? 0) + 1;
@@ -69619,20 +70120,20 @@ async function configureNotifications(ctx, state, deps) {
69619
70120
  }
69620
70121
  );
69621
70122
  }
69622
- function disposeNotifications(state) {
69623
- state.notificationRouter?.dispose();
69624
- state.notificationRouter = void 0;
69625
- state.notificationSink?.dispose();
69626
- state.notificationSink = void 0;
69627
- }
69628
- async function configureDeliveryCoordinator(state, deps) {
69629
- state.deliveryCoordinator?.dispose();
69630
- state.deliveryCoordinator = void 0;
69631
- state.overflowTracker?.dispose();
69632
- state.overflowTracker = void 0;
70123
+ function disposeNotifications(state2) {
70124
+ state2.notificationRouter?.dispose();
70125
+ state2.notificationRouter = void 0;
70126
+ state2.notificationSink?.dispose();
70127
+ state2.notificationSink = void 0;
70128
+ }
70129
+ async function configureDeliveryCoordinator(state2, deps) {
70130
+ state2.deliveryCoordinator?.dispose();
70131
+ state2.deliveryCoordinator = void 0;
70132
+ state2.overflowTracker?.dispose();
70133
+ state2.overflowTracker = void 0;
69633
70134
  const { DeliveryCoordinator: DeliveryCoordinator2 } = await Promise.resolve().then(() => (init_delivery_coordinator(), delivery_coordinator_exports));
69634
70135
  const { OverflowRecoveryTracker: OverflowRecoveryTracker2 } = await Promise.resolve().then(() => (init_overflow_recovery(), overflow_recovery_exports));
69635
- state.deliveryCoordinator = new DeliveryCoordinator2({
70136
+ state2.deliveryCoordinator = new DeliveryCoordinator2({
69636
70137
  emit: (event, data) => {
69637
70138
  deps.pi.events?.emit?.(event, data);
69638
70139
  },
@@ -69643,7 +70144,7 @@ async function configureDeliveryCoordinator(state, deps) {
69643
70144
  deps.sendAgentWakeUp(deps.pi, message);
69644
70145
  }
69645
70146
  });
69646
- state.overflowTracker = new OverflowRecoveryTracker2({
70147
+ state2.overflowTracker = new OverflowRecoveryTracker2({
69647
70148
  onPhaseChange: (phaseState, previousPhase) => {
69648
70149
  if (deps.observabilityState.metricRegistry) {
69649
70150
  deps.observabilityState.metricRegistry.counter("crew.task.overflow_recovery_total", "Overflow recovery phase transitions").inc({
@@ -69670,11 +70171,11 @@ async function configureDeliveryCoordinator(state, deps) {
69670
70171
  }
69671
70172
  });
69672
70173
  }
69673
- function disposeDeliveryCoordinator(state) {
69674
- state.deliveryCoordinator?.dispose();
69675
- state.deliveryCoordinator = void 0;
69676
- state.overflowTracker?.dispose();
69677
- state.overflowTracker = void 0;
70174
+ function disposeDeliveryCoordinator(state2) {
70175
+ state2.deliveryCoordinator?.dispose();
70176
+ state2.deliveryCoordinator = void 0;
70177
+ state2.overflowTracker?.dispose();
70178
+ state2.overflowTracker = void 0;
69678
70179
  }
69679
70180
  var init_lifecycle = __esm({
69680
70181
  "src/extension/registration/lifecycle.ts"() {
@@ -70651,21 +71152,21 @@ async function importOTLPExporter() {
70651
71152
  }
70652
71153
  return _cachedOTLPExporter;
70653
71154
  }
70654
- async function configureObservability(ctx, state, deps) {
70655
- await disposeObservability(state, deps.isCleanedUp());
71155
+ async function configureObservability(ctx, state2, deps) {
71156
+ await disposeObservability(state2, deps.isCleanedUp());
70656
71157
  const config = loadConfig(ctx.cwd).config;
70657
71158
  if (config.observability?.enabled === false) return;
70658
71159
  const { createMetricRegistry: createMetricRegistry2 } = await Promise.resolve().then(() => (init_metric_registry(), metric_registry_exports));
70659
71160
  const { wireEventToMetrics: wireEventToMetrics2 } = await Promise.resolve().then(() => (init_event_to_metric(), event_to_metric_exports));
70660
71161
  const { createMetricFileSink: createMetricFileSink2 } = await Promise.resolve().then(() => (init_metric_sink(), metric_sink_exports));
70661
- state.metricRegistry = createMetricRegistry2();
71162
+ state2.metricRegistry = createMetricRegistry2();
70662
71163
  if (deps.pi.events) {
70663
- state.eventMetricSub = wireEventToMetrics2(deps.pi.events, state.metricRegistry);
71164
+ state2.eventMetricSub = wireEventToMetrics2(deps.pi.events, state2.metricRegistry);
70664
71165
  }
70665
71166
  if (config.telemetry?.enabled !== false) {
70666
- state.metricSink = createMetricFileSink2({
71167
+ state2.metricSink = createMetricFileSink2({
70667
71168
  crewRoot: projectCrewRoot(ctx.cwd),
70668
- registry: state.metricRegistry,
71169
+ registry: state2.metricRegistry,
70669
71170
  retentionDays: config.observability?.metricRetentionDays ?? 7
70670
71171
  });
70671
71172
  }
@@ -70673,10 +71174,10 @@ async function configureObservability(ctx, state, deps) {
70673
71174
  const otlpEndpoint = config.otlp.endpoint;
70674
71175
  const otlpHeaders = config.otlp.headers;
70675
71176
  const otlpInterval = config.otlp.intervalMs;
70676
- const owningRegistry = state.metricRegistry;
71177
+ const owningRegistry = state2.metricRegistry;
70677
71178
  void importOTLPExporter().then((Ctor) => {
70678
- if (deps.isCleanedUp() || state.metricRegistry !== owningRegistry || !owningRegistry) return;
70679
- state.otlpExporter = new Ctor(
71179
+ if (deps.isCleanedUp() || state2.metricRegistry !== owningRegistry || !owningRegistry) return;
71180
+ state2.otlpExporter = new Ctor(
70680
71181
  {
70681
71182
  endpoint: otlpEndpoint,
70682
71183
  headers: otlpHeaders,
@@ -70684,15 +71185,15 @@ async function configureObservability(ctx, state, deps) {
70684
71185
  },
70685
71186
  owningRegistry
70686
71187
  );
70687
- state.otlpExporter?.start();
71188
+ state2.otlpExporter?.start();
70688
71189
  }).catch((error) => logInternalError("register.otlp-lazy-import", error));
70689
71190
  }
70690
71191
  const { HeartbeatWatcher: HeartbeatWatcher2 } = await Promise.resolve().then(() => (init_heartbeat_watcher(), heartbeat_watcher_exports));
70691
- state.heartbeatWatcher = new HeartbeatWatcher2({
71192
+ state2.heartbeatWatcher = new HeartbeatWatcher2({
70692
71193
  cwd: ctx.cwd,
70693
71194
  pollIntervalMs: config.observability?.pollIntervalMs ?? 5e3,
70694
71195
  manifestCache: deps.getManifestCache(ctx.cwd),
70695
- registry: state.metricRegistry,
71196
+ registry: state2.metricRegistry,
70696
71197
  router: {
70697
71198
  enqueue: (notification) => {
70698
71199
  deps.notifyOperator(notification);
@@ -70708,7 +71209,7 @@ async function configureObservability(ctx, state, deps) {
70708
71209
  attempts: 0,
70709
71210
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
70710
71211
  });
70711
- state.metricRegistry?.counter("crew.task.deadletter_total", "Deadletter triggers by reason").inc({ reason: "heartbeat-dead" });
71212
+ state2.metricRegistry?.counter("crew.task.deadletter_total", "Deadletter triggers by reason").inc({ reason: "heartbeat-dead" });
70712
71213
  deps.pi.events?.emit?.("crew.task.deadletter", {
70713
71214
  runId: manifest.runId,
70714
71215
  taskId,
@@ -70716,7 +71217,7 @@ async function configureObservability(ctx, state, deps) {
70716
71217
  });
70717
71218
  }
70718
71219
  });
70719
- state.heartbeatWatcher.start();
71220
+ state2.heartbeatWatcher.start();
70720
71221
  try {
70721
71222
  deps.pi.on?.("before_agent_start", () => {
70722
71223
  if (deps.isCleanedUp()) return;
@@ -70730,7 +71231,7 @@ async function configureObservability(ctx, state, deps) {
70730
71231
  }
70731
71232
  const autoRepairIntervalMs = config.reliability?.autoRepairIntervalMs ?? 3e5;
70732
71233
  if (autoRepairIntervalMs > 0) {
70733
- state.autoRepairTimer = setInterval(() => {
71234
+ state2.autoRepairTimer = setInterval(() => {
70734
71235
  if (deps.isCleanedUp()) return;
70735
71236
  try {
70736
71237
  const staleResults = deps.reconcileStaleRuns(ctx.cwd, deps.getManifestCache(ctx.cwd), extractSessionId(ctx));
@@ -70753,8 +71254,8 @@ async function configureObservability(ctx, state, deps) {
70753
71254
  logInternalError("register.autoRepair", error);
70754
71255
  }
70755
71256
  }, autoRepairIntervalMs);
70756
- state.autoRepairTimer.unref();
70757
- state.tempReconcileTimer = setInterval(() => {
71257
+ state2.autoRepairTimer.unref();
71258
+ state2.tempReconcileTimer = setInterval(() => {
70758
71259
  if (deps.isCleanedUp()) return;
70759
71260
  try {
70760
71261
  deps.reconcileOrphanedTempWorkspaces(Date.now(), {
@@ -70784,7 +71285,7 @@ async function configureObservability(ctx, state, deps) {
70784
71285
  logInternalError("register.tempAutoRepair", error);
70785
71286
  }
70786
71287
  }, autoRepairIntervalMs * 5);
70787
- state.tempReconcileTimer.unref();
71288
+ state2.tempReconcileTimer.unref();
70788
71289
  }
70789
71290
  if (config.reliability?.autoRecover === true) {
70790
71291
  const cwdSnapshot = ctx.cwd;
@@ -70805,25 +71306,25 @@ async function configureObservability(ctx, state, deps) {
70805
71306
  }).catch((error) => logInternalError("register.crash-recovery-lazy-import", error));
70806
71307
  }
70807
71308
  }
70808
- async function disposeObservability(state, _isCleanedUp) {
70809
- state.heartbeatWatcher?.dispose();
70810
- state.heartbeatWatcher = void 0;
70811
- if (state.autoRepairTimer) {
70812
- clearInterval(state.autoRepairTimer);
70813
- state.autoRepairTimer = void 0;
71309
+ async function disposeObservability(state2, _isCleanedUp) {
71310
+ state2.heartbeatWatcher?.dispose();
71311
+ state2.heartbeatWatcher = void 0;
71312
+ if (state2.autoRepairTimer) {
71313
+ clearInterval(state2.autoRepairTimer);
71314
+ state2.autoRepairTimer = void 0;
70814
71315
  }
70815
- if (state.tempReconcileTimer) {
70816
- clearInterval(state.tempReconcileTimer);
70817
- state.tempReconcileTimer = void 0;
71316
+ if (state2.tempReconcileTimer) {
71317
+ clearInterval(state2.tempReconcileTimer);
71318
+ state2.tempReconcileTimer = void 0;
70818
71319
  }
70819
- state.metricSink?.dispose();
70820
- state.metricSink = void 0;
70821
- state.eventMetricSub?.dispose();
70822
- state.eventMetricSub = void 0;
70823
- await state.otlpExporter?.dispose();
70824
- state.otlpExporter = void 0;
70825
- state.metricRegistry?.dispose();
70826
- state.metricRegistry = void 0;
71320
+ state2.metricSink?.dispose();
71321
+ state2.metricSink = void 0;
71322
+ state2.eventMetricSub?.dispose();
71323
+ state2.eventMetricSub = void 0;
71324
+ await state2.otlpExporter?.dispose();
71325
+ state2.otlpExporter = void 0;
71326
+ state2.metricRegistry?.dispose();
71327
+ state2.metricRegistry = void 0;
70827
71328
  }
70828
71329
  var _cachedOTLPExporter;
70829
71330
  var init_observability = __esm({
@@ -76163,7 +76664,10 @@ function safeStringify(value) {
76163
76664
  // src/extension/registration/lifecycle-handlers.ts
76164
76665
  init_child_pi();
76165
76666
  init_live_agent_manager();
76667
+ init_model_fallback();
76166
76668
  init_pi_args();
76669
+ init_provider_quota();
76670
+ init_session_model();
76167
76671
  init_orphan_worker_registry();
76168
76672
  init_crash_recovery();
76169
76673
  init_scheduler();
@@ -76449,6 +76953,20 @@ function installSessionLifecycleHandlers(pi, ctx) {
76449
76953
  installSessionShutdownHandler(pi, ctx);
76450
76954
  installSessionStartHandler(pi, ctx);
76451
76955
  installSessionBeforeSwitchHandler(pi, ctx);
76956
+ installModelTrackingHandlers(pi);
76957
+ }
76958
+ function installModelTrackingHandlers(pi) {
76959
+ pi.on("model_select", (event) => {
76960
+ noteSessionModel(event.model);
76961
+ });
76962
+ pi.on("thinking_level_select", (event) => {
76963
+ noteSessionThinking(event.level);
76964
+ });
76965
+ pi.on("after_provider_response", (event) => {
76966
+ const model = currentSessionModel();
76967
+ const provider = model ? providerOfModelRef(model) : void 0;
76968
+ if (provider) noteProviderResponse(provider, event.status, event.headers);
76969
+ });
76452
76970
  }
76453
76971
  function installSessionShutdownHandler(pi, ctx) {
76454
76972
  pi.on("session_shutdown", (event) => {
@@ -76478,6 +76996,7 @@ function installSessionBeforeSwitchHandler(pi, ctx) {
76478
76996
  ctx.lifecycleState.deliveryCoordinator?.deactivate();
76479
76997
  resetPowerbarDedupState();
76480
76998
  stopAsyncRunNotifier(ctx.notifierState);
76999
+ clearProviderQuotaCache();
76481
77000
  ctx.stopSessionBoundSubagents();
76482
77001
  });
76483
77002
  }
@@ -76498,6 +77017,8 @@ function installSessionStartHandler(pi, ctx) {
76498
77017
  ctx.sessionGeneration++;
76499
77018
  const ownerGeneration = ctx.sessionGeneration;
76500
77019
  ctx.currentCtx = extensionCtx;
77020
+ noteSessionModel(extensionCtx.model, "session_start");
77021
+ noteSessionThinking(extensionCtx.thinkingLevel);
76501
77022
  if (!ctx.crewAutocompleteRegistered) {
76502
77023
  ctx.crewAutocompleteRegistered = true;
76503
77024
  registerCrewAutocomplete(extensionCtx);