@cydm/happy-elves 0.1.0-beta.49 → 0.1.0-beta.50
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/cli/dist/commands/session.js +2 -0
- package/apps/daemon/dist/runtime/external-provider.js +21 -0
- package/apps/daemon/dist/session/lifecycle.js +114 -18
- package/apps/daemon/dist/session/metadata.js +3 -0
- package/apps/daemon/dist/types.d.ts +5 -0
- package/apps/daemon/package.json +1 -1
- package/npm-shrinkwrap.json +8 -8
- package/package.json +1 -1
- package/packages/runtime/dist/index.d.ts +6 -0
- package/packages/runtime-cli/dist/codex-history.js +55 -17
- package/packages/runtime-cli/dist/index.js +8 -0
- package/packages/runtime-cli/dist/session-store.d.ts +1 -0
- package/packages/runtime-cli/dist/session-store.js +8 -1
|
@@ -213,6 +213,8 @@ function diagnosticSessionMetadata(metadata) {
|
|
|
213
213
|
historicalOlderBackfilledEvents: numberMetadata(metadata, "historicalOlderBackfilledEvents"),
|
|
214
214
|
historicalTailRepairedEvents: numberMetadata(metadata, "historicalTailRepairedEvents"),
|
|
215
215
|
truncatedHistoricalBackfill: metadata.truncatedHistoricalBackfill === true,
|
|
216
|
+
runtimeResumeStatus: stringMetadata(metadata, "runtimeResumeStatus"),
|
|
217
|
+
runtimeResumeError: stringMetadata(metadata, "runtimeResumeError"),
|
|
216
218
|
importRequestId: stringMetadata(metadata, "importRequestId"),
|
|
217
219
|
currentHead: stringMetadata(metadata, "currentHead"),
|
|
218
220
|
emptyRuntimeSession: metadata.emptyRuntimeSession === true,
|
|
@@ -342,6 +342,27 @@ export function createExternalAgentRuntimeProvider(options) {
|
|
|
342
342
|
}), "ensureSession result handle");
|
|
343
343
|
return toRuntimeHandle(input.sessionKey, handle, input.cwd);
|
|
344
344
|
},
|
|
345
|
+
async attachSession(input) {
|
|
346
|
+
const client = requireClientForAgent(input.agent);
|
|
347
|
+
if (!client) {
|
|
348
|
+
if (typeof options.fallback.attachSession === "function")
|
|
349
|
+
return await options.fallback.attachSession(input);
|
|
350
|
+
return await options.fallback.ensureSession({
|
|
351
|
+
sessionKey: input.sessionKey,
|
|
352
|
+
agent: input.agent,
|
|
353
|
+
mode: "persistent",
|
|
354
|
+
resumeSessionId: input.runtimeSessionId,
|
|
355
|
+
cwd: input.cwd,
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
const handle = validatedProviderHandle(client.id, {
|
|
359
|
+
provider: client.id,
|
|
360
|
+
agent: input.agent,
|
|
361
|
+
sessionId: input.runtimeSessionId,
|
|
362
|
+
cwd: input.cwd,
|
|
363
|
+
}, "historical runtime handle");
|
|
364
|
+
return toRuntimeHandle(input.sessionKey, handle, input.cwd);
|
|
365
|
+
},
|
|
345
366
|
async listHistoricalSessions(input) {
|
|
346
367
|
return (await listHistoricalSessionPage(input)).sessions;
|
|
347
368
|
},
|
|
@@ -85,14 +85,14 @@ export async function handleImportHistoricalSession(ws, config, command) {
|
|
|
85
85
|
return;
|
|
86
86
|
const cwd = command.cwd || process.cwd();
|
|
87
87
|
try {
|
|
88
|
-
const
|
|
89
|
-
|
|
88
|
+
const runtime = getRuntime(cwd);
|
|
89
|
+
const importedMetadata = await commandSessionMetadata(config, command.encryptedMetadata);
|
|
90
|
+
const handle = await attachHistoricalRuntimeSession(runtime, {
|
|
91
|
+
sessionId: command.sessionId,
|
|
90
92
|
agent: command.agent,
|
|
91
|
-
mode: "persistent",
|
|
92
|
-
resumeSessionId: command.runtimeSessionId,
|
|
93
93
|
cwd,
|
|
94
|
+
runtimeSessionId: command.runtimeSessionId,
|
|
94
95
|
});
|
|
95
|
-
assertImportedRuntimeHandleMatches(command.runtimeSessionId, handle);
|
|
96
96
|
const state = {
|
|
97
97
|
sessionId: command.sessionId,
|
|
98
98
|
agent: command.agent,
|
|
@@ -100,7 +100,6 @@ export async function handleImportHistoricalSession(ws, config, command) {
|
|
|
100
100
|
name: command.name,
|
|
101
101
|
handle,
|
|
102
102
|
};
|
|
103
|
-
const importedMetadata = await commandSessionMetadata(config, command.encryptedMetadata);
|
|
104
103
|
sessions.set(command.sessionId, state);
|
|
105
104
|
const importedAt = new Date().toISOString();
|
|
106
105
|
const importedMetadataBase = {
|
|
@@ -133,14 +132,41 @@ export async function handleImportHistoricalSession(ws, config, command) {
|
|
|
133
132
|
...historicalBackfillMetadata(backfill),
|
|
134
133
|
},
|
|
135
134
|
});
|
|
135
|
+
let readyState = backfilledState;
|
|
136
|
+
let runtimeResumeError;
|
|
137
|
+
try {
|
|
138
|
+
const resumedHandle = await runtime.ensureSession({
|
|
139
|
+
sessionKey: command.sessionId,
|
|
140
|
+
agent: command.agent,
|
|
141
|
+
mode: "persistent",
|
|
142
|
+
resumeSessionId: command.runtimeSessionId,
|
|
143
|
+
cwd,
|
|
144
|
+
});
|
|
145
|
+
assertImportedRuntimeHandleMatches(command.runtimeSessionId, resumedHandle);
|
|
146
|
+
readyState = { ...backfilledState, handle: resumedHandle };
|
|
147
|
+
sessions.set(command.sessionId, readyState);
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
runtimeResumeError = errorMessage(error);
|
|
151
|
+
await appendAudit({
|
|
152
|
+
sessionId: command.sessionId,
|
|
153
|
+
machineId: config.machineId,
|
|
154
|
+
actor: "system",
|
|
155
|
+
action: "session.import.runtime_resume_unavailable",
|
|
156
|
+
summary: `Imported historical transcript but could not resume runtime session ${command.runtimeSessionId}`,
|
|
157
|
+
evidence: { requestId: command.requestId, runtimeSessionId: command.runtimeSessionId, error: runtimeResumeError },
|
|
158
|
+
});
|
|
159
|
+
}
|
|
136
160
|
await sendCommandResponse(ws, {
|
|
137
161
|
type: "machine:sessionReady",
|
|
138
162
|
requestId: command.requestId,
|
|
139
163
|
sessionId: command.sessionId,
|
|
140
|
-
encryptedMetadata: await encryptedSessionMetadata(config, command.sessionId,
|
|
164
|
+
encryptedMetadata: await encryptedSessionMetadata(config, command.sessionId, readyState, {
|
|
141
165
|
...importedMetadataBase,
|
|
142
166
|
...historicalBackfillMetadata(backfill),
|
|
143
167
|
historicalBackfilledAt: new Date().toISOString(),
|
|
168
|
+
runtimeResumeStatus: runtimeResumeError ? "unavailable" : "available",
|
|
169
|
+
...(runtimeResumeError ? { runtimeResumeError } : {}),
|
|
144
170
|
}),
|
|
145
171
|
});
|
|
146
172
|
}
|
|
@@ -162,6 +188,49 @@ export async function handleImportHistoricalSession(ws, config, command) {
|
|
|
162
188
|
});
|
|
163
189
|
}
|
|
164
190
|
}
|
|
191
|
+
async function attachHistoricalRuntimeSession(runtime, input) {
|
|
192
|
+
if (typeof runtime.attachSession === "function") {
|
|
193
|
+
const handle = await runtime.attachSession({
|
|
194
|
+
sessionKey: input.sessionId,
|
|
195
|
+
agent: input.agent,
|
|
196
|
+
cwd: input.cwd,
|
|
197
|
+
runtimeSessionId: input.runtimeSessionId,
|
|
198
|
+
});
|
|
199
|
+
assertImportedRuntimeHandleMatches(input.runtimeSessionId, handle);
|
|
200
|
+
return handle;
|
|
201
|
+
}
|
|
202
|
+
const handle = await runtime.ensureSession({
|
|
203
|
+
sessionKey: input.sessionId,
|
|
204
|
+
agent: input.agent,
|
|
205
|
+
mode: "persistent",
|
|
206
|
+
resumeSessionId: input.runtimeSessionId,
|
|
207
|
+
cwd: input.cwd,
|
|
208
|
+
});
|
|
209
|
+
assertImportedRuntimeHandleMatches(input.runtimeSessionId, handle);
|
|
210
|
+
return handle;
|
|
211
|
+
}
|
|
212
|
+
function errorMessage(error) {
|
|
213
|
+
return error instanceof Error ? error.message : String(error);
|
|
214
|
+
}
|
|
215
|
+
function resumeImportMetadata(fallback, runtimeResumeError, runtimeResumeAttempted = false) {
|
|
216
|
+
const metadata = {};
|
|
217
|
+
if (fallback?.importedFromRuntimeSessionId)
|
|
218
|
+
metadata.importedFromRuntimeSessionId = fallback.importedFromRuntimeSessionId;
|
|
219
|
+
if (runtimeResumeError) {
|
|
220
|
+
metadata.runtimeResumeStatus = "unavailable";
|
|
221
|
+
metadata.runtimeResumeError = runtimeResumeError;
|
|
222
|
+
}
|
|
223
|
+
else if (runtimeResumeAttempted && fallback?.importedFromRuntimeSessionId) {
|
|
224
|
+
metadata.runtimeResumeStatus = "available";
|
|
225
|
+
}
|
|
226
|
+
else {
|
|
227
|
+
if (fallback?.runtimeResumeStatus)
|
|
228
|
+
metadata.runtimeResumeStatus = fallback.runtimeResumeStatus;
|
|
229
|
+
if (fallback?.runtimeResumeError)
|
|
230
|
+
metadata.runtimeResumeError = fallback.runtimeResumeError;
|
|
231
|
+
}
|
|
232
|
+
return metadata;
|
|
233
|
+
}
|
|
165
234
|
export async function handleListHistoricalSessions(ws, config, command) {
|
|
166
235
|
if (command.type !== "machine:listHistoricalSessions")
|
|
167
236
|
return;
|
|
@@ -252,7 +321,7 @@ export async function resumeSession(ws, config, sessionId, requestId, fallback,
|
|
|
252
321
|
if (existing) {
|
|
253
322
|
const refreshed = await refreshRuntimeSessionName(existing);
|
|
254
323
|
const nameChanged = refreshed.name !== existing.name;
|
|
255
|
-
const backfill = await backfillHistoricalEvents(ws, config, refreshed,
|
|
324
|
+
const backfill = await backfillHistoricalEvents(ws, config, refreshed, fallback?.importedFromRuntimeSessionId, refreshed.historicalBackfill ?? fallback?.historicalBackfill);
|
|
256
325
|
const resumed = withHistoricalBackfill(refreshed, backfill);
|
|
257
326
|
sessions.set(sessionId, resumed);
|
|
258
327
|
await appendAudit({
|
|
@@ -277,6 +346,7 @@ export async function resumeSession(ws, config, sessionId, requestId, fallback,
|
|
|
277
346
|
...(nameChanged ? { name: resumed.name } : {}),
|
|
278
347
|
encryptedMetadata: await encryptedSessionMetadata(config, sessionId, resumed, {
|
|
279
348
|
resumedAt: new Date().toISOString(),
|
|
349
|
+
...resumeImportMetadata(fallback),
|
|
280
350
|
...historicalBackfillMetadata(backfill),
|
|
281
351
|
...(nameChanged ? { providerName: resumed.name, runtimeNameSyncedAt: new Date().toISOString() } : {}),
|
|
282
352
|
}),
|
|
@@ -289,20 +359,45 @@ export async function resumeSession(ws, config, sessionId, requestId, fallback,
|
|
|
289
359
|
const name = fallback?.name ?? sessionId;
|
|
290
360
|
const resumeSessionId = fallback?.runtime?.backendSessionId || fallback?.runtime?.agentSessionId;
|
|
291
361
|
const resumeHandle = runtimeHandleFromFallback(sessionId, agent, cwd, fallback);
|
|
362
|
+
const historicalRuntimeSessionId = fallback?.importedFromRuntimeSessionId ?? resumeSessionId;
|
|
292
363
|
try {
|
|
293
|
-
const
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
364
|
+
const runtime = getRuntime(cwd);
|
|
365
|
+
let runtimeResumeError;
|
|
366
|
+
let handle;
|
|
367
|
+
try {
|
|
368
|
+
handle = await runtime.ensureSession({
|
|
369
|
+
sessionKey: sessionId,
|
|
370
|
+
agent,
|
|
371
|
+
mode: "persistent",
|
|
372
|
+
resumeSessionId,
|
|
373
|
+
resumeHandle,
|
|
374
|
+
cwd,
|
|
375
|
+
restartableEmptySession: fallback?.emptyRuntimeSession === true,
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
catch (error) {
|
|
379
|
+
if (!fallback?.importedFromRuntimeSessionId)
|
|
380
|
+
throw error;
|
|
381
|
+
runtimeResumeError = errorMessage(error);
|
|
382
|
+
handle = await attachHistoricalRuntimeSession(runtime, {
|
|
383
|
+
sessionId,
|
|
384
|
+
agent,
|
|
385
|
+
cwd,
|
|
386
|
+
runtimeSessionId: fallback.importedFromRuntimeSessionId,
|
|
387
|
+
});
|
|
388
|
+
await appendAudit({
|
|
389
|
+
sessionId,
|
|
390
|
+
machineId: config.machineId,
|
|
391
|
+
actor: "system",
|
|
392
|
+
action: "session.resume.runtime_resume_unavailable",
|
|
393
|
+
summary: `Resumed historical transcript but could not resume runtime session ${fallback.importedFromRuntimeSessionId}`,
|
|
394
|
+
evidence: { requestId, runtimeSessionId: fallback.importedFromRuntimeSessionId, error: runtimeResumeError },
|
|
395
|
+
});
|
|
396
|
+
}
|
|
302
397
|
const state = await refreshRuntimeSessionName({ sessionId, agent, cwd, name, handle });
|
|
303
398
|
sessions.set(sessionId, state);
|
|
304
399
|
const nameChanged = state.name !== name;
|
|
305
|
-
const backfill = await backfillHistoricalEvents(ws, config, state,
|
|
400
|
+
const backfill = await backfillHistoricalEvents(ws, config, state, historicalRuntimeSessionId, fallback?.historicalBackfill);
|
|
306
401
|
const resumed = withHistoricalBackfill(state, backfill);
|
|
307
402
|
sessions.set(sessionId, resumed);
|
|
308
403
|
await appendAudit({
|
|
@@ -327,6 +422,7 @@ export async function resumeSession(ws, config, sessionId, requestId, fallback,
|
|
|
327
422
|
...(nameChanged ? { name: resumed.name } : {}),
|
|
328
423
|
encryptedMetadata: await encryptedSessionMetadata(config, sessionId, resumed, {
|
|
329
424
|
resumedAt: new Date().toISOString(),
|
|
425
|
+
...resumeImportMetadata(fallback, runtimeResumeError, true),
|
|
330
426
|
...historicalBackfillMetadata(backfill),
|
|
331
427
|
...(nameChanged ? { providerName: resumed.name, runtimeNameSyncedAt: new Date().toISOString() } : {}),
|
|
332
428
|
}),
|
|
@@ -37,6 +37,9 @@ export async function sessionFallbackFromCommand(config, command) {
|
|
|
37
37
|
cwd: metadata.cwd || process.cwd(),
|
|
38
38
|
name: metadata.name ?? command.sessionId,
|
|
39
39
|
emptyRuntimeSession: metadata.emptyRuntimeSession === true,
|
|
40
|
+
importedFromRuntimeSessionId: metadata.importedFromRuntimeSessionId,
|
|
41
|
+
runtimeResumeStatus: metadata.runtimeResumeStatus,
|
|
42
|
+
runtimeResumeError: metadata.runtimeResumeError,
|
|
40
43
|
historicalBackfill: historicalBackfillStateFromMetadata(metadata),
|
|
41
44
|
runtime: metadata.runtime,
|
|
42
45
|
};
|
|
@@ -65,6 +65,8 @@ export type SessionMetadata = {
|
|
|
65
65
|
rewoundTo?: string;
|
|
66
66
|
currentHead?: string;
|
|
67
67
|
emptyRuntimeSession?: boolean;
|
|
68
|
+
runtimeResumeStatus?: "available" | "unavailable";
|
|
69
|
+
runtimeResumeError?: string;
|
|
68
70
|
historicalBackfill?: "running" | HistoricalBackfillStatus;
|
|
69
71
|
historicalBackfillCursor?: number;
|
|
70
72
|
historicalBackfillProviderCursor?: string;
|
|
@@ -86,6 +88,9 @@ export type SessionMetadata = {
|
|
|
86
88
|
};
|
|
87
89
|
export type SessionFallback = Pick<SessionState, "agent" | "cwd" | "name"> & {
|
|
88
90
|
emptyRuntimeSession?: boolean;
|
|
91
|
+
importedFromRuntimeSessionId?: string;
|
|
92
|
+
runtimeResumeStatus?: "available" | "unavailable";
|
|
93
|
+
runtimeResumeError?: string;
|
|
89
94
|
historicalBackfill?: HistoricalBackfillState;
|
|
90
95
|
runtime?: SessionMetadata["runtime"];
|
|
91
96
|
};
|
package/apps/daemon/package.json
CHANGED
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cydm/happy-elves",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.50",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@cydm/happy-elves",
|
|
9
|
-
"version": "0.1.0-beta.
|
|
9
|
+
"version": "0.1.0-beta.50",
|
|
10
10
|
"license": "Apache-2.0",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@fastify/cors": "11.2.0",
|
|
@@ -775,9 +775,9 @@
|
|
|
775
775
|
}
|
|
776
776
|
},
|
|
777
777
|
"node_modules/bare-fs": {
|
|
778
|
-
"version": "4.7.
|
|
779
|
-
"resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.7.
|
|
780
|
-
"integrity": "sha512-
|
|
778
|
+
"version": "4.7.3",
|
|
779
|
+
"resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.7.3.tgz",
|
|
780
|
+
"integrity": "sha512-xRgplks8SvcKkdlv2M6Z2LZmRsmqd+x0nXXGXeMEjwdibj1HSDrlnqBRLeYdMvsgCox7Bq0e+DHwfczOfsn6IA==",
|
|
781
781
|
"license": "Apache-2.0",
|
|
782
782
|
"dependencies": {
|
|
783
783
|
"bare-events": "^2.5.4",
|
|
@@ -1552,9 +1552,9 @@
|
|
|
1552
1552
|
}
|
|
1553
1553
|
},
|
|
1554
1554
|
"node_modules/tsx": {
|
|
1555
|
-
"version": "4.22.
|
|
1556
|
-
"resolved": "https://registry.npmjs.org/tsx/-/tsx-4.22.
|
|
1557
|
-
"integrity": "sha512-
|
|
1555
|
+
"version": "4.22.5",
|
|
1556
|
+
"resolved": "https://registry.npmjs.org/tsx/-/tsx-4.22.5.tgz",
|
|
1557
|
+
"integrity": "sha512-F7JnSfPl5ASt6LqwWyUQ3T8BwN3q0eQEbFMYa2iRWaVQmmudo0d7fRmwM4O002gsvW1bs0yBYioutsAjqLJMvQ==",
|
|
1558
1558
|
"license": "MIT",
|
|
1559
1559
|
"dependencies": {
|
|
1560
1560
|
"esbuild": "~0.28.0"
|
package/package.json
CHANGED
|
@@ -58,6 +58,12 @@ export interface HappyElvesRuntime {
|
|
|
58
58
|
permissionMode?: RuntimePermissionMode;
|
|
59
59
|
restartableEmptySession?: boolean;
|
|
60
60
|
}): Promise<RuntimeHandle>;
|
|
61
|
+
attachSession?(input: {
|
|
62
|
+
sessionKey: string;
|
|
63
|
+
agent: string;
|
|
64
|
+
cwd?: string;
|
|
65
|
+
runtimeSessionId: string;
|
|
66
|
+
}): RuntimeHandle | Promise<RuntimeHandle>;
|
|
61
67
|
listHistoricalSessions?(input: {
|
|
62
68
|
agent?: string;
|
|
63
69
|
cwd?: string;
|
|
@@ -474,40 +474,78 @@ async function findCodexRolloutPathBySessionId(directories, sessionId, fileCache
|
|
|
474
474
|
return undefined;
|
|
475
475
|
}
|
|
476
476
|
async function readCodexRolloutMeta(path) {
|
|
477
|
-
const
|
|
478
|
-
const
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
const
|
|
482
|
-
|
|
483
|
-
|
|
477
|
+
const fallbackId = codexRolloutIdFromPath(path);
|
|
478
|
+
const lines = await readInitialLines(path, 64, 256 * 1024);
|
|
479
|
+
let createdAt;
|
|
480
|
+
let cwd;
|
|
481
|
+
for (const line of lines) {
|
|
482
|
+
const record = line ? parseJsonRecord(line) : undefined;
|
|
483
|
+
if (!record)
|
|
484
|
+
continue;
|
|
485
|
+
createdAt ??= timestampMs(record.timestamp);
|
|
486
|
+
const payload = recordFrom(record.payload);
|
|
487
|
+
cwd ??= stringField(payload, "cwd");
|
|
488
|
+
if (record.type !== "session_meta")
|
|
489
|
+
continue;
|
|
490
|
+
const id = stringField(payload, "id") ?? fallbackId;
|
|
491
|
+
if (!id)
|
|
492
|
+
return undefined;
|
|
493
|
+
return {
|
|
494
|
+
id,
|
|
495
|
+
cwd,
|
|
496
|
+
createdAt: timestampMs(payload.timestamp ?? record.timestamp) ?? createdAt,
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
if (!fallbackId)
|
|
484
500
|
return undefined;
|
|
501
|
+
const id = fallbackId;
|
|
485
502
|
return {
|
|
486
503
|
id,
|
|
487
|
-
cwd
|
|
488
|
-
createdAt
|
|
504
|
+
cwd,
|
|
505
|
+
createdAt,
|
|
489
506
|
};
|
|
490
507
|
}
|
|
491
|
-
|
|
492
|
-
|
|
508
|
+
function codexRolloutIdFromPath(path) {
|
|
509
|
+
const match = basename(path).match(/([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.jsonl$/iu);
|
|
510
|
+
return match?.[1];
|
|
511
|
+
}
|
|
512
|
+
async function readInitialLines(path, maxLines, maxBytes) {
|
|
513
|
+
return await new Promise((resolveLines) => {
|
|
493
514
|
const stream = createReadStream(path, { encoding: "utf8" });
|
|
494
515
|
let buffer = "";
|
|
516
|
+
let readBytes = 0;
|
|
517
|
+
const lines = [];
|
|
495
518
|
let settled = false;
|
|
496
|
-
const settle = (
|
|
519
|
+
const settle = () => {
|
|
497
520
|
if (settled)
|
|
498
521
|
return;
|
|
499
522
|
settled = true;
|
|
500
523
|
stream.destroy();
|
|
501
|
-
|
|
524
|
+
resolveLines(lines);
|
|
502
525
|
};
|
|
503
526
|
stream.on("data", (chunk) => {
|
|
527
|
+
readBytes += chunk.length;
|
|
504
528
|
buffer += chunk;
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
529
|
+
let newlineIndex = buffer.indexOf("\n");
|
|
530
|
+
while (newlineIndex >= 0) {
|
|
531
|
+
lines.push(buffer.slice(0, newlineIndex));
|
|
532
|
+
buffer = buffer.slice(newlineIndex + 1);
|
|
533
|
+
if (lines.length >= maxLines)
|
|
534
|
+
return settle();
|
|
535
|
+
newlineIndex = buffer.indexOf("\n");
|
|
536
|
+
}
|
|
537
|
+
if (readBytes >= maxBytes) {
|
|
538
|
+
if (buffer)
|
|
539
|
+
lines.push(buffer);
|
|
540
|
+
settle();
|
|
541
|
+
}
|
|
508
542
|
});
|
|
509
543
|
stream.on("error", () => settle());
|
|
510
|
-
stream.on("end", () =>
|
|
544
|
+
stream.on("end", () => {
|
|
545
|
+
if (buffer)
|
|
546
|
+
lines.push(buffer);
|
|
547
|
+
settle();
|
|
548
|
+
});
|
|
511
549
|
});
|
|
512
550
|
}
|
|
513
551
|
async function readCodexRolloutPreview(path) {
|
|
@@ -81,6 +81,14 @@ export function createCliRuntimeProvider(options) {
|
|
|
81
81
|
assertNotExternalProviderHandle(input.resumeHandle);
|
|
82
82
|
return (await sessions.ensure(input)).handle;
|
|
83
83
|
},
|
|
84
|
+
attachSession(input) {
|
|
85
|
+
const cwd = input.cwd || options.cwd;
|
|
86
|
+
if (input.agent === "codex")
|
|
87
|
+
return sessions.createLoadedCodex(input.sessionKey, cwd, input.runtimeSessionId).handle;
|
|
88
|
+
if (input.agent === "claude")
|
|
89
|
+
return sessions.createLoadedClaude(input.sessionKey, cwd, input.runtimeSessionId).handle;
|
|
90
|
+
throw new Error(`Unsupported CLI runtime agent: ${input.agent}`);
|
|
91
|
+
},
|
|
84
92
|
async listHistoricalSessions(input) {
|
|
85
93
|
return (await listHistoricalSessionPage(input)).sessions;
|
|
86
94
|
},
|
|
@@ -17,8 +17,14 @@ export class CliSessionStore {
|
|
|
17
17
|
}
|
|
18
18
|
async ensure(input) {
|
|
19
19
|
const existing = this.sessions.get(input.sessionKey);
|
|
20
|
-
if (existing)
|
|
20
|
+
if (existing) {
|
|
21
|
+
if (existing.codex && existing.codexEnsured !== true) {
|
|
22
|
+
const threadId = await existing.codex.ensure(input.permissionMode);
|
|
23
|
+
existing.handle.backendSessionId = threadId;
|
|
24
|
+
existing.codexEnsured = true;
|
|
25
|
+
}
|
|
21
26
|
return existing;
|
|
27
|
+
}
|
|
22
28
|
const cwd = input.cwd ?? this.input.cwd;
|
|
23
29
|
const agent = cliAgent(input.agent);
|
|
24
30
|
if (agent === "codex") {
|
|
@@ -71,6 +77,7 @@ export class CliSessionStore {
|
|
|
71
77
|
const threadId = await codex.ensure(permissionMode);
|
|
72
78
|
session.handle.backendSessionId = threadId;
|
|
73
79
|
session.codex = codex;
|
|
80
|
+
session.codexEnsured = true;
|
|
74
81
|
this.sessions.set(sessionKey, session);
|
|
75
82
|
return session;
|
|
76
83
|
}
|