@myagentroam/node 0.1.3 → 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 -356
- package/dist/connector.js +611 -6175
- 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 +275 -156
- 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-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/package.json +2 -2
|
@@ -1,59 +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
|
+
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 =
|
|
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
|
|
11
|
+
const CLAUDE_CONTEXT_USAGE_READ_BYTES = [CONTEXT_USAGE_READ_BYTES, 1024 * 1024, 4 * 1024 * 1024];
|
|
10
12
|
const transcriptIndex = new Map();
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
|
|
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
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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
|
-
|
|
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
|
-
|
|
226
|
-
|
|
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
|
-
|
|
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,8 +256,11 @@ async function readTranscript(path, runner, byteLimit) {
|
|
|
280
256
|
metaRecordIds.add(recordId);
|
|
281
257
|
if (isHiddenTranscriptRecord(value) ||
|
|
282
258
|
(parentId !== undefined && metaRecordIds.has(parentId))) {
|
|
283
|
-
|
|
259
|
+
return;
|
|
284
260
|
}
|
|
261
|
+
const generatedImage = codexGeneratedImageEvent(value);
|
|
262
|
+
if (generatedImage !== undefined)
|
|
263
|
+
items.push(generatedImage);
|
|
285
264
|
for (const item of transcriptItems(value)) {
|
|
286
265
|
if (item.kind === 'tool_result') {
|
|
287
266
|
const toolIndex = toolIndexes.get(item.toolUseId);
|
|
@@ -301,7 +280,7 @@ async function readTranscript(path, runner, byteLimit) {
|
|
|
301
280
|
}
|
|
302
281
|
items.push(item);
|
|
303
282
|
}
|
|
304
|
-
}
|
|
283
|
+
});
|
|
305
284
|
for (const result of pendingToolResults.values())
|
|
306
285
|
items.push({
|
|
307
286
|
kind: 'tool_call',
|
|
@@ -312,6 +291,9 @@ async function readTranscript(path, runner, byteLimit) {
|
|
|
312
291
|
inputTruncated: false,
|
|
313
292
|
outputSummary: result.output,
|
|
314
293
|
outputTruncated: result.truncated,
|
|
294
|
+
...(result.imageAttachments === undefined
|
|
295
|
+
? {}
|
|
296
|
+
: { imageAttachments: result.imageAttachments }),
|
|
315
297
|
failed: result.failed,
|
|
316
298
|
createdAt: result.createdAt
|
|
317
299
|
});
|
|
@@ -375,6 +357,37 @@ function codexContextUsage(value) {
|
|
|
375
357
|
return undefined;
|
|
376
358
|
return { usedTokens: promptUsage.input_tokens, maxTokens };
|
|
377
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
|
+
}
|
|
378
391
|
function transcriptItems(record) {
|
|
379
392
|
const createdAt = timestampOf(record);
|
|
380
393
|
const results = [...toolResultsOf(record, createdAt), ...codexToolResultsOf(record, createdAt)];
|
|
@@ -479,6 +492,7 @@ function codexToolResultsOf(record, createdAt) {
|
|
|
479
492
|
if (toolUseId === undefined)
|
|
480
493
|
return [];
|
|
481
494
|
const output = compactValue(codexToolOutput(payload['output'] ?? payload['contentItems']), 10_000);
|
|
495
|
+
const imageAttachments = codexImagePlaceholders(payload['output'] ?? payload['contentItems']);
|
|
482
496
|
return [
|
|
483
497
|
{
|
|
484
498
|
kind: 'tool_result',
|
|
@@ -486,6 +500,7 @@ function codexToolResultsOf(record, createdAt) {
|
|
|
486
500
|
output: output.text,
|
|
487
501
|
failed: payload['status'] === 'failed' || payload['error'] !== undefined,
|
|
488
502
|
truncated: output.truncated,
|
|
503
|
+
...(imageAttachments.length === 0 ? {} : { imageAttachments }),
|
|
489
504
|
createdAt
|
|
490
505
|
}
|
|
491
506
|
];
|
|
@@ -495,6 +510,31 @@ function codexToolPayload(record) {
|
|
|
495
510
|
? record['payload']
|
|
496
511
|
: undefined;
|
|
497
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
|
+
}
|
|
498
538
|
/** Extract Codex output text blocks instead of rendering their JSON envelopes. */
|
|
499
539
|
function codexToolOutput(value) {
|
|
500
540
|
if (!Array.isArray(value))
|
|
@@ -505,15 +545,94 @@ function codexToolOutput(value) {
|
|
|
505
545
|
if (!isRecord(block))
|
|
506
546
|
return [];
|
|
507
547
|
const direct = firstString(block, ['text', 'content']);
|
|
508
|
-
return direct === undefined ? [] : [direct];
|
|
548
|
+
return direct === undefined ? [] : [codexVisibleToolText(direct)];
|
|
509
549
|
});
|
|
510
550
|
return text.length > 0 ? text.join('') : value;
|
|
511
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
|
+
}
|
|
512
630
|
function withToolResult(call, result) {
|
|
513
631
|
return {
|
|
514
632
|
...call,
|
|
515
633
|
outputSummary: result.output,
|
|
516
634
|
outputTruncated: result.truncated,
|
|
635
|
+
...(result.imageAttachments === undefined ? {} : { imageAttachments: result.imageAttachments }),
|
|
517
636
|
failed: result.failed
|
|
518
637
|
};
|
|
519
638
|
}
|