@myagentroam/node 0.11.15 → 0.11.17

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 (53) hide show
  1. package/dist/connector.js +13 -24
  2. package/dist/native-session-history.js +35 -832
  3. package/dist/native-transcript-adapters.js +9 -0
  4. package/dist/native-transcript-model.js +1 -0
  5. package/dist/runner/abstract-runner.js +12 -0
  6. package/dist/runner/claude/conversation-parser.js +80 -1
  7. package/dist/runner/claude/managed-run-controller.js +4 -2
  8. package/dist/runner/claude/message-parsers.js +37 -0
  9. package/dist/runner/claude/native-context-usage.js +90 -0
  10. package/dist/runner/claude/native-transcript.js +347 -0
  11. package/dist/runner/claude/tool-normalizer.js +12 -2
  12. package/dist/runner/claude-code-runner.js +13 -2
  13. package/dist/runner/codex/conversation-parser.js +67 -3
  14. package/dist/runner/codex/managed-run-controller.js +3 -2
  15. package/dist/runner/codex/native-context-usage.js +42 -0
  16. package/dist/runner/codex/native-transcript.js +575 -0
  17. package/dist/runner/codex-runner.js +18 -3
  18. package/dist/runner/mar-agent/managed-run-controller.js +1 -1
  19. package/dist/runner/mar-agent-runner.js +6 -0
  20. package/dist/runner/opencode/conversation-parser.js +1 -1
  21. package/dist/runner/opencode/managed-run-controller.js +1 -1
  22. package/dist/runner/opencode-runner.js +7 -1
  23. package/dist/runner/runner-registry.js +4 -0
  24. package/dist/runtime-state.js +1 -6
  25. package/dist/service/continuous-run-coordinator.js +1 -0
  26. package/dist/service/conversation-template-manifest.js +179 -0
  27. package/dist/service/conversation-template-node-service.js +10 -132
  28. package/dist/service/external-session-resume-service.js +15 -0
  29. package/dist/service/fake-managed-run-service.js +1 -1
  30. package/dist/service/goal-continuation-coordinator.js +4 -3
  31. package/dist/service/goal-runtime.js +0 -9
  32. package/dist/service/hosted-conversation-node-service.js +16 -179
  33. package/dist/service/hosted-conversation-store.js +2 -33
  34. package/dist/service/native-session-projection-service.js +7 -24
  35. package/dist/service/node-control-channel.js +1 -1
  36. package/dist/service/node-control-message-service.js +0 -8
  37. package/dist/service/node-request-service.js +1 -1
  38. package/dist/service/node-run-event-bridge.js +0 -37
  39. package/dist/service/node-terminal-channel.js +1 -1
  40. package/dist/service/recent-run-projection-service.js +2 -1
  41. package/dist/service/run-workbench-service.js +1 -1
  42. package/dist/service/runner-interaction-service.js +2 -1
  43. package/dist/service/runner-service.js +6 -4
  44. package/dist/service/session-identity-service.js +1 -0
  45. package/dist/service/session-message-service.js +1 -0
  46. package/dist/service/session-query-service.js +1 -1
  47. package/dist/service/workbench-event-projection.js +1 -1
  48. package/dist/service/workbench-event-service.js +1 -1
  49. package/dist/service/workspace-file-service.js +0 -2
  50. package/dist/service/workspace-queue-workbench-service.js +2 -2
  51. package/dist/util/node-operation-parsers.js +0 -45
  52. package/dist/util/runner-native-session-parsers.js +17 -194
  53. package/package.json +5 -5
@@ -0,0 +1,575 @@
1
+ import { homedir } from 'node:os';
2
+ import { basename, join } from 'node:path';
3
+ import { forEachJsonlRecord } from '../../native-jsonl-reader.js';
4
+ import { compactRunnerValue as compactValue } from '../../util/node-operation-parsers.js';
5
+ import { stripGoalStartPrompt, publicGoalHistoryText } from '../../service/goal-runtime.js';
6
+ import { parseCodexComposerImageMarkers } from './composer-image-markers.js';
7
+ import { codexTranscriptSourceItemId } from './conversation-parser.js';
8
+ import { normalizeCodexTool } from './tool-normalizer.js';
9
+ function parse(_path, window) {
10
+ const { content, truncated } = window;
11
+ let cwd;
12
+ let id;
13
+ let derivedSession = false;
14
+ const items = [];
15
+ const toolIndexes = new Map();
16
+ const metaRecordIds = new Set();
17
+ let codexTurnId;
18
+ forEachJsonlRecord(content, (value) => {
19
+ if (derivedTranscriptRecord(value))
20
+ derivedSession = true;
21
+ cwd ??= workspacePath(value);
22
+ id ??= sessionId(value);
23
+ });
24
+ forEachJsonlRecord(truncated ? window.pageContent : content, (value) => {
25
+ if (value['type'] === 'event_msg' && isRecord(value['payload'])) {
26
+ const payload = value['payload'];
27
+ if (payload['type'] === 'task_started')
28
+ codexTurnId = typeof payload['turn_id'] === 'string' ? payload['turn_id'] : undefined;
29
+ else if (payload['type'] === 'task_complete')
30
+ codexTurnId = undefined;
31
+ }
32
+ const recordId = typeof value['uuid'] === 'string' ? value['uuid'] : undefined;
33
+ const parentId = typeof value['parentUuid'] === 'string' ? value['parentUuid'] : undefined;
34
+ if (value['isMeta'] === true && recordId !== undefined)
35
+ metaRecordIds.add(recordId);
36
+ if (isHiddenTranscriptRecord(value) ||
37
+ (parentId !== undefined && metaRecordIds.has(parentId))) {
38
+ return;
39
+ }
40
+ const generatedImage = codexGeneratedImageEvent(value);
41
+ if (generatedImage !== undefined)
42
+ items.push(generatedImage);
43
+ const sourceItemId = codexTranscriptSourceItemId(value, codexTurnId);
44
+ for (const parsedItem of transcriptItems(value)) {
45
+ const item = {
46
+ ...parsedItem,
47
+ ...(sourceItemId === undefined ? {} : { sourceItemId }),
48
+ ...(recordId === undefined ? {} : { nativeId: recordId }),
49
+ ...(parsedItem.kind === 'message' && parsedItem.role === 'USER' && codexTurnId !== undefined
50
+ ? { nativeTurnId: codexTurnId }
51
+ : {})
52
+ };
53
+ if (item.kind === 'tool_result') {
54
+ const toolIndex = toolIndexes.get(item.toolUseId);
55
+ if (toolIndex !== undefined)
56
+ items[toolIndex] = withToolResult(items[toolIndex], item);
57
+ continue;
58
+ }
59
+ if (item.kind === 'tool_call') {
60
+ toolIndexes.set(item.toolUseId, items.length);
61
+ items.push(item);
62
+ continue;
63
+ }
64
+ items.push(item);
65
+ }
66
+ });
67
+ if (derivedSession)
68
+ return undefined;
69
+ if (cwd === undefined)
70
+ return undefined;
71
+ const title = items.find((item) => item.kind === 'message' && item.role === 'USER')?.text;
72
+ const strippedTitle = title === undefined ? undefined : stripGoalStartPrompt(title);
73
+ return {
74
+ ...(id === undefined ? {} : { externalSessionId: id }),
75
+ cwd,
76
+ ...(strippedTitle === undefined || strippedTitle.length === 0
77
+ ? {}
78
+ : { title: compact(strippedTitle) }),
79
+ items
80
+ };
81
+ }
82
+ function derivedTranscriptRecord(value) {
83
+ if (value['type'] !== 'session_meta')
84
+ return false;
85
+ const payload = isRecord(value['payload']) ? value['payload'] : undefined;
86
+ if (payload === undefined)
87
+ return false;
88
+ if (payload['thread_source'] === 'subagent')
89
+ return true;
90
+ const source = payload['source'];
91
+ return (source === 'subagent' ||
92
+ (isRecord(source) && (source['kind'] === 'subagent' || isRecord(source['subagent']))));
93
+ }
94
+ function transcriptItems(record) {
95
+ return transcriptRecordItems(record);
96
+ }
97
+ function transcriptRecordItems(record) {
98
+ const createdAt = timestampOf(record);
99
+ const results = codexToolResultsOf(record, createdAt);
100
+ if (results.length > 0)
101
+ return results;
102
+ const values = [
103
+ ...codexToolUsesOf(record, createdAt),
104
+ ...codexReasoningSummariesOf(record, createdAt),
105
+ ...unsupportedCodexResponseItems(record, createdAt),
106
+ ...unsupportedTranscriptItems(record, createdAt)
107
+ ];
108
+ const role = roleOf(record);
109
+ if (role === undefined)
110
+ return values;
111
+ const rawText = messageText(textOf(record) ?? '');
112
+ if (rawText.length === 0)
113
+ return values;
114
+ // Codex transcripts can contain the host's injected AGENTS.md and runtime
115
+ // context as a `user` item before the first actual prompt. It is neither a
116
+ // user-visible chat turn nor a meaningful session title.
117
+ if (role === 'USER' && isInjectedContextPrompt(rawText))
118
+ return values;
119
+ const message = role === 'USER' ? parseCodexComposerImageMarkers(rawText) : { text: rawText, images: [] };
120
+ values.unshift({
121
+ kind: 'message',
122
+ role,
123
+ text: message.text,
124
+ ...(message.images.length === 0
125
+ ? {}
126
+ : {
127
+ imageAttachments: message.images.map((image) => ({
128
+ name: `Image #${image.index}`,
129
+ mime: imageMime(image.localPath),
130
+ localPath: image.localPath
131
+ }))
132
+ }),
133
+ createdAt
134
+ });
135
+ return values;
136
+ }
137
+ function codexToolUsesOf(record, createdAt) {
138
+ const payload = codexToolPayload(record);
139
+ if (payload?.['type'] !== 'custom_tool_call' && payload?.['type'] !== 'function_call')
140
+ return [];
141
+ const toolUseId = firstString(payload, ['call_id', 'id']);
142
+ const toolName = firstString(payload, ['name', 'tool']);
143
+ if (toolUseId === undefined || toolName === undefined)
144
+ return [];
145
+ const input = payload['input'] ?? payload['arguments'];
146
+ const summary = compactValue(input, 10_000);
147
+ return [
148
+ {
149
+ kind: 'tool_call',
150
+ toolUseId,
151
+ toolName: compact(toolName).slice(0, 128),
152
+ input,
153
+ inputSummary: summary.text || null,
154
+ inputTruncated: summary.truncated,
155
+ outputSummary: null,
156
+ outputTruncated: false,
157
+ failed: false,
158
+ createdAt
159
+ }
160
+ ];
161
+ }
162
+ function codexToolResultsOf(record, createdAt) {
163
+ const payload = codexToolPayload(record);
164
+ if (payload?.['type'] !== 'custom_tool_call_output' &&
165
+ payload?.['type'] !== 'function_call_output')
166
+ return [];
167
+ const toolUseId = firstString(payload, ['call_id', 'id']);
168
+ if (toolUseId === undefined)
169
+ return [];
170
+ const output = compactValue(codexToolOutput(payload['output'] ?? payload['contentItems']), 10_000);
171
+ const imageAttachments = codexImagePlaceholders(payload['output'] ?? payload['contentItems']);
172
+ return [
173
+ {
174
+ kind: 'tool_result',
175
+ toolUseId,
176
+ output: output.text,
177
+ failed: payload['status'] === 'failed' || payload['error'] !== undefined,
178
+ truncated: output.truncated,
179
+ ...(imageAttachments.length === 0 ? {} : { imageAttachments }),
180
+ createdAt
181
+ }
182
+ ];
183
+ }
184
+ function codexToolPayload(record) {
185
+ return record['type'] === 'response_item' && isRecord(record['payload'])
186
+ ? record['payload']
187
+ : undefined;
188
+ }
189
+ function codexGeneratedImageEvent(record) {
190
+ if (record['type'] !== 'event_msg' || !isRecord(record['payload']))
191
+ return undefined;
192
+ const payload = record['payload'];
193
+ if (payload['type'] !== 'image_generation_end' ||
194
+ typeof payload['call_id'] !== 'string' ||
195
+ typeof payload['saved_path'] !== 'string')
196
+ return undefined;
197
+ const localPath = payload['saved_path'];
198
+ if (!localPath.includes('/.codex/generated_images/') || !/\.(?:png|jpe?g|webp)$/i.test(localPath))
199
+ return undefined;
200
+ return {
201
+ kind: 'tool_call',
202
+ toolUseId: payload['call_id'],
203
+ toolName: 'imagegen',
204
+ input: null,
205
+ inputSummary: null,
206
+ inputTruncated: false,
207
+ outputSummary: payload['status'] === 'completed' ? 'Image generation completed' : 'Image generation failed',
208
+ outputTruncated: false,
209
+ toolCategory: 'IMAGE_GENERATION',
210
+ imageAttachments: [{ name: basename(localPath), mime: imageMime(localPath), localPath }],
211
+ failed: payload['status'] !== 'completed',
212
+ createdAt: timestampOf(record)
213
+ };
214
+ }
215
+ function codexToolOutput(value) {
216
+ if (!Array.isArray(value))
217
+ return value;
218
+ const text = value.flatMap((block) => {
219
+ if (typeof block === 'string')
220
+ return [block];
221
+ if (!isRecord(block))
222
+ return [];
223
+ const direct = firstString(block, ['text', 'content']);
224
+ return direct === undefined ? [] : [codexVisibleToolText(direct)];
225
+ });
226
+ return text.length > 0 ? text.join('') : value;
227
+ }
228
+ function codexVisibleToolText(value) {
229
+ const marker = 'data:image/';
230
+ let cursor = 0;
231
+ let visible = '';
232
+ while (cursor < value.length) {
233
+ const start = value.indexOf(marker, cursor);
234
+ if (start < 0)
235
+ return visible + value.slice(cursor);
236
+ visible += `${value.slice(cursor, start)}[Image content filtered]`;
237
+ const doubleQuote = value.indexOf('"', start);
238
+ const singleQuote = value.indexOf("'", start);
239
+ const end = doubleQuote < 0
240
+ ? singleQuote
241
+ : singleQuote < 0
242
+ ? doubleQuote
243
+ : Math.min(doubleQuote, singleQuote);
244
+ if (end < 0)
245
+ return visible;
246
+ cursor = end;
247
+ }
248
+ return visible;
249
+ }
250
+ function codexImagePlaceholders(value) {
251
+ const attachments = [];
252
+ let visited = 0;
253
+ const visit = (current, depth) => {
254
+ if (attachments.length >= 5 || visited >= 128 || depth > 5 || current == null)
255
+ return;
256
+ visited += 1;
257
+ if (typeof current === 'string') {
258
+ const paths = [];
259
+ collectGeneratedImagePaths(current, paths);
260
+ for (const localPath of paths) {
261
+ if (attachments.some((attachment) => attachment.localPath === localPath))
262
+ continue;
263
+ attachments.push({ name: basename(localPath), mime: imageMime(localPath), localPath });
264
+ }
265
+ return;
266
+ }
267
+ if (Array.isArray(current)) {
268
+ for (const entry of current)
269
+ visit(entry, depth + 1);
270
+ return;
271
+ }
272
+ if (!isRecord(current))
273
+ return;
274
+ const type = typeof current['type'] === 'string' ? current['type'] : '';
275
+ if (type === 'input_text' && typeof current['text'] === 'string') {
276
+ const mime = /["']image_url["']\s*:\s*["']data:(image\/[^;,"']+)[;,]/.exec(current['text'])?.[1];
277
+ if (mime !== undefined)
278
+ attachments.push({ name: `image-${attachments.length + 1}.${imageExtension(mime)}`, mime });
279
+ }
280
+ const imageUrl = isRecord(current['image_url'])
281
+ ? firstString(current['image_url'], ['url'])
282
+ : typeof current['image_url'] === 'string'
283
+ ? current['image_url']
284
+ : undefined;
285
+ const data = typeof current['data'] === 'string' ? current['data'] : imageUrl;
286
+ if ((type === 'image' || type === 'input_image' || type === 'output_image') &&
287
+ data?.startsWith('data:image/')) {
288
+ const mime = /^data:([^;,]+)[;,]/.exec(data)?.[1] ?? 'image/png';
289
+ attachments.push({ name: `image-${attachments.length + 1}.${imageExtension(mime)}`, mime });
290
+ }
291
+ for (const child of Object.values(current))
292
+ visit(child, depth + 1);
293
+ };
294
+ visit(value, 0);
295
+ return attachments;
296
+ }
297
+ function collectGeneratedImagePaths(value, paths) {
298
+ const marker = '/.codex/generated_images/';
299
+ let cursor = 0;
300
+ while (paths.length < 5) {
301
+ const markerIndex = value.indexOf(marker, cursor);
302
+ if (markerIndex < 0)
303
+ return;
304
+ let start = markerIndex;
305
+ while (start > 0 && !/[\s"']/.test(value[start - 1]))
306
+ start -= 1;
307
+ let end = markerIndex + marker.length;
308
+ while (end < value.length && !/[\s"']/.test(value[end]))
309
+ end += 1;
310
+ const path = value.slice(start, end).replace(/[),.;:]+$/, '');
311
+ if (/\.(?:png|jpe?g|webp)$/i.test(path) && !paths.includes(path))
312
+ paths.push(path);
313
+ cursor = end;
314
+ }
315
+ }
316
+ function imageMime(path) {
317
+ const extension = path.slice(path.lastIndexOf('.') + 1).toLowerCase();
318
+ if (extension === 'jpg' || extension === 'jpeg')
319
+ return 'image/jpeg';
320
+ if (extension === 'webp')
321
+ return 'image/webp';
322
+ return 'image/png';
323
+ }
324
+ function imageExtension(mime) {
325
+ if (mime === 'image/jpeg')
326
+ return 'jpg';
327
+ if (mime === 'image/webp')
328
+ return 'webp';
329
+ if (mime === 'image/gif')
330
+ return 'gif';
331
+ return 'png';
332
+ }
333
+ function codexViewedImagePath(call) {
334
+ const toolName = call.toolName.toLowerCase();
335
+ if (toolName === 'view_image' || toolName === 'imageview') {
336
+ return isRecord(call.input) && typeof call.input['path'] === 'string'
337
+ ? call.input['path']
338
+ : undefined;
339
+ }
340
+ if (toolName !== 'exec' || typeof call.input !== 'string')
341
+ return undefined;
342
+ if (!/tools\.view_image\s*\(/.test(call.input))
343
+ return undefined;
344
+ const encodedPath = /["']path["']\s*:\s*("(?:\\.|[^"\\])*")/.exec(call.input)?.[1];
345
+ if (encodedPath === undefined)
346
+ return undefined;
347
+ try {
348
+ const path = JSON.parse(encodedPath);
349
+ return typeof path === 'string' ? path : undefined;
350
+ }
351
+ catch {
352
+ return undefined;
353
+ }
354
+ }
355
+ function isInjectedContextPrompt(text) {
356
+ const normalized = text.replace(/\s+/g, ' ').trim();
357
+ if (normalized.startsWith('<codex_internal_context'))
358
+ return true;
359
+ if (/^<(?:environment_context|subagent_notification|turn_aborted)(?:\s|>)/u.test(normalized))
360
+ return true;
361
+ return (normalized.startsWith('# AGENTS.md instructions') &&
362
+ (normalized.includes('<INSTRUCTIONS>') || normalized.includes('<environment_context>')));
363
+ }
364
+ function unsupportedCodexResponseItems(record, createdAt) {
365
+ const payload = codexToolPayload(record);
366
+ const type = typeof payload?.type === 'string' ? payload.type : undefined;
367
+ if (type === undefined ||
368
+ typeof payload?.role === 'string' ||
369
+ type === 'reasoning' ||
370
+ type === 'custom_tool_call' ||
371
+ type === 'custom_tool_call_output' ||
372
+ type === 'function_call' ||
373
+ type === 'function_call_output')
374
+ return [];
375
+ return [
376
+ {
377
+ kind: 'unknown',
378
+ sourceEventType: `codex:${type}`,
379
+ label: `Codex ${compact(type).slice(0, 128)}`,
380
+ createdAt
381
+ }
382
+ ];
383
+ }
384
+ function codexReasoningSummariesOf(record, createdAt) {
385
+ const payload = codexToolPayload(record);
386
+ if (payload?.type !== 'reasoning')
387
+ return [];
388
+ const sections = Array.isArray(payload.summary)
389
+ ? payload.summary.flatMap((value, index) => {
390
+ if (typeof value !== 'string')
391
+ return [];
392
+ const text = compact(value).slice(0, 10_000);
393
+ return text.length === 0 ? [] : [{ index, text }];
394
+ })
395
+ : [];
396
+ return [{ kind: 'reasoning_summary', representation: 'summary', sections, createdAt }];
397
+ }
398
+ function withToolResult(call, result) {
399
+ const viewedImagePath = codexViewedImagePath(call);
400
+ const imageAttachments = result.imageAttachments?.map((attachment) => attachment.localPath !== undefined || viewedImagePath === undefined
401
+ ? attachment
402
+ : {
403
+ name: basename(viewedImagePath),
404
+ mime: imageMime(viewedImagePath),
405
+ localPath: viewedImagePath
406
+ });
407
+ return {
408
+ ...call,
409
+ outputSummary: result.output,
410
+ outputTruncated: result.truncated,
411
+ ...(imageAttachments === undefined ? {} : { imageAttachments }),
412
+ failed: result.failed
413
+ };
414
+ }
415
+ function workspacePath(record) {
416
+ return (firstString(record, ['cwd', 'working_directory', 'workspacePath']) ??
417
+ nestedString(record, ['payload', 'cwd']) ??
418
+ nestedString(record, ['message', 'cwd']));
419
+ }
420
+ function sessionId(record) {
421
+ // A Codex sub-agent transcript records its own thread in `payload.id`, but
422
+ // retains the parent thread in `payload.session_id`. Treating the latter
423
+ // as this file's identity makes the child file overwrite the parent thread
424
+ // in the discovery index, which then shows unrelated messages for the
425
+ // parent Chat.
426
+ if (record['type'] === 'session_meta') {
427
+ const transcriptId = nestedString(record, ['payload', 'id']);
428
+ if (transcriptId !== undefined)
429
+ return transcriptId;
430
+ }
431
+ return (firstString(record, ['sessionId', 'session_id', 'thread_id', 'conversationId']) ??
432
+ nestedString(record, ['payload', 'id']) ??
433
+ nestedString(record, ['payload', 'session_id']) ??
434
+ nestedString(record, ['payload', 'thread_id']));
435
+ }
436
+ function timestampOf(record) {
437
+ const raw = record['timestamp'] ?? record['created_at'] ?? record['createdAt'];
438
+ if (typeof raw === 'number' && Number.isFinite(raw))
439
+ return raw < 10_000_000_000 ? raw * 1_000 : raw;
440
+ if (typeof raw === 'string') {
441
+ const value = Date.parse(raw);
442
+ return Number.isNaN(value) ? null : value;
443
+ }
444
+ return null;
445
+ }
446
+ function firstString(value, keys) {
447
+ for (const key of keys)
448
+ if (typeof value[key] === 'string' && value[key].length > 0)
449
+ return value[key];
450
+ return undefined;
451
+ }
452
+ function nestedString(value, keys) {
453
+ let current = value;
454
+ for (const key of keys) {
455
+ if (!isRecord(current))
456
+ return undefined;
457
+ current = current[key];
458
+ }
459
+ return typeof current === 'string' && current.length > 0 ? current : undefined;
460
+ }
461
+ function compact(value) {
462
+ return value.replace(/\s+/g, ' ').trim().slice(0, 16_000);
463
+ }
464
+ function messageText(value) {
465
+ return value.trim().slice(0, 16_000);
466
+ }
467
+ function roleOf(record) {
468
+ const candidates = [
469
+ firstString(record, ['role']),
470
+ nestedString(record, ['message', 'role']),
471
+ nestedString(record, ['payload', 'role']),
472
+ firstString(record, ['type']),
473
+ nestedString(record, ['payload', 'type'])
474
+ ];
475
+ if (candidates.some((value) => value === 'user' || value === 'human' || value === 'input'))
476
+ return 'USER';
477
+ if (candidates.some((value) => value === 'assistant' || value === 'agent' || value === 'output'))
478
+ return 'ASSISTANT';
479
+ return undefined;
480
+ }
481
+ function textOf(record) {
482
+ return (firstString(record, ['content', 'text']) ??
483
+ nestedText(record['message']) ??
484
+ nestedText(record['payload']));
485
+ }
486
+ function nestedText(value) {
487
+ if (typeof value === 'string')
488
+ return value;
489
+ if (!isRecord(value))
490
+ return undefined;
491
+ const direct = firstString(value, ['content', 'text', 'message']);
492
+ if (direct !== undefined)
493
+ return direct;
494
+ if (Array.isArray(value['content'])) {
495
+ const parts = value['content'].flatMap((part) => {
496
+ if (typeof part === 'string')
497
+ return [part];
498
+ if (!isRecord(part))
499
+ return [];
500
+ return [firstString(part, ['text', 'content'])].filter((text) => text !== undefined);
501
+ });
502
+ return parts.join('\n');
503
+ }
504
+ return undefined;
505
+ }
506
+ function contentBlocks(record) {
507
+ const values = [record['message'], record['payload']];
508
+ for (const value of values)
509
+ if (isRecord(value) && Array.isArray(value['content']))
510
+ return value['content'].filter(isRecord);
511
+ return [];
512
+ }
513
+ function isHiddenTranscriptRecord(record) {
514
+ if (contentBlocks(record).some((block) => block['type'] === 'tool_result'))
515
+ return false;
516
+ if (record['interruptedByShutdown'] === true ||
517
+ record['isMeta'] === true ||
518
+ record['isVisibleInTranscriptOnly'] === true ||
519
+ record['isCompactSummary'] === true)
520
+ return true;
521
+ const text = textOf(record)?.trim() ?? '';
522
+ return (text.startsWith('<local-command-') ||
523
+ text.startsWith('<command-name>') ||
524
+ text.startsWith('<command-message>'));
525
+ }
526
+ function unsupportedTranscriptItems(record, createdAt) {
527
+ return contentBlocks(record).flatMap((block) => {
528
+ const type = typeof block.type === 'string' ? block.type : undefined;
529
+ if (type === undefined ||
530
+ type === 'text' ||
531
+ type === 'input_text' ||
532
+ type === 'output_text' ||
533
+ type === 'image' ||
534
+ type === 'input_image' ||
535
+ type === 'thinking' ||
536
+ type === 'redacted_thinking' ||
537
+ type === 'encrypted_thinking' ||
538
+ type === 'tool_use' ||
539
+ type === 'tool_result')
540
+ return [];
541
+ return [
542
+ {
543
+ kind: 'unknown',
544
+ sourceEventType: `transcript:${type}`,
545
+ label: `Agent ${compact(type).slice(0, 128)}`,
546
+ createdAt
547
+ }
548
+ ];
549
+ });
550
+ }
551
+ function isRecord(value) {
552
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
553
+ }
554
+ export const codexNativeTranscriptAdapter = {
555
+ runner: 'codex',
556
+ transcriptRoot: () => join(homedir(), '.codex', 'sessions'),
557
+ includesPath: () => true,
558
+ parse,
559
+ createProjection() {
560
+ return {
561
+ userText: (text) => parseCodexComposerImageMarkers(publicGoalHistoryText(text)).text,
562
+ planText: () => undefined,
563
+ normalizeTool: (entry) => normalizeCodexTool({
564
+ name: entry.toolName,
565
+ namespace: 'codex',
566
+ rawInput: entry.input,
567
+ inputSummary: entry.inputSummary,
568
+ outputSummary: entry.outputSummary,
569
+ truncated: entry.inputTruncated || entry.outputTruncated,
570
+ ...(entry.imageAttachments === undefined ? {} : { attachments: entry.imageAttachments })
571
+ }),
572
+ userInputQuestions: () => []
573
+ };
574
+ }
575
+ };
@@ -5,10 +5,12 @@ import { declaredRunnerProfiles } from '../runner-profiles.js';
5
5
  import { homedir } from 'node:os';
6
6
  import { resolve } from 'node:path';
7
7
  import { discoverAllNativeSessions } from '../native-session-history.js';
8
- import { readCodexNativeContextUsage } from '../native-session-history.js';
9
- import { codexThreadCwd, codexThreadHistoryModes, codexPaginatedThread, codexPaginatedCursor, codexPaginatedConversationPage, deduplicateDirectSessions, extractCodexThreads, isWithinWorkspace, latestNativeActivity } from './codex/conversation-parser.js';
8
+ import { readCodexNativeContextUsage } from './codex/native-context-usage.js';
9
+ import { codexThreadCwd, extractCodexThreads } from './codex/conversation-parser.js';
10
+ import { deduplicateDirectSessions, isWithinWorkspace, latestNativeActivity } from '../util/runner-native-session-parsers.js';
11
+ import { codexThreadHistoryModes, codexPaginatedThread, codexPaginatedCursor, codexPaginatedConversationPage } from './codex/conversation-parser.js';
10
12
  import { codexSessionControl } from '../codex-app-server.js';
11
- import { extractId } from '../util/runner-native-session-parsers.js';
13
+ import { extractId } from './codex/conversation-parser.js';
12
14
  import { nodeLog } from '../operational.js';
13
15
  import { conversationClientMessageId } from '../util/conversation-identity.js';
14
16
  const CODEX_MODEL_CACHE_MS = 10 * 60 * 1_000;
@@ -43,6 +45,16 @@ export class CodexRunner extends AbstractRunner {
43
45
  (client === undefined ? () => new CodexAppServerClient() : () => this.client);
44
46
  }
45
47
  client;
48
+ migrateSessionIdentity(previousId, nativeSessionId) {
49
+ for (const [turnId, managed] of this.execution.managedTurns) {
50
+ if (managed.sessionId === previousId)
51
+ this.execution.managedTurns.set(turnId, { ...managed, sessionId: nativeSessionId });
52
+ }
53
+ for (const [runId, managed] of this.execution.runs) {
54
+ if (managed.sessionId === previousId)
55
+ this.execution.runs.set(runId, { ...managed, sessionId: nativeSessionId });
56
+ }
57
+ }
46
58
  profile(capabilities) {
47
59
  const base = declaredRunnerProfiles({
48
60
  codexAvailable: capabilities.codex.available,
@@ -375,6 +387,9 @@ export class CodexRunner extends AbstractRunner {
375
387
  return (this.profileCacheForEnvironment(environment)?.defaultConfiguration ??
376
388
  emptyCodexConfiguration());
377
389
  }
390
+ goalAccess(currentAccess) {
391
+ return { access: 'full-access', accessChanged: currentAccess !== 'full-access' };
392
+ }
378
393
  supportsConfiguration(configuration, _workspace, environment) {
379
394
  if (configuration.runner !== this.name)
380
395
  return false;
@@ -8,7 +8,7 @@ import { homedir } from 'node:os';
8
8
  import { nodeLog } from '../../operational.js';
9
9
  import { createMarAgent, generateSessionTitle } from '@myagentroam/agent';
10
10
  import { marAgentReasoningEffortSchema, resolveMarAgentDefaultReasoningEffort } from '@myagentroam/protocol';
11
- import { boundedConversationItemId } from '../codex/conversation-parser.js';
11
+ import { boundedConversationItemId } from '../../util/node-operation-parsers.js';
12
12
  import { parseSecretEnvironment } from '../../util/secret-environment.js';
13
13
  import { MarAgentMcpClientCache } from './mcp-client-pool.js';
14
14
  import { MarAgentConversationNormalizer, normalizeMarAgentUsage } from './conversation-normalizer.js';
@@ -200,6 +200,12 @@ export class MarAgentRunner extends AbstractRunner {
200
200
  access: 'auto'
201
201
  };
202
202
  }
203
+ goalAccess(currentAccess) {
204
+ return { access: currentAccess ?? 'full-access', accessChanged: false };
205
+ }
206
+ restoreStoredAccess(access, _fallbackAccess) {
207
+ return access === 'auto' || access === 'full-access' ? access : 'full-access';
208
+ }
203
209
  presentSession(session, capabilities, context) {
204
210
  void capabilities;
205
211
  const activity = context.managedActive ? 'MANAGED_ACTIVE' : 'IDLE';
@@ -1,4 +1,4 @@
1
- import { boundedConversationItemId, compactRunnerText } from '../codex/conversation-parser.js';
1
+ import { boundedConversationItemId, compactRunnerText } from '../../util/node-operation-parsers.js';
2
2
  import { publicGoalHistoryText } from '../../service/goal-runtime.js';
3
3
  import { normalizeOpenCodeTool } from './tool-normalizer.js';
4
4
  import { RunnerUsageTracker, usageToken, usageTokenSum } from '../../service/runner-usage-tracker.js';
@@ -3,7 +3,7 @@ import { parseComposerTags } from '@myagentroam/protocol';
3
3
  import { nodeExecutionEnvironment } from '../../node-execution-environment.js';
4
4
  import { parseSecretEnvironment } from '../../util/secret-environment.js';
5
5
  import { runnerErrorProjection } from '../../util/runner-error.js';
6
- import { boundedConversationItemId, compactRunnerText } from '../codex/conversation-parser.js';
6
+ import { boundedConversationItemId, compactRunnerText } from '../../util/node-operation-parsers.js';
7
7
  import { openCodePatchToolPayload, openCodeQuestions } from './conversation-parser.js';
8
8
  import { normalizeOpenCodeTool } from './tool-normalizer.js';
9
9
  import { ResponseLatencyTracker } from '../../service/response-latency-tracker.js';
@@ -3,7 +3,7 @@ import { OpenCodeServerClient } from '../opencode-server.js';
3
3
  import { openCodeContextLimits, openCodeProfileModels } from '../opencode-runtime.js';
4
4
  import { declaredRunnerProfiles } from '../runner-profiles.js';
5
5
  import { AbstractRunner } from './abstract-runner.js';
6
- import { isWithinWorkspace } from './codex/conversation-parser.js';
6
+ import { isWithinWorkspace } from '../util/runner-native-session-parsers.js';
7
7
  import { createHash } from 'node:crypto';
8
8
  import { homedir } from 'node:os';
9
9
  import { openCodeConversationPage, openCodeUserMessagePreview } from './opencode/conversation-parser.js';
@@ -36,6 +36,12 @@ export class OpenCodeRunner extends AbstractRunner {
36
36
  this.client = client;
37
37
  this.clientFactory = clientFactory;
38
38
  }
39
+ migrateSessionIdentity(previousId, nativeSessionId) {
40
+ for (const [runId, managed] of this.execution) {
41
+ if (managed.sessionId === previousId)
42
+ this.execution.set(runId, { ...managed, sessionId: nativeSessionId });
43
+ }
44
+ }
39
45
  profile(capabilities) {
40
46
  const base = declaredRunnerProfiles({
41
47
  codexAvailable: capabilities.codex.available,