@myagentroam/node 0.11.14 → 0.11.16
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 +13 -20
- package/dist/native-session-history.js +35 -832
- package/dist/native-transcript-adapters.js +9 -0
- package/dist/native-transcript-model.js +1 -0
- package/dist/runner/abstract-runner.js +12 -0
- package/dist/runner/claude/conversation-parser.js +80 -1
- package/dist/runner/claude/managed-run-controller.js +4 -2
- package/dist/runner/claude/message-parsers.js +37 -0
- package/dist/runner/claude/native-context-usage.js +90 -0
- package/dist/runner/claude/native-transcript.js +347 -0
- package/dist/runner/claude/tool-normalizer.js +12 -2
- package/dist/runner/claude-code-runner.js +13 -2
- package/dist/runner/codex/conversation-parser.js +67 -3
- package/dist/runner/codex/managed-run-controller.js +3 -2
- package/dist/runner/codex/native-context-usage.js +42 -0
- package/dist/runner/codex/native-transcript.js +575 -0
- package/dist/runner/codex-runner.js +18 -3
- package/dist/runner/mar-agent/managed-run-controller.js +1 -1
- package/dist/runner/mar-agent-runner.js +6 -0
- package/dist/runner/opencode/conversation-parser.js +1 -1
- package/dist/runner/opencode/managed-run-controller.js +1 -1
- package/dist/runner/opencode-runner.js +7 -1
- package/dist/runner/runner-registry.js +4 -0
- package/dist/runtime-state.js +1 -0
- package/dist/service/continuous-run-coordinator.js +1 -0
- package/dist/service/conversation-template-manifest.js +179 -0
- package/dist/service/conversation-template-node-service.js +10 -132
- package/dist/service/external-session-resume-service.js +15 -0
- package/dist/service/fake-managed-run-service.js +1 -1
- package/dist/service/goal-continuation-coordinator.js +4 -3
- package/dist/service/goal-runtime.js +0 -9
- package/dist/service/hosted-conversation-node-service.js +16 -179
- package/dist/service/native-session-projection-service.js +7 -24
- package/dist/service/node-control-channel.js +1 -1
- package/dist/service/node-request-service.js +1 -1
- package/dist/service/node-run-event-bridge.js +0 -24
- package/dist/service/node-terminal-channel.js +1 -1
- package/dist/service/recent-run-projection-service.js +2 -1
- package/dist/service/run-workbench-service.js +1 -1
- package/dist/service/runner-interaction-service.js +2 -1
- package/dist/service/runner-service.js +6 -4
- package/dist/service/session-identity-service.js +1 -0
- package/dist/service/session-message-service.js +1 -0
- package/dist/service/session-query-service.js +1 -1
- package/dist/service/workbench-event-projection.js +1 -1
- package/dist/service/workbench-event-service.js +1 -1
- package/dist/service/workspace-file-service.js +0 -2
- package/dist/service/workspace-queue-workbench-service.js +2 -2
- package/dist/util/node-operation-parsers.js +0 -45
- package/dist/util/runner-native-session-parsers.js +17 -194
- package/package.json +4 -4
|
@@ -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 '
|
|
9
|
-
import { codexThreadCwd,
|
|
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 '
|
|
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 '
|
|
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 '
|
|
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 '
|
|
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 '
|
|
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,
|