monomind 2.1.0 → 2.1.4
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/README.md +77 -94
- package/package.json +3 -3
- package/packages/@monomind/cli/.claude/commands/mastermind/createorg.md +8 -9
- package/packages/@monomind/cli/.claude/helpers/handlers/capture-handler.cjs +24 -10
- package/packages/@monomind/cli/.claude/helpers/handlers/gates-handler.cjs +2 -2
- package/packages/@monomind/cli/.claude/helpers/handlers/route-handler.cjs +29 -0
- package/packages/@monomind/cli/.claude/helpers/handlers/session-handler.cjs +35 -23
- package/packages/@monomind/cli/.claude/helpers/hook-handler.cjs +21 -0
- package/packages/@monomind/cli/.claude/helpers/utils/monograph.cjs +73 -2
- package/packages/@monomind/cli/.claude/skills/mastermind-skills/createorg.md +91 -701
- package/packages/@monomind/cli/.claude/skills/mastermind-skills/org-settings.md +46 -47
- package/packages/@monomind/cli/.claude/skills/mastermind-skills/runorg.md +12 -1
- package/packages/@monomind/cli/README.md +77 -94
- package/packages/@monomind/cli/dist/src/browser/workflow/store.d.ts +2 -2
- package/packages/@monomind/cli/dist/src/commands/browse.d.ts +1 -1
- package/packages/@monomind/cli/dist/src/commands/doctor-env-checks.js +5 -0
- package/packages/@monomind/cli/dist/src/commands/org.js +46 -29
- package/packages/@monomind/cli/dist/src/commands/platforms.d.ts +1 -1
- package/packages/@monomind/cli/dist/src/init/executor.js +30 -6
- package/packages/@monomind/cli/dist/src/mcp-tools/auto-install.d.ts +8 -8
- package/packages/@monomind/cli/dist/src/mcp-tools/coherence/types.d.ts +31 -31
- package/packages/@monomind/cli/dist/src/mcp-tools/hive-mind-tools.js +5 -1
- package/packages/@monomind/cli/dist/src/mcp-tools/monograph-tools.js +28 -3
- package/packages/@monomind/cli/dist/src/mcp-tools/quality/coverage-analysis/prioritize-gaps.d.ts +36 -36
- package/packages/@monomind/cli/dist/src/mcp-tools/quality/security-compliance/detect-secrets.d.ts +4 -4
- package/packages/@monomind/cli/dist/src/memory/ewc-consolidation.d.ts +12 -0
- package/packages/@monomind/cli/dist/src/memory/sona-optimizer.d.ts +12 -0
- package/packages/@monomind/cli/dist/src/orgrt/broker.d.ts +3 -1
- package/packages/@monomind/cli/dist/src/orgrt/broker.js +8 -3
- package/packages/@monomind/cli/dist/src/orgrt/bus.d.ts +5 -1
- package/packages/@monomind/cli/dist/src/orgrt/bus.js +5 -1
- package/packages/@monomind/cli/dist/src/orgrt/daemon.d.ts +35 -1
- package/packages/@monomind/cli/dist/src/orgrt/daemon.js +190 -21
- package/packages/@monomind/cli/dist/src/orgrt/forwarder.js +47 -10
- package/packages/@monomind/cli/dist/src/orgrt/inbox.d.ts +11 -0
- package/packages/@monomind/cli/dist/src/orgrt/inbox.js +56 -0
- package/packages/@monomind/cli/dist/src/orgrt/policy.d.ts +5 -1
- package/packages/@monomind/cli/dist/src/orgrt/policy.js +58 -5
- package/packages/@monomind/cli/dist/src/orgrt/server.d.ts +3 -2
- package/packages/@monomind/cli/dist/src/orgrt/server.js +31 -40
- package/packages/@monomind/cli/dist/src/orgrt/session.d.ts +11 -1
- package/packages/@monomind/cli/dist/src/orgrt/session.js +32 -1
- package/packages/@monomind/cli/dist/src/orgrt/test-loop.js +8 -8
- package/packages/@monomind/cli/dist/src/orgrt/types.d.ts +53 -53
- package/packages/@monomind/cli/dist/src/output.d.ts +29 -29
- package/packages/@monomind/cli/dist/src/output.js +10 -2
- package/packages/@monomind/cli/dist/src/pricing/model-pricing.d.ts +1 -1
- package/packages/@monomind/cli/dist/src/types.d.ts +2 -2
- package/packages/@monomind/cli/dist/src/ui/collector.mjs +20 -5
- package/packages/@monomind/cli/dist/src/ui/dashboard.html +465 -500
- package/packages/@monomind/cli/dist/src/ui/orgs-files.js +192 -0
- package/packages/@monomind/cli/dist/src/ui/orgs.html +21 -52
- package/packages/@monomind/cli/dist/src/ui/server.mjs +174 -164
- package/packages/@monomind/cli/package.json +12 -16
- package/packages/@monomind/cli/scripts/publish.sh +6 -0
- package/scripts/generate-agent-avatars.mjs +3 -3
- package/packages/@monomind/cli/dist/src/orgrt/live.html +0 -56
|
@@ -9,6 +9,9 @@ import { collectAll, getWatchPaths, collectProject, collectSessions, collectSwar
|
|
|
9
9
|
import { addSseClient, removeSseClient, broadcast, getSseClientCount, closeSseClients, addMmClient, removeMmClient, broadcastMm, getMmClientCount } from './sse-manager.mjs';
|
|
10
10
|
|
|
11
11
|
const JSONL_SIZE_CAP = 10 * 1024 * 1024; // 10 MB — skip files larger than this in /api/graph
|
|
12
|
+
// Session id format for data/sessions/<id>.jsonl persistence — no path traversal (".."), starts
|
|
13
|
+
// with a word char, rest is word chars/dot/dash. Shared by every session-id-accepting endpoint.
|
|
14
|
+
const SESSION_ID_RE = /^(?!.*\.\.)[a-zA-Z0-9_][a-zA-Z0-9_.-]*$/;
|
|
12
15
|
const buildDocsState = new Map();
|
|
13
16
|
|
|
14
17
|
// Pricing per token (mirrors token-tracker.cjs FALLBACK_PRICING)
|
|
@@ -478,7 +481,9 @@ function _detectMimeType(filePath) {
|
|
|
478
481
|
'.json': 'application/json', '.md': 'text/markdown', '.txt': 'text/plain',
|
|
479
482
|
'.html': 'text/html', '.css': 'text/css', '.py': 'text/x-python',
|
|
480
483
|
'.sh': 'text/x-shellscript', '.yaml': 'text/yaml', '.yml': 'text/yaml',
|
|
481
|
-
'.toml': 'text/plain', '.env': 'text/plain', '.xml': 'text/xml'
|
|
484
|
+
'.toml': 'text/plain', '.env': 'text/plain', '.xml': 'text/xml',
|
|
485
|
+
'.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg',
|
|
486
|
+
'.gif': 'image/gif', '.svg': 'image/svg+xml', '.webp': 'image/webp' };
|
|
482
487
|
return map[ext] || 'application/octet-stream';
|
|
483
488
|
}
|
|
484
489
|
|
|
@@ -608,10 +613,26 @@ function bindServer(server, port) {
|
|
|
608
613
|
* uses a greedy BFS over the real filesystem to find the correct path.
|
|
609
614
|
* Falls back to cwd in session files, then to direct slug replacement.
|
|
610
615
|
*/
|
|
616
|
+
const _slugPathCache = new Map();
|
|
617
|
+
|
|
611
618
|
function resolveSlugToPath(slug, projDir) {
|
|
612
|
-
|
|
619
|
+
if (_slugPathCache.has(slug)) return _slugPathCache.get(slug);
|
|
620
|
+
const resolved = _resolveSlugToPathUncached(slug, projDir);
|
|
621
|
+
_slugPathCache.set(slug, resolved);
|
|
622
|
+
return resolved;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
function _resolveSlugToPathUncached(slug, projDir) {
|
|
626
|
+
// 1. Try filesystem BFS (most reliable). Branching is O(2^hyphens) in the
|
|
627
|
+
// worst case (a slug with N hyphens can require exploring both "new path
|
|
628
|
+
// segment" and "hyphen is part of this segment's name" at each of the N
|
|
629
|
+
// positions) — bound total exploration so a slug with many hyphens (e.g.
|
|
630
|
+
// a UUID-embedded temp/scratchpad dir) can't hang the event loop.
|
|
613
631
|
const parts = slug.replace(/^-/, '').split('-');
|
|
632
|
+
const MAX_CALLS = 20000;
|
|
633
|
+
let calls = 0;
|
|
614
634
|
function tryPaths(idx, current) {
|
|
635
|
+
if (++calls > MAX_CALLS) return null;
|
|
615
636
|
if (idx === parts.length) return fs.existsSync(current) ? current : null;
|
|
616
637
|
// Option A: next part is a new path component
|
|
617
638
|
const asDir = path.join(current, parts[idx]);
|
|
@@ -690,7 +711,7 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
690
711
|
// Extracted from the request dispatcher to reduce cyclomatic complexity.
|
|
691
712
|
// Handles POST /api/mastermind/event: parses body, enriches with runId/session,
|
|
692
713
|
// persists to JSONL files, broadcasts to SSE clients, returns {ok:true}.
|
|
693
|
-
async function handleMastermindEvent(req, res) {
|
|
714
|
+
async function handleMastermindEvent(req, res, corsOrigin) {
|
|
694
715
|
let body = '';
|
|
695
716
|
for await (const chunk of req) { body += chunk; if (body.length > 2097152) { req.destroy(); break; } }
|
|
696
717
|
let event = {};
|
|
@@ -887,7 +908,7 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
887
908
|
// Format: data/sessions/<sessionId>.jsonl + data/sessions/_index.json
|
|
888
909
|
try {
|
|
889
910
|
const _sid = String(event.session || '').trim();
|
|
890
|
-
if (_sid.length > 0 && _sid.length <= 128 &&
|
|
911
|
+
if (_sid.length > 0 && _sid.length <= 128 && SESSION_ID_RE.test(_sid)) {
|
|
891
912
|
const sessDir = path.join(dataDir, 'sessions');
|
|
892
913
|
fs.mkdirSync(sessDir, { recursive: true });
|
|
893
914
|
// Append event to per-session JSONL (O(1), no read)
|
|
@@ -1011,7 +1032,13 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
1011
1032
|
if (req.method === 'GET' && url === '/') {
|
|
1012
1033
|
const htmlPath = path.join(__dirname, 'dashboard.html');
|
|
1013
1034
|
try {
|
|
1014
|
-
|
|
1035
|
+
let html = fs.readFileSync(htmlPath, 'utf8');
|
|
1036
|
+
// Inject this process's auth credential so the page's own fetch() calls can
|
|
1037
|
+
// attach it (see the fetch-wrapper near the top of dashboard.html's <script>
|
|
1038
|
+
// block) — every non-GET route above requires it, but the served HTML was
|
|
1039
|
+
// previously never given a way to know it, so every write action 401'd from
|
|
1040
|
+
// the actual browser UI.
|
|
1041
|
+
html = html.replace('<head>', `<head>\n<meta name="mm-token" content="${dashboardAuthValue}">`);
|
|
1015
1042
|
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
1016
1043
|
res.end(html);
|
|
1017
1044
|
} catch (err) {
|
|
@@ -4721,6 +4748,122 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
|
|
|
4721
4748
|
return;
|
|
4722
4749
|
}
|
|
4723
4750
|
|
|
4751
|
+
// GET /api/questions?dir=<projectDir> — list unanswered ask_human questions for
|
|
4752
|
+
// every org in one project. Mirrors the existing -approvals.json sidecar convention,
|
|
4753
|
+
// but reads this feature's .monomind/orgs/<org>/questions.json files (one per org dir).
|
|
4754
|
+
if (req.method === 'GET' && url === '/api/questions') {
|
|
4755
|
+
try {
|
|
4756
|
+
const _qDir = new URL(req.url, 'http://localhost').searchParams.get('dir') || projectDir || process.cwd();
|
|
4757
|
+
const base = path.join(path.resolve(_qDir), '.monomind', 'orgs');
|
|
4758
|
+
const out = [];
|
|
4759
|
+
if (fs.existsSync(base)) {
|
|
4760
|
+
for (const orgName of fs.readdirSync(base)) {
|
|
4761
|
+
const qFile = path.join(base, orgName, 'questions.json');
|
|
4762
|
+
if (!fs.existsSync(qFile)) continue;
|
|
4763
|
+
let data = { questions: [] };
|
|
4764
|
+
try { data = JSON.parse(fs.readFileSync(qFile, 'utf8')); } catch (_) {}
|
|
4765
|
+
for (const q of (data.questions || [])) {
|
|
4766
|
+
if (q.answer === null || q.answer === undefined) out.push({ org: orgName, ...q });
|
|
4767
|
+
}
|
|
4768
|
+
}
|
|
4769
|
+
}
|
|
4770
|
+
res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) });
|
|
4771
|
+
res.end(JSON.stringify({ questions: out }));
|
|
4772
|
+
} catch (_e) { res.writeHead(500); res.end('{"questions":[]}'); }
|
|
4773
|
+
return;
|
|
4774
|
+
}
|
|
4775
|
+
|
|
4776
|
+
// POST /api/questions/answer — forward a human's answer to the org's live process
|
|
4777
|
+
// (looked up via the same file-based broker registry orgrt's cross-process delivery
|
|
4778
|
+
// already uses), or fail with a clear error if no process anywhere hosts it.
|
|
4779
|
+
if (req.method === 'POST' && url === '/api/questions/answer') {
|
|
4780
|
+
let body = '';
|
|
4781
|
+
for await (const chunk of req) { body += chunk; if (body.length > 2097152) { req.destroy(); break; } }
|
|
4782
|
+
try {
|
|
4783
|
+
const parsed = JSON.parse(body);
|
|
4784
|
+
const { dir, org, role, questionId, answer } = parsed;
|
|
4785
|
+
if (!org || !role || !questionId || answer === undefined) {
|
|
4786
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
4787
|
+
res.end(JSON.stringify({ ok: false, error: 'org, role, questionId, answer are required' }));
|
|
4788
|
+
return;
|
|
4789
|
+
}
|
|
4790
|
+
if (String(org).length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(String(org))) {
|
|
4791
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
4792
|
+
res.end(JSON.stringify({ ok: false, error: 'Invalid org name' }));
|
|
4793
|
+
return;
|
|
4794
|
+
}
|
|
4795
|
+
const brokerDir = path.join(os.homedir(), '.monomind', 'orgrt-broker');
|
|
4796
|
+
const entryPath = path.join(brokerDir, `${org}.json`);
|
|
4797
|
+
let hostUrl = null;
|
|
4798
|
+
try {
|
|
4799
|
+
const entry = JSON.parse(fs.readFileSync(entryPath, 'utf8'));
|
|
4800
|
+
if (Date.now() - entry.updatedAt < 90000) hostUrl = entry.url;
|
|
4801
|
+
} catch (_) {}
|
|
4802
|
+
if (!hostUrl) {
|
|
4803
|
+
// No live process to forward to — the control server has no daemon instance of
|
|
4804
|
+
// its own to call autoWake() on. If the org's definition still exists on disk,
|
|
4805
|
+
// queue the answer the same way inbox.ts's queueMessage()/drainInbox() already
|
|
4806
|
+
// do for offline cross-org messages, so it's delivered whenever the org next
|
|
4807
|
+
// starts (manually or via its own schedule) — matching the offline-delivery goal
|
|
4808
|
+
// for the dashboard path, not just the direct-daemon path Task 4 already covers.
|
|
4809
|
+
const projDir = path.resolve(dir || projectDir || process.cwd());
|
|
4810
|
+
const orgDefFile = path.join(projDir, '.monomind', 'orgs', `${org}.json`);
|
|
4811
|
+
if (!fs.existsSync(orgDefFile)) {
|
|
4812
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
4813
|
+
res.end(JSON.stringify({ ok: false, error: `org "${org}" not found — no running process and no saved definition` }));
|
|
4814
|
+
return;
|
|
4815
|
+
}
|
|
4816
|
+
const qFile = path.join(projDir, '.monomind', 'orgs', org, 'questions.json');
|
|
4817
|
+
let qData = { questions: [] };
|
|
4818
|
+
try { qData = JSON.parse(fs.readFileSync(qFile, 'utf8')); } catch (_) {}
|
|
4819
|
+
const qIdx = (qData.questions || []).findIndex(q => q.questionId === questionId);
|
|
4820
|
+
if (qIdx === -1) {
|
|
4821
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
4822
|
+
res.end(JSON.stringify({ ok: false, error: `question "${questionId}" not found for org "${org}"` }));
|
|
4823
|
+
return;
|
|
4824
|
+
}
|
|
4825
|
+
if (qData.questions[qIdx].answer !== null && qData.questions[qIdx].answer !== undefined) {
|
|
4826
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
4827
|
+
res.end(JSON.stringify({ ok: true, alreadyAnswered: true }));
|
|
4828
|
+
return;
|
|
4829
|
+
}
|
|
4830
|
+
const answeredQuestion = qData.questions[qIdx];
|
|
4831
|
+
qData.questions[qIdx] = { ...answeredQuestion, answer, answeredAt: Date.now() };
|
|
4832
|
+
const qTmp = `${qFile}.tmp`;
|
|
4833
|
+
fs.writeFileSync(qTmp, JSON.stringify(qData, null, 2));
|
|
4834
|
+
fs.renameSync(qTmp, qFile);
|
|
4835
|
+
const inboxDir = path.join(projDir, '.monomind', 'orgs', org);
|
|
4836
|
+
fs.mkdirSync(inboxDir, { recursive: true });
|
|
4837
|
+
fs.appendFileSync(path.join(inboxDir, 'inbox.jsonl'), JSON.stringify({
|
|
4838
|
+
fromQualified: 'human', toRole: role, subject: `answer:${questionId}`, body: `question: ${answeredQuestion.question}\n\nanswer: ${answer}`, ts: Date.now(),
|
|
4839
|
+
}) + '\n');
|
|
4840
|
+
const queuedEvent = { type: 'org:question-answered', org, role, questionId, ts: Date.now(), queued: true };
|
|
4841
|
+
appendToFile(path.join(projDir, 'data', 'mastermind-events.jsonl'), JSON.stringify(queuedEvent) + '\n').catch(() => {});
|
|
4842
|
+
broadcastMm(queuedEvent);
|
|
4843
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
4844
|
+
res.end(JSON.stringify({ ok: true, queued: true }));
|
|
4845
|
+
return;
|
|
4846
|
+
}
|
|
4847
|
+
const fwd = await fetch(`${hostUrl}/api/answer-question`, {
|
|
4848
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
4849
|
+
body: JSON.stringify({ org, role, questionId, answer }),
|
|
4850
|
+
signal: AbortSignal.timeout(5000),
|
|
4851
|
+
});
|
|
4852
|
+
const fwdData = await fwd.json().catch(() => ({}));
|
|
4853
|
+
if (fwd.ok && fwdData.ok) {
|
|
4854
|
+
const event = { type: 'org:question-answered', org, role, questionId, ts: Date.now() };
|
|
4855
|
+
appendToFile(path.join(path.resolve(dir || projectDir || process.cwd()), 'data', 'mastermind-events.jsonl'), JSON.stringify(event) + '\n').catch(() => {});
|
|
4856
|
+
broadcastMm(event);
|
|
4857
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
4858
|
+
res.end(JSON.stringify({ ok: true }));
|
|
4859
|
+
} else {
|
|
4860
|
+
res.writeHead(fwd.status || 500, { 'Content-Type': 'application/json' });
|
|
4861
|
+
res.end(JSON.stringify({ ok: false, error: fwdData.error || 'answer delivery failed' }));
|
|
4862
|
+
}
|
|
4863
|
+
} catch (_e) { res.writeHead(500); res.end('{"ok":false}'); }
|
|
4864
|
+
return;
|
|
4865
|
+
}
|
|
4866
|
+
|
|
4724
4867
|
// POST /api/org/:name/approvals/:id — approve or reject a pending approval request
|
|
4725
4868
|
// Body: { action: "approve" | "reject" | "revision_requested" }
|
|
4726
4869
|
if (req.method === 'POST' && url.match(/^\/api\/org\/[a-z0-9][a-z0-9_-]{0,63}\/approvals\/[^/]+$/i)) {
|
|
@@ -5113,6 +5256,12 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
|
|
|
5113
5256
|
const stat = fs.statSync(resolved);
|
|
5114
5257
|
if (!stat.isFile()) { res.writeHead(400); res.end('Not a file'); return; }
|
|
5115
5258
|
if (stat.size > 524288) { res.writeHead(413); res.end('File too large'); return; }
|
|
5259
|
+
const _fcMime = _detectMimeType(resolved);
|
|
5260
|
+
if (_fcMime.startsWith('image/')) {
|
|
5261
|
+
res.writeHead(200, { 'Content-Type': _fcMime, ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) });
|
|
5262
|
+
res.end(fs.readFileSync(resolved));
|
|
5263
|
+
return;
|
|
5264
|
+
}
|
|
5116
5265
|
const content = fs.readFileSync(resolved, 'utf8');
|
|
5117
5266
|
res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) });
|
|
5118
5267
|
res.end(content);
|
|
@@ -5397,7 +5546,7 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
|
|
|
5397
5546
|
// ------------------------------------------------- Mastermind event system
|
|
5398
5547
|
// POST /api/mastermind/event — ingest event from mastermind skill
|
|
5399
5548
|
if (req.method === 'POST' && url === '/api/mastermind/event') {
|
|
5400
|
-
return handleMastermindEvent(req, res);
|
|
5549
|
+
return handleMastermindEvent(req, res, corsOrigin);
|
|
5401
5550
|
}
|
|
5402
5551
|
|
|
5403
5552
|
// GET /api/mastermind-stream — SSE for real-time events
|
|
@@ -5449,7 +5598,7 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
|
|
|
5449
5598
|
const top = idx.slice(0, limitParam);
|
|
5450
5599
|
for (const entry of top) {
|
|
5451
5600
|
const _sid = String(entry.id || '').trim();
|
|
5452
|
-
if (!_sid ||
|
|
5601
|
+
if (!_sid || !SESSION_ID_RE.test(_sid)) continue;
|
|
5453
5602
|
let events = [];
|
|
5454
5603
|
try {
|
|
5455
5604
|
const jl = fs.readFileSync(path.join(sessDir, `${_sid}.jsonl`), 'utf8');
|
|
@@ -5557,6 +5706,23 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
|
|
|
5557
5706
|
return;
|
|
5558
5707
|
}
|
|
5559
5708
|
|
|
5709
|
+
// ------------------------------------------------ GET /orgs-files.js
|
|
5710
|
+
// Files-tab + diff-view script, split out of orgs.html (see orgs.html's
|
|
5711
|
+
// script-src comment). Not a generic static handler — this UI serves each
|
|
5712
|
+
// sibling asset via its own hardcoded route, same as GET /orgs above.
|
|
5713
|
+
if (req.method === 'GET' && url === '/orgs-files.js') {
|
|
5714
|
+
try {
|
|
5715
|
+
const jsPath = path.join(__dirname, 'orgs-files.js');
|
|
5716
|
+
const js = fs.readFileSync(jsPath, 'utf8');
|
|
5717
|
+
res.writeHead(200, { 'Content-Type': 'application/javascript; charset=utf-8' });
|
|
5718
|
+
res.end(js);
|
|
5719
|
+
} catch (err) {
|
|
5720
|
+
res.writeHead(404);
|
|
5721
|
+
res.end(`orgs-files.js not found: ${err.message}`);
|
|
5722
|
+
}
|
|
5723
|
+
return;
|
|
5724
|
+
}
|
|
5725
|
+
|
|
5560
5726
|
// GET /api/mastermind/loops — list all active loop state files
|
|
5561
5727
|
if (req.method === 'GET' && url === '/api/mastermind/loops') {
|
|
5562
5728
|
try {
|
|
@@ -5668,162 +5834,6 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
|
|
|
5668
5834
|
return;
|
|
5669
5835
|
}
|
|
5670
5836
|
|
|
5671
|
-
// ----------------------------------------------- GET /api/monoagent/platforms
|
|
5672
|
-
// Returns all supported platforms from `monoes connect list --all --json`
|
|
5673
|
-
if (req.method === 'GET' && url === '/api/monoagent/platforms') {
|
|
5674
|
-
try {
|
|
5675
|
-
const { execFile } = await import('child_process');
|
|
5676
|
-
const out = await new Promise((resolve, reject) => {
|
|
5677
|
-
execFile('monoes', ['connect', 'list', '--all', '--json'], { timeout: 8000 }, (err, stdout) => {
|
|
5678
|
-
if (err) reject(err); else resolve(stdout);
|
|
5679
|
-
});
|
|
5680
|
-
});
|
|
5681
|
-
// Parse + re-serialize to ensure only valid JSON reaches the client
|
|
5682
|
-
// (monoes may emit warning lines before the JSON array)
|
|
5683
|
-
let parsed; try { parsed = JSON.parse(out); } catch (_) { parsed = []; }
|
|
5684
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
5685
|
-
res.end(JSON.stringify(Array.isArray(parsed) ? parsed : []));
|
|
5686
|
-
} catch (e) { res.writeHead(200); res.end('[]'); }
|
|
5687
|
-
return;
|
|
5688
|
-
}
|
|
5689
|
-
|
|
5690
|
-
// ----------------------------------------------- GET /api/monoagent/connections
|
|
5691
|
-
// Returns active API/OAuth connections + browser sessions merged
|
|
5692
|
-
if (req.method === 'GET' && url === '/api/monoagent/connections') {
|
|
5693
|
-
try {
|
|
5694
|
-
const { execFile } = await import('child_process');
|
|
5695
|
-
const [connsOut, sessOut] = await Promise.all([
|
|
5696
|
-
new Promise((resolve) => {
|
|
5697
|
-
execFile('monoes', ['connect', 'list', '--json'], { timeout: 8000 }, (err, stdout) => resolve(err ? '[]' : stdout));
|
|
5698
|
-
}),
|
|
5699
|
-
new Promise((resolve) => {
|
|
5700
|
-
execFile('monoes', ['--json', 'login', 'status'], { timeout: 8000 }, (err, stdout) => resolve(err ? '[]' : stdout));
|
|
5701
|
-
}),
|
|
5702
|
-
]);
|
|
5703
|
-
let connections = []; try { connections = JSON.parse(connsOut); } catch (_) {}
|
|
5704
|
-
let sessions = []; try { sessions = JSON.parse(sessOut); } catch (_) {}
|
|
5705
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
5706
|
-
res.end(JSON.stringify({ connections, sessions }));
|
|
5707
|
-
} catch (e) { res.writeHead(200); res.end(JSON.stringify({ connections: [], sessions: [] })); }
|
|
5708
|
-
return;
|
|
5709
|
-
}
|
|
5710
|
-
|
|
5711
|
-
// ----------------------------------------------- POST /api/monoagent/login
|
|
5712
|
-
// Launches browser login for social platforms via `monoes login <platform>`
|
|
5713
|
-
if (req.method === 'POST' && url === '/api/monoagent/login') {
|
|
5714
|
-
try {
|
|
5715
|
-
let body = '';
|
|
5716
|
-
await new Promise((resolve, reject) => { req.on('data', d => { body += d; if (body.length > 65536) { req.destroy(); reject(new Error('body too large')); } }); req.on('end', resolve); req.on('error', reject); });
|
|
5717
|
-
const { id } = JSON.parse(body);
|
|
5718
|
-
if (!id || !/^[a-z][a-z0-9_-]*$/.test(id)) { res.writeHead(400); res.end(JSON.stringify({ error: 'invalid id' })); return; }
|
|
5719
|
-
const { spawn } = await import('child_process');
|
|
5720
|
-
const child = spawn('monoes', ['login', id, '--timeout', '10m'], { detached: true, stdio: 'ignore' });
|
|
5721
|
-
// Defer response until spawn confirms or errors — prevents race where error fires after res.end()
|
|
5722
|
-
child.once('spawn', () => {
|
|
5723
|
-
child.unref();
|
|
5724
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
5725
|
-
res.end(JSON.stringify({ ok: true }));
|
|
5726
|
-
});
|
|
5727
|
-
child.once('error', (err) => {
|
|
5728
|
-
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
5729
|
-
res.end(JSON.stringify({ error: err.message }));
|
|
5730
|
-
});
|
|
5731
|
-
} catch (e) { res.writeHead(500); res.end(JSON.stringify({ error: e.message })); }
|
|
5732
|
-
return;
|
|
5733
|
-
}
|
|
5734
|
-
|
|
5735
|
-
// ----------------------------------------------- POST /api/monoagent/connect
|
|
5736
|
-
if (req.method === 'POST' && url === '/api/monoagent/connect') {
|
|
5737
|
-
try {
|
|
5738
|
-
let body = '';
|
|
5739
|
-
await new Promise((resolve, reject) => { req.on('data', d => { body += d; if (body.length > 65536) { req.destroy(); reject(new Error('body too large')); } }); req.on('end', resolve); req.on('error', reject); });
|
|
5740
|
-
const { id, method, fields } = JSON.parse(body);
|
|
5741
|
-
if (!id || !/^[a-z][a-z0-9_-]*$/.test(id)) { res.writeHead(400); res.end(JSON.stringify({ error: 'invalid id' })); return; }
|
|
5742
|
-
if (method && !/^[a-z][a-z0-9_-]*$/.test(method)) { res.writeHead(400); res.end(JSON.stringify({ error: 'invalid method' })); return; }
|
|
5743
|
-
const { execFile } = await import('child_process');
|
|
5744
|
-
const args = ['connect', id];
|
|
5745
|
-
if (method) args.push('--method', method);
|
|
5746
|
-
if (fields && typeof fields === 'object') {
|
|
5747
|
-
for (const [k, v] of Object.entries(fields)) {
|
|
5748
|
-
// Only allow simple word keys — prevents --flag injection
|
|
5749
|
-
if (!/^[a-z][a-z0-9_]*$/.test(k)) continue;
|
|
5750
|
-
args.push(`--${k}`, String(v).slice(0, 2048));
|
|
5751
|
-
}
|
|
5752
|
-
}
|
|
5753
|
-
await new Promise((resolve, reject) => {
|
|
5754
|
-
execFile('monoes', args, { timeout: 30000 }, (err, stdout, stderr) => {
|
|
5755
|
-
const ok = !err;
|
|
5756
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
5757
|
-
res.end(JSON.stringify({ ok, stdout: (stdout || '').trim(), stderr: (stderr || '').trim() }));
|
|
5758
|
-
resolve();
|
|
5759
|
-
});
|
|
5760
|
-
});
|
|
5761
|
-
} catch (e) { res.writeHead(500); res.end(JSON.stringify({ error: e.message })); }
|
|
5762
|
-
return;
|
|
5763
|
-
}
|
|
5764
|
-
|
|
5765
|
-
// ----------------------------------------------- POST /api/monoagent/test
|
|
5766
|
-
if (req.method === 'POST' && url === '/api/monoagent/test') {
|
|
5767
|
-
try {
|
|
5768
|
-
let body = '';
|
|
5769
|
-
await new Promise((resolve, reject) => { req.on('data', d => { body += d; if (body.length > 65536) { req.destroy(); reject(new Error('body too large')); } }); req.on('end', resolve); req.on('error', reject); });
|
|
5770
|
-
const { id } = JSON.parse(body);
|
|
5771
|
-
if (!id || !/^[a-z0-9][a-z0-9_:-]*$/.test(id)) { res.writeHead(400); res.end(JSON.stringify({ error: 'id required' })); return; }
|
|
5772
|
-
const { execFile } = await import('child_process');
|
|
5773
|
-
await new Promise((resolve, reject) => {
|
|
5774
|
-
execFile('monoes', ['connect', 'test', id], { timeout: 15000 }, (err, stdout, stderr) => {
|
|
5775
|
-
if (err) reject(new Error(stderr || err.message)); else resolve(stdout);
|
|
5776
|
-
});
|
|
5777
|
-
});
|
|
5778
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
5779
|
-
res.end(JSON.stringify({ ok: true }));
|
|
5780
|
-
} catch (e) { res.writeHead(200); res.end(JSON.stringify({ ok: false, error: e.message })); }
|
|
5781
|
-
return;
|
|
5782
|
-
}
|
|
5783
|
-
|
|
5784
|
-
// ----------------------------------------------- POST /api/monoagent/disconnect
|
|
5785
|
-
if (req.method === 'POST' && url === '/api/monoagent/disconnect') {
|
|
5786
|
-
try {
|
|
5787
|
-
let body = '';
|
|
5788
|
-
await new Promise((resolve, reject) => { req.on('data', d => { body += d; if (body.length > 65536) { req.destroy(); reject(new Error('body too large')); } }); req.on('end', resolve); req.on('error', reject); });
|
|
5789
|
-
const { id, type } = JSON.parse(body);
|
|
5790
|
-
if (!id || !/^[a-z0-9][a-z0-9_:-]*$/.test(id)) { res.writeHead(400); res.end(JSON.stringify({ error: 'id required' })); return; }
|
|
5791
|
-
if (type !== 'session' && type !== 'connection') { res.writeHead(400); res.end(JSON.stringify({ error: 'invalid type' })); return; }
|
|
5792
|
-
const { execFile } = await import('child_process');
|
|
5793
|
-
const cmd = type === 'session' ? ['logout', id] : ['connect', 'remove', id];
|
|
5794
|
-
await new Promise((resolve, reject) => {
|
|
5795
|
-
execFile('monoes', cmd, { timeout: 10000 }, (err, _stdout, stderr) => {
|
|
5796
|
-
if (err) reject(new Error(stderr || err.message)); else resolve();
|
|
5797
|
-
});
|
|
5798
|
-
});
|
|
5799
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
5800
|
-
res.end(JSON.stringify({ ok: true }));
|
|
5801
|
-
} catch (e) { res.writeHead(200); res.end(JSON.stringify({ ok: false, error: e.message })); }
|
|
5802
|
-
return;
|
|
5803
|
-
}
|
|
5804
|
-
|
|
5805
|
-
// ----------------------------------------------- GET /api/monoagent/data
|
|
5806
|
-
if (req.method === 'GET' && url === '/api/monoagent/data') {
|
|
5807
|
-
try {
|
|
5808
|
-
const { execFile } = await import('child_process');
|
|
5809
|
-
const run = (args) => new Promise(resolve => {
|
|
5810
|
-
execFile('monoagent', args, { timeout: 8000 }, (err, stdout) => resolve(err ? '[]' : stdout));
|
|
5811
|
-
});
|
|
5812
|
-
const [wfOut, actOut] = await Promise.all([
|
|
5813
|
-
run(['workflow', 'list', '--json']),
|
|
5814
|
-
run(['action', 'list', '--json']),
|
|
5815
|
-
]);
|
|
5816
|
-
const workflows = JSON.parse(wfOut.trim() || '[]');
|
|
5817
|
-
const actions = JSON.parse(actOut.trim() || '[]');
|
|
5818
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
5819
|
-
res.end(JSON.stringify({ workflows, actions }));
|
|
5820
|
-
} catch (e) {
|
|
5821
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
5822
|
-
res.end(JSON.stringify({ workflows: [], actions: [], error: e.message }));
|
|
5823
|
-
}
|
|
5824
|
-
return;
|
|
5825
|
-
}
|
|
5826
|
-
|
|
5827
5837
|
// ------------------------------------------------- GET /api/playbooks
|
|
5828
5838
|
// List playbook definitions from <dir>/.monomind/playbooks/*.json.
|
|
5829
5839
|
// (Previously only POST was registered, so GET /api/playbooks?dir=... fell
|
|
@@ -6193,7 +6203,7 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
|
|
|
6193
6203
|
const _migIndex = [];
|
|
6194
6204
|
for (const sess of (_migOld || [])) {
|
|
6195
6205
|
const _msid = String(sess.id || '').trim();
|
|
6196
|
-
if (!_msid ||
|
|
6206
|
+
if (!_msid || !SESSION_ID_RE.test(_msid)) continue;
|
|
6197
6207
|
// Write per-session JSONL
|
|
6198
6208
|
const _mEvts = (sess.events || []);
|
|
6199
6209
|
const _mLines = _mEvts.map(e => JSON.stringify(e)).join('\n');
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@monoes/monomindcli",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.4",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "Monomind
|
|
5
|
+
"description": "CLI engine for Monomind — an open-source MCP server that extends Claude Code with a codebase knowledge graph (tree-sitter + SQLite), persistent memory, multi-agent task coordination, and session hooks. MIT licensed, fully local.",
|
|
6
6
|
"main": "dist/src/index.js",
|
|
7
7
|
"types": "dist/src/index.d.ts",
|
|
8
8
|
"sideEffects": false,
|
|
@@ -27,21 +27,17 @@
|
|
|
27
27
|
"claude",
|
|
28
28
|
"claude-code",
|
|
29
29
|
"anthropic",
|
|
30
|
-
"ai-agents",
|
|
31
|
-
"multi-agent",
|
|
32
|
-
"swarm",
|
|
33
30
|
"mcp",
|
|
34
31
|
"model-context-protocol",
|
|
35
|
-
"
|
|
32
|
+
"knowledge-graph",
|
|
33
|
+
"tree-sitter",
|
|
34
|
+
"code-analysis",
|
|
35
|
+
"ai-agents",
|
|
36
|
+
"multi-agent",
|
|
36
37
|
"cli",
|
|
37
|
-
"orchestration",
|
|
38
|
-
"automation",
|
|
39
38
|
"developer-tools",
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"embeddings",
|
|
43
|
-
"self-learning",
|
|
44
|
-
"enterprise"
|
|
39
|
+
"open-source",
|
|
40
|
+
"local-first"
|
|
45
41
|
],
|
|
46
42
|
"author": {
|
|
47
43
|
"name": "nokhodian",
|
|
@@ -83,8 +79,8 @@
|
|
|
83
79
|
"README.md"
|
|
84
80
|
],
|
|
85
81
|
"scripts": {
|
|
86
|
-
"build": "tsc && mkdir -p dist/src/browser/dashboard && cp src/browser/dashboard/ui.html dist/src/browser/dashboard/ui.html && mkdir -p dist/src/
|
|
87
|
-
"build:loose": "tsc --noEmitOnError false || true && mkdir -p dist/src/browser/dashboard && cp src/browser/dashboard/ui.html dist/src/browser/dashboard/ui.html && mkdir -p dist/src/
|
|
82
|
+
"build": "tsc && mkdir -p dist/src/browser/dashboard && cp src/browser/dashboard/ui.html dist/src/browser/dashboard/ui.html && mkdir -p dist/src/ui && cp src/ui/dashboard.html src/ui/server.mjs src/ui/collector.mjs src/ui/orgs.html src/ui/orgs-files.js src/ui/sse-manager.mjs dist/src/ui/ && rm -rf dist/src/ui/data && cp -r src/ui/data dist/src/ui/data",
|
|
83
|
+
"build:loose": "tsc --noEmitOnError false || true && mkdir -p dist/src/browser/dashboard && cp src/browser/dashboard/ui.html dist/src/browser/dashboard/ui.html && mkdir -p dist/src/ui && cp src/ui/dashboard.html src/ui/server.mjs src/ui/collector.mjs src/ui/orgs.html src/ui/orgs-files.js src/ui/sse-manager.mjs dist/src/ui/ && rm -rf dist/src/ui/data && cp -r src/ui/data dist/src/ui/data",
|
|
88
84
|
"test": "vitest run",
|
|
89
85
|
"test:pattern-store": "npx tsx src/transfer/store/tests/standalone-test.ts",
|
|
90
86
|
"prepublishOnly": "cp ../../../README.md ./README.md",
|
|
@@ -92,7 +88,7 @@
|
|
|
92
88
|
"publish:all": "./scripts/publish.sh"
|
|
93
89
|
},
|
|
94
90
|
"devDependencies": {
|
|
95
|
-
"typescript": "^
|
|
91
|
+
"typescript": "^7.0.2",
|
|
96
92
|
"vitest": "^4.1.4"
|
|
97
93
|
},
|
|
98
94
|
"dependencies": {
|
|
@@ -9,6 +9,12 @@ CLI_DIR="$(dirname "$SCRIPT_DIR")"
|
|
|
9
9
|
|
|
10
10
|
cd "$CLI_DIR"
|
|
11
11
|
|
|
12
|
+
# dist/src/ui/* (dashboard, server, etc.) is hand-authored source under src/ui/
|
|
13
|
+
# copied into dist/ by the build step — it is no longer committed to git, so a
|
|
14
|
+
# stale or missing dist/ here would silently ship a broken/absent dashboard.
|
|
15
|
+
echo "=== Building ==="
|
|
16
|
+
npm run build
|
|
17
|
+
|
|
12
18
|
# Get current version
|
|
13
19
|
VERSION=$(node -p "require('./package.json').version")
|
|
14
20
|
echo "Publishing version: $VERSION"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
3
|
* Generates 120 unique SVG agent avatar files + a sprite-sheet HTML.
|
|
4
|
-
* Output: packages/@monomind/cli/
|
|
4
|
+
* Output: packages/@monomind/cli/src/ui/data/avatars/
|
|
5
5
|
* Run: node scripts/generate-agent-avatars.mjs
|
|
6
6
|
*/
|
|
7
7
|
|
|
@@ -10,7 +10,7 @@ import path from 'path';
|
|
|
10
10
|
import { fileURLToPath } from 'url';
|
|
11
11
|
|
|
12
12
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
13
|
-
const OUT_DIR = path.resolve(__dirname, '../packages/@monomind/cli/
|
|
13
|
+
const OUT_DIR = path.resolve(__dirname, '../packages/@monomind/cli/src/ui/data/avatars');
|
|
14
14
|
fs.mkdirSync(OUT_DIR, { recursive: true });
|
|
15
15
|
|
|
16
16
|
// ─── Palettes ────────────────────────────────────────────────────────────────
|
|
@@ -489,4 +489,4 @@ const spritePath = path.join(OUT_DIR, '../agent-avatars.html');
|
|
|
489
489
|
fs.writeFileSync(spritePath, spriteHtml, 'utf8');
|
|
490
490
|
console.log(`✅ Sprite-sheet viewer: data/agent-avatars.html`);
|
|
491
491
|
console.log(`\n🎉 Done! ${AGENTS120.length} avatars ready.`);
|
|
492
|
-
console.log(` Open: packages/@monomind/cli/
|
|
492
|
+
console.log(` Open: packages/@monomind/cli/src/ui/data/agent-avatars.html`);
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
<!-- packages/@monomind/cli/src/orgrt/live.html -->
|
|
2
|
-
<!doctype html>
|
|
3
|
-
<html><head><meta charset="utf-8"><title>org live</title>
|
|
4
|
-
<style>
|
|
5
|
-
:root { color-scheme: dark; }
|
|
6
|
-
body { margin:0; font:13px/1.5 ui-monospace,monospace; background:#0d1117; color:#e6edf3;
|
|
7
|
-
display:grid; grid-template-columns:220px 1fr 1fr; grid-template-rows:auto 1fr 1fr; gap:1px; height:100vh; }
|
|
8
|
-
header { grid-column:1/4; padding:8px 14px; background:#161b22; font-weight:700; }
|
|
9
|
-
section { background:#161b22; overflow:auto; padding:10px; }
|
|
10
|
-
h2 { font-size:11px; text-transform:uppercase; letter-spacing:.1em; color:#7d8590; margin:0 0 8px; }
|
|
11
|
-
#agents { grid-row:2/4; }
|
|
12
|
-
.agent { padding:6px 8px; border-left:3px solid #2f81f7; margin-bottom:6px; background:#0d1117; }
|
|
13
|
-
.agent small { color:#7d8590; display:block; }
|
|
14
|
-
.row { padding:4px 6px; border-bottom:1px solid #21262d; word-break:break-word; }
|
|
15
|
-
.from { color:#2f81f7; font-weight:700; } .to { color:#a371f7; }
|
|
16
|
-
.deny { color:#f85149; } .allow { color:#3fb950; } .xorg { color:#d29922; font-weight:700; }
|
|
17
|
-
.ts { color:#484f58; font-size:11px; margin-right:6px; }
|
|
18
|
-
</style></head>
|
|
19
|
-
<body>
|
|
20
|
-
<header>org live — <span id="conn">connecting…</span></header>
|
|
21
|
-
<section id="agents"><h2>Agents</h2><div id="agents-list"></div></section>
|
|
22
|
-
<section id="chat"><h2>Chats & Messages</h2><div id="chat-list"></div></section>
|
|
23
|
-
<section id="tools"><h2>Tool Activity & Assets</h2><div id="tools-list"></div></section>
|
|
24
|
-
<script>
|
|
25
|
-
const $ = id => document.getElementById(id);
|
|
26
|
-
const fmt = ts => new Date(ts).toLocaleTimeString();
|
|
27
|
-
const add = (listId, html, cap = 500) => {
|
|
28
|
-
const el = document.createElement('div'); el.className = 'row'; el.innerHTML = html;
|
|
29
|
-
const list = $(listId); list.prepend(el);
|
|
30
|
-
while (list.children.length > cap) list.lastChild.remove();
|
|
31
|
-
};
|
|
32
|
-
const esc = s => String(s ?? '').replace(/[&<>]/g, c => ({'&':'&','<':'<','>':'>'}[c]));
|
|
33
|
-
const decisionClass = d => (d === 'allow' || d === 'deny') ? d : 'unknown';
|
|
34
|
-
function render(e) {
|
|
35
|
-
const t = `<span class="ts">${fmt(e.ts)}</span>`;
|
|
36
|
-
if (e.type === 'chat') add('chat-list', `${t}<span class="from">${esc(e.org)}/${esc(e.from)}</span>: ${esc(e.msg)}`);
|
|
37
|
-
else if (e.type === 'message') add('chat-list', `${t}<span class="from">${esc(e.from)}</span> → <span class="to">${esc(e.to)}</span> [${esc(e.subject||'')}] ${esc(e.msg)}`);
|
|
38
|
-
else if (e.type === 'xorg') add('chat-list', `${t}<span class="xorg">⇄ ${esc(e.from)} → ${esc(e.to)}</span> [${esc(e.subject||'')}] ${esc(e.msg)}`);
|
|
39
|
-
else if (e.type === 'tool') add('tools-list', `${t}<span class="from">${esc(e.from)}</span> ${esc(e.tool)} <span class="${decisionClass(e.decision)}">${esc(e.decision)}</span>${e.reason ? ' — ' + esc(e.reason) : ''}`);
|
|
40
|
-
else if (e.type === 'asset') add('tools-list', `${t}<span class="from">${esc(e.from)}</span> 📄 <b>${esc(e.path)}</b>`);
|
|
41
|
-
else if (e.type === 'status' || e.type === 'usage') add('tools-list', `${t}<i>${esc(e.org)}/${esc(e.from ?? 'org')}: ${esc(e.msg ?? JSON.stringify(e.data))}</i>`);
|
|
42
|
-
}
|
|
43
|
-
async function refreshAgents() {
|
|
44
|
-
const orgs = await fetch('/api/orgs').then(r => r.json()).catch(() => []);
|
|
45
|
-
$('agents-list').innerHTML = orgs.map(o =>
|
|
46
|
-
`<div class="agent"><b>${esc(o.name)}</b><small>${esc(o.run)}</small>` +
|
|
47
|
-
o.agents.map(a => `<div>${a.closed ? '⚫' : '🟢'} ${esc(a.id)} <small>${esc(a.usage)} tok</small></div>`).join('') +
|
|
48
|
-
`</div>`).join('') || '<i>no running orgs</i>';
|
|
49
|
-
}
|
|
50
|
-
const ws = new WebSocket(`ws://${location.host}/ws`);
|
|
51
|
-
ws.onopen = () => { $('conn').textContent = 'live'; refreshAgents(); };
|
|
52
|
-
ws.onclose = () => { $('conn').textContent = 'disconnected'; };
|
|
53
|
-
ws.onmessage = ev => { render(JSON.parse(ev.data)); };
|
|
54
|
-
setInterval(refreshAgents, 5000);
|
|
55
|
-
</script>
|
|
56
|
-
</body></html>
|