@myagentroam/node 0.11.42 → 0.11.44

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 (34) hide show
  1. package/dist/application/node-lifecycle.js +30 -7
  2. package/dist/connector.js +14 -3
  3. package/dist/hosting/hosted-conversation-node-service.js +1 -2
  4. package/dist/main.js +10 -3
  5. package/dist/marctl-guidance.js +3 -1
  6. package/dist/runner/abstract-runner.js +2 -2
  7. package/dist/runner/claude/managed-run-controller.js +1 -1
  8. package/dist/runner/claude/native-transcript.js +1 -1
  9. package/dist/runner/claude/tool-normalizer.js +21 -12
  10. package/dist/runner/codex/conversation-parser.js +17 -9
  11. package/dist/runner/codex/managed-run-controller.js +1 -1
  12. package/dist/runner/codex/tool-normalizer.js +17 -9
  13. package/dist/runner/execution-presentation.js +0 -3
  14. package/dist/runner/mar-agent/agent-network-extension.js +2 -2
  15. package/dist/runner/mar-agent/conversation-normalizer.js +259 -43
  16. package/dist/runner/mar-agent/managed-run-controller.js +2 -1
  17. package/dist/runner/mar-agent/runtime-model-factory.js +2 -4
  18. package/dist/runner/mar-agent/session-runtime-registry.js +22 -12
  19. package/dist/runner/mar-agent/session-runtime.js +16 -11
  20. package/dist/runner/mar-agent-runner.js +32 -8
  21. package/dist/runner/normalization.js +8 -0
  22. package/dist/runner/opencode/tool-normalizer.js +8 -4
  23. package/dist/runtime-state.js +0 -1
  24. package/dist/service/agent-network-bindings.js +9 -50
  25. package/dist/service/agent-network-invoke.js +9 -21
  26. package/dist/service/agent-network-observations.js +15 -13
  27. package/dist/service/agent-network-service.js +39 -31
  28. package/dist/service/goal-continuation-coordinator.js +5 -0
  29. package/dist/service/marctl-caller.js +17 -9
  30. package/dist/service/runner-service.js +7 -3
  31. package/dist/session/runtime/conversation-projection.js +3 -1
  32. package/dist/session/session-lifecycle-service.js +7 -4
  33. package/dist/session/session-message-service.js +4 -2
  34. package/package.json +5 -5
@@ -1,4 +1,4 @@
1
- import { marAgentNodeModelSummarySchema, parseComposerTags, resolveMarAgentDefaultReasoningEffort, serializeComposerTag } from '@myagentroam/protocol';
1
+ import { marAgentNodeModelSummarySchema, marAgentRuntimeModelConfigurationSchema, parseComposerTags, resolveMarAgentDefaultReasoningEffort, serializeComposerTag } from '@myagentroam/protocol';
2
2
  import { nodeLog } from '../operational.js';
3
3
  import { runnerErrorProjection } from './runner-error.js';
4
4
  import { AgentError, openAgentSessionCatalog } from '@myagentroam/agent';
@@ -7,7 +7,7 @@ import { resolve } from 'node:path';
7
7
  import { AbstractRunner } from './abstract-runner.js';
8
8
  import { marAgentGeneratedImageSources, marAgentGeneratedImagesDirectory } from './mar-agent/generated-images.js';
9
9
  import { projectMarAgentHistory } from './mar-agent/history-projection.js';
10
- import { captureMarAgentExecutionInput } from './mar-agent/execution-input.js';
10
+ import { captureMarAgentExecutionInput, marAgentEnvelopeInput } from './mar-agent/execution-input.js';
11
11
  export class MarAgentRunner extends AbstractRunner {
12
12
  homeDirectory;
13
13
  name = 'mar-agent';
@@ -31,6 +31,7 @@ export class MarAgentRunner extends AbstractRunner {
31
31
  return { runner: this.name, runnerInput: captureMarAgentExecutionInput(input) };
32
32
  }
33
33
  discoveryScope = 'ALL_WORKSPACES';
34
+ // Display and usage hints only; request admission/defaults use captured resources.
34
35
  #models = [];
35
36
  #releaseSessionResources;
36
37
  #generatedImageRegistrar;
@@ -45,6 +46,7 @@ export class MarAgentRunner extends AbstractRunner {
45
46
  return parsed.success ? [parsed.data] : [];
46
47
  })
47
48
  : [];
49
+ return this.profileForValidation();
48
50
  }
49
51
  setSessionResourceReleaser(release) {
50
52
  this.#releaseSessionResources = release;
@@ -114,8 +116,9 @@ export class MarAgentRunner extends AbstractRunner {
114
116
  available(capabilities) {
115
117
  return capabilities.marAgent?.available === true;
116
118
  }
117
- profileForValidation() {
118
- const models = this.#models.filter((model) => model.enabled && model.conversationModel !== false);
119
+ profileForValidation(catalog = this
120
+ .#models) {
121
+ const models = catalog.filter((model) => model.enabled && model.conversationModel !== false);
119
122
  return {
120
123
  runner: 'mar-agent',
121
124
  models: models.map((model, index) => ({
@@ -202,10 +205,29 @@ export class MarAgentRunner extends AbstractRunner {
202
205
  .map((model) => model.id));
203
206
  return this.profileForValidation().models.filter((model) => eligible.has(model.id));
204
207
  }
205
- assertChildModel(modelId) {
206
- if (!this.childModels().some((model) => model.id === modelId))
208
+ assertChildModel(modelId, context) {
209
+ if (!this.executionModels(context).some((model) => model.id === modelId && model.enabled && model.conversationModel && model.subagentModel))
207
210
  throw new Error('MAR_AGENT_SUBAGENT_MODEL_UNAVAILABLE');
208
211
  }
212
+ executionModels(context, purpose = 'admission') {
213
+ if (context.runner !== this.name)
214
+ return [];
215
+ const input = marAgentEnvelopeInput({ runnerInput: context.runnerInput });
216
+ const candidates = purpose === 'defaults' && Array.isArray(input?.models) && input.models.length > 0
217
+ ? input.models
218
+ : input?.model !== undefined
219
+ ? [input.model]
220
+ : Array.isArray(input?.models)
221
+ ? input.models
222
+ : [];
223
+ return candidates.flatMap((value) => {
224
+ const runtime = marAgentRuntimeModelConfigurationSchema.safeParse(value);
225
+ if (runtime.success)
226
+ return [runtime.data];
227
+ const summary = marAgentNodeModelSummarySchema.safeParse(value);
228
+ return summary.success ? [summary.data] : [];
229
+ });
230
+ }
209
231
  supportsConfiguration(configuration) {
210
232
  if (configuration.runner !== this.name)
211
233
  return false;
@@ -217,8 +239,10 @@ export class MarAgentRunner extends AbstractRunner {
217
239
  : model.supportedEfforts.includes(configuration.effort)) &&
218
240
  profile.accessOptions.some((candidate) => candidate.id === configuration.access));
219
241
  }
220
- defaultConfiguration() {
221
- const profile = this.profileForValidation();
242
+ defaultConfiguration(_workspace, _environment, executionContext) {
243
+ const profile = this.profileForValidation(executionContext === undefined
244
+ ? this.#models
245
+ : this.executionModels(executionContext, 'defaults'));
222
246
  const model = profile.models[0];
223
247
  return {
224
248
  runner: 'mar-agent',
@@ -1,6 +1,14 @@
1
1
  export function compactRunnerText(value, limit) {
2
2
  return value.length <= limit ? value : `${value.slice(0, Math.max(0, limit - 1))}…`;
3
3
  }
4
+ export function publicOutputSnapshot(value, truncated = false) {
5
+ const text = typeof value === 'string' ? value : compactRunnerValue(value, Number.MAX_SAFE_INTEGER).text;
6
+ let bounded = text.slice(-10_000);
7
+ const first = bounded.charCodeAt(0);
8
+ if (text.length > 10_000 && first >= 0xdc00 && first <= 0xdfff)
9
+ bounded = bounded.slice(1);
10
+ return { text: bounded, truncated: truncated || text.length > 10_000 };
11
+ }
4
12
  export function compactRunnerValue(value, limit) {
5
13
  if (value === undefined || value === null)
6
14
  return { text: '', truncated: false };
@@ -1,5 +1,5 @@
1
- import { compactRunnerText, compactRunnerValue } from '../normalization.js';
2
- import { publicOutputSnapshot, shellExecution } from '../execution-presentation.js';
1
+ import { compactRunnerText, compactRunnerValue, publicOutputSnapshot } from '../normalization.js';
2
+ import { shellExecution } from '../execution-presentation.js';
3
3
  export function normalizeOpenCodeTool(toolName, state) {
4
4
  const status = state?.status === 'completed'
5
5
  ? 'COMPLETED'
@@ -14,6 +14,7 @@ export function normalizeOpenCodeTool(toolName, state) {
14
14
  return {
15
15
  kind: 'command_execution',
16
16
  payload: {
17
+ toolName: publicToolName,
17
18
  command: compactRunnerText(typeof input?.command === 'string'
18
19
  ? input.command
19
20
  : typeof input?.cmd === 'string'
@@ -21,7 +22,6 @@ export function normalizeOpenCodeTool(toolName, state) {
21
22
  : publicToolName, 10_000),
22
23
  cwd: typeof input?.cwd === 'string' ? compactRunnerText(input.cwd, 4_096) : null,
23
24
  outputPreview: typeof state?.output === 'string' ? compactRunnerText(state.output, 10_000) : '',
24
- outputRef: null,
25
25
  exitCode: typeof state?.exitCode === 'number' ? state.exitCode : null,
26
26
  durationMs: null,
27
27
  truncated: typeof state?.output === 'string' && state.output.length > 10_000,
@@ -53,6 +53,7 @@ export function normalizeOpenCodeTool(toolName, state) {
53
53
  }
54
54
  }
55
55
  : {}),
56
+ ...(typeof state?.output === 'string' ? { output: publicOutputSnapshot(state.output) } : {}),
56
57
  ...subagent
57
58
  }
58
59
  };
@@ -75,7 +76,10 @@ function openCodeSubagentProjection(toolName, input, output) {
75
76
  ...(title.length === 0 ? {} : { title }),
76
77
  ...(nativeResult === undefined
77
78
  ? {}
78
- : { outputSummary: compactRunnerText(nativeResult.result, 10_000) || null })
79
+ : {
80
+ outputSummary: compactRunnerText(nativeResult.result, 10_000) || null,
81
+ output: publicOutputSnapshot(nativeResult.result)
82
+ })
79
83
  };
80
84
  }
81
85
  function openCodeTaskResult(value) {
@@ -1046,7 +1046,6 @@ export class NodeRuntimeState {
1046
1046
  command: input.command,
1047
1047
  cwd: input.cwd,
1048
1048
  outputPreview: input.outputPreview.slice(0, 10_000),
1049
- outputRef: null,
1050
1049
  exitCode: input.failed ? 1 : 0,
1051
1050
  durationMs: 0,
1052
1051
  truncated: false
@@ -3,10 +3,8 @@ import { resolvedPersonalInstructions } from '../runner/personal-instructions.js
3
3
  import { AgentNetworkService } from './agent-network-service.js';
4
4
  import { isPlainRecord } from '../util/records.js';
5
5
  import { safeErrorCode } from '../util/safe-error.js';
6
- import { withSessionMessageOrigin } from './composer-message-input.js';
7
6
  import { SESSION_ADMISSION_SIGNAL } from '../session/operation-admission.js';
8
7
  import { assertSessionControlOwner } from './run-ownership-policy.js';
9
- const approvedCalls = new WeakMap();
10
8
  const terminalCalls = new WeakMap();
11
9
  /** Bind a terminal actor only after the Node request boundary has authenticated its caller. */
12
10
  export function actorForAuthenticatedTerminal(actor, workspaceId) {
@@ -14,14 +12,6 @@ export function actorForAuthenticatedTerminal(actor, workspaceId) {
14
12
  terminalCalls.set(authenticated, { workspaceId });
15
13
  return authenticated;
16
14
  }
17
- /** Bind a Host-approved invocation to its exact target and domain operations. */
18
- export function actorForApprovedAgentCall(actor, context, agentIds, operations) {
19
- if (context.permission?.decision !== 'approved')
20
- return actor;
21
- const approved = Object.freeze({ ...actor });
22
- approvedCalls.set(approved, { agentIds: [...agentIds], operations: [...operations] });
23
- return approved;
24
- }
25
15
  import { sessionPage } from '../session/pagination.js';
26
16
  import { marctlSessionSummary, marctlSessionDetail } from './session-recent-activity.js';
27
17
  /** Bind the collaboration use case to the existing Session, Run and Goal owners. */
@@ -50,7 +40,7 @@ export function createNodeAgentNetwork(options) {
50
40
  const parent = terminal ? undefined : await actorSession(actor);
51
41
  const workspaceId = terminal?.workspaceId ?? parent?.workspaceId;
52
42
  const session = options.runtime.getAgentSession(id) ?? (await options.projection.resolve(id, true));
53
- if (!session || session.workspaceId !== workspaceId || session.id === parent?.id)
43
+ if (!session || session.workspaceId !== workspaceId)
54
44
  throw new Error('SESSION_NOT_FOUND');
55
45
  return session;
56
46
  }
@@ -93,11 +83,8 @@ export function createNodeAgentNetwork(options) {
93
83
  return result;
94
84
  }
95
85
  async function validateApproval(actor, session, input) {
96
- if (!terminalCalls.has(actor)) {
97
- const parent = await actorSession(actor);
98
- if (session.parentSessionId !== parent.id)
99
- throw new Error('AGENT_TARGET_NOT_AUTHORIZED');
100
- }
86
+ if (!terminalCalls.has(actor))
87
+ await actorSession(actor);
101
88
  assertSessionControlOwner(options.runtime, session.id, actor.requestUserId);
102
89
  const approval = options.runtime.getApproval(input.approvalId);
103
90
  if (!approval)
@@ -118,19 +105,10 @@ export function createNodeAgentNetwork(options) {
118
105
  return new AgentNetworkService({
119
106
  observations: options.observations,
120
107
  target,
121
- authorize: async (actor, session, operation) => {
108
+ authorize: async (actor, session) => {
109
+ if (!terminalCalls.has(actor))
110
+ await actorSession(actor);
122
111
  assertSessionControlOwner(options.runtime, session.id, actor.requestUserId);
123
- if (terminalCalls.has(actor))
124
- return;
125
- const source = await actorSession(actor);
126
- if (session.parentSessionId === source.id)
127
- return;
128
- if (source.parentSessionId === session.id && operation === 'send')
129
- return;
130
- const approved = approvedCalls.get(actor);
131
- if (approved?.agentIds.includes(session.id) && approved.operations.includes(operation))
132
- return;
133
- throw new Error('AGENT_TARGET_NOT_AUTHORIZED');
134
112
  },
135
113
  children: async (actor) => {
136
114
  if (terminalCalls.has(actor))
@@ -141,27 +119,8 @@ export function createNodeAgentNetwork(options) {
141
119
  options.projection.resolve(record.id, true)));
142
120
  return resolved.filter((session) => session !== undefined);
143
121
  },
144
- observe: (actor, session) => options.ensureObservations(actor).observe(session.id),
145
- isUpward: async (actor, session) => !terminalCalls.has(actor) && (await actorSession(actor)).parentSessionId === session.id,
146
- reportUpward: async (actor, session, input, signal) => {
147
- const child = await actorSession(actor);
148
- const runId = actor.runId;
149
- const executionId = actor.executionId;
150
- if (!runId ||
151
- !executionId ||
152
- options.runtime.currentExecutionId(runId) !== executionId ||
153
- child.parentSessionId !== session.id)
154
- throw new Error('AGENT_ACTOR_NOT_ACTIVE');
155
- signal?.throwIfAborted();
156
- const parent = options.observations.get(session.id);
157
- if (!parent?.report({
158
- childId: child.id,
159
- runId,
160
- executionId,
161
- value: withSessionMessageOrigin(input.content, origin(child), session.parentSessionId)
162
- }))
163
- throw new Error('AGENT_PARENT_NOT_WARM');
164
- return { delivery: 'reported', clientMessageId: input.clientMessageId };
122
+ observe: (actor) => {
123
+ options.ensureObservations(actor);
165
124
  },
166
125
  freezeExecution: (session) => {
167
126
  const snapshot = options.runtime.latestSessionExecution(session.id);
@@ -210,7 +169,7 @@ export function createNodeAgentNetwork(options) {
210
169
  throw new Error('SESSION_INVALID');
211
170
  const model = input.model ??
212
171
  database.getRunnerDefaults().find((entry) => entry.runner === runner)?.model ??
213
- runnerAdapter.defaultConfiguration(workspace, actor.secretEnvironment ?? {}).model;
172
+ runnerAdapter.defaultConfiguration(workspace, actor.secretEnvironment ?? {}, runnerAdapter.prepareExecutionContext(actor.runnerConfiguration ?? {})).model;
214
173
  const request = {
215
174
  ...executionInput(actor, runner, model, parent !== undefined),
216
175
  workspaceId: workspace.id,
@@ -1,4 +1,4 @@
1
- import { actorForApprovedAgentCall, actorForAuthenticatedTerminal } from './agent-network-bindings.js';
1
+ import { actorForAuthenticatedTerminal } from './agent-network-bindings.js';
2
2
  import { isPlainRecord } from '../util/records.js';
3
3
  import { randomUUID } from 'node:crypto';
4
4
  import { modelUsageGrantSchema } from '@myagentroam/protocol';
@@ -11,11 +11,14 @@ export function agentNetworkInvoke(input) {
11
11
  }
12
12
  /** Read actor resources from the current Node-owned Execution, not caller arguments. */
13
13
  export function executionAgentActor(input) {
14
- const { sessionId, executionId } = input;
14
+ const { executionId } = input;
15
+ // Warm runtimes and execution credentials may retain the bootstrap Session ID.
16
+ const sessionId = input.runtime.getAgentSessionById(input.sessionId)?.id;
15
17
  const execution = input.runtime.executionSnapshot(executionId);
16
18
  const prepared = input.coordinator.pending(executionId);
17
19
  const run = execution && input.runtime.getRun(execution.runId);
18
- if (!execution ||
20
+ if (!sessionId ||
21
+ !execution ||
19
22
  !prepared ||
20
23
  !run ||
21
24
  execution.sessionId !== sessionId ||
@@ -49,21 +52,6 @@ export function executionAgentActor(input) {
49
52
  }
50
53
  /** Shared command dispatch; callers must bind actor authority at their trusted boundary. */
51
54
  export async function invokeAgentNetwork(service, actor, action, payload, context, requestSignal) {
52
- const targets = typeof payload.agentId === 'string'
53
- ? [payload.agentId]
54
- : Array.isArray(payload.agentIds)
55
- ? payload.agentIds.filter((id) => typeof id === 'string')
56
- : [];
57
- const operations = action === 'send'
58
- ? ['send', 'goal']
59
- : action === 'stop'
60
- ? ['stop']
61
- : action === 'wait'
62
- ? ['wait']
63
- : [];
64
- const authorized = context
65
- ? actorForApprovedAgentCall(actor, context, targets, operations)
66
- : actor;
67
55
  const signal = context?.signal ?? requestSignal;
68
56
  switch (action) {
69
57
  case 'start':
@@ -82,11 +70,11 @@ export async function invokeAgentNetwork(service, actor, action, payload, contex
82
70
  ...(typeof payload.title === 'string' ? { title: payload.title.trim() } : {})
83
71
  }, signal);
84
72
  case 'send':
85
- return service.send(authorized, payload, signal);
73
+ return service.send(actor, payload, signal);
86
74
  case 'stop':
87
- return service.stop(authorized, payload);
75
+ return service.stop(actor, payload);
88
76
  case 'wait':
89
- return service.wait(authorized, {
77
+ return service.wait(actor, {
90
78
  ...payload,
91
79
  ...(context
92
80
  ? { signal: context.signal, interrupt: context.waitInterrupt }
@@ -32,6 +32,7 @@ export function agentObservationFromRunEvent(input) {
32
32
  /** Volatile, session-owned delivery of live direct-child observations. */
33
33
  export class AgentNetworkObservations {
34
34
  #parents = new Map();
35
+ #waiters = new Map();
35
36
  attach(parentId) {
36
37
  if (this.#parents.has(parentId))
37
38
  throw new Error('AGENT_NETWORK_PARENT_ALREADY_ATTACHED');
@@ -49,11 +50,21 @@ export class AgentNetworkObservations {
49
50
  publish(input) {
50
51
  this.#parents.get(input.parentId)?.publish(input);
51
52
  }
52
- /** Direct children are automatic; other Sessions are watched only when explicitly targeted. */
53
+ /** Call-local observations never enter a Session's automatic context or wake channel. */
54
+ watchExecutions(targets) {
55
+ const observations = new ParentObservations(() => this.#waiters.delete(observations));
56
+ this.#waiters.set(observations, targets);
57
+ return observations;
58
+ }
59
+ /** Only direct children are automatic; other targets reach active, execution-scoped waits. */
53
60
  publishChild(input) {
54
- for (const [parentId, parent] of this.#parents) {
55
- if (parentId === input.parentId || parent.observes(input.childId))
56
- parent.publish(input);
61
+ if (input.parentId != null)
62
+ this.#parents.get(input.parentId)?.publish(input);
63
+ for (const [waiter, targets] of this.#waiters) {
64
+ if (targets.some((target) => target.agentId === input.childId &&
65
+ target.runId === input.runId &&
66
+ target.executionId === input.executionId))
67
+ waiter.publish(input);
57
68
  }
58
69
  }
59
70
  promote(previousId, nextId) {
@@ -74,19 +85,11 @@ export class ParentObservations {
74
85
  #results = new Map();
75
86
  #listeners = new Set();
76
87
  #wakeListeners = new Set();
77
- #targets = new Set();
78
88
  #revision = 0;
79
89
  #closed = false;
80
90
  constructor(detach) {
81
91
  this.detach = detach;
82
92
  }
83
- observe(childId) {
84
- if (!this.#closed)
85
- this.#targets.add(childId);
86
- }
87
- observes(childId) {
88
- return this.#targets.has(childId);
89
- }
90
93
  wake = {
91
94
  pending: () => [...this.#items.values()].some((item) => item.kind === 'message' && item.wakes !== false),
92
95
  subscribe: (listener) => {
@@ -302,7 +305,6 @@ export class ParentObservations {
302
305
  this.#results.clear();
303
306
  this.#listeners.clear();
304
307
  this.#wakeListeners.clear();
305
- this.#targets.clear();
306
308
  this.detach();
307
309
  }
308
310
  }
@@ -41,7 +41,8 @@ export class AgentNetworkService {
41
41
  goal
42
42
  }, signal);
43
43
  try {
44
- this.options.observe(actor, created.session);
44
+ if (created.session.parentSessionId === actor.sessionId)
45
+ this.options.observe(actor, created.session);
45
46
  return { agentId: created.session.id, start: created.result };
46
47
  }
47
48
  catch (error) {
@@ -90,23 +91,8 @@ export class AgentNetworkService {
90
91
  if (input.approval !== undefined)
91
92
  await this.options.validateApproval(actor, session, input.approval);
92
93
  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);
94
+ if (session.parentSessionId === actor.sessionId)
95
+ this.options.observe(actor, session);
110
96
  const approvalResult = input.approval === undefined
111
97
  ? undefined
112
98
  : await this.options.approval(actor, session, input.approval, signal);
@@ -281,22 +267,44 @@ export class AgentNetworkService {
281
267
  if (!targets.length)
282
268
  throw new Error('AGENT_EXECUTION_NOT_FOUND');
283
269
  const selected = new Set(targets.map((target) => target.agentId));
270
+ const directChildren = new Set();
284
271
  for (const session of sessions)
285
- if (selected.has(session.id))
272
+ if (selected.has(session.id) && session.parentSessionId === actor.sessionId) {
286
273
  this.options.observe(actor, session);
287
- const observations = this.options.observations.get(actor.observationId ?? actor.sessionId);
288
- if (!observations)
274
+ directChildren.add(session.id);
275
+ }
276
+ const parent = this.options.observations.get(actor.observationId ?? actor.sessionId);
277
+ if (directChildren.size && !parent)
289
278
  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
- });
279
+ const otherTargets = targets.filter((target) => !directChildren.has(target.agentId));
280
+ const temporary = otherTargets.length && input.waitMs !== 0
281
+ ? this.options.observations.watchExecutions(otherTargets)
282
+ : undefined;
283
+ try {
284
+ return await waitForAgentTargets({
285
+ targets,
286
+ observations: {
287
+ capture: (id, executionId) => (directChildren.has(id) ? parent : temporary)?.capture(id, executionId),
288
+ subscribe: (listener) => {
289
+ const unsubscribeParent = directChildren.size ? parent?.subscribe(listener) : undefined;
290
+ const unsubscribeTemporary = temporary?.subscribe(listener);
291
+ return () => {
292
+ unsubscribeParent?.();
293
+ unsubscribeTemporary?.();
294
+ };
295
+ }
296
+ },
297
+ single: input.agentIds === undefined,
298
+ waitMs: input.waitMs ?? 30_000,
299
+ ...(input.mode === undefined ? {} : { mode: input.mode }),
300
+ ...(input.waitFor === undefined ? {} : { waitFor: input.waitFor }),
301
+ ...(input.signal === undefined ? {} : { signal: input.signal }),
302
+ ...(input.interrupt === undefined ? {} : { interrupt: input.interrupt })
303
+ });
304
+ }
305
+ finally {
306
+ temporary?.close();
307
+ }
300
308
  }
301
309
  }
302
310
  function sameGoal(left, right) {
@@ -180,6 +180,11 @@ export class GoalContinuationCoordinator {
180
180
  };
181
181
  }
182
182
  async start({ session, objective, initiatedByUserId, suppressNotifications, secretEnvironment, personalInstructions, executionContext, usageGrant, preserveActiveRun, messageIntent, messageContent, clientMessageId, messageOrigin, attachments, attachmentIds }) {
183
+ if (session.parentSessionId != null)
184
+ this.options
185
+ .runners()
186
+ .require(session.runner)
187
+ .assertChildModel(session.model, executionContext);
183
188
  this.invalidateGoalContinuation(session.id);
184
189
  const prepared = this.prepareGoalSession(session);
185
190
  const goalSession = prepared.session;
@@ -21,7 +21,7 @@ const executionManagementOperations = new Set([
21
21
  'session.config.update',
22
22
  'session.schedule.agent.save'
23
23
  ]);
24
- /** Prevent execution credentials from reaching legacy writes outside their Session tree. */
24
+ /** Keep execution management within its authenticated Workspace and Run ownership. */
25
25
  export async function authorizeMarctlExecutionOperation(input) {
26
26
  if (!isPlainRecord(input.caller))
27
27
  throw new Error('MARCTL_EXECUTION_CREDENTIAL_INVALID');
@@ -31,19 +31,27 @@ export async function authorizeMarctlExecutionOperation(input) {
31
31
  throw new Error('MARCTL_EXECUTION_CREDENTIAL_INVALID');
32
32
  if (executionReadOperations.has(input.operation))
33
33
  return;
34
- if (!executionManagementOperations.has(input.operation) ||
35
- !isPlainRecord(input.data) ||
36
- typeof input.data.sessionId !== 'string' ||
37
- typeof input.data.workspaceId !== 'string')
34
+ if (!isPlainRecord(input.data) || typeof input.data.workspaceId !== 'string')
38
35
  throw new Error('MARCTL_AGENT_NETWORK_REQUIRED');
36
+ let sessionId;
37
+ if (input.operation === 'run.approval.decide') {
38
+ const run = typeof input.data.runId === 'string' ? input.runtime.getRun(input.data.runId) : undefined;
39
+ if (!run)
40
+ throw new Error('RUN_NOT_FOUND');
41
+ sessionId = run.sessionId;
42
+ }
43
+ else {
44
+ if (!executionManagementOperations.has(input.operation) ||
45
+ typeof input.data.sessionId !== 'string')
46
+ throw new Error('MARCTL_AGENT_NETWORK_REQUIRED');
47
+ sessionId = input.data.sessionId;
48
+ }
39
49
  const source = input.runtime.getAgentSession(input.caller.sessionId);
40
- const target = input.runtime.getAgentSession(input.data.sessionId) ??
41
- (await input.resolveSession(input.data.sessionId));
50
+ const target = input.runtime.getAgentSession(sessionId) ?? (await input.resolveSession(sessionId));
42
51
  if (!source ||
43
52
  !target ||
44
53
  source.workspaceId !== input.data.workspaceId ||
45
- target.workspaceId !== source.workspaceId ||
46
- (target.id !== source.id && target.parentSessionId !== source.id))
54
+ target.workspaceId !== source.workspaceId)
47
55
  throw new Error('AGENT_TARGET_NOT_AUTHORIZED');
48
56
  assertSessionControlOwner(input.runtime, target.id, input.userId);
49
57
  }
@@ -44,10 +44,14 @@ export class RunnerService {
44
44
  async listProfiles(data) {
45
45
  const input = data;
46
46
  const marAgent = this.runners.optional('mar-agent');
47
+ let marAgentProfile;
47
48
  if (marAgent !== undefined &&
48
49
  'configureModels' in marAgent &&
49
50
  typeof marAgent.configureModels === 'function')
50
- marAgent.configureModels(input.marAgentModels);
51
+ marAgentProfile = marAgent.configureModels(input.marAgentModels);
52
+ // Capture the request's display catalog before any asynchronous Runner refresh.
53
+ // This cache is not used for model admission.
54
+ const requestProfiles = (profiles) => profiles.map((profile) => profile.runner === 'mar-agent' && marAgentProfile !== undefined ? marAgentProfile : profile);
51
55
  const environment = parseSecretEnvironment(input.secretEnvironment);
52
56
  const defaults = this.database()
53
57
  .getRunnerDefaults()
@@ -71,12 +75,12 @@ export class RunnerService {
71
75
  if (cacheOnly) {
72
76
  void refresh.catch(() => undefined);
73
77
  return {
74
- profiles: this.profiles(environment),
78
+ profiles: requestProfiles(this.profiles(environment)),
75
79
  defaults
76
80
  };
77
81
  }
78
82
  return {
79
- profiles: await refresh,
83
+ profiles: requestProfiles(await refresh),
80
84
  defaults
81
85
  };
82
86
  }
@@ -1,4 +1,4 @@
1
- import { permissionStopSchema } from '@myagentroam/protocol';
1
+ import { appendExecutionPresentation, permissionStopSchema } from '@myagentroam/protocol';
2
2
  import { projectConversationWorkProcesses } from './work-process.js';
3
3
  function isRecord(value) {
4
4
  return typeof value === 'object' && value !== null;
@@ -293,6 +293,8 @@ export function mergeConversationItemPayload(existing, next) {
293
293
  if (next.append === 'output' &&
294
294
  isRecord(next.payload.output) &&
295
295
  typeof next.payload.output.text === 'string') {
296
+ if (next.payload.toolPresentation !== undefined)
297
+ value.toolPresentation = appendExecutionPresentation(base.toolPresentation, next.payload.toolPresentation);
296
298
  const prior = isRecord(base.output) && typeof base.output.text === 'string' ? base.output : undefined;
297
299
  const text = `${prior?.text ?? ''}${next.payload.output.text}`;
298
300
  value.output = {
@@ -168,7 +168,8 @@ export class SessionLifecycleService {
168
168
  }
169
169
  const environment = parseSecretEnvironment(input.secretEnvironment);
170
170
  const saved = database.getRunnerDefaults().find((value) => value.runner === runner.name);
171
- const fallback = runner.defaultConfiguration(workspace, environment);
171
+ const executionContext = runner.prepareExecutionContext(input);
172
+ const fallback = runner.defaultConfiguration(workspace, environment, executionContext);
172
173
  const configuration = {
173
174
  runner: runner.name,
174
175
  model: typeof input.model === 'string' ? input.model : (saved?.model ?? fallback.model),
@@ -182,7 +183,7 @@ export class SessionLifecycleService {
182
183
  if (runner.name === 'mar-agent' && !['auto', 'full-access'].includes(configuration.access))
183
184
  throw new Error('SESSION_INVALID');
184
185
  if (parentSessionId !== undefined)
185
- runner.assertChildModel(configuration.model);
186
+ runner.assertChildModel(configuration.model, executionContext);
186
187
  const session = this.options.runtime.createAgentSession({
187
188
  ...(parentSessionId === undefined ? {} : { parentSessionId }),
188
189
  workspaceId: input.workspaceId,
@@ -221,8 +222,10 @@ export class SessionLifecycleService {
221
222
  input.access !== undefined &&
222
223
  !['auto', 'full-access'].includes(input.access))
223
224
  throw new Error('SESSION_INVALID');
224
- if (current.parentSessionId != null && typeof input.model === 'string')
225
- this.options.runners.require(current.runner).assertChildModel(input.model);
225
+ if (current.parentSessionId != null && typeof input.model === 'string') {
226
+ const runner = this.options.runners.require(current.runner);
227
+ runner.assertChildModel(input.model, runner.prepareExecutionContext(input));
228
+ }
226
229
  const session = this.options.runtime.updateAgentSessionConfiguration({
227
230
  sessionId: current.id,
228
231
  ...(typeof input.model === 'string' ? { model: input.model } : {}),
@@ -143,10 +143,12 @@ export class SessionMessageService {
143
143
  idempotent: true,
144
144
  auditReceipt: auditReceipt(session, existing.kind === 'queue' ? 'QUEUE' : 'SEND', existing.kind === 'queue' ? existing.item.id : existing.run.id, existing.kind === 'queue' ? existing.item.id : undefined)
145
145
  };
146
- if (session.parentSessionId != null)
147
- this.options.runners.require(session.runner).assertChildModel(session.model);
148
146
  const executionContext = input[RUNNER_EXECUTION_CONTEXT] ??
149
147
  this.options.runners.require(session.runner).prepareExecutionContext(input);
148
+ if (session.parentSessionId != null)
149
+ this.options.runners
150
+ .require(session.runner)
151
+ .assertChildModel(session.model, executionContext);
150
152
  const sourceContent = withSessionMessageOrigin(content, input.__marctlOrigin, session.parentSessionId);
151
153
  const goalMessage = internalGoal || internalSchedule || internalRestore
152
154
  ? undefined