@myagentroam/node 0.1.0

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 (68) hide show
  1. package/README.md +17 -0
  2. package/dist/capabilities.d.ts +5 -0
  3. package/dist/capabilities.js +23 -0
  4. package/dist/capabilities.js.map +1 -0
  5. package/dist/claude-agent-sdk.d.ts +101 -0
  6. package/dist/claude-agent-sdk.js +341 -0
  7. package/dist/claude-agent-sdk.js.map +1 -0
  8. package/dist/claude-channel.d.ts +28 -0
  9. package/dist/claude-channel.js +45 -0
  10. package/dist/claude-channel.js.map +1 -0
  11. package/dist/codex-app-server.d.ts +96 -0
  12. package/dist/codex-app-server.js +361 -0
  13. package/dist/codex-app-server.js.map +1 -0
  14. package/dist/config.d.ts +17 -0
  15. package/dist/config.js +51 -0
  16. package/dist/config.js.map +1 -0
  17. package/dist/connector.d.ts +343 -0
  18. package/dist/connector.js +5525 -0
  19. package/dist/connector.js.map +1 -0
  20. package/dist/database.d.ts +282 -0
  21. package/dist/database.js +1347 -0
  22. package/dist/database.js.map +1 -0
  23. package/dist/event-buffer.d.ts +12 -0
  24. package/dist/event-buffer.js +29 -0
  25. package/dist/event-buffer.js.map +1 -0
  26. package/dist/fake-runner.d.ts +42 -0
  27. package/dist/fake-runner.js +130 -0
  28. package/dist/fake-runner.js.map +1 -0
  29. package/dist/health.d.ts +4 -0
  30. package/dist/health.js +4 -0
  31. package/dist/health.js.map +1 -0
  32. package/dist/main.d.ts +2 -0
  33. package/dist/main.js +47 -0
  34. package/dist/main.js.map +1 -0
  35. package/dist/native-session-history.d.ts +72 -0
  36. package/dist/native-session-history.js +647 -0
  37. package/dist/native-session-history.js.map +1 -0
  38. package/dist/operational.d.ts +30 -0
  39. package/dist/operational.js +82 -0
  40. package/dist/operational.js.map +1 -0
  41. package/dist/process-tree.d.ts +9 -0
  42. package/dist/process-tree.js +19 -0
  43. package/dist/process-tree.js.map +1 -0
  44. package/dist/runner-command-engine.d.ts +19 -0
  45. package/dist/runner-command-engine.js +47 -0
  46. package/dist/runner-command-engine.js.map +1 -0
  47. package/dist/runner-profiles.d.ts +11 -0
  48. package/dist/runner-profiles.js +138 -0
  49. package/dist/runner-profiles.js.map +1 -0
  50. package/dist/runner-usage.d.ts +33 -0
  51. package/dist/runner-usage.js +193 -0
  52. package/dist/runner-usage.js.map +1 -0
  53. package/dist/runtime-state.d.ts +174 -0
  54. package/dist/runtime-state.js +957 -0
  55. package/dist/runtime-state.js.map +1 -0
  56. package/dist/service.d.ts +4 -0
  57. package/dist/service.js +39 -0
  58. package/dist/service.js.map +1 -0
  59. package/dist/storage.d.ts +2 -0
  60. package/dist/storage.js +33 -0
  61. package/dist/storage.js.map +1 -0
  62. package/dist/terminal.d.ts +132 -0
  63. package/dist/terminal.js +417 -0
  64. package/dist/terminal.js.map +1 -0
  65. package/dist/workspace.d.ts +116 -0
  66. package/dist/workspace.js +732 -0
  67. package/dist/workspace.js.map +1 -0
  68. package/package.json +36 -0
@@ -0,0 +1,647 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { open, readdir, unlink } from 'node:fs/promises';
3
+ import { homedir } from 'node:os';
4
+ import { basename, join, resolve } from 'node:path';
5
+ const DISCOVERY_READ_BYTES = 128 * 1024;
6
+ const TRANSCRIPT_READ_BYTES = 4 * 1024 * 1024;
7
+ const CONTEXT_USAGE_READ_BYTES = 256 * 1024;
8
+ const TASK_ACTIVITY_READ_BYTES = 32 * 1024 * 1024;
9
+ const transcriptIndex = new Map();
10
+ /** Reads the latest durable Codex task lifecycle event without exposing it. */
11
+ export async function readCodexNativeTaskActivity(workspacePath, externalSessionId, now = Date.now()) {
12
+ const wanted = canonical(workspacePath);
13
+ let path = transcriptIndex.get(indexKey('codex', wanted, externalSessionId));
14
+ if (path === undefined) {
15
+ await discoverNativeSessions('codex', workspacePath);
16
+ path = transcriptIndex.get(indexKey('codex', wanted, externalSessionId));
17
+ }
18
+ if (path === undefined)
19
+ return undefined;
20
+ let content;
21
+ try {
22
+ const handle = await open(path, 'r');
23
+ try {
24
+ const metadata = await handle.stat();
25
+ const start = Math.max(0, metadata.size - TASK_ACTIVITY_READ_BYTES);
26
+ const buffer = Buffer.alloc(metadata.size - start);
27
+ const { bytesRead } = await handle.read(buffer, 0, buffer.length, start);
28
+ content = buffer.subarray(0, bytesRead).toString('utf8');
29
+ }
30
+ finally {
31
+ await handle.close();
32
+ }
33
+ }
34
+ catch {
35
+ return undefined;
36
+ }
37
+ let latest;
38
+ for (const line of content.split('\n')) {
39
+ try {
40
+ const value = JSON.parse(line);
41
+ const payload = value.type === 'event_msg' && isRecord(value.payload) ? value.payload : undefined;
42
+ if (payload?.type !== 'task_started' && payload?.type !== 'task_complete')
43
+ continue;
44
+ const seconds = payload.type === 'task_started' ? payload.started_at : payload.completed_at;
45
+ const at = typeof seconds === 'number' ? seconds * 1000 : 0;
46
+ latest = { state: payload.type === 'task_started' ? 'ACTIVE' : 'IDLE', at };
47
+ }
48
+ catch {
49
+ // 单条损坏的 JSONL 记录不影响后续活动状态记录。
50
+ }
51
+ }
52
+ if (latest === undefined)
53
+ return undefined;
54
+ return latest.state === 'ACTIVE' && now - latest.at > 60 * 60_000 ? 'STALE' : latest.state;
55
+ }
56
+ /**
57
+ * Read-only adapter for the supported vendor JSONL stores. It requires an
58
+ * explicit cwd on the record and returns normalized visible messages and tool
59
+ * cards; no raw vendor events or hidden reasoning are exposed to the Workbench.
60
+ */
61
+ export async function discoverNativeSessions(runner, workspacePath) {
62
+ const wanted = canonical(workspacePath);
63
+ return (await discoverAllNativeSessions(runner)).filter((value) => canonical(value.cwd) === wanted);
64
+ }
65
+ /**
66
+ * Reads every supported transcript header before applying a Workspace filter.
67
+ * The caller owns in-process caching; a fixed file-count cutoff would silently
68
+ * hide older sessions from a project with a large global history.
69
+ */
70
+ export async function discoverAllNativeSessions(runner) {
71
+ const found = new Map();
72
+ const files = await listJsonl(transcriptRoot(runner));
73
+ const parsedFiles = await readDiscoveryHeaders(files, runner);
74
+ for (const [index, parsed] of parsedFiles.entries()) {
75
+ // Discovery only needs a session identity, cwd and an optional early
76
+ // title. Loading entire JSONL files here made opening a Workspace depend
77
+ // on the total history of every other project on the Node.
78
+ if (parsed === undefined)
79
+ continue;
80
+ const cwd = canonical(parsed.cwd);
81
+ transcriptIndex.set(indexKey(runner, cwd, parsed.externalSessionId), files[index]);
82
+ found.set(`${cwd}\u0000${parsed.externalSessionId}`, parsed);
83
+ }
84
+ return [...found.values()];
85
+ }
86
+ async function readDiscoveryHeaders(files, runner) {
87
+ const values = Array.from({ length: files.length });
88
+ let next = 0;
89
+ const worker = async () => {
90
+ while (true) {
91
+ const index = next;
92
+ next += 1;
93
+ if (index >= files.length)
94
+ return;
95
+ values[index] = await readTranscript(files[index], runner, DISCOVERY_READ_BYTES);
96
+ }
97
+ };
98
+ // Header reads are independent. Bounded concurrency avoids serial startup
99
+ // delays without opening an unbounded number of descriptors on large homes.
100
+ await Promise.all(Array.from({ length: Math.min(32, files.length) }, worker));
101
+ return values;
102
+ }
103
+ /** Reads one already-discovered external session again to follow appended JSONL records. */
104
+ export async function readNativeSession(runner, workspacePath, externalSessionId) {
105
+ const wanted = canonical(workspacePath);
106
+ const path = transcriptIndex.get(indexKey(runner, wanted, externalSessionId));
107
+ if (path === undefined)
108
+ return undefined;
109
+ const parsed = await readTranscript(path, runner, TRANSCRIPT_READ_BYTES);
110
+ if (parsed === undefined ||
111
+ parsed.externalSessionId !== externalSessionId ||
112
+ canonical(parsed.cwd) !== wanted) {
113
+ transcriptIndex.delete(indexKey(runner, wanted, externalSessionId));
114
+ return undefined;
115
+ }
116
+ return parsed;
117
+ }
118
+ /**
119
+ * Codex records the most recent prompt token count alongside the model context
120
+ * window in `event_msg/token_count`. Read only the tail of an already
121
+ * discovered transcript: the count is append-only and never needs a costly
122
+ * full-history scan.
123
+ */
124
+ export async function readCodexNativeContextUsage(workspacePath, externalSessionId) {
125
+ const wanted = canonical(workspacePath);
126
+ let path = transcriptIndex.get(indexKey('codex', wanted, externalSessionId));
127
+ if (path === undefined) {
128
+ await discoverNativeSessions('codex', workspacePath);
129
+ path = transcriptIndex.get(indexKey('codex', wanted, externalSessionId));
130
+ }
131
+ if (path === undefined)
132
+ return undefined;
133
+ let content;
134
+ try {
135
+ const handle = await open(path, 'r');
136
+ try {
137
+ const metadata = await handle.stat();
138
+ const start = Math.max(0, metadata.size - CONTEXT_USAGE_READ_BYTES);
139
+ const buffer = Buffer.alloc(metadata.size - start);
140
+ const { bytesRead } = await handle.read(buffer, 0, buffer.length, start);
141
+ content = buffer.subarray(0, bytesRead).toString('utf8');
142
+ }
143
+ finally {
144
+ await handle.close();
145
+ }
146
+ }
147
+ catch {
148
+ return undefined;
149
+ }
150
+ let usage;
151
+ for (const line of content.split('\n')) {
152
+ let record;
153
+ try {
154
+ record = JSON.parse(line);
155
+ }
156
+ catch {
157
+ continue;
158
+ }
159
+ const next = codexContextUsage(record);
160
+ if (next !== undefined)
161
+ usage = next;
162
+ }
163
+ return usage;
164
+ }
165
+ /**
166
+ * Permanently removes exactly one previously discovered transcript. The
167
+ * caller supplies no path: the in-process discovery index resolves it, then
168
+ * the record is re-read and its runner/cwd/native ID are checked again before
169
+ * unlinking. This keeps a destructive operation bounded to one known file.
170
+ */
171
+ export async function removeNativeSession(runner, workspacePath, externalSessionId) {
172
+ const wanted = canonical(workspacePath);
173
+ let path = transcriptIndex.get(indexKey(runner, wanted, externalSessionId));
174
+ if (path === undefined) {
175
+ await discoverNativeSessions(runner, workspacePath);
176
+ path = transcriptIndex.get(indexKey(runner, wanted, externalSessionId));
177
+ }
178
+ if (path === undefined)
179
+ throw new Error('NATIVE_TRANSCRIPT_UNAVAILABLE');
180
+ const parsed = await readTranscript(path, runner, TRANSCRIPT_READ_BYTES);
181
+ if (parsed === undefined ||
182
+ parsed.externalSessionId !== externalSessionId ||
183
+ canonical(parsed.cwd) !== wanted) {
184
+ transcriptIndex.delete(indexKey(runner, wanted, externalSessionId));
185
+ throw new Error('NATIVE_TRANSCRIPT_UNAVAILABLE');
186
+ }
187
+ try {
188
+ await unlink(path);
189
+ }
190
+ catch (error) {
191
+ if (error.code === 'ENOENT')
192
+ throw new Error('NATIVE_TRANSCRIPT_UNAVAILABLE');
193
+ throw error;
194
+ }
195
+ transcriptIndex.delete(indexKey(runner, wanted, externalSessionId));
196
+ }
197
+ function transcriptRoot(runner) {
198
+ return runner === 'codex'
199
+ ? join(homedir(), '.codex', 'sessions')
200
+ : join(homedir(), '.claude', 'projects');
201
+ }
202
+ async function listJsonl(directory) {
203
+ const files = [];
204
+ const walk = async (current) => {
205
+ let entries;
206
+ try {
207
+ entries = await readdir(current, { withFileTypes: true });
208
+ }
209
+ catch {
210
+ return;
211
+ }
212
+ for (const entry of entries) {
213
+ const path = join(current, entry.name);
214
+ if (entry.isDirectory())
215
+ await walk(path);
216
+ else if (entry.isFile() && entry.name.endsWith('.jsonl'))
217
+ files.push(path);
218
+ }
219
+ };
220
+ await walk(directory);
221
+ return files;
222
+ }
223
+ async function readTranscript(path, runner, byteLimit) {
224
+ let content;
225
+ let truncated = false;
226
+ try {
227
+ const handle = await open(path, 'r');
228
+ try {
229
+ const metadata = await handle.stat();
230
+ const headSize = Math.min(byteLimit, metadata.size);
231
+ const head = Buffer.alloc(headSize);
232
+ const { bytesRead: headBytesRead } = await handle.read(head, 0, head.length, 0);
233
+ if (headSize === metadata.size) {
234
+ content = head.subarray(0, headBytesRead).toString('utf8');
235
+ }
236
+ else {
237
+ // Discovery needs identity from the header and genuine recency from
238
+ // the latest visible records. Keep both bounded instead of falling
239
+ // back to mtime, whose changes do not necessarily mean a new chat.
240
+ const tailSize = Math.min(byteLimit, metadata.size - headSize);
241
+ truncated = headSize + tailSize < metadata.size;
242
+ const tail = Buffer.alloc(tailSize);
243
+ const { bytesRead: tailBytesRead } = await handle.read(tail, 0, tail.length, metadata.size - tailSize);
244
+ content = `${head.subarray(0, headBytesRead).toString('utf8')}\n${tail
245
+ .subarray(0, tailBytesRead)
246
+ .toString('utf8')}`;
247
+ }
248
+ }
249
+ finally {
250
+ await handle.close();
251
+ }
252
+ }
253
+ catch {
254
+ return undefined;
255
+ }
256
+ let cwd;
257
+ let id;
258
+ const items = [];
259
+ const toolIndexes = new Map();
260
+ const pendingToolResults = new Map();
261
+ const metaRecordIds = new Set();
262
+ for (const line of content.split('\n')) {
263
+ if (line.length === 0)
264
+ continue;
265
+ let value;
266
+ try {
267
+ value = JSON.parse(line);
268
+ }
269
+ catch {
270
+ continue;
271
+ }
272
+ if (!isRecord(value))
273
+ continue;
274
+ cwd ??= workspacePath(value);
275
+ id ??= sessionId(value);
276
+ const recordId = typeof value['uuid'] === 'string' ? value['uuid'] : undefined;
277
+ const parentId = typeof value['parentUuid'] === 'string' ? value['parentUuid'] : undefined;
278
+ if (value['isMeta'] === true && recordId !== undefined)
279
+ metaRecordIds.add(recordId);
280
+ if (isHiddenTranscriptRecord(value) ||
281
+ (parentId !== undefined && metaRecordIds.has(parentId))) {
282
+ continue;
283
+ }
284
+ for (const item of transcriptItems(value)) {
285
+ if (item.kind === 'tool_result') {
286
+ const toolIndex = toolIndexes.get(item.toolUseId);
287
+ if (toolIndex === undefined)
288
+ pendingToolResults.set(item.toolUseId, item);
289
+ else
290
+ items[toolIndex] = withToolResult(items[toolIndex], item);
291
+ continue;
292
+ }
293
+ if (item.kind === 'tool_call') {
294
+ const pending = pendingToolResults.get(item.toolUseId);
295
+ const completed = pending === undefined ? item : withToolResult(item, pending);
296
+ pendingToolResults.delete(item.toolUseId);
297
+ toolIndexes.set(item.toolUseId, items.length);
298
+ items.push(completed);
299
+ continue;
300
+ }
301
+ items.push(item);
302
+ }
303
+ }
304
+ for (const result of pendingToolResults.values())
305
+ items.push({
306
+ kind: 'tool_call',
307
+ toolUseId: result.toolUseId,
308
+ toolName: '工具',
309
+ input: null,
310
+ inputSummary: null,
311
+ inputTruncated: false,
312
+ outputSummary: result.output,
313
+ outputTruncated: result.truncated,
314
+ failed: result.failed,
315
+ createdAt: result.createdAt
316
+ });
317
+ if (cwd === undefined)
318
+ return undefined;
319
+ const title = items.find((item) => item.kind === 'message' && item.role === 'USER')?.text;
320
+ return {
321
+ runner,
322
+ externalSessionId: id ?? basename(path, '.jsonl'),
323
+ cwd,
324
+ ...(title === undefined ? {} : { title: compact(title) }),
325
+ digest: createHash('sha256').update(content).digest('hex'),
326
+ items,
327
+ lastActivityAt: Math.max(0, ...items.map((item) => item.createdAt ?? 0)),
328
+ truncated
329
+ };
330
+ }
331
+ function workspacePath(record) {
332
+ return (firstString(record, ['cwd', 'working_directory', 'workspacePath']) ??
333
+ nestedString(record, ['payload', 'cwd']) ??
334
+ nestedString(record, ['message', 'cwd']));
335
+ }
336
+ function sessionId(record) {
337
+ // A Codex sub-agent transcript records its own thread in `payload.id`, but
338
+ // retains the parent thread in `payload.session_id`. Treating the latter
339
+ // as this file's identity makes the child file overwrite the parent thread
340
+ // in the discovery index, which then shows unrelated messages for the
341
+ // parent Chat.
342
+ if (record['type'] === 'session_meta') {
343
+ const transcriptId = nestedString(record, ['payload', 'id']);
344
+ if (transcriptId !== undefined)
345
+ return transcriptId;
346
+ }
347
+ return (firstString(record, ['sessionId', 'session_id', 'thread_id', 'conversationId']) ??
348
+ nestedString(record, ['payload', 'id']) ??
349
+ nestedString(record, ['payload', 'session_id']) ??
350
+ nestedString(record, ['payload', 'thread_id']));
351
+ }
352
+ function codexContextUsage(value) {
353
+ if (!isRecord(value) || value.type !== 'event_msg' || !isRecord(value.payload))
354
+ return undefined;
355
+ const payload = value.payload;
356
+ if (payload.type !== 'token_count' || !isRecord(payload.info))
357
+ return undefined;
358
+ const promptUsage = payload.info.last_token_usage;
359
+ const maxTokens = payload.info.model_context_window;
360
+ if (!isRecord(promptUsage) ||
361
+ typeof promptUsage.input_tokens !== 'number' ||
362
+ typeof maxTokens !== 'number')
363
+ return undefined;
364
+ if (!Number.isFinite(promptUsage.input_tokens) ||
365
+ !Number.isInteger(promptUsage.input_tokens) ||
366
+ !Number.isFinite(maxTokens) ||
367
+ !Number.isInteger(maxTokens) ||
368
+ maxTokens <= 0 ||
369
+ promptUsage.input_tokens < 0)
370
+ return undefined;
371
+ return { usedTokens: promptUsage.input_tokens, maxTokens };
372
+ }
373
+ function transcriptItems(record) {
374
+ const createdAt = timestampOf(record);
375
+ const results = [...toolResultsOf(record, createdAt), ...codexToolResultsOf(record, createdAt)];
376
+ if (results.length > 0)
377
+ return results;
378
+ const values = [
379
+ ...toolUsesOf(record, createdAt),
380
+ ...codexToolUsesOf(record, createdAt)
381
+ ];
382
+ const role = roleOf(record);
383
+ if (role === undefined)
384
+ return values;
385
+ const text = messageText(textOf(record) ?? '');
386
+ if (text.length === 0)
387
+ return values;
388
+ // Codex transcripts can contain the host's injected AGENTS.md and runtime
389
+ // context as a `user` item before the first actual prompt. It is neither a
390
+ // user-visible chat turn nor a meaningful session title.
391
+ if (role === 'USER' && isInjectedContextPrompt(text))
392
+ return values;
393
+ values.unshift({ kind: 'message', role, text, createdAt });
394
+ return values;
395
+ }
396
+ function toolUsesOf(record, createdAt) {
397
+ return contentBlocks(record).flatMap((block) => {
398
+ if (block['type'] !== 'tool_use' ||
399
+ typeof block['id'] !== 'string' ||
400
+ typeof block['name'] !== 'string')
401
+ return [];
402
+ const input = block['input'];
403
+ const summary = compactValue(input, 10_000);
404
+ return [
405
+ {
406
+ kind: 'tool_call',
407
+ toolUseId: block['id'],
408
+ toolName: compact(block['name']).slice(0, 128),
409
+ input,
410
+ inputSummary: summary.text || null,
411
+ inputTruncated: summary.truncated,
412
+ outputSummary: null,
413
+ outputTruncated: false,
414
+ failed: false,
415
+ createdAt
416
+ }
417
+ ];
418
+ });
419
+ }
420
+ function toolResultsOf(record, createdAt) {
421
+ return contentBlocks(record).flatMap((block) => {
422
+ if (block['type'] !== 'tool_result' || typeof block['tool_use_id'] !== 'string')
423
+ return [];
424
+ const output = compactValue(block['content'], 10_000);
425
+ return [
426
+ {
427
+ kind: 'tool_result',
428
+ toolUseId: block['tool_use_id'],
429
+ output: output.text,
430
+ failed: block['is_error'] === true,
431
+ truncated: output.truncated,
432
+ createdAt
433
+ }
434
+ ];
435
+ });
436
+ }
437
+ /**
438
+ * Codex persists custom and standard function tools as paired `response_item`
439
+ * records. Their record IDs differ, while `call_id` is the stable identifier
440
+ * shared by invocation and result. Normalize on that key so JSONL fallback
441
+ * history has the same single-card shape as live events.
442
+ */
443
+ function codexToolUsesOf(record, createdAt) {
444
+ const payload = codexToolPayload(record);
445
+ if (payload?.['type'] !== 'custom_tool_call' && payload?.['type'] !== 'function_call')
446
+ return [];
447
+ const toolUseId = firstString(payload, ['call_id', 'id']);
448
+ const toolName = firstString(payload, ['name', 'tool']);
449
+ if (toolUseId === undefined || toolName === undefined)
450
+ return [];
451
+ const input = payload['input'] ?? payload['arguments'];
452
+ const summary = compactValue(input, 10_000);
453
+ return [
454
+ {
455
+ kind: 'tool_call',
456
+ toolUseId,
457
+ toolName: compact(toolName).slice(0, 128),
458
+ input,
459
+ inputSummary: summary.text || null,
460
+ inputTruncated: summary.truncated,
461
+ outputSummary: null,
462
+ outputTruncated: false,
463
+ failed: false,
464
+ createdAt
465
+ }
466
+ ];
467
+ }
468
+ function codexToolResultsOf(record, createdAt) {
469
+ const payload = codexToolPayload(record);
470
+ if (payload?.['type'] !== 'custom_tool_call_output' &&
471
+ payload?.['type'] !== 'function_call_output')
472
+ return [];
473
+ const toolUseId = firstString(payload, ['call_id', 'id']);
474
+ if (toolUseId === undefined)
475
+ return [];
476
+ const output = compactValue(codexToolOutput(payload['output'] ?? payload['contentItems']), 10_000);
477
+ return [
478
+ {
479
+ kind: 'tool_result',
480
+ toolUseId,
481
+ output: output.text,
482
+ failed: payload['status'] === 'failed' || payload['error'] !== undefined,
483
+ truncated: output.truncated,
484
+ createdAt
485
+ }
486
+ ];
487
+ }
488
+ function codexToolPayload(record) {
489
+ return record['type'] === 'response_item' && isRecord(record['payload'])
490
+ ? record['payload']
491
+ : undefined;
492
+ }
493
+ /** Extract Codex output text blocks instead of rendering their JSON envelopes. */
494
+ function codexToolOutput(value) {
495
+ if (!Array.isArray(value))
496
+ return value;
497
+ const text = value.flatMap((block) => {
498
+ if (typeof block === 'string')
499
+ return [block];
500
+ if (!isRecord(block))
501
+ return [];
502
+ const direct = firstString(block, ['text', 'content']);
503
+ return direct === undefined ? [] : [direct];
504
+ });
505
+ return text.length > 0 ? text.join('') : value;
506
+ }
507
+ function withToolResult(call, result) {
508
+ return {
509
+ ...call,
510
+ outputSummary: result.output,
511
+ outputTruncated: result.truncated,
512
+ failed: result.failed
513
+ };
514
+ }
515
+ function isInjectedContextPrompt(text) {
516
+ const normalized = text.replace(/\s+/g, ' ').trim();
517
+ if (normalized.startsWith('<codex_internal_context'))
518
+ return true;
519
+ return (normalized.startsWith('# AGENTS.md instructions') &&
520
+ (normalized.includes('<INSTRUCTIONS>') || normalized.includes('<environment_context>')));
521
+ }
522
+ function isHiddenTranscriptRecord(record) {
523
+ if (contentBlocks(record).some((block) => block['type'] === 'tool_result'))
524
+ return false;
525
+ if (record['interruptedByShutdown'] === true ||
526
+ record['isMeta'] === true ||
527
+ record['isVisibleInTranscriptOnly'] === true ||
528
+ record['isCompactSummary'] === true)
529
+ return true;
530
+ const text = textOf(record)?.trim() ?? '';
531
+ return (text.startsWith('<local-command-') ||
532
+ text.startsWith('<command-name>') ||
533
+ text.startsWith('<command-message>'));
534
+ }
535
+ function roleOf(record) {
536
+ const candidates = [
537
+ firstString(record, ['role']),
538
+ nestedString(record, ['message', 'role']),
539
+ nestedString(record, ['payload', 'role']),
540
+ firstString(record, ['type']),
541
+ nestedString(record, ['payload', 'type'])
542
+ ];
543
+ if (candidates.some((value) => value === 'user' || value === 'human' || value === 'input'))
544
+ return 'USER';
545
+ if (candidates.some((value) => value === 'assistant' || value === 'agent' || value === 'output'))
546
+ return 'ASSISTANT';
547
+ return undefined;
548
+ }
549
+ function textOf(record) {
550
+ return (firstString(record, ['content', 'text']) ??
551
+ nestedText(record['message']) ??
552
+ nestedText(record['payload']));
553
+ }
554
+ function nestedText(value) {
555
+ if (typeof value === 'string')
556
+ return value;
557
+ if (!isRecord(value))
558
+ return undefined;
559
+ const direct = firstString(value, ['content', 'text', 'message']);
560
+ if (direct !== undefined)
561
+ return direct;
562
+ if (Array.isArray(value['content'])) {
563
+ const parts = value['content'].flatMap((part) => {
564
+ if (typeof part === 'string')
565
+ return [part];
566
+ if (!isRecord(part))
567
+ return [];
568
+ return [firstString(part, ['text', 'content'])].filter((text) => text !== undefined);
569
+ });
570
+ return parts.join('\n');
571
+ }
572
+ return undefined;
573
+ }
574
+ function contentBlocks(record) {
575
+ const values = [record['message'], record['payload']];
576
+ for (const value of values)
577
+ if (isRecord(value) && Array.isArray(value['content']))
578
+ return value['content'].filter(isRecord);
579
+ return [];
580
+ }
581
+ function compactValue(value, limit) {
582
+ if (value === undefined || value === null)
583
+ return { text: '', truncated: false };
584
+ let text;
585
+ if (typeof value === 'string')
586
+ text = value;
587
+ else {
588
+ try {
589
+ text = JSON.stringify(value);
590
+ }
591
+ catch {
592
+ return { text: '(无法显示)', truncated: false };
593
+ }
594
+ }
595
+ return {
596
+ text: text.length <= limit ? text : `${text.slice(0, Math.max(0, limit - 1))}…`,
597
+ truncated: text.length > limit
598
+ };
599
+ }
600
+ function timestampOf(record) {
601
+ const raw = record['timestamp'] ?? record['created_at'] ?? record['createdAt'];
602
+ if (typeof raw === 'number' && Number.isFinite(raw))
603
+ return raw < 10_000_000_000 ? raw * 1_000 : raw;
604
+ if (typeof raw === 'string') {
605
+ const value = Date.parse(raw);
606
+ return Number.isNaN(value) ? null : value;
607
+ }
608
+ return null;
609
+ }
610
+ function firstString(value, keys) {
611
+ for (const key of keys)
612
+ if (typeof value[key] === 'string' && value[key].length > 0)
613
+ return value[key];
614
+ return undefined;
615
+ }
616
+ function nestedString(value, keys) {
617
+ let current = value;
618
+ for (const key of keys) {
619
+ if (!isRecord(current))
620
+ return undefined;
621
+ current = current[key];
622
+ }
623
+ return typeof current === 'string' && current.length > 0 ? current : undefined;
624
+ }
625
+ function compact(value) {
626
+ return value.replace(/\s+/g, ' ').trim().slice(0, 16_000);
627
+ }
628
+ /** 对话正文可能是 Markdown,不能像标题一样压缩内部空白。 */
629
+ function messageText(value) {
630
+ return value.trim().slice(0, 16_000);
631
+ }
632
+ function canonical(value) {
633
+ try {
634
+ const normalized = resolve(value).replace(/[\\/]+$/, '');
635
+ return process.platform === 'win32' ? normalized.toLocaleLowerCase() : normalized;
636
+ }
637
+ catch {
638
+ return value;
639
+ }
640
+ }
641
+ function indexKey(runner, cwd, externalSessionId) {
642
+ return `${runner}\u0000${cwd}\u0000${externalSessionId}`;
643
+ }
644
+ function isRecord(value) {
645
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
646
+ }
647
+ //# sourceMappingURL=native-session-history.js.map