@cydm/happy-elves 0.1.0-beta.38 → 0.1.0-beta.39
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/lib/args.js +1 -0
- package/apps/cli/dist/commands/lib/session-output.d.ts +1 -0
- package/apps/cli/dist/commands/lib/session-output.js +32 -2
- package/apps/cli/dist/commands/lib/usage.js +4 -3
- package/apps/cli/dist/commands/session.js +48 -3
- package/apps/daemon/package.json +1 -1
- package/apps/relay/dist/http-routes.js +96 -1
- package/apps/relay/dist/http-schemas.d.ts +10 -0
- package/apps/relay/dist/http-schemas.js +10 -0
- package/package.json +1 -1
- package/packages/client/dist/client.d.ts +2 -1
- package/packages/client/dist/client.js +22 -1
- package/packages/client/dist/index.d.ts +1 -1
- package/packages/client/dist/parsers.d.ts +2 -1
- package/packages/client/dist/parsers.js +18 -0
- package/packages/client/dist/types.d.ts +21 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ControllerClient, DecodedSessionEvent } from "../../../../../packages/client/dist/index.js";
|
|
2
2
|
export declare function compactText(value: string, max?: number): string;
|
|
3
3
|
export declare function lastAssistantOutput(events: DecodedSessionEvent[]): string;
|
|
4
|
+
export declare function compareSessionEventLogicalOrder(left: DecodedSessionEvent, right: DecodedSessionEvent): number;
|
|
4
5
|
export declare function displayPath(filePath: string): string;
|
|
5
6
|
export type StoredTurnOutput = {
|
|
6
7
|
sessionId: string;
|
|
@@ -48,6 +48,27 @@ function assistantOutputForTurn(events, turnId) {
|
|
|
48
48
|
.join("")
|
|
49
49
|
.trim();
|
|
50
50
|
}
|
|
51
|
+
function sessionEventLogicalOrder(event) {
|
|
52
|
+
const messageKey = event.messageId ?? "";
|
|
53
|
+
if (event.occurredAt !== undefined) {
|
|
54
|
+
return { time: event.occurredAt, seq: event.turnSeq ?? event.seq, messageKey, id: event.id };
|
|
55
|
+
}
|
|
56
|
+
if (event.messageId?.startsWith("hist_") && event.turnSeq !== undefined) {
|
|
57
|
+
return { time: 0, seq: event.turnSeq, messageKey, id: event.id };
|
|
58
|
+
}
|
|
59
|
+
return { time: event.createdAt, seq: event.seq, messageKey, id: event.id };
|
|
60
|
+
}
|
|
61
|
+
export function compareSessionEventLogicalOrder(left, right) {
|
|
62
|
+
const leftOrder = sessionEventLogicalOrder(left);
|
|
63
|
+
const rightOrder = sessionEventLogicalOrder(right);
|
|
64
|
+
if (leftOrder.time !== rightOrder.time)
|
|
65
|
+
return leftOrder.time - rightOrder.time;
|
|
66
|
+
if (leftOrder.seq !== rightOrder.seq)
|
|
67
|
+
return leftOrder.seq - rightOrder.seq;
|
|
68
|
+
if (leftOrder.messageKey !== rightOrder.messageKey)
|
|
69
|
+
return leftOrder.messageKey < rightOrder.messageKey ? -1 : 1;
|
|
70
|
+
return leftOrder.id - rightOrder.id;
|
|
71
|
+
}
|
|
51
72
|
function outputPaths(sessionId, turnId) {
|
|
52
73
|
const dir = path.join(outputRoot, sessionId);
|
|
53
74
|
const latest = path.join(dir, "latest.md");
|
|
@@ -142,8 +163,9 @@ export async function readStoredTurnOutput(sessionId, turnId) {
|
|
|
142
163
|
export function summarizeTurns(events) {
|
|
143
164
|
const folded = foldAliasedRuntimeEvents(events);
|
|
144
165
|
const turns = new Map();
|
|
145
|
-
const sorted = [...folded.events].sort(
|
|
166
|
+
const sorted = [...folded.events].sort(compareSessionEventLogicalOrder);
|
|
146
167
|
for (const event of sorted) {
|
|
168
|
+
const order = sessionEventLogicalOrder(event);
|
|
147
169
|
const current = turns.get(event.turnId) ?? {
|
|
148
170
|
turnId: event.turnId,
|
|
149
171
|
promptChunks: [],
|
|
@@ -155,11 +177,13 @@ export function summarizeTurns(events) {
|
|
|
155
177
|
firstEventId: event.id,
|
|
156
178
|
lastEventId: event.id,
|
|
157
179
|
eventCount: 0,
|
|
180
|
+
order,
|
|
158
181
|
};
|
|
159
182
|
current.startedAt ??= event.createdAt;
|
|
160
183
|
current.updatedAt = event.createdAt;
|
|
161
184
|
current.lastEventId = event.id;
|
|
162
185
|
current.eventCount += 1;
|
|
186
|
+
current.order = order;
|
|
163
187
|
if (event.turnSeq !== undefined)
|
|
164
188
|
current.turnSeq = event.turnSeq;
|
|
165
189
|
const payload = event.decoded;
|
|
@@ -194,8 +218,14 @@ export function summarizeTurns(events) {
|
|
|
194
218
|
firstEventId: turn.firstEventId,
|
|
195
219
|
lastEventId: turn.lastEventId,
|
|
196
220
|
eventCount: turn.eventCount,
|
|
221
|
+
order: turn.order,
|
|
197
222
|
}))
|
|
198
|
-
.sort((left, right) => right.
|
|
223
|
+
.sort((left, right) => right.order.time - left.order.time ||
|
|
224
|
+
right.order.seq - left.order.seq ||
|
|
225
|
+
(right.order.messageKey === left.order.messageKey ? 0 : right.order.messageKey < left.order.messageKey ? -1 : 1) ||
|
|
226
|
+
right.order.id - left.order.id ||
|
|
227
|
+
right.lastEventId - left.lastEventId)
|
|
228
|
+
.map(({ order: _order, ...turn }) => turn);
|
|
199
229
|
}
|
|
200
230
|
export function structuredOutputForTurn(sessionId, turn) {
|
|
201
231
|
const finalOutput = turn.output || turn.terminalText || undefined;
|
|
@@ -50,7 +50,7 @@ Check local controller config, relay reachability, and daemon state.
|
|
|
50
50
|
happy-elves session history --machine <machineId> [--cwd <path>] [--agent <agent>] [--include-archived] [--limit 20] --json
|
|
51
51
|
happy-elves session continue <runtimeSessionId> --machine <machineId> [--name <name>] --json
|
|
52
52
|
happy-elves session status <sessionId> [--verbose] [--json]
|
|
53
|
-
happy-elves session diagnose <sessionId> [--limit 200] [--json]
|
|
53
|
+
happy-elves session diagnose <sessionId> [--limit 200] [--repair-head] [--json]
|
|
54
54
|
happy-elves session close <sessionId> [--reason <reason>] --json
|
|
55
55
|
|
|
56
56
|
Session management:
|
|
@@ -62,7 +62,8 @@ Session management:
|
|
|
62
62
|
existing loaded session if it already exists.
|
|
63
63
|
diagnose reports the selected session's runtime handle metadata, backfill
|
|
64
64
|
counters, and latest materialized relay page counts for debugging empty or
|
|
65
|
-
stale transcripts.
|
|
65
|
+
stale transcripts. --repair-head advances a stale canonical head to the
|
|
66
|
+
latest completed relay event when the relay already has the event basis.
|
|
66
67
|
close archives a loaded session. There is no separate archive command.
|
|
67
68
|
`,
|
|
68
69
|
turn: `Usage:
|
|
@@ -141,7 +142,7 @@ Core commands:
|
|
|
141
142
|
session history --machine <machineId> [--cwd <path>] [--limit 20] --json
|
|
142
143
|
session continue <runtimeSessionId> --machine <machineId> --json
|
|
143
144
|
session status <sessionId> [--verbose] [--json]
|
|
144
|
-
session diagnose <sessionId> [--limit 200] [--json]
|
|
145
|
+
session diagnose <sessionId> [--limit 200] [--repair-head] [--json]
|
|
145
146
|
session close <sessionId> [--reason <reason>] --json
|
|
146
147
|
|
|
147
148
|
turn list <sessionId> [--limit 10] [--json]
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CliError, ControllerClient, compactSessionListItem, compactText, conciseSessionSummary, deriveOrchestration, listWorkspaceHistoricalSessions, ok, parsePermissionMode, readConfig, readStoredTurnOutput, requirePositional, requireString, showSession, structuredSessionProjection, summarizeTurns, wantsJson, wantsVerbose } from "./lib/index.js";
|
|
1
|
+
import { CliError, ControllerClient, compareSessionEventLogicalOrder, compactSessionListItem, compactText, conciseSessionSummary, deriveOrchestration, listWorkspaceHistoricalSessions, ok, parsePermissionMode, readConfig, readStoredTurnOutput, requirePositional, requireString, showSession, structuredSessionProjection, summarizeTurns, wantsJson, wantsVerbose } from "./lib/index.js";
|
|
2
2
|
function formatTurnTimestamp(value) {
|
|
3
3
|
return value === undefined ? "-" : new Date(value).toISOString();
|
|
4
4
|
}
|
|
@@ -104,6 +104,35 @@ function compactEventDiagnostic(event) {
|
|
|
104
104
|
...(event.turnSeq !== undefined ? { turnSeq: event.turnSeq } : {}),
|
|
105
105
|
};
|
|
106
106
|
}
|
|
107
|
+
function repairHeadInputForLatestCompletedEvent(events) {
|
|
108
|
+
const doneEvent = [...events]
|
|
109
|
+
.sort(compareSessionEventLogicalOrder)
|
|
110
|
+
.reverse()
|
|
111
|
+
.find((event) => event.decoded?.type === "done" && event.decoded.status === "completed");
|
|
112
|
+
const done = doneEvent?.decoded;
|
|
113
|
+
if (!doneEvent || done?.type !== "done") {
|
|
114
|
+
throw new CliError("No completed done event is available for head repair", "SESSION_HEAD_REPAIR_UNAVAILABLE");
|
|
115
|
+
}
|
|
116
|
+
if (!doneEvent.messageId) {
|
|
117
|
+
throw new CliError("Latest completed done event has no messageId basis", "SESSION_HEAD_REPAIR_UNAVAILABLE");
|
|
118
|
+
}
|
|
119
|
+
const turnSeq = doneEvent.turnSeq;
|
|
120
|
+
if (turnSeq === undefined) {
|
|
121
|
+
throw new CliError("Latest completed done event has no turnSeq basis", "SESSION_HEAD_REPAIR_UNAVAILABLE");
|
|
122
|
+
}
|
|
123
|
+
const currentHead = done.currentHead ?? done.lastTurnId ?? doneEvent.turnId;
|
|
124
|
+
const lastTurnId = done.lastTurnId ?? done.currentHead ?? doneEvent.turnId;
|
|
125
|
+
return {
|
|
126
|
+
currentHead,
|
|
127
|
+
lastTurnId,
|
|
128
|
+
basis: {
|
|
129
|
+
turnId: doneEvent.turnId,
|
|
130
|
+
eventMessageId: doneEvent.messageId,
|
|
131
|
+
turnSeq,
|
|
132
|
+
...(doneEvent.occurredAt === undefined ? {} : { occurredAt: doneEvent.occurredAt }),
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
}
|
|
107
136
|
function eventCollectionDiagnostics(events) {
|
|
108
137
|
return {
|
|
109
138
|
decryptErrors: events.filter((event) => event.decryptError).length,
|
|
@@ -330,15 +359,24 @@ export async function handleSession({ domain, action, positional, flags }) {
|
|
|
330
359
|
const limit = parsePositiveIntFlag(flags, "limit") ?? 200;
|
|
331
360
|
const client = await makeClient();
|
|
332
361
|
const snapshot = await client.snapshot();
|
|
333
|
-
const
|
|
334
|
-
if (!
|
|
362
|
+
const initialSession = snapshot.sessions.find((item) => item.id === sessionId);
|
|
363
|
+
if (!initialSession)
|
|
335
364
|
throw new CliError("Session not found", "SESSION_NOT_FOUND");
|
|
365
|
+
let session = initialSession;
|
|
366
|
+
if (flags["repair-head"] === true && session.status === "running") {
|
|
367
|
+
throw new CliError("Cannot repair a running session head", "SESSION_HEAD_REPAIR_RUNNING");
|
|
368
|
+
}
|
|
336
369
|
const machine = snapshot.machines.find((item) => item.id === session.machineId);
|
|
337
370
|
const metadata = await client.decodeSessionMetadata(session);
|
|
338
371
|
const latestPage = await client.collectPage(session.id, { limit });
|
|
339
372
|
const turns = summarizeTurns(latestPage.events);
|
|
340
373
|
const latestCompletedTurn = turns.find((turn) => turn.status === "completed");
|
|
341
374
|
const latestTerminalTurn = turns.find((turn) => turn.status !== "running");
|
|
375
|
+
const repair = flags["repair-head"] === true
|
|
376
|
+
? await client.repairSessionHead(session.id, repairHeadInputForLatestCompletedEvent(latestPage.events))
|
|
377
|
+
: undefined;
|
|
378
|
+
if (repair)
|
|
379
|
+
session = repair.session;
|
|
342
380
|
const data = {
|
|
343
381
|
generatedAt: new Date().toISOString(),
|
|
344
382
|
session: {
|
|
@@ -395,6 +433,13 @@ export async function handleSession({ domain, action, positional, flags }) {
|
|
|
395
433
|
staleHeadSuspected: staleHeadSuspected(session, latestCompletedTurn),
|
|
396
434
|
headMatchesLatestCompletedTurn: headMatchesTurn(session, latestCompletedTurn),
|
|
397
435
|
},
|
|
436
|
+
repair: repair ? {
|
|
437
|
+
advanced: repair.advanced,
|
|
438
|
+
basis: repair.basis,
|
|
439
|
+
currentHead: repair.session.currentHead,
|
|
440
|
+
lastTurnId: repair.session.lastTurnId,
|
|
441
|
+
reason: repair.reason,
|
|
442
|
+
} : undefined,
|
|
398
443
|
};
|
|
399
444
|
if (wantsJson(flags) || wantsVerbose(flags)) {
|
|
400
445
|
ok("session.diagnose", data, { machineId: session.machineId, sessionId: session.id });
|
package/apps/daemon/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { encryptedEnvelopeSchema, pairingCode, randomId, } from "../../../packages/shared/dist/index.js";
|
|
3
|
-
import { controllerDeviceSchema, controllerInviteClaimSchema, controllerInviteCreateSchema, deviceRevokeSchema, pairingClaimSchema, pairingStartSchema, scopedTokenCreateSchema, scopedTokenRevokeSchema, sessionEventsQuerySchema, } from "./http-schemas.js";
|
|
3
|
+
import { controllerDeviceSchema, controllerInviteClaimSchema, controllerInviteCreateSchema, deviceRevokeSchema, pairingClaimSchema, pairingStartSchema, scopedTokenCreateSchema, scopedTokenRevokeSchema, sessionHeadAdvanceSchema, sessionEventsQuerySchema, } from "./http-schemas.js";
|
|
4
4
|
import { HttpError } from "./errors.js";
|
|
5
5
|
import { encodePairingCode, isPairingCodeMatch, tokenDigest } from "./security.js";
|
|
6
6
|
const visibleSessionEventWhere = `
|
|
@@ -238,6 +238,65 @@ export function registerHttpRoutes(app, context, controllerInviteTtlMs) {
|
|
|
238
238
|
newestEventId: newestRow?.id,
|
|
239
239
|
};
|
|
240
240
|
});
|
|
241
|
+
app.post("/api/sessions/:sessionId/head", async (request) => {
|
|
242
|
+
const token = context.requireHttpToken(request.headers.authorization, "controller");
|
|
243
|
+
context.assertHttpScopedAction(token, "session.repairHead");
|
|
244
|
+
const params = z.object({ sessionId: z.string().min(1) }).parse(request.params);
|
|
245
|
+
const body = sessionHeadAdvanceSchema.parse(request.body);
|
|
246
|
+
context.assertScopedSession(context.tokenScope(token), token.account_id, params.sessionId);
|
|
247
|
+
const session = context.db
|
|
248
|
+
.prepare("SELECT * FROM sessions WHERE account_id = ? AND id = ?")
|
|
249
|
+
.get(token.account_id, params.sessionId);
|
|
250
|
+
if (!session)
|
|
251
|
+
throw new HttpError(404, "SESSION_NOT_FOUND", "Session not found");
|
|
252
|
+
if (session.status === "running") {
|
|
253
|
+
throw new HttpError(409, "SESSION_HEAD_REPAIR_RUNNING", "Cannot repair a running session head");
|
|
254
|
+
}
|
|
255
|
+
const basisEvent = context.db
|
|
256
|
+
.prepare(`SELECT * FROM session_events
|
|
257
|
+
WHERE account_id = ? AND session_id = ? AND machine_id = ? AND message_id = ?`)
|
|
258
|
+
.get(token.account_id, params.sessionId, session.machine_id, body.basis.eventMessageId);
|
|
259
|
+
if (!basisEvent)
|
|
260
|
+
throw new HttpError(409, "SESSION_HEAD_BASIS_MISSING", "Head advance basis event is not available");
|
|
261
|
+
if (basisEvent.turn_id !== body.basis.turnId || basisEvent.turn_seq !== body.basis.turnSeq) {
|
|
262
|
+
throw new HttpError(409, "SESSION_HEAD_BASIS_MISMATCH", "Head advance basis does not match the stored event");
|
|
263
|
+
}
|
|
264
|
+
const nextOrder = logicalEventOrderForRow(basisEvent);
|
|
265
|
+
const currentOrder = sessionHeadBasisOrderForRow(context.db, session);
|
|
266
|
+
const rewriteFenced = typeof session.head_basis_message_id === "string" &&
|
|
267
|
+
session.head_basis_message_id.startsWith("rewrite:");
|
|
268
|
+
const advanced = !rewriteFenced && compareHeadOrders(nextOrder, currentOrder) > 0;
|
|
269
|
+
if (advanced) {
|
|
270
|
+
const ts = context.now();
|
|
271
|
+
context.db.prepare(`UPDATE sessions
|
|
272
|
+
SET current_head = ?,
|
|
273
|
+
last_turn_id = ?,
|
|
274
|
+
head_basis_event_id = ?,
|
|
275
|
+
head_basis_message_id = ?,
|
|
276
|
+
head_basis_logical_time = ?,
|
|
277
|
+
head_basis_turn_seq = ?,
|
|
278
|
+
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);
|
|
280
|
+
}
|
|
281
|
+
const row = context.db
|
|
282
|
+
.prepare("SELECT * FROM sessions WHERE account_id = ? AND id = ?")
|
|
283
|
+
.get(token.account_id, params.sessionId);
|
|
284
|
+
if (!row)
|
|
285
|
+
throw new HttpError(404, "SESSION_NOT_FOUND", "Session not found");
|
|
286
|
+
const snapshot = context.sessionSnapshot(row);
|
|
287
|
+
context.broadcastControllers(token.account_id, { type: "server:session", session: snapshot });
|
|
288
|
+
return {
|
|
289
|
+
advanced,
|
|
290
|
+
session: snapshot,
|
|
291
|
+
basis: {
|
|
292
|
+
eventId: basisEvent.id,
|
|
293
|
+
messageId: basisEvent.message_id ?? undefined,
|
|
294
|
+
logicalTime: nextOrder.time,
|
|
295
|
+
turnSeq: nextOrder.seq,
|
|
296
|
+
},
|
|
297
|
+
...(rewriteFenced ? { reason: "rewrite-fenced" } : {}),
|
|
298
|
+
};
|
|
299
|
+
});
|
|
241
300
|
app.get("/api/devices", async (request) => {
|
|
242
301
|
const token = context.requireFullControllerToken(request.headers.authorization);
|
|
243
302
|
const rows = context.db
|
|
@@ -392,4 +451,40 @@ export function registerHttpRoutes(app, context, controllerInviteTtlMs) {
|
|
|
392
451
|
return { tokenId: body.tokenId, revokedAt: target.revoked_at ?? ts };
|
|
393
452
|
});
|
|
394
453
|
}
|
|
454
|
+
function compareHeadOrders(next, current) {
|
|
455
|
+
if (!current)
|
|
456
|
+
return 1;
|
|
457
|
+
if (next.time !== current.time)
|
|
458
|
+
return next.time > current.time ? 1 : -1;
|
|
459
|
+
if (next.seq !== current.seq)
|
|
460
|
+
return next.seq > current.seq ? 1 : -1;
|
|
461
|
+
if (next.id !== current.id)
|
|
462
|
+
return next.id > current.id ? 1 : -1;
|
|
463
|
+
return 0;
|
|
464
|
+
}
|
|
465
|
+
function logicalEventOrderForRow(row) {
|
|
466
|
+
if (row.occurred_at !== null && row.occurred_at !== undefined) {
|
|
467
|
+
return { time: row.occurred_at, seq: row.turn_seq ?? row.seq, id: row.id };
|
|
468
|
+
}
|
|
469
|
+
if (row.message_id?.startsWith("hist_") && row.turn_seq !== null && row.turn_seq !== undefined) {
|
|
470
|
+
return { time: 0, seq: row.turn_seq, id: row.id };
|
|
471
|
+
}
|
|
472
|
+
return { time: row.created_at, seq: row.turn_seq ?? row.seq, id: row.id };
|
|
473
|
+
}
|
|
474
|
+
function sessionHeadBasisOrderForRow(db, row) {
|
|
475
|
+
if (row.head_basis_logical_time !== null && row.head_basis_turn_seq !== null) {
|
|
476
|
+
return {
|
|
477
|
+
time: row.head_basis_logical_time,
|
|
478
|
+
seq: row.head_basis_turn_seq,
|
|
479
|
+
id: row.head_basis_event_id ?? 0,
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
const eventId = row.current_head ? /^event:(\d+)$/.exec(row.current_head)?.[1] : undefined;
|
|
483
|
+
if (!eventId)
|
|
484
|
+
return undefined;
|
|
485
|
+
const event = db
|
|
486
|
+
.prepare("SELECT * FROM session_events WHERE account_id = ? AND session_id = ? AND machine_id = ? AND id = ?")
|
|
487
|
+
.get(row.account_id, row.id, row.machine_id, Number(eventId));
|
|
488
|
+
return event ? logicalEventOrderForRow(event) : undefined;
|
|
489
|
+
}
|
|
395
490
|
//# sourceMappingURL=http-routes.js.map
|
|
@@ -46,3 +46,13 @@ export declare const sessionEventsQuerySchema: z.ZodObject<{
|
|
|
46
46
|
limit: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
|
|
47
47
|
since: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
|
|
48
48
|
}, z.core.$strip>;
|
|
49
|
+
export declare const sessionHeadAdvanceSchema: z.ZodObject<{
|
|
50
|
+
currentHead: z.ZodString;
|
|
51
|
+
lastTurnId: z.ZodString;
|
|
52
|
+
basis: z.ZodObject<{
|
|
53
|
+
turnId: z.ZodString;
|
|
54
|
+
eventMessageId: z.ZodString;
|
|
55
|
+
turnSeq: z.ZodNumber;
|
|
56
|
+
occurredAt: z.ZodOptional<z.ZodNumber>;
|
|
57
|
+
}, z.core.$strip>;
|
|
58
|
+
}, z.core.$strip>;
|
|
@@ -53,4 +53,14 @@ export const sessionEventsQuerySchema = z.object({
|
|
|
53
53
|
}).refine((query) => query.beforeCursor === undefined || query.since === undefined, {
|
|
54
54
|
message: "since and beforeCursor cannot both be provided",
|
|
55
55
|
});
|
|
56
|
+
export const sessionHeadAdvanceSchema = z.object({
|
|
57
|
+
currentHead: z.string().min(1),
|
|
58
|
+
lastTurnId: z.string().min(1),
|
|
59
|
+
basis: z.object({
|
|
60
|
+
turnId: z.string().min(1),
|
|
61
|
+
eventMessageId: z.string().min(1),
|
|
62
|
+
turnSeq: z.number().int().min(0),
|
|
63
|
+
occurredAt: z.number().int().optional(),
|
|
64
|
+
}),
|
|
65
|
+
});
|
|
56
66
|
//# sourceMappingURL=http-schemas.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type DirectoryListing, type EncryptedSessionEvent, type HistoricalSessionSnapshot, type MachineSnapshot, type PairingStartResponse, type SessionSnapshot } from "../../shared/dist/index.js";
|
|
2
2
|
import { sendCommand } from "./transport.js";
|
|
3
|
-
import type { AddControllerDeviceInput, AddControllerDeviceResult, CancelInput, CollectInput, CommandResult, ControllerClientConfig, ControllerInviteClaimInput, ControllerInviteClaimResult, ControllerInviteCreateInput, ControllerInviteCreateResult, ControllerSnapshot, ControllerSubscriptionHandlers, CreateScopedTokenInput, CreateSessionInput, DecodedSessionEvent, DecodedSessionEventsPage, DeviceListResponse, DirectoryInput, FilePreviewInput, ForkInput, HistoricalSessionListPage, HistoricalSessionsInput, ImportHistoricalSessionInput, MachineMetadata, MachineDevExecInput, MachineDevExecResponse, MachineDiagnosticsResponse, RunOptions, ScopedTokenInfo, ScopedTokenSelf, SessionFilter, SessionMetadata, WaitInput } from "./types.js";
|
|
3
|
+
import type { AddControllerDeviceInput, AddControllerDeviceResult, CancelInput, CollectInput, CommandResult, ControllerClientConfig, ControllerInviteClaimInput, ControllerInviteClaimResult, ControllerInviteCreateInput, ControllerInviteCreateResult, ControllerSnapshot, ControllerSubscriptionHandlers, CreateScopedTokenInput, CreateSessionInput, DecodedSessionEvent, DecodedSessionEventsPage, DeviceListResponse, DirectoryInput, FilePreviewInput, ForkInput, HistoricalSessionListPage, HistoricalSessionsInput, ImportHistoricalSessionInput, MachineMetadata, MachineDevExecInput, MachineDevExecResponse, MachineDiagnosticsResponse, RepairSessionHeadInput, RepairSessionHeadResult, RunOptions, ScopedTokenInfo, ScopedTokenSelf, SessionFilter, SessionMetadata, WaitInput } from "./types.js";
|
|
4
4
|
export declare class ControllerClient {
|
|
5
5
|
readonly config: ControllerClientConfig;
|
|
6
6
|
constructor(config: ControllerClientConfig);
|
|
@@ -33,6 +33,7 @@ export declare class ControllerClient {
|
|
|
33
33
|
createSession(input: CreateSessionInput): Promise<CommandResult>;
|
|
34
34
|
collect(sessionId: string, input?: CollectInput): Promise<DecodedSessionEvent[]>;
|
|
35
35
|
collectPage(sessionId: string, input?: CollectInput): Promise<DecodedSessionEventsPage>;
|
|
36
|
+
repairSessionHead(sessionId: string, input: RepairSessionHeadInput): Promise<RepairSessionHeadResult>;
|
|
36
37
|
wait(sessionId: string, input?: WaitInput): Promise<SessionSnapshot>;
|
|
37
38
|
resume(sessionId: string): Promise<CommandResult>;
|
|
38
39
|
listHistoricalSessionsPage(input: HistoricalSessionsInput): Promise<HistoricalSessionListPage>;
|
|
@@ -2,7 +2,7 @@ import { decryptJson, encryptJson, normalizeSessionName, parseServerMessage, ran
|
|
|
2
2
|
import { addControllerDevice, claimControllerInvite, createControllerInvite, createPairingCode, createScopedToken, listDevices, listScopedTokens, revokeDevice, revokeScopedToken, tokenSelf, } from "./account.js";
|
|
3
3
|
import { ControllerClientError, ControllerCommandError } from "./errors.js";
|
|
4
4
|
import { authenticatedJson, normalizeRelayUrl, readRelayJson, relayHttpError } from "./http.js";
|
|
5
|
-
import { parseSessionEventsResponse, } from "./parsers.js";
|
|
5
|
+
import { parseRepairSessionHeadResult, parseSessionEventsResponse, } from "./parsers.js";
|
|
6
6
|
import { sendAndWaitForDirectoryListing, sendAndWaitForFilePreview, sendAndWaitForHistoricalSessions, sendAndWaitForMachineDevExecResult, sendAndWaitForMachineDiagnostics, sendCommand, subscribe, } from "./transport.js";
|
|
7
7
|
import { DEFAULT_SESSION_CREATE_WAIT_MS, } from "./types.js";
|
|
8
8
|
import { isWaitCondition, matchesWaitCondition, parseEventCursor, parseEventLimit, parsePositiveDuration, requireNonEmpty, sleep, } from "./validation.js";
|
|
@@ -161,6 +161,27 @@ export class ControllerClient {
|
|
|
161
161
|
...(page.oldestEventId === undefined ? {} : { oldestEventId: page.oldestEventId }),
|
|
162
162
|
};
|
|
163
163
|
}
|
|
164
|
+
async repairSessionHead(sessionId, input) {
|
|
165
|
+
const checkedSessionId = requireNonEmpty(sessionId, "sessionId");
|
|
166
|
+
const currentHead = requireNonEmpty(input.currentHead, "currentHead");
|
|
167
|
+
const lastTurnId = requireNonEmpty(input.lastTurnId, "lastTurnId");
|
|
168
|
+
const turnId = requireNonEmpty(input.basis.turnId, "basis.turnId");
|
|
169
|
+
const eventMessageId = requireNonEmpty(input.basis.eventMessageId, "basis.eventMessageId");
|
|
170
|
+
return await authenticatedJson(this.config, `/api/sessions/${encodeURIComponent(checkedSessionId)}/head`, {
|
|
171
|
+
method: "POST",
|
|
172
|
+
headers: { "content-type": "application/json" },
|
|
173
|
+
body: JSON.stringify({
|
|
174
|
+
currentHead,
|
|
175
|
+
lastTurnId,
|
|
176
|
+
basis: {
|
|
177
|
+
turnId,
|
|
178
|
+
eventMessageId,
|
|
179
|
+
turnSeq: input.basis.turnSeq,
|
|
180
|
+
...(input.basis.occurredAt === undefined ? {} : { occurredAt: input.basis.occurredAt }),
|
|
181
|
+
},
|
|
182
|
+
}),
|
|
183
|
+
}, parseRepairSessionHeadResult);
|
|
184
|
+
}
|
|
164
185
|
async wait(sessionId, input = {}) {
|
|
165
186
|
const checkedSessionId = requireNonEmpty(sessionId, "sessionId");
|
|
166
187
|
const until = input.until ?? "idle";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { ControllerClient } from "./client.js";
|
|
2
2
|
export { ControllerClientError, ControllerCommandError } from "./errors.js";
|
|
3
|
-
export type { AddControllerDeviceInput, AddControllerDeviceResult, CancelInput, CollectInput, CommandResult, ControllerClientConfig, ControllerDevice, ControllerInviteClaimInput, ControllerInviteClaimResult, ControllerInviteCreateInput, ControllerInviteCreateResult, ControllerSnapshot, ControllerSubscriptionHandlers, CreateScopedTokenInput, CreateSessionInput, DecodedSessionEvent, DecodedSessionEventsPage, DeviceListResponse, DirectoryInput, DirectoryListResponse, FilePreviewInput, FilePreviewResponse, EventCursor, ForkInput, HistoricalSessionListPage, HistoricalSessionListResponse, HistoricalSessionsInput, ImportHistoricalSessionInput, MachineListResponse, MachineMetadata, PermissionMode, RunOptions, ScopedTokenInfo, ScopedTokenScope, ScopedTokenSelf, SessionFilter, SessionMetadata, WaitCondition, WaitInput, } from "./types.js";
|
|
3
|
+
export type { AddControllerDeviceInput, AddControllerDeviceResult, CancelInput, CollectInput, CommandResult, ControllerClientConfig, ControllerDevice, ControllerInviteClaimInput, ControllerInviteClaimResult, ControllerInviteCreateInput, ControllerInviteCreateResult, ControllerSnapshot, ControllerSubscriptionHandlers, CreateScopedTokenInput, CreateSessionInput, DecodedSessionEvent, DecodedSessionEventsPage, DeviceListResponse, DirectoryInput, DirectoryListResponse, FilePreviewInput, FilePreviewResponse, EventCursor, ForkInput, HistoricalSessionListPage, HistoricalSessionListResponse, HistoricalSessionsInput, ImportHistoricalSessionInput, MachineListResponse, MachineMetadata, PermissionMode, RepairSessionHeadInput, RepairSessionHeadResult, RunOptions, ScopedTokenInfo, ScopedTokenScope, ScopedTokenSelf, SessionFilter, SessionMetadata, WaitCondition, WaitInput, } from "./types.js";
|
|
4
4
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type PairingStartResponse } from "../../shared/dist/index.js";
|
|
2
|
-
import type { AddControllerDeviceResult, ControllerInviteClaimResult, ControllerInviteCreateResult, DeviceListResponse, ScopedTokenInfo, ScopedTokenSelf, SessionEventsResponse } from "./types.js";
|
|
2
|
+
import type { AddControllerDeviceResult, ControllerInviteClaimResult, ControllerInviteCreateResult, DeviceListResponse, ScopedTokenInfo, ScopedTokenSelf, RepairSessionHeadResult, SessionEventsResponse } from "./types.js";
|
|
3
3
|
export declare function parsePairingStartResponse(value: unknown): PairingStartResponse;
|
|
4
4
|
export declare function parseDevicesResponse(value: unknown): DeviceListResponse;
|
|
5
5
|
export declare function parseAddControllerDeviceResult(value: unknown): AddControllerDeviceResult;
|
|
@@ -22,4 +22,5 @@ export declare function parseScopedTokenRevoke(value: unknown): {
|
|
|
22
22
|
revokedAt: number;
|
|
23
23
|
};
|
|
24
24
|
export declare function parseSessionEventsResponse(value: unknown): SessionEventsResponse;
|
|
25
|
+
export declare function parseRepairSessionHeadResult(value: unknown): RepairSessionHeadResult;
|
|
25
26
|
//# sourceMappingURL=parsers.d.ts.map
|
|
@@ -175,4 +175,22 @@ export function parseSessionEventsResponse(value) {
|
|
|
175
175
|
...(oldestEventId === undefined ? {} : { oldestEventId }),
|
|
176
176
|
};
|
|
177
177
|
}
|
|
178
|
+
export function parseRepairSessionHeadResult(value) {
|
|
179
|
+
const record = assertRecord(value, "Session head repair");
|
|
180
|
+
const session = assertRecord(record.session, "Session head repair session");
|
|
181
|
+
const basis = assertRecord(record.basis, "Session head repair basis");
|
|
182
|
+
const messageId = basis.messageId;
|
|
183
|
+
const reason = record.reason;
|
|
184
|
+
return {
|
|
185
|
+
advanced: readBooleanField(record, "advanced", "Session head repair"),
|
|
186
|
+
session: session,
|
|
187
|
+
basis: {
|
|
188
|
+
eventId: readNumberField(basis, "eventId", "Session head repair basis"),
|
|
189
|
+
...(typeof messageId === "string" ? { messageId } : {}),
|
|
190
|
+
logicalTime: readNumberField(basis, "logicalTime", "Session head repair basis"),
|
|
191
|
+
turnSeq: readNumberField(basis, "turnSeq", "Session head repair basis"),
|
|
192
|
+
},
|
|
193
|
+
...(typeof reason === "string" ? { reason } : {}),
|
|
194
|
+
};
|
|
195
|
+
}
|
|
178
196
|
//# sourceMappingURL=parsers.js.map
|
|
@@ -202,6 +202,27 @@ export type CollectInput = {
|
|
|
202
202
|
since?: EventCursor;
|
|
203
203
|
limit?: number;
|
|
204
204
|
};
|
|
205
|
+
export type RepairSessionHeadInput = {
|
|
206
|
+
currentHead: string;
|
|
207
|
+
lastTurnId: string;
|
|
208
|
+
basis: {
|
|
209
|
+
turnId: string;
|
|
210
|
+
eventMessageId: string;
|
|
211
|
+
turnSeq: number;
|
|
212
|
+
occurredAt?: number;
|
|
213
|
+
};
|
|
214
|
+
};
|
|
215
|
+
export type RepairSessionHeadResult = {
|
|
216
|
+
advanced: boolean;
|
|
217
|
+
session: SessionSnapshot;
|
|
218
|
+
basis: {
|
|
219
|
+
eventId: number;
|
|
220
|
+
messageId?: string;
|
|
221
|
+
logicalTime: number;
|
|
222
|
+
turnSeq: number;
|
|
223
|
+
};
|
|
224
|
+
reason?: string;
|
|
225
|
+
};
|
|
205
226
|
export type ControllerInviteCreateInput = {
|
|
206
227
|
deviceName?: string;
|
|
207
228
|
ttlMs?: number;
|