@myagentroam/node 0.11.39 → 0.11.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/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 +233 -119
- 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 +4 -4
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { DatabaseSync } from 'node:sqlite';
|
|
2
|
+
import { NODE_SCHEMA_V13 } from './v013.js';
|
|
3
|
+
import { NODE_SCHEMA_MIGRATION_V14 } from './v014.js';
|
|
4
|
+
import { NODE_SCHEMA_MIGRATION_V15 } from './v015.js';
|
|
5
|
+
import { NODE_SCHEMA_MIGRATION_V16 } from './v016.js';
|
|
6
|
+
import { NODE_SCHEMA_MIGRATION_V17 } from './v017.js';
|
|
7
|
+
import { NODE_SCHEMA_MIGRATION_V18 } from './v018.js';
|
|
8
|
+
import { NODE_SCHEMA_MIGRATION_V19 } from './v019.js';
|
|
9
|
+
import { NODE_SCHEMA_MIGRATION_V20 } from './v020.js';
|
|
10
|
+
import { NODE_SCHEMA_MIGRATION_V21 } from './v021.js';
|
|
11
|
+
const sql = `ALTER TABLE agent_sessions ADD COLUMN parent_session_id TEXT;
|
|
12
|
+
UPDATE agent_sessions SET parent_session_id = CASE
|
|
13
|
+
WHEN json_type(metadata, '$.parentSessionId') = 'text'
|
|
14
|
+
THEN json_extract(metadata, '$.parentSessionId') ELSE NULL END;
|
|
15
|
+
UPDATE agent_sessions SET metadata = json_remove(metadata, '$.parentSessionId');
|
|
16
|
+
CREATE INDEX agent_sessions_workspace_parent ON agent_sessions(workspace_id, parent_session_id);`;
|
|
17
|
+
export const NODE_SCHEMA_MIGRATION_V22 = {
|
|
18
|
+
version: 22,
|
|
19
|
+
sql,
|
|
20
|
+
preflight: (database) => NODE_SCHEMA_MIGRATION_V21.assert(database),
|
|
21
|
+
assert(database) {
|
|
22
|
+
const reference = new DatabaseSync(':memory:');
|
|
23
|
+
try {
|
|
24
|
+
reference.exec(NODE_SCHEMA_V13);
|
|
25
|
+
for (const migration of [
|
|
26
|
+
NODE_SCHEMA_MIGRATION_V14,
|
|
27
|
+
NODE_SCHEMA_MIGRATION_V15,
|
|
28
|
+
NODE_SCHEMA_MIGRATION_V16,
|
|
29
|
+
NODE_SCHEMA_MIGRATION_V17,
|
|
30
|
+
NODE_SCHEMA_MIGRATION_V18,
|
|
31
|
+
NODE_SCHEMA_MIGRATION_V19,
|
|
32
|
+
NODE_SCHEMA_MIGRATION_V20,
|
|
33
|
+
NODE_SCHEMA_MIGRATION_V21
|
|
34
|
+
])
|
|
35
|
+
reference.exec(migration.sql);
|
|
36
|
+
reference.exec(sql);
|
|
37
|
+
const schema = (db) => db
|
|
38
|
+
.prepare("SELECT type,name,sql FROM sqlite_master WHERE sql IS NOT NULL AND name NOT LIKE 'sqlite_%' ORDER BY type,name")
|
|
39
|
+
.all()
|
|
40
|
+
.map((row) => [
|
|
41
|
+
row.type,
|
|
42
|
+
row.name,
|
|
43
|
+
row.name === 'node_schema_migrations'
|
|
44
|
+
? String(row.sql).replace(/\s+/g, '')
|
|
45
|
+
: String(row.sql).replace(/\s+/g, ' ').trim()
|
|
46
|
+
]);
|
|
47
|
+
const actual = schema(database);
|
|
48
|
+
const expected = schema(reference);
|
|
49
|
+
const mismatch = expected.find((row) => !actual.some((value) => JSON.stringify(value) === JSON.stringify(row))) ??
|
|
50
|
+
actual.find((row) => !expected.some((value) => JSON.stringify(value) === JSON.stringify(row)));
|
|
51
|
+
if (mismatch !== undefined)
|
|
52
|
+
throw new Error(`NODE_DATABASE_SCHEMA_INVALID:${String(mismatch[1])}`);
|
|
53
|
+
}
|
|
54
|
+
finally {
|
|
55
|
+
reference.close();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
};
|
|
@@ -36,6 +36,13 @@ export class AbstractRunner {
|
|
|
36
36
|
model.supportedEfforts.includes(configuration.effort) &&
|
|
37
37
|
profile.accessOptions.some((candidate) => candidate.id === configuration.access));
|
|
38
38
|
}
|
|
39
|
+
/** Models eligible for a child session, based on this runner's current profile. */
|
|
40
|
+
childModels() {
|
|
41
|
+
return this.profileForValidation().models;
|
|
42
|
+
}
|
|
43
|
+
assertChildModel(modelId) {
|
|
44
|
+
void modelId;
|
|
45
|
+
}
|
|
39
46
|
presentSession(session, capabilities, context) {
|
|
40
47
|
void [session, capabilities, context];
|
|
41
48
|
throw new RunnerCapabilityError(this.name, 'session.presentation');
|
|
@@ -72,6 +79,13 @@ export class AbstractRunner {
|
|
|
72
79
|
void [session, nativeSessionId];
|
|
73
80
|
return false;
|
|
74
81
|
}
|
|
82
|
+
supportsNativeBatchRemoval() {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
async removeNativeMembers(sessions) {
|
|
86
|
+
void sessions;
|
|
87
|
+
throw new Error('NATIVE_BATCH_REMOVE_UNAVAILABLE');
|
|
88
|
+
}
|
|
75
89
|
async forkNative(session, boundary, title) {
|
|
76
90
|
void [session, boundary, title];
|
|
77
91
|
throw new RunnerCapabilityError(this.name, 'session.rewind');
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { claudeContentBlocks } from './message-parsers.js';
|
|
2
2
|
import { compactRunnerText, compactRunnerValue } from '../normalization.js';
|
|
3
|
+
import { publicOutputSnapshot, shellExecution } from '../execution-presentation.js';
|
|
3
4
|
import { RunnerUsageTracker, usageToken, usageTokenSum } from '../../service/runner-usage-tracker.js';
|
|
4
5
|
export class ClaudeUsageNormalizer {
|
|
5
6
|
tracker;
|
|
@@ -105,7 +106,11 @@ export function normalizeClaudeTool(toolName, input, output, failed, truncated,
|
|
|
105
106
|
outputRef: null,
|
|
106
107
|
exitCode: null,
|
|
107
108
|
durationMs: null,
|
|
108
|
-
truncated: outputTruncated
|
|
109
|
+
truncated: outputTruncated,
|
|
110
|
+
...(output === null || output === undefined
|
|
111
|
+
? {}
|
|
112
|
+
: { output: publicOutputSnapshot(output, truncated) }),
|
|
113
|
+
toolPresentation: shellExecution(output ?? undefined, truncated)
|
|
109
114
|
}
|
|
110
115
|
};
|
|
111
116
|
if (isFileTool(toolName)) {
|
|
@@ -131,6 +131,7 @@ import { runnerErrorProjection } from '../runner-error.js';
|
|
|
131
131
|
import { conversationImageAttachmentSchema } from '@myagentroam/protocol';
|
|
132
132
|
import { parseCodexComposerImageMarkers } from './composer-image-markers.js';
|
|
133
133
|
import { normalizeCodexTool } from './tool-normalizer.js';
|
|
134
|
+
import { publicOutputSnapshot, shellExecution } from '../execution-presentation.js';
|
|
134
135
|
import { publicGoalHistoryText } from '../../service/goal-runtime.js';
|
|
135
136
|
const MAX_COMPOSER_ATTACHMENT_BYTES = 5 * 1024 * 1024;
|
|
136
137
|
const MAX_COMPOSER_ATTACHMENTS_BYTES = 10 * 1024 * 1024;
|
|
@@ -716,7 +717,7 @@ export function codexOfficialItem(session, nativeTurnId, value, turnSequence, it
|
|
|
716
717
|
? compactRunnerText(value.command, 10_000)
|
|
717
718
|
: '(No command provided)',
|
|
718
719
|
cwd: typeof value.cwd === 'string' ? compactRunnerText(value.cwd, 4_096) : null,
|
|
719
|
-
outputPreview:
|
|
720
|
+
outputPreview: publicOutputSnapshot(output).text,
|
|
720
721
|
outputRef: null,
|
|
721
722
|
exitCode: typeof value.exitCode === 'number' && Number.isInteger(value.exitCode)
|
|
722
723
|
? value.exitCode
|
|
@@ -726,7 +727,17 @@ export function codexOfficialItem(session, nativeTurnId, value, turnSequence, it
|
|
|
726
727
|
value.durationMs >= 0
|
|
727
728
|
? value.durationMs
|
|
728
729
|
: null,
|
|
729
|
-
truncated: output.length > 10_000
|
|
730
|
+
truncated: output.length > 10_000,
|
|
731
|
+
...(base.status !== 'IN_PROGRESS'
|
|
732
|
+
? {
|
|
733
|
+
output: typeof value.aggregatedOutput === 'string'
|
|
734
|
+
? publicOutputSnapshot(output)
|
|
735
|
+
: undefined
|
|
736
|
+
}
|
|
737
|
+
: {}),
|
|
738
|
+
toolPresentation: shellExecution(base.status === 'IN_PROGRESS' || typeof value.aggregatedOutput !== 'string'
|
|
739
|
+
? undefined
|
|
740
|
+
: output)
|
|
730
741
|
}
|
|
731
742
|
};
|
|
732
743
|
}
|
|
@@ -888,7 +899,7 @@ export function codexLiveConversationItem(value, completed, imageAttachments = [
|
|
|
888
899
|
? compactRunnerText(value.command, 10_000)
|
|
889
900
|
: '(No command provided)',
|
|
890
901
|
cwd: typeof value.cwd === 'string' ? compactRunnerText(value.cwd, 4_096) : null,
|
|
891
|
-
outputPreview:
|
|
902
|
+
outputPreview: publicOutputSnapshot(output).text,
|
|
892
903
|
outputRef: null,
|
|
893
904
|
exitCode: typeof value.exitCode === 'number' && Number.isInteger(value.exitCode)
|
|
894
905
|
? value.exitCode
|
|
@@ -898,7 +909,15 @@ export function codexLiveConversationItem(value, completed, imageAttachments = [
|
|
|
898
909
|
value.durationMs >= 0
|
|
899
910
|
? value.durationMs
|
|
900
911
|
: null,
|
|
901
|
-
truncated: output.length > 10_000
|
|
912
|
+
truncated: output.length > 10_000,
|
|
913
|
+
...(completed
|
|
914
|
+
? {
|
|
915
|
+
output: typeof value.aggregatedOutput === 'string'
|
|
916
|
+
? publicOutputSnapshot(output)
|
|
917
|
+
: undefined
|
|
918
|
+
}
|
|
919
|
+
: {}),
|
|
920
|
+
toolPresentation: shellExecution(!completed || typeof value.aggregatedOutput !== 'string' ? undefined : output)
|
|
902
921
|
}
|
|
903
922
|
};
|
|
904
923
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { NODE_MCP } from '../../mcp/naming.js';
|
|
2
2
|
import { nodeExecutionEnvironment } from '../../node-execution-environment.js';
|
|
3
3
|
import { extractId } from './conversation-parser.js';
|
|
4
|
+
import { publicOutputSnapshot } from '../execution-presentation.js';
|
|
4
5
|
import { normalizeCodexApproval } from './tool-normalizer.js';
|
|
5
6
|
import { parseSecretEnvironment } from '../../mcp/secret-environment.js';
|
|
6
7
|
import { safeErrorCode } from '../../util/safe-error.js';
|
|
@@ -279,13 +280,14 @@ export class CodexManagedRunController {
|
|
|
279
280
|
if (notification.method === 'item/commandExecution/outputDelta' &&
|
|
280
281
|
typeof params.itemId === 'string' &&
|
|
281
282
|
typeof params.delta === 'string') {
|
|
283
|
+
const output = publicOutputSnapshot(params.delta);
|
|
282
284
|
this.emitItem(runId, {
|
|
283
285
|
itemId: boundedConversationItemId('codex', params.itemId),
|
|
284
286
|
kind: 'command_execution',
|
|
285
287
|
status: 'IN_PROGRESS',
|
|
286
|
-
payload: { outputPreview:
|
|
288
|
+
payload: { outputPreview: output.text, output },
|
|
287
289
|
merge: true,
|
|
288
|
-
append: '
|
|
290
|
+
append: 'output'
|
|
289
291
|
});
|
|
290
292
|
return;
|
|
291
293
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { compactRunnerText } from '../normalization.js';
|
|
2
|
+
import { publicOutputSnapshot, shellExecution } from '../execution-presentation.js';
|
|
2
3
|
export function normalizeCodexApproval(method) {
|
|
3
4
|
return method === 'item/commandExecution/requestApproval'
|
|
4
5
|
? { toolName: 'command_execution', reason: 'Command execution requires approval.' }
|
|
@@ -17,6 +18,10 @@ export function normalizeCodexTool(input) {
|
|
|
17
18
|
payload: {
|
|
18
19
|
outputPreview,
|
|
19
20
|
truncated: input.truncated === true || (input.outputSummary?.length ?? 0) > 10_000,
|
|
21
|
+
...(input.outputSummary == null
|
|
22
|
+
? {}
|
|
23
|
+
: { output: publicOutputSnapshot(input.outputSummary, input.truncated === true) }),
|
|
24
|
+
toolPresentation: shellExecution(input.outputSummary ?? undefined, input.truncated === true),
|
|
20
25
|
...(attachments.length === 0 ? {} : { attachments })
|
|
21
26
|
}
|
|
22
27
|
};
|
|
@@ -37,6 +42,10 @@ export function normalizeCodexTool(input) {
|
|
|
37
42
|
outputRef: null,
|
|
38
43
|
exitCode: null,
|
|
39
44
|
durationMs: null,
|
|
45
|
+
toolPresentation: shellExecution(input.outputSummary ?? undefined, input.truncated === true),
|
|
46
|
+
...(input.outputSummary == null
|
|
47
|
+
? {}
|
|
48
|
+
: { output: publicOutputSnapshot(input.outputSummary, input.truncated === true) }),
|
|
40
49
|
truncated: input.truncated === true ||
|
|
41
50
|
command.length > 10_000 ||
|
|
42
51
|
(input.outputSummary?.length ?? 0) > 10_000,
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
const MAX_BYTES = 128 * 1024;
|
|
2
|
+
function tail(text, limit) {
|
|
3
|
+
if (limit === 0)
|
|
4
|
+
return '';
|
|
5
|
+
const result = text.slice(-limit);
|
|
6
|
+
const first = result.charCodeAt(0);
|
|
7
|
+
return first >= 0xdc00 && first <= 0xdfff ? result.slice(1) : result;
|
|
8
|
+
}
|
|
9
|
+
export function publicOutputSnapshot(text, truncated = false) {
|
|
10
|
+
return { text: tail(text, 10_000), truncated: truncated || text.length > 10_000 };
|
|
11
|
+
}
|
|
12
|
+
/** Keep public execution output bounded without copying an unbounded source. */
|
|
13
|
+
export function boundExecutionPresentation(execution) {
|
|
14
|
+
let truncated = (execution.output?.length ?? 0) > 4;
|
|
15
|
+
const output = execution.output?.slice(0, 4).map((block) => {
|
|
16
|
+
if (block.text.length > 10_000)
|
|
17
|
+
truncated = true;
|
|
18
|
+
return { ...block, text: tail(block.text, 10_000) };
|
|
19
|
+
});
|
|
20
|
+
const detail = {
|
|
21
|
+
...execution,
|
|
22
|
+
...(output === undefined ? {} : { output }),
|
|
23
|
+
...(execution.truncation === undefined ? {} : { truncation: { ...execution.truncation } })
|
|
24
|
+
};
|
|
25
|
+
if (truncated)
|
|
26
|
+
detail.truncation = { ...detail.truncation, output: detail.truncation?.output ?? 'HEAD' };
|
|
27
|
+
const presentation = { kind: 'EXECUTION', execution: detail };
|
|
28
|
+
const size = () => Buffer.byteLength(JSON.stringify(presentation));
|
|
29
|
+
for (const block of [...(detail.output ?? [])].reverse()) {
|
|
30
|
+
if (size() <= MAX_BYTES)
|
|
31
|
+
break;
|
|
32
|
+
const source = block.text;
|
|
33
|
+
detail.truncation = { ...detail.truncation, output: detail.truncation?.output ?? 'HEAD' };
|
|
34
|
+
truncated = true;
|
|
35
|
+
let low = 0;
|
|
36
|
+
let high = source.length;
|
|
37
|
+
while (low < high) {
|
|
38
|
+
const middle = Math.ceil((low + high) / 2);
|
|
39
|
+
block.text = tail(source, middle);
|
|
40
|
+
if (size() <= MAX_BYTES)
|
|
41
|
+
low = middle;
|
|
42
|
+
else
|
|
43
|
+
high = middle - 1;
|
|
44
|
+
}
|
|
45
|
+
block.text = tail(source, low);
|
|
46
|
+
}
|
|
47
|
+
return { presentation, truncated };
|
|
48
|
+
}
|
|
49
|
+
/** Project only the stream facts actually supplied by the runner. */
|
|
50
|
+
export function shellExecution(output, sourceTruncated = false) {
|
|
51
|
+
return boundExecutionPresentation({
|
|
52
|
+
kind: 'SHELL',
|
|
53
|
+
...(output === undefined ? {} : { output: [{ channel: 'COMBINED', text: output }] }),
|
|
54
|
+
...(sourceTruncated ? { truncation: { output: 'SOURCE' } } : {})
|
|
55
|
+
}).presentation;
|
|
56
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { agentNetworkWaitModelOutput } from './agent-network-wait-output.js';
|
|
2
|
+
const text = { type: 'string', minLength: 1 };
|
|
3
|
+
const attachmentIds = {
|
|
4
|
+
type: 'array',
|
|
5
|
+
items: text,
|
|
6
|
+
maxItems: 32,
|
|
7
|
+
description: 'Existing composer attachment IDs.'
|
|
8
|
+
};
|
|
9
|
+
const attachments = {
|
|
10
|
+
type: 'array',
|
|
11
|
+
maxItems: 5,
|
|
12
|
+
items: {
|
|
13
|
+
type: 'object',
|
|
14
|
+
additionalProperties: false,
|
|
15
|
+
required: ['name', 'mime', 'dataBase64'],
|
|
16
|
+
properties: { name: text, mime: text, dataBase64: text }
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
const goal = {
|
|
20
|
+
anyOf: [
|
|
21
|
+
{ type: 'null' },
|
|
22
|
+
{
|
|
23
|
+
type: 'object',
|
|
24
|
+
properties: { objective: text },
|
|
25
|
+
required: ['objective'],
|
|
26
|
+
additionalProperties: false
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
type: 'object',
|
|
30
|
+
properties: { clear: { type: 'boolean', const: true } },
|
|
31
|
+
required: ['clear'],
|
|
32
|
+
additionalProperties: false
|
|
33
|
+
}
|
|
34
|
+
]
|
|
35
|
+
};
|
|
36
|
+
const tools = [
|
|
37
|
+
{
|
|
38
|
+
action: 'start',
|
|
39
|
+
description: 'Start a child Agent with a message or image and optional Goal; returns without waiting.',
|
|
40
|
+
properties: {
|
|
41
|
+
content: { type: 'string' },
|
|
42
|
+
attachments,
|
|
43
|
+
attachmentIds,
|
|
44
|
+
runner: text,
|
|
45
|
+
model: text,
|
|
46
|
+
effort: text,
|
|
47
|
+
title: text,
|
|
48
|
+
goal,
|
|
49
|
+
clientMessageId: text
|
|
50
|
+
},
|
|
51
|
+
required: ['title']
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
action: 'send',
|
|
55
|
+
description: 'Send input, decide a child approval or update a Goal; goal:null leaves it unchanged, goal:{clear:true} clears it.',
|
|
56
|
+
properties: {
|
|
57
|
+
agentId: text,
|
|
58
|
+
content: { type: 'string' },
|
|
59
|
+
attachments,
|
|
60
|
+
attachmentIds,
|
|
61
|
+
goal,
|
|
62
|
+
approval: {
|
|
63
|
+
type: 'object',
|
|
64
|
+
additionalProperties: false,
|
|
65
|
+
required: ['approvalId', 'decision'],
|
|
66
|
+
properties: { approvalId: text, decision: { type: 'string', enum: ['approve', 'reject'] } }
|
|
67
|
+
},
|
|
68
|
+
deliveryIntent: { type: 'string', enum: ['SEND', 'QUEUE', 'REPLACE_CURRENT'] },
|
|
69
|
+
clientMessageId: text
|
|
70
|
+
},
|
|
71
|
+
required: ['agentId']
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
action: 'wait',
|
|
75
|
+
description: 'Wait for completion or messages; defaults to direct children, completion and 30 seconds. Use waitMs 0 for a snapshot.',
|
|
76
|
+
properties: {
|
|
77
|
+
agentId: text,
|
|
78
|
+
agentIds: { type: 'array', items: text, minItems: 1, maxItems: 8 },
|
|
79
|
+
mode: { type: 'string', enum: ['any', 'all'] },
|
|
80
|
+
waitFor: { type: 'string', enum: ['message', 'completion'] },
|
|
81
|
+
waitMs: { type: 'integer', minimum: 0, maximum: 300000 }
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
action: 'stop',
|
|
86
|
+
description: 'Stop the specified Agent Execution and clear its Goal.',
|
|
87
|
+
properties: { agentId: text, executionId: text },
|
|
88
|
+
required: ['agentId', 'executionId']
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
action: 'list',
|
|
92
|
+
description: 'List Sessions, descendants or a Runner profile.',
|
|
93
|
+
properties: {
|
|
94
|
+
scope: { type: 'string', enum: ['workspace', 'children', 'descendants', 'profile'] },
|
|
95
|
+
agentId: text,
|
|
96
|
+
parent: text,
|
|
97
|
+
runner: text,
|
|
98
|
+
query: { type: 'string' },
|
|
99
|
+
limit: { type: 'integer', minimum: 1, maximum: 100 },
|
|
100
|
+
cursor: text
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
action: 'view',
|
|
105
|
+
description: 'View an Agent session, Goal and recent history.',
|
|
106
|
+
properties: {
|
|
107
|
+
agentId: text,
|
|
108
|
+
includeHistory: { type: 'boolean' },
|
|
109
|
+
limit: { type: 'integer', minimum: 1, maximum: 100 },
|
|
110
|
+
cursor: text
|
|
111
|
+
},
|
|
112
|
+
required: ['agentId']
|
|
113
|
+
}
|
|
114
|
+
];
|
|
115
|
+
export function agentNetworkExtension(invoke, currentExecution) {
|
|
116
|
+
return {
|
|
117
|
+
id: 'mar.node.agent-network',
|
|
118
|
+
apiVersion: '1.0',
|
|
119
|
+
attachSession(context) {
|
|
120
|
+
for (const tool of tools)
|
|
121
|
+
context.tools.register({
|
|
122
|
+
name: `agent_${tool.action}`,
|
|
123
|
+
description: tool.description,
|
|
124
|
+
allowedCallers: ['direct', 'programmatic'],
|
|
125
|
+
eventInputFields: [
|
|
126
|
+
'agentId',
|
|
127
|
+
'title',
|
|
128
|
+
'executionId',
|
|
129
|
+
'runner',
|
|
130
|
+
'model',
|
|
131
|
+
'effort',
|
|
132
|
+
'waitFor',
|
|
133
|
+
'waitMs',
|
|
134
|
+
'scope'
|
|
135
|
+
],
|
|
136
|
+
inputSchema: {
|
|
137
|
+
type: 'object',
|
|
138
|
+
properties: tool.properties,
|
|
139
|
+
...(tool.required ? { required: [...tool.required] } : {}),
|
|
140
|
+
additionalProperties: false
|
|
141
|
+
},
|
|
142
|
+
async execute(input, call) {
|
|
143
|
+
const executionId = currentExecution();
|
|
144
|
+
if (!executionId)
|
|
145
|
+
throw new Error('AGENT_ACTOR_NOT_ACTIVE');
|
|
146
|
+
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
|
+
const modelResult = tool.action === 'wait' ? agentNetworkWaitModelOutput(result) : result;
|
|
158
|
+
return {
|
|
159
|
+
data: result,
|
|
160
|
+
content: JSON.stringify(modelResult),
|
|
161
|
+
eventSummary: JSON.stringify(eventData ?? {}),
|
|
162
|
+
eventData: eventData ?? {}
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { isPlainRecord } from '../../util/records.js';
|
|
2
|
+
function prefix(value, bytes) {
|
|
3
|
+
const input = Buffer.from(value, 'utf8');
|
|
4
|
+
let end = Math.min(bytes, input.length);
|
|
5
|
+
while (end > 0 && end < input.length && (input[end] & 0xc0) === 0x80)
|
|
6
|
+
end--;
|
|
7
|
+
return input.subarray(0, end).toString('utf8');
|
|
8
|
+
}
|
|
9
|
+
function projectAgent(input, maximumBytes) {
|
|
10
|
+
const result = {
|
|
11
|
+
...input,
|
|
12
|
+
...(Array.isArray(input.changedFiles) ? { changedFiles: [...input.changedFiles] } : {}),
|
|
13
|
+
...(Array.isArray(input.updates)
|
|
14
|
+
? { updates: input.updates.map((item) => (isPlainRecord(item) ? { ...item } : item)) }
|
|
15
|
+
: {})
|
|
16
|
+
};
|
|
17
|
+
const size = () => Buffer.byteLength(JSON.stringify(result), 'utf8');
|
|
18
|
+
while (size() > maximumBytes &&
|
|
19
|
+
Array.isArray(result.changedFiles) &&
|
|
20
|
+
result.changedFiles.length) {
|
|
21
|
+
result.changedFiles.pop();
|
|
22
|
+
result.changedFilesTruncated = true;
|
|
23
|
+
}
|
|
24
|
+
const shorten = (target, field, flag) => {
|
|
25
|
+
if (size() <= maximumBytes || typeof target[field] !== 'string')
|
|
26
|
+
return;
|
|
27
|
+
const value = target[field];
|
|
28
|
+
let low = 0;
|
|
29
|
+
let high = Buffer.byteLength(value, 'utf8');
|
|
30
|
+
target[flag] = true;
|
|
31
|
+
while (low < high) {
|
|
32
|
+
const middle = Math.ceil((low + high) / 2);
|
|
33
|
+
target[field] = prefix(value, middle);
|
|
34
|
+
if (size() <= maximumBytes)
|
|
35
|
+
low = middle;
|
|
36
|
+
else
|
|
37
|
+
high = middle - 1;
|
|
38
|
+
}
|
|
39
|
+
target[field] = prefix(value, low);
|
|
40
|
+
};
|
|
41
|
+
shorten(result, 'summary', 'summaryTruncated');
|
|
42
|
+
if (Array.isArray(result.updates))
|
|
43
|
+
for (const update of result.updates)
|
|
44
|
+
if (isPlainRecord(update))
|
|
45
|
+
shorten(update, 'value', 'truncated');
|
|
46
|
+
if (size() <= maximumBytes)
|
|
47
|
+
return result;
|
|
48
|
+
return {
|
|
49
|
+
agentId: input.agentId,
|
|
50
|
+
runId: input.runId,
|
|
51
|
+
executionId: input.executionId,
|
|
52
|
+
status: input.status,
|
|
53
|
+
waitOutcome: input.waitOutcome,
|
|
54
|
+
detailsTruncated: true
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
/** Limit only the model-facing view; the Node use-case result remains unchanged. */
|
|
58
|
+
export function agentNetworkWaitModelOutput(input) {
|
|
59
|
+
if (!isPlainRecord(input))
|
|
60
|
+
return input;
|
|
61
|
+
if (!Array.isArray(input.agents))
|
|
62
|
+
return {
|
|
63
|
+
...projectAgent(input, 34_000),
|
|
64
|
+
...(typeof input.waitFor === 'string' ? { waitFor: input.waitFor } : {}),
|
|
65
|
+
...(typeof input.reason === 'string' ? { reason: input.reason } : {})
|
|
66
|
+
};
|
|
67
|
+
const budget = Math.floor(34_000 / Math.max(1, input.agents.length));
|
|
68
|
+
return {
|
|
69
|
+
...input,
|
|
70
|
+
agents: input.agents.map((item) => (isPlainRecord(item) ? projectAgent(item, budget) : item))
|
|
71
|
+
};
|
|
72
|
+
}
|