monomind 2.1.0 → 2.1.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/README.md +74 -92
- package/package.json +3 -3
- package/packages/@monomind/cli/.claude/helpers/handlers/capture-handler.cjs +24 -10
- package/packages/@monomind/cli/.claude/helpers/handlers/gates-handler.cjs +2 -2
- package/packages/@monomind/cli/.claude/helpers/handlers/route-handler.cjs +29 -0
- package/packages/@monomind/cli/.claude/helpers/handlers/session-handler.cjs +35 -23
- package/packages/@monomind/cli/.claude/helpers/hook-handler.cjs +21 -0
- package/packages/@monomind/cli/.claude/helpers/utils/monograph.cjs +73 -2
- package/packages/@monomind/cli/README.md +74 -92
- package/packages/@monomind/cli/dist/src/browser/workflow/store.d.ts +2 -2
- package/packages/@monomind/cli/dist/src/commands/browse.d.ts +1 -1
- package/packages/@monomind/cli/dist/src/commands/doctor-env-checks.js +5 -0
- package/packages/@monomind/cli/dist/src/commands/org.js +46 -29
- package/packages/@monomind/cli/dist/src/commands/platforms.d.ts +1 -1
- package/packages/@monomind/cli/dist/src/init/executor.js +30 -6
- package/packages/@monomind/cli/dist/src/mcp-tools/auto-install.d.ts +8 -8
- package/packages/@monomind/cli/dist/src/mcp-tools/coherence/types.d.ts +31 -31
- package/packages/@monomind/cli/dist/src/mcp-tools/hive-mind-tools.js +5 -1
- package/packages/@monomind/cli/dist/src/mcp-tools/monograph-tools.js +28 -3
- package/packages/@monomind/cli/dist/src/mcp-tools/quality/coverage-analysis/prioritize-gaps.d.ts +36 -36
- package/packages/@monomind/cli/dist/src/mcp-tools/quality/security-compliance/detect-secrets.d.ts +4 -4
- package/packages/@monomind/cli/dist/src/memory/ewc-consolidation.d.ts +12 -0
- package/packages/@monomind/cli/dist/src/memory/sona-optimizer.d.ts +12 -0
- package/packages/@monomind/cli/dist/src/orgrt/broker.d.ts +3 -1
- package/packages/@monomind/cli/dist/src/orgrt/broker.js +8 -3
- package/packages/@monomind/cli/dist/src/orgrt/bus.d.ts +5 -1
- package/packages/@monomind/cli/dist/src/orgrt/bus.js +5 -1
- package/packages/@monomind/cli/dist/src/orgrt/daemon.d.ts +17 -1
- package/packages/@monomind/cli/dist/src/orgrt/daemon.js +115 -21
- package/packages/@monomind/cli/dist/src/orgrt/forwarder.js +41 -8
- package/packages/@monomind/cli/dist/src/orgrt/inbox.d.ts +11 -0
- package/packages/@monomind/cli/dist/src/orgrt/inbox.js +56 -0
- package/packages/@monomind/cli/dist/src/orgrt/policy.d.ts +5 -1
- package/packages/@monomind/cli/dist/src/orgrt/policy.js +58 -5
- package/packages/@monomind/cli/dist/src/orgrt/server.d.ts +3 -2
- package/packages/@monomind/cli/dist/src/orgrt/server.js +7 -40
- package/packages/@monomind/cli/dist/src/orgrt/session.d.ts +10 -1
- package/packages/@monomind/cli/dist/src/orgrt/session.js +24 -1
- package/packages/@monomind/cli/dist/src/orgrt/test-loop.js +8 -8
- package/packages/@monomind/cli/dist/src/orgrt/types.d.ts +52 -52
- package/packages/@monomind/cli/dist/src/output.d.ts +29 -29
- package/packages/@monomind/cli/dist/src/output.js +10 -2
- package/packages/@monomind/cli/dist/src/types.d.ts +2 -2
- package/packages/@monomind/cli/dist/src/ui/dashboard.html +203 -9
- package/packages/@monomind/cli/dist/src/ui/orgs-files.js +192 -0
- package/packages/@monomind/cli/dist/src/ui/orgs.html +21 -52
- package/packages/@monomind/cli/dist/src/ui/server.mjs +42 -6
- package/packages/@monomind/cli/package.json +12 -16
- package/packages/@monomind/cli/dist/src/orgrt/live.html +0 -56
|
@@ -428,6 +428,18 @@ html, body { height: 100%; background: var(--bg); color: var(--text-hi); font-fa
|
|
|
428
428
|
/* SVG org chart */
|
|
429
429
|
.org-chart-wrap { background: var(--surface); border: 1px solid var(--border); border-radius: var(--r); overflow: hidden; margin-bottom: 12px; }
|
|
430
430
|
.org-chart-svg { width: 100%; display: block; }
|
|
431
|
+
.v2-bubble-inner {
|
|
432
|
+
background: oklch(18% 0.01 55 / 0.95);
|
|
433
|
+
border: 1px solid oklch(72% 0.18 75 / 0.4);
|
|
434
|
+
border-radius: 8px;
|
|
435
|
+
padding: 4px 8px;
|
|
436
|
+
font: 500 9.5px 'Inter', system-ui, sans-serif;
|
|
437
|
+
color: oklch(88% 0.01 75);
|
|
438
|
+
text-align: center;
|
|
439
|
+
white-space: nowrap;
|
|
440
|
+
overflow: hidden;
|
|
441
|
+
text-overflow: ellipsis;
|
|
442
|
+
}
|
|
431
443
|
.org-chart-legend { display: flex; gap: 14px; flex-wrap: wrap; padding: 0 2px; }
|
|
432
444
|
.ocl-item { display: flex; align-items: center; gap: 5px; font-size: 10px; color: var(--text-lo); }
|
|
433
445
|
|
|
@@ -2468,7 +2480,7 @@ function switchView(v, { updateHash = true } = {}) {
|
|
|
2468
2480
|
const titles = { now:'Now', projects:'Projects', sessions:'Sessions', loops:'Loops', tokens:'Tokens', memory:'Memory', orgs:'Orgs', monograph:'Monograph', monoagent:'MonoAgent', global:'Global Feed', 'global-loops':'Global Loops', 'global-tokens':'Global Tokens', chat:'Global Agent Chat' };
|
|
2469
2481
|
document.getElementById('view-title').textContent = titles[v] || v;
|
|
2470
2482
|
const PROJECT = DIR ? shortPath(DIR) : 'monomind';
|
|
2471
|
-
const VIEW_LABELS = { now: 'Now', sessions: 'Sessions', projects: 'Projects', loops: 'Loops', tokens: 'Tokens', memory: 'Memory', orgs: 'Orgs', monograph: 'Monograph', global: 'Global Feed', 'global-loops': 'Global Loops', 'global-tokens': 'Global Tokens' };
|
|
2483
|
+
const VIEW_LABELS = { now: 'Now', sessions: 'Sessions', projects: 'Projects', loops: 'Loops', tokens: 'Tokens', memory: 'Memory', orgs: 'Orgs', monograph: 'Monograph', monoagent: 'MonoAgent', global: 'Global Feed', 'global-loops': 'Global Loops', 'global-tokens': 'Global Tokens', chat: 'Global Agent Chat' };
|
|
2472
2484
|
document.title = `monomind · ${PROJECT} · ${VIEW_LABELS[v] || v}`;
|
|
2473
2485
|
if (updateHash) _pushHash();
|
|
2474
2486
|
// Projects always re-fetches so onclick paths in cards stay current
|
|
@@ -5786,6 +5798,7 @@ let _v2SelOrg = null;
|
|
|
5786
5798
|
let _v2OrgData = null;
|
|
5787
5799
|
let _v2OrgTab = 'chart';
|
|
5788
5800
|
const _v2OrgEventLog = {};
|
|
5801
|
+
let _v2ChartLayout = { pos: {}, edgeIdByPair: new Map() };
|
|
5789
5802
|
|
|
5790
5803
|
async function renderOrgs() {
|
|
5791
5804
|
try {
|
|
@@ -6204,6 +6217,9 @@ function v2RenderOrgChart() {
|
|
|
6204
6217
|
<image href="${avatarSrc}" x="${-avR}" y="${-R + 2}" width="${avR * 2}" height="${avR * 2}" clip-path="url(#${nodeClipIds[i]})" preserveAspectRatio="xMidYMid meet" onerror="this.setAttribute('href','data/avatars/coder.svg')"/>
|
|
6205
6218
|
<text text-anchor="middle" y="${nameY}" font-size="9" font-weight="${leader?'600':'500'}" font-family="'Inter',system-ui,sans-serif" fill="${color}">${esc(nameText)}</text>
|
|
6206
6219
|
${subTypeText ? `<text text-anchor="middle" y="${+nameY + 11}" font-size="7.5" font-family="'Inter',system-ui,sans-serif" fill="oklch(48% 0.005 75)">${esc(subTypeText)}</text>` : ''}
|
|
6220
|
+
<foreignObject class="v2-activity-bubble" data-bubble-for="${esc(role.id)}" x="-70" y="${-R - 46}" width="140" height="34" style="overflow:visible;pointer-events:none;visibility:hidden;opacity:0">
|
|
6221
|
+
<div class="v2-bubble-inner" xmlns="http://www.w3.org/1999/xhtml"></div>
|
|
6222
|
+
</foreignObject>
|
|
6207
6223
|
</g>`;
|
|
6208
6224
|
});
|
|
6209
6225
|
|
|
@@ -6230,6 +6246,7 @@ function v2RenderOrgChart() {
|
|
|
6230
6246
|
<g id="v2oc-edges">${edgesHTML}</g>
|
|
6231
6247
|
<g id="v2oc-nodes">${nodesHTML}</g>
|
|
6232
6248
|
<g id="v2oc-particles" opacity="${isRunning ? 1 : 0}">${particlesHTML}</g>
|
|
6249
|
+
<g id="v2oc-letters"></g>
|
|
6233
6250
|
</svg>
|
|
6234
6251
|
</div>
|
|
6235
6252
|
<div class="org-chart-legend" style="margin-top:10px">
|
|
@@ -6262,8 +6279,158 @@ function v2RenderOrgChart() {
|
|
|
6262
6279
|
el.style.opacity = '0';
|
|
6263
6280
|
});
|
|
6264
6281
|
}
|
|
6282
|
+
|
|
6283
|
+
const edgeIdByPair = new Map();
|
|
6284
|
+
comms.forEach((edge, ei) => {
|
|
6285
|
+
if (edge.from === edge.to) return;
|
|
6286
|
+
edgeIdByPair.set(edge.from + '->' + edge.to, 'v2ep' + ei);
|
|
6287
|
+
});
|
|
6288
|
+
_v2ChartLayout = { pos, edgeIdByPair };
|
|
6289
|
+
v2OrgActivityReset();
|
|
6290
|
+
}
|
|
6291
|
+
|
|
6292
|
+
function _v2RoleName(id, roles) {
|
|
6293
|
+
const r = (roles || []).find(x => x.id === id);
|
|
6294
|
+
return r ? (r.name || r.title || r.id) : id;
|
|
6295
|
+
}
|
|
6296
|
+
|
|
6297
|
+
function v2DeriveActivityText(ev, roles) {
|
|
6298
|
+
if (!ev || !ev.from) return null;
|
|
6299
|
+
const type = ev.type || '';
|
|
6300
|
+
if (type === 'org:tool') return ev.tool ? ('running ' + ev.tool) : 'active';
|
|
6301
|
+
if (type === 'org:checkpoint') {
|
|
6302
|
+
const p = ev.progress || '';
|
|
6303
|
+
return p ? (p.length > 40 ? p.slice(0, 39) + '...' : p) : 'active';
|
|
6304
|
+
}
|
|
6305
|
+
if (type === 'org:comms') {
|
|
6306
|
+
if (!ev.to || ev.to === 'all') return 'broadcasting';
|
|
6307
|
+
return 'messaging ' + _v2RoleName(ev.to, roles);
|
|
6308
|
+
}
|
|
6309
|
+
if (type === 'org:artifact') {
|
|
6310
|
+
const label = ev.artifact && ev.artifact.label;
|
|
6311
|
+
return label ? ('writing ' + label) : 'active';
|
|
6312
|
+
}
|
|
6313
|
+
return 'active';
|
|
6314
|
+
}
|
|
6315
|
+
|
|
6316
|
+
function v2DeriveLetterTarget(ev, roles) {
|
|
6317
|
+
if (!ev || !ev.from) return null;
|
|
6318
|
+
const type = ev.type || '';
|
|
6319
|
+
if (type === 'org:comms') {
|
|
6320
|
+
if (!ev.to || ev.to === 'all') return null;
|
|
6321
|
+
return { from: ev.from, to: ev.to };
|
|
6322
|
+
}
|
|
6323
|
+
if (type === 'org:checkpoint' || type === 'org:artifact') {
|
|
6324
|
+
const leader = (roles || []).find(_v2OrgIsLeader);
|
|
6325
|
+
if (!leader || leader.id === ev.from) return null;
|
|
6326
|
+
return { from: ev.from, to: leader.id };
|
|
6327
|
+
}
|
|
6328
|
+
return null;
|
|
6329
|
+
}
|
|
6330
|
+
|
|
6331
|
+
let _v2LetterBurst = { lastTs: 0, count: 0 };
|
|
6332
|
+
|
|
6333
|
+
function v2SpawnLetter(fromId, toId) {
|
|
6334
|
+
const layout = _v2ChartLayout;
|
|
6335
|
+
const fp = layout.pos[fromId], tp = layout.pos[toId];
|
|
6336
|
+
if (!fp || !tp) return;
|
|
6337
|
+
const group = document.getElementById('v2oc-letters');
|
|
6338
|
+
if (!group) return;
|
|
6339
|
+
|
|
6340
|
+
const now = Date.now();
|
|
6341
|
+
if (now - _v2LetterBurst.lastTs < 150) _v2LetterBurst.count++;
|
|
6342
|
+
else _v2LetterBurst.count = 0;
|
|
6343
|
+
_v2LetterBurst.lastTs = now;
|
|
6344
|
+
const delay = (_v2LetterBurst.count * 0.08).toFixed(2);
|
|
6345
|
+
|
|
6346
|
+
const svgNS = 'http://www.w3.org/2000/svg';
|
|
6347
|
+
let pathId = layout.edgeIdByPair.get(fromId + '->' + toId);
|
|
6348
|
+
let synthesizedPath = null;
|
|
6349
|
+
if (!pathId) {
|
|
6350
|
+
pathId = 'v2lp' + Math.random().toString(36).slice(2, 9);
|
|
6351
|
+
synthesizedPath = document.createElementNS(svgNS, 'path');
|
|
6352
|
+
synthesizedPath.setAttribute('id', pathId);
|
|
6353
|
+
synthesizedPath.setAttribute('d', 'M' + fp.x + ',' + fp.y + ' L' + tp.x + ',' + tp.y);
|
|
6354
|
+
synthesizedPath.setAttribute('fill', 'none');
|
|
6355
|
+
synthesizedPath.setAttribute('stroke', 'none');
|
|
6356
|
+
document.getElementById('v2oc-edges').appendChild(synthesizedPath);
|
|
6357
|
+
}
|
|
6358
|
+
|
|
6359
|
+
const letter = document.createElementNS(svgNS, 'g');
|
|
6360
|
+
const circ = document.createElementNS(svgNS, 'circle');
|
|
6361
|
+
circ.setAttribute('r', '6');
|
|
6362
|
+
circ.setAttribute('fill', 'oklch(85% 0.16 85)');
|
|
6363
|
+
circ.setAttribute('stroke', 'oklch(30% 0.02 55)');
|
|
6364
|
+
circ.setAttribute('stroke-width', '1');
|
|
6365
|
+
letter.appendChild(circ);
|
|
6366
|
+
const flap = document.createElementNS(svgNS, 'path');
|
|
6367
|
+
flap.setAttribute('d', 'M-3.5,-2 L0,1 L3.5,-2');
|
|
6368
|
+
flap.setAttribute('stroke', 'oklch(30% 0.02 55)');
|
|
6369
|
+
flap.setAttribute('stroke-width', '1');
|
|
6370
|
+
flap.setAttribute('fill', 'none');
|
|
6371
|
+
letter.appendChild(flap);
|
|
6372
|
+
|
|
6373
|
+
const anim = document.createElementNS(svgNS, 'animateMotion');
|
|
6374
|
+
anim.setAttribute('dur', '0.8s');
|
|
6375
|
+
anim.setAttribute('begin', delay + 's');
|
|
6376
|
+
anim.setAttribute('fill', 'freeze');
|
|
6377
|
+
const mpath = document.createElementNS(svgNS, 'mpath');
|
|
6378
|
+
mpath.setAttributeNS('http://www.w3.org/1999/xlink', 'href', '#' + pathId);
|
|
6379
|
+
anim.appendChild(mpath);
|
|
6380
|
+
letter.appendChild(anim);
|
|
6381
|
+
group.appendChild(letter);
|
|
6382
|
+
|
|
6383
|
+
if (typeof gsap !== 'undefined') {
|
|
6384
|
+
gsap.fromTo(letter, { scale: 0, autoAlpha: 0 }, { scale: 1, autoAlpha: 1, duration: 0.15, delay: Number(delay), ease: 'back.out(1.7)' });
|
|
6385
|
+
}
|
|
6386
|
+
|
|
6387
|
+
const totalMs = (Number(delay) + 0.8) * 1000;
|
|
6388
|
+
setTimeout(function() {
|
|
6389
|
+
function cleanup() {
|
|
6390
|
+
letter.remove();
|
|
6391
|
+
if (synthesizedPath) synthesizedPath.remove();
|
|
6392
|
+
}
|
|
6393
|
+
if (typeof gsap !== 'undefined') {
|
|
6394
|
+
gsap.to(letter, { autoAlpha: 0, duration: 0.15, onComplete: cleanup });
|
|
6395
|
+
} else cleanup();
|
|
6396
|
+
}, totalMs);
|
|
6397
|
+
}
|
|
6398
|
+
|
|
6399
|
+
let _v2BubbleTimers = new Map();
|
|
6400
|
+
|
|
6401
|
+
function v2OrgActivityReset() {
|
|
6402
|
+
_v2BubbleTimers.forEach(t => clearTimeout(t));
|
|
6403
|
+
_v2BubbleTimers = new Map();
|
|
6265
6404
|
}
|
|
6266
6405
|
|
|
6406
|
+
function v2ShowActivityBubble(roleId, text) {
|
|
6407
|
+
const fo = document.querySelector('.v2-activity-bubble[data-bubble-for="' + CSS.escape(roleId) + '"]');
|
|
6408
|
+
if (!fo) return;
|
|
6409
|
+
const inner = fo.querySelector('.v2-bubble-inner');
|
|
6410
|
+
inner.textContent = text;
|
|
6411
|
+
if (typeof gsap !== 'undefined') {
|
|
6412
|
+
gsap.killTweensOf(fo);
|
|
6413
|
+
fo.style.visibility = 'visible';
|
|
6414
|
+
gsap.fromTo(fo, { scale: 0.8, autoAlpha: 0 }, {
|
|
6415
|
+
scale: 1, autoAlpha: 1, duration: 0.3, ease: 'back.out(1.7)',
|
|
6416
|
+
transformOrigin: '50% 100%',
|
|
6417
|
+
});
|
|
6418
|
+
} else {
|
|
6419
|
+
fo.style.visibility = 'visible';
|
|
6420
|
+
fo.style.opacity = '1';
|
|
6421
|
+
}
|
|
6422
|
+
const existing = _v2BubbleTimers.get(roleId);
|
|
6423
|
+
if (existing) clearTimeout(existing);
|
|
6424
|
+
_v2BubbleTimers.set(roleId, setTimeout(function() {
|
|
6425
|
+
if (typeof gsap !== 'undefined') {
|
|
6426
|
+
gsap.to(fo, { autoAlpha: 0, duration: 0.4, ease: 'power2.in', onComplete: function() { fo.style.visibility = 'hidden'; } });
|
|
6427
|
+
} else {
|
|
6428
|
+
fo.style.opacity = '0';
|
|
6429
|
+
fo.style.visibility = 'hidden';
|
|
6430
|
+
}
|
|
6431
|
+
_v2BubbleTimers.delete(roleId);
|
|
6432
|
+
}, 5000));
|
|
6433
|
+
}
|
|
6267
6434
|
|
|
6268
6435
|
// ── in-place running dot updater (avoids full re-render + GSAP flicker on SSE events) ──
|
|
6269
6436
|
function v2UpdateChartRunningDots() {
|
|
@@ -7940,17 +8107,28 @@ window.odtChatSelectAgent = function(name) {
|
|
|
7940
8107
|
});
|
|
7941
8108
|
const feed = document.getElementById('odt-chat-feed');
|
|
7942
8109
|
const emptyEl = document.getElementById('odt-chat-empty');
|
|
7943
|
-
const sess = _odtChatSessions.find(s => s.id === _odtChatCurrentId);
|
|
7944
8110
|
feed.querySelectorAll('.cv-msg').forEach(e => e.remove());
|
|
7945
|
-
|
|
7946
|
-
|
|
8111
|
+
const id = _odtChatCurrentId;
|
|
8112
|
+
let events = null;
|
|
8113
|
+
let sess = null;
|
|
8114
|
+
if (id && id.startsWith('rgrp:') && _odtRunGroups[id]) {
|
|
8115
|
+
events = _odtGetMergedRunGroupEvents(_odtRunGroups[id]);
|
|
8116
|
+
} else if (id && id.startsWith('grp:') && _odtSessionGroups[id]) {
|
|
8117
|
+
events = _odtGetMergedGroupEvents(_odtSessionGroups[id]);
|
|
8118
|
+
} else {
|
|
8119
|
+
sess = _odtChatSessions.find(s => s.id === id);
|
|
8120
|
+
events = sess ? (sess.events || []) : null;
|
|
8121
|
+
}
|
|
8122
|
+
if (events === null) { if (emptyEl) { emptyEl.style.display = 'block'; emptyEl.textContent = 'Select a session to see agent communications.'; } return; }
|
|
8123
|
+
const visible = events.filter(ev => _odtChatAgentMatches(ev));
|
|
7947
8124
|
if (!visible.length) {
|
|
7948
8125
|
if (emptyEl) { emptyEl.style.display = 'block'; emptyEl.textContent = name === 'all' ? 'No events.' : `${name} was online in this run but sent no messages.`; }
|
|
7949
8126
|
} else {
|
|
7950
8127
|
if (emptyEl) emptyEl.style.display = 'none';
|
|
7951
8128
|
visible.forEach(ev => _odtAppendEvent(ev, false));
|
|
8129
|
+
if (!feed.querySelector('.cv-msg') && emptyEl) { emptyEl.style.display = 'block'; emptyEl.textContent = 'No displayable events.'; }
|
|
7952
8130
|
}
|
|
7953
|
-
if (!sess._isOrgRun) {
|
|
8131
|
+
if (sess && !sess._isOrgRun) {
|
|
7954
8132
|
const _linkedRunId = 'run-' + (sess.id || '').replace(/^mm-/, '');
|
|
7955
8133
|
const _linkedRun = _odtChatSessions.find(s => s._isOrgRun && s.id === _linkedRunId);
|
|
7956
8134
|
if (_linkedRun) {
|
|
@@ -7976,17 +8154,25 @@ window.odtChatSelectAgent = function(name) {
|
|
|
7976
8154
|
// Chat tab render mode (Summary/Detailed/Raw) — persists to localStorage
|
|
7977
8155
|
let _odtChatMode = localStorage.getItem('odt-chat-mode') || 'detailed';
|
|
7978
8156
|
|
|
7979
|
-
function
|
|
7980
|
-
_odtChatMode = mode;
|
|
7981
|
-
localStorage.setItem('odt-chat-mode', mode);
|
|
8157
|
+
function _odtApplyChatModeUI(mode) {
|
|
7982
8158
|
document.querySelectorAll('.odt-mode-btn').forEach(b => b.classList.remove('active'));
|
|
7983
8159
|
const btn = document.getElementById('odt-mode-' + mode);
|
|
7984
8160
|
if (btn) btn.classList.add('active');
|
|
7985
8161
|
}
|
|
7986
8162
|
|
|
8163
|
+
function setOdtChatMode(mode) {
|
|
8164
|
+
_odtChatMode = mode;
|
|
8165
|
+
localStorage.setItem('odt-chat-mode', mode);
|
|
8166
|
+
_odtApplyChatModeUI(mode);
|
|
8167
|
+
// Re-render the current selection so the mode change is reflected immediately —
|
|
8168
|
+
// previously this only affected events appended after the switch.
|
|
8169
|
+
if (_odtChatCurrentId) window.odtChatSelectAgent(_odtChatCurrentAgent);
|
|
8170
|
+
}
|
|
8171
|
+
|
|
7987
8172
|
function initOdtChatMode() {
|
|
7988
8173
|
const saved = localStorage.getItem('odt-chat-mode') || 'detailed';
|
|
7989
|
-
|
|
8174
|
+
_odtChatMode = saved;
|
|
8175
|
+
_odtApplyChatModeUI(saved);
|
|
7990
8176
|
}
|
|
7991
8177
|
|
|
7992
8178
|
function _odtAppendEvent(ev, animate) {
|
|
@@ -8146,6 +8332,14 @@ function _odtHandleLiveEvent(ev) {
|
|
|
8146
8332
|
const _arr = [..._odtChatSeenKeys];
|
|
8147
8333
|
_odtChatSeenKeys = new Set(_arr.slice(1000));
|
|
8148
8334
|
}
|
|
8335
|
+
// Wire activity bubble for org chart visualization
|
|
8336
|
+
if (ev && ev.org === _v2SelOrg) {
|
|
8337
|
+
const roles = Array.isArray(_v2OrgData && _v2OrgData.roles) ? _v2OrgData.roles : [];
|
|
8338
|
+
const text = v2DeriveActivityText(ev, roles);
|
|
8339
|
+
if (text && ev.from) v2ShowActivityBubble(ev.from, text);
|
|
8340
|
+
const letterTarget = v2DeriveLetterTarget(ev, roles);
|
|
8341
|
+
if (letterTarget) v2SpawnLetter(letterTarget.from, letterTarget.to);
|
|
8342
|
+
}
|
|
8149
8343
|
// Route org run events (have runId + org) to the active run session
|
|
8150
8344
|
if (ev?.org && ev?.runId && ev.org === _v2SelOrg) {
|
|
8151
8345
|
let runSess = _odtChatSessions.find(s => s.id === ev.runId);
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
// packages/@monomind/cli/dist/src/ui/orgs-files.js
|
|
2
|
+
// Files tab + diff-view feature, split out of orgs.html to keep that file under
|
|
3
|
+
// the CLAUDE.md 500-line guideline. Loaded as a classic <script> right where this
|
|
4
|
+
// code used to live inline — shares the same global scope as orgs.html's main
|
|
5
|
+
// script (chatSessions, orgSessionMatch, esc, currentTab, loadChatSessions,
|
|
6
|
+
// viewArtifact are all defined there and referenced here by closure).
|
|
7
|
+
|
|
8
|
+
// ── Files tab: every asset produced by this org, grouped by path with full
|
|
9
|
+
// version history (not deduped) so successive writes to the same file can be
|
|
10
|
+
// diffed against each other, latest-touched file first ──
|
|
11
|
+
let filesGroups = [];
|
|
12
|
+
|
|
13
|
+
function collectOrgArtifacts() {
|
|
14
|
+
const bySessions = chatSessions.filter(orgSessionMatch);
|
|
15
|
+
const byPath = new Map(); // path -> [{ art, ts, from }]
|
|
16
|
+
bySessions.forEach(s => (s.events || []).forEach(ev => {
|
|
17
|
+
if (ev.type !== 'org:artifact') return;
|
|
18
|
+
const art = ev.artifact || (ev.path ? { path: ev.path, label: ev.label, mimeType: ev.mimeType } : null);
|
|
19
|
+
if (!art || !art.path) return;
|
|
20
|
+
if (!byPath.has(art.path)) byPath.set(art.path, []);
|
|
21
|
+
byPath.get(art.path).push({ art, ts: ev.ts || 0, from: ev.from || '' });
|
|
22
|
+
}));
|
|
23
|
+
const groups = [];
|
|
24
|
+
byPath.forEach((versions, path) => {
|
|
25
|
+
versions.sort((a, b) => a.ts - b.ts);
|
|
26
|
+
groups.push({ path, versions, latest: versions[versions.length - 1] });
|
|
27
|
+
});
|
|
28
|
+
groups.sort((a, b) => b.latest.ts - a.latest.ts);
|
|
29
|
+
return groups;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function mkFileCard(group) {
|
|
33
|
+
const { art, ts, from } = group.latest;
|
|
34
|
+
const el = document.createElement('div');
|
|
35
|
+
el.className = 'file-card';
|
|
36
|
+
const isText = (art.mimeType || '').startsWith('text/') || (art.mimeType || '') === 'application/json';
|
|
37
|
+
const labelRaw = art.label || (art.path || 'file').split('/').pop();
|
|
38
|
+
const timeStr = ts ? new Date(ts).toLocaleString([], { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' }) : '';
|
|
39
|
+
const meta = [from, art.path ? art.path.split('/').slice(-3).join('/') : null, timeStr].filter(Boolean).join(' · ');
|
|
40
|
+
const vBadge = group.versions.length > 1 ? `<span class="fc-vbadge">${group.versions.length}v</span>` : '';
|
|
41
|
+
const diffable = group.versions.filter(v => typeof v.art.content === 'string').length >= 2;
|
|
42
|
+
el.innerHTML = `
|
|
43
|
+
<div class="fc-icon">📄</div>
|
|
44
|
+
<div class="fc-body">
|
|
45
|
+
<div class="fc-name">${esc(labelRaw)} ${vBadge}</div>
|
|
46
|
+
<div class="fc-meta">${esc(meta)}</div>
|
|
47
|
+
</div>
|
|
48
|
+
${diffable ? `<button class="fc-diff" onclick="openDiffPanel(${JSON.stringify(art.path)})">Diff</button>` : ''}
|
|
49
|
+
${isText && art.path
|
|
50
|
+
? `<button class="fc-view" onclick="viewArtifact(${JSON.stringify(art.path)},${JSON.stringify(labelRaw)})">View</button>`
|
|
51
|
+
: `<span class="fc-binary">Binary</span>`}
|
|
52
|
+
`;
|
|
53
|
+
return el;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function paintFilesGrid() {
|
|
57
|
+
const grid = document.getElementById('files-grid');
|
|
58
|
+
const empty = document.getElementById('files-empty');
|
|
59
|
+
if (!grid) return;
|
|
60
|
+
filesGroups = collectOrgArtifacts();
|
|
61
|
+
renderFilesGridFromGroups();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Re-renders the grid from the current in-memory `filesGroups` — no rescan.
|
|
65
|
+
* Split out of paintFilesGrid() so applyArtifactEvent() can repaint after an
|
|
66
|
+
* O(groups) incremental update instead of forcing a full O(all-events) rescan. */
|
|
67
|
+
function renderFilesGridFromGroups() {
|
|
68
|
+
const grid = document.getElementById('files-grid');
|
|
69
|
+
const empty = document.getElementById('files-empty');
|
|
70
|
+
if (!grid) return;
|
|
71
|
+
grid.innerHTML = '';
|
|
72
|
+
if (!filesGroups.length) { if (empty) empty.style.display = 'block'; return; }
|
|
73
|
+
if (empty) empty.style.display = 'none';
|
|
74
|
+
filesGroups.forEach(group => grid.appendChild(mkFileCard(group)));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Incrementally folds a single new org:artifact event into `filesGroups` —
|
|
78
|
+
* O(groups) to re-sort by latest-touched, not O(all sessions' all events)
|
|
79
|
+
* like collectOrgArtifacts(). Mirrors collectOrgArtifacts()'s grouping rules. */
|
|
80
|
+
function applyArtifactEvent(ev) {
|
|
81
|
+
const art = ev.artifact || (ev.path ? { path: ev.path, label: ev.label, mimeType: ev.mimeType } : null);
|
|
82
|
+
if (!art || !art.path) return;
|
|
83
|
+
const entry = { art, ts: ev.ts || 0, from: ev.from || '' };
|
|
84
|
+
let group = filesGroups.find(g => g.path === art.path);
|
|
85
|
+
if (!group) {
|
|
86
|
+
group = { path: art.path, versions: [], latest: entry };
|
|
87
|
+
filesGroups.push(group);
|
|
88
|
+
}
|
|
89
|
+
group.versions.push(entry);
|
|
90
|
+
group.versions.sort((a, b) => a.ts - b.ts);
|
|
91
|
+
group.latest = group.versions[group.versions.length - 1];
|
|
92
|
+
filesGroups.sort((a, b) => b.latest.ts - a.latest.ts);
|
|
93
|
+
renderFilesGridFromGroups();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function renderFilesTab() {
|
|
97
|
+
paintFilesGrid(); // paint immediately with whatever's cached
|
|
98
|
+
loadChatSessions().then(() => { if (currentTab === 'files') paintFilesGrid(); });
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// ── Diff view: compare two captured versions of the same file ──
|
|
102
|
+
window.openDiffPanel = function(path) {
|
|
103
|
+
const group = filesGroups.find(g => g.path === path);
|
|
104
|
+
if (!group) return;
|
|
105
|
+
const versions = group.versions.filter(v => typeof v.art.content === 'string');
|
|
106
|
+
if (versions.length < 2) return;
|
|
107
|
+
const label = group.latest.art.label || path.split('/').pop();
|
|
108
|
+
renderDiffPanel(label, versions);
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
function renderDiffPanel(label, versions) {
|
|
112
|
+
let panel = document.getElementById('artifact-panel');
|
|
113
|
+
if (!panel) {
|
|
114
|
+
panel = document.createElement('div');
|
|
115
|
+
panel.id = 'artifact-panel';
|
|
116
|
+
panel.style.cssText = 'position:fixed;top:0;right:0;width:640px;height:100vh;background:#0a0a14;border-left:1px solid #333;z-index:1000;display:flex;flex-direction:column;overflow:hidden';
|
|
117
|
+
document.body.appendChild(panel);
|
|
118
|
+
}
|
|
119
|
+
const optLabel = v => `${new Date(v.ts).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' })} · ${esc(v.from || '?')}`;
|
|
120
|
+
const opts = versions.map((v, i) => `<option value="${i}">${optLabel(v)}</option>`).join('');
|
|
121
|
+
panel.innerHTML = `
|
|
122
|
+
<div style="padding:12px 14px;border-bottom:1px solid #1a1a2a;display:flex;align-items:center;gap:10px">
|
|
123
|
+
<span style="font-size:13px;font-weight:700;color:#ccc;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${esc(label)} — diff</span>
|
|
124
|
+
<button onclick="document.getElementById('artifact-panel').remove()" style="background:none;border:none;color:#888;font-size:16px;cursor:pointer">✕</button>
|
|
125
|
+
</div>
|
|
126
|
+
<div style="padding:8px 14px;border-bottom:1px solid #1a1a2a;display:flex;gap:8px;align-items:center;font-size:10px;color:#888">
|
|
127
|
+
<select id="diff-from" style="background:#151520;color:#ccc;border:1px solid #333;border-radius:3px;padding:2px 6px;font-size:10px">${opts}</select>
|
|
128
|
+
<span>→</span>
|
|
129
|
+
<select id="diff-to" style="background:#151520;color:#ccc;border:1px solid #333;border-radius:3px;padding:2px 6px;font-size:10px">${opts}</select>
|
|
130
|
+
</div>
|
|
131
|
+
<div id="diff-body" style="flex:1;overflow:auto;font-size:11px;line-height:1.6;font-family:var(--mono,ui-monospace,monospace)"></div>
|
|
132
|
+
`;
|
|
133
|
+
panel.style.display = 'flex';
|
|
134
|
+
const fromSel = document.getElementById('diff-from');
|
|
135
|
+
const toSel = document.getElementById('diff-to');
|
|
136
|
+
fromSel.value = String(versions.length - 2);
|
|
137
|
+
toSel.value = String(versions.length - 1);
|
|
138
|
+
const recompute = () => renderDiffBody(versions[Number(fromSel.value)].art.content, versions[Number(toSel.value)].art.content);
|
|
139
|
+
fromSel.onchange = recompute;
|
|
140
|
+
toSel.onchange = recompute;
|
|
141
|
+
recompute();
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function renderDiffBody(oldText, newText) {
|
|
145
|
+
const body = document.getElementById('diff-body');
|
|
146
|
+
if (!body) return;
|
|
147
|
+
if (oldText === newText) {
|
|
148
|
+
body.innerHTML = '<div style="padding:14px;color:#666">No differences between these two versions.</div>';
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
const rows = diffLines(oldText, newText);
|
|
152
|
+
if (!rows) {
|
|
153
|
+
body.innerHTML = '<div style="padding:14px;color:#666">File too large to diff line-by-line.</div>';
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
body.innerHTML = rows.map(r => {
|
|
157
|
+
const style = r.type === 'add' ? 'background:#0d2818;color:#7ee787'
|
|
158
|
+
: r.type === 'del' ? 'background:#2d0d10;color:#ffa198' : 'color:#8b949e';
|
|
159
|
+
const prefix = r.type === 'add' ? '+' : r.type === 'del' ? '-' : ' ';
|
|
160
|
+
return `<div style="${style};padding:1px 12px;white-space:pre-wrap;word-break:break-word">${esc(prefix + ' ' + r.text)}</div>`;
|
|
161
|
+
}).join('');
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** Line-level LCS diff. Returns null (caller shows a fallback message) rather
|
|
165
|
+
* than hanging the tab if both files are unexpectedly huge — the O(n*m) DP
|
|
166
|
+
* table would otherwise blow past what's reasonable to allocate client-side.
|
|
167
|
+
* Bounds n and m individually, not just their product: a lopsided pair (e.g.
|
|
168
|
+
* n=1, m=4000000 — one huge file diffed against a near-empty one) passes the
|
|
169
|
+
* product check but still allocates a 4M-row DP table, one row per line of A. */
|
|
170
|
+
function diffLines(a, b) {
|
|
171
|
+
const A = a.split('\n'), B = b.split('\n');
|
|
172
|
+
const n = A.length, m = B.length;
|
|
173
|
+
const DIFF_MAX_LINES = 20000;
|
|
174
|
+
if (n > DIFF_MAX_LINES || m > DIFF_MAX_LINES || n * m > 4000000) return null;
|
|
175
|
+
const dp = new Array(n + 1);
|
|
176
|
+
for (let i = 0; i <= n; i++) dp[i] = new Int32Array(m + 1);
|
|
177
|
+
for (let i = n - 1; i >= 0; i--) {
|
|
178
|
+
for (let j = m - 1; j >= 0; j--) {
|
|
179
|
+
dp[i][j] = A[i] === B[j] ? dp[i + 1][j + 1] + 1 : Math.max(dp[i + 1][j], dp[i][j + 1]);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
const out = [];
|
|
183
|
+
let i = 0, j = 0;
|
|
184
|
+
while (i < n && j < m) {
|
|
185
|
+
if (A[i] === B[j]) { out.push({ type: 'ctx', text: A[i] }); i++; j++; }
|
|
186
|
+
else if (dp[i + 1][j] >= dp[i][j + 1]) { out.push({ type: 'del', text: A[i] }); i++; }
|
|
187
|
+
else { out.push({ type: 'add', text: B[j] }); j++; }
|
|
188
|
+
}
|
|
189
|
+
while (i < n) { out.push({ type: 'del', text: A[i] }); i++; }
|
|
190
|
+
while (j < m) { out.push({ type: 'add', text: B[j] }); j++; }
|
|
191
|
+
return out;
|
|
192
|
+
}
|
|
@@ -412,6 +412,15 @@ html, body {
|
|
|
412
412
|
border-radius: 4px; padding: 4px 10px; font-size: 10px; font-weight: 700; cursor: pointer;
|
|
413
413
|
}
|
|
414
414
|
.fc-view:hover { background: #204a70; }
|
|
415
|
+
.fc-diff {
|
|
416
|
+
flex-shrink: 0; background: #2a1a3a; color: #b98dd9; border: 1px solid #8e44ad44;
|
|
417
|
+
border-radius: 4px; padding: 4px 10px; font-size: 10px; font-weight: 700; cursor: pointer;
|
|
418
|
+
}
|
|
419
|
+
.fc-diff:hover { background: #3a2050; }
|
|
420
|
+
.fc-vbadge {
|
|
421
|
+
font-size: 8px; font-weight: 700; color: var(--dim); border: 1px solid var(--border);
|
|
422
|
+
border-radius: 2px; padding: 1px 5px; margin-left: 4px; letter-spacing: 0.5px;
|
|
423
|
+
}
|
|
415
424
|
.fc-binary { flex-shrink: 0; font-size: 9px; color: var(--dim); }
|
|
416
425
|
|
|
417
426
|
/* ── Activity / event log ───────────────────────────────────────── */
|
|
@@ -1125,56 +1134,14 @@ window.switchTab = function(tab) {
|
|
|
1125
1134
|
else if (orgDetailData) renderTab(tab);
|
|
1126
1135
|
};
|
|
1127
1136
|
|
|
1128
|
-
//
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
const prev = seen.get(art.path);
|
|
1137
|
-
if (!prev || (ev.ts || 0) >= prev.ts) seen.set(art.path, { art, ts: ev.ts || 0, from: ev.from || '' });
|
|
1138
|
-
}));
|
|
1139
|
-
return Array.from(seen.values()).sort((a, b) => b.ts - a.ts);
|
|
1140
|
-
}
|
|
1141
|
-
|
|
1142
|
-
function mkFileCard(entry) {
|
|
1143
|
-
const { art, ts, from } = entry;
|
|
1144
|
-
const el = document.createElement('div');
|
|
1145
|
-
el.className = 'file-card';
|
|
1146
|
-
const isText = (art.mimeType || '').startsWith('text/') || (art.mimeType || '') === 'application/json';
|
|
1147
|
-
const labelRaw = art.label || (art.path || 'file').split('/').pop();
|
|
1148
|
-
const timeStr = ts ? new Date(ts).toLocaleString([], { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' }) : '';
|
|
1149
|
-
const meta = [from, art.path ? art.path.split('/').slice(-3).join('/') : null, timeStr].filter(Boolean).join(' · ');
|
|
1150
|
-
el.innerHTML = `
|
|
1151
|
-
<div class="fc-icon">📄</div>
|
|
1152
|
-
<div class="fc-body">
|
|
1153
|
-
<div class="fc-name">${esc(labelRaw)}</div>
|
|
1154
|
-
<div class="fc-meta">${esc(meta)}</div>
|
|
1155
|
-
</div>
|
|
1156
|
-
${isText && art.path
|
|
1157
|
-
? `<button class="fc-view" onclick="viewArtifact(${JSON.stringify(art.path)},${JSON.stringify(labelRaw)})">View</button>`
|
|
1158
|
-
: `<span class="fc-binary">Binary</span>`}
|
|
1159
|
-
`;
|
|
1160
|
-
return el;
|
|
1161
|
-
}
|
|
1162
|
-
|
|
1163
|
-
function paintFilesGrid() {
|
|
1164
|
-
const grid = document.getElementById('files-grid');
|
|
1165
|
-
const empty = document.getElementById('files-empty');
|
|
1166
|
-
if (!grid) return;
|
|
1167
|
-
const files = collectOrgArtifacts();
|
|
1168
|
-
grid.innerHTML = '';
|
|
1169
|
-
if (!files.length) { if (empty) empty.style.display = 'block'; return; }
|
|
1170
|
-
if (empty) empty.style.display = 'none';
|
|
1171
|
-
files.forEach(entry => grid.appendChild(mkFileCard(entry)));
|
|
1172
|
-
}
|
|
1173
|
-
|
|
1174
|
-
function renderFilesTab() {
|
|
1175
|
-
paintFilesGrid(); // paint immediately with whatever's cached
|
|
1176
|
-
loadChatSessions().then(() => { if (currentTab === 'files') paintFilesGrid(); });
|
|
1177
|
-
}
|
|
1137
|
+
// Files tab + diff view — extracted to orgs-files.js to keep this file under
|
|
1138
|
+
// the CLAUDE.md 500-line-per-file guideline. Loaded as a classic script below,
|
|
1139
|
+
// sharing this script's global scope (function declarations here call into it,
|
|
1140
|
+
// e.g. renderFilesTab() from renderTab() above and applyArtifactEvent() from
|
|
1141
|
+
// handleMmEvent() below).
|
|
1142
|
+
</script>
|
|
1143
|
+
<script src="orgs-files.js"></script>
|
|
1144
|
+
<script>
|
|
1178
1145
|
|
|
1179
1146
|
// ── Render all orgs in sidebar ────────────────────────────────────
|
|
1180
1147
|
function renderSidebar() {
|
|
@@ -2139,8 +2106,10 @@ function handleMmEvent(ev) {
|
|
|
2139
2106
|
appendChatEvent(ev, true);
|
|
2140
2107
|
chatScrollFeed();
|
|
2141
2108
|
}
|
|
2142
|
-
// Live-update the Files tab when a new asset lands while it's open
|
|
2143
|
-
|
|
2109
|
+
// Live-update the Files tab when a new asset lands while it's open — incremental,
|
|
2110
|
+
// not a rescan of every session's every event (that's collectOrgArtifacts()'s job,
|
|
2111
|
+
// reserved for tab-open / initial load in renderFilesTab()).
|
|
2112
|
+
if (ev.type === 'org:artifact' && currentTab === 'files') applyArtifactEvent(ev);
|
|
2144
2113
|
}
|
|
2145
2114
|
}
|
|
2146
2115
|
|