@earendil-works/pi-durable 1.0.3 → 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 +44 -0
- package/README.md +2 -0
- package/dist/env/node-watch.d.ts.map +1 -1
- package/dist/env/node-watch.js +89 -35
- package/dist/env/node-watch.js.map +1 -1
- package/dist/harness/agent.d.ts.map +1 -1
- package/dist/harness/agent.js +8 -1
- 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/output.d.ts.map +1 -1
- package/dist/harness/output.js +15 -3
- package/dist/harness/output.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/env-conformance.d.ts.map +1 -1
- package/dist/testing/env-conformance.js +34 -0
- package/dist/testing/env-conformance.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/tools/bash.d.ts +21 -3
- package/dist/tools/bash.d.ts.map +1 -1
- package/dist/tools/bash.js +85 -38
- package/dist/tools/bash.js.map +1 -1
- package/dist/tools/index.d.ts +2 -2
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +2 -2
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/read.d.ts.map +1 -1
- package/dist/tools/read.js +4 -2
- package/dist/tools/read.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
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,49 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.1.0] - 2026-10-07
|
|
4
|
+
|
|
5
|
+
### Breaking Changes
|
|
6
|
+
|
|
7
|
+
- `Storage` implementations must honor the new `order` field of `ConversationQuery`, `EntryQuery`, `TaskQuery`, and `SubmissionQuery`, and continue a cursor in the order it was returned with. A storage that ignores it returns pages in the wrong direction; the conformance suite covers both orders ([#10546](https://github.com/earendil-works/pi/issues/10546)).
|
|
8
|
+
- `TaskRuntime.context()` takes the cutoff as an options object, matching `Conversation.context()`: `runtime.context(conversationId, context, { at })` replaces `runtime.context(conversationId, context, at)`.
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- `ToolExecutionApi.models` and `HookApi.models`: the Harness's `models`, so tools and hooks can resolve models and make requests with the same catalog, credentials, and request transforms as generation ([#10395](https://github.com/earendil-works/pi/issues/10395)).
|
|
13
|
+
- `Conversation.context(context, { at })` returns the model context as of a visible earlier entry, the same view `fork(at)` would start with, without creating a conversation ([#10512](https://github.com/earendil-works/pi/issues/10512)).
|
|
14
|
+
- `ScanOrder` (`ascending` or `descending`) as `order` on conversation, entry, task, and submission scans, including `Tx.scanTasks()` and `Conversation.entries()`, so hosts can page the newest tasks first. Defaults are unchanged: entries newest first, everything else oldest first. A cursor continues in its scan's order, so the query may omit `order`; asking for the other order with it throws ([#10546](https://github.com/earendil-works/pi/issues/10546)).
|
|
15
|
+
- Task records carry `startedAt` and `endedAt`, wall-clock times the Session stamps at the first change to `running` and at the change to `terminal`, with `HarnessOptions.now` or the new `createSession(storage, { now })` option. `startedAt` survives waits and reopen. `inspect()` shows them on each live task's record. Records written by earlier versions have neither ([#10549](https://github.com/earendil-works/pi/issues/10549)).
|
|
16
|
+
- `pi.tool-result` messages carry `durationMs`: how long `execute()` took in that attempt, measured with a monotonic clock. Calls that did not execute, and interrupted or aborted calls, have none ([#10549](https://github.com/earendil-works/pi/issues/10549)).
|
|
17
|
+
- Assistant messages carry the `durationMs` pi-ai measures for each response ([#10549](https://github.com/earendil-works/pi/issues/10549)).
|
|
18
|
+
- `openDurableObjectSqliteStorage(ctx.storage)` in `@earendil-works/pi-durable/storage/sqlite/cloudflare`: SQLite storage on a SQLite-backed Cloudflare Durable Object.
|
|
19
|
+
- `settings.contextRetentionMs` (default ten minutes): how long an idle conversation keeps its last context read in memory; `0` drops it once idle.
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
- `runtime.context()` keeps each conversation's last read range and derived view in memory: a later read by any of its tasks with the same head marker scans and derives only the entries committed since. A generation no longer rereads and re-derives the whole transcript for each model request. Busy conversations keep it; idle ones for `settings.contextRetentionMs`.
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
|
|
27
|
+
- A conversation's first system message, and the baseline after a compaction or reset, now leads the model context instead of following the input that started the run. Providers with native mid-conversation tool changes, such as Anthropic, keep the prompt cache across later tool changes instead of rewriting it in full ([#10542](https://github.com/earendil-works/pi/issues/10542)).
|
|
28
|
+
|
|
29
|
+
## [1.0.4] - 2026-10-05
|
|
30
|
+
|
|
31
|
+
### Breaking Changes
|
|
32
|
+
|
|
33
|
+
- `NodeExecutionEnv.watch()` fails with `permission_denied` when a target, or a watched directory itself, cannot be read for lack of permission; before, it was watched as if missing. Later rescans that hit this report `{ error }` with `permission_denied` instead of `invalid`.
|
|
34
|
+
- The env conformance suite checks three more `watch()` behaviors: changes to the file a watched symbolic link points to, recursive coverage below a target that a non-recursive target overlaps, and a directory replaced at the same path. Custom environments that passed the 1.0.3 suite may need changes.
|
|
35
|
+
|
|
36
|
+
### Added
|
|
37
|
+
|
|
38
|
+
- `createPowerShellTool()` in `@earendil-works/pi-durable/tools`: a `powershell` tool that runs `pwsh`, else Windows PowerShell, directly through argv `exec` (no other shell parses the command), with UTF-8 output and the `bash` tool's output window, spill and errors.
|
|
39
|
+
|
|
40
|
+
### Fixed
|
|
41
|
+
|
|
42
|
+
- The `read` tool reads a file that grows while it is read (an active log) instead of failing with "changed while it was read"; it reads again only when the file shrank or was rewritten in place.
|
|
43
|
+
- `NodeExecutionEnv.watch()`: a directory replaced at the same path gets a new native watcher; a target that is a symbolic link to a file reports changes to that file; a non-recursive target no longer stops an overlapping recursive target from covering subdirectories; closing during a rescan no longer leaks watchers.
|
|
44
|
+
- Bounded tool output keeps a U+FEFF at the start of the retained tail, and drops a byte-order mark only at the very start of byte output.
|
|
45
|
+
- `settings.progress` fields given as `undefined` keep their defaults.
|
|
46
|
+
|
|
3
47
|
## [1.0.3] - 2026-10-05
|
|
4
48
|
|
|
5
49
|
### Breaking Changes
|
package/README.md
CHANGED
|
@@ -224,6 +224,7 @@ const harness = await Harness.open(storage, {
|
|
|
224
224
|
compaction: { reserveTokens: 16384 },
|
|
225
225
|
progress: { partialIntervalMs: 100, outputIntervalMs: 100 },
|
|
226
226
|
toolExecution: "parallel",
|
|
227
|
+
contextRetentionMs: 600_000, // idle conversations keep their context in memory this long (checked lazily where timers cannot be unreferenced)
|
|
227
228
|
get followUpMode() {
|
|
228
229
|
return userSettings.followUpMode;
|
|
229
230
|
},
|
|
@@ -539,6 +540,7 @@ const usage = await harness.usage(context); // { models: { "openai/gpt-6-sol": U
|
|
|
539
540
|
| Memory | `MemoryStorage` from the package root | Nothing is persisted. |
|
|
540
541
|
| SQLite | `openNodeSqliteStorage(file)` from `@earendil-works/pi-durable/storage/sqlite/node` | One database file. WAL mode with `synchronous = NORMAL`: commits survive process crashes; the newest may be lost on power or host failure. |
|
|
541
542
|
| JSONL | `openNodeJsonlStorage(directory, context)` from `@earendil-works/pi-durable/storage/jsonl/node` | Append-only files in one directory. Pass `{ fsync: true }` to flush before each commit marker. |
|
|
543
|
+
| Cloudflare Durable Object | `openDurableObjectSqliteStorage(ctx.storage)` from `@earendil-works/pi-durable/storage/sqlite/cloudflare` | The SQLite storage of one SQLite-backed Durable Object; commits are its storage transactions. |
|
|
542
544
|
|
|
543
545
|
One process owns a storage at a time; there is no cross-process locking. The portable SQLite and JSONL cores (`/storage/sqlite`, `/storage/jsonl`) run without Node APIs, for example on Bun or in Cloudflare Durable Objects, given an asynchronous `SqliteDatabase` facade or a `FileSystem` from `@earendil-works/pi-durable/env`.
|
|
544
546
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"node-watch.d.ts","sourceRoot":"","sources":["../../src/env/node-watch.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AACrD,OAAO,EAAa,KAAK,WAAW,EAAE,KAAK,WAAW,EAAE,KAAK,WAAW,EAAE,MAAM,YAAY,CAAC;AAE7F,sCAAsC;AACtC,MAAM,WAAW,gBAAgB;IAChC;;;OAGG;IACH,IAAI,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;IAC5B,qEAAqE;IACrE,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,2DAA2D;IAC3D,cAAc,CAAC,EAAE,MAAM,CAAC;CACxB;
|
|
1
|
+
{"version":3,"file":"node-watch.d.ts","sourceRoot":"","sources":["../../src/env/node-watch.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AACrD,OAAO,EAAa,KAAK,WAAW,EAAE,KAAK,WAAW,EAAE,KAAK,WAAW,EAAE,MAAM,YAAY,CAAC;AAE7F,sCAAsC;AACtC,MAAM,WAAW,gBAAgB;IAChC;;;OAGG;IACH,IAAI,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;IAC5B,qEAAqE;IACrE,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,2DAA2D;IAC3D,cAAc,CAAC,EAAE,MAAM,CAAC;CACxB;AAkHD;;;;;GAKG;AACH,qBAAa,eAAgB,YAAW,WAAW;;IAelD,OAAO,eAWN;IAED,6FAA6F;IAC7F,OAAa,IAAI,CAChB,OAAO,EAAE,SAAS,WAAW,EAAE,EAC/B,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,EACrC,QAAQ,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,IAAI,EACvC,OAAO,EAAE,gBAAgB,GACvB,OAAO,CAAC,eAAe,CAAC,CAwB1B;IAED,IAAI,IAAI,IAAI,QAAQ,GAAG,SAAS,CAE/B;IAEK,KAAK,CAAC,QAAQ,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAI5C;CA6SD"}
|
package/dist/env/node-watch.js
CHANGED
|
@@ -36,6 +36,9 @@ class BudgetExceeded extends Error {
|
|
|
36
36
|
function isNodeError(error) {
|
|
37
37
|
return error instanceof Error && "code" in error;
|
|
38
38
|
}
|
|
39
|
+
function isDenied(error) {
|
|
40
|
+
return isNodeError(error) && (error.code === "EACCES" || error.code === "EPERM");
|
|
41
|
+
}
|
|
39
42
|
function entryOf(stats, hash) {
|
|
40
43
|
const kind = stats.isFile()
|
|
41
44
|
? "file"
|
|
@@ -146,7 +149,7 @@ export class NodeFileWatcher {
|
|
|
146
149
|
this.#timer = undefined;
|
|
147
150
|
clearTimeout(this.#settleTimer);
|
|
148
151
|
this.#settleTimer = undefined;
|
|
149
|
-
for (const watcher of this.#watchers.values())
|
|
152
|
+
for (const { watcher } of this.#watchers.values())
|
|
150
153
|
watcher.close();
|
|
151
154
|
this.#watchers.clear();
|
|
152
155
|
}
|
|
@@ -204,9 +207,10 @@ export class NodeFileWatcher {
|
|
|
204
207
|
} while (this.#dirty && !this.#closed);
|
|
205
208
|
}
|
|
206
209
|
catch (error) {
|
|
210
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
207
211
|
const fileError = error instanceof FileError
|
|
208
212
|
? error
|
|
209
|
-
: new FileError(
|
|
213
|
+
: new FileError(isDenied(error) ? "permission_denied" : "invalid", message);
|
|
210
214
|
this.#deliver({ error: fileError });
|
|
211
215
|
this.#stop();
|
|
212
216
|
}
|
|
@@ -220,12 +224,16 @@ export class NodeFileWatcher {
|
|
|
220
224
|
async #sync(report) {
|
|
221
225
|
const changed = new Set();
|
|
222
226
|
for (let round = 0; round < 10 && !this.#closed; round++) {
|
|
223
|
-
const
|
|
227
|
+
const scan = await this.#scan();
|
|
228
|
+
// Closed during the scan: installing watchers now would leak them.
|
|
229
|
+
if (this.#closed)
|
|
230
|
+
break;
|
|
231
|
+
const next = scan.snapshot;
|
|
224
232
|
if (report)
|
|
225
233
|
for (const path of this.#diff(this.#snapshot, next))
|
|
226
234
|
changed.add(path);
|
|
227
235
|
this.#snapshot = next;
|
|
228
|
-
if (this.#mode === "polling" || !this.#reconcileWatchers(
|
|
236
|
+
if (this.#mode === "polling" || !this.#reconcileWatchers(scan))
|
|
229
237
|
break;
|
|
230
238
|
if (process.platform === "darwin")
|
|
231
239
|
this.#scheduleSettle();
|
|
@@ -254,9 +262,17 @@ export class NodeFileWatcher {
|
|
|
254
262
|
}
|
|
255
263
|
async #scan() {
|
|
256
264
|
const snapshot = new Map();
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
265
|
+
const linkedFiles = new Set();
|
|
266
|
+
// Directories are counted once, but traversed once per target: overlapping targets differ in recursion and
|
|
267
|
+
// exclusions, and an entry recorded for one target must still be descended into for another.
|
|
268
|
+
const counted = new Set();
|
|
269
|
+
const traversed = new Set();
|
|
270
|
+
/** Kinds of listed entries, not following links: a target that links to a directory is recorded by `stat`, but
|
|
271
|
+
* symbolic links below a recursive target are not followed. */
|
|
272
|
+
const listed = new Map();
|
|
273
|
+
const countDirectory = (path) => {
|
|
274
|
+
counted.add(path);
|
|
275
|
+
if (counted.size > this.#maxDirectories) {
|
|
260
276
|
throw new BudgetExceeded(`Watched paths exceed ${this.#maxDirectories} directories`);
|
|
261
277
|
}
|
|
262
278
|
};
|
|
@@ -270,12 +286,19 @@ export class NodeFileWatcher {
|
|
|
270
286
|
}
|
|
271
287
|
snapshot.set(path, entryOf(stats, hash));
|
|
272
288
|
};
|
|
273
|
-
const scanDirectory = async (target, directory) => {
|
|
289
|
+
const scanDirectory = async (index, target, directory) => {
|
|
290
|
+
const key = `${index}\0${directory}`;
|
|
291
|
+
if (traversed.has(key))
|
|
292
|
+
return;
|
|
293
|
+
traversed.add(key);
|
|
274
294
|
let names;
|
|
275
295
|
try {
|
|
276
296
|
names = await readdir(directory);
|
|
277
297
|
}
|
|
278
298
|
catch (error) {
|
|
299
|
+
// The watched directory itself must be readable; below it, unreadable directories are skipped.
|
|
300
|
+
if (directory === target.path && isDenied(error))
|
|
301
|
+
throw error;
|
|
279
302
|
if (isNodeError(error) && ["ENOENT", "EACCES", "EPERM", "ENOTDIR"].includes(error.code ?? ""))
|
|
280
303
|
return;
|
|
281
304
|
throw error;
|
|
@@ -284,19 +307,24 @@ export class NodeFileWatcher {
|
|
|
284
307
|
if (excluded(target, name))
|
|
285
308
|
continue;
|
|
286
309
|
const path = `${directory.endsWith(sep) ? directory : directory + sep}${name}`;
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
310
|
+
let kind = listed.get(path);
|
|
311
|
+
if (kind === undefined) {
|
|
312
|
+
const stats = await lstat(path).catch(() => undefined);
|
|
313
|
+
if (stats === undefined)
|
|
314
|
+
continue;
|
|
315
|
+
kind = entryOf(stats).kind;
|
|
316
|
+
listed.set(path, kind);
|
|
317
|
+
// A target's own entry (following links) wins over its listing by another target.
|
|
318
|
+
if (!snapshot.has(path))
|
|
319
|
+
await record(path, stats);
|
|
320
|
+
}
|
|
321
|
+
if (target.recursive && kind === "directory") {
|
|
322
|
+
countDirectory(path);
|
|
323
|
+
await scanDirectory(index, target, path);
|
|
296
324
|
}
|
|
297
325
|
}
|
|
298
326
|
};
|
|
299
|
-
for (const target of this.#targets) {
|
|
327
|
+
for (const [index, target] of this.#targets.entries()) {
|
|
300
328
|
for (const ancestor of ancestorsOf(target.path)) {
|
|
301
329
|
if (snapshot.has(ancestor))
|
|
302
330
|
continue;
|
|
@@ -305,24 +333,37 @@ export class NodeFileWatcher {
|
|
|
305
333
|
if (stats !== undefined)
|
|
306
334
|
snapshot.set(ancestor, { ...entryOf(stats), size: 0, mtimeMs: 0 });
|
|
307
335
|
}
|
|
308
|
-
// The target itself may be a symbolic link to what is watched; follow it.
|
|
309
|
-
|
|
310
|
-
|
|
336
|
+
// The target itself may be a symbolic link to what is watched; follow it. A missing target is watched for its
|
|
337
|
+
// creation; one that cannot be reached for lack of permission fails.
|
|
338
|
+
let stats;
|
|
339
|
+
try {
|
|
340
|
+
stats = await stat(target.path);
|
|
341
|
+
}
|
|
342
|
+
catch (error) {
|
|
343
|
+
if (isDenied(error))
|
|
344
|
+
throw error;
|
|
311
345
|
continue;
|
|
346
|
+
}
|
|
312
347
|
await record(target.path, stats);
|
|
348
|
+
if (stats.isFile() && (await lstat(target.path).catch(() => undefined))?.isSymbolicLink()) {
|
|
349
|
+
linkedFiles.add(target.path);
|
|
350
|
+
}
|
|
313
351
|
if (stats.isDirectory()) {
|
|
314
|
-
countDirectory();
|
|
315
|
-
await scanDirectory(target, target.path);
|
|
352
|
+
countDirectory(target.path);
|
|
353
|
+
await scanDirectory(index, target, target.path);
|
|
316
354
|
}
|
|
317
355
|
}
|
|
318
|
-
return snapshot;
|
|
356
|
+
return { snapshot, linkedFiles };
|
|
319
357
|
}
|
|
320
358
|
/**
|
|
321
|
-
* Watch every existing ancestor of each target, each target directory,
|
|
322
|
-
*
|
|
359
|
+
* Watch every existing ancestor of each target, each target directory, each target that is a symbolic link to a file
|
|
360
|
+
* (changes to that file are not events of the link's directory), and on Linux each directory below a recursive target
|
|
361
|
+
* (elsewhere one recursive watcher per target). Returns whether a watcher was added.
|
|
323
362
|
*/
|
|
324
|
-
#reconcileWatchers(snapshot) {
|
|
363
|
+
#reconcileWatchers({ snapshot, linkedFiles }) {
|
|
325
364
|
const wanted = new Map();
|
|
365
|
+
for (const path of linkedFiles)
|
|
366
|
+
wanted.set(path, false);
|
|
326
367
|
const perDirectory = process.platform === "linux" || process.platform === "android";
|
|
327
368
|
for (const target of this.#targets) {
|
|
328
369
|
for (const ancestor of ancestorsOf(target.path)) {
|
|
@@ -341,26 +382,32 @@ export class NodeFileWatcher {
|
|
|
341
382
|
}
|
|
342
383
|
}
|
|
343
384
|
}
|
|
344
|
-
for (const [path,
|
|
345
|
-
|
|
346
|
-
|
|
385
|
+
for (const [path, installed] of this.#watchers) {
|
|
386
|
+
const entry = snapshot.get(path);
|
|
387
|
+
// Gone, or replaced: a watcher follows the directory it was installed on, not the path.
|
|
388
|
+
if (!wanted.has(path) || entry === undefined || entry.dev !== installed.dev || entry.ino !== installed.ino) {
|
|
389
|
+
installed.watcher.close();
|
|
347
390
|
this.#watchers.delete(path);
|
|
348
391
|
}
|
|
349
392
|
}
|
|
350
393
|
let added = false;
|
|
351
394
|
for (const [path, recursive] of wanted) {
|
|
352
|
-
|
|
395
|
+
const entry = snapshot.get(path);
|
|
396
|
+
if (this.#watchers.has(path) || entry === undefined)
|
|
353
397
|
continue;
|
|
398
|
+
const linked = linkedFiles.has(path);
|
|
354
399
|
try {
|
|
355
|
-
const watcher = fsWatch(path, { recursive, persistent: false }, (_event, filename) =>
|
|
400
|
+
const watcher = fsWatch(path, { recursive, persistent: false }, (_event, filename) => linked
|
|
401
|
+
? this.#onLinkedFileEvent(path)
|
|
402
|
+
: this.#onEvent(path, filename === null ? undefined : String(filename)));
|
|
356
403
|
// A watched directory that disappears or fails: rescan, which also replaces the watcher.
|
|
357
404
|
watcher.on("error", () => {
|
|
358
405
|
watcher.close();
|
|
359
|
-
if (this.#watchers.get(path) === watcher)
|
|
406
|
+
if (this.#watchers.get(path)?.watcher === watcher)
|
|
360
407
|
this.#watchers.delete(path);
|
|
361
408
|
this.#scheduleFlush();
|
|
362
409
|
});
|
|
363
|
-
this.#watchers.set(path, watcher);
|
|
410
|
+
this.#watchers.set(path, { watcher, dev: entry.dev, ino: entry.ino });
|
|
364
411
|
added = true;
|
|
365
412
|
}
|
|
366
413
|
catch (error) {
|
|
@@ -377,7 +424,7 @@ export class NodeFileWatcher {
|
|
|
377
424
|
if (this.#mode === "polling")
|
|
378
425
|
return;
|
|
379
426
|
this.#mode = "polling";
|
|
380
|
-
for (const watcher of this.#watchers.values())
|
|
427
|
+
for (const { watcher } of this.#watchers.values())
|
|
381
428
|
watcher.close();
|
|
382
429
|
this.#watchers.clear();
|
|
383
430
|
this.#deliver({ overflow: true });
|
|
@@ -396,6 +443,13 @@ export class NodeFileWatcher {
|
|
|
396
443
|
if (relevant || filename === undefined)
|
|
397
444
|
this.#scheduleFlush();
|
|
398
445
|
}
|
|
446
|
+
/** The file a target links to changed: report the target. */
|
|
447
|
+
#onLinkedFileEvent(target) {
|
|
448
|
+
if (this.#closed)
|
|
449
|
+
return;
|
|
450
|
+
this.#events.add(target);
|
|
451
|
+
this.#scheduleFlush();
|
|
452
|
+
}
|
|
399
453
|
#inScope(path) {
|
|
400
454
|
for (const target of this.#targets) {
|
|
401
455
|
if (isWithin(target.path, path))
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"node-watch.js","sourceRoot":"","sources":["../../src/env/node-watch.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash } from \"node:crypto\";\nimport { type FSWatcher, watch as fsWatch, type Stats } from \"node:fs\";\nimport { lstat, readdir, readFile, stat, statfs } from \"node:fs/promises\";\nimport { dirname, relative, sep } from \"node:path\";\nimport type { Context } from \"@earendil-works/chord\";\nimport { FileError, type FileWatcher, type WatchChange, type WatchTarget } from \"./index.ts\";\n\n/** How `NodeExecutionEnv` watches. */\nexport interface NodeWatchOptions {\n\t/**\n\t * Force a mode. By default `polling` is chosen on Windows, where native watchers keep directories open and so block\n\t * renaming their parents, and for file systems that do not report remote changes.\n\t */\n\tmode?: \"native\" | \"polling\";\n\t/** Interval between snapshots in `polling` mode; default 2000 ms. */\n\tpollIntervalMs?: number;\n\t/** Most directories one watcher covers; default 10,000. */\n\tmaxDirectories?: number;\n}\n\ntype ResolvedTarget = {\n\treadonly path: string;\n\treadonly recursive: boolean;\n\treadonly hidden: boolean;\n\treadonly names: ReadonlySet<string>;\n};\n\n/** What a snapshot remembers of one path. Directories and ancestors are compared by identity only. */\ntype Entry = {\n\treadonly kind: \"file\" | \"directory\" | \"symlink\" | \"other\";\n\treadonly dev: number;\n\treadonly ino: number;\n\treadonly size: number;\n\treadonly mtimeMs: number;\n\treadonly hash?: string;\n};\n\ntype Snapshot = Map<string, Entry>;\n\nconst DEBOUNCE_MS = 50;\n/**\n * On macOS, `fs.watch` returns before libuv's FSEvents stream is live, and changes in between are never reported\n * (about 2% of changes made right after the call, none after 100 ms). A rescan this long after installing watchers\n * catches them.\n */\nconst FSEVENTS_SETTLE_MS = 500;\nconst DEFAULT_POLL_MS = 2000;\nconst DEFAULT_MAX_DIRECTORIES = 10_000;\n/** In polling mode, recently modified small files are also compared by content: a second write within the file system's\n * timestamp granularity can keep size and modification time. */\nconst HASH_MAX_BYTES = 256 * 1024;\nconst HASH_RECENT_MS = 5000;\n\n/** Linux `statfs` magic numbers of file systems that accept watches but do not report changes made elsewhere. */\nconst UNRELIABLE_FILE_SYSTEMS = new Set([\n\t0x6969, // NFS\n\t0x517b, // SMB\n\t0xff534d42, // CIFS\n\t0xfe534d42, // SMB2\n\t0x65735546, // FUSE (sshfs, Android shared storage)\n\t0x01021997, // 9P (WSL2 Windows drives)\n\t0x0bd00bd0, // Lustre\n\t0x47504653, // GPFS\n\t0x00c36400, // Ceph\n\t0x5346414f, // OpenAFS\n\t0x6b414653, // kAFS\n\t0x5dca2df5, // sdcardfs\n]);\n\nclass BudgetExceeded extends Error {}\n\nfunction isNodeError(error: unknown): error is NodeJS.ErrnoException {\n\treturn error instanceof Error && \"code\" in error;\n}\n\nfunction entryOf(stats: Stats, hash?: string): Entry {\n\tconst kind = stats.isFile()\n\t\t? \"file\"\n\t\t: stats.isDirectory()\n\t\t\t? \"directory\"\n\t\t\t: stats.isSymbolicLink()\n\t\t\t\t? \"symlink\"\n\t\t\t\t: \"other\";\n\treturn {\n\t\tkind,\n\t\tdev: stats.dev,\n\t\tino: stats.ino,\n\t\tsize: kind === \"directory\" ? 0 : stats.size,\n\t\tmtimeMs: kind === \"directory\" ? 0 : stats.mtimeMs,\n\t\t...(hash === undefined ? {} : { hash }),\n\t};\n}\n\nfunction sameEntry(a: Entry, b: Entry): boolean {\n\treturn (\n\t\ta.kind === b.kind &&\n\t\ta.dev === b.dev &&\n\t\ta.ino === b.ino &&\n\t\ta.size === b.size &&\n\t\ta.mtimeMs === b.mtimeMs &&\n\t\ta.hash === b.hash\n\t);\n}\n\n/** Ancestors of `path`, nearest first, up to the root. */\nfunction ancestorsOf(path: string): string[] {\n\tconst result: string[] = [];\n\tfor (let current = dirname(path); ; current = dirname(current)) {\n\t\tresult.push(current);\n\t\tif (dirname(current) === current) return result;\n\t}\n}\n\n/** Whether `path` is `ancestor` or below it. */\nfunction isWithin(path: string, ancestor: string): boolean {\n\treturn path === ancestor || path.startsWith(ancestor.endsWith(sep) ? ancestor : ancestor + sep);\n}\n\nfunction excluded(target: ResolvedTarget, name: string): boolean {\n\treturn (target.hidden && name.startsWith(\".\")) || target.names.has(name);\n}\n\n/**\n * Watches by snapshots: native events only trigger a debounced rescan, and changes are the difference between\n * snapshots plus the event paths. A replaced file, a renamed or recreated ancestor, or a directory created with its\n * contents therefore never depends on which events an operating system sends. New directories get their watchers\n * before they are scanned again, so nothing written into them before the watcher existed is missed.\n */\nexport class NodeFileWatcher implements FileWatcher {\n\treadonly #targets: readonly ResolvedTarget[];\n\treadonly #onChange: (change: WatchChange) => void;\n\treadonly #pollIntervalMs: number;\n\treadonly #maxDirectories: number;\n\treadonly #watchers = new Map<string, FSWatcher>();\n\treadonly #events = new Set<string>();\n\t#mode: \"native\" | \"polling\";\n\t#snapshot: Snapshot = new Map();\n\t#timer: ReturnType<typeof setTimeout> | undefined;\n\t#settleTimer: ReturnType<typeof setTimeout> | undefined;\n\t#running: Promise<void> | undefined;\n\t#dirty = false;\n\t#closed = false;\n\n\tprivate constructor(\n\t\ttargets: readonly ResolvedTarget[],\n\t\tonChange: (change: WatchChange) => void,\n\t\tmode: \"native\" | \"polling\",\n\t\toptions: NodeWatchOptions,\n\t) {\n\t\tthis.#targets = targets;\n\t\tthis.#onChange = onChange;\n\t\tthis.#mode = mode;\n\t\tthis.#pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_MS;\n\t\tthis.#maxDirectories = options.maxDirectories ?? DEFAULT_MAX_DIRECTORIES;\n\t}\n\n\t/** Establish coverage: watchers first, then the snapshot later changes are compared with. */\n\tstatic async open(\n\t\ttargets: readonly WatchTarget[],\n\t\tresolvePath: (path: string) => string,\n\t\tonChange: (change: WatchChange) => void,\n\t\toptions: NodeWatchOptions,\n\t): Promise<NodeFileWatcher> {\n\t\tconst resolved = targets.map((target) => ({\n\t\t\tpath: resolvePath(target.path),\n\t\t\trecursive: target.recursive === true,\n\t\t\thidden: target.exclude?.hidden === true,\n\t\t\tnames: new Set(target.exclude?.names ?? []),\n\t\t}));\n\t\t// Windows refuses to rename a directory while another directory below it is open, and a native watcher keeps\n\t\t// every watched directory open: watching would break renames of their parents, so Windows polls.\n\t\tconst mode =\n\t\t\toptions.mode ??\n\t\t\t(process.platform === \"win32\" || (await anyUnreliable(resolved.map((target) => target.path)))\n\t\t\t\t? \"polling\"\n\t\t\t\t: \"native\");\n\t\tconst watcher = new NodeFileWatcher(resolved, onChange, mode, options);\n\t\ttry {\n\t\t\tawait watcher.#sync(false);\n\t\t} catch (error) {\n\t\t\twatcher.#stop();\n\t\t\tif (error instanceof BudgetExceeded) throw new FileError(\"invalid\", error.message);\n\t\t\tthrow error;\n\t\t}\n\t\twatcher.#schedulePoll();\n\t\treturn watcher;\n\t}\n\n\tget mode(): \"native\" | \"polling\" {\n\t\treturn this.#mode;\n\t}\n\n\tasync close(_context: Context): Promise<void> {\n\t\tif (this.#closed) return;\n\t\tthis.#stop();\n\t\tawait this.#running;\n\t}\n\n\t#stop(): void {\n\t\tthis.#closed = true;\n\t\tclearTimeout(this.#timer);\n\t\tthis.#timer = undefined;\n\t\tclearTimeout(this.#settleTimer);\n\t\tthis.#settleTimer = undefined;\n\t\tfor (const watcher of this.#watchers.values()) watcher.close();\n\t\tthis.#watchers.clear();\n\t}\n\n\t#deliver(change: WatchChange): void {\n\t\tif (this.#closed) return;\n\t\ttry {\n\t\t\tthis.#onChange(change);\n\t\t} catch {\n\t\t\t// A throwing callback must not stop watching.\n\t\t}\n\t}\n\n\t#schedulePoll(): void {\n\t\tif (this.#closed || this.#mode !== \"polling\") return;\n\t\tthis.#timer = setTimeout(() => {\n\t\t\tthis.#timer = undefined;\n\t\t\tvoid this.#flush().finally(() => this.#schedulePoll());\n\t\t}, this.#pollIntervalMs);\n\t}\n\n\t#scheduleFlush(): void {\n\t\tif (this.#closed || this.#timer !== undefined) return;\n\t\tthis.#timer = setTimeout(() => {\n\t\t\tthis.#timer = undefined;\n\t\t\tvoid this.#flush();\n\t\t}, DEBOUNCE_MS);\n\t}\n\n\t#scheduleSettle(): void {\n\t\tif (this.#closed) return;\n\t\tclearTimeout(this.#settleTimer);\n\t\tthis.#settleTimer = setTimeout(() => {\n\t\t\tthis.#settleTimer = undefined;\n\t\t\tvoid this.#flush();\n\t\t}, FSEVENTS_SETTLE_MS);\n\t}\n\n\t#flush(): Promise<void> {\n\t\tif (this.#running !== undefined) {\n\t\t\tthis.#dirty = true;\n\t\t\treturn this.#running;\n\t\t}\n\t\tthis.#running = (async () => {\n\t\t\ttry {\n\t\t\t\tdo {\n\t\t\t\t\tthis.#dirty = false;\n\t\t\t\t\tconst events = [...this.#events];\n\t\t\t\t\tthis.#events.clear();\n\t\t\t\t\tconst changed = await this.#sync(true);\n\t\t\t\t\tfor (const path of events) changed.add(path);\n\t\t\t\t\tif (changed.size > 0) this.#deliver({ paths: [...changed].sort() });\n\t\t\t\t} while (this.#dirty && !this.#closed);\n\t\t\t} catch (error) {\n\t\t\t\tconst fileError =\n\t\t\t\t\terror instanceof FileError\n\t\t\t\t\t\t? error\n\t\t\t\t\t\t: new FileError(\"invalid\", error instanceof Error ? error.message : String(error));\n\t\t\t\tthis.#deliver({ error: fileError });\n\t\t\t\tthis.#stop();\n\t\t\t} finally {\n\t\t\t\tthis.#running = undefined;\n\t\t\t}\n\t\t})();\n\t\treturn this.#running;\n\t}\n\n\t/** Rescan, report differences, and install watchers for new directories, rescanning until none are new. */\n\tasync #sync(report: boolean): Promise<Set<string>> {\n\t\tconst changed = new Set<string>();\n\t\tfor (let round = 0; round < 10 && !this.#closed; round++) {\n\t\t\tconst next = await this.#scan();\n\t\t\tif (report) for (const path of this.#diff(this.#snapshot, next)) changed.add(path);\n\t\t\tthis.#snapshot = next;\n\t\t\tif (this.#mode === \"polling\" || !this.#reconcileWatchers(next)) break;\n\t\t\tif (process.platform === \"darwin\") this.#scheduleSettle();\n\t\t\t// Something written into a new directory before its watcher existed shows up in the next round.\n\t\t\treport = true;\n\t\t}\n\t\treturn changed;\n\t}\n\n\t#diff(previous: Snapshot, next: Snapshot): string[] {\n\t\tconst changed: string[] = [];\n\t\tfor (const [path, entry] of next) {\n\t\t\tconst before = previous.get(path);\n\t\t\tif (before === undefined || !sameEntry(before, entry)) changed.push(this.#reported(path));\n\t\t}\n\t\tfor (const path of previous.keys()) if (!next.has(path)) changed.push(this.#reported(path));\n\t\treturn changed;\n\t}\n\n\t/** An ancestor that changed identity moved every target below it; report those targets. */\n\t#reported(path: string): string {\n\t\tif (this.#targets.some((target) => isWithin(path, target.path))) return path;\n\t\treturn this.#targets.find((target) => isWithin(target.path, path))?.path ?? path;\n\t}\n\n\tasync #scan(): Promise<Snapshot> {\n\t\tconst snapshot: Snapshot = new Map();\n\t\tlet directories = 0;\n\t\tconst countDirectory = (): void => {\n\t\t\tif (++directories > this.#maxDirectories) {\n\t\t\t\tthrow new BudgetExceeded(`Watched paths exceed ${this.#maxDirectories} directories`);\n\t\t\t}\n\t\t};\n\t\tconst record = async (path: string, stats: Stats): Promise<void> => {\n\t\t\tlet hash: string | undefined;\n\t\t\tif (\n\t\t\t\tthis.#mode === \"polling\" &&\n\t\t\t\tstats.isFile() &&\n\t\t\t\tstats.size <= HASH_MAX_BYTES &&\n\t\t\t\tDate.now() - stats.mtimeMs < HASH_RECENT_MS\n\t\t\t) {\n\t\t\t\thash = await readFile(path).then(\n\t\t\t\t\t(content) => createHash(\"sha256\").update(content).digest(\"hex\"),\n\t\t\t\t\t() => undefined,\n\t\t\t\t);\n\t\t\t}\n\t\t\tsnapshot.set(path, entryOf(stats, hash));\n\t\t};\n\t\tconst scanDirectory = async (target: ResolvedTarget, directory: string): Promise<void> => {\n\t\t\tlet names: string[];\n\t\t\ttry {\n\t\t\t\tnames = await readdir(directory);\n\t\t\t} catch (error) {\n\t\t\t\tif (isNodeError(error) && [\"ENOENT\", \"EACCES\", \"EPERM\", \"ENOTDIR\"].includes(error.code ?? \"\")) return;\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t\tfor (const name of names) {\n\t\t\t\tif (excluded(target, name)) continue;\n\t\t\t\tconst path = `${directory.endsWith(sep) ? directory : directory + sep}${name}`;\n\t\t\t\tif (snapshot.has(path)) continue;\n\t\t\t\tconst stats = await lstat(path).catch(() => undefined);\n\t\t\t\tif (stats === undefined) continue;\n\t\t\t\tawait record(path, stats);\n\t\t\t\tif (target.recursive && stats.isDirectory()) {\n\t\t\t\t\tcountDirectory();\n\t\t\t\t\tawait scanDirectory(target, path);\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t\tfor (const target of this.#targets) {\n\t\t\tfor (const ancestor of ancestorsOf(target.path)) {\n\t\t\t\tif (snapshot.has(ancestor)) continue;\n\t\t\t\tconst stats = await lstat(ancestor).catch(() => undefined);\n\t\t\t\t// Identity only: an ancestor's own timestamps change with every unrelated sibling.\n\t\t\t\tif (stats !== undefined) snapshot.set(ancestor, { ...entryOf(stats), size: 0, mtimeMs: 0 });\n\t\t\t}\n\t\t\t// The target itself may be a symbolic link to what is watched; follow it.\n\t\t\tconst stats = await stat(target.path).catch(() => undefined);\n\t\t\tif (stats === undefined) continue;\n\t\t\tawait record(target.path, stats);\n\t\t\tif (stats.isDirectory()) {\n\t\t\t\tcountDirectory();\n\t\t\t\tawait scanDirectory(target, target.path);\n\t\t\t}\n\t\t}\n\t\treturn snapshot;\n\t}\n\n\t/**\n\t * Watch every existing ancestor of each target, each target directory, and on Linux each directory below a recursive\n\t * target (elsewhere one recursive watcher per target). Returns whether a watcher was added.\n\t */\n\t#reconcileWatchers(snapshot: Snapshot): boolean {\n\t\tconst wanted = new Map<string, boolean>();\n\t\tconst perDirectory = process.platform === \"linux\" || process.platform === \"android\";\n\t\tfor (const target of this.#targets) {\n\t\t\tfor (const ancestor of ancestorsOf(target.path)) {\n\t\t\t\tif (snapshot.get(ancestor)?.kind === \"directory\" && !wanted.has(ancestor)) wanted.set(ancestor, false);\n\t\t\t}\n\t\t\tif (snapshot.get(target.path)?.kind !== \"directory\") continue;\n\t\t\tconst recursiveRoot = target.recursive && !perDirectory;\n\t\t\twanted.set(target.path, recursiveRoot);\n\t\t\tif (target.recursive && perDirectory) {\n\t\t\t\tfor (const [path, entry] of snapshot) {\n\t\t\t\t\tif (entry.kind === \"directory\" && path !== target.path && isWithin(path, target.path)) {\n\t\t\t\t\t\twanted.set(path, false);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tfor (const [path, watcher] of this.#watchers) {\n\t\t\tif (!wanted.has(path)) {\n\t\t\t\twatcher.close();\n\t\t\t\tthis.#watchers.delete(path);\n\t\t\t}\n\t\t}\n\t\tlet added = false;\n\t\tfor (const [path, recursive] of wanted) {\n\t\t\tif (this.#watchers.has(path)) continue;\n\t\t\ttry {\n\t\t\t\tconst watcher = fsWatch(path, { recursive, persistent: false }, (_event, filename) =>\n\t\t\t\t\tthis.#onEvent(path, filename === null ? undefined : String(filename)),\n\t\t\t\t);\n\t\t\t\t// A watched directory that disappears or fails: rescan, which also replaces the watcher.\n\t\t\t\twatcher.on(\"error\", () => {\n\t\t\t\t\twatcher.close();\n\t\t\t\t\tif (this.#watchers.get(path) === watcher) this.#watchers.delete(path);\n\t\t\t\t\tthis.#scheduleFlush();\n\t\t\t\t});\n\t\t\t\tthis.#watchers.set(path, watcher);\n\t\t\t\tadded = true;\n\t\t\t} catch (error) {\n\t\t\t\t// Out of watches or unsupported: compare snapshots from now on, and say coverage was uncertain.\n\t\t\t\tif (isNodeError(error) && error.code !== \"ENOENT\" && error.code !== \"EACCES\" && error.code !== \"EPERM\") {\n\t\t\t\t\tthis.#switchToPolling();\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn added;\n\t}\n\n\t#switchToPolling(): void {\n\t\tif (this.#mode === \"polling\") return;\n\t\tthis.#mode = \"polling\";\n\t\tfor (const watcher of this.#watchers.values()) watcher.close();\n\t\tthis.#watchers.clear();\n\t\tthis.#deliver({ overflow: true });\n\t\tclearTimeout(this.#timer);\n\t\tthis.#timer = undefined;\n\t\tthis.#schedulePoll();\n\t}\n\n\t#onEvent(directory: string, filename: string | undefined): void {\n\t\tif (this.#closed) return;\n\t\tconst path =\n\t\t\tfilename === undefined ? directory : `${directory.endsWith(sep) ? directory : directory + sep}${filename}`;\n\t\t// Events about unrelated siblings of an ancestor, or about excluded entries, are ignored.\n\t\tconst relevant = this.#inScope(path);\n\t\tif (relevant) this.#events.add(this.#reported(path));\n\t\tif (relevant || filename === undefined) this.#scheduleFlush();\n\t}\n\n\t#inScope(path: string): boolean {\n\t\tfor (const target of this.#targets) {\n\t\t\tif (isWithin(target.path, path)) return true;\n\t\t\tif (!isWithin(path, target.path) || path === target.path) continue;\n\t\t\tconst components = relative(target.path, path).split(sep);\n\t\t\tif (!target.recursive && components.length > 1) continue;\n\t\t\tif (components.some((name) => excluded(target, name))) continue;\n\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t}\n}\n\n/** Whether any path, or its nearest existing ancestor, is on a file system that does not report remote changes. */\nasync function anyUnreliable(paths: readonly string[]): Promise<boolean> {\n\tif (process.platform !== \"linux\" && process.platform !== \"android\") return false;\n\tfor (const path of paths) {\n\t\tfor (const candidate of [path, ...ancestorsOf(path)]) {\n\t\t\tconst info = await statfs(candidate).catch(() => undefined);\n\t\t\tif (info === undefined) continue;\n\t\t\tif (UNRELIABLE_FILE_SYSTEMS.has(info.type)) return true;\n\t\t\tbreak;\n\t\t}\n\t}\n\treturn false;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"node-watch.js","sourceRoot":"","sources":["../../src/env/node-watch.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash } from \"node:crypto\";\nimport { type FSWatcher, watch as fsWatch, type Stats } from \"node:fs\";\nimport { lstat, readdir, readFile, stat, statfs } from \"node:fs/promises\";\nimport { dirname, relative, sep } from \"node:path\";\nimport type { Context } from \"@earendil-works/chord\";\nimport { FileError, type FileWatcher, type WatchChange, type WatchTarget } from \"./index.ts\";\n\n/** How `NodeExecutionEnv` watches. */\nexport interface NodeWatchOptions {\n\t/**\n\t * Force a mode. By default `polling` is chosen on Windows, where native watchers keep directories open and so block\n\t * renaming their parents, and for file systems that do not report remote changes.\n\t */\n\tmode?: \"native\" | \"polling\";\n\t/** Interval between snapshots in `polling` mode; default 2000 ms. */\n\tpollIntervalMs?: number;\n\t/** Most directories one watcher covers; default 10,000. */\n\tmaxDirectories?: number;\n}\n\ntype ResolvedTarget = {\n\treadonly path: string;\n\treadonly recursive: boolean;\n\treadonly hidden: boolean;\n\treadonly names: ReadonlySet<string>;\n};\n\n/** What a snapshot remembers of one path. Directories and ancestors are compared by identity only. */\ntype Entry = {\n\treadonly kind: \"file\" | \"directory\" | \"symlink\" | \"other\";\n\treadonly dev: number;\n\treadonly ino: number;\n\treadonly size: number;\n\treadonly mtimeMs: number;\n\treadonly hash?: string;\n};\n\ntype Snapshot = Map<string, Entry>;\n\n/** A native watcher and the identity of what it was installed on: a path replaced by another file needs a new one. */\ntype Installed = { readonly watcher: FSWatcher; readonly dev: number; readonly ino: number };\n\n/** A scan: the snapshot, and targets that are symbolic links to files, whose files need their own watchers. */\ntype Scan = { readonly snapshot: Snapshot; readonly linkedFiles: ReadonlySet<string> };\n\nconst DEBOUNCE_MS = 50;\n/**\n * On macOS, `fs.watch` returns before libuv's FSEvents stream is live, and changes in between are never reported\n * (about 2% of changes made right after the call, none after 100 ms). A rescan this long after installing watchers\n * catches them.\n */\nconst FSEVENTS_SETTLE_MS = 500;\nconst DEFAULT_POLL_MS = 2000;\nconst DEFAULT_MAX_DIRECTORIES = 10_000;\n/** In polling mode, recently modified small files are also compared by content: a second write within the file system's\n * timestamp granularity can keep size and modification time. */\nconst HASH_MAX_BYTES = 256 * 1024;\nconst HASH_RECENT_MS = 5000;\n\n/** Linux `statfs` magic numbers of file systems that accept watches but do not report changes made elsewhere. */\nconst UNRELIABLE_FILE_SYSTEMS = new Set([\n\t0x6969, // NFS\n\t0x517b, // SMB\n\t0xff534d42, // CIFS\n\t0xfe534d42, // SMB2\n\t0x65735546, // FUSE (sshfs, Android shared storage)\n\t0x01021997, // 9P (WSL2 Windows drives)\n\t0x0bd00bd0, // Lustre\n\t0x47504653, // GPFS\n\t0x00c36400, // Ceph\n\t0x5346414f, // OpenAFS\n\t0x6b414653, // kAFS\n\t0x5dca2df5, // sdcardfs\n]);\n\nclass BudgetExceeded extends Error {}\n\nfunction isNodeError(error: unknown): error is NodeJS.ErrnoException {\n\treturn error instanceof Error && \"code\" in error;\n}\n\nfunction isDenied(error: unknown): boolean {\n\treturn isNodeError(error) && (error.code === \"EACCES\" || error.code === \"EPERM\");\n}\n\nfunction entryOf(stats: Stats, hash?: string): Entry {\n\tconst kind = stats.isFile()\n\t\t? \"file\"\n\t\t: stats.isDirectory()\n\t\t\t? \"directory\"\n\t\t\t: stats.isSymbolicLink()\n\t\t\t\t? \"symlink\"\n\t\t\t\t: \"other\";\n\treturn {\n\t\tkind,\n\t\tdev: stats.dev,\n\t\tino: stats.ino,\n\t\tsize: kind === \"directory\" ? 0 : stats.size,\n\t\tmtimeMs: kind === \"directory\" ? 0 : stats.mtimeMs,\n\t\t...(hash === undefined ? {} : { hash }),\n\t};\n}\n\nfunction sameEntry(a: Entry, b: Entry): boolean {\n\treturn (\n\t\ta.kind === b.kind &&\n\t\ta.dev === b.dev &&\n\t\ta.ino === b.ino &&\n\t\ta.size === b.size &&\n\t\ta.mtimeMs === b.mtimeMs &&\n\t\ta.hash === b.hash\n\t);\n}\n\n/** Ancestors of `path`, nearest first, up to the root. */\nfunction ancestorsOf(path: string): string[] {\n\tconst result: string[] = [];\n\tfor (let current = dirname(path); ; current = dirname(current)) {\n\t\tresult.push(current);\n\t\tif (dirname(current) === current) return result;\n\t}\n}\n\n/** Whether `path` is `ancestor` or below it. */\nfunction isWithin(path: string, ancestor: string): boolean {\n\treturn path === ancestor || path.startsWith(ancestor.endsWith(sep) ? ancestor : ancestor + sep);\n}\n\nfunction excluded(target: ResolvedTarget, name: string): boolean {\n\treturn (target.hidden && name.startsWith(\".\")) || target.names.has(name);\n}\n\n/**\n * Watches by snapshots: native events only trigger a debounced rescan, and changes are the difference between\n * snapshots plus the event paths. A replaced file, a renamed or recreated ancestor, or a directory created with its\n * contents therefore never depends on which events an operating system sends. New directories get their watchers\n * before they are scanned again, so nothing written into them before the watcher existed is missed.\n */\nexport class NodeFileWatcher implements FileWatcher {\n\treadonly #targets: readonly ResolvedTarget[];\n\treadonly #onChange: (change: WatchChange) => void;\n\treadonly #pollIntervalMs: number;\n\treadonly #maxDirectories: number;\n\treadonly #watchers = new Map<string, Installed>();\n\treadonly #events = new Set<string>();\n\t#mode: \"native\" | \"polling\";\n\t#snapshot: Snapshot = new Map();\n\t#timer: ReturnType<typeof setTimeout> | undefined;\n\t#settleTimer: ReturnType<typeof setTimeout> | undefined;\n\t#running: Promise<void> | undefined;\n\t#dirty = false;\n\t#closed = false;\n\n\tprivate constructor(\n\t\ttargets: readonly ResolvedTarget[],\n\t\tonChange: (change: WatchChange) => void,\n\t\tmode: \"native\" | \"polling\",\n\t\toptions: NodeWatchOptions,\n\t) {\n\t\tthis.#targets = targets;\n\t\tthis.#onChange = onChange;\n\t\tthis.#mode = mode;\n\t\tthis.#pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_MS;\n\t\tthis.#maxDirectories = options.maxDirectories ?? DEFAULT_MAX_DIRECTORIES;\n\t}\n\n\t/** Establish coverage: watchers first, then the snapshot later changes are compared with. */\n\tstatic async open(\n\t\ttargets: readonly WatchTarget[],\n\t\tresolvePath: (path: string) => string,\n\t\tonChange: (change: WatchChange) => void,\n\t\toptions: NodeWatchOptions,\n\t): Promise<NodeFileWatcher> {\n\t\tconst resolved = targets.map((target) => ({\n\t\t\tpath: resolvePath(target.path),\n\t\t\trecursive: target.recursive === true,\n\t\t\thidden: target.exclude?.hidden === true,\n\t\t\tnames: new Set(target.exclude?.names ?? []),\n\t\t}));\n\t\t// Windows refuses to rename a directory while another directory below it is open, and a native watcher keeps\n\t\t// every watched directory open: watching would break renames of their parents, so Windows polls.\n\t\tconst mode =\n\t\t\toptions.mode ??\n\t\t\t(process.platform === \"win32\" || (await anyUnreliable(resolved.map((target) => target.path)))\n\t\t\t\t? \"polling\"\n\t\t\t\t: \"native\");\n\t\tconst watcher = new NodeFileWatcher(resolved, onChange, mode, options);\n\t\ttry {\n\t\t\tawait watcher.#sync(false);\n\t\t} catch (error) {\n\t\t\twatcher.#stop();\n\t\t\tif (error instanceof BudgetExceeded) throw new FileError(\"invalid\", error.message);\n\t\t\tthrow error;\n\t\t}\n\t\twatcher.#schedulePoll();\n\t\treturn watcher;\n\t}\n\n\tget mode(): \"native\" | \"polling\" {\n\t\treturn this.#mode;\n\t}\n\n\tasync close(_context: Context): Promise<void> {\n\t\tif (this.#closed) return;\n\t\tthis.#stop();\n\t\tawait this.#running;\n\t}\n\n\t#stop(): void {\n\t\tthis.#closed = true;\n\t\tclearTimeout(this.#timer);\n\t\tthis.#timer = undefined;\n\t\tclearTimeout(this.#settleTimer);\n\t\tthis.#settleTimer = undefined;\n\t\tfor (const { watcher } of this.#watchers.values()) watcher.close();\n\t\tthis.#watchers.clear();\n\t}\n\n\t#deliver(change: WatchChange): void {\n\t\tif (this.#closed) return;\n\t\ttry {\n\t\t\tthis.#onChange(change);\n\t\t} catch {\n\t\t\t// A throwing callback must not stop watching.\n\t\t}\n\t}\n\n\t#schedulePoll(): void {\n\t\tif (this.#closed || this.#mode !== \"polling\") return;\n\t\tthis.#timer = setTimeout(() => {\n\t\t\tthis.#timer = undefined;\n\t\t\tvoid this.#flush().finally(() => this.#schedulePoll());\n\t\t}, this.#pollIntervalMs);\n\t}\n\n\t#scheduleFlush(): void {\n\t\tif (this.#closed || this.#timer !== undefined) return;\n\t\tthis.#timer = setTimeout(() => {\n\t\t\tthis.#timer = undefined;\n\t\t\tvoid this.#flush();\n\t\t}, DEBOUNCE_MS);\n\t}\n\n\t#scheduleSettle(): void {\n\t\tif (this.#closed) return;\n\t\tclearTimeout(this.#settleTimer);\n\t\tthis.#settleTimer = setTimeout(() => {\n\t\t\tthis.#settleTimer = undefined;\n\t\t\tvoid this.#flush();\n\t\t}, FSEVENTS_SETTLE_MS);\n\t}\n\n\t#flush(): Promise<void> {\n\t\tif (this.#running !== undefined) {\n\t\t\tthis.#dirty = true;\n\t\t\treturn this.#running;\n\t\t}\n\t\tthis.#running = (async () => {\n\t\t\ttry {\n\t\t\t\tdo {\n\t\t\t\t\tthis.#dirty = false;\n\t\t\t\t\tconst events = [...this.#events];\n\t\t\t\t\tthis.#events.clear();\n\t\t\t\t\tconst changed = await this.#sync(true);\n\t\t\t\t\tfor (const path of events) changed.add(path);\n\t\t\t\t\tif (changed.size > 0) this.#deliver({ paths: [...changed].sort() });\n\t\t\t\t} while (this.#dirty && !this.#closed);\n\t\t\t} catch (error) {\n\t\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\t\tconst fileError =\n\t\t\t\t\terror instanceof FileError\n\t\t\t\t\t\t? error\n\t\t\t\t\t\t: new FileError(isDenied(error) ? \"permission_denied\" : \"invalid\", message);\n\t\t\t\tthis.#deliver({ error: fileError });\n\t\t\t\tthis.#stop();\n\t\t\t} finally {\n\t\t\t\tthis.#running = undefined;\n\t\t\t}\n\t\t})();\n\t\treturn this.#running;\n\t}\n\n\t/** Rescan, report differences, and install watchers for new directories, rescanning until none are new. */\n\tasync #sync(report: boolean): Promise<Set<string>> {\n\t\tconst changed = new Set<string>();\n\t\tfor (let round = 0; round < 10 && !this.#closed; round++) {\n\t\t\tconst scan = await this.#scan();\n\t\t\t// Closed during the scan: installing watchers now would leak them.\n\t\t\tif (this.#closed) break;\n\t\t\tconst next = scan.snapshot;\n\t\t\tif (report) for (const path of this.#diff(this.#snapshot, next)) changed.add(path);\n\t\t\tthis.#snapshot = next;\n\t\t\tif (this.#mode === \"polling\" || !this.#reconcileWatchers(scan)) break;\n\t\t\tif (process.platform === \"darwin\") this.#scheduleSettle();\n\t\t\t// Something written into a new directory before its watcher existed shows up in the next round.\n\t\t\treport = true;\n\t\t}\n\t\treturn changed;\n\t}\n\n\t#diff(previous: Snapshot, next: Snapshot): string[] {\n\t\tconst changed: string[] = [];\n\t\tfor (const [path, entry] of next) {\n\t\t\tconst before = previous.get(path);\n\t\t\tif (before === undefined || !sameEntry(before, entry)) changed.push(this.#reported(path));\n\t\t}\n\t\tfor (const path of previous.keys()) if (!next.has(path)) changed.push(this.#reported(path));\n\t\treturn changed;\n\t}\n\n\t/** An ancestor that changed identity moved every target below it; report those targets. */\n\t#reported(path: string): string {\n\t\tif (this.#targets.some((target) => isWithin(path, target.path))) return path;\n\t\treturn this.#targets.find((target) => isWithin(target.path, path))?.path ?? path;\n\t}\n\n\tasync #scan(): Promise<Scan> {\n\t\tconst snapshot: Snapshot = new Map();\n\t\tconst linkedFiles = new Set<string>();\n\t\t// Directories are counted once, but traversed once per target: overlapping targets differ in recursion and\n\t\t// exclusions, and an entry recorded for one target must still be descended into for another.\n\t\tconst counted = new Set<string>();\n\t\tconst traversed = new Set<string>();\n\t\t/** Kinds of listed entries, not following links: a target that links to a directory is recorded by `stat`, but\n\t\t * symbolic links below a recursive target are not followed. */\n\t\tconst listed = new Map<string, Entry[\"kind\"]>();\n\t\tconst countDirectory = (path: string): void => {\n\t\t\tcounted.add(path);\n\t\t\tif (counted.size > this.#maxDirectories) {\n\t\t\t\tthrow new BudgetExceeded(`Watched paths exceed ${this.#maxDirectories} directories`);\n\t\t\t}\n\t\t};\n\t\tconst record = async (path: string, stats: Stats): Promise<void> => {\n\t\t\tlet hash: string | undefined;\n\t\t\tif (\n\t\t\t\tthis.#mode === \"polling\" &&\n\t\t\t\tstats.isFile() &&\n\t\t\t\tstats.size <= HASH_MAX_BYTES &&\n\t\t\t\tDate.now() - stats.mtimeMs < HASH_RECENT_MS\n\t\t\t) {\n\t\t\t\thash = await readFile(path).then(\n\t\t\t\t\t(content) => createHash(\"sha256\").update(content).digest(\"hex\"),\n\t\t\t\t\t() => undefined,\n\t\t\t\t);\n\t\t\t}\n\t\t\tsnapshot.set(path, entryOf(stats, hash));\n\t\t};\n\t\tconst scanDirectory = async (index: number, target: ResolvedTarget, directory: string): Promise<void> => {\n\t\t\tconst key = `${index}\\0${directory}`;\n\t\t\tif (traversed.has(key)) return;\n\t\t\ttraversed.add(key);\n\t\t\tlet names: string[];\n\t\t\ttry {\n\t\t\t\tnames = await readdir(directory);\n\t\t\t} catch (error) {\n\t\t\t\t// The watched directory itself must be readable; below it, unreadable directories are skipped.\n\t\t\t\tif (directory === target.path && isDenied(error)) throw error;\n\t\t\t\tif (isNodeError(error) && [\"ENOENT\", \"EACCES\", \"EPERM\", \"ENOTDIR\"].includes(error.code ?? \"\")) return;\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t\tfor (const name of names) {\n\t\t\t\tif (excluded(target, name)) continue;\n\t\t\t\tconst path = `${directory.endsWith(sep) ? directory : directory + sep}${name}`;\n\t\t\t\tlet kind = listed.get(path);\n\t\t\t\tif (kind === undefined) {\n\t\t\t\t\tconst stats = await lstat(path).catch(() => undefined);\n\t\t\t\t\tif (stats === undefined) continue;\n\t\t\t\t\tkind = entryOf(stats).kind;\n\t\t\t\t\tlisted.set(path, kind);\n\t\t\t\t\t// A target's own entry (following links) wins over its listing by another target.\n\t\t\t\t\tif (!snapshot.has(path)) await record(path, stats);\n\t\t\t\t}\n\t\t\t\tif (target.recursive && kind === \"directory\") {\n\t\t\t\t\tcountDirectory(path);\n\t\t\t\t\tawait scanDirectory(index, target, path);\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t\tfor (const [index, target] of this.#targets.entries()) {\n\t\t\tfor (const ancestor of ancestorsOf(target.path)) {\n\t\t\t\tif (snapshot.has(ancestor)) continue;\n\t\t\t\tconst stats = await lstat(ancestor).catch(() => undefined);\n\t\t\t\t// Identity only: an ancestor's own timestamps change with every unrelated sibling.\n\t\t\t\tif (stats !== undefined) snapshot.set(ancestor, { ...entryOf(stats), size: 0, mtimeMs: 0 });\n\t\t\t}\n\t\t\t// The target itself may be a symbolic link to what is watched; follow it. A missing target is watched for its\n\t\t\t// creation; one that cannot be reached for lack of permission fails.\n\t\t\tlet stats: Stats;\n\t\t\ttry {\n\t\t\t\tstats = await stat(target.path);\n\t\t\t} catch (error) {\n\t\t\t\tif (isDenied(error)) throw error;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tawait record(target.path, stats);\n\t\t\tif (stats.isFile() && (await lstat(target.path).catch(() => undefined))?.isSymbolicLink()) {\n\t\t\t\tlinkedFiles.add(target.path);\n\t\t\t}\n\t\t\tif (stats.isDirectory()) {\n\t\t\t\tcountDirectory(target.path);\n\t\t\t\tawait scanDirectory(index, target, target.path);\n\t\t\t}\n\t\t}\n\t\treturn { snapshot, linkedFiles };\n\t}\n\n\t/**\n\t * Watch every existing ancestor of each target, each target directory, each target that is a symbolic link to a file\n\t * (changes to that file are not events of the link's directory), and on Linux each directory below a recursive target\n\t * (elsewhere one recursive watcher per target). Returns whether a watcher was added.\n\t */\n\t#reconcileWatchers({ snapshot, linkedFiles }: Scan): boolean {\n\t\tconst wanted = new Map<string, boolean>();\n\t\tfor (const path of linkedFiles) wanted.set(path, false);\n\t\tconst perDirectory = process.platform === \"linux\" || process.platform === \"android\";\n\t\tfor (const target of this.#targets) {\n\t\t\tfor (const ancestor of ancestorsOf(target.path)) {\n\t\t\t\tif (snapshot.get(ancestor)?.kind === \"directory\" && !wanted.has(ancestor)) wanted.set(ancestor, false);\n\t\t\t}\n\t\t\tif (snapshot.get(target.path)?.kind !== \"directory\") continue;\n\t\t\tconst recursiveRoot = target.recursive && !perDirectory;\n\t\t\twanted.set(target.path, recursiveRoot);\n\t\t\tif (target.recursive && perDirectory) {\n\t\t\t\tfor (const [path, entry] of snapshot) {\n\t\t\t\t\tif (entry.kind === \"directory\" && path !== target.path && isWithin(path, target.path)) {\n\t\t\t\t\t\twanted.set(path, false);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tfor (const [path, installed] of this.#watchers) {\n\t\t\tconst entry = snapshot.get(path);\n\t\t\t// Gone, or replaced: a watcher follows the directory it was installed on, not the path.\n\t\t\tif (!wanted.has(path) || entry === undefined || entry.dev !== installed.dev || entry.ino !== installed.ino) {\n\t\t\t\tinstalled.watcher.close();\n\t\t\t\tthis.#watchers.delete(path);\n\t\t\t}\n\t\t}\n\t\tlet added = false;\n\t\tfor (const [path, recursive] of wanted) {\n\t\t\tconst entry = snapshot.get(path);\n\t\t\tif (this.#watchers.has(path) || entry === undefined) continue;\n\t\t\tconst linked = linkedFiles.has(path);\n\t\t\ttry {\n\t\t\t\tconst watcher = fsWatch(path, { recursive, persistent: false }, (_event, filename) =>\n\t\t\t\t\tlinked\n\t\t\t\t\t\t? this.#onLinkedFileEvent(path)\n\t\t\t\t\t\t: this.#onEvent(path, filename === null ? undefined : String(filename)),\n\t\t\t\t);\n\t\t\t\t// A watched directory that disappears or fails: rescan, which also replaces the watcher.\n\t\t\t\twatcher.on(\"error\", () => {\n\t\t\t\t\twatcher.close();\n\t\t\t\t\tif (this.#watchers.get(path)?.watcher === watcher) this.#watchers.delete(path);\n\t\t\t\t\tthis.#scheduleFlush();\n\t\t\t\t});\n\t\t\t\tthis.#watchers.set(path, { watcher, dev: entry.dev, ino: entry.ino });\n\t\t\t\tadded = true;\n\t\t\t} catch (error) {\n\t\t\t\t// Out of watches or unsupported: compare snapshots from now on, and say coverage was uncertain.\n\t\t\t\tif (isNodeError(error) && error.code !== \"ENOENT\" && error.code !== \"EACCES\" && error.code !== \"EPERM\") {\n\t\t\t\t\tthis.#switchToPolling();\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn added;\n\t}\n\n\t#switchToPolling(): void {\n\t\tif (this.#mode === \"polling\") return;\n\t\tthis.#mode = \"polling\";\n\t\tfor (const { watcher } of this.#watchers.values()) watcher.close();\n\t\tthis.#watchers.clear();\n\t\tthis.#deliver({ overflow: true });\n\t\tclearTimeout(this.#timer);\n\t\tthis.#timer = undefined;\n\t\tthis.#schedulePoll();\n\t}\n\n\t#onEvent(directory: string, filename: string | undefined): void {\n\t\tif (this.#closed) return;\n\t\tconst path =\n\t\t\tfilename === undefined ? directory : `${directory.endsWith(sep) ? directory : directory + sep}${filename}`;\n\t\t// Events about unrelated siblings of an ancestor, or about excluded entries, are ignored.\n\t\tconst relevant = this.#inScope(path);\n\t\tif (relevant) this.#events.add(this.#reported(path));\n\t\tif (relevant || filename === undefined) this.#scheduleFlush();\n\t}\n\n\t/** The file a target links to changed: report the target. */\n\t#onLinkedFileEvent(target: string): void {\n\t\tif (this.#closed) return;\n\t\tthis.#events.add(target);\n\t\tthis.#scheduleFlush();\n\t}\n\n\t#inScope(path: string): boolean {\n\t\tfor (const target of this.#targets) {\n\t\t\tif (isWithin(target.path, path)) return true;\n\t\t\tif (!isWithin(path, target.path) || path === target.path) continue;\n\t\t\tconst components = relative(target.path, path).split(sep);\n\t\t\tif (!target.recursive && components.length > 1) continue;\n\t\t\tif (components.some((name) => excluded(target, name))) continue;\n\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t}\n}\n\n/** Whether any path, or its nearest existing ancestor, is on a file system that does not report remote changes. */\nasync function anyUnreliable(paths: readonly string[]): Promise<boolean> {\n\tif (process.platform !== \"linux\" && process.platform !== \"android\") return false;\n\tfor (const path of paths) {\n\t\tfor (const candidate of [path, ...ancestorsOf(path)]) {\n\t\t\tconst info = await statfs(candidate).catch(() => undefined);\n\t\t\tif (info === undefined) continue;\n\t\t\tif (UNRELIABLE_FILE_SYSTEMS.has(info.type)) return true;\n\t\t\tbreak;\n\t\t}\n\t}\n\treturn false;\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../../src/harness/agent.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,kBAAkB,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AAC1E,OAAO,KAAK,EACX,KAAK,EACL,WAAW,EACX,UAAU,EACV,gBAAgB,EAChB,uBAAuB,EAEvB,eAAe,EACf,cAAc,EAEd,gBAAgB,EAChB,QAAQ,EACR,gBAAgB,EAChB,MAAM,YAAY,CAAC;AAEpB,eAAO,MAAM,oBAAoB,EAAE,uBAKlC,CAAC;AAEF,eAAO,MAAM,yBAAyB,EAAE,gBAKvC,CAAC;AAEF,eAAO,MAAM,uBAAuB,EAAE,cAGrC,CAAC;AAEF,8DAA8D;AAC9D,eAAO,MAAM,gBAAgB,iBAAiB,CAAC;AAE/C,6FAA6F;AAC7F,eAAO,MAAM,QAAQ,kEAQnB,CAAC;
|
|
1
|
+
{"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../../src/harness/agent.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,kBAAkB,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AAC1E,OAAO,KAAK,EACX,KAAK,EACL,WAAW,EACX,UAAU,EACV,gBAAgB,EAChB,uBAAuB,EAEvB,eAAe,EACf,cAAc,EAEd,gBAAgB,EAChB,QAAQ,EACR,gBAAgB,EAChB,MAAM,YAAY,CAAC;AAEpB,eAAO,MAAM,oBAAoB,EAAE,uBAKlC,CAAC;AAEF,eAAO,MAAM,yBAAyB,EAAE,gBAKvC,CAAC;AAEF,eAAO,MAAM,uBAAuB,EAAE,cAGrC,CAAC;AAEF,8DAA8D;AAC9D,eAAO,MAAM,gBAAgB,iBAAiB,CAAC;AAE/C,6FAA6F;AAC7F,eAAO,MAAM,QAAQ,kEAQnB,CAAC;AAKH,8FAA8F;AAC9F,wBAAgB,eAAe,CAAC,QAAQ,EAAE,eAAe,GAAG,SAAS,GAAG,QAAQ,CAiB/E;AAED,4HAA4H;AAC5H,wBAAsB,SAAS,CAAC,EAAE,EAAE,EAAE,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAG1G;AAED;;;GAGG;AACH,wBAAsB,QAAQ,CAAC,EAAE,EAAE,EAAE,EAAE,cAAc,EAAE,cAAc,EAAE,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAY9G;AAuCD;;;;GAIG;AACH,wBAAsB,WAAW,CAAC,EAAE,EAAE,EAAE,EAAE,YAAY,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAMzF;AAED,sFAAsF;AACtF,wBAAgB,UAAU,CAAC,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,CAMnE;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,IAAI,SAAS,gBAAgB,EACzD,KAAK,EAAE,QAAQ,CAAC,UAAU,CAAC,GAAG,SAAS,EACvC,QAAQ,EAAE,gBAAgB,CAAC,IAAI,CAAC,EAChC,QAAQ,EAAE,QAAQ,EAClB,MAAM,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,GAC9B,KAAK,CAAC,IAAI,CAAC,CA0Db"}
|
package/dist/harness/agent.js
CHANGED
|
@@ -28,6 +28,8 @@ export const AgentDoc = defineDoc({
|
|
|
28
28
|
initial: () => ({}),
|
|
29
29
|
checkpointWhen: () => true,
|
|
30
30
|
});
|
|
31
|
+
/** Default `settings.contextRetentionMs`: ten minutes. */
|
|
32
|
+
const DEFAULT_CONTEXT_RETENTION_MS = 600_000;
|
|
31
33
|
/** Resolve the host settings: every field over its built-in default, object fields merged. */
|
|
32
34
|
export function resolveSettings(settings) {
|
|
33
35
|
const extensions = settings?.extensions;
|
|
@@ -36,10 +38,15 @@ export function resolveSettings(settings) {
|
|
|
36
38
|
stream: { ...settings?.stream },
|
|
37
39
|
retry: { ...DEFAULT_RETRY_POLICY, ...settings?.retry },
|
|
38
40
|
compaction: { ...DEFAULT_COMPACTION_POLICY, ...settings?.compaction },
|
|
39
|
-
|
|
41
|
+
// Field by field, so an explicitly undefined interval keeps its default.
|
|
42
|
+
progress: {
|
|
43
|
+
partialIntervalMs: settings?.progress?.partialIntervalMs ?? DEFAULT_PROGRESS_POLICY.partialIntervalMs,
|
|
44
|
+
outputIntervalMs: settings?.progress?.outputIntervalMs ?? DEFAULT_PROGRESS_POLICY.outputIntervalMs,
|
|
45
|
+
},
|
|
40
46
|
toolExecution: settings?.toolExecution ?? "parallel",
|
|
41
47
|
steeringMode: settings?.steeringMode ?? "one-at-a-time",
|
|
42
48
|
followUpMode: settings?.followUpMode ?? "one-at-a-time",
|
|
49
|
+
contextRetentionMs: settings?.contextRetentionMs ?? DEFAULT_CONTEXT_RETENTION_MS,
|
|
43
50
|
};
|
|
44
51
|
}
|
|
45
52
|
/** Apply one change to `pi.agent`: a given field replaces the stored one, `null` clears it, `undefined` changes nothing. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent.js","sourceRoot":"","sources":["../../src/harness/agent.ts"],"names":[],"mappings":"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","sourcesContent":["import { copyJson, type Draft } from \"@earendil-works/chord\";\nimport { defineDoc } from \"../documents.ts\";\nimport type { ConversationId, ConversationRecord, Tx } from \"../types.ts\";\nimport type {\n\tAgent,\n\tAgentChange,\n\tAgentState,\n\tCompactionPolicy,\n\tConversationRetryPolicy,\n\tExtension,\n\tHarnessSettings,\n\tProgressPolicy,\n\tPromptSection,\n\tRegistrySnapshot,\n\tSettings,\n\tToolRegistration,\n} from \"./types.ts\";\n\nexport const DEFAULT_RETRY_POLICY: ConversationRetryPolicy = {\n\tenabled: true,\n\tmaxRetries: 3,\n\tbaseDelayMs: 2000,\n\tmaxAgentDelayMs: 60000,\n};\n\nexport const DEFAULT_COMPACTION_POLICY: CompactionPolicy = {\n\tenabled: true,\n\treserveTokens: 16384,\n\tkeepRecentTokens: 20000,\n\tbackgroundTokens: 32768,\n};\n\nexport const DEFAULT_PROGRESS_POLICY: ProgressPolicy = {\n\tpartialIntervalMs: 100,\n\toutputIntervalMs: 100,\n};\n\n/** The reserved section key of the agent's `instructions`. */\nexport const INSTRUCTIONS_KEY = \"instructions\";\n\n/** Built-in agent document; rewindable so forks start from the agent at their fork entry. */\nexport const AgentDoc = defineDoc<AgentState>({\n\tkind: \"pi.agent\",\n\tversion: 1,\n\tscope: \"conversation\",\n\thistory: \"rewindable\",\n\tfork: \"asOf\",\n\tinitial: () => ({}),\n\tcheckpointWhen: () => true,\n});\n\n/** Resolve the host settings: every field over its built-in default, object fields merged. */\nexport function resolveSettings(settings: HarnessSettings | undefined): Settings {\n\tconst extensions = settings?.extensions;\n\treturn {\n\t\t...(extensions === undefined ? {} : { extensions }),\n\t\tstream: { ...settings?.stream },\n\t\tretry: { ...DEFAULT_RETRY_POLICY, ...settings?.retry },\n\t\tcompaction: { ...DEFAULT_COMPACTION_POLICY, ...settings?.compaction },\n\t\tprogress: { ...DEFAULT_PROGRESS_POLICY, ...settings?.progress },\n\t\ttoolExecution: settings?.toolExecution ?? \"parallel\",\n\t\tsteeringMode: settings?.steeringMode ?? \"one-at-a-time\",\n\t\tfollowUpMode: settings?.followUpMode ?? \"one-at-a-time\",\n\t};\n}\n\n/** Apply one change to `pi.agent`: a given field replaces the stored one, `null` clears it, `undefined` changes nothing. */\nexport async function configure(tx: Tx, conversationId: ConversationId, change: AgentChange): Promise<void> {\n\tconst state = await tx.doc(AgentDoc, conversationId);\n\tapplyChange(state, change);\n}\n\n/**\n * `addTools` of a tool round: an array gets each name it lacks appended, `{ remove }` loses the names, and unset tools\n * already offer every tool, so nothing is written.\n */\nexport async function addTools(tx: Tx, conversationId: ConversationId, added: readonly string[]): Promise<void> {\n\tconst state = await tx.doc(AgentDoc, conversationId);\n\tconst tools = state.tools;\n\tif (tools === undefined) return;\n\tif (Array.isArray(tools)) {\n\t\tfor (const name of added) if (!tools.includes(name)) tools.push(name);\n\t} else {\n\t\tconst remove = (tools as { remove: string[] }).remove;\n\t\tif (remove.some((name) => added.includes(name))) {\n\t\t\tstate.tools = { remove: remove.filter((name) => !added.includes(name)) };\n\t\t}\n\t}\n}\n\nfunction applyChange(state: Draft<AgentState>, change: AgentChange): void {\n\tconst set = <K extends keyof AgentState>(key: K, value: AgentState[K] | null | undefined) => {\n\t\tif (value === undefined) return;\n\t\tif (value === null) delete state[key];\n\t\telse state[key] = value as Draft<AgentState>[K];\n\t};\n\tset(\"model\", change.model === undefined || change.model === null ? change.model : { ...change.model });\n\tset(\"thinkingLevel\", change.thinkingLevel);\n\tconst extensions = change.extensions;\n\tset(\n\t\t\"extensions\",\n\t\textensions === undefined || extensions === null\n\t\t\t? extensions\n\t\t\t: isList(extensions)\n\t\t\t\t? names(extensions)\n\t\t\t\t: {\n\t\t\t\t\t\t...(extensions.add === undefined ? {} : { add: names(extensions.add) }),\n\t\t\t\t\t\t...(extensions.remove === undefined ? {} : { remove: names(extensions.remove) }),\n\t\t\t\t\t},\n\t);\n\tconst tools = change.tools;\n\tset(\n\t\t\"tools\",\n\t\ttools === undefined || tools === null ? tools : isList(tools) ? names(tools) : { remove: names(tools.remove) },\n\t);\n\tset(\"instructions\", change.instructions);\n\tset(\"cwd\", change.cwd);\n}\n\nfunction isList<T>(value: readonly T[] | object): value is readonly T[] {\n\treturn Array.isArray(value);\n}\n\nfunction names(items: readonly { readonly name: string }[]): string[] {\n\treturn items.map((item) => item.name);\n}\n\n/**\n * Built-in part of every Harness commit that creates or forks a conversation, for `pi.agent`: a fork keeps its `asOf`\n * copy; a new task-owned conversation copies the stored agent of its owner task's conversation; a new ownerless one\n * starts empty.\n */\nexport async function createAgent(tx: Tx, conversation: ConversationRecord): Promise<void> {\n\tif (conversation.parent !== undefined) return;\n\tconst agent = await tx.doc(AgentDoc, conversation.id);\n\tif (conversation.owner === undefined) return;\n\tconst owner = await tx.doc(AgentDoc, conversation.owner.conversationId);\n\tObject.assign(agent, copyJson(owner) as AgentState);\n}\n\n/** Handlers of the selected extensions' hooks for a task name, in extension order. */\nexport function agentHooks(agent: Agent, taskName: string): object[] {\n\tconst handlers: object[] = [];\n\tfor (const extension of agent.extensions) {\n\t\tfor (const hook of extension.hooks ?? []) if (hook.task === taskName) handlers.push(hook.handlers);\n\t}\n\treturn handlers;\n}\n\n/**\n * Resolve an agent from its stored state (absent: every field unset), a registry snapshot, and resolved settings. A\n * wrapper that throws or renames drops its target and is reported; a wrapper without a target does nothing.\n */\nexport function resolveAgent<Tool extends ToolRegistration>(\n\tstate: Readonly<AgentState> | undefined,\n\tsnapshot: RegistrySnapshot<Tool>,\n\tsettings: Settings,\n\treport: (error: unknown) => void,\n): Agent<Tool> {\n\tconst extensions = selectExtensions(state?.extensions, snapshot, settings);\n\n\tconst composed = new Map<string, Tool>();\n\tfor (const extension of extensions) for (const tool of extension.tools ?? []) composed.set(tool.name, tool);\n\tconst sections = new Map<string, PromptSection<Tool>>();\n\tfor (const extension of extensions) {\n\t\tfor (const section of extension.sections ?? []) sections.set(section.key, section);\n\t}\n\tfor (const extension of extensions) {\n\t\tfor (const wrap of extension.wraps ?? []) {\n\t\t\tif (\"tool\" in wrap)\n\t\t\t\tapplyWrap(\n\t\t\t\t\tcomposed,\n\t\t\t\t\twrap.tool,\n\t\t\t\t\t(tool) => wrap.wrap(tool),\n\t\t\t\t\t(tool) => tool.name,\n\t\t\t\t\treport,\n\t\t\t\t);\n\t\t\telse\n\t\t\t\tapplyWrap(\n\t\t\t\t\tsections,\n\t\t\t\t\twrap.section,\n\t\t\t\t\t(section) => wrap.wrap(section),\n\t\t\t\t\t(section) => section.key,\n\t\t\t\t\treport,\n\t\t\t\t);\n\t\t}\n\t}\n\n\tconst filter = state?.tools;\n\tlet tools: Tool[];\n\tif (filter === undefined) tools = [...composed.values()];\n\telse if (Array.isArray(filter)) {\n\t\ttools = [];\n\t\tfor (const name of new Set(filter)) {\n\t\t\tconst tool = composed.get(name);\n\t\t\tif (tool !== undefined) tools.push(tool);\n\t\t}\n\t} else {\n\t\tconst removed = new Set((filter as { remove: string[] }).remove);\n\t\ttools = [...composed.values()].filter((tool) => !removed.has(tool.name));\n\t}\n\n\tconst instructions = state?.instructions;\n\tconst agentSections = [...sections.values()];\n\tif (instructions !== undefined) agentSections.push({ key: INSTRUCTIONS_KEY, render: () => instructions });\n\n\tconst agent: Agent<Tool> = {\n\t\t...(state?.model === undefined ? {} : { model: state.model }),\n\t\tthinkingLevel: state?.thinkingLevel ?? \"off\",\n\t\textensions,\n\t\ttools,\n\t\tsections: agentSections,\n\t\t...(instructions === undefined ? {} : { instructions }),\n\t\t...(state?.cwd === undefined ? {} : { cwd: state.cwd }),\n\t};\n\treturn agent;\n}\n\n/** Selected installed extensions: the stored array, or the default selection edited by `{ add, remove }`. */\nfunction selectExtensions<Tool extends ToolRegistration>(\n\tstored: AgentState[\"extensions\"],\n\tsnapshot: RegistrySnapshot<Tool>,\n\tsettings: Settings,\n): Extension<Tool>[] {\n\tlet selected: string[];\n\tif (Array.isArray(stored)) selected = stored;\n\telse {\n\t\tconst base = settings.extensions?.map((extension) => extension.name) ?? snapshot.installed().map((e) => e.name);\n\t\tconst edit = stored as { add?: string[]; remove?: string[] } | undefined;\n\t\tconst removed = new Set(edit?.remove ?? []);\n\t\tselected = [...base, ...(edit?.add ?? [])].filter((name) => !removed.has(name));\n\t}\n\tconst extensions: Extension<Tool>[] = [];\n\tfor (const name of new Set(selected)) {\n\t\tconst extension = snapshot.extension(name);\n\t\tif (extension !== undefined) extensions.push(extension);\n\t}\n\treturn extensions;\n}\n\nfunction applyWrap<T>(\n\titems: Map<string, T>,\n\ttarget: string,\n\twrap: (item: T) => T,\n\tnameOf: (item: T) => string,\n\treport: (error: unknown) => void,\n): void {\n\tconst item = items.get(target);\n\tif (item === undefined) return;\n\ttry {\n\t\tconst wrapped = wrap(item);\n\t\tif (nameOf(wrapped) !== target) throw new Error(`Wrapper renamed ${target} to ${nameOf(wrapped)}`);\n\t\titems.set(target, wrapped);\n\t} catch (error) {\n\t\titems.delete(target);\n\t\treport(error);\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"agent.js","sourceRoot":"","sources":["../../src/harness/agent.ts"],"names":[],"mappings":"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","sourcesContent":["import { copyJson, type Draft } from \"@earendil-works/chord\";\nimport { defineDoc } from \"../documents.ts\";\nimport type { ConversationId, ConversationRecord, Tx } from \"../types.ts\";\nimport type {\n\tAgent,\n\tAgentChange,\n\tAgentState,\n\tCompactionPolicy,\n\tConversationRetryPolicy,\n\tExtension,\n\tHarnessSettings,\n\tProgressPolicy,\n\tPromptSection,\n\tRegistrySnapshot,\n\tSettings,\n\tToolRegistration,\n} from \"./types.ts\";\n\nexport const DEFAULT_RETRY_POLICY: ConversationRetryPolicy = {\n\tenabled: true,\n\tmaxRetries: 3,\n\tbaseDelayMs: 2000,\n\tmaxAgentDelayMs: 60000,\n};\n\nexport const DEFAULT_COMPACTION_POLICY: CompactionPolicy = {\n\tenabled: true,\n\treserveTokens: 16384,\n\tkeepRecentTokens: 20000,\n\tbackgroundTokens: 32768,\n};\n\nexport const DEFAULT_PROGRESS_POLICY: ProgressPolicy = {\n\tpartialIntervalMs: 100,\n\toutputIntervalMs: 100,\n};\n\n/** The reserved section key of the agent's `instructions`. */\nexport const INSTRUCTIONS_KEY = \"instructions\";\n\n/** Built-in agent document; rewindable so forks start from the agent at their fork entry. */\nexport const AgentDoc = defineDoc<AgentState>({\n\tkind: \"pi.agent\",\n\tversion: 1,\n\tscope: \"conversation\",\n\thistory: \"rewindable\",\n\tfork: \"asOf\",\n\tinitial: () => ({}),\n\tcheckpointWhen: () => true,\n});\n\n/** Default `settings.contextRetentionMs`: ten minutes. */\nconst DEFAULT_CONTEXT_RETENTION_MS = 600_000;\n\n/** Resolve the host settings: every field over its built-in default, object fields merged. */\nexport function resolveSettings(settings: HarnessSettings | undefined): Settings {\n\tconst extensions = settings?.extensions;\n\treturn {\n\t\t...(extensions === undefined ? {} : { extensions }),\n\t\tstream: { ...settings?.stream },\n\t\tretry: { ...DEFAULT_RETRY_POLICY, ...settings?.retry },\n\t\tcompaction: { ...DEFAULT_COMPACTION_POLICY, ...settings?.compaction },\n\t\t// Field by field, so an explicitly undefined interval keeps its default.\n\t\tprogress: {\n\t\t\tpartialIntervalMs: settings?.progress?.partialIntervalMs ?? DEFAULT_PROGRESS_POLICY.partialIntervalMs,\n\t\t\toutputIntervalMs: settings?.progress?.outputIntervalMs ?? DEFAULT_PROGRESS_POLICY.outputIntervalMs,\n\t\t},\n\t\ttoolExecution: settings?.toolExecution ?? \"parallel\",\n\t\tsteeringMode: settings?.steeringMode ?? \"one-at-a-time\",\n\t\tfollowUpMode: settings?.followUpMode ?? \"one-at-a-time\",\n\t\tcontextRetentionMs: settings?.contextRetentionMs ?? DEFAULT_CONTEXT_RETENTION_MS,\n\t};\n}\n\n/** Apply one change to `pi.agent`: a given field replaces the stored one, `null` clears it, `undefined` changes nothing. */\nexport async function configure(tx: Tx, conversationId: ConversationId, change: AgentChange): Promise<void> {\n\tconst state = await tx.doc(AgentDoc, conversationId);\n\tapplyChange(state, change);\n}\n\n/**\n * `addTools` of a tool round: an array gets each name it lacks appended, `{ remove }` loses the names, and unset tools\n * already offer every tool, so nothing is written.\n */\nexport async function addTools(tx: Tx, conversationId: ConversationId, added: readonly string[]): Promise<void> {\n\tconst state = await tx.doc(AgentDoc, conversationId);\n\tconst tools = state.tools;\n\tif (tools === undefined) return;\n\tif (Array.isArray(tools)) {\n\t\tfor (const name of added) if (!tools.includes(name)) tools.push(name);\n\t} else {\n\t\tconst remove = (tools as { remove: string[] }).remove;\n\t\tif (remove.some((name) => added.includes(name))) {\n\t\t\tstate.tools = { remove: remove.filter((name) => !added.includes(name)) };\n\t\t}\n\t}\n}\n\nfunction applyChange(state: Draft<AgentState>, change: AgentChange): void {\n\tconst set = <K extends keyof AgentState>(key: K, value: AgentState[K] | null | undefined) => {\n\t\tif (value === undefined) return;\n\t\tif (value === null) delete state[key];\n\t\telse state[key] = value as Draft<AgentState>[K];\n\t};\n\tset(\"model\", change.model === undefined || change.model === null ? change.model : { ...change.model });\n\tset(\"thinkingLevel\", change.thinkingLevel);\n\tconst extensions = change.extensions;\n\tset(\n\t\t\"extensions\",\n\t\textensions === undefined || extensions === null\n\t\t\t? extensions\n\t\t\t: isList(extensions)\n\t\t\t\t? names(extensions)\n\t\t\t\t: {\n\t\t\t\t\t\t...(extensions.add === undefined ? {} : { add: names(extensions.add) }),\n\t\t\t\t\t\t...(extensions.remove === undefined ? {} : { remove: names(extensions.remove) }),\n\t\t\t\t\t},\n\t);\n\tconst tools = change.tools;\n\tset(\n\t\t\"tools\",\n\t\ttools === undefined || tools === null ? tools : isList(tools) ? names(tools) : { remove: names(tools.remove) },\n\t);\n\tset(\"instructions\", change.instructions);\n\tset(\"cwd\", change.cwd);\n}\n\nfunction isList<T>(value: readonly T[] | object): value is readonly T[] {\n\treturn Array.isArray(value);\n}\n\nfunction names(items: readonly { readonly name: string }[]): string[] {\n\treturn items.map((item) => item.name);\n}\n\n/**\n * Built-in part of every Harness commit that creates or forks a conversation, for `pi.agent`: a fork keeps its `asOf`\n * copy; a new task-owned conversation copies the stored agent of its owner task's conversation; a new ownerless one\n * starts empty.\n */\nexport async function createAgent(tx: Tx, conversation: ConversationRecord): Promise<void> {\n\tif (conversation.parent !== undefined) return;\n\tconst agent = await tx.doc(AgentDoc, conversation.id);\n\tif (conversation.owner === undefined) return;\n\tconst owner = await tx.doc(AgentDoc, conversation.owner.conversationId);\n\tObject.assign(agent, copyJson(owner) as AgentState);\n}\n\n/** Handlers of the selected extensions' hooks for a task name, in extension order. */\nexport function agentHooks(agent: Agent, taskName: string): object[] {\n\tconst handlers: object[] = [];\n\tfor (const extension of agent.extensions) {\n\t\tfor (const hook of extension.hooks ?? []) if (hook.task === taskName) handlers.push(hook.handlers);\n\t}\n\treturn handlers;\n}\n\n/**\n * Resolve an agent from its stored state (absent: every field unset), a registry snapshot, and resolved settings. A\n * wrapper that throws or renames drops its target and is reported; a wrapper without a target does nothing.\n */\nexport function resolveAgent<Tool extends ToolRegistration>(\n\tstate: Readonly<AgentState> | undefined,\n\tsnapshot: RegistrySnapshot<Tool>,\n\tsettings: Settings,\n\treport: (error: unknown) => void,\n): Agent<Tool> {\n\tconst extensions = selectExtensions(state?.extensions, snapshot, settings);\n\n\tconst composed = new Map<string, Tool>();\n\tfor (const extension of extensions) for (const tool of extension.tools ?? []) composed.set(tool.name, tool);\n\tconst sections = new Map<string, PromptSection<Tool>>();\n\tfor (const extension of extensions) {\n\t\tfor (const section of extension.sections ?? []) sections.set(section.key, section);\n\t}\n\tfor (const extension of extensions) {\n\t\tfor (const wrap of extension.wraps ?? []) {\n\t\t\tif (\"tool\" in wrap)\n\t\t\t\tapplyWrap(\n\t\t\t\t\tcomposed,\n\t\t\t\t\twrap.tool,\n\t\t\t\t\t(tool) => wrap.wrap(tool),\n\t\t\t\t\t(tool) => tool.name,\n\t\t\t\t\treport,\n\t\t\t\t);\n\t\t\telse\n\t\t\t\tapplyWrap(\n\t\t\t\t\tsections,\n\t\t\t\t\twrap.section,\n\t\t\t\t\t(section) => wrap.wrap(section),\n\t\t\t\t\t(section) => section.key,\n\t\t\t\t\treport,\n\t\t\t\t);\n\t\t}\n\t}\n\n\tconst filter = state?.tools;\n\tlet tools: Tool[];\n\tif (filter === undefined) tools = [...composed.values()];\n\telse if (Array.isArray(filter)) {\n\t\ttools = [];\n\t\tfor (const name of new Set(filter)) {\n\t\t\tconst tool = composed.get(name);\n\t\t\tif (tool !== undefined) tools.push(tool);\n\t\t}\n\t} else {\n\t\tconst removed = new Set((filter as { remove: string[] }).remove);\n\t\ttools = [...composed.values()].filter((tool) => !removed.has(tool.name));\n\t}\n\n\tconst instructions = state?.instructions;\n\tconst agentSections = [...sections.values()];\n\tif (instructions !== undefined) agentSections.push({ key: INSTRUCTIONS_KEY, render: () => instructions });\n\n\tconst agent: Agent<Tool> = {\n\t\t...(state?.model === undefined ? {} : { model: state.model }),\n\t\tthinkingLevel: state?.thinkingLevel ?? \"off\",\n\t\textensions,\n\t\ttools,\n\t\tsections: agentSections,\n\t\t...(instructions === undefined ? {} : { instructions }),\n\t\t...(state?.cwd === undefined ? {} : { cwd: state.cwd }),\n\t};\n\treturn agent;\n}\n\n/** Selected installed extensions: the stored array, or the default selection edited by `{ add, remove }`. */\nfunction selectExtensions<Tool extends ToolRegistration>(\n\tstored: AgentState[\"extensions\"],\n\tsnapshot: RegistrySnapshot<Tool>,\n\tsettings: Settings,\n): Extension<Tool>[] {\n\tlet selected: string[];\n\tif (Array.isArray(stored)) selected = stored;\n\telse {\n\t\tconst base = settings.extensions?.map((extension) => extension.name) ?? snapshot.installed().map((e) => e.name);\n\t\tconst edit = stored as { add?: string[]; remove?: string[] } | undefined;\n\t\tconst removed = new Set(edit?.remove ?? []);\n\t\tselected = [...base, ...(edit?.add ?? [])].filter((name) => !removed.has(name));\n\t}\n\tconst extensions: Extension<Tool>[] = [];\n\tfor (const name of new Set(selected)) {\n\t\tconst extension = snapshot.extension(name);\n\t\tif (extension !== undefined) extensions.push(extension);\n\t}\n\treturn extensions;\n}\n\nfunction applyWrap<T>(\n\titems: Map<string, T>,\n\ttarget: string,\n\twrap: (item: T) => T,\n\tnameOf: (item: T) => string,\n\treport: (error: unknown) => void,\n): void {\n\tconst item = items.get(target);\n\tif (item === undefined) return;\n\ttry {\n\t\tconst wrapped = wrap(item);\n\t\tif (nameOf(wrapped) !== target) throw new Error(`Wrapper renamed ${target} to ${nameOf(wrapped)}`);\n\t\titems.set(target, wrapped);\n\t} catch (error) {\n\t\titems.delete(target);\n\t\treport(error);\n\t}\n}\n"]}
|
|
@@ -105,7 +105,7 @@ export const CompactionTask = defineTask({
|
|
|
105
105
|
if (model === undefined)
|
|
106
106
|
return failNoModel(runtime, ref, context);
|
|
107
107
|
// The context at `tail` is immutable, so this is the range `select` chose.
|
|
108
|
-
const view = await runtime.context(runtime.conversationId, context, tail);
|
|
108
|
+
const view = await runtime.context(runtime.conversationId, context, { at: tail });
|
|
109
109
|
const cut = view.entries.findIndex((entry) => entry.id === firstKept);
|
|
110
110
|
const now = runtime.now();
|
|
111
111
|
const messages = [
|