@cydm/happy-elves 0.1.0-beta.277 → 0.1.0-beta.279
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/package.json +1 -1
- package/apps/daemon/dist/session/lifecycle.d.ts +3 -1
- package/apps/daemon/dist/session/lifecycle.js +9 -5
- package/apps/daemon/package.json +1 -1
- package/apps/relay/dist/controller-handlers.js +11 -11
- package/apps/relay/dist/machine-handlers.js +51 -1
- package/apps/relay/dist/relay-context.js +26 -1
- package/package.json +1 -1
- package/packages/client/dist/client.d.ts +3 -1
- package/packages/client/dist/client.js +7 -2
- package/packages/shared/dist/protocol.d.ts +2 -0
- package/packages/shared/dist/protocol.js +2 -0
package/apps/cli/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type MachineCommand } from "../../../../packages/shared/dist/index.js";
|
|
2
2
|
import type { DaemonConfig, HistoricalBackfillStatus, SessionFallback, SessionState } from "../types.js";
|
|
3
3
|
import { type HistoricalSessionHead } from "./events.js";
|
|
4
|
+
type HistoricalBackfillDirection = "latest" | "older";
|
|
4
5
|
export type HistoricalBackfillResult = {
|
|
5
6
|
availableEvents: number;
|
|
6
7
|
cursor: number;
|
|
@@ -16,7 +17,7 @@ export type HistoricalBackfillResult = {
|
|
|
16
17
|
export declare function handleCreateSession(ws: WebSocket, config: DaemonConfig, command: MachineCommand): Promise<void>;
|
|
17
18
|
export declare function handleImportHistoricalSession(ws: WebSocket, config: DaemonConfig, command: MachineCommand): Promise<void>;
|
|
18
19
|
export declare function handleListHistoricalSessions(ws: WebSocket, config: DaemonConfig, command: MachineCommand): Promise<void>;
|
|
19
|
-
export declare function resumeSession(ws: WebSocket, config: DaemonConfig, sessionId: string, requestId: string, fallback?: SessionFallback, announceSuccess?: boolean): Promise<SessionState | null>;
|
|
20
|
+
export declare function resumeSession(ws: WebSocket, config: DaemonConfig, sessionId: string, requestId: string, fallback?: SessionFallback, announceSuccess?: boolean, syncDirection?: HistoricalBackfillDirection): Promise<SessionState | null>;
|
|
20
21
|
export declare function usesAuthoritativeTranscript(session: SessionState): boolean;
|
|
21
22
|
export declare function replaceAuthoritativeTranscript(ws: WebSocket, config: DaemonConfig, session: SessionState, runtimeSessionId?: string | undefined, input?: {
|
|
22
23
|
currentHead?: string;
|
|
@@ -28,3 +29,4 @@ export declare function replaceAuthoritativeTranscript(ws: WebSocket, config: Da
|
|
|
28
29
|
}): Promise<HistoricalBackfillResult>;
|
|
29
30
|
export declare function handleResume(ws: WebSocket, config: DaemonConfig, command: MachineCommand): Promise<void>;
|
|
30
31
|
export declare function handleClose(ws: WebSocket, config: DaemonConfig, command: MachineCommand): Promise<void>;
|
|
32
|
+
export {};
|
|
@@ -256,12 +256,14 @@ function historicalSessionPage(value) {
|
|
|
256
256
|
return { sessions: value };
|
|
257
257
|
return value;
|
|
258
258
|
}
|
|
259
|
-
export async function resumeSession(ws, config, sessionId, requestId, fallback, announceSuccess = true) {
|
|
259
|
+
export async function resumeSession(ws, config, sessionId, requestId, fallback, announceSuccess = true, syncDirection = "latest") {
|
|
260
260
|
const existing = sessions.get(sessionId);
|
|
261
261
|
if (existing) {
|
|
262
262
|
const refreshed = await refreshRuntimeSessionName(existing);
|
|
263
263
|
const nameChanged = refreshed.name !== existing.name;
|
|
264
|
-
const backfill = await backfillHistoricalEvents(ws, config, refreshed, undefined, refreshed.historicalBackfill ?? fallback?.historicalBackfill
|
|
264
|
+
const backfill = await backfillHistoricalEvents(ws, config, refreshed, undefined, refreshed.historicalBackfill ?? fallback?.historicalBackfill, {
|
|
265
|
+
direction: syncDirection,
|
|
266
|
+
});
|
|
265
267
|
const resumed = withHistoricalBackfill(refreshed, backfill);
|
|
266
268
|
sessions.set(sessionId, resumed);
|
|
267
269
|
await appendAudit({
|
|
@@ -311,7 +313,9 @@ export async function resumeSession(ws, config, sessionId, requestId, fallback,
|
|
|
311
313
|
const state = await refreshRuntimeSessionName({ sessionId, agent, cwd, name, handle });
|
|
312
314
|
sessions.set(sessionId, state);
|
|
313
315
|
const nameChanged = state.name !== name;
|
|
314
|
-
const backfill = await backfillHistoricalEvents(ws, config, state, resumeSessionId, fallback?.historicalBackfill
|
|
316
|
+
const backfill = await backfillHistoricalEvents(ws, config, state, resumeSessionId, fallback?.historicalBackfill, {
|
|
317
|
+
direction: syncDirection,
|
|
318
|
+
});
|
|
315
319
|
const resumed = withHistoricalBackfill(state, backfill);
|
|
316
320
|
sessions.set(sessionId, resumed);
|
|
317
321
|
await appendAudit({
|
|
@@ -558,7 +562,7 @@ async function backfillHistoricalEvents(ws, config, session, runtimeSessionId =
|
|
|
558
562
|
if (!runtimeSessionId)
|
|
559
563
|
return completedBackfillResult(0);
|
|
560
564
|
if (usesAuthoritativeTranscript(session)) {
|
|
561
|
-
if (previousBackfill?.status === "partial" && previousBackfill.providerCursor) {
|
|
565
|
+
if ((options.direction ?? "latest") === "older" && previousBackfill?.status === "partial" && previousBackfill.providerCursor) {
|
|
562
566
|
return await prependAuthoritativeTranscript(ws, config, session, runtimeSessionId, previousBackfill, { strict: options.strict });
|
|
563
567
|
}
|
|
564
568
|
return await replaceAuthoritativeTranscript(ws, config, session, runtimeSessionId, { strict: options.strict });
|
|
@@ -731,7 +735,7 @@ export async function handleResume(ws, config, command) {
|
|
|
731
735
|
return;
|
|
732
736
|
if (!claimCommandRequest(ws, command.requestId))
|
|
733
737
|
return;
|
|
734
|
-
await resumeSession(ws, config, command.sessionId, command.requestId, await sessionFallbackFromCommand(config, command));
|
|
738
|
+
await resumeSession(ws, config, command.sessionId, command.requestId, await sessionFallbackFromCommand(config, command), true, command.syncDirection ?? "latest");
|
|
735
739
|
}
|
|
736
740
|
export async function handleClose(ws, config, command) {
|
|
737
741
|
if (command.type !== "machine:close")
|
package/apps/daemon/package.json
CHANGED
|
@@ -354,6 +354,7 @@ export function handleControllerMessage(context, connection, message) {
|
|
|
354
354
|
type: "machine:resume",
|
|
355
355
|
requestId: message.requestId,
|
|
356
356
|
sessionId: message.sessionId,
|
|
357
|
+
...(message.syncDirection ? { syncDirection: message.syncDirection } : {}),
|
|
357
358
|
encryptedMetadata: sessionRow.encrypted_metadata ? parseJson(sessionRow.encrypted_metadata, undefined) : undefined,
|
|
358
359
|
});
|
|
359
360
|
return;
|
|
@@ -784,21 +785,20 @@ export function handleControllerMessage(context, connection, message) {
|
|
|
784
785
|
sendAck(connection, { type: "server:ack", requestId: message.requestId, ok: true });
|
|
785
786
|
return;
|
|
786
787
|
}
|
|
788
|
+
const closedSession = setSessionStatus(sessionRow.id, "closed");
|
|
789
|
+
finishSessionClaim({
|
|
790
|
+
accountId: connection.accountId,
|
|
791
|
+
sessionId: sessionRow.id,
|
|
792
|
+
terminalStatus: "cancelled",
|
|
793
|
+
errorCode: "SESSION_CLOSED",
|
|
794
|
+
});
|
|
795
|
+
if (closedSession)
|
|
796
|
+
broadcastControllers(connection.accountId, { type: "server:session", session: closedSession });
|
|
797
|
+
sendAck(connection, { type: "server:ack", requestId: message.requestId, ok: true });
|
|
787
798
|
const machineConnection = connectedMachine(connection.accountId, sessionRow.machine_id);
|
|
788
799
|
if (!machineConnection) {
|
|
789
|
-
const session = setSessionStatus(sessionRow.id, "closed");
|
|
790
|
-
finishSessionClaim({
|
|
791
|
-
accountId: connection.accountId,
|
|
792
|
-
sessionId: sessionRow.id,
|
|
793
|
-
terminalStatus: "cancelled",
|
|
794
|
-
errorCode: "SESSION_CLOSED",
|
|
795
|
-
});
|
|
796
|
-
if (session)
|
|
797
|
-
broadcastControllers(connection.accountId, { type: "server:session", session });
|
|
798
|
-
sendAck(connection, { type: "server:ack", requestId: message.requestId, ok: true });
|
|
799
800
|
return;
|
|
800
801
|
}
|
|
801
|
-
markCommandPending(connection.accountId, sessionRow.machine_id, message.requestId);
|
|
802
802
|
send(machineConnection.socket, {
|
|
803
803
|
type: "machine:close",
|
|
804
804
|
requestId: message.requestId,
|
|
@@ -223,7 +223,7 @@ export function handleMachineMessage(context, connection, message) {
|
|
|
223
223
|
current_head = COALESCE(?, current_head),
|
|
224
224
|
last_turn_id = COALESCE(?, last_turn_id),
|
|
225
225
|
updated_at = ?
|
|
226
|
-
WHERE id = ? AND account_id = ? AND machine_id =
|
|
226
|
+
WHERE id = ? AND account_id = ? AND machine_id = ? AND status != 'closed'`).run(message.result.terminalStatus, message.result.currentHead ?? null, message.result.lastTurnId ?? message.result.turnId ?? null, ts, message.result.sessionId, connection.accountId, connection.machineId);
|
|
227
227
|
finishSessionClaim({
|
|
228
228
|
accountId: connection.accountId,
|
|
229
229
|
sessionId: message.result.sessionId,
|
|
@@ -237,6 +237,30 @@ export function handleMachineMessage(context, connection, message) {
|
|
|
237
237
|
if (row)
|
|
238
238
|
broadcastControllers(connection.accountId, { type: "server:session", session: sessionSnapshot(row) });
|
|
239
239
|
}
|
|
240
|
+
if (message.result.status === "unknown" || message.result.status === "not_found" || message.result.status === "uninspectable") {
|
|
241
|
+
const claim = activePromptClaim(db, connection.accountId, message.result.sessionId, connection.machineId);
|
|
242
|
+
const turnId = message.result.turnId ?? claim?.turn_id;
|
|
243
|
+
const requestId = message.result.requestId ?? claim?.request_id;
|
|
244
|
+
const claimMatches = Boolean(claim) &&
|
|
245
|
+
(!message.result.requestId || message.result.requestId === claim?.request_id) &&
|
|
246
|
+
(!message.result.turnId || message.result.turnId === claim?.turn_id);
|
|
247
|
+
if (claimMatches && turnId) {
|
|
248
|
+
db.prepare(`UPDATE sessions
|
|
249
|
+
SET status = 'failed',
|
|
250
|
+
current_head = COALESCE(current_head, ?),
|
|
251
|
+
last_turn_id = ?,
|
|
252
|
+
updated_at = ?
|
|
253
|
+
WHERE id = ? AND account_id = ? AND machine_id = ? AND status != 'closed'`).run(turnId, turnId, ts, message.result.sessionId, connection.accountId, connection.machineId);
|
|
254
|
+
if (requestId) {
|
|
255
|
+
db.prepare(`UPDATE relay_admissions
|
|
256
|
+
SET last_error_code = COALESCE(last_error_code, ?), updated_at = ?
|
|
257
|
+
WHERE account_id = ? AND session_id = ? AND request_id = ?`).run(message.result.status === "not_found" ? "EXECUTION_NOT_FOUND" : "EXECUTION_UNKNOWN", ts, connection.accountId, message.result.sessionId, requestId);
|
|
258
|
+
}
|
|
259
|
+
const row = db.prepare("SELECT * FROM sessions WHERE id = ?").get(message.result.sessionId);
|
|
260
|
+
if (row)
|
|
261
|
+
broadcastControllers(connection.accountId, { type: "server:session", session: sessionSnapshot(row) });
|
|
262
|
+
}
|
|
263
|
+
}
|
|
240
264
|
rememberCommandResponse(connection.accountId, message.requestId, response);
|
|
241
265
|
clearCommandPending(connection.accountId, message.requestId);
|
|
242
266
|
broadcastControllers(connection.accountId, response);
|
|
@@ -371,6 +395,10 @@ export function handleMachineMessage(context, connection, message) {
|
|
|
371
395
|
acknowledgeMachineMessage();
|
|
372
396
|
return;
|
|
373
397
|
}
|
|
398
|
+
if (sessionRow.status === "closed") {
|
|
399
|
+
acknowledgeMachineMessage();
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
374
402
|
const replacementBasis = message.basis;
|
|
375
403
|
const replacementBasisEvent = replacementBasis
|
|
376
404
|
? message.events.find((event) => event.messageId === replacementBasis.eventMessageId &&
|
|
@@ -508,6 +536,10 @@ export function handleMachineMessage(context, connection, message) {
|
|
|
508
536
|
acknowledgeMachineMessage();
|
|
509
537
|
return;
|
|
510
538
|
}
|
|
539
|
+
if (sessionRow.status === "closed") {
|
|
540
|
+
acknowledgeMachineMessage();
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
511
543
|
const ts = now();
|
|
512
544
|
const inserted = [];
|
|
513
545
|
db.exec("BEGIN");
|
|
@@ -570,6 +602,10 @@ export function handleMachineMessage(context, connection, message) {
|
|
|
570
602
|
const sessionBefore = db
|
|
571
603
|
.prepare("SELECT * FROM sessions WHERE id = ? AND account_id = ? AND machine_id = ?")
|
|
572
604
|
.get(message.sessionId, connection.accountId, connection.machineId);
|
|
605
|
+
if (sessionBefore?.status === "closed") {
|
|
606
|
+
acknowledgeMachineMessage();
|
|
607
|
+
return;
|
|
608
|
+
}
|
|
573
609
|
const activeClaim = sessionBefore
|
|
574
610
|
? activePromptClaim(db, connection.accountId, message.sessionId, connection.machineId)
|
|
575
611
|
: undefined;
|
|
@@ -709,6 +745,18 @@ export function handleMachineMessage(context, connection, message) {
|
|
|
709
745
|
acknowledgeMachineMessage();
|
|
710
746
|
return;
|
|
711
747
|
}
|
|
748
|
+
if (sessionRow.status === "closed") {
|
|
749
|
+
finishSessionClaim({
|
|
750
|
+
accountId: connection.accountId,
|
|
751
|
+
sessionId: message.sessionId,
|
|
752
|
+
turnId: message.turnId,
|
|
753
|
+
terminalStatus: status,
|
|
754
|
+
...(message.error ? { errorCode: "TURN_ERROR" } : {}),
|
|
755
|
+
finishedAt: ts,
|
|
756
|
+
});
|
|
757
|
+
acknowledgeMachineMessage();
|
|
758
|
+
return;
|
|
759
|
+
}
|
|
712
760
|
const currentOrder = sessionHeadBasisOrder(db, sessionRow);
|
|
713
761
|
const basisComparison = basisOrder ? compareHeadBasisOrder(basisOrder, currentOrder) : null;
|
|
714
762
|
const isRuntimeAliasForCurrentRequest = Boolean(runtimeBasisEvent && runtimeTurnId !== message.turnId && (sessionRow.last_turn_id === message.turnId || requestBasisEvent));
|
|
@@ -860,6 +908,8 @@ function applySessionHeadAdvance(db, input, ts) {
|
|
|
860
908
|
.get(input.sessionId, input.accountId, input.machineId);
|
|
861
909
|
if (!sessionRow)
|
|
862
910
|
return { status: "session-not-found" };
|
|
911
|
+
if (sessionRow.status === "closed")
|
|
912
|
+
return { status: "noop", session: sessionRow };
|
|
863
913
|
const basisEvent = resolveHeadAdvanceBasisEvent(db, input.accountId, input.sessionId, input.machineId, input.basis);
|
|
864
914
|
if (!basisEvent)
|
|
865
915
|
return { status: "missing", session: sessionRow };
|
|
@@ -366,6 +366,29 @@ export function createRelayContext(options) {
|
|
|
366
366
|
if (!machine)
|
|
367
367
|
return "queued";
|
|
368
368
|
const command = JSON.parse(row.message);
|
|
369
|
+
if (command.type === "machine:prompt") {
|
|
370
|
+
const session = options.db
|
|
371
|
+
.prepare("SELECT status FROM sessions WHERE account_id = ? AND id = ? AND machine_id = ?")
|
|
372
|
+
.get(accountId, command.sessionId, row.machine_id);
|
|
373
|
+
if (session?.status === "closed") {
|
|
374
|
+
const ts = now();
|
|
375
|
+
options.db
|
|
376
|
+
.prepare(`UPDATE relay_dispatch_outbox
|
|
377
|
+
SET state = 'delivered', delivered_at = ?, updated_at = ?
|
|
378
|
+
WHERE account_id = ? AND request_id = ? AND state = 'queued'`)
|
|
379
|
+
.run(ts, ts, accountId, requestId);
|
|
380
|
+
finishSessionClaim({
|
|
381
|
+
accountId,
|
|
382
|
+
sessionId: command.sessionId,
|
|
383
|
+
requestId,
|
|
384
|
+
turnId: command.turnId,
|
|
385
|
+
terminalStatus: "cancelled",
|
|
386
|
+
errorCode: "SESSION_CLOSED",
|
|
387
|
+
finishedAt: ts,
|
|
388
|
+
});
|
|
389
|
+
return "queued";
|
|
390
|
+
}
|
|
391
|
+
}
|
|
369
392
|
send(machine.socket, command);
|
|
370
393
|
const ts = now();
|
|
371
394
|
options.db
|
|
@@ -635,7 +658,9 @@ export function createRelayContext(options) {
|
|
|
635
658
|
}
|
|
636
659
|
function setSessionStatus(sessionId, status) {
|
|
637
660
|
const ts = now();
|
|
638
|
-
options.db
|
|
661
|
+
options.db
|
|
662
|
+
.prepare("UPDATE sessions SET status = ?, updated_at = ? WHERE id = ? AND (status != 'closed' OR ? = 'closed')")
|
|
663
|
+
.run(status, ts, sessionId, status);
|
|
639
664
|
const row = options.db.prepare("SELECT * FROM sessions WHERE id = ?").get(sessionId);
|
|
640
665
|
return row ? sessionSnapshot(row) : undefined;
|
|
641
666
|
}
|
package/package.json
CHANGED
|
@@ -70,7 +70,9 @@ export declare class ControllerClient {
|
|
|
70
70
|
collectPage(sessionId: string, input?: CollectInput): Promise<DecodedSessionEventsPage>;
|
|
71
71
|
repairSessionHead(sessionId: string, input: RepairSessionHeadInput): Promise<RepairSessionHeadResult>;
|
|
72
72
|
wait(sessionId: string, input?: WaitInput): Promise<SessionSnapshot>;
|
|
73
|
-
resume(sessionId: string
|
|
73
|
+
resume(sessionId: string, options?: {
|
|
74
|
+
syncDirection?: "latest" | "older";
|
|
75
|
+
}): Promise<CommandResult>;
|
|
74
76
|
listHistoricalSessionsPage(input: HistoricalSessionsInput): Promise<HistoricalSessionListPage>;
|
|
75
77
|
listHistoricalSessions(input: HistoricalSessionsInput): Promise<HistoricalSessionSnapshot[]>;
|
|
76
78
|
listDirectory(input: DirectoryInput): Promise<DirectoryListing>;
|
|
@@ -302,10 +302,15 @@ export class ControllerClient {
|
|
|
302
302
|
await sleep(intervalMs);
|
|
303
303
|
}
|
|
304
304
|
}
|
|
305
|
-
async resume(sessionId) {
|
|
305
|
+
async resume(sessionId, options = {}) {
|
|
306
306
|
const checkedSessionId = requireNonEmpty(sessionId, "sessionId");
|
|
307
307
|
const requestId = randomId("req");
|
|
308
|
-
return await this.sendCommand({
|
|
308
|
+
return await this.sendCommand({
|
|
309
|
+
type: "controller:resume",
|
|
310
|
+
requestId,
|
|
311
|
+
sessionId: checkedSessionId,
|
|
312
|
+
...(options.syncDirection ? { syncDirection: options.syncDirection } : {}),
|
|
313
|
+
});
|
|
309
314
|
}
|
|
310
315
|
async listHistoricalSessionsPage(input) {
|
|
311
316
|
const machineId = requireNonEmpty(input.machineId, "machineId");
|
|
@@ -50,6 +50,7 @@ export type ControllerClientMessage = {
|
|
|
50
50
|
type: "controller:resume";
|
|
51
51
|
requestId: string;
|
|
52
52
|
sessionId: string;
|
|
53
|
+
syncDirection?: "latest" | "older";
|
|
53
54
|
} | {
|
|
54
55
|
type: "controller:listHistoricalSessions";
|
|
55
56
|
requestId: string;
|
|
@@ -384,6 +385,7 @@ export type MachineCommand = {
|
|
|
384
385
|
type: "machine:resume";
|
|
385
386
|
requestId: string;
|
|
386
387
|
sessionId: string;
|
|
388
|
+
syncDirection?: "latest" | "older";
|
|
387
389
|
encryptedMetadata?: EncryptedEnvelope;
|
|
388
390
|
} | {
|
|
389
391
|
type: "machine:listHistoricalSessions";
|
|
@@ -57,6 +57,7 @@ const controllerMessageSchema = z.discriminatedUnion("type", [
|
|
|
57
57
|
type: z.literal("controller:resume"),
|
|
58
58
|
requestId: z.string().min(1),
|
|
59
59
|
sessionId: z.string().min(1),
|
|
60
|
+
syncDirection: z.enum(["latest", "older"]).optional(),
|
|
60
61
|
}),
|
|
61
62
|
z.object({
|
|
62
63
|
type: z.literal("controller:listHistoricalSessions"),
|
|
@@ -458,6 +459,7 @@ const machineCommandSchema = z.discriminatedUnion("type", [
|
|
|
458
459
|
type: z.literal("machine:resume"),
|
|
459
460
|
requestId: z.string().min(1),
|
|
460
461
|
sessionId: z.string().min(1),
|
|
462
|
+
syncDirection: z.enum(["latest", "older"]).optional(),
|
|
461
463
|
encryptedMetadata: encryptedEnvelopeSchema.optional(),
|
|
462
464
|
}),
|
|
463
465
|
z.object({
|