monomind 2.1.0 → 2.1.4
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/README.md +77 -94
- package/package.json +3 -3
- package/packages/@monomind/cli/.claude/commands/mastermind/createorg.md +8 -9
- package/packages/@monomind/cli/.claude/helpers/handlers/capture-handler.cjs +24 -10
- package/packages/@monomind/cli/.claude/helpers/handlers/gates-handler.cjs +2 -2
- package/packages/@monomind/cli/.claude/helpers/handlers/route-handler.cjs +29 -0
- package/packages/@monomind/cli/.claude/helpers/handlers/session-handler.cjs +35 -23
- package/packages/@monomind/cli/.claude/helpers/hook-handler.cjs +21 -0
- package/packages/@monomind/cli/.claude/helpers/utils/monograph.cjs +73 -2
- package/packages/@monomind/cli/.claude/skills/mastermind-skills/createorg.md +91 -701
- package/packages/@monomind/cli/.claude/skills/mastermind-skills/org-settings.md +46 -47
- package/packages/@monomind/cli/.claude/skills/mastermind-skills/runorg.md +12 -1
- package/packages/@monomind/cli/README.md +77 -94
- package/packages/@monomind/cli/dist/src/browser/workflow/store.d.ts +2 -2
- package/packages/@monomind/cli/dist/src/commands/browse.d.ts +1 -1
- package/packages/@monomind/cli/dist/src/commands/doctor-env-checks.js +5 -0
- package/packages/@monomind/cli/dist/src/commands/org.js +46 -29
- package/packages/@monomind/cli/dist/src/commands/platforms.d.ts +1 -1
- package/packages/@monomind/cli/dist/src/init/executor.js +30 -6
- package/packages/@monomind/cli/dist/src/mcp-tools/auto-install.d.ts +8 -8
- package/packages/@monomind/cli/dist/src/mcp-tools/coherence/types.d.ts +31 -31
- package/packages/@monomind/cli/dist/src/mcp-tools/hive-mind-tools.js +5 -1
- package/packages/@monomind/cli/dist/src/mcp-tools/monograph-tools.js +28 -3
- package/packages/@monomind/cli/dist/src/mcp-tools/quality/coverage-analysis/prioritize-gaps.d.ts +36 -36
- package/packages/@monomind/cli/dist/src/mcp-tools/quality/security-compliance/detect-secrets.d.ts +4 -4
- package/packages/@monomind/cli/dist/src/memory/ewc-consolidation.d.ts +12 -0
- package/packages/@monomind/cli/dist/src/memory/sona-optimizer.d.ts +12 -0
- package/packages/@monomind/cli/dist/src/orgrt/broker.d.ts +3 -1
- package/packages/@monomind/cli/dist/src/orgrt/broker.js +8 -3
- package/packages/@monomind/cli/dist/src/orgrt/bus.d.ts +5 -1
- package/packages/@monomind/cli/dist/src/orgrt/bus.js +5 -1
- package/packages/@monomind/cli/dist/src/orgrt/daemon.d.ts +35 -1
- package/packages/@monomind/cli/dist/src/orgrt/daemon.js +190 -21
- package/packages/@monomind/cli/dist/src/orgrt/forwarder.js +47 -10
- package/packages/@monomind/cli/dist/src/orgrt/inbox.d.ts +11 -0
- package/packages/@monomind/cli/dist/src/orgrt/inbox.js +56 -0
- package/packages/@monomind/cli/dist/src/orgrt/policy.d.ts +5 -1
- package/packages/@monomind/cli/dist/src/orgrt/policy.js +58 -5
- package/packages/@monomind/cli/dist/src/orgrt/server.d.ts +3 -2
- package/packages/@monomind/cli/dist/src/orgrt/server.js +31 -40
- package/packages/@monomind/cli/dist/src/orgrt/session.d.ts +11 -1
- package/packages/@monomind/cli/dist/src/orgrt/session.js +32 -1
- package/packages/@monomind/cli/dist/src/orgrt/test-loop.js +8 -8
- package/packages/@monomind/cli/dist/src/orgrt/types.d.ts +53 -53
- package/packages/@monomind/cli/dist/src/output.d.ts +29 -29
- package/packages/@monomind/cli/dist/src/output.js +10 -2
- package/packages/@monomind/cli/dist/src/pricing/model-pricing.d.ts +1 -1
- package/packages/@monomind/cli/dist/src/types.d.ts +2 -2
- package/packages/@monomind/cli/dist/src/ui/collector.mjs +20 -5
- package/packages/@monomind/cli/dist/src/ui/dashboard.html +465 -500
- package/packages/@monomind/cli/dist/src/ui/orgs-files.js +192 -0
- package/packages/@monomind/cli/dist/src/ui/orgs.html +21 -52
- package/packages/@monomind/cli/dist/src/ui/server.mjs +174 -164
- package/packages/@monomind/cli/package.json +12 -16
- package/packages/@monomind/cli/scripts/publish.sh +6 -0
- package/scripts/generate-agent-avatars.mjs +3 -3
- package/packages/@monomind/cli/dist/src/orgrt/live.html +0 -56
|
@@ -428,6 +428,23 @@ html, body { height: 100%; background: var(--bg); color: var(--text-hi); font-fa
|
|
|
428
428
|
/* SVG org chart */
|
|
429
429
|
.org-chart-wrap { background: var(--surface); border: 1px solid var(--border); border-radius: var(--r); overflow: hidden; margin-bottom: 12px; }
|
|
430
430
|
.org-chart-svg { width: 100%; display: block; }
|
|
431
|
+
.v2-bubble-inner {
|
|
432
|
+
background: oklch(18% 0.01 55 / 0.95);
|
|
433
|
+
border: 1px solid oklch(72% 0.18 75 / 0.4);
|
|
434
|
+
border-radius: 8px;
|
|
435
|
+
padding: 4px 8px;
|
|
436
|
+
font: 500 9.5px 'Inter', system-ui, sans-serif;
|
|
437
|
+
color: oklch(88% 0.01 75);
|
|
438
|
+
text-align: center;
|
|
439
|
+
white-space: nowrap;
|
|
440
|
+
overflow: hidden;
|
|
441
|
+
text-overflow: ellipsis;
|
|
442
|
+
}
|
|
443
|
+
.v2-bubble-inner.v2-bubble-question {
|
|
444
|
+
background: oklch(28% 0.04 75 / 0.95);
|
|
445
|
+
border-color: oklch(78% 0.16 75 / 0.7);
|
|
446
|
+
color: oklch(92% 0.03 75);
|
|
447
|
+
}
|
|
431
448
|
.org-chart-legend { display: flex; gap: 14px; flex-wrap: wrap; padding: 0 2px; }
|
|
432
449
|
.ocl-item { display: flex; align-items: center; gap: 5px; font-size: 10px; color: var(--text-lo); }
|
|
433
450
|
|
|
@@ -1414,67 +1431,6 @@ textarea.sess-note-input:focus { border-color:var(--accent); }
|
|
|
1414
1431
|
select.pb-cfg-inp { padding: 4px 7px; cursor: pointer; }
|
|
1415
1432
|
@keyframes pb-pulse { 0%,100%{opacity:1} 50%{opacity:0.4} }
|
|
1416
1433
|
|
|
1417
|
-
/* ── systems grid ──────────────────────────────────────────── */
|
|
1418
|
-
#wf-systems-grid { display: block; }
|
|
1419
|
-
/* ── monoagent connections tile grid ─────────────────────────── */
|
|
1420
|
-
.ma-cat-hdr { display:flex; align-items:center; gap:10px; margin-bottom:10px; }
|
|
1421
|
-
.ma-cat-label { font-family:var(--mono); font-size:10px; font-weight:700; color:var(--text-lo); text-transform:uppercase; letter-spacing:2px; white-space:nowrap; }
|
|
1422
|
-
.ma-cat-line { flex:1; height:1px; background:var(--border); }
|
|
1423
|
-
.ma-cat-count { font-family:var(--mono); font-size:9.5px; color:var(--text-xs); }
|
|
1424
|
-
.ma-tiles { display:grid; grid-template-columns:repeat(auto-fill,minmax(96px,1fr)); gap:8px; margin-bottom:24px; }
|
|
1425
|
-
.ma-tile { background:var(--surface); border:1px solid var(--border); border-radius:10px; padding:16px 8px 12px; cursor:pointer; display:flex; flex-direction:column; align-items:center; gap:6px; transition:border-color 0.15s,box-shadow 0.15s; user-select:none; }
|
|
1426
|
-
.ma-tile:hover { border-color:var(--border-hi); }
|
|
1427
|
-
.ma-tile.connected { border-color:oklch(65% 0.15 150 / 0.5); box-shadow:0 0 10px oklch(65% 0.15 150 / 0.12); }
|
|
1428
|
-
.ma-tile-icon { font-size:22px; line-height:1; }
|
|
1429
|
-
.ma-tile-name { font-family:var(--mono); font-size:10px; font-weight:600; color:var(--text-mid); text-align:center; line-height:1.3; }
|
|
1430
|
-
.ma-tile-dot { width:6px; height:6px; border-radius:50%; background:var(--border); }
|
|
1431
|
-
.ma-tile.connected .ma-tile-dot { background:var(--green); box-shadow:0 0 5px oklch(65% 0.15 150 / 0.7); }
|
|
1432
|
-
.ma-tile.expired .ma-tile-dot { background:#fbbf24; box-shadow:0 0 5px rgba(251,191,36,0.5); }
|
|
1433
|
-
.ma-tile-acct { font-family:var(--mono); font-size:9px; color:var(--text-xs); max-width:84px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
|
1434
|
-
/* modal */
|
|
1435
|
-
.ma-modal-bg { position:fixed; inset:0; background:rgba(0,0,0,0.6); z-index:1200; display:flex; align-items:center; justify-content:center; padding:24px; }
|
|
1436
|
-
.ma-modal { width:100%; max-width:440px; background:var(--surface); border:1px solid var(--border-hi); border-radius:14px; box-shadow:0 8px 40px rgba(0,0,0,0.5); overflow:hidden; }
|
|
1437
|
-
.ma-modal-hdr { display:flex; align-items:center; justify-content:space-between; padding:16px 20px 12px; border-bottom:1px solid var(--border); }
|
|
1438
|
-
.ma-modal-hdr-left { display:flex; align-items:center; gap:10px; }
|
|
1439
|
-
.ma-modal-title { font-family:var(--mono); font-size:13px; font-weight:700; color:var(--text-hi); }
|
|
1440
|
-
.ma-modal-status { font-family:var(--mono); font-size:10px; text-transform:uppercase; letter-spacing:1px; }
|
|
1441
|
-
.ma-modal-body { padding:16px 20px 20px; display:flex; flex-direction:column; gap:14px; }
|
|
1442
|
-
.ma-modal-info { background:var(--surface-hi); border:1px solid var(--border); border-radius:8px; padding:10px 14px; display:flex; flex-direction:column; gap:8px; }
|
|
1443
|
-
.ma-modal-row { display:flex; justify-content:space-between; align-items:center; }
|
|
1444
|
-
.ma-modal-lbl { font-family:var(--mono); font-size:9.5px; color:var(--text-xs); text-transform:uppercase; letter-spacing:1px; }
|
|
1445
|
-
.ma-modal-val { font-family:var(--mono); font-size:11px; color:var(--text-mid); }
|
|
1446
|
-
.ma-modal-val.ok { color:var(--green); }
|
|
1447
|
-
.ma-modal-val.warn { color:#fbbf24; }
|
|
1448
|
-
.ma-field-label { font-family:var(--mono); font-size:9.5px; color:var(--text-xs); text-transform:uppercase; letter-spacing:1.5px; margin-bottom:4px; }
|
|
1449
|
-
.ma-field-input { width:100%; padding:7px 10px; border:1px solid var(--border-hi); border-radius:6px; background:var(--surface-hi); color:var(--text-hi); font-family:var(--mono); font-size:11px; box-sizing:border-box; outline:none; }
|
|
1450
|
-
.ma-field-input:focus { border-color:var(--accent); }
|
|
1451
|
-
.ma-steps { background:var(--surface-hi); border:1px solid var(--border); border-radius:8px; padding:8px 10px; max-height:130px; overflow-y:auto; display:flex; flex-direction:column; gap:5px; }
|
|
1452
|
-
.ma-step { display:flex; align-items:center; gap:8px; }
|
|
1453
|
-
.ma-step-dot { width:5px; height:5px; border-radius:50%; background:var(--text-xs); flex-shrink:0; }
|
|
1454
|
-
.ma-step-dot.ok { background:var(--green); }
|
|
1455
|
-
.ma-step-dot.err { background:oklch(60% 0.18 25); }
|
|
1456
|
-
.ma-step-txt { font-family:var(--mono); font-size:10px; color:var(--text-lo); }
|
|
1457
|
-
.ma-step-txt.ok { color:var(--green); }
|
|
1458
|
-
.ma-step-txt.err { color:oklch(60% 0.18 25); }
|
|
1459
|
-
.ma-btns { display:flex; gap:8px; flex-wrap:wrap; }
|
|
1460
|
-
.ma-btn { padding:6px 14px; border-radius:6px; border:none; font-family:var(--mono); font-size:11px; font-weight:600; cursor:pointer; display:flex; align-items:center; gap:5px; transition:opacity 0.15s; }
|
|
1461
|
-
.ma-btn:disabled { opacity:0.45; cursor:default; }
|
|
1462
|
-
.ma-btn-primary { background:var(--accent); color:#fff; }
|
|
1463
|
-
.ma-btn-primary:hover:not(:disabled) { opacity:0.85; }
|
|
1464
|
-
.ma-btn-secondary { background:var(--surface-hi); color:var(--text-mid); border:1px solid var(--border-hi); }
|
|
1465
|
-
.ma-btn-secondary:hover:not(:disabled) { border-color:var(--accent); color:var(--accent); }
|
|
1466
|
-
.ma-btn-danger { background:oklch(17% 0.015 25); color:oklch(60% 0.18 25); border:1px solid oklch(60% 0.18 25 / 0.3); }
|
|
1467
|
-
.ma-btn-danger:hover:not(:disabled) { opacity:0.85; }
|
|
1468
|
-
.ma-btn-ghost { background:none; color:var(--text-lo); border:1px solid var(--border); }
|
|
1469
|
-
.ma-btn-ghost:hover:not(:disabled) { color:var(--text-mid); }
|
|
1470
|
-
.ma-msg { font-family:var(--mono); font-size:11px; padding:7px 12px; border-radius:6px; }
|
|
1471
|
-
.ma-msg.ok { background:oklch(65% 0.15 150 / 0.08); border:1px solid oklch(65% 0.15 150 / 0.25); color:var(--green); }
|
|
1472
|
-
.ma-msg.err { background:oklch(60% 0.18 25 / 0.08); border:1px solid oklch(60% 0.18 25 / 0.25); color:oklch(60% 0.18 25); }
|
|
1473
|
-
.ma-method-tabs { display:flex; gap:6px; flex-wrap:wrap; }
|
|
1474
|
-
.ma-method-tab { font-family:var(--mono); font-size:10px; padding:3px 10px; border-radius:4px; background:var(--surface-hi); border:1px solid var(--border); color:var(--text-lo); cursor:pointer; }
|
|
1475
|
-
.ma-method-tab.active { background:var(--accent-dim); border-color:var(--accent); color:var(--accent); }
|
|
1476
|
-
.ma-browser-hint { font-size:11px; color:var(--text-lo); line-height:1.5; }
|
|
1477
|
-
|
|
1478
1434
|
/* ── playbook runs view ────────────────────────────────────── */
|
|
1479
1435
|
.wf-run-card { background: var(--surface); border: 1px solid var(--border); border-radius: 8px; padding: 14px 16px; margin-bottom: 10px; }
|
|
1480
1436
|
.wf-run-header { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; }
|
|
@@ -1578,10 +1534,6 @@ select.pb-cfg-inp { padding: 4px 7px; cursor: pointer; }
|
|
|
1578
1534
|
</div>
|
|
1579
1535
|
<div class="nav-no-proj" id="nav-no-proj-hint">Select a project above</div>
|
|
1580
1536
|
<div class="nav-sect" style="margin-top:auto;padding-top:8px;">
|
|
1581
|
-
<div class="nav-item" data-view="monoagent">
|
|
1582
|
-
<span class="ico">🤖</span><span class="lbl">MonoAgent</span>
|
|
1583
|
-
<span class="bdg" id="bdg-monoagent">—</span>
|
|
1584
|
-
</div>
|
|
1585
1537
|
<div class="nav-item" data-view="global">
|
|
1586
1538
|
<span class="ico">⊕</span><span class="lbl">Global Feed</span>
|
|
1587
1539
|
</div>
|
|
@@ -1595,6 +1547,10 @@ select.pb-cfg-inp { padding: 4px 7px; cursor: pointer; }
|
|
|
1595
1547
|
<div class="nav-item" data-view="chat" title="All agent sessions across projects">
|
|
1596
1548
|
<span class="ico">⌘</span><span class="lbl">Global Agent Chat</span>
|
|
1597
1549
|
</div>
|
|
1550
|
+
<div class="nav-item" data-view="humaninput" title="Pending questions from agents across all orgs">
|
|
1551
|
+
<span class="ico">❓</span><span class="lbl">Human Input</span>
|
|
1552
|
+
<span class="bdg" id="bdg-humaninput">—</span>
|
|
1553
|
+
</div>
|
|
1598
1554
|
</div>
|
|
1599
1555
|
</div>
|
|
1600
1556
|
<div id="sb-footer">
|
|
@@ -2250,30 +2206,6 @@ select.pb-cfg-inp { padding: 4px 7px; cursor: pointer; }
|
|
|
2250
2206
|
</div>
|
|
2251
2207
|
</div>
|
|
2252
2208
|
|
|
2253
|
-
<!-- MONOAGENT -->
|
|
2254
|
-
<div class="view" id="view-monoagent">
|
|
2255
|
-
<div class="vscroll">
|
|
2256
|
-
<div class="pg-title">MonoAgent</div>
|
|
2257
|
-
<div class="pg-sub">Social media automation workflows & run history</div>
|
|
2258
|
-
<div class="mg-tab-bar" style="margin-top:14px">
|
|
2259
|
-
<button class="odt-btn active" data-matab="workflows" onclick="maSwitchTab('workflows')">Workflows</button>
|
|
2260
|
-
<button class="odt-btn" data-matab="history" onclick="maSwitchTab('history')">Action History</button>
|
|
2261
|
-
</div>
|
|
2262
|
-
<div class="wf-pane active" id="ma-tab-workflows">
|
|
2263
|
-
<div style="display:flex;align-items:center;gap:8px;margin-bottom:16px;margin-top:4px">
|
|
2264
|
-
<button class="btn" onclick="loadMonoagentData()">↺ Refresh</button>
|
|
2265
|
-
</div>
|
|
2266
|
-
<div id="ma-workflows-list"><div class="loading-txt">Loading…</div></div>
|
|
2267
|
-
</div>
|
|
2268
|
-
<div class="wf-pane" id="ma-tab-history">
|
|
2269
|
-
<div style="display:flex;align-items:center;gap:8px;margin-bottom:16px;margin-top:4px">
|
|
2270
|
-
<button class="btn" onclick="loadMonoagentData()">↺ Refresh</button>
|
|
2271
|
-
</div>
|
|
2272
|
-
<div id="ma-history-list"><div class="loading-txt">Loading…</div></div>
|
|
2273
|
-
</div>
|
|
2274
|
-
</div>
|
|
2275
|
-
</div>
|
|
2276
|
-
|
|
2277
2209
|
<!-- GLOBAL FEED -->
|
|
2278
2210
|
<div class="view" id="view-global">
|
|
2279
2211
|
<div class="vscroll">
|
|
@@ -2327,6 +2259,17 @@ select.pb-cfg-inp { padding: 4px 7px; cursor: pointer; }
|
|
|
2327
2259
|
</div>
|
|
2328
2260
|
</div>
|
|
2329
2261
|
|
|
2262
|
+
<!-- HUMAN INPUT (global, cross-org) -->
|
|
2263
|
+
<div class="view" id="view-humaninput">
|
|
2264
|
+
<div class="vscroll">
|
|
2265
|
+
<div style="display:flex;align-items:baseline;gap:10px;margin-bottom:4px">
|
|
2266
|
+
<div class="pg-title" style="margin-bottom:0">Human Input</div>
|
|
2267
|
+
<span class="pg-sub" id="hi-sub" style="margin-bottom:0">Pending questions across all projects</span>
|
|
2268
|
+
</div>
|
|
2269
|
+
<div id="hi-content" style="margin-top:16px"><div class="loading-txt">Loading…</div></div>
|
|
2270
|
+
</div>
|
|
2271
|
+
</div>
|
|
2272
|
+
|
|
2330
2273
|
</div><!-- /view-wrap -->
|
|
2331
2274
|
</div><!-- /main -->
|
|
2332
2275
|
<div id="app-ambient-hint">Press A to exit ambient mode</div>
|
|
@@ -2427,6 +2370,27 @@ select.pb-cfg-inp { padding: 4px 7px; cursor: pointer; }
|
|
|
2427
2370
|
</div>
|
|
2428
2371
|
|
|
2429
2372
|
<script>
|
|
2373
|
+
// ── auth: attach this session's dashboard credential to every non-GET fetch ──
|
|
2374
|
+
// server.mjs requires an x-monomind-token header on all non-GET routes and
|
|
2375
|
+
// injects the current value via a <meta name="mm-token"> tag on page load.
|
|
2376
|
+
// Wrapping fetch() once here (rather than editing every individual POST/
|
|
2377
|
+
// DELETE call site across this file) means every existing and future write
|
|
2378
|
+
// action is covered automatically.
|
|
2379
|
+
(function () {
|
|
2380
|
+
const _mmAuthTag = document.querySelector('meta[name="mm-token"]');
|
|
2381
|
+
const _mmAuthValue = _mmAuthTag ? _mmAuthTag.content : '';
|
|
2382
|
+
const _origFetch = window.fetch.bind(window);
|
|
2383
|
+
window.fetch = function (input, init) {
|
|
2384
|
+
const method = (init && init.method ? init.method : 'GET').toUpperCase();
|
|
2385
|
+
if (_mmAuthValue && method !== 'GET' && method !== 'HEAD') {
|
|
2386
|
+
init = init ? { ...init } : {};
|
|
2387
|
+
init.headers = new Headers(init.headers || {});
|
|
2388
|
+
if (!init.headers.has('x-monomind-token')) init.headers.set('x-monomind-token', _mmAuthValue);
|
|
2389
|
+
}
|
|
2390
|
+
return _origFetch(input, init);
|
|
2391
|
+
};
|
|
2392
|
+
})();
|
|
2393
|
+
|
|
2430
2394
|
// ── state ──────────────────────────────────────────────────
|
|
2431
2395
|
let DIR = '';
|
|
2432
2396
|
let ORIGINAL_DIR = '';
|
|
@@ -2465,10 +2429,10 @@ function switchView(v, { updateHash = true } = {}) {
|
|
|
2465
2429
|
el.classList.toggle('active', el.dataset.view === v));
|
|
2466
2430
|
document.querySelectorAll('.view').forEach(el =>
|
|
2467
2431
|
el.classList.toggle('active', el.id === 'view-' + v));
|
|
2468
|
-
const titles = { now:'Now', projects:'Projects', sessions:'Sessions', loops:'Loops', tokens:'Tokens', memory:'Memory', orgs:'Orgs', monograph:'Monograph',
|
|
2432
|
+
const titles = { now:'Now', projects:'Projects', sessions:'Sessions', loops:'Loops', tokens:'Tokens', memory:'Memory', orgs:'Orgs', monograph:'Monograph', global:'Global Feed', 'global-loops':'Global Loops', 'global-tokens':'Global Tokens', chat:'Global Agent Chat', humaninput:'Human Input' };
|
|
2469
2433
|
document.getElementById('view-title').textContent = titles[v] || v;
|
|
2470
2434
|
const PROJECT = DIR ? shortPath(DIR) : 'monomind';
|
|
2471
|
-
const VIEW_LABELS = { now: 'Now', sessions: 'Sessions', projects: 'Projects', loops: 'Loops', tokens: 'Tokens', memory: 'Memory', orgs: 'Orgs', monograph: 'Monograph', global: 'Global Feed', 'global-loops': 'Global Loops', 'global-tokens': 'Global Tokens' };
|
|
2435
|
+
const VIEW_LABELS = { now: 'Now', sessions: 'Sessions', projects: 'Projects', loops: 'Loops', tokens: 'Tokens', memory: 'Memory', orgs: 'Orgs', monograph: 'Monograph', global: 'Global Feed', 'global-loops': 'Global Loops', 'global-tokens': 'Global Tokens', chat: 'Global Agent Chat', humaninput: 'Human Input' };
|
|
2472
2436
|
document.title = `monomind · ${PROJECT} · ${VIEW_LABELS[v] || v}`;
|
|
2473
2437
|
if (updateHash) _pushHash();
|
|
2474
2438
|
// Projects always re-fetches so onclick paths in cards stay current
|
|
@@ -2528,10 +2492,10 @@ function renderView(v) {
|
|
|
2528
2492
|
if (v === 'memory') renderMemory();
|
|
2529
2493
|
if (v === 'orgs') renderOrgs();
|
|
2530
2494
|
if (v === 'monograph') loadMonograph();
|
|
2531
|
-
if (v === 'monoagent') { loadMonoagentData(); }
|
|
2532
2495
|
if (v === 'global') renderGlobalFeed();
|
|
2533
2496
|
if (v === 'global-loops') renderGlobalLoops();
|
|
2534
2497
|
if (v === 'global-tokens') renderGlobalTokens();
|
|
2498
|
+
if (v === 'humaninput') renderHumanInputView();
|
|
2535
2499
|
}
|
|
2536
2500
|
|
|
2537
2501
|
function refreshCurrent() {
|
|
@@ -3658,7 +3622,7 @@ function renderProjectGrid(projects, query) {
|
|
|
3658
3622
|
const isCurrent = p.path === DIR;
|
|
3659
3623
|
const score = computeHealthScore(p);
|
|
3660
3624
|
const hCls = healthClass(score);
|
|
3661
|
-
return `<div class="proj-card${isCurrent ? ' current' : ''}" onclick="switchProject('${esc(p.path || '')}')">
|
|
3625
|
+
return `<div class="proj-card${isCurrent ? ' current' : ''}" onclick="switchProject('${esc(String(p.path || '').replace(/['\\]/g,'\\$&'))}')">
|
|
3662
3626
|
${isCurrent ? '<div class="proj-card-badge">active</div>' : ''}
|
|
3663
3627
|
<div class="proj-health ${hCls}" title="Health score: ${score}">${score}</div>
|
|
3664
3628
|
<div class="proj-card-name">${esc(p.name || p.slug)}</div>
|
|
@@ -3735,7 +3699,7 @@ async function renderSessions() {
|
|
|
3735
3699
|
let anomBadge = '';
|
|
3736
3700
|
if (medianCost > 0.05 && sCost > medianCost * 3 && sCost > 0.5) {
|
|
3737
3701
|
// f60: anomaly badge is clickable for explainer
|
|
3738
|
-
anomBadge = `<span class="sess-anomaly anom-cost clickable" style="cursor:pointer" onclick="showCostExplainer('${esc(s.id)}',event)" title="Cost ${((sCost/medianCost).toFixed(1))}× above median — click for details">! costly</span>`;
|
|
3702
|
+
anomBadge = `<span class="sess-anomaly anom-cost clickable" style="cursor:pointer" onclick="showCostExplainer('${esc(String(s.id).replace(/['\\]/g,'\\$&'))}',event)" title="Cost ${((sCost/medianCost).toFixed(1))}× above median — click for details">! costly</span>`;
|
|
3739
3703
|
} else if (s.toolCalls > 0 && (s.errorCount || 0) / s.toolCalls > 0.3) {
|
|
3740
3704
|
anomBadge = `<span class="sess-anomaly anom-err" title="${s.errorCount} tool errors">! errors</span>`;
|
|
3741
3705
|
}
|
|
@@ -3763,7 +3727,7 @@ async function renderSessions() {
|
|
|
3763
3727
|
</div>` : '';
|
|
3764
3728
|
// f51: error badge — clickable if errors exist
|
|
3765
3729
|
const errBadge = (s.errorCount > 0 && s.toolCalls > 0 && (s.errorCount / s.toolCalls) > 0.3)
|
|
3766
|
-
? `<span class="sess-anomaly anom-err clickable" onclick="toggleErrDrawer('${esc(s.id)}',event)" title="Click to see ${s.errorCount} tool errors">${s.errorCount} err</span>`
|
|
3730
|
+
? `<span class="sess-anomaly anom-err clickable" onclick="toggleErrDrawer('${esc(String(s.id).replace(/['\\]/g,'\\$&'))}',event)" title="Click to see ${s.errorCount} tool errors">${s.errorCount} err</span>`
|
|
3767
3731
|
: anomBadge;
|
|
3768
3732
|
// f50: custom tags
|
|
3769
3733
|
const ctags = getCustomTags(s.id);
|
|
@@ -3774,12 +3738,12 @@ async function renderSessions() {
|
|
|
3774
3738
|
const summaryHtml = s.summary
|
|
3775
3739
|
? `<div class="sr-summary">${esc(s.summary.slice(0, 180))}</div>`
|
|
3776
3740
|
: '';
|
|
3777
|
-
return `<div class="sess-row" data-sess-idx="${idx}" data-sess-id="${esc(s.id)}" onclick="handleSessRowClick(event,this,'${esc(s.id)}')" data-sess-data='${sData}'>
|
|
3741
|
+
return `<div class="sess-row" data-sess-idx="${idx}" data-sess-id="${esc(s.id)}" onclick="handleSessRowClick(event,this,'${esc(String(s.id).replace(/['\\]/g,'\\$&'))}')" data-sess-data='${sData}'>
|
|
3778
3742
|
<div class="sr-top">
|
|
3779
3743
|
<div class="sr-prompt">${esc(s.lastPrompt || s.id?.slice(0,8) || '—')}</div>
|
|
3780
3744
|
<div class="sr-time">${relTime(s.lastTs || s.mtime)}</div>${compactBadge}
|
|
3781
3745
|
<button class="sr-copy-btn" data-prompt="${esc(s.lastPrompt || s.id)}" onclick="copyPrompt(this.dataset.prompt,event)" title="Copy prompt to clipboard">⎘</button>
|
|
3782
|
-
<button class="sess-star${isStarred ? ' on' : ''}" data-sid="${esc(s.id)}" onclick="toggleBookmark('${esc(s.id)}',event)" title="${isStarred ? 'Remove bookmark' : 'Bookmark session'}">${isStarred ? '★' : '☆'}</button>
|
|
3746
|
+
<button class="sess-star${isStarred ? ' on' : ''}" data-sid="${esc(s.id)}" onclick="toggleBookmark('${esc(String(s.id).replace(/['\\]/g,'\\$&'))}',event)" title="${isStarred ? 'Remove bookmark' : 'Bookmark session'}">${isStarred ? '★' : '☆'}</button>
|
|
3783
3747
|
<span class="sr-view">→ view</span>
|
|
3784
3748
|
</div>
|
|
3785
3749
|
${summaryHtml}
|
|
@@ -3791,7 +3755,7 @@ async function renderSessions() {
|
|
|
3791
3755
|
${ctxGauge}
|
|
3792
3756
|
<div class="err-drawer" id="err-drawer-${esc(s.id)}"></div>
|
|
3793
3757
|
<div class="sess-notes-wrap" onclick="event.stopPropagation()">
|
|
3794
|
-
<button class="sess-notes-toggle${hasNote ? ' has-note' : ''}" onclick="toggleSessNote('${esc(s.id)}',this)">✎ ${hasNote ? 'Note' : 'Add note'}</button>
|
|
3758
|
+
<button class="sess-notes-toggle${hasNote ? ' has-note' : ''}" onclick="toggleSessNote('${esc(String(s.id).replace(/['\\]/g,'\\$&'))}',this)">✎ ${hasNote ? 'Note' : 'Add note'}</button>
|
|
3795
3759
|
<div class="sess-notes-area" id="snote-${esc(s.id)}">
|
|
3796
3760
|
<textarea class="sess-note-input" rows="2" placeholder="Session note…" oninput="saveSessNote('${esc(s.id)}',this.value,this.closest('.sess-notes-wrap').querySelector('.sess-notes-toggle'),this.closest('.sess-row').querySelector('.sess-note-saved'))">${esc(note)}</textarea>
|
|
3797
3761
|
<div class="sess-note-saved"></div>
|
|
@@ -3918,7 +3882,7 @@ function buildTagFilterBar(sessions) {
|
|
|
3918
3882
|
if (!allTags.common.size) return '';
|
|
3919
3883
|
const sorted = [...allTags.common].sort();
|
|
3920
3884
|
const chips = sorted.map(t =>
|
|
3921
|
-
`<button class="tag-chip${activeTagFilter === t ? ' active' : ''}" onclick="setTagFilter('${esc(t)}')">${esc(t)}</button>`
|
|
3885
|
+
`<button class="tag-chip${activeTagFilter === t ? ' active' : ''}" onclick="setTagFilter('${esc(String(t).replace(/['\\]/g,'\\$&'))}')">${esc(t)}</button>`
|
|
3922
3886
|
).join('');
|
|
3923
3887
|
return `<div class="tag-filter-bar">${chips}</div>`;
|
|
3924
3888
|
}
|
|
@@ -4169,7 +4133,7 @@ async function renderGlobalFeed() {
|
|
|
4169
4133
|
const gfSummaryHtml = s.summary
|
|
4170
4134
|
? `<div class="sr-summary">${esc(s.summary.slice(0, 180))}</div>`
|
|
4171
4135
|
: '';
|
|
4172
|
-
return `<div class="sess-row" onclick="switchProject('${esc(project.path)}');setTimeout(()=>jumpToSession('${esc(s.id)}'),150)">
|
|
4136
|
+
return `<div class="sess-row" onclick="switchProject('${esc(String(project.path).replace(/['\\]/g,'\\$&'))}');setTimeout(()=>jumpToSession('${esc(String(s.id).replace(/['\\]/g,'\\$&'))}'),150)">
|
|
4173
4137
|
<div class="sr-top">
|
|
4174
4138
|
<div class="sr-prompt">${esc(s.lastPrompt || s.id?.slice(0,8) || '—')}</div>
|
|
4175
4139
|
<div class="sr-time">${relTime(s.lastTs || s.mtime)}</div>${gfCompactBadge}
|
|
@@ -4184,6 +4148,73 @@ async function renderGlobalFeed() {
|
|
|
4184
4148
|
}
|
|
4185
4149
|
}
|
|
4186
4150
|
|
|
4151
|
+
async function renderHumanInputView() {
|
|
4152
|
+
const el = document.getElementById('hi-content');
|
|
4153
|
+
el.innerHTML = '<div class="loading-txt">Loading all projects…</div>';
|
|
4154
|
+
try {
|
|
4155
|
+
const data = await apiFetch('/api/projects');
|
|
4156
|
+
const projects = (data?.projects || []).slice(0, 8);
|
|
4157
|
+
if (!projects.length) {
|
|
4158
|
+
el.innerHTML = '<div class="empty"><div class="empty-ico">❓</div><div>No projects found</div></div>';
|
|
4159
|
+
updateHumanInputBadge(0);
|
|
4160
|
+
return;
|
|
4161
|
+
}
|
|
4162
|
+
const results = await Promise.allSettled(
|
|
4163
|
+
projects.map(p => apiFetch('/api/questions?dir=' + enc(p.path)).then(d => ({ project: p, questions: d.questions || [] })))
|
|
4164
|
+
);
|
|
4165
|
+
const entries = [];
|
|
4166
|
+
for (const r of results) {
|
|
4167
|
+
if (r.status !== 'fulfilled') continue;
|
|
4168
|
+
const { project, questions } = r.value;
|
|
4169
|
+
for (const q of questions) entries.push({ project, q });
|
|
4170
|
+
}
|
|
4171
|
+
entries.sort((a, b) => (b.q.ts || 0) - (a.q.ts || 0));
|
|
4172
|
+
updateHumanInputBadge(entries.length);
|
|
4173
|
+
document.getElementById('hi-sub').textContent = entries.length
|
|
4174
|
+
? `${entries.length} pending question(s) across ${projects.length} projects`
|
|
4175
|
+
: `No pending questions across ${projects.length} projects`;
|
|
4176
|
+
if (!entries.length) {
|
|
4177
|
+
el.innerHTML = '<div class="empty"><div class="empty-ico">❓</div><div>No pending questions</div></div>';
|
|
4178
|
+
return;
|
|
4179
|
+
}
|
|
4180
|
+
el.innerHTML = '<div class="sess-list">' + entries.map(({ project, q }) => `
|
|
4181
|
+
<div class="sess-row" data-question-id="${esc(q.questionId)}" style="flex-direction:column;align-items:stretch;gap:6px">
|
|
4182
|
+
<div style="display:flex;justify-content:space-between;gap:8px">
|
|
4183
|
+
<div><b>${esc(q.org)}</b> · ${esc(q.role)} <span style="color:var(--text-xs)">(${esc(project.name || project.path)})</span></div>
|
|
4184
|
+
<div style="color:var(--text-xs);font-size:11px;font-family:var(--mono)">${relTime(q.ts)}</div>
|
|
4185
|
+
</div>
|
|
4186
|
+
<div style="color:var(--text-hi)">${esc(q.question)}</div>
|
|
4187
|
+
<div style="display:flex;gap:6px">
|
|
4188
|
+
<input type="text" class="hi-answer-input" placeholder="Your answer…" style="flex:1;padding:4px 8px;font-size:12px" />
|
|
4189
|
+
<button class="btn" style="font-size:11px" onclick="submitHumanAnswer('${esc(String(project.path).replace(/['\\]/g,'\\$&'))}','${esc(String(q.org).replace(/['\\]/g,'\\$&'))}','${esc(String(q.role).replace(/['\\]/g,'\\$&'))}','${esc(String(q.questionId).replace(/['\\]/g,'\\$&'))}',this)">Answer</button>
|
|
4190
|
+
</div>
|
|
4191
|
+
</div>
|
|
4192
|
+
`).join('') + '</div>';
|
|
4193
|
+
} catch (e) { el.innerHTML = '<div class="empty">Failed: ' + esc(e.message) + '</div>'; }
|
|
4194
|
+
}
|
|
4195
|
+
|
|
4196
|
+
function updateHumanInputBadge(count) {
|
|
4197
|
+
const b = document.getElementById('bdg-humaninput');
|
|
4198
|
+
if (b) b.textContent = String(count);
|
|
4199
|
+
}
|
|
4200
|
+
|
|
4201
|
+
async function submitHumanAnswer(dir, org, role, questionId, btnEl) {
|
|
4202
|
+
const row = btnEl.closest('[data-question-id]');
|
|
4203
|
+
const input = row.querySelector('.hi-answer-input');
|
|
4204
|
+
const answer = input.value.trim();
|
|
4205
|
+
if (!answer) return;
|
|
4206
|
+
btnEl.disabled = true;
|
|
4207
|
+
try {
|
|
4208
|
+
const res = await fetch('/api/questions/answer', {
|
|
4209
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
4210
|
+
body: JSON.stringify({ dir, org, role, questionId, answer }),
|
|
4211
|
+
});
|
|
4212
|
+
const data = await res.json();
|
|
4213
|
+
if (data.ok) { showToast('Answered', `${org}:${role}`, 'ok'); row.remove(); }
|
|
4214
|
+
else { showToast('Error', data.error || 'failed', 'err'); btnEl.disabled = false; }
|
|
4215
|
+
} catch (e) { showToast('Error', e.message, 'err'); btnEl.disabled = false; }
|
|
4216
|
+
}
|
|
4217
|
+
|
|
4187
4218
|
// ── agent chat view ────────────────────────────────────────
|
|
4188
4219
|
let chatVSessions = {}; // sessionId or groupId → session/group object
|
|
4189
4220
|
let chatVGroupMap = {}; // groupId → group object
|
|
@@ -5075,7 +5106,7 @@ function renderLeaderboard() {
|
|
|
5075
5106
|
const cost = '$' + s.totalCost.toFixed(2);
|
|
5076
5107
|
const dur = s.totalDurationMs ? fmtDur(s.totalDurationMs) : '—';
|
|
5077
5108
|
const prompt = s.lastPrompt || s.id;
|
|
5078
|
-
return `<tr onclick="jumpToSession('${esc(s.id)}')" title="${esc(prompt)}">
|
|
5109
|
+
return `<tr onclick="jumpToSession('${esc(String(s.id).replace(/['\\]/g,'\\$&'))}')" title="${esc(prompt)}">
|
|
5079
5110
|
<td class="lb-rank">${i + 1}</td>
|
|
5080
5111
|
<td class="lb-prompt">${esc(prompt.slice(0, 60))}</td>
|
|
5081
5112
|
<td class="lb-cost">${cost}</td>
|
|
@@ -5325,7 +5356,7 @@ function buildSwimlane() {
|
|
|
5325
5356
|
const color = `oklch(65% 0.14 ${hue})`;
|
|
5326
5357
|
const name = (s.lastPrompt || s.id).slice(0, 16);
|
|
5327
5358
|
return `<div class="sw-row">
|
|
5328
|
-
<div class="sw-lbl" onclick="jumpToSession('${esc(s.id)}')" title="${esc(s.lastPrompt || s.id)}">${esc(name)}</div>
|
|
5359
|
+
<div class="sw-lbl" onclick="jumpToSession('${esc(String(s.id).replace(/['\\]/g,'\\$&'))}')" title="${esc(s.lastPrompt || s.id)}">${esc(name)}</div>
|
|
5329
5360
|
<div class="sw-track">
|
|
5330
5361
|
<div class="sw-bar" style="left:${leftPct}%;width:${widthPct}%;background:${color}" title="${esc(name)} · ${fmtDur(dur)}"></div>
|
|
5331
5362
|
</div>
|
|
@@ -5786,6 +5817,7 @@ let _v2SelOrg = null;
|
|
|
5786
5817
|
let _v2OrgData = null;
|
|
5787
5818
|
let _v2OrgTab = 'chart';
|
|
5788
5819
|
const _v2OrgEventLog = {};
|
|
5820
|
+
let _v2ChartLayout = { pos: {}, edgeIdByPair: new Map() };
|
|
5789
5821
|
|
|
5790
5822
|
async function renderOrgs() {
|
|
5791
5823
|
try {
|
|
@@ -6204,6 +6236,9 @@ function v2RenderOrgChart() {
|
|
|
6204
6236
|
<image href="${avatarSrc}" x="${-avR}" y="${-R + 2}" width="${avR * 2}" height="${avR * 2}" clip-path="url(#${nodeClipIds[i]})" preserveAspectRatio="xMidYMid meet" onerror="this.setAttribute('href','data/avatars/coder.svg')"/>
|
|
6205
6237
|
<text text-anchor="middle" y="${nameY}" font-size="9" font-weight="${leader?'600':'500'}" font-family="'Inter',system-ui,sans-serif" fill="${color}">${esc(nameText)}</text>
|
|
6206
6238
|
${subTypeText ? `<text text-anchor="middle" y="${+nameY + 11}" font-size="7.5" font-family="'Inter',system-ui,sans-serif" fill="oklch(48% 0.005 75)">${esc(subTypeText)}</text>` : ''}
|
|
6239
|
+
<foreignObject class="v2-activity-bubble" data-bubble-for="${esc(role.id)}" x="-70" y="${-R - 46}" width="140" height="34" style="overflow:visible;pointer-events:none;visibility:hidden;opacity:0">
|
|
6240
|
+
<div class="v2-bubble-inner" xmlns="http://www.w3.org/1999/xhtml"></div>
|
|
6241
|
+
</foreignObject>
|
|
6207
6242
|
</g>`;
|
|
6208
6243
|
});
|
|
6209
6244
|
|
|
@@ -6230,6 +6265,7 @@ function v2RenderOrgChart() {
|
|
|
6230
6265
|
<g id="v2oc-edges">${edgesHTML}</g>
|
|
6231
6266
|
<g id="v2oc-nodes">${nodesHTML}</g>
|
|
6232
6267
|
<g id="v2oc-particles" opacity="${isRunning ? 1 : 0}">${particlesHTML}</g>
|
|
6268
|
+
<g id="v2oc-letters"></g>
|
|
6233
6269
|
</svg>
|
|
6234
6270
|
</div>
|
|
6235
6271
|
<div class="org-chart-legend" style="margin-top:10px">
|
|
@@ -6262,8 +6298,206 @@ function v2RenderOrgChart() {
|
|
|
6262
6298
|
el.style.opacity = '0';
|
|
6263
6299
|
});
|
|
6264
6300
|
}
|
|
6301
|
+
|
|
6302
|
+
const edgeIdByPair = new Map();
|
|
6303
|
+
comms.forEach((edge, ei) => {
|
|
6304
|
+
if (edge.from === edge.to) return;
|
|
6305
|
+
edgeIdByPair.set(edge.from + '->' + edge.to, 'v2ep' + ei);
|
|
6306
|
+
});
|
|
6307
|
+
_v2ChartLayout = { pos, edgeIdByPair };
|
|
6308
|
+
v2OrgActivityReset();
|
|
6309
|
+
// The org-chart's live bubbles (activity + pending-question) and flying letters are ALL
|
|
6310
|
+
// driven by _odtHandleLiveEvent, which only receives events while the odt SSE connections
|
|
6311
|
+
// are open. Those were previously established only from v2RenderOrgChat (the Chat tab), so a
|
|
6312
|
+
// user who opens the Chart tab without first visiting Chat got NO live chart updates — the
|
|
6313
|
+
// question bubble never lit. Establish them here too so the Chart tab is self-sufficient.
|
|
6314
|
+
// Both connect helpers are idempotent (guarded) and null-safe on their live-dot elements.
|
|
6315
|
+
_odtConnectChatSSE();
|
|
6316
|
+
if (_v2SelOrg) _odtConnectOrgTail(_v2SelOrg);
|
|
6317
|
+
}
|
|
6318
|
+
|
|
6319
|
+
function _v2RoleName(id, roles) {
|
|
6320
|
+
const r = (roles || []).find(x => x.id === id);
|
|
6321
|
+
return r ? (r.name || r.title || r.id) : id;
|
|
6322
|
+
}
|
|
6323
|
+
|
|
6324
|
+
function v2DeriveActivityText(ev, roles) {
|
|
6325
|
+
if (!ev || !ev.from) return null;
|
|
6326
|
+
const type = ev.type || '';
|
|
6327
|
+
if (type === 'org:tool') return ev.tool ? ('running ' + ev.tool) : 'active';
|
|
6328
|
+
if (type === 'org:checkpoint') {
|
|
6329
|
+
const p = ev.progress || '';
|
|
6330
|
+
return p ? (p.length > 40 ? p.slice(0, 39) + '...' : p) : 'active';
|
|
6331
|
+
}
|
|
6332
|
+
if (type === 'org:comms') {
|
|
6333
|
+
if (!ev.to || ev.to === 'all') return 'broadcasting';
|
|
6334
|
+
return 'messaging ' + _v2RoleName(ev.to, roles);
|
|
6335
|
+
}
|
|
6336
|
+
if (type === 'org:artifact') {
|
|
6337
|
+
const label = ev.artifact && ev.artifact.label;
|
|
6338
|
+
return label ? ('writing ' + label) : 'active';
|
|
6339
|
+
}
|
|
6340
|
+
return 'active';
|
|
6341
|
+
}
|
|
6342
|
+
|
|
6343
|
+
function v2DeriveLetterTarget(ev, roles) {
|
|
6344
|
+
if (!ev || !ev.from) return null;
|
|
6345
|
+
const type = ev.type || '';
|
|
6346
|
+
if (type === 'org:comms') {
|
|
6347
|
+
if (!ev.to || ev.to === 'all') return null;
|
|
6348
|
+
return { from: ev.from, to: ev.to };
|
|
6349
|
+
}
|
|
6350
|
+
if (type === 'org:checkpoint' || type === 'org:artifact') {
|
|
6351
|
+
const leader = (roles || []).find(_v2OrgIsLeader);
|
|
6352
|
+
if (!leader || leader.id === ev.from) return null;
|
|
6353
|
+
return { from: ev.from, to: leader.id };
|
|
6354
|
+
}
|
|
6355
|
+
return null;
|
|
6356
|
+
}
|
|
6357
|
+
|
|
6358
|
+
let _v2LetterBurst = { lastTs: 0, count: 0 };
|
|
6359
|
+
|
|
6360
|
+
function v2SpawnLetter(fromId, toId) {
|
|
6361
|
+
const layout = _v2ChartLayout;
|
|
6362
|
+
const fp = layout.pos[fromId], tp = layout.pos[toId];
|
|
6363
|
+
if (!fp || !tp) return;
|
|
6364
|
+
const group = document.getElementById('v2oc-letters');
|
|
6365
|
+
if (!group) return;
|
|
6366
|
+
|
|
6367
|
+
const now = Date.now();
|
|
6368
|
+
if (now - _v2LetterBurst.lastTs < 150) _v2LetterBurst.count++;
|
|
6369
|
+
else _v2LetterBurst.count = 0;
|
|
6370
|
+
_v2LetterBurst.lastTs = now;
|
|
6371
|
+
const delay = (_v2LetterBurst.count * 0.08).toFixed(2);
|
|
6372
|
+
|
|
6373
|
+
const svgNS = 'http://www.w3.org/2000/svg';
|
|
6374
|
+
let pathId = layout.edgeIdByPair.get(fromId + '->' + toId);
|
|
6375
|
+
let synthesizedPath = null;
|
|
6376
|
+
if (!pathId) {
|
|
6377
|
+
pathId = 'v2lp' + Math.random().toString(36).slice(2, 9);
|
|
6378
|
+
synthesizedPath = document.createElementNS(svgNS, 'path');
|
|
6379
|
+
synthesizedPath.setAttribute('id', pathId);
|
|
6380
|
+
synthesizedPath.setAttribute('d', 'M' + fp.x + ',' + fp.y + ' L' + tp.x + ',' + tp.y);
|
|
6381
|
+
synthesizedPath.setAttribute('fill', 'none');
|
|
6382
|
+
synthesizedPath.setAttribute('stroke', 'none');
|
|
6383
|
+
document.getElementById('v2oc-edges').appendChild(synthesizedPath);
|
|
6384
|
+
}
|
|
6385
|
+
|
|
6386
|
+
const letter = document.createElementNS(svgNS, 'g');
|
|
6387
|
+
const circ = document.createElementNS(svgNS, 'circle');
|
|
6388
|
+
circ.setAttribute('r', '6');
|
|
6389
|
+
circ.setAttribute('fill', 'oklch(85% 0.16 85)');
|
|
6390
|
+
circ.setAttribute('stroke', 'oklch(30% 0.02 55)');
|
|
6391
|
+
circ.setAttribute('stroke-width', '1');
|
|
6392
|
+
letter.appendChild(circ);
|
|
6393
|
+
const flap = document.createElementNS(svgNS, 'path');
|
|
6394
|
+
flap.setAttribute('d', 'M-3.5,-2 L0,1 L3.5,-2');
|
|
6395
|
+
flap.setAttribute('stroke', 'oklch(30% 0.02 55)');
|
|
6396
|
+
flap.setAttribute('stroke-width', '1');
|
|
6397
|
+
flap.setAttribute('fill', 'none');
|
|
6398
|
+
letter.appendChild(flap);
|
|
6399
|
+
|
|
6400
|
+
const anim = document.createElementNS(svgNS, 'animateMotion');
|
|
6401
|
+
anim.setAttribute('dur', '0.8s');
|
|
6402
|
+
anim.setAttribute('begin', delay + 's');
|
|
6403
|
+
anim.setAttribute('fill', 'freeze');
|
|
6404
|
+
const mpath = document.createElementNS(svgNS, 'mpath');
|
|
6405
|
+
mpath.setAttributeNS('http://www.w3.org/1999/xlink', 'href', '#' + pathId);
|
|
6406
|
+
anim.appendChild(mpath);
|
|
6407
|
+
letter.appendChild(anim);
|
|
6408
|
+
group.appendChild(letter);
|
|
6409
|
+
|
|
6410
|
+
if (typeof gsap !== 'undefined') {
|
|
6411
|
+
gsap.fromTo(letter, { scale: 0, autoAlpha: 0 }, { scale: 1, autoAlpha: 1, duration: 0.15, delay: Number(delay), ease: 'back.out(1.7)' });
|
|
6412
|
+
}
|
|
6413
|
+
|
|
6414
|
+
const totalMs = (Number(delay) + 0.8) * 1000;
|
|
6415
|
+
setTimeout(function() {
|
|
6416
|
+
function cleanup() {
|
|
6417
|
+
letter.remove();
|
|
6418
|
+
if (synthesizedPath) synthesizedPath.remove();
|
|
6419
|
+
}
|
|
6420
|
+
if (typeof gsap !== 'undefined') {
|
|
6421
|
+
gsap.to(letter, { autoAlpha: 0, duration: 0.15, onComplete: cleanup });
|
|
6422
|
+
} else cleanup();
|
|
6423
|
+
}, totalMs);
|
|
6424
|
+
}
|
|
6425
|
+
|
|
6426
|
+
let _v2BubbleTimers = new Map();
|
|
6427
|
+
|
|
6428
|
+
function v2OrgActivityReset() {
|
|
6429
|
+
_v2BubbleTimers.forEach(t => clearTimeout(t));
|
|
6430
|
+
_v2BubbleTimers = new Map();
|
|
6431
|
+
}
|
|
6432
|
+
|
|
6433
|
+
function _v2FadeOutBubble(fo) {
|
|
6434
|
+
if (typeof gsap !== 'undefined') {
|
|
6435
|
+
gsap.to(fo, { autoAlpha: 0, duration: 0.4, ease: 'power2.in', onComplete: function() { fo.style.visibility = 'hidden'; } });
|
|
6436
|
+
} else {
|
|
6437
|
+
fo.style.opacity = '0';
|
|
6438
|
+
fo.style.visibility = 'hidden';
|
|
6439
|
+
}
|
|
6440
|
+
}
|
|
6441
|
+
|
|
6442
|
+
function v2ShowActivityBubble(roleId, text) {
|
|
6443
|
+
const fo = document.querySelector('.v2-activity-bubble[data-bubble-for="' + CSS.escape(roleId) + '"]');
|
|
6444
|
+
if (!fo) return;
|
|
6445
|
+
if (fo.dataset.questionPending === '1') return; // a pending question owns this bubble until answered
|
|
6446
|
+
const inner = fo.querySelector('.v2-bubble-inner');
|
|
6447
|
+
inner.textContent = text;
|
|
6448
|
+
if (typeof gsap !== 'undefined') {
|
|
6449
|
+
gsap.killTweensOf(fo);
|
|
6450
|
+
gsap.fromTo(fo, { scale: 0.8, autoAlpha: 0 }, {
|
|
6451
|
+
scale: 1, autoAlpha: 1, duration: 0.3, ease: 'back.out(1.7)',
|
|
6452
|
+
transformOrigin: '50% 100%',
|
|
6453
|
+
});
|
|
6454
|
+
} else {
|
|
6455
|
+
fo.style.visibility = 'visible';
|
|
6456
|
+
fo.style.opacity = '1';
|
|
6457
|
+
}
|
|
6458
|
+
const existing = _v2BubbleTimers.get(roleId);
|
|
6459
|
+
if (existing) clearTimeout(existing);
|
|
6460
|
+
_v2BubbleTimers.set(roleId, setTimeout(function() {
|
|
6461
|
+
_v2FadeOutBubble(fo);
|
|
6462
|
+
_v2BubbleTimers.delete(roleId);
|
|
6463
|
+
}, 5000));
|
|
6464
|
+
}
|
|
6465
|
+
|
|
6466
|
+
// Truncates by Unicode code point (via the string iterator), not UTF-16 code
|
|
6467
|
+
// unit — a plain .slice() can bisect a surrogate pair (e.g. an emoji in the
|
|
6468
|
+
// question text) and render a broken glyph.
|
|
6469
|
+
function _v2TruncateText(text, maxLen) {
|
|
6470
|
+
const chars = [...text];
|
|
6471
|
+
if (chars.length <= maxLen) return text;
|
|
6472
|
+
return chars.slice(0, maxLen - 1).join('') + '…';
|
|
6473
|
+
}
|
|
6474
|
+
|
|
6475
|
+
function v2ShowQuestionBubble(roleId, questionText) {
|
|
6476
|
+
const fo = document.querySelector('.v2-activity-bubble[data-bubble-for="' + CSS.escape(roleId) + '"]');
|
|
6477
|
+
if (!fo) return;
|
|
6478
|
+
const existing = _v2BubbleTimers.get(roleId);
|
|
6479
|
+
if (existing) { clearTimeout(existing); _v2BubbleTimers.delete(roleId); }
|
|
6480
|
+
fo.dataset.questionPending = '1';
|
|
6481
|
+
const inner = fo.querySelector('.v2-bubble-inner');
|
|
6482
|
+
inner.textContent = '❓ ' + _v2TruncateText(questionText, 40);
|
|
6483
|
+
inner.classList.add('v2-bubble-question');
|
|
6484
|
+
if (typeof gsap !== 'undefined') {
|
|
6485
|
+
gsap.killTweensOf(fo);
|
|
6486
|
+
gsap.fromTo(fo, { scale: 0.8, autoAlpha: 0 }, { scale: 1, autoAlpha: 1, duration: 0.3, ease: 'back.out(1.7)', transformOrigin: '50% 100%' });
|
|
6487
|
+
} else {
|
|
6488
|
+
fo.style.visibility = 'visible';
|
|
6489
|
+
fo.style.opacity = '1';
|
|
6490
|
+
}
|
|
6265
6491
|
}
|
|
6266
6492
|
|
|
6493
|
+
function v2ClearQuestionBubble(roleId) {
|
|
6494
|
+
const fo = document.querySelector('.v2-activity-bubble[data-bubble-for="' + CSS.escape(roleId) + '"]');
|
|
6495
|
+
if (!fo) return;
|
|
6496
|
+
delete fo.dataset.questionPending;
|
|
6497
|
+
const inner = fo.querySelector('.v2-bubble-inner');
|
|
6498
|
+
if (inner) inner.classList.remove('v2-bubble-question');
|
|
6499
|
+
_v2FadeOutBubble(fo);
|
|
6500
|
+
}
|
|
6267
6501
|
|
|
6268
6502
|
// ── in-place running dot updater (avoids full re-render + GSAP flicker on SSE events) ──
|
|
6269
6503
|
function v2UpdateChartRunningDots() {
|
|
@@ -7940,17 +8174,28 @@ window.odtChatSelectAgent = function(name) {
|
|
|
7940
8174
|
});
|
|
7941
8175
|
const feed = document.getElementById('odt-chat-feed');
|
|
7942
8176
|
const emptyEl = document.getElementById('odt-chat-empty');
|
|
7943
|
-
const sess = _odtChatSessions.find(s => s.id === _odtChatCurrentId);
|
|
7944
8177
|
feed.querySelectorAll('.cv-msg').forEach(e => e.remove());
|
|
7945
|
-
|
|
7946
|
-
|
|
8178
|
+
const id = _odtChatCurrentId;
|
|
8179
|
+
let events = null;
|
|
8180
|
+
let sess = null;
|
|
8181
|
+
if (id && id.startsWith('rgrp:') && _odtRunGroups[id]) {
|
|
8182
|
+
events = _odtGetMergedRunGroupEvents(_odtRunGroups[id]);
|
|
8183
|
+
} else if (id && id.startsWith('grp:') && _odtSessionGroups[id]) {
|
|
8184
|
+
events = _odtGetMergedGroupEvents(_odtSessionGroups[id]);
|
|
8185
|
+
} else {
|
|
8186
|
+
sess = _odtChatSessions.find(s => s.id === id);
|
|
8187
|
+
events = sess ? (sess.events || []) : null;
|
|
8188
|
+
}
|
|
8189
|
+
if (events === null) { if (emptyEl) { emptyEl.style.display = 'block'; emptyEl.textContent = 'Select a session to see agent communications.'; } return; }
|
|
8190
|
+
const visible = events.filter(ev => _odtChatAgentMatches(ev));
|
|
7947
8191
|
if (!visible.length) {
|
|
7948
8192
|
if (emptyEl) { emptyEl.style.display = 'block'; emptyEl.textContent = name === 'all' ? 'No events.' : `${name} was online in this run but sent no messages.`; }
|
|
7949
8193
|
} else {
|
|
7950
8194
|
if (emptyEl) emptyEl.style.display = 'none';
|
|
7951
8195
|
visible.forEach(ev => _odtAppendEvent(ev, false));
|
|
8196
|
+
if (!feed.querySelector('.cv-msg') && emptyEl) { emptyEl.style.display = 'block'; emptyEl.textContent = 'No displayable events.'; }
|
|
7952
8197
|
}
|
|
7953
|
-
if (!sess._isOrgRun) {
|
|
8198
|
+
if (sess && !sess._isOrgRun) {
|
|
7954
8199
|
const _linkedRunId = 'run-' + (sess.id || '').replace(/^mm-/, '');
|
|
7955
8200
|
const _linkedRun = _odtChatSessions.find(s => s._isOrgRun && s.id === _linkedRunId);
|
|
7956
8201
|
if (_linkedRun) {
|
|
@@ -7976,17 +8221,25 @@ window.odtChatSelectAgent = function(name) {
|
|
|
7976
8221
|
// Chat tab render mode (Summary/Detailed/Raw) — persists to localStorage
|
|
7977
8222
|
let _odtChatMode = localStorage.getItem('odt-chat-mode') || 'detailed';
|
|
7978
8223
|
|
|
7979
|
-
function
|
|
7980
|
-
_odtChatMode = mode;
|
|
7981
|
-
localStorage.setItem('odt-chat-mode', mode);
|
|
8224
|
+
function _odtApplyChatModeUI(mode) {
|
|
7982
8225
|
document.querySelectorAll('.odt-mode-btn').forEach(b => b.classList.remove('active'));
|
|
7983
8226
|
const btn = document.getElementById('odt-mode-' + mode);
|
|
7984
8227
|
if (btn) btn.classList.add('active');
|
|
7985
8228
|
}
|
|
7986
8229
|
|
|
8230
|
+
function setOdtChatMode(mode) {
|
|
8231
|
+
_odtChatMode = mode;
|
|
8232
|
+
localStorage.setItem('odt-chat-mode', mode);
|
|
8233
|
+
_odtApplyChatModeUI(mode);
|
|
8234
|
+
// Re-render the current selection so the mode change is reflected immediately —
|
|
8235
|
+
// previously this only affected events appended after the switch.
|
|
8236
|
+
if (_odtChatCurrentId) window.odtChatSelectAgent(_odtChatCurrentAgent);
|
|
8237
|
+
}
|
|
8238
|
+
|
|
7987
8239
|
function initOdtChatMode() {
|
|
7988
8240
|
const saved = localStorage.getItem('odt-chat-mode') || 'detailed';
|
|
7989
|
-
|
|
8241
|
+
_odtChatMode = saved;
|
|
8242
|
+
_odtApplyChatModeUI(saved);
|
|
7990
8243
|
}
|
|
7991
8244
|
|
|
7992
8245
|
function _odtAppendEvent(ev, animate) {
|
|
@@ -8146,6 +8399,22 @@ function _odtHandleLiveEvent(ev) {
|
|
|
8146
8399
|
const _arr = [..._odtChatSeenKeys];
|
|
8147
8400
|
_odtChatSeenKeys = new Set(_arr.slice(1000));
|
|
8148
8401
|
}
|
|
8402
|
+
if (ev?.type === 'org:question' && ev.from) {
|
|
8403
|
+
if (ev.org === _v2SelOrg) v2ShowQuestionBubble(ev.from, ev.question || '');
|
|
8404
|
+
if (viewRendered['humaninput']) renderHumanInputView();
|
|
8405
|
+
else updateHumanInputBadge((parseInt(document.getElementById('bdg-humaninput')?.textContent, 10) || 0) + 1);
|
|
8406
|
+
}
|
|
8407
|
+
if (ev?.type === 'org:question-answered' && ev.role) {
|
|
8408
|
+
if (ev.org === _v2SelOrg) v2ClearQuestionBubble(ev.role);
|
|
8409
|
+
}
|
|
8410
|
+
// Wire activity bubble for org chart visualization
|
|
8411
|
+
if (ev && ev.org === _v2SelOrg) {
|
|
8412
|
+
const roles = Array.isArray(_v2OrgData && _v2OrgData.roles) ? _v2OrgData.roles : [];
|
|
8413
|
+
const text = v2DeriveActivityText(ev, roles);
|
|
8414
|
+
if (text && ev.from) v2ShowActivityBubble(ev.from, text);
|
|
8415
|
+
const letterTarget = v2DeriveLetterTarget(ev, roles);
|
|
8416
|
+
if (letterTarget) v2SpawnLetter(letterTarget.from, letterTarget.to);
|
|
8417
|
+
}
|
|
8149
8418
|
// Route org run events (have runId + org) to the active run session
|
|
8150
8419
|
if (ev?.org && ev?.runId && ev.org === _v2SelOrg) {
|
|
8151
8420
|
let runSess = _odtChatSessions.find(s => s.id === ev.runId);
|
|
@@ -8358,23 +8627,65 @@ async function v2RenderOrgFiles() {
|
|
|
8358
8627
|
'<table style="width:100%;border-collapse:collapse;font-size:12px">'+
|
|
8359
8628
|
'<thead><tr style="color:var(--text-xs);font-size:9px;text-align:left"><th style="padding:4px 8px">File</th><th>Type</th><th>Size</th><th>Modified</th><th></th></tr></thead>'+
|
|
8360
8629
|
'<tbody>'+g.items.map(f => {
|
|
8630
|
+
const isImg = OF_IMG_RE.test(f.name);
|
|
8361
8631
|
return '<tr style="border-top:1px solid var(--border)" data-fp="'+esc(f.path)+'" data-fn="'+esc(f.name)+'">'+
|
|
8362
8632
|
'<td style="padding:6px 8px"><span style="font-family:var(--mono);font-size:11px;color:var(--text-hi);cursor:pointer" onclick="v2ViewOrgFile(this.closest(\'tr\').dataset.fp,this.closest(\'tr\').dataset.fn)">'+esc(f.name)+'</span></td>'+
|
|
8363
8633
|
'<td style="padding:6px 8px">'+badge(f.type)+'</td>'+
|
|
8364
8634
|
'<td style="padding:6px 8px;font-size:10px;color:var(--text-lo);font-family:var(--mono)">'+fmtSize(f.size||0)+'</td>'+
|
|
8365
8635
|
'<td style="padding:6px 8px;font-size:10px;color:var(--text-lo)">'+fmtTime(f.mtime)+'</td>'+
|
|
8366
|
-
'<td style="padding:6px 8px
|
|
8636
|
+
'<td style="padding:6px 8px;white-space:nowrap">'+
|
|
8637
|
+
'<button class="btn" style="font-size:9px;padding:2px 7px" onclick="v2ViewOrgFile(this.closest(\'tr\').dataset.fp,this.closest(\'tr\').dataset.fn)">View</button> '+
|
|
8638
|
+
'<button class="btn" style="font-size:9px;padding:2px 7px" onclick="_ofCopyPath(this.closest(\'tr\').dataset.fp)">Copy Path</button>'+
|
|
8639
|
+
(isImg ? '' : ' <button class="btn" style="font-size:9px;padding:2px 7px" onclick="_ofCopyContent(this.closest(\'tr\').dataset.fp)">Copy</button>') +
|
|
8640
|
+
'</td></tr>';
|
|
8367
8641
|
}).join('')+'</tbody></table></div>';
|
|
8368
8642
|
}).join('')+'</div>';
|
|
8369
8643
|
} catch(e) { el.innerHTML = '<div style="padding:20px;color:oklch(60% 0.22 25)">Error: '+e.message+'</div>'; }
|
|
8370
8644
|
}
|
|
8371
8645
|
|
|
8646
|
+
function _ofCopyPath(filePath) {
|
|
8647
|
+
if (!filePath) return;
|
|
8648
|
+
navigator.clipboard.writeText(filePath).then(() => {
|
|
8649
|
+
if (typeof showToast === 'function') showToast('Path copied', '', 'ok');
|
|
8650
|
+
});
|
|
8651
|
+
}
|
|
8652
|
+
|
|
8653
|
+
async function _ofCopyContent(filePath) {
|
|
8654
|
+
if (!filePath) return;
|
|
8655
|
+
try {
|
|
8656
|
+
const dirParam = (typeof DIR !== 'undefined' && DIR) ? '&dir='+encodeURIComponent(DIR) : '';
|
|
8657
|
+
const r = await fetch('/api/file-content?path='+encodeURIComponent(filePath)+dirParam);
|
|
8658
|
+
if (!r.ok) { if (typeof showToast === 'function') showToast('Copy failed', 'Error '+r.status, 'error'); return; }
|
|
8659
|
+
const text = await r.text();
|
|
8660
|
+
await navigator.clipboard.writeText(text);
|
|
8661
|
+
if (typeof showToast === 'function') showToast('Content copied', '', 'ok');
|
|
8662
|
+
} catch (e) {
|
|
8663
|
+
if (typeof showToast === 'function') showToast('Copy failed', e.message, 'error');
|
|
8664
|
+
}
|
|
8665
|
+
}
|
|
8666
|
+
|
|
8372
8667
|
let _ofModal = null;
|
|
8373
8668
|
let _ofRawText = '';
|
|
8374
8669
|
|
|
8670
|
+
const OF_IMG_RE = /\.(png|jpe?g|gif|svg|webp)$/i;
|
|
8671
|
+
|
|
8672
|
+
function _ofMdTableRow(row) {
|
|
8673
|
+
var cells = row.trim().replace(/^\||\|$/g,'').split('|').map(function(c){ return c.trim(); });
|
|
8674
|
+
return cells;
|
|
8675
|
+
}
|
|
8676
|
+
|
|
8375
8677
|
function _ofMdToHtml(md) {
|
|
8376
8678
|
return md
|
|
8377
8679
|
.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>')
|
|
8680
|
+
.replace(/^(\|.+\|)[ \t]*\n\|[ \t]*:?-{2,}:?[ \t]*(?:\|[ \t]*:?-{2,}:?[ \t]*)*\|?[ \t]*\n((?:\|.*\|[ \t]*\n?)*)/gm, function(_m, headerRow, bodyRows) {
|
|
8681
|
+
var head = _ofMdTableRow(headerRow);
|
|
8682
|
+
var thead = '<tr>' + head.map(function(c){ return '<th style="padding:6px 10px;text-align:left;border-bottom:1px solid var(--border);color:var(--text-hi)">'+c+'</th>'; }).join('') + '</tr>';
|
|
8683
|
+
var tbody = bodyRows.split('\n').filter(function(r){ return r.trim(); }).map(function(r){
|
|
8684
|
+
var cells = _ofMdTableRow(r);
|
|
8685
|
+
return '<tr>' + cells.map(function(c){ return '<td style="padding:6px 10px;border-bottom:1px solid var(--border)">'+c+'</td>'; }).join('') + '</tr>';
|
|
8686
|
+
}).join('');
|
|
8687
|
+
return '<table style="border-collapse:collapse;width:100%;margin:8px 0;font-size:11px">'+thead+tbody+'</table>';
|
|
8688
|
+
})
|
|
8378
8689
|
.replace(/^#{6}\s+(.+)$/gm,'<h6 style="font-size:11px;font-weight:700;color:var(--text-hi);margin:10px 0 4px">$1</h6>')
|
|
8379
8690
|
.replace(/^#{5}\s+(.+)$/gm,'<h5 style="font-size:12px;font-weight:700;color:var(--text-hi);margin:10px 0 4px">$1</h5>')
|
|
8380
8691
|
.replace(/^#{4}\s+(.+)$/gm,'<h4 style="font-size:13px;font-weight:700;color:var(--text-hi);margin:12px 0 5px">$1</h4>')
|
|
@@ -8389,6 +8700,9 @@ function _ofMdToHtml(md) {
|
|
|
8389
8700
|
.replace(/^\s*[-*]\s+(.+)$/gm,'<li style="margin:2px 0;padding-left:4px">$1</li>')
|
|
8390
8701
|
.replace(/(<li.*<\/li>\n?)+/g,'<ul style="padding-left:18px;margin:6px 0">$&</ul>')
|
|
8391
8702
|
.replace(/^\d+\.\s+(.+)$/gm,'<li style="margin:2px 0;padding-left:4px">$1</li>')
|
|
8703
|
+
.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, function(_m, alt, url) {
|
|
8704
|
+
return '<img src="'+String(url).replace(/"/g,'"')+'" alt="'+String(alt).replace(/"/g,'"')+'" loading="lazy" style="max-width:100%;border-radius:4px;margin:8px 0;display:block">';
|
|
8705
|
+
})
|
|
8392
8706
|
.replace(/\[([^\]]+)\]\(([^)]+)\)/g,'<a href="$2" style="color:var(--accent);text-decoration:underline" target="_blank" rel="noopener">$1</a>')
|
|
8393
8707
|
.replace(/\n\n+/g,'</p><p style="margin:0 0 8px">')
|
|
8394
8708
|
.replace(/^(?!<[hupoli]|<pre|<hr)(.+)$/gm, '$1')
|
|
@@ -8399,6 +8713,7 @@ async function v2ViewOrgFile(filePath, fileName) {
|
|
|
8399
8713
|
if (_ofModal) { _ofModal.remove(); _ofModal = null; }
|
|
8400
8714
|
const isMd = /\.(md|markdown)$/i.test(fileName);
|
|
8401
8715
|
const isJson = /\.json$/i.test(fileName);
|
|
8716
|
+
const isImage = OF_IMG_RE.test(fileName);
|
|
8402
8717
|
_ofRawText = '';
|
|
8403
8718
|
_ofModal = document.createElement('div');
|
|
8404
8719
|
_ofModal.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,0.7);z-index:9999;display:flex;align-items:center;justify-content:center;padding:20px;box-sizing:border-box';
|
|
@@ -8408,8 +8723,9 @@ async function v2ViewOrgFile(filePath, fileName) {
|
|
|
8408
8723
|
'<div style="display:flex;align-items:center;gap:8px;padding:10px 14px;border-bottom:1px solid var(--border);flex-shrink:0">' +
|
|
8409
8724
|
'<span style="font-family:var(--mono);font-size:11px;color:var(--text-hi);flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="'+esc(filePath)+'">'+esc(fileName)+'</span>' +
|
|
8410
8725
|
(isMd ? '<button id="of-toggle" class="btn" style="font-size:9px;padding:2px 7px" onclick="_ofToggleRender()">Raw</button>' : '') +
|
|
8411
|
-
'<button class="btn" style="font-size:9px;padding:2px 7px" onclick="navigator.clipboard.writeText(_ofRawText).then(()=>{ if(typeof showToast===\'function\') showToast(\'Copied\',\'\',\'ok\'); })">Copy</button>' +
|
|
8412
|
-
'<button class="btn" style="font-size:9px;padding:2px 7px;border-color:oklch(68% 0.20 150);color:oklch(68% 0.20 150)" onclick="
|
|
8726
|
+
(isImage ? '' : '<button class="btn" style="font-size:9px;padding:2px 7px" onclick="navigator.clipboard.writeText(_ofRawText).then(()=>{ if(typeof showToast===\'function\') showToast(\'Copied\',\'\',\'ok\'); })">Copy</button>') +
|
|
8727
|
+
(isImage ? '<button class="btn" style="font-size:9px;padding:2px 7px;border-color:oklch(68% 0.20 150);color:oklch(68% 0.20 150)" onclick="_ofSaveImage(\''+esc(String(fileName).replace(/['\\]/g,'\\$&'))+'\')">Save</button>'
|
|
8728
|
+
: '<button class="btn" style="font-size:9px;padding:2px 7px;border-color:oklch(68% 0.20 150);color:oklch(68% 0.20 150)" onclick="_ofSaveFile(\''+esc(String(fileName).replace(/['\\]/g,'\\$&'))+'\')">Save</button>') +
|
|
8413
8729
|
'<button class="btn" style="font-size:9px;padding:2px 7px" onclick="_ofModal&&(_ofModal.remove(),_ofModal=null)">✕</button>' +
|
|
8414
8730
|
'</div>' +
|
|
8415
8731
|
'<div id="of-content" style="flex:1;overflow-y:auto;padding:16px 18px;font-size:12px;line-height:1.7;color:var(--text-mid);scrollbar-width:thin;scrollbar-color:var(--border) transparent">' +
|
|
@@ -8417,9 +8733,18 @@ async function v2ViewOrgFile(filePath, fileName) {
|
|
|
8417
8733
|
'</div>' +
|
|
8418
8734
|
'</div>';
|
|
8419
8735
|
document.body.appendChild(_ofModal);
|
|
8736
|
+
const dirParam = (typeof DIR !== 'undefined' && DIR) ? '&dir='+encodeURIComponent(DIR) : '';
|
|
8737
|
+
const fileUrl = '/api/file-content?path='+encodeURIComponent(filePath)+dirParam;
|
|
8738
|
+
if (isImage) {
|
|
8739
|
+
const el = document.getElementById('of-content');
|
|
8740
|
+
if (el) {
|
|
8741
|
+
_ofRawText = fileUrl;
|
|
8742
|
+
el.innerHTML = '<img src="'+esc(fileUrl)+'" alt="'+esc(fileName)+'" style="max-width:100%;display:block;margin:0 auto" onerror="this.replaceWith(document.createTextNode(\'Failed to load image\'))">';
|
|
8743
|
+
}
|
|
8744
|
+
return;
|
|
8745
|
+
}
|
|
8420
8746
|
try {
|
|
8421
|
-
const
|
|
8422
|
-
const r = await fetch('/api/file-content?path='+encodeURIComponent(filePath)+dirParam);
|
|
8747
|
+
const r = await fetch(fileUrl);
|
|
8423
8748
|
const el = document.getElementById('of-content');
|
|
8424
8749
|
if (!el) return;
|
|
8425
8750
|
if (!r.ok) { el.textContent = 'Error '+r.status+': '+r.statusText; return; }
|
|
@@ -8466,6 +8791,14 @@ function _ofSaveFile(defaultName) {
|
|
|
8466
8791
|
URL.revokeObjectURL(a.href);
|
|
8467
8792
|
}
|
|
8468
8793
|
|
|
8794
|
+
function _ofSaveImage(defaultName) {
|
|
8795
|
+
if (!_ofRawText) return;
|
|
8796
|
+
const a = document.createElement('a');
|
|
8797
|
+
a.href = _ofRawText;
|
|
8798
|
+
a.download = defaultName || 'image';
|
|
8799
|
+
a.click();
|
|
8800
|
+
}
|
|
8801
|
+
|
|
8469
8802
|
// ── WORKSPACES ─────────────────────────────────────────────
|
|
8470
8803
|
async function v2RenderOrgWorkspaces() {
|
|
8471
8804
|
const el = document.getElementById('odt-workspaces');
|
|
@@ -8707,7 +9040,7 @@ window.v2ShowCopyOrgDialog = async function() {
|
|
|
8707
9040
|
const label = p.name || p.slug || p.path?.split('/').pop() || p.path;
|
|
8708
9041
|
const shortP = (p.path || '').replace(/\/Users\/[^/]+\//, '~/');
|
|
8709
9042
|
const isCurrent = p.path === (window._orgDir || '');
|
|
8710
|
-
return `<div class="ocp-row${isCurrent ? ' ocp-sel' : ''}" title="${esc(p.path)}" onclick="document.querySelectorAll('.ocp-row').forEach(r=>r.classList.remove('ocp-sel'));this.classList.add('ocp-sel');document.getElementById('org-copy-dest').value='${esc(p.path).replace(/'/g,
|
|
9043
|
+
return `<div class="ocp-row${isCurrent ? ' ocp-sel' : ''}" title="${esc(p.path)}" onclick="document.querySelectorAll('.ocp-row').forEach(r=>r.classList.remove('ocp-sel'));this.classList.add('ocp-sel');document.getElementById('org-copy-dest').value='${esc(String(p.path).replace(/['\\]/g,'\\$&'))}'" style="padding:7px 10px;font-size:12px;font-family:var(--mono);cursor:pointer;border-bottom:1px solid var(--border);display:flex;gap:8px;align-items:baseline">
|
|
8711
9044
|
<span style="color:var(--text-hi)">${esc(label)}</span>
|
|
8712
9045
|
<span style="color:var(--text-lo);font-size:10px;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${esc(shortP)}</span>
|
|
8713
9046
|
</div>`;
|
|
@@ -9915,7 +10248,7 @@ async function loadProjCosts() {
|
|
|
9915
10248
|
const rows = data.projects.slice(0, 10).map((p, i) => {
|
|
9916
10249
|
const barW = maxCost > 0 ? Math.round((p.cost / maxCost) * 100) : 0;
|
|
9917
10250
|
const name = p.path.split('/').filter(Boolean).pop() || p.path;
|
|
9918
|
-
return `<tr onclick="switchProject('${esc(p.path)}')" style="cursor:pointer" title="${esc(p.path)}">
|
|
10251
|
+
return `<tr onclick="switchProject('${esc(String(p.path).replace(/['\\]/g,'\\$&'))}')" style="cursor:pointer" title="${esc(p.path)}">
|
|
9919
10252
|
<td class="lb-rank">${i + 1}</td>
|
|
9920
10253
|
<td style="font-size:12px;color:var(--text-mid);max-width:140px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${esc(name)}</td>
|
|
9921
10254
|
<td class="lb-cost">$${p.cost.toFixed(2)}</td>
|
|
@@ -9987,7 +10320,7 @@ function buildGanttTimeline() {
|
|
|
9987
10320
|
const opacity = 0.3 + costRatio * 0.7;
|
|
9988
10321
|
const color = DONUT_COLORS[i % DONUT_COLORS.length];
|
|
9989
10322
|
const tip = `${s.lastPrompt ? s.lastPrompt.slice(0,60) : s.id} | $${(s.totalCost||0).toFixed(3)} | ${fmtDur(s.totalDurationMs||0)}`;
|
|
9990
|
-
track += `<div class="tl-bar" style="left:${startPct}%;width:${durPct}%;background:${color};opacity:${opacity}" title="${esc(tip)}" onclick="jumpToSession('${esc(s.id)}')"></div>`;
|
|
10323
|
+
track += `<div class="tl-bar" style="left:${startPct}%;width:${durPct}%;background:${color};opacity:${opacity}" title="${esc(tip)}" onclick="jumpToSession('${esc(String(s.id).replace(/['\\]/g,'\\$&'))}')"></div>`;
|
|
9991
10324
|
}
|
|
9992
10325
|
html += `<div class="tl-day-row"><div class="tl-day-lbl">${lbl}</div><div class="tl-track">${track}</div></div>`;
|
|
9993
10326
|
}
|
|
@@ -10304,7 +10637,7 @@ function showCustomTagInput(sessId, event) {
|
|
|
10304
10637
|
iw.className = 'ctag-input-wrap';
|
|
10305
10638
|
iw.onclick = e => e.stopPropagation();
|
|
10306
10639
|
iw.innerHTML = `<input class="ctag-input" type="text" placeholder="tag name…" maxlength="20" autofocus>
|
|
10307
|
-
<button class="ctag-ok" onclick="(()=>{const inp=this.closest('.ctag-input-wrap').querySelector('input');addCustomTag('${esc(sessId)}',inp.value,event);inp.value='';})()">Add</button>`;
|
|
10640
|
+
<button class="ctag-ok" onclick="(()=>{const inp=this.closest('.ctag-input-wrap').querySelector('input');addCustomTag('${esc(String(sessId).replace(/['\\]/g,'\\$&'))}',inp.value,event);inp.value='';})()">Add</button>`;
|
|
10308
10641
|
wrap.appendChild(iw);
|
|
10309
10642
|
const inp = iw.querySelector('input');
|
|
10310
10643
|
inp.focus();
|
|
@@ -10316,11 +10649,11 @@ function showCustomTagInput(sessId, event) {
|
|
|
10316
10649
|
|
|
10317
10650
|
function renderCustomTagsInline(sessId, tags) {
|
|
10318
10651
|
const tagHtml = tags.map(t =>
|
|
10319
|
-
`<span class="sr-ctag">${esc(t)}<span class="ctag-del" onclick="removeCustomTag('${esc(sessId)}','${esc(t)}',event)" title="Remove tag">✕</span></span>`
|
|
10652
|
+
`<span class="sr-ctag">${esc(t)}<span class="ctag-del" onclick="removeCustomTag('${esc(String(sessId).replace(/['\\]/g,'\\$&'))}','${esc(String(t).replace(/['\\]/g,'\\$&'))}',event)" title="Remove tag">✕</span></span>`
|
|
10320
10653
|
).join('');
|
|
10321
10654
|
return `<div class="sr-custom-tags" data-sess="${esc(sessId)}" onclick="event.stopPropagation()">
|
|
10322
10655
|
${tagHtml}
|
|
10323
|
-
<button class="ctag-add-btn" onclick="showCustomTagInput('${esc(sessId)}',event)" title="Add tag">+ tag</button>
|
|
10656
|
+
<button class="ctag-add-btn" onclick="showCustomTagInput('${esc(String(sessId).replace(/['\\]/g,'\\$&'))}',event)" title="Add tag">+ tag</button>
|
|
10324
10657
|
</div>`;
|
|
10325
10658
|
}
|
|
10326
10659
|
|
|
@@ -10335,14 +10668,14 @@ async function toggleErrDrawer(sessId, event) {
|
|
|
10335
10668
|
try {
|
|
10336
10669
|
const data = await apiFetch('/api/session-errors?dir=' + enc(DIR) + '&id=' + enc(sessId));
|
|
10337
10670
|
if (!data.errors?.length) {
|
|
10338
|
-
drawer.innerHTML = `<div class="err-drawer-head"><span>No error details found</span><button class="err-close" onclick="closeErrDrawer('${esc(sessId)}')">✕</button></div>`;
|
|
10671
|
+
drawer.innerHTML = `<div class="err-drawer-head"><span>No error details found</span><button class="err-close" onclick="closeErrDrawer('${esc(String(sessId).replace(/['\\]/g,'\\$&'))}')">✕</button></div>`;
|
|
10339
10672
|
return;
|
|
10340
10673
|
}
|
|
10341
10674
|
const items = data.errors.map(e => `<div class="err-item">${esc(e.text)}</div>`).join('');
|
|
10342
|
-
drawer.innerHTML = `<div class="err-drawer-head"><span>${data.errors.length} error${data.errors.length !== 1 ? 's' : ''}</span><button class="err-close" onclick="closeErrDrawer('${esc(sessId)}')">✕</button></div>
|
|
10675
|
+
drawer.innerHTML = `<div class="err-drawer-head"><span>${data.errors.length} error${data.errors.length !== 1 ? 's' : ''}</span><button class="err-close" onclick="closeErrDrawer('${esc(String(sessId).replace(/['\\]/g,'\\$&'))}')">✕</button></div>
|
|
10343
10676
|
<div class="err-drawer-body">${items}</div>`;
|
|
10344
10677
|
} catch (err) {
|
|
10345
|
-
drawer.innerHTML = `<div class="err-drawer-head"><span>Could not load: ${esc(err.message)}</span><button class="err-close" onclick="closeErrDrawer('${esc(sessId)}')">✕</button></div>`;
|
|
10678
|
+
drawer.innerHTML = `<div class="err-drawer-head"><span>Could not load: ${esc(err.message)}</span><button class="err-close" onclick="closeErrDrawer('${esc(String(sessId).replace(/['\\]/g,'\\$&'))}')">✕</button></div>`;
|
|
10346
10679
|
}
|
|
10347
10680
|
}
|
|
10348
10681
|
|
|
@@ -10481,19 +10814,12 @@ let _mgWikiMode = 'all';
|
|
|
10481
10814
|
let _mgWikiSearchTimer = null;
|
|
10482
10815
|
|
|
10483
10816
|
/* ── Playbook View ─────────────────────────────────────────── */
|
|
10484
|
-
// monoagent connections state
|
|
10485
|
-
let _maPlatforms = []; // full registry from monoes
|
|
10486
|
-
let _maConns = []; // active API/OAuth connections
|
|
10487
|
-
let _maSessions = []; // browser sessions
|
|
10488
|
-
let _maSelected = null; // currently open modal platform def
|
|
10489
|
-
let _maLoginPoll = null; // active login poll interval (cleared on modal close)
|
|
10490
10817
|
|
|
10491
10818
|
function wfSwitchTab(tab) {
|
|
10492
10819
|
document.querySelectorAll('.wf-pane, #wf-tab-builder').forEach(p => p.classList.remove('active'));
|
|
10493
10820
|
document.querySelectorAll('[data-wftab]').forEach(b => b.classList.remove('active'));
|
|
10494
10821
|
document.getElementById('wf-tab-' + tab).classList.add('active');
|
|
10495
10822
|
document.querySelector('[data-wftab="' + tab + '"]').classList.add('active');
|
|
10496
|
-
if (tab === 'systems') loadPlatformSessions();
|
|
10497
10823
|
if (tab === 'library') loadWorkflowDefs();
|
|
10498
10824
|
if (tab === 'runs') loadWorkflowRuns();
|
|
10499
10825
|
if (tab === 'builder') pbInit();
|
|
@@ -10527,31 +10853,13 @@ async function loadWorkflowDefs() {
|
|
|
10527
10853
|
</div>
|
|
10528
10854
|
</div>
|
|
10529
10855
|
<div style="display:flex;flex-direction:column;gap:4px;flex-shrink:0">
|
|
10530
|
-
<button class="wf-def-run-btn" title="Copy run command" onclick="navigator.clipboard?.writeText('npx monomind browse playbook run .monomind/playbooks/${esc(d.file)}').then(()=>this.textContent='Copied!').catch(()=>{});setTimeout(()=>this.textContent='▶ Run',1500)">▶ Run</button>
|
|
10856
|
+
<button class="wf-def-run-btn" title="Copy run command" onclick="navigator.clipboard?.writeText('npx monomind browse playbook run .monomind/playbooks/${esc(String(d.file).replace(/['\\]/g,'\\$&'))}').then(()=>this.textContent='Copied!').catch(()=>{});setTimeout(()=>this.textContent='▶ Run',1500)">▶ Run</button>
|
|
10531
10857
|
<button class="wf-def-run-btn" style="font-size:10px" title="Open in Builder" onclick="pbLoadDef(${JSON.stringify(d.id)});wfSwitchTab('builder')">✎ Edit</button>
|
|
10532
10858
|
</div>
|
|
10533
10859
|
</div>`;
|
|
10534
10860
|
}).join('');
|
|
10535
10861
|
}
|
|
10536
10862
|
|
|
10537
|
-
async function loadPlatformSessions() {
|
|
10538
|
-
const grid = document.getElementById('wf-systems-grid');
|
|
10539
|
-
if (!grid) return;
|
|
10540
|
-
grid.innerHTML = '<div class="loading-txt">Loading…</div>';
|
|
10541
|
-
try {
|
|
10542
|
-
const [plats, conns] = await Promise.all([
|
|
10543
|
-
fetch('/api/monoagent/platforms').then(r => r.json()).catch(() => []),
|
|
10544
|
-
fetch('/api/monoagent/connections').then(r => r.json()).catch(() => ({ connections: [], sessions: [] })),
|
|
10545
|
-
]);
|
|
10546
|
-
_maPlatforms = Array.isArray(plats) ? plats : [];
|
|
10547
|
-
_maConns = Array.isArray(conns.connections) ? conns.connections : [];
|
|
10548
|
-
_maSessions = Array.isArray(conns.sessions) ? conns.sessions : [];
|
|
10549
|
-
renderSystems();
|
|
10550
|
-
} catch (e) {
|
|
10551
|
-
grid.innerHTML = '<div class="loading-txt">Failed to load systems.</div>';
|
|
10552
|
-
}
|
|
10553
|
-
}
|
|
10554
|
-
|
|
10555
10863
|
function relativeTime(ts) {
|
|
10556
10864
|
if (!ts) return '—';
|
|
10557
10865
|
const s = Math.floor((Date.now() - ts) / 1000);
|
|
@@ -10561,291 +10869,6 @@ function relativeTime(ts) {
|
|
|
10561
10869
|
return Math.floor(s / 86400) + 'd ago';
|
|
10562
10870
|
}
|
|
10563
10871
|
|
|
10564
|
-
const MA_CATEGORY_ORDER = ['social', 'service', 'communication', 'database'];
|
|
10565
|
-
const MA_CATEGORY_LABELS = { social: 'Social', service: 'Services & APIs', communication: 'Communication', database: 'Databases' };
|
|
10566
|
-
const BROWSER_PLATFORMS = new Set(['instagram', 'linkedin', 'x', 'tiktok', 'gemini']);
|
|
10567
|
-
|
|
10568
|
-
function maResolveConn(pid) {
|
|
10569
|
-
pid = pid.toLowerCase();
|
|
10570
|
-
if (BROWSER_PLATFORMS.has(pid)) {
|
|
10571
|
-
const now = Date.now();
|
|
10572
|
-
const s = _maSessions.find(x => (x.Platform || '').toLowerCase() === pid);
|
|
10573
|
-
if (!s) return null;
|
|
10574
|
-
const expiry = new Date(s.Expiry).getTime();
|
|
10575
|
-
return { _type: 'session', id: String(s.ID), account: s.Username || '—', method: 'browser', status: expiry > now ? 'active' : 'expired' };
|
|
10576
|
-
}
|
|
10577
|
-
const c = _maConns.find(x => (x.platform || '').toLowerCase() === pid);
|
|
10578
|
-
if (!c) return null;
|
|
10579
|
-
return { _type: 'connection', id: c.id, account: c.account_id || c.label || '—', method: c.method || '—', status: c.status || 'active', lastTested: c.last_tested };
|
|
10580
|
-
}
|
|
10581
|
-
|
|
10582
|
-
function renderSystems() {
|
|
10583
|
-
const grid = document.getElementById('wf-systems-grid');
|
|
10584
|
-
if (!grid) return;
|
|
10585
|
-
|
|
10586
|
-
if (!_maPlatforms.length) {
|
|
10587
|
-
grid.innerHTML = `<div style="padding:24px;text-align:center;color:var(--text-xs);font-size:12px">
|
|
10588
|
-
<div style="font-size:28px;margin-bottom:8px">🔌</div>
|
|
10589
|
-
<div style="font-weight:600;color:var(--text-lo);margin-bottom:4px">monoagent not found</div>
|
|
10590
|
-
<div style="font-size:11px">Install monoes: <code style="color:var(--accent)">go install github.com/monoes/mono-agent/cmd/monoes@latest</code></div>
|
|
10591
|
-
</div>`;
|
|
10592
|
-
return;
|
|
10593
|
-
}
|
|
10594
|
-
|
|
10595
|
-
const groups = {};
|
|
10596
|
-
for (const p of _maPlatforms) {
|
|
10597
|
-
const cat = (p.Category || p.category || 'service').toLowerCase();
|
|
10598
|
-
if (!groups[cat]) groups[cat] = [];
|
|
10599
|
-
groups[cat].push(p);
|
|
10600
|
-
}
|
|
10601
|
-
|
|
10602
|
-
const cats = [...MA_CATEGORY_ORDER, ...Object.keys(groups).filter(c => !MA_CATEGORY_ORDER.includes(c))].filter(c => groups[c]);
|
|
10603
|
-
const totalConn = _maPlatforms.filter(p => maResolveConn(p.ID || p.id)).length;
|
|
10604
|
-
|
|
10605
|
-
let html = `<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:16px">
|
|
10606
|
-
<span style="font-size:11px;color:var(--text-xs)">${totalConn} / ${_maPlatforms.length} connected</span>
|
|
10607
|
-
<button class="ma-btn ma-btn-ghost" style="font-size:10px;padding:3px 10px" onclick="loadPlatformSessions()">↺ Refresh</button>
|
|
10608
|
-
</div>`;
|
|
10609
|
-
|
|
10610
|
-
for (const cat of cats) {
|
|
10611
|
-
const list = groups[cat];
|
|
10612
|
-
const catConn = list.filter(p => maResolveConn(p.ID || p.id)).length;
|
|
10613
|
-
html += `<div class="ma-cat-hdr">
|
|
10614
|
-
<span class="ma-cat-label">${MA_CATEGORY_LABELS[cat] || cat}</span>
|
|
10615
|
-
<div class="ma-cat-line"></div>
|
|
10616
|
-
<span class="ma-cat-count">${catConn}/${list.length}</span>
|
|
10617
|
-
</div><div class="ma-tiles">`;
|
|
10618
|
-
for (const p of list) {
|
|
10619
|
-
const pid = p.ID || p.id;
|
|
10620
|
-
const conn = maResolveConn(pid);
|
|
10621
|
-
const connected = conn && conn.status === 'active';
|
|
10622
|
-
const expired = conn && conn.status !== 'active';
|
|
10623
|
-
const cls = connected ? 'connected' : expired ? 'expired' : '';
|
|
10624
|
-
html += `<div class="ma-tile ${cls}" data-pid="${esc(pid)}" onclick="maOpenModal(this.dataset.pid)">
|
|
10625
|
-
<span class="ma-tile-icon">${esc(p.IconEmoji || p.iconEmoji || '🔌')}</span>
|
|
10626
|
-
<span class="ma-tile-name">${esc(p.Name || p.name || pid)}</span>
|
|
10627
|
-
<div style="display:flex;align-items:center;gap:4px">
|
|
10628
|
-
<span class="ma-tile-dot"></span>
|
|
10629
|
-
${conn ? `<span class="ma-tile-acct">${esc(conn.account)}</span>` : ''}
|
|
10630
|
-
</div>
|
|
10631
|
-
</div>`;
|
|
10632
|
-
}
|
|
10633
|
-
html += `</div>`;
|
|
10634
|
-
}
|
|
10635
|
-
|
|
10636
|
-
grid.innerHTML = html;
|
|
10637
|
-
}
|
|
10638
|
-
|
|
10639
|
-
function maOpenModal(pid) {
|
|
10640
|
-
const p = _maPlatforms.find(x => (x.ID || x.id) === pid);
|
|
10641
|
-
if (!p) return;
|
|
10642
|
-
_maSelected = p;
|
|
10643
|
-
maRenderModal(p, maResolveConn(pid), []);
|
|
10644
|
-
}
|
|
10645
|
-
|
|
10646
|
-
function maRenderModal(p, conn, steps, selMethod, fields, state) {
|
|
10647
|
-
const pid = p.ID || p.id;
|
|
10648
|
-
const name = p.Name || p.name || pid;
|
|
10649
|
-
const emoji = p.IconEmoji || p.iconEmoji || '🔌';
|
|
10650
|
-
const methods = (p.Methods || p.methods || []);
|
|
10651
|
-
const method = selMethod || methods[0] || '';
|
|
10652
|
-
const isBrowser = method === 'browser';
|
|
10653
|
-
const connected = conn && conn.status === 'active';
|
|
10654
|
-
const expired = conn && conn.status !== 'active' && conn;
|
|
10655
|
-
state = state || {};
|
|
10656
|
-
|
|
10657
|
-
const existing = document.getElementById('ma-modal-overlay');
|
|
10658
|
-
if (existing) existing.remove();
|
|
10659
|
-
|
|
10660
|
-
const overlay = document.createElement('div');
|
|
10661
|
-
overlay.id = 'ma-modal-overlay';
|
|
10662
|
-
overlay.className = 'ma-modal-bg';
|
|
10663
|
-
overlay.addEventListener('click', e => { if (e.target === overlay) maCloseModal(); });
|
|
10664
|
-
|
|
10665
|
-
let bodyHtml = '';
|
|
10666
|
-
|
|
10667
|
-
if (conn) {
|
|
10668
|
-
// Connected state
|
|
10669
|
-
const statusColor = connected ? 'var(--green)' : '#fbbf24';
|
|
10670
|
-
bodyHtml = `
|
|
10671
|
-
<div class="ma-modal-info">
|
|
10672
|
-
${[['Account', conn.account], ['Method', conn.method], ['Status', conn.status]].map(([l,v]) =>
|
|
10673
|
-
`<div class="ma-modal-row"><span class="ma-modal-lbl">${l}</span><span class="ma-modal-val ${v==='active'?'ok':v==='expired'?'warn':''}">${esc(v)}</span></div>`
|
|
10674
|
-
).join('')}
|
|
10675
|
-
${conn.lastTested ? `<div class="ma-modal-row"><span class="ma-modal-lbl">Last tested</span><span class="ma-modal-val">${esc(relativeTime(new Date(conn.lastTested).getTime()))}</span></div>` : ''}
|
|
10676
|
-
</div>
|
|
10677
|
-
${state.testMsg ? `<div class="ma-msg ${state.testOk ? 'ok' : 'err'}">${esc(state.testMsg)}</div>` : ''}
|
|
10678
|
-
${state.err ? `<div class="ma-msg err">${esc(state.err)}</div>` : ''}
|
|
10679
|
-
${steps.length ? `<div class="ma-steps">${steps.map(s=>`<div class="ma-step"><span class="ma-step-dot ${s.kind}"></span><span class="ma-step-txt ${s.kind}">${esc(s.msg)}</span></div>`).join('')}</div>` : ''}
|
|
10680
|
-
<div class="ma-btns">
|
|
10681
|
-
<button class="ma-btn ma-btn-secondary" id="ma-test-btn" onclick="maTest('${pid}','${conn.id}')">Test</button>
|
|
10682
|
-
${expired ? `<button class="ma-btn ma-btn-primary" onclick="maStartLogin('${pid}')">Log in again</button>` : ''}
|
|
10683
|
-
${state.confirmDisc ? `
|
|
10684
|
-
<button class="ma-btn ma-btn-danger" onclick="maDisconnect('${pid}','${conn.id}','${conn._type}')">Yes, disconnect</button>
|
|
10685
|
-
<button class="ma-btn ma-btn-ghost" data-pid="${esc(pid)}" onclick="maOpenModal(this.dataset.pid)">Cancel</button>
|
|
10686
|
-
` : `
|
|
10687
|
-
<button class="ma-btn ma-btn-danger" onclick="maConfirmDisconnect('${pid}')">Disconnect</button>
|
|
10688
|
-
<button class="ma-btn ma-btn-ghost" onclick="maCloseModal()">Close</button>
|
|
10689
|
-
`}
|
|
10690
|
-
</div>`;
|
|
10691
|
-
} else {
|
|
10692
|
-
// Not connected — show method tabs + connect form
|
|
10693
|
-
const methodTabsHtml = methods.length > 1 ? `<div class="ma-method-tabs">${methods.map(m =>
|
|
10694
|
-
`<button class="ma-method-tab ${m===method?'active':''}" data-pid="${esc(p.ID||p.id)}" data-method="${esc(m)}" onclick="maRenderModal(_maPlatforms.find(x=>(x.ID||x.id)===this.dataset.pid),null,[],this.dataset.method,{},{})">${esc(m)}</button>`
|
|
10695
|
-
).join('')}</div>` : '';
|
|
10696
|
-
|
|
10697
|
-
let formHtml = '';
|
|
10698
|
-
if (isBrowser) {
|
|
10699
|
-
formHtml = `<div class="ma-browser-hint">A browser window will open. Log in to ${esc(name)} and the session will be saved automatically.</div>
|
|
10700
|
-
${steps.length ? `<div class="ma-steps">${steps.map(s=>`<div class="ma-step"><span class="ma-step-dot ${s.kind}"></span><span class="ma-step-txt ${s.kind}">${esc(s.msg)}</span></div>`).join('')}</div>` : ''}
|
|
10701
|
-
${state.running ? `<div style="display:flex;align-items:center;gap:8px;font-size:11px;color:var(--text-xs)"><span class="spinner" style="width:10px;height:10px;border-width:2px"></span>Waiting for login…</div>` : ''}
|
|
10702
|
-
${state.err ? `<div class="ma-msg err">${esc(state.err)}</div>` : ''}`;
|
|
10703
|
-
} else {
|
|
10704
|
-
const fieldDefs = (p.Fields || p.fields || {})[method] || [];
|
|
10705
|
-
formHtml = fieldDefs.map(f => {
|
|
10706
|
-
const key = f.Key || f.key;
|
|
10707
|
-
const label = f.Label || f.label || key;
|
|
10708
|
-
const secret = f.Secret || f.secret;
|
|
10709
|
-
const req = f.Required || f.required;
|
|
10710
|
-
const val = (fields || {})[key] || '';
|
|
10711
|
-
return `<div>
|
|
10712
|
-
<div class="ma-field-label">${esc(label)}${req ? ' <span style="color:oklch(60% 0.18 25)">*</span>' : ''}</div>
|
|
10713
|
-
<input class="ma-field-input" type="${secret ? 'password' : 'text'}" data-key="${esc(key)}" value="${esc(val)}" autocomplete="${secret ? 'new-password' : 'off'}" placeholder="${esc(label)}">
|
|
10714
|
-
${f.HelpURL || f.helpURL ? `<a href="#" style="font-size:9.5px;color:var(--accent);text-decoration:none" onclick="event.preventDefault()">How to get this ↗</a>` : ''}
|
|
10715
|
-
</div>`;
|
|
10716
|
-
}).join('') || `<div class="ma-browser-hint">No configuration required.</div>`;
|
|
10717
|
-
formHtml += `${state.err ? `<div class="ma-msg err">${esc(state.err)}</div>` : ''}
|
|
10718
|
-
${state.ok ? `<div class="ma-msg ok">Connected!</div>` : ''}`;
|
|
10719
|
-
}
|
|
10720
|
-
|
|
10721
|
-
const connectBtn = isBrowser
|
|
10722
|
-
? `<button class="ma-btn ma-btn-primary" id="ma-conn-btn" onclick="maStartLogin('${pid}')">Connect ${esc(name)}</button>`
|
|
10723
|
-
: `<button class="ma-btn ma-btn-primary" id="ma-conn-btn" data-pid="${esc(pid)}" data-method="${esc(method)}" onclick="maConnectApiKey(this.dataset.pid,this.dataset.method)">Connect</button>`;
|
|
10724
|
-
|
|
10725
|
-
bodyHtml = `
|
|
10726
|
-
${methodTabsHtml}
|
|
10727
|
-
${formHtml}
|
|
10728
|
-
<div class="ma-btns">
|
|
10729
|
-
${state.ok ? '' : connectBtn}
|
|
10730
|
-
<button class="ma-btn ma-btn-ghost" onclick="loadPlatformSessions();maCloseModal()">Refresh</button>
|
|
10731
|
-
<button class="ma-btn ma-btn-ghost" onclick="maCloseModal()">Close</button>
|
|
10732
|
-
</div>`;
|
|
10733
|
-
}
|
|
10734
|
-
|
|
10735
|
-
overlay.innerHTML = `<div class="ma-modal">
|
|
10736
|
-
<div class="ma-modal-hdr">
|
|
10737
|
-
<div class="ma-modal-hdr-left">
|
|
10738
|
-
<span style="font-size:26px">${esc(emoji)}</span>
|
|
10739
|
-
<div>
|
|
10740
|
-
<div class="ma-modal-title">${esc(name)}</div>
|
|
10741
|
-
<div style="display:flex;align-items:center;gap:5px;margin-top:2px">
|
|
10742
|
-
<span style="width:6px;height:6px;border-radius:50%;background:${connected?'var(--green)':expired?'#fbbf24':'var(--border)'}"></span>
|
|
10743
|
-
<span class="ma-modal-status" style="color:${connected?'var(--green)':expired?'#fbbf24':'var(--text-xs)'}">${connected?'Connected':expired?'Session expired':'Not connected'}</span>
|
|
10744
|
-
</div>
|
|
10745
|
-
</div>
|
|
10746
|
-
</div>
|
|
10747
|
-
<button class="ma-btn ma-btn-ghost" style="padding:4px 8px" onclick="maCloseModal()">✕</button>
|
|
10748
|
-
</div>
|
|
10749
|
-
<div class="ma-modal-body">${bodyHtml}</div>
|
|
10750
|
-
</div>`;
|
|
10751
|
-
|
|
10752
|
-
document.body.appendChild(overlay);
|
|
10753
|
-
}
|
|
10754
|
-
|
|
10755
|
-
function maCloseModal() {
|
|
10756
|
-
if (_maLoginPoll) { clearInterval(_maLoginPoll); _maLoginPoll = null; }
|
|
10757
|
-
const el = document.getElementById('ma-modal-overlay');
|
|
10758
|
-
if (el) el.remove();
|
|
10759
|
-
_maSelected = null;
|
|
10760
|
-
}
|
|
10761
|
-
|
|
10762
|
-
function maConfirmDisconnect(pid) {
|
|
10763
|
-
const p = _maPlatforms.find(x => (x.ID || x.id) === pid);
|
|
10764
|
-
const conn = maResolveConn(pid);
|
|
10765
|
-
if (p && conn) maRenderModal(p, conn, [], null, {}, { confirmDisc: true });
|
|
10766
|
-
}
|
|
10767
|
-
|
|
10768
|
-
async function maStartLogin(pid) {
|
|
10769
|
-
const p = _maPlatforms.find(x => (x.ID || x.id) === pid);
|
|
10770
|
-
if (!p) return;
|
|
10771
|
-
const steps = [{ msg: 'Opening browser…', kind: '' }];
|
|
10772
|
-
maRenderModal(p, null, steps, 'browser', {}, { running: true });
|
|
10773
|
-
try {
|
|
10774
|
-
const r = await fetch('/api/monoagent/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: pid }) });
|
|
10775
|
-
if (!r.ok) throw new Error('HTTP ' + r.status);
|
|
10776
|
-
steps.push({ msg: 'Browser open — log in then come back', kind: 'ok' });
|
|
10777
|
-
maRenderModal(p, null, steps, 'browser', {}, { running: false });
|
|
10778
|
-
showToast('Browser opened', 'Log in to ' + (p.Name || pid) + ' and hit Refresh', 'ok');
|
|
10779
|
-
// Poll for completion
|
|
10780
|
-
let polls = 0;
|
|
10781
|
-
if (_maLoginPoll) clearInterval(_maLoginPoll);
|
|
10782
|
-
_maLoginPoll = setInterval(async () => {
|
|
10783
|
-
polls++;
|
|
10784
|
-
const d = await fetch('/api/monoagent/connections').then(r => r.json()).catch(() => null);
|
|
10785
|
-
if (d) { _maConns = d.connections || []; _maSessions = d.sessions || []; }
|
|
10786
|
-
const conn = maResolveConn(pid);
|
|
10787
|
-
if (conn || polls >= 18) {
|
|
10788
|
-
clearInterval(_maLoginPoll); _maLoginPoll = null;
|
|
10789
|
-
if (conn) { renderSystems(); maOpenModal(pid); showToast('Connected', (p.Name||pid) + ' session saved', 'ok'); }
|
|
10790
|
-
else { maRenderModal(p, null, [], 'browser', {}, { err: 'Login timed out. Close the browser tab and try again.' }); }
|
|
10791
|
-
}
|
|
10792
|
-
}, 5000);
|
|
10793
|
-
} catch (e) {
|
|
10794
|
-
maRenderModal(p, null, [], 'browser', {}, { err: e.message });
|
|
10795
|
-
}
|
|
10796
|
-
}
|
|
10797
|
-
|
|
10798
|
-
async function maConnectApiKey(pid, method) {
|
|
10799
|
-
const p = _maPlatforms.find(x => (x.ID || x.id) === pid);
|
|
10800
|
-
if (!p) return;
|
|
10801
|
-
const modal = document.getElementById('ma-modal-overlay');
|
|
10802
|
-
const inputs = modal ? modal.querySelectorAll('.ma-field-input') : [];
|
|
10803
|
-
const fields = {};
|
|
10804
|
-
inputs.forEach(inp => { fields[inp.dataset.key] = inp.value.trim(); });
|
|
10805
|
-
const btn = document.getElementById('ma-conn-btn');
|
|
10806
|
-
if (btn) { btn.disabled = true; btn.textContent = 'Connecting…'; }
|
|
10807
|
-
try {
|
|
10808
|
-
const r = await fetch('/api/monoagent/connect', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: pid, method, fields }) });
|
|
10809
|
-
const d = await r.json();
|
|
10810
|
-
if (d.ok) {
|
|
10811
|
-
await loadPlatformSessions();
|
|
10812
|
-
maRenderModal(p, maResolveConn(pid), [], method, fields, { ok: true });
|
|
10813
|
-
showToast('Connected', (p.Name || pid) + ' connected', 'ok');
|
|
10814
|
-
} else {
|
|
10815
|
-
maRenderModal(p, null, [], method, fields, { err: d.stderr || d.error || 'Connection failed' });
|
|
10816
|
-
}
|
|
10817
|
-
} catch (e) {
|
|
10818
|
-
maRenderModal(p, null, [], method, fields, { err: e.message });
|
|
10819
|
-
}
|
|
10820
|
-
}
|
|
10821
|
-
|
|
10822
|
-
async function maTest(pid, connId) {
|
|
10823
|
-
const p = _maPlatforms.find(x => (x.ID || x.id) === pid);
|
|
10824
|
-
const conn = maResolveConn(pid);
|
|
10825
|
-
if (!p || !conn) return;
|
|
10826
|
-
const btn = document.getElementById('ma-test-btn');
|
|
10827
|
-
if (btn) { btn.disabled = true; btn.textContent = 'Testing…'; }
|
|
10828
|
-
try {
|
|
10829
|
-
const d = await fetch('/api/monoagent/test', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: connId }) }).then(r => r.json());
|
|
10830
|
-
maRenderModal(p, conn, [], null, {}, { testOk: d.ok, testMsg: d.ok ? '✓ Connection OK' : ('✗ ' + (d.error || 'Test failed')) });
|
|
10831
|
-
} catch (e) {
|
|
10832
|
-
maRenderModal(p, conn, [], null, {}, { testMsg: '✗ ' + e.message, testOk: false });
|
|
10833
|
-
}
|
|
10834
|
-
}
|
|
10835
|
-
|
|
10836
|
-
async function maDisconnect(pid, connId, type) {
|
|
10837
|
-
const p = _maPlatforms.find(x => (x.ID || x.id) === pid);
|
|
10838
|
-
if (!p) return;
|
|
10839
|
-
try {
|
|
10840
|
-
await fetch('/api/monoagent/disconnect', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: connId, type }) });
|
|
10841
|
-
await loadPlatformSessions();
|
|
10842
|
-
maCloseModal();
|
|
10843
|
-
showToast('Disconnected', (p.Name || pid) + ' removed', 'ok');
|
|
10844
|
-
} catch (e) {
|
|
10845
|
-
showToast('Error', e.message, 'err');
|
|
10846
|
-
}
|
|
10847
|
-
}
|
|
10848
|
-
|
|
10849
10872
|
let _wfRuns = [];
|
|
10850
10873
|
async function loadWorkflowRuns() {
|
|
10851
10874
|
const runsListEl = document.getElementById('wf-runs-list');
|
|
@@ -11347,7 +11370,7 @@ function pbRenderPalette() {
|
|
|
11347
11370
|
<span class="pb-cat-arrow">▸</span><span style="display:inline-block;width:8px;height:8px;border-radius:50%;background:${catColor};margin-right:5px;vertical-align:middle;flex-shrink:0"></span>${cat}
|
|
11348
11371
|
</div>
|
|
11349
11372
|
<div class="pb-cat-body ${cat === cats[0] ? '' : 'collapsed'}">
|
|
11350
|
-
${nodes.map((n,i) => `<button class="pb-node-btn" onclick="pbAddNode('${esc(n.type)}',PB_NODE_DEFS.filter(d=>d.cat==='${cat}')[${i}])" title="${esc(n.type)}${n.defaultConfig ? ' (op: '+n.defaultConfig.operation+')' : ''}">
|
|
11373
|
+
${nodes.map((n,i) => `<button class="pb-node-btn" onclick="pbAddNode('${esc(String(n.type).replace(/['\\]/g,'\\$&'))}',PB_NODE_DEFS.filter(d=>d.cat==='${cat}')[${i}])" title="${esc(n.type)}${n.defaultConfig ? ' (op: '+n.defaultConfig.operation+')' : ''}">
|
|
11351
11374
|
<span class="pb-node-icon">${n.icon}</span>${esc(n.label)}
|
|
11352
11375
|
</button>`).join('')}
|
|
11353
11376
|
</div>
|
|
@@ -11424,13 +11447,13 @@ function pbRenderCanvas() {
|
|
|
11424
11447
|
<div class="pb-node-hdr">
|
|
11425
11448
|
<span class="pb-node-type-icon">${def.icon}</span>
|
|
11426
11449
|
<span class="pb-node-title" title="${esc(node.type)}">${esc(node.name || node.type)}</span>
|
|
11427
|
-
<button class="pb-node-del" onclick="pbDeleteNode('${esc(node.id)}')" title="Remove">✕</button>
|
|
11450
|
+
<button class="pb-node-del" onclick="pbDeleteNode('${esc(String(node.id).replace(/['\\]/g,'\\$&'))}')" title="Remove">✕</button>
|
|
11428
11451
|
</div>
|
|
11429
11452
|
<div class="pb-node-body">
|
|
11430
11453
|
${st ? `<div class="pb-node-status"><div class="pb-ns-dot ${st}"></div><span style="color:${st==='running'?'var(--accent)':st==='completed'?'var(--green)':'var(--red)'}">${st}</span></div>` : ''}
|
|
11431
11454
|
<div style="font-size:10px;color:var(--text-xs)">${esc(node.type)}</div>
|
|
11432
11455
|
</div>
|
|
11433
|
-
<button class="pb-node-conn-btn" title="Connect to another node" onclick="pbStartConnect('${esc(node.id)}',event)">→</button>
|
|
11456
|
+
<button class="pb-node-conn-btn" title="Connect to another node" onclick="pbStartConnect('${esc(String(node.id).replace(/['\\]/g,'\\$&'))}',event)">→</button>
|
|
11434
11457
|
`;
|
|
11435
11458
|
card.addEventListener('mousedown', (e) => pbDragStart(node.id, e));
|
|
11436
11459
|
card.addEventListener('click', (e) => { if (!e.defaultPrevented) pbSelectNode(node.id); });
|
|
@@ -12074,12 +12097,12 @@ function mgRenderAnalyzeButtons() {
|
|
|
12074
12097
|
const cEl = document.getElementById('mg-client-btns');
|
|
12075
12098
|
const sEl = document.getElementById('mg-server-btns');
|
|
12076
12099
|
cEl.innerHTML = MG_CLIENT_ANALYSES.map(a => `
|
|
12077
|
-
<div class="mg-analyze-btn" id="mg-abtn-${esc(a.id)}" onclick="mgRunClient('${esc(a.id)}')">
|
|
12100
|
+
<div class="mg-analyze-btn" id="mg-abtn-${esc(a.id)}" onclick="mgRunClient('${esc(String(a.id).replace(/['\\]/g,'\\$&'))}')">
|
|
12078
12101
|
<div class="mab-name">${esc(a.label)}</div>
|
|
12079
12102
|
<div class="mab-desc">${esc(a.desc)}</div>
|
|
12080
12103
|
</div>`).join('');
|
|
12081
12104
|
sEl.innerHTML = MG_SERVER_ANALYSES.map(a => `
|
|
12082
|
-
<div class="mg-analyze-btn mg-server" id="mg-abtn-${esc(a.id)}" onclick="mgRunServer('${esc(a.id)}')">
|
|
12105
|
+
<div class="mg-analyze-btn mg-server" id="mg-abtn-${esc(a.id)}" onclick="mgRunServer('${esc(String(a.id).replace(/['\\]/g,'\\$&'))}')">
|
|
12083
12106
|
<div class="mab-name mg">${esc(a.label)}</div>
|
|
12084
12107
|
<div class="mab-desc">${esc(a.desc)}</div>
|
|
12085
12108
|
</div>`).join('');
|
|
@@ -12770,7 +12793,7 @@ function mgRenderExport() {
|
|
|
12770
12793
|
<div class="mg-export-card">
|
|
12771
12794
|
<div class="mec-name">${esc(x.name)}</div>
|
|
12772
12795
|
<div class="mec-desc">${esc(x.desc)}</div>
|
|
12773
|
-
<button class="btn" onclick="mgExport('${esc(x.id)}')" style="margin-top:auto">EXPORT</button>
|
|
12796
|
+
<button class="btn" onclick="mgExport('${esc(String(x.id).replace(/['\\]/g,'\\$&'))}')" style="margin-top:auto">EXPORT</button>
|
|
12774
12797
|
</div>`).join('');
|
|
12775
12798
|
}
|
|
12776
12799
|
|
|
@@ -13047,7 +13070,7 @@ function mgRenderWiki() {
|
|
|
13047
13070
|
const types = [...new Set(nodes.map(n => n.type || n.kind || 'unknown'))].sort();
|
|
13048
13071
|
const pillsEl = document.getElementById('mg-wiki-pills');
|
|
13049
13072
|
pillsEl.innerHTML = `<span class="mg-pill active" data-type="all" onclick="mgWikiFilterType('all')">All</span>` +
|
|
13050
|
-
types.map(t => `<span class="mg-pill" data-type="${esc(t)}" onclick="mgWikiFilterType('${esc(t)}')">${esc(t)}</span>`).join('');
|
|
13073
|
+
types.map(t => `<span class="mg-pill" data-type="${esc(t)}" onclick="mgWikiFilterType('${esc(String(t).replace(/['\\]/g,'\\$&'))}')">${esc(t)}</span>`).join('');
|
|
13051
13074
|
|
|
13052
13075
|
mgRenderWikiList('');
|
|
13053
13076
|
}
|
|
@@ -14053,64 +14076,6 @@ async function mmRenderGraph(body) {
|
|
|
14053
14076
|
} catch (e) { body.innerHTML = '<div class="empty">Failed: ' + esc(e.message) + '</div>'; }
|
|
14054
14077
|
}
|
|
14055
14078
|
|
|
14056
|
-
/* ═══════════════════════════════════════════════════════════════
|
|
14057
|
-
MONOAGENT TAB
|
|
14058
|
-
═══════════════════════════════════════════════════════════════ */
|
|
14059
|
-
const MA_STATE_COLOR = { completed:'#22c55e', failed:'#ef4444', running:'#3b82f6', pending:'#a3a3a3' };
|
|
14060
|
-
let _maWorkflows = [], _maActions = [];
|
|
14061
|
-
|
|
14062
|
-
function maSwitchTab(tab) {
|
|
14063
|
-
document.querySelectorAll('[data-matab]').forEach(b => b.classList.toggle('active', b.dataset.matab === tab));
|
|
14064
|
-
document.querySelectorAll('#view-monoagent .wf-pane').forEach(p => p.classList.toggle('active', p.id === 'ma-tab-' + tab));
|
|
14065
|
-
}
|
|
14066
|
-
|
|
14067
|
-
async function loadMonoagentData() {
|
|
14068
|
-
document.getElementById('ma-workflows-list').innerHTML = '<div class="loading-txt">Loading…</div>';
|
|
14069
|
-
document.getElementById('ma-history-list').innerHTML = '<div class="loading-txt">Loading…</div>';
|
|
14070
|
-
try {
|
|
14071
|
-
const data = await apiFetch('/api/monoagent/data');
|
|
14072
|
-
_maWorkflows = Array.isArray(data.workflows) ? data.workflows : [];
|
|
14073
|
-
_maActions = Array.isArray(data.actions) ? data.actions : [];
|
|
14074
|
-
} catch (e) {
|
|
14075
|
-
_maWorkflows = []; _maActions = [];
|
|
14076
|
-
}
|
|
14077
|
-
const bdg = document.getElementById('bdg-monoagent');
|
|
14078
|
-
if (bdg) bdg.textContent = _maWorkflows.length || '—';
|
|
14079
|
-
renderMonoagentWorkflows();
|
|
14080
|
-
renderMonoagentHistory();
|
|
14081
|
-
}
|
|
14082
|
-
|
|
14083
|
-
function renderMonoagentWorkflows() {
|
|
14084
|
-
const el = document.getElementById('ma-workflows-list');
|
|
14085
|
-
if (!el) return;
|
|
14086
|
-
if (!_maWorkflows.length) { el.innerHTML = '<div class="empty">No monoagent workflows found. Create one with: <code>monoagent workflow create</code></div>'; return; }
|
|
14087
|
-
el.innerHTML = _maWorkflows.map(w => `<div class="m-card" style="margin-bottom:10px;padding:12px 14px">
|
|
14088
|
-
<div style="display:flex;justify-content:space-between;align-items:center">
|
|
14089
|
-
<span style="font-weight:600;color:var(--text-hi);font-family:var(--mono);font-size:13px">${esc(w.name||w.id||'—')}</span>
|
|
14090
|
-
<span style="font-size:11px;color:var(--text-lo)">${esc(w.id||'')}</span>
|
|
14091
|
-
</div>
|
|
14092
|
-
${w.description ? `<div style="font-size:12px;color:var(--text-lo);margin-top:4px">${esc(w.description)}</div>` : ''}
|
|
14093
|
-
<div style="font-size:11px;color:var(--text-xs);margin-top:6px">${(w.nodes||[]).length} nodes · trigger: ${esc(w.trigger||'manual')}</div>
|
|
14094
|
-
</div>`).join('');
|
|
14095
|
-
}
|
|
14096
|
-
|
|
14097
|
-
function renderMonoagentHistory() {
|
|
14098
|
-
const el = document.getElementById('ma-history-list');
|
|
14099
|
-
if (!el) return;
|
|
14100
|
-
if (!_maActions.length) { el.innerHTML = '<div class="empty">No action history yet.</div>'; return; }
|
|
14101
|
-
el.innerHTML = _maActions.map(a => {
|
|
14102
|
-
const color = MA_STATE_COLOR[a.status] || MA_STATE_COLOR.pending;
|
|
14103
|
-
const ts = a.createdAt ? new Date(a.createdAt).toLocaleString() : '—';
|
|
14104
|
-
return `<div class="m-card" style="margin-bottom:8px;padding:10px 14px;display:flex;justify-content:space-between;align-items:center">
|
|
14105
|
-
<div>
|
|
14106
|
-
<span style="font-weight:600;color:var(--text-hi);font-size:12px;font-family:var(--mono)">${esc(a.type||a.action||'—')}</span>
|
|
14107
|
-
<span style="font-size:11px;color:var(--text-lo);margin-left:8px">${esc(a.platform||'')}</span>
|
|
14108
|
-
<div style="font-size:11px;color:var(--text-xs);margin-top:2px">${ts}</div>
|
|
14109
|
-
</div>
|
|
14110
|
-
<span style="font-size:11px;font-weight:600;color:${color}">${esc(a.status||'—')}</span>
|
|
14111
|
-
</div>`;
|
|
14112
|
-
}).join('');
|
|
14113
|
-
}
|
|
14114
14079
|
</script>
|
|
14115
14080
|
</body>
|
|
14116
14081
|
</html>
|