@monoes/monomindcli 1.11.14 → 1.13.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.
- package/.claude/agents/generated/channel-intelligence-director.md +87 -0
- package/.claude/agents/generated/chief-growth-officer.md +88 -0
- package/.claude/agents/generated/content-seo-strategist.md +90 -0
- package/.claude/agents/generated/developer-community-strategist.md +91 -0
- package/.claude/agents/generated/outreach-partnership-strategist.md +90 -0
- package/.claude/agents/generated/social-media-strategist.md +91 -0
- package/.claude/agents/generated/video-visual-strategist.md +90 -0
- package/.claude/commands/mastermind/master.md +1 -1
- package/.claude/helpers/auto-memory-hook.mjs +13 -4
- package/.claude/helpers/control-start.cjs +5 -0
- package/.claude/helpers/event-logger.cjs +114 -0
- package/.claude/helpers/handlers/adr-draft-handler.cjs +19 -5
- package/.claude/helpers/handlers/agent-start-handler.cjs +13 -4
- package/.claude/helpers/handlers/compact-handler.cjs +2 -0
- package/.claude/helpers/handlers/edit-handler.cjs +1 -1
- package/.claude/helpers/handlers/gates-handler.cjs +3 -0
- package/.claude/helpers/handlers/graph-status-handler.cjs +14 -8
- package/.claude/helpers/handlers/loops-status-handler.cjs +5 -2
- package/.claude/helpers/handlers/route-handler.cjs +24 -10
- package/.claude/helpers/handlers/session-handler.cjs +11 -4
- package/.claude/helpers/handlers/session-restore-handler.cjs +35 -19
- package/.claude/helpers/handlers/task-handler.cjs +13 -5
- package/.claude/helpers/hook-handler.cjs +40 -0
- package/.claude/helpers/intelligence.cjs +130 -53
- package/.claude/helpers/loop-tracker.cjs +15 -3
- package/.claude/helpers/memory-palace.cjs +461 -0
- package/.claude/helpers/memory.cjs +138 -14
- package/.claude/helpers/metrics-db.mjs +87 -0
- package/.claude/helpers/router.cjs +300 -42
- package/.claude/helpers/session.cjs +89 -30
- package/.claude/helpers/statusline.cjs +148 -4
- package/.claude/helpers/toggle-statusline.cjs +73 -0
- package/.claude/helpers/token-tracker.cjs +934 -0
- package/.claude/helpers/utils/micro-agents.cjs +20 -4
- package/.claude/helpers/utils/monograph.cjs +39 -4
- package/.claude/helpers/utils/telemetry.cjs +3 -3
- package/.claude/skills/mastermind/_protocol.md +25 -15
- package/.claude/skills/mastermind/architect.md +3 -3
- package/.claude/skills/mastermind/autodev.md +4 -2
- package/.claude/skills/mastermind/idea.md +10 -0
- package/.claude/skills/mastermind/ops.md +3 -3
- package/.claude/skills/mastermind/runorg.md +153 -86
- package/dist/src/agents/registry-builder.d.ts.map +1 -1
- package/dist/src/agents/registry-builder.js +2 -0
- package/dist/src/agents/registry-builder.js.map +1 -1
- package/dist/src/autopilot-state.d.ts.map +1 -1
- package/dist/src/autopilot-state.js +10 -5
- package/dist/src/autopilot-state.js.map +1 -1
- package/dist/src/benchmarks/benchmark-runner.d.ts.map +1 -1
- package/dist/src/benchmarks/benchmark-runner.js +13 -0
- package/dist/src/benchmarks/benchmark-runner.js.map +1 -1
- package/dist/src/benchmarks/metric-evaluators.d.ts.map +1 -1
- package/dist/src/benchmarks/metric-evaluators.js +20 -9
- package/dist/src/benchmarks/metric-evaluators.js.map +1 -1
- package/dist/src/browser/actions.d.ts.map +1 -1
- package/dist/src/browser/actions.js +10 -3
- package/dist/src/browser/actions.js.map +1 -1
- package/dist/src/browser/browser.d.ts.map +1 -1
- package/dist/src/browser/browser.js +12 -2
- package/dist/src/browser/browser.js.map +1 -1
- package/dist/src/browser/cdp.d.ts.map +1 -1
- package/dist/src/browser/cdp.js +21 -3
- package/dist/src/browser/cdp.js.map +1 -1
- package/dist/src/browser/har.d.ts.map +1 -1
- package/dist/src/browser/har.js +27 -5
- package/dist/src/browser/har.js.map +1 -1
- package/dist/src/commands/agent.d.ts.map +1 -1
- package/dist/src/commands/agent.js +11 -8
- package/dist/src/commands/agent.js.map +1 -1
- package/dist/src/commands/analyze.d.ts.map +1 -1
- package/dist/src/commands/analyze.js +36 -21
- package/dist/src/commands/analyze.js.map +1 -1
- package/dist/src/commands/autopilot.d.ts.map +1 -1
- package/dist/src/commands/autopilot.js +12 -4
- package/dist/src/commands/autopilot.js.map +1 -1
- package/dist/src/commands/benchmark.d.ts.map +1 -1
- package/dist/src/commands/benchmark.js +51 -8
- package/dist/src/commands/benchmark.js.map +1 -1
- package/dist/src/commands/browse.d.ts.map +1 -1
- package/dist/src/commands/browse.js +5 -2
- package/dist/src/commands/browse.js.map +1 -1
- package/dist/src/commands/claims.d.ts.map +1 -1
- package/dist/src/commands/claims.js +29 -11
- package/dist/src/commands/claims.js.map +1 -1
- package/dist/src/commands/cleanup.d.ts.map +1 -1
- package/dist/src/commands/cleanup.js +25 -5
- package/dist/src/commands/cleanup.js.map +1 -1
- package/dist/src/commands/config.d.ts.map +1 -1
- package/dist/src/commands/config.js +15 -7
- package/dist/src/commands/config.js.map +1 -1
- package/dist/src/commands/daemon.d.ts.map +1 -1
- package/dist/src/commands/daemon.js +6 -0
- package/dist/src/commands/daemon.js.map +1 -1
- package/dist/src/commands/deployment.d.ts.map +1 -1
- package/dist/src/commands/deployment.js +34 -19
- package/dist/src/commands/deployment.js.map +1 -1
- package/dist/src/commands/doctor.d.ts.map +1 -1
- package/dist/src/commands/doctor.js +133 -15
- package/dist/src/commands/doctor.js.map +1 -1
- package/dist/src/commands/guidance.d.ts.map +1 -1
- package/dist/src/commands/guidance.js +15 -2
- package/dist/src/commands/guidance.js.map +1 -1
- package/dist/src/commands/hive-mind.d.ts.map +1 -1
- package/dist/src/commands/hive-mind.js +37 -14
- package/dist/src/commands/hive-mind.js.map +1 -1
- package/dist/src/commands/hooks.d.ts.map +1 -1
- package/dist/src/commands/hooks.js +42 -25
- package/dist/src/commands/hooks.js.map +1 -1
- package/dist/src/commands/init.d.ts.map +1 -1
- package/dist/src/commands/init.js +9 -4
- package/dist/src/commands/init.js.map +1 -1
- package/dist/src/commands/issues.d.ts.map +1 -1
- package/dist/src/commands/issues.js +29 -26
- package/dist/src/commands/issues.js.map +1 -1
- package/dist/src/commands/mcp.d.ts.map +1 -1
- package/dist/src/commands/mcp.js +11 -5
- package/dist/src/commands/mcp.js.map +1 -1
- package/dist/src/commands/memory.d.ts.map +1 -1
- package/dist/src/commands/memory.js +10 -0
- package/dist/src/commands/memory.js.map +1 -1
- package/dist/src/commands/migrate.js +5 -5
- package/dist/src/commands/migrate.js.map +1 -1
- package/dist/src/commands/monograph.d.ts.map +1 -1
- package/dist/src/commands/monograph.js +18 -5
- package/dist/src/commands/monograph.js.map +1 -1
- package/dist/src/commands/monovector/backup.d.ts.map +1 -1
- package/dist/src/commands/monovector/backup.js +8 -2
- package/dist/src/commands/monovector/backup.js.map +1 -1
- package/dist/src/commands/monovector/benchmark.d.ts.map +1 -1
- package/dist/src/commands/monovector/benchmark.js +20 -7
- package/dist/src/commands/monovector/benchmark.js.map +1 -1
- package/dist/src/commands/monovector/import.d.ts.map +1 -1
- package/dist/src/commands/monovector/import.js +15 -0
- package/dist/src/commands/monovector/import.js.map +1 -1
- package/dist/src/commands/monovector/migrate.d.ts.map +1 -1
- package/dist/src/commands/monovector/migrate.js +4 -1
- package/dist/src/commands/monovector/migrate.js.map +1 -1
- package/dist/src/commands/monovector/optimize.d.ts.map +1 -1
- package/dist/src/commands/monovector/optimize.js +11 -0
- package/dist/src/commands/monovector/optimize.js.map +1 -1
- package/dist/src/commands/monovector/setup.d.ts.map +1 -1
- package/dist/src/commands/monovector/setup.js +11 -1
- package/dist/src/commands/monovector/setup.js.map +1 -1
- package/dist/src/commands/neural.js +1 -1
- package/dist/src/commands/neural.js.map +1 -1
- package/dist/src/commands/performance.d.ts.map +1 -1
- package/dist/src/commands/performance.js +20 -7
- package/dist/src/commands/performance.js.map +1 -1
- package/dist/src/commands/platforms.d.ts.map +1 -1
- package/dist/src/commands/platforms.js +90 -8
- package/dist/src/commands/platforms.js.map +1 -1
- package/dist/src/commands/plugins.d.ts.map +1 -1
- package/dist/src/commands/plugins.js +12 -5
- package/dist/src/commands/plugins.js.map +1 -1
- package/dist/src/commands/process.d.ts.map +1 -1
- package/dist/src/commands/process.js +33 -10
- package/dist/src/commands/process.js.map +1 -1
- package/dist/src/commands/progress.d.ts.map +1 -1
- package/dist/src/commands/progress.js +5 -3
- package/dist/src/commands/progress.js.map +1 -1
- package/dist/src/commands/providers.js +5 -5
- package/dist/src/commands/providers.js.map +1 -1
- package/dist/src/commands/replay.d.ts.map +1 -1
- package/dist/src/commands/replay.js +8 -2
- package/dist/src/commands/replay.js.map +1 -1
- package/dist/src/commands/route.d.ts.map +1 -1
- package/dist/src/commands/route.js +27 -7
- package/dist/src/commands/route.js.map +1 -1
- package/dist/src/commands/security.d.ts.map +1 -1
- package/dist/src/commands/security.js +4 -0
- package/dist/src/commands/security.js.map +1 -1
- package/dist/src/commands/session.d.ts.map +1 -1
- package/dist/src/commands/session.js +12 -1
- package/dist/src/commands/session.js.map +1 -1
- package/dist/src/commands/start.d.ts.map +1 -1
- package/dist/src/commands/start.js +11 -4
- package/dist/src/commands/start.js.map +1 -1
- package/dist/src/commands/status.d.ts.map +1 -1
- package/dist/src/commands/status.js +7 -4
- package/dist/src/commands/status.js.map +1 -1
- package/dist/src/commands/swarm.d.ts.map +1 -1
- package/dist/src/commands/swarm.js +27 -13
- package/dist/src/commands/swarm.js.map +1 -1
- package/dist/src/commands/task.d.ts.map +1 -1
- package/dist/src/commands/task.js +26 -11
- package/dist/src/commands/task.js.map +1 -1
- package/dist/src/commands/tokens.d.ts.map +1 -1
- package/dist/src/commands/tokens.js +7 -2
- package/dist/src/commands/tokens.js.map +1 -1
- package/dist/src/commands/transfer-store.d.ts.map +1 -1
- package/dist/src/commands/transfer-store.js +36 -22
- package/dist/src/commands/transfer-store.js.map +1 -1
- package/dist/src/commands/update.d.ts.map +1 -1
- package/dist/src/commands/update.js +15 -3
- package/dist/src/commands/update.js.map +1 -1
- package/dist/src/commands/workflow.d.ts.map +1 -1
- package/dist/src/commands/workflow.js +39 -6
- package/dist/src/commands/workflow.js.map +1 -1
- package/dist/src/consensus/audit-writer.d.ts.map +1 -1
- package/dist/src/consensus/audit-writer.js +18 -7
- package/dist/src/consensus/audit-writer.js.map +1 -1
- package/dist/src/consensus/vote-signer.d.ts.map +1 -1
- package/dist/src/consensus/vote-signer.js +25 -8
- package/dist/src/consensus/vote-signer.js.map +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +7 -3
- package/dist/src/index.js.map +1 -1
- package/dist/src/init/executor.d.ts.map +1 -1
- package/dist/src/init/executor.js +14 -11
- package/dist/src/init/executor.js.map +1 -1
- package/dist/src/init/shared-instructions-generator.d.ts.map +1 -1
- package/dist/src/init/shared-instructions-generator.js +20 -4
- package/dist/src/init/shared-instructions-generator.js.map +1 -1
- package/dist/src/init/statusline-generator.d.ts.map +1 -1
- package/dist/src/init/statusline-generator.js +36 -15
- package/dist/src/init/statusline-generator.js.map +1 -1
- package/dist/src/mcp-tools/a2a-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/a2a-tools.js +98 -13
- package/dist/src/mcp-tools/a2a-tools.js.map +1 -1
- package/dist/src/mcp-tools/agent-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/agent-tools.js +16 -3
- package/dist/src/mcp-tools/agent-tools.js.map +1 -1
- package/dist/src/mcp-tools/analyze-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/analyze-tools.js +80 -17
- package/dist/src/mcp-tools/analyze-tools.js.map +1 -1
- package/dist/src/mcp-tools/browser-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/browser-tools.js +84 -22
- package/dist/src/mcp-tools/browser-tools.js.map +1 -1
- package/dist/src/mcp-tools/claims-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/claims-tools.js +35 -7
- package/dist/src/mcp-tools/claims-tools.js.map +1 -1
- package/dist/src/mcp-tools/config-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/config-tools.js +82 -17
- package/dist/src/mcp-tools/config-tools.js.map +1 -1
- package/dist/src/mcp-tools/coordination-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/coordination-tools.js +37 -4
- package/dist/src/mcp-tools/coordination-tools.js.map +1 -1
- package/dist/src/mcp-tools/daa-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/daa-tools.js +49 -7
- package/dist/src/mcp-tools/daa-tools.js.map +1 -1
- package/dist/src/mcp-tools/embeddings-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/embeddings-tools.js +45 -18
- package/dist/src/mcp-tools/embeddings-tools.js.map +1 -1
- package/dist/src/mcp-tools/github-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/github-tools.js +75 -25
- package/dist/src/mcp-tools/github-tools.js.map +1 -1
- package/dist/src/mcp-tools/guidance-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/guidance-tools.js +32 -10
- package/dist/src/mcp-tools/guidance-tools.js.map +1 -1
- package/dist/src/mcp-tools/hive-mind-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/hive-mind-tools.js +91 -20
- package/dist/src/mcp-tools/hive-mind-tools.js.map +1 -1
- package/dist/src/mcp-tools/hooks-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/hooks-tools.js +188 -29
- package/dist/src/mcp-tools/hooks-tools.js.map +1 -1
- package/dist/src/mcp-tools/memory-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/memory-tools.js +25 -7
- package/dist/src/mcp-tools/memory-tools.js.map +1 -1
- package/dist/src/mcp-tools/monograph-compat.d.ts.map +1 -1
- package/dist/src/mcp-tools/monograph-compat.js +11 -2
- package/dist/src/mcp-tools/monograph-compat.js.map +1 -1
- package/dist/src/mcp-tools/monograph-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/monograph-tools.js +476 -62
- package/dist/src/mcp-tools/monograph-tools.js.map +1 -1
- package/dist/src/mcp-tools/neural-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/neural-tools.js +44 -9
- package/dist/src/mcp-tools/neural-tools.js.map +1 -1
- package/dist/src/mcp-tools/performance-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/performance-tools.js +45 -10
- package/dist/src/mcp-tools/performance-tools.js.map +1 -1
- package/dist/src/mcp-tools/progress-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/progress-tools.js +7 -4
- package/dist/src/mcp-tools/progress-tools.js.map +1 -1
- package/dist/src/mcp-tools/request-tracker.d.ts.map +1 -1
- package/dist/src/mcp-tools/request-tracker.js +15 -1
- package/dist/src/mcp-tools/request-tracker.js.map +1 -1
- package/dist/src/mcp-tools/security-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/security-tools.js +61 -9
- package/dist/src/mcp-tools/security-tools.js.map +1 -1
- package/dist/src/mcp-tools/session-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/session-tools.js +45 -14
- package/dist/src/mcp-tools/session-tools.js.map +1 -1
- package/dist/src/mcp-tools/swarm-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/swarm-tools.js +15 -3
- package/dist/src/mcp-tools/swarm-tools.js.map +1 -1
- package/dist/src/mcp-tools/system-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/system-tools.js +14 -7
- package/dist/src/mcp-tools/system-tools.js.map +1 -1
- package/dist/src/mcp-tools/task-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/task-tools.js +52 -10
- package/dist/src/mcp-tools/task-tools.js.map +1 -1
- package/dist/src/mcp-tools/terminal-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/terminal-tools.js +40 -6
- package/dist/src/mcp-tools/terminal-tools.js.map +1 -1
- package/dist/src/mcp-tools/transfer-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/transfer-tools.js +37 -4
- package/dist/src/mcp-tools/transfer-tools.js.map +1 -1
- package/dist/src/mcp-tools/workflow-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/workflow-tools.js +29 -6
- package/dist/src/mcp-tools/workflow-tools.js.map +1 -1
- package/dist/src/memory/ewc-consolidation.d.ts.map +1 -1
- package/dist/src/memory/ewc-consolidation.js +26 -10
- package/dist/src/memory/ewc-consolidation.js.map +1 -1
- package/dist/src/memory/intelligence.d.ts.map +1 -1
- package/dist/src/memory/intelligence.js +80 -19
- package/dist/src/memory/intelligence.js.map +1 -1
- package/dist/src/memory/memory-bridge.d.ts.map +1 -1
- package/dist/src/memory/memory-bridge.js +21 -2
- package/dist/src/memory/memory-bridge.js.map +1 -1
- package/dist/src/memory/memory-initializer.d.ts.map +1 -1
- package/dist/src/memory/memory-initializer.js +67 -3
- package/dist/src/memory/memory-initializer.js.map +1 -1
- package/dist/src/memory/sona-optimizer.d.ts.map +1 -1
- package/dist/src/memory/sona-optimizer.js +14 -4
- package/dist/src/memory/sona-optimizer.js.map +1 -1
- package/dist/src/monovector/command-outcomes.d.ts.map +1 -1
- package/dist/src/monovector/command-outcomes.js +43 -7
- package/dist/src/monovector/command-outcomes.js.map +1 -1
- package/dist/src/monovector/coverage-router.d.ts.map +1 -1
- package/dist/src/monovector/coverage-router.js +8 -4
- package/dist/src/monovector/coverage-router.js.map +1 -1
- package/dist/src/monovector/coverage-tools.d.ts.map +1 -1
- package/dist/src/monovector/coverage-tools.js +6 -3
- package/dist/src/monovector/coverage-tools.js.map +1 -1
- package/dist/src/monovector/diff-classifier.d.ts.map +1 -1
- package/dist/src/monovector/diff-classifier.js +13 -0
- package/dist/src/monovector/diff-classifier.js.map +1 -1
- package/dist/src/monovector/route-outcomes.d.ts +2 -1
- package/dist/src/monovector/route-outcomes.d.ts.map +1 -1
- package/dist/src/monovector/route-outcomes.js +46 -4
- package/dist/src/monovector/route-outcomes.js.map +1 -1
- package/dist/src/plugins/manager.d.ts.map +1 -1
- package/dist/src/plugins/manager.js +8 -3
- package/dist/src/plugins/manager.js.map +1 -1
- package/dist/src/plugins/store/discovery.d.ts.map +1 -1
- package/dist/src/plugins/store/discovery.js +46 -2
- package/dist/src/plugins/store/discovery.js.map +1 -1
- package/dist/src/plugins/store/search.d.ts.map +1 -1
- package/dist/src/plugins/store/search.js +5 -4
- package/dist/src/plugins/store/search.js.map +1 -1
- package/dist/src/production/circuit-breaker.d.ts.map +1 -1
- package/dist/src/production/circuit-breaker.js +17 -3
- package/dist/src/production/circuit-breaker.js.map +1 -1
- package/dist/src/production/error-handler.d.ts.map +1 -1
- package/dist/src/production/error-handler.js +3 -0
- package/dist/src/production/error-handler.js.map +1 -1
- package/dist/src/production/monitoring.d.ts.map +1 -1
- package/dist/src/production/monitoring.js +20 -3
- package/dist/src/production/monitoring.js.map +1 -1
- package/dist/src/production/rate-limiter.d.ts.map +1 -1
- package/dist/src/production/rate-limiter.js +13 -4
- package/dist/src/production/rate-limiter.js.map +1 -1
- package/dist/src/production/retry.d.ts.map +1 -1
- package/dist/src/production/retry.js +17 -9
- package/dist/src/production/retry.js.map +1 -1
- package/dist/src/routing/embed-worker.js +6 -2
- package/dist/src/routing/embed-worker.js.map +1 -1
- package/dist/src/routing/embedder.d.ts.map +1 -1
- package/dist/src/routing/embedder.js +0 -0
- package/dist/src/routing/embedder.js.map +1 -1
- package/dist/src/routing/llm-caller.d.ts.map +1 -1
- package/dist/src/routing/llm-caller.js +13 -2
- package/dist/src/routing/llm-caller.js.map +1 -1
- package/dist/src/routing/route-layer-factory.d.ts.map +1 -1
- package/dist/src/routing/route-layer-factory.js +18 -3
- package/dist/src/routing/route-layer-factory.js.map +1 -1
- package/dist/src/services/claim-service.d.ts +1 -0
- package/dist/src/services/claim-service.d.ts.map +1 -1
- package/dist/src/services/claim-service.js +8 -0
- package/dist/src/services/claim-service.js.map +1 -1
- package/dist/src/services/config-file-manager.d.ts.map +1 -1
- package/dist/src/services/config-file-manager.js +14 -2
- package/dist/src/services/config-file-manager.js.map +1 -1
- package/dist/src/services/headless-worker-executor.d.ts.map +1 -1
- package/dist/src/services/headless-worker-executor.js +18 -2
- package/dist/src/services/headless-worker-executor.js.map +1 -1
- package/dist/src/services/worker-daemon.d.ts.map +1 -1
- package/dist/src/services/worker-daemon.js +348 -17
- package/dist/src/services/worker-daemon.js.map +1 -1
- package/dist/src/transfer/anonymization/index.d.ts +0 -3
- package/dist/src/transfer/anonymization/index.d.ts.map +1 -1
- package/dist/src/transfer/anonymization/index.js +16 -1
- package/dist/src/transfer/anonymization/index.js.map +1 -1
- package/dist/src/transfer/export.d.ts.map +1 -1
- package/dist/src/transfer/export.js +8 -0
- package/dist/src/transfer/export.js.map +1 -1
- package/dist/src/transfer/ipfs/upload.d.ts.map +1 -1
- package/dist/src/transfer/ipfs/upload.js +33 -3
- package/dist/src/transfer/ipfs/upload.js.map +1 -1
- package/dist/src/transfer/serialization/cfp.d.ts.map +1 -1
- package/dist/src/transfer/serialization/cfp.js +8 -2
- package/dist/src/transfer/serialization/cfp.js.map +1 -1
- package/dist/src/transfer/storage/gcs.d.ts.map +1 -1
- package/dist/src/transfer/storage/gcs.js +37 -3
- package/dist/src/transfer/storage/gcs.js.map +1 -1
- package/dist/src/transfer/store/discovery.d.ts.map +1 -1
- package/dist/src/transfer/store/discovery.js +45 -3
- package/dist/src/transfer/store/discovery.js.map +1 -1
- package/dist/src/transfer/store/download.d.ts.map +1 -1
- package/dist/src/transfer/store/download.js +5 -0
- package/dist/src/transfer/store/download.js.map +1 -1
- package/dist/src/transfer/store/publish.d.ts.map +1 -1
- package/dist/src/transfer/store/publish.js +13 -1
- package/dist/src/transfer/store/publish.js.map +1 -1
- package/dist/src/transfer/store/registry.d.ts +8 -0
- package/dist/src/transfer/store/registry.d.ts.map +1 -1
- package/dist/src/transfer/store/registry.js +30 -5
- package/dist/src/transfer/store/registry.js.map +1 -1
- package/dist/src/transfer/store/search.d.ts.map +1 -1
- package/dist/src/transfer/store/search.js +20 -5
- package/dist/src/transfer/store/search.js.map +1 -1
- package/dist/src/ui/collector.mjs +39 -5
- package/dist/src/ui/dashboard.html +1603 -1284
- package/dist/src/ui/orgs.html +722 -12
- package/dist/src/ui/server.mjs +717 -136
- package/dist/src/update/checker.d.ts.map +1 -1
- package/dist/src/update/checker.js +59 -7
- package/dist/src/update/checker.js.map +1 -1
- package/dist/src/update/executor.d.ts.map +1 -1
- package/dist/src/update/executor.js +50 -3
- package/dist/src/update/executor.js.map +1 -1
- package/dist/src/update/index.d.ts.map +1 -1
- package/dist/src/update/index.js +18 -1
- package/dist/src/update/index.js.map +1 -1
- package/dist/src/update/rate-limiter.d.ts +6 -0
- package/dist/src/update/rate-limiter.d.ts.map +1 -1
- package/dist/src/update/rate-limiter.js +79 -7
- package/dist/src/update/rate-limiter.js.map +1 -1
- package/dist/src/update/validator.d.ts.map +1 -1
- package/dist/src/update/validator.js +52 -1
- package/dist/src/update/validator.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -3
- package/dist/src/ui/data/mastermind-events.jsonl +0 -59
package/dist/src/ui/server.mjs
CHANGED
|
@@ -11,6 +11,7 @@ const buildDocsState = new Map();
|
|
|
11
11
|
|
|
12
12
|
// Pricing per token (mirrors token-tracker.cjs FALLBACK_PRICING)
|
|
13
13
|
const _SJ_PRICING = {
|
|
14
|
+
'claude-opus-4-8': { in: 5e-6, out: 25e-6, cw: 6.25e-6, cr: 0.5e-6 },
|
|
14
15
|
'claude-opus-4-6': { in: 5e-6, out: 25e-6, cw: 6.25e-6, cr: 0.5e-6 },
|
|
15
16
|
'claude-opus-4-5': { in: 5e-6, out: 25e-6, cw: 6.25e-6, cr: 0.5e-6 },
|
|
16
17
|
'claude-opus-4': { in: 15e-6, out: 75e-6, cw: 18.75e-6, cr: 1.5e-6 },
|
|
@@ -20,13 +21,16 @@ const _SJ_PRICING = {
|
|
|
20
21
|
'claude-3-7-sonnet': { in: 3e-6, out: 15e-6, cw: 3.75e-6, cr: 0.3e-6 },
|
|
21
22
|
'claude-3-5-sonnet': { in: 3e-6, out: 15e-6, cw: 3.75e-6, cr: 0.3e-6 },
|
|
22
23
|
'claude-haiku-4-5': { in: 1e-6, out: 5e-6, cw: 1.25e-6, cr: 0.1e-6 },
|
|
24
|
+
'claude-haiku-4': { in: 0.8e-6, out: 4e-6, cw: 1e-6, cr: 0.08e-6 },
|
|
23
25
|
'claude-3-5-haiku': { in: 0.8e-6, out: 4e-6, cw: 1e-6, cr: 0.08e-6 },
|
|
24
26
|
'gpt-4o': { in: 2.5e-6, out: 10e-6, cw: 2.5e-6, cr: 1.25e-6 },
|
|
25
27
|
'gpt-4o-mini': { in: 0.15e-6, out: 0.6e-6, cw: 0.15e-6, cr: 0.075e-6 },
|
|
26
28
|
'gemini-2.5-pro': { in: 1.25e-6, out: 10e-6, cw: 1.25e-6, cr: 0.315e-6 },
|
|
27
29
|
};
|
|
28
30
|
function _sjGetPricing(model) {
|
|
29
|
-
const
|
|
31
|
+
const _ALIAS = { 'haiku': 'claude-haiku-4-5', 'opus': 'claude-opus-4-6', 'sonnet': 'claude-sonnet-4-6' };
|
|
32
|
+
let canonical = (model || '').replace(/@.*$/, '').replace(/-\d{8}$/, '');
|
|
33
|
+
canonical = _ALIAS[canonical] || canonical;
|
|
30
34
|
if (_SJ_PRICING[canonical]) return _SJ_PRICING[canonical];
|
|
31
35
|
for (const k of Object.keys(_SJ_PRICING)) { if (canonical.startsWith(k) || canonical.includes(k)) return _SJ_PRICING[k]; }
|
|
32
36
|
return null;
|
|
@@ -51,7 +55,7 @@ function categorizeTool(name) {
|
|
|
51
55
|
if (['Read','Write','Edit','MultiEdit','Glob','Grep','LS'].includes(name)) return 'file';
|
|
52
56
|
if (name === 'Bash') return 'bash';
|
|
53
57
|
if (['Agent','Task'].includes(name)) return 'agent';
|
|
54
|
-
if (name.startsWith('
|
|
58
|
+
if (name.startsWith('mcp__monomind__memory') || name.startsWith('mcp__monomind__agentdb')) return 'memory';
|
|
55
59
|
if (['WebFetch','WebSearch'].includes(name)) return 'web';
|
|
56
60
|
if (name === 'TodoWrite' || name === 'TodoRead') return 'task';
|
|
57
61
|
if (name === 'Skill') return 'skill';
|
|
@@ -131,8 +135,8 @@ function buildToolLabel(name, input) {
|
|
|
131
135
|
if (name === 'WebFetch') return `Fetch ${(input.url || '').slice(0, 50)}`;
|
|
132
136
|
if (name === 'WebSearch') return `Search ${(input.query || '').slice(0, 40)}`;
|
|
133
137
|
if (name === 'Skill') return `Skill: ${input.skill || '?'}`;
|
|
134
|
-
if (name.startsWith('
|
|
135
|
-
if (name.startsWith('mcp__')) return name.replace('
|
|
138
|
+
if (name.startsWith('mcp__monomind__memory')) return name.replace('mcp__monomind__memory_', 'mem:');
|
|
139
|
+
if (name.startsWith('mcp__')) return name.replace('mcp__monomind__', '⬡ ').replace('mcp__', '⬡ ').slice(0, 40);
|
|
136
140
|
return name.slice(0, 40);
|
|
137
141
|
}
|
|
138
142
|
|
|
@@ -179,6 +183,41 @@ function pathToSections(filename) {
|
|
|
179
183
|
const sseClients = new Set();
|
|
180
184
|
// Mastermind real-time event stream clients
|
|
181
185
|
const mmSseClients = new Set();
|
|
186
|
+
// Active org run tracking: org -> runId (enables event routing for orgs without runId in payload)
|
|
187
|
+
const activeOrgRuns = new Map();
|
|
188
|
+
|
|
189
|
+
// Returns the shared git directory parent so run files survive branch switches and
|
|
190
|
+
// are shared across all worktrees. In a worktree, .git is a FILE pointing to the
|
|
191
|
+
// shared .git dir (e.g. /main/.git/worktrees/feat); we navigate up two levels to
|
|
192
|
+
// reach /main/.git, then up one more to /main/ for the monomind data root.
|
|
193
|
+
// Falls back to the working directory if git isn't available.
|
|
194
|
+
const _gitMonomindCache = new Map();
|
|
195
|
+
function _getGitMonomindDir(workDir) {
|
|
196
|
+
if (!workDir) return null;
|
|
197
|
+
if (_gitMonomindCache.has(workDir)) return _gitMonomindCache.get(workDir);
|
|
198
|
+
let result = null;
|
|
199
|
+
try {
|
|
200
|
+
const gitEntry = path.join(workDir, '.git');
|
|
201
|
+
const st = fs.statSync(gitEntry);
|
|
202
|
+
if (st.isDirectory()) {
|
|
203
|
+
// Regular repo: .git is a directory
|
|
204
|
+
result = path.join(gitEntry, 'monomind');
|
|
205
|
+
} else if (st.isFile()) {
|
|
206
|
+
// Worktree: .git is a text file "gitdir: /main/.git/worktrees/name"
|
|
207
|
+
const m = fs.readFileSync(gitEntry, 'utf8').trim().match(/^gitdir:\s*(.+)/);
|
|
208
|
+
if (m) {
|
|
209
|
+
// Resolve relative paths (gitdir can be relative to the worktree root)
|
|
210
|
+
const worktreeDir = path.resolve(workDir, m[1].trim());
|
|
211
|
+
// /main/.git/worktrees/name -> /main/.git -> /main/.git/monomind
|
|
212
|
+
const commonGitDir = path.dirname(path.dirname(worktreeDir));
|
|
213
|
+
result = path.join(commonGitDir, 'monomind');
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
} catch {}
|
|
217
|
+
if (!result) result = path.join(workDir, '.monomind'); // fallback
|
|
218
|
+
_gitMonomindCache.set(workDir, result);
|
|
219
|
+
return result;
|
|
220
|
+
}
|
|
182
221
|
|
|
183
222
|
// Server state
|
|
184
223
|
let running = false;
|
|
@@ -370,12 +409,22 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
370
409
|
const cwd = projectDir || process.cwd();
|
|
371
410
|
const name = gitExec('git config user.name', { cwd, encoding: 'utf8' }).trim();
|
|
372
411
|
const email = gitExec('git config user.email', { cwd, encoding: 'utf8' }).trim();
|
|
412
|
+
let remoteUrl = '';
|
|
413
|
+
try { remoteUrl = gitExec('git remote get-url origin', { cwd, encoding: 'utf8' }).trim(); } catch {}
|
|
414
|
+
// Normalise SSH remote to HTTPS URL for browser linking
|
|
415
|
+
if (remoteUrl.startsWith('git@')) {
|
|
416
|
+
remoteUrl = remoteUrl.replace(/^git@([^:]+):/, 'https://$1/').replace(/\.git$/, '');
|
|
417
|
+
} else if (remoteUrl.endsWith('.git')) {
|
|
418
|
+
remoteUrl = remoteUrl.slice(0, -4);
|
|
419
|
+
}
|
|
420
|
+
let branch = '';
|
|
421
|
+
try { branch = gitExec('git rev-parse --abbrev-ref HEAD', { cwd, encoding: 'utf8' }).trim(); } catch {}
|
|
373
422
|
res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
|
|
374
|
-
res.end(JSON.stringify({ name, email, cwd }));
|
|
423
|
+
res.end(JSON.stringify({ name, email, cwd, remoteUrl, branch }));
|
|
375
424
|
} catch (_) {
|
|
376
425
|
const cwd2 = projectDir || process.cwd();
|
|
377
426
|
res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
|
|
378
|
-
res.end(JSON.stringify({ name: '', email: '', cwd: cwd2 }));
|
|
427
|
+
res.end(JSON.stringify({ name: '', email: '', cwd: cwd2, remoteUrl: '', branch: '' }));
|
|
379
428
|
}
|
|
380
429
|
return;
|
|
381
430
|
}
|
|
@@ -410,7 +459,23 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
410
459
|
return;
|
|
411
460
|
}
|
|
412
461
|
try {
|
|
413
|
-
|
|
462
|
+
// Security: validate that the requested file stays within the user's
|
|
463
|
+
// home directory. Without this, ?file=/etc/passwd discloses arbitrary
|
|
464
|
+
// system files to any process that can reach localhost:4242.
|
|
465
|
+
const _resolvedFile = path.resolve(file);
|
|
466
|
+
const _homeDir = os.homedir();
|
|
467
|
+
if (!_resolvedFile.startsWith(_homeDir + path.sep) && !_resolvedFile.startsWith(_homeDir)) {
|
|
468
|
+
res.writeHead(403, { 'Content-Type': 'application/json' });
|
|
469
|
+
res.end(JSON.stringify({ error: 'Access denied: file must be within the home directory' }));
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
// Only allow JSONL files (session logs).
|
|
473
|
+
if (!_resolvedFile.endsWith('.jsonl')) {
|
|
474
|
+
res.writeHead(403, { 'Content-Type': 'application/json' });
|
|
475
|
+
res.end(JSON.stringify({ error: 'Access denied: only .jsonl files are permitted' }));
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
const raw = fs.readFileSync(_resolvedFile, 'utf8');
|
|
414
479
|
const allLines = raw.split('\n').filter(Boolean);
|
|
415
480
|
const lines = allLines.slice(-limit);
|
|
416
481
|
const events = parseSessionLines(lines);
|
|
@@ -450,7 +515,7 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
450
515
|
for (const { f, mtime } of sessionFiles) {
|
|
451
516
|
const fp = path.join(projectClaudeDir, f);
|
|
452
517
|
const id = f.replace('.jsonl', '');
|
|
453
|
-
let lastPrompt = '', summaries = [], totalDurationMs = 0, totalMessages = 0, firstTs = null, lastTs = null, totalCost = 0, toolCalls = 0, userMessages = 0, cacheReadTokens = 0, totalInputTokens = 0;
|
|
518
|
+
let lastPrompt = '', summaries = [], totalDurationMs = 0, totalMessages = 0, firstTs = null, lastTs = null, totalCost = 0, toolCalls = 0, userMessages = 0, cacheReadTokens = 0, totalInputTokens = 0, errorCount = 0;
|
|
454
519
|
const modelBreakdown = {};
|
|
455
520
|
const filesTouchedSet = new Set();
|
|
456
521
|
try {
|
|
@@ -460,7 +525,12 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
460
525
|
let e; try { e = JSON.parse(line); } catch { continue; }
|
|
461
526
|
if (e.timestamp) { if (!firstTs) firstTs = e.timestamp; lastTs = e.timestamp; }
|
|
462
527
|
if (e.type === 'last-prompt' && e.lastPrompt) lastPrompt = e.lastPrompt;
|
|
463
|
-
if (e.type === 'user')
|
|
528
|
+
if (e.type === 'user') {
|
|
529
|
+
userMessages++;
|
|
530
|
+
for (const b of (e.message?.content || [])) {
|
|
531
|
+
if (b && b.type === 'tool_result' && b.is_error) errorCount++;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
464
534
|
if (e.type === 'system' && e.subtype === 'compact_boundary') pendingCompact = true;
|
|
465
535
|
if (pendingCompact && e.type === 'user') {
|
|
466
536
|
const msg = e.message || {};
|
|
@@ -502,7 +572,9 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
502
572
|
}
|
|
503
573
|
} catch {}
|
|
504
574
|
const filesTouched = [...filesTouchedSet].slice(0, 20);
|
|
505
|
-
|
|
575
|
+
const compactCount = summaries.length;
|
|
576
|
+
const summary = summaries.length ? summaries[summaries.length - 1].text : null;
|
|
577
|
+
sessions.push({ id, mtime, firstTs, lastTs, lastPrompt, summaries, summary, compactCount, errorCount, totalDurationMs, totalMessages, totalCost, toolCalls, userMessages, cacheReadTokens, totalInputTokens, modelBreakdown, filesTouched, file: fp });
|
|
506
578
|
}
|
|
507
579
|
res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', 'Cache-Control': 'no-cache' });
|
|
508
580
|
res.end(JSON.stringify({ sessions }));
|
|
@@ -569,6 +641,69 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
569
641
|
return;
|
|
570
642
|
}
|
|
571
643
|
|
|
644
|
+
// ------------------------------------------------------- GET /api/recent-events
|
|
645
|
+
if (req.method === 'GET' && url === '/api/recent-events') {
|
|
646
|
+
try {
|
|
647
|
+
const qs = new URL(req.url, 'http://localhost').searchParams;
|
|
648
|
+
const dir = qs.get('dir') || projectDir || process.cwd();
|
|
649
|
+
const limit = Math.min(parseInt(qs.get('limit') || '50', 10), 200);
|
|
650
|
+
const d = path.resolve(dir || process.cwd());
|
|
651
|
+
const slug = d.replace(/\//g, '-');
|
|
652
|
+
const projectClaudeDir = path.join(os.homedir(), '.claude', 'projects', slug);
|
|
653
|
+
let sessionFiles = [];
|
|
654
|
+
try {
|
|
655
|
+
sessionFiles = fs.readdirSync(projectClaudeDir)
|
|
656
|
+
.filter(f => f.endsWith('.jsonl'))
|
|
657
|
+
.map(f => { try { return { f, mtime: fs.statSync(path.join(projectClaudeDir, f)).mtimeMs }; } catch { return null; } })
|
|
658
|
+
.filter(Boolean)
|
|
659
|
+
.sort((a, b) => b.mtime - a.mtime)
|
|
660
|
+
.slice(0, 5); // check last 5 sessions
|
|
661
|
+
} catch {}
|
|
662
|
+
|
|
663
|
+
const events = [];
|
|
664
|
+
const HOOK_RE = /^<(local-command-|command-name>|command-message>)/;
|
|
665
|
+
for (const { f } of sessionFiles) {
|
|
666
|
+
const fp = path.join(projectClaudeDir, f);
|
|
667
|
+
const sessId = f.replace('.jsonl', '');
|
|
668
|
+
try {
|
|
669
|
+
const lines = fs.readFileSync(fp, 'utf8').split('\n').filter(Boolean).slice(-200);
|
|
670
|
+
for (const line of lines) {
|
|
671
|
+
let e; try { e = JSON.parse(line); } catch { continue; }
|
|
672
|
+
if (e.type === 'assistant') {
|
|
673
|
+
const content = e.message?.content || [];
|
|
674
|
+
for (const block of content) {
|
|
675
|
+
if (block?.type === 'tool_use') {
|
|
676
|
+
events.push({ kind: 'tool', ts: e.timestamp, tool: block.name, session: sessId });
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
} else if (e.type === 'user') {
|
|
680
|
+
const content = e.message?.content || [];
|
|
681
|
+
for (const block of content) {
|
|
682
|
+
if (block?.type === 'text' && block.text?.trim() && !HOOK_RE.test(block.text.trim())) {
|
|
683
|
+
events.push({ kind: 'user', ts: e.timestamp, text: block.text.slice(0, 120), session: sessId });
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
} catch {}
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
// sort by ts desc, take limit
|
|
692
|
+
events.sort((a, b) => {
|
|
693
|
+
const ta = a.ts ? new Date(a.ts).getTime() : 0;
|
|
694
|
+
const tb = b.ts ? new Date(b.ts).getTime() : 0;
|
|
695
|
+
return tb - ta;
|
|
696
|
+
});
|
|
697
|
+
|
|
698
|
+
res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
|
|
699
|
+
res.end(JSON.stringify({ events: events.slice(0, limit) }));
|
|
700
|
+
} catch (err) {
|
|
701
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
702
|
+
res.end(JSON.stringify({ error: err.message }));
|
|
703
|
+
}
|
|
704
|
+
return;
|
|
705
|
+
}
|
|
706
|
+
|
|
572
707
|
// ------------------------------------------------------- GET /api/tool-errors
|
|
573
708
|
if (req.method === 'GET' && url === '/api/tool-errors') {
|
|
574
709
|
try {
|
|
@@ -891,7 +1026,7 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
891
1026
|
// ------------------------------------------------------- PUT /api/memory-file
|
|
892
1027
|
if (req.method === 'PUT' && url === '/api/memory-file') {
|
|
893
1028
|
let body = '';
|
|
894
|
-
req.on('data', chunk => { body += chunk; });
|
|
1029
|
+
req.on('data', chunk => { body += chunk; if (body.length > 2097152) { req.destroy(); return; } });
|
|
895
1030
|
req.on('end', () => {
|
|
896
1031
|
try {
|
|
897
1032
|
const qs = new URL(req.url, 'http://localhost').searchParams;
|
|
@@ -925,7 +1060,7 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
925
1060
|
// ------------------------------------------------------- DELETE /api/memory-file
|
|
926
1061
|
if (req.method === 'DELETE' && url === '/api/memory-file') {
|
|
927
1062
|
let body = '';
|
|
928
|
-
req.on('data', chunk => { body += chunk; });
|
|
1063
|
+
req.on('data', chunk => { body += chunk; if (body.length > 2097152) { req.destroy(); return; } });
|
|
929
1064
|
req.on('end', () => {
|
|
930
1065
|
try {
|
|
931
1066
|
const qs = new URL(req.url, 'http://localhost').searchParams;
|
|
@@ -1060,8 +1195,7 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
1060
1195
|
let alive = false;
|
|
1061
1196
|
try { process.kill(pid, 0); alive = true; } catch {}
|
|
1062
1197
|
const alreadyTracked = loops.some(l => l.id === sessionId || l.sessionId === sessionId);
|
|
1063
|
-
|
|
1064
|
-
if (alive && sessionId && !alreadyTracked && !hasRepeatLoops && !stopFiles.has(sessionId)) {
|
|
1198
|
+
if (alive && sessionId && !alreadyTracked && !stopFiles.has(sessionId)) {
|
|
1065
1199
|
// Try to extract ScheduleWakeup context from session JSONL
|
|
1066
1200
|
let loopEntry = null;
|
|
1067
1201
|
try {
|
|
@@ -1144,6 +1278,10 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
1144
1278
|
}
|
|
1145
1279
|
} catch {}
|
|
1146
1280
|
|
|
1281
|
+
// Dedup: suppress scheduled_tasks_lock noise when real repeat loops exist
|
|
1282
|
+
const hasRepeatLoops = loops.some(l => l.source !== 'scheduled_tasks_lock' && l.source !== 'schedule_wakeup_hook');
|
|
1283
|
+
if (hasRepeatLoops) loops = loops.filter(l => l.source !== 'scheduled_tasks_lock' && l.source !== 'schedule_wakeup_hook');
|
|
1284
|
+
|
|
1147
1285
|
loops.sort((a, b) => (b.startedAt || 0) - (a.startedAt || 0));
|
|
1148
1286
|
res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', 'Cache-Control': 'no-cache' });
|
|
1149
1287
|
res.end(JSON.stringify({ loops }));
|
|
@@ -1154,13 +1292,14 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
1154
1292
|
// ---------------------------------------------------------- POST /api/loops/stop
|
|
1155
1293
|
if (req.method === 'POST' && url === '/api/loops/stop') {
|
|
1156
1294
|
let body = '';
|
|
1157
|
-
req.on('data', chunk => { body += chunk; });
|
|
1295
|
+
req.on('data', chunk => { body += chunk; if (body.length > 2097152) { req.destroy(); return; } });
|
|
1158
1296
|
req.on('end', () => {
|
|
1159
1297
|
try {
|
|
1160
|
-
const stopQs = new URL(req.url, 'http://localhost').searchParams;
|
|
1161
1298
|
const { id } = JSON.parse(body);
|
|
1162
1299
|
if (!id) { res.writeHead(400); res.end(JSON.stringify({ error: 'id required' })); return; }
|
|
1163
|
-
const
|
|
1300
|
+
const _stopQs = new URL(req.url, 'http://localhost').searchParams;
|
|
1301
|
+
const _stopDir = path.resolve(_stopQs.get('dir') || projectDir || process.cwd());
|
|
1302
|
+
const loopsDir = path.join(_stopDir, '.monomind', 'loops');
|
|
1164
1303
|
fs.mkdirSync(loopsDir, { recursive: true });
|
|
1165
1304
|
fs.writeFileSync(path.join(loopsDir, `${id}.stop`), `stop-requested-${Date.now()}`);
|
|
1166
1305
|
res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
|
|
@@ -1173,17 +1312,27 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
1173
1312
|
// ---------------------------------------------------------- POST /api/loops/create
|
|
1174
1313
|
if (req.method === 'POST' && url === '/api/loops/create') {
|
|
1175
1314
|
let body = '';
|
|
1176
|
-
req.on('data', chunk => { body += chunk; });
|
|
1315
|
+
req.on('data', chunk => { body += chunk; if (body.length > 2097152) { req.destroy(); return; } });
|
|
1177
1316
|
req.on('end', () => {
|
|
1178
1317
|
try {
|
|
1179
|
-
const
|
|
1180
|
-
const { name, prompt, interval, maxReps } = JSON.parse(body);
|
|
1318
|
+
const _qs = new URL(req.url, 'http://localhost').searchParams;
|
|
1319
|
+
const { name: _rawName, prompt: _rawPrompt, interval: _rawInterval, maxReps: _rawMaxReps } = JSON.parse(body);
|
|
1320
|
+
// Cap field sizes to prevent individual large-field disk inflation.
|
|
1321
|
+
// The 2MB body cap already limits total payload, but a single field
|
|
1322
|
+
// near 2MB would produce a multi-MB loop config file per request.
|
|
1323
|
+
const MAX_LOOP_PROMPT_LEN = 64 * 1024; // 64 KB
|
|
1324
|
+
const MAX_LOOP_NAME_LEN = 512;
|
|
1325
|
+
const MAX_LOOP_INTERVAL_LEN = 64;
|
|
1326
|
+
const prompt = typeof _rawPrompt === 'string' ? _rawPrompt.slice(0, MAX_LOOP_PROMPT_LEN) : null;
|
|
1327
|
+
const name = typeof _rawName === 'string' ? _rawName.slice(0, MAX_LOOP_NAME_LEN) : null;
|
|
1328
|
+
const interval = typeof _rawInterval === 'string' ? _rawInterval.slice(0, MAX_LOOP_INTERVAL_LEN) : null;
|
|
1329
|
+
const maxReps = typeof _rawMaxReps === 'number' && Number.isFinite(_rawMaxReps) ? Math.max(1, Math.min(Math.floor(_rawMaxReps), 10000)) : null;
|
|
1181
1330
|
if (!prompt) { res.writeHead(400); res.end(JSON.stringify({ error: 'prompt required' })); return; }
|
|
1182
|
-
const loopsDir = path.join(
|
|
1331
|
+
const loopsDir = path.join(path.resolve(_qs.get('dir') || projectDir || process.cwd()), '.monomind', 'loops');
|
|
1183
1332
|
fs.mkdirSync(loopsDir, { recursive: true });
|
|
1184
1333
|
const id = `loop-${Date.now()}-${Math.random().toString(36).slice(2,7)}`;
|
|
1185
1334
|
const nowMs = Date.now();
|
|
1186
|
-
const loop = { id, type: 'repeat', name: name || prompt.slice(0, 40), prompt, interval: interval || '1h', maxReps
|
|
1335
|
+
const loop = { id, type: 'repeat', name: name || prompt.slice(0, 40), prompt, interval: interval || '1h', maxReps, status: 'active', currentRep: 0, startedAt: nowMs, lastRunAt: null };
|
|
1187
1336
|
fs.writeFileSync(path.join(loopsDir, `${id}.json`), JSON.stringify(loop, null, 2));
|
|
1188
1337
|
res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
|
|
1189
1338
|
res.end(JSON.stringify({ ok: true, id }));
|
|
@@ -1196,7 +1345,10 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
1196
1345
|
if (req.method === 'GET' && url === '/api/session-errors') {
|
|
1197
1346
|
const qs = new URL(req.url, 'http://localhost').searchParams;
|
|
1198
1347
|
const d = path.resolve(qs.get('dir') || projectDir || process.cwd());
|
|
1199
|
-
|
|
1348
|
+
// Cap sessionId to prevent O(n×m) DoS via f.includes(sessionId) substring
|
|
1349
|
+
// match against every filename when sessionId is a very long string.
|
|
1350
|
+
const _rawSessId = qs.get('id') || '';
|
|
1351
|
+
const sessionId = _rawSessId.slice(0, 256);
|
|
1200
1352
|
const slug = d.replace(/\//g, '-');
|
|
1201
1353
|
const projectClaudeDir = path.join(os.homedir(), '.claude', 'projects', slug);
|
|
1202
1354
|
try {
|
|
@@ -1267,7 +1419,7 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
1267
1419
|
// ------------------------------------------------------- DELETE /api/knowledge-chunk
|
|
1268
1420
|
if (req.method === 'DELETE' && url === '/api/knowledge-chunk') {
|
|
1269
1421
|
let body = '';
|
|
1270
|
-
req.on('data', chunk => { body += chunk; });
|
|
1422
|
+
req.on('data', chunk => { body += chunk; if (body.length > 2097152) { req.destroy(); return; } });
|
|
1271
1423
|
req.on('end', () => {
|
|
1272
1424
|
try {
|
|
1273
1425
|
const qs = new URL(req.url, 'http://localhost').searchParams;
|
|
@@ -1306,7 +1458,7 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
1306
1458
|
// ------------------------------------------------------- PUT /api/knowledge-chunk
|
|
1307
1459
|
if (req.method === 'PUT' && url === '/api/knowledge-chunk') {
|
|
1308
1460
|
let body = '';
|
|
1309
|
-
req.on('data', chunk => { body += chunk; });
|
|
1461
|
+
req.on('data', chunk => { body += chunk; if (body.length > 2097152) { req.destroy(); return; } });
|
|
1310
1462
|
req.on('end', () => {
|
|
1311
1463
|
try {
|
|
1312
1464
|
const qs = new URL(req.url, 'http://localhost').searchParams;
|
|
@@ -1802,7 +1954,8 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
1802
1954
|
try {
|
|
1803
1955
|
const qs = new URL(req.url, 'http://localhost').searchParams;
|
|
1804
1956
|
const dir = qs.get('dir') || projectDir || process.cwd();
|
|
1805
|
-
|
|
1957
|
+
// Cap ?q= to prevent DoS via megabyte FTS query strings.
|
|
1958
|
+
const q = (qs.get('q') || '').trim().slice(0, 4096);
|
|
1806
1959
|
const limit = Math.min(100, parseInt(qs.get('limit') || '50', 10));
|
|
1807
1960
|
const d = path.resolve(dir || process.cwd());
|
|
1808
1961
|
const dbPath = path.join(d, '.monomind', 'monograph.db');
|
|
@@ -1936,7 +2089,7 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
1936
2089
|
try {
|
|
1937
2090
|
const qs = new URL(req.url, 'http://localhost').searchParams;
|
|
1938
2091
|
const dir = qs.get('dir') || projectDir || process.cwd();
|
|
1939
|
-
const q = qs.get('q') || '';
|
|
2092
|
+
const q = (qs.get('q') || '').trim().slice(0, 4096);
|
|
1940
2093
|
const d = path.resolve(dir || process.cwd());
|
|
1941
2094
|
const dbPath = path.join(d, '.monomind', 'monograph.db');
|
|
1942
2095
|
if (!q) { res.writeHead(400); res.end(JSON.stringify({ error: 'Missing ?q= parameter' })); return; }
|
|
@@ -1973,7 +2126,7 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
1973
2126
|
try {
|
|
1974
2127
|
const qs = new URL(req.url, 'http://localhost').searchParams;
|
|
1975
2128
|
const dir = qs.get('dir') || projectDir || process.cwd();
|
|
1976
|
-
const nodeQ = qs.get('node') || '';
|
|
2129
|
+
const nodeQ = (qs.get('node') || '').trim().slice(0, 4096);
|
|
1977
2130
|
const d = path.resolve(dir || process.cwd());
|
|
1978
2131
|
const dbPath = path.join(d, '.monomind', 'monograph.db');
|
|
1979
2132
|
if (!nodeQ) { res.writeHead(400); res.end(JSON.stringify({ error: 'Missing ?node= parameter' })); return; }
|
|
@@ -2015,8 +2168,8 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
2015
2168
|
try {
|
|
2016
2169
|
const qs = new URL(req.url, 'http://localhost').searchParams;
|
|
2017
2170
|
const dir = qs.get('dir') || projectDir || process.cwd();
|
|
2018
|
-
const from = qs.get('from') || '';
|
|
2019
|
-
const to = qs.get('to') || '';
|
|
2171
|
+
const from = (qs.get('from') || '').trim().slice(0, 4096);
|
|
2172
|
+
const to = (qs.get('to') || '').trim().slice(0, 4096);
|
|
2020
2173
|
const d = path.resolve(dir || process.cwd());
|
|
2021
2174
|
const dbPath = path.join(d, '.monomind', 'monograph.db');
|
|
2022
2175
|
if (!from || !to) { res.writeHead(400); res.end(JSON.stringify({ error: 'Missing ?from= and ?to= parameters' })); return; }
|
|
@@ -2136,7 +2289,7 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
2136
2289
|
// -------------------------------------------------- POST /api/mcp/call
|
|
2137
2290
|
if (req.method === 'POST' && url === '/api/mcp/call') {
|
|
2138
2291
|
let body = '';
|
|
2139
|
-
req.on('data', c => body += c);
|
|
2292
|
+
req.on('data', c => { body += c; if (body.length > 2097152) { req.destroy(); return; } });
|
|
2140
2293
|
req.on('end', async () => {
|
|
2141
2294
|
const json = res => { res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' }); };
|
|
2142
2295
|
const ok = (data) => { json(res); res.end(JSON.stringify({ content: [{ type: 'text', text: typeof data === 'string' ? data : JSON.stringify(data, null, 2) }] })); };
|
|
@@ -2184,7 +2337,7 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
2184
2337
|
ok(`nodes: ${n}\nedges: ${e}`);
|
|
2185
2338
|
} else if (tool === 'monograph_cypher') {
|
|
2186
2339
|
// Translate basic MATCH (n:Label) queries to SQL
|
|
2187
|
-
const q = (input.query || '').trim();
|
|
2340
|
+
const q = (String(input.query || '')).trim().slice(0, 4096);
|
|
2188
2341
|
const labelMatch = q.match(/MATCH\s+\(n:(\w+)\)/i);
|
|
2189
2342
|
if (labelMatch) {
|
|
2190
2343
|
const label = labelMatch[1];
|
|
@@ -2242,12 +2395,13 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
2242
2395
|
} else if (tool === 'monograph_diff') {
|
|
2243
2396
|
ok('Graph diff: compare two snapshots using monograph snapshot + monograph diff commands');
|
|
2244
2397
|
} else if (tool === 'monograph_rename') {
|
|
2245
|
-
|
|
2398
|
+
// Cap sym to prevent O(n) FTS scan DoS via oversized query string.
|
|
2399
|
+
const sym = String(input.symbolName || '').slice(0, 4096);
|
|
2246
2400
|
if (!sym) { ok('Provide symbolName to rename'); return; }
|
|
2247
2401
|
const hits = ftsSearch(db2, sym, 20);
|
|
2248
2402
|
ok(`Found ${hits.length} occurrences of "${sym}":\n` + hits.map(h => ` ${h.filePath || '?'}:${h.startLine || '?'} — ${h.name}`).join('\n'));
|
|
2249
2403
|
} else if (tool === 'monograph_impact') {
|
|
2250
|
-
const target = input.target || '';
|
|
2404
|
+
const target = String(input.target || '').slice(0, 4096);
|
|
2251
2405
|
const dir3 = input.direction || 'both';
|
|
2252
2406
|
const depth = input.maxDepth || 4;
|
|
2253
2407
|
const hits = ftsSearch(db2, target, 5);
|
|
@@ -2274,7 +2428,7 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
2274
2428
|
}
|
|
2275
2429
|
ok(`Impact of "${hits[0].name}" (${dir3}, depth=${depth}):\n` + (results.join('\n') || ' (no dependencies found)'));
|
|
2276
2430
|
} else if (tool === 'monograph_context') {
|
|
2277
|
-
const id = input.id || '';
|
|
2431
|
+
const id = String(input.id || '').slice(0, 4096);
|
|
2278
2432
|
const hits = ftsSearch(db2, id, 5);
|
|
2279
2433
|
if (!hits.length) { ok(`Node not found: ${id}`); return; }
|
|
2280
2434
|
const node = hits[0];
|
|
@@ -2282,7 +2436,7 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
2282
2436
|
const inEdges = db2.prepare('SELECT e.relation, n.name FROM edges e JOIN nodes n ON n.id = e.source_id WHERE e.target_id = ? LIMIT 20').all(node.id);
|
|
2283
2437
|
ok(`# ${node.name} (${node.label})\nFile: ${node.filePath || '?'}\n\n**Imports / depends on (${outEdges.length}):**\n${outEdges.map(e => ` → ${e.name} [${e.relation}]`).join('\n') || ' (none)'}\n\n**Used by / depended on by (${inEdges.length}):**\n${inEdges.map(e => ` ← ${e.name} [${e.relation}]`).join('\n') || ' (none)'}`);
|
|
2284
2438
|
} else if (tool === 'monograph_query' || tool === 'monograph_suggest') {
|
|
2285
|
-
const q2 = input.query || input.task || '';
|
|
2439
|
+
const q2 = String(input.query || input.task || '').slice(0, 4096);
|
|
2286
2440
|
const hits2 = ftsSearch(db2, q2, 20);
|
|
2287
2441
|
ok(hits2.map(h => `${h.name} (${h.label}) — ${h.filePath || '?'}:${h.startLine || '?'}`).join('\n') || 'No results');
|
|
2288
2442
|
|
|
@@ -2631,7 +2785,7 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
2631
2785
|
if (['Read','Write','Edit','MultiEdit','Glob','Grep','LS'].includes(name)) return 'file';
|
|
2632
2786
|
if (name === 'Bash') return 'bash';
|
|
2633
2787
|
if (['Agent','Task'].includes(name)) return 'agent';
|
|
2634
|
-
if (name.startsWith('
|
|
2788
|
+
if (name.startsWith('mcp__monomind__memory') || name.startsWith('mcp__monomind__agentdb')) return 'memory';
|
|
2635
2789
|
if (['WebFetch','WebSearch'].includes(name)) return 'web';
|
|
2636
2790
|
if (name === 'Skill') return 'skill';
|
|
2637
2791
|
return 'other';
|
|
@@ -2648,9 +2802,35 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
2648
2802
|
try { stat = fs.statSync(fp); } catch { continue; }
|
|
2649
2803
|
|
|
2650
2804
|
// Skip files over size cap to avoid memory spikes on large sessions
|
|
2805
|
+
// But still do a lightweight scan for agent spawns (tool_use blocks named Agent/Task)
|
|
2651
2806
|
if (stat.size > JSONL_SIZE_CAP) {
|
|
2807
|
+
const truncSpawns = {};
|
|
2808
|
+
try {
|
|
2809
|
+
const raw = fs.readFileSync(fp, 'utf8');
|
|
2810
|
+
for (const line of raw.split('\n')) {
|
|
2811
|
+
if (!line.includes('"tool_use"') || (!line.includes('"Agent"') && !line.includes('"Task"'))) continue;
|
|
2812
|
+
let e; try { e = JSON.parse(line); } catch { continue; }
|
|
2813
|
+
if (e.type !== 'assistant') continue;
|
|
2814
|
+
for (const block of (e.message?.content || [])) {
|
|
2815
|
+
if (!block || block.type !== 'tool_use') continue;
|
|
2816
|
+
if (block.name !== 'Agent' && block.name !== 'Task') continue;
|
|
2817
|
+
const sub = block.input?.subagent_type || block.input?.description || '?';
|
|
2818
|
+
truncSpawns[sub] = (truncSpawns[sub] || 0) + 1;
|
|
2819
|
+
}
|
|
2820
|
+
}
|
|
2821
|
+
} catch {}
|
|
2652
2822
|
nodes.push({ id: sid, type: 'session', label: sid.slice(0,8), turns: 0, totalTools: 0,
|
|
2653
|
-
toolCounts: {}, cost: 0, mtime: stat.mtimeMs, size: stat.size, agentSpawns:
|
|
2823
|
+
toolCounts: {}, cost: 0, mtime: stat.mtimeMs, size: stat.size, agentSpawns: truncSpawns, truncated: true });
|
|
2824
|
+
for (const [subType, count] of Object.entries(truncSpawns)) {
|
|
2825
|
+
const nodeId = 'agent::' + subType;
|
|
2826
|
+
if (!agentTypeNodes[subType]) {
|
|
2827
|
+
agentTypeNodes[subType] = true;
|
|
2828
|
+
nodes.push({ id: nodeId, type: 'agenttype', label: subType, totalSpawns: 0 });
|
|
2829
|
+
}
|
|
2830
|
+
const aNode = nodes.find(n => n.id === nodeId);
|
|
2831
|
+
if (aNode) aNode.totalSpawns = (aNode.totalSpawns || 0) + count;
|
|
2832
|
+
edges.push({ source: sid, target: nodeId, weight: count, label: String(count) });
|
|
2833
|
+
}
|
|
2654
2834
|
continue;
|
|
2655
2835
|
}
|
|
2656
2836
|
|
|
@@ -2663,7 +2843,12 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
2663
2843
|
const lines = raw.split('\n').filter(Boolean);
|
|
2664
2844
|
for (const line of lines) {
|
|
2665
2845
|
let e; try { e = JSON.parse(line); } catch { continue; }
|
|
2666
|
-
if (e.type === 'user')
|
|
2846
|
+
if (e.type === 'user') {
|
|
2847
|
+
// Only count actual human turns, not tool-result responses
|
|
2848
|
+
const ct = e.message?.content;
|
|
2849
|
+
const isToolResult = Array.isArray(ct) && ct.length > 0 && ct.every(b => b && b.type === 'tool_result');
|
|
2850
|
+
if (!isToolResult) turns++;
|
|
2851
|
+
}
|
|
2667
2852
|
if (e.type === 'assistant') {
|
|
2668
2853
|
for (const block of (e.message?.content || [])) {
|
|
2669
2854
|
if (!block || block.type !== 'tool_use') continue;
|
|
@@ -2674,8 +2859,8 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
2674
2859
|
agentSpawns[sub] = (agentSpawns[sub] || 0) + 1;
|
|
2675
2860
|
}
|
|
2676
2861
|
}
|
|
2862
|
+
if (e.message?.usage) totalCost += _sjCalcCost(e.message.model || '', e.message.usage);
|
|
2677
2863
|
}
|
|
2678
|
-
if (e.costUSD) totalCost += e.costUSD;
|
|
2679
2864
|
}
|
|
2680
2865
|
} catch {}
|
|
2681
2866
|
|
|
@@ -2733,8 +2918,13 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
2733
2918
|
try {
|
|
2734
2919
|
const qs = new URL(req.url, 'http://localhost').searchParams;
|
|
2735
2920
|
const dir = qs.get('dir') || projectDir || process.cwd();
|
|
2736
|
-
|
|
2737
|
-
|
|
2921
|
+
// Cap swarmId and agentId to prevent O(n×m) DoS: filter() compares
|
|
2922
|
+
// each event against the query string, so a megabyte-scale ID causes
|
|
2923
|
+
// O(events × m) string comparisons.
|
|
2924
|
+
const _rawSwarmId = qs.get('swarmId') || undefined;
|
|
2925
|
+
const _rawAgentId = qs.get('agentId') || undefined;
|
|
2926
|
+
const swarmId = typeof _rawSwarmId === 'string' ? _rawSwarmId.slice(0, 256) : undefined;
|
|
2927
|
+
const agentId = typeof _rawAgentId === 'string' ? _rawAgentId.slice(0, 256) : undefined;
|
|
2738
2928
|
const last = qs.get('last') ? parseInt(qs.get('last')) : undefined;
|
|
2739
2929
|
const events = collectSwarmEvents(path.resolve(dir), { swarmId, agentId, last });
|
|
2740
2930
|
res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', 'Cache-Control': 'no-cache' });
|
|
@@ -2778,47 +2968,71 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
2778
2968
|
if (req.method === 'GET' && url.startsWith('/api/token-usage')) {
|
|
2779
2969
|
try {
|
|
2780
2970
|
const qs = new URL(req.url, 'http://localhost').searchParams;
|
|
2781
|
-
const period = qs.get('period')
|
|
2971
|
+
const period = ['today','week','30days','month'].includes(qs.get('period')) ? qs.get('period') : 'today';
|
|
2782
2972
|
const dir = path.resolve(qs.get('dir') || projectDir || process.cwd());
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
if (period === 'today') {
|
|
2790
|
-
fromStr = todayStr; daysBack = 1;
|
|
2791
|
-
} else if (period === 'week') {
|
|
2792
|
-
const d = new Date(now); d.setDate(d.getDate() - 6);
|
|
2793
|
-
fromStr = d.toISOString().slice(0, 10); daysBack = 7;
|
|
2794
|
-
} else if (period === '30d' || period === '30days') {
|
|
2795
|
-
const d = new Date(now); d.setDate(d.getDate() - 29);
|
|
2796
|
-
fromStr = d.toISOString().slice(0, 10); daysBack = 30;
|
|
2797
|
-
} else { // month
|
|
2798
|
-
fromStr = now.toISOString().slice(0, 7) + '-01'; daysBack = 32;
|
|
2799
|
-
}
|
|
2800
|
-
|
|
2801
|
-
const raw = collectTokens(dir, Math.max(daysBack + 1, 14));
|
|
2802
|
-
// Filter daily to the requested window
|
|
2803
|
-
const daily = (raw.daily || []).filter(d => d.date >= fromStr);
|
|
2804
|
-
// Aggregate summary for the period
|
|
2805
|
-
let cost = 0, calls = 0, tokensIn = 0, tokensOut = 0;
|
|
2806
|
-
for (const d of daily) { cost += d.cost; calls += d.calls; tokensIn += d.tokensIn; tokensOut += d.tokensOut; }
|
|
2807
|
-
// Filter rows to sessions that had activity in the window
|
|
2808
|
-
const rows = (raw.rows || []).filter(r => {
|
|
2809
|
-
// rows don't have dates yet, just include top ones by cost
|
|
2810
|
-
return true;
|
|
2811
|
-
}).slice(0, 30);
|
|
2812
|
-
|
|
2813
|
-
const summary = {
|
|
2814
|
-
todayCost: cost,
|
|
2815
|
-
todayCalls: calls,
|
|
2816
|
-
cost, calls,
|
|
2817
|
-
totalTokens: tokensIn + tokensOut,
|
|
2818
|
-
modelCount: Object.keys(raw.summary?.modelBreakdown || {}).length || null,
|
|
2973
|
+
const trackerPath = path.join(dir, '.claude', 'helpers', 'token-tracker.cjs');
|
|
2974
|
+
const fallback = () => {
|
|
2975
|
+
const summary = (() => { try { return JSON.parse(fs.readFileSync(path.join(dir, '.monomind', 'metrics', 'token-summary.json'), 'utf8')); } catch { return {}; } })();
|
|
2976
|
+
res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', 'Cache-Control': 'no-cache' });
|
|
2977
|
+
const fbSum = { todayCost: summary.todayCost || 0, cost: summary.todayCost || 0, todayCalls: summary.todayCalls || 0, calls: summary.todayCalls || 0, totalTokens: 0, totalTokensIn: 0, totalTokensOut: 0, cacheTokens: 0, modelCount: 0 };
|
|
2978
|
+
res.end(JSON.stringify({ summary: fbSum, totalCost: summary.todayCost || 0, totalCalls: summary.todayCalls || 0, totalIn: 0, totalOut: 0, totalCR: 0, totalCW: 0, rows: [], models: [], categories: [], tools: [], mcpServers: [], projects: [], modelBreakdown: {}, categoryBreakdown: {}, toolBreakdown: {}, mcpBreakdown: {}, periodLabel: period }));
|
|
2819
2979
|
};
|
|
2820
|
-
|
|
2821
|
-
|
|
2980
|
+
if (!fs.existsSync(trackerPath)) { fallback(); return; }
|
|
2981
|
+
try {
|
|
2982
|
+
const _req = createRequire(import.meta.url);
|
|
2983
|
+
const tracker = _req(trackerPath);
|
|
2984
|
+
const range = tracker.getDateRange(period);
|
|
2985
|
+
const projects = tracker.parseAllSessions(range.start, range.end);
|
|
2986
|
+
let totalCost = 0, totalIn = 0, totalOut = 0, totalCR = 0, totalCW = 0, totalCalls = 0;
|
|
2987
|
+
const modelBreakdown = {}, categoryBreakdown = {}, toolBreakdown = {}, mcpBreakdown = {};
|
|
2988
|
+
for (const p of projects) {
|
|
2989
|
+
totalCost += p.totalCost || 0;
|
|
2990
|
+
for (const s of (p.sessions || [])) {
|
|
2991
|
+
totalIn += s.totalInputTokens || 0;
|
|
2992
|
+
totalOut += s.totalOutputTokens || 0;
|
|
2993
|
+
totalCR += s.totalCacheRead || 0;
|
|
2994
|
+
totalCW += s.totalCacheWrite || 0;
|
|
2995
|
+
totalCalls += s.apiCalls || 0;
|
|
2996
|
+
for (const [mn, m] of Object.entries(s.modelBreakdown || {})) {
|
|
2997
|
+
if (!modelBreakdown[mn]) modelBreakdown[mn] = { calls: 0, cost: 0, tokens: 0 };
|
|
2998
|
+
modelBreakdown[mn].calls += m.calls || 0;
|
|
2999
|
+
modelBreakdown[mn].cost += m.cost || 0;
|
|
3000
|
+
modelBreakdown[mn].tokens += m.tokens || 0;
|
|
3001
|
+
}
|
|
3002
|
+
for (const [cat, c] of Object.entries(s.categoryBreakdown || {})) {
|
|
3003
|
+
if (!categoryBreakdown[cat]) categoryBreakdown[cat] = { turns: 0, cost: 0 };
|
|
3004
|
+
categoryBreakdown[cat].turns += c.turns || 0;
|
|
3005
|
+
categoryBreakdown[cat].cost += c.cost || 0;
|
|
3006
|
+
}
|
|
3007
|
+
for (const [tool, t] of Object.entries(s.toolBreakdown || {})) {
|
|
3008
|
+
if (!toolBreakdown[tool]) toolBreakdown[tool] = { calls: 0 };
|
|
3009
|
+
toolBreakdown[tool].calls += t.calls || 0;
|
|
3010
|
+
}
|
|
3011
|
+
for (const [srv, m] of Object.entries(s.mcpBreakdown || {})) {
|
|
3012
|
+
if (!mcpBreakdown[srv]) mcpBreakdown[srv] = { calls: 0 };
|
|
3013
|
+
mcpBreakdown[srv].calls += m.calls || 0;
|
|
3014
|
+
}
|
|
3015
|
+
}
|
|
3016
|
+
}
|
|
3017
|
+
// Build client-friendly arrays from breakdown dicts
|
|
3018
|
+
const models = Object.entries(modelBreakdown).map(([model, m]) => ({ model, cost: m.cost, calls: m.calls, tokens: m.tokens })).sort((a, b) => b.cost - a.cost);
|
|
3019
|
+
const categories = Object.entries(categoryBreakdown).map(([category, c]) => ({ category, turns: c.turns, cost: c.cost })).sort((a, b) => b.turns - a.turns);
|
|
3020
|
+
const tools = Object.entries(toolBreakdown).map(([tool, t]) => ({ tool, count: t.calls })).sort((a, b) => b.count - a.count);
|
|
3021
|
+
const mcpServers = Object.entries(mcpBreakdown).map(([server, m]) => ({ server, count: m.calls })).sort((a, b) => b.count - a.count);
|
|
3022
|
+
const projectRows = projects.map(p => ({ project: p.name || p.slug || p.dir || '?', cost: p.totalCost || 0 })).sort((a, b) => b.cost - a.cost);
|
|
3023
|
+
// Build rows array from sessions for per-session table
|
|
3024
|
+
const rows = [];
|
|
3025
|
+
for (const p of projects) {
|
|
3026
|
+
for (const s of (p.sessions || [])) {
|
|
3027
|
+
rows.push({ id: s.id || '', session: s.lastPrompt || s.id || '', calls: s.apiCalls || 0, cost: s.totalCost || 0, tokens: (s.totalInputTokens || 0) + (s.totalOutputTokens || 0) });
|
|
3028
|
+
}
|
|
3029
|
+
}
|
|
3030
|
+
rows.sort((a, b) => b.cost - a.cost);
|
|
3031
|
+
// Summary object matching client expectations
|
|
3032
|
+
const summary = { todayCost: totalCost, cost: totalCost, todayCalls: totalCalls, calls: totalCalls, totalTokens: totalIn + totalOut, totalTokensIn: totalIn, totalTokensOut: totalOut, cacheTokens: totalCR, modelCount: models.length };
|
|
3033
|
+
res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', 'Cache-Control': 'no-cache' });
|
|
3034
|
+
res.end(JSON.stringify({ summary, totalCost, totalCalls, totalIn, totalOut, totalCR, totalCW, rows, models, categories, tools, mcpServers, projects: projectRows, modelBreakdown, categoryBreakdown, toolBreakdown, mcpBreakdown, periodLabel: period }));
|
|
3035
|
+
} catch (e) { fallback(); }
|
|
2822
3036
|
} catch (err) {
|
|
2823
3037
|
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
2824
3038
|
res.end(JSON.stringify({ error: err.message }));
|
|
@@ -2972,7 +3186,7 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
2972
3186
|
const orgsDir = path.join(path.resolve(_orgsQs.get('dir') || projectDir || process.cwd()), '.monomind', 'orgs');
|
|
2973
3187
|
let orgs = [];
|
|
2974
3188
|
if (fs.existsSync(orgsDir)) {
|
|
2975
|
-
const _sidecarSuffixRe = /-(approvals|state|activity|goals|routines|projects|members|issues|workspaces|worktrees|environments|plugins|adapters|bootstrap|threads|budgets|project-workspaces|approval-comments|secrets)\.json$/;
|
|
3189
|
+
const _sidecarSuffixRe = /-(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$/;
|
|
2976
3190
|
const files = fs.readdirSync(orgsDir).filter(f => f.endsWith('.json') && !_sidecarSuffixRe.test(f));
|
|
2977
3191
|
// Read events file once, outside the per-org loop
|
|
2978
3192
|
let recentLines = [];
|
|
@@ -3003,7 +3217,7 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
3003
3217
|
const stopTs = lastStop ? (JSON.parse(lastStop).ts || 0) : 0;
|
|
3004
3218
|
running = startTs > stopTs;
|
|
3005
3219
|
}
|
|
3006
|
-
orgs.push({ name: cfg.name, goal: cfg.goal, roles: cfg.roles
|
|
3220
|
+
orgs.push({ name: cfg.name, goal: cfg.goal, roles: Array.isArray(cfg.roles) ? cfg.roles : [], topology: cfg.topology, created_at: cfg.created_at, running, status: cfg.status, loop: cfg.loop ? { poll_interval_minutes: cfg.loop.poll_interval_minutes, last_run: cfg.loop.last_run, next_run: cfg.loop.next_run } : undefined });
|
|
3007
3221
|
} catch(_) {}
|
|
3008
3222
|
}
|
|
3009
3223
|
}
|
|
@@ -3013,12 +3227,65 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
3013
3227
|
return;
|
|
3014
3228
|
}
|
|
3015
3229
|
|
|
3230
|
+
// POST /api/orgs/:name/import — import an org config by name (orgs.html upload flow)
|
|
3231
|
+
if (req.method === 'POST' && /^\/api\/orgs\/[a-z0-9][a-z0-9_-]{0,63}\/import$/i.test(url)) {
|
|
3232
|
+
let body = '';
|
|
3233
|
+
req.on('data', c => { body += c; if (body.length > 2e6) req.destroy(); });
|
|
3234
|
+
req.on('end', () => {
|
|
3235
|
+
try {
|
|
3236
|
+
const urlParts = url.split('/');
|
|
3237
|
+
const orgName = decodeURIComponent(urlParts[3]);
|
|
3238
|
+
if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: 'Invalid org name' })); return; }
|
|
3239
|
+
const cfg = JSON.parse(body);
|
|
3240
|
+
const _importQs = new URL(req.url, 'http://localhost').searchParams;
|
|
3241
|
+
const dir = path.resolve(_importQs.get('dir') || projectDir || process.cwd());
|
|
3242
|
+
const orgsDir = path.join(dir, '.monomind', 'orgs');
|
|
3243
|
+
fs.mkdirSync(orgsDir, { recursive: true });
|
|
3244
|
+
const destFile = path.join(orgsDir, `${orgName}.json`);
|
|
3245
|
+
fs.writeFileSync(destFile, JSON.stringify({ ...cfg, name: orgName }, null, 2), 'utf8');
|
|
3246
|
+
res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
|
|
3247
|
+
res.end(JSON.stringify({ ok: true, name: orgName, file: destFile }));
|
|
3248
|
+
} catch (e) {
|
|
3249
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
3250
|
+
res.end(JSON.stringify({ error: e.message }));
|
|
3251
|
+
}
|
|
3252
|
+
});
|
|
3253
|
+
return;
|
|
3254
|
+
}
|
|
3255
|
+
|
|
3256
|
+
// POST /api/orgs — import / create org from JSON body
|
|
3257
|
+
if (req.method === 'POST' && url === '/api/orgs') {
|
|
3258
|
+
let body = '';
|
|
3259
|
+
req.on('data', c => { body += c; if (body.length > 2e6) req.destroy(); });
|
|
3260
|
+
req.on('end', () => {
|
|
3261
|
+
try {
|
|
3262
|
+
const cfg = JSON.parse(body);
|
|
3263
|
+
const qs = new URL(req.url, 'http://localhost').searchParams;
|
|
3264
|
+
const dir = qs.get('dir') || cfg.dir || projectDir || process.cwd();
|
|
3265
|
+
const name = (cfg.name || '').toLowerCase().replace(/[^a-z0-9_-]/g, '-').replace(/^-+|-+$/g, '').slice(0, 64);
|
|
3266
|
+
if (!name) { res.writeHead(400, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: 'Invalid org name' })); return; }
|
|
3267
|
+
const orgsDir = path.join(path.resolve(dir), '.monomind', 'orgs');
|
|
3268
|
+
fs.mkdirSync(orgsDir, { recursive: true });
|
|
3269
|
+
const destFile = path.join(orgsDir, `${name}.json`);
|
|
3270
|
+
const cleanCfg = Object.fromEntries(Object.entries({ ...cfg, name }).filter(([k]) => !k.startsWith('_')));
|
|
3271
|
+
fs.writeFileSync(destFile, JSON.stringify(cleanCfg, null, 2), 'utf8');
|
|
3272
|
+
res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
|
|
3273
|
+
res.end(JSON.stringify({ ok: true, name, file: destFile }));
|
|
3274
|
+
} catch (e) {
|
|
3275
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
3276
|
+
res.end(JSON.stringify({ error: e.message }));
|
|
3277
|
+
}
|
|
3278
|
+
});
|
|
3279
|
+
return;
|
|
3280
|
+
}
|
|
3281
|
+
|
|
3016
3282
|
// GET /api/orgs/:name — get specific org config (exact path: /api/orgs/<slug>)
|
|
3017
3283
|
if (req.method === 'GET' && /^\/api\/orgs\/[a-z0-9][a-z0-9_-]{0,63}$/i.test(url)) {
|
|
3018
3284
|
try {
|
|
3019
3285
|
const orgName = decodeURIComponent(url.slice('/api/orgs/'.length));
|
|
3020
3286
|
if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('Invalid org name'); return; }
|
|
3021
|
-
const
|
|
3287
|
+
const _orgsOneQs = new URL(req.url, 'http://localhost').searchParams;
|
|
3288
|
+
const f = path.join(path.resolve(_orgsOneQs.get('dir') || projectDir || process.cwd()), '.monomind', 'orgs', `${orgName}.json`);
|
|
3022
3289
|
if (!fs.existsSync(f)) { res.writeHead(404); res.end('{"error":"not found"}'); return; }
|
|
3023
3290
|
res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
|
|
3024
3291
|
res.end(fs.readFileSync(f, 'utf8'));
|
|
@@ -3050,8 +3317,17 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
3050
3317
|
const stopFile = path.join(orgsDir, '.stops', `${orgName}.stop`);
|
|
3051
3318
|
const running = !fs.existsSync(stopFile) && Object.values(state.agents || {}).some(a => a.status === 'running');
|
|
3052
3319
|
|
|
3320
|
+
// Read real tasks from the task store and group by status column
|
|
3321
|
+
const taskStoreData = readJsonSafe(path.join(d, '.monomind', 'tasks', 'store.json'));
|
|
3322
|
+
const allTasks = taskStoreData ? Object.values(taskStoreData.tasks || {}) : [];
|
|
3323
|
+
const tasks = {
|
|
3324
|
+
todo: allTasks.filter(t => t.status === 'pending').map(t => ({ id: t.taskId, description: t.description, status: 'todo', ts: t.createdAt })),
|
|
3325
|
+
doing: allTasks.filter(t => t.status === 'in_progress').map(t => ({ id: t.taskId, description: t.description, status: 'doing', ts: t.startedAt || t.createdAt })),
|
|
3326
|
+
done: allTasks.filter(t => t.status === 'completed' || t.status === 'failed' || t.status === 'cancelled').map(t => ({ id: t.taskId, description: t.description, status: t.status, ts: t.completedAt || t.createdAt })),
|
|
3327
|
+
};
|
|
3328
|
+
|
|
3053
3329
|
const result = { config, state, goals: goalsData.goals, routines: routinesData.routines,
|
|
3054
|
-
approvals: approvalsData.approvals, running, tasks
|
|
3330
|
+
approvals: approvalsData.approvals, running, tasks };
|
|
3055
3331
|
|
|
3056
3332
|
res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
|
|
3057
3333
|
res.end(JSON.stringify(result));
|
|
@@ -3115,7 +3391,8 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
3115
3391
|
const parts = url.split('/');
|
|
3116
3392
|
const orgName = decodeURIComponent(parts[3]);
|
|
3117
3393
|
if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('[]'); return; }
|
|
3118
|
-
const
|
|
3394
|
+
const _projsQs = new URL(req.url, 'http://localhost').searchParams;
|
|
3395
|
+
const d = path.resolve(_projsQs.get('dir') || projectDir || process.cwd());
|
|
3119
3396
|
const projFile = path.join(d, '.monomind', 'orgs', `${orgName}-projects.json`);
|
|
3120
3397
|
if (!fs.existsSync(projFile)) { res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' }); res.end('[]'); return; }
|
|
3121
3398
|
const data = JSON.parse(fs.readFileSync(projFile, 'utf8'));
|
|
@@ -3131,7 +3408,8 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
3131
3408
|
const parts = url.split('/');
|
|
3132
3409
|
const orgName = decodeURIComponent(parts[3]);
|
|
3133
3410
|
if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('{}'); return; }
|
|
3134
|
-
const
|
|
3411
|
+
const _membersQs = new URL(req.url, 'http://localhost').searchParams;
|
|
3412
|
+
const d = path.resolve(_membersQs.get('dir') || projectDir || process.cwd());
|
|
3135
3413
|
const membersFile = path.join(d, '.monomind', 'orgs', `${orgName}-members.json`);
|
|
3136
3414
|
if (!fs.existsSync(membersFile)) {
|
|
3137
3415
|
res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
|
|
@@ -3151,7 +3429,8 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
3151
3429
|
const parts = url.split('/');
|
|
3152
3430
|
const orgName = decodeURIComponent(parts[3]);
|
|
3153
3431
|
if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('{}'); return; }
|
|
3154
|
-
const
|
|
3432
|
+
const _adaptersQs = new URL(req.url, 'http://localhost').searchParams;
|
|
3433
|
+
const d = path.resolve(_adaptersQs.get('dir') || projectDir || process.cwd());
|
|
3155
3434
|
const adaptersFile = path.join(d, '.monomind', 'orgs', `${orgName}-adapters.json`);
|
|
3156
3435
|
if (!fs.existsSync(adaptersFile)) {
|
|
3157
3436
|
// Return defaults derived from org config if available
|
|
@@ -3179,7 +3458,8 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
3179
3458
|
const parts = url.split('/');
|
|
3180
3459
|
const orgName = decodeURIComponent(parts[3]);
|
|
3181
3460
|
if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('{}'); return; }
|
|
3182
|
-
const
|
|
3461
|
+
const _skillsQs = new URL(req.url, 'http://localhost').searchParams;
|
|
3462
|
+
const d = path.resolve(_skillsQs.get('dir') || projectDir || process.cwd());
|
|
3183
3463
|
const skillsDir = path.join(d, '.claude', 'skills');
|
|
3184
3464
|
const orgFile = path.join(d, '.monomind', 'orgs', `${orgName}.json`);
|
|
3185
3465
|
|
|
@@ -3286,13 +3566,13 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
3286
3566
|
// GET /api/org/:name/search?q=<query> — fuzzy search across org data
|
|
3287
3567
|
if (req.method === 'GET' && /^\/api\/org\/[a-z0-9][a-z0-9_-]{0,63}\/search(\?.*)?$/i.test(url)) {
|
|
3288
3568
|
try {
|
|
3289
|
-
const urlObj = new URL(`http://x${url}`);
|
|
3569
|
+
const urlObj = new URL(`http://x${req.url}`);
|
|
3290
3570
|
const orgName = decodeURIComponent(urlObj.pathname.split('/')[3]);
|
|
3291
3571
|
if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('{}'); return; }
|
|
3292
3572
|
const q = (urlObj.searchParams.get('q') || '').toLowerCase().trim();
|
|
3293
3573
|
if (!q || q.length < 2) { res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' }); res.end('{"hits":[]}'); return; }
|
|
3294
3574
|
|
|
3295
|
-
const d = projectDir || process.cwd();
|
|
3575
|
+
const d = path.resolve(urlObj.searchParams.get('dir') || projectDir || process.cwd());
|
|
3296
3576
|
const orgsDir = path.join(d, '.monomind', 'orgs');
|
|
3297
3577
|
const readJ = (f) => { try { return JSON.parse(fs.readFileSync(f, 'utf8')); } catch(_) { return null; } };
|
|
3298
3578
|
|
|
@@ -3310,8 +3590,8 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
3310
3590
|
// Goals
|
|
3311
3591
|
const goals = readJ(path.join(orgsDir, `${orgName}-goals.json`));
|
|
3312
3592
|
for (const g of (goals?.goals || [])) {
|
|
3313
|
-
if (match(g.title) || match(g.description)) {
|
|
3314
|
-
hits.push({ type: 'goal', id: g.id, title: g.title, meta: g.status || 'open' });
|
|
3593
|
+
if (match(g.title) || match(g.text) || match(g.goal) || match(g.description)) {
|
|
3594
|
+
hits.push({ type: 'goal', id: g.id, title: g.title || g.text || g.goal, meta: g.status || 'open' });
|
|
3315
3595
|
}
|
|
3316
3596
|
}
|
|
3317
3597
|
|
|
@@ -3339,6 +3619,14 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
3339
3619
|
}
|
|
3340
3620
|
}
|
|
3341
3621
|
|
|
3622
|
+
// Issues
|
|
3623
|
+
const issuesData = readJ(path.join(orgsDir, `${orgName}-issues.json`));
|
|
3624
|
+
for (const i of (issuesData?.issues || [])) {
|
|
3625
|
+
if (match(i.title) || match(i.description) || match(i.slug)) {
|
|
3626
|
+
hits.push({ type: 'issue', id: i.id || i.slug, title: i.title || i.slug, meta: i.status || 'open' });
|
|
3627
|
+
}
|
|
3628
|
+
}
|
|
3629
|
+
|
|
3342
3630
|
// Recent activity events
|
|
3343
3631
|
const eventsFile = path.join(d, 'data', 'mastermind-events.jsonl');
|
|
3344
3632
|
if (fs.existsSync(eventsFile)) {
|
|
@@ -3365,13 +3653,17 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
3365
3653
|
try {
|
|
3366
3654
|
const orgName = decodeURIComponent(url.split('/')[3]);
|
|
3367
3655
|
if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('Invalid org name'); return; }
|
|
3368
|
-
const
|
|
3656
|
+
const _issuesQs = new URL(req.url, 'http://localhost').searchParams;
|
|
3657
|
+
const _issuesDir = path.resolve(_issuesQs.get('dir') || projectDir || process.cwd());
|
|
3658
|
+
const issuesPath = path.join(_issuesDir, '.monomind', 'orgs', `${orgName}-issues.json`);
|
|
3369
3659
|
let payload = { issues: [] };
|
|
3370
3660
|
try {
|
|
3371
3661
|
const raw = JSON.parse(fs.readFileSync(issuesPath, 'utf8'));
|
|
3372
3662
|
payload.issues = (raw.issues || []).map(i => ({
|
|
3373
|
-
id: i.id, slug: i.slug, title: i.title,
|
|
3663
|
+
id: i.id, slug: i.slug, title: i.title, description: i.description || null,
|
|
3664
|
+
status: i.status || 'open',
|
|
3374
3665
|
priority: i.priority || 'medium', assignee_id: i.assignee_id || null,
|
|
3666
|
+
assignee: i.assignee || i.assignee_id || null,
|
|
3375
3667
|
project_id: i.project_id || null, parent_id: i.parent_id || null,
|
|
3376
3668
|
created_at: i.created_at, updated_at: i.updated_at
|
|
3377
3669
|
}));
|
|
@@ -3422,11 +3714,12 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
3422
3714
|
try {
|
|
3423
3715
|
const actPath = path.join(base, `${orgName}-activity.jsonl`);
|
|
3424
3716
|
const lines = fs.readFileSync(actPath, 'utf8').split('\n').filter(Boolean);
|
|
3425
|
-
const
|
|
3717
|
+
const cutoffMs = Date.now() - 7 * 24 * 60 * 60 * 1000;
|
|
3426
3718
|
lines.forEach(line => {
|
|
3427
3719
|
try {
|
|
3428
3720
|
const ev = JSON.parse(line);
|
|
3429
|
-
|
|
3721
|
+
const evMs = typeof ev.ts === 'number' ? ev.ts : (ev.ts ? Date.parse(ev.ts) : 0);
|
|
3722
|
+
if (!evMs || evMs < cutoffMs) return;
|
|
3430
3723
|
totalRuns++;
|
|
3431
3724
|
if (ev.type && ev.type.includes('complete')) successRuns++;
|
|
3432
3725
|
} catch(_) {}
|
|
@@ -3460,7 +3753,8 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
3460
3753
|
try {
|
|
3461
3754
|
const orgName = decodeURIComponent(url.split('/')[3]);
|
|
3462
3755
|
if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('Invalid org name'); return; }
|
|
3463
|
-
const
|
|
3756
|
+
const _envsQs = new URL(req.url, 'http://localhost').searchParams;
|
|
3757
|
+
const envsPath = path.join(path.resolve(_envsQs.get('dir') || projectDir || process.cwd()), '.monomind', 'orgs', `${orgName}-environments.json`);
|
|
3464
3758
|
let payload = { environments: [], default_env: null };
|
|
3465
3759
|
try {
|
|
3466
3760
|
const raw = JSON.parse(fs.readFileSync(envsPath, 'utf8'));
|
|
@@ -3486,7 +3780,8 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
3486
3780
|
try {
|
|
3487
3781
|
const orgName = decodeURIComponent(url.split('/')[3]);
|
|
3488
3782
|
if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('Invalid org name'); return; }
|
|
3489
|
-
const
|
|
3783
|
+
const _wsQs = new URL(req.url, 'http://localhost').searchParams;
|
|
3784
|
+
const base = path.join(path.resolve(_wsQs.get('dir') || projectDir || process.cwd()), '.monomind', 'orgs');
|
|
3490
3785
|
let payload = { workspaces: [] };
|
|
3491
3786
|
try {
|
|
3492
3787
|
const wsRaw = JSON.parse(fs.readFileSync(path.join(base, `${orgName}-workspaces.json`), 'utf8'));
|
|
@@ -3509,17 +3804,18 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
3509
3804
|
}
|
|
3510
3805
|
|
|
3511
3806
|
// GET /api/org/:name/invites — active invites + pending join requests
|
|
3512
|
-
if (req.method === 'GET' && url.match(/^\/api\/org\/[a-z0-9][a-z0-9_-]{0,63}\/invites
|
|
3807
|
+
if (req.method === 'GET' && url.match(/^\/api\/org\/[a-z0-9][a-z0-9_-]{0,63}\/invites(\?.*)?$/i)) {
|
|
3513
3808
|
try {
|
|
3514
|
-
const orgName = decodeURIComponent(url.split('/')[3]);
|
|
3809
|
+
const orgName = decodeURIComponent(url.split('/')[3].split('?')[0]);
|
|
3515
3810
|
if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('Invalid org name'); return; }
|
|
3516
|
-
const
|
|
3811
|
+
const _invitesQs = new URL(req.url, 'http://localhost').searchParams;
|
|
3812
|
+
const base = path.join(path.resolve(_invitesQs.get('dir') || projectDir || process.cwd()), '.monomind', 'orgs');
|
|
3517
3813
|
let payload = { invites: [], join_requests: [] };
|
|
3518
3814
|
try {
|
|
3519
3815
|
const raw = JSON.parse(fs.readFileSync(path.join(base, `${orgName}-members.json`), 'utf8'));
|
|
3520
3816
|
const all = raw.join_requests || [];
|
|
3521
3817
|
payload.invites = all.filter(r => r.type === 'invite' && r.status === 'pending')
|
|
3522
|
-
.map(r => ({ id: r.id, token: r.token ? r.token.slice(0, 8) + '…' : r.id, role: r.role || 'operator', createdAt: r.createdAt || null, status: r.status }));
|
|
3818
|
+
.map(r => ({ id: r.id, token: r.token ? r.token.slice(0, 8) + '…' : r.id, role: r.role || 'operator', createdAt: r.createdAt || null, expiresAt: r.expiresAt || null, status: r.status }));
|
|
3523
3819
|
payload.join_requests = all.filter(r => r.type !== 'invite' && r.status === 'pending_approval')
|
|
3524
3820
|
.map(r => ({ id: r.id, requestType: r.requestType || 'human', role: r.role || 'viewer', createdAt: r.createdAt || null, message: r.message || '' }));
|
|
3525
3821
|
} catch(_) { /* members file missing */ }
|
|
@@ -3534,7 +3830,8 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
3534
3830
|
try {
|
|
3535
3831
|
const orgName = decodeURIComponent(url.split('/')[3]);
|
|
3536
3832
|
if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('Invalid org name'); return; }
|
|
3537
|
-
const
|
|
3833
|
+
const _pluginsQs = new URL(req.url, 'http://localhost').searchParams;
|
|
3834
|
+
const base = path.join(path.resolve(_pluginsQs.get('dir') || projectDir || process.cwd()), '.monomind');
|
|
3538
3835
|
let plugins = [];
|
|
3539
3836
|
try {
|
|
3540
3837
|
const reg = JSON.parse(fs.readFileSync(path.join(base, 'plugins', 'registry.json'), 'utf8'));
|
|
@@ -3572,7 +3869,8 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
3572
3869
|
try {
|
|
3573
3870
|
const orgName = decodeURIComponent(url.split('/')[3]);
|
|
3574
3871
|
if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('Invalid org name'); return; }
|
|
3575
|
-
const
|
|
3872
|
+
const _myIssuesQs = new URL(req.url, 'http://localhost').searchParams;
|
|
3873
|
+
const base = path.join(path.resolve(_myIssuesQs.get('dir') || projectDir || process.cwd()), '.monomind', 'orgs');
|
|
3576
3874
|
let payload = { issues: [] };
|
|
3577
3875
|
try {
|
|
3578
3876
|
const raw = JSON.parse(fs.readFileSync(path.join(base, `${orgName}-issues.json`), 'utf8'));
|
|
@@ -3582,12 +3880,15 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
3582
3880
|
.map(i => ({
|
|
3583
3881
|
id: i.id,
|
|
3584
3882
|
title: i.title || null,
|
|
3883
|
+
description: i.description || null,
|
|
3585
3884
|
status: i.status || 'open',
|
|
3586
3885
|
priority: i.priority || 'medium',
|
|
3587
3886
|
assigneeId: i.assigneeId || i.assigned_to || null,
|
|
3588
3887
|
projectId: i.projectId || i.project_id || null,
|
|
3589
3888
|
createdAt: i.createdAt || null,
|
|
3590
3889
|
lastActivityAt: i.lastActivityAt || null,
|
|
3890
|
+
updated_at: i.updated_at || i.lastActivityAt || i.updatedAt || i.ts || null,
|
|
3891
|
+
ts: i.ts || i.updated_at || i.lastActivityAt || null,
|
|
3591
3892
|
}));
|
|
3592
3893
|
} catch(_) { /* issues file missing */ }
|
|
3593
3894
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
@@ -3601,7 +3902,9 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
3601
3902
|
try {
|
|
3602
3903
|
const orgName = decodeURIComponent(url.split('/')[3]);
|
|
3603
3904
|
if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('Invalid org name'); return; }
|
|
3604
|
-
const
|
|
3905
|
+
const _agentsQs = new URL(req.url, 'http://localhost').searchParams;
|
|
3906
|
+
const d = path.resolve(_agentsQs.get('dir') || projectDir || process.cwd());
|
|
3907
|
+
const base = path.join(d, '.monomind', 'orgs');
|
|
3605
3908
|
const readJsonSafe = (f) => { try { return JSON.parse(fs.readFileSync(f, 'utf8')); } catch(_) { return null; } };
|
|
3606
3909
|
const config = readJsonSafe(path.join(base, `${orgName}.json`)) || {};
|
|
3607
3910
|
const stateData = readJsonSafe(path.join(base, `${orgName}-state.json`)) || {};
|
|
@@ -3614,8 +3917,8 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
3614
3917
|
return {
|
|
3615
3918
|
id: r.id,
|
|
3616
3919
|
title: r.title || r.id,
|
|
3617
|
-
adapterType:
|
|
3618
|
-
adapterModel: (r.adapter && r.adapter.model) || null,
|
|
3920
|
+
adapterType: r.agent_type || r.type || null,
|
|
3921
|
+
adapterModel: (r.adapter_config && r.adapter_config.model) || (r.adapter && r.adapter.model) || null,
|
|
3619
3922
|
governance: r.governance || null,
|
|
3620
3923
|
reportsTo: r.reports_to || null,
|
|
3621
3924
|
status: s.status || 'idle',
|
|
@@ -3636,7 +3939,8 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
3636
3939
|
try {
|
|
3637
3940
|
const orgName = decodeURIComponent(url.split('/')[3].split('?')[0]);
|
|
3638
3941
|
if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('Invalid org name'); return; }
|
|
3639
|
-
const
|
|
3942
|
+
const _approvalsQs = new URL(req.url, 'http://localhost').searchParams;
|
|
3943
|
+
const base = path.join(path.resolve(_approvalsQs.get('dir') || projectDir || process.cwd()), '.monomind', 'orgs');
|
|
3640
3944
|
const readJsonSafe = (f) => { try { return JSON.parse(fs.readFileSync(f, 'utf8')); } catch(_) { return null; } };
|
|
3641
3945
|
const data = readJsonSafe(path.join(base, `${orgName}-approvals.json`)) || { approvals: [] };
|
|
3642
3946
|
const approvals = (data.approvals || [])
|
|
@@ -3671,7 +3975,7 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
3671
3975
|
// Body: { action: "approve" | "reject" | "revision_requested" }
|
|
3672
3976
|
if (req.method === 'POST' && url.match(/^\/api\/org\/[a-z0-9][a-z0-9_-]{0,63}\/approvals\/[^/]+$/i)) {
|
|
3673
3977
|
let body = '';
|
|
3674
|
-
for await (const chunk of req) body += chunk;
|
|
3978
|
+
for await (const chunk of req) { body += chunk; if (body.length > 2097152) { req.destroy(); break; } }
|
|
3675
3979
|
try {
|
|
3676
3980
|
const parts = url.split('/');
|
|
3677
3981
|
const orgName = decodeURIComponent(parts[3]);
|
|
@@ -3683,7 +3987,8 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
3683
3987
|
if (!['approve', 'reject', 'revision_requested'].includes(action)) {
|
|
3684
3988
|
res.writeHead(400); res.end('{"error":"action must be approve, reject, or revision_requested"}'); return;
|
|
3685
3989
|
}
|
|
3686
|
-
const
|
|
3990
|
+
const _postApprovalsQs = new URL(req.url, 'http://localhost').searchParams;
|
|
3991
|
+
const base = path.join(path.resolve(_postApprovalsQs.get('dir') || projectDir || process.cwd()), '.monomind', 'orgs');
|
|
3687
3992
|
const approvalsFile = path.join(base, `${orgName}-approvals.json`);
|
|
3688
3993
|
let data = { approvals: [] };
|
|
3689
3994
|
try { data = JSON.parse(fs.readFileSync(approvalsFile, 'utf8')); } catch(_) {}
|
|
@@ -3701,7 +4006,7 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
3701
4006
|
fs.renameSync(tmp, approvalsFile);
|
|
3702
4007
|
// Emit org:approval:resolved event so boss agent unblocks
|
|
3703
4008
|
const event = { type: 'org:approval:resolved', org: orgName, approval_id: approvalId, status, ts: Date.now() };
|
|
3704
|
-
try { fs.appendFileSync(path.join(projectDir || process.cwd(), 'data', 'mastermind-events.jsonl'), JSON.stringify(event) + '\n'); } catch(_) {}
|
|
4009
|
+
try { fs.appendFileSync(path.join(path.resolve(_postApprovalsQs.get('dir') || projectDir || process.cwd()), 'data', 'mastermind-events.jsonl'), JSON.stringify(event) + '\n'); } catch(_) {}
|
|
3705
4010
|
const msg = `data: ${JSON.stringify(event)}\n\n`;
|
|
3706
4011
|
for (const c of mmSseClients) { try { c.write(msg); } catch(_) { mmSseClients.delete(c); } }
|
|
3707
4012
|
res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
|
|
@@ -3715,7 +4020,8 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
3715
4020
|
try {
|
|
3716
4021
|
const orgName = decodeURIComponent(url.split('/')[3]);
|
|
3717
4022
|
if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('Invalid org name'); return; }
|
|
3718
|
-
const
|
|
4023
|
+
const _secretsQs = new URL(req.url, 'http://localhost').searchParams;
|
|
4024
|
+
const base = path.join(path.resolve(_secretsQs.get('dir') || projectDir || process.cwd()), '.monomind', 'orgs');
|
|
3719
4025
|
const secretsDir = path.join(base, '.secrets');
|
|
3720
4026
|
const readJsonSafe = (f) => { try { return JSON.parse(fs.readFileSync(f, 'utf8')); } catch(_) { return null; } };
|
|
3721
4027
|
// Read secrets index — NEVER expose actual values
|
|
@@ -3723,6 +4029,7 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
3723
4029
|
const data = readJsonSafe(indexFile) || { secrets: [] };
|
|
3724
4030
|
const secrets = (data.secrets || []).map(s => ({
|
|
3725
4031
|
name: s.name,
|
|
4032
|
+
purpose: s.purpose || null,
|
|
3726
4033
|
maskedRef: s.maskedRef || `${(s.name||'').substring(0,4)}***`,
|
|
3727
4034
|
status: s.status || 'active',
|
|
3728
4035
|
createdAt: s.createdAt || null,
|
|
@@ -3742,7 +4049,8 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
3742
4049
|
try {
|
|
3743
4050
|
const orgName = decodeURIComponent(url.split('/')[3]);
|
|
3744
4051
|
if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('Invalid org name'); return; }
|
|
3745
|
-
const
|
|
4052
|
+
const _budgetsQs = new URL(req.url, 'http://localhost').searchParams;
|
|
4053
|
+
const base = path.join(path.resolve(_budgetsQs.get('dir') || projectDir || process.cwd()), '.monomind', 'orgs');
|
|
3746
4054
|
let budgetData = { org_budget: {}, agent_budgets: {}, period: 'monthly', currency: 'USD' };
|
|
3747
4055
|
try { budgetData = JSON.parse(fs.readFileSync(path.join(base, `${orgName}-budgets.json`), 'utf8')); } catch(_) {}
|
|
3748
4056
|
// Enrich with per-agent spend from state file.
|
|
@@ -3785,12 +4093,17 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
3785
4093
|
try {
|
|
3786
4094
|
const orgName = decodeURIComponent(url.split('/')[3]);
|
|
3787
4095
|
if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('Invalid org name'); return; }
|
|
3788
|
-
const
|
|
4096
|
+
const _threadsQs = new URL(req.url, 'http://localhost').searchParams;
|
|
4097
|
+
const threadsFile = path.join(path.resolve(_threadsQs.get('dir') || projectDir || process.cwd()), '.monomind', 'orgs', `${orgName}-threads.jsonl`);
|
|
3789
4098
|
let threads = [];
|
|
3790
4099
|
try {
|
|
3791
4100
|
const lines = fs.readFileSync(threadsFile, 'utf8').split('\n').filter(l => l.trim());
|
|
3792
4101
|
threads = lines.map(l => { try { return JSON.parse(l); } catch(_) { return null; } }).filter(Boolean);
|
|
3793
|
-
threads = threads.filter(t => t.type === 'thread' || !t.type)
|
|
4102
|
+
threads = threads.filter(t => t.type === 'thread' || !t.type).map(t => ({
|
|
4103
|
+
...t,
|
|
4104
|
+
author: t.author || t.authorName || t.createdBy || t.authorId || null,
|
|
4105
|
+
messageCount: t.messageCount != null ? t.messageCount : (Array.isArray(t.messages) ? t.messages.length : (typeof t.messages === 'number' ? t.messages : null)),
|
|
4106
|
+
}));
|
|
3794
4107
|
} catch(_) {}
|
|
3795
4108
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
3796
4109
|
res.end(JSON.stringify({ threads }));
|
|
@@ -3804,7 +4117,8 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
3804
4117
|
try {
|
|
3805
4118
|
const orgName = decodeURIComponent(url.split('/')[3].split('?')[0]);
|
|
3806
4119
|
if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('Invalid org name'); return; }
|
|
3807
|
-
const
|
|
4120
|
+
const _joinQs = new URL(req.url, 'http://localhost').searchParams;
|
|
4121
|
+
const joinFile = path.join(path.resolve(_joinQs.get('dir') || projectDir || process.cwd()), '.monomind', 'orgs', `${orgName}-join-requests.json`);
|
|
3808
4122
|
let requests = [];
|
|
3809
4123
|
try {
|
|
3810
4124
|
const raw = fs.readFileSync(joinFile, 'utf8');
|
|
@@ -3831,7 +4145,8 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
3831
4145
|
try {
|
|
3832
4146
|
const orgName = decodeURIComponent(url.split('/')[3]);
|
|
3833
4147
|
if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('Invalid org name'); return; }
|
|
3834
|
-
const
|
|
4148
|
+
const _goalsQs = new URL(req.url, 'http://localhost').searchParams;
|
|
4149
|
+
const goalsFile = path.join(path.resolve(_goalsQs.get('dir') || projectDir || process.cwd()), '.monomind', 'orgs', `${orgName}-goals.json`);
|
|
3835
4150
|
let data = { goals: [] };
|
|
3836
4151
|
try { data = JSON.parse(fs.readFileSync(goalsFile, 'utf8')); } catch(_) {}
|
|
3837
4152
|
res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
|
|
@@ -3845,7 +4160,8 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
3845
4160
|
try {
|
|
3846
4161
|
const orgName = decodeURIComponent(url.split('/')[3]);
|
|
3847
4162
|
if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('Invalid org name'); return; }
|
|
3848
|
-
const
|
|
4163
|
+
const _routinesQs = new URL(req.url, 'http://localhost').searchParams;
|
|
4164
|
+
const routinesFile = path.join(path.resolve(_routinesQs.get('dir') || projectDir || process.cwd()), '.monomind', 'orgs', `${orgName}-routines.json`);
|
|
3849
4165
|
let data = { routines: [] };
|
|
3850
4166
|
try { data = JSON.parse(fs.readFileSync(routinesFile, 'utf8')); } catch(_) {}
|
|
3851
4167
|
res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
|
|
@@ -3858,13 +4174,14 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
3858
4174
|
// Body: { goals: [{id, title, description, status, priority, assignee_id, created_at}] }
|
|
3859
4175
|
if (req.method === 'POST' && url.match(/^\/api\/org\/[a-z0-9][a-z0-9_-]{0,63}\/goals$/i)) {
|
|
3860
4176
|
let body = '';
|
|
3861
|
-
for await (const chunk of req) body += chunk;
|
|
4177
|
+
for await (const chunk of req) { body += chunk; if (body.length > 2097152) { req.destroy(); break; } }
|
|
3862
4178
|
try {
|
|
3863
4179
|
const orgName = decodeURIComponent(url.split('/')[3]);
|
|
3864
4180
|
if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('Invalid org name'); return; }
|
|
3865
4181
|
const parsed = JSON.parse(body);
|
|
3866
4182
|
if (!parsed || !Array.isArray(parsed.goals)) { res.writeHead(400); res.end('{"error":"goals array required"}'); return; }
|
|
3867
|
-
const
|
|
4183
|
+
const _postGoalsQs = new URL(req.url, 'http://localhost').searchParams;
|
|
4184
|
+
const goalsFile = path.join(path.resolve(_postGoalsQs.get('dir') || projectDir || process.cwd()), '.monomind', 'orgs', `${orgName}-goals.json`);
|
|
3868
4185
|
const tmp = `${goalsFile}.tmp`;
|
|
3869
4186
|
const payload = { org: orgName, updated_at: new Date().toISOString(), goals: parsed.goals };
|
|
3870
4187
|
fs.writeFileSync(tmp, JSON.stringify(payload, null, 2), 'utf-8');
|
|
@@ -3879,13 +4196,14 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
3879
4196
|
// Body: { routines: [{name, description, schedule, enabled, last_run, next_run}] }
|
|
3880
4197
|
if (req.method === 'POST' && url.match(/^\/api\/org\/[a-z0-9][a-z0-9_-]{0,63}\/routines$/i)) {
|
|
3881
4198
|
let body = '';
|
|
3882
|
-
for await (const chunk of req) body += chunk;
|
|
4199
|
+
for await (const chunk of req) { body += chunk; if (body.length > 2097152) { req.destroy(); break; } }
|
|
3883
4200
|
try {
|
|
3884
4201
|
const orgName = decodeURIComponent(url.split('/')[3]);
|
|
3885
4202
|
if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('Invalid org name'); return; }
|
|
3886
4203
|
const parsed = JSON.parse(body);
|
|
3887
4204
|
if (!parsed || !Array.isArray(parsed.routines)) { res.writeHead(400); res.end('{"error":"routines array required"}'); return; }
|
|
3888
|
-
const
|
|
4205
|
+
const _postRoutinesQs = new URL(req.url, 'http://localhost').searchParams;
|
|
4206
|
+
const routinesFile = path.join(path.resolve(_postRoutinesQs.get('dir') || projectDir || process.cwd()), '.monomind', 'orgs', `${orgName}-routines.json`);
|
|
3889
4207
|
const tmp = `${routinesFile}.tmp`;
|
|
3890
4208
|
const payload = { org: orgName, updated_at: new Date().toISOString(), routines: parsed.routines };
|
|
3891
4209
|
fs.writeFileSync(tmp, JSON.stringify(payload, null, 2), 'utf-8');
|
|
@@ -3896,16 +4214,95 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
3896
4214
|
return;
|
|
3897
4215
|
}
|
|
3898
4216
|
|
|
3899
|
-
//
|
|
3900
|
-
if (req.method === '
|
|
4217
|
+
// GET /api/org/:name/files — all files related to an org
|
|
4218
|
+
if (req.method === 'GET' && url.match(/^\/api\/org\/[a-z0-9][a-z0-9_-]{0,63}\/files$/i)) {
|
|
3901
4219
|
try {
|
|
3902
4220
|
const orgName = decodeURIComponent(url.split('/')[3]);
|
|
4221
|
+
if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('{"error":"Invalid org name"}'); return; }
|
|
4222
|
+
const _filesQs = new URL(req.url, 'http://localhost').searchParams;
|
|
4223
|
+
const d = path.resolve(_filesQs.get('dir') || projectDir || process.cwd());
|
|
4224
|
+
const orgsDir = path.join(d, '.monomind', 'orgs');
|
|
4225
|
+
const files = [];
|
|
4226
|
+
const seen = new Set();
|
|
4227
|
+
const addFile = (fp, type) => {
|
|
4228
|
+
if (seen.has(fp)) return; seen.add(fp);
|
|
4229
|
+
try { const st = fs.statSync(fp); files.push({ name: path.basename(fp), path: fp, type, size: st.size, mtime: st.mtime.toISOString() }); } catch (_) {}
|
|
4230
|
+
};
|
|
4231
|
+
addFile(path.join(orgsDir, orgName + '.json'), 'config');
|
|
4232
|
+
for (const s of ['-state','-approvals','-goals','-routines','-projects','-members','-issues','-threads','-budgets']) {
|
|
4233
|
+
const fp = path.join(orgsDir, orgName + s + '.json');
|
|
4234
|
+
if (fs.existsSync(fp)) addFile(fp, s.slice(1));
|
|
4235
|
+
}
|
|
4236
|
+
const walkDir = (dir, depth) => {
|
|
4237
|
+
if (depth > 3) return;
|
|
4238
|
+
let entries; try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch (_) { return; }
|
|
4239
|
+
for (const e of entries) {
|
|
4240
|
+
if (e.name.startsWith('.')) continue;
|
|
4241
|
+
const fp = path.join(dir, e.name);
|
|
4242
|
+
if (e.isDirectory()) walkDir(fp, depth + 1);
|
|
4243
|
+
else addFile(fp, 'generated');
|
|
4244
|
+
}
|
|
4245
|
+
};
|
|
4246
|
+
const orgWorkDir = path.join(orgsDir, orgName);
|
|
4247
|
+
if (fs.existsSync(orgWorkDir)) walkDir(orgWorkDir, 0);
|
|
4248
|
+
let orgCfg = null;
|
|
4249
|
+
try { orgCfg = JSON.parse(fs.readFileSync(path.join(orgsDir, orgName + '.json'), 'utf8')); } catch (_) {}
|
|
4250
|
+
if (orgCfg && Array.isArray(orgCfg.roles)) {
|
|
4251
|
+
const agentsDir = path.join(d, '.claude', 'agents');
|
|
4252
|
+
const walkAgents = (dir) => {
|
|
4253
|
+
let entries; try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch (_) { return; }
|
|
4254
|
+
for (const e of entries) {
|
|
4255
|
+
if (e.isDirectory()) { walkAgents(path.join(dir, e.name)); continue; }
|
|
4256
|
+
if (!e.name.endsWith('.md')) continue;
|
|
4257
|
+
const fp = path.join(dir, e.name);
|
|
4258
|
+
const base = e.name.replace('.md', '').toLowerCase();
|
|
4259
|
+
if (orgCfg.roles.some(r => base === (r.id||'').toLowerCase() || base === (r.agent_type||'').toLowerCase() || (r.instructions_file||'').endsWith(e.name))) addFile(fp, 'agent-definition');
|
|
4260
|
+
}
|
|
4261
|
+
};
|
|
4262
|
+
if (fs.existsSync(agentsDir)) walkAgents(agentsDir);
|
|
4263
|
+
}
|
|
4264
|
+
files.sort((a, b) => new Date(b.mtime) - new Date(a.mtime));
|
|
4265
|
+
res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', 'Cache-Control': 'no-cache' });
|
|
4266
|
+
res.end(JSON.stringify(files));
|
|
4267
|
+
} catch (e) { res.writeHead(500); res.end(JSON.stringify({ error: e.message })); }
|
|
4268
|
+
return;
|
|
4269
|
+
}
|
|
4270
|
+
|
|
4271
|
+
// GET /api/file-content — return raw text content of a .monomind file
|
|
4272
|
+
if (req.method === 'GET' && url === '/api/file-content') {
|
|
4273
|
+
try {
|
|
4274
|
+
const _fcQs = new URL(req.url, 'http://localhost').searchParams;
|
|
4275
|
+
const rawPath = _fcQs.get('path');
|
|
4276
|
+
const baseDir = path.resolve(_fcQs.get('dir') || projectDir || process.cwd());
|
|
4277
|
+
if (!rawPath) { res.writeHead(400); res.end('Missing path'); return; }
|
|
4278
|
+
const resolved = path.resolve(rawPath);
|
|
4279
|
+
// Security: must be inside .monomind of the project dir
|
|
4280
|
+
const monomindDir = path.join(baseDir, '.monomind');
|
|
4281
|
+
if (!resolved.startsWith(monomindDir + path.sep) && resolved !== monomindDir) {
|
|
4282
|
+
res.writeHead(403); res.end('Forbidden'); return;
|
|
4283
|
+
}
|
|
4284
|
+
if (!fs.existsSync(resolved)) { res.writeHead(404); res.end('Not found'); return; }
|
|
4285
|
+
const stat = fs.statSync(resolved);
|
|
4286
|
+
if (!stat.isFile()) { res.writeHead(400); res.end('Not a file'); return; }
|
|
4287
|
+
if (stat.size > 524288) { res.writeHead(413); res.end('File too large'); return; }
|
|
4288
|
+
const content = fs.readFileSync(resolved, 'utf8');
|
|
4289
|
+
res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8', 'Access-Control-Allow-Origin': '*' });
|
|
4290
|
+
res.end(content);
|
|
4291
|
+
} catch(_) { res.writeHead(500); res.end('Internal error'); }
|
|
4292
|
+
return;
|
|
4293
|
+
}
|
|
4294
|
+
|
|
4295
|
+
// DELETE /api/orgs/:name — delete an org config and all associated data files
|
|
4296
|
+
if (req.method === 'DELETE' && url.match(/^\/api\/orgs\/[a-z0-9][a-z0-9_-]{0,63}(\?.*)?$/i)) {
|
|
4297
|
+
try {
|
|
4298
|
+
const orgName = decodeURIComponent(url.split('/')[3].split('?')[0]);
|
|
3903
4299
|
if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('Invalid org name'); return; }
|
|
3904
|
-
const
|
|
4300
|
+
const _delOrgQs = new URL(req.url, 'http://localhost').searchParams;
|
|
4301
|
+
const orgsDir = path.join(path.resolve(_delOrgQs.get('dir') || projectDir || process.cwd()), '.monomind', 'orgs');
|
|
3905
4302
|
const configFile = path.join(orgsDir, `${orgName}.json`);
|
|
3906
4303
|
if (!fs.existsSync(configFile)) { res.writeHead(404); res.end('{"error":"org not found"}'); return; }
|
|
3907
4304
|
// Remove all org-associated files (config + state + data)
|
|
3908
|
-
const suffixes = ['', '-state', '-goals', '-routines', '-approvals', '-activity', '-issues', '-members', '-projects', '-workspaces', '-worktrees', '-environments', '-plugins', '-adapters'];
|
|
4305
|
+
const suffixes = ['', '-state', '-goals', '-routines', '-approvals', '-activity', '-issues', '-members', '-projects', '-workspaces', '-worktrees', '-environments', '-plugins', '-adapters', '-budgets', '-threads', '-secrets', '-join-requests', '-bootstrap', '-project-workspaces', '-approval-comments', '-skills'];
|
|
3909
4306
|
for (const suf of suffixes) {
|
|
3910
4307
|
const f = path.join(orgsDir, `${orgName}${suf}.json`);
|
|
3911
4308
|
try { if (fs.existsSync(f)) fs.unlinkSync(f); } catch(_) {}
|
|
@@ -3914,6 +4311,18 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
3914
4311
|
}
|
|
3915
4312
|
// Remove stop file if present
|
|
3916
4313
|
try { fs.unlinkSync(path.join(orgsDir, '.stops', `${orgName}.stop`)); } catch(_) {}
|
|
4314
|
+
// Remove org subdirectory under .monomind/orgs/ (legacy flat-file location)
|
|
4315
|
+
try { const orgWorkDir = path.join(orgsDir, orgName); if (fs.existsSync(orgWorkDir)) fs.rmSync(orgWorkDir, { recursive: true, force: true }); } catch(_) {}
|
|
4316
|
+
// Remove org subdirectory under git-safe location (.git/monomind/orgs/<name>/) so run
|
|
4317
|
+
// files written by the worktree-aware path (feat 880f034e) are also cleaned up on delete
|
|
4318
|
+
try {
|
|
4319
|
+
const _delWorkDir = path.resolve(_delOrgQs.get('dir') || projectDir || process.cwd());
|
|
4320
|
+
const _delGitMonoDir = _getGitMonomindDir(_delWorkDir);
|
|
4321
|
+
if (_delGitMonoDir) {
|
|
4322
|
+
const gitOrgDir = path.join(_delGitMonoDir, 'orgs', orgName);
|
|
4323
|
+
if (fs.existsSync(gitOrgDir)) fs.rmSync(gitOrgDir, { recursive: true, force: true });
|
|
4324
|
+
}
|
|
4325
|
+
} catch(_) {}
|
|
3917
4326
|
// Remove loop prompt file if present (created for scheduled orgs by createorg)
|
|
3918
4327
|
try { const lpf = path.join(path.resolve(projectDir || process.cwd()), '.monomind', 'loops', `${orgName}.md`); if (fs.existsSync(lpf)) fs.unlinkSync(lpf); } catch(_) {}
|
|
3919
4328
|
// Emit org:delete event
|
|
@@ -3932,13 +4341,15 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
3932
4341
|
try {
|
|
3933
4342
|
const orgName = decodeURIComponent(url.split('/')[3]);
|
|
3934
4343
|
if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('Invalid org name'); return; }
|
|
4344
|
+
const _stopOrgQs = new URL(req.url, 'http://localhost').searchParams;
|
|
4345
|
+
const _stopOrgBase = path.resolve(_stopOrgQs.get('dir') || projectDir || process.cwd());
|
|
3935
4346
|
const stopEvent = { type: 'org:stop', org: orgName, ts: Date.now() };
|
|
3936
|
-
const dataDir = path.join(
|
|
4347
|
+
const dataDir = path.join(_stopOrgBase, 'data');
|
|
3937
4348
|
try { fs.mkdirSync(dataDir, { recursive: true }); } catch(_) {}
|
|
3938
4349
|
try { fs.appendFileSync(path.join(dataDir, 'mastermind-events.jsonl'), JSON.stringify(stopEvent) + '\n'); } catch(_) {}
|
|
3939
4350
|
// Write stop marker file for boss agent to detect
|
|
3940
4351
|
try {
|
|
3941
|
-
const stopDir = path.join(
|
|
4352
|
+
const stopDir = path.join(_stopOrgBase, '.monomind', 'orgs', '.stops');
|
|
3942
4353
|
fs.mkdirSync(stopDir, { recursive: true });
|
|
3943
4354
|
fs.writeFileSync(path.join(stopDir, `${orgName}.stop`), String(Date.now()));
|
|
3944
4355
|
} catch(_) {}
|
|
@@ -3953,7 +4364,7 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
3953
4364
|
// POST /api/orgs/:name/copy — copy org config to another project directory
|
|
3954
4365
|
if (req.method === 'POST' && url.match(/^\/api\/orgs\/[a-z0-9][a-z0-9_-]{0,63}\/copy$/i)) {
|
|
3955
4366
|
let body = '';
|
|
3956
|
-
for await (const chunk of req) body += chunk;
|
|
4367
|
+
for await (const chunk of req) { body += chunk; if (body.length > 2097152) { req.destroy(); break; } }
|
|
3957
4368
|
try {
|
|
3958
4369
|
const orgName = decodeURIComponent(url.split('/')[3]);
|
|
3959
4370
|
if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end(JSON.stringify({ error: 'Invalid org name' })); return; }
|
|
@@ -3962,7 +4373,8 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
3962
4373
|
const destination = payload.destination ? String(payload.destination).trim() : '';
|
|
3963
4374
|
if (!destination) { res.writeHead(400); res.end(JSON.stringify({ error: 'destination is required' })); return; }
|
|
3964
4375
|
if (!path.isAbsolute(destination)) { res.writeHead(400); res.end(JSON.stringify({ error: 'destination must be an absolute path' })); return; }
|
|
3965
|
-
const
|
|
4376
|
+
const _copyOrgQs = new URL(req.url, 'http://localhost').searchParams;
|
|
4377
|
+
const srcOrgsDir = path.join(path.resolve(_copyOrgQs.get('dir') || projectDir || process.cwd()), '.monomind', 'orgs');
|
|
3966
4378
|
const srcFile = path.join(srcOrgsDir, `${orgName}.json`);
|
|
3967
4379
|
if (!fs.existsSync(srcFile)) { res.writeHead(404); res.end(JSON.stringify({ error: 'org not found' })); return; }
|
|
3968
4380
|
const destOrgsDir = path.join(path.resolve(destination), '.monomind', 'orgs');
|
|
@@ -3975,16 +4387,87 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
3975
4387
|
return;
|
|
3976
4388
|
}
|
|
3977
4389
|
|
|
4390
|
+
// GET /api/org/:name/runs — list structured run files for an org
|
|
4391
|
+
if (req.method === 'GET' && /^\/api\/org\/[a-z0-9][a-z0-9_-]{0,63}\/runs(\?.*)?$/i.test(url)) {
|
|
4392
|
+
try {
|
|
4393
|
+
const _rQs = new URL(req.url, 'http://localhost').searchParams;
|
|
4394
|
+
const _rOrgName = decodeURIComponent(url.split('/')[3] || '');
|
|
4395
|
+
const _rWorkDir = path.resolve(_rQs.get('dir') || projectDir || process.cwd());
|
|
4396
|
+
const _rMonoDir = _getGitMonomindDir(_rWorkDir) || path.join(_rWorkDir, '.monomind');
|
|
4397
|
+
const _rDir = path.join(_rMonoDir, 'orgs', _rOrgName, 'runs');
|
|
4398
|
+
const runs = [];
|
|
4399
|
+
if (fs.existsSync(_rDir)) {
|
|
4400
|
+
const files = fs.readdirSync(_rDir).filter(f => f.endsWith('.jsonl')).sort().reverse();
|
|
4401
|
+
for (const f of files.slice(0, 50)) {
|
|
4402
|
+
try {
|
|
4403
|
+
const raw = fs.readFileSync(path.join(_rDir, f), 'utf8');
|
|
4404
|
+
const allLines = raw.split('\n').filter(Boolean);
|
|
4405
|
+
const eventCount = allLines.length;
|
|
4406
|
+
const parse = l => { try { return JSON.parse(l); } catch { return null; } };
|
|
4407
|
+
const headEvents = allLines.slice(0, 5).map(parse).filter(Boolean);
|
|
4408
|
+
const tailEvents = allLines.slice(-5).map(parse).filter(Boolean);
|
|
4409
|
+
const first = headEvents.find(e => e.type === 'run:start') || headEvents[0];
|
|
4410
|
+
const last = tailEvents.slice().reverse().find(e => e.type === 'run:complete' || e.type === 'org:complete');
|
|
4411
|
+
const cycles = allLines.filter(l => l.includes('"org:checkpoint"')).length;
|
|
4412
|
+
const lastEvent = tailEvents[tailEvents.length - 1] || headEvents[headEvents.length - 1];
|
|
4413
|
+
const ageMs = lastEvent?.ts ? Date.now() - lastEvent.ts : Infinity;
|
|
4414
|
+
const isStale = !last && ageMs > 30 * 60 * 1000;
|
|
4415
|
+
runs.push({ runId: f.replace('.jsonl', ''), startedAt: first?.ts || 0, endedAt: last?.ts || 0,
|
|
4416
|
+
status: last ? 'complete' : isStale ? 'stale' : 'running',
|
|
4417
|
+
eventCount, cycleCount: cycles, goal: first?.goal || '', bossRole: first?.bossRole || '' });
|
|
4418
|
+
} catch (_) {}
|
|
4419
|
+
}
|
|
4420
|
+
}
|
|
4421
|
+
res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', 'Cache-Control': 'no-cache' });
|
|
4422
|
+
res.end(JSON.stringify(runs));
|
|
4423
|
+
} catch (_) { res.writeHead(500); res.end('[]'); }
|
|
4424
|
+
return;
|
|
4425
|
+
}
|
|
4426
|
+
|
|
4427
|
+
// GET /api/org/:name/runs/:runId — get all events for a specific run
|
|
4428
|
+
if (req.method === 'GET' && /^\/api\/org\/[a-z0-9][a-z0-9_-]{0,63}\/runs\/[a-z0-9][a-z0-9_-]{0,79}(\?.*)?$/i.test(url)) {
|
|
4429
|
+
try {
|
|
4430
|
+
const _rvQs = new URL(req.url, 'http://localhost').searchParams;
|
|
4431
|
+
const _rvParts = url.replace(/\?.*$/, '').split('/');
|
|
4432
|
+
const _rvOrgName = decodeURIComponent(_rvParts[3] || '');
|
|
4433
|
+
const _rvRunId = decodeURIComponent(_rvParts[5] || '');
|
|
4434
|
+
const _rvWorkDir = path.resolve(_rvQs.get('dir') || projectDir || process.cwd());
|
|
4435
|
+
const _rvMonoDir = _getGitMonomindDir(_rvWorkDir) || path.join(_rvWorkDir, '.monomind');
|
|
4436
|
+
const _rvFile = path.join(_rvMonoDir, 'orgs', _rvOrgName, 'runs', `${_rvRunId}.jsonl`);
|
|
4437
|
+
if (!fs.existsSync(_rvFile)) { res.writeHead(404); res.end('{"error":"run not found"}'); return; }
|
|
4438
|
+
const events = fs.readFileSync(_rvFile, 'utf8').split('\n').filter(Boolean)
|
|
4439
|
+
.map(l => { try { return JSON.parse(l); } catch { return null; } }).filter(Boolean);
|
|
4440
|
+
res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', 'Cache-Control': 'no-cache' });
|
|
4441
|
+
res.end(JSON.stringify(events));
|
|
4442
|
+
} catch (_) { res.writeHead(500); res.end('[]'); }
|
|
4443
|
+
return;
|
|
4444
|
+
}
|
|
4445
|
+
|
|
3978
4446
|
// ------------------------------------------------- Mastermind event system
|
|
3979
4447
|
// POST /api/mastermind/event — ingest event from mastermind skill
|
|
3980
4448
|
if (req.method === 'POST' && url === '/api/mastermind/event') {
|
|
3981
4449
|
let body = '';
|
|
3982
|
-
for await (const chunk of req) body += chunk;
|
|
4450
|
+
for await (const chunk of req) { body += chunk; if (body.length > 2097152) { req.destroy(); break; } }
|
|
3983
4451
|
let event = {};
|
|
3984
4452
|
try { event = JSON.parse(body); } catch (_) {}
|
|
3985
4453
|
event.ts = event.ts || Date.now();
|
|
3986
|
-
// Use project path from event if provided (multi-project support)
|
|
3987
|
-
|
|
4454
|
+
// Use project path from event if provided (multi-project support).
|
|
4455
|
+
// Security: path.isAbsolute() alone is insufficient — an attacker can
|
|
4456
|
+
// supply event.project="/etc" and cause writes to system directories.
|
|
4457
|
+
// Only accept paths that resolve to an existing directory AND are not
|
|
4458
|
+
// the filesystem root (/), AND are not obviously system paths.
|
|
4459
|
+
// Cap to 4096 chars to prevent OOM from huge path strings.
|
|
4460
|
+
const _rawProject = event.project;
|
|
4461
|
+
let eventProject = null;
|
|
4462
|
+
if (typeof _rawProject === 'string' && _rawProject.length > 0 && _rawProject.length <= 4096
|
|
4463
|
+
&& path.isAbsolute(_rawProject)) {
|
|
4464
|
+
// Reject filesystem root and common system directories
|
|
4465
|
+
const _norm = path.resolve(_rawProject);
|
|
4466
|
+
const _systemPaths = ['/', '/etc', '/usr', '/bin', '/sbin', '/lib', '/lib64', '/boot', '/dev', '/sys', '/proc', '/tmp'];
|
|
4467
|
+
if (!_systemPaths.includes(_norm) && !_systemPaths.some(p => _norm.startsWith(p + '/'))) {
|
|
4468
|
+
eventProject = _norm;
|
|
4469
|
+
}
|
|
4470
|
+
}
|
|
3988
4471
|
const root = eventProject || projectDir || process.cwd();
|
|
3989
4472
|
const dataDir = path.join(root, 'data');
|
|
3990
4473
|
try { fs.mkdirSync(dataDir, { recursive: true }); } catch (_) {}
|
|
@@ -3998,6 +4481,28 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
3998
4481
|
} catch (_) {}
|
|
3999
4482
|
}
|
|
4000
4483
|
try { fs.appendFileSync(path.join(dataDir, 'mastermind-events.jsonl'), JSON.stringify(event) + '\n'); } catch (_) {}
|
|
4484
|
+
// Track active runs and route org events to run files
|
|
4485
|
+
if (event.org) {
|
|
4486
|
+
const _orgKey = String(event.org).trim();
|
|
4487
|
+
// Any event with both org+runId updates the active run map (run:start written directly to file so org:start is first via curl)
|
|
4488
|
+
if (event.runId) activeOrgRuns.set(_orgKey, String(event.runId).trim());
|
|
4489
|
+
else if (activeOrgRuns.has(_orgKey)) event.runId = activeOrgRuns.get(_orgKey);
|
|
4490
|
+
if (event.type === 'run:complete' || event.type === 'org:complete') activeOrgRuns.delete(_orgKey);
|
|
4491
|
+
}
|
|
4492
|
+
// Persist to git-safe run file (survives branch switches + shared across worktrees)
|
|
4493
|
+
if (event.org && event.runId) {
|
|
4494
|
+
try {
|
|
4495
|
+
const _orn = String(event.org).trim();
|
|
4496
|
+
const _rid = String(event.runId).trim();
|
|
4497
|
+
if (_orn.length > 0 && _orn.length <= 64 && /^[a-z0-9][a-z0-9_-]*$/i.test(_orn)
|
|
4498
|
+
&& _rid.length > 0 && _rid.length <= 80 && /^[a-z0-9][a-z0-9_-]*$/i.test(_rid)) {
|
|
4499
|
+
const _monoDir = _getGitMonomindDir(root) || path.join(root, '.monomind');
|
|
4500
|
+
const _runDir = path.join(_monoDir, 'orgs', _orn, 'runs');
|
|
4501
|
+
fs.mkdirSync(_runDir, { recursive: true });
|
|
4502
|
+
fs.appendFileSync(path.join(_runDir, `${_rid}.jsonl`), JSON.stringify(event) + '\n');
|
|
4503
|
+
}
|
|
4504
|
+
} catch (_) {}
|
|
4505
|
+
}
|
|
4001
4506
|
// Persist session
|
|
4002
4507
|
try {
|
|
4003
4508
|
const sessFile = path.join(dataDir, 'mastermind-sessions.json');
|
|
@@ -4016,12 +4521,19 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
4016
4521
|
}
|
|
4017
4522
|
}
|
|
4018
4523
|
fs.writeFileSync(sessFile, JSON.stringify(sessions.slice(0, 50), null, 2));
|
|
4019
|
-
// Also write individual session file for direct traceability
|
|
4524
|
+
// Also write individual session file for direct traceability.
|
|
4525
|
+
// Security: validate event.session before using it as a filename to
|
|
4526
|
+
// prevent path traversal (e.g. "../../../etc/cron.d/payload").
|
|
4020
4527
|
const sessionObj = sessions.find(s => s.id === event.session);
|
|
4021
4528
|
if (sessionObj) {
|
|
4022
4529
|
const sessDir = path.join(dataDir, 'sessions');
|
|
4023
4530
|
try { fs.mkdirSync(sessDir, { recursive: true }); } catch (_) {}
|
|
4024
|
-
try {
|
|
4531
|
+
try {
|
|
4532
|
+
const _sid = String(event.session || '').trim();
|
|
4533
|
+
if (_sid.length > 0 && _sid.length <= 128 && /^[a-zA-Z0-9_.-]+$/.test(_sid)) {
|
|
4534
|
+
fs.writeFileSync(path.join(sessDir, `${_sid}.json`), JSON.stringify(sessionObj, null, 2));
|
|
4535
|
+
}
|
|
4536
|
+
} catch (_) {}
|
|
4025
4537
|
}
|
|
4026
4538
|
} catch (_) {}
|
|
4027
4539
|
// For org:stop events, write a stop marker the boss agent can detect
|
|
@@ -4198,6 +4710,75 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
4198
4710
|
return;
|
|
4199
4711
|
}
|
|
4200
4712
|
|
|
4713
|
+
// GET /api/status — live system snapshot for dashboard polling
|
|
4714
|
+
if (req.method === 'GET' && url === '/api/status') {
|
|
4715
|
+
try {
|
|
4716
|
+
const root = projectDir || process.cwd();
|
|
4717
|
+
// Active org runs: { orgName -> runId }
|
|
4718
|
+
const orgRuns = {};
|
|
4719
|
+
activeOrgRuns.forEach((runId, org) => { orgRuns[org] = runId; });
|
|
4720
|
+
// Recent events (last 10)
|
|
4721
|
+
let recentEvents = [];
|
|
4722
|
+
try {
|
|
4723
|
+
const evPath = path.join(root, 'data', 'mastermind-events.jsonl');
|
|
4724
|
+
const lines = fs.readFileSync(evPath, 'utf8').split('\n').filter(l => l.trim()).slice(-10);
|
|
4725
|
+
recentEvents = lines.map(l => { try { return JSON.parse(l); } catch(_) { return null; } }).filter(Boolean);
|
|
4726
|
+
} catch(_) {}
|
|
4727
|
+
res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
|
|
4728
|
+
res.end(JSON.stringify({
|
|
4729
|
+
ts: Date.now(),
|
|
4730
|
+
uptime: process.uptime(),
|
|
4731
|
+
sseClients: mmSseClients.size,
|
|
4732
|
+
activeOrgs: Object.keys(orgRuns).length,
|
|
4733
|
+
orgRuns,
|
|
4734
|
+
recentEvents,
|
|
4735
|
+
}));
|
|
4736
|
+
} catch(err) {
|
|
4737
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
4738
|
+
res.end(JSON.stringify({ error: err.message }));
|
|
4739
|
+
}
|
|
4740
|
+
return;
|
|
4741
|
+
}
|
|
4742
|
+
|
|
4743
|
+
// GET /api/orgs/:name/runs/current — events from the active run file for an org
|
|
4744
|
+
if (req.method === 'GET' && /^\/api\/orgs\/[^/]+\/runs\/current$/.test(url)) {
|
|
4745
|
+
try {
|
|
4746
|
+
const orgName = decodeURIComponent(url.split('/')[3]);
|
|
4747
|
+
const root = projectDir || process.cwd();
|
|
4748
|
+
// Validate orgName
|
|
4749
|
+
if (!orgName || orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) {
|
|
4750
|
+
res.writeHead(400); res.end('{"error":"invalid org name"}'); return;
|
|
4751
|
+
}
|
|
4752
|
+
const runId = activeOrgRuns.get(orgName);
|
|
4753
|
+
const monoDir = _getGitMonomindDir(root) || path.join(root, '.monomind');
|
|
4754
|
+
// Try active run first, then fall back to most recent run file
|
|
4755
|
+
let runFile = null;
|
|
4756
|
+
if (runId) {
|
|
4757
|
+
const candidate = path.join(monoDir, 'orgs', orgName, 'runs', `${runId}.jsonl`);
|
|
4758
|
+
if (fs.existsSync(candidate)) runFile = candidate;
|
|
4759
|
+
}
|
|
4760
|
+
if (!runFile) {
|
|
4761
|
+
const runsDir = path.join(monoDir, 'orgs', orgName, 'runs');
|
|
4762
|
+
if (fs.existsSync(runsDir)) {
|
|
4763
|
+
const files = fs.readdirSync(runsDir).filter(f => f.endsWith('.jsonl'));
|
|
4764
|
+
if (files.length) {
|
|
4765
|
+
files.sort();
|
|
4766
|
+
runFile = path.join(runsDir, files[files.length - 1]);
|
|
4767
|
+
}
|
|
4768
|
+
}
|
|
4769
|
+
}
|
|
4770
|
+
if (!runFile) { res.writeHead(404); res.end('{"events":[],"runId":null}'); return; }
|
|
4771
|
+
const detectedRunId = path.basename(runFile, '.jsonl');
|
|
4772
|
+
const lines = fs.readFileSync(runFile, 'utf8').split('\n').filter(l => l.trim()).slice(-100);
|
|
4773
|
+
const events = lines.map(l => { try { return JSON.parse(l); } catch(_) { return null; } }).filter(Boolean);
|
|
4774
|
+
res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
|
|
4775
|
+
res.end(JSON.stringify({ runId: detectedRunId, events, active: activeOrgRuns.has(orgName) }));
|
|
4776
|
+
} catch(err) {
|
|
4777
|
+
res.writeHead(500); res.end(JSON.stringify({ error: err.message }));
|
|
4778
|
+
}
|
|
4779
|
+
return;
|
|
4780
|
+
}
|
|
4781
|
+
|
|
4201
4782
|
// GET /api/mastermind/metrics — aggregate system metrics from token-summary and swarm-activity
|
|
4202
4783
|
if (req.method === 'GET' && url === '/api/mastermind/metrics') {
|
|
4203
4784
|
try {
|