@myagentroam/node 0.9.135 → 0.9.137

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.
@@ -237,6 +237,9 @@ export class CodexAppServerClient {
237
237
  listThreads() {
238
238
  return this.request('thread/list', {});
239
239
  }
240
+ readThread(threadId) {
241
+ return this.request('thread/read', { threadId, includeTurns: false });
242
+ }
240
243
  listCollaborationModes() {
241
244
  return this.request('collaborationMode/list', {});
242
245
  }
@@ -251,6 +254,17 @@ export class CodexAppServerClient {
251
254
  ...codexThreadConfiguration(cwd, configuration)
252
255
  });
253
256
  }
257
+ revertThread(threadId, beforeTurnId) {
258
+ return this.request('thread/revert', { threadId, beforeTurnId });
259
+ }
260
+ listThreadTurns(threadId, input) {
261
+ return this.request('thread/turns/list', {
262
+ threadId,
263
+ ...input,
264
+ itemsView: 'full',
265
+ sortDirection: 'desc'
266
+ });
267
+ }
254
268
  setThreadName(threadId, name) {
255
269
  return this.request('thread/name/set', { threadId, name });
256
270
  }
package/dist/connector.js CHANGED
@@ -1075,12 +1075,14 @@ export class NodeConnector {
1075
1075
  const session = this.runtime.getAgentSession(sessionId);
1076
1076
  return session?.channelToken ?? undefined;
1077
1077
  },
1078
- goalMessage: (session, content) => {
1078
+ goalMessage: (session, content, intent) => {
1079
1079
  const state = this.commandStates.get(session.id, 'goal');
1080
1080
  if (state?.active !== true)
1081
1081
  return undefined;
1082
1082
  const current = this.goalRuntime.get(session.id);
1083
1083
  if (current?.status === 'active') {
1084
+ if (intent !== 'REPLACE_CURRENT')
1085
+ return undefined;
1084
1086
  this.invalidateGoalContinuation(session.id);
1085
1087
  const resumed = this.goalRuntime.resume(session.id);
1086
1088
  if (resumed !== current)
@@ -1111,7 +1113,7 @@ export class NodeConnector {
1111
1113
  prepareGoalInsertion: (sessionId, submissionId, goal) => {
1112
1114
  if (!this.goalGenerationCurrent(sessionId, goal))
1113
1115
  return false;
1114
- this.continuousRunCoordinator.removePendingQueue(sessionId, submissionId);
1116
+ this.continuousRunCoordinator.removeGoalQueue(sessionId, undefined, submissionId);
1115
1117
  return true;
1116
1118
  },
1117
1119
  goalPreparationFailed: (session, goal, activated) => this.handleGoalPreparationFailed(session, goal, activated)
@@ -6,6 +6,14 @@ export function nodeExecutionEnvironment(provided = {}, inherited = process.env,
6
6
  const environment = Object.fromEntries(Object.entries(inherited).filter((entry) => typeof entry[1] === 'string'));
7
7
  Object.assign(environment, provided);
8
8
  environment['MAR_NODE_CONTEXT'] = '1';
9
+ // Never identify a new execution as the user that launched the Node service.
10
+ const username = provided['MAR_USERNAME'] ?? '';
11
+ if (username.length > 128 || /[\r\n\0]/u.test(username))
12
+ throw new Error('SECRET_ENVIRONMENT_INVALID');
13
+ for (const key of Object.keys(environment))
14
+ if (key.toUpperCase() === 'MAR_USERNAME')
15
+ delete environment[key];
16
+ environment['MAR_USERNAME'] = username;
9
17
  const platform = options.platform ?? process.platform;
10
18
  const packageRoot = options.packageRoot ?? resolve(dirname(fileURLToPath(import.meta.url)), '..');
11
19
  const cliBins = [join(packageRoot, 'node_modules', '.bin')];
@@ -1,3 +1,4 @@
1
+ import { createHash } from 'node:crypto';
1
2
  import { mcpRuntimeDescriptorSchema } from '@myagentroam/protocol';
2
3
  import { nodeExecutionEnvironment } from '../node-execution-environment.js';
3
4
  /**
@@ -78,8 +79,12 @@ export class AbstractRunner {
78
79
  void [session, boundary, title];
79
80
  throw new RunnerCapabilityError(this.name, 'session.rewind');
80
81
  }
81
- async rollbackNative(session, boundary) {
82
- void [session, boundary];
82
+ supportsRollback(session) {
83
+ void session;
84
+ return false;
85
+ }
86
+ async rollbackNative(session, boundary, targetTurnBoundary) {
87
+ void [session, boundary, targetTurnBoundary];
83
88
  throw new RunnerCapabilityError(this.name, 'session.rollback');
84
89
  }
85
90
  async clearNative(session) {
@@ -108,8 +113,8 @@ export class AbstractRunner {
108
113
  void [session, nativeSessionId];
109
114
  return undefined;
110
115
  }
111
- async readOfficialConversation(session, input, managedTurn) {
112
- void [session, input, managedTurn];
116
+ async readOfficialConversation(session, input, managedTurn, registerImages) {
117
+ void [session, input, managedTurn, registerImages];
113
118
  return undefined;
114
119
  }
115
120
  async readConversationHistory(readers) {
@@ -168,6 +173,7 @@ function resolvedMcpServers(raw, secrets) {
168
173
  if (!Array.isArray(raw))
169
174
  return [];
170
175
  const marmgrEnvironment = {
176
+ MAR_USERNAME: secrets['MAR_USERNAME'] ?? '',
171
177
  ...(secrets['MAR_MGR_SERVER_URL'] === undefined
172
178
  ? {}
173
179
  : { MAR_MGR_SERVER_URL: secrets['MAR_MGR_SERVER_URL'] }),
@@ -196,6 +202,16 @@ function resolvedMcpServers(raw, secrets) {
196
202
  result.push({
197
203
  name: record['serverName'],
198
204
  lifecycle: 'EXECUTION',
205
+ origin: 'BUILTIN',
206
+ allowedToolCallers: record['__marGoal'] === true
207
+ ? { mar_get_goal: ['direct', 'programmatic'], mar_update_goal: ['direct'] }
208
+ : record['__marScheduled'] === true
209
+ ? {
210
+ mar_get_scheduled_task: ['direct', 'programmatic'],
211
+ mar_create_scheduled_task: ['direct'],
212
+ mar_update_scheduled_task: ['direct']
213
+ }
214
+ : { mar_send_email_notification: ['direct'] },
199
215
  transport: 'STDIO',
200
216
  command: record['command'],
201
217
  args: record['args'],
@@ -221,6 +237,15 @@ function resolvedMcpServers(raw, secrets) {
221
237
  if (!runtime.success)
222
238
  continue;
223
239
  const configuration = isRecord(record['configuration']) ? record['configuration'] : {};
240
+ const sourceIdentity = createHash('sha256')
241
+ .update(JSON.stringify({
242
+ id: record['id'] ?? null,
243
+ name: record['serverName'],
244
+ runtime: runtime.data,
245
+ configuration
246
+ }))
247
+ .digest('hex');
248
+ const authorizationIdentity = secrets['MAR_USERNAME']?.trim() || undefined;
224
249
  const resolveReferences = (candidate) => {
225
250
  if (!isRecord(candidate))
226
251
  return {};
@@ -242,6 +267,9 @@ function resolvedMcpServers(raw, secrets) {
242
267
  continue;
243
268
  result.push({
244
269
  name: record['serverName'],
270
+ origin: 'USER',
271
+ sourceIdentity,
272
+ ...(authorizationIdentity === undefined ? {} : { authorizationIdentity }),
245
273
  transport: 'STDIO',
246
274
  command: runtime.data.command,
247
275
  args: [
@@ -263,6 +291,9 @@ function resolvedMcpServers(raw, secrets) {
263
291
  continue;
264
292
  result.push({
265
293
  name: record['serverName'],
294
+ origin: 'USER',
295
+ sourceIdentity,
296
+ ...(authorizationIdentity === undefined ? {} : { authorizationIdentity }),
266
297
  transport: 'HTTP',
267
298
  url: runtime.data.url,
268
299
  headers: {
@@ -180,7 +180,8 @@ export class ClaudeManagedRunController {
180
180
  : {}),
181
181
  mcpServers: this.runner.mcpConfiguration(payload.mcpInstallations, {
182
182
  ...secretEnvironment,
183
- ...mcpSecretEnvironment
183
+ ...mcpSecretEnvironment,
184
+ MAR_USERNAME: secretEnvironment['MAR_USERNAME'] ?? ''
184
185
  }),
185
186
  ...(attachments.length === 0
186
187
  ? {}
@@ -331,6 +332,12 @@ export class ClaudeManagedRunController {
331
332
  }
332
333
  }
333
334
  if (record.type === 'result') {
335
+ const nativeTtft = [record.ttft_ms, record.ttft_stream_ms].find((value) => typeof value === 'number' &&
336
+ Number.isFinite(value) &&
337
+ value >= 0 &&
338
+ Number.isSafeInteger(Math.round(value)));
339
+ if (nativeTtft !== undefined)
340
+ this.host.emit(runId, 'run.performance', { latencyMs: Math.round(nativeTtft) });
334
341
  const subtype = typeof record.subtype === 'string' ? record.subtype : 'unknown';
335
342
  const completedNativeSessionId = this.runner.execution.runs.get(runId)?.nativeSessionId ?? nativeSessionId;
336
343
  this.host.emit(runId, 'turn.completed', {
@@ -1,3 +1,6 @@
1
+ import { basename, extname } from 'node:path';
2
+ import { runnerErrorProjection } from '../../util/runner-error.js';
3
+ import { conversationImageAttachmentSchema } from '@myagentroam/protocol';
1
4
  import { parseCodexComposerImageMarkers } from './composer-image-markers.js';
2
5
  import { normalizeCodexTool } from './tool-normalizer.js';
3
6
  import { stripGoalStartPrompt } from '../../service/goal-runtime.js';
@@ -5,6 +8,68 @@ const MAX_COMPOSER_ATTACHMENT_BYTES = 5 * 1024 * 1024;
5
8
  const MAX_COMPOSER_ATTACHMENTS_BYTES = 10 * 1024 * 1024;
6
9
  const MAX_ASSISTANT_MESSAGE_CHARACTERS = 64 * 1024;
7
10
  export * from '../../util/runner-native-session-parsers.js';
11
+ /** Only the official thread descriptor can declare history-only Revert support. */
12
+ export function codexThreadHistoryModes(result) {
13
+ if (!isPlainRecord(result))
14
+ return [];
15
+ const descriptors = Array.isArray(result.data) ? result.data : [result.thread];
16
+ return descriptors.flatMap((value) => isPlainRecord(value) && typeof value.id === 'string'
17
+ ? [{ id: value.id, paginated: value.historyMode === 'paginated' }]
18
+ : []);
19
+ }
20
+ export function codexPaginatedThread(result) {
21
+ return (isPlainRecord(result) &&
22
+ isPlainRecord(result.thread) &&
23
+ result.thread.historyMode === 'paginated');
24
+ }
25
+ export function codexPaginatedCursor(sessionId, cursor) {
26
+ if (cursor === undefined)
27
+ return undefined;
28
+ try {
29
+ const value = JSON.parse(Buffer.from(cursor, 'base64url').toString('utf8'));
30
+ if (isPlainRecord(value) &&
31
+ value.source === 'codex-paginated' &&
32
+ value.sessionId === sessionId &&
33
+ typeof value.cursor === 'string' &&
34
+ value.cursor.length > 0)
35
+ return value.cursor;
36
+ }
37
+ catch {
38
+ // Invalid or foreign cursors never fall back to JSONL.
39
+ }
40
+ throw new Error('EVENT_CURSOR_INVALID');
41
+ }
42
+ export function codexPaginatedConversationPage(session, result, earlierTurnId, managedTurn, registerImages) {
43
+ if (!isPlainRecord(result) || !Array.isArray(result.data))
44
+ throw new Error('NATIVE_TRANSCRIPT_UNAVAILABLE');
45
+ const values = result.data;
46
+ if (values.some((value) => !isPlainRecord(value) ||
47
+ typeof value.id !== 'string' ||
48
+ !Array.isArray(value.items) ||
49
+ value.itemsView !== 'full') ||
50
+ (result.nextCursor !== undefined &&
51
+ result.nextCursor !== null &&
52
+ typeof result.nextCursor !== 'string'))
53
+ throw new Error('NATIVE_TRANSCRIPT_UNAVAILABLE');
54
+ const turns = values.flatMap((value, index) => {
55
+ const native = value;
56
+ const turn = codexOfficialTurn(session, native, index, managedTurn(native.id), registerImages);
57
+ if (turn === undefined)
58
+ return [];
59
+ const earlier = values[index + 1];
60
+ return [{ ...turn, rewind: { before: earlier?.id ?? earlierTurnId, after: native.id } }];
61
+ });
62
+ return {
63
+ turns: turns.reverse(),
64
+ nextCursor: typeof result.nextCursor === 'string'
65
+ ? Buffer.from(JSON.stringify({
66
+ source: 'codex-paginated',
67
+ sessionId: session.id,
68
+ cursor: result.nextCursor
69
+ })).toString('base64url')
70
+ : null
71
+ };
72
+ }
8
73
  export function codexOfficialConversationPage(session, result, input, managedTurnForNative) {
9
74
  if (!isPlainRecord(result) || !isPlainRecord(result.thread))
10
75
  return undefined;
@@ -34,12 +99,36 @@ export function codexOfficialConversationPage(session, result, input, managedTur
34
99
  : null
35
100
  };
36
101
  }
37
- export function codexOfficialTurn(session, value, sequence, managedTurn) {
102
+ export function codexOfficialTurn(session, value, sequence, managedTurn, registerImages) {
38
103
  if (!isPlainRecord(value) || typeof value.id !== 'string' || !Array.isArray(value.items))
39
104
  return undefined;
105
+ const status = value.status === 'inProgress'
106
+ ? 'RUNNING'
107
+ : value.status === 'failed'
108
+ ? 'FAILED'
109
+ : value.status === 'interrupted'
110
+ ? 'INTERRUPTED'
111
+ : 'SUCCEEDED';
40
112
  const startedAt = codexTimestamp(value.startedAt);
41
113
  const completedAt = codexTimestamp(value.completedAt) ?? startedAt;
42
- const items = codexOfficialItems(session, value.id, value.items, sequence, startedAt, completedAt);
114
+ const items = codexOfficialItems(session, value.id, value.items, sequence, startedAt, completedAt, registerImages);
115
+ if ((status === 'FAILED' || status === 'INTERRUPTED') && isPlainRecord(value.error)) {
116
+ const projected = runnerErrorProjection(value.error, 'CODEX_RUN_FAILED');
117
+ items.push({
118
+ id: `${session.id}:official:${value.id}:error`,
119
+ sessionId: session.id,
120
+ turnId: `${session.id}:official:${value.id}`,
121
+ runId: null,
122
+ parentItemId: null,
123
+ sourceSequence: sequence * 10_000 + value.items.length + 1,
124
+ revision: 0,
125
+ kind: 'error',
126
+ status: 'FAILED',
127
+ startedAt,
128
+ completedAt,
129
+ payload: { ...projected, recoverable: true, retryAfterMs: null }
130
+ });
131
+ }
43
132
  if (items.length === 0)
44
133
  return undefined;
45
134
  if (managedTurn !== undefined) {
@@ -98,6 +187,12 @@ export function codexOfficialTurn(session, value, sequence, managedTurn) {
98
187
  let replacement;
99
188
  if (managedItem.kind === 'user_message')
100
189
  replacement = officialUser;
190
+ else if (managedItem.kind === 'error' && isPlainRecord(managedItem.payload))
191
+ replacement = officialItems.find((item) => item.kind === 'error' &&
192
+ isPlainRecord(item.payload) &&
193
+ isPlainRecord(managedItem.payload) &&
194
+ item.payload.code === managedItem.payload.code &&
195
+ item.payload.message === managedItem.payload.message);
101
196
  else if (managedItem.kind === 'context_compaction')
102
197
  replacement = officialContextCompactions.find((item) => !consumedOfficialIds.has(item.id));
103
198
  else {
@@ -123,7 +218,7 @@ export function codexOfficialTurn(session, value, sequence, managedTurn) {
123
218
  reconciledItems.push(item);
124
219
  return {
125
220
  ...managedTurn,
126
- status: officialComplete ? 'SUCCEEDED' : managedTurn.status,
221
+ status: officialComplete ? status : managedTurn.status,
127
222
  // Reconcile against the managed runtime order. Official items replace
128
223
  // matching live projections in place; newly visible terminal items such
129
224
  // as a plan are appended after the commentary and tools that preceded it.
@@ -137,13 +232,13 @@ export function codexOfficialTurn(session, value, sequence, managedTurn) {
137
232
  sessionId: session.id,
138
233
  runId: null,
139
234
  userItemId: items.find((item) => item.kind === 'user_message')?.id ?? null,
140
- status: 'SUCCEEDED',
235
+ status,
141
236
  model: null,
142
237
  effort: null,
143
238
  access: null,
144
239
  items,
145
240
  startedAt,
146
- completedAt
241
+ completedAt: status === 'RUNNING' ? null : completedAt
147
242
  };
148
243
  }
149
244
  /**
@@ -197,15 +292,21 @@ export function conversationItemNativeId(itemId) {
197
292
  return markerIndex < 0 ? undefined : itemId.slice(markerIndex + marker.length);
198
293
  }
199
294
  /** Keep a custom tool invocation and its output in one Workbench card. */
200
- export function codexOfficialItems(session, nativeTurnId, values, turnSequence, startedAt, completedAt) {
295
+ export function codexOfficialItems(session, nativeTurnId, values, turnSequence, startedAt, completedAt, registerImages) {
201
296
  const items = [];
202
297
  const pairedToolIndexes = new Map();
203
298
  // App Server preserves execution order within a Turn. Keep that order while
204
299
  // merging a paired tool invocation with its output into one Workbench card.
205
300
  for (const [itemSequence, value] of values.entries()) {
206
- const item = codexOfficialItem(session, nativeTurnId, value, turnSequence, itemSequence, startedAt, completedAt);
301
+ let item = codexOfficialItem(session, nativeTurnId, value, turnSequence, itemSequence, startedAt, completedAt);
207
302
  if (item === undefined)
208
303
  continue;
304
+ if (isPlainRecord(value)) {
305
+ const sources = codexOfficialImageSources(value, item.id);
306
+ const attachments = sources.map((source) => registerImages?.([source])[0] ?? { name: source.name, mime: source.mime });
307
+ if (attachments.length > 0 && isPlainRecord(item.payload))
308
+ item = { ...item, payload: { ...item.payload, attachments } };
309
+ }
209
310
  // Codex persists one compaction as both `compacted` state and a
210
311
  // `context_compacted` event. App Server normalizes both records to
211
312
  // consecutive `contextCompaction` items without preserving their source.
@@ -229,7 +330,73 @@ export function codexOfficialItems(session, nativeTurnId, values, turnSequence,
229
330
  const result = invocation === item ? previous : item;
230
331
  items[existingIndex] = mergeCodexCustomToolItems(invocation, result);
231
332
  }
232
- return items;
333
+ return [...coalesceImageGenerationItems(items)];
334
+ }
335
+ function codexLocalImageSource(path, name = basename(path)) {
336
+ const mime = {
337
+ '.png': 'image/png',
338
+ '.jpg': 'image/jpeg',
339
+ '.jpeg': 'image/jpeg',
340
+ '.gif': 'image/gif',
341
+ '.webp': 'image/webp'
342
+ }[extname(path).toLowerCase()];
343
+ return mime === undefined ? { name, mime: 'image/unknown' } : { name, mime, localPath: path };
344
+ }
345
+ function codexOfficialImageSources(value, sourceId) {
346
+ if (value.type !== 'userMessage') {
347
+ const path = value.type === 'imageView'
348
+ ? value.path
349
+ : value.type === 'imageGeneration'
350
+ ? value.savedPath
351
+ : undefined;
352
+ return typeof path === 'string' ? [codexLocalImageSource(path)] : [];
353
+ }
354
+ const images = [];
355
+ const paths = new Set();
356
+ let totalBytes = 0;
357
+ const content = Array.isArray(value.content) ? value.content : [];
358
+ for (const part of content) {
359
+ if (!isPlainRecord(part) || images.length >= 5)
360
+ continue;
361
+ const name = `Image #${images.length + 1}`;
362
+ if (part.type === 'localImage' && typeof part.path === 'string') {
363
+ if (!paths.has(part.path)) {
364
+ paths.add(part.path);
365
+ images.push(codexLocalImageSource(part.path, name));
366
+ }
367
+ }
368
+ else if (part.type === 'image') {
369
+ const image = codexImageAttachments([{ type: 'input_image', image_url: part.url }])[0];
370
+ const publicMime = image !== undefined &&
371
+ conversationImageAttachmentSchema.safeParse({ name, mime: image.mime }).success;
372
+ const byteSize = image === undefined ? 0 : Buffer.byteLength(image.dataBase64, 'base64');
373
+ const readable = image !== undefined &&
374
+ publicMime &&
375
+ totalBytes + byteSize <= MAX_COMPOSER_ATTACHMENTS_BYTES;
376
+ if (readable)
377
+ totalBytes += byteSize;
378
+ images.push(!readable || image === undefined
379
+ ? { name, mime: publicMime ? image.mime : 'image/unknown' }
380
+ : {
381
+ name,
382
+ mime: image.mime,
383
+ byteSize,
384
+ sourceId: `${sourceId}:image:${images.length}`,
385
+ read: async () => ({ ...image, name })
386
+ });
387
+ }
388
+ else if (part.type === 'text' && typeof part.text === 'string') {
389
+ for (const marker of parseCodexComposerImageMarkers(part.text).images) {
390
+ if (images.length >= 5)
391
+ break;
392
+ if (!paths.has(marker.localPath)) {
393
+ paths.add(marker.localPath);
394
+ images.push(codexLocalImageSource(marker.localPath, `Image #${marker.index}`));
395
+ }
396
+ }
397
+ }
398
+ }
399
+ return images;
233
400
  }
234
401
  export function codexPairedToolCallId(value) {
235
402
  if (!isPlainRecord(value))
@@ -292,7 +459,8 @@ export function coalesceImageGenerationItems(items) {
292
459
  if (item.kind !== 'tool_call' ||
293
460
  payload?.toolName !== 'imagegen' ||
294
461
  previous?.kind !== 'tool_call' ||
295
- previousPayload?.toolName !== 'imagegen') {
462
+ previousPayload?.toolName !== 'imagegen' ||
463
+ previous.id !== item.id) {
296
464
  merged.push(item);
297
465
  continue;
298
466
  }
@@ -302,8 +470,14 @@ export function coalesceImageGenerationItems(items) {
302
470
  ];
303
471
  merged[merged.length - 1] = {
304
472
  ...previous,
473
+ status: item.status,
305
474
  completedAt: item.completedAt ?? previous.completedAt,
306
- payload: { ...previousPayload, attachments }
475
+ payload: {
476
+ ...previousPayload,
477
+ ...payload,
478
+ inputSummary: previousPayload.inputSummary ?? payload.inputSummary,
479
+ attachments
480
+ }
307
481
  };
308
482
  }
309
483
  return merged;
@@ -348,15 +522,22 @@ export function codexOfficialItem(session, nativeTurnId, value, turnSequence, it
348
522
  turnId: `${session.id}:official:${nativeTurnId}`,
349
523
  runId: null,
350
524
  parentItemId: null,
351
- sourceSequence: turnSequence * 10_000 + itemSequence,
525
+ sourceSequence: turnSequence * 10_000 + itemSequence + 1,
352
526
  revision: 0,
353
- status: 'COMPLETED',
527
+ status: codexItemStatus(value.status, value.status !== 'inProgress'),
354
528
  startedAt,
355
529
  completedAt
356
530
  };
357
531
  if (value.type === 'userMessage') {
358
532
  const text = stripGoalStartPrompt(codexContentText(value.content));
359
- return text.length === 0
533
+ const hasImage = Array.isArray(value.content) &&
534
+ value.content.some((part) => isPlainRecord(part) &&
535
+ (part.type === 'image' ||
536
+ (part.type === 'localImage' && typeof part.path === 'string') ||
537
+ (part.type === 'text' &&
538
+ typeof part.text === 'string' &&
539
+ parseCodexComposerImageMarkers(part.text).images.length > 0)));
540
+ return text.length === 0 && !hasImage
360
541
  ? undefined
361
542
  : {
362
543
  ...base,
@@ -409,7 +409,8 @@ export class CodexManagedRunController {
409
409
  : {}),
410
410
  mcpServers: this.runner.mcpConfiguration(payload.mcpInstallations, {
411
411
  ...secretEnvironment,
412
- ...mcpSecretEnvironment
412
+ ...mcpSecretEnvironment,
413
+ MAR_USERNAME: secretEnvironment['MAR_USERNAME'] ?? ''
413
414
  })
414
415
  }, attachments);
415
416
  }
@@ -1,15 +1,9 @@
1
1
  import { compactRunnerText } from '../../util/node-operation-parsers.js';
2
2
  export function normalizeCodexTool(input) {
3
- const wrapped = unwrapCodexExec(input.name, input.rawInput);
4
- const name = compactRunnerText(wrapped?.name ?? input.name, 128) || 'tool';
5
- const rawInput = wrapped?.rawInput ?? input.rawInput;
3
+ const name = compactRunnerText(input.name, 128) || 'tool';
4
+ const rawInput = input.rawInput;
6
5
  const attachments = input.attachments ?? [];
7
6
  const subagent = codexSubagentProjection(input.namespace, name, rawInput, input.outputSummary);
8
- if (name === 'apply_patch') {
9
- const changes = patchFileChanges(wrapped?.patch);
10
- if (changes.length > 0)
11
- return { kind: 'file_change', payload: { changes } };
12
- }
13
7
  if (isCommandTool(name)) {
14
8
  const outputPreview = compactRunnerText(input.outputSummary ?? '', 10_000);
15
9
  if (input.outputOnly === true)
@@ -70,72 +64,8 @@ export function normalizeCodexTool(input) {
70
64
  }
71
65
  };
72
66
  }
73
- function unwrapCodexExec(name, rawInput) {
74
- if (name !== 'exec' || typeof rawInput !== 'string')
75
- return undefined;
76
- const match = /\btools\.([A-Za-z_][A-Za-z0-9_]*)\s*\(/u.exec(rawInput);
77
- if (match?.[1] === undefined)
78
- return undefined;
79
- const wrappedName = match[1];
80
- if (wrappedName === 'exec_command') {
81
- const command = quotedProperty(rawInput, 'cmd');
82
- const cwd = quotedProperty(rawInput, 'workdir');
83
- return {
84
- name: wrappedName,
85
- rawInput: {
86
- ...(command === undefined ? {} : { cmd: command }),
87
- ...(cwd === undefined ? {} : { cwd })
88
- }
89
- };
90
- }
91
- if (wrappedName === 'apply_patch') {
92
- const patch = quotedVariable(rawInput, 'patch');
93
- return { name: wrappedName, rawInput: patch ?? rawInput, ...(patch ? { patch } : {}) };
94
- }
95
- return { name: wrappedName, rawInput };
96
- }
97
- function quotedProperty(source, key) {
98
- const match = new RegExp(`\\b${key}\\s*:\\s*("(?:\\\\.|[^"\\\\])*")`, 'u').exec(source);
99
- return decodeJsonString(match?.[1]);
100
- }
101
- function quotedVariable(source, name) {
102
- const match = new RegExp(`\\b(?:const|let)\\s+${name}\\s*=\\s*("(?:\\\\.|[^"\\\\])*")`, 'u').exec(source);
103
- return decodeJsonString(match?.[1]);
104
- }
105
- function decodeJsonString(value) {
106
- if (value === undefined)
107
- return undefined;
108
- try {
109
- const parsed = JSON.parse(value);
110
- return typeof parsed === 'string' ? parsed : undefined;
111
- }
112
- catch {
113
- return undefined;
114
- }
115
- }
116
- function patchFileChanges(patch) {
117
- if (patch === undefined)
118
- return [];
119
- const changes = [];
120
- for (const line of patch.split(/\r?\n/u)) {
121
- const match = /^\*\*\* (Add|Delete|Update) File: (.+)$/u.exec(line);
122
- if (match?.[1] === undefined || match[2] === undefined)
123
- continue;
124
- const path = compactRunnerText(match[2], 4_096);
125
- if (path.length === 0)
126
- continue;
127
- changes.push({
128
- path,
129
- change: match[1] === 'Add' ? 'ADDED' : match[1] === 'Delete' ? 'DELETED' : 'MODIFIED',
130
- additions: null,
131
- deletions: null,
132
- diffAvailable: false
133
- });
134
- }
135
- return changes;
136
- }
137
67
  function isCommandTool(name) {
138
- return name === 'exec' || name === 'exec_command';
68
+ return name === 'exec_command';
139
69
  }
140
70
  function record(value) {
141
71
  return value !== null && typeof value === 'object' && !Array.isArray(value)