@earendil-works/pi-durable 1.0.4 → 1.1.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/CHANGELOG.md +26 -0
- package/README.md +2 -0
- package/dist/harness/agent.d.ts.map +1 -1
- package/dist/harness/agent.js +3 -0
- package/dist/harness/agent.js.map +1 -1
- package/dist/harness/compaction.js +1 -1
- package/dist/harness/compaction.js.map +1 -1
- package/dist/harness/context.d.ts +29 -1
- package/dist/harness/context.d.ts.map +1 -1
- package/dist/harness/context.js +125 -24
- package/dist/harness/context.js.map +1 -1
- package/dist/harness/generation.d.ts.map +1 -1
- package/dist/harness/generation.js +3 -3
- package/dist/harness/generation.js.map +1 -1
- package/dist/harness/harness.d.ts.map +1 -1
- package/dist/harness/harness.js +4 -3
- package/dist/harness/harness.js.map +1 -1
- package/dist/harness/scheduler.d.ts.map +1 -1
- package/dist/harness/scheduler.js +98 -5
- package/dist/harness/scheduler.js.map +1 -1
- package/dist/harness/tool.d.ts +1 -1
- package/dist/harness/tool.d.ts.map +1 -1
- package/dist/harness/tool.js +15 -5
- package/dist/harness/tool.js.map +1 -1
- package/dist/harness/types.d.ts +18 -2
- package/dist/harness/types.d.ts.map +1 -1
- package/dist/harness/types.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/session/session.d.ts +5 -3
- package/dist/session/session.d.ts.map +1 -1
- package/dist/session/session.js +5 -4
- package/dist/session/session.js.map +1 -1
- package/dist/session/transaction.d.ts +2 -0
- package/dist/session/transaction.d.ts.map +1 -1
- package/dist/session/transaction.js +18 -1
- package/dist/session/transaction.js.map +1 -1
- package/dist/storage/jsonl/storage.d.ts +4 -0
- package/dist/storage/jsonl/storage.d.ts.map +1 -1
- package/dist/storage/memory.d.ts +2 -0
- package/dist/storage/memory.d.ts.map +1 -1
- package/dist/storage/memory.js +72 -18
- package/dist/storage/memory.js.map +1 -1
- package/dist/storage/scan.d.ts +18 -0
- package/dist/storage/scan.d.ts.map +1 -0
- package/dist/storage/scan.js +28 -0
- package/dist/storage/scan.js.map +1 -0
- package/dist/storage/sqlite/cloudflare.d.ts +46 -0
- package/dist/storage/sqlite/cloudflare.d.ts.map +1 -0
- package/dist/storage/sqlite/cloudflare.js +106 -0
- package/dist/storage/sqlite/cloudflare.js.map +1 -0
- package/dist/storage/sqlite/storage.d.ts +2 -0
- package/dist/storage/sqlite/storage.d.ts.map +1 -1
- package/dist/storage/sqlite/storage.js +68 -16
- package/dist/storage/sqlite/storage.js.map +1 -1
- package/dist/testing/storage-conformance.d.ts.map +1 -1
- package/dist/testing/storage-conformance.js +58 -0
- package/dist/testing/storage-conformance.js.map +1 -1
- package/dist/types.d.ts +30 -7
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +10 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scheduler.d.ts","sourceRoot":"","sources":["../../src/harness/scheduler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,OAAO,EAAY,KAAK,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAE/E,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AACpD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAC7D,OAAO,KAAK,EAEX,cAAc,EAQd,OAAO,EAEP,MAAM,EACN,WAAW,EACX,UAAU,EAGV,MAAM,aAAa,CAAC;AAGrB,OAAO,KAAK,EACX,KAAK,EAEL,kBAAkB,EAClB,iBAAiB,EACjB,cAAc,EACd,gBAAgB,EAChB,QAAQ,EACR,WAAW,EACX,cAAc,EACd,MAAM,YAAY,CAAC;AAGpB,KAAK,aAAa,GAAG,UAAU,CAAC,SAAS,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC;AAiEjE,wEAAwE;AACxE,MAAM,MAAM,gBAAgB,GAAG,OAAO,CAAC,WAAW,CAAC,SAAS,CAAC,EAAE;IAAE,QAAQ,CAAC,MAAM,EAAE,SAAS,GAAG,UAAU,CAAA;CAAE,CAAC,CAAC;AAE5G,0GAA0G;AAC1G,MAAM,MAAM,iBAAiB,GAAG;IAAE,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAAC,KAAK,IAAI,IAAI,CAAA;CAAE,CAAC;AAuBhF,MAAM,MAAM,oBAAoB,GAAG;IAClC,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC;IAC9B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,cAAc,CAAC;IAClC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,sGAAsG;IACtG,QAAQ,CAAC,KAAK,EAAE,CAAC,cAAc,EAAE,cAAc,EAAE,QAAQ,EAAE,gBAAgB,EAAE,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,KAAK,CAAC,CAAC;IACjH,iDAAiD;IACjD,QAAQ,CAAC,QAAQ,EAAE,MAAM,QAAQ,CAAC;IAClC,oEAAoE;IACpE,QAAQ,CAAC,GAAG,EAAE,CAAC,cAAc,EAAE,cAAc,EAAE,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,YAAY,GAAG,SAAS,CAAC,CAAC;IACtG,QAAQ,CAAC,GAAG,EAAE,MAAM,MAAM,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IAC1C,sGAAsG;IACtG,QAAQ,CAAC,aAAa,EAAE,CAAC,EAAE,EAAE,WAAW,EAAE,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,gBAAgB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7G,0FAA0F;IAC1F,QAAQ,CAAC,cAAc,EAAE,CAAC,EAAE,EAAE,WAAW,EAAE,cAAc,EAAE,cAAc,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5F,wFAAwF;IACxF,QAAQ,CAAC,YAAY,EAAE,CACtB,EAAE,EAAE,cAAc,EAClB,OAAO,EAAE,iBAAiB,EAC1B,OAAO,EAAE,OAAO,KACZ,OAAO,CAAC,kBAAkB,GAAG,SAAS,CAAC,CAAC;IAC7C,qFAAqF;IACrF,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;CAC1B,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH,qBAAa,aAAa;;
|
|
1
|
+
{"version":3,"file":"scheduler.d.ts","sourceRoot":"","sources":["../../src/harness/scheduler.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { copyJson } from "@earendil-works/chord";
|
|
2
2
|
import { awaitWithContext, withAbortSignal } from "@earendil-works/chord/context";
|
|
3
3
|
import { agentHooks } from "./agent.js";
|
|
4
|
-
import {
|
|
4
|
+
import { readContextFrom } from "./context.js";
|
|
5
5
|
import { closedError, scanAll, Waiters } from "./util.js";
|
|
6
6
|
const SCAN_PAGE_SIZE = 256;
|
|
7
7
|
/** Longest delay `setTimeout` supports; longer sleeps wait in several steps. */
|
|
@@ -37,6 +37,14 @@ export class TaskScheduler {
|
|
|
37
37
|
#conversation;
|
|
38
38
|
#context;
|
|
39
39
|
#live = new Map();
|
|
40
|
+
/**
|
|
41
|
+
* Context range last read through a task runtime, per conversation: a later read, by any of its tasks, scans only
|
|
42
|
+
* newer entries. `idleSince` is the Harness time it was first seen idle. Derived and never persisted; dropped at the
|
|
43
|
+
* first idle check after `settings.contextRetentionMs` of idleness, and at close.
|
|
44
|
+
*/
|
|
45
|
+
#contexts = new Map();
|
|
46
|
+
/** Timer for the earliest expiry of an idle context, only where timers can be unreferenced (see `#scheduleExpiry`). */
|
|
47
|
+
#expiry;
|
|
40
48
|
#invocations = new Map();
|
|
41
49
|
#taskWaiters = new Waiters();
|
|
42
50
|
/** Idle waiters by conversation; `undefined` waits for the whole Harness. */
|
|
@@ -279,14 +287,77 @@ export class TaskScheduler {
|
|
|
279
287
|
this.#scheduleReconcile();
|
|
280
288
|
if (!changed)
|
|
281
289
|
return;
|
|
282
|
-
this.#
|
|
290
|
+
this.#settleIdle();
|
|
283
291
|
this.#kick();
|
|
284
292
|
}
|
|
285
|
-
|
|
293
|
+
/**
|
|
294
|
+
* `settings.contextRetentionMs`, or 0 when the host's settings throw. Kept contexts are only a cache, so dropping them
|
|
295
|
+
* is safe, while a throw here would escape commit listeners, reconciliation, and the expiry timer.
|
|
296
|
+
*/
|
|
297
|
+
#contextRetentionMs() {
|
|
298
|
+
try {
|
|
299
|
+
return this.#settings().contextRetentionMs;
|
|
300
|
+
}
|
|
301
|
+
catch (error) {
|
|
302
|
+
this.#report(error);
|
|
303
|
+
return 0;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
/** Resolve idle waiters, and drop each kept context whose conversation has been idle for the retention period. */
|
|
307
|
+
#settleIdle() {
|
|
286
308
|
for (const conversationId of this.#idleWaiters.keys()) {
|
|
287
309
|
if (this.#idle(conversationId))
|
|
288
310
|
this.#idleWaiters.resolve(conversationId);
|
|
289
311
|
}
|
|
312
|
+
const now = this.#now();
|
|
313
|
+
const retention = this.#contextRetentionMs();
|
|
314
|
+
for (const [conversationId, kept] of this.#contexts) {
|
|
315
|
+
if (kept.idleSince !== undefined && now - kept.idleSince >= retention) {
|
|
316
|
+
this.#contexts.delete(conversationId);
|
|
317
|
+
}
|
|
318
|
+
else if (!this.#idle(conversationId)) {
|
|
319
|
+
kept.idleSince = undefined;
|
|
320
|
+
}
|
|
321
|
+
else if (retention > 0) {
|
|
322
|
+
kept.idleSince ??= now;
|
|
323
|
+
}
|
|
324
|
+
else {
|
|
325
|
+
this.#contexts.delete(conversationId);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
this.#scheduleExpiry();
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Run `#settleIdle()` when the earliest idle context expires. The timer is unreferenced, so it never keeps the process
|
|
332
|
+
* alive. Where timers cannot be unreferenced, as in Cloudflare Workers, none is kept: a pending timer could keep a
|
|
333
|
+
* Durable Object from being evicted, and eviction frees the contexts. There, task changes alone check expiry.
|
|
334
|
+
*/
|
|
335
|
+
#scheduleExpiry() {
|
|
336
|
+
const retention = this.#contextRetentionMs();
|
|
337
|
+
let at;
|
|
338
|
+
for (const kept of this.#contexts.values()) {
|
|
339
|
+
if (kept.idleSince !== undefined && (at === undefined || kept.idleSince + retention < at)) {
|
|
340
|
+
at = kept.idleSince + retention;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
if (this.#expiry !== undefined && this.#expiry.at === at)
|
|
344
|
+
return;
|
|
345
|
+
if (this.#expiry !== undefined)
|
|
346
|
+
clearTimeout(this.#expiry.timer);
|
|
347
|
+
this.#expiry = undefined;
|
|
348
|
+
if (at === undefined || this.#closing)
|
|
349
|
+
return;
|
|
350
|
+
const timer = setTimeout(() => {
|
|
351
|
+
this.#expiry = undefined;
|
|
352
|
+
this.#settleIdle();
|
|
353
|
+
}, Math.min(Math.max(0, at - this.#now()), MAX_TIMER_DELAY));
|
|
354
|
+
const unref = timer.unref;
|
|
355
|
+
if (typeof unref !== "function") {
|
|
356
|
+
clearTimeout(timer);
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
unref.call(timer);
|
|
360
|
+
this.#expiry = { at, timer };
|
|
290
361
|
}
|
|
291
362
|
// ─── Ownership ───────────────────────────────────────────────────────────
|
|
292
363
|
#scheduleReconcile() {
|
|
@@ -349,7 +420,7 @@ export class TaskScheduler {
|
|
|
349
420
|
if (!this.#closing)
|
|
350
421
|
this.#report(error);
|
|
351
422
|
}
|
|
352
|
-
this.#
|
|
423
|
+
this.#settleIdle();
|
|
353
424
|
}
|
|
354
425
|
/** Whether any of `ids` holds or ended with an outcome other than `completed`. */
|
|
355
426
|
async #anyFailed(ids) {
|
|
@@ -552,6 +623,10 @@ export class TaskScheduler {
|
|
|
552
623
|
const error = closedError();
|
|
553
624
|
this.#taskWaiters.rejectAll(error);
|
|
554
625
|
this.#idleWaiters.rejectAll(error);
|
|
626
|
+
this.#contexts.clear();
|
|
627
|
+
if (this.#expiry !== undefined)
|
|
628
|
+
clearTimeout(this.#expiry.timer);
|
|
629
|
+
this.#expiry = undefined;
|
|
555
630
|
for (const invocation of this.#invocations.values())
|
|
556
631
|
invocation.controller.abort();
|
|
557
632
|
}
|
|
@@ -1022,7 +1097,25 @@ export class TaskScheduler {
|
|
|
1022
1097
|
return token === undefined || entry?.kind === token.kind ? entry : undefined;
|
|
1023
1098
|
});
|
|
1024
1099
|
}),
|
|
1025
|
-
context: (conversationId, context,
|
|
1100
|
+
context: (conversationId, context, options) => this.#read(invocation, async () => {
|
|
1101
|
+
const { view, range } = await readContextFrom(this.#session, this.#storage, conversationId, context, options?.at, this.#contexts.get(conversationId)?.range);
|
|
1102
|
+
// Keep it unless the invocation ended or a concurrent read already kept a newer range.
|
|
1103
|
+
const kept = this.#contexts.get(conversationId);
|
|
1104
|
+
if (range !== undefined &&
|
|
1105
|
+
!this.#closing &&
|
|
1106
|
+
!invocation.ended &&
|
|
1107
|
+
(kept === undefined || kept.range.bounds.tail <= range.bounds.tail)) {
|
|
1108
|
+
// A read of another, idle conversation starts or continues its retention period.
|
|
1109
|
+
if (!this.#idle(conversationId)) {
|
|
1110
|
+
this.#contexts.set(conversationId, { range, idleSince: undefined });
|
|
1111
|
+
}
|
|
1112
|
+
else if (this.#contextRetentionMs() > 0) {
|
|
1113
|
+
this.#contexts.set(conversationId, { range, idleSince: kept?.idleSince ?? this.#now() });
|
|
1114
|
+
this.#scheduleExpiry();
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
return view;
|
|
1118
|
+
}),
|
|
1026
1119
|
now: () => {
|
|
1027
1120
|
if (invocation.ended)
|
|
1028
1121
|
throw endedError(invocation);
|