@fungi.computer/shiitake 0.5.0 → 0.7.0
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 +59 -21
- package/dist/agent-control.d.ts +94 -2
- package/dist/agent-control.js +147 -26
- package/dist/client-entry.d.ts +20 -0
- package/dist/client-entry.js +5 -0
- package/dist/client-visitor.d.ts +33 -0
- package/dist/client-visitor.js +231 -0
- package/dist/client-wire.d.ts +9 -0
- package/dist/client-wire.js +24 -3
- package/dist/client.d.ts +27 -6
- package/dist/client.js +23 -0
- package/dist/cloudflare.d.ts +20 -0
- package/dist/cloudflare.js +4 -1
- package/dist/delegation.d.ts +79 -53
- package/dist/delegation.js +26 -21
- package/dist/effect.d.ts +26 -1
- package/dist/effect.js +14 -1
- package/dist/errors.d.ts +2 -2
- package/dist/errors.js +2 -0
- package/dist/extensions.d.ts +22 -3
- package/dist/extensions.js +1 -1
- package/dist/fleet.d.ts +25 -2
- package/dist/fleet.js +108 -24
- package/dist/hosted.js +3 -0
- package/dist/http-routes.d.ts +23 -9
- package/dist/http.d.ts +1 -0
- package/dist/http.js +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/internal/accepted-run-executor.js +2 -1
- package/dist/internal/admission-door.d.ts +6 -1
- package/dist/internal/admission-door.js +33 -3
- package/dist/internal/contracts.d.ts +19 -1
- package/dist/internal/coordinator.js +115 -52
- package/dist/internal/effect-client.js +1 -1
- package/dist/internal/effect-contracts.d.ts +9 -2
- package/dist/internal/extension-command-registry.d.ts +2 -1
- package/dist/internal/extension-command-registry.js +3 -1
- package/dist/internal/extension-runtime.d.ts +3 -1
- package/dist/internal/extension-runtime.js +18 -8
- package/dist/internal/model-acquisition.js +2 -0
- package/dist/internal/observation-kernel.d.ts +2 -0
- package/dist/internal/observation-kernel.js +17 -6
- package/dist/internal/promise-facade.js +2 -0
- package/dist/internal/run-scope.d.ts +14 -0
- package/dist/internal/run-scope.js +58 -15
- package/dist/internal/runtime-coordinator.d.ts +14 -0
- package/dist/internal/runtime-coordinator.js +60 -11
- package/dist/internal/runtime-core.d.ts +3 -0
- package/dist/internal/runtime-core.js +1 -0
- package/dist/internal/runtime-reconciliation.js +123 -25
- package/dist/internal/session-context.d.ts +5 -5
- package/dist/internal/session-context.js +80 -83
- package/dist/internal/session-status.d.ts +2 -2
- package/dist/internal/session-status.js +5 -3
- package/dist/internal/woodstock/agent-reply.d.ts +5 -0
- package/dist/internal/woodstock/agent-reply.js +3 -0
- package/dist/internal/woodstock/client-projection.js +13 -11
- package/dist/internal/woodstock/fleet-store.d.ts +3 -2
- package/dist/internal/woodstock/fleet-store.js +24 -4
- package/dist/internal/woodstock/index.d.ts +7 -3
- package/dist/internal/woodstock/index.js +23 -6
- package/dist/internal/woodstock/progress.d.ts +11 -1
- package/dist/internal/woodstock/progress.js +126 -25
- package/dist/internal/woodstock/session-delegator.d.ts +13 -0
- package/dist/internal/woodstock/session-delegator.js +44 -0
- package/dist/internal/woodstock/session-entry.d.ts +12 -2
- package/dist/internal/woodstock/session-store.d.ts +16 -2
- package/dist/internal/woodstock/session-store.js +349 -66
- package/dist/internal/woodstock/work-senders.d.ts +32 -0
- package/dist/internal/woodstock/work-senders.js +70 -0
- package/dist/node.js +11 -2
- package/dist/remote-client.js +7 -2
- package/dist/server-visitor-stream.d.ts +13 -0
- package/dist/server-visitor-stream.js +124 -0
- package/dist/session-attention.d.ts +30 -0
- package/dist/session-attention.js +14 -0
- package/docs/api/agent-control.md +212 -15
- package/docs/api/client-entry.md +3 -0
- package/docs/api/client.md +209 -5
- package/docs/api/cloudflare.md +23 -1
- package/docs/api/delegation.md +284 -77
- package/docs/api/effect.md +100 -12
- package/docs/api/extensions.md +73 -12
- package/docs/api/fleet.md +138 -25
- package/docs/api/http.md +28 -0
- package/docs/api/index.md +43 -17
- package/docs/api/testing.md +9 -7
- package/package.json +10 -6
|
@@ -7,6 +7,8 @@ import { compactionSummaryMessage } from "../compaction/utils.js";
|
|
|
7
7
|
import { SessionTitleSchema, sessionTitleLimit } from "../client-boundary.js";
|
|
8
8
|
import { commandTimestamp, decodeStoredSessionEntry, encodeStoredSessionEntry, isCompactCommand, commandContextMessage, operatorContextMessage, promptCommand, } from "./session-entry.js";
|
|
9
9
|
import { prepareSessionAttention } from "./session-attention.js";
|
|
10
|
+
import { prepareWorkSenders } from "./work-senders.js";
|
|
11
|
+
import { prepareSessionDelegators } from "./session-delegator.js";
|
|
10
12
|
import { copySessionGrants, prepareSessionGrants } from "./session-grants.js";
|
|
11
13
|
import { prepareSessionInbox, touchSessionInbox } from "./session-inbox.js";
|
|
12
14
|
import { sessionIds } from "./identities.js";
|
|
@@ -268,9 +270,9 @@ function selectedLineageFirstPromptId(owner, sessionId, leafId) {
|
|
|
268
270
|
UNION
|
|
269
271
|
SELECT parent.id, parent.parent_id, parent.sequence,
|
|
270
272
|
parent.type, parent.role, parent.state, parent.work_id
|
|
271
|
-
FROM
|
|
272
|
-
JOIN
|
|
273
|
-
WHERE parent.session_id = ?
|
|
273
|
+
FROM branch AS child
|
|
274
|
+
CROSS JOIN session_entries AS parent
|
|
275
|
+
WHERE parent.id = child.parent_id AND parent.session_id = ?
|
|
274
276
|
)
|
|
275
277
|
SELECT
|
|
276
278
|
(SELECT command.id FROM branch AS command
|
|
@@ -534,6 +536,12 @@ const pendingAdmissions = `FROM session_command_admissions AS a
|
|
|
534
536
|
WHERE e.session_id = a.session_id AND e.work_id = a.work_id
|
|
535
537
|
AND e.type IN ('command', 'cancellation', 'run_terminal', 'work_terminal')
|
|
536
538
|
)`;
|
|
539
|
+
/** A written run terminal closes the window for attaching another steer. */
|
|
540
|
+
export function hasRecordedRunTerminal(owner, sessionId, workId) {
|
|
541
|
+
return (owner.sql
|
|
542
|
+
.exec("SELECT 1 FROM session_entries WHERE session_id = ? AND work_id = ? AND type = 'run_terminal' LIMIT 1", sessionId, workId)
|
|
543
|
+
.toArray().length > 0);
|
|
544
|
+
}
|
|
537
545
|
const decodeIdentity = Schema.decodeUnknownSync(Schema.Struct({ identity: Schema.NullOr(Schema.String) }));
|
|
538
546
|
/** Extract the host-selected principal without giving the engine domain grammar. */
|
|
539
547
|
export function readAdmissionIdentity(owner, path, meta) {
|
|
@@ -576,7 +584,8 @@ export function readPendingSessionCommands(owner, sessionId, limit, only = "all"
|
|
|
576
584
|
.one()).total;
|
|
577
585
|
const commands = owner.sql
|
|
578
586
|
.exec(`SELECT ${admissionColumns("a.")} ${pendingAdmissions} ${filter}
|
|
579
|
-
ORDER BY a.
|
|
587
|
+
ORDER BY (json_extract(a.data_json, '$.delivery') = 'steer') DESC,
|
|
588
|
+
a.sequence ASC LIMIT ?`, sessionId, limit)
|
|
580
589
|
.toArray()
|
|
581
590
|
.map(parseCommandAdmissionRow);
|
|
582
591
|
return { commands, total };
|
|
@@ -645,6 +654,7 @@ export function settleSessionControls(owner, input) {
|
|
|
645
654
|
jobId: row.entry.workId,
|
|
646
655
|
sessionId: input.sessionId,
|
|
647
656
|
commandFactId: row.id,
|
|
657
|
+
delivery: "steer",
|
|
648
658
|
});
|
|
649
659
|
continue;
|
|
650
660
|
}
|
|
@@ -680,6 +690,18 @@ function readSessionCommandAdmissionByWorkId(owner, sessionId, workId) {
|
|
|
680
690
|
export function readSessionCommandAdmissionByRequest(owner, sessionId, requestId) {
|
|
681
691
|
return readCommandAdmission(owner, sessionId, { requestId });
|
|
682
692
|
}
|
|
693
|
+
/** Probe the indexed terminal facts without materializing a live work's body. */
|
|
694
|
+
export function hasSessionWorkTerminal(owner, sessionId, workId) {
|
|
695
|
+
// Keep the readability check even when the work has no terminal rows.
|
|
696
|
+
readSessionRow(owner, sessionId);
|
|
697
|
+
return (owner.sql
|
|
698
|
+
.exec(`SELECT 1 AS present FROM session_entries
|
|
699
|
+
INDEXED BY idx_session_entries_work_terminal
|
|
700
|
+
WHERE session_id = ? AND work_id = ?
|
|
701
|
+
AND type IN ('run_terminal', 'work_terminal')
|
|
702
|
+
LIMIT 1`, sessionId, workId)
|
|
703
|
+
.toArray().length > 0);
|
|
704
|
+
}
|
|
683
705
|
/** Read entries correlated to one accepted work identity in Session order. */
|
|
684
706
|
export function readSessionEntriesForWork(owner, sessionId, workId) {
|
|
685
707
|
// Refuse the Session before selecting entries: a damaged work may have no
|
|
@@ -832,6 +854,7 @@ export function deleteStoredSession(owner, input) {
|
|
|
832
854
|
"DELETE FROM session_command_admissions WHERE session_id = ?",
|
|
833
855
|
// The inbox row references the Session; it goes in the same transaction.
|
|
834
856
|
"DELETE FROM session_attention WHERE session_id = ?",
|
|
857
|
+
"DELETE FROM session_work_senders WHERE session_id = ?",
|
|
835
858
|
"DELETE FROM session_tool_grants WHERE session_id = ?",
|
|
836
859
|
"DELETE FROM session_inbox WHERE session_id = ?",
|
|
837
860
|
"DELETE FROM session_creation_receipts WHERE session_id = ?",
|
|
@@ -1128,6 +1151,8 @@ export function prepareSessionStore(owner) {
|
|
|
1128
1151
|
createSessionEntryTable(owner);
|
|
1129
1152
|
prepareSessionDocuments(owner);
|
|
1130
1153
|
prepareSessionAttention(owner);
|
|
1154
|
+
prepareWorkSenders(owner);
|
|
1155
|
+
prepareSessionDelegators(owner);
|
|
1131
1156
|
prepareSessionGrants(owner);
|
|
1132
1157
|
prepareSessionInbox(owner);
|
|
1133
1158
|
owner.sql.exec(`
|
|
@@ -1136,13 +1161,28 @@ export function prepareSessionStore(owner) {
|
|
|
1136
1161
|
session_id TEXT NOT NULL UNIQUE REFERENCES sessions(id)
|
|
1137
1162
|
)
|
|
1138
1163
|
`);
|
|
1164
|
+
// Preview admissions could have mistaken a later worker for the creator.
|
|
1165
|
+
owner.sql.exec(`DELETE FROM session_delegators
|
|
1166
|
+
WHERE NOT EXISTS (
|
|
1167
|
+
SELECT 1 FROM session_creation_receipts AS creation
|
|
1168
|
+
WHERE creation.session_id = session_delegators.session_id
|
|
1169
|
+
AND creation.request_id = session_delegators.mail_key
|
|
1170
|
+
)`);
|
|
1139
1171
|
owner.sql.exec("CREATE INDEX IF NOT EXISTS idx_session_entries_parent ON session_entries(parent_id)");
|
|
1172
|
+
// Replace the preview index, not its payload-length expression: preparing
|
|
1173
|
+
// identity lookup must never read every stored message's payload.
|
|
1174
|
+
owner.sql.exec("DROP INDEX IF EXISTS idx_session_entries_lineage");
|
|
1175
|
+
owner.sql.exec(`CREATE INDEX IF NOT EXISTS idx_session_entries_ancestry
|
|
1176
|
+
ON session_entries(id, session_id, parent_id, sequence, state, type)`);
|
|
1140
1177
|
owner.sql.exec("CREATE INDEX IF NOT EXISTS idx_session_entries_session ON session_entries(session_id, sequence)");
|
|
1141
1178
|
owner.sql.exec("CREATE INDEX IF NOT EXISTS idx_session_entries_streaming ON session_entries(session_id, state)");
|
|
1142
1179
|
owner.sql.exec(`CREATE UNIQUE INDEX IF NOT EXISTS idx_session_entries_request
|
|
1143
1180
|
ON session_entries(session_id, request_id)
|
|
1144
1181
|
WHERE request_id IS NOT NULL`);
|
|
1145
1182
|
owner.sql.exec("CREATE INDEX IF NOT EXISTS idx_session_entries_work ON session_entries(work_id, sequence)");
|
|
1183
|
+
owner.sql.exec(`CREATE INDEX IF NOT EXISTS idx_session_entries_work_terminal
|
|
1184
|
+
ON session_entries(session_id, work_id)
|
|
1185
|
+
WHERE type IN ('run_terminal', 'work_terminal')`);
|
|
1146
1186
|
// Context and preference reads look up these rare older facts directly
|
|
1147
1187
|
// rather than walking the history behind them.
|
|
1148
1188
|
owner.sql.exec(`CREATE INDEX IF NOT EXISTS idx_session_entries_model_preference
|
|
@@ -1339,6 +1379,8 @@ const lineageByteLimit = 4 * 1_048_576;
|
|
|
1339
1379
|
* selected walk steps only to an older parent, so it ends without
|
|
1340
1380
|
* deduplicating; the fork walk keeps every link for its whole-lineage check.
|
|
1341
1381
|
*/
|
|
1382
|
+
// Keep each recursive step child-first: SQLite must look up that child's
|
|
1383
|
+
// parent, not scan every entry of the Session before joining the walk.
|
|
1342
1384
|
const lineageWalks = {
|
|
1343
1385
|
selected: `WITH RECURSIVE branch (id, parent_id, sequence, state) AS (
|
|
1344
1386
|
SELECT id, parent_id, sequence, state
|
|
@@ -1346,9 +1388,10 @@ const lineageWalks = {
|
|
|
1346
1388
|
WHERE session_id = ? AND id = ?
|
|
1347
1389
|
UNION ALL
|
|
1348
1390
|
SELECT parent.id, parent.parent_id, parent.sequence, parent.state
|
|
1349
|
-
FROM
|
|
1350
|
-
JOIN
|
|
1351
|
-
WHERE parent.
|
|
1391
|
+
FROM branch AS child
|
|
1392
|
+
CROSS JOIN session_entries AS parent
|
|
1393
|
+
WHERE parent.id = child.parent_id AND parent.session_id = ?
|
|
1394
|
+
AND child.sequence > ?
|
|
1352
1395
|
AND +parent.sequence < child.sequence
|
|
1353
1396
|
)`,
|
|
1354
1397
|
fork: `WITH RECURSIVE branch (id, parent_id, sequence, state) AS (
|
|
@@ -1357,9 +1400,10 @@ const lineageWalks = {
|
|
|
1357
1400
|
WHERE session_id = ? AND id = ? AND state != 'streaming'
|
|
1358
1401
|
UNION
|
|
1359
1402
|
SELECT parent.id, parent.parent_id, parent.sequence, parent.state
|
|
1360
|
-
FROM
|
|
1361
|
-
JOIN
|
|
1362
|
-
WHERE parent.
|
|
1403
|
+
FROM branch AS child
|
|
1404
|
+
CROSS JOIN session_entries AS parent
|
|
1405
|
+
WHERE parent.id = child.parent_id AND parent.session_id = ?
|
|
1406
|
+
AND child.sequence > ?
|
|
1363
1407
|
AND parent.state != 'streaming'
|
|
1364
1408
|
)`,
|
|
1365
1409
|
};
|
|
@@ -1375,6 +1419,130 @@ const lineageFailures = {
|
|
|
1375
1419
|
invalid: "Fork path is invalid",
|
|
1376
1420
|
},
|
|
1377
1421
|
};
|
|
1422
|
+
const decodeAncestryRow = Schema.decodeUnknownResult(Schema.Struct({
|
|
1423
|
+
id: boundedId,
|
|
1424
|
+
parent_id: Schema.NullOr(Schema.String),
|
|
1425
|
+
sequence,
|
|
1426
|
+
state: Schema.Literals(["complete", "streaming"]),
|
|
1427
|
+
}), closed);
|
|
1428
|
+
const sessionReads = new WeakMap();
|
|
1429
|
+
/**
|
|
1430
|
+
* One synchronous, transaction-consistent Session baseline. Activity, timeline,
|
|
1431
|
+
* extension projections and context figures share its identity-only ancestry;
|
|
1432
|
+
* asynchronous model description runs after this scope has ended. Nothing is
|
|
1433
|
+
* retained between reads, and payload windows keep their row and byte bounds.
|
|
1434
|
+
*/
|
|
1435
|
+
export function withSessionRead(owner, sessionId, read, before = Number.MAX_SAFE_INTEGER) {
|
|
1436
|
+
const previous = sessionReads.get(owner);
|
|
1437
|
+
sessionReads.set(owner, { sessionId, before, encoded: new Map() });
|
|
1438
|
+
try {
|
|
1439
|
+
return owner.transactionSync(read);
|
|
1440
|
+
}
|
|
1441
|
+
finally {
|
|
1442
|
+
if (previous === undefined)
|
|
1443
|
+
sessionReads.delete(owner);
|
|
1444
|
+
else
|
|
1445
|
+
sessionReads.set(owner, previous);
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
/** Join the shared Session baseline, or keep a direct projection atomic. */
|
|
1449
|
+
export function withSessionReadTransaction(owner, sessionId, read) {
|
|
1450
|
+
return sessionReads.get(owner)?.sessionId === sessionId
|
|
1451
|
+
? read()
|
|
1452
|
+
: owner.transactionSync(read);
|
|
1453
|
+
}
|
|
1454
|
+
/**
|
|
1455
|
+
* Walk identities once to the kept context boundary and the requested page's
|
|
1456
|
+
* row lookahead, whichever needs the older floor. Only compactions need their
|
|
1457
|
+
* payload to identify that boundary. A historical page's intervening gap is
|
|
1458
|
+
* traversed in SQL, not materialized into this request's identity cache.
|
|
1459
|
+
*
|
|
1460
|
+
* A context's kept entry can reuse the suffix starting at that ancestor. A later
|
|
1461
|
+
* tip may share only if it is the seed or an ancestor retained in that suffix;
|
|
1462
|
+
* another valid leaf uses its own walk instead of becoming an unavailable read.
|
|
1463
|
+
* A query crossing an omitted gap or asking below the cached floor also uses
|
|
1464
|
+
* its existing bounded walk. Nothing is retained between requests.
|
|
1465
|
+
*/
|
|
1466
|
+
function readAncestry(owner, sessionId, tipId, range) {
|
|
1467
|
+
const read = sessionReads.get(owner);
|
|
1468
|
+
if (read?.sessionId !== sessionId)
|
|
1469
|
+
return undefined;
|
|
1470
|
+
read.ancestry ??= owner.sql
|
|
1471
|
+
.exec(`WITH RECURSIVE read_window (before_sequence, rows) AS (VALUES (?, ?)),
|
|
1472
|
+
ancestry (id, parent_id, sequence, state, head, context_closed, taken) AS (
|
|
1473
|
+
SELECT id, parent_id, sequence, state,
|
|
1474
|
+
${compactionHead("session_entries")}, 0,
|
|
1475
|
+
CASE WHEN state != 'streaming'
|
|
1476
|
+
AND sequence < (SELECT before_sequence FROM read_window)
|
|
1477
|
+
THEN 1 ELSE 0 END
|
|
1478
|
+
FROM session_entries INDEXED BY idx_session_entries_ancestry
|
|
1479
|
+
WHERE session_id = ? AND id = ?
|
|
1480
|
+
UNION ALL
|
|
1481
|
+
SELECT parent.id, parent.parent_id, parent.sequence, parent.state,
|
|
1482
|
+
COALESCE(child.head, ${compactionHead("parent")}),
|
|
1483
|
+
child.context_closed OR
|
|
1484
|
+
(child.head IS child.id AND child.state != 'streaming'),
|
|
1485
|
+
child.taken + CASE WHEN parent.state != 'streaming'
|
|
1486
|
+
AND parent.sequence < (SELECT before_sequence FROM read_window)
|
|
1487
|
+
THEN 1 ELSE 0 END
|
|
1488
|
+
FROM ancestry AS child
|
|
1489
|
+
CROSS JOIN session_entries AS parent INDEXED BY idx_session_entries_ancestry
|
|
1490
|
+
WHERE parent.id = child.parent_id AND parent.session_id = ?
|
|
1491
|
+
AND +parent.sequence < child.sequence
|
|
1492
|
+
AND (child.taken < (SELECT rows FROM read_window)
|
|
1493
|
+
OR (child.context_closed = 0
|
|
1494
|
+
AND (child.head IS NOT child.id OR child.state = 'streaming')))
|
|
1495
|
+
)
|
|
1496
|
+
SELECT id, parent_id, sequence, state FROM ancestry
|
|
1497
|
+
WHERE context_closed = 0
|
|
1498
|
+
OR sequence <= (SELECT before_sequence FROM read_window)
|
|
1499
|
+
ORDER BY sequence DESC`, read.before, lineageRowLimit + 1, sessionId, tipId, sessionId)
|
|
1500
|
+
.toArray()
|
|
1501
|
+
.map((raw) => {
|
|
1502
|
+
const decoded = decodeAncestryRow(raw);
|
|
1503
|
+
if (decoded._tag === "Failure")
|
|
1504
|
+
throw unavailable("Stored Session ancestry is invalid");
|
|
1505
|
+
return decoded.success;
|
|
1506
|
+
});
|
|
1507
|
+
const start = read.ancestry.findIndex(({ id }) => id === tipId);
|
|
1508
|
+
if (start === -1)
|
|
1509
|
+
return undefined;
|
|
1510
|
+
const rows = read.ancestry.slice(start);
|
|
1511
|
+
if (range !== undefined) {
|
|
1512
|
+
const deepest = rows.at(-1);
|
|
1513
|
+
if (deepest !== undefined &&
|
|
1514
|
+
deepest.parent_id !== null &&
|
|
1515
|
+
deepest.sequence > range.floor + 1)
|
|
1516
|
+
return undefined;
|
|
1517
|
+
const ceiling = range.ceiling ?? Number.MAX_SAFE_INTEGER;
|
|
1518
|
+
for (let index = 1; index < rows.length; index += 1) {
|
|
1519
|
+
const child = rows[index - 1];
|
|
1520
|
+
const parent = rows[index];
|
|
1521
|
+
if (child !== undefined &&
|
|
1522
|
+
parent !== undefined &&
|
|
1523
|
+
child.parent_id !== parent.id &&
|
|
1524
|
+
range.floor < child.sequence - 1 &&
|
|
1525
|
+
ceiling > parent.sequence)
|
|
1526
|
+
return undefined;
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
const encoded = read.encoded.get(tipId);
|
|
1530
|
+
if (encoded !== undefined)
|
|
1531
|
+
return encoded;
|
|
1532
|
+
const value = JSON.stringify(rows);
|
|
1533
|
+
read.encoded.set(tipId, value);
|
|
1534
|
+
return value;
|
|
1535
|
+
}
|
|
1536
|
+
/** A table over already-walked identities, decoded by SQLite's JSON reader. */
|
|
1537
|
+
function ancestryTable(name) {
|
|
1538
|
+
return `${name} AS MATERIALIZED (
|
|
1539
|
+
SELECT json_extract(value, '$.id') AS id,
|
|
1540
|
+
json_extract(value, '$.parent_id') AS parent_id,
|
|
1541
|
+
json_extract(value, '$.sequence') AS sequence,
|
|
1542
|
+
json_extract(value, '$.state') AS state
|
|
1543
|
+
FROM json_each(?)
|
|
1544
|
+
)`;
|
|
1545
|
+
}
|
|
1378
1546
|
const count = Schema.Int.pipe(Schema.check(Schema.isGreaterThanOrEqualTo(0)));
|
|
1379
1547
|
const decodeLineageCheck = Schema.decodeUnknownResult(Schema.Struct({
|
|
1380
1548
|
length: count,
|
|
@@ -1392,9 +1560,16 @@ const decodeLineageDiagnostic = Schema.decodeUnknownResult(Schema.Struct({ work_
|
|
|
1392
1560
|
* the tip while its rows are above it, so only those rows, and the first at
|
|
1393
1561
|
* or below it, are visited.
|
|
1394
1562
|
*/
|
|
1395
|
-
function lineageQuery(owner, lineage,
|
|
1563
|
+
function lineageQuery(owner, lineage, range, query, ...bindings) {
|
|
1564
|
+
const ancestry = lineage.walk === "selected"
|
|
1565
|
+
? readAncestry(owner, lineage.sessionId, lineage.tipId, range)
|
|
1566
|
+
: undefined;
|
|
1567
|
+
if (ancestry !== undefined)
|
|
1568
|
+
return owner.sql
|
|
1569
|
+
.exec(`WITH ${ancestryTable("branch")} ${query}`, ancestry, ...bindings)
|
|
1570
|
+
.toArray();
|
|
1396
1571
|
return owner.sql
|
|
1397
|
-
.exec(`${lineageWalks[lineage.walk]} ${query}`, lineage.sessionId, lineage.tipId, lineage.sessionId, floor, ...bindings)
|
|
1572
|
+
.exec(`${lineageWalks[lineage.walk]} ${query}`, lineage.sessionId, lineage.tipId, lineage.sessionId, range.floor, ...bindings)
|
|
1398
1573
|
.toArray();
|
|
1399
1574
|
}
|
|
1400
1575
|
/**
|
|
@@ -1493,7 +1668,7 @@ export function readSessionLineage(owner, input) {
|
|
|
1493
1668
|
});
|
|
1494
1669
|
}
|
|
1495
1670
|
function readLineageRange(owner, lineage, oldest, newest) {
|
|
1496
|
-
const rows = Object.freeze(lineageQuery(owner, lineage, oldest - 1, `SELECT ${entryColumns}
|
|
1671
|
+
const rows = Object.freeze(lineageQuery(owner, lineage, { floor: oldest - 1, ceiling: newest }, `SELECT ${entryColumns}
|
|
1497
1672
|
FROM session_entries
|
|
1498
1673
|
WHERE session_id = ? AND state != 'streaming'
|
|
1499
1674
|
AND sequence BETWEEN ? AND ?
|
|
@@ -1526,18 +1701,38 @@ const decodeWindowSizes = Schema.decodeUnknownResult(Schema.Struct({
|
|
|
1526
1701
|
* walk visits the rows from the tip down to the window, never the history
|
|
1527
1702
|
* below it. The deepest walked row comes back with the sizes, so a walk that
|
|
1528
1703
|
* ended at a missing parent above where it meant to stop is refused, never
|
|
1529
|
-
* read as the end of history.
|
|
1704
|
+
* read as the end of history. When a read already owns the ancestry, choose
|
|
1705
|
+
* the same bounded candidate identities before consulting their record sizes.
|
|
1706
|
+
* SQLite's octet_length(column) reads the byte count from record metadata,
|
|
1707
|
+
* without loading the text's overflow pages or a payload-length index.
|
|
1530
1708
|
*/
|
|
1531
1709
|
function readWindowSizes(owner, lineage, from, after, before, rows) {
|
|
1710
|
+
let ancestry = lineage.walk === "selected"
|
|
1711
|
+
? readAncestry(owner, lineage.sessionId, lineage.tipId)
|
|
1712
|
+
: undefined;
|
|
1713
|
+
if (ancestry !== undefined) {
|
|
1714
|
+
const candidates = sessionReads
|
|
1715
|
+
.get(owner)
|
|
1716
|
+
?.ancestry?.filter((row) => row.sequence <= lineage.newestSequence &&
|
|
1717
|
+
row.state !== "streaming" &&
|
|
1718
|
+
row.sequence > after &&
|
|
1719
|
+
row.sequence < before);
|
|
1720
|
+
const last = from === "newest" ? candidates?.[rows - 1] : undefined;
|
|
1721
|
+
ancestry = readAncestry(owner, lineage.sessionId, lineage.tipId, {
|
|
1722
|
+
floor: last === undefined ? after : last.sequence - 1,
|
|
1723
|
+
ceiling: before - 1,
|
|
1724
|
+
});
|
|
1725
|
+
}
|
|
1726
|
+
const direction = from === "newest" ? "DESC" : "ASC";
|
|
1532
1727
|
const ends = `SELECT
|
|
1533
1728
|
(SELECT json_group_array(json_array(sequence, bytes)) FROM (
|
|
1534
|
-
SELECT sequence,
|
|
1729
|
+
SELECT sequence, octet_length(data_json) AS bytes
|
|
1535
1730
|
FROM session_entries
|
|
1536
1731
|
WHERE ${from === "newest" ? "" : "session_id = ? AND"}
|
|
1537
1732
|
state != 'streaming'
|
|
1538
1733
|
AND sequence > ? AND sequence < ?
|
|
1539
|
-
AND id IN (SELECT id FROM branch)
|
|
1540
|
-
ORDER BY sequence ${
|
|
1734
|
+
AND id IN (SELECT id FROM ${ancestry === undefined ? "branch" : "candidates"})
|
|
1735
|
+
ORDER BY sequence ${direction}
|
|
1541
1736
|
LIMIT ?)) AS sizes,
|
|
1542
1737
|
(SELECT parent_id FROM branch ORDER BY sequence ASC LIMIT 1)
|
|
1543
1738
|
AS deepest_parent,
|
|
@@ -1546,9 +1741,18 @@ function readWindowSizes(owner, lineage, from, after, before, rows) {
|
|
|
1546
1741
|
const counts = (row) => `CASE WHEN ${row}.state != 'streaming'
|
|
1547
1742
|
AND ${row}.sequence < ? AND ${row}.sequence > ?
|
|
1548
1743
|
THEN 1 ELSE 0 END`;
|
|
1549
|
-
const raw =
|
|
1744
|
+
const raw = ancestry !== undefined
|
|
1550
1745
|
? owner.sql
|
|
1551
|
-
.exec(`WITH
|
|
1746
|
+
.exec(`WITH ${ancestryTable("branch")},
|
|
1747
|
+
candidates AS MATERIALIZED (
|
|
1748
|
+
SELECT id FROM branch
|
|
1749
|
+
WHERE state != 'streaming' AND sequence > ? AND sequence < ?
|
|
1750
|
+
ORDER BY sequence ${direction} LIMIT ?
|
|
1751
|
+
) ${ends}`, ancestry, after, before, rows, ...(from === "newest" ? [] : [lineage.sessionId]), after, before, rows)
|
|
1752
|
+
.toArray()
|
|
1753
|
+
: from === "newest"
|
|
1754
|
+
? owner.sql
|
|
1755
|
+
.exec(`WITH RECURSIVE branch (id, parent_id, sequence, state, taken) AS (
|
|
1552
1756
|
SELECT id, parent_id, sequence, state,
|
|
1553
1757
|
${counts("session_entries")}
|
|
1554
1758
|
FROM session_entries
|
|
@@ -1556,14 +1760,15 @@ function readWindowSizes(owner, lineage, from, after, before, rows) {
|
|
|
1556
1760
|
UNION ALL
|
|
1557
1761
|
SELECT parent.id, parent.parent_id, parent.sequence, parent.state,
|
|
1558
1762
|
child.taken + ${counts("parent")}
|
|
1559
|
-
FROM
|
|
1560
|
-
JOIN
|
|
1561
|
-
WHERE parent.
|
|
1763
|
+
FROM branch AS child
|
|
1764
|
+
CROSS JOIN session_entries AS parent
|
|
1765
|
+
WHERE parent.id = child.parent_id AND parent.session_id = ?
|
|
1766
|
+
AND +parent.sequence < child.sequence
|
|
1562
1767
|
AND child.taken < ? AND child.sequence > ?
|
|
1563
1768
|
)
|
|
1564
1769
|
${ends}`, before, after, lineage.sessionId, lineage.tipId, before, after, lineage.sessionId, rows, after, after, before, rows)
|
|
1565
|
-
|
|
1566
|
-
|
|
1770
|
+
.toArray()
|
|
1771
|
+
: lineageQuery(owner, lineage, { floor: after }, ends, lineage.sessionId, after, before, rows);
|
|
1567
1772
|
const decoded = decodeWindowSizes(raw[0]);
|
|
1568
1773
|
if (decoded._tag === "Failure")
|
|
1569
1774
|
throw unavailable("Stored Session entry size is invalid");
|
|
@@ -1607,7 +1812,7 @@ export function readLineageWindow(owner, lineage, input) {
|
|
|
1607
1812
|
}
|
|
1608
1813
|
/** Whether a complete row with this sequence lies on the lineage. */
|
|
1609
1814
|
export function lineageHasSequence(owner, lineage, rowSequence) {
|
|
1610
|
-
return (lineageQuery(owner, lineage, rowSequence - 1, `SELECT 1 AS present
|
|
1815
|
+
return (lineageQuery(owner, lineage, { floor: rowSequence - 1, ceiling: rowSequence }, `SELECT 1 AS present
|
|
1611
1816
|
FROM session_entries
|
|
1612
1817
|
WHERE session_id = ? AND state != 'streaming' AND sequence = ?
|
|
1613
1818
|
AND id IN (SELECT id FROM branch)`, lineage.sessionId, rowSequence).length > 0);
|
|
@@ -1637,15 +1842,9 @@ export function lineageEntrySequence(owner, lineage, id) {
|
|
|
1637
1842
|
function newestLineagePreference(owner, sessionId, tipId) {
|
|
1638
1843
|
const continues = (row) => `${row}.id NOT IN (SELECT id FROM targets)
|
|
1639
1844
|
AND ${row}.sequence > (SELECT MIN(sequence) FROM targets)`;
|
|
1640
|
-
const [newest] =
|
|
1641
|
-
.exec(`WITH RECURSIVE targets AS MATERIALIZED (
|
|
1642
|
-
SELECT id, sequence FROM session_entries
|
|
1845
|
+
const [newest] = readTargetIds(owner, sessionId, tipId, `SELECT id, sequence FROM session_entries
|
|
1643
1846
|
WHERE session_id = ? AND type = 'model_preference'
|
|
1644
|
-
AND state != 'streaming'
|
|
1645
|
-
),
|
|
1646
|
-
${targetWalk(continues("child"))}
|
|
1647
|
-
${walkSteps(continues("walk"))}`, sessionId, sessionId, tipId, sessionId)
|
|
1648
|
-
.toArray());
|
|
1847
|
+
AND state != 'streaming'`, [sessionId], continues);
|
|
1649
1848
|
return newest === undefined
|
|
1650
1849
|
? undefined
|
|
1651
1850
|
: readEntryRow(owner, sessionId, newest);
|
|
@@ -1659,7 +1858,7 @@ function readLineageDocument(owner, lineage, extensionId, name) {
|
|
|
1659
1858
|
if (lineage === undefined)
|
|
1660
1859
|
return undefined;
|
|
1661
1860
|
// No index finds documents yet, so this still walks the whole lineage.
|
|
1662
|
-
const raw = lineageQuery(owner, lineage, 0, `SELECT ${entryColumns}
|
|
1861
|
+
const raw = lineageQuery(owner, lineage, { floor: 0 }, `SELECT ${entryColumns}
|
|
1663
1862
|
FROM session_entries
|
|
1664
1863
|
WHERE session_id = ? AND state != 'streaming' AND type = 'document'
|
|
1665
1864
|
AND json_extract(data_json, '$.extensionId') = ?
|
|
@@ -1676,7 +1875,10 @@ function readLineageDocument(owner, lineage, extensionId, name) {
|
|
|
1676
1875
|
throw unavailable("Stored Session document disagrees with its row");
|
|
1677
1876
|
return entry.valueJson;
|
|
1678
1877
|
}
|
|
1679
|
-
const
|
|
1878
|
+
const decodeWorkBounds = Schema.decodeUnknownResult(Schema.Struct({
|
|
1879
|
+
oldest: Schema.NullOr(sequence),
|
|
1880
|
+
newest: Schema.NullOr(sequence),
|
|
1881
|
+
}), closed);
|
|
1680
1882
|
/**
|
|
1681
1883
|
* Look up, on the lineage, only the facts of the given works. Diagnostics come
|
|
1682
1884
|
* only from owned terminal fields, never provider message or retry text. The works'
|
|
@@ -1691,30 +1893,31 @@ export function readLineageWorkFacts(owner, lineage, workIds) {
|
|
|
1691
1893
|
cancellations: new Map(),
|
|
1692
1894
|
});
|
|
1693
1895
|
const works = JSON.stringify([...workIds]);
|
|
1694
|
-
const
|
|
1695
|
-
.exec(`SELECT MIN(
|
|
1696
|
-
SELECT MIN(sequence) AS sequence
|
|
1896
|
+
const bounds = decodeWorkBounds(owner.sql
|
|
1897
|
+
.exec(`SELECT MIN(oldest) AS oldest, MAX(newest) AS newest FROM (
|
|
1898
|
+
SELECT MIN(sequence) AS oldest, MAX(sequence) AS newest
|
|
1697
1899
|
FROM session_entries INDEXED BY idx_session_entries_work
|
|
1698
1900
|
WHERE work_id IN (SELECT value FROM json_each(?))
|
|
1699
1901
|
AND session_id = ?
|
|
1700
1902
|
UNION ALL
|
|
1701
|
-
SELECT MIN(sequence) FROM session_entries
|
|
1903
|
+
SELECT MIN(sequence), MAX(sequence) FROM session_entries
|
|
1702
1904
|
INDEXED BY idx_session_entries_cancellation_target
|
|
1703
1905
|
WHERE session_id = ? AND type = 'cancellation'
|
|
1704
1906
|
AND json_extract(data_json, '$.targetWorkId')
|
|
1705
1907
|
IN (SELECT value FROM json_each(?))
|
|
1706
1908
|
)`, works, lineage.sessionId, lineage.sessionId, works)
|
|
1707
1909
|
.one());
|
|
1708
|
-
if (
|
|
1910
|
+
if (bounds._tag === "Failure")
|
|
1709
1911
|
throw unavailable("Stored Session work order is invalid");
|
|
1710
|
-
if (oldest.success.
|
|
1912
|
+
if (bounds.success.oldest === null || bounds.success.newest === null)
|
|
1711
1913
|
return Object.freeze({
|
|
1712
1914
|
commands: new Set(),
|
|
1713
1915
|
diagnostics: new Map(),
|
|
1714
1916
|
cancellations: new Map(),
|
|
1715
1917
|
});
|
|
1716
|
-
const floor =
|
|
1717
|
-
const
|
|
1918
|
+
const floor = bounds.success.oldest - 1;
|
|
1919
|
+
const range = { floor, ceiling: bounds.success.newest };
|
|
1920
|
+
const commands = new Set(lineageQuery(owner, lineage, range, `SELECT DISTINCT work_id
|
|
1718
1921
|
FROM session_entries
|
|
1719
1922
|
WHERE session_id = ? AND sequence > ? AND state != 'streaming' AND type = 'command'
|
|
1720
1923
|
AND work_id IN (SELECT value FROM json_each(?))
|
|
@@ -1725,7 +1928,7 @@ export function readLineageWorkFacts(owner, lineage, workIds) {
|
|
|
1725
1928
|
return decoded.success.work_id;
|
|
1726
1929
|
}));
|
|
1727
1930
|
const diagnostics = new Map();
|
|
1728
|
-
for (const raw of lineageQuery(owner, lineage,
|
|
1931
|
+
for (const raw of lineageQuery(owner, lineage, range, `SELECT work_id, diagnostic FROM (
|
|
1729
1932
|
SELECT work_id, sequence,
|
|
1730
1933
|
json_extract(data_json, '$.diagnostic') AS diagnostic
|
|
1731
1934
|
FROM session_entries
|
|
@@ -1742,7 +1945,7 @@ export function readLineageWorkFacts(owner, lineage, workIds) {
|
|
|
1742
1945
|
diagnostics.set(decoded.success.work_id, decoded.success.diagnostic);
|
|
1743
1946
|
}
|
|
1744
1947
|
const cancellations = new Map();
|
|
1745
|
-
for (const { entry } of lineageQuery(owner, lineage,
|
|
1948
|
+
for (const { entry } of lineageQuery(owner, lineage, range, `SELECT ${entryColumns}
|
|
1746
1949
|
FROM session_entries
|
|
1747
1950
|
WHERE session_id = ? AND sequence > ? AND state != 'streaming' AND type = 'cancellation'
|
|
1748
1951
|
AND json_extract(data_json, '$.targetWorkId')
|
|
@@ -1771,25 +1974,80 @@ export function readSelectedPath(owner, input) {
|
|
|
1771
1974
|
* steps past it; a statement without targets reads only its starting row,
|
|
1772
1975
|
* so a missing one is still refused.
|
|
1773
1976
|
*/
|
|
1774
|
-
function targetWalk(continues) {
|
|
1775
|
-
|
|
1977
|
+
function targetWalk(sessionId, tipId, continues, ancestry) {
|
|
1978
|
+
if (ancestry !== undefined)
|
|
1979
|
+
return {
|
|
1980
|
+
sql: `${ancestryTable("ancestors")},
|
|
1981
|
+
stopped AS (
|
|
1982
|
+
SELECT child.*,
|
|
1983
|
+
COALESCE(SUM(CASE WHEN ${continues} THEN 0 ELSE 1 END)
|
|
1984
|
+
OVER (ORDER BY child.sequence DESC
|
|
1985
|
+
ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING), 0) AS stops
|
|
1986
|
+
FROM ancestors AS child
|
|
1987
|
+
),
|
|
1988
|
+
walk AS (
|
|
1989
|
+
SELECT id, parent_id, sequence, state FROM stopped WHERE stops = 0
|
|
1990
|
+
)`,
|
|
1991
|
+
bindings: [ancestry],
|
|
1992
|
+
recursive: false,
|
|
1993
|
+
};
|
|
1994
|
+
return {
|
|
1995
|
+
sql: `walk (id, parent_id, sequence, state) AS (
|
|
1776
1996
|
SELECT id, parent_id, sequence, state
|
|
1777
1997
|
FROM session_entries
|
|
1778
1998
|
WHERE session_id = ? AND id = ?
|
|
1779
1999
|
UNION ALL
|
|
1780
2000
|
SELECT parent.id, parent.parent_id, parent.sequence, parent.state
|
|
1781
|
-
FROM
|
|
1782
|
-
JOIN
|
|
1783
|
-
WHERE parent.
|
|
2001
|
+
FROM walk AS child
|
|
2002
|
+
CROSS JOIN session_entries AS parent
|
|
2003
|
+
WHERE parent.id = child.parent_id AND parent.session_id = ?
|
|
2004
|
+
AND +parent.sequence < child.sequence
|
|
1784
2005
|
AND ${continues}
|
|
1785
|
-
)
|
|
2006
|
+
)`,
|
|
2007
|
+
bindings: [sessionId, tipId, sessionId],
|
|
2008
|
+
recursive: true,
|
|
2009
|
+
};
|
|
2010
|
+
}
|
|
2011
|
+
/**
|
|
2012
|
+
* A shared prefix can answer a target read only if it reaches that read's
|
|
2013
|
+
* stopping point without crossing an omitted gap. Otherwise run the original
|
|
2014
|
+
* target-bounded walk; a cache horizon is not evidence of damaged history.
|
|
2015
|
+
*/
|
|
2016
|
+
function readTargetIds(owner, sessionId, tipId, targets, bindings, continues) {
|
|
2017
|
+
const ancestry = readAncestry(owner, sessionId, tipId);
|
|
2018
|
+
const run = (shared) => {
|
|
2019
|
+
const walk = targetWalk(sessionId, tipId, continues("child"), shared);
|
|
2020
|
+
return owner.sql
|
|
2021
|
+
.exec(`WITH ${walk.recursive ? "RECURSIVE " : ""}targets AS MATERIALIZED (
|
|
2022
|
+
${targets}
|
|
2023
|
+
),
|
|
2024
|
+
${walk.sql}
|
|
2025
|
+
${walkSteps(continues("walk"))}`, ...bindings, ...walk.bindings)
|
|
2026
|
+
.toArray();
|
|
2027
|
+
};
|
|
2028
|
+
let raw = run(ancestry);
|
|
2029
|
+
if (ancestry !== undefined) {
|
|
2030
|
+
const last = raw.at(-1);
|
|
2031
|
+
if (last !== undefined) {
|
|
2032
|
+
const deepest = decodeWalkStep(last);
|
|
2033
|
+
if (deepest._tag === "Failure")
|
|
2034
|
+
throw unavailable("Stored Session walk row is invalid");
|
|
2035
|
+
if ((deepest.success.continues === 1 &&
|
|
2036
|
+
deepest.success.parent_id !== null) ||
|
|
2037
|
+
readAncestry(owner, sessionId, tipId, {
|
|
2038
|
+
floor: deepest.success.sequence - 1,
|
|
2039
|
+
}) === undefined)
|
|
2040
|
+
raw = run(undefined);
|
|
2041
|
+
}
|
|
2042
|
+
}
|
|
2043
|
+
return walkedTargets(raw);
|
|
1786
2044
|
}
|
|
1787
2045
|
/**
|
|
1788
2046
|
* The walked targets, newest first, then the deepest walked row, each with
|
|
1789
2047
|
* whether the walk meant to pass it. Only these rows reach JavaScript.
|
|
1790
2048
|
*/
|
|
1791
2049
|
function walkSteps(continues) {
|
|
1792
|
-
return `SELECT id, parent_id, target, continues FROM (
|
|
2050
|
+
return `SELECT id, parent_id, sequence, target, continues FROM (
|
|
1793
2051
|
SELECT id, parent_id, sequence,
|
|
1794
2052
|
id IN (SELECT id FROM targets) AS target,
|
|
1795
2053
|
COALESCE((${continues}), 0) AS continues
|
|
@@ -1802,6 +2060,7 @@ const flag = Schema.Literals([0, 1]);
|
|
|
1802
2060
|
const decodeWalkStep = Schema.decodeUnknownResult(Schema.Struct({
|
|
1803
2061
|
id: boundedId,
|
|
1804
2062
|
parent_id: Schema.NullOr(Schema.String),
|
|
2063
|
+
sequence,
|
|
1805
2064
|
target: flag,
|
|
1806
2065
|
continues: flag,
|
|
1807
2066
|
}), closed);
|
|
@@ -1846,9 +2105,10 @@ const contextWalk = `WITH RECURSIVE walk (id, parent_id, sequence, state, head)
|
|
|
1846
2105
|
UNION ALL
|
|
1847
2106
|
SELECT parent.id, parent.parent_id, parent.sequence, parent.state,
|
|
1848
2107
|
COALESCE(child.head, ${compactionHead("parent")})
|
|
1849
|
-
FROM
|
|
1850
|
-
JOIN
|
|
1851
|
-
WHERE parent.
|
|
2108
|
+
FROM walk AS child
|
|
2109
|
+
CROSS JOIN session_entries AS parent
|
|
2110
|
+
WHERE parent.id = child.parent_id AND parent.session_id = ?
|
|
2111
|
+
AND +parent.sequence < child.sequence
|
|
1852
2112
|
AND (child.head IS NOT child.id OR child.state = 'streaming')
|
|
1853
2113
|
)`;
|
|
1854
2114
|
/**
|
|
@@ -1860,9 +2120,7 @@ const contextWalk = `WITH RECURSIVE walk (id, parent_id, sequence, state, head)
|
|
|
1860
2120
|
function readDecidingFacts(owner, sessionId, kept, window, workId) {
|
|
1861
2121
|
const commands = JSON.stringify(window.flatMap(({ entry }) => entry.kind === "command" ? [entry.workId] : []));
|
|
1862
2122
|
const continues = (row) => `${row}.sequence > (SELECT MIN(sequence) FROM targets)`;
|
|
1863
|
-
const facts =
|
|
1864
|
-
.exec(`WITH RECURSIVE targets AS MATERIALIZED (
|
|
1865
|
-
SELECT id, sequence FROM session_entries
|
|
2123
|
+
const facts = readTargetIds(owner, sessionId, kept.id, `SELECT id, sequence FROM session_entries
|
|
1866
2124
|
WHERE session_id = ? AND type = 'compaction' AND work_id = ?
|
|
1867
2125
|
AND sequence < ? AND state != 'streaming'
|
|
1868
2126
|
UNION ALL
|
|
@@ -1872,11 +2130,7 @@ function readDecidingFacts(owner, sessionId, kept, window, workId) {
|
|
|
1872
2130
|
WHERE session_id = ? AND type = 'cancellation'
|
|
1873
2131
|
AND json_extract(data_json, '$.targetWorkId')
|
|
1874
2132
|
IN (SELECT value FROM json_each(?))
|
|
1875
|
-
AND sequence < ? AND state != 'streaming'
|
|
1876
|
-
),
|
|
1877
|
-
${targetWalk(continues("child"))}
|
|
1878
|
-
${walkSteps(continues("walk"))}`, sessionId, workId, kept.sequence, sessionId, commands, kept.sequence, sessionId, kept.id, sessionId)
|
|
1879
|
-
.toArray());
|
|
2133
|
+
AND sequence < ? AND state != 'streaming'`, [sessionId, workId, kept.sequence, sessionId, commands, kept.sequence], continues);
|
|
1880
2134
|
if (facts.length === 0)
|
|
1881
2135
|
return Object.freeze([]);
|
|
1882
2136
|
return Object.freeze(owner.sql
|
|
@@ -1895,12 +2149,40 @@ function readDecidingFacts(owner, sessionId, kept, window, workId) {
|
|
|
1895
2149
|
*/
|
|
1896
2150
|
function readContextWindow(owner, sessionId, tipId) {
|
|
1897
2151
|
const failures = lineageFailures.selected;
|
|
2152
|
+
let ancestry = readAncestry(owner, sessionId, tipId);
|
|
2153
|
+
// The seed owns this cache's compaction boundary. An ancestor tip can have an
|
|
2154
|
+
// older boundary of its own (including one in an omitted historical gap).
|
|
2155
|
+
if (sessionReads.get(owner)?.ancestry?.[0]?.id !== tipId)
|
|
2156
|
+
ancestry = undefined;
|
|
2157
|
+
const source = ancestry === undefined
|
|
2158
|
+
? { sql: contextWalk, bindings: [sessionId, tipId, sessionId] }
|
|
2159
|
+
: {
|
|
2160
|
+
sql: `WITH ${ancestryTable("ancestors")},
|
|
2161
|
+
boundary AS (
|
|
2162
|
+
SELECT sequence, json_extract(data_json, '$.head') AS head
|
|
2163
|
+
FROM session_entries
|
|
2164
|
+
WHERE session_id = ? AND type = 'compaction'
|
|
2165
|
+
AND state != 'streaming'
|
|
2166
|
+
AND id IN (SELECT id FROM ancestors)
|
|
2167
|
+
ORDER BY sequence DESC LIMIT 1
|
|
2168
|
+
),
|
|
2169
|
+
walk AS (
|
|
2170
|
+
SELECT * FROM ancestors
|
|
2171
|
+
WHERE sequence >= COALESCE((
|
|
2172
|
+
SELECT ancestors.sequence FROM ancestors, boundary
|
|
2173
|
+
WHERE ancestors.id = boundary.head
|
|
2174
|
+
AND ancestors.state != 'streaming'
|
|
2175
|
+
AND ancestors.sequence <= boundary.sequence
|
|
2176
|
+
), 0)
|
|
2177
|
+
)`,
|
|
2178
|
+
bindings: [ancestry, sessionId],
|
|
2179
|
+
};
|
|
1898
2180
|
const rows = owner.sql
|
|
1899
|
-
.exec(`${
|
|
2181
|
+
.exec(`${source.sql}
|
|
1900
2182
|
SELECT ${entryColumns}
|
|
1901
2183
|
FROM session_entries
|
|
1902
2184
|
WHERE id IN (SELECT id FROM walk) AND state != 'streaming'
|
|
1903
|
-
ORDER BY sequence ASC`,
|
|
2185
|
+
ORDER BY sequence ASC`, ...source.bindings)
|
|
1904
2186
|
.toArray()
|
|
1905
2187
|
.map((raw) => parseEntryRow(owner, raw));
|
|
1906
2188
|
const kept = rows[0];
|
|
@@ -1961,7 +2243,7 @@ function readRunContextRows(owner, sessionId, command, current, workId) {
|
|
|
1961
2243
|
throw unavailable("Accepted Session command lineage is unavailable");
|
|
1962
2244
|
const tail = current === undefined
|
|
1963
2245
|
? []
|
|
1964
|
-
: lineageQuery(owner, current, command.sequence, `SELECT ${entryColumns}
|
|
2246
|
+
: lineageQuery(owner, current, { floor: command.sequence }, `SELECT ${entryColumns}
|
|
1965
2247
|
FROM session_entries
|
|
1966
2248
|
WHERE session_id = ? AND sequence > ? AND state != 'streaming'
|
|
1967
2249
|
AND id IN (SELECT id FROM branch)
|
|
@@ -2325,6 +2607,7 @@ export function acceptSessionCommandOwned(owner, sessionId, input, meta, origin,
|
|
|
2325
2607
|
jobId: commandFactId,
|
|
2326
2608
|
sessionId,
|
|
2327
2609
|
commandFactId,
|
|
2610
|
+
delivery: input.type === "prompt" ? input.delivery : "followUp",
|
|
2328
2611
|
});
|
|
2329
2612
|
return acceptanceFromAdmission(command, "new");
|
|
2330
2613
|
}
|