@llblab/pi-actors 0.43.1 → 0.45.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/AGENTS.md +6 -6
- package/BACKLOG.md +1 -189
- package/CHANGELOG.md +419 -402
- package/README.md +8 -6
- package/dist/index.js +7 -3
- package/dist/lib/async-runs.d.ts +2 -0
- package/dist/lib/async-runs.js +41 -34
- package/dist/lib/config.js +1 -0
- package/dist/lib/file-state.d.ts +3 -1
- package/dist/lib/file-state.js +25 -7
- package/dist/lib/inspector-overlay.js +46 -30
- package/dist/lib/inspector.js +8 -2
- package/dist/lib/limits.d.ts +10 -0
- package/dist/lib/limits.js +10 -0
- package/dist/lib/observability.d.ts +4 -2
- package/dist/lib/observability.js +43 -36
- package/dist/lib/recipes-discovery.js +20 -3
- package/dist/lib/recipes-references.d.ts +10 -0
- package/dist/lib/recipes-references.js +168 -5
- package/dist/lib/run-evidence-policy.d.ts +95 -0
- package/dist/lib/run-evidence-policy.js +177 -0
- package/dist/lib/run-ui-runtime.js +2 -0
- package/dist/lib/runs-controls.d.ts +7 -5
- package/dist/lib/runs-controls.js +186 -49
- package/dist/lib/runs-retention.js +27 -14
- package/dist/lib/runs-trace.d.ts +25 -1
- package/dist/lib/runs-trace.js +410 -21
- package/dist/lib/runtime-triage.js +6 -22
- package/dist/lib/schema.d.ts +3 -0
- package/dist/lib/schema.js +34 -1
- package/dist/lib/tools-inspect.js +58 -14
- package/dist/lib/tools-local.js +27 -10
- package/dist/lib/tools-spawn.js +2 -2
- package/dist/lib/trace-projection.js +90 -44
- package/dist/scripts/conformance.mjs +5 -0
- package/dist/scripts/locker.mjs +31 -74
- package/dist/scripts/music-player.mjs +41 -129
- package/dist/scripts/release-gates.mjs +26 -4
- package/dist/skills/actors/SKILL.md +9 -8
- package/dist/skills/swarm/SKILL.md +2 -2
- package/docs/README.md +0 -1
- package/docs/actor-inspector.md +3 -2
- package/docs/async-runs.md +10 -8
- package/docs/command-templates.md +2 -2
- package/docs/recipe-library.md +4 -2
- package/docs/template-recipes.md +10 -4
- package/docs/tool-registry.md +1 -1
- package/index.ts +9 -3
- package/lib/async-runs.ts +51 -54
- package/lib/config.ts +4 -0
- package/lib/file-state.ts +28 -7
- package/lib/inspector-overlay.ts +29 -17
- package/lib/inspector.ts +16 -2
- package/lib/limits.ts +10 -0
- package/lib/observability.ts +55 -57
- package/lib/recipes-discovery.ts +23 -3
- package/lib/recipes-references.ts +227 -7
- package/lib/run-evidence-policy.ts +242 -0
- package/lib/run-ui-runtime.ts +2 -0
- package/lib/runs-controls.ts +177 -108
- package/lib/runs-retention.ts +28 -20
- package/lib/runs-trace.ts +496 -21
- package/lib/runtime-triage.ts +11 -25
- package/lib/schema.ts +53 -1
- package/lib/tools-inspect.ts +56 -15
- package/lib/tools-local.ts +49 -19
- package/lib/tools-spawn.ts +2 -2
- package/lib/trace-projection.ts +137 -75
- package/package.json +1 -1
- package/scripts/conformance.mjs +5 -0
- package/scripts/locker.mjs +31 -74
- package/scripts/music-player.mjs +41 -129
- package/scripts/release-gates.mjs +26 -4
- package/skills/actors/SKILL.md +9 -8
- package/skills/swarm/SKILL.md +2 -2
- package/dist/lib/runtime-notifier.d.ts +0 -48
- package/dist/lib/runtime-notifier.js +0 -138
- package/docs/0.43-baseline.md +0 -39
- package/lib/runtime-notifier.ts +0 -211
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Runtime wake notifications for actor state.
|
|
3
|
-
* Zones: advisory wake layer, file-backed runtime state, cross-platform notification boundary
|
|
4
|
-
* Owns best-effort live wake signals while durable Run state remains canonical.
|
|
5
|
-
*/
|
|
6
|
-
import { randomUUID } from "node:crypto";
|
|
7
|
-
import { appendFileSync, existsSync, mkdirSync, readFileSync, statSync, watch, } from "node:fs";
|
|
8
|
-
import { basename, dirname, join } from "node:path";
|
|
9
|
-
import { readJsonlFileResilient } from "./state-readers.js";
|
|
10
|
-
const DEFAULT_POLL_INTERVAL_MS = 1000;
|
|
11
|
-
export function runtimeWakeFile(stateDir) {
|
|
12
|
-
return join(stateDir, "wake.jsonl");
|
|
13
|
-
}
|
|
14
|
-
function normalizeWakeEvent(stateDir, event) {
|
|
15
|
-
const actor = event.actor.trim();
|
|
16
|
-
const reason = event.reason.trim();
|
|
17
|
-
if (!actor)
|
|
18
|
-
throw new Error("Runtime wake event requires actor.");
|
|
19
|
-
if (!reason)
|
|
20
|
-
throw new Error("Runtime wake event requires reason.");
|
|
21
|
-
return {
|
|
22
|
-
actor,
|
|
23
|
-
id: randomUUID(),
|
|
24
|
-
...(event.metadata ? { metadata: event.metadata } : {}),
|
|
25
|
-
reason,
|
|
26
|
-
state_dir: stateDir,
|
|
27
|
-
ts: new Date().toISOString(),
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
export function notifyRuntimeWake(stateDir, event) {
|
|
31
|
-
const normalized = normalizeWakeEvent(stateDir, event);
|
|
32
|
-
const file = runtimeWakeFile(stateDir);
|
|
33
|
-
mkdirSync(dirname(file), { recursive: true });
|
|
34
|
-
appendFileSync(file, `${JSON.stringify(normalized)}\n`, "utf8");
|
|
35
|
-
return normalized;
|
|
36
|
-
}
|
|
37
|
-
export function parseRuntimeWakeEventLine(line) {
|
|
38
|
-
try {
|
|
39
|
-
const record = JSON.parse(line);
|
|
40
|
-
if (typeof record.actor !== "string" ||
|
|
41
|
-
typeof record.id !== "string" ||
|
|
42
|
-
typeof record.reason !== "string" ||
|
|
43
|
-
typeof record.state_dir !== "string" ||
|
|
44
|
-
typeof record.ts !== "string") {
|
|
45
|
-
return undefined;
|
|
46
|
-
}
|
|
47
|
-
return {
|
|
48
|
-
actor: record.actor,
|
|
49
|
-
id: record.id,
|
|
50
|
-
...(record.metadata &&
|
|
51
|
-
typeof record.metadata === "object" &&
|
|
52
|
-
!Array.isArray(record.metadata)
|
|
53
|
-
? { metadata: record.metadata }
|
|
54
|
-
: {}),
|
|
55
|
-
reason: record.reason,
|
|
56
|
-
state_dir: record.state_dir,
|
|
57
|
-
ts: record.ts,
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
catch {
|
|
61
|
-
return undefined;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
export function readRuntimeWakeEvents(stateDir) {
|
|
65
|
-
return readJsonlFileResilient(runtimeWakeFile(stateDir))
|
|
66
|
-
.records.map((record) => parseRuntimeWakeEventLine(JSON.stringify(record)))
|
|
67
|
-
.filter((event) => Boolean(event));
|
|
68
|
-
}
|
|
69
|
-
export function createFileRuntimeNotifier(stateDir, options = {}) {
|
|
70
|
-
const file = runtimeWakeFile(stateDir);
|
|
71
|
-
const pollIntervalMs = Math.max(25, Number(options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS));
|
|
72
|
-
return {
|
|
73
|
-
notify: (event) => notifyRuntimeWake(stateDir, event),
|
|
74
|
-
subscribe: (actor, onWake, subscribeOptions = {}) => {
|
|
75
|
-
mkdirSync(dirname(file), { recursive: true });
|
|
76
|
-
let position = options.replay || !existsSync(file) ? 0 : statSync(file).size;
|
|
77
|
-
let pending = "";
|
|
78
|
-
let closed = false;
|
|
79
|
-
const reconcile = (reason) => {
|
|
80
|
-
if (closed)
|
|
81
|
-
return;
|
|
82
|
-
subscribeOptions.onReconcile?.({
|
|
83
|
-
actor,
|
|
84
|
-
reason,
|
|
85
|
-
state_dir: stateDir,
|
|
86
|
-
ts: new Date().toISOString(),
|
|
87
|
-
});
|
|
88
|
-
};
|
|
89
|
-
const drain = () => {
|
|
90
|
-
if (closed || !existsSync(file))
|
|
91
|
-
return;
|
|
92
|
-
const buffer = readFileSync(file);
|
|
93
|
-
if (position > buffer.length) {
|
|
94
|
-
position = 0;
|
|
95
|
-
pending = "";
|
|
96
|
-
}
|
|
97
|
-
const chunk = pending + buffer.subarray(position).toString("utf8");
|
|
98
|
-
position = buffer.length;
|
|
99
|
-
const lines = chunk.split("\n");
|
|
100
|
-
pending = chunk.endsWith("\n") ? "" : (lines.pop() ?? "");
|
|
101
|
-
for (const line of lines) {
|
|
102
|
-
if (!line.trim())
|
|
103
|
-
continue;
|
|
104
|
-
const event = parseRuntimeWakeEventLine(line);
|
|
105
|
-
if (event && event.actor === actor) {
|
|
106
|
-
onWake(event);
|
|
107
|
-
reconcile("wake");
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
};
|
|
111
|
-
let watcher;
|
|
112
|
-
if (options.watch !== false) {
|
|
113
|
-
try {
|
|
114
|
-
watcher = watch(dirname(file), { persistent: false }, (_eventType, changedFile) => {
|
|
115
|
-
if (!changedFile || String(changedFile) === basename(file))
|
|
116
|
-
drain();
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
|
-
catch {
|
|
120
|
-
// fs.watch availability varies by platform/filesystem; polling below is the fallback.
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
reconcile("initial");
|
|
124
|
-
const timer = setInterval(() => {
|
|
125
|
-
drain();
|
|
126
|
-
reconcile("poll");
|
|
127
|
-
}, pollIntervalMs);
|
|
128
|
-
timer.unref?.();
|
|
129
|
-
return {
|
|
130
|
-
close: () => {
|
|
131
|
-
closed = true;
|
|
132
|
-
clearInterval(timer);
|
|
133
|
-
watcher?.close();
|
|
134
|
-
},
|
|
135
|
-
};
|
|
136
|
-
},
|
|
137
|
-
};
|
|
138
|
-
}
|
package/docs/0.43-baseline.md
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
# Released 0.43 Baseline
|
|
2
|
-
|
|
3
|
-
Release `0.43.0` is frozen at commit `0d6db30cd2e070c1d03ed1e60bef70538a3083c1`. Local and remote `main`, immutable tag `v0.43.0`, the GitHub Release, and `@llblab/pi-actors@0.43.0` all resolve to that commit. The npm artifact records the same `gitHead`, shasum `64b5982bc1cda5723ce2f80f4ed15476b6016d62`, and `latest` dist-tag.
|
|
4
|
-
|
|
5
|
-
The continuing `dev` line descends from release parent `f4e4e78891e0c3d570b31c35572aeaa226af6f91`, whose tree exactly matches the tagged merge tree. This preserves content equivalence without copying the content-neutral merge wrapper into `dev`.
|
|
6
|
-
|
|
7
|
-
## Reproduced Evidence
|
|
8
|
-
|
|
9
|
-
An isolated detached `v0.43.0` worktree produced this evidence on 2026-08-11:
|
|
10
|
-
|
|
11
|
-
- `npm ci` succeeded with 147 installed packages. Its generic audit summary included three peer-tree findings; the package-owned `--omit=peer` audit passed with zero vulnerabilities.
|
|
12
|
-
- `npm run test:preservation` passed 91 of 91 tests.
|
|
13
|
-
- `npm run release:validate` passed 525 tests with 5 platform skips, 106 conformance tests, 58 Recipe QA files, package dry-run, removed-surface checks, strict Domain DAG, and ABCd context validation.
|
|
14
|
-
- GitHub reported no open issue or pull request requiring post-release scope changes.
|
|
15
|
-
|
|
16
|
-
## Shipped-Line Ratchet
|
|
17
|
-
|
|
18
|
-
The released tree contains exactly **28,853** lines under the surfaces measured by `scripts/release-gates.mjs`:
|
|
19
|
-
|
|
20
|
-
- `lib/`
|
|
21
|
-
- `scripts/`
|
|
22
|
-
- `recipes/`
|
|
23
|
-
- `docs/`
|
|
24
|
-
- `skills/`
|
|
25
|
-
|
|
26
|
-
Release validation requires every retained post-`0.43.0` tree to remain strictly below 28,853 lines. Tests, fixtures, and workflows remain outside this existing metric. The ratchet prevents deleted communication-plane code from funding replacement bloat; it does not grant removed behavior preservation status.
|
|
27
|
-
|
|
28
|
-
## Retained Invariants
|
|
29
|
-
|
|
30
|
-
The preservation suite covers owner-filtered Run discovery, immutable generation fencing, process identity, lifecycle locking, shutdown and parent teardown, terminal reconciliation, bounded complete captures, Pi session provenance, path containment, redaction, review recovery, generation-bound Control, and canonical Trace. Rooms, routing, addressed messages, mailboxes, and communication topology are not retained.
|
|
31
|
-
|
|
32
|
-
## Validation
|
|
33
|
-
|
|
34
|
-
```bash
|
|
35
|
-
npm run test:preservation
|
|
36
|
-
npm run release:validate
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
Release gates also reject removed-surface residue, Domain DAG violations, ABCd context drift, stale package contents, and shipped-line growth at or above the released baseline.
|
package/lib/runtime-notifier.ts
DELETED
|
@@ -1,211 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Runtime wake notifications for actor state.
|
|
3
|
-
* Zones: advisory wake layer, file-backed runtime state, cross-platform notification boundary
|
|
4
|
-
* Owns best-effort live wake signals while durable Run state remains canonical.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { randomUUID } from "node:crypto";
|
|
8
|
-
import {
|
|
9
|
-
appendFileSync,
|
|
10
|
-
existsSync,
|
|
11
|
-
mkdirSync,
|
|
12
|
-
readFileSync,
|
|
13
|
-
statSync,
|
|
14
|
-
watch,
|
|
15
|
-
type FSWatcher,
|
|
16
|
-
} from "node:fs";
|
|
17
|
-
import { basename, dirname, join } from "node:path";
|
|
18
|
-
|
|
19
|
-
import { readJsonlFileResilient } from "./state-readers.ts";
|
|
20
|
-
|
|
21
|
-
export interface RuntimeWakeEvent {
|
|
22
|
-
actor: string;
|
|
23
|
-
id: string;
|
|
24
|
-
metadata?: Record<string, unknown>;
|
|
25
|
-
reason: string;
|
|
26
|
-
state_dir: string;
|
|
27
|
-
ts: string;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export interface RuntimeNotifierSubscription {
|
|
31
|
-
close(): void;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export type RuntimeReconcileReason = "initial" | "poll" | "wake";
|
|
35
|
-
|
|
36
|
-
export interface RuntimeReconcileEvent {
|
|
37
|
-
actor: string;
|
|
38
|
-
reason: RuntimeReconcileReason;
|
|
39
|
-
state_dir: string;
|
|
40
|
-
ts: string;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export interface RuntimeNotifierSubscribeOptions {
|
|
44
|
-
onReconcile?: (event: RuntimeReconcileEvent) => void;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export interface FileRuntimeNotifierOptions {
|
|
48
|
-
pollIntervalMs?: number;
|
|
49
|
-
replay?: boolean;
|
|
50
|
-
watch?: boolean;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export interface RuntimeNotifier {
|
|
54
|
-
notify(event: { actor: string; metadata?: Record<string, unknown>; reason: string }): RuntimeWakeEvent;
|
|
55
|
-
subscribe(
|
|
56
|
-
actor: string,
|
|
57
|
-
onWake: (event: RuntimeWakeEvent) => void,
|
|
58
|
-
options?: RuntimeNotifierSubscribeOptions,
|
|
59
|
-
): RuntimeNotifierSubscription;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const DEFAULT_POLL_INTERVAL_MS = 1000;
|
|
63
|
-
|
|
64
|
-
export function runtimeWakeFile(stateDir: string): string {
|
|
65
|
-
return join(stateDir, "wake.jsonl");
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function normalizeWakeEvent(
|
|
69
|
-
stateDir: string,
|
|
70
|
-
event: { actor: string; metadata?: Record<string, unknown>; reason: string },
|
|
71
|
-
): RuntimeWakeEvent {
|
|
72
|
-
const actor = event.actor.trim();
|
|
73
|
-
const reason = event.reason.trim();
|
|
74
|
-
if (!actor) throw new Error("Runtime wake event requires actor.");
|
|
75
|
-
if (!reason) throw new Error("Runtime wake event requires reason.");
|
|
76
|
-
return {
|
|
77
|
-
actor,
|
|
78
|
-
id: randomUUID(),
|
|
79
|
-
...(event.metadata ? { metadata: event.metadata } : {}),
|
|
80
|
-
reason,
|
|
81
|
-
state_dir: stateDir,
|
|
82
|
-
ts: new Date().toISOString(),
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
export function notifyRuntimeWake(
|
|
87
|
-
stateDir: string,
|
|
88
|
-
event: { actor: string; metadata?: Record<string, unknown>; reason: string },
|
|
89
|
-
): RuntimeWakeEvent {
|
|
90
|
-
const normalized = normalizeWakeEvent(stateDir, event);
|
|
91
|
-
const file = runtimeWakeFile(stateDir);
|
|
92
|
-
mkdirSync(dirname(file), { recursive: true });
|
|
93
|
-
appendFileSync(file, `${JSON.stringify(normalized)}\n`, "utf8");
|
|
94
|
-
return normalized;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
export function parseRuntimeWakeEventLine(
|
|
98
|
-
line: string,
|
|
99
|
-
): RuntimeWakeEvent | undefined {
|
|
100
|
-
try {
|
|
101
|
-
const record = JSON.parse(line) as Record<string, unknown>;
|
|
102
|
-
if (
|
|
103
|
-
typeof record.actor !== "string" ||
|
|
104
|
-
typeof record.id !== "string" ||
|
|
105
|
-
typeof record.reason !== "string" ||
|
|
106
|
-
typeof record.state_dir !== "string" ||
|
|
107
|
-
typeof record.ts !== "string"
|
|
108
|
-
) {
|
|
109
|
-
return undefined;
|
|
110
|
-
}
|
|
111
|
-
return {
|
|
112
|
-
actor: record.actor,
|
|
113
|
-
id: record.id,
|
|
114
|
-
...(record.metadata &&
|
|
115
|
-
typeof record.metadata === "object" &&
|
|
116
|
-
!Array.isArray(record.metadata)
|
|
117
|
-
? { metadata: record.metadata as Record<string, unknown> }
|
|
118
|
-
: {}),
|
|
119
|
-
reason: record.reason,
|
|
120
|
-
state_dir: record.state_dir,
|
|
121
|
-
ts: record.ts,
|
|
122
|
-
};
|
|
123
|
-
} catch {
|
|
124
|
-
return undefined;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
export function readRuntimeWakeEvents(stateDir: string): RuntimeWakeEvent[] {
|
|
129
|
-
return readJsonlFileResilient<Record<string, unknown>>(runtimeWakeFile(stateDir))
|
|
130
|
-
.records.map((record) => parseRuntimeWakeEventLine(JSON.stringify(record)))
|
|
131
|
-
.filter((event): event is RuntimeWakeEvent => Boolean(event));
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
export function createFileRuntimeNotifier(
|
|
135
|
-
stateDir: string,
|
|
136
|
-
options: FileRuntimeNotifierOptions = {},
|
|
137
|
-
): RuntimeNotifier {
|
|
138
|
-
const file = runtimeWakeFile(stateDir);
|
|
139
|
-
const pollIntervalMs = Math.max(
|
|
140
|
-
25,
|
|
141
|
-
Number(options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS),
|
|
142
|
-
);
|
|
143
|
-
|
|
144
|
-
return {
|
|
145
|
-
notify: (event) => notifyRuntimeWake(stateDir, event),
|
|
146
|
-
subscribe: (actor, onWake, subscribeOptions = {}) => {
|
|
147
|
-
mkdirSync(dirname(file), { recursive: true });
|
|
148
|
-
let position =
|
|
149
|
-
options.replay || !existsSync(file) ? 0 : statSync(file).size;
|
|
150
|
-
let pending = "";
|
|
151
|
-
let closed = false;
|
|
152
|
-
const reconcile = (reason: RuntimeReconcileReason): void => {
|
|
153
|
-
if (closed) return;
|
|
154
|
-
subscribeOptions.onReconcile?.({
|
|
155
|
-
actor,
|
|
156
|
-
reason,
|
|
157
|
-
state_dir: stateDir,
|
|
158
|
-
ts: new Date().toISOString(),
|
|
159
|
-
});
|
|
160
|
-
};
|
|
161
|
-
const drain = (): void => {
|
|
162
|
-
if (closed || !existsSync(file)) return;
|
|
163
|
-
const buffer = readFileSync(file);
|
|
164
|
-
if (position > buffer.length) {
|
|
165
|
-
position = 0;
|
|
166
|
-
pending = "";
|
|
167
|
-
}
|
|
168
|
-
const chunk = pending + buffer.subarray(position).toString("utf8");
|
|
169
|
-
position = buffer.length;
|
|
170
|
-
const lines = chunk.split("\n");
|
|
171
|
-
pending = chunk.endsWith("\n") ? "" : (lines.pop() ?? "");
|
|
172
|
-
for (const line of lines) {
|
|
173
|
-
if (!line.trim()) continue;
|
|
174
|
-
const event = parseRuntimeWakeEventLine(line);
|
|
175
|
-
if (event && event.actor === actor) {
|
|
176
|
-
onWake(event);
|
|
177
|
-
reconcile("wake");
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
};
|
|
181
|
-
|
|
182
|
-
let watcher: FSWatcher | undefined;
|
|
183
|
-
if (options.watch !== false) {
|
|
184
|
-
try {
|
|
185
|
-
watcher = watch(
|
|
186
|
-
dirname(file),
|
|
187
|
-
{ persistent: false },
|
|
188
|
-
(_eventType, changedFile) => {
|
|
189
|
-
if (!changedFile || String(changedFile) === basename(file)) drain();
|
|
190
|
-
},
|
|
191
|
-
);
|
|
192
|
-
} catch {
|
|
193
|
-
// fs.watch availability varies by platform/filesystem; polling below is the fallback.
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
reconcile("initial");
|
|
197
|
-
const timer = setInterval(() => {
|
|
198
|
-
drain();
|
|
199
|
-
reconcile("poll");
|
|
200
|
-
}, pollIntervalMs);
|
|
201
|
-
timer.unref?.();
|
|
202
|
-
return {
|
|
203
|
-
close: () => {
|
|
204
|
-
closed = true;
|
|
205
|
-
clearInterval(timer);
|
|
206
|
-
watcher?.close();
|
|
207
|
-
},
|
|
208
|
-
};
|
|
209
|
-
},
|
|
210
|
-
};
|
|
211
|
-
}
|