@router-agent/runtime 0.0.0-stage → 0.3.32

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 (82) hide show
  1. package/dist/agents/claude.d.ts +51 -0
  2. package/dist/agents/claude.js +270 -0
  3. package/dist/agents/claude.js.map +1 -0
  4. package/dist/agents/codex.d.ts +36 -0
  5. package/dist/agents/codex.js +292 -0
  6. package/dist/agents/codex.js.map +1 -0
  7. package/dist/agents/profile.d.ts +39 -0
  8. package/dist/agents/profile.js +2 -0
  9. package/dist/agents/profile.js.map +1 -0
  10. package/dist/agents/registry.d.ts +21 -0
  11. package/dist/agents/registry.js +78 -0
  12. package/dist/agents/registry.js.map +1 -0
  13. package/dist/errors.d.ts +4 -0
  14. package/dist/errors.js +9 -0
  15. package/dist/errors.js.map +1 -0
  16. package/dist/evidence-gate.d.ts +78 -0
  17. package/dist/evidence-gate.js +196 -0
  18. package/dist/evidence-gate.js.map +1 -0
  19. package/dist/index.d.ts +22 -0
  20. package/dist/index.js +21 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/naming.d.ts +23 -0
  23. package/dist/naming.js +40 -0
  24. package/dist/naming.js.map +1 -0
  25. package/dist/permissions/acp.d.ts +21 -0
  26. package/dist/permissions/acp.js +73 -0
  27. package/dist/permissions/acp.js.map +1 -0
  28. package/dist/permissions.d.ts +1 -0
  29. package/dist/permissions.js +2 -0
  30. package/dist/permissions.js.map +1 -0
  31. package/dist/plugins/loader.d.ts +46 -0
  32. package/dist/plugins/loader.js +117 -0
  33. package/dist/plugins/loader.js.map +1 -0
  34. package/dist/plugins/types.d.ts +52 -0
  35. package/dist/plugins/types.js +2 -0
  36. package/dist/plugins/types.js.map +1 -0
  37. package/dist/port.d.ts +295 -0
  38. package/dist/port.js +17 -0
  39. package/dist/port.js.map +1 -0
  40. package/dist/registry.d.ts +13 -0
  41. package/dist/registry.js +35 -0
  42. package/dist/registry.js.map +1 -0
  43. package/dist/report-poller.d.ts +58 -0
  44. package/dist/report-poller.js +130 -0
  45. package/dist/report-poller.js.map +1 -0
  46. package/dist/resume.d.ts +52 -0
  47. package/dist/resume.js +109 -0
  48. package/dist/resume.js.map +1 -0
  49. package/dist/runtimes/acp-agents.d.ts +85 -0
  50. package/dist/runtimes/acp-agents.js +167 -0
  51. package/dist/runtimes/acp-agents.js.map +1 -0
  52. package/dist/runtimes/acp-detect-utils.d.ts +4 -0
  53. package/dist/runtimes/acp-detect-utils.js +35 -0
  54. package/dist/runtimes/acp-detect-utils.js.map +1 -0
  55. package/dist/runtimes/builtin-options.d.ts +3 -0
  56. package/dist/runtimes/builtin-options.js +44 -0
  57. package/dist/runtimes/builtin-options.js.map +1 -0
  58. package/dist/runtimes/catalog.d.ts +31 -0
  59. package/dist/runtimes/catalog.js +75 -0
  60. package/dist/runtimes/catalog.js.map +1 -0
  61. package/dist/runtimes/options.d.ts +60 -0
  62. package/dist/runtimes/options.js +89 -0
  63. package/dist/runtimes/options.js.map +1 -0
  64. package/dist/spec/contract.md +120 -0
  65. package/dist/spec/index.d.ts +35 -0
  66. package/dist/spec/index.js +59 -0
  67. package/dist/spec/index.js.map +1 -0
  68. package/dist/spec/prompt-mode.md +176 -0
  69. package/dist/spec/reader.d.ts +5 -0
  70. package/dist/spec/reader.js +32 -0
  71. package/dist/spec/reader.js.map +1 -0
  72. package/dist/status.d.ts +8 -0
  73. package/dist/status.js +22 -0
  74. package/dist/status.js.map +1 -0
  75. package/dist/workspaces/git-worktree.d.ts +44 -0
  76. package/dist/workspaces/git-worktree.js +183 -0
  77. package/dist/workspaces/git-worktree.js.map +1 -0
  78. package/dist/workspaces/overview.d.ts +66 -0
  79. package/dist/workspaces/overview.js +12 -0
  80. package/dist/workspaces/overview.js.map +1 -0
  81. package/package.json +36 -4
  82. package/README.md +0 -3
@@ -0,0 +1,51 @@
1
+ import type { SessionSnapshot } from '../port.js';
2
+ import type { AgentSessionReader } from './profile.js';
3
+ /** Claude Code 默认把会话记录写在这里。 */
4
+ export declare function defaultClaudeProjectsDir(): string;
5
+ /** Claude Code 把 cwd 里所有非字母数字字符替换成 `-` 作为项目目录名。 */
6
+ export declare function claudeProjectSlug(workdir: string): string;
7
+ /**
8
+ * 按 workdir 找最近一次 Claude Code 会话的 id。
9
+ *
10
+ * Claude Code 的会话文件名就是 `claude --resume` 接受的 session id,
11
+ * 所以这里只按 mtime 取最新的 .jsonl,不解析文件内容。
12
+ */
13
+ export declare function latestClaudeSessionId(workdir: string, opts?: {
14
+ projectsDir?: string;
15
+ }): string | null;
16
+ export interface ReadSessionOptions {
17
+ projectsDir?: string;
18
+ /** 只读文件尾部这么多字节,避免把几 MB 的会话全量读进内存。 */
19
+ maxBytes?: number;
20
+ /** 最多保留多少条最近活动。 */
21
+ maxEvents?: number;
22
+ /** 精确匹配会话 ID;找不到时返回 null,不退化成「最新文件」。 */
23
+ sessionId?: string;
24
+ }
25
+ /**
26
+ * 读取某个 workdir 对应 Claude Code 会话的最新活动。
27
+ *
28
+ * 优先用 Agent 自己的结构化会话记录,而不是终端渲染屏幕:
29
+ * 这里只有 assistant 文本与工具调用,没有 TUI 状态栏、spinner 和输入回显。
30
+ * 找不到会话(非 Claude 运行时 / 文件尚未落盘)时返回 null,调用方退回终端读取。
31
+ */
32
+ export declare function readClaudeSession(workdir: string, opts?: ReadSessionOptions): SessionSnapshot | null;
33
+ /** 某个会话的记录文件路径;找不到返回 null(不做「最新文件」兜底,避免串工单)。 */
34
+ export declare function claudeTranscriptPath(workdir: string, sessionId: string, opts?: {
35
+ projectsDir?: string;
36
+ }): string | null;
37
+ /** 记录文件当前大小,用来只检查「注入之后新增的内容」。 */
38
+ export declare function transcriptSize(path: string): number;
39
+ /** 读取记录文件从 `from` 开始的内容;文件变短(重建)时从头读。 */
40
+ export declare function readTranscriptSince(path: string, from: number): string;
41
+ /** 工具调用的结构化摘要:只保留「工具名 + 目标」,不把完整命令 / 文件内容带进话题。 */
42
+ export interface ToolInfo {
43
+ name: string;
44
+ target?: string;
45
+ }
46
+ /** 从工具名与入参里提炼结构化目标,供进展巡检生成语义摘要。 */
47
+ export declare function describeToolInfo(name: unknown, input: unknown): ToolInfo;
48
+ /** 把工具调用压成一行人话;不把完整命令 / 文件内容带进话题。 */
49
+ export declare function describeToolUse(name: unknown, input: unknown): string;
50
+ /** Claude Code 的会话记录读取器。 */
51
+ export declare const claudeSessionReader: AgentSessionReader;
@@ -0,0 +1,270 @@
1
+ /**
2
+ * Claude Code 的会话记录读取。
3
+ *
4
+ * Claude 把每个项目的会话写在 `~/.claude/projects/<cwd 转义>/<sessionId>.jsonl`,
5
+ * 文件名本身就是 `claude --resume` 接受的 session id。
6
+ */
7
+ import { closeSync, existsSync, openSync, readdirSync, readSync, statSync } from 'node:fs';
8
+ import { homedir } from 'node:os';
9
+ import { basename, join } from 'node:path';
10
+ /** Claude Code 默认把会话记录写在这里。 */
11
+ export function defaultClaudeProjectsDir() {
12
+ return join(homedir(), '.claude', 'projects');
13
+ }
14
+ /** Claude Code 把 cwd 里所有非字母数字字符替换成 `-` 作为项目目录名。 */
15
+ export function claudeProjectSlug(workdir) {
16
+ return workdir.replace(/[^a-zA-Z0-9]/g, '-');
17
+ }
18
+ /**
19
+ * 按 workdir 找最近一次 Claude Code 会话的 id。
20
+ *
21
+ * Claude Code 的会话文件名就是 `claude --resume` 接受的 session id,
22
+ * 所以这里只按 mtime 取最新的 .jsonl,不解析文件内容。
23
+ */
24
+ export function latestClaudeSessionId(workdir, opts = {}) {
25
+ if (!workdir)
26
+ return null;
27
+ const dir = join(opts.projectsDir ?? defaultClaudeProjectsDir(), claudeProjectSlug(workdir));
28
+ if (!existsSync(dir))
29
+ return null;
30
+ let files;
31
+ try {
32
+ files = readdirSync(dir).filter(file => file.endsWith('.jsonl'));
33
+ }
34
+ catch {
35
+ return null;
36
+ }
37
+ if (files.length === 0)
38
+ return null;
39
+ let latest = null;
40
+ for (const file of files) {
41
+ let mtime = 0;
42
+ try {
43
+ mtime = statSync(join(dir, file)).mtimeMs;
44
+ }
45
+ catch {
46
+ // 读不到 mtime 的文件排在最后,不影响其它候选。
47
+ }
48
+ if (!latest || mtime > latest.mtime)
49
+ latest = { file, mtime };
50
+ }
51
+ return latest ? latest.file.slice(0, -'.jsonl'.length) : null;
52
+ }
53
+ /**
54
+ * 读取某个 workdir 对应 Claude Code 会话的最新活动。
55
+ *
56
+ * 优先用 Agent 自己的结构化会话记录,而不是终端渲染屏幕:
57
+ * 这里只有 assistant 文本与工具调用,没有 TUI 状态栏、spinner 和输入回显。
58
+ * 找不到会话(非 Claude 运行时 / 文件尚未落盘)时返回 null,调用方退回终端读取。
59
+ */
60
+ export function readClaudeSession(workdir, opts = {}) {
61
+ if (!workdir)
62
+ return null;
63
+ const projectsDir = opts.projectsDir ?? defaultClaudeProjectsDir();
64
+ const dir = join(projectsDir, claudeProjectSlug(workdir));
65
+ if (!existsSync(dir))
66
+ return null;
67
+ let files;
68
+ try {
69
+ files = readdirSync(dir).filter(file => file.endsWith('.jsonl'));
70
+ }
71
+ catch {
72
+ return null;
73
+ }
74
+ if (files.length === 0)
75
+ return null;
76
+ if (opts.sessionId) {
77
+ const exact = files.find(file => file === `${opts.sessionId}.jsonl` || file.includes(opts.sessionId));
78
+ if (!exact)
79
+ return null;
80
+ files = [exact];
81
+ }
82
+ const latest = files
83
+ .map(file => {
84
+ const path = join(dir, file);
85
+ try {
86
+ return { path, mtime: statSync(path).mtimeMs };
87
+ }
88
+ catch {
89
+ return { path, mtime: 0 };
90
+ }
91
+ })
92
+ .sort((a, b) => b.mtime - a.mtime)[0];
93
+ const maxBytes = opts.maxBytes ?? 512 * 1024;
94
+ const maxEvents = opts.maxEvents ?? 40;
95
+ const events = [];
96
+ let at = '';
97
+ let sessionId = '';
98
+ for (const line of readTailLines(latest.path, maxBytes)) {
99
+ let record;
100
+ try {
101
+ record = JSON.parse(line);
102
+ }
103
+ catch {
104
+ continue;
105
+ }
106
+ if (record?.type !== 'assistant')
107
+ continue;
108
+ const timestamp = typeof record.timestamp === 'string' ? record.timestamp : '';
109
+ const content = record.message?.content;
110
+ if (!timestamp || !Array.isArray(content))
111
+ continue;
112
+ const before = events.length;
113
+ for (const block of content) {
114
+ if (!block || typeof block !== 'object')
115
+ continue;
116
+ if (block.type === 'text') {
117
+ const text = String(block.text ?? '').trim();
118
+ if (text)
119
+ events.push({ at: timestamp, kind: 'text', text });
120
+ }
121
+ else if (block.type === 'tool_use') {
122
+ events.push({
123
+ at: timestamp,
124
+ kind: 'tool',
125
+ text: describeToolUse(block.name, block.input),
126
+ tool: describeToolInfo(block.name, block.input),
127
+ });
128
+ }
129
+ }
130
+ if (events.length > before) {
131
+ at = timestamp;
132
+ if (typeof record.sessionId === 'string')
133
+ sessionId = record.sessionId;
134
+ }
135
+ }
136
+ if (events.length === 0)
137
+ return null;
138
+ return {
139
+ at,
140
+ events: events.slice(-maxEvents),
141
+ ...(sessionId ? { sessionId } : {}),
142
+ filePath: latest.path,
143
+ };
144
+ }
145
+ /** 从文件尾部读有界的一段;首行可能被截断,直接丢弃。 */
146
+ function readTailLines(path, maxBytes) {
147
+ const size = statSync(path).size;
148
+ const start = Math.max(0, size - maxBytes);
149
+ const length = size - start;
150
+ if (length <= 0)
151
+ return [];
152
+ const buffer = Buffer.alloc(length);
153
+ const fd = openSync(path, 'r');
154
+ try {
155
+ readSync(fd, buffer, 0, length, start);
156
+ }
157
+ finally {
158
+ closeSync(fd);
159
+ }
160
+ const lines = buffer.toString('utf8').split('\n');
161
+ if (start > 0)
162
+ lines.shift();
163
+ return lines.filter(line => line.trim().length > 0);
164
+ }
165
+ /** 某个会话的记录文件路径;找不到返回 null(不做「最新文件」兜底,避免串工单)。 */
166
+ export function claudeTranscriptPath(workdir, sessionId, opts = {}) {
167
+ if (!workdir || !sessionId)
168
+ return null;
169
+ const file = join(opts.projectsDir ?? defaultClaudeProjectsDir(), claudeProjectSlug(workdir), `${sessionId}.jsonl`);
170
+ return existsSync(file) ? file : null;
171
+ }
172
+ /** 记录文件当前大小,用来只检查「注入之后新增的内容」。 */
173
+ export function transcriptSize(path) {
174
+ try {
175
+ return statSync(path).size;
176
+ }
177
+ catch {
178
+ return 0;
179
+ }
180
+ }
181
+ /** 读取记录文件从 `from` 开始的内容;文件变短(重建)时从头读。 */
182
+ export function readTranscriptSince(path, from) {
183
+ let size = 0;
184
+ try {
185
+ size = statSync(path).size;
186
+ }
187
+ catch {
188
+ return '';
189
+ }
190
+ const start = size >= from ? from : 0;
191
+ const length = size - start;
192
+ if (length <= 0)
193
+ return '';
194
+ const buffer = Buffer.alloc(length);
195
+ const fd = openSync(path, 'r');
196
+ try {
197
+ readSync(fd, buffer, 0, length, start);
198
+ }
199
+ finally {
200
+ closeSync(fd);
201
+ }
202
+ return buffer.toString('utf8');
203
+ }
204
+ /** 从工具名与入参里提炼结构化目标,供进展巡检生成语义摘要。 */
205
+ export function describeToolInfo(name, input) {
206
+ const tool = String(name ?? 'tool');
207
+ const args = (input && typeof input === 'object' ? input : {});
208
+ const file = typeof args.file_path === 'string' ? basename(args.file_path) : '';
209
+ const target = (value) => {
210
+ const text = oneLine(value);
211
+ return text ? { target: text } : {};
212
+ };
213
+ switch (tool) {
214
+ case 'Bash':
215
+ return { name: 'Bash', ...target(args.description ?? args.command) };
216
+ case 'Read':
217
+ case 'Edit':
218
+ case 'Write':
219
+ case 'MultiEdit':
220
+ case 'NotebookEdit':
221
+ return { name: tool, ...(file ? { target: file } : {}) };
222
+ case 'Grep':
223
+ case 'Glob':
224
+ return { name: tool, ...target(args.pattern) };
225
+ case 'Task':
226
+ return { name: 'Task', ...target(args.description) };
227
+ default:
228
+ return { name: tool };
229
+ }
230
+ }
231
+ /** 把工具调用压成一行人话;不把完整命令 / 文件内容带进话题。 */
232
+ export function describeToolUse(name, input) {
233
+ const info = describeToolInfo(name, input);
234
+ switch (info.name) {
235
+ case 'Bash':
236
+ return `Bash:${info.target ?? '执行命令'}`;
237
+ case 'Read':
238
+ case 'Edit':
239
+ case 'Write':
240
+ case 'MultiEdit':
241
+ case 'NotebookEdit':
242
+ return info.target ? `${info.name}(${info.target})` : info.name;
243
+ case 'Grep':
244
+ case 'Glob':
245
+ return `${info.name}(${info.target ?? '搜索'})`;
246
+ case 'Task':
247
+ return `Task:${info.target ?? '子任务'}`;
248
+ default:
249
+ return info.name;
250
+ }
251
+ }
252
+ function oneLine(value) {
253
+ const text = String(value ?? '').replace(/\s+/g, ' ').trim();
254
+ return text.length <= 160 ? text : `${text.slice(0, 160)}…`;
255
+ }
256
+ /** Claude Code 的会话记录读取器。 */
257
+ export const claudeSessionReader = {
258
+ latestSessionId(workdir, opts) {
259
+ return latestClaudeSessionId(workdir, opts?.root ? { projectsDir: opts.root } : {});
260
+ },
261
+ read(workdir, opts) {
262
+ return readClaudeSession(workdir, {
263
+ ...(opts?.root ? { projectsDir: opts.root } : {}),
264
+ ...(opts?.maxBytes !== undefined ? { maxBytes: opts.maxBytes } : {}),
265
+ ...(opts?.maxEvents !== undefined ? { maxEvents: opts.maxEvents } : {}),
266
+ ...(opts?.sessionId ? { sessionId: opts.sessionId } : {}),
267
+ });
268
+ },
269
+ };
270
+ //# sourceMappingURL=claude.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claude.js","sourceRoot":"","sources":["../../../../../packages/runtime/src/agents/claude.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ import type { SessionSnapshot } from '../port.js';
2
+ import type { AgentSessionReader } from './profile.js';
3
+ /** Codex 的数据根目录。 */
4
+ export declare function defaultCodexHome(): string;
5
+ /** 找到属于这个 workdir 的最近一次 Codex 会话。 */
6
+ export declare function findCodexSession(workdir: string, opts?: {
7
+ root?: string;
8
+ sessionId?: string;
9
+ }): {
10
+ path: string;
11
+ sessionId: string;
12
+ } | null;
13
+ /** 按 workdir 找最近一次 Codex 会话的 id。 */
14
+ export declare function latestCodexSessionId(workdir: string, opts?: {
15
+ root?: string;
16
+ }): string | null;
17
+ /** Codex 的工具调用摘要:只留「工具名 + 目标」,不把完整命令带进话题。 */
18
+ export interface CodexToolInfo {
19
+ name: string;
20
+ target?: string;
21
+ }
22
+ export declare function describeCodexToolInfo(name: unknown, args: unknown): CodexToolInfo;
23
+ export declare function describeCodexToolUse(name: unknown, args: unknown): string;
24
+ /**
25
+ * 读取某个 workdir 对应 Codex 会话的最新活动。
26
+ *
27
+ * 只取 assistant 文本与工具调用;reasoning 摘要与 usage 记录不进话题。
28
+ */
29
+ export declare function readCodexSession(workdir: string, opts?: {
30
+ root?: string;
31
+ maxBytes?: number;
32
+ maxEvents?: number;
33
+ sessionId?: string;
34
+ }): SessionSnapshot | null;
35
+ /** Codex 的会话记录读取器。 */
36
+ export declare const codexSessionReader: AgentSessionReader;
@@ -0,0 +1,292 @@
1
+ /**
2
+ * Codex 的会话记录读取。
3
+ *
4
+ * Codex 把每次会话写成 `~/.codex/sessions/<年>/<月>/<日>/rollout-<时间>-<uuid>.jsonl`:
5
+ * 目录按日期分,不像 Claude 那样按项目分,所以要靠首行的 `session_meta.cwd` 反查
6
+ * 「哪个会话属于这个 worktree」。
7
+ */
8
+ import { closeSync, existsSync, openSync, readdirSync, readSync, statSync } from 'node:fs';
9
+ import { homedir } from 'node:os';
10
+ import { join } from 'node:path';
11
+ /** Codex 的数据根目录。 */
12
+ export function defaultCodexHome() {
13
+ return join(homedir(), '.codex');
14
+ }
15
+ /** 最多检查多少个 rollout 文件;按 mtime 从新到旧找,超出就不再往下翻。 */
16
+ const MAX_CANDIDATES = 300;
17
+ /** 查找结果的短缓存:一次巡检里「找会话」与「读会话」会连着调两次。 */
18
+ const LOOKUP_TTL_MS = 5_000;
19
+ const lookupCache = new Map();
20
+ function sessionsRoot(root) {
21
+ return join(root, 'sessions');
22
+ }
23
+ /** 收集 rollout 文件(年/月/日 三层目录),按 mtime 从新到旧排序。 */
24
+ function collectRollouts(root) {
25
+ const base = sessionsRoot(root);
26
+ if (!existsSync(base))
27
+ return [];
28
+ const candidates = [];
29
+ let years;
30
+ try {
31
+ years = readdirSync(base);
32
+ }
33
+ catch {
34
+ return [];
35
+ }
36
+ for (const year of years) {
37
+ const yearDir = join(base, year);
38
+ let months;
39
+ try {
40
+ months = readdirSync(yearDir);
41
+ }
42
+ catch {
43
+ continue;
44
+ }
45
+ for (const month of months) {
46
+ const monthDir = join(yearDir, month);
47
+ let days;
48
+ try {
49
+ days = readdirSync(monthDir);
50
+ }
51
+ catch {
52
+ continue;
53
+ }
54
+ for (const day of days) {
55
+ const dayDir = join(monthDir, day);
56
+ let files;
57
+ try {
58
+ files = readdirSync(dayDir);
59
+ }
60
+ catch {
61
+ continue;
62
+ }
63
+ for (const file of files) {
64
+ if (!file.endsWith('.jsonl'))
65
+ continue;
66
+ const path = join(dayDir, file);
67
+ try {
68
+ candidates.push({ path, mtime: statSync(path).mtimeMs });
69
+ }
70
+ catch {
71
+ // 读不到 mtime 的文件排在最后。
72
+ }
73
+ }
74
+ }
75
+ }
76
+ }
77
+ return candidates.sort((a, b) => b.mtime - a.mtime);
78
+ }
79
+ /** 读首行解析 session_meta。 */
80
+ function readSessionMeta(path) {
81
+ let fd;
82
+ try {
83
+ fd = openSync(path, 'r');
84
+ }
85
+ catch {
86
+ return null;
87
+ }
88
+ try {
89
+ const buffer = Buffer.alloc(64 * 1024);
90
+ const read = readSync(fd, buffer, 0, buffer.length, 0);
91
+ const firstLine = buffer.toString('utf8', 0, read).split('\n')[0] ?? '';
92
+ const record = JSON.parse(firstLine);
93
+ if (record?.type !== 'session_meta')
94
+ return null;
95
+ const payload = (record.payload ?? {});
96
+ const sessionId = String(payload.session_id ?? payload.id ?? '');
97
+ const cwd = String(payload.cwd ?? '');
98
+ if (!sessionId || !cwd)
99
+ return null;
100
+ return { sessionId, cwd };
101
+ }
102
+ catch {
103
+ return null;
104
+ }
105
+ finally {
106
+ closeSync(fd);
107
+ }
108
+ }
109
+ /** 找到属于这个 workdir 的最近一次 Codex 会话。 */
110
+ export function findCodexSession(workdir, opts = {}) {
111
+ if (!workdir)
112
+ return null;
113
+ const root = opts.root ?? defaultCodexHome();
114
+ const cacheKey = `${root}\u0000${workdir}\u0000${opts.sessionId ?? ''}`;
115
+ const cached = lookupCache.get(cacheKey);
116
+ const now = Date.now();
117
+ if (cached && now - cached.at < LOOKUP_TTL_MS)
118
+ return cached.found;
119
+ const found = scanCodexSession(workdir, root, opts.sessionId);
120
+ lookupCache.set(cacheKey, { found, at: now });
121
+ return found;
122
+ }
123
+ /** 扫描 rollout 目录,找到 cwd 匹配且最近的一次会话。 */
124
+ function scanCodexSession(workdir, root, sessionId) {
125
+ for (const candidate of collectRollouts(root).slice(0, MAX_CANDIDATES)) {
126
+ const meta = readSessionMeta(candidate.path);
127
+ if (!meta || meta.cwd !== workdir)
128
+ continue;
129
+ if (sessionId && meta.sessionId !== sessionId)
130
+ continue;
131
+ return { path: candidate.path, sessionId: meta.sessionId };
132
+ }
133
+ return null;
134
+ }
135
+ /** 按 workdir 找最近一次 Codex 会话的 id。 */
136
+ export function latestCodexSessionId(workdir, opts = {}) {
137
+ return findCodexSession(workdir, opts)?.sessionId ?? null;
138
+ }
139
+ export function describeCodexToolInfo(name, args) {
140
+ const tool = String(name ?? 'tool');
141
+ const input = parseArgs(args);
142
+ const target = oneLine(input.cmd ?? input.command ?? input.path ?? input.file_path ?? '');
143
+ switch (tool) {
144
+ case 'exec_command':
145
+ case 'shell':
146
+ case 'local_shell':
147
+ return { name: tool, ...(target ? { target } : {}) };
148
+ case 'apply_patch':
149
+ case 'read_file':
150
+ case 'write_file':
151
+ case 'view_file':
152
+ return { name: tool, ...(target ? { target } : {}) };
153
+ default:
154
+ return { name: tool };
155
+ }
156
+ }
157
+ export function describeCodexToolUse(name, args) {
158
+ const info = describeCodexToolInfo(name, args);
159
+ return info.target ? `${info.name}(${info.target})` : info.name;
160
+ }
161
+ /**
162
+ * 读取某个 workdir 对应 Codex 会话的最新活动。
163
+ *
164
+ * 只取 assistant 文本与工具调用;reasoning 摘要与 usage 记录不进话题。
165
+ */
166
+ export function readCodexSession(workdir, opts = {}) {
167
+ if (!workdir)
168
+ return null;
169
+ const root = opts.root ?? defaultCodexHome();
170
+ const found = findCodexSession(workdir, {
171
+ root,
172
+ ...(opts.sessionId ? { sessionId: opts.sessionId } : {}),
173
+ });
174
+ if (!found)
175
+ return null;
176
+ const maxBytes = opts.maxBytes ?? 512 * 1024;
177
+ const maxEvents = opts.maxEvents ?? 40;
178
+ const events = [];
179
+ let at = '';
180
+ for (const line of readTailLines(found.path, maxBytes)) {
181
+ let record;
182
+ try {
183
+ record = JSON.parse(line);
184
+ }
185
+ catch {
186
+ continue;
187
+ }
188
+ if (record?.type !== 'response_item')
189
+ continue;
190
+ const timestamp = typeof record.timestamp === 'string' ? record.timestamp : '';
191
+ const payload = (record.payload ?? {});
192
+ if (!timestamp)
193
+ continue;
194
+ if (payload.type === 'message' && payload.role === 'assistant') {
195
+ const content = Array.isArray(payload.content) ? payload.content : [];
196
+ const before = events.length;
197
+ for (const block of content) {
198
+ if (!block || typeof block !== 'object')
199
+ continue;
200
+ if (block.type !== 'output_text')
201
+ continue;
202
+ const text = String(block.text ?? '').trim();
203
+ if (text)
204
+ events.push({ at: timestamp, kind: 'text', text });
205
+ }
206
+ if (events.length > before)
207
+ at = timestamp;
208
+ continue;
209
+ }
210
+ if (payload.type === 'function_call') {
211
+ events.push({
212
+ at: timestamp,
213
+ kind: 'tool',
214
+ text: describeCodexToolUse(payload.name, payload.arguments),
215
+ tool: describeCodexToolInfo(payload.name, payload.arguments),
216
+ });
217
+ at = timestamp;
218
+ }
219
+ }
220
+ if (events.length === 0)
221
+ return null;
222
+ return {
223
+ at,
224
+ events: events.slice(-maxEvents),
225
+ sessionId: found.sessionId,
226
+ filePath: found.path,
227
+ };
228
+ }
229
+ /** 从文件尾部读有界的一段;首行可能被截断,直接丢弃。 */
230
+ function readTailLines(path, maxBytes) {
231
+ let size = 0;
232
+ try {
233
+ size = statSync(path).size;
234
+ }
235
+ catch {
236
+ return [];
237
+ }
238
+ const start = Math.max(0, size - maxBytes);
239
+ const length = size - start;
240
+ if (length <= 0)
241
+ return [];
242
+ const buffer = Buffer.alloc(length);
243
+ let fd;
244
+ try {
245
+ fd = openSync(path, 'r');
246
+ }
247
+ catch {
248
+ return [];
249
+ }
250
+ try {
251
+ readSync(fd, buffer, 0, length, start);
252
+ }
253
+ finally {
254
+ closeSync(fd);
255
+ }
256
+ const lines = buffer.toString('utf8').split('\n');
257
+ if (start > 0)
258
+ lines.shift();
259
+ return lines.filter(line => line.trim().length > 0);
260
+ }
261
+ function parseArgs(args) {
262
+ if (args && typeof args === 'object')
263
+ return args;
264
+ if (typeof args !== 'string')
265
+ return {};
266
+ try {
267
+ const parsed = JSON.parse(args);
268
+ return parsed && typeof parsed === 'object' ? parsed : {};
269
+ }
270
+ catch {
271
+ return {};
272
+ }
273
+ }
274
+ function oneLine(value) {
275
+ const text = String(value ?? '').replace(/\s+/g, ' ').trim();
276
+ return text.length <= 160 ? text : `${text.slice(0, 160)}…`;
277
+ }
278
+ /** Codex 的会话记录读取器。 */
279
+ export const codexSessionReader = {
280
+ latestSessionId(workdir, opts) {
281
+ return latestCodexSessionId(workdir, opts?.root ? { root: opts.root } : {});
282
+ },
283
+ read(workdir, opts) {
284
+ return readCodexSession(workdir, {
285
+ ...(opts?.root ? { root: opts.root } : {}),
286
+ ...(opts?.maxBytes !== undefined ? { maxBytes: opts.maxBytes } : {}),
287
+ ...(opts?.maxEvents !== undefined ? { maxEvents: opts.maxEvents } : {}),
288
+ ...(opts?.sessionId ? { sessionId: opts.sessionId } : {}),
289
+ });
290
+ },
291
+ };
292
+ //# sourceMappingURL=codex.js.map