@myagentroam/node 0.11.39 → 0.11.41

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/dist/connector.js +176 -21
  2. package/dist/database.js +35 -11
  3. package/dist/main.js +12 -1
  4. package/dist/marctl-guidance.js +18 -27
  5. package/dist/migrations/v022.js +58 -0
  6. package/dist/runner/abstract-runner.js +14 -0
  7. package/dist/runner/claude/tool-normalizer.js +6 -1
  8. package/dist/runner/codex/conversation-parser.js +23 -4
  9. package/dist/runner/codex/managed-run-controller.js +4 -2
  10. package/dist/runner/codex/tool-normalizer.js +9 -0
  11. package/dist/runner/execution-presentation.js +56 -0
  12. package/dist/runner/mar-agent/agent-network-extension.js +168 -0
  13. package/dist/runner/mar-agent/agent-network-wait-output.js +72 -0
  14. package/dist/runner/mar-agent/conversation-normalizer.js +232 -118
  15. package/dist/runner/mar-agent/managed-run-controller.js +5 -0
  16. package/dist/runner/mar-agent/marctl-skill.js +3 -3
  17. package/dist/runner/mar-agent/runtime-model-factory.js +1 -0
  18. package/dist/runner/mar-agent/session-runtime.js +34 -1
  19. package/dist/runner/mar-agent-runner.js +36 -0
  20. package/dist/runner/opencode/tool-normalizer.js +6 -1
  21. package/dist/runtime-state.js +57 -0
  22. package/dist/service/agent-network-bindings.js +483 -0
  23. package/dist/service/agent-network-goal.js +19 -0
  24. package/dist/service/agent-network-invoke.js +179 -0
  25. package/dist/service/agent-network-observations.js +377 -0
  26. package/dist/service/agent-network-service.js +315 -0
  27. package/dist/service/agent-network-wait.js +130 -0
  28. package/dist/service/composer-message-input.js +9 -2
  29. package/dist/service/continuous-run-coordinator.js +24 -1
  30. package/dist/service/goal-continuation-coordinator.js +12 -2
  31. package/dist/service/marctl-caller.js +80 -0
  32. package/dist/service/marctl-execution-credential-control.js +65 -0
  33. package/dist/service/model-usage-service.js +9 -0
  34. package/dist/service/node-control-message-service.js +10 -0
  35. package/dist/service/node-request-service.js +52 -7
  36. package/dist/service/node-upgrade-coordinator.js +79 -8
  37. package/dist/service/node-upgrade-credential-control.js +57 -0
  38. package/dist/service/node-upgrade-reporter.js +108 -0
  39. package/dist/service/run-event-service.js +8 -0
  40. package/dist/service/run-workbench-service.js +11 -0
  41. package/dist/service/supervised-node-child.js +153 -0
  42. package/dist/service/supervisor-upgrade-handoff.js +104 -0
  43. package/dist/session/native-session-projection-service.js +69 -6
  44. package/dist/session/operation-admission.js +7 -0
  45. package/dist/session/runtime/conversation-projection.js +25 -3
  46. package/dist/session/session-catalog-service.js +156 -32
  47. package/dist/session/session-lifecycle-service.js +68 -42
  48. package/dist/session/session-message-service.js +16 -6
  49. package/dist/supervisor.js +209 -20
  50. package/dist/terminal.js +41 -21
  51. package/package.json +5 -5
@@ -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: compactRunnerText(output, 10_000),
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: compactRunnerText(output, 10_000),
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: compactRunnerText(params.delta, 10_000) },
288
+ payload: { outputPreview: output.text, output },
287
289
  merge: true,
288
- append: 'outputPreview'
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
+ }