@myagentroam/node 0.11.45 → 0.11.46

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 (36) hide show
  1. package/dist/connector.js +4 -4
  2. package/dist/database.js +7 -5
  3. package/dist/marctl-guidance.js +2 -2
  4. package/dist/migrations/v023.js +25 -0
  5. package/dist/runner/abstract-runner.js +1 -1
  6. package/dist/runner/claude/managed-run-controller.js +1 -1
  7. package/dist/runner/claude-code-runner.js +1 -1
  8. package/dist/runner/codex/managed-run-controller.js +1 -1
  9. package/dist/runner/codex-runner.js +10 -5
  10. package/dist/runner/mar-agent/agent-network-extension.js +40 -14
  11. package/dist/runner/mar-agent/conversation-normalizer.js +1 -1
  12. package/dist/runner/mar-agent/history-projection.js +1 -1
  13. package/dist/runner/mar-agent/hosted-conversation-runtime.js +1 -1
  14. package/dist/runner/mar-agent/managed-run-controller.js +2 -2
  15. package/dist/runner/mar-agent-runner.js +5 -5
  16. package/dist/runner/opencode/managed-run-controller.js +2 -2
  17. package/dist/runner/opencode-runner.js +5 -5
  18. package/dist/runner-profiles.js +1 -1
  19. package/dist/runtime-state.js +7 -7
  20. package/dist/service/agent-network-bindings.js +6 -6
  21. package/dist/service/agent-network-service.js +2 -1
  22. package/dist/service/continuous-run-coordinator.js +1 -1
  23. package/dist/service/external-session-resume-service.js +1 -1
  24. package/dist/service/goal-continuation-coordinator.js +3 -3
  25. package/dist/service/native-session-watch-service.js +6 -3
  26. package/dist/service/planned-restart-handoff.js +24 -5
  27. package/dist/service/restart-session-restorer.js +2 -2
  28. package/dist/service/session-recent-activity.js +1 -1
  29. package/dist/session/native-session-projection-service.js +8 -8
  30. package/dist/session/session-catalog-service.js +2 -2
  31. package/dist/session/session-command-service.js +1 -0
  32. package/dist/session/session-context-service.js +1 -1
  33. package/dist/session/session-lifecycle-service.js +12 -10
  34. package/dist/session/session-message-service.js +4 -3
  35. package/dist/workspace/workspace-session-service.js +1 -1
  36. package/package.json +5 -5
package/dist/connector.js CHANGED
@@ -443,7 +443,7 @@ export class NodeConnector {
443
443
  emitSession: (session) => this.emitSessionWorkbenchEvent({ session }),
444
444
  emitPage: (session, page) => this.emitWorkbenchEvent('conversation', {
445
445
  sessionId: session.id,
446
- snapshot: this.conversationSegmentService.projectInitial(session, page, 10)
446
+ snapshot: this.conversationSegmentService.projectInitial(session, page, 3)
447
447
  }),
448
448
  nodeId: () => this.config?.nodeId ?? 'runtime-node',
449
449
  nodeGeneration: () => this.nodeGeneration,
@@ -854,7 +854,7 @@ export class NodeConnector {
854
854
  prompt: item.content,
855
855
  modelText: prepared.input,
856
856
  clientMessageId: item.clientMessageId,
857
- model: prepared.model,
857
+ modelId: prepared.modelId,
858
858
  effort: prepared.effort,
859
859
  access: prepared.access,
860
860
  collaborationMode: prepared.collaborationMode,
@@ -968,7 +968,7 @@ export class NodeConnector {
968
968
  runner: session.runner,
969
969
  externalSessionId: nativeSessionId,
970
970
  workspaceId: session.workspaceId,
971
- ...(session.model === null ? {} : { model: session.model }),
971
+ ...(session.modelId === null ? {} : { modelId: session.modelId }),
972
972
  ...(session.effort === null ? {} : { effort: session.effort }),
973
973
  ...(session.access === null ? {} : { access: session.access }),
974
974
  ...(session.customTitle === null ? {} : { customTitle: session.customTitle }),
@@ -1508,7 +1508,7 @@ export class NodeConnector {
1508
1508
  workspaceId: task.workspaceId,
1509
1509
  ownerUserId: task.ownerUserId,
1510
1510
  runner: task.runner,
1511
- model: configuredSession.model
1511
+ modelId: configuredSession.modelId
1512
1512
  });
1513
1513
  return {
1514
1514
  secretEnvironment: context.secretEnvironment,
package/dist/database.js CHANGED
@@ -4,6 +4,7 @@ import { NODE_SCHEMA_MIGRATION_V19 } from './migrations/v019.js';
4
4
  import { NODE_SCHEMA_MIGRATION_V20 } from './migrations/v020.js';
5
5
  import { NODE_SCHEMA_MIGRATION_V21 } from './migrations/v021.js';
6
6
  import { NODE_SCHEMA_MIGRATION_V22 } from './migrations/v022.js';
7
+ import { NODE_SCHEMA_MIGRATION_V23 } from './migrations/v023.js';
7
8
  import { normalizeSessionPrompt } from './runner/personal-instructions.js';
8
9
  import { randomUUID } from 'node:crypto';
9
10
  import { closeSync, existsSync, mkdirSync, openSync, rmSync } from 'node:fs';
@@ -27,7 +28,8 @@ export const NODE_SCHEMA_MIGRATIONS = [
27
28
  NODE_SCHEMA_MIGRATION_V19,
28
29
  NODE_SCHEMA_MIGRATION_V20,
29
30
  NODE_SCHEMA_MIGRATION_V21,
30
- NODE_SCHEMA_MIGRATION_V22
31
+ NODE_SCHEMA_MIGRATION_V22,
32
+ NODE_SCHEMA_MIGRATION_V23
31
33
  ];
32
34
  const LEGACY_NODE_SCHEMA_V12 = NODE_SCHEMA_VERSION_V13 - 1;
33
35
  const OBSOLETE_NODE_SCHEMA_V16 = 16;
@@ -314,7 +316,7 @@ export class NodeDatabase {
314
316
  pinOrder = 0;
315
317
  else if (input.pinned === false)
316
318
  pinOrder = null;
317
- const model = input.model === undefined ? (current?.model ?? null) : input.model;
319
+ const model = input.modelId === undefined ? (current?.modelId ?? null) : input.modelId;
318
320
  const effort = input.effort === undefined ? (current?.effort ?? null) : input.effort;
319
321
  const access = input.access === undefined ? (current?.access ?? null) : input.access;
320
322
  const parentSessionId = current === undefined || input.initializeParent === true
@@ -326,7 +328,7 @@ export class NodeDatabase {
326
328
  sessionPrompt,
327
329
  runnerTitle,
328
330
  runnerTitlePending,
329
- model,
331
+ modelId: model,
330
332
  effort,
331
333
  access
332
334
  });
@@ -367,7 +369,7 @@ export class NodeDatabase {
367
369
  runnerTitlePending,
368
370
  pinnedAt: pinOrder === null ? null : 1,
369
371
  pinOrder,
370
- model,
372
+ modelId: model,
371
373
  effort,
372
374
  access
373
375
  };
@@ -666,7 +668,7 @@ function sessionMetadata(row) {
666
668
  sessionPrompt: value('sessionPrompt'),
667
669
  runnerTitle: value('runnerTitle'),
668
670
  runnerTitlePending: raw['runnerTitlePending'] === true,
669
- model: value('model'),
671
+ modelId: value('modelId'),
670
672
  effort: value('effort'),
671
673
  access: value('access')
672
674
  };
@@ -10,7 +10,7 @@ const marctlSafety = 'Acceptance is not completion. If a request result is unkno
10
10
  export const marctlManagementInstructions = [
11
11
  '## MAR Workspace management',
12
12
  'Use native agent_* tools for Agent discovery, creation, communication, observation, waiting and stopping. Do not switch to marctl when a native operation fails.',
13
- 'Use marctl agents config <id> --model <id> --effort <effort> to change future model settings; agents rename <id> --title <title> changes a title.',
13
+ 'Use marctl agents config <id> --model-id <id> --effort <effort> to change future model settings; agents rename <id> --title <title> changes a title.',
14
14
  'Use agents prompt get/set/clear for persistent instructions; set accepts -m or --message-file. Use agents schedule list/get/create/update for scheduled tasks; update requires --expected-revision.',
15
15
  'marctl nodes lists authorized Nodes; marctl ssh executes remote commands and marctl scp transfers files. Use marctl help agents, nodes, ssh or scp for options.',
16
16
  marctlSafety
@@ -19,7 +19,7 @@ export const marctlDescription = 'Use marctl agents to create, send to, wait for
19
19
  export const marctlInstructions = [
20
20
  '## MAR Workspace CLI',
21
21
  'Run marctl from the shell. Commands use the authenticated Node and Workspace; marctl help agents, nodes, ssh or scp shows options.',
22
- 'Use agents start --title <title> -m <text> [--runner <runner>] [--model <id>] [--effort <effort>] [--goal <objective>]. An Agent must supply a nonblank title; a manual terminal may omit it. The default Runner is the current Session Runner; a terminal without a current Session must specify --runner or an explicit --parent. Start returns without waiting.',
22
+ 'Use agents start --title <title> -m <text> [--runner <runner>] [--model-id <id>] [--effort <effort>] [--goal <objective>]. An Agent must supply a nonblank title; a manual terminal may omit it. The default Runner is the current Session Runner; a terminal without a current Session must specify --runner or an explicit --parent. Start returns without waiting.',
23
23
  'Use agents send <id> -m <text> [--steer|--queue], agents wait [<id>] [--wait-for message|completion] [--timeout <duration>], agents stop <id> --execution <execution-id>, agents list [--scope children|descendants|workspace|profile], and agents view <id> [--history]. Wait defaults to direct-child completion for 30 seconds; --timeout 0ms takes a snapshot. Stop clears the target Session Goal.',
24
24
  'Prefer agents send <id> --steer -m <text> for timely communication or course correction. Use --queue only for deliberately deferred input: it is consumed after current work and earlier queued inputs finish, and with an active Goal it is not consumed until that Goal exits. Acceptance is not a guarantee of immediate processing.',
25
25
  'Start and send accept repeated --image <local-path> for PNG, JPEG, GIF or WebP; limits are five images, 5 MiB each and 10 MiB total. Send can update a Goal alone with --goal <objective> or --clear-goal; omitted Goal leaves it unchanged. Decide a direct child approval with send <id> --approval <approval-id> --decision approve|reject.',
@@ -0,0 +1,25 @@
1
+ import { NODE_SCHEMA_MIGRATION_V22 } from './v022.js';
2
+ const defaultPaths = ['codex', 'claude-code', 'opencode', 'mar-agent'].map((runner) => `$.runnerDefaults."${runner}"`);
3
+ const rename = (column, path) => `json_remove(CASE WHEN json_type(${column}, '${path}.modelId') IS NULL
4
+ THEN json_set(${column}, '${path}.modelId', json_extract(${column}, '${path}.model'))
5
+ ELSE ${column} END, '${path}.model')`;
6
+ export const NODE_SCHEMA_MIGRATION_V23 = {
7
+ version: 23,
8
+ sql: [
9
+ `UPDATE agent_sessions SET metadata = ${rename('metadata', '$')}
10
+ WHERE json_type(metadata, '$.model') IS NOT NULL;`,
11
+ ...defaultPaths.map((path) => `UPDATE node_settings SET settings = ${rename('settings', path)}
12
+ WHERE json_type(settings, '${path}.model') IS NOT NULL;`)
13
+ ].join('\n'),
14
+ preflight: (database) => NODE_SCHEMA_MIGRATION_V22.assert(database),
15
+ assert(database) {
16
+ NODE_SCHEMA_MIGRATION_V22.assert(database);
17
+ if (database
18
+ .prepare("SELECT 1 FROM agent_sessions WHERE json_type(metadata, '$.model') IS NOT NULL")
19
+ .get() !== undefined ||
20
+ defaultPaths.some((path) => database
21
+ .prepare(`SELECT 1 FROM node_settings WHERE json_type(settings, '${path}.model') IS NOT NULL`)
22
+ .get() !== undefined))
23
+ throw new Error('NODE_DATABASE_SCHEMA_INVALID:modelId');
24
+ }
25
+ };
@@ -30,7 +30,7 @@ export class AbstractRunner {
30
30
  supportsConfiguration(configuration, workspace, environment) {
31
31
  void [workspace, environment];
32
32
  const profile = this.profileForValidation();
33
- const model = profile.models.find((candidate) => candidate.id === configuration.model);
33
+ const model = profile.models.find((candidate) => candidate.id === configuration.modelId);
34
34
  return (configuration.runner === this.name &&
35
35
  model !== undefined &&
36
36
  model.supportedEfforts.includes(configuration.effort) &&
@@ -62,7 +62,7 @@ export class ClaudeManagedRunController {
62
62
  input,
63
63
  cwd: session.cwd,
64
64
  externalSessionId: session.externalSessionId,
65
- model: session.model,
65
+ model: session.modelId,
66
66
  effort: session.effort,
67
67
  access: session.access,
68
68
  secretEnvironment
@@ -49,7 +49,7 @@ export class ClaudeCodeRunner extends AbstractRunner {
49
49
  defaultConfiguration() {
50
50
  return {
51
51
  runner: this.name,
52
- model: claudeDefaultModel(this.environment),
52
+ modelId: claudeDefaultModel(this.environment),
53
53
  effort: 'medium',
54
54
  access: 'default'
55
55
  };
@@ -400,7 +400,7 @@ export class CodexManagedRunController {
400
400
  usage = new CodexUsageNormalizer(runId, (value) => this.host.emit(runId, 'run.usage', {
401
401
  ...value,
402
402
  sessionId: this.runner.execution.runs.get(runId)?.threadId
403
- }), active ? (this.host.session(active.sessionId)?.model ?? undefined) : undefined);
403
+ }), active ? (this.host.session(active.sessionId)?.modelId ?? undefined) : undefined);
404
404
  this.usage.set(runId, usage);
405
405
  }
406
406
  return usage;
@@ -202,7 +202,7 @@ export class CodexRunner extends AbstractRunner {
202
202
  ? emptyCodexConfiguration()
203
203
  : {
204
204
  runner: this.name,
205
- model: selected.model,
205
+ modelId: selected.model,
206
206
  effort: selected.defaultEffort,
207
207
  access: 'on-request'
208
208
  }
@@ -361,7 +361,8 @@ export class CodexRunner extends AbstractRunner {
361
361
  return false;
362
362
  const profile = this.profileCacheForEnvironment(environment)?.profile;
363
363
  return (profile !== undefined &&
364
- profile.models.some((model) => model.id === configuration.model && model.supportedEfforts.includes(configuration.effort)) &&
364
+ profile.models.some((model) => model.id === configuration.modelId &&
365
+ model.supportedEfforts.includes(configuration.effort)) &&
365
366
  profile.accessOptions.some((option) => option.id === configuration.access));
366
367
  }
367
368
  profileCacheForEnvironment(_environment) {
@@ -695,7 +696,11 @@ export class CodexRunner extends AbstractRunner {
695
696
  const threadId = session.externalSessionId;
696
697
  if (threadId === null)
697
698
  throw new Error('SESSION_REWIND_UNAVAILABLE');
698
- const configuration = { model: session.model, effort: session.effort, access: session.access };
699
+ const configuration = {
700
+ model: session.modelId,
701
+ effort: session.effort,
702
+ access: session.access
703
+ };
699
704
  return this.withTemporaryClient(async (client) => {
700
705
  const result = boundary === null
701
706
  ? await client.startThread(session.cwd, configuration)
@@ -719,7 +724,7 @@ export class CodexRunner extends AbstractRunner {
719
724
  const threadId = session.externalSessionId;
720
725
  return this.withTemporaryClient(async (client) => {
721
726
  const resumed = await client.resumeThread(threadId, session.cwd, {
722
- model: session.model,
727
+ model: session.modelId,
723
728
  effort: session.effort,
724
729
  access: session.access
725
730
  });
@@ -753,7 +758,7 @@ export class CodexRunner extends AbstractRunner {
753
758
  }
754
759
  }
755
760
  function emptyCodexConfiguration() {
756
- return { runner: 'codex', model: '', effort: '', access: 'on-request' };
761
+ return { runner: 'codex', modelId: '', effort: '', access: 'on-request' };
757
762
  }
758
763
  function codexModelPage(value) {
759
764
  if (!isPlainRecord(value) || !Array.isArray(value.data))
@@ -1,6 +1,10 @@
1
1
  import { agentNetworkWaitModelOutput } from './agent-network-wait-output.js';
2
2
  import { agentOperationEvent } from './agent-operation-presentation.js';
3
3
  const text = { type: 'string', minLength: 1 };
4
+ const runner = {
5
+ ...text,
6
+ description: 'Runner type (for example mar-agent), not a model name; defaults to the current Runner.'
7
+ };
4
8
  const attachmentIds = {
5
9
  type: 'array',
6
10
  items: text,
@@ -42,8 +46,11 @@ const tools = [
42
46
  content: { type: 'string' },
43
47
  attachments,
44
48
  attachmentIds,
45
- runner: text,
46
- model: text,
49
+ runner,
50
+ modelId: {
51
+ ...text,
52
+ description: 'Model ID from the target Runner Profile models[].id, not a display name. For MAR Agent this is an internal configuration ID, not an upstream model ID; omit to use the Runner default.'
53
+ },
47
54
  effort: text,
48
55
  title: text,
49
56
  goal,
@@ -53,7 +60,7 @@ const tools = [
53
60
  },
54
61
  {
55
62
  action: 'send',
56
- description: 'Send input, decide an approval or update a Goal; goal:null leaves it unchanged, goal:{clear:true} clears it. Prefer REPLACE_CURRENT for timely communication or course correction. QUEUE is consumed only after current work and earlier queued inputs finish; an active Goal must exit first. Sending to a non-child does not subscribe to its updates.',
63
+ description: 'Send input, decide an approval or update a Goal; goal:null leaves it unchanged, goal:{clear:true} clears it. Messages default to REPLACE_CURRENT (steer); use QUEUE only when deferred delivery is explicitly requested. QUEUE is consumed only after current work and earlier queued inputs finish; an active Goal must exit first.',
57
64
  properties: {
58
65
  agentId: text,
59
66
  content: { type: 'string' },
@@ -66,14 +73,18 @@ const tools = [
66
73
  required: ['approvalId', 'decision'],
67
74
  properties: { approvalId: text, decision: { type: 'string', enum: ['approve', 'reject'] } }
68
75
  },
69
- deliveryIntent: { type: 'string', enum: ['SEND', 'QUEUE', 'REPLACE_CURRENT'] },
76
+ deliveryIntent: {
77
+ type: 'string',
78
+ enum: ['SEND', 'QUEUE', 'REPLACE_CURRENT'],
79
+ description: 'Message delivery defaults to REPLACE_CURRENT (steer); explicit SEND remains supported.'
80
+ },
70
81
  clientMessageId: text
71
82
  },
72
83
  required: ['agentId']
73
84
  },
74
85
  {
75
86
  action: 'wait',
76
- description: 'Wait for completion or messages; defaults to direct children, completion and 30 seconds. Use waitMs 0 for a snapshot. Non-child targets are observed only during this call.',
87
+ description: 'Wait for completion or messages; defaults to direct children, completion and 30 seconds. Use waitMs 0 for a snapshot.',
77
88
  properties: {
78
89
  agentId: text,
79
90
  agentIds: { type: 'array', items: text, minItems: 1, maxItems: 8 },
@@ -90,13 +101,18 @@ const tools = [
90
101
  },
91
102
  {
92
103
  action: 'list',
93
- description: 'List Sessions, descendants or a Runner profile.',
104
+ description: 'List direct children by default, discover Workspace Sessions, inspect descendants or query a Runner profile.',
94
105
  properties: {
95
106
  scope: { type: 'string', enum: ['workspace', 'children', 'descendants', 'profile'] },
96
- agentId: text,
97
- parent: text,
98
- runner: text,
99
- query: { type: 'string' },
107
+ agentId: {
108
+ ...text,
109
+ description: 'Root for children or descendants; defaults to the current Session.'
110
+ },
111
+ runner: {
112
+ ...runner,
113
+ description: 'Runner for profile scope; defaults to the current Runner, not a model name.'
114
+ },
115
+ query: { type: 'string', description: 'Search Sessions; not a model selector.' },
100
116
  limit: { type: 'integer', minimum: 1, maximum: 100 },
101
117
  cursor: text
102
118
  }
@@ -117,12 +133,14 @@ const delegationPrompt = [
117
133
  '# Delegation and coordination',
118
134
  'Delegate bounded, self-contained tasks when independent progress or context savings materially helps. Keep simple or tightly coupled work local.',
119
135
  'Give each child an objective, necessary context, expected output, and a read-only boundary or non-overlapping write scope.',
136
+ 'Follow up through the direct delegate by default; inspect deeper Sessions only when a concrete issue needs independent verification.',
120
137
  'While children run, continue meaningful non-overlapping work. Do not redo delegated work. Wait only when the next step needs the result.',
121
138
  'Verify consequential findings and changes. Join or stop task-required child work before final delivery, and retain responsibility for the final result.'
122
139
  ].join('\n\n');
123
140
  const collaborationPrompt = [
124
141
  '# Workspace collaboration',
125
- "Use native agent_* tools to collaborate within the Agent Network when another Session's context or ongoing work materially helps. Keep requests focused and avoid unnecessary cross-Session coordination. Peer Sessions retain their own work and lifecycle; do not treat them as task-owned children or stop them merely for task cleanup. Sending to a non-child does not subscribe to its updates; explicitly wait when you need its progress or completion."
142
+ "Use native agent_* tools to collaborate within the Agent Network when another Session's context or ongoing work materially helps. Keep requests focused and avoid unnecessary cross-Session coordination. Peer Sessions retain their own work and lifecycle; do not treat them as task-owned children or stop them merely for task cleanup. Explicitly wait when you need progress or completion.",
143
+ "Reuse known Session IDs rather than rediscovering them. Send updates for meaningful progress, blockers, or changes that affect the recipient's work; combine related updates and avoid acknowledgment-only exchanges."
126
144
  ].join('\n\n');
127
145
  const managementPrompt = [
128
146
  '# Workspace management',
@@ -168,7 +186,7 @@ export function agentNetworkExtension(invoke, currentExecution) {
168
186
  'title',
169
187
  'executionId',
170
188
  'runner',
171
- 'model',
189
+ 'modelId',
172
190
  'effort',
173
191
  'waitFor',
174
192
  'waitMs',
@@ -184,12 +202,20 @@ export function agentNetworkExtension(invoke, currentExecution) {
184
202
  const executionId = currentExecution();
185
203
  if (!executionId)
186
204
  throw new Error('AGENT_ACTOR_NOT_ACTIVE');
187
- const result = await invoke(executionId, tool.action, input, call);
205
+ const args = input;
206
+ const operationInput = tool.action === 'send' &&
207
+ args.deliveryIntent === undefined &&
208
+ ((typeof args.content === 'string' && !!args.content.trim()) ||
209
+ (Array.isArray(args.attachments) && args.attachments.length > 0) ||
210
+ (Array.isArray(args.attachmentIds) && args.attachmentIds.length > 0))
211
+ ? { ...args, deliveryIntent: 'REPLACE_CURRENT' }
212
+ : args;
213
+ const result = await invoke(executionId, tool.action, operationInput, call);
188
214
  const modelResult = tool.action === 'wait' ? agentNetworkWaitModelOutput(result) : result;
189
215
  return {
190
216
  data: result,
191
217
  content: JSON.stringify(modelResult),
192
- ...agentOperationEvent(tool.action, input, result)
218
+ ...agentOperationEvent(tool.action, operationInput, result)
193
219
  };
194
220
  }
195
221
  });
@@ -704,7 +704,7 @@ export class MarAgentConversationNormalizer {
704
704
  return [
705
705
  genericToolItem(state?.itemId ?? itemId('tool', event.callId), event.toolName, event.code === 'MAR_AGENT_CANCELLED' || event.code === 'MAR_AGENT_EXECUTION_CANCELLED'
706
706
  ? 'CANCELLED'
707
- : 'FAILED', state?.input, event.message ?? event.code, event.code, event.truncated === true, state?.presentationData, state?.subagentTitle, state?.subagentDetails, state?.sleepStartedAt, event.outputTruncation, state?.inputTruncated)
707
+ : 'FAILED', state?.input, event.message ?? event.code, event.code, event.truncated === true, event.data === undefined ? state?.presentationData : event.data, state?.subagentTitle, state?.subagentDetails, state?.sleepStartedAt, event.outputTruncation, state?.inputTruncated)
708
708
  ];
709
709
  }
710
710
  #codeModeToolResults(event) {
@@ -15,7 +15,7 @@ export function projectMarAgentHistory(session, entries, options = {}) {
15
15
  runId: null,
16
16
  userItemId: null,
17
17
  status: 'INTERRUPTED',
18
- model: session.model,
18
+ model: session.modelId,
19
19
  effort: session.effort,
20
20
  access: session.access,
21
21
  rewind: { before: previous?.id ?? null, after: turnId },
@@ -470,7 +470,7 @@ export class MarAgentHostedConversationRuntime {
470
470
  function historySession(input) {
471
471
  return {
472
472
  id: input.sessionId,
473
- model: input.modelId ?? null,
473
+ modelId: input.modelId ?? null,
474
474
  effort: null,
475
475
  access: 'full-access'
476
476
  };
@@ -118,7 +118,7 @@ export class MarAgentManagedRunController {
118
118
  const execution = context.executionContext;
119
119
  const resolved = resolveMarAgentExecutionInput(execution?.runner === 'mar-agent'
120
120
  ? execution.runnerInput
121
- : undefined, session.model ?? '');
121
+ : undefined, session.modelId ?? '');
122
122
  if (resolved === undefined)
123
123
  throw new Error('RUNNER_COMMAND_UNAVAILABLE');
124
124
  const runId = randomUUID();
@@ -409,7 +409,7 @@ export class MarAgentManagedRunController {
409
409
  if (input.mode !== 'compact')
410
410
  this.#steerTargets.set(input.runId, async (request) => {
411
411
  if (controller.signal.aborted ||
412
- (request.model && request.model !== input.model.id) ||
412
+ (request.modelId && request.modelId !== input.model.id) ||
413
413
  (request.effort &&
414
414
  request.effort !==
415
415
  (reasoningEffort ??
@@ -232,7 +232,7 @@ export class MarAgentRunner extends AbstractRunner {
232
232
  if (configuration.runner !== this.name)
233
233
  return false;
234
234
  const profile = this.profileForValidation();
235
- const model = profile.models.find((candidate) => candidate.id === configuration.model);
235
+ const model = profile.models.find((candidate) => candidate.id === configuration.modelId);
236
236
  return (model !== undefined &&
237
237
  (model.supportedEfforts.length === 0
238
238
  ? configuration.effort === model.defaultEffort
@@ -246,7 +246,7 @@ export class MarAgentRunner extends AbstractRunner {
246
246
  const model = profile.models[0];
247
247
  return {
248
248
  runner: 'mar-agent',
249
- model: model?.id ?? '',
249
+ modelId: model?.id ?? '',
250
250
  effort: model?.defaultEffort ?? '',
251
251
  access: 'auto'
252
252
  };
@@ -357,7 +357,7 @@ export class MarAgentRunner extends AbstractRunner {
357
357
  }),
358
358
  ...(executionConfiguration?.modelId === undefined
359
359
  ? {}
360
- : { model: executionConfiguration.modelId }),
360
+ : { modelId: executionConfiguration.modelId }),
361
361
  ...(executionConfiguration?.reasoningEffort === undefined
362
362
  ? {}
363
363
  : { effort: executionConfiguration.reasoningEffort })
@@ -577,9 +577,9 @@ export class MarAgentRunner extends AbstractRunner {
577
577
  return [resolve(session.cwd), ...(generatedImages === undefined ? [] : [generatedImages])];
578
578
  }
579
579
  async readContextUsage(session) {
580
- if (session.externalSessionId === null || session.model === null)
580
+ if (session.externalSessionId === null || session.modelId === null)
581
581
  return undefined;
582
- const model = this.#models.find((candidate) => candidate.id === session.model);
582
+ const model = this.#models.find((candidate) => candidate.id === session.modelId);
583
583
  if (model === undefined)
584
584
  return undefined;
585
585
  let catalog;
@@ -51,7 +51,7 @@ export class OpenCodeManagedRunController {
51
51
  }
52
52
  compact(session, environment, initiatedByUserId) {
53
53
  if (session.externalSessionId === null ||
54
- session.model === null ||
54
+ session.modelId === null ||
55
55
  session.nativeControl !== 'MAR_MANAGED' ||
56
56
  this.host.hasActiveSessionLease(session.id))
57
57
  throw new Error('RUNNER_COMMAND_UNAVAILABLE');
@@ -78,7 +78,7 @@ export class OpenCodeManagedRunController {
78
78
  });
79
79
  this.host.emit(runId, 'run.started', { command: 'compact' }, 'STARTING');
80
80
  this.host.emitItem(runId, openCodeCompactionItem('opencode-compact', runId, false));
81
- void this.runCompact(runId, session.model);
81
+ void this.runCompact(runId, session.modelId);
82
82
  return {
83
83
  command: 'compact',
84
84
  run: this.host.run(runId),
@@ -147,7 +147,7 @@ export class OpenCodeRunner extends AbstractRunner {
147
147
  if (configuration.runner !== this.name)
148
148
  return false;
149
149
  const models = this.cachedModels(environment);
150
- const model = models.find((candidate) => candidate.id === configuration.model);
150
+ const model = models.find((candidate) => candidate.id === configuration.modelId);
151
151
  return (model !== undefined &&
152
152
  (model.supportedEfforts.length === 0 ||
153
153
  model.supportedEfforts.includes(configuration.effort)) &&
@@ -157,7 +157,7 @@ export class OpenCodeRunner extends AbstractRunner {
157
157
  const model = this.cachedModels(environment)[0];
158
158
  return {
159
159
  runner: this.name,
160
- model: model?.id ?? '',
160
+ modelId: model?.id ?? '',
161
161
  effort: model?.supportedEfforts[0] ?? '',
162
162
  access: 'default'
163
163
  };
@@ -377,11 +377,11 @@ export class OpenCodeRunner extends AbstractRunner {
377
377
  return value.id;
378
378
  }
379
379
  async executeCompact(session, environment) {
380
- if (session.externalSessionId === null || session.model === null)
380
+ if (session.externalSessionId === null || session.modelId === null)
381
381
  throw new Error('RUNNER_COMMAND_UNAVAILABLE');
382
382
  const client = this.managedClient(environment);
383
383
  try {
384
- return await client.summarize(session.cwd, session.externalSessionId, session.model);
384
+ return await client.summarize(session.cwd, session.externalSessionId, session.modelId);
385
385
  }
386
386
  finally {
387
387
  this.releaseManagedClient(client);
@@ -402,7 +402,7 @@ export class OpenCodeRunner extends AbstractRunner {
402
402
  const tokens = object(latest?.tokens);
403
403
  const cache = object(tokens?.cache);
404
404
  const usedTokens = positiveNumber(tokens?.input) + positiveNumber(cache?.read);
405
- const modelId = provider !== undefined && model !== undefined ? `${provider}/${model}` : session.model;
405
+ const modelId = provider !== undefined && model !== undefined ? `${provider}/${model}` : session.modelId;
406
406
  if (modelId === null || usedTokens <= 0)
407
407
  return undefined;
408
408
  const maxTokens = Date.now() >= this.contextLimitsExpiresAt ? undefined : this.contextLimits.get(modelId);
@@ -239,7 +239,7 @@ export function supportsRunnerConfiguration(input) {
239
239
  const profile = input.runner === 'claude-code'
240
240
  ? claudeProfile(input.environment ?? process.env)
241
241
  : definitions[input.runner];
242
- const model = profile.models.find((candidate) => candidate.id === input.model);
242
+ const model = profile.models.find((candidate) => candidate.id === input.modelId);
243
243
  return (model !== undefined &&
244
244
  model.supportedEfforts.includes(input.effort) &&
245
245
  profile.accessOptions.some((candidate) => candidate.id === input.access));
@@ -103,7 +103,7 @@ export class NodeRuntimeState {
103
103
  nativeControl: input.externalSessionId === undefined ? 'MAR_MANAGED' : (input.nativeControl ?? 'EXTERNAL'),
104
104
  channelToken: null,
105
105
  cwd: input.cwd,
106
- model: input.model ?? null,
106
+ modelId: input.modelId ?? null,
107
107
  effort: input.effort ?? null,
108
108
  access: input.access ?? null,
109
109
  customTitle,
@@ -139,7 +139,7 @@ export class NodeRuntimeState {
139
139
  nativeControl: input.externalSessionId === undefined ? 'MAR_MANAGED' : 'EXTERNAL',
140
140
  channelToken: null,
141
141
  cwd: input.cwd,
142
- model: null,
142
+ modelId: null,
143
143
  effort: null,
144
144
  access: null,
145
145
  customTitle: null,
@@ -214,7 +214,7 @@ export class NodeRuntimeState {
214
214
  }
215
215
  updateAgentSessionConfiguration(input) {
216
216
  return this.replaceSession(input.sessionId, {
217
- ...(input.model === undefined ? {} : { model: input.model }),
217
+ ...(input.modelId === undefined ? {} : { modelId: input.modelId }),
218
218
  ...(input.effort === undefined ? {} : { effort: input.effort }),
219
219
  ...(input.access === undefined ? {} : { access: input.access })
220
220
  });
@@ -456,7 +456,7 @@ export class NodeRuntimeState {
456
456
  runId: run.id,
457
457
  userItemId: user.id,
458
458
  status: 'PREPARING',
459
- model: session.model,
459
+ model: session.modelId,
460
460
  effort: session.effort,
461
461
  access: session.access,
462
462
  items: [user],
@@ -1059,7 +1059,7 @@ export class NodeRuntimeState {
1059
1059
  runId: null,
1060
1060
  userItemId: null,
1061
1061
  status: input.failed ? 'FAILED' : 'SUCCEEDED',
1062
- model: session.model,
1062
+ model: session.modelId,
1063
1063
  effort: session.effort,
1064
1064
  access: session.access,
1065
1065
  items: [],
@@ -1374,7 +1374,7 @@ export class NodeRuntimeState {
1374
1374
  runId: run.id,
1375
1375
  userItemId: null,
1376
1376
  status: terminal(run.status) ? run.status : 'RUNNING',
1377
- model: session.model,
1377
+ model: session.modelId,
1378
1378
  effort: session.effort,
1379
1379
  access: session.access,
1380
1380
  items: [],
@@ -1416,7 +1416,7 @@ export class NodeRuntimeState {
1416
1416
  runId: run.id,
1417
1417
  userItemId: userId,
1418
1418
  status: 'PREPARING',
1419
- model: session.model,
1419
+ model: session.modelId,
1420
1420
  effort: session.effort,
1421
1421
  access: session.access,
1422
1422
  items: [user],
@@ -167,9 +167,9 @@ export function createNodeAgentNetwork(options) {
167
167
  const workspace = workspaceId === undefined ? undefined : database?.getWorkspace(workspaceId);
168
168
  if (!runnerAdapter || !database || !workspace)
169
169
  throw new Error('SESSION_INVALID');
170
- const model = input.model ??
171
- database.getRunnerDefaults().find((entry) => entry.runner === runner)?.model ??
172
- runnerAdapter.defaultConfiguration(workspace, actor.secretEnvironment ?? {}, runnerAdapter.prepareExecutionContext(actor.runnerConfiguration ?? {})).model;
170
+ const model = input.modelId ??
171
+ database.getRunnerDefaults().find((entry) => entry.runner === runner)?.modelId ??
172
+ runnerAdapter.defaultConfiguration(workspace, actor.secretEnvironment ?? {}, runnerAdapter.prepareExecutionContext(actor.runnerConfiguration ?? {})).modelId;
173
173
  const request = {
174
174
  ...executionInput(actor, runner, model, parent !== undefined),
175
175
  workspaceId: workspace.id,
@@ -182,7 +182,7 @@ export function createNodeAgentNetwork(options) {
182
182
  __suppressNotifications: true,
183
183
  ...(!terminal && parent ? { __marctlOrigin: origin(parent) } : {}),
184
184
  ...(input.title === undefined ? {} : { title: input.title }),
185
- ...(model ? { model } : {}),
185
+ ...(model ? { modelId: model } : {}),
186
186
  ...(input.effort === undefined ? {} : { effort: input.effort }),
187
187
  ...(input.attachments === undefined ? {} : { attachments: input.attachments }),
188
188
  ...(input.attachmentIds === undefined ? {} : { attachmentIds: input.attachmentIds })
@@ -229,7 +229,7 @@ export function createNodeAgentNetwork(options) {
229
229
  const current = await options.resume.resolveForMessage(session.id);
230
230
  const executionContext = options.runners
231
231
  .require(current.runner)
232
- .prepareExecutionContext(executionInput(actor, current.runner, current.model, current.parentSessionId != null));
232
+ .prepareExecutionContext(executionInput(actor, current.runner, current.modelId, current.parentSessionId != null));
233
233
  signal?.throwIfAborted();
234
234
  const result = await options.withGrant(actor, () => options.coordinator.start({
235
235
  session: current,
@@ -262,7 +262,7 @@ export function createNodeAgentNetwork(options) {
262
262
  const parent = terminalCalls.has(actor) ? undefined : await actorSession(actor);
263
263
  signal?.throwIfAborted();
264
264
  const result = await options.withGrant(actor, () => options.messages.operations()['session.message']({
265
- ...executionInput(actor, session.runner, session.model, session.parentSessionId != null),
265
+ ...executionInput(actor, session.runner, session.modelId, session.parentSessionId != null),
266
266
  ...input,
267
267
  ...(signal ? { [SESSION_ADMISSION_SIGNAL]: signal } : {}),
268
268
  sessionId: session.id,
@@ -27,7 +27,8 @@ export class AgentNetworkService {
27
27
  input.attachmentIds.some((id) => typeof id !== 'string' || !id))) ||
28
28
  (!!input.attachments?.length && !!input.attachmentIds?.length) ||
29
29
  !validOptionalString(input.runner) ||
30
- !validOptionalString(input.model) ||
30
+ 'model' in input ||
31
+ !validOptionalString(input.modelId) ||
31
32
  !validOptionalString(input.effort) ||
32
33
  !validOptionalString(input.title) ||
33
34
  !validOptionalString(input.parentSessionId) ||
@@ -717,7 +717,7 @@ export class ContinuousRunCoordinator {
717
717
  ...(pending.externalSessionId === null
718
718
  ? {}
719
719
  : { externalSessionId: pending.externalSessionId }),
720
- ...(pending.model === null ? {} : { model: pending.model }),
720
+ ...(pending.modelId === null ? {} : { model: pending.modelId }),
721
721
  ...(pending.effort === null ? {} : { effort: pending.effort }),
722
722
  ...(pending.access === null ? {} : { access: pending.access }),
723
723
  collaborationMode: pending.collaborationMode,
@@ -106,7 +106,7 @@ export class ExternalSessionResumeService {
106
106
  cwd,
107
107
  createdAt: session.createdAt,
108
108
  lastActivityAt: session.lastActivityAt,
109
- model: configuration.model,
109
+ modelId: configuration.modelId,
110
110
  effort: configuration.effort,
111
111
  access: configuration.access,
112
112
  ...(session.runnerTitle === null ? {} : { title: session.runnerTitle }),
@@ -184,7 +184,7 @@ export class GoalContinuationCoordinator {
184
184
  this.options
185
185
  .runners()
186
186
  .require(session.runner)
187
- .assertChildModel(session.model, executionContext);
187
+ .assertChildModel(session.modelId, executionContext);
188
188
  this.invalidateGoalContinuation(session.id);
189
189
  const prepared = this.prepareGoalSession(session);
190
190
  const goalSession = prepared.session;
@@ -558,13 +558,13 @@ export class GoalContinuationCoordinator {
558
558
  : session;
559
559
  if (changed &&
560
560
  prepared.externalSessionId !== null &&
561
- prepared.model !== null &&
561
+ prepared.modelId !== null &&
562
562
  prepared.effort !== null)
563
563
  this.options.database()?.saveNativeSessionMetadata({
564
564
  runner: prepared.runner,
565
565
  externalSessionId: prepared.externalSessionId,
566
566
  workspaceId: prepared.workspaceId,
567
- model: prepared.model,
567
+ modelId: prepared.modelId,
568
568
  effort: prepared.effort,
569
569
  access
570
570
  });
@@ -5,6 +5,7 @@ import { SessionWatchSlotCoordinator } from './session-watch-slot-coordinator.js
5
5
  const INTERVAL_MS = 2_000;
6
6
  const TTL_MS = 45_000;
7
7
  const MAX_FAILURES = 3;
8
+ const INITIAL_SEGMENT_LIMIT = 3;
8
9
  export class NativeSessionWatchService {
9
10
  options;
10
11
  watches = new Map();
@@ -323,7 +324,9 @@ export class NativeSessionWatchService {
323
324
  return;
324
325
  stage = 'project_snapshot';
325
326
  const page = update.page;
326
- const payload = lease.unit === 'SEGMENT' ? projectInitialPage(session, page, 'SEGMENT', 10) : page;
327
+ const payload = lease.unit === 'SEGMENT'
328
+ ? projectInitialPage(session, page, 'SEGMENT', INITIAL_SEGMENT_LIMIT)
329
+ : page;
327
330
  this.emitSnapshot(lease, session, payload);
328
331
  this.releaseSnapshotPendingEvents(lease, page);
329
332
  const hydration = watch.observation.hydrate();
@@ -405,7 +408,7 @@ export class NativeSessionWatchService {
405
408
  if (!lease.ready)
406
409
  continue;
407
410
  const payload = lease.unit === 'SEGMENT'
408
- ? this.options.projectInitialPage?.(session, page, 'SEGMENT', 10)
411
+ ? this.options.projectInitialPage?.(session, page, 'SEGMENT', INITIAL_SEGMENT_LIMIT)
409
412
  : page;
410
413
  if (payload === undefined)
411
414
  continue;
@@ -431,7 +434,7 @@ export class NativeSessionWatchService {
431
434
  if (!lease.snapshotSent)
432
435
  continue;
433
436
  const payload = lease.unit === 'SEGMENT'
434
- ? this.options.projectInitialPage?.(session, page, 'SEGMENT', 10)
437
+ ? this.options.projectInitialPage?.(session, page, 'SEGMENT', INITIAL_SEGMENT_LIMIT)
435
438
  : page;
436
439
  if (payload === undefined)
437
440
  continue;
@@ -51,7 +51,7 @@ const sessionSchema = z
51
51
  id: identifier,
52
52
  workspaceId: identifier,
53
53
  cwd: z.string().min(1),
54
- model: z.string().nullable(),
54
+ modelId: z.string().nullable(),
55
55
  effort: z.string().nullable(),
56
56
  access: z.string().nullable()
57
57
  })
@@ -68,7 +68,7 @@ const sessionSchema = z
68
68
  .strict();
69
69
  export const plannedRestartSchema = z
70
70
  .object({
71
- schemaVersion: z.literal(1),
71
+ schemaVersion: z.literal(2),
72
72
  id: identifier,
73
73
  nodeId: identifier,
74
74
  serverUrl: z.string().min(1),
@@ -76,6 +76,14 @@ export const plannedRestartSchema = z
76
76
  sessions: z.array(sessionSchema)
77
77
  })
78
78
  .strict();
79
+ const legacyPlannedRestartSchema = plannedRestartSchema.extend({
80
+ schemaVersion: z.literal(1),
81
+ sessions: z.array(sessionSchema.extend({
82
+ session: sessionSchema.shape.session.omit({ modelId: true }).extend({
83
+ model: z.string().nullable()
84
+ })
85
+ }))
86
+ });
79
87
  export const RESTART_NOTICE = 'The previous execution was interrupted by a system restart. Check the session history and current workspace, then continue unfinished work. Verify completed or uncertain actions before proceeding; do not blindly repeat actions with external side effects.';
80
88
  /** A one-shot orderly-shutdown handoff, not a crash-recovery journal. */
81
89
  export class PlannedRestartHandoff {
@@ -129,7 +137,18 @@ export class PlannedRestartHandoff {
129
137
  return;
130
138
  }
131
139
  try {
132
- const value = plannedRestartSchema.parse(JSON.parse(text));
140
+ const saved = JSON.parse(text);
141
+ const legacy = legacyPlannedRestartSchema.safeParse(saved);
142
+ const value = plannedRestartSchema.parse(legacy.success
143
+ ? {
144
+ ...legacy.data,
145
+ schemaVersion: 2,
146
+ sessions: legacy.data.sessions.map((entry) => {
147
+ const { model, ...session } = entry.session;
148
+ return { ...entry, session: { ...session, modelId: model } };
149
+ })
150
+ }
151
+ : saved);
133
152
  const identity = this.options.identity();
134
153
  if (value.nodeId !== identity.nodeId || value.serverUrl !== identity.serverUrl)
135
154
  throw new Error('RESTART_HANDOFF_IDENTITY_MISMATCH');
@@ -190,7 +209,7 @@ export class PlannedRestartHandoff {
190
209
  id: session.externalSessionId,
191
210
  workspaceId: session.workspaceId,
192
211
  cwd: session.cwd,
193
- model: session.model,
212
+ modelId: session.modelId,
194
213
  effort: session.effort,
195
214
  access: session.access
196
215
  },
@@ -207,7 +226,7 @@ export class PlannedRestartHandoff {
207
226
  });
208
227
  }
209
228
  return plannedRestartSchema.parse({
210
- schemaVersion: 1,
229
+ schemaVersion: 2,
211
230
  id: randomUUID(),
212
231
  ...this.options.identity(),
213
232
  createdAt: Date.now(),
@@ -18,7 +18,7 @@ export class RestartSessionRestorer {
18
18
  workspaceId: entry.session.workspaceId,
19
19
  ownerUserId,
20
20
  runner: 'mar-agent',
21
- model: entry.session.model
21
+ modelId: entry.session.modelId
22
22
  }));
23
23
  return contexts.get(ownerUserId);
24
24
  };
@@ -35,7 +35,7 @@ export class RestartSessionRestorer {
35
35
  throw new Error('RESTART_SESSION_MISMATCH');
36
36
  session = this.options.runtime.updateAgentSessionConfiguration({
37
37
  sessionId: session.id,
38
- model: entry.session.model ?? session.model,
38
+ modelId: entry.session.modelId ?? session.modelId,
39
39
  effort: entry.session.effort ?? session.effort,
40
40
  access: entry.session.access ?? session.access
41
41
  });
@@ -127,7 +127,7 @@ export function marctlSessionDetail(runtime, session, queuedCount) {
127
127
  ...identity(session),
128
128
  status,
129
129
  runId,
130
- model: session.model,
130
+ modelId: session.modelId,
131
131
  effort: session.effort,
132
132
  queuedCount,
133
133
  latestMessage,
@@ -89,7 +89,7 @@ export class NativeSessionProjectionService {
89
89
  nativeControl: 'EXTERNAL',
90
90
  channelToken: null,
91
91
  cwd: workspace.path,
92
- model: record.metadata.model,
92
+ modelId: record.metadata.modelId,
93
93
  effort: record.metadata.effort,
94
94
  access: record.metadata.access,
95
95
  customTitle: record.metadata.customTitle,
@@ -123,7 +123,7 @@ export class NativeSessionProjectionService {
123
123
  cwd: direct.cwd,
124
124
  createdAt: direct.createdAt,
125
125
  lastActivityAt: direct.lastActivityAt,
126
- ...(direct.model === null ? {} : { model: direct.model }),
126
+ ...(direct.modelId === null ? {} : { modelId: direct.modelId }),
127
127
  ...(direct.effort === null ? {} : { effort: direct.effort }),
128
128
  ...(direct.access === null ? {} : { access: direct.access }),
129
129
  ...(direct.runnerTitle === null ? {} : { title: direct.runnerTitle }),
@@ -142,9 +142,9 @@ export class NativeSessionProjectionService {
142
142
  const fallback = saved ?? runner.defaultConfiguration(workspace);
143
143
  const configuration = {
144
144
  runner: session.runner,
145
- model: typeof session.model === 'string' && session.model.trim().length > 0
146
- ? session.model
147
- : fallback.model,
145
+ modelId: typeof session.modelId === 'string' && session.modelId.trim().length > 0
146
+ ? session.modelId
147
+ : fallback.modelId,
148
148
  effort: typeof session.effort === 'string' ? session.effort : fallback.effort,
149
149
  access: runner.restoreStoredAccess(session.access, fallback.access)
150
150
  };
@@ -152,13 +152,13 @@ export class NativeSessionProjectionService {
152
152
  }
153
153
  ensureResumedConfiguration(session) {
154
154
  const configuration = this.resumedConfiguration(session);
155
- if (session.model === configuration.model &&
155
+ if (session.modelId === configuration.modelId &&
156
156
  session.effort === configuration.effort &&
157
157
  session.access === configuration.access)
158
158
  return session;
159
159
  return this.options.runtime.updateAgentSessionConfiguration({
160
160
  sessionId: session.id,
161
- model: configuration.model,
161
+ modelId: configuration.modelId,
162
162
  effort: configuration.effort,
163
163
  access: configuration.access
164
164
  });
@@ -214,7 +214,7 @@ export class NativeSessionProjectionService {
214
214
  nativeControl: 'EXTERNAL',
215
215
  channelToken: null,
216
216
  cwd: input.cwd,
217
- model: metadata?.model ?? null,
217
+ modelId: metadata?.modelId ?? null,
218
218
  effort: metadata?.effort ?? null,
219
219
  access: metadata?.access ?? null,
220
220
  customTitle: metadata?.customTitle ?? null,
@@ -187,7 +187,7 @@ export class SessionCatalogService {
187
187
  nativeControl: 'EXTERNAL',
188
188
  channelToken: null,
189
189
  cwd: workspace.path,
190
- model: persisted.metadata.model,
190
+ modelId: persisted.metadata.modelId,
191
191
  effort: persisted.metadata.effort,
192
192
  access: persisted.metadata.access,
193
193
  customTitle: persisted.metadata.customTitle,
@@ -283,7 +283,7 @@ export class SessionCatalogService {
283
283
  cwd,
284
284
  nativeControl: 'MAR_MANAGED',
285
285
  ...(nativeId === null ? {} : { externalSessionId: nativeId }),
286
- ...(source.model === null ? {} : { model: source.model }),
286
+ ...(source.modelId === null ? {} : { modelId: source.modelId }),
287
287
  ...(source.effort === null ? {} : { effort: source.effort }),
288
288
  ...(source.access === null ? {} : { access: source.access }),
289
289
  title
@@ -31,6 +31,7 @@ export class SessionCommandService {
31
31
  }
32
32
  async sessionCommand(input) {
33
33
  if (typeof input.sessionId !== 'string' ||
34
+ Object.hasOwn(input, 'model') ||
34
35
  typeof input.commandId !== 'string' ||
35
36
  input.commandId.length === 0 ||
36
37
  typeof input.input !== 'string' ||
@@ -20,7 +20,7 @@ export class SessionContextService {
20
20
  return { context: null, pending: false };
21
21
  }
22
22
  const context = {
23
- model: usage.model ?? session.model,
23
+ model: usage.model ?? session.modelId,
24
24
  usedTokens: usage.usedTokens,
25
25
  maxTokens: usage.maxTokens,
26
26
  percentage: Math.round((usage.usedTokens / usage.maxTokens) * 1000) / 10
@@ -120,7 +120,8 @@ export class SessionLifecycleService {
120
120
  (input.externalSessionId !== undefined && typeof input.externalSessionId !== 'string') ||
121
121
  !validSessionTitle(input.title) ||
122
122
  !validSessionTitle(input.customTitle) ||
123
- !validSessionOption(input.model) ||
123
+ Object.hasOwn(input, 'model') ||
124
+ !validSessionOption(input.modelId) ||
124
125
  !validSessionEffort(input.effort) ||
125
126
  !validSessionOption(input.access))
126
127
  throw new Error('SESSION_INVALID');
@@ -172,7 +173,7 @@ export class SessionLifecycleService {
172
173
  const fallback = runner.defaultConfiguration(workspace, environment, executionContext);
173
174
  const configuration = {
174
175
  runner: runner.name,
175
- model: typeof input.model === 'string' ? input.model : (saved?.model ?? fallback.model),
176
+ modelId: typeof input.modelId === 'string' ? input.modelId : (saved?.modelId ?? fallback.modelId),
176
177
  effort: typeof input.effort === 'string' ? input.effort : (saved?.effort ?? fallback.effort),
177
178
  access: typeof input.access === 'string'
178
179
  ? input.access
@@ -183,7 +184,7 @@ export class SessionLifecycleService {
183
184
  if (runner.name === 'mar-agent' && !['auto', 'full-access'].includes(configuration.access))
184
185
  throw new Error('SESSION_INVALID');
185
186
  if (parentSessionId !== undefined)
186
- runner.assertChildModel(configuration.model, executionContext);
187
+ runner.assertChildModel(configuration.modelId, executionContext);
187
188
  const session = this.options.runtime.createAgentSession({
188
189
  ...(parentSessionId === undefined ? {} : { parentSessionId }),
189
190
  workspaceId: input.workspaceId,
@@ -192,7 +193,7 @@ export class SessionLifecycleService {
192
193
  ...(typeof input.externalSessionId === 'string'
193
194
  ? { externalSessionId: input.externalSessionId }
194
195
  : {}),
195
- model: configuration.model,
196
+ modelId: configuration.modelId,
196
197
  effort: configuration.effort,
197
198
  access: configuration.access,
198
199
  ...(typeof input.title === 'string' ? { title: input.title } : {}),
@@ -208,8 +209,9 @@ export class SessionLifecycleService {
208
209
  }
209
210
  async updateConfiguration(input) {
210
211
  if (typeof input.sessionId !== 'string' ||
211
- (input.model === undefined && input.effort === undefined && input.access === undefined) ||
212
- !validSessionOption(input.model) ||
212
+ (input.modelId === undefined && input.effort === undefined && input.access === undefined) ||
213
+ Object.hasOwn(input, 'model') ||
214
+ !validSessionOption(input.modelId) ||
213
215
  !validSessionEffort(input.effort) ||
214
216
  !validSessionOption(input.access))
215
217
  throw new Error('SESSION_INVALID');
@@ -222,13 +224,13 @@ export class SessionLifecycleService {
222
224
  input.access !== undefined &&
223
225
  !['auto', 'full-access'].includes(input.access))
224
226
  throw new Error('SESSION_INVALID');
225
- if (current.parentSessionId != null && typeof input.model === 'string') {
227
+ if (current.parentSessionId != null && typeof input.modelId === 'string') {
226
228
  const runner = this.options.runners.require(current.runner);
227
- runner.assertChildModel(input.model, runner.prepareExecutionContext(input));
229
+ runner.assertChildModel(input.modelId, runner.prepareExecutionContext(input));
228
230
  }
229
231
  const session = this.options.runtime.updateAgentSessionConfiguration({
230
232
  sessionId: current.id,
231
- ...(typeof input.model === 'string' ? { model: input.model } : {}),
233
+ ...(typeof input.modelId === 'string' ? { modelId: input.modelId } : {}),
232
234
  ...(typeof input.effort === 'string' ? { effort: input.effort } : {}),
233
235
  ...(typeof input.access === 'string' ? { access: input.access } : {})
234
236
  });
@@ -237,7 +239,7 @@ export class SessionLifecycleService {
237
239
  runner: session.runner,
238
240
  externalSessionId: session.externalSessionId,
239
241
  workspaceId: session.workspaceId,
240
- model: session.model,
242
+ modelId: session.modelId,
241
243
  effort: session.effort,
242
244
  access: session.access
243
245
  });
@@ -91,7 +91,8 @@ export class SessionMessageService {
91
91
  }
92
92
  async prepareMessage(input) {
93
93
  checkSessionAdmission(input);
94
- if (typeof input.sessionId !== 'string' ||
94
+ if (Object.hasOwn(input, 'model') ||
95
+ typeof input.sessionId !== 'string' ||
95
96
  typeof input.clientMessageId !== 'string' ||
96
97
  input.clientMessageId.length === 0 ||
97
98
  typeof input.content !== 'string' ||
@@ -148,7 +149,7 @@ export class SessionMessageService {
148
149
  if (session.parentSessionId != null)
149
150
  this.options.runners
150
151
  .require(session.runner)
151
- .assertChildModel(session.model, executionContext);
152
+ .assertChildModel(session.modelId, executionContext);
152
153
  const sourceContent = withSessionMessageOrigin(content, input.__marctlOrigin, session.parentSessionId);
153
154
  const goalMessage = internalGoal || internalSchedule || internalRestore
154
155
  ? undefined
@@ -296,7 +297,7 @@ export class SessionMessageService {
296
297
  cwd,
297
298
  workspacePath,
298
299
  externalSessionId: currentSession.externalSessionId,
299
- model: currentSession.model,
300
+ modelId: currentSession.modelId,
300
301
  effort: currentSession.effort,
301
302
  access: currentSession.access,
302
303
  collaborationMode: planActive ? 'plan' : 'default',
@@ -209,7 +209,7 @@ function sessionSignature(sessions) {
209
209
  session.runner,
210
210
  session.externalSessionId,
211
211
  session.nativeControl,
212
- session.model,
212
+ session.modelId,
213
213
  session.effort,
214
214
  session.access,
215
215
  session.runnerTitle,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@myagentroam/node",
3
- "version": "0.11.45",
3
+ "version": "0.11.46",
4
4
  "description": "MyAgentRoam Node runtime CLI.",
5
5
  "license": "Apache-2.0 AND GPL-2.0-or-later",
6
6
  "type": "module",
@@ -38,10 +38,10 @@
38
38
  "sharp": "0.35.5",
39
39
  "ws": "^8.21.3",
40
40
  "zod": "4.4.3",
41
- "@myagentroam/agent": "0.11.45",
42
- "@myagentroam/code-navigation": "0.11.1",
43
- "@myagentroam/marctl": "0.11.6",
44
- "@myagentroam/protocol": "0.11.45"
41
+ "@myagentroam/agent": "0.11.46",
42
+ "@myagentroam/marctl": "0.11.7",
43
+ "@myagentroam/protocol": "0.11.46",
44
+ "@myagentroam/code-navigation": "0.11.1"
45
45
  },
46
46
  "devDependencies": {
47
47
  "@types/ws": "^8.18.1"