@monoes/monomindcli 1.11.13 → 1.12.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 +13 -6
- package/.claude/helpers/handlers/session-handler.cjs +11 -4
- package/.claude/helpers/handlers/session-restore-handler.cjs +21 -11
- package/.claude/helpers/handlers/task-handler.cjs +13 -5
- package/.claude/helpers/intelligence.cjs +7 -2
- package/.claude/helpers/loop-tracker.cjs +15 -3
- package/.claude/helpers/memory.cjs +6 -1
- package/.claude/helpers/router.cjs +5 -2
- package/.claude/helpers/session.cjs +2 -0
- package/.claude/helpers/statusline.cjs +10 -2
- package/.claude/helpers/utils/micro-agents.cjs +20 -4
- 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 +97 -20
- 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 +148 -26
- 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 +53 -12
- 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 +9 -3
- 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 +926 -1268
- package/dist/src/ui/orgs.html +722 -12
- package/dist/src/ui/server.mjs +573 -134
- 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;
|
|
@@ -410,7 +449,23 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
410
449
|
return;
|
|
411
450
|
}
|
|
412
451
|
try {
|
|
413
|
-
|
|
452
|
+
// Security: validate that the requested file stays within the user's
|
|
453
|
+
// home directory. Without this, ?file=/etc/passwd discloses arbitrary
|
|
454
|
+
// system files to any process that can reach localhost:4242.
|
|
455
|
+
const _resolvedFile = path.resolve(file);
|
|
456
|
+
const _homeDir = os.homedir();
|
|
457
|
+
if (!_resolvedFile.startsWith(_homeDir + path.sep) && !_resolvedFile.startsWith(_homeDir)) {
|
|
458
|
+
res.writeHead(403, { 'Content-Type': 'application/json' });
|
|
459
|
+
res.end(JSON.stringify({ error: 'Access denied: file must be within the home directory' }));
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
// Only allow JSONL files (session logs).
|
|
463
|
+
if (!_resolvedFile.endsWith('.jsonl')) {
|
|
464
|
+
res.writeHead(403, { 'Content-Type': 'application/json' });
|
|
465
|
+
res.end(JSON.stringify({ error: 'Access denied: only .jsonl files are permitted' }));
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
const raw = fs.readFileSync(_resolvedFile, 'utf8');
|
|
414
469
|
const allLines = raw.split('\n').filter(Boolean);
|
|
415
470
|
const lines = allLines.slice(-limit);
|
|
416
471
|
const events = parseSessionLines(lines);
|
|
@@ -450,7 +505,7 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
450
505
|
for (const { f, mtime } of sessionFiles) {
|
|
451
506
|
const fp = path.join(projectClaudeDir, f);
|
|
452
507
|
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;
|
|
508
|
+
let lastPrompt = '', summaries = [], totalDurationMs = 0, totalMessages = 0, firstTs = null, lastTs = null, totalCost = 0, toolCalls = 0, userMessages = 0, cacheReadTokens = 0, totalInputTokens = 0, errorCount = 0;
|
|
454
509
|
const modelBreakdown = {};
|
|
455
510
|
const filesTouchedSet = new Set();
|
|
456
511
|
try {
|
|
@@ -460,7 +515,12 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
460
515
|
let e; try { e = JSON.parse(line); } catch { continue; }
|
|
461
516
|
if (e.timestamp) { if (!firstTs) firstTs = e.timestamp; lastTs = e.timestamp; }
|
|
462
517
|
if (e.type === 'last-prompt' && e.lastPrompt) lastPrompt = e.lastPrompt;
|
|
463
|
-
if (e.type === 'user')
|
|
518
|
+
if (e.type === 'user') {
|
|
519
|
+
userMessages++;
|
|
520
|
+
for (const b of (e.message?.content || [])) {
|
|
521
|
+
if (b && b.type === 'tool_result' && b.is_error) errorCount++;
|
|
522
|
+
}
|
|
523
|
+
}
|
|
464
524
|
if (e.type === 'system' && e.subtype === 'compact_boundary') pendingCompact = true;
|
|
465
525
|
if (pendingCompact && e.type === 'user') {
|
|
466
526
|
const msg = e.message || {};
|
|
@@ -502,7 +562,9 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
502
562
|
}
|
|
503
563
|
} catch {}
|
|
504
564
|
const filesTouched = [...filesTouchedSet].slice(0, 20);
|
|
505
|
-
|
|
565
|
+
const compactCount = summaries.length;
|
|
566
|
+
const summary = summaries.length ? summaries[summaries.length - 1].text : null;
|
|
567
|
+
sessions.push({ id, mtime, firstTs, lastTs, lastPrompt, summaries, summary, compactCount, errorCount, totalDurationMs, totalMessages, totalCost, toolCalls, userMessages, cacheReadTokens, totalInputTokens, modelBreakdown, filesTouched, file: fp });
|
|
506
568
|
}
|
|
507
569
|
res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', 'Cache-Control': 'no-cache' });
|
|
508
570
|
res.end(JSON.stringify({ sessions }));
|
|
@@ -891,7 +953,7 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
891
953
|
// ------------------------------------------------------- PUT /api/memory-file
|
|
892
954
|
if (req.method === 'PUT' && url === '/api/memory-file') {
|
|
893
955
|
let body = '';
|
|
894
|
-
req.on('data', chunk => { body += chunk; });
|
|
956
|
+
req.on('data', chunk => { body += chunk; if (body.length > 2097152) { req.destroy(); return; } });
|
|
895
957
|
req.on('end', () => {
|
|
896
958
|
try {
|
|
897
959
|
const qs = new URL(req.url, 'http://localhost').searchParams;
|
|
@@ -925,7 +987,7 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
925
987
|
// ------------------------------------------------------- DELETE /api/memory-file
|
|
926
988
|
if (req.method === 'DELETE' && url === '/api/memory-file') {
|
|
927
989
|
let body = '';
|
|
928
|
-
req.on('data', chunk => { body += chunk; });
|
|
990
|
+
req.on('data', chunk => { body += chunk; if (body.length > 2097152) { req.destroy(); return; } });
|
|
929
991
|
req.on('end', () => {
|
|
930
992
|
try {
|
|
931
993
|
const qs = new URL(req.url, 'http://localhost').searchParams;
|
|
@@ -1060,8 +1122,7 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
1060
1122
|
let alive = false;
|
|
1061
1123
|
try { process.kill(pid, 0); alive = true; } catch {}
|
|
1062
1124
|
const alreadyTracked = loops.some(l => l.id === sessionId || l.sessionId === sessionId);
|
|
1063
|
-
|
|
1064
|
-
if (alive && sessionId && !alreadyTracked && !hasRepeatLoops && !stopFiles.has(sessionId)) {
|
|
1125
|
+
if (alive && sessionId && !alreadyTracked && !stopFiles.has(sessionId)) {
|
|
1065
1126
|
// Try to extract ScheduleWakeup context from session JSONL
|
|
1066
1127
|
let loopEntry = null;
|
|
1067
1128
|
try {
|
|
@@ -1144,6 +1205,10 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
1144
1205
|
}
|
|
1145
1206
|
} catch {}
|
|
1146
1207
|
|
|
1208
|
+
// Dedup: suppress scheduled_tasks_lock noise when real repeat loops exist
|
|
1209
|
+
const hasRepeatLoops = loops.some(l => l.source !== 'scheduled_tasks_lock' && l.source !== 'schedule_wakeup_hook');
|
|
1210
|
+
if (hasRepeatLoops) loops = loops.filter(l => l.source !== 'scheduled_tasks_lock' && l.source !== 'schedule_wakeup_hook');
|
|
1211
|
+
|
|
1147
1212
|
loops.sort((a, b) => (b.startedAt || 0) - (a.startedAt || 0));
|
|
1148
1213
|
res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', 'Cache-Control': 'no-cache' });
|
|
1149
1214
|
res.end(JSON.stringify({ loops }));
|
|
@@ -1154,13 +1219,14 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
1154
1219
|
// ---------------------------------------------------------- POST /api/loops/stop
|
|
1155
1220
|
if (req.method === 'POST' && url === '/api/loops/stop') {
|
|
1156
1221
|
let body = '';
|
|
1157
|
-
req.on('data', chunk => { body += chunk; });
|
|
1222
|
+
req.on('data', chunk => { body += chunk; if (body.length > 2097152) { req.destroy(); return; } });
|
|
1158
1223
|
req.on('end', () => {
|
|
1159
1224
|
try {
|
|
1160
|
-
const stopQs = new URL(req.url, 'http://localhost').searchParams;
|
|
1161
1225
|
const { id } = JSON.parse(body);
|
|
1162
1226
|
if (!id) { res.writeHead(400); res.end(JSON.stringify({ error: 'id required' })); return; }
|
|
1163
|
-
const
|
|
1227
|
+
const _stopQs = new URL(req.url, 'http://localhost').searchParams;
|
|
1228
|
+
const _stopDir = path.resolve(_stopQs.get('dir') || projectDir || process.cwd());
|
|
1229
|
+
const loopsDir = path.join(_stopDir, '.monomind', 'loops');
|
|
1164
1230
|
fs.mkdirSync(loopsDir, { recursive: true });
|
|
1165
1231
|
fs.writeFileSync(path.join(loopsDir, `${id}.stop`), `stop-requested-${Date.now()}`);
|
|
1166
1232
|
res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
|
|
@@ -1173,17 +1239,27 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
1173
1239
|
// ---------------------------------------------------------- POST /api/loops/create
|
|
1174
1240
|
if (req.method === 'POST' && url === '/api/loops/create') {
|
|
1175
1241
|
let body = '';
|
|
1176
|
-
req.on('data', chunk => { body += chunk; });
|
|
1242
|
+
req.on('data', chunk => { body += chunk; if (body.length > 2097152) { req.destroy(); return; } });
|
|
1177
1243
|
req.on('end', () => {
|
|
1178
1244
|
try {
|
|
1179
|
-
const
|
|
1180
|
-
const { name, prompt, interval, maxReps } = JSON.parse(body);
|
|
1245
|
+
const _qs = new URL(req.url, 'http://localhost').searchParams;
|
|
1246
|
+
const { name: _rawName, prompt: _rawPrompt, interval: _rawInterval, maxReps: _rawMaxReps } = JSON.parse(body);
|
|
1247
|
+
// Cap field sizes to prevent individual large-field disk inflation.
|
|
1248
|
+
// The 2MB body cap already limits total payload, but a single field
|
|
1249
|
+
// near 2MB would produce a multi-MB loop config file per request.
|
|
1250
|
+
const MAX_LOOP_PROMPT_LEN = 64 * 1024; // 64 KB
|
|
1251
|
+
const MAX_LOOP_NAME_LEN = 512;
|
|
1252
|
+
const MAX_LOOP_INTERVAL_LEN = 64;
|
|
1253
|
+
const prompt = typeof _rawPrompt === 'string' ? _rawPrompt.slice(0, MAX_LOOP_PROMPT_LEN) : null;
|
|
1254
|
+
const name = typeof _rawName === 'string' ? _rawName.slice(0, MAX_LOOP_NAME_LEN) : null;
|
|
1255
|
+
const interval = typeof _rawInterval === 'string' ? _rawInterval.slice(0, MAX_LOOP_INTERVAL_LEN) : null;
|
|
1256
|
+
const maxReps = typeof _rawMaxReps === 'number' && Number.isFinite(_rawMaxReps) ? Math.max(1, Math.min(Math.floor(_rawMaxReps), 10000)) : null;
|
|
1181
1257
|
if (!prompt) { res.writeHead(400); res.end(JSON.stringify({ error: 'prompt required' })); return; }
|
|
1182
|
-
const loopsDir = path.join(
|
|
1258
|
+
const loopsDir = path.join(path.resolve(_qs.get('dir') || projectDir || process.cwd()), '.monomind', 'loops');
|
|
1183
1259
|
fs.mkdirSync(loopsDir, { recursive: true });
|
|
1184
1260
|
const id = `loop-${Date.now()}-${Math.random().toString(36).slice(2,7)}`;
|
|
1185
1261
|
const nowMs = Date.now();
|
|
1186
|
-
const loop = { id, type: 'repeat', name: name || prompt.slice(0, 40), prompt, interval: interval || '1h', maxReps
|
|
1262
|
+
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
1263
|
fs.writeFileSync(path.join(loopsDir, `${id}.json`), JSON.stringify(loop, null, 2));
|
|
1188
1264
|
res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
|
|
1189
1265
|
res.end(JSON.stringify({ ok: true, id }));
|
|
@@ -1196,7 +1272,10 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
1196
1272
|
if (req.method === 'GET' && url === '/api/session-errors') {
|
|
1197
1273
|
const qs = new URL(req.url, 'http://localhost').searchParams;
|
|
1198
1274
|
const d = path.resolve(qs.get('dir') || projectDir || process.cwd());
|
|
1199
|
-
|
|
1275
|
+
// Cap sessionId to prevent O(n×m) DoS via f.includes(sessionId) substring
|
|
1276
|
+
// match against every filename when sessionId is a very long string.
|
|
1277
|
+
const _rawSessId = qs.get('id') || '';
|
|
1278
|
+
const sessionId = _rawSessId.slice(0, 256);
|
|
1200
1279
|
const slug = d.replace(/\//g, '-');
|
|
1201
1280
|
const projectClaudeDir = path.join(os.homedir(), '.claude', 'projects', slug);
|
|
1202
1281
|
try {
|
|
@@ -1267,7 +1346,7 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
1267
1346
|
// ------------------------------------------------------- DELETE /api/knowledge-chunk
|
|
1268
1347
|
if (req.method === 'DELETE' && url === '/api/knowledge-chunk') {
|
|
1269
1348
|
let body = '';
|
|
1270
|
-
req.on('data', chunk => { body += chunk; });
|
|
1349
|
+
req.on('data', chunk => { body += chunk; if (body.length > 2097152) { req.destroy(); return; } });
|
|
1271
1350
|
req.on('end', () => {
|
|
1272
1351
|
try {
|
|
1273
1352
|
const qs = new URL(req.url, 'http://localhost').searchParams;
|
|
@@ -1306,7 +1385,7 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
1306
1385
|
// ------------------------------------------------------- PUT /api/knowledge-chunk
|
|
1307
1386
|
if (req.method === 'PUT' && url === '/api/knowledge-chunk') {
|
|
1308
1387
|
let body = '';
|
|
1309
|
-
req.on('data', chunk => { body += chunk; });
|
|
1388
|
+
req.on('data', chunk => { body += chunk; if (body.length > 2097152) { req.destroy(); return; } });
|
|
1310
1389
|
req.on('end', () => {
|
|
1311
1390
|
try {
|
|
1312
1391
|
const qs = new URL(req.url, 'http://localhost').searchParams;
|
|
@@ -1802,7 +1881,8 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
1802
1881
|
try {
|
|
1803
1882
|
const qs = new URL(req.url, 'http://localhost').searchParams;
|
|
1804
1883
|
const dir = qs.get('dir') || projectDir || process.cwd();
|
|
1805
|
-
|
|
1884
|
+
// Cap ?q= to prevent DoS via megabyte FTS query strings.
|
|
1885
|
+
const q = (qs.get('q') || '').trim().slice(0, 4096);
|
|
1806
1886
|
const limit = Math.min(100, parseInt(qs.get('limit') || '50', 10));
|
|
1807
1887
|
const d = path.resolve(dir || process.cwd());
|
|
1808
1888
|
const dbPath = path.join(d, '.monomind', 'monograph.db');
|
|
@@ -1936,7 +2016,7 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
1936
2016
|
try {
|
|
1937
2017
|
const qs = new URL(req.url, 'http://localhost').searchParams;
|
|
1938
2018
|
const dir = qs.get('dir') || projectDir || process.cwd();
|
|
1939
|
-
const q = qs.get('q') || '';
|
|
2019
|
+
const q = (qs.get('q') || '').trim().slice(0, 4096);
|
|
1940
2020
|
const d = path.resolve(dir || process.cwd());
|
|
1941
2021
|
const dbPath = path.join(d, '.monomind', 'monograph.db');
|
|
1942
2022
|
if (!q) { res.writeHead(400); res.end(JSON.stringify({ error: 'Missing ?q= parameter' })); return; }
|
|
@@ -1973,7 +2053,7 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
1973
2053
|
try {
|
|
1974
2054
|
const qs = new URL(req.url, 'http://localhost').searchParams;
|
|
1975
2055
|
const dir = qs.get('dir') || projectDir || process.cwd();
|
|
1976
|
-
const nodeQ = qs.get('node') || '';
|
|
2056
|
+
const nodeQ = (qs.get('node') || '').trim().slice(0, 4096);
|
|
1977
2057
|
const d = path.resolve(dir || process.cwd());
|
|
1978
2058
|
const dbPath = path.join(d, '.monomind', 'monograph.db');
|
|
1979
2059
|
if (!nodeQ) { res.writeHead(400); res.end(JSON.stringify({ error: 'Missing ?node= parameter' })); return; }
|
|
@@ -2015,8 +2095,8 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
2015
2095
|
try {
|
|
2016
2096
|
const qs = new URL(req.url, 'http://localhost').searchParams;
|
|
2017
2097
|
const dir = qs.get('dir') || projectDir || process.cwd();
|
|
2018
|
-
const from = qs.get('from') || '';
|
|
2019
|
-
const to = qs.get('to') || '';
|
|
2098
|
+
const from = (qs.get('from') || '').trim().slice(0, 4096);
|
|
2099
|
+
const to = (qs.get('to') || '').trim().slice(0, 4096);
|
|
2020
2100
|
const d = path.resolve(dir || process.cwd());
|
|
2021
2101
|
const dbPath = path.join(d, '.monomind', 'monograph.db');
|
|
2022
2102
|
if (!from || !to) { res.writeHead(400); res.end(JSON.stringify({ error: 'Missing ?from= and ?to= parameters' })); return; }
|
|
@@ -2136,7 +2216,7 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
2136
2216
|
// -------------------------------------------------- POST /api/mcp/call
|
|
2137
2217
|
if (req.method === 'POST' && url === '/api/mcp/call') {
|
|
2138
2218
|
let body = '';
|
|
2139
|
-
req.on('data', c => body += c);
|
|
2219
|
+
req.on('data', c => { body += c; if (body.length > 2097152) { req.destroy(); return; } });
|
|
2140
2220
|
req.on('end', async () => {
|
|
2141
2221
|
const json = res => { res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' }); };
|
|
2142
2222
|
const ok = (data) => { json(res); res.end(JSON.stringify({ content: [{ type: 'text', text: typeof data === 'string' ? data : JSON.stringify(data, null, 2) }] })); };
|
|
@@ -2184,7 +2264,7 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
2184
2264
|
ok(`nodes: ${n}\nedges: ${e}`);
|
|
2185
2265
|
} else if (tool === 'monograph_cypher') {
|
|
2186
2266
|
// Translate basic MATCH (n:Label) queries to SQL
|
|
2187
|
-
const q = (input.query || '').trim();
|
|
2267
|
+
const q = (String(input.query || '')).trim().slice(0, 4096);
|
|
2188
2268
|
const labelMatch = q.match(/MATCH\s+\(n:(\w+)\)/i);
|
|
2189
2269
|
if (labelMatch) {
|
|
2190
2270
|
const label = labelMatch[1];
|
|
@@ -2242,12 +2322,13 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
2242
2322
|
} else if (tool === 'monograph_diff') {
|
|
2243
2323
|
ok('Graph diff: compare two snapshots using monograph snapshot + monograph diff commands');
|
|
2244
2324
|
} else if (tool === 'monograph_rename') {
|
|
2245
|
-
|
|
2325
|
+
// Cap sym to prevent O(n) FTS scan DoS via oversized query string.
|
|
2326
|
+
const sym = String(input.symbolName || '').slice(0, 4096);
|
|
2246
2327
|
if (!sym) { ok('Provide symbolName to rename'); return; }
|
|
2247
2328
|
const hits = ftsSearch(db2, sym, 20);
|
|
2248
2329
|
ok(`Found ${hits.length} occurrences of "${sym}":\n` + hits.map(h => ` ${h.filePath || '?'}:${h.startLine || '?'} — ${h.name}`).join('\n'));
|
|
2249
2330
|
} else if (tool === 'monograph_impact') {
|
|
2250
|
-
const target = input.target || '';
|
|
2331
|
+
const target = String(input.target || '').slice(0, 4096);
|
|
2251
2332
|
const dir3 = input.direction || 'both';
|
|
2252
2333
|
const depth = input.maxDepth || 4;
|
|
2253
2334
|
const hits = ftsSearch(db2, target, 5);
|
|
@@ -2274,7 +2355,7 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
2274
2355
|
}
|
|
2275
2356
|
ok(`Impact of "${hits[0].name}" (${dir3}, depth=${depth}):\n` + (results.join('\n') || ' (no dependencies found)'));
|
|
2276
2357
|
} else if (tool === 'monograph_context') {
|
|
2277
|
-
const id = input.id || '';
|
|
2358
|
+
const id = String(input.id || '').slice(0, 4096);
|
|
2278
2359
|
const hits = ftsSearch(db2, id, 5);
|
|
2279
2360
|
if (!hits.length) { ok(`Node not found: ${id}`); return; }
|
|
2280
2361
|
const node = hits[0];
|
|
@@ -2282,7 +2363,7 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
2282
2363
|
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
2364
|
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
2365
|
} else if (tool === 'monograph_query' || tool === 'monograph_suggest') {
|
|
2285
|
-
const q2 = input.query || input.task || '';
|
|
2366
|
+
const q2 = String(input.query || input.task || '').slice(0, 4096);
|
|
2286
2367
|
const hits2 = ftsSearch(db2, q2, 20);
|
|
2287
2368
|
ok(hits2.map(h => `${h.name} (${h.label}) — ${h.filePath || '?'}:${h.startLine || '?'}`).join('\n') || 'No results');
|
|
2288
2369
|
|
|
@@ -2631,7 +2712,7 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
2631
2712
|
if (['Read','Write','Edit','MultiEdit','Glob','Grep','LS'].includes(name)) return 'file';
|
|
2632
2713
|
if (name === 'Bash') return 'bash';
|
|
2633
2714
|
if (['Agent','Task'].includes(name)) return 'agent';
|
|
2634
|
-
if (name.startsWith('
|
|
2715
|
+
if (name.startsWith('mcp__monomind__memory') || name.startsWith('mcp__monomind__agentdb')) return 'memory';
|
|
2635
2716
|
if (['WebFetch','WebSearch'].includes(name)) return 'web';
|
|
2636
2717
|
if (name === 'Skill') return 'skill';
|
|
2637
2718
|
return 'other';
|
|
@@ -2648,9 +2729,35 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
2648
2729
|
try { stat = fs.statSync(fp); } catch { continue; }
|
|
2649
2730
|
|
|
2650
2731
|
// Skip files over size cap to avoid memory spikes on large sessions
|
|
2732
|
+
// But still do a lightweight scan for agent spawns (tool_use blocks named Agent/Task)
|
|
2651
2733
|
if (stat.size > JSONL_SIZE_CAP) {
|
|
2734
|
+
const truncSpawns = {};
|
|
2735
|
+
try {
|
|
2736
|
+
const raw = fs.readFileSync(fp, 'utf8');
|
|
2737
|
+
for (const line of raw.split('\n')) {
|
|
2738
|
+
if (!line.includes('"tool_use"') || (!line.includes('"Agent"') && !line.includes('"Task"'))) continue;
|
|
2739
|
+
let e; try { e = JSON.parse(line); } catch { continue; }
|
|
2740
|
+
if (e.type !== 'assistant') continue;
|
|
2741
|
+
for (const block of (e.message?.content || [])) {
|
|
2742
|
+
if (!block || block.type !== 'tool_use') continue;
|
|
2743
|
+
if (block.name !== 'Agent' && block.name !== 'Task') continue;
|
|
2744
|
+
const sub = block.input?.subagent_type || block.input?.description || '?';
|
|
2745
|
+
truncSpawns[sub] = (truncSpawns[sub] || 0) + 1;
|
|
2746
|
+
}
|
|
2747
|
+
}
|
|
2748
|
+
} catch {}
|
|
2652
2749
|
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:
|
|
2750
|
+
toolCounts: {}, cost: 0, mtime: stat.mtimeMs, size: stat.size, agentSpawns: truncSpawns, truncated: true });
|
|
2751
|
+
for (const [subType, count] of Object.entries(truncSpawns)) {
|
|
2752
|
+
const nodeId = 'agent::' + subType;
|
|
2753
|
+
if (!agentTypeNodes[subType]) {
|
|
2754
|
+
agentTypeNodes[subType] = true;
|
|
2755
|
+
nodes.push({ id: nodeId, type: 'agenttype', label: subType, totalSpawns: 0 });
|
|
2756
|
+
}
|
|
2757
|
+
const aNode = nodes.find(n => n.id === nodeId);
|
|
2758
|
+
if (aNode) aNode.totalSpawns = (aNode.totalSpawns || 0) + count;
|
|
2759
|
+
edges.push({ source: sid, target: nodeId, weight: count, label: String(count) });
|
|
2760
|
+
}
|
|
2654
2761
|
continue;
|
|
2655
2762
|
}
|
|
2656
2763
|
|
|
@@ -2663,7 +2770,12 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
2663
2770
|
const lines = raw.split('\n').filter(Boolean);
|
|
2664
2771
|
for (const line of lines) {
|
|
2665
2772
|
let e; try { e = JSON.parse(line); } catch { continue; }
|
|
2666
|
-
if (e.type === 'user')
|
|
2773
|
+
if (e.type === 'user') {
|
|
2774
|
+
// Only count actual human turns, not tool-result responses
|
|
2775
|
+
const ct = e.message?.content;
|
|
2776
|
+
const isToolResult = Array.isArray(ct) && ct.length > 0 && ct.every(b => b && b.type === 'tool_result');
|
|
2777
|
+
if (!isToolResult) turns++;
|
|
2778
|
+
}
|
|
2667
2779
|
if (e.type === 'assistant') {
|
|
2668
2780
|
for (const block of (e.message?.content || [])) {
|
|
2669
2781
|
if (!block || block.type !== 'tool_use') continue;
|
|
@@ -2674,8 +2786,8 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
2674
2786
|
agentSpawns[sub] = (agentSpawns[sub] || 0) + 1;
|
|
2675
2787
|
}
|
|
2676
2788
|
}
|
|
2789
|
+
if (e.message?.usage) totalCost += _sjCalcCost(e.message.model || '', e.message.usage);
|
|
2677
2790
|
}
|
|
2678
|
-
if (e.costUSD) totalCost += e.costUSD;
|
|
2679
2791
|
}
|
|
2680
2792
|
} catch {}
|
|
2681
2793
|
|
|
@@ -2733,8 +2845,13 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
2733
2845
|
try {
|
|
2734
2846
|
const qs = new URL(req.url, 'http://localhost').searchParams;
|
|
2735
2847
|
const dir = qs.get('dir') || projectDir || process.cwd();
|
|
2736
|
-
|
|
2737
|
-
|
|
2848
|
+
// Cap swarmId and agentId to prevent O(n×m) DoS: filter() compares
|
|
2849
|
+
// each event against the query string, so a megabyte-scale ID causes
|
|
2850
|
+
// O(events × m) string comparisons.
|
|
2851
|
+
const _rawSwarmId = qs.get('swarmId') || undefined;
|
|
2852
|
+
const _rawAgentId = qs.get('agentId') || undefined;
|
|
2853
|
+
const swarmId = typeof _rawSwarmId === 'string' ? _rawSwarmId.slice(0, 256) : undefined;
|
|
2854
|
+
const agentId = typeof _rawAgentId === 'string' ? _rawAgentId.slice(0, 256) : undefined;
|
|
2738
2855
|
const last = qs.get('last') ? parseInt(qs.get('last')) : undefined;
|
|
2739
2856
|
const events = collectSwarmEvents(path.resolve(dir), { swarmId, agentId, last });
|
|
2740
2857
|
res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', 'Cache-Control': 'no-cache' });
|
|
@@ -2778,47 +2895,71 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
2778
2895
|
if (req.method === 'GET' && url.startsWith('/api/token-usage')) {
|
|
2779
2896
|
try {
|
|
2780
2897
|
const qs = new URL(req.url, 'http://localhost').searchParams;
|
|
2781
|
-
const period = qs.get('period')
|
|
2898
|
+
const period = ['today','week','30days','month'].includes(qs.get('period')) ? qs.get('period') : 'today';
|
|
2782
2899
|
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,
|
|
2900
|
+
const trackerPath = path.join(dir, '.claude', 'helpers', 'token-tracker.cjs');
|
|
2901
|
+
const fallback = () => {
|
|
2902
|
+
const summary = (() => { try { return JSON.parse(fs.readFileSync(path.join(dir, '.monomind', 'metrics', 'token-summary.json'), 'utf8')); } catch { return {}; } })();
|
|
2903
|
+
res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', 'Cache-Control': 'no-cache' });
|
|
2904
|
+
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 };
|
|
2905
|
+
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
2906
|
};
|
|
2820
|
-
|
|
2821
|
-
|
|
2907
|
+
if (!fs.existsSync(trackerPath)) { fallback(); return; }
|
|
2908
|
+
try {
|
|
2909
|
+
const _req = createRequire(import.meta.url);
|
|
2910
|
+
const tracker = _req(trackerPath);
|
|
2911
|
+
const range = tracker.getDateRange(period);
|
|
2912
|
+
const projects = tracker.parseAllSessions(range.start, range.end);
|
|
2913
|
+
let totalCost = 0, totalIn = 0, totalOut = 0, totalCR = 0, totalCW = 0, totalCalls = 0;
|
|
2914
|
+
const modelBreakdown = {}, categoryBreakdown = {}, toolBreakdown = {}, mcpBreakdown = {};
|
|
2915
|
+
for (const p of projects) {
|
|
2916
|
+
totalCost += p.totalCost || 0;
|
|
2917
|
+
for (const s of (p.sessions || [])) {
|
|
2918
|
+
totalIn += s.totalInputTokens || 0;
|
|
2919
|
+
totalOut += s.totalOutputTokens || 0;
|
|
2920
|
+
totalCR += s.totalCacheRead || 0;
|
|
2921
|
+
totalCW += s.totalCacheWrite || 0;
|
|
2922
|
+
totalCalls += s.apiCalls || 0;
|
|
2923
|
+
for (const [mn, m] of Object.entries(s.modelBreakdown || {})) {
|
|
2924
|
+
if (!modelBreakdown[mn]) modelBreakdown[mn] = { calls: 0, cost: 0, tokens: 0 };
|
|
2925
|
+
modelBreakdown[mn].calls += m.calls || 0;
|
|
2926
|
+
modelBreakdown[mn].cost += m.cost || 0;
|
|
2927
|
+
modelBreakdown[mn].tokens += m.tokens || 0;
|
|
2928
|
+
}
|
|
2929
|
+
for (const [cat, c] of Object.entries(s.categoryBreakdown || {})) {
|
|
2930
|
+
if (!categoryBreakdown[cat]) categoryBreakdown[cat] = { turns: 0, cost: 0 };
|
|
2931
|
+
categoryBreakdown[cat].turns += c.turns || 0;
|
|
2932
|
+
categoryBreakdown[cat].cost += c.cost || 0;
|
|
2933
|
+
}
|
|
2934
|
+
for (const [tool, t] of Object.entries(s.toolBreakdown || {})) {
|
|
2935
|
+
if (!toolBreakdown[tool]) toolBreakdown[tool] = { calls: 0 };
|
|
2936
|
+
toolBreakdown[tool].calls += t.calls || 0;
|
|
2937
|
+
}
|
|
2938
|
+
for (const [srv, m] of Object.entries(s.mcpBreakdown || {})) {
|
|
2939
|
+
if (!mcpBreakdown[srv]) mcpBreakdown[srv] = { calls: 0 };
|
|
2940
|
+
mcpBreakdown[srv].calls += m.calls || 0;
|
|
2941
|
+
}
|
|
2942
|
+
}
|
|
2943
|
+
}
|
|
2944
|
+
// Build client-friendly arrays from breakdown dicts
|
|
2945
|
+
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);
|
|
2946
|
+
const categories = Object.entries(categoryBreakdown).map(([category, c]) => ({ category, turns: c.turns, cost: c.cost })).sort((a, b) => b.turns - a.turns);
|
|
2947
|
+
const tools = Object.entries(toolBreakdown).map(([tool, t]) => ({ tool, count: t.calls })).sort((a, b) => b.count - a.count);
|
|
2948
|
+
const mcpServers = Object.entries(mcpBreakdown).map(([server, m]) => ({ server, count: m.calls })).sort((a, b) => b.count - a.count);
|
|
2949
|
+
const projectRows = projects.map(p => ({ project: p.name || p.slug || p.dir || '?', cost: p.totalCost || 0 })).sort((a, b) => b.cost - a.cost);
|
|
2950
|
+
// Build rows array from sessions for per-session table
|
|
2951
|
+
const rows = [];
|
|
2952
|
+
for (const p of projects) {
|
|
2953
|
+
for (const s of (p.sessions || [])) {
|
|
2954
|
+
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) });
|
|
2955
|
+
}
|
|
2956
|
+
}
|
|
2957
|
+
rows.sort((a, b) => b.cost - a.cost);
|
|
2958
|
+
// Summary object matching client expectations
|
|
2959
|
+
const summary = { todayCost: totalCost, cost: totalCost, todayCalls: totalCalls, calls: totalCalls, totalTokens: totalIn + totalOut, totalTokensIn: totalIn, totalTokensOut: totalOut, cacheTokens: totalCR, modelCount: models.length };
|
|
2960
|
+
res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', 'Cache-Control': 'no-cache' });
|
|
2961
|
+
res.end(JSON.stringify({ summary, totalCost, totalCalls, totalIn, totalOut, totalCR, totalCW, rows, models, categories, tools, mcpServers, projects: projectRows, modelBreakdown, categoryBreakdown, toolBreakdown, mcpBreakdown, periodLabel: period }));
|
|
2962
|
+
} catch (e) { fallback(); }
|
|
2822
2963
|
} catch (err) {
|
|
2823
2964
|
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
2824
2965
|
res.end(JSON.stringify({ error: err.message }));
|
|
@@ -2972,7 +3113,7 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
2972
3113
|
const orgsDir = path.join(path.resolve(_orgsQs.get('dir') || projectDir || process.cwd()), '.monomind', 'orgs');
|
|
2973
3114
|
let orgs = [];
|
|
2974
3115
|
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$/;
|
|
3116
|
+
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
3117
|
const files = fs.readdirSync(orgsDir).filter(f => f.endsWith('.json') && !_sidecarSuffixRe.test(f));
|
|
2977
3118
|
// Read events file once, outside the per-org loop
|
|
2978
3119
|
let recentLines = [];
|
|
@@ -3003,7 +3144,7 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
3003
3144
|
const stopTs = lastStop ? (JSON.parse(lastStop).ts || 0) : 0;
|
|
3004
3145
|
running = startTs > stopTs;
|
|
3005
3146
|
}
|
|
3006
|
-
orgs.push({ name: cfg.name, goal: cfg.goal, roles: cfg.roles
|
|
3147
|
+
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
3148
|
} catch(_) {}
|
|
3008
3149
|
}
|
|
3009
3150
|
}
|
|
@@ -3013,12 +3154,65 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
3013
3154
|
return;
|
|
3014
3155
|
}
|
|
3015
3156
|
|
|
3157
|
+
// POST /api/orgs/:name/import — import an org config by name (orgs.html upload flow)
|
|
3158
|
+
if (req.method === 'POST' && /^\/api\/orgs\/[a-z0-9][a-z0-9_-]{0,63}\/import$/i.test(url)) {
|
|
3159
|
+
let body = '';
|
|
3160
|
+
req.on('data', c => { body += c; if (body.length > 2e6) req.destroy(); });
|
|
3161
|
+
req.on('end', () => {
|
|
3162
|
+
try {
|
|
3163
|
+
const urlParts = url.split('/');
|
|
3164
|
+
const orgName = decodeURIComponent(urlParts[3]);
|
|
3165
|
+
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; }
|
|
3166
|
+
const cfg = JSON.parse(body);
|
|
3167
|
+
const _importQs = new URL(req.url, 'http://localhost').searchParams;
|
|
3168
|
+
const dir = path.resolve(_importQs.get('dir') || projectDir || process.cwd());
|
|
3169
|
+
const orgsDir = path.join(dir, '.monomind', 'orgs');
|
|
3170
|
+
fs.mkdirSync(orgsDir, { recursive: true });
|
|
3171
|
+
const destFile = path.join(orgsDir, `${orgName}.json`);
|
|
3172
|
+
fs.writeFileSync(destFile, JSON.stringify({ ...cfg, name: orgName }, null, 2), 'utf8');
|
|
3173
|
+
res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
|
|
3174
|
+
res.end(JSON.stringify({ ok: true, name: orgName, file: destFile }));
|
|
3175
|
+
} catch (e) {
|
|
3176
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
3177
|
+
res.end(JSON.stringify({ error: e.message }));
|
|
3178
|
+
}
|
|
3179
|
+
});
|
|
3180
|
+
return;
|
|
3181
|
+
}
|
|
3182
|
+
|
|
3183
|
+
// POST /api/orgs — import / create org from JSON body
|
|
3184
|
+
if (req.method === 'POST' && url === '/api/orgs') {
|
|
3185
|
+
let body = '';
|
|
3186
|
+
req.on('data', c => { body += c; if (body.length > 2e6) req.destroy(); });
|
|
3187
|
+
req.on('end', () => {
|
|
3188
|
+
try {
|
|
3189
|
+
const cfg = JSON.parse(body);
|
|
3190
|
+
const qs = new URL(req.url, 'http://localhost').searchParams;
|
|
3191
|
+
const dir = qs.get('dir') || cfg.dir || projectDir || process.cwd();
|
|
3192
|
+
const name = (cfg.name || '').toLowerCase().replace(/[^a-z0-9_-]/g, '-').replace(/^-+|-+$/g, '').slice(0, 64);
|
|
3193
|
+
if (!name) { res.writeHead(400, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: 'Invalid org name' })); return; }
|
|
3194
|
+
const orgsDir = path.join(path.resolve(dir), '.monomind', 'orgs');
|
|
3195
|
+
fs.mkdirSync(orgsDir, { recursive: true });
|
|
3196
|
+
const destFile = path.join(orgsDir, `${name}.json`);
|
|
3197
|
+
const cleanCfg = Object.fromEntries(Object.entries({ ...cfg, name }).filter(([k]) => !k.startsWith('_')));
|
|
3198
|
+
fs.writeFileSync(destFile, JSON.stringify(cleanCfg, null, 2), 'utf8');
|
|
3199
|
+
res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
|
|
3200
|
+
res.end(JSON.stringify({ ok: true, name, file: destFile }));
|
|
3201
|
+
} catch (e) {
|
|
3202
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
3203
|
+
res.end(JSON.stringify({ error: e.message }));
|
|
3204
|
+
}
|
|
3205
|
+
});
|
|
3206
|
+
return;
|
|
3207
|
+
}
|
|
3208
|
+
|
|
3016
3209
|
// GET /api/orgs/:name — get specific org config (exact path: /api/orgs/<slug>)
|
|
3017
3210
|
if (req.method === 'GET' && /^\/api\/orgs\/[a-z0-9][a-z0-9_-]{0,63}$/i.test(url)) {
|
|
3018
3211
|
try {
|
|
3019
3212
|
const orgName = decodeURIComponent(url.slice('/api/orgs/'.length));
|
|
3020
3213
|
if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('Invalid org name'); return; }
|
|
3021
|
-
const
|
|
3214
|
+
const _orgsOneQs = new URL(req.url, 'http://localhost').searchParams;
|
|
3215
|
+
const f = path.join(path.resolve(_orgsOneQs.get('dir') || projectDir || process.cwd()), '.monomind', 'orgs', `${orgName}.json`);
|
|
3022
3216
|
if (!fs.existsSync(f)) { res.writeHead(404); res.end('{"error":"not found"}'); return; }
|
|
3023
3217
|
res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
|
|
3024
3218
|
res.end(fs.readFileSync(f, 'utf8'));
|
|
@@ -3050,8 +3244,17 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
3050
3244
|
const stopFile = path.join(orgsDir, '.stops', `${orgName}.stop`);
|
|
3051
3245
|
const running = !fs.existsSync(stopFile) && Object.values(state.agents || {}).some(a => a.status === 'running');
|
|
3052
3246
|
|
|
3247
|
+
// Read real tasks from the task store and group by status column
|
|
3248
|
+
const taskStoreData = readJsonSafe(path.join(d, '.monomind', 'tasks', 'store.json'));
|
|
3249
|
+
const allTasks = taskStoreData ? Object.values(taskStoreData.tasks || {}) : [];
|
|
3250
|
+
const tasks = {
|
|
3251
|
+
todo: allTasks.filter(t => t.status === 'pending').map(t => ({ id: t.taskId, description: t.description, status: 'todo', ts: t.createdAt })),
|
|
3252
|
+
doing: allTasks.filter(t => t.status === 'in_progress').map(t => ({ id: t.taskId, description: t.description, status: 'doing', ts: t.startedAt || t.createdAt })),
|
|
3253
|
+
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 })),
|
|
3254
|
+
};
|
|
3255
|
+
|
|
3053
3256
|
const result = { config, state, goals: goalsData.goals, routines: routinesData.routines,
|
|
3054
|
-
approvals: approvalsData.approvals, running, tasks
|
|
3257
|
+
approvals: approvalsData.approvals, running, tasks };
|
|
3055
3258
|
|
|
3056
3259
|
res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
|
|
3057
3260
|
res.end(JSON.stringify(result));
|
|
@@ -3115,7 +3318,8 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
3115
3318
|
const parts = url.split('/');
|
|
3116
3319
|
const orgName = decodeURIComponent(parts[3]);
|
|
3117
3320
|
if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('[]'); return; }
|
|
3118
|
-
const
|
|
3321
|
+
const _projsQs = new URL(req.url, 'http://localhost').searchParams;
|
|
3322
|
+
const d = path.resolve(_projsQs.get('dir') || projectDir || process.cwd());
|
|
3119
3323
|
const projFile = path.join(d, '.monomind', 'orgs', `${orgName}-projects.json`);
|
|
3120
3324
|
if (!fs.existsSync(projFile)) { res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' }); res.end('[]'); return; }
|
|
3121
3325
|
const data = JSON.parse(fs.readFileSync(projFile, 'utf8'));
|
|
@@ -3131,7 +3335,8 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
3131
3335
|
const parts = url.split('/');
|
|
3132
3336
|
const orgName = decodeURIComponent(parts[3]);
|
|
3133
3337
|
if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('{}'); return; }
|
|
3134
|
-
const
|
|
3338
|
+
const _membersQs = new URL(req.url, 'http://localhost').searchParams;
|
|
3339
|
+
const d = path.resolve(_membersQs.get('dir') || projectDir || process.cwd());
|
|
3135
3340
|
const membersFile = path.join(d, '.monomind', 'orgs', `${orgName}-members.json`);
|
|
3136
3341
|
if (!fs.existsSync(membersFile)) {
|
|
3137
3342
|
res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
|
|
@@ -3151,7 +3356,8 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
3151
3356
|
const parts = url.split('/');
|
|
3152
3357
|
const orgName = decodeURIComponent(parts[3]);
|
|
3153
3358
|
if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('{}'); return; }
|
|
3154
|
-
const
|
|
3359
|
+
const _adaptersQs = new URL(req.url, 'http://localhost').searchParams;
|
|
3360
|
+
const d = path.resolve(_adaptersQs.get('dir') || projectDir || process.cwd());
|
|
3155
3361
|
const adaptersFile = path.join(d, '.monomind', 'orgs', `${orgName}-adapters.json`);
|
|
3156
3362
|
if (!fs.existsSync(adaptersFile)) {
|
|
3157
3363
|
// Return defaults derived from org config if available
|
|
@@ -3179,7 +3385,8 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
3179
3385
|
const parts = url.split('/');
|
|
3180
3386
|
const orgName = decodeURIComponent(parts[3]);
|
|
3181
3387
|
if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('{}'); return; }
|
|
3182
|
-
const
|
|
3388
|
+
const _skillsQs = new URL(req.url, 'http://localhost').searchParams;
|
|
3389
|
+
const d = path.resolve(_skillsQs.get('dir') || projectDir || process.cwd());
|
|
3183
3390
|
const skillsDir = path.join(d, '.claude', 'skills');
|
|
3184
3391
|
const orgFile = path.join(d, '.monomind', 'orgs', `${orgName}.json`);
|
|
3185
3392
|
|
|
@@ -3286,13 +3493,13 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
3286
3493
|
// GET /api/org/:name/search?q=<query> — fuzzy search across org data
|
|
3287
3494
|
if (req.method === 'GET' && /^\/api\/org\/[a-z0-9][a-z0-9_-]{0,63}\/search(\?.*)?$/i.test(url)) {
|
|
3288
3495
|
try {
|
|
3289
|
-
const urlObj = new URL(`http://x${url}`);
|
|
3496
|
+
const urlObj = new URL(`http://x${req.url}`);
|
|
3290
3497
|
const orgName = decodeURIComponent(urlObj.pathname.split('/')[3]);
|
|
3291
3498
|
if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('{}'); return; }
|
|
3292
3499
|
const q = (urlObj.searchParams.get('q') || '').toLowerCase().trim();
|
|
3293
3500
|
if (!q || q.length < 2) { res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' }); res.end('{"hits":[]}'); return; }
|
|
3294
3501
|
|
|
3295
|
-
const d = projectDir || process.cwd();
|
|
3502
|
+
const d = path.resolve(urlObj.searchParams.get('dir') || projectDir || process.cwd());
|
|
3296
3503
|
const orgsDir = path.join(d, '.monomind', 'orgs');
|
|
3297
3504
|
const readJ = (f) => { try { return JSON.parse(fs.readFileSync(f, 'utf8')); } catch(_) { return null; } };
|
|
3298
3505
|
|
|
@@ -3310,8 +3517,8 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
3310
3517
|
// Goals
|
|
3311
3518
|
const goals = readJ(path.join(orgsDir, `${orgName}-goals.json`));
|
|
3312
3519
|
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' });
|
|
3520
|
+
if (match(g.title) || match(g.text) || match(g.goal) || match(g.description)) {
|
|
3521
|
+
hits.push({ type: 'goal', id: g.id, title: g.title || g.text || g.goal, meta: g.status || 'open' });
|
|
3315
3522
|
}
|
|
3316
3523
|
}
|
|
3317
3524
|
|
|
@@ -3339,6 +3546,14 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
3339
3546
|
}
|
|
3340
3547
|
}
|
|
3341
3548
|
|
|
3549
|
+
// Issues
|
|
3550
|
+
const issuesData = readJ(path.join(orgsDir, `${orgName}-issues.json`));
|
|
3551
|
+
for (const i of (issuesData?.issues || [])) {
|
|
3552
|
+
if (match(i.title) || match(i.description) || match(i.slug)) {
|
|
3553
|
+
hits.push({ type: 'issue', id: i.id || i.slug, title: i.title || i.slug, meta: i.status || 'open' });
|
|
3554
|
+
}
|
|
3555
|
+
}
|
|
3556
|
+
|
|
3342
3557
|
// Recent activity events
|
|
3343
3558
|
const eventsFile = path.join(d, 'data', 'mastermind-events.jsonl');
|
|
3344
3559
|
if (fs.existsSync(eventsFile)) {
|
|
@@ -3365,13 +3580,17 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
3365
3580
|
try {
|
|
3366
3581
|
const orgName = decodeURIComponent(url.split('/')[3]);
|
|
3367
3582
|
if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('Invalid org name'); return; }
|
|
3368
|
-
const
|
|
3583
|
+
const _issuesQs = new URL(req.url, 'http://localhost').searchParams;
|
|
3584
|
+
const _issuesDir = path.resolve(_issuesQs.get('dir') || projectDir || process.cwd());
|
|
3585
|
+
const issuesPath = path.join(_issuesDir, '.monomind', 'orgs', `${orgName}-issues.json`);
|
|
3369
3586
|
let payload = { issues: [] };
|
|
3370
3587
|
try {
|
|
3371
3588
|
const raw = JSON.parse(fs.readFileSync(issuesPath, 'utf8'));
|
|
3372
3589
|
payload.issues = (raw.issues || []).map(i => ({
|
|
3373
|
-
id: i.id, slug: i.slug, title: i.title,
|
|
3590
|
+
id: i.id, slug: i.slug, title: i.title, description: i.description || null,
|
|
3591
|
+
status: i.status || 'open',
|
|
3374
3592
|
priority: i.priority || 'medium', assignee_id: i.assignee_id || null,
|
|
3593
|
+
assignee: i.assignee || i.assignee_id || null,
|
|
3375
3594
|
project_id: i.project_id || null, parent_id: i.parent_id || null,
|
|
3376
3595
|
created_at: i.created_at, updated_at: i.updated_at
|
|
3377
3596
|
}));
|
|
@@ -3422,11 +3641,12 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
3422
3641
|
try {
|
|
3423
3642
|
const actPath = path.join(base, `${orgName}-activity.jsonl`);
|
|
3424
3643
|
const lines = fs.readFileSync(actPath, 'utf8').split('\n').filter(Boolean);
|
|
3425
|
-
const
|
|
3644
|
+
const cutoffMs = Date.now() - 7 * 24 * 60 * 60 * 1000;
|
|
3426
3645
|
lines.forEach(line => {
|
|
3427
3646
|
try {
|
|
3428
3647
|
const ev = JSON.parse(line);
|
|
3429
|
-
|
|
3648
|
+
const evMs = typeof ev.ts === 'number' ? ev.ts : (ev.ts ? Date.parse(ev.ts) : 0);
|
|
3649
|
+
if (!evMs || evMs < cutoffMs) return;
|
|
3430
3650
|
totalRuns++;
|
|
3431
3651
|
if (ev.type && ev.type.includes('complete')) successRuns++;
|
|
3432
3652
|
} catch(_) {}
|
|
@@ -3460,7 +3680,8 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
3460
3680
|
try {
|
|
3461
3681
|
const orgName = decodeURIComponent(url.split('/')[3]);
|
|
3462
3682
|
if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('Invalid org name'); return; }
|
|
3463
|
-
const
|
|
3683
|
+
const _envsQs = new URL(req.url, 'http://localhost').searchParams;
|
|
3684
|
+
const envsPath = path.join(path.resolve(_envsQs.get('dir') || projectDir || process.cwd()), '.monomind', 'orgs', `${orgName}-environments.json`);
|
|
3464
3685
|
let payload = { environments: [], default_env: null };
|
|
3465
3686
|
try {
|
|
3466
3687
|
const raw = JSON.parse(fs.readFileSync(envsPath, 'utf8'));
|
|
@@ -3486,7 +3707,8 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
3486
3707
|
try {
|
|
3487
3708
|
const orgName = decodeURIComponent(url.split('/')[3]);
|
|
3488
3709
|
if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('Invalid org name'); return; }
|
|
3489
|
-
const
|
|
3710
|
+
const _wsQs = new URL(req.url, 'http://localhost').searchParams;
|
|
3711
|
+
const base = path.join(path.resolve(_wsQs.get('dir') || projectDir || process.cwd()), '.monomind', 'orgs');
|
|
3490
3712
|
let payload = { workspaces: [] };
|
|
3491
3713
|
try {
|
|
3492
3714
|
const wsRaw = JSON.parse(fs.readFileSync(path.join(base, `${orgName}-workspaces.json`), 'utf8'));
|
|
@@ -3509,17 +3731,18 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
3509
3731
|
}
|
|
3510
3732
|
|
|
3511
3733
|
// 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
|
|
3734
|
+
if (req.method === 'GET' && url.match(/^\/api\/org\/[a-z0-9][a-z0-9_-]{0,63}\/invites(\?.*)?$/i)) {
|
|
3513
3735
|
try {
|
|
3514
|
-
const orgName = decodeURIComponent(url.split('/')[3]);
|
|
3736
|
+
const orgName = decodeURIComponent(url.split('/')[3].split('?')[0]);
|
|
3515
3737
|
if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('Invalid org name'); return; }
|
|
3516
|
-
const
|
|
3738
|
+
const _invitesQs = new URL(req.url, 'http://localhost').searchParams;
|
|
3739
|
+
const base = path.join(path.resolve(_invitesQs.get('dir') || projectDir || process.cwd()), '.monomind', 'orgs');
|
|
3517
3740
|
let payload = { invites: [], join_requests: [] };
|
|
3518
3741
|
try {
|
|
3519
3742
|
const raw = JSON.parse(fs.readFileSync(path.join(base, `${orgName}-members.json`), 'utf8'));
|
|
3520
3743
|
const all = raw.join_requests || [];
|
|
3521
3744
|
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 }));
|
|
3745
|
+
.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
3746
|
payload.join_requests = all.filter(r => r.type !== 'invite' && r.status === 'pending_approval')
|
|
3524
3747
|
.map(r => ({ id: r.id, requestType: r.requestType || 'human', role: r.role || 'viewer', createdAt: r.createdAt || null, message: r.message || '' }));
|
|
3525
3748
|
} catch(_) { /* members file missing */ }
|
|
@@ -3534,7 +3757,8 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
3534
3757
|
try {
|
|
3535
3758
|
const orgName = decodeURIComponent(url.split('/')[3]);
|
|
3536
3759
|
if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('Invalid org name'); return; }
|
|
3537
|
-
const
|
|
3760
|
+
const _pluginsQs = new URL(req.url, 'http://localhost').searchParams;
|
|
3761
|
+
const base = path.join(path.resolve(_pluginsQs.get('dir') || projectDir || process.cwd()), '.monomind');
|
|
3538
3762
|
let plugins = [];
|
|
3539
3763
|
try {
|
|
3540
3764
|
const reg = JSON.parse(fs.readFileSync(path.join(base, 'plugins', 'registry.json'), 'utf8'));
|
|
@@ -3572,7 +3796,8 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
3572
3796
|
try {
|
|
3573
3797
|
const orgName = decodeURIComponent(url.split('/')[3]);
|
|
3574
3798
|
if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('Invalid org name'); return; }
|
|
3575
|
-
const
|
|
3799
|
+
const _myIssuesQs = new URL(req.url, 'http://localhost').searchParams;
|
|
3800
|
+
const base = path.join(path.resolve(_myIssuesQs.get('dir') || projectDir || process.cwd()), '.monomind', 'orgs');
|
|
3576
3801
|
let payload = { issues: [] };
|
|
3577
3802
|
try {
|
|
3578
3803
|
const raw = JSON.parse(fs.readFileSync(path.join(base, `${orgName}-issues.json`), 'utf8'));
|
|
@@ -3582,12 +3807,15 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
3582
3807
|
.map(i => ({
|
|
3583
3808
|
id: i.id,
|
|
3584
3809
|
title: i.title || null,
|
|
3810
|
+
description: i.description || null,
|
|
3585
3811
|
status: i.status || 'open',
|
|
3586
3812
|
priority: i.priority || 'medium',
|
|
3587
3813
|
assigneeId: i.assigneeId || i.assigned_to || null,
|
|
3588
3814
|
projectId: i.projectId || i.project_id || null,
|
|
3589
3815
|
createdAt: i.createdAt || null,
|
|
3590
3816
|
lastActivityAt: i.lastActivityAt || null,
|
|
3817
|
+
updated_at: i.updated_at || i.lastActivityAt || i.updatedAt || i.ts || null,
|
|
3818
|
+
ts: i.ts || i.updated_at || i.lastActivityAt || null,
|
|
3591
3819
|
}));
|
|
3592
3820
|
} catch(_) { /* issues file missing */ }
|
|
3593
3821
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
@@ -3601,7 +3829,9 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
3601
3829
|
try {
|
|
3602
3830
|
const orgName = decodeURIComponent(url.split('/')[3]);
|
|
3603
3831
|
if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('Invalid org name'); return; }
|
|
3604
|
-
const
|
|
3832
|
+
const _agentsQs = new URL(req.url, 'http://localhost').searchParams;
|
|
3833
|
+
const d = path.resolve(_agentsQs.get('dir') || projectDir || process.cwd());
|
|
3834
|
+
const base = path.join(d, '.monomind', 'orgs');
|
|
3605
3835
|
const readJsonSafe = (f) => { try { return JSON.parse(fs.readFileSync(f, 'utf8')); } catch(_) { return null; } };
|
|
3606
3836
|
const config = readJsonSafe(path.join(base, `${orgName}.json`)) || {};
|
|
3607
3837
|
const stateData = readJsonSafe(path.join(base, `${orgName}-state.json`)) || {};
|
|
@@ -3614,8 +3844,8 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
3614
3844
|
return {
|
|
3615
3845
|
id: r.id,
|
|
3616
3846
|
title: r.title || r.id,
|
|
3617
|
-
adapterType:
|
|
3618
|
-
adapterModel: (r.adapter && r.adapter.model) || null,
|
|
3847
|
+
adapterType: r.agent_type || r.type || null,
|
|
3848
|
+
adapterModel: (r.adapter_config && r.adapter_config.model) || (r.adapter && r.adapter.model) || null,
|
|
3619
3849
|
governance: r.governance || null,
|
|
3620
3850
|
reportsTo: r.reports_to || null,
|
|
3621
3851
|
status: s.status || 'idle',
|
|
@@ -3636,7 +3866,8 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
3636
3866
|
try {
|
|
3637
3867
|
const orgName = decodeURIComponent(url.split('/')[3].split('?')[0]);
|
|
3638
3868
|
if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('Invalid org name'); return; }
|
|
3639
|
-
const
|
|
3869
|
+
const _approvalsQs = new URL(req.url, 'http://localhost').searchParams;
|
|
3870
|
+
const base = path.join(path.resolve(_approvalsQs.get('dir') || projectDir || process.cwd()), '.monomind', 'orgs');
|
|
3640
3871
|
const readJsonSafe = (f) => { try { return JSON.parse(fs.readFileSync(f, 'utf8')); } catch(_) { return null; } };
|
|
3641
3872
|
const data = readJsonSafe(path.join(base, `${orgName}-approvals.json`)) || { approvals: [] };
|
|
3642
3873
|
const approvals = (data.approvals || [])
|
|
@@ -3671,7 +3902,7 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
3671
3902
|
// Body: { action: "approve" | "reject" | "revision_requested" }
|
|
3672
3903
|
if (req.method === 'POST' && url.match(/^\/api\/org\/[a-z0-9][a-z0-9_-]{0,63}\/approvals\/[^/]+$/i)) {
|
|
3673
3904
|
let body = '';
|
|
3674
|
-
for await (const chunk of req) body += chunk;
|
|
3905
|
+
for await (const chunk of req) { body += chunk; if (body.length > 2097152) { req.destroy(); break; } }
|
|
3675
3906
|
try {
|
|
3676
3907
|
const parts = url.split('/');
|
|
3677
3908
|
const orgName = decodeURIComponent(parts[3]);
|
|
@@ -3683,7 +3914,8 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
3683
3914
|
if (!['approve', 'reject', 'revision_requested'].includes(action)) {
|
|
3684
3915
|
res.writeHead(400); res.end('{"error":"action must be approve, reject, or revision_requested"}'); return;
|
|
3685
3916
|
}
|
|
3686
|
-
const
|
|
3917
|
+
const _postApprovalsQs = new URL(req.url, 'http://localhost').searchParams;
|
|
3918
|
+
const base = path.join(path.resolve(_postApprovalsQs.get('dir') || projectDir || process.cwd()), '.monomind', 'orgs');
|
|
3687
3919
|
const approvalsFile = path.join(base, `${orgName}-approvals.json`);
|
|
3688
3920
|
let data = { approvals: [] };
|
|
3689
3921
|
try { data = JSON.parse(fs.readFileSync(approvalsFile, 'utf8')); } catch(_) {}
|
|
@@ -3701,7 +3933,7 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
3701
3933
|
fs.renameSync(tmp, approvalsFile);
|
|
3702
3934
|
// Emit org:approval:resolved event so boss agent unblocks
|
|
3703
3935
|
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(_) {}
|
|
3936
|
+
try { fs.appendFileSync(path.join(path.resolve(_postApprovalsQs.get('dir') || projectDir || process.cwd()), 'data', 'mastermind-events.jsonl'), JSON.stringify(event) + '\n'); } catch(_) {}
|
|
3705
3937
|
const msg = `data: ${JSON.stringify(event)}\n\n`;
|
|
3706
3938
|
for (const c of mmSseClients) { try { c.write(msg); } catch(_) { mmSseClients.delete(c); } }
|
|
3707
3939
|
res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
|
|
@@ -3715,7 +3947,8 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
3715
3947
|
try {
|
|
3716
3948
|
const orgName = decodeURIComponent(url.split('/')[3]);
|
|
3717
3949
|
if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('Invalid org name'); return; }
|
|
3718
|
-
const
|
|
3950
|
+
const _secretsQs = new URL(req.url, 'http://localhost').searchParams;
|
|
3951
|
+
const base = path.join(path.resolve(_secretsQs.get('dir') || projectDir || process.cwd()), '.monomind', 'orgs');
|
|
3719
3952
|
const secretsDir = path.join(base, '.secrets');
|
|
3720
3953
|
const readJsonSafe = (f) => { try { return JSON.parse(fs.readFileSync(f, 'utf8')); } catch(_) { return null; } };
|
|
3721
3954
|
// Read secrets index — NEVER expose actual values
|
|
@@ -3723,6 +3956,7 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
3723
3956
|
const data = readJsonSafe(indexFile) || { secrets: [] };
|
|
3724
3957
|
const secrets = (data.secrets || []).map(s => ({
|
|
3725
3958
|
name: s.name,
|
|
3959
|
+
purpose: s.purpose || null,
|
|
3726
3960
|
maskedRef: s.maskedRef || `${(s.name||'').substring(0,4)}***`,
|
|
3727
3961
|
status: s.status || 'active',
|
|
3728
3962
|
createdAt: s.createdAt || null,
|
|
@@ -3742,7 +3976,8 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
3742
3976
|
try {
|
|
3743
3977
|
const orgName = decodeURIComponent(url.split('/')[3]);
|
|
3744
3978
|
if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('Invalid org name'); return; }
|
|
3745
|
-
const
|
|
3979
|
+
const _budgetsQs = new URL(req.url, 'http://localhost').searchParams;
|
|
3980
|
+
const base = path.join(path.resolve(_budgetsQs.get('dir') || projectDir || process.cwd()), '.monomind', 'orgs');
|
|
3746
3981
|
let budgetData = { org_budget: {}, agent_budgets: {}, period: 'monthly', currency: 'USD' };
|
|
3747
3982
|
try { budgetData = JSON.parse(fs.readFileSync(path.join(base, `${orgName}-budgets.json`), 'utf8')); } catch(_) {}
|
|
3748
3983
|
// Enrich with per-agent spend from state file.
|
|
@@ -3785,12 +4020,17 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
3785
4020
|
try {
|
|
3786
4021
|
const orgName = decodeURIComponent(url.split('/')[3]);
|
|
3787
4022
|
if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('Invalid org name'); return; }
|
|
3788
|
-
const
|
|
4023
|
+
const _threadsQs = new URL(req.url, 'http://localhost').searchParams;
|
|
4024
|
+
const threadsFile = path.join(path.resolve(_threadsQs.get('dir') || projectDir || process.cwd()), '.monomind', 'orgs', `${orgName}-threads.jsonl`);
|
|
3789
4025
|
let threads = [];
|
|
3790
4026
|
try {
|
|
3791
4027
|
const lines = fs.readFileSync(threadsFile, 'utf8').split('\n').filter(l => l.trim());
|
|
3792
4028
|
threads = lines.map(l => { try { return JSON.parse(l); } catch(_) { return null; } }).filter(Boolean);
|
|
3793
|
-
threads = threads.filter(t => t.type === 'thread' || !t.type)
|
|
4029
|
+
threads = threads.filter(t => t.type === 'thread' || !t.type).map(t => ({
|
|
4030
|
+
...t,
|
|
4031
|
+
author: t.author || t.authorName || t.createdBy || t.authorId || null,
|
|
4032
|
+
messageCount: t.messageCount != null ? t.messageCount : (Array.isArray(t.messages) ? t.messages.length : (typeof t.messages === 'number' ? t.messages : null)),
|
|
4033
|
+
}));
|
|
3794
4034
|
} catch(_) {}
|
|
3795
4035
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
3796
4036
|
res.end(JSON.stringify({ threads }));
|
|
@@ -3804,7 +4044,8 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
3804
4044
|
try {
|
|
3805
4045
|
const orgName = decodeURIComponent(url.split('/')[3].split('?')[0]);
|
|
3806
4046
|
if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('Invalid org name'); return; }
|
|
3807
|
-
const
|
|
4047
|
+
const _joinQs = new URL(req.url, 'http://localhost').searchParams;
|
|
4048
|
+
const joinFile = path.join(path.resolve(_joinQs.get('dir') || projectDir || process.cwd()), '.monomind', 'orgs', `${orgName}-join-requests.json`);
|
|
3808
4049
|
let requests = [];
|
|
3809
4050
|
try {
|
|
3810
4051
|
const raw = fs.readFileSync(joinFile, 'utf8');
|
|
@@ -3831,7 +4072,8 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
3831
4072
|
try {
|
|
3832
4073
|
const orgName = decodeURIComponent(url.split('/')[3]);
|
|
3833
4074
|
if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('Invalid org name'); return; }
|
|
3834
|
-
const
|
|
4075
|
+
const _goalsQs = new URL(req.url, 'http://localhost').searchParams;
|
|
4076
|
+
const goalsFile = path.join(path.resolve(_goalsQs.get('dir') || projectDir || process.cwd()), '.monomind', 'orgs', `${orgName}-goals.json`);
|
|
3835
4077
|
let data = { goals: [] };
|
|
3836
4078
|
try { data = JSON.parse(fs.readFileSync(goalsFile, 'utf8')); } catch(_) {}
|
|
3837
4079
|
res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
|
|
@@ -3845,7 +4087,8 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
3845
4087
|
try {
|
|
3846
4088
|
const orgName = decodeURIComponent(url.split('/')[3]);
|
|
3847
4089
|
if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('Invalid org name'); return; }
|
|
3848
|
-
const
|
|
4090
|
+
const _routinesQs = new URL(req.url, 'http://localhost').searchParams;
|
|
4091
|
+
const routinesFile = path.join(path.resolve(_routinesQs.get('dir') || projectDir || process.cwd()), '.monomind', 'orgs', `${orgName}-routines.json`);
|
|
3849
4092
|
let data = { routines: [] };
|
|
3850
4093
|
try { data = JSON.parse(fs.readFileSync(routinesFile, 'utf8')); } catch(_) {}
|
|
3851
4094
|
res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
|
|
@@ -3858,13 +4101,14 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
3858
4101
|
// Body: { goals: [{id, title, description, status, priority, assignee_id, created_at}] }
|
|
3859
4102
|
if (req.method === 'POST' && url.match(/^\/api\/org\/[a-z0-9][a-z0-9_-]{0,63}\/goals$/i)) {
|
|
3860
4103
|
let body = '';
|
|
3861
|
-
for await (const chunk of req) body += chunk;
|
|
4104
|
+
for await (const chunk of req) { body += chunk; if (body.length > 2097152) { req.destroy(); break; } }
|
|
3862
4105
|
try {
|
|
3863
4106
|
const orgName = decodeURIComponent(url.split('/')[3]);
|
|
3864
4107
|
if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('Invalid org name'); return; }
|
|
3865
4108
|
const parsed = JSON.parse(body);
|
|
3866
4109
|
if (!parsed || !Array.isArray(parsed.goals)) { res.writeHead(400); res.end('{"error":"goals array required"}'); return; }
|
|
3867
|
-
const
|
|
4110
|
+
const _postGoalsQs = new URL(req.url, 'http://localhost').searchParams;
|
|
4111
|
+
const goalsFile = path.join(path.resolve(_postGoalsQs.get('dir') || projectDir || process.cwd()), '.monomind', 'orgs', `${orgName}-goals.json`);
|
|
3868
4112
|
const tmp = `${goalsFile}.tmp`;
|
|
3869
4113
|
const payload = { org: orgName, updated_at: new Date().toISOString(), goals: parsed.goals };
|
|
3870
4114
|
fs.writeFileSync(tmp, JSON.stringify(payload, null, 2), 'utf-8');
|
|
@@ -3879,13 +4123,14 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
3879
4123
|
// Body: { routines: [{name, description, schedule, enabled, last_run, next_run}] }
|
|
3880
4124
|
if (req.method === 'POST' && url.match(/^\/api\/org\/[a-z0-9][a-z0-9_-]{0,63}\/routines$/i)) {
|
|
3881
4125
|
let body = '';
|
|
3882
|
-
for await (const chunk of req) body += chunk;
|
|
4126
|
+
for await (const chunk of req) { body += chunk; if (body.length > 2097152) { req.destroy(); break; } }
|
|
3883
4127
|
try {
|
|
3884
4128
|
const orgName = decodeURIComponent(url.split('/')[3]);
|
|
3885
4129
|
if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('Invalid org name'); return; }
|
|
3886
4130
|
const parsed = JSON.parse(body);
|
|
3887
4131
|
if (!parsed || !Array.isArray(parsed.routines)) { res.writeHead(400); res.end('{"error":"routines array required"}'); return; }
|
|
3888
|
-
const
|
|
4132
|
+
const _postRoutinesQs = new URL(req.url, 'http://localhost').searchParams;
|
|
4133
|
+
const routinesFile = path.join(path.resolve(_postRoutinesQs.get('dir') || projectDir || process.cwd()), '.monomind', 'orgs', `${orgName}-routines.json`);
|
|
3889
4134
|
const tmp = `${routinesFile}.tmp`;
|
|
3890
4135
|
const payload = { org: orgName, updated_at: new Date().toISOString(), routines: parsed.routines };
|
|
3891
4136
|
fs.writeFileSync(tmp, JSON.stringify(payload, null, 2), 'utf-8');
|
|
@@ -3896,16 +4141,95 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
3896
4141
|
return;
|
|
3897
4142
|
}
|
|
3898
4143
|
|
|
3899
|
-
//
|
|
3900
|
-
if (req.method === '
|
|
4144
|
+
// GET /api/org/:name/files — all files related to an org
|
|
4145
|
+
if (req.method === 'GET' && url.match(/^\/api\/org\/[a-z0-9][a-z0-9_-]{0,63}\/files$/i)) {
|
|
3901
4146
|
try {
|
|
3902
4147
|
const orgName = decodeURIComponent(url.split('/')[3]);
|
|
4148
|
+
if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('{"error":"Invalid org name"}'); return; }
|
|
4149
|
+
const _filesQs = new URL(req.url, 'http://localhost').searchParams;
|
|
4150
|
+
const d = path.resolve(_filesQs.get('dir') || projectDir || process.cwd());
|
|
4151
|
+
const orgsDir = path.join(d, '.monomind', 'orgs');
|
|
4152
|
+
const files = [];
|
|
4153
|
+
const seen = new Set();
|
|
4154
|
+
const addFile = (fp, type) => {
|
|
4155
|
+
if (seen.has(fp)) return; seen.add(fp);
|
|
4156
|
+
try { const st = fs.statSync(fp); files.push({ name: path.basename(fp), path: fp, type, size: st.size, mtime: st.mtime.toISOString() }); } catch (_) {}
|
|
4157
|
+
};
|
|
4158
|
+
addFile(path.join(orgsDir, orgName + '.json'), 'config');
|
|
4159
|
+
for (const s of ['-state','-approvals','-goals','-routines','-projects','-members','-issues','-threads','-budgets']) {
|
|
4160
|
+
const fp = path.join(orgsDir, orgName + s + '.json');
|
|
4161
|
+
if (fs.existsSync(fp)) addFile(fp, s.slice(1));
|
|
4162
|
+
}
|
|
4163
|
+
const walkDir = (dir, depth) => {
|
|
4164
|
+
if (depth > 3) return;
|
|
4165
|
+
let entries; try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch (_) { return; }
|
|
4166
|
+
for (const e of entries) {
|
|
4167
|
+
if (e.name.startsWith('.')) continue;
|
|
4168
|
+
const fp = path.join(dir, e.name);
|
|
4169
|
+
if (e.isDirectory()) walkDir(fp, depth + 1);
|
|
4170
|
+
else addFile(fp, 'generated');
|
|
4171
|
+
}
|
|
4172
|
+
};
|
|
4173
|
+
const orgWorkDir = path.join(orgsDir, orgName);
|
|
4174
|
+
if (fs.existsSync(orgWorkDir)) walkDir(orgWorkDir, 0);
|
|
4175
|
+
let orgCfg = null;
|
|
4176
|
+
try { orgCfg = JSON.parse(fs.readFileSync(path.join(orgsDir, orgName + '.json'), 'utf8')); } catch (_) {}
|
|
4177
|
+
if (orgCfg && Array.isArray(orgCfg.roles)) {
|
|
4178
|
+
const agentsDir = path.join(d, '.claude', 'agents');
|
|
4179
|
+
const walkAgents = (dir) => {
|
|
4180
|
+
let entries; try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch (_) { return; }
|
|
4181
|
+
for (const e of entries) {
|
|
4182
|
+
if (e.isDirectory()) { walkAgents(path.join(dir, e.name)); continue; }
|
|
4183
|
+
if (!e.name.endsWith('.md')) continue;
|
|
4184
|
+
const fp = path.join(dir, e.name);
|
|
4185
|
+
const base = e.name.replace('.md', '').toLowerCase();
|
|
4186
|
+
if (orgCfg.roles.some(r => base === (r.id||'').toLowerCase() || base === (r.agent_type||'').toLowerCase() || (r.instructions_file||'').endsWith(e.name))) addFile(fp, 'agent-definition');
|
|
4187
|
+
}
|
|
4188
|
+
};
|
|
4189
|
+
if (fs.existsSync(agentsDir)) walkAgents(agentsDir);
|
|
4190
|
+
}
|
|
4191
|
+
files.sort((a, b) => new Date(b.mtime) - new Date(a.mtime));
|
|
4192
|
+
res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', 'Cache-Control': 'no-cache' });
|
|
4193
|
+
res.end(JSON.stringify(files));
|
|
4194
|
+
} catch (e) { res.writeHead(500); res.end(JSON.stringify({ error: e.message })); }
|
|
4195
|
+
return;
|
|
4196
|
+
}
|
|
4197
|
+
|
|
4198
|
+
// GET /api/file-content — return raw text content of a .monomind file
|
|
4199
|
+
if (req.method === 'GET' && url === '/api/file-content') {
|
|
4200
|
+
try {
|
|
4201
|
+
const _fcQs = new URL(req.url, 'http://localhost').searchParams;
|
|
4202
|
+
const rawPath = _fcQs.get('path');
|
|
4203
|
+
const baseDir = path.resolve(_fcQs.get('dir') || projectDir || process.cwd());
|
|
4204
|
+
if (!rawPath) { res.writeHead(400); res.end('Missing path'); return; }
|
|
4205
|
+
const resolved = path.resolve(rawPath);
|
|
4206
|
+
// Security: must be inside .monomind of the project dir
|
|
4207
|
+
const monomindDir = path.join(baseDir, '.monomind');
|
|
4208
|
+
if (!resolved.startsWith(monomindDir + path.sep) && resolved !== monomindDir) {
|
|
4209
|
+
res.writeHead(403); res.end('Forbidden'); return;
|
|
4210
|
+
}
|
|
4211
|
+
if (!fs.existsSync(resolved)) { res.writeHead(404); res.end('Not found'); return; }
|
|
4212
|
+
const stat = fs.statSync(resolved);
|
|
4213
|
+
if (!stat.isFile()) { res.writeHead(400); res.end('Not a file'); return; }
|
|
4214
|
+
if (stat.size > 524288) { res.writeHead(413); res.end('File too large'); return; }
|
|
4215
|
+
const content = fs.readFileSync(resolved, 'utf8');
|
|
4216
|
+
res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8', 'Access-Control-Allow-Origin': '*' });
|
|
4217
|
+
res.end(content);
|
|
4218
|
+
} catch(_) { res.writeHead(500); res.end('Internal error'); }
|
|
4219
|
+
return;
|
|
4220
|
+
}
|
|
4221
|
+
|
|
4222
|
+
// DELETE /api/orgs/:name — delete an org config and all associated data files
|
|
4223
|
+
if (req.method === 'DELETE' && url.match(/^\/api\/orgs\/[a-z0-9][a-z0-9_-]{0,63}(\?.*)?$/i)) {
|
|
4224
|
+
try {
|
|
4225
|
+
const orgName = decodeURIComponent(url.split('/')[3].split('?')[0]);
|
|
3903
4226
|
if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('Invalid org name'); return; }
|
|
3904
|
-
const
|
|
4227
|
+
const _delOrgQs = new URL(req.url, 'http://localhost').searchParams;
|
|
4228
|
+
const orgsDir = path.join(path.resolve(_delOrgQs.get('dir') || projectDir || process.cwd()), '.monomind', 'orgs');
|
|
3905
4229
|
const configFile = path.join(orgsDir, `${orgName}.json`);
|
|
3906
4230
|
if (!fs.existsSync(configFile)) { res.writeHead(404); res.end('{"error":"org not found"}'); return; }
|
|
3907
4231
|
// Remove all org-associated files (config + state + data)
|
|
3908
|
-
const suffixes = ['', '-state', '-goals', '-routines', '-approvals', '-activity', '-issues', '-members', '-projects', '-workspaces', '-worktrees', '-environments', '-plugins', '-adapters'];
|
|
4232
|
+
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
4233
|
for (const suf of suffixes) {
|
|
3910
4234
|
const f = path.join(orgsDir, `${orgName}${suf}.json`);
|
|
3911
4235
|
try { if (fs.existsSync(f)) fs.unlinkSync(f); } catch(_) {}
|
|
@@ -3914,6 +4238,18 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
3914
4238
|
}
|
|
3915
4239
|
// Remove stop file if present
|
|
3916
4240
|
try { fs.unlinkSync(path.join(orgsDir, '.stops', `${orgName}.stop`)); } catch(_) {}
|
|
4241
|
+
// Remove org subdirectory under .monomind/orgs/ (legacy flat-file location)
|
|
4242
|
+
try { const orgWorkDir = path.join(orgsDir, orgName); if (fs.existsSync(orgWorkDir)) fs.rmSync(orgWorkDir, { recursive: true, force: true }); } catch(_) {}
|
|
4243
|
+
// Remove org subdirectory under git-safe location (.git/monomind/orgs/<name>/) so run
|
|
4244
|
+
// files written by the worktree-aware path (feat 880f034e) are also cleaned up on delete
|
|
4245
|
+
try {
|
|
4246
|
+
const _delWorkDir = path.resolve(_delOrgQs.get('dir') || projectDir || process.cwd());
|
|
4247
|
+
const _delGitMonoDir = _getGitMonomindDir(_delWorkDir);
|
|
4248
|
+
if (_delGitMonoDir) {
|
|
4249
|
+
const gitOrgDir = path.join(_delGitMonoDir, 'orgs', orgName);
|
|
4250
|
+
if (fs.existsSync(gitOrgDir)) fs.rmSync(gitOrgDir, { recursive: true, force: true });
|
|
4251
|
+
}
|
|
4252
|
+
} catch(_) {}
|
|
3917
4253
|
// Remove loop prompt file if present (created for scheduled orgs by createorg)
|
|
3918
4254
|
try { const lpf = path.join(path.resolve(projectDir || process.cwd()), '.monomind', 'loops', `${orgName}.md`); if (fs.existsSync(lpf)) fs.unlinkSync(lpf); } catch(_) {}
|
|
3919
4255
|
// Emit org:delete event
|
|
@@ -3932,13 +4268,15 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
3932
4268
|
try {
|
|
3933
4269
|
const orgName = decodeURIComponent(url.split('/')[3]);
|
|
3934
4270
|
if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('Invalid org name'); return; }
|
|
4271
|
+
const _stopOrgQs = new URL(req.url, 'http://localhost').searchParams;
|
|
4272
|
+
const _stopOrgBase = path.resolve(_stopOrgQs.get('dir') || projectDir || process.cwd());
|
|
3935
4273
|
const stopEvent = { type: 'org:stop', org: orgName, ts: Date.now() };
|
|
3936
|
-
const dataDir = path.join(
|
|
4274
|
+
const dataDir = path.join(_stopOrgBase, 'data');
|
|
3937
4275
|
try { fs.mkdirSync(dataDir, { recursive: true }); } catch(_) {}
|
|
3938
4276
|
try { fs.appendFileSync(path.join(dataDir, 'mastermind-events.jsonl'), JSON.stringify(stopEvent) + '\n'); } catch(_) {}
|
|
3939
4277
|
// Write stop marker file for boss agent to detect
|
|
3940
4278
|
try {
|
|
3941
|
-
const stopDir = path.join(
|
|
4279
|
+
const stopDir = path.join(_stopOrgBase, '.monomind', 'orgs', '.stops');
|
|
3942
4280
|
fs.mkdirSync(stopDir, { recursive: true });
|
|
3943
4281
|
fs.writeFileSync(path.join(stopDir, `${orgName}.stop`), String(Date.now()));
|
|
3944
4282
|
} catch(_) {}
|
|
@@ -3953,7 +4291,7 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
3953
4291
|
// POST /api/orgs/:name/copy — copy org config to another project directory
|
|
3954
4292
|
if (req.method === 'POST' && url.match(/^\/api\/orgs\/[a-z0-9][a-z0-9_-]{0,63}\/copy$/i)) {
|
|
3955
4293
|
let body = '';
|
|
3956
|
-
for await (const chunk of req) body += chunk;
|
|
4294
|
+
for await (const chunk of req) { body += chunk; if (body.length > 2097152) { req.destroy(); break; } }
|
|
3957
4295
|
try {
|
|
3958
4296
|
const orgName = decodeURIComponent(url.split('/')[3]);
|
|
3959
4297
|
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 +4300,8 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
3962
4300
|
const destination = payload.destination ? String(payload.destination).trim() : '';
|
|
3963
4301
|
if (!destination) { res.writeHead(400); res.end(JSON.stringify({ error: 'destination is required' })); return; }
|
|
3964
4302
|
if (!path.isAbsolute(destination)) { res.writeHead(400); res.end(JSON.stringify({ error: 'destination must be an absolute path' })); return; }
|
|
3965
|
-
const
|
|
4303
|
+
const _copyOrgQs = new URL(req.url, 'http://localhost').searchParams;
|
|
4304
|
+
const srcOrgsDir = path.join(path.resolve(_copyOrgQs.get('dir') || projectDir || process.cwd()), '.monomind', 'orgs');
|
|
3966
4305
|
const srcFile = path.join(srcOrgsDir, `${orgName}.json`);
|
|
3967
4306
|
if (!fs.existsSync(srcFile)) { res.writeHead(404); res.end(JSON.stringify({ error: 'org not found' })); return; }
|
|
3968
4307
|
const destOrgsDir = path.join(path.resolve(destination), '.monomind', 'orgs');
|
|
@@ -3975,16 +4314,87 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
3975
4314
|
return;
|
|
3976
4315
|
}
|
|
3977
4316
|
|
|
4317
|
+
// GET /api/org/:name/runs — list structured run files for an org
|
|
4318
|
+
if (req.method === 'GET' && /^\/api\/org\/[a-z0-9][a-z0-9_-]{0,63}\/runs(\?.*)?$/i.test(url)) {
|
|
4319
|
+
try {
|
|
4320
|
+
const _rQs = new URL(req.url, 'http://localhost').searchParams;
|
|
4321
|
+
const _rOrgName = decodeURIComponent(url.split('/')[3] || '');
|
|
4322
|
+
const _rWorkDir = path.resolve(_rQs.get('dir') || projectDir || process.cwd());
|
|
4323
|
+
const _rMonoDir = _getGitMonomindDir(_rWorkDir) || path.join(_rWorkDir, '.monomind');
|
|
4324
|
+
const _rDir = path.join(_rMonoDir, 'orgs', _rOrgName, 'runs');
|
|
4325
|
+
const runs = [];
|
|
4326
|
+
if (fs.existsSync(_rDir)) {
|
|
4327
|
+
const files = fs.readdirSync(_rDir).filter(f => f.endsWith('.jsonl')).sort().reverse();
|
|
4328
|
+
for (const f of files.slice(0, 50)) {
|
|
4329
|
+
try {
|
|
4330
|
+
const raw = fs.readFileSync(path.join(_rDir, f), 'utf8');
|
|
4331
|
+
const allLines = raw.split('\n').filter(Boolean);
|
|
4332
|
+
const eventCount = allLines.length;
|
|
4333
|
+
const parse = l => { try { return JSON.parse(l); } catch { return null; } };
|
|
4334
|
+
const headEvents = allLines.slice(0, 5).map(parse).filter(Boolean);
|
|
4335
|
+
const tailEvents = allLines.slice(-5).map(parse).filter(Boolean);
|
|
4336
|
+
const first = headEvents.find(e => e.type === 'run:start') || headEvents[0];
|
|
4337
|
+
const last = tailEvents.slice().reverse().find(e => e.type === 'run:complete' || e.type === 'org:complete');
|
|
4338
|
+
const cycles = allLines.filter(l => l.includes('"org:checkpoint"')).length;
|
|
4339
|
+
const lastEvent = tailEvents[tailEvents.length - 1] || headEvents[headEvents.length - 1];
|
|
4340
|
+
const ageMs = lastEvent?.ts ? Date.now() - lastEvent.ts : Infinity;
|
|
4341
|
+
const isStale = !last && ageMs > 30 * 60 * 1000;
|
|
4342
|
+
runs.push({ runId: f.replace('.jsonl', ''), startedAt: first?.ts || 0, endedAt: last?.ts || 0,
|
|
4343
|
+
status: last ? 'complete' : isStale ? 'stale' : 'running',
|
|
4344
|
+
eventCount, cycleCount: cycles, goal: first?.goal || '', bossRole: first?.bossRole || '' });
|
|
4345
|
+
} catch (_) {}
|
|
4346
|
+
}
|
|
4347
|
+
}
|
|
4348
|
+
res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', 'Cache-Control': 'no-cache' });
|
|
4349
|
+
res.end(JSON.stringify(runs));
|
|
4350
|
+
} catch (_) { res.writeHead(500); res.end('[]'); }
|
|
4351
|
+
return;
|
|
4352
|
+
}
|
|
4353
|
+
|
|
4354
|
+
// GET /api/org/:name/runs/:runId — get all events for a specific run
|
|
4355
|
+
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)) {
|
|
4356
|
+
try {
|
|
4357
|
+
const _rvQs = new URL(req.url, 'http://localhost').searchParams;
|
|
4358
|
+
const _rvParts = url.replace(/\?.*$/, '').split('/');
|
|
4359
|
+
const _rvOrgName = decodeURIComponent(_rvParts[3] || '');
|
|
4360
|
+
const _rvRunId = decodeURIComponent(_rvParts[5] || '');
|
|
4361
|
+
const _rvWorkDir = path.resolve(_rvQs.get('dir') || projectDir || process.cwd());
|
|
4362
|
+
const _rvMonoDir = _getGitMonomindDir(_rvWorkDir) || path.join(_rvWorkDir, '.monomind');
|
|
4363
|
+
const _rvFile = path.join(_rvMonoDir, 'orgs', _rvOrgName, 'runs', `${_rvRunId}.jsonl`);
|
|
4364
|
+
if (!fs.existsSync(_rvFile)) { res.writeHead(404); res.end('{"error":"run not found"}'); return; }
|
|
4365
|
+
const events = fs.readFileSync(_rvFile, 'utf8').split('\n').filter(Boolean)
|
|
4366
|
+
.map(l => { try { return JSON.parse(l); } catch { return null; } }).filter(Boolean);
|
|
4367
|
+
res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', 'Cache-Control': 'no-cache' });
|
|
4368
|
+
res.end(JSON.stringify(events));
|
|
4369
|
+
} catch (_) { res.writeHead(500); res.end('[]'); }
|
|
4370
|
+
return;
|
|
4371
|
+
}
|
|
4372
|
+
|
|
3978
4373
|
// ------------------------------------------------- Mastermind event system
|
|
3979
4374
|
// POST /api/mastermind/event — ingest event from mastermind skill
|
|
3980
4375
|
if (req.method === 'POST' && url === '/api/mastermind/event') {
|
|
3981
4376
|
let body = '';
|
|
3982
|
-
for await (const chunk of req) body += chunk;
|
|
4377
|
+
for await (const chunk of req) { body += chunk; if (body.length > 2097152) { req.destroy(); break; } }
|
|
3983
4378
|
let event = {};
|
|
3984
4379
|
try { event = JSON.parse(body); } catch (_) {}
|
|
3985
4380
|
event.ts = event.ts || Date.now();
|
|
3986
|
-
// Use project path from event if provided (multi-project support)
|
|
3987
|
-
|
|
4381
|
+
// Use project path from event if provided (multi-project support).
|
|
4382
|
+
// Security: path.isAbsolute() alone is insufficient — an attacker can
|
|
4383
|
+
// supply event.project="/etc" and cause writes to system directories.
|
|
4384
|
+
// Only accept paths that resolve to an existing directory AND are not
|
|
4385
|
+
// the filesystem root (/), AND are not obviously system paths.
|
|
4386
|
+
// Cap to 4096 chars to prevent OOM from huge path strings.
|
|
4387
|
+
const _rawProject = event.project;
|
|
4388
|
+
let eventProject = null;
|
|
4389
|
+
if (typeof _rawProject === 'string' && _rawProject.length > 0 && _rawProject.length <= 4096
|
|
4390
|
+
&& path.isAbsolute(_rawProject)) {
|
|
4391
|
+
// Reject filesystem root and common system directories
|
|
4392
|
+
const _norm = path.resolve(_rawProject);
|
|
4393
|
+
const _systemPaths = ['/', '/etc', '/usr', '/bin', '/sbin', '/lib', '/lib64', '/boot', '/dev', '/sys', '/proc', '/tmp'];
|
|
4394
|
+
if (!_systemPaths.includes(_norm) && !_systemPaths.some(p => _norm.startsWith(p + '/'))) {
|
|
4395
|
+
eventProject = _norm;
|
|
4396
|
+
}
|
|
4397
|
+
}
|
|
3988
4398
|
const root = eventProject || projectDir || process.cwd();
|
|
3989
4399
|
const dataDir = path.join(root, 'data');
|
|
3990
4400
|
try { fs.mkdirSync(dataDir, { recursive: true }); } catch (_) {}
|
|
@@ -3998,6 +4408,28 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
3998
4408
|
} catch (_) {}
|
|
3999
4409
|
}
|
|
4000
4410
|
try { fs.appendFileSync(path.join(dataDir, 'mastermind-events.jsonl'), JSON.stringify(event) + '\n'); } catch (_) {}
|
|
4411
|
+
// Track active runs and route org events to run files
|
|
4412
|
+
if (event.org) {
|
|
4413
|
+
const _orgKey = String(event.org).trim();
|
|
4414
|
+
// Any event with both org+runId updates the active run map (run:start written directly to file so org:start is first via curl)
|
|
4415
|
+
if (event.runId) activeOrgRuns.set(_orgKey, String(event.runId).trim());
|
|
4416
|
+
else if (activeOrgRuns.has(_orgKey)) event.runId = activeOrgRuns.get(_orgKey);
|
|
4417
|
+
if (event.type === 'run:complete' || event.type === 'org:complete') activeOrgRuns.delete(_orgKey);
|
|
4418
|
+
}
|
|
4419
|
+
// Persist to git-safe run file (survives branch switches + shared across worktrees)
|
|
4420
|
+
if (event.org && event.runId) {
|
|
4421
|
+
try {
|
|
4422
|
+
const _orn = String(event.org).trim();
|
|
4423
|
+
const _rid = String(event.runId).trim();
|
|
4424
|
+
if (_orn.length > 0 && _orn.length <= 64 && /^[a-z0-9][a-z0-9_-]*$/i.test(_orn)
|
|
4425
|
+
&& _rid.length > 0 && _rid.length <= 80 && /^[a-z0-9][a-z0-9_-]*$/i.test(_rid)) {
|
|
4426
|
+
const _monoDir = _getGitMonomindDir(root) || path.join(root, '.monomind');
|
|
4427
|
+
const _runDir = path.join(_monoDir, 'orgs', _orn, 'runs');
|
|
4428
|
+
fs.mkdirSync(_runDir, { recursive: true });
|
|
4429
|
+
fs.appendFileSync(path.join(_runDir, `${_rid}.jsonl`), JSON.stringify(event) + '\n');
|
|
4430
|
+
}
|
|
4431
|
+
} catch (_) {}
|
|
4432
|
+
}
|
|
4001
4433
|
// Persist session
|
|
4002
4434
|
try {
|
|
4003
4435
|
const sessFile = path.join(dataDir, 'mastermind-sessions.json');
|
|
@@ -4016,12 +4448,19 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
4016
4448
|
}
|
|
4017
4449
|
}
|
|
4018
4450
|
fs.writeFileSync(sessFile, JSON.stringify(sessions.slice(0, 50), null, 2));
|
|
4019
|
-
// Also write individual session file for direct traceability
|
|
4451
|
+
// Also write individual session file for direct traceability.
|
|
4452
|
+
// Security: validate event.session before using it as a filename to
|
|
4453
|
+
// prevent path traversal (e.g. "../../../etc/cron.d/payload").
|
|
4020
4454
|
const sessionObj = sessions.find(s => s.id === event.session);
|
|
4021
4455
|
if (sessionObj) {
|
|
4022
4456
|
const sessDir = path.join(dataDir, 'sessions');
|
|
4023
4457
|
try { fs.mkdirSync(sessDir, { recursive: true }); } catch (_) {}
|
|
4024
|
-
try {
|
|
4458
|
+
try {
|
|
4459
|
+
const _sid = String(event.session || '').trim();
|
|
4460
|
+
if (_sid.length > 0 && _sid.length <= 128 && /^[a-zA-Z0-9_.-]+$/.test(_sid)) {
|
|
4461
|
+
fs.writeFileSync(path.join(sessDir, `${_sid}.json`), JSON.stringify(sessionObj, null, 2));
|
|
4462
|
+
}
|
|
4463
|
+
} catch (_) {}
|
|
4025
4464
|
}
|
|
4026
4465
|
} catch (_) {}
|
|
4027
4466
|
// For org:stop events, write a stop marker the boss agent can detect
|