@mono-agent/web 0.14.0 → 0.15.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/README.md +42 -13
- package/dist/contracts.d.ts +9 -0
- package/dist/contracts.d.ts.map +1 -1
- package/dist/contracts.js +2 -0
- package/dist/contracts.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/operator-client.d.ts +14 -1
- package/dist/operator-client.d.ts.map +1 -1
- package/dist/operator-client.js +28 -0
- package/dist/operator-client.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +23 -19
- package/dist/server.js.map +1 -1
- package/dist/service.d.ts +8 -1
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +142 -7
- package/dist/service.js.map +1 -1
- package/dist/store.d.ts +29 -1
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +354 -7
- package/dist/store.js.map +1 -1
- package/package.json +5 -5
- package/webapp/dist/assets/index-D3Tzt8AW.css +1 -0
- package/webapp/dist/assets/index-D6sHjkQD.js +51 -0
- package/webapp/dist/index.html +2 -2
- package/webapp/dist/sw.js +1 -1
- package/webapp/dist/assets/index-BpHUwRCC.css +0 -1
- package/webapp/dist/assets/index-DpEe0YWc.js +0 -47
package/dist/store.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type AgentStreamWireFrame } from "@mono-agent/agent-contracts";
|
|
2
2
|
import { type WebAgentSummary, type WebAttachment, type WebMessage, type WebNotificationTriggerKind, type WebQuote, type WebThread, type WebThreadDetail } from "./contracts.js";
|
|
3
3
|
import { type WebStatePathOptions, type WebStatePaths } from "./state-paths.js";
|
|
4
4
|
export interface StoredAttachment {
|
|
@@ -42,6 +42,20 @@ export interface BeginStoredTurnResult {
|
|
|
42
42
|
readonly attachments: readonly StoredAttachment[];
|
|
43
43
|
readonly thread: WebThread;
|
|
44
44
|
}
|
|
45
|
+
export interface StoredLiveInput {
|
|
46
|
+
readonly id: string;
|
|
47
|
+
readonly threadId: string;
|
|
48
|
+
readonly messageId: string;
|
|
49
|
+
readonly text: string;
|
|
50
|
+
readonly status: "offered" | "queued";
|
|
51
|
+
readonly createdAt: string;
|
|
52
|
+
}
|
|
53
|
+
export interface ReserveStoredLiveInputResult {
|
|
54
|
+
readonly input: StoredLiveInput;
|
|
55
|
+
readonly message: WebMessage;
|
|
56
|
+
readonly thread: WebThread;
|
|
57
|
+
readonly offered: boolean;
|
|
58
|
+
}
|
|
45
59
|
export interface ReserveWebNotificationInput {
|
|
46
60
|
readonly sourceId: string;
|
|
47
61
|
readonly deliveryKey: string;
|
|
@@ -82,6 +96,9 @@ export declare class WebStore {
|
|
|
82
96
|
readonly title?: string;
|
|
83
97
|
readonly archived?: boolean;
|
|
84
98
|
}): WebThread;
|
|
99
|
+
deleteArchivedThread(id: string): Promise<{
|
|
100
|
+
readonly orphanedFiles: number;
|
|
101
|
+
}>;
|
|
85
102
|
createUpload(input: CreateStoredUploadInput): StoredAttachment;
|
|
86
103
|
markUploadComplete(id: string, sizeBytes: number): StoredAttachment;
|
|
87
104
|
getStoredAttachment(id: string): StoredAttachment | undefined;
|
|
@@ -93,7 +110,15 @@ export declare class WebStore {
|
|
|
93
110
|
removeStagedAttachment(id: string): Promise<void>;
|
|
94
111
|
purgeStagedAttachments(before: string): Promise<number>;
|
|
95
112
|
purgePartialUploadFiles(before?: string): Promise<number>;
|
|
113
|
+
purgeUnreferencedAttachmentFiles(): Promise<number>;
|
|
96
114
|
beginTurn(input: BeginStoredTurnInput): BeginStoredTurnResult;
|
|
115
|
+
reserveLiveInput(threadId: string, text: string): ReserveStoredLiveInputResult;
|
|
116
|
+
markLiveInputApplied(id: string): WebMessage | undefined;
|
|
117
|
+
queueLiveInput(id: string): WebMessage | undefined;
|
|
118
|
+
cancelLiveInput(id: string): WebMessage | undefined;
|
|
119
|
+
cancelLiveInputs(threadId: string): WebMessage[];
|
|
120
|
+
queuedLiveInputThreadIds(): string[];
|
|
121
|
+
promoteNextQueuedLiveInput(threadId: string): BeginStoredTurnResult | undefined;
|
|
97
122
|
applyStreamFrame(turnId: string, frame: AgentStreamWireFrame): WebMessage;
|
|
98
123
|
applyStreamFrames(turnId: string, frames: readonly AgentStreamWireFrame[]): WebMessage;
|
|
99
124
|
completeTurn(turnId: string, finalText?: string, metadata?: Readonly<Record<string, unknown>>): WebThreadDetail;
|
|
@@ -112,6 +137,7 @@ export declare class WebStore {
|
|
|
112
137
|
private initialize;
|
|
113
138
|
private validateStorage;
|
|
114
139
|
private recoverInterruptedTurns;
|
|
140
|
+
private recoverLiveInputs;
|
|
115
141
|
private finishTurn;
|
|
116
142
|
private mapThread;
|
|
117
143
|
private mapMessage;
|
|
@@ -121,6 +147,8 @@ export declare class WebStore {
|
|
|
121
147
|
private requireThreadDetail;
|
|
122
148
|
private requireTurn;
|
|
123
149
|
private requireMessage;
|
|
150
|
+
private getLiveInput;
|
|
151
|
+
private requireLiveInput;
|
|
124
152
|
private requireStoredAttachment;
|
|
125
153
|
private recordThreadRevision;
|
|
126
154
|
private setSetting;
|
package/dist/store.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"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"}
|
package/dist/store.js
CHANGED
|
@@ -2,7 +2,8 @@ import { createHash, randomUUID } from "node:crypto";
|
|
|
2
2
|
import { chmod, lstat, readdir, unlink } from "node:fs/promises";
|
|
3
3
|
import { resolve } from "node:path";
|
|
4
4
|
import { DatabaseSync } from "node:sqlite";
|
|
5
|
-
import {
|
|
5
|
+
import { AGENT_LIVE_INPUT_MAX_CHARACTERS, } from "@mono-agent/agent-contracts";
|
|
6
|
+
import { WEB_MAX_FILES_PER_TURN, WEB_MAX_LIVE_INPUTS_PER_THREAD, WEB_MAX_TURN_ATTACHMENT_BYTES, WEB_MAX_TURN_TEXT_CHARACTERS, } from "./contracts.js";
|
|
6
7
|
import { WebConsoleError } from "./errors.js";
|
|
7
8
|
import { prepareWebStatePaths } from "./state-paths.js";
|
|
8
9
|
export class WebStore {
|
|
@@ -38,6 +39,7 @@ export class WebStore {
|
|
|
38
39
|
chmod(`${paths.database}-shm`, 0o600).catch(ignoreMissing),
|
|
39
40
|
]);
|
|
40
41
|
store.recoverInterruptedTurns();
|
|
42
|
+
store.recoverLiveInputs();
|
|
41
43
|
return store;
|
|
42
44
|
}
|
|
43
45
|
catch (error) {
|
|
@@ -213,7 +215,17 @@ export class WebStore {
|
|
|
213
215
|
const thread = this.getThread(id);
|
|
214
216
|
if (thread === undefined)
|
|
215
217
|
return undefined;
|
|
216
|
-
const rows = this.database.prepare(
|
|
218
|
+
const rows = this.database.prepare(`
|
|
219
|
+
SELECT m.* FROM messages m
|
|
220
|
+
LEFT JOIN turns t ON t.id = m.turn_id
|
|
221
|
+
WHERE m.thread_id = ?
|
|
222
|
+
ORDER BY COALESCE(t.started_at, m.created_at),
|
|
223
|
+
CASE WHEN m.turn_id IS NOT NULL AND m.role = 'user' THEN 0
|
|
224
|
+
WHEN m.turn_id IS NOT NULL AND m.role = 'system' THEN 1
|
|
225
|
+
WHEN m.turn_id IS NOT NULL THEN 2
|
|
226
|
+
ELSE 3 END,
|
|
227
|
+
m.created_at, m.rowid
|
|
228
|
+
`).all(id);
|
|
217
229
|
return { thread, messages: rows.map((row) => this.mapMessage(row)) };
|
|
218
230
|
}
|
|
219
231
|
currentThreadId() {
|
|
@@ -251,6 +263,28 @@ export class WebStore {
|
|
|
251
263
|
});
|
|
252
264
|
return { ...this.requireThread(id), sourceId: current.sourceId };
|
|
253
265
|
}
|
|
266
|
+
async deleteArchivedThread(id) {
|
|
267
|
+
const thread = this.requireThread(id);
|
|
268
|
+
if (thread.archivedAt === null) {
|
|
269
|
+
throw new WebConsoleError("thread_not_archived", "Archive the conversation before deleting it.", 409);
|
|
270
|
+
}
|
|
271
|
+
const attachments = this.database.prepare("SELECT * FROM attachments WHERE thread_id = ?")
|
|
272
|
+
.all(id);
|
|
273
|
+
this.transaction(() => {
|
|
274
|
+
this.database.prepare("DELETE FROM notification_deliveries WHERE thread_id = ?").run(id);
|
|
275
|
+
this.database.prepare("DELETE FROM revisions WHERE entity_kind = 'thread' AND entity_id = ?").run(id);
|
|
276
|
+
this.database.prepare("DELETE FROM threads WHERE id = ?").run(id);
|
|
277
|
+
this.database.prepare("DELETE FROM settings WHERE key = 'current_thread_id' AND value = ?").run(id);
|
|
278
|
+
});
|
|
279
|
+
let orphanedFiles = 0;
|
|
280
|
+
for (const row of attachments) {
|
|
281
|
+
await unlink(this.attachmentPath(mapStoredAttachment(row))).catch((error) => {
|
|
282
|
+
if (error.code !== "ENOENT")
|
|
283
|
+
orphanedFiles += 1;
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
return { orphanedFiles };
|
|
287
|
+
}
|
|
254
288
|
createUpload(input) {
|
|
255
289
|
const id = randomUUID();
|
|
256
290
|
const now = this.now();
|
|
@@ -335,6 +369,23 @@ export class WebStore {
|
|
|
335
369
|
}
|
|
336
370
|
return removed;
|
|
337
371
|
}
|
|
372
|
+
async purgeUnreferencedAttachmentFiles() {
|
|
373
|
+
const referenced = new Set(this.database.prepare("SELECT storage_name FROM attachments").all()
|
|
374
|
+
.map((row) => row.storage_name));
|
|
375
|
+
const entries = await readdir(this.paths.uploads, { withFileTypes: true });
|
|
376
|
+
let removed = 0;
|
|
377
|
+
for (const entry of entries) {
|
|
378
|
+
if (!/^[0-9a-f-]{36}\.bin$/iu.test(entry.name) || referenced.has(entry.name))
|
|
379
|
+
continue;
|
|
380
|
+
const path = resolve(this.paths.uploads, entry.name);
|
|
381
|
+
const info = await lstat(path);
|
|
382
|
+
if (!info.isFile() || info.isSymbolicLink())
|
|
383
|
+
continue;
|
|
384
|
+
await unlink(path);
|
|
385
|
+
removed += 1;
|
|
386
|
+
}
|
|
387
|
+
return removed;
|
|
388
|
+
}
|
|
338
389
|
beginTurn(input) {
|
|
339
390
|
const thread = this.requireThread(input.threadId);
|
|
340
391
|
if (thread.archivedAt !== null) {
|
|
@@ -430,6 +481,181 @@ export class WebStore {
|
|
|
430
481
|
thread: this.requireThread(input.threadId),
|
|
431
482
|
};
|
|
432
483
|
}
|
|
484
|
+
reserveLiveInput(threadId, text) {
|
|
485
|
+
const thread = this.requireThread(threadId);
|
|
486
|
+
if (thread.archivedAt !== null) {
|
|
487
|
+
throw new WebConsoleError("thread_archived", "Unarchive this conversation before sending another message.", 409);
|
|
488
|
+
}
|
|
489
|
+
if (!thread.canSend) {
|
|
490
|
+
throw new WebConsoleError("agent_offline", "This agent is offline. The conversation remains available read-only.", 409);
|
|
491
|
+
}
|
|
492
|
+
if (text.trim().length === 0) {
|
|
493
|
+
throw new WebConsoleError("empty_turn", "Enter a message.", 400);
|
|
494
|
+
}
|
|
495
|
+
if (text.length > AGENT_LIVE_INPUT_MAX_CHARACTERS) {
|
|
496
|
+
throw new WebConsoleError("turn_text_too_large", `A live follow-up may contain at most ${AGENT_LIVE_INPUT_MAX_CHARACTERS} characters.`, 413);
|
|
497
|
+
}
|
|
498
|
+
const usage = this.database.prepare("SELECT COUNT(*) AS count FROM live_inputs WHERE thread_id = ?").get(threadId);
|
|
499
|
+
if (usage.count >= WEB_MAX_LIVE_INPUTS_PER_THREAD) {
|
|
500
|
+
throw new WebConsoleError("live_input_queue_full", "Too many follow-up messages are waiting.", 429);
|
|
501
|
+
}
|
|
502
|
+
const active = this.database.prepare("SELECT id, model, effort FROM turns WHERE thread_id = ? AND status = 'running'").get(threadId);
|
|
503
|
+
const id = randomUUID();
|
|
504
|
+
const messageId = randomUUID();
|
|
505
|
+
const now = this.now();
|
|
506
|
+
const status = active === undefined ? "queued" : "offered";
|
|
507
|
+
const parts = [
|
|
508
|
+
liveInputTelemetry(status === "offered" ? "pending" : "queued"),
|
|
509
|
+
{ type: "text", text },
|
|
510
|
+
];
|
|
511
|
+
this.transaction(() => {
|
|
512
|
+
this.database.prepare(`
|
|
513
|
+
INSERT INTO messages (id, thread_id, turn_id, role, parts_json, created_at, updated_at, status)
|
|
514
|
+
VALUES (?, ?, ?, 'user', ?, ?, ?, 'complete')
|
|
515
|
+
`).run(messageId, threadId, active?.id ?? null, JSON.stringify(parts), now, now);
|
|
516
|
+
this.database.prepare(`
|
|
517
|
+
INSERT INTO live_inputs (
|
|
518
|
+
id, thread_id, message_id, active_turn_id, text, model, effort, status, created_at, updated_at
|
|
519
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
520
|
+
`).run(id, threadId, messageId, active?.id ?? null, text, active?.model ?? null, active?.effort ?? null, status, now, now);
|
|
521
|
+
const title = deriveAutomaticTitle(text, []);
|
|
522
|
+
this.database.prepare(`
|
|
523
|
+
UPDATE threads
|
|
524
|
+
SET title = CASE WHEN title_manual = 0 AND title = 'New conversation' THEN ? ELSE title END,
|
|
525
|
+
updated_at = ?, revision = revision + 1
|
|
526
|
+
WHERE id = ?
|
|
527
|
+
`).run(title, now, threadId);
|
|
528
|
+
this.recordThreadRevision(threadId, "live_input_received", now);
|
|
529
|
+
this.setSetting("current_thread_id", threadId);
|
|
530
|
+
});
|
|
531
|
+
const stored = this.requireLiveInput(id);
|
|
532
|
+
return {
|
|
533
|
+
input: mapLiveInput(stored),
|
|
534
|
+
message: this.requireMessage(messageId),
|
|
535
|
+
thread: this.requireThread(threadId),
|
|
536
|
+
offered: status === "offered",
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
markLiveInputApplied(id) {
|
|
540
|
+
const row = this.getLiveInput(id);
|
|
541
|
+
if (row === undefined)
|
|
542
|
+
return undefined;
|
|
543
|
+
const message = this.requireMessage(row.message_id);
|
|
544
|
+
const now = this.now();
|
|
545
|
+
this.transaction(() => {
|
|
546
|
+
this.database.prepare("UPDATE messages SET parts_json = ?, updated_at = ? WHERE id = ?")
|
|
547
|
+
.run(JSON.stringify(withLiveInputStatus(message.parts, "applied")), now, row.message_id);
|
|
548
|
+
this.database.prepare("DELETE FROM live_inputs WHERE id = ?").run(id);
|
|
549
|
+
this.database.prepare("UPDATE threads SET updated_at = ?, revision = revision + 1 WHERE id = ?")
|
|
550
|
+
.run(now, row.thread_id);
|
|
551
|
+
this.recordThreadRevision(row.thread_id, "live_input_applied", now);
|
|
552
|
+
});
|
|
553
|
+
return this.requireMessage(row.message_id);
|
|
554
|
+
}
|
|
555
|
+
queueLiveInput(id) {
|
|
556
|
+
const row = this.getLiveInput(id);
|
|
557
|
+
if (row === undefined)
|
|
558
|
+
return undefined;
|
|
559
|
+
const message = this.requireMessage(row.message_id);
|
|
560
|
+
const now = this.now();
|
|
561
|
+
this.transaction(() => {
|
|
562
|
+
this.database.prepare(`
|
|
563
|
+
UPDATE live_inputs SET status = 'queued', active_turn_id = NULL, updated_at = ? WHERE id = ?
|
|
564
|
+
`).run(now, id);
|
|
565
|
+
this.database.prepare("UPDATE messages SET turn_id = NULL, parts_json = ?, updated_at = ? WHERE id = ?")
|
|
566
|
+
.run(JSON.stringify(withLiveInputStatus(message.parts, "queued")), now, row.message_id);
|
|
567
|
+
this.database.prepare("UPDATE threads SET updated_at = ?, revision = revision + 1 WHERE id = ?")
|
|
568
|
+
.run(now, row.thread_id);
|
|
569
|
+
this.recordThreadRevision(row.thread_id, "live_input_queued", now);
|
|
570
|
+
});
|
|
571
|
+
return this.requireMessage(row.message_id);
|
|
572
|
+
}
|
|
573
|
+
cancelLiveInput(id) {
|
|
574
|
+
const row = this.getLiveInput(id);
|
|
575
|
+
if (row === undefined)
|
|
576
|
+
return undefined;
|
|
577
|
+
const message = this.requireMessage(row.message_id);
|
|
578
|
+
const now = this.now();
|
|
579
|
+
this.transaction(() => {
|
|
580
|
+
this.database.prepare("UPDATE messages SET turn_id = NULL, parts_json = ?, updated_at = ? WHERE id = ?")
|
|
581
|
+
.run(JSON.stringify(withLiveInputStatus(message.parts, "cancelled")), now, row.message_id);
|
|
582
|
+
this.database.prepare("DELETE FROM live_inputs WHERE id = ?").run(id);
|
|
583
|
+
this.database.prepare("UPDATE threads SET updated_at = ?, revision = revision + 1 WHERE id = ?")
|
|
584
|
+
.run(now, row.thread_id);
|
|
585
|
+
this.recordThreadRevision(row.thread_id, "live_input_cancelled", now);
|
|
586
|
+
});
|
|
587
|
+
return this.requireMessage(row.message_id);
|
|
588
|
+
}
|
|
589
|
+
cancelLiveInputs(threadId) {
|
|
590
|
+
const rows = this.database.prepare("SELECT * FROM live_inputs WHERE thread_id = ? ORDER BY created_at, rowid").all(threadId);
|
|
591
|
+
if (rows.length === 0)
|
|
592
|
+
return [];
|
|
593
|
+
const now = this.now();
|
|
594
|
+
this.transaction(() => {
|
|
595
|
+
const update = this.database.prepare("UPDATE messages SET turn_id = NULL, parts_json = ?, updated_at = ? WHERE id = ?");
|
|
596
|
+
for (const row of rows) {
|
|
597
|
+
const message = this.requireMessage(row.message_id);
|
|
598
|
+
update.run(JSON.stringify(withLiveInputStatus(message.parts, "cancelled")), now, row.message_id);
|
|
599
|
+
}
|
|
600
|
+
this.database.prepare("DELETE FROM live_inputs WHERE thread_id = ?").run(threadId);
|
|
601
|
+
this.database.prepare("UPDATE threads SET updated_at = ?, revision = revision + 1 WHERE id = ?")
|
|
602
|
+
.run(now, threadId);
|
|
603
|
+
this.recordThreadRevision(threadId, "live_inputs_cancelled", now);
|
|
604
|
+
});
|
|
605
|
+
return rows.map((row) => this.requireMessage(row.message_id));
|
|
606
|
+
}
|
|
607
|
+
queuedLiveInputThreadIds() {
|
|
608
|
+
return this.database.prepare(`
|
|
609
|
+
SELECT thread_id FROM live_inputs WHERE status = 'queued'
|
|
610
|
+
GROUP BY thread_id ORDER BY MIN(created_at), thread_id
|
|
611
|
+
`).all().map((row) => row.thread_id);
|
|
612
|
+
}
|
|
613
|
+
promoteNextQueuedLiveInput(threadId) {
|
|
614
|
+
const active = this.database.prepare("SELECT id FROM turns WHERE thread_id = ? AND status = 'running'").get(threadId);
|
|
615
|
+
if (active !== undefined)
|
|
616
|
+
return undefined;
|
|
617
|
+
const row = this.database.prepare(`
|
|
618
|
+
SELECT * FROM live_inputs
|
|
619
|
+
WHERE thread_id = ? AND status = 'queued'
|
|
620
|
+
ORDER BY created_at, rowid LIMIT 1
|
|
621
|
+
`).get(threadId);
|
|
622
|
+
if (row === undefined)
|
|
623
|
+
return undefined;
|
|
624
|
+
const thread = this.requireThread(threadId);
|
|
625
|
+
if (!thread.canSend || thread.archivedAt !== null)
|
|
626
|
+
return undefined;
|
|
627
|
+
const turnId = randomUUID();
|
|
628
|
+
const assistantMessageId = randomUUID();
|
|
629
|
+
const now = this.now();
|
|
630
|
+
const userMessage = this.requireMessage(row.message_id);
|
|
631
|
+
this.transaction(() => {
|
|
632
|
+
this.database.prepare(`
|
|
633
|
+
INSERT INTO turns (
|
|
634
|
+
id, thread_id, status, text, model, effort, assistant_message_id,
|
|
635
|
+
started_at, finished_at, error_code, error_message
|
|
636
|
+
) VALUES (?, ?, 'running', ?, ?, ?, ?, ?, NULL, NULL, NULL)
|
|
637
|
+
`).run(turnId, threadId, row.text, row.model, row.effort, assistantMessageId, now);
|
|
638
|
+
this.database.prepare("UPDATE messages SET turn_id = ?, parts_json = ?, updated_at = ? WHERE id = ?")
|
|
639
|
+
.run(turnId, JSON.stringify(withoutLiveInputTelemetry(userMessage.parts)), now, row.message_id);
|
|
640
|
+
this.database.prepare(`
|
|
641
|
+
INSERT INTO messages (id, thread_id, turn_id, role, parts_json, created_at, updated_at, status)
|
|
642
|
+
VALUES (?, ?, ?, 'assistant', '[]', ?, ?, 'running')
|
|
643
|
+
`).run(assistantMessageId, threadId, turnId, now, now);
|
|
644
|
+
this.database.prepare("DELETE FROM live_inputs WHERE id = ?").run(row.id);
|
|
645
|
+
this.database.prepare("UPDATE threads SET updated_at = ?, revision = revision + 1 WHERE id = ?")
|
|
646
|
+
.run(now, threadId);
|
|
647
|
+
this.recordThreadRevision(threadId, "turn_started", now);
|
|
648
|
+
});
|
|
649
|
+
return {
|
|
650
|
+
turnId,
|
|
651
|
+
conversationId: `web:${threadId}`,
|
|
652
|
+
text: row.text,
|
|
653
|
+
userMessageId: row.message_id,
|
|
654
|
+
assistantMessageId,
|
|
655
|
+
attachments: [],
|
|
656
|
+
thread: this.requireThread(threadId),
|
|
657
|
+
};
|
|
658
|
+
}
|
|
433
659
|
applyStreamFrame(turnId, frame) {
|
|
434
660
|
return this.applyStreamFrames(turnId, [frame]);
|
|
435
661
|
}
|
|
@@ -502,13 +728,13 @@ export class WebStore {
|
|
|
502
728
|
try {
|
|
503
729
|
this.database.exec("PRAGMA foreign_keys = ON; PRAGMA journal_mode = WAL; PRAGMA synchronous = NORMAL;");
|
|
504
730
|
const versionRow = this.database.prepare("PRAGMA user_version").get();
|
|
505
|
-
if (versionRow.user_version >
|
|
506
|
-
throw new WebConsoleError("unsupported_storage_schema", `Web state schema ${versionRow.user_version} is newer than supported schema
|
|
731
|
+
if (versionRow.user_version > 3) {
|
|
732
|
+
throw new WebConsoleError("unsupported_storage_schema", `Web state schema ${versionRow.user_version} is newer than supported schema 3.`, 500);
|
|
507
733
|
}
|
|
508
734
|
if (versionRow.user_version < 0) {
|
|
509
735
|
throw new WebConsoleError("storage_corrupt", "Web state schema version is invalid.", 500);
|
|
510
736
|
}
|
|
511
|
-
const migrating = versionRow.user_version <
|
|
737
|
+
const migrating = versionRow.user_version < 3;
|
|
512
738
|
if (migrating)
|
|
513
739
|
this.database.exec("BEGIN IMMEDIATE");
|
|
514
740
|
try {
|
|
@@ -564,6 +790,20 @@ export class WebStore {
|
|
|
564
790
|
status TEXT NOT NULL
|
|
565
791
|
);
|
|
566
792
|
CREATE INDEX IF NOT EXISTS messages_by_thread ON messages(thread_id, created_at);
|
|
793
|
+
CREATE TABLE IF NOT EXISTS live_inputs (
|
|
794
|
+
id TEXT PRIMARY KEY,
|
|
795
|
+
thread_id TEXT NOT NULL REFERENCES threads(id) ON DELETE CASCADE,
|
|
796
|
+
message_id TEXT NOT NULL UNIQUE REFERENCES messages(id) ON DELETE CASCADE,
|
|
797
|
+
active_turn_id TEXT REFERENCES turns(id) ON DELETE SET NULL,
|
|
798
|
+
text TEXT NOT NULL,
|
|
799
|
+
model TEXT,
|
|
800
|
+
effort TEXT,
|
|
801
|
+
status TEXT NOT NULL CHECK (status IN ('offered', 'queued')),
|
|
802
|
+
created_at TEXT NOT NULL,
|
|
803
|
+
updated_at TEXT NOT NULL
|
|
804
|
+
);
|
|
805
|
+
CREATE INDEX IF NOT EXISTS live_inputs_by_thread
|
|
806
|
+
ON live_inputs(thread_id, status, created_at);
|
|
567
807
|
CREATE TABLE IF NOT EXISTS attachments (
|
|
568
808
|
id TEXT PRIMARY KEY,
|
|
569
809
|
thread_id TEXT REFERENCES threads(id) ON DELETE CASCADE,
|
|
@@ -610,7 +850,7 @@ export class WebStore {
|
|
|
610
850
|
}
|
|
611
851
|
}
|
|
612
852
|
if (migrating)
|
|
613
|
-
this.database.exec("PRAGMA user_version =
|
|
853
|
+
this.database.exec("PRAGMA user_version = 3; COMMIT");
|
|
614
854
|
}
|
|
615
855
|
catch (error) {
|
|
616
856
|
if (this.database.isTransaction)
|
|
@@ -635,6 +875,7 @@ export class WebStore {
|
|
|
635
875
|
"threads",
|
|
636
876
|
"turns",
|
|
637
877
|
"messages",
|
|
878
|
+
"live_inputs",
|
|
638
879
|
"attachments",
|
|
639
880
|
"revisions",
|
|
640
881
|
"settings",
|
|
@@ -662,6 +903,33 @@ export class WebStore {
|
|
|
662
903
|
this.interruptTurn(turnId, "The web service restarted before this turn completed.");
|
|
663
904
|
}
|
|
664
905
|
}
|
|
906
|
+
recoverLiveInputs() {
|
|
907
|
+
const rows = this.database.prepare("SELECT * FROM live_inputs WHERE status = 'offered' ORDER BY created_at, rowid").all();
|
|
908
|
+
if (rows.length === 0)
|
|
909
|
+
return;
|
|
910
|
+
const now = this.now();
|
|
911
|
+
const threadIds = new Set(rows.map((row) => row.thread_id));
|
|
912
|
+
this.transaction(() => {
|
|
913
|
+
const updateInput = this.database.prepare(`
|
|
914
|
+
UPDATE live_inputs SET status = 'queued', active_turn_id = NULL, updated_at = ? WHERE id = ?
|
|
915
|
+
`);
|
|
916
|
+
const updateMessage = this.database.prepare("UPDATE messages SET turn_id = NULL, parts_json = ?, updated_at = ? WHERE id = ?");
|
|
917
|
+
for (const row of rows) {
|
|
918
|
+
const persisted = this.database.prepare("SELECT parts_json FROM messages WHERE id = ?")
|
|
919
|
+
.get(row.message_id);
|
|
920
|
+
if (persisted === undefined) {
|
|
921
|
+
throw new WebConsoleError("storage_corrupt", `Live input ${row.id} has no message.`, 500);
|
|
922
|
+
}
|
|
923
|
+
updateInput.run(now, row.id);
|
|
924
|
+
updateMessage.run(JSON.stringify(withLiveInputStatus(parseParts(persisted.parts_json), "queued")), now, row.message_id);
|
|
925
|
+
}
|
|
926
|
+
for (const threadId of threadIds) {
|
|
927
|
+
this.database.prepare("UPDATE threads SET updated_at = ?, revision = revision + 1 WHERE id = ?")
|
|
928
|
+
.run(now, threadId);
|
|
929
|
+
this.recordThreadRevision(threadId, "live_inputs_recovered", now);
|
|
930
|
+
}
|
|
931
|
+
});
|
|
932
|
+
}
|
|
665
933
|
finishTurn(turnId, status, finalText, errorCode, errorMessage, runtime) {
|
|
666
934
|
const turn = this.requireTurn(turnId);
|
|
667
935
|
const existing = this.requireMessage(turn.assistant_message_id);
|
|
@@ -716,17 +984,20 @@ export class WebStore {
|
|
|
716
984
|
.all(row.id);
|
|
717
985
|
const storedParts = parseParts(row.parts_json);
|
|
718
986
|
const quote = quoteFromParts(storedParts);
|
|
987
|
+
const liveInputStatus = liveInputStatusFromParts(storedParts);
|
|
719
988
|
return {
|
|
720
989
|
id: row.id,
|
|
721
990
|
threadId: row.thread_id,
|
|
722
991
|
...(row.turn_id === null ? {} : { turnId: row.turn_id }),
|
|
723
992
|
role: normalizeRole(row.role),
|
|
724
993
|
...(quote === undefined ? {} : { quote }),
|
|
725
|
-
parts: storedParts.filter((part) => part.type !== "telemetry"
|
|
994
|
+
parts: storedParts.filter((part) => part.type !== "telemetry"
|
|
995
|
+
|| (part.event !== QUOTE_TELEMETRY_EVENT && part.event !== LIVE_INPUT_TELEMETRY_EVENT)),
|
|
726
996
|
attachments: attachments.map((attachment) => toWebAttachment(mapStoredAttachment(attachment))),
|
|
727
997
|
createdAt: row.created_at,
|
|
728
998
|
updatedAt: row.updated_at,
|
|
729
999
|
status: normalizeMessageStatus(row.status),
|
|
1000
|
+
...(liveInputStatus === undefined ? {} : { liveInputStatus }),
|
|
730
1001
|
};
|
|
731
1002
|
}
|
|
732
1003
|
latestRunState(threadId) {
|
|
@@ -784,6 +1055,16 @@ export class WebStore {
|
|
|
784
1055
|
throw new WebConsoleError("message_not_found", "Message not found.", 404);
|
|
785
1056
|
return this.mapMessage(row);
|
|
786
1057
|
}
|
|
1058
|
+
getLiveInput(id) {
|
|
1059
|
+
return this.database.prepare("SELECT * FROM live_inputs WHERE id = ?")
|
|
1060
|
+
.get(id);
|
|
1061
|
+
}
|
|
1062
|
+
requireLiveInput(id) {
|
|
1063
|
+
const row = this.getLiveInput(id);
|
|
1064
|
+
if (row === undefined)
|
|
1065
|
+
throw new WebConsoleError("live_input_not_found", "Live input not found.", 404);
|
|
1066
|
+
return row;
|
|
1067
|
+
}
|
|
787
1068
|
requireStoredAttachment(id) {
|
|
788
1069
|
const attachment = this.getStoredAttachment(id);
|
|
789
1070
|
if (attachment === undefined)
|
|
@@ -890,6 +1171,16 @@ function mapStoredAttachment(row) {
|
|
|
890
1171
|
updatedAt: row.updated_at,
|
|
891
1172
|
};
|
|
892
1173
|
}
|
|
1174
|
+
function mapLiveInput(row) {
|
|
1175
|
+
return {
|
|
1176
|
+
id: row.id,
|
|
1177
|
+
threadId: row.thread_id,
|
|
1178
|
+
messageId: row.message_id,
|
|
1179
|
+
text: row.text,
|
|
1180
|
+
status: row.status,
|
|
1181
|
+
createdAt: row.created_at,
|
|
1182
|
+
};
|
|
1183
|
+
}
|
|
893
1184
|
export function toWebAttachment(attachment) {
|
|
894
1185
|
return {
|
|
895
1186
|
id: attachment.id,
|
|
@@ -939,8 +1230,37 @@ function applyEvent(parts, event) {
|
|
|
939
1230
|
});
|
|
940
1231
|
return;
|
|
941
1232
|
}
|
|
1233
|
+
if (event.type === "runtime_telemetry" && event.kind === "context_compaction") {
|
|
1234
|
+
upsertContextCompaction(parts, event);
|
|
1235
|
+
return;
|
|
1236
|
+
}
|
|
942
1237
|
parts.push({ type: "telemetry", event: event.type, data: event });
|
|
943
1238
|
}
|
|
1239
|
+
function contextCompactionOperationId(value) {
|
|
1240
|
+
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
1241
|
+
return undefined;
|
|
1242
|
+
const event = value;
|
|
1243
|
+
if (event.type !== "runtime_telemetry" || event.kind !== "context_compaction")
|
|
1244
|
+
return undefined;
|
|
1245
|
+
const data = event.data;
|
|
1246
|
+
if (data === null || typeof data !== "object" || Array.isArray(data))
|
|
1247
|
+
return undefined;
|
|
1248
|
+
const operationId = data.operationId;
|
|
1249
|
+
return typeof operationId === "string" && operationId.length > 0 ? operationId : undefined;
|
|
1250
|
+
}
|
|
1251
|
+
function upsertContextCompaction(parts, event) {
|
|
1252
|
+
const operationId = contextCompactionOperationId(event);
|
|
1253
|
+
const next = { type: "telemetry", event: event.type, data: event };
|
|
1254
|
+
if (operationId === undefined) {
|
|
1255
|
+
parts.push(next);
|
|
1256
|
+
return;
|
|
1257
|
+
}
|
|
1258
|
+
const index = parts.findIndex((part) => part.type === "telemetry" && contextCompactionOperationId(part.data) === operationId);
|
|
1259
|
+
if (index < 0)
|
|
1260
|
+
parts.push(next);
|
|
1261
|
+
else
|
|
1262
|
+
parts[index] = next;
|
|
1263
|
+
}
|
|
944
1264
|
function existingToolName(parts, id) {
|
|
945
1265
|
const existing = parts.find((part) => part.type === "tool-call" && part.toolCallId === id);
|
|
946
1266
|
return existing?.type === "tool-call" ? existing.toolName : undefined;
|
|
@@ -1035,6 +1355,33 @@ function parseParts(value) {
|
|
|
1035
1355
|
return parsed;
|
|
1036
1356
|
}
|
|
1037
1357
|
const QUOTE_TELEMETRY_EVENT = "quote";
|
|
1358
|
+
const LIVE_INPUT_TELEMETRY_EVENT = "live_input";
|
|
1359
|
+
function liveInputTelemetry(status) {
|
|
1360
|
+
return { type: "telemetry", event: LIVE_INPUT_TELEMETRY_EVENT, data: { status } };
|
|
1361
|
+
}
|
|
1362
|
+
function withoutLiveInputTelemetry(parts) {
|
|
1363
|
+
return parts.filter((part) => part.type !== "telemetry" || part.event !== LIVE_INPUT_TELEMETRY_EVENT);
|
|
1364
|
+
}
|
|
1365
|
+
function withLiveInputStatus(parts, status) {
|
|
1366
|
+
return [liveInputTelemetry(status), ...withoutLiveInputTelemetry(parts)];
|
|
1367
|
+
}
|
|
1368
|
+
function liveInputStatusFromParts(parts) {
|
|
1369
|
+
const markers = parts.filter((part) => part.type === "telemetry" && part.event === LIVE_INPUT_TELEMETRY_EVENT);
|
|
1370
|
+
if (markers.length === 0)
|
|
1371
|
+
return undefined;
|
|
1372
|
+
if (markers.length !== 1) {
|
|
1373
|
+
throw new WebConsoleError("storage_corrupt", "Persisted live-input metadata is duplicated.", 500);
|
|
1374
|
+
}
|
|
1375
|
+
const data = markers[0]?.data;
|
|
1376
|
+
if (typeof data !== "object" || data === null || Array.isArray(data)) {
|
|
1377
|
+
throw new WebConsoleError("storage_corrupt", "Persisted live-input metadata is invalid.", 500);
|
|
1378
|
+
}
|
|
1379
|
+
const status = data.status;
|
|
1380
|
+
if (status !== "pending" && status !== "applied" && status !== "queued" && status !== "cancelled") {
|
|
1381
|
+
throw new WebConsoleError("storage_corrupt", "Persisted live-input status is invalid.", 500);
|
|
1382
|
+
}
|
|
1383
|
+
return status;
|
|
1384
|
+
}
|
|
1038
1385
|
function quoteFromParts(parts) {
|
|
1039
1386
|
const markers = parts.filter((part) => part.type === "telemetry" && part.event === QUOTE_TELEMETRY_EVENT);
|
|
1040
1387
|
if (markers.length === 0)
|