@agentpulselabs/cli-plugin-pulse 1.1.3 → 1.1.6
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/package.json +3 -2
- package/src/hub/core/db.mjs +40 -5
- package/src/hub/core/wake.mjs +5 -3
- package/src/hub/workers/collector.mjs +59 -19
- package/src/hub/workers/manager.mjs +44 -6
- package/src/lib/contract.sha256 +1 -1
- package/src/lib/daemonRuntime.js +25 -7
- package/src/lib/hubDbReader.js +6 -0
- package/src/lib/hubScan.js +1 -0
- package/src/lib/ledger.js +21 -12
- package/src/lib/lifecycle.js +3 -1
- package/src/lib/mirrorSource.js +19 -6
- package/src/lib/runnerStore.js +61 -49
- package/src/runner/bridge.js +8 -3
- package/src/runner/busIdentity.js +11 -1
- package/src/runner/capture.js +83 -20
- package/src/runner/channelDiscipline.js +48 -14
- package/src/runner/channelFiles.js +39 -0
- package/src/runner/channelWalk.js +42 -0
- package/src/runner/collectWorker.js +92 -0
- package/src/runner/deliveryStates.js +22 -9
- package/src/runner/directoryCache.js +34 -0
- package/src/runner/facade.js +149 -87
- package/src/runner/fileReads.js +64 -0
- package/src/runner/fileReadsWorker.js +61 -0
- package/src/runner/hubCapture.js +105 -56
- package/src/runner/loopMonitor.js +89 -0
- package/src/runner/runnerHub.js +3 -1
- package/src/runner/runnerProcess.js +58 -16
- package/src/runner/runnerService.js +18 -2
- package/src/runner/serial.js +5 -0
- package/src/runner/spool.js +46 -11
- package/src/runner/transcriptFeed.js +88 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// Cursor transcript writes, noticed within about a second: the hub's lib/transcript-feed.mjs with the same passes and
|
|
2
|
+
// the same notices, except that a folder is listed again only when its own mtime moved (an entry was added, removed
|
|
3
|
+
// or renamed). Its fast pass listed every session folder of every active project every second, ~770 readdir calls a
|
|
4
|
+
// second on a busy Mac; here that is a stat per folder, and a readdir only for the folders that changed.
|
|
5
|
+
import fs from 'node:fs';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
|
|
8
|
+
const FAST_MS = 1000;
|
|
9
|
+
const FULL_MS = 30e3;
|
|
10
|
+
const ACTIVE_MS = 6 * 3600e3;
|
|
11
|
+
|
|
12
|
+
const safe = (fn, fallback) => { try { return fn(); } catch { return fallback; } };
|
|
13
|
+
const sig = (file) => safe(() => { const s = fs.statSync(file); return { m: s.mtimeMs, z: s.size }; }, null);
|
|
14
|
+
|
|
15
|
+
export function watchTranscripts(projectsDir, onChange, { fastMs = FAST_MS, fullMs = FULL_MS, activeMs = ACTIVE_MS } = {}) {
|
|
16
|
+
const seen = new Map();
|
|
17
|
+
const activeSessions = new Set();
|
|
18
|
+
const activeRoots = new Set();
|
|
19
|
+
/** folder → { m, entries }: its listing as of its mtime `m`. */
|
|
20
|
+
const listings = new Map();
|
|
21
|
+
|
|
22
|
+
const list = (dir) => {
|
|
23
|
+
const m = safe(() => fs.statSync(dir).mtimeMs, null);
|
|
24
|
+
if (m === null) { listings.delete(dir); return []; }
|
|
25
|
+
const hit = listings.get(dir);
|
|
26
|
+
if (hit && hit.m === m) return hit.entries;
|
|
27
|
+
const entries = safe(() => fs.readdirSync(dir, { withFileTypes: true }).map((d) => ({ name: d.name, dir: d.isDirectory() })), []);
|
|
28
|
+
listings.set(dir, { m, entries });
|
|
29
|
+
return entries;
|
|
30
|
+
};
|
|
31
|
+
const dirs = (dir) => list(dir).filter((e) => e.dir).map((e) => e.name);
|
|
32
|
+
const jsonl = (dir) => list(dir).filter((e) => e.name.endsWith('.jsonl')).map((e) => path.join(dir, e.name));
|
|
33
|
+
const sessionFiles = (sessionDir) => [...jsonl(sessionDir), ...jsonl(path.join(sessionDir, 'subagents'))];
|
|
34
|
+
|
|
35
|
+
const check = (file, now, notify) => {
|
|
36
|
+
const s = sig(file);
|
|
37
|
+
if (!s) return false;
|
|
38
|
+
const was = seen.get(file);
|
|
39
|
+
seen.set(file, s);
|
|
40
|
+
if (notify && (!was || was.m !== s.m || was.z !== s.z)) onChange(file);
|
|
41
|
+
return now - s.m < activeMs;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const full = (notify) => {
|
|
45
|
+
const now = Date.now();
|
|
46
|
+
activeSessions.clear();
|
|
47
|
+
activeRoots.clear();
|
|
48
|
+
const present = new Set();
|
|
49
|
+
for (const project of dirs(projectsDir)) {
|
|
50
|
+
const root = path.join(projectsDir, project, 'agent-transcripts');
|
|
51
|
+
const sessions = dirs(root);
|
|
52
|
+
if (!sessions.length) continue;
|
|
53
|
+
present.add(root);
|
|
54
|
+
const rootRecent = now - (sig(root)?.m || 0) < activeMs;
|
|
55
|
+
let any = false;
|
|
56
|
+
for (const id of sessions) {
|
|
57
|
+
const dir = path.join(root, id);
|
|
58
|
+
present.add(dir).add(path.join(dir, 'subagents'));
|
|
59
|
+
let live = false;
|
|
60
|
+
for (const f of sessionFiles(dir)) live = check(f, now, notify) || live;
|
|
61
|
+
if (live) { activeSessions.add(dir); any = true; }
|
|
62
|
+
}
|
|
63
|
+
if (any || rootRecent) activeRoots.add(root);
|
|
64
|
+
}
|
|
65
|
+
present.add(projectsDir);
|
|
66
|
+
for (const dir of listings.keys()) if (!present.has(dir)) listings.delete(dir);
|
|
67
|
+
for (const file of seen.keys()) if (!present.has(path.dirname(file))) seen.delete(file);
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const fast = () => {
|
|
71
|
+
const now = Date.now();
|
|
72
|
+
for (const root of activeRoots) {
|
|
73
|
+
for (const id of dirs(root)) {
|
|
74
|
+
const dir = path.join(root, id);
|
|
75
|
+
if (!activeSessions.has(dir) && sessionFiles(dir).some((f) => !seen.has(f))) activeSessions.add(dir);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
for (const dir of activeSessions) for (const f of sessionFiles(dir)) check(f, now, true);
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
full(false);
|
|
82
|
+
const timers = [setInterval(() => safe(fast), fastMs), setInterval(() => safe(() => full(true)), fullMs)];
|
|
83
|
+
for (const t of timers) t.unref();
|
|
84
|
+
return {
|
|
85
|
+
watching: () => ({ sessions: activeSessions.size, projects: activeRoots.size }),
|
|
86
|
+
close: () => { for (const t of timers) clearInterval(t); },
|
|
87
|
+
};
|
|
88
|
+
}
|