@fungi.computer/shiitake 0.6.0 → 0.8.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 +1 -1
- package/dist/agent-control.js +40 -21
- package/dist/client-visitor.d.ts +2 -0
- package/dist/client-visitor.js +6 -1
- package/dist/client-wire.d.ts +4 -0
- package/dist/client-wire.js +26 -15
- package/dist/client.d.ts +23 -11
- package/dist/client.js +22 -0
- package/dist/cloudflare.d.ts +20 -0
- package/dist/cloudflare.js +4 -1
- package/dist/delegation.d.ts +5 -0
- package/dist/delegation.js +24 -8
- package/dist/effect.d.ts +1 -1
- package/dist/errors.d.ts +2 -2
- package/dist/errors.js +2 -0
- package/dist/extensions.d.ts +1 -1
- package/dist/fleet.d.ts +14 -2
- package/dist/fleet.js +81 -19
- package/dist/http-routes.d.ts +12 -16
- package/dist/http.d.ts +1 -0
- package/dist/http.js +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/internal/admission-door.d.ts +1 -1
- package/dist/internal/admission-door.js +8 -4
- package/dist/internal/contracts.d.ts +5 -0
- package/dist/internal/coordinator.js +107 -50
- package/dist/internal/effect-contracts.d.ts +5 -0
- package/dist/internal/extension-runtime.js +5 -3
- package/dist/internal/observation-kernel.js +12 -3
- package/dist/internal/promise-facade.js +2 -0
- package/dist/internal/run-scope.d.ts +1 -0
- package/dist/internal/run-scope.js +29 -7
- package/dist/internal/runtime-coordinator.d.ts +14 -0
- package/dist/internal/runtime-coordinator.js +57 -10
- package/dist/internal/runtime-core.d.ts +3 -0
- package/dist/internal/runtime-core.js +1 -0
- package/dist/internal/runtime-reconciliation.js +4 -1
- package/dist/internal/session-context.d.ts +5 -5
- package/dist/internal/session-context.js +80 -83
- package/dist/internal/session-status.d.ts +4 -2
- package/dist/internal/session-status.js +22 -4
- package/dist/internal/woodstock/client-projection.js +5 -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 +3 -3
- package/dist/internal/woodstock/index.js +18 -7
- package/dist/internal/woodstock/progress.d.ts +13 -1
- package/dist/internal/woodstock/progress.js +130 -24
- package/dist/internal/woodstock/session-delegator.d.ts +2 -1
- package/dist/internal/woodstock/session-store.d.ts +14 -2
- package/dist/internal/woodstock/session-store.js +329 -66
- package/dist/node.js +11 -2
- package/dist/server-visitor-stream.js +33 -24
- package/dist/testing.d.ts +2 -0
- package/dist/testing.js +2 -0
- package/docs/api/agent-control.md +3 -2
- package/docs/api/client.md +79 -19
- package/docs/api/cloudflare.md +16 -0
- package/docs/api/delegation.md +84 -50
- package/docs/api/effect.md +85 -7
- package/docs/api/extensions.md +8 -8
- package/docs/api/fleet.md +118 -29
- package/docs/api/http.md +28 -0
- package/docs/api/index.md +39 -15
- package/docs/api/testing.md +20 -7
- package/package.json +5 -5
|
@@ -270,9 +270,9 @@ function selectedLineageFirstPromptId(owner, sessionId, leafId) {
|
|
|
270
270
|
UNION
|
|
271
271
|
SELECT parent.id, parent.parent_id, parent.sequence,
|
|
272
272
|
parent.type, parent.role, parent.state, parent.work_id
|
|
273
|
-
FROM
|
|
274
|
-
JOIN
|
|
275
|
-
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 = ?
|
|
276
276
|
)
|
|
277
277
|
SELECT
|
|
278
278
|
(SELECT command.id FROM branch AS command
|
|
@@ -536,6 +536,12 @@ const pendingAdmissions = `FROM session_command_admissions AS a
|
|
|
536
536
|
WHERE e.session_id = a.session_id AND e.work_id = a.work_id
|
|
537
537
|
AND e.type IN ('command', 'cancellation', 'run_terminal', 'work_terminal')
|
|
538
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
|
+
}
|
|
539
545
|
const decodeIdentity = Schema.decodeUnknownSync(Schema.Struct({ identity: Schema.NullOr(Schema.String) }));
|
|
540
546
|
/** Extract the host-selected principal without giving the engine domain grammar. */
|
|
541
547
|
export function readAdmissionIdentity(owner, path, meta) {
|
|
@@ -578,7 +584,8 @@ export function readPendingSessionCommands(owner, sessionId, limit, only = "all"
|
|
|
578
584
|
.one()).total;
|
|
579
585
|
const commands = owner.sql
|
|
580
586
|
.exec(`SELECT ${admissionColumns("a.")} ${pendingAdmissions} ${filter}
|
|
581
|
-
ORDER BY a.
|
|
587
|
+
ORDER BY (json_extract(a.data_json, '$.delivery') = 'steer') DESC,
|
|
588
|
+
a.sequence ASC LIMIT ?`, sessionId, limit)
|
|
582
589
|
.toArray()
|
|
583
590
|
.map(parseCommandAdmissionRow);
|
|
584
591
|
return { commands, total };
|
|
@@ -647,6 +654,7 @@ export function settleSessionControls(owner, input) {
|
|
|
647
654
|
jobId: row.entry.workId,
|
|
648
655
|
sessionId: input.sessionId,
|
|
649
656
|
commandFactId: row.id,
|
|
657
|
+
delivery: "steer",
|
|
650
658
|
});
|
|
651
659
|
continue;
|
|
652
660
|
}
|
|
@@ -1153,7 +1161,19 @@ export function prepareSessionStore(owner) {
|
|
|
1153
1161
|
session_id TEXT NOT NULL UNIQUE REFERENCES sessions(id)
|
|
1154
1162
|
)
|
|
1155
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
|
+
)`);
|
|
1156
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)`);
|
|
1157
1177
|
owner.sql.exec("CREATE INDEX IF NOT EXISTS idx_session_entries_session ON session_entries(session_id, sequence)");
|
|
1158
1178
|
owner.sql.exec("CREATE INDEX IF NOT EXISTS idx_session_entries_streaming ON session_entries(session_id, state)");
|
|
1159
1179
|
owner.sql.exec(`CREATE UNIQUE INDEX IF NOT EXISTS idx_session_entries_request
|
|
@@ -1359,6 +1379,8 @@ const lineageByteLimit = 4 * 1_048_576;
|
|
|
1359
1379
|
* selected walk steps only to an older parent, so it ends without
|
|
1360
1380
|
* deduplicating; the fork walk keeps every link for its whole-lineage check.
|
|
1361
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.
|
|
1362
1384
|
const lineageWalks = {
|
|
1363
1385
|
selected: `WITH RECURSIVE branch (id, parent_id, sequence, state) AS (
|
|
1364
1386
|
SELECT id, parent_id, sequence, state
|
|
@@ -1366,9 +1388,10 @@ const lineageWalks = {
|
|
|
1366
1388
|
WHERE session_id = ? AND id = ?
|
|
1367
1389
|
UNION ALL
|
|
1368
1390
|
SELECT parent.id, parent.parent_id, parent.sequence, parent.state
|
|
1369
|
-
FROM
|
|
1370
|
-
JOIN
|
|
1371
|
-
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 > ?
|
|
1372
1395
|
AND +parent.sequence < child.sequence
|
|
1373
1396
|
)`,
|
|
1374
1397
|
fork: `WITH RECURSIVE branch (id, parent_id, sequence, state) AS (
|
|
@@ -1377,9 +1400,10 @@ const lineageWalks = {
|
|
|
1377
1400
|
WHERE session_id = ? AND id = ? AND state != 'streaming'
|
|
1378
1401
|
UNION
|
|
1379
1402
|
SELECT parent.id, parent.parent_id, parent.sequence, parent.state
|
|
1380
|
-
FROM
|
|
1381
|
-
JOIN
|
|
1382
|
-
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 > ?
|
|
1383
1407
|
AND parent.state != 'streaming'
|
|
1384
1408
|
)`,
|
|
1385
1409
|
};
|
|
@@ -1395,6 +1419,130 @@ const lineageFailures = {
|
|
|
1395
1419
|
invalid: "Fork path is invalid",
|
|
1396
1420
|
},
|
|
1397
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
|
+
}
|
|
1398
1546
|
const count = Schema.Int.pipe(Schema.check(Schema.isGreaterThanOrEqualTo(0)));
|
|
1399
1547
|
const decodeLineageCheck = Schema.decodeUnknownResult(Schema.Struct({
|
|
1400
1548
|
length: count,
|
|
@@ -1412,9 +1560,16 @@ const decodeLineageDiagnostic = Schema.decodeUnknownResult(Schema.Struct({ work_
|
|
|
1412
1560
|
* the tip while its rows are above it, so only those rows, and the first at
|
|
1413
1561
|
* or below it, are visited.
|
|
1414
1562
|
*/
|
|
1415
|
-
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();
|
|
1416
1571
|
return owner.sql
|
|
1417
|
-
.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)
|
|
1418
1573
|
.toArray();
|
|
1419
1574
|
}
|
|
1420
1575
|
/**
|
|
@@ -1513,7 +1668,7 @@ export function readSessionLineage(owner, input) {
|
|
|
1513
1668
|
});
|
|
1514
1669
|
}
|
|
1515
1670
|
function readLineageRange(owner, lineage, oldest, newest) {
|
|
1516
|
-
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}
|
|
1517
1672
|
FROM session_entries
|
|
1518
1673
|
WHERE session_id = ? AND state != 'streaming'
|
|
1519
1674
|
AND sequence BETWEEN ? AND ?
|
|
@@ -1546,18 +1701,38 @@ const decodeWindowSizes = Schema.decodeUnknownResult(Schema.Struct({
|
|
|
1546
1701
|
* walk visits the rows from the tip down to the window, never the history
|
|
1547
1702
|
* below it. The deepest walked row comes back with the sizes, so a walk that
|
|
1548
1703
|
* ended at a missing parent above where it meant to stop is refused, never
|
|
1549
|
-
* 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.
|
|
1550
1708
|
*/
|
|
1551
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";
|
|
1552
1727
|
const ends = `SELECT
|
|
1553
1728
|
(SELECT json_group_array(json_array(sequence, bytes)) FROM (
|
|
1554
|
-
SELECT sequence,
|
|
1729
|
+
SELECT sequence, octet_length(data_json) AS bytes
|
|
1555
1730
|
FROM session_entries
|
|
1556
1731
|
WHERE ${from === "newest" ? "" : "session_id = ? AND"}
|
|
1557
1732
|
state != 'streaming'
|
|
1558
1733
|
AND sequence > ? AND sequence < ?
|
|
1559
|
-
AND id IN (SELECT id FROM branch)
|
|
1560
|
-
ORDER BY sequence ${
|
|
1734
|
+
AND id IN (SELECT id FROM ${ancestry === undefined ? "branch" : "candidates"})
|
|
1735
|
+
ORDER BY sequence ${direction}
|
|
1561
1736
|
LIMIT ?)) AS sizes,
|
|
1562
1737
|
(SELECT parent_id FROM branch ORDER BY sequence ASC LIMIT 1)
|
|
1563
1738
|
AS deepest_parent,
|
|
@@ -1566,9 +1741,18 @@ function readWindowSizes(owner, lineage, from, after, before, rows) {
|
|
|
1566
1741
|
const counts = (row) => `CASE WHEN ${row}.state != 'streaming'
|
|
1567
1742
|
AND ${row}.sequence < ? AND ${row}.sequence > ?
|
|
1568
1743
|
THEN 1 ELSE 0 END`;
|
|
1569
|
-
const raw =
|
|
1744
|
+
const raw = ancestry !== undefined
|
|
1570
1745
|
? owner.sql
|
|
1571
|
-
.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 (
|
|
1572
1756
|
SELECT id, parent_id, sequence, state,
|
|
1573
1757
|
${counts("session_entries")}
|
|
1574
1758
|
FROM session_entries
|
|
@@ -1576,14 +1760,15 @@ function readWindowSizes(owner, lineage, from, after, before, rows) {
|
|
|
1576
1760
|
UNION ALL
|
|
1577
1761
|
SELECT parent.id, parent.parent_id, parent.sequence, parent.state,
|
|
1578
1762
|
child.taken + ${counts("parent")}
|
|
1579
|
-
FROM
|
|
1580
|
-
JOIN
|
|
1581
|
-
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
|
|
1582
1767
|
AND child.taken < ? AND child.sequence > ?
|
|
1583
1768
|
)
|
|
1584
1769
|
${ends}`, before, after, lineage.sessionId, lineage.tipId, before, after, lineage.sessionId, rows, after, after, before, rows)
|
|
1585
|
-
|
|
1586
|
-
|
|
1770
|
+
.toArray()
|
|
1771
|
+
: lineageQuery(owner, lineage, { floor: after }, ends, lineage.sessionId, after, before, rows);
|
|
1587
1772
|
const decoded = decodeWindowSizes(raw[0]);
|
|
1588
1773
|
if (decoded._tag === "Failure")
|
|
1589
1774
|
throw unavailable("Stored Session entry size is invalid");
|
|
@@ -1627,7 +1812,7 @@ export function readLineageWindow(owner, lineage, input) {
|
|
|
1627
1812
|
}
|
|
1628
1813
|
/** Whether a complete row with this sequence lies on the lineage. */
|
|
1629
1814
|
export function lineageHasSequence(owner, lineage, rowSequence) {
|
|
1630
|
-
return (lineageQuery(owner, lineage, rowSequence - 1, `SELECT 1 AS present
|
|
1815
|
+
return (lineageQuery(owner, lineage, { floor: rowSequence - 1, ceiling: rowSequence }, `SELECT 1 AS present
|
|
1631
1816
|
FROM session_entries
|
|
1632
1817
|
WHERE session_id = ? AND state != 'streaming' AND sequence = ?
|
|
1633
1818
|
AND id IN (SELECT id FROM branch)`, lineage.sessionId, rowSequence).length > 0);
|
|
@@ -1657,15 +1842,9 @@ export function lineageEntrySequence(owner, lineage, id) {
|
|
|
1657
1842
|
function newestLineagePreference(owner, sessionId, tipId) {
|
|
1658
1843
|
const continues = (row) => `${row}.id NOT IN (SELECT id FROM targets)
|
|
1659
1844
|
AND ${row}.sequence > (SELECT MIN(sequence) FROM targets)`;
|
|
1660
|
-
const [newest] =
|
|
1661
|
-
.exec(`WITH RECURSIVE targets AS MATERIALIZED (
|
|
1662
|
-
SELECT id, sequence FROM session_entries
|
|
1845
|
+
const [newest] = readTargetIds(owner, sessionId, tipId, `SELECT id, sequence FROM session_entries
|
|
1663
1846
|
WHERE session_id = ? AND type = 'model_preference'
|
|
1664
|
-
AND state != 'streaming'
|
|
1665
|
-
),
|
|
1666
|
-
${targetWalk(continues("child"))}
|
|
1667
|
-
${walkSteps(continues("walk"))}`, sessionId, sessionId, tipId, sessionId)
|
|
1668
|
-
.toArray());
|
|
1847
|
+
AND state != 'streaming'`, [sessionId], continues);
|
|
1669
1848
|
return newest === undefined
|
|
1670
1849
|
? undefined
|
|
1671
1850
|
: readEntryRow(owner, sessionId, newest);
|
|
@@ -1679,7 +1858,7 @@ function readLineageDocument(owner, lineage, extensionId, name) {
|
|
|
1679
1858
|
if (lineage === undefined)
|
|
1680
1859
|
return undefined;
|
|
1681
1860
|
// No index finds documents yet, so this still walks the whole lineage.
|
|
1682
|
-
const raw = lineageQuery(owner, lineage, 0, `SELECT ${entryColumns}
|
|
1861
|
+
const raw = lineageQuery(owner, lineage, { floor: 0 }, `SELECT ${entryColumns}
|
|
1683
1862
|
FROM session_entries
|
|
1684
1863
|
WHERE session_id = ? AND state != 'streaming' AND type = 'document'
|
|
1685
1864
|
AND json_extract(data_json, '$.extensionId') = ?
|
|
@@ -1696,7 +1875,10 @@ function readLineageDocument(owner, lineage, extensionId, name) {
|
|
|
1696
1875
|
throw unavailable("Stored Session document disagrees with its row");
|
|
1697
1876
|
return entry.valueJson;
|
|
1698
1877
|
}
|
|
1699
|
-
const
|
|
1878
|
+
const decodeWorkBounds = Schema.decodeUnknownResult(Schema.Struct({
|
|
1879
|
+
oldest: Schema.NullOr(sequence),
|
|
1880
|
+
newest: Schema.NullOr(sequence),
|
|
1881
|
+
}), closed);
|
|
1700
1882
|
/**
|
|
1701
1883
|
* Look up, on the lineage, only the facts of the given works. Diagnostics come
|
|
1702
1884
|
* only from owned terminal fields, never provider message or retry text. The works'
|
|
@@ -1711,30 +1893,31 @@ export function readLineageWorkFacts(owner, lineage, workIds) {
|
|
|
1711
1893
|
cancellations: new Map(),
|
|
1712
1894
|
});
|
|
1713
1895
|
const works = JSON.stringify([...workIds]);
|
|
1714
|
-
const
|
|
1715
|
-
.exec(`SELECT MIN(
|
|
1716
|
-
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
|
|
1717
1899
|
FROM session_entries INDEXED BY idx_session_entries_work
|
|
1718
1900
|
WHERE work_id IN (SELECT value FROM json_each(?))
|
|
1719
1901
|
AND session_id = ?
|
|
1720
1902
|
UNION ALL
|
|
1721
|
-
SELECT MIN(sequence) FROM session_entries
|
|
1903
|
+
SELECT MIN(sequence), MAX(sequence) FROM session_entries
|
|
1722
1904
|
INDEXED BY idx_session_entries_cancellation_target
|
|
1723
1905
|
WHERE session_id = ? AND type = 'cancellation'
|
|
1724
1906
|
AND json_extract(data_json, '$.targetWorkId')
|
|
1725
1907
|
IN (SELECT value FROM json_each(?))
|
|
1726
1908
|
)`, works, lineage.sessionId, lineage.sessionId, works)
|
|
1727
1909
|
.one());
|
|
1728
|
-
if (
|
|
1910
|
+
if (bounds._tag === "Failure")
|
|
1729
1911
|
throw unavailable("Stored Session work order is invalid");
|
|
1730
|
-
if (oldest.success.
|
|
1912
|
+
if (bounds.success.oldest === null || bounds.success.newest === null)
|
|
1731
1913
|
return Object.freeze({
|
|
1732
1914
|
commands: new Set(),
|
|
1733
1915
|
diagnostics: new Map(),
|
|
1734
1916
|
cancellations: new Map(),
|
|
1735
1917
|
});
|
|
1736
|
-
const floor =
|
|
1737
|
-
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
|
|
1738
1921
|
FROM session_entries
|
|
1739
1922
|
WHERE session_id = ? AND sequence > ? AND state != 'streaming' AND type = 'command'
|
|
1740
1923
|
AND work_id IN (SELECT value FROM json_each(?))
|
|
@@ -1745,7 +1928,7 @@ export function readLineageWorkFacts(owner, lineage, workIds) {
|
|
|
1745
1928
|
return decoded.success.work_id;
|
|
1746
1929
|
}));
|
|
1747
1930
|
const diagnostics = new Map();
|
|
1748
|
-
for (const raw of lineageQuery(owner, lineage,
|
|
1931
|
+
for (const raw of lineageQuery(owner, lineage, range, `SELECT work_id, diagnostic FROM (
|
|
1749
1932
|
SELECT work_id, sequence,
|
|
1750
1933
|
json_extract(data_json, '$.diagnostic') AS diagnostic
|
|
1751
1934
|
FROM session_entries
|
|
@@ -1762,7 +1945,7 @@ export function readLineageWorkFacts(owner, lineage, workIds) {
|
|
|
1762
1945
|
diagnostics.set(decoded.success.work_id, decoded.success.diagnostic);
|
|
1763
1946
|
}
|
|
1764
1947
|
const cancellations = new Map();
|
|
1765
|
-
for (const { entry } of lineageQuery(owner, lineage,
|
|
1948
|
+
for (const { entry } of lineageQuery(owner, lineage, range, `SELECT ${entryColumns}
|
|
1766
1949
|
FROM session_entries
|
|
1767
1950
|
WHERE session_id = ? AND sequence > ? AND state != 'streaming' AND type = 'cancellation'
|
|
1768
1951
|
AND json_extract(data_json, '$.targetWorkId')
|
|
@@ -1791,25 +1974,80 @@ export function readSelectedPath(owner, input) {
|
|
|
1791
1974
|
* steps past it; a statement without targets reads only its starting row,
|
|
1792
1975
|
* so a missing one is still refused.
|
|
1793
1976
|
*/
|
|
1794
|
-
function targetWalk(continues) {
|
|
1795
|
-
|
|
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 (
|
|
1796
1996
|
SELECT id, parent_id, sequence, state
|
|
1797
1997
|
FROM session_entries
|
|
1798
1998
|
WHERE session_id = ? AND id = ?
|
|
1799
1999
|
UNION ALL
|
|
1800
2000
|
SELECT parent.id, parent.parent_id, parent.sequence, parent.state
|
|
1801
|
-
FROM
|
|
1802
|
-
JOIN
|
|
1803
|
-
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
|
|
1804
2005
|
AND ${continues}
|
|
1805
|
-
)
|
|
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);
|
|
1806
2044
|
}
|
|
1807
2045
|
/**
|
|
1808
2046
|
* The walked targets, newest first, then the deepest walked row, each with
|
|
1809
2047
|
* whether the walk meant to pass it. Only these rows reach JavaScript.
|
|
1810
2048
|
*/
|
|
1811
2049
|
function walkSteps(continues) {
|
|
1812
|
-
return `SELECT id, parent_id, target, continues FROM (
|
|
2050
|
+
return `SELECT id, parent_id, sequence, target, continues FROM (
|
|
1813
2051
|
SELECT id, parent_id, sequence,
|
|
1814
2052
|
id IN (SELECT id FROM targets) AS target,
|
|
1815
2053
|
COALESCE((${continues}), 0) AS continues
|
|
@@ -1822,6 +2060,7 @@ const flag = Schema.Literals([0, 1]);
|
|
|
1822
2060
|
const decodeWalkStep = Schema.decodeUnknownResult(Schema.Struct({
|
|
1823
2061
|
id: boundedId,
|
|
1824
2062
|
parent_id: Schema.NullOr(Schema.String),
|
|
2063
|
+
sequence,
|
|
1825
2064
|
target: flag,
|
|
1826
2065
|
continues: flag,
|
|
1827
2066
|
}), closed);
|
|
@@ -1866,9 +2105,10 @@ const contextWalk = `WITH RECURSIVE walk (id, parent_id, sequence, state, head)
|
|
|
1866
2105
|
UNION ALL
|
|
1867
2106
|
SELECT parent.id, parent.parent_id, parent.sequence, parent.state,
|
|
1868
2107
|
COALESCE(child.head, ${compactionHead("parent")})
|
|
1869
|
-
FROM
|
|
1870
|
-
JOIN
|
|
1871
|
-
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
|
|
1872
2112
|
AND (child.head IS NOT child.id OR child.state = 'streaming')
|
|
1873
2113
|
)`;
|
|
1874
2114
|
/**
|
|
@@ -1880,9 +2120,7 @@ const contextWalk = `WITH RECURSIVE walk (id, parent_id, sequence, state, head)
|
|
|
1880
2120
|
function readDecidingFacts(owner, sessionId, kept, window, workId) {
|
|
1881
2121
|
const commands = JSON.stringify(window.flatMap(({ entry }) => entry.kind === "command" ? [entry.workId] : []));
|
|
1882
2122
|
const continues = (row) => `${row}.sequence > (SELECT MIN(sequence) FROM targets)`;
|
|
1883
|
-
const facts =
|
|
1884
|
-
.exec(`WITH RECURSIVE targets AS MATERIALIZED (
|
|
1885
|
-
SELECT id, sequence FROM session_entries
|
|
2123
|
+
const facts = readTargetIds(owner, sessionId, kept.id, `SELECT id, sequence FROM session_entries
|
|
1886
2124
|
WHERE session_id = ? AND type = 'compaction' AND work_id = ?
|
|
1887
2125
|
AND sequence < ? AND state != 'streaming'
|
|
1888
2126
|
UNION ALL
|
|
@@ -1892,11 +2130,7 @@ function readDecidingFacts(owner, sessionId, kept, window, workId) {
|
|
|
1892
2130
|
WHERE session_id = ? AND type = 'cancellation'
|
|
1893
2131
|
AND json_extract(data_json, '$.targetWorkId')
|
|
1894
2132
|
IN (SELECT value FROM json_each(?))
|
|
1895
|
-
AND sequence < ? AND state != 'streaming'
|
|
1896
|
-
),
|
|
1897
|
-
${targetWalk(continues("child"))}
|
|
1898
|
-
${walkSteps(continues("walk"))}`, sessionId, workId, kept.sequence, sessionId, commands, kept.sequence, sessionId, kept.id, sessionId)
|
|
1899
|
-
.toArray());
|
|
2133
|
+
AND sequence < ? AND state != 'streaming'`, [sessionId, workId, kept.sequence, sessionId, commands, kept.sequence], continues);
|
|
1900
2134
|
if (facts.length === 0)
|
|
1901
2135
|
return Object.freeze([]);
|
|
1902
2136
|
return Object.freeze(owner.sql
|
|
@@ -1915,12 +2149,40 @@ function readDecidingFacts(owner, sessionId, kept, window, workId) {
|
|
|
1915
2149
|
*/
|
|
1916
2150
|
function readContextWindow(owner, sessionId, tipId) {
|
|
1917
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
|
+
};
|
|
1918
2180
|
const rows = owner.sql
|
|
1919
|
-
.exec(`${
|
|
2181
|
+
.exec(`${source.sql}
|
|
1920
2182
|
SELECT ${entryColumns}
|
|
1921
2183
|
FROM session_entries
|
|
1922
2184
|
WHERE id IN (SELECT id FROM walk) AND state != 'streaming'
|
|
1923
|
-
ORDER BY sequence ASC`,
|
|
2185
|
+
ORDER BY sequence ASC`, ...source.bindings)
|
|
1924
2186
|
.toArray()
|
|
1925
2187
|
.map((raw) => parseEntryRow(owner, raw));
|
|
1926
2188
|
const kept = rows[0];
|
|
@@ -1981,7 +2243,7 @@ function readRunContextRows(owner, sessionId, command, current, workId) {
|
|
|
1981
2243
|
throw unavailable("Accepted Session command lineage is unavailable");
|
|
1982
2244
|
const tail = current === undefined
|
|
1983
2245
|
? []
|
|
1984
|
-
: lineageQuery(owner, current, command.sequence, `SELECT ${entryColumns}
|
|
2246
|
+
: lineageQuery(owner, current, { floor: command.sequence }, `SELECT ${entryColumns}
|
|
1985
2247
|
FROM session_entries
|
|
1986
2248
|
WHERE session_id = ? AND sequence > ? AND state != 'streaming'
|
|
1987
2249
|
AND id IN (SELECT id FROM branch)
|
|
@@ -2345,6 +2607,7 @@ export function acceptSessionCommandOwned(owner, sessionId, input, meta, origin,
|
|
|
2345
2607
|
jobId: commandFactId,
|
|
2346
2608
|
sessionId,
|
|
2347
2609
|
commandFactId,
|
|
2610
|
+
delivery: input.type === "prompt" ? input.delivery : "followUp",
|
|
2348
2611
|
});
|
|
2349
2612
|
return acceptanceFromAdmission(command, "new");
|
|
2350
2613
|
}
|
package/dist/node.js
CHANGED
|
@@ -50,8 +50,13 @@ export function nodeRuntimeHost(observeFailure) {
|
|
|
50
50
|
wakeTimer = setTimeout(() => {
|
|
51
51
|
wakeTimer = undefined;
|
|
52
52
|
wakeAt = undefined;
|
|
53
|
+
// A capped wait may finish before the actual deadline.
|
|
54
|
+
if (at > Date.now()) {
|
|
55
|
+
scheduleWake(at);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
53
58
|
void bound?.requestWake().catch(reportFailure);
|
|
54
|
-
}, Math.max(0, at - Date.now()));
|
|
59
|
+
}, Math.min(2_147_483_647, Math.max(0, at - Date.now())));
|
|
55
60
|
};
|
|
56
61
|
const runtime = Object.freeze({
|
|
57
62
|
bind: ({ reconcile, pendingWork }) => {
|
|
@@ -133,7 +138,11 @@ export function nodeRuntimeHost(observeFailure) {
|
|
|
133
138
|
return Object.freeze({
|
|
134
139
|
runtime,
|
|
135
140
|
scheduleWake,
|
|
136
|
-
close: () =>
|
|
141
|
+
close: () => {
|
|
142
|
+
scheduleWake(null);
|
|
143
|
+
closed = true;
|
|
144
|
+
return bound?.close() ?? Promise.resolve();
|
|
145
|
+
},
|
|
137
146
|
});
|
|
138
147
|
}
|
|
139
148
|
/**
|