@monoes/monomindcli 2.16.12 → 2.16.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/agents/core/coder.md +1 -1
- package/.claude/agents/engineering/engineering-devops-automator.md +1 -1
- package/.claude/agents/github/repo-architect.md +1 -1
- package/.claude/agents/testing/production-validator.md +1 -1
- package/.claude/agents/testing/testing-tool-evaluator.md +1 -1
- package/.claude/commands/github/pr-manager.md +1 -1
- package/.claude/helpers/pick-rank.cjs +34 -4
- package/.claude/skills/monolean-audit/SKILL.md +1 -1
- package/dist/src/commands/cleanup-data.d.ts +30 -0
- package/dist/src/commands/cleanup-data.d.ts.map +1 -0
- package/dist/src/commands/cleanup-data.js +138 -0
- package/dist/src/commands/cleanup-data.js.map +1 -0
- package/dist/src/commands/cleanup-scratch.d.ts +43 -0
- package/dist/src/commands/cleanup-scratch.d.ts.map +1 -0
- package/dist/src/commands/cleanup-scratch.js +125 -0
- package/dist/src/commands/cleanup-scratch.js.map +1 -0
- package/dist/src/commands/cleanup.d.ts +2 -39
- package/dist/src/commands/cleanup.d.ts.map +1 -1
- package/dist/src/commands/cleanup.js +5 -247
- package/dist/src/commands/cleanup.js.map +1 -1
- package/dist/src/commands/doctor-gitignore-checks.d.ts +10 -0
- package/dist/src/commands/doctor-gitignore-checks.d.ts.map +1 -0
- package/dist/src/commands/doctor-gitignore-checks.js +141 -0
- package/dist/src/commands/doctor-gitignore-checks.js.map +1 -0
- package/dist/src/commands/doctor-helpers-checks.d.ts +10 -0
- package/dist/src/commands/doctor-helpers-checks.d.ts.map +1 -0
- package/dist/src/commands/doctor-helpers-checks.js +292 -0
- package/dist/src/commands/doctor-helpers-checks.js.map +1 -0
- package/dist/src/commands/doctor-memory-checks.d.ts +47 -0
- package/dist/src/commands/doctor-memory-checks.d.ts.map +1 -0
- package/dist/src/commands/doctor-memory-checks.js +298 -0
- package/dist/src/commands/doctor-memory-checks.js.map +1 -0
- package/dist/src/commands/doctor-monograph-checks.d.ts +9 -0
- package/dist/src/commands/doctor-monograph-checks.d.ts.map +1 -0
- package/dist/src/commands/doctor-monograph-checks.js +331 -0
- package/dist/src/commands/doctor-monograph-checks.js.map +1 -0
- package/dist/src/commands/doctor-project-checks.d.ts +7 -66
- package/dist/src/commands/doctor-project-checks.d.ts.map +1 -1
- package/dist/src/commands/doctor-project-checks.js +10 -1331
- package/dist/src/commands/doctor-project-checks.js.map +1 -1
- package/dist/src/commands/doctor-routing-checks.d.ts +10 -0
- package/dist/src/commands/doctor-routing-checks.d.ts.map +1 -0
- package/dist/src/commands/doctor-routing-checks.js +194 -0
- package/dist/src/commands/doctor-routing-checks.js.map +1 -0
- package/dist/src/commands/doctor-worker-checks.d.ts +17 -0
- package/dist/src/commands/doctor-worker-checks.d.ts.map +1 -0
- package/dist/src/commands/doctor-worker-checks.js +122 -0
- package/dist/src/commands/doctor-worker-checks.js.map +1 -0
- package/dist/src/commands/hooks-routing-commands.js +1 -1
- package/dist/src/commands/hooks-routing-commands.js.map +1 -1
- package/dist/src/commands/init.d.ts.map +1 -1
- package/dist/src/commands/init.js +6 -15
- package/dist/src/commands/init.js.map +1 -1
- package/dist/src/index.js +2 -2
- package/dist/src/index.js.map +1 -1
- package/dist/src/init/never-commit.d.ts +3 -3
- package/dist/src/init/types.d.ts +1 -1
- package/dist/src/init/types.js +1 -1
- package/dist/src/knowledge/document-store.js +1 -1
- package/dist/src/knowledge/document-store.js.map +1 -1
- package/dist/src/mcp-tools/hooks-edit-command.d.ts +10 -0
- package/dist/src/mcp-tools/hooks-edit-command.d.ts.map +1 -0
- package/dist/src/mcp-tools/hooks-edit-command.js +247 -0
- package/dist/src/mcp-tools/hooks-edit-command.js.map +1 -0
- package/dist/src/mcp-tools/hooks-learning.d.ts +9 -0
- package/dist/src/mcp-tools/hooks-learning.d.ts.map +1 -0
- package/dist/src/mcp-tools/hooks-learning.js +408 -0
- package/dist/src/mcp-tools/hooks-learning.js.map +1 -0
- package/dist/src/mcp-tools/hooks-metrics-list.d.ts +8 -0
- package/dist/src/mcp-tools/hooks-metrics-list.d.ts.map +1 -0
- package/dist/src/mcp-tools/hooks-metrics-list.js +205 -0
- package/dist/src/mcp-tools/hooks-metrics-list.js.map +1 -0
- package/dist/src/mcp-tools/hooks-route.d.ts +12 -0
- package/dist/src/mcp-tools/hooks-route.d.ts.map +1 -0
- package/dist/src/mcp-tools/hooks-route.js +251 -0
- package/dist/src/mcp-tools/hooks-route.js.map +1 -0
- package/dist/src/mcp-tools/hooks-routing.d.ts +6 -27
- package/dist/src/mcp-tools/hooks-routing.d.ts.map +1 -1
- package/dist/src/mcp-tools/hooks-routing.js +6 -1727
- package/dist/src/mcp-tools/hooks-routing.js.map +1 -1
- package/dist/src/mcp-tools/hooks-session.d.ts +9 -0
- package/dist/src/mcp-tools/hooks-session.d.ts.map +1 -0
- package/dist/src/mcp-tools/hooks-session.js +243 -0
- package/dist/src/mcp-tools/hooks-session.js.map +1 -0
- package/dist/src/mcp-tools/hooks-task.d.ts +17 -0
- package/dist/src/mcp-tools/hooks-task.d.ts.map +1 -0
- package/dist/src/mcp-tools/hooks-task.js +427 -0
- package/dist/src/mcp-tools/hooks-task.js.map +1 -0
- package/dist/src/mcp-tools/hooks-tools.d.ts +2 -1
- package/dist/src/mcp-tools/hooks-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/hooks-tools.js +2 -1
- package/dist/src/mcp-tools/hooks-tools.js.map +1 -1
- package/dist/src/mcp-tools/memory-tools.js +1 -1
- package/dist/src/mcp-tools/memory-tools.js.map +1 -1
- package/dist/src/memory/bm25-index.d.ts +2 -2
- package/dist/src/memory/bm25-index.js +2 -2
- package/dist/src/memory/embedding-operations.js +2 -2
- package/dist/src/memory/embedding-operations.js.map +1 -1
- package/dist/src/memory/entity-name-key.d.ts +1 -1
- package/dist/src/memory/entity-name-key.js +1 -1
- package/dist/src/memory/hnsw-operations.d.ts +1 -1
- package/dist/src/memory/hnsw-operations.js +1 -1
- package/dist/src/memory/memory-bridge-backend.d.ts +43 -0
- package/dist/src/memory/memory-bridge-backend.d.ts.map +1 -0
- package/dist/src/memory/memory-bridge-backend.js +363 -0
- package/dist/src/memory/memory-bridge-backend.js.map +1 -0
- package/dist/src/memory/memory-bridge-core.d.ts +42 -0
- package/dist/src/memory/memory-bridge-core.d.ts.map +1 -0
- package/dist/src/memory/memory-bridge-core.js +126 -0
- package/dist/src/memory/memory-bridge-core.js.map +1 -0
- package/dist/src/memory/memory-bridge-entries.d.ts +131 -0
- package/dist/src/memory/memory-bridge-entries.d.ts.map +1 -0
- package/dist/src/memory/memory-bridge-entries.js +227 -0
- package/dist/src/memory/memory-bridge-entries.js.map +1 -0
- package/dist/src/memory/memory-bridge-feedback.d.ts +60 -0
- package/dist/src/memory/memory-bridge-feedback.d.ts.map +1 -0
- package/dist/src/memory/memory-bridge-feedback.js +118 -0
- package/dist/src/memory/memory-bridge-feedback.js.map +1 -0
- package/dist/src/memory/memory-bridge-paths.d.ts +123 -0
- package/dist/src/memory/memory-bridge-paths.d.ts.map +1 -0
- package/dist/src/memory/memory-bridge-paths.js +335 -0
- package/dist/src/memory/memory-bridge-paths.js.map +1 -0
- package/dist/src/memory/memory-bridge-search.d.ts +48 -0
- package/dist/src/memory/memory-bridge-search.d.ts.map +1 -0
- package/dist/src/memory/memory-bridge-search.js +383 -0
- package/dist/src/memory/memory-bridge-search.js.map +1 -0
- package/dist/src/memory/memory-bridge-sessions.d.ts +38 -0
- package/dist/src/memory/memory-bridge-sessions.d.ts.map +1 -0
- package/dist/src/memory/memory-bridge-sessions.js +109 -0
- package/dist/src/memory/memory-bridge-sessions.js.map +1 -0
- package/dist/src/memory/memory-bridge-store.d.ts +58 -0
- package/dist/src/memory/memory-bridge-store.d.ts.map +1 -0
- package/dist/src/memory/memory-bridge-store.js +201 -0
- package/dist/src/memory/memory-bridge-store.js.map +1 -0
- package/dist/src/memory/memory-bridge.d.ts +10 -438
- package/dist/src/memory/memory-bridge.d.ts.map +1 -1
- package/dist/src/memory/memory-bridge.js +14 -1787
- package/dist/src/memory/memory-bridge.js.map +1 -1
- package/dist/src/memory/memory-kg-claims.d.ts +52 -0
- package/dist/src/memory/memory-kg-claims.d.ts.map +1 -0
- package/dist/src/memory/memory-kg-claims.js +99 -0
- package/dist/src/memory/memory-kg-claims.js.map +1 -0
- package/dist/src/memory/memory-kg-index.d.ts +86 -0
- package/dist/src/memory/memory-kg-index.d.ts.map +1 -0
- package/dist/src/memory/memory-kg-index.js +256 -0
- package/dist/src/memory/memory-kg-index.js.map +1 -0
- package/dist/src/memory/memory-kg-ingest.d.ts +44 -0
- package/dist/src/memory/memory-kg-ingest.d.ts.map +1 -0
- package/dist/src/memory/memory-kg-ingest.js +361 -0
- package/dist/src/memory/memory-kg-ingest.js.map +1 -0
- package/dist/src/memory/memory-kg-model.d.ts +168 -0
- package/dist/src/memory/memory-kg-model.d.ts.map +1 -0
- package/dist/src/memory/memory-kg-model.js +158 -0
- package/dist/src/memory/memory-kg-model.js.map +1 -0
- package/dist/src/memory/memory-kg-names.d.ts +64 -0
- package/dist/src/memory/memory-kg-names.d.ts.map +1 -0
- package/dist/src/memory/memory-kg-names.js +187 -0
- package/dist/src/memory/memory-kg-names.js.map +1 -0
- package/dist/src/memory/memory-kg-promote.d.ts +79 -0
- package/dist/src/memory/memory-kg-promote.d.ts.map +1 -0
- package/dist/src/memory/memory-kg-promote.js +248 -0
- package/dist/src/memory/memory-kg-promote.js.map +1 -0
- package/dist/src/memory/memory-kg-rebuild.d.ts +77 -0
- package/dist/src/memory/memory-kg-rebuild.d.ts.map +1 -0
- package/dist/src/memory/memory-kg-rebuild.js +299 -0
- package/dist/src/memory/memory-kg-rebuild.js.map +1 -0
- package/dist/src/memory/memory-kg-rollback.d.ts +47 -0
- package/dist/src/memory/memory-kg-rollback.d.ts.map +1 -0
- package/dist/src/memory/memory-kg-rollback.js +193 -0
- package/dist/src/memory/memory-kg-rollback.js.map +1 -0
- package/dist/src/memory/memory-kg-rules.d.ts +64 -0
- package/dist/src/memory/memory-kg-rules.d.ts.map +1 -0
- package/dist/src/memory/memory-kg-rules.js +251 -0
- package/dist/src/memory/memory-kg-rules.js.map +1 -0
- package/dist/src/memory/memory-kg-scan.d.ts +118 -0
- package/dist/src/memory/memory-kg-scan.d.ts.map +1 -0
- package/dist/src/memory/memory-kg-scan.js +209 -0
- package/dist/src/memory/memory-kg-scan.js.map +1 -0
- package/dist/src/memory/memory-kg-search.d.ts +80 -0
- package/dist/src/memory/memory-kg-search.d.ts.map +1 -0
- package/dist/src/memory/memory-kg-search.js +239 -0
- package/dist/src/memory/memory-kg-search.js.map +1 -0
- package/dist/src/memory/memory-kg.d.ts +19 -547
- package/dist/src/memory/memory-kg.d.ts.map +1 -1
- package/dist/src/memory/memory-kg.js +10 -2389
- package/dist/src/memory/memory-kg.js.map +1 -1
- package/dist/src/ui/org-hil.mjs +1 -1
- package/dist/src/ui/routes-org-agents.mjs +318 -0
- package/dist/src/ui/routes-org-approvals.mjs +133 -0
- package/dist/src/ui/routes-org-config.mjs +459 -0
- package/dist/src/ui/routes-org-control.mjs +300 -0
- package/dist/src/ui/routes-org-files.mjs +172 -0
- package/dist/src/ui/routes-org-helpers.mjs +84 -0
- package/dist/src/ui/routes-org-knowledge.mjs +262 -0
- package/dist/src/ui/routes-org-lifecycle.mjs +224 -0
- package/dist/src/ui/routes-org-live.mjs +278 -0
- package/dist/src/ui/routes-org-mastermind.mjs +346 -0
- package/dist/src/ui/routes-org-planning.mjs +398 -0
- package/dist/src/ui/routes-org-runs.mjs +430 -0
- package/dist/src/ui/routes-org-status.mjs +342 -0
- package/dist/src/ui/routes-org-workflows.mjs +212 -0
- package/dist/src/ui/routes-org.mjs +26 -3847
- package/dist/src/ui/server.mjs +1 -1
- package/dist/src/utils/native-error.d.ts +1 -1
- package/dist/src/utils/native-error.js +1 -1
- package/dist/src/utils/paths.d.ts +2 -1
- package/dist/src/utils/paths.d.ts.map +1 -1
- package/dist/src/utils/paths.js +2 -1
- package/dist/src/utils/paths.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/org-skills/adversarial-reviewer/SKILL.md +1 -1
- package/org-skills/board-deck-builder/SKILL.md +1 -1
- package/org-skills/code-reviewer/SKILL.md +1 -1
- package/org-skills/debugging-wizard/SKILL.md +1 -1
- package/org-skills/scientific-thinking-literature-review/SKILL.md +1 -1
- package/org-skills/scientific-thinking-scholar-evaluation/SKILL.md +1 -1
- package/package.json +8 -8
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import * as hil from './org-hil.mjs';
|
|
3
|
+
import {
|
|
4
|
+
decodeHilSegment,
|
|
5
|
+
hilEvent,
|
|
6
|
+
hilRespond,
|
|
7
|
+
hilRoot,
|
|
8
|
+
readHilBody,
|
|
9
|
+
validOrgName,
|
|
10
|
+
} from './routes-org-helpers.mjs';
|
|
11
|
+
|
|
12
|
+
// Org dashboard routes: human-in-the-loop approvals, gates and questions.
|
|
13
|
+
// Registered, in order, by handleOrgRoutes in routes-org.mjs.
|
|
14
|
+
export async function handleOrgApprovalRoutes(req, res, url, corsOrigin, ctx) {
|
|
15
|
+
// GET /api/org/:name/approvals — the org's tool/action approvals
|
|
16
|
+
// (<org>/approvals.json, written by orgrt/approvals.ts), newest first.
|
|
17
|
+
if (
|
|
18
|
+
req.method === 'GET' &&
|
|
19
|
+
url.match(/^\/api\/org\/[a-z0-9][a-z0-9_-]{0,63}\/approvals(\?.*)?$/i)
|
|
20
|
+
) {
|
|
21
|
+
const orgName = decodeURIComponent(url.split('/')[3].split('?')[0]);
|
|
22
|
+
return hilRespond(res, corsOrigin, () => {
|
|
23
|
+
const approvals = hil.listApprovals(hilRoot(req, ctx), orgName);
|
|
24
|
+
return { approvals, pending: approvals.filter((a) => a.status === 'pending').length };
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// GET /api/org/:name/gates — the org's decision gates (<org>/gates.json).
|
|
29
|
+
if (req.method === 'GET' && url.match(/^\/api\/org\/[a-z0-9][a-z0-9_-]{0,63}\/gates(\?.*)?$/i)) {
|
|
30
|
+
const orgName = decodeURIComponent(url.split('/')[3].split('?')[0]);
|
|
31
|
+
return hilRespond(res, corsOrigin, () => {
|
|
32
|
+
const gates = hil.listGates(hilRoot(req, ctx), orgName);
|
|
33
|
+
return { gates, pending: gates.filter((g) => g.status === 'pending').length };
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// GET /api/questions?dir= — ask_human questions (pending and answered) for
|
|
38
|
+
// every org in one project, each with its `blocking` flag.
|
|
39
|
+
if (req.method === 'GET' && (url === '/api/questions' || url.startsWith('/api/questions?'))) {
|
|
40
|
+
return hilRespond(res, corsOrigin, () => ({
|
|
41
|
+
questions: hil.pendingForProject(hilRoot(req, ctx)).questions,
|
|
42
|
+
}));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// GET /api/hil?dir= — everything in one project that waits on a human:
|
|
46
|
+
// questions, approvals and decision gates across all its orgs.
|
|
47
|
+
if (req.method === 'GET' && (url === '/api/hil' || url.startsWith('/api/hil?'))) {
|
|
48
|
+
return hilRespond(res, corsOrigin, () => hil.pendingForProject(hilRoot(req, ctx)));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// POST /api/questions/answer { dir, org, questionId, answer } — live to the
|
|
52
|
+
// hosting daemon (operator credential), else queued for the org's next run.
|
|
53
|
+
if (req.method === 'POST' && url === '/api/questions/answer') {
|
|
54
|
+
return hilRespond(res, corsOrigin, async () => {
|
|
55
|
+
const body = await readHilBody(req);
|
|
56
|
+
const { org, questionId, answer } = body || {};
|
|
57
|
+
if (!validOrgName(org) || !questionId || typeof answer !== 'string' || !answer.trim())
|
|
58
|
+
throw Object.assign(new Error('org, questionId and a non-empty answer are required'), {
|
|
59
|
+
status: 400,
|
|
60
|
+
});
|
|
61
|
+
const root = path.resolve(body.dir || ctx.projectDir || process.cwd());
|
|
62
|
+
const r = await hil.answerQuestion(root, org, String(questionId), answer.trim());
|
|
63
|
+
hilEvent(ctx, root, {
|
|
64
|
+
type: 'org:question-answered',
|
|
65
|
+
org,
|
|
66
|
+
role: r.role,
|
|
67
|
+
questionId,
|
|
68
|
+
delivery: r.delivery,
|
|
69
|
+
});
|
|
70
|
+
return { ok: true, ...r };
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// POST /api/org/:name/gates/:id { approved: boolean, resolution? } — resolve
|
|
75
|
+
// a decision gate, live or (org not running) in gates.json.
|
|
76
|
+
if (
|
|
77
|
+
req.method === 'POST' &&
|
|
78
|
+
url.match(/^\/api\/org\/[a-z0-9][a-z0-9_-]{0,63}\/gates\/[^/?]+(\?.*)?$/i)
|
|
79
|
+
) {
|
|
80
|
+
const parts = url.split('?')[0].split('/');
|
|
81
|
+
const orgName = decodeURIComponent(parts[3]);
|
|
82
|
+
const root = hilRoot(req, ctx);
|
|
83
|
+
return hilRespond(res, corsOrigin, async () => {
|
|
84
|
+
const gateId = decodeHilSegment(parts[5]);
|
|
85
|
+
const body = await readHilBody(req);
|
|
86
|
+
const resolution =
|
|
87
|
+
typeof body?.resolution === 'string' && body.resolution.trim()
|
|
88
|
+
? body.resolution.trim().slice(0, 4000)
|
|
89
|
+
: undefined;
|
|
90
|
+
if (typeof body?.approved !== 'boolean')
|
|
91
|
+
throw Object.assign(new Error('approved (boolean) is required'), { status: 400 });
|
|
92
|
+
const r = await hil.resolveGate(root, orgName, gateId, body.approved, resolution);
|
|
93
|
+
const status = body.approved ? 'approved' : 'rejected';
|
|
94
|
+
hilEvent(ctx, root, {
|
|
95
|
+
type: 'org:gate:resolved',
|
|
96
|
+
org: orgName,
|
|
97
|
+
gateId,
|
|
98
|
+
status,
|
|
99
|
+
delivery: r.delivery,
|
|
100
|
+
});
|
|
101
|
+
return { ok: true, status, ...r };
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// POST /api/org/:name/approvals/:requestId { action: "approve" | "reject" } —
|
|
106
|
+
// resolve one tool/action approval, live or (org not running) in approvals.json.
|
|
107
|
+
if (
|
|
108
|
+
req.method === 'POST' &&
|
|
109
|
+
url.match(/^\/api\/org\/[a-z0-9][a-z0-9_-]{0,63}\/approvals\/[^/?]+(\?.*)?$/i)
|
|
110
|
+
) {
|
|
111
|
+
const parts = url.split('?')[0].split('/');
|
|
112
|
+
const orgName = decodeURIComponent(parts[3]);
|
|
113
|
+
const root = hilRoot(req, ctx);
|
|
114
|
+
return hilRespond(res, corsOrigin, async () => {
|
|
115
|
+
const requestId = decodeHilSegment(parts[5]);
|
|
116
|
+
const body = await readHilBody(req);
|
|
117
|
+
if (body?.action !== 'approve' && body?.action !== 'reject')
|
|
118
|
+
throw Object.assign(new Error('action must be approve or reject'), { status: 400 });
|
|
119
|
+
const approved = body.action === 'approve';
|
|
120
|
+
const r = await hil.resolveApproval(root, orgName, requestId, approved);
|
|
121
|
+
const status = approved ? 'approved' : 'denied';
|
|
122
|
+
hilEvent(ctx, root, {
|
|
123
|
+
type: 'org:approval:resolved',
|
|
124
|
+
org: orgName,
|
|
125
|
+
requestId,
|
|
126
|
+
status,
|
|
127
|
+
delivery: r.delivery,
|
|
128
|
+
});
|
|
129
|
+
return { ok: true, status, ...r };
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
@@ -0,0 +1,459 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { approvalsOrEmpty } from './routes-org-helpers.mjs';
|
|
4
|
+
|
|
5
|
+
// Org dashboard routes: org list, import, create, config and activity.
|
|
6
|
+
// Registered, in order, by handleOrgRoutes in routes-org.mjs.
|
|
7
|
+
export async function handleOrgConfigRoutes(req, res, url, corsOrigin, ctx) {
|
|
8
|
+
// ------------------------------------------------- Org management
|
|
9
|
+
// GET /api/orgs — list all saved org configs
|
|
10
|
+
if (req.method === 'GET' && url === '/api/orgs') {
|
|
11
|
+
try {
|
|
12
|
+
const _orgsQs = new URL(req.url, 'http://localhost').searchParams;
|
|
13
|
+
const _orgsExplicitDir = _orgsQs.get('dir');
|
|
14
|
+
const _orgsServerRoot = path.resolve(_orgsExplicitDir || ctx.projectDir || process.cwd());
|
|
15
|
+
// Collect project dirs to search: explicit dir + known-projects (like sessions API)
|
|
16
|
+
const _orgsProjDirs = new Set([_orgsServerRoot]);
|
|
17
|
+
if (!_orgsExplicitDir) {
|
|
18
|
+
try {
|
|
19
|
+
const _knownOrgsFile = path.join(_orgsServerRoot, 'data', 'known-projects.json');
|
|
20
|
+
if (fs.existsSync(_knownOrgsFile)) {
|
|
21
|
+
JSON.parse(fs.readFileSync(_knownOrgsFile, 'utf8')).forEach((p) =>
|
|
22
|
+
_orgsProjDirs.add(p),
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
} catch (_) {}
|
|
26
|
+
}
|
|
27
|
+
const _sidecarSuffixRe =
|
|
28
|
+
/-(approvals|state|activity|goals|routines|projects|members|issues|workspaces|worktrees|environments|plugins|adapters|bootstrap|threads|budgets|project-workspaces|approval-comments|secrets|join-requests|skills)\.json$/;
|
|
29
|
+
const _orgsSeen = new Set();
|
|
30
|
+
const orgs = [];
|
|
31
|
+
for (const _opd of _orgsProjDirs) {
|
|
32
|
+
const orgsDir = path.join(_opd, '.monomind', 'orgs');
|
|
33
|
+
if (!fs.existsSync(orgsDir)) continue;
|
|
34
|
+
const files = fs
|
|
35
|
+
.readdirSync(orgsDir)
|
|
36
|
+
.filter((f) => f.endsWith('.json') && !_sidecarSuffixRe.test(f));
|
|
37
|
+
for (const f of files) {
|
|
38
|
+
try {
|
|
39
|
+
const cfg = JSON.parse(fs.readFileSync(path.join(orgsDir, f), 'utf8'));
|
|
40
|
+
const _lOrgName = cfg.name || '';
|
|
41
|
+
if (!_lOrgName || _orgsSeen.has(_lOrgName)) continue;
|
|
42
|
+
_orgsSeen.add(_lOrgName);
|
|
43
|
+
const _rs = ctx._readRunState(_lOrgName, _opd);
|
|
44
|
+
const _ttl = Math.max((_rs?.checkpointInterval || 600000) * 2, 7200000);
|
|
45
|
+
const running =
|
|
46
|
+
(_rs?.status === 'running' && Date.now() - (_rs?.lastEventAt || 0) < _ttl) ||
|
|
47
|
+
ctx.activeOrgRuns.has(_lOrgName);
|
|
48
|
+
orgs.push({
|
|
49
|
+
name: cfg.name,
|
|
50
|
+
goal: cfg.goal,
|
|
51
|
+
roles: Array.isArray(cfg.roles) ? cfg.roles : [],
|
|
52
|
+
topology: cfg.topology,
|
|
53
|
+
created_at: cfg.created_at,
|
|
54
|
+
running,
|
|
55
|
+
status: cfg.status,
|
|
56
|
+
projectDir: _opd,
|
|
57
|
+
lastEventAt: _rs?.lastEventAt || null,
|
|
58
|
+
loop: cfg.loop
|
|
59
|
+
? {
|
|
60
|
+
poll_interval_minutes: cfg.loop.poll_interval_minutes,
|
|
61
|
+
last_run: cfg.loop.last_run,
|
|
62
|
+
next_run: cfg.loop.next_run,
|
|
63
|
+
}
|
|
64
|
+
: undefined,
|
|
65
|
+
});
|
|
66
|
+
} catch (_) {}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
res.writeHead(200, {
|
|
70
|
+
'Content-Type': 'application/json',
|
|
71
|
+
...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}),
|
|
72
|
+
});
|
|
73
|
+
res.end(JSON.stringify(orgs));
|
|
74
|
+
} catch (_) {
|
|
75
|
+
res.writeHead(500);
|
|
76
|
+
res.end('[]');
|
|
77
|
+
}
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// POST /api/orgs/:name/import — import an org config by name (orgs.html upload flow)
|
|
82
|
+
if (req.method === 'POST' && /^\/api\/orgs\/[a-z0-9][a-z0-9_-]{0,63}\/import$/i.test(url)) {
|
|
83
|
+
let body = '';
|
|
84
|
+
req.on('data', (c) => {
|
|
85
|
+
body += c;
|
|
86
|
+
if (body.length > 2e6) req.destroy();
|
|
87
|
+
});
|
|
88
|
+
req.on('end', () => {
|
|
89
|
+
try {
|
|
90
|
+
const urlParts = url.split('/');
|
|
91
|
+
const orgName = decodeURIComponent(urlParts[3]);
|
|
92
|
+
if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) {
|
|
93
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
94
|
+
res.end(JSON.stringify({ error: 'Invalid org name' }));
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
const cfg = JSON.parse(body);
|
|
98
|
+
const _importQs = new URL(req.url, 'http://localhost').searchParams;
|
|
99
|
+
const dir = path.resolve(_importQs.get('dir') || ctx.projectDir || process.cwd());
|
|
100
|
+
const orgsDir = path.join(dir, '.monomind', 'orgs');
|
|
101
|
+
fs.mkdirSync(orgsDir, { recursive: true });
|
|
102
|
+
const destFile = path.join(orgsDir, `${orgName}.json`);
|
|
103
|
+
fs.writeFileSync(destFile, JSON.stringify({ ...cfg, name: orgName }, null, 2), 'utf8');
|
|
104
|
+
res.writeHead(200, {
|
|
105
|
+
'Content-Type': 'application/json',
|
|
106
|
+
...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}),
|
|
107
|
+
});
|
|
108
|
+
res.end(JSON.stringify({ ok: true, name: orgName, file: destFile }));
|
|
109
|
+
} catch (e) {
|
|
110
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
111
|
+
res.end(JSON.stringify({ error: e.message }));
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// POST /api/orgs — import / create org from JSON body
|
|
118
|
+
if (req.method === 'POST' && url === '/api/orgs') {
|
|
119
|
+
let body = '';
|
|
120
|
+
req.on('data', (c) => {
|
|
121
|
+
body += c;
|
|
122
|
+
if (body.length > 2e6) req.destroy();
|
|
123
|
+
});
|
|
124
|
+
req.on('end', () => {
|
|
125
|
+
try {
|
|
126
|
+
const cfg = JSON.parse(body);
|
|
127
|
+
const qs = new URL(req.url, 'http://localhost').searchParams;
|
|
128
|
+
const dir = qs.get('dir') || cfg.dir || ctx.projectDir || process.cwd();
|
|
129
|
+
const name = (cfg.name || '')
|
|
130
|
+
.toLowerCase()
|
|
131
|
+
.replace(/[^a-z0-9_-]/g, '-')
|
|
132
|
+
.replace(/^-+|-+$/g, '')
|
|
133
|
+
.slice(0, 64);
|
|
134
|
+
if (!name) {
|
|
135
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
136
|
+
res.end(JSON.stringify({ error: 'Invalid org name' }));
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
const orgsDir = path.join(path.resolve(dir), '.monomind', 'orgs');
|
|
140
|
+
fs.mkdirSync(orgsDir, { recursive: true });
|
|
141
|
+
const destFile = path.join(orgsDir, `${name}.json`);
|
|
142
|
+
const cleanCfg = Object.fromEntries(
|
|
143
|
+
Object.entries({ ...cfg, name }).filter(([k]) => !k.startsWith('_')),
|
|
144
|
+
);
|
|
145
|
+
fs.writeFileSync(destFile, JSON.stringify(cleanCfg, null, 2), 'utf8');
|
|
146
|
+
res.writeHead(200, {
|
|
147
|
+
'Content-Type': 'application/json',
|
|
148
|
+
...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}),
|
|
149
|
+
});
|
|
150
|
+
res.end(JSON.stringify({ ok: true, name, file: destFile }));
|
|
151
|
+
} catch (e) {
|
|
152
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
153
|
+
res.end(JSON.stringify({ error: e.message }));
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
return true;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// GET /api/orgs/:name — get specific org config (exact path: /api/orgs/<slug>)
|
|
160
|
+
if (req.method === 'GET' && /^\/api\/orgs\/[a-z0-9][a-z0-9_-]{0,63}$/i.test(url)) {
|
|
161
|
+
try {
|
|
162
|
+
const orgName = decodeURIComponent(url.slice('/api/orgs/'.length));
|
|
163
|
+
if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) {
|
|
164
|
+
res.writeHead(400);
|
|
165
|
+
res.end('Invalid org name');
|
|
166
|
+
return true;
|
|
167
|
+
}
|
|
168
|
+
const _orgsOneQs = new URL(req.url, 'http://localhost').searchParams;
|
|
169
|
+
const _orgsOneRoot = path.resolve(_orgsOneQs.get('dir') || ctx.projectDir || process.cwd());
|
|
170
|
+
const _orgsOneProjDir = ctx._resolveOrgProjectDir(orgName, _orgsOneRoot) || _orgsOneRoot;
|
|
171
|
+
const f = path.join(_orgsOneProjDir, '.monomind', 'orgs', `${orgName}.json`);
|
|
172
|
+
if (!fs.existsSync(f)) {
|
|
173
|
+
res.writeHead(404);
|
|
174
|
+
res.end('{"error":"not found"}');
|
|
175
|
+
return true;
|
|
176
|
+
}
|
|
177
|
+
res.writeHead(200, {
|
|
178
|
+
'Content-Type': 'application/json',
|
|
179
|
+
...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}),
|
|
180
|
+
});
|
|
181
|
+
res.end(fs.readFileSync(f, 'utf8'));
|
|
182
|
+
} catch (_) {
|
|
183
|
+
res.writeHead(500);
|
|
184
|
+
res.end('{}');
|
|
185
|
+
}
|
|
186
|
+
return true;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// GET /api/org/:name — ORG ROOM: rich org data (config + state + tasks + routines + goals)
|
|
190
|
+
if (req.method === 'GET' && /^\/api\/org\/[a-z0-9][a-z0-9_-]{0,63}$/i.test(url)) {
|
|
191
|
+
try {
|
|
192
|
+
const orgName = decodeURIComponent(url.slice('/api/org/'.length));
|
|
193
|
+
if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) {
|
|
194
|
+
res.writeHead(400);
|
|
195
|
+
res.end('Invalid org name');
|
|
196
|
+
return true;
|
|
197
|
+
}
|
|
198
|
+
const _orgQs = new URL(req.url, 'http://localhost').searchParams;
|
|
199
|
+
const _orgServerRoot = path.resolve(_orgQs.get('dir') || ctx.projectDir || process.cwd());
|
|
200
|
+
// Resolve which project dir actually has this org's config
|
|
201
|
+
const d = ctx._resolveOrgProjectDir(orgName, _orgServerRoot) || _orgServerRoot;
|
|
202
|
+
const orgsDir = path.join(d, '.monomind', 'orgs');
|
|
203
|
+
|
|
204
|
+
const readJsonSafe = (f) => {
|
|
205
|
+
try {
|
|
206
|
+
return JSON.parse(fs.readFileSync(f, 'utf8'));
|
|
207
|
+
} catch (_) {
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
const configFile = path.join(orgsDir, `${orgName}.json`);
|
|
213
|
+
if (!fs.existsSync(configFile)) {
|
|
214
|
+
res.writeHead(404);
|
|
215
|
+
res.end('{"error":"org not found"}');
|
|
216
|
+
return true;
|
|
217
|
+
}
|
|
218
|
+
const config = readJsonSafe(configFile);
|
|
219
|
+
|
|
220
|
+
const state = readJsonSafe(path.join(orgsDir, `${orgName}-state.json`)) || { agents: {} };
|
|
221
|
+
const goalsData = readJsonSafe(path.join(orgsDir, `${orgName}-goals.json`)) || { goals: [] };
|
|
222
|
+
const routinesData = readJsonSafe(path.join(orgsDir, `${orgName}-routines.json`)) || {
|
|
223
|
+
routines: [],
|
|
224
|
+
};
|
|
225
|
+
const approvalsData = { approvals: approvalsOrEmpty(orgsDir, orgName) };
|
|
226
|
+
|
|
227
|
+
// Check running status: stop file absence AND (in-memory ctx.activeOrgRuns OR state-file agents OR active loop file)
|
|
228
|
+
// Path must match what `org run`'s poll loop and `org serve`'s pollStopfiles()
|
|
229
|
+
// actually watch (.monomind/orgs/<name>/stop) — see the POST .../stop handler below.
|
|
230
|
+
const stopFile = path.join(orgsDir, orgName, 'stop');
|
|
231
|
+
const _loopsDir = path.join(d, '.monomind', 'loops');
|
|
232
|
+
const _loopRunning = (() => {
|
|
233
|
+
try {
|
|
234
|
+
if (!fs.existsSync(_loopsDir)) return false;
|
|
235
|
+
// Get the org's state file mtime to correlate with loop activity
|
|
236
|
+
const orgStateMtime = (() => {
|
|
237
|
+
try {
|
|
238
|
+
return fs.statSync(path.join(orgsDir, `${orgName}-state.json`)).mtimeMs;
|
|
239
|
+
} catch {
|
|
240
|
+
return 0;
|
|
241
|
+
}
|
|
242
|
+
})();
|
|
243
|
+
// Also check org's most recent run file mtime
|
|
244
|
+
const orgRunsDir = path.join(
|
|
245
|
+
ctx._getGitMonomindDir(d) || path.join(d, '.monomind'),
|
|
246
|
+
'orgs',
|
|
247
|
+
orgName,
|
|
248
|
+
'runs',
|
|
249
|
+
);
|
|
250
|
+
const orgLastRunMtime = (() => {
|
|
251
|
+
try {
|
|
252
|
+
if (!fs.existsSync(orgRunsDir)) return 0;
|
|
253
|
+
const runFiles = fs
|
|
254
|
+
.readdirSync(orgRunsDir)
|
|
255
|
+
.filter((f) => f.endsWith('.jsonl') && !f.startsWith('._'));
|
|
256
|
+
if (!runFiles.length) return 0;
|
|
257
|
+
return Math.max(
|
|
258
|
+
...runFiles.map((f) => {
|
|
259
|
+
try {
|
|
260
|
+
return fs.statSync(path.join(orgRunsDir, f)).mtimeMs;
|
|
261
|
+
} catch {
|
|
262
|
+
return 0;
|
|
263
|
+
}
|
|
264
|
+
}),
|
|
265
|
+
);
|
|
266
|
+
} catch {
|
|
267
|
+
return 0;
|
|
268
|
+
}
|
|
269
|
+
})();
|
|
270
|
+
const orgLastActivity = Math.max(orgStateMtime, orgLastRunMtime);
|
|
271
|
+
return fs.readdirSync(_loopsDir).some((f) => {
|
|
272
|
+
if (!f.endsWith('.json') || f.endsWith('.stop')) return false;
|
|
273
|
+
try {
|
|
274
|
+
const lp = JSON.parse(fs.readFileSync(path.join(_loopsDir, f), 'utf8'));
|
|
275
|
+
if (!lp.command?.includes('runorg')) return false;
|
|
276
|
+
if (!['running', 'paused'].includes(lp.status)) return false;
|
|
277
|
+
// Primary match: explicit orgName field (written by runorg command since v1.14.2)
|
|
278
|
+
if (lp.orgName === orgName) return true;
|
|
279
|
+
// Fallback: org name in prompt (early loop files that preserved --org flag)
|
|
280
|
+
if ((lp.prompt || '').includes(orgName)) return true;
|
|
281
|
+
// Heuristic: if loop's lastRunAt is within 3x wait interval of org's last activity
|
|
282
|
+
const waitMs = (lp.wait || 60) * 3 * 1000;
|
|
283
|
+
return (
|
|
284
|
+
orgLastActivity > 0 && Math.abs(orgLastActivity - (lp.lastRunAt || 0)) < waitMs
|
|
285
|
+
);
|
|
286
|
+
} catch {
|
|
287
|
+
return false;
|
|
288
|
+
}
|
|
289
|
+
});
|
|
290
|
+
} catch {
|
|
291
|
+
return false;
|
|
292
|
+
}
|
|
293
|
+
})();
|
|
294
|
+
const _runstateData = ctx._readRunState(orgName, d);
|
|
295
|
+
const _runstateTtl = Math.max((_runstateData?.checkpointInterval || 600000) * 2, 7200000);
|
|
296
|
+
const _runstateAlive =
|
|
297
|
+
_runstateData?.status === 'running' &&
|
|
298
|
+
Date.now() - (_runstateData?.lastEventAt || 0) < _runstateTtl;
|
|
299
|
+
const running =
|
|
300
|
+
!fs.existsSync(stopFile) &&
|
|
301
|
+
(_runstateAlive || ctx.activeOrgRuns.has(orgName) || _loopRunning);
|
|
302
|
+
|
|
303
|
+
// Read real tasks from the task store and group by status column
|
|
304
|
+
const taskStoreData = readJsonSafe(path.join(d, '.monomind', 'tasks', 'store.json'));
|
|
305
|
+
const allTasks = taskStoreData ? Object.values(taskStoreData.tasks || {}) : [];
|
|
306
|
+
const tasks = {
|
|
307
|
+
todo: allTasks
|
|
308
|
+
.filter((t) => t.status === 'pending')
|
|
309
|
+
.map((t) => ({
|
|
310
|
+
id: t.taskId,
|
|
311
|
+
description: t.description,
|
|
312
|
+
status: 'todo',
|
|
313
|
+
ts: t.createdAt,
|
|
314
|
+
})),
|
|
315
|
+
doing: allTasks
|
|
316
|
+
.filter((t) => t.status === 'in_progress')
|
|
317
|
+
.map((t) => ({
|
|
318
|
+
id: t.taskId,
|
|
319
|
+
description: t.description,
|
|
320
|
+
status: 'doing',
|
|
321
|
+
ts: t.startedAt || t.createdAt,
|
|
322
|
+
})),
|
|
323
|
+
done: allTasks
|
|
324
|
+
.filter(
|
|
325
|
+
(t) => t.status === 'completed' || t.status === 'failed' || t.status === 'cancelled',
|
|
326
|
+
)
|
|
327
|
+
.map((t) => ({
|
|
328
|
+
id: t.taskId,
|
|
329
|
+
description: t.description,
|
|
330
|
+
status: t.status,
|
|
331
|
+
ts: t.completedAt || t.createdAt,
|
|
332
|
+
})),
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
const result = {
|
|
336
|
+
config,
|
|
337
|
+
state,
|
|
338
|
+
goals: goalsData.goals,
|
|
339
|
+
routines: routinesData.routines,
|
|
340
|
+
approvals: approvalsData.approvals,
|
|
341
|
+
running,
|
|
342
|
+
tasks,
|
|
343
|
+
runId: _runstateData?.runId || null,
|
|
344
|
+
lastEventAt: _runstateData?.lastEventAt || null,
|
|
345
|
+
agentStates: _runstateData?.agentStates || {},
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
res.writeHead(200, {
|
|
349
|
+
'Content-Type': 'application/json',
|
|
350
|
+
...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}),
|
|
351
|
+
});
|
|
352
|
+
res.end(JSON.stringify(result));
|
|
353
|
+
} catch (_) {
|
|
354
|
+
res.writeHead(500);
|
|
355
|
+
res.end('{}');
|
|
356
|
+
}
|
|
357
|
+
return true;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// GET /api/org/:name/activity — recent org events from mastermind-events.jsonl
|
|
361
|
+
if (req.method === 'GET' && /^\/api\/org\/[a-z0-9][a-z0-9_-]{0,63}\/activity$/i.test(url)) {
|
|
362
|
+
try {
|
|
363
|
+
const parts = url.split('/');
|
|
364
|
+
const orgName = decodeURIComponent(parts[3]);
|
|
365
|
+
if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) {
|
|
366
|
+
res.writeHead(400);
|
|
367
|
+
res.end('[]');
|
|
368
|
+
return true;
|
|
369
|
+
}
|
|
370
|
+
const _actQs = new URL(req.url, 'http://localhost').searchParams;
|
|
371
|
+
const _actServerRoot = path.resolve(_actQs.get('dir') || ctx.projectDir || process.cwd());
|
|
372
|
+
const d = ctx._resolveOrgProjectDir(orgName, _actServerRoot) || _actServerRoot;
|
|
373
|
+
const orgsDir = path.join(d, '.monomind', 'orgs');
|
|
374
|
+
const readJ = (f) => {
|
|
375
|
+
try {
|
|
376
|
+
return JSON.parse(fs.readFileSync(f, 'utf8'));
|
|
377
|
+
} catch (_) {
|
|
378
|
+
return null;
|
|
379
|
+
}
|
|
380
|
+
};
|
|
381
|
+
const events = [];
|
|
382
|
+
|
|
383
|
+
// 1) Global mastermind events that EXPLICITLY belong to this org (strict — no untagged leak)
|
|
384
|
+
const eventsFile = path.join(d, 'data', 'mastermind-events.jsonl');
|
|
385
|
+
if (fs.existsSync(eventsFile)) {
|
|
386
|
+
const lines = fs.readFileSync(eventsFile, 'utf8').split('\n').filter(Boolean);
|
|
387
|
+
for (const l of lines.slice(-1000)) {
|
|
388
|
+
try {
|
|
389
|
+
const e = JSON.parse(l);
|
|
390
|
+
if (e && e.org === orgName) events.push(e);
|
|
391
|
+
} catch (_) {}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// 2) Synthesize an org-scoped timeline from this org's own records (real data, distinct per org)
|
|
396
|
+
const cfg = readJ(path.join(orgsDir, `${orgName}.json`));
|
|
397
|
+
if (cfg) {
|
|
398
|
+
const createdMs = cfg.created_at ? Date.parse(cfg.created_at) : null;
|
|
399
|
+
if (createdMs)
|
|
400
|
+
events.push({
|
|
401
|
+
type: 'org:create',
|
|
402
|
+
ts: createdMs,
|
|
403
|
+
msg: String(cfg.goal || 'Org created').slice(0, 80),
|
|
404
|
+
});
|
|
405
|
+
// Roles are defined atomically at org creation — there is no per-role
|
|
406
|
+
// timestamp in the config, so every role:defined event uses the org's
|
|
407
|
+
// real created_at instead of a fabricated per-index offset.
|
|
408
|
+
(cfg.roles || []).forEach((r) => {
|
|
409
|
+
events.push({
|
|
410
|
+
type: 'role:defined',
|
|
411
|
+
ts: createdMs,
|
|
412
|
+
role: r.title || r.id,
|
|
413
|
+
msg: r.agent_type || '',
|
|
414
|
+
});
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
const goals = readJ(path.join(orgsDir, `${orgName}-goals.json`));
|
|
418
|
+
(goals?.goals || []).forEach((g) =>
|
|
419
|
+
events.push({
|
|
420
|
+
type: 'goal',
|
|
421
|
+
ts: Date.parse(g.created_at || g.updated_at || '') || null,
|
|
422
|
+
role: g.status || '',
|
|
423
|
+
msg: String(g.text || g.title || g.goal || '').slice(0, 80),
|
|
424
|
+
}),
|
|
425
|
+
);
|
|
426
|
+
approvalsOrEmpty(orgsDir, orgName).forEach((a) => {
|
|
427
|
+
events.push({
|
|
428
|
+
type: 'approval',
|
|
429
|
+
ts: a.ts,
|
|
430
|
+
role: a.roleId || '',
|
|
431
|
+
msg: `${a.action} — ${a.status}`.slice(0, 80),
|
|
432
|
+
});
|
|
433
|
+
});
|
|
434
|
+
const state = readJ(path.join(orgsDir, `${orgName}-state.json`));
|
|
435
|
+
if (state?.agents) {
|
|
436
|
+
for (const [aid, a] of Object.entries(state.agents)) {
|
|
437
|
+
const raw = a.lastHeartbeat || a.last_seen || a.updated_at || null;
|
|
438
|
+
const ts = typeof raw === 'number' ? raw : raw ? Date.parse(raw) : null;
|
|
439
|
+
events.push({ type: 'org:heartbeat', ts, agent: aid, msg: a.status || '' });
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
const out = events
|
|
444
|
+
.filter((e) => e?.ts)
|
|
445
|
+
.sort((a, b) => b.ts - a.ts)
|
|
446
|
+
.slice(0, 100);
|
|
447
|
+
res.writeHead(200, {
|
|
448
|
+
'Content-Type': 'application/json',
|
|
449
|
+
...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}),
|
|
450
|
+
});
|
|
451
|
+
res.end(JSON.stringify(out));
|
|
452
|
+
} catch (_) {
|
|
453
|
+
res.writeHead(500);
|
|
454
|
+
res.end('[]');
|
|
455
|
+
}
|
|
456
|
+
return true;
|
|
457
|
+
}
|
|
458
|
+
return false;
|
|
459
|
+
}
|