@myagentroam/node 0.11.38 → 0.11.40

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 (65) hide show
  1. package/dist/computer-use/contracts.js +8 -0
  2. package/dist/{runner/mar-agent/computer-use-indicator.js → computer-use/indicator.js} +61 -38
  3. package/dist/computer-use/probe.js +95 -0
  4. package/dist/computer-use/provider.js +159 -0
  5. package/dist/computer-use/session.js +334 -0
  6. package/dist/computer-use/worker-source.js +135 -0
  7. package/dist/computer-use/worker.js +321 -0
  8. package/dist/connector.js +181 -22
  9. package/dist/database.js +35 -11
  10. package/dist/main.js +12 -1
  11. package/dist/marctl-guidance.js +18 -27
  12. package/dist/mcp/naming.js +6 -1
  13. package/dist/migrations/v022.js +58 -0
  14. package/dist/runner/abstract-runner.js +14 -0
  15. package/dist/runner/claude/tool-normalizer.js +6 -1
  16. package/dist/runner/codex/conversation-parser.js +23 -4
  17. package/dist/runner/codex/managed-run-controller.js +4 -2
  18. package/dist/runner/codex/tool-normalizer.js +9 -0
  19. package/dist/runner/execution-presentation.js +56 -0
  20. package/dist/runner/mar-agent/agent-network-extension.js +168 -0
  21. package/dist/runner/mar-agent/agent-network-wait-output.js +72 -0
  22. package/dist/runner/mar-agent/computer-use-skill.js +11 -2
  23. package/dist/runner/mar-agent/computer-use.js +104 -728
  24. package/dist/runner/mar-agent/conversation-normalizer.js +241 -121
  25. package/dist/runner/mar-agent/managed-run-controller.js +13 -5
  26. package/dist/runner/mar-agent/marctl-skill.js +3 -3
  27. package/dist/runner/mar-agent/runtime-model-factory.js +1 -0
  28. package/dist/runner/mar-agent/session-runtime.js +34 -1
  29. package/dist/runner/mar-agent-runner.js +36 -0
  30. package/dist/runner/opencode/tool-normalizer.js +6 -1
  31. package/dist/runtime-state.js +57 -0
  32. package/dist/service/agent-network-bindings.js +483 -0
  33. package/dist/service/agent-network-goal.js +19 -0
  34. package/dist/service/agent-network-invoke.js +179 -0
  35. package/dist/service/agent-network-observations.js +377 -0
  36. package/dist/service/agent-network-service.js +315 -0
  37. package/dist/service/agent-network-wait.js +130 -0
  38. package/dist/service/composer-message-input.js +9 -2
  39. package/dist/service/computer-use-enablement-service.js +13 -7
  40. package/dist/service/continuous-run-coordinator.js +24 -1
  41. package/dist/service/conversation-hosting-enablement-service.js +2 -2
  42. package/dist/service/goal-continuation-coordinator.js +12 -2
  43. package/dist/service/marctl-caller.js +80 -0
  44. package/dist/service/marctl-execution-credential-control.js +65 -0
  45. package/dist/service/model-usage-service.js +9 -0
  46. package/dist/service/node-config-mutations.js +27 -0
  47. package/dist/service/node-control-message-service.js +10 -0
  48. package/dist/service/node-request-service.js +52 -7
  49. package/dist/service/node-upgrade-coordinator.js +79 -8
  50. package/dist/service/node-upgrade-credential-control.js +57 -0
  51. package/dist/service/node-upgrade-reporter.js +108 -0
  52. package/dist/service/run-event-service.js +8 -0
  53. package/dist/service/run-workbench-service.js +11 -0
  54. package/dist/service/supervised-node-child.js +153 -0
  55. package/dist/service/supervisor-upgrade-handoff.js +104 -0
  56. package/dist/session/native-session-projection-service.js +69 -6
  57. package/dist/session/operation-admission.js +7 -0
  58. package/dist/session/runtime/conversation-projection.js +25 -3
  59. package/dist/session/session-catalog-service.js +156 -32
  60. package/dist/session/session-lifecycle-service.js +68 -42
  61. package/dist/session/session-message-service.js +16 -6
  62. package/dist/supervisor.js +209 -20
  63. package/dist/terminal.js +41 -21
  64. package/package.json +6 -5
  65. /package/dist/{runner/mar-agent/computer-use-probe.js → computer-use/probe-source.js} +0 -0
@@ -0,0 +1,315 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { isPlainRecord } from '../util/records.js';
3
+ import { safeErrorCode } from '../util/safe-error.js';
4
+ import { parseAgentGoal } from './agent-network-goal.js';
5
+ import { validateAgentWaitOptions, waitForAgentTargets } from './agent-network-wait.js';
6
+ /** Product collaboration delegates execution and Goal ownership to existing Node services. */
7
+ export class AgentNetworkService {
8
+ options;
9
+ constructor(options) {
10
+ this.options = options;
11
+ }
12
+ list(actor, input = {}) {
13
+ return this.options.list(actor, input);
14
+ }
15
+ view(actor, input) {
16
+ return this.options.view(actor, input);
17
+ }
18
+ async start(actor, input, signal) {
19
+ signal?.throwIfAborted();
20
+ const goal = parseAgentGoal(input.goal);
21
+ const content = input.content ?? '';
22
+ if (typeof content !== 'string' ||
23
+ (!content.trim() && !input.attachments?.length && !input.attachmentIds?.length) ||
24
+ (input.attachments !== undefined && !Array.isArray(input.attachments)) ||
25
+ (input.attachmentIds !== undefined &&
26
+ (!Array.isArray(input.attachmentIds) ||
27
+ input.attachmentIds.some((id) => typeof id !== 'string' || !id))) ||
28
+ (!!input.attachments?.length && !!input.attachmentIds?.length) ||
29
+ !validOptionalString(input.runner) ||
30
+ !validOptionalString(input.model) ||
31
+ !validOptionalString(input.effort) ||
32
+ !validOptionalString(input.title) ||
33
+ !validOptionalString(input.parentSessionId) ||
34
+ !validOptionalString(input.clientMessageId))
35
+ throw new Error('AGENT_START_INVALID');
36
+ const { goal: _goal, ...start } = input;
37
+ const created = await this.options.start(actor, {
38
+ ...start,
39
+ content,
40
+ clientMessageId: input.clientMessageId ?? randomUUID(),
41
+ goal
42
+ }, signal);
43
+ try {
44
+ this.options.observe(actor, created.session);
45
+ return { agentId: created.session.id, start: created.result };
46
+ }
47
+ catch (error) {
48
+ return {
49
+ agentId: created.session.id,
50
+ start: created.result,
51
+ observation: { status: 'failed', code: safeErrorCode(error, 'AGENT_OBSERVATION_FAILED') }
52
+ };
53
+ }
54
+ }
55
+ async send(actor, input, signal) {
56
+ signal?.throwIfAborted();
57
+ const goal = parseAgentGoal(input.goal);
58
+ const content = input.content ?? '';
59
+ if (typeof input.agentId !== 'string' ||
60
+ !input.agentId ||
61
+ typeof content !== 'string' ||
62
+ (!content.trim() &&
63
+ !input.attachments?.length &&
64
+ !input.attachmentIds?.length &&
65
+ goal.kind === 'unchanged' &&
66
+ input.approval === undefined) ||
67
+ (input.attachments !== undefined && !Array.isArray(input.attachments)) ||
68
+ (input.attachmentIds !== undefined &&
69
+ (!Array.isArray(input.attachmentIds) ||
70
+ input.attachmentIds.some((id) => typeof id !== 'string' || !id))) ||
71
+ (!!input.attachments?.length && !!input.attachmentIds?.length) ||
72
+ !validOptionalString(input.clientMessageId) ||
73
+ (input.approval !== undefined &&
74
+ (!isPlainRecord(input.approval) ||
75
+ Object.keys(input.approval).some((key) => key !== 'approvalId' && key !== 'decision') ||
76
+ typeof input.approval.approvalId !== 'string' ||
77
+ !input.approval.approvalId ||
78
+ !['approve', 'reject'].includes(input.approval.decision))))
79
+ throw new Error('AGENT_SEND_INVALID');
80
+ if (input.deliveryIntent !== undefined &&
81
+ !['SEND', 'QUEUE', 'REPLACE_CURRENT'].includes(input.deliveryIntent))
82
+ throw new Error('AGENT_SEND_INVALID');
83
+ const hasMessage = !!content.trim() || !!input.attachments?.length || !!input.attachmentIds?.length;
84
+ const session = await this.options.target(actor, input.agentId);
85
+ signal?.throwIfAborted();
86
+ if (hasMessage)
87
+ await this.options.authorize(actor, session, 'send');
88
+ if (goal.kind !== 'unchanged')
89
+ await this.options.authorize(actor, session, 'goal');
90
+ if (input.approval !== undefined)
91
+ await this.options.validateApproval(actor, session, input.approval);
92
+ signal?.throwIfAborted();
93
+ if (hasMessage && (await this.options.isUpward(actor, session))) {
94
+ signal?.throwIfAborted();
95
+ if (goal.kind !== 'unchanged' ||
96
+ input.approval !== undefined ||
97
+ input.deliveryIntent === 'REPLACE_CURRENT' ||
98
+ input.deliveryIntent === 'QUEUE' ||
99
+ input.attachments?.length ||
100
+ input.attachmentIds?.length)
101
+ throw new Error('AGENT_UPWARD_REPORT_INVALID');
102
+ const result = await this.options.reportUpward(actor, session, {
103
+ clientMessageId: input.clientMessageId ?? randomUUID(),
104
+ content
105
+ }, signal);
106
+ return { agentId: session.id, message: { status: 'accepted', result } };
107
+ }
108
+ signal?.throwIfAborted();
109
+ this.options.observe(actor, session);
110
+ const approvalResult = input.approval === undefined
111
+ ? undefined
112
+ : await this.options.approval(actor, session, input.approval, signal);
113
+ const approvalReceipt = input.approval === undefined ? {} : { approval: approvalResult };
114
+ if (approvalResult?.status === 'partial')
115
+ return {
116
+ agentId: session.id,
117
+ ...approvalReceipt,
118
+ ...(goal.kind === 'unchanged' ? {} : { goal: { status: 'not_attempted' } }),
119
+ message: { status: 'not_attempted' }
120
+ };
121
+ if (signal?.aborted && approvalResult !== undefined)
122
+ return {
123
+ agentId: session.id,
124
+ ...approvalReceipt,
125
+ ...(goal.kind === 'unchanged' ? {} : { goal: { status: 'not_attempted' } }),
126
+ message: { status: 'not_attempted' }
127
+ };
128
+ signal?.throwIfAborted();
129
+ const captured = this.options.observations.get(actor.sessionId)?.captureBeforeSend(session.id);
130
+ const message = hasMessage
131
+ ? {
132
+ clientMessageId: input.clientMessageId ?? randomUUID(),
133
+ content,
134
+ deliveryIntent: input.deliveryIntent ?? 'SEND',
135
+ ...(input.attachments === undefined ? {} : { attachments: input.attachments }),
136
+ ...(input.attachmentIds === undefined ? {} : { attachmentIds: input.attachmentIds })
137
+ }
138
+ : undefined;
139
+ let goalResult;
140
+ if (goal.kind !== 'unchanged') {
141
+ const previous = this.options.goal(session.id);
142
+ try {
143
+ goalResult = await this.options.setGoal(actor, session, goal, goal.kind === 'set' ? message : undefined, signal);
144
+ if (goal.kind === 'set' && message)
145
+ captured?.acknowledge();
146
+ }
147
+ catch (error) {
148
+ const current = this.options.goal(session.id);
149
+ if (sameGoal(current, previous) && input.approval === undefined)
150
+ throw error;
151
+ return {
152
+ agentId: session.id,
153
+ ...approvalReceipt,
154
+ goal: {
155
+ status: 'failed',
156
+ code: safeErrorCode(error, 'AGENT_GOAL_FAILED'),
157
+ current: current ?? null
158
+ },
159
+ message: { status: goal.kind === 'set' && message ? 'unknown' : 'not_attempted' }
160
+ };
161
+ }
162
+ }
163
+ if (!message || goal.kind === 'set')
164
+ return {
165
+ agentId: session.id,
166
+ ...approvalReceipt,
167
+ ...(goal.kind === 'unchanged' ? {} : { goal: { status: 'applied', result: goalResult } }),
168
+ message: message ? { status: 'accepted', result: goalResult } : null
169
+ };
170
+ if (signal?.aborted && (goal.kind !== 'unchanged' || approvalResult !== undefined))
171
+ return {
172
+ agentId: session.id,
173
+ ...approvalReceipt,
174
+ ...(goal.kind === 'unchanged' ? {} : { goal: { status: 'applied', result: goalResult } }),
175
+ message: { status: 'not_attempted' }
176
+ };
177
+ signal?.throwIfAborted();
178
+ try {
179
+ const result = await this.options.message(actor, session, message, signal);
180
+ captured?.acknowledge();
181
+ return {
182
+ agentId: session.id,
183
+ ...approvalReceipt,
184
+ ...(goal.kind === 'unchanged' ? {} : { goal: { status: 'applied', result: goalResult } }),
185
+ message: { status: 'accepted', result }
186
+ };
187
+ }
188
+ catch (error) {
189
+ if (signal?.aborted && error === signal.reason) {
190
+ if (goal.kind === 'unchanged' && approvalResult === undefined)
191
+ throw error;
192
+ return {
193
+ agentId: session.id,
194
+ ...approvalReceipt,
195
+ ...(goal.kind === 'unchanged' ? {} : { goal: { status: 'applied', result: goalResult } }),
196
+ message: { status: 'not_attempted' }
197
+ };
198
+ }
199
+ return {
200
+ agentId: session.id,
201
+ ...approvalReceipt,
202
+ ...(goal.kind === 'unchanged' ? {} : { goal: { status: 'applied', result: goalResult } }),
203
+ message: { status: 'unknown', code: safeErrorCode(error, 'AGENT_SEND_FAILED') }
204
+ };
205
+ }
206
+ }
207
+ async stop(actor, input) {
208
+ if (typeof input.agentId !== 'string' ||
209
+ !input.agentId ||
210
+ typeof input.executionId !== 'string' ||
211
+ !input.executionId)
212
+ throw new Error('AGENT_STOP_INVALID');
213
+ const session = await this.options.target(actor, input.agentId);
214
+ await this.options.authorize(actor, session, 'stop');
215
+ const goalBefore = this.options.goal(session.id);
216
+ try {
217
+ const result = this.options.stop(actor, session, input.executionId);
218
+ return {
219
+ agentId: session.id,
220
+ executionId: input.executionId,
221
+ cancel: { status: 'accepted', result },
222
+ goal: {
223
+ status: goalBefore != null && this.options.goal(session.id) == null ? 'cleared' : 'unchanged'
224
+ }
225
+ };
226
+ }
227
+ catch (error) {
228
+ const code = safeErrorCode(error, 'AGENT_STOP_FAILED');
229
+ if (code === 'RUN_EXECUTION_STALE' || code === 'RUN_OWNED_BY_ANOTHER_USER')
230
+ throw error;
231
+ const current = this.options.goal(session.id);
232
+ return {
233
+ agentId: session.id,
234
+ executionId: input.executionId,
235
+ cancel: { status: 'unknown', code },
236
+ goal: { status: goalBefore != null && current == null ? 'cleared' : 'unchanged' }
237
+ };
238
+ }
239
+ }
240
+ async wait(actor, input) {
241
+ validateAgentWaitOptions(input);
242
+ if ((input.mode !== undefined && input.agentIds === undefined) ||
243
+ (input.agentId !== undefined && input.agentIds !== undefined) ||
244
+ (input.agentId !== undefined && (typeof input.agentId !== 'string' || !input.agentId)) ||
245
+ (input.agentIds !== undefined &&
246
+ (!Array.isArray(input.agentIds) ||
247
+ input.agentIds.length === 0 ||
248
+ input.agentIds.length > 8 ||
249
+ input.agentIds.some((id) => typeof id !== 'string' || !id) ||
250
+ new Set(input.agentIds).size !== input.agentIds.length)))
251
+ throw new Error('AGENT_WAIT_INVALID');
252
+ input.signal?.throwIfAborted();
253
+ const ids = input.agentId === undefined ? input.agentIds : [input.agentId];
254
+ let sessions;
255
+ if (ids)
256
+ sessions = await Promise.all(ids.map((id) => this.options.target(actor, id)));
257
+ else
258
+ sessions = await this.options.children(actor);
259
+ await Promise.all(sessions.map((session) => this.options.authorize(actor, session, 'wait')));
260
+ input.signal?.throwIfAborted();
261
+ let targets = sessions.flatMap((session) => {
262
+ const target = this.options.freezeExecution(session);
263
+ return target === undefined ? [] : [target];
264
+ });
265
+ if (ids && targets.length !== sessions.length)
266
+ throw new Error('AGENT_EXECUTION_NOT_FOUND');
267
+ if (!ids) {
268
+ const active = targets.filter((target) => !['SUCCEEDED', 'FAILED', 'CANCELLED', 'INTERRUPTED'].includes(target.read().status));
269
+ if (active.length) {
270
+ const creation = new Map(sessions.map((session) => [session.id, session.createdAt]));
271
+ targets = active.sort((a, b) => (creation.get(b.agentId) ?? 0) - (creation.get(a.agentId) ?? 0));
272
+ }
273
+ else {
274
+ const withExecution = new Set(targets.map((target) => target.agentId));
275
+ const latest = [...sessions]
276
+ .filter((session) => withExecution.has(session.id))
277
+ .sort((a, b) => b.createdAt - a.createdAt)[0];
278
+ targets = targets.filter((target) => target.agentId === latest?.id);
279
+ }
280
+ }
281
+ if (!targets.length)
282
+ throw new Error('AGENT_EXECUTION_NOT_FOUND');
283
+ const selected = new Set(targets.map((target) => target.agentId));
284
+ for (const session of sessions)
285
+ if (selected.has(session.id))
286
+ this.options.observe(actor, session);
287
+ const observations = this.options.observations.get(actor.observationId ?? actor.sessionId);
288
+ if (!observations)
289
+ throw new Error('AGENT_OBSERVATION_UNAVAILABLE');
290
+ return waitForAgentTargets({
291
+ targets,
292
+ observations,
293
+ single: input.agentIds === undefined,
294
+ waitMs: input.waitMs ?? 30_000,
295
+ ...(input.mode === undefined ? {} : { mode: input.mode }),
296
+ ...(input.waitFor === undefined ? {} : { waitFor: input.waitFor }),
297
+ ...(input.signal === undefined ? {} : { signal: input.signal }),
298
+ ...(input.interrupt === undefined ? {} : { interrupt: input.interrupt })
299
+ });
300
+ }
301
+ }
302
+ function sameGoal(left, right) {
303
+ if (left === right)
304
+ return true;
305
+ return (isPlainRecord(left) &&
306
+ isPlainRecord(right) &&
307
+ typeof left.id === 'string' &&
308
+ left.id === right.id &&
309
+ typeof left.revision === 'number' &&
310
+ left.revision === right.revision &&
311
+ left.status === right.status);
312
+ }
313
+ function validOptionalString(value) {
314
+ return value === undefined || (typeof value === 'string' && value.trim().length > 0);
315
+ }
@@ -0,0 +1,130 @@
1
+ import { waitForToolCondition } from '@myagentroam/agent/extensions';
2
+ export function validateAgentWaitOptions(input) {
3
+ if ((input.mode !== undefined && input.mode !== 'any' && input.mode !== 'all') ||
4
+ (input.waitFor !== undefined &&
5
+ input.waitFor !== 'message' &&
6
+ input.waitFor !== 'completion') ||
7
+ (input.waitMs !== undefined &&
8
+ (!Number.isInteger(input.waitMs) || input.waitMs < 0 || input.waitMs > 300_000)))
9
+ throw new Error('AGENT_WAIT_INVALID');
10
+ }
11
+ /** One deadline and one notification subscription, with Session-scoped consumption. */
12
+ export async function waitForAgentTargets(input) {
13
+ const { targets, observations } = input;
14
+ validateAgentWaitOptions(input);
15
+ if (!targets.length ||
16
+ (!input.single && targets.length > 8) ||
17
+ new Set(targets.map((target) => target.agentId)).size !== targets.length)
18
+ throw new Error('AGENT_WAIT_INVALID');
19
+ input.signal?.throwIfAborted();
20
+ const waitMs = input.waitMs ?? 30_000;
21
+ const snapshot = waitMs === 0;
22
+ const terminalSnapshots = new Map();
23
+ const read = (target) => {
24
+ const state = target.read();
25
+ const executionId = target.executionId;
26
+ const observation = executionId === undefined
27
+ ? undefined
28
+ : (observations.capture(target.agentId, executionId) ??
29
+ terminalSnapshots.get(JSON.stringify([target.agentId, target.runId, executionId])));
30
+ const terminal = ['SUCCEEDED', 'FAILED', 'CANCELLED', 'INTERRUPTED'].includes(state.status);
31
+ const message = observation?.updates.some((update) => update.kind === 'message') === true;
32
+ return {
33
+ state,
34
+ executionId,
35
+ observation,
36
+ terminal,
37
+ ready: terminal || (input.waitFor === 'message' && message)
38
+ };
39
+ };
40
+ const satisfied = () => {
41
+ const values = targets.map(read);
42
+ return input.mode === 'all'
43
+ ? values.every((value) => value.ready)
44
+ : values.some((value) => value.ready);
45
+ };
46
+ let outcome = 'snapshot';
47
+ if (!snapshot) {
48
+ outcome = await waitForToolCondition({
49
+ ready: satisfied,
50
+ subscribe: (listener) => observations.subscribe((terminal) => {
51
+ if (terminal &&
52
+ targets.some((target) => target.agentId === terminal.childId &&
53
+ target.runId === terminal.runId &&
54
+ target.executionId === terminal.executionId))
55
+ terminalSnapshots.set(JSON.stringify([terminal.childId, terminal.runId, terminal.executionId]), terminal);
56
+ listener();
57
+ }),
58
+ waitMs: Math.max(waitMs, 10_000),
59
+ signal: input.signal,
60
+ interrupt: input.interrupt
61
+ });
62
+ input.signal?.throwIfAborted();
63
+ }
64
+ const values = targets.map((target) => ({ target, ...read(target) }));
65
+ const pendingAgentIds = values
66
+ .filter((value) => !value.ready)
67
+ .map((value) => value.target.agentId);
68
+ const ordered = input.single
69
+ ? [...values].sort((a, b) => (a.observation?.firstRevision ?? Infinity) - (b.observation?.firstRevision ?? Infinity))
70
+ : values;
71
+ const selected = input.single
72
+ ? [
73
+ ordered.find((value) => value.ready) ??
74
+ ordered.find((value) => (value.observation?.updates.length ?? 0) > 0) ??
75
+ values[0]
76
+ ]
77
+ : values;
78
+ const agents = selected.map(({ target, executionId, state, observation, terminal, ready }) => {
79
+ const waitOutcome = snapshot
80
+ ? 'snapshot'
81
+ : terminal
82
+ ? 'completion'
83
+ : ready
84
+ ? 'message'
85
+ : outcome === 'timeout'
86
+ ? 'timeout'
87
+ : outcome === 'interrupted'
88
+ ? 'interrupted'
89
+ : 'snapshot';
90
+ const include = !snapshot && (ready || outcome === 'timeout' || outcome === 'interrupted');
91
+ const projection = include && terminal ? observation?.result : undefined;
92
+ const updates = include && projection?.summary && !projection.summaryTruncated
93
+ ? observation?.updates.filter((update) => !update.final)
94
+ : include
95
+ ? observation?.updates
96
+ : undefined;
97
+ if (include)
98
+ observation?.acknowledge();
99
+ return {
100
+ agentId: target.agentId,
101
+ runId: target.runId,
102
+ ...(executionId === undefined ? {} : { executionId }),
103
+ status: state.status,
104
+ waitOutcome,
105
+ ...projection,
106
+ ...(updates?.length ? { updates } : {})
107
+ };
108
+ });
109
+ const ready = values.filter((value) => value.ready);
110
+ const groupOutcome = outcome === 'ready'
111
+ ? ready.every((value) => value.terminal)
112
+ ? 'completion'
113
+ : 'message'
114
+ : outcome;
115
+ if (input.single)
116
+ return {
117
+ ...agents[0],
118
+ waitFor: input.waitFor ?? 'completion',
119
+ ...(groupOutcome === 'interrupted' ? { waitOutcome: 'interrupted' } : {}),
120
+ ...(groupOutcome === 'interrupted' ? { reason: 'steer' } : {})
121
+ };
122
+ return {
123
+ mode: input.mode ?? 'any',
124
+ waitFor: input.waitFor ?? 'completion',
125
+ waitOutcome: snapshot ? 'snapshot' : groupOutcome,
126
+ ...(groupOutcome === 'interrupted' ? { reason: 'steer' } : {}),
127
+ agents,
128
+ pendingAgentIds
129
+ };
130
+ }
@@ -3,10 +3,17 @@ import { relative, resolve } from 'node:path';
3
3
  import { homedir } from 'node:os';
4
4
  import { SkillDirectoryService } from './skill-directory-service.js';
5
5
  /** Keep the admitted source snapshot beside message and Goal markup. */
6
- export function withSessionMessageOrigin(input, origin) {
6
+ export function withSessionMessageOrigin(input, origin, parentSessionId) {
7
7
  if (origin === undefined)
8
8
  return input;
9
- const tag = serializeComposerTag('mar_session_origin', sessionMessageOriginSchema.parse(origin));
9
+ const parsed = sessionMessageOriginSchema.parse(origin);
10
+ const snapshot = parsed.sessionId === undefined
11
+ ? parsed
12
+ : {
13
+ ...parsed,
14
+ relation: parsed.sessionId === parentSessionId ? 'parent' : 'other'
15
+ };
16
+ const tag = serializeComposerTag('mar_session_origin', snapshot);
10
17
  const parts = parseComposerTags(input);
11
18
  const first = parts[0];
12
19
  const goal = first?.kind === 'mar_goal_start' || first?.kind === 'mar_goal_continue' ? first : undefined;
@@ -1,5 +1,7 @@
1
1
  import { computerUseDisableInputSchema, computerUseEnableInputSchema, computerUseEnableResultSchema } from '@myagentroam/protocol';
2
2
  import { computerUseRuntimeExecutable, saveNodeConfig } from '../config.js';
3
+ import { ComputerUseRuntimeError } from '../computer-use-runtime.js';
4
+ import { ComputerUseError } from '../computer-use/contracts.js';
3
5
  import { nodeOperationData } from '../connection/node-operation-data.js';
4
6
  /** Persists Computer Use authorization and owns its asynchronous capability probe. */
5
7
  export class ComputerUseEnablementService {
@@ -17,8 +19,8 @@ export class ComputerUseEnablementService {
17
19
  }
18
20
  operations() {
19
21
  return {
20
- 'computer.use.enable': (data) => this.enable(data),
21
- 'computer.use.disable': (data) => this.disable(data)
22
+ 'computer.use.enable': (data) => this.options.mutations.run(() => this.enable(data)),
23
+ 'computer.use.disable': (data) => this.options.mutations.run(() => this.disable(data))
22
24
  };
23
25
  }
24
26
  initialize() {
@@ -107,9 +109,13 @@ export class ComputerUseEnablementService {
107
109
  });
108
110
  void this.options.provider
109
111
  .reset()
110
- .then(() => this.options.prepareRuntime())
111
- .then(() => this.options.provider.support())
112
- .then((support) => {
112
+ .then(async () => {
113
+ if (generation !== this.#probeGeneration)
114
+ return;
115
+ await this.options.prepareRuntime();
116
+ if (generation !== this.#probeGeneration)
117
+ return;
118
+ const support = await this.options.provider.support();
113
119
  if (generation !== this.#probeGeneration)
114
120
  return;
115
121
  this.#set(this.#fromSupport(support));
@@ -117,8 +123,8 @@ export class ComputerUseEnablementService {
117
123
  .catch((error) => {
118
124
  if (generation !== this.#probeGeneration)
119
125
  return;
120
- this.#set(this.#unavailable(error instanceof Error && /^[A-Z][A-Z0-9_]{0,79}$/u.test(error.message)
121
- ? error.message
126
+ this.#set(this.#unavailable(error instanceof ComputerUseError || error instanceof ComputerUseRuntimeError
127
+ ? error.code
122
128
  : 'COMPUTER_PROBE_FAILED'));
123
129
  });
124
130
  }
@@ -672,8 +672,30 @@ export class ContinuousRunCoordinator {
672
672
  const goalMcp = pending.goal === undefined ? undefined : await this.options.goalMcp?.(executionInput);
673
673
  const scheduleMcp = await this.options.scheduleMcp?.(executionInput);
674
674
  const notificationMcp = await this.options.notificationMcp?.(executionInput);
675
+ const { MAR_CTL_USER_TOKEN: parentToken, ...runnerEnvironment } = pending.secretEnvironment;
676
+ let executionToken;
677
+ if (parentToken !== undefined) {
678
+ const execution = this.options.runtime.executionSnapshot(executionId);
679
+ if (!this.options.mintMarctlExecutionCredential ||
680
+ !execution ||
681
+ execution.sessionId !== pending.sessionId ||
682
+ execution.runId !== active.id ||
683
+ !Number.isSafeInteger(active.initiatedByUserId))
684
+ throw new Error('MARCTL_EXECUTION_AUTHORIZATION_INVALID');
685
+ executionToken = await this.options.mintMarctlExecutionCredential({
686
+ parentToken,
687
+ workspaceId: pending.workspaceId,
688
+ sessionId: pending.sessionId,
689
+ executionId,
690
+ userId: active.initiatedByUserId
691
+ });
692
+ }
675
693
  if (this.suspended || this.options.stopped() || !this.dispatchedExecutions.has(executionId))
676
694
  return;
695
+ if (executionToken !== undefined &&
696
+ (this.options.runtime.activeSessionRun(pending.sessionId)?.id !== active.id ||
697
+ this.options.runtime.currentExecutionId(active.id) !== executionId))
698
+ throw new Error('MARCTL_EXECUTION_AUTHORIZATION_INVALID');
677
699
  if (!this.goalCurrent(pending)) {
678
700
  this.dispatchedExecutions.delete(executionId);
679
701
  this.rejectStaleGoal(executionId);
@@ -701,7 +723,8 @@ export class ContinuousRunCoordinator {
701
723
  collaborationMode: pending.collaborationMode,
702
724
  ...(pending.serviceTier === undefined ? {} : { serviceTier: pending.serviceTier }),
703
725
  secretEnvironment: {
704
- ...pending.secretEnvironment,
726
+ ...runnerEnvironment,
727
+ ...(executionToken === undefined ? {} : { MAR_CTL_USER_TOKEN: executionToken }),
705
728
  MAR_CTL_NODE_ID: this.options.runtime.getAgentSession(pending.sessionId)?.nodeId ?? '',
706
729
  MAR_CTL_WORKSPACE_ID: pending.workspaceId,
707
730
  MAR_CTL_SESSION_ID: this.options.runtime.getAgentSession(pending.sessionId)?.id ?? pending.sessionId,
@@ -9,8 +9,8 @@ export class ConversationHostingEnablementService {
9
9
  }
10
10
  operations() {
11
11
  return {
12
- 'conversation.hosting.enable': (data) => this.enable(data),
13
- 'conversation.hosting.disable': (data) => this.disable(data)
12
+ 'conversation.hosting.enable': (data) => this.options.mutations.run(() => this.enable(data)),
13
+ 'conversation.hosting.disable': (data) => this.options.mutations.run(() => this.disable(data))
14
14
  };
15
15
  }
16
16
  async disable(data) {
@@ -179,7 +179,7 @@ export class GoalContinuationCoordinator {
179
179
  goalAccessChanged: prepared.changed
180
180
  };
181
181
  }
182
- async start({ session, objective, initiatedByUserId, suppressNotifications, secretEnvironment, personalInstructions, executionContext, usageGrant }) {
182
+ async start({ session, objective, initiatedByUserId, suppressNotifications, secretEnvironment, personalInstructions, executionContext, usageGrant, preserveActiveRun, messageIntent, messageContent, clientMessageId, messageOrigin, attachments, attachmentIds }) {
183
183
  this.invalidateGoalContinuation(session.id);
184
184
  const prepared = this.prepareGoalSession(session);
185
185
  const goalSession = prepared.session;
@@ -198,8 +198,18 @@ export class GoalContinuationCoordinator {
198
198
  kind: 'start',
199
199
  content: this.options.goals.prompt(attempt.sessionId, 'start'),
200
200
  objective: goal.objective,
201
- deliveryIntent: replacing ? 'REPLACE_CURRENT' : 'SEND',
201
+ deliveryIntent: messageIntent ??
202
+ (preserveActiveRun && activeRun !== undefined
203
+ ? 'QUEUE'
204
+ : replacing
205
+ ? 'REPLACE_CURRENT'
206
+ : 'SEND'),
202
207
  goal: goalRef,
208
+ ...(messageContent === undefined ? {} : { messageContent }),
209
+ ...(clientMessageId === undefined ? {} : { clientMessageId }),
210
+ ...(messageOrigin === undefined ? {} : { messageOrigin }),
211
+ ...(attachments === undefined ? {} : { attachments }),
212
+ ...(attachmentIds === undefined ? {} : { attachmentIds }),
203
213
  ...(suppressNotifications === true ? { suppressNotifications: true } : {}),
204
214
  ...(usageGrant === undefined ? {} : { usageGrant }),
205
215
  ...(initiatedByUserId === undefined ? {} : { initiatedByUserId }),