shortcutxl 0.4.0 → 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 (75) hide show
  1. package/BINARY-INVENTORY.json +12 -12
  2. package/CHANGELOG.md +6 -0
  3. package/dist/app/agent-session-runtime-adapter.d.ts +5 -0
  4. package/dist/app/agent-session-runtime-adapter.js +7 -4
  5. package/dist/app/agent-session.d.ts +7 -2
  6. package/dist/app/agent-session.js +9 -5
  7. package/dist/app/observability/client-logging/client-logger.d.ts +2 -1
  8. package/dist/app/observability/client-logging/client-logger.js +11 -5
  9. package/dist/app/providers/model-resolver.d.ts +6 -0
  10. package/dist/app/providers/model-resolver.js +23 -20
  11. package/dist/app/services/user-context.d.ts +2 -0
  12. package/dist/app/services/user-context.js +4 -1
  13. package/dist/app/session/account-default-model.d.ts +33 -0
  14. package/dist/app/session/account-default-model.js +53 -0
  15. package/dist/app/session/model-manager.d.ts +15 -4
  16. package/dist/app/session/model-manager.js +23 -8
  17. package/dist/app/session/session-lifecycle.js +14 -12
  18. package/dist/cli.js +1892 -956
  19. package/dist/config.d.ts +13 -5
  20. package/dist/config.js +37 -23
  21. package/dist/contracts/model-stream.d.ts +5 -0
  22. package/dist/contracts/wire.d.ts +3 -1
  23. package/dist/core/agent-engine.d.ts +2 -1
  24. package/dist/core/agent-engine.js +3 -0
  25. package/dist/core/agent-loop.js +8 -2
  26. package/dist/core/core-types.d.ts +3 -1
  27. package/dist/core/session/compaction/compaction.d.ts +2 -0
  28. package/dist/core/session/compaction/compaction.js +3 -0
  29. package/dist/core/session/compaction-bridge.d.ts +2 -1
  30. package/dist/core/session/compaction-bridge.js +4 -1
  31. package/dist/embedded-agent/compaction-wiring.js +4 -1
  32. package/dist/embedded-agent/compose.js +12 -4
  33. package/dist/main.d.ts +7 -0
  34. package/dist/main.js +21 -17
  35. package/dist/rpc/rpc-host.js +7 -1
  36. package/dist/shell/interactive/auth-workflow.js +7 -1
  37. package/dist/shortcut-default-model.d.ts +30 -0
  38. package/dist/shortcut-default-model.js +86 -0
  39. package/dist/startup/terminal-update-flow.js +4 -1
  40. package/dist/startup/update-action.d.ts +17 -2
  41. package/dist/startup/update-action.js +114 -40
  42. package/dist/startup/update-diagnostics.d.ts +85 -0
  43. package/dist/startup/update-diagnostics.js +188 -0
  44. package/dist/startup/update-execution.d.ts +13 -1
  45. package/dist/startup/update-execution.js +205 -6
  46. package/dist/startup/update-installation.d.ts +55 -0
  47. package/dist/startup/update-installation.js +242 -0
  48. package/dist/startup/update-manager.d.ts +5 -0
  49. package/dist/startup/update-manager.js +23 -5
  50. package/dist/startup/update-verification.d.ts +87 -0
  51. package/dist/startup/update-verification.js +204 -0
  52. package/dist/supported-models.generated.js +1 -1
  53. package/native-app/main/chunks/{config-CwtqLhpn.js → config-DSoKsgPT.js} +1 -1
  54. package/native-app/main/chunks/{linux-C5J5oKJy.js → linux-TBGGGecs.js} +3 -3
  55. package/native-app/main/chunks/{mac-Bt_bFwUg.js → mac-Dg93p_2s.js} +3 -3
  56. package/native-app/main/chunks/{setup-worker-protocol-QUVpcuEb.js → setup-worker-protocol-CHVmPxBP.js} +54 -35
  57. package/native-app/main/chunks/{shared-Q59yhreZ.js → shared-BbAcLTbB.js} +1 -1
  58. package/native-app/main/index.js +190 -16
  59. package/native-app/main/setup-worker.js +3 -3
  60. package/native-app/package.json +1 -1
  61. package/native-app/renderer/assets/{index-DkKrXRl6.js → index-BJUrz0xE.js} +2210 -167
  62. package/native-app/renderer/assets/server-PLrRTws-.js +2689 -0
  63. package/native-app/renderer/index.html +1 -1
  64. package/package.json +1 -1
  65. package/xll/python/Lib/site-packages/httpx-0.28.1.dist-info/RECORD +1 -1
  66. package/xll/python/Lib/site-packages/idna-3.18.dist-info/RECORD +1 -1
  67. package/xll/python/Lib/site-packages/pip-26.2.dist-info/RECORD +3 -3
  68. package/xll/python/Lib/site-packages/pywin32-311.dist-info/RECORD +2 -2
  69. package/xll/python/Scripts/httpx.exe +0 -0
  70. package/xll/python/Scripts/idna.exe +0 -0
  71. package/xll/python/Scripts/pip.exe +0 -0
  72. package/xll/python/Scripts/pip3.13.exe +0 -0
  73. package/xll/python/Scripts/pip3.exe +0 -0
  74. package/xll/python/Scripts/pywin32_postinstall.exe +0 -0
  75. package/xll/python/Scripts/pywin32_testall.exe +0 -0
package/dist/config.d.ts CHANGED
@@ -14,18 +14,26 @@ export interface UpdateLaunchCommand {
14
14
  }
15
15
  /** Render a human-readable instruction for updating the shipped package. */
16
16
  export declare function getUpdateInstruction(packageName: string): string;
17
+ /** Where and which release an update installs. */
18
+ export interface UpdateTarget {
19
+ /** Advertised release; pinned so a registry whose `latest` lags cannot install an older one. */
20
+ version?: string;
21
+ /** npm global prefix of the running installation, so npm replaces what the user runs. */
22
+ npmPrefix?: string;
23
+ }
17
24
  /**
18
25
  * Get the shell command to auto-update the agent package.
19
26
  */
20
- export declare function getUpdateCommand(packageName: string): string;
27
+ export declare function getUpdateCommand(packageName: string, target?: UpdateTarget): string;
21
28
  /** Return the update as an executable and arguments for hosts that avoid shell command strings. */
22
- export declare function getUpdateLaunchCommand(packageName: string): UpdateLaunchCommand;
29
+ export declare function getUpdateLaunchCommand(packageName: string, target?: UpdateTarget): UpdateLaunchCommand;
23
30
  /**
24
- * Get the base directory for resolving package assets (themes, package.json, README.md, CHANGELOG.md).
25
- * - For Node.js (dist/): returns __dirname (the dist/ directory)
26
- * - For tsx (src/): returns parent directory (the package root)
31
+ * Get the base directory for resolving package assets (themes, README.md, CHANGELOG.md).
32
+ * Defaults to the running package root; SHORTCUT_PACKAGE_DIR can select separate assets.
27
33
  */
28
34
  export declare function getPackageDir(): string;
35
+ /** Locate the running code independently of an optional asset-directory override. */
36
+ export declare function getRuntimePackageDir(): string;
29
37
  /**
30
38
  * Returns true when running directly from a source checkout (tsx / repo root),
31
39
  * not from an installed package payload.
package/dist/config.js CHANGED
@@ -41,46 +41,62 @@ function detectInstallMethod() {
41
41
  /** Render a human-readable instruction for updating the shipped package. */
42
42
  export function getUpdateInstruction(packageName) {
43
43
  const method = detectInstallMethod();
44
+ const suffix = process.platform === 'win32' ? '.cmd' : '';
44
45
  switch (method) {
45
46
  case 'pnpm':
46
47
  return `Run: pnpm add -g ${packageName}`;
47
48
  case 'yarn':
48
49
  return `Run: yarn global add ${packageName}`;
49
50
  case 'npm':
50
- return `Run: npm install -g ${packageName}`;
51
+ return `Run: npm${suffix} install -g ${packageName}`;
51
52
  default:
52
- return `Run: npm install -g ${packageName}`;
53
+ return `Run: npm${suffix} install -g ${packageName}`;
53
54
  }
54
55
  }
55
56
  /**
56
57
  * Get the shell command to auto-update the agent package.
57
58
  */
58
- export function getUpdateCommand(packageName) {
59
- const command = getUpdateLaunchCommand(packageName);
60
- return [command.executable, ...command.args].join(' ');
59
+ export function getUpdateCommand(packageName, target = {}) {
60
+ const command = getUpdateLaunchCommand(packageName, target);
61
+ return [command.executable, ...command.args.map(quoteShellArgument)].join(' ');
61
62
  }
62
63
  /** Return the update as an executable and arguments for hosts that avoid shell command strings. */
63
- export function getUpdateLaunchCommand(packageName) {
64
+ export function getUpdateLaunchCommand(packageName, target = {}) {
65
+ const spec = target.version ? `${packageName}@${target.version}` : packageName;
64
66
  switch (detectInstallMethod()) {
65
67
  case 'pnpm':
66
- return { executable: 'pnpm', args: ['add', '-g', packageName] };
68
+ return { executable: 'pnpm', args: ['add', '-g', spec] };
67
69
  case 'yarn':
68
- return { executable: 'yarn', args: ['global', 'add', packageName] };
70
+ return { executable: 'yarn', args: ['global', 'add', spec] };
69
71
  case 'npm':
70
72
  default:
71
- return { executable: 'npm', args: ['install', '-g', packageName] };
73
+ return {
74
+ executable: 'npm',
75
+ args: [
76
+ 'install',
77
+ '-g',
78
+ // Forward slashes keep Windows paths intact in both Git Bash and cmd.exe.
79
+ ...(target.npmPrefix ? ['--prefix', target.npmPrefix.replaceAll('\\', '/')] : []),
80
+ spec
81
+ ]
82
+ };
72
83
  }
73
84
  }
85
+ /** Quote an argument for the POSIX shell or cmd.exe that runs the update command. */
86
+ function quoteShellArgument(argument) {
87
+ return /^[\w@./:=+-]+$/.test(argument) ? argument : `"${argument.replaceAll('"', '')}"`;
88
+ }
74
89
  // =============================================================================
75
90
  // Package Asset Paths (shipped with executable)
76
91
  // =============================================================================
77
92
  /**
78
- * Get the base directory for resolving package assets (themes, package.json, README.md, CHANGELOG.md).
79
- * - For Node.js (dist/): returns __dirname (the dist/ directory)
80
- * - For tsx (src/): returns parent directory (the package root)
93
+ * Get the base directory for resolving package assets (themes, README.md, CHANGELOG.md).
94
+ * Defaults to the running package root; SHORTCUT_PACKAGE_DIR can select separate assets.
81
95
  */
82
96
  export function getPackageDir() {
83
- // Allow override via environment variable (useful for Nix/Guix where store paths tokenize poorly)
97
+ return getPackageDirOverride() ?? getRuntimePackageDir();
98
+ }
99
+ function getPackageDirOverride() {
84
100
  const envDir = process.env.SHORTCUT_PACKAGE_DIR;
85
101
  if (envDir) {
86
102
  if (envDir === '~')
@@ -89,7 +105,10 @@ export function getPackageDir() {
89
105
  return homedir() + envDir.slice(1);
90
106
  return envDir;
91
107
  }
92
- // Node.js: walk up from __dirname until we find package.json
108
+ return undefined;
109
+ }
110
+ /** Locate the running code independently of an optional asset-directory override. */
111
+ export function getRuntimePackageDir() {
93
112
  let dir = __dirname;
94
113
  while (dir !== dirname(dir)) {
95
114
  if (existsSync(join(dir, 'package.json'))) {
@@ -97,8 +116,8 @@ export function getPackageDir() {
97
116
  }
98
117
  dir = dirname(dir);
99
118
  }
100
- // Fallback (shouldn't happen)
101
- return __dirname;
119
+ // Standalone Nix/Guix layouts can keep their only package metadata with the assets.
120
+ return getPackageDirOverride() ?? __dirname;
102
121
  }
103
122
  /**
104
123
  * Returns true when running directly from a source checkout (tsx / repo root),
@@ -162,11 +181,6 @@ export function getExportTemplateDir() {
162
181
  'src/shell/export-html'
163
182
  ]);
164
183
  }
165
- /** Get path to package.json */
166
- /** Get path to the package.json shipped with the runtime. */
167
- function getPackageJsonPath() {
168
- return join(getPackageDir(), 'package.json');
169
- }
170
184
  /** Get path to agent-docs/docs directory (used for system prompt context) */
171
185
  export function getDocsPath() {
172
186
  return resolve(join(getPackageDir(), 'agent-docs', 'docs'));
@@ -182,7 +196,7 @@ export function getChangelogPath() {
182
196
  // =============================================================================
183
197
  // App Config (from package.json shortcutConfig)
184
198
  // =============================================================================
185
- const pkg = JSON.parse(readFileSync(getPackageJsonPath(), 'utf-8'));
199
+ const pkg = JSON.parse(readFileSync(join(getRuntimePackageDir(), 'package.json'), 'utf-8'));
186
200
  export const APP_NAME = pkg.shortcutConfig?.name || 'shortcut';
187
201
  export const CONFIG_DIR_NAME = pkg.shortcutConfig?.configDir || '.shortcut';
188
202
  export const VERSION = pkg.version;
@@ -235,7 +249,7 @@ export const NPM_REGISTRY_URL = process.env.SHORTCUT_NPM_REGISTRY_URL?.trim() ||
235
249
  * 3. Global install fallback (bin in PATH) → ['shortcut']
236
250
  */
237
251
  export function resolveOwnBinary() {
238
- const packageDir = getPackageDir();
252
+ const packageDir = getRuntimePackageDir();
239
253
  const currentScript = process.argv[1] ? resolve(process.argv[1]) : '';
240
254
  const preferSource = currentScript.endsWith('.ts');
241
255
  const candidates = preferSource
@@ -273,6 +273,11 @@ export interface ShortcutXLModelStreamTool {
273
273
  readonly name: string;
274
274
  readonly parameters: Record<string, unknown>;
275
275
  }
276
+ /** Telemetry-only execution provenance; never a billing or provider identifier. */
277
+ export interface ShortcutXLModelRequestCorrelation {
278
+ readonly runtimeRunId?: string;
279
+ readonly originUserMessageId?: string;
280
+ }
276
281
  export interface ShortcutXLModelStreamRequest {
277
282
  readonly agentMode?: string;
278
283
  readonly effortLevel?: ShortcutXLModelStreamEffortLevel;
@@ -6,7 +6,7 @@ import type { Api, Context, Model, SimpleStreamOptions } from '../ai/types.js';
6
6
  import { type AgentCommand, type AgentEvent } from './agent-api.js';
7
7
  import { type HostToolRequest, type HostToolResult, type ToolRegistry } from './host-tool-api.js';
8
8
  import type { ShortcutXLUsageLogRequestType } from './model-call.js';
9
- import { type ShortcutXLLlmOperation, type ShortcutXLModelStreamEvent } from './model-stream.js';
9
+ import { type ShortcutXLLlmOperation, type ShortcutXLModelRequestCorrelation, type ShortcutXLModelStreamEvent } from './model-stream.js';
10
10
  export declare const WorkerMessageKind: {
11
11
  readonly HostToolRequest: "hostToolRequest";
12
12
  readonly HostToolResult: "hostToolResult";
@@ -51,6 +51,7 @@ export interface HostToolResultMessage {
51
51
  }
52
52
  export type ShortcutXLAiStreamOptions = Pick<SimpleStreamOptions, 'cacheRetention' | 'headers' | 'maxRetryDelayMs' | 'maxTokens' | 'metadata' | 'reasoning' | 'temperature' | 'toolChoice' | 'transport'> & {
53
53
  readonly agentMode?: string;
54
+ readonly requestCorrelation?: ShortcutXLModelRequestCorrelation;
54
55
  readonly continuationRunId?: string;
55
56
  readonly fileAttachmentIds?: readonly string[];
56
57
  readonly fastModeModelIds?: readonly string[];
@@ -61,6 +62,7 @@ export type ShortcutXLAiStreamOptions = Pick<SimpleStreamOptions, 'cacheRetentio
61
62
  readonly userMessageId?: string;
62
63
  };
63
64
  export type ShortcutXLAiInvokeOptions = Pick<SimpleStreamOptions, 'cacheRetention' | 'headers' | 'maxRetryDelayMs' | 'maxTokens' | 'metadata' | 'reasoning' | 'temperature' | 'toolChoice' | 'transport'> & {
65
+ readonly requestCorrelation?: ShortcutXLModelRequestCorrelation;
64
66
  readonly continuationRunId?: string;
65
67
  readonly requestType?: ShortcutXLUsageLogRequestType;
66
68
  readonly fastModeModelIds?: readonly string[];
@@ -1,7 +1,7 @@
1
1
  /** Turn driver: wraps `agentLoop` with per-session config and reduces stream events into AgentState. */
2
2
  import type { EventStream } from '../ai/event-stream.js';
3
3
  import { type AssistantMessage, type Message, type ToolCall, type Transport } from '../ai/types.js';
4
- import type { ShortcutXLLlmOperation } from '../contracts/model-stream.js';
4
+ import type { ShortcutXLLlmOperation, ShortcutXLModelRequestCorrelation } from '../contracts/model-stream.js';
5
5
  import type { AgentMessageQueue } from './agent-message-queue.js';
6
6
  import { type AgentContext, type AgentLoopConfig, type AgentLoopEvent, type AgentMessage, type AgentState, type AgentUserMessageExecutionStart, type InFlightMeta, type InFlightOpKind, type StreamFn, type ToolExecutionContext } from './core-types.js';
7
7
  import { type HeartbeatClock } from './heartbeat.js';
@@ -31,6 +31,7 @@ export interface AgentEngineRunOptions {
31
31
  onEvent: (event: AgentLoopEvent) => void;
32
32
  }
33
33
  export interface AgentEngineStreamObservabilityContext {
34
+ requestCorrelation?: ShortcutXLModelRequestCorrelation;
34
35
  agentMode?: string;
35
36
  continuationRunId?: string;
36
37
  initialFileId?: string;
@@ -186,6 +186,9 @@ export class AgentEngine {
186
186
  };
187
187
  const config = {
188
188
  model,
189
+ requestCorrelation: this._streamObservabilityContext?.requestCorrelation
190
+ ? { ...this._streamObservabilityContext.requestCorrelation }
191
+ : undefined,
189
192
  maxTokens: model.maxTokens,
190
193
  reasoning,
191
194
  sessionId: this._sessionId,
@@ -555,6 +555,10 @@ function emitToolResultMessage(stream, result) {
555
555
  * the full response is available.
556
556
  */
557
557
  export async function streamAssistantResponse(context, config, signal, stream, streamFn, onToolCallReady) {
558
+ // Capture telemetry provenance before asynchronous transforms; never consult a later run.
559
+ const requestCorrelation = config.requestCorrelation
560
+ ? { ...config.requestCorrelation }
561
+ : undefined;
558
562
  // Apply context transform if configured (AgentMessage[] → AgentMessage[])
559
563
  let messages = context.messages;
560
564
  if (config.transformContext) {
@@ -571,11 +575,13 @@ export async function streamAssistantResponse(context, config, signal, stream, s
571
575
  throw new Error('AgentController runtime requires an explicit stream function.');
572
576
  }
573
577
  const resolvedApiKey = (config.getApiKey ? await config.getApiKey(config.model.provider) : undefined) || config.apiKey;
574
- const response = await streamFn(config.model, llmContext, {
578
+ const requestOptions = {
575
579
  ...config,
580
+ requestCorrelation,
576
581
  apiKey: resolvedApiKey,
577
582
  signal
578
- });
583
+ };
584
+ const response = await streamFn(config.model, llmContext, requestOptions);
579
585
  let partialMessage = null;
580
586
  let addedPartial = false;
581
587
  // microtask starvation that blocks TUI rendering on slower machines.
@@ -6,7 +6,7 @@ import type { EventStream } from '../ai/event-stream.js';
6
6
  import type { streamSimple } from '../ai/stream.js';
7
7
  import type { AssistantMessage, AssistantMessageEvent, ImageContent, Message, Model, SimpleStreamOptions, TextContent, Tool, ToolCall, ToolResultMessage, UserMessage } from '../ai/types.js';
8
8
  import type { RunFailureCause } from '../contracts/agent-failure.js';
9
- import type { ShortcutXLLlmOperation } from '../contracts/model-stream.js';
9
+ import type { ShortcutXLLlmOperation, ShortcutXLModelRequestCorrelation } from '../contracts/model-stream.js';
10
10
  import type { UserMessageUiMetadata } from '../contracts/user-message-ui-metadata.js';
11
11
  import type { RuntimeFilesystemApprovalGrant, RuntimePermissionPolicy } from './permission-policy.js';
12
12
  import type { SessionState } from './session-state.js';
@@ -100,6 +100,8 @@ export interface AgentLoopConfig extends SimpleStreamOptions {
100
100
  * Set from `AgentSubmitOptions.speed`.
101
101
  */
102
102
  speed?: 'fast' | 'standard' | 'ultrafast' | 'flex';
103
+ /** Immutable main execution provenance for content-free request correlation. */
104
+ requestCorrelation?: ShortcutXLModelRequestCorrelation;
103
105
  /** Host-generated user message identifier forwarded to ShortcutXL's LLM proxy. */
104
106
  userMessageId?: string;
105
107
  /** Stable billing-continuation scope shared by every model call in the active run. */
@@ -5,6 +5,7 @@
5
5
  * and after compaction the session is reloaded.
6
6
  */
7
7
  import type { AssistantMessage, Context, Model, SimpleStreamOptions, Usage } from '../../../ai/types.js';
8
+ import type { ShortcutXLModelRequestCorrelation } from '../../../contracts/model-stream.js';
8
9
  import type { AgentMessage, StreamFn } from '../../core-types.js';
9
10
  import type { SessionEntry } from '../../session-schema.js';
10
11
  import { type FileOperations } from './utils.js';
@@ -17,6 +18,7 @@ export type CompactionCompleteFn = (model: Model<any>, context: Context, options
17
18
  /** Run compaction summaries through a host's stream function, so they share its transport. */
18
19
  export declare function createStreamBackedCompactionComplete(streamFn: StreamFn): CompactionCompleteFn;
19
20
  export interface CompactionAttribution {
21
+ requestCorrelation?: ShortcutXLModelRequestCorrelation;
20
22
  agentMode?: string;
21
23
  continuationRunId?: string;
22
24
  userMessageId?: string;
@@ -48,6 +48,9 @@ async function completeCompactionSummary(model, promptText, apiKey, errorPrefix,
48
48
  apiKey,
49
49
  reasoning: 'low',
50
50
  llmOperation: 'compaction',
51
+ ...(attribution?.requestCorrelation
52
+ ? { requestCorrelation: { ...attribution.requestCorrelation } }
53
+ : {}),
51
54
  ...(attribution?.agentMode ? { agentMode: attribution.agentMode } : {}),
52
55
  ...(attribution?.continuationRunId
53
56
  ? { continuationRunId: attribution.continuationRunId }
@@ -4,9 +4,10 @@ import type { AgentModelRegistry } from '../../contracts/agent-model.js';
4
4
  import type { AgentSessionContext, AgentSessionEntry } from '../../contracts/agent-session-store.js';
5
5
  import type { HeartbeatWrapFn } from '../agent-engine.js';
6
6
  import type { AgentController, AgentMessage } from '../index.js';
7
- import type { CompactionResult } from './compaction/index.js';
7
+ import type { CompactionAttribution, CompactionResult } from './compaction/index.js';
8
8
  import { SessionCompaction, type CompactionActions } from './session-compaction.js';
9
9
  export interface TriggerCompactionOptions {
10
+ requestCorrelation?: CompactionAttribution['requestCorrelation'];
10
11
  /** Per-turn heartbeat wrap. Captured synchronously by the caller while the
11
12
  * agent loop is still active; outlives the loop's _activeWrap clear. */
12
13
  heartbeatWrap?: HeartbeatWrapFn;
@@ -115,8 +115,11 @@ export async function triggerCompactionIfNeeded(deps, msg, skipAbortedCheck, opt
115
115
  }, skipAbortedCheck);
116
116
  if (opts?.onlyAction !== undefined && decision.action !== opts.onlyAction)
117
117
  return;
118
- const attribution = opts?.agentMode || opts?.continuationRunId || opts?.userMessageId
118
+ const attribution = opts?.requestCorrelation || opts?.agentMode || opts?.continuationRunId || opts?.userMessageId
119
119
  ? {
120
+ ...(opts.requestCorrelation
121
+ ? { requestCorrelation: { ...opts.requestCorrelation } }
122
+ : {}),
120
123
  ...(opts.agentMode ? { agentMode: opts.agentMode } : {}),
121
124
  ...(opts.continuationRunId ? { continuationRunId: opts.continuationRunId } : {}),
122
125
  ...(opts.userMessageId ? { userMessageId: opts.userMessageId } : {})
@@ -54,8 +54,11 @@ export function createEmbeddedCompactionWiring(options) {
54
54
  const perTurnUserMessageId = options.getPerTurnOptions().userMessageId;
55
55
  const activeUserMessageId = getActiveUserMessageId(options.agent.state.messages);
56
56
  const userMessageId = perTurnUserMessageId ?? activeUserMessageId;
57
- const attribution = continuationRunId || userMessageId
57
+ const correlation = options.agent.streamObservabilityContext?.requestCorrelation;
58
+ const requestCorrelation = correlation ? { ...correlation } : undefined;
59
+ const attribution = requestCorrelation || continuationRunId || userMessageId
58
60
  ? {
61
+ ...(requestCorrelation ? { requestCorrelation } : {}),
59
62
  ...(continuationRunId ? { continuationRunId } : {}),
60
63
  ...(userMessageId ? { userMessageId } : {})
61
64
  }
@@ -324,13 +324,14 @@ function appendCancellationBoundaryIfNeeded(agent, funnel) {
324
324
  funnel.assertAlignedWith(agent.state.messages.length);
325
325
  }
326
326
  /** Assemble the per-turn stream-observability context (mode, file ids, interaction mode) for tracing. */
327
- function buildStreamObservabilityContext(submitOptions, agentMode) {
327
+ function buildStreamObservabilityContext(submitOptions, agentMode, originUserMessageId) {
328
328
  const fileAttachmentIds = submitOptions?.fileUploads
329
329
  ?.map((file) => file.fileId)
330
330
  .filter((fileId) => typeof fileId === 'string' && fileId.length > 0) ?? [];
331
331
  const interactionMode = submitOptions?.interactionMode;
332
332
  return {
333
333
  agentMode,
334
+ ...(originUserMessageId ? { requestCorrelation: { originUserMessageId } } : {}),
334
335
  ...(submitOptions?.continuationRunId
335
336
  ? { continuationRunId: submitOptions.continuationRunId }
336
337
  : {}),
@@ -1016,6 +1017,13 @@ export async function composeEmbeddedAgentRuntimeInternal(options) {
1016
1017
  });
1017
1018
  const beginGoalAwareRun = () => {
1018
1019
  const runId = runStatsAccumulator.beginRun();
1020
+ agentRuntime.streamObservabilityContext = {
1021
+ ...agentRuntime.streamObservabilityContext,
1022
+ requestCorrelation: {
1023
+ ...agentRuntime.streamObservabilityContext?.requestCorrelation,
1024
+ runtimeRunId: runId
1025
+ }
1026
+ };
1019
1027
  goalRuntime.beginRun({
1020
1028
  executable: isExecutableGoalMode(runtimeAgentMode),
1021
1029
  runId
@@ -1361,7 +1369,7 @@ export async function composeEmbeddedAgentRuntimeInternal(options) {
1361
1369
  const refreshContinuationContext = copyContinuationContext(refreshSubmitOptions, continuationContext.latestUserTaskForRefresh);
1362
1370
  activePerTurnOptions = refreshSubmitOptions;
1363
1371
  activeContinuationContext = refreshContinuationContext;
1364
- agentRuntime.streamObservabilityContext = buildStreamObservabilityContext(activePerTurnOptions, runtimeAgentMode);
1372
+ agentRuntime.streamObservabilityContext = buildStreamObservabilityContext(activePerTurnOptions, runtimeAgentMode, agentRuntime.streamObservabilityContext?.requestCorrelation?.originUserMessageId);
1365
1373
  agent.speed = selectedSpeed;
1366
1374
  beginGoalAwareRun();
1367
1375
  runStatsAccumulator.refreshRunConfig();
@@ -1411,7 +1419,7 @@ export async function composeEmbeddedAgentRuntimeInternal(options) {
1411
1419
  rangeRestrictions: submitOptions?.rangeRestrictions
1412
1420
  };
1413
1421
  activeContinuationContext = submitOptions ?? latestSubmitContinuationContext;
1414
- agentRuntime.streamObservabilityContext = buildStreamObservabilityContext(activePerTurnOptions, runtimeAgentMode);
1422
+ agentRuntime.streamObservabilityContext = buildStreamObservabilityContext(activePerTurnOptions, runtimeAgentMode, agentRuntime.streamObservabilityContext?.requestCorrelation?.originUserMessageId);
1415
1423
  agent.speed = selectedSpeed;
1416
1424
  beginGoalAwareRun();
1417
1425
  await compactionWiring.triggerIfNeeded();
@@ -2317,7 +2325,7 @@ export async function composeEmbeddedAgentRuntimeInternal(options) {
2317
2325
  const turnCleanup = registerTurnCleanup(agent, {}, baselineStreamObservabilityContext);
2318
2326
  let runStarted = false;
2319
2327
  try {
2320
- agentRuntime.streamObservabilityContext = buildStreamObservabilityContext(resolvedSubmitOptions, runtimeAgentMode);
2328
+ agentRuntime.streamObservabilityContext = buildStreamObservabilityContext(resolvedSubmitOptions, runtimeAgentMode, resolvedSubmitOptions.userMessageId);
2321
2329
  // Mint runId before compaction so the summarizer's usage rolls into
2322
2330
  // this run AND beats the heartbeat-emit-before-agent_start race.
2323
2331
  beginGoalAwareRun();
package/dist/main.d.ts CHANGED
@@ -10,6 +10,7 @@ import { SettingsManager } from './app/settings-manager.js';
10
10
  import { type StartupInfoEntry } from './app/startup-info.js';
11
11
  import { type Args } from './cli/args.js';
12
12
  import { type CreateLocalAgentSessionOptions } from './local/index.js';
13
+ import { type ShortcutDefaultModelPlan } from './shortcut-default-model.js';
13
14
  import { StartupProgress } from './startup/startup-progress.js';
14
15
  /** Resolve the per-run durable-memory policy without changing the persisted user setting. */
15
16
  export declare function shouldEnableDurableMemory(settingsEnabled: boolean, disableMemory: boolean | undefined): boolean;
@@ -30,6 +31,12 @@ export declare function isShortcutModelAllowedForUser(model: {
30
31
  readonly id?: string;
31
32
  } | undefined, userInfo: UserInfo | null): boolean;
32
33
  export declare function applyShortcutModelPolicyForUser(modelRegistry: ModelRegistry, userInfo: UserInfo | null): void;
34
+ /**
35
+ * The users/me facts that pick the plan default model (signed out: product default). A local
36
+ * gateway serves the full catalog whatever the plan, as the policy above allows, so its plan
37
+ * does not make the account Free.
38
+ */
39
+ export declare function shortcutDefaultModelPlanForUser(userInfo: UserInfo | null, viaGateway?: boolean): ShortcutDefaultModelPlan;
33
40
  /** Composition root: dispatch CLI args to print, RPC, or interactive entry points. */
34
41
  export declare function main(args: string[]): Promise<void>;
35
42
  //# sourceMappingURL=main.d.ts.map
package/dist/main.js CHANGED
@@ -32,6 +32,7 @@ import { resolveCliModel } from './app/providers/model-resolver.js';
32
32
  import { registerShortcutProvider } from './app/providers/register-shortcut-provider.js';
33
33
  import { DefaultResourceLoader } from './app/resources/resource-loader.js';
34
34
  import { syncInstalledShortcutXLPlugins } from './app/resources/shortcutxl-plugin-sync.js';
35
+ import { alignSessionModelWithAccount } from './app/session/account-default-model.js';
35
36
  import { listAllSessions, listSessions } from './app/session/session-catalog.js';
36
37
  import { openSessionManager } from './app/session/session-factory.js';
37
38
  import { SettingsManager } from './app/settings-manager.js';
@@ -76,9 +77,10 @@ import { SHORTCUT_API_HEADERS } from './endpoints.js';
76
77
  import { createLocalAgentSession } from './local/index.js';
77
78
  import { appendCliExecutionContract, handleConfigCommand, handleShortcutAuthCommand, handleXllStartupStatus, isTruthyEnvFlag, prepareInitialMessage, readPipedStdin, resolveApiKeyArgument, runInteractiveShell, validateAgentMode } from './main-helpers.js';
78
79
  import { getCurrentMode, MODE, setCurrentMode } from './mode-names.js';
79
- import { isShortcutFableModelId, isShortcutPaidPlanModel, isShortcutPickerGatedModel, setShortcutPaidPlanModelsAllowed, SHORTCUT_FREE_MODEL_ID, SHORTCUT_OPENAI_MODEL_ID, SHORTCUT_PROVIDER_ID } from './model-ids.js';
80
+ import { isShortcutFableModelId, isShortcutPaidPlanModel, isShortcutPickerGatedModel, setShortcutPaidPlanModelsAllowed, SHORTCUT_PROVIDER_ID } from './model-ids.js';
80
81
  import { parseShortcutRuntimeModelOverlay } from './runtime-model-overlay.js';
81
82
  import { attachSessionUpload, createInteractiveSessionClient, createNetworkPrompt, createPrintModeSessionClient, createSandboxBashTool, createShortySlashCommand, ensureWslSandbox, exportFromFile, getTraceHooks, hasCustomExtensionUI, initTheme, InteractiveMode, isDistroInstalled, isWsl2Available, KeybindingsManager, runPrintMode, SANDBOX_DISTRO, startDurableMemoryStatusUpdater, startLoopStatusPolling, stopThemeWatcher } from './shell/index.js';
83
+ import { setShortcutDefaultModelPlan } from './shortcut-default-model.js';
82
84
  import { closeMogCliSessions, initializeMogCli } from './startup/mog-cli.js';
83
85
  import { finalizeOutputWorkbook, OUTPUT_WORKBOOK_NOT_CREATED } from './startup/output-workbook.js';
84
86
  import { STARTUP_PHASE, StartupProgress } from './startup/startup-progress.js';
@@ -104,6 +106,7 @@ import { createSessionManager } from './startup/session-selection.js';
104
106
  import { applyRuntimeApiKeyAndRefreshShortcutLoggerContext, applyShortcutUserInfoToLogger, refreshShortcutUserInfoLoggerContext } from './startup/shortcut-user-logger-context.js';
105
107
  import { createSkillSyncRunner } from './startup/skill-sync-commands.js';
106
108
  import { runTerminalUpdateFlow } from './startup/terminal-update-flow.js';
109
+ import { reportPendingUpdateAttempts } from './startup/update-diagnostics.js';
107
110
  import { refreshUpdateStatus } from './startup/update-manager.js';
108
111
  import { runWindowsExcelRuntimeUpgradeIfNeeded } from './startup/windows-excel-runtime-upgrade.js';
109
112
  import { createWorkbookPrePromptContext } from './startup/workbook-pre-prompt.js';
@@ -189,6 +192,7 @@ export async function buildSessionOptions(parsed, sessionManager, modelRegistry,
189
192
  settingsManager: _settingsManager
190
193
  });
191
194
  options.model = initialModel.model;
195
+ options.modelExplicit = initialModel.modelExplicit;
192
196
  // API key from CLI - set in authStorage
193
197
  // (handled by caller before createLocalAgentSession)
194
198
  const buildRuntimeTools = async (warnings, trigger) => {
@@ -459,21 +463,19 @@ export function applyShortcutModelPolicyForUser(modelRegistry, userInfo) {
459
463
  (getLocalShortcutRouting()?.state.kind === 'gateway' ||
460
464
  isShortcutModelAllowedForUser(model, userInfo)));
461
465
  setShortcutPaidPlanModelsAllowed(() => getLocalShortcutRouting()?.state.kind === 'gateway' || userInfo?.plan !== null);
466
+ setShortcutDefaultModelPlan(() => shortcutDefaultModelPlanForUser(userInfo, getLocalShortcutRouting()?.state.kind === 'gateway'));
462
467
  }
463
- async function moveSessionOffPolicyDeniedModel(session, modelRegistry) {
464
- const currentModel = session?.model;
465
- if (!session || !currentModel)
466
- return;
467
- if (modelRegistry.find(currentModel.provider, currentModel.id))
468
- return;
469
- const availableModels = await modelRegistry.getAvailable();
470
- const fallback = availableModels.find((model) => model.provider === SHORTCUT_PROVIDER_ID && model.id === SHORTCUT_OPENAI_MODEL_ID) ??
471
- availableModels.find((model) => model.provider === SHORTCUT_PROVIDER_ID && model.id === SHORTCUT_FREE_MODEL_ID) ??
472
- availableModels.find((model) => model.provider === SHORTCUT_PROVIDER_ID) ??
473
- availableModels[0];
474
- if (fallback) {
475
- await session.setModel(fallback);
476
- }
468
+ /**
469
+ * The users/me facts that pick the plan default model (signed out: product default). A local
470
+ * gateway serves the full catalog whatever the plan, as the policy above allows, so its plan
471
+ * does not make the account Free.
472
+ */
473
+ export function shortcutDefaultModelPlanForUser(userInfo, viaGateway = false) {
474
+ return {
475
+ plan: viaGateway ? undefined : userInfo?.plan,
476
+ teamId: userInfo?.teamId,
477
+ defaultModelPickerEntryKey: userInfo?.defaultModelPickerEntryKey
478
+ };
477
479
  }
478
480
  async function refreshSessionForMogPolicyChange(session) {
479
481
  if (!session)
@@ -997,6 +999,8 @@ export async function main(args) {
997
999
  cli_mode: cliMode
998
1000
  }
999
1001
  });
1002
+ // Update attempts run before this logger exists; report their receipts on the next start.
1003
+ void reportPendingUpdateAttempts(clientLogger);
1000
1004
  clientLogger.info({
1001
1005
  module: 'session',
1002
1006
  event: 'session_started',
@@ -1157,7 +1161,7 @@ export async function main(args) {
1157
1161
  return;
1158
1162
  shortcutUserInfo = await refreshAccount();
1159
1163
  applyShortcutModelPolicyForUser(modelRegistry, shortcutUserInfo);
1160
- await moveSessionOffPolicyDeniedModel(session, modelRegistry);
1164
+ await alignSessionModelWithAccount(session, modelRegistry);
1161
1165
  },
1162
1166
  fetchSessionCreditUsageStatus: async () => {
1163
1167
  const credential = authStorage.get(SHORTCUT_PROVIDER_ID);
@@ -1346,7 +1350,7 @@ export async function main(args) {
1346
1350
  }
1347
1351
  });
1348
1352
  sessionRef?.setFastModeAllowed(isFastModeAllowedForUser(userInfo), getFastModeDisabledReasonForUser(userInfo) ?? 'team');
1349
- await moveSessionOffPolicyDeniedModel(sessionRef, modelRegistry);
1353
+ await alignSessionModelWithAccount(sessionRef, modelRegistry);
1350
1354
  setRemoteControlTeamAccount(Boolean(userInfo?.teamId));
1351
1355
  if (isMogRuntimeEnabledForUser(shortcutUserInfo) !== previousMogRuntimeEnabled) {
1352
1356
  await refreshSessionForMogPolicyChange(sessionRef);
@@ -20,6 +20,7 @@ import { getCurrentMode, isAgentMode, isSelectableAgentMode, onModeChange, SELEC
20
20
  import { exportSessionToHtml } from '../shell/export-html/index.js';
21
21
  import { openLocalFileLink } from '../shell/links/local-file-opener.js';
22
22
  import { readLocalFilePreview } from '../shell/links/local-file-preview.js';
23
+ import { resolveShortcutDefaultModel } from '../shortcut-default-model.js';
23
24
  import { RPC_PROTOCOL_VERSION } from './rpc-types.js';
24
25
  function errorMessage(error) {
25
26
  return error instanceof Error ? error.message : String(error);
@@ -374,7 +375,12 @@ export class RpcSessionHost {
374
375
  .getAvailable()
375
376
  .filter((model) => model.provider === providerId);
376
377
  if ((!this.session.model || this.session.model.provider !== providerId) && models[0]) {
377
- await this.session.setModel(models[0]);
378
+ // The runtime picks this model, not the user: the plan default (never catalog
379
+ // order, which put Claude Opus 5.5 first), and login may re-resolve it later.
380
+ const loginDefault = resolveShortcutDefaultModel(models) ??
381
+ filterInteractiveSelectableModels(models)[0] ??
382
+ models[0];
383
+ await this.session.setModel(loginDefault, { explicit: false });
378
384
  }
379
385
  this.publish({
380
386
  type: 'notification',
@@ -1,7 +1,9 @@
1
1
  /** Interactive-shell auth workflow for login, token storage, and session-bound auth checks. */
2
2
  import { getOAuthProviders } from '../../ai/utils/oauth/index.js';
3
3
  import { LoginRequiredError } from '../../app/agent-session.js';
4
+ import { filterInteractiveSelectableModels } from '../../app/session/session-models.js';
4
5
  import { getAuthPath } from '../../config.js';
6
+ import { resolveShortcutDefaultModel } from '../../shortcut-default-model.js';
5
7
  import { LoginDialogComponent } from '../components/selectors/login-dialog.js';
6
8
  import { OAuthSelectorComponent } from '../components/selectors/oauth-selector.js';
7
9
  export class AuthWorkflow {
@@ -168,7 +170,11 @@ export class AuthWorkflow {
168
170
  const currentModel = this.deps.session.model;
169
171
  if (!currentModel || currentModel.provider !== providerId) {
170
172
  try {
171
- await this.deps.session.setModel(providerModels[0]);
173
+ // The runtime picks this model, not the user, so login can re-resolve it.
174
+ const loginDefault = resolveShortcutDefaultModel(providerModels) ??
175
+ filterInteractiveSelectableModels(providerModels)[0] ??
176
+ providerModels[0];
177
+ await this.deps.session.setModel(loginDefault, { explicit: false });
172
178
  this.deps.onModelChanged();
173
179
  }
174
180
  catch {
@@ -0,0 +1,30 @@
1
+ /** The account facts users/me reports that decide the default model. */
2
+ export interface ShortcutDefaultModelPlan {
3
+ /** users/me `plan`: null means no paid plan (Free); undefined means unknown or signed out. */
4
+ readonly plan?: string | null;
5
+ readonly teamId?: string;
6
+ /** Org-admin-saved picker key from users/me (Team accounts only). */
7
+ readonly defaultModelPickerEntryKey?: string | null;
8
+ }
9
+ /** Model id behind a picker key, following a retired key to its successor; picker-listed only. */
10
+ export declare function shortcutPickerKeyModelId(key: string | null | undefined): string | undefined;
11
+ /**
12
+ * The product default picker entry's model (GPT-6 Astra today). If a catalog edit ever makes
13
+ * `defaults.pickerKey` a model the CLI picker does not list, fall back to the first listed
14
+ * picker entry rather than failing at import; a unit test pins the catalog value.
15
+ */
16
+ export declare const SHORTCUT_PRODUCT_DEFAULT_MODEL_ID: string;
17
+ /**
18
+ * Ordered default candidates for an account. The first one the account can run wins;
19
+ * every candidate is a picker-listed model.
20
+ */
21
+ export declare function getShortcutDefaultModelCandidates(plan?: ShortcutDefaultModelPlan): readonly string[];
22
+ /** Pick the plan default from the models the account can run now (Shortcut provider only). */
23
+ export declare function resolveShortcutDefaultModel<M extends {
24
+ provider: string;
25
+ id: string;
26
+ }>(availableModels: readonly M[], plan?: ShortcutDefaultModelPlan): M | undefined;
27
+ /** Hosts set this from users/me so startup and login defaults follow the account's plan. */
28
+ export declare function setShortcutDefaultModelPlan(plan: () => ShortcutDefaultModelPlan): void;
29
+ export declare function getShortcutDefaultModelPlan(): ShortcutDefaultModelPlan;
30
+ //# sourceMappingURL=shortcut-default-model.d.ts.map