@monoes/monomindcli 1.9.16 → 1.10.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/commands/mastermind/_repeat.md +182 -39
- package/.claude/commands/mastermind/architect.md +17 -11
- package/.claude/commands/mastermind/brain.md +4 -0
- package/.claude/commands/mastermind/build.md +4 -0
- package/.claude/commands/mastermind/content.md +4 -0
- package/.claude/commands/mastermind/createorg.md +5 -3
- package/.claude/commands/mastermind/finance.md +4 -0
- package/.claude/commands/mastermind/idea.md +4 -0
- package/.claude/commands/mastermind/marketing.md +4 -0
- package/.claude/commands/mastermind/master.md +100 -46
- package/.claude/commands/mastermind/ops.md +4 -0
- package/.claude/commands/mastermind/release.md +4 -0
- package/.claude/commands/mastermind/research.md +4 -0
- package/.claude/commands/mastermind/review.md +4 -0
- package/.claude/commands/mastermind/runorg.md +5 -3
- package/.claude/commands/mastermind/sales.md +4 -0
- package/.claude/commands/mastermind/techport.md +9 -0
- package/.claude/commands/monomind/do.md +5 -1
- package/.claude/commands/monomind/idea.md +5 -1
- package/.claude/commands/monomind/improve.md +5 -1
- package/.claude/commands/monomind/repeat.md +85 -29
- package/.claude/commands/monomind/review.md +6 -2
- package/.claude/commands/monomind/understand.md +10 -8
- package/.claude/helpers/extras-registry.json +235 -235
- package/.claude/helpers/graphify-freshen.cjs +13 -1
- package/.claude/helpers/hook-handler.cjs +1 -1
- package/.claude/helpers/router.cjs +4 -1
- package/.claude/skills/mastermind/_protocol.md +37 -21
- package/.claude/skills/mastermind/access.md +236 -0
- package/.claude/skills/mastermind/activity.md +191 -0
- package/.claude/skills/mastermind/adapter-manager.md +259 -0
- package/.claude/skills/mastermind/adapters.md +204 -0
- package/.claude/skills/mastermind/agent-detail.md +242 -0
- package/.claude/skills/mastermind/agents.md +178 -0
- package/.claude/skills/mastermind/approval-detail.md +259 -0
- package/.claude/skills/mastermind/approve.md +181 -0
- package/.claude/skills/mastermind/architect.md +24 -8
- package/.claude/skills/mastermind/backup.md +197 -0
- package/.claude/skills/mastermind/bootstrap.md +190 -0
- package/.claude/skills/mastermind/budgets.md +237 -0
- package/.claude/skills/mastermind/companies.md +256 -0
- package/.claude/skills/mastermind/costs.md +151 -0
- package/.claude/skills/mastermind/createorg.md +23 -5
- package/.claude/skills/mastermind/diagnose.md +249 -0
- package/.claude/skills/mastermind/env.md +198 -0
- package/.claude/skills/mastermind/environments.md +250 -0
- package/.claude/skills/mastermind/export.md +324 -0
- package/.claude/skills/mastermind/goal-detail.md +255 -0
- package/.claude/skills/mastermind/goals.md +149 -0
- package/.claude/skills/mastermind/heartbeat.md +164 -0
- package/.claude/skills/mastermind/idea.md +318 -186
- package/.claude/skills/mastermind/import.md +281 -0
- package/.claude/skills/mastermind/inbox.md +214 -0
- package/.claude/skills/mastermind/instance-settings.md +315 -0
- package/.claude/skills/mastermind/instance.md +231 -0
- package/.claude/skills/mastermind/invite-landing.md +227 -0
- package/.claude/skills/mastermind/invites.md +254 -0
- package/.claude/skills/mastermind/issue-detail.md +291 -0
- package/.claude/skills/mastermind/issues.md +235 -0
- package/.claude/skills/mastermind/join-queue.md +170 -0
- package/.claude/skills/mastermind/liveness.md +392 -0
- package/.claude/skills/mastermind/memory.md +321 -0
- package/.claude/skills/mastermind/my-issues.md +146 -0
- package/.claude/skills/mastermind/new-agent.md +241 -0
- package/.claude/skills/mastermind/org-chart.md +207 -0
- package/.claude/skills/mastermind/org-settings.md +217 -0
- package/.claude/skills/mastermind/plan-to-tasks.md +136 -0
- package/.claude/skills/mastermind/plugin-manager.md +241 -0
- package/.claude/skills/mastermind/plugin-settings.md +273 -0
- package/.claude/skills/mastermind/plugins.md +190 -0
- package/.claude/skills/mastermind/profile.md +187 -0
- package/.claude/skills/mastermind/project-detail.md +249 -0
- package/.claude/skills/mastermind/project-workspace.md +244 -0
- package/.claude/skills/mastermind/projects.md +164 -0
- package/.claude/skills/mastermind/routine-detail.md +253 -0
- package/.claude/skills/mastermind/routines.md +202 -0
- package/.claude/skills/mastermind/runorg.md +74 -9
- package/.claude/skills/mastermind/search.md +186 -0
- package/.claude/skills/mastermind/secrets.md +199 -0
- package/.claude/skills/mastermind/skills.md +156 -0
- package/.claude/skills/mastermind/tasks.md +149 -0
- package/.claude/skills/mastermind/techport.md +5 -5
- package/.claude/skills/mastermind/threads.md +259 -0
- package/.claude/skills/mastermind/tree-control.md +250 -0
- package/.claude/skills/mastermind/wiki.md +314 -0
- package/.claude/skills/mastermind/workspace-detail.md +317 -0
- package/.claude/skills/mastermind/workspaces.md +261 -0
- package/.claude/skills/mastermind/worktree.md +187 -0
- package/dist/src/init/executor.js +8 -8
- package/dist/src/init/executor.js.map +1 -1
- package/dist/src/init/statusline-generator.d.ts.map +1 -1
- package/dist/src/init/statusline-generator.js +12 -0
- package/dist/src/init/statusline-generator.js.map +1 -1
- package/dist/src/ui/.monomind/data/ranked-context.json +1 -1
- package/dist/src/ui/.monomind/loops/mastermind-review-1778664132789.json +16 -0
- package/dist/src/ui/.monomind/sessions/current.json +5 -5
- package/dist/src/ui/.monomind/sessions/session-1776778451399.json +15 -0
- package/dist/src/ui/dashboard.html +3030 -181
- package/dist/src/ui/data/mastermind-events.jsonl +8 -0
- package/dist/src/ui/data/mastermind-sessions.json +1 -0
- package/dist/src/ui/server.mjs +738 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/.claude/skills/.monomind/data/ranked-context.json +0 -5
- package/.claude/skills/.monomind/sessions/current.json +0 -13
- package/.claude/skills/.monomind/sessions/session-1777829336455.json +0 -15
- package/.claude/skills/.monomind/sessions/session-1777831614725.json +0 -15
- package/.claude/skills/.monomind/sessions/session-1777832095857.json +0 -15
- package/.claude/skills/.monomind/sessions/session-1777839814183.json +0 -15
- package/.claude/skills/.monomind/sessions/session-1777841847131.json +0 -15
- package/.claude/skills/.monomind/sessions/session-1777843309463.json +0 -15
- package/.claude/skills/.monomind/sessions/session-1777880867159.json +0 -15
- package/.claude/skills/.monomind/sessions/session-1777881884593.json +0 -15
- package/.claude/skills/.monomind/sessions/session-1777884090471.json +0 -15
- package/.claude/skills/.monomind/sessions/session-1777884808221.json +0 -15
- package/.claude/skills/.monomind/sessions/session-1777885672155.json +0 -15
- package/.claude/skills/.monomind/sessions/session-1777886852818.json +0 -15
- package/.claude/skills/.monomind/sessions/session-1777896532690.json +0 -15
|
@@ -1243,6 +1243,405 @@
|
|
|
1243
1243
|
flex: 1; border: none; width: 100%; display: block;
|
|
1244
1244
|
background: #07071a;
|
|
1245
1245
|
}
|
|
1246
|
+
#mm-hub { flex: 1; display: flex; flex-direction: column; overflow: hidden; }
|
|
1247
|
+
#mm-tabs {
|
|
1248
|
+
display: flex; gap: 0; border-bottom: 1px solid rgba(200,120,255,0.18);
|
|
1249
|
+
flex-shrink: 0; overflow-x: auto;
|
|
1250
|
+
}
|
|
1251
|
+
.mm-tab {
|
|
1252
|
+
background: none; border: none; border-bottom: 2px solid transparent;
|
|
1253
|
+
color: rgba(150,100,200,0.55); font-family: 'Azeret Mono', monospace;
|
|
1254
|
+
font-size: 9px; letter-spacing: 0.1em; padding: 8px 16px;
|
|
1255
|
+
cursor: pointer; transition: all 0.15s; white-space: nowrap;
|
|
1256
|
+
}
|
|
1257
|
+
.mm-tab:hover { color: rgba(210,140,255,0.8); background: rgba(200,120,255,0.06); }
|
|
1258
|
+
.mm-tab.active { color: rgba(210,140,255,1); border-bottom-color: rgba(200,120,255,0.7); }
|
|
1259
|
+
#mm-body { flex: 1; overflow-y: auto; padding: 16px 20px; }
|
|
1260
|
+
.mm-pane { display: none; }
|
|
1261
|
+
.mm-pane.active { display: block; }
|
|
1262
|
+
.mm-section-title {
|
|
1263
|
+
font-family: 'Azeret Mono', monospace; font-size: 9px;
|
|
1264
|
+
color: rgba(200,120,255,0.5); letter-spacing: 0.14em; text-transform: uppercase;
|
|
1265
|
+
margin-bottom: 10px; padding-bottom: 4px;
|
|
1266
|
+
border-bottom: 1px solid rgba(200,120,255,0.12);
|
|
1267
|
+
}
|
|
1268
|
+
.mm-skill-grid {
|
|
1269
|
+
display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
|
1270
|
+
gap: 6px; margin-bottom: 16px;
|
|
1271
|
+
}
|
|
1272
|
+
.mm-skill-card {
|
|
1273
|
+
background: rgba(200,120,255,0.05); border: 1px solid rgba(200,120,255,0.15);
|
|
1274
|
+
border-radius: 4px; padding: 8px 10px; font-size: 9px;
|
|
1275
|
+
font-family: 'Azeret Mono', monospace; cursor: default;
|
|
1276
|
+
transition: border-color 0.15s;
|
|
1277
|
+
}
|
|
1278
|
+
.mm-skill-card:hover { border-color: rgba(200,120,255,0.4); }
|
|
1279
|
+
.mm-skill-name { color: rgba(210,140,255,0.9); margin-bottom: 3px; font-size: 10px; }
|
|
1280
|
+
.mm-skill-desc { color: rgba(150,100,200,0.6); font-size: 8px; line-height: 1.4; }
|
|
1281
|
+
.mm-loop-row {
|
|
1282
|
+
display: flex; align-items: center; gap: 12px; padding: 8px 10px;
|
|
1283
|
+
border: 1px solid rgba(200,120,255,0.12); border-radius: 4px;
|
|
1284
|
+
margin-bottom: 6px; background: rgba(200,120,255,0.03);
|
|
1285
|
+
}
|
|
1286
|
+
.mm-org-card {
|
|
1287
|
+
display: flex; align-items: center; justify-content: space-between;
|
|
1288
|
+
padding: 10px 14px; border: 1px solid rgba(200,120,255,0.15); border-radius: 4px;
|
|
1289
|
+
margin-bottom: 6px; background: rgba(200,120,255,0.04); cursor: pointer;
|
|
1290
|
+
transition: border-color 0.15s;
|
|
1291
|
+
}
|
|
1292
|
+
.mm-org-card:hover { border-color: rgba(200,120,255,0.4); background: rgba(200,120,255,0.08); }
|
|
1293
|
+
.mm-org-name { color: rgba(210,140,255,0.9); font-size: 11px; font-family: 'Azeret Mono', monospace; }
|
|
1294
|
+
.mm-org-meta { color: rgba(150,100,200,0.5); font-size: 9px; font-family: 'Azeret Mono', monospace; }
|
|
1295
|
+
.mm-btn {
|
|
1296
|
+
background: none; border: 1px solid rgba(200,120,255,0.4); color: rgba(210,140,255,0.85);
|
|
1297
|
+
font-family: 'Azeret Mono', monospace; font-size: 9px; letter-spacing: 0.08em;
|
|
1298
|
+
padding: 4px 12px; cursor: pointer; border-radius: 3px; transition: background 0.15s;
|
|
1299
|
+
}
|
|
1300
|
+
.mm-btn:hover { background: rgba(200,120,255,0.12); }
|
|
1301
|
+
.mm-metric-grid {
|
|
1302
|
+
display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: 8px; margin-bottom: 16px;
|
|
1303
|
+
}
|
|
1304
|
+
.mm-metric-card {
|
|
1305
|
+
background: rgba(200,120,255,0.05); border: 1px solid rgba(200,120,255,0.15);
|
|
1306
|
+
border-radius: 4px; padding: 12px 14px; text-align: center;
|
|
1307
|
+
}
|
|
1308
|
+
.mm-metric-val { color: rgba(210,140,255,0.95); font-size: 22px; font-family: 'Syne', sans-serif; font-weight: 700; }
|
|
1309
|
+
.mm-metric-label { color: rgba(150,100,200,0.5); font-size: 8px; letter-spacing: 0.1em; margin-top: 2px; }
|
|
1310
|
+
|
|
1311
|
+
/* ─── ORG ROOM OVERLAY ──────────────────────────────────────────── */
|
|
1312
|
+
#orgroom-overlay {
|
|
1313
|
+
position: fixed; inset: 0; z-index: 1003;
|
|
1314
|
+
background: #060810;
|
|
1315
|
+
display: none; flex-direction: column;
|
|
1316
|
+
overflow: hidden;
|
|
1317
|
+
}
|
|
1318
|
+
#orgroom-overlay.open { display: flex; }
|
|
1319
|
+
#orgroom-header {
|
|
1320
|
+
display: flex; align-items: center; gap: 14px;
|
|
1321
|
+
padding: 10px 16px; border-bottom: 1px solid rgba(0,230,180,0.18);
|
|
1322
|
+
flex-shrink: 0; background: rgba(6,8,16,0.97);
|
|
1323
|
+
backdrop-filter: blur(16px); z-index: 10; position: relative;
|
|
1324
|
+
}
|
|
1325
|
+
#orgroom-back {
|
|
1326
|
+
background: none; border: 1px solid rgba(0,200,160,0.4); color: rgba(0,230,180,0.9);
|
|
1327
|
+
font-family: 'Azeret Mono', monospace; font-size: 11px; letter-spacing: 0.08em;
|
|
1328
|
+
padding: 4px 10px; cursor: pointer; border-radius: 3px; transition: background 0.15s;
|
|
1329
|
+
}
|
|
1330
|
+
#orgroom-back:hover { background: rgba(0,200,160,0.1); }
|
|
1331
|
+
#orgroom-title {
|
|
1332
|
+
font-family: 'Syne', sans-serif; font-weight: 700; font-size: 13px;
|
|
1333
|
+
color: rgba(0,230,180,0.9); letter-spacing: 0.14em; text-transform: uppercase;
|
|
1334
|
+
}
|
|
1335
|
+
#orgroom-sub {
|
|
1336
|
+
font-family: 'Azeret Mono', monospace; font-size: 9px;
|
|
1337
|
+
color: rgba(0,160,120,0.6); letter-spacing: 0.12em;
|
|
1338
|
+
}
|
|
1339
|
+
#orgroom-tabs {
|
|
1340
|
+
display: flex; gap: 2px; padding: 8px 16px 0;
|
|
1341
|
+
border-bottom: 1px solid rgba(0,230,180,0.12);
|
|
1342
|
+
flex-shrink: 0;
|
|
1343
|
+
}
|
|
1344
|
+
.orgroom-tab {
|
|
1345
|
+
font-family: 'Azeret Mono', monospace; font-size: 10px; letter-spacing: 0.1em;
|
|
1346
|
+
padding: 5px 14px; cursor: pointer; border: none; background: none;
|
|
1347
|
+
color: rgba(0,200,160,0.5); border-bottom: 2px solid transparent;
|
|
1348
|
+
transition: color 0.15s, border-color 0.15s;
|
|
1349
|
+
}
|
|
1350
|
+
.orgroom-tab:hover { color: rgba(0,230,180,0.8); }
|
|
1351
|
+
.orgroom-tab.active { color: rgba(0,230,180,1); border-bottom-color: rgba(0,230,180,0.8); }
|
|
1352
|
+
#orgroom-body {
|
|
1353
|
+
flex: 1; overflow: auto; padding: 16px;
|
|
1354
|
+
font-family: 'Azeret Mono', monospace; font-size: 11px; color: var(--text);
|
|
1355
|
+
}
|
|
1356
|
+
.orgroom-pane { display: none; }
|
|
1357
|
+
.orgroom-pane.active { display: block; }
|
|
1358
|
+
|
|
1359
|
+
/* ORG CHART canvas area */
|
|
1360
|
+
#orgroom-chart-svg {
|
|
1361
|
+
width: 100%; overflow: auto;
|
|
1362
|
+
background: rgba(0,0,0,0.3); border-radius: 4px; padding: 12px;
|
|
1363
|
+
min-height: 200px;
|
|
1364
|
+
}
|
|
1365
|
+
.oc-card {
|
|
1366
|
+
fill: rgba(0,40,30,0.9); stroke: rgba(0,200,160,0.5); stroke-width: 1;
|
|
1367
|
+
rx: 4; cursor: default;
|
|
1368
|
+
}
|
|
1369
|
+
.oc-card-boss { stroke: rgba(0,230,180,0.9); stroke-width: 2; }
|
|
1370
|
+
.oc-label { font-size: 11px; fill: rgba(0,230,180,0.9); font-family: monospace; }
|
|
1371
|
+
.oc-sub { font-size: 9px; fill: rgba(0,160,120,0.7); font-family: monospace; }
|
|
1372
|
+
.oc-edge { stroke: rgba(0,200,160,0.3); stroke-width: 1; fill: none; }
|
|
1373
|
+
|
|
1374
|
+
/* Heartbeats table */
|
|
1375
|
+
.orgroom-table { width: 100%; border-collapse: collapse; }
|
|
1376
|
+
.orgroom-table th { text-align: left; font-size: 9px; color: var(--dim); letter-spacing: 0.1em;
|
|
1377
|
+
padding: 4px 8px; border-bottom: 1px solid rgba(0,200,160,0.15); }
|
|
1378
|
+
.orgroom-table td { padding: 5px 8px; font-size: 10px; border-bottom: 1px solid rgba(255,255,255,0.04); }
|
|
1379
|
+
.orgroom-status-dot {
|
|
1380
|
+
display: inline-block; width: 7px; height: 7px; border-radius: 50%;
|
|
1381
|
+
background: var(--dim); margin-right: 5px; vertical-align: middle;
|
|
1382
|
+
}
|
|
1383
|
+
.orgroom-status-dot.running { background: var(--green); box-shadow: 0 0 6px var(--green); animation: pulse-dot 1.5s infinite; }
|
|
1384
|
+
.orgroom-status-dot.idle { background: rgba(0,200,160,0.4); }
|
|
1385
|
+
.orgroom-status-dot.paused { background: rgba(255,180,0,0.7); }
|
|
1386
|
+
.orgroom-status-dot.error { background: rgba(255,80,80,0.8); }
|
|
1387
|
+
|
|
1388
|
+
/* Cost bars */
|
|
1389
|
+
.cost-bar-wrap { background: rgba(0,0,0,0.4); border-radius: 2px; height: 6px; width: 100%; margin-top: 3px; }
|
|
1390
|
+
.cost-bar { height: 6px; border-radius: 2px; background: linear-gradient(90deg, rgba(0,200,160,0.6), rgba(0,230,180,0.9)); transition: width 0.3s; }
|
|
1391
|
+
.cost-bar.warn { background: linear-gradient(90deg, rgba(255,180,0,0.6), rgba(255,200,50,0.9)); }
|
|
1392
|
+
.cost-bar.danger { background: linear-gradient(90deg, rgba(255,80,60,0.6), rgba(255,100,80,0.9)); }
|
|
1393
|
+
|
|
1394
|
+
/* Live stream pane */
|
|
1395
|
+
#orgroom-live-stream {
|
|
1396
|
+
font-size: 10px; font-family: 'Azeret Mono', monospace;
|
|
1397
|
+
background: rgba(0,0,0,0.4); border-radius: 4px; padding: 10px;
|
|
1398
|
+
height: calc(100vh - 200px); overflow-y: auto; display: flex; flex-direction: column-reverse;
|
|
1399
|
+
}
|
|
1400
|
+
.live-event-row { padding: 2px 0; border-bottom: 1px solid rgba(255,255,255,0.03); }
|
|
1401
|
+
.live-event-row .live-ts { color: var(--dim); font-size: 9px; margin-right: 8px; }
|
|
1402
|
+
.live-event-row .live-type { color: rgba(0,200,160,0.8); margin-right: 6px; }
|
|
1403
|
+
.live-event-row.type-heartbeat .live-type { color: rgba(0,229,135,0.8); }
|
|
1404
|
+
.live-event-row.type-approval .live-type { color: rgba(255,180,0,0.8); }
|
|
1405
|
+
.live-event-row.type-error .live-type { color: rgba(255,80,80,0.8); }
|
|
1406
|
+
|
|
1407
|
+
/* Skills pane */
|
|
1408
|
+
.skill-card {
|
|
1409
|
+
display: flex; align-items: flex-start; gap: 10px;
|
|
1410
|
+
padding: 8px 10px; border: 1px solid rgba(0,200,160,0.1); border-radius: 4px;
|
|
1411
|
+
margin-bottom: 6px; background: rgba(0,20,15,0.5);
|
|
1412
|
+
}
|
|
1413
|
+
.skill-card-name { color: rgba(0,230,180,0.9); font-size: 10px; font-weight: 600; margin-bottom: 2px; }
|
|
1414
|
+
.skill-card-desc { color: var(--muted); font-size: 9px; }
|
|
1415
|
+
.skill-badge { font-size: 8px; padding: 1px 5px; border-radius: 2px; background: rgba(0,200,160,0.15); color: rgba(0,200,160,0.7); white-space: nowrap; }
|
|
1416
|
+
.skill-search { width: 100%; background: rgba(0,0,0,0.4); border: 1px solid rgba(0,200,160,0.2);
|
|
1417
|
+
color: var(--text); font-family: 'Azeret Mono', monospace; font-size: 10px;
|
|
1418
|
+
padding: 5px 8px; border-radius: 3px; margin-bottom: 10px; outline: none; }
|
|
1419
|
+
|
|
1420
|
+
/* Settings pane */
|
|
1421
|
+
.settings-section { margin-bottom: 20px; }
|
|
1422
|
+
.settings-section-title { font-size: 9px; letter-spacing: 0.12em; color: var(--dim); margin-bottom: 8px; text-transform: uppercase; }
|
|
1423
|
+
.settings-row { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; }
|
|
1424
|
+
.settings-label { font-size: 10px; color: var(--muted); min-width: 140px; }
|
|
1425
|
+
.settings-input {
|
|
1426
|
+
background: rgba(0,0,0,0.4); border: 1px solid rgba(0,200,160,0.2);
|
|
1427
|
+
color: var(--text); font-family: 'Azeret Mono', monospace; font-size: 10px;
|
|
1428
|
+
padding: 4px 8px; border-radius: 3px; flex: 1; outline: none; max-width: 320px;
|
|
1429
|
+
}
|
|
1430
|
+
.settings-input:focus { border-color: rgba(0,200,160,0.5); }
|
|
1431
|
+
.settings-select {
|
|
1432
|
+
background: rgba(0,0,0,0.4); border: 1px solid rgba(0,200,160,0.2);
|
|
1433
|
+
color: var(--text); font-family: 'Azeret Mono', monospace; font-size: 10px;
|
|
1434
|
+
padding: 4px 8px; border-radius: 3px; outline: none;
|
|
1435
|
+
}
|
|
1436
|
+
.settings-btn {
|
|
1437
|
+
background: rgba(0,180,140,0.15); border: 1px solid rgba(0,200,160,0.4);
|
|
1438
|
+
color: rgba(0,230,180,0.9); font-family: 'Azeret Mono', monospace; font-size: 10px;
|
|
1439
|
+
padding: 4px 12px; border-radius: 3px; cursor: pointer; transition: background 0.15s;
|
|
1440
|
+
}
|
|
1441
|
+
.settings-btn:hover { background: rgba(0,200,160,0.25); }
|
|
1442
|
+
.settings-btn.danger { border-color: rgba(255,80,80,0.4); color: rgba(255,80,80,0.9); background: rgba(255,40,40,0.08); }
|
|
1443
|
+
.settings-btn.danger:hover { background: rgba(255,40,40,0.18); }
|
|
1444
|
+
.settings-divider { border: none; border-top: 1px solid rgba(0,200,160,0.1); margin: 12px 0; }
|
|
1445
|
+
.settings-note { font-size: 9px; color: var(--dim); margin-top: 4px; }
|
|
1446
|
+
|
|
1447
|
+
/* Charts pane */
|
|
1448
|
+
.chart-grid-wrap { display: flex; flex-direction: column; gap: 18px; }
|
|
1449
|
+
.chart-section-title { font-size: 9px; color: var(--dim); letter-spacing: 0.1em; margin-bottom: 8px; text-transform: uppercase; }
|
|
1450
|
+
.activity-heatmap { display: grid; grid-template-rows: repeat(7, 10px); grid-auto-flow: column; gap: 2px; }
|
|
1451
|
+
.heatmap-cell { width: 10px; height: 10px; border-radius: 2px; background: rgba(0,200,160,0.08); }
|
|
1452
|
+
.heatmap-cell.l1 { background: rgba(0,200,160,0.2); }
|
|
1453
|
+
.heatmap-cell.l2 { background: rgba(0,200,160,0.45); }
|
|
1454
|
+
.heatmap-cell.l3 { background: rgba(0,200,160,0.7); }
|
|
1455
|
+
.heatmap-cell.l4 { background: rgba(0,230,180,0.95); }
|
|
1456
|
+
.heatmap-cell.fail { background: rgba(255,80,60,0.6); }
|
|
1457
|
+
.heatmap-day-labels { display: flex; gap: 2px; margin-top: 3px; }
|
|
1458
|
+
.heatmap-day-label { width: 10px; font-size: 7px; color: var(--dim); text-align: center; }
|
|
1459
|
+
.run-bar-row { display: flex; align-items: center; gap: 8px; margin-bottom: 5px; }
|
|
1460
|
+
.run-bar-label { font-size: 10px; color: var(--muted); min-width: 120px; }
|
|
1461
|
+
.run-bar-track { flex: 1; height: 6px; background: rgba(0,0,0,0.4); border-radius: 2px; overflow: hidden; }
|
|
1462
|
+
.run-bar-fill { height: 100%; border-radius: 2px; background: linear-gradient(90deg, rgba(0,200,160,0.5), rgba(0,230,180,0.8)); }
|
|
1463
|
+
.run-bar-count { font-size: 9px; color: var(--dim); min-width: 30px; text-align: right; }
|
|
1464
|
+
|
|
1465
|
+
/* Members pane */
|
|
1466
|
+
.member-row { display: flex; align-items: center; gap: 10px; padding: 8px 10px;
|
|
1467
|
+
border: 1px solid rgba(0,200,160,0.1); border-radius: 4px; margin-bottom: 5px;
|
|
1468
|
+
background: rgba(0,20,15,0.4); }
|
|
1469
|
+
.member-avatar { width: 28px; height: 28px; border-radius: 50%; background: rgba(0,200,160,0.2);
|
|
1470
|
+
display: flex; align-items: center; justify-content: center; font-size: 11px; color: rgba(0,200,160,0.9); flex-shrink: 0; }
|
|
1471
|
+
.member-id { color: rgba(0,230,180,0.9); font-size: 10px; flex: 1; }
|
|
1472
|
+
.member-role-badge { font-size: 8px; padding: 2px 6px; border-radius: 2px; }
|
|
1473
|
+
.role-owner { background: rgba(255,180,0,0.2); color: rgba(255,200,60,0.9); }
|
|
1474
|
+
.role-admin { background: rgba(0,200,160,0.2); color: rgba(0,230,180,0.9); }
|
|
1475
|
+
.role-operator { background: rgba(100,140,255,0.2); color: rgba(120,160,255,0.9); }
|
|
1476
|
+
.role-viewer { background: rgba(100,100,100,0.2); color: rgba(160,160,160,0.9); }
|
|
1477
|
+
.member-status-suspended { color: rgba(255,80,80,0.7); font-size: 9px; }
|
|
1478
|
+
.member-grants { font-size: 8px; color: var(--dim); }
|
|
1479
|
+
.invite-form { display: flex; gap: 8px; align-items: center; margin-bottom: 14px; flex-wrap: wrap; }
|
|
1480
|
+
.invite-select { background: rgba(0,0,0,0.4); border: 1px solid rgba(0,200,160,0.2); color: var(--text);
|
|
1481
|
+
font-family: 'Azeret Mono', monospace; font-size: 10px; padding: 4px 8px; border-radius: 3px; outline: none; }
|
|
1482
|
+
.invite-btn { background: rgba(0,180,140,0.15); border: 1px solid rgba(0,200,160,0.4);
|
|
1483
|
+
color: rgba(0,230,180,0.9); font-family: 'Azeret Mono', monospace; font-size: 10px;
|
|
1484
|
+
padding: 4px 12px; border-radius: 3px; cursor: pointer; }
|
|
1485
|
+
.invite-url-box { background: rgba(0,0,0,0.4); border: 1px solid rgba(0,200,160,0.2);
|
|
1486
|
+
border-radius: 3px; padding: 6px 10px; font-size: 9px; color: rgba(0,200,160,0.8); word-break: break-all; margin-top: 8px; }
|
|
1487
|
+
|
|
1488
|
+
/* Goals pane */
|
|
1489
|
+
.goal-tree-root { margin-bottom: 6px; }
|
|
1490
|
+
.goal-node { padding: 7px 10px; border: 1px solid rgba(0,200,160,0.12); border-radius: 4px;
|
|
1491
|
+
background: rgba(0,20,15,0.4); margin-bottom: 4px; display: flex; align-items: flex-start; gap: 8px; }
|
|
1492
|
+
.goal-node-children { padding-left: 22px; border-left: 1px solid rgba(0,200,160,0.15); margin-left: 10px; margin-bottom: 4px; }
|
|
1493
|
+
.goal-title { color: rgba(0,230,180,0.9); font-size: 10px; flex: 1; }
|
|
1494
|
+
.goal-desc { font-size: 9px; color: var(--muted); margin-top: 2px; }
|
|
1495
|
+
.goal-status-badge { font-size: 8px; padding: 2px 6px; border-radius: 2px; flex-shrink: 0; white-space: nowrap; }
|
|
1496
|
+
.goal-open { background: rgba(100,120,255,0.2); color: rgba(140,160,255,0.9); }
|
|
1497
|
+
.goal-in_progress { background: rgba(0,200,160,0.2); color: rgba(0,230,180,0.9); }
|
|
1498
|
+
.goal-done { background: rgba(60,200,100,0.2); color: rgba(80,220,120,0.9); }
|
|
1499
|
+
.goal-progress-bar { height: 3px; background: rgba(0,200,160,0.15); border-radius: 2px; margin-top: 4px; }
|
|
1500
|
+
.goal-progress-fill { height: 100%; background: rgba(0,200,160,0.7); border-radius: 2px; transition: width 0.3s; }
|
|
1501
|
+
.goal-meta { font-size: 8px; color: var(--dim); margin-top: 3px; }
|
|
1502
|
+
|
|
1503
|
+
/* Workspaces pane */
|
|
1504
|
+
.ws-project-group { margin-bottom: 18px; }
|
|
1505
|
+
.ws-project-label { font-size: 9px; color: rgba(0,200,160,0.7); letter-spacing: 0.1em;
|
|
1506
|
+
text-transform: uppercase; margin-bottom: 6px; padding-bottom: 4px; border-bottom: 1px solid rgba(0,200,160,0.1); }
|
|
1507
|
+
.ws-card { padding: 9px 12px; border: 1px solid rgba(0,200,160,0.12); border-radius: 4px;
|
|
1508
|
+
background: rgba(0,15,12,0.5); margin-bottom: 6px; display: flex; align-items: flex-start; gap: 10px; }
|
|
1509
|
+
.ws-card-info { flex: 1; min-width: 0; }
|
|
1510
|
+
.ws-id { color: rgba(0,230,180,0.8); font-size: 10px; font-weight: 500; }
|
|
1511
|
+
.ws-branch { font-size: 9px; color: rgba(150,200,180,0.7); margin-top: 2px; }
|
|
1512
|
+
.ws-path { font-size: 8px; color: var(--dim); margin-top: 2px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
1513
|
+
.ws-agent { font-size: 9px; color: rgba(255,183,0,0.8); margin-top: 2px; }
|
|
1514
|
+
.ws-status-badge { font-size: 8px; padding: 2px 7px; border-radius: 2px; flex-shrink: 0; }
|
|
1515
|
+
.ws-active { background: rgba(0,200,160,0.18); color: rgba(0,230,180,0.9); }
|
|
1516
|
+
.ws-stopped { background: rgba(255,80,80,0.15); color: rgba(255,100,100,0.7); }
|
|
1517
|
+
.ws-detached { background: rgba(120,120,120,0.2); color: rgba(160,160,160,0.7); }
|
|
1518
|
+
.ws-svc-count { font-size: 8px; color: var(--dim); margin-top: 3px; }
|
|
1519
|
+
.ws-stop-btn { font-size: 8px; background: rgba(255,80,80,0.12); border: 1px solid rgba(255,80,80,0.2);
|
|
1520
|
+
color: rgba(255,100,100,0.7); padding: 3px 8px; border-radius: 2px; cursor: pointer; flex-shrink: 0; }
|
|
1521
|
+
.ws-stop-btn:hover { background: rgba(255,80,80,0.22); }
|
|
1522
|
+
|
|
1523
|
+
/* Board pane */
|
|
1524
|
+
.board-columns { display: flex; gap: 12px; overflow-x: auto; padding-bottom: 8px; min-height: 200px; }
|
|
1525
|
+
.board-col { flex: 0 0 200px; min-width: 180px; }
|
|
1526
|
+
.board-col-header { font-size: 9px; letter-spacing: 0.12em; text-transform: uppercase; padding: 5px 8px;
|
|
1527
|
+
border-radius: 3px 3px 0 0; margin-bottom: 6px; display: flex; justify-content: space-between; align-items: center; }
|
|
1528
|
+
.board-col-open .board-col-header { background: rgba(100,120,255,0.15); color: rgba(140,160,255,0.9); }
|
|
1529
|
+
.board-col-in_progress .board-col-header { background: rgba(0,200,160,0.15); color: rgba(0,230,180,0.9); }
|
|
1530
|
+
.board-col-done .board-col-header { background: rgba(60,200,100,0.12); color: rgba(80,220,120,0.8); }
|
|
1531
|
+
.board-col-cancelled .board-col-header { background: rgba(100,100,100,0.15); color: rgba(160,160,160,0.7); }
|
|
1532
|
+
.board-col-blocked .board-col-header { background: rgba(255,80,80,0.12); color: rgba(255,100,100,0.7); }
|
|
1533
|
+
.board-count-badge { font-size: 8px; background: rgba(255,255,255,0.1); padding: 1px 5px; border-radius: 8px; }
|
|
1534
|
+
.board-card { padding: 7px 9px; border: 1px solid rgba(255,255,255,0.07); border-radius: 3px;
|
|
1535
|
+
background: rgba(0,10,20,0.5); margin-bottom: 5px; cursor: default; }
|
|
1536
|
+
.board-card:hover { border-color: rgba(0,200,160,0.2); background: rgba(0,15,25,0.6); }
|
|
1537
|
+
.board-card-title { font-size: 10px; color: var(--text); margin-bottom: 3px; line-height: 1.3; }
|
|
1538
|
+
.board-card-meta { font-size: 8px; color: var(--dim); display: flex; gap: 6px; flex-wrap: wrap; }
|
|
1539
|
+
.board-priority-critical { color: rgba(255,60,60,0.8); }
|
|
1540
|
+
.board-priority-high { color: rgba(255,150,50,0.8); }
|
|
1541
|
+
.board-priority-medium { color: rgba(255,220,80,0.7); }
|
|
1542
|
+
.board-priority-low { color: rgba(120,180,120,0.7); }
|
|
1543
|
+
.board-empty { font-size: 9px; color: var(--dim); padding: 10px 8px; }
|
|
1544
|
+
|
|
1545
|
+
/* Health pane */
|
|
1546
|
+
.health-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 12px; margin-bottom: 20px; }
|
|
1547
|
+
.health-metric-card { padding: 12px 14px; border: 1px solid rgba(0,200,160,0.12); border-radius: 4px;
|
|
1548
|
+
background: rgba(0,12,10,0.5); }
|
|
1549
|
+
.health-metric-label { font-size: 8px; color: var(--dim); letter-spacing: 0.1em; text-transform: uppercase; margin-bottom: 6px; }
|
|
1550
|
+
.health-metric-value { font-size: 22px; font-family: 'Syne', sans-serif; font-weight: 800; color: rgba(0,230,180,0.9); }
|
|
1551
|
+
.health-metric-unit { font-size: 9px; color: var(--muted); margin-left: 3px; }
|
|
1552
|
+
.health-metric-sub { font-size: 9px; color: var(--muted); margin-top: 3px; }
|
|
1553
|
+
.health-ok { color: rgba(0,230,180,0.9); }
|
|
1554
|
+
.health-warn { color: rgba(255,183,0,0.9); }
|
|
1555
|
+
.health-crit { color: rgba(255,80,80,0.9); }
|
|
1556
|
+
.health-bar { height: 4px; background: rgba(0,200,160,0.12); border-radius: 2px; margin-top: 8px; }
|
|
1557
|
+
.health-bar-fill { height: 100%; border-radius: 2px; transition: width 0.4s; }
|
|
1558
|
+
.health-bar-ok { background: rgba(0,200,160,0.7); }
|
|
1559
|
+
.health-bar-warn { background: rgba(255,183,0,0.7); }
|
|
1560
|
+
.health-bar-crit { background: rgba(255,80,80,0.7); }
|
|
1561
|
+
.health-section-title { font-size: 9px; color: rgba(0,200,160,0.7); letter-spacing: 0.1em;
|
|
1562
|
+
text-transform: uppercase; margin-bottom: 10px; margin-top: 18px; padding-bottom: 4px;
|
|
1563
|
+
border-bottom: 1px solid rgba(0,200,160,0.1); }
|
|
1564
|
+
|
|
1565
|
+
/* Invites tab */
|
|
1566
|
+
.invite-section-title { font-size: 9px; color: rgba(0,200,160,0.7); letter-spacing: 0.1em;
|
|
1567
|
+
text-transform: uppercase; margin-bottom: 8px; margin-top: 18px; padding-bottom: 4px;
|
|
1568
|
+
border-bottom: 1px solid rgba(0,200,160,0.1); }
|
|
1569
|
+
.invite-row { display: flex; align-items: center; gap: 8px; padding: 6px 8px;
|
|
1570
|
+
border-bottom: 1px solid rgba(255,255,255,0.04); font-size: 10px; }
|
|
1571
|
+
.invite-row:last-child { border-bottom: none; }
|
|
1572
|
+
.invite-token { font-family: monospace; font-size: 9px; color: rgba(200,200,255,0.6); flex: 1; }
|
|
1573
|
+
.invite-role { font-size: 8px; padding: 2px 6px; border-radius: 2px;
|
|
1574
|
+
background: rgba(0,200,160,0.12); color: rgba(0,200,160,0.8); }
|
|
1575
|
+
.invite-meta { font-size: 8px; color: var(--dim); }
|
|
1576
|
+
.join-row { padding: 8px; border: 1px solid rgba(255,255,255,0.06); border-radius: 3px;
|
|
1577
|
+
margin-bottom: 6px; font-size: 10px; }
|
|
1578
|
+
.join-row-header { display: flex; align-items: center; gap: 8px; margin-bottom: 4px; }
|
|
1579
|
+
.join-type-badge { font-size: 8px; padding: 2px 6px; border-radius: 2px;
|
|
1580
|
+
background: rgba(100,100,200,0.18); color: rgba(150,150,255,0.9); }
|
|
1581
|
+
.join-msg { font-size: 9px; color: rgba(200,200,220,0.6); margin-top: 3px; }
|
|
1582
|
+
.join-hint { font-size: 8px; color: var(--dim); margin-top: 4px; font-family: monospace; }
|
|
1583
|
+
|
|
1584
|
+
/* Plugins tab */
|
|
1585
|
+
.plugin-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 8px; padding: 4px 0; }
|
|
1586
|
+
.plugin-card { border: 1px solid rgba(255,255,255,0.08); border-radius: 4px;
|
|
1587
|
+
padding: 10px 12px; background: rgba(255,255,255,0.02); }
|
|
1588
|
+
.plugin-card-header { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; }
|
|
1589
|
+
.plugin-status-dot { width: 6px; height: 6px; border-radius: 50%; flex-shrink: 0; }
|
|
1590
|
+
.plugin-status-installed { background: rgba(0,200,160,0.8); }
|
|
1591
|
+
.plugin-status-error { background: rgba(255,80,80,0.8); }
|
|
1592
|
+
.plugin-status-disabled { background: rgba(150,150,150,0.5); }
|
|
1593
|
+
.plugin-name { font-size: 10px; font-weight: 600; flex: 1; }
|
|
1594
|
+
.plugin-version { font-size: 8px; color: var(--dim); }
|
|
1595
|
+
.plugin-category { font-size: 8px; color: rgba(0,200,160,0.6); margin-bottom: 4px;
|
|
1596
|
+
text-transform: uppercase; letter-spacing: 0.08em; }
|
|
1597
|
+
.plugin-pkg { font-size: 8px; color: var(--dim); font-family: monospace; margin-bottom: 4px; }
|
|
1598
|
+
.plugin-error { font-size: 8px; color: rgba(255,100,100,0.8); margin-top: 4px;
|
|
1599
|
+
padding: 3px 6px; background: rgba(255,80,80,0.08); border-radius: 2px; }
|
|
1600
|
+
.plugin-override { font-size: 8px; color: rgba(255,183,0,0.7); margin-top: 3px; }
|
|
1601
|
+
|
|
1602
|
+
/* My Issues tab */
|
|
1603
|
+
.myissues-empty { color: var(--dim); font-size: 10px; padding: 12px 0; }
|
|
1604
|
+
.myissues-priority-high { color: rgba(255,120,80,0.9); }
|
|
1605
|
+
.myissues-priority-medium { color: rgba(255,183,0,0.85); }
|
|
1606
|
+
.myissues-priority-low { color: rgba(100,200,160,0.7); }
|
|
1607
|
+
.myissues-status-open { color: rgba(150,150,255,0.8); }
|
|
1608
|
+
.myissues-status-in_progress { color: rgba(0,200,160,0.9); }
|
|
1609
|
+
.myissues-hint { font-size: 8px; color: var(--dim); margin-top: 10px; font-family: monospace; }
|
|
1610
|
+
|
|
1611
|
+
/* Command Palette */
|
|
1612
|
+
#cmd-palette-overlay {
|
|
1613
|
+
position: fixed; inset: 0; z-index: 2000; display: none;
|
|
1614
|
+
align-items: flex-start; justify-content: center;
|
|
1615
|
+
padding-top: 80px;
|
|
1616
|
+
background: rgba(0,0,0,0.7); backdrop-filter: blur(4px);
|
|
1617
|
+
}
|
|
1618
|
+
#cmd-palette-overlay.open { display: flex; }
|
|
1619
|
+
#cmd-palette-box {
|
|
1620
|
+
width: 560px; max-width: 95vw; max-height: 420px;
|
|
1621
|
+
background: rgba(8,10,22,0.98); border: 1px solid rgba(0,229,200,0.25);
|
|
1622
|
+
border-radius: 8px; overflow: hidden; display: flex; flex-direction: column;
|
|
1623
|
+
box-shadow: 0 16px 64px rgba(0,0,0,0.8);
|
|
1624
|
+
}
|
|
1625
|
+
#cmd-palette-input {
|
|
1626
|
+
width: 100%; background: none; border: none; border-bottom: 1px solid rgba(0,229,200,0.15);
|
|
1627
|
+
color: var(--text); font-family: 'Azeret Mono', monospace; font-size: 13px;
|
|
1628
|
+
padding: 14px 16px; outline: none;
|
|
1629
|
+
}
|
|
1630
|
+
#cmd-palette-input::placeholder { color: var(--dim); }
|
|
1631
|
+
#cmd-palette-results { flex: 1; overflow-y: auto; padding: 6px 0; }
|
|
1632
|
+
.cp-group-label { font-size: 8px; letter-spacing: 0.14em; color: var(--dim); padding: 6px 14px 2px; text-transform: uppercase; }
|
|
1633
|
+
.cp-item {
|
|
1634
|
+
display: flex; align-items: center; gap: 10px; padding: 7px 14px;
|
|
1635
|
+
cursor: pointer; transition: background 0.1s;
|
|
1636
|
+
}
|
|
1637
|
+
.cp-item:hover, .cp-item.selected { background: rgba(0,229,200,0.08); }
|
|
1638
|
+
.cp-item-icon { font-size: 12px; width: 20px; text-align: center; color: var(--dim); flex-shrink: 0; }
|
|
1639
|
+
.cp-item-label { font-size: 11px; color: var(--text); flex: 1; }
|
|
1640
|
+
.cp-item-hint { font-size: 9px; color: var(--dim); }
|
|
1641
|
+
.cp-empty { padding: 24px; text-align: center; color: var(--dim); font-size: 11px; }
|
|
1642
|
+
#cmd-palette-footer { padding: 5px 14px; border-top: 1px solid rgba(0,229,200,0.08);
|
|
1643
|
+
display: flex; gap: 14px; font-size: 9px; color: var(--dim); }
|
|
1644
|
+
#cmd-palette-footer kbd { background: rgba(255,255,255,0.06); border-radius: 2px; padding: 1px 4px; }
|
|
1246
1645
|
|
|
1247
1646
|
/* ─── MONOGRAPH OVERLAY ──────────────────────────────────────────── */
|
|
1248
1647
|
#monograph-overlay {
|
|
@@ -1972,6 +2371,7 @@
|
|
|
1972
2371
|
<button class="po-tab" onclick="switchPalaceTab('swarm')">SWARM</button>
|
|
1973
2372
|
<button class="po-tab" onclick="switchPalaceTab('graph')">AGENT GRAPH</button>
|
|
1974
2373
|
<button class="po-tab" onclick="switchPalaceTab('usage')">USAGE</button>
|
|
2374
|
+
<button class="po-tab" onclick="switchPalaceTab('adrs')">ADRs</button>
|
|
1975
2375
|
</div>
|
|
1976
2376
|
<div id="po-stats"></div>
|
|
1977
2377
|
</div>
|
|
@@ -2112,6 +2512,20 @@
|
|
|
2112
2512
|
</div>
|
|
2113
2513
|
<div id="po-usage-body" style="flex:1;overflow-y:auto;"></div>
|
|
2114
2514
|
</div>
|
|
2515
|
+
<div id="po-adrs-tab" class="po-tab-pane" style="flex-direction:column;overflow:hidden;">
|
|
2516
|
+
<div id="po-adrs-header" style="display:flex;align-items:center;gap:16px;padding:12px 20px;border-bottom:1px solid var(--border);flex-shrink:0;">
|
|
2517
|
+
<div style="display:flex;gap:20px;">
|
|
2518
|
+
<div class="know-stat"><div class="know-stat-label">TOTAL</div><div class="know-stat-val" id="po-adrs-count">—</div></div>
|
|
2519
|
+
<div class="know-stat"><div class="know-stat-label">IMPL</div><div class="know-stat-val" id="po-adrs-impl-count">—</div></div>
|
|
2520
|
+
<div class="know-stat"><div class="know-stat-label">GUIDANCE</div><div class="know-stat-val" id="po-adrs-guidance-count">—</div></div>
|
|
2521
|
+
</div>
|
|
2522
|
+
<input id="po-adrs-search" type="text" placeholder="Filter ADRs…" oninput="filterAdrs(this.value)"
|
|
2523
|
+
style="flex:1;max-width:320px;background:rgba(0,0,0,0.3);border:1px solid var(--border);color:var(--text);font-family:'Azeret Mono',monospace;font-size:10px;padding:4px 8px;border-radius:3px;outline:none;">
|
|
2524
|
+
</div>
|
|
2525
|
+
<div id="po-adrs-body" style="flex:1;overflow-y:auto;padding:16px 20px;">
|
|
2526
|
+
<div style="color:var(--muted);font-size:10px;">Loading…</div>
|
|
2527
|
+
</div>
|
|
2528
|
+
</div>
|
|
2115
2529
|
</div>
|
|
2116
2530
|
<!-- Edit memory modal -->
|
|
2117
2531
|
<div id="po-edit-modal">
|
|
@@ -2161,9 +2575,196 @@
|
|
|
2161
2575
|
<div id="mm-header">
|
|
2162
2576
|
<button id="mm-back" onclick="closeMastermindOverlay()">← BACK</button>
|
|
2163
2577
|
<span id="mm-header-title">🧠 MASTERMIND</span>
|
|
2164
|
-
<span id="mm-header-sub">AUTONOMOUS BUSINESS BRAIN ·
|
|
2578
|
+
<span id="mm-header-sub">AUTONOMOUS BUSINESS BRAIN · ORGS · SKILLS · LOOPS</span>
|
|
2579
|
+
</div>
|
|
2580
|
+
<div id="mm-hub">
|
|
2581
|
+
<div id="mm-tabs">
|
|
2582
|
+
<button class="mm-tab active" onclick="switchMmTab('orgs')">ORGS</button>
|
|
2583
|
+
<button class="mm-tab" onclick="switchMmTab('skills')">SKILLS</button>
|
|
2584
|
+
<button class="mm-tab" onclick="switchMmTab('loops')">LOOPS</button>
|
|
2585
|
+
<button class="mm-tab" onclick="switchMmTab('create')">CREATE ORG</button>
|
|
2586
|
+
<button class="mm-tab" onclick="switchMmTab('metrics')">METRICS</button>
|
|
2587
|
+
</div>
|
|
2588
|
+
<div id="mm-body">
|
|
2589
|
+
<div class="mm-pane active" id="mm-pane-orgs">
|
|
2590
|
+
<div class="mm-section-title">Organizations</div>
|
|
2591
|
+
<div id="mm-orgs-list"><span style="color:rgba(150,100,200,0.4)">Loading orgs…</span></div>
|
|
2592
|
+
</div>
|
|
2593
|
+
<div class="mm-pane" id="mm-pane-skills">
|
|
2594
|
+
<div class="mm-section-title">Mastermind Slash Commands</div>
|
|
2595
|
+
<input id="mm-skills-search" placeholder="search skills…" style="width:100%;max-width:320px;background:rgba(200,120,255,0.06);border:1px solid rgba(200,120,255,0.25);color:rgba(210,140,255,0.9);font-family:'Azeret Mono',monospace;font-size:9px;padding:5px 8px;border-radius:3px;outline:none;margin-bottom:12px;display:block" oninput="filterMmSkills(this.value)" />
|
|
2596
|
+
<div id="mm-skills-grid" class="mm-skill-grid"></div>
|
|
2597
|
+
</div>
|
|
2598
|
+
<div class="mm-pane" id="mm-pane-loops">
|
|
2599
|
+
<div class="mm-section-title">Active Loops</div>
|
|
2600
|
+
<div id="mm-loops-list"><span style="color:rgba(150,100,200,0.4)">Loading loops…</span></div>
|
|
2601
|
+
</div>
|
|
2602
|
+
<div class="mm-pane" id="mm-pane-create">
|
|
2603
|
+
<div class="mm-section-title">Create a New Org</div>
|
|
2604
|
+
<div style="max-width:480px">
|
|
2605
|
+
<div style="margin-bottom:10px;font-size:9px;color:rgba(150,100,200,0.5)">Define an autonomous agent organization. Run <code style="color:rgba(210,140,255,0.7)">/mastermind:createorg</code> in Claude Code for the full wizard.</div>
|
|
2606
|
+
<div style="margin-bottom:8px">
|
|
2607
|
+
<label style="display:block;font-size:9px;color:rgba(150,100,200,0.7);margin-bottom:3px;letter-spacing:0.08em">ORG NAME (slug)</label>
|
|
2608
|
+
<input id="mm-create-name" placeholder="e.g. content-team" style="width:100%;background:rgba(200,120,255,0.06);border:1px solid rgba(200,120,255,0.25);color:rgba(210,140,255,0.9);font-family:'Azeret Mono',monospace;font-size:10px;padding:6px 8px;border-radius:3px;outline:none;box-sizing:border-box" />
|
|
2609
|
+
</div>
|
|
2610
|
+
<div style="margin-bottom:8px">
|
|
2611
|
+
<label style="display:block;font-size:9px;color:rgba(150,100,200,0.7);margin-bottom:3px;letter-spacing:0.08em">GOAL / DESCRIPTION</label>
|
|
2612
|
+
<textarea id="mm-create-goal" rows="3" placeholder="What is this org trying to achieve?" style="width:100%;background:rgba(200,120,255,0.06);border:1px solid rgba(200,120,255,0.25);color:rgba(210,140,255,0.9);font-family:'Azeret Mono',monospace;font-size:9px;padding:6px 8px;border-radius:3px;outline:none;resize:vertical;box-sizing:border-box"></textarea>
|
|
2613
|
+
</div>
|
|
2614
|
+
<div style="margin-bottom:8px">
|
|
2615
|
+
<label style="display:block;font-size:9px;color:rgba(150,100,200,0.7);margin-bottom:3px;letter-spacing:0.08em">GOVERNANCE</label>
|
|
2616
|
+
<select id="mm-create-gov" style="background:rgba(200,120,255,0.06);border:1px solid rgba(200,120,255,0.25);color:rgba(210,140,255,0.9);font-family:'Azeret Mono',monospace;font-size:9px;padding:5px 8px;border-radius:3px;outline:none">
|
|
2617
|
+
<option value="democratic">democratic</option>
|
|
2618
|
+
<option value="autocratic">autocratic</option>
|
|
2619
|
+
<option value="consensus">consensus</option>
|
|
2620
|
+
<option value="meritocratic">meritocratic</option>
|
|
2621
|
+
</select>
|
|
2622
|
+
</div>
|
|
2623
|
+
<div style="margin-top:12px;display:flex;gap:10px;align-items:center">
|
|
2624
|
+
<button class="mm-btn" onclick="mmCreateOrgCmd()">GENERATE COMMAND</button>
|
|
2625
|
+
<span id="mm-create-output" style="font-size:9px;color:rgba(150,100,200,0.5)"></span>
|
|
2626
|
+
</div>
|
|
2627
|
+
<div id="mm-create-cmd-box" style="display:none;margin-top:12px;background:rgba(0,0,0,0.4);border:1px solid rgba(200,120,255,0.2);border-radius:4px;padding:10px;font-family:'Azeret Mono',monospace;font-size:9px;color:rgba(210,140,255,0.85);word-break:break-all"></div>
|
|
2628
|
+
</div>
|
|
2629
|
+
</div>
|
|
2630
|
+
<div class="mm-pane" id="mm-pane-metrics">
|
|
2631
|
+
<div class="mm-section-title">System Metrics</div>
|
|
2632
|
+
<div id="mm-metrics-grid" class="mm-metric-grid"></div>
|
|
2633
|
+
<div class="mm-section-title" style="margin-top:16px">Recent Events</div>
|
|
2634
|
+
<div id="mm-metrics-events" style="font-size:9px;color:rgba(150,100,200,0.6);font-family:'Azeret Mono',monospace"></div>
|
|
2635
|
+
</div>
|
|
2636
|
+
</div>
|
|
2637
|
+
</div>
|
|
2638
|
+
</div>
|
|
2639
|
+
|
|
2640
|
+
<!-- ════════════════════════════ ORG ROOM OVERLAY ══════════════════ -->
|
|
2641
|
+
<div id="orgroom-overlay">
|
|
2642
|
+
<div id="orgroom-header">
|
|
2643
|
+
<button id="orgroom-back" onclick="closeOrgRoom()">← BACK</button>
|
|
2644
|
+
<span id="orgroom-title">⬡ ORG ROOM</span>
|
|
2645
|
+
<span id="orgroom-sub">AUTONOMOUS ORGANIZATION CONTROL CENTER</span>
|
|
2646
|
+
<span style="margin-left:auto;font-size:9px;color:rgba(0,200,160,0.5);" id="orgroom-org-label"></span>
|
|
2647
|
+
</div>
|
|
2648
|
+
<div id="orgroom-tabs">
|
|
2649
|
+
<button class="orgroom-tab active" onclick="switchOrgTab('chart')">ORG CHART</button>
|
|
2650
|
+
<button class="orgroom-tab" onclick="switchOrgTab('heartbeats')">HEARTBEATS</button>
|
|
2651
|
+
<button class="orgroom-tab" onclick="switchOrgTab('tasks')">TASK BOARD</button>
|
|
2652
|
+
<button class="orgroom-tab" onclick="switchOrgTab('costs')">COSTS</button>
|
|
2653
|
+
<button class="orgroom-tab" onclick="switchOrgTab('routines')">ROUTINES</button>
|
|
2654
|
+
<button class="orgroom-tab" onclick="switchOrgTab('inbox')">INBOX</button>
|
|
2655
|
+
<button class="orgroom-tab" onclick="switchOrgTab('projects')">PROJECTS</button>
|
|
2656
|
+
<button class="orgroom-tab" onclick="switchOrgTab('activity')">ACTIVITY</button>
|
|
2657
|
+
<button class="orgroom-tab" onclick="switchOrgTab('live')" style="color:rgba(0,229,135,0.6);">● LIVE</button>
|
|
2658
|
+
<button class="orgroom-tab" onclick="switchOrgTab('charts')">CHARTS</button>
|
|
2659
|
+
<button class="orgroom-tab" onclick="switchOrgTab('members')">MEMBERS</button>
|
|
2660
|
+
<button class="orgroom-tab" onclick="switchOrgTab('skills')">SKILLS</button>
|
|
2661
|
+
<button class="orgroom-tab" onclick="switchOrgTab('goals')">GOALS</button>
|
|
2662
|
+
<button class="orgroom-tab" onclick="switchOrgTab('workspaces')">WORKSPACES</button>
|
|
2663
|
+
<button class="orgroom-tab" onclick="switchOrgTab('board')">BOARD</button>
|
|
2664
|
+
<button class="orgroom-tab" onclick="switchOrgTab('health')">HEALTH</button>
|
|
2665
|
+
<button class="orgroom-tab" onclick="switchOrgTab('invites')">INVITES</button>
|
|
2666
|
+
<button class="orgroom-tab" onclick="switchOrgTab('plugins')">PLUGINS</button>
|
|
2667
|
+
<button class="orgroom-tab" onclick="switchOrgTab('myissues')">MY ISSUES</button>
|
|
2668
|
+
<button class="orgroom-tab" onclick="switchOrgTab('agents')">AGENTS</button>
|
|
2669
|
+
<button class="orgroom-tab" onclick="switchOrgTab('approvals')">APPROVALS</button>
|
|
2670
|
+
<button class="orgroom-tab" onclick="switchOrgTab('secrets')">SECRETS</button>
|
|
2671
|
+
<button class="orgroom-tab" onclick="switchOrgTab('envs')">ENVIRONMENTS</button>
|
|
2672
|
+
<button class="orgroom-tab" onclick="switchOrgTab('access')">ACCESS</button>
|
|
2673
|
+
<button class="orgroom-tab" onclick="switchOrgTab('issues')">ISSUES</button>
|
|
2674
|
+
<button class="orgroom-tab" onclick="switchOrgTab('joinqueue')">JOIN REQUESTS</button>
|
|
2675
|
+
<button class="orgroom-tab" onclick="switchOrgTab('budgets')">BUDGETS</button>
|
|
2676
|
+
<button class="orgroom-tab" onclick="switchOrgTab('threads')">THREADS</button>
|
|
2677
|
+
<button class="orgroom-tab" onclick="switchOrgTab('settings')">SETTINGS</button>
|
|
2678
|
+
</div>
|
|
2679
|
+
<div id="orgroom-body">
|
|
2680
|
+
<div class="orgroom-pane active" id="orgroom-pane-chart">
|
|
2681
|
+
<div id="orgroom-chart-svg"><span style="color:var(--dim)">Loading org chart…</span></div>
|
|
2682
|
+
</div>
|
|
2683
|
+
<div class="orgroom-pane" id="orgroom-pane-heartbeats">
|
|
2684
|
+
<div id="orgroom-heartbeats-body"><span style="color:var(--dim)">Loading heartbeats…</span></div>
|
|
2685
|
+
</div>
|
|
2686
|
+
<div class="orgroom-pane" id="orgroom-pane-tasks">
|
|
2687
|
+
<div id="orgroom-tasks-body"><span style="color:var(--dim)">Loading task board…</span></div>
|
|
2688
|
+
</div>
|
|
2689
|
+
<div class="orgroom-pane" id="orgroom-pane-costs">
|
|
2690
|
+
<div id="orgroom-costs-body"><span style="color:var(--dim)">Loading cost data…</span></div>
|
|
2691
|
+
</div>
|
|
2692
|
+
<div class="orgroom-pane" id="orgroom-pane-routines">
|
|
2693
|
+
<div id="orgroom-routines-body"><span style="color:var(--dim)">Loading routines…</span></div>
|
|
2694
|
+
</div>
|
|
2695
|
+
<div class="orgroom-pane" id="orgroom-pane-inbox">
|
|
2696
|
+
<div id="orgroom-inbox-body"><span style="color:var(--dim)">Loading inbox…</span></div>
|
|
2697
|
+
</div>
|
|
2698
|
+
<div class="orgroom-pane" id="orgroom-pane-projects">
|
|
2699
|
+
<div id="orgroom-projects-body"><span style="color:var(--dim)">Loading projects…</span></div>
|
|
2700
|
+
</div>
|
|
2701
|
+
<div class="orgroom-pane" id="orgroom-pane-activity">
|
|
2702
|
+
<div id="orgroom-activity-body"><span style="color:var(--dim)">Loading activity…</span></div>
|
|
2703
|
+
</div>
|
|
2704
|
+
<div class="orgroom-pane" id="orgroom-pane-live">
|
|
2705
|
+
<div id="orgroom-live-body"><span style="color:var(--dim)">Connecting to live stream…</span></div>
|
|
2706
|
+
</div>
|
|
2707
|
+
<div class="orgroom-pane" id="orgroom-pane-charts">
|
|
2708
|
+
<div id="orgroom-charts-body"><span style="color:var(--dim)">Loading charts…</span></div>
|
|
2709
|
+
</div>
|
|
2710
|
+
<div class="orgroom-pane" id="orgroom-pane-members">
|
|
2711
|
+
<div id="orgroom-members-body"><span style="color:var(--dim)">Loading members…</span></div>
|
|
2712
|
+
</div>
|
|
2713
|
+
<div class="orgroom-pane" id="orgroom-pane-skills">
|
|
2714
|
+
<div id="orgroom-skills-body"><span style="color:var(--dim)">Loading skills…</span></div>
|
|
2715
|
+
</div>
|
|
2716
|
+
<div class="orgroom-pane" id="orgroom-pane-goals">
|
|
2717
|
+
<div id="orgroom-goals-body"><span style="color:var(--dim)">Loading goals…</span></div>
|
|
2718
|
+
</div>
|
|
2719
|
+
<div class="orgroom-pane" id="orgroom-pane-workspaces">
|
|
2720
|
+
<div id="orgroom-workspaces-body"><span style="color:var(--dim)">Loading workspaces…</span></div>
|
|
2721
|
+
</div>
|
|
2722
|
+
<div class="orgroom-pane" id="orgroom-pane-board">
|
|
2723
|
+
<div id="orgroom-board-body"><span style="color:var(--dim)">Loading board…</span></div>
|
|
2724
|
+
</div>
|
|
2725
|
+
<div class="orgroom-pane" id="orgroom-pane-health">
|
|
2726
|
+
<div id="orgroom-health-body"><span style="color:var(--dim)">Loading health…</span></div>
|
|
2727
|
+
</div>
|
|
2728
|
+
<div class="orgroom-pane" id="orgroom-pane-invites">
|
|
2729
|
+
<div id="orgroom-invites-body"><span style="color:var(--dim)">Loading invites…</span></div>
|
|
2730
|
+
</div>
|
|
2731
|
+
<div class="orgroom-pane" id="orgroom-pane-plugins">
|
|
2732
|
+
<div id="orgroom-plugins-body"><span style="color:var(--dim)">Loading plugins…</span></div>
|
|
2733
|
+
</div>
|
|
2734
|
+
<div class="orgroom-pane" id="orgroom-pane-myissues">
|
|
2735
|
+
<div id="orgroom-myissues-body"><span style="color:var(--dim)">Loading issues…</span></div>
|
|
2736
|
+
</div>
|
|
2737
|
+
<div class="orgroom-pane" id="orgroom-pane-agents">
|
|
2738
|
+
<div id="orgroom-agents-body"><span style="color:var(--dim)">Loading agents…</span></div>
|
|
2739
|
+
</div>
|
|
2740
|
+
<div class="orgroom-pane" id="orgroom-pane-approvals">
|
|
2741
|
+
<div id="orgroom-approvals-body"><span style="color:var(--dim)">Loading approvals…</span></div>
|
|
2742
|
+
</div>
|
|
2743
|
+
<div class="orgroom-pane" id="orgroom-pane-secrets">
|
|
2744
|
+
<div id="orgroom-secrets-body"><span style="color:var(--dim)">Loading secrets…</span></div>
|
|
2745
|
+
</div>
|
|
2746
|
+
<div class="orgroom-pane" id="orgroom-pane-envs">
|
|
2747
|
+
<div id="orgroom-envs-body"><span style="color:var(--dim)">Loading environments…</span></div>
|
|
2748
|
+
</div>
|
|
2749
|
+
<div class="orgroom-pane" id="orgroom-pane-access">
|
|
2750
|
+
<div id="orgroom-access-body"><span style="color:var(--dim)">Loading access…</span></div>
|
|
2751
|
+
</div>
|
|
2752
|
+
<div class="orgroom-pane" id="orgroom-pane-issues">
|
|
2753
|
+
<div id="orgroom-issues-body"><span style="color:var(--dim)">Loading issues…</span></div>
|
|
2754
|
+
</div>
|
|
2755
|
+
<div class="orgroom-pane" id="orgroom-pane-joinqueue">
|
|
2756
|
+
<div id="orgroom-joinqueue-body"><span style="color:var(--dim)">Loading join requests…</span></div>
|
|
2757
|
+
</div>
|
|
2758
|
+
<div class="orgroom-pane" id="orgroom-pane-budgets">
|
|
2759
|
+
<div id="orgroom-budgets-body"><span style="color:var(--dim)">Loading budgets…</span></div>
|
|
2760
|
+
</div>
|
|
2761
|
+
<div class="orgroom-pane" id="orgroom-pane-threads">
|
|
2762
|
+
<div id="orgroom-threads-body"><span style="color:var(--dim)">Loading threads…</span></div>
|
|
2763
|
+
</div>
|
|
2764
|
+
<div class="orgroom-pane" id="orgroom-pane-settings">
|
|
2765
|
+
<div id="orgroom-settings-body"><span style="color:var(--dim)">Loading settings…</span></div>
|
|
2766
|
+
</div>
|
|
2165
2767
|
</div>
|
|
2166
|
-
<iframe id="mm-frame" src="" title="Mastermind"></iframe>
|
|
2167
2768
|
</div>
|
|
2168
2769
|
|
|
2169
2770
|
<!-- ═══════════════════════════ MONOGRAPH OVERLAY ══════════════════ -->
|
|
@@ -3745,205 +4346,2240 @@ function renderMemoryFiles(files) {
|
|
|
3745
4346
|
}
|
|
3746
4347
|
}
|
|
3747
4348
|
|
|
3748
|
-
function renderMemory(data) {
|
|
3749
|
-
const mem = data.memory || {};
|
|
4349
|
+
function renderMemory(data) {
|
|
4350
|
+
const mem = data.memory || {};
|
|
4351
|
+
|
|
4352
|
+
const hnswEl = document.getElementById('hnsw-indicator');
|
|
4353
|
+
if (hnswEl) {
|
|
4354
|
+
hnswEl.innerHTML = mem.hnsw ? '<span class="hnsw-badge"><span class="live-dot amber" style="width:5px;height:5px;"></span>HNSW</span>' : '';
|
|
4355
|
+
}
|
|
4356
|
+
|
|
4357
|
+
const left = document.getElementById('memory-left');
|
|
4358
|
+
const right = document.getElementById('memory-right');
|
|
4359
|
+
if (!left || !right) return;
|
|
4360
|
+
|
|
4361
|
+
// Left: fetch memory files directly from endpoint (works regardless of server version)
|
|
4362
|
+
left.innerHTML = '<div class="section-label">MEMORIES</div><div style="color:var(--dim);font-size:10px;padding:4px 0;">Loading…</div>';
|
|
4363
|
+
const dir = selectedProjectDir || '';
|
|
4364
|
+
fetch(`/api/memory-files${dir ? '?dir=' + encodeURIComponent(dir) : ''}`)
|
|
4365
|
+
.then(r => r.ok ? r.json() : { memories: [] })
|
|
4366
|
+
.then(d => renderMemoryFiles(d.memories || []))
|
|
4367
|
+
.catch(() => renderMemoryFiles([]));
|
|
4368
|
+
|
|
4369
|
+
// Right: storage stats + type counts placeholder
|
|
4370
|
+
let rightHtml = '<div class="section-label">STORAGE</div>';
|
|
4371
|
+
const dbTitle = mem.dbPath ? `title="${escHtml(mem.dbPath)}"` : '';
|
|
4372
|
+
rightHtml += `<div class="mem-stat-row" ${dbTitle}><span class="mem-stat-key">DB SIZE</span><span class="mem-stat-val" style="color:${mem.dbSize ? 'var(--text)' : 'var(--muted)'}">${mem.dbSize ? fmtBytes(mem.dbSize) : '—'}</span></div>`;
|
|
4373
|
+
rightHtml += `<div class="mem-stat-row" style="margin-top:4px;"><span class="mem-stat-key">HNSW INDEX</span><span class="mem-stat-val" style="color:${mem.hnsw ? 'var(--green)' : 'var(--muted)'}">${mem.hnsw ? 'PRESENT' : 'ABSENT'}</span></div>`;
|
|
4374
|
+
rightHtml += '<div class="section-label" style="margin-top:10px;">RUVECTOR</div>';
|
|
4375
|
+
rightHtml += `<div class="mem-stat-row"><span class="mem-stat-key">DB SIZE</span><span class="mem-stat-val" style="color:${mem.ruvectorExists ? 'var(--text)' : 'var(--muted)'}">${mem.ruvectorExists ? fmtBytes(mem.ruvectorSize) : '—'}</span></div>`;
|
|
4376
|
+
rightHtml += `<div class="mem-stat-row" style="margin-top:4px;"><span class="mem-stat-key">PATTERNS</span><span class="mem-stat-val" style="color:${mem.ruvectorPatterns ? 'var(--teal)' : 'var(--muted)'}">${mem.ruvectorPatterns || '—'}</span></div>`;
|
|
4377
|
+
rightHtml += '<div class="section-label" style="margin-top:10px;">BY TYPE</div><div data-by-type>—</div>';
|
|
4378
|
+
|
|
4379
|
+
// Knowledge section integrated into Memory Palace
|
|
4380
|
+
const k = data.knowledge || {};
|
|
4381
|
+
const kChunks = k.chunks || 0;
|
|
4382
|
+
const kSkills = k.skills || 0;
|
|
4383
|
+
const kRecent = k.recent || [];
|
|
4384
|
+
rightHtml += '<div class="section-label" style="margin-top:10px;">KNOWLEDGE</div>';
|
|
4385
|
+
rightHtml += `<div class="mem-stat-row"><span class="mem-stat-key">CHUNKS</span><span class="mem-stat-val" style="color:${kChunks ? 'var(--teal)' : 'var(--muted)'}">${kChunks || '—'}</span></div>`;
|
|
4386
|
+
rightHtml += `<div class="mem-stat-row" style="margin-top:3px;"><span class="mem-stat-key">SKILLS</span><span class="mem-stat-val" style="color:${kSkills ? 'var(--text)' : 'var(--muted)'}">${kSkills || '—'}</span></div>`;
|
|
4387
|
+
if (kRecent.length) {
|
|
4388
|
+
rightHtml += '<div style="margin-top:6px;">';
|
|
4389
|
+
kRecent.slice(0,3).forEach(c => {
|
|
4390
|
+
if (!c) return;
|
|
4391
|
+
const text = c.content || c.text || c.chunk || c.value || '';
|
|
4392
|
+
if (!text) return;
|
|
4393
|
+
rightHtml += `<div class="chunk-preview" title="${escHtml(String(text))}">${escHtml(String(text).slice(0,55))}</div>`;
|
|
4394
|
+
});
|
|
4395
|
+
rightHtml += '</div>';
|
|
4396
|
+
}
|
|
4397
|
+
|
|
4398
|
+
right.innerHTML = rightHtml;
|
|
4399
|
+
}
|
|
4400
|
+
|
|
4401
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
4402
|
+
// MASTERMIND ORGS PANEL
|
|
4403
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
4404
|
+
const orgEventLog = {}; // org name → array of recent events
|
|
4405
|
+
|
|
4406
|
+
function fmtOrgEventType(type) {
|
|
4407
|
+
const map = {
|
|
4408
|
+
'org:start': 'STARTED', 'org:stop': 'STOPPED', 'org:complete': 'COMPLETE',
|
|
4409
|
+
'org:agent:online': 'AGENT ON', 'org:comms': 'COMMS', 'org:checkpoint': 'CHECK',
|
|
4410
|
+
'org:create': 'CREATED',
|
|
4411
|
+
};
|
|
4412
|
+
return map[type] || (typeof type === 'string' ? type.toUpperCase() : 'UNKNOWN');
|
|
4413
|
+
}
|
|
4414
|
+
|
|
4415
|
+
let _orgRenderInFlight = false;
|
|
4416
|
+
async function renderOrgs() {
|
|
4417
|
+
if (_orgRenderInFlight) return;
|
|
4418
|
+
_orgRenderInFlight = true;
|
|
4419
|
+
try {
|
|
4420
|
+
const _orgCtrl = new AbortController();
|
|
4421
|
+
const _orgTimeout = setTimeout(() => _orgCtrl.abort(), 10000);
|
|
4422
|
+
let orgs;
|
|
4423
|
+
try {
|
|
4424
|
+
const r = await fetch('/api/orgs', { signal: _orgCtrl.signal });
|
|
4425
|
+
orgs = await r.json();
|
|
4426
|
+
} finally {
|
|
4427
|
+
clearTimeout(_orgTimeout);
|
|
4428
|
+
}
|
|
4429
|
+
if (!Array.isArray(orgs)) return;
|
|
4430
|
+
const body = document.getElementById('orgs-body');
|
|
4431
|
+
const badge = document.getElementById('orgs-badge');
|
|
4432
|
+
const dot = document.getElementById('orgs-live-dot');
|
|
4433
|
+
const runLabel = document.getElementById('orgs-running-label');
|
|
4434
|
+
if (!body) return;
|
|
4435
|
+
|
|
4436
|
+
badge.textContent = orgs.length;
|
|
4437
|
+
const runningCount = orgs.filter(o => o.running).length;
|
|
4438
|
+
if (runningCount > 0) {
|
|
4439
|
+
dot.classList.add('live');
|
|
4440
|
+
runLabel.textContent = `${runningCount} RUNNING`;
|
|
4441
|
+
runLabel.style.color = 'var(--green)';
|
|
4442
|
+
} else {
|
|
4443
|
+
dot.classList.remove('live');
|
|
4444
|
+
runLabel.textContent = '';
|
|
4445
|
+
}
|
|
4446
|
+
|
|
4447
|
+
if (!orgs.length) {
|
|
4448
|
+
body.innerHTML = `<div class="orgs-empty">
|
|
4449
|
+
<div class="orgs-empty-icon">⬡</div>
|
|
4450
|
+
<div class="orgs-empty-label">NO PERSISTENT ORGS</div>
|
|
4451
|
+
<div class="orgs-empty-sub">Orgs are named agent teams that coordinate across sessions — boss assigns work, specialists execute in parallel.</div>
|
|
4452
|
+
<div class="orgs-empty-cmd">/mastermind:createorg</div>
|
|
4453
|
+
</div>`;
|
|
4454
|
+
return;
|
|
4455
|
+
}
|
|
4456
|
+
|
|
4457
|
+
let html = `<div class="org-row" style="background:rgba(0,0,0,0.2);">
|
|
4458
|
+
<div class="org-header" style="grid-column:1/3">ORG</div>
|
|
4459
|
+
<div class="org-header">ROLES</div>
|
|
4460
|
+
<div class="org-header">TOPOLOGY</div>
|
|
4461
|
+
<div class="org-header">STATUS</div>
|
|
4462
|
+
<div class="org-header">ACTION</div>
|
|
4463
|
+
</div>`;
|
|
4464
|
+
|
|
4465
|
+
for (const org of orgs) {
|
|
4466
|
+
const events = (orgEventLog[org.name] || []).slice(-3);
|
|
4467
|
+
const statusHtml = org.running
|
|
4468
|
+
? `<span class="org-live-badge">LIVE</span>`
|
|
4469
|
+
: `<span style="font-size:9px;color:var(--dim);">IDLE</span>`;
|
|
4470
|
+
// Use data- attributes for org name to avoid JS string injection via onclick
|
|
4471
|
+
const actionHtml = org.running
|
|
4472
|
+
? `<button class="org-btn stop" data-org-stop="${escHtml(org.name)}">STOP</button>`
|
|
4473
|
+
: `<button class="org-btn" data-org-view="${escHtml(org.name)}">ORG ROOM</button>`;
|
|
4474
|
+
|
|
4475
|
+
html += `<div class="org-row" data-org="${escHtml(org.name)}">
|
|
4476
|
+
<div class="org-cell" style="padding-left:10px;">
|
|
4477
|
+
<div class="org-status-dot ${org.running ? 'running' : ''}"></div>
|
|
4478
|
+
</div>
|
|
4479
|
+
<div class="org-cell">
|
|
4480
|
+
<div class="org-name">${escHtml(org.name)}</div>
|
|
4481
|
+
<div class="org-goal">${escHtml((org.goal || '').slice(0, 70))}${(org.goal || '').length > 70 ? '…' : ''}</div>
|
|
4482
|
+
</div>
|
|
4483
|
+
<div class="org-cell" style="color:var(--teal)">${org.roles || 0}</div>
|
|
4484
|
+
<div class="org-cell" style="color:var(--muted)">${escHtml(org.topology || 'hierarchical')}</div>
|
|
4485
|
+
<div class="org-cell">${statusHtml}</div>
|
|
4486
|
+
<div class="org-cell">${actionHtml}</div>
|
|
4487
|
+
</div>`;
|
|
4488
|
+
|
|
4489
|
+
if (events.length) {
|
|
4490
|
+
for (const ev of events) {
|
|
4491
|
+
const t = new Date(ev.ts).toLocaleTimeString('en', { hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit' });
|
|
4492
|
+
const rawDetail = ev.role ? ` (${ev.role})` : ev.msg ? ` — ${ev.msg}` : ev.agent ? ` → ${ev.agent}` : '';
|
|
4493
|
+
html += `<div class="org-event-log">
|
|
4494
|
+
<span class="org-event-time">${t}</span>
|
|
4495
|
+
<span class="org-event-type">${escHtml(fmtOrgEventType(ev.type))}</span>
|
|
4496
|
+
<span>${escHtml(rawDetail)}</span>
|
|
4497
|
+
</div>`;
|
|
4498
|
+
}
|
|
4499
|
+
}
|
|
4500
|
+
}
|
|
4501
|
+
|
|
4502
|
+
body.innerHTML = html;
|
|
4503
|
+
|
|
4504
|
+
// Attach event handlers via addEventListener (avoids onclick JS injection)
|
|
4505
|
+
body.querySelectorAll('[data-org-stop]').forEach(btn => {
|
|
4506
|
+
btn.addEventListener('click', () => stopOrg(btn.dataset.orgStop));
|
|
4507
|
+
});
|
|
4508
|
+
body.querySelectorAll('[data-org-view]').forEach(btn => {
|
|
4509
|
+
btn.addEventListener('click', () => openMastermindForOrg(btn.dataset.orgView));
|
|
4510
|
+
});
|
|
4511
|
+
} catch { /* server may not be ready yet */ } finally { _orgRenderInFlight = false; }
|
|
4512
|
+
}
|
|
4513
|
+
|
|
4514
|
+
async function stopOrg(name) {
|
|
4515
|
+
try {
|
|
4516
|
+
await fetch(`/api/orgs/${encodeURIComponent(name)}/stop`, { method: 'POST' });
|
|
4517
|
+
setTimeout(renderOrgs, 500);
|
|
4518
|
+
} catch {}
|
|
4519
|
+
}
|
|
4520
|
+
|
|
4521
|
+
function openMastermindForOrg(name) {
|
|
4522
|
+
openOrgRoom(name);
|
|
4523
|
+
}
|
|
4524
|
+
|
|
4525
|
+
// Debounce timer for SSE-triggered renders (avoid one HTTP fetch per event)
|
|
4526
|
+
let _orgRenderDebounce = null;
|
|
4527
|
+
|
|
4528
|
+
// Listen for org events from the SSE stream and update the event log
|
|
4529
|
+
function handleOrgEvent(ev) {
|
|
4530
|
+
const orgName = ev.org;
|
|
4531
|
+
if (!orgName || typeof orgName !== 'string') return;
|
|
4532
|
+
// Guard against prototype-polluting keys from attacker-controlled SSE payloads
|
|
4533
|
+
if (orgName === '__proto__' || orgName === 'constructor' || orgName === 'prototype') return;
|
|
4534
|
+
if (!Object.prototype.hasOwnProperty.call(orgEventLog, orgName)) orgEventLog[orgName] = [];
|
|
4535
|
+
orgEventLog[orgName].push(ev);
|
|
4536
|
+
if (orgEventLog[orgName].length > 20) orgEventLog[orgName].shift();
|
|
4537
|
+
// Debounce: re-render at most once per 500ms on SSE bursts
|
|
4538
|
+
const panel = document.getElementById('panel-orgs');
|
|
4539
|
+
if (panel && panel.classList.contains('open')) {
|
|
4540
|
+
clearTimeout(_orgRenderDebounce);
|
|
4541
|
+
_orgRenderDebounce = setTimeout(renderOrgs, 500);
|
|
4542
|
+
}
|
|
4543
|
+
}
|
|
4544
|
+
|
|
4545
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
4546
|
+
// ORG ROOM OVERLAY
|
|
4547
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
4548
|
+
let _orgroomCurrentOrg = null;
|
|
4549
|
+
let _orgroomCurrentTab = 'chart';
|
|
4550
|
+
let _orgroomData = null;
|
|
4551
|
+
|
|
4552
|
+
window.openOrgRoom = function(orgName) {
|
|
4553
|
+
_orgroomCurrentOrg = orgName;
|
|
4554
|
+
_orgroomCurrentTab = 'chart';
|
|
4555
|
+
document.getElementById('orgroom-org-label').textContent = orgName.toUpperCase();
|
|
4556
|
+
document.getElementById('orgroom-overlay').classList.add('open');
|
|
4557
|
+
document.querySelectorAll('.orgroom-tab').forEach((t, i) => t.classList.toggle('active', i === 0));
|
|
4558
|
+
document.querySelectorAll('.orgroom-pane').forEach(p => p.classList.remove('active'));
|
|
4559
|
+
document.getElementById('orgroom-pane-chart').classList.add('active');
|
|
4560
|
+
loadOrgRoomData(orgName);
|
|
4561
|
+
};
|
|
4562
|
+
|
|
4563
|
+
window.closeOrgRoom = function() {
|
|
4564
|
+
document.getElementById('orgroom-overlay').classList.remove('open');
|
|
4565
|
+
_orgroomCurrentOrg = null;
|
|
4566
|
+
_orgroomData = null;
|
|
4567
|
+
};
|
|
4568
|
+
|
|
4569
|
+
const _orgroomTabLabels = {
|
|
4570
|
+
chart: 'ORG CHART', heartbeats: 'HEARTBEATS', tasks: 'TASK BOARD',
|
|
4571
|
+
costs: 'COSTS', routines: 'ROUTINES', inbox: 'INBOX', projects: 'PROJECTS', activity: 'ACTIVITY'
|
|
4572
|
+
};
|
|
4573
|
+
|
|
4574
|
+
window.switchOrgTab = function(tab) {
|
|
4575
|
+
_orgroomCurrentTab = tab;
|
|
4576
|
+
document.querySelectorAll('.orgroom-tab').forEach(t => {
|
|
4577
|
+
t.classList.toggle('active', t.textContent === (_orgroomTabLabels[tab] || tab.toUpperCase()));
|
|
4578
|
+
});
|
|
4579
|
+
document.querySelectorAll('.orgroom-pane').forEach(p => p.classList.remove('active'));
|
|
4580
|
+
const pane = document.getElementById(`orgroom-pane-${tab}`);
|
|
4581
|
+
if (pane) pane.classList.add('active');
|
|
4582
|
+
if (_orgroomData) renderOrgRoomTab(tab, _orgroomData);
|
|
4583
|
+
};
|
|
4584
|
+
|
|
4585
|
+
async function loadOrgRoomData(orgName) {
|
|
4586
|
+
try {
|
|
4587
|
+
const [mainR, actR, projR, skillsR, membersR, adaptersR, envsR, wsR, issuesR, healthR, invitesR, pluginsR, myIssuesR, agentsR, approvalsR, secretsR, joinQueueR, budgetsR, threadsR] = await Promise.all([
|
|
4588
|
+
fetch(`/api/org/${encodeURIComponent(orgName)}`),
|
|
4589
|
+
fetch(`/api/org/${encodeURIComponent(orgName)}/activity`).catch(() => null),
|
|
4590
|
+
fetch(`/api/org/${encodeURIComponent(orgName)}/projects`).catch(() => null),
|
|
4591
|
+
fetch(`/api/org/${encodeURIComponent(orgName)}/skills`).catch(() => null),
|
|
4592
|
+
fetch(`/api/org/${encodeURIComponent(orgName)}/members`).catch(() => null),
|
|
4593
|
+
fetch(`/api/org/${encodeURIComponent(orgName)}/adapters`).catch(() => null),
|
|
4594
|
+
fetch(`/api/org/${encodeURIComponent(orgName)}/environments`).catch(() => null),
|
|
4595
|
+
fetch(`/api/org/${encodeURIComponent(orgName)}/workspaces`).catch(() => null),
|
|
4596
|
+
fetch(`/api/org/${encodeURIComponent(orgName)}/issues`).catch(() => null),
|
|
4597
|
+
fetch(`/api/org/${encodeURIComponent(orgName)}/health`).catch(() => null),
|
|
4598
|
+
fetch(`/api/org/${encodeURIComponent(orgName)}/invites`).catch(() => null),
|
|
4599
|
+
fetch(`/api/org/${encodeURIComponent(orgName)}/plugins`).catch(() => null),
|
|
4600
|
+
fetch(`/api/org/${encodeURIComponent(orgName)}/my-issues`).catch(() => null),
|
|
4601
|
+
fetch(`/api/org/${encodeURIComponent(orgName)}/agents`).catch(() => null),
|
|
4602
|
+
fetch(`/api/org/${encodeURIComponent(orgName)}/approvals`).catch(() => null),
|
|
4603
|
+
fetch(`/api/org/${encodeURIComponent(orgName)}/secrets`).catch(() => null),
|
|
4604
|
+
fetch(`/api/org/${encodeURIComponent(orgName)}/join-requests`).catch(() => null),
|
|
4605
|
+
fetch(`/api/org/${encodeURIComponent(orgName)}/budgets`).catch(() => null),
|
|
4606
|
+
fetch(`/api/org/${encodeURIComponent(orgName)}/threads`).catch(() => null),
|
|
4607
|
+
]);
|
|
4608
|
+
if (!mainR.ok) throw new Error('not found');
|
|
4609
|
+
_orgroomData = await mainR.json();
|
|
4610
|
+
_orgroomData._activity = actR && actR.ok ? await actR.json() : [];
|
|
4611
|
+
_orgroomData._projects = projR && projR.ok ? await projR.json() : [];
|
|
4612
|
+
_orgroomData._skills = skillsR && skillsR.ok ? await skillsR.json() : { skills: [], role_skill_map: {} };
|
|
4613
|
+
_orgroomData._members = membersR && membersR.ok ? await membersR.json() : { members: [], join_requests: [] };
|
|
4614
|
+
_orgroomData._adapters = adaptersR && adaptersR.ok ? await adaptersR.json() : { adapters: [], default_adapter: '' };
|
|
4615
|
+
_orgroomData._environments = envsR && envsR.ok ? await envsR.json() : { environments: [], default_env: null };
|
|
4616
|
+
_orgroomData._workspaces = wsR && wsR.ok ? await wsR.json() : { workspaces: [] };
|
|
4617
|
+
_orgroomData._issues = issuesR && issuesR.ok ? await issuesR.json() : { issues: [] };
|
|
4618
|
+
_orgroomData._health = healthR && healthR.ok ? await healthR.json() : null;
|
|
4619
|
+
_orgroomData._invites = invitesR && invitesR.ok ? await invitesR.json() : { invites: [], join_requests: [] };
|
|
4620
|
+
_orgroomData._plugins = pluginsR && pluginsR.ok ? await pluginsR.json() : { plugins: [] };
|
|
4621
|
+
_orgroomData._myissues = myIssuesR && myIssuesR.ok ? await myIssuesR.json() : { issues: [] };
|
|
4622
|
+
_orgroomData._agents = agentsR && agentsR.ok ? await agentsR.json() : { agents: [] };
|
|
4623
|
+
_orgroomData._approvals = approvalsR && approvalsR.ok ? await approvalsR.json() : { approvals: [], pending: 0 };
|
|
4624
|
+
_orgroomData._secrets = secretsR && secretsR.ok ? await secretsR.json() : { secrets: [] };
|
|
4625
|
+
_orgroomData._joinRequests = joinQueueR && joinQueueR.ok ? await joinQueueR.json() : { requests: [], pending: 0 };
|
|
4626
|
+
_orgroomData._budgets = budgetsR && budgetsR.ok ? await budgetsR.json() : { org_budget: {}, agent_budgets: {}, agents: [] };
|
|
4627
|
+
_orgroomData._threads = threadsR && threadsR.ok ? await threadsR.json() : { threads: [] };
|
|
4628
|
+
renderOrgRoomTab(_orgroomCurrentTab, _orgroomData);
|
|
4629
|
+
} catch(e) {
|
|
4630
|
+
const body = document.getElementById('orgroom-body');
|
|
4631
|
+
if (body) body.innerHTML = `<div style="color:var(--dim);padding:20px;">Could not load org data for "${escHtml(orgName)}". Is the org created?</div>`;
|
|
4632
|
+
}
|
|
4633
|
+
}
|
|
4634
|
+
|
|
4635
|
+
function renderOrgRoomTab(tab, data) {
|
|
4636
|
+
if (tab === 'chart') renderOrgChart(data);
|
|
4637
|
+
else if (tab === 'heartbeats') renderOrgHeartbeats(data);
|
|
4638
|
+
else if (tab === 'tasks') renderOrgTasks(data);
|
|
4639
|
+
else if (tab === 'costs') renderOrgCosts(data);
|
|
4640
|
+
else if (tab === 'routines') renderOrgRoutines(data);
|
|
4641
|
+
else if (tab === 'inbox') renderOrgInbox(data);
|
|
4642
|
+
else if (tab === 'projects') renderOrgProjects(data);
|
|
4643
|
+
else if (tab === 'activity') renderOrgActivity(data);
|
|
4644
|
+
else if (tab === 'live') renderOrgLive(data);
|
|
4645
|
+
else if (tab === 'charts') renderOrgCharts(data);
|
|
4646
|
+
else if (tab === 'members') renderOrgMembers(data);
|
|
4647
|
+
else if (tab === 'skills') renderOrgSkills(data);
|
|
4648
|
+
else if (tab === 'goals') renderOrgGoals(data);
|
|
4649
|
+
else if (tab === 'workspaces') renderOrgWorkspaces(data);
|
|
4650
|
+
else if (tab === 'board') renderOrgBoard(data);
|
|
4651
|
+
else if (tab === 'health') renderOrgHealth(data);
|
|
4652
|
+
else if (tab === 'invites') renderOrgInvites(data);
|
|
4653
|
+
else if (tab === 'plugins') renderOrgPlugins(data);
|
|
4654
|
+
else if (tab === 'myissues') renderOrgMyIssues(data);
|
|
4655
|
+
else if (tab === 'agents') renderOrgAgents(data);
|
|
4656
|
+
else if (tab === 'approvals') renderOrgApprovals(data);
|
|
4657
|
+
else if (tab === 'secrets') renderOrgSecrets(data);
|
|
4658
|
+
else if (tab === 'envs') renderOrgEnvs(data);
|
|
4659
|
+
else if (tab === 'access') renderOrgAccess(data);
|
|
4660
|
+
else if (tab === 'issues') renderOrgIssues(data);
|
|
4661
|
+
else if (tab === 'joinqueue') renderOrgJoinQueue(data);
|
|
4662
|
+
else if (tab === 'budgets') renderOrgBudgets(data);
|
|
4663
|
+
else if (tab === 'threads') renderOrgThreads(data);
|
|
4664
|
+
else if (tab === 'settings') renderOrgSettings(data);
|
|
4665
|
+
}
|
|
4666
|
+
|
|
4667
|
+
function renderOrgChart(data) {
|
|
4668
|
+
const el = document.getElementById('orgroom-chart-svg');
|
|
4669
|
+
if (!el) return;
|
|
4670
|
+
const roles = (data.config && data.config.roles) || [];
|
|
4671
|
+
if (!roles.length) { el.innerHTML = '<span style="color:var(--dim)">No roles defined in this org.</span>'; return; }
|
|
4672
|
+
|
|
4673
|
+
// Build adjacency: parent → children
|
|
4674
|
+
const children = {};
|
|
4675
|
+
const rootRoles = [];
|
|
4676
|
+
roles.forEach(r => {
|
|
4677
|
+
if (!r.reports_to) rootRoles.push(r);
|
|
4678
|
+
else {
|
|
4679
|
+
if (!children[r.reports_to]) children[r.reports_to] = [];
|
|
4680
|
+
children[r.reports_to].push(r);
|
|
4681
|
+
}
|
|
4682
|
+
});
|
|
4683
|
+
|
|
4684
|
+
const CARD_W = 160, CARD_H = 56, GAP_X = 24, GAP_Y = 60, PAD = 20;
|
|
4685
|
+
const positions = {};
|
|
4686
|
+
let maxX = 0, maxY = 0;
|
|
4687
|
+
|
|
4688
|
+
function subtreeWidth(roleId) {
|
|
4689
|
+
const kids = children[roleId] || [];
|
|
4690
|
+
if (!kids.length) return CARD_W;
|
|
4691
|
+
const total = kids.reduce((s, k) => s + subtreeWidth(k.id), 0) + (kids.length - 1) * GAP_X;
|
|
4692
|
+
return Math.max(CARD_W, total);
|
|
4693
|
+
}
|
|
4694
|
+
|
|
4695
|
+
function layout(roleId, x, y) {
|
|
4696
|
+
positions[roleId] = { x, y };
|
|
4697
|
+
maxX = Math.max(maxX, x + CARD_W);
|
|
4698
|
+
maxY = Math.max(maxY, y + CARD_H);
|
|
4699
|
+
const kids = children[roleId] || [];
|
|
4700
|
+
if (!kids.length) return;
|
|
4701
|
+
const totalW = kids.reduce((s, k) => s + subtreeWidth(k.id), 0) + (kids.length - 1) * GAP_X;
|
|
4702
|
+
let cx = x + CARD_W / 2 - totalW / 2;
|
|
4703
|
+
kids.forEach(k => {
|
|
4704
|
+
const kw = subtreeWidth(k.id);
|
|
4705
|
+
layout(k.id, cx, y + CARD_H + GAP_Y);
|
|
4706
|
+
cx += kw + GAP_X;
|
|
4707
|
+
});
|
|
4708
|
+
}
|
|
4709
|
+
|
|
4710
|
+
// Layout each root
|
|
4711
|
+
let rootX = PAD;
|
|
4712
|
+
rootRoles.forEach(r => {
|
|
4713
|
+
const sw = subtreeWidth(r.id);
|
|
4714
|
+
layout(r.id, rootX, PAD);
|
|
4715
|
+
rootX += sw + GAP_X;
|
|
4716
|
+
});
|
|
4717
|
+
|
|
4718
|
+
const state = (data.state && data.state.agents) || {};
|
|
4719
|
+
const svgW = maxX + PAD, svgH = maxY + PAD;
|
|
4720
|
+
let svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${svgW}" height="${svgH}" style="min-width:${svgW}px">`;
|
|
4721
|
+
|
|
4722
|
+
// Draw edges
|
|
4723
|
+
roles.forEach(r => {
|
|
4724
|
+
if (!r.reports_to || !positions[r.id] || !positions[r.reports_to]) return;
|
|
4725
|
+
const from = positions[r.reports_to];
|
|
4726
|
+
const to = positions[r.id];
|
|
4727
|
+
const fx = from.x + CARD_W / 2, fy = from.y + CARD_H;
|
|
4728
|
+
const tx = to.x + CARD_W / 2, ty = to.y;
|
|
4729
|
+
const my = (fy + ty) / 2;
|
|
4730
|
+
svg += `<path class="oc-edge" d="M${fx},${fy} C${fx},${my} ${tx},${my} ${tx},${ty}"/>`;
|
|
4731
|
+
});
|
|
4732
|
+
|
|
4733
|
+
// Draw cards
|
|
4734
|
+
roles.forEach(r => {
|
|
4735
|
+
const p = positions[r.id];
|
|
4736
|
+
if (!p) return;
|
|
4737
|
+
const isRoot = !r.reports_to;
|
|
4738
|
+
const agentState = state[r.id] || {};
|
|
4739
|
+
const status = agentState.status || 'idle';
|
|
4740
|
+
const statusColor = status === 'running' ? '#00e6b4' : status === 'paused' ? '#ffb400' : status === 'error' ? '#ff5050' : '#334';
|
|
4741
|
+
svg += `<rect x="${p.x}" y="${p.y}" width="${CARD_W}" height="${CARD_H}" rx="4"
|
|
4742
|
+
fill="rgba(0,40,30,0.9)" stroke="${isRoot ? 'rgba(0,230,180,0.9)' : 'rgba(0,200,160,0.4)'}" stroke-width="${isRoot ? 2 : 1}"/>`;
|
|
4743
|
+
svg += `<circle cx="${p.x + CARD_W - 12}" cy="${p.y + 12}" r="5" fill="${statusColor}"/>`;
|
|
4744
|
+
svg += `<text x="${p.x + 10}" y="${p.y + 20}" class="oc-label">${escHtml(r.title)}</text>`;
|
|
4745
|
+
svg += `<text x="${p.x + 10}" y="${p.y + 34}" class="oc-sub">${escHtml(r.agent_type || '')}</text>`;
|
|
4746
|
+
svg += `<text x="${p.x + 10}" y="${p.y + 47}" class="oc-sub" fill="rgba(0,200,160,0.5)">${escHtml(status)}</text>`;
|
|
4747
|
+
});
|
|
4748
|
+
|
|
4749
|
+
svg += '</svg>';
|
|
4750
|
+
el.innerHTML = svg;
|
|
4751
|
+
}
|
|
4752
|
+
|
|
4753
|
+
function renderOrgHeartbeats(data) {
|
|
4754
|
+
const el = document.getElementById('orgroom-heartbeats-body');
|
|
4755
|
+
if (!el) return;
|
|
4756
|
+
const agents = (data.state && data.state.agents) || {};
|
|
4757
|
+
const roles = (data.config && data.config.roles) || [];
|
|
4758
|
+
|
|
4759
|
+
const statusDot = s => `<span class="orgroom-status-dot ${s || 'idle'}"></span>`;
|
|
4760
|
+
const fmtTime = t => { if (!t) return '—'; const d = new Date(t); return d.toLocaleTimeString('en', { hour12: false }); };
|
|
4761
|
+
|
|
4762
|
+
let html = `<table class="orgroom-table">
|
|
4763
|
+
<tr><th>ROLE</th><th>AGENT TYPE</th><th>STATUS</th><th>LAST HEARTBEAT</th><th>SOURCE</th><th>TOKENS IN</th><th>TOKENS OUT</th></tr>`;
|
|
4764
|
+
|
|
4765
|
+
roles.forEach(r => {
|
|
4766
|
+
const s = agents[r.id] || {};
|
|
4767
|
+
const status = s.status || 'idle';
|
|
4768
|
+
html += `<tr>
|
|
4769
|
+
<td>${statusDot(status)}<span style="color:rgba(0,230,180,0.9)">${escHtml(r.id)}</span><div style="font-size:9px;color:var(--dim)">${escHtml(r.title)}</div></td>
|
|
4770
|
+
<td style="color:var(--muted)">${escHtml(r.agent_type || '')}</td>
|
|
4771
|
+
<td>${escHtml(status)}</td>
|
|
4772
|
+
<td style="color:var(--dim)">${fmtTime(s.last_heartbeat)}</td>
|
|
4773
|
+
<td style="color:var(--dim)">${escHtml(s.heartbeat_source || '—')}</td>
|
|
4774
|
+
<td style="color:rgba(0,200,160,0.7)">${(s.tokens_in || 0).toLocaleString()}</td>
|
|
4775
|
+
<td style="color:rgba(0,200,160,0.7)">${(s.tokens_out || 0).toLocaleString()}</td>
|
|
4776
|
+
</tr>`;
|
|
4777
|
+
});
|
|
4778
|
+
|
|
4779
|
+
if (!roles.length) html += `<tr><td colspan="7" style="color:var(--dim);text-align:center;padding:16px">No agents defined in this org.</td></tr>`;
|
|
4780
|
+
html += '</table>';
|
|
4781
|
+
|
|
4782
|
+
// Recent heartbeat events from orgEventLog
|
|
4783
|
+
const orgName = _orgroomCurrentOrg;
|
|
4784
|
+
const recentEvents = ((orgEventLog[orgName] || []).filter(e => e.type && e.type.startsWith('org:heartbeat'))).slice(-10);
|
|
4785
|
+
if (recentEvents.length) {
|
|
4786
|
+
html += `<div style="margin-top:16px;font-size:9px;color:var(--dim);letter-spacing:0.1em">RECENT HEARTBEAT EVENTS</div>`;
|
|
4787
|
+
html += `<div style="margin-top:6px;display:flex;flex-direction:column;gap:3px">`;
|
|
4788
|
+
recentEvents.slice().reverse().forEach(ev => {
|
|
4789
|
+
const t = new Date(ev.ts).toLocaleTimeString('en', { hour12: false });
|
|
4790
|
+
html += `<div style="display:flex;gap:8px;font-size:10px"><span style="color:var(--dim)">${t}</span><span style="color:rgba(0,200,160,0.7)">${escHtml(ev.type)}</span><span>${escHtml(ev.role || '')} ${escHtml(ev.title ? '· ' + ev.title : '')}</span></div>`;
|
|
4791
|
+
});
|
|
4792
|
+
html += '</div>';
|
|
4793
|
+
}
|
|
4794
|
+
el.innerHTML = html;
|
|
4795
|
+
}
|
|
4796
|
+
|
|
4797
|
+
function renderOrgTasks(data) {
|
|
4798
|
+
const el = document.getElementById('orgroom-tasks-body');
|
|
4799
|
+
if (!el) return;
|
|
4800
|
+
const tasks = data.tasks || { todo: [], doing: [], done: [] };
|
|
4801
|
+
|
|
4802
|
+
const renderCol = (title, cards, color) => {
|
|
4803
|
+
if (!cards.length) return `<div style="color:var(--dim);font-size:10px;padding:8px">— empty —</div>`;
|
|
4804
|
+
return cards.map(c => `<div style="background:rgba(0,0,0,0.3);border-left:3px solid ${color};padding:6px 10px;margin-bottom:4px;border-radius:2px">
|
|
4805
|
+
<div style="font-size:10px;color:rgba(255,255,255,0.85)">${escHtml(c.title || c.id)}</div>
|
|
4806
|
+
${c.role ? `<div style="font-size:9px;color:var(--dim)">${escHtml(c.role)}</div>` : ''}
|
|
4807
|
+
</div>`).join('');
|
|
4808
|
+
};
|
|
4809
|
+
|
|
4810
|
+
el.innerHTML = `<div style="display:grid;grid-template-columns:repeat(3,1fr);gap:12px">
|
|
4811
|
+
<div>
|
|
4812
|
+
<div style="font-size:9px;color:var(--dim);letter-spacing:0.1em;margin-bottom:8px">TODO (${tasks.todo.length})</div>
|
|
4813
|
+
${renderCol('todo', tasks.todo, 'rgba(100,100,150,0.8)')}
|
|
4814
|
+
</div>
|
|
4815
|
+
<div>
|
|
4816
|
+
<div style="font-size:9px;color:rgba(0,200,160,0.7);letter-spacing:0.1em;margin-bottom:8px">DOING (${tasks.doing.length})</div>
|
|
4817
|
+
${renderCol('doing', tasks.doing, 'rgba(0,200,160,0.8)')}
|
|
4818
|
+
</div>
|
|
4819
|
+
<div>
|
|
4820
|
+
<div style="font-size:9px;color:rgba(100,200,100,0.7);letter-spacing:0.1em;margin-bottom:8px">DONE (${tasks.done.length})</div>
|
|
4821
|
+
${renderCol('done', tasks.done, 'rgba(100,200,100,0.6)')}
|
|
4822
|
+
</div>
|
|
4823
|
+
</div>`;
|
|
4824
|
+
}
|
|
4825
|
+
|
|
4826
|
+
function renderOrgCosts(data) {
|
|
4827
|
+
const el = document.getElementById('orgroom-costs-body');
|
|
4828
|
+
if (!el) return;
|
|
4829
|
+
const state = (data.state && data.state.agents) || {};
|
|
4830
|
+
const roles = (data.config && data.config.roles) || [];
|
|
4831
|
+
const budget = (data.config && data.config.run_config && data.config.run_config.budget_tokens) || 0;
|
|
4832
|
+
const alertThreshold = (data.config && data.config.run_config && data.config.run_config.alert_threshold) || 0.8;
|
|
4833
|
+
|
|
4834
|
+
let totalIn = 0, totalOut = 0;
|
|
4835
|
+
const rows = roles.map(r => {
|
|
4836
|
+
const s = state[r.id] || {};
|
|
4837
|
+
const tokIn = s.tokens_in || 0;
|
|
4838
|
+
const tokOut = s.tokens_out || 0;
|
|
4839
|
+
totalIn += tokIn; totalOut += tokOut;
|
|
4840
|
+
// Approximate cost: $3/1M input, $15/1M output (Sonnet-class)
|
|
4841
|
+
const cost = (tokIn * 3 + tokOut * 15) / 1_000_000;
|
|
4842
|
+
return { id: r.id, title: r.title, tokIn, tokOut, cost };
|
|
4843
|
+
});
|
|
4844
|
+
|
|
4845
|
+
const totalCost = (totalIn * 3 + totalOut * 15) / 1_000_000;
|
|
4846
|
+
const totalTok = totalIn + totalOut;
|
|
4847
|
+
const usedPct = budget > 0 ? Math.min(100, (totalTok / budget) * 100) : 0;
|
|
4848
|
+
const barClass = usedPct > 90 ? 'danger' : usedPct > (alertThreshold * 100) ? 'warn' : '';
|
|
4849
|
+
|
|
4850
|
+
let html = '';
|
|
4851
|
+
|
|
4852
|
+
if (budget > 0) {
|
|
4853
|
+
html += `<div style="margin-bottom:16px;background:rgba(0,0,0,0.3);border-radius:4px;padding:12px">
|
|
4854
|
+
<div style="display:flex;justify-content:space-between;margin-bottom:6px">
|
|
4855
|
+
<span style="font-size:9px;color:var(--dim);letter-spacing:0.1em">BUDGET UTILIZATION</span>
|
|
4856
|
+
<span style="font-size:10px;color:rgba(0,230,180,0.9)">${usedPct.toFixed(1)}%</span>
|
|
4857
|
+
</div>
|
|
4858
|
+
<div class="cost-bar-wrap"><div class="cost-bar ${barClass}" style="width:${usedPct}%"></div></div>
|
|
4859
|
+
<div style="display:flex;justify-content:space-between;margin-top:4px;font-size:9px;color:var(--dim)">
|
|
4860
|
+
<span>${totalTok.toLocaleString()} tokens used</span>
|
|
4861
|
+
<span>${budget.toLocaleString()} token budget</span>
|
|
4862
|
+
</div>
|
|
4863
|
+
</div>`;
|
|
4864
|
+
}
|
|
4865
|
+
|
|
4866
|
+
html += `<table class="orgroom-table">
|
|
4867
|
+
<tr><th>AGENT</th><th>TOKENS IN</th><th>TOKENS OUT</th><th>EST. COST</th></tr>`;
|
|
4868
|
+
|
|
4869
|
+
rows.forEach(r => {
|
|
4870
|
+
const agentTok = r.tokIn + r.tokOut;
|
|
4871
|
+
const agentPct = totalTok > 0 ? (agentTok / totalTok) * 100 : 0;
|
|
4872
|
+
html += `<tr>
|
|
4873
|
+
<td><div style="color:rgba(0,230,180,0.9)">${escHtml(r.id)}</div><div style="font-size:9px;color:var(--dim)">${escHtml(r.title)}</div></td>
|
|
4874
|
+
<td style="color:var(--muted)">${r.tokIn.toLocaleString()}</td>
|
|
4875
|
+
<td style="color:var(--muted)">${r.tokOut.toLocaleString()}</td>
|
|
4876
|
+
<td style="color:rgba(0,200,160,0.8)">$${r.cost.toFixed(4)}</td>
|
|
4877
|
+
</tr>
|
|
4878
|
+
<tr><td colspan="4" style="padding:2px 8px 8px"><div class="cost-bar-wrap"><div class="cost-bar" style="width:${agentPct}%"></div></div></td></tr>`;
|
|
4879
|
+
});
|
|
4880
|
+
|
|
4881
|
+
html += `<tr style="border-top:1px solid rgba(0,200,160,0.2)">
|
|
4882
|
+
<td style="color:rgba(0,230,180,0.9);font-weight:bold">TOTAL</td>
|
|
4883
|
+
<td>${totalIn.toLocaleString()}</td>
|
|
4884
|
+
<td>${totalOut.toLocaleString()}</td>
|
|
4885
|
+
<td style="color:rgba(0,200,160,0.9);font-weight:bold">$${totalCost.toFixed(4)}</td>
|
|
4886
|
+
</tr>`;
|
|
4887
|
+
html += '</table>';
|
|
4888
|
+
|
|
4889
|
+
if (!roles.length) html = '<div style="color:var(--dim);padding:16px">No agents defined in this org.</div>';
|
|
4890
|
+
el.innerHTML = html;
|
|
4891
|
+
}
|
|
4892
|
+
|
|
4893
|
+
function renderOrgRoutines(data) {
|
|
4894
|
+
const el = document.getElementById('orgroom-routines-body');
|
|
4895
|
+
if (!el) return;
|
|
4896
|
+
const routines = data.routines || [];
|
|
4897
|
+
|
|
4898
|
+
if (!routines.length) {
|
|
4899
|
+
el.innerHTML = `<div style="color:var(--dim);padding:20px;text-align:center">
|
|
4900
|
+
<div style="font-size:24px;margin-bottom:8px">⟳</div>
|
|
4901
|
+
<div>No routines configured for this org.</div>
|
|
4902
|
+
<div style="font-size:10px;margin-top:8px;color:rgba(0,200,160,0.5)">Add one with /mastermind:routines --org ${escHtml(_orgroomCurrentOrg)}</div>
|
|
4903
|
+
</div>`;
|
|
4904
|
+
return;
|
|
4905
|
+
}
|
|
4906
|
+
|
|
4907
|
+
const fmtStatus = s => {
|
|
4908
|
+
const colors = { active: 'rgba(0,230,180,0.9)', paused: 'rgba(255,180,0,0.8)', stopped: 'rgba(255,80,80,0.7)' };
|
|
4909
|
+
return `<span style="color:${colors[s] || 'var(--dim)'}">● ${s || 'unknown'}</span>`;
|
|
4910
|
+
};
|
|
4911
|
+
|
|
4912
|
+
let html = `<div style="display:flex;flex-direction:column;gap:10px">`;
|
|
4913
|
+
routines.forEach(r => {
|
|
4914
|
+
html += `<div style="background:rgba(0,0,0,0.3);border:1px solid rgba(0,200,160,0.15);border-radius:4px;padding:12px">
|
|
4915
|
+
<div style="display:flex;justify-content:space-between;align-items:start;margin-bottom:6px">
|
|
4916
|
+
<div>
|
|
4917
|
+
<div style="color:rgba(0,230,180,0.9);font-size:11px">${escHtml(r.name || r.id)}</div>
|
|
4918
|
+
<div style="font-size:9px;color:var(--dim)">${escHtml(r.id)}</div>
|
|
4919
|
+
</div>
|
|
4920
|
+
${fmtStatus(r.status)}
|
|
4921
|
+
</div>
|
|
4922
|
+
<div style="font-size:10px;margin-bottom:4px;color:rgba(255,255,255,0.7)">${escHtml(r.task_title || '')}</div>
|
|
4923
|
+
<div style="display:flex;gap:16px;font-size:9px;color:var(--dim)">
|
|
4924
|
+
<span>agent: <span style="color:rgba(0,200,160,0.7)">${escHtml(r.agent_id || '')}</span></span>
|
|
4925
|
+
<span>schedule: <span style="color:rgba(0,200,160,0.7)">${escHtml(r.schedule || '')}</span></span>
|
|
4926
|
+
<span>last: ${escHtml(r.last_run ? new Date(r.last_run).toLocaleDateString() : 'never')}</span>
|
|
4927
|
+
</div>
|
|
4928
|
+
<div style="display:flex;gap:16px;font-size:9px;color:var(--dim);margin-top:2px">
|
|
4929
|
+
<span>concurrency: ${escHtml(r.concurrency || 'coalesce_if_active')}</span>
|
|
4930
|
+
<span>catchup: ${escHtml(r.catchup || 'skip_missed')}</span>
|
|
4931
|
+
</div>
|
|
4932
|
+
</div>`;
|
|
4933
|
+
});
|
|
4934
|
+
html += '</div>';
|
|
4935
|
+
el.innerHTML = html;
|
|
4936
|
+
}
|
|
4937
|
+
|
|
4938
|
+
function renderOrgInbox(data) {
|
|
4939
|
+
const el = document.getElementById('orgroom-inbox-body');
|
|
4940
|
+
if (!el) return;
|
|
4941
|
+
const approvals = (data.approvals || []).filter(a => a.status === 'pending');
|
|
4942
|
+
const agents = (data.state && data.state.agents) || {};
|
|
4943
|
+
const runningAgents = Object.entries(agents).filter(([, s]) => s.status === 'running');
|
|
4944
|
+
const roles = (data.config && data.config.roles) || [];
|
|
4945
|
+
const budget = (data.config && data.config.run_config && data.config.run_config.budget_tokens) || 0;
|
|
4946
|
+
const alertThreshold = (data.config && data.config.run_config && data.config.run_config.alert_threshold) || 0.8;
|
|
4947
|
+
const orgName = _orgroomCurrentOrg;
|
|
4948
|
+
|
|
4949
|
+
// Budget alert check
|
|
4950
|
+
let budgetAlert = null;
|
|
4951
|
+
if (budget > 0) {
|
|
4952
|
+
const totalIn = Object.values(agents).reduce((s, a) => s + (a.tokens_in || 0), 0);
|
|
4953
|
+
const totalOut = Object.values(agents).reduce((s, a) => s + (a.tokens_out || 0), 0);
|
|
4954
|
+
const pct = (totalIn + totalOut) / budget;
|
|
4955
|
+
if (pct >= alertThreshold) budgetAlert = `${(pct * 100).toFixed(1)}% of ${budget.toLocaleString()} token budget used`;
|
|
4956
|
+
}
|
|
4957
|
+
|
|
4958
|
+
const total = approvals.length + runningAgents.length + (budgetAlert ? 1 : 0);
|
|
4959
|
+
|
|
4960
|
+
let html = `<div style="display:grid;grid-template-columns:repeat(3,1fr);gap:12px;margin-bottom:20px">
|
|
4961
|
+
<div style="background:rgba(${approvals.length > 0 ? '255,80,60' : '0,0,0'},0.2);border:1px solid rgba(${approvals.length > 0 ? '255,80,60' : '0,200,160'},0.2);border-radius:4px;padding:12px;text-align:center">
|
|
4962
|
+
<div style="font-size:24px;font-weight:bold;color:${approvals.length > 0 ? 'rgba(255,100,80,0.9)' : 'var(--dim)'}">${approvals.length}</div>
|
|
4963
|
+
<div style="font-size:9px;color:var(--dim);letter-spacing:0.1em;margin-top:4px">PENDING APPROVALS</div>
|
|
4964
|
+
</div>
|
|
4965
|
+
<div style="background:rgba(0,0,0,0.2);border:1px solid rgba(0,200,160,0.15);border-radius:4px;padding:12px;text-align:center">
|
|
4966
|
+
<div style="font-size:24px;font-weight:bold;color:${runningAgents.length > 0 ? 'rgba(0,230,180,0.9)' : 'var(--dim)'}">${runningAgents.length}</div>
|
|
4967
|
+
<div style="font-size:9px;color:var(--dim);letter-spacing:0.1em;margin-top:4px">AGENTS RUNNING</div>
|
|
4968
|
+
</div>
|
|
4969
|
+
<div style="background:rgba(${budgetAlert ? '255,180,0' : '0,0,0'},0.15);border:1px solid rgba(${budgetAlert ? '255,180,0' : '0,200,160'},0.15);border-radius:4px;padding:12px;text-align:center">
|
|
4970
|
+
<div style="font-size:24px;font-weight:bold;color:${budgetAlert ? 'rgba(255,180,0,0.9)' : 'var(--dim)'}">${budgetAlert ? '!' : '✓'}</div>
|
|
4971
|
+
<div style="font-size:9px;color:var(--dim);letter-spacing:0.1em;margin-top:4px">BUDGET STATUS</div>
|
|
4972
|
+
</div>
|
|
4973
|
+
</div>`;
|
|
4974
|
+
|
|
4975
|
+
if (total === 0) {
|
|
4976
|
+
html += `<div style="text-align:center;padding:32px;color:var(--dim)">
|
|
4977
|
+
<div style="font-size:20px;margin-bottom:8px">✓</div>
|
|
4978
|
+
<div>Inbox clear — no items need attention.</div>
|
|
4979
|
+
</div>`;
|
|
4980
|
+
el.innerHTML = html; return;
|
|
4981
|
+
}
|
|
4982
|
+
|
|
4983
|
+
if (budgetAlert) {
|
|
4984
|
+
html += `<div style="background:rgba(255,180,0,0.1);border:1px solid rgba(255,180,0,0.3);border-radius:4px;padding:10px 14px;margin-bottom:10px;display:flex;align-items:center;gap:10px">
|
|
4985
|
+
<span style="color:rgba(255,180,0,0.9);font-size:14px">⚠</span>
|
|
4986
|
+
<div><div style="font-size:10px;color:rgba(255,180,0,0.9);letter-spacing:0.08em">BUDGET ALERT</div>
|
|
4987
|
+
<div style="font-size:11px;color:rgba(255,255,255,0.7);margin-top:2px">${escHtml(budgetAlert)}</div></div>
|
|
4988
|
+
<div style="margin-left:auto;font-size:9px;color:var(--dim)">/mastermind:costs --org ${escHtml(orgName)}</div>
|
|
4989
|
+
</div>`;
|
|
4990
|
+
}
|
|
4991
|
+
|
|
4992
|
+
if (approvals.length > 0) {
|
|
4993
|
+
html += `<div style="font-size:9px;color:rgba(255,80,60,0.8);letter-spacing:0.1em;margin-bottom:8px">PENDING APPROVALS (${approvals.length})</div>`;
|
|
4994
|
+
approvals.forEach(a => {
|
|
4995
|
+
const risk = a.risk_level || 'low';
|
|
4996
|
+
const riskColor = risk === 'high' ? 'rgba(255,80,60,0.9)' : risk === 'medium' ? 'rgba(255,180,0,0.8)' : 'rgba(0,200,160,0.6)';
|
|
4997
|
+
const role = roles.find(r => r.id === a.agent_id);
|
|
4998
|
+
html += `<div style="background:rgba(255,80,60,0.08);border:1px solid rgba(255,80,60,0.2);border-radius:4px;padding:10px 14px;margin-bottom:8px">
|
|
4999
|
+
<div style="display:flex;justify-content:space-between;align-items:start">
|
|
5000
|
+
<div>
|
|
5001
|
+
<div style="font-size:11px;color:rgba(255,255,255,0.85)">${escHtml(a.title || '')}</div>
|
|
5002
|
+
<div style="font-size:9px;color:var(--dim);margin-top:2px">agent: ${escHtml(a.agent_id)} ${role ? '· ' + escHtml(role.title) : ''}</div>
|
|
5003
|
+
</div>
|
|
5004
|
+
<span style="font-size:9px;color:${riskColor};border:1px solid ${riskColor};padding:2px 6px;border-radius:2px">${risk.toUpperCase()}</span>
|
|
5005
|
+
</div>
|
|
5006
|
+
<div style="font-size:10px;color:rgba(255,255,255,0.5);margin-top:6px">${escHtml((a.action || '').slice(0, 120))}</div>
|
|
5007
|
+
<div style="margin-top:8px;font-size:9px;color:rgba(0,200,160,0.6)">/mastermind:approve --org ${escHtml(orgName)} --action approve --approval-id ${escHtml(a.id)}</div>
|
|
5008
|
+
</div>`;
|
|
5009
|
+
});
|
|
5010
|
+
}
|
|
5011
|
+
|
|
5012
|
+
if (runningAgents.length > 0) {
|
|
5013
|
+
html += `<div style="font-size:9px;color:rgba(0,230,180,0.7);letter-spacing:0.1em;margin-top:16px;margin-bottom:8px">ACTIVE AGENTS (${runningAgents.length})</div>`;
|
|
5014
|
+
runningAgents.forEach(([roleId, state]) => {
|
|
5015
|
+
const role = roles.find(r => r.id === roleId);
|
|
5016
|
+
const since = state.last_heartbeat ? new Date(state.last_heartbeat).toLocaleTimeString('en', { hour12: false }) : '?';
|
|
5017
|
+
html += `<div style="background:rgba(0,0,0,0.3);border:1px solid rgba(0,200,160,0.2);border-radius:4px;padding:8px 14px;margin-bottom:6px;display:flex;align-items:center;gap:12px">
|
|
5018
|
+
<span class="orgroom-status-dot running"></span>
|
|
5019
|
+
<div>
|
|
5020
|
+
<span style="color:rgba(0,230,180,0.9)">${escHtml(roleId)}</span>
|
|
5021
|
+
${role ? `<span style="color:var(--dim);font-size:9px"> · ${escHtml(role.title)}</span>` : ''}
|
|
5022
|
+
</div>
|
|
5023
|
+
<div style="margin-left:auto;font-size:9px;color:var(--dim)">since ${since}</div>
|
|
5024
|
+
</div>`;
|
|
5025
|
+
});
|
|
5026
|
+
}
|
|
5027
|
+
|
|
5028
|
+
el.innerHTML = html;
|
|
5029
|
+
}
|
|
5030
|
+
|
|
5031
|
+
function renderOrgProjects(data) {
|
|
5032
|
+
const el = document.getElementById('orgroom-projects-body');
|
|
5033
|
+
if (!el) return;
|
|
5034
|
+
const projects = data._projects || [];
|
|
5035
|
+
const orgName = _orgroomCurrentOrg;
|
|
5036
|
+
|
|
5037
|
+
if (!projects.length) {
|
|
5038
|
+
el.innerHTML = `<div style="text-align:center;padding:32px;color:var(--dim)">
|
|
5039
|
+
<div style="font-size:24px;margin-bottom:8px">⬡</div>
|
|
5040
|
+
<div>No projects yet.</div>
|
|
5041
|
+
<div style="font-size:10px;margin-top:8px;color:rgba(0,200,160,0.5)">/mastermind:projects --org ${escHtml(orgName)} --action add --project-name "My Project"</div>
|
|
5042
|
+
</div>`;
|
|
5043
|
+
return;
|
|
5044
|
+
}
|
|
5045
|
+
|
|
5046
|
+
const statusColor = s => ({ active: 'rgba(0,230,180,0.9)', archived: 'rgba(100,100,100,0.5)', paused: 'rgba(255,180,0,0.7)' }[s] || 'var(--dim)');
|
|
5047
|
+
|
|
5048
|
+
let html = `<div style="display:flex;flex-direction:column;gap:10px">`;
|
|
5049
|
+
projects.forEach(p => {
|
|
5050
|
+
html += `<div style="background:rgba(0,0,0,0.3);border:1px solid rgba(0,200,160,0.15);border-radius:4px;padding:12px 16px">
|
|
5051
|
+
<div style="display:flex;justify-content:space-between;align-items:start">
|
|
5052
|
+
<div>
|
|
5053
|
+
<div style="font-size:12px;color:rgba(0,230,180,0.9)">${escHtml(p.name || p.id)}</div>
|
|
5054
|
+
<div style="font-size:9px;color:var(--dim);margin-top:2px">${escHtml(p.id)}</div>
|
|
5055
|
+
</div>
|
|
5056
|
+
<span style="font-size:9px;color:${statusColor(p.status)}">${(p.status || 'active').toUpperCase()}</span>
|
|
5057
|
+
</div>
|
|
5058
|
+
${p.description ? `<div style="font-size:10px;color:rgba(255,255,255,0.6);margin-top:6px">${escHtml(p.description)}</div>` : ''}
|
|
5059
|
+
<div style="display:flex;gap:16px;font-size:9px;color:var(--dim);margin-top:8px">
|
|
5060
|
+
${p.lead_agent ? `<span>lead: <span style="color:rgba(0,200,160,0.7)">${escHtml(p.lead_agent)}</span></span>` : ''}
|
|
5061
|
+
${p.task_count !== undefined ? `<span>tasks: <span style="color:rgba(0,200,160,0.7)">${p.task_count}</span></span>` : ''}
|
|
5062
|
+
<span>created: ${p.created_at ? new Date(p.created_at).toLocaleDateString() : '?'}</span>
|
|
5063
|
+
</div>
|
|
5064
|
+
</div>`;
|
|
5065
|
+
});
|
|
5066
|
+
html += `</div>
|
|
5067
|
+
<div style="margin-top:14px;font-size:9px;color:rgba(0,200,160,0.4)">/mastermind:projects --org ${escHtml(orgName)} --action add</div>`;
|
|
5068
|
+
el.innerHTML = html;
|
|
5069
|
+
}
|
|
5070
|
+
|
|
5071
|
+
function renderOrgActivity(data) {
|
|
5072
|
+
const el = document.getElementById('orgroom-activity-body');
|
|
5073
|
+
if (!el) return;
|
|
5074
|
+
const orgName = _orgroomCurrentOrg;
|
|
5075
|
+
|
|
5076
|
+
// Merge server-fetched activity with live SSE events from orgEventLog
|
|
5077
|
+
const liveEvents = (orgEventLog[orgName] || []).slice().reverse();
|
|
5078
|
+
const serverEvents = (data._activity || []);
|
|
5079
|
+
const allEvents = [...liveEvents, ...serverEvents.filter(se => !liveEvents.some(le => le.ts === se.ts && le.type === se.type))];
|
|
5080
|
+
allEvents.sort((a, b) => (b.ts || 0) - (a.ts || 0));
|
|
5081
|
+
|
|
5082
|
+
if (!allEvents.length) {
|
|
5083
|
+
el.innerHTML = `<div style="text-align:center;padding:32px;color:var(--dim)">
|
|
5084
|
+
<div style="font-size:24px;margin-bottom:8px">◌</div>
|
|
5085
|
+
<div>No activity yet. Start the org to see events.</div>
|
|
5086
|
+
</div>`;
|
|
5087
|
+
return;
|
|
5088
|
+
}
|
|
5089
|
+
|
|
5090
|
+
const eventTypeColor = t => {
|
|
5091
|
+
if (t.includes('start')) return 'rgba(0,230,180,0.8)';
|
|
5092
|
+
if (t.includes('complete') || t.includes('done')) return 'rgba(100,200,100,0.8)';
|
|
5093
|
+
if (t.includes('error') || t.includes('alert')) return 'rgba(255,80,60,0.8)';
|
|
5094
|
+
if (t.includes('approval')) return 'rgba(255,180,0,0.8)';
|
|
5095
|
+
if (t.includes('checkpoint') || t.includes('comms')) return 'rgba(150,150,200,0.8)';
|
|
5096
|
+
return 'rgba(0,200,160,0.6)';
|
|
5097
|
+
};
|
|
5098
|
+
|
|
5099
|
+
const fmtEventDetail = ev => {
|
|
5100
|
+
const parts = [];
|
|
5101
|
+
if (ev.role) parts.push(`role:${ev.role}`);
|
|
5102
|
+
if (ev.title) parts.push(ev.title);
|
|
5103
|
+
if (ev.msg) parts.push(ev.msg);
|
|
5104
|
+
if (ev.goal) parts.push(`goal:${ev.goal}`);
|
|
5105
|
+
if (ev.approval_id) parts.push(`#${ev.approval_id}`);
|
|
5106
|
+
return parts.join(' · ');
|
|
5107
|
+
};
|
|
5108
|
+
|
|
5109
|
+
let html = `<div style="display:flex;flex-direction:column;gap:3px">`;
|
|
5110
|
+
allEvents.slice(0, 80).forEach(ev => {
|
|
5111
|
+
const t = ev.ts ? new Date(ev.ts).toLocaleTimeString('en', { hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit' }) : '?';
|
|
5112
|
+
const detail = fmtEventDetail(ev);
|
|
5113
|
+
html += `<div style="display:grid;grid-template-columns:80px 180px 1fr;gap:8px;padding:4px 8px;border-radius:2px;background:rgba(0,0,0,0.15);align-items:baseline">
|
|
5114
|
+
<span style="font-size:9px;color:var(--dim);font-variant-numeric:tabular-nums">${t}</span>
|
|
5115
|
+
<span style="font-size:9px;color:${eventTypeColor(ev.type || '')};letter-spacing:0.05em">${escHtml(fmtOrgEventType(ev.type))}</span>
|
|
5116
|
+
<span style="font-size:10px;color:rgba(255,255,255,0.6)">${escHtml(detail)}</span>
|
|
5117
|
+
</div>`;
|
|
5118
|
+
});
|
|
5119
|
+
html += `</div>`;
|
|
5120
|
+
if (allEvents.length > 80) html += `<div style="text-align:center;margin-top:8px;font-size:9px;color:var(--dim)">${allEvents.length - 80} older events not shown</div>`;
|
|
5121
|
+
el.innerHTML = html;
|
|
5122
|
+
}
|
|
5123
|
+
|
|
5124
|
+
// ─── LIVE TAB ──────────────────────────────────────────────────────
|
|
5125
|
+
let _orgroomLiveSse = null;
|
|
5126
|
+
function renderOrgLive(data) {
|
|
5127
|
+
const el = document.getElementById('orgroom-live-body');
|
|
5128
|
+
if (!el) return;
|
|
5129
|
+
const orgName = _orgroomCurrentOrg;
|
|
5130
|
+
const agents = data && data.state && data.state.agents ? data.state.agents : {};
|
|
5131
|
+
const runningAgents = Object.entries(agents).filter(([,a]) => a.status === 'running');
|
|
5132
|
+
|
|
5133
|
+
// Close any previous SSE for a different org
|
|
5134
|
+
if (_orgroomLiveSse && _orgroomLiveSse._org !== orgName) {
|
|
5135
|
+
_orgroomLiveSse.close();
|
|
5136
|
+
_orgroomLiveSse = null;
|
|
5137
|
+
}
|
|
5138
|
+
|
|
5139
|
+
let html = `<div style="display:flex;gap:16px;margin-bottom:12px;flex-wrap:wrap">`;
|
|
5140
|
+
html += `<div style="background:rgba(0,0,0,0.35);border:1px solid rgba(0,200,160,0.15);border-radius:4px;padding:8px 14px;min-width:120px">
|
|
5141
|
+
<div style="font-size:9px;color:var(--dim);margin-bottom:3px">RUNNING</div>
|
|
5142
|
+
<div style="font-size:18px;color:${runningAgents.length ? 'var(--green)' : 'var(--dim)'}">${runningAgents.length}</div>
|
|
5143
|
+
</div>`;
|
|
5144
|
+
const totalTok = Object.values(agents).reduce((s,a) => s + (a.tokens_in||0) + (a.tokens_out||0), 0);
|
|
5145
|
+
html += `<div style="background:rgba(0,0,0,0.35);border:1px solid rgba(0,200,160,0.15);border-radius:4px;padding:8px 14px;min-width:120px">
|
|
5146
|
+
<div style="font-size:9px;color:var(--dim);margin-bottom:3px">TOKENS USED</div>
|
|
5147
|
+
<div style="font-size:18px;color:rgba(0,200,160,0.9)">${totalTok >= 1000 ? (totalTok/1000).toFixed(1)+'K' : totalTok}</div>
|
|
5148
|
+
</div>`;
|
|
5149
|
+
html += `</div>`;
|
|
5150
|
+
|
|
5151
|
+
// Running agent cards
|
|
5152
|
+
if (runningAgents.length) {
|
|
5153
|
+
html += `<div style="font-size:9px;color:var(--dim);letter-spacing:0.1em;margin-bottom:8px">ACTIVE AGENTS</div>`;
|
|
5154
|
+
html += `<div style="display:flex;gap:8px;flex-wrap:wrap;margin-bottom:14px">`;
|
|
5155
|
+
runningAgents.forEach(([id, a]) => {
|
|
5156
|
+
const beat = a.last_heartbeat ? new Date(a.last_heartbeat).toLocaleTimeString('en', {hour12:false,hour:'2-digit',minute:'2-digit',second:'2-digit'}) : '?';
|
|
5157
|
+
html += `<div style="background:rgba(0,30,20,0.7);border:1px solid rgba(0,229,135,0.3);border-radius:4px;padding:8px 12px;min-width:160px">
|
|
5158
|
+
<div style="display:flex;align-items:center;gap:6px;margin-bottom:4px">
|
|
5159
|
+
<span class="orgroom-status-dot running"></span>
|
|
5160
|
+
<span style="color:rgba(0,229,135,0.9);font-size:10px">${escHtml(id)}</span>
|
|
5161
|
+
</div>
|
|
5162
|
+
<div style="font-size:9px;color:var(--dim)">last beat: ${beat}</div>
|
|
5163
|
+
<div style="font-size:9px;color:var(--dim)">in:${a.tokens_in||0} out:${a.tokens_out||0}</div>
|
|
5164
|
+
</div>`;
|
|
5165
|
+
});
|
|
5166
|
+
html += `</div>`;
|
|
5167
|
+
} else {
|
|
5168
|
+
html += `<div style="text-align:center;padding:20px;color:var(--dim)">No agents currently running. Start the org to see live activity.</div>`;
|
|
5169
|
+
}
|
|
5170
|
+
|
|
5171
|
+
// Live event stream
|
|
5172
|
+
html += `<div style="font-size:9px;color:var(--dim);letter-spacing:0.1em;margin-bottom:6px">LIVE EVENT STREAM</div>`;
|
|
5173
|
+
html += `<div id="orgroom-live-stream">`;
|
|
5174
|
+
const liveEvents = (orgEventLog[orgName] || []).slice().reverse();
|
|
5175
|
+
if (liveEvents.length) {
|
|
5176
|
+
liveEvents.slice(0, 100).forEach(ev => {
|
|
5177
|
+
const t = ev.ts ? new Date(ev.ts).toLocaleTimeString('en',{hour12:false,hour:'2-digit',minute:'2-digit',second:'2-digit'}) : '';
|
|
5178
|
+
const cat = (ev.type||'').includes('heartbeat') ? 'type-heartbeat' : (ev.type||'').includes('approval') ? 'type-approval' : (ev.type||'').includes('error') ? 'type-error' : '';
|
|
5179
|
+
html += `<div class="live-event-row ${cat}">
|
|
5180
|
+
<span class="live-ts">${t}</span>
|
|
5181
|
+
<span class="live-type">${escHtml(fmtOrgEventType(ev.type))}</span>
|
|
5182
|
+
<span style="color:rgba(255,255,255,0.5);font-size:9px">${escHtml(ev.role||ev.msg||ev.title||'')}</span>
|
|
5183
|
+
</div>`;
|
|
5184
|
+
});
|
|
5185
|
+
} else {
|
|
5186
|
+
html += `<div style="text-align:center;padding:20px;color:var(--dim)">No live events yet.</div>`;
|
|
5187
|
+
}
|
|
5188
|
+
html += `</div>`;
|
|
5189
|
+
el.innerHTML = html;
|
|
5190
|
+
}
|
|
5191
|
+
|
|
5192
|
+
// ─── SKILLS TAB ────────────────────────────────────────────────────
|
|
5193
|
+
function renderOrgSkills(data) {
|
|
5194
|
+
const el = document.getElementById('orgroom-skills-body');
|
|
5195
|
+
if (!el) return;
|
|
5196
|
+
const skillsData = (data && data._skills) || { skills: [], role_skill_map: {} };
|
|
5197
|
+
const skills = skillsData.skills || [];
|
|
5198
|
+
const roleMap = skillsData.role_skill_map || {};
|
|
5199
|
+
const roles = (data && data.config && data.config.roles) || [];
|
|
5200
|
+
|
|
5201
|
+
let html = `<input class="skill-search" id="orgroom-skill-filter" placeholder="Filter skills…" oninput="filterOrgSkills(this.value)" />`;
|
|
5202
|
+
|
|
5203
|
+
if (!skills.length) {
|
|
5204
|
+
html += `<div style="color:var(--dim);padding:20px;text-align:center">No skills found in .claude/skills/</div>`;
|
|
5205
|
+
el.innerHTML = html;
|
|
5206
|
+
return;
|
|
5207
|
+
}
|
|
5208
|
+
|
|
5209
|
+
// Role skill map section
|
|
5210
|
+
if (roles.length) {
|
|
5211
|
+
html += `<div style="font-size:9px;color:var(--dim);letter-spacing:0.1em;margin-bottom:8px">ROLE SKILL ASSIGNMENTS</div>`;
|
|
5212
|
+
html += `<table class="orgroom-table" style="margin-bottom:16px"><thead><tr>
|
|
5213
|
+
<th>ROLE</th><th>ASSIGNED SKILLS</th>
|
|
5214
|
+
</tr></thead><tbody>`;
|
|
5215
|
+
roles.forEach(r => {
|
|
5216
|
+
const assigned = roleMap[r.id] || [];
|
|
5217
|
+
html += `<tr>
|
|
5218
|
+
<td style="color:rgba(0,200,160,0.8)">${escHtml(r.id)}</td>
|
|
5219
|
+
<td>${assigned.length ? assigned.map(s=>`<span class="skill-badge">${escHtml(s)}</span>`).join(' ') : '<span style="color:var(--dim)">inherited</span>'}</td>
|
|
5220
|
+
</tr>`;
|
|
5221
|
+
});
|
|
5222
|
+
html += `</tbody></table>`;
|
|
5223
|
+
}
|
|
5224
|
+
|
|
5225
|
+
html += `<div style="font-size:9px;color:var(--dim);letter-spacing:0.1em;margin-bottom:8px">ALL SKILLS (${skills.length})</div>`;
|
|
5226
|
+
html += `<div id="orgroom-skills-list">`;
|
|
5227
|
+
skills.forEach(s => {
|
|
5228
|
+
const modeColor = s.default_mode === 'auto' ? 'rgba(0,200,160,0.7)' : s.default_mode === 'confirm' ? 'rgba(255,180,0,0.7)' : 'rgba(150,150,200,0.7)';
|
|
5229
|
+
html += `<div class="skill-card" data-name="${escHtml(s.name)}">
|
|
5230
|
+
<div style="flex:1">
|
|
5231
|
+
<div class="skill-card-name">${escHtml(s.name)}</div>
|
|
5232
|
+
<div class="skill-card-desc">${escHtml(s.description || '')}</div>
|
|
5233
|
+
</div>
|
|
5234
|
+
<span class="skill-badge" style="color:${modeColor}">${escHtml(s.default_mode || 'auto')}</span>
|
|
5235
|
+
<span class="skill-badge">${escHtml(s.type || 'skill')}</span>
|
|
5236
|
+
</div>`;
|
|
5237
|
+
});
|
|
5238
|
+
html += `</div>`;
|
|
5239
|
+
el.innerHTML = html;
|
|
5240
|
+
}
|
|
5241
|
+
|
|
5242
|
+
function filterOrgSkills(q) {
|
|
5243
|
+
const container = document.getElementById('orgroom-skills-list');
|
|
5244
|
+
if (!container) return;
|
|
5245
|
+
const ql = q.toLowerCase();
|
|
5246
|
+
container.querySelectorAll('.skill-card').forEach(card => {
|
|
5247
|
+
card.style.display = !ql || card.dataset.name.toLowerCase().includes(ql) ? '' : 'none';
|
|
5248
|
+
});
|
|
5249
|
+
}
|
|
5250
|
+
|
|
5251
|
+
// ─── GOALS TAB ─────────────────────────────────────────────────────
|
|
5252
|
+
function renderOrgGoals(data) {
|
|
5253
|
+
const el = document.getElementById('orgroom-goals-body');
|
|
5254
|
+
if (!el) return;
|
|
5255
|
+
const goals = (data && data.goals) || [];
|
|
5256
|
+
|
|
5257
|
+
if (!goals.length) {
|
|
5258
|
+
el.innerHTML = `<div style="color:var(--dim);padding:20px;">No goals defined. Run <code>/mastermind:goals --org ${escHtml(_orgroomCurrentOrg)} --action add</code> to create goals.</div>`;
|
|
5259
|
+
return;
|
|
5260
|
+
}
|
|
5261
|
+
|
|
5262
|
+
// Build hierarchy
|
|
5263
|
+
const byId = {};
|
|
5264
|
+
goals.forEach(g => { byId[g.id] = g; });
|
|
5265
|
+
const roots = [];
|
|
5266
|
+
const children = {};
|
|
5267
|
+
goals.forEach(g => {
|
|
5268
|
+
if (!g.parent_id) { roots.push(g); }
|
|
5269
|
+
else {
|
|
5270
|
+
if (!children[g.parent_id]) children[g.parent_id] = [];
|
|
5271
|
+
children[g.parent_id].push(g);
|
|
5272
|
+
}
|
|
5273
|
+
});
|
|
5274
|
+
|
|
5275
|
+
function goalProgress(goal) {
|
|
5276
|
+
const kids = children[goal.id] || [];
|
|
5277
|
+
if (!kids.length) return goal.status === 'done' ? 100 : (goal.status === 'in_progress' ? 50 : 0);
|
|
5278
|
+
const doneKids = kids.filter(k => k.status === 'done').length;
|
|
5279
|
+
return Math.round((doneKids / kids.length) * 100);
|
|
5280
|
+
}
|
|
5281
|
+
|
|
5282
|
+
function renderGoalNode(goal, depth) {
|
|
5283
|
+
const kids = children[goal.id] || [];
|
|
5284
|
+
const statusCls = 'goal-' + (goal.status || 'open').replace(/[^a-z_]/g, '_');
|
|
5285
|
+
const pct = goalProgress(goal);
|
|
5286
|
+
const projCount = (goal.linked_projects || []).length;
|
|
5287
|
+
let html = `<div class="goal-node">
|
|
5288
|
+
<div style="flex:1;min-width:0">
|
|
5289
|
+
<div style="display:flex;align-items:center;gap:8px">
|
|
5290
|
+
<span class="goal-title">${escHtml(goal.title || goal.id)}</span>
|
|
5291
|
+
<span class="goal-status-badge ${statusCls}">${goal.status || 'open'}</span>
|
|
5292
|
+
</div>
|
|
5293
|
+
${goal.description ? `<div class="goal-desc">${escHtml(goal.description)}</div>` : ''}
|
|
5294
|
+
${pct > 0 ? `<div class="goal-progress-bar"><div class="goal-progress-fill" style="width:${pct}%"></div></div>` : ''}
|
|
5295
|
+
<div class="goal-meta">${kids.length ? `${kids.length} sub-goal(s) · ${pct}% done · ` : ''}${projCount ? `${projCount} project(s) · ` : ''}${goal.id}</div>
|
|
5296
|
+
</div>
|
|
5297
|
+
</div>`;
|
|
5298
|
+
if (kids.length) {
|
|
5299
|
+
html += `<div class="goal-node-children">`;
|
|
5300
|
+
kids.forEach(k => { html += renderGoalNode(k, depth + 1); });
|
|
5301
|
+
html += `</div>`;
|
|
5302
|
+
}
|
|
5303
|
+
return html;
|
|
5304
|
+
}
|
|
5305
|
+
|
|
5306
|
+
let html = `<div style="padding:8px 0">`;
|
|
5307
|
+
html += `<div style="font-size:9px;color:var(--dim);margin-bottom:12px;">${goals.length} goal(s) total</div>`;
|
|
5308
|
+
roots.forEach(g => {
|
|
5309
|
+
html += `<div class="goal-tree-root">${renderGoalNode(g, 0)}</div>`;
|
|
5310
|
+
});
|
|
5311
|
+
html += `</div>`;
|
|
5312
|
+
el.innerHTML = html;
|
|
5313
|
+
}
|
|
5314
|
+
|
|
5315
|
+
// ─── WORKSPACES TAB ────────────────────────────────────────────────
|
|
5316
|
+
function renderOrgWorkspaces(data) {
|
|
5317
|
+
const el = document.getElementById('orgroom-workspaces-body');
|
|
5318
|
+
if (!el) return;
|
|
5319
|
+
const ws = (data && data._workspaces && data._workspaces.workspaces) || [];
|
|
5320
|
+
const projects = (data && data._projects && data._projects.projects) || [];
|
|
5321
|
+
|
|
5322
|
+
if (!ws.length) {
|
|
5323
|
+
el.innerHTML = `<div style="color:var(--dim);padding:20px;">No workspaces registered. Run <code>/mastermind:workspaces --org ${escHtml(_orgroomCurrentOrg)} --action attach</code> to register one.</div>`;
|
|
5324
|
+
return;
|
|
5325
|
+
}
|
|
5326
|
+
|
|
5327
|
+
const projMap = {};
|
|
5328
|
+
projects.forEach(p => { projMap[p.id] = p.name || p.id; });
|
|
5329
|
+
|
|
5330
|
+
// Group by project
|
|
5331
|
+
const byProject = {};
|
|
5332
|
+
ws.forEach(w => {
|
|
5333
|
+
const pid = w.project_id || '_ungrouped';
|
|
5334
|
+
if (!byProject[pid]) byProject[pid] = [];
|
|
5335
|
+
byProject[pid].push(w);
|
|
5336
|
+
});
|
|
5337
|
+
|
|
5338
|
+
const orgName = _orgroomCurrentOrg;
|
|
5339
|
+
let html = `<div style="padding:8px 0">`;
|
|
5340
|
+
html += `<div style="font-size:9px;color:var(--dim);margin-bottom:12px;">${ws.length} workspace(s) · ${Object.keys(byProject).length} project(s)</div>`;
|
|
5341
|
+
|
|
5342
|
+
Object.entries(byProject).forEach(([pid, workspaces]) => {
|
|
5343
|
+
const projName = projMap[pid] || pid;
|
|
5344
|
+
html += `<div class="ws-project-group">
|
|
5345
|
+
<div class="ws-project-label">◈ ${escHtml(projName)}</div>`;
|
|
5346
|
+
|
|
5347
|
+
workspaces.forEach(w => {
|
|
5348
|
+
const stCls = 'ws-' + (w.status || 'unknown');
|
|
5349
|
+
const svcCount = (w.services || []).length;
|
|
5350
|
+
html += `<div class="ws-card">
|
|
5351
|
+
<div class="ws-card-info">
|
|
5352
|
+
<div class="ws-id">${escHtml(w.id)}</div>
|
|
5353
|
+
<div class="ws-branch">⎇ ${escHtml(w.branch || '?')}</div>
|
|
5354
|
+
${w.agent_id ? `<div class="ws-agent">↳ ${escHtml(w.agent_id)}</div>` : ''}
|
|
5355
|
+
${w.worktree_path ? `<div class="ws-path">${escHtml(w.worktree_path)}</div>` : ''}
|
|
5356
|
+
<div class="ws-svc-count">${svcCount ? `${svcCount} service(s) running` : 'no services'}</div>
|
|
5357
|
+
</div>
|
|
5358
|
+
<div style="display:flex;flex-direction:column;align-items:flex-end;gap:6px;flex-shrink:0">
|
|
5359
|
+
<span class="ws-status-badge ${stCls}">${w.status || 'unknown'}</span>
|
|
5360
|
+
${w.status === 'active' ? `<button class="ws-stop-btn" onclick="stopWsHint('${escHtml(orgName)}','${escHtml(w.id)}')">STOP</button>` : ''}
|
|
5361
|
+
</div>
|
|
5362
|
+
</div>`;
|
|
5363
|
+
});
|
|
5364
|
+
|
|
5365
|
+
html += `</div>`;
|
|
5366
|
+
});
|
|
5367
|
+
|
|
5368
|
+
html += `</div>`;
|
|
5369
|
+
el.innerHTML = html;
|
|
5370
|
+
}
|
|
5371
|
+
|
|
5372
|
+
window.stopWsHint = function(orgName, wsId) {
|
|
5373
|
+
alert(`Run in Claude Code:\n/mastermind:workspaces --org ${orgName} --action stop --workspace-id ${wsId}`);
|
|
5374
|
+
};
|
|
5375
|
+
|
|
5376
|
+
// ─── BOARD TAB ─────────────────────────────────────────────────────
|
|
5377
|
+
function renderOrgBoard(data) {
|
|
5378
|
+
const el = document.getElementById('orgroom-board-body');
|
|
5379
|
+
if (!el) return;
|
|
5380
|
+
|
|
5381
|
+
const issues = (data && data._issues && data._issues.issues) || [];
|
|
5382
|
+
const statuses = ['open', 'in_progress', 'blocked', 'done', 'cancelled'];
|
|
5383
|
+
const statusLabels = { open: 'OPEN', in_progress: 'IN PROGRESS', blocked: 'BLOCKED', done: 'DONE', cancelled: 'CANCELLED' };
|
|
5384
|
+
const orgName = _orgroomCurrentOrg;
|
|
5385
|
+
|
|
5386
|
+
if (!issues.length) {
|
|
5387
|
+
el.innerHTML = `<div style="color:var(--dim);padding:20px;">No tasks found. Run <code>/mastermind:tasks --org ${escHtml(orgName)} --action list</code> to see tasks.</div>`;
|
|
5388
|
+
return;
|
|
5389
|
+
}
|
|
5390
|
+
|
|
5391
|
+
const byStatus = {};
|
|
5392
|
+
statuses.forEach(s => { byStatus[s] = []; });
|
|
5393
|
+
issues.forEach(issue => {
|
|
5394
|
+
const s = issue.status || 'open';
|
|
5395
|
+
if (!byStatus[s]) byStatus[s] = [];
|
|
5396
|
+
byStatus[s].push(issue);
|
|
5397
|
+
});
|
|
5398
|
+
|
|
5399
|
+
const priorityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
5400
|
+
Object.values(byStatus).forEach(arr => {
|
|
5401
|
+
arr.sort((a, b) => (priorityOrder[a.priority] ?? 2) - (priorityOrder[b.priority] ?? 2));
|
|
5402
|
+
});
|
|
5403
|
+
|
|
5404
|
+
let html = `<div style="padding:8px 0">`;
|
|
5405
|
+
html += `<div style="font-size:9px;color:var(--dim);margin-bottom:12px;">${issues.length} task(s) total</div>`;
|
|
5406
|
+
html += `<div class="board-columns">`;
|
|
5407
|
+
|
|
5408
|
+
statuses.forEach(status => {
|
|
5409
|
+
const cards = byStatus[status] || [];
|
|
5410
|
+
html += `<div class="board-col board-col-${status.replace('_','-')}">
|
|
5411
|
+
<div class="board-col-header">${statusLabels[status] || status}
|
|
5412
|
+
<span class="board-count-badge">${cards.length}</span>
|
|
5413
|
+
</div>`;
|
|
5414
|
+
|
|
5415
|
+
if (!cards.length) {
|
|
5416
|
+
html += `<div class="board-empty">Empty</div>`;
|
|
5417
|
+
} else {
|
|
5418
|
+
cards.forEach(issue => {
|
|
5419
|
+
const priCls = 'board-priority-' + (issue.priority || 'medium');
|
|
5420
|
+
html += `<div class="board-card">
|
|
5421
|
+
<div class="board-card-title">${escHtml((issue.title || issue.id || '').slice(0, 60))}</div>
|
|
5422
|
+
<div class="board-card-meta">
|
|
5423
|
+
<span class="${priCls}">${issue.priority || 'medium'}</span>
|
|
5424
|
+
${issue.assignee_id ? `<span>↳ ${escHtml(issue.assignee_id)}</span>` : ''}
|
|
5425
|
+
${issue.project_id ? `<span>◈ ${escHtml(issue.project_id)}</span>` : ''}
|
|
5426
|
+
</div>
|
|
5427
|
+
</div>`;
|
|
5428
|
+
});
|
|
5429
|
+
}
|
|
5430
|
+
html += `</div>`;
|
|
5431
|
+
});
|
|
5432
|
+
|
|
5433
|
+
html += `</div></div>`;
|
|
5434
|
+
el.innerHTML = html;
|
|
5435
|
+
}
|
|
5436
|
+
|
|
5437
|
+
// ─── HEALTH TAB ────────────────────────────────────────────────────
|
|
5438
|
+
function renderOrgHealth(data) {
|
|
5439
|
+
const el = document.getElementById('orgroom-health-body');
|
|
5440
|
+
if (!el) return;
|
|
5441
|
+
const orgName = _orgroomCurrentOrg;
|
|
5442
|
+
const health = data && data._health;
|
|
5443
|
+
const state = (data && data.state && data.state.agents) || {};
|
|
5444
|
+
const roles = (data && data.config && data.config.roles) || [];
|
|
5445
|
+
const issues = (data && data._issues && data._issues.issues) || [];
|
|
5446
|
+
const activity = (data && data._activity) || [];
|
|
5447
|
+
|
|
5448
|
+
// Compute local fallbacks if health endpoint returns nothing
|
|
5449
|
+
const agentList = Object.entries(state);
|
|
5450
|
+
const running = agentList.filter(([,v]) => v.status === 'running').length;
|
|
5451
|
+
const idle = agentList.filter(([,v]) => v.status !== 'running').length;
|
|
5452
|
+
const totalAgents = roles.length || agentList.length;
|
|
5453
|
+
|
|
5454
|
+
const openIssues = issues.filter(i => i.status === 'open').length;
|
|
5455
|
+
const inProgIssues = issues.filter(i => i.status === 'in_progress').length;
|
|
5456
|
+
const doneIssues = issues.filter(i => i.status === 'done').length;
|
|
5457
|
+
|
|
5458
|
+
// 7-day run success rate
|
|
5459
|
+
const cutoff = new Date(); cutoff.setDate(cutoff.getDate() - 7);
|
|
5460
|
+
const recentRuns = activity.filter(e => e.ts && new Date(e.ts) >= cutoff);
|
|
5461
|
+
const successRuns = recentRuns.filter(e => e.type && e.type.includes('complete')).length;
|
|
5462
|
+
const successRate = recentRuns.length > 0 ? Math.round((successRuns / recentRuns.length) * 100) : null;
|
|
5463
|
+
|
|
5464
|
+
// Budget from health endpoint or fallback
|
|
5465
|
+
const budgetPct = health ? (health.budget_used_pct ?? null) : null;
|
|
5466
|
+
const budgetCls = budgetPct === null ? '' : budgetPct >= 90 ? 'health-crit' : budgetPct >= 70 ? 'health-warn' : 'health-ok';
|
|
5467
|
+
const barCls = budgetPct === null ? 'health-bar-ok' : budgetPct >= 90 ? 'health-bar-crit' : budgetPct >= 70 ? 'health-bar-warn' : 'health-bar-ok';
|
|
5468
|
+
|
|
5469
|
+
const srCls = successRate === null ? '' : successRate >= 80 ? 'health-ok' : successRate >= 50 ? 'health-warn' : 'health-crit';
|
|
5470
|
+
|
|
5471
|
+
let html = `<div style="padding:8px 0">`;
|
|
5472
|
+
|
|
5473
|
+
// Metrics grid
|
|
5474
|
+
html += `<div class="health-grid">
|
|
5475
|
+
<div class="health-metric-card">
|
|
5476
|
+
<div class="health-metric-label">Agents</div>
|
|
5477
|
+
<div class="health-metric-value health-ok">${totalAgents}</div>
|
|
5478
|
+
<div class="health-metric-sub">${running} running · ${idle} idle</div>
|
|
5479
|
+
</div>
|
|
5480
|
+
<div class="health-metric-card">
|
|
5481
|
+
<div class="health-metric-label">Open Issues</div>
|
|
5482
|
+
<div class="health-metric-value ${openIssues > 0 ? 'health-warn' : 'health-ok'}">${openIssues}</div>
|
|
5483
|
+
<div class="health-metric-sub">${inProgIssues} in progress · ${doneIssues} done</div>
|
|
5484
|
+
</div>`;
|
|
5485
|
+
|
|
5486
|
+
if (successRate !== null) {
|
|
5487
|
+
html += `<div class="health-metric-card">
|
|
5488
|
+
<div class="health-metric-label">Success Rate (7d)</div>
|
|
5489
|
+
<div class="health-metric-value ${srCls}">${successRate}<span class="health-metric-unit">%</span></div>
|
|
5490
|
+
<div class="health-metric-sub">${successRuns} of ${recentRuns.length} runs</div>
|
|
5491
|
+
</div>`;
|
|
5492
|
+
}
|
|
5493
|
+
|
|
5494
|
+
if (budgetPct !== null) {
|
|
5495
|
+
html += `<div class="health-metric-card">
|
|
5496
|
+
<div class="health-metric-label">Budget Used</div>
|
|
5497
|
+
<div class="health-metric-value ${budgetCls}">${budgetPct}<span class="health-metric-unit">%</span></div>
|
|
5498
|
+
<div class="health-bar"><div class="health-bar-fill ${barCls}" style="width:${Math.min(100,budgetPct)}%"></div></div>
|
|
5499
|
+
</div>`;
|
|
5500
|
+
}
|
|
5501
|
+
|
|
5502
|
+
html += `</div>`;
|
|
5503
|
+
|
|
5504
|
+
// Agent status section
|
|
5505
|
+
if (agentList.length) {
|
|
5506
|
+
html += `<div class="health-section-title">Agent Status</div>`;
|
|
5507
|
+
html += `<table class="orgroom-table"><thead><tr>
|
|
5508
|
+
<th>AGENT</th><th>STATUS</th><th>LAST RUN</th><th>TOKENS USED</th>
|
|
5509
|
+
</tr></thead><tbody>`;
|
|
5510
|
+
agentList.forEach(([id, info]) => {
|
|
5511
|
+
const stCls = info.status === 'running' ? 'health-ok' : info.status === 'error' ? 'health-crit' : '';
|
|
5512
|
+
html += `<tr>
|
|
5513
|
+
<td>${escHtml(id)}</td>
|
|
5514
|
+
<td class="${stCls}">${info.status || 'unknown'}</td>
|
|
5515
|
+
<td>${info.last_run || '-'}</td>
|
|
5516
|
+
<td>${info.tokens_used || '-'}</td>
|
|
5517
|
+
</tr>`;
|
|
5518
|
+
});
|
|
5519
|
+
html += `</tbody></table>`;
|
|
5520
|
+
}
|
|
5521
|
+
|
|
5522
|
+
// Commands hint
|
|
5523
|
+
html += `<div style="margin-top:16px;font-size:9px;color:var(--dim);">
|
|
5524
|
+
<div>View details: <code>/mastermind:agent-detail --org ${escHtml(orgName)} --agent-id <id></code></div>
|
|
5525
|
+
<div style="margin-top:4px;">Full health: <code>/mastermind:instance --action health</code></div>
|
|
5526
|
+
</div>`;
|
|
5527
|
+
|
|
5528
|
+
html += `</div>`;
|
|
5529
|
+
el.innerHTML = html;
|
|
5530
|
+
}
|
|
5531
|
+
|
|
5532
|
+
// ─── INVITES TAB ──────────────────────────────────────────────────
|
|
5533
|
+
function renderOrgInvites(data) {
|
|
5534
|
+
const el = document.getElementById('orgroom-invites-body');
|
|
5535
|
+
if (!el) return;
|
|
5536
|
+
const inv = (data && data._invites) || { invites: [], join_requests: [] };
|
|
5537
|
+
const orgName = data && (data.name || data.org_name || '');
|
|
5538
|
+
let html = '';
|
|
5539
|
+
|
|
5540
|
+
// Active invites section
|
|
5541
|
+
html += `<div class="invite-section-title">Active Invites (${inv.invites.length})</div>`;
|
|
5542
|
+
if (!inv.invites.length) {
|
|
5543
|
+
html += `<div style="color:var(--dim);font-size:10px;padding:8px 0;">No pending invites. To create one: /mastermind:invites --org ${escHtml(orgName)} --action create --role operator</div>`;
|
|
5544
|
+
} else {
|
|
5545
|
+
html += `<table class="orgroom-table"><thead><tr>
|
|
5546
|
+
<th>TOKEN</th><th>ROLE</th><th>CREATED</th>
|
|
5547
|
+
</tr></thead><tbody>`;
|
|
5548
|
+
inv.invites.forEach(i => {
|
|
5549
|
+
html += `<tr>
|
|
5550
|
+
<td><span class="invite-token">${escHtml(i.token || i.id)}</span></td>
|
|
5551
|
+
<td><span class="invite-role">${escHtml(i.role || 'operator')}</span></td>
|
|
5552
|
+
<td class="invite-meta">${escHtml(i.createdAt ? i.createdAt.slice(0,10) : '—')}</td>
|
|
5553
|
+
</tr>`;
|
|
5554
|
+
});
|
|
5555
|
+
html += `</tbody></table>`;
|
|
5556
|
+
}
|
|
5557
|
+
|
|
5558
|
+
// Join requests section
|
|
5559
|
+
html += `<div class="invite-section-title">Pending Join Requests (${inv.join_requests.length})</div>`;
|
|
5560
|
+
if (!inv.join_requests.length) {
|
|
5561
|
+
html += `<div style="color:var(--dim);font-size:10px;padding:8px 0;">No pending join requests.</div>`;
|
|
5562
|
+
} else {
|
|
5563
|
+
inv.join_requests.forEach(r => {
|
|
5564
|
+
html += `<div class="join-row">
|
|
5565
|
+
<div class="join-row-header">
|
|
5566
|
+
<span style="font-size:10px;font-weight:600;">${escHtml(r.id)}</span>
|
|
5567
|
+
<span class="join-type-badge">${escHtml(r.requestType || 'human')}</span>
|
|
5568
|
+
<span class="invite-role">${escHtml(r.role || 'viewer')}</span>
|
|
5569
|
+
<span class="invite-meta">${escHtml(r.createdAt ? r.createdAt.slice(0,10) : '—')}</span>
|
|
5570
|
+
</div>
|
|
5571
|
+
${r.message ? `<div class="join-msg">${escHtml(r.message)}</div>` : ''}
|
|
5572
|
+
<div class="join-hint">→ approve: /mastermind:invites --org ${escHtml(orgName)} --action approve-join --request-id ${escHtml(r.id)}</div>
|
|
5573
|
+
<div class="join-hint">→ reject: /mastermind:invites --org ${escHtml(orgName)} --action reject-join --request-id ${escHtml(r.id)}</div>
|
|
5574
|
+
</div>`;
|
|
5575
|
+
});
|
|
5576
|
+
}
|
|
5577
|
+
|
|
5578
|
+
el.innerHTML = html;
|
|
5579
|
+
}
|
|
5580
|
+
|
|
5581
|
+
// ─── PLUGINS TAB ──────────────────────────────────────────────────
|
|
5582
|
+
function renderOrgPlugins(data) {
|
|
5583
|
+
const el = document.getElementById('orgroom-plugins-body');
|
|
5584
|
+
if (!el) return;
|
|
5585
|
+
const pluginData = (data && data._plugins) || { plugins: [] };
|
|
5586
|
+
const plugins = pluginData.plugins || [];
|
|
5587
|
+
const orgName = data && (data.name || data.org_name || '');
|
|
5588
|
+
let html = '';
|
|
5589
|
+
|
|
5590
|
+
const installed = plugins.filter(p => p.status === 'installed' || !p.status);
|
|
5591
|
+
const errored = plugins.filter(p => p.status === 'error');
|
|
5592
|
+
const disabled = plugins.filter(p => p.status === 'disabled');
|
|
5593
|
+
|
|
5594
|
+
html += `<div style="font-size:9px;color:var(--dim);margin-bottom:12px;">
|
|
5595
|
+
${installed.length} installed · ${errored.length} error · ${disabled.length} disabled
|
|
5596
|
+
<span style="font-family:monospace;color:rgba(0,200,160,0.5);">/mastermind:plugin-settings --plugin-id <id></span>
|
|
5597
|
+
</div>`;
|
|
5598
|
+
|
|
5599
|
+
if (!plugins.length) {
|
|
5600
|
+
html += `<div style="color:var(--dim);font-size:10px;padding:8px 0;">No plugins installed. Browse plugins via /mastermind:plugins --action list.</div>`;
|
|
5601
|
+
el.innerHTML = html;
|
|
5602
|
+
return;
|
|
5603
|
+
}
|
|
5604
|
+
|
|
5605
|
+
html += `<div class="plugin-grid">`;
|
|
5606
|
+
plugins.forEach(p => {
|
|
5607
|
+
const statusCls = p.status === 'error' ? 'plugin-status-error'
|
|
5608
|
+
: p.status === 'disabled' ? 'plugin-status-disabled' : 'plugin-status-installed';
|
|
5609
|
+
const healthOk = !p.health || p.health.status === 'ok';
|
|
5610
|
+
html += `<div class="plugin-card">
|
|
5611
|
+
<div class="plugin-card-header">
|
|
5612
|
+
<div class="plugin-status-dot ${statusCls}"></div>
|
|
5613
|
+
<div class="plugin-name">${escHtml(p.id)}</div>
|
|
5614
|
+
<div class="plugin-version">${escHtml(p.version || '')}</div>
|
|
5615
|
+
</div>
|
|
5616
|
+
${p.category ? `<div class="plugin-category">${escHtml(p.category)}</div>` : ''}
|
|
5617
|
+
${p.packageName ? `<div class="plugin-pkg">${escHtml(p.packageName)}</div>` : ''}
|
|
5618
|
+
${p._orgOverride ? `<div class="plugin-override">⚠ org override</div>` : ''}
|
|
5619
|
+
${!healthOk ? `<div class="plugin-error">${escHtml(p.health.message || 'health error')}</div>` : ''}
|
|
5620
|
+
${p.lastError ? `<div class="plugin-error">${escHtml(p.lastError)}</div>` : ''}
|
|
5621
|
+
</div>`;
|
|
5622
|
+
});
|
|
5623
|
+
html += `</div>`;
|
|
5624
|
+
|
|
5625
|
+
el.innerHTML = html;
|
|
5626
|
+
}
|
|
5627
|
+
|
|
5628
|
+
// ─── MY ISSUES TAB ──────────────────────────────────────────────────
|
|
5629
|
+
function renderOrgMyIssues(data) {
|
|
5630
|
+
const el = document.getElementById('orgroom-myissues-body');
|
|
5631
|
+
if (!el) return;
|
|
5632
|
+
const issueData = (data && data._myissues) || { issues: [] };
|
|
5633
|
+
const issues = issueData.issues || [];
|
|
5634
|
+
const orgName = data && (data.name || data.org_name || '');
|
|
5635
|
+
let html = '';
|
|
5636
|
+
|
|
5637
|
+
const prioClass = p => p === 'high' || p === 'urgent' ? 'myissues-priority-high'
|
|
5638
|
+
: p === 'low' ? 'myissues-priority-low' : 'myissues-priority-medium';
|
|
5639
|
+
const stClass = s => s === 'in_progress' ? 'myissues-status-in_progress' : 'myissues-status-open';
|
|
5640
|
+
|
|
5641
|
+
const open = issues.filter(i => i.status === 'open' || i.status === 'in_progress');
|
|
5642
|
+
const done = issues.filter(i => i.status === 'done' || i.status === 'cancelled');
|
|
5643
|
+
|
|
5644
|
+
html += `<div style="font-size:9px;color:var(--dim);margin-bottom:10px;">
|
|
5645
|
+
${open.length} open / in-progress · ${done.length} closed
|
|
5646
|
+
<span style="font-family:monospace;color:rgba(0,200,160,0.5);">/mastermind:my-issues --org ${escHtml(orgName)}</span>
|
|
5647
|
+
</div>`;
|
|
5648
|
+
|
|
5649
|
+
if (!open.length && !done.length) {
|
|
5650
|
+
html += `<div class="myissues-empty">No issues found. Assign issues via: /mastermind:my-issues --org ${escHtml(orgName)} --action assign-self --issue-id <id></div>`;
|
|
5651
|
+
el.innerHTML = html;
|
|
5652
|
+
return;
|
|
5653
|
+
}
|
|
5654
|
+
|
|
5655
|
+
html += `<table class="orgroom-table"><thead><tr>
|
|
5656
|
+
<th>ID</th><th>STATUS</th><th>PRIORITY</th><th>TITLE</th><th>ASSIGNEE</th>
|
|
5657
|
+
</tr></thead><tbody>`;
|
|
5658
|
+
|
|
5659
|
+
[...open, ...done].slice(0, 50).forEach(i => {
|
|
5660
|
+
const pri = (i.priority || 'medium').toLowerCase();
|
|
5661
|
+
const st = (i.status || 'open').toLowerCase();
|
|
5662
|
+
html += `<tr>
|
|
5663
|
+
<td style="font-family:monospace;font-size:9px;">${escHtml(i.id || '')}</td>
|
|
5664
|
+
<td><span class="${stClass(st)}">${escHtml(st)}</span></td>
|
|
5665
|
+
<td><span class="${prioClass(pri)}">${escHtml(pri)}</span></td>
|
|
5666
|
+
<td style="max-width:220px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">${escHtml(i.title || '(no title)')}</td>
|
|
5667
|
+
<td style="font-size:9px;color:var(--dim);">${escHtml(i.assigneeId || i.assigned_to || '—')}</td>
|
|
5668
|
+
</tr>`;
|
|
5669
|
+
});
|
|
5670
|
+
html += `</tbody></table>`;
|
|
5671
|
+
|
|
5672
|
+
if (issues.length > 50) {
|
|
5673
|
+
html += `<div class="myissues-hint">Showing 50 of ${issues.length} issues.</div>`;
|
|
5674
|
+
}
|
|
5675
|
+
html += `<div class="myissues-hint">Assign to self: /mastermind:my-issues --org ${escHtml(orgName)} --action assign-self --issue-id <id></div>`;
|
|
5676
|
+
|
|
5677
|
+
el.innerHTML = html;
|
|
5678
|
+
}
|
|
5679
|
+
|
|
5680
|
+
// ─── AGENTS TAB ─────────────────────────────────────────────────────
|
|
5681
|
+
function renderOrgAgents(data) {
|
|
5682
|
+
const el = document.getElementById('orgroom-agents-body');
|
|
5683
|
+
if (!el) return;
|
|
5684
|
+
const agents = (data._agents && data._agents.agents) || (data.config && data.config.roles) || [];
|
|
5685
|
+
const stateMap = (data.state && data.state.agents) || {};
|
|
5686
|
+
|
|
5687
|
+
if (!agents.length) {
|
|
5688
|
+
el.innerHTML = '<div style="color:var(--dim);padding:16px">No agents defined. Add one: /mastermind:new-agent</div>';
|
|
5689
|
+
return;
|
|
5690
|
+
}
|
|
5691
|
+
|
|
5692
|
+
const statusColor = s => s === 'running' ? '#00e6b4' : s === 'paused' ? '#ffb400' : s === 'error' ? '#ff5050' : s === 'idle' ? 'rgba(0,200,160,0.4)' : 'var(--dim)';
|
|
5693
|
+
|
|
5694
|
+
let html = `<table class="orgroom-table">
|
|
5695
|
+
<tr><th>ID</th><th>TITLE</th><th>ADAPTER</th><th>STATUS</th><th>HEARTBEAT</th><th>TOKENS</th></tr>`;
|
|
5696
|
+
|
|
5697
|
+
agents.forEach(a => {
|
|
5698
|
+
const id = a.id || a.id;
|
|
5699
|
+
const title = a.title || id;
|
|
5700
|
+
const adapterType = a.adapterType || (a.adapter && a.adapter.type) || '-';
|
|
5701
|
+
const adapterModel = a.adapterModel || (a.adapter && a.adapter.model) || '';
|
|
5702
|
+
const s = stateMap[id] || {};
|
|
5703
|
+
const status = a.status || s.status || 'idle';
|
|
5704
|
+
const hb = a.lastHeartbeat || s.last_heartbeat || s.lastHeartbeat || null;
|
|
5705
|
+
const hbStr = hb ? new Date(hb).toLocaleTimeString() : '—';
|
|
5706
|
+
const tokIn = a.tokensIn || s.tokens_in || 0;
|
|
5707
|
+
const tokOut = a.tokensOut || s.tokens_out || 0;
|
|
5708
|
+
const tok = tokIn + tokOut;
|
|
5709
|
+
html += `<tr>
|
|
5710
|
+
<td style="color:rgba(0,230,180,0.7);font-size:9px">${escHtml(id)}</td>
|
|
5711
|
+
<td style="color:rgba(0,230,180,0.9)">${escHtml(title)}</td>
|
|
5712
|
+
<td><div style="font-size:9px">${escHtml(adapterType)}</div>${adapterModel ? `<div style="font-size:8px;color:var(--dim)">${escHtml(adapterModel)}</div>` : ''}</td>
|
|
5713
|
+
<td><span style="color:${statusColor(status)};font-size:9px">● ${escHtml(status)}</span></td>
|
|
5714
|
+
<td style="font-size:9px;color:var(--muted)">${hbStr}</td>
|
|
5715
|
+
<td style="font-size:9px;color:var(--muted)">${tok > 0 ? tok.toLocaleString() : '—'}</td>
|
|
5716
|
+
</tr>`;
|
|
5717
|
+
});
|
|
5718
|
+
|
|
5719
|
+
html += `</table>`;
|
|
5720
|
+
html += `<div style="margin-top:8px;font-size:9px;color:var(--dim)">${agents.length} agent(s) · /mastermind:agents --org <org> · /mastermind:agent-detail --org <org> --agent-id <id></div>`;
|
|
5721
|
+
el.innerHTML = html;
|
|
5722
|
+
}
|
|
5723
|
+
|
|
5724
|
+
// ─── APPROVALS TAB ──────────────────────────────────────────────────
|
|
5725
|
+
function renderOrgApprovals(data) {
|
|
5726
|
+
const el = document.getElementById('orgroom-approvals-body');
|
|
5727
|
+
if (!el) return;
|
|
5728
|
+
const approvalsData = data._approvals || {};
|
|
5729
|
+
const approvals = approvalsData.approvals || data.approvals || [];
|
|
5730
|
+
const pending = approvalsData.pending || approvals.filter(a => a.status === 'pending' || a.status === 'revision_requested').length;
|
|
5731
|
+
|
|
5732
|
+
if (!approvals.length) {
|
|
5733
|
+
el.innerHTML = '<div style="color:var(--dim);padding:16px">No approvals found. Agents will post here when they need human sign-off.</div>';
|
|
5734
|
+
return;
|
|
5735
|
+
}
|
|
5736
|
+
|
|
5737
|
+
const statusColor = s => s === 'pending' ? '#ffb400' : s === 'approved' ? '#00e6b4' : s === 'rejected' ? '#ff5050' : s === 'revision_requested' ? '#ff9800' : 'var(--dim)';
|
|
5738
|
+
|
|
5739
|
+
let html = `<div style="margin-bottom:8px;font-size:9px;color:var(--dim)">${pending} pending · ${approvals.length} total · /mastermind:approve --org <org></div>`;
|
|
5740
|
+
html += `<table class="orgroom-table">
|
|
5741
|
+
<tr><th>ID</th><th>TITLE / ACTION</th><th>AGENT</th><th>STATUS</th><th>CREATED</th></tr>`;
|
|
5742
|
+
|
|
5743
|
+
approvals.forEach(a => {
|
|
5744
|
+
const title = a.title || a.action || a.id;
|
|
5745
|
+
const ts = a.createdAt ? new Date(a.createdAt).toLocaleDateString() : '—';
|
|
5746
|
+
html += `<tr>
|
|
5747
|
+
<td style="font-size:9px;color:var(--dim)">${escHtml(a.id || '')}</td>
|
|
5748
|
+
<td style="color:rgba(0,230,180,0.9)">${escHtml(title)}</td>
|
|
5749
|
+
<td style="font-size:9px;color:var(--muted)">${escHtml(a.agentId || a.agentTitle || '—')}</td>
|
|
5750
|
+
<td><span style="color:${statusColor(a.status)};font-size:9px">● ${escHtml(a.status || 'pending')}</span></td>
|
|
5751
|
+
<td style="font-size:9px;color:var(--dim)">${ts}</td>
|
|
5752
|
+
</tr>`;
|
|
5753
|
+
});
|
|
5754
|
+
|
|
5755
|
+
html += `</table>`;
|
|
5756
|
+
el.innerHTML = html;
|
|
5757
|
+
}
|
|
5758
|
+
|
|
5759
|
+
// ─── SECRETS TAB ─────────────────────────────────────────────────────
|
|
5760
|
+
function renderOrgSecrets(data) {
|
|
5761
|
+
const el = document.getElementById('orgroom-secrets-body');
|
|
5762
|
+
if (!el) return;
|
|
5763
|
+
const secrets = (data._secrets && data._secrets.secrets) || [];
|
|
5764
|
+
|
|
5765
|
+
if (!secrets.length) {
|
|
5766
|
+
el.innerHTML = `<div style="color:var(--dim);padding:16px">No secrets stored.
|
|
5767
|
+
<div style="margin-top:8px;font-size:9px">/mastermind:secrets --org <org> --action set --secret-name MY_KEY --secret-value \$MY_ENV_VAR</div>
|
|
5768
|
+
</div>`;
|
|
5769
|
+
return;
|
|
5770
|
+
}
|
|
5771
|
+
|
|
5772
|
+
const statusColor = s => s === 'active' ? '#00e6b4' : s === 'rotated' ? '#ffb400' : '#ff5050';
|
|
5773
|
+
|
|
5774
|
+
let html = `<div style="margin-bottom:8px;font-size:9px;color:var(--dim)">⚠ Values are never displayed · ${secrets.length} secret(s) stored</div>`;
|
|
5775
|
+
html += `<table class="orgroom-table">
|
|
5776
|
+
<tr><th>NAME</th><th>MASKED REF</th><th>STATUS</th><th>LAST USED</th><th>USES</th></tr>`;
|
|
5777
|
+
|
|
5778
|
+
secrets.forEach(s => {
|
|
5779
|
+
const lastUsed = s.lastUsedAt ? new Date(s.lastUsedAt).toLocaleDateString() : '—';
|
|
5780
|
+
html += `<tr>
|
|
5781
|
+
<td style="color:rgba(0,230,180,0.9)">${escHtml(s.name || '')}</td>
|
|
5782
|
+
<td style="font-size:9px;color:var(--dim);font-family:monospace">${escHtml(s.maskedRef || '***')}</td>
|
|
5783
|
+
<td><span style="color:${statusColor(s.status)};font-size:9px">● ${escHtml(s.status || 'active')}</span></td>
|
|
5784
|
+
<td style="font-size:9px;color:var(--muted)">${lastUsed}</td>
|
|
5785
|
+
<td style="font-size:9px;color:var(--muted)">${s.usageCount || 0}</td>
|
|
5786
|
+
</tr>`;
|
|
5787
|
+
});
|
|
5788
|
+
|
|
5789
|
+
html += `</table>`;
|
|
5790
|
+
html += `<div style="margin-top:8px;font-size:9px;color:var(--dim)">/mastermind:secrets --org <org> --action list/set/rotate/revoke</div>`;
|
|
5791
|
+
el.innerHTML = html;
|
|
5792
|
+
}
|
|
5793
|
+
|
|
5794
|
+
// ─── ENVIRONMENTS TAB ────────────────────────────────────────────────
|
|
5795
|
+
function renderOrgEnvs(data) {
|
|
5796
|
+
const el = document.getElementById('orgroom-envs-body');
|
|
5797
|
+
if (!el) return;
|
|
5798
|
+
const envsData = data._environments || {};
|
|
5799
|
+
const envs = envsData.environments || [];
|
|
5800
|
+
|
|
5801
|
+
if (!envs.length) {
|
|
5802
|
+
el.innerHTML = `<div style="color:var(--dim);padding:16px">No environments configured.
|
|
5803
|
+
<div style="margin-top:8px;font-size:9px">/mastermind:environments --org <org> --action create --name prod --driver ssh</div>
|
|
5804
|
+
</div>`;
|
|
5805
|
+
return;
|
|
5806
|
+
}
|
|
5807
|
+
|
|
5808
|
+
const driverColor = d => d === 'local' ? 'rgba(0,230,180,0.7)' : d === 'ssh' ? '#4fc3f7' : d === 'sandbox' ? '#ffb400' : 'var(--dim)';
|
|
5809
|
+
|
|
5810
|
+
let html = `<table class="orgroom-table">
|
|
5811
|
+
<tr><th>NAME</th><th>DRIVER</th><th>HOST / PATH</th><th>STATUS</th><th>DEFAULT</th></tr>`;
|
|
5812
|
+
|
|
5813
|
+
envs.forEach(env => {
|
|
5814
|
+
const host = env.config && (env.config.host || env.config.remoteWorkspacePath || env.config.provider || '—');
|
|
5815
|
+
const isDefault = envsData.default_env === env.id ? '★' : '';
|
|
5816
|
+
html += `<tr>
|
|
5817
|
+
<td style="color:rgba(0,230,180,0.9)">${escHtml(env.name || env.id || '')}</td>
|
|
5818
|
+
<td><span style="color:${driverColor(env.driver)};font-size:9px">${escHtml(env.driver || '—')}</span></td>
|
|
5819
|
+
<td style="font-size:9px;color:var(--muted)">${escHtml(host ? String(host) : '—')}</td>
|
|
5820
|
+
<td><span style="color:${env.probeStatus === 'ok' ? '#00e6b4' : env.probeStatus === 'error' ? '#ff5050' : 'var(--dim)'};font-size:9px">● ${escHtml(env.probeStatus || 'unknown')}</span></td>
|
|
5821
|
+
<td style="color:rgba(0,230,180,0.9);text-align:center">${isDefault}</td>
|
|
5822
|
+
</tr>`;
|
|
5823
|
+
});
|
|
5824
|
+
|
|
5825
|
+
html += `</table>`;
|
|
5826
|
+
html += `<div style="margin-top:8px;font-size:9px;color:var(--dim)">/mastermind:environments --org <org> · /mastermind:env --org <org></div>`;
|
|
5827
|
+
el.innerHTML = html;
|
|
5828
|
+
}
|
|
5829
|
+
|
|
5830
|
+
// ─── ACCESS TAB ──────────────────────────────────────────────────────
|
|
5831
|
+
function renderOrgAccess(data) {
|
|
5832
|
+
const el = document.getElementById('orgroom-access-body');
|
|
5833
|
+
if (!el) return;
|
|
5834
|
+
const membersData = data._members || {};
|
|
5835
|
+
const members = membersData.members || [];
|
|
5836
|
+
|
|
5837
|
+
if (!members.length) {
|
|
5838
|
+
el.innerHTML = `<div style="color:var(--dim);padding:16px">No members found.
|
|
5839
|
+
<div style="margin-top:8px;font-size:9px">/mastermind:invites --org <org> --action create · /mastermind:access --org <org></div>
|
|
5840
|
+
</div>`;
|
|
5841
|
+
return;
|
|
5842
|
+
}
|
|
5843
|
+
|
|
5844
|
+
const roleColor = r => r === 'owner' ? 'rgba(0,230,180,1)' : r === 'admin' ? 'rgba(0,230,180,0.7)' : r === 'operator' ? '#4fc3f7' : 'var(--muted)';
|
|
5845
|
+
const PERMISSION_KEYS = ['agents:create','users:invite','users:manage_permissions','tasks:assign','joins:approve','environments:manage'];
|
|
5846
|
+
|
|
5847
|
+
let html = `<div style="margin-bottom:8px;font-size:9px;color:var(--dim)">${members.length} member(s) · /mastermind:access --org <org></div>`;
|
|
5848
|
+
html += `<table class="orgroom-table">
|
|
5849
|
+
<tr><th>MEMBER</th><th>TYPE</th><th>ROLE</th><th>STATUS</th><th>GRANTS</th></tr>`;
|
|
5850
|
+
|
|
5851
|
+
members.forEach(m => {
|
|
5852
|
+
const name = m.displayName || m.name || m.id;
|
|
5853
|
+
const role = m.membershipRole || m.role || '-';
|
|
5854
|
+
const grants = (m.grants || []).map(g => g.permissionKey || g).join(', ') || '—';
|
|
5855
|
+
const typeLabel = m.memberType === 'agent' ? '🤖' : '👤';
|
|
5856
|
+
html += `<tr>
|
|
5857
|
+
<td style="color:rgba(0,230,180,0.9)">${typeLabel} ${escHtml(name)}</td>
|
|
5858
|
+
<td style="font-size:9px;color:var(--dim)">${escHtml(m.memberType || 'human')}</td>
|
|
5859
|
+
<td><span style="color:${roleColor(role)};font-size:9px">${escHtml(role)}</span></td>
|
|
5860
|
+
<td><span style="color:${m.status === 'active' ? '#00e6b4' : '#ff5050'};font-size:9px">● ${escHtml(m.status || 'active')}</span></td>
|
|
5861
|
+
<td style="font-size:9px;color:var(--dim);max-width:200px;overflow:hidden;text-overflow:ellipsis">${escHtml(grants)}</td>
|
|
5862
|
+
</tr>`;
|
|
5863
|
+
});
|
|
5864
|
+
|
|
5865
|
+
html += `</table>`;
|
|
5866
|
+
el.innerHTML = html;
|
|
5867
|
+
}
|
|
5868
|
+
|
|
5869
|
+
// ─── BUDGETS TAB ──────────────────────────────────────────────────
|
|
5870
|
+
function renderOrgBudgets(data) {
|
|
5871
|
+
const el = document.getElementById('orgroom-budgets-body');
|
|
5872
|
+
if (!el) return;
|
|
5873
|
+
const bd = data._budgets || {};
|
|
5874
|
+
const orgBudget = bd.org_budget || {};
|
|
5875
|
+
const agentBudgets = bd.agent_budgets || {};
|
|
5876
|
+
const agents = (bd.agents || (data.config && data.config.roles) || []);
|
|
5877
|
+
const stateRoles = ((data._state || {}).roles || []);
|
|
5878
|
+
const orgName = _orgroomCurrentOrg;
|
|
5879
|
+
|
|
5880
|
+
const agentSpend = {};
|
|
5881
|
+
stateRoles.forEach(r => {
|
|
5882
|
+
agentSpend[r.id] = {
|
|
5883
|
+
tokensIn: r.tokens_in || 0,
|
|
5884
|
+
tokensOut: r.tokens_out || 0,
|
|
5885
|
+
cost: r.total_cost_usd || 0,
|
|
5886
|
+
};
|
|
5887
|
+
});
|
|
5888
|
+
|
|
5889
|
+
let orgTokensSpent = 0, orgCostSpent = 0;
|
|
5890
|
+
Object.values(agentSpend).forEach(a => { orgTokensSpent += a.tokensIn + a.tokensOut; orgCostSpent += a.cost; });
|
|
5891
|
+
|
|
5892
|
+
const fmtUsd = v => `$${Number(v || 0).toFixed(4)}`;
|
|
5893
|
+
const pctBar = (spent, lim) => {
|
|
5894
|
+
if (!lim) return '';
|
|
5895
|
+
const pct = Math.min(100, spent / lim * 100);
|
|
5896
|
+
const filled = Math.round(pct / 5);
|
|
5897
|
+
const bar = '█'.repeat(filled) + '░'.repeat(20 - filled);
|
|
5898
|
+
const color = pct > 90 ? '#ff5050' : pct > 70 ? '#ffd54f' : '#00e6b4';
|
|
5899
|
+
return `<span style="color:${color};font-family:monospace;font-size:9px">[${bar}] ${pct.toFixed(0)}%</span>`;
|
|
5900
|
+
};
|
|
5901
|
+
|
|
5902
|
+
let html = `<div style="margin-bottom:12px">
|
|
5903
|
+
<div style="font-size:9px;color:var(--dim);margin-bottom:4px">ORG-WIDE</div>
|
|
5904
|
+
<table class="orgroom-table">
|
|
5905
|
+
<tr><th>METRIC</th><th>SPENT</th><th>LIMIT</th><th>USAGE</th></tr>
|
|
5906
|
+
<tr>
|
|
5907
|
+
<td>Tokens</td>
|
|
5908
|
+
<td>${orgTokensSpent.toLocaleString()}</td>
|
|
5909
|
+
<td>${orgBudget.limit_tokens ? orgBudget.limit_tokens.toLocaleString() : '—'}</td>
|
|
5910
|
+
<td>${pctBar(orgTokensSpent, orgBudget.limit_tokens)}</td>
|
|
5911
|
+
</tr>
|
|
5912
|
+
<tr>
|
|
5913
|
+
<td>Cost (USD)</td>
|
|
5914
|
+
<td>${fmtUsd(orgCostSpent)}</td>
|
|
5915
|
+
<td>${orgBudget.limit_usd ? '$' + Number(orgBudget.limit_usd).toFixed(2) : '—'}</td>
|
|
5916
|
+
<td>${pctBar(orgCostSpent, orgBudget.limit_usd)}</td>
|
|
5917
|
+
</tr>
|
|
5918
|
+
</table>
|
|
5919
|
+
</div>`;
|
|
5920
|
+
|
|
5921
|
+
html += `<div style="font-size:9px;color:var(--dim);margin-bottom:4px">PER AGENT</div>`;
|
|
5922
|
+
html += `<table class="orgroom-table">
|
|
5923
|
+
<tr><th>AGENT</th><th>TOKENS IN</th><th>TOKENS OUT</th><th>COST</th><th>LIMIT</th></tr>`;
|
|
5924
|
+
|
|
5925
|
+
agents.slice(0, 30).forEach(a => {
|
|
5926
|
+
const rid = a.id;
|
|
5927
|
+
const title = a.title || rid;
|
|
5928
|
+
const sp = agentSpend[rid] || { tokensIn: 0, tokensOut: 0, cost: 0 };
|
|
5929
|
+
const ab = agentBudgets[rid] || {};
|
|
5930
|
+
const lim = ab.limit_usd ? '$' + Number(ab.limit_usd).toFixed(2) : '—';
|
|
5931
|
+
const over = ab.limit_usd && sp.cost > ab.limit_usd;
|
|
5932
|
+
html += `<tr ${over ? 'style="color:#ff5050"' : ''}>
|
|
5933
|
+
<td>${escHtml(title)}${over ? ' ⚠' : ''}</td>
|
|
5934
|
+
<td>${sp.tokensIn.toLocaleString()}</td>
|
|
5935
|
+
<td>${sp.tokensOut.toLocaleString()}</td>
|
|
5936
|
+
<td>${fmtUsd(sp.cost)}</td>
|
|
5937
|
+
<td style="font-size:9px;color:var(--dim)">${lim}</td>
|
|
5938
|
+
</tr>`;
|
|
5939
|
+
});
|
|
5940
|
+
|
|
5941
|
+
html += `</table>
|
|
5942
|
+
<div style="margin-top:8px;font-size:9px;color:var(--dim)">/mastermind:budgets --org <org> · /mastermind:budgets --org <org> --action set --agent-id <id> --limit-usd 5.00</div>`;
|
|
5943
|
+
el.innerHTML = html;
|
|
5944
|
+
}
|
|
5945
|
+
|
|
5946
|
+
// ─── THREADS TAB ──────────────────────────────────────────────────
|
|
5947
|
+
function renderOrgThreads(data) {
|
|
5948
|
+
const el = document.getElementById('orgroom-threads-body');
|
|
5949
|
+
if (!el) return;
|
|
5950
|
+
const td = data._threads || {};
|
|
5951
|
+
const threads = td.threads || [];
|
|
5952
|
+
const orgName = _orgroomCurrentOrg;
|
|
5953
|
+
|
|
5954
|
+
if (!threads.length) {
|
|
5955
|
+
el.innerHTML = `<div style="color:var(--dim);padding:16px">No threads found.
|
|
5956
|
+
<div style="margin-top:8px;font-size:9px">/mastermind:threads --org ${escHtml(orgName)} --action create --message 'Hello team'</div>
|
|
5957
|
+
</div>`;
|
|
5958
|
+
return;
|
|
5959
|
+
}
|
|
5960
|
+
|
|
5961
|
+
let html = `<div style="margin-bottom:8px;font-size:9px;color:var(--dim)">${threads.length} thread(s) · /mastermind:threads --org <org></div>`;
|
|
5962
|
+
html += `<table class="orgroom-table">
|
|
5963
|
+
<tr><th>SUBJECT</th><th>AUTHOR</th><th>MSGS</th><th>ISSUE</th><th>CREATED</th></tr>`;
|
|
5964
|
+
|
|
5965
|
+
threads.slice(0, 50).forEach(t => {
|
|
5966
|
+
const subj = (t.subject || '(no subject)').slice(0, 40);
|
|
5967
|
+
const author = (t.authorName || t.authorId || '—').slice(0, 20);
|
|
5968
|
+
const msgs = (t.messages || []).length;
|
|
5969
|
+
const issue = t.issueId || '—';
|
|
5970
|
+
const created = (t.createdAt || '-').slice(0, 10);
|
|
5971
|
+
html += `<tr>
|
|
5972
|
+
<td style="color:rgba(0,230,180,0.9)">${escHtml(subj)}</td>
|
|
5973
|
+
<td style="font-size:9px;color:var(--dim)">${escHtml(author)}</td>
|
|
5974
|
+
<td>${msgs}</td>
|
|
5975
|
+
<td style="font-size:9px;color:var(--dim)">${escHtml(issue)}</td>
|
|
5976
|
+
<td style="font-size:9px;color:var(--dim)">${escHtml(created)}</td>
|
|
5977
|
+
</tr>`;
|
|
5978
|
+
});
|
|
5979
|
+
|
|
5980
|
+
html += `</table>`;
|
|
5981
|
+
el.innerHTML = html;
|
|
5982
|
+
}
|
|
5983
|
+
|
|
5984
|
+
// ─── ISSUES TAB ──────────────────────────────────────────────────
|
|
5985
|
+
function renderOrgIssues(data) {
|
|
5986
|
+
const el = document.getElementById('orgroom-issues-body');
|
|
5987
|
+
if (!el) return;
|
|
5988
|
+
const issuesData = data._issues || {};
|
|
5989
|
+
const issues = issuesData.issues || [];
|
|
5990
|
+
const orgName = _orgroomCurrentOrg;
|
|
5991
|
+
|
|
5992
|
+
if (!issues.length) {
|
|
5993
|
+
el.innerHTML = `<div style="color:var(--dim);padding:16px">No issues found.
|
|
5994
|
+
<div style="margin-top:8px;font-size:9px">/mastermind:issues --org ${escHtml(orgName)} --action create --title 'My Issue'</div>
|
|
5995
|
+
</div>`;
|
|
5996
|
+
return;
|
|
5997
|
+
}
|
|
5998
|
+
|
|
5999
|
+
const statusColor = s => s === 'done' ? '#00e6b4' : s === 'in_progress' ? '#4fc3f7' : s === 'in_review' ? '#ffd54f' : s === 'cancelled' ? '#ff5050' : 'var(--muted)';
|
|
6000
|
+
const priBadge = p => p === 'urgent' ? '🔴' : p === 'high' ? '🟠' : p === 'medium' ? '🟡' : '⚪';
|
|
6001
|
+
|
|
6002
|
+
let html = `<div style="margin-bottom:8px;font-size:9px;color:var(--dim)">${issues.length} issue(s) · /mastermind:issues --org <org></div>`;
|
|
6003
|
+
html += `<table class="orgroom-table">
|
|
6004
|
+
<tr><th>TITLE</th><th>STATUS</th><th>PRI</th><th>ASSIGNEE</th><th>UPDATED</th></tr>`;
|
|
6005
|
+
|
|
6006
|
+
issues.slice(0, 100).forEach(iss => {
|
|
6007
|
+
const title = iss.title || iss.id || '-';
|
|
6008
|
+
const st = iss.status || 'open';
|
|
6009
|
+
const pri = iss.priority || 'medium';
|
|
6010
|
+
const asgn = iss.assigneeTitle || iss.assigneeId || '—';
|
|
6011
|
+
const upd = (iss.updatedAt || iss.createdAt || '-').slice(0, 10);
|
|
6012
|
+
html += `<tr>
|
|
6013
|
+
<td style="color:rgba(0,230,180,0.9);max-width:220px;overflow:hidden;text-overflow:ellipsis">${escHtml(title)}</td>
|
|
6014
|
+
<td><span style="color:${statusColor(st)};font-size:9px">● ${escHtml(st)}</span></td>
|
|
6015
|
+
<td style="font-size:11px">${priBadge(pri)}</td>
|
|
6016
|
+
<td style="font-size:9px;color:var(--dim)">${escHtml(asgn)}</td>
|
|
6017
|
+
<td style="font-size:9px;color:var(--dim)">${escHtml(upd)}</td>
|
|
6018
|
+
</tr>`;
|
|
6019
|
+
});
|
|
6020
|
+
|
|
6021
|
+
html += `</table>`;
|
|
6022
|
+
if (issues.length > 100) html += `<div style="font-size:9px;color:var(--dim);margin-top:6px">Showing 100 of ${issues.length} issues.</div>`;
|
|
6023
|
+
el.innerHTML = html;
|
|
6024
|
+
}
|
|
6025
|
+
|
|
6026
|
+
// ─── JOIN REQUESTS TAB ──────────────────────────────────────────────────
|
|
6027
|
+
function renderOrgJoinQueue(data) {
|
|
6028
|
+
const el = document.getElementById('orgroom-joinqueue-body');
|
|
6029
|
+
if (!el) return;
|
|
6030
|
+
const jqData = data._joinRequests || {};
|
|
6031
|
+
const requests = jqData.requests || [];
|
|
6032
|
+
const orgName = _orgroomCurrentOrg;
|
|
6033
|
+
|
|
6034
|
+
const pending = requests.filter(r => r.status === 'pending_approval');
|
|
6035
|
+
|
|
6036
|
+
if (!requests.length) {
|
|
6037
|
+
el.innerHTML = `<div style="color:var(--dim);padding:16px">No join requests.
|
|
6038
|
+
<div style="margin-top:8px;font-size:9px">/mastermind:join-queue --org ${escHtml(orgName)}</div>
|
|
6039
|
+
</div>`;
|
|
6040
|
+
return;
|
|
6041
|
+
}
|
|
6042
|
+
|
|
6043
|
+
const stColor = s => s === 'approved' ? '#00e6b4' : s === 'rejected' ? '#ff5050' : '#ffd54f';
|
|
6044
|
+
|
|
6045
|
+
let html = `<div style="margin-bottom:8px;font-size:9px;color:var(--dim)">${requests.length} request(s) · ${pending.length} pending · /mastermind:join-queue --org <org></div>`;
|
|
6046
|
+
html += `<table class="orgroom-table">
|
|
6047
|
+
<tr><th>REQUESTER</th><th>TYPE</th><th>STATUS</th><th>CREATED</th></tr>`;
|
|
6048
|
+
|
|
6049
|
+
requests.slice(0, 50).forEach(r => {
|
|
6050
|
+
const name = r.requesterName || r.requesterId || r.id || '-';
|
|
6051
|
+
const type = r.type || 'human';
|
|
6052
|
+
const st = r.status || 'pending_approval';
|
|
6053
|
+
const created = (r.createdAt || '-').slice(0, 10);
|
|
6054
|
+
html += `<tr>
|
|
6055
|
+
<td style="color:rgba(0,230,180,0.9)">${escHtml(name)}</td>
|
|
6056
|
+
<td style="font-size:9px;color:var(--dim)">${type === 'agent' ? '🤖' : '👤'} ${escHtml(type)}</td>
|
|
6057
|
+
<td><span style="color:${stColor(st)};font-size:9px">● ${escHtml(st)}</span></td>
|
|
6058
|
+
<td style="font-size:9px;color:var(--dim)">${escHtml(created)}</td>
|
|
6059
|
+
</tr>`;
|
|
6060
|
+
});
|
|
6061
|
+
|
|
6062
|
+
html += `</table>`;
|
|
6063
|
+
el.innerHTML = html;
|
|
6064
|
+
}
|
|
6065
|
+
|
|
6066
|
+
// ─── SETTINGS TAB ──────────────────────────────────────────────────
|
|
6067
|
+
function renderOrgSettings(data) {
|
|
6068
|
+
const el = document.getElementById('orgroom-settings-body');
|
|
6069
|
+
if (!el) return;
|
|
6070
|
+
const config = (data && data.config) || {};
|
|
6071
|
+
const gov = config.governance || {};
|
|
6072
|
+
const runCfg = config.run_config || {};
|
|
6073
|
+
const orgName = _orgroomCurrentOrg;
|
|
6074
|
+
|
|
6075
|
+
let html = `<div style="max-width:520px">`;
|
|
6076
|
+
|
|
6077
|
+
// General
|
|
6078
|
+
html += `<div class="settings-section">
|
|
6079
|
+
<div class="settings-section-title">General</div>
|
|
6080
|
+
<div class="settings-row">
|
|
6081
|
+
<span class="settings-label">Org Name</span>
|
|
6082
|
+
<input class="settings-input" id="os-name" value="${escHtml(config.name || orgName)}" />
|
|
6083
|
+
</div>
|
|
6084
|
+
<div class="settings-row">
|
|
6085
|
+
<span class="settings-label">Goal</span>
|
|
6086
|
+
<input class="settings-input" id="os-goal" value="${escHtml(config.goal || '')}" />
|
|
6087
|
+
</div>
|
|
6088
|
+
<div class="settings-row">
|
|
6089
|
+
<span class="settings-label">Topology</span>
|
|
6090
|
+
<select class="settings-select" id="os-topology">
|
|
6091
|
+
${['hierarchical','mesh','hierarchical-mesh','adaptive','star','ring'].map(t =>
|
|
6092
|
+
`<option value="${t}" ${(config.topology||'hierarchical')===t?'selected':''}>${t}</option>`
|
|
6093
|
+
).join('')}
|
|
6094
|
+
</select>
|
|
6095
|
+
</div>
|
|
6096
|
+
</div>`;
|
|
6097
|
+
|
|
6098
|
+
// Governance
|
|
6099
|
+
html += `<hr class="settings-divider">
|
|
6100
|
+
<div class="settings-section">
|
|
6101
|
+
<div class="settings-section-title">Governance</div>
|
|
6102
|
+
<div class="settings-row">
|
|
6103
|
+
<span class="settings-label">Policy</span>
|
|
6104
|
+
<select class="settings-select" id="os-governance">
|
|
6105
|
+
${['auto','board','strict'].map(p =>
|
|
6106
|
+
`<option value="${p}" ${(gov.policy||'auto')===p?'selected':''}>${p}</option>`
|
|
6107
|
+
).join('')}
|
|
6108
|
+
</select>
|
|
6109
|
+
</div>
|
|
6110
|
+
<div class="settings-note" style="margin-left:140px">auto: agents act freely · board: human approves high-risk · strict: all actions need approval</div>
|
|
6111
|
+
</div>`;
|
|
6112
|
+
|
|
6113
|
+
// Budget
|
|
6114
|
+
html += `<hr class="settings-divider">
|
|
6115
|
+
<div class="settings-section">
|
|
6116
|
+
<div class="settings-section-title">Budget</div>
|
|
6117
|
+
<div class="settings-row">
|
|
6118
|
+
<span class="settings-label">Budget Tokens</span>
|
|
6119
|
+
<input class="settings-input" id="os-budget" type="number" min="0" value="${runCfg.budget_tokens || 0}" style="max-width:140px" />
|
|
6120
|
+
</div>
|
|
6121
|
+
<div class="settings-row">
|
|
6122
|
+
<span class="settings-label">Alert Threshold</span>
|
|
6123
|
+
<input class="settings-input" id="os-threshold" type="number" min="0" max="100" value="${Math.round((runCfg.alert_threshold||0.8)*100)}" style="max-width:80px" />
|
|
6124
|
+
<span style="font-size:10px;color:var(--dim)">%</span>
|
|
6125
|
+
</div>
|
|
6126
|
+
<div class="settings-row">
|
|
6127
|
+
<span class="settings-label">CEO Adapter</span>
|
|
6128
|
+
<select class="settings-select" id="os-ceo-adapter">
|
|
6129
|
+
${['claude-sonnet-4-6','claude-opus-4-7','claude-haiku-4-5'].map(m =>
|
|
6130
|
+
`<option value="${m}" ${(runCfg.ceo_adapter||'claude-sonnet-4-6')===m?'selected':''}>${m}</option>`
|
|
6131
|
+
).join('')}
|
|
6132
|
+
</select>
|
|
6133
|
+
</div>
|
|
6134
|
+
</div>`;
|
|
6135
|
+
|
|
6136
|
+
// Save / Export / Import
|
|
6137
|
+
html += `<hr class="settings-divider">
|
|
6138
|
+
<div class="settings-row" style="gap:8px;flex-wrap:wrap">
|
|
6139
|
+
<button class="settings-btn" onclick="saveOrgSettings('${escHtml(orgName)}')">Save Changes</button>
|
|
6140
|
+
<button class="settings-btn" onclick="exportOrgSettings('${escHtml(orgName)}')">Export JSON</button>
|
|
6141
|
+
<label class="settings-btn" style="cursor:pointer">
|
|
6142
|
+
Import JSON<input type="file" accept=".json" style="display:none" onchange="importOrgSettings('${escHtml(orgName)}', this)" />
|
|
6143
|
+
</label>
|
|
6144
|
+
</div>
|
|
6145
|
+
<div id="os-save-msg" style="margin-top:8px;font-size:10px;color:rgba(0,200,160,0.8)"></div>`;
|
|
6146
|
+
|
|
6147
|
+
html += `</div>`;
|
|
6148
|
+
el.innerHTML = html;
|
|
6149
|
+
}
|
|
6150
|
+
|
|
6151
|
+
function saveOrgSettings(orgName) {
|
|
6152
|
+
const fields = {
|
|
6153
|
+
name: document.getElementById('os-name')?.value,
|
|
6154
|
+
goal: document.getElementById('os-goal')?.value,
|
|
6155
|
+
topology: document.getElementById('os-topology')?.value,
|
|
6156
|
+
governance: document.getElementById('os-governance')?.value,
|
|
6157
|
+
budget_tokens: parseInt(document.getElementById('os-budget')?.value || '0', 10),
|
|
6158
|
+
alert_threshold: (parseFloat(document.getElementById('os-threshold')?.value || '80') / 100).toFixed(2),
|
|
6159
|
+
ceo_adapter: document.getElementById('os-ceo-adapter')?.value,
|
|
6160
|
+
};
|
|
6161
|
+
// Post via CLI command hint (no direct write endpoint — use mastermind:org-settings)
|
|
6162
|
+
const msg = document.getElementById('os-save-msg');
|
|
6163
|
+
if (msg) {
|
|
6164
|
+
const cmds = Object.entries(fields).filter(([k,v]) => v !== undefined && v !== '').map(([k,v]) =>
|
|
6165
|
+
`/mastermind:org-settings --org ${orgName} --action edit --field ${k} --value "${v}"`
|
|
6166
|
+
).join('\n');
|
|
6167
|
+
msg.innerHTML = `<div style="background:rgba(0,0,0,0.3);border:1px solid rgba(0,200,160,0.2);border-radius:3px;padding:8px;font-size:9px;white-space:pre;color:rgba(0,200,160,0.8)">${escHtml(cmds)}</div>
|
|
6168
|
+
<div style="margin-top:4px;color:var(--dim)">Run these commands in Claude Code to apply changes.</div>`;
|
|
6169
|
+
}
|
|
6170
|
+
}
|
|
6171
|
+
|
|
6172
|
+
function exportOrgSettings(orgName) {
|
|
6173
|
+
fetch(`/api/org/${encodeURIComponent(orgName)}`)
|
|
6174
|
+
.then(r => r.json())
|
|
6175
|
+
.then(data => {
|
|
6176
|
+
const blob = new Blob([JSON.stringify(data.config, null, 2)], { type: 'application/json' });
|
|
6177
|
+
const a = document.createElement('a');
|
|
6178
|
+
a.href = URL.createObjectURL(blob);
|
|
6179
|
+
a.download = `${orgName}-export.json`;
|
|
6180
|
+
a.click();
|
|
6181
|
+
});
|
|
6182
|
+
}
|
|
6183
|
+
|
|
6184
|
+
function importOrgSettings(orgName, input) {
|
|
6185
|
+
const file = input.files[0];
|
|
6186
|
+
if (!file) return;
|
|
6187
|
+
const msg = document.getElementById('os-save-msg');
|
|
6188
|
+
if (msg) msg.textContent = `To import: /mastermind:org-settings --org ${orgName} --action import --import-path /path/to/${escHtml(file.name)}`;
|
|
6189
|
+
}
|
|
6190
|
+
|
|
6191
|
+
// ─── CHARTS TAB ────────────────────────────────────────────────────
|
|
6192
|
+
function renderOrgCharts(data) {
|
|
6193
|
+
const el = document.getElementById('orgroom-charts-body');
|
|
6194
|
+
if (!el) return;
|
|
6195
|
+
const agents = data && data.state && data.state.agents ? data.state.agents : {};
|
|
6196
|
+
const activity = (data && data._activity) || [];
|
|
6197
|
+
const roles = (data && data.config && data.config.roles) || [];
|
|
6198
|
+
|
|
6199
|
+
// Build 14-day heatmap from activity events
|
|
6200
|
+
const days = [];
|
|
6201
|
+
for (let i = 13; i >= 0; i--) {
|
|
6202
|
+
const d = new Date(); d.setDate(d.getDate() - i);
|
|
6203
|
+
days.push(d.toISOString().slice(0, 10));
|
|
6204
|
+
}
|
|
6205
|
+
const eventsByDay = {};
|
|
6206
|
+
days.forEach(d => { eventsByDay[d] = { success: 0, fail: 0, total: 0 }; });
|
|
6207
|
+
activity.forEach(ev => {
|
|
6208
|
+
if (!ev.ts) return;
|
|
6209
|
+
const day = new Date(ev.ts).toISOString().slice(0, 10);
|
|
6210
|
+
if (!eventsByDay[day]) return;
|
|
6211
|
+
eventsByDay[day].total++;
|
|
6212
|
+
if (ev.type && ev.type.includes('complete')) eventsByDay[day].success++;
|
|
6213
|
+
if (ev.type && (ev.type.includes('error') || ev.type.includes('fail'))) eventsByDay[day].fail++;
|
|
6214
|
+
});
|
|
6215
|
+
|
|
6216
|
+
const maxTotal = Math.max(1, ...Object.values(eventsByDay).map(d => d.total));
|
|
6217
|
+
|
|
6218
|
+
let html = `<div class="chart-grid-wrap">`;
|
|
6219
|
+
|
|
6220
|
+
// 14-day activity heatmap
|
|
6221
|
+
html += `<div>
|
|
6222
|
+
<div class="chart-section-title">14-Day Activity Heatmap</div>
|
|
6223
|
+
<div style="display:flex;gap:2px;align-items:flex-end">`;
|
|
6224
|
+
days.forEach(day => {
|
|
6225
|
+
const d = eventsByDay[day];
|
|
6226
|
+
const pct = d.total / maxTotal;
|
|
6227
|
+
const cls = d.fail > 0 ? 'fail' : pct === 0 ? '' : pct < 0.25 ? 'l1' : pct < 0.5 ? 'l2' : pct < 0.75 ? 'l3' : 'l4';
|
|
6228
|
+
const dt = new Date(day + 'T12:00:00');
|
|
6229
|
+
const label = `${dt.getMonth()+1}/${dt.getDate()}`;
|
|
6230
|
+
html += `<div title="${label}: ${d.total} events${d.fail ? `, ${d.fail} errors` : ''}"
|
|
6231
|
+
style="flex:1;min-width:10px;height:${Math.max(4, Math.round(pct*60)+4)}px;border-radius:2px;
|
|
6232
|
+
background:${cls==='fail'?'rgba(255,80,60,0.6)':cls===''?'rgba(0,200,160,0.08)':
|
|
6233
|
+
cls==='l1'?'rgba(0,200,160,0.2)':cls==='l2'?'rgba(0,200,160,0.45)':
|
|
6234
|
+
cls==='l3'?'rgba(0,200,160,0.7)':'rgba(0,230,180,0.95)'}"></div>`;
|
|
6235
|
+
});
|
|
6236
|
+
html += `</div>
|
|
6237
|
+
<div style="display:flex;gap:2px;margin-top:3px">`;
|
|
6238
|
+
days.forEach((day, i) => {
|
|
6239
|
+
const dt = new Date(day + 'T12:00:00');
|
|
6240
|
+
const lbl = (i === 0 || i === 6 || i === 13) ? `${dt.getMonth()+1}/${dt.getDate()}` : '';
|
|
6241
|
+
html += `<div style="flex:1;font-size:7px;color:var(--dim);text-align:center">${lbl}</div>`;
|
|
6242
|
+
});
|
|
6243
|
+
html += `</div>
|
|
6244
|
+
<div style="display:flex;gap:12px;margin-top:6px;font-size:9px;color:var(--dim)">
|
|
6245
|
+
<span style="display:flex;align-items:center;gap:4px"><span style="display:inline-block;width:8px;height:8px;border-radius:2px;background:rgba(0,200,160,0.08)"></span>none</span>
|
|
6246
|
+
<span style="display:flex;align-items:center;gap:4px"><span style="display:inline-block;width:8px;height:8px;border-radius:2px;background:rgba(0,200,160,0.45)"></span>medium</span>
|
|
6247
|
+
<span style="display:flex;align-items:center;gap:4px"><span style="display:inline-block;width:8px;height:8px;border-radius:2px;background:rgba(0,230,180,0.95)"></span>high</span>
|
|
6248
|
+
<span style="display:flex;align-items:center;gap:4px"><span style="display:inline-block;width:8px;height:8px;border-radius:2px;background:rgba(255,80,60,0.6)"></span>errors</span>
|
|
6249
|
+
</div>
|
|
6250
|
+
</div>`;
|
|
6251
|
+
|
|
6252
|
+
// Per-agent run bars
|
|
6253
|
+
html += `<div>
|
|
6254
|
+
<div class="chart-section-title">Agent Activity (14 days)</div>`;
|
|
6255
|
+
if (roles.length) {
|
|
6256
|
+
roles.forEach(r => {
|
|
6257
|
+
const agentEvents = activity.filter(ev => ev.role === r.id || ev.agent_id === r.id);
|
|
6258
|
+
const count = agentEvents.length;
|
|
6259
|
+
const errors = agentEvents.filter(ev => ev.type && (ev.type.includes('error') || ev.type.includes('fail'))).length;
|
|
6260
|
+
const maxCount = Math.max(1, activity.length / roles.length * 2);
|
|
6261
|
+
const pct = Math.min(100, Math.round(count / maxCount * 100));
|
|
6262
|
+
const agentState = agents[r.id] || {};
|
|
6263
|
+
const statusCls = agentState.status === 'running' ? 'running' : agentState.status || '';
|
|
6264
|
+
html += `<div class="run-bar-row">
|
|
6265
|
+
<div class="run-bar-label">
|
|
6266
|
+
<span class="orgroom-status-dot ${statusCls}"></span>
|
|
6267
|
+
${escHtml(r.id)}
|
|
6268
|
+
</div>
|
|
6269
|
+
<div class="run-bar-track">
|
|
6270
|
+
<div class="run-bar-fill" style="width:${pct}%;${errors>0?'background:linear-gradient(90deg,rgba(200,80,60,0.5),rgba(255,100,80,0.7))':''}"></div>
|
|
6271
|
+
</div>
|
|
6272
|
+
<span class="run-bar-count">${count}${errors ? ` <span style="color:rgba(255,80,60,0.8)">(${errors}err)</span>` : ''}</span>
|
|
6273
|
+
</div>`;
|
|
6274
|
+
});
|
|
6275
|
+
} else {
|
|
6276
|
+
html += `<div style="color:var(--dim)">No agent data yet.</div>`;
|
|
6277
|
+
}
|
|
6278
|
+
html += `</div>`;
|
|
6279
|
+
|
|
6280
|
+
// Summary stats
|
|
6281
|
+
const totalEvents = activity.length;
|
|
6282
|
+
const totalErrors = activity.filter(ev => ev.type && (ev.type.includes('error') || ev.type.includes('fail'))).length;
|
|
6283
|
+
const successRate = totalEvents > 0 ? Math.round((1 - totalErrors/totalEvents)*100) : 100;
|
|
6284
|
+
html += `<div style="display:flex;gap:12px;flex-wrap:wrap">
|
|
6285
|
+
${[
|
|
6286
|
+
['TOTAL EVENTS', totalEvents, 'rgba(0,200,160,0.9)'],
|
|
6287
|
+
['ERRORS', totalErrors, totalErrors > 0 ? 'rgba(255,80,60,0.9)' : 'var(--dim)'],
|
|
6288
|
+
['SUCCESS RATE', successRate+'%', successRate >= 90 ? 'rgba(0,229,135,0.9)' : 'rgba(255,180,0,0.9)'],
|
|
6289
|
+
['ACTIVE AGENTS', Object.values(agents).filter(a => a.status === 'running').length, 'rgba(0,229,135,0.9)'],
|
|
6290
|
+
].map(([label, val, color]) => `<div style="background:rgba(0,0,0,0.35);border:1px solid rgba(0,200,160,0.12);border-radius:4px;padding:8px 14px;min-width:110px">
|
|
6291
|
+
<div style="font-size:8px;color:var(--dim);margin-bottom:3px">${label}</div>
|
|
6292
|
+
<div style="font-size:16px;color:${color}">${val}</div>
|
|
6293
|
+
</div>`).join('')}
|
|
6294
|
+
</div>`;
|
|
6295
|
+
|
|
6296
|
+
html += `</div>`;
|
|
6297
|
+
el.innerHTML = html;
|
|
6298
|
+
}
|
|
6299
|
+
|
|
6300
|
+
// ─── MEMBERS TAB ───────────────────────────────────────────────────
|
|
6301
|
+
function renderOrgMembers(data) {
|
|
6302
|
+
const el = document.getElementById('orgroom-members-body');
|
|
6303
|
+
if (!el) return;
|
|
6304
|
+
const orgName = _orgroomCurrentOrg;
|
|
6305
|
+
const membersData = (data && data._members) || { members: [], join_requests: [] };
|
|
6306
|
+
const members = membersData.members || [];
|
|
6307
|
+
const joinReqs = (membersData.join_requests || []).filter(r => r.status === 'pending');
|
|
6308
|
+
|
|
6309
|
+
let html = ``;
|
|
6310
|
+
|
|
6311
|
+
// Invite form
|
|
6312
|
+
html += `<div style="margin-bottom:14px">
|
|
6313
|
+
<div class="chart-section-title">Invite New Member</div>
|
|
6314
|
+
<div class="invite-form">
|
|
6315
|
+
<select class="invite-select" id="invite-role-select">
|
|
6316
|
+
<option value="viewer">Viewer — read only</option>
|
|
6317
|
+
<option value="operator" selected>Operator — assign tasks</option>
|
|
6318
|
+
<option value="admin">Admin — create agents, invite</option>
|
|
6319
|
+
<option value="owner">Owner — full access</option>
|
|
6320
|
+
</select>
|
|
6321
|
+
<button class="invite-btn" onclick="generateOrgInvite('${escHtml(orgName)}')">Generate Invite</button>
|
|
6322
|
+
</div>
|
|
6323
|
+
<div id="invite-url-output"></div>
|
|
6324
|
+
</div>`;
|
|
3750
6325
|
|
|
3751
|
-
|
|
3752
|
-
if (
|
|
3753
|
-
|
|
6326
|
+
// Pending join requests
|
|
6327
|
+
if (joinReqs.length) {
|
|
6328
|
+
html += `<div style="margin-bottom:14px">
|
|
6329
|
+
<div class="chart-section-title" style="color:rgba(255,180,0,0.7)">Pending Join Requests (${joinReqs.length})</div>`;
|
|
6330
|
+
joinReqs.forEach(r => {
|
|
6331
|
+
html += `<div class="member-row" style="border-color:rgba(255,180,0,0.2)">
|
|
6332
|
+
<div class="member-avatar" style="background:rgba(255,180,0,0.15);color:rgba(255,200,60,0.9)">?</div>
|
|
6333
|
+
<div style="flex:1">
|
|
6334
|
+
<div class="member-id">${escHtml(r.id)}</div>
|
|
6335
|
+
<div style="font-size:9px;color:var(--dim)">requested role: ${escHtml(r.role || 'viewer')} · ${escHtml(r.createdAt || '')}</div>
|
|
6336
|
+
</div>
|
|
6337
|
+
<div style="display:flex;gap:6px">
|
|
6338
|
+
<button class="invite-btn" style="font-size:9px;padding:2px 8px" onclick="orgAccessCmd('${escHtml(orgName)}','approve-join','${escHtml(r.id)}')">Approve</button>
|
|
6339
|
+
<button class="invite-btn" style="font-size:9px;padding:2px 8px;border-color:rgba(255,80,80,0.4);color:rgba(255,80,80,0.8)" onclick="orgAccessCmd('${escHtml(orgName)}','reject-join','${escHtml(r.id)}')">Reject</button>
|
|
6340
|
+
</div>
|
|
6341
|
+
</div>`;
|
|
6342
|
+
});
|
|
6343
|
+
html += `</div>`;
|
|
3754
6344
|
}
|
|
3755
6345
|
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
if (!
|
|
6346
|
+
// Member list
|
|
6347
|
+
html += `<div class="chart-section-title">Members (${members.length})</div>`;
|
|
6348
|
+
if (!members.length) {
|
|
6349
|
+
html += `<div style="color:var(--dim);padding:20px;text-align:center">No members yet. Use the invite form above to add the first member.</div>`;
|
|
6350
|
+
} else {
|
|
6351
|
+
members.forEach(m => {
|
|
6352
|
+
const roleClass = `role-${m.role || 'viewer'}`;
|
|
6353
|
+
const initials = (m.id || '?').slice(0, 2).toUpperCase();
|
|
6354
|
+
const grants = (m.grants || []);
|
|
6355
|
+
html += `<div class="member-row ${m.status === 'suspended' ? 'member-status-suspended' : ''}">
|
|
6356
|
+
<div class="member-avatar">${initials}</div>
|
|
6357
|
+
<div style="flex:1">
|
|
6358
|
+
<div class="member-id">${escHtml(m.id)}${m.status === 'suspended' ? ' <span style="color:rgba(255,80,80,0.7);font-size:8px">SUSPENDED</span>' : ''}</div>
|
|
6359
|
+
${grants.length ? `<div class="member-grants">${grants.map(g => escHtml(g)).join(' · ')}</div>` : ''}
|
|
6360
|
+
</div>
|
|
6361
|
+
<span class="member-role-badge ${roleClass}">${m.role || 'viewer'}</span>
|
|
6362
|
+
</div>`;
|
|
6363
|
+
});
|
|
6364
|
+
}
|
|
3759
6365
|
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
fetch(`/api/memory-files${dir ? '?dir=' + encodeURIComponent(dir) : ''}`)
|
|
3764
|
-
.then(r => r.ok ? r.json() : { memories: [] })
|
|
3765
|
-
.then(d => renderMemoryFiles(d.memories || []))
|
|
3766
|
-
.catch(() => renderMemoryFiles([]));
|
|
6366
|
+
html += `<div style="margin-top:10px;font-size:9px;color:var(--dim)">
|
|
6367
|
+
Manage roles via: <code style="color:rgba(0,200,160,0.7)">/mastermind:access --org ${escHtml(orgName)} --action set-role --member-id <id> --role <owner|admin|operator|viewer></code>
|
|
6368
|
+
</div>`;
|
|
3767
6369
|
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
const dbTitle = mem.dbPath ? `title="${escHtml(mem.dbPath)}"` : '';
|
|
3771
|
-
rightHtml += `<div class="mem-stat-row" ${dbTitle}><span class="mem-stat-key">DB SIZE</span><span class="mem-stat-val" style="color:${mem.dbSize ? 'var(--text)' : 'var(--muted)'}">${mem.dbSize ? fmtBytes(mem.dbSize) : '—'}</span></div>`;
|
|
3772
|
-
rightHtml += `<div class="mem-stat-row" style="margin-top:4px;"><span class="mem-stat-key">HNSW INDEX</span><span class="mem-stat-val" style="color:${mem.hnsw ? 'var(--green)' : 'var(--muted)'}">${mem.hnsw ? 'PRESENT' : 'ABSENT'}</span></div>`;
|
|
3773
|
-
rightHtml += '<div class="section-label" style="margin-top:10px;">RUVECTOR</div>';
|
|
3774
|
-
rightHtml += `<div class="mem-stat-row"><span class="mem-stat-key">DB SIZE</span><span class="mem-stat-val" style="color:${mem.ruvectorExists ? 'var(--text)' : 'var(--muted)'}">${mem.ruvectorExists ? fmtBytes(mem.ruvectorSize) : '—'}</span></div>`;
|
|
3775
|
-
rightHtml += `<div class="mem-stat-row" style="margin-top:4px;"><span class="mem-stat-key">PATTERNS</span><span class="mem-stat-val" style="color:${mem.ruvectorPatterns ? 'var(--teal)' : 'var(--muted)'}">${mem.ruvectorPatterns || '—'}</span></div>`;
|
|
3776
|
-
rightHtml += '<div class="section-label" style="margin-top:10px;">BY TYPE</div><div data-by-type>—</div>';
|
|
6370
|
+
el.innerHTML = html;
|
|
6371
|
+
}
|
|
3777
6372
|
|
|
3778
|
-
|
|
3779
|
-
const
|
|
3780
|
-
const
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
kRecent.slice(0,3).forEach(c => {
|
|
3789
|
-
if (!c) return;
|
|
3790
|
-
const text = c.content || c.text || c.chunk || c.value || '';
|
|
3791
|
-
if (!text) return;
|
|
3792
|
-
rightHtml += `<div class="chunk-preview" title="${escHtml(String(text))}">${escHtml(String(text).slice(0,55))}</div>`;
|
|
3793
|
-
});
|
|
3794
|
-
rightHtml += '</div>';
|
|
6373
|
+
function generateOrgInvite(orgName) {
|
|
6374
|
+
const role = document.getElementById('invite-role-select')?.value || 'operator';
|
|
6375
|
+
const out = document.getElementById('invite-url-output');
|
|
6376
|
+
if (out) {
|
|
6377
|
+
const token = Math.random().toString(36).slice(2) + Math.random().toString(36).slice(2);
|
|
6378
|
+
out.innerHTML = `<div class="invite-url-box">
|
|
6379
|
+
<div style="margin-bottom:4px">Token: <strong>${token}</strong></div>
|
|
6380
|
+
<div style="font-size:9px;color:var(--dim)">Run in Claude Code:</div>
|
|
6381
|
+
<div>/mastermind:access --org ${escHtml(orgName)} --action invite --role ${escHtml(role)}</div>
|
|
6382
|
+
</div>`;
|
|
3795
6383
|
}
|
|
6384
|
+
}
|
|
3796
6385
|
|
|
3797
|
-
|
|
6386
|
+
function orgAccessCmd(orgName, action, reqId) {
|
|
6387
|
+
const msg = document.getElementById('invite-url-output');
|
|
6388
|
+
if (msg) msg.innerHTML = `<div class="invite-url-box">/mastermind:access --org ${escHtml(orgName)} --action ${action} --request-id ${escHtml(reqId)}</div>`;
|
|
6389
|
+
}
|
|
6390
|
+
|
|
6391
|
+
// ─── ORG ROOM SEARCH ───────────────────────────────────────────────
|
|
6392
|
+
async function orgRoomSearch(orgName, q) {
|
|
6393
|
+
if (!q || q.length < 2) return;
|
|
6394
|
+
try {
|
|
6395
|
+
const r = await fetch(`/api/org/${encodeURIComponent(orgName)}/search?q=${encodeURIComponent(q)}`);
|
|
6396
|
+
if (!r.ok) return;
|
|
6397
|
+
const data = await r.json();
|
|
6398
|
+
return data.hits || [];
|
|
6399
|
+
} catch(_) { return []; }
|
|
3798
6400
|
}
|
|
3799
6401
|
|
|
3800
6402
|
// ═══════════════════════════════════════════════════════════════════
|
|
3801
|
-
//
|
|
6403
|
+
// COMMAND PALETTE (Cmd+K / Ctrl+K)
|
|
3802
6404
|
// ═══════════════════════════════════════════════════════════════════
|
|
3803
|
-
|
|
6405
|
+
let _cpSelectedIndex = 0;
|
|
6406
|
+
let _cpItems = [];
|
|
6407
|
+
|
|
6408
|
+
const ORG_ROOM_TABS = [
|
|
6409
|
+
{ id: 'chart', label: 'ORG CHART', icon: '◉' },
|
|
6410
|
+
{ id: 'heartbeats', label: 'HEARTBEATS', icon: '♡' },
|
|
6411
|
+
{ id: 'tasks', label: 'TASK BOARD', icon: '☑' },
|
|
6412
|
+
{ id: 'costs', label: 'COSTS', icon: '$' },
|
|
6413
|
+
{ id: 'routines', label: 'ROUTINES', icon: '⏱' },
|
|
6414
|
+
{ id: 'inbox', label: 'INBOX', icon: '✉' },
|
|
6415
|
+
{ id: 'projects', label: 'PROJECTS', icon: '◈' },
|
|
6416
|
+
{ id: 'activity', label: 'ACTIVITY', icon: '◎' },
|
|
6417
|
+
{ id: 'live', label: '● LIVE', icon: '●' },
|
|
6418
|
+
{ id: 'charts', label: 'CHARTS', icon: '▤' },
|
|
6419
|
+
{ id: 'members', label: 'MEMBERS', icon: '◌' },
|
|
6420
|
+
{ id: 'skills', label: 'SKILLS', icon: '⚡' },
|
|
6421
|
+
{ id: 'goals', label: 'GOALS', icon: '◎' },
|
|
6422
|
+
{ id: 'workspaces', label: 'WORKSPACES', icon: '⊡' },
|
|
6423
|
+
{ id: 'board', label: 'BOARD', icon: '▦' },
|
|
6424
|
+
{ id: 'health', label: 'HEALTH', icon: '♥' },
|
|
6425
|
+
{ id: 'invites', label: 'INVITES', icon: '✉' },
|
|
6426
|
+
{ id: 'plugins', label: 'PLUGINS', icon: '⬡' },
|
|
6427
|
+
{ id: 'myissues', label: 'MY ISSUES', icon: '◎' },
|
|
6428
|
+
{ id: 'agents', label: 'AGENTS', icon: '◈' },
|
|
6429
|
+
{ id: 'approvals', label: 'APPROVALS', icon: '✓' },
|
|
6430
|
+
{ id: 'secrets', label: 'SECRETS', icon: '⚿' },
|
|
6431
|
+
{ id: 'envs', label: 'ENVIRONMENTS', icon: '⊞' },
|
|
6432
|
+
{ id: 'access', label: 'ACCESS', icon: '◐' },
|
|
6433
|
+
{ id: 'issues', label: 'ISSUES', icon: '◎' },
|
|
6434
|
+
{ id: 'joinqueue', label: 'JOIN REQUESTS', icon: '↗' },
|
|
6435
|
+
{ id: 'budgets', label: 'BUDGETS', icon: '$' },
|
|
6436
|
+
{ id: 'threads', label: 'THREADS', icon: '◈' },
|
|
6437
|
+
{ id: 'settings', label: 'SETTINGS', icon: '⚙' },
|
|
6438
|
+
];
|
|
6439
|
+
|
|
6440
|
+
const MASTERMIND_SKILLS = [
|
|
6441
|
+
'mastermind:createorg','mastermind:runorg','mastermind:agents','mastermind:tasks',
|
|
6442
|
+
'mastermind:goals','mastermind:costs','mastermind:heartbeat','mastermind:approve',
|
|
6443
|
+
'mastermind:routines','mastermind:projects','mastermind:secrets','mastermind:env',
|
|
6444
|
+
'mastermind:worktree','mastermind:inbox','mastermind:org-settings','mastermind:skills',
|
|
6445
|
+
'mastermind:search','mastermind:bootstrap','mastermind:backup','mastermind:adapters',
|
|
6446
|
+
'mastermind:plugins','mastermind:access','mastermind:instance',
|
|
6447
|
+
'mastermind:environments','mastermind:agent-detail','mastermind:routine-detail','mastermind:workspaces',
|
|
6448
|
+
'mastermind:issue-detail','mastermind:goal-detail','mastermind:project-detail','mastermind:export',
|
|
6449
|
+
'mastermind:workspace-detail','mastermind:new-agent','mastermind:plugin-settings','mastermind:invites',
|
|
6450
|
+
'mastermind:approval-detail','mastermind:instance-settings','mastermind:profile','mastermind:my-issues',
|
|
6451
|
+
'mastermind:project-workspace','mastermind:activity',
|
|
6452
|
+
'mastermind:adapter-manager','mastermind:plugin-manager','mastermind:companies',
|
|
6453
|
+
'mastermind:invite-landing','mastermind:org-chart',
|
|
6454
|
+
'mastermind:import',
|
|
6455
|
+
'mastermind:issues','mastermind:join-queue','mastermind:plan-to-tasks','mastermind:diagnose',
|
|
6456
|
+
'mastermind:tree-control','mastermind:threads','mastermind:budgets',
|
|
6457
|
+
'mastermind:memory','mastermind:wiki','mastermind:liveness',
|
|
6458
|
+
];
|
|
6459
|
+
|
|
6460
|
+
function openCmdPalette() {
|
|
6461
|
+
const overlay = document.getElementById('cmd-palette-overlay');
|
|
6462
|
+
if (!overlay) return;
|
|
6463
|
+
overlay.classList.add('open');
|
|
6464
|
+
const input = document.getElementById('cmd-palette-input');
|
|
6465
|
+
if (input) { input.value = ''; input.focus(); }
|
|
6466
|
+
cmdPaletteSearch('');
|
|
6467
|
+
}
|
|
3804
6468
|
|
|
3805
|
-
function
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
'org:create': 'CREATED',
|
|
3810
|
-
};
|
|
3811
|
-
return map[type] || (typeof type === 'string' ? type.toUpperCase() : 'UNKNOWN');
|
|
6469
|
+
function closeCmdPalette() {
|
|
6470
|
+
document.getElementById('cmd-palette-overlay')?.classList.remove('open');
|
|
6471
|
+
_cpItems = [];
|
|
6472
|
+
_cpSelectedIndex = 0;
|
|
3812
6473
|
}
|
|
3813
6474
|
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
_orgRenderInFlight = true;
|
|
3818
|
-
try {
|
|
3819
|
-
const _orgCtrl = new AbortController();
|
|
3820
|
-
const _orgTimeout = setTimeout(() => _orgCtrl.abort(), 10000);
|
|
3821
|
-
let orgs;
|
|
3822
|
-
try {
|
|
3823
|
-
const r = await fetch('/api/orgs', { signal: _orgCtrl.signal });
|
|
3824
|
-
orgs = await r.json();
|
|
3825
|
-
} finally {
|
|
3826
|
-
clearTimeout(_orgTimeout);
|
|
3827
|
-
}
|
|
3828
|
-
if (!Array.isArray(orgs)) return;
|
|
3829
|
-
const body = document.getElementById('orgs-body');
|
|
3830
|
-
const badge = document.getElementById('orgs-badge');
|
|
3831
|
-
const dot = document.getElementById('orgs-live-dot');
|
|
3832
|
-
const runLabel = document.getElementById('orgs-running-label');
|
|
3833
|
-
if (!body) return;
|
|
6475
|
+
function buildCpItems(q) {
|
|
6476
|
+
const ql = (q || '').toLowerCase().trim();
|
|
6477
|
+
const items = [];
|
|
3834
6478
|
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
|
|
3842
|
-
dot.classList.remove('live');
|
|
3843
|
-
runLabel.textContent = '';
|
|
3844
|
-
}
|
|
6479
|
+
// Orgs
|
|
6480
|
+
const orgs = _orgsData || [];
|
|
6481
|
+
const matchedOrgs = orgs.filter(o => !ql || (o.name || o.org_name || '').toLowerCase().includes(ql) || (o.org_name || '').toLowerCase().includes(ql));
|
|
6482
|
+
matchedOrgs.slice(0, 4).forEach(o => {
|
|
6483
|
+
const name = o.name || o.org_name;
|
|
6484
|
+
items.push({ group: 'ORGS', icon: '⬡', label: name, hint: 'open org room', action: () => { closeCmdPalette(); openOrgRoom(name); } });
|
|
6485
|
+
});
|
|
3845
6486
|
|
|
3846
|
-
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
return;
|
|
3854
|
-
}
|
|
6487
|
+
// ORG ROOM tabs (if org room is open)
|
|
6488
|
+
if (_orgroomCurrentOrg) {
|
|
6489
|
+
const matchedTabs = ORG_ROOM_TABS.filter(t => !ql || t.label.toLowerCase().includes(ql));
|
|
6490
|
+
matchedTabs.slice(0, 5).forEach(t => {
|
|
6491
|
+
items.push({ group: 'TABS', icon: t.icon, label: t.label, hint: _orgroomCurrentOrg, action: () => { closeCmdPalette(); switchOrgTab(t.id); } });
|
|
6492
|
+
});
|
|
6493
|
+
}
|
|
3855
6494
|
|
|
3856
|
-
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
<div class="org-header">ACTION</div>
|
|
3862
|
-
</div>`;
|
|
6495
|
+
// Skills
|
|
6496
|
+
const matchedSkills = MASTERMIND_SKILLS.filter(s => !ql || s.includes(ql));
|
|
6497
|
+
matchedSkills.slice(0, 5).forEach(s => {
|
|
6498
|
+
items.push({ group: 'SKILLS', icon: '/', label: s, hint: 'copy slash command', action: () => { closeCmdPalette(); copyText(s); } });
|
|
6499
|
+
});
|
|
3863
6500
|
|
|
3864
|
-
|
|
3865
|
-
|
|
3866
|
-
|
|
3867
|
-
|
|
3868
|
-
|
|
3869
|
-
|
|
3870
|
-
|
|
3871
|
-
|
|
3872
|
-
|
|
6501
|
+
// Quick actions
|
|
6502
|
+
const actions = [
|
|
6503
|
+
{ label: 'Open Mastermind Panel', icon: '🧠', action: () => { closeCmdPalette(); openMastermindOverlay(); } },
|
|
6504
|
+
{ label: 'Open Monograph', icon: '◈', action: () => { closeCmdPalette(); openMonographOverlay(); } },
|
|
6505
|
+
{ label: 'Refresh Dashboard', icon: '↺', action: () => { closeCmdPalette(); location.reload(); } },
|
|
6506
|
+
];
|
|
6507
|
+
actions.filter(a => !ql || a.label.toLowerCase().includes(ql)).slice(0, 3).forEach(a => {
|
|
6508
|
+
items.push({ group: 'ACTIONS', icon: a.icon, label: a.label, hint: '', action: a.action });
|
|
6509
|
+
});
|
|
3873
6510
|
|
|
3874
|
-
|
|
3875
|
-
|
|
3876
|
-
<div class="org-status-dot ${org.running ? 'running' : ''}"></div>
|
|
3877
|
-
</div>
|
|
3878
|
-
<div class="org-cell">
|
|
3879
|
-
<div class="org-name">${escHtml(org.name)}</div>
|
|
3880
|
-
<div class="org-goal">${escHtml((org.goal || '').slice(0, 70))}${(org.goal || '').length > 70 ? '…' : ''}</div>
|
|
3881
|
-
</div>
|
|
3882
|
-
<div class="org-cell" style="color:var(--teal)">${org.roles || 0}</div>
|
|
3883
|
-
<div class="org-cell" style="color:var(--muted)">${escHtml(org.topology || 'hierarchical')}</div>
|
|
3884
|
-
<div class="org-cell">${statusHtml}</div>
|
|
3885
|
-
<div class="org-cell">${actionHtml}</div>
|
|
3886
|
-
</div>`;
|
|
6511
|
+
return items;
|
|
6512
|
+
}
|
|
3887
6513
|
|
|
3888
|
-
|
|
3889
|
-
|
|
3890
|
-
|
|
3891
|
-
|
|
3892
|
-
|
|
3893
|
-
<span class="org-event-time">${t}</span>
|
|
3894
|
-
<span class="org-event-type">${escHtml(fmtOrgEventType(ev.type))}</span>
|
|
3895
|
-
<span>${escHtml(rawDetail)}</span>
|
|
3896
|
-
</div>`;
|
|
3897
|
-
}
|
|
3898
|
-
}
|
|
3899
|
-
}
|
|
6514
|
+
function cmdPaletteSearch(q) {
|
|
6515
|
+
_cpItems = buildCpItems(q);
|
|
6516
|
+
_cpSelectedIndex = 0;
|
|
6517
|
+
renderCpResults();
|
|
6518
|
+
}
|
|
3900
6519
|
|
|
3901
|
-
|
|
6520
|
+
function renderCpResults() {
|
|
6521
|
+
const container = document.getElementById('cmd-palette-results');
|
|
6522
|
+
if (!container) return;
|
|
6523
|
+
if (!_cpItems.length) {
|
|
6524
|
+
container.innerHTML = `<div class="cp-empty">No results</div>`;
|
|
6525
|
+
return;
|
|
6526
|
+
}
|
|
3902
6527
|
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
}
|
|
3910
|
-
|
|
6528
|
+
let html = '';
|
|
6529
|
+
let lastGroup = null;
|
|
6530
|
+
_cpItems.forEach((item, i) => {
|
|
6531
|
+
if (item.group !== lastGroup) {
|
|
6532
|
+
html += `<div class="cp-group-label">${escHtml(item.group)}</div>`;
|
|
6533
|
+
lastGroup = item.group;
|
|
6534
|
+
}
|
|
6535
|
+
html += `<div class="cp-item${i === _cpSelectedIndex ? ' selected' : ''}" onclick="cpSelect(${i})">
|
|
6536
|
+
<span class="cp-item-icon">${escHtml(item.icon || '')}</span>
|
|
6537
|
+
<span class="cp-item-label">${escHtml(item.label)}</span>
|
|
6538
|
+
${item.hint ? `<span class="cp-item-hint">${escHtml(item.hint)}</span>` : ''}
|
|
6539
|
+
</div>`;
|
|
6540
|
+
});
|
|
6541
|
+
container.innerHTML = html;
|
|
3911
6542
|
}
|
|
3912
6543
|
|
|
3913
|
-
|
|
3914
|
-
|
|
3915
|
-
|
|
3916
|
-
setTimeout(renderOrgs, 500);
|
|
3917
|
-
} catch {}
|
|
6544
|
+
function cpSelect(idx) {
|
|
6545
|
+
const item = _cpItems[idx];
|
|
6546
|
+
if (item && item.action) item.action();
|
|
3918
6547
|
}
|
|
3919
6548
|
|
|
3920
|
-
function
|
|
3921
|
-
|
|
3922
|
-
|
|
3923
|
-
|
|
3924
|
-
|
|
3925
|
-
|
|
6549
|
+
function cmdPaletteKeydown(e) {
|
|
6550
|
+
if (e.key === 'Escape') { closeCmdPalette(); return; }
|
|
6551
|
+
if (e.key === 'ArrowDown') {
|
|
6552
|
+
e.preventDefault();
|
|
6553
|
+
_cpSelectedIndex = Math.min(_cpSelectedIndex + 1, _cpItems.length - 1);
|
|
6554
|
+
renderCpResults();
|
|
6555
|
+
return;
|
|
6556
|
+
}
|
|
6557
|
+
if (e.key === 'ArrowUp') {
|
|
6558
|
+
e.preventDefault();
|
|
6559
|
+
_cpSelectedIndex = Math.max(_cpSelectedIndex - 1, 0);
|
|
6560
|
+
renderCpResults();
|
|
6561
|
+
return;
|
|
6562
|
+
}
|
|
6563
|
+
if (e.key === 'Enter') {
|
|
6564
|
+
e.preventDefault();
|
|
6565
|
+
cpSelect(_cpSelectedIndex);
|
|
6566
|
+
return;
|
|
6567
|
+
}
|
|
3926
6568
|
}
|
|
3927
6569
|
|
|
3928
|
-
//
|
|
3929
|
-
|
|
3930
|
-
|
|
3931
|
-
|
|
3932
|
-
|
|
3933
|
-
|
|
3934
|
-
|
|
3935
|
-
// Guard against prototype-polluting keys from attacker-controlled SSE payloads
|
|
3936
|
-
if (orgName === '__proto__' || orgName === 'constructor' || orgName === 'prototype') return;
|
|
3937
|
-
if (!Object.prototype.hasOwnProperty.call(orgEventLog, orgName)) orgEventLog[orgName] = [];
|
|
3938
|
-
orgEventLog[orgName].push(ev);
|
|
3939
|
-
if (orgEventLog[orgName].length > 20) orgEventLog[orgName].shift();
|
|
3940
|
-
// Debounce: re-render at most once per 500ms on SSE bursts
|
|
3941
|
-
const panel = document.getElementById('panel-orgs');
|
|
3942
|
-
if (panel && panel.classList.contains('open')) {
|
|
3943
|
-
clearTimeout(_orgRenderDebounce);
|
|
3944
|
-
_orgRenderDebounce = setTimeout(renderOrgs, 500);
|
|
6570
|
+
// Global Cmd+K / Ctrl+K listener
|
|
6571
|
+
document.addEventListener('keydown', e => {
|
|
6572
|
+
if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
|
|
6573
|
+
e.preventDefault();
|
|
6574
|
+
const overlay = document.getElementById('cmd-palette-overlay');
|
|
6575
|
+
if (overlay && overlay.classList.contains('open')) closeCmdPalette();
|
|
6576
|
+
else openCmdPalette();
|
|
3945
6577
|
}
|
|
3946
|
-
|
|
6578
|
+
if (e.key === 'Escape') {
|
|
6579
|
+
const overlay = document.getElementById('cmd-palette-overlay');
|
|
6580
|
+
if (overlay && overlay.classList.contains('open')) closeCmdPalette();
|
|
6581
|
+
}
|
|
6582
|
+
});
|
|
3947
6583
|
|
|
3948
6584
|
// ═══════════════════════════════════════════════════════════════════
|
|
3949
6585
|
// SCHEDULED LOOPS PANEL
|
|
@@ -4609,7 +7245,7 @@ window.switchPalaceTab = function(tab) {
|
|
|
4609
7245
|
b.classList.toggle('active', !!(btnTab && btnTab[1] === tab));
|
|
4610
7246
|
});
|
|
4611
7247
|
document.querySelectorAll('.po-tab-pane').forEach(p => p.classList.remove('active'));
|
|
4612
|
-
const paneIds = { drawers: 'po-drawers-tab', sessions: 'po-sessions-tab', chunks: 'po-chunks-tab', routing: 'po-routing-tab', swarm: 'po-swarm-tab', graph: 'po-graph-tab', usage: 'po-usage-tab' };
|
|
7248
|
+
const paneIds = { drawers: 'po-drawers-tab', sessions: 'po-sessions-tab', chunks: 'po-chunks-tab', routing: 'po-routing-tab', swarm: 'po-swarm-tab', graph: 'po-graph-tab', usage: 'po-usage-tab', adrs: 'po-adrs-tab' };
|
|
4613
7249
|
const pane = document.getElementById(paneIds[tab]);
|
|
4614
7250
|
if (pane) pane.classList.add('active');
|
|
4615
7251
|
if (typeof kgCodeGraph !== 'undefined' && kgCodeGraph.stop) kgCodeGraph.stop();
|
|
@@ -4627,13 +7263,15 @@ async function loadPalaceData() {
|
|
|
4627
7263
|
|
|
4628
7264
|
const knowUrl = `/api/section?name=knowledge${dir ? '&dir=' + encodeURIComponent(dir) : ''}`;
|
|
4629
7265
|
const swarmUrl = `/api/section?name=swarm${dir ? '&dir=' + encodeURIComponent(dir) : ''}`;
|
|
4630
|
-
const
|
|
7266
|
+
const adrsUrl = `/api/adrs${dir ? '?dir=' + encodeURIComponent(dir) : ''}`;
|
|
7267
|
+
const [palaceRes, graphRes, memRes, sessRes, knowRes, swarmRes, adrsRes] = await Promise.all([
|
|
4631
7268
|
fetch(palaceUrl).catch(() => ({ ok: false })),
|
|
4632
7269
|
fetch(graphUrl).catch(() => ({ ok: false })),
|
|
4633
7270
|
fetch(memUrl).catch(() => ({ ok: false })),
|
|
4634
7271
|
fetch(sectionUrl).catch(() => ({ ok: false })),
|
|
4635
7272
|
fetch(knowUrl).catch(() => ({ ok: false })),
|
|
4636
|
-
fetch(swarmUrl).catch(() => ({ ok: false }))
|
|
7273
|
+
fetch(swarmUrl).catch(() => ({ ok: false })),
|
|
7274
|
+
fetch(adrsUrl).catch(() => ({ ok: false }))
|
|
4637
7275
|
]);
|
|
4638
7276
|
|
|
4639
7277
|
palaceData = await safeJson(palaceRes, { drawers: [], identity: null, kg: [] });
|
|
@@ -4647,6 +7285,8 @@ async function loadPalaceData() {
|
|
|
4647
7285
|
palaceData.knowledge = knowData.knowledge || {};
|
|
4648
7286
|
const swarmSectionData = await safeJson(swarmRes, {});
|
|
4649
7287
|
palaceData.swarmSection = swarmSectionData.swarm || swarmSectionData || {};
|
|
7288
|
+
const adrsData = await safeJson(adrsRes, { adrs: [] });
|
|
7289
|
+
palaceData.adrs = adrsData.adrs || [];
|
|
4650
7290
|
// Update stats bar
|
|
4651
7291
|
const stats = document.getElementById('po-stats');
|
|
4652
7292
|
if (stats && palaceData) {
|
|
@@ -4711,6 +7351,56 @@ function renderPalaceTab(tab) {
|
|
|
4711
7351
|
else if (tab === 'swarm') renderPalaceSwarm();
|
|
4712
7352
|
else if (tab === 'graph') { renderAgentGraph(palaceData.graph || { nodes: [], edges: [] }); }
|
|
4713
7353
|
else if (tab === 'usage') renderPalaceUsage();
|
|
7354
|
+
else if (tab === 'adrs') renderAdrs();
|
|
7355
|
+
}
|
|
7356
|
+
|
|
7357
|
+
// ── ADR TAB ──────────────────────────────────────────────────────────────────
|
|
7358
|
+
let _adrFilter = '';
|
|
7359
|
+
window.filterAdrs = function(val) { _adrFilter = val.toLowerCase(); renderAdrs(); };
|
|
7360
|
+
|
|
7361
|
+
function renderAdrs(adrs) {
|
|
7362
|
+
const body = document.getElementById('po-adrs-body');
|
|
7363
|
+
if (!body) return;
|
|
7364
|
+
const list = adrs || (palaceData && palaceData.adrs) || [];
|
|
7365
|
+
const filtered = _adrFilter ? list.filter(a =>
|
|
7366
|
+
(a.number + ' ' + a.title + ' ' + (a.status || '') + ' ' + (a.group || '')).toLowerCase().includes(_adrFilter)
|
|
7367
|
+
) : list;
|
|
7368
|
+
|
|
7369
|
+
const impl = list.filter(a => a.group === 'implementation');
|
|
7370
|
+
const guidance = list.filter(a => a.group === 'guidance');
|
|
7371
|
+
document.getElementById('po-adrs-count').textContent = list.length;
|
|
7372
|
+
document.getElementById('po-adrs-impl-count').textContent = impl.length;
|
|
7373
|
+
document.getElementById('po-adrs-guidance-count').textContent = guidance.length;
|
|
7374
|
+
|
|
7375
|
+
if (!filtered.length) {
|
|
7376
|
+
body.innerHTML = `<div style="color:var(--muted);font-size:10px;padding:20px 0;">${list.length ? 'No ADRs match filter.' : 'No ADRs found.'}</div>`;
|
|
7377
|
+
return;
|
|
7378
|
+
}
|
|
7379
|
+
|
|
7380
|
+
const statusColor = s => s === 'Accepted' || s === 'Implemented' ? 'var(--teal)' : s === 'Proposed' ? '#f5a623' : s === 'Superseded' || s === 'Deprecated' ? 'var(--muted)' : 'var(--text)';
|
|
7381
|
+
const grouped = {};
|
|
7382
|
+
for (const a of filtered) { (grouped[a.group] = grouped[a.group] || []).push(a); }
|
|
7383
|
+
|
|
7384
|
+
let html = '';
|
|
7385
|
+
for (const [group, items] of Object.entries(grouped)) {
|
|
7386
|
+
html += `<div style="font-size:9px;letter-spacing:0.12em;color:var(--muted);padding:12px 0 6px;text-transform:uppercase;border-bottom:1px solid var(--border);margin-bottom:8px;">${group}</div>`;
|
|
7387
|
+
html += '<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:8px;margin-bottom:16px;">';
|
|
7388
|
+
for (const a of items) {
|
|
7389
|
+
html += `<div class="chunk-card" style="cursor:default;">
|
|
7390
|
+
<div style="display:flex;align-items:baseline;gap:8px;margin-bottom:4px;">
|
|
7391
|
+
<span style="font-size:9px;font-weight:700;color:var(--teal);font-family:'Azeret Mono',monospace;flex-shrink:0;">${escHtml(a.number)}</span>
|
|
7392
|
+
<span style="font-size:10px;font-weight:600;color:var(--text);line-height:1.3;">${escHtml(a.title)}</span>
|
|
7393
|
+
</div>
|
|
7394
|
+
<div style="display:flex;align-items:center;gap:8px;margin-top:6px;">
|
|
7395
|
+
<span style="font-size:8px;letter-spacing:0.1em;color:${statusColor(a.status)};border:1px solid currentColor;padding:1px 5px;border-radius:2px;">${escHtml(a.status || 'UNKNOWN')}</span>
|
|
7396
|
+
${a.date ? `<span style="font-size:8px;color:var(--muted);">${escHtml(a.date)}</span>` : ''}
|
|
7397
|
+
</div>
|
|
7398
|
+
${a.summary ? `<div style="font-size:9px;color:var(--muted);margin-top:6px;line-height:1.5;">${escHtml(a.summary.substring(0, 120))}${a.summary.length > 120 ? '…' : ''}</div>` : ''}
|
|
7399
|
+
</div>`;
|
|
7400
|
+
}
|
|
7401
|
+
html += '</div>';
|
|
7402
|
+
}
|
|
7403
|
+
body.innerHTML = html;
|
|
4714
7404
|
}
|
|
4715
7405
|
|
|
4716
7406
|
// ── USAGE TAB ────────────────────────────────────────────────────────────────
|
|
@@ -7526,14 +10216,156 @@ async function loadDocStats() { /* replaced by loadWikiTab */ }
|
|
|
7526
10216
|
// ═══════════════════════════════════════════════════════════════════
|
|
7527
10217
|
// MASTERMIND OVERLAY
|
|
7528
10218
|
// ═══════════════════════════════════════════════════════════════════
|
|
10219
|
+
// ─── Mastermind Hub ──────────────────────────────────────────────────────────
|
|
10220
|
+
|
|
10221
|
+
const _MM_SKILLS_DEFS = (typeof MASTERMIND_SKILLS !== 'undefined' ? MASTERMIND_SKILLS : []).map(s => ({
|
|
10222
|
+
name: s,
|
|
10223
|
+
desc: {
|
|
10224
|
+
'mastermind:createorg': 'Create a new autonomous agent organization',
|
|
10225
|
+
'mastermind:runorg': 'Run a live heartbeat cycle for an org',
|
|
10226
|
+
'mastermind:status': 'Show org status, roles, goals, and health',
|
|
10227
|
+
'mastermind:issues': 'List, create, update, and search org issues',
|
|
10228
|
+
'mastermind:join-queue': 'Manage pending join requests for an org',
|
|
10229
|
+
'mastermind:plan-to-tasks': 'Convert a plan into assigned org issues',
|
|
10230
|
+
'mastermind:diagnose': 'Diagnose why work has stalled in an org',
|
|
10231
|
+
'mastermind:tree-control':'Pause, hold, release, or cancel issue trees',
|
|
10232
|
+
'mastermind:threads': 'List, view, create, and reply to threads',
|
|
10233
|
+
'mastermind:budgets': 'View and set token/cost budgets',
|
|
10234
|
+
'mastermind:goals': 'Manage org goals and progress tracking',
|
|
10235
|
+
'mastermind:projects': 'Manage org projects and milestones',
|
|
10236
|
+
'mastermind:members': 'Manage org member roster and roles',
|
|
10237
|
+
'mastermind:routines': 'Schedule recurring agent tasks',
|
|
10238
|
+
'mastermind:approvals': 'Review pending approval gates',
|
|
10239
|
+
'mastermind:adapters': 'Manage third-party adapter connections',
|
|
10240
|
+
'mastermind:environments':'Manage deployment environments',
|
|
10241
|
+
'mastermind:workspaces': 'Manage org workspaces and assignments',
|
|
10242
|
+
'mastermind:plugins': 'Manage org plugin configuration',
|
|
10243
|
+
'mastermind:secrets': 'Store and reference org secrets safely',
|
|
10244
|
+
'mastermind:export': 'Export org configuration and state',
|
|
10245
|
+
'mastermind:import': 'Import org configuration from file',
|
|
10246
|
+
'mastermind:compliance': 'Run compliance and audit checks',
|
|
10247
|
+
'mastermind:activity': 'View org activity log',
|
|
10248
|
+
'mastermind:liveness': 'Check issue liveness and auto-recovery',
|
|
10249
|
+
'mastermind:memory': 'Org-scoped PARA memory: ingest facts, recall context, synthesize',
|
|
10250
|
+
'mastermind:wiki': 'Org knowledge base: query, ingest, distill, and maintain pages',
|
|
10251
|
+
'mastermind:liveness': 'Enforce issue liveness contract: checkout, release, wakeup, recover',
|
|
10252
|
+
}[s] || ''
|
|
10253
|
+
}));
|
|
10254
|
+
|
|
10255
|
+
let _mmCurrentTab = 'orgs';
|
|
10256
|
+
|
|
10257
|
+
window.switchMmTab = function(tab) {
|
|
10258
|
+
_mmCurrentTab = tab;
|
|
10259
|
+
document.querySelectorAll('.mm-tab').forEach(b => b.classList.toggle('active', b.textContent.trim().toLowerCase().replace(' org','').replace('create','create') === tab || b.getAttribute('onclick') === `switchMmTab('${tab}')`));
|
|
10260
|
+
document.querySelectorAll('.mm-pane').forEach(p => p.classList.remove('active'));
|
|
10261
|
+
const pane = document.getElementById(`mm-pane-${tab}`);
|
|
10262
|
+
if (pane) pane.classList.add('active');
|
|
10263
|
+
if (tab === 'orgs') loadMmOrgs();
|
|
10264
|
+
if (tab === 'loops') loadMmLoops();
|
|
10265
|
+
if (tab === 'metrics') loadMmMetrics();
|
|
10266
|
+
if (tab === 'skills') renderMmSkills('');
|
|
10267
|
+
};
|
|
10268
|
+
|
|
10269
|
+
window.filterMmSkills = function(q) { renderMmSkills(q); };
|
|
10270
|
+
|
|
10271
|
+
function renderMmSkills(q) {
|
|
10272
|
+
const grid = document.getElementById('mm-skills-grid');
|
|
10273
|
+
if (!grid) return;
|
|
10274
|
+
const lower = (q || '').toLowerCase();
|
|
10275
|
+
const filtered = _MM_SKILLS_DEFS.filter(s => !lower || s.name.includes(lower) || s.desc.toLowerCase().includes(lower));
|
|
10276
|
+
grid.innerHTML = filtered.map(s => `
|
|
10277
|
+
<div class="mm-skill-card" onclick="navigator.clipboard&&navigator.clipboard.writeText('/${s.name}')">
|
|
10278
|
+
<div class="mm-skill-name">/${s.name.replace('mastermind:','')}</div>
|
|
10279
|
+
<div class="mm-skill-desc">${s.desc || ''}</div>
|
|
10280
|
+
</div>`).join('') || '<span style="color:rgba(150,100,200,0.4)">No skills match.</span>';
|
|
10281
|
+
}
|
|
10282
|
+
|
|
10283
|
+
window.loadMmOrgs = async function() {
|
|
10284
|
+
const el = document.getElementById('mm-orgs-list');
|
|
10285
|
+
if (!el) return;
|
|
10286
|
+
try {
|
|
10287
|
+
const r = await fetch('/api/orgs');
|
|
10288
|
+
const data = r.ok ? await r.json() : { orgs: [] };
|
|
10289
|
+
const orgs = data.orgs || [];
|
|
10290
|
+
if (!orgs.length) { el.innerHTML = '<span style="color:rgba(150,100,200,0.4)">No orgs found. Use /mastermind:createorg to create one.</span>'; return; }
|
|
10291
|
+
el.innerHTML = orgs.map(o => `
|
|
10292
|
+
<div class="mm-org-card" onclick="openOrgRoom('${o.name||o.id||''}')">
|
|
10293
|
+
<div class="mm-org-name">${o.name||o.id||'?'}</div>
|
|
10294
|
+
<div class="mm-org-meta">${(o.goal||'').slice(0,80)||'—'}</div>
|
|
10295
|
+
<div class="mm-org-meta" style="margin-top:3px;opacity:0.6">${o.roles&&o.roles.length?o.roles.length+' agent(s)':''} ${o.status?'· '+o.status:''}</div>
|
|
10296
|
+
</div>`).join('');
|
|
10297
|
+
} catch(_) { el.innerHTML = '<span style="color:rgba(200,80,80,0.6)">Failed to load orgs.</span>'; }
|
|
10298
|
+
};
|
|
10299
|
+
|
|
10300
|
+
window.loadMmLoops = async function() {
|
|
10301
|
+
const el = document.getElementById('mm-loops-list');
|
|
10302
|
+
if (!el) return;
|
|
10303
|
+
try {
|
|
10304
|
+
const r = await fetch('/api/mastermind/loops');
|
|
10305
|
+
const data = r.ok ? await r.json() : { loops: [] };
|
|
10306
|
+
const loops = data.loops || [];
|
|
10307
|
+
if (!loops.length) { el.innerHTML = '<span style="color:rgba(150,100,200,0.4)">No active loops.</span>'; return; }
|
|
10308
|
+
el.innerHTML = loops.map(l => {
|
|
10309
|
+
const pct = l.maxReps ? Math.round((l.currentRep||1)/l.maxReps*100) : 0;
|
|
10310
|
+
const bar = '█'.repeat(Math.round(pct/5)) + '░'.repeat(20-Math.round(pct/5));
|
|
10311
|
+
return `<div class="mm-loop-row">
|
|
10312
|
+
<span style="color:rgba(210,140,255,0.85);font-size:9px">${l.command||'?'}</span>
|
|
10313
|
+
<span style="color:rgba(150,100,200,0.5);font-size:8px;margin-left:8px">${l.type||''}</span>
|
|
10314
|
+
<span style="color:rgba(0,200,160,0.7);font-size:8px;margin-left:8px">run ${l.currentRep||1}${l.maxReps?' / '+l.maxReps:''}</span>
|
|
10315
|
+
<span style="color:rgba(150,100,200,0.35);font-size:8px;margin-left:8px;font-family:monospace">[${bar}] ${pct}%</span>
|
|
10316
|
+
<span style="color:rgba(150,100,200,0.4);font-size:8px;margin-left:auto">${l.status||''}</span>
|
|
10317
|
+
</div>`;
|
|
10318
|
+
}).join('');
|
|
10319
|
+
} catch(_) { el.innerHTML = '<span style="color:rgba(200,80,80,0.6)">Failed to load loops.</span>'; }
|
|
10320
|
+
};
|
|
10321
|
+
|
|
10322
|
+
window.loadMmMetrics = async function() {
|
|
10323
|
+
const grid = document.getElementById('mm-metrics-grid');
|
|
10324
|
+
const evEl = document.getElementById('mm-metrics-events');
|
|
10325
|
+
if (!grid) return;
|
|
10326
|
+
try {
|
|
10327
|
+
const r = await fetch('/api/mastermind/metrics');
|
|
10328
|
+
const data = r.ok ? await r.json() : {};
|
|
10329
|
+
const t = data.tokens || {};
|
|
10330
|
+
const s = data.swarm || {};
|
|
10331
|
+
const metrics = [
|
|
10332
|
+
{ label: 'TODAY COST', value: t.today_usd != null ? '$'+Number(t.today_usd).toFixed(4) : '—' },
|
|
10333
|
+
{ label: 'MONTH COST', value: t.month_usd != null ? '$'+Number(t.month_usd).toFixed(2) : '—' },
|
|
10334
|
+
{ label: 'TOTAL CALLS', value: t.total_calls != null ? Number(t.total_calls).toLocaleString() : '—' },
|
|
10335
|
+
{ label: 'ACTIVE AGENTS',value: s.active_agents != null ? s.active_agents : '—' },
|
|
10336
|
+
{ label: 'TASKS DONE', value: s.tasks_done != null ? s.tasks_done : '—' },
|
|
10337
|
+
{ label: 'SWARM RUNS', value: s.swarm_runs != null ? s.swarm_runs : '—' },
|
|
10338
|
+
];
|
|
10339
|
+
grid.innerHTML = metrics.map(m => `
|
|
10340
|
+
<div class="mm-metric-card">
|
|
10341
|
+
<div class="mm-metric-label" style="font-size:8px;letter-spacing:0.1em;color:rgba(150,100,200,0.55);margin-bottom:4px">${m.label}</div>
|
|
10342
|
+
<div class="mm-metric-value" style="font-size:18px;color:rgba(210,140,255,0.9)">${m.value}</div>
|
|
10343
|
+
</div>`).join('');
|
|
10344
|
+
const events = data.recentEvents || [];
|
|
10345
|
+
if (evEl) evEl.innerHTML = events.slice().reverse().map(e => {
|
|
10346
|
+
const ts = e.ts ? new Date(e.ts).toISOString().slice(11,19) : '';
|
|
10347
|
+
return `<div style="padding:3px 0;border-bottom:1px solid rgba(200,120,255,0.06)">[${ts}] <span style="color:rgba(210,140,255,0.75)">${e.type||''}</span> ${e.loopId||e.org||''}</div>`;
|
|
10348
|
+
}).join('') || '<span style="color:rgba(150,100,200,0.4)">No recent events.</span>';
|
|
10349
|
+
} catch(_) { grid.innerHTML = '<span style="color:rgba(200,80,80,0.6)">Failed to load metrics.</span>'; }
|
|
10350
|
+
};
|
|
10351
|
+
|
|
10352
|
+
window.mmCreateOrgCmd = function() {
|
|
10353
|
+
const name = (document.getElementById('mm-create-name')||{}).value || '';
|
|
10354
|
+
const goal = (document.getElementById('mm-create-goal')||{}).value || '';
|
|
10355
|
+
const gov = (document.getElementById('mm-create-gov')||{}).value || 'democratic';
|
|
10356
|
+
const out = document.getElementById('mm-create-output');
|
|
10357
|
+
const box = document.getElementById('mm-create-cmd-box');
|
|
10358
|
+
if (!name) { if (out) out.textContent = 'Org name is required.'; return; }
|
|
10359
|
+
const cmd = `/mastermind:createorg --name ${name}${goal?' --goal "'+goal.replace(/"/g,"'")+'"':''}${gov?' --governance '+gov:''}`;
|
|
10360
|
+
if (out) out.textContent = 'Command ready — copy and run in Claude Code:';
|
|
10361
|
+
if (box) { box.style.display='block'; box.textContent = cmd; }
|
|
10362
|
+
navigator.clipboard && navigator.clipboard.writeText(cmd).catch(()=>{});
|
|
10363
|
+
};
|
|
10364
|
+
|
|
7529
10365
|
window.openMastermindOverlay = function() {
|
|
7530
|
-
|
|
7531
|
-
|
|
7532
|
-
|
|
7533
|
-
// Lazy-load the iframe — only set src on first open
|
|
7534
|
-
if (!frame.src || frame.src === window.location.href) {
|
|
7535
|
-
frame.src = '/mastermind';
|
|
7536
|
-
}
|
|
10366
|
+
document.getElementById('mastermind-overlay').classList.add('open');
|
|
10367
|
+
// Load initial tab data
|
|
10368
|
+
switchMmTab(_mmCurrentTab || 'orgs');
|
|
7537
10369
|
};
|
|
7538
10370
|
|
|
7539
10371
|
window.closeMastermindOverlay = function() {
|
|
@@ -7561,5 +10393,22 @@ window.closeMastermindOverlay = function() {
|
|
|
7561
10393
|
</div>
|
|
7562
10394
|
</div>
|
|
7563
10395
|
</div>
|
|
10396
|
+
|
|
10397
|
+
<!-- ═══════════════════════════ COMMAND PALETTE ═══════════════════ -->
|
|
10398
|
+
<div id="cmd-palette-overlay" onclick="if(event.target===this)closeCmdPalette()">
|
|
10399
|
+
<div id="cmd-palette-box">
|
|
10400
|
+
<input id="cmd-palette-input" placeholder="Search orgs, tabs, actions, skills…"
|
|
10401
|
+
autocomplete="off" spellcheck="false"
|
|
10402
|
+
oninput="cmdPaletteSearch(this.value)"
|
|
10403
|
+
onkeydown="cmdPaletteKeydown(event)" />
|
|
10404
|
+
<div id="cmd-palette-results"></div>
|
|
10405
|
+
<div id="cmd-palette-footer">
|
|
10406
|
+
<span><kbd>↑↓</kbd> navigate</span>
|
|
10407
|
+
<span><kbd>↵</kbd> select</span>
|
|
10408
|
+
<span><kbd>Esc</kbd> close</span>
|
|
10409
|
+
<span><kbd>⌘K</kbd> toggle</span>
|
|
10410
|
+
</div>
|
|
10411
|
+
</div>
|
|
10412
|
+
</div>
|
|
7564
10413
|
</body>
|
|
7565
10414
|
</html>
|