shortcutxl 0.4.1 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/BINARY-INVENTORY.json +10 -10
  2. package/dist/app/agent-session-runtime-adapter.d.ts +5 -0
  3. package/dist/app/agent-session-runtime-adapter.js +7 -4
  4. package/dist/app/agent-session.d.ts +7 -2
  5. package/dist/app/agent-session.js +9 -5
  6. package/dist/app/observability/client-logging/client-logger.d.ts +2 -1
  7. package/dist/app/observability/client-logging/client-logger.js +11 -5
  8. package/dist/app/providers/model-resolver.d.ts +6 -0
  9. package/dist/app/providers/model-resolver.js +23 -20
  10. package/dist/app/services/user-context.d.ts +2 -0
  11. package/dist/app/services/user-context.js +4 -1
  12. package/dist/app/session/account-default-model.d.ts +33 -0
  13. package/dist/app/session/account-default-model.js +53 -0
  14. package/dist/app/session/model-manager.d.ts +14 -3
  15. package/dist/app/session/model-manager.js +19 -7
  16. package/dist/cli.js +1880 -948
  17. package/dist/config.d.ts +13 -5
  18. package/dist/config.js +37 -23
  19. package/dist/contracts/model-stream.d.ts +5 -0
  20. package/dist/contracts/wire.d.ts +3 -1
  21. package/dist/core/agent-engine.d.ts +2 -1
  22. package/dist/core/agent-engine.js +3 -0
  23. package/dist/core/agent-loop.js +8 -2
  24. package/dist/core/core-types.d.ts +3 -1
  25. package/dist/core/session/compaction/compaction.d.ts +2 -0
  26. package/dist/core/session/compaction/compaction.js +3 -0
  27. package/dist/core/session/compaction-bridge.d.ts +2 -1
  28. package/dist/core/session/compaction-bridge.js +4 -1
  29. package/dist/embedded-agent/compaction-wiring.js +4 -1
  30. package/dist/embedded-agent/compose.js +12 -4
  31. package/dist/main.d.ts +7 -0
  32. package/dist/main.js +21 -17
  33. package/dist/rpc/rpc-host.js +7 -1
  34. package/dist/shell/interactive/auth-workflow.js +7 -1
  35. package/dist/shortcut-default-model.d.ts +30 -0
  36. package/dist/shortcut-default-model.js +86 -0
  37. package/dist/startup/terminal-update-flow.js +4 -1
  38. package/dist/startup/update-action.d.ts +17 -2
  39. package/dist/startup/update-action.js +114 -40
  40. package/dist/startup/update-diagnostics.d.ts +85 -0
  41. package/dist/startup/update-diagnostics.js +188 -0
  42. package/dist/startup/update-execution.d.ts +13 -1
  43. package/dist/startup/update-execution.js +205 -6
  44. package/dist/startup/update-installation.d.ts +55 -0
  45. package/dist/startup/update-installation.js +242 -0
  46. package/dist/startup/update-manager.d.ts +5 -0
  47. package/dist/startup/update-manager.js +23 -5
  48. package/dist/startup/update-verification.d.ts +87 -0
  49. package/dist/startup/update-verification.js +204 -0
  50. package/native-app/main/chunks/{config-CwtqLhpn.js → config-DSoKsgPT.js} +1 -1
  51. package/native-app/main/chunks/{linux-C5J5oKJy.js → linux-TBGGGecs.js} +3 -3
  52. package/native-app/main/chunks/{mac-Bt_bFwUg.js → mac-Dg93p_2s.js} +3 -3
  53. package/native-app/main/chunks/{setup-worker-protocol-QUVpcuEb.js → setup-worker-protocol-CHVmPxBP.js} +54 -35
  54. package/native-app/main/chunks/{shared-Q59yhreZ.js → shared-BbAcLTbB.js} +1 -1
  55. package/native-app/main/index.js +189 -15
  56. package/native-app/main/setup-worker.js +3 -3
  57. package/native-app/package.json +1 -1
  58. package/native-app/renderer/assets/{index-De3NGXD5.js → index-BJUrz0xE.js} +2204 -163
  59. package/native-app/renderer/assets/server-PLrRTws-.js +2689 -0
  60. package/native-app/renderer/index.html +1 -1
  61. package/package.json +1 -1
  62. package/xll/python/Lib/site-packages/httpx-0.28.1.dist-info/RECORD +1 -1
  63. package/xll/python/Lib/site-packages/idna-3.18.dist-info/RECORD +1 -1
  64. package/xll/python/Lib/site-packages/pip-26.2.dist-info/RECORD +3 -3
  65. package/xll/python/Lib/site-packages/pywin32-311.dist-info/RECORD +2 -2
  66. package/xll/python/Scripts/httpx.exe +0 -0
  67. package/xll/python/Scripts/idna.exe +0 -0
  68. package/xll/python/Scripts/pip.exe +0 -0
  69. package/xll/python/Scripts/pip3.13.exe +0 -0
  70. package/xll/python/Scripts/pip3.exe +0 -0
  71. package/xll/python/Scripts/pywin32_postinstall.exe +0 -0
  72. package/xll/python/Scripts/pywin32_testall.exe +0 -0
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "generatedAt": "2026-10-20T06:34:34.546Z",
3
+ "generatedAt": "2026-10-20T23:01:29.571Z",
4
4
  "package": "shortcutxl",
5
5
  "build": {
6
6
  "builder": "github-actions",
7
7
  "repository": "lyfegame/shortcut",
8
8
  "sourceRef": "refs/heads/main",
9
- "sourceCommit": "d31a59fd8f7ebec7800d9bd325ccc4835ecab91c",
9
+ "sourceCommit": "29226447fc54eb8379f8b0b5823e158a24d562d3",
10
10
  "workflowRef": "lyfegame/shortcut/.github/workflows/release-shortcut-cli-npm-package.yml@refs/heads/main",
11
- "runId": "39380994342",
11
+ "runId": "39512005187",
12
12
  "runnerOs": "Windows",
13
13
  "runnerImage": "win25-vs2026",
14
14
  "runnerImageVersion": "20261007.264.1"
@@ -550,7 +550,7 @@
550
550
  },
551
551
  {
552
552
  "path": "xll/python/Scripts/httpx.exe",
553
- "sha256": "0ae7fc1605a56db84f3824478722830defc55116228e0d6ce810d593cc54f4e3",
553
+ "sha256": "1ef3333c745fc7dbaff8a2dcf6de6491e9e44e44580a1b3ca630019ec18aac8b",
554
554
  "source": "httpx console launcher installed into embedded Python",
555
555
  "version": "see packaged httpx distribution",
556
556
  "builtBy": "third-party",
@@ -558,7 +558,7 @@
558
558
  },
559
559
  {
560
560
  "path": "xll/python/Scripts/idna.exe",
561
- "sha256": "2aba1463e9caa2be1f8f12beee70995c893af72dd9dc81a8317056e478a15470",
561
+ "sha256": "89c1645a20d2119a8b1271a572cbf398e7000b9dd57a04b56906c513024f4771",
562
562
  "source": "Python package console launcher installed into embedded Python",
563
563
  "version": "see owning Python package metadata in site-packages",
564
564
  "builtBy": "third-party",
@@ -566,7 +566,7 @@
566
566
  },
567
567
  {
568
568
  "path": "xll/python/Scripts/pip.exe",
569
- "sha256": "ccdb9c9263d20f71609aadb08a785ba78c7e6f6757437629d96fd1078f6676af",
569
+ "sha256": "733632fc67f2f4f54f646f7a97c126ea76dc88a25761908d1c4411eed0b600ff",
570
570
  "source": "pip console launcher installed into embedded Python",
571
571
  "version": "see packaged pip distribution",
572
572
  "builtBy": "third-party",
@@ -574,7 +574,7 @@
574
574
  },
575
575
  {
576
576
  "path": "xll/python/Scripts/pip3.13.exe",
577
- "sha256": "ccdb9c9263d20f71609aadb08a785ba78c7e6f6757437629d96fd1078f6676af",
577
+ "sha256": "733632fc67f2f4f54f646f7a97c126ea76dc88a25761908d1c4411eed0b600ff",
578
578
  "source": "pip console launcher installed into embedded Python",
579
579
  "version": "see packaged pip distribution",
580
580
  "builtBy": "third-party",
@@ -582,7 +582,7 @@
582
582
  },
583
583
  {
584
584
  "path": "xll/python/Scripts/pip3.exe",
585
- "sha256": "ccdb9c9263d20f71609aadb08a785ba78c7e6f6757437629d96fd1078f6676af",
585
+ "sha256": "733632fc67f2f4f54f646f7a97c126ea76dc88a25761908d1c4411eed0b600ff",
586
586
  "source": "pip console launcher installed into embedded Python",
587
587
  "version": "see packaged pip distribution",
588
588
  "builtBy": "third-party",
@@ -590,7 +590,7 @@
590
590
  },
591
591
  {
592
592
  "path": "xll/python/Scripts/pywin32_postinstall.exe",
593
- "sha256": "72d1e776a493eddcfbcd852b3b9f79c50480d6886fffa1c1798d88625148f22a",
593
+ "sha256": "36d372cde19e37f53f33cfe10de6ee4b4f26f6abeb98b2831bf3fe4c95655380",
594
594
  "source": "Python package console launcher installed into embedded Python",
595
595
  "version": "see owning Python package metadata in site-packages",
596
596
  "builtBy": "third-party",
@@ -598,7 +598,7 @@
598
598
  },
599
599
  {
600
600
  "path": "xll/python/Scripts/pywin32_testall.exe",
601
- "sha256": "13146537a7cf67f66380470f48d4de7d2d4d12632415c673f191ab0e744fad7d",
601
+ "sha256": "223e9358a7135a893a91ac7e2ab575cbe6ab83052292687c55b3e33424bd94d1",
602
602
  "source": "Python package console launcher installed into embedded Python",
603
603
  "version": "see owning Python package metadata in site-packages",
604
604
  "builtBy": "third-party",
@@ -48,6 +48,8 @@ export interface CreateAgentSessionRuntimeAdapterOptions {
48
48
  fastModeDisabledReason?: FastModeDisabledReason;
49
49
  modeName?: string;
50
50
  model?: Model<Api>;
51
+ /** Whether `model` is the user's choice; false for a resolved default. Defaults to true. */
52
+ modelExplicit?: boolean;
51
53
  observability?: RuntimeObservability;
52
54
  permissionPolicy?: RuntimePermissionPolicy;
53
55
  prePromptContext?: () => Promise<{
@@ -73,12 +75,15 @@ export interface CreateAgentSessionRuntimeAdapterResult extends CreateAgentSessi
73
75
  }
74
76
  export declare function resolveInitialSessionModel(options: {
75
77
  model?: Model<Api>;
78
+ modelExplicit?: boolean;
76
79
  modelRegistry: AgentModelRegistry;
77
80
  sessionManager?: ProductSessionStore;
78
81
  settingsManager: AgentSessionSettingsStore;
79
82
  }): Promise<{
80
83
  model: Model<Api> | undefined;
81
84
  modelFallbackMessage?: string;
85
+ /** False when the model is a resolved default that login may replace. */
86
+ modelExplicit: boolean;
82
87
  }>;
83
88
  export declare function createAgentSessionRuntimeAdapter(options: CreateAgentSessionRuntimeAdapterOptions): Promise<CreateAgentSessionRuntimeAdapterResult>;
84
89
  //# sourceMappingURL=agent-session-runtime-adapter.d.ts.map
@@ -23,14 +23,14 @@ import { convertToLlm } from './messages.js';
23
23
  import { findInitialModel } from './providers/model-resolver.js';
24
24
  export async function resolveInitialSessionModel(options) {
25
25
  if (options.model)
26
- return { model: options.model };
26
+ return { model: options.model, modelExplicit: options.modelExplicit ?? true };
27
27
  const existingSession = options.sessionManager?.buildSessionContext();
28
28
  const hasExistingSession = (existingSession?.messages.length ?? 0) > 0;
29
29
  let modelFallbackMessage;
30
30
  if (hasExistingSession && existingSession?.model) {
31
31
  const restoredModel = options.modelRegistry.find(existingSession.model.provider, existingSession.model.modelId);
32
32
  if (restoredModel && (await options.modelRegistry.getApiKey(restoredModel))) {
33
- return { model: restoredModel };
33
+ return { model: restoredModel, modelExplicit: true };
34
34
  }
35
35
  modelFallbackMessage = `Could not restore model ${existingSession.model.provider}/${existingSession.model.modelId}`;
36
36
  }
@@ -44,13 +44,14 @@ export async function resolveInitialSessionModel(options) {
44
44
  if (!resolved.model) {
45
45
  return {
46
46
  model: undefined,
47
- modelFallbackMessage: 'No models available. Configure credentials or provide a model.'
47
+ modelFallbackMessage: 'No models available. Configure credentials or provide a model.',
48
+ modelExplicit: false
48
49
  };
49
50
  }
50
51
  if (modelFallbackMessage) {
51
52
  modelFallbackMessage += `. Using ${resolved.model.provider}/${resolved.model.id}`;
52
53
  }
53
- return { model: resolved.model, modelFallbackMessage };
54
+ return { model: resolved.model, modelFallbackMessage, modelExplicit: resolved.explicit };
54
55
  }
55
56
  function createAgentSessionCore(session, pendingHostToolRequests, shortyRuntime) {
56
57
  // AgentSession itself doesn't carry a "the last turn was cancelled" flag —
@@ -141,6 +142,7 @@ export async function createAgentSessionRuntimeAdapter(options) {
141
142
  .some((entry) => entry.type === 'thinking_level_change');
142
143
  const initialModel = await resolveInitialSessionModel({
143
144
  model: options.model,
145
+ modelExplicit: options.modelExplicit,
144
146
  modelRegistry,
145
147
  sessionManager,
146
148
  settingsManager
@@ -264,6 +266,7 @@ export async function createAgentSessionRuntimeAdapter(options) {
264
266
  modelRegistry,
265
267
  fastModeAllowed: options.fastModeAllowed,
266
268
  fastModeDisabledReason: options.fastModeDisabledReason,
269
+ initialModelExplicit: initialModel.modelExplicit,
267
270
  extensionRunnerRef,
268
271
  prePromptContext: options.prePromptContext,
269
272
  budgetLimits: options.budgetLimits,
@@ -29,6 +29,7 @@ import type { PromptTemplate } from './resources/prompt-templates.js';
29
29
  import { type AutonomousState, type ConsumedPendingRefresh, type PendingRefresh } from './session/autonomous-workflow.js';
30
30
  import { type ForkResult, type NavigateTreeResult } from './session/branch-manager.js';
31
31
  import { type ExtensionBindings } from './session/extension-lifecycle.js';
32
+ import { type SetModelOptions } from './session/model-manager.js';
32
33
  import { type ModelCycleResult } from './session/session-models.js';
33
34
  import type { AgentSessionSettingsStore } from './session/session-settings-store.js';
34
35
  import { type FastModeDisabledReason } from './session/session-speed.js';
@@ -105,6 +106,8 @@ export interface AgentSessionConfig {
105
106
  modelRegistry: AgentModelRegistry;
106
107
  fastModeAllowed?: boolean;
107
108
  fastModeDisabledReason?: FastModeDisabledReason;
109
+ /** False when the initial model is a resolved default that login may re-resolve (default true). */
110
+ initialModelExplicit?: boolean;
108
111
  /** Mutable ref used by AgentController to access the current ExtensionRunner */
109
112
  extensionRunnerRef?: {
110
113
  current?: ExtensionRunner;
@@ -449,8 +452,10 @@ export declare class AgentSession {
449
452
  setup?: (sessionManager: ExtensionSessionSetupStore) => Promise<void>;
450
453
  preserveAutonomous?: boolean;
451
454
  }): Promise<boolean>;
452
- /** Switch the session's active LLM to `model`, persisting the change. */
453
- setModel(model: Model<any>): Promise<void>;
455
+ /** Switch the active LLM; `{ explicit: false }` marks a runtime-chosen default (not saved). */
456
+ setModel(model: Model<any>, options?: SetModelOptions): Promise<void>;
457
+ /** True when the current model is the user's choice rather than a resolved default. */
458
+ get isModelExplicitlySelected(): boolean;
454
459
  /** Cycle to the next configured model and report the switch result. */
455
460
  cycleModel(direction?: 'forward' | 'backward'): Promise<ModelCycleResult | undefined>;
456
461
  /** Set the model's reasoning/thinking level for subsequent turns. */
@@ -163,7 +163,7 @@ export class AgentSession {
163
163
  });
164
164
  // Construct extracted modules with stable deps
165
165
  this._tools = new ToolRegistry(this.agent, this._resourceLoader, this.settingsManager, config.cwd, config.initialPermissionPolicy);
166
- this._models = new ModelManager(this.agent, this._modelRegistry, this.sessionManager, this.settingsManager);
166
+ this._models = new ModelManager(this.agent, this._modelRegistry, this.sessionManager, this.settingsManager, config.initialModelExplicit ?? true);
167
167
  this._branches = new BranchManager({
168
168
  agent: this.agent,
169
169
  sessionManager: this.sessionManager,
@@ -242,7 +242,7 @@ export class AgentSession {
242
242
  compaction: this._compaction,
243
243
  compactionActions: () => this._compactionActions,
244
244
  removeLastErrorGroup: () => this._removeLastErrorGroup(),
245
- setModel: (model) => this.setModel(model)
245
+ setModel: (model) => this.setModel(model, { explicit: false }) // automatic fallback, not a pick
246
246
  });
247
247
  // Forward extracted module events to session listeners
248
248
  this._recovery.onEvent((event) => this._emit(event));
@@ -1330,11 +1330,15 @@ export class AgentSession {
1330
1330
  // =========================================================================
1331
1331
  // Model Management (delegated to ModelManager)
1332
1332
  // =========================================================================
1333
- /** Switch the session's active LLM to `model`, persisting the change. */
1334
- async setModel(model) {
1335
- await this._models.setModel(model);
1333
+ /** Switch the active LLM; `{ explicit: false }` marks a runtime-chosen default (not saved). */
1334
+ async setModel(model, options) {
1335
+ await this._models.setModel(model, options);
1336
1336
  this._speed.clearUnavailableSpeed();
1337
1337
  }
1338
+ /** True when the current model is the user's choice rather than a resolved default. */
1339
+ get isModelExplicitlySelected() {
1340
+ return this._models.isExplicit;
1341
+ }
1338
1342
  /** Cycle to the next configured model and report the switch result. */
1339
1343
  async cycleModel(direction = 'forward') {
1340
1344
  const result = await this._models.cycleModel(direction);
@@ -60,7 +60,8 @@ export declare class ShortcutClientLogger {
60
60
  private bufferAccountGeneration?;
61
61
  private emit;
62
62
  private startPeriodicFlush;
63
- flush(): Promise<void>;
63
+ /** Send buffered entries; resolves true only when this call delivered a batch. */
64
+ flush(): Promise<boolean>;
64
65
  destroy(): Promise<void>;
65
66
  }
66
67
  //# sourceMappingURL=client-logger.d.ts.map
@@ -79,9 +79,9 @@ export class ShortcutClientLogger {
79
79
  this.flushTimer = null;
80
80
  }
81
81
  }
82
- disable() {
82
+ async disable() {
83
83
  this.setEnabled(false);
84
- return this.flush();
84
+ await this.flush();
85
85
  }
86
86
  info(input) {
87
87
  this.emit('info', input);
@@ -127,27 +127,32 @@ export class ShortcutClientLogger {
127
127
  void this.flush();
128
128
  }, this.flushIntervalMs);
129
129
  }
130
+ /** Send buffered entries; resolves true only when this call delivered a batch. */
130
131
  async flush() {
131
132
  const routing = getLocalShortcutRouting();
132
133
  if (routing &&
133
134
  (!routing.canSendUsageLogs() || this.bufferAccountGeneration !== routing.generation)) {
134
135
  this.buffer = [];
135
- return;
136
+ return false;
136
137
  }
137
138
  if (this.pendingFlush) {
138
139
  await this.pendingFlush;
139
- return;
140
+ return false;
140
141
  }
141
142
  if (this.buffer.length === 0) {
142
- return;
143
+ return false;
143
144
  }
144
145
  const batch = {
145
146
  client_time: this.now().toISOString(),
146
147
  entries: this.buffer
147
148
  };
148
149
  this.buffer = [];
150
+ let delivered = false;
149
151
  this.pendingFlush = this.transport
150
152
  .send(batch)
153
+ .then(() => {
154
+ delivered = true;
155
+ })
151
156
  .catch(() => {
152
157
  // Fail open. Observability must never break the primary user flow.
153
158
  })
@@ -155,6 +160,7 @@ export class ShortcutClientLogger {
155
160
  this.pendingFlush = null;
156
161
  });
157
162
  await this.pendingFlush;
163
+ return delivered;
158
164
  }
159
165
  async destroy() {
160
166
  this.destroyed = true;
@@ -70,6 +70,12 @@ export interface InitialModelResult {
70
70
  model: Model<Api> | undefined;
71
71
  thinkingLevel: ThinkingLevel;
72
72
  fallbackMessage: string | undefined;
73
+ /**
74
+ * True when the model was chosen by the user (CLI flag or saved settings), false when it is
75
+ * the resolved default. A default may be re-resolved when the account changes (login).
76
+ * Saved settings are honoured as written, even for a model the picker no longer lists.
77
+ */
78
+ explicit: boolean;
73
79
  }
74
80
  /**
75
81
  * Find the initial model to use based on priority:
@@ -4,7 +4,9 @@
4
4
  import chalk from 'chalk';
5
5
  import { resolveModelReasoningLevel } from '../../ai/models.js';
6
6
  import { DEFAULT_THINKING_LEVEL, isValidThinkingLevel } from '../../core/defaults.js';
7
- import { SHORTCUT_FREE_MODEL_ID, SHORTCUT_MODEL_ID, SHORTCUT_OPENAI_MODEL_ID, SHORTCUT_PROVIDER_ID } from '../../model-ids.js';
7
+ import { SHORTCUT_MODEL_ID, SHORTCUT_OPENAI_MODEL_ID, SHORTCUT_PROVIDER_ID } from '../../model-ids.js';
8
+ import { getShortcutDefaultModelCandidates, getShortcutDefaultModelPlan, SHORTCUT_PRODUCT_DEFAULT_MODEL_ID } from '../../shortcut-default-model.js';
9
+ import { filterInteractiveSelectableModels } from '../session/session-models.js';
8
10
  export function formatModelRef(model) {
9
11
  return `${model.provider}/${model.id}`;
10
12
  }
@@ -37,7 +39,7 @@ export function resolveExactModelRef(availableModels, requested) {
37
39
  }
38
40
  /** Default model IDs for each provider with a preferred initial model. */
39
41
  export const defaultModelPerProvider = {
40
- [SHORTCUT_PROVIDER_ID]: SHORTCUT_MODEL_ID.Gpt61Sol,
42
+ [SHORTCUT_PROVIDER_ID]: SHORTCUT_PRODUCT_DEFAULT_MODEL_ID,
41
43
  anthropic: SHORTCUT_MODEL_ID.ClaudeOpus5,
42
44
  openai: SHORTCUT_OPENAI_MODEL_ID,
43
45
  'azure-openai-responses': SHORTCUT_OPENAI_MODEL_ID,
@@ -57,16 +59,8 @@ export const defaultModelPerProvider = {
57
59
  };
58
60
  function getDefaultModelCandidatesForProvider(provider) {
59
61
  if (provider === SHORTCUT_PROVIDER_ID) {
60
- return [
61
- SHORTCUT_MODEL_ID.Gpt61Sol,
62
- SHORTCUT_MODEL_ID.Gpt6Sol,
63
- SHORTCUT_OPENAI_MODEL_ID,
64
- SHORTCUT_MODEL_ID.ClaudeFable51,
65
- SHORTCUT_MODEL_ID.ClaudeFable5,
66
- SHORTCUT_MODEL_ID.ClaudeOpus5,
67
- // Accounts without a paid plan see none of the models above.
68
- SHORTCUT_FREE_MODEL_ID
69
- ];
62
+ // Plan-aware and picker-listed only, matching the web app's plan default.
63
+ return getShortcutDefaultModelCandidates(getShortcutDefaultModelPlan());
70
64
  }
71
65
  return [defaultModelPerProvider[provider]].filter((modelId) => !!modelId);
72
66
  }
@@ -330,7 +324,8 @@ export async function findInitialModel(options) {
330
324
  return {
331
325
  model: found,
332
326
  thinkingLevel: resolveModelReasoningLevel(found, undefined),
333
- fallbackMessage: undefined
327
+ fallbackMessage: undefined,
328
+ explicit: true
334
329
  };
335
330
  }
336
331
  // 2. Try saved default from settings
@@ -339,7 +334,7 @@ export async function findInitialModel(options) {
339
334
  if (found) {
340
335
  model = found;
341
336
  thinkingLevel = resolveModelReasoningLevel(found, defaultThinkingLevel);
342
- return { model, thinkingLevel, fallbackMessage: undefined };
337
+ return { model, thinkingLevel, fallbackMessage: undefined, explicit: true };
343
338
  }
344
339
  }
345
340
  // 4. Try first available model with valid API key
@@ -353,19 +348,27 @@ export async function findInitialModel(options) {
353
348
  return {
354
349
  model: match,
355
350
  thinkingLevel: resolveModelReasoningLevel(match, undefined),
356
- fallbackMessage: undefined
351
+ fallbackMessage: undefined,
352
+ explicit: false
357
353
  };
358
354
  }
359
355
  }
360
356
  }
361
- // If no default found, use first available
357
+ // If no default found, use the first available model the picker lists.
358
+ const firstModel = filterInteractiveSelectableModels(availableModels)[0] ?? availableModels[0];
362
359
  return {
363
- model: availableModels[0],
364
- thinkingLevel: resolveModelReasoningLevel(availableModels[0], undefined),
365
- fallbackMessage: undefined
360
+ model: firstModel,
361
+ thinkingLevel: resolveModelReasoningLevel(firstModel, undefined),
362
+ fallbackMessage: undefined,
363
+ explicit: false
366
364
  };
367
365
  }
368
366
  // 5. No model found
369
- return { model: undefined, thinkingLevel: DEFAULT_THINKING_LEVEL, fallbackMessage: undefined };
367
+ return {
368
+ model: undefined,
369
+ thinkingLevel: DEFAULT_THINKING_LEVEL,
370
+ fallbackMessage: undefined,
371
+ explicit: false
372
+ };
370
373
  }
371
374
  //# sourceMappingURL=model-resolver.js.map
@@ -19,6 +19,8 @@ export interface UserInfo {
19
19
  proCreditsTier?: number | null;
20
20
  authorizedSkillTeamIds?: string[];
21
21
  additionalModelPickerEntries?: UserModelPickerEntry[];
22
+ /** Picker key the user's default resolves to (the org-admin-saved key for Team accounts). */
23
+ defaultModelPickerEntryKey?: string | null;
22
24
  }
23
25
  export interface UserModelPickerEntry {
24
26
  modelName: string;
@@ -90,7 +90,10 @@ export async function fetchUserInfo(accessToken, options) {
90
90
  plan: hasPlan ? (data.plan ?? null) : undefined,
91
91
  proCreditsTier: data.proCreditsTier ?? null,
92
92
  authorizedSkillTeamIds: parseAuthorizedSkillTeamIds(data),
93
- ...(additionalModelPickerEntries.length > 0 ? { additionalModelPickerEntries } : {})
93
+ ...(additionalModelPickerEntries.length > 0 ? { additionalModelPickerEntries } : {}),
94
+ ...(typeof data.defaultModelPickerEntryKey === 'string'
95
+ ? { defaultModelPickerEntryKey: data.defaultModelPickerEntryKey }
96
+ : {})
94
97
  };
95
98
  }
96
99
  catch (error) {
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Keeps a session's model in line with the signed-in account.
3
+ *
4
+ * A model the runtime resolved itself (no user pick yet) follows the account's plan
5
+ * default when the account changes, so a runtime that starts signed out lands on the
6
+ * same model as one that starts signed in. A model the user picked is kept unless the
7
+ * new account cannot use it.
8
+ */
9
+ import type { Model } from '../../ai/types.js';
10
+ import type { SetModelOptions } from './model-manager.js';
11
+ export interface AccountModelSession {
12
+ readonly model: Model<any> | undefined;
13
+ readonly isModelExplicitlySelected: boolean;
14
+ /** The user's saved pick (settings defaultProvider/defaultModel), if any. */
15
+ readonly settingsManager?: {
16
+ getDefaultProvider(): string | undefined;
17
+ getDefaultModel(): string | undefined;
18
+ };
19
+ setModel(model: Model<any>, options?: SetModelOptions): Promise<void>;
20
+ }
21
+ export interface AccountModelRegistry {
22
+ find(provider: string, modelId: string): Model<any> | undefined;
23
+ getAvailable(): Model<any>[] | Promise<Model<any>[]>;
24
+ }
25
+ /** Move off a model the account's policy no longer allows, onto the plan default. */
26
+ export declare function moveSessionOffPolicyDeniedModel(session: AccountModelSession | null | undefined, modelRegistry: AccountModelRegistry): Promise<void>;
27
+ /**
28
+ * After a login or account refresh: a resolved default becomes the user's saved pick when
29
+ * there is one (a signed-out start cannot restore it), otherwise the account's plan default;
30
+ * an explicit pick is kept unless the account cannot use it.
31
+ */
32
+ export declare function alignSessionModelWithAccount(session: AccountModelSession | null | undefined, modelRegistry: AccountModelRegistry): Promise<void>;
33
+ //# sourceMappingURL=account-default-model.d.ts.map
@@ -0,0 +1,53 @@
1
+ import { resolveShortcutDefaultModel } from '../../shortcut-default-model.js';
2
+ import { filterInteractiveSelectableModels } from './session-models.js';
3
+ function sameModel(a, b) {
4
+ return a?.provider === b.provider && a?.id === b.id;
5
+ }
6
+ /** Move off a model the account's policy no longer allows, onto the plan default. */
7
+ export async function moveSessionOffPolicyDeniedModel(session, modelRegistry) {
8
+ const currentModel = session?.model;
9
+ if (!session || !currentModel)
10
+ return;
11
+ if (modelRegistry.find(currentModel.provider, currentModel.id))
12
+ return;
13
+ const availableModels = await modelRegistry.getAvailable();
14
+ const fallback = resolveShortcutDefaultModel(availableModels) ??
15
+ filterInteractiveSelectableModels(availableModels)[0] ??
16
+ availableModels[0];
17
+ if (fallback)
18
+ await session.setModel(fallback, { explicit: false });
19
+ }
20
+ /** The user's saved pick (settings), when the account can run it. */
21
+ function savedPick(session, availableModels) {
22
+ const provider = session.settingsManager?.getDefaultProvider();
23
+ const modelId = session.settingsManager?.getDefaultModel();
24
+ if (!provider || !modelId)
25
+ return undefined;
26
+ return availableModels.find((model) => model.provider === provider && model.id === modelId);
27
+ }
28
+ /**
29
+ * After a login or account refresh: a resolved default becomes the user's saved pick when
30
+ * there is one (a signed-out start cannot restore it), otherwise the account's plan default;
31
+ * an explicit pick is kept unless the account cannot use it.
32
+ */
33
+ export async function alignSessionModelWithAccount(session, modelRegistry) {
34
+ if (!session)
35
+ return;
36
+ if (!session.isModelExplicitlySelected) {
37
+ const availableModels = await modelRegistry.getAvailable();
38
+ const pick = savedPick(session, availableModels);
39
+ if (pick) {
40
+ await session.setModel(pick);
41
+ return;
42
+ }
43
+ const planDefault = resolveShortcutDefaultModel(availableModels);
44
+ if (planDefault) {
45
+ if (!sameModel(session.model, planDefault)) {
46
+ await session.setModel(planDefault, { explicit: false });
47
+ }
48
+ return;
49
+ }
50
+ }
51
+ await moveSessionOffPolicyDeniedModel(session, modelRegistry);
52
+ }
53
+ //# sourceMappingURL=account-default-model.js.map
@@ -21,14 +21,25 @@ export interface ModelManagerAgent {
21
21
  setModel(model: Model<any>): void;
22
22
  setThinkingLevel(level: ThinkingLevel): void;
23
23
  }
24
+ export interface SetModelOptions {
25
+ /**
26
+ * False when the runtime picks the model itself (the plan default after login, a
27
+ * policy fallback). Such a pick is not saved as the user's default model and may be
28
+ * re-resolved when the account changes. Defaults to true: a user selection.
29
+ */
30
+ explicit?: boolean;
31
+ }
24
32
  export declare class ModelManager {
25
33
  private readonly _agent;
26
34
  private readonly _modelRegistry;
27
35
  private readonly _sessionManager;
28
36
  private readonly _settingsManager;
29
- constructor(_agent: ModelManagerAgent, _modelRegistry: AgentModelRegistry, _sessionManager: ModelSessionStore, _settingsManager: RuntimeDefaultsStore);
30
- /** Set a specific model. Validates API key and persists. */
31
- setModel(model: Model<any>): Promise<void>;
37
+ private _explicit;
38
+ constructor(_agent: ModelManagerAgent, _modelRegistry: AgentModelRegistry, _sessionManager: ModelSessionStore, _settingsManager: RuntimeDefaultsStore, initialModelExplicit?: boolean);
39
+ /** True when the current model is the user's choice rather than a resolved default. */
40
+ get isExplicit(): boolean;
41
+ /** Set a specific model. Validates API key; persists as the default only for user picks. */
42
+ setModel(model: Model<any>, options?: SetModelOptions): Promise<void>;
32
43
  /** Cycle to the next available model. Returns result or undefined if none available. */
33
44
  cycleModel(direction?: 'forward' | 'backward'): Promise<ModelCycleResult | undefined>;
34
45
  /**
@@ -16,16 +16,24 @@ export class ModelManager {
16
16
  _modelRegistry;
17
17
  _sessionManager;
18
18
  _settingsManager;
19
- constructor(_agent, _modelRegistry, _sessionManager, _settingsManager) {
19
+ _explicit;
20
+ constructor(_agent, _modelRegistry, _sessionManager, _settingsManager, initialModelExplicit = true) {
20
21
  this._agent = _agent;
21
22
  this._modelRegistry = _modelRegistry;
22
23
  this._sessionManager = _sessionManager;
23
24
  this._settingsManager = _settingsManager;
25
+ this._explicit = initialModelExplicit;
24
26
  }
25
- /** Set a specific model. Validates API key and persists. */
26
- async setModel(model) {
27
+ /** True when the current model is the user's choice rather than a resolved default. */
28
+ get isExplicit() {
29
+ return this._explicit;
30
+ }
31
+ /** Set a specific model. Validates API key; persists as the default only for user picks. */
32
+ async setModel(model, options = {}) {
27
33
  await this._validateApiKey(model);
28
- this._applyAndPersist(model);
34
+ const explicit = options.explicit ?? true;
35
+ this._applyAndPersist(model, explicit);
36
+ this._explicit = explicit;
29
37
  this.setThinkingLevel(this._agent.state.thinkingLevel);
30
38
  }
31
39
  /** Cycle to the next available model. Returns result or undefined if none available. */
@@ -35,7 +43,8 @@ export class ModelManager {
35
43
  if (!next)
36
44
  return undefined;
37
45
  await this._validateApiKey(next);
38
- this._applyAndPersist(next);
46
+ this._applyAndPersist(next, true);
47
+ this._explicit = true;
39
48
  this.setThinkingLevel(this._agent.state.thinkingLevel);
40
49
  return { model: next, thinkingLevel: this._agent.state.thinkingLevel };
41
50
  }
@@ -45,6 +54,8 @@ export class ModelManager {
45
54
  */
46
55
  async restoreModel(model) {
47
56
  this._agent.setModel(model);
57
+ // A resumed session keeps the model it was using; login does not replace it.
58
+ this._explicit = true;
48
59
  }
49
60
  /** Set thinking level, clamped to model capabilities. Persists only on change. */
50
61
  setThinkingLevel(level) {
@@ -75,10 +86,11 @@ export class ModelManager {
75
86
  if (!apiKey)
76
87
  throw new Error(`No API key for ${model.provider}/${model.id}`);
77
88
  }
78
- _applyAndPersist(model) {
89
+ _applyAndPersist(model, saveAsDefault) {
79
90
  this._agent.setModel(model);
80
91
  this._sessionManager.appendModelChange(model.provider, model.id);
81
- this._settingsManager.setDefaultModelAndProvider(model.provider, model.id);
92
+ if (saveAsDefault)
93
+ this._settingsManager.setDefaultModelAndProvider(model.provider, model.id);
82
94
  }
83
95
  }
84
96
  //# sourceMappingURL=model-manager.js.map