@myagentroam/node 0.1.3 → 0.1.6

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 (195) hide show
  1. package/README.md +9 -1
  2. package/dist/capabilities.d.ts +2 -0
  3. package/dist/capabilities.js +15 -4
  4. package/dist/claude-agent-sdk.d.ts +4 -16
  5. package/dist/claude-agent-sdk.js +18 -79
  6. package/dist/claude-channel.js +0 -1
  7. package/dist/codex-app-server.d.ts +5 -0
  8. package/dist/codex-app-server.js +26 -5
  9. package/dist/config.d.ts +3 -0
  10. package/dist/config.js +18 -2
  11. package/dist/connector/node-connector-options.d.ts +26 -0
  12. package/dist/connector/node-connector-options.js +1 -0
  13. package/dist/connector/node-operation-router.d.ts +9 -0
  14. package/dist/connector/node-operation-router.js +22 -0
  15. package/dist/connector.d.ts +62 -356
  16. package/dist/connector.js +714 -6176
  17. package/dist/control-outbound-scheduler.d.ts +26 -0
  18. package/dist/control-outbound-scheduler.js +121 -0
  19. package/dist/database.d.ts +4 -0
  20. package/dist/database.js +15 -10
  21. package/dist/event-buffer.js +0 -1
  22. package/dist/fake-runner.js +0 -1
  23. package/dist/health.js +0 -1
  24. package/dist/main.js +6 -1
  25. package/dist/migrations/v001.js +0 -1
  26. package/dist/migrations/v002.js +0 -1
  27. package/dist/migrations/v003.d.ts +4 -0
  28. package/dist/migrations/v003.js +44 -0
  29. package/dist/native-jsonl-reader.d.ts +14 -0
  30. package/dist/native-jsonl-reader.js +97 -0
  31. package/dist/native-session-history.d.ts +41 -4
  32. package/dist/native-session-history.js +340 -159
  33. package/dist/opencode-runtime.d.ts +33 -0
  34. package/dist/opencode-runtime.js +80 -0
  35. package/dist/opencode-server.d.ts +47 -0
  36. package/dist/opencode-server.js +250 -0
  37. package/dist/operational.js +10 -3
  38. package/dist/process-tree.js +0 -1
  39. package/dist/runner/abstract-runner.d.ts +153 -0
  40. package/dist/runner/abstract-runner.js +154 -0
  41. package/dist/runner/claude/managed-run-controller.d.ts +52 -0
  42. package/dist/runner/claude/managed-run-controller.js +326 -0
  43. package/dist/runner/claude-code-runner.d.ts +70 -0
  44. package/dist/runner/claude-code-runner.js +286 -0
  45. package/dist/runner/codex/conversation-parser.d.ts +59 -0
  46. package/dist/runner/codex/conversation-parser.js +816 -0
  47. package/dist/runner/codex/managed-run-controller.d.ts +80 -0
  48. package/dist/runner/codex/managed-run-controller.js +388 -0
  49. package/dist/runner/codex-runner.d.ts +85 -0
  50. package/dist/runner/codex-runner.js +365 -0
  51. package/dist/runner/fake-test-runner.d.ts +26 -0
  52. package/dist/runner/fake-test-runner.js +47 -0
  53. package/dist/runner/opencode/conversation-parser.d.ts +27 -0
  54. package/dist/runner/opencode/conversation-parser.js +217 -0
  55. package/dist/runner/opencode/managed-run-controller.d.ts +76 -0
  56. package/dist/runner/opencode/managed-run-controller.js +451 -0
  57. package/dist/runner/opencode-runner.d.ts +60 -0
  58. package/dist/runner/opencode-runner.js +330 -0
  59. package/dist/runner/runner-registry.d.ts +19 -0
  60. package/dist/runner/runner-registry.js +75 -0
  61. package/dist/runner-command-engine.js +0 -1
  62. package/dist/runner-profiles.d.ts +4 -0
  63. package/dist/runner-profiles.js +76 -3
  64. package/dist/runner-usage.d.ts +2 -2
  65. package/dist/runner-usage.js +3 -6
  66. package/dist/runtime-state.js +26 -14
  67. package/dist/service/attachment-domain-state.d.ts +5 -0
  68. package/dist/service/attachment-domain-state.js +5 -0
  69. package/dist/service/conversation-history-service.d.ts +34 -0
  70. package/dist/service/conversation-history-service.js +158 -0
  71. package/dist/service/external-session-resume-service.d.ts +32 -0
  72. package/dist/service/external-session-resume-service.js +98 -0
  73. package/dist/service/fake-managed-run-service.d.ts +29 -0
  74. package/dist/service/fake-managed-run-service.js +62 -0
  75. package/dist/service/managed-runner-session-service.d.ts +12 -0
  76. package/dist/service/managed-runner-session-service.js +35 -0
  77. package/dist/service/native-session-projection-service.d.ts +51 -0
  78. package/dist/service/native-session-projection-service.js +271 -0
  79. package/dist/service/native-session-watch-service.d.ts +37 -0
  80. package/dist/service/native-session-watch-service.js +150 -0
  81. package/dist/service/node-connection-lifecycle-service.d.ts +37 -0
  82. package/dist/service/node-connection-lifecycle-service.js +85 -0
  83. package/dist/service/node-control-channel.d.ts +32 -0
  84. package/dist/service/node-control-channel.js +85 -0
  85. package/dist/service/node-control-message-service.d.ts +28 -0
  86. package/dist/service/node-control-message-service.js +104 -0
  87. package/dist/service/node-request-service.d.ts +14 -0
  88. package/dist/service/node-request-service.js +50 -0
  89. package/dist/service/node-run-event-bridge.d.ts +40 -0
  90. package/dist/service/node-run-event-bridge.js +83 -0
  91. package/dist/service/node-terminal-channel.d.ts +29 -0
  92. package/dist/service/node-terminal-channel.js +182 -0
  93. package/dist/service/node-upgrade-coordinator.d.ts +29 -0
  94. package/dist/service/node-upgrade-coordinator.js +137 -0
  95. package/dist/service/node-workspace-coordinator.d.ts +32 -0
  96. package/dist/service/node-workspace-coordinator.js +80 -0
  97. package/dist/service/run-attachment-service.d.ts +37 -0
  98. package/dist/service/run-attachment-service.js +160 -0
  99. package/dist/service/run-domain-state.d.ts +4 -0
  100. package/dist/service/run-domain-state.js +4 -0
  101. package/dist/service/run-event-service.d.ts +31 -0
  102. package/dist/service/run-event-service.js +102 -0
  103. package/dist/service/run-workbench-service.d.ts +27 -0
  104. package/dist/service/run-workbench-service.js +113 -0
  105. package/dist/service/runner-channel-message-service.d.ts +13 -0
  106. package/dist/service/runner-channel-message-service.js +20 -0
  107. package/dist/service/runner-interaction-service.d.ts +40 -0
  108. package/dist/service/runner-interaction-service.js +246 -0
  109. package/dist/service/runner-service.d.ts +44 -0
  110. package/dist/service/runner-service.js +68 -0
  111. package/dist/service/session-catalog-service.d.ts +45 -0
  112. package/dist/service/session-catalog-service.js +224 -0
  113. package/dist/service/session-command-service.d.ts +23 -0
  114. package/dist/service/session-command-service.js +69 -0
  115. package/dist/service/session-context-service.d.ts +34 -0
  116. package/dist/service/session-context-service.js +87 -0
  117. package/dist/service/session-domain-state.d.ts +8 -0
  118. package/dist/service/session-domain-state.js +8 -0
  119. package/dist/service/session-identity-service.d.ts +24 -0
  120. package/dist/service/session-identity-service.js +86 -0
  121. package/dist/service/session-lifecycle-service.d.ts +31 -0
  122. package/dist/service/session-lifecycle-service.js +242 -0
  123. package/dist/service/session-message-service.d.ts +33 -0
  124. package/dist/service/session-message-service.js +157 -0
  125. package/dist/service/session-presentation-service.d.ts +33 -0
  126. package/dist/service/session-presentation-service.js +36 -0
  127. package/dist/service/session-query-service.d.ts +31 -0
  128. package/dist/service/session-query-service.js +77 -0
  129. package/dist/service/terminal-relay-state.d.ts +6 -0
  130. package/dist/service/terminal-relay-state.js +3 -0
  131. package/dist/service/terminal-service.d.ts +12 -0
  132. package/dist/service/terminal-service.js +52 -0
  133. package/dist/service/workbench-event-service.d.ts +21 -0
  134. package/dist/service/workbench-event-service.js +53 -0
  135. package/dist/service/workbench-manifest-service.d.ts +89 -0
  136. package/dist/service/workbench-manifest-service.js +70 -0
  137. package/dist/service/workspace-change-service.d.ts +9 -0
  138. package/dist/service/workspace-change-service.js +86 -0
  139. package/dist/service/workspace-content-search-service.d.ts +14 -0
  140. package/dist/service/workspace-content-search-service.js +477 -0
  141. package/dist/service/workspace-domain-state.d.ts +75 -0
  142. package/dist/service/workspace-domain-state.js +40 -0
  143. package/dist/service/workspace-file-service.d.ts +17 -0
  144. package/dist/service/workspace-file-service.js +109 -0
  145. package/dist/service/workspace-queue-workbench-service.d.ts +55 -0
  146. package/dist/service/workspace-queue-workbench-service.js +226 -0
  147. package/dist/service/workspace-service.d.ts +15 -0
  148. package/dist/service/workspace-service.js +67 -0
  149. package/dist/service/workspace-session-service.d.ts +23 -0
  150. package/dist/service/workspace-session-service.js +139 -0
  151. package/dist/service/workspace-upload-service.d.ts +37 -0
  152. package/dist/service/workspace-upload-service.js +240 -0
  153. package/dist/service/workspace-watch-service.d.ts +18 -0
  154. package/dist/service/workspace-watch-service.js +120 -0
  155. package/dist/service/workspace-workbench-service.d.ts +47 -0
  156. package/dist/service/workspace-workbench-service.js +223 -0
  157. package/dist/service.js +2 -3
  158. package/dist/storage.js +0 -1
  159. package/dist/supervisor.d.ts +1 -0
  160. package/dist/supervisor.js +154 -0
  161. package/dist/terminal.d.ts +1 -0
  162. package/dist/terminal.js +1 -2
  163. package/dist/util/node-operation-parsers.d.ts +92 -0
  164. package/dist/util/node-operation-parsers.js +382 -0
  165. package/dist/util/runner-native-session-parsers.d.ts +72 -0
  166. package/dist/util/runner-native-session-parsers.js +381 -0
  167. package/dist/util/secret-environment.d.ts +1 -0
  168. package/dist/util/secret-environment.js +25 -0
  169. package/dist/workspace.d.ts +62 -2
  170. package/dist/workspace.js +539 -57
  171. package/package.json +3 -2
  172. package/dist/capabilities.js.map +0 -1
  173. package/dist/claude-agent-sdk.js.map +0 -1
  174. package/dist/claude-channel.js.map +0 -1
  175. package/dist/codex-app-server.js.map +0 -1
  176. package/dist/config.js.map +0 -1
  177. package/dist/connector.js.map +0 -1
  178. package/dist/database.js.map +0 -1
  179. package/dist/event-buffer.js.map +0 -1
  180. package/dist/fake-runner.js.map +0 -1
  181. package/dist/health.js.map +0 -1
  182. package/dist/main.js.map +0 -1
  183. package/dist/migrations/v001.js.map +0 -1
  184. package/dist/migrations/v002.js.map +0 -1
  185. package/dist/native-session-history.js.map +0 -1
  186. package/dist/operational.js.map +0 -1
  187. package/dist/process-tree.js.map +0 -1
  188. package/dist/runner-command-engine.js.map +0 -1
  189. package/dist/runner-profiles.js.map +0 -1
  190. package/dist/runner-usage.js.map +0 -1
  191. package/dist/runtime-state.js.map +0 -1
  192. package/dist/service.js.map +0 -1
  193. package/dist/storage.js.map +0 -1
  194. package/dist/terminal.js.map +0 -1
  195. package/dist/workspace.js.map +0 -1
@@ -1,59 +1,18 @@
1
1
  import { createHash } from 'node:crypto';
2
- import { open, readdir, unlink } from 'node:fs/promises';
2
+ import { readFile, readdir, unlink } from 'node:fs/promises';
3
3
  import { homedir } from 'node:os';
4
4
  import { basename, join, resolve } from 'node:path';
5
+ import { forEachJsonlRecord, listJsonlFiles, mapConcurrent, readJsonlWindow } from './native-jsonl-reader.js';
6
+ import { nodeLog } from './operational.js';
5
7
  import { stripClaudePlanTag } from './runner-command-engine.js';
6
- const DISCOVERY_READ_BYTES = 128 * 1024;
8
+ const DISCOVERY_READ_BYTES = 32 * 1024;
7
9
  const TRANSCRIPT_READ_BYTES = 4 * 1024 * 1024;
8
10
  const CONTEXT_USAGE_READ_BYTES = 256 * 1024;
9
- const TASK_ACTIVITY_READ_BYTES = 32 * 1024 * 1024;
11
+ const CLAUDE_CONTEXT_USAGE_READ_BYTES = [CONTEXT_USAGE_READ_BYTES, 1024 * 1024, 4 * 1024 * 1024];
10
12
  const transcriptIndex = new Map();
11
- /** Reads the latest durable Codex task lifecycle event without exposing it. */
12
- export async function readCodexNativeTaskActivity(workspacePath, externalSessionId, now = Date.now()) {
13
- const wanted = canonical(workspacePath);
14
- let path = transcriptIndex.get(indexKey('codex', wanted, externalSessionId));
15
- if (path === undefined) {
16
- await discoverNativeSessions('codex', workspacePath);
17
- path = transcriptIndex.get(indexKey('codex', wanted, externalSessionId));
18
- }
19
- if (path === undefined)
20
- return undefined;
21
- let content;
22
- try {
23
- const handle = await open(path, 'r');
24
- try {
25
- const metadata = await handle.stat();
26
- const start = Math.max(0, metadata.size - TASK_ACTIVITY_READ_BYTES);
27
- const buffer = Buffer.alloc(metadata.size - start);
28
- const { bytesRead } = await handle.read(buffer, 0, buffer.length, start);
29
- content = buffer.subarray(0, bytesRead).toString('utf8');
30
- }
31
- finally {
32
- await handle.close();
33
- }
34
- }
35
- catch {
36
- return undefined;
37
- }
38
- let latest;
39
- for (const line of content.split('\n')) {
40
- try {
41
- const value = JSON.parse(line);
42
- const payload = value.type === 'event_msg' && isRecord(value.payload) ? value.payload : undefined;
43
- if (payload?.type !== 'task_started' && payload?.type !== 'task_complete')
44
- continue;
45
- const seconds = payload.type === 'task_started' ? payload.started_at : payload.completed_at;
46
- const at = typeof seconds === 'number' ? seconds * 1000 : 0;
47
- latest = { state: payload.type === 'task_started' ? 'ACTIVE' : 'IDLE', at };
48
- }
49
- catch {
50
- // 单条损坏的 JSONL 记录不影响后续活动状态记录。
51
- }
52
- }
53
- if (latest === undefined)
54
- return undefined;
55
- return latest.state === 'ACTIVE' && now - latest.at > 60 * 60_000 ? 'STALE' : latest.state;
56
- }
13
+ const discoveryReads = new Map();
14
+ const DEFAULT_CLAUDE_CONTEXT_WINDOW_TOKENS = 200_000;
15
+ const CLAUDE_CONTEXT_WINDOW_ENV = 'MAR_CLAUDE_CONTEXT_WINDOW_TOKENS';
57
16
  /**
58
17
  * Read-only adapter for the supported vendor JSONL stores. It requires an
59
18
  * explicit cwd on the record and returns normalized visible messages and tool
@@ -69,8 +28,23 @@ export async function discoverNativeSessions(runner, workspacePath) {
69
28
  * hide older sessions from a project with a large global history.
70
29
  */
71
30
  export async function discoverAllNativeSessions(runner) {
31
+ const current = discoveryReads.get(runner);
32
+ if (current !== undefined)
33
+ return current;
34
+ const discovery = performNativeSessionDiscovery(runner).finally(() => discoveryReads.delete(runner));
35
+ discoveryReads.set(runner, discovery);
36
+ return discovery;
37
+ }
38
+ async function performNativeSessionDiscovery(runner) {
39
+ const startedAt = performance.now();
72
40
  const found = new Map();
73
- const files = await listJsonl(transcriptRoot(runner));
41
+ nodeLog('native.session.discovery.started', { runner });
42
+ const files = await listJsonlFiles(transcriptRoot(runner));
43
+ nodeLog('native.session.discovery.files-listed', {
44
+ runner,
45
+ files: files.length,
46
+ durationMs: Math.round(performance.now() - startedAt)
47
+ });
74
48
  const parsedFiles = await readDiscoveryHeaders(files, runner);
75
49
  for (const [index, parsed] of parsedFiles.entries()) {
76
50
  // Discovery only needs a session identity, cwd and an optional early
@@ -82,24 +56,29 @@ export async function discoverAllNativeSessions(runner) {
82
56
  transcriptIndex.set(indexKey(runner, cwd, parsed.externalSessionId), files[index]);
83
57
  found.set(`${cwd}\u0000${parsed.externalSessionId}`, parsed);
84
58
  }
59
+ nodeLog('native.session.discovery.completed', {
60
+ runner,
61
+ files: files.length,
62
+ sessions: found.size,
63
+ durationMs: Math.round(performance.now() - startedAt)
64
+ });
85
65
  return [...found.values()];
86
66
  }
87
67
  async function readDiscoveryHeaders(files, runner) {
88
- const values = Array.from({ length: files.length });
89
- let next = 0;
90
- const worker = async () => {
91
- while (true) {
92
- const index = next;
93
- next += 1;
94
- if (index >= files.length)
95
- return;
96
- values[index] = await readTranscript(files[index], runner, DISCOVERY_READ_BYTES);
97
- }
98
- };
99
- // Header reads are independent. Bounded concurrency avoids serial startup
100
- // delays without opening an unbounded number of descriptors on large homes.
101
- await Promise.all(Array.from({ length: Math.min(32, files.length) }, worker));
102
- return values;
68
+ let completed = 0;
69
+ const startedAt = performance.now();
70
+ return mapConcurrent(files, 32, async (path) => {
71
+ const value = await readTranscript(path, runner, DISCOVERY_READ_BYTES);
72
+ completed += 1;
73
+ if (completed % 250 === 0)
74
+ nodeLog('native.session.discovery.progress', {
75
+ runner,
76
+ completed,
77
+ files: files.length,
78
+ durationMs: Math.round(performance.now() - startedAt)
79
+ });
80
+ return value;
81
+ });
103
82
  }
104
83
  /** Reads one already-discovered external session again to follow appended JSONL records. */
105
84
  export async function readNativeSession(runner, workspacePath, externalSessionId) {
@@ -131,37 +110,94 @@ export async function readCodexNativeContextUsage(workspacePath, externalSession
131
110
  }
132
111
  if (path === undefined)
133
112
  return undefined;
134
- let content;
113
+ const window = await readJsonlWindow(path, { tailBytes: CONTEXT_USAGE_READ_BYTES });
114
+ if (window === undefined)
115
+ return undefined;
116
+ let usage;
117
+ forEachJsonlRecord(window.content, (record) => {
118
+ const next = codexContextUsage(record);
119
+ if (next !== undefined)
120
+ usage = next;
121
+ });
122
+ return usage;
123
+ }
124
+ /** Reads the latest actual Claude prompt occupancy without starting a CLI/SDK control stream. */
125
+ export async function readClaudeNativeContextUsage(workspacePath, externalSessionId, options = {}) {
126
+ const wanted = canonical(workspacePath);
127
+ let path = transcriptIndex.get(indexKey('claude-code', wanted, externalSessionId));
128
+ path ??= await locateClaudeTranscript(wanted, externalSessionId);
129
+ if (path === undefined) {
130
+ await discoverNativeSessions('claude-code', workspacePath);
131
+ path = transcriptIndex.get(indexKey('claude-code', wanted, externalSessionId));
132
+ }
133
+ if (path === undefined)
134
+ return undefined;
135
+ const latest = await readLatestClaudeContextUsage(path);
136
+ if (latest === undefined)
137
+ return undefined;
138
+ return {
139
+ ...latest,
140
+ maxTokens: await claudeContextWindowTokens(options)
141
+ };
142
+ }
143
+ async function readLatestClaudeContextUsage(path) {
144
+ for (const tailBytes of CLAUDE_CONTEXT_USAGE_READ_BYTES) {
145
+ const window = await readJsonlWindow(path, { tailBytes });
146
+ if (window === undefined)
147
+ return undefined;
148
+ let latest;
149
+ forEachJsonlRecord(window.content, (record) => {
150
+ const usage = claudeContextUsage(record);
151
+ if (usage !== undefined)
152
+ latest = usage;
153
+ });
154
+ if (latest !== undefined)
155
+ return latest;
156
+ if (!window.truncated)
157
+ return undefined;
158
+ }
159
+ return undefined;
160
+ }
161
+ /** Claude main transcripts use the native session ID as their filename. */
162
+ async function locateClaudeTranscript(wantedCwd, externalSessionId) {
163
+ const root = transcriptRoot('claude-code');
164
+ let entries;
135
165
  try {
136
- const handle = await open(path, 'r');
137
- try {
138
- const metadata = await handle.stat();
139
- const start = Math.max(0, metadata.size - CONTEXT_USAGE_READ_BYTES);
140
- const buffer = Buffer.alloc(metadata.size - start);
141
- const { bytesRead } = await handle.read(buffer, 0, buffer.length, start);
142
- content = buffer.subarray(0, bytesRead).toString('utf8');
143
- }
144
- finally {
145
- await handle.close();
146
- }
166
+ entries = await readdir(root, { withFileTypes: true });
147
167
  }
148
168
  catch {
149
169
  return undefined;
150
170
  }
151
- let usage;
152
- for (const line of content.split('\n')) {
153
- let record;
154
- try {
155
- record = JSON.parse(line);
156
- }
157
- catch {
158
- continue;
171
+ const candidates = entries
172
+ .filter((entry) => entry.isDirectory())
173
+ .map((entry) => join(root, entry.name, `${externalSessionId}.jsonl`));
174
+ const parsed = await mapConcurrent(candidates, 16, async (path) => ({
175
+ path,
176
+ history: await readTranscript(path, 'claude-code', DISCOVERY_READ_BYTES)
177
+ }));
178
+ const match = parsed.find(({ history }) => history?.externalSessionId === externalSessionId && canonical(history.cwd) === wantedCwd);
179
+ if (match === undefined)
180
+ return undefined;
181
+ transcriptIndex.set(indexKey('claude-code', wantedCwd, externalSessionId), match.path);
182
+ return match.path;
183
+ }
184
+ export async function claudeContextWindowTokens(options = {}) {
185
+ const environment = options.environment ?? process.env;
186
+ const direct = positiveInteger(environment[CLAUDE_CONTEXT_WINDOW_ENV]);
187
+ if (direct !== undefined)
188
+ return direct;
189
+ try {
190
+ const settings = JSON.parse(await readFile(options.settingsPath ?? join(homedir(), '.claude', 'settings.json'), 'utf8'));
191
+ if (isRecord(settings) && isRecord(settings.env)) {
192
+ const configured = positiveInteger(settings.env[CLAUDE_CONTEXT_WINDOW_ENV]);
193
+ if (configured !== undefined)
194
+ return configured;
159
195
  }
160
- const next = codexContextUsage(record);
161
- if (next !== undefined)
162
- usage = next;
163
196
  }
164
- return usage;
197
+ catch {
198
+ // Missing or malformed optional Claude settings use the stable default.
199
+ }
200
+ return DEFAULT_CLAUDE_CONTEXT_WINDOW_TOKENS;
165
201
  }
166
202
  /**
167
203
  * Permanently removes exactly one previously discovered transcript. The
@@ -200,78 +236,18 @@ function transcriptRoot(runner) {
200
236
  ? join(homedir(), '.codex', 'sessions')
201
237
  : join(homedir(), '.claude', 'projects');
202
238
  }
203
- async function listJsonl(directory) {
204
- const files = [];
205
- const walk = async (current) => {
206
- let entries;
207
- try {
208
- entries = await readdir(current, { withFileTypes: true });
209
- }
210
- catch {
211
- return;
212
- }
213
- for (const entry of entries) {
214
- const path = join(current, entry.name);
215
- if (entry.isDirectory())
216
- await walk(path);
217
- else if (entry.isFile() && entry.name.endsWith('.jsonl'))
218
- files.push(path);
219
- }
220
- };
221
- await walk(directory);
222
- return files;
223
- }
224
239
  async function readTranscript(path, runner, byteLimit) {
225
- let content;
226
- let truncated = false;
227
- try {
228
- const handle = await open(path, 'r');
229
- try {
230
- const metadata = await handle.stat();
231
- const headSize = Math.min(byteLimit, metadata.size);
232
- const head = Buffer.alloc(headSize);
233
- const { bytesRead: headBytesRead } = await handle.read(head, 0, head.length, 0);
234
- if (headSize === metadata.size) {
235
- content = head.subarray(0, headBytesRead).toString('utf8');
236
- }
237
- else {
238
- // Discovery needs identity from the header and genuine recency from
239
- // the latest visible records. Keep both bounded instead of falling
240
- // back to mtime, whose changes do not necessarily mean a new chat.
241
- const tailSize = Math.min(byteLimit, metadata.size - headSize);
242
- truncated = headSize + tailSize < metadata.size;
243
- const tail = Buffer.alloc(tailSize);
244
- const { bytesRead: tailBytesRead } = await handle.read(tail, 0, tail.length, metadata.size - tailSize);
245
- content = `${head.subarray(0, headBytesRead).toString('utf8')}\n${tail
246
- .subarray(0, tailBytesRead)
247
- .toString('utf8')}`;
248
- }
249
- }
250
- finally {
251
- await handle.close();
252
- }
253
- }
254
- catch {
240
+ const window = await readJsonlWindow(path, { headBytes: byteLimit, tailBytes: byteLimit });
241
+ if (window === undefined)
255
242
  return undefined;
256
- }
243
+ const { content, truncated } = window;
257
244
  let cwd;
258
245
  let id;
259
246
  const items = [];
260
247
  const toolIndexes = new Map();
261
248
  const pendingToolResults = new Map();
262
249
  const metaRecordIds = new Set();
263
- for (const line of content.split('\n')) {
264
- if (line.length === 0)
265
- continue;
266
- let value;
267
- try {
268
- value = JSON.parse(line);
269
- }
270
- catch {
271
- continue;
272
- }
273
- if (!isRecord(value))
274
- continue;
250
+ forEachJsonlRecord(content, (value) => {
275
251
  cwd ??= workspacePath(value);
276
252
  id ??= sessionId(value);
277
253
  const recordId = typeof value['uuid'] === 'string' ? value['uuid'] : undefined;
@@ -280,9 +256,13 @@ async function readTranscript(path, runner, byteLimit) {
280
256
  metaRecordIds.add(recordId);
281
257
  if (isHiddenTranscriptRecord(value) ||
282
258
  (parentId !== undefined && metaRecordIds.has(parentId))) {
283
- continue;
259
+ return;
284
260
  }
285
- for (const item of transcriptItems(value)) {
261
+ const generatedImage = codexGeneratedImageEvent(value);
262
+ if (generatedImage !== undefined)
263
+ items.push(generatedImage);
264
+ for (const parsedItem of transcriptItems(value)) {
265
+ const item = recordId === undefined ? parsedItem : { ...parsedItem, nativeId: recordId };
286
266
  if (item.kind === 'tool_result') {
287
267
  const toolIndex = toolIndexes.get(item.toolUseId);
288
268
  if (toolIndex === undefined)
@@ -301,7 +281,7 @@ async function readTranscript(path, runner, byteLimit) {
301
281
  }
302
282
  items.push(item);
303
283
  }
304
- }
284
+ });
305
285
  for (const result of pendingToolResults.values())
306
286
  items.push({
307
287
  kind: 'tool_call',
@@ -312,6 +292,9 @@ async function readTranscript(path, runner, byteLimit) {
312
292
  inputTruncated: false,
313
293
  outputSummary: result.output,
314
294
  outputTruncated: result.truncated,
295
+ ...(result.imageAttachments === undefined
296
+ ? {}
297
+ : { imageAttachments: result.imageAttachments }),
315
298
  failed: result.failed,
316
299
  createdAt: result.createdAt
317
300
  });
@@ -375,6 +358,37 @@ function codexContextUsage(value) {
375
358
  return undefined;
376
359
  return { usedTokens: promptUsage.input_tokens, maxTokens };
377
360
  }
361
+ function claudeContextUsage(value) {
362
+ if (value.type !== 'assistant' || !isRecord(value.message))
363
+ return undefined;
364
+ const model = value.message.model;
365
+ const usage = value.message.usage;
366
+ if (typeof model !== 'string' ||
367
+ model.length === 0 ||
368
+ model === '<synthetic>' ||
369
+ !isRecord(usage))
370
+ return undefined;
371
+ const inputTokens = nonNegativeInteger(usage.input_tokens);
372
+ const cacheCreationTokens = optionalNonNegativeInteger(usage.cache_creation_input_tokens);
373
+ const cacheReadTokens = optionalNonNegativeInteger(usage.cache_read_input_tokens);
374
+ if (inputTokens === undefined ||
375
+ cacheCreationTokens === undefined ||
376
+ cacheReadTokens === undefined)
377
+ return undefined;
378
+ return { model, usedTokens: inputTokens + cacheCreationTokens + cacheReadTokens };
379
+ }
380
+ function optionalNonNegativeInteger(value) {
381
+ return value === undefined ? 0 : nonNegativeInteger(value);
382
+ }
383
+ function nonNegativeInteger(value) {
384
+ return typeof value === 'number' && Number.isSafeInteger(value) && value >= 0 ? value : undefined;
385
+ }
386
+ function positiveInteger(value) {
387
+ if (typeof value !== 'string' || !/^\d+$/.test(value.trim()))
388
+ return undefined;
389
+ const parsed = Number(value);
390
+ return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : undefined;
391
+ }
378
392
  function transcriptItems(record) {
379
393
  const createdAt = timestampOf(record);
380
394
  const results = [...toolResultsOf(record, createdAt), ...codexToolResultsOf(record, createdAt)];
@@ -382,7 +396,10 @@ function transcriptItems(record) {
382
396
  return results;
383
397
  const values = [
384
398
  ...toolUsesOf(record, createdAt),
385
- ...codexToolUsesOf(record, createdAt)
399
+ ...codexToolUsesOf(record, createdAt),
400
+ ...codexReasoningSummariesOf(record, createdAt),
401
+ ...unsupportedCodexResponseItems(record, createdAt),
402
+ ...unsupportedTranscriptItems(record, createdAt)
386
403
  ];
387
404
  const role = roleOf(record);
388
405
  if (role === undefined)
@@ -398,6 +415,65 @@ function transcriptItems(record) {
398
415
  values.unshift({ kind: 'message', role, text, createdAt });
399
416
  return values;
400
417
  }
418
+ function unsupportedCodexResponseItems(record, createdAt) {
419
+ const payload = codexToolPayload(record);
420
+ const type = typeof payload?.type === 'string' ? payload.type : undefined;
421
+ if (type === undefined ||
422
+ typeof payload?.role === 'string' ||
423
+ type === 'reasoning' ||
424
+ type === 'custom_tool_call' ||
425
+ type === 'custom_tool_call_output' ||
426
+ type === 'function_call' ||
427
+ type === 'function_call_output')
428
+ return [];
429
+ return [
430
+ {
431
+ kind: 'unknown',
432
+ sourceEventType: `codex:${type}`,
433
+ label: `Codex ${compact(type).slice(0, 128)}`,
434
+ createdAt
435
+ }
436
+ ];
437
+ }
438
+ function codexReasoningSummariesOf(record, createdAt) {
439
+ const payload = codexToolPayload(record);
440
+ if (payload?.type !== 'reasoning')
441
+ return [];
442
+ const sections = Array.isArray(payload.summary)
443
+ ? payload.summary.flatMap((value, index) => {
444
+ if (typeof value !== 'string')
445
+ return [];
446
+ const text = compact(value).slice(0, 10_000);
447
+ return text.length === 0 ? [] : [{ index, text }];
448
+ })
449
+ : [];
450
+ return [{ kind: 'reasoning_summary', sections, createdAt }];
451
+ }
452
+ function unsupportedTranscriptItems(record, createdAt) {
453
+ return contentBlocks(record).flatMap((block) => {
454
+ const type = typeof block.type === 'string' ? block.type : undefined;
455
+ if (type === undefined ||
456
+ type === 'text' ||
457
+ type === 'input_text' ||
458
+ type === 'output_text' ||
459
+ type === 'image' ||
460
+ type === 'input_image' ||
461
+ type === 'thinking' ||
462
+ type === 'redacted_thinking' ||
463
+ type === 'encrypted_thinking' ||
464
+ type === 'tool_use' ||
465
+ type === 'tool_result')
466
+ return [];
467
+ return [
468
+ {
469
+ kind: 'unknown',
470
+ sourceEventType: `transcript:${type}`,
471
+ label: `Agent ${compact(type).slice(0, 128)}`,
472
+ createdAt
473
+ }
474
+ ];
475
+ });
476
+ }
401
477
  function toolUsesOf(record, createdAt) {
402
478
  return contentBlocks(record).flatMap((block) => {
403
479
  if (block['type'] !== 'tool_use' ||
@@ -479,6 +555,7 @@ function codexToolResultsOf(record, createdAt) {
479
555
  if (toolUseId === undefined)
480
556
  return [];
481
557
  const output = compactValue(codexToolOutput(payload['output'] ?? payload['contentItems']), 10_000);
558
+ const imageAttachments = codexImagePlaceholders(payload['output'] ?? payload['contentItems']);
482
559
  return [
483
560
  {
484
561
  kind: 'tool_result',
@@ -486,6 +563,7 @@ function codexToolResultsOf(record, createdAt) {
486
563
  output: output.text,
487
564
  failed: payload['status'] === 'failed' || payload['error'] !== undefined,
488
565
  truncated: output.truncated,
566
+ ...(imageAttachments.length === 0 ? {} : { imageAttachments }),
489
567
  createdAt
490
568
  }
491
569
  ];
@@ -495,6 +573,31 @@ function codexToolPayload(record) {
495
573
  ? record['payload']
496
574
  : undefined;
497
575
  }
576
+ function codexGeneratedImageEvent(record) {
577
+ if (record['type'] !== 'event_msg' || !isRecord(record['payload']))
578
+ return undefined;
579
+ const payload = record['payload'];
580
+ if (payload['type'] !== 'image_generation_end' ||
581
+ typeof payload['call_id'] !== 'string' ||
582
+ typeof payload['saved_path'] !== 'string')
583
+ return undefined;
584
+ const localPath = payload['saved_path'];
585
+ if (!localPath.includes('/.codex/generated_images/') || !/\.(?:png|jpe?g|webp)$/i.test(localPath))
586
+ return undefined;
587
+ return {
588
+ kind: 'tool_call',
589
+ toolUseId: payload['call_id'],
590
+ toolName: 'imagegen',
591
+ input: null,
592
+ inputSummary: null,
593
+ inputTruncated: false,
594
+ outputSummary: payload['status'] === 'completed' ? '图片生成完成' : '图片生成失败',
595
+ outputTruncated: false,
596
+ imageAttachments: [{ name: basename(localPath), mime: imageMime(localPath), localPath }],
597
+ failed: payload['status'] !== 'completed',
598
+ createdAt: timestampOf(record)
599
+ };
600
+ }
498
601
  /** Extract Codex output text blocks instead of rendering their JSON envelopes. */
499
602
  function codexToolOutput(value) {
500
603
  if (!Array.isArray(value))
@@ -505,15 +608,94 @@ function codexToolOutput(value) {
505
608
  if (!isRecord(block))
506
609
  return [];
507
610
  const direct = firstString(block, ['text', 'content']);
508
- return direct === undefined ? [] : [direct];
611
+ return direct === undefined ? [] : [codexVisibleToolText(direct)];
509
612
  });
510
613
  return text.length > 0 ? text.join('') : value;
511
614
  }
615
+ function codexVisibleToolText(value) {
616
+ const marker = 'data:image/';
617
+ let cursor = 0;
618
+ let visible = '';
619
+ while (cursor < value.length) {
620
+ const start = value.indexOf(marker, cursor);
621
+ if (start < 0)
622
+ return visible + value.slice(cursor);
623
+ visible += `${value.slice(cursor, start)}[图片内容已过滤]`;
624
+ const doubleQuote = value.indexOf('"', start);
625
+ const singleQuote = value.indexOf("'", start);
626
+ const end = doubleQuote < 0
627
+ ? singleQuote
628
+ : singleQuote < 0
629
+ ? doubleQuote
630
+ : Math.min(doubleQuote, singleQuote);
631
+ if (end < 0)
632
+ return visible;
633
+ cursor = end;
634
+ }
635
+ return visible;
636
+ }
637
+ /** Extract only image placeholder metadata from native Codex output. */
638
+ function codexImagePlaceholders(value) {
639
+ const paths = [];
640
+ let visited = 0;
641
+ const visit = (current, depth) => {
642
+ if (paths.length >= 5 || visited >= 128 || depth > 5 || current == null)
643
+ return;
644
+ visited += 1;
645
+ if (typeof current === 'string') {
646
+ collectGeneratedImagePaths(current, paths);
647
+ return;
648
+ }
649
+ if (Array.isArray(current)) {
650
+ for (const entry of current)
651
+ visit(entry, depth + 1);
652
+ return;
653
+ }
654
+ if (!isRecord(current))
655
+ return;
656
+ for (const child of Object.values(current))
657
+ visit(child, depth + 1);
658
+ };
659
+ visit(value, 0);
660
+ return paths.map((localPath) => ({
661
+ name: basename(localPath),
662
+ mime: imageMime(localPath),
663
+ localPath
664
+ }));
665
+ }
666
+ function collectGeneratedImagePaths(value, paths) {
667
+ const marker = '/.codex/generated_images/';
668
+ let cursor = 0;
669
+ while (paths.length < 5) {
670
+ const markerIndex = value.indexOf(marker, cursor);
671
+ if (markerIndex < 0)
672
+ return;
673
+ let start = markerIndex;
674
+ while (start > 0 && !/[\s"']/.test(value[start - 1]))
675
+ start -= 1;
676
+ let end = markerIndex + marker.length;
677
+ while (end < value.length && !/[\s"']/.test(value[end]))
678
+ end += 1;
679
+ const path = value.slice(start, end).replace(/[),.;:]+$/, '');
680
+ if (/\.(?:png|jpe?g|webp)$/i.test(path) && !paths.includes(path))
681
+ paths.push(path);
682
+ cursor = end;
683
+ }
684
+ }
685
+ function imageMime(path) {
686
+ const extension = path.slice(path.lastIndexOf('.') + 1).toLowerCase();
687
+ if (extension === 'jpg' || extension === 'jpeg')
688
+ return 'image/jpeg';
689
+ if (extension === 'webp')
690
+ return 'image/webp';
691
+ return 'image/png';
692
+ }
512
693
  function withToolResult(call, result) {
513
694
  return {
514
695
  ...call,
515
696
  outputSummary: result.output,
516
697
  outputTruncated: result.truncated,
698
+ ...(result.imageAttachments === undefined ? {} : { imageAttachments: result.imageAttachments }),
517
699
  failed: result.failed
518
700
  };
519
701
  }
@@ -649,4 +831,3 @@ function indexKey(runner, cwd, externalSessionId) {
649
831
  function isRecord(value) {
650
832
  return typeof value === 'object' && value !== null && !Array.isArray(value);
651
833
  }
652
- //# sourceMappingURL=native-session-history.js.map
@@ -0,0 +1,33 @@
1
+ export interface OpenCodeProbe {
2
+ readonly available: boolean;
3
+ readonly compatible: boolean;
4
+ readonly version?: string;
5
+ }
6
+ export interface OpenCodeProviderCatalog {
7
+ readonly connected?: readonly string[];
8
+ readonly default?: Readonly<Record<string, string>>;
9
+ readonly all?: readonly {
10
+ readonly id?: string;
11
+ readonly name?: string;
12
+ readonly source?: 'env' | 'config' | 'custom' | 'api';
13
+ readonly models?: Readonly<Record<string, {
14
+ readonly id?: string;
15
+ readonly name?: string;
16
+ readonly variants?: Readonly<Record<string, {
17
+ readonly disabled?: boolean;
18
+ }>>;
19
+ readonly limit?: {
20
+ readonly context?: number;
21
+ };
22
+ }>>;
23
+ }[];
24
+ }
25
+ export interface OpenCodeProfileModel {
26
+ readonly id: string;
27
+ readonly label: string;
28
+ readonly supportedEfforts: string[];
29
+ }
30
+ export declare function probeOpenCode(command?: string): OpenCodeProbe;
31
+ export declare function probeOpenCodeAsync(command?: string): Promise<OpenCodeProbe>;
32
+ export declare function openCodeProfileModels(catalog: OpenCodeProviderCatalog): OpenCodeProfileModel[];
33
+ export declare function openCodeContextLimits(catalog: OpenCodeProviderCatalog): ReadonlyMap<string, number>;