@yemi33/minions 0.1.2450 → 0.1.2451
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/dashboard/js/render-inbox.js +50 -13
- package/dashboard.js +13 -0
- package/package.json +1 -1
|
@@ -19,13 +19,44 @@ function _timeSinceMs(mtimeMs) {
|
|
|
19
19
|
return days + 'd ago';
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
// Per-file content cache keyed by name → { mtimeMs, content }. The inbox can
|
|
23
|
+
// hold thousands of notes; without a cache every 4 s refresh re-fetched EVERY
|
|
24
|
+
// file's body. Firing that many fetches at once exhausted the browser socket
|
|
25
|
+
// pool and flooded the console with `net::ERR_INSUFFICIENT_RESOURCES` on every
|
|
26
|
+
// page (the refresh loop runs regardless of the active page). Caching by
|
|
27
|
+
// (name, mtimeMs) means steady-state refreshes re-fetch nothing — only new or
|
|
28
|
+
// edited notes are pulled — and the pool below bounds the initial load so the
|
|
29
|
+
// socket pool is never saturated. (W-mscjlch1005qdc11)
|
|
30
|
+
const _inboxContentCache = new Map();
|
|
31
|
+
// Max concurrent /state/notes/inbox/<file> fetches. Chrome caps same-host HTTP/1
|
|
32
|
+
// connections at 6; 8 keeps the pipe full without tripping ERR_INSUFFICIENT_RESOURCES.
|
|
33
|
+
const INBOX_FETCH_CONCURRENCY = 8;
|
|
34
|
+
|
|
35
|
+
// Run async `worker(item)` over `items` with at most `limit` in flight at once.
|
|
36
|
+
// Preserves input order in the returned results array. Used to bound the inbox
|
|
37
|
+
// per-file content fetches so a large inbox can't exhaust the browser's socket pool.
|
|
38
|
+
async function _mapWithConcurrency(items, limit, worker) {
|
|
39
|
+
const results = new Array(items.length);
|
|
40
|
+
let next = 0;
|
|
41
|
+
async function run() {
|
|
42
|
+
while (next < items.length) {
|
|
43
|
+
const i = next++;
|
|
44
|
+
results[i] = await worker(items[i], i);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
const pool = [];
|
|
48
|
+
for (let i = 0; i < Math.min(limit, items.length); i++) pool.push(run());
|
|
49
|
+
await Promise.all(pool);
|
|
50
|
+
return results;
|
|
51
|
+
}
|
|
52
|
+
|
|
22
53
|
// Fetch the inbox via the /state/notes/inbox directory listing + per-file
|
|
23
54
|
// content fetch. The listing call returns {entries:[{name, mtimeMs, size,
|
|
24
|
-
// isDir}]} and is cheap (one statSync per entry).
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
// content edits within one 4 s poll.
|
|
55
|
+
// isDir}]} and is cheap (one statSync per entry). Per-file bodies are fetched
|
|
56
|
+
// through a bounded-concurrency pool and cached by (name, mtimeMs), so unchanged
|
|
57
|
+
// notes are served from memory and only new/edited notes hit the network. Issue
|
|
58
|
+
// #2949 — the staleness fix applies here too: the inbox directory mtime advances
|
|
59
|
+
// on every new note, and the mtime cache key surfaces content edits within one 4 s poll.
|
|
29
60
|
async function fetchInboxFromDisk() {
|
|
30
61
|
try {
|
|
31
62
|
const listResp = await fetch('/state/notes/inbox');
|
|
@@ -35,21 +66,27 @@ async function fetchInboxFromDisk() {
|
|
|
35
66
|
const mdFiles = entries
|
|
36
67
|
.filter((e) => e && !e.isDir && typeof e.name === 'string' && e.name.endsWith('.md'))
|
|
37
68
|
.sort((a, b) => (b.mtimeMs || 0) - (a.mtimeMs || 0));
|
|
38
|
-
|
|
69
|
+
// Drop cache entries for notes that no longer exist so the cache can't grow
|
|
70
|
+
// unbounded across the lifetime of the page.
|
|
71
|
+
const liveNames = new Set(mdFiles.map((e) => e.name));
|
|
72
|
+
for (const key of _inboxContentCache.keys()) {
|
|
73
|
+
if (!liveNames.has(key)) _inboxContentCache.delete(key);
|
|
74
|
+
}
|
|
75
|
+
const items = await _mapWithConcurrency(mdFiles, INBOX_FETCH_CONCURRENCY, async (e) => {
|
|
76
|
+
const cached = _inboxContentCache.get(e.name);
|
|
77
|
+
if (cached && cached.mtimeMs === e.mtimeMs) {
|
|
78
|
+
return { name: e.name, mtime: e.mtimeMs, age: _timeSinceMs(e.mtimeMs), content: cached.content };
|
|
79
|
+
}
|
|
39
80
|
try {
|
|
40
81
|
const r = await fetch('/state/notes/inbox/' + encodeURIComponent(e.name));
|
|
41
82
|
if (!r.ok) return null;
|
|
42
83
|
const content = await r.text();
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
mtime: e.mtimeMs,
|
|
46
|
-
age: _timeSinceMs(e.mtimeMs),
|
|
47
|
-
content,
|
|
48
|
-
};
|
|
84
|
+
_inboxContentCache.set(e.name, { mtimeMs: e.mtimeMs, content });
|
|
85
|
+
return { name: e.name, mtime: e.mtimeMs, age: _timeSinceMs(e.mtimeMs), content };
|
|
49
86
|
} catch {
|
|
50
87
|
return null;
|
|
51
88
|
}
|
|
52
|
-
})
|
|
89
|
+
});
|
|
53
90
|
return items.filter(Boolean);
|
|
54
91
|
} catch {
|
|
55
92
|
return null;
|
package/dashboard.js
CHANGED
|
@@ -3690,6 +3690,19 @@ function handleStateRead(req, res) {
|
|
|
3690
3690
|
let lstat;
|
|
3691
3691
|
try { lstat = fs.lstatSync(resolved); }
|
|
3692
3692
|
catch {
|
|
3693
|
+
// A not-yet-created but allowlisted top-level state directory (e.g. the
|
|
3694
|
+
// meetings dir before the first meeting is recorded) is an EMPTY directory,
|
|
3695
|
+
// not a missing resource. Returning 404 here made the dashboard's directory-
|
|
3696
|
+
// listing clients (render-meetings.js) log a console error on every refresh.
|
|
3697
|
+
// List it as empty instead so "no items yet" is a clean 200. Missing FILES
|
|
3698
|
+
// and missing nested paths still 404. (W-mscjlch1005qdc11)
|
|
3699
|
+
if (isSingleSegment && STATE_READ_ALLOWED_DIRS.has(top)) {
|
|
3700
|
+
res.statusCode = 200;
|
|
3701
|
+
res.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
3702
|
+
res.setHeader('Cache-Control', 'private, max-age=0, must-revalidate');
|
|
3703
|
+
res.end(JSON.stringify({ path: rel.replace(/\\/g, '/'), entries: [] }));
|
|
3704
|
+
return;
|
|
3705
|
+
}
|
|
3693
3706
|
res.statusCode = 404;
|
|
3694
3707
|
res.setHeader('Content-Type', 'application/json');
|
|
3695
3708
|
res.end(JSON.stringify({ error: 'not found' }));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2451",
|
|
4
4
|
"description": "Multi-agent AI dev team that runs from ~/.minions/ — five autonomous agents share a single engine, dashboard, and knowledge base",
|
|
5
5
|
"bin": {
|
|
6
6
|
"minions": "bin/minions.js"
|