mastracode 0.45.0-alpha.7 → 0.45.0

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.
@@ -5,7 +5,7 @@ import { ThreadLockError } from "@mastra/code-sdk/utils/thread-lock";
5
5
  import { mastraBrand } from "@mastra/code-sdk/theme-palette";
6
6
  import chalk from "chalk";
7
7
  import { execFile, execFileSync, spawn } from "child_process";
8
- import { ACCOUNT_SWITCH_PART_TYPE, PACK_FALLBACK_STATE_KEY, accountSwitchNoticeText, isAccountSwitchReason, isPackFallbackReason, packFallbackNoticeText, providerFromModelId } from "@mastra/code-sdk/auth/account-rotation-processor";
8
+ import { ACCOUNT_SWITCH_PART_TYPE, MODEL_FALLBACK_PART_TYPE, MODEL_FALLBACK_STATE_KEY, accountSwitchNoticeText, isAccountSwitchReason, isModelFallbackReason, modelFallbackNoticeText, providerFromModelId } from "@mastra/code-sdk/auth/account-rotation-processor";
9
9
  import { PROVIDER_DEFAULT_MODELS, getOAuthProviders } from "@mastra/code-sdk/auth/storage";
10
10
  import { ONBOARDING_VERSION, getAvailableModePacks, getAvailableOmPacks, loadSettings as loadSettings$1, saveSettings as saveSettings$1, selectPreferredOMPack } from "@mastra/code-sdk/onboarding/index";
11
11
  import { detectPackageManager, fetchChangelog, fetchLatestVersion, isNewerVersion, performUpdate } from "@mastra/code-sdk/utils/update-check";
@@ -27,6 +27,7 @@ import { THINK_COMMAND_DESCRIPTOR, getAvailableThinkingLevelsForModel, isThinkin
27
27
  import { VALID_EVENTS } from "@mastra/code-sdk/hooks/index";
28
28
  import { openUrlInBrowser } from "@mastra/code-sdk/utils/open-url";
29
29
  import { parseError } from "@mastra/code-sdk/utils/errors";
30
+ import { getAvailableModePacks as getAvailableModePacks$1, getBuiltinModePack, listBuiltinModePacks, resolveModePackFallbackChain } from "@mastra/code-sdk/onboarding/packs";
30
31
  import { formatSkillActivation } from "@mastra/core/workspace";
31
32
  import { approvePlanFile, readPlanFile, resolvePlanPath } from "@mastra/code-sdk/utils/plans";
32
33
  import { TASKS_STATE_ID } from "@mastra/core/tools";
@@ -43,7 +44,6 @@ import { highlight } from "cli-highlight";
43
44
  import { TOOL_CATEGORIES, getToolCategory } from "@mastra/code-sdk/permissions";
44
45
  import { parse } from "jsonriver";
45
46
  import { stripVTControlCharacters } from "util";
46
- import { getAvailableModePacks as getAvailableModePacks$1, getBuiltinModePack, resolveModePackFallbackChain } from "@mastra/code-sdk/onboarding/packs";
47
47
  import { getClipboardImage, getClipboardText, setClipboardText } from "@mastra/code-sdk/clipboard/index";
48
48
  import { removeCustomPackFromSettings } from "@mastra/code-sdk/onboarding/custom-packs";
49
49
  import { removeCustomProviderFromSettings, upsertCustomProviderInSettings } from "@mastra/code-sdk/onboarding/custom-providers";
@@ -60,7 +60,6 @@ import { deleteWorkflow, getWorkflow, listWorkflows, runWorkflow } from "@mastra
60
60
  import { RequestContext } from "@mastra/core/request-context";
61
61
  import { MaintenanceLockError, acquireMaintenanceLock } from "@mastra/code-sdk/utils/maintenance-lock";
62
62
  import { runStorageMaintenance } from "@mastra/code-sdk/utils/storage-maintenance";
63
- import { listResolvableModePacks } from "@mastra/code-sdk/agents/model";
64
63
  import { fileURLToPath } from "url";
65
64
  import { loadCustomCommands } from "@mastra/code-sdk/utils/slash-command-loader";
66
65
  //#region src/commit-attribution.ts
@@ -675,22 +674,21 @@ const OM_EVENT_BY_TYPE = {
675
674
  "data-om-observation-failed": "failed",
676
675
  "data-om-thread-update": "thread-title"
677
676
  };
678
- const PACK_FALLBACK_PART_TYPE = "data-mastracode-pack-fallback";
679
677
  function packFallbackRenderPart(data) {
680
678
  if (!data || typeof data !== "object") return null;
681
679
  const record = data;
682
680
  const endpoint = (value) => {
683
681
  if (!value || typeof value !== "object") return null;
684
682
  const entry = value;
685
- if (typeof entry.packId !== "string" || typeof entry.label !== "string") return null;
683
+ if (typeof entry.entryId !== "string" || typeof entry.label !== "string") return null;
686
684
  return {
687
- packId: entry.packId,
685
+ entryId: entry.entryId,
688
686
  label: entry.label
689
687
  };
690
688
  };
691
689
  const from = endpoint(record.from);
692
690
  const to = endpoint(record.to);
693
- if (!from || !to || !isPackFallbackReason(record.reason)) return null;
691
+ if (!from || !to || !isModelFallbackReason(record.reason)) return null;
694
692
  return {
695
693
  kind: "pack-fallback",
696
694
  from,
@@ -857,7 +855,7 @@ function getAssistantRenderParts(message) {
857
855
  if (switchPart) out.push(switchPart);
858
856
  break;
859
857
  }
860
- if (partType === PACK_FALLBACK_PART_TYPE) {
858
+ if (partType === MODEL_FALLBACK_PART_TYPE) {
861
859
  const hopPart = packFallbackRenderPart(part.data);
862
860
  if (hopPart) out.push(hopPart);
863
861
  break;
@@ -2271,7 +2269,6 @@ async function handleGoalCommand(ctx, args) {
2271
2269
  return;
2272
2270
  }
2273
2271
  }
2274
- state.planStartedGoalId = void 0;
2275
2272
  if (state.session.run.isRunning() || state.session.suspensions.hasPending()) {
2276
2273
  state.activeInlineQuestion = void 0;
2277
2274
  state.pendingInlineQuestions.length = 0;
@@ -2472,7 +2469,6 @@ async function setGoal(ctx, objective, judgeModelId, maxTurns) {
2472
2469
  ctx.showError("Failed to set goal.");
2473
2470
  return null;
2474
2471
  }
2475
- state.planStartedGoalId = void 0;
2476
2472
  await goalManager.saveToThread(state);
2477
2473
  ctx.updateStatusLine();
2478
2474
  return goal;
@@ -5126,6 +5122,216 @@ function showTextStatus(ctx) {
5126
5122
  ctx.showInfo(lines.join("\n"));
5127
5123
  }
5128
5124
  //#endregion
5125
+ //#region src/tui/model-packs/apply.ts
5126
+ const packApplicationVersions = /* @__PURE__ */ new WeakMap();
5127
+ const packApplicationQueues = /* @__PURE__ */ new WeakMap();
5128
+ function beginPackApplication(ctx, modeId, expectedThreadId) {
5129
+ const session = ctx.state.session;
5130
+ const version = (packApplicationVersions.get(session) ?? 0) + 1;
5131
+ packApplicationVersions.set(session, version);
5132
+ const isCurrent = () => packApplicationVersions.get(session) === version && ctx.state.session.mode.get() === modeId && ctx.state.session.thread.getId() === expectedThreadId;
5133
+ const previous = packApplicationQueues.get(session) ?? Promise.resolve();
5134
+ return {
5135
+ isCurrent,
5136
+ run: (apply) => {
5137
+ const result = previous.catch(() => void 0).then(apply);
5138
+ packApplicationQueues.set(session, result.then(() => void 0, () => void 0));
5139
+ return result;
5140
+ }
5141
+ };
5142
+ }
5143
+ function modelRoutesMatch(current, expected) {
5144
+ if (!current || typeof current !== "object") return false;
5145
+ const entries = current.entries;
5146
+ if (!Array.isArray(entries) || entries.length !== expected.entries.length) return false;
5147
+ return expected.entries.every((entry, index) => {
5148
+ const currentEntry = entries[index];
5149
+ if (!currentEntry || typeof currentEntry !== "object") return false;
5150
+ const candidate = currentEntry;
5151
+ return candidate.id === entry.id && candidate.label === entry.label && candidate.modelId === entry.modelId && candidate.accountId === entry.accountId && candidate.memoryModelId === entry.memoryModelId;
5152
+ });
5153
+ }
5154
+ function listResolvableModePacks(settings) {
5155
+ return [...listBuiltinModePacks(), ...settings.customModelPacks.map((pack) => ({
5156
+ id: `custom:${pack.name}`,
5157
+ name: pack.name,
5158
+ description: "Saved custom pack",
5159
+ models: {
5160
+ build: pack.models.build ?? "",
5161
+ plan: pack.models.plan ?? "",
5162
+ fast: pack.models.fast ?? "",
5163
+ ...pack.models.memory ? { memory: pack.models.memory } : {}
5164
+ }
5165
+ }))];
5166
+ }
5167
+ function resolvePackSelection(settings, packId, modeId) {
5168
+ const packs = listResolvableModePacks(settings);
5169
+ const packsById = new Map(packs.map((pack) => [pack.id, pack]));
5170
+ const pack = packsById.get(packId);
5171
+ if (!pack) return void 0;
5172
+ const packModels = resolveModePackModels(settings, pack);
5173
+ const modelId = packModels[modeId];
5174
+ if (!modelId) return void 0;
5175
+ const subagentModels = Object.fromEntries(Object.entries({
5176
+ explore: "fast",
5177
+ plan: "plan",
5178
+ execute: "build"
5179
+ }).flatMap(([agentType, subagentModeId]) => {
5180
+ const subagentModelId = packModels[subagentModeId];
5181
+ return subagentModelId ? [[agentType, subagentModelId]] : [];
5182
+ }));
5183
+ const entries = resolveModePackFallbackChain(settings.models.packFallbacks, packId, settings.customModelPacks).flatMap((routeId) => {
5184
+ const routePack = packsById.get(routeId);
5185
+ if (!routePack) return [];
5186
+ const routeModels = resolveModePackModels(settings, routePack);
5187
+ const routeModelId = routeModels[modeId];
5188
+ if (!routeModelId) return [];
5189
+ const accountId = settings.models.packAccountPreferences?.[routeId]?.[routeModelId];
5190
+ return [{
5191
+ id: routeId,
5192
+ label: routePack.name,
5193
+ modelId: routeModelId,
5194
+ ...accountId ? { accountId } : {},
5195
+ ...routeModels.memory ? { memoryModelId: routeModels.memory } : {}
5196
+ }];
5197
+ });
5198
+ const memoryModelId = packModels.memory;
5199
+ return {
5200
+ modelId,
5201
+ subagentModels,
5202
+ ...memoryModelId ? {
5203
+ observerModelId: memoryModelId,
5204
+ reflectorModelId: memoryModelId
5205
+ } : {},
5206
+ modelRoute: { entries }
5207
+ };
5208
+ }
5209
+ async function applyPackToSession(ctx, packId, options = {}) {
5210
+ const settings = options.settings ?? loadSettings();
5211
+ const modeId = options.modeId ?? ctx.state.session.mode.get();
5212
+ const expectedThreadId = "expectedThreadId" in options ? options.expectedThreadId : ctx.state.session.thread.getId();
5213
+ const selection = resolvePackSelection(settings, packId, modeId);
5214
+ if (!selection) throw new Error(`Model pack "${packId}" has no model for ${modeId} mode`);
5215
+ const application = beginPackApplication(ctx, modeId, expectedThreadId);
5216
+ return application.run(async () => {
5217
+ const cancelled = () => ({
5218
+ ...selection,
5219
+ applied: false
5220
+ });
5221
+ if (!application.isCurrent()) return cancelled();
5222
+ try {
5223
+ if (expectedThreadId) {
5224
+ await ctx.state.session.thread.setSetting({
5225
+ key: THREAD_ACTIVE_MODEL_PACK_ID_KEY,
5226
+ value: packId
5227
+ });
5228
+ if (!application.isCurrent()) return cancelled();
5229
+ if (options.clearPendingFallback !== false) {
5230
+ await ctx.state.session.thread.setSetting({
5231
+ key: MODEL_FALLBACK_STATE_KEY,
5232
+ value: void 0
5233
+ });
5234
+ if (!application.isCurrent()) return cancelled();
5235
+ }
5236
+ }
5237
+ await ctx.state.session.model.switch(selection.modelId, { ...options.thinkingLevel !== void 0 ? { thinkingLevel: options.thinkingLevel } : {} });
5238
+ if (!application.isCurrent()) return cancelled();
5239
+ for (const [agentType, modelId] of Object.entries(selection.subagentModels)) {
5240
+ await ctx.state.session.subagents.model.set({
5241
+ modelId,
5242
+ agentType
5243
+ });
5244
+ if (!application.isCurrent()) return cancelled();
5245
+ }
5246
+ if (selection.observerModelId) {
5247
+ await ctx.state.session.om.observer.switchModel({ modelId: selection.observerModelId });
5248
+ if (!application.isCurrent()) return cancelled();
5249
+ }
5250
+ if (selection.reflectorModelId) {
5251
+ await ctx.state.session.om.reflector.switchModel({ modelId: selection.reflectorModelId });
5252
+ if (!application.isCurrent()) return cancelled();
5253
+ }
5254
+ await ctx.state.session.state.set({
5255
+ modelRoute: selection.modelRoute,
5256
+ ...options.clearPendingFallback === false ? {} : { [MODEL_FALLBACK_STATE_KEY]: null }
5257
+ });
5258
+ if (!application.isCurrent()) return cancelled();
5259
+ await options.afterApply?.(selection);
5260
+ return {
5261
+ ...selection,
5262
+ applied: application.isCurrent()
5263
+ };
5264
+ } catch (error) {
5265
+ if (application.isCurrent()) await options.onError?.(error);
5266
+ throw error;
5267
+ }
5268
+ });
5269
+ }
5270
+ async function resolveActivePackId(ctx, settings) {
5271
+ const threadId = ctx.state.session.thread.getId();
5272
+ const thread = threadId ? (await ctx.state.session.thread.list()).find((item) => item.id === threadId) : void 0;
5273
+ if (ctx.state.session.thread.getId() !== threadId) return void 0;
5274
+ return {
5275
+ packId: resolveThreadActiveModelPackId(settings, listResolvableModePacks(settings), thread?.metadata),
5276
+ threadId
5277
+ };
5278
+ }
5279
+ async function applyCurrentThreadPack(ctx, options = {}) {
5280
+ const settings = loadSettings();
5281
+ const modeId = options.modeId ?? ctx.state.session.mode.get();
5282
+ const resolution = options.packId === void 0 ? await resolveActivePackId(ctx, settings) : {
5283
+ packId: options.packId,
5284
+ threadId: ctx.state.session.thread.getId()
5285
+ };
5286
+ if (!resolution) return { applied: false };
5287
+ if (resolution.packId && resolvePackSelection(settings, resolution.packId, modeId)) return applyPackToSession(ctx, resolution.packId, {
5288
+ modeId,
5289
+ settings,
5290
+ expectedThreadId: resolution.threadId
5291
+ });
5292
+ const application = beginPackApplication(ctx, modeId, resolution.threadId);
5293
+ return application.run(async () => {
5294
+ if (!application.isCurrent()) return { applied: false };
5295
+ if (options.applyModeDefault !== false) {
5296
+ const mode = ctx.state.controller.listModes().find((item) => item.id === modeId);
5297
+ const modelId = settings.models.modeDefaults[modeId] ?? mode?.defaultModelId;
5298
+ if (modelId && ctx.state.session.model.get() !== modelId) {
5299
+ await ctx.state.session.model.switch(modelId);
5300
+ if (!application.isCurrent()) return { applied: false };
5301
+ }
5302
+ }
5303
+ if (resolution.threadId) {
5304
+ await ctx.state.session.thread.setSetting({
5305
+ key: MODEL_FALLBACK_STATE_KEY,
5306
+ value: void 0
5307
+ });
5308
+ if (!application.isCurrent()) return { applied: false };
5309
+ }
5310
+ await ctx.state.session.state.set({
5311
+ modelRoute: void 0,
5312
+ [MODEL_FALLBACK_STATE_KEY]: null
5313
+ });
5314
+ return { applied: application.isCurrent() };
5315
+ });
5316
+ }
5317
+ async function switchModeWithPack(ctx, modeId) {
5318
+ await ctx.state.session.mode.switch({ modeId });
5319
+ if (!(await applyCurrentThreadPack(ctx, { modeId })).applied) throw new Error("Mode switch was superseded before its model selection completed.");
5320
+ }
5321
+ async function reconcilePackAfterModeChange(ctx, modeId) {
5322
+ const settings = loadSettings();
5323
+ const resolution = await resolveActivePackId(ctx, settings);
5324
+ if (!resolution?.packId || ctx.state.session.mode.get() !== modeId) return;
5325
+ const selection = resolvePackSelection(settings, resolution.packId, modeId);
5326
+ if (!selection) return;
5327
+ const state = ctx.state.session.state.get();
5328
+ if (ctx.state.session.model.get() === selection.modelId && modelRoutesMatch(state.modelRoute, selection.modelRoute) && state[MODEL_FALLBACK_STATE_KEY] == null) return;
5329
+ await applyPackToSession(ctx, resolution.packId, {
5330
+ modeId,
5331
+ expectedThreadId: resolution.threadId
5332
+ });
5333
+ }
5334
+ //#endregion
5129
5335
  //#region src/tui/commands/mode.ts
5130
5336
  function applyCurrentModeColorToRenderedTools(ctx) {
5131
5337
  const color = ctx.state.session.mode.resolve().metadata?.color;
@@ -5140,7 +5346,7 @@ async function handleModeCommand(ctx, args) {
5140
5346
  return;
5141
5347
  }
5142
5348
  if (args[0]) try {
5143
- await ctx.state.session.mode.switch({ modeId: args[0] });
5349
+ await switchModeWithPack(ctx, args[0]);
5144
5350
  applyCurrentModeColorToRenderedTools(ctx);
5145
5351
  } catch (err) {
5146
5352
  ctx.showError(`Failed to switch mode: ${err instanceof Error ? err.message : String(err)}`);
@@ -5254,6 +5460,9 @@ var AccountSwitchNoticeComponent = class extends Container {
5254
5460
  return "other";
5255
5461
  }
5256
5462
  };
5463
+ function packFallbackNoticeText(data) {
5464
+ return modelFallbackNoticeText(data).replace("Switched model route:", "Switched model pack:");
5465
+ }
5257
5466
  var PackFallbackNoticeComponent = class extends Container {
5258
5467
  textChild;
5259
5468
  constructor(data) {
@@ -12713,7 +12922,6 @@ async function handleSandboxCommand(ctx, args) {
12713
12922
  //#region src/tui/commands/models.ts
12714
12923
  async function switchCurrentModeModel(ctx, selectedModelId) {
12715
12924
  const modeId = ctx.state.session.mode.get();
12716
- const modeSettingKey = `modeModelId_${modeId}`;
12717
12925
  const settings = loadSettings();
12718
12926
  const nextSettings = structuredClone(settings);
12719
12927
  nextSettings.models.modePackOverrides ??= {};
@@ -12723,12 +12931,11 @@ async function switchCurrentModeModel(ctx, selectedModelId) {
12723
12931
  const threadId = ctx.state.session.thread.getId();
12724
12932
  const thread = threadId ? (await ctx.state.session.thread.list()).find((item) => item.id === threadId) : void 0;
12725
12933
  const threadSettings = parseThreadSettings(thread?.metadata);
12726
- const previousModeSetting = thread?.metadata?.[modeSettingKey];
12727
12934
  const previousPackSetting = thread?.metadata?.[THREAD_ACTIVE_MODEL_PACK_ID_KEY];
12728
12935
  const previousFallbackStatus = thread?.metadata?.[THREAD_FALLBACK_STATUS_KEY];
12729
- const previousPendingFallback = thread?.metadata?.[PACK_FALLBACK_STATE_KEY];
12730
- const previousSessionState = ctx.state.session.state?.get?.();
12731
- const previousSessionPackId = previousSessionState?.activeModelPackId;
12936
+ const previousPendingFallback = thread?.metadata?.[MODEL_FALLBACK_STATE_KEY];
12937
+ const previousSessionState = ctx.state.session.state.get();
12938
+ const previousFallbackUi = ctx.state.fallbackStatus;
12732
12939
  const activePackId = threadSettings.activeModelPackId ?? nextSettings.models.activeModelPackId;
12733
12940
  const builtinPack = activePackId ? getBuiltinModePack(activePackId) : void 0;
12734
12941
  const customPack = activePackId?.startsWith("custom:") ? nextSettings.customModelPacks.find((item) => `custom:${item.name}` === activePackId) : void 0;
@@ -12739,10 +12946,8 @@ async function switchCurrentModeModel(ctx, selectedModelId) {
12739
12946
  const effectivePackModels = activePack ? resolveModePackModels(nextSettings, activePack) : {};
12740
12947
  const modeModels = {};
12741
12948
  for (const item of modes) {
12742
- const persistedModelId = threadSettings.modeModelIds[item.id];
12743
12949
  const fallbackModelId = effectivePackModels[item.id] ?? nextSettings.models.modeDefaults[item.id] ?? item.defaultModelId;
12744
- if (persistedModelId) modeModels[item.id] = persistedModelId;
12745
- else if (fallbackModelId) modeModels[item.id] = fallbackModelId;
12950
+ if (fallbackModelId) modeModels[item.id] = fallbackModelId;
12746
12951
  }
12747
12952
  modeModels[modeId] = modelId;
12748
12953
  let nextPackId;
@@ -12771,82 +12976,73 @@ async function switchCurrentModeModel(ctx, selectedModelId) {
12771
12976
  nextSettings.models.activeModelPackId = nextPackId;
12772
12977
  nextSettings.models.modeDefaults = modeModels;
12773
12978
  }
12774
- let modeSettingSaved = false;
12775
- let packSettingSaved = false;
12776
- let sessionPackSaved = false;
12777
- let fallbackStatusCleared = false;
12778
- let pendingThreadFallbackCleared = false;
12779
- let pendingSessionFallbackCleared = false;
12780
- let globalSettingsWriteStarted = false;
12781
- try {
12782
- await ctx.state.session.thread.setSetting({
12783
- key: modeSettingKey,
12784
- value: modelId
12785
- });
12786
- modeSettingSaved = true;
12787
- if (await ctx.state.session.thread.getSetting({ key: modeSettingKey }) !== modelId) throw new Error(`Could not save the ${modeId} mode model`);
12788
- await ctx.state.session.thread.setSetting({
12789
- key: THREAD_ACTIVE_MODEL_PACK_ID_KEY,
12790
- value: nextPackId
12791
- });
12792
- packSettingSaved = true;
12793
- if (await ctx.state.session.thread.getSetting({ key: THREAD_ACTIVE_MODEL_PACK_ID_KEY }) !== nextPackId) throw new Error("Could not save the active model pack");
12794
- globalSettingsWriteStarted = true;
12795
- saveSettings(nextSettings);
12796
- await ctx.state.session.model.switch({
12797
- modelId,
12798
- scope: "global"
12799
- });
12800
- await ctx.state.session.state.set({ activeModelPackId: nextPackId });
12801
- sessionPackSaved = true;
12802
- await ctx.state.session.thread.setSetting({
12803
- key: THREAD_FALLBACK_STATUS_KEY,
12804
- value: void 0
12805
- });
12806
- fallbackStatusCleared = true;
12807
- await ctx.state.session.thread.setSetting({
12808
- key: PACK_FALLBACK_STATE_KEY,
12809
- value: void 0
12810
- });
12811
- pendingThreadFallbackCleared = true;
12812
- await ctx.state.session.state.set({ [PACK_FALLBACK_STATE_KEY]: null });
12813
- pendingSessionFallbackCleared = true;
12814
- } catch (error) {
12815
- if (globalSettingsWriteStarted) try {
12816
- saveSettings(settings);
12817
- } catch {}
12818
- const rollbacks = [];
12819
- if (sessionPackSaved) rollbacks.push(ctx.state.session.state.set({ activeModelPackId: previousSessionPackId }));
12820
- if (pendingThreadFallbackCleared) rollbacks.push(ctx.state.session.thread.setSetting({
12821
- key: PACK_FALLBACK_STATE_KEY,
12822
- value: previousPendingFallback
12823
- }));
12824
- if (pendingSessionFallbackCleared) {
12825
- const previousSessionPending = previousSessionState?.[PACK_FALLBACK_STATE_KEY];
12826
- rollbacks.push(ctx.state.session.state.set({ [PACK_FALLBACK_STATE_KEY]: previousSessionPending === void 0 ? null : previousSessionPending }));
12979
+ await applyPackToSession(ctx, nextPackId, {
12980
+ modeId,
12981
+ settings: nextSettings,
12982
+ expectedThreadId: threadId,
12983
+ afterApply: async () => {
12984
+ saveSettings(nextSettings);
12985
+ if (await ctx.state.session.thread.getSetting({ key: THREAD_ACTIVE_MODEL_PACK_ID_KEY }) !== nextPackId) throw new Error("Could not save the active model pack");
12986
+ await ctx.state.session.thread.setSetting({
12987
+ key: THREAD_FALLBACK_STATUS_KEY,
12988
+ value: void 0
12989
+ });
12990
+ ctx.state.fallbackStatus = void 0;
12991
+ ctx.updateStatusLine();
12992
+ ctx.showInfo(`Switched ${modeId} mode to ${modelId}`);
12993
+ },
12994
+ onError: async () => {
12995
+ try {
12996
+ saveSettings(settings);
12997
+ } catch {}
12998
+ await Promise.allSettled([
12999
+ ctx.state.session.thread.setSetting({
13000
+ key: THREAD_ACTIVE_MODEL_PACK_ID_KEY,
13001
+ value: previousPackSetting
13002
+ }),
13003
+ ctx.state.session.thread.setSetting({
13004
+ key: THREAD_FALLBACK_STATUS_KEY,
13005
+ value: previousFallbackStatus
13006
+ }),
13007
+ ctx.state.session.thread.setSetting({
13008
+ key: MODEL_FALLBACK_STATE_KEY,
13009
+ value: previousPendingFallback
13010
+ }),
13011
+ ctx.state.session.thread.setSetting({
13012
+ key: "subagentModelId_explore",
13013
+ value: previousSessionState.subagentModelId_explore
13014
+ }),
13015
+ ctx.state.session.thread.setSetting({
13016
+ key: "subagentModelId_plan",
13017
+ value: previousSessionState.subagentModelId_plan
13018
+ }),
13019
+ ctx.state.session.thread.setSetting({
13020
+ key: "subagentModelId_execute",
13021
+ value: previousSessionState.subagentModelId_execute
13022
+ }),
13023
+ ctx.state.session.thread.setSetting({
13024
+ key: "observerModelId",
13025
+ value: previousSessionState.observerModelId
13026
+ }),
13027
+ ctx.state.session.thread.setSetting({
13028
+ key: "reflectorModelId",
13029
+ value: previousSessionState.reflectorModelId
13030
+ }),
13031
+ ctx.state.session.model.switch(previousModelId),
13032
+ ctx.state.session.state.set({
13033
+ modelRoute: previousSessionState.modelRoute,
13034
+ [MODEL_FALLBACK_STATE_KEY]: previousSessionState[MODEL_FALLBACK_STATE_KEY] ?? null,
13035
+ subagentModelId_explore: previousSessionState.subagentModelId_explore,
13036
+ subagentModelId_plan: previousSessionState.subagentModelId_plan,
13037
+ subagentModelId_execute: previousSessionState.subagentModelId_execute,
13038
+ observerModelId: previousSessionState.observerModelId,
13039
+ reflectorModelId: previousSessionState.reflectorModelId
13040
+ })
13041
+ ]);
13042
+ ctx.state.fallbackStatus = previousFallbackUi;
13043
+ ctx.updateStatusLine();
12827
13044
  }
12828
- if (fallbackStatusCleared) rollbacks.push(ctx.state.session.thread.setSetting({
12829
- key: THREAD_FALLBACK_STATUS_KEY,
12830
- value: previousFallbackStatus
12831
- }));
12832
- if (packSettingSaved) rollbacks.push(ctx.state.session.thread.setSetting({
12833
- key: THREAD_ACTIVE_MODEL_PACK_ID_KEY,
12834
- value: previousPackSetting
12835
- }));
12836
- if (modeSettingSaved) rollbacks.push(ctx.state.session.thread.setSetting({
12837
- key: modeSettingKey,
12838
- value: previousModeSetting
12839
- }));
12840
- if (ctx.state.session.model.get() !== previousModelId) rollbacks.push(ctx.state.session.model.switch({
12841
- modelId: previousModelId,
12842
- scope: "global"
12843
- }));
12844
- await Promise.allSettled(rollbacks);
12845
- throw error;
12846
- }
12847
- ctx.state.fallbackStatus = void 0;
12848
- ctx.updateStatusLine();
12849
- ctx.showInfo(`Switched ${modeId} mode to ${modelId}`);
13045
+ });
12850
13046
  }
12851
13047
  async function handleModelCommand(ctx) {
12852
13048
  try {
@@ -14164,47 +14360,6 @@ function upsertCustomPackInSettings(settings, pack, modeDefaults, previousPackId
14164
14360
  }
14165
14361
  async function applyPack(ctx, pack, previousPackId) {
14166
14362
  const modes = ctx.state.controller.listModes();
14167
- for (const mode of modes) {
14168
- const modelId = pack.models[mode.id];
14169
- if (modelId) {
14170
- mode.defaultModelId = modelId;
14171
- await ctx.state.session.thread.setSetting({
14172
- key: `modeModelId_${mode.id}`,
14173
- value: modelId
14174
- });
14175
- }
14176
- }
14177
- const currentModeId = ctx.state.session.mode.get();
14178
- const currentModeModel = pack.models[currentModeId];
14179
- if (currentModeModel) await ctx.state.session.model.switch({ modelId: currentModeModel });
14180
- for (const [agentType, modeId] of Object.entries({
14181
- explore: "fast",
14182
- plan: "plan",
14183
- execute: "build"
14184
- })) {
14185
- const saModelId = pack.models[modeId];
14186
- if (saModelId) await ctx.state.session.subagents.model.set({
14187
- modelId: saModelId,
14188
- agentType
14189
- });
14190
- }
14191
- await ctx.state.session.thread.setSetting({
14192
- key: THREAD_ACTIVE_MODEL_PACK_ID_KEY,
14193
- value: pack.id
14194
- });
14195
- await ctx.state.session.thread.setSetting({
14196
- key: THREAD_FALLBACK_STATUS_KEY,
14197
- value: void 0
14198
- });
14199
- await ctx.state.session.thread.setSetting({
14200
- key: PACK_FALLBACK_STATE_KEY,
14201
- value: void 0
14202
- });
14203
- ctx.state.fallbackStatus = void 0;
14204
- await ctx.state.session.state.set({
14205
- activeModelPackId: pack.id,
14206
- [PACK_FALLBACK_STATE_KEY]: null
14207
- });
14208
14363
  const s = loadSettings();
14209
14364
  const modeDefaults = {};
14210
14365
  for (const mode of modes) {
@@ -14218,14 +14373,27 @@ async function applyPack(ctx, pack, previousPackId) {
14218
14373
  s.models.modeDefaults = {};
14219
14374
  }
14220
14375
  s.models.subagentModels = {};
14376
+ const currentModeId = ctx.state.session.mode.get();
14377
+ const currentModeModel = resolveModePackModels(s, pack)[currentModeId];
14221
14378
  const hasOpenAI = Object.values(pack.models).some((modelId) => modelId.startsWith("openai/"));
14222
14379
  const sessionOverride = ctx.state.session.state.get()?.thinkingLevel;
14223
14380
  const defaultThinking = resolveDefaultThinkingLevel(s, currentModeId);
14224
14381
  const effectiveThinking = sessionOverride ?? defaultThinking.level;
14225
14382
  if (hasOpenAI && sessionOverride === void 0 && defaultThinking.source === "global" && defaultThinking.level === "off") s.preferences.thinkingLevel = "low";
14226
- else if (currentModeModel?.startsWith("openai/") && effectiveThinking === "max") await ctx.state.session.state.set({ thinkingLevel: "xhigh" });
14227
- saveSettings(s);
14228
- updateStatusLine(ctx.state);
14383
+ const shouldSetXhigh = currentModeModel?.startsWith("openai/") && effectiveThinking === "max";
14384
+ return (await applyPackToSession(ctx, pack.id, {
14385
+ settings: s,
14386
+ ...shouldSetXhigh ? { thinkingLevel: "xhigh" } : {},
14387
+ afterApply: async () => {
14388
+ await ctx.state.session.thread.setSetting({
14389
+ key: THREAD_FALLBACK_STATUS_KEY,
14390
+ value: void 0
14391
+ });
14392
+ saveSettings(s);
14393
+ ctx.state.fallbackStatus = void 0;
14394
+ updateStatusLine(ctx.state);
14395
+ }
14396
+ })).applied;
14229
14397
  }
14230
14398
  function getOverriddenPackModes(pack, builtinPack) {
14231
14399
  return [
@@ -14541,6 +14709,12 @@ async function runSetSubscriptionRouting(ctx, pack) {
14541
14709
  if (Object.keys(packPreferences).length > 0) settings.models.packAccountPreferences[pack.id] = packPreferences;
14542
14710
  else delete settings.models.packAccountPreferences[pack.id];
14543
14711
  saveSettings(settings);
14712
+ const threadId = ctx.state.session.thread.getId();
14713
+ const thread = threadId ? (await ctx.state.session.thread.list()).find((item) => item.id === threadId) : void 0;
14714
+ if (resolveThreadActiveModelPackId(settings, listResolvableModePacks(settings), thread?.metadata) === pack.id) await applyPackToSession(ctx, pack.id, {
14715
+ settings,
14716
+ expectedThreadId: threadId
14717
+ });
14544
14718
  const providerId = providerFromModelId(modelId);
14545
14719
  const accountLabel = accountId ? ctx.authStorage?.listAccounts(providerId ?? "").find((account) => account.id === accountId)?.label ?? accountId : "Automatic";
14546
14720
  ctx.showInfo(`${pack.name} · ${modelId}: ${accountLabel}`);
@@ -14658,8 +14832,7 @@ async function handleModelsPackCommand(ctx) {
14658
14832
  imported.id = `custom:${newName}`;
14659
14833
  }
14660
14834
  }
14661
- await applyPack(ctx, imported);
14662
- ctx.showInfo(`Imported and activated ${imported.name} pack`);
14835
+ if (await applyPack(ctx, imported)) ctx.showInfo(`Imported and activated ${imported.name} pack`);
14663
14836
  resolve();
14664
14837
  return;
14665
14838
  }
@@ -14739,8 +14912,7 @@ async function handleModelsPackCommand(ctx) {
14739
14912
  resolve();
14740
14913
  return;
14741
14914
  }
14742
- await applyPack(ctx, pack, previousPackId);
14743
- ctx.showInfo(resetBuiltinPack ? `Reset and switched to ${pack.name} pack` : `Switched to ${pack.name} pack`);
14915
+ if (await applyPack(ctx, pack, previousPackId)) ctx.showInfo(resetBuiltinPack ? `Reset and switched to ${pack.name} pack` : `Switched to ${pack.name} pack`);
14744
14916
  resolve();
14745
14917
  };
14746
14918
  selectList.onCancel = () => {
@@ -18205,7 +18377,7 @@ async function performLogin(ctx, providerId, opts) {
18205
18377
  const hasSelectedModel = ctx.state.session.model.get() !== "";
18206
18378
  const defaultModel = PROVIDER_DEFAULT_MODELS[providerId];
18207
18379
  if (defaultModel && !hasSelectedModel) {
18208
- await ctx.state.session.model.switch({ modelId: defaultModel });
18380
+ await ctx.state.session.model.switch(defaultModel);
18209
18381
  ctx.showInfo(`Logged in to ${providerName} - switched to ${defaultModel}`);
18210
18382
  } else ctx.showInfo(`Successfully logged in to ${providerName}`);
18211
18383
  }
@@ -21370,7 +21542,10 @@ function handleAgentEnd(ctx) {
21370
21542
  }
21371
21543
  });
21372
21544
  if (state.streamingComponent) finalizeStreamingAssistant(state);
21373
- state.activeGoalJudge = void 0;
21545
+ if (state.activeGoalJudge) {
21546
+ removeJudgeComponent(state, state.activeGoalJudge.component);
21547
+ state.activeGoalJudge = void 0;
21548
+ }
21374
21549
  state.followUpComponents = [];
21375
21550
  for (const tool of state.pendingTools.values()) tool.stopLiveUpdates?.();
21376
21551
  state.idleCounter?.setThinking(false);
@@ -21526,17 +21701,6 @@ function handleGoalEvaluation(ctx, payload) {
21526
21701
  ctx.updateStatusLine();
21527
21702
  flushRender(state);
21528
21703
  state.activeGoalJudge = void 0;
21529
- if (payload.status === "done") {
21530
- const goal = state.goalManager.getGoal();
21531
- if (goal && goal.id === state.planStartedGoalId) {
21532
- const goalId = state.planStartedGoalId;
21533
- state.planStartedGoalId = void 0;
21534
- state.session.mode.switch({ modeId: "plan" }).catch((error) => {
21535
- ctx.showError(`Failed to switch to Plan mode: ${error instanceof Error ? error.message : String(error)}`);
21536
- state.planStartedGoalId = goalId;
21537
- });
21538
- }
21539
- }
21540
21704
  }
21541
21705
  //#endregion
21542
21706
  //#region src/tui/handlers/message.ts
@@ -21763,18 +21927,11 @@ function handleMessageEnd(ctx, message) {
21763
21927
  flushRender(state);
21764
21928
  }
21765
21929
  /**
21766
- * Thread stickiness for a pack hop (Q19): the rotation processor writes the
21767
- * landed pack into session state (PACK_FALLBACK_STATE_KEY) when a cascade
21768
- * advances; this handler consumes it on the typed `state_changed` event —
21769
- * data parts never ride controller message events, so this key is the live
21770
- * channel. Applies the landed pack exactly like the manual /models switch
21771
- * (applyPack in models-pack.ts): every mode's thread model, subagent models,
21772
- * thinking-level fixups, thread metadata, and settings, so the thread stays
21773
- * on the landed pack until the user switches manually.
21930
+ * Keep a thread on the pack selected by a generic model-route hop.
21774
21931
  */
21775
21932
  async function handlePackFallbackState(ectx, event) {
21776
- if (!event.changedKeys.includes(PACK_FALLBACK_STATE_KEY)) return;
21777
- const pending = event.state[PACK_FALLBACK_STATE_KEY];
21933
+ if (!event.changedKeys.includes(MODEL_FALLBACK_STATE_KEY)) return;
21934
+ const pending = event.state[MODEL_FALLBACK_STATE_KEY];
21778
21935
  if (pending === null || pending === void 0) return;
21779
21936
  const entryThreadId = ectx.state.session.thread.getId();
21780
21937
  const pendingThreadId = typeof pending.threadId === "string" ? pending.threadId : entryThreadId;
@@ -21787,104 +21944,60 @@ async function handlePackFallbackState(ectx, event) {
21787
21944
  const isOriginThreadActive = () => !pendingThreadId || ectx.state.session.thread.getId() === pendingThreadId;
21788
21945
  const clearPending = async () => {
21789
21946
  await setOriginThreadSetting({
21790
- key: PACK_FALLBACK_STATE_KEY,
21947
+ key: MODEL_FALLBACK_STATE_KEY,
21791
21948
  value: void 0
21792
21949
  });
21793
- if (isOriginThreadActive()) await ectx.state.session.state.set({ [PACK_FALLBACK_STATE_KEY]: null });
21950
+ if (isOriginThreadActive()) await ectx.state.session.state.set({ [MODEL_FALLBACK_STATE_KEY]: null });
21794
21951
  };
21795
- if (typeof pending.toPackId !== "string" || typeof pending.toModelId !== "string" || pending.threadId !== void 0 && typeof pending.threadId !== "string") {
21796
- await clearPending();
21797
- return;
21798
- }
21799
- if (pending.toModelId.length === 0 || pending.toPackId.length === 0) {
21952
+ if (typeof pending.fromEntryId !== "string" || typeof pending.toEntryId !== "string" || typeof pending.toModelId !== "string" || pending.threadId !== void 0 && typeof pending.threadId !== "string" || pending.toEntryId.length === 0 || pending.toModelId.length === 0) {
21800
21953
  await clearPending();
21801
21954
  return;
21802
21955
  }
21803
21956
  if (!isOriginThreadActive()) return;
21804
21957
  const settings = loadSettings();
21805
21958
  const packs = listResolvableModePacks(settings);
21806
- const pack = packs.find((candidate) => candidate.id === pending.toPackId);
21959
+ const pack = packs.find((candidate) => candidate.id === pending.toEntryId);
21807
21960
  if (!pack) {
21808
21961
  await clearPending();
21809
21962
  return;
21810
21963
  }
21811
- const failedPack = packs.find((candidate) => candidate.id === pending.fromPackId);
21964
+ const failedPack = packs.find((candidate) => candidate.id === pending.fromEntryId);
21812
21965
  const fallbackStatus = {
21813
21966
  usingPack: pack.name,
21814
- failedPack: failedPack?.name ?? pending.fromPackId
21967
+ failedPack: failedPack?.name ?? pending.fromEntryId
21815
21968
  };
21816
- const packModels = resolveModePackModels(settings, pack);
21817
- const modes = ectx.state.controller.listModes();
21818
- for (const mode of modes) {
21819
- const modelId = packModels[mode.id];
21820
- if (modelId) await setOriginThreadSetting({
21821
- key: `modeModelId_${mode.id}`,
21822
- value: modelId
21823
- });
21824
- }
21825
- await setOriginThreadSetting({
21826
- key: THREAD_ACTIVE_MODEL_PACK_ID_KEY,
21827
- value: pending.toPackId
21828
- });
21829
- await setOriginThreadSetting({
21830
- key: THREAD_FALLBACK_STATUS_KEY,
21831
- value: fallbackStatus
21832
- });
21833
- if (!isOriginThreadActive()) return;
21834
- const subagentModeMap = {
21835
- explore: "fast",
21836
- plan: "plan",
21837
- execute: "build"
21838
- };
21839
- const subagentState = {};
21840
- for (const [agentType, modeId] of Object.entries(subagentModeMap)) {
21841
- const modelId = packModels[modeId];
21842
- if (!modelId) continue;
21843
- const key = `subagentModelId_${agentType}`;
21844
- subagentState[key] = modelId;
21845
- await setOriginThreadSetting({
21846
- key,
21847
- value: modelId
21969
+ try {
21970
+ await applyPackToSession(ectx, pending.toEntryId, {
21971
+ clearPendingFallback: false,
21972
+ afterApply: async (selection) => {
21973
+ if (!isOriginThreadActive()) return;
21974
+ await setOriginThreadSetting({
21975
+ key: THREAD_FALLBACK_STATUS_KEY,
21976
+ value: fallbackStatus
21977
+ });
21978
+ if (!isOriginThreadActive()) return;
21979
+ const sessionOverride = ectx.state.session.state.get()?.thinkingLevel;
21980
+ const currentModeId = ectx.state.session.mode.get();
21981
+ const defaultThinking = resolveDefaultThinkingLevel({
21982
+ ...settings,
21983
+ models: {
21984
+ ...settings.models,
21985
+ activeModelPackId: pending.toEntryId
21986
+ }
21987
+ }, currentModeId);
21988
+ const effectiveThinking = sessionOverride ?? defaultThinking.level;
21989
+ if (selection.modelRoute.entries.some((entry) => entry.modelId.startsWith("openai/")) && sessionOverride === void 0 && defaultThinking.source === "global" && defaultThinking.level === "off") await ectx.state.session.state.set({ thinkingLevel: "low" });
21990
+ else if (selection.modelId.startsWith("openai/") && effectiveThinking === "max") await ectx.state.session.state.set({ thinkingLevel: "xhigh" });
21991
+ ectx.state.fallbackStatus = fallbackStatus;
21992
+ ectx.updateStatusLine();
21993
+ await ectx.refreshModelAuthStatus();
21994
+ await clearPending();
21995
+ }
21848
21996
  });
21997
+ } catch (error) {
21998
+ await clearPending();
21999
+ throw error;
21849
22000
  }
21850
- if (!isOriginThreadActive()) return;
21851
- const currentModeId = ectx.state.session.mode.get();
21852
- const currentModeModel = packModels[currentModeId] ?? pending.toModelId;
21853
- const runtimeSettings = {
21854
- ...settings,
21855
- models: {
21856
- ...settings.models,
21857
- activeModelPackId: pending.toPackId
21858
- }
21859
- };
21860
- const hasOpenAI = Object.values(packModels).some((modelId) => modelId.startsWith("openai/"));
21861
- const sessionOverride = ectx.state.session.state.get()?.thinkingLevel;
21862
- const defaultThinking = resolveDefaultThinkingLevel(runtimeSettings, currentModeId);
21863
- const effectiveThinking = sessionOverride ?? defaultThinking.level;
21864
- const stateUpdates = {
21865
- activeModelPackId: pending.toPackId,
21866
- ...subagentState
21867
- };
21868
- if (hasOpenAI && sessionOverride === void 0 && defaultThinking.source === "global" && defaultThinking.level === "off") stateUpdates.thinkingLevel = "low";
21869
- else if (currentModeModel.startsWith("openai/") && effectiveThinking === "max") stateUpdates.thinkingLevel = "xhigh";
21870
- if (!await ectx.state.session.state.setIf(stateUpdates, isOriginThreadActive)) return;
21871
- ectx.state.session.model.set({ modelId: currentModeModel });
21872
- ectx.state.session.emit({
21873
- type: "model_changed",
21874
- modelId: currentModeModel,
21875
- scope: "thread",
21876
- modeId: currentModeId
21877
- });
21878
- for (const [key, modelId] of Object.entries(subagentState)) ectx.state.session.emit({
21879
- type: "subagent_model_changed",
21880
- modelId,
21881
- scope: "thread",
21882
- agentType: key.slice(16)
21883
- });
21884
- ectx.state.fallbackStatus = fallbackStatus;
21885
- ectx.updateStatusLine();
21886
- await ectx.refreshModelAuthStatus();
21887
- await clearPending();
21888
22001
  }
21889
22002
  //#endregion
21890
22003
  //#region src/tui/handlers/om.ts
@@ -22424,6 +22537,14 @@ async function handlePlanApproval(ctx, toolCallId, submittedPath) {
22424
22537
  if (state.pendingFocus === approvalComponent) state.pendingFocus = void 0;
22425
22538
  if (!state.ui.hasOverlay()) state.ui.setFocus(state.editor);
22426
22539
  };
22540
+ const restoreApprovalAfterError = (error) => {
22541
+ ctx.showError(`Failed to start plan: ${error instanceof Error ? error.message : String(error)}`);
22542
+ approvalComponent.activate(approvalOptions);
22543
+ state.activeInlinePlanApproval = approvalComponent;
22544
+ state.ui.requestRender();
22545
+ if (state.ui.hasOverlay()) state.pendingFocus = approvalComponent;
22546
+ else state.ui.setFocus(approvalComponent);
22547
+ };
22427
22548
  const approvalOptions = {
22428
22549
  toolCallId,
22429
22550
  title: resolvedTitle,
@@ -22432,21 +22553,32 @@ async function handlePlanApproval(ctx, toolCallId, submittedPath) {
22432
22553
  previousPlan,
22433
22554
  onApprove: async () => {
22434
22555
  releaseApprovalFocus();
22435
- firePermissionResult("approved");
22556
+ try {
22557
+ await switchModeWithPack(ctx, "build");
22558
+ } catch (error) {
22559
+ restoreApprovalAfterError(error);
22560
+ return;
22561
+ }
22436
22562
  await prepareApprovedPlan(ctx, resolvedTitle, plan, planPath);
22563
+ firePermissionResult("approved");
22437
22564
  const resumed = resumeApprovedPlan(ctx, toolCallId, resolvedTitle, plan, snapshotKey);
22438
22565
  resolve();
22439
22566
  await resumed;
22440
22567
  },
22441
22568
  onGoal: async () => {
22442
22569
  releaseApprovalFocus();
22443
- firePermissionResult("approved");
22570
+ try {
22571
+ await switchModeWithPack(ctx, "build");
22572
+ } catch (error) {
22573
+ restoreApprovalAfterError(error);
22574
+ return;
22575
+ }
22444
22576
  await prepareApprovedPlan(ctx, resolvedTitle, plan, planPath);
22577
+ firePermissionResult("approved");
22445
22578
  const goal = await ctx.setGoal(formatPlanGoalObjective(resolvedTitle, plan), "Goal cancelled.").catch((error) => {
22446
22579
  ctx.showError(`Failed to set goal: ${error instanceof Error ? error.message : String(error)}`);
22447
22580
  return null;
22448
22581
  });
22449
- if (goal) state.planStartedGoalId = goal.id;
22450
22582
  const approvalRecorded = goal ? waitForToolEnd(state.session, toolCallId) : void 0;
22451
22583
  const resumed = resumeApprovedPlan(ctx, toolCallId, resolvedTitle, plan, snapshotKey);
22452
22584
  resolve();
@@ -24766,7 +24898,6 @@ function createTUIState(options) {
24766
24898
  tokensPerSec: 0,
24767
24899
  latestRequestPromptTokens: void 0,
24768
24900
  goalManager: new GoalManager(),
24769
- planStartedGoalId: void 0,
24770
24901
  customSlashCommands: [],
24771
24902
  skillCommands: [],
24772
24903
  goalSkillCommands: [],
@@ -25052,15 +25183,17 @@ async function dispatchEvent(event, ectx, state) {
25052
25183
  handleToolEnd(ectx, event.toolCallId, event.result, event.isError, event.providerMetadata);
25053
25184
  break;
25054
25185
  case "info":
25055
- ectx.showInfo(event.message);
25186
+ ectx.showInfo(event.message.replace(/^Switched model route:/, "Switched model pack:"));
25056
25187
  break;
25057
25188
  case "error":
25058
25189
  ectx.showFormattedError(event);
25059
25190
  break;
25060
25191
  case "mode_changed":
25192
+ await reconcilePackAfterModeChange(ectx, event.modeId);
25061
25193
  await ectx.refreshModelAuthStatus();
25062
25194
  break;
25063
25195
  case "model_changed":
25196
+ ectx.updateStatusLine();
25064
25197
  await ectx.refreshModelAuthStatus();
25065
25198
  break;
25066
25199
  case "thread_changed": {
@@ -25957,7 +26090,7 @@ function setupKeyboardShortcuts(state, callbacks) {
25957
26090
  if (modes.length <= 1) return;
25958
26091
  const currentId = state.session.mode.get();
25959
26092
  const nextMode = modes[(modes.findIndex((m) => m.id === currentId) + 1) % modes.length];
25960
- await state.session.mode.switch({ modeId: nextMode.id });
26093
+ await switchModeWithPack({ state }, nextMode.id);
25961
26094
  });
25962
26095
  state.editor.onAction("toggleYolo", () => {
25963
26096
  const current = state.session.state.get()?.yolo === true;
@@ -27009,11 +27142,11 @@ async function syncInitialThreadState(state) {
27009
27142
  const initThread = initThreads.find((t) => t.id === initThreadId);
27010
27143
  const metadata = initThread?.metadata;
27011
27144
  const persistedFallbackStatus = fallbackStatusFromMetadata(metadata);
27012
- const pendingFallback = metadata?.[PACK_FALLBACK_STATE_KEY];
27013
- const validPendingFallback = pendingFallback && typeof pendingFallback === "object" && typeof pendingFallback.fromPackId === "string" && typeof pendingFallback.toPackId === "string" && typeof pendingFallback.toModelId === "string" && (pendingFallback.reason === "pool-exhausted" || pendingFallback.reason === "persistent-outage") && typeof pendingFallback.at === "string" ? pendingFallback : null;
27014
- const currentPending = (state.session.state?.get?.())?.[PACK_FALLBACK_STATE_KEY];
27145
+ const pendingFallback = metadata?.[MODEL_FALLBACK_STATE_KEY];
27146
+ const validPendingFallback = pendingFallback && typeof pendingFallback === "object" && typeof pendingFallback.fromEntryId === "string" && typeof pendingFallback.toEntryId === "string" && typeof pendingFallback.toModelId === "string" && (pendingFallback.reason === "pool-exhausted" || pendingFallback.reason === "persistent-outage") && typeof pendingFallback.at === "string" ? pendingFallback : null;
27147
+ const currentPending = (state.session.state?.get?.())?.[MODEL_FALLBACK_STATE_KEY];
27015
27148
  if (validPendingFallback || currentPending) {
27016
- const updates = { [PACK_FALLBACK_STATE_KEY]: validPendingFallback };
27149
+ const updates = { [MODEL_FALLBACK_STATE_KEY]: validPendingFallback };
27017
27150
  if (!(state.session.state.setIf ? await state.session.state.setIf(updates, () => state.session.thread.getId() === initThreadId) : state.session.thread.getId() === initThreadId ? await state.session.state.set(updates).then(() => true) : false) || state.session.thread.getId() !== initThreadId) return;
27018
27151
  }
27019
27152
  setCurrentThreadTitle(state, initThread?.title);
@@ -27607,8 +27740,8 @@ var MastraTUI = class MastraTUI {
27607
27740
  }
27608
27741
  if (event.type === "model_changed") analytics.capture("mastracode_model_changed", {
27609
27742
  modelId: event.modelId,
27610
- scope: event.scope,
27611
- mode: event.modeId ?? this.state.session.mode.get(),
27743
+ thinkingLevel: event.thinkingLevel,
27744
+ mode: this.state.session.mode.get(),
27612
27745
  threadId: this.state.session.thread.getId(),
27613
27746
  resourceId: this.state.session.identity.getResourceId()
27614
27747
  });
@@ -27694,16 +27827,30 @@ var MastraTUI = class MastraTUI {
27694
27827
  async syncThreadActivePackMetadata(thread, isCurrent = () => true) {
27695
27828
  const settings = loadSettings$1();
27696
27829
  const currentThreadId = this.state.session.thread.getId();
27697
- if (!currentThreadId || !isCurrent()) return;
27830
+ if (!isCurrent()) return;
27831
+ if (!currentThreadId) {
27832
+ if (!this.state.options.initialModelOverride) {
27833
+ await applyCurrentThreadPack({ state: this.state }, { packId: settings.models.activeModelPackId });
27834
+ if (!isCurrent()) return;
27835
+ }
27836
+ updateStatusLine(this.state);
27837
+ return;
27838
+ }
27698
27839
  const ownsUpdate = () => isCurrent() && this.state.session.thread.getId() === currentThreadId;
27699
27840
  const resolvedThread = thread?.id === currentThreadId ? thread : (await this.state.session.thread.list()).find((t) => t.id === currentThreadId);
27700
- const packs = getAvailableModePacks(await this.buildProviderAccess(), settings.customModelPacks).filter((p) => p.id !== "custom");
27841
+ const packs = listResolvableModePacks(settings);
27701
27842
  const metadata = resolvedThread?.metadata;
27702
27843
  if (!ownsUpdate()) return;
27703
- const resolvedPackId = resolveThreadActiveModelPackId(settings, packs, metadata);
27844
+ const hasThreadPack = typeof metadata?.[THREAD_ACTIVE_MODEL_PACK_ID_KEY] === "string";
27845
+ const hasPersistedModel = typeof metadata?.currentModelId === "string" && metadata.currentModelId.length > 0;
27846
+ const resolvedPackId = (this.state.options.initialModelOverride || hasPersistedModel) && !hasThreadPack ? null : resolveThreadActiveModelPackId(settings, packs, metadata);
27704
27847
  const fallbackStatus = fallbackStatusFromMetadata(metadata);
27705
- const updates = { activeModelPackId: resolvedPackId };
27706
- if (!(this.state.session.state.setIf ? await this.state.session.state.setIf(updates, ownsUpdate) : ownsUpdate() ? (await this.state.session.state.set(updates), ownsUpdate()) : false) || !ownsUpdate()) return;
27848
+ if (!ownsUpdate()) return;
27849
+ await applyCurrentThreadPack({ state: this.state }, {
27850
+ packId: resolvedPackId,
27851
+ applyModeDefault: false
27852
+ });
27853
+ if (!ownsUpdate()) return;
27707
27854
  this.state.fallbackStatus = fallbackStatus;
27708
27855
  updateStatusLine(this.state);
27709
27856
  }
@@ -27927,7 +28074,7 @@ var MastraTUI = class MastraTUI {
27927
28074
  const { PROVIDER_DEFAULT_MODELS } = await import("@mastra/code-sdk/auth/storage");
27928
28075
  const defaultModel = PROVIDER_DEFAULT_MODELS[providerId];
27929
28076
  if (defaultModel) {
27930
- await this.state.session.model.switch({ modelId: defaultModel });
28077
+ await this.state.session.model.switch(defaultModel);
27931
28078
  showInfo(this.state, `Logged in to ${providerName} - switched to ${defaultModel}`);
27932
28079
  } else showInfo(this.state, `Successfully logged in to ${providerName}`);
27933
28080
  await seedOMDefaultAfterLogin(this.state, providerId, (message) => showInfo(this.state, message));
@@ -28032,33 +28179,8 @@ var MastraTUI = class MastraTUI {
28032
28179
  });
28033
28180
  }
28034
28181
  async applyOnboardingResult(result) {
28035
- const controller = this.state.controller;
28036
28182
  const modePack = result.modePack;
28037
- const modes = controller.listModes();
28038
- for (const mode of modes) {
28039
- const modelId = modePack.models[mode.id];
28040
- if (modelId) {
28041
- mode.defaultModelId = modelId;
28042
- await this.state.session.thread.setSetting({
28043
- key: `modeModelId_${mode.id}`,
28044
- value: modelId
28045
- });
28046
- }
28047
- }
28048
- const currentModeId = this.state.session.mode.get();
28049
- const currentModeModel = modePack.models[currentModeId];
28050
- if (currentModeModel) await this.state.session.model.switch({ modelId: currentModeModel });
28051
- for (const [agentType, modeId] of Object.entries({
28052
- explore: "fast",
28053
- plan: "plan",
28054
- execute: "build"
28055
- })) {
28056
- const saModelId = modePack.models[modeId];
28057
- if (saModelId) await this.state.session.subagents.model.set({
28058
- modelId: saModelId,
28059
- agentType
28060
- });
28061
- }
28183
+ const modes = this.state.controller.listModes();
28062
28184
  const omPack = result.omPack.modelId ? result.omPack : void 0;
28063
28185
  if (omPack) await applyOMModelToSession(this.state, omPack.modelId);
28064
28186
  await this.state.session.state.set({ yolo: result.yolo });
@@ -28072,37 +28194,25 @@ var MastraTUI = class MastraTUI {
28072
28194
  const modelId = modePack.models[mode.id];
28073
28195
  if (modelId) modeDefaults[mode.id] = modelId;
28074
28196
  }
28075
- let activeModePackId = modePack.id;
28076
- if (modePack.id === "custom" || modePack.id.startsWith("custom:")) {
28077
- const customName = modePack.id === "custom" ? modePack.name?.trim() || "Custom" : modePack.id.slice(7) || "Custom";
28078
- activeModePackId = `custom:${customName}`;
28079
- const entry = {
28080
- name: customName,
28081
- models: modeDefaults,
28082
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
28083
- };
28084
- const idx = settings.customModelPacks.findIndex((p) => p.name === customName);
28085
- if (idx >= 0) settings.customModelPacks[idx] = entry;
28086
- else settings.customModelPacks.push(entry);
28087
- settings.models.modeDefaults = modeDefaults;
28088
- } else settings.models.modeDefaults = {};
28089
- settings.onboarding.modePackId = activeModePackId;
28090
- settings.models.activeModelPackId = activeModePackId;
28091
- if (this.state.session.thread.getId()) {
28092
- await this.state.session.thread.setSetting({
28093
- key: THREAD_ACTIVE_MODEL_PACK_ID_KEY,
28094
- value: activeModePackId
28095
- });
28096
- await this.state.session.thread.setSetting({
28097
- key: THREAD_FALLBACK_STATUS_KEY,
28098
- value: void 0
28099
- });
28197
+ let activeModePackId = settings.models.activeModelPackId;
28198
+ if (modes.every((mode) => Boolean(modeDefaults[mode.id]))) {
28199
+ activeModePackId = modePack.id;
28200
+ if (modePack.id === "custom" || modePack.id.startsWith("custom:")) {
28201
+ const customName = modePack.id === "custom" ? modePack.name?.trim() || "Custom" : modePack.id.slice(7) || "Custom";
28202
+ activeModePackId = `custom:${customName}`;
28203
+ const entry = {
28204
+ name: customName,
28205
+ models: modeDefaults,
28206
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
28207
+ };
28208
+ const idx = settings.customModelPacks.findIndex((p) => p.name === customName);
28209
+ if (idx >= 0) settings.customModelPacks[idx] = entry;
28210
+ else settings.customModelPacks.push(entry);
28211
+ settings.models.modeDefaults = modeDefaults;
28212
+ } else settings.models.modeDefaults = {};
28213
+ settings.onboarding.modePackId = activeModePackId;
28214
+ settings.models.activeModelPackId = activeModePackId;
28100
28215
  }
28101
- await this.state.session.state.set({
28102
- activeModelPackId: activeModePackId,
28103
- fallbackStatus: void 0
28104
- });
28105
- this.state.fallbackStatus = void 0;
28106
28216
  settings.models.activeOmPackId = omPack?.id ?? null;
28107
28217
  settings.models.omModelOverride = omPack?.id === "custom" ? omPack.modelId : null;
28108
28218
  settings.models.observerModelOverride = null;
@@ -28110,6 +28220,12 @@ var MastraTUI = class MastraTUI {
28110
28220
  settings.preferences.yolo = result.yolo;
28111
28221
  settings.models.subagentModels = {};
28112
28222
  saveSettings$1(settings);
28223
+ await applyCurrentThreadPack({ state: this.state }, { packId: activeModePackId });
28224
+ await this.state.session.thread.setSetting({
28225
+ key: THREAD_FALLBACK_STATUS_KEY,
28226
+ value: void 0
28227
+ });
28228
+ this.state.fallbackStatus = void 0;
28113
28229
  updateStatusLine(this.state);
28114
28230
  await this.refreshModelAuthStatus();
28115
28231
  }
@@ -28267,4 +28383,4 @@ var MastraTUI = class MastraTUI {
28267
28383
  //#endregion
28268
28384
  export { mastraBrand as _, OMProgressComponent as a, TUI_CO_AUTHOR as b, ToolExecutionComponentEnhanced as c, detectTerminalTheme as d, applyThemeMode as f, mastra as g, getThemeMode as h, LoginDialogComponent as i, ModelSelectorComponent as l, getMarkdownTheme as m, createTUIState as n, formatOMStatus as o, getEditorTheme as p, LoginSelectorComponent as r, UserMessageComponent as s, MastraTUI as t, AssistantMessageComponent as u, restoreTerminalForeground as v, theme as y };
28269
28385
 
28270
- //# sourceMappingURL=tui-Boav6A4H.js.map
28386
+ //# sourceMappingURL=tui-CDdSkr_Q.js.map