@monoes/monomindcli 1.6.0 → 1.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/commands/monomind-createtask.md +340 -0
- package/.claude/commands/monomind-do.md +41 -0
- package/.claude/commands/monomind-repeat.md +55 -1
- package/.claude/helpers/hook-handler.cjs +1 -0
- package/.claude/helpers/router.cjs +165 -4
- package/dist/src/mcp-tools/agent-tools.js +12 -1
- package/dist/src/mcp-tools/hive-mind-tools.js +18 -1
- package/dist/src/mcp-tools/hooks-tools.js +11 -1
- package/dist/src/mcp-tools/swarm-tools.js +11 -1
- package/dist/src/ui/collector.mjs +98 -9
- package/dist/src/ui/dashboard.html +1173 -511
- package/dist/src/ui/server.mjs +168 -3
- package/package.json +1 -1
|
@@ -145,18 +145,14 @@
|
|
|
145
145
|
/* Row 0: all projects (full width) */
|
|
146
146
|
#panel-projects { grid-column: 1 / 5; grid-row: 1; min-height: 200px; }
|
|
147
147
|
|
|
148
|
-
/* Row 1:
|
|
149
|
-
#panel-
|
|
150
|
-
#panel-tokens { grid-column: 3 / 5; grid-row: 2; }
|
|
148
|
+
/* Row 1: tokens (full) */
|
|
149
|
+
#panel-tokens { grid-column: 1 / 5; grid-row: 2; }
|
|
151
150
|
|
|
152
|
-
/* Row 2:
|
|
153
|
-
#panel-
|
|
154
|
-
#panel-hooks { grid-column: 3 / 5; grid-row: 3; }
|
|
151
|
+
/* Row 2: loops (full width) */
|
|
152
|
+
#panel-loops { grid-column: 1 / 5; grid-row: 3; }
|
|
155
153
|
|
|
156
|
-
/* Row 3:
|
|
157
|
-
#panel-
|
|
158
|
-
#panel-activity { grid-column: 3; grid-row: 4; }
|
|
159
|
-
#panel-system { grid-column: 4; grid-row: 4; }
|
|
154
|
+
/* Row 3: activity (full width) */
|
|
155
|
+
#panel-activity { grid-column: 1 / 5; grid-row: 4; }
|
|
160
156
|
|
|
161
157
|
/* All grid children must not overflow their columns */
|
|
162
158
|
#grid > * { min-width: 0; }
|
|
@@ -306,11 +302,7 @@
|
|
|
306
302
|
}
|
|
307
303
|
|
|
308
304
|
/* Override display for specific panels that need block */
|
|
309
|
-
#panel-
|
|
310
|
-
#panel-hooks.open .panel-body { display: block; }
|
|
311
|
-
#panel-knowledge.open .panel-body,
|
|
312
|
-
#panel-metrics.open .panel-body,
|
|
313
|
-
#panel-system.open .panel-body { display: block; }
|
|
305
|
+
#panel-knowledge.open .panel-body { display: block; }
|
|
314
306
|
|
|
315
307
|
/* Graphify panel inner layout */
|
|
316
308
|
.graphify-layout { display: grid; grid-template-columns: 220px 1fr; gap: 12px; }
|
|
@@ -525,27 +517,28 @@
|
|
|
525
517
|
}
|
|
526
518
|
|
|
527
519
|
/* ─── TOKENS PANEL ──────────────────────────────────────────────── */
|
|
520
|
+
#panel-tokens.open .panel-body { min-height: 0; padding: 6px 10px; }
|
|
528
521
|
.token-summary {
|
|
529
522
|
display: grid;
|
|
530
523
|
grid-template-columns: 1fr 1fr 1fr 1fr;
|
|
531
|
-
gap:
|
|
524
|
+
gap: 4px;
|
|
532
525
|
}
|
|
533
526
|
.token-stat {
|
|
534
527
|
background: rgba(0, 0, 0, 0.3);
|
|
535
528
|
border: 1px solid var(--border);
|
|
536
529
|
border-radius: 4px;
|
|
537
|
-
padding:
|
|
530
|
+
padding: 4px 6px;
|
|
538
531
|
text-align: center;
|
|
539
532
|
}
|
|
540
533
|
.token-stat-label {
|
|
541
|
-
font-size:
|
|
534
|
+
font-size: 8px;
|
|
542
535
|
color: var(--muted);
|
|
543
536
|
letter-spacing: 0.08em;
|
|
544
537
|
text-transform: uppercase;
|
|
545
|
-
margin-bottom:
|
|
538
|
+
margin-bottom: 2px;
|
|
546
539
|
}
|
|
547
540
|
.token-stat-val {
|
|
548
|
-
font-size:
|
|
541
|
+
font-size: 11px;
|
|
549
542
|
font-weight: 500;
|
|
550
543
|
color: var(--teal);
|
|
551
544
|
}
|
|
@@ -783,6 +776,133 @@
|
|
|
783
776
|
#po-chunk-edit-modal { display: none; position: absolute; inset: 0; background: rgba(0,0,0,0.65); z-index: 22; align-items: center; justify-content: center; }
|
|
784
777
|
#po-chunk-edit-modal.open { display: flex; }
|
|
785
778
|
|
|
779
|
+
/* ─── LOOPS PANEL ──────────────────────────────────────────────── */
|
|
780
|
+
#loops-body { display: flex; flex-direction: column; gap: 8px; }
|
|
781
|
+
.loop-card {
|
|
782
|
+
background: rgba(0,0,0,0.3);
|
|
783
|
+
border: 1px solid var(--border);
|
|
784
|
+
border-radius: 4px;
|
|
785
|
+
padding: 10px 12px;
|
|
786
|
+
display: grid;
|
|
787
|
+
grid-template-columns: auto 1fr auto;
|
|
788
|
+
grid-template-rows: auto auto auto;
|
|
789
|
+
gap: 4px 10px;
|
|
790
|
+
transition: border-color 0.15s;
|
|
791
|
+
position: relative;
|
|
792
|
+
overflow: hidden;
|
|
793
|
+
}
|
|
794
|
+
.loop-card::before {
|
|
795
|
+
content: '';
|
|
796
|
+
position: absolute;
|
|
797
|
+
left: 0; top: 0; bottom: 0;
|
|
798
|
+
width: 3px;
|
|
799
|
+
border-radius: 4px 0 0 4px;
|
|
800
|
+
}
|
|
801
|
+
.loop-card.lc-repeat::before { background: var(--teal); }
|
|
802
|
+
.loop-card.lc-do::before { background: var(--amber); }
|
|
803
|
+
.loop-card.lc-stop-pending { opacity: 0.55; }
|
|
804
|
+
.loop-card:hover { border-color: rgba(255,255,255,0.15); }
|
|
805
|
+
.loop-type-pill {
|
|
806
|
+
grid-row: 1;
|
|
807
|
+
grid-column: 1;
|
|
808
|
+
font-size: 8px;
|
|
809
|
+
font-weight: 700;
|
|
810
|
+
letter-spacing: 0.12em;
|
|
811
|
+
padding: 2px 5px;
|
|
812
|
+
border-radius: 2px;
|
|
813
|
+
align-self: start;
|
|
814
|
+
}
|
|
815
|
+
.loop-type-pill.lp-repeat { background: rgba(0,229,200,0.12); color: var(--teal); border: 1px solid rgba(0,229,200,0.25); }
|
|
816
|
+
.loop-type-pill.lp-do { background: rgba(255,179,0,0.12); color: var(--amber); border: 1px solid rgba(255,179,0,0.25); }
|
|
817
|
+
.loop-prompt {
|
|
818
|
+
grid-row: 1;
|
|
819
|
+
grid-column: 2;
|
|
820
|
+
font-size: 10px;
|
|
821
|
+
color: var(--text);
|
|
822
|
+
font-weight: 600;
|
|
823
|
+
white-space: nowrap;
|
|
824
|
+
overflow: hidden;
|
|
825
|
+
text-overflow: ellipsis;
|
|
826
|
+
align-self: center;
|
|
827
|
+
}
|
|
828
|
+
.loop-stop-btn {
|
|
829
|
+
grid-row: 1 / 4;
|
|
830
|
+
grid-column: 3;
|
|
831
|
+
align-self: center;
|
|
832
|
+
background: rgba(220,60,60,0.08);
|
|
833
|
+
border: 1px solid rgba(220,60,60,0.3);
|
|
834
|
+
color: rgba(220,100,100,0.9);
|
|
835
|
+
font-family: 'Azeret Mono', monospace;
|
|
836
|
+
font-size: 8px;
|
|
837
|
+
font-weight: 700;
|
|
838
|
+
letter-spacing: 0.1em;
|
|
839
|
+
padding: 5px 8px;
|
|
840
|
+
cursor: pointer;
|
|
841
|
+
border-radius: 3px;
|
|
842
|
+
transition: background 0.15s, border-color 0.15s, color 0.15s;
|
|
843
|
+
white-space: nowrap;
|
|
844
|
+
}
|
|
845
|
+
.loop-stop-btn:hover { background: rgba(220,60,60,0.2); border-color: rgba(220,80,80,0.6); color: #f88; }
|
|
846
|
+
.loop-stop-btn:disabled { opacity: 0.35; cursor: not-allowed; }
|
|
847
|
+
.loop-meta {
|
|
848
|
+
grid-row: 2;
|
|
849
|
+
grid-column: 1 / 3;
|
|
850
|
+
display: flex;
|
|
851
|
+
gap: 12px;
|
|
852
|
+
align-items: center;
|
|
853
|
+
}
|
|
854
|
+
.loop-meta-item { font-size: 9px; color: var(--muted); }
|
|
855
|
+
.loop-meta-item span { color: var(--dim); }
|
|
856
|
+
.loop-progress-row {
|
|
857
|
+
grid-row: 3;
|
|
858
|
+
grid-column: 1 / 3;
|
|
859
|
+
display: flex;
|
|
860
|
+
align-items: center;
|
|
861
|
+
gap: 8px;
|
|
862
|
+
}
|
|
863
|
+
.loop-progress-bar {
|
|
864
|
+
flex: 1;
|
|
865
|
+
height: 2px;
|
|
866
|
+
background: rgba(255,255,255,0.06);
|
|
867
|
+
border-radius: 1px;
|
|
868
|
+
overflow: hidden;
|
|
869
|
+
}
|
|
870
|
+
.loop-progress-fill {
|
|
871
|
+
height: 100%;
|
|
872
|
+
border-radius: 1px;
|
|
873
|
+
transition: width 0.4s ease;
|
|
874
|
+
}
|
|
875
|
+
.lc-repeat .loop-progress-fill { background: var(--teal); }
|
|
876
|
+
.lc-do .loop-progress-fill { background: var(--amber); }
|
|
877
|
+
.loop-countdown {
|
|
878
|
+
font-size: 9px;
|
|
879
|
+
color: var(--dim);
|
|
880
|
+
white-space: nowrap;
|
|
881
|
+
min-width: 70px;
|
|
882
|
+
text-align: right;
|
|
883
|
+
}
|
|
884
|
+
.loop-task-label {
|
|
885
|
+
grid-row: 2;
|
|
886
|
+
grid-column: 1 / 3;
|
|
887
|
+
font-size: 9px;
|
|
888
|
+
color: var(--muted);
|
|
889
|
+
white-space: nowrap;
|
|
890
|
+
overflow: hidden;
|
|
891
|
+
text-overflow: ellipsis;
|
|
892
|
+
}
|
|
893
|
+
.loop-status-dot {
|
|
894
|
+
display: inline-block;
|
|
895
|
+
width: 6px;
|
|
896
|
+
height: 6px;
|
|
897
|
+
border-radius: 50%;
|
|
898
|
+
margin-right: 4px;
|
|
899
|
+
vertical-align: middle;
|
|
900
|
+
}
|
|
901
|
+
.loop-status-dot.running { background: var(--teal); box-shadow: 0 0 5px var(--teal); animation: pulse 1.6s ease-in-out infinite; }
|
|
902
|
+
.loop-status-dot.waiting { background: var(--amber); box-shadow: 0 0 4px var(--amber); animation: pulse 2.5s ease-in-out infinite; }
|
|
903
|
+
.loop-status-dot.stopped { background: rgba(220,60,60,0.7); }
|
|
904
|
+
.loops-empty { font-size: 10px; color: var(--dim); text-align: center; padding: 18px 0; letter-spacing: 0.08em; }
|
|
905
|
+
|
|
786
906
|
/* ─── METRICS PANEL ─────────────────────────────────────────────── */
|
|
787
907
|
.metric-block {
|
|
788
908
|
background: rgba(0,0,0,0.25);
|
|
@@ -812,30 +932,41 @@
|
|
|
812
932
|
.security-warn { color: var(--amber); }
|
|
813
933
|
.security-fail { color: var(--red); }
|
|
814
934
|
|
|
815
|
-
/* ───
|
|
816
|
-
#
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
935
|
+
/* ─── SESSION JOURNAL ────────────────────────────────────────────── */
|
|
936
|
+
#journal-list { display: flex; flex-direction: column; gap: 1px; }
|
|
937
|
+
.jn-card {
|
|
938
|
+
border-left: 2px solid var(--border);
|
|
939
|
+
padding: 10px 12px;
|
|
940
|
+
cursor: pointer;
|
|
941
|
+
transition: border-color 0.12s, background 0.12s;
|
|
942
|
+
}
|
|
943
|
+
.jn-card:hover { border-left-color: var(--teal); background: rgba(0,229,200,0.03); }
|
|
944
|
+
.jn-card.open { border-left-color: var(--teal); background: rgba(0,229,200,0.04); }
|
|
945
|
+
.jn-header { display: flex; align-items: baseline; gap: 10px; margin-bottom: 4px; }
|
|
946
|
+
.jn-date { font-size: 9px; color: var(--teal); white-space: nowrap; font-family: 'Azeret Mono', monospace; }
|
|
947
|
+
.jn-id { font-size: 8px; color: var(--dim); font-family: 'Azeret Mono', monospace; }
|
|
948
|
+
.jn-stats { font-size: 8px; color: var(--muted); margin-left: auto; white-space: nowrap; }
|
|
949
|
+
.jn-prompt {
|
|
950
|
+
font-size: 10px; color: var(--text); font-style: italic;
|
|
951
|
+
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
|
952
|
+
margin-bottom: 4px;
|
|
820
953
|
}
|
|
821
|
-
.
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
font-size:
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
954
|
+
.jn-excerpt { font-size: 9px; color: var(--muted); line-height: 1.5; }
|
|
955
|
+
.jn-summary-full {
|
|
956
|
+
display: none; margin-top: 8px; padding-top: 8px;
|
|
957
|
+
border-top: 1px solid var(--border);
|
|
958
|
+
font-size: 9px; color: var(--muted); line-height: 1.6;
|
|
959
|
+
white-space: pre-wrap; max-height: 320px; overflow-y: auto;
|
|
960
|
+
}
|
|
961
|
+
.jn-card.open .jn-summary-full { display: block; }
|
|
962
|
+
.jn-no-summary { font-size: 9px; color: var(--dim); font-style: italic; }
|
|
963
|
+
.jn-badge-compact {
|
|
964
|
+
font-size: 7px; letter-spacing: 0.06em;
|
|
965
|
+
background: rgba(0,229,200,0.1); color: var(--teal);
|
|
966
|
+
border: 1px solid rgba(0,229,200,0.2); border-radius: 2px;
|
|
967
|
+
padding: 1px 4px; flex-shrink: 0;
|
|
831
968
|
}
|
|
832
|
-
|
|
833
|
-
.activity-entry.type-swarm { border-left-color: var(--amber); background: rgba(255,183,0,0.03); }
|
|
834
|
-
.activity-entry.type-agent { border-left-color: var(--green); background: rgba(0,229,135,0.03); }
|
|
835
|
-
.activity-entry.type-error { border-left-color: var(--red); background: rgba(255,68,102,0.03); }
|
|
836
|
-
.activity-entry.type-default { border-left-color: var(--muted); }
|
|
837
|
-
.activity-time { color: var(--dim); white-space: nowrap; font-size: 9px; flex-shrink: 0; letter-spacing: 0.03em; }
|
|
838
|
-
.activity-msg { color: var(--text); flex: 1; font-size: 10px; }
|
|
969
|
+
#journal-empty { padding: 20px; font-size: 10px; color: var(--dim); text-align: center; }
|
|
839
970
|
|
|
840
971
|
/* ─── SYSTEM PANEL ──────────────────────────────────────────────── */
|
|
841
972
|
.sys-row {
|
|
@@ -1160,15 +1291,165 @@
|
|
|
1160
1291
|
.po-sd-sidebar-section { display: flex; flex-direction: column; gap: 4px; }
|
|
1161
1292
|
.po-sd-sidebar-label { font-size: 9px; color: var(--muted); letter-spacing: 0.1em; margin-bottom: 2px; }
|
|
1162
1293
|
|
|
1163
|
-
/*
|
|
1164
|
-
#po-
|
|
1165
|
-
#po-
|
|
1166
|
-
padding: 6px
|
|
1167
|
-
|
|
1168
|
-
|
|
1294
|
+
/* Swarm pane */
|
|
1295
|
+
#po-swarm-tab { flex-direction: row; }
|
|
1296
|
+
#po-swarm-list {
|
|
1297
|
+
padding: 6px;
|
|
1298
|
+
}
|
|
1299
|
+
.po-swarm-item {
|
|
1300
|
+
display: flex; flex-direction: column; gap: 2px;
|
|
1301
|
+
padding: 7px 10px; border-radius: 4px; cursor: pointer; margin-bottom: 3px;
|
|
1302
|
+
border: 1px solid transparent; transition: all 0.12s;
|
|
1303
|
+
}
|
|
1304
|
+
.po-swarm-item:hover { background: rgba(0,229,200,0.04); border-color: var(--border); }
|
|
1305
|
+
.po-swarm-item.selected { background: rgba(0,229,200,0.08); border-color: rgba(0,229,200,0.3); }
|
|
1306
|
+
.po-swarm-item-row { display: flex; align-items: center; gap: 6px; }
|
|
1307
|
+
.po-swarm-item-id { font-size: 10px; font-weight: 600; color: var(--text); font-family: 'Azeret Mono', monospace; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1; }
|
|
1308
|
+
.po-swarm-item-badge { font-size: 8px; padding: 1px 5px; border-radius: 2px; font-family: 'Azeret Mono', monospace; letter-spacing: 0.05em; }
|
|
1309
|
+
.po-swarm-item-badge.topo { background: rgba(0,229,200,0.12); color: var(--teal); }
|
|
1310
|
+
.po-swarm-item-badge.status-completed { background: rgba(76,175,80,0.15); color: #4caf50; }
|
|
1311
|
+
.po-swarm-item-badge.status-error { background: rgba(239,83,80,0.15); color: #EF5350; }
|
|
1312
|
+
.po-swarm-item-badge.status-terminated { background: rgba(255,180,0,0.15); color: #ffb400; }
|
|
1313
|
+
.po-swarm-item-badge.status-stopped { background: rgba(150,150,180,0.15); color: var(--muted); }
|
|
1314
|
+
.po-swarm-item-badge.status-running { background: rgba(76,175,80,0.15); color: #4caf50; }
|
|
1315
|
+
.po-swarm-item-meta { font-size: 9px; color: var(--muted); }
|
|
1316
|
+
#po-swarm-detail {
|
|
1317
|
+
flex: 1; overflow: hidden; display: flex; flex-direction: column;
|
|
1318
|
+
}
|
|
1319
|
+
#po-swarm-header {
|
|
1320
|
+
padding: 10px 16px; border-bottom: 1px solid var(--border); flex-shrink: 0;
|
|
1321
|
+
display: flex; flex-direction: column; gap: 3px;
|
|
1169
1322
|
}
|
|
1170
|
-
#po-
|
|
1171
|
-
|
|
1323
|
+
#po-swarm-stats-bar {
|
|
1324
|
+
display: flex; gap: 12px; font-size: 10px; color: var(--muted); padding: 6px 16px;
|
|
1325
|
+
border-bottom: 1px solid var(--border); flex-shrink: 0;
|
|
1326
|
+
}
|
|
1327
|
+
#po-swarm-stats-bar span { display: flex; align-items: center; gap: 4px; }
|
|
1328
|
+
#po-swarm-body {
|
|
1329
|
+
flex: 1; overflow-y: auto; display: flex; flex-direction: column;
|
|
1330
|
+
}
|
|
1331
|
+
#po-swarm-canvas-wrap {
|
|
1332
|
+
position: relative; min-height: 200px; transition: min-height 0.25s ease;
|
|
1333
|
+
flex-shrink: 0;
|
|
1334
|
+
}
|
|
1335
|
+
#po-swarm-canvas-wrap.shrunk { min-height: 120px; }
|
|
1336
|
+
#po-swarm-topo-label {
|
|
1337
|
+
position: absolute; top: 8px; left: 14px; font-size: 9px;
|
|
1338
|
+
color: var(--muted); letter-spacing: 0.1em; z-index: 2;
|
|
1339
|
+
}
|
|
1340
|
+
#po-swarm-canvas { width: 100%; height: 100%; display: block; }
|
|
1341
|
+
#po-swarm-agents-section { padding: 10px 14px; border-top: 1px solid var(--border); }
|
|
1342
|
+
.po-swarm-agent-item {
|
|
1343
|
+
display: flex; align-items: center; gap: 8px;
|
|
1344
|
+
padding: 5px 8px; border-radius: 3px; cursor: pointer; margin-bottom: 2px;
|
|
1345
|
+
transition: background 0.12s;
|
|
1346
|
+
}
|
|
1347
|
+
.po-swarm-agent-item:hover { background: rgba(0,229,200,0.05); }
|
|
1348
|
+
.po-swarm-agent-item.selected { background: rgba(255,180,0,0.08); }
|
|
1349
|
+
.po-swarm-agent-dot { width: 6px; height: 6px; border-radius: 50%; flex-shrink: 0; }
|
|
1350
|
+
.po-swarm-agent-type { font-size: 10px; font-family: 'Azeret Mono', monospace; color: var(--text); width: 100px; }
|
|
1351
|
+
.po-swarm-agent-role { font-size: 8px; font-family: 'Azeret Mono', monospace; color: var(--muted); }
|
|
1352
|
+
.po-swarm-agent-stats { font-size: 9px; color: var(--muted); font-family: 'Azeret Mono', monospace; margin-left: auto; }
|
|
1353
|
+
#po-swarm-agent-drawer {
|
|
1354
|
+
border-top: 1px solid var(--border); flex-shrink: 0;
|
|
1355
|
+
max-height: 240px; overflow: hidden;
|
|
1356
|
+
transition: max-height 0.25s ease;
|
|
1357
|
+
}
|
|
1358
|
+
#po-swarm-agent-header {
|
|
1359
|
+
display: flex; align-items: center; padding: 8px 14px;
|
|
1360
|
+
border-bottom: 1px solid var(--border); gap: 12px;
|
|
1361
|
+
}
|
|
1362
|
+
#po-swarm-agent-info { flex: 1; font-size: 10px; font-family: 'Azeret Mono', monospace; display: flex; gap: 14px; }
|
|
1363
|
+
#po-swarm-agent-close { background: none; border: none; color: var(--muted); font-size: 14px; cursor: pointer; padding: 0 2px; }
|
|
1364
|
+
#po-swarm-agent-close:hover { color: var(--text); }
|
|
1365
|
+
#po-swarm-agent-timeline {
|
|
1366
|
+
overflow-y: auto; padding: 8px 14px; max-height: 190px;
|
|
1367
|
+
display: flex; flex-direction: column; gap: 3px;
|
|
1368
|
+
}
|
|
1369
|
+
.po-swarm-msg {
|
|
1370
|
+
padding: 3px 8px; font-size: 9px; font-family: 'Azeret Mono', monospace;
|
|
1371
|
+
color: var(--muted); border-radius: 0 3px 3px 0;
|
|
1372
|
+
}
|
|
1373
|
+
.po-swarm-msg.sent { border-left: 2px solid var(--teal); }
|
|
1374
|
+
.po-swarm-msg.received { border-left: 2px solid #4488cc; }
|
|
1375
|
+
.po-swarm-msg-ts { color: rgba(100,100,140,0.6); }
|
|
1376
|
+
.po-swarm-msg-type { color: var(--text); font-weight: 600; }
|
|
1377
|
+
.po-swarm-msg-payload { color: rgba(180,180,220,0.7); }
|
|
1378
|
+
.po-swarm-event-row {
|
|
1379
|
+
display: flex; align-items: baseline; gap: 6px;
|
|
1380
|
+
padding: 2px 0; font-size: 9px; font-family: 'Azeret Mono', monospace;
|
|
1381
|
+
border-bottom: 1px solid rgba(58,58,90,0.1);
|
|
1382
|
+
}
|
|
1383
|
+
.po-swarm-event-ts { color: rgba(100,100,140,0.6); min-width: 60px; }
|
|
1384
|
+
.po-swarm-event-kind { color: var(--teal); font-weight: 600; min-width: 130px; }
|
|
1385
|
+
.po-swarm-event-detail { color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
1386
|
+
#po-swarm-clean-btn:hover { background: rgba(239,83,80,0.2); }
|
|
1387
|
+
|
|
1388
|
+
/* Agent graph pane */
|
|
1389
|
+
#po-ag-sidebar {
|
|
1390
|
+
width: 190px; min-width: 190px; flex-shrink: 0;
|
|
1391
|
+
border-right: 1px solid var(--border);
|
|
1392
|
+
display: flex; flex-direction: column; overflow: hidden;
|
|
1393
|
+
background: rgba(0,0,0,0.12);
|
|
1394
|
+
}
|
|
1395
|
+
#po-ag-sidebar-hdr {
|
|
1396
|
+
padding: 8px 12px; border-bottom: 1px solid var(--border);
|
|
1397
|
+
font-size: 9px; color: var(--muted); letter-spacing: 0.1em;
|
|
1398
|
+
display: flex; justify-content: space-between; align-items: center;
|
|
1399
|
+
flex-shrink: 0;
|
|
1400
|
+
}
|
|
1401
|
+
#po-ag-session-list { flex: 1; overflow-y: auto; }
|
|
1402
|
+
.po-ag-sess {
|
|
1403
|
+
padding: 8px 12px; cursor: pointer; border-bottom: 1px solid rgba(255,255,255,0.04);
|
|
1404
|
+
transition: background 0.1s;
|
|
1405
|
+
}
|
|
1406
|
+
.po-ag-sess:hover { background: rgba(255,255,255,0.04); }
|
|
1407
|
+
.po-ag-sess.active { background: rgba(0,229,200,0.07); border-left: 2px solid var(--teal); padding-left: 10px; }
|
|
1408
|
+
.po-ag-sess-id { font-size: 10px; color: var(--text); font-family: 'Azeret Mono', monospace; margin-bottom: 3px; }
|
|
1409
|
+
.po-ag-sess-meta { font-size: 9px; color: var(--muted); line-height: 1.5; }
|
|
1410
|
+
.po-ag-sess-meta .hi { color: var(--dim); }
|
|
1411
|
+
.po-ag-sess-spawns { display: inline-flex; align-items: center; gap: 3px; margin-top: 2px; }
|
|
1412
|
+
.po-ag-sess-spawn-dot { width: 5px; height: 5px; border-radius: 50%; background: var(--amber); }
|
|
1413
|
+
#po-ag-main {
|
|
1414
|
+
flex: 1; display: flex; flex-direction: column; overflow: hidden;
|
|
1415
|
+
}
|
|
1416
|
+
#po-ag-summary-bar {
|
|
1417
|
+
padding: 8px 16px; border-bottom: 1px solid var(--border);
|
|
1418
|
+
display: flex; gap: 20px; flex-shrink: 0; flex-wrap: wrap;
|
|
1419
|
+
background: rgba(0,0,0,0.1);
|
|
1420
|
+
}
|
|
1421
|
+
.po-ag-stat { display: flex; flex-direction: column; }
|
|
1422
|
+
.po-ag-stat-lbl { font-size: 8px; color: var(--muted); letter-spacing: 0.08em; }
|
|
1423
|
+
.po-ag-stat-val { font-size: 12px; color: var(--text); font-weight: 700; font-family: 'Azeret Mono', monospace; }
|
|
1424
|
+
#po-ag-content { flex: 1; overflow-y: auto; padding: 14px 16px; display: flex; flex-direction: column; gap: 12px; }
|
|
1425
|
+
.po-ag-section-lbl {
|
|
1426
|
+
font-size: 9px; color: var(--muted); letter-spacing: 0.1em;
|
|
1427
|
+
margin-bottom: 6px; padding-bottom: 4px; border-bottom: 1px solid var(--border);
|
|
1428
|
+
}
|
|
1429
|
+
.po-ag-agent-card {
|
|
1430
|
+
background: rgba(0,0,0,0.25); border: 1px solid var(--border);
|
|
1431
|
+
border-radius: 4px; padding: 10px 12px;
|
|
1432
|
+
display: flex; flex-direction: column; gap: 6px;
|
|
1433
|
+
transition: border-color 0.12s;
|
|
1434
|
+
}
|
|
1435
|
+
.po-ag-agent-card:hover { border-color: rgba(255,179,71,0.3); }
|
|
1436
|
+
.po-ag-agent-top { display: flex; align-items: center; gap: 8px; }
|
|
1437
|
+
.po-ag-agent-name { font-size: 11px; font-weight: 700; color: var(--text); flex: 1; }
|
|
1438
|
+
.po-ag-agent-type { font-size: 8px; color: var(--amber); background: rgba(255,179,71,0.1); border: 1px solid rgba(255,179,71,0.2); border-radius: 2px; padding: 1px 5px; letter-spacing: 0.06em; }
|
|
1439
|
+
.po-ag-spawn-row { display: flex; align-items: center; gap: 8px; }
|
|
1440
|
+
.po-ag-spawn-lbl { font-size: 9px; color: var(--muted); min-width: 60px; }
|
|
1441
|
+
.po-ag-bar-wrap { flex: 1; height: 3px; background: rgba(255,255,255,0.07); border-radius: 2px; overflow: hidden; }
|
|
1442
|
+
.po-ag-bar-fill { height: 100%; border-radius: 2px; background: var(--amber); transition: width 0.4s ease; }
|
|
1443
|
+
.po-ag-spawn-count { font-size: 9px; color: var(--dim); min-width: 40px; text-align: right; }
|
|
1444
|
+
.po-ag-tool-section { margin-top: 4px; }
|
|
1445
|
+
.po-ag-tool-row { display: flex; align-items: center; gap: 8px; margin-bottom: 4px; }
|
|
1446
|
+
.po-ag-tool-name { font-size: 9px; color: var(--teal); min-width: 55px; font-family: 'Azeret Mono', monospace; }
|
|
1447
|
+
.po-ag-tool-bar-wrap { flex: 1; height: 2px; background: rgba(255,255,255,0.06); border-radius: 1px; overflow: hidden; }
|
|
1448
|
+
.po-ag-tool-bar-fill { height: 100%; border-radius: 1px; background: var(--teal); }
|
|
1449
|
+
.po-ag-tool-count { font-size: 9px; color: var(--dim); min-width: 36px; text-align: right; }
|
|
1450
|
+
.po-ag-sess-hdr { margin-bottom: 10px; }
|
|
1451
|
+
.po-ag-sess-title { font-size: 11px; font-weight: 700; color: var(--text); font-family: 'Azeret Mono', monospace; margin-bottom: 4px; }
|
|
1452
|
+
.po-ag-sess-subtitle { font-size: 9px; color: var(--muted); }
|
|
1172
1453
|
|
|
1173
1454
|
/* Knowledge graph pane */
|
|
1174
1455
|
#po-knowledge-tab { flex-direction: row; }
|
|
@@ -1266,6 +1547,7 @@
|
|
|
1266
1547
|
<button class="po-tab active" onclick="switchPalaceTab('drawers')">MEMORIES</button>
|
|
1267
1548
|
<button class="po-tab" onclick="switchPalaceTab('sessions')">SESSIONS</button>
|
|
1268
1549
|
<button class="po-tab" onclick="switchPalaceTab('chunks')">KNOWLEDGE</button>
|
|
1550
|
+
<button class="po-tab" onclick="switchPalaceTab('routing')">ROUTING</button>
|
|
1269
1551
|
<button class="po-tab" onclick="switchPalaceTab('swarm')">SWARM</button>
|
|
1270
1552
|
<button class="po-tab" onclick="switchPalaceTab('graph')">AGENT GRAPH</button>
|
|
1271
1553
|
<button class="po-tab" onclick="switchPalaceTab('knowledge')">CODE GRAPH</button>
|
|
@@ -1318,23 +1600,73 @@
|
|
|
1318
1600
|
</div>
|
|
1319
1601
|
</div>
|
|
1320
1602
|
</div>
|
|
1321
|
-
<div id="po-graph-tab" class="po-tab-pane">
|
|
1322
|
-
<div id="po-
|
|
1323
|
-
<
|
|
1324
|
-
|
|
1603
|
+
<div id="po-graph-tab" class="po-tab-pane" style="flex-direction:row;overflow:hidden;">
|
|
1604
|
+
<div id="po-ag-sidebar">
|
|
1605
|
+
<div id="po-ag-sidebar-hdr">
|
|
1606
|
+
<span>SESSIONS</span>
|
|
1607
|
+
<span id="po-ag-session-count" style="color:var(--dim);font-size:9px;"></span>
|
|
1608
|
+
</div>
|
|
1609
|
+
<div id="po-ag-session-list"></div>
|
|
1610
|
+
</div>
|
|
1611
|
+
<div id="po-ag-main">
|
|
1612
|
+
<div id="po-ag-summary-bar"></div>
|
|
1613
|
+
<div id="po-ag-content">
|
|
1614
|
+
<div class="po-select-hint" id="po-ag-hint">SELECT A SESSION</div>
|
|
1615
|
+
</div>
|
|
1616
|
+
</div>
|
|
1617
|
+
</div>
|
|
1618
|
+
<div id="po-routing-tab" class="po-tab-pane" style="flex-direction:row;overflow:hidden;">
|
|
1619
|
+
<div style="width:240px;min-width:240px;border-right:1px solid var(--border);display:flex;flex-direction:column;overflow:hidden;background:rgba(0,0,0,0.1);">
|
|
1620
|
+
<div style="padding:8px 12px;border-bottom:1px solid var(--border);font-size:9px;color:var(--muted);letter-spacing:0.1em;flex-shrink:0;">LAST ROUTE</div>
|
|
1621
|
+
<div id="po-hooks-left" style="flex:1;overflow-y:auto;padding:10px 12px;"></div>
|
|
1622
|
+
</div>
|
|
1623
|
+
<div style="flex:1;display:flex;flex-direction:column;overflow:hidden;">
|
|
1624
|
+
<div style="padding:8px 12px;border-bottom:1px solid var(--border);font-size:9px;color:var(--muted);letter-spacing:0.1em;flex-shrink:0;">ROUTING HISTORY</div>
|
|
1625
|
+
<div id="po-hooks-right" style="flex:1;overflow-y:auto;padding:10px 12px;"></div>
|
|
1325
1626
|
</div>
|
|
1326
|
-
<canvas id="po-kg-canvas"></canvas>
|
|
1327
1627
|
</div>
|
|
1328
1628
|
<div id="po-swarm-tab" class="po-tab-pane" style="flex-direction:row;overflow:hidden;">
|
|
1329
|
-
<div
|
|
1330
|
-
<div
|
|
1331
|
-
<div id="po-swarm-
|
|
1332
|
-
<
|
|
1629
|
+
<div style="width:220px;min-width:220px;display:flex;flex-direction:column;border-right:1px solid var(--border);">
|
|
1630
|
+
<div id="po-swarm-list" style="flex:1;overflow-y:auto;"></div>
|
|
1631
|
+
<div id="po-swarm-footer" style="padding:6px 8px;border-top:1px solid var(--border);display:flex;align-items:center;gap:6px;">
|
|
1632
|
+
<span id="po-swarm-data-size" style="font-size:8px;color:var(--muted);font-family:'Azeret Mono',monospace;flex:1;"></span>
|
|
1633
|
+
<button id="po-swarm-clean-btn" onclick="cleanSwarmDataUI()" style="font-size:8px;font-family:'Azeret Mono',monospace;background:rgba(239,83,80,0.1);color:#EF5350;border:1px solid rgba(239,83,80,0.3);border-radius:3px;padding:2px 6px;cursor:pointer;">CLEAN</button>
|
|
1333
1634
|
</div>
|
|
1334
1635
|
</div>
|
|
1335
|
-
<div
|
|
1336
|
-
<div id="po-swarm-
|
|
1337
|
-
<
|
|
1636
|
+
<div id="po-swarm-detail">
|
|
1637
|
+
<div id="po-swarm-hint" class="po-select-hint">SELECT A SWARM RUN</div>
|
|
1638
|
+
<div id="po-swarm-header" style="display:none;">
|
|
1639
|
+
<div style="font-size:11px;font-weight:700;color:var(--text);font-family:'Azeret Mono',monospace;" id="po-swarm-title"></div>
|
|
1640
|
+
<div style="font-size:9px;color:var(--muted);" id="po-swarm-subtitle"></div>
|
|
1641
|
+
</div>
|
|
1642
|
+
<div id="po-swarm-stats-bar" style="display:none;">
|
|
1643
|
+
<span id="po-swarm-stat-topo"></span>
|
|
1644
|
+
<span id="po-swarm-stat-consensus"></span>
|
|
1645
|
+
<span id="po-swarm-stat-agents"></span>
|
|
1646
|
+
<span id="po-swarm-stat-status"></span>
|
|
1647
|
+
<span id="po-swarm-stat-duration"></span>
|
|
1648
|
+
</div>
|
|
1649
|
+
<div id="po-swarm-body" style="display:none;">
|
|
1650
|
+
<div id="po-swarm-canvas-wrap">
|
|
1651
|
+
<div id="po-swarm-topo-label"></div>
|
|
1652
|
+
<canvas id="po-swarm-canvas"></canvas>
|
|
1653
|
+
</div>
|
|
1654
|
+
<div id="po-swarm-agents-section">
|
|
1655
|
+
<div class="po-sd-sidebar-label">AGENTS (click to inspect)</div>
|
|
1656
|
+
<div id="po-swarm-agent-list"></div>
|
|
1657
|
+
</div>
|
|
1658
|
+
<div id="po-swarm-agent-drawer" style="display:none;">
|
|
1659
|
+
<div id="po-swarm-agent-header">
|
|
1660
|
+
<div id="po-swarm-agent-info"></div>
|
|
1661
|
+
<button id="po-swarm-agent-close" onclick="closeSwarmAgent()">✕</button>
|
|
1662
|
+
</div>
|
|
1663
|
+
<div id="po-swarm-agent-timeline"></div>
|
|
1664
|
+
</div>
|
|
1665
|
+
<div id="po-swarm-events-section" style="padding:10px 14px;border-top:1px solid var(--border);">
|
|
1666
|
+
<div class="po-sd-sidebar-label">EVENT LOG</div>
|
|
1667
|
+
<div id="po-swarm-events-list" style="max-height:200px;overflow-y:auto;"></div>
|
|
1668
|
+
</div>
|
|
1669
|
+
</div>
|
|
1338
1670
|
</div>
|
|
1339
1671
|
</div>
|
|
1340
1672
|
<div id="po-chunks-tab" class="po-tab-pane" style="flex-direction:column;overflow:hidden;">
|
|
@@ -1415,6 +1747,7 @@
|
|
|
1415
1747
|
<h1>MONOMIND CONTROL</h1>
|
|
1416
1748
|
<div id="header-meta">
|
|
1417
1749
|
<span><span id="conn-dot"></span><span id="conn-label">CONNECTING</span></span>
|
|
1750
|
+
<button onclick="openPalaceOverlay()" style="background:none;border:1px solid rgba(0,229,200,0.3);color:var(--teal);font-family:'Azeret Mono',monospace;font-size:9px;letter-spacing:0.1em;padding:3px 9px;cursor:pointer;border-radius:3px;transition:background 0.15s;" onmouseover="this.style.background='rgba(0,229,200,0.1)'" onmouseout="this.style.background='none'">⬡ MEMORY PALACE</button>
|
|
1418
1751
|
</div>
|
|
1419
1752
|
</div>
|
|
1420
1753
|
<div id="header-right">
|
|
@@ -1439,7 +1772,7 @@
|
|
|
1439
1772
|
<div class="proj-header">Project</div>
|
|
1440
1773
|
<div class="proj-header">Path</div>
|
|
1441
1774
|
<div class="proj-header" style="text-align:right">Sessions</div>
|
|
1442
|
-
<div class="proj-header" style="text-align:right">
|
|
1775
|
+
<div class="proj-header" style="text-align:right">Memories</div>
|
|
1443
1776
|
<div class="proj-header" style="text-align:right">Size</div>
|
|
1444
1777
|
<div class="proj-header">Last Active</div>
|
|
1445
1778
|
<div class="proj-cell" style="grid-column:1/7;color:var(--muted)">Loading projects…</div>
|
|
@@ -1449,17 +1782,6 @@
|
|
|
1449
1782
|
</div>
|
|
1450
1783
|
|
|
1451
1784
|
<!-- ─── AGENTS ──────────────────────────────────────────────── -->
|
|
1452
|
-
<div class="panel open" id="panel-agents">
|
|
1453
|
-
<div class="panel-header" onclick="togglePanel('panel-agents')">
|
|
1454
|
-
<div class="panel-title"><span class="live-dot green"></span>AGENTS</div>
|
|
1455
|
-
<span class="panel-badge" id="agents-badge">0</span>
|
|
1456
|
-
<span class="panel-chevron">›</span>
|
|
1457
|
-
</div>
|
|
1458
|
-
<div class="panel-body" id="agents-body">
|
|
1459
|
-
<div class="placeholder">NO AGENTS</div>
|
|
1460
|
-
</div>
|
|
1461
|
-
</div>
|
|
1462
|
-
|
|
1463
1785
|
<!-- ─── TOKENS ──────────────────────────────────────────────── -->
|
|
1464
1786
|
<div class="panel open" id="panel-tokens">
|
|
1465
1787
|
<div class="panel-header" onclick="togglePanel('panel-tokens')">
|
|
@@ -1473,76 +1795,35 @@
|
|
|
1473
1795
|
</div>
|
|
1474
1796
|
</div>
|
|
1475
1797
|
|
|
1476
|
-
<!-- ─── MEMORY PALACE ───────────────────────────────────────── -->
|
|
1477
|
-
<div class="panel open" id="panel-memory">
|
|
1478
|
-
<div class="panel-header" onclick="togglePanel('panel-memory')">
|
|
1479
|
-
<div class="panel-title"><span class="live-dot"></span>MEMORY PALACE</div>
|
|
1480
|
-
<div style="display:flex;gap:6px;align-items:center;">
|
|
1481
|
-
<span id="hnsw-indicator"></span>
|
|
1482
|
-
<button onclick="event.stopPropagation();openPalaceOverlay()" style="background:none;border:1px solid rgba(0,229,200,0.3);color:var(--teal);font-family:'Azeret Mono',monospace;font-size:9px;letter-spacing:0.08em;padding:2px 7px;cursor:pointer;border-radius:3px;transition:background 0.15s;" onmouseover="this.style.background='rgba(0,229,200,0.08)'" onmouseout="this.style.background='none'">EXPLORE</button>
|
|
1483
|
-
<span class="panel-badge" id="memory-badge">0</span>
|
|
1484
|
-
<span class="panel-chevron">›</span>
|
|
1485
|
-
</div>
|
|
1486
|
-
</div>
|
|
1487
|
-
<div class="panel-body" id="memory-body">
|
|
1488
|
-
<div class="memory-layout">
|
|
1489
|
-
<div class="memory-left" id="memory-left"></div>
|
|
1490
|
-
<div class="memory-right" id="memory-right"></div>
|
|
1491
|
-
</div>
|
|
1492
|
-
</div>
|
|
1493
|
-
</div>
|
|
1494
1798
|
|
|
1495
|
-
<!-- ───
|
|
1496
|
-
<div class="panel open" id="panel-
|
|
1497
|
-
<div class="panel-header" onclick="togglePanel('panel-
|
|
1498
|
-
<div class="panel-title"><span class="live-dot
|
|
1799
|
+
<!-- ─── SCHEDULED LOOPS ──────────────────────────────────────── -->
|
|
1800
|
+
<div class="panel open" id="panel-loops">
|
|
1801
|
+
<div class="panel-header" onclick="togglePanel('panel-loops')">
|
|
1802
|
+
<div class="panel-title"><span class="live-dot" id="loops-live-dot"></span>SCHEDULED LOOPS</div>
|
|
1499
1803
|
<div style="display:flex;align-items:center;gap:6px;">
|
|
1500
|
-
<span class="panel-badge" id="
|
|
1804
|
+
<span class="panel-badge" id="loops-badge">0</span>
|
|
1501
1805
|
<span class="panel-chevron">›</span>
|
|
1502
1806
|
</div>
|
|
1503
1807
|
</div>
|
|
1504
|
-
<div class="panel-body" id="
|
|
1505
|
-
<div class="
|
|
1506
|
-
<div class="hooks-col" id="hooks-left"></div>
|
|
1507
|
-
<div class="hooks-col" id="hooks-right"></div>
|
|
1508
|
-
</div>
|
|
1808
|
+
<div class="panel-body" id="loops-body">
|
|
1809
|
+
<div class="loops-empty">NO ACTIVE LOOPS</div>
|
|
1509
1810
|
</div>
|
|
1510
1811
|
</div>
|
|
1511
1812
|
|
|
1512
|
-
<!-- ───
|
|
1513
|
-
<div class="panel open" id="panel-metrics">
|
|
1514
|
-
<div class="panel-header" onclick="togglePanel('panel-metrics')">
|
|
1515
|
-
<div class="panel-title"><span class="live-dot muted"></span>METRICS</div>
|
|
1516
|
-
<span class="panel-chevron">›</span>
|
|
1517
|
-
</div>
|
|
1518
|
-
<div class="panel-body" id="metrics-body">
|
|
1519
|
-
<div class="placeholder">—</div>
|
|
1520
|
-
</div>
|
|
1521
|
-
</div>
|
|
1522
|
-
|
|
1523
|
-
<!-- ─── ACTIVITY ────────────────────────────────────────────── -->
|
|
1813
|
+
<!-- ─── SESSION JOURNAL ────────────────────────────────────────── -->
|
|
1524
1814
|
<div class="panel open" id="panel-activity">
|
|
1525
1815
|
<div class="panel-header" onclick="togglePanel('panel-activity')">
|
|
1526
|
-
<div class="panel-title"><span class="live-dot"></span>
|
|
1816
|
+
<div class="panel-title"><span class="live-dot"></span>SESSION JOURNAL</div>
|
|
1527
1817
|
<div style="display:flex;align-items:center;gap:6px;">
|
|
1528
1818
|
<span class="panel-badge" id="activity-badge">0</span>
|
|
1529
1819
|
<span class="panel-chevron">›</span>
|
|
1530
1820
|
</div>
|
|
1531
1821
|
</div>
|
|
1532
1822
|
<div class="panel-body" id="activity-body">
|
|
1533
|
-
<div id="
|
|
1823
|
+
<div id="journal-list"><div id="journal-empty">Loading sessions…</div></div>
|
|
1534
1824
|
</div>
|
|
1535
1825
|
</div>
|
|
1536
1826
|
|
|
1537
|
-
<div class="panel" id="panel-system">
|
|
1538
|
-
<div class="panel-header" onclick="togglePanel('panel-system')">
|
|
1539
|
-
<div class="panel-title"><span class="live-dot muted"></span>SYSTEM</div>
|
|
1540
|
-
<span class="panel-chevron">›</span>
|
|
1541
|
-
</div>
|
|
1542
|
-
<div class="panel-body" id="system-body">
|
|
1543
|
-
<div class="placeholder">—</div>
|
|
1544
|
-
</div>
|
|
1545
|
-
</div>
|
|
1546
1827
|
|
|
1547
1828
|
|
|
1548
1829
|
</div><!-- end #grid -->
|
|
@@ -1561,14 +1842,12 @@ const MAX_ACTIVITY = 80;
|
|
|
1561
1842
|
// Track which panels are currently open (match initial HTML .open classes)
|
|
1562
1843
|
const expandedPanels = new Set([
|
|
1563
1844
|
'panel-projects',
|
|
1564
|
-
'panel-
|
|
1845
|
+
'panel-tokens', 'panel-activity'
|
|
1565
1846
|
]);
|
|
1566
1847
|
|
|
1567
1848
|
// Section name → panel id mapping
|
|
1568
1849
|
const SECTION_PANEL = {
|
|
1569
|
-
|
|
1570
|
-
tokens: 'panel-tokens', memory: 'panel-memory', hooks: 'panel-hooks',
|
|
1571
|
-
knowledge: 'panel-memory', metrics: 'panel-metrics', system: 'panel-system',
|
|
1850
|
+
tokens: 'panel-tokens',
|
|
1572
1851
|
};
|
|
1573
1852
|
|
|
1574
1853
|
// ═══════════════════════════════════════════════════════════════════
|
|
@@ -1596,13 +1875,7 @@ window.togglePanel = function(panelId) {
|
|
|
1596
1875
|
function renderPanelById(panelId) {
|
|
1597
1876
|
if (!appData) return;
|
|
1598
1877
|
switch (panelId) {
|
|
1599
|
-
case 'panel-agents': renderAgents(appData); break;
|
|
1600
1878
|
case 'panel-tokens': renderTokens(appData); break;
|
|
1601
|
-
case 'panel-memory': renderMemory(appData); break;
|
|
1602
|
-
case 'panel-hooks': renderHooks(appData); break;
|
|
1603
|
-
case 'panel-knowledge': renderMemory(appData); break;
|
|
1604
|
-
case 'panel-metrics': renderMetrics(appData); break;
|
|
1605
|
-
case 'panel-system': renderSystem(appData); break;
|
|
1606
1879
|
}
|
|
1607
1880
|
}
|
|
1608
1881
|
|
|
@@ -1625,10 +1898,7 @@ async function fetchAndRenderSections(sections) {
|
|
|
1625
1898
|
renderPanelById(panelId);
|
|
1626
1899
|
} catch (e) { /* ignore transient fetch errors */ }
|
|
1627
1900
|
}
|
|
1628
|
-
|
|
1629
|
-
if (metricsStale && expandedPanels.has('panel-metrics') && appData) {
|
|
1630
|
-
renderMetrics(appData);
|
|
1631
|
-
}
|
|
1901
|
+
|
|
1632
1902
|
}
|
|
1633
1903
|
|
|
1634
1904
|
// ═══════════════════════════════════════════════════════════════════
|
|
@@ -1716,9 +1986,8 @@ function markLive(panelId) {
|
|
|
1716
1986
|
|
|
1717
1987
|
function updateLiveBorders() {
|
|
1718
1988
|
const panelIds = [
|
|
1719
|
-
'panel-
|
|
1720
|
-
'panel-
|
|
1721
|
-
'panel-activity','panel-system'
|
|
1989
|
+
'panel-tokens',
|
|
1990
|
+
'panel-activity'
|
|
1722
1991
|
];
|
|
1723
1992
|
const isLive = lastUpdateTime > 0 && (Date.now() - lastUpdateTime) < 5000;
|
|
1724
1993
|
panelIds.forEach(id => {
|
|
@@ -1774,13 +2043,12 @@ async function selectProject(dir) {
|
|
|
1774
2043
|
if (selectedProjectDir === dir) return;
|
|
1775
2044
|
selectedProjectDir = dir;
|
|
1776
2045
|
_codeGraphLoaded = false; kgCodeGraph.reset();
|
|
2046
|
+
renderJournal();
|
|
1777
2047
|
try {
|
|
1778
2048
|
const res = await fetch(`/api/data?dir=${encodeURIComponent(dir)}`);
|
|
1779
2049
|
const data = await res.json();
|
|
1780
2050
|
updateAllPanels(data);
|
|
1781
|
-
} catch (e) {
|
|
1782
|
-
appendActivity(`Failed to load project: ${e.message}`, 'error');
|
|
1783
|
-
}
|
|
2051
|
+
} catch (e) {}
|
|
1784
2052
|
}
|
|
1785
2053
|
|
|
1786
2054
|
function renderProjects(data) {
|
|
@@ -1803,7 +2071,7 @@ function renderProjects(data) {
|
|
|
1803
2071
|
<div class="proj-header">Project</div>
|
|
1804
2072
|
<div class="proj-header">Path</div>
|
|
1805
2073
|
<div class="proj-header" style="text-align:right">Sessions</div>
|
|
1806
|
-
<div class="proj-header" style="text-align:right">
|
|
2074
|
+
<div class="proj-header" style="text-align:right">Memories</div>
|
|
1807
2075
|
<div class="proj-header" style="text-align:right">Size</div>
|
|
1808
2076
|
<div class="proj-header">Last Active</div>
|
|
1809
2077
|
`;
|
|
@@ -1822,7 +2090,7 @@ function renderProjects(data) {
|
|
|
1822
2090
|
</div>
|
|
1823
2091
|
<div class="proj-cell proj-path" style="${highlight}${cursor}" ${rowClick}>${p.path.replace(/\/Users\/[^/]+/, '~')}</div>
|
|
1824
2092
|
<div class="proj-cell proj-count" style="${highlight}${cursor};justify-content:flex-end" ${rowClick}>${p.sessionCount}</div>
|
|
1825
|
-
<div class="proj-cell proj-dots" style="${highlight}${cursor};justify-content:flex-end" ${rowClick}>${p.
|
|
2093
|
+
<div class="proj-cell proj-dots" style="${highlight}${cursor};justify-content:flex-end" ${rowClick}>${p.memoryCount || '—'}</div>
|
|
1826
2094
|
<div class="proj-cell proj-size" style="${highlight}${cursor};justify-content:flex-end" ${rowClick}>${formatBytes(p.totalSize)}</div>
|
|
1827
2095
|
<div class="proj-cell proj-age" style="${highlight}${cursor}" ${rowClick}>${timeAgo(p.lastActivity)}</div>
|
|
1828
2096
|
`;
|
|
@@ -2085,12 +2353,12 @@ function renderTokens(data) {
|
|
|
2085
2353
|
<div class="token-stat">
|
|
2086
2354
|
<div class="token-stat-label">DAILY</div>
|
|
2087
2355
|
<div class="token-stat-val">${fmtCost(todayCost)}</div>
|
|
2088
|
-
${todayCalls != null ? `<div style="font-size:
|
|
2356
|
+
${todayCalls != null ? `<div style="font-size:8px;color:var(--muted);">${fmtNum(todayCalls)} calls</div>` : ''}
|
|
2089
2357
|
</div>
|
|
2090
2358
|
<div class="token-stat">
|
|
2091
2359
|
<div class="token-stat-label">MONTHLY</div>
|
|
2092
2360
|
<div class="token-stat-val">${fmtCost(monthlyCost)}</div>
|
|
2093
|
-
${monthlyCalls != null ? `<div style="font-size:
|
|
2361
|
+
${monthlyCalls != null ? `<div style="font-size:8px;color:var(--muted);">${fmtNum(monthlyCalls)} calls</div>` : ''}
|
|
2094
2362
|
</div>
|
|
2095
2363
|
<div class="token-stat">
|
|
2096
2364
|
<div class="token-stat-label">ALL TIME $</div>
|
|
@@ -2623,6 +2891,123 @@ function renderMemory(data) {
|
|
|
2623
2891
|
right.innerHTML = rightHtml;
|
|
2624
2892
|
}
|
|
2625
2893
|
|
|
2894
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
2895
|
+
// SCHEDULED LOOPS PANEL
|
|
2896
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
2897
|
+
let _loopCountdownTimers = [];
|
|
2898
|
+
|
|
2899
|
+
function fmtCountdown(nextRunAt) {
|
|
2900
|
+
if (!nextRunAt) return '';
|
|
2901
|
+
const diff = nextRunAt - Date.now();
|
|
2902
|
+
if (diff <= 0) return 'running now';
|
|
2903
|
+
const s = Math.floor(diff / 1000);
|
|
2904
|
+
if (s < 60) return `next in ${s}s`;
|
|
2905
|
+
const m = Math.floor(s / 60), rs = s % 60;
|
|
2906
|
+
return `next in ${m}m ${rs}s`;
|
|
2907
|
+
}
|
|
2908
|
+
|
|
2909
|
+
function fmtRelTime(ts) {
|
|
2910
|
+
if (!ts) return '—';
|
|
2911
|
+
const diff = Date.now() - ts;
|
|
2912
|
+
if (diff < 5000) return 'just now';
|
|
2913
|
+
if (diff < 60000) return `${Math.floor(diff/1000)}s ago`;
|
|
2914
|
+
if (diff < 3600000) return `${Math.floor(diff/60000)}m ago`;
|
|
2915
|
+
return new Date(ts).toLocaleTimeString();
|
|
2916
|
+
}
|
|
2917
|
+
|
|
2918
|
+
async function renderLoops() {
|
|
2919
|
+
try {
|
|
2920
|
+
const r = await fetch('/api/loops');
|
|
2921
|
+
const { loops = [] } = await r.json();
|
|
2922
|
+
const body = document.getElementById('loops-body');
|
|
2923
|
+
const badge = document.getElementById('loops-badge');
|
|
2924
|
+
const dot = document.getElementById('loops-live-dot');
|
|
2925
|
+
if (!body) return;
|
|
2926
|
+
|
|
2927
|
+
// Clear old countdown timers
|
|
2928
|
+
_loopCountdownTimers.forEach(clearInterval);
|
|
2929
|
+
_loopCountdownTimers = [];
|
|
2930
|
+
|
|
2931
|
+
const active = loops.filter(l => l.status !== 'complete');
|
|
2932
|
+
badge.textContent = active.length || '0';
|
|
2933
|
+
|
|
2934
|
+
if (dot) {
|
|
2935
|
+
dot.className = 'live-dot' + (active.length > 0 ? ' green' : '');
|
|
2936
|
+
}
|
|
2937
|
+
|
|
2938
|
+
if (!loops.length) {
|
|
2939
|
+
body.innerHTML = '<div class="loops-empty">NO ACTIVE LOOPS</div>';
|
|
2940
|
+
return;
|
|
2941
|
+
}
|
|
2942
|
+
|
|
2943
|
+
body.innerHTML = loops.map(loop => {
|
|
2944
|
+
const isRepeat = loop.type === 'repeat';
|
|
2945
|
+
const isDo = loop.type === 'do';
|
|
2946
|
+
const typeClass = isRepeat ? 'lc-repeat' : 'lc-do';
|
|
2947
|
+
const pillClass = isRepeat ? 'lp-repeat' : 'lp-do';
|
|
2948
|
+
const typeLabel = isRepeat ? 'REPEAT' : 'DO';
|
|
2949
|
+
const stopPending = loop.stopRequested;
|
|
2950
|
+
const statusDotClass = stopPending ? 'stopped' : (loop.status === 'waiting' ? 'waiting' : 'running');
|
|
2951
|
+
|
|
2952
|
+
const progress = isRepeat && loop.maxReps > 0
|
|
2953
|
+
? Math.round((loop.currentRep - 1) / loop.maxReps * 100)
|
|
2954
|
+
: null;
|
|
2955
|
+
const progressBar = progress !== null
|
|
2956
|
+
? `<div class="loop-progress-row">
|
|
2957
|
+
<div class="loop-progress-bar"><div class="loop-progress-fill" style="width:${progress}%"></div></div>
|
|
2958
|
+
<div class="loop-countdown" id="lcd-${loop.id}">${fmtCountdown(loop.nextRunAt)}</div>
|
|
2959
|
+
</div>`
|
|
2960
|
+
: `<div class="loop-progress-row">
|
|
2961
|
+
<div style="flex:1"></div>
|
|
2962
|
+
<div class="loop-countdown" id="lcd-${loop.id}">${fmtCountdown(loop.nextRunAt)}</div>
|
|
2963
|
+
</div>`;
|
|
2964
|
+
|
|
2965
|
+
const metaItems = isRepeat
|
|
2966
|
+
? `<span class="loop-meta-item">run <span>${loop.currentRep || 1}/${loop.maxReps || '?'}</span></span>
|
|
2967
|
+
<span class="loop-meta-item">every <span>${loop.interval || '?'}m</span></span>
|
|
2968
|
+
<span class="loop-meta-item">started <span>${fmtRelTime(loop.startedAt)}</span></span>`
|
|
2969
|
+
: `<span class="loop-meta-item">task <span>${escHtml(String(loop.currentTask || '—').slice(0,40))}</span></span>
|
|
2970
|
+
<span class="loop-meta-item">last run <span>${fmtRelTime(loop.lastRunAt)}</span></span>`;
|
|
2971
|
+
|
|
2972
|
+
const stopLabel = stopPending ? 'STOPPING…' : 'STOP';
|
|
2973
|
+
const disabledAttr = stopPending ? 'disabled' : '';
|
|
2974
|
+
|
|
2975
|
+
return `<div class="loop-card ${typeClass}${stopPending ? ' lc-stop-pending' : ''}" data-loop-id="${escHtml(loop.id)}">
|
|
2976
|
+
<span class="loop-type-pill ${pillClass}"><span class="loop-status-dot ${statusDotClass}"></span>${typeLabel}</span>
|
|
2977
|
+
<div class="loop-prompt" title="${escHtml(loop.prompt || '')}">${escHtml((loop.prompt || '').slice(0,80))}</div>
|
|
2978
|
+
<button class="loop-stop-btn" onclick="stopLoop('${escHtml(loop.id)}')" ${disabledAttr}>${stopLabel}</button>
|
|
2979
|
+
<div class="loop-meta">${metaItems}</div>
|
|
2980
|
+
${progressBar}
|
|
2981
|
+
</div>`;
|
|
2982
|
+
}).join('');
|
|
2983
|
+
|
|
2984
|
+
// Live countdown update
|
|
2985
|
+
const timer = setInterval(() => {
|
|
2986
|
+
loops.forEach(loop => {
|
|
2987
|
+
const el = document.getElementById(`lcd-${loop.id}`);
|
|
2988
|
+
if (el) el.textContent = fmtCountdown(loop.nextRunAt);
|
|
2989
|
+
});
|
|
2990
|
+
}, 1000);
|
|
2991
|
+
_loopCountdownTimers.push(timer);
|
|
2992
|
+
} catch {}
|
|
2993
|
+
}
|
|
2994
|
+
|
|
2995
|
+
async function stopLoop(id) {
|
|
2996
|
+
const card = document.querySelector(`[data-loop-id="${id}"]`);
|
|
2997
|
+
const btn = card?.querySelector('.loop-stop-btn');
|
|
2998
|
+
if (btn) { btn.disabled = true; btn.textContent = 'STOPPING…'; }
|
|
2999
|
+
try {
|
|
3000
|
+
await fetch('/api/loops/stop', {
|
|
3001
|
+
method: 'POST',
|
|
3002
|
+
headers: { 'Content-Type': 'application/json' },
|
|
3003
|
+
body: JSON.stringify({ id })
|
|
3004
|
+
});
|
|
3005
|
+
setTimeout(renderLoops, 400);
|
|
3006
|
+
} catch {
|
|
3007
|
+
if (btn) { btn.disabled = false; btn.textContent = 'STOP'; }
|
|
3008
|
+
}
|
|
3009
|
+
}
|
|
3010
|
+
|
|
2626
3011
|
// ═══════════════════════════════════════════════════════════════════
|
|
2627
3012
|
// HOOKS & ROUTING PANEL
|
|
2628
3013
|
// ═══════════════════════════════════════════════════════════════════
|
|
@@ -2632,11 +3017,8 @@ function renderHooks(data) {
|
|
|
2632
3017
|
const feedback = h.feedback || [];
|
|
2633
3018
|
const workers = h.workerDispatch || [];
|
|
2634
3019
|
|
|
2635
|
-
const
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
const left = document.getElementById('hooks-left');
|
|
2639
|
-
const right = document.getElementById('hooks-right');
|
|
3020
|
+
const left = document.getElementById('po-hooks-left');
|
|
3021
|
+
const right = document.getElementById('po-hooks-right');
|
|
2640
3022
|
if (!left || !right) return;
|
|
2641
3023
|
|
|
2642
3024
|
// Left: last route — actual fields: agent, confidence, reason, semanticRouting, updatedAt
|
|
@@ -2823,45 +3205,77 @@ function renderMetrics(data) {
|
|
|
2823
3205
|
}
|
|
2824
3206
|
|
|
2825
3207
|
// ═══════════════════════════════════════════════════════════════════
|
|
2826
|
-
//
|
|
3208
|
+
// SESSION JOURNAL
|
|
2827
3209
|
// ═══════════════════════════════════════════════════════════════════
|
|
2828
|
-
function appendActivity(
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
const
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
// Trim old entries
|
|
2839
|
-
while (log.children.length > MAX_ACTIVITY) {
|
|
2840
|
-
log.removeChild(log.lastChild);
|
|
2841
|
-
}
|
|
3210
|
+
function appendActivity() {} // no-op: panel replaced by Session Journal
|
|
3211
|
+
function inferActivityType() { return 'default'; }
|
|
3212
|
+
|
|
3213
|
+
function fmtDur(ms) {
|
|
3214
|
+
if (!ms) return '';
|
|
3215
|
+
const m = Math.round(ms / 60000);
|
|
3216
|
+
if (m < 60) return m + 'm';
|
|
3217
|
+
return Math.floor(m / 60) + 'h ' + (m % 60) + 'm';
|
|
3218
|
+
}
|
|
2842
3219
|
|
|
2843
|
-
|
|
2844
|
-
|
|
3220
|
+
function toggleJournalCard(el) {
|
|
3221
|
+
el.classList.toggle('open');
|
|
2845
3222
|
}
|
|
2846
3223
|
|
|
2847
|
-
function
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
3224
|
+
async function renderJournal() {
|
|
3225
|
+
const dir = selectedProjectDir || '';
|
|
3226
|
+
const url = `/api/session-journal${dir ? '?dir=' + encodeURIComponent(dir) : ''}`;
|
|
3227
|
+
let sessions = [];
|
|
3228
|
+
try {
|
|
3229
|
+
const res = await fetch(url);
|
|
3230
|
+
if (res.ok) ({ sessions } = await res.json());
|
|
3231
|
+
} catch {}
|
|
2853
3232
|
|
|
2854
|
-
const
|
|
2855
|
-
const
|
|
2856
|
-
if (
|
|
3233
|
+
const list = document.getElementById('journal-list');
|
|
3234
|
+
const badge = document.getElementById('activity-badge');
|
|
3235
|
+
if (!list) return;
|
|
3236
|
+
if (badge) badge.textContent = sessions.length;
|
|
2857
3237
|
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
3238
|
+
if (!sessions.length) {
|
|
3239
|
+
list.innerHTML = '<div id="journal-empty">NO SESSIONS FOUND</div>';
|
|
3240
|
+
return;
|
|
3241
|
+
}
|
|
2861
3242
|
|
|
2862
|
-
|
|
3243
|
+
list.innerHTML = sessions.map(s => {
|
|
3244
|
+
const dt = s.lastTs ? new Date(s.lastTs) : (s.mtime ? new Date(s.mtime) : null);
|
|
3245
|
+
const dateStr = dt ? dt.toLocaleDateString(undefined, { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' }) : '';
|
|
3246
|
+
const dur = fmtDur(s.totalDurationMs);
|
|
3247
|
+
const msgs = s.totalMessages ? s.totalMessages + ' msgs' : '';
|
|
3248
|
+
const stats = [dur, msgs].filter(Boolean).join(' · ');
|
|
3249
|
+
const shortId = s.id.slice(0, 8) + '…';
|
|
3250
|
+
const prompt = s.lastPrompt ? escHtml(s.lastPrompt.slice(0, 120)) : '';
|
|
3251
|
+
|
|
3252
|
+
const latestSummary = s.summaries && s.summaries.length ? s.summaries[s.summaries.length - 1] : null;
|
|
3253
|
+
const excerpt = latestSummary ? escHtml(latestSummary.text.slice(0, 200).replace(/\n+/g, ' ')) : '';
|
|
3254
|
+
const fullText = latestSummary ? escHtml(latestSummary.text) : '';
|
|
3255
|
+
const compactBadge = s.summaries && s.summaries.length > 0
|
|
3256
|
+
? `<span class="jn-badge-compact">${s.summaries.length > 1 ? s.summaries.length + ' compacts' : 'compacted'}</span>` : '';
|
|
3257
|
+
|
|
3258
|
+
return `<div class="jn-card" onclick="toggleJournalCard(this)">
|
|
3259
|
+
<div class="jn-header">
|
|
3260
|
+
<span class="jn-date">${dateStr}</span>
|
|
3261
|
+
<span class="jn-id">${shortId}</span>
|
|
3262
|
+
${compactBadge}
|
|
3263
|
+
${stats ? `<span class="jn-stats">${stats}</span>` : ''}
|
|
3264
|
+
</div>
|
|
3265
|
+
${prompt ? `<div class="jn-prompt">"${prompt}${s.lastPrompt && s.lastPrompt.length > 120 ? '…' : ''}"</div>` : ''}
|
|
3266
|
+
${excerpt
|
|
3267
|
+
? `<div class="jn-excerpt">${excerpt}${latestSummary && latestSummary.text.length > 200 ? '…' : ''}</div>
|
|
3268
|
+
<div class="jn-summary-full">${fullText}</div>`
|
|
3269
|
+
: `<div class="jn-no-summary">No compact summary available</div>`
|
|
3270
|
+
}
|
|
3271
|
+
</div>`;
|
|
3272
|
+
}).join('');
|
|
2863
3273
|
}
|
|
2864
3274
|
|
|
3275
|
+
// Initial load + refresh every 2 minutes
|
|
3276
|
+
renderJournal();
|
|
3277
|
+
setInterval(renderJournal, 120000);
|
|
3278
|
+
|
|
2865
3279
|
// ═══════════════════════════════════════════════════════════════════
|
|
2866
3280
|
// SYSTEM PANEL
|
|
2867
3281
|
// ═══════════════════════════════════════════════════════════════════
|
|
@@ -2939,14 +3353,7 @@ function updateAllPanels(data) {
|
|
|
2939
3353
|
renderProjects(data); // always render (navigation panel)
|
|
2940
3354
|
renderProjectStats(data);
|
|
2941
3355
|
// Only render panels that are currently expanded
|
|
2942
|
-
if (expandedPanels.has('panel-agents')) renderAgents(data);
|
|
2943
3356
|
if (expandedPanels.has('panel-tokens')) renderTokens(data);
|
|
2944
|
-
if (expandedPanels.has('panel-memory')) renderMemory(data);
|
|
2945
|
-
// Always update hooks badge even when panel is collapsed
|
|
2946
|
-
{ const h = data.hooks || {}; const lr = h.lastRoute || {}; const badge = document.getElementById('hooks-badge'); if (badge) badge.textContent = lr.agent || lr.suggestedAgent || (h.feedback && h.feedback.length) || '—'; }
|
|
2947
|
-
if (expandedPanels.has('panel-hooks')) renderHooks(data);
|
|
2948
|
-
if (expandedPanels.has('panel-metrics')) renderMetrics(data);
|
|
2949
|
-
if (expandedPanels.has('panel-system')) renderSystem(data);
|
|
2950
3357
|
// Activity log entry
|
|
2951
3358
|
if (prev) {
|
|
2952
3359
|
const type = inferActivityType(data, prev);
|
|
@@ -3046,6 +3453,12 @@ async function fetchInitial() {
|
|
|
3046
3453
|
}
|
|
3047
3454
|
}
|
|
3048
3455
|
|
|
3456
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
3457
|
+
// LOOPS POLLING — every 10 seconds
|
|
3458
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
3459
|
+
renderLoops();
|
|
3460
|
+
setInterval(renderLoops, 10000);
|
|
3461
|
+
|
|
3049
3462
|
// ═══════════════════════════════════════════════════════════════════
|
|
3050
3463
|
// LIVE BORDER REFRESH TIMER
|
|
3051
3464
|
// ═══════════════════════════════════════════════════════════════════
|
|
@@ -3114,10 +3527,9 @@ window.switchPalaceTab = function(tab) {
|
|
|
3114
3527
|
b.classList.toggle('active', !!(btnTab && btnTab[1] === tab));
|
|
3115
3528
|
});
|
|
3116
3529
|
document.querySelectorAll('.po-tab-pane').forEach(p => p.classList.remove('active'));
|
|
3117
|
-
const paneIds = { drawers: 'po-drawers-tab', sessions: 'po-sessions-tab', chunks: 'po-chunks-tab', swarm: 'po-swarm-tab', graph: 'po-graph-tab', knowledge: 'po-knowledge-tab' };
|
|
3530
|
+
const paneIds = { drawers: 'po-drawers-tab', sessions: 'po-sessions-tab', chunks: 'po-chunks-tab', routing: 'po-routing-tab', swarm: 'po-swarm-tab', graph: 'po-graph-tab', knowledge: 'po-knowledge-tab' };
|
|
3118
3531
|
const pane = document.getElementById(paneIds[tab]);
|
|
3119
3532
|
if (pane) pane.classList.add('active');
|
|
3120
|
-
if (tab !== 'graph') kgGraph.stop();
|
|
3121
3533
|
if (tab !== 'knowledge') kgCodeGraph.stop();
|
|
3122
3534
|
if (palaceData) renderPalaceTab(tab);
|
|
3123
3535
|
};
|
|
@@ -3213,8 +3625,9 @@ function renderPalaceTab(tab) {
|
|
|
3213
3625
|
if (tab === 'drawers') renderMemories();
|
|
3214
3626
|
else if (tab === 'sessions') renderPalaceSessions();
|
|
3215
3627
|
else if (tab === 'chunks') renderPalaceChunks();
|
|
3628
|
+
else if (tab === 'routing') renderHooks(appData || {});
|
|
3216
3629
|
else if (tab === 'swarm') renderPalaceSwarm();
|
|
3217
|
-
else if (tab === 'graph') {
|
|
3630
|
+
else if (tab === 'graph') { renderAgentGraph(palaceData.graph || { nodes: [], edges: [] }); }
|
|
3218
3631
|
else if (tab === 'knowledge') { renderPalaceKnowledge(); renderPalaceCodeGraph(); }
|
|
3219
3632
|
}
|
|
3220
3633
|
|
|
@@ -3355,165 +3768,483 @@ window.deleteKnowledgeChunk = async function(chunkId) {
|
|
|
3355
3768
|
}
|
|
3356
3769
|
};
|
|
3357
3770
|
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3771
|
+
// ─── Swarm History Tab ─────────────────────────────────────────────────────
|
|
3772
|
+
let _swarmHistoryEntries = [];
|
|
3773
|
+
let _swarmSelectedIdx = null;
|
|
3774
|
+
let _swarmSelectedAgentId = null;
|
|
3775
|
+
let _swarmLiveState = null;
|
|
3776
|
+
|
|
3777
|
+
async function renderSwarmHistory() {
|
|
3778
|
+
const list = document.getElementById('po-swarm-list');
|
|
3779
|
+
if (!list) return;
|
|
3780
|
+
|
|
3781
|
+
const dir = selectedProjectDir || '';
|
|
3782
|
+
try {
|
|
3783
|
+
const r = await fetch(`/api/swarm-history${dir ? '?dir=' + encodeURIComponent(dir) : ''}`);
|
|
3784
|
+
const d = r.ok ? await r.json() : { entries: [] };
|
|
3785
|
+
_swarmHistoryEntries = d.entries || [];
|
|
3786
|
+
} catch {
|
|
3787
|
+
_swarmHistoryEntries = [];
|
|
3788
|
+
}
|
|
3789
|
+
|
|
3790
|
+
const sw = (appData && appData.swarm) ? appData.swarm : {};
|
|
3363
3791
|
const state = sw.state || {};
|
|
3364
|
-
|
|
3365
|
-
const config = sw.config || {};
|
|
3366
|
-
const suggestion = sw.suggestion || {};
|
|
3367
|
-
const agentRegs = (src.agents && src.agents.registrations) ? src.agents.registrations : [];
|
|
3792
|
+
_swarmLiveState = (state.status === 'running' || state.status === 'initializing') ? state : null;
|
|
3368
3793
|
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
|
|
3374
|
-
? (
|
|
3375
|
-
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
).
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3794
|
+
let html = '';
|
|
3795
|
+
|
|
3796
|
+
if (_swarmLiveState) {
|
|
3797
|
+
const id = _swarmLiveState.swarmId || _swarmLiveState.id || 'live';
|
|
3798
|
+
const topo = (_swarmLiveState.topology || '—').toUpperCase();
|
|
3799
|
+
const agentCount = Array.isArray(_swarmLiveState.agents) ? _swarmLiveState.agents.length : (_swarmLiveState.agents || 0);
|
|
3800
|
+
html += `<div class="po-swarm-item${_swarmSelectedIdx === 'live' ? ' selected' : ''}" onclick="selectSwarmRun('live')">
|
|
3801
|
+
<div class="po-swarm-item-row">
|
|
3802
|
+
<span class="live-dot green"></span>
|
|
3803
|
+
<span class="po-swarm-item-id">${escHtml(String(id).slice(0, 18))}</span>
|
|
3804
|
+
<span class="po-swarm-item-badge status-running">LIVE</span>
|
|
3805
|
+
</div>
|
|
3806
|
+
<div class="po-swarm-item-row">
|
|
3807
|
+
<span class="po-swarm-item-badge topo">${escHtml(topo)}</span>
|
|
3808
|
+
<span class="po-swarm-item-meta">${agentCount} agents</span>
|
|
3809
|
+
</div>
|
|
3810
|
+
</div>`;
|
|
3811
|
+
}
|
|
3812
|
+
|
|
3813
|
+
_swarmHistoryEntries.forEach((entry, i) => {
|
|
3814
|
+
const id = (entry.swarmId || '?').slice(0, 18);
|
|
3815
|
+
const topo = (entry.topology || '—').toUpperCase();
|
|
3816
|
+
const status = entry.status || 'unknown';
|
|
3817
|
+
const statusClass = 'status-' + status;
|
|
3818
|
+
const agentCount = (entry.agents || []).length;
|
|
3819
|
+
const age = entry.endedAt ? fmtAge(new Date(entry.endedAt).getTime()) : '—';
|
|
3820
|
+
html += `<div class="po-swarm-item${_swarmSelectedIdx === i ? ' selected' : ''}" onclick="selectSwarmRun(${i})">
|
|
3821
|
+
<div class="po-swarm-item-row">
|
|
3822
|
+
<span class="po-swarm-item-id">${escHtml(id)}</span>
|
|
3823
|
+
<span class="po-swarm-item-badge ${escHtml(statusClass)}">${escHtml(status.toUpperCase())}</span>
|
|
3824
|
+
</div>
|
|
3825
|
+
<div class="po-swarm-item-row">
|
|
3826
|
+
<span class="po-swarm-item-badge topo">${escHtml(topo)}</span>
|
|
3827
|
+
<span class="po-swarm-item-meta">${agentCount} agents · ${escHtml(age)}</span>
|
|
3828
|
+
</div>
|
|
3829
|
+
</div>`;
|
|
3830
|
+
});
|
|
3831
|
+
|
|
3832
|
+
if (!_swarmLiveState && _swarmHistoryEntries.length === 0) {
|
|
3833
|
+
html = '<div style="padding:16px;color:var(--muted);font-size:10px;text-align:center;">NO SWARM HISTORY</div>';
|
|
3834
|
+
}
|
|
3835
|
+
|
|
3836
|
+
list.innerHTML = html;
|
|
3837
|
+
|
|
3838
|
+
// Update data size in footer
|
|
3839
|
+
try {
|
|
3840
|
+
const sr = await fetch(`/api/swarm-data-size${dir ? '?dir=' + encodeURIComponent(dir) : ''}`);
|
|
3841
|
+
if (sr.ok) {
|
|
3842
|
+
const sz = await sr.json();
|
|
3843
|
+
const sizeEl = document.getElementById('po-swarm-data-size');
|
|
3844
|
+
if (sizeEl) sizeEl.textContent = sz.humanSize || '0 B';
|
|
3845
|
+
const cleanBtn = document.getElementById('po-swarm-clean-btn');
|
|
3846
|
+
if (cleanBtn) cleanBtn.style.display = sz.totalBytes > 0 ? '' : 'none';
|
|
3847
|
+
}
|
|
3848
|
+
} catch {}
|
|
3849
|
+
}
|
|
3850
|
+
|
|
3851
|
+
async function cleanSwarmDataUI() {
|
|
3852
|
+
if (!confirm('Remove all swarm event recordings? This cannot be undone.')) return;
|
|
3853
|
+
const dir = selectedProjectDir || '';
|
|
3854
|
+
try {
|
|
3855
|
+
await fetch(`/api/swarm-clean${dir ? '?dir=' + encodeURIComponent(dir) : ''}`, { method: 'DELETE' });
|
|
3856
|
+
} catch {}
|
|
3857
|
+
_swarmHistoryEntries = [];
|
|
3858
|
+
_swarmSelectedIdx = null;
|
|
3859
|
+
_swarmSelectedAgentId = null;
|
|
3860
|
+
renderSwarmHistory();
|
|
3861
|
+
const hint = document.getElementById('po-swarm-hint');
|
|
3862
|
+
const header = document.getElementById('po-swarm-header');
|
|
3863
|
+
const statsBar = document.getElementById('po-swarm-stats-bar');
|
|
3864
|
+
const body = document.getElementById('po-swarm-body');
|
|
3865
|
+
if (hint) hint.style.display = '';
|
|
3866
|
+
if (header) header.style.display = 'none';
|
|
3867
|
+
if (statsBar) statsBar.style.display = 'none';
|
|
3868
|
+
if (body) body.style.display = 'none';
|
|
3869
|
+
}
|
|
3870
|
+
|
|
3871
|
+
function renderPalaceSwarm() { renderSwarmHistory(); }
|
|
3872
|
+
|
|
3873
|
+
async function selectSwarmRun(idx) {
|
|
3874
|
+
_swarmSelectedIdx = idx;
|
|
3875
|
+
_swarmSelectedAgentId = null;
|
|
3876
|
+
|
|
3877
|
+
const items = document.querySelectorAll('.po-swarm-item');
|
|
3878
|
+
items.forEach((el, i) => {
|
|
3879
|
+
const itemIdx = _swarmLiveState ? (i === 0 ? 'live' : i - 1) : i;
|
|
3880
|
+
el.classList.toggle('selected', itemIdx === idx);
|
|
3881
|
+
});
|
|
3882
|
+
|
|
3883
|
+
let entry;
|
|
3884
|
+
if (idx === 'live') {
|
|
3885
|
+
entry = _buildLiveEntry();
|
|
3886
|
+
} else {
|
|
3887
|
+
entry = _swarmHistoryEntries[idx];
|
|
3888
|
+
}
|
|
3889
|
+
if (!entry) return;
|
|
3890
|
+
|
|
3891
|
+
const hint = document.getElementById('po-swarm-hint');
|
|
3892
|
+
const header = document.getElementById('po-swarm-header');
|
|
3893
|
+
const statsBar = document.getElementById('po-swarm-stats-bar');
|
|
3894
|
+
const body = document.getElementById('po-swarm-body');
|
|
3895
|
+
if (hint) hint.style.display = 'none';
|
|
3896
|
+
if (header) header.style.display = '';
|
|
3897
|
+
if (statsBar) statsBar.style.display = '';
|
|
3898
|
+
if (body) body.style.display = '';
|
|
3899
|
+
|
|
3900
|
+
const title = document.getElementById('po-swarm-title');
|
|
3901
|
+
const subtitle = document.getElementById('po-swarm-subtitle');
|
|
3902
|
+
if (title) title.textContent = entry.swarmId || '—';
|
|
3903
|
+
if (subtitle) {
|
|
3904
|
+
const dur = entry.durationMs ? fmtDuration(entry.durationMs) : '—';
|
|
3905
|
+
subtitle.textContent = `${entry.topology || '—'} · ${entry.consensus || '—'} · ${(entry.agents || []).length} agents · ${dur}`;
|
|
3906
|
+
}
|
|
3907
|
+
|
|
3908
|
+
const topo = document.getElementById('po-swarm-stat-topo');
|
|
3909
|
+
const cons = document.getElementById('po-swarm-stat-consensus');
|
|
3910
|
+
const agts = document.getElementById('po-swarm-stat-agents');
|
|
3911
|
+
const stat = document.getElementById('po-swarm-stat-status');
|
|
3912
|
+
const durEl = document.getElementById('po-swarm-stat-duration');
|
|
3913
|
+
if (topo) topo.innerHTML = `<span style="color:var(--teal)">${escHtml((entry.topology || '—').toUpperCase())}</span>`;
|
|
3914
|
+
if (cons) cons.textContent = (entry.consensus || '—').toUpperCase();
|
|
3915
|
+
if (agts) agts.textContent = `${(entry.agents || []).length} AGENTS`;
|
|
3916
|
+
if (stat) {
|
|
3917
|
+
const s = (entry.status || '—').toUpperCase();
|
|
3918
|
+
const c = entry.status === 'completed' ? 'var(--green,#4caf50)' : entry.status === 'error' ? '#EF5350' : entry.status === 'running' ? '#4caf50' : 'var(--muted)';
|
|
3919
|
+
stat.innerHTML = `<span style="color:${c}">${escHtml(s)}</span>`;
|
|
3920
|
+
}
|
|
3921
|
+
if (durEl) {
|
|
3922
|
+
const d = entry.durationMs ? fmtDuration(entry.durationMs) : (entry.startedAt && entry.endedAt ? fmtDuration(new Date(entry.endedAt) - new Date(entry.startedAt)) : '—');
|
|
3923
|
+
durEl.textContent = d;
|
|
3924
|
+
}
|
|
3925
|
+
|
|
3926
|
+
closeSwarmAgent();
|
|
3927
|
+
drawSwarmTopology(entry, null);
|
|
3928
|
+
|
|
3929
|
+
const agentList = document.getElementById('po-swarm-agent-list');
|
|
3930
|
+
if (agentList) {
|
|
3931
|
+
const agents = entry.agents || [];
|
|
3932
|
+
if (agents.length === 0) {
|
|
3933
|
+
agentList.innerHTML = '<div style="color:var(--muted);font-size:9px;">No agents recorded</div>';
|
|
3934
|
+
} else {
|
|
3935
|
+
agentList.innerHTML = agents.map(a => {
|
|
3936
|
+
const dotColor = (a.tasksFailed || 0) > 0 ? '#EF5350' : '#4caf50';
|
|
3937
|
+
const isQueen = (a.role || '').toLowerCase().includes('queen') || (a.role || '').toLowerCase().includes('coordinator') || (a.role || '').toLowerCase().includes('lead');
|
|
3938
|
+
return `<div class="po-swarm-agent-item" data-agent-id="${escHtml(a.id || a.type)}" onclick="selectSwarmAgent('${escHtml(a.id || a.type)}')">
|
|
3939
|
+
<span class="po-swarm-agent-dot" style="background:${dotColor}"></span>
|
|
3940
|
+
<span class="po-swarm-agent-type">${escHtml(a.type || '?')}</span>
|
|
3941
|
+
<span class="po-swarm-agent-role">${isQueen ? 'QUEEN' : 'WORKER'}</span>
|
|
3942
|
+
<span class="po-swarm-agent-stats">${a.tasksCompleted || 0} tasks · ${a.messageCount || 0} msgs</span>
|
|
3943
|
+
</div>`;
|
|
3944
|
+
}).join('');
|
|
3945
|
+
}
|
|
3946
|
+
}
|
|
3947
|
+
|
|
3948
|
+
// Populate event log section
|
|
3949
|
+
const evList = document.getElementById('po-swarm-events-list');
|
|
3950
|
+
if (evList) {
|
|
3951
|
+
const dir = selectedProjectDir || '';
|
|
3952
|
+
try {
|
|
3953
|
+
const evr = await fetch(`/api/swarm-events${dir ? '?dir=' + encodeURIComponent(dir) : ''}`);
|
|
3954
|
+
if (evr.ok) {
|
|
3955
|
+
const ed = await evr.json();
|
|
3956
|
+
const evts = (ed.events || []).reverse();
|
|
3957
|
+
if (evts.length === 0) {
|
|
3958
|
+
evList.innerHTML = '<div style="color:var(--muted);font-size:9px;">No events recorded yet</div>';
|
|
3959
|
+
} else {
|
|
3960
|
+
evList.innerHTML = evts.slice(0, 200).map(ev => {
|
|
3961
|
+
const ts = ev.ts ? new Date(ev.ts).toTimeString().slice(0, 8) : '—';
|
|
3962
|
+
const kind = ev.kind || '?';
|
|
3963
|
+
const detail = ev.agentId || ev.swarmId || ev.message || ev.proposalId || '';
|
|
3964
|
+
return `<div class="po-swarm-event-row">
|
|
3965
|
+
<span class="po-swarm-event-ts">${escHtml(ts)}</span>
|
|
3966
|
+
<span class="po-swarm-event-kind">${escHtml(kind)}</span>
|
|
3967
|
+
<span class="po-swarm-event-detail">${escHtml(String(detail).slice(0, 80))}</span>
|
|
3968
|
+
</div>`;
|
|
3969
|
+
}).join('');
|
|
3970
|
+
}
|
|
3971
|
+
}
|
|
3972
|
+
} catch {
|
|
3973
|
+
evList.innerHTML = '<div style="color:var(--muted);font-size:9px;">Failed to load events</div>';
|
|
3974
|
+
}
|
|
3975
|
+
}
|
|
3976
|
+
}
|
|
3977
|
+
|
|
3978
|
+
function fmtDuration(ms) {
|
|
3979
|
+
if (!ms || ms < 0) return '—';
|
|
3980
|
+
const sec = Math.floor(ms / 1000);
|
|
3981
|
+
if (sec < 60) return sec + 's';
|
|
3982
|
+
const min = Math.floor(sec / 60);
|
|
3983
|
+
const remSec = sec % 60;
|
|
3984
|
+
if (min < 60) return min + 'm ' + remSec + 's';
|
|
3985
|
+
const hr = Math.floor(min / 60);
|
|
3986
|
+
return hr + 'h ' + (min % 60) + 'm';
|
|
3987
|
+
}
|
|
3988
|
+
|
|
3989
|
+
function drawSwarmTopology(entry, highlightAgentId) {
|
|
3990
|
+
const wrap = document.getElementById('po-swarm-canvas-wrap');
|
|
3402
3991
|
const canvas = document.getElementById('po-swarm-canvas');
|
|
3403
|
-
|
|
3404
|
-
if (!canvas
|
|
3992
|
+
const label = document.getElementById('po-swarm-topo-label');
|
|
3993
|
+
if (!canvas || !wrap) return;
|
|
3994
|
+
|
|
3995
|
+
if (label) label.textContent = entry.status === 'running' ? 'LIVE TOPOLOGY' : 'TOPOLOGY';
|
|
3996
|
+
|
|
3997
|
+
if (!wrap.offsetWidth || !wrap.offsetHeight) {
|
|
3998
|
+
requestAnimationFrame(() => drawSwarmTopology(entry, highlightAgentId));
|
|
3999
|
+
return;
|
|
4000
|
+
}
|
|
4001
|
+
|
|
3405
4002
|
const dpr = window.devicePixelRatio || 1;
|
|
3406
|
-
canvas.width =
|
|
3407
|
-
canvas.height =
|
|
4003
|
+
canvas.width = wrap.offsetWidth * dpr;
|
|
4004
|
+
canvas.height = wrap.offsetHeight * dpr;
|
|
4005
|
+
canvas.style.width = wrap.offsetWidth + 'px';
|
|
4006
|
+
canvas.style.height = wrap.offsetHeight + 'px';
|
|
3408
4007
|
const ctx = canvas.getContext('2d');
|
|
3409
4008
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
3410
4009
|
const CW = canvas.width, CH = canvas.height;
|
|
3411
4010
|
|
|
3412
|
-
// Grid background
|
|
3413
4011
|
const step = 30 * dpr;
|
|
3414
4012
|
ctx.strokeStyle = 'rgba(58,58,90,0.15)';
|
|
3415
4013
|
ctx.lineWidth = dpr * 0.5;
|
|
3416
|
-
for (let gx = 0; gx <= CW; gx += step) { ctx.beginPath(); ctx.moveTo(gx,0); ctx.lineTo(gx,CH); ctx.stroke(); }
|
|
3417
|
-
for (let gy = 0; gy <= CH; gy += step) { ctx.beginPath(); ctx.moveTo(0,gy); ctx.lineTo(CW,gy); ctx.stroke(); }
|
|
4014
|
+
for (let gx = 0; gx <= CW; gx += step) { ctx.beginPath(); ctx.moveTo(gx, 0); ctx.lineTo(gx, CH); ctx.stroke(); }
|
|
4015
|
+
for (let gy = 0; gy <= CH; gy += step) { ctx.beginPath(); ctx.moveTo(0, gy); ctx.lineTo(CW, gy); ctx.stroke(); }
|
|
4016
|
+
|
|
4017
|
+
const agents = entry.agents || [];
|
|
4018
|
+
const n = agents.length;
|
|
4019
|
+
if (n === 0) {
|
|
4020
|
+
ctx.fillStyle = 'rgba(100,100,140,0.6)';
|
|
4021
|
+
ctx.font = `500 ${9 * dpr}px "Azeret Mono", monospace`;
|
|
4022
|
+
ctx.textAlign = 'center';
|
|
4023
|
+
ctx.fillText('NO AGENTS', CW / 2, CH / 2);
|
|
4024
|
+
return;
|
|
4025
|
+
}
|
|
3418
4026
|
|
|
3419
|
-
const consensusLow = consensus.toLowerCase();
|
|
3420
4027
|
const cx = CW / 2, cy = CH / 2;
|
|
4028
|
+
const r = Math.min(CW, CH) * 0.36;
|
|
4029
|
+
const topo = (entry.topology || '').toLowerCase();
|
|
3421
4030
|
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
if (
|
|
3435
|
-
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
|
|
4031
|
+
const queenIdx = agents.findIndex(a => {
|
|
4032
|
+
const role = (a.role || '').toLowerCase();
|
|
4033
|
+
return role.includes('queen') || role.includes('coordinator') || role.includes('lead');
|
|
4034
|
+
});
|
|
4035
|
+
|
|
4036
|
+
const workers = agents.filter((_, i) => i !== queenIdx);
|
|
4037
|
+
const wn = workers.length;
|
|
4038
|
+
|
|
4039
|
+
const positions = new Array(n);
|
|
4040
|
+
if (queenIdx >= 0) positions[queenIdx] = [cx, cy];
|
|
4041
|
+
let wi = 0;
|
|
4042
|
+
agents.forEach((_, i) => {
|
|
4043
|
+
if (i === queenIdx) return;
|
|
4044
|
+
const angle = (wi / wn) * Math.PI * 2 - Math.PI / 2;
|
|
4045
|
+
positions[i] = [cx + r * Math.cos(angle), cy + r * Math.sin(angle)];
|
|
4046
|
+
wi++;
|
|
4047
|
+
});
|
|
4048
|
+
|
|
4049
|
+
ctx.lineWidth = dpr;
|
|
4050
|
+
if (topo === 'mesh' || topo === 'hierarchical-mesh') {
|
|
4051
|
+
ctx.strokeStyle = 'rgba(0,229,200,0.12)';
|
|
4052
|
+
const workerPositions = agents.map((_, i) => i !== queenIdx ? positions[i] : null).filter(Boolean);
|
|
4053
|
+
for (let i = 0; i < workerPositions.length; i++) {
|
|
4054
|
+
for (let j = i + 1; j <= i + 2 && j < workerPositions.length; j++) {
|
|
4055
|
+
ctx.beginPath(); ctx.moveTo(workerPositions[i][0], workerPositions[i][1]); ctx.lineTo(workerPositions[j][0], workerPositions[j][1]); ctx.stroke();
|
|
3439
4056
|
}
|
|
3440
|
-
} else {
|
|
3441
|
-
positions.forEach(([nx, ny]) => { ctx.beginPath(); ctx.moveTo(cx, cy); ctx.lineTo(nx, ny); ctx.stroke(); });
|
|
3442
4057
|
}
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
4058
|
+
}
|
|
4059
|
+
if (topo === 'ring') {
|
|
4060
|
+
ctx.strokeStyle = 'rgba(0,229,200,0.15)';
|
|
4061
|
+
const workerPositions = agents.map((_, i) => i !== queenIdx ? positions[i] : null).filter(Boolean);
|
|
4062
|
+
for (let i = 0; i < workerPositions.length; i++) {
|
|
4063
|
+
const j = (i + 1) % workerPositions.length;
|
|
4064
|
+
ctx.beginPath(); ctx.moveTo(workerPositions[i][0], workerPositions[i][1]); ctx.lineTo(workerPositions[j][0], workerPositions[j][1]); ctx.stroke();
|
|
4065
|
+
}
|
|
4066
|
+
}
|
|
4067
|
+
if (topo === 'hierarchical' || topo === 'star' || topo === 'hierarchical-mesh') {
|
|
4068
|
+
ctx.strokeStyle = 'rgba(0,229,200,0.15)';
|
|
4069
|
+
agents.forEach((_, i) => {
|
|
4070
|
+
if (i === queenIdx) return;
|
|
4071
|
+
ctx.beginPath(); ctx.moveTo(cx, cy); ctx.lineTo(positions[i][0], positions[i][1]); ctx.stroke();
|
|
3455
4072
|
});
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
4073
|
+
}
|
|
4074
|
+
|
|
4075
|
+
agents.forEach((a, i) => {
|
|
4076
|
+
if (i === queenIdx) return;
|
|
4077
|
+
const [nx, ny] = positions[i];
|
|
4078
|
+
const agentId = a.id || a.type;
|
|
4079
|
+
const isHighlighted = highlightAgentId && agentId === highlightAgentId;
|
|
4080
|
+
|
|
4081
|
+
const nodeR = isHighlighted ? 7 * dpr : 5 * dpr;
|
|
4082
|
+
ctx.beginPath(); ctx.arc(nx, ny, nodeR, 0, Math.PI * 2);
|
|
4083
|
+
if (isHighlighted) {
|
|
4084
|
+
ctx.fillStyle = 'rgba(255,180,0,0.4)'; ctx.fill();
|
|
4085
|
+
ctx.strokeStyle = 'rgba(255,180,0,0.8)'; ctx.lineWidth = dpr * 1.5;
|
|
4086
|
+
} else {
|
|
4087
|
+
ctx.fillStyle = 'rgba(0,229,200,0.3)'; ctx.fill();
|
|
4088
|
+
ctx.strokeStyle = 'rgba(0,229,200,0.6)'; ctx.lineWidth = dpr;
|
|
4089
|
+
}
|
|
4090
|
+
ctx.stroke();
|
|
4091
|
+
|
|
4092
|
+
const lbl = (a.type || '?').slice(0, 8).toUpperCase();
|
|
4093
|
+
ctx.fillStyle = isHighlighted ? 'rgba(255,180,0,0.7)' : 'rgba(180,180,220,0.6)';
|
|
4094
|
+
ctx.font = `${7 * dpr}px "Azeret Mono", monospace`;
|
|
4095
|
+
ctx.textAlign = 'center';
|
|
4096
|
+
ctx.fillText(lbl, nx, ny + 10 * dpr);
|
|
4097
|
+
});
|
|
4098
|
+
|
|
4099
|
+
if (queenIdx >= 0) {
|
|
4100
|
+
const queen = agents[queenIdx];
|
|
4101
|
+
const queenId = queen.id || queen.type;
|
|
4102
|
+
const isHighlighted = highlightAgentId && queenId === highlightAgentId;
|
|
4103
|
+
const qR = isHighlighted ? 11 * dpr : 9 * dpr;
|
|
4104
|
+
ctx.beginPath(); ctx.arc(cx, cy, qR, 0, Math.PI * 2);
|
|
4105
|
+
if (isHighlighted) {
|
|
4106
|
+
ctx.fillStyle = 'rgba(255,180,0,0.7)'; ctx.fill();
|
|
4107
|
+
ctx.strokeStyle = 'rgba(255,180,0,1)';
|
|
4108
|
+
} else {
|
|
4109
|
+
ctx.fillStyle = 'rgba(0,229,200,0.7)'; ctx.fill();
|
|
4110
|
+
ctx.strokeStyle = 'rgba(0,229,200,1)';
|
|
4111
|
+
}
|
|
4112
|
+
ctx.lineWidth = dpr * 2; ctx.stroke();
|
|
3460
4113
|
ctx.fillStyle = '#fff';
|
|
3461
4114
|
ctx.font = `bold ${8 * dpr}px "Azeret Mono", monospace`;
|
|
3462
4115
|
ctx.textAlign = 'center'; ctx.textBaseline = 'middle';
|
|
3463
4116
|
ctx.fillText('Q', cx, cy);
|
|
3464
4117
|
ctx.textBaseline = 'alphabetic';
|
|
3465
|
-
}
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
4118
|
+
}
|
|
4119
|
+
}
|
|
4120
|
+
|
|
4121
|
+
async function selectSwarmAgent(agentId) {
|
|
4122
|
+
_swarmSelectedAgentId = agentId;
|
|
4123
|
+
|
|
4124
|
+
const entry = _swarmSelectedIdx === 'live'
|
|
4125
|
+
? _buildLiveEntry()
|
|
4126
|
+
: _swarmHistoryEntries[_swarmSelectedIdx];
|
|
4127
|
+
if (!entry) return;
|
|
4128
|
+
|
|
4129
|
+
document.querySelectorAll('.po-swarm-agent-item').forEach(el => {
|
|
4130
|
+
el.classList.toggle('selected', el.dataset.agentId === agentId);
|
|
4131
|
+
});
|
|
4132
|
+
|
|
4133
|
+
const wrap = document.getElementById('po-swarm-canvas-wrap');
|
|
4134
|
+
if (wrap) wrap.classList.add('shrunk');
|
|
4135
|
+
|
|
4136
|
+
drawSwarmTopology(entry, agentId);
|
|
4137
|
+
|
|
4138
|
+
const agent = (entry.agents || []).find(a => (a.id || a.type) === agentId);
|
|
4139
|
+
if (!agent) return;
|
|
4140
|
+
|
|
4141
|
+
const drawer = document.getElementById('po-swarm-agent-drawer');
|
|
4142
|
+
const info = document.getElementById('po-swarm-agent-info');
|
|
4143
|
+
const timeline = document.getElementById('po-swarm-agent-timeline');
|
|
4144
|
+
if (!drawer) return;
|
|
4145
|
+
drawer.style.display = '';
|
|
4146
|
+
|
|
4147
|
+
const isQueen = (agent.role || '').toLowerCase().includes('queen') || (agent.role || '').toLowerCase().includes('coordinator') || (agent.role || '').toLowerCase().includes('lead');
|
|
4148
|
+
if (info) {
|
|
4149
|
+
info.innerHTML = `
|
|
4150
|
+
<span style="color:var(--teal);font-weight:700;">${escHtml(agent.type || '?')}</span>
|
|
4151
|
+
<span style="color:var(--muted);">${isQueen ? 'QUEEN' : 'WORKER'}</span>
|
|
4152
|
+
<span><span style="color:var(--muted);">TASKS</span> <span style="color:var(--teal);">${agent.tasksCompleted || 0}/${(agent.tasksCompleted || 0) + (agent.tasksFailed || 0)}</span></span>
|
|
4153
|
+
<span><span style="color:var(--muted);">MSGS</span> <span style="color:var(--teal);">${agent.messageCount || 0}</span></span>
|
|
4154
|
+
<span><span style="color:var(--muted);">UTIL</span> <span style="color:#4caf50;">${agent.utilization || 0}%</span></span>
|
|
4155
|
+
`;
|
|
4156
|
+
}
|
|
4157
|
+
|
|
4158
|
+
if (timeline) {
|
|
4159
|
+
// Fetch events from API for this agent
|
|
4160
|
+
const dir = selectedProjectDir || '';
|
|
4161
|
+
let events = [];
|
|
4162
|
+
try {
|
|
4163
|
+
const evr = await fetch(`/api/swarm-events?agentId=${encodeURIComponent(agentId)}${dir ? '&dir=' + encodeURIComponent(dir) : ''}`);
|
|
4164
|
+
if (evr.ok) { const ed = await evr.json(); events = ed.events || []; }
|
|
4165
|
+
} catch {}
|
|
4166
|
+
|
|
4167
|
+
// Also include entry-level messages (legacy)
|
|
4168
|
+
const msgs = (entry.messages || []).filter(m => m.from === agentId || m.to === agentId);
|
|
4169
|
+
|
|
4170
|
+
if (events.length === 0 && msgs.length === 0) {
|
|
4171
|
+
timeline.innerHTML = '<div style="color:var(--muted);font-size:9px;padding:4px;">No communication recorded</div>';
|
|
3483
4172
|
} else {
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
|
|
4173
|
+
let html = '';
|
|
4174
|
+
// Render events from events.jsonl
|
|
4175
|
+
if (events.length > 0) {
|
|
4176
|
+
html += events.map(ev => {
|
|
4177
|
+
const ts = ev.ts ? new Date(ev.ts).toTimeString().slice(0, 8) : '—';
|
|
4178
|
+
const kind = ev.kind || '?';
|
|
4179
|
+
const detail = ev.message || ev.task || ev.topology || '';
|
|
4180
|
+
const isSent = kind.includes('spawn') || kind.includes('init') || kind.includes('broadcast');
|
|
4181
|
+
return `<div class="po-swarm-msg ${isSent ? 'sent' : 'received'}">
|
|
4182
|
+
<span class="po-swarm-msg-ts">${escHtml(ts)}</span>
|
|
4183
|
+
<span class="po-swarm-msg-type">${escHtml(kind)}</span>
|
|
4184
|
+
${detail ? `<span class="po-swarm-msg-payload">${escHtml(String(detail).slice(0, 120))}</span>` : ''}
|
|
4185
|
+
</div>`;
|
|
4186
|
+
}).join('');
|
|
4187
|
+
}
|
|
4188
|
+
// Render legacy messages
|
|
4189
|
+
if (msgs.length > 0) {
|
|
4190
|
+
html += msgs.sort((a, b) => (a.timestamp || 0) - (b.timestamp || 0)).map(m => {
|
|
4191
|
+
const isSent = m.from === agentId;
|
|
4192
|
+
const dir = isSent ? '→' : '←';
|
|
4193
|
+
const peer = isSent ? m.to : m.from;
|
|
4194
|
+
const ts = m.timestamp ? new Date(m.timestamp).toTimeString().slice(0, 8) : '—';
|
|
4195
|
+
const payload = (m.payload || '').slice(0, 100);
|
|
4196
|
+
return `<div class="po-swarm-msg ${isSent ? 'sent' : 'received'}">
|
|
4197
|
+
<span class="po-swarm-msg-ts">${escHtml(ts)}</span>
|
|
4198
|
+
${escHtml(dir)} ${escHtml(peer || 'broadcast')}:
|
|
4199
|
+
<span class="po-swarm-msg-type">${escHtml(m.type || '?')}</span>
|
|
4200
|
+
${payload ? `<span class="po-swarm-msg-payload">${escHtml(payload)}</span>` : ''}
|
|
4201
|
+
</div>`;
|
|
4202
|
+
}).join('');
|
|
4203
|
+
}
|
|
4204
|
+
timeline.innerHTML = html;
|
|
3513
4205
|
}
|
|
3514
4206
|
}
|
|
3515
4207
|
}
|
|
3516
4208
|
|
|
4209
|
+
function _buildLiveEntry() {
|
|
4210
|
+
const sw = (appData && appData.swarm) ? appData.swarm : {};
|
|
4211
|
+
const state = sw.state || {};
|
|
4212
|
+
return {
|
|
4213
|
+
swarmId: state.swarmId || state.id || 'live',
|
|
4214
|
+
topology: state.topology || sw.config?.topology || '—',
|
|
4215
|
+
consensus: state.consensus || sw.config?.consensus || '—',
|
|
4216
|
+
status: state.status || 'running',
|
|
4217
|
+
agents: (state.agents || state.agentPlan || []).map(a => ({
|
|
4218
|
+
id: a.id || a.type || a.role,
|
|
4219
|
+
type: a.type || a.role || '?',
|
|
4220
|
+
role: a.role || 'worker',
|
|
4221
|
+
tasksCompleted: a.tasksCompleted || 0,
|
|
4222
|
+
tasksFailed: a.tasksFailed || 0,
|
|
4223
|
+
messageCount: a.messageCount || 0,
|
|
4224
|
+
utilization: a.utilization || 0,
|
|
4225
|
+
})),
|
|
4226
|
+
messages: state.messages || [],
|
|
4227
|
+
errors: state.errors || [],
|
|
4228
|
+
startedAt: state.startedAt || state.createdAt,
|
|
4229
|
+
endedAt: null,
|
|
4230
|
+
durationMs: state.startedAt ? Date.now() - new Date(state.startedAt).getTime() : 0,
|
|
4231
|
+
};
|
|
4232
|
+
}
|
|
4233
|
+
|
|
4234
|
+
function closeSwarmAgent() {
|
|
4235
|
+
_swarmSelectedAgentId = null;
|
|
4236
|
+
const drawer = document.getElementById('po-swarm-agent-drawer');
|
|
4237
|
+
if (drawer) drawer.style.display = 'none';
|
|
4238
|
+
const wrap = document.getElementById('po-swarm-canvas-wrap');
|
|
4239
|
+
if (wrap) wrap.classList.remove('shrunk');
|
|
4240
|
+
document.querySelectorAll('.po-swarm-agent-item').forEach(el => el.classList.remove('selected'));
|
|
4241
|
+
|
|
4242
|
+
const entry = _swarmSelectedIdx === 'live'
|
|
4243
|
+
? _buildLiveEntry()
|
|
4244
|
+
: _swarmHistoryEntries[_swarmSelectedIdx];
|
|
4245
|
+
if (entry) drawSwarmTopology(entry, null);
|
|
4246
|
+
}
|
|
4247
|
+
|
|
3517
4248
|
async function renderPalaceKnowledge() {
|
|
3518
4249
|
const statsEl = document.getElementById('po-knowledge-stats');
|
|
3519
4250
|
const bodyEl = document.getElementById('po-knowledge-body');
|
|
@@ -4020,194 +4751,126 @@ window.selectTemplate = function(type) {
|
|
|
4020
4751
|
setTimeout(() => document.getElementById('po-edit-textarea').focus(), 50);
|
|
4021
4752
|
};
|
|
4022
4753
|
|
|
4023
|
-
// Agent Graph renderer —
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
|
|
4030
|
-
|
|
4031
|
-
|
|
4032
|
-
|
|
4033
|
-
|
|
4034
|
-
|
|
4035
|
-
|
|
4036
|
-
|
|
4037
|
-
|
|
4038
|
-
|
|
4039
|
-
|
|
4040
|
-
|
|
4041
|
-
|
|
4042
|
-
|
|
4043
|
-
|
|
4044
|
-
|
|
4045
|
-
|
|
4046
|
-
|
|
4047
|
-
|
|
4048
|
-
|
|
4049
|
-
|
|
4050
|
-
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
const
|
|
4059
|
-
|
|
4060
|
-
|
|
4061
|
-
sessionNodes.forEach((n, i) => {
|
|
4062
|
-
const angle = (i / Math.max(sessionNodes.length, 1)) * Math.PI * 2 - Math.PI / 2;
|
|
4063
|
-
n._x = cx + Math.cos(angle) * innerR;
|
|
4064
|
-
n._y = cy + Math.sin(angle) * innerR;
|
|
4065
|
-
n._size = 4 + (n.totalTools / maxTools) * 9;
|
|
4066
|
-
});
|
|
4067
|
-
|
|
4068
|
-
// Agent type nodes on outer ring, sized by spawn count
|
|
4069
|
-
agentNodes.forEach((n, i) => {
|
|
4070
|
-
const angle = (i / Math.max(agentNodes.length, 1)) * Math.PI * 2 - Math.PI / 2;
|
|
4071
|
-
n._x = cx + Math.cos(angle) * outerR;
|
|
4072
|
-
n._y = cy + Math.sin(angle) * outerR;
|
|
4073
|
-
n._size = 3 + (n.totalSpawns / maxSpawns) * 7;
|
|
4074
|
-
});
|
|
4075
|
-
|
|
4076
|
-
nodes = [...sessionNodes, ...agentNodes];
|
|
4077
|
-
const nodeIds = new Set(nodes.map(n => n.id));
|
|
4078
|
-
edges = graphData.edges.filter(e => nodeIds.has(e.source) && nodeIds.has(e.target));
|
|
4079
|
-
|
|
4080
|
-
const totalSpawns = agentNodes.reduce((s, n) => s + (n.totalSpawns || 0), 0);
|
|
4081
|
-
const info = document.getElementById('po-graph-info');
|
|
4082
|
-
if (info) info.textContent = `${sessionNodes.length} sessions · ${agentNodes.length} agent types · ${totalSpawns} total spawns`;
|
|
4083
|
-
|
|
4084
|
-
if (animRAF) cancelAnimationFrame(animRAF);
|
|
4085
|
-
draw();
|
|
4086
|
-
animRAF = requestAnimationFrame(function loop() {
|
|
4087
|
-
if (palaceCurrentTab !== 'graph') { animRAF = null; return; }
|
|
4088
|
-
const prev = hoveredNode;
|
|
4089
|
-
hoveredNode = null;
|
|
4090
|
-
for (const n of nodes) {
|
|
4091
|
-
const dx = n._x - mouseX, dy = n._y - mouseY;
|
|
4092
|
-
const r = (n._size || 5) + 4;
|
|
4093
|
-
if (dx*dx + dy*dy < r*r) { hoveredNode = n; break; }
|
|
4094
|
-
}
|
|
4095
|
-
if (hoveredNode !== prev) draw();
|
|
4096
|
-
animRAF = requestAnimationFrame(loop);
|
|
4097
|
-
});
|
|
4098
|
-
}
|
|
4099
|
-
|
|
4100
|
-
function draw() {
|
|
4101
|
-
if (!canvas || !ctx) return;
|
|
4102
|
-
const W = canvas.width, H = canvas.height;
|
|
4103
|
-
if (!W || !H) return;
|
|
4104
|
-
ctx.clearRect(0, 0, W, H);
|
|
4105
|
-
const cx = W/2, cy = H/2;
|
|
4106
|
-
const innerR = Math.min(W,H)*0.22, outerR = Math.min(W,H)*0.40;
|
|
4107
|
-
|
|
4108
|
-
// Guide rings
|
|
4109
|
-
ctx.beginPath(); ctx.arc(cx, cy, innerR, 0, Math.PI*2);
|
|
4110
|
-
ctx.strokeStyle = 'rgba(0,229,200,0.06)'; ctx.lineWidth = 1; ctx.stroke();
|
|
4111
|
-
ctx.beginPath(); ctx.arc(cx, cy, outerR, 0, Math.PI*2);
|
|
4112
|
-
ctx.strokeStyle = 'rgba(255,179,71,0.06)'; ctx.lineWidth = 1; ctx.stroke();
|
|
4113
|
-
|
|
4114
|
-
// Ring labels at top
|
|
4115
|
-
ctx.font = '8px monospace'; ctx.textAlign = 'center';
|
|
4116
|
-
ctx.fillStyle = 'rgba(0,229,200,0.3)';
|
|
4117
|
-
ctx.fillText('SESSIONS', cx, cy - innerR - 6);
|
|
4118
|
-
ctx.fillStyle = 'rgba(255,179,71,0.3)';
|
|
4119
|
-
ctx.fillText('AGENT TYPES', cx, cy - outerR - 6);
|
|
4120
|
-
|
|
4121
|
-
// Edges
|
|
4122
|
-
const maxW = Math.max(...edges.map(e => e.weight || 1), 1);
|
|
4123
|
-
const idToNode = {};
|
|
4124
|
-
for (const n of nodes) idToNode[n.id] = n;
|
|
4125
|
-
for (const e of edges) {
|
|
4126
|
-
const a = idToNode[e.source], b = idToNode[e.target];
|
|
4127
|
-
if (!a || !b) continue;
|
|
4128
|
-
const hl = hoveredNode && (hoveredNode.id === e.source || hoveredNode.id === e.target);
|
|
4129
|
-
const alpha = 0.05 + (e.weight / maxW) * 0.22;
|
|
4130
|
-
ctx.beginPath(); ctx.moveTo(a._x, a._y); ctx.lineTo(b._x, b._y);
|
|
4131
|
-
ctx.strokeStyle = hl ? 'rgba(0,229,200,0.65)' : `rgba(120,128,168,${alpha.toFixed(2)})`;
|
|
4132
|
-
ctx.lineWidth = hl ? Math.max(1, (e.weight / maxW) * 3) : Math.max(0.4, (e.weight / maxW) * 1.5);
|
|
4133
|
-
ctx.stroke();
|
|
4134
|
-
if (showLabels && hl) {
|
|
4135
|
-
ctx.font = '8px monospace'; ctx.fillStyle = 'rgba(0,229,200,0.9)'; ctx.textAlign = 'center';
|
|
4136
|
-
ctx.fillText('×' + e.label, (a._x+b._x)/2, (a._y+b._y)/2 - 4);
|
|
4137
|
-
}
|
|
4138
|
-
}
|
|
4139
|
-
|
|
4140
|
-
// Nodes
|
|
4141
|
-
for (const n of nodes) {
|
|
4142
|
-
const isHov = hoveredNode && hoveredNode.id === n.id;
|
|
4143
|
-
const isSession = n.type === 'session';
|
|
4144
|
-
const r = (n._size || 5) + (isHov ? 2 : 0);
|
|
4145
|
-
if (isHov) { ctx.shadowBlur = 18; ctx.shadowColor = isSession ? '#00E5C8' : '#FFB347'; }
|
|
4146
|
-
ctx.beginPath(); ctx.arc(n._x, n._y, r, 0, Math.PI*2);
|
|
4147
|
-
ctx.fillStyle = isSession
|
|
4148
|
-
? (isHov ? '#00E5C8' : 'rgba(0,229,200,0.82)')
|
|
4149
|
-
: (isHov ? '#FFB347' : 'rgba(255,179,71,0.78)');
|
|
4150
|
-
ctx.fill(); ctx.shadowBlur = 0;
|
|
4151
|
-
if (showLabels) {
|
|
4152
|
-
const label = (n.label || n.id).length > 18 ? (n.label || n.id).slice(0, 16) + '…' : (n.label || n.id);
|
|
4153
|
-
ctx.font = isHov ? '10px monospace' : '8px monospace';
|
|
4154
|
-
ctx.fillStyle = isHov ? 'rgba(255,255,255,0.95)'
|
|
4155
|
-
: (isSession ? 'rgba(0,229,200,0.65)' : 'rgba(255,179,71,0.6)');
|
|
4156
|
-
ctx.textAlign = 'center';
|
|
4157
|
-
ctx.fillText(label, n._x, n._y + r + 11);
|
|
4158
|
-
}
|
|
4159
|
-
}
|
|
4160
|
-
|
|
4161
|
-
// Tooltip
|
|
4162
|
-
if (hoveredNode) {
|
|
4163
|
-
const n = hoveredNode;
|
|
4164
|
-
const isSession = n.type === 'session';
|
|
4165
|
-
const lines = [isSession ? n.label + ' (session)' : n.label + ' (agent type)'];
|
|
4166
|
-
if (isSession) {
|
|
4167
|
-
lines.push(`${n.turns || 0} turns · ${n.totalTools || 0} tools · $${(n.cost || 0).toFixed(3)}`);
|
|
4168
|
-
const ts = n.mtime ? new Date(n.mtime).toLocaleDateString() : '';
|
|
4169
|
-
if (ts) lines.push(ts);
|
|
4170
|
-
const spawned = Object.entries(n.agentSpawns || {}).sort((a,b) => b[1]-a[1]).slice(0, 5);
|
|
4171
|
-
spawned.forEach(([k,v]) => lines.push(`→ ${k.slice(0,22)} ×${v}`));
|
|
4172
|
-
} else {
|
|
4173
|
-
lines.push(`${n.totalSpawns} total spawns`);
|
|
4174
|
-
const ne = edges.filter(e => e.source === n.id || e.target === n.id).slice(0, 5);
|
|
4175
|
-
ne.forEach(e => {
|
|
4176
|
-
const other = e.source === n.id ? e.target : e.source;
|
|
4177
|
-
lines.push(`${other.slice(0, 8)}… ×${e.weight}`);
|
|
4178
|
-
});
|
|
4179
|
-
}
|
|
4180
|
-
const tw = 290, th = lines.length * 14 + 16;
|
|
4181
|
-
let tx = n._x + 14, ty = n._y - th / 2;
|
|
4182
|
-
if (tx + tw > W - 4) tx = n._x - tw - 14;
|
|
4183
|
-
if (ty < 4) ty = 4;
|
|
4184
|
-
if (ty + th > H - 4) ty = H - th - 4;
|
|
4185
|
-
ctx.fillStyle = 'rgba(7,8,15,0.95)'; ctx.strokeStyle = 'rgba(0,229,200,0.28)'; ctx.lineWidth = 1;
|
|
4186
|
-
roundRect(ctx, tx, ty, tw, th, 4); ctx.fill(); ctx.stroke();
|
|
4187
|
-
ctx.font = '9px monospace'; ctx.textAlign = 'left';
|
|
4188
|
-
lines.forEach((l, i) => {
|
|
4189
|
-
ctx.fillStyle = i === 0 ? 'rgba(255,255,255,0.92)'
|
|
4190
|
-
: i === 1 ? (isSession ? 'rgba(0,229,200,0.65)' : 'rgba(255,179,71,0.65)')
|
|
4191
|
-
: 'rgba(212,212,232,0.8)';
|
|
4192
|
-
ctx.fillText(l, tx + 8, ty + 13 + i * 14);
|
|
4193
|
-
});
|
|
4194
|
-
}
|
|
4754
|
+
// Agent Graph renderer — HTML two-pane: session list on left, details on right
|
|
4755
|
+
let _agGraphData = null;
|
|
4756
|
+
|
|
4757
|
+
function renderAgentGraph(graphData) {
|
|
4758
|
+
_agGraphData = graphData;
|
|
4759
|
+
|
|
4760
|
+
const sessionNodes = (graphData.nodes || []).filter(n => n.type === 'session')
|
|
4761
|
+
.sort((a, b) => (b.mtime || 0) - (a.mtime || 0));
|
|
4762
|
+
const agentTypeNodes = (graphData.nodes || []).filter(n => n.type === 'agenttype')
|
|
4763
|
+
.sort((a, b) => (b.totalSpawns || 0) - (a.totalSpawns || 0));
|
|
4764
|
+
|
|
4765
|
+
const countEl = document.getElementById('po-ag-session-count');
|
|
4766
|
+
if (countEl) countEl.textContent = sessionNodes.length + ' sessions';
|
|
4767
|
+
|
|
4768
|
+
const totalTools = sessionNodes.reduce((s, n) => s + (n.totalTools || 0), 0);
|
|
4769
|
+
const totalCost = sessionNodes.reduce((s, n) => s + (n.cost || 0), 0);
|
|
4770
|
+
const totalTurns = sessionNodes.reduce((s, n) => s + (n.turns || 0), 0);
|
|
4771
|
+
const totalSpawns = agentTypeNodes.reduce((s, n) => s + (n.totalSpawns || 0), 0);
|
|
4772
|
+
const summaryBar = document.getElementById('po-ag-summary-bar');
|
|
4773
|
+
if (summaryBar) {
|
|
4774
|
+
summaryBar.innerHTML = [
|
|
4775
|
+
[sessionNodes.length, 'SESSIONS'],
|
|
4776
|
+
[agentTypeNodes.length, 'AGENT TYPES'],
|
|
4777
|
+
[totalSpawns, 'SPAWNS'],
|
|
4778
|
+
[totalTools, 'TOOL CALLS'],
|
|
4779
|
+
[totalTurns, 'TURNS'],
|
|
4780
|
+
['$' + totalCost.toFixed(3), 'COST'],
|
|
4781
|
+
].map(([v, l]) => `<div class="po-ag-stat"><span class="po-ag-stat-val">${v}</span><span class="po-ag-stat-lbl">${l}</span></div>`).join('');
|
|
4782
|
+
}
|
|
4783
|
+
|
|
4784
|
+
const listEl = document.getElementById('po-ag-session-list');
|
|
4785
|
+
if (!listEl) return;
|
|
4786
|
+
|
|
4787
|
+
if (!sessionNodes.length) {
|
|
4788
|
+
listEl.innerHTML = '<div style="padding:16px 12px;color:var(--dim);font-size:9px;text-align:center;">NO SESSIONS RECORDED</div>';
|
|
4789
|
+
const content = document.getElementById('po-ag-content');
|
|
4790
|
+
if (content) content.innerHTML = '<div class="po-select-hint">SELECT A SESSION</div>';
|
|
4791
|
+
return;
|
|
4195
4792
|
}
|
|
4196
4793
|
|
|
4197
|
-
|
|
4198
|
-
|
|
4199
|
-
|
|
4200
|
-
|
|
4201
|
-
|
|
4202
|
-
|
|
4203
|
-
|
|
4204
|
-
|
|
4794
|
+
listEl.innerHTML = sessionNodes.map(n => {
|
|
4795
|
+
const dt = n.mtime ? new Date(n.mtime) : null;
|
|
4796
|
+
const dateStr = dt ? dt.toLocaleDateString(undefined, { month: 'short', day: 'numeric' }) : '';
|
|
4797
|
+
const spawns = Object.values(n.agentSpawns || {}).reduce((s, v) => s + v, 0);
|
|
4798
|
+
return `<div class="po-ag-sess" data-id="${n.id}" onclick="selectAgentSession('${n.id}')">
|
|
4799
|
+
<div style="display:flex;justify-content:space-between;align-items:baseline;gap:4px;">
|
|
4800
|
+
<div class="po-ag-sess-id">${(n.label || n.id).slice(0, 20)}</div>
|
|
4801
|
+
<div style="font-size:8px;color:var(--dim);white-space:nowrap;">${dateStr}</div>
|
|
4802
|
+
</div>
|
|
4803
|
+
<div style="display:flex;gap:8px;margin-top:2px;">
|
|
4804
|
+
<span style="font-size:8px;color:var(--teal);">${n.totalTools || 0} tools</span>
|
|
4805
|
+
${spawns > 0 ? `<span style="font-size:8px;color:var(--amber);">${spawns} spawns</span>` : ''}
|
|
4806
|
+
${(n.cost || 0) > 0 ? `<span style="font-size:8px;color:var(--dim);">$${(n.cost || 0).toFixed(3)}</span>` : ''}
|
|
4807
|
+
</div>
|
|
4808
|
+
</div>`;
|
|
4809
|
+
}).join('');
|
|
4205
4810
|
|
|
4206
|
-
|
|
4207
|
-
|
|
4811
|
+
const content = document.getElementById('po-ag-content');
|
|
4812
|
+
if (content) content.innerHTML = '<div class="po-select-hint">SELECT A SESSION</div>';
|
|
4813
|
+
}
|
|
4208
4814
|
|
|
4209
|
-
|
|
4210
|
-
|
|
4815
|
+
function selectAgentSession(id) {
|
|
4816
|
+
if (!_agGraphData) return;
|
|
4817
|
+
const n = _agGraphData.nodes.find(x => x.id === id);
|
|
4818
|
+
if (!n) return;
|
|
4819
|
+
|
|
4820
|
+
document.querySelectorAll('#po-ag-session-list .po-ag-sess').forEach(el =>
|
|
4821
|
+
el.classList.toggle('active', el.dataset.id === id));
|
|
4822
|
+
|
|
4823
|
+
const agentSpawns = n.agentSpawns || {};
|
|
4824
|
+
const toolCounts = n.toolCounts || {};
|
|
4825
|
+
const totalSpawns = Object.values(agentSpawns).reduce((s, v) => s + v, 0);
|
|
4826
|
+
const maxSpawn = Math.max(...Object.values(agentSpawns).concat([1]));
|
|
4827
|
+
const maxTool = Math.max(...Object.values(toolCounts).concat([1]));
|
|
4828
|
+
const dt = n.mtime ? new Date(n.mtime) : null;
|
|
4829
|
+
const dateStr = dt ? dt.toLocaleString() : '';
|
|
4830
|
+
|
|
4831
|
+
const spawnEntries = Object.entries(agentSpawns).sort((a, b) => b[1] - a[1]);
|
|
4832
|
+
const toolEntries = Object.entries(toolCounts).sort((a, b) => b[1] - a[1]);
|
|
4833
|
+
|
|
4834
|
+
const content = document.getElementById('po-ag-content');
|
|
4835
|
+
if (!content) return;
|
|
4836
|
+
|
|
4837
|
+
content.innerHTML = `
|
|
4838
|
+
<div>
|
|
4839
|
+
<div class="po-ag-sess-title">${n.label || n.id}</div>
|
|
4840
|
+
<div class="po-ag-sess-subtitle">${dateStr}</div>
|
|
4841
|
+
<div style="display:flex;gap:14px;margin-top:8px;flex-wrap:wrap;">
|
|
4842
|
+
<span style="font-size:9px;"><span style="color:var(--teal)">${n.turns || 0}</span> <span style="color:var(--dim)">turns</span></span>
|
|
4843
|
+
<span style="font-size:9px;"><span style="color:var(--amber)">${totalSpawns}</span> <span style="color:var(--dim)">spawns</span></span>
|
|
4844
|
+
<span style="font-size:9px;"><span style="color:var(--teal)">${n.totalTools || 0}</span> <span style="color:var(--dim)">tool calls</span></span>
|
|
4845
|
+
${(n.cost || 0) > 0 ? `<span style="font-size:9px;color:var(--muted);">$${(n.cost || 0).toFixed(4)}</span>` : ''}
|
|
4846
|
+
</div>
|
|
4847
|
+
</div>
|
|
4848
|
+
${spawnEntries.length > 0 ? `
|
|
4849
|
+
<div>
|
|
4850
|
+
<div class="po-ag-section-lbl">AGENT TYPES SPAWNED</div>
|
|
4851
|
+
${spawnEntries.map(([type, count]) => `
|
|
4852
|
+
<div class="po-ag-agent-card">
|
|
4853
|
+
<div class="po-ag-spawn-row">
|
|
4854
|
+
<span class="po-ag-agent-type">${type}</span>
|
|
4855
|
+
<div class="po-ag-bar-wrap"><div class="po-ag-bar-fill" style="width:${Math.round(count/maxSpawn*100)}%"></div></div>
|
|
4856
|
+
<span class="po-ag-spawn-count">×${count}</span>
|
|
4857
|
+
</div>
|
|
4858
|
+
</div>
|
|
4859
|
+
`).join('')}
|
|
4860
|
+
</div>` : ''}
|
|
4861
|
+
${toolEntries.length > 0 ? `
|
|
4862
|
+
<div>
|
|
4863
|
+
<div class="po-ag-section-lbl">TOOL USAGE (top ${Math.min(toolEntries.length, 15)})</div>
|
|
4864
|
+
${toolEntries.slice(0, 15).map(([tool, count]) => `
|
|
4865
|
+
<div class="po-ag-tool-row">
|
|
4866
|
+
<span class="po-ag-tool-name">${tool.slice(0, 18)}</span>
|
|
4867
|
+
<div class="po-ag-tool-bar-wrap"><div class="po-ag-tool-bar-fill" style="width:${Math.round(count/maxTool*100)}%"></div></div>
|
|
4868
|
+
<span class="po-ag-tool-count">${count}</span>
|
|
4869
|
+
</div>
|
|
4870
|
+
`).join('')}
|
|
4871
|
+
</div>` : '<div style="font-size:9px;color:var(--dim);text-align:center;padding:20px 0;">NO TOOL DATA</div>'}
|
|
4872
|
+
`;
|
|
4873
|
+
}
|
|
4211
4874
|
|
|
4212
4875
|
// ══════════════════ CODE KNOWLEDGE GRAPH — force-directed ══════════════════
|
|
4213
4876
|
const kgCodeGraph = (function() {
|
|
@@ -4388,7 +5051,6 @@ window.openPalaceOverlay = async function() {
|
|
|
4388
5051
|
// Stop KG graph animations when overlay closes
|
|
4389
5052
|
const _origClose = window.closePalaceOverlay;
|
|
4390
5053
|
window.closePalaceOverlay = function() {
|
|
4391
|
-
kgGraph.stop();
|
|
4392
5054
|
kgCodeGraph.stop();
|
|
4393
5055
|
_origClose();
|
|
4394
5056
|
};
|