@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.
- package/dist/application/node-lifecycle.js +30 -7
- package/dist/connector.js +14 -3
- package/dist/hosting/hosted-conversation-node-service.js +1 -2
- package/dist/main.js +10 -3
- package/dist/marctl-guidance.js +3 -1
- package/dist/runner/abstract-runner.js +2 -2
- package/dist/runner/claude/managed-run-controller.js +1 -1
- package/dist/runner/claude/native-transcript.js +1 -1
- package/dist/runner/claude/tool-normalizer.js +21 -12
- package/dist/runner/codex/conversation-parser.js +17 -9
- package/dist/runner/codex/managed-run-controller.js +1 -1
- package/dist/runner/codex/tool-normalizer.js +17 -9
- package/dist/runner/execution-presentation.js +0 -3
- package/dist/runner/mar-agent/agent-network-extension.js +2 -2
- package/dist/runner/mar-agent/conversation-normalizer.js +259 -43
- package/dist/runner/mar-agent/managed-run-controller.js +2 -1
- package/dist/runner/mar-agent/runtime-model-factory.js +2 -4
- package/dist/runner/mar-agent/session-runtime-registry.js +22 -12
- package/dist/runner/mar-agent/session-runtime.js +16 -11
- package/dist/runner/mar-agent-runner.js +32 -8
- package/dist/runner/normalization.js +8 -0
- package/dist/runner/opencode/tool-normalizer.js +8 -4
- package/dist/runtime-state.js +0 -1
- package/dist/service/agent-network-bindings.js +9 -50
- package/dist/service/agent-network-invoke.js +9 -21
- package/dist/service/agent-network-observations.js +15 -13
- package/dist/service/agent-network-service.js +39 -31
- package/dist/service/goal-continuation-coordinator.js +5 -0
- package/dist/service/marctl-caller.js +17 -9
- package/dist/service/runner-service.js +7 -3
- package/dist/session/runtime/conversation-projection.js +3 -1
- package/dist/session/session-lifecycle-service.js +7 -4
- package/dist/session/session-message-service.js +4 -2
- 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
|
-
|
|
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.
|
|
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 {
|
|
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
|
-
: {
|
|
79
|
+
: {
|
|
80
|
+
outputSummary: compactRunnerText(nativeResult.result, 10_000) || null,
|
|
81
|
+
output: publicOutputSnapshot(nativeResult.result)
|
|
82
|
+
})
|
|
79
83
|
};
|
|
80
84
|
}
|
|
81
85
|
function openCodeTaskResult(value) {
|
package/dist/runtime-state.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
|
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
|
|
145
|
-
|
|
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 {
|
|
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 {
|
|
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 (!
|
|
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(
|
|
73
|
+
return service.send(actor, payload, signal);
|
|
86
74
|
case 'stop':
|
|
87
|
-
return service.stop(
|
|
75
|
+
return service.stop(actor, payload);
|
|
88
76
|
case 'wait':
|
|
89
|
-
return service.wait(
|
|
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
|
-
/**
|
|
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
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
-
|
|
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 (
|
|
94
|
-
|
|
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
|
-
|
|
288
|
-
|
|
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
|
-
|
|
291
|
-
|
|
292
|
-
observations
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
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
|
-
/**
|
|
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 (!
|
|
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.
|
|
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
|
|
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)
|
|
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
|