remote-codex 0.1.10 → 0.11.1
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/apps/supervisor-api/dist/chunk-6M32PPHZ.js +24507 -0
- package/apps/supervisor-api/dist/chunk-7AA2MFXK.js +24499 -0
- package/apps/supervisor-api/dist/chunk-HKBFCPHH.js +24511 -0
- package/apps/supervisor-api/dist/index.js +12525 -28436
- package/apps/supervisor-api/dist/worker-index.d.ts +2 -0
- package/apps/supervisor-api/dist/worker-index.js +33 -0
- package/apps/supervisor-web/dist/assets/{highlighted-body-OFNGDK62-CyMcatlD.js → highlighted-body-OFNGDK62-p31aS0f0.js} +1 -1
- package/apps/supervisor-web/dist/assets/index-BiuFei_K.css +32 -0
- package/apps/supervisor-web/dist/assets/index-D1R9CUnx.js +2161 -0
- package/apps/supervisor-web/dist/assets/{xterm-DbYWMNQ0.js → xterm-D92BViLH.js} +1 -1
- package/apps/supervisor-web/dist/index.html +2 -2
- package/package.json +2 -3
- package/packages/agent-runtime/src/index.ts +4 -0
- package/packages/agent-runtime/src/management-errors.ts +11 -0
- package/packages/agent-runtime/src/model-pricing.ts +325 -0
- package/packages/agent-runtime/src/registry.ts +19 -4
- package/packages/agent-runtime/src/runtime-errors.ts +97 -0
- package/packages/agent-runtime/src/types.ts +36 -3
- package/packages/agent-runtime/src/unavailable-runtime.ts +169 -0
- package/packages/claude/src/historyItems.ts +41 -5
- package/packages/claude/src/runtimeAdapter.test.ts +117 -6
- package/packages/claude/src/runtimeAdapter.ts +421 -65
- package/packages/codex/src/historyItems.test.ts +137 -0
- package/packages/codex/src/historyItems.ts +135 -17
- package/packages/codex/src/hookHistory.test.ts +59 -0
- package/packages/codex/src/index.ts +7 -0
- package/packages/codex/src/local-session-store.ts +390 -0
- package/packages/codex/src/management/codex-management-service.ts +454 -0
- package/packages/codex/src/management/codexHostConfig.test.ts +88 -0
- package/packages/codex/src/management/codexHostConfig.ts +188 -0
- package/packages/codex/src/management/errors.ts +20 -0
- package/packages/codex/src/modelPricing.test.ts +235 -0
- package/packages/codex/src/modelPricing.ts +9 -0
- package/packages/codex/src/runtime-errors.test.ts +72 -0
- package/packages/codex/src/runtime-errors.ts +37 -0
- package/packages/codex/src/runtimeAdapter.ts +15 -0
- package/packages/codex/src/thread-title.ts +1 -0
- package/packages/opencode/src/historyItems.test.ts +504 -0
- package/packages/opencode/src/historyItems.ts +896 -0
- package/packages/opencode/src/index.ts +2 -0
- package/packages/opencode/src/runtimeAdapter.test.ts +1444 -0
- package/packages/opencode/src/runtimeAdapter.ts +1473 -0
- package/packages/shared/src/agent-providers.ts +56 -0
- package/packages/shared/src/index.ts +240 -35
- package/apps/supervisor-web/dist/assets/index-BlAhoIuq.js +0 -379
- package/apps/supervisor-web/dist/assets/index-DI0NRNgr.css +0 -32
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
import type { Dirent } from 'node:fs';
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
|
|
5
|
+
import Database from 'better-sqlite3';
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
ThreadHistoryItemDto,
|
|
9
|
+
ThreadSourceDto,
|
|
10
|
+
ThreadTurnDto,
|
|
11
|
+
truncateAutoThreadTitle,
|
|
12
|
+
} from '../../shared/src/index';
|
|
13
|
+
|
|
14
|
+
interface LocalStateThreadRow {
|
|
15
|
+
id: string;
|
|
16
|
+
cwd: string;
|
|
17
|
+
title: string | null;
|
|
18
|
+
rolloutPath: string | null;
|
|
19
|
+
model: string | null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface ParsedTranscript {
|
|
23
|
+
cwd: string | null;
|
|
24
|
+
title: string | null;
|
|
25
|
+
turns: ThreadTurnDto[];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface LocalCodexSessionRecord {
|
|
29
|
+
sessionId: string;
|
|
30
|
+
cwd: string;
|
|
31
|
+
title: string | null;
|
|
32
|
+
model: string | null;
|
|
33
|
+
rolloutPath: string | null;
|
|
34
|
+
turns: ThreadTurnDto[];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface LocalCodexImportSession {
|
|
38
|
+
provider: 'codex';
|
|
39
|
+
source: Extract<ThreadSourceDto, 'local_codex_import'>;
|
|
40
|
+
sessionId: string;
|
|
41
|
+
cwd: string;
|
|
42
|
+
title: string;
|
|
43
|
+
model: string | null;
|
|
44
|
+
summaryText: string | null;
|
|
45
|
+
fastMode: boolean;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
interface MutableTurn {
|
|
49
|
+
id: string;
|
|
50
|
+
startedAt: string | null;
|
|
51
|
+
status: ThreadTurnDto['status'];
|
|
52
|
+
error: string | null;
|
|
53
|
+
items: ThreadHistoryItemDto[];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function basenameFromPath(absPath: string) {
|
|
57
|
+
const normalized = absPath.replace(/[\\/]+$/, '');
|
|
58
|
+
return normalized.split(/[\\/]/).at(-1) ?? normalized;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function summarizeTitleFromTurns(turns: ThreadTurnDto[]) {
|
|
62
|
+
const firstUserMessage = turns
|
|
63
|
+
.flatMap((turn) => turn.items)
|
|
64
|
+
.find((item) => item.kind === 'userMessage' && item.text.trim());
|
|
65
|
+
|
|
66
|
+
if (!firstUserMessage) {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return truncateAutoThreadTitle(firstUserMessage.text);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function createHistoryItemId(turnId: string, prefix: string, index: number) {
|
|
74
|
+
return `${turnId}-${prefix}-${index}`;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function finalizeTurn(turn: MutableTurn | null, turns: ThreadTurnDto[]) {
|
|
78
|
+
if (!turn || turn.items.length === 0) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
turns.push({
|
|
83
|
+
id: turn.id,
|
|
84
|
+
startedAt: turn.startedAt,
|
|
85
|
+
status: turn.status,
|
|
86
|
+
error: turn.error,
|
|
87
|
+
items: turn.items,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function parseTranscript(contents: string): ParsedTranscript {
|
|
92
|
+
const turns: ThreadTurnDto[] = [];
|
|
93
|
+
let cwd: string | null = null;
|
|
94
|
+
let currentTurn: MutableTurn | null = null;
|
|
95
|
+
let fallbackTurnCount = 0;
|
|
96
|
+
let agentItemCount = 0;
|
|
97
|
+
let userItemCount = 0;
|
|
98
|
+
|
|
99
|
+
const ensureCurrentTurn = (timestamp?: string) => {
|
|
100
|
+
if (currentTurn) {
|
|
101
|
+
return currentTurn;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
fallbackTurnCount += 1;
|
|
105
|
+
currentTurn = {
|
|
106
|
+
id: `local-turn-${fallbackTurnCount}`,
|
|
107
|
+
startedAt: timestamp ?? null,
|
|
108
|
+
status: 'inProgress',
|
|
109
|
+
error: null,
|
|
110
|
+
items: [],
|
|
111
|
+
};
|
|
112
|
+
agentItemCount = 0;
|
|
113
|
+
userItemCount = 0;
|
|
114
|
+
return currentTurn;
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
for (const line of contents.split('\n')) {
|
|
118
|
+
if (!line.trim()) {
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
let entry: any;
|
|
123
|
+
try {
|
|
124
|
+
entry = JSON.parse(line);
|
|
125
|
+
} catch {
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (entry.type === 'session_meta') {
|
|
130
|
+
const payload = entry.payload ?? {};
|
|
131
|
+
if (typeof payload.cwd === 'string' && payload.cwd.trim()) {
|
|
132
|
+
cwd = payload.cwd;
|
|
133
|
+
}
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (entry.type !== 'event_msg') {
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const payload = entry.payload ?? {};
|
|
142
|
+
const payloadType = payload.type;
|
|
143
|
+
|
|
144
|
+
if (payloadType === 'task_started') {
|
|
145
|
+
finalizeTurn(currentTurn, turns);
|
|
146
|
+
currentTurn = {
|
|
147
|
+
id:
|
|
148
|
+
typeof payload.turn_id === 'string' && payload.turn_id.trim()
|
|
149
|
+
? payload.turn_id
|
|
150
|
+
: `local-turn-${fallbackTurnCount + 1}`,
|
|
151
|
+
startedAt: entry.timestamp ?? null,
|
|
152
|
+
status: 'inProgress',
|
|
153
|
+
error: null,
|
|
154
|
+
items: [],
|
|
155
|
+
};
|
|
156
|
+
agentItemCount = 0;
|
|
157
|
+
userItemCount = 0;
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (payloadType === 'user_message' && typeof payload.message === 'string') {
|
|
162
|
+
const turn = ensureCurrentTurn(entry.timestamp);
|
|
163
|
+
userItemCount += 1;
|
|
164
|
+
turn.items.push({
|
|
165
|
+
id: createHistoryItemId(turn.id, 'user', userItemCount),
|
|
166
|
+
kind: 'userMessage',
|
|
167
|
+
text: payload.message,
|
|
168
|
+
});
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (payloadType === 'agent_message' && typeof payload.message === 'string') {
|
|
173
|
+
const turn = ensureCurrentTurn(entry.timestamp);
|
|
174
|
+
agentItemCount += 1;
|
|
175
|
+
turn.items.push({
|
|
176
|
+
id: createHistoryItemId(turn.id, 'agent', agentItemCount),
|
|
177
|
+
kind: 'agentMessage',
|
|
178
|
+
text: payload.message,
|
|
179
|
+
status: typeof payload.phase === 'string' ? payload.phase : null,
|
|
180
|
+
});
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (payloadType === 'task_complete') {
|
|
185
|
+
const turn = ensureCurrentTurn(entry.timestamp);
|
|
186
|
+
turn.status = turn.error ? 'failed' : 'completed';
|
|
187
|
+
finalizeTurn(turn, turns);
|
|
188
|
+
currentTurn = null;
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (payloadType === 'error') {
|
|
193
|
+
const turn = ensureCurrentTurn(entry.timestamp);
|
|
194
|
+
turn.status = 'failed';
|
|
195
|
+
turn.error =
|
|
196
|
+
typeof payload.message === 'string'
|
|
197
|
+
? payload.message
|
|
198
|
+
: 'Local Codex session failed.';
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
finalizeTurn(currentTurn, turns);
|
|
203
|
+
|
|
204
|
+
return {
|
|
205
|
+
cwd,
|
|
206
|
+
title: summarizeTitleFromTurns(turns),
|
|
207
|
+
turns,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
async function fileExists(filePath: string) {
|
|
212
|
+
try {
|
|
213
|
+
await fs.access(filePath);
|
|
214
|
+
return true;
|
|
215
|
+
} catch {
|
|
216
|
+
return false;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export class LocalCodexSessionStore {
|
|
221
|
+
constructor(private readonly codexHome: string) {}
|
|
222
|
+
|
|
223
|
+
async findSession(
|
|
224
|
+
sessionId: string,
|
|
225
|
+
): Promise<LocalCodexSessionRecord | null> {
|
|
226
|
+
const stateRecord = await this.findSessionInStateDatabases(sessionId);
|
|
227
|
+
const transcriptPath = await this.resolveTranscriptPath(
|
|
228
|
+
stateRecord?.rolloutPath ?? null,
|
|
229
|
+
sessionId,
|
|
230
|
+
);
|
|
231
|
+
const transcript = transcriptPath
|
|
232
|
+
? parseTranscript(await fs.readFile(transcriptPath, 'utf8'))
|
|
233
|
+
: null;
|
|
234
|
+
const cwd = stateRecord?.cwd ?? transcript?.cwd ?? null;
|
|
235
|
+
|
|
236
|
+
if (!cwd) {
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return {
|
|
241
|
+
sessionId,
|
|
242
|
+
cwd,
|
|
243
|
+
title:
|
|
244
|
+
stateRecord?.title?.trim() ||
|
|
245
|
+
transcript?.title?.trim() ||
|
|
246
|
+
basenameFromPath(cwd),
|
|
247
|
+
model: stateRecord?.model ?? null,
|
|
248
|
+
rolloutPath: transcriptPath,
|
|
249
|
+
turns: transcript?.turns ?? [],
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
async findImportSession(
|
|
254
|
+
sessionId: string,
|
|
255
|
+
input: { fastMode: boolean },
|
|
256
|
+
): Promise<LocalCodexImportSession | null> {
|
|
257
|
+
const localSession = await this.findSession(sessionId);
|
|
258
|
+
if (!localSession) {
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return {
|
|
263
|
+
provider: 'codex',
|
|
264
|
+
source: 'local_codex_import',
|
|
265
|
+
sessionId: localSession.sessionId,
|
|
266
|
+
cwd: localSession.cwd,
|
|
267
|
+
title: truncateAutoThreadTitle(
|
|
268
|
+
localSession.title?.trim() || 'Untitled imported session',
|
|
269
|
+
),
|
|
270
|
+
model: localSession.model,
|
|
271
|
+
summaryText:
|
|
272
|
+
localSession.turns
|
|
273
|
+
.flatMap((turn) => turn.items)
|
|
274
|
+
.find((item) => item.kind === 'userMessage')
|
|
275
|
+
?.text ?? null,
|
|
276
|
+
fastMode: input.fastMode,
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
private async findSessionInStateDatabases(
|
|
281
|
+
sessionId: string,
|
|
282
|
+
): Promise<LocalStateThreadRow | null> {
|
|
283
|
+
let entries: string[];
|
|
284
|
+
try {
|
|
285
|
+
entries = await fs.readdir(this.codexHome);
|
|
286
|
+
} catch {
|
|
287
|
+
return null;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const stateFiles = await Promise.all(
|
|
291
|
+
entries
|
|
292
|
+
.filter((entry) => /^state_\d+\.sqlite$/i.test(entry))
|
|
293
|
+
.map(async (entry) => {
|
|
294
|
+
const absPath = path.join(this.codexHome, entry);
|
|
295
|
+
const stats = await fs.stat(absPath);
|
|
296
|
+
return {
|
|
297
|
+
absPath,
|
|
298
|
+
mtimeMs: stats.mtimeMs,
|
|
299
|
+
};
|
|
300
|
+
}),
|
|
301
|
+
);
|
|
302
|
+
|
|
303
|
+
stateFiles.sort((left, right) => right.mtimeMs - left.mtimeMs);
|
|
304
|
+
|
|
305
|
+
for (const stateFile of stateFiles) {
|
|
306
|
+
const sqlite = new Database(stateFile.absPath, {
|
|
307
|
+
readonly: true,
|
|
308
|
+
fileMustExist: true,
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
try {
|
|
312
|
+
const row = sqlite
|
|
313
|
+
.prepare(
|
|
314
|
+
`
|
|
315
|
+
SELECT
|
|
316
|
+
id,
|
|
317
|
+
cwd,
|
|
318
|
+
title,
|
|
319
|
+
rollout_path AS rolloutPath,
|
|
320
|
+
model
|
|
321
|
+
FROM threads
|
|
322
|
+
WHERE id = ?
|
|
323
|
+
LIMIT 1
|
|
324
|
+
`,
|
|
325
|
+
)
|
|
326
|
+
.get(sessionId) as LocalStateThreadRow | undefined;
|
|
327
|
+
|
|
328
|
+
if (row) {
|
|
329
|
+
return row;
|
|
330
|
+
}
|
|
331
|
+
} catch {
|
|
332
|
+
// Ignore incompatible sqlite files and continue probing.
|
|
333
|
+
} finally {
|
|
334
|
+
sqlite.close();
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
return null;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
private async resolveTranscriptPath(
|
|
342
|
+
rolloutPath: string | null,
|
|
343
|
+
sessionId: string,
|
|
344
|
+
): Promise<string | null> {
|
|
345
|
+
if (rolloutPath?.trim()) {
|
|
346
|
+
const absolutePath = path.isAbsolute(rolloutPath)
|
|
347
|
+
? rolloutPath
|
|
348
|
+
: path.resolve(this.codexHome, rolloutPath);
|
|
349
|
+
|
|
350
|
+
if (await fileExists(absolutePath)) {
|
|
351
|
+
return absolutePath;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
return this.findTranscriptFile(path.join(this.codexHome, 'sessions'), sessionId);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
private async findTranscriptFile(
|
|
359
|
+
directory: string,
|
|
360
|
+
sessionId: string,
|
|
361
|
+
): Promise<string | null> {
|
|
362
|
+
let entries: Dirent[];
|
|
363
|
+
try {
|
|
364
|
+
entries = await fs.readdir(directory, { withFileTypes: true });
|
|
365
|
+
} catch {
|
|
366
|
+
return null;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
for (const entry of entries) {
|
|
370
|
+
const absPath = path.join(directory, entry.name);
|
|
371
|
+
if (entry.isDirectory()) {
|
|
372
|
+
const nested = await this.findTranscriptFile(absPath, sessionId);
|
|
373
|
+
if (nested) {
|
|
374
|
+
return nested;
|
|
375
|
+
}
|
|
376
|
+
continue;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
if (
|
|
380
|
+
entry.isFile() &&
|
|
381
|
+
entry.name.endsWith('.jsonl') &&
|
|
382
|
+
entry.name.includes(sessionId)
|
|
383
|
+
) {
|
|
384
|
+
return absPath;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
return null;
|
|
389
|
+
}
|
|
390
|
+
}
|