@monoes/monomindcli 2.14.1 → 2.15.0

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 (194) hide show
  1. package/.claude/helpers/control-start.cjs +10 -13
  2. package/.claude/skills/monodesign/scripts/context.mjs +4 -4
  3. package/.claude/skills/monodesign/scripts/critique-storage.mjs +1 -1
  4. package/.claude/skills/monodesign/scripts/detect-csp.mjs +1 -6
  5. package/.claude/skills/monodesign/scripts/detector/browser/injected/index.mjs +16 -18
  6. package/.claude/skills/monodesign/scripts/detector/cli/main.mjs +3 -3
  7. package/.claude/skills/monodesign/scripts/detector/detect-antipatterns-browser.js +67 -47
  8. package/.claude/skills/monodesign/scripts/detector/engines/regex/detect-text.mjs +6 -6
  9. package/.claude/skills/monodesign/scripts/detector/engines/static-html/css-cascade.mjs +6 -5
  10. package/.claude/skills/monodesign/scripts/detector/engines/static-html/detect-html.mjs +6 -6
  11. package/.claude/skills/monodesign/scripts/detector/fix/index.mjs +3 -3
  12. package/.claude/skills/monodesign/scripts/detector/node/file-system.mjs +1 -1
  13. package/.claude/skills/monodesign/scripts/detector/registry/antipatterns.mjs +1 -1
  14. package/.claude/skills/monodesign/scripts/detector/rules/checks.mjs +24 -25
  15. package/.claude/skills/monodesign/scripts/detector/shared/color.mjs +2 -2
  16. package/.claude/skills/monodesign/scripts/detector/shared/constants.mjs +1 -1
  17. package/.claude/skills/monodesign/scripts/detector/shared/inline-ignores.mjs +2 -2
  18. package/.claude/skills/monodesign/scripts/hook-admin.mjs +6 -6
  19. package/.claude/skills/monodesign/scripts/hook-before-edit.mjs +4 -4
  20. package/.claude/skills/monodesign/scripts/hook-lib.mjs +20 -11
  21. package/.claude/skills/monodesign/scripts/hook.mjs +1 -1
  22. package/.claude/skills/monodesign/scripts/lib/design-parser.mjs +9 -42
  23. package/.claude/skills/monodesign/scripts/lib/is-generated.mjs +1 -1
  24. package/.claude/skills/monodesign/scripts/lib/monodesign-config.mjs +5 -5
  25. package/.claude/skills/monodesign/scripts/lib/monodesign-paths.mjs +1 -1
  26. package/.claude/skills/monodesign/scripts/live/event-validation.mjs +13 -13
  27. package/.claude/skills/monodesign/scripts/live/manual-apply.mjs +4 -4
  28. package/.claude/skills/monodesign/scripts/live/manual-edits-buffer.mjs +1 -1
  29. package/.claude/skills/monodesign/scripts/live/session-store.mjs +5 -5
  30. package/.claude/skills/monodesign/scripts/live/svelte-component.mjs +13 -13
  31. package/.claude/skills/monodesign/scripts/live/sveltekit-adapter.mjs +4 -4
  32. package/.claude/skills/monodesign/scripts/live/ui-core.mjs +1 -1
  33. package/.claude/skills/monodesign/scripts/live-accept.mjs +24 -24
  34. package/.claude/skills/monodesign/scripts/live-browser-dom.js +8 -7
  35. package/.claude/skills/monodesign/scripts/live-browser-session.js +8 -7
  36. package/.claude/skills/monodesign/scripts/live-browser.js +405 -432
  37. package/.claude/skills/monodesign/scripts/live-commit-manual-edits.mjs +6 -6
  38. package/.claude/skills/monodesign/scripts/live-copy-edit-agent.mjs +2 -2
  39. package/.claude/skills/monodesign/scripts/live-discard-manual-edits.mjs +1 -1
  40. package/.claude/skills/monodesign/scripts/live-inject.mjs +7 -7
  41. package/.claude/skills/monodesign/scripts/live-insert.mjs +11 -11
  42. package/.claude/skills/monodesign/scripts/live-manual-edit-evidence.mjs +4 -4
  43. package/.claude/skills/monodesign/scripts/live-poll.mjs +5 -5
  44. package/.claude/skills/monodesign/scripts/live-server.mjs +10 -10
  45. package/.claude/skills/monodesign/scripts/live-wrap.mjs +31 -41
  46. package/.claude/skills/monodesign/scripts/live.mjs +5 -5
  47. package/.claude/skills/monodesign/scripts/palette.mjs +3 -1
  48. package/.claude/skills/monodesign/scripts/pin.mjs +1 -1
  49. package/dist/src/commands/doc-filters.d.ts.map +1 -1
  50. package/dist/src/commands/doc-filters.js +6 -0
  51. package/dist/src/commands/doc-filters.js.map +1 -1
  52. package/dist/src/commands/doc-library.d.ts +2 -1
  53. package/dist/src/commands/doc-library.d.ts.map +1 -1
  54. package/dist/src/commands/doc-library.js +66 -2
  55. package/dist/src/commands/doc-library.js.map +1 -1
  56. package/dist/src/commands/doc.d.ts.map +1 -1
  57. package/dist/src/commands/doc.js +2 -0
  58. package/dist/src/commands/doc.js.map +1 -1
  59. package/dist/src/commands/ui.d.ts.map +1 -1
  60. package/dist/src/commands/ui.js +60 -0
  61. package/dist/src/commands/ui.js.map +1 -1
  62. package/dist/src/knowledge/capture-envelope.d.ts +5 -0
  63. package/dist/src/knowledge/capture-envelope.d.ts.map +1 -1
  64. package/dist/src/knowledge/capture-envelope.js +2 -0
  65. package/dist/src/knowledge/capture-envelope.js.map +1 -1
  66. package/dist/src/knowledge/document-chunking.d.ts +42 -0
  67. package/dist/src/knowledge/document-chunking.d.ts.map +1 -0
  68. package/dist/src/knowledge/document-chunking.js +288 -0
  69. package/dist/src/knowledge/document-chunking.js.map +1 -0
  70. package/dist/src/knowledge/document-index.d.ts +124 -0
  71. package/dist/src/knowledge/document-index.d.ts.map +1 -0
  72. package/dist/src/knowledge/document-index.js +314 -0
  73. package/dist/src/knowledge/document-index.js.map +1 -0
  74. package/dist/src/knowledge/document-ingest.d.ts +23 -0
  75. package/dist/src/knowledge/document-ingest.d.ts.map +1 -0
  76. package/dist/src/knowledge/document-ingest.js +368 -0
  77. package/dist/src/knowledge/document-ingest.js.map +1 -0
  78. package/dist/src/knowledge/document-pipeline.d.ts +26 -208
  79. package/dist/src/knowledge/document-pipeline.d.ts.map +1 -1
  80. package/dist/src/knowledge/document-pipeline.js +25 -1104
  81. package/dist/src/knowledge/document-pipeline.js.map +1 -1
  82. package/dist/src/knowledge/document-search.d.ts +27 -0
  83. package/dist/src/knowledge/document-search.d.ts.map +1 -0
  84. package/dist/src/knowledge/document-search.js +138 -0
  85. package/dist/src/knowledge/document-search.js.map +1 -0
  86. package/dist/src/knowledge/document-store.d.ts +28 -0
  87. package/dist/src/knowledge/document-store.d.ts.map +1 -0
  88. package/dist/src/knowledge/document-store.js +61 -0
  89. package/dist/src/knowledge/document-store.js.map +1 -0
  90. package/dist/src/knowledge/document-types.d.ts +103 -0
  91. package/dist/src/knowledge/document-types.d.ts.map +1 -0
  92. package/dist/src/knowledge/document-types.js +12 -0
  93. package/dist/src/knowledge/document-types.js.map +1 -0
  94. package/dist/src/knowledge/highlights.d.ts +168 -0
  95. package/dist/src/knowledge/highlights.d.ts.map +1 -0
  96. package/dist/src/knowledge/highlights.js +311 -0
  97. package/dist/src/knowledge/highlights.js.map +1 -0
  98. package/dist/src/knowledge/lookup.d.ts +80 -0
  99. package/dist/src/knowledge/lookup.d.ts.map +1 -0
  100. package/dist/src/knowledge/lookup.js +156 -0
  101. package/dist/src/knowledge/lookup.js.map +1 -0
  102. package/dist/src/knowledge/okf-bundle.d.ts +19 -0
  103. package/dist/src/knowledge/okf-bundle.d.ts.map +1 -0
  104. package/dist/src/knowledge/okf-bundle.js +106 -0
  105. package/dist/src/knowledge/okf-bundle.js.map +1 -0
  106. package/dist/src/knowledge/profile-store.d.ts +134 -0
  107. package/dist/src/knowledge/profile-store.d.ts.map +1 -0
  108. package/dist/src/knowledge/profile-store.js +237 -0
  109. package/dist/src/knowledge/profile-store.js.map +1 -0
  110. package/dist/src/orgrt/agent-runner.d.ts +4 -0
  111. package/dist/src/orgrt/agent-runner.d.ts.map +1 -1
  112. package/dist/src/orgrt/agent-runner.js +22 -0
  113. package/dist/src/orgrt/agent-runner.js.map +1 -1
  114. package/dist/src/orgrt/antigravity-runner.d.ts.map +1 -1
  115. package/dist/src/orgrt/antigravity-runner.js +2 -1
  116. package/dist/src/orgrt/antigravity-runner.js.map +1 -1
  117. package/dist/src/orgrt/authority-mask.d.ts +32 -0
  118. package/dist/src/orgrt/authority-mask.d.ts.map +1 -0
  119. package/dist/src/orgrt/authority-mask.js +135 -0
  120. package/dist/src/orgrt/authority-mask.js.map +1 -0
  121. package/dist/src/orgrt/codex-runner.d.ts.map +1 -1
  122. package/dist/src/orgrt/codex-runner.js +2 -1
  123. package/dist/src/orgrt/codex-runner.js.map +1 -1
  124. package/dist/src/orgrt/copilot-runner.d.ts.map +1 -1
  125. package/dist/src/orgrt/copilot-runner.js +2 -1
  126. package/dist/src/orgrt/copilot-runner.js.map +1 -1
  127. package/dist/src/orgrt/crush-runner.d.ts.map +1 -1
  128. package/dist/src/orgrt/crush-runner.js +6 -1
  129. package/dist/src/orgrt/crush-runner.js.map +1 -1
  130. package/dist/src/orgrt/daemon.d.ts +4 -0
  131. package/dist/src/orgrt/daemon.d.ts.map +1 -1
  132. package/dist/src/orgrt/daemon.js +13 -2
  133. package/dist/src/orgrt/daemon.js.map +1 -1
  134. package/dist/src/orgrt/decisions.d.ts +9 -0
  135. package/dist/src/orgrt/decisions.d.ts.map +1 -1
  136. package/dist/src/orgrt/decisions.js +32 -5
  137. package/dist/src/orgrt/decisions.js.map +1 -1
  138. package/dist/src/orgrt/file-roots.d.ts +2 -1
  139. package/dist/src/orgrt/file-roots.d.ts.map +1 -1
  140. package/dist/src/orgrt/file-roots.js +7 -3
  141. package/dist/src/orgrt/file-roots.js.map +1 -1
  142. package/dist/src/orgrt/grok-runner.d.ts.map +1 -1
  143. package/dist/src/orgrt/grok-runner.js +2 -1
  144. package/dist/src/orgrt/grok-runner.js.map +1 -1
  145. package/dist/src/orgrt/hermes-runner.d.ts.map +1 -1
  146. package/dist/src/orgrt/hermes-runner.js +2 -1
  147. package/dist/src/orgrt/hermes-runner.js.map +1 -1
  148. package/dist/src/orgrt/inbox.d.ts.map +1 -1
  149. package/dist/src/orgrt/inbox.js +70 -4
  150. package/dist/src/orgrt/inbox.js.map +1 -1
  151. package/dist/src/orgrt/kimicode-runner.d.ts.map +1 -1
  152. package/dist/src/orgrt/kimicode-runner.js +2 -1
  153. package/dist/src/orgrt/kimicode-runner.js.map +1 -1
  154. package/dist/src/orgrt/opencode-runner.d.ts.map +1 -1
  155. package/dist/src/orgrt/opencode-runner.js +3 -2
  156. package/dist/src/orgrt/opencode-runner.js.map +1 -1
  157. package/dist/src/orgrt/pi-rpc-runner.d.ts.map +1 -1
  158. package/dist/src/orgrt/pi-rpc-runner.js +3 -2
  159. package/dist/src/orgrt/pi-rpc-runner.js.map +1 -1
  160. package/dist/src/orgrt/pi-runner.d.ts.map +1 -1
  161. package/dist/src/orgrt/pi-runner.js +2 -1
  162. package/dist/src/orgrt/pi-runner.js.map +1 -1
  163. package/dist/src/orgrt/policy.d.ts.map +1 -1
  164. package/dist/src/orgrt/policy.js +3 -0
  165. package/dist/src/orgrt/policy.js.map +1 -1
  166. package/dist/src/orgrt/qwen-rpc-runner.d.ts.map +1 -1
  167. package/dist/src/orgrt/qwen-rpc-runner.js +3 -2
  168. package/dist/src/orgrt/qwen-rpc-runner.js.map +1 -1
  169. package/dist/src/orgrt/qwen-runner.d.ts.map +1 -1
  170. package/dist/src/orgrt/qwen-runner.js +2 -1
  171. package/dist/src/orgrt/qwen-runner.js.map +1 -1
  172. package/dist/src/orgrt/role-sandbox.d.ts +21 -0
  173. package/dist/src/orgrt/role-sandbox.d.ts.map +1 -1
  174. package/dist/src/orgrt/role-sandbox.js +31 -6
  175. package/dist/src/orgrt/role-sandbox.js.map +1 -1
  176. package/dist/src/orgrt/session.d.ts.map +1 -1
  177. package/dist/src/orgrt/session.js +21 -2
  178. package/dist/src/orgrt/session.js.map +1 -1
  179. package/dist/src/orgrt/task-dag.d.ts +3 -0
  180. package/dist/src/orgrt/task-dag.d.ts.map +1 -1
  181. package/dist/src/orgrt/task-dag.js +1 -0
  182. package/dist/src/orgrt/task-dag.js.map +1 -1
  183. package/dist/src/orgrt/types.d.ts +5 -0
  184. package/dist/src/orgrt/types.d.ts.map +1 -1
  185. package/dist/src/orgrt/types.js +23 -1
  186. package/dist/src/orgrt/types.js.map +1 -1
  187. package/dist/src/ui/dashboard.html +28 -848
  188. package/dist/src/ui/human-auth.mjs +103 -0
  189. package/dist/src/ui/org-hil.mjs +86 -37
  190. package/dist/src/ui/org-runtime.mjs +21 -13
  191. package/dist/src/ui/routes-org.mjs +49 -445
  192. package/dist/src/ui/server.mjs +90 -2
  193. package/dist/tsconfig.tsbuildinfo +1 -1
  194. package/package.json +6 -6
@@ -29,23 +29,43 @@ function hilRoot(req, ctx) {
29
29
  return path.resolve(dir || ctx.projectDir || process.cwd());
30
30
  }
31
31
 
32
- /** JSON body (≤64KB); null when absent or malformed. */
32
+ const HIL_BODY_MAX = 65536;
33
+
34
+ /** JSON body (≤64KB); null when malformed. Call inside hilRespond: an
35
+ * oversized body throws 413 and an aborted upload 400, instead of an
36
+ * unhandled rejection that would take the dashboard process down. Bytes are
37
+ * decoded once at the end, so a character split across chunks survives. */
33
38
  async function readHilBody(req) {
34
- let body = '';
35
- for await (const chunk of req) {
36
- body += chunk;
37
- if (body.length > 65536) {
38
- req.destroy();
39
- return null;
39
+ const chunks = [];
40
+ let size = 0;
41
+ try {
42
+ // Past the cap, keep draining (without keeping) so the 413 still reaches
43
+ // the client — leaving the iterator early destroys the socket.
44
+ for await (const chunk of req) {
45
+ size += chunk.length;
46
+ if (size <= HIL_BODY_MAX) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
40
47
  }
48
+ } catch (err) {
49
+ throw Object.assign(new Error(`request body not received: ${err.message}`), { status: 400 });
41
50
  }
51
+ if (size > HIL_BODY_MAX)
52
+ throw Object.assign(new Error('request body is over 64KB'), { status: 413 });
42
53
  try {
43
- return JSON.parse(body || '{}');
54
+ return JSON.parse(Buffer.concat(chunks).toString('utf8') || '{}');
44
55
  } catch (_) {
45
56
  return null;
46
57
  }
47
58
  }
48
59
 
60
+ /** decodeURIComponent for a path segment; a malformed escape is a 400. */
61
+ function decodeHilSegment(seg) {
62
+ try {
63
+ return decodeURIComponent(seg);
64
+ } catch (_) {
65
+ throw Object.assign(new Error(`malformed path segment: ${seg}`), { status: 400 });
66
+ }
67
+ }
68
+
49
69
  /** Run `fn` and answer with its JSON result, or the HIL error's status. */
50
70
  async function hilRespond(res, corsOrigin, fn) {
51
71
  const cors = corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {};
@@ -524,74 +544,6 @@ export async function handleOrgRoutes(req, res, url, corsOrigin, ctx) {
524
544
  return true;
525
545
  }
526
546
 
527
- // GET /api/org/:name/projects — org projects from projects json file
528
- if (req.method === 'GET' && /^\/api\/org\/[a-z0-9][a-z0-9_-]{0,63}\/projects$/i.test(url)) {
529
- try {
530
- const parts = url.split('/');
531
- const orgName = decodeURIComponent(parts[3]);
532
- if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) {
533
- res.writeHead(400);
534
- res.end('[]');
535
- return true;
536
- }
537
- const _projsQs = new URL(req.url, 'http://localhost').searchParams;
538
- const d = path.resolve(_projsQs.get('dir') || ctx.projectDir || process.cwd());
539
- const projFile = path.join(d, '.monomind', 'orgs', `${orgName}-projects.json`);
540
- if (!fs.existsSync(projFile)) {
541
- res.writeHead(200, {
542
- 'Content-Type': 'application/json',
543
- ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}),
544
- });
545
- res.end('[]');
546
- return true;
547
- }
548
- const data = JSON.parse(fs.readFileSync(projFile, 'utf8'));
549
- res.writeHead(200, {
550
- 'Content-Type': 'application/json',
551
- ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}),
552
- });
553
- res.end(JSON.stringify(data.projects || []));
554
- } catch (_) {
555
- res.writeHead(500);
556
- res.end('[]');
557
- }
558
- return true;
559
- }
560
-
561
- // GET /api/org/:name/members — org member list and join requests
562
- if (req.method === 'GET' && /^\/api\/org\/[a-z0-9][a-z0-9_-]{0,63}\/members$/i.test(url)) {
563
- try {
564
- const parts = url.split('/');
565
- const orgName = decodeURIComponent(parts[3]);
566
- if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) {
567
- res.writeHead(400);
568
- res.end('{}');
569
- return true;
570
- }
571
- const _membersQs = new URL(req.url, 'http://localhost').searchParams;
572
- const d = path.resolve(_membersQs.get('dir') || ctx.projectDir || process.cwd());
573
- const membersFile = path.join(d, '.monomind', 'orgs', `${orgName}-members.json`);
574
- if (!fs.existsSync(membersFile)) {
575
- res.writeHead(200, {
576
- 'Content-Type': 'application/json',
577
- ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}),
578
- });
579
- res.end('{"members":[],"join_requests":[]}');
580
- return true;
581
- }
582
- const data = JSON.parse(fs.readFileSync(membersFile, 'utf8'));
583
- res.writeHead(200, {
584
- 'Content-Type': 'application/json',
585
- ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}),
586
- });
587
- res.end(JSON.stringify(data));
588
- } catch (_) {
589
- res.writeHead(500);
590
- res.end('{}');
591
- }
592
- return true;
593
- }
594
-
595
547
  // GET /api/org/:name/adapters — org adapter registry
596
548
  if (req.method === 'GET' && /^\/api\/org\/[a-z0-9][a-z0-9_-]{0,63}\/adapters$/i.test(url)) {
597
549
  try {
@@ -1039,47 +991,6 @@ export async function handleOrgRoutes(req, res, url, corsOrigin, ctx) {
1039
991
  return true;
1040
992
  }
1041
993
 
1042
- // GET /api/org/:name/issues — org task/issue list from issues file
1043
- if (req.method === 'GET' && url.match(/^\/api\/org\/[a-z0-9][a-z0-9_-]{0,63}\/issues$/i)) {
1044
- try {
1045
- const orgName = decodeURIComponent(url.split('/')[3]);
1046
- if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) {
1047
- res.writeHead(400);
1048
- res.end('Invalid org name');
1049
- return true;
1050
- }
1051
- const _issuesQs = new URL(req.url, 'http://localhost').searchParams;
1052
- const _issuesDir = path.resolve(_issuesQs.get('dir') || ctx.projectDir || process.cwd());
1053
- const issuesPath = path.join(_issuesDir, '.monomind', 'orgs', `${orgName}-issues.json`);
1054
- const payload = { issues: [] };
1055
- try {
1056
- const raw = JSON.parse(fs.readFileSync(issuesPath, 'utf8'));
1057
- payload.issues = (raw.issues || []).map((i) => ({
1058
- id: i.id,
1059
- slug: i.slug,
1060
- title: i.title,
1061
- description: i.description || null,
1062
- status: i.status || 'open',
1063
- priority: i.priority || 'medium',
1064
- assignee_id: i.assignee_id || null,
1065
- assignee: i.assignee || i.assignee_id || null,
1066
- project_id: i.project_id || null,
1067
- parent_id: i.parent_id || null,
1068
- created_at: i.created_at,
1069
- updated_at: i.updated_at,
1070
- }));
1071
- } catch (_) {
1072
- /* file missing is fine */
1073
- }
1074
- res.writeHead(200, { 'Content-Type': 'application/json' });
1075
- res.end(JSON.stringify(payload));
1076
- } catch (_) {
1077
- res.writeHead(500);
1078
- res.end('{}');
1079
- }
1080
- return true;
1081
- }
1082
-
1083
994
  // GET /api/org/:name/health — aggregate org health, from the same runtime
1084
995
  // sources as /runtime: live role sessions, the task DAG, this run's usage on
1085
996
  // the budget's enforcement basis, and how the last 7 days of runs ended.
@@ -1094,7 +1005,12 @@ export async function handleOrgRoutes(req, res, url, corsOrigin, ctx) {
1094
1005
  ).length;
1095
1006
  const activeTasks = v.tasks.filter((t) => t.status === 'running').length;
1096
1007
  const weekAgo = Date.now() - 7 * 24 * 60 * 60 * 1000;
1097
- const week = v.history.filter((h) => (h.endedAt || 0) >= weekAgo);
1008
+ // A resumed run keeps its run id and appends a history line per stop:
1009
+ // count each run once, by its latest line (history is newest first).
1010
+ const seenRuns = new Set();
1011
+ const week = v.history.filter(
1012
+ (h) => (h.endedAt || 0) >= weekAgo && !seenRuns.has(h.run) && seenRuns.add(h.run),
1013
+ );
1098
1014
  // Only a boss's gate-checked org_complete with 'achieved' counts as success.
1099
1015
  const achieved = week.filter(
1100
1016
  (h) => h.closedBy === 'org-complete' && h.outcome?.status === 'achieved',
@@ -1118,149 +1034,6 @@ export async function handleOrgRoutes(req, res, url, corsOrigin, ctx) {
1118
1034
  });
1119
1035
  }
1120
1036
 
1121
- // GET /api/org/:name/environments — org execution environments (strips key material)
1122
- if (req.method === 'GET' && url.match(/^\/api\/org\/[a-z0-9][a-z0-9_-]{0,63}\/environments$/i)) {
1123
- try {
1124
- const orgName = decodeURIComponent(url.split('/')[3]);
1125
- if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) {
1126
- res.writeHead(400);
1127
- res.end('Invalid org name');
1128
- return true;
1129
- }
1130
- const _envsQs = new URL(req.url, 'http://localhost').searchParams;
1131
- const envsPath = path.join(
1132
- path.resolve(_envsQs.get('dir') || ctx.projectDir || process.cwd()),
1133
- '.monomind',
1134
- 'orgs',
1135
- `${orgName}-environments.json`,
1136
- );
1137
- const payload = { environments: [], default_env: null };
1138
- try {
1139
- const raw = JSON.parse(fs.readFileSync(envsPath, 'utf8'));
1140
- // Strip any accidental key_material or private_key fields — never send to browser
1141
- payload.default_env = raw.default_env || null;
1142
- payload.environments = (raw.environments || []).map((e) => {
1143
- const safe = { ...e };
1144
- delete safe.key_material;
1145
- delete safe.private_key;
1146
- delete safe.ssh_key;
1147
- delete safe.password;
1148
- return safe;
1149
- });
1150
- } catch (_) {
1151
- /* file missing is fine — return empty */
1152
- }
1153
- res.writeHead(200, { 'Content-Type': 'application/json' });
1154
- res.end(JSON.stringify(payload));
1155
- } catch (_) {
1156
- res.writeHead(500);
1157
- res.end('{}');
1158
- }
1159
- return true;
1160
- }
1161
-
1162
- // GET /api/org/:name/workspaces — org workspaces cross-referenced with worktree registry
1163
- if (req.method === 'GET' && url.match(/^\/api\/org\/[a-z0-9][a-z0-9_-]{0,63}\/workspaces$/i)) {
1164
- try {
1165
- const orgName = decodeURIComponent(url.split('/')[3]);
1166
- if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) {
1167
- res.writeHead(400);
1168
- res.end('Invalid org name');
1169
- return true;
1170
- }
1171
- const _wsQs = new URL(req.url, 'http://localhost').searchParams;
1172
- const base = path.join(
1173
- path.resolve(_wsQs.get('dir') || ctx.projectDir || process.cwd()),
1174
- '.monomind',
1175
- 'orgs',
1176
- );
1177
- const payload = { workspaces: [] };
1178
- try {
1179
- const wsRaw = JSON.parse(
1180
- fs.readFileSync(path.join(base, `${orgName}-workspaces.json`), 'utf8'),
1181
- );
1182
- const workspaces = wsRaw.workspaces || [];
1183
- // Optionally cross-reference worktree registry for branch/status enrichment
1184
- const worktreeMap = {};
1185
- try {
1186
- const wtRaw = JSON.parse(
1187
- fs.readFileSync(path.join(base, `${orgName}-worktrees.json`), 'utf8'),
1188
- );
1189
- (wtRaw.worktrees || []).forEach((wt) => {
1190
- worktreeMap[wt.path] = wt;
1191
- });
1192
- } catch (_) {
1193
- /* no worktree registry, that's fine */
1194
- }
1195
- payload.workspaces = workspaces.map((w) => {
1196
- const wt = w.worktree_path ? worktreeMap[w.worktree_path] : null;
1197
- return wt ? { ...w, branch: w.branch || wt.branch || w.branch } : w;
1198
- });
1199
- } catch (_) {
1200
- /* file missing is fine */
1201
- }
1202
- res.writeHead(200, { 'Content-Type': 'application/json' });
1203
- res.end(JSON.stringify(payload));
1204
- } catch (_) {
1205
- res.writeHead(500);
1206
- res.end('{}');
1207
- }
1208
- return true;
1209
- }
1210
-
1211
- // GET /api/org/:name/invites — active invites + pending join requests
1212
- if (
1213
- req.method === 'GET' &&
1214
- url.match(/^\/api\/org\/[a-z0-9][a-z0-9_-]{0,63}\/invites(\?.*)?$/i)
1215
- ) {
1216
- try {
1217
- const orgName = decodeURIComponent(url.split('/')[3].split('?')[0]);
1218
- if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) {
1219
- res.writeHead(400);
1220
- res.end('Invalid org name');
1221
- return true;
1222
- }
1223
- const _invitesQs = new URL(req.url, 'http://localhost').searchParams;
1224
- const base = path.join(
1225
- path.resolve(_invitesQs.get('dir') || ctx.projectDir || process.cwd()),
1226
- '.monomind',
1227
- 'orgs',
1228
- );
1229
- const payload = { invites: [], join_requests: [] };
1230
- try {
1231
- const raw = JSON.parse(fs.readFileSync(path.join(base, `${orgName}-members.json`), 'utf8'));
1232
- const all = raw.join_requests || [];
1233
- payload.invites = all
1234
- .filter((r) => r.type === 'invite' && r.status === 'pending')
1235
- .map((r) => ({
1236
- id: r.id,
1237
- token: r.token ? `${r.token.slice(0, 8)}…` : r.id,
1238
- role: r.role || 'operator',
1239
- createdAt: r.createdAt || null,
1240
- expiresAt: r.expiresAt || null,
1241
- status: r.status,
1242
- }));
1243
- payload.join_requests = all
1244
- .filter((r) => r.type !== 'invite' && r.status === 'pending_approval')
1245
- .map((r) => ({
1246
- id: r.id,
1247
- requestType: r.requestType || 'human',
1248
- role: r.role || 'viewer',
1249
- createdAt: r.createdAt || null,
1250
- message: r.message || '',
1251
- }));
1252
- } catch (_) {
1253
- /* members file missing */
1254
- }
1255
- res.writeHead(200, { 'Content-Type': 'application/json' });
1256
- res.end(JSON.stringify(payload));
1257
- } catch (_) {
1258
- res.writeHead(500);
1259
- res.end('{}');
1260
- }
1261
- return true;
1262
- }
1263
-
1264
1037
  // GET /api/org/:name/plugins — plugins from registry filtered/merged with org overrides
1265
1038
  if (req.method === 'GET' && url.match(/^\/api\/org\/[a-z0-9][a-z0-9_-]{0,63}\/plugins$/i)) {
1266
1039
  try {
@@ -1322,52 +1095,6 @@ export async function handleOrgRoutes(req, res, url, corsOrigin, ctx) {
1322
1095
  return true;
1323
1096
  }
1324
1097
 
1325
- // GET /api/org/:name/my-issues — open + in_progress issues (self-assignable queue)
1326
- if (req.method === 'GET' && url.match(/^\/api\/org\/[a-z0-9][a-z0-9_-]{0,63}\/my-issues$/i)) {
1327
- try {
1328
- const orgName = decodeURIComponent(url.split('/')[3]);
1329
- if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) {
1330
- res.writeHead(400);
1331
- res.end('Invalid org name');
1332
- return true;
1333
- }
1334
- const _myIssuesQs = new URL(req.url, 'http://localhost').searchParams;
1335
- const base = path.join(
1336
- path.resolve(_myIssuesQs.get('dir') || ctx.projectDir || process.cwd()),
1337
- '.monomind',
1338
- 'orgs',
1339
- );
1340
- const payload = { issues: [] };
1341
- try {
1342
- const raw = JSON.parse(fs.readFileSync(path.join(base, `${orgName}-issues.json`), 'utf8'));
1343
- // Return open + in_progress issues — the "my issues" queue for the operator
1344
- payload.issues = (raw.issues || [])
1345
- .filter((i) => i.status === 'open' || i.status === 'in_progress')
1346
- .map((i) => ({
1347
- id: i.id,
1348
- title: i.title || null,
1349
- description: i.description || null,
1350
- status: i.status || 'open',
1351
- priority: i.priority || 'medium',
1352
- assigneeId: i.assigneeId || i.assigned_to || null,
1353
- projectId: i.projectId || i.project_id || null,
1354
- createdAt: i.createdAt || null,
1355
- lastActivityAt: i.lastActivityAt || null,
1356
- updated_at: i.updated_at || i.lastActivityAt || i.updatedAt || i.ts || null,
1357
- ts: i.ts || i.updated_at || i.lastActivityAt || null,
1358
- }));
1359
- } catch (_) {
1360
- /* issues file missing */
1361
- }
1362
- res.writeHead(200, { 'Content-Type': 'application/json' });
1363
- res.end(JSON.stringify(payload));
1364
- } catch (_) {
1365
- res.writeHead(500);
1366
- res.end('{}');
1367
- }
1368
- return true;
1369
- }
1370
-
1371
1098
  // GET /api/org/:name/runtime — what the org is doing now, from the runtime's
1372
1099
  // own sources (live daemon status, run bus, idle watchdog, history).
1373
1100
  if (req.method === 'GET' && url.match(/^\/api\/org\/[a-z0-9][a-z0-9_-]{0,63}\/runtime$/i)) {
@@ -1385,9 +1112,9 @@ export async function handleOrgRoutes(req, res, url, corsOrigin, ctx) {
1385
1112
  // same OrgDefSchema parse `org run` does.
1386
1113
  if (req.method === 'POST' && url.match(/^\/api\/org\/[a-z0-9][a-z0-9_-]{0,63}\/config$/i)) {
1387
1114
  const orgName = decodeURIComponent(url.split('/')[3]);
1388
- const body = await readHilBody(req);
1389
1115
  const root = hilRoot(req, ctx);
1390
- return hilRespond(res, corsOrigin, () => {
1116
+ return hilRespond(res, corsOrigin, async () => {
1117
+ const body = await readHilBody(req);
1391
1118
  if (!body || typeof body !== 'object')
1392
1119
  throw Object.assign(new Error('JSON body required'), { status: 400 });
1393
1120
  try {
@@ -1500,16 +1227,14 @@ export async function handleOrgRoutes(req, res, url, corsOrigin, ctx) {
1500
1227
  // POST /api/questions/answer { dir, org, questionId, answer } — live to the
1501
1228
  // hosting daemon (operator credential), else queued for the org's next run.
1502
1229
  if (req.method === 'POST' && url === '/api/questions/answer') {
1503
- const body = await readHilBody(req);
1504
- const { org, questionId, answer } = body || {};
1505
- if (!validOrgName(org) || !questionId || typeof answer !== 'string' || !answer.trim())
1506
- return hilRespond(res, corsOrigin, () => {
1230
+ return hilRespond(res, corsOrigin, async () => {
1231
+ const body = await readHilBody(req);
1232
+ const { org, questionId, answer } = body || {};
1233
+ if (!validOrgName(org) || !questionId || typeof answer !== 'string' || !answer.trim())
1507
1234
  throw Object.assign(new Error('org, questionId and a non-empty answer are required'), {
1508
1235
  status: 400,
1509
1236
  });
1510
- });
1511
- const root = path.resolve(body.dir || ctx.projectDir || process.cwd());
1512
- return hilRespond(res, corsOrigin, async () => {
1237
+ const root = path.resolve(body.dir || ctx.projectDir || process.cwd());
1513
1238
  const r = await hil.answerQuestion(root, org, String(questionId), answer.trim());
1514
1239
  hilEvent(ctx, root, {
1515
1240
  type: 'org:question-answered',
@@ -1530,14 +1255,14 @@ export async function handleOrgRoutes(req, res, url, corsOrigin, ctx) {
1530
1255
  ) {
1531
1256
  const parts = url.split('?')[0].split('/');
1532
1257
  const orgName = decodeURIComponent(parts[3]);
1533
- const gateId = decodeURIComponent(parts[5]);
1534
- const body = await readHilBody(req);
1535
- const resolution =
1536
- typeof body?.resolution === 'string' && body.resolution.trim()
1537
- ? body.resolution.trim().slice(0, 4000)
1538
- : undefined;
1539
1258
  const root = hilRoot(req, ctx);
1540
1259
  return hilRespond(res, corsOrigin, async () => {
1260
+ const gateId = decodeHilSegment(parts[5]);
1261
+ const body = await readHilBody(req);
1262
+ const resolution =
1263
+ typeof body?.resolution === 'string' && body.resolution.trim()
1264
+ ? body.resolution.trim().slice(0, 4000)
1265
+ : undefined;
1541
1266
  if (typeof body?.approved !== 'boolean')
1542
1267
  throw Object.assign(new Error('approved (boolean) is required'), { status: 400 });
1543
1268
  const r = await hil.resolveGate(root, orgName, gateId, body.approved, resolution);
@@ -1561,10 +1286,10 @@ export async function handleOrgRoutes(req, res, url, corsOrigin, ctx) {
1561
1286
  ) {
1562
1287
  const parts = url.split('?')[0].split('/');
1563
1288
  const orgName = decodeURIComponent(parts[3]);
1564
- const requestId = decodeURIComponent(parts[5]);
1565
- const body = await readHilBody(req);
1566
1289
  const root = hilRoot(req, ctx);
1567
1290
  return hilRespond(res, corsOrigin, async () => {
1291
+ const requestId = decodeHilSegment(parts[5]);
1292
+ const body = await readHilBody(req);
1568
1293
  if (body?.action !== 'approve' && body?.action !== 'reject')
1569
1294
  throw Object.assign(new Error('action must be approve or reject'), { status: 400 });
1570
1295
  const approved = body.action === 'approve';
@@ -1581,54 +1306,6 @@ export async function handleOrgRoutes(req, res, url, corsOrigin, ctx) {
1581
1306
  });
1582
1307
  }
1583
1308
 
1584
- // GET /api/org/:name/secrets — masked secrets list (NEVER exposes values)
1585
- if (req.method === 'GET' && url.match(/^\/api\/org\/[a-z0-9][a-z0-9_-]{0,63}\/secrets$/i)) {
1586
- try {
1587
- const orgName = decodeURIComponent(url.split('/')[3]);
1588
- if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) {
1589
- res.writeHead(400);
1590
- res.end('Invalid org name');
1591
- return true;
1592
- }
1593
- const _secretsQs = new URL(req.url, 'http://localhost').searchParams;
1594
- const base = path.join(
1595
- path.resolve(_secretsQs.get('dir') || ctx.projectDir || process.cwd()),
1596
- '.monomind',
1597
- 'orgs',
1598
- );
1599
- const secretsDir = path.join(base, '.secrets');
1600
- const readJsonSafe = (f) => {
1601
- try {
1602
- return JSON.parse(fs.readFileSync(f, 'utf8'));
1603
- } catch (_) {
1604
- return null;
1605
- }
1606
- };
1607
- // Read secrets index — NEVER expose actual values
1608
- const indexFile = path.join(secretsDir, `${orgName}-index.json`);
1609
- const data = readJsonSafe(indexFile) || { secrets: [] };
1610
- const secrets = (data.secrets || []).map((s) => ({
1611
- name: s.name,
1612
- purpose: s.purpose || null,
1613
- maskedRef: s.maskedRef || `${(s.name || '').substring(0, 4)}***`,
1614
- status: s.status || 'active',
1615
- createdAt: s.createdAt || null,
1616
- rotatedAt: s.rotatedAt || null,
1617
- lastUsedAt: s.lastUsedAt || null,
1618
- usageCount: s.usageCount || 0,
1619
- }));
1620
- res.writeHead(200, {
1621
- 'Content-Type': 'application/json',
1622
- ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}),
1623
- });
1624
- res.end(JSON.stringify({ secrets }));
1625
- } catch (_) {
1626
- res.writeHead(500);
1627
- res.end('{"secrets":[]}');
1628
- }
1629
- return true;
1630
- }
1631
-
1632
1309
  // GET /api/org/:name/budgets — org and per-agent budget data
1633
1310
  // Returns: { org_budget: {limit_tokens, limit_usd}, agent_budgets: {agentId: {limit_usd}}, agents: [{id, title, tokens_in, tokens_out, total_cost_usd}] }
1634
1311
  if (req.method === 'GET' && url.match(/^\/api\/org\/[a-z0-9][a-z0-9_-]{0,63}\/budgets$/i)) {
@@ -1847,79 +1524,6 @@ export async function handleOrgRoutes(req, res, url, corsOrigin, ctx) {
1847
1524
  return true;
1848
1525
  }
1849
1526
 
1850
- // GET /api/org/:name/join-requests — pending join requests for this org
1851
- // Returns: { requests: [{id, requesterId, requesterName, type, status, createdAt, resolvedAt}], pending: N }
1852
- if (
1853
- req.method === 'GET' &&
1854
- url.match(/^\/api\/org\/[a-z0-9][a-z0-9_-]{0,63}\/join-requests(\?.*)?$/i)
1855
- ) {
1856
- try {
1857
- const orgName = decodeURIComponent(url.split('/')[3].split('?')[0]);
1858
- if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) {
1859
- res.writeHead(400);
1860
- res.end('Invalid org name');
1861
- return true;
1862
- }
1863
- const _joinQs = new URL(req.url, 'http://localhost').searchParams;
1864
- const joinFile = path.join(
1865
- path.resolve(_joinQs.get('dir') || ctx.projectDir || process.cwd()),
1866
- '.monomind',
1867
- 'orgs',
1868
- `${orgName}-join-requests.json`,
1869
- );
1870
- let requests = [];
1871
- try {
1872
- const raw = fs.readFileSync(joinFile, 'utf8');
1873
- const data = JSON.parse(raw);
1874
- requests = (data.requests || []).map((r) => ({
1875
- id: r.id,
1876
- requesterId: r.requesterId,
1877
- requesterName: r.requesterName || r.requesterId,
1878
- type: r.type || 'human',
1879
- status: r.status || 'pending_approval',
1880
- createdAt: r.createdAt,
1881
- resolvedAt: r.resolvedAt || null,
1882
- }));
1883
- } catch (_) {}
1884
- const pending = requests.filter((r) => r.status === 'pending_approval').length;
1885
- res.writeHead(200, { 'Content-Type': 'application/json' });
1886
- res.end(JSON.stringify({ requests, pending }));
1887
- } catch (_) {
1888
- res.writeHead(500);
1889
- res.end('{"requests":[],"pending":0}');
1890
- }
1891
- return true;
1892
- }
1893
-
1894
- // GET /api/org/:name/goals — read org goals
1895
- if (req.method === 'GET' && url.match(/^\/api\/org\/[a-z0-9][a-z0-9_-]{0,63}\/goals$/i)) {
1896
- try {
1897
- const orgName = decodeURIComponent(url.split('/')[3]);
1898
- if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) {
1899
- res.writeHead(400);
1900
- res.end('Invalid org name');
1901
- return true;
1902
- }
1903
- const _goalsQs = new URL(req.url, 'http://localhost').searchParams;
1904
- const _goalsRoot = path.resolve(_goalsQs.get('dir') || ctx.projectDir || process.cwd());
1905
- const _goalsProjDir = ctx._resolveOrgProjectDir(orgName, _goalsRoot) || _goalsRoot;
1906
- const goalsFile = path.join(_goalsProjDir, '.monomind', 'orgs', `${orgName}-goals.json`);
1907
- let data = { goals: [] };
1908
- try {
1909
- data = JSON.parse(fs.readFileSync(goalsFile, 'utf8'));
1910
- } catch (_) {}
1911
- res.writeHead(200, {
1912
- 'Content-Type': 'application/json',
1913
- ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}),
1914
- });
1915
- res.end(JSON.stringify({ goals: data.goals || [] }));
1916
- } catch (_) {
1917
- res.writeHead(500);
1918
- res.end('{"goals":[]}');
1919
- }
1920
- return true;
1921
- }
1922
-
1923
1527
  // GET /api/org/:name/routines — read org routines (falls back to synthesizing from org config's loop object)
1924
1528
  if (req.method === 'GET' && url.match(/^\/api\/org\/[a-z0-9][a-z0-9_-]{0,63}\/routines$/i)) {
1925
1529
  try {