@monoes/monomindcli 1.11.14 → 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 +38 -12
- 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 +934 -1282
- 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
|
@@ -63,9 +63,10 @@ html, body { height: 100%; background: var(--bg); color: var(--text-hi); font-fa
|
|
|
63
63
|
#view-title { font-size: 14px; font-weight: 600; color: var(--text-hi); }
|
|
64
64
|
.pill { display: inline-flex; align-items: center; gap: 5px; font-size: 11px; color: var(--text-lo); background: var(--surface); border: 1px solid var(--border); border-radius: 10px; padding: 2px 8px; }
|
|
65
65
|
.live-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--green); animation: blink 2s ease-in-out infinite; }
|
|
66
|
-
.live-dot.polling { background: oklch(
|
|
66
|
+
.live-dot.polling { background: oklch(78% 0.16 80); animation-duration: 3s; }
|
|
67
67
|
@keyframes blink { 0%,100%{opacity:1} 50%{opacity:0.35} }
|
|
68
68
|
@media (prefers-reduced-motion: reduce) { .live-dot { animation: none; } }
|
|
69
|
+
.live-dot.polling { background: oklch(78% 0.16 80); animation-duration: 2s; }
|
|
69
70
|
#tb-right { margin-left: auto; display: flex; align-items: center; gap: 8px; }
|
|
70
71
|
.btn { font-size: 11px; color: var(--text-lo); background: transparent; border: 1px solid var(--border); border-radius: var(--r); padding: 4px 10px; cursor: pointer; transition: color 0.1s, border-color 0.1s; }
|
|
71
72
|
.btn:hover { color: var(--text-hi); border-color: var(--text-lo); }
|
|
@@ -247,10 +248,13 @@ html, body { height: 100%; background: var(--bg); color: var(--text-hi); font-fa
|
|
|
247
248
|
.lp-fill { height: 100%; background: var(--accent); border-radius: 2px; transition: width 0.4s ease; }
|
|
248
249
|
.loop-stop-btn { font-size: 11px; padding: 2px 8px; background: none; border: 1px solid var(--border); border-radius: 4px; color: var(--text-lo); cursor: pointer; font-family: var(--sans); }
|
|
249
250
|
.loop-stop-btn:hover { border-color: var(--red); color: var(--red); }
|
|
250
|
-
.loop-status.
|
|
251
|
-
.loop-
|
|
252
|
-
.loop-
|
|
253
|
-
.loop-hil-banner {
|
|
251
|
+
.loop-status.done { background: var(--surface-hi); color: var(--text-xs); }
|
|
252
|
+
.loop-status.hil { background: oklch(78% 0.18 80 / 0.15); color: oklch(78% 0.18 80); }
|
|
253
|
+
.loop-row.hil { border-color: oklch(78% 0.18 80 / 0.3); }
|
|
254
|
+
.loop-hil-banner { margin-top: 6px; padding: 5px 8px; background: oklch(78% 0.18 80 / 0.1); border: 1px solid oklch(78% 0.18 80 / 0.3); border-radius: 4px; font-size: 11px; color: oklch(78% 0.18 80); }
|
|
255
|
+
.loop-type-badge { font-size: 15px; line-height: 1; color: var(--accent); }
|
|
256
|
+
.loop-type-badge.rep { color: var(--text-lo); }
|
|
257
|
+
.lp-bar { height: 4px; background: var(--border); border-radius: 2px; margin-top: 5px; overflow: hidden; position: relative; }
|
|
254
258
|
|
|
255
259
|
/* memory */
|
|
256
260
|
.mem-section { margin-bottom: 22px; }
|
|
@@ -599,7 +603,6 @@ html, body { height: 100%; background: var(--bg); color: var(--text-hi); font-fa
|
|
|
599
603
|
.shm-grid { display:grid; grid-template-rows:repeat(7,10px); grid-auto-flow:column; grid-auto-columns:10px; gap:2px; margin-top:6px; }
|
|
600
604
|
.shm-cell { border-radius:2px; background:var(--surface-hi); cursor:pointer; transition:outline 0.1s; }
|
|
601
605
|
.shm-cell:hover { outline:1px solid var(--accent); outline-offset:-1px; }
|
|
602
|
-
.shm-cell.shm-0 { cursor: default; }
|
|
603
606
|
.shm-cell.shm-1 { background:oklch(72% 0.18 75 / 0.22); }
|
|
604
607
|
.shm-cell.shm-2 { background:oklch(72% 0.18 75 / 0.42); }
|
|
605
608
|
.shm-cell.shm-3 { background:oklch(72% 0.18 75 / 0.65); }
|
|
@@ -676,6 +679,38 @@ html, body { height: 100%; background: var(--bg); color: var(--text-hi); font-fa
|
|
|
676
679
|
#rp-fill { height:100%; background:var(--accent); border-radius:2px; transition:width 0.15s; }
|
|
677
680
|
#rp-counter { font-size:11px; color:var(--text-lo); font-family:var(--mono); white-space:nowrap; }
|
|
678
681
|
|
|
682
|
+
/* ── agent chat view ─────────────────────────────────────── */
|
|
683
|
+
#view-chat .vscroll { display:flex; flex-direction:column; height:100%; overflow:hidden; padding:0; }
|
|
684
|
+
#chat-v-bar { display:flex; align-items:center; gap:8px; padding:14px 18px 10px; border-bottom:1px solid var(--border); flex-shrink:0; flex-wrap:wrap; }
|
|
685
|
+
#chat-v-bar-title { font-size:13px; font-weight:600; color:var(--text-hi); }
|
|
686
|
+
#chat-v-sel { background:var(--surface); color:var(--text-mid); border:1px solid var(--border); border-radius:4px; font-size:11px; font-family:var(--mono); padding:3px 7px; cursor:pointer; max-width:300px; }
|
|
687
|
+
#chat-v-sel:focus { outline:none; border-color:var(--accent); }
|
|
688
|
+
#chat-v-live-dot { width:5px; height:5px; border-radius:50%; background:var(--text-xs); flex-shrink:0; margin-left:auto; transition:background 0.4s; }
|
|
689
|
+
#chat-v-live-dot.on { background:oklch(68% 0.20 150); animation:livepulse-cv 2.2s ease-in-out infinite; }
|
|
690
|
+
@keyframes livepulse-cv { 0%,100%{opacity:1} 50%{opacity:0.4} }
|
|
691
|
+
#chat-v-live-lbl { font-size:9px; color:var(--text-lo); }
|
|
692
|
+
#chat-v-feed { flex:1; overflow-y:auto; padding:12px 18px; display:flex; flex-direction:column; gap:5px; scrollbar-width:thin; scrollbar-color:var(--border) transparent; }
|
|
693
|
+
#chat-v-feed::-webkit-scrollbar { width:4px; }
|
|
694
|
+
#chat-v-feed::-webkit-scrollbar-thumb { background:var(--border); border-radius:2px; }
|
|
695
|
+
#chat-v-empty { font-size:11px; color:var(--text-lo); text-align:center; padding:32px 0; line-height:2; }
|
|
696
|
+
.cv-msg { display:flex; flex-direction:column; max-width:90%; }
|
|
697
|
+
.cv-msg.cv-sys { align-self:center; max-width:100%; }
|
|
698
|
+
.cv-msg.cv-agent { align-self:flex-start; }
|
|
699
|
+
.cv-msg.cv-ic { align-self:flex-start; }
|
|
700
|
+
@keyframes cv-in { from{opacity:0;transform:translateY(3px)} to{opacity:1;transform:none} }
|
|
701
|
+
.cv-msg.cv-new { animation:cv-in 0.18s ease-out; }
|
|
702
|
+
.cv-msg.cv-sys .cv-bub { display:flex; align-items:center; gap:6px; flex-wrap:wrap; background:var(--surface); border:1px solid var(--border); border-radius:6px; padding:4px 12px; font-size:10px; color:var(--text-lo); text-align:center; }
|
|
703
|
+
.cv-msg.cv-agent .cv-bub { display:flex; align-items:baseline; gap:6px; flex-wrap:wrap; background:var(--surface-hi); border:1px solid oklch(62% 0.20 186 / 0.18); border-radius:2px 8px 8px 8px; padding:7px 11px; color:var(--text-mid); font-size:11px; line-height:1.6; word-break:break-word; white-space:pre-wrap; }
|
|
704
|
+
.cv-msg.cv-ic .cv-bub { display:flex; align-items:baseline; gap:6px; flex-wrap:wrap; background:oklch(10% 0.013 295); border:1px solid oklch(68% 0.18 295 / 0.22); border-radius:2px 8px 8px 8px; padding:7px 11px; color:oklch(68% 0.010 295); font-size:11px; line-height:1.6; word-break:break-word; white-space:pre-wrap; }
|
|
705
|
+
.cv-meta { display:flex; align-items:center; gap:5px; margin-bottom:3px; flex-wrap:wrap; }
|
|
706
|
+
.cv-msg.cv-sys .cv-meta { display:none; }
|
|
707
|
+
.cv-tag { font-size:8px; padding:1px 6px; border-radius:10px; border:1px solid oklch(62% 0.20 186 / 0.25); color:oklch(62% 0.20 186); letter-spacing:0.4px; flex-shrink:0; }
|
|
708
|
+
.cv-tag.cv-sender { border-color:oklch(68% 0.18 295 / 0.35); color:oklch(68% 0.14 295); }
|
|
709
|
+
.cv-tag.cv-receiver{ border-color:oklch(78% 0.18 80 / 0.35); color:oklch(78% 0.18 80); }
|
|
710
|
+
.cv-arrow { font-size:9px; color:var(--text-xs); }
|
|
711
|
+
.cv-ts { font-size:8px; color:var(--text-xs); margin-left:auto; }
|
|
712
|
+
.cv-etype { font-size:7px; padding:1px 4px; border-radius:2px; background:var(--surface); border:1px solid var(--border); color:var(--text-xs); letter-spacing:0.3px; }
|
|
713
|
+
|
|
679
714
|
/* ── global feed (multi-project) ─────────────────────────── */
|
|
680
715
|
.gf-proj-tag { font-size:10px; padding:1px 6px; border-radius:6px; background:var(--surface-hi); color:var(--text-lo); white-space:nowrap; flex-shrink:0; margin-top:3px; }
|
|
681
716
|
|
|
@@ -1235,8 +1270,7 @@ textarea.sess-note-input:focus { border-color:var(--accent); }
|
|
|
1235
1270
|
.mg-query-row input, .mg-query-row select, .mg-query-row textarea { background:var(--surface-hi); border:1px solid var(--border); border-radius:4px; color:var(--text-hi); padding:6px 10px; font-size:12px; font-family:var(--sans); outline:none; }
|
|
1236
1271
|
.mg-query-row input:focus, .mg-query-row textarea:focus { border-color:var(--accent); }
|
|
1237
1272
|
.mg-query-row textarea { resize:vertical; min-height:70px; width:100%; }
|
|
1238
|
-
.mg-query-result { margin-top:10px; padding:10px 12px; background:var(--surface-hi); border-radius:4px; font-size:11px; font-family:var(--mono); white-space:pre-wrap; word-break:break-word; color:var(--text-mid); max-height:300px; overflow-y:auto;
|
|
1239
|
-
.mg-query-result:hover::after { content:'⎘ copy'; position:sticky; float:right; font-size:10px; color:var(--text-xs); pointer-events:none; }
|
|
1273
|
+
.mg-query-result { margin-top:10px; padding:10px 12px; background:var(--surface-hi); border-radius:4px; font-size:11px; font-family:var(--mono); white-space:pre-wrap; word-break:break-word; color:var(--text-mid); max-height:300px; overflow-y:auto; }
|
|
1240
1274
|
/* live border glow */
|
|
1241
1275
|
@keyframes live-fade { 0% { box-shadow: 0 0 0 1px oklch(72% 0.18 75 / 0.4); } 100% { box-shadow: none; } }
|
|
1242
1276
|
.live-glow { animation: live-fade 8s ease-out forwards; }
|
|
@@ -1298,10 +1332,10 @@ textarea.sess-note-input:focus { border-color:var(--accent); }
|
|
|
1298
1332
|
</div>
|
|
1299
1333
|
<div id="sb-nav">
|
|
1300
1334
|
<div class="nav-sect">
|
|
1301
|
-
<div class="nav-item active" data-view="now"
|
|
1335
|
+
<div class="nav-item active" data-view="now">
|
|
1302
1336
|
<span class="ico">◉</span><span class="lbl">Now</span>
|
|
1303
1337
|
</div>
|
|
1304
|
-
<div class="nav-item" data-view="projects"
|
|
1338
|
+
<div class="nav-item" data-view="projects">
|
|
1305
1339
|
<span class="ico">⊞</span><span class="lbl">Projects</span>
|
|
1306
1340
|
<span class="bdg" id="bdg-projects">—</span>
|
|
1307
1341
|
</div>
|
|
@@ -1313,31 +1347,34 @@ textarea.sess-note-input:focus { border-color:var(--accent); }
|
|
|
1313
1347
|
<div class="nav-proj-name" id="nav-proj-name">—</div>
|
|
1314
1348
|
</div>
|
|
1315
1349
|
<div class="nav-proj-items">
|
|
1316
|
-
<div class="nav-item" data-view="sessions"
|
|
1350
|
+
<div class="nav-item" data-view="sessions">
|
|
1317
1351
|
<span class="ico">◫</span><span class="lbl">Sessions</span>
|
|
1318
1352
|
<span class="bdg" id="bdg-sessions">—</span>
|
|
1319
1353
|
</div>
|
|
1320
|
-
<div class="nav-item" data-view="loops"
|
|
1354
|
+
<div class="nav-item" data-view="loops">
|
|
1321
1355
|
<span class="ico">↺</span><span class="lbl">Loops</span>
|
|
1322
1356
|
<span class="bdg" id="bdg-loops">—</span>
|
|
1323
1357
|
</div>
|
|
1324
|
-
<div class="nav-item" data-view="tokens"
|
|
1358
|
+
<div class="nav-item" data-view="tokens">
|
|
1325
1359
|
<span class="ico">$</span><span class="lbl">Tokens</span>
|
|
1326
1360
|
</div>
|
|
1327
|
-
<div class="nav-item" data-view="memory"
|
|
1361
|
+
<div class="nav-item" data-view="memory">
|
|
1328
1362
|
<span class="ico">◈</span><span class="lbl">Memory</span>
|
|
1329
1363
|
</div>
|
|
1330
|
-
<div class="nav-item" data-view="orgs"
|
|
1364
|
+
<div class="nav-item" data-view="orgs">
|
|
1331
1365
|
<span class="ico">⬡</span><span class="lbl">Orgs</span>
|
|
1332
1366
|
</div>
|
|
1333
|
-
<div class="nav-item" data-view="monograph"
|
|
1367
|
+
<div class="nav-item" data-view="monograph">
|
|
1334
1368
|
<span class="ico">⬡</span><span class="lbl">Monograph</span>
|
|
1335
1369
|
</div>
|
|
1370
|
+
<div class="nav-item" data-view="chat">
|
|
1371
|
+
<span class="ico">⌘</span><span class="lbl">Agent Chat</span>
|
|
1372
|
+
</div>
|
|
1336
1373
|
</div>
|
|
1337
1374
|
</div>
|
|
1338
1375
|
<div class="nav-no-proj" id="nav-no-proj-hint">Select a project above</div>
|
|
1339
1376
|
<div class="nav-sect" style="margin-top:auto;padding-top:8px;">
|
|
1340
|
-
<div class="nav-item" data-view="global"
|
|
1377
|
+
<div class="nav-item" data-view="global">
|
|
1341
1378
|
<span class="ico">⊕</span><span class="lbl">Global Feed</span>
|
|
1342
1379
|
</div>
|
|
1343
1380
|
<div class="nav-item" data-view="global-loops" title="Global Loops — loops across all projects">
|
|
@@ -1365,10 +1402,12 @@ textarea.sess-note-input:focus { border-color:var(--accent); }
|
|
|
1365
1402
|
<span id="topbar-activity"></span>
|
|
1366
1403
|
<div id="tb-right">
|
|
1367
1404
|
<button class="btn" onclick="openMastermind()" style="color:oklch(65% 0.16 295);border-color:oklch(65% 0.16 295 / 0.4)" title="Mastermind — orgs, skills, loops, metrics">⬡ Mastermind</button>
|
|
1405
|
+
<button class="btn" onclick="switchView('orgs')" title="Manage autonomous organisations" style="color:oklch(70% 0.14 185);border-color:oklch(70% 0.14 185 / 0.4)">⬡ Orgs</button>
|
|
1406
|
+
<button class="btn" onclick="switchView('orgs')" title="Manage autonomous organisations" style="color:oklch(70% 0.14 185);border-color:oklch(70% 0.14 185 / 0.4)">⬡ Orgs</button>
|
|
1368
1407
|
<button class="btn" id="btn-budget" onclick="openBudgetModal()" title="Set daily/monthly cost budget">⚑ Budget</button>
|
|
1369
|
-
<button class="btn" onclick="openCmdPalette()"
|
|
1408
|
+
<button class="btn" onclick="openCmdPalette()">⌕ Search <kbd style="font-size:10px;opacity:0.6;margin-left:3px">⌘K</kbd></button>
|
|
1370
1409
|
<button class="btn" onclick="openShortcutHelp()" title="Keyboard shortcuts (?)">? Help</button>
|
|
1371
|
-
<button class="btn" onclick="refreshCurrent()"
|
|
1410
|
+
<button class="btn" onclick="refreshCurrent()">↺ Refresh</button>
|
|
1372
1411
|
</div>
|
|
1373
1412
|
</div>
|
|
1374
1413
|
|
|
@@ -1414,13 +1453,13 @@ textarea.sess-note-input:focus { border-color:var(--accent); }
|
|
|
1414
1453
|
<div id="feed-search">
|
|
1415
1454
|
<input id="feed-search-input" type="text" placeholder="Search feed…" oninput="filterFeed(this.value)" onkeydown="if(event.key==='Escape')closeFeedSearch()">
|
|
1416
1455
|
<span id="feed-search-count"></span>
|
|
1417
|
-
<button id="feed-search-close" onclick="closeFeedSearch()"
|
|
1456
|
+
<button id="feed-search-close" onclick="closeFeedSearch()">✕</button>
|
|
1418
1457
|
</div>
|
|
1419
1458
|
<div id="sess-ctx">
|
|
1420
|
-
<button class="sctx-back" onclick="switchView('sessions')"
|
|
1459
|
+
<button class="sctx-back" onclick="switchView('sessions')">← Sessions</button>
|
|
1421
1460
|
<span class="sctx-sep">/</span>
|
|
1422
1461
|
<span class="sctx-label" id="sctx-label"></span>
|
|
1423
|
-
<button class="sctx-live" onclick="goLive()"
|
|
1462
|
+
<button class="sctx-live" onclick="goLive()">⬤ Go live</button>
|
|
1424
1463
|
</div>
|
|
1425
1464
|
<div id="feed-recap"></div>
|
|
1426
1465
|
<div id="replay-bar">
|
|
@@ -1434,10 +1473,10 @@ textarea.sess-note-input:focus { border-color:var(--accent); }
|
|
|
1434
1473
|
<div id="feed-timeline" title="Session tool activity timeline"></div>
|
|
1435
1474
|
<div id="feed-time-filter">
|
|
1436
1475
|
<span class="tf-lbl">Range</span>
|
|
1437
|
-
<button class="tf-btn active" data-tf="all"
|
|
1438
|
-
<button class="tf-btn" data-tf="1h"
|
|
1439
|
-
<button class="tf-btn" data-tf="6h"
|
|
1440
|
-
<button class="tf-btn" data-tf="24h"
|
|
1476
|
+
<button class="tf-btn active" data-tf="all" onclick="setFeedTimeFilter('all')">All</button>
|
|
1477
|
+
<button class="tf-btn" data-tf="1h" onclick="setFeedTimeFilter('1h')">1h</button>
|
|
1478
|
+
<button class="tf-btn" data-tf="6h" onclick="setFeedTimeFilter('6h')">6h</button>
|
|
1479
|
+
<button class="tf-btn" data-tf="24h" onclick="setFeedTimeFilter('24h')">24h</button>
|
|
1441
1480
|
<span class="kb-hint"><kbd>J</kbd><kbd>K</kbd> navigate <kbd>↵</kbd> detail <kbd>/</kbd> find <kbd>G</kbd> live <kbd>A</kbd> ambient <kbd>⌘K</kbd> search</span>
|
|
1442
1481
|
</div>
|
|
1443
1482
|
<div id="weekly-card">
|
|
@@ -1466,7 +1505,7 @@ textarea.sess-note-input:focus { border-color:var(--accent); }
|
|
|
1466
1505
|
<div id="detail-panel">
|
|
1467
1506
|
<div id="detail-head">
|
|
1468
1507
|
<h3 id="detail-title">Detail</h3>
|
|
1469
|
-
<button id="detail-close" onclick="closeDetail()"
|
|
1508
|
+
<button id="detail-close" onclick="closeDetail()">✕</button>
|
|
1470
1509
|
</div>
|
|
1471
1510
|
<div id="detail-body"></div>
|
|
1472
1511
|
</div>
|
|
@@ -1559,12 +1598,12 @@ textarea.sess-note-input:focus { border-color:var(--accent); }
|
|
|
1559
1598
|
<table class="lb-table"><thead><tr>
|
|
1560
1599
|
<th class="lb-rank">#</th><th>Session</th><th class="lb-cost">Cost</th><th class="lb-dur">Duration</th>
|
|
1561
1600
|
</tr></thead><tbody id="lb-body"></tbody></table>
|
|
1562
|
-
<div id="lb-overflow" style="font-size:
|
|
1601
|
+
<div id="lb-overflow" style="font-size:10px;color:var(--text-xs);text-align:center;padding:6px 0"></div>
|
|
1563
1602
|
</div>
|
|
1564
1603
|
<div id="cost-histogram-panel"></div>
|
|
1565
1604
|
<div id="timeline-panel"><div id="timeline-head">Session Timeline <span style="font-weight:400;font-size:10px;color:var(--text-xs)">— each bar = one session, width = duration, color = cost</span></div><div id="timeline-scroll"></div></div>
|
|
1566
1605
|
<div id="model-donut-panel"></div>
|
|
1567
|
-
<div id="file-pivot-bar"><span class="fpb-label" id="fpb-label"></span><button class="fpb-clear"
|
|
1606
|
+
<div id="file-pivot-bar"><span class="fpb-label" id="fpb-label"></span><button class="fpb-clear" onclick="clearFilePivot()">✕ Clear filter</button></div>
|
|
1568
1607
|
<div id="model-mix-panel" style="display:none;margin-bottom:16px">
|
|
1569
1608
|
<div id="model-mix-body"></div>
|
|
1570
1609
|
</div>
|
|
@@ -1581,21 +1620,21 @@ textarea.sess-note-input:focus { border-color:var(--accent); }
|
|
|
1581
1620
|
<div id="patterns-body"></div>
|
|
1582
1621
|
</div>
|
|
1583
1622
|
<div id="sess-heatmap" style="margin-bottom:14px;display:none">
|
|
1584
|
-
<div class="shm-label"><span>12-week activity</span><button id="shm-clear" onclick="clearHeatmapFilter()"
|
|
1623
|
+
<div class="shm-label"><span>12-week activity</span><button id="shm-clear" onclick="clearHeatmapFilter()">✕ Clear filter</button></div>
|
|
1585
1624
|
<div class="shm-grid" id="shm-grid"></div>
|
|
1586
1625
|
</div>
|
|
1587
1626
|
<div class="period-toggles" id="period-toggles">
|
|
1588
1627
|
<span style="font-size:10px;color:var(--text-xs);align-self:center;text-transform:uppercase;letter-spacing:0.06em">Period:</span>
|
|
1589
|
-
<button class="period-btn active" data-period="day"
|
|
1590
|
-
<button class="period-btn" data-period="week"
|
|
1591
|
-
<button class="period-btn" data-period="month"
|
|
1592
|
-
<button class="period-btn" data-period="all"
|
|
1628
|
+
<button class="period-btn active" data-period="day" onclick="setPeriod('day')">Day</button>
|
|
1629
|
+
<button class="period-btn" data-period="week" onclick="setPeriod('week')">Week</button>
|
|
1630
|
+
<button class="period-btn" data-period="month" onclick="setPeriod('month')">Month</button>
|
|
1631
|
+
<button class="period-btn" data-period="all" onclick="setPeriod('all')">All</button>
|
|
1593
1632
|
</div>
|
|
1594
1633
|
<div id="bulk-toolbar">
|
|
1595
1634
|
<span class="bulk-count" id="bulk-count">0 selected</span>
|
|
1596
|
-
<button class="bulk-btn" onclick="bulkExport()"
|
|
1597
|
-
<button class="bulk-btn" onclick="bulkBookmark()"
|
|
1598
|
-
<button class="bulk-btn danger" onclick="clearBulkSelection()"
|
|
1635
|
+
<button class="bulk-btn" onclick="bulkExport()">⬇ Export</button>
|
|
1636
|
+
<button class="bulk-btn" onclick="bulkBookmark()">☆ Bookmark all</button>
|
|
1637
|
+
<button class="bulk-btn danger" onclick="clearBulkSelection()">✕ Clear</button>
|
|
1599
1638
|
</div>
|
|
1600
1639
|
<div id="sess-filter-wrap">
|
|
1601
1640
|
<input id="sess-filter-input" type="text" placeholder="Filter sessions by prompt…" oninput="filterSessions(this.value)" autocomplete="off">
|
|
@@ -1610,33 +1649,33 @@ textarea.sess-note-input:focus { border-color:var(--accent); }
|
|
|
1610
1649
|
<div class="vscroll">
|
|
1611
1650
|
<div class="pg-title">Loops</div>
|
|
1612
1651
|
<div class="pg-sub">Scheduled automation loops</div>
|
|
1613
|
-
<
|
|
1652
|
+
<input id="loop-list-filter" class="filter-input" placeholder="Filter loops…" oninput="filterLoopList(this.value)" style="margin-bottom:10px;width:100%;max-width:380px">
|
|
1653
|
+
<button id="btn-new-loop" onclick="showLoopForm()">+ New Loop</button>
|
|
1614
1654
|
<div id="loop-create-form" style="display:none">
|
|
1615
1655
|
<div class="lcf-title">Create Loop</div>
|
|
1616
1656
|
<div class="lcf-row">
|
|
1617
1657
|
<label class="lcf-label">Prompt</label>
|
|
1618
|
-
<textarea class="lcf-textarea" id="lcf-prompt" placeholder="What should the agent do each iteration?"
|
|
1658
|
+
<textarea class="lcf-textarea" id="lcf-prompt" placeholder="What should the agent do each iteration?"></textarea>
|
|
1619
1659
|
</div>
|
|
1620
1660
|
<div class="lcf-row">
|
|
1621
1661
|
<label class="lcf-label">Name (optional)</label>
|
|
1622
|
-
<input class="lcf-input" id="lcf-name" type="text" placeholder="My loop"
|
|
1662
|
+
<input class="lcf-input" id="lcf-name" type="text" placeholder="My loop">
|
|
1623
1663
|
</div>
|
|
1624
1664
|
<div class="lcf-row-inline">
|
|
1625
1665
|
<div class="lcf-row">
|
|
1626
1666
|
<label class="lcf-label">Interval</label>
|
|
1627
|
-
<input class="lcf-input" id="lcf-interval" type="text" placeholder="1h" value="1h"
|
|
1667
|
+
<input class="lcf-input" id="lcf-interval" type="text" placeholder="1h" value="1h">
|
|
1628
1668
|
</div>
|
|
1629
1669
|
<div class="lcf-row">
|
|
1630
1670
|
<label class="lcf-label">Max reps (blank = ∞)</label>
|
|
1631
|
-
<input class="lcf-input" id="lcf-maxreps" type="number" placeholder="∞" min="1"
|
|
1671
|
+
<input class="lcf-input" id="lcf-maxreps" type="number" placeholder="∞" min="1">
|
|
1632
1672
|
</div>
|
|
1633
1673
|
</div>
|
|
1634
1674
|
<div class="lcf-actions">
|
|
1635
|
-
<button class="lcf-cancel"
|
|
1636
|
-
<button class="lcf-submit"
|
|
1675
|
+
<button class="lcf-cancel" onclick="hideLoopForm()">Cancel</button>
|
|
1676
|
+
<button class="lcf-submit" onclick="createLoop()">Create Loop</button>
|
|
1637
1677
|
</div>
|
|
1638
1678
|
</div>
|
|
1639
|
-
<div class="filter-bar" style="margin:8px 0"><input class="filter-input" id="loop-list-filter" type="text" placeholder="Filter loops…" oninput="filterLoopList(this.value)" title="Filter loops by name or prompt"></div>
|
|
1640
1679
|
<div id="loops-content" class="loop-list"><div class="loading-txt">Loading…</div></div>
|
|
1641
1680
|
</div>
|
|
1642
1681
|
</div>
|
|
@@ -1650,10 +1689,10 @@ textarea.sess-note-input:focus { border-color:var(--accent); }
|
|
|
1650
1689
|
<div style="background:var(--surface);border:1px solid var(--border);border-radius:6px;padding:12px 14px">
|
|
1651
1690
|
<div style="font-size:10px;text-transform:uppercase;letter-spacing:0.07em;color:var(--text-xs);margin-bottom:8px">Daily usage (last 14 days)</div>
|
|
1652
1691
|
<div class="tok-periods">
|
|
1653
|
-
<button class="tok-period-btn active" data-period="today"
|
|
1654
|
-
<button class="tok-period-btn" data-period="week"
|
|
1655
|
-
<button class="tok-period-btn" data-period="30d"
|
|
1656
|
-
<button class="tok-period-btn" data-period="month"
|
|
1692
|
+
<button class="tok-period-btn active" data-period="today" onclick="setTokPeriod(this,'today')">Today</button>
|
|
1693
|
+
<button class="tok-period-btn" data-period="week" onclick="setTokPeriod(this,'week')">Week</button>
|
|
1694
|
+
<button class="tok-period-btn" data-period="30d" onclick="setTokPeriod(this,'30d')">30 Days</button>
|
|
1695
|
+
<button class="tok-period-btn" data-period="month" onclick="setTokPeriod(this,'month')">Month</button>
|
|
1657
1696
|
</div>
|
|
1658
1697
|
<canvas id="tok-chart" height="100" style="width:100%;display:block"></canvas>
|
|
1659
1698
|
</div>
|
|
@@ -1667,18 +1706,15 @@ textarea.sess-note-input:focus { border-color:var(--accent); }
|
|
|
1667
1706
|
<div class="pg-title">Memory</div>
|
|
1668
1707
|
<div class="pg-sub">Knowledge palace — stored facts, graph, identity</div>
|
|
1669
1708
|
<div class="mem-tab-bar" style="display:flex;gap:4px;margin-bottom:14px;border-bottom:1px solid var(--border);padding-bottom:6px">
|
|
1670
|
-
<button class="odt-btn active" data-memtab="memories"
|
|
1671
|
-
<button class="odt-btn" data-memtab="routing"
|
|
1672
|
-
<button class="odt-btn" data-memtab="usage"
|
|
1673
|
-
<button class="odt-btn" data-memtab="adrs"
|
|
1674
|
-
<button class="odt-btn" data-memtab="swarm"
|
|
1675
|
-
<button class="odt-btn" data-memtab="chunks"
|
|
1676
|
-
<button class="odt-btn" data-memtab="agent-graph"
|
|
1709
|
+
<button class="odt-btn active" data-memtab="memories" onclick="switchMemTab('memories')">Memories</button>
|
|
1710
|
+
<button class="odt-btn" data-memtab="routing" onclick="switchMemTab('routing')">Routing</button>
|
|
1711
|
+
<button class="odt-btn" data-memtab="usage" onclick="switchMemTab('usage')">Usage</button>
|
|
1712
|
+
<button class="odt-btn" data-memtab="adrs" onclick="switchMemTab('adrs')">ADRs</button>
|
|
1713
|
+
<button class="odt-btn" data-memtab="swarm" onclick="switchMemTab('swarm')">Swarm</button>
|
|
1714
|
+
<button class="odt-btn" data-memtab="chunks" onclick="switchMemTab('chunks')">Chunks</button>
|
|
1715
|
+
<button class="odt-btn" data-memtab="agent-graph" onclick="switchMemTab('agent-graph')">Agent Graph</button>
|
|
1677
1716
|
</div>
|
|
1678
1717
|
<div id="mem-tab-memories">
|
|
1679
|
-
<div class="filter-bar" style="margin-bottom:8px">
|
|
1680
|
-
<input class="filter-input" id="mem-list-filter" type="text" placeholder="Filter memories…" oninput="filterMemList(this.value)" title="Filter memories by name or description">
|
|
1681
|
-
</div>
|
|
1682
1718
|
<div class="mem-split" id="mem-split">
|
|
1683
1719
|
<div class="mem-list-pane" id="mem-list-pane">
|
|
1684
1720
|
<div class="loading-txt" style="padding:16px">Loading…</div>
|
|
@@ -1687,7 +1723,7 @@ textarea.sess-note-input:focus { border-color:var(--accent); }
|
|
|
1687
1723
|
<div style="color:var(--text-lo);font-size:13px;padding:20px 0">Select a memory</div>
|
|
1688
1724
|
</div>
|
|
1689
1725
|
</div>
|
|
1690
|
-
<div style="margin-top:10px"><button class="btn"
|
|
1726
|
+
<div style="margin-top:10px"><button class="btn" onclick="openNewMemModal()">+ New Memory</button></div>
|
|
1691
1727
|
</div>
|
|
1692
1728
|
<div id="mem-tab-routing" style="display:none"><div class="loading-txt">Loading…</div></div>
|
|
1693
1729
|
<div id="mem-tab-usage" style="display:none"><div class="loading-txt">Loading…</div></div>
|
|
@@ -1704,7 +1740,7 @@ textarea.sess-note-input:focus { border-color:var(--accent); }
|
|
|
1704
1740
|
<div style="color:var(--text-lo);font-size:13px">Select a swarm run</div>
|
|
1705
1741
|
</div>
|
|
1706
1742
|
</div>
|
|
1707
|
-
<button class="btn"
|
|
1743
|
+
<button class="btn" style="margin-top:10px;color:var(--red);border-color:var(--red)" onclick="cleanSwarmData()">⌫ Clean Data</button>
|
|
1708
1744
|
</div>
|
|
1709
1745
|
<div id="mem-tab-chunks" style="display:none">
|
|
1710
1746
|
<div class="chunk-stats-bar" id="chunk-stats-bar">
|
|
@@ -1739,7 +1775,7 @@ textarea.sess-note-input:focus { border-color:var(--accent); }
|
|
|
1739
1775
|
<div id="orgs-list-head">
|
|
1740
1776
|
<div class="orgs-view-title">Orgs <span id="orgs-proj-label" style="font-size:11px;font-weight:400;opacity:0.5;margin-left:6px;"></span></div>
|
|
1741
1777
|
<div class="orgs-view-sub" id="orgs-view-sub">MASTERMIND organizations</div>
|
|
1742
|
-
<
|
|
1778
|
+
<input id="org-list-filter" class="filter-input" placeholder="Filter orgs…" oninput="filterOrgList(this.value)" style="width:100%;margin-top:8px">
|
|
1743
1779
|
</div>
|
|
1744
1780
|
<div id="orgs-list-scroll">
|
|
1745
1781
|
<div class="loading-txt" id="orgs-list-content">Loading…</div>
|
|
@@ -1760,42 +1796,18 @@ textarea.sess-note-input:focus { border-color:var(--accent); }
|
|
|
1760
1796
|
<span class="odh-pill" id="odh-topo">—</span>
|
|
1761
1797
|
<span class="odh-pill" id="odh-roles">0 roles</span>
|
|
1762
1798
|
<div class="odh-right">
|
|
1763
|
-
<button class="btn" id="org-copy-btn" onclick="v2ShowCopyOrgDialog()"
|
|
1764
|
-
<button class="btn" id="org-stop-btn"
|
|
1765
|
-
</div>
|
|
1766
|
-
</div>
|
|
1767
|
-
|
|
1768
|
-
<!-- Copy org dialog (overlay within the detail pane) -->
|
|
1769
|
-
<div id="org-copy-dialog" style="display:none;position:absolute;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.55);z-index:200;align-items:center;justify-content:center">
|
|
1770
|
-
<div style="background:var(--surface);border:1px solid var(--border);border-radius:10px;padding:24px 28px;min-width:320px;max-width:480px;width:90%;box-shadow:0 8px 32px rgba(0,0,0,0.4)">
|
|
1771
|
-
<div style="font-size:13px;font-weight:600;color:var(--text-hi);margin-bottom:4px">Copy org to another project</div>
|
|
1772
|
-
<div style="font-size:11px;color:var(--text-lo);margin-bottom:16px">The org config will be copied into the selected project's <code style="font-family:var(--mono)">.monomind/orgs/</code> directory.</div>
|
|
1773
|
-
<div style="margin-bottom:12px">
|
|
1774
|
-
<label style="font-size:11px;color:var(--text-lo);display:block;margin-bottom:5px">Destination project</label>
|
|
1775
|
-
<select id="org-copy-dest-select" title="Select destination project" onchange="if(this.value!=='__custom__')document.getElementById('org-copy-dest-input').value=this.value" style="width:100%;background:var(--surface-hi);border:1px solid var(--border);border-radius:5px;padding:7px 10px;font-size:12px;color:var(--text-hi);font-family:var(--mono)">
|
|
1776
|
-
<option value="">Loading projects…</option>
|
|
1777
|
-
</select>
|
|
1778
|
-
</div>
|
|
1779
|
-
<div style="margin-bottom:14px">
|
|
1780
|
-
<label style="font-size:11px;color:var(--text-lo);display:block;margin-bottom:5px">Or enter a custom absolute path</label>
|
|
1781
|
-
<input id="org-copy-dest-input" type="text" placeholder="/absolute/path/to/project" style="width:100%;background:var(--surface-hi);border:1px solid var(--border);border-radius:5px;padding:7px 10px;font-size:12px;color:var(--text-hi);font-family:var(--mono);box-sizing:border-box" />
|
|
1782
|
-
</div>
|
|
1783
|
-
<div id="org-copy-feedback" style="font-size:11px;margin-bottom:10px;min-height:16px"></div>
|
|
1784
|
-
<div style="display:flex;gap:8px;justify-content:flex-end">
|
|
1785
|
-
<button class="btn" title="Discard and close copy dialog" onclick="v2HideCopyOrgDialog()">Cancel</button>
|
|
1786
|
-
<button class="btn" id="org-copy-confirm-btn" title="Copy org to selected project" onclick="v2DoCopyOrg()" style="color:var(--accent);border-color:var(--accent)">Copy</button>
|
|
1787
|
-
</div>
|
|
1799
|
+
<button class="btn" id="org-copy-btn" onclick="v2ShowCopyOrgDialog()" style="color:var(--accent);border-color:var(--accent)">Copy to…</button>
|
|
1800
|
+
<button class="btn" id="org-stop-btn" onclick="v2StopOrg()" style="display:none;color:var(--red);border-color:oklch(60% 0.18 25 / 0.4)">Stop</button>
|
|
1788
1801
|
</div>
|
|
1789
1802
|
</div>
|
|
1790
|
-
|
|
1791
1803
|
<div id="org-detail-tabs">
|
|
1792
|
-
<button class="odt-btn active" data-tab="chart"
|
|
1793
|
-
<button class="odt-btn" data-tab="activity"
|
|
1794
|
-
<button class="odt-btn" data-tab="health"
|
|
1795
|
-
<button class="odt-btn" data-tab="approvals"
|
|
1796
|
-
<button class="odt-btn" data-tab="budgets"
|
|
1797
|
-
<button class="odt-btn" data-tab="charts"
|
|
1798
|
-
<button class="odt-btn" data-tab="skills"
|
|
1804
|
+
<button class="odt-btn active" data-tab="chart" onclick="v2SwitchOrgTab('chart')">Chart</button>
|
|
1805
|
+
<button class="odt-btn" data-tab="activity" onclick="v2SwitchOrgTab('activity')">Activity</button>
|
|
1806
|
+
<button class="odt-btn" data-tab="health" onclick="v2SwitchOrgTab('health')">Health</button>
|
|
1807
|
+
<button class="odt-btn" data-tab="approvals" onclick="v2SwitchOrgTab('approvals')">Approvals</button>
|
|
1808
|
+
<button class="odt-btn" data-tab="budgets" onclick="v2SwitchOrgTab('budgets')">Budgets</button>
|
|
1809
|
+
<button class="odt-btn" data-tab="charts" onclick="v2SwitchOrgTab('charts')">Charts</button>
|
|
1810
|
+
<button class="odt-btn" data-tab="skills" onclick="v2SwitchOrgTab('skills')">Skills</button>
|
|
1799
1811
|
</div>
|
|
1800
1812
|
<div id="org-detail-body">
|
|
1801
1813
|
<div class="odt-pane active" id="odt-chart"></div>
|
|
@@ -1829,7 +1841,27 @@ textarea.sess-note-input:focus { border-color:var(--accent); }
|
|
|
1829
1841
|
<div class="odt-pane" id="odt-threads"></div>
|
|
1830
1842
|
</div>
|
|
1831
1843
|
</div>
|
|
1844
|
+
<div id="org-copy-dialog" style="display:none;position:absolute;inset:0;background:rgba(0,0,0,0.55);z-index:90;align-items:center;justify-content:center">
|
|
1845
|
+
<div style="background:var(--surface);border:1px solid var(--border);border-radius:10px;padding:24px;width:320px;display:flex;flex-direction:column;gap:14px">
|
|
1846
|
+
<div style="font-size:13px;font-weight:600;color:var(--text-hi)">Copy org to project</div>
|
|
1847
|
+
<div><input id="org-copy-dest" class="filter-input" placeholder="Destination project path…" style="width:100%"></div>
|
|
1848
|
+
<div style="display:flex;gap:8px;justify-content:flex-end">
|
|
1849
|
+
<button class="btn" onclick="document.getElementById('org-copy-dialog').style.display='none'">Cancel</button>
|
|
1850
|
+
<button class="btn" onclick="v2DoCopyOrg()" style="color:var(--accent);border-color:var(--accent)">Copy</button>
|
|
1851
|
+
</div>
|
|
1852
|
+
</div>
|
|
1853
|
+
</div>
|
|
1832
1854
|
<!-- agent detail drawer (node-click / role-card-click) -->
|
|
1855
|
+
<div id="org-copy-dialog" style="display:none;position:absolute;inset:0;background:rgba(0,0,0,0.55);z-index:90;align-items:center;justify-content:center">
|
|
1856
|
+
<div style="background:var(--surface);border:1px solid var(--border);border-radius:10px;padding:24px;width:320px;display:flex;flex-direction:column;gap:14px">
|
|
1857
|
+
<div style="font-size:13px;font-weight:600;color:var(--text-hi)">Copy org to project</div>
|
|
1858
|
+
<input id="org-copy-dest" class="filter-input" placeholder="Destination project path…" style="width:100%">
|
|
1859
|
+
<div style="display:flex;gap:8px;justify-content:flex-end">
|
|
1860
|
+
<button class="btn" onclick="document.getElementById('org-copy-dialog').style.display='none'">Cancel</button>
|
|
1861
|
+
<button class="btn" onclick="v2DoCopyOrg()" style="color:var(--accent);border-color:var(--accent)">Copy</button>
|
|
1862
|
+
</div>
|
|
1863
|
+
</div>
|
|
1864
|
+
</div>
|
|
1833
1865
|
<div id="org-agent-drawer" aria-hidden="true">
|
|
1834
1866
|
<div id="oad-head"></div>
|
|
1835
1867
|
<div id="oad-body"></div>
|
|
@@ -1844,13 +1876,13 @@ textarea.sess-note-input:focus { border-color:var(--accent); }
|
|
|
1844
1876
|
<div class="pg-sub">Knowledge graph — dependencies, structure, analysis</div>
|
|
1845
1877
|
<!-- Tab bar -->
|
|
1846
1878
|
<div class="mg-tab-bar">
|
|
1847
|
-
<button class="odt-btn active" data-mgtab="overview"
|
|
1848
|
-
<button class="odt-btn" data-mgtab="graph"
|
|
1849
|
-
<button class="odt-btn" data-mgtab="analyze"
|
|
1850
|
-
<button class="odt-btn" data-mgtab="query"
|
|
1851
|
-
<button class="odt-btn" data-mgtab="export"
|
|
1852
|
-
<button class="odt-btn" data-mgtab="report"
|
|
1853
|
-
<button class="odt-btn" data-mgtab="wiki"
|
|
1879
|
+
<button class="odt-btn active" data-mgtab="overview" onclick="mgSwitchTab('overview')">Overview</button>
|
|
1880
|
+
<button class="odt-btn" data-mgtab="graph" onclick="mgSwitchTab('graph')">Graph</button>
|
|
1881
|
+
<button class="odt-btn" data-mgtab="analyze" onclick="mgSwitchTab('analyze')">Analyze</button>
|
|
1882
|
+
<button class="odt-btn" data-mgtab="query" onclick="mgSwitchTab('query')">Query</button>
|
|
1883
|
+
<button class="odt-btn" data-mgtab="export" onclick="mgSwitchTab('export')">Export</button>
|
|
1884
|
+
<button class="odt-btn" data-mgtab="report" onclick="mgSwitchTab('report')">Report</button>
|
|
1885
|
+
<button class="odt-btn" data-mgtab="wiki" onclick="mgSwitchTab('wiki')">Wiki</button>
|
|
1854
1886
|
</div>
|
|
1855
1887
|
|
|
1856
1888
|
<!-- TAB 1: OVERVIEW -->
|
|
@@ -1876,9 +1908,9 @@ textarea.sess-note-input:focus { border-color:var(--accent); }
|
|
|
1876
1908
|
<div class="mg-pane" id="mg-tab-graph">
|
|
1877
1909
|
<iframe id="mg-iframe" src="" style="width:100%;height:500px;border:none;border-radius:6px;background:var(--surface);" title="Monograph graph"></iframe>
|
|
1878
1910
|
<div class="mg-controls-row">
|
|
1879
|
-
<button class="btn" id="mg-watch-btn"
|
|
1880
|
-
<button class="btn" id="mg-rebuild-btn"
|
|
1881
|
-
<a class="btn" id="mg-open-tab-link" href="#"
|
|
1911
|
+
<button class="btn" id="mg-watch-btn" onclick="mgToggleWatch()">WATCH</button>
|
|
1912
|
+
<button class="btn" id="mg-rebuild-btn" onclick="mgRebuild()">REBUILD</button>
|
|
1913
|
+
<a class="btn" id="mg-open-tab-link" href="#" target="_blank" rel="noopener">OPEN IN TAB</a>
|
|
1882
1914
|
<span class="mg-watch-indicator" id="mg-watch-status">○ IDLE</span>
|
|
1883
1915
|
</div>
|
|
1884
1916
|
</div>
|
|
@@ -1912,12 +1944,12 @@ textarea.sess-note-input:focus { border-color:var(--accent); }
|
|
|
1912
1944
|
<div class="mqs-title">Impact analysis</div>
|
|
1913
1945
|
<div class="mg-query-row">
|
|
1914
1946
|
<input type="text" id="mg-q-impact-node" placeholder="Node ID / name…" style="flex:1;min-width:140px" onkeydown="if(event.key==='Enter')mgQueryImpact()">
|
|
1915
|
-
<select id="mg-q-impact-dir"
|
|
1947
|
+
<select id="mg-q-impact-dir" style="width:120px">
|
|
1916
1948
|
<option value="both">Both</option>
|
|
1917
1949
|
<option value="upstream">Upstream</option>
|
|
1918
1950
|
<option value="downstream">Downstream</option>
|
|
1919
1951
|
</select>
|
|
1920
|
-
<button class="btn"
|
|
1952
|
+
<button class="btn" onclick="mgQueryImpact()">Run</button>
|
|
1921
1953
|
</div>
|
|
1922
1954
|
<div id="mg-q-impact-result" class="mg-query-result" style="display:none"></div>
|
|
1923
1955
|
</div>
|
|
@@ -1925,7 +1957,7 @@ textarea.sess-note-input:focus { border-color:var(--accent); }
|
|
|
1925
1957
|
<div class="mqs-title">Context</div>
|
|
1926
1958
|
<div class="mg-query-row">
|
|
1927
1959
|
<input type="text" id="mg-q-ctx-node" placeholder="Node ID / file path…" style="flex:1;min-width:200px" onkeydown="if(event.key==='Enter')mgQueryContext()">
|
|
1928
|
-
<button class="btn"
|
|
1960
|
+
<button class="btn" onclick="mgQueryContext()">Run</button>
|
|
1929
1961
|
</div>
|
|
1930
1962
|
<div id="mg-q-ctx-result" class="mg-query-result" style="display:none"></div>
|
|
1931
1963
|
</div>
|
|
@@ -1933,8 +1965,8 @@ textarea.sess-note-input:focus { border-color:var(--accent); }
|
|
|
1933
1965
|
<div class="mqs-title">Shortest path</div>
|
|
1934
1966
|
<div class="mg-query-row">
|
|
1935
1967
|
<input type="text" id="mg-q-path-from" placeholder="From node…" style="flex:1;min-width:120px" onkeydown="if(event.key==='Enter')mgQueryPath()">
|
|
1936
|
-
<input type="text" id="mg-q-path-to" placeholder="To node…" style="flex:1;min-width:120px"
|
|
1937
|
-
<button class="btn"
|
|
1968
|
+
<input type="text" id="mg-q-path-to" placeholder="To node…" style="flex:1;min-width:120px">
|
|
1969
|
+
<button class="btn" onclick="mgQueryPath()">Run</button>
|
|
1938
1970
|
</div>
|
|
1939
1971
|
<div id="mg-q-path-result" class="mg-query-result" style="display:none"></div>
|
|
1940
1972
|
</div>
|
|
@@ -1942,7 +1974,7 @@ textarea.sess-note-input:focus { border-color:var(--accent); }
|
|
|
1942
1974
|
<div class="mqs-title">Cypher query</div>
|
|
1943
1975
|
<div class="mg-query-row" style="flex-direction:column;align-items:stretch">
|
|
1944
1976
|
<textarea id="mg-q-cypher" placeholder="MATCH (n) RETURN n.name LIMIT 20"></textarea>
|
|
1945
|
-
<button class="btn"
|
|
1977
|
+
<button class="btn" style="align-self:flex-end;margin-top:6px" onclick="mgQueryCypher()">Run</button>
|
|
1946
1978
|
</div>
|
|
1947
1979
|
<div id="mg-q-cypher-result" class="mg-query-result" style="display:none"></div>
|
|
1948
1980
|
</div>
|
|
@@ -1950,31 +1982,31 @@ textarea.sess-note-input:focus { border-color:var(--accent); }
|
|
|
1950
1982
|
<div class="mqs-title">Ask the graph</div>
|
|
1951
1983
|
<div class="mg-query-row">
|
|
1952
1984
|
<input type="text" id="mg-q-ask" placeholder="Question or keyword…" style="flex:1;min-width:180px" onkeydown="if(event.key==='Enter')mgQueryAsk()">
|
|
1953
|
-
<select id="mg-q-ask-mode"
|
|
1985
|
+
<select id="mg-q-ask-mode" style="width:110px">
|
|
1954
1986
|
<option value="search">Search</option>
|
|
1955
1987
|
<option value="explain">Explain</option>
|
|
1956
1988
|
<option value="neighbors">Neighbors</option>
|
|
1957
1989
|
</select>
|
|
1958
|
-
<select id="mg-q-ask-budget"
|
|
1990
|
+
<select id="mg-q-ask-budget" style="width:80px">
|
|
1959
1991
|
<option value="100">100</option>
|
|
1960
1992
|
<option value="500">500</option>
|
|
1961
1993
|
<option value="1000">1000</option>
|
|
1962
1994
|
</select>
|
|
1963
|
-
<button class="btn"
|
|
1995
|
+
<button class="btn" onclick="mgQueryAsk()">Ask</button>
|
|
1964
1996
|
</div>
|
|
1965
1997
|
<div id="mg-q-ask-result" class="mg-query-result" style="display:none"></div>
|
|
1966
1998
|
</div>
|
|
1967
1999
|
<div class="mg-query-section">
|
|
1968
2000
|
<div class="mqs-title">Ripple impact <span style="font-size:10px;color:var(--text-xs);font-weight:400">multi-hop cascade</span></div>
|
|
1969
2001
|
<div class="mg-query-row">
|
|
1970
|
-
<input type="text" id="mg-q-ripple-node" placeholder="Node name or file…" style="flex:1;min-width:160px"
|
|
2002
|
+
<input type="text" id="mg-q-ripple-node" placeholder="Node name or file…" style="flex:1;min-width:160px">
|
|
1971
2003
|
<select id="mg-q-ripple-hops" style="width:80px" title="Max hops">
|
|
1972
2004
|
<option value="2">2 hops</option>
|
|
1973
2005
|
<option value="3" selected>3 hops</option>
|
|
1974
2006
|
<option value="4">4 hops</option>
|
|
1975
2007
|
<option value="5">5 hops</option>
|
|
1976
2008
|
</select>
|
|
1977
|
-
<button class="btn"
|
|
2009
|
+
<button class="btn" onclick="mgQueryRipple()">Run</button>
|
|
1978
2010
|
</div>
|
|
1979
2011
|
<div id="mg-q-ripple-result" class="mg-query-result" style="display:none"></div>
|
|
1980
2012
|
</div>
|
|
@@ -1983,7 +2015,7 @@ textarea.sess-note-input:focus { border-color:var(--accent); }
|
|
|
1983
2015
|
<div class="mg-query-row">
|
|
1984
2016
|
<input type="text" id="mg-q-chain-from" placeholder="From node…" style="flex:1;min-width:120px" onkeydown="if(event.key==='Enter')mgQueryImportChain()">
|
|
1985
2017
|
<input type="text" id="mg-q-chain-to" placeholder="To node…" style="flex:1;min-width:120px" onkeydown="if(event.key==='Enter')mgQueryImportChain()">
|
|
1986
|
-
<button class="btn"
|
|
2018
|
+
<button class="btn" onclick="mgQueryImportChain()">Run</button>
|
|
1987
2019
|
</div>
|
|
1988
2020
|
<div id="mg-q-chain-result" class="mg-query-result" style="display:none"></div>
|
|
1989
2021
|
</div>
|
|
@@ -1999,7 +2031,7 @@ textarea.sess-note-input:focus { border-color:var(--accent); }
|
|
|
1999
2031
|
<!-- TAB 6: REPORT -->
|
|
2000
2032
|
<div class="mg-pane" id="mg-tab-report">
|
|
2001
2033
|
<div style="display:flex;gap:8px;align-items:center;margin-bottom:12px">
|
|
2002
|
-
<button class="btn"
|
|
2034
|
+
<button class="btn" onclick="mgLoadReport()">REFRESH REPORT</button>
|
|
2003
2035
|
</div>
|
|
2004
2036
|
<div id="mg-report-content"><div class="loading-txt">Loading…</div></div>
|
|
2005
2037
|
</div>
|
|
@@ -2013,14 +2045,14 @@ textarea.sess-note-input:focus { border-color:var(--accent); }
|
|
|
2013
2045
|
</div>
|
|
2014
2046
|
<div class="mg-filter-pills" id="mg-wiki-pills"></div>
|
|
2015
2047
|
<div class="mg-mode-toggle">
|
|
2016
|
-
<button class="btn" id="mg-wiki-mode-all"
|
|
2017
|
-
<button class="btn" id="mg-wiki-mode-docs"
|
|
2018
|
-
<button class="btn" id="mg-wiki-mode-code"
|
|
2048
|
+
<button class="btn" id="mg-wiki-mode-all" onclick="mgWikiMode('all')" style="opacity:1">All</button>
|
|
2049
|
+
<button class="btn" id="mg-wiki-mode-docs" onclick="mgWikiMode('docs')" style="opacity:0.5">Docs</button>
|
|
2050
|
+
<button class="btn" id="mg-wiki-mode-code" onclick="mgWikiMode('code')" style="opacity:0.5">Code</button>
|
|
2019
2051
|
</div>
|
|
2020
2052
|
<div class="mg-query-row" style="margin-bottom:10px">
|
|
2021
2053
|
<input type="text" id="mg-wiki-search" placeholder="Search nodes…" style="flex:1" oninput="mgWikiSearchDebounced(this.value)">
|
|
2022
|
-
<button class="btn"
|
|
2023
|
-
<button class="btn"
|
|
2054
|
+
<button class="btn" onclick="mgRebuildDocs()" id="mg-build-docs-btn">BUILD DOCS</button>
|
|
2055
|
+
<button class="btn" onclick="mgWikiRefresh()">REFRESH</button>
|
|
2024
2056
|
</div>
|
|
2025
2057
|
<div id="mg-wiki-list"></div>
|
|
2026
2058
|
<div id="mg-wiki-detail" class="mg-detail-panel" style="display:none"></div>
|
|
@@ -2039,24 +2071,43 @@ textarea.sess-note-input:focus { border-color:var(--accent); }
|
|
|
2039
2071
|
</div>
|
|
2040
2072
|
</div>
|
|
2041
2073
|
|
|
2042
|
-
|
|
2043
|
-
<div class="
|
|
2044
|
-
<div
|
|
2045
|
-
<div
|
|
2046
|
-
|
|
2074
|
+
<!-- GLOBAL LOOPS -->
|
|
2075
|
+
<div class="view" id="view-global-loops">
|
|
2076
|
+
<div class="vscroll">
|
|
2077
|
+
<div style="display:flex;align-items:baseline;gap:10px;margin-bottom:4px">
|
|
2078
|
+
<div class="pg-title" style="margin-bottom:0">Global Loops</div>
|
|
2079
|
+
<span class="pg-sub" id="gl-sub" style="margin-bottom:0">Loops across all projects</span>
|
|
2080
|
+
</div>
|
|
2081
|
+
<div id="gl-content" style="margin-top:16px"><div class="loading-txt">Loading…</div></div>
|
|
2047
2082
|
</div>
|
|
2048
|
-
<div id="gl-content" style="margin-top:16px"><div class="loading-txt">Loading…</div></div>
|
|
2049
2083
|
</div>
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
<div class="
|
|
2053
|
-
<div
|
|
2054
|
-
<div
|
|
2055
|
-
|
|
2084
|
+
|
|
2085
|
+
<!-- GLOBAL TOKENS -->
|
|
2086
|
+
<div class="view" id="view-global-tokens">
|
|
2087
|
+
<div class="vscroll">
|
|
2088
|
+
<div style="display:flex;align-items:baseline;gap:10px;margin-bottom:4px">
|
|
2089
|
+
<div class="pg-title" style="margin-bottom:0">Global Tokens</div>
|
|
2090
|
+
<span class="pg-sub" id="gt-sub" style="margin-bottom:0">Token usage across all projects</span>
|
|
2091
|
+
</div>
|
|
2092
|
+
<div id="gt-content" style="margin-top:16px"><div class="loading-txt">Loading…</div></div>
|
|
2093
|
+
</div>
|
|
2094
|
+
</div>
|
|
2095
|
+
|
|
2096
|
+
<div class="view" id="view-chat">
|
|
2097
|
+
<div class="vscroll">
|
|
2098
|
+
<div id="chat-v-bar">
|
|
2099
|
+
<span id="chat-v-bar-title">Agent Chat</span>
|
|
2100
|
+
<select id="chat-v-sel" onchange="chatVSelectSession(this.value)">
|
|
2101
|
+
<option value="">— select a session —</option>
|
|
2102
|
+
</select>
|
|
2103
|
+
<div id="chat-v-live-dot"></div>
|
|
2104
|
+
<span id="chat-v-live-lbl">OFFLINE</span>
|
|
2105
|
+
</div>
|
|
2106
|
+
<div id="chat-v-feed">
|
|
2107
|
+
<div id="chat-v-empty">Select a session above to see agent communications.<br>Agent messages, intercom signals, and system events appear here in real time.</div>
|
|
2108
|
+
</div>
|
|
2056
2109
|
</div>
|
|
2057
|
-
<div id="gt-content" style="margin-top:16px"><div class="loading-txt">Loading…</div></div>
|
|
2058
2110
|
</div>
|
|
2059
|
-
</div>
|
|
2060
2111
|
|
|
2061
2112
|
</div><!-- /view-wrap -->
|
|
2062
2113
|
</div><!-- /main -->
|
|
@@ -2070,12 +2121,12 @@ textarea.sess-note-input:focus { border-color:var(--accent); }
|
|
|
2070
2121
|
<button id="mm-close" onclick="closeMastermind()" aria-label="Close">✕</button>
|
|
2071
2122
|
</div>
|
|
2072
2123
|
<div id="mm-tabs-bar">
|
|
2073
|
-
<button class="mm-tab-btn active" data-mmtab="orgs"
|
|
2074
|
-
<button class="mm-tab-btn" data-mmtab="skills"
|
|
2075
|
-
<button class="mm-tab-btn" data-mmtab="loops"
|
|
2076
|
-
<button class="mm-tab-btn" data-mmtab="createorg"
|
|
2077
|
-
<button class="mm-tab-btn" data-mmtab="metrics"
|
|
2078
|
-
<button class="mm-tab-btn" data-mmtab="graph"
|
|
2124
|
+
<button class="mm-tab-btn active" data-mmtab="orgs" onclick="mmSwitchTab('orgs')">Orgs</button>
|
|
2125
|
+
<button class="mm-tab-btn" data-mmtab="skills" onclick="mmSwitchTab('skills')">Skills</button>
|
|
2126
|
+
<button class="mm-tab-btn" data-mmtab="loops" onclick="mmSwitchTab('loops')">Loops</button>
|
|
2127
|
+
<button class="mm-tab-btn" data-mmtab="createorg" onclick="mmSwitchTab('createorg')">Create Org</button>
|
|
2128
|
+
<button class="mm-tab-btn" data-mmtab="metrics" onclick="mmSwitchTab('metrics')">Metrics</button>
|
|
2129
|
+
<button class="mm-tab-btn" data-mmtab="graph" onclick="mmSwitchTab('graph')">Graph</button>
|
|
2079
2130
|
</div>
|
|
2080
2131
|
<div id="mm-body"></div>
|
|
2081
2132
|
</div>
|
|
@@ -2085,10 +2136,10 @@ textarea.sess-note-input:focus { border-color:var(--accent); }
|
|
|
2085
2136
|
<div id="chunk-modal-box">
|
|
2086
2137
|
<div id="chunk-modal-title">Edit Chunk</div>
|
|
2087
2138
|
<div id="chunk-modal-src"></div>
|
|
2088
|
-
<textarea id="chunk-modal-ta"
|
|
2139
|
+
<textarea id="chunk-modal-ta" spellcheck="false"></textarea>
|
|
2089
2140
|
<div class="chunk-modal-btns">
|
|
2090
|
-
<button class="btn"
|
|
2091
|
-
<button class="btn"
|
|
2141
|
+
<button class="btn" onclick="closeChunkModal()">Cancel</button>
|
|
2142
|
+
<button class="btn" style="color:var(--accent);border-color:var(--accent)" onclick="saveChunkModal()">Save</button>
|
|
2092
2143
|
</div>
|
|
2093
2144
|
</div>
|
|
2094
2145
|
</div>
|
|
@@ -2096,10 +2147,10 @@ textarea.sess-note-input:focus { border-color:var(--accent); }
|
|
|
2096
2147
|
<div id="mem-modal" onclick="if(event.target===this)closeMemModal()">
|
|
2097
2148
|
<div id="mem-modal-box">
|
|
2098
2149
|
<div id="mem-modal-title">Edit Memory</div>
|
|
2099
|
-
<textarea id="mem-modal-ta"
|
|
2150
|
+
<textarea id="mem-modal-ta" spellcheck="false"></textarea>
|
|
2100
2151
|
<div class="mem-modal-btns">
|
|
2101
|
-
<button class="btn"
|
|
2102
|
-
<button class="btn"
|
|
2152
|
+
<button class="btn" onclick="closeMemModal()">Cancel</button>
|
|
2153
|
+
<button class="btn" style="color:var(--accent);border-color:var(--accent)" onclick="saveMemModal()">Save</button>
|
|
2103
2154
|
</div>
|
|
2104
2155
|
</div>
|
|
2105
2156
|
</div>
|
|
@@ -2110,8 +2161,8 @@ textarea.sess-note-input:focus { border-color:var(--accent); }
|
|
|
2110
2161
|
<div id="report-box">
|
|
2111
2162
|
<div class="rp-head">
|
|
2112
2163
|
<span class="rp-title">Report Card</span>
|
|
2113
|
-
<button class="rp-copy-btn" onclick="copyReportCard()"
|
|
2114
|
-
<button class="rp-close-btn" onclick="closeReportCard()"
|
|
2164
|
+
<button class="rp-copy-btn" onclick="copyReportCard()">⎘ Copy</button>
|
|
2165
|
+
<button class="rp-close-btn" onclick="closeReportCard()">✕</button>
|
|
2115
2166
|
</div>
|
|
2116
2167
|
<div id="report-content"><pre id="report-pre"></pre></div>
|
|
2117
2168
|
</div>
|
|
@@ -2120,7 +2171,7 @@ textarea.sess-note-input:focus { border-color:var(--accent); }
|
|
|
2120
2171
|
<!-- shortcut help modal -->
|
|
2121
2172
|
<div id="shortcut-modal" onclick="if(event.target===this)closeShortcutHelp()">
|
|
2122
2173
|
<div id="shortcut-box">
|
|
2123
|
-
<div class="sk-title">Keyboard Shortcuts <button class="sk-close" onclick="closeShortcutHelp()"
|
|
2174
|
+
<div class="sk-title">Keyboard Shortcuts <button class="sk-close" onclick="closeShortcutHelp()">✕</button></div>
|
|
2124
2175
|
<div class="sk-section">Sessions view</div>
|
|
2125
2176
|
<div class="sk-row"><span class="sk-desc">Navigate rows</span><span class="sk-keys"><kbd>J</kbd><kbd>K</kbd></span></div>
|
|
2126
2177
|
<div class="sk-row"><span class="sk-desc">Open focused session</span><span class="sk-keys"><kbd>↵</kbd></span></div>
|
|
@@ -2129,17 +2180,12 @@ textarea.sess-note-input:focus { border-color:var(--accent); }
|
|
|
2129
2180
|
<div class="sk-row"><span class="sk-desc">Open detail drawer</span><span class="sk-keys"><kbd>↵</kbd></span></div>
|
|
2130
2181
|
<div class="sk-row"><span class="sk-desc">Search in feed</span><span class="sk-keys"><kbd>/</kbd></span></div>
|
|
2131
2182
|
<div class="sk-row"><span class="sk-desc">Jump to live session</span><span class="sk-keys"><kbd>G</kbd></span></div>
|
|
2183
|
+
<div class="sk-row"><span class="sk-desc">Refresh current view</span><span class="sk-keys"><kbd>R</kbd></span></div>
|
|
2132
2184
|
<div class="sk-row"><span class="sk-desc">Toggle ambient mode</span><span class="sk-keys"><kbd>A</kbd></span></div>
|
|
2133
2185
|
<div class="sk-section">Global</div>
|
|
2134
|
-
<div class="sk-row"><span class="sk-desc">Refresh current view</span><span class="sk-keys"><kbd>R</kbd></span></div>
|
|
2135
|
-
<div class="sk-row"><span class="sk-desc">Open Mastermind overlay</span><span class="sk-keys"><kbd>M</kbd></span></div>
|
|
2136
2186
|
<div class="sk-row"><span class="sk-desc">Command palette</span><span class="sk-keys"><kbd>⌘</kbd><kbd>K</kbd></span></div>
|
|
2137
2187
|
<div class="sk-row"><span class="sk-desc">Close / dismiss</span><span class="sk-keys"><kbd>Esc</kbd></span></div>
|
|
2138
2188
|
<div class="sk-row"><span class="sk-desc">This help</span><span class="sk-keys"><kbd>?</kbd></span></div>
|
|
2139
|
-
<div class="sk-section">View navigation</div>
|
|
2140
|
-
<div class="sk-row"><span class="sk-desc">Now / Sessions / Projects</span><span class="sk-keys"><kbd>1</kbd><kbd>2</kbd><kbd>3</kbd></span></div>
|
|
2141
|
-
<div class="sk-row"><span class="sk-desc">Loops / Tokens / Memory</span><span class="sk-keys"><kbd>4</kbd><kbd>5</kbd><kbd>6</kbd></span></div>
|
|
2142
|
-
<div class="sk-row"><span class="sk-desc">Orgs / Monograph / Global</span><span class="sk-keys"><kbd>7</kbd><kbd>8</kbd><kbd>9</kbd></span></div>
|
|
2143
2189
|
</div>
|
|
2144
2190
|
</div>
|
|
2145
2191
|
|
|
@@ -2156,8 +2202,8 @@ textarea.sess-note-input:focus { border-color:var(--accent); }
|
|
|
2156
2202
|
<input class="bm-input" id="bm-monthly" type="number" min="0" step="10" placeholder="e.g. 200">
|
|
2157
2203
|
</div>
|
|
2158
2204
|
<div class="bm-btns">
|
|
2159
|
-
<button class="bm-cancel"
|
|
2160
|
-
<button class="bm-save"
|
|
2205
|
+
<button class="bm-cancel" onclick="closeBudgetModal()">Cancel</button>
|
|
2206
|
+
<button class="bm-save" onclick="saveBudget()">Save</button>
|
|
2161
2207
|
</div>
|
|
2162
2208
|
</div>
|
|
2163
2209
|
</div>
|
|
@@ -2177,13 +2223,13 @@ let userScrolled = false;
|
|
|
2177
2223
|
let selectedEntryId = null;
|
|
2178
2224
|
let allDrawers = [];
|
|
2179
2225
|
let dismissedAlerts = new Set();
|
|
2180
|
-
let alertState = { todayCost: 0,
|
|
2226
|
+
let alertState = { todayCost: 0, errorCount: 0, longLoops: [], anomaly: null, budgetAlert: null, budgetCls: 'alert-warn' };
|
|
2181
2227
|
let feedTimeFilter = 'all';
|
|
2182
2228
|
let cmdFocusIdx = 0;
|
|
2183
2229
|
let cmdItems = [];
|
|
2184
2230
|
let liveTailMode = false;
|
|
2185
2231
|
let liveTailTimer = null;
|
|
2186
|
-
let bookmarks = new Set(
|
|
2232
|
+
let bookmarks; try { bookmarks = new Set(JSON.parse(localStorage.getItem('mm-bookmarks') || '[]')); } catch { bookmarks = new Set(); }
|
|
2187
2233
|
let showStarredOnly = false;
|
|
2188
2234
|
|
|
2189
2235
|
// ── nav ────────────────────────────────────────────────────
|
|
@@ -2201,13 +2247,14 @@ function switchView(v) {
|
|
|
2201
2247
|
el.classList.toggle('active', el.dataset.view === v));
|
|
2202
2248
|
document.querySelectorAll('.view').forEach(el =>
|
|
2203
2249
|
el.classList.toggle('active', el.id === 'view-' + v));
|
|
2204
|
-
const titles = { now:'Now', projects:'Projects', sessions:'Sessions', loops:'Loops', tokens:'Tokens', memory:'Memory', orgs:'Orgs', monograph:'Monograph', global:'Global Feed', 'global-loops':'Global Loops', 'global-tokens':'Global Tokens' };
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
const
|
|
2208
|
-
document.title =
|
|
2250
|
+
const titles = { now:'Now', projects:'Projects', sessions:'Sessions', loops:'Loops', tokens:'Tokens', memory:'Memory', orgs:'Orgs', monograph:'Monograph', global:'Global Feed', 'global-loops':'Global Loops', 'global-tokens':'Global Tokens', chat:'Agent Chat' };
|
|
2251
|
+
document.getElementById('view-title').textContent = titles[v] || v;
|
|
2252
|
+
const PROJECT = DIR ? shortPath(DIR) : 'monomind';
|
|
2253
|
+
const VIEW_LABELS = { now: 'Now', sessions: 'Sessions', projects: 'Projects', loops: 'Loops', tokens: 'Tokens', memory: 'Memory', orgs: 'Orgs', monograph: 'Monograph', global: 'Global Feed', 'global-loops': 'Global Loops', 'global-tokens': 'Global Tokens' };
|
|
2254
|
+
document.title = `monomind · ${PROJECT} · ${VIEW_LABELS[v] || v}`;
|
|
2209
2255
|
// Projects always re-fetches so onclick paths in cards stay current
|
|
2210
2256
|
if (v === 'projects') { renderProjects(); return; }
|
|
2257
|
+
if (v === 'chat') { initChatView(); return; }
|
|
2211
2258
|
if (!viewRendered[v]) { renderView(v); viewRendered[v] = true; }
|
|
2212
2259
|
}
|
|
2213
2260
|
|
|
@@ -2238,9 +2285,7 @@ async function init() {
|
|
|
2238
2285
|
ORIGINAL_DIR = DIR;
|
|
2239
2286
|
gitUser = gu;
|
|
2240
2287
|
document.getElementById('sb-user').textContent = gu.name || gu.email || '—';
|
|
2241
|
-
|
|
2242
|
-
sbPath.textContent = shortPath(DIR);
|
|
2243
|
-
sbPath.title = DIR;
|
|
2288
|
+
document.getElementById('sb-path').textContent = DIR;
|
|
2244
2289
|
document.getElementById('sb-proj').textContent = DIR.split('/').filter(Boolean).pop() || '—';
|
|
2245
2290
|
_showNavProjectCtx(DIR);
|
|
2246
2291
|
} catch (_) {}
|
|
@@ -2273,9 +2318,7 @@ async function init() {
|
|
|
2273
2318
|
} catch (_) {}
|
|
2274
2319
|
DIR = projParam;
|
|
2275
2320
|
document.getElementById('sb-proj').textContent = DIR.split('/').filter(Boolean).pop() || '—';
|
|
2276
|
-
|
|
2277
|
-
sbPath2.textContent = shortPath(DIR);
|
|
2278
|
-
sbPath2.title = DIR;
|
|
2321
|
+
document.getElementById('sb-path').textContent = DIR;
|
|
2279
2322
|
_showNavProjectCtx(DIR);
|
|
2280
2323
|
}
|
|
2281
2324
|
restoreURLParams();
|
|
@@ -2288,28 +2331,16 @@ async function init() {
|
|
|
2288
2331
|
|
|
2289
2332
|
function _setLiveMode(mode) {
|
|
2290
2333
|
const dot = document.querySelector('.live-dot');
|
|
2291
|
-
const pill = dot?.closest('.pill');
|
|
2292
2334
|
if (!dot) return;
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
if (pill) { const t = pill.lastChild; if (t?.nodeType === 3) t.textContent = ' live'; }
|
|
2297
|
-
} else {
|
|
2298
|
-
dot.classList.add('polling');
|
|
2299
|
-
dot.title = 'Polling every 30s (SSE unavailable)';
|
|
2300
|
-
if (pill) { const t = pill.lastChild; if (t?.nodeType === 3) t.textContent = ' polling'; }
|
|
2301
|
-
}
|
|
2335
|
+
dot.classList.toggle('polling', mode === 'poll');
|
|
2336
|
+
const pill = dot.closest('.pill');
|
|
2337
|
+
if (pill) pill.childNodes.forEach(n => { if (n.nodeType === 3) n.textContent = mode === 'poll' ? ' polling' : ' live'; });
|
|
2302
2338
|
}
|
|
2303
2339
|
|
|
2304
2340
|
function startPolling() {
|
|
2305
|
-
clearInterval(pollTimer);
|
|
2306
2341
|
_setLiveMode('poll');
|
|
2307
|
-
pollTimer
|
|
2308
|
-
|
|
2309
|
-
else if (currentView === 'loops') renderLoops();
|
|
2310
|
-
else viewRendered['loops'] = false; // loops data may be stale — re-fetch on next nav
|
|
2311
|
-
loadLoopMetrics();
|
|
2312
|
-
}, 30000);
|
|
2342
|
+
clearInterval(pollTimer);
|
|
2343
|
+
pollTimer = setInterval(() => { if (currentView === 'now') refreshNowSilent(); }, 30000);
|
|
2313
2344
|
}
|
|
2314
2345
|
|
|
2315
2346
|
let _sseSource = null;
|
|
@@ -2319,18 +2350,24 @@ function initSSE() {
|
|
|
2319
2350
|
try {
|
|
2320
2351
|
const src = new EventSource('/api/events-stream?dir=' + enc(DIR));
|
|
2321
2352
|
src.addEventListener('update', () => { if (currentView === 'now') refreshNowSilent(); });
|
|
2322
|
-
src.addEventListener('connected', () => {
|
|
2353
|
+
src.addEventListener('connected', () => {});
|
|
2323
2354
|
src.onerror = () => { src.close(); _sseSource = null; startPolling(); };
|
|
2324
2355
|
_sseSource = src;
|
|
2325
2356
|
clearInterval(pollTimer); // SSE replaces polling
|
|
2326
|
-
_setLiveMode('sse');
|
|
2327
2357
|
} catch { startPolling(); }
|
|
2328
2358
|
}
|
|
2329
2359
|
|
|
2330
|
-
async function apiFetch(url) {
|
|
2331
|
-
const
|
|
2332
|
-
|
|
2333
|
-
|
|
2360
|
+
async function apiFetch(url, timeout = 15000) {
|
|
2361
|
+
const ctrl = new AbortController();
|
|
2362
|
+
const tid = setTimeout(() => ctrl.abort(), timeout);
|
|
2363
|
+
try {
|
|
2364
|
+
const r = await fetch(url, { signal: ctrl.signal });
|
|
2365
|
+
if (!r.ok) throw new Error(`HTTP ${r.status}${r.statusText ? ' ' + r.statusText : ''}`);
|
|
2366
|
+
return r.json();
|
|
2367
|
+
} catch (e) {
|
|
2368
|
+
if (e.name === 'AbortError') throw new Error('Request timed out');
|
|
2369
|
+
throw e;
|
|
2370
|
+
} finally { clearTimeout(tid); }
|
|
2334
2371
|
}
|
|
2335
2372
|
|
|
2336
2373
|
// ── project switching ──────────────────────────────────────
|
|
@@ -2362,9 +2399,7 @@ function switchProject(path) {
|
|
|
2362
2399
|
_mgLoaded = false;
|
|
2363
2400
|
_mgGraph = null;
|
|
2364
2401
|
document.getElementById('sb-proj').textContent = path.split('/').filter(Boolean).pop() || '—';
|
|
2365
|
-
|
|
2366
|
-
sbPath3.textContent = shortPath(path);
|
|
2367
|
-
sbPath3.title = path;
|
|
2402
|
+
document.getElementById('sb-path').textContent = path;
|
|
2368
2403
|
_showNavProjectCtx(path);
|
|
2369
2404
|
viewRendered = {};
|
|
2370
2405
|
allSessions = [];
|
|
@@ -2422,7 +2457,7 @@ async function loadFeed() {
|
|
|
2422
2457
|
allSessions = sessions;
|
|
2423
2458
|
document.getElementById('bdg-sessions').textContent = sessions.length || '—';
|
|
2424
2459
|
if (!sessions.length) {
|
|
2425
|
-
setFeedContent('<div class="feed-empty">No sessions yet in this project
|
|
2460
|
+
setFeedContent('<div class="feed-empty">No sessions yet in this project.</div>');
|
|
2426
2461
|
return;
|
|
2427
2462
|
}
|
|
2428
2463
|
sessionIdx = 0;
|
|
@@ -2453,9 +2488,7 @@ async function loadFeedSilent() {
|
|
|
2453
2488
|
|
|
2454
2489
|
async function loadFeedForSession(sess) {
|
|
2455
2490
|
if (!sess) return;
|
|
2456
|
-
|
|
2457
|
-
feedSessEl.textContent = sess.id.slice(0, 8) + '…';
|
|
2458
|
-
feedSessEl.title = sess.id;
|
|
2491
|
+
document.getElementById('feed-sess').textContent = sess.id.slice(0, 8) + '…';
|
|
2459
2492
|
document.getElementById('btn-prev-sess').style.opacity = sessionIdx < allSessions.length - 1 ? '1' : '0.3';
|
|
2460
2493
|
document.getElementById('btn-next-sess').style.opacity = sessionIdx > 0 ? '1' : '0.3';
|
|
2461
2494
|
showSessCtx(sess);
|
|
@@ -2513,7 +2546,7 @@ function catLabel(c) {
|
|
|
2513
2546
|
|
|
2514
2547
|
function renderFeedEvents(events, silent) {
|
|
2515
2548
|
if (!events.length) {
|
|
2516
|
-
if (!silent) setFeedContent('<div class="feed-empty">No events in this session yet
|
|
2549
|
+
if (!silent) setFeedContent('<div class="feed-empty">No events in this session yet.</div>');
|
|
2517
2550
|
return;
|
|
2518
2551
|
}
|
|
2519
2552
|
|
|
@@ -2530,7 +2563,7 @@ function renderFeedEvents(events, silent) {
|
|
|
2530
2563
|
if (feedTimeFilter !== 'all') {
|
|
2531
2564
|
const ms = { '1h': 3600000, '6h': 21600000, '24h': 86400000 }[feedTimeFilter] || 0;
|
|
2532
2565
|
const cutoff = Date.now() - ms;
|
|
2533
|
-
visible = filtered.filter(ev => !ev.ts || (
|
|
2566
|
+
visible = filtered.filter(ev => !ev.ts || new Date(ev.ts).getTime() >= cutoff);
|
|
2534
2567
|
}
|
|
2535
2568
|
|
|
2536
2569
|
// update error alert state
|
|
@@ -2584,7 +2617,7 @@ function renderFeedEvents(events, silent) {
|
|
|
2584
2617
|
|
|
2585
2618
|
function renderGroupRow(g) {
|
|
2586
2619
|
const { ico, catCls } = toolStyle(g.cat, '');
|
|
2587
|
-
const itemsData = JSON.stringify(g.items).replace(
|
|
2620
|
+
const itemsData = JSON.stringify(g.items).replace(/'/g, ''');
|
|
2588
2621
|
return `<div class="feed-group" data-items='${itemsData}' onclick="expandGroup(this)">
|
|
2589
2622
|
<div class="feed-ico ${catCls}" style="font-size:9px">${ico}</div>
|
|
2590
2623
|
<span class="fg-label">${g.count} ${esc(g.label)}</span>
|
|
@@ -2593,9 +2626,7 @@ function renderGroupRow(g) {
|
|
|
2593
2626
|
}
|
|
2594
2627
|
|
|
2595
2628
|
function expandGroup(el) {
|
|
2596
|
-
|
|
2597
|
-
try { items = JSON.parse(el.dataset.items); } catch { return; }
|
|
2598
|
-
if (!Array.isArray(items)) return;
|
|
2629
|
+
const items = JSON.parse(el.dataset.items);
|
|
2599
2630
|
const html = items.map(renderFeedEntry).join('');
|
|
2600
2631
|
el.outerHTML = html;
|
|
2601
2632
|
// re-apply active feed search to newly injected entries
|
|
@@ -2605,8 +2636,7 @@ function expandGroup(el) {
|
|
|
2605
2636
|
|
|
2606
2637
|
function renderFeedEntry(ev) {
|
|
2607
2638
|
const ts = ev.ts ? relTime(ev.ts) : '';
|
|
2608
|
-
|
|
2609
|
-
let lbl = '', detail = '', lblTitle = '', id = ev.id || ev.uuid || '';
|
|
2639
|
+
let lbl = '', detail = '', id = ev.id || ev.uuid || '';
|
|
2610
2640
|
let catCls, ico;
|
|
2611
2641
|
|
|
2612
2642
|
if (ev.kind === 'tool') {
|
|
@@ -2616,21 +2646,20 @@ function renderFeedEntry(ev) {
|
|
|
2616
2646
|
} else {
|
|
2617
2647
|
ico = '↵'; catCls = 'cat-user';
|
|
2618
2648
|
const t = (ev.text || '').trim();
|
|
2619
|
-
|
|
2620
|
-
else lbl = esc(t);
|
|
2649
|
+
lbl = esc(t.length > 90 ? t.slice(0, 90) + '…' : t);
|
|
2621
2650
|
}
|
|
2622
2651
|
|
|
2623
2652
|
const errClass = ev._errored ? ' errored' : '';
|
|
2624
2653
|
const selClass = selectedEntryId && selectedEntryId === id ? ' selected' : '';
|
|
2625
2654
|
|
|
2626
|
-
const evData = JSON.stringify(ev).replace(
|
|
2655
|
+
const evData = JSON.stringify(ev).replace(/'/g, ''');
|
|
2627
2656
|
return `<div class="feed-entry k-${ev.kind}${errClass}${selClass}" data-ev='${evData}' onclick="openDetail(this.dataset.ev)">
|
|
2628
2657
|
<div class="feed-ico ${catCls}">${ico}</div>
|
|
2629
2658
|
<div class="feed-body">
|
|
2630
|
-
<div class="feed-lbl"
|
|
2659
|
+
<div class="feed-lbl">${lbl}</div>
|
|
2631
2660
|
${detail ? `<div class="feed-detail">${detail}</div>` : ''}
|
|
2632
2661
|
</div>
|
|
2633
|
-
<div class="feed-ts"
|
|
2662
|
+
<div class="feed-ts">${ts}</div>
|
|
2634
2663
|
</div>`;
|
|
2635
2664
|
}
|
|
2636
2665
|
|
|
@@ -2655,9 +2684,7 @@ function setFeedContent(html) {
|
|
|
2655
2684
|
|
|
2656
2685
|
// ── detail panel ───────────────────────────────────────────
|
|
2657
2686
|
function openDetail(evJson) {
|
|
2658
|
-
|
|
2659
|
-
try { ev = JSON.parse(evJson); } catch { return; }
|
|
2660
|
-
if (!ev) return;
|
|
2687
|
+
const ev = JSON.parse(evJson);
|
|
2661
2688
|
selectedEntryId = ev.id || ev.uuid || '';
|
|
2662
2689
|
|
|
2663
2690
|
const panel = document.getElementById('detail-panel');
|
|
@@ -2668,23 +2695,20 @@ function openDetail(evJson) {
|
|
|
2668
2695
|
|
|
2669
2696
|
if (ev.kind === 'tool') {
|
|
2670
2697
|
const { catCls } = toolStyle(ev.cat, ev.name);
|
|
2671
|
-
const _toolId = (ev.id || '').toString();
|
|
2672
|
-
const _toolLabel = ev.label || ev.name || '';
|
|
2673
2698
|
title = ev.name || 'Tool';
|
|
2674
2699
|
bodyHtml = `
|
|
2675
2700
|
<div class="d-cat-pill ${catCls}" style="font-size:11px">${esc(ev.cat || 'other')}</div>
|
|
2676
|
-
<div class="d-row"><div class="d-lbl">Label</div><div class="d-val"
|
|
2701
|
+
<div class="d-row"><div class="d-lbl">Label</div><div class="d-val">${esc(ev.label || ev.name)}</div></div>
|
|
2677
2702
|
${ev.subagent ? `<div class="d-row"><div class="d-lbl">Subagent</div><div class="d-val">${esc(ev.subagent)}</div></div>` : ''}
|
|
2678
2703
|
${ev._errored ? `<div class="d-row"><div class="d-lbl">Status</div><div class="d-val error">Error</div></div>` : ''}
|
|
2679
|
-
<div class="d-row"><div class="d-lbl">Time</div><div class="d-val">${ev.ts ? new Date(
|
|
2680
|
-
<div class="d-row"><div class="d-lbl">Tool ID</div><div class="d-val
|
|
2704
|
+
<div class="d-row"><div class="d-lbl">Time</div><div class="d-val">${ev.ts ? new Date(ev.ts).toLocaleTimeString() : '—'}</div></div>
|
|
2705
|
+
<div class="d-row"><div class="d-lbl">Tool ID</div><div class="d-val mono">${esc((ev.id || '').slice(0, 24))}</div></div>
|
|
2681
2706
|
`;
|
|
2682
2707
|
} else if (ev.kind === 'user') {
|
|
2683
|
-
const _userText = ev.text || '';
|
|
2684
2708
|
title = 'User message';
|
|
2685
2709
|
bodyHtml = `
|
|
2686
|
-
<div class="d-row"><div class="d-lbl">Time</div><div class="d-val">${ev.ts ? new Date(
|
|
2687
|
-
<div class="d-row"><div class="d-lbl">Message</div><div class="d-val"
|
|
2710
|
+
<div class="d-row"><div class="d-lbl">Time</div><div class="d-val">${ev.ts ? new Date(ev.ts).toLocaleTimeString() : '—'}</div></div>
|
|
2711
|
+
<div class="d-row"><div class="d-lbl">Message</div><div class="d-val" style="white-space:pre-wrap">${esc(ev.text || '')}</div></div>
|
|
2688
2712
|
`;
|
|
2689
2713
|
}
|
|
2690
2714
|
|
|
@@ -2712,10 +2736,10 @@ function buildSparkline() {
|
|
|
2712
2736
|
if (idx >= 0 && idx < DAYS) buckets[idx]++;
|
|
2713
2737
|
}
|
|
2714
2738
|
const max = Math.max(...buckets, 1);
|
|
2715
|
-
//
|
|
2716
|
-
const
|
|
2717
|
-
|
|
2718
|
-
const
|
|
2739
|
+
// offset so first cell starts on Monday of the week 12 weeks ago
|
|
2740
|
+
const todayDow = new Date().getDay(); // 0=Sun
|
|
2741
|
+
// pad start so column 0 begins on Monday
|
|
2742
|
+
const startOffset = todayDow === 0 ? 6 : todayDow - 1;
|
|
2719
2743
|
const cells = buckets.map((v, i) => {
|
|
2720
2744
|
const isToday = i === DAYS - 1;
|
|
2721
2745
|
const level = v === 0 ? 0 : Math.min(4, Math.ceil(v / max * 4));
|
|
@@ -2724,7 +2748,7 @@ function buildSparkline() {
|
|
|
2724
2748
|
const title = `${label}: ${v} session${v !== 1 ? 's' : ''}`;
|
|
2725
2749
|
return `<div class="cal-cell cal-${level}${isToday ? ' cal-today' : ''}" title="${title}"></div>`;
|
|
2726
2750
|
});
|
|
2727
|
-
return `<div class="spark-wrap"><div class="spark-lbl">12-week activity ${buildWowDelta()}</div><div class="cal-grid">${
|
|
2751
|
+
return `<div class="spark-wrap"><div class="spark-lbl">12-week activity ${buildWowDelta()}</div><div class="cal-grid">${cells.join('')}</div></div>`;
|
|
2728
2752
|
}
|
|
2729
2753
|
|
|
2730
2754
|
// ── alerts rail ────────────────────────────────────────────
|
|
@@ -2754,10 +2778,6 @@ function updateAlerts() {
|
|
|
2754
2778
|
all.push({ id: 'loop-' + l, cls: 'alert-warn', ico: '↺', msg: `Long-running loop: ${l}` });
|
|
2755
2779
|
}
|
|
2756
2780
|
|
|
2757
|
-
for (const l of (alertState.hilLoops || [])) {
|
|
2758
|
-
all.push({ id: 'hil-' + l, cls: 'alert-warn', ico: '⚠', msg: `Loop waiting for response: ${l}`, action: `switchView('loops')` });
|
|
2759
|
-
}
|
|
2760
|
-
|
|
2761
2781
|
const visible = all.filter(a => !dismissedAlerts.has(a.id));
|
|
2762
2782
|
if (!visible.length) {
|
|
2763
2783
|
rail.className = '';
|
|
@@ -2767,7 +2787,7 @@ function updateAlerts() {
|
|
|
2767
2787
|
rail.className = 'has-alerts';
|
|
2768
2788
|
rail.innerHTML = visible.map(a =>
|
|
2769
2789
|
`<div class="alert-item ${a.cls}" data-alert-id="${a.id}"${a.action ? ` onclick="${a.action}" style="cursor:pointer"` : ''}>
|
|
2770
|
-
<span class="al-ico">${a.ico}</span>${esc(a.msg)}<span class="al-x"
|
|
2790
|
+
<span class="al-ico">${a.ico}</span>${esc(a.msg)}<span class="al-x" onclick="event.stopPropagation();dismissAlert('${a.id}')">✕</span>
|
|
2771
2791
|
</div>`).join('');
|
|
2772
2792
|
}
|
|
2773
2793
|
|
|
@@ -2799,12 +2819,7 @@ function showSessCtx(sess) {
|
|
|
2799
2819
|
bar.classList.remove('show');
|
|
2800
2820
|
return;
|
|
2801
2821
|
}
|
|
2802
|
-
|
|
2803
|
-
const sCtxTime = sCtxAge ? ' · ' + relTime(sCtxAge) : '';
|
|
2804
|
-
const sCtxText = sess.lastPrompt || sess.id.slice(0, 16) + '…';
|
|
2805
|
-
const label = document.getElementById('sctx-label');
|
|
2806
|
-
label.textContent = sCtxText + sCtxTime;
|
|
2807
|
-
label.title = sCtxText + (sCtxAge ? ' · ' + new Date(typeof sCtxAge === 'number' ? sCtxAge : Number(sCtxAge) || sCtxAge).toLocaleString() : '');
|
|
2822
|
+
document.getElementById('sctx-label').textContent = sess.lastPrompt || sess.id.slice(0, 16) + '…';
|
|
2808
2823
|
bar.classList.add('show');
|
|
2809
2824
|
}
|
|
2810
2825
|
|
|
@@ -2826,7 +2841,6 @@ async function loadTodayMetrics() {
|
|
|
2826
2841
|
const data = await apiFetch('/api/section?name=tokens&dir=' + enc(DIR));
|
|
2827
2842
|
const s = data?.tokens?.summary || {};
|
|
2828
2843
|
alertState.todayCost = typeof s.todayCost === 'number' ? s.todayCost : 0;
|
|
2829
|
-
alertState.monthCost = typeof s.monthCost === 'number' ? s.monthCost : 0;
|
|
2830
2844
|
updateAlerts();
|
|
2831
2845
|
checkBudget();
|
|
2832
2846
|
// topbar cost badge
|
|
@@ -2863,17 +2877,13 @@ async function loadLoopMetrics() {
|
|
|
2863
2877
|
try {
|
|
2864
2878
|
const data = await apiFetch('/api/loops?dir=' + enc(DIR));
|
|
2865
2879
|
const loops = Array.isArray(data) ? data : (data.loops || []);
|
|
2866
|
-
|
|
2867
|
-
document.getElementById('bdg-loops').textContent = loops.length ? (hilCount ? loops.length + '⚠' : loops.length) : '—';
|
|
2880
|
+
document.getElementById('bdg-loops').textContent = loops.length || '—';
|
|
2868
2881
|
|
|
2869
2882
|
// alert on loops running > 2h
|
|
2870
2883
|
const TWO_HOURS = 2 * 3600 * 1000;
|
|
2871
2884
|
const now = Date.now();
|
|
2872
2885
|
alertState.longLoops = loops
|
|
2873
|
-
.filter(l => l.status !== 'stopped' && l.status !== 'paused' && l.
|
|
2874
|
-
.map(l => (l.name || l.prompt || 'loop').split('--')[0].trim().slice(0, 30));
|
|
2875
|
-
alertState.hilLoops = loops
|
|
2876
|
-
.filter(l => l.status === 'hil:pending')
|
|
2886
|
+
.filter(l => l.status !== 'stopped' && l.status !== 'paused' && l.startedAt && (now - new Date(l.startedAt).getTime()) > TWO_HOURS)
|
|
2877
2887
|
.map(l => (l.name || l.prompt || 'loop').split('--')[0].trim().slice(0, 30));
|
|
2878
2888
|
updateAlerts();
|
|
2879
2889
|
|
|
@@ -2882,24 +2892,13 @@ async function loadLoopMetrics() {
|
|
|
2882
2892
|
return;
|
|
2883
2893
|
}
|
|
2884
2894
|
const items = loops.slice(0, 5).map(l => {
|
|
2885
|
-
const
|
|
2886
|
-
const name = fullName.slice(0, 36);
|
|
2887
|
-
const isHilMini = l.status === 'hil:pending';
|
|
2888
|
-
const isTillendMini = l.type === 'tillend';
|
|
2889
|
-
const intervalMini = fmtInterval(l.interval || l.schedule) || 'running';
|
|
2890
|
-
const repMini = isTillendMini && l.currentRep ? `run ${l.currentRep}${l.maxReps ? '/' + l.maxReps : ''}` : null;
|
|
2891
|
-
const hilDot = isHilMini ? ' <span style="color:oklch(75% 0.16 60);font-size:9px">⚠HIL</span>' : '';
|
|
2892
|
-
const typeDot = isTillendMini ? '<span style="color:oklch(70% 0.18 280);font-size:9px;margin-right:3px">∞</span>' : '';
|
|
2895
|
+
const name = (l.name || l.prompt || 'loop').split('--')[0].trim().slice(0, 36);
|
|
2893
2896
|
return `<div class="mini-loop">
|
|
2894
|
-
<div class="ml-name"
|
|
2895
|
-
<div class="ml-meta"><span class="ml-dot"></span>${esc(
|
|
2897
|
+
<div class="ml-name">${esc(name)}</div>
|
|
2898
|
+
<div class="ml-meta"><span class="ml-dot"></span>${esc(l.interval || l.schedule || 'running')}</div>
|
|
2896
2899
|
</div>`;
|
|
2897
2900
|
}).join('');
|
|
2898
|
-
|
|
2899
|
-
const overflowNote = overflow > 0
|
|
2900
|
-
? `<div style="font-size:10px;color:var(--text-xs);padding:3px 0 0">+${overflow} more — open Loops tab</div>`
|
|
2901
|
-
: '';
|
|
2902
|
-
document.getElementById('m-loops').innerHTML = `<div class="m-group-title">Active Loops</div>${items}${overflowNote}`;
|
|
2901
|
+
document.getElementById('m-loops').innerHTML = `<div class="m-group-title">Active Loops</div>${items}`;
|
|
2903
2902
|
} catch (_) {
|
|
2904
2903
|
document.getElementById('m-loops').innerHTML = `<div class="m-group-title">Active Loops</div><div class="loading-txt">—</div>`;
|
|
2905
2904
|
}
|
|
@@ -2931,16 +2930,16 @@ async function loadStatusStrip() {
|
|
|
2931
2930
|
|
|
2932
2931
|
// HNSW status
|
|
2933
2932
|
const hnswOn = mem.hnsw === true || mem.hnswEnabled === true || mem.hnsw_enabled === true;
|
|
2934
|
-
pills.push(`<span class="ss-pill ${hnswOn ? 'on' : ''}"
|
|
2933
|
+
pills.push(`<span class="ss-pill ${hnswOn ? 'on' : ''}">HNSW ${hnswOn ? 'ON' : 'OFF'}</span>`);
|
|
2935
2934
|
|
|
2936
2935
|
// Patterns count
|
|
2937
2936
|
if (mem.patterns != null) {
|
|
2938
|
-
pills.push(`<span class="ss-pill"
|
|
2937
|
+
pills.push(`<span class="ss-pill">PATTERNS ${Number(mem.patterns).toLocaleString()}</span>`);
|
|
2939
2938
|
}
|
|
2940
2939
|
|
|
2941
2940
|
// Chunks count
|
|
2942
2941
|
if (mem.chunks != null) {
|
|
2943
|
-
pills.push(`<span class="ss-pill"
|
|
2942
|
+
pills.push(`<span class="ss-pill">CHUNKS ${Number(mem.chunks).toLocaleString()}</span>`);
|
|
2944
2943
|
}
|
|
2945
2944
|
|
|
2946
2945
|
// Swarm status
|
|
@@ -2972,7 +2971,7 @@ async function loadTokensView() {
|
|
|
2972
2971
|
const rows = Array.isArray(data?.tokens?.rows) ? data.tokens.rows : [];
|
|
2973
2972
|
cards.innerHTML = [
|
|
2974
2973
|
{ label:'Today Cost', val: typeof s.todayCost === 'number' ? '$' + s.todayCost.toFixed(2) : '—' },
|
|
2975
|
-
{ label:'Today Calls', val: s.todayCalls
|
|
2974
|
+
{ label:'Today Calls', val: s.todayCalls ?? '—' },
|
|
2976
2975
|
{ label:'Month Cost', val: typeof s.monthCost === 'number' ? '$' + s.monthCost.toFixed(2) : '—' },
|
|
2977
2976
|
{ label:'Total Tokens', val: s.totalTokens != null ? Number(s.totalTokens).toLocaleString() : '—' },
|
|
2978
2977
|
].map(c => `<div class="tok-card"><div class="tc-label">${esc(c.label)}</div><div class="tc-val">${esc(String(c.val))}</div></div>`).join('');
|
|
@@ -2984,13 +2983,12 @@ async function loadTokensView() {
|
|
|
2984
2983
|
'<th style="padding:4px 8px 4px 0">Session</th><th style="padding:4px 8px">Calls</th><th style="padding:4px 8px">Tokens</th><th style="padding:4px 8px">Cost</th>' +
|
|
2985
2984
|
'</tr></thead><tbody>' +
|
|
2986
2985
|
rows.slice(0, 30).map(r => `<tr style="border-top:1px solid var(--border)">
|
|
2987
|
-
<td style="padding:4px 8px 4px 0;color:var(--text-hi);max-width:260px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap"
|
|
2988
|
-
<td style="padding:4px 8px;color:var(--text-lo)">${r.calls
|
|
2986
|
+
<td style="padding:4px 8px 4px 0;color:var(--text-hi);max-width:260px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${esc(r.session || r.id || '—')}</td>
|
|
2987
|
+
<td style="padding:4px 8px;color:var(--text-lo)">${r.calls ?? '—'}</td>
|
|
2989
2988
|
<td style="padding:4px 8px;color:var(--text-lo)">${r.tokens != null ? Number(r.tokens).toLocaleString() : '—'}</td>
|
|
2990
2989
|
<td style="padding:4px 8px;color:var(--accent)">$${Number(r.cost ?? 0).toFixed(4)}</td>
|
|
2991
2990
|
</tr>`).join('') +
|
|
2992
|
-
'</tbody></table></div>'
|
|
2993
|
-
(rows.length > 30 ? `<div style="font-size:11px;color:var(--text-xs);margin-top:6px;text-align:right">Showing 30 of ${rows.length} sessions</div>` : '');
|
|
2991
|
+
'</tbody></table></div>';
|
|
2994
2992
|
} else { table.innerHTML = ''; }
|
|
2995
2993
|
markLiveGlow('view-tokens');
|
|
2996
2994
|
} catch (_) {
|
|
@@ -3042,6 +3040,7 @@ function renderTokChart(daily, animated = true) {
|
|
|
3042
3040
|
});
|
|
3043
3041
|
canvas.addEventListener('mouseleave', () => { canvas._tokTip.style.display = 'none'; });
|
|
3044
3042
|
}
|
|
3043
|
+
|
|
3045
3044
|
const targets = vals.map((v, i) => ({
|
|
3046
3045
|
v, i,
|
|
3047
3046
|
isToday: i === vals.length - 1,
|
|
@@ -3102,7 +3101,7 @@ async function setTokPeriod(btn, period) {
|
|
|
3102
3101
|
if (cards) cards.innerHTML = [
|
|
3103
3102
|
{ label: 'Cost', val: typeof s.todayCost === 'number' ? '$' + s.todayCost.toFixed(2)
|
|
3104
3103
|
: typeof s.cost === 'number' ? '$' + s.cost.toFixed(2) : '—' },
|
|
3105
|
-
{ label: 'Calls', val:
|
|
3104
|
+
{ label: 'Calls', val: s.todayCalls ?? s.calls ?? '—' },
|
|
3106
3105
|
{ label: 'Tokens', val: s.totalTokens != null ? Number(s.totalTokens).toLocaleString() : '—' },
|
|
3107
3106
|
{ label: 'Models', val: s.modelCount ?? s.models ?? '—' },
|
|
3108
3107
|
].map(c => `<div class="tok-card"><div class="tc-label">${esc(c.label)}</div><div class="tc-val">${esc(String(c.val))}</div></div>`).join('');
|
|
@@ -3114,13 +3113,12 @@ async function setTokPeriod(btn, period) {
|
|
|
3114
3113
|
'<th style="padding:3px 8px">Calls</th><th style="padding:3px 8px">Cost</th></tr></thead><tbody>' +
|
|
3115
3114
|
rows.slice(0, 30).map(r =>
|
|
3116
3115
|
`<tr style="border-top:1px solid var(--border)">` +
|
|
3117
|
-
`<td style="padding:3px 8px 3px 0;color:var(--text-hi);max-width:220px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap"
|
|
3118
|
-
`<td style="padding:3px 8px;color:var(--text-lo)">${r.calls
|
|
3116
|
+
`<td style="padding:3px 8px 3px 0;color:var(--text-hi);max-width:220px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${esc(r.session || r.label || r.id || '—')}</td>` +
|
|
3117
|
+
`<td style="padding:3px 8px;color:var(--text-lo)">${r.calls ?? '—'}</td>` +
|
|
3119
3118
|
`<td style="padding:3px 8px;color:var(--accent)">$${Number(r.cost ?? 0).toFixed(4)}</td>` +
|
|
3120
3119
|
`</tr>`
|
|
3121
3120
|
).join('') +
|
|
3122
|
-
'</tbody></table></div>'
|
|
3123
|
-
(rows.length > 30 ? `<div style="font-size:11px;color:var(--text-xs);margin-top:6px;text-align:right">Showing 30 of ${rows.length} entries</div>` : '');
|
|
3121
|
+
'</tbody></table></div>';
|
|
3124
3122
|
} else if (table) { table.innerHTML = ''; }
|
|
3125
3123
|
markLiveGlow('view-tokens');
|
|
3126
3124
|
// Update topbar badge when showing today's data
|
|
@@ -3173,58 +3171,25 @@ async function loadMemRouting() {
|
|
|
3173
3171
|
const last = rows[rows.length - 1];
|
|
3174
3172
|
window._lastRouteAgent = last.suggestedAgent || last.route || last.category || last.agent || last.agentType || '';
|
|
3175
3173
|
}
|
|
3176
|
-
if (!rows.length) { pane.innerHTML = '<div class="empty">No routing data
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
pane.innerHTML = '<div class="filter-bar" style="margin-bottom:8px"><input class="filter-input" id="routing-filter" type="text" placeholder="Filter by agent or task…" oninput="filterRouting(this.value)" title="Filter routing entries"></div>' +
|
|
3180
|
-
overflowNote +
|
|
3181
|
-
'<div id="routing-rows">' +
|
|
3182
|
-
displayRows.map(r => {
|
|
3174
|
+
if (!rows.length) { pane.innerHTML = '<div class="empty">No routing data</div>'; return; }
|
|
3175
|
+
pane.innerHTML = '<div class="m-group-title" style="margin-bottom:6px">Routing Feedback</div>' +
|
|
3176
|
+
rows.slice(-40).reverse().map(r => {
|
|
3183
3177
|
const agent = r.suggestedAgent || r.route || r.category || r.agent || '—';
|
|
3184
3178
|
const task = r.task || r.prompt || r.description || r.sessionId?.slice(0, 8) || '—';
|
|
3185
3179
|
const ts = r.timestamp || r.ts || r.created_at;
|
|
3186
3180
|
const conf = r.confidence != null ? Math.round(r.confidence * 100) + '%' : '';
|
|
3187
|
-
return `<div
|
|
3181
|
+
return `<div style="padding:5px 0;border-bottom:1px solid var(--border);font-size:11px;font-family:monospace">
|
|
3188
3182
|
<div style="color:var(--text-hi);display:flex;align-items:center;gap:8px">
|
|
3189
3183
|
<span style="color:var(--accent)">${esc(agent)}</span>
|
|
3190
3184
|
${conf ? `<span style="color:var(--text-lo)">${esc(conf)}</span>` : ''}
|
|
3191
|
-
<span style="color:var(--text-lo);margin-left:auto;font-size:10px"
|
|
3185
|
+
<span style="color:var(--text-lo);margin-left:auto;font-size:10px">${relTime(ts)}</span>
|
|
3192
3186
|
</div>
|
|
3193
3187
|
<div style="color:var(--text-lo);margin-top:1px">${esc(task)}</div>
|
|
3194
3188
|
</div>`;
|
|
3195
|
-
}).join('')
|
|
3189
|
+
}).join('');
|
|
3196
3190
|
} catch (_) { pane.innerHTML = '<div class="empty">Failed to load routing data</div>'; }
|
|
3197
3191
|
}
|
|
3198
3192
|
|
|
3199
|
-
function filterRouting(q) {
|
|
3200
|
-
const lq = (q || '').toLowerCase();
|
|
3201
|
-
document.querySelectorAll('#routing-rows .routing-entry').forEach(el => {
|
|
3202
|
-
const agent = (el.dataset.agent || '').toLowerCase();
|
|
3203
|
-
const task = (el.dataset.task || '').toLowerCase();
|
|
3204
|
-
el.style.display = (!lq || agent.includes(lq) || task.includes(lq)) ? '' : 'none';
|
|
3205
|
-
});
|
|
3206
|
-
}
|
|
3207
|
-
|
|
3208
|
-
function filterLoopList(q) {
|
|
3209
|
-
const lq = (q || '').toLowerCase();
|
|
3210
|
-
document.querySelectorAll('#loops-content .loop-row').forEach(el => {
|
|
3211
|
-
const text = (el.textContent || '').toLowerCase();
|
|
3212
|
-
const expand = el.nextElementSibling;
|
|
3213
|
-
const visible = !lq || text.includes(lq);
|
|
3214
|
-
el.style.display = visible ? '' : 'none';
|
|
3215
|
-
if (expand && expand.classList.contains('loop-expand')) expand.style.display = 'none';
|
|
3216
|
-
});
|
|
3217
|
-
}
|
|
3218
|
-
|
|
3219
|
-
function filterOrgList(q) {
|
|
3220
|
-
const lq = (q || '').toLowerCase();
|
|
3221
|
-
document.querySelectorAll('#orgs-list-scroll .org-item').forEach(el => {
|
|
3222
|
-
const name = (el.dataset.org || '').toLowerCase();
|
|
3223
|
-
const goal = (el.querySelector('.oi-goal')?.textContent || '').toLowerCase();
|
|
3224
|
-
el.style.display = (!lq || name.includes(lq) || goal.includes(lq)) ? '' : 'none';
|
|
3225
|
-
});
|
|
3226
|
-
}
|
|
3227
|
-
|
|
3228
3193
|
async function loadMemUsage() {
|
|
3229
3194
|
const pane = document.getElementById('mem-tab-usage');
|
|
3230
3195
|
if (!pane) return;
|
|
@@ -3232,10 +3197,10 @@ async function loadMemUsage() {
|
|
|
3232
3197
|
// Period tabs
|
|
3233
3198
|
pane.innerHTML = `
|
|
3234
3199
|
<div class="tok-periods" style="margin-bottom:14px">
|
|
3235
|
-
<button class="tok-period-btn active" data-period="today"
|
|
3236
|
-
<button class="tok-period-btn" data-period="week"
|
|
3237
|
-
<button class="tok-period-btn" data-period="30d"
|
|
3238
|
-
<button class="tok-period-btn" data-period="month"
|
|
3200
|
+
<button class="tok-period-btn active" data-period="today" onclick="loadMemUsagePeriod(this,'today')">Today</button>
|
|
3201
|
+
<button class="tok-period-btn" data-period="week" onclick="loadMemUsagePeriod(this,'week')">Week</button>
|
|
3202
|
+
<button class="tok-period-btn" data-period="30d" onclick="loadMemUsagePeriod(this,'30d')">30 Days</button>
|
|
3203
|
+
<button class="tok-period-btn" data-period="month" onclick="loadMemUsagePeriod(this,'month')">Month</button>
|
|
3239
3204
|
</div>
|
|
3240
3205
|
<div id="mem-usage-content"><div class="loading-txt">Loading…</div></div>
|
|
3241
3206
|
`;
|
|
@@ -3260,25 +3225,19 @@ async function loadMemUsagePeriod(btn, period) {
|
|
|
3260
3225
|
|
|
3261
3226
|
function barChart(items, valKey, labelKey, color, maxItems) {
|
|
3262
3227
|
if (!items.length) return '<div class="empty" style="font-size:12px">No data</div>';
|
|
3263
|
-
const
|
|
3264
|
-
|
|
3265
|
-
const rows = shown.map(item => {
|
|
3228
|
+
const maxVal = Math.max(...items.slice(0, maxItems).map(x => Number(x[valKey] || 0)), 0.0001);
|
|
3229
|
+
return items.slice(0, maxItems).map(item => {
|
|
3266
3230
|
const pct = Math.round((Number(item[valKey] || 0) / maxVal) * 100);
|
|
3267
|
-
const
|
|
3268
|
-
const label = esc(fullLabel.slice(0, 24));
|
|
3231
|
+
const label = esc(String(item[labelKey] || '—').slice(0, 24));
|
|
3269
3232
|
const val = typeof item[valKey] === 'number' && valKey === 'cost'
|
|
3270
3233
|
? '$' + Number(item[valKey]).toFixed(4)
|
|
3271
3234
|
: String(item[valKey] || 0);
|
|
3272
3235
|
return `<div style="display:flex;align-items:center;gap:8px;padding:3px 0;font-size:12px">
|
|
3273
|
-
<div style="width:130px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--text-hi);font-family:var(--mono);font-size:11px"
|
|
3274
|
-
<div style="flex:1;height:7px;background:var(--border);border-radius:2px;overflow:hidden"
|
|
3236
|
+
<div style="width:130px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--text-hi);font-family:var(--mono);font-size:11px">${label}</div>
|
|
3237
|
+
<div style="flex:1;height:7px;background:var(--border);border-radius:2px;overflow:hidden"><div style="width:${pct}%;height:100%;background:${color};border-radius:2px"></div></div>
|
|
3275
3238
|
<div style="width:60px;text-align:right;color:var(--text-lo);font-size:11px;font-family:var(--mono)">${esc(val)}</div>
|
|
3276
3239
|
</div>`;
|
|
3277
3240
|
}).join('');
|
|
3278
|
-
const overflow = items.length > maxItems
|
|
3279
|
-
? `<div style="font-size:11px;color:var(--text-xs);margin-top:3px;text-align:right">Showing ${maxItems} of ${items.length}</div>`
|
|
3280
|
-
: '';
|
|
3281
|
-
return rows + overflow;
|
|
3282
3241
|
}
|
|
3283
3242
|
|
|
3284
3243
|
const totalCost = typeof s.todayCost === 'number' ? s.todayCost : (typeof s.cost === 'number' ? s.cost : null);
|
|
@@ -3321,12 +3280,11 @@ async function loadMemUsagePeriod(btn, period) {
|
|
|
3321
3280
|
<div style="overflow-x:auto"><table style="width:100%;border-collapse:collapse;font-size:11px">
|
|
3322
3281
|
<thead><tr style="color:var(--text-xs);text-align:left"><th style="padding:3px 8px 3px 0">Session</th><th>Calls</th><th>Cost</th></tr></thead>
|
|
3323
3282
|
<tbody>${rows.slice(0,30).map(r => `<tr style="border-top:1px solid var(--border)">
|
|
3324
|
-
<td style="padding:3px 8px 3px 0;color:var(--text-hi);max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-family:var(--mono);font-size:11px"
|
|
3325
|
-
<td style="padding:3px 8px;color:var(--text-lo)">${r.calls
|
|
3283
|
+
<td style="padding:3px 8px 3px 0;color:var(--text-hi);max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-family:var(--mono);font-size:11px">${esc(r.session||r.label||r.id||'—')}</td>
|
|
3284
|
+
<td style="padding:3px 8px;color:var(--text-lo)">${r.calls??'—'}</td>
|
|
3326
3285
|
<td style="padding:3px 8px;color:var(--accent)">$${Number(r.cost??0).toFixed(4)}</td>
|
|
3327
3286
|
</tr>`).join('')}</tbody>
|
|
3328
|
-
</table></div
|
|
3329
|
-
${rows.length > 30 ? `<div style="font-size:11px;color:var(--text-xs);margin-top:6px;text-align:right">Showing 30 of ${rows.length} entries</div>` : ''}` : ''}
|
|
3287
|
+
</table></div>` : ''}
|
|
3330
3288
|
`;
|
|
3331
3289
|
} catch (e) {
|
|
3332
3290
|
content.innerHTML = '<div class="empty">Failed: ' + esc(e.message) + '</div>';
|
|
@@ -3348,15 +3306,14 @@ async function loadMemADRs() {
|
|
|
3348
3306
|
function renderADRs(list) {
|
|
3349
3307
|
const pane = document.getElementById('adr-content');
|
|
3350
3308
|
if (!pane) return;
|
|
3351
|
-
if (!list.length) { pane.innerHTML = '<div class="empty">No ADRs found
|
|
3309
|
+
if (!list.length) { pane.innerHTML = '<div class="empty">No ADRs found</div>'; return; }
|
|
3352
3310
|
pane.innerHTML = list.slice(0, 50).map(a => `<div style="padding:8px 0;border-bottom:1px solid var(--border)">
|
|
3353
3311
|
<div style="display:flex;justify-content:space-between;align-items:baseline;margin-bottom:3px">
|
|
3354
3312
|
<span style="color:var(--text-hi);font-size:12px;font-family:monospace">${esc(a.id || a.title || '—')}</span>
|
|
3355
3313
|
<span class="ss-pill ${a.status === 'accepted' ? 'on' : a.status === 'deprecated' ? 'warn' : ''}">${esc(a.status || '?')}</span>
|
|
3356
3314
|
</div>
|
|
3357
|
-
<div style="font-size:11px;color:var(--text-lo)"
|
|
3358
|
-
</div>`).join('')
|
|
3359
|
-
(list.length > 50 ? `<div style="font-size:11px;color:var(--text-xs);margin-top:8px;text-align:right">Showing 50 of ${list.length} ADRs</div>` : '');
|
|
3315
|
+
<div style="font-size:11px;color:var(--text-lo)">${esc((a.context || a.summary || '').slice(0, 120))}</div>
|
|
3316
|
+
</div>`).join('');
|
|
3360
3317
|
}
|
|
3361
3318
|
|
|
3362
3319
|
function filterADRs(q) {
|
|
@@ -3371,15 +3328,11 @@ function filterADRs(q) {
|
|
|
3371
3328
|
|
|
3372
3329
|
function renderMiniSessions(sessions) {
|
|
3373
3330
|
if (!sessions.length) return;
|
|
3374
|
-
const items = sessions.map((s, i) =>
|
|
3375
|
-
const costStr = typeof s.totalCost === 'number' && s.totalCost > 0.001 ? ' · $' + s.totalCost.toFixed(2) : '';
|
|
3376
|
-
const durStr = s.totalDurationMs ? ' · ' + fmtDur(s.totalDurationMs) : '';
|
|
3377
|
-
return `
|
|
3331
|
+
const items = sessions.map((s, i) => `
|
|
3378
3332
|
<div class="mini-sess" onclick="sessionIdx=${i};userScrolled=false;loadFeedForSession(allSessions[${i}])">
|
|
3379
|
-
<div class="ms-prompt"
|
|
3380
|
-
<div class="ms-meta"
|
|
3381
|
-
</div
|
|
3382
|
-
}).join('');
|
|
3333
|
+
<div class="ms-prompt">${esc(s.lastPrompt || s.id.slice(0, 8))}</div>
|
|
3334
|
+
<div class="ms-meta">${relTime(s.lastTs || s.mtime)}</div>
|
|
3335
|
+
</div>`).join('');
|
|
3383
3336
|
document.getElementById('m-sessions').innerHTML = `<div class="m-group-title">Recent Sessions</div>${items}`;
|
|
3384
3337
|
buildSwimlane();
|
|
3385
3338
|
}
|
|
@@ -3406,7 +3359,7 @@ function renderProjectGrid(projects, query) {
|
|
|
3406
3359
|
(p.name || p.slug || '').toLowerCase().includes(query.toLowerCase()) ||
|
|
3407
3360
|
(p.path || '').toLowerCase().includes(query.toLowerCase())) : projects;
|
|
3408
3361
|
if (!filtered.length) {
|
|
3409
|
-
el.innerHTML = '<div class="empty"><div class="empty-ico">⊞</div><div>No projects match</div
|
|
3362
|
+
el.innerHTML = '<div class="empty"><div class="empty-ico">⊞</div><div>No projects match</div></div>';
|
|
3410
3363
|
return;
|
|
3411
3364
|
}
|
|
3412
3365
|
el.className = 'proj-grid';
|
|
@@ -3417,12 +3370,12 @@ function renderProjectGrid(projects, query) {
|
|
|
3417
3370
|
return `<div class="proj-card${isCurrent ? ' current' : ''}" onclick="switchProject('${esc(p.path || '')}')">
|
|
3418
3371
|
${isCurrent ? '<div class="proj-card-badge">active</div>' : ''}
|
|
3419
3372
|
<div class="proj-health ${hCls}" title="Health score: ${score}">${score}</div>
|
|
3420
|
-
<div class="proj-card-name"
|
|
3421
|
-
<div class="proj-card-path"
|
|
3373
|
+
<div class="proj-card-name">${esc(p.name || p.slug)}</div>
|
|
3374
|
+
<div class="proj-card-path">${esc(p.path || '')}</div>
|
|
3422
3375
|
<div class="proj-card-stats">
|
|
3423
3376
|
<div class="proj-stat"><div class="ps-v">${p.sessionCount || 0}</div><div class="ps-l">sessions</div></div>
|
|
3424
3377
|
<div class="proj-stat"><div class="ps-v">${p.memoryCount || 0}</div><div class="ps-l">memories</div></div>
|
|
3425
|
-
${p.lastActivity ? `<div class="proj-stat"><div class="ps-v" style="font-size:12px"
|
|
3378
|
+
${p.lastActivity ? `<div class="proj-stat"><div class="ps-v" style="font-size:12px">${relTime(p.lastActivity)}</div><div class="ps-l">last active</div></div>` : ''}
|
|
3426
3379
|
</div>
|
|
3427
3380
|
</div>`;
|
|
3428
3381
|
}).join('');
|
|
@@ -3444,33 +3397,19 @@ async function renderSessions() {
|
|
|
3444
3397
|
document.getElementById('sess-pg-sub').textContent =
|
|
3445
3398
|
sessions.length + ' session' + (sessions.length !== 1 ? 's' : '') + ' · ' + (DIR.split('/').pop() || DIR);
|
|
3446
3399
|
if (!sessions.length) {
|
|
3447
|
-
el.innerHTML = '<div class="empty"><div class="empty-ico">◫</div><div>No sessions yet</div
|
|
3400
|
+
el.innerHTML = '<div class="empty"><div class="empty-ico">◫</div><div>No sessions yet</div></div>';
|
|
3448
3401
|
return;
|
|
3449
3402
|
}
|
|
3450
3403
|
let toShow = showStarredOnly ? sessions.filter(s => bookmarks.has(s.id)) : sessions;
|
|
3451
3404
|
if (activeTagFilter) toShow = toShow.filter(s => (allTags.sessionTags.get(s.id) || []).includes(activeTagFilter));
|
|
3452
3405
|
if (heatmapDateFilter) toShow = toShow.filter(s => {
|
|
3453
3406
|
const t = s.lastTs || s.mtime; if (!t) return false;
|
|
3454
|
-
return new Date(typeof t === 'number' ? t :
|
|
3407
|
+
return new Date(typeof t === 'number' ? t : t).toDateString() === heatmapDateFilter;
|
|
3455
3408
|
});
|
|
3456
3409
|
// f57: file pivot filter
|
|
3457
3410
|
if (filePivot) toShow = toShow.filter(s => (s.filesTouched || []).includes(filePivot));
|
|
3458
3411
|
if (!toShow.length) {
|
|
3459
|
-
|
|
3460
|
-
if (filePivot) {
|
|
3461
|
-
emptyMsg = 'No sessions touching ' + esc(filePivot.split('/').pop());
|
|
3462
|
-
emptyHint = 'Clear the file filter to see all sessions';
|
|
3463
|
-
} else if (heatmapDateFilter) {
|
|
3464
|
-
emptyMsg = 'No sessions on ' + esc(heatmapDateFilter);
|
|
3465
|
-
emptyHint = 'Click another date on the heatmap or clear the filter';
|
|
3466
|
-
} else if (activeTagFilter) {
|
|
3467
|
-
emptyMsg = 'No sessions tagged "' + esc(activeTagFilter) + '"';
|
|
3468
|
-
emptyHint = 'Clear the tag filter to see all sessions';
|
|
3469
|
-
} else {
|
|
3470
|
-
emptyMsg = 'No bookmarked sessions';
|
|
3471
|
-
emptyHint = 'Click the ☆ on any session row to bookmark it.';
|
|
3472
|
-
}
|
|
3473
|
-
el.innerHTML = `<div class="empty"><div class="empty-ico">☆</div><div>${emptyMsg}</div><div style="font-size:11px;color:var(--text-xs);margin-top:4px">${emptyHint}</div></div>`;
|
|
3412
|
+
el.innerHTML = '<div class="empty"><div class="empty-ico">☆</div><div>No bookmarked sessions</div></div>';
|
|
3474
3413
|
buildSessionHeatmap(sessions);
|
|
3475
3414
|
return;
|
|
3476
3415
|
}
|
|
@@ -3517,7 +3456,7 @@ async function renderSessions() {
|
|
|
3517
3456
|
const summaries = (s.summaries || []).slice(0, 2).map(sm => { const t = typeof sm === 'string' ? sm : (sm.summary || sm.text || String(sm)); return `<span class="sr-tag">${esc(t.slice(0, 40))}</span>`; }).join('');
|
|
3518
3457
|
const autoTags = (allTags.sessionTags.get(s.id) || []).map(t => `<span class="sr-autotag">${esc(t)}</span>`).join('');
|
|
3519
3458
|
const isStarred = bookmarks.has(s.id);
|
|
3520
|
-
const sData = JSON.stringify(s).replace(
|
|
3459
|
+
const sData = JSON.stringify(s).replace(/'/g, ''');
|
|
3521
3460
|
const note = getSessNote(s.id);
|
|
3522
3461
|
const hasNote = !!note;
|
|
3523
3462
|
const files = (s.filesTouched || []).slice(0, 5);
|
|
@@ -3542,14 +3481,12 @@ async function renderSessions() {
|
|
|
3542
3481
|
? `<span class="sr-compact-badge">+${s.compactCount} compacted</span>`
|
|
3543
3482
|
: '';
|
|
3544
3483
|
const summaryHtml = s.summary
|
|
3545
|
-
? `<div class="sr-summary"
|
|
3484
|
+
? `<div class="sr-summary">${esc(s.summary.slice(0, 180))}</div>`
|
|
3546
3485
|
: '';
|
|
3547
|
-
const srTimeTs = s.lastTs || s.mtime;
|
|
3548
|
-
const srTimeFull = srTimeTs ? new Date(typeof srTimeTs === 'number' ? srTimeTs : Number(srTimeTs) || srTimeTs).toLocaleString() : '';
|
|
3549
3486
|
return `<div class="sess-row" data-sess-idx="${idx}" data-sess-id="${esc(s.id)}" onclick="handleSessRowClick(event,this,'${esc(s.id)}')" data-sess-data='${sData}'>
|
|
3550
3487
|
<div class="sr-top">
|
|
3551
|
-
<div class="sr-prompt"
|
|
3552
|
-
<div class="sr-time"
|
|
3488
|
+
<div class="sr-prompt">${esc(s.lastPrompt || s.id?.slice(0,8) || '—')}</div>
|
|
3489
|
+
<div class="sr-time">${relTime(s.lastTs || s.mtime)}</div>${compactBadge}
|
|
3553
3490
|
<button class="sr-copy-btn" data-prompt="${esc(s.lastPrompt || s.id)}" onclick="copyPrompt(this.dataset.prompt,event)" title="Copy prompt to clipboard">⎘</button>
|
|
3554
3491
|
<button class="sess-star${isStarred ? ' on' : ''}" data-sid="${esc(s.id)}" onclick="toggleBookmark('${esc(s.id)}',event)" title="${isStarred ? 'Remove bookmark' : 'Bookmark session'}">${isStarred ? '★' : '☆'}</button>
|
|
3555
3492
|
<span class="sr-view">→ view</span>
|
|
@@ -3563,7 +3500,7 @@ async function renderSessions() {
|
|
|
3563
3500
|
${ctxGauge}
|
|
3564
3501
|
<div class="err-drawer" id="err-drawer-${esc(s.id)}"></div>
|
|
3565
3502
|
<div class="sess-notes-wrap" onclick="event.stopPropagation()">
|
|
3566
|
-
<button class="sess-notes-toggle${hasNote ? ' has-note' : ''}"
|
|
3503
|
+
<button class="sess-notes-toggle${hasNote ? ' has-note' : ''}" onclick="toggleSessNote('${esc(s.id)}',this)">✎ ${hasNote ? 'Note' : 'Add note'}</button>
|
|
3567
3504
|
<div class="sess-notes-area" id="snote-${esc(s.id)}">
|
|
3568
3505
|
<textarea class="sess-note-input" rows="2" placeholder="Session note…" oninput="saveSessNote('${esc(s.id)}',this.value,this.closest('.sess-notes-wrap').querySelector('.sess-notes-toggle'),this.closest('.sess-row').querySelector('.sess-note-saved'))">${esc(note)}</textarea>
|
|
3569
3506
|
<div class="sess-note-saved"></div>
|
|
@@ -3604,14 +3541,14 @@ async function renderSessions() {
|
|
|
3604
3541
|
const todayCost = allSessions.filter(s => {
|
|
3605
3542
|
const t = s.firstTs || s.mtime;
|
|
3606
3543
|
if (!t) return false;
|
|
3607
|
-
const d = new Date(typeof t === 'number' ? t :
|
|
3544
|
+
const d = new Date(typeof t === 'number' ? t : t);
|
|
3608
3545
|
const now = new Date();
|
|
3609
3546
|
return d.getFullYear() === now.getFullYear() && d.getMonth() === now.getMonth() && d.getDate() === now.getDate();
|
|
3610
3547
|
}).reduce((a, s) => a + (s.totalCost || 0), 0);
|
|
3611
3548
|
const monthCost = allSessions.filter(s => {
|
|
3612
3549
|
const t = s.firstTs || s.mtime;
|
|
3613
3550
|
if (!t) return false;
|
|
3614
|
-
const d = new Date(typeof t === 'number' ? t :
|
|
3551
|
+
const d = new Date(typeof t === 'number' ? t : t);
|
|
3615
3552
|
const now = new Date();
|
|
3616
3553
|
return d.getFullYear() === now.getFullYear() && d.getMonth() === now.getMonth();
|
|
3617
3554
|
}).reduce((a, s) => a + (s.totalCost || 0), 0);
|
|
@@ -3690,7 +3627,7 @@ function buildTagFilterBar(sessions) {
|
|
|
3690
3627
|
if (!allTags.common.size) return '';
|
|
3691
3628
|
const sorted = [...allTags.common].sort();
|
|
3692
3629
|
const chips = sorted.map(t =>
|
|
3693
|
-
`<button class="tag-chip${activeTagFilter === t ? ' active' : ''}"
|
|
3630
|
+
`<button class="tag-chip${activeTagFilter === t ? ' active' : ''}" onclick="setTagFilter('${esc(t)}')">${esc(t)}</button>`
|
|
3694
3631
|
).join('');
|
|
3695
3632
|
return `<div class="tag-filter-bar">${chips}</div>`;
|
|
3696
3633
|
}
|
|
@@ -3719,12 +3656,10 @@ function buildRecap(events, sess) {
|
|
|
3719
3656
|
const topPct = topCat ? Math.round(topCat[1] / tools.length * 100) : 0;
|
|
3720
3657
|
|
|
3721
3658
|
const costStr = sess?.totalCost != null ? '$' + sess.totalCost.toFixed(2) : (sess?.cost != null ? '$' + sess.cost.toFixed(2) : null);
|
|
3722
|
-
const durStr = sess?.totalDurationMs ? fmtDur(sess.totalDurationMs) : null;
|
|
3723
3659
|
|
|
3724
3660
|
const stats = [
|
|
3725
3661
|
tools.length ? `<span class="recap-stat rs-tool">${tools.length} tool calls${topCat ? ' · ' + topPct + '% ' + topCat[0] : ''}</span>` : '',
|
|
3726
3662
|
users.length ? `<span class="recap-stat rs-user">${users.length} message${users.length !== 1 ? 's' : ''}</span>` : '',
|
|
3727
|
-
durStr ? `<span class="recap-stat">${durStr}</span>` : '',
|
|
3728
3663
|
costStr ? `<span class="recap-stat rs-cost">${costStr}</span>` : '',
|
|
3729
3664
|
errors.length ? `<span class="recap-stat rs-err">${errors.length} error${errors.length !== 1 ? 's' : ''}</span>` : '',
|
|
3730
3665
|
].filter(Boolean).join('');
|
|
@@ -3733,74 +3668,6 @@ function buildRecap(events, sess) {
|
|
|
3733
3668
|
recap.className = 'show';
|
|
3734
3669
|
}
|
|
3735
3670
|
|
|
3736
|
-
// ── feature 3: global feed ─────────────────────────────────
|
|
3737
|
-
async function renderGlobalFeed() {
|
|
3738
|
-
const el = document.getElementById('gf-content');
|
|
3739
|
-
el.innerHTML = '<div class="loading-txt">Loading all projects…</div>';
|
|
3740
|
-
try {
|
|
3741
|
-
// fetch project list
|
|
3742
|
-
const data = await apiFetch('/api/projects');
|
|
3743
|
-
const allProjects = data?.projects || [];
|
|
3744
|
-
const projects = allProjects.slice(0, 8);
|
|
3745
|
-
if (!projects.length) {
|
|
3746
|
-
el.innerHTML = '<div class="empty"><div class="empty-ico">⊕</div><div>No projects found</div><div style="font-size:11px;color:var(--text-xs);margin-top:4px">Run <code>npx monomind init</code> inside a project to register it.</div></div>';
|
|
3747
|
-
return;
|
|
3748
|
-
}
|
|
3749
|
-
document.getElementById('gf-sub').textContent = allProjects.length > 8
|
|
3750
|
-
? `Last activity across ${projects.length} of ${allProjects.length} projects`
|
|
3751
|
-
: `Last activity across ${projects.length} project${projects.length !== 1 ? 's' : ''}`;
|
|
3752
|
-
|
|
3753
|
-
// fetch sessions for each project in parallel
|
|
3754
|
-
const results = await Promise.allSettled(
|
|
3755
|
-
projects.map(p => apiFetch('/api/session-journal?dir=' + enc(p.path)).then(d => ({ project: p, sessions: d.sessions || [] })))
|
|
3756
|
-
);
|
|
3757
|
-
|
|
3758
|
-
// flatten + sort by recency
|
|
3759
|
-
const entries = [];
|
|
3760
|
-
for (const r of results) {
|
|
3761
|
-
if (r.status !== 'fulfilled') continue;
|
|
3762
|
-
const { project, sessions } = r.value;
|
|
3763
|
-
for (const s of sessions.slice(0, 3)) {
|
|
3764
|
-
entries.push({ project, session: s });
|
|
3765
|
-
}
|
|
3766
|
-
}
|
|
3767
|
-
entries.sort((a, b) => {
|
|
3768
|
-
const ta = a.session.lastTs || a.session.mtime || 0;
|
|
3769
|
-
const tb = b.session.lastTs || b.session.mtime || 0;
|
|
3770
|
-
return (typeof tb === 'number' ? tb : Number(tb) || new Date(tb).getTime() || 0) - (typeof ta === 'number' ? ta : Number(ta) || new Date(ta).getTime() || 0);
|
|
3771
|
-
});
|
|
3772
|
-
|
|
3773
|
-
if (!entries.length) {
|
|
3774
|
-
el.innerHTML = '<div class="empty"><div class="empty-ico">⊕</div><div>No sessions found</div><div style="font-size:11px;color:var(--text-xs);margin-top:4px">Sessions appear when Claude Code runs inside registered projects</div></div>';
|
|
3775
|
-
return;
|
|
3776
|
-
}
|
|
3777
|
-
|
|
3778
|
-
el.innerHTML = '<div class="sess-list">' + entries.map(({ project, session: s }) => {
|
|
3779
|
-
const projName = project.name || project.slug || project.path?.split('/').pop() || '?';
|
|
3780
|
-
const dur = s.totalDurationMs ? fmtDur(s.totalDurationMs) : '';
|
|
3781
|
-
const cost = typeof s.totalCost === 'number' ? '$' + s.totalCost.toFixed(2) : '';
|
|
3782
|
-
const meta = [dur, cost].filter(Boolean).join(' · ') || s.id.slice(0, 12);
|
|
3783
|
-
const gfCompactBadge = (s.compactCount > 0)
|
|
3784
|
-
? `<span class="sr-compact-badge">+${s.compactCount} compacted</span>`
|
|
3785
|
-
: '';
|
|
3786
|
-
const gfSummaryHtml = s.summary
|
|
3787
|
-
? `<div class="sr-summary" title="${esc(s.summary)}">${esc(s.summary.slice(0, 180))}${s.summary.length > 180 ? '…' : ''}</div>`
|
|
3788
|
-
: '';
|
|
3789
|
-
return `<div class="sess-row" onclick="switchProject('${esc(project.path)}');setTimeout(()=>jumpToSession('${esc(s.id)}'),150)">
|
|
3790
|
-
<div class="sr-top">
|
|
3791
|
-
<div class="sr-prompt" title="${esc(s.lastPrompt || s.id || '')}">${esc(s.lastPrompt || s.id?.slice(0,8) || '—')}</div>
|
|
3792
|
-
<div class="sr-time" title="${(t => t ? new Date(typeof t === 'number' ? t : Number(t) || t).toLocaleString() : '')(s.lastTs || s.mtime)}">${relTime(s.lastTs || s.mtime)}</div>${gfCompactBadge}
|
|
3793
|
-
<span class="gf-proj-tag">${esc(projName)}</span>
|
|
3794
|
-
</div>
|
|
3795
|
-
${gfSummaryHtml}
|
|
3796
|
-
<div class="sr-meta">${esc(meta)}</div>
|
|
3797
|
-
</div>`;
|
|
3798
|
-
}).join('') + '</div>';
|
|
3799
|
-
} catch (err) {
|
|
3800
|
-
el.innerHTML = '<div class="empty">Could not load: ' + esc(err.message) + '</div>';
|
|
3801
|
-
}
|
|
3802
|
-
}
|
|
3803
|
-
|
|
3804
3671
|
// ── global loops (multi-project) ───────────────────────────
|
|
3805
3672
|
function deduplicateLoops(loops) {
|
|
3806
3673
|
const hasRepeat = loops.some(l => l.source === '_repeat.md');
|
|
@@ -3841,94 +3708,33 @@ async function renderGlobalLoops() {
|
|
|
3841
3708
|
if (!loops.length) continue;
|
|
3842
3709
|
totalLoops += loops.length;
|
|
3843
3710
|
const projName = project.name || project.slug || project.path?.split('/').pop() || '?';
|
|
3844
|
-
const rows = loops.map(
|
|
3711
|
+
const rows = loops.map(l => {
|
|
3845
3712
|
const isTillend = l.type === 'tillend';
|
|
3846
3713
|
const curRep = l.currentRep || 0;
|
|
3847
3714
|
const maxReps = l.maxReps || 0;
|
|
3848
|
-
const nextAt = l.nextRunAt ? parseInt(l.nextRunAt) : 0;
|
|
3715
|
+
const nextAt = l.nextRunAt ? parseInt(l.nextRunAt, 10) : 0;
|
|
3849
3716
|
const isHil = l.status === 'hil:pending';
|
|
3850
3717
|
const isExplicitlyActive = l.status === 'running' || l.status === 'waiting' || l.status === 'active';
|
|
3851
|
-
const
|
|
3852
|
-
const
|
|
3853
|
-
nextAt > 0 && nextAt <= Date.now();
|
|
3854
|
-
const isStaledActive = isExplicitlyActive && nextAt > 0 &&
|
|
3855
|
-
(Date.now() - nextAt) > LOOP_STALE_MS;
|
|
3856
|
-
const isFinished = isOverdue || isStaledActive ||
|
|
3857
|
-
(!isExplicitlyActive && maxReps > 0 && curRep >= maxReps) ||
|
|
3718
|
+
const isOverdue = !l.status?.startsWith('hil') && !isExplicitlyActive && nextAt > 0 && nextAt <= Date.now();
|
|
3719
|
+
const isFinished = isOverdue || (maxReps > 0 && curRep >= maxReps) ||
|
|
3858
3720
|
['finished','done','complete','completed','expired'].includes(l.status);
|
|
3859
3721
|
const running = !isFinished && l.status !== 'stopped' && l.status !== 'paused';
|
|
3722
|
+
const name = l.name || (l.prompt || 'loop').split('--')[0].trim().slice(0, 60);
|
|
3860
3723
|
const intervalStr = fmtInterval(l.interval || l.schedule);
|
|
3861
|
-
const
|
|
3862
|
-
if (_l.command) {
|
|
3863
|
-
const flags = [];
|
|
3864
|
-
if (_l.type && _l.type !== 'repeat') flags.push('--' + _l.type);
|
|
3865
|
-
if (_l.maxReps) flags.push('--maxruns ' + _l.maxReps);
|
|
3866
|
-
if (_l.wait || _l.interval) flags.push('--wait ' + (_l.wait || _l.interval * 60));
|
|
3867
|
-
if (_l.currentRep != null) flags.push('--rep ' + _l.currentRep);
|
|
3868
|
-
if (_l.id) flags.push('--loop ' + _l.id);
|
|
3869
|
-
return { userPrompt: _l.prompt || '', command: _l.command, flagsStr: flags.join(' ') };
|
|
3870
|
-
}
|
|
3871
|
-
const full = _l.prompt || '';
|
|
3872
|
-
const cmdM = full.match(/^(\/\S+)/);
|
|
3873
|
-
if (!cmdM) return { userPrompt: full, command: '', flagsStr: '' };
|
|
3874
|
-
const tokens = full.slice(cmdM[1].length).trim().split(/\s+/);
|
|
3875
|
-
let ti = 0, fp = [];
|
|
3876
|
-
while (ti < tokens.length && tokens[ti] && tokens[ti].startsWith('--')) {
|
|
3877
|
-
fp.push(tokens[ti++]);
|
|
3878
|
-
if (ti < tokens.length && tokens[ti] && !tokens[ti].startsWith('--')) fp.push(tokens[ti++]);
|
|
3879
|
-
}
|
|
3880
|
-
return { userPrompt: tokens.slice(ti).join(' '), command: cmdM[1], flagsStr: fp.join(' ') };
|
|
3881
|
-
})(l);
|
|
3882
|
-
const userPrompt = _lp.userPrompt;
|
|
3883
|
-
const cmdStr = _lp.command;
|
|
3884
|
-
const flagsStr = _lp.flagsStr;
|
|
3885
|
-
const fullPrompt = l.prompt || '';
|
|
3886
|
-
const name = (l.name || userPrompt || cmdStr || 'loop').slice(0, 60);
|
|
3887
|
-
const startedAt = l.startedAt ? new Date(l.startedAt).toLocaleString() : '—';
|
|
3888
|
-
const lastRun = l.lastRunAt ? relTime(l.lastRunAt) : (l.startedAt ? relTime(l.startedAt) : '—');
|
|
3889
|
-
const pct = (!isTillend && maxReps > 0) ? Math.min(100, Math.round(curRep / maxReps * 100)) : 0;
|
|
3890
|
-
const progBar = (!isTillend && maxReps > 0 && running)
|
|
3891
|
-
? `<div class="lp-bar"><div class="lp-fill" style="width:${pct}%"></div></div>` : '';
|
|
3892
|
-
const runCountDisplay = isTillend
|
|
3893
|
-
? `run ${curRep} / ∞${maxReps > 0 ? ' (cap: ' + maxReps + ')' : ''}`
|
|
3894
|
-
: (maxReps > 0 ? `${curRep} / ${maxReps}` : String(curRep || '—'));
|
|
3895
|
-
const cdownSpan = nextAt
|
|
3896
|
-
? ` <span class="loop-cdown${nextAt - Date.now() <= 0 ? ' overdue' : ''}" data-nextat="${nextAt}">${fmtCountdown(nextAt)}</span>` : '';
|
|
3897
|
-
const stopBtn = running
|
|
3898
|
-
? `<button class="loop-stop-btn" data-loop-id="${esc(l.id || l.name || String(idx))}" onclick="stopLoop(event, this.dataset.loopId)" title="Stop this loop">■ Stop</button>` : '';
|
|
3899
|
-
const typeBadge = `<span class="loop-type-badge${isTillend ? ' tillend' : ''}">${esc(l.type || 'repeat')}</span>`;
|
|
3724
|
+
const type = esc(l.type || 'repeat');
|
|
3900
3725
|
const statusClass = isHil ? 'hil' : (running ? 'active' : 'stopped');
|
|
3901
3726
|
const statusLabel = isHil ? '⚠ HIL' : (running ? 'active' : (isFinished ? 'done' : 'stopped'));
|
|
3902
|
-
const
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
<div class="loop-
|
|
3908
|
-
|
|
3909
|
-
<div class="loop-meta">${esc(metaParts)}${cdownSpan}</div>
|
|
3910
|
-
${hilBanner}
|
|
3911
|
-
${progBar}
|
|
3912
|
-
</div>
|
|
3913
|
-
<div class="loop-status ${statusClass}">${statusLabel}</div>
|
|
3914
|
-
${stopBtn}
|
|
3727
|
+
const overdueSpan = isOverdue ? ' <span class="loop-cdown overdue">overdue</span>' : '';
|
|
3728
|
+
const metaStr = intervalStr ? esc(intervalStr) + overdueSpan : overdueSpan;
|
|
3729
|
+
return `<div class="loop-row" style="cursor:default">
|
|
3730
|
+
<div class="loop-ico">${isTillend ? '∞' : '↺'}</div>
|
|
3731
|
+
<div class="loop-body">
|
|
3732
|
+
<div class="loop-name" title="${esc(l.name || l.prompt || '')}"><span class="loop-type-badge${isTillend ? ' tillend' : ''}">${type}</span>${esc(name)}</div>
|
|
3733
|
+
<div class="loop-meta">${metaStr}</div>
|
|
3915
3734
|
</div>
|
|
3916
|
-
<
|
|
3917
|
-
|
|
3918
|
-
|
|
3919
|
-
${flagsStr ? `<div class="le-row"><div class="le-lbl">Flags</div><div class="le-val mono" style="display:flex;align-items:flex-start;gap:8px"><span title="${esc(flagsStr)}">${esc(flagsStr.slice(0, 300))}${flagsStr.length > 300 ? '…' : ''}</span><button class="btn" style="flex-shrink:0;font-size:10px;padding:1px 6px" title="Copy flags" onclick="event.stopPropagation();navigator.clipboard.writeText(${JSON.stringify(flagsStr).replace(/"/g, '"')}).then(()=>showToast('Copied','Flags copied','ok'))">⎘</button></div></div>` : ''}
|
|
3920
|
-
<div class="le-row"><div class="le-lbl">Project</div><div class="le-val"><span class="gf-proj-tag">${esc(projName)}</span></div></div>
|
|
3921
|
-
<div class="le-row"><div class="le-lbl">Type</div><div class="le-val">${esc(l.type || 'repeat')}</div></div>
|
|
3922
|
-
<div class="le-row"><div class="le-lbl">Interval</div><div class="le-val">${esc(intervalStr || '—')}</div></div>
|
|
3923
|
-
<div class="le-row"><div class="le-lbl">Status</div><div class="le-val">${isHil ? '⚠ hil:pending' : (running ? '● running' : (isFinished ? '✓ done' : '○ stopped'))}</div></div>
|
|
3924
|
-
${isHil && l.id ? `<div class="le-row"><div class="le-lbl">HIL file</div><div class="le-val mono" style="color:oklch(75% 0.16 60);word-break:break-all">.monomind/loops/${esc(l.id)}-hil.md</div></div>` : ''}
|
|
3925
|
-
<div class="le-row"><div class="le-lbl">Started</div><div class="le-val mono">${esc(startedAt)}</div></div>
|
|
3926
|
-
${(()=>{ const sMs=l.startedAt?(typeof l.startedAt==='number'?l.startedAt:new Date(l.startedAt).getTime()):0; const age=sMs>0&&sMs<Date.now()?Date.now()-sMs:0; return age>0?`<div class="le-row"><div class="le-lbl">Running for</div><div class="le-val">${fmtDur(age)}</div></div>`:''; })()}
|
|
3927
|
-
<div class="le-row"><div class="le-lbl">Last run</div><div class="le-val" title="${l.lastRunAt ? new Date(typeof l.lastRunAt === 'number' ? l.lastRunAt : Number(l.lastRunAt) || l.lastRunAt).toLocaleString() : ''}">${esc(lastRun)}</div></div>
|
|
3928
|
-
<div class="le-row"><div class="le-lbl">${isTillend ? 'Progress' : 'Run count'}</div><div class="le-val">${esc(runCountDisplay)}</div></div>
|
|
3929
|
-
${l.source ? `<div class="le-row"><div class="le-lbl">Source</div><div class="le-val">${esc(l.source)}</div></div>` : ''}
|
|
3930
|
-
${buildLoopSparkline(l)}
|
|
3931
|
-
</div>`;
|
|
3735
|
+
<span class="gf-proj-tag">${esc(projName)}</span>
|
|
3736
|
+
<div class="loop-status ${statusClass}">${statusLabel}</div>
|
|
3737
|
+
</div>`;
|
|
3932
3738
|
}).join('');
|
|
3933
3739
|
sections.push(`<div style="margin-bottom:18px">
|
|
3934
3740
|
<div class="m-group-title" style="margin-bottom:6px">${esc(projName)}</div>
|
|
@@ -3944,7 +3750,6 @@ async function renderGlobalLoops() {
|
|
|
3944
3750
|
return;
|
|
3945
3751
|
}
|
|
3946
3752
|
el.innerHTML = sections.join('');
|
|
3947
|
-
startCountdowns();
|
|
3948
3753
|
} catch (err) {
|
|
3949
3754
|
el.innerHTML = '<div class="empty">Could not load: ' + esc(err.message) + '</div>';
|
|
3950
3755
|
}
|
|
@@ -4002,9 +3807,7 @@ async function renderGlobalTokens() {
|
|
|
4002
3807
|
</tr>`).join('');
|
|
4003
3808
|
|
|
4004
3809
|
const projectCount = rows.length;
|
|
4005
|
-
document.getElementById('gt-sub').textContent =
|
|
4006
|
-
? `Token usage across ${projectCount} of ${allProjects.length} projects`
|
|
4007
|
-
: `Token usage across ${projectCount} project${projectCount !== 1 ? 's' : ''}`;
|
|
3810
|
+
document.getElementById('gt-sub').textContent = `Token usage across ${projectCount} project${projectCount !== 1 ? 's' : ''}`;
|
|
4008
3811
|
|
|
4009
3812
|
el.innerHTML = `<div id="gt-cards" style="display:flex;gap:10px;flex-wrap:wrap;margin-bottom:20px">${cards}</div>
|
|
4010
3813
|
<div id="gt-table">
|
|
@@ -4025,6 +3828,217 @@ async function renderGlobalTokens() {
|
|
|
4025
3828
|
}
|
|
4026
3829
|
}
|
|
4027
3830
|
|
|
3831
|
+
// ── feature 3: global feed ─────────────────────────────────
|
|
3832
|
+
async function renderGlobalFeed() {
|
|
3833
|
+
const el = document.getElementById('gf-content');
|
|
3834
|
+
el.innerHTML = '<div class="loading-txt">Loading all projects…</div>';
|
|
3835
|
+
try {
|
|
3836
|
+
// fetch project list
|
|
3837
|
+
const data = await apiFetch('/api/projects');
|
|
3838
|
+
const projects = (data?.projects || []).slice(0, 8);
|
|
3839
|
+
if (!projects.length) {
|
|
3840
|
+
el.innerHTML = '<div class="empty"><div class="empty-ico">⊕</div><div>No projects found</div></div>';
|
|
3841
|
+
return;
|
|
3842
|
+
}
|
|
3843
|
+
document.getElementById('gf-sub').textContent = `Last activity across ${projects.length} projects`;
|
|
3844
|
+
|
|
3845
|
+
// fetch sessions for each project in parallel
|
|
3846
|
+
const results = await Promise.allSettled(
|
|
3847
|
+
projects.map(p => apiFetch('/api/session-journal?dir=' + enc(p.path)).then(d => ({ project: p, sessions: d.sessions || [] })))
|
|
3848
|
+
);
|
|
3849
|
+
|
|
3850
|
+
// flatten + sort by recency
|
|
3851
|
+
const entries = [];
|
|
3852
|
+
for (const r of results) {
|
|
3853
|
+
if (r.status !== 'fulfilled') continue;
|
|
3854
|
+
const { project, sessions } = r.value;
|
|
3855
|
+
for (const s of sessions.slice(0, 3)) {
|
|
3856
|
+
entries.push({ project, session: s });
|
|
3857
|
+
}
|
|
3858
|
+
}
|
|
3859
|
+
entries.sort((a, b) => {
|
|
3860
|
+
const ta = a.session.lastTs || a.session.mtime || 0;
|
|
3861
|
+
const tb = b.session.lastTs || b.session.mtime || 0;
|
|
3862
|
+
return (typeof tb === 'number' ? tb : new Date(tb).getTime()) - (typeof ta === 'number' ? ta : new Date(ta).getTime());
|
|
3863
|
+
});
|
|
3864
|
+
|
|
3865
|
+
if (!entries.length) {
|
|
3866
|
+
el.innerHTML = '<div class="empty"><div class="empty-ico">⊕</div><div>No sessions found</div></div>';
|
|
3867
|
+
return;
|
|
3868
|
+
}
|
|
3869
|
+
|
|
3870
|
+
el.innerHTML = '<div class="sess-list">' + entries.map(({ project, session: s }) => {
|
|
3871
|
+
const projName = project.name || project.slug || project.path?.split('/').pop() || '?';
|
|
3872
|
+
const dur = s.totalDurationMs ? fmtDur(s.totalDurationMs) : '';
|
|
3873
|
+
const cost = typeof s.totalCost === 'number' ? '$' + s.totalCost.toFixed(2) : '';
|
|
3874
|
+
const meta = [dur, cost].filter(Boolean).join(' · ') || s.id.slice(0, 12);
|
|
3875
|
+
const gfCompactBadge = (s.compactCount > 0)
|
|
3876
|
+
? `<span class="sr-compact-badge">+${s.compactCount} compacted</span>`
|
|
3877
|
+
: '';
|
|
3878
|
+
const gfSummaryHtml = s.summary
|
|
3879
|
+
? `<div class="sr-summary">${esc(s.summary.slice(0, 180))}</div>`
|
|
3880
|
+
: '';
|
|
3881
|
+
return `<div class="sess-row" onclick="switchProject('${esc(project.path)}');setTimeout(()=>jumpToSession('${esc(s.id)}'),150)">
|
|
3882
|
+
<div class="sr-top">
|
|
3883
|
+
<div class="sr-prompt">${esc(s.lastPrompt || s.id?.slice(0,8) || '—')}</div>
|
|
3884
|
+
<div class="sr-time">${relTime(s.lastTs || s.mtime)}</div>${gfCompactBadge}
|
|
3885
|
+
<span class="gf-proj-tag">${esc(projName)}</span>
|
|
3886
|
+
</div>
|
|
3887
|
+
${gfSummaryHtml}
|
|
3888
|
+
<div class="sr-meta">${esc(meta)}</div>
|
|
3889
|
+
</div>`;
|
|
3890
|
+
}).join('') + '</div>';
|
|
3891
|
+
} catch (err) {
|
|
3892
|
+
el.innerHTML = '<div class="empty">Could not load: ' + esc(err.message) + '</div>';
|
|
3893
|
+
}
|
|
3894
|
+
}
|
|
3895
|
+
|
|
3896
|
+
// ── agent chat view ────────────────────────────────────────
|
|
3897
|
+
let chatVSessions = {};
|
|
3898
|
+
let chatVCurrentId = null;
|
|
3899
|
+
let chatVSseSource = null;
|
|
3900
|
+
|
|
3901
|
+
function initChatView() {
|
|
3902
|
+
loadChatViewSessions();
|
|
3903
|
+
if (!chatVSseSource) connectChatViewSSE();
|
|
3904
|
+
}
|
|
3905
|
+
|
|
3906
|
+
async function loadChatViewSessions() {
|
|
3907
|
+
try {
|
|
3908
|
+
const data = await apiFetch('/api/mastermind/sessions');
|
|
3909
|
+
chatVSessions = {};
|
|
3910
|
+
const sel = document.getElementById('chat-v-sel');
|
|
3911
|
+
const prev = sel.value;
|
|
3912
|
+
while (sel.options.length > 1) sel.remove(1);
|
|
3913
|
+
const sessions = Object.values(data.sessions || {});
|
|
3914
|
+
sessions.sort((a, b) => (b.startedAt || 0) - (a.startedAt || 0));
|
|
3915
|
+
sessions.forEach(s => {
|
|
3916
|
+
const opt = document.createElement('option');
|
|
3917
|
+
opt.value = s.id;
|
|
3918
|
+
const ts = s.startedAt ? new Date(s.startedAt).toLocaleTimeString([], {hour:'2-digit',minute:'2-digit'}) : '';
|
|
3919
|
+
opt.textContent = (s.id.slice(0,16)) + (ts ? ' ' + ts : '') + (s.status === 'running' ? ' ●' : '');
|
|
3920
|
+
sel.appendChild(opt);
|
|
3921
|
+
chatVSessions[s.id] = s;
|
|
3922
|
+
});
|
|
3923
|
+
if (prev && chatVSessions[prev]) { sel.value = prev; }
|
|
3924
|
+
else {
|
|
3925
|
+
const running = sessions.find(s => s.status === 'running');
|
|
3926
|
+
if (running) { sel.value = running.id; chatVSelectSession(running.id); }
|
|
3927
|
+
}
|
|
3928
|
+
} catch(e) { console.warn('chat sessions load failed', e); }
|
|
3929
|
+
}
|
|
3930
|
+
|
|
3931
|
+
function chatVSelectSession(id) {
|
|
3932
|
+
chatVCurrentId = id;
|
|
3933
|
+
const feed = document.getElementById('chat-v-feed');
|
|
3934
|
+
const empty = document.getElementById('chat-v-empty');
|
|
3935
|
+
if (!id || !chatVSessions[id]) {
|
|
3936
|
+
feed.innerHTML = '';
|
|
3937
|
+
feed.appendChild(empty);
|
|
3938
|
+
return;
|
|
3939
|
+
}
|
|
3940
|
+
feed.innerHTML = '';
|
|
3941
|
+
const session = chatVSessions[id];
|
|
3942
|
+
const events = session.events || [];
|
|
3943
|
+
events.forEach(ev => appendChatViewEvent(ev, false));
|
|
3944
|
+
feed.scrollTop = feed.scrollHeight;
|
|
3945
|
+
}
|
|
3946
|
+
|
|
3947
|
+
function appendChatViewEvent(ev, animate) {
|
|
3948
|
+
if (chatVCurrentId && ev.session && ev.session !== chatVCurrentId) return;
|
|
3949
|
+
const feed = document.getElementById('chat-v-feed');
|
|
3950
|
+
if (!feed) return;
|
|
3951
|
+
const empty = document.getElementById('chat-v-empty');
|
|
3952
|
+
if (empty && feed.contains(empty)) feed.removeChild(empty);
|
|
3953
|
+
|
|
3954
|
+
let el;
|
|
3955
|
+
const ts = ev.ts ? new Date(ev.ts).toLocaleTimeString([],{hour:'2-digit',minute:'2-digit',second:'2-digit'}) : '';
|
|
3956
|
+
if (ev.type === 'intercom') {
|
|
3957
|
+
el = mkCVIntercom(ev.from, ev.to, ev.msg || '', ts);
|
|
3958
|
+
} else if (ev.type === 'agent:message' || ev.type === 'agent:spawn') {
|
|
3959
|
+
el = mkCVAgent(ev.agent || ev.name || '?', ev.msg || ev.message || ev.role || ev.type, ts, ev.type);
|
|
3960
|
+
} else if (ev.type === 'session:start') {
|
|
3961
|
+
el = mkCVSys('Session started' + (ev.prompt ? ': ' + esc(ev.prompt.slice(0,80)) : ''), ts);
|
|
3962
|
+
} else if (ev.type === 'session:complete') {
|
|
3963
|
+
el = mkCVSys('Session complete' + (ev.status ? ' — ' + esc(ev.status) : ''), ts);
|
|
3964
|
+
} else if (ev.type === 'domain:dispatch') {
|
|
3965
|
+
el = mkCVSys('→ ' + esc(ev.domain || '') + (ev.cmd ? ': ' + esc(ev.cmd.slice(0,80)) : ''), ts);
|
|
3966
|
+
} else if (ev.type === 'domain:complete') {
|
|
3967
|
+
el = mkCVSys('✓ ' + esc(ev.domain || '') + (ev.status ? ' [' + esc(ev.status) + ']' : ''), ts);
|
|
3968
|
+
} else if (ev.type === 'loop:start') {
|
|
3969
|
+
el = mkCVSys('Loop started: ' + esc(ev.command || ''), ts);
|
|
3970
|
+
if (currentView === 'loops') renderLoops();
|
|
3971
|
+
} else if (ev.type === 'loop:complete') {
|
|
3972
|
+
el = mkCVSys('Loop complete: ' + esc(ev.command || '') + (ev.ranReps ? ' (' + ev.ranReps + ' runs)' : ''), ts);
|
|
3973
|
+
if (currentView === 'loops') renderLoops();
|
|
3974
|
+
} else if (ev.type === 'loop:tick') {
|
|
3975
|
+
el = mkCVSys('Loop tick: ' + esc(ev.command || ev.id || ''), ts);
|
|
3976
|
+
if (currentView === 'loops') renderLoops();
|
|
3977
|
+
} else if (ev.type === 'loop:hil') {
|
|
3978
|
+
el = mkCVSys('⚠ Loop HIL: ' + esc(ev.command || ev.id || ''), ts);
|
|
3979
|
+
if (currentView === 'loops') renderLoops();
|
|
3980
|
+
} else {
|
|
3981
|
+
el = mkCVSys(esc(ev.type || 'event'), ts);
|
|
3982
|
+
}
|
|
3983
|
+
if (animate) el.classList.add('cv-new');
|
|
3984
|
+
feed.appendChild(el);
|
|
3985
|
+
feed.scrollTop = feed.scrollHeight;
|
|
3986
|
+
}
|
|
3987
|
+
|
|
3988
|
+
function mkCVSys(html, ts) {
|
|
3989
|
+
const d = document.createElement('div');
|
|
3990
|
+
d.className = 'cv-msg cv-sys';
|
|
3991
|
+
d.innerHTML = `<div class="cv-bub"><span class="cv-etype">SYS</span><span class="cv-text">${html}</span><span class="cv-ts">${ts}</span></div>`;
|
|
3992
|
+
return d;
|
|
3993
|
+
}
|
|
3994
|
+
|
|
3995
|
+
function mkCVAgent(name, text, ts, typeTag) {
|
|
3996
|
+
const d = document.createElement('div');
|
|
3997
|
+
d.className = 'cv-msg cv-agent';
|
|
3998
|
+
const tag = typeTag === 'agent:spawn' ? 'SPAWN' : 'MSG';
|
|
3999
|
+
d.innerHTML = `<div class="cv-bub"><span class="cv-tag">${esc(name)}</span><span class="cv-etype">${tag}</span><span class="cv-text">${esc(String(text).slice(0,200))}</span><span class="cv-ts">${ts}</span></div>`;
|
|
4000
|
+
return d;
|
|
4001
|
+
}
|
|
4002
|
+
|
|
4003
|
+
function mkCVIntercom(from, to, text, ts) {
|
|
4004
|
+
const d = document.createElement('div');
|
|
4005
|
+
d.className = 'cv-msg cv-ic';
|
|
4006
|
+
d.innerHTML = `<div class="cv-bub"><span class="cv-tag cv-sender">${esc(from||'?')}</span><span class="cv-arrow">→</span><span class="cv-tag cv-receiver">${esc(to||'?')}</span><span class="cv-etype">IC</span><span class="cv-text">${esc(String(text).slice(0,200))}</span><span class="cv-ts">${ts}</span></div>`;
|
|
4007
|
+
return d;
|
|
4008
|
+
}
|
|
4009
|
+
|
|
4010
|
+
function connectChatViewSSE() {
|
|
4011
|
+
if (chatVSseSource) return;
|
|
4012
|
+
const dot = document.getElementById('chat-v-live-dot');
|
|
4013
|
+
const lbl = document.getElementById('chat-v-live-lbl');
|
|
4014
|
+
chatVSseSource = new EventSource('/api/mastermind-stream');
|
|
4015
|
+
chatVSseSource.onopen = () => { dot && dot.classList.add('on'); lbl && (lbl.textContent = 'LIVE'); };
|
|
4016
|
+
chatVSseSource.onmessage = e => {
|
|
4017
|
+
try { handleChatViewEvent(JSON.parse(e.data)); } catch(_) {}
|
|
4018
|
+
};
|
|
4019
|
+
chatVSseSource.onerror = () => {
|
|
4020
|
+
dot && dot.classList.remove('on');
|
|
4021
|
+
lbl && (lbl.textContent = 'OFFLINE');
|
|
4022
|
+
chatVSseSource.close();
|
|
4023
|
+
chatVSseSource = null;
|
|
4024
|
+
setTimeout(connectChatViewSSE, 5000);
|
|
4025
|
+
};
|
|
4026
|
+
}
|
|
4027
|
+
|
|
4028
|
+
function handleChatViewEvent(ev) {
|
|
4029
|
+
if (!ev || !ev.session) return;
|
|
4030
|
+
if (!chatVSessions[ev.session]) {
|
|
4031
|
+
chatVSessions[ev.session] = { id: ev.session, events: [], startedAt: ev.ts, status: 'running' };
|
|
4032
|
+
loadChatViewSessions();
|
|
4033
|
+
} else {
|
|
4034
|
+
const s = chatVSessions[ev.session];
|
|
4035
|
+
s.events = s.events || [];
|
|
4036
|
+
s.events.push(ev);
|
|
4037
|
+
if (ev.type === 'session:complete') s.status = 'complete';
|
|
4038
|
+
}
|
|
4039
|
+
if (chatVCurrentId === ev.session) appendChatViewEvent(ev, true);
|
|
4040
|
+
}
|
|
4041
|
+
|
|
4028
4042
|
// ── feature 4: budget cap + desktop notification ───────────
|
|
4029
4043
|
let budget = (function(){ try { return JSON.parse(localStorage.getItem('mm-budget') || '{}'); } catch { return {}; } })();
|
|
4030
4044
|
|
|
@@ -4049,7 +4063,6 @@ function saveBudget() {
|
|
|
4049
4063
|
closeBudgetModal();
|
|
4050
4064
|
checkBudget(); // check immediately
|
|
4051
4065
|
updateBudgetBtnStyle();
|
|
4052
|
-
showToast('Budget saved', budget.daily || budget.monthly ? `Daily: ${budget.daily ? '$'+budget.daily : '—'} · Monthly: ${budget.monthly ? '$'+budget.monthly : '—'}` : 'Budget cleared', 'ok');
|
|
4053
4066
|
}
|
|
4054
4067
|
|
|
4055
4068
|
function updateBudgetBtnStyle() {
|
|
@@ -4061,36 +4074,21 @@ function updateBudgetBtnStyle() {
|
|
|
4061
4074
|
|
|
4062
4075
|
function checkBudget() {
|
|
4063
4076
|
const cost = alertState.todayCost;
|
|
4064
|
-
|
|
4065
|
-
|
|
4066
|
-
if (budget.daily && cost) {
|
|
4077
|
+
if (!cost) return;
|
|
4078
|
+
if (budget.daily) {
|
|
4067
4079
|
const pct = cost / budget.daily;
|
|
4068
4080
|
if (pct >= 1 && !dismissedAlerts.has('budget-daily-over')) {
|
|
4069
4081
|
alertState.budgetAlert = `Daily budget exceeded: $${cost.toFixed(2)} / $${budget.daily}`;
|
|
4070
4082
|
alertState.budgetCls = 'alert-crit';
|
|
4071
|
-
updateAlerts(); return;
|
|
4072
4083
|
} else if (pct >= 0.8 && !dismissedAlerts.has('budget-daily-warn')) {
|
|
4073
4084
|
alertState.budgetAlert = `Approaching daily budget: $${cost.toFixed(2)} / $${budget.daily}`;
|
|
4074
4085
|
alertState.budgetCls = 'alert-warn';
|
|
4075
4086
|
maybeNotify('monomind budget', `$${cost.toFixed(2)} of $${budget.daily} daily budget used`);
|
|
4076
|
-
|
|
4077
|
-
|
|
4078
|
-
}
|
|
4079
|
-
// Monthly budget check
|
|
4080
|
-
if (budget.monthly && moCost) {
|
|
4081
|
-
const mpct = moCost / budget.monthly;
|
|
4082
|
-
if (mpct >= 1 && !dismissedAlerts.has('budget-monthly-over')) {
|
|
4083
|
-
alertState.budgetAlert = `Monthly budget exceeded: $${moCost.toFixed(2)} / $${budget.monthly}`;
|
|
4084
|
-
alertState.budgetCls = 'alert-crit';
|
|
4085
|
-
updateAlerts(); return;
|
|
4086
|
-
} else if (mpct >= 0.8 && !dismissedAlerts.has('budget-monthly-warn')) {
|
|
4087
|
-
alertState.budgetAlert = `Approaching monthly budget: $${moCost.toFixed(2)} / $${budget.monthly}`;
|
|
4088
|
-
alertState.budgetCls = 'alert-warn';
|
|
4089
|
-
updateAlerts(); return;
|
|
4087
|
+
} else {
|
|
4088
|
+
alertState.budgetAlert = null;
|
|
4090
4089
|
}
|
|
4090
|
+
updateAlerts();
|
|
4091
4091
|
}
|
|
4092
|
-
alertState.budgetAlert = null;
|
|
4093
|
-
updateAlerts();
|
|
4094
4092
|
}
|
|
4095
4093
|
|
|
4096
4094
|
function maybeNotify(title, body) {
|
|
@@ -4212,7 +4210,7 @@ function buildBreakdownByName(events) {
|
|
|
4212
4210
|
const pct = Math.round(cnt / total * 100);
|
|
4213
4211
|
return `<div class="tb-row">
|
|
4214
4212
|
<div class="tb-lbl" style="width:54px" title="${esc(name)}">${esc(name.length > 8 ? name.slice(0,7)+'…' : name)}</div>
|
|
4215
|
-
<div class="tb-bar-wrap"
|
|
4213
|
+
<div class="tb-bar-wrap"><div class="tb-bar" style="width:${pct}%;background:${getColor(name)}"></div></div>
|
|
4216
4214
|
<div class="tb-count">${cnt}</div>
|
|
4217
4215
|
</div>`;
|
|
4218
4216
|
}).join('');
|
|
@@ -4236,7 +4234,7 @@ function buildDigest() {
|
|
|
4236
4234
|
const todayStart = new Date(); todayStart.setHours(0, 0, 0, 0);
|
|
4237
4235
|
const todaySessions = allSessions.filter(s => {
|
|
4238
4236
|
const t = s.lastTs || s.mtime;
|
|
4239
|
-
return t && new Date(typeof t === 'number' ? t :
|
|
4237
|
+
return t && new Date(typeof t === 'number' ? t : t).getTime() >= todayStart.getTime();
|
|
4240
4238
|
});
|
|
4241
4239
|
if (!todaySessions.length) return;
|
|
4242
4240
|
|
|
@@ -4255,8 +4253,8 @@ function buildDigest() {
|
|
|
4255
4253
|
const stats = [
|
|
4256
4254
|
`${todaySessions.length} session${todaySessions.length > 1 ? 's' : ''}`,
|
|
4257
4255
|
totalCost > 0 ? `$${totalCost.toFixed(2)} spent` : null,
|
|
4258
|
-
totalTools > 0 ? `${totalTools
|
|
4259
|
-
totalMsgs > 0 ? `${totalMsgs
|
|
4256
|
+
totalTools > 0 ? `${totalTools} tool calls` : null,
|
|
4257
|
+
totalMsgs > 0 ? `${totalMsgs} messages` : null,
|
|
4260
4258
|
longestMs > 0 ? `${fmtDur(longestMs)} longest` : null,
|
|
4261
4259
|
...themes.map(t => `#${t}`),
|
|
4262
4260
|
].filter(Boolean);
|
|
@@ -4268,7 +4266,7 @@ function buildDigest() {
|
|
|
4268
4266
|
const monthCostSoFar = allSessions.filter(s => {
|
|
4269
4267
|
const t = s.firstTs || s.mtime;
|
|
4270
4268
|
if (!t) return false;
|
|
4271
|
-
const d = new Date(typeof t === 'number' ? t :
|
|
4269
|
+
const d = new Date(typeof t === 'number' ? t : t);
|
|
4272
4270
|
return d.getFullYear() === today2.getFullYear() && d.getMonth() === today2.getMonth();
|
|
4273
4271
|
}).reduce((a, s) => a + (s.totalCost || 0), 0);
|
|
4274
4272
|
const dailyAvg = dayOfMonth > 0 ? monthCostSoFar / dayOfMonth : 0;
|
|
@@ -4337,25 +4335,23 @@ function toggleLeaderboard() {
|
|
|
4337
4335
|
}
|
|
4338
4336
|
|
|
4339
4337
|
function renderLeaderboard() {
|
|
4340
|
-
const all = [...allSessions]
|
|
4341
|
-
.filter(s => typeof s.totalCost === 'number' && s.totalCost > 0)
|
|
4342
|
-
.sort((a, b) => b.totalCost - a.totalCost);
|
|
4338
|
+
const all = [...allSessions].filter(s => typeof s.totalCost === 'number' && s.totalCost > 0).sort((a, b) => b.totalCost - a.totalCost);
|
|
4343
4339
|
const sorted = all.slice(0, 15);
|
|
4344
4340
|
const body = document.getElementById('lb-body');
|
|
4345
|
-
|
|
4346
|
-
if (!sorted.length) { body.innerHTML = '<tr><td colspan="4" style="text-align:center;color:var(--text-xs);padding:12px">No cost data yet</td></tr>'; if (overflow) overflow.textContent = ''; return; }
|
|
4341
|
+
if (!sorted.length) { body.innerHTML = '<tr><td colspan="4" style="text-align:center;color:var(--text-xs);padding:12px">No cost data yet</td></tr>'; return; }
|
|
4347
4342
|
body.innerHTML = sorted.map((s, i) => {
|
|
4348
4343
|
const cost = '$' + s.totalCost.toFixed(2);
|
|
4349
4344
|
const dur = s.totalDurationMs ? fmtDur(s.totalDurationMs) : '—';
|
|
4350
4345
|
const prompt = s.lastPrompt || s.id;
|
|
4351
4346
|
return `<tr onclick="jumpToSession('${esc(s.id)}')" title="${esc(prompt)}">
|
|
4352
4347
|
<td class="lb-rank">${i + 1}</td>
|
|
4353
|
-
<td class="lb-prompt">${esc(prompt.slice(0, 60))}
|
|
4348
|
+
<td class="lb-prompt">${esc(prompt.slice(0, 60))}</td>
|
|
4354
4349
|
<td class="lb-cost">${cost}</td>
|
|
4355
4350
|
<td class="lb-dur">${dur}</td>
|
|
4356
4351
|
</tr>`;
|
|
4357
4352
|
}).join('');
|
|
4358
|
-
|
|
4353
|
+
const ov = document.getElementById('lb-overflow');
|
|
4354
|
+
if (ov) ov.textContent = all.length > 15 ? `Showing top 15 of ${all.length} sessions` : '';
|
|
4359
4355
|
}
|
|
4360
4356
|
|
|
4361
4357
|
// ── feature 12: session diff ──────────────────────────────
|
|
@@ -4472,7 +4468,7 @@ async function exportSession() {
|
|
|
4472
4468
|
const events = data.events || [];
|
|
4473
4469
|
const lines = [
|
|
4474
4470
|
`# Session: ${sess.lastPrompt || sess.id}`,
|
|
4475
|
-
`> ${new Date(
|
|
4471
|
+
`> ${new Date(sess.lastTs || sess.mtime).toLocaleString()}`,
|
|
4476
4472
|
sess.totalCost != null ? `> Cost: $${sess.totalCost.toFixed(2)}` : '',
|
|
4477
4473
|
sess.totalDurationMs ? `> Duration: ${fmtDur(sess.totalDurationMs)}` : '',
|
|
4478
4474
|
'',
|
|
@@ -4480,7 +4476,7 @@ async function exportSession() {
|
|
|
4480
4476
|
for (const ev of events) {
|
|
4481
4477
|
if (ev.kind === 'user' && ev.text?.trim()) {
|
|
4482
4478
|
lines.push(`\n## ${ev.text.trim().slice(0, 80)}`);
|
|
4483
|
-
if (ev.ts) lines.push(`_${new Date(
|
|
4479
|
+
if (ev.ts) lines.push(`_${new Date(ev.ts).toLocaleTimeString()}_`);
|
|
4484
4480
|
} else if (ev.kind === 'tool') {
|
|
4485
4481
|
const label = ev.label || ev.name || ev.cat;
|
|
4486
4482
|
lines.push(`- \`${ev.name || ev.cat}\`${label ? ': ' + label : ''}${ev._errored ? ' ⚠ error' : ''}`);
|
|
@@ -4556,9 +4552,9 @@ function renderBurnGauge() {
|
|
|
4556
4552
|
}
|
|
4557
4553
|
const now = Date.now();
|
|
4558
4554
|
// calls in last 5 min, 15 min, 60 min
|
|
4559
|
-
const t5 = tools.filter(e => now - (
|
|
4560
|
-
const t15 = tools.filter(e => now - (
|
|
4561
|
-
const t60 = tools.filter(e => now - (
|
|
4555
|
+
const t5 = tools.filter(e => now - new Date(e.ts).getTime() < 300000).length;
|
|
4556
|
+
const t15 = tools.filter(e => now - new Date(e.ts).getTime() < 900000).length;
|
|
4557
|
+
const t60 = tools.filter(e => now - new Date(e.ts).getTime() < 3600000).length;
|
|
4562
4558
|
const rate5 = (t5 / 5).toFixed(1); // calls/min
|
|
4563
4559
|
const rate15 = (t15 / 15).toFixed(1);
|
|
4564
4560
|
const rate60 = (t60 / 60).toFixed(1);
|
|
@@ -4587,7 +4583,7 @@ function buildSwimlane() {
|
|
|
4587
4583
|
const LANE_HUES = [75, 200, 300, 150, 25, 220, 340, 120];
|
|
4588
4584
|
const rows = recent.map((s, si) => {
|
|
4589
4585
|
const start = s.firstTs || s.startTs || s.mtime || now;
|
|
4590
|
-
const startMs = typeof start === 'number' ? start :
|
|
4586
|
+
const startMs = typeof start === 'number' ? start : new Date(start).getTime();
|
|
4591
4587
|
const dur = s.totalDurationMs || 60000;
|
|
4592
4588
|
const endMs = startMs + dur;
|
|
4593
4589
|
const leftPct = Math.max(0, Math.min(100, ((startMs - windowStart) / windowMs) * 100));
|
|
@@ -4605,17 +4601,17 @@ function buildSwimlane() {
|
|
|
4605
4601
|
}).join('');
|
|
4606
4602
|
// dead time: find largest gap between consecutive sessions
|
|
4607
4603
|
const sorted = recent.slice().sort((a, b) => {
|
|
4608
|
-
const
|
|
4609
|
-
const
|
|
4604
|
+
const aTs = typeof (a.firstTs || a.mtime) === 'number' ? (a.firstTs || a.mtime) : new Date(a.firstTs || a.mtime).getTime();
|
|
4605
|
+
const bTs = typeof (b.firstTs || b.mtime) === 'number' ? (b.firstTs || b.mtime) : new Date(b.firstTs || b.mtime).getTime();
|
|
4610
4606
|
return aTs - bTs;
|
|
4611
4607
|
});
|
|
4612
4608
|
let maxGapMs = 0; let gapStart = 0;
|
|
4613
4609
|
for (let i = 1; i < sorted.length; i++) {
|
|
4614
4610
|
const prev = sorted[i - 1];
|
|
4615
4611
|
const curr = sorted[i];
|
|
4616
|
-
const
|
|
4612
|
+
const prevTs = typeof (prev.firstTs || prev.mtime) === 'number' ? (prev.firstTs || prev.mtime) : new Date(prev.firstTs || prev.mtime).getTime();
|
|
4617
4613
|
const prevEnd = prevTs + (prev.totalDurationMs || 60000);
|
|
4618
|
-
const
|
|
4614
|
+
const currTs = typeof (curr.firstTs || curr.mtime) === 'number' ? (curr.firstTs || curr.mtime) : new Date(curr.firstTs || curr.mtime).getTime();
|
|
4619
4615
|
const gap = currTs - prevEnd;
|
|
4620
4616
|
if (gap > maxGapMs) { maxGapMs = gap; gapStart = prevEnd; }
|
|
4621
4617
|
}
|
|
@@ -4649,149 +4645,103 @@ function buildLoopSparkline(l) {
|
|
|
4649
4645
|
return `<div class="le-spark"><span style="font-size:10px;color:var(--text-xs)">last ${runHistory.slice(-10).length} runs</span><div class="loop-sparkline">${bars}</div></div>`;
|
|
4650
4646
|
}
|
|
4651
4647
|
|
|
4652
|
-
function fmtInterval(v) {
|
|
4653
|
-
if (!v && v !== 0) return '';
|
|
4654
|
-
if (typeof v === 'string') return v;
|
|
4655
|
-
const m = parseInt(v);
|
|
4656
|
-
if (isNaN(m) || m <= 0) return String(v);
|
|
4657
|
-
if (m < 60) return m + 'm';
|
|
4658
|
-
if (m % 60 === 0) return (m / 60) + 'h';
|
|
4659
|
-
return Math.floor(m / 60) + 'h ' + (m % 60) + 'm';
|
|
4660
|
-
}
|
|
4661
|
-
|
|
4662
4648
|
function fmtCountdown(nextAt) {
|
|
4663
|
-
const ms =
|
|
4649
|
+
const ms = Number(nextAt) - Date.now();
|
|
4664
4650
|
if (ms <= 0) return 'overdue';
|
|
4665
|
-
const
|
|
4666
|
-
const m = Math.floor(
|
|
4667
|
-
const
|
|
4668
|
-
if (h > 0) return `next in ${h}h ${m}m`;
|
|
4669
|
-
|
|
4651
|
+
const s = Math.floor(ms / 1000);
|
|
4652
|
+
const m = Math.floor(s / 60);
|
|
4653
|
+
const h = Math.floor(m / 60);
|
|
4654
|
+
if (h > 0) return `next in ${h}h ${m % 60}m`;
|
|
4655
|
+
if (m > 0) return `next in ${m}m ${s % 60}s`;
|
|
4656
|
+
return `next in ${s}s`;
|
|
4670
4657
|
}
|
|
4671
4658
|
|
|
4672
|
-
function
|
|
4673
|
-
if (!
|
|
4674
|
-
const
|
|
4675
|
-
if (
|
|
4676
|
-
return
|
|
4659
|
+
function fmtInterval(minutes) {
|
|
4660
|
+
if (!minutes && minutes !== 0) return '—';
|
|
4661
|
+
const m = typeof minutes === 'string' ? parseFloat(minutes) : minutes;
|
|
4662
|
+
if (isNaN(m) || m <= 0) return String(minutes);
|
|
4663
|
+
if (m < 60) return m + 'm';
|
|
4664
|
+
const h = Math.floor(m / 60), rem = Math.round(m % 60);
|
|
4665
|
+
return rem ? `${h}h ${rem}m` : `${h}h`;
|
|
4677
4666
|
}
|
|
4678
4667
|
|
|
4679
4668
|
// ── loops ──────────────────────────────────────────────────
|
|
4669
|
+
const LOOP_STALE_MS = 2 * 60 * 60 * 1000;
|
|
4670
|
+
|
|
4680
4671
|
async function renderLoops() {
|
|
4681
4672
|
const el = document.getElementById('loops-content');
|
|
4682
4673
|
el.innerHTML = '<div class="loading-txt">Loading…</div>';
|
|
4683
4674
|
try {
|
|
4684
4675
|
const data = await apiFetch('/api/loops?dir=' + enc(DIR));
|
|
4685
|
-
|
|
4686
|
-
|
|
4687
|
-
|
|
4676
|
+
let loops = Array.isArray(data) ? data : (data.loops || []);
|
|
4677
|
+
// Dedup: if real _repeat.md loops exist, hide scheduled_tasks_lock noise
|
|
4678
|
+
const hasRepeatLoops = loops.some(l => l.source !== 'scheduled_tasks_lock' && l.source !== 'schedule_wakeup_hook');
|
|
4679
|
+
if (hasRepeatLoops) loops = loops.filter(l => l.source !== 'scheduled_tasks_lock' && l.source !== 'schedule_wakeup_hook');
|
|
4680
|
+
document.getElementById('bdg-loops').textContent = loops.length || '—';
|
|
4688
4681
|
if (!loops.length) {
|
|
4689
|
-
el.innerHTML = '<div class="empty"><div class="empty-ico">↺</div><div>No loops scheduled</div><div style="font-size:11px;color:var(--text-xs);margin-top:
|
|
4682
|
+
el.innerHTML = '<div class="empty"><div class="empty-ico">↺</div><div>No loops scheduled</div><div style="font-size:11px;color:var(--text-xs);margin-top:6px">Create one with + New Loop above or via <code>npx monomind autodev --tillend</code></div></div>';
|
|
4690
4683
|
return;
|
|
4691
4684
|
}
|
|
4692
|
-
|
|
4693
|
-
|
|
4694
|
-
|
|
4695
|
-
|
|
4696
|
-
|
|
4697
|
-
if (l.source !== 'schedule_wakeup_hook') return true;
|
|
4698
|
-
const m = (l.prompt || '').match(/--loop\s+\S+\s+(.+)$/s);
|
|
4699
|
-
if (!m) return true;
|
|
4700
|
-
return !repeatPrompts.has(m[1].trim());
|
|
4701
|
-
});
|
|
4702
|
-
el.innerHTML = dedupedLoops.map((l, idx) => {
|
|
4703
|
-
const isHil = l.status === 'hil:pending';
|
|
4704
|
-
const isTillend = l.type === 'tillend';
|
|
4705
|
-
const curRep = l.currentRep || 0;
|
|
4706
|
-
const maxReps = l.maxReps || 0;
|
|
4707
|
-
const nextAt = l.nextRunAt ? parseInt(l.nextRunAt) : 0;
|
|
4708
|
-
// Loops with status 'running'/'waiting'/'active' are explicitly active.
|
|
4709
|
-
// Don't mark them overdue unless nextRunAt is >2h stale (loop died without cleanup).
|
|
4685
|
+
el.innerHTML = loops.map((l, idx) => {
|
|
4686
|
+
const maxReps = l.maxReps || 0;
|
|
4687
|
+
const curRep = l.currentRep || 0;
|
|
4688
|
+
const isTillend = !maxReps || l.loopType === 'tillend' || String(l.command || '').includes('--tillend');
|
|
4689
|
+
const nextAt = l.nextRunAt ? parseInt(l.nextRunAt, 10) : 0;
|
|
4710
4690
|
const isExplicitlyActive = l.status === 'running' || l.status === 'waiting' || l.status === 'active';
|
|
4711
|
-
const
|
|
4712
|
-
const
|
|
4713
|
-
|
|
4714
|
-
|
|
4715
|
-
|
|
4716
|
-
const
|
|
4717
|
-
|
|
4718
|
-
|
|
4719
|
-
|
|
4720
|
-
const
|
|
4721
|
-
const
|
|
4722
|
-
|
|
4723
|
-
const
|
|
4724
|
-
|
|
4725
|
-
|
|
4726
|
-
|
|
4727
|
-
|
|
4728
|
-
|
|
4729
|
-
|
|
4730
|
-
|
|
4731
|
-
|
|
4732
|
-
}
|
|
4733
|
-
const full = _l.prompt || '';
|
|
4734
|
-
const cmdM = full.match(/^(\/\S+)/);
|
|
4735
|
-
if (!cmdM) return { userPrompt: full, command: '', flagsStr: '' };
|
|
4736
|
-
const tokens = full.slice(cmdM[1].length).trim().split(/\s+/);
|
|
4737
|
-
let ti = 0, fp = [];
|
|
4738
|
-
while (ti < tokens.length && tokens[ti] && tokens[ti].startsWith('--')) {
|
|
4739
|
-
fp.push(tokens[ti++]);
|
|
4740
|
-
if (ti < tokens.length && tokens[ti] && !tokens[ti].startsWith('--')) fp.push(tokens[ti++]);
|
|
4741
|
-
}
|
|
4742
|
-
return { userPrompt: tokens.slice(ti).join(' '), command: cmdM[1], flagsStr: fp.join(' ') };
|
|
4743
|
-
})(l);
|
|
4744
|
-
const userPrompt = _lp.userPrompt;
|
|
4745
|
-
const cmdStr = _lp.command;
|
|
4746
|
-
const flagsStr = _lp.flagsStr;
|
|
4747
|
-
const fullPrompt = l.prompt || '';
|
|
4748
|
-
const name = (l.name || userPrompt || cmdStr || 'loop').slice(0, 60);
|
|
4749
|
-
const startedAt = l.startedAt ? new Date(l.startedAt).toLocaleString() : '—';
|
|
4750
|
-
const lastRun = l.lastRunAt ? relTime(l.lastRunAt) : (l.startedAt ? relTime(l.startedAt) : '—');
|
|
4751
|
-
const pct = (!isTillend && maxReps > 0) ? Math.min(100, Math.round(curRep / maxReps * 100)) : 0;
|
|
4752
|
-
const progBar = (!isTillend && maxReps > 0 && running)
|
|
4753
|
-
? `<div class="lp-bar"><div class="lp-fill" style="width:${pct}%"></div></div>`
|
|
4754
|
-
: '';
|
|
4755
|
-
const runCountDisplay = isTillend
|
|
4756
|
-
? `run ${curRep} / ∞${maxReps > 0 ? ' (cap: ' + maxReps + ')' : ''}`
|
|
4757
|
-
: (maxReps > 0 ? `${curRep} / ${maxReps}` : String(curRep || '—'));
|
|
4758
|
-
const cdownSpan = nextAt
|
|
4759
|
-
? ` <span class="loop-cdown${nextAt - Date.now() <= 0 ? ' overdue' : ''}" data-nextat="${nextAt}">${fmtCountdown(nextAt)}</span>`
|
|
4691
|
+
const isOverdue = !isExplicitlyActive && nextAt > 0 && nextAt <= Date.now();
|
|
4692
|
+
const isStaledActive = isExplicitlyActive && nextAt > 0 && (Date.now() - nextAt) > LOOP_STALE_MS;
|
|
4693
|
+
const isFinished = (maxReps > 0 && curRep >= maxReps)
|
|
4694
|
+
|| ['finished','done','complete','completed','expired'].includes(l.status)
|
|
4695
|
+
|| isOverdue || isStaledActive;
|
|
4696
|
+
const isHil = l.status === 'hil:pending';
|
|
4697
|
+
const running = !isFinished && !isHil && l.status !== 'stopped' && l.status !== 'paused';
|
|
4698
|
+
const name = l.name || (l.prompt || 'loop').split('--')[0].trim().slice(0, 60);
|
|
4699
|
+
const interval = fmtInterval(l.interval || l.schedule || '');
|
|
4700
|
+
const fullPrompt = l.prompt || '';
|
|
4701
|
+
const command = l.command || '';
|
|
4702
|
+
const startedAt = l.startedAt ? new Date(l.startedAt).toLocaleString() : '—';
|
|
4703
|
+
const lastRun = l.lastRunAt ? relTime(l.lastRunAt) : (l.startedAt ? relTime(l.startedAt) : '—');
|
|
4704
|
+
const runs = curRep != null ? curRep : '—';
|
|
4705
|
+
const pct = (maxReps > 0) ? Math.min(100, Math.round(curRep / maxReps * 100)) : 0;
|
|
4706
|
+
const progBar = isTillend
|
|
4707
|
+
? `<div class="lp-bar" title="tillend loop"><div class="lp-fill lp-fill-inf" style="width:100%;opacity:0.3;background:linear-gradient(90deg,var(--accent),transparent)"></div><span style="position:absolute;left:6px;top:0;font-size:9px;color:var(--text-lo)">run ${curRep} / ∞${l.capReps ? ' (cap: '+l.capReps+')' : ''}</span></div>`
|
|
4708
|
+
: (maxReps > 0 && running)
|
|
4709
|
+
? `<div class="lp-bar"><div class="lp-fill" style="width:${pct}%"></div></div>`
|
|
4710
|
+
: '';
|
|
4711
|
+
const cdownSpan = (running && nextAt)
|
|
4712
|
+
? ` <span class="loop-cdown" data-nextat="${nextAt}">${fmtCountdown(nextAt)}</span>`
|
|
4760
4713
|
: '';
|
|
4761
|
-
const stopBtn = running
|
|
4762
|
-
? `<button class="loop-stop-btn" data-loop-id="${esc(l.id || l.name || String(idx))}" onclick="stopLoop(event, this.dataset.loopId)"
|
|
4714
|
+
const stopBtn = (running || isHil)
|
|
4715
|
+
? `<button class="loop-stop-btn" data-loop-id="${esc(l.id || l.name || String(idx))}" onclick="stopLoop(event, this.dataset.loopId)">■ Stop</button>`
|
|
4763
4716
|
: '';
|
|
4764
|
-
const typeBadge =
|
|
4765
|
-
|
|
4766
|
-
|
|
4717
|
+
const typeBadge = isTillend
|
|
4718
|
+
? `<span class="loop-type-badge" title="till-end loop">∞</span>`
|
|
4719
|
+
: `<span class="loop-type-badge rep" title="repeat loop">↺</span>`;
|
|
4767
4720
|
const hilBanner = isHil
|
|
4768
|
-
? `<div class="loop-hil-banner">⚠
|
|
4721
|
+
? `<div class="loop-hil-banner">⚠ Human-in-the-loop confirmation required — check session for approval prompt</div>`
|
|
4769
4722
|
: '';
|
|
4770
|
-
const
|
|
4771
|
-
|
|
4772
|
-
|
|
4723
|
+
const statusLabel = isFinished ? 'done' : isHil ? 'HIL' : running ? 'active' : 'stopped';
|
|
4724
|
+
const statusCls = isFinished ? 'done' : isHil ? 'hil' : running ? 'active' : 'stopped';
|
|
4725
|
+
return `<div class="loop-row${isHil ? ' hil' : ''}" data-loop-status="${esc(l.status || '')}" onclick="toggleLoop(this)">
|
|
4726
|
+
<div class="loop-ico">${typeBadge}</div>
|
|
4773
4727
|
<div class="loop-body">
|
|
4774
|
-
<div class="loop-name"
|
|
4775
|
-
<div class="loop-meta">${esc(
|
|
4776
|
-
${hilBanner}
|
|
4728
|
+
<div class="loop-name">${esc(name)}</div>
|
|
4729
|
+
<div class="loop-meta">${interval !== '—' ? interval + ' · ' : ''}${esc([l.description].filter(Boolean).join('').slice(0, 80))}${cdownSpan}</div>
|
|
4777
4730
|
${progBar}
|
|
4731
|
+
${hilBanner}
|
|
4778
4732
|
</div>
|
|
4779
|
-
<div class="loop-status ${
|
|
4733
|
+
<div class="loop-status ${statusCls}">${statusLabel}${isHil ? ' ⚠' : ''}</div>
|
|
4780
4734
|
${stopBtn}
|
|
4781
4735
|
</div>
|
|
4782
4736
|
<div class="loop-expand">
|
|
4783
|
-
${
|
|
4784
|
-
${
|
|
4785
|
-
|
|
4786
|
-
<div class="le-row"><div class="le-lbl">
|
|
4787
|
-
<div class="le-row"><div class="le-lbl">
|
|
4788
|
-
<div class="le-row"><div class="le-lbl">Status</div><div class="le-val">${isHil ? '⚠ hil:pending' : (running ? '● running' : (isFinished ? '✓ done' : '○ stopped'))}</div></div>
|
|
4789
|
-
${isHil && l.id ? `<div class="le-row"><div class="le-lbl">HIL file</div><div class="le-val mono" style="color:oklch(75% 0.16 60);word-break:break-all">.monomind/loops/${esc(l.id)}-hil.md</div></div>` : ''}
|
|
4737
|
+
${fullPrompt ? `<div class="le-row"><div class="le-lbl">Prompt</div><div class="le-val mono" style="cursor:pointer" onclick="navigator.clipboard.writeText(${JSON.stringify(fullPrompt)}).then(()=>showToast('Copied','','ok'))">${esc(fullPrompt.slice(0, 300))}</div></div>` : ''}
|
|
4738
|
+
${command && command !== fullPrompt ? `<div class="le-row"><div class="le-lbl">Command</div><div class="le-val mono" style="cursor:pointer" onclick="navigator.clipboard.writeText(${JSON.stringify(command)}).then(()=>showToast('Copied','','ok'))">${esc(command.slice(0, 200))}</div></div>` : ''}
|
|
4739
|
+
<div class="le-row"><div class="le-lbl">Type</div><div class="le-val">${isTillend ? '∞ tillend' : '↺ repeat'}</div></div>
|
|
4740
|
+
<div class="le-row"><div class="le-lbl">Interval</div><div class="le-val">${interval}</div></div>
|
|
4741
|
+
<div class="le-row"><div class="le-lbl">Status</div><div class="le-val">${isFinished ? '✓ done' : isHil ? '⚠ hil:pending' : running ? '● running' : '○ stopped'}</div></div>
|
|
4790
4742
|
<div class="le-row"><div class="le-lbl">Started</div><div class="le-val mono">${esc(startedAt)}</div></div>
|
|
4791
|
-
|
|
4792
|
-
<div class="le-row"><div class="le-lbl">
|
|
4793
|
-
<div class="le-row"><div class="le-lbl">${isTillend ? 'Progress' : 'Run count'}</div><div class="le-val">${esc(runCountDisplay)}</div></div>
|
|
4794
|
-
${l.source ? `<div class="le-row"><div class="le-lbl">Source</div><div class="le-val">${esc(l.source)}</div></div>` : ''}
|
|
4743
|
+
<div class="le-row"><div class="le-lbl">Last run</div><div class="le-val">${esc(lastRun)}</div></div>
|
|
4744
|
+
<div class="le-row"><div class="le-lbl">Run count</div><div class="le-val">${esc(String(runs))}${maxReps ? ' / ' + maxReps : isTillend ? ' / ∞' : ''}</div></div>
|
|
4795
4745
|
${buildLoopSparkline(l)}
|
|
4796
4746
|
</div>`;
|
|
4797
4747
|
}).join('');
|
|
@@ -4831,25 +4781,27 @@ async function stopLoop(evt, id) {
|
|
|
4831
4781
|
const btn = evt.currentTarget;
|
|
4832
4782
|
if (!btn.dataset.confirming) {
|
|
4833
4783
|
btn.dataset.confirming = '1';
|
|
4784
|
+
const orig = btn.textContent;
|
|
4834
4785
|
btn.textContent = '■ Confirm?';
|
|
4835
|
-
btn.style.cssText = 'background:oklch(55% 0.2 25 / 0.25);color:oklch(72% 0.2 25);border-color:oklch(55% 0.2 25 / 0.4)';
|
|
4836
4786
|
btn._resetTimer = setTimeout(() => {
|
|
4837
4787
|
delete btn.dataset.confirming;
|
|
4838
|
-
btn.textContent =
|
|
4839
|
-
btn.style.cssText = '';
|
|
4788
|
+
btn.textContent = orig;
|
|
4840
4789
|
}, 3000);
|
|
4841
4790
|
return;
|
|
4842
4791
|
}
|
|
4843
4792
|
clearTimeout(btn._resetTimer);
|
|
4844
4793
|
delete btn.dataset.confirming;
|
|
4845
4794
|
try {
|
|
4846
|
-
await fetch('/api/loops/stop?dir=' + enc(DIR), {
|
|
4795
|
+
const r = await fetch('/api/loops/stop?dir=' + enc(DIR), {
|
|
4847
4796
|
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
4848
|
-
body: JSON.stringify({ id })
|
|
4797
|
+
body: JSON.stringify({ id }),
|
|
4849
4798
|
});
|
|
4850
|
-
|
|
4851
|
-
|
|
4852
|
-
|
|
4799
|
+
if (!r.ok) throw new Error('HTTP ' + r.status);
|
|
4800
|
+
showToast('Stopped', 'Loop stop requested', 'ok');
|
|
4801
|
+
setTimeout(() => renderLoops(), 400);
|
|
4802
|
+
} catch (e) {
|
|
4803
|
+
showToast('Error', e.message, 'err');
|
|
4804
|
+
}
|
|
4853
4805
|
}
|
|
4854
4806
|
|
|
4855
4807
|
let _cdownInterval = null;
|
|
@@ -4859,32 +4811,8 @@ function startCountdowns() {
|
|
|
4859
4811
|
_cdownInterval = setInterval(() => {
|
|
4860
4812
|
document.querySelectorAll('.loop-cdown[data-nextat]').forEach(el => {
|
|
4861
4813
|
const ms = parseInt(el.dataset.nextat) - Date.now();
|
|
4862
|
-
if (ms <= 0) {
|
|
4863
|
-
const row = el.closest('.loop-row');
|
|
4864
|
-
const loopStatus = row ? (row.dataset.loopStatus || '') : '';
|
|
4865
|
-
const isActiveStatus = loopStatus === 'running' || loopStatus === 'waiting' || loopStatus === 'active';
|
|
4866
|
-
if (isActiveStatus) {
|
|
4867
|
-
// Loop is between rounds or executing — not done, just waiting for next update
|
|
4868
|
-
el.textContent = 'executing…';
|
|
4869
|
-
el.classList.remove('overdue');
|
|
4870
|
-
} else {
|
|
4871
|
-
el.textContent = 'overdue';
|
|
4872
|
-
el.classList.add('overdue');
|
|
4873
|
-
if (row) {
|
|
4874
|
-
const badge = row.querySelector('.loop-status');
|
|
4875
|
-
if (badge && badge.classList.contains('active')) {
|
|
4876
|
-
badge.classList.remove('active');
|
|
4877
|
-
badge.classList.add('stopped');
|
|
4878
|
-
badge.textContent = 'done';
|
|
4879
|
-
}
|
|
4880
|
-
const stopBtn = row.querySelector('.loop-stop-btn');
|
|
4881
|
-
if (stopBtn) stopBtn.remove();
|
|
4882
|
-
}
|
|
4883
|
-
}
|
|
4884
|
-
return;
|
|
4885
|
-
}
|
|
4886
4814
|
el.textContent = fmtCountdown(el.dataset.nextat);
|
|
4887
|
-
el.classList.
|
|
4815
|
+
el.classList.toggle('overdue', ms <= 0);
|
|
4888
4816
|
});
|
|
4889
4817
|
}, 1000);
|
|
4890
4818
|
}
|
|
@@ -4936,7 +4864,7 @@ function buildEfficiencyPanel() {
|
|
|
4936
4864
|
<span class="eff-lbl" title="${esc(s.lastPrompt||s.id)}">${esc(lbl)}</span>
|
|
4937
4865
|
<span class="eff-pct ${cls}">${pct}%</span>
|
|
4938
4866
|
</div>
|
|
4939
|
-
<div class="eff-bar-wrap"
|
|
4867
|
+
<div class="eff-bar-wrap"><div class="eff-bar-fill" style="width:${pct}%;background:${fillColor}"></div></div>`;
|
|
4940
4868
|
}).join('');
|
|
4941
4869
|
el.innerHTML = `<div class="m-group-title">Cache Efficiency <span class="${avgCls}" style="font-size:10px;font-weight:400">${avgPct}% avg</span></div>${rows}`;
|
|
4942
4870
|
}
|
|
@@ -4973,10 +4901,10 @@ function renderModelMix() {
|
|
|
4973
4901
|
const short = model.replace(/^claude-/,'').replace(/-\d{8}$/,'');
|
|
4974
4902
|
const pct = totalCost > 0 ? Math.round(d.cost/totalCost*100) : 0;
|
|
4975
4903
|
return `<tr>
|
|
4976
|
-
<td style="font-size:11px"
|
|
4904
|
+
<td style="font-size:11px">${esc(short)}</td>
|
|
4977
4905
|
<td class="lb-cost">$${d.cost.toFixed(2)}</td>
|
|
4978
4906
|
<td class="lb-dur">${pct}%</td>
|
|
4979
|
-
<td class="lb-dur">${d.calls
|
|
4907
|
+
<td class="lb-dur">${d.calls}</td>
|
|
4980
4908
|
</tr>`;
|
|
4981
4909
|
}).join('')}
|
|
4982
4910
|
</tbody></table>`;
|
|
@@ -4995,14 +4923,14 @@ function buildWeeklyRecap() {
|
|
|
4995
4923
|
const weekStart = new Date(); weekStart.setDate(weekStart.getDate() - weekStart.getDay()); weekStart.setHours(0,0,0,0);
|
|
4996
4924
|
const weekSess = allSessions.filter(s => {
|
|
4997
4925
|
const t = s.lastTs || s.mtime;
|
|
4998
|
-
return t && new Date(typeof t === 'number' ? t :
|
|
4926
|
+
return t && new Date(typeof t === 'number' ? t : t).getTime() >= weekStart.getTime();
|
|
4999
4927
|
});
|
|
5000
4928
|
if (!weekSess.length) return;
|
|
5001
4929
|
const totalCost = weekSess.reduce((a,s) => a + (s.totalCost||0), 0);
|
|
5002
4930
|
const totalTools = weekSess.reduce((a,s) => a + (s.toolCalls||0), 0);
|
|
5003
4931
|
const days = new Set(weekSess.map(s => {
|
|
5004
4932
|
const t = s.lastTs || s.mtime;
|
|
5005
|
-
return new Date(typeof t === 'number' ? t :
|
|
4933
|
+
return new Date(typeof t === 'number' ? t : t).toDateString();
|
|
5006
4934
|
})).size;
|
|
5007
4935
|
const longestMs = Math.max(...weekSess.map(s => s.totalDurationMs||0));
|
|
5008
4936
|
const streak = calcStreak();
|
|
@@ -5070,12 +4998,11 @@ function buildActivityHeatmap() {
|
|
|
5070
4998
|
for (const s of allSessions) {
|
|
5071
4999
|
const t = s.firstTs || s.mtime;
|
|
5072
5000
|
if (!t) continue;
|
|
5073
|
-
const d = new Date(typeof t === 'number' ? t :
|
|
5001
|
+
const d = new Date(typeof t === 'number' ? t : t);
|
|
5074
5002
|
grid[d.getDay()][d.getHours()]++;
|
|
5075
5003
|
}
|
|
5076
5004
|
const maxVal = Math.max(1, ...grid.flat());
|
|
5077
5005
|
const DAYS = ['Su','Mo','Tu','We','Th','Fr','Sa'];
|
|
5078
|
-
const FULL_DAYS = ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'];
|
|
5079
5006
|
let html = '<div class="heatmap-grid">';
|
|
5080
5007
|
html += '<div class="heatmap-row"><div class="heatmap-lbl"></div>';
|
|
5081
5008
|
for (let h = 0; h < 24; h++) {
|
|
@@ -5088,7 +5015,7 @@ function buildActivityHeatmap() {
|
|
|
5088
5015
|
const v = grid[d][h];
|
|
5089
5016
|
const alpha = v > 0 ? Math.max(0.18, v/maxVal).toFixed(2) : 0;
|
|
5090
5017
|
const bg = v > 0 ? `oklch(65% 0.18 200 / ${alpha})` : 'transparent';
|
|
5091
|
-
html += `<div class="heatmap-cell" style="background:${bg};border:1px solid ${v>0?'transparent':'var(--border)'}" title="${
|
|
5018
|
+
html += `<div class="heatmap-cell" style="background:${bg};border:1px solid ${v>0?'transparent':'var(--border)'}" title="${DAYS[d]} ${h}:00 — ${v} session${v!==1?'s':''}"></div>`;
|
|
5092
5019
|
}
|
|
5093
5020
|
html += '</div>';
|
|
5094
5021
|
}
|
|
@@ -5170,8 +5097,8 @@ function v2RenderOrgList() {
|
|
|
5170
5097
|
return `<div class="org-item${active}" data-org="${esc(o.name)}" onclick="v2SelectOrg(this.dataset.org)">
|
|
5171
5098
|
<div class="oi-dot ${o.running ? 'running' : ''}"></div>
|
|
5172
5099
|
<div class="oi-body">
|
|
5173
|
-
<div class="oi-name"
|
|
5174
|
-
${goalSnip ? `<div class="oi-goal"
|
|
5100
|
+
<div class="oi-name">${esc(o.name)}</div>
|
|
5101
|
+
${goalSnip ? `<div class="oi-goal">${esc(goalSnip)}</div>` : ''}
|
|
5175
5102
|
<div class="oi-chips">
|
|
5176
5103
|
${o.running ? '<span class="oi-chip live">LIVE</span>' : ''}
|
|
5177
5104
|
<span class="oi-chip">${rolesN} roles</span>
|
|
@@ -5637,7 +5564,7 @@ function v2RenderOrgRoles() {
|
|
|
5637
5564
|
const pane = document.getElementById('odt-roles');
|
|
5638
5565
|
if (!pane) return;
|
|
5639
5566
|
if (!roles.length) {
|
|
5640
|
-
pane.innerHTML = '<div class="empty">No roles defined
|
|
5567
|
+
pane.innerHTML = '<div class="empty">No roles defined</div>';
|
|
5641
5568
|
return;
|
|
5642
5569
|
}
|
|
5643
5570
|
// Determine leader: explicit reports_to=undefined + type=planner/coordinator, or first role, or id=boss
|
|
@@ -5709,7 +5636,7 @@ function v2RenderAgentDrawer(data) {
|
|
|
5709
5636
|
headEl.innerHTML = `
|
|
5710
5637
|
<img class="oad-avatar" src="${avatar}" alt="${esc(name)}" onerror="this.src='data/avatars/coder.svg'"/>
|
|
5711
5638
|
<div class="oad-id">
|
|
5712
|
-
<div class="oad-name"
|
|
5639
|
+
<div class="oad-name">${esc(name)}</div>
|
|
5713
5640
|
<div class="oad-pills">
|
|
5714
5641
|
${type ? `<span class="oad-pill">${esc(type)}</span>` : ''}
|
|
5715
5642
|
${model ? `<span class="oad-pill model">${esc(model)}</span>` : ''}
|
|
@@ -5787,7 +5714,7 @@ function v2RenderOrgActivity() {
|
|
|
5787
5714
|
if (!_v2SelOrg) return;
|
|
5788
5715
|
const activity = _v2OrgData?._activity || [];
|
|
5789
5716
|
const orgEvents = _v2OrgEventLog[_v2SelOrg] || [];
|
|
5790
|
-
const events = [...activity, ...orgEvents].sort((a,b) => (
|
|
5717
|
+
const events = [...activity, ...orgEvents].sort((a,b) => (b.ts||0)-(a.ts||0)).slice(0,80);
|
|
5791
5718
|
const pane = document.getElementById('odt-activity');
|
|
5792
5719
|
if (!pane) return;
|
|
5793
5720
|
const fmtOrgEvType = t => {
|
|
@@ -5795,14 +5722,13 @@ function v2RenderOrgActivity() {
|
|
|
5795
5722
|
return m[t]||(t||'').replace(/^org:/,'');
|
|
5796
5723
|
};
|
|
5797
5724
|
if (!events.length) {
|
|
5798
|
-
pane.innerHTML = '<div class="empty">No activity recorded
|
|
5725
|
+
pane.innerHTML = '<div class="empty">No activity recorded</div>';
|
|
5799
5726
|
return;
|
|
5800
5727
|
}
|
|
5801
5728
|
pane.innerHTML = `<div class="act-v2-list">${events.map(ev => {
|
|
5802
|
-
const t = ev.ts ? new Date(
|
|
5729
|
+
const t = ev.ts ? new Date(ev.ts).toLocaleTimeString('en',{hour12:false,hour:'2-digit',minute:'2-digit',second:'2-digit'}) : '';
|
|
5803
5730
|
const detail = ev.role||ev.msg||ev.agent||'';
|
|
5804
|
-
|
|
5805
|
-
return `<div class="av2-row"><span class="av2-time" title="${esc(tFull)}">${esc(t)}</span><span class="av2-type">${esc(fmtOrgEvType(ev.type))}</span><span class="av2-msg">${esc(detail)}</span></div>`;
|
|
5731
|
+
return `<div class="av2-row"><span class="av2-time">${esc(t)}</span><span class="av2-type">${esc(fmtOrgEvType(ev.type))}</span><span class="av2-msg">${esc(detail)}</span></div>`;
|
|
5806
5732
|
}).join('')}</div>`;
|
|
5807
5733
|
}
|
|
5808
5734
|
|
|
@@ -5840,12 +5766,12 @@ function v2RenderOrgHeartbeats() {
|
|
|
5840
5766
|
status: a.status || 'idle',
|
|
5841
5767
|
}));
|
|
5842
5768
|
}
|
|
5843
|
-
if (!hb.length) { pane.innerHTML = '<div class="empty">No agents to report
|
|
5769
|
+
if (!hb.length) { pane.innerHTML = '<div class="empty">No agents to report</div>'; return; }
|
|
5844
5770
|
const cls = (st) => (st === 'active' || st === 'running') ? 'on' : ((st === 'error' || st === 'failed') ? 'warn' : '');
|
|
5845
5771
|
pane.innerHTML = `<div class="m-group-title">Agent Heartbeats</div>` +
|
|
5846
5772
|
hb.slice(0, 50).map(h => `<div style="display:flex;justify-content:space-between;align-items:center;gap:10px;padding:6px 0;border-bottom:1px solid var(--border);font-size:12px">
|
|
5847
|
-
<span style="color:var(--text-hi);font-family:var(--mono);flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap"
|
|
5848
|
-
<span style="color:var(--text-lo);white-space:nowrap"
|
|
5773
|
+
<span style="color:var(--text-hi);font-family:var(--mono);flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${esc(h.agent || '—')}</span>
|
|
5774
|
+
<span style="color:var(--text-lo);white-space:nowrap">${h.ts ? relTime(h.ts) : 'never'}</span>
|
|
5849
5775
|
<span class="ss-pill ${cls(h.status)}">${esc(String(h.status || 'idle').toUpperCase())}</span>
|
|
5850
5776
|
</div>`).join('');
|
|
5851
5777
|
}
|
|
@@ -5864,15 +5790,15 @@ function v2RenderOrgTasks() {
|
|
|
5864
5790
|
(Array.isArray(items) ? items : []).forEach(t => tasks.push({ ...t, status: t.status || col }));
|
|
5865
5791
|
}
|
|
5866
5792
|
}
|
|
5867
|
-
if (!tasks.length) { pane.innerHTML = '<div class="empty">No tasks
|
|
5793
|
+
if (!tasks.length) { pane.innerHTML = '<div class="empty">No tasks</div>'; return; }
|
|
5868
5794
|
const rank = { running: 0, doing: 0, todo: 1, pending: 1, done: 2 };
|
|
5869
5795
|
tasks.sort((a, b) => (rank[a.status] ?? 1) - (rank[b.status] ?? 1));
|
|
5870
5796
|
const pill = (st) => st === 'done' ? 'on' : (st === 'running' || st === 'doing' ? 'warn' : '');
|
|
5871
5797
|
pane.innerHTML = `<div class="m-group-title">Tasks (${tasks.length})</div>` +
|
|
5872
5798
|
tasks.slice(0, 80).map(t => `<div style="display:flex;gap:10px;align-items:baseline;padding:5px 0;border-bottom:1px solid var(--border);font-size:12px">
|
|
5873
5799
|
<span class="ss-pill ${pill(t.status)}">${esc(t.status || '?')}</span>
|
|
5874
|
-
<span style="flex:1;color:var(--text-hi);font-family:var(--mono);font-size:11px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap"
|
|
5875
|
-
<span style="color:var(--text-lo);white-space:nowrap"
|
|
5800
|
+
<span style="flex:1;color:var(--text-hi);font-family:var(--mono);font-size:11px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${esc(t.description || t.title || t.id || '—')}</span>
|
|
5801
|
+
<span style="color:var(--text-lo);white-space:nowrap">${relTime(t.ts || t.created_at || t.updated_at)}</span>
|
|
5876
5802
|
</div>`).join('');
|
|
5877
5803
|
}
|
|
5878
5804
|
|
|
@@ -5896,7 +5822,7 @@ function v2RenderOrgCosts() {
|
|
|
5896
5822
|
? c.map(r => ({ label: r.label ?? r.name ?? '—', cost: Number(r.value ?? r.cost ?? 0), tin: 0, tout: 0 }))
|
|
5897
5823
|
: Object.entries(c).map(([k, v]) => ({ label: k, cost: Number(v) || 0, tin: 0, tout: 0 }));
|
|
5898
5824
|
}
|
|
5899
|
-
if (!rows.length) { pane.innerHTML = '<div class="empty">No cost data
|
|
5825
|
+
if (!rows.length) { pane.innerHTML = '<div class="empty">No cost data</div>'; return; }
|
|
5900
5826
|
const cur = (b && b.currency) || 'USD';
|
|
5901
5827
|
const period = (b && b.period) || '';
|
|
5902
5828
|
const total = rows.reduce((s, r) => s + r.cost, 0);
|
|
@@ -5908,7 +5834,7 @@ function v2RenderOrgCosts() {
|
|
|
5908
5834
|
<span style="color:var(--accent);font-family:var(--mono);font-size:15px;font-weight:600">$${total.toFixed(4)}</span>
|
|
5909
5835
|
</div>` +
|
|
5910
5836
|
rows.map(r => `<div style="display:flex;justify-content:space-between;align-items:baseline;gap:10px;padding:6px 0;border-bottom:1px solid var(--border);font-size:12px">
|
|
5911
|
-
<span style="color:var(--text-hi);flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap"
|
|
5837
|
+
<span style="color:var(--text-hi);flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${esc(String(r.label))}</span>
|
|
5912
5838
|
<span style="color:var(--text-lo);font-family:var(--mono);font-size:10px;white-space:nowrap">${(r.tin + r.tout).toLocaleString()} tok</span>
|
|
5913
5839
|
<span style="color:var(--accent);font-family:var(--mono);white-space:nowrap">$${r.cost.toFixed(4)}</span>
|
|
5914
5840
|
</div>`).join('') +
|
|
@@ -5925,14 +5851,14 @@ function v2RenderOrgMembers() {
|
|
|
5925
5851
|
const roles = Array.isArray(_v2OrgData?.roles) ? _v2OrgData.roles : [];
|
|
5926
5852
|
const list = members.length ? members : roles;
|
|
5927
5853
|
const joinReqs = Array.isArray(_v2OrgData?._joinRequests) ? _v2OrgData._joinRequests : [];
|
|
5928
|
-
if (!list.length) { pane.innerHTML = '<div class="empty">No members
|
|
5854
|
+
if (!list.length) { pane.innerHTML = '<div class="empty">No members</div>'; return; }
|
|
5929
5855
|
const src = members.length ? 'joined members' : 'defined roles';
|
|
5930
5856
|
const active = (r) => r.running || r.active || r.status === 'active';
|
|
5931
5857
|
pane.innerHTML = `<div class="m-group-title">Members (${list.length}) · ${src}</div>` +
|
|
5932
5858
|
list.map(r => `<div style="display:flex;gap:10px;align-items:center;padding:6px 0;border-bottom:1px solid var(--border);font-size:12px">
|
|
5933
5859
|
<span style="width:28px;height:28px;border-radius:50%;background:var(--surface-hi);display:inline-flex;align-items:center;justify-content:center;font-size:14px;flex-shrink:0">◈</span>
|
|
5934
5860
|
<div style="flex:1;min-width:0">
|
|
5935
|
-
<div style="color:var(--text-hi);font-family:var(--mono);font-size:12px"
|
|
5861
|
+
<div style="color:var(--text-hi);font-family:var(--mono);font-size:12px">${esc(r.name || r.id || r.user || '—')}</div>
|
|
5936
5862
|
<div style="color:var(--text-lo);font-size:10px">${esc(r.title || r.type || r.role || '')}</div>
|
|
5937
5863
|
</div>
|
|
5938
5864
|
<span class="ss-pill ${active(r) ? 'on' : ''}">${active(r) ? 'ACTIVE' : 'IDLE'}</span>
|
|
@@ -5946,7 +5872,7 @@ function v2RenderOrgGoals() {
|
|
|
5946
5872
|
const el = document.getElementById('odt-goals');
|
|
5947
5873
|
if (!el || !_v2OrgData) return;
|
|
5948
5874
|
const goals = _v2OrgData.goals || _v2OrgData.config?.goals || [];
|
|
5949
|
-
if (!goals.length) { el.innerHTML = '<div class="empty">No goals defined
|
|
5875
|
+
if (!goals.length) { el.innerHTML = '<div class="empty">No goals defined</div>'; return; }
|
|
5950
5876
|
function renderGoal(g, depth) {
|
|
5951
5877
|
if (depth > 20) return ''; // Depth guard to prevent stack overflow
|
|
5952
5878
|
const indent = depth * 20;
|
|
@@ -5970,7 +5896,7 @@ function v2RenderOrgBoard() {
|
|
|
5970
5896
|
const el = document.getElementById('odt-board');
|
|
5971
5897
|
if (!el || !_v2OrgData) return;
|
|
5972
5898
|
const issues = _v2OrgData._issues || [];
|
|
5973
|
-
if (!issues.length) { el.innerHTML = '<div class="empty">No issues
|
|
5899
|
+
if (!issues.length) { el.innerHTML = '<div class="empty">No issues</div>'; return; }
|
|
5974
5900
|
const cols = ['open', 'in_progress', 'blocked', 'done', 'cancelled'];
|
|
5975
5901
|
const PRIORITY = { urgent: '🔴', high: '🟠', medium: '🟡', low: '🟢' };
|
|
5976
5902
|
el.innerHTML = `<div style="display:flex;gap:10px;overflow-x:auto;padding-bottom:8px">` +
|
|
@@ -5979,10 +5905,9 @@ function v2RenderOrgBoard() {
|
|
|
5979
5905
|
return `<div style="min-width:160px;flex:1">
|
|
5980
5906
|
<div style="font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:0.07em;color:var(--text-lo);padding:6px 0;border-bottom:1px solid var(--border);margin-bottom:8px">${esc(col.replace('_', ' '))} <span style="background:var(--surface-hi);padding:1px 6px;border-radius:8px">${cards.length}</span></div>
|
|
5981
5907
|
${cards.slice(0, 15).map(i => `<div style="background:var(--surface);border:1px solid var(--border);border-radius:6px;padding:7px 9px;margin-bottom:5px;font-size:12px">
|
|
5982
|
-
<div style="color:var(--text-hi)"
|
|
5908
|
+
<div style="color:var(--text-hi)">${PRIORITY[i.priority] || ''} ${esc((i.title || i.description || '—').slice(0, 60))}</div>
|
|
5983
5909
|
${i.assignee ? `<div style="font-size:10px;color:var(--text-lo);margin-top:3px">${esc(i.assignee)}</div>` : ''}
|
|
5984
5910
|
</div>`).join('')}
|
|
5985
|
-
${cards.length > 15 ? `<div style="font-size:11px;color:var(--text-xs);text-align:center;padding:4px 0">+${cards.length - 15} more</div>` : ''}
|
|
5986
5911
|
</div>`;
|
|
5987
5912
|
}).join('') + `</div>`;
|
|
5988
5913
|
}
|
|
@@ -6006,9 +5931,9 @@ function v2RenderOrgLive() {
|
|
|
6006
5931
|
<div class="m-group-title" style="margin-bottom:6px">Activity Feed</div>
|
|
6007
5932
|
<div style="max-height:240px;overflow-y:auto;font-size:11px;font-family:var(--mono)">
|
|
6008
5933
|
${(_v2OrgData._activity || []).slice(-30).reverse().map(e => `<div style="padding:3px 0;border-bottom:1px solid var(--border);color:var(--text-lo)">
|
|
6009
|
-
|
|
5934
|
+
${esc(relTime(e.ts || e.timestamp || e.created_at))}
|
|
6010
5935
|
<span style="color:var(--text-mid);margin-left:6px">${esc(e.type || e.kind || e.event || '—')}</span>
|
|
6011
|
-
${e.message ? `<span style="color:var(--text-hi);margin-left:6px"
|
|
5936
|
+
${e.message ? `<span style="color:var(--text-hi);margin-left:6px">${esc(e.message.toString().slice(0, 80))}</span>` : ''}
|
|
6012
5937
|
</div>`).join('')}
|
|
6013
5938
|
</div>`;
|
|
6014
5939
|
// auto-refresh every 5s while tab is active
|
|
@@ -6033,7 +5958,7 @@ async function v2RenderOrgApprovals() {
|
|
|
6033
5958
|
const _enc = encodeURIComponent(_v2SelOrg);
|
|
6034
5959
|
const data = await fetch(`/api/org/${_enc}/approvals${DIR ? '?dir=' + encodeURIComponent(DIR) : ''}`).then(r => r.ok ? r.json() : []).catch(() => []);
|
|
6035
5960
|
const approvals = Array.isArray(data) ? data : (data.approvals || []);
|
|
6036
|
-
if (!approvals.length) { el.innerHTML = '<div class="empty">No pending approvals
|
|
5961
|
+
if (!approvals.length) { el.innerHTML = '<div class="empty">No pending approvals</div>'; return; }
|
|
6037
5962
|
el.innerHTML = `<table style="width:100%;border-collapse:collapse;font-size:12px">
|
|
6038
5963
|
<thead><tr style="color:var(--text-xs);text-align:left">
|
|
6039
5964
|
<th style="padding:6px 8px">Requester</th><th>Action</th><th>Status</th><th>Date</th><th></th>
|
|
@@ -6044,13 +5969,13 @@ async function v2RenderOrgApprovals() {
|
|
|
6044
5969
|
const aid = a.id || '';
|
|
6045
5970
|
return `<tr style="border-top:1px solid var(--border)">
|
|
6046
5971
|
<td style="padding:6px 8px;color:var(--text-hi)">${esc(a.requester || a.agent || '—')}</td>
|
|
6047
|
-
<td style="padding:6px 8px;color:var(--text-lo);max-width:180px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap"
|
|
5972
|
+
<td style="padding:6px 8px;color:var(--text-lo);max-width:180px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${esc((a.action || a.description || '').slice(0, 80))}</td>
|
|
6048
5973
|
<td style="padding:6px 8px"><span class="ss-pill ${cls}">${esc(a.status || 'pending')}</span></td>
|
|
6049
|
-
<td style="padding:6px 8px;color:var(--text-xs);font-size:11px;font-family:var(--mono)"
|
|
5974
|
+
<td style="padding:6px 8px;color:var(--text-xs);font-size:11px;font-family:var(--mono)">${relTime(a.created_at || a.ts)}</td>
|
|
6050
5975
|
<td style="padding:6px 8px;white-space:nowrap">
|
|
6051
5976
|
${pending
|
|
6052
|
-
? `<button class="btn" style="font-size:10px;color:var(--green);border-color:var(--green)" title="Approve" aria-label="Approve" onclick="orgApprovalAction(${JSON.stringify(aid)
|
|
6053
|
-
<button class="btn" style="font-size:10px;color:var(--red);border-color:var(--red);margin-left:3px" title="Reject" aria-label="Reject" onclick="orgApprovalAction(${JSON.stringify(aid)
|
|
5977
|
+
? `<button class="btn" style="font-size:10px;color:var(--green);border-color:var(--green)" title="Approve" aria-label="Approve" onclick="orgApprovalAction(${JSON.stringify(aid)},'approve')">✓</button>
|
|
5978
|
+
<button class="btn" style="font-size:10px;color:var(--red);border-color:var(--red);margin-left:3px" title="Reject" aria-label="Reject" onclick="orgApprovalAction(${JSON.stringify(aid)},'reject')">✕</button>`
|
|
6054
5979
|
: ''}
|
|
6055
5980
|
</td>
|
|
6056
5981
|
</tr>`;
|
|
@@ -6086,7 +6011,7 @@ async function v2RenderOrgSecrets() {
|
|
|
6086
6011
|
${s.purpose ? `<span style="color:var(--text-lo)">${esc(s.purpose)}</span>` : ''}
|
|
6087
6012
|
<span style="margin-left:auto;font-family:var(--mono);color:var(--border)">••••••••</span>
|
|
6088
6013
|
</div>`).join('')
|
|
6089
|
-
: '<div class="empty">No secrets configured
|
|
6014
|
+
: '<div class="empty">No secrets configured</div>');
|
|
6090
6015
|
} catch (e) { el.innerHTML = '<div class="empty">Failed: ' + esc(e.message) + '</div>'; }
|
|
6091
6016
|
}
|
|
6092
6017
|
|
|
@@ -6103,19 +6028,19 @@ function v2RenderOrgSettings() {
|
|
|
6103
6028
|
el.innerHTML = `
|
|
6104
6029
|
<div style="font-size:11px;color:var(--text-lo);margin-bottom:14px">Changes generate a CLI command — no direct writes from UI.</div>
|
|
6105
6030
|
<div style="display:flex;flex-direction:column;gap:12px;max-width:380px">
|
|
6106
|
-
<div><div class="le-lbl">Goal</div><input id="os-goal" class="filter-input"
|
|
6031
|
+
<div><div class="le-lbl">Goal</div><input id="os-goal" class="filter-input" value="${esc(d.goal || '')}"></div>
|
|
6107
6032
|
<div><div class="le-lbl">Topology</div>
|
|
6108
|
-
<select id="os-topo" class="filter-input"
|
|
6033
|
+
<select id="os-topo" class="filter-input" style="cursor:pointer">
|
|
6109
6034
|
${topos.map(t => `<option ${d.topology === t ? 'selected' : ''}>${esc(t)}</option>`).join('')}
|
|
6110
6035
|
</select>
|
|
6111
6036
|
</div>
|
|
6112
6037
|
<div><div class="le-lbl">Governance</div>
|
|
6113
|
-
<select id="os-gov" class="filter-input"
|
|
6038
|
+
<select id="os-gov" class="filter-input" style="cursor:pointer">
|
|
6114
6039
|
${govs.map(g => `<option ${govVal === g ? 'selected' : ''}>${esc(g)}</option>`).join('')}
|
|
6115
6040
|
</select>
|
|
6116
6041
|
</div>
|
|
6117
|
-
<div><div class="le-lbl">Budget (tokens)</div><input id="os-budget" class="filter-input" type="number"
|
|
6118
|
-
<button class="btn"
|
|
6042
|
+
<div><div class="le-lbl">Budget (tokens)</div><input id="os-budget" class="filter-input" type="number" value="${esc(String(budgetVal || ''))}"></div>
|
|
6043
|
+
<button class="btn" style="width:fit-content;color:var(--accent);border-color:var(--accent)" onclick="generateOrgSettingsCmd()">Generate CLI Command</button>
|
|
6119
6044
|
<div id="os-cmd-out" style="display:none;font-family:var(--mono);font-size:11px;background:var(--bg);border:1px solid var(--border);border-radius:6px;padding:10px;word-break:break-all;cursor:pointer;color:var(--text-hi)" title="Click to copy" onclick="navigator.clipboard.writeText(this.textContent).then(()=>showToast('Copied','','ok'))"></div>
|
|
6120
6045
|
</div>`;
|
|
6121
6046
|
}
|
|
@@ -6145,7 +6070,7 @@ async function v2RenderOrgRoutines() {
|
|
|
6145
6070
|
const _enc = encodeURIComponent(_v2SelOrg);
|
|
6146
6071
|
const data = await fetch(`/api/org/${_enc}/routines${DIR ? '?dir=' + encodeURIComponent(DIR) : ''}`).then(r => r.ok ? r.json() : []).catch(() => []);
|
|
6147
6072
|
const routines = Array.isArray(data) ? data : (data.routines || _v2OrgData?.config?.routines || []);
|
|
6148
|
-
if (!routines.length) { el.innerHTML = '<div class="empty">No routines configured
|
|
6073
|
+
if (!routines.length) { el.innerHTML = '<div class="empty">No routines configured</div>'; return; }
|
|
6149
6074
|
el.innerHTML = `<table style="width:100%;border-collapse:collapse;font-size:12px">
|
|
6150
6075
|
<thead><tr style="color:var(--text-xs);text-align:left">
|
|
6151
6076
|
<th style="padding:5px 8px">Name</th><th>Cron</th><th>Last Run</th><th>Status</th>
|
|
@@ -6153,7 +6078,7 @@ async function v2RenderOrgRoutines() {
|
|
|
6153
6078
|
routines.map(r => `<tr style="border-top:1px solid var(--border)">
|
|
6154
6079
|
<td style="padding:6px 8px;color:var(--text-hi)">${esc(r.name || '—')}</td>
|
|
6155
6080
|
<td style="padding:6px 8px;font-family:var(--mono);color:var(--text-lo)">${esc(r.cron || r.schedule || '—')}</td>
|
|
6156
|
-
<td style="padding:6px 8px;color:var(--text-lo)"
|
|
6081
|
+
<td style="padding:6px 8px;color:var(--text-lo)">${r.lastRun ? relTime(r.lastRun) : '—'}</td>
|
|
6157
6082
|
<td style="padding:6px 8px"><span class="ss-pill ${r.active || r.status === 'active' ? 'on' : ''}">${esc(r.status || '—')}</span></td>
|
|
6158
6083
|
</tr>`).join('') + '</tbody></table>';
|
|
6159
6084
|
} catch (e) { el.innerHTML = '<div class="empty">Failed: ' + esc(e.message) + '</div>'; }
|
|
@@ -6168,7 +6093,7 @@ async function v2RenderOrgMyIssues() {
|
|
|
6168
6093
|
const _enc = encodeURIComponent(_v2SelOrg);
|
|
6169
6094
|
const data = await fetch(`/api/org/${_enc}/my-issues${DIR ? '?dir=' + encodeURIComponent(DIR) : ''}`).then(r => r.ok ? r.json() : []).catch(() => []);
|
|
6170
6095
|
const issues = Array.isArray(data) ? data : (data.issues || []);
|
|
6171
|
-
if (!issues.length) { el.innerHTML = '<div class="empty">No issues assigned to you
|
|
6096
|
+
if (!issues.length) { el.innerHTML = '<div class="empty">No issues assigned to you</div>'; return; }
|
|
6172
6097
|
const PRIORITY = { urgent: '🔴', high: '🟠', medium: '🟡', low: '🟢' };
|
|
6173
6098
|
el.innerHTML = `<table style="width:100%;border-collapse:collapse;font-size:12px">
|
|
6174
6099
|
<thead><tr style="color:var(--text-xs);text-align:left">
|
|
@@ -6178,9 +6103,9 @@ async function v2RenderOrgMyIssues() {
|
|
|
6178
6103
|
const cls = i.status === 'done' ? 'on' : i.status === 'blocked' ? 'warn' : '';
|
|
6179
6104
|
return `<tr style="border-top:1px solid var(--border)">
|
|
6180
6105
|
<td style="padding:5px 4px">${PRIORITY[i.priority] || '·'}</td>
|
|
6181
|
-
<td style="padding:5px 8px;color:var(--text-hi);max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap"
|
|
6106
|
+
<td style="padding:5px 8px;color:var(--text-hi);max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${esc((i.title || i.description || '—').slice(0, 80))}</td>
|
|
6182
6107
|
<td style="padding:5px 8px"><span class="ss-pill ${cls}">${esc(i.status || 'open')}</span></td>
|
|
6183
|
-
<td style="padding:5px 8px;color:var(--text-xs);font-size:11px;font-family:var(--mono)"
|
|
6108
|
+
<td style="padding:5px 8px;color:var(--text-xs);font-size:11px;font-family:var(--mono)">${relTime(i.updated_at || i.ts)}</td>
|
|
6184
6109
|
</tr>`;
|
|
6185
6110
|
}).join('') + '</tbody></table>';
|
|
6186
6111
|
} catch (e) { el.innerHTML = '<div class="empty">Failed: ' + esc(e.message) + '</div>'; }
|
|
@@ -6209,21 +6134,19 @@ function v2RenderOrgBudgets() {
|
|
|
6209
6134
|
html += `<div class="m-group-title">USD Budget</div>${fillBar(b.usd || 0, b.usdLimit)}<div style="font-size:12px;color:var(--text-lo);margin:4px 0 14px">$${Number(b.usd || 0).toFixed(4)} / ${b.usdLimit ? '$' + Number(b.usdLimit).toFixed(2) : '∞'}</div>`;
|
|
6210
6135
|
}
|
|
6211
6136
|
if (agents.length) {
|
|
6212
|
-
const shownAgents = agents.slice(0, 20);
|
|
6213
6137
|
html += '<div class="m-group-title" style="margin-bottom:6px">Per Agent</div>' +
|
|
6214
6138
|
`<table style="width:100%;border-collapse:collapse;font-size:12px"><thead><tr style="color:var(--text-xs);text-align:left"><th style="padding:4px 8px">Agent</th><th>Tokens In</th><th>Tokens Out</th><th>Cost</th></tr></thead><tbody>` +
|
|
6215
|
-
|
|
6139
|
+
agents.slice(0, 20).map(a => {
|
|
6216
6140
|
const over = a.budgetLimit && ((a.tokensIn || 0) + (a.tokensOut || 0)) > a.budgetLimit;
|
|
6217
6141
|
return `<tr style="border-top:1px solid var(--border)${over ? ';color:var(--red)' : ''}">
|
|
6218
|
-
<td style="padding:4px 8px;font-family:var(--mono);font-size:11px;color:${over ? 'var(--red)' : 'var(--text-hi)'}"
|
|
6142
|
+
<td style="padding:4px 8px;font-family:var(--mono);font-size:11px;color:${over ? 'var(--red)' : 'var(--text-hi)'}">${esc((a.id || a.title || '—').toString().slice(0, 14))}</td>
|
|
6219
6143
|
<td style="padding:4px 8px;color:var(--text-lo)">${Number(a.tokensIn || 0).toLocaleString()}</td>
|
|
6220
6144
|
<td style="padding:4px 8px;color:var(--text-lo)">${Number(a.tokensOut || 0).toLocaleString()}</td>
|
|
6221
6145
|
<td style="padding:4px 8px;color:var(--accent)">$${Number(a.cost || 0).toFixed(4)}${over ? ' ⚠' : ''}</td>
|
|
6222
6146
|
</tr>`;
|
|
6223
|
-
}).join('') + '</tbody></table>'
|
|
6224
|
-
(agents.length > 20 ? `<div style="font-size:11px;color:var(--text-xs);margin-top:4px;text-align:right">Showing 20 of ${agents.length} agents</div>` : '');
|
|
6147
|
+
}).join('') + '</tbody></table>';
|
|
6225
6148
|
}
|
|
6226
|
-
el.innerHTML = html || '<div class="empty">No budget data
|
|
6149
|
+
el.innerHTML = html || '<div class="empty">No budget data</div>';
|
|
6227
6150
|
}
|
|
6228
6151
|
|
|
6229
6152
|
// ── PLUGINS ────────────────────────────────────────────────
|
|
@@ -6235,14 +6158,14 @@ async function v2RenderOrgPlugins() {
|
|
|
6235
6158
|
const _enc = encodeURIComponent(_v2SelOrg);
|
|
6236
6159
|
const data = await fetch(`/api/org/${_enc}/plugins${DIR ? '?dir=' + encodeURIComponent(DIR) : ''}`).then(r => r.ok ? r.json() : []).catch(() => []);
|
|
6237
6160
|
const plugins = Array.isArray(data) ? data : (data.plugins || []);
|
|
6238
|
-
if (!plugins.length) { el.innerHTML = '<div class="empty">No plugins installed
|
|
6161
|
+
if (!plugins.length) { el.innerHTML = '<div class="empty">No plugins installed</div>'; return; }
|
|
6239
6162
|
el.innerHTML = `<div class="proj-grid">` +
|
|
6240
6163
|
plugins.map(p => {
|
|
6241
6164
|
const status = p.status || 'installed';
|
|
6242
6165
|
const col = status === 'error' ? 'var(--red)' : status === 'installed' ? 'var(--accent)' : 'var(--text-lo)';
|
|
6243
6166
|
return `<div class="proj-card">
|
|
6244
|
-
<div class="proj-card-name"
|
|
6245
|
-
<div class="proj-card-path"
|
|
6167
|
+
<div class="proj-card-name">${esc(p.name || '—')}</div>
|
|
6168
|
+
<div class="proj-card-path">${esc((p.description || '').slice(0, 80))}</div>
|
|
6246
6169
|
<div style="margin-top:8px"><span class="ss-pill" style="color:${esc(col)};border-color:${esc(col)}44;background:${esc(col)}18">${esc(status)}</span></div>
|
|
6247
6170
|
</div>`;
|
|
6248
6171
|
}).join('') + `</div>`;
|
|
@@ -6265,7 +6188,7 @@ function v2RenderOrgCharts() {
|
|
|
6265
6188
|
events: 0, errors: 0,
|
|
6266
6189
|
}));
|
|
6267
6190
|
activity.forEach(e => {
|
|
6268
|
-
const
|
|
6191
|
+
const ts = new Date(e.ts || e.timestamp || e.created_at || 0).getTime();
|
|
6269
6192
|
const idx = buckets.findIndex((b, i) =>
|
|
6270
6193
|
ts >= b.ts && (i === 13 || ts < buckets[i + 1].ts));
|
|
6271
6194
|
if (idx >= 0) {
|
|
@@ -6290,7 +6213,7 @@ function v2RenderOrgCharts() {
|
|
|
6290
6213
|
// Per-agent run bars
|
|
6291
6214
|
const agentRuns = agents.slice(0, 10).map(a => {
|
|
6292
6215
|
const runs = activity.filter(e => e.agentId === a.id || e.agent === a.id).length;
|
|
6293
|
-
return { name: (a.type || a.title || a.id || '—').toString(), runs };
|
|
6216
|
+
return { name: (a.type || a.title || a.id || '—').toString().slice(0, 20), runs };
|
|
6294
6217
|
}).filter(a => a.runs > 0).sort((x, y) => y.runs - x.runs);
|
|
6295
6218
|
const maxRuns = Math.max(...agentRuns.map(a => a.runs), 1);
|
|
6296
6219
|
|
|
@@ -6310,7 +6233,7 @@ function v2RenderOrgCharts() {
|
|
|
6310
6233
|
<div style="display:flex;gap:2px;align-items:flex-end;padding-bottom:28px;margin-bottom:16px;overflow-x:auto">${heatmap}</div>
|
|
6311
6234
|
${agentRuns.length ? `<div class="m-group-title" style="margin-bottom:6px">Per-Agent Runs</div>
|
|
6312
6235
|
${agentRuns.map(a => `<div style="display:flex;align-items:center;gap:8px;padding:3px 0;font-size:12px">
|
|
6313
|
-
<div style="width:130px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--text-hi);font-family:var(--mono);font-size:11px"
|
|
6236
|
+
<div style="width:130px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--text-hi);font-family:var(--mono);font-size:11px">${esc(a.name)}</div>
|
|
6314
6237
|
<div style="flex:1;height:7px;background:var(--border);border-radius:2px;overflow:hidden"><div style="width:${Math.round(a.runs/maxRuns*100)}%;height:100%;background:var(--accent);border-radius:2px"></div></div>
|
|
6315
6238
|
<div style="width:30px;text-align:right;color:var(--text-lo);font-family:var(--mono);font-size:11px">${a.runs}</div>
|
|
6316
6239
|
</div>`).join('')}` : ''}
|
|
@@ -6326,10 +6249,10 @@ async function v2RenderOrgProjects() {
|
|
|
6326
6249
|
const _enc = encodeURIComponent(_v2SelOrg);
|
|
6327
6250
|
const data = await fetch(`/api/org/${_enc}/projects${DIR?'?dir='+encodeURIComponent(DIR):''}`).then(r=>r.ok?r.json():[]).catch(()=>[]);
|
|
6328
6251
|
const projects = Array.isArray(data) ? data : (data.projects || []);
|
|
6329
|
-
if (!projects.length) { el.innerHTML = '<div class="empty">No projects configured
|
|
6252
|
+
if (!projects.length) { el.innerHTML = '<div class="empty">No projects configured</div>'; return; }
|
|
6330
6253
|
el.innerHTML = `<div class="proj-grid">${projects.map(p => `<div class="proj-card">
|
|
6331
|
-
<div class="proj-card-name"
|
|
6332
|
-
<div class="proj-card-path"
|
|
6254
|
+
<div class="proj-card-name">${esc(p.name || p.title || '—')}</div>
|
|
6255
|
+
<div class="proj-card-path">${esc((p.description || p.path || '').slice(0,80))}</div>
|
|
6333
6256
|
<div class="proj-card-stats" style="margin-top:8px">
|
|
6334
6257
|
${p.status ? `<span class="ss-pill ${p.status==='active'?'on':''}">${esc(p.status)}</span>` : ''}
|
|
6335
6258
|
</div>
|
|
@@ -6342,7 +6265,7 @@ async function v2RenderOrgSkills() {
|
|
|
6342
6265
|
const el = document.getElementById('odt-skills');
|
|
6343
6266
|
if (!el || !_v2OrgData) return;
|
|
6344
6267
|
const roles = Array.isArray(_v2OrgData.roles) ? _v2OrgData.roles : [];
|
|
6345
|
-
if (!roles.length) { el.innerHTML = '<div class="empty">No roles defined
|
|
6268
|
+
if (!roles.length) { el.innerHTML = '<div class="empty">No roles defined</div>'; return; }
|
|
6346
6269
|
el.innerHTML = '<div class="empty">Loading skills…</div>';
|
|
6347
6270
|
const org = _v2SelOrg, dirq = DIR ? '?dir=' + encodeURIComponent(DIR) : '';
|
|
6348
6271
|
// Enrich each role with expertise + task types from its agent definition (same source as the detail drawer)
|
|
@@ -6381,7 +6304,7 @@ async function v2RenderOrgWorkspaces() {
|
|
|
6381
6304
|
const _enc = encodeURIComponent(_v2SelOrg);
|
|
6382
6305
|
const data = await fetch(`/api/org/${_enc}/workspaces${DIR?'?dir='+encodeURIComponent(DIR):''}`).then(r=>r.ok?r.json():[]).catch(()=>[]);
|
|
6383
6306
|
const ws = Array.isArray(data) ? data : (data.workspaces || []);
|
|
6384
|
-
if (!ws.length) { el.innerHTML = '<div class="empty">No workspaces configured
|
|
6307
|
+
if (!ws.length) { el.innerHTML = '<div class="empty">No workspaces configured</div>'; return; }
|
|
6385
6308
|
el.innerHTML = ws.map(w => `<div style="padding:10px 0;border-bottom:1px solid var(--border)">
|
|
6386
6309
|
<div style="display:flex;align-items:center;gap:8px;margin-bottom:4px">
|
|
6387
6310
|
<span style="font-size:13px;color:var(--text-hi);font-weight:500">${esc(w.name || w.id || '—')}</span>
|
|
@@ -6402,16 +6325,16 @@ async function v2RenderOrgInvites() {
|
|
|
6402
6325
|
const _enc = encodeURIComponent(_v2SelOrg);
|
|
6403
6326
|
const data = await fetch(`/api/org/${_enc}/invites${DIR?'?dir='+encodeURIComponent(DIR):''}`).then(r=>r.ok?r.json():[]).catch(()=>[]);
|
|
6404
6327
|
const invites = Array.isArray(data) ? data : (data.invites || []);
|
|
6405
|
-
if (!invites.length) { el.innerHTML = '<div class="empty">No active invite tokens
|
|
6328
|
+
if (!invites.length) { el.innerHTML = '<div class="empty">No active invite tokens</div>'; return; }
|
|
6406
6329
|
el.innerHTML = `<table style="width:100%;border-collapse:collapse;font-size:12px">
|
|
6407
6330
|
<thead><tr style="color:var(--text-xs);text-align:left">
|
|
6408
6331
|
<th style="padding:5px 8px">Token</th><th>Role</th><th>Expires</th><th>Created</th>
|
|
6409
6332
|
</tr></thead>
|
|
6410
6333
|
<tbody>${invites.map(i => `<tr style="border-top:1px solid var(--border)">
|
|
6411
|
-
<td style="padding:5px 8px;color:var(--text-hi);font-family:var(--mono);font-size:11px"
|
|
6334
|
+
<td style="padding:5px 8px;color:var(--text-hi);font-family:var(--mono);font-size:11px">${esc((i.token||i.id||'—').slice(0,16))}…</td>
|
|
6412
6335
|
<td style="padding:5px 8px"><span class="ss-pill">${esc(i.role||'viewer')}</span></td>
|
|
6413
|
-
<td style="padding:5px 8px;color:var(--text-lo)"
|
|
6414
|
-
<td style="padding:5px 8px;color:var(--text-lo)"
|
|
6336
|
+
<td style="padding:5px 8px;color:var(--text-lo)">${i.expiresAt ? relTime(i.expiresAt) : '—'}</td>
|
|
6337
|
+
<td style="padding:5px 8px;color:var(--text-lo)">${i.createdAt ? relTime(i.createdAt) : '—'}</td>
|
|
6415
6338
|
</tr>`).join('')}</tbody>
|
|
6416
6339
|
</table>`;
|
|
6417
6340
|
} catch (e) { el.innerHTML = '<div class="empty">Failed: ' + esc(e.message) + '</div>'; }
|
|
@@ -6422,17 +6345,17 @@ function v2RenderOrgAgentsFull() {
|
|
|
6422
6345
|
const el = document.getElementById('odt-agents-full');
|
|
6423
6346
|
if (!el || !_v2OrgData) return;
|
|
6424
6347
|
const agents = _v2OrgData._agents || [];
|
|
6425
|
-
if (!agents.length) { el.innerHTML = '<div class="empty">No agents found
|
|
6348
|
+
if (!agents.length) { el.innerHTML = '<div class="empty">No agents found</div>'; return; }
|
|
6426
6349
|
el.innerHTML = `<table style="width:100%;border-collapse:collapse;font-size:12px">
|
|
6427
6350
|
<thead><tr style="color:var(--text-xs);text-align:left">
|
|
6428
6351
|
<th style="padding:5px 8px">ID</th><th>Type</th><th>Adapter</th><th>Status</th><th>Heartbeat</th><th>Tokens In</th><th>Tokens Out</th>
|
|
6429
6352
|
</tr></thead>
|
|
6430
6353
|
<tbody>${agents.map(a => `<tr style="border-top:1px solid var(--border)">
|
|
6431
|
-
<td style="padding:5px 8px;color:var(--text-hi);font-family:var(--mono);font-size:11px"
|
|
6354
|
+
<td style="padding:5px 8px;color:var(--text-hi);font-family:var(--mono);font-size:11px">${esc((a.id||'—').toString().slice(0,12))}</td>
|
|
6432
6355
|
<td style="padding:5px 8px;color:var(--text-hi)">${esc(a.type||a.title||'—')}</td>
|
|
6433
6356
|
<td style="padding:5px 8px;color:var(--text-lo)">${esc(a.adapter||'—')}</td>
|
|
6434
6357
|
<td style="padding:5px 8px"><span class="ss-pill ${(a.status==='running'||a.running)?'on':''}">${esc(a.status||'idle')}</span></td>
|
|
6435
|
-
<td style="padding:5px 8px;color:var(--text-lo)"
|
|
6358
|
+
<td style="padding:5px 8px;color:var(--text-lo)">${a.lastHeartbeat ? relTime(a.lastHeartbeat) : '—'}</td>
|
|
6436
6359
|
<td style="padding:5px 8px;color:var(--text-lo)">${a.tokensIn != null ? Number(a.tokensIn).toLocaleString() : '—'}</td>
|
|
6437
6360
|
<td style="padding:5px 8px;color:var(--text-lo)">${a.tokensOut != null ? Number(a.tokensOut).toLocaleString() : '—'}</td>
|
|
6438
6361
|
</tr>`).join('')}</tbody>
|
|
@@ -6448,7 +6371,7 @@ async function v2RenderOrgEnvironments() {
|
|
|
6448
6371
|
const _enc = encodeURIComponent(_v2SelOrg);
|
|
6449
6372
|
const data = await fetch(`/api/org/${_enc}/environments${DIR?'?dir='+encodeURIComponent(DIR):''}`).then(r=>r.ok?r.json():[]).catch(()=>[]);
|
|
6450
6373
|
const envs = Array.isArray(data) ? data : (data.environments || []);
|
|
6451
|
-
if (!envs.length) { el.innerHTML = '<div class="empty">No environments configured
|
|
6374
|
+
if (!envs.length) { el.innerHTML = '<div class="empty">No environments configured</div>'; return; }
|
|
6452
6375
|
el.innerHTML = `<table style="width:100%;border-collapse:collapse;font-size:12px">
|
|
6453
6376
|
<thead><tr style="color:var(--text-xs);text-align:left">
|
|
6454
6377
|
<th style="padding:5px 8px">Name</th><th>Driver</th><th>Host</th><th>Status</th>
|
|
@@ -6468,7 +6391,7 @@ function v2RenderOrgAccess() {
|
|
|
6468
6391
|
const el = document.getElementById('odt-access');
|
|
6469
6392
|
if (!el || !_v2OrgData) return;
|
|
6470
6393
|
const members = _v2OrgData._members || [];
|
|
6471
|
-
if (!members.length) { el.innerHTML = '<div class="empty">No members found
|
|
6394
|
+
if (!members.length) { el.innerHTML = '<div class="empty">No members found</div>'; return; }
|
|
6472
6395
|
const TIER = { owner: 0, admin: 1, operator: 2, viewer: 3 };
|
|
6473
6396
|
const byRole = {};
|
|
6474
6397
|
members.forEach(m => { const r = m.role || 'viewer'; (byRole[r] || (byRole[r] = [])).push(m); });
|
|
@@ -6490,7 +6413,7 @@ function v2RenderOrgIssuesFull() {
|
|
|
6490
6413
|
const el = document.getElementById('odt-issues-full');
|
|
6491
6414
|
if (!el || !_v2OrgData) return;
|
|
6492
6415
|
const issues = _v2OrgData._issues || [];
|
|
6493
|
-
if (!issues.length) { el.innerHTML = '<div class="empty">No issues found
|
|
6416
|
+
if (!issues.length) { el.innerHTML = '<div class="empty">No issues found</div>'; return; }
|
|
6494
6417
|
const PRIORITY = { urgent:'🔴', high:'🟠', medium:'🟡', low:'🟢' };
|
|
6495
6418
|
el.innerHTML = `<table style="width:100%;border-collapse:collapse;font-size:12px">
|
|
6496
6419
|
<thead><tr style="color:var(--text-xs);text-align:left">
|
|
@@ -6498,10 +6421,10 @@ function v2RenderOrgIssuesFull() {
|
|
|
6498
6421
|
</tr></thead>
|
|
6499
6422
|
<tbody>${issues.slice(0,100).map(i => `<tr style="border-top:1px solid var(--border)">
|
|
6500
6423
|
<td style="padding:5px 4px">${PRIORITY[i.priority]||'·'}</td>
|
|
6501
|
-
<td style="padding:5px 8px;color:var(--text-hi);max-width:220px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap"
|
|
6424
|
+
<td style="padding:5px 8px;color:var(--text-hi);max-width:220px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${esc((i.title||i.description||'—').slice(0,80))}</td>
|
|
6502
6425
|
<td style="padding:5px 8px;color:var(--text-lo)">${esc(i.assignee||'—')}</td>
|
|
6503
6426
|
<td style="padding:5px 8px"><span class="ss-pill ${i.status==='done'?'on':i.status==='blocked'?'warn':''}">${esc(i.status||'open')}</span></td>
|
|
6504
|
-
<td style="padding:5px 8px;color:var(--text-xs);font-family:var(--mono);font-size:11px"
|
|
6427
|
+
<td style="padding:5px 8px;color:var(--text-xs);font-family:var(--mono);font-size:11px">${relTime(i.updated_at||i.ts)}</td>
|
|
6505
6428
|
</tr>`).join('')}</tbody>
|
|
6506
6429
|
</table>`;
|
|
6507
6430
|
}
|
|
@@ -6515,7 +6438,7 @@ async function v2RenderOrgJoinRequests() {
|
|
|
6515
6438
|
const _enc = encodeURIComponent(_v2SelOrg);
|
|
6516
6439
|
const data = await fetch(`/api/org/${_enc}/join-requests${DIR?'?dir='+encodeURIComponent(DIR):''}`).then(r=>r.ok?r.json():[]).catch(()=>[]);
|
|
6517
6440
|
const reqs = Array.isArray(data) ? data : (data.joinRequests || data.join_requests || []);
|
|
6518
|
-
if (!reqs.length) { el.innerHTML = '<div class="empty">No pending join requests
|
|
6441
|
+
if (!reqs.length) { el.innerHTML = '<div class="empty">No pending join requests</div>'; return; }
|
|
6519
6442
|
el.innerHTML = `<table style="width:100%;border-collapse:collapse;font-size:12px">
|
|
6520
6443
|
<thead><tr style="color:var(--text-xs);text-align:left">
|
|
6521
6444
|
<th style="padding:5px 8px">Requester</th><th>Type</th><th>Status</th><th>Created</th>
|
|
@@ -6524,7 +6447,7 @@ async function v2RenderOrgJoinRequests() {
|
|
|
6524
6447
|
<td style="padding:5px 8px;color:var(--text-hi)">${esc(r.name||r.username||r.id||'—')}</td>
|
|
6525
6448
|
<td style="padding:5px 8px;color:var(--text-lo)">${r.type==='agent'?'🤖 agent':'👤 human'}</td>
|
|
6526
6449
|
<td style="padding:5px 8px"><span class="ss-pill ${r.status==='approved'?'on':r.status==='rejected'?'warn':''}">${esc(r.status||'pending')}</span></td>
|
|
6527
|
-
<td style="padding:5px 8px;color:var(--text-lo)"
|
|
6450
|
+
<td style="padding:5px 8px;color:var(--text-lo)">${r.createdAt ? relTime(r.createdAt) : '—'}</td>
|
|
6528
6451
|
</tr>`).join('')}</tbody>
|
|
6529
6452
|
</table>`;
|
|
6530
6453
|
} catch (e) { el.innerHTML = '<div class="empty">Failed: ' + esc(e.message) + '</div>'; }
|
|
@@ -6539,17 +6462,17 @@ async function v2RenderOrgThreads() {
|
|
|
6539
6462
|
const _enc = encodeURIComponent(_v2SelOrg);
|
|
6540
6463
|
const data = await fetch(`/api/org/${_enc}/threads${DIR?'?dir='+encodeURIComponent(DIR):''}`).then(r=>r.ok?r.json():[]).catch(()=>[]);
|
|
6541
6464
|
const threads = Array.isArray(data) ? data : (data.threads || []);
|
|
6542
|
-
if (!threads.length) { el.innerHTML = '<div class="empty">No threads found
|
|
6465
|
+
if (!threads.length) { el.innerHTML = '<div class="empty">No threads found</div>'; return; }
|
|
6543
6466
|
el.innerHTML = `<table style="width:100%;border-collapse:collapse;font-size:12px">
|
|
6544
6467
|
<thead><tr style="color:var(--text-xs);text-align:left">
|
|
6545
6468
|
<th style="padding:5px 8px">Subject</th><th>Author</th><th>Messages</th><th>Issue</th><th>Created</th>
|
|
6546
6469
|
</tr></thead>
|
|
6547
6470
|
<tbody>${threads.map(t => `<tr style="border-top:1px solid var(--border)">
|
|
6548
|
-
<td style="padding:5px 8px;color:var(--text-hi);max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap"
|
|
6471
|
+
<td style="padding:5px 8px;color:var(--text-hi);max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${esc((t.subject||t.title||'—').slice(0,50))}</td>
|
|
6549
6472
|
<td style="padding:5px 8px;color:var(--text-lo)">${esc(t.author||t.createdBy||'—')}</td>
|
|
6550
6473
|
<td style="padding:5px 8px;color:var(--text-lo);text-align:center">${t.messageCount ?? t.messages ?? '—'}</td>
|
|
6551
|
-
<td style="padding:5px 8px;color:var(--text-lo);font-family:var(--mono);font-size:11px"
|
|
6552
|
-
<td style="padding:5px 8px;color:var(--text-lo)"
|
|
6474
|
+
<td style="padding:5px 8px;color:var(--text-lo);font-family:var(--mono);font-size:11px">${t.issueId ? '#'+esc(t.issueId.toString().slice(0,8)) : '—'}</td>
|
|
6475
|
+
<td style="padding:5px 8px;color:var(--text-lo)">${t.createdAt ? relTime(t.createdAt) : '—'}</td>
|
|
6553
6476
|
</tr>`).join('')}</tbody>
|
|
6554
6477
|
</table>`;
|
|
6555
6478
|
} catch (e) { el.innerHTML = '<div class="empty">Failed: ' + esc(e.message) + '</div>'; }
|
|
@@ -6559,103 +6482,55 @@ async function v2RenderOrgThreads() {
|
|
|
6559
6482
|
window.v2StopOrg = async function() {
|
|
6560
6483
|
if (!_v2SelOrg) return;
|
|
6561
6484
|
const stopped = _v2SelOrg;
|
|
6562
|
-
try {
|
|
6563
|
-
await fetch(`/api/orgs/${encodeURIComponent(stopped)}/stop`, {method:'POST'});
|
|
6564
|
-
showToast('Stopped', `Org "${stopped}" stopped`, 'ok');
|
|
6565
|
-
} catch(e) { showToast('Error', e.message, 'err'); }
|
|
6485
|
+
try { await fetch(`/api/orgs/${encodeURIComponent(stopped)}/stop`, {method:'POST'}); } catch(_) {}
|
|
6566
6486
|
setTimeout(async () => { await renderOrgs(); if (_v2SelOrg === stopped) v2SelectOrg(stopped); }, 600);
|
|
6567
6487
|
};
|
|
6568
6488
|
|
|
6569
|
-
|
|
6570
|
-
|
|
6571
|
-
if (
|
|
6572
|
-
const dialog = document.getElementById('org-copy-dialog');
|
|
6573
|
-
const feedback = document.getElementById('org-copy-feedback');
|
|
6574
|
-
const select = document.getElementById('org-copy-dest-select');
|
|
6575
|
-
const input = document.getElementById('org-copy-dest-input');
|
|
6576
|
-
if (!dialog) return;
|
|
6577
|
-
feedback.textContent = '';
|
|
6578
|
-
input.value = '';
|
|
6579
|
-
select.innerHTML = '<option value="">Loading…</option>';
|
|
6580
|
-
dialog.style.display = 'flex';
|
|
6581
|
-
// Populate project list
|
|
6582
|
-
try {
|
|
6583
|
-
const data = await apiFetch('/api/projects');
|
|
6584
|
-
const projects = (data.projects || []).filter(p => p.path && p.path !== DIR);
|
|
6585
|
-
select.innerHTML = '<option value="">— select a project —</option>' +
|
|
6586
|
-
projects.map(p => `<option value="${esc(p.path)}">${esc(p.name)} (${esc(p.path)})</option>`).join('') +
|
|
6587
|
-
'<option value="__custom__">Custom path…</option>';
|
|
6588
|
-
} catch(e) {
|
|
6589
|
-
select.innerHTML = '<option value="__custom__">Enter path manually</option>';
|
|
6590
|
-
}
|
|
6489
|
+
window.v2ShowCopyOrgDialog = function() {
|
|
6490
|
+
const d = document.getElementById('org-copy-dialog');
|
|
6491
|
+
if (d) { d.style.display = 'flex'; document.getElementById('org-copy-dest')?.focus(); }
|
|
6591
6492
|
};
|
|
6592
|
-
|
|
6593
|
-
window.v2HideCopyOrgDialog = function() {
|
|
6594
|
-
const dialog = document.getElementById('org-copy-dialog');
|
|
6595
|
-
if (dialog) dialog.style.display = 'none';
|
|
6596
|
-
};
|
|
6597
|
-
|
|
6598
6493
|
window.v2DoCopyOrg = async function() {
|
|
6494
|
+
const dest = document.getElementById('org-copy-dest')?.value.trim();
|
|
6495
|
+
if (!dest) { showToast('Required', 'Enter destination path', 'warn'); return; }
|
|
6599
6496
|
if (!_v2SelOrg) return;
|
|
6600
|
-
const select = document.getElementById('org-copy-dest-select');
|
|
6601
|
-
const input = document.getElementById('org-copy-dest-input');
|
|
6602
|
-
const feedback = document.getElementById('org-copy-feedback');
|
|
6603
|
-
const btn = document.getElementById('org-copy-confirm-btn');
|
|
6604
|
-
const destination = (input.value.trim()) || (select.value !== '__custom__' ? select.value : '');
|
|
6605
|
-
if (!destination) {
|
|
6606
|
-
feedback.textContent = 'Please select or enter a destination.';
|
|
6607
|
-
feedback.style.color = 'var(--red, #e55)';
|
|
6608
|
-
return;
|
|
6609
|
-
}
|
|
6610
|
-
btn.disabled = true;
|
|
6611
|
-
btn.textContent = 'Copying…';
|
|
6612
|
-
feedback.textContent = '';
|
|
6613
6497
|
try {
|
|
6614
|
-
const r = await fetch(
|
|
6615
|
-
method: 'POST',
|
|
6616
|
-
|
|
6617
|
-
body: JSON.stringify({ destination }),
|
|
6498
|
+
const r = await fetch('/api/org/' + enc(_v2SelOrg) + '/copy?dir=' + enc(DIR), {
|
|
6499
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
6500
|
+
body: JSON.stringify({ destination: dest }),
|
|
6618
6501
|
});
|
|
6619
|
-
|
|
6620
|
-
|
|
6621
|
-
|
|
6622
|
-
|
|
6623
|
-
setTimeout(v2HideCopyOrgDialog, 1800);
|
|
6624
|
-
} else {
|
|
6625
|
-
feedback.textContent = 'Error: ' + (json.error || r.status);
|
|
6626
|
-
feedback.style.color = 'var(--red, #e55)';
|
|
6627
|
-
}
|
|
6628
|
-
} catch(e) {
|
|
6629
|
-
feedback.textContent = 'Error: ' + e.message;
|
|
6630
|
-
feedback.style.color = 'var(--red, #e55)';
|
|
6631
|
-
} finally {
|
|
6632
|
-
btn.disabled = false;
|
|
6633
|
-
btn.textContent = 'Copy';
|
|
6634
|
-
}
|
|
6502
|
+
if (!r.ok) throw new Error('HTTP ' + r.status);
|
|
6503
|
+
showToast('Copied', `Org "${_v2SelOrg}" copied to ${dest}`, 'ok');
|
|
6504
|
+
document.getElementById('org-copy-dialog').style.display = 'none';
|
|
6505
|
+
} catch (e) { showToast('Error', e.message, 'err'); }
|
|
6635
6506
|
};
|
|
6636
6507
|
|
|
6508
|
+
function filterLoopList(q) {
|
|
6509
|
+
const query = q.trim().toLowerCase();
|
|
6510
|
+
document.querySelectorAll('#loops-content .loop-row').forEach(row => {
|
|
6511
|
+
const show = !query || (row.textContent || '').toLowerCase().includes(query);
|
|
6512
|
+
row.style.display = show ? '' : 'none';
|
|
6513
|
+
const expand = row.nextElementSibling;
|
|
6514
|
+
if (expand && expand.classList.contains('loop-expand')) expand.style.display = show ? '' : 'none';
|
|
6515
|
+
});
|
|
6516
|
+
}
|
|
6517
|
+
|
|
6518
|
+
function filterOrgList(q) {
|
|
6519
|
+
const query = q.trim().toLowerCase();
|
|
6520
|
+
document.querySelectorAll('#orgs-list-content .org-item').forEach(item => {
|
|
6521
|
+
item.style.display = (!query || (item.textContent || '').toLowerCase().includes(query)) ? '' : 'none';
|
|
6522
|
+
});
|
|
6523
|
+
}
|
|
6524
|
+
|
|
6637
6525
|
// live SSE for org events
|
|
6638
6526
|
(function v2OrgSSE() {
|
|
6639
6527
|
let src;
|
|
6640
|
-
let _connectTime = 0;
|
|
6641
6528
|
function connect() {
|
|
6642
6529
|
if (src) src.close();
|
|
6643
|
-
_connectTime = Date.now();
|
|
6644
6530
|
src = new EventSource('/api/mastermind-stream');
|
|
6645
6531
|
src.onmessage = e => {
|
|
6646
6532
|
try {
|
|
6647
6533
|
const ev = JSON.parse(e.data);
|
|
6648
|
-
if (ev?.type?.startsWith('loop:')) {
|
|
6649
|
-
// Skip replayed historical events (server replays last 50 on connect)
|
|
6650
|
-
if (ev.ts && ev.ts < _connectTime) return;
|
|
6651
|
-
if (currentView === 'loops') renderLoops();
|
|
6652
|
-
else viewRendered['loops'] = false; // stale — re-fetch on next switchView
|
|
6653
|
-
loadLoopMetrics();
|
|
6654
|
-
if (_mmCurrentTab === 'loops' && document.getElementById('mm-overlay')?.classList.contains('open')) {
|
|
6655
|
-
mmRenderLoops(document.getElementById('mm-body'));
|
|
6656
|
-
}
|
|
6657
|
-
return;
|
|
6658
|
-
}
|
|
6659
6534
|
if (!ev?.org || !ev?.type?.startsWith('org:')) return;
|
|
6660
6535
|
const n = ev.org;
|
|
6661
6536
|
// Filter by project dir if the event carries one; skip events from other projects.
|
|
@@ -6724,12 +6599,11 @@ function buildTimeline(events) {
|
|
|
6724
6599
|
// Only tool + user events with timestamps
|
|
6725
6600
|
const stamped = events.filter(ev => ev.ts && (ev.kind === 'tool' || ev.kind === 'user'));
|
|
6726
6601
|
if (stamped.length < 2) { tl.innerHTML = ''; return; }
|
|
6727
|
-
const
|
|
6728
|
-
const times = stamped.map(tsMs);
|
|
6602
|
+
const times = stamped.map(ev => new Date(ev.ts).getTime());
|
|
6729
6603
|
const tMin = Math.min(...times), tMax = Math.max(...times);
|
|
6730
6604
|
const span = tMax - tMin || 1;
|
|
6731
6605
|
const segs = stamped.map(ev => {
|
|
6732
|
-
const pct = ((
|
|
6606
|
+
const pct = ((new Date(ev.ts).getTime() - tMin) / span * 100).toFixed(2);
|
|
6733
6607
|
const cat = ev.kind === 'user' ? 'user' : (ev.cat || 'other');
|
|
6734
6608
|
const color = TL_COLORS[cat] || TL_COLORS.other;
|
|
6735
6609
|
const label = ev.kind === 'user' ? 'user message' : (ev.label || ev.name || cat);
|
|
@@ -6824,13 +6698,12 @@ async function copySession() {
|
|
|
6824
6698
|
try {
|
|
6825
6699
|
const data = await apiFetch('/api/session?dir=' + enc(DIR) + '&file=' + enc(sess.file) + '&limit=300');
|
|
6826
6700
|
const events = data.events || [];
|
|
6827
|
-
const
|
|
6828
|
-
const lines = [`# Session: ${sess.lastPrompt || sess.id}`, `> ${new Date(typeof _sessTs === 'number' ? _sessTs : Number(_sessTs) || _sessTs).toLocaleString()}`, ''];
|
|
6701
|
+
const lines = [`# Session: ${sess.lastPrompt || sess.id}`, `> ${new Date(sess.lastTs || sess.mtime).toLocaleString()}`, ''];
|
|
6829
6702
|
for (const ev of events) {
|
|
6830
6703
|
if (ev.kind === 'user' && ev.text?.trim()) {
|
|
6831
6704
|
lines.push('**User:** ' + ev.text.trim().replace(/\n/g, ' '));
|
|
6832
6705
|
} else if (ev.kind === 'tool') {
|
|
6833
|
-
lines.push(`- \`${ev.name || ev.cat}
|
|
6706
|
+
lines.push(`- \`${ev.name || ev.cat}\`: ${ev.label || ''}`);
|
|
6834
6707
|
}
|
|
6835
6708
|
}
|
|
6836
6709
|
await navigator.clipboard.writeText(lines.join('\n'));
|
|
@@ -6897,10 +6770,7 @@ function cmdSearch(q) {
|
|
|
6897
6770
|
results.innerHTML = sq.length >= 2
|
|
6898
6771
|
? '<div class="cmd-empty">Searching sessions…</div>'
|
|
6899
6772
|
: '<div class="cmd-empty">Type at least 2 chars after > to search all sessions</div>';
|
|
6900
|
-
if (sq.length >= 2)
|
|
6901
|
-
clearTimeout(cmdSearch._debounce);
|
|
6902
|
-
cmdSearch._debounce = setTimeout(() => searchSessions(sq), 300);
|
|
6903
|
-
}
|
|
6773
|
+
if (sq.length >= 2) searchSessions(sq);
|
|
6904
6774
|
return;
|
|
6905
6775
|
}
|
|
6906
6776
|
|
|
@@ -6929,20 +6799,10 @@ function cmdSearch(q) {
|
|
|
6929
6799
|
|
|
6930
6800
|
// ACTIONS group
|
|
6931
6801
|
const actionItems = [
|
|
6932
|
-
{ label: '
|
|
6933
|
-
{ label: 'Go to Sessions', action: () => switchView('sessions') },
|
|
6934
|
-
{ label: 'Go to Projects', action: () => switchView('projects') },
|
|
6935
|
-
{ label: 'Go to Loops', action: () => switchView('loops') },
|
|
6936
|
-
{ label: 'Go to Tokens', action: () => switchView('tokens') },
|
|
6937
|
-
{ label: 'Go to Memory', action: () => switchView('memory') },
|
|
6938
|
-
{ label: 'Go to Orgs', action: () => switchView('orgs') },
|
|
6939
|
-
{ label: 'Go to Monograph', action: () => switchView('monograph') },
|
|
6940
|
-
{ label: 'Go to Global Feed',action: () => switchView('global') },
|
|
6802
|
+
{ label: 'Open Monograph', action: () => { const l = document.querySelector('.nav-item[data-view="monograph"]'); if (l) l.click(); } },
|
|
6941
6803
|
{ label: 'Refresh Dashboard', action: () => refreshCurrent() },
|
|
6942
6804
|
{ label: 'Toggle Compact View', action: () => toggleDensity() },
|
|
6943
|
-
{ label: '
|
|
6944
|
-
{ label: 'Open Mastermind', action: () => openMastermind() },
|
|
6945
|
-
{ label: 'Keyboard Shortcuts', action: () => { closeCmdPalette(); openShortcutHelp(); } },
|
|
6805
|
+
{ label: 'Open Loops', action: () => { const l = document.querySelector('.nav-item[data-view="loops"]'); if (l) l.click(); } },
|
|
6946
6806
|
].filter(a => !lq || a.label.toLowerCase().includes(lq));
|
|
6947
6807
|
|
|
6948
6808
|
// TABS group — only if org room is open
|
|
@@ -6968,8 +6828,8 @@ function cmdSearch(q) {
|
|
|
6968
6828
|
html += `<div class="cmd-item" data-ci="${idx}">
|
|
6969
6829
|
<span class="ci-ico">◫</span>
|
|
6970
6830
|
<div class="cmd-item-body">
|
|
6971
|
-
<div class="ci-title"
|
|
6972
|
-
<div class="ci-sub"
|
|
6831
|
+
<div class="ci-title">${esc(s.lastPrompt || s.id.slice(0, 32))}</div>
|
|
6832
|
+
<div class="ci-sub">${relTime(s.lastTs || s.mtime)}</div>
|
|
6973
6833
|
</div>
|
|
6974
6834
|
</div>`;
|
|
6975
6835
|
});
|
|
@@ -6984,7 +6844,7 @@ function cmdSearch(q) {
|
|
|
6984
6844
|
<span class="ci-ico">◈</span>
|
|
6985
6845
|
<div class="cmd-item-body">
|
|
6986
6846
|
<div class="ci-title">${esc(d.key || d.namespace || '—')}</div>
|
|
6987
|
-
<div class="ci-sub"
|
|
6847
|
+
<div class="ci-sub">${esc(String(d.value || d.text || '').slice(0, 60))}</div>
|
|
6988
6848
|
</div>
|
|
6989
6849
|
</div>`;
|
|
6990
6850
|
});
|
|
@@ -7030,7 +6890,7 @@ function cmdSearch(q) {
|
|
|
7030
6890
|
<span class="ci-ico">✦</span>
|
|
7031
6891
|
<div class="cmd-item-body">
|
|
7032
6892
|
<div class="ci-title">${esc(skLabel)}</div>
|
|
7033
|
-
<div class="ci-sub"
|
|
6893
|
+
<div class="ci-sub">${typeof sk === 'string' ? '' : esc((sk.description || '').slice(0, 60))}</div>
|
|
7034
6894
|
</div>
|
|
7035
6895
|
</div>`;
|
|
7036
6896
|
});
|
|
@@ -7097,7 +6957,7 @@ function executeCmdItem() {
|
|
|
7097
6957
|
else if (item.type === 'memory') switchView('memory');
|
|
7098
6958
|
else if (item.type === 'project') switchProject(item.data.path);
|
|
7099
6959
|
else if (item.type === 'org') { switchView('orgs'); setTimeout(() => v2SelectOrg(item.data.name), 80); }
|
|
7100
|
-
else if (item.type === 'skill')
|
|
6960
|
+
else if (item.type === 'skill') switchView('memory');
|
|
7101
6961
|
else if (item.type === 'action') { if (item.data.action) item.data.action(); }
|
|
7102
6962
|
else if (item.type === 'orgtab') { const btn = document.querySelector(`.odt-btn[data-tab="${item.data.tab}"]`); if (btn) btn.click(); }
|
|
7103
6963
|
}
|
|
@@ -7120,8 +6980,8 @@ async function searchSessions(q) {
|
|
|
7120
6980
|
html += `<div class="cmd-item" data-ci="${idx}">
|
|
7121
6981
|
<span class="ci-ico">◫</span>
|
|
7122
6982
|
<div class="cmd-item-body">
|
|
7123
|
-
<div class="ci-title"
|
|
7124
|
-
<div class="ci-sub"
|
|
6983
|
+
<div class="ci-title">${esc(r.lastPrompt || r.id.slice(0, 32))}</div>
|
|
6984
|
+
<div class="ci-sub">${esc(snippet.length > 70 ? snippet.slice(0, 70) + '…' : snippet)}</div>
|
|
7125
6985
|
</div>
|
|
7126
6986
|
</div>`;
|
|
7127
6987
|
});
|
|
@@ -7146,22 +7006,21 @@ document.addEventListener('keydown', e => {
|
|
|
7146
7006
|
return;
|
|
7147
7007
|
}
|
|
7148
7008
|
|
|
7149
|
-
// Escape always closes modals, even when focused inside an input/textarea
|
|
7150
|
-
if (e.key === 'Escape') { closeDetail(); closeCmdPalette(); closeShortcutHelp(); closeBudgetModal(); closeChunkModal(); closeMemModal(); closeReportCard(); closeMastermind(); if (document.getElementById('app').classList.contains('ambient')) toggleAmbient(); return; }
|
|
7151
|
-
|
|
7152
7009
|
// ignore when typing in inputs
|
|
7153
7010
|
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return;
|
|
7154
7011
|
if (document.getElementById('cmd-palette').classList.contains('open')) return;
|
|
7012
|
+
|
|
7013
|
+
if (e.key === 'Escape') { closeDetail(); closeCmdPalette(); closeShortcutHelp(); closeMastermind(); closeBudgetModal(); if (document.getElementById('app').classList.contains('ambient')) toggleAmbient(); }
|
|
7155
7014
|
if (e.key === '?') { e.preventDefault(); openShortcutHelp(); return; }
|
|
7156
|
-
if (e.key === 'r' || e.key === 'R') { e.preventDefault(); refreshCurrent(); return; }
|
|
7157
7015
|
if (e.key === 'm' || e.key === 'M') { e.preventDefault(); openMastermind(); return; }
|
|
7016
|
+
const _vkeys = { '1':'now','2':'sessions','3':'projects','4':'loops','5':'tokens','6':'memory','7':'orgs','8':'monograph','9':'global' };
|
|
7017
|
+
if (_vkeys[e.key] && !e.ctrlKey && !e.metaKey && !e.altKey) { switchView(_vkeys[e.key]); return; }
|
|
7158
7018
|
if (e.key === 'a' || e.key === 'A') { if (currentView === 'now') { e.preventDefault(); toggleAmbient(); } }
|
|
7159
|
-
const _viewKeys = {'1':'now','2':'sessions','3':'projects','4':'loops','5':'tokens','6':'memory','7':'orgs','8':'monograph','9':'global'};
|
|
7160
|
-
if (_viewKeys[e.key] && !e.metaKey && !e.ctrlKey && !e.altKey) { e.preventDefault(); switchView(_viewKeys[e.key]); return; }
|
|
7161
7019
|
|
|
7162
7020
|
if (currentView === 'now') {
|
|
7163
7021
|
if (e.key === '/') { e.preventDefault(); toggleFeedSearch(); }
|
|
7164
7022
|
if (e.key === 'g' || e.key === 'G') { e.preventDefault(); goLive(); }
|
|
7023
|
+
if (e.key === 'r' || e.key === 'R') { e.preventDefault(); refreshCurrent(); }
|
|
7165
7024
|
|
|
7166
7025
|
if (e.key === 'j' || e.key === 'k') {
|
|
7167
7026
|
e.preventDefault();
|
|
@@ -7172,11 +7031,9 @@ document.addEventListener('keydown', e => {
|
|
|
7172
7031
|
else cur = cur < 0 ? 0 : Math.max(cur - 1, 0);
|
|
7173
7032
|
entries.forEach((el, i) => el.classList.toggle('selected', i === cur));
|
|
7174
7033
|
entries[cur].scrollIntoView({ block: 'nearest' });
|
|
7175
|
-
|
|
7176
|
-
|
|
7177
|
-
|
|
7178
|
-
: '';
|
|
7179
|
-
} catch { selectedEntryId = ''; }
|
|
7034
|
+
selectedEntryId = entries[cur].dataset.ev
|
|
7035
|
+
? (JSON.parse(entries[cur].dataset.ev).id || '')
|
|
7036
|
+
: '';
|
|
7180
7037
|
}
|
|
7181
7038
|
|
|
7182
7039
|
if (e.key === 'Enter') {
|
|
@@ -7231,22 +7088,6 @@ function filterSessions(q) {
|
|
|
7231
7088
|
});
|
|
7232
7089
|
const countEl = document.getElementById('sess-filter-count');
|
|
7233
7090
|
if (countEl) countEl.textContent = lq && rows.length ? `${visible} / ${rows.length}` : '';
|
|
7234
|
-
// zero-results empty state
|
|
7235
|
-
const noRes = document.getElementById('sess-filter-noresult');
|
|
7236
|
-
if (lq && visible === 0 && rows.length > 0) {
|
|
7237
|
-
if (!noRes) {
|
|
7238
|
-
const el = document.createElement('div');
|
|
7239
|
-
el.id = 'sess-filter-noresult';
|
|
7240
|
-
el.className = 'empty';
|
|
7241
|
-
el.style.cssText = 'padding:24px 0;font-size:13px';
|
|
7242
|
-
el.textContent = 'No sessions match "' + q.slice(0, 40) + '"';
|
|
7243
|
-
document.getElementById('sess-content').appendChild(el);
|
|
7244
|
-
} else {
|
|
7245
|
-
noRes.textContent = 'No sessions match "' + q.slice(0, 40) + '"';
|
|
7246
|
-
}
|
|
7247
|
-
} else if (noRes) {
|
|
7248
|
-
noRes.remove();
|
|
7249
|
-
}
|
|
7250
7091
|
}
|
|
7251
7092
|
|
|
7252
7093
|
// ── feature 32: keyboard shortcut help modal ──────────────
|
|
@@ -7273,7 +7114,7 @@ function updateCurrentActivity(events) {
|
|
|
7273
7114
|
} else if (name) {
|
|
7274
7115
|
activity = name;
|
|
7275
7116
|
}
|
|
7276
|
-
if (activity) { el.textContent = '⤷ ' + activity; el.
|
|
7117
|
+
if (activity) { el.textContent = '⤷ ' + activity; el.classList.add('loaded'); }
|
|
7277
7118
|
else { el.textContent = ''; el.classList.remove('loaded'); }
|
|
7278
7119
|
}
|
|
7279
7120
|
|
|
@@ -7297,8 +7138,7 @@ function buildPatterns() {
|
|
|
7297
7138
|
if (!STOP_WORDS.has(w) && !seen.has(w)) { freq[w] = (freq[w] || 0) + 1; seen.add(w); }
|
|
7298
7139
|
}
|
|
7299
7140
|
}
|
|
7300
|
-
const
|
|
7301
|
-
const sorted = allTerms.slice(0, 20);
|
|
7141
|
+
const sorted = Object.entries(freq).filter(([,c]) => c >= 2).sort((a, b) => b[1] - a[1]).slice(0, 20);
|
|
7302
7142
|
if (!sorted.length) { el.innerHTML = '<div class="loading-txt">Not enough prompt data</div>'; return; }
|
|
7303
7143
|
const maxCount = sorted[0][1];
|
|
7304
7144
|
const rows = sorted.map(([word, count], i) => {
|
|
@@ -7311,8 +7151,7 @@ function buildPatterns() {
|
|
|
7311
7151
|
}).join('');
|
|
7312
7152
|
el.innerHTML = `<table class="lb-table"><thead><tr>
|
|
7313
7153
|
<th class="lb-rank">#</th><th>Term</th><th></th><th class="lb-cost">Sessions</th>
|
|
7314
|
-
</tr></thead><tbody>${rows}</tbody></table
|
|
7315
|
-
(allTerms.length > 20 ? `<div style="font-size:11px;color:var(--text-xs);margin-top:4px;text-align:right">Showing 20 of ${allTerms.length} terms</div>` : '');
|
|
7154
|
+
</tr></thead><tbody>${rows}</tbody></table>`;
|
|
7316
7155
|
}
|
|
7317
7156
|
|
|
7318
7157
|
// ── feature 35: session streak tracker ────────────────────
|
|
@@ -7320,7 +7159,7 @@ function calcStreak() {
|
|
|
7320
7159
|
const dates = new Set(allSessions.map(s => {
|
|
7321
7160
|
const t = s.firstTs || s.mtime;
|
|
7322
7161
|
if (!t) return null;
|
|
7323
|
-
return new Date(typeof t === 'number' ? t :
|
|
7162
|
+
return new Date(typeof t === 'number' ? t : t).toDateString();
|
|
7324
7163
|
}).filter(Boolean));
|
|
7325
7164
|
let streak = 0;
|
|
7326
7165
|
const today = new Date();
|
|
@@ -7338,16 +7177,14 @@ function calcStreak() {
|
|
|
7338
7177
|
|
|
7339
7178
|
// ── feature 25: notification toasts ──────────────────────
|
|
7340
7179
|
let _toastLastBudgetKey = '';
|
|
7341
|
-
let _toastLastKey = ''; let _toastLastTs = 0;
|
|
7342
7180
|
function showToast(title, msg, type = 'info', duration = 5000) {
|
|
7181
|
+
const key = title + '|' + msg;
|
|
7182
|
+
if (showToast._last === key && Date.now() - (showToast._lastTs || 0) < 2000) return;
|
|
7183
|
+
showToast._last = key; showToast._lastTs = Date.now();
|
|
7184
|
+
const existing = document.querySelectorAll('.toast');
|
|
7185
|
+
if (existing.length >= 5) existing[0].remove();
|
|
7343
7186
|
const rack = document.getElementById('toast-rack');
|
|
7344
7187
|
if (!rack) return;
|
|
7345
|
-
// Dedup: skip identical toast within 2s
|
|
7346
|
-
const key = type + '|' + title + '|' + msg;
|
|
7347
|
-
if (key === _toastLastKey && Date.now() - _toastLastTs < 2000) return;
|
|
7348
|
-
_toastLastKey = key; _toastLastTs = Date.now();
|
|
7349
|
-
// Cap at 5 visible toasts — evict oldest
|
|
7350
|
-
while (rack.children.length >= 5) rack.firstChild.remove();
|
|
7351
7188
|
const icoMap = { warn: '⚑', err: '⚠', ok: '✓', info: '◉' };
|
|
7352
7189
|
const div = document.createElement('div');
|
|
7353
7190
|
div.className = 'toast t-' + type;
|
|
@@ -7356,7 +7193,7 @@ function showToast(title, msg, type = 'info', duration = 5000) {
|
|
|
7356
7193
|
<div class="toast-title">${esc(title)}</div>
|
|
7357
7194
|
<div class="toast-msg">${esc(msg)}</div>
|
|
7358
7195
|
</div>
|
|
7359
|
-
<button class="toast-close" onclick="this.closest('.toast').remove()"
|
|
7196
|
+
<button class="toast-close" onclick="this.closest('.toast').remove()">✕</button>`;
|
|
7360
7197
|
rack.appendChild(div);
|
|
7361
7198
|
if (duration > 0) setTimeout(() => { try { div.remove(); } catch {} }, duration);
|
|
7362
7199
|
}
|
|
@@ -7427,11 +7264,7 @@ function buildSessionHeatmap(sessions) {
|
|
|
7427
7264
|
if (idx >= 0 && idx < DAYS) buckets[idx].count++;
|
|
7428
7265
|
}
|
|
7429
7266
|
const max = Math.max(...buckets.map(b => b.count), 1);
|
|
7430
|
-
|
|
7431
|
-
const firstDowShm = new Date(now - (DAYS - 1) * DAY).getDay(); // 0=Sun
|
|
7432
|
-
const shmOffset = firstDowShm === 0 ? 6 : firstDowShm - 1;
|
|
7433
|
-
const padShm = Array.from({ length: shmOffset }, () => '<div class="shm-cell" style="opacity:0;pointer-events:none"></div>');
|
|
7434
|
-
el.innerHTML = padShm.join('') + buckets.map(b => {
|
|
7267
|
+
el.innerHTML = buckets.map(b => {
|
|
7435
7268
|
const level = b.count === 0 ? 0 : Math.min(4, Math.ceil(b.count / max * 4));
|
|
7436
7269
|
const isActive = b.date === heatmapDateFilter;
|
|
7437
7270
|
return `<div class="shm-cell shm-${level}${isActive ? ' shm-active' : ''}" title="${b.date}: ${b.count} session${b.count !== 1 ? 's' : ''}" onclick="setHeatmapFilter('${b.date}',${b.count})"></div>`;
|
|
@@ -7525,8 +7358,7 @@ function bulkExport() {
|
|
|
7525
7358
|
if (!toExport.length) return;
|
|
7526
7359
|
const headers = ['Date', 'Session ID', 'Prompt', 'Cost ($)', 'Duration (s)', 'Tool Calls', 'Files Touched'];
|
|
7527
7360
|
const rows = toExport.map(s => {
|
|
7528
|
-
const
|
|
7529
|
-
const dt = new Date(typeof _ts0 === 'number' ? _ts0 : Number(_ts0) || _ts0).toISOString().slice(0, 19).replace('T', ' ');
|
|
7361
|
+
const dt = new Date(s.firstTs || s.mtime || 0).toISOString().slice(0, 19).replace('T', ' ');
|
|
7530
7362
|
const cost = typeof s.totalCost === 'number' ? s.totalCost.toFixed(4) : '';
|
|
7531
7363
|
const dur = s.totalDurationMs ? Math.round(s.totalDurationMs / 1000) : '';
|
|
7532
7364
|
const prompt = (s.lastPrompt || '').replace(/"/g, '""');
|
|
@@ -7580,8 +7412,7 @@ function exportSessionsCSV() {
|
|
|
7580
7412
|
if (!allSessions.length) { showToast('No data', 'No sessions loaded yet', 'warn'); return; }
|
|
7581
7413
|
const headers = ['Date', 'Session ID', 'Prompt', 'Cost ($)', 'Duration (s)', 'Tool Calls', 'User Messages', 'Cache Hit %', 'Input Tokens'];
|
|
7582
7414
|
const rows = allSessions.map(s => {
|
|
7583
|
-
const
|
|
7584
|
-
const dt = new Date(typeof _ts1 === 'number' ? _ts1 : Number(_ts1) || _ts1).toISOString().slice(0, 19).replace('T', ' ');
|
|
7415
|
+
const dt = new Date(s.firstTs || s.mtime || 0).toISOString().slice(0, 19).replace('T', ' ');
|
|
7585
7416
|
const cost = typeof s.totalCost === 'number' ? s.totalCost.toFixed(4) : '';
|
|
7586
7417
|
const dur = s.totalDurationMs ? Math.round(s.totalDurationMs / 1000) : '';
|
|
7587
7418
|
const cachePct = s.totalInputTokens > 0 ? Math.round((s.cacheReadTokens || 0) / s.totalInputTokens * 100) : '';
|
|
@@ -7614,12 +7445,11 @@ async function loadToolRank() {
|
|
|
7614
7445
|
const data = await apiFetch('/api/tool-ranking?dir=' + enc(DIR));
|
|
7615
7446
|
if (!data.tools?.length) { el.innerHTML = '<div class="loading-txt">No tool usage data</div>'; return; }
|
|
7616
7447
|
const maxCount = data.tools[0].count;
|
|
7617
|
-
const
|
|
7618
|
-
const rows = shownTools.map((t, i) => {
|
|
7448
|
+
const rows = data.tools.slice(0, 15).map((t, i) => {
|
|
7619
7449
|
const barW = Math.round((t.count / maxCount) * 100);
|
|
7620
7450
|
const errRate = t.errors > 0 ? ((t.errors / t.count) * 100).toFixed(0) + '%' : '—';
|
|
7621
7451
|
return `<tr><td class="lb-rank">${i + 1}</td>
|
|
7622
|
-
<td style="font-size:12px;color:var(--text-mid);max-width:180px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap"
|
|
7452
|
+
<td style="font-size:12px;color:var(--text-mid);max-width:180px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${esc(t.tool)}</td>
|
|
7623
7453
|
<td style="width:80px;padding:4px 6px">
|
|
7624
7454
|
<div style="height:4px;background:var(--surface-hi);border-radius:2px;overflow:hidden">
|
|
7625
7455
|
<div style="height:100%;width:${barW}%;background:oklch(65% 0.15 200);border-radius:2px"></div>
|
|
@@ -7631,8 +7461,7 @@ async function loadToolRank() {
|
|
|
7631
7461
|
}).join('');
|
|
7632
7462
|
el.innerHTML = `<table class="lb-table"><thead><tr>
|
|
7633
7463
|
<th class="lb-rank">#</th><th>Tool</th><th></th><th class="lb-cost">Calls</th><th class="lb-dur">Error%</th>
|
|
7634
|
-
</tr></thead><tbody>${rows}</tbody></table
|
|
7635
|
-
(data.tools.length > 15 ? `<div style="font-size:11px;color:var(--text-xs);margin-top:4px;text-align:right">Showing 15 of ${data.tools.length} tools</div>` : '');
|
|
7464
|
+
</tr></thead><tbody>${rows}</tbody></table>`;
|
|
7636
7465
|
} catch (err) {
|
|
7637
7466
|
el.innerHTML = '<div class="loading-txt">Could not load: ' + esc(err.message) + '</div>';
|
|
7638
7467
|
}
|
|
@@ -7654,14 +7483,13 @@ async function loadProjCosts() {
|
|
|
7654
7483
|
const data = await apiFetch('/api/project-costs');
|
|
7655
7484
|
if (!data.projects?.length) { el.innerHTML = '<div class="loading-txt">No cost data across projects</div>'; return; }
|
|
7656
7485
|
const maxCost = data.projects[0].cost;
|
|
7657
|
-
const
|
|
7658
|
-
const rows = shownProjects.map((p, i) => {
|
|
7486
|
+
const rows = data.projects.slice(0, 10).map((p, i) => {
|
|
7659
7487
|
const barW = maxCost > 0 ? Math.round((p.cost / maxCost) * 100) : 0;
|
|
7660
7488
|
const name = p.path.split('/').filter(Boolean).pop() || p.path;
|
|
7661
7489
|
return `<tr onclick="switchProject('${esc(p.path)}')" style="cursor:pointer" title="${esc(p.path)}">
|
|
7662
7490
|
<td class="lb-rank">${i + 1}</td>
|
|
7663
7491
|
<td style="font-size:12px;color:var(--text-mid);max-width:140px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${esc(name)}</td>
|
|
7664
|
-
<td class="lb-cost">$${
|
|
7492
|
+
<td class="lb-cost">$${p.cost.toFixed(2)}</td>
|
|
7665
7493
|
<td style="width:80px;padding:4px 6px">
|
|
7666
7494
|
<div style="height:4px;background:var(--surface-hi);border-radius:2px;overflow:hidden">
|
|
7667
7495
|
<div style="height:100%;width:${barW}%;background:oklch(72% 0.18 75 / 0.7);border-radius:2px"></div>
|
|
@@ -7672,8 +7500,7 @@ async function loadProjCosts() {
|
|
|
7672
7500
|
}).join('');
|
|
7673
7501
|
el.innerHTML = `<table class="lb-table"><thead><tr>
|
|
7674
7502
|
<th class="lb-rank">#</th><th>Project</th><th class="lb-cost">Cost</th><th></th><th class="lb-dur">Sessions</th>
|
|
7675
|
-
</tr></thead><tbody>${rows}</tbody></table
|
|
7676
|
-
(data.projects.length > 10 ? `<div style="font-size:11px;color:var(--text-xs);margin-top:4px;text-align:right">Showing 10 of ${data.projects.length} projects</div>` : '');
|
|
7503
|
+
</tr></thead><tbody>${rows}</tbody></table>`;
|
|
7677
7504
|
} catch (err) {
|
|
7678
7505
|
el.innerHTML = '<div class="loading-txt">Could not load: ' + esc(err.message) + '</div>';
|
|
7679
7506
|
}
|
|
@@ -7701,7 +7528,7 @@ function buildGanttTimeline() {
|
|
|
7701
7528
|
}
|
|
7702
7529
|
for (const s of allSessions) {
|
|
7703
7530
|
const t = s.firstTs || s.mtime; if (!t) continue;
|
|
7704
|
-
const d = new Date(typeof t === 'number' ? t :
|
|
7531
|
+
const d = new Date(typeof t === 'number' ? t : t); d.setHours(0,0,0,0);
|
|
7705
7532
|
const key = d.toDateString();
|
|
7706
7533
|
if (key in days) days[key].push(s);
|
|
7707
7534
|
}
|
|
@@ -7719,11 +7546,11 @@ function buildGanttTimeline() {
|
|
|
7719
7546
|
// Sort sessions by start time
|
|
7720
7547
|
const sorted = [...sessions].sort((a, b) => {
|
|
7721
7548
|
const ta = a.firstTs || a.mtime || 0; const tb = b.firstTs || b.mtime || 0;
|
|
7722
|
-
return (typeof ta === 'number' ? ta :
|
|
7549
|
+
return (typeof ta === 'number' ? ta : new Date(ta).getTime()) - (typeof tb === 'number' ? tb : new Date(tb).getTime());
|
|
7723
7550
|
});
|
|
7724
7551
|
for (let i = 0; i < sorted.length; i++) {
|
|
7725
7552
|
const s = sorted[i];
|
|
7726
|
-
const
|
|
7553
|
+
const startTs = typeof (s.firstTs || s.mtime) === 'number' ? (s.firstTs || s.mtime) : new Date(s.firstTs || s.mtime).getTime();
|
|
7727
7554
|
const dayStart = new Date(dateStr).getTime();
|
|
7728
7555
|
const startPct = Math.min(95, ((startTs - dayStart) / DAY) * 100);
|
|
7729
7556
|
const durPct = s.totalDurationMs ? Math.max(0.5, Math.min(15, (s.totalDurationMs / DAY) * 100)) : 1;
|
|
@@ -7750,7 +7577,7 @@ function showReportCard() {
|
|
|
7750
7577
|
|
|
7751
7578
|
const todaySess = allSessions.filter(s => {
|
|
7752
7579
|
const t = s.firstTs || s.mtime; if (!t) return false;
|
|
7753
|
-
return new Date(typeof t === 'number' ? t :
|
|
7580
|
+
return new Date(typeof t === 'number' ? t : t).toDateString() === todayStr;
|
|
7754
7581
|
});
|
|
7755
7582
|
const weekSess = allSessions.filter(s => {
|
|
7756
7583
|
const t = s.firstTs || s.mtime; if (!t) return false;
|
|
@@ -7900,7 +7727,7 @@ function showCostExplainer(sessId, event) {
|
|
|
7900
7727
|
.map(([m,d])=>`<div class="err-item">${esc(m.replace(/^claude-/,'').replace(/-\d{8}$/,''))}: $${(d.cost||0).toFixed(4)} · ${d.calls||0} calls</div>`).join('');
|
|
7901
7728
|
drawer.innerHTML = `<div class="err-drawer-head" style="color:oklch(70% 0.18 80)">
|
|
7902
7729
|
<span>Cost anomaly — $${(sess.totalCost||0).toFixed(3)} (${ratio}× median, top ${100-pct}%)</span>
|
|
7903
|
-
<button class="err-close" onclick="this.closest('.err-drawer').classList.remove('open');this.closest('.err-drawer').innerHTML=''"
|
|
7730
|
+
<button class="err-close" onclick="this.closest('.err-drawer').classList.remove('open');this.closest('.err-drawer').innerHTML=''">✕</button>
|
|
7904
7731
|
</div>
|
|
7905
7732
|
<div class="err-drawer-body">
|
|
7906
7733
|
<div class="err-item" style="color:var(--text-lo)">Tool calls: ${sess.toolCalls||0} · Messages: ${sess.totalMessages||0} · Tokens in: ${(sess.totalInputTokens||0).toLocaleString()}</div>
|
|
@@ -7969,7 +7796,7 @@ function buildHourlyHeatmap() {
|
|
|
7969
7796
|
const grid = Array.from({ length: 7 }, () => new Array(24).fill(0));
|
|
7970
7797
|
for (const s of allSessions) {
|
|
7971
7798
|
const t = s.firstTs || s.mtime; if (!t) continue;
|
|
7972
|
-
const d = new Date(typeof t === 'number' ? t :
|
|
7799
|
+
const d = new Date(typeof t === 'number' ? t : t);
|
|
7973
7800
|
const dow = d.getDay(); // 0=Sun
|
|
7974
7801
|
const hour = d.getHours();
|
|
7975
7802
|
grid[dow][hour]++;
|
|
@@ -8000,7 +7827,7 @@ function buildHourlyHeatmap() {
|
|
|
8000
7827
|
|
|
8001
7828
|
// ── feature 50: custom tag editor ─────────────────────────
|
|
8002
7829
|
const _customTagsKey = 'mm-custom-tags';
|
|
8003
|
-
let _customTagsMap = new Map(Object.entries(
|
|
7830
|
+
let _customTagsMap; try { _customTagsMap = new Map(Object.entries(JSON.parse(localStorage.getItem(_customTagsKey) || '{}'))); } catch { _customTagsMap = new Map(); }
|
|
8004
7831
|
|
|
8005
7832
|
function getCustomTags(sessId) {
|
|
8006
7833
|
return _customTagsMap.get(sessId) || [];
|
|
@@ -8020,12 +7847,10 @@ function addCustomTag(sessId, tag, event) {
|
|
|
8020
7847
|
if (!tags.includes(t)) { tags.push(t); saveCustomTags(sessId, tags); }
|
|
8021
7848
|
const wrap = document.querySelector(`.sr-custom-tags[data-sess="${CSS.escape(sessId)}"]`);
|
|
8022
7849
|
if (wrap) wrap.outerHTML = renderCustomTagsInline(sessId, tags);
|
|
8023
|
-
// rebuild tag filter bar
|
|
7850
|
+
// rebuild tag filter bar in the DOM if it exists
|
|
8024
7851
|
initTags();
|
|
8025
7852
|
const tfBar = document.querySelector('.tag-filter-bar');
|
|
8026
|
-
|
|
8027
|
-
if (tfBar) tfBar.outerHTML = newBarHtml;
|
|
8028
|
-
else if (newBarHtml) renderSessions();
|
|
7853
|
+
if (tfBar) tfBar.outerHTML = buildTagFilterBar(allSessions);
|
|
8029
7854
|
}
|
|
8030
7855
|
|
|
8031
7856
|
function removeCustomTag(sessId, tag, event) {
|
|
@@ -8034,9 +7859,10 @@ function removeCustomTag(sessId, tag, event) {
|
|
|
8034
7859
|
saveCustomTags(sessId, tags);
|
|
8035
7860
|
const wrap = document.querySelector(`.sr-custom-tags[data-sess="${CSS.escape(sessId)}"]`);
|
|
8036
7861
|
if (wrap) wrap.outerHTML = renderCustomTagsInline(sessId, tags);
|
|
8037
|
-
initTags();
|
|
8038
7862
|
const tfBar = document.querySelector('.tag-filter-bar');
|
|
8039
|
-
|
|
7863
|
+
const newBarHtml = buildTagFilterBar(allSessions);
|
|
7864
|
+
if (tfBar) tfBar.outerHTML = newBarHtml;
|
|
7865
|
+
else if (newBarHtml) renderSessions();
|
|
8040
7866
|
}
|
|
8041
7867
|
|
|
8042
7868
|
function showCustomTagInput(sessId, event) {
|
|
@@ -8049,7 +7875,7 @@ function showCustomTagInput(sessId, event) {
|
|
|
8049
7875
|
iw.className = 'ctag-input-wrap';
|
|
8050
7876
|
iw.onclick = e => e.stopPropagation();
|
|
8051
7877
|
iw.innerHTML = `<input class="ctag-input" type="text" placeholder="tag name…" maxlength="20" autofocus>
|
|
8052
|
-
<button class="ctag-ok"
|
|
7878
|
+
<button class="ctag-ok" onclick="(()=>{const inp=this.closest('.ctag-input-wrap').querySelector('input');addCustomTag('${esc(sessId)}',inp.value,event);inp.value='';})()">Add</button>`;
|
|
8053
7879
|
wrap.appendChild(iw);
|
|
8054
7880
|
const inp = iw.querySelector('input');
|
|
8055
7881
|
inp.focus();
|
|
@@ -8061,11 +7887,11 @@ function showCustomTagInput(sessId, event) {
|
|
|
8061
7887
|
|
|
8062
7888
|
function renderCustomTagsInline(sessId, tags) {
|
|
8063
7889
|
const tagHtml = tags.map(t =>
|
|
8064
|
-
`<span class="sr-ctag">${esc(t)}<span class="ctag-del"
|
|
7890
|
+
`<span class="sr-ctag">${esc(t)}<span class="ctag-del" onclick="removeCustomTag('${esc(sessId)}','${esc(t)}',event)" title="Remove tag">✕</span></span>`
|
|
8065
7891
|
).join('');
|
|
8066
7892
|
return `<div class="sr-custom-tags" data-sess="${esc(sessId)}" onclick="event.stopPropagation()">
|
|
8067
7893
|
${tagHtml}
|
|
8068
|
-
<button class="ctag-add-btn"
|
|
7894
|
+
<button class="ctag-add-btn" onclick="showCustomTagInput('${esc(sessId)}',event)" title="Add tag">+ tag</button>
|
|
8069
7895
|
</div>`;
|
|
8070
7896
|
}
|
|
8071
7897
|
|
|
@@ -8080,14 +7906,14 @@ async function toggleErrDrawer(sessId, event) {
|
|
|
8080
7906
|
try {
|
|
8081
7907
|
const data = await apiFetch('/api/session-errors?dir=' + enc(DIR) + '&id=' + enc(sessId));
|
|
8082
7908
|
if (!data.errors?.length) {
|
|
8083
|
-
drawer.innerHTML = `<div class="err-drawer-head"><span>No error details found</span><button class="err-close" onclick="closeErrDrawer('${esc(sessId)}')"
|
|
7909
|
+
drawer.innerHTML = `<div class="err-drawer-head"><span>No error details found</span><button class="err-close" onclick="closeErrDrawer('${esc(sessId)}')">✕</button></div>`;
|
|
8084
7910
|
return;
|
|
8085
7911
|
}
|
|
8086
7912
|
const items = data.errors.map(e => `<div class="err-item">${esc(e.text)}</div>`).join('');
|
|
8087
|
-
drawer.innerHTML = `<div class="err-drawer-head"><span>${data.errors.length} error${data.errors.length !== 1 ? 's' : ''}</span><button class="err-close" onclick="closeErrDrawer('${esc(sessId)}')"
|
|
7913
|
+
drawer.innerHTML = `<div class="err-drawer-head"><span>${data.errors.length} error${data.errors.length !== 1 ? 's' : ''}</span><button class="err-close" onclick="closeErrDrawer('${esc(sessId)}')">✕</button></div>
|
|
8088
7914
|
<div class="err-drawer-body">${items}</div>`;
|
|
8089
7915
|
} catch (err) {
|
|
8090
|
-
drawer.innerHTML = `<div class="err-drawer-head"><span>Could not load: ${esc(err.message)}</span><button class="err-close" onclick="closeErrDrawer('${esc(sessId)}')"
|
|
7916
|
+
drawer.innerHTML = `<div class="err-drawer-head"><span>Could not load: ${esc(err.message)}</span><button class="err-close" onclick="closeErrDrawer('${esc(sessId)}')">✕</button></div>`;
|
|
8091
7917
|
}
|
|
8092
7918
|
}
|
|
8093
7919
|
|
|
@@ -8121,7 +7947,7 @@ function buildCostHistogram() {
|
|
|
8121
7947
|
const counts = new Array(BUCKETS).fill(0);
|
|
8122
7948
|
for (const c of costs) { const i = Math.min(BUCKETS - 1, Math.floor((c - minC) / bucketSize)); counts[i]++; }
|
|
8123
7949
|
const maxCount = Math.max(1, ...counts);
|
|
8124
|
-
const fmt = v => v < 0.01 ?
|
|
7950
|
+
const fmt = v => v < 0.01 ? v.toFixed(4) : v < 1 ? '$' + v.toFixed(2) : '$' + v.toFixed(1);
|
|
8125
7951
|
const bars = counts.map((n, i) => {
|
|
8126
7952
|
const h = Math.max(2, Math.round((n / maxCount) * 46));
|
|
8127
7953
|
const lo = minC + i * bucketSize; const hi = lo + bucketSize;
|
|
@@ -8187,25 +8013,26 @@ function esc(s) {
|
|
|
8187
8013
|
|
|
8188
8014
|
function relTime(ts) {
|
|
8189
8015
|
if (!ts) return '';
|
|
8190
|
-
const
|
|
8191
|
-
const diff = Date.now() - abs;
|
|
8016
|
+
const diff = Date.now() - (typeof ts === 'number' ? ts : Number(ts) || new Date(ts).getTime() || 0);
|
|
8192
8017
|
const s = Math.floor(diff / 1000);
|
|
8193
8018
|
if (s < 5) return 'now';
|
|
8194
|
-
if (s < 60) return s + 's
|
|
8019
|
+
if (s < 60) return s + 's';
|
|
8195
8020
|
const m = Math.floor(s / 60);
|
|
8196
|
-
if (m < 60) return m + 'm
|
|
8021
|
+
if (m < 60) return m + 'm';
|
|
8197
8022
|
const h = Math.floor(m / 60);
|
|
8198
|
-
if (h < 24) return h + 'h
|
|
8199
|
-
|
|
8200
|
-
|
|
8201
|
-
|
|
8202
|
-
|
|
8203
|
-
|
|
8204
|
-
|
|
8023
|
+
if (h < 24) return h + 'h';
|
|
8024
|
+
return Math.floor(h / 24) + 'd';
|
|
8025
|
+
}
|
|
8026
|
+
|
|
8027
|
+
function shortPath(p) {
|
|
8028
|
+
if (!p) return '';
|
|
8029
|
+
const parts = p.replace(/\\/g, '/').split('/').filter(Boolean);
|
|
8030
|
+
if (parts.length <= 2) return p;
|
|
8031
|
+
return '…/' + parts.slice(-2).join('/');
|
|
8205
8032
|
}
|
|
8206
8033
|
|
|
8207
8034
|
function fmtDur(ms) {
|
|
8208
|
-
if (
|
|
8035
|
+
if (ms > 0 && ms < 1000) return '<1s';
|
|
8209
8036
|
const s = Math.floor(ms / 1000);
|
|
8210
8037
|
if (s < 60) return s + 's';
|
|
8211
8038
|
const m = Math.floor(s / 60);
|
|
@@ -8402,7 +8229,7 @@ function renderMgOverview() {
|
|
|
8402
8229
|
topEl.innerHTML = prSorted.map(n => `
|
|
8403
8230
|
<div class="mg-bar-row">
|
|
8404
8231
|
<div class="mg-bar-lbl" title="${esc(n.label)}">${esc(mgShortLabel(n.label))}</div>
|
|
8405
|
-
<div class="mg-bar-track"
|
|
8232
|
+
<div class="mg-bar-track"><div class="mg-bar-fill" style="width:${maxPR ? Math.round((n.score/maxPR)*100) : 0}%"></div></div>
|
|
8406
8233
|
<div class="mg-bar-val">${n.score.toExponential(1)}</div>
|
|
8407
8234
|
</div>`).join('');
|
|
8408
8235
|
}
|
|
@@ -8427,7 +8254,7 @@ function renderMgOverview() {
|
|
|
8427
8254
|
typeEl.innerHTML = typeEntries.map(([t, c]) => `
|
|
8428
8255
|
<div class="mg-bar-row">
|
|
8429
8256
|
<div class="mg-bar-lbl">${esc(t)}</div>
|
|
8430
|
-
<div class="mg-bar-track"
|
|
8257
|
+
<div class="mg-bar-track"><div class="mg-bar-fill mg" style="width:${Math.round((c/maxTC)*100)}%"></div></div>
|
|
8431
8258
|
<div class="mg-bar-val">${c}</div>
|
|
8432
8259
|
</div>`).join('');
|
|
8433
8260
|
}
|
|
@@ -8479,11 +8306,9 @@ async function mgRebuild() {
|
|
|
8479
8306
|
try {
|
|
8480
8307
|
const res = await fetch('/api/monograph-build?dir=' + enc(DIR), { method: 'POST' });
|
|
8481
8308
|
if (!res.ok) throw new Error('HTTP ' + res.status);
|
|
8482
|
-
showToast('Rebuilding', 'Knowledge graph rebuild started', 'info');
|
|
8483
8309
|
_mgLoaded = false;
|
|
8484
8310
|
await loadMonograph();
|
|
8485
|
-
|
|
8486
|
-
} catch (e) { showToast('Error', e.message, 'err'); }
|
|
8311
|
+
} catch (_) {}
|
|
8487
8312
|
btn.disabled = false; btn.textContent = 'REBUILD';
|
|
8488
8313
|
}
|
|
8489
8314
|
|
|
@@ -8610,16 +8435,14 @@ function mgRunClient(id) {
|
|
|
8610
8435
|
} else if (id === 'pagerank') {
|
|
8611
8436
|
// Proper power-iteration PageRank (d=0.85, 50 iterations)
|
|
8612
8437
|
const prMap = mgComputePageRank(g);
|
|
8613
|
-
const
|
|
8438
|
+
const ranked = nodes.map(n => {
|
|
8614
8439
|
const k = n.id || n.name || n.label || '';
|
|
8615
8440
|
return { label: n.label || n.name || k, score: prMap[k] || 0 };
|
|
8616
|
-
}).sort((a, b) => b.score - a.score);
|
|
8617
|
-
const ranked = allRanked.slice(0, 20);
|
|
8441
|
+
}).sort((a, b) => b.score - a.score).slice(0, 20);
|
|
8618
8442
|
const maxScore = ranked.length ? ranked[0].score : 1;
|
|
8619
8443
|
html = `<table class="mg-table"><thead><tr><th>#</th><th>Node</th><th>PageRank</th><th style="width:180px">Weight</th></tr></thead><tbody>` +
|
|
8620
8444
|
ranked.map((r, i) => `<tr><td>${i+1}</td><td title="${esc(r.label)}">${esc(mgShortLabel(r.label))}</td><td>${r.score.toExponential(3)}</td><td><div class="mg-bar-track" style="height:8px"><div class="mg-bar-fill" style="width:${maxScore ? Math.round((r.score/maxScore)*100) : 0}%"></div></div></td></tr>`).join('') +
|
|
8621
|
-
`</tbody></table
|
|
8622
|
-
(allRanked.length > 20 ? `<div style="font-size:11px;color:var(--text-xs);margin-top:4px;text-align:right">Showing 20 of ${allRanked.length} nodes</div>` : '');
|
|
8445
|
+
`</tbody></table>`;
|
|
8623
8446
|
|
|
8624
8447
|
} else if (id === 'deadcode') {
|
|
8625
8448
|
const isolated = nodes.filter(n => (degMap[n.id || n.name || n.label || ''] || 0) === 0);
|
|
@@ -8651,8 +8474,7 @@ function mgRunClient(id) {
|
|
|
8651
8474
|
<div class="mg-kv-card"><div class="mkv-k">Components</div><div class="mkv-v">${comps.length}</div></div>
|
|
8652
8475
|
<div class="mg-kv-card"><div class="mkv-k">Largest</div><div class="mkv-v">${comps[0] ? comps[0].length : 0}</div></div>
|
|
8653
8476
|
</div>` +
|
|
8654
|
-
top5.map((c, i) => `<div style="margin-bottom:8px"><div style="font-size:11px;color:var(--text-xs);margin-bottom:3px">Component ${i+1} — ${c.length} nodes</div><div style="font-size:11px;font-family:var(--mono);color:var(--text-mid)">${c.slice(0,5).map(esc).join(', ')}${c.length > 5 ? ` … +${c.length-5} more` : ''}</div></div>`).join('')
|
|
8655
|
-
(comps.length > 5 ? `<div style="font-size:11px;color:var(--text-xs);margin-top:4px">Showing 5 of ${comps.length} components</div>` : '');
|
|
8477
|
+
top5.map((c, i) => `<div style="margin-bottom:8px"><div style="font-size:11px;color:var(--text-xs);margin-bottom:3px">Component ${i+1} — ${c.length} nodes</div><div style="font-size:11px;font-family:var(--mono);color:var(--text-mid)">${c.slice(0,5).map(esc).join(', ')}${c.length > 5 ? ` … +${c.length-5} more` : ''}</div></div>`).join('');
|
|
8656
8478
|
|
|
8657
8479
|
} else if (id === 'topo') {
|
|
8658
8480
|
const inDeg = {}; const adj = {};
|
|
@@ -8729,16 +8551,14 @@ function mgRunClient(id) {
|
|
|
8729
8551
|
|
|
8730
8552
|
} else if (id === 'betweenness') {
|
|
8731
8553
|
const bc = mgApproxBetweenness(g);
|
|
8732
|
-
const
|
|
8733
|
-
const bcRanked = bcAll.slice(0,20);
|
|
8554
|
+
const bcRanked = Object.entries(bc).sort((a,b) => b[1]-a[1]).slice(0,20);
|
|
8734
8555
|
const maxBC = bcRanked.length ? bcRanked[0][1] : 1;
|
|
8735
8556
|
if (!bcRanked.length || maxBC === 0) { html = '<div class="loading-txt">Not enough graph data for betweenness</div>'; }
|
|
8736
8557
|
else {
|
|
8737
8558
|
html = `<div style="font-size:11px;color:var(--text-xs);margin-bottom:8px">Approximate betweenness centrality (sample-based BFS). High score = architectural bridge.</div>
|
|
8738
8559
|
<table class="mg-table"><thead><tr><th>#</th><th>Node</th><th>Score</th><th style="width:160px">Weight</th></tr></thead><tbody>` +
|
|
8739
8560
|
bcRanked.map(([k, v], i) => `<tr><td>${i+1}</td><td title="${esc(k)}">${esc(mgShortLabel(k))}</td><td>${v.toFixed(1)}</td><td><div class="mg-bar-track" style="height:8px"><div class="mg-bar-fill mg" style="width:${Math.round((v/maxBC)*100)}%"></div></div></td></tr>`).join('') +
|
|
8740
|
-
`</tbody></table
|
|
8741
|
-
(bcAll.length > 20 ? `<div style="font-size:11px;color:var(--text-xs);margin-top:4px;text-align:right">Showing 20 of ${bcAll.length} nodes</div>` : '');
|
|
8561
|
+
`</tbody></table>`;
|
|
8742
8562
|
}
|
|
8743
8563
|
|
|
8744
8564
|
} else if (id === 'jaccard') {
|
|
@@ -8861,7 +8681,7 @@ function mgRenderAnalysisResult(toolId, text) {
|
|
|
8861
8681
|
return `<div class="mgr-ranked-row">
|
|
8862
8682
|
<span class="mgr-ranked-num">${r.rank}</span>
|
|
8863
8683
|
<span class="mgr-ranked-name" title="${h(r.name)}">${h(r.name)}</span>
|
|
8864
|
-
<div class="mgr-ranked-bar-wrap"
|
|
8684
|
+
<div class="mgr-ranked-bar-wrap"><div class="mgr-ranked-bar-fill" style="width:${pct}%"></div></div>
|
|
8865
8685
|
<span class="mgr-ranked-val">${h(String(r.val))}</span>
|
|
8866
8686
|
</div>`;
|
|
8867
8687
|
}).join('')}</div>`;
|
|
@@ -8991,7 +8811,7 @@ function mgRenderAnalysisResult(toolId, text) {
|
|
|
8991
8811
|
return `<div class="mgr-ranked-row">
|
|
8992
8812
|
<span class="mgr-ranked-num">${r.rank}</span>
|
|
8993
8813
|
<span class="mgr-ranked-name" title="${h(r.path)}">${h(name)}</span>
|
|
8994
|
-
<div class="mgr-ranked-bar-wrap"
|
|
8814
|
+
<div class="mgr-ranked-bar-wrap"><div class="mgr-ranked-bar-fill" style="width:${pct}%"></div></div>
|
|
8995
8815
|
<span class="mgr-ranked-val">${r.lines.toLocaleString()}</span>
|
|
8996
8816
|
</div>`;
|
|
8997
8817
|
}).join('')}</div>`;
|
|
@@ -9039,13 +8859,6 @@ async function mgRunServer(id) {
|
|
|
9039
8859
|
}
|
|
9040
8860
|
|
|
9041
8861
|
// ── QUERY TAB ──────────────────────────────────────────────
|
|
9042
|
-
// Click any query result box to copy its text content
|
|
9043
|
-
document.addEventListener('click', e => {
|
|
9044
|
-
const el = e.target.closest('.mg-query-result');
|
|
9045
|
-
if (!el || !el.textContent.trim()) return;
|
|
9046
|
-
navigator.clipboard.writeText(el.textContent).then(() => showToast('Copied', 'Query result copied', 'ok')).catch(() => {});
|
|
9047
|
-
});
|
|
9048
|
-
|
|
9049
8862
|
function mgQuerySearch(q) {
|
|
9050
8863
|
const res = document.getElementById('mg-q-search-result');
|
|
9051
8864
|
if (!q.trim()) { res.style.display = 'none'; return; }
|
|
@@ -9240,7 +9053,7 @@ function mgRenderExport() {
|
|
|
9240
9053
|
<div class="mg-export-card">
|
|
9241
9054
|
<div class="mec-name">${esc(x.name)}</div>
|
|
9242
9055
|
<div class="mec-desc">${esc(x.desc)}</div>
|
|
9243
|
-
<button class="btn"
|
|
9056
|
+
<button class="btn" onclick="mgExport('${esc(x.id)}')" style="margin-top:auto">EXPORT</button>
|
|
9244
9057
|
</div>`).join('');
|
|
9245
9058
|
}
|
|
9246
9059
|
|
|
@@ -9504,8 +9317,8 @@ function mgRenderWiki() {
|
|
|
9504
9317
|
// type pills
|
|
9505
9318
|
const types = [...new Set(nodes.map(n => n.type || n.kind || 'unknown'))].sort();
|
|
9506
9319
|
const pillsEl = document.getElementById('mg-wiki-pills');
|
|
9507
|
-
pillsEl.innerHTML = `<span class="mg-pill active" data-type="all"
|
|
9508
|
-
types.map(t => `<span class="mg-pill" data-type="${esc(t)}"
|
|
9320
|
+
pillsEl.innerHTML = `<span class="mg-pill active" data-type="all" onclick="mgWikiFilterType('all')">All</span>` +
|
|
9321
|
+
types.map(t => `<span class="mg-pill" data-type="${esc(t)}" onclick="mgWikiFilterType('${esc(t)}')">${esc(t)}</span>`).join('');
|
|
9509
9322
|
|
|
9510
9323
|
mgRenderWikiList('');
|
|
9511
9324
|
}
|
|
@@ -9576,11 +9389,9 @@ function mgWikiSearchDebounced(q) {
|
|
|
9576
9389
|
apiFetch('/api/monograph-wiki-search?q=' + enc(q) + '&dir=' + enc(DIR))
|
|
9577
9390
|
.then(d => {
|
|
9578
9391
|
if (!d || !d.nodes || !d.nodes.length) return;
|
|
9579
|
-
|
|
9580
|
-
el._wikiData = serverNodes;
|
|
9581
|
-
el.innerHTML = serverNodes.map((n, idx) => {
|
|
9392
|
+
el.innerHTML = d.nodes.slice(0, 50).map(n => {
|
|
9582
9393
|
const lbl = n.label || n.name || '';
|
|
9583
|
-
return `<div class="mg-node-item"
|
|
9394
|
+
return `<div class="mg-node-item"><div class="mni-ico">${mgNodeIcon(n.type)}</div><div class="mni-lbl">${esc(lbl)}</div><div class="mni-path">${esc(mgShortPath(lbl))}</div><div class="mni-deg">${n.degree || 0}</div></div>`;
|
|
9584
9395
|
}).join('');
|
|
9585
9396
|
}).catch(() => {});
|
|
9586
9397
|
}
|
|
@@ -9605,9 +9416,9 @@ function mgWikiShowDetail(idx) {
|
|
|
9605
9416
|
</div>
|
|
9606
9417
|
${contentPreview ? `<div class="mdp-content-preview">${esc(contentPreview)}</div>` : ''}
|
|
9607
9418
|
<div class="mdp-actions">
|
|
9608
|
-
${contentPreview ? `<button class="btn" id="mg-copy-btn"
|
|
9609
|
-
<button class="btn"
|
|
9610
|
-
<button class="btn"
|
|
9419
|
+
${contentPreview ? `<button class="btn" id="mg-copy-btn" data-copy-idx="${idx}">COPY CONTENT</button>` : ''}
|
|
9420
|
+
<button class="btn" data-explain-idx="${idx}">EXPLAIN</button>
|
|
9421
|
+
<button class="btn" data-related-idx="${idx}">FIND RELATED</button>
|
|
9611
9422
|
</div>
|
|
9612
9423
|
<div class="mdp-explain-result" id="mg-explain-result" style="display:none"></div>
|
|
9613
9424
|
<div id="mg-related-result" style="display:none;margin-top:10px"></div>`;
|
|
@@ -9642,26 +9453,6 @@ async function mgWikiExplain(nodeId, btn) {
|
|
|
9642
9453
|
btn.disabled = false; btn.textContent = 'EXPLAIN';
|
|
9643
9454
|
}
|
|
9644
9455
|
|
|
9645
|
-
function mgWikiJumpToNode(nodeId) {
|
|
9646
|
-
if (!_mgGraph) return;
|
|
9647
|
-
const el = document.getElementById('mg-wiki-list');
|
|
9648
|
-
const data = el._wikiData || (_mgGraph.nodes || []);
|
|
9649
|
-
const idx = data.findIndex(n => (n.id || n.name || n.label || '') === nodeId);
|
|
9650
|
-
if (idx >= 0) {
|
|
9651
|
-
mgWikiShowDetail(idx);
|
|
9652
|
-
document.getElementById('mg-wiki-detail')?.scrollIntoView({ behavior:'smooth', block:'nearest' });
|
|
9653
|
-
} else {
|
|
9654
|
-
// node not in current filtered list — search for it and jump
|
|
9655
|
-
const searchEl = document.getElementById('mg-wiki-search');
|
|
9656
|
-
if (searchEl) { searchEl.value = nodeId; mgRenderWikiList(nodeId); }
|
|
9657
|
-
setTimeout(() => {
|
|
9658
|
-
const newData = document.getElementById('mg-wiki-list')?._wikiData || [];
|
|
9659
|
-
const newIdx = newData.findIndex(n => (n.id || n.name || n.label || '') === nodeId);
|
|
9660
|
-
if (newIdx >= 0) mgWikiShowDetail(newIdx);
|
|
9661
|
-
}, 100);
|
|
9662
|
-
}
|
|
9663
|
-
}
|
|
9664
|
-
|
|
9665
9456
|
function mgWikiFindRelated(nodeId) {
|
|
9666
9457
|
const el = document.getElementById('mg-related-result');
|
|
9667
9458
|
if (!el || !_mgGraph) return;
|
|
@@ -9677,12 +9468,11 @@ function mgWikiFindRelated(nodeId) {
|
|
|
9677
9468
|
hop1.forEach(v => { (adj[v] ? [...adj[v]] : []).forEach(w => { if (w !== nodeId && !hop1.includes(w)) hop2.add(w); }); });
|
|
9678
9469
|
const related = [...hop1, ...hop2].slice(0, 20);
|
|
9679
9470
|
if (!related.length) { el.innerHTML = '<div class="loading-txt">No related nodes found</div>'; el.style.display = 'block'; return; }
|
|
9680
|
-
el.innerHTML = `<div style="font-size:11px;color:var(--text-xs);margin-bottom:6px">Related nodes (2-hop neighborhood)
|
|
9471
|
+
el.innerHTML = `<div style="font-size:11px;color:var(--text-xs);margin-bottom:6px">Related nodes (2-hop neighborhood)</div>` +
|
|
9681
9472
|
`<ul class="mg-node-list">` + related.map(k => {
|
|
9682
9473
|
const n = (_mgGraph.nodes || []).find(x => (x.id || x.name || x.label || '') === k);
|
|
9683
9474
|
const lbl = n ? (n.label || n.name || k) : k;
|
|
9684
|
-
|
|
9685
|
-
return `<li style="cursor:pointer;display:flex;align-items:center;gap:6px;padding:2px 0" onclick="mgWikiJumpToNode(${JSON.stringify(k).replace(/"/g, '"')})" title="Open ${esc(lbl)}"><span style="opacity:0.6">${typeIcon}</span>${esc(lbl)}</li>`;
|
|
9475
|
+
return `<li>${esc(lbl)}</li>`;
|
|
9686
9476
|
}).join('') + `</ul>`;
|
|
9687
9477
|
el.style.display = 'block';
|
|
9688
9478
|
}
|
|
@@ -9698,19 +9488,18 @@ async function mgRebuildDocs() {
|
|
|
9698
9488
|
btn.disabled = true; btn.textContent = 'BUILDING…';
|
|
9699
9489
|
try {
|
|
9700
9490
|
await fetch('/api/monograph-build-docs?dir=' + enc(DIR), { method:'POST' });
|
|
9701
|
-
showToast('Building', 'Documentation build started…', 'info');
|
|
9702
9491
|
// poll until done (max 60 attempts = ~2 minutes)
|
|
9703
9492
|
let polls = 0;
|
|
9704
9493
|
const poll = async () => {
|
|
9705
|
-
if (++polls > 60) { btn.disabled = false; btn.textContent = 'BUILD DOCS';
|
|
9494
|
+
if (++polls > 60) { btn.disabled = false; btn.textContent = 'BUILD DOCS'; return; }
|
|
9706
9495
|
try {
|
|
9707
9496
|
const d = await apiFetch('/api/monograph-build-docs-status?dir=' + enc(DIR));
|
|
9708
|
-
if (d && d.done) { btn.disabled = false; btn.textContent = 'BUILD DOCS'; mgWikiRefresh();
|
|
9497
|
+
if (d && d.done) { btn.disabled = false; btn.textContent = 'BUILD DOCS'; mgWikiRefresh(); return; }
|
|
9709
9498
|
} catch (_) {}
|
|
9710
9499
|
setTimeout(poll, 2000);
|
|
9711
9500
|
};
|
|
9712
9501
|
poll();
|
|
9713
|
-
} catch (e) { btn.disabled = false; btn.textContent = 'BUILD DOCS';
|
|
9502
|
+
} catch (e) { btn.disabled = false; btn.textContent = 'BUILD DOCS'; }
|
|
9714
9503
|
}
|
|
9715
9504
|
|
|
9716
9505
|
// ── memory CRUD ────────────────────────────────────────────
|
|
@@ -9748,41 +9537,6 @@ async function loadMemoriesTab() {
|
|
|
9748
9537
|
}
|
|
9749
9538
|
}
|
|
9750
9539
|
|
|
9751
|
-
function filterMemList(q) {
|
|
9752
|
-
const lq = (q || '').toLowerCase();
|
|
9753
|
-
let totalVisible = 0;
|
|
9754
|
-
document.querySelectorAll('#mem-list-pane .mem-item').forEach(el => {
|
|
9755
|
-
const text = (el.textContent || '').toLowerCase();
|
|
9756
|
-
const show = !lq || text.includes(lq);
|
|
9757
|
-
el.style.display = show ? '' : 'none';
|
|
9758
|
-
if (show) totalVisible++;
|
|
9759
|
-
});
|
|
9760
|
-
document.querySelectorAll('#mem-list-pane .mem-type-hdr').forEach(hdr => {
|
|
9761
|
-
let next = hdr.nextElementSibling;
|
|
9762
|
-
let anyVisible = false;
|
|
9763
|
-
while (next && !next.classList.contains('mem-type-hdr')) {
|
|
9764
|
-
if (next.style.display !== 'none') anyVisible = true;
|
|
9765
|
-
next = next.nextElementSibling;
|
|
9766
|
-
}
|
|
9767
|
-
hdr.style.display = anyVisible ? '' : 'none';
|
|
9768
|
-
});
|
|
9769
|
-
const list = document.getElementById('mem-list-pane');
|
|
9770
|
-
const noRes = document.getElementById('mem-filter-noresult');
|
|
9771
|
-
if (lq && totalVisible === 0 && _memFiles.length > 0) {
|
|
9772
|
-
if (!noRes) {
|
|
9773
|
-
const el = document.createElement('div');
|
|
9774
|
-
el.id = 'mem-filter-noresult';
|
|
9775
|
-
el.style.cssText = 'padding:12px 14px;color:var(--text-lo);font-size:12px';
|
|
9776
|
-
el.textContent = 'No memories match "' + q.slice(0, 30) + '"';
|
|
9777
|
-
list.appendChild(el);
|
|
9778
|
-
} else {
|
|
9779
|
-
noRes.textContent = 'No memories match "' + q.slice(0, 30) + '"';
|
|
9780
|
-
}
|
|
9781
|
-
} else if (noRes) {
|
|
9782
|
-
noRes.remove();
|
|
9783
|
-
}
|
|
9784
|
-
}
|
|
9785
|
-
|
|
9786
9540
|
function _renderMemList() {
|
|
9787
9541
|
const list = document.getElementById('mem-list-pane');
|
|
9788
9542
|
if (!list) return;
|
|
@@ -9798,8 +9552,7 @@ function _renderMemList() {
|
|
|
9798
9552
|
const col = _MEM_COLORS[type] || _MEM_COLOR_FALLBACK;
|
|
9799
9553
|
const fname = f.filename || f.name || '?';
|
|
9800
9554
|
const active = _selMemFilename === fname ? ' active' : '';
|
|
9801
|
-
|
|
9802
|
-
return '<div class="mem-item' + active + '" data-filename="' + esc(fname) + '" onclick="selectMem(this.dataset.filename)" title="' + esc(memTitle) + '">' +
|
|
9555
|
+
return '<div class="mem-item' + active + '" data-filename="' + esc(fname) + '" onclick="selectMem(this.dataset.filename)">' +
|
|
9803
9556
|
'<span class="mem-type-dot" style="background:' + esc(col) + ';flex-shrink:0"></span>' +
|
|
9804
9557
|
'<span class="mem-item-name">' + esc(f.name || fname.replace('.md', '')) + '</span>' +
|
|
9805
9558
|
'</div>';
|
|
@@ -9818,17 +9571,17 @@ function selectMem(filename) {
|
|
|
9818
9571
|
const rawBody = f.body || f.content || '';
|
|
9819
9572
|
const bodyHtml = rawBody
|
|
9820
9573
|
.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
|
9821
|
-
.replace(/\*\*(.+?)\*\*/g, (_, g) => '<strong>' + g + '</strong>')
|
|
9822
|
-
.replace(/^#{1,3} (.+)/gm, (_, g) => '<div style="font-weight:600;color:var(--text-hi);margin:6px 0 2px">' + g + '</div>')
|
|
9823
|
-
.replace(/^- (.+)/gm, (_, g) => '<div style="padding-left:12px">• ' + g + '</div>');
|
|
9574
|
+
.replace(/\*\*(.+?)\*\*/g, (_, g) => '<strong>' + esc(g) + '</strong>')
|
|
9575
|
+
.replace(/^#{1,3} (.+)/gm, (_, g) => '<div style="font-weight:600;color:var(--text-hi);margin:6px 0 2px">' + esc(g) + '</div>')
|
|
9576
|
+
.replace(/^- (.+)/gm, (_, g) => '<div style="padding-left:12px">• ' + esc(g) + '</div>');
|
|
9824
9577
|
const srcBadge = f.source === 'backend'
|
|
9825
9578
|
? '<span class="mem-badge" style="background:var(--text-lo)22;color:var(--text-lo);margin-left:6px" title="Stored in the AgentDB backend store, not as a file">backend</span>'
|
|
9826
9579
|
: '';
|
|
9827
9580
|
const actions = (f.readonly || f.source === 'backend')
|
|
9828
9581
|
? '<div class="mem-actions"><span style="font-size:11px;color:var(--text-lo)">Read-only — stored in the backend memory store (not a file)</span></div>'
|
|
9829
9582
|
: '<div class="mem-actions">' +
|
|
9830
|
-
'<button class="btn"
|
|
9831
|
-
'<button class="btn"
|
|
9583
|
+
'<button class="btn" onclick="openEditMemModal(' + JSON.stringify(filename) + ')">✎ Edit</button>' +
|
|
9584
|
+
'<button class="btn" style="color:var(--red);border-color:var(--red)" onclick="deleteMem(' + JSON.stringify(filename) + ')">✕ Delete</button>' +
|
|
9832
9585
|
'</div>';
|
|
9833
9586
|
detail.innerHTML =
|
|
9834
9587
|
'<span class="mem-badge" style="background:' + col + '22;color:' + col + '">' + esc(f.type || '?') + '</span>' + srcBadge +
|
|
@@ -9938,7 +9691,7 @@ function _renderSwarmRunList() {
|
|
|
9938
9691
|
'<span class="swarm-topo-pill">' + esc(topo) + '</span>' +
|
|
9939
9692
|
(live ? '<span class="swarm-live">⬤ LIVE</span>' : '') +
|
|
9940
9693
|
'</div>' +
|
|
9941
|
-
'<div style="font-size:11px;color:var(--text-lo);margin-top:2px">' + (r.agentCount || 0) + ' agents · ' +
|
|
9694
|
+
'<div style="font-size:11px;color:var(--text-lo);margin-top:2px">' + (r.agentCount || 0) + ' agents · ' + relTime(r.startedAt || r.created_at) + '</div>' +
|
|
9942
9695
|
'</div>';
|
|
9943
9696
|
}).join('');
|
|
9944
9697
|
}
|
|
@@ -9951,7 +9704,7 @@ async function selectSwarmRun(idx) {
|
|
|
9951
9704
|
if (!detail) return;
|
|
9952
9705
|
detail.innerHTML =
|
|
9953
9706
|
'<div style="margin-bottom:10px">' +
|
|
9954
|
-
'<div style="font-size:13px;font-weight:600;color:var(--text-hi)"
|
|
9707
|
+
'<div style="font-size:13px;font-weight:600;color:var(--text-hi)">' + esc((run.swarmId || run.id || '—').toString().slice(0, 14)) + '</div>' +
|
|
9955
9708
|
'<div style="font-size:11px;color:var(--text-lo);margin-top:3px">' + esc(run.topology || '—') + ' · ' + esc(run.consensus || '—') + ' · ' + (run.agentCount || 0) + ' agents</div>' +
|
|
9956
9709
|
'</div>' +
|
|
9957
9710
|
'<canvas id="swarm-topo-canvas" style="width:100%;max-width:380px;height:190px;display:block;border:1px solid var(--border);border-radius:6px;margin-bottom:12px"></canvas>' +
|
|
@@ -10028,16 +9781,14 @@ function _renderSwarmAgents(run) {
|
|
|
10028
9781
|
if (!el) return;
|
|
10029
9782
|
const agents = run.agents || [];
|
|
10030
9783
|
if (!agents.length) { el.innerHTML = ''; return; }
|
|
10031
|
-
const shownA = agents.slice(0, 15);
|
|
10032
9784
|
el.innerHTML = '<div class="m-group-title" style="margin-bottom:4px">Agents</div>' +
|
|
10033
|
-
|
|
9785
|
+
agents.slice(0, 15).map(a =>
|
|
10034
9786
|
'<div style="display:flex;gap:10px;padding:3px 0;font-size:11px;border-bottom:1px solid var(--border)">' +
|
|
10035
|
-
'<span style="color:var(--text-lo);font-family:var(--mono);min-width:70px"
|
|
9787
|
+
'<span style="color:var(--text-lo);font-family:var(--mono);min-width:70px">' + esc((a.id || '').toString().slice(0, 10)) + '</span>' +
|
|
10036
9788
|
'<span style="color:var(--text-hi)">' + esc(a.type || a.role || 'worker') + '</span>' +
|
|
10037
9789
|
'<span style="margin-left:auto;color:var(--text-xs)">' + esc(a.status || '—') + '</span>' +
|
|
10038
9790
|
'</div>'
|
|
10039
|
-
).join('')
|
|
10040
|
-
(agents.length > 15 ? '<div style="font-size:11px;color:var(--text-xs);padding:3px 0">+' + (agents.length - 15) + ' more agents</div>' : '');
|
|
9791
|
+
).join('');
|
|
10041
9792
|
}
|
|
10042
9793
|
|
|
10043
9794
|
async function _loadSwarmEvents(swarmId) {
|
|
@@ -10048,13 +9799,11 @@ async function _loadSwarmEvents(swarmId) {
|
|
|
10048
9799
|
const data = await apiFetch('/api/swarm-events?agentId=' + enc(swarmId) + '&dir=' + enc(DIR));
|
|
10049
9800
|
const events = Array.isArray(data) ? data : (data.events || []);
|
|
10050
9801
|
if (!events.length) { el.innerHTML = ''; return; }
|
|
10051
|
-
|
|
10052
|
-
|
|
10053
|
-
shownEv.map(e =>
|
|
9802
|
+
el.innerHTML = '<div class="m-group-title" style="margin-bottom:3px">Events</div>' +
|
|
9803
|
+
events.slice(-40).map(e =>
|
|
10054
9804
|
'<div style="color:var(--text-lo);padding:2px 0;border-bottom:1px solid var(--border)">' +
|
|
10055
|
-
|
|
10056
|
-
|
|
10057
|
-
'<span title="' + esc((e.message || e.data || '').toString()) + '">' + esc((e.message || e.data || '').toString().slice(0, 70)) + ((e.message || e.data || '').toString().length > 70 ? '…' : '') + '</span>' +
|
|
9805
|
+
esc(relTime(e.ts || e.timestamp)) + ' <span style="color:var(--text-mid)">' + esc(e.type || e.kind || '?') + '</span> ' +
|
|
9806
|
+
esc((e.message || e.data || '').toString().slice(0, 70)) +
|
|
10058
9807
|
'</div>'
|
|
10059
9808
|
).join('');
|
|
10060
9809
|
el.scrollTop = el.scrollHeight;
|
|
@@ -10118,25 +9867,23 @@ function _renderChunks(list) {
|
|
|
10118
9867
|
grid.innerHTML = '<div class="empty">No chunks indexed.<br><span style="font-size:11px;color:var(--text-xs)">Run /monomind:understand to build the index.</span></div>';
|
|
10119
9868
|
return;
|
|
10120
9869
|
}
|
|
10121
|
-
|
|
10122
|
-
grid.innerHTML = shown.map(c => {
|
|
9870
|
+
grid.innerHTML = list.slice(0, 200).map(c => {
|
|
10123
9871
|
const src = (c.source || c.file || '').split('/').slice(-2).join('/');
|
|
10124
9872
|
const excerpt = (c.content || c.text || c.body || '').slice(0, 220);
|
|
10125
9873
|
const ns = c.namespace || c.type || '';
|
|
10126
|
-
const chunkId = JSON.stringify(c.id || c.path || c.source || '')
|
|
10127
|
-
const chunkContent = JSON.stringify(c.content || c.text || c.body || '')
|
|
10128
|
-
const chunkSrc = JSON.stringify(src)
|
|
9874
|
+
const chunkId = JSON.stringify(c.id || c.path || c.source || '');
|
|
9875
|
+
const chunkContent = JSON.stringify(c.content || c.text || c.body || '');
|
|
9876
|
+
const chunkSrc = JSON.stringify(src);
|
|
10129
9877
|
return '<div class="chunk-card" data-search="' + esc((src + ' ' + excerpt + ' ' + ns).toLowerCase()) + '">' +
|
|
10130
9878
|
'<div class="chunk-src">' + esc(src || '—') + '</div>' +
|
|
10131
|
-
'<div class="chunk-excerpt"
|
|
9879
|
+
'<div class="chunk-excerpt">' + esc(excerpt) + '</div>' +
|
|
10132
9880
|
'<div class="chunk-footer">' +
|
|
10133
9881
|
(ns ? '<span class="chunk-ns">' + esc(ns) + '</span>' : '') +
|
|
10134
|
-
'<button class="btn"
|
|
10135
|
-
'<button class="btn"
|
|
9882
|
+
'<button class="btn" style="margin-left:auto;font-size:10px;padding:1px 7px" onclick="openChunkEdit(' + chunkId + ',' + chunkContent + ',' + chunkSrc + ')">✎ Edit</button>' +
|
|
9883
|
+
'<button class="btn" style="font-size:10px;padding:1px 7px;color:var(--red);border-color:var(--red)" onclick="deleteChunk(' + chunkId + ')">✕</button>' +
|
|
10136
9884
|
'</div>' +
|
|
10137
9885
|
'</div>';
|
|
10138
|
-
}).join('')
|
|
10139
|
-
(list.length > 200 ? '<div style="font-size:11px;color:var(--text-xs);padding:8px 0;text-align:center">Showing 200 of ' + list.length + ' chunks — use the filter to narrow results</div>' : '');
|
|
9886
|
+
}).join('');
|
|
10140
9887
|
}
|
|
10141
9888
|
|
|
10142
9889
|
function openChunkEdit(id, content, srcLabel) {
|
|
@@ -10210,33 +9957,9 @@ async function buildKnowledgeDocs() {
|
|
|
10210
9957
|
|
|
10211
9958
|
function filterChunks(q) {
|
|
10212
9959
|
const lq = q.toLowerCase();
|
|
10213
|
-
|
|
10214
|
-
|
|
10215
|
-
cards.forEach(el => {
|
|
10216
|
-
const show = !lq || (el.dataset.search || '').includes(lq);
|
|
10217
|
-
el.style.display = show ? '' : 'none';
|
|
10218
|
-
if (show) visible++;
|
|
9960
|
+
document.querySelectorAll('#chunks-grid .chunk-card').forEach(el => {
|
|
9961
|
+
el.style.display = (!lq || (el.dataset.search || '').includes(lq)) ? '' : 'none';
|
|
10219
9962
|
});
|
|
10220
|
-
const countEl = document.getElementById('chunk-count-val');
|
|
10221
|
-
if (countEl && lq) countEl.textContent = visible + ' / ' + cards.length;
|
|
10222
|
-
else if (countEl) countEl.textContent = cards.length.toLocaleString();
|
|
10223
|
-
// zero-results empty state
|
|
10224
|
-
const grid = document.getElementById('chunks-grid');
|
|
10225
|
-
const noRes = document.getElementById('chunks-filter-noresult');
|
|
10226
|
-
if (lq && visible === 0 && cards.length > 0) {
|
|
10227
|
-
if (!noRes) {
|
|
10228
|
-
const el = document.createElement('div');
|
|
10229
|
-
el.id = 'chunks-filter-noresult';
|
|
10230
|
-
el.className = 'empty';
|
|
10231
|
-
el.style.cssText = 'padding:20px 0;font-size:13px';
|
|
10232
|
-
el.textContent = 'No chunks match "' + q.slice(0, 40) + '"';
|
|
10233
|
-
grid.appendChild(el);
|
|
10234
|
-
} else {
|
|
10235
|
-
noRes.textContent = 'No chunks match "' + q.slice(0, 40) + '"';
|
|
10236
|
-
}
|
|
10237
|
-
} else if (noRes) {
|
|
10238
|
-
noRes.remove();
|
|
10239
|
-
}
|
|
10240
9963
|
}
|
|
10241
9964
|
|
|
10242
9965
|
async function deleteChunk(id) {
|
|
@@ -10263,28 +9986,8 @@ async function loadAgentGraphTab() {
|
|
|
10263
9986
|
const bar = document.getElementById('ag-summary-bar');
|
|
10264
9987
|
if (bar) bar.innerHTML = '<div class="loading-txt">Loading…</div>';
|
|
10265
9988
|
try {
|
|
10266
|
-
const
|
|
10267
|
-
|
|
10268
|
-
const sesNodes = (raw.nodes || []).filter(n => n.type === 'session');
|
|
10269
|
-
const agNodes = (raw.nodes || []).filter(n => n.type === 'agenttype');
|
|
10270
|
-
const sessions = sesNodes.map(n => ({
|
|
10271
|
-
id: n.id,
|
|
10272
|
-
file: n.id,
|
|
10273
|
-
turns: n.turns || 0,
|
|
10274
|
-
toolCount: n.totalTools || 0,
|
|
10275
|
-
spawnCount: Object.values(n.agentSpawns || {}).reduce((a, b) => a + b, 0),
|
|
10276
|
-
cost: n.cost || 0,
|
|
10277
|
-
agentTypes: n.agentSpawns || {},
|
|
10278
|
-
tools: n.toolCounts || {},
|
|
10279
|
-
}));
|
|
10280
|
-
_agData = {
|
|
10281
|
-
sessions,
|
|
10282
|
-
sessionCount: sessions.length,
|
|
10283
|
-
agentTypes: agNodes.length,
|
|
10284
|
-
totalSpawns: agNodes.reduce((a, n) => a + (n.totalSpawns || 0), 0),
|
|
10285
|
-
totalToolCalls: sesNodes.reduce((a, n) => a + (n.totalTools || 0), 0),
|
|
10286
|
-
totalCost: sesNodes.reduce((a, n) => a + (n.cost || 0), 0),
|
|
10287
|
-
};
|
|
9989
|
+
const data = await apiFetch('/api/graph?dir=' + enc(DIR));
|
|
9990
|
+
_agData = data;
|
|
10288
9991
|
_renderAgSummary();
|
|
10289
9992
|
_renderAgSessList();
|
|
10290
9993
|
} catch (e) {
|
|
@@ -10315,27 +10018,15 @@ function _renderAgSessList() {
|
|
|
10315
10018
|
const el = document.getElementById('ag-sess-list');
|
|
10316
10019
|
if (!_agData || !el) return;
|
|
10317
10020
|
const sessions = _agData.sessions || [];
|
|
10318
|
-
if (!sessions.length) { el.innerHTML = '<div class="empty" style="font-size:12px">No sessions
|
|
10319
|
-
el.innerHTML =
|
|
10320
|
-
'<
|
|
10321
|
-
|
|
10322
|
-
|
|
10323
|
-
'
|
|
10324
|
-
|
|
10325
|
-
|
|
10326
|
-
|
|
10327
|
-
'<div style="font-size:10px;color:var(--text-lo);margin-top:1px">' + (s.spawnCount || 0) + ' spawns · ' + (s.toolCount || 0) + ' tools</div>' +
|
|
10328
|
-
'</div>'
|
|
10329
|
-
).join('') +
|
|
10330
|
-
'</div>';
|
|
10331
|
-
}
|
|
10332
|
-
|
|
10333
|
-
function filterAgSessions(q) {
|
|
10334
|
-
const lq = (q || '').toLowerCase();
|
|
10335
|
-
document.querySelectorAll('#ag-sess-rows .sess-row').forEach(el => {
|
|
10336
|
-
const sid = (el.dataset.sid || '').toLowerCase();
|
|
10337
|
-
el.style.display = (!lq || sid.includes(lq)) ? '' : 'none';
|
|
10338
|
-
});
|
|
10021
|
+
if (!sessions.length) { el.innerHTML = '<div class="empty" style="font-size:12px">No sessions</div>'; return; }
|
|
10022
|
+
el.innerHTML = sessions.map((s, i) =>
|
|
10023
|
+
'<div class="sess-row" style="margin-bottom:4px" onclick="selectAgSession(' + i + ')">' +
|
|
10024
|
+
'<div class="sr-top">' +
|
|
10025
|
+
'<div class="sr-prompt" style="font-size:11px">' + esc((s.id || s.file || '').slice(-16)) + '</div>' +
|
|
10026
|
+
'</div>' +
|
|
10027
|
+
'<div style="font-size:10px;color:var(--text-lo);margin-top:1px">' + (s.spawnCount || 0) + ' spawns · ' + (s.toolCount || 0) + ' tools</div>' +
|
|
10028
|
+
'</div>'
|
|
10029
|
+
).join('');
|
|
10339
10030
|
}
|
|
10340
10031
|
|
|
10341
10032
|
function selectAgSession(idx) {
|
|
@@ -10344,10 +10035,8 @@ function selectAgSession(idx) {
|
|
|
10344
10035
|
document.querySelectorAll('#ag-sess-list .sess-row').forEach((el, i) => el.classList.toggle('active', i === idx));
|
|
10345
10036
|
const detail = document.getElementById('ag-detail');
|
|
10346
10037
|
if (!detail) return;
|
|
10347
|
-
const
|
|
10348
|
-
const
|
|
10349
|
-
const agArr = agAllArr.slice(0, 12);
|
|
10350
|
-
const toolArr = toolAllArr.slice(0, 15);
|
|
10038
|
+
const agArr = Object.entries(s.agentTypes || {}).sort((a, b) => b[1] - a[1]).slice(0, 12);
|
|
10039
|
+
const toolArr = Object.entries(s.tools || {}).sort((a, b) => b[1] - a[1]).slice(0, 15);
|
|
10351
10040
|
const maxAg = agArr.length ? Math.max(...agArr.map(x => x[1])) : 1;
|
|
10352
10041
|
const maxTool = toolArr.length ? Math.max(...toolArr.map(x => x[1])) : 1;
|
|
10353
10042
|
detail.innerHTML =
|
|
@@ -10357,20 +10046,20 @@ function selectAgSession(idx) {
|
|
|
10357
10046
|
'<span>Tools: <b>' + (s.toolCount || 0) + '</b></span>' +
|
|
10358
10047
|
(s.cost != null ? '<span style="color:var(--accent)">$' + Number(s.cost).toFixed(4) + '</span>' : '') +
|
|
10359
10048
|
'</div>' +
|
|
10360
|
-
(agArr.length ? '<div class="m-group-title" style="margin-bottom:5px">Agent Types
|
|
10049
|
+
(agArr.length ? '<div class="m-group-title" style="margin-bottom:5px">Agent Types</div>' +
|
|
10361
10050
|
agArr.map(function(entry) {
|
|
10362
10051
|
var type = entry[0], count = entry[1];
|
|
10363
10052
|
return '<div style="display:flex;align-items:center;gap:8px;padding:3px 0;font-size:12px">' +
|
|
10364
|
-
'<div style="width:110px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--text-hi)"
|
|
10053
|
+
'<div style="width:110px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--text-hi)">' + esc(type) + '</div>' +
|
|
10365
10054
|
'<div style="flex:1;height:7px;background:var(--border);border-radius:2px;overflow:hidden"><div style="width:' + Math.round(count/maxAg*100) + '%;height:100%;background:var(--accent);border-radius:2px"></div></div>' +
|
|
10366
10055
|
'<div style="width:22px;text-align:right;color:var(--text-lo);font-size:11px;font-family:var(--mono)">' + count + '</div>' +
|
|
10367
10056
|
'</div>';
|
|
10368
10057
|
}).join('') : '') +
|
|
10369
|
-
(toolArr.length ? '<div class="m-group-title" style="margin-bottom:5px;margin-top:14px">Top Tools
|
|
10058
|
+
(toolArr.length ? '<div class="m-group-title" style="margin-bottom:5px;margin-top:14px">Top Tools</div>' +
|
|
10370
10059
|
toolArr.map(function(entry) {
|
|
10371
10060
|
var tool = entry[0], count = entry[1];
|
|
10372
10061
|
return '<div style="display:flex;align-items:center;gap:8px;padding:3px 0;font-size:12px">' +
|
|
10373
|
-
'<div style="width:110px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--text-hi);font-family:var(--mono);font-size:11px"
|
|
10062
|
+
'<div style="width:110px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--text-hi);font-family:var(--mono);font-size:11px">' + esc(tool) + '</div>' +
|
|
10374
10063
|
'<div style="flex:1;height:7px;background:var(--border);border-radius:2px;overflow:hidden"><div style="width:' + Math.round(count/maxTool*100) + '%;height:100%;background:oklch(62% 0.12 195);border-radius:2px"></div></div>' +
|
|
10375
10064
|
'<div style="width:22px;text-align:right;color:var(--text-lo);font-size:11px;font-family:var(--mono)">' + count + '</div>' +
|
|
10376
10065
|
'</div>';
|
|
@@ -10434,10 +10123,10 @@ function mmSwitchTab(tab) {
|
|
|
10434
10123
|
const body = document.getElementById('mm-body');
|
|
10435
10124
|
if (!body) return;
|
|
10436
10125
|
if (tab === 'orgs') mmRenderOrgs(body);
|
|
10437
|
-
else if (tab === 'skills')
|
|
10438
|
-
else if (tab === 'loops') mmRenderLoops(body);
|
|
10126
|
+
else if (tab === 'skills') mmRenderSkills(body);
|
|
10127
|
+
else if (tab === 'loops') { mmRenderLoops(body); body.insertAdjacentHTML('afterbegin', '<button class="btn" onclick="mmSwitchTab(\'loops\')" style="margin-bottom:10px;font-size:11px">↺ Refresh</button>'); }
|
|
10439
10128
|
else if (tab === 'createorg') mmRenderCreateOrg(body);
|
|
10440
|
-
else if (tab === 'metrics') mmRenderMetrics(body);
|
|
10129
|
+
else if (tab === 'metrics') { mmRenderMetrics(body); body.insertAdjacentHTML('afterbegin', '<button class="btn" onclick="mmSwitchTab(\'metrics\')" style="margin-bottom:10px;font-size:11px">↺ Refresh</button>'); }
|
|
10441
10130
|
else if (tab === 'graph') mmRenderGraph(body);
|
|
10442
10131
|
}
|
|
10443
10132
|
|
|
@@ -10446,9 +10135,9 @@ function mmRenderOrgs(body) {
|
|
|
10446
10135
|
if (!orgs.length) { body.innerHTML = '<div class="empty">No orgs found. Run /mastermind:createorg to create one.</div>'; return; }
|
|
10447
10136
|
body.innerHTML = orgs.map(o => {
|
|
10448
10137
|
const running = o.running;
|
|
10449
|
-
return `<div class="mm-skill-item" onclick="closeMastermind();v2SelectOrg(${JSON.stringify(o.name)
|
|
10138
|
+
return `<div class="mm-skill-item" onclick="closeMastermind();v2SelectOrg(${JSON.stringify(o.name)});switchView('orgs')">
|
|
10450
10139
|
<span class="mm-skill-name">${esc(o.name)}</span>
|
|
10451
|
-
<span class="mm-skill-desc"
|
|
10140
|
+
<span class="mm-skill-desc">${esc((o.goal || '').slice(0, 60))} ${running ? '⬤ LIVE' : ''}</span>
|
|
10452
10141
|
</div>`;
|
|
10453
10142
|
}).join('');
|
|
10454
10143
|
}
|
|
@@ -10457,8 +10146,8 @@ let _mmSkillFilter = '';
|
|
|
10457
10146
|
function mmRenderSkills(body) {
|
|
10458
10147
|
const q = _mmSkillFilter.toLowerCase();
|
|
10459
10148
|
const filtered = q ? _MM_SKILLS_CATALOG.filter(s => s.name.toLowerCase().includes(q) || s.desc.toLowerCase().includes(q)) : _MM_SKILLS_CATALOG;
|
|
10460
|
-
body.innerHTML = `<div class="filter-bar" style="margin-bottom:12px"><input class="filter-input"
|
|
10461
|
-
filtered.map(s => `<div class="mm-skill-item"
|
|
10149
|
+
body.innerHTML = `<div class="filter-bar" style="margin-bottom:12px"><input class="filter-input" type="text" placeholder="Search skills…" value="${esc(_mmSkillFilter)}" oninput="_mmSkillFilter=this.value;mmRenderSkills(document.getElementById('mm-body'))"></div>` +
|
|
10150
|
+
filtered.map(s => `<div class="mm-skill-item" onclick="navigator.clipboard.writeText(${JSON.stringify(s.name)}).then(()=>showToast('Copied',${JSON.stringify(s.name)},'ok'))">
|
|
10462
10151
|
<span class="mm-skill-name">${esc(s.name)}</span>
|
|
10463
10152
|
<span class="mm-skill-desc">${esc(s.desc)}</span>
|
|
10464
10153
|
</div>`).join('');
|
|
@@ -10470,66 +10159,30 @@ async function mmRenderLoops(body) {
|
|
|
10470
10159
|
const data = await apiFetch('/api/loops?dir=' + enc(DIR));
|
|
10471
10160
|
const loops = Array.isArray(data) ? data : (data.loops || []);
|
|
10472
10161
|
if (!loops.length) { body.innerHTML = '<div class="empty">No active loops. Use /mastermind:autodev --tillend to start one.</div>'; return; }
|
|
10473
|
-
|
|
10474
|
-
|
|
10475
|
-
|
|
10476
|
-
|
|
10477
|
-
|
|
10478
|
-
const
|
|
10479
|
-
|
|
10480
|
-
|
|
10481
|
-
|
|
10482
|
-
|
|
10483
|
-
const
|
|
10484
|
-
const
|
|
10485
|
-
const
|
|
10486
|
-
const
|
|
10487
|
-
const
|
|
10488
|
-
const isExplicitlyActiveMm = l.status === 'running' || l.status === 'waiting' || l.status === 'active';
|
|
10489
|
-
const _STALE = 2 * 60 * 60 * 1000;
|
|
10490
|
-
const isOverdueMm = !l.status?.startsWith('hil') && !isExplicitlyActiveMm &&
|
|
10491
|
-
nextAt > 0 && nextAt <= Date.now();
|
|
10492
|
-
const isStaledActiveMm = isExplicitlyActiveMm && nextAt > 0 && (Date.now() - nextAt) > _STALE;
|
|
10493
|
-
const isFinishedMm = isOverdueMm || isStaledActiveMm ||
|
|
10494
|
-
(!isExplicitlyActiveMm && maxReps > 0 && curRep >= maxReps) ||
|
|
10495
|
-
l.status === 'finished' || l.status === 'done' ||
|
|
10496
|
-
l.status === 'complete' || l.status === 'completed' || l.status === 'expired';
|
|
10497
|
-
const running = !isFinishedMm && l.status !== 'stopped' && l.status !== 'paused';
|
|
10498
|
-
const _mmLp = (function(_l) {
|
|
10499
|
-
if (_l.command) return { userPrompt: _l.prompt || '', command: _l.command };
|
|
10500
|
-
const full = _l.prompt || '';
|
|
10501
|
-
const cmdM = full.match(/^(\/\S+)/);
|
|
10502
|
-
if (!cmdM) return { userPrompt: full, command: '' };
|
|
10503
|
-
const tokens = full.slice(cmdM[1].length).trim().split(/\s+/);
|
|
10504
|
-
let ti = 0;
|
|
10505
|
-
while (ti < tokens.length && tokens[ti] && tokens[ti].startsWith('--')) {
|
|
10506
|
-
ti++;
|
|
10507
|
-
if (ti < tokens.length && tokens[ti] && !tokens[ti].startsWith('--')) ti++;
|
|
10508
|
-
}
|
|
10509
|
-
return { userPrompt: tokens.slice(ti).join(' '), command: cmdM[1] };
|
|
10510
|
-
})(l);
|
|
10511
|
-
const name = (l.name || _mmLp.userPrompt || _mmLp.command || 'loop').slice(0, 60);
|
|
10512
|
-
const ms = nextAt ? nextAt - Date.now() : 0;
|
|
10513
|
-
const cdown = ms > 0 ? (Math.floor(ms/60000) + 'm ' + Math.floor((ms%60000)/1000) + 's') : '';
|
|
10514
|
-
const intervalMm = fmtInterval(l.interval || l.schedule);
|
|
10515
|
-
const runCount = isTillendMm
|
|
10516
|
-
? `run ${curRep} / ∞${maxReps ? ' (cap: ' + maxReps + ')' : ''}`
|
|
10517
|
-
: (maxReps > 0 ? `run ${curRep} / ${maxReps}` : (curRep ? `run ${curRep}` : ''));
|
|
10518
|
-
const statusLabel = isHilMm ? '⚠ HIL' : (running ? 'active' : (isFinishedMm ? 'done' : 'stopped'));
|
|
10519
|
-
const statusColor = isHilMm ? 'oklch(75% 0.16 60)' : '';
|
|
10520
|
-
const typeIco = isTillendMm ? '∞ ' : '↺ ';
|
|
10521
|
-
const _sMs = l.startedAt ? (typeof l.startedAt === 'number' ? l.startedAt : new Date(l.startedAt).getTime()) : 0;
|
|
10522
|
-
const ageMs = _sMs > 0 && _sMs < Date.now() ? Date.now() - _sMs : 0;
|
|
10523
|
-
const ageStr = ageMs > 0 ? fmtDur(ageMs) : '';
|
|
10524
|
-
const metaParts = [intervalMm, ageStr ? 'running ' + ageStr : '', cdown ? 'next in ' + cdown : '', runCount].filter(Boolean).join(' · ');
|
|
10162
|
+
body.innerHTML = loops.map(l => {
|
|
10163
|
+
const maxReps = l.maxReps || 0;
|
|
10164
|
+
const curRep = l.currentRep || 0;
|
|
10165
|
+
const isTillend = !maxReps || l.loopType === 'tillend' || String(l.command || '').includes('--tillend');
|
|
10166
|
+
const nextAt = l.nextRunAt ? parseInt(l.nextRunAt, 10) : 0;
|
|
10167
|
+
const isExplicitlyActive = l.status === 'running' || l.status === 'waiting' || l.status === 'active';
|
|
10168
|
+
const isOverdue = !isExplicitlyActive && nextAt > 0 && nextAt <= Date.now();
|
|
10169
|
+
const isStaledActive = isExplicitlyActive && nextAt > 0 && (Date.now() - nextAt) > LOOP_STALE_MS;
|
|
10170
|
+
const isFinished = (maxReps > 0 && curRep >= maxReps) || ['finished','done','complete','completed','expired'].includes(l.status) || isOverdue || isStaledActive;
|
|
10171
|
+
const isHil = l.status === 'hil:pending';
|
|
10172
|
+
const running = !isFinished && !isHil && l.status !== 'stopped' && l.status !== 'paused';
|
|
10173
|
+
const name = (l.name || l.prompt || 'loop').split('--')[0].trim().slice(0, 60);
|
|
10174
|
+
const ms = nextAt ? nextAt - Date.now() : 0;
|
|
10175
|
+
const cdown = ms > 0 ? (Math.floor(ms/60000) + 'm ' + Math.floor((ms%60000)/1000) + 's') : (running ? 'running' : isFinished ? 'done' : 'stopped');
|
|
10176
|
+
const typeBadge = isTillend ? '∞' : '↺';
|
|
10525
10177
|
return `<div style="padding:10px 0;border-bottom:1px solid var(--border)">
|
|
10526
10178
|
<div style="display:flex;align-items:center;gap:8px">
|
|
10527
|
-
<span style="font-size:13px;color:var(--
|
|
10528
|
-
<span
|
|
10529
|
-
${
|
|
10179
|
+
<span style="font-size:13px;color:var(--accent)">${typeBadge}</span>
|
|
10180
|
+
<span style="font-size:13px;color:var(--text-hi);flex:1">${esc(name)}</span>
|
|
10181
|
+
${isHil ? '<span style="color:oklch(78% 0.18 80);font-size:10px">⚠ HIL</span>' : ''}
|
|
10182
|
+
<span class="ss-pill ${running ? 'on' : ''}">${running ? 'active' : isFinished ? 'done' : 'stopped'}</span>
|
|
10183
|
+
${(running || isHil) ? `<button class="btn" style="font-size:10px;color:var(--red);border-color:var(--red)" onclick="stopLoop(event,${JSON.stringify(l.id||l.name||'')});mmSwitchTab('loops')">■ Stop</button>` : ''}
|
|
10530
10184
|
</div>
|
|
10531
|
-
<div style="font-size:11px;color:var(--text-lo);margin-top:4px;font-family:var(--mono)">${esc(
|
|
10532
|
-
${isHilMm ? `<div style="font-size:10px;color:oklch(75% 0.16 60);margin-top:4px">⚠ Waiting for human response — check HIL file to resume</div>` : ''}
|
|
10185
|
+
<div style="font-size:11px;color:var(--text-lo);margin-top:4px;font-family:var(--mono)">${esc(fmtInterval(l.interval||l.schedule||''))} ${cdown ? '· ' + cdown : ''} ${curRep != null ? '· run ' + curRep + (isTillend ? '/∞' : maxReps ? '/'+maxReps : '') : ''}</div>
|
|
10533
10186
|
</div>`;
|
|
10534
10187
|
}).join('');
|
|
10535
10188
|
} catch (e) { body.innerHTML = '<div class="empty">Failed: ' + esc(e.message) + '</div>'; }
|
|
@@ -10543,17 +10196,17 @@ function mmRenderCreateOrg(body) {
|
|
|
10543
10196
|
<div><div class="le-lbl">Org Name</div><input id="mco-name" class="filter-input" placeholder="my-team"></div>
|
|
10544
10197
|
<div><div class="le-lbl">Goal</div><input id="mco-goal" class="filter-input" placeholder="Build and ship features autonomously"></div>
|
|
10545
10198
|
<div><div class="le-lbl">Topology</div>
|
|
10546
|
-
<select id="mco-topo" class="filter-input"
|
|
10199
|
+
<select id="mco-topo" class="filter-input" style="cursor:pointer">
|
|
10547
10200
|
${topos.map(t => `<option>${t}</option>`).join('')}
|
|
10548
10201
|
</select>
|
|
10549
10202
|
</div>
|
|
10550
10203
|
<div><div class="le-lbl">Adapter</div>
|
|
10551
|
-
<select id="mco-adapter" class="filter-input"
|
|
10204
|
+
<select id="mco-adapter" class="filter-input" style="cursor:pointer">
|
|
10552
10205
|
${adapters.map(a => `<option>${a}</option>`).join('')}
|
|
10553
10206
|
</select>
|
|
10554
10207
|
</div>
|
|
10555
|
-
<div><div class="le-lbl">Max Agents</div><input id="mco-agents" class="filter-input" type="number" value="8" min="1" max="50"
|
|
10556
|
-
<button class="btn"
|
|
10208
|
+
<div><div class="le-lbl">Max Agents</div><input id="mco-agents" class="filter-input" type="number" value="8" min="1" max="50"></div>
|
|
10209
|
+
<button class="btn" style="width:fit-content;color:oklch(65% 0.16 295);border-color:oklch(65% 0.16 295)" onclick="mmGenerateOrgCmd()">Generate CLI Command</button>
|
|
10557
10210
|
<div id="mco-cmd-out" style="display:none;font-family:var(--mono);font-size:12px;background:var(--bg);border:1px solid var(--border);border-radius:6px;padding:10px;word-break:break-all;cursor:pointer;color:var(--text-hi)" title="Click to copy" onclick="navigator.clipboard.writeText(this.textContent).then(()=>showToast('Copied','','ok'))"></div>
|
|
10558
10211
|
</div>`;
|
|
10559
10212
|
}
|
|
@@ -10597,7 +10250,6 @@ async function mmRenderMetrics(body) {
|
|
|
10597
10250
|
</div>
|
|
10598
10251
|
<div class="m-group-title" style="margin-bottom:8px">Swarm</div>
|
|
10599
10252
|
<div style="display:flex;justify-content:space-between;padding:4px 0;font-size:12px;border-bottom:1px solid var(--border)"><span style="color:var(--text-hi)">Topology</span><span style="color:${swarmTopo !== 'IDLE' ? 'var(--green)' : 'var(--text-lo)'};font-family:var(--mono)">${esc(swarmTopo)}</span></div>
|
|
10600
|
-
<div style="margin-top:12px;display:flex;justify-content:flex-end"><button class="btn" title="Refresh metrics" style="font-size:10px" onclick="mmRenderMetrics(document.getElementById('mm-body'))">↺ Refresh</button></div>
|
|
10601
10253
|
`;
|
|
10602
10254
|
} catch (e) { body.innerHTML = '<div class="empty">Failed: ' + esc(e.message) + '</div>'; }
|
|
10603
10255
|
}
|
|
@@ -10623,13 +10275,13 @@ async function mmRenderGraph(body) {
|
|
|
10623
10275
|
<div class="chunk-stat"><div class="chunk-stat-val">${Object.keys(nodeTypes).length}</div><div class="chunk-stat-lbl">Types</div></div>
|
|
10624
10276
|
</div>
|
|
10625
10277
|
${topNodes.length ? `<div class="m-group-title" style="margin-bottom:6px">God Nodes</div>
|
|
10626
|
-
${topNodes.map(n => `<div style="display:flex;justify-content:space-between;padding:4px 0;font-size:12px;border-bottom:1px solid var(--border)
|
|
10278
|
+
${topNodes.map(n => `<div style="display:flex;justify-content:space-between;padding:4px 0;font-size:12px;border-bottom:1px solid var(--border)">
|
|
10627
10279
|
<span style="color:var(--text-hi);font-family:var(--mono);font-size:11px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:70%">${esc(n.name || n.id || '—')}</span>
|
|
10628
|
-
<span style="color:var(--text-lo);font-family:var(--mono);font-size:11px">${n.degree ?? '—'}
|
|
10280
|
+
<span style="color:var(--text-lo);font-family:var(--mono);font-size:11px">${n.degree ?? '—'}</span>
|
|
10629
10281
|
</div>`).join('')}` : ''}
|
|
10630
10282
|
<div style="margin-top:14px;display:flex;gap:8px;flex-wrap:wrap">
|
|
10631
|
-
<button class="btn"
|
|
10632
|
-
<button class="btn"
|
|
10283
|
+
<button class="btn" onclick="switchView('monograph');closeMastermind()">Open Monograph →</button>
|
|
10284
|
+
<button class="btn" onclick="fetch('/api/monograph-build?dir='+enc(DIR),{method:'POST'}).then(()=>showToast('Building','Graph rebuild started','ok'))">↺ Rebuild Graph</button>
|
|
10633
10285
|
</div>
|
|
10634
10286
|
`;
|
|
10635
10287
|
} catch (e) { body.innerHTML = '<div class="empty">Failed: ' + esc(e.message) + '</div>'; }
|