@monoes/monomindcli 1.10.46 → 1.10.54
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/commands/monomind/do.md +52 -0
- package/.claude/commands/monomind/improve.md +2 -0
- package/.claude/helpers/handlers/route-handler.cjs +61 -11
- package/.claude/helpers/skill-registry.json +99 -51
- package/.claude/skills/agent-browser-testing/SKILL.md +522 -152
- package/.claude/skills/github-issue-triage/SKILL.md +354 -0
- package/.claude/skills/github-repo-recap/SKILL.md +207 -0
- package/.claude/skills/mastermind/_delegation.md +83 -0
- package/.claude/skills/mastermind/_protocol.md +14 -0
- package/dist/src/init/executor.d.ts.map +1 -1
- package/dist/src/init/executor.js +18 -19
- package/dist/src/init/executor.js.map +1 -1
- package/dist/src/init/helpers-generator.js +3 -3
- package/dist/src/init/helpers-generator.js.map +1 -1
- package/dist/src/ui/dashboard-v2.html +782 -42
- package/dist/src/ui/dashboard.html +1 -0
- package/dist/src/ui/data/agent-avatars.html +763 -0
- package/dist/src/ui/data/agent-avatars.json +966 -0
- package/dist/src/ui/data/avatars/account-strategist.svg +58 -0
- package/dist/src/ui/data/avatars/accounts-payable.svg +54 -0
- package/dist/src/ui/data/avatars/adaptive-coordinator.svg +55 -0
- package/dist/src/ui/data/avatars/adaptive-coordinator2.svg +54 -0
- package/dist/src/ui/data/avatars/ai-citation.svg +57 -0
- package/dist/src/ui/data/avatars/ai-engineer.svg +61 -0
- package/dist/src/ui/data/avatars/analytics-reporter.svg +53 -0
- package/dist/src/ui/data/avatars/api-tester.svg +53 -0
- package/dist/src/ui/data/avatars/architecture.svg +54 -0
- package/dist/src/ui/data/avatars/automation-governance.svg +55 -0
- package/dist/src/ui/data/avatars/backend-dev.svg +53 -0
- package/dist/src/ui/data/avatars/benchmarker.svg +54 -0
- package/dist/src/ui/data/avatars/blockchain-auditor.svg +53 -0
- package/dist/src/ui/data/avatars/byzantine-coord.svg +57 -0
- package/dist/src/ui/data/avatars/case-analyst.svg +57 -0
- package/dist/src/ui/data/avatars/cicd-engineer.svg +55 -0
- package/dist/src/ui/data/avatars/cloud-architect.svg +54 -0
- package/dist/src/ui/data/avatars/code-review-swarm.svg +57 -0
- package/dist/src/ui/data/avatars/coder-v119.svg +57 -0
- package/dist/src/ui/data/avatars/coder.svg +58 -0
- package/dist/src/ui/data/avatars/collective-coord.svg +54 -0
- package/dist/src/ui/data/avatars/compliance-auditor.svg +58 -0
- package/dist/src/ui/data/avatars/consensus-coordinator.svg +54 -0
- package/dist/src/ui/data/avatars/content-creator.svg +54 -0
- package/dist/src/ui/data/avatars/crdt-synchronizer.svg +53 -0
- package/dist/src/ui/data/avatars/cro-specialist.svg +58 -0
- package/dist/src/ui/data/avatars/data-consolidator.svg +54 -0
- package/dist/src/ui/data/avatars/data-engineer.svg +53 -0
- package/dist/src/ui/data/avatars/database-optimizer.svg +61 -0
- package/dist/src/ui/data/avatars/deal-strategist.svg +54 -0
- package/dist/src/ui/data/avatars/defender.svg +53 -0
- package/dist/src/ui/data/avatars/devops-automator.svg +56 -0
- package/dist/src/ui/data/avatars/discovery-coach.svg +54 -0
- package/dist/src/ui/data/avatars/email-marketing.svg +57 -0
- package/dist/src/ui/data/avatars/embedded-firmware.svg +61 -0
- package/dist/src/ui/data/avatars/evidence-collector.svg +57 -0
- package/dist/src/ui/data/avatars/experiment-tracker.svg +53 -0
- package/dist/src/ui/data/avatars/feedback-synthesizer.svg +54 -0
- package/dist/src/ui/data/avatars/finance-tracker.svg +54 -0
- package/dist/src/ui/data/avatars/frontend-developer.svg +54 -0
- package/dist/src/ui/data/avatars/game-audio-engineer.svg +59 -0
- package/dist/src/ui/data/avatars/game-designer.svg +54 -0
- package/dist/src/ui/data/avatars/gossip-coordinator.svg +54 -0
- package/dist/src/ui/data/avatars/hierarchical-coord.svg +54 -0
- package/dist/src/ui/data/avatars/incident-commander.svg +57 -0
- package/dist/src/ui/data/avatars/infrastructure.svg +54 -0
- package/dist/src/ui/data/avatars/input-validator.svg +53 -0
- package/dist/src/ui/data/avatars/ios-developer.svg +54 -0
- package/dist/src/ui/data/avatars/issue-tracker.svg +53 -0
- package/dist/src/ui/data/avatars/judge.svg +55 -0
- package/dist/src/ui/data/avatars/launch-strategist.svg +54 -0
- package/dist/src/ui/data/avatars/legal-compliance.svg +53 -0
- package/dist/src/ui/data/avatars/level-designer.svg +53 -0
- package/dist/src/ui/data/avatars/load-balancer.svg +57 -0
- package/dist/src/ui/data/avatars/mcp-builder.svg +53 -0
- package/dist/src/ui/data/avatars/memory-coordinator.svg +55 -0
- package/dist/src/ui/data/avatars/mesh-coordinator.svg +55 -0
- package/dist/src/ui/data/avatars/ml-developer.svg +58 -0
- package/dist/src/ui/data/avatars/mobile-app-builder.svg +53 -0
- package/dist/src/ui/data/avatars/mobile-dev.svg +54 -0
- package/dist/src/ui/data/avatars/model-qa.svg +58 -0
- package/dist/src/ui/data/avatars/narrative-designer.svg +58 -0
- package/dist/src/ui/data/avatars/outbound-strategist.svg +55 -0
- package/dist/src/ui/data/avatars/path-validator.svg +54 -0
- package/dist/src/ui/data/avatars/payment-agent.svg +53 -0
- package/dist/src/ui/data/avatars/perf-analyzer.svg +58 -0
- package/dist/src/ui/data/avatars/pipeline-analyst.svg +54 -0
- package/dist/src/ui/data/avatars/planner.svg +55 -0
- package/dist/src/ui/data/avatars/pr-manager.svg +54 -0
- package/dist/src/ui/data/avatars/pricing-strategist.svg +54 -0
- package/dist/src/ui/data/avatars/product-manager.svg +54 -0
- package/dist/src/ui/data/avatars/production-validator.svg +54 -0
- package/dist/src/ui/data/avatars/project-shepherd.svg +54 -0
- package/dist/src/ui/data/avatars/proposal-strategist.svg +54 -0
- package/dist/src/ui/data/avatars/prosecutor.svg +57 -0
- package/dist/src/ui/data/avatars/pseudocode.svg +53 -0
- package/dist/src/ui/data/avatars/queen-coordinator.svg +55 -0
- package/dist/src/ui/data/avatars/quorum-manager.svg +53 -0
- package/dist/src/ui/data/avatars/raft-manager.svg +53 -0
- package/dist/src/ui/data/avatars/reality-checker.svg +58 -0
- package/dist/src/ui/data/avatars/recruitment.svg +58 -0
- package/dist/src/ui/data/avatars/refinement.svg +53 -0
- package/dist/src/ui/data/avatars/release-manager.svg +54 -0
- package/dist/src/ui/data/avatars/repo-architect.svg +54 -0
- package/dist/src/ui/data/avatars/researcher.svg +58 -0
- package/dist/src/ui/data/avatars/resource-allocator.svg +53 -0
- package/dist/src/ui/data/avatars/reviewer.svg +53 -0
- package/dist/src/ui/data/avatars/safe-executor.svg +53 -0
- package/dist/src/ui/data/avatars/sales-coach.svg +53 -0
- package/dist/src/ui/data/avatars/sales-engineer.svg +58 -0
- package/dist/src/ui/data/avatars/scout-explorer.svg +58 -0
- package/dist/src/ui/data/avatars/security-architect.svg +54 -0
- package/dist/src/ui/data/avatars/security-auditor.svg +55 -0
- package/dist/src/ui/data/avatars/senior-developer.svg +58 -0
- package/dist/src/ui/data/avatars/senior-pm.svg +58 -0
- package/dist/src/ui/data/avatars/seo-specialist.svg +57 -0
- package/dist/src/ui/data/avatars/social-media.svg +54 -0
- package/dist/src/ui/data/avatars/solidity-engineer.svg +58 -0
- package/dist/src/ui/data/avatars/sparc-coder.svg +58 -0
- package/dist/src/ui/data/avatars/sparc-coord.svg +56 -0
- package/dist/src/ui/data/avatars/specification.svg +57 -0
- package/dist/src/ui/data/avatars/sprint-prioritizer.svg +53 -0
- package/dist/src/ui/data/avatars/sre.svg +54 -0
- package/dist/src/ui/data/avatars/studio-operations.svg +53 -0
- package/dist/src/ui/data/avatars/studio-producer.svg +55 -0
- package/dist/src/ui/data/avatars/support-responder.svg +56 -0
- package/dist/src/ui/data/avatars/system-architect.svg +54 -0
- package/dist/src/ui/data/avatars/task-orchestrator.svg +56 -0
- package/dist/src/ui/data/avatars/technical-artist.svg +53 -0
- package/dist/src/ui/data/avatars/technical-writer.svg +59 -0
- package/dist/src/ui/data/avatars/tester.svg +53 -0
- package/dist/src/ui/data/avatars/threat-detection.svg +61 -0
- package/dist/src/ui/data/avatars/trend-researcher.svg +54 -0
- package/dist/src/ui/data/avatars/trial-director.svg +55 -0
- package/dist/src/ui/data/avatars/unity-architect.svg +54 -0
- package/dist/src/ui/data/avatars/visionos-engineer.svg +57 -0
- package/dist/src/ui/data/avatars/worker-specialist.svg +55 -0
- package/dist/src/ui/data/avatars/workflow-architect.svg +57 -0
- package/dist/src/ui/data/avatars/workflow-automation.svg +54 -0
- package/dist/src/ui/data/avatars/zk-steward.svg +54 -0
- package/dist/src/ui/data/known-projects.json +1 -1
- package/dist/src/ui/data/mastermind-events.jsonl +28 -0
- package/dist/src/ui/orgs.html +1171 -0
- package/dist/src/ui/server.mjs +25 -8
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
|
@@ -34,7 +34,7 @@ html, body { height: 100%; background: var(--bg); color: var(--text-hi); font-fa
|
|
|
34
34
|
#sb-logo { padding: 18px 16px 14px; border-bottom: 1px solid var(--border); }
|
|
35
35
|
#sb-logo .mark { font-size: 13px; font-weight: 600; letter-spacing: 0.05em; color: var(--text-hi); }
|
|
36
36
|
#sb-logo .proj { font-size: 11px; color: var(--accent); margin-top: 3px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 500; }
|
|
37
|
-
#sb-nav { flex: 1; padding: 10px 8px; overflow-y: auto; }
|
|
37
|
+
#sb-nav { flex: 1; padding: 10px 8px; overflow-y: auto; display: flex; flex-direction: column; }
|
|
38
38
|
.nav-sect { margin-bottom: 8px; }
|
|
39
39
|
.nav-lbl { font-size: 10px; letter-spacing: 0.08em; color: var(--text-xs); padding: 8px 8px 4px; text-transform: uppercase; }
|
|
40
40
|
.nav-item { display: flex; align-items: center; gap: 8px; padding: 7px 8px; border-radius: var(--r); cursor: pointer; color: var(--text-mid); transition: background 0.1s, color 0.1s; user-select: none; }
|
|
@@ -44,6 +44,15 @@ html, body { height: 100%; background: var(--bg); color: var(--text-hi); font-fa
|
|
|
44
44
|
.nav-item .lbl { font-size: 13px; }
|
|
45
45
|
.nav-item .bdg { margin-left: auto; font-size: 10px; background: var(--surface-hi); color: var(--text-lo); border-radius: 8px; padding: 1px 6px; min-width: 18px; text-align: center; }
|
|
46
46
|
.nav-item.active .bdg { background: var(--accent-dim); color: var(--accent); }
|
|
47
|
+
/* project context section */
|
|
48
|
+
#nav-project-ctx { display: none; }
|
|
49
|
+
#nav-project-ctx.visible { display: block; }
|
|
50
|
+
.nav-proj-hdr { display: flex; align-items: center; gap: 7px; padding: 10px 8px 5px; cursor: pointer; }
|
|
51
|
+
.nav-proj-hdr:hover .nav-proj-name { color: var(--text-hi); }
|
|
52
|
+
.nav-proj-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--accent); flex-shrink: 0; }
|
|
53
|
+
.nav-proj-name { font-size: 12px; font-weight: 600; color: var(--accent); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
54
|
+
.nav-proj-items { padding-left: 12px; border-left: 1px solid oklch(72% 0.18 75 / 0.2); margin-left: 11px; }
|
|
55
|
+
.nav-no-proj { display: flex; align-items: center; gap: 8px; padding: 8px 8px 6px 10px; font-size: 11px; color: var(--text-xs); font-style: italic; }
|
|
47
56
|
#sb-footer { padding: 10px 14px; border-top: 1px solid var(--border); }
|
|
48
57
|
#sb-user { font-size: 12px; font-weight: 500; color: var(--text-mid); }
|
|
49
58
|
#sb-path { font-size: 10px; color: var(--text-lo); margin-top: 2px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-family: var(--mono); }
|
|
@@ -211,12 +220,103 @@ html, body { height: 100%; background: var(--bg); color: var(--text-hi); font-fa
|
|
|
211
220
|
.dr-ts { font-size: 10px; color: var(--text-lo); margin-top: 2px; }
|
|
212
221
|
.drawer-item.hidden { display: none; }
|
|
213
222
|
|
|
214
|
-
/* orgs */
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
223
|
+
/* orgs ── two-panel layout */
|
|
224
|
+
#view-orgs { flex-direction: row; }
|
|
225
|
+
#orgs-list-pane { width: 280px; flex-shrink: 0; border-right: 1px solid var(--border); display: flex; flex-direction: column; overflow: hidden; }
|
|
226
|
+
#orgs-list-head { padding: 14px 16px 12px; border-bottom: 1px solid var(--border); flex-shrink: 0; }
|
|
227
|
+
#orgs-list-scroll { flex: 1; overflow-y: auto; padding: 8px; }
|
|
228
|
+
#orgs-list-scroll::-webkit-scrollbar { width: 3px; }
|
|
229
|
+
#orgs-list-scroll::-webkit-scrollbar-thumb { background: var(--border); border-radius: 2px; }
|
|
230
|
+
.orgs-view-title { font-size: 14px; font-weight: 600; color: var(--text-hi); }
|
|
231
|
+
.orgs-view-sub { font-size: 11px; color: var(--text-lo); margin-top: 2px; }
|
|
232
|
+
|
|
233
|
+
.org-item { display: flex; align-items: flex-start; gap: 9px; padding: 9px 10px; border-radius: var(--r); cursor: pointer; transition: background 0.09s; border: 1px solid transparent; }
|
|
234
|
+
.org-item:hover { background: var(--surface-hi); }
|
|
235
|
+
.org-item.active { background: var(--accent-dim); border-color: oklch(72% 0.18 75 / 0.25); }
|
|
236
|
+
.oi-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--border); flex-shrink: 0; margin-top: 4px; transition: background 0.3s; }
|
|
237
|
+
.oi-dot.running { background: var(--green); box-shadow: 0 0 5px oklch(65% 0.15 150 / 0.5); }
|
|
238
|
+
@media (prefers-reduced-motion: no-preference) { .oi-dot.running { animation: blink 2s ease-in-out infinite; } }
|
|
239
|
+
.oi-body { flex: 1; min-width: 0; }
|
|
240
|
+
.oi-name { font-size: 13px; font-weight: 500; color: var(--text-hi); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
241
|
+
.oi-goal { font-size: 11px; color: var(--text-lo); margin-top: 2px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
242
|
+
.oi-chips { display: flex; gap: 5px; margin-top: 5px; flex-wrap: wrap; }
|
|
243
|
+
.oi-chip { font-size: 10px; padding: 1px 6px; border-radius: 8px; background: var(--surface-hi); color: var(--text-lo); }
|
|
244
|
+
.oi-chip.live { background: oklch(65% 0.15 150 / 0.12); color: var(--green); }
|
|
245
|
+
|
|
246
|
+
/* detail pane */
|
|
247
|
+
#org-detail-pane { flex: 1; display: flex; flex-direction: column; overflow: hidden; min-width: 0; }
|
|
248
|
+
#org-detail-pane.empty-state { align-items: center; justify-content: center; }
|
|
249
|
+
.orgs-no-sel { color: var(--text-lo); font-size: 13px; text-align: center; }
|
|
250
|
+
.orgs-no-sel-ico { font-size: 28px; margin-bottom: 10px; opacity: 0.3; }
|
|
251
|
+
|
|
252
|
+
#org-detail-head { padding: 12px 18px 11px; border-bottom: 1px solid var(--border); display: flex; align-items: center; gap: 10px; flex-shrink: 0; }
|
|
253
|
+
.odh-name { font-size: 16px; font-weight: 600; color: var(--text-hi); }
|
|
254
|
+
.odh-badge { font-size: 11px; padding: 2px 8px; border-radius: 8px; }
|
|
255
|
+
.odh-badge.idle { background: var(--surface-hi); color: var(--text-lo); }
|
|
256
|
+
.odh-badge.live { background: oklch(65% 0.15 150 / 0.12); color: var(--green); }
|
|
257
|
+
.odh-pill { font-size: 11px; padding: 2px 8px; border-radius: 8px; background: var(--surface); color: var(--text-lo); border: 1px solid var(--border); }
|
|
258
|
+
.odh-right { margin-left: auto; display: flex; gap: 6px; }
|
|
259
|
+
|
|
260
|
+
#org-detail-tabs { display: flex; gap: 0; border-bottom: 1px solid var(--border); flex-shrink: 0; padding: 0 16px; }
|
|
261
|
+
.odt-btn { font-size: 12px; padding: 8px 12px; background: none; border: none; border-bottom: 2px solid transparent; margin-bottom: -1px; cursor: pointer; color: var(--text-lo); font-family: var(--sans); transition: color 0.1s; }
|
|
262
|
+
.odt-btn:hover { color: var(--text-hi); }
|
|
263
|
+
.odt-btn.active { color: var(--accent); border-bottom-color: var(--accent); }
|
|
264
|
+
|
|
265
|
+
#org-detail-body { flex: 1; overflow-y: auto; padding: 18px 20px; }
|
|
266
|
+
#org-detail-body::-webkit-scrollbar { width: 3px; }
|
|
267
|
+
#org-detail-body::-webkit-scrollbar-thumb { background: var(--border); border-radius: 2px; }
|
|
268
|
+
.odt-pane { display: none; }
|
|
269
|
+
.odt-pane.active { display: block; }
|
|
270
|
+
|
|
271
|
+
/* org meta strip */
|
|
272
|
+
.org-meta-strip { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 8px; margin-bottom: 16px; }
|
|
273
|
+
.oms-cell { background: var(--surface); border: 1px solid var(--border); border-radius: var(--r); padding: 10px 12px; }
|
|
274
|
+
.oms-lbl { font-size: 10px; text-transform: uppercase; letter-spacing: 0.07em; color: var(--text-lo); margin-bottom: 4px; }
|
|
275
|
+
.oms-val { font-size: 12px; color: var(--text-hi); word-break: break-word; line-height: 1.4; }
|
|
276
|
+
|
|
277
|
+
/* SVG org chart */
|
|
278
|
+
.org-chart-wrap { background: var(--surface); border: 1px solid var(--border); border-radius: var(--r); overflow: hidden; margin-bottom: 12px; }
|
|
279
|
+
.org-chart-svg { width: 100%; display: block; }
|
|
280
|
+
.org-chart-legend { display: flex; gap: 14px; flex-wrap: wrap; padding: 0 2px; }
|
|
281
|
+
.ocl-item { display: flex; align-items: center; gap: 5px; font-size: 10px; color: var(--text-lo); }
|
|
282
|
+
|
|
283
|
+
/* roles in v2 */
|
|
284
|
+
.roles-v2-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 8px; }
|
|
285
|
+
.role-v2 { background: var(--surface); border: 1px solid var(--border); border-radius: var(--r); padding: 12px 14px; }
|
|
286
|
+
.rv2-head { display: flex; align-items: flex-start; gap: 10px; margin-bottom: 6px; }
|
|
287
|
+
.rv2-avatar { width: 48px; height: 48px; border-radius: 50%; flex-shrink: 0; object-fit: cover; }
|
|
288
|
+
.rv2-info { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 4px; padding-top: 2px; }
|
|
289
|
+
.rv2-title { font-size: 13px; font-weight: 500; color: var(--text-hi); line-height: 1.3; }
|
|
290
|
+
.rv2-agent { font-size: 10px; padding: 1px 7px; border-radius: 8px; background: var(--accent-dim); color: var(--accent); white-space: nowrap; align-self: flex-start; }
|
|
291
|
+
.rv2-id { font-size: 10px; color: var(--text-lo); font-family: var(--mono); margin-bottom: 4px; }
|
|
292
|
+
.rv2-reports { font-size: 11px; color: var(--text-lo); margin-bottom: 6px; }
|
|
293
|
+
.rv2-reports span { color: var(--text-mid); }
|
|
294
|
+
.rv2-resps { }
|
|
295
|
+
.rv2-resp { font-size: 11px; color: var(--text-lo); line-height: 1.6; display: flex; gap: 6px; }
|
|
296
|
+
.rv2-resp::before { content: '–'; opacity: 0.4; flex-shrink: 0; }
|
|
297
|
+
/* role avatars in org list */
|
|
298
|
+
.oi-avatars { display: flex; margin-top: 5px; }
|
|
299
|
+
.oi-av { width: 22px; height: 22px; border-radius: 50%; object-fit: cover; margin-right: -5px; border: 1.5px solid var(--bg); }
|
|
300
|
+
.oi-av-more { width: 22px; height: 22px; border-radius: 50%; background: var(--surface); border: 1.5px solid var(--border); font-size: 9px; color: var(--text-lo); display: flex; align-items: center; justify-content: center; margin-right: -5px; }
|
|
301
|
+
/* chart avatar clip */
|
|
302
|
+
.v2oc-node image { pointer-events: none; }
|
|
303
|
+
|
|
304
|
+
/* activity in v2 */
|
|
305
|
+
.act-v2-list { display: flex; flex-direction: column; }
|
|
306
|
+
.av2-row { display: grid; grid-template-columns: 56px 90px 1fr; gap: 8px; padding: 5px 0; border-bottom: 1px solid oklch(25% 0.008 55 / 0.5); font-size: 11px; align-items: baseline; }
|
|
307
|
+
.av2-row:last-child { border-bottom: none; }
|
|
308
|
+
.av2-time { color: var(--text-xs); font-family: var(--mono); font-size: 10px; }
|
|
309
|
+
.av2-type { color: var(--accent); font-weight: 500; }
|
|
310
|
+
.av2-msg { color: var(--text-lo); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
311
|
+
|
|
312
|
+
/* health in v2 */
|
|
313
|
+
.health-v2-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(130px, 1fr)); gap: 8px; margin-bottom: 12px; }
|
|
314
|
+
.hv2-cell { background: var(--surface); border: 1px solid var(--border); border-radius: var(--r); padding: 10px 12px; }
|
|
315
|
+
.hv2-lbl { font-size: 10px; text-transform: uppercase; letter-spacing: 0.07em; color: var(--text-lo); margin-bottom: 4px; }
|
|
316
|
+
.hv2-val { font-size: 20px; font-weight: 700; color: var(--text-hi); font-family: var(--mono); }
|
|
317
|
+
.hv2-val.green { color: var(--green); }
|
|
318
|
+
.hv2-val.amber { color: var(--accent); }
|
|
319
|
+
.hv2-val.red { color: var(--red); }
|
|
220
320
|
|
|
221
321
|
/* ── alerts rail ─────────────────────────────────────────── */
|
|
222
322
|
#alerts-rail { display: none; flex-shrink: 0; border-bottom: 1px solid var(--border); padding: 0 16px; min-height: 36px; flex-direction: row; gap: 8px; align-items: center; overflow-x: auto; }
|
|
@@ -832,7 +932,12 @@ textarea.sess-note-input:focus { border-color:var(--accent); }
|
|
|
832
932
|
.ctx-pressure-fill.warn { background:oklch(70% 0.18 80); }
|
|
833
933
|
.ctx-pressure-fill.crit { background:oklch(65% 0.2 25); }
|
|
834
934
|
.ctx-pressure-lbl { font-size:9px; color:var(--text-xs); white-space:nowrap; }
|
|
935
|
+
/* org chart nodes — CSS fallback animation */
|
|
936
|
+
.v2oc-node { transform-box: fill-box; transform-origin: center center; }
|
|
937
|
+
@keyframes v2nodeIn { from { transform: scale(0); opacity: 0; } to { transform: scale(1); opacity: 1; } }
|
|
938
|
+
@keyframes v2edgeIn { from { opacity: 0; } to { opacity: 1; } }
|
|
835
939
|
</style>
|
|
940
|
+
<script src="https://cdn.jsdelivr.net/npm/gsap@3/dist/gsap.min.js" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
|
836
941
|
</head>
|
|
837
942
|
<body>
|
|
838
943
|
<div id="app">
|
|
@@ -848,30 +953,36 @@ textarea.sess-note-input:focus { border-color:var(--accent); }
|
|
|
848
953
|
<div class="nav-item active" data-view="now">
|
|
849
954
|
<span class="ico">◉</span><span class="lbl">Now</span>
|
|
850
955
|
</div>
|
|
851
|
-
</div>
|
|
852
|
-
<div class="nav-sect">
|
|
853
|
-
<div class="nav-lbl">Workspace</div>
|
|
854
956
|
<div class="nav-item" data-view="projects">
|
|
855
957
|
<span class="ico">⊞</span><span class="lbl">Projects</span>
|
|
856
958
|
<span class="bdg" id="bdg-projects">—</span>
|
|
857
959
|
</div>
|
|
858
|
-
<div class="nav-item" data-view="sessions">
|
|
859
|
-
<span class="ico">◫</span><span class="lbl">Sessions</span>
|
|
860
|
-
<span class="bdg" id="bdg-sessions">—</span>
|
|
861
|
-
</div>
|
|
862
|
-
<div class="nav-item" data-view="loops">
|
|
863
|
-
<span class="ico">↺</span><span class="lbl">Loops</span>
|
|
864
|
-
<span class="bdg" id="bdg-loops">—</span>
|
|
865
|
-
</div>
|
|
866
960
|
</div>
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
<div class="nav-
|
|
870
|
-
<
|
|
961
|
+
<!-- project-scoped section: hidden until a project is selected -->
|
|
962
|
+
<div id="nav-project-ctx">
|
|
963
|
+
<div class="nav-proj-hdr" onclick="switchView('projects')" title="Change project">
|
|
964
|
+
<div class="nav-proj-dot"></div>
|
|
965
|
+
<div class="nav-proj-name" id="nav-proj-name">—</div>
|
|
871
966
|
</div>
|
|
872
|
-
<div class="nav-
|
|
873
|
-
<
|
|
967
|
+
<div class="nav-proj-items">
|
|
968
|
+
<div class="nav-item" data-view="sessions">
|
|
969
|
+
<span class="ico">◫</span><span class="lbl">Sessions</span>
|
|
970
|
+
<span class="bdg" id="bdg-sessions">—</span>
|
|
971
|
+
</div>
|
|
972
|
+
<div class="nav-item" data-view="loops">
|
|
973
|
+
<span class="ico">↺</span><span class="lbl">Loops</span>
|
|
974
|
+
<span class="bdg" id="bdg-loops">—</span>
|
|
975
|
+
</div>
|
|
976
|
+
<div class="nav-item" data-view="memory">
|
|
977
|
+
<span class="ico">◈</span><span class="lbl">Memory</span>
|
|
978
|
+
</div>
|
|
979
|
+
<div class="nav-item" data-view="orgs">
|
|
980
|
+
<span class="ico">⬡</span><span class="lbl">Orgs</span>
|
|
981
|
+
</div>
|
|
874
982
|
</div>
|
|
983
|
+
</div>
|
|
984
|
+
<div class="nav-no-proj" id="nav-no-proj-hint">Select a project above</div>
|
|
985
|
+
<div class="nav-sect" style="margin-top:auto;padding-top:8px;">
|
|
875
986
|
<div class="nav-item" data-view="global">
|
|
876
987
|
<span class="ico">⊕</span><span class="lbl">Global Feed</span>
|
|
877
988
|
</div>
|
|
@@ -1180,10 +1291,47 @@ textarea.sess-note-input:focus { border-color:var(--accent); }
|
|
|
1180
1291
|
|
|
1181
1292
|
<!-- ORGS -->
|
|
1182
1293
|
<div class="view" id="view-orgs">
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
<div
|
|
1186
|
-
|
|
1294
|
+
<!-- List sidebar -->
|
|
1295
|
+
<div id="orgs-list-pane">
|
|
1296
|
+
<div id="orgs-list-head">
|
|
1297
|
+
<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>
|
|
1298
|
+
<div class="orgs-view-sub" id="orgs-view-sub">MASTERMIND organizations</div>
|
|
1299
|
+
</div>
|
|
1300
|
+
<div id="orgs-list-scroll">
|
|
1301
|
+
<div class="loading-txt" id="orgs-list-content">Loading…</div>
|
|
1302
|
+
</div>
|
|
1303
|
+
</div>
|
|
1304
|
+
<!-- Detail pane -->
|
|
1305
|
+
<div id="org-detail-pane" class="empty-state">
|
|
1306
|
+
<div class="orgs-no-sel">
|
|
1307
|
+
<div class="orgs-no-sel-ico">⬡</div>
|
|
1308
|
+
Select an org
|
|
1309
|
+
</div>
|
|
1310
|
+
<div id="org-detail-inner" style="display:none;width:100%;height:100%;display:none;flex-direction:column;overflow:hidden;">
|
|
1311
|
+
<div id="org-detail-head">
|
|
1312
|
+
<div>
|
|
1313
|
+
<div class="odh-name" id="odh-name">—</div>
|
|
1314
|
+
</div>
|
|
1315
|
+
<span class="odh-badge idle" id="odh-badge">IDLE</span>
|
|
1316
|
+
<span class="odh-pill" id="odh-topo">—</span>
|
|
1317
|
+
<span class="odh-pill" id="odh-roles">0 roles</span>
|
|
1318
|
+
<div class="odh-right">
|
|
1319
|
+
<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>
|
|
1320
|
+
</div>
|
|
1321
|
+
</div>
|
|
1322
|
+
<div id="org-detail-tabs">
|
|
1323
|
+
<button class="odt-btn active" data-tab="chart" onclick="v2SwitchOrgTab('chart')">Chart</button>
|
|
1324
|
+
<button class="odt-btn" data-tab="roles" onclick="v2SwitchOrgTab('roles')">Roles</button>
|
|
1325
|
+
<button class="odt-btn" data-tab="activity" onclick="v2SwitchOrgTab('activity')">Activity</button>
|
|
1326
|
+
<button class="odt-btn" data-tab="health" onclick="v2SwitchOrgTab('health')">Health</button>
|
|
1327
|
+
</div>
|
|
1328
|
+
<div id="org-detail-body">
|
|
1329
|
+
<div class="odt-pane active" id="odt-chart"></div>
|
|
1330
|
+
<div class="odt-pane" id="odt-roles"></div>
|
|
1331
|
+
<div class="odt-pane" id="odt-activity"></div>
|
|
1332
|
+
<div class="odt-pane" id="odt-health"></div>
|
|
1333
|
+
</div>
|
|
1334
|
+
</div>
|
|
1187
1335
|
</div>
|
|
1188
1336
|
</div>
|
|
1189
1337
|
|
|
@@ -1325,6 +1473,7 @@ async function init() {
|
|
|
1325
1473
|
document.getElementById('sb-user').textContent = gu.name || gu.email || '—';
|
|
1326
1474
|
document.getElementById('sb-path').textContent = DIR;
|
|
1327
1475
|
document.getElementById('sb-proj').textContent = DIR.split('/').filter(Boolean).pop() || '—';
|
|
1476
|
+
_showNavProjectCtx(DIR);
|
|
1328
1477
|
} catch (_) {}
|
|
1329
1478
|
// deep-link: ?sess=<id>&proj=<path>
|
|
1330
1479
|
const params = new URLSearchParams(window.location.search);
|
|
@@ -1334,6 +1483,7 @@ async function init() {
|
|
|
1334
1483
|
DIR = projParam;
|
|
1335
1484
|
document.getElementById('sb-proj').textContent = DIR.split('/').filter(Boolean).pop() || '—';
|
|
1336
1485
|
document.getElementById('sb-path').textContent = DIR;
|
|
1486
|
+
_showNavProjectCtx(DIR);
|
|
1337
1487
|
}
|
|
1338
1488
|
restoreURLParams();
|
|
1339
1489
|
viewRendered['now'] = true;
|
|
@@ -1369,13 +1519,36 @@ async function apiFetch(url) {
|
|
|
1369
1519
|
}
|
|
1370
1520
|
|
|
1371
1521
|
// ── project switching ──────────────────────────────────────
|
|
1522
|
+
function _showNavProjectCtx(path) {
|
|
1523
|
+
const projName = (path || '').split('/').filter(Boolean).pop() || '';
|
|
1524
|
+
const ctx = document.getElementById('nav-project-ctx');
|
|
1525
|
+
const hint = document.getElementById('nav-no-proj-hint');
|
|
1526
|
+
if (projName) {
|
|
1527
|
+
document.getElementById('nav-proj-name').textContent = projName;
|
|
1528
|
+
ctx.classList.add('visible');
|
|
1529
|
+
hint.style.display = 'none';
|
|
1530
|
+
} else {
|
|
1531
|
+
ctx.classList.remove('visible');
|
|
1532
|
+
hint.style.display = '';
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1372
1536
|
function switchProject(path) {
|
|
1373
1537
|
if (DIR === path) { switchView('sessions'); return; }
|
|
1374
1538
|
DIR = path;
|
|
1375
1539
|
document.getElementById('sb-proj').textContent = path.split('/').filter(Boolean).pop() || '—';
|
|
1376
1540
|
document.getElementById('sb-path').textContent = path;
|
|
1541
|
+
_showNavProjectCtx(path);
|
|
1377
1542
|
viewRendered = {};
|
|
1378
1543
|
allSessions = [];
|
|
1544
|
+
_v2SelOrg = null;
|
|
1545
|
+
_v2OrgData = null;
|
|
1546
|
+
_v2Orgs = [];
|
|
1547
|
+
Object.keys(_v2OrgEventLog).forEach(k => delete _v2OrgEventLog[k]);
|
|
1548
|
+
document.getElementById('org-detail-pane')?.classList.add('empty-state');
|
|
1549
|
+
document.getElementById('org-detail-inner') && (document.getElementById('org-detail-inner').style.display = 'none');
|
|
1550
|
+
const _noSel = document.querySelector('#org-detail-pane .orgs-no-sel');
|
|
1551
|
+
if (_noSel) _noSel.style.display = '';
|
|
1379
1552
|
closeDetail();
|
|
1380
1553
|
switchView('sessions');
|
|
1381
1554
|
}
|
|
@@ -3204,26 +3377,593 @@ function filterMemoryNs(ns) {
|
|
|
3204
3377
|
}
|
|
3205
3378
|
|
|
3206
3379
|
// ── orgs ───────────────────────────────────────────────────
|
|
3380
|
+
let _v2Orgs = [];
|
|
3381
|
+
let _v2SelOrg = null;
|
|
3382
|
+
let _v2OrgData = null;
|
|
3383
|
+
let _v2OrgTab = 'chart';
|
|
3384
|
+
const _v2OrgEventLog = {};
|
|
3385
|
+
|
|
3207
3386
|
async function renderOrgs() {
|
|
3208
|
-
const el = document.getElementById('orgs-content');
|
|
3209
|
-
el.innerHTML = '<div class="loading-txt">Loading…</div>';
|
|
3210
3387
|
try {
|
|
3211
|
-
const
|
|
3212
|
-
const
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3388
|
+
const q = DIR ? '?dir=' + encodeURIComponent(DIR) : '';
|
|
3389
|
+
const data = await apiFetch('/api/orgs' + q);
|
|
3390
|
+
_v2Orgs = Array.isArray(data) ? data : (data.orgs || []);
|
|
3391
|
+
} catch (_) { _v2Orgs = []; }
|
|
3392
|
+
// Show current project name in sub-header
|
|
3393
|
+
const orgProjLabel = document.getElementById('orgs-proj-label');
|
|
3394
|
+
if (orgProjLabel) orgProjLabel.textContent = DIR ? DIR.split('/').filter(Boolean).pop() : '';
|
|
3395
|
+
try { v2RenderOrgList(); } catch (_) {}
|
|
3396
|
+
}
|
|
3397
|
+
|
|
3398
|
+
function v2RenderOrgList() {
|
|
3399
|
+
const wrap = document.getElementById('orgs-list-scroll');
|
|
3400
|
+
const sub = document.getElementById('orgs-view-sub');
|
|
3401
|
+
if (!wrap || !sub) return;
|
|
3402
|
+
const running = _v2Orgs.filter(o => o.running).length;
|
|
3403
|
+
sub.textContent = running > 0
|
|
3404
|
+
? `${_v2Orgs.length} orgs · ${running} running`
|
|
3405
|
+
: `${_v2Orgs.length} org${_v2Orgs.length !== 1 ? 's' : ''}`;
|
|
3406
|
+
|
|
3407
|
+
if (!_v2Orgs.length) {
|
|
3408
|
+
wrap.innerHTML = '<div class="empty"><div class="empty-ico">⬡</div><div>No orgs found<br><span style="font-size:11px;color:var(--text-xs)">/mastermind:createorg</span></div></div>';
|
|
3409
|
+
return;
|
|
3410
|
+
}
|
|
3411
|
+
|
|
3412
|
+
wrap.innerHTML = _v2Orgs.map(o => {
|
|
3413
|
+
const rolesArr = Array.isArray(o.roles) ? o.roles : [];
|
|
3414
|
+
const rolesN = rolesArr.length || (typeof o.roles === 'number' ? o.roles : 0);
|
|
3415
|
+
const active = _v2SelOrg === o.name ? ' active' : '';
|
|
3416
|
+
const goalSnip = (o.goal || '').slice(0, 60) + ((o.goal || '').length > 60 ? '…' : '');
|
|
3417
|
+
const MAX_SHOW = 4;
|
|
3418
|
+
const shownRoles = rolesArr.slice(0, MAX_SHOW);
|
|
3419
|
+
const extraCount = rolesN - shownRoles.length;
|
|
3420
|
+
const avatarsHtml = shownRoles.length ? `<div class="oi-avatars">
|
|
3421
|
+
${shownRoles.map(r => `<img class="oi-av" src="${v2RoleAvatar(r)}" alt="${esc(r.name||r.id||'')}" loading="lazy" title="${esc(r.name||r.id||'')}" onerror="this.src='data/avatars/coder.svg'"/>`).join('')}
|
|
3422
|
+
${extraCount > 0 ? `<div class="oi-av-more">+${extraCount}</div>` : ''}
|
|
3423
|
+
</div>` : '';
|
|
3424
|
+
return `<div class="org-item${active}" data-org="${esc(o.name)}" onclick="v2SelectOrg(this.dataset.org)">
|
|
3425
|
+
<div class="oi-dot ${o.running ? 'running' : ''}"></div>
|
|
3426
|
+
<div class="oi-body">
|
|
3427
|
+
<div class="oi-name">${esc(o.name)}</div>
|
|
3428
|
+
${goalSnip ? `<div class="oi-goal">${esc(goalSnip)}</div>` : ''}
|
|
3429
|
+
<div class="oi-chips">
|
|
3430
|
+
${o.running ? '<span class="oi-chip live">LIVE</span>' : ''}
|
|
3431
|
+
<span class="oi-chip">${rolesN} roles</span>
|
|
3432
|
+
<span class="oi-chip">${esc(o.topology || 'hierarchical')}</span>
|
|
3433
|
+
</div>
|
|
3434
|
+
${avatarsHtml}
|
|
3435
|
+
</div>
|
|
3436
|
+
</div>`;
|
|
3437
|
+
}).join('');
|
|
3438
|
+
}
|
|
3439
|
+
|
|
3440
|
+
async function v2SelectOrg(name) {
|
|
3441
|
+
_v2SelOrg = name;
|
|
3442
|
+
const requested = name;
|
|
3443
|
+
const requestedDir = DIR;
|
|
3444
|
+
document.querySelectorAll('.org-item').forEach(el => {
|
|
3445
|
+
el.classList.toggle('active', el.dataset.org === name);
|
|
3446
|
+
});
|
|
3447
|
+
|
|
3448
|
+
// Show detail inner, hide placeholder
|
|
3449
|
+
const pane = document.getElementById('org-detail-pane');
|
|
3450
|
+
if (!pane) return;
|
|
3451
|
+
pane.classList.remove('empty-state');
|
|
3452
|
+
const noSel = pane.querySelector('.orgs-no-sel');
|
|
3453
|
+
if (noSel) noSel.style.display = 'none';
|
|
3454
|
+
const inner = document.getElementById('org-detail-inner');
|
|
3455
|
+
if (!inner) return;
|
|
3456
|
+
inner.style.display = 'flex';
|
|
3457
|
+
|
|
3458
|
+
// Populate header from list data
|
|
3459
|
+
const listOrg = _v2Orgs.find(o => o.name === name) || {};
|
|
3460
|
+
v2UpdateOrgHeader(listOrg, null);
|
|
3461
|
+
|
|
3462
|
+
// Fetch full data
|
|
3463
|
+
_v2OrgData = null;
|
|
3464
|
+
const _enc = encodeURIComponent(name);
|
|
3465
|
+
try {
|
|
3466
|
+
const [mainR, actR, healthR] = await Promise.all([
|
|
3467
|
+
apiFetch(`/api/org/${_enc}${DIR ? '?dir=' + encodeURIComponent(DIR) : ''}`),
|
|
3468
|
+
fetch(`/api/org/${_enc}/activity${DIR ? '?dir=' + encodeURIComponent(DIR) : ''}`).then(r=>r.ok?r.json():[]).catch(()=>[]),
|
|
3469
|
+
fetch(`/api/org/${_enc}/health${DIR ? '?dir=' + encodeURIComponent(DIR) : ''}`).then(r=>r.ok?r.json():null).catch(()=>null),
|
|
3470
|
+
]);
|
|
3471
|
+
if (_v2SelOrg !== requested || DIR !== requestedDir) return;
|
|
3472
|
+
// API returns {config, state, goals, ...} — flatten config; config wins for schema fields
|
|
3473
|
+
_v2OrgData = mainR.config ? { ...mainR, ...mainR.config, running: mainR.running } : mainR;
|
|
3474
|
+
_v2OrgData._activity = Array.isArray(actR) ? actR : [];
|
|
3475
|
+
_v2OrgData._health = healthR;
|
|
3476
|
+
} catch (_) {
|
|
3477
|
+
if (_v2SelOrg !== requested || DIR !== requestedDir) return;
|
|
3478
|
+
_v2OrgData = { ...listOrg, communication: [], _activity: [], _health: null };
|
|
3479
|
+
}
|
|
3480
|
+
|
|
3481
|
+
v2UpdateOrgHeader(listOrg, _v2OrgData);
|
|
3482
|
+
v2RenderOrgTab(_v2OrgTab);
|
|
3483
|
+
}
|
|
3484
|
+
|
|
3485
|
+
function v2UpdateOrgHeader(listOrg, data) {
|
|
3486
|
+
document.getElementById('odh-name').textContent = listOrg.name || _v2SelOrg || '—';
|
|
3487
|
+
const running = data ? (data.running ?? listOrg.running) : listOrg.running;
|
|
3488
|
+
const badge = document.getElementById('odh-badge');
|
|
3489
|
+
badge.textContent = running ? 'LIVE' : 'IDLE';
|
|
3490
|
+
badge.className = 'odh-badge ' + (running ? 'live' : 'idle');
|
|
3491
|
+
const roles = data ? (Array.isArray(data.roles) ? data.roles.length : (data.roles || 0)) : (listOrg.roles || 0);
|
|
3492
|
+
document.getElementById('odh-roles').textContent = roles + ' role' + (roles !== 1 ? 's' : '');
|
|
3493
|
+
document.getElementById('odh-topo').textContent = (data?.topology || listOrg.topology || '—');
|
|
3494
|
+
document.getElementById('org-stop-btn').style.display = running ? '' : 'none';
|
|
3495
|
+
}
|
|
3496
|
+
|
|
3497
|
+
window.v2SwitchOrgTab = function(tab) {
|
|
3498
|
+
_v2OrgTab = tab;
|
|
3499
|
+
document.querySelectorAll('.odt-btn').forEach(b => {
|
|
3500
|
+
b.classList.toggle('active', b.dataset.tab === tab);
|
|
3501
|
+
});
|
|
3502
|
+
document.querySelectorAll('.odt-pane').forEach(p => p.classList.remove('active'));
|
|
3503
|
+
document.getElementById('odt-' + tab)?.classList.add('active');
|
|
3504
|
+
v2RenderOrgTab(tab);
|
|
3505
|
+
};
|
|
3506
|
+
|
|
3507
|
+
function v2RenderOrgTab(tab) {
|
|
3508
|
+
if (!_v2OrgData) {
|
|
3509
|
+
const p = document.getElementById('odt-' + tab);
|
|
3510
|
+
if (p) p.innerHTML = '<div class="empty">Loading…</div>';
|
|
3511
|
+
return;
|
|
3512
|
+
}
|
|
3513
|
+
if (tab === 'chart') v2RenderOrgChart();
|
|
3514
|
+
else if (tab === 'roles') v2RenderOrgRoles();
|
|
3515
|
+
else if (tab === 'activity') v2RenderOrgActivity();
|
|
3516
|
+
else if (tab === 'health') v2RenderOrgHealth();
|
|
3517
|
+
}
|
|
3518
|
+
|
|
3519
|
+
// ── org chart ───────────────────────────────
|
|
3520
|
+
function _v2OrgIsLeader(r) {
|
|
3521
|
+
return r.id === 'boss' || r.type === 'coordinator' || r.type === 'planner';
|
|
3522
|
+
}
|
|
3523
|
+
|
|
3524
|
+
function v2RenderOrgChart() {
|
|
3525
|
+
if (!_v2SelOrg) return;
|
|
3526
|
+
const d = _v2OrgData;
|
|
3527
|
+
if (!d) return;
|
|
3528
|
+
const roles = Array.isArray(d.roles) ? d.roles : [];
|
|
3529
|
+
const listOrg = _v2Orgs.find(o => o.name === _v2SelOrg) || {};
|
|
3530
|
+
const isRunning = listOrg.running || d.running;
|
|
3531
|
+
const goalText = d.goal || '—';
|
|
3532
|
+
const topo = (d.topology || 'hierarchical').toLowerCase();
|
|
3533
|
+
const created = d.created_at ? new Date(d.created_at).toLocaleDateString('en', {month:'short',day:'numeric',year:'numeric'}) : '—';
|
|
3534
|
+
const pane = document.getElementById('odt-chart');
|
|
3535
|
+
|
|
3536
|
+
// ── color palette ─────────────────────────────────────
|
|
3537
|
+
const COLORS = [
|
|
3538
|
+
'oklch(72% 0.18 75)', // amber — leader
|
|
3539
|
+
'oklch(65% 0.12 240)', // blue
|
|
3540
|
+
'oklch(65% 0.15 150)', // green
|
|
3541
|
+
'oklch(65% 0.13 290)', // purple
|
|
3542
|
+
'oklch(65% 0.14 35)', // orange
|
|
3543
|
+
'oklch(62% 0.12 195)', // cyan
|
|
3544
|
+
];
|
|
3545
|
+
// fix: reserve COLORS[0] for leaders; non-leaders start at index 1
|
|
3546
|
+
const colorOf = (role, idx) => COLORS[_v2OrgIsLeader(role) ? 0 : (idx % (COLORS.length - 1)) + 1];
|
|
3547
|
+
|
|
3548
|
+
// ── auto-generate edges ───────────────────────────────
|
|
3549
|
+
let comms = Array.isArray(d.communication) ? [...d.communication] : [];
|
|
3550
|
+
if (!comms.length && roles.length > 1) {
|
|
3551
|
+
const _lr0 = roles.find(_v2OrgIsLeader) || roles[0];
|
|
3552
|
+
comms = roles.filter(r => r.id !== _lr0.id).flatMap(r => [
|
|
3553
|
+
{ from: _lr0.id, to: r.id, type: 'command' },
|
|
3554
|
+
{ from: r.id, to: _lr0.id, type: 'report' },
|
|
3555
|
+
]);
|
|
3556
|
+
}
|
|
3557
|
+
|
|
3558
|
+
// ── layout positions ──────────────────────────────────
|
|
3559
|
+
const W = 720, R = 42, PAD_X = R + 20, PAD_Y = R + 24;
|
|
3560
|
+
const roleIds = new Set(roles.map(r => r.id));
|
|
3561
|
+
// all leaders at depth 0 (supports co-lead orgs)
|
|
3562
|
+
const leaders = roles.filter(_v2OrgIsLeader);
|
|
3563
|
+
const lr = leaders[0] || roles[0];
|
|
3564
|
+
const hasSubs = lr && roles.length > 1;
|
|
3565
|
+
|
|
3566
|
+
const pos = {};
|
|
3567
|
+
let _layoutH = null; // separate from pos to avoid key collision
|
|
3568
|
+
|
|
3569
|
+
const _hubAndSpoke = () => {
|
|
3570
|
+
if (leaders.length > 0) {
|
|
3571
|
+
leaders.forEach((r, i) => {
|
|
3572
|
+
const x = leaders.length === 1 ? W / 2 : PAD_X + ((W - PAD_X * 2) / (leaders.length - 1)) * i;
|
|
3573
|
+
pos[r.id] = { x, y: PAD_Y };
|
|
3574
|
+
});
|
|
3216
3575
|
}
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
|
|
3576
|
+
const subs = roles.filter(r => !_v2OrgIsLeader(r));
|
|
3577
|
+
const usableW = W - PAD_X * 2;
|
|
3578
|
+
// collapse to single row when no leaders — avoids blank 220px upper band
|
|
3579
|
+
const subY = leaders.length > 0 ? PAD_Y + 220 : PAD_Y;
|
|
3580
|
+
subs.forEach((r, i) => {
|
|
3581
|
+
const x = subs.length === 1 ? W / 2 : PAD_X + (usableW / (subs.length - 1)) * i;
|
|
3582
|
+
pos[r.id] = { x, y: subY };
|
|
3583
|
+
});
|
|
3584
|
+
const _hasBothRows = leaders.length > 0 && subs.length > 0;
|
|
3585
|
+
_layoutH = _hasBothRows ? PAD_Y + 220 + R + 8 : PAD_Y + R + 8;
|
|
3586
|
+
};
|
|
3587
|
+
|
|
3588
|
+
if (!roles.length) {
|
|
3589
|
+
// nothing
|
|
3590
|
+
} else if (!hasSubs) {
|
|
3591
|
+
pos[roles[0].id] = { x: W / 2, y: 90 };
|
|
3592
|
+
} else if (topo === 'mesh') {
|
|
3593
|
+
// circular — radius and cy both scale with n to prevent overlap and top-clip
|
|
3594
|
+
const n = roles.length, cx = W / 2;
|
|
3595
|
+
const minRad = (2 * R + 6) * n / (2 * Math.PI);
|
|
3596
|
+
const rad = Math.max(minRad, Math.min((W - PAD_X * 2) / 2, 130) * 0.82);
|
|
3597
|
+
const cy = Math.max(170, rad + PAD_Y); // shift center down so top nodes stay in viewBox
|
|
3598
|
+
roles.forEach((r, i) => {
|
|
3599
|
+
const a = (2 * Math.PI * i / n) - Math.PI / 2;
|
|
3600
|
+
pos[r.id] = { x: cx + rad * Math.cos(a), y: cy + rad * Math.sin(a) };
|
|
3601
|
+
});
|
|
3602
|
+
_layoutH = Math.round(cy + rad + PAD_Y);
|
|
3603
|
+
} else if (topo === 'ring' || topo === 'pipeline') {
|
|
3604
|
+
// linear sequence
|
|
3605
|
+
const usableW = W - PAD_X * 2;
|
|
3606
|
+
roles.forEach((r, i) => {
|
|
3607
|
+
const x = roles.length === 1 ? W / 2 : PAD_X + (usableW / (roles.length - 1)) * i;
|
|
3608
|
+
pos[r.id] = { x, y: 90 };
|
|
3609
|
+
});
|
|
3610
|
+
_layoutH = 180;
|
|
3611
|
+
} else if (Array.isArray(d.communication) && d.communication.length > 0) {
|
|
3612
|
+
// BFS multi-level layout — only traverse forward (non-report) edges
|
|
3613
|
+
const fwdEdges = comms.filter(e => e.type !== 'report');
|
|
3614
|
+
if (!fwdEdges.length) {
|
|
3615
|
+
// all-report graph: render as hub-and-spoke
|
|
3616
|
+
_hubAndSpoke();
|
|
3617
|
+
} else {
|
|
3618
|
+
const depth = {};
|
|
3619
|
+
leaders.forEach(r => { depth[r.id] = 0; });
|
|
3620
|
+
const queue = [...leaders.map(r => r.id)];
|
|
3621
|
+
while (queue.length) {
|
|
3622
|
+
const cur = queue.shift();
|
|
3623
|
+
fwdEdges.forEach(e => {
|
|
3624
|
+
// fix: skip ghost ids not present in roles
|
|
3625
|
+
if (e.from === cur && roleIds.has(e.to) && depth[e.to] === undefined) {
|
|
3626
|
+
depth[e.to] = depth[cur] + 1;
|
|
3627
|
+
queue.push(e.to);
|
|
3628
|
+
}
|
|
3629
|
+
});
|
|
3630
|
+
}
|
|
3631
|
+
roles.forEach(r => { if (depth[r.id] === undefined) depth[r.id] = 1; });
|
|
3632
|
+
// normalize: shift all depths so the shallowest layer is 0 — avoids blank top band when leaders=[]
|
|
3633
|
+
const minDepth = Math.min(...roles.map(r => depth[r.id]));
|
|
3634
|
+
if (minDepth > 0) roles.forEach(r => { depth[r.id] -= minDepth; });
|
|
3635
|
+
const maxDepth = Math.max(...roles.map(r => depth[r.id]));
|
|
3636
|
+
const layerOf = {};
|
|
3637
|
+
for (let d2 = 0; d2 <= maxDepth; d2++) layerOf[d2] = [];
|
|
3638
|
+
roles.forEach(r => layerOf[depth[r.id]].push(r));
|
|
3639
|
+
|
|
3640
|
+
const LAYER_H = 110;
|
|
3641
|
+
_layoutH = PAD_Y + maxDepth * LAYER_H + R + 24; // bottom row center + radius + margin
|
|
3642
|
+
|
|
3643
|
+
for (let d2 = 0; d2 <= maxDepth; d2++) {
|
|
3644
|
+
const layer = layerOf[d2];
|
|
3645
|
+
const usableW = W - PAD_X * 2;
|
|
3646
|
+
layer.forEach((r, i) => {
|
|
3647
|
+
const x = layer.length === 1 ? W / 2 : PAD_X + (usableW / (layer.length - 1)) * i;
|
|
3648
|
+
pos[r.id] = { x, y: PAD_Y + d2 * LAYER_H };
|
|
3649
|
+
});
|
|
3650
|
+
}
|
|
3651
|
+
}
|
|
3652
|
+
} else {
|
|
3653
|
+
_hubAndSpoke();
|
|
3654
|
+
}
|
|
3655
|
+
|
|
3656
|
+
const H = _layoutH || (hasSubs ? 320 : 180);
|
|
3657
|
+
|
|
3658
|
+
// ── build edge SVG strings with offset parallel arrows ─
|
|
3659
|
+
let edgesHTML = '', particlesHTML = '';
|
|
3660
|
+
const OFFSET = 8; // perpendicular offset px between cmd/report arrows
|
|
3661
|
+
|
|
3662
|
+
comms.forEach((edge, ei) => {
|
|
3663
|
+
if (edge.from === edge.to) return; // skip self-loops — zero-length path parks a static particle
|
|
3664
|
+
const fp = pos[edge.from], tp = pos[edge.to];
|
|
3665
|
+
if (!fp || !tp) return;
|
|
3666
|
+
const dx = tp.x - fp.x, dy = tp.y - fp.y;
|
|
3667
|
+
const len = Math.sqrt(dx * dx + dy * dy) || 1;
|
|
3668
|
+
// Perpendicular unit vector — derived from canonical direction to stay
|
|
3669
|
+
// consistent for both halves of an anti-parallel pair (A→B and B→A),
|
|
3670
|
+
// so command and report arrows always offset to opposite sides.
|
|
3671
|
+
const canonDir = String(edge.from) < String(edge.to) ? 1 : -1;
|
|
3672
|
+
const px = (-dy / len) * canonDir, py = (dx / len) * canonDir;
|
|
3673
|
+
const edgeSide = edge.type === 'report' ? -1 : 1;
|
|
3674
|
+
const ox = px * OFFSET * edgeSide, oy = py * OFFSET * edgeSide;
|
|
3675
|
+
// Trim from circle edges
|
|
3676
|
+
const trimS = R + 3, trimE = R + 8;
|
|
3677
|
+
const x1 = (fp.x + ox + dx / len * trimS).toFixed(1);
|
|
3678
|
+
const y1 = (fp.y + oy + dy / len * trimS).toFixed(1);
|
|
3679
|
+
const x2 = (tp.x + ox - dx / len * trimE).toFixed(1);
|
|
3680
|
+
const y2 = (tp.y + oy - dy / len * trimE).toFixed(1);
|
|
3681
|
+
const pid = `v2ep${ei}`;
|
|
3682
|
+
|
|
3683
|
+
let stroke, dash, marker, opacity, sw;
|
|
3684
|
+
if (edge.type === 'command') {
|
|
3685
|
+
stroke='oklch(72% 0.18 75)'; dash=''; marker='v2ac'; opacity=0.7; sw=1.8;
|
|
3686
|
+
} else if (edge.type === 'report') {
|
|
3687
|
+
stroke='oklch(65% 0.12 240)'; dash='5 4'; marker='v2ar'; opacity=0.55; sw=1.2;
|
|
3688
|
+
} else {
|
|
3689
|
+
stroke='oklch(65% 0.15 150)'; dash=''; marker='v2ah'; opacity=0.6; sw=1.5;
|
|
3690
|
+
}
|
|
3691
|
+
edgesHTML += `<path id="${pid}" class="v2oc-edge" d="M${x1},${y1} L${x2},${y2}" stroke="${stroke}" stroke-opacity="${opacity}" stroke-width="${sw}" ${dash?`stroke-dasharray="${dash}"`:''} fill="none" marker-end="url(#${marker})"/>`;
|
|
3692
|
+
|
|
3693
|
+
// Animated flow particle on command edges
|
|
3694
|
+
if (edge.type === 'command') {
|
|
3695
|
+
const delay = (ei * 0.4).toFixed(2);
|
|
3696
|
+
particlesHTML += `<circle class="v2oc-particle" r="3.5" fill="${stroke}" opacity="0.7">
|
|
3697
|
+
<animateMotion dur="2s" repeatCount="indefinite" begin="${delay}s"><mpath href="#${pid}" xlink:href="#${pid}"/></animateMotion>
|
|
3698
|
+
</circle>`;
|
|
3699
|
+
}
|
|
3700
|
+
});
|
|
3701
|
+
|
|
3702
|
+
// ── build node SVG strings ─────────────────────────────
|
|
3703
|
+
// Pre-build per-node clipPath ids for avatar images
|
|
3704
|
+
const nodeClipIds = roles.map((r, i) => `v2ac-${_v2SelOrg.replace(/\W/g,'')}-${i}`);
|
|
3705
|
+
const avatarClipDefs = roles.map((r, i) =>
|
|
3706
|
+
`<clipPath id="${nodeClipIds[i]}"><circle r="${Math.round(R * 0.52)}"/></clipPath>`
|
|
3707
|
+
).join('');
|
|
3708
|
+
|
|
3709
|
+
let nodesHTML = '';
|
|
3710
|
+
roles.forEach((role, i) => {
|
|
3711
|
+
const p = pos[role.id];
|
|
3712
|
+
if (!p) return;
|
|
3713
|
+
const leader = _v2OrgIsLeader(role);
|
|
3714
|
+
const color = colorOf(role, i);
|
|
3715
|
+
const displayName = role.name || role.title || role.id;
|
|
3716
|
+
const subType = role.type || role.agent_type || '';
|
|
3717
|
+
const MAX_LBL = Math.floor(R / 4.2);
|
|
3718
|
+
const nameText = displayName.length > MAX_LBL ? displayName.slice(0, MAX_LBL - 1) + '…' : displayName;
|
|
3719
|
+
const subTypeText = subType.length > MAX_LBL ? subType.slice(0, MAX_LBL - 1) + '…' : subType;
|
|
3720
|
+
const outerRing = leader ? `<circle r="${R + 9}" fill="none" stroke="${color}" stroke-width="0.6" opacity="0.18"/>` : '';
|
|
3721
|
+
const nameY = subType ? (R * 0.55 + 2).toFixed(0) : (R * 0.55 + 8).toFixed(0);
|
|
3722
|
+
const avR = Math.round(R * 0.52);
|
|
3723
|
+
const avatarSrc = v2RoleAvatar(role);
|
|
3724
|
+
nodesHTML += `<g class="v2oc-node" transform="translate(${p.x.toFixed(1)},${p.y.toFixed(1)})">
|
|
3725
|
+
<title>${esc(displayName)}${subType ? ` · ${esc(subType)}` : ''}</title>
|
|
3726
|
+
${outerRing}
|
|
3727
|
+
<circle r="${R}" fill="oklch(12% 0.008 55)" stroke="${color}" stroke-width="${leader ? 2.5 : 1.8}" filter="url(#v2glow)"/>
|
|
3728
|
+
<image href="${avatarSrc}" x="${-avR}" y="${-R + 2}" width="${avR * 2}" height="${avR * 2}" clip-path="url(#${nodeClipIds[i]})" preserveAspectRatio="xMidYMid meet" onerror="this.setAttribute('href','data/avatars/coder.svg')"/>
|
|
3729
|
+
<text text-anchor="middle" y="${nameY}" font-size="9" font-weight="${leader?'600':'500'}" font-family="'Inter',system-ui,sans-serif" fill="${color}">${esc(nameText)}</text>
|
|
3730
|
+
${subTypeText ? `<text text-anchor="middle" y="${+nameY + 11}" font-size="7.5" font-family="'Inter',system-ui,sans-serif" fill="oklch(48% 0.005 75)">${esc(subTypeText)}</text>` : ''}
|
|
3731
|
+
</g>`;
|
|
3732
|
+
});
|
|
3733
|
+
|
|
3734
|
+
// ── assemble HTML ─────────────────────────────────────
|
|
3735
|
+
pane.innerHTML = `
|
|
3736
|
+
<div class="org-meta-strip">
|
|
3737
|
+
<div class="oms-cell"><div class="oms-lbl">Goal</div><div class="oms-val" style="color:var(--text-mid);font-size:11px;line-height:1.4">${esc(goalText)}</div></div>
|
|
3738
|
+
<div class="oms-cell"><div class="oms-lbl">Topology</div><div class="oms-val">${esc(topo)}</div></div>
|
|
3739
|
+
<div class="oms-cell"><div class="oms-lbl">Created</div><div class="oms-val" style="color:var(--text-mid)">${esc(created)}</div></div>
|
|
3740
|
+
</div>
|
|
3741
|
+
<div class="org-chart-wrap">
|
|
3742
|
+
<svg class="org-chart-svg" id="v2-org-svg" viewBox="0 0 ${W} ${H}" style="min-height:${Math.round(H * 0.5)}px">
|
|
3743
|
+
<defs>
|
|
3744
|
+
<marker id="v2ac" markerWidth="7" markerHeight="7" refX="6" refY="3.5" orient="auto"><path d="M0,0.5 L0,6.5 L6,3.5 z" fill="oklch(72% 0.18 75 / 0.8)"/></marker>
|
|
3745
|
+
<marker id="v2ar" markerWidth="7" markerHeight="7" refX="6" refY="3.5" orient="auto"><path d="M0,0.5 L0,6.5 L6,3.5 z" fill="oklch(65% 0.12 240 / 0.7)"/></marker>
|
|
3746
|
+
<marker id="v2ah" markerWidth="7" markerHeight="7" refX="6" refY="3.5" orient="auto"><path d="M0,0.5 L0,6.5 L6,3.5 z" fill="oklch(65% 0.15 150 / 0.75)"/></marker>
|
|
3747
|
+
<filter id="v2glow" x="-50%" y="-50%" width="200%" height="200%">
|
|
3748
|
+
<feGaussianBlur in="SourceGraphic" stdDeviation="3" result="b"/>
|
|
3749
|
+
<feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
|
|
3750
|
+
</filter>
|
|
3751
|
+
${avatarClipDefs}
|
|
3752
|
+
</defs>
|
|
3753
|
+
<rect width="${W}" height="${H}" fill="oklch(12% 0.008 55)"/>
|
|
3754
|
+
<g id="v2oc-edges">${edgesHTML}</g>
|
|
3755
|
+
<g id="v2oc-nodes">${nodesHTML}</g>
|
|
3756
|
+
<g id="v2oc-particles" opacity="${isRunning ? 1 : 0}">${particlesHTML}</g>
|
|
3757
|
+
</svg>
|
|
3758
|
+
</div>
|
|
3759
|
+
<div class="org-chart-legend" style="margin-top:10px">
|
|
3760
|
+
<div class="ocl-item"><span style="display:inline-block;width:20px;height:2px;background:oklch(72% 0.18 75);vertical-align:middle;margin-right:3px;border-radius:1px"></span>Command</div>
|
|
3761
|
+
<div class="ocl-item"><span style="display:inline-block;width:20px;height:1px;background:oklch(65% 0.12 240);vertical-align:middle;margin-right:3px;border-top:1px dashed oklch(65% 0.12 240)"></span>Report</div>
|
|
3762
|
+
<div class="ocl-item"><span style="display:inline-block;width:20px;height:2px;background:oklch(65% 0.15 150);vertical-align:middle;margin-right:3px;border-radius:1px"></span>Handoff</div>
|
|
3763
|
+
</div>`;
|
|
3764
|
+
|
|
3765
|
+
// ── GSAP entrance animation ───────────────────────────
|
|
3766
|
+
if (typeof gsap !== 'undefined') {
|
|
3767
|
+
const nodes = pane.querySelectorAll('.v2oc-node');
|
|
3768
|
+
const edges = pane.querySelectorAll('.v2oc-edge');
|
|
3769
|
+
// Nodes: scale + fade in, staggered
|
|
3770
|
+
gsap.fromTo(nodes,
|
|
3771
|
+
{ scale: 0, autoAlpha: 0 },
|
|
3772
|
+
{ scale: 1, autoAlpha: 1, duration: 0.5, ease: 'back.out(1.7)', stagger: 0.08 }
|
|
3773
|
+
);
|
|
3774
|
+
// Edges: draw along path after nodes appear
|
|
3775
|
+
edges.forEach((path, i) => {
|
|
3776
|
+
const pathLen = path.getTotalLength ? path.getTotalLength() : 200;
|
|
3777
|
+
gsap.fromTo(path,
|
|
3778
|
+
{ strokeDasharray: pathLen, strokeDashoffset: pathLen, autoAlpha: 0 },
|
|
3779
|
+
{ strokeDashoffset: 0, autoAlpha: 1, duration: 0.65, ease: 'power2.out', delay: 0.3 + i * 0.04 }
|
|
3780
|
+
);
|
|
3781
|
+
});
|
|
3782
|
+
} else {
|
|
3783
|
+
// CSS fallback: simple fade-in stagger
|
|
3784
|
+
pane.querySelectorAll('.v2oc-node').forEach((el, i) => {
|
|
3785
|
+
el.style.animation = `v2nodeIn 0.45s cubic-bezier(0.34,1.56,0.64,1) ${(i * 0.08).toFixed(2)}s forwards`;
|
|
3786
|
+
el.style.opacity = '0';
|
|
3787
|
+
});
|
|
3788
|
+
}
|
|
3789
|
+
}
|
|
3790
|
+
|
|
3791
|
+
|
|
3792
|
+
// ── avatar lookup ────────────────────────────
|
|
3793
|
+
const _v2AvatarKnown = new Set([
|
|
3794
|
+
'coder','senior-developer','reviewer','tester','planner','researcher',
|
|
3795
|
+
'security-architect','security-auditor','threat-detection','input-validator',
|
|
3796
|
+
'path-validator','safe-executor','hierarchical-coord','mesh-coordinator',
|
|
3797
|
+
'adaptive-coordinator','collective-coord','queen-coordinator','worker-specialist',
|
|
3798
|
+
'byzantine-coord','raft-manager','gossip-coordinator','crdt-synchronizer',
|
|
3799
|
+
'quorum-manager','consensus-coordinator','perf-analyzer','benchmarker',
|
|
3800
|
+
'task-orchestrator','memory-coordinator','load-balancer','resource-allocator',
|
|
3801
|
+
'pr-manager','code-review-swarm','issue-tracker','release-manager','repo-architect',
|
|
3802
|
+
'workflow-automation','sparc-coord','sparc-coder','specification','pseudocode',
|
|
3803
|
+
'architecture','refinement','backend-dev','frontend-developer','mobile-dev',
|
|
3804
|
+
'ml-developer','cicd-engineer','system-architect','ai-engineer','model-qa',
|
|
3805
|
+
'data-engineer','analytics-reporter','experiment-tracker','data-consolidator',
|
|
3806
|
+
'devops-automator','sre','incident-commander','infrastructure','database-optimizer',
|
|
3807
|
+
'cloud-architect','prosecutor','defender','judge','case-analyst','trial-director',
|
|
3808
|
+
'legal-compliance','technical-writer','content-creator','seo-specialist',
|
|
3809
|
+
'social-media','email-marketing','ai-citation','product-manager','sprint-prioritizer',
|
|
3810
|
+
'launch-strategist','pricing-strategist','feedback-synthesizer','cro-specialist',
|
|
3811
|
+
'sales-engineer','deal-strategist','account-strategist','outbound-strategist',
|
|
3812
|
+
'pipeline-analyst','sales-coach','support-responder','discovery-coach',
|
|
3813
|
+
'proposal-strategist','game-designer','narrative-designer','level-designer',
|
|
3814
|
+
'game-audio-engineer','technical-artist','unity-architect','blockchain-auditor',
|
|
3815
|
+
'solidity-engineer','zk-steward','studio-producer','project-shepherd',
|
|
3816
|
+
'senior-pm','studio-operations','workflow-architect','adaptive-coordinator2',
|
|
3817
|
+
'api-tester','evidence-collector','reality-checker','production-validator',
|
|
3818
|
+
'finance-tracker','accounts-payable','recruitment','visionos-engineer',
|
|
3819
|
+
'embedded-firmware','ios-developer','mobile-app-builder','mcp-builder',
|
|
3820
|
+
'automation-governance','payment-agent','compliance-auditor','trend-researcher',
|
|
3821
|
+
'scout-explorer'
|
|
3822
|
+
]);
|
|
3823
|
+
const _v2TypeFallback = {
|
|
3824
|
+
planner:'planner', coordinator:'hierarchical-coord', researcher:'researcher',
|
|
3825
|
+
analyst:'researcher', reviewer:'reviewer', coder:'coder', tester:'tester',
|
|
3826
|
+
architect:'system-architect', engineer:'backend-dev', developer:'coder',
|
|
3827
|
+
manager:'product-manager', security:'security-architect',
|
|
3828
|
+
reporter:'analytics-reporter', strategist:'product-manager',
|
|
3829
|
+
designer:'game-designer', writer:'technical-writer', auditor:'security-auditor',
|
|
3830
|
+
};
|
|
3831
|
+
function v2RoleAvatar(role) {
|
|
3832
|
+
const id = ((role && role.id) || '').toLowerCase().replace(/\s+/g,'-');
|
|
3833
|
+
const type = ((role && (role.type || role.agent_type)) || '').toLowerCase().replace(/\s+/g,'-');
|
|
3834
|
+
if (_v2AvatarKnown.has(id)) return `data/avatars/${id}.svg`;
|
|
3835
|
+
if (_v2AvatarKnown.has(type)) return `data/avatars/${type}.svg`;
|
|
3836
|
+
for (const k of _v2AvatarKnown) {
|
|
3837
|
+
if (id && (id.includes(k) || k.includes(id))) return `data/avatars/${k}.svg`;
|
|
3838
|
+
}
|
|
3839
|
+
for (const [t, av] of Object.entries(_v2TypeFallback)) {
|
|
3840
|
+
if ((type && type.includes(t)) || (id && id.includes(t))) return `data/avatars/${av}.svg`;
|
|
3841
|
+
}
|
|
3842
|
+
return 'data/avatars/coder.svg';
|
|
3843
|
+
}
|
|
3844
|
+
|
|
3845
|
+
// ── org roles ───────────────────────────────
|
|
3846
|
+
function v2RenderOrgRoles() {
|
|
3847
|
+
if (!_v2SelOrg) return;
|
|
3848
|
+
const roles = Array.isArray(_v2OrgData?.roles) ? _v2OrgData.roles : [];
|
|
3849
|
+
const V2_ROLE_COLORS = ['oklch(72% 0.18 75)','oklch(65% 0.12 240)','oklch(65% 0.15 150)','oklch(65% 0.13 290)','oklch(65% 0.14 35)','oklch(62% 0.12 195)'];
|
|
3850
|
+
const pane = document.getElementById('odt-roles');
|
|
3851
|
+
if (!pane) return;
|
|
3852
|
+
if (!roles.length) {
|
|
3853
|
+
pane.innerHTML = '<div class="empty">No roles defined</div>';
|
|
3854
|
+
return;
|
|
3224
3855
|
}
|
|
3856
|
+
// Determine leader: explicit reports_to=undefined + type=planner/coordinator, or first role, or id=boss
|
|
3857
|
+
const leaderIds = new Set(roles.filter(r => r.id === 'boss' || r.type === 'coordinator' || r.type === 'planner').map(r => r.id));
|
|
3858
|
+
if (!leaderIds.size) leaderIds.add(roles[0]?.id);
|
|
3859
|
+
pane.innerHTML = `<div class="roles-v2-grid">${roles.map((r, i) => {
|
|
3860
|
+
const color = V2_ROLE_COLORS[i % V2_ROLE_COLORS.length];
|
|
3861
|
+
const isLeader = leaderIds.has(r.id);
|
|
3862
|
+
const resps = Array.isArray(r.responsibilities) ? r.responsibilities : [];
|
|
3863
|
+
const displayName = r.name || r.title || r.id;
|
|
3864
|
+
const roleType = r.type || r.agent_type || '';
|
|
3865
|
+
const avatarSrc = v2RoleAvatar(r);
|
|
3866
|
+
return `<div class="role-v2" style="border-top:2px solid ${color}">
|
|
3867
|
+
<div class="rv2-head">
|
|
3868
|
+
<img class="rv2-avatar" src="${avatarSrc}" alt="${esc(displayName)}" loading="lazy" onerror="this.src='data/avatars/coder.svg'"/>
|
|
3869
|
+
<div class="rv2-info">
|
|
3870
|
+
<div class="rv2-title">${esc(displayName)}${isLeader ? ' <span style="font-size:10px;color:var(--accent);font-weight:400;opacity:0.8">lead</span>' : ''}</div>
|
|
3871
|
+
${roleType ? `<span class="rv2-agent">${esc(roleType)}</span>` : ''}
|
|
3872
|
+
</div>
|
|
3873
|
+
</div>
|
|
3874
|
+
<div class="rv2-id">${esc(r.id)}</div>
|
|
3875
|
+
${r.reports_to ? `<div class="rv2-reports">reports to <span>${esc(r.reports_to)}</span></div>` : ''}
|
|
3876
|
+
${resps.length ? `<div class="rv2-resps">${resps.map(s=>`<div class="rv2-resp">${esc(s)}</div>`).join('')}</div>` : ''}
|
|
3877
|
+
</div>`;
|
|
3878
|
+
}).join('')}</div>`;
|
|
3879
|
+
}
|
|
3880
|
+
|
|
3881
|
+
// ── org activity ────────────────────────────
|
|
3882
|
+
function v2RenderOrgActivity() {
|
|
3883
|
+
if (!_v2SelOrg) return;
|
|
3884
|
+
const activity = _v2OrgData?._activity || [];
|
|
3885
|
+
const orgEvents = _v2OrgEventLog[_v2SelOrg] || [];
|
|
3886
|
+
const events = [...activity, ...orgEvents].sort((a,b) => (b.ts||0)-(a.ts||0)).slice(0,80);
|
|
3887
|
+
const pane = document.getElementById('odt-activity');
|
|
3888
|
+
if (!pane) return;
|
|
3889
|
+
const fmtOrgEvType = t => {
|
|
3890
|
+
const m={'org:start':'start','org:stop':'stop','org:complete':'done','org:create':'create','org:heartbeat':'hb','org:agent:online':'agent-on','org:comms':'comms'};
|
|
3891
|
+
return m[t]||(t||'').replace(/^org:/,'');
|
|
3892
|
+
};
|
|
3893
|
+
if (!events.length) {
|
|
3894
|
+
pane.innerHTML = '<div class="empty">No activity recorded</div>';
|
|
3895
|
+
return;
|
|
3896
|
+
}
|
|
3897
|
+
pane.innerHTML = `<div class="act-v2-list">${events.map(ev => {
|
|
3898
|
+
const t = ev.ts ? new Date(ev.ts).toLocaleTimeString('en',{hour12:false,hour:'2-digit',minute:'2-digit',second:'2-digit'}) : '';
|
|
3899
|
+
const detail = ev.role||ev.msg||ev.agent||'';
|
|
3900
|
+
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>`;
|
|
3901
|
+
}).join('')}</div>`;
|
|
3902
|
+
}
|
|
3903
|
+
|
|
3904
|
+
// ── org health ──────────────────────────────
|
|
3905
|
+
function v2RenderOrgHealth() {
|
|
3906
|
+
if (!_v2SelOrg) return;
|
|
3907
|
+
const listOrg = _v2Orgs.find(o => o.name === _v2SelOrg) || {};
|
|
3908
|
+
const health = _v2OrgData?._health;
|
|
3909
|
+
const roles = Array.isArray(_v2OrgData?.roles) ? _v2OrgData.roles.length : 0;
|
|
3910
|
+
const pane = document.getElementById('odt-health');
|
|
3911
|
+
if (!pane) return;
|
|
3912
|
+
pane.innerHTML = `
|
|
3913
|
+
<div class="health-v2-grid">
|
|
3914
|
+
<div class="hv2-cell"><div class="hv2-lbl">Status</div><div class="hv2-val ${(_v2OrgData?.running??listOrg.running)?'green':''}">${(_v2OrgData?.running??listOrg.running)?'LIVE':'IDLE'}</div></div>
|
|
3915
|
+
<div class="hv2-cell"><div class="hv2-lbl">Roles</div><div class="hv2-val">${roles}</div></div>
|
|
3916
|
+
<div class="hv2-cell"><div class="hv2-lbl">Topology</div><div class="hv2-val" style="font-size:14px">${esc((_v2OrgData?.topology||'—').toUpperCase())}</div></div>
|
|
3917
|
+
${health?.agents_active!=null?`<div class="hv2-cell"><div class="hv2-lbl">Agents</div><div class="hv2-val ${health.agents_active>0?'green':''}">${health.agents_active}</div></div>`:''}
|
|
3918
|
+
${health?.tasks_pending!=null?`<div class="hv2-cell"><div class="hv2-lbl">Tasks</div><div class="hv2-val ${health.tasks_pending>5?'amber':''}">${health.tasks_pending}</div></div>`:''}
|
|
3919
|
+
</div>
|
|
3920
|
+
${health?.errors?.length?`<div style="margin-top:12px"><div class="m-group-title">Errors</div>${health.errors.slice(0,5).map(e=>`<div style="font-size:11px;color:var(--red);padding:4px 0;border-bottom:1px solid var(--border)">${esc(e)}</div>`).join('')}</div>`:`<div style="margin-top:10px;font-size:12px;color:var(--text-lo)">${listOrg.running?'Running — no errors detected':'Org is idle'}</div>`}
|
|
3921
|
+
`;
|
|
3225
3922
|
}
|
|
3226
3923
|
|
|
3924
|
+
// stop org
|
|
3925
|
+
window.v2StopOrg = async function() {
|
|
3926
|
+
if (!_v2SelOrg) return;
|
|
3927
|
+
const stopped = _v2SelOrg;
|
|
3928
|
+
try { await fetch(`/api/orgs/${encodeURIComponent(stopped)}/stop`, {method:'POST'}); } catch(_) {}
|
|
3929
|
+
setTimeout(async () => { await renderOrgs(); if (_v2SelOrg === stopped) v2SelectOrg(stopped); }, 600);
|
|
3930
|
+
};
|
|
3931
|
+
|
|
3932
|
+
// live SSE for org events
|
|
3933
|
+
(function v2OrgSSE() {
|
|
3934
|
+
let src;
|
|
3935
|
+
function connect() {
|
|
3936
|
+
if (src) src.close();
|
|
3937
|
+
src = new EventSource('/api/events');
|
|
3938
|
+
src.onmessage = e => {
|
|
3939
|
+
try {
|
|
3940
|
+
const ev = JSON.parse(e.data);
|
|
3941
|
+
if (!ev?.org || !ev?.type?.startsWith('org:')) return;
|
|
3942
|
+
const n = ev.org;
|
|
3943
|
+
const evDir = ev.dir || '';
|
|
3944
|
+
if (!evDir || (DIR && evDir !== DIR)) return;
|
|
3945
|
+
if (!_v2OrgEventLog[n]) _v2OrgEventLog[n] = [];
|
|
3946
|
+
_v2OrgEventLog[n].push(ev);
|
|
3947
|
+
if (_v2OrgEventLog[n].length > 50) _v2OrgEventLog[n].shift();
|
|
3948
|
+
if (n === _v2SelOrg && _v2OrgTab === 'activity') v2RenderOrgActivity();
|
|
3949
|
+
if (ev.type === 'org:start' || ev.type === 'org:stop') {
|
|
3950
|
+
setTimeout(async () => {
|
|
3951
|
+
await renderOrgs();
|
|
3952
|
+
if (_v2SelOrg === n) {
|
|
3953
|
+
const listOrg = _v2Orgs.find(o => o.name === _v2SelOrg) || {};
|
|
3954
|
+
if (_v2OrgData) _v2OrgData.running = listOrg.running;
|
|
3955
|
+
v2UpdateOrgHeader(listOrg, _v2OrgData);
|
|
3956
|
+
if (_v2OrgTab === 'health') v2RenderOrgHealth();
|
|
3957
|
+
}
|
|
3958
|
+
}, 400);
|
|
3959
|
+
}
|
|
3960
|
+
} catch(_) {}
|
|
3961
|
+
};
|
|
3962
|
+
src.onerror = () => setTimeout(connect, 5000);
|
|
3963
|
+
}
|
|
3964
|
+
connect();
|
|
3965
|
+
})();
|
|
3966
|
+
|
|
3227
3967
|
// ── density toggle ─────────────────────────────────────────
|
|
3228
3968
|
let compactMode = false;
|
|
3229
3969
|
function toggleDensity() {
|