@myagentroam/node 0.1.2 → 0.1.4
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/claude-agent-sdk.d.ts +1 -15
- package/dist/claude-agent-sdk.js +9 -77
- package/dist/claude-agent-sdk.js.map +1 -1
- package/dist/codex-app-server.d.ts +4 -0
- package/dist/codex-app-server.js +18 -4
- package/dist/codex-app-server.js.map +1 -1
- package/dist/connector/node-connector-options.d.ts +23 -0
- package/dist/connector/node-connector-options.js +2 -0
- package/dist/connector/node-connector-options.js.map +1 -0
- package/dist/connector/node-operation-router.d.ts +9 -0
- package/dist/connector/node-operation-router.js +23 -0
- package/dist/connector/node-operation-router.js.map +1 -0
- package/dist/connector.d.ts +59 -353
- package/dist/connector.js +614 -5936
- package/dist/connector.js.map +1 -1
- package/dist/control-outbound-scheduler.d.ts +26 -0
- package/dist/control-outbound-scheduler.js +122 -0
- package/dist/control-outbound-scheduler.js.map +1 -0
- package/dist/database.js +1 -1
- package/dist/database.js.map +1 -1
- package/dist/native-jsonl-reader.d.ts +14 -0
- package/dist/native-jsonl-reader.js +98 -0
- package/dist/native-jsonl-reader.js.map +1 -0
- package/dist/native-session-history.d.ts +22 -3
- package/dist/native-session-history.js +281 -157
- package/dist/native-session-history.js.map +1 -1
- package/dist/runner/abstract-runner.d.ts +148 -0
- package/dist/runner/abstract-runner.js +139 -0
- package/dist/runner/abstract-runner.js.map +1 -0
- package/dist/runner/claude/managed-run-controller.d.ts +52 -0
- package/dist/runner/claude/managed-run-controller.js +307 -0
- package/dist/runner/claude/managed-run-controller.js.map +1 -0
- package/dist/runner/claude-code-runner.d.ts +68 -0
- package/dist/runner/claude-code-runner.js +268 -0
- package/dist/runner/claude-code-runner.js.map +1 -0
- package/dist/runner/codex/conversation-parser.d.ts +61 -0
- package/dist/runner/codex/conversation-parser.js +676 -0
- package/dist/runner/codex/conversation-parser.js.map +1 -0
- package/dist/runner/codex/managed-run-controller.d.ts +79 -0
- package/dist/runner/codex/managed-run-controller.js +379 -0
- package/dist/runner/codex/managed-run-controller.js.map +1 -0
- package/dist/runner/codex-runner.d.ts +81 -0
- package/dist/runner/codex-runner.js +342 -0
- package/dist/runner/codex-runner.js.map +1 -0
- package/dist/runner/fake-test-runner.d.ts +26 -0
- package/dist/runner/fake-test-runner.js +48 -0
- package/dist/runner/fake-test-runner.js.map +1 -0
- package/dist/runner/runner-registry.d.ts +17 -0
- package/dist/runner/runner-registry.js +66 -0
- package/dist/runner/runner-registry.js.map +1 -0
- package/dist/runner-command-engine.d.ts +9 -0
- package/dist/runner-command-engine.js +24 -0
- package/dist/runner-command-engine.js.map +1 -1
- package/dist/runner-profiles.js +12 -0
- package/dist/runner-profiles.js.map +1 -1
- package/dist/runner-usage.d.ts +2 -2
- package/dist/runner-usage.js +3 -5
- package/dist/runner-usage.js.map +1 -1
- package/dist/runtime-state.js +6 -5
- package/dist/runtime-state.js.map +1 -1
- package/dist/service/attachment-domain-state.d.ts +5 -0
- package/dist/service/attachment-domain-state.js +6 -0
- package/dist/service/attachment-domain-state.js.map +1 -0
- package/dist/service/conversation-history-service.d.ts +34 -0
- package/dist/service/conversation-history-service.js +159 -0
- package/dist/service/conversation-history-service.js.map +1 -0
- package/dist/service/external-session-resume-service.d.ts +32 -0
- package/dist/service/external-session-resume-service.js +99 -0
- package/dist/service/external-session-resume-service.js.map +1 -0
- package/dist/service/fake-managed-run-service.d.ts +29 -0
- package/dist/service/fake-managed-run-service.js +63 -0
- package/dist/service/fake-managed-run-service.js.map +1 -0
- package/dist/service/managed-runner-session-service.d.ts +10 -0
- package/dist/service/managed-runner-session-service.js +31 -0
- package/dist/service/managed-runner-session-service.js.map +1 -0
- package/dist/service/native-session-projection-service.d.ts +50 -0
- package/dist/service/native-session-projection-service.js +268 -0
- package/dist/service/native-session-projection-service.js.map +1 -0
- package/dist/service/native-session-watch-service.d.ts +37 -0
- package/dist/service/native-session-watch-service.js +151 -0
- package/dist/service/native-session-watch-service.js.map +1 -0
- package/dist/service/node-connection-lifecycle-service.d.ts +37 -0
- package/dist/service/node-connection-lifecycle-service.js +84 -0
- package/dist/service/node-connection-lifecycle-service.js.map +1 -0
- package/dist/service/node-control-channel.d.ts +32 -0
- package/dist/service/node-control-channel.js +84 -0
- package/dist/service/node-control-channel.js.map +1 -0
- package/dist/service/node-control-message-service.d.ts +28 -0
- package/dist/service/node-control-message-service.js +105 -0
- package/dist/service/node-control-message-service.js.map +1 -0
- package/dist/service/node-request-service.d.ts +14 -0
- package/dist/service/node-request-service.js +51 -0
- package/dist/service/node-request-service.js.map +1 -0
- package/dist/service/node-run-event-bridge.d.ts +38 -0
- package/dist/service/node-run-event-bridge.js +80 -0
- package/dist/service/node-run-event-bridge.js.map +1 -0
- package/dist/service/node-terminal-channel.d.ts +29 -0
- package/dist/service/node-terminal-channel.js +176 -0
- package/dist/service/node-terminal-channel.js.map +1 -0
- package/dist/service/node-workspace-coordinator.d.ts +32 -0
- package/dist/service/node-workspace-coordinator.js +81 -0
- package/dist/service/node-workspace-coordinator.js.map +1 -0
- package/dist/service/run-attachment-service.d.ts +37 -0
- package/dist/service/run-attachment-service.js +161 -0
- package/dist/service/run-attachment-service.js.map +1 -0
- package/dist/service/run-domain-state.d.ts +4 -0
- package/dist/service/run-domain-state.js +5 -0
- package/dist/service/run-domain-state.js.map +1 -0
- package/dist/service/run-event-service.d.ts +31 -0
- package/dist/service/run-event-service.js +103 -0
- package/dist/service/run-event-service.js.map +1 -0
- package/dist/service/run-workbench-service.d.ts +27 -0
- package/dist/service/run-workbench-service.js +114 -0
- package/dist/service/run-workbench-service.js.map +1 -0
- package/dist/service/runner-channel-message-service.d.ts +13 -0
- package/dist/service/runner-channel-message-service.js +21 -0
- package/dist/service/runner-channel-message-service.js.map +1 -0
- package/dist/service/runner-interaction-service.d.ts +40 -0
- package/dist/service/runner-interaction-service.js +245 -0
- package/dist/service/runner-interaction-service.js.map +1 -0
- package/dist/service/runner-service.d.ts +43 -0
- package/dist/service/runner-service.js +55 -0
- package/dist/service/runner-service.js.map +1 -0
- package/dist/service/session-catalog-service.d.ts +30 -0
- package/dist/service/session-catalog-service.js +145 -0
- package/dist/service/session-catalog-service.js.map +1 -0
- package/dist/service/session-command-service.d.ts +23 -0
- package/dist/service/session-command-service.js +70 -0
- package/dist/service/session-command-service.js.map +1 -0
- package/dist/service/session-context-service.d.ts +34 -0
- package/dist/service/session-context-service.js +88 -0
- package/dist/service/session-context-service.js.map +1 -0
- package/dist/service/session-domain-state.d.ts +8 -0
- package/dist/service/session-domain-state.js +9 -0
- package/dist/service/session-domain-state.js.map +1 -0
- package/dist/service/session-identity-service.d.ts +24 -0
- package/dist/service/session-identity-service.js +87 -0
- package/dist/service/session-identity-service.js.map +1 -0
- package/dist/service/session-lifecycle-service.d.ts +31 -0
- package/dist/service/session-lifecycle-service.js +236 -0
- package/dist/service/session-lifecycle-service.js.map +1 -0
- package/dist/service/session-message-service.d.ts +33 -0
- package/dist/service/session-message-service.js +158 -0
- package/dist/service/session-message-service.js.map +1 -0
- package/dist/service/session-presentation-service.d.ts +33 -0
- package/dist/service/session-presentation-service.js +37 -0
- package/dist/service/session-presentation-service.js.map +1 -0
- package/dist/service/session-query-service.d.ts +31 -0
- package/dist/service/session-query-service.js +78 -0
- package/dist/service/session-query-service.js.map +1 -0
- package/dist/service/terminal-relay-state.d.ts +6 -0
- package/dist/service/terminal-relay-state.js +4 -0
- package/dist/service/terminal-relay-state.js.map +1 -0
- package/dist/service/terminal-service.d.ts +12 -0
- package/dist/service/terminal-service.js +53 -0
- package/dist/service/terminal-service.js.map +1 -0
- package/dist/service/workbench-event-service.d.ts +21 -0
- package/dist/service/workbench-event-service.js +54 -0
- package/dist/service/workbench-event-service.js.map +1 -0
- package/dist/service/workbench-manifest-service.d.ts +88 -0
- package/dist/service/workbench-manifest-service.js +69 -0
- package/dist/service/workbench-manifest-service.js.map +1 -0
- package/dist/service/workspace-change-service.d.ts +9 -0
- package/dist/service/workspace-change-service.js +86 -0
- package/dist/service/workspace-change-service.js.map +1 -0
- package/dist/service/workspace-domain-state.d.ts +75 -0
- package/dist/service/workspace-domain-state.js +41 -0
- package/dist/service/workspace-domain-state.js.map +1 -0
- package/dist/service/workspace-file-service.d.ts +14 -0
- package/dist/service/workspace-file-service.js +77 -0
- package/dist/service/workspace-file-service.js.map +1 -0
- package/dist/service/workspace-queue-workbench-service.d.ts +55 -0
- package/dist/service/workspace-queue-workbench-service.js +227 -0
- package/dist/service/workspace-queue-workbench-service.js.map +1 -0
- package/dist/service/workspace-service.d.ts +15 -0
- package/dist/service/workspace-service.js +54 -0
- package/dist/service/workspace-service.js.map +1 -0
- package/dist/service/workspace-session-service.d.ts +23 -0
- package/dist/service/workspace-session-service.js +140 -0
- package/dist/service/workspace-session-service.js.map +1 -0
- package/dist/service/workspace-upload-service.d.ts +37 -0
- package/dist/service/workspace-upload-service.js +241 -0
- package/dist/service/workspace-upload-service.js.map +1 -0
- package/dist/service/workspace-watch-service.d.ts +18 -0
- package/dist/service/workspace-watch-service.js +121 -0
- package/dist/service/workspace-watch-service.js.map +1 -0
- package/dist/service/workspace-workbench-service.d.ts +38 -0
- package/dist/service/workspace-workbench-service.js +156 -0
- package/dist/service/workspace-workbench-service.js.map +1 -0
- package/dist/terminal.d.ts +1 -0
- package/dist/terminal.js +1 -1
- package/dist/terminal.js.map +1 -1
- package/dist/util/node-operation-parsers.d.ts +91 -0
- package/dist/util/node-operation-parsers.js +380 -0
- package/dist/util/node-operation-parsers.js.map +1 -0
- package/dist/util/runner-native-session-parsers.d.ts +72 -0
- package/dist/util/runner-native-session-parsers.js +337 -0
- package/dist/util/runner-native-session-parsers.js.map +1 -0
- package/dist/util/secret-environment.d.ts +1 -0
- package/dist/util/secret-environment.js +26 -0
- package/dist/util/secret-environment.js.map +1 -0
- package/dist/workspace.d.ts +9 -2
- package/dist/workspace.js +41 -0
- package/dist/workspace.js.map +1 -1
- package/package.json +2 -2
|
@@ -1,58 +1,18 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
|
-
import {
|
|
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
|
-
|
|
5
|
+
import { forEachJsonlRecord, listJsonlFiles, mapConcurrent, readJsonlWindow } from './native-jsonl-reader.js';
|
|
6
|
+
import { nodeLog } from './operational.js';
|
|
7
|
+
import { stripClaudePlanTag } from './runner-command-engine.js';
|
|
8
|
+
const DISCOVERY_READ_BYTES = 32 * 1024;
|
|
6
9
|
const TRANSCRIPT_READ_BYTES = 4 * 1024 * 1024;
|
|
7
10
|
const CONTEXT_USAGE_READ_BYTES = 256 * 1024;
|
|
8
|
-
const
|
|
11
|
+
const CLAUDE_CONTEXT_USAGE_READ_BYTES = [CONTEXT_USAGE_READ_BYTES, 1024 * 1024, 4 * 1024 * 1024];
|
|
9
12
|
const transcriptIndex = new Map();
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
}
|
|
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';
|
|
56
16
|
/**
|
|
57
17
|
* Read-only adapter for the supported vendor JSONL stores. It requires an
|
|
58
18
|
* explicit cwd on the record and returns normalized visible messages and tool
|
|
@@ -68,8 +28,23 @@ export async function discoverNativeSessions(runner, workspacePath) {
|
|
|
68
28
|
* hide older sessions from a project with a large global history.
|
|
69
29
|
*/
|
|
70
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();
|
|
71
40
|
const found = new Map();
|
|
72
|
-
|
|
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
|
+
});
|
|
73
48
|
const parsedFiles = await readDiscoveryHeaders(files, runner);
|
|
74
49
|
for (const [index, parsed] of parsedFiles.entries()) {
|
|
75
50
|
// Discovery only needs a session identity, cwd and an optional early
|
|
@@ -81,24 +56,29 @@ export async function discoverAllNativeSessions(runner) {
|
|
|
81
56
|
transcriptIndex.set(indexKey(runner, cwd, parsed.externalSessionId), files[index]);
|
|
82
57
|
found.set(`${cwd}\u0000${parsed.externalSessionId}`, parsed);
|
|
83
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
|
+
});
|
|
84
65
|
return [...found.values()];
|
|
85
66
|
}
|
|
86
67
|
async function readDiscoveryHeaders(files, runner) {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
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
|
+
});
|
|
102
82
|
}
|
|
103
83
|
/** Reads one already-discovered external session again to follow appended JSONL records. */
|
|
104
84
|
export async function readNativeSession(runner, workspacePath, externalSessionId) {
|
|
@@ -130,37 +110,94 @@ export async function readCodexNativeContextUsage(workspacePath, externalSession
|
|
|
130
110
|
}
|
|
131
111
|
if (path === undefined)
|
|
132
112
|
return undefined;
|
|
133
|
-
|
|
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;
|
|
134
165
|
try {
|
|
135
|
-
|
|
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
|
-
}
|
|
166
|
+
entries = await readdir(root, { withFileTypes: true });
|
|
146
167
|
}
|
|
147
168
|
catch {
|
|
148
169
|
return undefined;
|
|
149
170
|
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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;
|
|
158
195
|
}
|
|
159
|
-
const next = codexContextUsage(record);
|
|
160
|
-
if (next !== undefined)
|
|
161
|
-
usage = next;
|
|
162
196
|
}
|
|
163
|
-
|
|
197
|
+
catch {
|
|
198
|
+
// Missing or malformed optional Claude settings use the stable default.
|
|
199
|
+
}
|
|
200
|
+
return DEFAULT_CLAUDE_CONTEXT_WINDOW_TOKENS;
|
|
164
201
|
}
|
|
165
202
|
/**
|
|
166
203
|
* Permanently removes exactly one previously discovered transcript. The
|
|
@@ -199,78 +236,18 @@ function transcriptRoot(runner) {
|
|
|
199
236
|
? join(homedir(), '.codex', 'sessions')
|
|
200
237
|
: join(homedir(), '.claude', 'projects');
|
|
201
238
|
}
|
|
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
239
|
async function readTranscript(path, runner, byteLimit) {
|
|
224
|
-
|
|
225
|
-
|
|
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 {
|
|
240
|
+
const window = await readJsonlWindow(path, { headBytes: byteLimit, tailBytes: byteLimit });
|
|
241
|
+
if (window === undefined)
|
|
254
242
|
return undefined;
|
|
255
|
-
}
|
|
243
|
+
const { content, truncated } = window;
|
|
256
244
|
let cwd;
|
|
257
245
|
let id;
|
|
258
246
|
const items = [];
|
|
259
247
|
const toolIndexes = new Map();
|
|
260
248
|
const pendingToolResults = new Map();
|
|
261
249
|
const metaRecordIds = new Set();
|
|
262
|
-
|
|
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;
|
|
250
|
+
forEachJsonlRecord(content, (value) => {
|
|
274
251
|
cwd ??= workspacePath(value);
|
|
275
252
|
id ??= sessionId(value);
|
|
276
253
|
const recordId = typeof value['uuid'] === 'string' ? value['uuid'] : undefined;
|
|
@@ -279,8 +256,11 @@ async function readTranscript(path, runner, byteLimit) {
|
|
|
279
256
|
metaRecordIds.add(recordId);
|
|
280
257
|
if (isHiddenTranscriptRecord(value) ||
|
|
281
258
|
(parentId !== undefined && metaRecordIds.has(parentId))) {
|
|
282
|
-
|
|
259
|
+
return;
|
|
283
260
|
}
|
|
261
|
+
const generatedImage = codexGeneratedImageEvent(value);
|
|
262
|
+
if (generatedImage !== undefined)
|
|
263
|
+
items.push(generatedImage);
|
|
284
264
|
for (const item of transcriptItems(value)) {
|
|
285
265
|
if (item.kind === 'tool_result') {
|
|
286
266
|
const toolIndex = toolIndexes.get(item.toolUseId);
|
|
@@ -300,7 +280,7 @@ async function readTranscript(path, runner, byteLimit) {
|
|
|
300
280
|
}
|
|
301
281
|
items.push(item);
|
|
302
282
|
}
|
|
303
|
-
}
|
|
283
|
+
});
|
|
304
284
|
for (const result of pendingToolResults.values())
|
|
305
285
|
items.push({
|
|
306
286
|
kind: 'tool_call',
|
|
@@ -311,17 +291,24 @@ async function readTranscript(path, runner, byteLimit) {
|
|
|
311
291
|
inputTruncated: false,
|
|
312
292
|
outputSummary: result.output,
|
|
313
293
|
outputTruncated: result.truncated,
|
|
294
|
+
...(result.imageAttachments === undefined
|
|
295
|
+
? {}
|
|
296
|
+
: { imageAttachments: result.imageAttachments }),
|
|
314
297
|
failed: result.failed,
|
|
315
298
|
createdAt: result.createdAt
|
|
316
299
|
});
|
|
317
300
|
if (cwd === undefined)
|
|
318
301
|
return undefined;
|
|
319
302
|
const title = items.find((item) => item.kind === 'message' && item.role === 'USER')?.text;
|
|
303
|
+
// 标签式 Plan Mode 的指令标签不构成会话标题。
|
|
304
|
+
const strippedTitle = title === undefined ? undefined : stripClaudePlanTag(title);
|
|
320
305
|
return {
|
|
321
306
|
runner,
|
|
322
307
|
externalSessionId: id ?? basename(path, '.jsonl'),
|
|
323
308
|
cwd,
|
|
324
|
-
...(
|
|
309
|
+
...(strippedTitle === undefined || strippedTitle.length === 0
|
|
310
|
+
? {}
|
|
311
|
+
: { title: compact(strippedTitle) }),
|
|
325
312
|
digest: createHash('sha256').update(content).digest('hex'),
|
|
326
313
|
items,
|
|
327
314
|
lastActivityAt: Math.max(0, ...items.map((item) => item.createdAt ?? 0)),
|
|
@@ -370,6 +357,37 @@ function codexContextUsage(value) {
|
|
|
370
357
|
return undefined;
|
|
371
358
|
return { usedTokens: promptUsage.input_tokens, maxTokens };
|
|
372
359
|
}
|
|
360
|
+
function claudeContextUsage(value) {
|
|
361
|
+
if (value.type !== 'assistant' || !isRecord(value.message))
|
|
362
|
+
return undefined;
|
|
363
|
+
const model = value.message.model;
|
|
364
|
+
const usage = value.message.usage;
|
|
365
|
+
if (typeof model !== 'string' ||
|
|
366
|
+
model.length === 0 ||
|
|
367
|
+
model === '<synthetic>' ||
|
|
368
|
+
!isRecord(usage))
|
|
369
|
+
return undefined;
|
|
370
|
+
const inputTokens = nonNegativeInteger(usage.input_tokens);
|
|
371
|
+
const cacheCreationTokens = optionalNonNegativeInteger(usage.cache_creation_input_tokens);
|
|
372
|
+
const cacheReadTokens = optionalNonNegativeInteger(usage.cache_read_input_tokens);
|
|
373
|
+
if (inputTokens === undefined ||
|
|
374
|
+
cacheCreationTokens === undefined ||
|
|
375
|
+
cacheReadTokens === undefined)
|
|
376
|
+
return undefined;
|
|
377
|
+
return { model, usedTokens: inputTokens + cacheCreationTokens + cacheReadTokens };
|
|
378
|
+
}
|
|
379
|
+
function optionalNonNegativeInteger(value) {
|
|
380
|
+
return value === undefined ? 0 : nonNegativeInteger(value);
|
|
381
|
+
}
|
|
382
|
+
function nonNegativeInteger(value) {
|
|
383
|
+
return typeof value === 'number' && Number.isSafeInteger(value) && value >= 0 ? value : undefined;
|
|
384
|
+
}
|
|
385
|
+
function positiveInteger(value) {
|
|
386
|
+
if (typeof value !== 'string' || !/^\d+$/.test(value.trim()))
|
|
387
|
+
return undefined;
|
|
388
|
+
const parsed = Number(value);
|
|
389
|
+
return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : undefined;
|
|
390
|
+
}
|
|
373
391
|
function transcriptItems(record) {
|
|
374
392
|
const createdAt = timestampOf(record);
|
|
375
393
|
const results = [...toolResultsOf(record, createdAt), ...codexToolResultsOf(record, createdAt)];
|
|
@@ -474,6 +492,7 @@ function codexToolResultsOf(record, createdAt) {
|
|
|
474
492
|
if (toolUseId === undefined)
|
|
475
493
|
return [];
|
|
476
494
|
const output = compactValue(codexToolOutput(payload['output'] ?? payload['contentItems']), 10_000);
|
|
495
|
+
const imageAttachments = codexImagePlaceholders(payload['output'] ?? payload['contentItems']);
|
|
477
496
|
return [
|
|
478
497
|
{
|
|
479
498
|
kind: 'tool_result',
|
|
@@ -481,6 +500,7 @@ function codexToolResultsOf(record, createdAt) {
|
|
|
481
500
|
output: output.text,
|
|
482
501
|
failed: payload['status'] === 'failed' || payload['error'] !== undefined,
|
|
483
502
|
truncated: output.truncated,
|
|
503
|
+
...(imageAttachments.length === 0 ? {} : { imageAttachments }),
|
|
484
504
|
createdAt
|
|
485
505
|
}
|
|
486
506
|
];
|
|
@@ -490,6 +510,31 @@ function codexToolPayload(record) {
|
|
|
490
510
|
? record['payload']
|
|
491
511
|
: undefined;
|
|
492
512
|
}
|
|
513
|
+
function codexGeneratedImageEvent(record) {
|
|
514
|
+
if (record['type'] !== 'event_msg' || !isRecord(record['payload']))
|
|
515
|
+
return undefined;
|
|
516
|
+
const payload = record['payload'];
|
|
517
|
+
if (payload['type'] !== 'image_generation_end' ||
|
|
518
|
+
typeof payload['call_id'] !== 'string' ||
|
|
519
|
+
typeof payload['saved_path'] !== 'string')
|
|
520
|
+
return undefined;
|
|
521
|
+
const localPath = payload['saved_path'];
|
|
522
|
+
if (!localPath.includes('/.codex/generated_images/') || !/\.(?:png|jpe?g|webp)$/i.test(localPath))
|
|
523
|
+
return undefined;
|
|
524
|
+
return {
|
|
525
|
+
kind: 'tool_call',
|
|
526
|
+
toolUseId: payload['call_id'],
|
|
527
|
+
toolName: 'imagegen',
|
|
528
|
+
input: null,
|
|
529
|
+
inputSummary: null,
|
|
530
|
+
inputTruncated: false,
|
|
531
|
+
outputSummary: payload['status'] === 'completed' ? '图片生成完成' : '图片生成失败',
|
|
532
|
+
outputTruncated: false,
|
|
533
|
+
imageAttachments: [{ name: basename(localPath), mime: imageMime(localPath), localPath }],
|
|
534
|
+
failed: payload['status'] !== 'completed',
|
|
535
|
+
createdAt: timestampOf(record)
|
|
536
|
+
};
|
|
537
|
+
}
|
|
493
538
|
/** Extract Codex output text blocks instead of rendering their JSON envelopes. */
|
|
494
539
|
function codexToolOutput(value) {
|
|
495
540
|
if (!Array.isArray(value))
|
|
@@ -500,15 +545,94 @@ function codexToolOutput(value) {
|
|
|
500
545
|
if (!isRecord(block))
|
|
501
546
|
return [];
|
|
502
547
|
const direct = firstString(block, ['text', 'content']);
|
|
503
|
-
return direct === undefined ? [] : [direct];
|
|
548
|
+
return direct === undefined ? [] : [codexVisibleToolText(direct)];
|
|
504
549
|
});
|
|
505
550
|
return text.length > 0 ? text.join('') : value;
|
|
506
551
|
}
|
|
552
|
+
function codexVisibleToolText(value) {
|
|
553
|
+
const marker = 'data:image/';
|
|
554
|
+
let cursor = 0;
|
|
555
|
+
let visible = '';
|
|
556
|
+
while (cursor < value.length) {
|
|
557
|
+
const start = value.indexOf(marker, cursor);
|
|
558
|
+
if (start < 0)
|
|
559
|
+
return visible + value.slice(cursor);
|
|
560
|
+
visible += `${value.slice(cursor, start)}[图片内容已过滤]`;
|
|
561
|
+
const doubleQuote = value.indexOf('"', start);
|
|
562
|
+
const singleQuote = value.indexOf("'", start);
|
|
563
|
+
const end = doubleQuote < 0
|
|
564
|
+
? singleQuote
|
|
565
|
+
: singleQuote < 0
|
|
566
|
+
? doubleQuote
|
|
567
|
+
: Math.min(doubleQuote, singleQuote);
|
|
568
|
+
if (end < 0)
|
|
569
|
+
return visible;
|
|
570
|
+
cursor = end;
|
|
571
|
+
}
|
|
572
|
+
return visible;
|
|
573
|
+
}
|
|
574
|
+
/** Extract only image placeholder metadata from native Codex output. */
|
|
575
|
+
function codexImagePlaceholders(value) {
|
|
576
|
+
const paths = [];
|
|
577
|
+
let visited = 0;
|
|
578
|
+
const visit = (current, depth) => {
|
|
579
|
+
if (paths.length >= 5 || visited >= 128 || depth > 5 || current == null)
|
|
580
|
+
return;
|
|
581
|
+
visited += 1;
|
|
582
|
+
if (typeof current === 'string') {
|
|
583
|
+
collectGeneratedImagePaths(current, paths);
|
|
584
|
+
return;
|
|
585
|
+
}
|
|
586
|
+
if (Array.isArray(current)) {
|
|
587
|
+
for (const entry of current)
|
|
588
|
+
visit(entry, depth + 1);
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
if (!isRecord(current))
|
|
592
|
+
return;
|
|
593
|
+
for (const child of Object.values(current))
|
|
594
|
+
visit(child, depth + 1);
|
|
595
|
+
};
|
|
596
|
+
visit(value, 0);
|
|
597
|
+
return paths.map((localPath) => ({
|
|
598
|
+
name: basename(localPath),
|
|
599
|
+
mime: imageMime(localPath),
|
|
600
|
+
localPath
|
|
601
|
+
}));
|
|
602
|
+
}
|
|
603
|
+
function collectGeneratedImagePaths(value, paths) {
|
|
604
|
+
const marker = '/.codex/generated_images/';
|
|
605
|
+
let cursor = 0;
|
|
606
|
+
while (paths.length < 5) {
|
|
607
|
+
const markerIndex = value.indexOf(marker, cursor);
|
|
608
|
+
if (markerIndex < 0)
|
|
609
|
+
return;
|
|
610
|
+
let start = markerIndex;
|
|
611
|
+
while (start > 0 && !/[\s"']/.test(value[start - 1]))
|
|
612
|
+
start -= 1;
|
|
613
|
+
let end = markerIndex + marker.length;
|
|
614
|
+
while (end < value.length && !/[\s"']/.test(value[end]))
|
|
615
|
+
end += 1;
|
|
616
|
+
const path = value.slice(start, end).replace(/[),.;:]+$/, '');
|
|
617
|
+
if (/\.(?:png|jpe?g|webp)$/i.test(path) && !paths.includes(path))
|
|
618
|
+
paths.push(path);
|
|
619
|
+
cursor = end;
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
function imageMime(path) {
|
|
623
|
+
const extension = path.slice(path.lastIndexOf('.') + 1).toLowerCase();
|
|
624
|
+
if (extension === 'jpg' || extension === 'jpeg')
|
|
625
|
+
return 'image/jpeg';
|
|
626
|
+
if (extension === 'webp')
|
|
627
|
+
return 'image/webp';
|
|
628
|
+
return 'image/png';
|
|
629
|
+
}
|
|
507
630
|
function withToolResult(call, result) {
|
|
508
631
|
return {
|
|
509
632
|
...call,
|
|
510
633
|
outputSummary: result.output,
|
|
511
634
|
outputTruncated: result.truncated,
|
|
635
|
+
...(result.imageAttachments === undefined ? {} : { imageAttachments: result.imageAttachments }),
|
|
512
636
|
failed: result.failed
|
|
513
637
|
};
|
|
514
638
|
}
|