@myagentroam/node 0.1.3 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -1
- package/dist/capabilities.d.ts +2 -0
- package/dist/capabilities.js +15 -4
- package/dist/claude-agent-sdk.d.ts +4 -16
- package/dist/claude-agent-sdk.js +18 -79
- package/dist/claude-channel.js +0 -1
- package/dist/codex-app-server.d.ts +5 -0
- package/dist/codex-app-server.js +26 -5
- package/dist/config.d.ts +3 -0
- package/dist/config.js +18 -2
- package/dist/connector/node-connector-options.d.ts +26 -0
- package/dist/connector/node-connector-options.js +1 -0
- package/dist/connector/node-operation-router.d.ts +9 -0
- package/dist/connector/node-operation-router.js +22 -0
- package/dist/connector.d.ts +62 -356
- package/dist/connector.js +714 -6176
- package/dist/control-outbound-scheduler.d.ts +26 -0
- package/dist/control-outbound-scheduler.js +121 -0
- package/dist/database.d.ts +4 -0
- package/dist/database.js +15 -10
- package/dist/event-buffer.js +0 -1
- package/dist/fake-runner.js +0 -1
- package/dist/health.js +0 -1
- package/dist/main.js +6 -1
- package/dist/migrations/v001.js +0 -1
- package/dist/migrations/v002.js +0 -1
- package/dist/migrations/v003.d.ts +4 -0
- package/dist/migrations/v003.js +44 -0
- package/dist/native-jsonl-reader.d.ts +14 -0
- package/dist/native-jsonl-reader.js +97 -0
- package/dist/native-session-history.d.ts +41 -4
- package/dist/native-session-history.js +340 -159
- package/dist/opencode-runtime.d.ts +33 -0
- package/dist/opencode-runtime.js +80 -0
- package/dist/opencode-server.d.ts +47 -0
- package/dist/opencode-server.js +250 -0
- package/dist/operational.js +10 -3
- package/dist/process-tree.js +0 -1
- package/dist/runner/abstract-runner.d.ts +153 -0
- package/dist/runner/abstract-runner.js +154 -0
- package/dist/runner/claude/managed-run-controller.d.ts +52 -0
- package/dist/runner/claude/managed-run-controller.js +326 -0
- package/dist/runner/claude-code-runner.d.ts +70 -0
- package/dist/runner/claude-code-runner.js +286 -0
- package/dist/runner/codex/conversation-parser.d.ts +59 -0
- package/dist/runner/codex/conversation-parser.js +816 -0
- package/dist/runner/codex/managed-run-controller.d.ts +80 -0
- package/dist/runner/codex/managed-run-controller.js +388 -0
- package/dist/runner/codex-runner.d.ts +85 -0
- package/dist/runner/codex-runner.js +365 -0
- package/dist/runner/fake-test-runner.d.ts +26 -0
- package/dist/runner/fake-test-runner.js +47 -0
- package/dist/runner/opencode/conversation-parser.d.ts +27 -0
- package/dist/runner/opencode/conversation-parser.js +217 -0
- package/dist/runner/opencode/managed-run-controller.d.ts +76 -0
- package/dist/runner/opencode/managed-run-controller.js +451 -0
- package/dist/runner/opencode-runner.d.ts +60 -0
- package/dist/runner/opencode-runner.js +330 -0
- package/dist/runner/runner-registry.d.ts +19 -0
- package/dist/runner/runner-registry.js +75 -0
- package/dist/runner-command-engine.js +0 -1
- package/dist/runner-profiles.d.ts +4 -0
- package/dist/runner-profiles.js +76 -3
- package/dist/runner-usage.d.ts +2 -2
- package/dist/runner-usage.js +3 -6
- package/dist/runtime-state.js +26 -14
- package/dist/service/attachment-domain-state.d.ts +5 -0
- package/dist/service/attachment-domain-state.js +5 -0
- package/dist/service/conversation-history-service.d.ts +34 -0
- package/dist/service/conversation-history-service.js +158 -0
- package/dist/service/external-session-resume-service.d.ts +32 -0
- package/dist/service/external-session-resume-service.js +98 -0
- package/dist/service/fake-managed-run-service.d.ts +29 -0
- package/dist/service/fake-managed-run-service.js +62 -0
- package/dist/service/managed-runner-session-service.d.ts +12 -0
- package/dist/service/managed-runner-session-service.js +35 -0
- package/dist/service/native-session-projection-service.d.ts +51 -0
- package/dist/service/native-session-projection-service.js +271 -0
- package/dist/service/native-session-watch-service.d.ts +37 -0
- package/dist/service/native-session-watch-service.js +150 -0
- package/dist/service/node-connection-lifecycle-service.d.ts +37 -0
- package/dist/service/node-connection-lifecycle-service.js +85 -0
- package/dist/service/node-control-channel.d.ts +32 -0
- package/dist/service/node-control-channel.js +85 -0
- package/dist/service/node-control-message-service.d.ts +28 -0
- package/dist/service/node-control-message-service.js +104 -0
- package/dist/service/node-request-service.d.ts +14 -0
- package/dist/service/node-request-service.js +50 -0
- package/dist/service/node-run-event-bridge.d.ts +40 -0
- package/dist/service/node-run-event-bridge.js +83 -0
- package/dist/service/node-terminal-channel.d.ts +29 -0
- package/dist/service/node-terminal-channel.js +182 -0
- package/dist/service/node-upgrade-coordinator.d.ts +29 -0
- package/dist/service/node-upgrade-coordinator.js +137 -0
- package/dist/service/node-workspace-coordinator.d.ts +32 -0
- package/dist/service/node-workspace-coordinator.js +80 -0
- package/dist/service/run-attachment-service.d.ts +37 -0
- package/dist/service/run-attachment-service.js +160 -0
- package/dist/service/run-domain-state.d.ts +4 -0
- package/dist/service/run-domain-state.js +4 -0
- package/dist/service/run-event-service.d.ts +31 -0
- package/dist/service/run-event-service.js +102 -0
- package/dist/service/run-workbench-service.d.ts +27 -0
- package/dist/service/run-workbench-service.js +113 -0
- package/dist/service/runner-channel-message-service.d.ts +13 -0
- package/dist/service/runner-channel-message-service.js +20 -0
- package/dist/service/runner-interaction-service.d.ts +40 -0
- package/dist/service/runner-interaction-service.js +246 -0
- package/dist/service/runner-service.d.ts +44 -0
- package/dist/service/runner-service.js +68 -0
- package/dist/service/session-catalog-service.d.ts +45 -0
- package/dist/service/session-catalog-service.js +224 -0
- package/dist/service/session-command-service.d.ts +23 -0
- package/dist/service/session-command-service.js +69 -0
- package/dist/service/session-context-service.d.ts +34 -0
- package/dist/service/session-context-service.js +87 -0
- package/dist/service/session-domain-state.d.ts +8 -0
- package/dist/service/session-domain-state.js +8 -0
- package/dist/service/session-identity-service.d.ts +24 -0
- package/dist/service/session-identity-service.js +86 -0
- package/dist/service/session-lifecycle-service.d.ts +31 -0
- package/dist/service/session-lifecycle-service.js +242 -0
- package/dist/service/session-message-service.d.ts +33 -0
- package/dist/service/session-message-service.js +157 -0
- package/dist/service/session-presentation-service.d.ts +33 -0
- package/dist/service/session-presentation-service.js +36 -0
- package/dist/service/session-query-service.d.ts +31 -0
- package/dist/service/session-query-service.js +77 -0
- package/dist/service/terminal-relay-state.d.ts +6 -0
- package/dist/service/terminal-relay-state.js +3 -0
- package/dist/service/terminal-service.d.ts +12 -0
- package/dist/service/terminal-service.js +52 -0
- package/dist/service/workbench-event-service.d.ts +21 -0
- package/dist/service/workbench-event-service.js +53 -0
- package/dist/service/workbench-manifest-service.d.ts +89 -0
- package/dist/service/workbench-manifest-service.js +70 -0
- package/dist/service/workspace-change-service.d.ts +9 -0
- package/dist/service/workspace-change-service.js +86 -0
- package/dist/service/workspace-content-search-service.d.ts +14 -0
- package/dist/service/workspace-content-search-service.js +477 -0
- package/dist/service/workspace-domain-state.d.ts +75 -0
- package/dist/service/workspace-domain-state.js +40 -0
- package/dist/service/workspace-file-service.d.ts +17 -0
- package/dist/service/workspace-file-service.js +109 -0
- package/dist/service/workspace-queue-workbench-service.d.ts +55 -0
- package/dist/service/workspace-queue-workbench-service.js +226 -0
- package/dist/service/workspace-service.d.ts +15 -0
- package/dist/service/workspace-service.js +67 -0
- package/dist/service/workspace-session-service.d.ts +23 -0
- package/dist/service/workspace-session-service.js +139 -0
- package/dist/service/workspace-upload-service.d.ts +37 -0
- package/dist/service/workspace-upload-service.js +240 -0
- package/dist/service/workspace-watch-service.d.ts +18 -0
- package/dist/service/workspace-watch-service.js +120 -0
- package/dist/service/workspace-workbench-service.d.ts +47 -0
- package/dist/service/workspace-workbench-service.js +223 -0
- package/dist/service.js +2 -3
- package/dist/storage.js +0 -1
- package/dist/supervisor.d.ts +1 -0
- package/dist/supervisor.js +154 -0
- package/dist/terminal.d.ts +1 -0
- package/dist/terminal.js +1 -2
- package/dist/util/node-operation-parsers.d.ts +92 -0
- package/dist/util/node-operation-parsers.js +382 -0
- package/dist/util/runner-native-session-parsers.d.ts +72 -0
- package/dist/util/runner-native-session-parsers.js +381 -0
- package/dist/util/secret-environment.d.ts +1 -0
- package/dist/util/secret-environment.js +25 -0
- package/dist/workspace.d.ts +62 -2
- package/dist/workspace.js +539 -57
- package/package.json +3 -2
- package/dist/capabilities.js.map +0 -1
- package/dist/claude-agent-sdk.js.map +0 -1
- package/dist/claude-channel.js.map +0 -1
- package/dist/codex-app-server.js.map +0 -1
- package/dist/config.js.map +0 -1
- package/dist/connector.js.map +0 -1
- package/dist/database.js.map +0 -1
- package/dist/event-buffer.js.map +0 -1
- package/dist/fake-runner.js.map +0 -1
- package/dist/health.js.map +0 -1
- package/dist/main.js.map +0 -1
- package/dist/migrations/v001.js.map +0 -1
- package/dist/migrations/v002.js.map +0 -1
- package/dist/native-session-history.js.map +0 -1
- package/dist/operational.js.map +0 -1
- package/dist/process-tree.js.map +0 -1
- package/dist/runner-command-engine.js.map +0 -1
- package/dist/runner-profiles.js.map +0 -1
- package/dist/runner-usage.js.map +0 -1
- package/dist/runtime-state.js.map +0 -1
- package/dist/service.js.map +0 -1
- package/dist/storage.js.map +0 -1
- package/dist/terminal.js.map +0 -1
- package/dist/workspace.js.map +0 -1
|
@@ -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,9 +256,13 @@ async function readTranscript(path, runner, byteLimit) {
|
|
|
280
256
|
metaRecordIds.add(recordId);
|
|
281
257
|
if (isHiddenTranscriptRecord(value) ||
|
|
282
258
|
(parentId !== undefined && metaRecordIds.has(parentId))) {
|
|
283
|
-
|
|
259
|
+
return;
|
|
284
260
|
}
|
|
285
|
-
|
|
261
|
+
const generatedImage = codexGeneratedImageEvent(value);
|
|
262
|
+
if (generatedImage !== undefined)
|
|
263
|
+
items.push(generatedImage);
|
|
264
|
+
for (const parsedItem of transcriptItems(value)) {
|
|
265
|
+
const item = recordId === undefined ? parsedItem : { ...parsedItem, nativeId: recordId };
|
|
286
266
|
if (item.kind === 'tool_result') {
|
|
287
267
|
const toolIndex = toolIndexes.get(item.toolUseId);
|
|
288
268
|
if (toolIndex === undefined)
|
|
@@ -301,7 +281,7 @@ async function readTranscript(path, runner, byteLimit) {
|
|
|
301
281
|
}
|
|
302
282
|
items.push(item);
|
|
303
283
|
}
|
|
304
|
-
}
|
|
284
|
+
});
|
|
305
285
|
for (const result of pendingToolResults.values())
|
|
306
286
|
items.push({
|
|
307
287
|
kind: 'tool_call',
|
|
@@ -312,6 +292,9 @@ async function readTranscript(path, runner, byteLimit) {
|
|
|
312
292
|
inputTruncated: false,
|
|
313
293
|
outputSummary: result.output,
|
|
314
294
|
outputTruncated: result.truncated,
|
|
295
|
+
...(result.imageAttachments === undefined
|
|
296
|
+
? {}
|
|
297
|
+
: { imageAttachments: result.imageAttachments }),
|
|
315
298
|
failed: result.failed,
|
|
316
299
|
createdAt: result.createdAt
|
|
317
300
|
});
|
|
@@ -375,6 +358,37 @@ function codexContextUsage(value) {
|
|
|
375
358
|
return undefined;
|
|
376
359
|
return { usedTokens: promptUsage.input_tokens, maxTokens };
|
|
377
360
|
}
|
|
361
|
+
function claudeContextUsage(value) {
|
|
362
|
+
if (value.type !== 'assistant' || !isRecord(value.message))
|
|
363
|
+
return undefined;
|
|
364
|
+
const model = value.message.model;
|
|
365
|
+
const usage = value.message.usage;
|
|
366
|
+
if (typeof model !== 'string' ||
|
|
367
|
+
model.length === 0 ||
|
|
368
|
+
model === '<synthetic>' ||
|
|
369
|
+
!isRecord(usage))
|
|
370
|
+
return undefined;
|
|
371
|
+
const inputTokens = nonNegativeInteger(usage.input_tokens);
|
|
372
|
+
const cacheCreationTokens = optionalNonNegativeInteger(usage.cache_creation_input_tokens);
|
|
373
|
+
const cacheReadTokens = optionalNonNegativeInteger(usage.cache_read_input_tokens);
|
|
374
|
+
if (inputTokens === undefined ||
|
|
375
|
+
cacheCreationTokens === undefined ||
|
|
376
|
+
cacheReadTokens === undefined)
|
|
377
|
+
return undefined;
|
|
378
|
+
return { model, usedTokens: inputTokens + cacheCreationTokens + cacheReadTokens };
|
|
379
|
+
}
|
|
380
|
+
function optionalNonNegativeInteger(value) {
|
|
381
|
+
return value === undefined ? 0 : nonNegativeInteger(value);
|
|
382
|
+
}
|
|
383
|
+
function nonNegativeInteger(value) {
|
|
384
|
+
return typeof value === 'number' && Number.isSafeInteger(value) && value >= 0 ? value : undefined;
|
|
385
|
+
}
|
|
386
|
+
function positiveInteger(value) {
|
|
387
|
+
if (typeof value !== 'string' || !/^\d+$/.test(value.trim()))
|
|
388
|
+
return undefined;
|
|
389
|
+
const parsed = Number(value);
|
|
390
|
+
return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : undefined;
|
|
391
|
+
}
|
|
378
392
|
function transcriptItems(record) {
|
|
379
393
|
const createdAt = timestampOf(record);
|
|
380
394
|
const results = [...toolResultsOf(record, createdAt), ...codexToolResultsOf(record, createdAt)];
|
|
@@ -382,7 +396,10 @@ function transcriptItems(record) {
|
|
|
382
396
|
return results;
|
|
383
397
|
const values = [
|
|
384
398
|
...toolUsesOf(record, createdAt),
|
|
385
|
-
...codexToolUsesOf(record, createdAt)
|
|
399
|
+
...codexToolUsesOf(record, createdAt),
|
|
400
|
+
...codexReasoningSummariesOf(record, createdAt),
|
|
401
|
+
...unsupportedCodexResponseItems(record, createdAt),
|
|
402
|
+
...unsupportedTranscriptItems(record, createdAt)
|
|
386
403
|
];
|
|
387
404
|
const role = roleOf(record);
|
|
388
405
|
if (role === undefined)
|
|
@@ -398,6 +415,65 @@ function transcriptItems(record) {
|
|
|
398
415
|
values.unshift({ kind: 'message', role, text, createdAt });
|
|
399
416
|
return values;
|
|
400
417
|
}
|
|
418
|
+
function unsupportedCodexResponseItems(record, createdAt) {
|
|
419
|
+
const payload = codexToolPayload(record);
|
|
420
|
+
const type = typeof payload?.type === 'string' ? payload.type : undefined;
|
|
421
|
+
if (type === undefined ||
|
|
422
|
+
typeof payload?.role === 'string' ||
|
|
423
|
+
type === 'reasoning' ||
|
|
424
|
+
type === 'custom_tool_call' ||
|
|
425
|
+
type === 'custom_tool_call_output' ||
|
|
426
|
+
type === 'function_call' ||
|
|
427
|
+
type === 'function_call_output')
|
|
428
|
+
return [];
|
|
429
|
+
return [
|
|
430
|
+
{
|
|
431
|
+
kind: 'unknown',
|
|
432
|
+
sourceEventType: `codex:${type}`,
|
|
433
|
+
label: `Codex ${compact(type).slice(0, 128)}`,
|
|
434
|
+
createdAt
|
|
435
|
+
}
|
|
436
|
+
];
|
|
437
|
+
}
|
|
438
|
+
function codexReasoningSummariesOf(record, createdAt) {
|
|
439
|
+
const payload = codexToolPayload(record);
|
|
440
|
+
if (payload?.type !== 'reasoning')
|
|
441
|
+
return [];
|
|
442
|
+
const sections = Array.isArray(payload.summary)
|
|
443
|
+
? payload.summary.flatMap((value, index) => {
|
|
444
|
+
if (typeof value !== 'string')
|
|
445
|
+
return [];
|
|
446
|
+
const text = compact(value).slice(0, 10_000);
|
|
447
|
+
return text.length === 0 ? [] : [{ index, text }];
|
|
448
|
+
})
|
|
449
|
+
: [];
|
|
450
|
+
return [{ kind: 'reasoning_summary', sections, createdAt }];
|
|
451
|
+
}
|
|
452
|
+
function unsupportedTranscriptItems(record, createdAt) {
|
|
453
|
+
return contentBlocks(record).flatMap((block) => {
|
|
454
|
+
const type = typeof block.type === 'string' ? block.type : undefined;
|
|
455
|
+
if (type === undefined ||
|
|
456
|
+
type === 'text' ||
|
|
457
|
+
type === 'input_text' ||
|
|
458
|
+
type === 'output_text' ||
|
|
459
|
+
type === 'image' ||
|
|
460
|
+
type === 'input_image' ||
|
|
461
|
+
type === 'thinking' ||
|
|
462
|
+
type === 'redacted_thinking' ||
|
|
463
|
+
type === 'encrypted_thinking' ||
|
|
464
|
+
type === 'tool_use' ||
|
|
465
|
+
type === 'tool_result')
|
|
466
|
+
return [];
|
|
467
|
+
return [
|
|
468
|
+
{
|
|
469
|
+
kind: 'unknown',
|
|
470
|
+
sourceEventType: `transcript:${type}`,
|
|
471
|
+
label: `Agent ${compact(type).slice(0, 128)}`,
|
|
472
|
+
createdAt
|
|
473
|
+
}
|
|
474
|
+
];
|
|
475
|
+
});
|
|
476
|
+
}
|
|
401
477
|
function toolUsesOf(record, createdAt) {
|
|
402
478
|
return contentBlocks(record).flatMap((block) => {
|
|
403
479
|
if (block['type'] !== 'tool_use' ||
|
|
@@ -479,6 +555,7 @@ function codexToolResultsOf(record, createdAt) {
|
|
|
479
555
|
if (toolUseId === undefined)
|
|
480
556
|
return [];
|
|
481
557
|
const output = compactValue(codexToolOutput(payload['output'] ?? payload['contentItems']), 10_000);
|
|
558
|
+
const imageAttachments = codexImagePlaceholders(payload['output'] ?? payload['contentItems']);
|
|
482
559
|
return [
|
|
483
560
|
{
|
|
484
561
|
kind: 'tool_result',
|
|
@@ -486,6 +563,7 @@ function codexToolResultsOf(record, createdAt) {
|
|
|
486
563
|
output: output.text,
|
|
487
564
|
failed: payload['status'] === 'failed' || payload['error'] !== undefined,
|
|
488
565
|
truncated: output.truncated,
|
|
566
|
+
...(imageAttachments.length === 0 ? {} : { imageAttachments }),
|
|
489
567
|
createdAt
|
|
490
568
|
}
|
|
491
569
|
];
|
|
@@ -495,6 +573,31 @@ function codexToolPayload(record) {
|
|
|
495
573
|
? record['payload']
|
|
496
574
|
: undefined;
|
|
497
575
|
}
|
|
576
|
+
function codexGeneratedImageEvent(record) {
|
|
577
|
+
if (record['type'] !== 'event_msg' || !isRecord(record['payload']))
|
|
578
|
+
return undefined;
|
|
579
|
+
const payload = record['payload'];
|
|
580
|
+
if (payload['type'] !== 'image_generation_end' ||
|
|
581
|
+
typeof payload['call_id'] !== 'string' ||
|
|
582
|
+
typeof payload['saved_path'] !== 'string')
|
|
583
|
+
return undefined;
|
|
584
|
+
const localPath = payload['saved_path'];
|
|
585
|
+
if (!localPath.includes('/.codex/generated_images/') || !/\.(?:png|jpe?g|webp)$/i.test(localPath))
|
|
586
|
+
return undefined;
|
|
587
|
+
return {
|
|
588
|
+
kind: 'tool_call',
|
|
589
|
+
toolUseId: payload['call_id'],
|
|
590
|
+
toolName: 'imagegen',
|
|
591
|
+
input: null,
|
|
592
|
+
inputSummary: null,
|
|
593
|
+
inputTruncated: false,
|
|
594
|
+
outputSummary: payload['status'] === 'completed' ? '图片生成完成' : '图片生成失败',
|
|
595
|
+
outputTruncated: false,
|
|
596
|
+
imageAttachments: [{ name: basename(localPath), mime: imageMime(localPath), localPath }],
|
|
597
|
+
failed: payload['status'] !== 'completed',
|
|
598
|
+
createdAt: timestampOf(record)
|
|
599
|
+
};
|
|
600
|
+
}
|
|
498
601
|
/** Extract Codex output text blocks instead of rendering their JSON envelopes. */
|
|
499
602
|
function codexToolOutput(value) {
|
|
500
603
|
if (!Array.isArray(value))
|
|
@@ -505,15 +608,94 @@ function codexToolOutput(value) {
|
|
|
505
608
|
if (!isRecord(block))
|
|
506
609
|
return [];
|
|
507
610
|
const direct = firstString(block, ['text', 'content']);
|
|
508
|
-
return direct === undefined ? [] : [direct];
|
|
611
|
+
return direct === undefined ? [] : [codexVisibleToolText(direct)];
|
|
509
612
|
});
|
|
510
613
|
return text.length > 0 ? text.join('') : value;
|
|
511
614
|
}
|
|
615
|
+
function codexVisibleToolText(value) {
|
|
616
|
+
const marker = 'data:image/';
|
|
617
|
+
let cursor = 0;
|
|
618
|
+
let visible = '';
|
|
619
|
+
while (cursor < value.length) {
|
|
620
|
+
const start = value.indexOf(marker, cursor);
|
|
621
|
+
if (start < 0)
|
|
622
|
+
return visible + value.slice(cursor);
|
|
623
|
+
visible += `${value.slice(cursor, start)}[图片内容已过滤]`;
|
|
624
|
+
const doubleQuote = value.indexOf('"', start);
|
|
625
|
+
const singleQuote = value.indexOf("'", start);
|
|
626
|
+
const end = doubleQuote < 0
|
|
627
|
+
? singleQuote
|
|
628
|
+
: singleQuote < 0
|
|
629
|
+
? doubleQuote
|
|
630
|
+
: Math.min(doubleQuote, singleQuote);
|
|
631
|
+
if (end < 0)
|
|
632
|
+
return visible;
|
|
633
|
+
cursor = end;
|
|
634
|
+
}
|
|
635
|
+
return visible;
|
|
636
|
+
}
|
|
637
|
+
/** Extract only image placeholder metadata from native Codex output. */
|
|
638
|
+
function codexImagePlaceholders(value) {
|
|
639
|
+
const paths = [];
|
|
640
|
+
let visited = 0;
|
|
641
|
+
const visit = (current, depth) => {
|
|
642
|
+
if (paths.length >= 5 || visited >= 128 || depth > 5 || current == null)
|
|
643
|
+
return;
|
|
644
|
+
visited += 1;
|
|
645
|
+
if (typeof current === 'string') {
|
|
646
|
+
collectGeneratedImagePaths(current, paths);
|
|
647
|
+
return;
|
|
648
|
+
}
|
|
649
|
+
if (Array.isArray(current)) {
|
|
650
|
+
for (const entry of current)
|
|
651
|
+
visit(entry, depth + 1);
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
if (!isRecord(current))
|
|
655
|
+
return;
|
|
656
|
+
for (const child of Object.values(current))
|
|
657
|
+
visit(child, depth + 1);
|
|
658
|
+
};
|
|
659
|
+
visit(value, 0);
|
|
660
|
+
return paths.map((localPath) => ({
|
|
661
|
+
name: basename(localPath),
|
|
662
|
+
mime: imageMime(localPath),
|
|
663
|
+
localPath
|
|
664
|
+
}));
|
|
665
|
+
}
|
|
666
|
+
function collectGeneratedImagePaths(value, paths) {
|
|
667
|
+
const marker = '/.codex/generated_images/';
|
|
668
|
+
let cursor = 0;
|
|
669
|
+
while (paths.length < 5) {
|
|
670
|
+
const markerIndex = value.indexOf(marker, cursor);
|
|
671
|
+
if (markerIndex < 0)
|
|
672
|
+
return;
|
|
673
|
+
let start = markerIndex;
|
|
674
|
+
while (start > 0 && !/[\s"']/.test(value[start - 1]))
|
|
675
|
+
start -= 1;
|
|
676
|
+
let end = markerIndex + marker.length;
|
|
677
|
+
while (end < value.length && !/[\s"']/.test(value[end]))
|
|
678
|
+
end += 1;
|
|
679
|
+
const path = value.slice(start, end).replace(/[),.;:]+$/, '');
|
|
680
|
+
if (/\.(?:png|jpe?g|webp)$/i.test(path) && !paths.includes(path))
|
|
681
|
+
paths.push(path);
|
|
682
|
+
cursor = end;
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
function imageMime(path) {
|
|
686
|
+
const extension = path.slice(path.lastIndexOf('.') + 1).toLowerCase();
|
|
687
|
+
if (extension === 'jpg' || extension === 'jpeg')
|
|
688
|
+
return 'image/jpeg';
|
|
689
|
+
if (extension === 'webp')
|
|
690
|
+
return 'image/webp';
|
|
691
|
+
return 'image/png';
|
|
692
|
+
}
|
|
512
693
|
function withToolResult(call, result) {
|
|
513
694
|
return {
|
|
514
695
|
...call,
|
|
515
696
|
outputSummary: result.output,
|
|
516
697
|
outputTruncated: result.truncated,
|
|
698
|
+
...(result.imageAttachments === undefined ? {} : { imageAttachments: result.imageAttachments }),
|
|
517
699
|
failed: result.failed
|
|
518
700
|
};
|
|
519
701
|
}
|
|
@@ -649,4 +831,3 @@ function indexKey(runner, cwd, externalSessionId) {
|
|
|
649
831
|
function isRecord(value) {
|
|
650
832
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
651
833
|
}
|
|
652
|
-
//# sourceMappingURL=native-session-history.js.map
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export interface OpenCodeProbe {
|
|
2
|
+
readonly available: boolean;
|
|
3
|
+
readonly compatible: boolean;
|
|
4
|
+
readonly version?: string;
|
|
5
|
+
}
|
|
6
|
+
export interface OpenCodeProviderCatalog {
|
|
7
|
+
readonly connected?: readonly string[];
|
|
8
|
+
readonly default?: Readonly<Record<string, string>>;
|
|
9
|
+
readonly all?: readonly {
|
|
10
|
+
readonly id?: string;
|
|
11
|
+
readonly name?: string;
|
|
12
|
+
readonly source?: 'env' | 'config' | 'custom' | 'api';
|
|
13
|
+
readonly models?: Readonly<Record<string, {
|
|
14
|
+
readonly id?: string;
|
|
15
|
+
readonly name?: string;
|
|
16
|
+
readonly variants?: Readonly<Record<string, {
|
|
17
|
+
readonly disabled?: boolean;
|
|
18
|
+
}>>;
|
|
19
|
+
readonly limit?: {
|
|
20
|
+
readonly context?: number;
|
|
21
|
+
};
|
|
22
|
+
}>>;
|
|
23
|
+
}[];
|
|
24
|
+
}
|
|
25
|
+
export interface OpenCodeProfileModel {
|
|
26
|
+
readonly id: string;
|
|
27
|
+
readonly label: string;
|
|
28
|
+
readonly supportedEfforts: string[];
|
|
29
|
+
}
|
|
30
|
+
export declare function probeOpenCode(command?: string): OpenCodeProbe;
|
|
31
|
+
export declare function probeOpenCodeAsync(command?: string): Promise<OpenCodeProbe>;
|
|
32
|
+
export declare function openCodeProfileModels(catalog: OpenCodeProviderCatalog): OpenCodeProfileModel[];
|
|
33
|
+
export declare function openCodeContextLimits(catalog: OpenCodeProviderCatalog): ReadonlyMap<string, number>;
|