@myagentroam/node 0.11.39 → 0.11.41
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/connector.js +176 -21
- package/dist/database.js +35 -11
- package/dist/main.js +12 -1
- package/dist/marctl-guidance.js +18 -27
- 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/conversation-normalizer.js +232 -118
- package/dist/runner/mar-agent/managed-run-controller.js +5 -0
- 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/continuous-run-coordinator.js +24 -1
- 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-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 +5 -5
|
@@ -122,6 +122,7 @@ export class MarAgentRunner extends AbstractRunner {
|
|
|
122
122
|
id: model.id,
|
|
123
123
|
label: model.name,
|
|
124
124
|
supportedEfforts: [...model.reasoningEfforts],
|
|
125
|
+
subagentModel: model.subagentModel === true,
|
|
125
126
|
defaultEffort: resolveMarAgentDefaultReasoningEffort(model.defaultReasoningEffort, model.reasoningEfforts),
|
|
126
127
|
...(index === 0 ? { isDefault: true } : {})
|
|
127
128
|
})),
|
|
@@ -195,6 +196,16 @@ export class MarAgentRunner extends AbstractRunner {
|
|
|
195
196
|
]
|
|
196
197
|
};
|
|
197
198
|
}
|
|
199
|
+
childModels() {
|
|
200
|
+
const eligible = new Set(this.#models
|
|
201
|
+
.filter((model) => model.enabled && model.conversationModel !== false && model.subagentModel === true)
|
|
202
|
+
.map((model) => model.id));
|
|
203
|
+
return this.profileForValidation().models.filter((model) => eligible.has(model.id));
|
|
204
|
+
}
|
|
205
|
+
assertChildModel(modelId) {
|
|
206
|
+
if (!this.childModels().some((model) => model.id === modelId))
|
|
207
|
+
throw new Error('MAR_AGENT_SUBAGENT_MODEL_UNAVAILABLE');
|
|
208
|
+
}
|
|
198
209
|
supportsConfiguration(configuration) {
|
|
199
210
|
if (configuration.runner !== this.name)
|
|
200
211
|
return false;
|
|
@@ -289,6 +300,9 @@ export class MarAgentRunner extends AbstractRunner {
|
|
|
289
300
|
runner: this.name,
|
|
290
301
|
externalSessionId: session.id,
|
|
291
302
|
cwd: session.workspace,
|
|
303
|
+
...(session.source?.parentSessionId === undefined
|
|
304
|
+
? {}
|
|
305
|
+
: { parentSessionId: session.source.parentSessionId }),
|
|
292
306
|
lastActivityAt: Date.parse(session.lastActivityAt)
|
|
293
307
|
})
|
|
294
308
|
]
|
|
@@ -311,6 +325,9 @@ export class MarAgentRunner extends AbstractRunner {
|
|
|
311
325
|
runner: this.name,
|
|
312
326
|
externalSessionId: session.id,
|
|
313
327
|
cwd: session.workspace,
|
|
328
|
+
...(session.source?.parentSessionId === undefined
|
|
329
|
+
? {}
|
|
330
|
+
: { parentSessionId: session.source.parentSessionId }),
|
|
314
331
|
...title,
|
|
315
332
|
lastActivityAt: Date.parse(session.lastActivityAt)
|
|
316
333
|
}),
|
|
@@ -406,6 +423,25 @@ export class MarAgentRunner extends AbstractRunner {
|
|
|
406
423
|
await catalog.close().catch(() => undefined);
|
|
407
424
|
}
|
|
408
425
|
}
|
|
426
|
+
supportsNativeBatchRemoval() {
|
|
427
|
+
return true;
|
|
428
|
+
}
|
|
429
|
+
async removeNativeMembers(sessions) {
|
|
430
|
+
const ids = sessions.map((session) => {
|
|
431
|
+
if (session.runner !== this.name || session.externalSessionId === null)
|
|
432
|
+
throw new Error('NATIVE_TRANSCRIPT_UNAVAILABLE');
|
|
433
|
+
return session.externalSessionId;
|
|
434
|
+
});
|
|
435
|
+
for (const id of ids)
|
|
436
|
+
await this.#releaseSessionResources?.(id);
|
|
437
|
+
const catalog = await openAgentSessionCatalog({ homeDirectory: this.homeDirectory });
|
|
438
|
+
try {
|
|
439
|
+
await catalog.deleteMembers(ids);
|
|
440
|
+
}
|
|
441
|
+
finally {
|
|
442
|
+
await catalog.close().catch(() => undefined);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
409
445
|
async readManagedRunnerTitle(session, nativeSessionId) {
|
|
410
446
|
if (session.runner !== this.name || session.externalSessionId !== nativeSessionId)
|
|
411
447
|
return undefined;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { compactRunnerText, compactRunnerValue } from '../normalization.js';
|
|
2
|
+
import { publicOutputSnapshot, shellExecution } from '../execution-presentation.js';
|
|
2
3
|
export function normalizeOpenCodeTool(toolName, state) {
|
|
3
4
|
const status = state?.status === 'completed'
|
|
4
5
|
? 'COMPLETED'
|
|
@@ -23,7 +24,11 @@ export function normalizeOpenCodeTool(toolName, state) {
|
|
|
23
24
|
outputRef: null,
|
|
24
25
|
exitCode: typeof state?.exitCode === 'number' ? state.exitCode : null,
|
|
25
26
|
durationMs: null,
|
|
26
|
-
truncated:
|
|
27
|
+
truncated: typeof state?.output === 'string' && state.output.length > 10_000,
|
|
28
|
+
...(typeof state?.output === 'string'
|
|
29
|
+
? { output: publicOutputSnapshot(state.output) }
|
|
30
|
+
: {}),
|
|
31
|
+
toolPresentation: shellExecution(typeof state?.output === 'string' ? state.output : undefined)
|
|
27
32
|
}
|
|
28
33
|
};
|
|
29
34
|
return {
|
package/dist/runtime-state.js
CHANGED
|
@@ -872,6 +872,63 @@ export class NodeRuntimeState {
|
|
|
872
872
|
return executionId;
|
|
873
873
|
return undefined;
|
|
874
874
|
}
|
|
875
|
+
/** Read the identity and status of one Execution without following a later turn. */
|
|
876
|
+
executionSnapshot(executionId) {
|
|
877
|
+
const execution = this.executions.get(executionId);
|
|
878
|
+
if (execution !== undefined) {
|
|
879
|
+
const run = this.getRun(execution.runId);
|
|
880
|
+
return run === undefined
|
|
881
|
+
? undefined
|
|
882
|
+
: {
|
|
883
|
+
executionId,
|
|
884
|
+
runId: run.id,
|
|
885
|
+
sessionId: run.sessionId,
|
|
886
|
+
status: execution.status
|
|
887
|
+
};
|
|
888
|
+
}
|
|
889
|
+
const retained = this.retainedExecutionTurns.get(executionId);
|
|
890
|
+
if (retained === undefined)
|
|
891
|
+
return undefined;
|
|
892
|
+
const turn = this.turns.get(retained.sessionId)?.find((value) => value.id === retained.turnId);
|
|
893
|
+
return turn?.runId == null
|
|
894
|
+
? undefined
|
|
895
|
+
: {
|
|
896
|
+
executionId,
|
|
897
|
+
runId: turn.runId,
|
|
898
|
+
sessionId: retained.sessionId,
|
|
899
|
+
status: turn.status
|
|
900
|
+
};
|
|
901
|
+
}
|
|
902
|
+
latestRunExecution(runId) {
|
|
903
|
+
const turn = this.conversationTurnForRun(runId);
|
|
904
|
+
if (turn === undefined)
|
|
905
|
+
return undefined;
|
|
906
|
+
for (const execution of this.executions.values())
|
|
907
|
+
if (execution.runId === runId && execution.turnId === turn.id)
|
|
908
|
+
return this.executionSnapshot(execution.id);
|
|
909
|
+
for (const [executionId, retained] of this.retainedExecutionTurns)
|
|
910
|
+
if (retained.sessionId === turn.sessionId && retained.turnId === turn.id)
|
|
911
|
+
return this.executionSnapshot(executionId);
|
|
912
|
+
return undefined;
|
|
913
|
+
}
|
|
914
|
+
/** Resolve the current or most recent retained Execution of a Session. */
|
|
915
|
+
latestSessionExecution(sessionId) {
|
|
916
|
+
const active = this.activeSessionRun(sessionId);
|
|
917
|
+
if (active !== undefined)
|
|
918
|
+
return this.latestRunExecution(active.id);
|
|
919
|
+
const turns = this.turns.get(sessionId) ?? [];
|
|
920
|
+
for (let index = turns.length - 1; index >= 0; index -= 1) {
|
|
921
|
+
const turn = turns[index];
|
|
922
|
+
const live = [...this.executions.values()].find((value) => value.turnId === turn.id);
|
|
923
|
+
if (live !== undefined)
|
|
924
|
+
return this.executionSnapshot(live.id);
|
|
925
|
+
for (const [executionId, retained] of this.retainedExecutionTurns) {
|
|
926
|
+
if (retained.sessionId === sessionId && retained.turnId === turn.id)
|
|
927
|
+
return this.executionSnapshot(executionId);
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
return undefined;
|
|
931
|
+
}
|
|
875
932
|
createExecution(runId) {
|
|
876
933
|
const canonicalRunId = this.canonicalRunId(runId);
|
|
877
934
|
const turn = this.currentTurnForRun(canonicalRunId);
|
|
@@ -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
|
+
}
|