@myagentroam/node 0.11.43 → 0.11.45
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 +5 -1
- package/dist/main.js +10 -3
- package/dist/runner/claude/managed-run-controller.js +3 -10
- package/dist/runner/claude/native-transcript.js +1 -1
- package/dist/runner/claude/tool-normalizer.js +16 -11
- package/dist/runner/codex/conversation-parser.js +29 -38
- package/dist/runner/codex/managed-run-controller.js +4 -4
- package/dist/runner/codex/native-transcript.js +5 -3
- package/dist/runner/codex/tool-normalizer.js +13 -17
- package/dist/runner/execution-presentation.js +8 -51
- package/dist/runner/mar-agent/agent-network-extension.js +43 -13
- 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/session-runtime-registry.js +22 -12
- package/dist/runner/mar-agent/session-runtime.js +16 -11
- package/dist/runner/normalization.js +5 -0
- package/dist/runner/opencode/tool-normalizer.js +10 -10
- package/dist/service/agent-network-bindings.js +10 -49
- package/dist/service/agent-network-invoke.js +4 -19
- package/dist/service/agent-network-service.js +0 -16
- package/dist/service/marctl-caller.js +17 -9
- package/dist/session/runtime/conversation-projection.js +10 -3
- package/dist/workspace/git.js +44 -1
- package/dist/workspace/workspace-change-service.js +39 -1
- package/dist/workspace/workspace-workbench-service.js +8 -0
- package/package.json +4 -4
|
@@ -1,4 +1,5 @@
|
|
|
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 };
|
|
3
4
|
const attachmentIds = {
|
|
4
5
|
type: 'array',
|
|
@@ -52,7 +53,7 @@ const tools = [
|
|
|
52
53
|
},
|
|
53
54
|
{
|
|
54
55
|
action: 'send',
|
|
55
|
-
description: 'Send input, decide
|
|
56
|
+
description: 'Send input, decide an approval or update a Goal; goal:null leaves it unchanged, goal:{clear:true} clears it. Prefer REPLACE_CURRENT for timely communication or course correction. QUEUE is consumed only after current work and earlier queued inputs finish; an active Goal must exit first. Sending to a non-child does not subscribe to its updates.',
|
|
56
57
|
properties: {
|
|
57
58
|
agentId: text,
|
|
58
59
|
content: { type: 'string' },
|
|
@@ -112,11 +113,50 @@ const tools = [
|
|
|
112
113
|
required: ['agentId']
|
|
113
114
|
}
|
|
114
115
|
];
|
|
116
|
+
const delegationPrompt = [
|
|
117
|
+
'# Delegation and coordination',
|
|
118
|
+
'Delegate bounded, self-contained tasks when independent progress or context savings materially helps. Keep simple or tightly coupled work local.',
|
|
119
|
+
'Give each child an objective, necessary context, expected output, and a read-only boundary or non-overlapping write scope.',
|
|
120
|
+
'While children run, continue meaningful non-overlapping work. Do not redo delegated work. Wait only when the next step needs the result.',
|
|
121
|
+
'Verify consequential findings and changes. Join or stop task-required child work before final delivery, and retain responsibility for the final result.'
|
|
122
|
+
].join('\n\n');
|
|
123
|
+
const collaborationPrompt = [
|
|
124
|
+
'# Workspace collaboration',
|
|
125
|
+
"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. Sending to a non-child does not subscribe to its updates; explicitly wait when you need its progress or completion."
|
|
126
|
+
].join('\n\n');
|
|
127
|
+
const managementPrompt = [
|
|
128
|
+
'# Workspace management',
|
|
129
|
+
"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."
|
|
130
|
+
].join('\n\n');
|
|
115
131
|
export function agentNetworkExtension(invoke, currentExecution) {
|
|
116
132
|
return {
|
|
117
133
|
id: 'mar.node.agent-network',
|
|
118
134
|
apiVersion: '1.0',
|
|
119
135
|
attachSession(context) {
|
|
136
|
+
context.hooks.intercept('prompt.sections', (input, _call, next) => {
|
|
137
|
+
const available = new Set(input.facts?.tools);
|
|
138
|
+
const enabled = input.facts?.mode !== 'compact';
|
|
139
|
+
return next({
|
|
140
|
+
...input,
|
|
141
|
+
sections: [
|
|
142
|
+
...input.sections,
|
|
143
|
+
{
|
|
144
|
+
id: 'mar.node.agent-network:delegation',
|
|
145
|
+
text: enabled && available.has('agent_start') ? delegationPrompt : null
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
id: 'mar.node.agent-network:collaboration',
|
|
149
|
+
text: enabled && available.has('agent_send') ? collaborationPrompt : null
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
id: 'mar.node.agent-network:management',
|
|
153
|
+
text: enabled && tools.some((tool) => available.has(`agent_${tool.action}`))
|
|
154
|
+
? managementPrompt
|
|
155
|
+
: null
|
|
156
|
+
}
|
|
157
|
+
]
|
|
158
|
+
});
|
|
159
|
+
});
|
|
120
160
|
for (const tool of tools)
|
|
121
161
|
context.tools.register({
|
|
122
162
|
name: `agent_${tool.action}`,
|
|
@@ -124,6 +164,7 @@ export function agentNetworkExtension(invoke, currentExecution) {
|
|
|
124
164
|
allowedCallers: ['direct', 'programmatic'],
|
|
125
165
|
eventInputFields: [
|
|
126
166
|
'agentId',
|
|
167
|
+
'agentIds',
|
|
127
168
|
'title',
|
|
128
169
|
'executionId',
|
|
129
170
|
'runner',
|
|
@@ -144,22 +185,11 @@ export function agentNetworkExtension(invoke, currentExecution) {
|
|
|
144
185
|
if (!executionId)
|
|
145
186
|
throw new Error('AGENT_ACTOR_NOT_ACTIVE');
|
|
146
187
|
const result = await invoke(executionId, tool.action, input, call);
|
|
147
|
-
const value = result !== null && typeof result === 'object' && !Array.isArray(result)
|
|
148
|
-
? result
|
|
149
|
-
: undefined;
|
|
150
|
-
const id = value?.agentId;
|
|
151
|
-
const eventData = ['start', 'send', 'stop', 'wait'].includes(tool.action) &&
|
|
152
|
-
typeof id === 'string' &&
|
|
153
|
-
id.length > 0 &&
|
|
154
|
-
id.length <= 128
|
|
155
|
-
? { agentId: id, productSessionId: id }
|
|
156
|
-
: undefined;
|
|
157
188
|
const modelResult = tool.action === 'wait' ? agentNetworkWaitModelOutput(result) : result;
|
|
158
189
|
return {
|
|
159
190
|
data: result,
|
|
160
191
|
content: JSON.stringify(modelResult),
|
|
161
|
-
|
|
162
|
-
eventData: eventData ?? {}
|
|
192
|
+
...agentOperationEvent(tool.action, input, result)
|
|
163
193
|
};
|
|
164
194
|
}
|
|
165
195
|
});
|
|
@@ -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
|
+
}
|