@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,398 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+
4
+ // Org dashboard routes: budgets, threads, routines and goals.
5
+ // Registered, in order, by handleOrgRoutes in routes-org.mjs.
6
+ export async function handleOrgPlanningRoutes(req, res, url, corsOrigin, ctx) {
7
+ // GET /api/org/:name/budgets — org and per-agent budget data
8
+ // Returns: { org_budget: {limit_tokens, limit_usd}, agent_budgets: {agentId: {limit_usd}}, agents: [{id, title, tokens_in, tokens_out, total_cost_usd}] }
9
+ if (req.method === 'GET' && url.match(/^\/api\/org\/[a-z0-9][a-z0-9_-]{0,63}\/budgets$/i)) {
10
+ try {
11
+ const orgName = decodeURIComponent(url.split('/')[3]);
12
+ if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) {
13
+ res.writeHead(400);
14
+ res.end('Invalid org name');
15
+ return true;
16
+ }
17
+ const _budgetsQs = new URL(req.url, 'http://localhost').searchParams;
18
+ const base = path.join(
19
+ path.resolve(_budgetsQs.get('dir') || ctx.projectDir || process.cwd()),
20
+ '.monomind',
21
+ 'orgs',
22
+ );
23
+ let budgetData = { org_budget: {}, agent_budgets: {}, period: 'monthly', currency: 'USD' };
24
+ try {
25
+ budgetData = JSON.parse(
26
+ fs.readFileSync(path.join(base, `${orgName}-budgets.json`), 'utf8'),
27
+ );
28
+ } catch (_) {}
29
+ // Enrich with per-agent spend from state file.
30
+ // State file format: { agents: { "<role_id>": { tokens_in, tokens_out, ... } } }
31
+ let agents = [];
32
+ try {
33
+ const state = JSON.parse(fs.readFileSync(path.join(base, `${orgName}-state.json`), 'utf8'));
34
+ const agentMap = state.agents || {};
35
+ // Also load role titles from org config for enrichment
36
+ const roleMap = {};
37
+ try {
38
+ const cfg = JSON.parse(fs.readFileSync(path.join(base, `${orgName}.json`), 'utf8'));
39
+ (cfg.roles || []).forEach((r) => {
40
+ roleMap[r.id] = r.title || r.id;
41
+ });
42
+ } catch (_) {}
43
+ agents = Object.entries(agentMap).map(([id, s]) => ({
44
+ id,
45
+ title: roleMap[id] || s.title || id,
46
+ tokens_in: s.tokens_in || 0,
47
+ tokens_out: s.tokens_out || 0,
48
+ tokens_used: s.tokens_used || (s.tokens_in || 0) + (s.tokens_out || 0),
49
+ total_cost_usd: s.total_cost_usd || 0,
50
+ }));
51
+ } catch (_) {}
52
+ // Scan org run jsonl files for usage events (fallback when state.json has no token data).
53
+ // Two event-type variants show up here in practice and must BOTH be matched, each with its
54
+ // own field mapping — matching only one silently drops real v2 cost data from the UI:
55
+ // - 'agent:usage': flattened { role, tokens_in, tokens_out, cost_usd } (legacy/direct writers).
56
+ // - 'org:usage': orgrt's actual forwarded shape (attachForwarder's translate() default case
57
+ // for a raw OrgBus 'usage' event) — { from, data: { tokens, cost_usd } }. orgrt never emits
58
+ // 'agent:usage' itself, so scanning for that alone means this fallback never fires for real runs.
59
+ const _hasTokenData = agents.some(
60
+ (a) => a.tokens_in > 0 || a.tokens_out > 0 || a.total_cost_usd > 0,
61
+ );
62
+ if (!_hasTokenData) {
63
+ try {
64
+ const _runsDir = path.join(base, orgName, 'runs');
65
+ if (fs.existsSync(_runsDir)) {
66
+ const _usageByRole = {};
67
+ const _bump = (role, tokensIn, tokensOut, tokensTotal, costUsd) => {
68
+ if (!role) return;
69
+ role = String(role).trim();
70
+ if (!role) return;
71
+ if (!_usageByRole[role])
72
+ _usageByRole[role] = {
73
+ tokens_in: 0,
74
+ tokens_out: 0,
75
+ tokens_used: 0,
76
+ total_cost_usd: 0,
77
+ };
78
+ _usageByRole[role].tokens_in += tokensIn;
79
+ _usageByRole[role].tokens_out += tokensOut;
80
+ _usageByRole[role].tokens_used += tokensIn + tokensOut + tokensTotal;
81
+ _usageByRole[role].total_cost_usd += costUsd;
82
+ };
83
+ for (const f of fs.readdirSync(_runsDir)) {
84
+ if (!f.endsWith('.jsonl') || f.startsWith('._')) continue;
85
+ const lines = fs
86
+ .readFileSync(path.join(_runsDir, f), 'utf8')
87
+ .split('\n')
88
+ .filter(Boolean);
89
+ for (const l of lines) {
90
+ try {
91
+ const ev = JSON.parse(l);
92
+ if (ev.type === 'agent:usage' && ev.role) {
93
+ _bump(
94
+ ev.role,
95
+ Number(ev.tokens_in) || 0,
96
+ Number(ev.tokens_out) || 0,
97
+ 0,
98
+ Number(ev.cost_usd) || 0,
99
+ );
100
+ } else if (ev.type === 'org:usage' && ev.from) {
101
+ _bump(
102
+ ev.from,
103
+ 0,
104
+ 0,
105
+ Number(ev.data?.tokens) || 0,
106
+ Number(ev.data?.cost_usd) || 0,
107
+ );
108
+ }
109
+ } catch (_) {}
110
+ }
111
+ }
112
+ if (Object.keys(_usageByRole).length > 0) {
113
+ // Merge usage into agents list; preserve role titles
114
+ agents = agents.map((a) => {
115
+ const u = _usageByRole[a.id] || {};
116
+ return {
117
+ ...a,
118
+ tokens_in: u.tokens_in || 0,
119
+ tokens_out: u.tokens_out || 0,
120
+ tokens_used: u.tokens_used || (u.tokens_in || 0) + (u.tokens_out || 0),
121
+ total_cost_usd: u.total_cost_usd || 0,
122
+ };
123
+ });
124
+ // Add any roles that appeared in events but aren't in config
125
+ for (const [role, u] of Object.entries(_usageByRole)) {
126
+ if (!agents.find((a) => a.id === role))
127
+ agents.push({ id: role, title: role, ...u });
128
+ }
129
+ }
130
+ }
131
+ } catch (_) {}
132
+ }
133
+ // Do NOT fall back to zero-value role stubs — empty agents array is the honest signal
134
+ // that no usage has been tracked yet; the UI shows "No cost data" rather than $0.0000 rows.
135
+ res.writeHead(200, { 'Content-Type': 'application/json' });
136
+ res.end(JSON.stringify({ ...budgetData, agents }));
137
+ } catch (_) {
138
+ res.writeHead(500);
139
+ res.end('{"org_budget":{},"agent_budgets":{},"agents":[]}');
140
+ }
141
+ return true;
142
+ }
143
+
144
+ // GET /api/org/:name/threads — conversation threads from threads.jsonl
145
+ // Returns: { threads: [{id, subject, authorId, authorName, issueId, createdAt, messages:[]}] }
146
+ if (req.method === 'GET' && url.match(/^\/api\/org\/[a-z0-9][a-z0-9_-]{0,63}\/threads$/i)) {
147
+ try {
148
+ const orgName = decodeURIComponent(url.split('/')[3]);
149
+ if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) {
150
+ res.writeHead(400);
151
+ res.end('Invalid org name');
152
+ return true;
153
+ }
154
+ const _threadsQs = new URL(req.url, 'http://localhost').searchParams;
155
+ const _threadsRoot = path.resolve(_threadsQs.get('dir') || ctx.projectDir || process.cwd());
156
+ const _threadsProjDir = ctx._resolveOrgProjectDir(orgName, _threadsRoot) || _threadsRoot;
157
+ const threadsFile = path.join(
158
+ _threadsProjDir,
159
+ '.monomind',
160
+ 'orgs',
161
+ `${orgName}-threads.jsonl`,
162
+ );
163
+ let threads = [];
164
+ try {
165
+ const lines = fs
166
+ .readFileSync(threadsFile, 'utf8')
167
+ .split('\n')
168
+ .filter((l) => l.trim());
169
+ threads = lines
170
+ .map((l) => {
171
+ try {
172
+ return JSON.parse(l);
173
+ } catch (_) {
174
+ return null;
175
+ }
176
+ })
177
+ .filter(Boolean);
178
+ // Group 'message' entries (from org:comms) by run_id into synthetic thread objects
179
+ const msgsByRun = {};
180
+ threads
181
+ .filter((t) => t.type === 'message')
182
+ .forEach((m) => {
183
+ const rid = m.run_id || 'unknown';
184
+ if (!msgsByRun[rid])
185
+ msgsByRun[rid] = {
186
+ id: `thread-${rid}`,
187
+ type: 'thread',
188
+ subject: `Run ${rid}`,
189
+ run_id: rid,
190
+ createdAt: m.ts,
191
+ messages: [],
192
+ };
193
+ msgsByRun[rid].messages.push({ from: m.from, to: m.to, msg: m.msg, ts: m.ts });
194
+ });
195
+ const syntheticThreads = Object.values(msgsByRun).map((t) => ({
196
+ ...t,
197
+ messageCount: t.messages.length,
198
+ author: t.messages[0]?.from || null,
199
+ }));
200
+ threads = threads
201
+ .filter((t) => t.type === 'thread' || !t.type)
202
+ .map((t) => ({
203
+ ...t,
204
+ author: t.author || t.authorName || t.createdBy || t.authorId || null,
205
+ messageCount:
206
+ t.messageCount != null
207
+ ? t.messageCount
208
+ : Array.isArray(t.messages)
209
+ ? t.messages.length
210
+ : typeof t.messages === 'number'
211
+ ? t.messages
212
+ : null,
213
+ }));
214
+ threads = [...threads, ...syntheticThreads];
215
+ } catch (_) {}
216
+ res.writeHead(200, { 'Content-Type': 'application/json' });
217
+ res.end(JSON.stringify({ threads }));
218
+ } catch (_) {
219
+ res.writeHead(500);
220
+ res.end('{"threads":[]}');
221
+ }
222
+ return true;
223
+ }
224
+
225
+ // GET /api/org/:name/routines — read org routines (falls back to synthesizing from org config's loop object)
226
+ if (req.method === 'GET' && url.match(/^\/api\/org\/[a-z0-9][a-z0-9_-]{0,63}\/routines$/i)) {
227
+ try {
228
+ const orgName = decodeURIComponent(url.split('/')[3]);
229
+ if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) {
230
+ res.writeHead(400);
231
+ res.end('Invalid org name');
232
+ return true;
233
+ }
234
+ const _routinesQs = new URL(req.url, 'http://localhost').searchParams;
235
+ const _routinesBase = path.resolve(_routinesQs.get('dir') || ctx.projectDir || process.cwd());
236
+ const _routinesProjDir = ctx._resolveOrgProjectDir(orgName, _routinesBase) || _routinesBase;
237
+ const routinesFile = path.join(
238
+ _routinesProjDir,
239
+ '.monomind',
240
+ 'orgs',
241
+ `${orgName}-routines.json`,
242
+ );
243
+ let data = { routines: [] };
244
+ try {
245
+ data = JSON.parse(fs.readFileSync(routinesFile, 'utf8'));
246
+ } catch (_) {}
247
+ // Synthesize routines from org config's loop/schedule settings when no explicit routines are defined
248
+ if (!data.routines?.length) {
249
+ try {
250
+ const orgCfg = JSON.parse(
251
+ fs.readFileSync(
252
+ path.join(_routinesProjDir, '.monomind', 'orgs', `${orgName}.json`),
253
+ 'utf8',
254
+ ),
255
+ );
256
+ const loop = orgCfg.loop;
257
+ if (loop && (loop.poll_interval_minutes || loop.interval_minutes)) {
258
+ const intervalMin = loop.poll_interval_minutes || loop.interval_minutes;
259
+ data.routines = [
260
+ {
261
+ name: `${orgName}-cycle`,
262
+ description: orgCfg.goal ? orgCfg.goal.slice(0, 120) : 'Org iteration cycle',
263
+ schedule: `every ${intervalMin}m`,
264
+ cron: null,
265
+ enabled: orgCfg.status === 'active',
266
+ status: orgCfg.status || 'stopped',
267
+ prompt_file: loop.run_prompt_file || null,
268
+ source: 'loop-config',
269
+ lastRun: null,
270
+ },
271
+ ];
272
+ } else if (orgCfg.schedule) {
273
+ data.routines = [
274
+ {
275
+ name: `${orgName}-schedule`,
276
+ description: orgCfg.goal ? orgCfg.goal.slice(0, 120) : 'Org scheduled run',
277
+ schedule: String(orgCfg.schedule),
278
+ cron: null,
279
+ enabled: orgCfg.status === 'active',
280
+ status: orgCfg.status || 'stopped',
281
+ source: 'schedule-config',
282
+ lastRun: null,
283
+ },
284
+ ];
285
+ }
286
+ } catch (_) {}
287
+ }
288
+ res.writeHead(200, {
289
+ 'Content-Type': 'application/json',
290
+ ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}),
291
+ });
292
+ res.end(JSON.stringify({ routines: data.routines || [] }));
293
+ } catch (_) {
294
+ res.writeHead(500);
295
+ res.end('{"routines":[]}');
296
+ }
297
+ return true;
298
+ }
299
+
300
+ // POST /api/org/:name/goals — upsert the org goals file
301
+ // Body: { goals: [{id, title, description, status, priority, assignee_id, created_at}] }
302
+ if (req.method === 'POST' && url.match(/^\/api\/org\/[a-z0-9][a-z0-9_-]{0,63}\/goals$/i)) {
303
+ let body = '';
304
+ for await (const chunk of req) {
305
+ body += chunk;
306
+ if (body.length > 2097152) {
307
+ req.destroy();
308
+ break;
309
+ }
310
+ }
311
+ try {
312
+ const orgName = decodeURIComponent(url.split('/')[3]);
313
+ if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) {
314
+ res.writeHead(400);
315
+ res.end('Invalid org name');
316
+ return true;
317
+ }
318
+ const parsed = JSON.parse(body);
319
+ if (!parsed || !Array.isArray(parsed.goals)) {
320
+ res.writeHead(400);
321
+ res.end('{"error":"goals array required"}');
322
+ return true;
323
+ }
324
+ const _postGoalsQs = new URL(req.url, 'http://localhost').searchParams;
325
+ const goalsFile = path.join(
326
+ path.resolve(_postGoalsQs.get('dir') || ctx.projectDir || process.cwd()),
327
+ '.monomind',
328
+ 'orgs',
329
+ `${orgName}-goals.json`,
330
+ );
331
+ const tmp = `${goalsFile}.tmp`;
332
+ const payload = { org: orgName, updated_at: new Date().toISOString(), goals: parsed.goals };
333
+ fs.writeFileSync(tmp, JSON.stringify(payload, null, 2), 'utf-8');
334
+ fs.renameSync(tmp, goalsFile);
335
+ res.writeHead(200, {
336
+ 'Content-Type': 'application/json',
337
+ ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}),
338
+ });
339
+ res.end(JSON.stringify({ ok: true, count: parsed.goals.length }));
340
+ } catch (_) {
341
+ res.writeHead(500);
342
+ res.end(`{"error":"${String(_).replace(/"/g, '\\"')}"}`);
343
+ }
344
+ return true;
345
+ }
346
+
347
+ // POST /api/org/:name/routines — upsert the org routines file
348
+ // Body: { routines: [{name, description, schedule, enabled, last_run, next_run}] }
349
+ if (req.method === 'POST' && url.match(/^\/api\/org\/[a-z0-9][a-z0-9_-]{0,63}\/routines$/i)) {
350
+ let body = '';
351
+ for await (const chunk of req) {
352
+ body += chunk;
353
+ if (body.length > 2097152) {
354
+ req.destroy();
355
+ break;
356
+ }
357
+ }
358
+ try {
359
+ const orgName = decodeURIComponent(url.split('/')[3]);
360
+ if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) {
361
+ res.writeHead(400);
362
+ res.end('Invalid org name');
363
+ return true;
364
+ }
365
+ const parsed = JSON.parse(body);
366
+ if (!parsed || !Array.isArray(parsed.routines)) {
367
+ res.writeHead(400);
368
+ res.end('{"error":"routines array required"}');
369
+ return true;
370
+ }
371
+ const _postRoutinesQs = new URL(req.url, 'http://localhost').searchParams;
372
+ const routinesFile = path.join(
373
+ path.resolve(_postRoutinesQs.get('dir') || ctx.projectDir || process.cwd()),
374
+ '.monomind',
375
+ 'orgs',
376
+ `${orgName}-routines.json`,
377
+ );
378
+ const tmp = `${routinesFile}.tmp`;
379
+ const payload = {
380
+ org: orgName,
381
+ updated_at: new Date().toISOString(),
382
+ routines: parsed.routines,
383
+ };
384
+ fs.writeFileSync(tmp, JSON.stringify(payload, null, 2), 'utf-8');
385
+ fs.renameSync(tmp, routinesFile);
386
+ res.writeHead(200, {
387
+ 'Content-Type': 'application/json',
388
+ ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}),
389
+ });
390
+ res.end(JSON.stringify({ ok: true, count: parsed.routines.length }));
391
+ } catch (_) {
392
+ res.writeHead(500);
393
+ res.end(`{"error":"${String(_).replace(/"/g, '\\"')}"}`);
394
+ }
395
+ return true;
396
+ }
397
+ return false;
398
+ }