@monoes/monomindcli 2.16.12 → 2.16.13
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/.claude/agents/core/coder.md +1 -1
- package/.claude/agents/engineering/engineering-devops-automator.md +1 -1
- package/.claude/agents/github/repo-architect.md +1 -1
- package/.claude/agents/testing/production-validator.md +1 -1
- package/.claude/agents/testing/testing-tool-evaluator.md +1 -1
- package/.claude/commands/github/pr-manager.md +1 -1
- package/.claude/helpers/pick-rank.cjs +34 -4
- package/.claude/skills/monolean-audit/SKILL.md +1 -1
- package/dist/src/commands/cleanup-data.d.ts +30 -0
- package/dist/src/commands/cleanup-data.d.ts.map +1 -0
- package/dist/src/commands/cleanup-data.js +138 -0
- package/dist/src/commands/cleanup-data.js.map +1 -0
- package/dist/src/commands/cleanup-scratch.d.ts +43 -0
- package/dist/src/commands/cleanup-scratch.d.ts.map +1 -0
- package/dist/src/commands/cleanup-scratch.js +125 -0
- package/dist/src/commands/cleanup-scratch.js.map +1 -0
- package/dist/src/commands/cleanup.d.ts +2 -39
- package/dist/src/commands/cleanup.d.ts.map +1 -1
- package/dist/src/commands/cleanup.js +5 -247
- package/dist/src/commands/cleanup.js.map +1 -1
- package/dist/src/commands/doctor-gitignore-checks.d.ts +10 -0
- package/dist/src/commands/doctor-gitignore-checks.d.ts.map +1 -0
- package/dist/src/commands/doctor-gitignore-checks.js +141 -0
- package/dist/src/commands/doctor-gitignore-checks.js.map +1 -0
- package/dist/src/commands/doctor-helpers-checks.d.ts +10 -0
- package/dist/src/commands/doctor-helpers-checks.d.ts.map +1 -0
- package/dist/src/commands/doctor-helpers-checks.js +292 -0
- package/dist/src/commands/doctor-helpers-checks.js.map +1 -0
- package/dist/src/commands/doctor-memory-checks.d.ts +47 -0
- package/dist/src/commands/doctor-memory-checks.d.ts.map +1 -0
- package/dist/src/commands/doctor-memory-checks.js +298 -0
- package/dist/src/commands/doctor-memory-checks.js.map +1 -0
- package/dist/src/commands/doctor-monograph-checks.d.ts +9 -0
- package/dist/src/commands/doctor-monograph-checks.d.ts.map +1 -0
- package/dist/src/commands/doctor-monograph-checks.js +331 -0
- package/dist/src/commands/doctor-monograph-checks.js.map +1 -0
- package/dist/src/commands/doctor-project-checks.d.ts +7 -66
- package/dist/src/commands/doctor-project-checks.d.ts.map +1 -1
- package/dist/src/commands/doctor-project-checks.js +10 -1331
- package/dist/src/commands/doctor-project-checks.js.map +1 -1
- package/dist/src/commands/doctor-routing-checks.d.ts +10 -0
- package/dist/src/commands/doctor-routing-checks.d.ts.map +1 -0
- package/dist/src/commands/doctor-routing-checks.js +194 -0
- package/dist/src/commands/doctor-routing-checks.js.map +1 -0
- package/dist/src/commands/doctor-worker-checks.d.ts +17 -0
- package/dist/src/commands/doctor-worker-checks.d.ts.map +1 -0
- package/dist/src/commands/doctor-worker-checks.js +122 -0
- package/dist/src/commands/doctor-worker-checks.js.map +1 -0
- package/dist/src/commands/hooks-routing-commands.js +1 -1
- package/dist/src/commands/hooks-routing-commands.js.map +1 -1
- package/dist/src/commands/init.d.ts.map +1 -1
- package/dist/src/commands/init.js +6 -15
- package/dist/src/commands/init.js.map +1 -1
- package/dist/src/index.js +2 -2
- package/dist/src/index.js.map +1 -1
- package/dist/src/init/never-commit.d.ts +3 -3
- package/dist/src/init/types.d.ts +1 -1
- package/dist/src/init/types.js +1 -1
- package/dist/src/knowledge/document-store.js +1 -1
- package/dist/src/knowledge/document-store.js.map +1 -1
- package/dist/src/mcp-tools/hooks-edit-command.d.ts +10 -0
- package/dist/src/mcp-tools/hooks-edit-command.d.ts.map +1 -0
- package/dist/src/mcp-tools/hooks-edit-command.js +247 -0
- package/dist/src/mcp-tools/hooks-edit-command.js.map +1 -0
- package/dist/src/mcp-tools/hooks-learning.d.ts +9 -0
- package/dist/src/mcp-tools/hooks-learning.d.ts.map +1 -0
- package/dist/src/mcp-tools/hooks-learning.js +408 -0
- package/dist/src/mcp-tools/hooks-learning.js.map +1 -0
- package/dist/src/mcp-tools/hooks-metrics-list.d.ts +8 -0
- package/dist/src/mcp-tools/hooks-metrics-list.d.ts.map +1 -0
- package/dist/src/mcp-tools/hooks-metrics-list.js +205 -0
- package/dist/src/mcp-tools/hooks-metrics-list.js.map +1 -0
- package/dist/src/mcp-tools/hooks-route.d.ts +12 -0
- package/dist/src/mcp-tools/hooks-route.d.ts.map +1 -0
- package/dist/src/mcp-tools/hooks-route.js +251 -0
- package/dist/src/mcp-tools/hooks-route.js.map +1 -0
- package/dist/src/mcp-tools/hooks-routing.d.ts +6 -27
- package/dist/src/mcp-tools/hooks-routing.d.ts.map +1 -1
- package/dist/src/mcp-tools/hooks-routing.js +6 -1727
- package/dist/src/mcp-tools/hooks-routing.js.map +1 -1
- package/dist/src/mcp-tools/hooks-session.d.ts +9 -0
- package/dist/src/mcp-tools/hooks-session.d.ts.map +1 -0
- package/dist/src/mcp-tools/hooks-session.js +243 -0
- package/dist/src/mcp-tools/hooks-session.js.map +1 -0
- package/dist/src/mcp-tools/hooks-task.d.ts +17 -0
- package/dist/src/mcp-tools/hooks-task.d.ts.map +1 -0
- package/dist/src/mcp-tools/hooks-task.js +427 -0
- package/dist/src/mcp-tools/hooks-task.js.map +1 -0
- package/dist/src/mcp-tools/hooks-tools.d.ts +2 -1
- package/dist/src/mcp-tools/hooks-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/hooks-tools.js +2 -1
- package/dist/src/mcp-tools/hooks-tools.js.map +1 -1
- package/dist/src/mcp-tools/memory-tools.js +1 -1
- package/dist/src/mcp-tools/memory-tools.js.map +1 -1
- package/dist/src/memory/bm25-index.d.ts +2 -2
- package/dist/src/memory/bm25-index.js +2 -2
- package/dist/src/memory/embedding-operations.js +2 -2
- package/dist/src/memory/embedding-operations.js.map +1 -1
- package/dist/src/memory/entity-name-key.d.ts +1 -1
- package/dist/src/memory/entity-name-key.js +1 -1
- package/dist/src/memory/hnsw-operations.d.ts +1 -1
- package/dist/src/memory/hnsw-operations.js +1 -1
- package/dist/src/memory/memory-bridge-backend.d.ts +43 -0
- package/dist/src/memory/memory-bridge-backend.d.ts.map +1 -0
- package/dist/src/memory/memory-bridge-backend.js +363 -0
- package/dist/src/memory/memory-bridge-backend.js.map +1 -0
- package/dist/src/memory/memory-bridge-core.d.ts +42 -0
- package/dist/src/memory/memory-bridge-core.d.ts.map +1 -0
- package/dist/src/memory/memory-bridge-core.js +126 -0
- package/dist/src/memory/memory-bridge-core.js.map +1 -0
- package/dist/src/memory/memory-bridge-entries.d.ts +131 -0
- package/dist/src/memory/memory-bridge-entries.d.ts.map +1 -0
- package/dist/src/memory/memory-bridge-entries.js +227 -0
- package/dist/src/memory/memory-bridge-entries.js.map +1 -0
- package/dist/src/memory/memory-bridge-feedback.d.ts +60 -0
- package/dist/src/memory/memory-bridge-feedback.d.ts.map +1 -0
- package/dist/src/memory/memory-bridge-feedback.js +118 -0
- package/dist/src/memory/memory-bridge-feedback.js.map +1 -0
- package/dist/src/memory/memory-bridge-paths.d.ts +123 -0
- package/dist/src/memory/memory-bridge-paths.d.ts.map +1 -0
- package/dist/src/memory/memory-bridge-paths.js +335 -0
- package/dist/src/memory/memory-bridge-paths.js.map +1 -0
- package/dist/src/memory/memory-bridge-search.d.ts +48 -0
- package/dist/src/memory/memory-bridge-search.d.ts.map +1 -0
- package/dist/src/memory/memory-bridge-search.js +383 -0
- package/dist/src/memory/memory-bridge-search.js.map +1 -0
- package/dist/src/memory/memory-bridge-sessions.d.ts +38 -0
- package/dist/src/memory/memory-bridge-sessions.d.ts.map +1 -0
- package/dist/src/memory/memory-bridge-sessions.js +109 -0
- package/dist/src/memory/memory-bridge-sessions.js.map +1 -0
- package/dist/src/memory/memory-bridge-store.d.ts +58 -0
- package/dist/src/memory/memory-bridge-store.d.ts.map +1 -0
- package/dist/src/memory/memory-bridge-store.js +201 -0
- package/dist/src/memory/memory-bridge-store.js.map +1 -0
- package/dist/src/memory/memory-bridge.d.ts +10 -438
- package/dist/src/memory/memory-bridge.d.ts.map +1 -1
- package/dist/src/memory/memory-bridge.js +14 -1787
- package/dist/src/memory/memory-bridge.js.map +1 -1
- package/dist/src/memory/memory-kg-claims.d.ts +52 -0
- package/dist/src/memory/memory-kg-claims.d.ts.map +1 -0
- package/dist/src/memory/memory-kg-claims.js +99 -0
- package/dist/src/memory/memory-kg-claims.js.map +1 -0
- package/dist/src/memory/memory-kg-index.d.ts +86 -0
- package/dist/src/memory/memory-kg-index.d.ts.map +1 -0
- package/dist/src/memory/memory-kg-index.js +256 -0
- package/dist/src/memory/memory-kg-index.js.map +1 -0
- package/dist/src/memory/memory-kg-ingest.d.ts +44 -0
- package/dist/src/memory/memory-kg-ingest.d.ts.map +1 -0
- package/dist/src/memory/memory-kg-ingest.js +361 -0
- package/dist/src/memory/memory-kg-ingest.js.map +1 -0
- package/dist/src/memory/memory-kg-model.d.ts +168 -0
- package/dist/src/memory/memory-kg-model.d.ts.map +1 -0
- package/dist/src/memory/memory-kg-model.js +158 -0
- package/dist/src/memory/memory-kg-model.js.map +1 -0
- package/dist/src/memory/memory-kg-names.d.ts +64 -0
- package/dist/src/memory/memory-kg-names.d.ts.map +1 -0
- package/dist/src/memory/memory-kg-names.js +187 -0
- package/dist/src/memory/memory-kg-names.js.map +1 -0
- package/dist/src/memory/memory-kg-promote.d.ts +79 -0
- package/dist/src/memory/memory-kg-promote.d.ts.map +1 -0
- package/dist/src/memory/memory-kg-promote.js +248 -0
- package/dist/src/memory/memory-kg-promote.js.map +1 -0
- package/dist/src/memory/memory-kg-rebuild.d.ts +77 -0
- package/dist/src/memory/memory-kg-rebuild.d.ts.map +1 -0
- package/dist/src/memory/memory-kg-rebuild.js +299 -0
- package/dist/src/memory/memory-kg-rebuild.js.map +1 -0
- package/dist/src/memory/memory-kg-rollback.d.ts +47 -0
- package/dist/src/memory/memory-kg-rollback.d.ts.map +1 -0
- package/dist/src/memory/memory-kg-rollback.js +193 -0
- package/dist/src/memory/memory-kg-rollback.js.map +1 -0
- package/dist/src/memory/memory-kg-rules.d.ts +64 -0
- package/dist/src/memory/memory-kg-rules.d.ts.map +1 -0
- package/dist/src/memory/memory-kg-rules.js +251 -0
- package/dist/src/memory/memory-kg-rules.js.map +1 -0
- package/dist/src/memory/memory-kg-scan.d.ts +118 -0
- package/dist/src/memory/memory-kg-scan.d.ts.map +1 -0
- package/dist/src/memory/memory-kg-scan.js +209 -0
- package/dist/src/memory/memory-kg-scan.js.map +1 -0
- package/dist/src/memory/memory-kg-search.d.ts +80 -0
- package/dist/src/memory/memory-kg-search.d.ts.map +1 -0
- package/dist/src/memory/memory-kg-search.js +239 -0
- package/dist/src/memory/memory-kg-search.js.map +1 -0
- package/dist/src/memory/memory-kg.d.ts +19 -547
- package/dist/src/memory/memory-kg.d.ts.map +1 -1
- package/dist/src/memory/memory-kg.js +10 -2389
- package/dist/src/memory/memory-kg.js.map +1 -1
- package/dist/src/ui/org-hil.mjs +1 -1
- package/dist/src/ui/routes-org-agents.mjs +318 -0
- package/dist/src/ui/routes-org-approvals.mjs +133 -0
- package/dist/src/ui/routes-org-config.mjs +459 -0
- package/dist/src/ui/routes-org-control.mjs +300 -0
- package/dist/src/ui/routes-org-files.mjs +172 -0
- package/dist/src/ui/routes-org-helpers.mjs +84 -0
- package/dist/src/ui/routes-org-knowledge.mjs +262 -0
- package/dist/src/ui/routes-org-lifecycle.mjs +224 -0
- package/dist/src/ui/routes-org-live.mjs +278 -0
- package/dist/src/ui/routes-org-mastermind.mjs +346 -0
- package/dist/src/ui/routes-org-planning.mjs +398 -0
- package/dist/src/ui/routes-org-runs.mjs +430 -0
- package/dist/src/ui/routes-org-status.mjs +342 -0
- package/dist/src/ui/routes-org-workflows.mjs +212 -0
- package/dist/src/ui/routes-org.mjs +26 -3847
- package/dist/src/ui/server.mjs +1 -1
- package/dist/src/utils/native-error.d.ts +1 -1
- package/dist/src/utils/native-error.js +1 -1
- package/dist/src/utils/paths.d.ts +2 -1
- package/dist/src/utils/paths.d.ts.map +1 -1
- package/dist/src/utils/paths.js +2 -1
- package/dist/src/utils/paths.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/org-skills/adversarial-reviewer/SKILL.md +1 -1
- package/org-skills/board-deck-builder/SKILL.md +1 -1
- package/org-skills/code-reviewer/SKILL.md +1 -1
- package/org-skills/debugging-wizard/SKILL.md +1 -1
- package/org-skills/scientific-thinking-literature-review/SKILL.md +1 -1
- package/org-skills/scientific-thinking-scholar-evaluation/SKILL.md +1 -1
- package/package.json +8 -8
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import * as hil from './org-hil.mjs';
|
|
4
|
+
|
|
5
|
+
// Org dashboard routes: mark-complete, current-run stream and chat.
|
|
6
|
+
// Registered, in order, by handleOrgRoutes in routes-org.mjs.
|
|
7
|
+
export async function handleOrgControlRoutes(req, res, url, corsOrigin, ctx) {
|
|
8
|
+
// ---- POST /api/orgs/:name/mark-complete — manual STALE recovery ----
|
|
9
|
+
if (
|
|
10
|
+
req.method === 'POST' &&
|
|
11
|
+
/^\/api\/orgs\/[a-z0-9][a-z0-9_-]{0,63}\/mark-complete$/i.test(url)
|
|
12
|
+
) {
|
|
13
|
+
const _mcOrgName = decodeURIComponent(url.split('/')[3]);
|
|
14
|
+
if (_mcOrgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(_mcOrgName)) {
|
|
15
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
16
|
+
res.end(JSON.stringify({ error: 'Invalid org name' }));
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
const _mcRoot = ctx.projectDir || process.cwd();
|
|
20
|
+
const _mcMonoDir = ctx._getGitMonomindDir(_mcRoot) || path.join(_mcRoot, '.monomind');
|
|
21
|
+
const _mcRunId = ctx.activeOrgRuns.get(_mcOrgName) || ctx._getActiveRunId(_mcOrgName, _mcRoot);
|
|
22
|
+
if (!_mcRunId) {
|
|
23
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
24
|
+
res.end(JSON.stringify({ error: `No active run for org: ${_mcOrgName}` }));
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
const _mcEvent = {
|
|
28
|
+
type: 'run:complete',
|
|
29
|
+
org: _mcOrgName,
|
|
30
|
+
runId: _mcRunId,
|
|
31
|
+
ts: Date.now(),
|
|
32
|
+
reason: 'manual',
|
|
33
|
+
};
|
|
34
|
+
try {
|
|
35
|
+
const _mcRunFile = path.join(_mcMonoDir, 'orgs', _mcOrgName, 'runs', `${_mcRunId}.jsonl`);
|
|
36
|
+
if (fs.existsSync(_mcRunFile))
|
|
37
|
+
await ctx.appendToFile(_mcRunFile, `${JSON.stringify(_mcEvent)}\n`);
|
|
38
|
+
ctx.activeOrgRuns.delete(_mcOrgName);
|
|
39
|
+
// Clean up ppid-keyed active-run files for this org
|
|
40
|
+
const _mcCapDir = path.join(ctx.MONOMIND_HOME, '.monomind', 'capture');
|
|
41
|
+
try {
|
|
42
|
+
const _mcPpidDir = path.join(_mcCapDir, 'active-runs');
|
|
43
|
+
if (fs.existsSync(_mcPpidDir)) {
|
|
44
|
+
fs.readdirSync(_mcPpidDir)
|
|
45
|
+
.filter((f) => f.endsWith('.json'))
|
|
46
|
+
.forEach((_pf) => {
|
|
47
|
+
try {
|
|
48
|
+
const _pd = JSON.parse(fs.readFileSync(path.join(_mcPpidDir, _pf), 'utf8'));
|
|
49
|
+
if (_pd.org === _mcOrgName) fs.unlinkSync(path.join(_mcPpidDir, _pf));
|
|
50
|
+
} catch (_) {}
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
const _mcActiveFile = path.join(_mcCapDir, 'active-run.json');
|
|
54
|
+
if (fs.existsSync(_mcActiveFile)) {
|
|
55
|
+
try {
|
|
56
|
+
const _a = JSON.parse(fs.readFileSync(_mcActiveFile, 'utf8'));
|
|
57
|
+
if (_a.org === _mcOrgName) fs.unlinkSync(_mcActiveFile);
|
|
58
|
+
} catch (_) {}
|
|
59
|
+
}
|
|
60
|
+
} catch (_) {}
|
|
61
|
+
ctx._updateRunState(_mcEvent, _mcRoot);
|
|
62
|
+
ctx.broadcastMm(_mcEvent);
|
|
63
|
+
const _mcFwdClients = ctx.runStreamClients.get(_mcOrgName);
|
|
64
|
+
if (_mcFwdClients && _mcFwdClients.size > 0) {
|
|
65
|
+
const _mcLine = `data: ${JSON.stringify(_mcEvent)}\n\n`;
|
|
66
|
+
for (const _cl of _mcFwdClients) {
|
|
67
|
+
try {
|
|
68
|
+
_cl.write(_mcLine);
|
|
69
|
+
} catch (_) {
|
|
70
|
+
_mcFwdClients.delete(_cl);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
res.writeHead(200, {
|
|
75
|
+
'Content-Type': 'application/json',
|
|
76
|
+
...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}),
|
|
77
|
+
});
|
|
78
|
+
res.end(JSON.stringify({ ok: true, runId: _mcRunId }));
|
|
79
|
+
} catch (e) {
|
|
80
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
81
|
+
res.end(JSON.stringify({ error: e.message }));
|
|
82
|
+
}
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ---- GET /api/orgs/:name/runs/current/stream — Phase 3 streaming tail ----
|
|
87
|
+
if (
|
|
88
|
+
req.method === 'GET' &&
|
|
89
|
+
/^\/api\/orgs\/[a-z0-9][a-z0-9_-]{0,63}\/runs\/current\/stream$/i.test(url)
|
|
90
|
+
) {
|
|
91
|
+
const _stOrgName = decodeURIComponent(url.split('/')[3]);
|
|
92
|
+
if (_stOrgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(_stOrgName)) {
|
|
93
|
+
res.writeHead(400);
|
|
94
|
+
res.end('Invalid org name');
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
const _stQs = new URL(req.url, 'http://localhost').searchParams;
|
|
98
|
+
const _stSince = Math.max(0, parseInt(_stQs.get('since') || '0', 10) || 0);
|
|
99
|
+
res.writeHead(200, {
|
|
100
|
+
'Content-Type': 'text/event-stream',
|
|
101
|
+
'Cache-Control': 'no-cache',
|
|
102
|
+
Connection: 'keep-alive',
|
|
103
|
+
...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}),
|
|
104
|
+
'X-Accel-Buffering': 'no',
|
|
105
|
+
});
|
|
106
|
+
res.write(': connected\n\n');
|
|
107
|
+
// Register client for live events
|
|
108
|
+
if (!ctx.runStreamClients.has(_stOrgName)) ctx.runStreamClients.set(_stOrgName, new Set());
|
|
109
|
+
ctx.runStreamClients.get(_stOrgName).add(res);
|
|
110
|
+
// Replay events since `since` (SQLite row id cursor; falls back to JSONL line offset)
|
|
111
|
+
try {
|
|
112
|
+
let _stReplayedViaSqlite = false;
|
|
113
|
+
if (ctx._runDb) {
|
|
114
|
+
// SQLite path: cursor is last row id seen (client sends 0 on first connect)
|
|
115
|
+
const _stStmt = ctx._runDb.prepare(
|
|
116
|
+
'SELECT id, raw FROM run_events WHERE org=? AND id > ? ORDER BY id LIMIT 2000',
|
|
117
|
+
);
|
|
118
|
+
_stStmt.bind([_stOrgName, _stSince]);
|
|
119
|
+
let _stLastId = _stSince;
|
|
120
|
+
while (_stStmt.step()) {
|
|
121
|
+
const _stRow = _stStmt.getAsObject();
|
|
122
|
+
try {
|
|
123
|
+
res.write(`data: ${_stRow.raw}\n\n`);
|
|
124
|
+
_stLastId = _stRow.id;
|
|
125
|
+
} catch (_) {
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
_stStmt.free();
|
|
130
|
+
if (_stLastId > _stSince) {
|
|
131
|
+
// SQLite had rows — send cursor and skip JSONL fallback
|
|
132
|
+
res.write(
|
|
133
|
+
`data: ${JSON.stringify({ type: 'stream:replay-done', count: _stLastId })}\n\n`,
|
|
134
|
+
);
|
|
135
|
+
_stReplayedViaSqlite = true;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if (!_stReplayedViaSqlite) {
|
|
139
|
+
// JSONL fallback: SQLite absent or returned 0 rows — read directly from run file.
|
|
140
|
+
// `since` is a 0-based line offset in this path.
|
|
141
|
+
const _stRoot = ctx.projectDir || process.cwd();
|
|
142
|
+
const _stRunId =
|
|
143
|
+
ctx.activeOrgRuns.get(_stOrgName) || ctx._getActiveRunId(_stOrgName, _stRoot);
|
|
144
|
+
if (_stRunId) {
|
|
145
|
+
const _stMono = ctx._getGitMonomindDir(_stRoot) || path.join(_stRoot, '.monomind');
|
|
146
|
+
const _stRunFile = path.join(_stMono, 'orgs', _stOrgName, 'runs', `${_stRunId}.jsonl`);
|
|
147
|
+
if (fs.existsSync(_stRunFile)) {
|
|
148
|
+
const _stLines = fs.readFileSync(_stRunFile, 'utf8').trim().split('\n').filter(Boolean);
|
|
149
|
+
for (let _i = _stSince; _i < _stLines.length; _i++) {
|
|
150
|
+
try {
|
|
151
|
+
res.write(`data: ${_stLines[_i]}\n\n`);
|
|
152
|
+
} catch (_) {
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
res.write(
|
|
157
|
+
`data: ${JSON.stringify({ type: 'stream:replay-done', count: _stLines.length })}\n\n`,
|
|
158
|
+
);
|
|
159
|
+
} else {
|
|
160
|
+
res.write(`data: ${JSON.stringify({ type: 'stream:replay-done', count: 0 })}\n\n`);
|
|
161
|
+
}
|
|
162
|
+
} else {
|
|
163
|
+
res.write(`data: ${JSON.stringify({ type: 'stream:replay-done', count: 0 })}\n\n`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
} catch (_) {}
|
|
167
|
+
const _stKa = setInterval(() => {
|
|
168
|
+
try {
|
|
169
|
+
res.write(': ping\n\n');
|
|
170
|
+
} catch (_) {
|
|
171
|
+
clearInterval(_stKa);
|
|
172
|
+
}
|
|
173
|
+
}, 20000);
|
|
174
|
+
req.on('close', () => {
|
|
175
|
+
clearInterval(_stKa);
|
|
176
|
+
const _stClients = ctx.runStreamClients.get(_stOrgName);
|
|
177
|
+
if (_stClients) {
|
|
178
|
+
_stClients.delete(res);
|
|
179
|
+
if (_stClients.size === 0) ctx.runStreamClients.delete(_stOrgName);
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
return true;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// ---- POST /api/orgs/:name/chat — user sends a message to a running org ----
|
|
186
|
+
if (req.method === 'POST' && /^\/api\/orgs\/[a-z0-9][a-z0-9_-]{0,63}\/chat$/i.test(url)) {
|
|
187
|
+
let _chBody = '';
|
|
188
|
+
for await (const chunk of req) {
|
|
189
|
+
_chBody += chunk;
|
|
190
|
+
if (_chBody.length > 65536) {
|
|
191
|
+
req.destroy();
|
|
192
|
+
break;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
try {
|
|
196
|
+
const _chOrgName = decodeURIComponent(url.split('/')[3]);
|
|
197
|
+
if (_chOrgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(_chOrgName)) {
|
|
198
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
199
|
+
res.end(JSON.stringify({ error: 'Invalid org name' }));
|
|
200
|
+
return true;
|
|
201
|
+
}
|
|
202
|
+
let _chPayload = {};
|
|
203
|
+
try {
|
|
204
|
+
_chPayload = JSON.parse(_chBody);
|
|
205
|
+
} catch (_) {}
|
|
206
|
+
const _chText = String(_chPayload.text || '')
|
|
207
|
+
.trim()
|
|
208
|
+
.slice(0, 4096);
|
|
209
|
+
if (!_chText) {
|
|
210
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
211
|
+
res.end(JSON.stringify({ error: 'text is required' }));
|
|
212
|
+
return true;
|
|
213
|
+
}
|
|
214
|
+
const _chQs = new URL(req.url, 'http://localhost').searchParams;
|
|
215
|
+
const _chServerRoot = path.resolve(_chQs.get('dir') || ctx.projectDir || process.cwd());
|
|
216
|
+
const _chRoot = ctx._resolveOrgProjectDir(_chOrgName, _chServerRoot) || _chServerRoot;
|
|
217
|
+
const _chMonoDir = ctx._getGitMonomindDir(_chRoot) || path.join(_chRoot, '.monomind');
|
|
218
|
+
const _chRunId =
|
|
219
|
+
ctx.activeOrgRuns.get(_chOrgName) || ctx._getActiveRunId(_chOrgName, _chRoot);
|
|
220
|
+
// Deliver to the target role (default: the root role) — live into its
|
|
221
|
+
// mailbox, or queued in the org's inbox for its next run.
|
|
222
|
+
let _chRoles = [];
|
|
223
|
+
try {
|
|
224
|
+
const _chCfg = JSON.parse(
|
|
225
|
+
fs.readFileSync(path.join(_chRoot, '.monomind', 'orgs', `${_chOrgName}.json`), 'utf8'),
|
|
226
|
+
);
|
|
227
|
+
if (Array.isArray(_chCfg.roles)) _chRoles = _chCfg.roles;
|
|
228
|
+
} catch (_) {}
|
|
229
|
+
const _chTargetRole =
|
|
230
|
+
typeof _chPayload.role === 'string' && _chPayload.role
|
|
231
|
+
? _chRoles.find((r) => r.id === _chPayload.role)?.id
|
|
232
|
+
: (_chRoles.find((r) => !r.reports_to) || _chRoles[0])?.id;
|
|
233
|
+
if (!_chTargetRole) {
|
|
234
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
235
|
+
res.end(
|
|
236
|
+
JSON.stringify({
|
|
237
|
+
ok: false,
|
|
238
|
+
error: _chPayload.role
|
|
239
|
+
? `org "${_chOrgName}" has no role "${_chPayload.role}"`
|
|
240
|
+
: `org "${_chOrgName}" has no role to receive it`,
|
|
241
|
+
}),
|
|
242
|
+
);
|
|
243
|
+
return true;
|
|
244
|
+
}
|
|
245
|
+
let _chDelivery;
|
|
246
|
+
try {
|
|
247
|
+
_chDelivery = (await hil.sendHumanMessage(_chRoot, _chOrgName, _chTargetRole, _chText))
|
|
248
|
+
.delivery;
|
|
249
|
+
} catch (err) {
|
|
250
|
+
const { status, error } = hil.hilErrorStatus(err);
|
|
251
|
+
res.writeHead(status, { 'Content-Type': 'application/json' });
|
|
252
|
+
res.end(JSON.stringify({ ok: false, error }));
|
|
253
|
+
return true;
|
|
254
|
+
}
|
|
255
|
+
const _chEvent = {
|
|
256
|
+
type: 'user:message',
|
|
257
|
+
org: _chOrgName,
|
|
258
|
+
runId: _chRunId || null,
|
|
259
|
+
text: _chText,
|
|
260
|
+
ts: Date.now(),
|
|
261
|
+
};
|
|
262
|
+
// Write to run JSONL if active run exists
|
|
263
|
+
if (_chRunId) {
|
|
264
|
+
const _chRunFile = path.join(_chMonoDir, 'orgs', _chOrgName, 'runs', `${_chRunId}.jsonl`);
|
|
265
|
+
if (fs.existsSync(_chRunFile))
|
|
266
|
+
await ctx.appendToFile(_chRunFile, `${JSON.stringify(_chEvent)}\n`);
|
|
267
|
+
}
|
|
268
|
+
// Broadcast to SSE stream clients
|
|
269
|
+
ctx.broadcastMm(_chEvent);
|
|
270
|
+
const _chFwdClients = ctx.runStreamClients.get(_chOrgName);
|
|
271
|
+
if (_chFwdClients && _chFwdClients.size > 0) {
|
|
272
|
+
const _chLine = `data: ${JSON.stringify(_chEvent)}\n\n`;
|
|
273
|
+
for (const _cl of _chFwdClients) {
|
|
274
|
+
try {
|
|
275
|
+
_cl.write(_chLine);
|
|
276
|
+
} catch (_) {
|
|
277
|
+
_chFwdClients.delete(_cl);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
res.writeHead(200, {
|
|
282
|
+
'Content-Type': 'application/json',
|
|
283
|
+
...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}),
|
|
284
|
+
});
|
|
285
|
+
res.end(
|
|
286
|
+
JSON.stringify({
|
|
287
|
+
ok: true,
|
|
288
|
+
delivered: _chDelivery === 'live',
|
|
289
|
+
delivery: _chDelivery,
|
|
290
|
+
role: _chTargetRole,
|
|
291
|
+
}),
|
|
292
|
+
);
|
|
293
|
+
} catch (e) {
|
|
294
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
295
|
+
res.end(JSON.stringify({ error: e.message }));
|
|
296
|
+
}
|
|
297
|
+
return true;
|
|
298
|
+
}
|
|
299
|
+
return false;
|
|
300
|
+
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
// Org dashboard routes: org file listing and file content.
|
|
5
|
+
// Registered, in order, by handleOrgRoutes in routes-org.mjs.
|
|
6
|
+
export async function handleOrgFileRoutes(req, res, url, corsOrigin, ctx) {
|
|
7
|
+
// GET /api/org/:name/files — all files related to an org
|
|
8
|
+
if (req.method === 'GET' && url.match(/^\/api\/org\/[a-z0-9][a-z0-9_-]{0,63}\/files$/i)) {
|
|
9
|
+
try {
|
|
10
|
+
const orgName = decodeURIComponent(url.split('/')[3]);
|
|
11
|
+
if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) {
|
|
12
|
+
res.writeHead(400);
|
|
13
|
+
res.end('{"error":"Invalid org name"}');
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
const _filesQs = new URL(req.url, 'http://localhost').searchParams;
|
|
17
|
+
const d = path.resolve(_filesQs.get('dir') || ctx.projectDir || process.cwd());
|
|
18
|
+
const orgsDir = path.join(d, '.monomind', 'orgs');
|
|
19
|
+
const files = [];
|
|
20
|
+
const seen = new Set();
|
|
21
|
+
const addFile = (fp, type) => {
|
|
22
|
+
if (seen.has(fp)) return;
|
|
23
|
+
seen.add(fp);
|
|
24
|
+
try {
|
|
25
|
+
const st = fs.statSync(fp);
|
|
26
|
+
files.push({
|
|
27
|
+
name: path.basename(fp),
|
|
28
|
+
path: fp,
|
|
29
|
+
type,
|
|
30
|
+
size: st.size,
|
|
31
|
+
mtime: st.mtime.toISOString(),
|
|
32
|
+
});
|
|
33
|
+
} catch (_) {}
|
|
34
|
+
};
|
|
35
|
+
addFile(path.join(orgsDir, `${orgName}.json`), 'config');
|
|
36
|
+
for (const s of [
|
|
37
|
+
'-state',
|
|
38
|
+
'-approvals',
|
|
39
|
+
'-goals',
|
|
40
|
+
'-routines',
|
|
41
|
+
'-projects',
|
|
42
|
+
'-members',
|
|
43
|
+
'-issues',
|
|
44
|
+
'-threads',
|
|
45
|
+
'-budgets',
|
|
46
|
+
]) {
|
|
47
|
+
const fp = path.join(orgsDir, `${orgName + s}.json`);
|
|
48
|
+
if (fs.existsSync(fp)) addFile(fp, s.slice(1));
|
|
49
|
+
}
|
|
50
|
+
const walkDir = (dir, depth) => {
|
|
51
|
+
if (depth > 3) return;
|
|
52
|
+
let entries;
|
|
53
|
+
try {
|
|
54
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
55
|
+
} catch (_) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
for (const e of entries) {
|
|
59
|
+
if (e.name.startsWith('.')) continue;
|
|
60
|
+
const fp = path.join(dir, e.name);
|
|
61
|
+
if (e.isDirectory()) walkDir(fp, depth + 1);
|
|
62
|
+
else addFile(fp, 'generated');
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
const orgWorkDir = path.join(orgsDir, orgName);
|
|
66
|
+
if (fs.existsSync(orgWorkDir)) walkDir(orgWorkDir, 0);
|
|
67
|
+
let orgCfg = null;
|
|
68
|
+
try {
|
|
69
|
+
orgCfg = JSON.parse(fs.readFileSync(path.join(orgsDir, `${orgName}.json`), 'utf8'));
|
|
70
|
+
} catch (_) {}
|
|
71
|
+
if (orgCfg && Array.isArray(orgCfg.roles)) {
|
|
72
|
+
const agentsDir = path.join(d, '.claude', 'agents');
|
|
73
|
+
const walkAgents = (dir) => {
|
|
74
|
+
let entries;
|
|
75
|
+
try {
|
|
76
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
77
|
+
} catch (_) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
for (const e of entries) {
|
|
81
|
+
if (e.isDirectory()) {
|
|
82
|
+
walkAgents(path.join(dir, e.name));
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
if (!e.name.endsWith('.md')) continue;
|
|
86
|
+
const fp = path.join(dir, e.name);
|
|
87
|
+
const base = e.name.replace('.md', '').toLowerCase();
|
|
88
|
+
if (
|
|
89
|
+
orgCfg.roles.some(
|
|
90
|
+
(r) =>
|
|
91
|
+
base === (r.id || '').toLowerCase() ||
|
|
92
|
+
base === (r.agent_type || '').toLowerCase() ||
|
|
93
|
+
(r.instructions_file || '').endsWith(e.name),
|
|
94
|
+
)
|
|
95
|
+
)
|
|
96
|
+
addFile(fp, 'agent-definition');
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
if (fs.existsSync(agentsDir)) walkAgents(agentsDir);
|
|
100
|
+
}
|
|
101
|
+
files.sort((a, b) => new Date(b.mtime) - new Date(a.mtime));
|
|
102
|
+
res.writeHead(200, {
|
|
103
|
+
'Content-Type': 'application/json',
|
|
104
|
+
...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}),
|
|
105
|
+
'Cache-Control': 'no-cache',
|
|
106
|
+
});
|
|
107
|
+
res.end(JSON.stringify(files));
|
|
108
|
+
} catch (e) {
|
|
109
|
+
res.writeHead(500);
|
|
110
|
+
res.end(JSON.stringify({ error: e.message }));
|
|
111
|
+
}
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// GET /api/file-content — return raw text content of a .monomind file
|
|
116
|
+
if (req.method === 'GET' && url === '/api/file-content') {
|
|
117
|
+
try {
|
|
118
|
+
const _fcQs = new URL(req.url, 'http://localhost').searchParams;
|
|
119
|
+
const rawPath = _fcQs.get('path');
|
|
120
|
+
const baseDir = path.resolve(_fcQs.get('dir') || ctx.projectDir || process.cwd());
|
|
121
|
+
if (!rawPath) {
|
|
122
|
+
res.writeHead(400);
|
|
123
|
+
res.end('Missing path');
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
const resolved = path.resolve(rawPath);
|
|
127
|
+
// Security: must be inside .monomind of the project dir
|
|
128
|
+
const monomindDir = path.join(baseDir, '.monomind');
|
|
129
|
+
if (!resolved.startsWith(monomindDir + path.sep) && resolved !== monomindDir) {
|
|
130
|
+
res.writeHead(403);
|
|
131
|
+
res.end('Forbidden');
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
if (!fs.existsSync(resolved)) {
|
|
135
|
+
res.writeHead(404);
|
|
136
|
+
res.end('Not found');
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
139
|
+
const stat = fs.statSync(resolved);
|
|
140
|
+
if (!stat.isFile()) {
|
|
141
|
+
res.writeHead(400);
|
|
142
|
+
res.end('Not a file');
|
|
143
|
+
return true;
|
|
144
|
+
}
|
|
145
|
+
if (stat.size > 524288) {
|
|
146
|
+
res.writeHead(413);
|
|
147
|
+
res.end('File too large');
|
|
148
|
+
return true;
|
|
149
|
+
}
|
|
150
|
+
const _fcMime = ctx._detectMimeType(resolved);
|
|
151
|
+
if (_fcMime.startsWith('image/')) {
|
|
152
|
+
res.writeHead(200, {
|
|
153
|
+
'Content-Type': _fcMime,
|
|
154
|
+
...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}),
|
|
155
|
+
});
|
|
156
|
+
res.end(fs.readFileSync(resolved));
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
159
|
+
const content = fs.readFileSync(resolved, 'utf8');
|
|
160
|
+
res.writeHead(200, {
|
|
161
|
+
'Content-Type': 'text/plain; charset=utf-8',
|
|
162
|
+
...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}),
|
|
163
|
+
});
|
|
164
|
+
res.end(content);
|
|
165
|
+
} catch (_) {
|
|
166
|
+
res.writeHead(500);
|
|
167
|
+
res.end('Internal error');
|
|
168
|
+
}
|
|
169
|
+
return true;
|
|
170
|
+
}
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import * as hil from './org-hil.mjs';
|
|
3
|
+
|
|
4
|
+
// Shared helpers for the org dashboard routes (routes-org*.mjs).
|
|
5
|
+
|
|
6
|
+
/** listApprovals for read-only views, where an unreadable file shows as none. */
|
|
7
|
+
export function approvalsOrEmpty(orgsDir, orgName) {
|
|
8
|
+
try {
|
|
9
|
+
return hil.listApprovals(path.dirname(path.dirname(orgsDir)), orgName);
|
|
10
|
+
} catch (_) {
|
|
11
|
+
return [];
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const validOrgName = (n) => typeof n === 'string' && /^[a-z0-9][a-z0-9_-]{0,63}$/i.test(n);
|
|
16
|
+
|
|
17
|
+
/** Project root for a HIL route: ?dir= or the server's project. */
|
|
18
|
+
export function hilRoot(req, ctx) {
|
|
19
|
+
const dir = new URL(req.url, 'http://localhost').searchParams.get('dir');
|
|
20
|
+
return path.resolve(dir || ctx.projectDir || process.cwd());
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const HIL_BODY_MAX = 65536;
|
|
24
|
+
|
|
25
|
+
/** JSON body (≤64KB); null when malformed. Call inside hilRespond: an
|
|
26
|
+
* oversized body throws 413 and an aborted upload 400, instead of an
|
|
27
|
+
* unhandled rejection that would take the dashboard process down. Bytes are
|
|
28
|
+
* decoded once at the end, so a character split across chunks survives. */
|
|
29
|
+
export async function readHilBody(req) {
|
|
30
|
+
const chunks = [];
|
|
31
|
+
let size = 0;
|
|
32
|
+
try {
|
|
33
|
+
// Past the cap, keep draining (without keeping) so the 413 still reaches
|
|
34
|
+
// the client — leaving the iterator early destroys the socket.
|
|
35
|
+
for await (const chunk of req) {
|
|
36
|
+
size += chunk.length;
|
|
37
|
+
if (size <= HIL_BODY_MAX) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
38
|
+
}
|
|
39
|
+
} catch (err) {
|
|
40
|
+
throw Object.assign(new Error(`request body not received: ${err.message}`), { status: 400 });
|
|
41
|
+
}
|
|
42
|
+
if (size > HIL_BODY_MAX)
|
|
43
|
+
throw Object.assign(new Error('request body is over 64KB'), { status: 413 });
|
|
44
|
+
try {
|
|
45
|
+
return JSON.parse(Buffer.concat(chunks).toString('utf8') || '{}');
|
|
46
|
+
} catch (_) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** decodeURIComponent for a path segment; a malformed escape is a 400. */
|
|
52
|
+
export function decodeHilSegment(seg) {
|
|
53
|
+
try {
|
|
54
|
+
return decodeURIComponent(seg);
|
|
55
|
+
} catch (_) {
|
|
56
|
+
throw Object.assign(new Error(`malformed path segment: ${seg}`), { status: 400 });
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Run `fn` and answer with its JSON result, or the HIL error's status. */
|
|
61
|
+
export async function hilRespond(res, corsOrigin, fn) {
|
|
62
|
+
const cors = corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {};
|
|
63
|
+
try {
|
|
64
|
+
const data = await fn();
|
|
65
|
+
res.writeHead(200, { 'Content-Type': 'application/json', ...cors });
|
|
66
|
+
res.end(JSON.stringify(data));
|
|
67
|
+
} catch (err) {
|
|
68
|
+
const { status, error } = err.status
|
|
69
|
+
? { status: err.status, error: err.message }
|
|
70
|
+
: hil.hilErrorStatus(err);
|
|
71
|
+
res.writeHead(status, { 'Content-Type': 'application/json', ...cors });
|
|
72
|
+
res.end(JSON.stringify({ ok: false, error }));
|
|
73
|
+
}
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Record a human decision in the dashboard's event log and push it to live views. */
|
|
78
|
+
export function hilEvent(ctx, root, ev) {
|
|
79
|
+
const event = { ...ev, resolvedBy: hil.DASHBOARD_RESOLVER, ts: Date.now() };
|
|
80
|
+
ctx
|
|
81
|
+
.appendToFile(path.join(root, 'data', 'mastermind-events.jsonl'), `${JSON.stringify(event)}\n`)
|
|
82
|
+
.catch(() => {});
|
|
83
|
+
ctx.broadcastMm(event);
|
|
84
|
+
}
|