@monoes/monomindcli 1.11.13 → 1.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/agents/generated/channel-intelligence-director.md +87 -0
- package/.claude/agents/generated/chief-growth-officer.md +88 -0
- package/.claude/agents/generated/content-seo-strategist.md +90 -0
- package/.claude/agents/generated/developer-community-strategist.md +91 -0
- package/.claude/agents/generated/outreach-partnership-strategist.md +90 -0
- package/.claude/agents/generated/social-media-strategist.md +91 -0
- package/.claude/agents/generated/video-visual-strategist.md +90 -0
- package/.claude/commands/mastermind/master.md +1 -1
- package/.claude/helpers/auto-memory-hook.mjs +13 -4
- package/.claude/helpers/control-start.cjs +5 -0
- package/.claude/helpers/event-logger.cjs +114 -0
- package/.claude/helpers/handlers/adr-draft-handler.cjs +19 -5
- package/.claude/helpers/handlers/agent-start-handler.cjs +13 -4
- package/.claude/helpers/handlers/compact-handler.cjs +2 -0
- package/.claude/helpers/handlers/edit-handler.cjs +1 -1
- package/.claude/helpers/handlers/gates-handler.cjs +3 -0
- package/.claude/helpers/handlers/graph-status-handler.cjs +14 -8
- package/.claude/helpers/handlers/loops-status-handler.cjs +5 -2
- package/.claude/helpers/handlers/route-handler.cjs +13 -6
- package/.claude/helpers/handlers/session-handler.cjs +11 -4
- package/.claude/helpers/handlers/session-restore-handler.cjs +21 -11
- package/.claude/helpers/handlers/task-handler.cjs +13 -5
- package/.claude/helpers/intelligence.cjs +7 -2
- package/.claude/helpers/loop-tracker.cjs +15 -3
- package/.claude/helpers/memory.cjs +6 -1
- package/.claude/helpers/router.cjs +5 -2
- package/.claude/helpers/session.cjs +2 -0
- package/.claude/helpers/statusline.cjs +10 -2
- package/.claude/helpers/utils/micro-agents.cjs +20 -4
- package/.claude/skills/mastermind/_protocol.md +25 -15
- package/.claude/skills/mastermind/architect.md +3 -3
- package/.claude/skills/mastermind/autodev.md +4 -2
- package/.claude/skills/mastermind/idea.md +10 -0
- package/.claude/skills/mastermind/ops.md +3 -3
- package/.claude/skills/mastermind/runorg.md +153 -86
- package/dist/src/agents/registry-builder.d.ts.map +1 -1
- package/dist/src/agents/registry-builder.js +2 -0
- package/dist/src/agents/registry-builder.js.map +1 -1
- package/dist/src/autopilot-state.d.ts.map +1 -1
- package/dist/src/autopilot-state.js +10 -5
- package/dist/src/autopilot-state.js.map +1 -1
- package/dist/src/benchmarks/benchmark-runner.d.ts.map +1 -1
- package/dist/src/benchmarks/benchmark-runner.js +13 -0
- package/dist/src/benchmarks/benchmark-runner.js.map +1 -1
- package/dist/src/benchmarks/metric-evaluators.d.ts.map +1 -1
- package/dist/src/benchmarks/metric-evaluators.js +20 -9
- package/dist/src/benchmarks/metric-evaluators.js.map +1 -1
- package/dist/src/browser/actions.d.ts.map +1 -1
- package/dist/src/browser/actions.js +10 -3
- package/dist/src/browser/actions.js.map +1 -1
- package/dist/src/browser/browser.d.ts.map +1 -1
- package/dist/src/browser/browser.js +12 -2
- package/dist/src/browser/browser.js.map +1 -1
- package/dist/src/browser/cdp.d.ts.map +1 -1
- package/dist/src/browser/cdp.js +21 -3
- package/dist/src/browser/cdp.js.map +1 -1
- package/dist/src/browser/har.d.ts.map +1 -1
- package/dist/src/browser/har.js +27 -5
- package/dist/src/browser/har.js.map +1 -1
- package/dist/src/commands/agent.d.ts.map +1 -1
- package/dist/src/commands/agent.js +11 -8
- package/dist/src/commands/agent.js.map +1 -1
- package/dist/src/commands/analyze.d.ts.map +1 -1
- package/dist/src/commands/analyze.js +36 -21
- package/dist/src/commands/analyze.js.map +1 -1
- package/dist/src/commands/autopilot.d.ts.map +1 -1
- package/dist/src/commands/autopilot.js +12 -4
- package/dist/src/commands/autopilot.js.map +1 -1
- package/dist/src/commands/benchmark.d.ts.map +1 -1
- package/dist/src/commands/benchmark.js +51 -8
- package/dist/src/commands/benchmark.js.map +1 -1
- package/dist/src/commands/browse.d.ts.map +1 -1
- package/dist/src/commands/browse.js +5 -2
- package/dist/src/commands/browse.js.map +1 -1
- package/dist/src/commands/claims.d.ts.map +1 -1
- package/dist/src/commands/claims.js +29 -11
- package/dist/src/commands/claims.js.map +1 -1
- package/dist/src/commands/cleanup.d.ts.map +1 -1
- package/dist/src/commands/cleanup.js +25 -5
- package/dist/src/commands/cleanup.js.map +1 -1
- package/dist/src/commands/config.d.ts.map +1 -1
- package/dist/src/commands/config.js +15 -7
- package/dist/src/commands/config.js.map +1 -1
- package/dist/src/commands/daemon.d.ts.map +1 -1
- package/dist/src/commands/daemon.js +6 -0
- package/dist/src/commands/daemon.js.map +1 -1
- package/dist/src/commands/deployment.d.ts.map +1 -1
- package/dist/src/commands/deployment.js +34 -19
- package/dist/src/commands/deployment.js.map +1 -1
- package/dist/src/commands/doctor.d.ts.map +1 -1
- package/dist/src/commands/doctor.js +97 -20
- package/dist/src/commands/doctor.js.map +1 -1
- package/dist/src/commands/guidance.d.ts.map +1 -1
- package/dist/src/commands/guidance.js +15 -2
- package/dist/src/commands/guidance.js.map +1 -1
- package/dist/src/commands/hive-mind.d.ts.map +1 -1
- package/dist/src/commands/hive-mind.js +37 -14
- package/dist/src/commands/hive-mind.js.map +1 -1
- package/dist/src/commands/hooks.d.ts.map +1 -1
- package/dist/src/commands/hooks.js +42 -25
- package/dist/src/commands/hooks.js.map +1 -1
- package/dist/src/commands/init.d.ts.map +1 -1
- package/dist/src/commands/init.js +9 -4
- package/dist/src/commands/init.js.map +1 -1
- package/dist/src/commands/issues.d.ts.map +1 -1
- package/dist/src/commands/issues.js +29 -26
- package/dist/src/commands/issues.js.map +1 -1
- package/dist/src/commands/mcp.d.ts.map +1 -1
- package/dist/src/commands/mcp.js +11 -5
- package/dist/src/commands/mcp.js.map +1 -1
- package/dist/src/commands/memory.d.ts.map +1 -1
- package/dist/src/commands/memory.js +10 -0
- package/dist/src/commands/memory.js.map +1 -1
- package/dist/src/commands/migrate.js +5 -5
- package/dist/src/commands/migrate.js.map +1 -1
- package/dist/src/commands/monograph.d.ts.map +1 -1
- package/dist/src/commands/monograph.js +18 -5
- package/dist/src/commands/monograph.js.map +1 -1
- package/dist/src/commands/monovector/backup.d.ts.map +1 -1
- package/dist/src/commands/monovector/backup.js +8 -2
- package/dist/src/commands/monovector/backup.js.map +1 -1
- package/dist/src/commands/monovector/benchmark.d.ts.map +1 -1
- package/dist/src/commands/monovector/benchmark.js +20 -7
- package/dist/src/commands/monovector/benchmark.js.map +1 -1
- package/dist/src/commands/monovector/import.d.ts.map +1 -1
- package/dist/src/commands/monovector/import.js +15 -0
- package/dist/src/commands/monovector/import.js.map +1 -1
- package/dist/src/commands/monovector/migrate.d.ts.map +1 -1
- package/dist/src/commands/monovector/migrate.js +4 -1
- package/dist/src/commands/monovector/migrate.js.map +1 -1
- package/dist/src/commands/monovector/optimize.d.ts.map +1 -1
- package/dist/src/commands/monovector/optimize.js +11 -0
- package/dist/src/commands/monovector/optimize.js.map +1 -1
- package/dist/src/commands/monovector/setup.d.ts.map +1 -1
- package/dist/src/commands/monovector/setup.js +11 -1
- package/dist/src/commands/monovector/setup.js.map +1 -1
- package/dist/src/commands/neural.js +1 -1
- package/dist/src/commands/neural.js.map +1 -1
- package/dist/src/commands/performance.d.ts.map +1 -1
- package/dist/src/commands/performance.js +20 -7
- package/dist/src/commands/performance.js.map +1 -1
- package/dist/src/commands/platforms.d.ts.map +1 -1
- package/dist/src/commands/platforms.js +90 -8
- package/dist/src/commands/platforms.js.map +1 -1
- package/dist/src/commands/plugins.d.ts.map +1 -1
- package/dist/src/commands/plugins.js +12 -5
- package/dist/src/commands/plugins.js.map +1 -1
- package/dist/src/commands/process.d.ts.map +1 -1
- package/dist/src/commands/process.js +33 -10
- package/dist/src/commands/process.js.map +1 -1
- package/dist/src/commands/progress.d.ts.map +1 -1
- package/dist/src/commands/progress.js +5 -3
- package/dist/src/commands/progress.js.map +1 -1
- package/dist/src/commands/providers.js +5 -5
- package/dist/src/commands/providers.js.map +1 -1
- package/dist/src/commands/replay.d.ts.map +1 -1
- package/dist/src/commands/replay.js +8 -2
- package/dist/src/commands/replay.js.map +1 -1
- package/dist/src/commands/route.d.ts.map +1 -1
- package/dist/src/commands/route.js +27 -7
- package/dist/src/commands/route.js.map +1 -1
- package/dist/src/commands/security.d.ts.map +1 -1
- package/dist/src/commands/security.js +4 -0
- package/dist/src/commands/security.js.map +1 -1
- package/dist/src/commands/session.d.ts.map +1 -1
- package/dist/src/commands/session.js +12 -1
- package/dist/src/commands/session.js.map +1 -1
- package/dist/src/commands/start.d.ts.map +1 -1
- package/dist/src/commands/start.js +11 -4
- package/dist/src/commands/start.js.map +1 -1
- package/dist/src/commands/status.d.ts.map +1 -1
- package/dist/src/commands/status.js +7 -4
- package/dist/src/commands/status.js.map +1 -1
- package/dist/src/commands/swarm.d.ts.map +1 -1
- package/dist/src/commands/swarm.js +27 -13
- package/dist/src/commands/swarm.js.map +1 -1
- package/dist/src/commands/task.d.ts.map +1 -1
- package/dist/src/commands/task.js +26 -11
- package/dist/src/commands/task.js.map +1 -1
- package/dist/src/commands/tokens.d.ts.map +1 -1
- package/dist/src/commands/tokens.js +7 -2
- package/dist/src/commands/tokens.js.map +1 -1
- package/dist/src/commands/transfer-store.d.ts.map +1 -1
- package/dist/src/commands/transfer-store.js +36 -22
- package/dist/src/commands/transfer-store.js.map +1 -1
- package/dist/src/commands/update.d.ts.map +1 -1
- package/dist/src/commands/update.js +15 -3
- package/dist/src/commands/update.js.map +1 -1
- package/dist/src/commands/workflow.d.ts.map +1 -1
- package/dist/src/commands/workflow.js +39 -6
- package/dist/src/commands/workflow.js.map +1 -1
- package/dist/src/consensus/audit-writer.d.ts.map +1 -1
- package/dist/src/consensus/audit-writer.js +18 -7
- package/dist/src/consensus/audit-writer.js.map +1 -1
- package/dist/src/consensus/vote-signer.d.ts.map +1 -1
- package/dist/src/consensus/vote-signer.js +25 -8
- package/dist/src/consensus/vote-signer.js.map +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +7 -3
- package/dist/src/index.js.map +1 -1
- package/dist/src/init/executor.d.ts.map +1 -1
- package/dist/src/init/executor.js +14 -11
- package/dist/src/init/executor.js.map +1 -1
- package/dist/src/init/shared-instructions-generator.d.ts.map +1 -1
- package/dist/src/init/shared-instructions-generator.js +20 -4
- package/dist/src/init/shared-instructions-generator.js.map +1 -1
- package/dist/src/init/statusline-generator.d.ts.map +1 -1
- package/dist/src/init/statusline-generator.js +36 -15
- package/dist/src/init/statusline-generator.js.map +1 -1
- package/dist/src/mcp-tools/a2a-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/a2a-tools.js +98 -13
- package/dist/src/mcp-tools/a2a-tools.js.map +1 -1
- package/dist/src/mcp-tools/agent-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/agent-tools.js +16 -3
- package/dist/src/mcp-tools/agent-tools.js.map +1 -1
- package/dist/src/mcp-tools/analyze-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/analyze-tools.js +80 -17
- package/dist/src/mcp-tools/analyze-tools.js.map +1 -1
- package/dist/src/mcp-tools/browser-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/browser-tools.js +84 -22
- package/dist/src/mcp-tools/browser-tools.js.map +1 -1
- package/dist/src/mcp-tools/claims-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/claims-tools.js +35 -7
- package/dist/src/mcp-tools/claims-tools.js.map +1 -1
- package/dist/src/mcp-tools/config-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/config-tools.js +82 -17
- package/dist/src/mcp-tools/config-tools.js.map +1 -1
- package/dist/src/mcp-tools/coordination-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/coordination-tools.js +37 -4
- package/dist/src/mcp-tools/coordination-tools.js.map +1 -1
- package/dist/src/mcp-tools/daa-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/daa-tools.js +49 -7
- package/dist/src/mcp-tools/daa-tools.js.map +1 -1
- package/dist/src/mcp-tools/embeddings-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/embeddings-tools.js +45 -18
- package/dist/src/mcp-tools/embeddings-tools.js.map +1 -1
- package/dist/src/mcp-tools/github-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/github-tools.js +75 -25
- package/dist/src/mcp-tools/github-tools.js.map +1 -1
- package/dist/src/mcp-tools/guidance-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/guidance-tools.js +32 -10
- package/dist/src/mcp-tools/guidance-tools.js.map +1 -1
- package/dist/src/mcp-tools/hive-mind-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/hive-mind-tools.js +91 -20
- package/dist/src/mcp-tools/hive-mind-tools.js.map +1 -1
- package/dist/src/mcp-tools/hooks-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/hooks-tools.js +188 -29
- package/dist/src/mcp-tools/hooks-tools.js.map +1 -1
- package/dist/src/mcp-tools/memory-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/memory-tools.js +25 -7
- package/dist/src/mcp-tools/memory-tools.js.map +1 -1
- package/dist/src/mcp-tools/monograph-compat.d.ts.map +1 -1
- package/dist/src/mcp-tools/monograph-compat.js +11 -2
- package/dist/src/mcp-tools/monograph-compat.js.map +1 -1
- package/dist/src/mcp-tools/monograph-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/monograph-tools.js +148 -26
- package/dist/src/mcp-tools/monograph-tools.js.map +1 -1
- package/dist/src/mcp-tools/neural-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/neural-tools.js +44 -9
- package/dist/src/mcp-tools/neural-tools.js.map +1 -1
- package/dist/src/mcp-tools/performance-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/performance-tools.js +45 -10
- package/dist/src/mcp-tools/performance-tools.js.map +1 -1
- package/dist/src/mcp-tools/progress-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/progress-tools.js +7 -4
- package/dist/src/mcp-tools/progress-tools.js.map +1 -1
- package/dist/src/mcp-tools/request-tracker.d.ts.map +1 -1
- package/dist/src/mcp-tools/request-tracker.js +15 -1
- package/dist/src/mcp-tools/request-tracker.js.map +1 -1
- package/dist/src/mcp-tools/security-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/security-tools.js +61 -9
- package/dist/src/mcp-tools/security-tools.js.map +1 -1
- package/dist/src/mcp-tools/session-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/session-tools.js +45 -14
- package/dist/src/mcp-tools/session-tools.js.map +1 -1
- package/dist/src/mcp-tools/swarm-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/swarm-tools.js +15 -3
- package/dist/src/mcp-tools/swarm-tools.js.map +1 -1
- package/dist/src/mcp-tools/system-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/system-tools.js +14 -7
- package/dist/src/mcp-tools/system-tools.js.map +1 -1
- package/dist/src/mcp-tools/task-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/task-tools.js +52 -10
- package/dist/src/mcp-tools/task-tools.js.map +1 -1
- package/dist/src/mcp-tools/terminal-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/terminal-tools.js +40 -6
- package/dist/src/mcp-tools/terminal-tools.js.map +1 -1
- package/dist/src/mcp-tools/transfer-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/transfer-tools.js +37 -4
- package/dist/src/mcp-tools/transfer-tools.js.map +1 -1
- package/dist/src/mcp-tools/workflow-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/workflow-tools.js +29 -6
- package/dist/src/mcp-tools/workflow-tools.js.map +1 -1
- package/dist/src/memory/ewc-consolidation.d.ts.map +1 -1
- package/dist/src/memory/ewc-consolidation.js +26 -10
- package/dist/src/memory/ewc-consolidation.js.map +1 -1
- package/dist/src/memory/intelligence.d.ts.map +1 -1
- package/dist/src/memory/intelligence.js +80 -19
- package/dist/src/memory/intelligence.js.map +1 -1
- package/dist/src/memory/memory-bridge.d.ts.map +1 -1
- package/dist/src/memory/memory-bridge.js +21 -2
- package/dist/src/memory/memory-bridge.js.map +1 -1
- package/dist/src/memory/memory-initializer.d.ts.map +1 -1
- package/dist/src/memory/memory-initializer.js +67 -3
- package/dist/src/memory/memory-initializer.js.map +1 -1
- package/dist/src/memory/sona-optimizer.d.ts.map +1 -1
- package/dist/src/memory/sona-optimizer.js +14 -4
- package/dist/src/memory/sona-optimizer.js.map +1 -1
- package/dist/src/monovector/command-outcomes.d.ts.map +1 -1
- package/dist/src/monovector/command-outcomes.js +43 -7
- package/dist/src/monovector/command-outcomes.js.map +1 -1
- package/dist/src/monovector/coverage-router.d.ts.map +1 -1
- package/dist/src/monovector/coverage-router.js +8 -4
- package/dist/src/monovector/coverage-router.js.map +1 -1
- package/dist/src/monovector/coverage-tools.d.ts.map +1 -1
- package/dist/src/monovector/coverage-tools.js +6 -3
- package/dist/src/monovector/coverage-tools.js.map +1 -1
- package/dist/src/monovector/diff-classifier.d.ts.map +1 -1
- package/dist/src/monovector/diff-classifier.js +13 -0
- package/dist/src/monovector/diff-classifier.js.map +1 -1
- package/dist/src/monovector/route-outcomes.d.ts +2 -1
- package/dist/src/monovector/route-outcomes.d.ts.map +1 -1
- package/dist/src/monovector/route-outcomes.js +46 -4
- package/dist/src/monovector/route-outcomes.js.map +1 -1
- package/dist/src/plugins/manager.d.ts.map +1 -1
- package/dist/src/plugins/manager.js +8 -3
- package/dist/src/plugins/manager.js.map +1 -1
- package/dist/src/plugins/store/discovery.d.ts.map +1 -1
- package/dist/src/plugins/store/discovery.js +46 -2
- package/dist/src/plugins/store/discovery.js.map +1 -1
- package/dist/src/plugins/store/search.d.ts.map +1 -1
- package/dist/src/plugins/store/search.js +5 -4
- package/dist/src/plugins/store/search.js.map +1 -1
- package/dist/src/production/circuit-breaker.d.ts.map +1 -1
- package/dist/src/production/circuit-breaker.js +17 -3
- package/dist/src/production/circuit-breaker.js.map +1 -1
- package/dist/src/production/error-handler.d.ts.map +1 -1
- package/dist/src/production/error-handler.js +3 -0
- package/dist/src/production/error-handler.js.map +1 -1
- package/dist/src/production/monitoring.d.ts.map +1 -1
- package/dist/src/production/monitoring.js +20 -3
- package/dist/src/production/monitoring.js.map +1 -1
- package/dist/src/production/rate-limiter.d.ts.map +1 -1
- package/dist/src/production/rate-limiter.js +13 -4
- package/dist/src/production/rate-limiter.js.map +1 -1
- package/dist/src/production/retry.d.ts.map +1 -1
- package/dist/src/production/retry.js +17 -9
- package/dist/src/production/retry.js.map +1 -1
- package/dist/src/routing/embed-worker.js +6 -2
- package/dist/src/routing/embed-worker.js.map +1 -1
- package/dist/src/routing/embedder.d.ts.map +1 -1
- package/dist/src/routing/embedder.js +0 -0
- package/dist/src/routing/embedder.js.map +1 -1
- package/dist/src/routing/llm-caller.d.ts.map +1 -1
- package/dist/src/routing/llm-caller.js +13 -2
- package/dist/src/routing/llm-caller.js.map +1 -1
- package/dist/src/routing/route-layer-factory.d.ts.map +1 -1
- package/dist/src/routing/route-layer-factory.js +18 -3
- package/dist/src/routing/route-layer-factory.js.map +1 -1
- package/dist/src/services/claim-service.d.ts +1 -0
- package/dist/src/services/claim-service.d.ts.map +1 -1
- package/dist/src/services/claim-service.js +8 -0
- package/dist/src/services/claim-service.js.map +1 -1
- package/dist/src/services/config-file-manager.d.ts.map +1 -1
- package/dist/src/services/config-file-manager.js +14 -2
- package/dist/src/services/config-file-manager.js.map +1 -1
- package/dist/src/services/headless-worker-executor.d.ts.map +1 -1
- package/dist/src/services/headless-worker-executor.js +18 -2
- package/dist/src/services/headless-worker-executor.js.map +1 -1
- package/dist/src/services/worker-daemon.d.ts.map +1 -1
- package/dist/src/services/worker-daemon.js +53 -12
- package/dist/src/services/worker-daemon.js.map +1 -1
- package/dist/src/transfer/anonymization/index.d.ts +0 -3
- package/dist/src/transfer/anonymization/index.d.ts.map +1 -1
- package/dist/src/transfer/anonymization/index.js +16 -1
- package/dist/src/transfer/anonymization/index.js.map +1 -1
- package/dist/src/transfer/export.d.ts.map +1 -1
- package/dist/src/transfer/export.js +8 -0
- package/dist/src/transfer/export.js.map +1 -1
- package/dist/src/transfer/ipfs/upload.d.ts.map +1 -1
- package/dist/src/transfer/ipfs/upload.js +33 -3
- package/dist/src/transfer/ipfs/upload.js.map +1 -1
- package/dist/src/transfer/serialization/cfp.d.ts.map +1 -1
- package/dist/src/transfer/serialization/cfp.js +9 -3
- package/dist/src/transfer/serialization/cfp.js.map +1 -1
- package/dist/src/transfer/storage/gcs.d.ts.map +1 -1
- package/dist/src/transfer/storage/gcs.js +37 -3
- package/dist/src/transfer/storage/gcs.js.map +1 -1
- package/dist/src/transfer/store/discovery.d.ts.map +1 -1
- package/dist/src/transfer/store/discovery.js +45 -3
- package/dist/src/transfer/store/discovery.js.map +1 -1
- package/dist/src/transfer/store/download.d.ts.map +1 -1
- package/dist/src/transfer/store/download.js +5 -0
- package/dist/src/transfer/store/download.js.map +1 -1
- package/dist/src/transfer/store/publish.d.ts.map +1 -1
- package/dist/src/transfer/store/publish.js +13 -1
- package/dist/src/transfer/store/publish.js.map +1 -1
- package/dist/src/transfer/store/registry.d.ts +8 -0
- package/dist/src/transfer/store/registry.d.ts.map +1 -1
- package/dist/src/transfer/store/registry.js +30 -5
- package/dist/src/transfer/store/registry.js.map +1 -1
- package/dist/src/transfer/store/search.d.ts.map +1 -1
- package/dist/src/transfer/store/search.js +20 -5
- package/dist/src/transfer/store/search.js.map +1 -1
- package/dist/src/ui/collector.mjs +39 -5
- package/dist/src/ui/dashboard.html +926 -1268
- package/dist/src/ui/orgs.html +722 -12
- package/dist/src/ui/server.mjs +573 -134
- package/dist/src/update/checker.d.ts.map +1 -1
- package/dist/src/update/checker.js +59 -7
- package/dist/src/update/checker.js.map +1 -1
- package/dist/src/update/executor.d.ts.map +1 -1
- package/dist/src/update/executor.js +50 -3
- package/dist/src/update/executor.js.map +1 -1
- package/dist/src/update/index.d.ts.map +1 -1
- package/dist/src/update/index.js +18 -1
- package/dist/src/update/index.js.map +1 -1
- package/dist/src/update/rate-limiter.d.ts +6 -0
- package/dist/src/update/rate-limiter.d.ts.map +1 -1
- package/dist/src/update/rate-limiter.js +79 -7
- package/dist/src/update/rate-limiter.js.map +1 -1
- package/dist/src/update/validator.d.ts.map +1 -1
- package/dist/src/update/validator.js +52 -1
- package/dist/src/update/validator.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -3
- package/dist/src/ui/data/mastermind-events.jsonl +0 -59
|
@@ -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>
|
|
@@ -2603,8 +2636,7 @@ function expandGroup(el) {
|
|
|
2603
2636
|
|
|
2604
2637
|
function renderFeedEntry(ev) {
|
|
2605
2638
|
const ts = ev.ts ? relTime(ev.ts) : '';
|
|
2606
|
-
|
|
2607
|
-
let lbl = '', detail = '', lblTitle = '', id = ev.id || ev.uuid || '';
|
|
2639
|
+
let lbl = '', detail = '', id = ev.id || ev.uuid || '';
|
|
2608
2640
|
let catCls, ico;
|
|
2609
2641
|
|
|
2610
2642
|
if (ev.kind === 'tool') {
|
|
@@ -2614,21 +2646,20 @@ function renderFeedEntry(ev) {
|
|
|
2614
2646
|
} else {
|
|
2615
2647
|
ico = '↵'; catCls = 'cat-user';
|
|
2616
2648
|
const t = (ev.text || '').trim();
|
|
2617
|
-
|
|
2618
|
-
else lbl = esc(t);
|
|
2649
|
+
lbl = esc(t.length > 90 ? t.slice(0, 90) + '…' : t);
|
|
2619
2650
|
}
|
|
2620
2651
|
|
|
2621
2652
|
const errClass = ev._errored ? ' errored' : '';
|
|
2622
2653
|
const selClass = selectedEntryId && selectedEntryId === id ? ' selected' : '';
|
|
2623
2654
|
|
|
2624
|
-
const evData = JSON.stringify(ev).replace(
|
|
2655
|
+
const evData = JSON.stringify(ev).replace(/'/g, ''');
|
|
2625
2656
|
return `<div class="feed-entry k-${ev.kind}${errClass}${selClass}" data-ev='${evData}' onclick="openDetail(this.dataset.ev)">
|
|
2626
2657
|
<div class="feed-ico ${catCls}">${ico}</div>
|
|
2627
2658
|
<div class="feed-body">
|
|
2628
|
-
<div class="feed-lbl"
|
|
2659
|
+
<div class="feed-lbl">${lbl}</div>
|
|
2629
2660
|
${detail ? `<div class="feed-detail">${detail}</div>` : ''}
|
|
2630
2661
|
</div>
|
|
2631
|
-
<div class="feed-ts"
|
|
2662
|
+
<div class="feed-ts">${ts}</div>
|
|
2632
2663
|
</div>`;
|
|
2633
2664
|
}
|
|
2634
2665
|
|
|
@@ -2664,23 +2695,20 @@ function openDetail(evJson) {
|
|
|
2664
2695
|
|
|
2665
2696
|
if (ev.kind === 'tool') {
|
|
2666
2697
|
const { catCls } = toolStyle(ev.cat, ev.name);
|
|
2667
|
-
const _toolId = (ev.id || '').toString();
|
|
2668
|
-
const _toolLabel = ev.label || ev.name || '';
|
|
2669
2698
|
title = ev.name || 'Tool';
|
|
2670
2699
|
bodyHtml = `
|
|
2671
2700
|
<div class="d-cat-pill ${catCls}" style="font-size:11px">${esc(ev.cat || 'other')}</div>
|
|
2672
|
-
<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>
|
|
2673
2702
|
${ev.subagent ? `<div class="d-row"><div class="d-lbl">Subagent</div><div class="d-val">${esc(ev.subagent)}</div></div>` : ''}
|
|
2674
2703
|
${ev._errored ? `<div class="d-row"><div class="d-lbl">Status</div><div class="d-val error">Error</div></div>` : ''}
|
|
2675
|
-
<div class="d-row"><div class="d-lbl">Time</div><div class="d-val">${ev.ts ? new Date(
|
|
2676
|
-
<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>
|
|
2677
2706
|
`;
|
|
2678
2707
|
} else if (ev.kind === 'user') {
|
|
2679
|
-
const _userText = ev.text || '';
|
|
2680
2708
|
title = 'User message';
|
|
2681
2709
|
bodyHtml = `
|
|
2682
|
-
<div class="d-row"><div class="d-lbl">Time</div><div class="d-val">${ev.ts ? new Date(
|
|
2683
|
-
<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>
|
|
2684
2712
|
`;
|
|
2685
2713
|
}
|
|
2686
2714
|
|
|
@@ -2708,10 +2736,10 @@ function buildSparkline() {
|
|
|
2708
2736
|
if (idx >= 0 && idx < DAYS) buckets[idx]++;
|
|
2709
2737
|
}
|
|
2710
2738
|
const max = Math.max(...buckets, 1);
|
|
2711
|
-
//
|
|
2712
|
-
const
|
|
2713
|
-
|
|
2714
|
-
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;
|
|
2715
2743
|
const cells = buckets.map((v, i) => {
|
|
2716
2744
|
const isToday = i === DAYS - 1;
|
|
2717
2745
|
const level = v === 0 ? 0 : Math.min(4, Math.ceil(v / max * 4));
|
|
@@ -2720,7 +2748,7 @@ function buildSparkline() {
|
|
|
2720
2748
|
const title = `${label}: ${v} session${v !== 1 ? 's' : ''}`;
|
|
2721
2749
|
return `<div class="cal-cell cal-${level}${isToday ? ' cal-today' : ''}" title="${title}"></div>`;
|
|
2722
2750
|
});
|
|
2723
|
-
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>`;
|
|
2724
2752
|
}
|
|
2725
2753
|
|
|
2726
2754
|
// ── alerts rail ────────────────────────────────────────────
|
|
@@ -2750,10 +2778,6 @@ function updateAlerts() {
|
|
|
2750
2778
|
all.push({ id: 'loop-' + l, cls: 'alert-warn', ico: '↺', msg: `Long-running loop: ${l}` });
|
|
2751
2779
|
}
|
|
2752
2780
|
|
|
2753
|
-
for (const l of (alertState.hilLoops || [])) {
|
|
2754
|
-
all.push({ id: 'hil-' + l, cls: 'alert-warn', ico: '⚠', msg: `Loop waiting for response: ${l}`, action: `switchView('loops')` });
|
|
2755
|
-
}
|
|
2756
|
-
|
|
2757
2781
|
const visible = all.filter(a => !dismissedAlerts.has(a.id));
|
|
2758
2782
|
if (!visible.length) {
|
|
2759
2783
|
rail.className = '';
|
|
@@ -2763,7 +2787,7 @@ function updateAlerts() {
|
|
|
2763
2787
|
rail.className = 'has-alerts';
|
|
2764
2788
|
rail.innerHTML = visible.map(a =>
|
|
2765
2789
|
`<div class="alert-item ${a.cls}" data-alert-id="${a.id}"${a.action ? ` onclick="${a.action}" style="cursor:pointer"` : ''}>
|
|
2766
|
-
<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>
|
|
2767
2791
|
</div>`).join('');
|
|
2768
2792
|
}
|
|
2769
2793
|
|
|
@@ -2795,12 +2819,7 @@ function showSessCtx(sess) {
|
|
|
2795
2819
|
bar.classList.remove('show');
|
|
2796
2820
|
return;
|
|
2797
2821
|
}
|
|
2798
|
-
|
|
2799
|
-
const sCtxTime = sCtxAge ? ' · ' + relTime(sCtxAge) : '';
|
|
2800
|
-
const sCtxText = sess.lastPrompt || sess.id.slice(0, 16) + '…';
|
|
2801
|
-
const label = document.getElementById('sctx-label');
|
|
2802
|
-
label.textContent = sCtxText + sCtxTime;
|
|
2803
|
-
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) + '…';
|
|
2804
2823
|
bar.classList.add('show');
|
|
2805
2824
|
}
|
|
2806
2825
|
|
|
@@ -2822,7 +2841,6 @@ async function loadTodayMetrics() {
|
|
|
2822
2841
|
const data = await apiFetch('/api/section?name=tokens&dir=' + enc(DIR));
|
|
2823
2842
|
const s = data?.tokens?.summary || {};
|
|
2824
2843
|
alertState.todayCost = typeof s.todayCost === 'number' ? s.todayCost : 0;
|
|
2825
|
-
alertState.monthCost = typeof s.monthCost === 'number' ? s.monthCost : 0;
|
|
2826
2844
|
updateAlerts();
|
|
2827
2845
|
checkBudget();
|
|
2828
2846
|
// topbar cost badge
|
|
@@ -2859,17 +2877,13 @@ async function loadLoopMetrics() {
|
|
|
2859
2877
|
try {
|
|
2860
2878
|
const data = await apiFetch('/api/loops?dir=' + enc(DIR));
|
|
2861
2879
|
const loops = Array.isArray(data) ? data : (data.loops || []);
|
|
2862
|
-
|
|
2863
|
-
document.getElementById('bdg-loops').textContent = loops.length ? (hilCount ? loops.length + '⚠' : loops.length) : '—';
|
|
2880
|
+
document.getElementById('bdg-loops').textContent = loops.length || '—';
|
|
2864
2881
|
|
|
2865
2882
|
// alert on loops running > 2h
|
|
2866
2883
|
const TWO_HOURS = 2 * 3600 * 1000;
|
|
2867
2884
|
const now = Date.now();
|
|
2868
2885
|
alertState.longLoops = loops
|
|
2869
|
-
.filter(l => l.status !== 'stopped' && l.status !== 'paused' && l.
|
|
2870
|
-
.map(l => (l.name || l.prompt || 'loop').split('--')[0].trim().slice(0, 30));
|
|
2871
|
-
alertState.hilLoops = loops
|
|
2872
|
-
.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)
|
|
2873
2887
|
.map(l => (l.name || l.prompt || 'loop').split('--')[0].trim().slice(0, 30));
|
|
2874
2888
|
updateAlerts();
|
|
2875
2889
|
|
|
@@ -2878,24 +2892,13 @@ async function loadLoopMetrics() {
|
|
|
2878
2892
|
return;
|
|
2879
2893
|
}
|
|
2880
2894
|
const items = loops.slice(0, 5).map(l => {
|
|
2881
|
-
const
|
|
2882
|
-
const name = fullName.slice(0, 36);
|
|
2883
|
-
const isHilMini = l.status === 'hil:pending';
|
|
2884
|
-
const isTillendMini = l.type === 'tillend';
|
|
2885
|
-
const intervalMini = fmtInterval(l.interval || l.schedule) || 'running';
|
|
2886
|
-
const repMini = isTillendMini && l.currentRep ? `run ${l.currentRep}${l.maxReps ? '/' + l.maxReps : ''}` : null;
|
|
2887
|
-
const hilDot = isHilMini ? ' <span style="color:oklch(75% 0.16 60);font-size:9px">⚠HIL</span>' : '';
|
|
2888
|
-
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);
|
|
2889
2896
|
return `<div class="mini-loop">
|
|
2890
|
-
<div class="ml-name"
|
|
2891
|
-
<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>
|
|
2892
2899
|
</div>`;
|
|
2893
2900
|
}).join('');
|
|
2894
|
-
|
|
2895
|
-
const overflowNote = overflow > 0
|
|
2896
|
-
? `<div style="font-size:10px;color:var(--text-xs);padding:3px 0 0">+${overflow} more — open Loops tab</div>`
|
|
2897
|
-
: '';
|
|
2898
|
-
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}`;
|
|
2899
2902
|
} catch (_) {
|
|
2900
2903
|
document.getElementById('m-loops').innerHTML = `<div class="m-group-title">Active Loops</div><div class="loading-txt">—</div>`;
|
|
2901
2904
|
}
|
|
@@ -2927,16 +2930,16 @@ async function loadStatusStrip() {
|
|
|
2927
2930
|
|
|
2928
2931
|
// HNSW status
|
|
2929
2932
|
const hnswOn = mem.hnsw === true || mem.hnswEnabled === true || mem.hnsw_enabled === true;
|
|
2930
|
-
pills.push(`<span class="ss-pill ${hnswOn ? 'on' : ''}"
|
|
2933
|
+
pills.push(`<span class="ss-pill ${hnswOn ? 'on' : ''}">HNSW ${hnswOn ? 'ON' : 'OFF'}</span>`);
|
|
2931
2934
|
|
|
2932
2935
|
// Patterns count
|
|
2933
2936
|
if (mem.patterns != null) {
|
|
2934
|
-
pills.push(`<span class="ss-pill"
|
|
2937
|
+
pills.push(`<span class="ss-pill">PATTERNS ${Number(mem.patterns).toLocaleString()}</span>`);
|
|
2935
2938
|
}
|
|
2936
2939
|
|
|
2937
2940
|
// Chunks count
|
|
2938
2941
|
if (mem.chunks != null) {
|
|
2939
|
-
pills.push(`<span class="ss-pill"
|
|
2942
|
+
pills.push(`<span class="ss-pill">CHUNKS ${Number(mem.chunks).toLocaleString()}</span>`);
|
|
2940
2943
|
}
|
|
2941
2944
|
|
|
2942
2945
|
// Swarm status
|
|
@@ -2968,7 +2971,7 @@ async function loadTokensView() {
|
|
|
2968
2971
|
const rows = Array.isArray(data?.tokens?.rows) ? data.tokens.rows : [];
|
|
2969
2972
|
cards.innerHTML = [
|
|
2970
2973
|
{ label:'Today Cost', val: typeof s.todayCost === 'number' ? '$' + s.todayCost.toFixed(2) : '—' },
|
|
2971
|
-
{ label:'Today Calls', val: s.todayCalls
|
|
2974
|
+
{ label:'Today Calls', val: s.todayCalls ?? '—' },
|
|
2972
2975
|
{ label:'Month Cost', val: typeof s.monthCost === 'number' ? '$' + s.monthCost.toFixed(2) : '—' },
|
|
2973
2976
|
{ label:'Total Tokens', val: s.totalTokens != null ? Number(s.totalTokens).toLocaleString() : '—' },
|
|
2974
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('');
|
|
@@ -2980,13 +2983,12 @@ async function loadTokensView() {
|
|
|
2980
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>' +
|
|
2981
2984
|
'</tr></thead><tbody>' +
|
|
2982
2985
|
rows.slice(0, 30).map(r => `<tr style="border-top:1px solid var(--border)">
|
|
2983
|
-
<td style="padding:4px 8px 4px 0;color:var(--text-hi);max-width:260px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap"
|
|
2984
|
-
<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>
|
|
2985
2988
|
<td style="padding:4px 8px;color:var(--text-lo)">${r.tokens != null ? Number(r.tokens).toLocaleString() : '—'}</td>
|
|
2986
2989
|
<td style="padding:4px 8px;color:var(--accent)">$${Number(r.cost ?? 0).toFixed(4)}</td>
|
|
2987
2990
|
</tr>`).join('') +
|
|
2988
|
-
'</tbody></table></div>'
|
|
2989
|
-
(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>';
|
|
2990
2992
|
} else { table.innerHTML = ''; }
|
|
2991
2993
|
markLiveGlow('view-tokens');
|
|
2992
2994
|
} catch (_) {
|
|
@@ -3038,6 +3040,7 @@ function renderTokChart(daily, animated = true) {
|
|
|
3038
3040
|
});
|
|
3039
3041
|
canvas.addEventListener('mouseleave', () => { canvas._tokTip.style.display = 'none'; });
|
|
3040
3042
|
}
|
|
3043
|
+
|
|
3041
3044
|
const targets = vals.map((v, i) => ({
|
|
3042
3045
|
v, i,
|
|
3043
3046
|
isToday: i === vals.length - 1,
|
|
@@ -3098,7 +3101,7 @@ async function setTokPeriod(btn, period) {
|
|
|
3098
3101
|
if (cards) cards.innerHTML = [
|
|
3099
3102
|
{ label: 'Cost', val: typeof s.todayCost === 'number' ? '$' + s.todayCost.toFixed(2)
|
|
3100
3103
|
: typeof s.cost === 'number' ? '$' + s.cost.toFixed(2) : '—' },
|
|
3101
|
-
{ label: 'Calls', val:
|
|
3104
|
+
{ label: 'Calls', val: s.todayCalls ?? s.calls ?? '—' },
|
|
3102
3105
|
{ label: 'Tokens', val: s.totalTokens != null ? Number(s.totalTokens).toLocaleString() : '—' },
|
|
3103
3106
|
{ label: 'Models', val: s.modelCount ?? s.models ?? '—' },
|
|
3104
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('');
|
|
@@ -3110,13 +3113,12 @@ async function setTokPeriod(btn, period) {
|
|
|
3110
3113
|
'<th style="padding:3px 8px">Calls</th><th style="padding:3px 8px">Cost</th></tr></thead><tbody>' +
|
|
3111
3114
|
rows.slice(0, 30).map(r =>
|
|
3112
3115
|
`<tr style="border-top:1px solid var(--border)">` +
|
|
3113
|
-
`<td style="padding:3px 8px 3px 0;color:var(--text-hi);max-width:220px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap"
|
|
3114
|
-
`<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>` +
|
|
3115
3118
|
`<td style="padding:3px 8px;color:var(--accent)">$${Number(r.cost ?? 0).toFixed(4)}</td>` +
|
|
3116
3119
|
`</tr>`
|
|
3117
3120
|
).join('') +
|
|
3118
|
-
'</tbody></table></div>'
|
|
3119
|
-
(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>';
|
|
3120
3122
|
} else if (table) { table.innerHTML = ''; }
|
|
3121
3123
|
markLiveGlow('view-tokens');
|
|
3122
3124
|
// Update topbar badge when showing today's data
|
|
@@ -3169,58 +3171,25 @@ async function loadMemRouting() {
|
|
|
3169
3171
|
const last = rows[rows.length - 1];
|
|
3170
3172
|
window._lastRouteAgent = last.suggestedAgent || last.route || last.category || last.agent || last.agentType || '';
|
|
3171
3173
|
}
|
|
3172
|
-
if (!rows.length) { pane.innerHTML = '<div class="empty">No routing data
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
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>' +
|
|
3176
|
-
overflowNote +
|
|
3177
|
-
'<div id="routing-rows">' +
|
|
3178
|
-
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 => {
|
|
3179
3177
|
const agent = r.suggestedAgent || r.route || r.category || r.agent || '—';
|
|
3180
3178
|
const task = r.task || r.prompt || r.description || r.sessionId?.slice(0, 8) || '—';
|
|
3181
3179
|
const ts = r.timestamp || r.ts || r.created_at;
|
|
3182
3180
|
const conf = r.confidence != null ? Math.round(r.confidence * 100) + '%' : '';
|
|
3183
|
-
return `<div
|
|
3181
|
+
return `<div style="padding:5px 0;border-bottom:1px solid var(--border);font-size:11px;font-family:monospace">
|
|
3184
3182
|
<div style="color:var(--text-hi);display:flex;align-items:center;gap:8px">
|
|
3185
3183
|
<span style="color:var(--accent)">${esc(agent)}</span>
|
|
3186
3184
|
${conf ? `<span style="color:var(--text-lo)">${esc(conf)}</span>` : ''}
|
|
3187
|
-
<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>
|
|
3188
3186
|
</div>
|
|
3189
3187
|
<div style="color:var(--text-lo);margin-top:1px">${esc(task)}</div>
|
|
3190
3188
|
</div>`;
|
|
3191
|
-
}).join('')
|
|
3189
|
+
}).join('');
|
|
3192
3190
|
} catch (_) { pane.innerHTML = '<div class="empty">Failed to load routing data</div>'; }
|
|
3193
3191
|
}
|
|
3194
3192
|
|
|
3195
|
-
function filterRouting(q) {
|
|
3196
|
-
const lq = (q || '').toLowerCase();
|
|
3197
|
-
document.querySelectorAll('#routing-rows .routing-entry').forEach(el => {
|
|
3198
|
-
const agent = (el.dataset.agent || '').toLowerCase();
|
|
3199
|
-
const task = (el.dataset.task || '').toLowerCase();
|
|
3200
|
-
el.style.display = (!lq || agent.includes(lq) || task.includes(lq)) ? '' : 'none';
|
|
3201
|
-
});
|
|
3202
|
-
}
|
|
3203
|
-
|
|
3204
|
-
function filterLoopList(q) {
|
|
3205
|
-
const lq = (q || '').toLowerCase();
|
|
3206
|
-
document.querySelectorAll('#loops-content .loop-row').forEach(el => {
|
|
3207
|
-
const text = (el.textContent || '').toLowerCase();
|
|
3208
|
-
const expand = el.nextElementSibling;
|
|
3209
|
-
const visible = !lq || text.includes(lq);
|
|
3210
|
-
el.style.display = visible ? '' : 'none';
|
|
3211
|
-
if (expand && expand.classList.contains('loop-expand')) expand.style.display = 'none';
|
|
3212
|
-
});
|
|
3213
|
-
}
|
|
3214
|
-
|
|
3215
|
-
function filterOrgList(q) {
|
|
3216
|
-
const lq = (q || '').toLowerCase();
|
|
3217
|
-
document.querySelectorAll('#orgs-list-scroll .org-item').forEach(el => {
|
|
3218
|
-
const name = (el.dataset.org || '').toLowerCase();
|
|
3219
|
-
const goal = (el.querySelector('.oi-goal')?.textContent || '').toLowerCase();
|
|
3220
|
-
el.style.display = (!lq || name.includes(lq) || goal.includes(lq)) ? '' : 'none';
|
|
3221
|
-
});
|
|
3222
|
-
}
|
|
3223
|
-
|
|
3224
3193
|
async function loadMemUsage() {
|
|
3225
3194
|
const pane = document.getElementById('mem-tab-usage');
|
|
3226
3195
|
if (!pane) return;
|
|
@@ -3228,10 +3197,10 @@ async function loadMemUsage() {
|
|
|
3228
3197
|
// Period tabs
|
|
3229
3198
|
pane.innerHTML = `
|
|
3230
3199
|
<div class="tok-periods" style="margin-bottom:14px">
|
|
3231
|
-
<button class="tok-period-btn active" data-period="today"
|
|
3232
|
-
<button class="tok-period-btn" data-period="week"
|
|
3233
|
-
<button class="tok-period-btn" data-period="30d"
|
|
3234
|
-
<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>
|
|
3235
3204
|
</div>
|
|
3236
3205
|
<div id="mem-usage-content"><div class="loading-txt">Loading…</div></div>
|
|
3237
3206
|
`;
|
|
@@ -3256,25 +3225,19 @@ async function loadMemUsagePeriod(btn, period) {
|
|
|
3256
3225
|
|
|
3257
3226
|
function barChart(items, valKey, labelKey, color, maxItems) {
|
|
3258
3227
|
if (!items.length) return '<div class="empty" style="font-size:12px">No data</div>';
|
|
3259
|
-
const
|
|
3260
|
-
|
|
3261
|
-
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 => {
|
|
3262
3230
|
const pct = Math.round((Number(item[valKey] || 0) / maxVal) * 100);
|
|
3263
|
-
const
|
|
3264
|
-
const label = esc(fullLabel.slice(0, 24));
|
|
3231
|
+
const label = esc(String(item[labelKey] || '—').slice(0, 24));
|
|
3265
3232
|
const val = typeof item[valKey] === 'number' && valKey === 'cost'
|
|
3266
3233
|
? '$' + Number(item[valKey]).toFixed(4)
|
|
3267
3234
|
: String(item[valKey] || 0);
|
|
3268
3235
|
return `<div style="display:flex;align-items:center;gap:8px;padding:3px 0;font-size:12px">
|
|
3269
|
-
<div style="width:130px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--text-hi);font-family:var(--mono);font-size:11px"
|
|
3270
|
-
<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>
|
|
3271
3238
|
<div style="width:60px;text-align:right;color:var(--text-lo);font-size:11px;font-family:var(--mono)">${esc(val)}</div>
|
|
3272
3239
|
</div>`;
|
|
3273
3240
|
}).join('');
|
|
3274
|
-
const overflow = items.length > maxItems
|
|
3275
|
-
? `<div style="font-size:11px;color:var(--text-xs);margin-top:3px;text-align:right">Showing ${maxItems} of ${items.length}</div>`
|
|
3276
|
-
: '';
|
|
3277
|
-
return rows + overflow;
|
|
3278
3241
|
}
|
|
3279
3242
|
|
|
3280
3243
|
const totalCost = typeof s.todayCost === 'number' ? s.todayCost : (typeof s.cost === 'number' ? s.cost : null);
|
|
@@ -3317,12 +3280,11 @@ async function loadMemUsagePeriod(btn, period) {
|
|
|
3317
3280
|
<div style="overflow-x:auto"><table style="width:100%;border-collapse:collapse;font-size:11px">
|
|
3318
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>
|
|
3319
3282
|
<tbody>${rows.slice(0,30).map(r => `<tr style="border-top:1px solid var(--border)">
|
|
3320
|
-
<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"
|
|
3321
|
-
<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>
|
|
3322
3285
|
<td style="padding:3px 8px;color:var(--accent)">$${Number(r.cost??0).toFixed(4)}</td>
|
|
3323
3286
|
</tr>`).join('')}</tbody>
|
|
3324
|
-
</table></div
|
|
3325
|
-
${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>` : ''}
|
|
3326
3288
|
`;
|
|
3327
3289
|
} catch (e) {
|
|
3328
3290
|
content.innerHTML = '<div class="empty">Failed: ' + esc(e.message) + '</div>';
|
|
@@ -3344,15 +3306,14 @@ async function loadMemADRs() {
|
|
|
3344
3306
|
function renderADRs(list) {
|
|
3345
3307
|
const pane = document.getElementById('adr-content');
|
|
3346
3308
|
if (!pane) return;
|
|
3347
|
-
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; }
|
|
3348
3310
|
pane.innerHTML = list.slice(0, 50).map(a => `<div style="padding:8px 0;border-bottom:1px solid var(--border)">
|
|
3349
3311
|
<div style="display:flex;justify-content:space-between;align-items:baseline;margin-bottom:3px">
|
|
3350
3312
|
<span style="color:var(--text-hi);font-size:12px;font-family:monospace">${esc(a.id || a.title || '—')}</span>
|
|
3351
3313
|
<span class="ss-pill ${a.status === 'accepted' ? 'on' : a.status === 'deprecated' ? 'warn' : ''}">${esc(a.status || '?')}</span>
|
|
3352
3314
|
</div>
|
|
3353
|
-
<div style="font-size:11px;color:var(--text-lo)"
|
|
3354
|
-
</div>`).join('')
|
|
3355
|
-
(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('');
|
|
3356
3317
|
}
|
|
3357
3318
|
|
|
3358
3319
|
function filterADRs(q) {
|
|
@@ -3367,15 +3328,11 @@ function filterADRs(q) {
|
|
|
3367
3328
|
|
|
3368
3329
|
function renderMiniSessions(sessions) {
|
|
3369
3330
|
if (!sessions.length) return;
|
|
3370
|
-
const items = sessions.map((s, i) =>
|
|
3371
|
-
const costStr = typeof s.totalCost === 'number' && s.totalCost > 0.001 ? ' · $' + s.totalCost.toFixed(2) : '';
|
|
3372
|
-
const durStr = s.totalDurationMs ? ' · ' + fmtDur(s.totalDurationMs) : '';
|
|
3373
|
-
return `
|
|
3331
|
+
const items = sessions.map((s, i) => `
|
|
3374
3332
|
<div class="mini-sess" onclick="sessionIdx=${i};userScrolled=false;loadFeedForSession(allSessions[${i}])">
|
|
3375
|
-
<div class="ms-prompt"
|
|
3376
|
-
<div class="ms-meta"
|
|
3377
|
-
</div
|
|
3378
|
-
}).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('');
|
|
3379
3336
|
document.getElementById('m-sessions').innerHTML = `<div class="m-group-title">Recent Sessions</div>${items}`;
|
|
3380
3337
|
buildSwimlane();
|
|
3381
3338
|
}
|
|
@@ -3402,7 +3359,7 @@ function renderProjectGrid(projects, query) {
|
|
|
3402
3359
|
(p.name || p.slug || '').toLowerCase().includes(query.toLowerCase()) ||
|
|
3403
3360
|
(p.path || '').toLowerCase().includes(query.toLowerCase())) : projects;
|
|
3404
3361
|
if (!filtered.length) {
|
|
3405
|
-
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>';
|
|
3406
3363
|
return;
|
|
3407
3364
|
}
|
|
3408
3365
|
el.className = 'proj-grid';
|
|
@@ -3413,12 +3370,12 @@ function renderProjectGrid(projects, query) {
|
|
|
3413
3370
|
return `<div class="proj-card${isCurrent ? ' current' : ''}" onclick="switchProject('${esc(p.path || '')}')">
|
|
3414
3371
|
${isCurrent ? '<div class="proj-card-badge">active</div>' : ''}
|
|
3415
3372
|
<div class="proj-health ${hCls}" title="Health score: ${score}">${score}</div>
|
|
3416
|
-
<div class="proj-card-name"
|
|
3417
|
-
<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>
|
|
3418
3375
|
<div class="proj-card-stats">
|
|
3419
3376
|
<div class="proj-stat"><div class="ps-v">${p.sessionCount || 0}</div><div class="ps-l">sessions</div></div>
|
|
3420
3377
|
<div class="proj-stat"><div class="ps-v">${p.memoryCount || 0}</div><div class="ps-l">memories</div></div>
|
|
3421
|
-
${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>` : ''}
|
|
3422
3379
|
</div>
|
|
3423
3380
|
</div>`;
|
|
3424
3381
|
}).join('');
|
|
@@ -3440,33 +3397,19 @@ async function renderSessions() {
|
|
|
3440
3397
|
document.getElementById('sess-pg-sub').textContent =
|
|
3441
3398
|
sessions.length + ' session' + (sessions.length !== 1 ? 's' : '') + ' · ' + (DIR.split('/').pop() || DIR);
|
|
3442
3399
|
if (!sessions.length) {
|
|
3443
|
-
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>';
|
|
3444
3401
|
return;
|
|
3445
3402
|
}
|
|
3446
3403
|
let toShow = showStarredOnly ? sessions.filter(s => bookmarks.has(s.id)) : sessions;
|
|
3447
3404
|
if (activeTagFilter) toShow = toShow.filter(s => (allTags.sessionTags.get(s.id) || []).includes(activeTagFilter));
|
|
3448
3405
|
if (heatmapDateFilter) toShow = toShow.filter(s => {
|
|
3449
3406
|
const t = s.lastTs || s.mtime; if (!t) return false;
|
|
3450
|
-
return new Date(typeof t === 'number' ? t :
|
|
3407
|
+
return new Date(typeof t === 'number' ? t : t).toDateString() === heatmapDateFilter;
|
|
3451
3408
|
});
|
|
3452
3409
|
// f57: file pivot filter
|
|
3453
3410
|
if (filePivot) toShow = toShow.filter(s => (s.filesTouched || []).includes(filePivot));
|
|
3454
3411
|
if (!toShow.length) {
|
|
3455
|
-
|
|
3456
|
-
if (filePivot) {
|
|
3457
|
-
emptyMsg = 'No sessions touching ' + esc(filePivot.split('/').pop());
|
|
3458
|
-
emptyHint = 'Clear the file filter to see all sessions';
|
|
3459
|
-
} else if (heatmapDateFilter) {
|
|
3460
|
-
emptyMsg = 'No sessions on ' + esc(heatmapDateFilter);
|
|
3461
|
-
emptyHint = 'Click another date on the heatmap or clear the filter';
|
|
3462
|
-
} else if (activeTagFilter) {
|
|
3463
|
-
emptyMsg = 'No sessions tagged "' + esc(activeTagFilter) + '"';
|
|
3464
|
-
emptyHint = 'Clear the tag filter to see all sessions';
|
|
3465
|
-
} else {
|
|
3466
|
-
emptyMsg = 'No bookmarked sessions';
|
|
3467
|
-
emptyHint = 'Click the ☆ on any session row to bookmark it.';
|
|
3468
|
-
}
|
|
3469
|
-
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>';
|
|
3470
3413
|
buildSessionHeatmap(sessions);
|
|
3471
3414
|
return;
|
|
3472
3415
|
}
|
|
@@ -3513,7 +3456,7 @@ async function renderSessions() {
|
|
|
3513
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('');
|
|
3514
3457
|
const autoTags = (allTags.sessionTags.get(s.id) || []).map(t => `<span class="sr-autotag">${esc(t)}</span>`).join('');
|
|
3515
3458
|
const isStarred = bookmarks.has(s.id);
|
|
3516
|
-
const sData = JSON.stringify(s).replace(
|
|
3459
|
+
const sData = JSON.stringify(s).replace(/'/g, ''');
|
|
3517
3460
|
const note = getSessNote(s.id);
|
|
3518
3461
|
const hasNote = !!note;
|
|
3519
3462
|
const files = (s.filesTouched || []).slice(0, 5);
|
|
@@ -3538,14 +3481,12 @@ async function renderSessions() {
|
|
|
3538
3481
|
? `<span class="sr-compact-badge">+${s.compactCount} compacted</span>`
|
|
3539
3482
|
: '';
|
|
3540
3483
|
const summaryHtml = s.summary
|
|
3541
|
-
? `<div class="sr-summary"
|
|
3484
|
+
? `<div class="sr-summary">${esc(s.summary.slice(0, 180))}</div>`
|
|
3542
3485
|
: '';
|
|
3543
|
-
const srTimeTs = s.lastTs || s.mtime;
|
|
3544
|
-
const srTimeFull = srTimeTs ? new Date(typeof srTimeTs === 'number' ? srTimeTs : Number(srTimeTs) || srTimeTs).toLocaleString() : '';
|
|
3545
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}'>
|
|
3546
3487
|
<div class="sr-top">
|
|
3547
|
-
<div class="sr-prompt"
|
|
3548
|
-
<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}
|
|
3549
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>
|
|
3550
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>
|
|
3551
3492
|
<span class="sr-view">→ view</span>
|
|
@@ -3559,7 +3500,7 @@ async function renderSessions() {
|
|
|
3559
3500
|
${ctxGauge}
|
|
3560
3501
|
<div class="err-drawer" id="err-drawer-${esc(s.id)}"></div>
|
|
3561
3502
|
<div class="sess-notes-wrap" onclick="event.stopPropagation()">
|
|
3562
|
-
<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>
|
|
3563
3504
|
<div class="sess-notes-area" id="snote-${esc(s.id)}">
|
|
3564
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>
|
|
3565
3506
|
<div class="sess-note-saved"></div>
|
|
@@ -3600,14 +3541,14 @@ async function renderSessions() {
|
|
|
3600
3541
|
const todayCost = allSessions.filter(s => {
|
|
3601
3542
|
const t = s.firstTs || s.mtime;
|
|
3602
3543
|
if (!t) return false;
|
|
3603
|
-
const d = new Date(typeof t === 'number' ? t :
|
|
3544
|
+
const d = new Date(typeof t === 'number' ? t : t);
|
|
3604
3545
|
const now = new Date();
|
|
3605
3546
|
return d.getFullYear() === now.getFullYear() && d.getMonth() === now.getMonth() && d.getDate() === now.getDate();
|
|
3606
3547
|
}).reduce((a, s) => a + (s.totalCost || 0), 0);
|
|
3607
3548
|
const monthCost = allSessions.filter(s => {
|
|
3608
3549
|
const t = s.firstTs || s.mtime;
|
|
3609
3550
|
if (!t) return false;
|
|
3610
|
-
const d = new Date(typeof t === 'number' ? t :
|
|
3551
|
+
const d = new Date(typeof t === 'number' ? t : t);
|
|
3611
3552
|
const now = new Date();
|
|
3612
3553
|
return d.getFullYear() === now.getFullYear() && d.getMonth() === now.getMonth();
|
|
3613
3554
|
}).reduce((a, s) => a + (s.totalCost || 0), 0);
|
|
@@ -3686,7 +3627,7 @@ function buildTagFilterBar(sessions) {
|
|
|
3686
3627
|
if (!allTags.common.size) return '';
|
|
3687
3628
|
const sorted = [...allTags.common].sort();
|
|
3688
3629
|
const chips = sorted.map(t =>
|
|
3689
|
-
`<button class="tag-chip${activeTagFilter === t ? ' active' : ''}"
|
|
3630
|
+
`<button class="tag-chip${activeTagFilter === t ? ' active' : ''}" onclick="setTagFilter('${esc(t)}')">${esc(t)}</button>`
|
|
3690
3631
|
).join('');
|
|
3691
3632
|
return `<div class="tag-filter-bar">${chips}</div>`;
|
|
3692
3633
|
}
|
|
@@ -3715,12 +3656,10 @@ function buildRecap(events, sess) {
|
|
|
3715
3656
|
const topPct = topCat ? Math.round(topCat[1] / tools.length * 100) : 0;
|
|
3716
3657
|
|
|
3717
3658
|
const costStr = sess?.totalCost != null ? '$' + sess.totalCost.toFixed(2) : (sess?.cost != null ? '$' + sess.cost.toFixed(2) : null);
|
|
3718
|
-
const durStr = sess?.totalDurationMs ? fmtDur(sess.totalDurationMs) : null;
|
|
3719
3659
|
|
|
3720
3660
|
const stats = [
|
|
3721
3661
|
tools.length ? `<span class="recap-stat rs-tool">${tools.length} tool calls${topCat ? ' · ' + topPct + '% ' + topCat[0] : ''}</span>` : '',
|
|
3722
3662
|
users.length ? `<span class="recap-stat rs-user">${users.length} message${users.length !== 1 ? 's' : ''}</span>` : '',
|
|
3723
|
-
durStr ? `<span class="recap-stat">${durStr}</span>` : '',
|
|
3724
3663
|
costStr ? `<span class="recap-stat rs-cost">${costStr}</span>` : '',
|
|
3725
3664
|
errors.length ? `<span class="recap-stat rs-err">${errors.length} error${errors.length !== 1 ? 's' : ''}</span>` : '',
|
|
3726
3665
|
].filter(Boolean).join('');
|
|
@@ -3729,74 +3668,6 @@ function buildRecap(events, sess) {
|
|
|
3729
3668
|
recap.className = 'show';
|
|
3730
3669
|
}
|
|
3731
3670
|
|
|
3732
|
-
// ── feature 3: global feed ─────────────────────────────────
|
|
3733
|
-
async function renderGlobalFeed() {
|
|
3734
|
-
const el = document.getElementById('gf-content');
|
|
3735
|
-
el.innerHTML = '<div class="loading-txt">Loading all projects…</div>';
|
|
3736
|
-
try {
|
|
3737
|
-
// fetch project list
|
|
3738
|
-
const data = await apiFetch('/api/projects');
|
|
3739
|
-
const allProjects = data?.projects || [];
|
|
3740
|
-
const projects = allProjects.slice(0, 8);
|
|
3741
|
-
if (!projects.length) {
|
|
3742
|
-
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>';
|
|
3743
|
-
return;
|
|
3744
|
-
}
|
|
3745
|
-
document.getElementById('gf-sub').textContent = allProjects.length > 8
|
|
3746
|
-
? `Last activity across ${projects.length} of ${allProjects.length} projects`
|
|
3747
|
-
: `Last activity across ${projects.length} project${projects.length !== 1 ? 's' : ''}`;
|
|
3748
|
-
|
|
3749
|
-
// fetch sessions for each project in parallel
|
|
3750
|
-
const results = await Promise.allSettled(
|
|
3751
|
-
projects.map(p => apiFetch('/api/session-journal?dir=' + enc(p.path)).then(d => ({ project: p, sessions: d.sessions || [] })))
|
|
3752
|
-
);
|
|
3753
|
-
|
|
3754
|
-
// flatten + sort by recency
|
|
3755
|
-
const entries = [];
|
|
3756
|
-
for (const r of results) {
|
|
3757
|
-
if (r.status !== 'fulfilled') continue;
|
|
3758
|
-
const { project, sessions } = r.value;
|
|
3759
|
-
for (const s of sessions.slice(0, 3)) {
|
|
3760
|
-
entries.push({ project, session: s });
|
|
3761
|
-
}
|
|
3762
|
-
}
|
|
3763
|
-
entries.sort((a, b) => {
|
|
3764
|
-
const ta = a.session.lastTs || a.session.mtime || 0;
|
|
3765
|
-
const tb = b.session.lastTs || b.session.mtime || 0;
|
|
3766
|
-
return (typeof tb === 'number' ? tb : Number(tb) || new Date(tb).getTime() || 0) - (typeof ta === 'number' ? ta : Number(ta) || new Date(ta).getTime() || 0);
|
|
3767
|
-
});
|
|
3768
|
-
|
|
3769
|
-
if (!entries.length) {
|
|
3770
|
-
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>';
|
|
3771
|
-
return;
|
|
3772
|
-
}
|
|
3773
|
-
|
|
3774
|
-
el.innerHTML = '<div class="sess-list">' + entries.map(({ project, session: s }) => {
|
|
3775
|
-
const projName = project.name || project.slug || project.path?.split('/').pop() || '?';
|
|
3776
|
-
const dur = s.totalDurationMs ? fmtDur(s.totalDurationMs) : '';
|
|
3777
|
-
const cost = typeof s.totalCost === 'number' ? '$' + s.totalCost.toFixed(2) : '';
|
|
3778
|
-
const meta = [dur, cost].filter(Boolean).join(' · ') || s.id.slice(0, 12);
|
|
3779
|
-
const gfCompactBadge = (s.compactCount > 0)
|
|
3780
|
-
? `<span class="sr-compact-badge">+${s.compactCount} compacted</span>`
|
|
3781
|
-
: '';
|
|
3782
|
-
const gfSummaryHtml = s.summary
|
|
3783
|
-
? `<div class="sr-summary" title="${esc(s.summary)}">${esc(s.summary.slice(0, 180))}${s.summary.length > 180 ? '…' : ''}</div>`
|
|
3784
|
-
: '';
|
|
3785
|
-
return `<div class="sess-row" onclick="switchProject('${esc(project.path)}');setTimeout(()=>jumpToSession('${esc(s.id)}'),150)">
|
|
3786
|
-
<div class="sr-top">
|
|
3787
|
-
<div class="sr-prompt" title="${esc(s.lastPrompt || s.id || '')}">${esc(s.lastPrompt || s.id?.slice(0,8) || '—')}</div>
|
|
3788
|
-
<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}
|
|
3789
|
-
<span class="gf-proj-tag">${esc(projName)}</span>
|
|
3790
|
-
</div>
|
|
3791
|
-
${gfSummaryHtml}
|
|
3792
|
-
<div class="sr-meta">${esc(meta)}</div>
|
|
3793
|
-
</div>`;
|
|
3794
|
-
}).join('') + '</div>';
|
|
3795
|
-
} catch (err) {
|
|
3796
|
-
el.innerHTML = '<div class="empty">Could not load: ' + esc(err.message) + '</div>';
|
|
3797
|
-
}
|
|
3798
|
-
}
|
|
3799
|
-
|
|
3800
3671
|
// ── global loops (multi-project) ───────────────────────────
|
|
3801
3672
|
function deduplicateLoops(loops) {
|
|
3802
3673
|
const hasRepeat = loops.some(l => l.source === '_repeat.md');
|
|
@@ -3837,94 +3708,33 @@ async function renderGlobalLoops() {
|
|
|
3837
3708
|
if (!loops.length) continue;
|
|
3838
3709
|
totalLoops += loops.length;
|
|
3839
3710
|
const projName = project.name || project.slug || project.path?.split('/').pop() || '?';
|
|
3840
|
-
const rows = loops.map(
|
|
3711
|
+
const rows = loops.map(l => {
|
|
3841
3712
|
const isTillend = l.type === 'tillend';
|
|
3842
3713
|
const curRep = l.currentRep || 0;
|
|
3843
3714
|
const maxReps = l.maxReps || 0;
|
|
3844
|
-
const nextAt = l.nextRunAt ? parseInt(l.nextRunAt) : 0;
|
|
3715
|
+
const nextAt = l.nextRunAt ? parseInt(l.nextRunAt, 10) : 0;
|
|
3845
3716
|
const isHil = l.status === 'hil:pending';
|
|
3846
3717
|
const isExplicitlyActive = l.status === 'running' || l.status === 'waiting' || l.status === 'active';
|
|
3847
|
-
const
|
|
3848
|
-
const
|
|
3849
|
-
nextAt > 0 && nextAt <= Date.now();
|
|
3850
|
-
const isStaledActive = isExplicitlyActive && nextAt > 0 &&
|
|
3851
|
-
(Date.now() - nextAt) > LOOP_STALE_MS;
|
|
3852
|
-
const isFinished = isOverdue || isStaledActive ||
|
|
3853
|
-
(!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) ||
|
|
3854
3720
|
['finished','done','complete','completed','expired'].includes(l.status);
|
|
3855
3721
|
const running = !isFinished && l.status !== 'stopped' && l.status !== 'paused';
|
|
3722
|
+
const name = l.name || (l.prompt || 'loop').split('--')[0].trim().slice(0, 60);
|
|
3856
3723
|
const intervalStr = fmtInterval(l.interval || l.schedule);
|
|
3857
|
-
const
|
|
3858
|
-
if (_l.command) {
|
|
3859
|
-
const flags = [];
|
|
3860
|
-
if (_l.type && _l.type !== 'repeat') flags.push('--' + _l.type);
|
|
3861
|
-
if (_l.maxReps) flags.push('--maxruns ' + _l.maxReps);
|
|
3862
|
-
if (_l.wait || _l.interval) flags.push('--wait ' + (_l.wait || _l.interval * 60));
|
|
3863
|
-
if (_l.currentRep != null) flags.push('--rep ' + _l.currentRep);
|
|
3864
|
-
if (_l.id) flags.push('--loop ' + _l.id);
|
|
3865
|
-
return { userPrompt: _l.prompt || '', command: _l.command, flagsStr: flags.join(' ') };
|
|
3866
|
-
}
|
|
3867
|
-
const full = _l.prompt || '';
|
|
3868
|
-
const cmdM = full.match(/^(\/\S+)/);
|
|
3869
|
-
if (!cmdM) return { userPrompt: full, command: '', flagsStr: '' };
|
|
3870
|
-
const tokens = full.slice(cmdM[1].length).trim().split(/\s+/);
|
|
3871
|
-
let ti = 0, fp = [];
|
|
3872
|
-
while (ti < tokens.length && tokens[ti] && tokens[ti].startsWith('--')) {
|
|
3873
|
-
fp.push(tokens[ti++]);
|
|
3874
|
-
if (ti < tokens.length && tokens[ti] && !tokens[ti].startsWith('--')) fp.push(tokens[ti++]);
|
|
3875
|
-
}
|
|
3876
|
-
return { userPrompt: tokens.slice(ti).join(' '), command: cmdM[1], flagsStr: fp.join(' ') };
|
|
3877
|
-
})(l);
|
|
3878
|
-
const userPrompt = _lp.userPrompt;
|
|
3879
|
-
const cmdStr = _lp.command;
|
|
3880
|
-
const flagsStr = _lp.flagsStr;
|
|
3881
|
-
const fullPrompt = l.prompt || '';
|
|
3882
|
-
const name = (l.name || userPrompt || cmdStr || 'loop').slice(0, 60);
|
|
3883
|
-
const startedAt = l.startedAt ? new Date(l.startedAt).toLocaleString() : '—';
|
|
3884
|
-
const lastRun = l.lastRunAt ? relTime(l.lastRunAt) : (l.startedAt ? relTime(l.startedAt) : '—');
|
|
3885
|
-
const pct = (!isTillend && maxReps > 0) ? Math.min(100, Math.round(curRep / maxReps * 100)) : 0;
|
|
3886
|
-
const progBar = (!isTillend && maxReps > 0 && running)
|
|
3887
|
-
? `<div class="lp-bar"><div class="lp-fill" style="width:${pct}%"></div></div>` : '';
|
|
3888
|
-
const runCountDisplay = isTillend
|
|
3889
|
-
? `run ${curRep} / ∞${maxReps > 0 ? ' (cap: ' + maxReps + ')' : ''}`
|
|
3890
|
-
: (maxReps > 0 ? `${curRep} / ${maxReps}` : String(curRep || '—'));
|
|
3891
|
-
const cdownSpan = nextAt
|
|
3892
|
-
? ` <span class="loop-cdown${nextAt - Date.now() <= 0 ? ' overdue' : ''}" data-nextat="${nextAt}">${fmtCountdown(nextAt)}</span>` : '';
|
|
3893
|
-
const stopBtn = running
|
|
3894
|
-
? `<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>` : '';
|
|
3895
|
-
const typeBadge = `<span class="loop-type-badge${isTillend ? ' tillend' : ''}">${esc(l.type || 'repeat')}</span>`;
|
|
3724
|
+
const type = esc(l.type || 'repeat');
|
|
3896
3725
|
const statusClass = isHil ? 'hil' : (running ? 'active' : 'stopped');
|
|
3897
3726
|
const statusLabel = isHil ? '⚠ HIL' : (running ? 'active' : (isFinished ? 'done' : 'stopped'));
|
|
3898
|
-
const
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
|
|
3902
|
-
|
|
3903
|
-
<div class="loop-
|
|
3904
|
-
|
|
3905
|
-
<div class="loop-meta">${esc(metaParts)}${cdownSpan}</div>
|
|
3906
|
-
${hilBanner}
|
|
3907
|
-
${progBar}
|
|
3908
|
-
</div>
|
|
3909
|
-
<div class="loop-status ${statusClass}">${statusLabel}</div>
|
|
3910
|
-
${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>
|
|
3911
3734
|
</div>
|
|
3912
|
-
<
|
|
3913
|
-
|
|
3914
|
-
|
|
3915
|
-
${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)}).then(()=>showToast('Copied','Flags copied','ok'))">⎘</button></div></div>` : ''}
|
|
3916
|
-
<div class="le-row"><div class="le-lbl">Project</div><div class="le-val"><span class="gf-proj-tag">${esc(projName)}</span></div></div>
|
|
3917
|
-
<div class="le-row"><div class="le-lbl">Type</div><div class="le-val">${esc(l.type || 'repeat')}</div></div>
|
|
3918
|
-
<div class="le-row"><div class="le-lbl">Interval</div><div class="le-val">${esc(intervalStr || '—')}</div></div>
|
|
3919
|
-
<div class="le-row"><div class="le-lbl">Status</div><div class="le-val">${isHil ? '⚠ hil:pending' : (running ? '● running' : (isFinished ? '✓ done' : '○ stopped'))}</div></div>
|
|
3920
|
-
${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>` : ''}
|
|
3921
|
-
<div class="le-row"><div class="le-lbl">Started</div><div class="le-val mono">${esc(startedAt)}</div></div>
|
|
3922
|
-
${(()=>{ 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>`:''; })()}
|
|
3923
|
-
<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>
|
|
3924
|
-
<div class="le-row"><div class="le-lbl">${isTillend ? 'Progress' : 'Run count'}</div><div class="le-val">${esc(runCountDisplay)}</div></div>
|
|
3925
|
-
${l.source ? `<div class="le-row"><div class="le-lbl">Source</div><div class="le-val">${esc(l.source)}</div></div>` : ''}
|
|
3926
|
-
${buildLoopSparkline(l)}
|
|
3927
|
-
</div>`;
|
|
3735
|
+
<span class="gf-proj-tag">${esc(projName)}</span>
|
|
3736
|
+
<div class="loop-status ${statusClass}">${statusLabel}</div>
|
|
3737
|
+
</div>`;
|
|
3928
3738
|
}).join('');
|
|
3929
3739
|
sections.push(`<div style="margin-bottom:18px">
|
|
3930
3740
|
<div class="m-group-title" style="margin-bottom:6px">${esc(projName)}</div>
|
|
@@ -3940,7 +3750,6 @@ async function renderGlobalLoops() {
|
|
|
3940
3750
|
return;
|
|
3941
3751
|
}
|
|
3942
3752
|
el.innerHTML = sections.join('');
|
|
3943
|
-
startCountdowns();
|
|
3944
3753
|
} catch (err) {
|
|
3945
3754
|
el.innerHTML = '<div class="empty">Could not load: ' + esc(err.message) + '</div>';
|
|
3946
3755
|
}
|
|
@@ -3998,9 +3807,7 @@ async function renderGlobalTokens() {
|
|
|
3998
3807
|
</tr>`).join('');
|
|
3999
3808
|
|
|
4000
3809
|
const projectCount = rows.length;
|
|
4001
|
-
document.getElementById('gt-sub').textContent =
|
|
4002
|
-
? `Token usage across ${projectCount} of ${allProjects.length} projects`
|
|
4003
|
-
: `Token usage across ${projectCount} project${projectCount !== 1 ? 's' : ''}`;
|
|
3810
|
+
document.getElementById('gt-sub').textContent = `Token usage across ${projectCount} project${projectCount !== 1 ? 's' : ''}`;
|
|
4004
3811
|
|
|
4005
3812
|
el.innerHTML = `<div id="gt-cards" style="display:flex;gap:10px;flex-wrap:wrap;margin-bottom:20px">${cards}</div>
|
|
4006
3813
|
<div id="gt-table">
|
|
@@ -4021,6 +3828,217 @@ async function renderGlobalTokens() {
|
|
|
4021
3828
|
}
|
|
4022
3829
|
}
|
|
4023
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
|
+
|
|
4024
4042
|
// ── feature 4: budget cap + desktop notification ───────────
|
|
4025
4043
|
let budget = (function(){ try { return JSON.parse(localStorage.getItem('mm-budget') || '{}'); } catch { return {}; } })();
|
|
4026
4044
|
|
|
@@ -4045,7 +4063,6 @@ function saveBudget() {
|
|
|
4045
4063
|
closeBudgetModal();
|
|
4046
4064
|
checkBudget(); // check immediately
|
|
4047
4065
|
updateBudgetBtnStyle();
|
|
4048
|
-
showToast('Budget saved', budget.daily || budget.monthly ? `Daily: ${budget.daily ? '$'+budget.daily : '—'} · Monthly: ${budget.monthly ? '$'+budget.monthly : '—'}` : 'Budget cleared', 'ok');
|
|
4049
4066
|
}
|
|
4050
4067
|
|
|
4051
4068
|
function updateBudgetBtnStyle() {
|
|
@@ -4057,36 +4074,21 @@ function updateBudgetBtnStyle() {
|
|
|
4057
4074
|
|
|
4058
4075
|
function checkBudget() {
|
|
4059
4076
|
const cost = alertState.todayCost;
|
|
4060
|
-
|
|
4061
|
-
|
|
4062
|
-
if (budget.daily && cost) {
|
|
4077
|
+
if (!cost) return;
|
|
4078
|
+
if (budget.daily) {
|
|
4063
4079
|
const pct = cost / budget.daily;
|
|
4064
4080
|
if (pct >= 1 && !dismissedAlerts.has('budget-daily-over')) {
|
|
4065
4081
|
alertState.budgetAlert = `Daily budget exceeded: $${cost.toFixed(2)} / $${budget.daily}`;
|
|
4066
4082
|
alertState.budgetCls = 'alert-crit';
|
|
4067
|
-
updateAlerts(); return;
|
|
4068
4083
|
} else if (pct >= 0.8 && !dismissedAlerts.has('budget-daily-warn')) {
|
|
4069
4084
|
alertState.budgetAlert = `Approaching daily budget: $${cost.toFixed(2)} / $${budget.daily}`;
|
|
4070
4085
|
alertState.budgetCls = 'alert-warn';
|
|
4071
4086
|
maybeNotify('monomind budget', `$${cost.toFixed(2)} of $${budget.daily} daily budget used`);
|
|
4072
|
-
|
|
4073
|
-
|
|
4074
|
-
}
|
|
4075
|
-
// Monthly budget check
|
|
4076
|
-
if (budget.monthly && moCost) {
|
|
4077
|
-
const mpct = moCost / budget.monthly;
|
|
4078
|
-
if (mpct >= 1 && !dismissedAlerts.has('budget-monthly-over')) {
|
|
4079
|
-
alertState.budgetAlert = `Monthly budget exceeded: $${moCost.toFixed(2)} / $${budget.monthly}`;
|
|
4080
|
-
alertState.budgetCls = 'alert-crit';
|
|
4081
|
-
updateAlerts(); return;
|
|
4082
|
-
} else if (mpct >= 0.8 && !dismissedAlerts.has('budget-monthly-warn')) {
|
|
4083
|
-
alertState.budgetAlert = `Approaching monthly budget: $${moCost.toFixed(2)} / $${budget.monthly}`;
|
|
4084
|
-
alertState.budgetCls = 'alert-warn';
|
|
4085
|
-
updateAlerts(); return;
|
|
4087
|
+
} else {
|
|
4088
|
+
alertState.budgetAlert = null;
|
|
4086
4089
|
}
|
|
4090
|
+
updateAlerts();
|
|
4087
4091
|
}
|
|
4088
|
-
alertState.budgetAlert = null;
|
|
4089
|
-
updateAlerts();
|
|
4090
4092
|
}
|
|
4091
4093
|
|
|
4092
4094
|
function maybeNotify(title, body) {
|
|
@@ -4208,7 +4210,7 @@ function buildBreakdownByName(events) {
|
|
|
4208
4210
|
const pct = Math.round(cnt / total * 100);
|
|
4209
4211
|
return `<div class="tb-row">
|
|
4210
4212
|
<div class="tb-lbl" style="width:54px" title="${esc(name)}">${esc(name.length > 8 ? name.slice(0,7)+'…' : name)}</div>
|
|
4211
|
-
<div class="tb-bar-wrap"
|
|
4213
|
+
<div class="tb-bar-wrap"><div class="tb-bar" style="width:${pct}%;background:${getColor(name)}"></div></div>
|
|
4212
4214
|
<div class="tb-count">${cnt}</div>
|
|
4213
4215
|
</div>`;
|
|
4214
4216
|
}).join('');
|
|
@@ -4232,7 +4234,7 @@ function buildDigest() {
|
|
|
4232
4234
|
const todayStart = new Date(); todayStart.setHours(0, 0, 0, 0);
|
|
4233
4235
|
const todaySessions = allSessions.filter(s => {
|
|
4234
4236
|
const t = s.lastTs || s.mtime;
|
|
4235
|
-
return t && new Date(typeof t === 'number' ? t :
|
|
4237
|
+
return t && new Date(typeof t === 'number' ? t : t).getTime() >= todayStart.getTime();
|
|
4236
4238
|
});
|
|
4237
4239
|
if (!todaySessions.length) return;
|
|
4238
4240
|
|
|
@@ -4251,8 +4253,8 @@ function buildDigest() {
|
|
|
4251
4253
|
const stats = [
|
|
4252
4254
|
`${todaySessions.length} session${todaySessions.length > 1 ? 's' : ''}`,
|
|
4253
4255
|
totalCost > 0 ? `$${totalCost.toFixed(2)} spent` : null,
|
|
4254
|
-
totalTools > 0 ? `${totalTools
|
|
4255
|
-
totalMsgs > 0 ? `${totalMsgs
|
|
4256
|
+
totalTools > 0 ? `${totalTools} tool calls` : null,
|
|
4257
|
+
totalMsgs > 0 ? `${totalMsgs} messages` : null,
|
|
4256
4258
|
longestMs > 0 ? `${fmtDur(longestMs)} longest` : null,
|
|
4257
4259
|
...themes.map(t => `#${t}`),
|
|
4258
4260
|
].filter(Boolean);
|
|
@@ -4264,7 +4266,7 @@ function buildDigest() {
|
|
|
4264
4266
|
const monthCostSoFar = allSessions.filter(s => {
|
|
4265
4267
|
const t = s.firstTs || s.mtime;
|
|
4266
4268
|
if (!t) return false;
|
|
4267
|
-
const d = new Date(typeof t === 'number' ? t :
|
|
4269
|
+
const d = new Date(typeof t === 'number' ? t : t);
|
|
4268
4270
|
return d.getFullYear() === today2.getFullYear() && d.getMonth() === today2.getMonth();
|
|
4269
4271
|
}).reduce((a, s) => a + (s.totalCost || 0), 0);
|
|
4270
4272
|
const dailyAvg = dayOfMonth > 0 ? monthCostSoFar / dayOfMonth : 0;
|
|
@@ -4333,25 +4335,23 @@ function toggleLeaderboard() {
|
|
|
4333
4335
|
}
|
|
4334
4336
|
|
|
4335
4337
|
function renderLeaderboard() {
|
|
4336
|
-
const all = [...allSessions]
|
|
4337
|
-
.filter(s => typeof s.totalCost === 'number' && s.totalCost > 0)
|
|
4338
|
-
.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);
|
|
4339
4339
|
const sorted = all.slice(0, 15);
|
|
4340
4340
|
const body = document.getElementById('lb-body');
|
|
4341
|
-
|
|
4342
|
-
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; }
|
|
4343
4342
|
body.innerHTML = sorted.map((s, i) => {
|
|
4344
4343
|
const cost = '$' + s.totalCost.toFixed(2);
|
|
4345
4344
|
const dur = s.totalDurationMs ? fmtDur(s.totalDurationMs) : '—';
|
|
4346
4345
|
const prompt = s.lastPrompt || s.id;
|
|
4347
4346
|
return `<tr onclick="jumpToSession('${esc(s.id)}')" title="${esc(prompt)}">
|
|
4348
4347
|
<td class="lb-rank">${i + 1}</td>
|
|
4349
|
-
<td class="lb-prompt">${esc(prompt.slice(0, 60))}
|
|
4348
|
+
<td class="lb-prompt">${esc(prompt.slice(0, 60))}</td>
|
|
4350
4349
|
<td class="lb-cost">${cost}</td>
|
|
4351
4350
|
<td class="lb-dur">${dur}</td>
|
|
4352
4351
|
</tr>`;
|
|
4353
4352
|
}).join('');
|
|
4354
|
-
|
|
4353
|
+
const ov = document.getElementById('lb-overflow');
|
|
4354
|
+
if (ov) ov.textContent = all.length > 15 ? `Showing top 15 of ${all.length} sessions` : '';
|
|
4355
4355
|
}
|
|
4356
4356
|
|
|
4357
4357
|
// ── feature 12: session diff ──────────────────────────────
|
|
@@ -4468,7 +4468,7 @@ async function exportSession() {
|
|
|
4468
4468
|
const events = data.events || [];
|
|
4469
4469
|
const lines = [
|
|
4470
4470
|
`# Session: ${sess.lastPrompt || sess.id}`,
|
|
4471
|
-
`> ${new Date(
|
|
4471
|
+
`> ${new Date(sess.lastTs || sess.mtime).toLocaleString()}`,
|
|
4472
4472
|
sess.totalCost != null ? `> Cost: $${sess.totalCost.toFixed(2)}` : '',
|
|
4473
4473
|
sess.totalDurationMs ? `> Duration: ${fmtDur(sess.totalDurationMs)}` : '',
|
|
4474
4474
|
'',
|
|
@@ -4476,7 +4476,7 @@ async function exportSession() {
|
|
|
4476
4476
|
for (const ev of events) {
|
|
4477
4477
|
if (ev.kind === 'user' && ev.text?.trim()) {
|
|
4478
4478
|
lines.push(`\n## ${ev.text.trim().slice(0, 80)}`);
|
|
4479
|
-
if (ev.ts) lines.push(`_${new Date(
|
|
4479
|
+
if (ev.ts) lines.push(`_${new Date(ev.ts).toLocaleTimeString()}_`);
|
|
4480
4480
|
} else if (ev.kind === 'tool') {
|
|
4481
4481
|
const label = ev.label || ev.name || ev.cat;
|
|
4482
4482
|
lines.push(`- \`${ev.name || ev.cat}\`${label ? ': ' + label : ''}${ev._errored ? ' ⚠ error' : ''}`);
|
|
@@ -4552,9 +4552,9 @@ function renderBurnGauge() {
|
|
|
4552
4552
|
}
|
|
4553
4553
|
const now = Date.now();
|
|
4554
4554
|
// calls in last 5 min, 15 min, 60 min
|
|
4555
|
-
const t5 = tools.filter(e => now - (
|
|
4556
|
-
const t15 = tools.filter(e => now - (
|
|
4557
|
-
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;
|
|
4558
4558
|
const rate5 = (t5 / 5).toFixed(1); // calls/min
|
|
4559
4559
|
const rate15 = (t15 / 15).toFixed(1);
|
|
4560
4560
|
const rate60 = (t60 / 60).toFixed(1);
|
|
@@ -4583,7 +4583,7 @@ function buildSwimlane() {
|
|
|
4583
4583
|
const LANE_HUES = [75, 200, 300, 150, 25, 220, 340, 120];
|
|
4584
4584
|
const rows = recent.map((s, si) => {
|
|
4585
4585
|
const start = s.firstTs || s.startTs || s.mtime || now;
|
|
4586
|
-
const startMs = typeof start === 'number' ? start :
|
|
4586
|
+
const startMs = typeof start === 'number' ? start : new Date(start).getTime();
|
|
4587
4587
|
const dur = s.totalDurationMs || 60000;
|
|
4588
4588
|
const endMs = startMs + dur;
|
|
4589
4589
|
const leftPct = Math.max(0, Math.min(100, ((startMs - windowStart) / windowMs) * 100));
|
|
@@ -4601,17 +4601,17 @@ function buildSwimlane() {
|
|
|
4601
4601
|
}).join('');
|
|
4602
4602
|
// dead time: find largest gap between consecutive sessions
|
|
4603
4603
|
const sorted = recent.slice().sort((a, b) => {
|
|
4604
|
-
const
|
|
4605
|
-
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();
|
|
4606
4606
|
return aTs - bTs;
|
|
4607
4607
|
});
|
|
4608
4608
|
let maxGapMs = 0; let gapStart = 0;
|
|
4609
4609
|
for (let i = 1; i < sorted.length; i++) {
|
|
4610
4610
|
const prev = sorted[i - 1];
|
|
4611
4611
|
const curr = sorted[i];
|
|
4612
|
-
const
|
|
4612
|
+
const prevTs = typeof (prev.firstTs || prev.mtime) === 'number' ? (prev.firstTs || prev.mtime) : new Date(prev.firstTs || prev.mtime).getTime();
|
|
4613
4613
|
const prevEnd = prevTs + (prev.totalDurationMs || 60000);
|
|
4614
|
-
const
|
|
4614
|
+
const currTs = typeof (curr.firstTs || curr.mtime) === 'number' ? (curr.firstTs || curr.mtime) : new Date(curr.firstTs || curr.mtime).getTime();
|
|
4615
4615
|
const gap = currTs - prevEnd;
|
|
4616
4616
|
if (gap > maxGapMs) { maxGapMs = gap; gapStart = prevEnd; }
|
|
4617
4617
|
}
|
|
@@ -4645,149 +4645,103 @@ function buildLoopSparkline(l) {
|
|
|
4645
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>`;
|
|
4646
4646
|
}
|
|
4647
4647
|
|
|
4648
|
-
function fmtInterval(v) {
|
|
4649
|
-
if (!v && v !== 0) return '';
|
|
4650
|
-
if (typeof v === 'string') return v;
|
|
4651
|
-
const m = parseInt(v);
|
|
4652
|
-
if (isNaN(m) || m <= 0) return String(v);
|
|
4653
|
-
if (m < 60) return m + 'm';
|
|
4654
|
-
if (m % 60 === 0) return (m / 60) + 'h';
|
|
4655
|
-
return Math.floor(m / 60) + 'h ' + (m % 60) + 'm';
|
|
4656
|
-
}
|
|
4657
|
-
|
|
4658
4648
|
function fmtCountdown(nextAt) {
|
|
4659
|
-
const ms =
|
|
4649
|
+
const ms = Number(nextAt) - Date.now();
|
|
4660
4650
|
if (ms <= 0) return 'overdue';
|
|
4661
|
-
const
|
|
4662
|
-
const m = Math.floor(
|
|
4663
|
-
const
|
|
4664
|
-
if (h > 0) return `next in ${h}h ${m}m`;
|
|
4665
|
-
|
|
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`;
|
|
4666
4657
|
}
|
|
4667
4658
|
|
|
4668
|
-
function
|
|
4669
|
-
if (!
|
|
4670
|
-
const
|
|
4671
|
-
if (
|
|
4672
|
-
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`;
|
|
4673
4666
|
}
|
|
4674
4667
|
|
|
4675
4668
|
// ── loops ──────────────────────────────────────────────────
|
|
4669
|
+
const LOOP_STALE_MS = 2 * 60 * 60 * 1000;
|
|
4670
|
+
|
|
4676
4671
|
async function renderLoops() {
|
|
4677
4672
|
const el = document.getElementById('loops-content');
|
|
4678
4673
|
el.innerHTML = '<div class="loading-txt">Loading…</div>';
|
|
4679
4674
|
try {
|
|
4680
4675
|
const data = await apiFetch('/api/loops?dir=' + enc(DIR));
|
|
4681
|
-
|
|
4682
|
-
|
|
4683
|
-
|
|
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 || '—';
|
|
4684
4681
|
if (!loops.length) {
|
|
4685
|
-
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>';
|
|
4686
4683
|
return;
|
|
4687
4684
|
}
|
|
4688
|
-
|
|
4689
|
-
|
|
4690
|
-
|
|
4691
|
-
|
|
4692
|
-
|
|
4693
|
-
if (l.source !== 'schedule_wakeup_hook') return true;
|
|
4694
|
-
const m = (l.prompt || '').match(/--loop\s+\S+\s+(.+)$/s);
|
|
4695
|
-
if (!m) return true;
|
|
4696
|
-
return !repeatPrompts.has(m[1].trim());
|
|
4697
|
-
});
|
|
4698
|
-
el.innerHTML = dedupedLoops.map((l, idx) => {
|
|
4699
|
-
const isHil = l.status === 'hil:pending';
|
|
4700
|
-
const isTillend = l.type === 'tillend';
|
|
4701
|
-
const curRep = l.currentRep || 0;
|
|
4702
|
-
const maxReps = l.maxReps || 0;
|
|
4703
|
-
const nextAt = l.nextRunAt ? parseInt(l.nextRunAt) : 0;
|
|
4704
|
-
// Loops with status 'running'/'waiting'/'active' are explicitly active.
|
|
4705
|
-
// 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;
|
|
4706
4690
|
const isExplicitlyActive = l.status === 'running' || l.status === 'waiting' || l.status === 'active';
|
|
4707
|
-
const
|
|
4708
|
-
const
|
|
4709
|
-
|
|
4710
|
-
|
|
4711
|
-
|
|
4712
|
-
const
|
|
4713
|
-
|
|
4714
|
-
|
|
4715
|
-
|
|
4716
|
-
const
|
|
4717
|
-
const
|
|
4718
|
-
|
|
4719
|
-
const
|
|
4720
|
-
|
|
4721
|
-
|
|
4722
|
-
|
|
4723
|
-
|
|
4724
|
-
|
|
4725
|
-
|
|
4726
|
-
|
|
4727
|
-
|
|
4728
|
-
}
|
|
4729
|
-
const full = _l.prompt || '';
|
|
4730
|
-
const cmdM = full.match(/^(\/\S+)/);
|
|
4731
|
-
if (!cmdM) return { userPrompt: full, command: '', flagsStr: '' };
|
|
4732
|
-
const tokens = full.slice(cmdM[1].length).trim().split(/\s+/);
|
|
4733
|
-
let ti = 0, fp = [];
|
|
4734
|
-
while (ti < tokens.length && tokens[ti] && tokens[ti].startsWith('--')) {
|
|
4735
|
-
fp.push(tokens[ti++]);
|
|
4736
|
-
if (ti < tokens.length && tokens[ti] && !tokens[ti].startsWith('--')) fp.push(tokens[ti++]);
|
|
4737
|
-
}
|
|
4738
|
-
return { userPrompt: tokens.slice(ti).join(' '), command: cmdM[1], flagsStr: fp.join(' ') };
|
|
4739
|
-
})(l);
|
|
4740
|
-
const userPrompt = _lp.userPrompt;
|
|
4741
|
-
const cmdStr = _lp.command;
|
|
4742
|
-
const flagsStr = _lp.flagsStr;
|
|
4743
|
-
const fullPrompt = l.prompt || '';
|
|
4744
|
-
const name = (l.name || userPrompt || cmdStr || 'loop').slice(0, 60);
|
|
4745
|
-
const startedAt = l.startedAt ? new Date(l.startedAt).toLocaleString() : '—';
|
|
4746
|
-
const lastRun = l.lastRunAt ? relTime(l.lastRunAt) : (l.startedAt ? relTime(l.startedAt) : '—');
|
|
4747
|
-
const pct = (!isTillend && maxReps > 0) ? Math.min(100, Math.round(curRep / maxReps * 100)) : 0;
|
|
4748
|
-
const progBar = (!isTillend && maxReps > 0 && running)
|
|
4749
|
-
? `<div class="lp-bar"><div class="lp-fill" style="width:${pct}%"></div></div>`
|
|
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>`
|
|
4750
4713
|
: '';
|
|
4751
|
-
const
|
|
4752
|
-
?
|
|
4753
|
-
: (maxReps > 0 ? `${curRep} / ${maxReps}` : String(curRep || '—'));
|
|
4754
|
-
const cdownSpan = nextAt
|
|
4755
|
-
? ` <span class="loop-cdown${nextAt - Date.now() <= 0 ? ' overdue' : ''}" data-nextat="${nextAt}">${fmtCountdown(nextAt)}</span>`
|
|
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>`
|
|
4756
4716
|
: '';
|
|
4757
|
-
const
|
|
4758
|
-
? `<
|
|
4759
|
-
:
|
|
4760
|
-
const typeBadge = `<span class="loop-type-badge${isTillend ? ' tillend' : ''}">${esc(l.type || 'repeat')}</span>`;
|
|
4761
|
-
const statusClass = isHil ? 'hil' : (running ? 'active' : 'stopped');
|
|
4762
|
-
const statusLabel = isHil ? '⚠ HIL' : (running ? 'active' : (isFinished ? 'done' : 'stopped'));
|
|
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>`;
|
|
4763
4720
|
const hilBanner = isHil
|
|
4764
|
-
? `<div class="loop-hil-banner">⚠
|
|
4721
|
+
? `<div class="loop-hil-banner">⚠ Human-in-the-loop confirmation required — check session for approval prompt</div>`
|
|
4765
4722
|
: '';
|
|
4766
|
-
const
|
|
4767
|
-
|
|
4768
|
-
|
|
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>
|
|
4769
4727
|
<div class="loop-body">
|
|
4770
|
-
<div class="loop-name"
|
|
4771
|
-
<div class="loop-meta">${esc(
|
|
4772
|
-
${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>
|
|
4773
4730
|
${progBar}
|
|
4731
|
+
${hilBanner}
|
|
4774
4732
|
</div>
|
|
4775
|
-
<div class="loop-status ${
|
|
4733
|
+
<div class="loop-status ${statusCls}">${statusLabel}${isHil ? ' ⚠' : ''}</div>
|
|
4776
4734
|
${stopBtn}
|
|
4777
4735
|
</div>
|
|
4778
4736
|
<div class="loop-expand">
|
|
4779
|
-
${
|
|
4780
|
-
${
|
|
4781
|
-
|
|
4782
|
-
<div class="le-row"><div class="le-lbl">
|
|
4783
|
-
<div class="le-row"><div class="le-lbl">
|
|
4784
|
-
<div class="le-row"><div class="le-lbl">Status</div><div class="le-val">${isHil ? '⚠ hil:pending' : (running ? '● running' : (isFinished ? '✓ done' : '○ stopped'))}</div></div>
|
|
4785
|
-
${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>
|
|
4786
4742
|
<div class="le-row"><div class="le-lbl">Started</div><div class="le-val mono">${esc(startedAt)}</div></div>
|
|
4787
|
-
|
|
4788
|
-
<div class="le-row"><div class="le-lbl">
|
|
4789
|
-
<div class="le-row"><div class="le-lbl">${isTillend ? 'Progress' : 'Run count'}</div><div class="le-val">${esc(runCountDisplay)}</div></div>
|
|
4790
|
-
${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>
|
|
4791
4745
|
${buildLoopSparkline(l)}
|
|
4792
4746
|
</div>`;
|
|
4793
4747
|
}).join('');
|
|
@@ -4827,25 +4781,27 @@ async function stopLoop(evt, id) {
|
|
|
4827
4781
|
const btn = evt.currentTarget;
|
|
4828
4782
|
if (!btn.dataset.confirming) {
|
|
4829
4783
|
btn.dataset.confirming = '1';
|
|
4784
|
+
const orig = btn.textContent;
|
|
4830
4785
|
btn.textContent = '■ Confirm?';
|
|
4831
|
-
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)';
|
|
4832
4786
|
btn._resetTimer = setTimeout(() => {
|
|
4833
4787
|
delete btn.dataset.confirming;
|
|
4834
|
-
btn.textContent =
|
|
4835
|
-
btn.style.cssText = '';
|
|
4788
|
+
btn.textContent = orig;
|
|
4836
4789
|
}, 3000);
|
|
4837
4790
|
return;
|
|
4838
4791
|
}
|
|
4839
4792
|
clearTimeout(btn._resetTimer);
|
|
4840
4793
|
delete btn.dataset.confirming;
|
|
4841
4794
|
try {
|
|
4842
|
-
await fetch('/api/loops/stop?dir=' + enc(DIR), {
|
|
4795
|
+
const r = await fetch('/api/loops/stop?dir=' + enc(DIR), {
|
|
4843
4796
|
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
4844
|
-
body: JSON.stringify({ id })
|
|
4797
|
+
body: JSON.stringify({ id }),
|
|
4845
4798
|
});
|
|
4846
|
-
|
|
4847
|
-
|
|
4848
|
-
|
|
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
|
+
}
|
|
4849
4805
|
}
|
|
4850
4806
|
|
|
4851
4807
|
let _cdownInterval = null;
|
|
@@ -4855,32 +4811,8 @@ function startCountdowns() {
|
|
|
4855
4811
|
_cdownInterval = setInterval(() => {
|
|
4856
4812
|
document.querySelectorAll('.loop-cdown[data-nextat]').forEach(el => {
|
|
4857
4813
|
const ms = parseInt(el.dataset.nextat) - Date.now();
|
|
4858
|
-
if (ms <= 0) {
|
|
4859
|
-
const row = el.closest('.loop-row');
|
|
4860
|
-
const loopStatus = row ? (row.dataset.loopStatus || '') : '';
|
|
4861
|
-
const isActiveStatus = loopStatus === 'running' || loopStatus === 'waiting' || loopStatus === 'active';
|
|
4862
|
-
if (isActiveStatus) {
|
|
4863
|
-
// Loop is between rounds or executing — not done, just waiting for next update
|
|
4864
|
-
el.textContent = 'executing…';
|
|
4865
|
-
el.classList.remove('overdue');
|
|
4866
|
-
} else {
|
|
4867
|
-
el.textContent = 'overdue';
|
|
4868
|
-
el.classList.add('overdue');
|
|
4869
|
-
if (row) {
|
|
4870
|
-
const badge = row.querySelector('.loop-status');
|
|
4871
|
-
if (badge && badge.classList.contains('active')) {
|
|
4872
|
-
badge.classList.remove('active');
|
|
4873
|
-
badge.classList.add('stopped');
|
|
4874
|
-
badge.textContent = 'done';
|
|
4875
|
-
}
|
|
4876
|
-
const stopBtn = row.querySelector('.loop-stop-btn');
|
|
4877
|
-
if (stopBtn) stopBtn.remove();
|
|
4878
|
-
}
|
|
4879
|
-
}
|
|
4880
|
-
return;
|
|
4881
|
-
}
|
|
4882
4814
|
el.textContent = fmtCountdown(el.dataset.nextat);
|
|
4883
|
-
el.classList.
|
|
4815
|
+
el.classList.toggle('overdue', ms <= 0);
|
|
4884
4816
|
});
|
|
4885
4817
|
}, 1000);
|
|
4886
4818
|
}
|
|
@@ -4932,7 +4864,7 @@ function buildEfficiencyPanel() {
|
|
|
4932
4864
|
<span class="eff-lbl" title="${esc(s.lastPrompt||s.id)}">${esc(lbl)}</span>
|
|
4933
4865
|
<span class="eff-pct ${cls}">${pct}%</span>
|
|
4934
4866
|
</div>
|
|
4935
|
-
<div class="eff-bar-wrap"
|
|
4867
|
+
<div class="eff-bar-wrap"><div class="eff-bar-fill" style="width:${pct}%;background:${fillColor}"></div></div>`;
|
|
4936
4868
|
}).join('');
|
|
4937
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}`;
|
|
4938
4870
|
}
|
|
@@ -4969,10 +4901,10 @@ function renderModelMix() {
|
|
|
4969
4901
|
const short = model.replace(/^claude-/,'').replace(/-\d{8}$/,'');
|
|
4970
4902
|
const pct = totalCost > 0 ? Math.round(d.cost/totalCost*100) : 0;
|
|
4971
4903
|
return `<tr>
|
|
4972
|
-
<td style="font-size:11px"
|
|
4904
|
+
<td style="font-size:11px">${esc(short)}</td>
|
|
4973
4905
|
<td class="lb-cost">$${d.cost.toFixed(2)}</td>
|
|
4974
4906
|
<td class="lb-dur">${pct}%</td>
|
|
4975
|
-
<td class="lb-dur">${d.calls
|
|
4907
|
+
<td class="lb-dur">${d.calls}</td>
|
|
4976
4908
|
</tr>`;
|
|
4977
4909
|
}).join('')}
|
|
4978
4910
|
</tbody></table>`;
|
|
@@ -4991,14 +4923,14 @@ function buildWeeklyRecap() {
|
|
|
4991
4923
|
const weekStart = new Date(); weekStart.setDate(weekStart.getDate() - weekStart.getDay()); weekStart.setHours(0,0,0,0);
|
|
4992
4924
|
const weekSess = allSessions.filter(s => {
|
|
4993
4925
|
const t = s.lastTs || s.mtime;
|
|
4994
|
-
return t && new Date(typeof t === 'number' ? t :
|
|
4926
|
+
return t && new Date(typeof t === 'number' ? t : t).getTime() >= weekStart.getTime();
|
|
4995
4927
|
});
|
|
4996
4928
|
if (!weekSess.length) return;
|
|
4997
4929
|
const totalCost = weekSess.reduce((a,s) => a + (s.totalCost||0), 0);
|
|
4998
4930
|
const totalTools = weekSess.reduce((a,s) => a + (s.toolCalls||0), 0);
|
|
4999
4931
|
const days = new Set(weekSess.map(s => {
|
|
5000
4932
|
const t = s.lastTs || s.mtime;
|
|
5001
|
-
return new Date(typeof t === 'number' ? t :
|
|
4933
|
+
return new Date(typeof t === 'number' ? t : t).toDateString();
|
|
5002
4934
|
})).size;
|
|
5003
4935
|
const longestMs = Math.max(...weekSess.map(s => s.totalDurationMs||0));
|
|
5004
4936
|
const streak = calcStreak();
|
|
@@ -5066,12 +4998,11 @@ function buildActivityHeatmap() {
|
|
|
5066
4998
|
for (const s of allSessions) {
|
|
5067
4999
|
const t = s.firstTs || s.mtime;
|
|
5068
5000
|
if (!t) continue;
|
|
5069
|
-
const d = new Date(typeof t === 'number' ? t :
|
|
5001
|
+
const d = new Date(typeof t === 'number' ? t : t);
|
|
5070
5002
|
grid[d.getDay()][d.getHours()]++;
|
|
5071
5003
|
}
|
|
5072
5004
|
const maxVal = Math.max(1, ...grid.flat());
|
|
5073
5005
|
const DAYS = ['Su','Mo','Tu','We','Th','Fr','Sa'];
|
|
5074
|
-
const FULL_DAYS = ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'];
|
|
5075
5006
|
let html = '<div class="heatmap-grid">';
|
|
5076
5007
|
html += '<div class="heatmap-row"><div class="heatmap-lbl"></div>';
|
|
5077
5008
|
for (let h = 0; h < 24; h++) {
|
|
@@ -5084,7 +5015,7 @@ function buildActivityHeatmap() {
|
|
|
5084
5015
|
const v = grid[d][h];
|
|
5085
5016
|
const alpha = v > 0 ? Math.max(0.18, v/maxVal).toFixed(2) : 0;
|
|
5086
5017
|
const bg = v > 0 ? `oklch(65% 0.18 200 / ${alpha})` : 'transparent';
|
|
5087
|
-
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>`;
|
|
5088
5019
|
}
|
|
5089
5020
|
html += '</div>';
|
|
5090
5021
|
}
|
|
@@ -5166,8 +5097,8 @@ function v2RenderOrgList() {
|
|
|
5166
5097
|
return `<div class="org-item${active}" data-org="${esc(o.name)}" onclick="v2SelectOrg(this.dataset.org)">
|
|
5167
5098
|
<div class="oi-dot ${o.running ? 'running' : ''}"></div>
|
|
5168
5099
|
<div class="oi-body">
|
|
5169
|
-
<div class="oi-name"
|
|
5170
|
-
${goalSnip ? `<div class="oi-goal"
|
|
5100
|
+
<div class="oi-name">${esc(o.name)}</div>
|
|
5101
|
+
${goalSnip ? `<div class="oi-goal">${esc(goalSnip)}</div>` : ''}
|
|
5171
5102
|
<div class="oi-chips">
|
|
5172
5103
|
${o.running ? '<span class="oi-chip live">LIVE</span>' : ''}
|
|
5173
5104
|
<span class="oi-chip">${rolesN} roles</span>
|
|
@@ -5633,7 +5564,7 @@ function v2RenderOrgRoles() {
|
|
|
5633
5564
|
const pane = document.getElementById('odt-roles');
|
|
5634
5565
|
if (!pane) return;
|
|
5635
5566
|
if (!roles.length) {
|
|
5636
|
-
pane.innerHTML = '<div class="empty">No roles defined
|
|
5567
|
+
pane.innerHTML = '<div class="empty">No roles defined</div>';
|
|
5637
5568
|
return;
|
|
5638
5569
|
}
|
|
5639
5570
|
// Determine leader: explicit reports_to=undefined + type=planner/coordinator, or first role, or id=boss
|
|
@@ -5705,7 +5636,7 @@ function v2RenderAgentDrawer(data) {
|
|
|
5705
5636
|
headEl.innerHTML = `
|
|
5706
5637
|
<img class="oad-avatar" src="${avatar}" alt="${esc(name)}" onerror="this.src='data/avatars/coder.svg'"/>
|
|
5707
5638
|
<div class="oad-id">
|
|
5708
|
-
<div class="oad-name"
|
|
5639
|
+
<div class="oad-name">${esc(name)}</div>
|
|
5709
5640
|
<div class="oad-pills">
|
|
5710
5641
|
${type ? `<span class="oad-pill">${esc(type)}</span>` : ''}
|
|
5711
5642
|
${model ? `<span class="oad-pill model">${esc(model)}</span>` : ''}
|
|
@@ -5783,7 +5714,7 @@ function v2RenderOrgActivity() {
|
|
|
5783
5714
|
if (!_v2SelOrg) return;
|
|
5784
5715
|
const activity = _v2OrgData?._activity || [];
|
|
5785
5716
|
const orgEvents = _v2OrgEventLog[_v2SelOrg] || [];
|
|
5786
|
-
const events = [...activity, ...orgEvents].sort((a,b) => (
|
|
5717
|
+
const events = [...activity, ...orgEvents].sort((a,b) => (b.ts||0)-(a.ts||0)).slice(0,80);
|
|
5787
5718
|
const pane = document.getElementById('odt-activity');
|
|
5788
5719
|
if (!pane) return;
|
|
5789
5720
|
const fmtOrgEvType = t => {
|
|
@@ -5791,14 +5722,13 @@ function v2RenderOrgActivity() {
|
|
|
5791
5722
|
return m[t]||(t||'').replace(/^org:/,'');
|
|
5792
5723
|
};
|
|
5793
5724
|
if (!events.length) {
|
|
5794
|
-
pane.innerHTML = '<div class="empty">No activity recorded
|
|
5725
|
+
pane.innerHTML = '<div class="empty">No activity recorded</div>';
|
|
5795
5726
|
return;
|
|
5796
5727
|
}
|
|
5797
5728
|
pane.innerHTML = `<div class="act-v2-list">${events.map(ev => {
|
|
5798
|
-
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'}) : '';
|
|
5799
5730
|
const detail = ev.role||ev.msg||ev.agent||'';
|
|
5800
|
-
|
|
5801
|
-
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>`;
|
|
5802
5732
|
}).join('')}</div>`;
|
|
5803
5733
|
}
|
|
5804
5734
|
|
|
@@ -5836,12 +5766,12 @@ function v2RenderOrgHeartbeats() {
|
|
|
5836
5766
|
status: a.status || 'idle',
|
|
5837
5767
|
}));
|
|
5838
5768
|
}
|
|
5839
|
-
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; }
|
|
5840
5770
|
const cls = (st) => (st === 'active' || st === 'running') ? 'on' : ((st === 'error' || st === 'failed') ? 'warn' : '');
|
|
5841
5771
|
pane.innerHTML = `<div class="m-group-title">Agent Heartbeats</div>` +
|
|
5842
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">
|
|
5843
|
-
<span style="color:var(--text-hi);font-family:var(--mono);flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap"
|
|
5844
|
-
<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>
|
|
5845
5775
|
<span class="ss-pill ${cls(h.status)}">${esc(String(h.status || 'idle').toUpperCase())}</span>
|
|
5846
5776
|
</div>`).join('');
|
|
5847
5777
|
}
|
|
@@ -5860,15 +5790,15 @@ function v2RenderOrgTasks() {
|
|
|
5860
5790
|
(Array.isArray(items) ? items : []).forEach(t => tasks.push({ ...t, status: t.status || col }));
|
|
5861
5791
|
}
|
|
5862
5792
|
}
|
|
5863
|
-
if (!tasks.length) { pane.innerHTML = '<div class="empty">No tasks
|
|
5793
|
+
if (!tasks.length) { pane.innerHTML = '<div class="empty">No tasks</div>'; return; }
|
|
5864
5794
|
const rank = { running: 0, doing: 0, todo: 1, pending: 1, done: 2 };
|
|
5865
5795
|
tasks.sort((a, b) => (rank[a.status] ?? 1) - (rank[b.status] ?? 1));
|
|
5866
5796
|
const pill = (st) => st === 'done' ? 'on' : (st === 'running' || st === 'doing' ? 'warn' : '');
|
|
5867
5797
|
pane.innerHTML = `<div class="m-group-title">Tasks (${tasks.length})</div>` +
|
|
5868
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">
|
|
5869
5799
|
<span class="ss-pill ${pill(t.status)}">${esc(t.status || '?')}</span>
|
|
5870
|
-
<span style="flex:1;color:var(--text-hi);font-family:var(--mono);font-size:11px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap"
|
|
5871
|
-
<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>
|
|
5872
5802
|
</div>`).join('');
|
|
5873
5803
|
}
|
|
5874
5804
|
|
|
@@ -5892,7 +5822,7 @@ function v2RenderOrgCosts() {
|
|
|
5892
5822
|
? c.map(r => ({ label: r.label ?? r.name ?? '—', cost: Number(r.value ?? r.cost ?? 0), tin: 0, tout: 0 }))
|
|
5893
5823
|
: Object.entries(c).map(([k, v]) => ({ label: k, cost: Number(v) || 0, tin: 0, tout: 0 }));
|
|
5894
5824
|
}
|
|
5895
|
-
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; }
|
|
5896
5826
|
const cur = (b && b.currency) || 'USD';
|
|
5897
5827
|
const period = (b && b.period) || '';
|
|
5898
5828
|
const total = rows.reduce((s, r) => s + r.cost, 0);
|
|
@@ -5904,7 +5834,7 @@ function v2RenderOrgCosts() {
|
|
|
5904
5834
|
<span style="color:var(--accent);font-family:var(--mono);font-size:15px;font-weight:600">$${total.toFixed(4)}</span>
|
|
5905
5835
|
</div>` +
|
|
5906
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">
|
|
5907
|
-
<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>
|
|
5908
5838
|
<span style="color:var(--text-lo);font-family:var(--mono);font-size:10px;white-space:nowrap">${(r.tin + r.tout).toLocaleString()} tok</span>
|
|
5909
5839
|
<span style="color:var(--accent);font-family:var(--mono);white-space:nowrap">$${r.cost.toFixed(4)}</span>
|
|
5910
5840
|
</div>`).join('') +
|
|
@@ -5921,14 +5851,14 @@ function v2RenderOrgMembers() {
|
|
|
5921
5851
|
const roles = Array.isArray(_v2OrgData?.roles) ? _v2OrgData.roles : [];
|
|
5922
5852
|
const list = members.length ? members : roles;
|
|
5923
5853
|
const joinReqs = Array.isArray(_v2OrgData?._joinRequests) ? _v2OrgData._joinRequests : [];
|
|
5924
|
-
if (!list.length) { pane.innerHTML = '<div class="empty">No members
|
|
5854
|
+
if (!list.length) { pane.innerHTML = '<div class="empty">No members</div>'; return; }
|
|
5925
5855
|
const src = members.length ? 'joined members' : 'defined roles';
|
|
5926
5856
|
const active = (r) => r.running || r.active || r.status === 'active';
|
|
5927
5857
|
pane.innerHTML = `<div class="m-group-title">Members (${list.length}) · ${src}</div>` +
|
|
5928
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">
|
|
5929
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>
|
|
5930
5860
|
<div style="flex:1;min-width:0">
|
|
5931
|
-
<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>
|
|
5932
5862
|
<div style="color:var(--text-lo);font-size:10px">${esc(r.title || r.type || r.role || '')}</div>
|
|
5933
5863
|
</div>
|
|
5934
5864
|
<span class="ss-pill ${active(r) ? 'on' : ''}">${active(r) ? 'ACTIVE' : 'IDLE'}</span>
|
|
@@ -5942,7 +5872,7 @@ function v2RenderOrgGoals() {
|
|
|
5942
5872
|
const el = document.getElementById('odt-goals');
|
|
5943
5873
|
if (!el || !_v2OrgData) return;
|
|
5944
5874
|
const goals = _v2OrgData.goals || _v2OrgData.config?.goals || [];
|
|
5945
|
-
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; }
|
|
5946
5876
|
function renderGoal(g, depth) {
|
|
5947
5877
|
if (depth > 20) return ''; // Depth guard to prevent stack overflow
|
|
5948
5878
|
const indent = depth * 20;
|
|
@@ -5966,7 +5896,7 @@ function v2RenderOrgBoard() {
|
|
|
5966
5896
|
const el = document.getElementById('odt-board');
|
|
5967
5897
|
if (!el || !_v2OrgData) return;
|
|
5968
5898
|
const issues = _v2OrgData._issues || [];
|
|
5969
|
-
if (!issues.length) { el.innerHTML = '<div class="empty">No issues
|
|
5899
|
+
if (!issues.length) { el.innerHTML = '<div class="empty">No issues</div>'; return; }
|
|
5970
5900
|
const cols = ['open', 'in_progress', 'blocked', 'done', 'cancelled'];
|
|
5971
5901
|
const PRIORITY = { urgent: '🔴', high: '🟠', medium: '🟡', low: '🟢' };
|
|
5972
5902
|
el.innerHTML = `<div style="display:flex;gap:10px;overflow-x:auto;padding-bottom:8px">` +
|
|
@@ -5975,10 +5905,9 @@ function v2RenderOrgBoard() {
|
|
|
5975
5905
|
return `<div style="min-width:160px;flex:1">
|
|
5976
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>
|
|
5977
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">
|
|
5978
|
-
<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>
|
|
5979
5909
|
${i.assignee ? `<div style="font-size:10px;color:var(--text-lo);margin-top:3px">${esc(i.assignee)}</div>` : ''}
|
|
5980
5910
|
</div>`).join('')}
|
|
5981
|
-
${cards.length > 15 ? `<div style="font-size:11px;color:var(--text-xs);text-align:center;padding:4px 0">+${cards.length - 15} more</div>` : ''}
|
|
5982
5911
|
</div>`;
|
|
5983
5912
|
}).join('') + `</div>`;
|
|
5984
5913
|
}
|
|
@@ -6002,9 +5931,9 @@ function v2RenderOrgLive() {
|
|
|
6002
5931
|
<div class="m-group-title" style="margin-bottom:6px">Activity Feed</div>
|
|
6003
5932
|
<div style="max-height:240px;overflow-y:auto;font-size:11px;font-family:var(--mono)">
|
|
6004
5933
|
${(_v2OrgData._activity || []).slice(-30).reverse().map(e => `<div style="padding:3px 0;border-bottom:1px solid var(--border);color:var(--text-lo)">
|
|
6005
|
-
|
|
5934
|
+
${esc(relTime(e.ts || e.timestamp || e.created_at))}
|
|
6006
5935
|
<span style="color:var(--text-mid);margin-left:6px">${esc(e.type || e.kind || e.event || '—')}</span>
|
|
6007
|
-
${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>` : ''}
|
|
6008
5937
|
</div>`).join('')}
|
|
6009
5938
|
</div>`;
|
|
6010
5939
|
// auto-refresh every 5s while tab is active
|
|
@@ -6029,7 +5958,7 @@ async function v2RenderOrgApprovals() {
|
|
|
6029
5958
|
const _enc = encodeURIComponent(_v2SelOrg);
|
|
6030
5959
|
const data = await fetch(`/api/org/${_enc}/approvals${DIR ? '?dir=' + encodeURIComponent(DIR) : ''}`).then(r => r.ok ? r.json() : []).catch(() => []);
|
|
6031
5960
|
const approvals = Array.isArray(data) ? data : (data.approvals || []);
|
|
6032
|
-
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; }
|
|
6033
5962
|
el.innerHTML = `<table style="width:100%;border-collapse:collapse;font-size:12px">
|
|
6034
5963
|
<thead><tr style="color:var(--text-xs);text-align:left">
|
|
6035
5964
|
<th style="padding:6px 8px">Requester</th><th>Action</th><th>Status</th><th>Date</th><th></th>
|
|
@@ -6040,13 +5969,13 @@ async function v2RenderOrgApprovals() {
|
|
|
6040
5969
|
const aid = a.id || '';
|
|
6041
5970
|
return `<tr style="border-top:1px solid var(--border)">
|
|
6042
5971
|
<td style="padding:6px 8px;color:var(--text-hi)">${esc(a.requester || a.agent || '—')}</td>
|
|
6043
|
-
<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>
|
|
6044
5973
|
<td style="padding:6px 8px"><span class="ss-pill ${cls}">${esc(a.status || 'pending')}</span></td>
|
|
6045
|
-
<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>
|
|
6046
5975
|
<td style="padding:6px 8px;white-space:nowrap">
|
|
6047
5976
|
${pending
|
|
6048
|
-
? `<button class="btn" style="font-size:10px;color:var(--green);border-color:var(--green)" title="Approve" aria-label="Approve" onclick="orgApprovalAction(${JSON.stringify(aid)
|
|
6049
|
-
<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>`
|
|
6050
5979
|
: ''}
|
|
6051
5980
|
</td>
|
|
6052
5981
|
</tr>`;
|
|
@@ -6082,7 +6011,7 @@ async function v2RenderOrgSecrets() {
|
|
|
6082
6011
|
${s.purpose ? `<span style="color:var(--text-lo)">${esc(s.purpose)}</span>` : ''}
|
|
6083
6012
|
<span style="margin-left:auto;font-family:var(--mono);color:var(--border)">••••••••</span>
|
|
6084
6013
|
</div>`).join('')
|
|
6085
|
-
: '<div class="empty">No secrets configured
|
|
6014
|
+
: '<div class="empty">No secrets configured</div>');
|
|
6086
6015
|
} catch (e) { el.innerHTML = '<div class="empty">Failed: ' + esc(e.message) + '</div>'; }
|
|
6087
6016
|
}
|
|
6088
6017
|
|
|
@@ -6099,19 +6028,19 @@ function v2RenderOrgSettings() {
|
|
|
6099
6028
|
el.innerHTML = `
|
|
6100
6029
|
<div style="font-size:11px;color:var(--text-lo);margin-bottom:14px">Changes generate a CLI command — no direct writes from UI.</div>
|
|
6101
6030
|
<div style="display:flex;flex-direction:column;gap:12px;max-width:380px">
|
|
6102
|
-
<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>
|
|
6103
6032
|
<div><div class="le-lbl">Topology</div>
|
|
6104
|
-
<select id="os-topo" class="filter-input"
|
|
6033
|
+
<select id="os-topo" class="filter-input" style="cursor:pointer">
|
|
6105
6034
|
${topos.map(t => `<option ${d.topology === t ? 'selected' : ''}>${esc(t)}</option>`).join('')}
|
|
6106
6035
|
</select>
|
|
6107
6036
|
</div>
|
|
6108
6037
|
<div><div class="le-lbl">Governance</div>
|
|
6109
|
-
<select id="os-gov" class="filter-input"
|
|
6038
|
+
<select id="os-gov" class="filter-input" style="cursor:pointer">
|
|
6110
6039
|
${govs.map(g => `<option ${govVal === g ? 'selected' : ''}>${esc(g)}</option>`).join('')}
|
|
6111
6040
|
</select>
|
|
6112
6041
|
</div>
|
|
6113
|
-
<div><div class="le-lbl">Budget (tokens)</div><input id="os-budget" class="filter-input" type="number"
|
|
6114
|
-
<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>
|
|
6115
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>
|
|
6116
6045
|
</div>`;
|
|
6117
6046
|
}
|
|
@@ -6141,7 +6070,7 @@ async function v2RenderOrgRoutines() {
|
|
|
6141
6070
|
const _enc = encodeURIComponent(_v2SelOrg);
|
|
6142
6071
|
const data = await fetch(`/api/org/${_enc}/routines${DIR ? '?dir=' + encodeURIComponent(DIR) : ''}`).then(r => r.ok ? r.json() : []).catch(() => []);
|
|
6143
6072
|
const routines = Array.isArray(data) ? data : (data.routines || _v2OrgData?.config?.routines || []);
|
|
6144
|
-
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; }
|
|
6145
6074
|
el.innerHTML = `<table style="width:100%;border-collapse:collapse;font-size:12px">
|
|
6146
6075
|
<thead><tr style="color:var(--text-xs);text-align:left">
|
|
6147
6076
|
<th style="padding:5px 8px">Name</th><th>Cron</th><th>Last Run</th><th>Status</th>
|
|
@@ -6149,7 +6078,7 @@ async function v2RenderOrgRoutines() {
|
|
|
6149
6078
|
routines.map(r => `<tr style="border-top:1px solid var(--border)">
|
|
6150
6079
|
<td style="padding:6px 8px;color:var(--text-hi)">${esc(r.name || '—')}</td>
|
|
6151
6080
|
<td style="padding:6px 8px;font-family:var(--mono);color:var(--text-lo)">${esc(r.cron || r.schedule || '—')}</td>
|
|
6152
|
-
<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>
|
|
6153
6082
|
<td style="padding:6px 8px"><span class="ss-pill ${r.active || r.status === 'active' ? 'on' : ''}">${esc(r.status || '—')}</span></td>
|
|
6154
6083
|
</tr>`).join('') + '</tbody></table>';
|
|
6155
6084
|
} catch (e) { el.innerHTML = '<div class="empty">Failed: ' + esc(e.message) + '</div>'; }
|
|
@@ -6164,7 +6093,7 @@ async function v2RenderOrgMyIssues() {
|
|
|
6164
6093
|
const _enc = encodeURIComponent(_v2SelOrg);
|
|
6165
6094
|
const data = await fetch(`/api/org/${_enc}/my-issues${DIR ? '?dir=' + encodeURIComponent(DIR) : ''}`).then(r => r.ok ? r.json() : []).catch(() => []);
|
|
6166
6095
|
const issues = Array.isArray(data) ? data : (data.issues || []);
|
|
6167
|
-
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; }
|
|
6168
6097
|
const PRIORITY = { urgent: '🔴', high: '🟠', medium: '🟡', low: '🟢' };
|
|
6169
6098
|
el.innerHTML = `<table style="width:100%;border-collapse:collapse;font-size:12px">
|
|
6170
6099
|
<thead><tr style="color:var(--text-xs);text-align:left">
|
|
@@ -6174,9 +6103,9 @@ async function v2RenderOrgMyIssues() {
|
|
|
6174
6103
|
const cls = i.status === 'done' ? 'on' : i.status === 'blocked' ? 'warn' : '';
|
|
6175
6104
|
return `<tr style="border-top:1px solid var(--border)">
|
|
6176
6105
|
<td style="padding:5px 4px">${PRIORITY[i.priority] || '·'}</td>
|
|
6177
|
-
<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>
|
|
6178
6107
|
<td style="padding:5px 8px"><span class="ss-pill ${cls}">${esc(i.status || 'open')}</span></td>
|
|
6179
|
-
<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>
|
|
6180
6109
|
</tr>`;
|
|
6181
6110
|
}).join('') + '</tbody></table>';
|
|
6182
6111
|
} catch (e) { el.innerHTML = '<div class="empty">Failed: ' + esc(e.message) + '</div>'; }
|
|
@@ -6205,21 +6134,19 @@ function v2RenderOrgBudgets() {
|
|
|
6205
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>`;
|
|
6206
6135
|
}
|
|
6207
6136
|
if (agents.length) {
|
|
6208
|
-
const shownAgents = agents.slice(0, 20);
|
|
6209
6137
|
html += '<div class="m-group-title" style="margin-bottom:6px">Per Agent</div>' +
|
|
6210
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>` +
|
|
6211
|
-
|
|
6139
|
+
agents.slice(0, 20).map(a => {
|
|
6212
6140
|
const over = a.budgetLimit && ((a.tokensIn || 0) + (a.tokensOut || 0)) > a.budgetLimit;
|
|
6213
6141
|
return `<tr style="border-top:1px solid var(--border)${over ? ';color:var(--red)' : ''}">
|
|
6214
|
-
<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>
|
|
6215
6143
|
<td style="padding:4px 8px;color:var(--text-lo)">${Number(a.tokensIn || 0).toLocaleString()}</td>
|
|
6216
6144
|
<td style="padding:4px 8px;color:var(--text-lo)">${Number(a.tokensOut || 0).toLocaleString()}</td>
|
|
6217
6145
|
<td style="padding:4px 8px;color:var(--accent)">$${Number(a.cost || 0).toFixed(4)}${over ? ' ⚠' : ''}</td>
|
|
6218
6146
|
</tr>`;
|
|
6219
|
-
}).join('') + '</tbody></table>'
|
|
6220
|
-
(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>';
|
|
6221
6148
|
}
|
|
6222
|
-
el.innerHTML = html || '<div class="empty">No budget data
|
|
6149
|
+
el.innerHTML = html || '<div class="empty">No budget data</div>';
|
|
6223
6150
|
}
|
|
6224
6151
|
|
|
6225
6152
|
// ── PLUGINS ────────────────────────────────────────────────
|
|
@@ -6231,14 +6158,14 @@ async function v2RenderOrgPlugins() {
|
|
|
6231
6158
|
const _enc = encodeURIComponent(_v2SelOrg);
|
|
6232
6159
|
const data = await fetch(`/api/org/${_enc}/plugins${DIR ? '?dir=' + encodeURIComponent(DIR) : ''}`).then(r => r.ok ? r.json() : []).catch(() => []);
|
|
6233
6160
|
const plugins = Array.isArray(data) ? data : (data.plugins || []);
|
|
6234
|
-
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; }
|
|
6235
6162
|
el.innerHTML = `<div class="proj-grid">` +
|
|
6236
6163
|
plugins.map(p => {
|
|
6237
6164
|
const status = p.status || 'installed';
|
|
6238
6165
|
const col = status === 'error' ? 'var(--red)' : status === 'installed' ? 'var(--accent)' : 'var(--text-lo)';
|
|
6239
6166
|
return `<div class="proj-card">
|
|
6240
|
-
<div class="proj-card-name"
|
|
6241
|
-
<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>
|
|
6242
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>
|
|
6243
6170
|
</div>`;
|
|
6244
6171
|
}).join('') + `</div>`;
|
|
@@ -6261,7 +6188,7 @@ function v2RenderOrgCharts() {
|
|
|
6261
6188
|
events: 0, errors: 0,
|
|
6262
6189
|
}));
|
|
6263
6190
|
activity.forEach(e => {
|
|
6264
|
-
const
|
|
6191
|
+
const ts = new Date(e.ts || e.timestamp || e.created_at || 0).getTime();
|
|
6265
6192
|
const idx = buckets.findIndex((b, i) =>
|
|
6266
6193
|
ts >= b.ts && (i === 13 || ts < buckets[i + 1].ts));
|
|
6267
6194
|
if (idx >= 0) {
|
|
@@ -6286,7 +6213,7 @@ function v2RenderOrgCharts() {
|
|
|
6286
6213
|
// Per-agent run bars
|
|
6287
6214
|
const agentRuns = agents.slice(0, 10).map(a => {
|
|
6288
6215
|
const runs = activity.filter(e => e.agentId === a.id || e.agent === a.id).length;
|
|
6289
|
-
return { name: (a.type || a.title || a.id || '—').toString(), runs };
|
|
6216
|
+
return { name: (a.type || a.title || a.id || '—').toString().slice(0, 20), runs };
|
|
6290
6217
|
}).filter(a => a.runs > 0).sort((x, y) => y.runs - x.runs);
|
|
6291
6218
|
const maxRuns = Math.max(...agentRuns.map(a => a.runs), 1);
|
|
6292
6219
|
|
|
@@ -6306,7 +6233,7 @@ function v2RenderOrgCharts() {
|
|
|
6306
6233
|
<div style="display:flex;gap:2px;align-items:flex-end;padding-bottom:28px;margin-bottom:16px;overflow-x:auto">${heatmap}</div>
|
|
6307
6234
|
${agentRuns.length ? `<div class="m-group-title" style="margin-bottom:6px">Per-Agent Runs</div>
|
|
6308
6235
|
${agentRuns.map(a => `<div style="display:flex;align-items:center;gap:8px;padding:3px 0;font-size:12px">
|
|
6309
|
-
<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>
|
|
6310
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>
|
|
6311
6238
|
<div style="width:30px;text-align:right;color:var(--text-lo);font-family:var(--mono);font-size:11px">${a.runs}</div>
|
|
6312
6239
|
</div>`).join('')}` : ''}
|
|
@@ -6322,10 +6249,10 @@ async function v2RenderOrgProjects() {
|
|
|
6322
6249
|
const _enc = encodeURIComponent(_v2SelOrg);
|
|
6323
6250
|
const data = await fetch(`/api/org/${_enc}/projects${DIR?'?dir='+encodeURIComponent(DIR):''}`).then(r=>r.ok?r.json():[]).catch(()=>[]);
|
|
6324
6251
|
const projects = Array.isArray(data) ? data : (data.projects || []);
|
|
6325
|
-
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; }
|
|
6326
6253
|
el.innerHTML = `<div class="proj-grid">${projects.map(p => `<div class="proj-card">
|
|
6327
|
-
<div class="proj-card-name"
|
|
6328
|
-
<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>
|
|
6329
6256
|
<div class="proj-card-stats" style="margin-top:8px">
|
|
6330
6257
|
${p.status ? `<span class="ss-pill ${p.status==='active'?'on':''}">${esc(p.status)}</span>` : ''}
|
|
6331
6258
|
</div>
|
|
@@ -6338,7 +6265,7 @@ async function v2RenderOrgSkills() {
|
|
|
6338
6265
|
const el = document.getElementById('odt-skills');
|
|
6339
6266
|
if (!el || !_v2OrgData) return;
|
|
6340
6267
|
const roles = Array.isArray(_v2OrgData.roles) ? _v2OrgData.roles : [];
|
|
6341
|
-
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; }
|
|
6342
6269
|
el.innerHTML = '<div class="empty">Loading skills…</div>';
|
|
6343
6270
|
const org = _v2SelOrg, dirq = DIR ? '?dir=' + encodeURIComponent(DIR) : '';
|
|
6344
6271
|
// Enrich each role with expertise + task types from its agent definition (same source as the detail drawer)
|
|
@@ -6377,7 +6304,7 @@ async function v2RenderOrgWorkspaces() {
|
|
|
6377
6304
|
const _enc = encodeURIComponent(_v2SelOrg);
|
|
6378
6305
|
const data = await fetch(`/api/org/${_enc}/workspaces${DIR?'?dir='+encodeURIComponent(DIR):''}`).then(r=>r.ok?r.json():[]).catch(()=>[]);
|
|
6379
6306
|
const ws = Array.isArray(data) ? data : (data.workspaces || []);
|
|
6380
|
-
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; }
|
|
6381
6308
|
el.innerHTML = ws.map(w => `<div style="padding:10px 0;border-bottom:1px solid var(--border)">
|
|
6382
6309
|
<div style="display:flex;align-items:center;gap:8px;margin-bottom:4px">
|
|
6383
6310
|
<span style="font-size:13px;color:var(--text-hi);font-weight:500">${esc(w.name || w.id || '—')}</span>
|
|
@@ -6398,16 +6325,16 @@ async function v2RenderOrgInvites() {
|
|
|
6398
6325
|
const _enc = encodeURIComponent(_v2SelOrg);
|
|
6399
6326
|
const data = await fetch(`/api/org/${_enc}/invites${DIR?'?dir='+encodeURIComponent(DIR):''}`).then(r=>r.ok?r.json():[]).catch(()=>[]);
|
|
6400
6327
|
const invites = Array.isArray(data) ? data : (data.invites || []);
|
|
6401
|
-
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; }
|
|
6402
6329
|
el.innerHTML = `<table style="width:100%;border-collapse:collapse;font-size:12px">
|
|
6403
6330
|
<thead><tr style="color:var(--text-xs);text-align:left">
|
|
6404
6331
|
<th style="padding:5px 8px">Token</th><th>Role</th><th>Expires</th><th>Created</th>
|
|
6405
6332
|
</tr></thead>
|
|
6406
6333
|
<tbody>${invites.map(i => `<tr style="border-top:1px solid var(--border)">
|
|
6407
|
-
<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>
|
|
6408
6335
|
<td style="padding:5px 8px"><span class="ss-pill">${esc(i.role||'viewer')}</span></td>
|
|
6409
|
-
<td style="padding:5px 8px;color:var(--text-lo)"
|
|
6410
|
-
<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>
|
|
6411
6338
|
</tr>`).join('')}</tbody>
|
|
6412
6339
|
</table>`;
|
|
6413
6340
|
} catch (e) { el.innerHTML = '<div class="empty">Failed: ' + esc(e.message) + '</div>'; }
|
|
@@ -6418,17 +6345,17 @@ function v2RenderOrgAgentsFull() {
|
|
|
6418
6345
|
const el = document.getElementById('odt-agents-full');
|
|
6419
6346
|
if (!el || !_v2OrgData) return;
|
|
6420
6347
|
const agents = _v2OrgData._agents || [];
|
|
6421
|
-
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; }
|
|
6422
6349
|
el.innerHTML = `<table style="width:100%;border-collapse:collapse;font-size:12px">
|
|
6423
6350
|
<thead><tr style="color:var(--text-xs);text-align:left">
|
|
6424
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>
|
|
6425
6352
|
</tr></thead>
|
|
6426
6353
|
<tbody>${agents.map(a => `<tr style="border-top:1px solid var(--border)">
|
|
6427
|
-
<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>
|
|
6428
6355
|
<td style="padding:5px 8px;color:var(--text-hi)">${esc(a.type||a.title||'—')}</td>
|
|
6429
6356
|
<td style="padding:5px 8px;color:var(--text-lo)">${esc(a.adapter||'—')}</td>
|
|
6430
6357
|
<td style="padding:5px 8px"><span class="ss-pill ${(a.status==='running'||a.running)?'on':''}">${esc(a.status||'idle')}</span></td>
|
|
6431
|
-
<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>
|
|
6432
6359
|
<td style="padding:5px 8px;color:var(--text-lo)">${a.tokensIn != null ? Number(a.tokensIn).toLocaleString() : '—'}</td>
|
|
6433
6360
|
<td style="padding:5px 8px;color:var(--text-lo)">${a.tokensOut != null ? Number(a.tokensOut).toLocaleString() : '—'}</td>
|
|
6434
6361
|
</tr>`).join('')}</tbody>
|
|
@@ -6444,7 +6371,7 @@ async function v2RenderOrgEnvironments() {
|
|
|
6444
6371
|
const _enc = encodeURIComponent(_v2SelOrg);
|
|
6445
6372
|
const data = await fetch(`/api/org/${_enc}/environments${DIR?'?dir='+encodeURIComponent(DIR):''}`).then(r=>r.ok?r.json():[]).catch(()=>[]);
|
|
6446
6373
|
const envs = Array.isArray(data) ? data : (data.environments || []);
|
|
6447
|
-
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; }
|
|
6448
6375
|
el.innerHTML = `<table style="width:100%;border-collapse:collapse;font-size:12px">
|
|
6449
6376
|
<thead><tr style="color:var(--text-xs);text-align:left">
|
|
6450
6377
|
<th style="padding:5px 8px">Name</th><th>Driver</th><th>Host</th><th>Status</th>
|
|
@@ -6464,7 +6391,7 @@ function v2RenderOrgAccess() {
|
|
|
6464
6391
|
const el = document.getElementById('odt-access');
|
|
6465
6392
|
if (!el || !_v2OrgData) return;
|
|
6466
6393
|
const members = _v2OrgData._members || [];
|
|
6467
|
-
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; }
|
|
6468
6395
|
const TIER = { owner: 0, admin: 1, operator: 2, viewer: 3 };
|
|
6469
6396
|
const byRole = {};
|
|
6470
6397
|
members.forEach(m => { const r = m.role || 'viewer'; (byRole[r] || (byRole[r] = [])).push(m); });
|
|
@@ -6486,7 +6413,7 @@ function v2RenderOrgIssuesFull() {
|
|
|
6486
6413
|
const el = document.getElementById('odt-issues-full');
|
|
6487
6414
|
if (!el || !_v2OrgData) return;
|
|
6488
6415
|
const issues = _v2OrgData._issues || [];
|
|
6489
|
-
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; }
|
|
6490
6417
|
const PRIORITY = { urgent:'🔴', high:'🟠', medium:'🟡', low:'🟢' };
|
|
6491
6418
|
el.innerHTML = `<table style="width:100%;border-collapse:collapse;font-size:12px">
|
|
6492
6419
|
<thead><tr style="color:var(--text-xs);text-align:left">
|
|
@@ -6494,10 +6421,10 @@ function v2RenderOrgIssuesFull() {
|
|
|
6494
6421
|
</tr></thead>
|
|
6495
6422
|
<tbody>${issues.slice(0,100).map(i => `<tr style="border-top:1px solid var(--border)">
|
|
6496
6423
|
<td style="padding:5px 4px">${PRIORITY[i.priority]||'·'}</td>
|
|
6497
|
-
<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>
|
|
6498
6425
|
<td style="padding:5px 8px;color:var(--text-lo)">${esc(i.assignee||'—')}</td>
|
|
6499
6426
|
<td style="padding:5px 8px"><span class="ss-pill ${i.status==='done'?'on':i.status==='blocked'?'warn':''}">${esc(i.status||'open')}</span></td>
|
|
6500
|
-
<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>
|
|
6501
6428
|
</tr>`).join('')}</tbody>
|
|
6502
6429
|
</table>`;
|
|
6503
6430
|
}
|
|
@@ -6511,7 +6438,7 @@ async function v2RenderOrgJoinRequests() {
|
|
|
6511
6438
|
const _enc = encodeURIComponent(_v2SelOrg);
|
|
6512
6439
|
const data = await fetch(`/api/org/${_enc}/join-requests${DIR?'?dir='+encodeURIComponent(DIR):''}`).then(r=>r.ok?r.json():[]).catch(()=>[]);
|
|
6513
6440
|
const reqs = Array.isArray(data) ? data : (data.joinRequests || data.join_requests || []);
|
|
6514
|
-
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; }
|
|
6515
6442
|
el.innerHTML = `<table style="width:100%;border-collapse:collapse;font-size:12px">
|
|
6516
6443
|
<thead><tr style="color:var(--text-xs);text-align:left">
|
|
6517
6444
|
<th style="padding:5px 8px">Requester</th><th>Type</th><th>Status</th><th>Created</th>
|
|
@@ -6520,7 +6447,7 @@ async function v2RenderOrgJoinRequests() {
|
|
|
6520
6447
|
<td style="padding:5px 8px;color:var(--text-hi)">${esc(r.name||r.username||r.id||'—')}</td>
|
|
6521
6448
|
<td style="padding:5px 8px;color:var(--text-lo)">${r.type==='agent'?'🤖 agent':'👤 human'}</td>
|
|
6522
6449
|
<td style="padding:5px 8px"><span class="ss-pill ${r.status==='approved'?'on':r.status==='rejected'?'warn':''}">${esc(r.status||'pending')}</span></td>
|
|
6523
|
-
<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>
|
|
6524
6451
|
</tr>`).join('')}</tbody>
|
|
6525
6452
|
</table>`;
|
|
6526
6453
|
} catch (e) { el.innerHTML = '<div class="empty">Failed: ' + esc(e.message) + '</div>'; }
|
|
@@ -6535,17 +6462,17 @@ async function v2RenderOrgThreads() {
|
|
|
6535
6462
|
const _enc = encodeURIComponent(_v2SelOrg);
|
|
6536
6463
|
const data = await fetch(`/api/org/${_enc}/threads${DIR?'?dir='+encodeURIComponent(DIR):''}`).then(r=>r.ok?r.json():[]).catch(()=>[]);
|
|
6537
6464
|
const threads = Array.isArray(data) ? data : (data.threads || []);
|
|
6538
|
-
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; }
|
|
6539
6466
|
el.innerHTML = `<table style="width:100%;border-collapse:collapse;font-size:12px">
|
|
6540
6467
|
<thead><tr style="color:var(--text-xs);text-align:left">
|
|
6541
6468
|
<th style="padding:5px 8px">Subject</th><th>Author</th><th>Messages</th><th>Issue</th><th>Created</th>
|
|
6542
6469
|
</tr></thead>
|
|
6543
6470
|
<tbody>${threads.map(t => `<tr style="border-top:1px solid var(--border)">
|
|
6544
|
-
<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>
|
|
6545
6472
|
<td style="padding:5px 8px;color:var(--text-lo)">${esc(t.author||t.createdBy||'—')}</td>
|
|
6546
6473
|
<td style="padding:5px 8px;color:var(--text-lo);text-align:center">${t.messageCount ?? t.messages ?? '—'}</td>
|
|
6547
|
-
<td style="padding:5px 8px;color:var(--text-lo);font-family:var(--mono);font-size:11px"
|
|
6548
|
-
<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>
|
|
6549
6476
|
</tr>`).join('')}</tbody>
|
|
6550
6477
|
</table>`;
|
|
6551
6478
|
} catch (e) { el.innerHTML = '<div class="empty">Failed: ' + esc(e.message) + '</div>'; }
|
|
@@ -6555,103 +6482,55 @@ async function v2RenderOrgThreads() {
|
|
|
6555
6482
|
window.v2StopOrg = async function() {
|
|
6556
6483
|
if (!_v2SelOrg) return;
|
|
6557
6484
|
const stopped = _v2SelOrg;
|
|
6558
|
-
try {
|
|
6559
|
-
await fetch(`/api/orgs/${encodeURIComponent(stopped)}/stop`, {method:'POST'});
|
|
6560
|
-
showToast('Stopped', `Org "${stopped}" stopped`, 'ok');
|
|
6561
|
-
} catch(e) { showToast('Error', e.message, 'err'); }
|
|
6485
|
+
try { await fetch(`/api/orgs/${encodeURIComponent(stopped)}/stop`, {method:'POST'}); } catch(_) {}
|
|
6562
6486
|
setTimeout(async () => { await renderOrgs(); if (_v2SelOrg === stopped) v2SelectOrg(stopped); }, 600);
|
|
6563
6487
|
};
|
|
6564
6488
|
|
|
6565
|
-
|
|
6566
|
-
|
|
6567
|
-
if (
|
|
6568
|
-
const dialog = document.getElementById('org-copy-dialog');
|
|
6569
|
-
const feedback = document.getElementById('org-copy-feedback');
|
|
6570
|
-
const select = document.getElementById('org-copy-dest-select');
|
|
6571
|
-
const input = document.getElementById('org-copy-dest-input');
|
|
6572
|
-
if (!dialog) return;
|
|
6573
|
-
feedback.textContent = '';
|
|
6574
|
-
input.value = '';
|
|
6575
|
-
select.innerHTML = '<option value="">Loading…</option>';
|
|
6576
|
-
dialog.style.display = 'flex';
|
|
6577
|
-
// Populate project list
|
|
6578
|
-
try {
|
|
6579
|
-
const data = await apiFetch('/api/projects');
|
|
6580
|
-
const projects = (data.projects || []).filter(p => p.path && p.path !== DIR);
|
|
6581
|
-
select.innerHTML = '<option value="">— select a project —</option>' +
|
|
6582
|
-
projects.map(p => `<option value="${esc(p.path)}">${esc(p.name)} (${esc(p.path)})</option>`).join('') +
|
|
6583
|
-
'<option value="__custom__">Custom path…</option>';
|
|
6584
|
-
} catch(e) {
|
|
6585
|
-
select.innerHTML = '<option value="__custom__">Enter path manually</option>';
|
|
6586
|
-
}
|
|
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(); }
|
|
6587
6492
|
};
|
|
6588
|
-
|
|
6589
|
-
window.v2HideCopyOrgDialog = function() {
|
|
6590
|
-
const dialog = document.getElementById('org-copy-dialog');
|
|
6591
|
-
if (dialog) dialog.style.display = 'none';
|
|
6592
|
-
};
|
|
6593
|
-
|
|
6594
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; }
|
|
6595
6496
|
if (!_v2SelOrg) return;
|
|
6596
|
-
const select = document.getElementById('org-copy-dest-select');
|
|
6597
|
-
const input = document.getElementById('org-copy-dest-input');
|
|
6598
|
-
const feedback = document.getElementById('org-copy-feedback');
|
|
6599
|
-
const btn = document.getElementById('org-copy-confirm-btn');
|
|
6600
|
-
const destination = (input.value.trim()) || (select.value !== '__custom__' ? select.value : '');
|
|
6601
|
-
if (!destination) {
|
|
6602
|
-
feedback.textContent = 'Please select or enter a destination.';
|
|
6603
|
-
feedback.style.color = 'var(--red, #e55)';
|
|
6604
|
-
return;
|
|
6605
|
-
}
|
|
6606
|
-
btn.disabled = true;
|
|
6607
|
-
btn.textContent = 'Copying…';
|
|
6608
|
-
feedback.textContent = '';
|
|
6609
6497
|
try {
|
|
6610
|
-
const r = await fetch(
|
|
6611
|
-
method: 'POST',
|
|
6612
|
-
|
|
6613
|
-
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 }),
|
|
6614
6501
|
});
|
|
6615
|
-
|
|
6616
|
-
|
|
6617
|
-
|
|
6618
|
-
|
|
6619
|
-
setTimeout(v2HideCopyOrgDialog, 1800);
|
|
6620
|
-
} else {
|
|
6621
|
-
feedback.textContent = 'Error: ' + (json.error || r.status);
|
|
6622
|
-
feedback.style.color = 'var(--red, #e55)';
|
|
6623
|
-
}
|
|
6624
|
-
} catch(e) {
|
|
6625
|
-
feedback.textContent = 'Error: ' + e.message;
|
|
6626
|
-
feedback.style.color = 'var(--red, #e55)';
|
|
6627
|
-
} finally {
|
|
6628
|
-
btn.disabled = false;
|
|
6629
|
-
btn.textContent = 'Copy';
|
|
6630
|
-
}
|
|
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'); }
|
|
6631
6506
|
};
|
|
6632
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
|
+
|
|
6633
6525
|
// live SSE for org events
|
|
6634
6526
|
(function v2OrgSSE() {
|
|
6635
6527
|
let src;
|
|
6636
|
-
let _connectTime = 0;
|
|
6637
6528
|
function connect() {
|
|
6638
6529
|
if (src) src.close();
|
|
6639
|
-
_connectTime = Date.now();
|
|
6640
6530
|
src = new EventSource('/api/mastermind-stream');
|
|
6641
6531
|
src.onmessage = e => {
|
|
6642
6532
|
try {
|
|
6643
6533
|
const ev = JSON.parse(e.data);
|
|
6644
|
-
if (ev?.type?.startsWith('loop:')) {
|
|
6645
|
-
// Skip replayed historical events (server replays last 50 on connect)
|
|
6646
|
-
if (ev.ts && ev.ts < _connectTime) return;
|
|
6647
|
-
if (currentView === 'loops') renderLoops();
|
|
6648
|
-
else viewRendered['loops'] = false; // stale — re-fetch on next switchView
|
|
6649
|
-
loadLoopMetrics();
|
|
6650
|
-
if (_mmCurrentTab === 'loops' && document.getElementById('mm-overlay')?.classList.contains('open')) {
|
|
6651
|
-
mmRenderLoops(document.getElementById('mm-body'));
|
|
6652
|
-
}
|
|
6653
|
-
return;
|
|
6654
|
-
}
|
|
6655
6534
|
if (!ev?.org || !ev?.type?.startsWith('org:')) return;
|
|
6656
6535
|
const n = ev.org;
|
|
6657
6536
|
// Filter by project dir if the event carries one; skip events from other projects.
|
|
@@ -6720,12 +6599,11 @@ function buildTimeline(events) {
|
|
|
6720
6599
|
// Only tool + user events with timestamps
|
|
6721
6600
|
const stamped = events.filter(ev => ev.ts && (ev.kind === 'tool' || ev.kind === 'user'));
|
|
6722
6601
|
if (stamped.length < 2) { tl.innerHTML = ''; return; }
|
|
6723
|
-
const
|
|
6724
|
-
const times = stamped.map(tsMs);
|
|
6602
|
+
const times = stamped.map(ev => new Date(ev.ts).getTime());
|
|
6725
6603
|
const tMin = Math.min(...times), tMax = Math.max(...times);
|
|
6726
6604
|
const span = tMax - tMin || 1;
|
|
6727
6605
|
const segs = stamped.map(ev => {
|
|
6728
|
-
const pct = ((
|
|
6606
|
+
const pct = ((new Date(ev.ts).getTime() - tMin) / span * 100).toFixed(2);
|
|
6729
6607
|
const cat = ev.kind === 'user' ? 'user' : (ev.cat || 'other');
|
|
6730
6608
|
const color = TL_COLORS[cat] || TL_COLORS.other;
|
|
6731
6609
|
const label = ev.kind === 'user' ? 'user message' : (ev.label || ev.name || cat);
|
|
@@ -6820,13 +6698,12 @@ async function copySession() {
|
|
|
6820
6698
|
try {
|
|
6821
6699
|
const data = await apiFetch('/api/session?dir=' + enc(DIR) + '&file=' + enc(sess.file) + '&limit=300');
|
|
6822
6700
|
const events = data.events || [];
|
|
6823
|
-
const
|
|
6824
|
-
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()}`, ''];
|
|
6825
6702
|
for (const ev of events) {
|
|
6826
6703
|
if (ev.kind === 'user' && ev.text?.trim()) {
|
|
6827
6704
|
lines.push('**User:** ' + ev.text.trim().replace(/\n/g, ' '));
|
|
6828
6705
|
} else if (ev.kind === 'tool') {
|
|
6829
|
-
lines.push(`- \`${ev.name || ev.cat}
|
|
6706
|
+
lines.push(`- \`${ev.name || ev.cat}\`: ${ev.label || ''}`);
|
|
6830
6707
|
}
|
|
6831
6708
|
}
|
|
6832
6709
|
await navigator.clipboard.writeText(lines.join('\n'));
|
|
@@ -6893,10 +6770,7 @@ function cmdSearch(q) {
|
|
|
6893
6770
|
results.innerHTML = sq.length >= 2
|
|
6894
6771
|
? '<div class="cmd-empty">Searching sessions…</div>'
|
|
6895
6772
|
: '<div class="cmd-empty">Type at least 2 chars after > to search all sessions</div>';
|
|
6896
|
-
if (sq.length >= 2)
|
|
6897
|
-
clearTimeout(cmdSearch._debounce);
|
|
6898
|
-
cmdSearch._debounce = setTimeout(() => searchSessions(sq), 300);
|
|
6899
|
-
}
|
|
6773
|
+
if (sq.length >= 2) searchSessions(sq);
|
|
6900
6774
|
return;
|
|
6901
6775
|
}
|
|
6902
6776
|
|
|
@@ -6925,20 +6799,10 @@ function cmdSearch(q) {
|
|
|
6925
6799
|
|
|
6926
6800
|
// ACTIONS group
|
|
6927
6801
|
const actionItems = [
|
|
6928
|
-
{ label: '
|
|
6929
|
-
{ label: 'Go to Sessions', action: () => switchView('sessions') },
|
|
6930
|
-
{ label: 'Go to Projects', action: () => switchView('projects') },
|
|
6931
|
-
{ label: 'Go to Loops', action: () => switchView('loops') },
|
|
6932
|
-
{ label: 'Go to Tokens', action: () => switchView('tokens') },
|
|
6933
|
-
{ label: 'Go to Memory', action: () => switchView('memory') },
|
|
6934
|
-
{ label: 'Go to Orgs', action: () => switchView('orgs') },
|
|
6935
|
-
{ label: 'Go to Monograph', action: () => switchView('monograph') },
|
|
6936
|
-
{ 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(); } },
|
|
6937
6803
|
{ label: 'Refresh Dashboard', action: () => refreshCurrent() },
|
|
6938
6804
|
{ label: 'Toggle Compact View', action: () => toggleDensity() },
|
|
6939
|
-
{ label: '
|
|
6940
|
-
{ label: 'Open Mastermind', action: () => openMastermind() },
|
|
6941
|
-
{ label: 'Keyboard Shortcuts', action: () => { closeCmdPalette(); openShortcutHelp(); } },
|
|
6805
|
+
{ label: 'Open Loops', action: () => { const l = document.querySelector('.nav-item[data-view="loops"]'); if (l) l.click(); } },
|
|
6942
6806
|
].filter(a => !lq || a.label.toLowerCase().includes(lq));
|
|
6943
6807
|
|
|
6944
6808
|
// TABS group — only if org room is open
|
|
@@ -6964,8 +6828,8 @@ function cmdSearch(q) {
|
|
|
6964
6828
|
html += `<div class="cmd-item" data-ci="${idx}">
|
|
6965
6829
|
<span class="ci-ico">◫</span>
|
|
6966
6830
|
<div class="cmd-item-body">
|
|
6967
|
-
<div class="ci-title"
|
|
6968
|
-
<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>
|
|
6969
6833
|
</div>
|
|
6970
6834
|
</div>`;
|
|
6971
6835
|
});
|
|
@@ -6980,7 +6844,7 @@ function cmdSearch(q) {
|
|
|
6980
6844
|
<span class="ci-ico">◈</span>
|
|
6981
6845
|
<div class="cmd-item-body">
|
|
6982
6846
|
<div class="ci-title">${esc(d.key || d.namespace || '—')}</div>
|
|
6983
|
-
<div class="ci-sub"
|
|
6847
|
+
<div class="ci-sub">${esc(String(d.value || d.text || '').slice(0, 60))}</div>
|
|
6984
6848
|
</div>
|
|
6985
6849
|
</div>`;
|
|
6986
6850
|
});
|
|
@@ -7026,7 +6890,7 @@ function cmdSearch(q) {
|
|
|
7026
6890
|
<span class="ci-ico">✦</span>
|
|
7027
6891
|
<div class="cmd-item-body">
|
|
7028
6892
|
<div class="ci-title">${esc(skLabel)}</div>
|
|
7029
|
-
<div class="ci-sub"
|
|
6893
|
+
<div class="ci-sub">${typeof sk === 'string' ? '' : esc((sk.description || '').slice(0, 60))}</div>
|
|
7030
6894
|
</div>
|
|
7031
6895
|
</div>`;
|
|
7032
6896
|
});
|
|
@@ -7093,7 +6957,7 @@ function executeCmdItem() {
|
|
|
7093
6957
|
else if (item.type === 'memory') switchView('memory');
|
|
7094
6958
|
else if (item.type === 'project') switchProject(item.data.path);
|
|
7095
6959
|
else if (item.type === 'org') { switchView('orgs'); setTimeout(() => v2SelectOrg(item.data.name), 80); }
|
|
7096
|
-
else if (item.type === 'skill')
|
|
6960
|
+
else if (item.type === 'skill') switchView('memory');
|
|
7097
6961
|
else if (item.type === 'action') { if (item.data.action) item.data.action(); }
|
|
7098
6962
|
else if (item.type === 'orgtab') { const btn = document.querySelector(`.odt-btn[data-tab="${item.data.tab}"]`); if (btn) btn.click(); }
|
|
7099
6963
|
}
|
|
@@ -7116,8 +6980,8 @@ async function searchSessions(q) {
|
|
|
7116
6980
|
html += `<div class="cmd-item" data-ci="${idx}">
|
|
7117
6981
|
<span class="ci-ico">◫</span>
|
|
7118
6982
|
<div class="cmd-item-body">
|
|
7119
|
-
<div class="ci-title"
|
|
7120
|
-
<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>
|
|
7121
6985
|
</div>
|
|
7122
6986
|
</div>`;
|
|
7123
6987
|
});
|
|
@@ -7142,22 +7006,21 @@ document.addEventListener('keydown', e => {
|
|
|
7142
7006
|
return;
|
|
7143
7007
|
}
|
|
7144
7008
|
|
|
7145
|
-
// Escape always closes modals, even when focused inside an input/textarea
|
|
7146
|
-
if (e.key === 'Escape') { closeDetail(); closeCmdPalette(); closeShortcutHelp(); closeBudgetModal(); closeChunkModal(); closeMemModal(); closeReportCard(); closeMastermind(); if (document.getElementById('app').classList.contains('ambient')) toggleAmbient(); return; }
|
|
7147
|
-
|
|
7148
7009
|
// ignore when typing in inputs
|
|
7149
7010
|
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return;
|
|
7150
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(); }
|
|
7151
7014
|
if (e.key === '?') { e.preventDefault(); openShortcutHelp(); return; }
|
|
7152
|
-
if (e.key === 'r' || e.key === 'R') { e.preventDefault(); refreshCurrent(); return; }
|
|
7153
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; }
|
|
7154
7018
|
if (e.key === 'a' || e.key === 'A') { if (currentView === 'now') { e.preventDefault(); toggleAmbient(); } }
|
|
7155
|
-
const _viewKeys = {'1':'now','2':'sessions','3':'projects','4':'loops','5':'tokens','6':'memory','7':'orgs','8':'monograph','9':'global'};
|
|
7156
|
-
if (_viewKeys[e.key] && !e.metaKey && !e.ctrlKey && !e.altKey) { e.preventDefault(); switchView(_viewKeys[e.key]); return; }
|
|
7157
7019
|
|
|
7158
7020
|
if (currentView === 'now') {
|
|
7159
7021
|
if (e.key === '/') { e.preventDefault(); toggleFeedSearch(); }
|
|
7160
7022
|
if (e.key === 'g' || e.key === 'G') { e.preventDefault(); goLive(); }
|
|
7023
|
+
if (e.key === 'r' || e.key === 'R') { e.preventDefault(); refreshCurrent(); }
|
|
7161
7024
|
|
|
7162
7025
|
if (e.key === 'j' || e.key === 'k') {
|
|
7163
7026
|
e.preventDefault();
|
|
@@ -7225,22 +7088,6 @@ function filterSessions(q) {
|
|
|
7225
7088
|
});
|
|
7226
7089
|
const countEl = document.getElementById('sess-filter-count');
|
|
7227
7090
|
if (countEl) countEl.textContent = lq && rows.length ? `${visible} / ${rows.length}` : '';
|
|
7228
|
-
// zero-results empty state
|
|
7229
|
-
const noRes = document.getElementById('sess-filter-noresult');
|
|
7230
|
-
if (lq && visible === 0 && rows.length > 0) {
|
|
7231
|
-
if (!noRes) {
|
|
7232
|
-
const el = document.createElement('div');
|
|
7233
|
-
el.id = 'sess-filter-noresult';
|
|
7234
|
-
el.className = 'empty';
|
|
7235
|
-
el.style.cssText = 'padding:24px 0;font-size:13px';
|
|
7236
|
-
el.textContent = 'No sessions match "' + q.slice(0, 40) + '"';
|
|
7237
|
-
document.getElementById('sess-content').appendChild(el);
|
|
7238
|
-
} else {
|
|
7239
|
-
noRes.textContent = 'No sessions match "' + q.slice(0, 40) + '"';
|
|
7240
|
-
}
|
|
7241
|
-
} else if (noRes) {
|
|
7242
|
-
noRes.remove();
|
|
7243
|
-
}
|
|
7244
7091
|
}
|
|
7245
7092
|
|
|
7246
7093
|
// ── feature 32: keyboard shortcut help modal ──────────────
|
|
@@ -7267,7 +7114,7 @@ function updateCurrentActivity(events) {
|
|
|
7267
7114
|
} else if (name) {
|
|
7268
7115
|
activity = name;
|
|
7269
7116
|
}
|
|
7270
|
-
if (activity) { el.textContent = '⤷ ' + activity; el.
|
|
7117
|
+
if (activity) { el.textContent = '⤷ ' + activity; el.classList.add('loaded'); }
|
|
7271
7118
|
else { el.textContent = ''; el.classList.remove('loaded'); }
|
|
7272
7119
|
}
|
|
7273
7120
|
|
|
@@ -7291,8 +7138,7 @@ function buildPatterns() {
|
|
|
7291
7138
|
if (!STOP_WORDS.has(w) && !seen.has(w)) { freq[w] = (freq[w] || 0) + 1; seen.add(w); }
|
|
7292
7139
|
}
|
|
7293
7140
|
}
|
|
7294
|
-
const
|
|
7295
|
-
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);
|
|
7296
7142
|
if (!sorted.length) { el.innerHTML = '<div class="loading-txt">Not enough prompt data</div>'; return; }
|
|
7297
7143
|
const maxCount = sorted[0][1];
|
|
7298
7144
|
const rows = sorted.map(([word, count], i) => {
|
|
@@ -7305,8 +7151,7 @@ function buildPatterns() {
|
|
|
7305
7151
|
}).join('');
|
|
7306
7152
|
el.innerHTML = `<table class="lb-table"><thead><tr>
|
|
7307
7153
|
<th class="lb-rank">#</th><th>Term</th><th></th><th class="lb-cost">Sessions</th>
|
|
7308
|
-
</tr></thead><tbody>${rows}</tbody></table
|
|
7309
|
-
(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>`;
|
|
7310
7155
|
}
|
|
7311
7156
|
|
|
7312
7157
|
// ── feature 35: session streak tracker ────────────────────
|
|
@@ -7314,7 +7159,7 @@ function calcStreak() {
|
|
|
7314
7159
|
const dates = new Set(allSessions.map(s => {
|
|
7315
7160
|
const t = s.firstTs || s.mtime;
|
|
7316
7161
|
if (!t) return null;
|
|
7317
|
-
return new Date(typeof t === 'number' ? t :
|
|
7162
|
+
return new Date(typeof t === 'number' ? t : t).toDateString();
|
|
7318
7163
|
}).filter(Boolean));
|
|
7319
7164
|
let streak = 0;
|
|
7320
7165
|
const today = new Date();
|
|
@@ -7332,16 +7177,14 @@ function calcStreak() {
|
|
|
7332
7177
|
|
|
7333
7178
|
// ── feature 25: notification toasts ──────────────────────
|
|
7334
7179
|
let _toastLastBudgetKey = '';
|
|
7335
|
-
let _toastLastKey = ''; let _toastLastTs = 0;
|
|
7336
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();
|
|
7337
7186
|
const rack = document.getElementById('toast-rack');
|
|
7338
7187
|
if (!rack) return;
|
|
7339
|
-
// Dedup: skip identical toast within 2s
|
|
7340
|
-
const key = type + '|' + title + '|' + msg;
|
|
7341
|
-
if (key === _toastLastKey && Date.now() - _toastLastTs < 2000) return;
|
|
7342
|
-
_toastLastKey = key; _toastLastTs = Date.now();
|
|
7343
|
-
// Cap at 5 visible toasts — evict oldest
|
|
7344
|
-
while (rack.children.length >= 5) rack.firstChild.remove();
|
|
7345
7188
|
const icoMap = { warn: '⚑', err: '⚠', ok: '✓', info: '◉' };
|
|
7346
7189
|
const div = document.createElement('div');
|
|
7347
7190
|
div.className = 'toast t-' + type;
|
|
@@ -7350,7 +7193,7 @@ function showToast(title, msg, type = 'info', duration = 5000) {
|
|
|
7350
7193
|
<div class="toast-title">${esc(title)}</div>
|
|
7351
7194
|
<div class="toast-msg">${esc(msg)}</div>
|
|
7352
7195
|
</div>
|
|
7353
|
-
<button class="toast-close" onclick="this.closest('.toast').remove()"
|
|
7196
|
+
<button class="toast-close" onclick="this.closest('.toast').remove()">✕</button>`;
|
|
7354
7197
|
rack.appendChild(div);
|
|
7355
7198
|
if (duration > 0) setTimeout(() => { try { div.remove(); } catch {} }, duration);
|
|
7356
7199
|
}
|
|
@@ -7421,11 +7264,7 @@ function buildSessionHeatmap(sessions) {
|
|
|
7421
7264
|
if (idx >= 0 && idx < DAYS) buckets[idx].count++;
|
|
7422
7265
|
}
|
|
7423
7266
|
const max = Math.max(...buckets.map(b => b.count), 1);
|
|
7424
|
-
|
|
7425
|
-
const firstDowShm = new Date(now - (DAYS - 1) * DAY).getDay(); // 0=Sun
|
|
7426
|
-
const shmOffset = firstDowShm === 0 ? 6 : firstDowShm - 1;
|
|
7427
|
-
const padShm = Array.from({ length: shmOffset }, () => '<div class="shm-cell" style="opacity:0;pointer-events:none"></div>');
|
|
7428
|
-
el.innerHTML = padShm.join('') + buckets.map(b => {
|
|
7267
|
+
el.innerHTML = buckets.map(b => {
|
|
7429
7268
|
const level = b.count === 0 ? 0 : Math.min(4, Math.ceil(b.count / max * 4));
|
|
7430
7269
|
const isActive = b.date === heatmapDateFilter;
|
|
7431
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>`;
|
|
@@ -7519,8 +7358,7 @@ function bulkExport() {
|
|
|
7519
7358
|
if (!toExport.length) return;
|
|
7520
7359
|
const headers = ['Date', 'Session ID', 'Prompt', 'Cost ($)', 'Duration (s)', 'Tool Calls', 'Files Touched'];
|
|
7521
7360
|
const rows = toExport.map(s => {
|
|
7522
|
-
const
|
|
7523
|
-
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', ' ');
|
|
7524
7362
|
const cost = typeof s.totalCost === 'number' ? s.totalCost.toFixed(4) : '';
|
|
7525
7363
|
const dur = s.totalDurationMs ? Math.round(s.totalDurationMs / 1000) : '';
|
|
7526
7364
|
const prompt = (s.lastPrompt || '').replace(/"/g, '""');
|
|
@@ -7574,8 +7412,7 @@ function exportSessionsCSV() {
|
|
|
7574
7412
|
if (!allSessions.length) { showToast('No data', 'No sessions loaded yet', 'warn'); return; }
|
|
7575
7413
|
const headers = ['Date', 'Session ID', 'Prompt', 'Cost ($)', 'Duration (s)', 'Tool Calls', 'User Messages', 'Cache Hit %', 'Input Tokens'];
|
|
7576
7414
|
const rows = allSessions.map(s => {
|
|
7577
|
-
const
|
|
7578
|
-
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', ' ');
|
|
7579
7416
|
const cost = typeof s.totalCost === 'number' ? s.totalCost.toFixed(4) : '';
|
|
7580
7417
|
const dur = s.totalDurationMs ? Math.round(s.totalDurationMs / 1000) : '';
|
|
7581
7418
|
const cachePct = s.totalInputTokens > 0 ? Math.round((s.cacheReadTokens || 0) / s.totalInputTokens * 100) : '';
|
|
@@ -7608,12 +7445,11 @@ async function loadToolRank() {
|
|
|
7608
7445
|
const data = await apiFetch('/api/tool-ranking?dir=' + enc(DIR));
|
|
7609
7446
|
if (!data.tools?.length) { el.innerHTML = '<div class="loading-txt">No tool usage data</div>'; return; }
|
|
7610
7447
|
const maxCount = data.tools[0].count;
|
|
7611
|
-
const
|
|
7612
|
-
const rows = shownTools.map((t, i) => {
|
|
7448
|
+
const rows = data.tools.slice(0, 15).map((t, i) => {
|
|
7613
7449
|
const barW = Math.round((t.count / maxCount) * 100);
|
|
7614
7450
|
const errRate = t.errors > 0 ? ((t.errors / t.count) * 100).toFixed(0) + '%' : '—';
|
|
7615
7451
|
return `<tr><td class="lb-rank">${i + 1}</td>
|
|
7616
|
-
<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>
|
|
7617
7453
|
<td style="width:80px;padding:4px 6px">
|
|
7618
7454
|
<div style="height:4px;background:var(--surface-hi);border-radius:2px;overflow:hidden">
|
|
7619
7455
|
<div style="height:100%;width:${barW}%;background:oklch(65% 0.15 200);border-radius:2px"></div>
|
|
@@ -7625,8 +7461,7 @@ async function loadToolRank() {
|
|
|
7625
7461
|
}).join('');
|
|
7626
7462
|
el.innerHTML = `<table class="lb-table"><thead><tr>
|
|
7627
7463
|
<th class="lb-rank">#</th><th>Tool</th><th></th><th class="lb-cost">Calls</th><th class="lb-dur">Error%</th>
|
|
7628
|
-
</tr></thead><tbody>${rows}</tbody></table
|
|
7629
|
-
(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>`;
|
|
7630
7465
|
} catch (err) {
|
|
7631
7466
|
el.innerHTML = '<div class="loading-txt">Could not load: ' + esc(err.message) + '</div>';
|
|
7632
7467
|
}
|
|
@@ -7648,14 +7483,13 @@ async function loadProjCosts() {
|
|
|
7648
7483
|
const data = await apiFetch('/api/project-costs');
|
|
7649
7484
|
if (!data.projects?.length) { el.innerHTML = '<div class="loading-txt">No cost data across projects</div>'; return; }
|
|
7650
7485
|
const maxCost = data.projects[0].cost;
|
|
7651
|
-
const
|
|
7652
|
-
const rows = shownProjects.map((p, i) => {
|
|
7486
|
+
const rows = data.projects.slice(0, 10).map((p, i) => {
|
|
7653
7487
|
const barW = maxCost > 0 ? Math.round((p.cost / maxCost) * 100) : 0;
|
|
7654
7488
|
const name = p.path.split('/').filter(Boolean).pop() || p.path;
|
|
7655
7489
|
return `<tr onclick="switchProject('${esc(p.path)}')" style="cursor:pointer" title="${esc(p.path)}">
|
|
7656
7490
|
<td class="lb-rank">${i + 1}</td>
|
|
7657
7491
|
<td style="font-size:12px;color:var(--text-mid);max-width:140px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${esc(name)}</td>
|
|
7658
|
-
<td class="lb-cost">$${
|
|
7492
|
+
<td class="lb-cost">$${p.cost.toFixed(2)}</td>
|
|
7659
7493
|
<td style="width:80px;padding:4px 6px">
|
|
7660
7494
|
<div style="height:4px;background:var(--surface-hi);border-radius:2px;overflow:hidden">
|
|
7661
7495
|
<div style="height:100%;width:${barW}%;background:oklch(72% 0.18 75 / 0.7);border-radius:2px"></div>
|
|
@@ -7666,8 +7500,7 @@ async function loadProjCosts() {
|
|
|
7666
7500
|
}).join('');
|
|
7667
7501
|
el.innerHTML = `<table class="lb-table"><thead><tr>
|
|
7668
7502
|
<th class="lb-rank">#</th><th>Project</th><th class="lb-cost">Cost</th><th></th><th class="lb-dur">Sessions</th>
|
|
7669
|
-
</tr></thead><tbody>${rows}</tbody></table
|
|
7670
|
-
(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>`;
|
|
7671
7504
|
} catch (err) {
|
|
7672
7505
|
el.innerHTML = '<div class="loading-txt">Could not load: ' + esc(err.message) + '</div>';
|
|
7673
7506
|
}
|
|
@@ -7695,7 +7528,7 @@ function buildGanttTimeline() {
|
|
|
7695
7528
|
}
|
|
7696
7529
|
for (const s of allSessions) {
|
|
7697
7530
|
const t = s.firstTs || s.mtime; if (!t) continue;
|
|
7698
|
-
const d = new Date(typeof t === 'number' ? t :
|
|
7531
|
+
const d = new Date(typeof t === 'number' ? t : t); d.setHours(0,0,0,0);
|
|
7699
7532
|
const key = d.toDateString();
|
|
7700
7533
|
if (key in days) days[key].push(s);
|
|
7701
7534
|
}
|
|
@@ -7713,11 +7546,11 @@ function buildGanttTimeline() {
|
|
|
7713
7546
|
// Sort sessions by start time
|
|
7714
7547
|
const sorted = [...sessions].sort((a, b) => {
|
|
7715
7548
|
const ta = a.firstTs || a.mtime || 0; const tb = b.firstTs || b.mtime || 0;
|
|
7716
|
-
return (typeof ta === 'number' ? ta :
|
|
7549
|
+
return (typeof ta === 'number' ? ta : new Date(ta).getTime()) - (typeof tb === 'number' ? tb : new Date(tb).getTime());
|
|
7717
7550
|
});
|
|
7718
7551
|
for (let i = 0; i < sorted.length; i++) {
|
|
7719
7552
|
const s = sorted[i];
|
|
7720
|
-
const
|
|
7553
|
+
const startTs = typeof (s.firstTs || s.mtime) === 'number' ? (s.firstTs || s.mtime) : new Date(s.firstTs || s.mtime).getTime();
|
|
7721
7554
|
const dayStart = new Date(dateStr).getTime();
|
|
7722
7555
|
const startPct = Math.min(95, ((startTs - dayStart) / DAY) * 100);
|
|
7723
7556
|
const durPct = s.totalDurationMs ? Math.max(0.5, Math.min(15, (s.totalDurationMs / DAY) * 100)) : 1;
|
|
@@ -7744,7 +7577,7 @@ function showReportCard() {
|
|
|
7744
7577
|
|
|
7745
7578
|
const todaySess = allSessions.filter(s => {
|
|
7746
7579
|
const t = s.firstTs || s.mtime; if (!t) return false;
|
|
7747
|
-
return new Date(typeof t === 'number' ? t :
|
|
7580
|
+
return new Date(typeof t === 'number' ? t : t).toDateString() === todayStr;
|
|
7748
7581
|
});
|
|
7749
7582
|
const weekSess = allSessions.filter(s => {
|
|
7750
7583
|
const t = s.firstTs || s.mtime; if (!t) return false;
|
|
@@ -7894,7 +7727,7 @@ function showCostExplainer(sessId, event) {
|
|
|
7894
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('');
|
|
7895
7728
|
drawer.innerHTML = `<div class="err-drawer-head" style="color:oklch(70% 0.18 80)">
|
|
7896
7729
|
<span>Cost anomaly — $${(sess.totalCost||0).toFixed(3)} (${ratio}× median, top ${100-pct}%)</span>
|
|
7897
|
-
<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>
|
|
7898
7731
|
</div>
|
|
7899
7732
|
<div class="err-drawer-body">
|
|
7900
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>
|
|
@@ -7963,7 +7796,7 @@ function buildHourlyHeatmap() {
|
|
|
7963
7796
|
const grid = Array.from({ length: 7 }, () => new Array(24).fill(0));
|
|
7964
7797
|
for (const s of allSessions) {
|
|
7965
7798
|
const t = s.firstTs || s.mtime; if (!t) continue;
|
|
7966
|
-
const d = new Date(typeof t === 'number' ? t :
|
|
7799
|
+
const d = new Date(typeof t === 'number' ? t : t);
|
|
7967
7800
|
const dow = d.getDay(); // 0=Sun
|
|
7968
7801
|
const hour = d.getHours();
|
|
7969
7802
|
grid[dow][hour]++;
|
|
@@ -7994,7 +7827,7 @@ function buildHourlyHeatmap() {
|
|
|
7994
7827
|
|
|
7995
7828
|
// ── feature 50: custom tag editor ─────────────────────────
|
|
7996
7829
|
const _customTagsKey = 'mm-custom-tags';
|
|
7997
|
-
let _customTagsMap = new Map(Object.entries(
|
|
7830
|
+
let _customTagsMap; try { _customTagsMap = new Map(Object.entries(JSON.parse(localStorage.getItem(_customTagsKey) || '{}'))); } catch { _customTagsMap = new Map(); }
|
|
7998
7831
|
|
|
7999
7832
|
function getCustomTags(sessId) {
|
|
8000
7833
|
return _customTagsMap.get(sessId) || [];
|
|
@@ -8014,12 +7847,10 @@ function addCustomTag(sessId, tag, event) {
|
|
|
8014
7847
|
if (!tags.includes(t)) { tags.push(t); saveCustomTags(sessId, tags); }
|
|
8015
7848
|
const wrap = document.querySelector(`.sr-custom-tags[data-sess="${CSS.escape(sessId)}"]`);
|
|
8016
7849
|
if (wrap) wrap.outerHTML = renderCustomTagsInline(sessId, tags);
|
|
8017
|
-
// rebuild tag filter bar
|
|
7850
|
+
// rebuild tag filter bar in the DOM if it exists
|
|
8018
7851
|
initTags();
|
|
8019
7852
|
const tfBar = document.querySelector('.tag-filter-bar');
|
|
8020
|
-
|
|
8021
|
-
if (tfBar) tfBar.outerHTML = newBarHtml;
|
|
8022
|
-
else if (newBarHtml) renderSessions();
|
|
7853
|
+
if (tfBar) tfBar.outerHTML = buildTagFilterBar(allSessions);
|
|
8023
7854
|
}
|
|
8024
7855
|
|
|
8025
7856
|
function removeCustomTag(sessId, tag, event) {
|
|
@@ -8028,9 +7859,10 @@ function removeCustomTag(sessId, tag, event) {
|
|
|
8028
7859
|
saveCustomTags(sessId, tags);
|
|
8029
7860
|
const wrap = document.querySelector(`.sr-custom-tags[data-sess="${CSS.escape(sessId)}"]`);
|
|
8030
7861
|
if (wrap) wrap.outerHTML = renderCustomTagsInline(sessId, tags);
|
|
8031
|
-
initTags();
|
|
8032
7862
|
const tfBar = document.querySelector('.tag-filter-bar');
|
|
8033
|
-
|
|
7863
|
+
const newBarHtml = buildTagFilterBar(allSessions);
|
|
7864
|
+
if (tfBar) tfBar.outerHTML = newBarHtml;
|
|
7865
|
+
else if (newBarHtml) renderSessions();
|
|
8034
7866
|
}
|
|
8035
7867
|
|
|
8036
7868
|
function showCustomTagInput(sessId, event) {
|
|
@@ -8043,7 +7875,7 @@ function showCustomTagInput(sessId, event) {
|
|
|
8043
7875
|
iw.className = 'ctag-input-wrap';
|
|
8044
7876
|
iw.onclick = e => e.stopPropagation();
|
|
8045
7877
|
iw.innerHTML = `<input class="ctag-input" type="text" placeholder="tag name…" maxlength="20" autofocus>
|
|
8046
|
-
<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>`;
|
|
8047
7879
|
wrap.appendChild(iw);
|
|
8048
7880
|
const inp = iw.querySelector('input');
|
|
8049
7881
|
inp.focus();
|
|
@@ -8074,14 +7906,14 @@ async function toggleErrDrawer(sessId, event) {
|
|
|
8074
7906
|
try {
|
|
8075
7907
|
const data = await apiFetch('/api/session-errors?dir=' + enc(DIR) + '&id=' + enc(sessId));
|
|
8076
7908
|
if (!data.errors?.length) {
|
|
8077
|
-
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>`;
|
|
8078
7910
|
return;
|
|
8079
7911
|
}
|
|
8080
7912
|
const items = data.errors.map(e => `<div class="err-item">${esc(e.text)}</div>`).join('');
|
|
8081
|
-
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>
|
|
8082
7914
|
<div class="err-drawer-body">${items}</div>`;
|
|
8083
7915
|
} catch (err) {
|
|
8084
|
-
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>`;
|
|
8085
7917
|
}
|
|
8086
7918
|
}
|
|
8087
7919
|
|
|
@@ -8115,7 +7947,7 @@ function buildCostHistogram() {
|
|
|
8115
7947
|
const counts = new Array(BUCKETS).fill(0);
|
|
8116
7948
|
for (const c of costs) { const i = Math.min(BUCKETS - 1, Math.floor((c - minC) / bucketSize)); counts[i]++; }
|
|
8117
7949
|
const maxCount = Math.max(1, ...counts);
|
|
8118
|
-
const fmt = v => v < 0.01 ?
|
|
7950
|
+
const fmt = v => v < 0.01 ? v.toFixed(4) : v < 1 ? '$' + v.toFixed(2) : '$' + v.toFixed(1);
|
|
8119
7951
|
const bars = counts.map((n, i) => {
|
|
8120
7952
|
const h = Math.max(2, Math.round((n / maxCount) * 46));
|
|
8121
7953
|
const lo = minC + i * bucketSize; const hi = lo + bucketSize;
|
|
@@ -8181,25 +8013,26 @@ function esc(s) {
|
|
|
8181
8013
|
|
|
8182
8014
|
function relTime(ts) {
|
|
8183
8015
|
if (!ts) return '';
|
|
8184
|
-
const
|
|
8185
|
-
const diff = Date.now() - abs;
|
|
8016
|
+
const diff = Date.now() - (typeof ts === 'number' ? ts : Number(ts) || new Date(ts).getTime() || 0);
|
|
8186
8017
|
const s = Math.floor(diff / 1000);
|
|
8187
8018
|
if (s < 5) return 'now';
|
|
8188
|
-
if (s < 60) return s + 's
|
|
8019
|
+
if (s < 60) return s + 's';
|
|
8189
8020
|
const m = Math.floor(s / 60);
|
|
8190
|
-
if (m < 60) return m + 'm
|
|
8021
|
+
if (m < 60) return m + 'm';
|
|
8191
8022
|
const h = Math.floor(m / 60);
|
|
8192
|
-
if (h < 24) return h + 'h
|
|
8193
|
-
|
|
8194
|
-
|
|
8195
|
-
|
|
8196
|
-
|
|
8197
|
-
|
|
8198
|
-
|
|
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('/');
|
|
8199
8032
|
}
|
|
8200
8033
|
|
|
8201
8034
|
function fmtDur(ms) {
|
|
8202
|
-
if (
|
|
8035
|
+
if (ms > 0 && ms < 1000) return '<1s';
|
|
8203
8036
|
const s = Math.floor(ms / 1000);
|
|
8204
8037
|
if (s < 60) return s + 's';
|
|
8205
8038
|
const m = Math.floor(s / 60);
|
|
@@ -8396,7 +8229,7 @@ function renderMgOverview() {
|
|
|
8396
8229
|
topEl.innerHTML = prSorted.map(n => `
|
|
8397
8230
|
<div class="mg-bar-row">
|
|
8398
8231
|
<div class="mg-bar-lbl" title="${esc(n.label)}">${esc(mgShortLabel(n.label))}</div>
|
|
8399
|
-
<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>
|
|
8400
8233
|
<div class="mg-bar-val">${n.score.toExponential(1)}</div>
|
|
8401
8234
|
</div>`).join('');
|
|
8402
8235
|
}
|
|
@@ -8421,7 +8254,7 @@ function renderMgOverview() {
|
|
|
8421
8254
|
typeEl.innerHTML = typeEntries.map(([t, c]) => `
|
|
8422
8255
|
<div class="mg-bar-row">
|
|
8423
8256
|
<div class="mg-bar-lbl">${esc(t)}</div>
|
|
8424
|
-
<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>
|
|
8425
8258
|
<div class="mg-bar-val">${c}</div>
|
|
8426
8259
|
</div>`).join('');
|
|
8427
8260
|
}
|
|
@@ -8473,11 +8306,9 @@ async function mgRebuild() {
|
|
|
8473
8306
|
try {
|
|
8474
8307
|
const res = await fetch('/api/monograph-build?dir=' + enc(DIR), { method: 'POST' });
|
|
8475
8308
|
if (!res.ok) throw new Error('HTTP ' + res.status);
|
|
8476
|
-
showToast('Rebuilding', 'Knowledge graph rebuild started', 'info');
|
|
8477
8309
|
_mgLoaded = false;
|
|
8478
8310
|
await loadMonograph();
|
|
8479
|
-
|
|
8480
|
-
} catch (e) { showToast('Error', e.message, 'err'); }
|
|
8311
|
+
} catch (_) {}
|
|
8481
8312
|
btn.disabled = false; btn.textContent = 'REBUILD';
|
|
8482
8313
|
}
|
|
8483
8314
|
|
|
@@ -8604,16 +8435,14 @@ function mgRunClient(id) {
|
|
|
8604
8435
|
} else if (id === 'pagerank') {
|
|
8605
8436
|
// Proper power-iteration PageRank (d=0.85, 50 iterations)
|
|
8606
8437
|
const prMap = mgComputePageRank(g);
|
|
8607
|
-
const
|
|
8438
|
+
const ranked = nodes.map(n => {
|
|
8608
8439
|
const k = n.id || n.name || n.label || '';
|
|
8609
8440
|
return { label: n.label || n.name || k, score: prMap[k] || 0 };
|
|
8610
|
-
}).sort((a, b) => b.score - a.score);
|
|
8611
|
-
const ranked = allRanked.slice(0, 20);
|
|
8441
|
+
}).sort((a, b) => b.score - a.score).slice(0, 20);
|
|
8612
8442
|
const maxScore = ranked.length ? ranked[0].score : 1;
|
|
8613
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>` +
|
|
8614
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('') +
|
|
8615
|
-
`</tbody></table
|
|
8616
|
-
(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>`;
|
|
8617
8446
|
|
|
8618
8447
|
} else if (id === 'deadcode') {
|
|
8619
8448
|
const isolated = nodes.filter(n => (degMap[n.id || n.name || n.label || ''] || 0) === 0);
|
|
@@ -8645,8 +8474,7 @@ function mgRunClient(id) {
|
|
|
8645
8474
|
<div class="mg-kv-card"><div class="mkv-k">Components</div><div class="mkv-v">${comps.length}</div></div>
|
|
8646
8475
|
<div class="mg-kv-card"><div class="mkv-k">Largest</div><div class="mkv-v">${comps[0] ? comps[0].length : 0}</div></div>
|
|
8647
8476
|
</div>` +
|
|
8648
|
-
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('')
|
|
8649
|
-
(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('');
|
|
8650
8478
|
|
|
8651
8479
|
} else if (id === 'topo') {
|
|
8652
8480
|
const inDeg = {}; const adj = {};
|
|
@@ -8723,16 +8551,14 @@ function mgRunClient(id) {
|
|
|
8723
8551
|
|
|
8724
8552
|
} else if (id === 'betweenness') {
|
|
8725
8553
|
const bc = mgApproxBetweenness(g);
|
|
8726
|
-
const
|
|
8727
|
-
const bcRanked = bcAll.slice(0,20);
|
|
8554
|
+
const bcRanked = Object.entries(bc).sort((a,b) => b[1]-a[1]).slice(0,20);
|
|
8728
8555
|
const maxBC = bcRanked.length ? bcRanked[0][1] : 1;
|
|
8729
8556
|
if (!bcRanked.length || maxBC === 0) { html = '<div class="loading-txt">Not enough graph data for betweenness</div>'; }
|
|
8730
8557
|
else {
|
|
8731
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>
|
|
8732
8559
|
<table class="mg-table"><thead><tr><th>#</th><th>Node</th><th>Score</th><th style="width:160px">Weight</th></tr></thead><tbody>` +
|
|
8733
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('') +
|
|
8734
|
-
`</tbody></table
|
|
8735
|
-
(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>`;
|
|
8736
8562
|
}
|
|
8737
8563
|
|
|
8738
8564
|
} else if (id === 'jaccard') {
|
|
@@ -8855,7 +8681,7 @@ function mgRenderAnalysisResult(toolId, text) {
|
|
|
8855
8681
|
return `<div class="mgr-ranked-row">
|
|
8856
8682
|
<span class="mgr-ranked-num">${r.rank}</span>
|
|
8857
8683
|
<span class="mgr-ranked-name" title="${h(r.name)}">${h(r.name)}</span>
|
|
8858
|
-
<div class="mgr-ranked-bar-wrap"
|
|
8684
|
+
<div class="mgr-ranked-bar-wrap"><div class="mgr-ranked-bar-fill" style="width:${pct}%"></div></div>
|
|
8859
8685
|
<span class="mgr-ranked-val">${h(String(r.val))}</span>
|
|
8860
8686
|
</div>`;
|
|
8861
8687
|
}).join('')}</div>`;
|
|
@@ -8985,7 +8811,7 @@ function mgRenderAnalysisResult(toolId, text) {
|
|
|
8985
8811
|
return `<div class="mgr-ranked-row">
|
|
8986
8812
|
<span class="mgr-ranked-num">${r.rank}</span>
|
|
8987
8813
|
<span class="mgr-ranked-name" title="${h(r.path)}">${h(name)}</span>
|
|
8988
|
-
<div class="mgr-ranked-bar-wrap"
|
|
8814
|
+
<div class="mgr-ranked-bar-wrap"><div class="mgr-ranked-bar-fill" style="width:${pct}%"></div></div>
|
|
8989
8815
|
<span class="mgr-ranked-val">${r.lines.toLocaleString()}</span>
|
|
8990
8816
|
</div>`;
|
|
8991
8817
|
}).join('')}</div>`;
|
|
@@ -9033,13 +8859,6 @@ async function mgRunServer(id) {
|
|
|
9033
8859
|
}
|
|
9034
8860
|
|
|
9035
8861
|
// ── QUERY TAB ──────────────────────────────────────────────
|
|
9036
|
-
// Click any query result box to copy its text content
|
|
9037
|
-
document.addEventListener('click', e => {
|
|
9038
|
-
const el = e.target.closest('.mg-query-result');
|
|
9039
|
-
if (!el || !el.textContent.trim()) return;
|
|
9040
|
-
navigator.clipboard.writeText(el.textContent).then(() => showToast('Copied', 'Query result copied', 'ok')).catch(() => {});
|
|
9041
|
-
});
|
|
9042
|
-
|
|
9043
8862
|
function mgQuerySearch(q) {
|
|
9044
8863
|
const res = document.getElementById('mg-q-search-result');
|
|
9045
8864
|
if (!q.trim()) { res.style.display = 'none'; return; }
|
|
@@ -9234,7 +9053,7 @@ function mgRenderExport() {
|
|
|
9234
9053
|
<div class="mg-export-card">
|
|
9235
9054
|
<div class="mec-name">${esc(x.name)}</div>
|
|
9236
9055
|
<div class="mec-desc">${esc(x.desc)}</div>
|
|
9237
|
-
<button class="btn"
|
|
9056
|
+
<button class="btn" onclick="mgExport('${esc(x.id)}')" style="margin-top:auto">EXPORT</button>
|
|
9238
9057
|
</div>`).join('');
|
|
9239
9058
|
}
|
|
9240
9059
|
|
|
@@ -9498,8 +9317,8 @@ function mgRenderWiki() {
|
|
|
9498
9317
|
// type pills
|
|
9499
9318
|
const types = [...new Set(nodes.map(n => n.type || n.kind || 'unknown'))].sort();
|
|
9500
9319
|
const pillsEl = document.getElementById('mg-wiki-pills');
|
|
9501
|
-
pillsEl.innerHTML = `<span class="mg-pill active" data-type="all"
|
|
9502
|
-
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('');
|
|
9503
9322
|
|
|
9504
9323
|
mgRenderWikiList('');
|
|
9505
9324
|
}
|
|
@@ -9570,11 +9389,9 @@ function mgWikiSearchDebounced(q) {
|
|
|
9570
9389
|
apiFetch('/api/monograph-wiki-search?q=' + enc(q) + '&dir=' + enc(DIR))
|
|
9571
9390
|
.then(d => {
|
|
9572
9391
|
if (!d || !d.nodes || !d.nodes.length) return;
|
|
9573
|
-
|
|
9574
|
-
el._wikiData = serverNodes;
|
|
9575
|
-
el.innerHTML = serverNodes.map((n, idx) => {
|
|
9392
|
+
el.innerHTML = d.nodes.slice(0, 50).map(n => {
|
|
9576
9393
|
const lbl = n.label || n.name || '';
|
|
9577
|
-
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>`;
|
|
9578
9395
|
}).join('');
|
|
9579
9396
|
}).catch(() => {});
|
|
9580
9397
|
}
|
|
@@ -9599,9 +9416,9 @@ function mgWikiShowDetail(idx) {
|
|
|
9599
9416
|
</div>
|
|
9600
9417
|
${contentPreview ? `<div class="mdp-content-preview">${esc(contentPreview)}</div>` : ''}
|
|
9601
9418
|
<div class="mdp-actions">
|
|
9602
|
-
${contentPreview ? `<button class="btn" id="mg-copy-btn"
|
|
9603
|
-
<button class="btn"
|
|
9604
|
-
<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>
|
|
9605
9422
|
</div>
|
|
9606
9423
|
<div class="mdp-explain-result" id="mg-explain-result" style="display:none"></div>
|
|
9607
9424
|
<div id="mg-related-result" style="display:none;margin-top:10px"></div>`;
|
|
@@ -9636,26 +9453,6 @@ async function mgWikiExplain(nodeId, btn) {
|
|
|
9636
9453
|
btn.disabled = false; btn.textContent = 'EXPLAIN';
|
|
9637
9454
|
}
|
|
9638
9455
|
|
|
9639
|
-
function mgWikiJumpToNode(nodeId) {
|
|
9640
|
-
if (!_mgGraph) return;
|
|
9641
|
-
const el = document.getElementById('mg-wiki-list');
|
|
9642
|
-
const data = el._wikiData || (_mgGraph.nodes || []);
|
|
9643
|
-
const idx = data.findIndex(n => (n.id || n.name || n.label || '') === nodeId);
|
|
9644
|
-
if (idx >= 0) {
|
|
9645
|
-
mgWikiShowDetail(idx);
|
|
9646
|
-
document.getElementById('mg-wiki-detail')?.scrollIntoView({ behavior:'smooth', block:'nearest' });
|
|
9647
|
-
} else {
|
|
9648
|
-
// node not in current filtered list — search for it and jump
|
|
9649
|
-
const searchEl = document.getElementById('mg-wiki-search');
|
|
9650
|
-
if (searchEl) { searchEl.value = nodeId; mgRenderWikiList(nodeId); }
|
|
9651
|
-
setTimeout(() => {
|
|
9652
|
-
const newData = document.getElementById('mg-wiki-list')?._wikiData || [];
|
|
9653
|
-
const newIdx = newData.findIndex(n => (n.id || n.name || n.label || '') === nodeId);
|
|
9654
|
-
if (newIdx >= 0) mgWikiShowDetail(newIdx);
|
|
9655
|
-
}, 100);
|
|
9656
|
-
}
|
|
9657
|
-
}
|
|
9658
|
-
|
|
9659
9456
|
function mgWikiFindRelated(nodeId) {
|
|
9660
9457
|
const el = document.getElementById('mg-related-result');
|
|
9661
9458
|
if (!el || !_mgGraph) return;
|
|
@@ -9671,12 +9468,11 @@ function mgWikiFindRelated(nodeId) {
|
|
|
9671
9468
|
hop1.forEach(v => { (adj[v] ? [...adj[v]] : []).forEach(w => { if (w !== nodeId && !hop1.includes(w)) hop2.add(w); }); });
|
|
9672
9469
|
const related = [...hop1, ...hop2].slice(0, 20);
|
|
9673
9470
|
if (!related.length) { el.innerHTML = '<div class="loading-txt">No related nodes found</div>'; el.style.display = 'block'; return; }
|
|
9674
|
-
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>` +
|
|
9675
9472
|
`<ul class="mg-node-list">` + related.map(k => {
|
|
9676
9473
|
const n = (_mgGraph.nodes || []).find(x => (x.id || x.name || x.label || '') === k);
|
|
9677
9474
|
const lbl = n ? (n.label || n.name || k) : k;
|
|
9678
|
-
|
|
9679
|
-
return `<li style="cursor:pointer;display:flex;align-items:center;gap:6px;padding:2px 0" onclick="mgWikiJumpToNode(${JSON.stringify(k)})" title="Open ${esc(lbl)}"><span style="opacity:0.6">${typeIcon}</span>${esc(lbl)}</li>`;
|
|
9475
|
+
return `<li>${esc(lbl)}</li>`;
|
|
9680
9476
|
}).join('') + `</ul>`;
|
|
9681
9477
|
el.style.display = 'block';
|
|
9682
9478
|
}
|
|
@@ -9692,19 +9488,18 @@ async function mgRebuildDocs() {
|
|
|
9692
9488
|
btn.disabled = true; btn.textContent = 'BUILDING…';
|
|
9693
9489
|
try {
|
|
9694
9490
|
await fetch('/api/monograph-build-docs?dir=' + enc(DIR), { method:'POST' });
|
|
9695
|
-
showToast('Building', 'Documentation build started…', 'info');
|
|
9696
9491
|
// poll until done (max 60 attempts = ~2 minutes)
|
|
9697
9492
|
let polls = 0;
|
|
9698
9493
|
const poll = async () => {
|
|
9699
|
-
if (++polls > 60) { btn.disabled = false; btn.textContent = 'BUILD DOCS';
|
|
9494
|
+
if (++polls > 60) { btn.disabled = false; btn.textContent = 'BUILD DOCS'; return; }
|
|
9700
9495
|
try {
|
|
9701
9496
|
const d = await apiFetch('/api/monograph-build-docs-status?dir=' + enc(DIR));
|
|
9702
|
-
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; }
|
|
9703
9498
|
} catch (_) {}
|
|
9704
9499
|
setTimeout(poll, 2000);
|
|
9705
9500
|
};
|
|
9706
9501
|
poll();
|
|
9707
|
-
} catch (e) { btn.disabled = false; btn.textContent = 'BUILD DOCS';
|
|
9502
|
+
} catch (e) { btn.disabled = false; btn.textContent = 'BUILD DOCS'; }
|
|
9708
9503
|
}
|
|
9709
9504
|
|
|
9710
9505
|
// ── memory CRUD ────────────────────────────────────────────
|
|
@@ -9742,41 +9537,6 @@ async function loadMemoriesTab() {
|
|
|
9742
9537
|
}
|
|
9743
9538
|
}
|
|
9744
9539
|
|
|
9745
|
-
function filterMemList(q) {
|
|
9746
|
-
const lq = (q || '').toLowerCase();
|
|
9747
|
-
let totalVisible = 0;
|
|
9748
|
-
document.querySelectorAll('#mem-list-pane .mem-item').forEach(el => {
|
|
9749
|
-
const text = (el.textContent || '').toLowerCase();
|
|
9750
|
-
const show = !lq || text.includes(lq);
|
|
9751
|
-
el.style.display = show ? '' : 'none';
|
|
9752
|
-
if (show) totalVisible++;
|
|
9753
|
-
});
|
|
9754
|
-
document.querySelectorAll('#mem-list-pane .mem-type-hdr').forEach(hdr => {
|
|
9755
|
-
let next = hdr.nextElementSibling;
|
|
9756
|
-
let anyVisible = false;
|
|
9757
|
-
while (next && !next.classList.contains('mem-type-hdr')) {
|
|
9758
|
-
if (next.style.display !== 'none') anyVisible = true;
|
|
9759
|
-
next = next.nextElementSibling;
|
|
9760
|
-
}
|
|
9761
|
-
hdr.style.display = anyVisible ? '' : 'none';
|
|
9762
|
-
});
|
|
9763
|
-
const list = document.getElementById('mem-list-pane');
|
|
9764
|
-
const noRes = document.getElementById('mem-filter-noresult');
|
|
9765
|
-
if (lq && totalVisible === 0 && _memFiles.length > 0) {
|
|
9766
|
-
if (!noRes) {
|
|
9767
|
-
const el = document.createElement('div');
|
|
9768
|
-
el.id = 'mem-filter-noresult';
|
|
9769
|
-
el.style.cssText = 'padding:12px 14px;color:var(--text-lo);font-size:12px';
|
|
9770
|
-
el.textContent = 'No memories match "' + q.slice(0, 30) + '"';
|
|
9771
|
-
list.appendChild(el);
|
|
9772
|
-
} else {
|
|
9773
|
-
noRes.textContent = 'No memories match "' + q.slice(0, 30) + '"';
|
|
9774
|
-
}
|
|
9775
|
-
} else if (noRes) {
|
|
9776
|
-
noRes.remove();
|
|
9777
|
-
}
|
|
9778
|
-
}
|
|
9779
|
-
|
|
9780
9540
|
function _renderMemList() {
|
|
9781
9541
|
const list = document.getElementById('mem-list-pane');
|
|
9782
9542
|
if (!list) return;
|
|
@@ -9792,8 +9552,7 @@ function _renderMemList() {
|
|
|
9792
9552
|
const col = _MEM_COLORS[type] || _MEM_COLOR_FALLBACK;
|
|
9793
9553
|
const fname = f.filename || f.name || '?';
|
|
9794
9554
|
const active = _selMemFilename === fname ? ' active' : '';
|
|
9795
|
-
|
|
9796
|
-
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)">' +
|
|
9797
9556
|
'<span class="mem-type-dot" style="background:' + esc(col) + ';flex-shrink:0"></span>' +
|
|
9798
9557
|
'<span class="mem-item-name">' + esc(f.name || fname.replace('.md', '')) + '</span>' +
|
|
9799
9558
|
'</div>';
|
|
@@ -9812,17 +9571,17 @@ function selectMem(filename) {
|
|
|
9812
9571
|
const rawBody = f.body || f.content || '';
|
|
9813
9572
|
const bodyHtml = rawBody
|
|
9814
9573
|
.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
|
9815
|
-
.replace(/\*\*(.+?)\*\*/g, (_, g) => '<strong>' + g + '</strong>')
|
|
9816
|
-
.replace(/^#{1,3} (.+)/gm, (_, g) => '<div style="font-weight:600;color:var(--text-hi);margin:6px 0 2px">' + g + '</div>')
|
|
9817
|
-
.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>');
|
|
9818
9577
|
const srcBadge = f.source === 'backend'
|
|
9819
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>'
|
|
9820
9579
|
: '';
|
|
9821
9580
|
const actions = (f.readonly || f.source === 'backend')
|
|
9822
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>'
|
|
9823
9582
|
: '<div class="mem-actions">' +
|
|
9824
|
-
'<button class="btn"
|
|
9825
|
-
'<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>' +
|
|
9826
9585
|
'</div>';
|
|
9827
9586
|
detail.innerHTML =
|
|
9828
9587
|
'<span class="mem-badge" style="background:' + col + '22;color:' + col + '">' + esc(f.type || '?') + '</span>' + srcBadge +
|
|
@@ -9932,7 +9691,7 @@ function _renderSwarmRunList() {
|
|
|
9932
9691
|
'<span class="swarm-topo-pill">' + esc(topo) + '</span>' +
|
|
9933
9692
|
(live ? '<span class="swarm-live">⬤ LIVE</span>' : '') +
|
|
9934
9693
|
'</div>' +
|
|
9935
|
-
'<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>' +
|
|
9936
9695
|
'</div>';
|
|
9937
9696
|
}).join('');
|
|
9938
9697
|
}
|
|
@@ -9945,7 +9704,7 @@ async function selectSwarmRun(idx) {
|
|
|
9945
9704
|
if (!detail) return;
|
|
9946
9705
|
detail.innerHTML =
|
|
9947
9706
|
'<div style="margin-bottom:10px">' +
|
|
9948
|
-
'<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>' +
|
|
9949
9708
|
'<div style="font-size:11px;color:var(--text-lo);margin-top:3px">' + esc(run.topology || '—') + ' · ' + esc(run.consensus || '—') + ' · ' + (run.agentCount || 0) + ' agents</div>' +
|
|
9950
9709
|
'</div>' +
|
|
9951
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>' +
|
|
@@ -10022,16 +9781,14 @@ function _renderSwarmAgents(run) {
|
|
|
10022
9781
|
if (!el) return;
|
|
10023
9782
|
const agents = run.agents || [];
|
|
10024
9783
|
if (!agents.length) { el.innerHTML = ''; return; }
|
|
10025
|
-
const shownA = agents.slice(0, 15);
|
|
10026
9784
|
el.innerHTML = '<div class="m-group-title" style="margin-bottom:4px">Agents</div>' +
|
|
10027
|
-
|
|
9785
|
+
agents.slice(0, 15).map(a =>
|
|
10028
9786
|
'<div style="display:flex;gap:10px;padding:3px 0;font-size:11px;border-bottom:1px solid var(--border)">' +
|
|
10029
|
-
'<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>' +
|
|
10030
9788
|
'<span style="color:var(--text-hi)">' + esc(a.type || a.role || 'worker') + '</span>' +
|
|
10031
9789
|
'<span style="margin-left:auto;color:var(--text-xs)">' + esc(a.status || '—') + '</span>' +
|
|
10032
9790
|
'</div>'
|
|
10033
|
-
).join('')
|
|
10034
|
-
(agents.length > 15 ? '<div style="font-size:11px;color:var(--text-xs);padding:3px 0">+' + (agents.length - 15) + ' more agents</div>' : '');
|
|
9791
|
+
).join('');
|
|
10035
9792
|
}
|
|
10036
9793
|
|
|
10037
9794
|
async function _loadSwarmEvents(swarmId) {
|
|
@@ -10042,13 +9799,11 @@ async function _loadSwarmEvents(swarmId) {
|
|
|
10042
9799
|
const data = await apiFetch('/api/swarm-events?agentId=' + enc(swarmId) + '&dir=' + enc(DIR));
|
|
10043
9800
|
const events = Array.isArray(data) ? data : (data.events || []);
|
|
10044
9801
|
if (!events.length) { el.innerHTML = ''; return; }
|
|
10045
|
-
|
|
10046
|
-
|
|
10047
|
-
shownEv.map(e =>
|
|
9802
|
+
el.innerHTML = '<div class="m-group-title" style="margin-bottom:3px">Events</div>' +
|
|
9803
|
+
events.slice(-40).map(e =>
|
|
10048
9804
|
'<div style="color:var(--text-lo);padding:2px 0;border-bottom:1px solid var(--border)">' +
|
|
10049
|
-
|
|
10050
|
-
|
|
10051
|
-
'<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)) +
|
|
10052
9807
|
'</div>'
|
|
10053
9808
|
).join('');
|
|
10054
9809
|
el.scrollTop = el.scrollHeight;
|
|
@@ -10112,25 +9867,23 @@ function _renderChunks(list) {
|
|
|
10112
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>';
|
|
10113
9868
|
return;
|
|
10114
9869
|
}
|
|
10115
|
-
|
|
10116
|
-
grid.innerHTML = shown.map(c => {
|
|
9870
|
+
grid.innerHTML = list.slice(0, 200).map(c => {
|
|
10117
9871
|
const src = (c.source || c.file || '').split('/').slice(-2).join('/');
|
|
10118
9872
|
const excerpt = (c.content || c.text || c.body || '').slice(0, 220);
|
|
10119
9873
|
const ns = c.namespace || c.type || '';
|
|
10120
|
-
const chunkId = JSON.stringify(c.id || c.path || c.source || '')
|
|
10121
|
-
const chunkContent = JSON.stringify(c.content || c.text || c.body || '')
|
|
10122
|
-
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);
|
|
10123
9877
|
return '<div class="chunk-card" data-search="' + esc((src + ' ' + excerpt + ' ' + ns).toLowerCase()) + '">' +
|
|
10124
9878
|
'<div class="chunk-src">' + esc(src || '—') + '</div>' +
|
|
10125
|
-
'<div class="chunk-excerpt"
|
|
9879
|
+
'<div class="chunk-excerpt">' + esc(excerpt) + '</div>' +
|
|
10126
9880
|
'<div class="chunk-footer">' +
|
|
10127
9881
|
(ns ? '<span class="chunk-ns">' + esc(ns) + '</span>' : '') +
|
|
10128
|
-
'<button class="btn"
|
|
10129
|
-
'<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>' +
|
|
10130
9884
|
'</div>' +
|
|
10131
9885
|
'</div>';
|
|
10132
|
-
}).join('')
|
|
10133
|
-
(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('');
|
|
10134
9887
|
}
|
|
10135
9888
|
|
|
10136
9889
|
function openChunkEdit(id, content, srcLabel) {
|
|
@@ -10204,33 +9957,9 @@ async function buildKnowledgeDocs() {
|
|
|
10204
9957
|
|
|
10205
9958
|
function filterChunks(q) {
|
|
10206
9959
|
const lq = q.toLowerCase();
|
|
10207
|
-
|
|
10208
|
-
|
|
10209
|
-
cards.forEach(el => {
|
|
10210
|
-
const show = !lq || (el.dataset.search || '').includes(lq);
|
|
10211
|
-
el.style.display = show ? '' : 'none';
|
|
10212
|
-
if (show) visible++;
|
|
9960
|
+
document.querySelectorAll('#chunks-grid .chunk-card').forEach(el => {
|
|
9961
|
+
el.style.display = (!lq || (el.dataset.search || '').includes(lq)) ? '' : 'none';
|
|
10213
9962
|
});
|
|
10214
|
-
const countEl = document.getElementById('chunk-count-val');
|
|
10215
|
-
if (countEl && lq) countEl.textContent = visible + ' / ' + cards.length;
|
|
10216
|
-
else if (countEl) countEl.textContent = cards.length.toLocaleString();
|
|
10217
|
-
// zero-results empty state
|
|
10218
|
-
const grid = document.getElementById('chunks-grid');
|
|
10219
|
-
const noRes = document.getElementById('chunks-filter-noresult');
|
|
10220
|
-
if (lq && visible === 0 && cards.length > 0) {
|
|
10221
|
-
if (!noRes) {
|
|
10222
|
-
const el = document.createElement('div');
|
|
10223
|
-
el.id = 'chunks-filter-noresult';
|
|
10224
|
-
el.className = 'empty';
|
|
10225
|
-
el.style.cssText = 'padding:20px 0;font-size:13px';
|
|
10226
|
-
el.textContent = 'No chunks match "' + q.slice(0, 40) + '"';
|
|
10227
|
-
grid.appendChild(el);
|
|
10228
|
-
} else {
|
|
10229
|
-
noRes.textContent = 'No chunks match "' + q.slice(0, 40) + '"';
|
|
10230
|
-
}
|
|
10231
|
-
} else if (noRes) {
|
|
10232
|
-
noRes.remove();
|
|
10233
|
-
}
|
|
10234
9963
|
}
|
|
10235
9964
|
|
|
10236
9965
|
async function deleteChunk(id) {
|
|
@@ -10257,28 +9986,8 @@ async function loadAgentGraphTab() {
|
|
|
10257
9986
|
const bar = document.getElementById('ag-summary-bar');
|
|
10258
9987
|
if (bar) bar.innerHTML = '<div class="loading-txt">Loading…</div>';
|
|
10259
9988
|
try {
|
|
10260
|
-
const
|
|
10261
|
-
|
|
10262
|
-
const sesNodes = (raw.nodes || []).filter(n => n.type === 'session');
|
|
10263
|
-
const agNodes = (raw.nodes || []).filter(n => n.type === 'agenttype');
|
|
10264
|
-
const sessions = sesNodes.map(n => ({
|
|
10265
|
-
id: n.id,
|
|
10266
|
-
file: n.id,
|
|
10267
|
-
turns: n.turns || 0,
|
|
10268
|
-
toolCount: n.totalTools || 0,
|
|
10269
|
-
spawnCount: Object.values(n.agentSpawns || {}).reduce((a, b) => a + b, 0),
|
|
10270
|
-
cost: n.cost || 0,
|
|
10271
|
-
agentTypes: n.agentSpawns || {},
|
|
10272
|
-
tools: n.toolCounts || {},
|
|
10273
|
-
}));
|
|
10274
|
-
_agData = {
|
|
10275
|
-
sessions,
|
|
10276
|
-
sessionCount: sessions.length,
|
|
10277
|
-
agentTypes: agNodes.length,
|
|
10278
|
-
totalSpawns: agNodes.reduce((a, n) => a + (n.totalSpawns || 0), 0),
|
|
10279
|
-
totalToolCalls: sesNodes.reduce((a, n) => a + (n.totalTools || 0), 0),
|
|
10280
|
-
totalCost: sesNodes.reduce((a, n) => a + (n.cost || 0), 0),
|
|
10281
|
-
};
|
|
9989
|
+
const data = await apiFetch('/api/graph?dir=' + enc(DIR));
|
|
9990
|
+
_agData = data;
|
|
10282
9991
|
_renderAgSummary();
|
|
10283
9992
|
_renderAgSessList();
|
|
10284
9993
|
} catch (e) {
|
|
@@ -10309,27 +10018,15 @@ function _renderAgSessList() {
|
|
|
10309
10018
|
const el = document.getElementById('ag-sess-list');
|
|
10310
10019
|
if (!_agData || !el) return;
|
|
10311
10020
|
const sessions = _agData.sessions || [];
|
|
10312
|
-
if (!sessions.length) { el.innerHTML = '<div class="empty" style="font-size:12px">No sessions
|
|
10313
|
-
el.innerHTML =
|
|
10314
|
-
'<
|
|
10315
|
-
|
|
10316
|
-
|
|
10317
|
-
'
|
|
10318
|
-
|
|
10319
|
-
|
|
10320
|
-
|
|
10321
|
-
'<div style="font-size:10px;color:var(--text-lo);margin-top:1px">' + (s.spawnCount || 0) + ' spawns · ' + (s.toolCount || 0) + ' tools</div>' +
|
|
10322
|
-
'</div>'
|
|
10323
|
-
).join('') +
|
|
10324
|
-
'</div>';
|
|
10325
|
-
}
|
|
10326
|
-
|
|
10327
|
-
function filterAgSessions(q) {
|
|
10328
|
-
const lq = (q || '').toLowerCase();
|
|
10329
|
-
document.querySelectorAll('#ag-sess-rows .sess-row').forEach(el => {
|
|
10330
|
-
const sid = (el.dataset.sid || '').toLowerCase();
|
|
10331
|
-
el.style.display = (!lq || sid.includes(lq)) ? '' : 'none';
|
|
10332
|
-
});
|
|
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('');
|
|
10333
10030
|
}
|
|
10334
10031
|
|
|
10335
10032
|
function selectAgSession(idx) {
|
|
@@ -10338,10 +10035,8 @@ function selectAgSession(idx) {
|
|
|
10338
10035
|
document.querySelectorAll('#ag-sess-list .sess-row').forEach((el, i) => el.classList.toggle('active', i === idx));
|
|
10339
10036
|
const detail = document.getElementById('ag-detail');
|
|
10340
10037
|
if (!detail) return;
|
|
10341
|
-
const
|
|
10342
|
-
const
|
|
10343
|
-
const agArr = agAllArr.slice(0, 12);
|
|
10344
|
-
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);
|
|
10345
10040
|
const maxAg = agArr.length ? Math.max(...agArr.map(x => x[1])) : 1;
|
|
10346
10041
|
const maxTool = toolArr.length ? Math.max(...toolArr.map(x => x[1])) : 1;
|
|
10347
10042
|
detail.innerHTML =
|
|
@@ -10351,20 +10046,20 @@ function selectAgSession(idx) {
|
|
|
10351
10046
|
'<span>Tools: <b>' + (s.toolCount || 0) + '</b></span>' +
|
|
10352
10047
|
(s.cost != null ? '<span style="color:var(--accent)">$' + Number(s.cost).toFixed(4) + '</span>' : '') +
|
|
10353
10048
|
'</div>' +
|
|
10354
|
-
(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>' +
|
|
10355
10050
|
agArr.map(function(entry) {
|
|
10356
10051
|
var type = entry[0], count = entry[1];
|
|
10357
10052
|
return '<div style="display:flex;align-items:center;gap:8px;padding:3px 0;font-size:12px">' +
|
|
10358
|
-
'<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>' +
|
|
10359
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>' +
|
|
10360
10055
|
'<div style="width:22px;text-align:right;color:var(--text-lo);font-size:11px;font-family:var(--mono)">' + count + '</div>' +
|
|
10361
10056
|
'</div>';
|
|
10362
10057
|
}).join('') : '') +
|
|
10363
|
-
(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>' +
|
|
10364
10059
|
toolArr.map(function(entry) {
|
|
10365
10060
|
var tool = entry[0], count = entry[1];
|
|
10366
10061
|
return '<div style="display:flex;align-items:center;gap:8px;padding:3px 0;font-size:12px">' +
|
|
10367
|
-
'<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>' +
|
|
10368
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>' +
|
|
10369
10064
|
'<div style="width:22px;text-align:right;color:var(--text-lo);font-size:11px;font-family:var(--mono)">' + count + '</div>' +
|
|
10370
10065
|
'</div>';
|
|
@@ -10428,10 +10123,10 @@ function mmSwitchTab(tab) {
|
|
|
10428
10123
|
const body = document.getElementById('mm-body');
|
|
10429
10124
|
if (!body) return;
|
|
10430
10125
|
if (tab === 'orgs') mmRenderOrgs(body);
|
|
10431
|
-
else if (tab === 'skills')
|
|
10432
|
-
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>'); }
|
|
10433
10128
|
else if (tab === 'createorg') mmRenderCreateOrg(body);
|
|
10434
|
-
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>'); }
|
|
10435
10130
|
else if (tab === 'graph') mmRenderGraph(body);
|
|
10436
10131
|
}
|
|
10437
10132
|
|
|
@@ -10442,7 +10137,7 @@ function mmRenderOrgs(body) {
|
|
|
10442
10137
|
const running = o.running;
|
|
10443
10138
|
return `<div class="mm-skill-item" onclick="closeMastermind();v2SelectOrg(${JSON.stringify(o.name)});switchView('orgs')">
|
|
10444
10139
|
<span class="mm-skill-name">${esc(o.name)}</span>
|
|
10445
|
-
<span class="mm-skill-desc"
|
|
10140
|
+
<span class="mm-skill-desc">${esc((o.goal || '').slice(0, 60))} ${running ? '⬤ LIVE' : ''}</span>
|
|
10446
10141
|
</div>`;
|
|
10447
10142
|
}).join('');
|
|
10448
10143
|
}
|
|
@@ -10451,8 +10146,8 @@ let _mmSkillFilter = '';
|
|
|
10451
10146
|
function mmRenderSkills(body) {
|
|
10452
10147
|
const q = _mmSkillFilter.toLowerCase();
|
|
10453
10148
|
const filtered = q ? _MM_SKILLS_CATALOG.filter(s => s.name.toLowerCase().includes(q) || s.desc.toLowerCase().includes(q)) : _MM_SKILLS_CATALOG;
|
|
10454
|
-
body.innerHTML = `<div class="filter-bar" style="margin-bottom:12px"><input class="filter-input"
|
|
10455
|
-
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'))">
|
|
10456
10151
|
<span class="mm-skill-name">${esc(s.name)}</span>
|
|
10457
10152
|
<span class="mm-skill-desc">${esc(s.desc)}</span>
|
|
10458
10153
|
</div>`).join('');
|
|
@@ -10464,66 +10159,30 @@ async function mmRenderLoops(body) {
|
|
|
10464
10159
|
const data = await apiFetch('/api/loops?dir=' + enc(DIR));
|
|
10465
10160
|
const loops = Array.isArray(data) ? data : (data.loops || []);
|
|
10466
10161
|
if (!loops.length) { body.innerHTML = '<div class="empty">No active loops. Use /mastermind:autodev --tillend to start one.</div>'; return; }
|
|
10467
|
-
|
|
10468
|
-
|
|
10469
|
-
|
|
10470
|
-
|
|
10471
|
-
|
|
10472
|
-
const
|
|
10473
|
-
|
|
10474
|
-
|
|
10475
|
-
|
|
10476
|
-
|
|
10477
|
-
const
|
|
10478
|
-
const
|
|
10479
|
-
const
|
|
10480
|
-
const
|
|
10481
|
-
const
|
|
10482
|
-
const isExplicitlyActiveMm = l.status === 'running' || l.status === 'waiting' || l.status === 'active';
|
|
10483
|
-
const _STALE = 2 * 60 * 60 * 1000;
|
|
10484
|
-
const isOverdueMm = !l.status?.startsWith('hil') && !isExplicitlyActiveMm &&
|
|
10485
|
-
nextAt > 0 && nextAt <= Date.now();
|
|
10486
|
-
const isStaledActiveMm = isExplicitlyActiveMm && nextAt > 0 && (Date.now() - nextAt) > _STALE;
|
|
10487
|
-
const isFinishedMm = isOverdueMm || isStaledActiveMm ||
|
|
10488
|
-
(!isExplicitlyActiveMm && maxReps > 0 && curRep >= maxReps) ||
|
|
10489
|
-
l.status === 'finished' || l.status === 'done' ||
|
|
10490
|
-
l.status === 'complete' || l.status === 'completed' || l.status === 'expired';
|
|
10491
|
-
const running = !isFinishedMm && l.status !== 'stopped' && l.status !== 'paused';
|
|
10492
|
-
const _mmLp = (function(_l) {
|
|
10493
|
-
if (_l.command) return { userPrompt: _l.prompt || '', command: _l.command };
|
|
10494
|
-
const full = _l.prompt || '';
|
|
10495
|
-
const cmdM = full.match(/^(\/\S+)/);
|
|
10496
|
-
if (!cmdM) return { userPrompt: full, command: '' };
|
|
10497
|
-
const tokens = full.slice(cmdM[1].length).trim().split(/\s+/);
|
|
10498
|
-
let ti = 0;
|
|
10499
|
-
while (ti < tokens.length && tokens[ti] && tokens[ti].startsWith('--')) {
|
|
10500
|
-
ti++;
|
|
10501
|
-
if (ti < tokens.length && tokens[ti] && !tokens[ti].startsWith('--')) ti++;
|
|
10502
|
-
}
|
|
10503
|
-
return { userPrompt: tokens.slice(ti).join(' '), command: cmdM[1] };
|
|
10504
|
-
})(l);
|
|
10505
|
-
const name = (l.name || _mmLp.userPrompt || _mmLp.command || 'loop').slice(0, 60);
|
|
10506
|
-
const ms = nextAt ? nextAt - Date.now() : 0;
|
|
10507
|
-
const cdown = ms > 0 ? (Math.floor(ms/60000) + 'm ' + Math.floor((ms%60000)/1000) + 's') : '';
|
|
10508
|
-
const intervalMm = fmtInterval(l.interval || l.schedule);
|
|
10509
|
-
const runCount = isTillendMm
|
|
10510
|
-
? `run ${curRep} / ∞${maxReps ? ' (cap: ' + maxReps + ')' : ''}`
|
|
10511
|
-
: (maxReps > 0 ? `run ${curRep} / ${maxReps}` : (curRep ? `run ${curRep}` : ''));
|
|
10512
|
-
const statusLabel = isHilMm ? '⚠ HIL' : (running ? 'active' : (isFinishedMm ? 'done' : 'stopped'));
|
|
10513
|
-
const statusColor = isHilMm ? 'oklch(75% 0.16 60)' : '';
|
|
10514
|
-
const typeIco = isTillendMm ? '∞ ' : '↺ ';
|
|
10515
|
-
const _sMs = l.startedAt ? (typeof l.startedAt === 'number' ? l.startedAt : new Date(l.startedAt).getTime()) : 0;
|
|
10516
|
-
const ageMs = _sMs > 0 && _sMs < Date.now() ? Date.now() - _sMs : 0;
|
|
10517
|
-
const ageStr = ageMs > 0 ? fmtDur(ageMs) : '';
|
|
10518
|
-
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 ? '∞' : '↺';
|
|
10519
10177
|
return `<div style="padding:10px 0;border-bottom:1px solid var(--border)">
|
|
10520
10178
|
<div style="display:flex;align-items:center;gap:8px">
|
|
10521
|
-
<span style="font-size:13px;color:var(--
|
|
10522
|
-
<span
|
|
10523
|
-
${
|
|
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>` : ''}
|
|
10524
10184
|
</div>
|
|
10525
|
-
<div style="font-size:11px;color:var(--text-lo);margin-top:4px;font-family:var(--mono)">${esc(
|
|
10526
|
-
${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>
|
|
10527
10186
|
</div>`;
|
|
10528
10187
|
}).join('');
|
|
10529
10188
|
} catch (e) { body.innerHTML = '<div class="empty">Failed: ' + esc(e.message) + '</div>'; }
|
|
@@ -10537,17 +10196,17 @@ function mmRenderCreateOrg(body) {
|
|
|
10537
10196
|
<div><div class="le-lbl">Org Name</div><input id="mco-name" class="filter-input" placeholder="my-team"></div>
|
|
10538
10197
|
<div><div class="le-lbl">Goal</div><input id="mco-goal" class="filter-input" placeholder="Build and ship features autonomously"></div>
|
|
10539
10198
|
<div><div class="le-lbl">Topology</div>
|
|
10540
|
-
<select id="mco-topo" class="filter-input"
|
|
10199
|
+
<select id="mco-topo" class="filter-input" style="cursor:pointer">
|
|
10541
10200
|
${topos.map(t => `<option>${t}</option>`).join('')}
|
|
10542
10201
|
</select>
|
|
10543
10202
|
</div>
|
|
10544
10203
|
<div><div class="le-lbl">Adapter</div>
|
|
10545
|
-
<select id="mco-adapter" class="filter-input"
|
|
10204
|
+
<select id="mco-adapter" class="filter-input" style="cursor:pointer">
|
|
10546
10205
|
${adapters.map(a => `<option>${a}</option>`).join('')}
|
|
10547
10206
|
</select>
|
|
10548
10207
|
</div>
|
|
10549
|
-
<div><div class="le-lbl">Max Agents</div><input id="mco-agents" class="filter-input" type="number" value="8" min="1" max="50"
|
|
10550
|
-
<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>
|
|
10551
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>
|
|
10552
10211
|
</div>`;
|
|
10553
10212
|
}
|
|
@@ -10591,7 +10250,6 @@ async function mmRenderMetrics(body) {
|
|
|
10591
10250
|
</div>
|
|
10592
10251
|
<div class="m-group-title" style="margin-bottom:8px">Swarm</div>
|
|
10593
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>
|
|
10594
|
-
<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>
|
|
10595
10253
|
`;
|
|
10596
10254
|
} catch (e) { body.innerHTML = '<div class="empty">Failed: ' + esc(e.message) + '</div>'; }
|
|
10597
10255
|
}
|
|
@@ -10617,13 +10275,13 @@ async function mmRenderGraph(body) {
|
|
|
10617
10275
|
<div class="chunk-stat"><div class="chunk-stat-val">${Object.keys(nodeTypes).length}</div><div class="chunk-stat-lbl">Types</div></div>
|
|
10618
10276
|
</div>
|
|
10619
10277
|
${topNodes.length ? `<div class="m-group-title" style="margin-bottom:6px">God Nodes</div>
|
|
10620
|
-
${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)">
|
|
10621
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>
|
|
10622
|
-
<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>
|
|
10623
10281
|
</div>`).join('')}` : ''}
|
|
10624
10282
|
<div style="margin-top:14px;display:flex;gap:8px;flex-wrap:wrap">
|
|
10625
|
-
<button class="btn"
|
|
10626
|
-
<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>
|
|
10627
10285
|
</div>
|
|
10628
10286
|
`;
|
|
10629
10287
|
} catch (e) { body.innerHTML = '<div class="empty">Failed: ' + esc(e.message) + '</div>'; }
|