@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.
Files changed (217) hide show
  1. package/.claude/agents/core/coder.md +1 -1
  2. package/.claude/agents/engineering/engineering-devops-automator.md +1 -1
  3. package/.claude/agents/github/repo-architect.md +1 -1
  4. package/.claude/agents/testing/production-validator.md +1 -1
  5. package/.claude/agents/testing/testing-tool-evaluator.md +1 -1
  6. package/.claude/commands/github/pr-manager.md +1 -1
  7. package/.claude/helpers/pick-rank.cjs +34 -4
  8. package/.claude/skills/monolean-audit/SKILL.md +1 -1
  9. package/dist/src/commands/cleanup-data.d.ts +30 -0
  10. package/dist/src/commands/cleanup-data.d.ts.map +1 -0
  11. package/dist/src/commands/cleanup-data.js +138 -0
  12. package/dist/src/commands/cleanup-data.js.map +1 -0
  13. package/dist/src/commands/cleanup-scratch.d.ts +43 -0
  14. package/dist/src/commands/cleanup-scratch.d.ts.map +1 -0
  15. package/dist/src/commands/cleanup-scratch.js +125 -0
  16. package/dist/src/commands/cleanup-scratch.js.map +1 -0
  17. package/dist/src/commands/cleanup.d.ts +2 -39
  18. package/dist/src/commands/cleanup.d.ts.map +1 -1
  19. package/dist/src/commands/cleanup.js +5 -247
  20. package/dist/src/commands/cleanup.js.map +1 -1
  21. package/dist/src/commands/doctor-gitignore-checks.d.ts +10 -0
  22. package/dist/src/commands/doctor-gitignore-checks.d.ts.map +1 -0
  23. package/dist/src/commands/doctor-gitignore-checks.js +141 -0
  24. package/dist/src/commands/doctor-gitignore-checks.js.map +1 -0
  25. package/dist/src/commands/doctor-helpers-checks.d.ts +10 -0
  26. package/dist/src/commands/doctor-helpers-checks.d.ts.map +1 -0
  27. package/dist/src/commands/doctor-helpers-checks.js +292 -0
  28. package/dist/src/commands/doctor-helpers-checks.js.map +1 -0
  29. package/dist/src/commands/doctor-memory-checks.d.ts +47 -0
  30. package/dist/src/commands/doctor-memory-checks.d.ts.map +1 -0
  31. package/dist/src/commands/doctor-memory-checks.js +298 -0
  32. package/dist/src/commands/doctor-memory-checks.js.map +1 -0
  33. package/dist/src/commands/doctor-monograph-checks.d.ts +9 -0
  34. package/dist/src/commands/doctor-monograph-checks.d.ts.map +1 -0
  35. package/dist/src/commands/doctor-monograph-checks.js +331 -0
  36. package/dist/src/commands/doctor-monograph-checks.js.map +1 -0
  37. package/dist/src/commands/doctor-project-checks.d.ts +7 -66
  38. package/dist/src/commands/doctor-project-checks.d.ts.map +1 -1
  39. package/dist/src/commands/doctor-project-checks.js +10 -1331
  40. package/dist/src/commands/doctor-project-checks.js.map +1 -1
  41. package/dist/src/commands/doctor-routing-checks.d.ts +10 -0
  42. package/dist/src/commands/doctor-routing-checks.d.ts.map +1 -0
  43. package/dist/src/commands/doctor-routing-checks.js +194 -0
  44. package/dist/src/commands/doctor-routing-checks.js.map +1 -0
  45. package/dist/src/commands/doctor-worker-checks.d.ts +17 -0
  46. package/dist/src/commands/doctor-worker-checks.d.ts.map +1 -0
  47. package/dist/src/commands/doctor-worker-checks.js +122 -0
  48. package/dist/src/commands/doctor-worker-checks.js.map +1 -0
  49. package/dist/src/commands/hooks-routing-commands.js +1 -1
  50. package/dist/src/commands/hooks-routing-commands.js.map +1 -1
  51. package/dist/src/commands/init.d.ts.map +1 -1
  52. package/dist/src/commands/init.js +6 -15
  53. package/dist/src/commands/init.js.map +1 -1
  54. package/dist/src/index.js +2 -2
  55. package/dist/src/index.js.map +1 -1
  56. package/dist/src/init/never-commit.d.ts +3 -3
  57. package/dist/src/init/types.d.ts +1 -1
  58. package/dist/src/init/types.js +1 -1
  59. package/dist/src/knowledge/document-store.js +1 -1
  60. package/dist/src/knowledge/document-store.js.map +1 -1
  61. package/dist/src/mcp-tools/hooks-edit-command.d.ts +10 -0
  62. package/dist/src/mcp-tools/hooks-edit-command.d.ts.map +1 -0
  63. package/dist/src/mcp-tools/hooks-edit-command.js +247 -0
  64. package/dist/src/mcp-tools/hooks-edit-command.js.map +1 -0
  65. package/dist/src/mcp-tools/hooks-learning.d.ts +9 -0
  66. package/dist/src/mcp-tools/hooks-learning.d.ts.map +1 -0
  67. package/dist/src/mcp-tools/hooks-learning.js +408 -0
  68. package/dist/src/mcp-tools/hooks-learning.js.map +1 -0
  69. package/dist/src/mcp-tools/hooks-metrics-list.d.ts +8 -0
  70. package/dist/src/mcp-tools/hooks-metrics-list.d.ts.map +1 -0
  71. package/dist/src/mcp-tools/hooks-metrics-list.js +205 -0
  72. package/dist/src/mcp-tools/hooks-metrics-list.js.map +1 -0
  73. package/dist/src/mcp-tools/hooks-route.d.ts +12 -0
  74. package/dist/src/mcp-tools/hooks-route.d.ts.map +1 -0
  75. package/dist/src/mcp-tools/hooks-route.js +251 -0
  76. package/dist/src/mcp-tools/hooks-route.js.map +1 -0
  77. package/dist/src/mcp-tools/hooks-routing.d.ts +6 -27
  78. package/dist/src/mcp-tools/hooks-routing.d.ts.map +1 -1
  79. package/dist/src/mcp-tools/hooks-routing.js +6 -1727
  80. package/dist/src/mcp-tools/hooks-routing.js.map +1 -1
  81. package/dist/src/mcp-tools/hooks-session.d.ts +9 -0
  82. package/dist/src/mcp-tools/hooks-session.d.ts.map +1 -0
  83. package/dist/src/mcp-tools/hooks-session.js +243 -0
  84. package/dist/src/mcp-tools/hooks-session.js.map +1 -0
  85. package/dist/src/mcp-tools/hooks-task.d.ts +17 -0
  86. package/dist/src/mcp-tools/hooks-task.d.ts.map +1 -0
  87. package/dist/src/mcp-tools/hooks-task.js +427 -0
  88. package/dist/src/mcp-tools/hooks-task.js.map +1 -0
  89. package/dist/src/mcp-tools/hooks-tools.d.ts +2 -1
  90. package/dist/src/mcp-tools/hooks-tools.d.ts.map +1 -1
  91. package/dist/src/mcp-tools/hooks-tools.js +2 -1
  92. package/dist/src/mcp-tools/hooks-tools.js.map +1 -1
  93. package/dist/src/mcp-tools/memory-tools.js +1 -1
  94. package/dist/src/mcp-tools/memory-tools.js.map +1 -1
  95. package/dist/src/memory/bm25-index.d.ts +2 -2
  96. package/dist/src/memory/bm25-index.js +2 -2
  97. package/dist/src/memory/embedding-operations.js +2 -2
  98. package/dist/src/memory/embedding-operations.js.map +1 -1
  99. package/dist/src/memory/entity-name-key.d.ts +1 -1
  100. package/dist/src/memory/entity-name-key.js +1 -1
  101. package/dist/src/memory/hnsw-operations.d.ts +1 -1
  102. package/dist/src/memory/hnsw-operations.js +1 -1
  103. package/dist/src/memory/memory-bridge-backend.d.ts +43 -0
  104. package/dist/src/memory/memory-bridge-backend.d.ts.map +1 -0
  105. package/dist/src/memory/memory-bridge-backend.js +363 -0
  106. package/dist/src/memory/memory-bridge-backend.js.map +1 -0
  107. package/dist/src/memory/memory-bridge-core.d.ts +42 -0
  108. package/dist/src/memory/memory-bridge-core.d.ts.map +1 -0
  109. package/dist/src/memory/memory-bridge-core.js +126 -0
  110. package/dist/src/memory/memory-bridge-core.js.map +1 -0
  111. package/dist/src/memory/memory-bridge-entries.d.ts +131 -0
  112. package/dist/src/memory/memory-bridge-entries.d.ts.map +1 -0
  113. package/dist/src/memory/memory-bridge-entries.js +227 -0
  114. package/dist/src/memory/memory-bridge-entries.js.map +1 -0
  115. package/dist/src/memory/memory-bridge-feedback.d.ts +60 -0
  116. package/dist/src/memory/memory-bridge-feedback.d.ts.map +1 -0
  117. package/dist/src/memory/memory-bridge-feedback.js +118 -0
  118. package/dist/src/memory/memory-bridge-feedback.js.map +1 -0
  119. package/dist/src/memory/memory-bridge-paths.d.ts +123 -0
  120. package/dist/src/memory/memory-bridge-paths.d.ts.map +1 -0
  121. package/dist/src/memory/memory-bridge-paths.js +335 -0
  122. package/dist/src/memory/memory-bridge-paths.js.map +1 -0
  123. package/dist/src/memory/memory-bridge-search.d.ts +48 -0
  124. package/dist/src/memory/memory-bridge-search.d.ts.map +1 -0
  125. package/dist/src/memory/memory-bridge-search.js +383 -0
  126. package/dist/src/memory/memory-bridge-search.js.map +1 -0
  127. package/dist/src/memory/memory-bridge-sessions.d.ts +38 -0
  128. package/dist/src/memory/memory-bridge-sessions.d.ts.map +1 -0
  129. package/dist/src/memory/memory-bridge-sessions.js +109 -0
  130. package/dist/src/memory/memory-bridge-sessions.js.map +1 -0
  131. package/dist/src/memory/memory-bridge-store.d.ts +58 -0
  132. package/dist/src/memory/memory-bridge-store.d.ts.map +1 -0
  133. package/dist/src/memory/memory-bridge-store.js +201 -0
  134. package/dist/src/memory/memory-bridge-store.js.map +1 -0
  135. package/dist/src/memory/memory-bridge.d.ts +10 -438
  136. package/dist/src/memory/memory-bridge.d.ts.map +1 -1
  137. package/dist/src/memory/memory-bridge.js +14 -1787
  138. package/dist/src/memory/memory-bridge.js.map +1 -1
  139. package/dist/src/memory/memory-kg-claims.d.ts +52 -0
  140. package/dist/src/memory/memory-kg-claims.d.ts.map +1 -0
  141. package/dist/src/memory/memory-kg-claims.js +99 -0
  142. package/dist/src/memory/memory-kg-claims.js.map +1 -0
  143. package/dist/src/memory/memory-kg-index.d.ts +86 -0
  144. package/dist/src/memory/memory-kg-index.d.ts.map +1 -0
  145. package/dist/src/memory/memory-kg-index.js +256 -0
  146. package/dist/src/memory/memory-kg-index.js.map +1 -0
  147. package/dist/src/memory/memory-kg-ingest.d.ts +44 -0
  148. package/dist/src/memory/memory-kg-ingest.d.ts.map +1 -0
  149. package/dist/src/memory/memory-kg-ingest.js +361 -0
  150. package/dist/src/memory/memory-kg-ingest.js.map +1 -0
  151. package/dist/src/memory/memory-kg-model.d.ts +168 -0
  152. package/dist/src/memory/memory-kg-model.d.ts.map +1 -0
  153. package/dist/src/memory/memory-kg-model.js +158 -0
  154. package/dist/src/memory/memory-kg-model.js.map +1 -0
  155. package/dist/src/memory/memory-kg-names.d.ts +64 -0
  156. package/dist/src/memory/memory-kg-names.d.ts.map +1 -0
  157. package/dist/src/memory/memory-kg-names.js +187 -0
  158. package/dist/src/memory/memory-kg-names.js.map +1 -0
  159. package/dist/src/memory/memory-kg-promote.d.ts +79 -0
  160. package/dist/src/memory/memory-kg-promote.d.ts.map +1 -0
  161. package/dist/src/memory/memory-kg-promote.js +248 -0
  162. package/dist/src/memory/memory-kg-promote.js.map +1 -0
  163. package/dist/src/memory/memory-kg-rebuild.d.ts +77 -0
  164. package/dist/src/memory/memory-kg-rebuild.d.ts.map +1 -0
  165. package/dist/src/memory/memory-kg-rebuild.js +299 -0
  166. package/dist/src/memory/memory-kg-rebuild.js.map +1 -0
  167. package/dist/src/memory/memory-kg-rollback.d.ts +47 -0
  168. package/dist/src/memory/memory-kg-rollback.d.ts.map +1 -0
  169. package/dist/src/memory/memory-kg-rollback.js +193 -0
  170. package/dist/src/memory/memory-kg-rollback.js.map +1 -0
  171. package/dist/src/memory/memory-kg-rules.d.ts +64 -0
  172. package/dist/src/memory/memory-kg-rules.d.ts.map +1 -0
  173. package/dist/src/memory/memory-kg-rules.js +251 -0
  174. package/dist/src/memory/memory-kg-rules.js.map +1 -0
  175. package/dist/src/memory/memory-kg-scan.d.ts +118 -0
  176. package/dist/src/memory/memory-kg-scan.d.ts.map +1 -0
  177. package/dist/src/memory/memory-kg-scan.js +209 -0
  178. package/dist/src/memory/memory-kg-scan.js.map +1 -0
  179. package/dist/src/memory/memory-kg-search.d.ts +80 -0
  180. package/dist/src/memory/memory-kg-search.d.ts.map +1 -0
  181. package/dist/src/memory/memory-kg-search.js +239 -0
  182. package/dist/src/memory/memory-kg-search.js.map +1 -0
  183. package/dist/src/memory/memory-kg.d.ts +19 -547
  184. package/dist/src/memory/memory-kg.d.ts.map +1 -1
  185. package/dist/src/memory/memory-kg.js +10 -2389
  186. package/dist/src/memory/memory-kg.js.map +1 -1
  187. package/dist/src/ui/org-hil.mjs +1 -1
  188. package/dist/src/ui/routes-org-agents.mjs +318 -0
  189. package/dist/src/ui/routes-org-approvals.mjs +133 -0
  190. package/dist/src/ui/routes-org-config.mjs +459 -0
  191. package/dist/src/ui/routes-org-control.mjs +300 -0
  192. package/dist/src/ui/routes-org-files.mjs +172 -0
  193. package/dist/src/ui/routes-org-helpers.mjs +84 -0
  194. package/dist/src/ui/routes-org-knowledge.mjs +262 -0
  195. package/dist/src/ui/routes-org-lifecycle.mjs +224 -0
  196. package/dist/src/ui/routes-org-live.mjs +278 -0
  197. package/dist/src/ui/routes-org-mastermind.mjs +346 -0
  198. package/dist/src/ui/routes-org-planning.mjs +398 -0
  199. package/dist/src/ui/routes-org-runs.mjs +430 -0
  200. package/dist/src/ui/routes-org-status.mjs +342 -0
  201. package/dist/src/ui/routes-org-workflows.mjs +212 -0
  202. package/dist/src/ui/routes-org.mjs +26 -3847
  203. package/dist/src/ui/server.mjs +1 -1
  204. package/dist/src/utils/native-error.d.ts +1 -1
  205. package/dist/src/utils/native-error.js +1 -1
  206. package/dist/src/utils/paths.d.ts +2 -1
  207. package/dist/src/utils/paths.d.ts.map +1 -1
  208. package/dist/src/utils/paths.js +2 -1
  209. package/dist/src/utils/paths.js.map +1 -1
  210. package/dist/tsconfig.tsbuildinfo +1 -1
  211. package/org-skills/adversarial-reviewer/SKILL.md +1 -1
  212. package/org-skills/board-deck-builder/SKILL.md +1 -1
  213. package/org-skills/code-reviewer/SKILL.md +1 -1
  214. package/org-skills/debugging-wizard/SKILL.md +1 -1
  215. package/org-skills/scientific-thinking-literature-review/SKILL.md +1 -1
  216. package/org-skills/scientific-thinking-scholar-evaluation/SKILL.md +1 -1
  217. 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
+ }