@cydm/happy-elves 0.1.0-beta.41 → 0.1.0-beta.42
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 +3 -3
- package/apps/daemon/dist/session/events.d.ts +1 -1
- package/apps/daemon/dist/session/events.js +2 -1
- package/apps/daemon/dist/session/lifecycle.js +1 -1
- package/apps/daemon/dist/session/prompt.js +1 -1
- package/apps/daemon/package.json +1 -1
- package/apps/relay/dist/http-routes.js +21 -10
- package/apps/relay/dist/http-schemas.d.ts +5 -0
- package/apps/relay/dist/http-schemas.js +1 -0
- package/apps/relay/dist/machine-handlers.js +19 -8
- package/package.json +1 -1
- package/packages/client/dist/client.js +2 -0
- package/packages/client/dist/parsers.js +1 -0
- package/packages/client/dist/types.d.ts +2 -0
- package/packages/shared/dist/protocol.d.ts +1 -0
- package/packages/shared/dist/protocol.js +1 -0
|
@@ -125,6 +125,7 @@ function repairHeadInputForLatestCompletedEvent(events) {
|
|
|
125
125
|
return {
|
|
126
126
|
currentHead,
|
|
127
127
|
lastTurnId,
|
|
128
|
+
status: "completed",
|
|
128
129
|
basis: {
|
|
129
130
|
turnId: doneEvent.turnId,
|
|
130
131
|
eventMessageId: doneEvent.messageId,
|
|
@@ -487,9 +488,6 @@ export async function handleSession({ domain, action, positional, flags }) {
|
|
|
487
488
|
if (!initialSession)
|
|
488
489
|
throw new CliError("Session not found", "SESSION_NOT_FOUND");
|
|
489
490
|
let session = initialSession;
|
|
490
|
-
if (flags["repair-head"] === true && session.status === "running") {
|
|
491
|
-
throw new CliError("Cannot repair a running session head", "SESSION_HEAD_REPAIR_RUNNING");
|
|
492
|
-
}
|
|
493
491
|
const machine = snapshot.machines.find((item) => item.id === session.machineId);
|
|
494
492
|
const metadata = await client.decodeSessionMetadata(session);
|
|
495
493
|
const latestPage = await client.collectPage(session.id, { limit });
|
|
@@ -573,7 +571,9 @@ export async function handleSession({ domain, action, positional, flags }) {
|
|
|
573
571
|
},
|
|
574
572
|
repair: repair ? {
|
|
575
573
|
advanced: repair.advanced,
|
|
574
|
+
settled: repair.settled,
|
|
576
575
|
basis: repair.basis,
|
|
576
|
+
status: repair.session.status,
|
|
577
577
|
currentHead: repair.session.currentHead,
|
|
578
578
|
lastTurnId: repair.session.lastTurnId,
|
|
579
579
|
reason: repair.reason,
|
|
@@ -4,7 +4,7 @@ import type { DaemonConfig } from "../types.js";
|
|
|
4
4
|
export declare function emitEvent(ws: WebSocket, config: DaemonConfig, sessionId: string, turnId: string, payload: SessionEventPayload): Promise<void>;
|
|
5
5
|
export declare function emitHistoricalEvents(ws: WebSocket, config: DaemonConfig, sessionId: string, events: RuntimeHistoricalEvent[], startIndex?: number): Promise<void>;
|
|
6
6
|
export declare function emitRuntimeTailEvents(ws: WebSocket, config: DaemonConfig, sessionId: string, events: RuntimeHistoricalEvent[], startIndex?: number): Promise<void>;
|
|
7
|
-
export declare function emitSessionHeadAdvanced(ws: WebSocket, sessionId: string, head: HistoricalSessionHead, encryptedMetadata?: EncryptedEnvelope): Promise<void>;
|
|
7
|
+
export declare function emitSessionHeadAdvanced(ws: WebSocket, sessionId: string, head: HistoricalSessionHead, encryptedMetadata?: EncryptedEnvelope, status?: "completed" | "cancelled" | "failed"): Promise<void>;
|
|
8
8
|
export declare function emitTranscriptReplacement(ws: WebSocket, config: DaemonConfig, sessionId: string, events: RuntimeHistoricalEvent[], input?: {
|
|
9
9
|
requestId?: string;
|
|
10
10
|
currentHead?: string;
|
|
@@ -32,13 +32,14 @@ export async function emitRuntimeTailEvents(ws, config, sessionId, events, start
|
|
|
32
32
|
}, ws);
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
|
-
export async function emitSessionHeadAdvanced(ws, sessionId, head, encryptedMetadata) {
|
|
35
|
+
export async function emitSessionHeadAdvanced(ws, sessionId, head, encryptedMetadata, status) {
|
|
36
36
|
await sendReliable({
|
|
37
37
|
type: "machine:sessionHeadAdvanced",
|
|
38
38
|
sessionId,
|
|
39
39
|
currentHead: head.currentHead,
|
|
40
40
|
lastTurnId: head.lastTurnId,
|
|
41
41
|
basis: head.basis,
|
|
42
|
+
status,
|
|
42
43
|
encryptedMetadata,
|
|
43
44
|
}, ws);
|
|
44
45
|
}
|
|
@@ -605,7 +605,7 @@ async function backfillHistoricalEvents(ws, config, session, runtimeSessionId =
|
|
|
605
605
|
await emitSessionHeadAdvanced(ws, session.sessionId, head, await encryptedSessionMetadata(config, session.sessionId, backfilledSession, {
|
|
606
606
|
...historicalBackfillMetadata(result),
|
|
607
607
|
historicalBackfilledAt: new Date().toISOString(),
|
|
608
|
-
}));
|
|
608
|
+
}), "completed");
|
|
609
609
|
}
|
|
610
610
|
return result;
|
|
611
611
|
}
|
|
@@ -121,7 +121,7 @@ export async function handlePrompt(ws, config, command) {
|
|
|
121
121
|
currentHead: result.currentHead,
|
|
122
122
|
lastTurnId: result.lastTurnId,
|
|
123
123
|
});
|
|
124
|
-
await refreshAuthoritativeTranscriptAfterTurn(ws, config, session, command.requestId, result.currentHead, result.lastTurnId);
|
|
124
|
+
await refreshAuthoritativeTranscriptAfterTurn(ws, config, session, command.requestId, result.currentHead, result.lastTurnId, result.status);
|
|
125
125
|
releasePromptTurn();
|
|
126
126
|
await sendReliable({
|
|
127
127
|
type: "machine:turnDone",
|
package/apps/daemon/package.json
CHANGED
|
@@ -249,7 +249,7 @@ export function registerHttpRoutes(app, context, controllerInviteTtlMs) {
|
|
|
249
249
|
.get(token.account_id, params.sessionId);
|
|
250
250
|
if (!session)
|
|
251
251
|
throw new HttpError(404, "SESSION_NOT_FOUND", "Session not found");
|
|
252
|
-
if (session.status === "running") {
|
|
252
|
+
if (session.status === "running" && !body.status) {
|
|
253
253
|
throw new HttpError(409, "SESSION_HEAD_REPAIR_RUNNING", "Cannot repair a running session head");
|
|
254
254
|
}
|
|
255
255
|
const basisEvent = context.db
|
|
@@ -265,18 +265,28 @@ export function registerHttpRoutes(app, context, controllerInviteTtlMs) {
|
|
|
265
265
|
const currentOrder = sessionHeadBasisOrderForRow(context.db, session);
|
|
266
266
|
const rewriteFenced = typeof session.head_basis_message_id === "string" &&
|
|
267
267
|
session.head_basis_message_id.startsWith("rewrite:");
|
|
268
|
-
const
|
|
269
|
-
|
|
268
|
+
const comparison = compareHeadOrders(nextOrder, currentOrder);
|
|
269
|
+
const currentHeadAlreadySettled = session.current_head === `event:${basisEvent.id}` ||
|
|
270
|
+
session.current_head === body.currentHead ||
|
|
271
|
+
session.last_turn_id === body.lastTurnId;
|
|
272
|
+
const advanced = !rewriteFenced && comparison > 0;
|
|
273
|
+
const statusChanged = !rewriteFenced &&
|
|
274
|
+
body.status !== undefined &&
|
|
275
|
+
session.status === "running" &&
|
|
276
|
+
comparison === 0 &&
|
|
277
|
+
currentHeadAlreadySettled;
|
|
278
|
+
if (advanced || statusChanged) {
|
|
270
279
|
const ts = context.now();
|
|
271
280
|
context.db.prepare(`UPDATE sessions
|
|
272
|
-
SET
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
281
|
+
SET status = COALESCE(?, status),
|
|
282
|
+
current_head = COALESCE(?, current_head),
|
|
283
|
+
last_turn_id = COALESCE(?, last_turn_id),
|
|
284
|
+
head_basis_event_id = COALESCE(?, head_basis_event_id),
|
|
285
|
+
head_basis_message_id = COALESCE(?, head_basis_message_id),
|
|
286
|
+
head_basis_logical_time = COALESCE(?, head_basis_logical_time),
|
|
287
|
+
head_basis_turn_seq = COALESCE(?, head_basis_turn_seq),
|
|
278
288
|
updated_at = ?
|
|
279
|
-
WHERE id = ? AND account_id = ?`).run(body.currentHead, body.lastTurnId, basisEvent.id, basisEvent.message_id, nextOrder.time, nextOrder.seq, ts, params.sessionId, token.account_id);
|
|
289
|
+
WHERE id = ? AND account_id = ?`).run(body.status && session.status === "running" ? body.status : null, advanced ? body.currentHead : null, advanced ? body.lastTurnId : null, advanced ? basisEvent.id : null, advanced ? basisEvent.message_id : null, advanced ? nextOrder.time : null, advanced ? nextOrder.seq : null, ts, params.sessionId, token.account_id);
|
|
280
290
|
}
|
|
281
291
|
const row = context.db
|
|
282
292
|
.prepare("SELECT * FROM sessions WHERE account_id = ? AND id = ?")
|
|
@@ -294,6 +304,7 @@ export function registerHttpRoutes(app, context, controllerInviteTtlMs) {
|
|
|
294
304
|
logicalTime: nextOrder.time,
|
|
295
305
|
turnSeq: nextOrder.seq,
|
|
296
306
|
},
|
|
307
|
+
...(statusChanged ? { settled: true } : {}),
|
|
297
308
|
...(rewriteFenced ? { reason: "rewrite-fenced" } : {}),
|
|
298
309
|
};
|
|
299
310
|
});
|
|
@@ -49,6 +49,11 @@ export declare const sessionEventsQuerySchema: z.ZodObject<{
|
|
|
49
49
|
export declare const sessionHeadAdvanceSchema: z.ZodObject<{
|
|
50
50
|
currentHead: z.ZodString;
|
|
51
51
|
lastTurnId: z.ZodString;
|
|
52
|
+
status: z.ZodOptional<z.ZodEnum<{
|
|
53
|
+
completed: "completed";
|
|
54
|
+
failed: "failed";
|
|
55
|
+
cancelled: "cancelled";
|
|
56
|
+
}>>;
|
|
52
57
|
basis: z.ZodObject<{
|
|
53
58
|
turnId: z.ZodString;
|
|
54
59
|
eventMessageId: z.ZodString;
|
|
@@ -56,6 +56,7 @@ export const sessionEventsQuerySchema = z.object({
|
|
|
56
56
|
export const sessionHeadAdvanceSchema = z.object({
|
|
57
57
|
currentHead: z.string().min(1),
|
|
58
58
|
lastTurnId: z.string().min(1),
|
|
59
|
+
status: z.enum(["completed", "cancelled", "failed"]).optional(),
|
|
59
60
|
basis: z.object({
|
|
60
61
|
turnId: z.string().min(1),
|
|
61
62
|
eventMessageId: z.string().min(1),
|
|
@@ -418,6 +418,7 @@ export function handleMachineMessage(context, connection, message) {
|
|
|
418
418
|
currentHead: message.currentHead,
|
|
419
419
|
lastTurnId: message.lastTurnId,
|
|
420
420
|
basis: message.basis,
|
|
421
|
+
status: message.status,
|
|
421
422
|
...(message.encryptedMetadata ? { encryptedMetadata: message.encryptedMetadata } : {}),
|
|
422
423
|
};
|
|
423
424
|
const result = applySessionHeadAdvance(db, input, ts);
|
|
@@ -634,6 +635,7 @@ function pendingRowToHeadAdvanceInput(row) {
|
|
|
634
635
|
turnSeq: row.basis_turn_seq,
|
|
635
636
|
...(row.basis_occurred_at !== null ? { occurredAt: row.basis_occurred_at } : {}),
|
|
636
637
|
},
|
|
638
|
+
status: "completed",
|
|
637
639
|
...(row.encrypted_metadata ? { encryptedMetadata: JSON.parse(row.encrypted_metadata) } : {}),
|
|
638
640
|
};
|
|
639
641
|
}
|
|
@@ -655,19 +657,28 @@ function applySessionHeadAdvance(db, input, ts) {
|
|
|
655
657
|
sessionRow.head_basis_message_id.startsWith(REWRITE_HEAD_BASIS_PREFIX);
|
|
656
658
|
const comparison = compareHeadBasisOrder(nextOrder, currentOrder);
|
|
657
659
|
const currentHeadIsBasisEvent = sessionRow.current_head === `event:${basisEvent.id}`;
|
|
660
|
+
const currentHeadAlreadySettled = currentHeadIsBasisEvent ||
|
|
661
|
+
sessionRow.current_head === input.currentHead ||
|
|
662
|
+
sessionRow.last_turn_id === input.lastTurnId;
|
|
658
663
|
const shouldAdvance = !rewriteFenced && (comparison > 0 || (comparison === 0 && currentHeadIsBasisEvent));
|
|
659
|
-
|
|
664
|
+
const shouldSettleStatus = !rewriteFenced &&
|
|
665
|
+
input.status !== undefined &&
|
|
666
|
+
sessionRow.status === "running" &&
|
|
667
|
+
comparison === 0 &&
|
|
668
|
+
currentHeadAlreadySettled;
|
|
669
|
+
if (!shouldAdvance && !shouldSettleStatus)
|
|
660
670
|
return { status: "noop", session: sessionRow };
|
|
661
671
|
db.prepare(`UPDATE sessions
|
|
662
|
-
SET
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
672
|
+
SET status = COALESCE(?, status),
|
|
673
|
+
current_head = COALESCE(?, current_head),
|
|
674
|
+
last_turn_id = COALESCE(?, last_turn_id),
|
|
675
|
+
head_basis_event_id = COALESCE(?, head_basis_event_id),
|
|
676
|
+
head_basis_message_id = COALESCE(?, head_basis_message_id),
|
|
677
|
+
head_basis_logical_time = COALESCE(?, head_basis_logical_time),
|
|
678
|
+
head_basis_turn_seq = COALESCE(?, head_basis_turn_seq),
|
|
668
679
|
encrypted_metadata = COALESCE(?, encrypted_metadata),
|
|
669
680
|
updated_at = ?
|
|
670
|
-
WHERE id = ? AND account_id = ? AND machine_id = ?`).run(input.currentHead, input.lastTurnId, basisEvent.id, basisEvent.message_id, nextOrder.time, nextOrder.seq, input.encryptedMetadata ? JSON.stringify(input.encryptedMetadata) : null, ts, input.sessionId, input.accountId, input.machineId);
|
|
681
|
+
WHERE id = ? AND account_id = ? AND machine_id = ?`).run(input.status && sessionRow.status === "running" ? input.status : null, shouldAdvance ? input.currentHead : null, shouldAdvance ? input.lastTurnId : null, shouldAdvance ? basisEvent.id : null, shouldAdvance ? basisEvent.message_id : null, shouldAdvance ? nextOrder.time : null, shouldAdvance ? nextOrder.seq : null, input.encryptedMetadata ? JSON.stringify(input.encryptedMetadata) : null, ts, input.sessionId, input.accountId, input.machineId);
|
|
671
682
|
const session = db.prepare("SELECT * FROM sessions WHERE id = ?").get(input.sessionId);
|
|
672
683
|
return { status: "applied", session };
|
|
673
684
|
}
|
package/package.json
CHANGED
|
@@ -165,6 +165,7 @@ export class ControllerClient {
|
|
|
165
165
|
const checkedSessionId = requireNonEmpty(sessionId, "sessionId");
|
|
166
166
|
const currentHead = requireNonEmpty(input.currentHead, "currentHead");
|
|
167
167
|
const lastTurnId = requireNonEmpty(input.lastTurnId, "lastTurnId");
|
|
168
|
+
const status = input.status;
|
|
168
169
|
const turnId = requireNonEmpty(input.basis.turnId, "basis.turnId");
|
|
169
170
|
const eventMessageId = requireNonEmpty(input.basis.eventMessageId, "basis.eventMessageId");
|
|
170
171
|
return await authenticatedJson(this.config, `/api/sessions/${encodeURIComponent(checkedSessionId)}/head`, {
|
|
@@ -173,6 +174,7 @@ export class ControllerClient {
|
|
|
173
174
|
body: JSON.stringify({
|
|
174
175
|
currentHead,
|
|
175
176
|
lastTurnId,
|
|
177
|
+
...(status ? { status } : {}),
|
|
176
178
|
basis: {
|
|
177
179
|
turnId,
|
|
178
180
|
eventMessageId,
|
|
@@ -183,6 +183,7 @@ export function parseRepairSessionHeadResult(value) {
|
|
|
183
183
|
const reason = record.reason;
|
|
184
184
|
return {
|
|
185
185
|
advanced: readBooleanField(record, "advanced", "Session head repair"),
|
|
186
|
+
...(typeof record.settled === "boolean" ? { settled: record.settled } : {}),
|
|
186
187
|
session: session,
|
|
187
188
|
basis: {
|
|
188
189
|
eventId: readNumberField(basis, "eventId", "Session head repair basis"),
|
|
@@ -205,6 +205,7 @@ export type CollectInput = {
|
|
|
205
205
|
export type RepairSessionHeadInput = {
|
|
206
206
|
currentHead: string;
|
|
207
207
|
lastTurnId: string;
|
|
208
|
+
status?: "completed" | "cancelled" | "failed";
|
|
208
209
|
basis: {
|
|
209
210
|
turnId: string;
|
|
210
211
|
eventMessageId: string;
|
|
@@ -214,6 +215,7 @@ export type RepairSessionHeadInput = {
|
|
|
214
215
|
};
|
|
215
216
|
export type RepairSessionHeadResult = {
|
|
216
217
|
advanced: boolean;
|
|
218
|
+
settled?: boolean;
|
|
217
219
|
session: SessionSnapshot;
|
|
218
220
|
basis: {
|
|
219
221
|
eventId: number;
|
|
@@ -144,6 +144,7 @@ export type MachineClientMessage = {
|
|
|
144
144
|
currentHead: string;
|
|
145
145
|
lastTurnId: string;
|
|
146
146
|
basis: SessionHeadAdvanceBasis;
|
|
147
|
+
status?: "completed" | "cancelled" | "failed";
|
|
147
148
|
encryptedMetadata?: EncryptedEnvelope;
|
|
148
149
|
} | {
|
|
149
150
|
type: "machine:turnDone";
|
|
@@ -169,6 +169,7 @@ const machineMessageSchema = z.discriminatedUnion("type", [
|
|
|
169
169
|
messageId: z.string().min(1).optional(),
|
|
170
170
|
currentHead: z.string().min(1),
|
|
171
171
|
lastTurnId: z.string().min(1),
|
|
172
|
+
status: z.enum(["completed", "cancelled", "failed"]).optional(),
|
|
172
173
|
basis: z.object({
|
|
173
174
|
turnId: z.string().min(1),
|
|
174
175
|
eventMessageId: z.string().min(1),
|