@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.
- package/dist/computer-use/contracts.js +8 -0
- package/dist/{runner/mar-agent/computer-use-indicator.js → computer-use/indicator.js} +61 -38
- package/dist/computer-use/probe.js +95 -0
- package/dist/computer-use/provider.js +159 -0
- package/dist/computer-use/session.js +334 -0
- package/dist/computer-use/worker-source.js +135 -0
- package/dist/computer-use/worker.js +321 -0
- package/dist/connector.js +181 -22
- package/dist/database.js +35 -11
- package/dist/main.js +12 -1
- package/dist/marctl-guidance.js +18 -27
- package/dist/mcp/naming.js +6 -1
- package/dist/migrations/v022.js +58 -0
- package/dist/runner/abstract-runner.js +14 -0
- package/dist/runner/claude/tool-normalizer.js +6 -1
- package/dist/runner/codex/conversation-parser.js +23 -4
- package/dist/runner/codex/managed-run-controller.js +4 -2
- package/dist/runner/codex/tool-normalizer.js +9 -0
- package/dist/runner/execution-presentation.js +56 -0
- package/dist/runner/mar-agent/agent-network-extension.js +168 -0
- package/dist/runner/mar-agent/agent-network-wait-output.js +72 -0
- package/dist/runner/mar-agent/computer-use-skill.js +11 -2
- package/dist/runner/mar-agent/computer-use.js +104 -728
- package/dist/runner/mar-agent/conversation-normalizer.js +241 -121
- package/dist/runner/mar-agent/managed-run-controller.js +13 -5
- package/dist/runner/mar-agent/marctl-skill.js +3 -3
- package/dist/runner/mar-agent/runtime-model-factory.js +1 -0
- package/dist/runner/mar-agent/session-runtime.js +34 -1
- package/dist/runner/mar-agent-runner.js +36 -0
- package/dist/runner/opencode/tool-normalizer.js +6 -1
- package/dist/runtime-state.js +57 -0
- package/dist/service/agent-network-bindings.js +483 -0
- package/dist/service/agent-network-goal.js +19 -0
- package/dist/service/agent-network-invoke.js +179 -0
- package/dist/service/agent-network-observations.js +377 -0
- package/dist/service/agent-network-service.js +315 -0
- package/dist/service/agent-network-wait.js +130 -0
- package/dist/service/composer-message-input.js +9 -2
- package/dist/service/computer-use-enablement-service.js +13 -7
- package/dist/service/continuous-run-coordinator.js +24 -1
- package/dist/service/conversation-hosting-enablement-service.js +2 -2
- package/dist/service/goal-continuation-coordinator.js +12 -2
- package/dist/service/marctl-caller.js +80 -0
- package/dist/service/marctl-execution-credential-control.js +65 -0
- package/dist/service/model-usage-service.js +9 -0
- package/dist/service/node-config-mutations.js +27 -0
- package/dist/service/node-control-message-service.js +10 -0
- package/dist/service/node-request-service.js +52 -7
- package/dist/service/node-upgrade-coordinator.js +79 -8
- package/dist/service/node-upgrade-credential-control.js +57 -0
- package/dist/service/node-upgrade-reporter.js +108 -0
- package/dist/service/run-event-service.js +8 -0
- package/dist/service/run-workbench-service.js +11 -0
- package/dist/service/supervised-node-child.js +153 -0
- package/dist/service/supervisor-upgrade-handoff.js +104 -0
- package/dist/session/native-session-projection-service.js +69 -6
- package/dist/session/operation-admission.js +7 -0
- package/dist/session/runtime/conversation-projection.js +25 -3
- package/dist/session/session-catalog-service.js +156 -32
- package/dist/session/session-lifecycle-service.js +68 -42
- package/dist/session/session-message-service.js +16 -6
- package/dist/supervisor.js +209 -20
- package/dist/terminal.js +41 -21
- package/package.json +6 -5
- /package/dist/{runner/mar-agent/computer-use-probe.js → computer-use/probe-source.js} +0 -0
|
@@ -0,0 +1,483 @@
|
|
|
1
|
+
import { auditConversationReceiptSchema, marAgentRuntimeModelConfigurationSchema } from '@myagentroam/protocol';
|
|
2
|
+
import { AgentNetworkService } from './agent-network-service.js';
|
|
3
|
+
import { isPlainRecord } from '../util/records.js';
|
|
4
|
+
import { safeErrorCode } from '../util/safe-error.js';
|
|
5
|
+
import { withSessionMessageOrigin } from './composer-message-input.js';
|
|
6
|
+
import { SESSION_ADMISSION_SIGNAL } from '../session/operation-admission.js';
|
|
7
|
+
import { assertSessionControlOwner } from './run-ownership-policy.js';
|
|
8
|
+
const approvedCalls = new WeakMap();
|
|
9
|
+
const terminalCalls = new WeakMap();
|
|
10
|
+
/** Bind a terminal actor only after the Node request boundary has authenticated its caller. */
|
|
11
|
+
export function actorForAuthenticatedTerminal(actor, workspaceId) {
|
|
12
|
+
const authenticated = Object.freeze({ ...actor });
|
|
13
|
+
terminalCalls.set(authenticated, { workspaceId });
|
|
14
|
+
return authenticated;
|
|
15
|
+
}
|
|
16
|
+
/** Bind a Host-approved invocation to its exact target and domain operations. */
|
|
17
|
+
export function actorForApprovedAgentCall(actor, context, agentIds, operations) {
|
|
18
|
+
if (context.permission?.decision !== 'approved')
|
|
19
|
+
return actor;
|
|
20
|
+
const approved = Object.freeze({ ...actor });
|
|
21
|
+
approvedCalls.set(approved, { agentIds: [...agentIds], operations: [...operations] });
|
|
22
|
+
return approved;
|
|
23
|
+
}
|
|
24
|
+
import { sessionPage } from '../session/pagination.js';
|
|
25
|
+
import { marctlSessionSummary, marctlSessionDetail } from './session-recent-activity.js';
|
|
26
|
+
/** Bind the collaboration use case to the existing Session, Run and Goal owners. */
|
|
27
|
+
export function createNodeAgentNetwork(options) {
|
|
28
|
+
async function actorSession(actor) {
|
|
29
|
+
if (!Number.isSafeInteger(actor.requestUserId) || (actor.requestUserId ?? 0) <= 0)
|
|
30
|
+
throw new Error('USER_AUTHORIZATION_REQUIRED');
|
|
31
|
+
const parent = options.runtime.getAgentSession(actor.sessionId) ??
|
|
32
|
+
(await options.projection.resolve(actor.sessionId, true));
|
|
33
|
+
if (!parent)
|
|
34
|
+
throw new Error('SESSION_NOT_FOUND');
|
|
35
|
+
if (actor.runId !== undefined) {
|
|
36
|
+
const run = options.runtime.getRun(actor.runId);
|
|
37
|
+
if (!run ||
|
|
38
|
+
run.sessionId !== parent.id ||
|
|
39
|
+
options.runtime.activeSessionRun(parent.id)?.id !== run.id ||
|
|
40
|
+
(actor.executionId !== undefined &&
|
|
41
|
+
options.runtime.currentExecutionId(run.id) !== actor.executionId) ||
|
|
42
|
+
run.initiatedByUserId !== actor.requestUserId)
|
|
43
|
+
throw new Error('AGENT_ACTOR_INVALID');
|
|
44
|
+
}
|
|
45
|
+
return parent;
|
|
46
|
+
}
|
|
47
|
+
async function target(actor, id) {
|
|
48
|
+
const terminal = terminalCalls.get(actor);
|
|
49
|
+
const parent = terminal ? undefined : await actorSession(actor);
|
|
50
|
+
const workspaceId = terminal?.workspaceId ?? parent?.workspaceId;
|
|
51
|
+
const session = options.runtime.getAgentSession(id) ?? (await options.projection.resolve(id, true));
|
|
52
|
+
if (!session || session.workspaceId !== workspaceId || session.id === parent?.id)
|
|
53
|
+
throw new Error('SESSION_NOT_FOUND');
|
|
54
|
+
return session;
|
|
55
|
+
}
|
|
56
|
+
function origin(parent) {
|
|
57
|
+
return {
|
|
58
|
+
sessionId: parent.id,
|
|
59
|
+
name: parent.customTitle?.trim() || parent.runnerTitle?.trim() || parent.id
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function childEnvironment(actor) {
|
|
63
|
+
// Retained only inside Node; dispatch exchanges its delegation before creating the child process.
|
|
64
|
+
return actor.secretEnvironment ?? {};
|
|
65
|
+
}
|
|
66
|
+
function executionInput(actor, runner, model, requireSubagent = true) {
|
|
67
|
+
const result = {
|
|
68
|
+
...actor.runnerConfiguration,
|
|
69
|
+
secretEnvironment: childEnvironment(actor),
|
|
70
|
+
...(actor.personalInstructions === undefined
|
|
71
|
+
? {}
|
|
72
|
+
: { personalInstructions: actor.personalInstructions }),
|
|
73
|
+
...(actor.requestUserId === undefined ? {} : { __requestUserId: actor.requestUserId }),
|
|
74
|
+
...(actor.usageGrant === undefined ? {} : { __usageGrant: actor.usageGrant })
|
|
75
|
+
};
|
|
76
|
+
if (runner === 'mar-agent') {
|
|
77
|
+
const candidates = [
|
|
78
|
+
result.marAgentModel,
|
|
79
|
+
...(Array.isArray(result.marAgentModels) ? result.marAgentModels : [])
|
|
80
|
+
];
|
|
81
|
+
const selected = candidates.flatMap((value) => {
|
|
82
|
+
const parsed = marAgentRuntimeModelConfigurationSchema.safeParse(value);
|
|
83
|
+
return parsed.success && parsed.data.id === model ? [parsed.data] : [];
|
|
84
|
+
})[0];
|
|
85
|
+
if (!selected ||
|
|
86
|
+
!selected.enabled ||
|
|
87
|
+
!selected.conversationModel ||
|
|
88
|
+
(requireSubagent && !selected.subagentModel))
|
|
89
|
+
throw new Error('MAR_AGENT_SUBAGENT_MODEL_UNAVAILABLE');
|
|
90
|
+
result.marAgentModel = selected;
|
|
91
|
+
}
|
|
92
|
+
return result;
|
|
93
|
+
}
|
|
94
|
+
async function validateApproval(actor, session, input) {
|
|
95
|
+
if (!terminalCalls.has(actor)) {
|
|
96
|
+
const parent = await actorSession(actor);
|
|
97
|
+
if (session.parentSessionId !== parent.id)
|
|
98
|
+
throw new Error('AGENT_TARGET_NOT_AUTHORIZED');
|
|
99
|
+
}
|
|
100
|
+
assertSessionControlOwner(options.runtime, session.id, actor.requestUserId);
|
|
101
|
+
const approval = options.runtime.getApproval(input.approvalId);
|
|
102
|
+
if (!approval)
|
|
103
|
+
throw new Error('APPROVAL_NOT_FOUND');
|
|
104
|
+
const runId = options.runtime.canonicalRunId(approval.runId);
|
|
105
|
+
const run = options.runtime.getRun(runId);
|
|
106
|
+
const active = options.runtime.activeSessionRun(session.id);
|
|
107
|
+
if (!run ||
|
|
108
|
+
run.sessionId !== session.id ||
|
|
109
|
+
active?.id !== runId ||
|
|
110
|
+
(approval.runId !== runId && approval.runId !== options.runtime.currentExecutionId(runId)))
|
|
111
|
+
throw new Error('APPROVAL_NOT_FOUND');
|
|
112
|
+
if (approval.status === 'EXPIRED')
|
|
113
|
+
throw new Error('APPROVAL_EXPIRED');
|
|
114
|
+
if (approval.status !== 'PENDING')
|
|
115
|
+
throw new Error('APPROVAL_ALREADY_DECIDED');
|
|
116
|
+
}
|
|
117
|
+
return new AgentNetworkService({
|
|
118
|
+
observations: options.observations,
|
|
119
|
+
target,
|
|
120
|
+
authorize: async (actor, session, operation) => {
|
|
121
|
+
assertSessionControlOwner(options.runtime, session.id, actor.requestUserId);
|
|
122
|
+
if (terminalCalls.has(actor))
|
|
123
|
+
return;
|
|
124
|
+
const source = await actorSession(actor);
|
|
125
|
+
if (session.parentSessionId === source.id)
|
|
126
|
+
return;
|
|
127
|
+
if (source.parentSessionId === session.id && operation === 'send')
|
|
128
|
+
return;
|
|
129
|
+
const approved = approvedCalls.get(actor);
|
|
130
|
+
if (approved?.agentIds.includes(session.id) && approved.operations.includes(operation))
|
|
131
|
+
return;
|
|
132
|
+
throw new Error('AGENT_TARGET_NOT_AUTHORIZED');
|
|
133
|
+
},
|
|
134
|
+
children: async (actor) => {
|
|
135
|
+
if (terminalCalls.has(actor))
|
|
136
|
+
throw new Error('AGENT_TARGET_REQUIRED');
|
|
137
|
+
const parent = await actorSession(actor);
|
|
138
|
+
const records = options.database()?.listChildSessionRecords(parent.workspaceId, parent.id) ?? [];
|
|
139
|
+
const resolved = await Promise.all(records.map((record) => options.runtime.getAgentSession(record.id) ??
|
|
140
|
+
options.projection.resolve(record.id, true)));
|
|
141
|
+
return resolved.filter((session) => session !== undefined);
|
|
142
|
+
},
|
|
143
|
+
observe: (actor, session) => options.ensureObservations(actor).observe(session.id),
|
|
144
|
+
isUpward: async (actor, session) => !terminalCalls.has(actor) && (await actorSession(actor)).parentSessionId === session.id,
|
|
145
|
+
reportUpward: async (actor, session, input, signal) => {
|
|
146
|
+
const child = await actorSession(actor);
|
|
147
|
+
const runId = actor.runId;
|
|
148
|
+
const executionId = actor.executionId;
|
|
149
|
+
if (!runId ||
|
|
150
|
+
!executionId ||
|
|
151
|
+
options.runtime.currentExecutionId(runId) !== executionId ||
|
|
152
|
+
child.parentSessionId !== session.id)
|
|
153
|
+
throw new Error('AGENT_ACTOR_NOT_ACTIVE');
|
|
154
|
+
signal?.throwIfAborted();
|
|
155
|
+
const parent = options.observations.get(session.id);
|
|
156
|
+
if (!parent?.report({
|
|
157
|
+
childId: child.id,
|
|
158
|
+
runId,
|
|
159
|
+
executionId,
|
|
160
|
+
value: withSessionMessageOrigin(input.content, origin(child), session.parentSessionId)
|
|
161
|
+
}))
|
|
162
|
+
throw new Error('AGENT_PARENT_NOT_WARM');
|
|
163
|
+
return { delivery: 'reported', clientMessageId: input.clientMessageId };
|
|
164
|
+
},
|
|
165
|
+
freezeExecution: (session) => {
|
|
166
|
+
const snapshot = options.runtime.latestSessionExecution(session.id);
|
|
167
|
+
const run = options.runtime.activeSessionRun(session.id);
|
|
168
|
+
const runId = snapshot?.runId ?? run?.id;
|
|
169
|
+
if (!runId)
|
|
170
|
+
return undefined;
|
|
171
|
+
let executionId = snapshot?.executionId;
|
|
172
|
+
const currentId = () => {
|
|
173
|
+
executionId ??= options.runtime.latestRunExecution(runId)?.executionId;
|
|
174
|
+
return executionId;
|
|
175
|
+
};
|
|
176
|
+
return {
|
|
177
|
+
agentId: session.id,
|
|
178
|
+
runId,
|
|
179
|
+
get executionId() {
|
|
180
|
+
return currentId();
|
|
181
|
+
},
|
|
182
|
+
read: () => {
|
|
183
|
+
const id = currentId();
|
|
184
|
+
return id === undefined
|
|
185
|
+
? (options.runtime.getRun(runId) ?? { status: 'UNKNOWN' })
|
|
186
|
+
: (options.runtime.executionSnapshot(id) ?? { status: 'UNKNOWN' });
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
},
|
|
190
|
+
start: async (actor, input, signal) => {
|
|
191
|
+
const terminal = terminalCalls.get(actor);
|
|
192
|
+
const parent = terminal
|
|
193
|
+
? input.parentSessionId === undefined
|
|
194
|
+
? undefined
|
|
195
|
+
: await target(actor, input.parentSessionId)
|
|
196
|
+
: await actorSession(actor);
|
|
197
|
+
if (!terminal && input.parentSessionId !== undefined && input.parentSessionId !== parent?.id)
|
|
198
|
+
throw new Error('AGENT_TARGET_NOT_AUTHORIZED');
|
|
199
|
+
signal?.throwIfAborted();
|
|
200
|
+
options.ensureObservations(actor);
|
|
201
|
+
const runner = input.runner ?? parent?.runner;
|
|
202
|
+
if (!runner)
|
|
203
|
+
throw new Error('RUNNER_REQUIRED');
|
|
204
|
+
const runnerAdapter = options.runners.product(runner);
|
|
205
|
+
const database = options.database();
|
|
206
|
+
const workspaceId = terminal?.workspaceId ?? parent?.workspaceId;
|
|
207
|
+
const workspace = workspaceId === undefined ? undefined : database?.getWorkspace(workspaceId);
|
|
208
|
+
if (!runnerAdapter || !database || !workspace)
|
|
209
|
+
throw new Error('SESSION_INVALID');
|
|
210
|
+
const model = input.model ??
|
|
211
|
+
database.getRunnerDefaults().find((entry) => entry.runner === runner)?.model ??
|
|
212
|
+
runnerAdapter.defaultConfiguration(workspace, actor.secretEnvironment ?? {}).model;
|
|
213
|
+
const request = {
|
|
214
|
+
...executionInput(actor, runner, model, parent !== undefined),
|
|
215
|
+
workspaceId: workspace.id,
|
|
216
|
+
...(signal ? { [SESSION_ADMISSION_SIGNAL]: signal } : {}),
|
|
217
|
+
...(parent ? { parentSessionId: parent.id } : {}),
|
|
218
|
+
runner,
|
|
219
|
+
cwd: parent?.cwd ?? workspace.path,
|
|
220
|
+
content: input.content,
|
|
221
|
+
clientMessageId: input.clientMessageId,
|
|
222
|
+
...(!terminal && parent ? { __marctlOrigin: origin(parent) } : {}),
|
|
223
|
+
...(input.title === undefined ? {} : { title: input.title }),
|
|
224
|
+
...(model ? { model } : {}),
|
|
225
|
+
...(input.effort === undefined ? {} : { effort: input.effort }),
|
|
226
|
+
...(input.attachments === undefined ? {} : { attachments: input.attachments }),
|
|
227
|
+
...(input.attachmentIds === undefined ? {} : { attachmentIds: input.attachmentIds })
|
|
228
|
+
};
|
|
229
|
+
signal?.throwIfAborted();
|
|
230
|
+
const result = await options.withGrant(actor, () => input.goal.kind === 'set'
|
|
231
|
+
? options.lifecycle.startAgentWithGoal(request, (session, executionContext) => options.coordinator.start({
|
|
232
|
+
session,
|
|
233
|
+
executionContext,
|
|
234
|
+
objective: input.goal.kind === 'set' ? input.goal.objective : '',
|
|
235
|
+
secretEnvironment: childEnvironment(actor),
|
|
236
|
+
messageContent: input.content,
|
|
237
|
+
...(!terminal && parent ? { messageOrigin: origin(parent) } : {}),
|
|
238
|
+
clientMessageId: input.clientMessageId,
|
|
239
|
+
...(input.attachments === undefined ? {} : { attachments: input.attachments }),
|
|
240
|
+
...(input.attachmentIds === undefined
|
|
241
|
+
? {}
|
|
242
|
+
: { attachmentIds: input.attachmentIds }),
|
|
243
|
+
...(actor.requestUserId === undefined
|
|
244
|
+
? {}
|
|
245
|
+
: { initiatedByUserId: actor.requestUserId }),
|
|
246
|
+
...(actor.usageGrant === undefined ? {} : { usageGrant: actor.usageGrant }),
|
|
247
|
+
...(actor.personalInstructions === undefined
|
|
248
|
+
? {}
|
|
249
|
+
: { personalInstructions: actor.personalInstructions })
|
|
250
|
+
}))
|
|
251
|
+
: options.lifecycle.operations()['session.start'](request));
|
|
252
|
+
if (!isPlainRecord(result) ||
|
|
253
|
+
!isPlainRecord(result.session) ||
|
|
254
|
+
typeof result.session.id !== 'string')
|
|
255
|
+
throw new Error('SESSION_START_INVALID_RESULT');
|
|
256
|
+
const session = await target(actor, result.session.id);
|
|
257
|
+
return {
|
|
258
|
+
session,
|
|
259
|
+
result: { ...publicReceipt(result), clientMessageId: input.clientMessageId }
|
|
260
|
+
};
|
|
261
|
+
},
|
|
262
|
+
setGoal: async (actor, session, change, message, signal) => {
|
|
263
|
+
const parent = terminalCalls.has(actor) ? undefined : await actorSession(actor);
|
|
264
|
+
signal?.throwIfAborted();
|
|
265
|
+
if (change.kind === 'clear')
|
|
266
|
+
return publicReceipt(options.coordinator.clear(session));
|
|
267
|
+
const current = await options.resume.resolveForMessage(session.id);
|
|
268
|
+
const executionContext = options.runners
|
|
269
|
+
.require(current.runner)
|
|
270
|
+
.prepareExecutionContext(executionInput(actor, current.runner, current.model, current.parentSessionId != null));
|
|
271
|
+
signal?.throwIfAborted();
|
|
272
|
+
const result = await options.withGrant(actor, () => options.coordinator.start({
|
|
273
|
+
session: current,
|
|
274
|
+
objective: change.objective,
|
|
275
|
+
executionContext,
|
|
276
|
+
secretEnvironment: childEnvironment(actor),
|
|
277
|
+
...(actor.requestUserId === undefined ? {} : { initiatedByUserId: actor.requestUserId }),
|
|
278
|
+
...(actor.personalInstructions === undefined
|
|
279
|
+
? {}
|
|
280
|
+
: { personalInstructions: actor.personalInstructions }),
|
|
281
|
+
...(actor.usageGrant === undefined ? {} : { usageGrant: actor.usageGrant }),
|
|
282
|
+
preserveActiveRun: true,
|
|
283
|
+
...(parent ? { messageOrigin: origin(parent) } : {}),
|
|
284
|
+
...(message === undefined
|
|
285
|
+
? {}
|
|
286
|
+
: {
|
|
287
|
+
messageContent: message.content,
|
|
288
|
+
clientMessageId: message.clientMessageId,
|
|
289
|
+
messageIntent: message.deliveryIntent,
|
|
290
|
+
...(message.attachments === undefined ? {} : { attachments: message.attachments }),
|
|
291
|
+
...(message.attachmentIds === undefined
|
|
292
|
+
? {}
|
|
293
|
+
: { attachmentIds: message.attachmentIds })
|
|
294
|
+
})
|
|
295
|
+
}));
|
|
296
|
+
return publicReceipt(result);
|
|
297
|
+
},
|
|
298
|
+
message: async (actor, session, input, signal) => {
|
|
299
|
+
const parent = terminalCalls.has(actor) ? undefined : await actorSession(actor);
|
|
300
|
+
signal?.throwIfAborted();
|
|
301
|
+
const result = await options.withGrant(actor, () => options.messages.operations()['session.message']({
|
|
302
|
+
...executionInput(actor, session.runner, session.model, session.parentSessionId != null),
|
|
303
|
+
...input,
|
|
304
|
+
...(signal ? { [SESSION_ADMISSION_SIGNAL]: signal } : {}),
|
|
305
|
+
sessionId: session.id,
|
|
306
|
+
...(parent ? { __marctlOrigin: origin(parent) } : {})
|
|
307
|
+
}));
|
|
308
|
+
return publicReceipt(result);
|
|
309
|
+
},
|
|
310
|
+
validateApproval,
|
|
311
|
+
approval: async (actor, session, input, signal) => {
|
|
312
|
+
await validateApproval(actor, session, input);
|
|
313
|
+
signal?.throwIfAborted();
|
|
314
|
+
const approval = options.runtime.getApproval(input.approvalId);
|
|
315
|
+
const runId = options.runtime.canonicalRunId(approval.runId);
|
|
316
|
+
const decision = input.decision === 'approve' ? 'APPROVED' : 'REJECTED';
|
|
317
|
+
try {
|
|
318
|
+
options.runs.operations()['run.approval.decide']({
|
|
319
|
+
runId,
|
|
320
|
+
approvalId: input.approvalId,
|
|
321
|
+
decision,
|
|
322
|
+
__requestUserId: actor.requestUserId
|
|
323
|
+
});
|
|
324
|
+
return {
|
|
325
|
+
status: 'applied',
|
|
326
|
+
result: { approvalId: input.approvalId, decision: input.decision }
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
catch (error) {
|
|
330
|
+
if (approval.status === 'PENDING' &&
|
|
331
|
+
options.runtime.getApproval(input.approvalId)?.status === decision)
|
|
332
|
+
return {
|
|
333
|
+
status: 'partial',
|
|
334
|
+
code: safeErrorCode(error, 'APPROVAL_DECISION_FAILED'),
|
|
335
|
+
result: { approvalId: input.approvalId, decision: input.decision }
|
|
336
|
+
};
|
|
337
|
+
throw error;
|
|
338
|
+
}
|
|
339
|
+
},
|
|
340
|
+
stop: (actor, session, executionId) => publicReceipt(options.runs.stopAgentExecution({
|
|
341
|
+
sessionId: session.id,
|
|
342
|
+
executionId,
|
|
343
|
+
...(actor.requestUserId === undefined ? {} : { requestUserId: actor.requestUserId })
|
|
344
|
+
})),
|
|
345
|
+
goal: (sessionId) => publicGoal(options.goals.get(sessionId)),
|
|
346
|
+
list: async (actor, input) => {
|
|
347
|
+
const terminal = terminalCalls.get(actor);
|
|
348
|
+
const source = terminal ? undefined : await actorSession(actor);
|
|
349
|
+
const workspaceId = terminal?.workspaceId ?? source?.workspaceId;
|
|
350
|
+
if (!workspaceId)
|
|
351
|
+
throw new Error('SESSION_INVALID');
|
|
352
|
+
const current = source ? marctlSessionSummary(options.runtime, source) : null;
|
|
353
|
+
const scope = input.scope ?? (terminal ? 'workspace' : 'children');
|
|
354
|
+
if (scope === 'profile') {
|
|
355
|
+
const runner = input.runner ?? source?.runner;
|
|
356
|
+
if (!runner)
|
|
357
|
+
throw new Error('RUNNER_REQUIRED');
|
|
358
|
+
const profiles = await options.profiles(actor.secretEnvironment ?? {}, actor.runnerConfiguration?.marAgentModels);
|
|
359
|
+
const profile = profiles.find((item) => item.runner === runner);
|
|
360
|
+
if (!profile)
|
|
361
|
+
throw new Error('RUNNER_PROFILE_NOT_FOUND');
|
|
362
|
+
const models = profile.runner !== 'mar-agent'
|
|
363
|
+
? profile.models
|
|
364
|
+
: profile.models.filter((model) => {
|
|
365
|
+
const configurations = [
|
|
366
|
+
actor.runnerConfiguration?.marAgentModel,
|
|
367
|
+
...(Array.isArray(actor.runnerConfiguration?.marAgentModels)
|
|
368
|
+
? actor.runnerConfiguration.marAgentModels
|
|
369
|
+
: [])
|
|
370
|
+
];
|
|
371
|
+
return configurations.some((value) => isPlainRecord(value) &&
|
|
372
|
+
value.id === model.id &&
|
|
373
|
+
value.enabled === true &&
|
|
374
|
+
value.conversationModel === true &&
|
|
375
|
+
value.subagentModel === true);
|
|
376
|
+
});
|
|
377
|
+
return { current, runner: profile.runner, models, capabilities: profile.capabilities };
|
|
378
|
+
}
|
|
379
|
+
if (scope === 'workspace') {
|
|
380
|
+
const result = await options.listSessions({
|
|
381
|
+
workspaceId,
|
|
382
|
+
view: 'marctl',
|
|
383
|
+
registeredOnly: true,
|
|
384
|
+
...(input.query === undefined ? {} : { query: input.query }),
|
|
385
|
+
...(input.parent === undefined ? {} : { parentSessionId: input.parent }),
|
|
386
|
+
...(input.limit === undefined ? {} : { limit: input.limit }),
|
|
387
|
+
...(input.cursor === undefined ? {} : { cursor: input.cursor })
|
|
388
|
+
});
|
|
389
|
+
if (!isPlainRecord(result))
|
|
390
|
+
throw new Error('SESSION_LIST_INVALID_RESULT');
|
|
391
|
+
return { ...result, current };
|
|
392
|
+
}
|
|
393
|
+
if (scope !== 'children' && scope !== 'descendants')
|
|
394
|
+
throw new Error('AGENT_LIST_INVALID');
|
|
395
|
+
const root = input.agentId === undefined || input.agentId === source?.id
|
|
396
|
+
? source
|
|
397
|
+
: await target(actor, input.agentId);
|
|
398
|
+
if (!root)
|
|
399
|
+
throw new Error('AGENT_TARGET_REQUIRED');
|
|
400
|
+
const database = options.database();
|
|
401
|
+
if (!database)
|
|
402
|
+
throw new Error('DATABASE_UNAVAILABLE');
|
|
403
|
+
const records = scope === 'children'
|
|
404
|
+
? database.listChildSessionRecords(root.workspaceId, root.id)
|
|
405
|
+
: database.sessionTreeRecords(root.workspaceId, root.id).slice(1);
|
|
406
|
+
const sessions = (await Promise.all(records.map((record) => options.runtime.getAgentSession(record.id) ??
|
|
407
|
+
options.projection.resolve(record.id, true))))
|
|
408
|
+
.filter((session) => session !== undefined)
|
|
409
|
+
.filter((session) => !input.query ||
|
|
410
|
+
(session.customTitle ?? session.runnerTitle ?? '')
|
|
411
|
+
.normalize('NFKC')
|
|
412
|
+
.toLocaleLowerCase()
|
|
413
|
+
.includes(input.query.normalize('NFKC').toLocaleLowerCase()));
|
|
414
|
+
const page = sessionPage(sessions, { limit: input.limit, cursor: input.cursor });
|
|
415
|
+
return {
|
|
416
|
+
current,
|
|
417
|
+
sessions: page.sessions.map((session) => marctlSessionSummary(options.runtime, session)),
|
|
418
|
+
nextCursor: page.nextCursor,
|
|
419
|
+
truncated: page.nextCursor !== null
|
|
420
|
+
};
|
|
421
|
+
},
|
|
422
|
+
view: async (actor, input) => {
|
|
423
|
+
const source = terminalCalls.has(actor) ? undefined : await actorSession(actor);
|
|
424
|
+
const session = input.agentId === source?.id ? source : await target(actor, input.agentId);
|
|
425
|
+
assertSessionControlOwner(options.runtime, session.id, actor.requestUserId);
|
|
426
|
+
return {
|
|
427
|
+
session: marctlSessionDetail(options.runtime, session, options.runtime.sessionQueue(session.id).itemIds.length),
|
|
428
|
+
goal: publicGoal(options.goals.get(session.id)) ?? null,
|
|
429
|
+
...(input.includeHistory
|
|
430
|
+
? {
|
|
431
|
+
history: await options.history({
|
|
432
|
+
sessionId: session.id,
|
|
433
|
+
workspaceId: session.workspaceId,
|
|
434
|
+
registeredOnly: true,
|
|
435
|
+
limit: input.limit,
|
|
436
|
+
cursor: input.cursor
|
|
437
|
+
})
|
|
438
|
+
}
|
|
439
|
+
: {})
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
function publicGoal(goal) {
|
|
445
|
+
return goal === undefined
|
|
446
|
+
? undefined
|
|
447
|
+
: {
|
|
448
|
+
id: goal.id,
|
|
449
|
+
objective: goal.objective,
|
|
450
|
+
status: goal.status,
|
|
451
|
+
revision: goal.revision,
|
|
452
|
+
startedAt: goal.startedAt,
|
|
453
|
+
turns: goal.turns
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
/** Project receipts without leaking Session internals or execution credentials. */
|
|
457
|
+
function publicReceipt(value) {
|
|
458
|
+
if (!isPlainRecord(value))
|
|
459
|
+
return {};
|
|
460
|
+
const result = {};
|
|
461
|
+
const audit = auditConversationReceiptSchema.safeParse(value.auditReceipt);
|
|
462
|
+
if (audit.success)
|
|
463
|
+
result.auditReceipt = audit.data;
|
|
464
|
+
for (const key of ['delivery', 'queueItemId', 'idempotent', 'cleared', 'goalAccessChanged'])
|
|
465
|
+
if (typeof value[key] === 'string' || typeof value[key] === 'boolean')
|
|
466
|
+
result[key] = value[key];
|
|
467
|
+
if (isPlainRecord(value.run) && typeof value.run.id === 'string')
|
|
468
|
+
result.run = { id: value.run.id, status: value.run.status };
|
|
469
|
+
if (isPlainRecord(value.turn) && typeof value.turn.id === 'string')
|
|
470
|
+
result.turn = { id: value.turn.id, status: value.turn.status };
|
|
471
|
+
if (isPlainRecord(value.goal)) {
|
|
472
|
+
const goal = value.goal;
|
|
473
|
+
result.goal = {
|
|
474
|
+
id: goal.id,
|
|
475
|
+
objective: goal.objective,
|
|
476
|
+
status: goal.status,
|
|
477
|
+
revision: goal.revision
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
if (isPlainRecord(value.goalExit))
|
|
481
|
+
result.goalExit = value.goalExit;
|
|
482
|
+
return result;
|
|
483
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { isPlainRecord } from '../util/records.js';
|
|
2
|
+
/** Shared Goal input contract for native tools and CLI. */
|
|
3
|
+
export function parseAgentGoal(input) {
|
|
4
|
+
if (input === undefined || input === null)
|
|
5
|
+
return { kind: 'unchanged' };
|
|
6
|
+
if (!isPlainRecord(input))
|
|
7
|
+
throw new Error('AGENT_GOAL_INVALID');
|
|
8
|
+
const keys = Object.keys(input);
|
|
9
|
+
if (keys.length !== 1)
|
|
10
|
+
throw new Error('AGENT_GOAL_INVALID');
|
|
11
|
+
if (keys[0] === 'clear' && input.clear === true)
|
|
12
|
+
return { kind: 'clear' };
|
|
13
|
+
if (keys[0] === 'objective' && typeof input.objective === 'string') {
|
|
14
|
+
const objective = input.objective.trim();
|
|
15
|
+
if (objective.length > 0 && objective.length <= 20_000)
|
|
16
|
+
return { kind: 'set', objective };
|
|
17
|
+
}
|
|
18
|
+
throw new Error('AGENT_GOAL_INVALID');
|
|
19
|
+
}
|