@myagentroam/node 0.11.44 → 0.11.46
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 +4 -4
- package/dist/database.js +7 -5
- package/dist/marctl-guidance.js +2 -2
- package/dist/migrations/v023.js +25 -0
- package/dist/runner/abstract-runner.js +1 -1
- package/dist/runner/claude/managed-run-controller.js +4 -11
- package/dist/runner/claude/native-transcript.js +1 -1
- package/dist/runner/claude/tool-normalizer.js +16 -11
- package/dist/runner/claude-code-runner.js +1 -1
- package/dist/runner/codex/conversation-parser.js +29 -38
- package/dist/runner/codex/managed-run-controller.js +5 -5
- package/dist/runner/codex/native-transcript.js +5 -3
- package/dist/runner/codex/tool-normalizer.js +13 -17
- package/dist/runner/codex-runner.js +10 -5
- package/dist/runner/execution-presentation.js +8 -51
- package/dist/runner/mar-agent/agent-network-extension.js +80 -24
- package/dist/runner/mar-agent/agent-operation-presentation.js +167 -0
- package/dist/runner/mar-agent/conversation-normalizer.js +118 -107
- package/dist/runner/mar-agent/history-projection.js +1 -1
- package/dist/runner/mar-agent/hosted-conversation-runtime.js +1 -1
- package/dist/runner/mar-agent/managed-run-controller.js +2 -2
- package/dist/runner/mar-agent-runner.js +5 -5
- package/dist/runner/normalization.js +5 -0
- package/dist/runner/opencode/managed-run-controller.js +2 -2
- package/dist/runner/opencode/tool-normalizer.js +10 -10
- package/dist/runner/opencode-runner.js +5 -5
- package/dist/runner-profiles.js +1 -1
- package/dist/runtime-state.js +7 -7
- package/dist/service/agent-network-bindings.js +10 -6
- package/dist/service/agent-network-service.js +2 -1
- package/dist/service/continuous-run-coordinator.js +1 -1
- package/dist/service/external-session-resume-service.js +1 -1
- package/dist/service/goal-continuation-coordinator.js +3 -3
- package/dist/service/native-session-watch-service.js +6 -3
- package/dist/service/planned-restart-handoff.js +24 -5
- package/dist/service/restart-session-restorer.js +2 -2
- package/dist/service/session-recent-activity.js +1 -1
- package/dist/session/native-session-projection-service.js +8 -8
- package/dist/session/runtime/conversation-projection.js +10 -3
- package/dist/session/session-catalog-service.js +2 -2
- package/dist/session/session-command-service.js +1 -0
- package/dist/session/session-context-service.js +1 -1
- package/dist/session/session-lifecycle-service.js +12 -10
- package/dist/session/session-message-service.js +4 -3
- package/dist/workspace/git.js +44 -1
- package/dist/workspace/workspace-change-service.js +39 -1
- package/dist/workspace/workspace-session-service.js +1 -1
- package/dist/workspace/workspace-workbench-service.js +8 -0
- package/package.json +4 -4
|
@@ -202,7 +202,7 @@ export class CodexRunner extends AbstractRunner {
|
|
|
202
202
|
? emptyCodexConfiguration()
|
|
203
203
|
: {
|
|
204
204
|
runner: this.name,
|
|
205
|
-
|
|
205
|
+
modelId: selected.model,
|
|
206
206
|
effort: selected.defaultEffort,
|
|
207
207
|
access: 'on-request'
|
|
208
208
|
}
|
|
@@ -361,7 +361,8 @@ export class CodexRunner extends AbstractRunner {
|
|
|
361
361
|
return false;
|
|
362
362
|
const profile = this.profileCacheForEnvironment(environment)?.profile;
|
|
363
363
|
return (profile !== undefined &&
|
|
364
|
-
profile.models.some((model) => model.id === configuration.
|
|
364
|
+
profile.models.some((model) => model.id === configuration.modelId &&
|
|
365
|
+
model.supportedEfforts.includes(configuration.effort)) &&
|
|
365
366
|
profile.accessOptions.some((option) => option.id === configuration.access));
|
|
366
367
|
}
|
|
367
368
|
profileCacheForEnvironment(_environment) {
|
|
@@ -695,7 +696,11 @@ export class CodexRunner extends AbstractRunner {
|
|
|
695
696
|
const threadId = session.externalSessionId;
|
|
696
697
|
if (threadId === null)
|
|
697
698
|
throw new Error('SESSION_REWIND_UNAVAILABLE');
|
|
698
|
-
const configuration = {
|
|
699
|
+
const configuration = {
|
|
700
|
+
model: session.modelId,
|
|
701
|
+
effort: session.effort,
|
|
702
|
+
access: session.access
|
|
703
|
+
};
|
|
699
704
|
return this.withTemporaryClient(async (client) => {
|
|
700
705
|
const result = boundary === null
|
|
701
706
|
? await client.startThread(session.cwd, configuration)
|
|
@@ -719,7 +724,7 @@ export class CodexRunner extends AbstractRunner {
|
|
|
719
724
|
const threadId = session.externalSessionId;
|
|
720
725
|
return this.withTemporaryClient(async (client) => {
|
|
721
726
|
const resumed = await client.resumeThread(threadId, session.cwd, {
|
|
722
|
-
model: session.
|
|
727
|
+
model: session.modelId,
|
|
723
728
|
effort: session.effort,
|
|
724
729
|
access: session.access
|
|
725
730
|
});
|
|
@@ -753,7 +758,7 @@ export class CodexRunner extends AbstractRunner {
|
|
|
753
758
|
}
|
|
754
759
|
}
|
|
755
760
|
function emptyCodexConfiguration() {
|
|
756
|
-
return { runner: 'codex',
|
|
761
|
+
return { runner: 'codex', modelId: '', effort: '', access: 'on-request' };
|
|
757
762
|
}
|
|
758
763
|
function codexModelPage(value) {
|
|
759
764
|
if (!isPlainRecord(value) || !Array.isArray(value.data))
|
|
@@ -1,53 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
function
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
}
|
|
9
|
-
/** Keep public execution output bounded without copying an unbounded source. */
|
|
10
|
-
export function boundExecutionPresentation(execution) {
|
|
11
|
-
let truncated = (execution.output?.length ?? 0) > 4;
|
|
12
|
-
const output = execution.output?.slice(0, 4).map((block) => {
|
|
13
|
-
if (block.text.length > 10_000)
|
|
14
|
-
truncated = true;
|
|
15
|
-
return { ...block, text: tail(block.text, 10_000) };
|
|
16
|
-
});
|
|
17
|
-
const detail = {
|
|
18
|
-
...execution,
|
|
19
|
-
...(output === undefined ? {} : { output }),
|
|
20
|
-
...(execution.truncation === undefined ? {} : { truncation: { ...execution.truncation } })
|
|
21
|
-
};
|
|
22
|
-
if (truncated)
|
|
23
|
-
detail.truncation = { ...detail.truncation, output: detail.truncation?.output ?? 'HEAD' };
|
|
24
|
-
const presentation = { kind: 'EXECUTION', execution: detail };
|
|
25
|
-
const size = () => Buffer.byteLength(JSON.stringify(presentation));
|
|
26
|
-
for (const block of [...(detail.output ?? [])].reverse()) {
|
|
27
|
-
if (size() <= MAX_BYTES)
|
|
28
|
-
break;
|
|
29
|
-
const source = block.text;
|
|
30
|
-
detail.truncation = { ...detail.truncation, output: detail.truncation?.output ?? 'HEAD' };
|
|
31
|
-
truncated = true;
|
|
32
|
-
let low = 0;
|
|
33
|
-
let high = source.length;
|
|
34
|
-
while (low < high) {
|
|
35
|
-
const middle = Math.ceil((low + high) / 2);
|
|
36
|
-
block.text = tail(source, middle);
|
|
37
|
-
if (size() <= MAX_BYTES)
|
|
38
|
-
low = middle;
|
|
39
|
-
else
|
|
40
|
-
high = middle - 1;
|
|
1
|
+
/** Execution metadata is independent of the public output body and its budget. */
|
|
2
|
+
export function shellExecution(inputTruncated = false) {
|
|
3
|
+
return {
|
|
4
|
+
kind: 'EXECUTION',
|
|
5
|
+
execution: {
|
|
6
|
+
kind: 'SHELL',
|
|
7
|
+
...(inputTruncated ? { truncation: { input: true } } : {})
|
|
41
8
|
}
|
|
42
|
-
|
|
43
|
-
}
|
|
44
|
-
return { presentation, truncated };
|
|
45
|
-
}
|
|
46
|
-
/** Project only the stream facts actually supplied by the runner. */
|
|
47
|
-
export function shellExecution(output, sourceTruncated = false) {
|
|
48
|
-
return boundExecutionPresentation({
|
|
49
|
-
kind: 'SHELL',
|
|
50
|
-
...(output === undefined ? {} : { output: [{ channel: 'COMBINED', text: output }] }),
|
|
51
|
-
...(sourceTruncated ? { truncation: { output: 'SOURCE' } } : {})
|
|
52
|
-
}).presentation;
|
|
9
|
+
};
|
|
53
10
|
}
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { agentNetworkWaitModelOutput } from './agent-network-wait-output.js';
|
|
2
|
+
import { agentOperationEvent } from './agent-operation-presentation.js';
|
|
2
3
|
const text = { type: 'string', minLength: 1 };
|
|
4
|
+
const runner = {
|
|
5
|
+
...text,
|
|
6
|
+
description: 'Runner type (for example mar-agent), not a model name; defaults to the current Runner.'
|
|
7
|
+
};
|
|
3
8
|
const attachmentIds = {
|
|
4
9
|
type: 'array',
|
|
5
10
|
items: text,
|
|
@@ -41,8 +46,11 @@ const tools = [
|
|
|
41
46
|
content: { type: 'string' },
|
|
42
47
|
attachments,
|
|
43
48
|
attachmentIds,
|
|
44
|
-
runner
|
|
45
|
-
|
|
49
|
+
runner,
|
|
50
|
+
modelId: {
|
|
51
|
+
...text,
|
|
52
|
+
description: 'Model ID from the target Runner Profile models[].id, not a display name. For MAR Agent this is an internal configuration ID, not an upstream model ID; omit to use the Runner default.'
|
|
53
|
+
},
|
|
46
54
|
effort: text,
|
|
47
55
|
title: text,
|
|
48
56
|
goal,
|
|
@@ -52,7 +60,7 @@ const tools = [
|
|
|
52
60
|
},
|
|
53
61
|
{
|
|
54
62
|
action: 'send',
|
|
55
|
-
description: 'Send input, decide an approval or update a Goal; goal:null leaves it unchanged, goal:{clear:true} clears it.
|
|
63
|
+
description: 'Send input, decide an approval or update a Goal; goal:null leaves it unchanged, goal:{clear:true} clears it. Messages default to REPLACE_CURRENT (steer); use QUEUE only when deferred delivery is explicitly requested. QUEUE is consumed only after current work and earlier queued inputs finish; an active Goal must exit first.',
|
|
56
64
|
properties: {
|
|
57
65
|
agentId: text,
|
|
58
66
|
content: { type: 'string' },
|
|
@@ -65,14 +73,18 @@ const tools = [
|
|
|
65
73
|
required: ['approvalId', 'decision'],
|
|
66
74
|
properties: { approvalId: text, decision: { type: 'string', enum: ['approve', 'reject'] } }
|
|
67
75
|
},
|
|
68
|
-
deliveryIntent: {
|
|
76
|
+
deliveryIntent: {
|
|
77
|
+
type: 'string',
|
|
78
|
+
enum: ['SEND', 'QUEUE', 'REPLACE_CURRENT'],
|
|
79
|
+
description: 'Message delivery defaults to REPLACE_CURRENT (steer); explicit SEND remains supported.'
|
|
80
|
+
},
|
|
69
81
|
clientMessageId: text
|
|
70
82
|
},
|
|
71
83
|
required: ['agentId']
|
|
72
84
|
},
|
|
73
85
|
{
|
|
74
86
|
action: 'wait',
|
|
75
|
-
description: 'Wait for completion or messages; defaults to direct children, completion and 30 seconds. Use waitMs 0 for a snapshot.
|
|
87
|
+
description: 'Wait for completion or messages; defaults to direct children, completion and 30 seconds. Use waitMs 0 for a snapshot.',
|
|
76
88
|
properties: {
|
|
77
89
|
agentId: text,
|
|
78
90
|
agentIds: { type: 'array', items: text, minItems: 1, maxItems: 8 },
|
|
@@ -89,13 +101,18 @@ const tools = [
|
|
|
89
101
|
},
|
|
90
102
|
{
|
|
91
103
|
action: 'list',
|
|
92
|
-
description: 'List Sessions, descendants or a Runner profile.',
|
|
104
|
+
description: 'List direct children by default, discover Workspace Sessions, inspect descendants or query a Runner profile.',
|
|
93
105
|
properties: {
|
|
94
106
|
scope: { type: 'string', enum: ['workspace', 'children', 'descendants', 'profile'] },
|
|
95
|
-
agentId:
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
107
|
+
agentId: {
|
|
108
|
+
...text,
|
|
109
|
+
description: 'Root for children or descendants; defaults to the current Session.'
|
|
110
|
+
},
|
|
111
|
+
runner: {
|
|
112
|
+
...runner,
|
|
113
|
+
description: 'Runner for profile scope; defaults to the current Runner, not a model name.'
|
|
114
|
+
},
|
|
115
|
+
query: { type: 'string', description: 'Search Sessions; not a model selector.' },
|
|
99
116
|
limit: { type: 'integer', minimum: 1, maximum: 100 },
|
|
100
117
|
cursor: text
|
|
101
118
|
}
|
|
@@ -112,11 +129,52 @@ const tools = [
|
|
|
112
129
|
required: ['agentId']
|
|
113
130
|
}
|
|
114
131
|
];
|
|
132
|
+
const delegationPrompt = [
|
|
133
|
+
'# Delegation and coordination',
|
|
134
|
+
'Delegate bounded, self-contained tasks when independent progress or context savings materially helps. Keep simple or tightly coupled work local.',
|
|
135
|
+
'Give each child an objective, necessary context, expected output, and a read-only boundary or non-overlapping write scope.',
|
|
136
|
+
'Follow up through the direct delegate by default; inspect deeper Sessions only when a concrete issue needs independent verification.',
|
|
137
|
+
'While children run, continue meaningful non-overlapping work. Do not redo delegated work. Wait only when the next step needs the result.',
|
|
138
|
+
'Verify consequential findings and changes. Join or stop task-required child work before final delivery, and retain responsibility for the final result.'
|
|
139
|
+
].join('\n\n');
|
|
140
|
+
const collaborationPrompt = [
|
|
141
|
+
'# Workspace collaboration',
|
|
142
|
+
"Use native agent_* tools to collaborate within the Agent Network when another Session's context or ongoing work materially helps. Keep requests focused and avoid unnecessary cross-Session coordination. Peer Sessions retain their own work and lifecycle; do not treat them as task-owned children or stop them merely for task cleanup. Explicitly wait when you need progress or completion.",
|
|
143
|
+
"Reuse known Session IDs rather than rediscovering them. Send updates for meaningful progress, blockers, or changes that affect the recipient's work; combine related updates and avoid acknowledgment-only exchanges."
|
|
144
|
+
].join('\n\n');
|
|
145
|
+
const managementPrompt = [
|
|
146
|
+
'# Workspace management',
|
|
147
|
+
"Load marctl when needed to manage existing Session settings, titles, persistent prompts or schedules, or to discover authorized Nodes, execute remote commands or transfer files. Ordinary Agent collaboration and selecting a child's model or effort at creation use native agent_* tools. Do not switch to marctl when a native operation fails."
|
|
148
|
+
].join('\n\n');
|
|
115
149
|
export function agentNetworkExtension(invoke, currentExecution) {
|
|
116
150
|
return {
|
|
117
151
|
id: 'mar.node.agent-network',
|
|
118
152
|
apiVersion: '1.0',
|
|
119
153
|
attachSession(context) {
|
|
154
|
+
context.hooks.intercept('prompt.sections', (input, _call, next) => {
|
|
155
|
+
const available = new Set(input.facts?.tools);
|
|
156
|
+
const enabled = input.facts?.mode !== 'compact';
|
|
157
|
+
return next({
|
|
158
|
+
...input,
|
|
159
|
+
sections: [
|
|
160
|
+
...input.sections,
|
|
161
|
+
{
|
|
162
|
+
id: 'mar.node.agent-network:delegation',
|
|
163
|
+
text: enabled && available.has('agent_start') ? delegationPrompt : null
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
id: 'mar.node.agent-network:collaboration',
|
|
167
|
+
text: enabled && available.has('agent_send') ? collaborationPrompt : null
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
id: 'mar.node.agent-network:management',
|
|
171
|
+
text: enabled && tools.some((tool) => available.has(`agent_${tool.action}`))
|
|
172
|
+
? managementPrompt
|
|
173
|
+
: null
|
|
174
|
+
}
|
|
175
|
+
]
|
|
176
|
+
});
|
|
177
|
+
});
|
|
120
178
|
for (const tool of tools)
|
|
121
179
|
context.tools.register({
|
|
122
180
|
name: `agent_${tool.action}`,
|
|
@@ -124,10 +182,11 @@ export function agentNetworkExtension(invoke, currentExecution) {
|
|
|
124
182
|
allowedCallers: ['direct', 'programmatic'],
|
|
125
183
|
eventInputFields: [
|
|
126
184
|
'agentId',
|
|
185
|
+
'agentIds',
|
|
127
186
|
'title',
|
|
128
187
|
'executionId',
|
|
129
188
|
'runner',
|
|
130
|
-
'
|
|
189
|
+
'modelId',
|
|
131
190
|
'effort',
|
|
132
191
|
'waitFor',
|
|
133
192
|
'waitMs',
|
|
@@ -143,23 +202,20 @@ export function agentNetworkExtension(invoke, currentExecution) {
|
|
|
143
202
|
const executionId = currentExecution();
|
|
144
203
|
if (!executionId)
|
|
145
204
|
throw new Error('AGENT_ACTOR_NOT_ACTIVE');
|
|
146
|
-
const
|
|
147
|
-
const
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
? { agentId: id, productSessionId: id }
|
|
156
|
-
: undefined;
|
|
205
|
+
const args = input;
|
|
206
|
+
const operationInput = tool.action === 'send' &&
|
|
207
|
+
args.deliveryIntent === undefined &&
|
|
208
|
+
((typeof args.content === 'string' && !!args.content.trim()) ||
|
|
209
|
+
(Array.isArray(args.attachments) && args.attachments.length > 0) ||
|
|
210
|
+
(Array.isArray(args.attachmentIds) && args.attachmentIds.length > 0))
|
|
211
|
+
? { ...args, deliveryIntent: 'REPLACE_CURRENT' }
|
|
212
|
+
: args;
|
|
213
|
+
const result = await invoke(executionId, tool.action, operationInput, call);
|
|
157
214
|
const modelResult = tool.action === 'wait' ? agentNetworkWaitModelOutput(result) : result;
|
|
158
215
|
return {
|
|
159
216
|
data: result,
|
|
160
217
|
content: JSON.stringify(modelResult),
|
|
161
|
-
|
|
162
|
-
eventData: eventData ?? {}
|
|
218
|
+
...agentOperationEvent(tool.action, operationInput, result)
|
|
163
219
|
};
|
|
164
220
|
}
|
|
165
221
|
});
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { agentOperationPresentationSchema } from '@myagentroam/protocol';
|
|
2
|
+
import { isPlainRecord } from '../../util/records.js';
|
|
3
|
+
const actions = {
|
|
4
|
+
agent_start: 'START',
|
|
5
|
+
agent_send: 'SEND',
|
|
6
|
+
agent_wait: 'WAIT',
|
|
7
|
+
agent_stop: 'STOP',
|
|
8
|
+
agent_list: 'LIST',
|
|
9
|
+
agent_view: 'VIEW'
|
|
10
|
+
};
|
|
11
|
+
export function agentOperationAction(toolName) {
|
|
12
|
+
return Object.hasOwn(actions, toolName) ? actions[toolName] : undefined;
|
|
13
|
+
}
|
|
14
|
+
function text(value, max = 128) {
|
|
15
|
+
return typeof value === 'string' && value.trim()
|
|
16
|
+
? value.slice(0, max).replace(/[\uD800-\uDBFF]$/u, '')
|
|
17
|
+
: undefined;
|
|
18
|
+
}
|
|
19
|
+
function latestWaitMessage(updates) {
|
|
20
|
+
if (!Array.isArray(updates))
|
|
21
|
+
return undefined;
|
|
22
|
+
const message = updates.findLast((entry) => isPlainRecord(entry) && entry.kind === 'message' && typeof entry.value === 'string');
|
|
23
|
+
if (!isPlainRecord(message))
|
|
24
|
+
return undefined;
|
|
25
|
+
const value = text(message.value, 800);
|
|
26
|
+
if (!value)
|
|
27
|
+
return undefined;
|
|
28
|
+
return {
|
|
29
|
+
value,
|
|
30
|
+
...(message.final === true ? { final: true } : {}),
|
|
31
|
+
...(message.truncated === true || message.value.length > 800
|
|
32
|
+
? { truncated: true }
|
|
33
|
+
: {})
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
/** Input identities are display-only; only successful Node results establish navigation. */
|
|
37
|
+
export function agentOperationPresentation(toolName, input, data) {
|
|
38
|
+
const action = agentOperationAction(toolName);
|
|
39
|
+
if (!action)
|
|
40
|
+
return undefined;
|
|
41
|
+
const parsed = agentOperationPresentationSchema.safeParse(isPlainRecord(data) ? data.agentOperation : undefined);
|
|
42
|
+
if (parsed.success && parsed.data.action === action)
|
|
43
|
+
return parsed.data;
|
|
44
|
+
const value = isPlainRecord(input) ? input : {};
|
|
45
|
+
const ids = typeof value.agentId === 'string'
|
|
46
|
+
? [value.agentId]
|
|
47
|
+
: Array.isArray(value.agentIds)
|
|
48
|
+
? value.agentIds
|
|
49
|
+
: [];
|
|
50
|
+
return {
|
|
51
|
+
action,
|
|
52
|
+
targets: ids
|
|
53
|
+
.filter((id) => typeof id === 'string' && id.length > 0 && id.length <= 128)
|
|
54
|
+
.slice(0, 8)
|
|
55
|
+
.map((agentId) => ({ agentId })),
|
|
56
|
+
...(text(value.scope, 32) ? { scope: text(value.scope, 32) } : {})
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/** Whitelist public receipts; never serialize configuration, credentials or full history. */
|
|
60
|
+
export function agentOperationEvent(action, input, result) {
|
|
61
|
+
const operation = agentOperationPresentation(`agent_${action}`, input);
|
|
62
|
+
const value = isPlainRecord(result) ? result : {};
|
|
63
|
+
const args = isPlainRecord(input) ? input : {};
|
|
64
|
+
const rows = action === 'list' ? value.sessions : action === 'wait' ? value.agents : undefined;
|
|
65
|
+
const records = Array.isArray(rows)
|
|
66
|
+
? rows
|
|
67
|
+
: action === 'list'
|
|
68
|
+
? []
|
|
69
|
+
: action === 'view'
|
|
70
|
+
? isPlainRecord(value.session)
|
|
71
|
+
? [value.session]
|
|
72
|
+
: []
|
|
73
|
+
: typeof value.agentId === 'string'
|
|
74
|
+
? [value]
|
|
75
|
+
: [];
|
|
76
|
+
const returnedTargets = records
|
|
77
|
+
.flatMap((entry) => {
|
|
78
|
+
if (!isPlainRecord(entry))
|
|
79
|
+
return [];
|
|
80
|
+
const rawId = entry.agentId ?? entry.id;
|
|
81
|
+
const agentId = typeof rawId === 'string' && rawId.length <= 128 ? text(rawId) : undefined;
|
|
82
|
+
if (!agentId)
|
|
83
|
+
return [];
|
|
84
|
+
const title = text(entry.title ?? (action === 'start' ? args.title : undefined), 500);
|
|
85
|
+
return [{ agentId, sessionId: agentId, ...(title ? { title } : {}) }];
|
|
86
|
+
})
|
|
87
|
+
.slice(0, 100);
|
|
88
|
+
// An any-wait may return one result for several requested targets; keep the call multi-target.
|
|
89
|
+
const targets = action === 'wait' && operation.targets.length > 0
|
|
90
|
+
? operation.targets.map((target) => returnedTargets.find((result) => result.agentId === target.agentId) ?? target)
|
|
91
|
+
: returnedTargets;
|
|
92
|
+
const presentation = { ...operation, targets };
|
|
93
|
+
if (records.length > returnedTargets.length)
|
|
94
|
+
presentation.targetsTruncated = true;
|
|
95
|
+
// Core accepts at most 8 KiB of explicit extension event data.
|
|
96
|
+
while (Buffer.byteLength(JSON.stringify({ agentOperation: presentation }), 'utf8') > 7000) {
|
|
97
|
+
targets.pop();
|
|
98
|
+
presentation.targetsTruncated = true;
|
|
99
|
+
}
|
|
100
|
+
const receipt = {};
|
|
101
|
+
if (action === 'list' && Array.isArray(value.models)) {
|
|
102
|
+
receipt.runner = text(value.runner);
|
|
103
|
+
receipt.modelCount = value.models.length;
|
|
104
|
+
}
|
|
105
|
+
if (action === 'view' && isPlainRecord(value.session)) {
|
|
106
|
+
for (const key of ['status', 'model', 'effort']) {
|
|
107
|
+
const field = text(value.session[key]);
|
|
108
|
+
if (field)
|
|
109
|
+
receipt[key] = field;
|
|
110
|
+
}
|
|
111
|
+
const message = isPlainRecord(value.session.latestMessage)
|
|
112
|
+
? value.session.latestMessage
|
|
113
|
+
: undefined;
|
|
114
|
+
const messageText = text(message?.text, 800);
|
|
115
|
+
if (messageText)
|
|
116
|
+
receipt.latestMessage = {
|
|
117
|
+
text: messageText,
|
|
118
|
+
truncated: message?.truncated === true || messageText !== message?.text
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
for (const key of ['status', 'waitOutcome', 'waitFor', 'mode', 'reason', 'executionId']) {
|
|
122
|
+
const field = text(value[key]);
|
|
123
|
+
if (field)
|
|
124
|
+
receipt[key] = field;
|
|
125
|
+
}
|
|
126
|
+
for (const key of ['message', 'cancel', 'goal', 'approval', 'observation']) {
|
|
127
|
+
const item = value[key];
|
|
128
|
+
if (!isPlainRecord(item))
|
|
129
|
+
continue;
|
|
130
|
+
receipt[key] = {
|
|
131
|
+
...(text(item.status) ? { status: text(item.status) } : {}),
|
|
132
|
+
...(text(item.code) ? { code: text(item.code) } : {}),
|
|
133
|
+
...(isPlainRecord(item.result) && text(item.result.delivery)
|
|
134
|
+
? { delivery: text(item.result.delivery) }
|
|
135
|
+
: {})
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
if (Array.isArray(rows))
|
|
139
|
+
receipt.count = rows.length;
|
|
140
|
+
if (action === 'wait' && Array.isArray(rows))
|
|
141
|
+
receipt.agents = rows.slice(0, 8).map((entry) => {
|
|
142
|
+
const agent = isPlainRecord(entry) ? entry : {};
|
|
143
|
+
return {
|
|
144
|
+
agentId: text(agent.agentId),
|
|
145
|
+
status: text(agent.status),
|
|
146
|
+
waitOutcome: text(agent.waitOutcome),
|
|
147
|
+
summary: text(agent.summary, 800),
|
|
148
|
+
latestMessage: latestWaitMessage(agent.updates),
|
|
149
|
+
...(agent.summaryTruncated === true ||
|
|
150
|
+
(typeof agent.summary === 'string' && agent.summary.length > 800)
|
|
151
|
+
? { summaryTruncated: true }
|
|
152
|
+
: {})
|
|
153
|
+
};
|
|
154
|
+
});
|
|
155
|
+
if (action === 'wait' && !Array.isArray(rows))
|
|
156
|
+
receipt.latestMessage = latestWaitMessage(value.updates);
|
|
157
|
+
const summary = text(value.summary, 4000);
|
|
158
|
+
if (summary)
|
|
159
|
+
receipt.summary = summary;
|
|
160
|
+
if (value.summaryTruncated === true ||
|
|
161
|
+
(typeof value.summary === 'string' && value.summary.length > 4000))
|
|
162
|
+
receipt.summaryTruncated = true;
|
|
163
|
+
return {
|
|
164
|
+
eventData: { agentOperation: presentation },
|
|
165
|
+
eventSummary: JSON.stringify(receipt)
|
|
166
|
+
};
|
|
167
|
+
}
|