agent-memory-brain 0.2.0__py3-none-any.whl
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.
- agent_memory_brain-0.2.0.dist-info/METADATA +98 -0
- agent_memory_brain-0.2.0.dist-info/RECORD +54 -0
- agent_memory_brain-0.2.0.dist-info/WHEEL +4 -0
- agent_memory_brain-0.2.0.dist-info/entry_points.txt +3 -0
- agent_memory_brain-0.2.0.dist-info/licenses/LICENSE +67 -0
- devmem/__init__.py +3 -0
- devmem/ado.py +433 -0
- devmem/agent_colors.py +74 -0
- devmem/cli.py +500 -0
- devmem/db.py +1069 -0
- devmem/dispatch.py +478 -0
- devmem/embed.py +111 -0
- devmem/filelock.py +53 -0
- devmem/git_sync.py +261 -0
- devmem/guard.py +214 -0
- devmem/hook_log.py +49 -0
- devmem/ingest/__init__.py +1 -0
- devmem/ingest/claude.py +87 -0
- devmem/ingest/codex.py +175 -0
- devmem/ingest/runner.py +318 -0
- devmem/ingest/subagents.py +250 -0
- devmem/maintenance.py +801 -0
- devmem/mcp_server.py +542 -0
- devmem/messages.py +684 -0
- devmem/panel/__init__.py +1 -0
- devmem/panel/app.py +971 -0
- devmem/panel/state.py +358 -0
- devmem/panel/static/base.css +27 -0
- devmem/panel/static/brain.css +473 -0
- devmem/panel/static/brain.js +1118 -0
- devmem/panel/static/brain3d.js +600 -0
- devmem/panel/static/chat.js +18 -0
- devmem/panel/static/common.js +208 -0
- devmem/panel/static/site.css +508 -0
- devmem/panel/static/tokens.css +19 -0
- devmem/panel/templates/ado.html +62 -0
- devmem/panel/templates/agents.html +35 -0
- devmem/panel/templates/audit.html +19 -0
- devmem/panel/templates/base.html +42 -0
- devmem/panel/templates/brain.html +101 -0
- devmem/panel/templates/maintenance.html +32 -0
- devmem/panel/templates/memory.html +71 -0
- devmem/panel/templates/message_thread.html +49 -0
- devmem/panel/templates/messages.html +8 -0
- devmem/panel/templates/overview.html +109 -0
- devmem/panel/templates/project.html +48 -0
- devmem/panel/templates/projects.html +21 -0
- devmem/panel/templates/session.html +49 -0
- devmem/panel/templates/sessions.html +67 -0
- devmem/panel/templates/subagent.html +27 -0
- devmem/procs.py +10 -0
- devmem/projects.py +17 -0
- devmem/secrets.py +9 -0
- devmem/skills.py +105 -0
|
@@ -0,0 +1,600 @@
|
|
|
1
|
+
// devmem 3D brain: a glowing neural web on 3d-force-graph (WebGL / three.js).
|
|
2
|
+
//
|
|
3
|
+
// The pinned 3d-force-graph bundle does not expose THREE. Its second three.js copy must stay coupled to r162, so the few classes needed for
|
|
4
|
+
// canvas-texture sprites (labels, glow halos) are taken from objects the library itself creates:
|
|
5
|
+
// a probe node's default mesh and the post-processing composer's render target. If that fails the web
|
|
6
|
+
// still renders with the library's default spheres and hover tooltips.
|
|
7
|
+
(function () {
|
|
8
|
+
"use strict";
|
|
9
|
+
|
|
10
|
+
var KIND_COLORS = { note: "#88a8c9", decision: "#aa9dc5", preference: "#c7959c", snippet: "#90b697", todo: "#ccb484" };
|
|
11
|
+
var LINK_COLORS = { blocks: "#f47c70", depends_on: "#ccb484", relates_to: "#9c9890", overlaps: "#66635d", fixes: "#90b697", supersedes: "#4a463f" };
|
|
12
|
+
var R = 120; // overall web radius (world units)
|
|
13
|
+
var ADDITIVE = 2; // THREE.AdditiveBlending
|
|
14
|
+
|
|
15
|
+
function webglAvailable() {
|
|
16
|
+
try {
|
|
17
|
+
var c = document.createElement("canvas");
|
|
18
|
+
return !!(window.WebGLRenderingContext && (c.getContext("webgl2") || c.getContext("webgl")));
|
|
19
|
+
} catch (e) { return false; }
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function create(container, opts) {
|
|
23
|
+
var agentColor = opts.agentColor, agentShort = opts.agentShort, esc = opts.esc, REDUCED = !!opts.reduced;
|
|
24
|
+
var C = null; // harvested three.js constructors
|
|
25
|
+
var Graph = ForceGraph3D({ controlType: "orbit", rendererConfig: { antialias: true, alpha: false, powerPreference: "low-power" } })(container);
|
|
26
|
+
var nodes = [], links = [], nodeById = {}, linkByKey = {};
|
|
27
|
+
var pendingGraph = null, pendingBloom = {}, anims = [], lastImpulse = {};
|
|
28
|
+
var active = true, ready = false, fitted = false, resumeTimer = null, loopOn = false, lastTwinkle = 0;
|
|
29
|
+
var texCache = {}, geomCache = {}, lastUserMove = 0, fitTimer = null;
|
|
30
|
+
var bloomPass = null, bloomAdded = false, bloomEnabled = false, bloomError = "";
|
|
31
|
+
|
|
32
|
+
try { bloomEnabled = localStorage.getItem("devmem.bloom") === "1"; } catch (e) { /* default off */ }
|
|
33
|
+
|
|
34
|
+
function bloomActive() { return !!(bloomPass && bloomPass.enabled); }
|
|
35
|
+
|
|
36
|
+
function haloBaseSize(n) {
|
|
37
|
+
var size = n.radius * (n.type === "hub" ? 10 : n.type === "project" ? 7 : n.type === "agent" ? 9 : 6);
|
|
38
|
+
return size * (bloomActive() ? 0.62 : 0.5);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function haloOpacity(n) { return n.baseGlow * (bloomActive() ? 0.42 : 0.3); }
|
|
42
|
+
|
|
43
|
+
function syncHalos() {
|
|
44
|
+
for (var i = 0; i < nodes.length; i++) {
|
|
45
|
+
var n = nodes[i], parts = n.parts;
|
|
46
|
+
if (!parts || !parts.halo || isAnimating(n)) continue;
|
|
47
|
+
parts.halo.material.opacity = haloOpacity(n);
|
|
48
|
+
parts.halo.scale.setScalar(haloBaseSize(n));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function resizeBloom(width, height) {
|
|
53
|
+
if (!bloomPass || !bloomPass.setSize) return;
|
|
54
|
+
// UnrealBloomPass halves internally; pass CSS canvas size times renderer pixel ratio for real half-resolution bloom.
|
|
55
|
+
var ratio = Graph.renderer && Graph.renderer().getPixelRatio ? Graph.renderer().getPixelRatio() : window.devicePixelRatio || 1;
|
|
56
|
+
bloomPass.setSize(Math.max(1, Math.ceil(width * ratio)), Math.max(1, Math.ceil(height * ratio)));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function ensureBloom() {
|
|
60
|
+
if (bloomAdded || !window.__devmemBloom) return;
|
|
61
|
+
try {
|
|
62
|
+
var composer = Graph.postProcessingComposer && Graph.postProcessingComposer();
|
|
63
|
+
if (!composer || !composer.addPass) { bloomError = "3D graph has no post-processing composer"; setGlow(bloomEnabled); return; }
|
|
64
|
+
// A plain x/y object is sufficient; UnrealBloomPass copies it into its own Vector2.
|
|
65
|
+
bloomPass = new window.__devmemBloom({ x: 1, y: 1 }, 0.75, 0.45, 0.25);
|
|
66
|
+
bloomPass.enabled = bloomEnabled;
|
|
67
|
+
composer.addPass(bloomPass);
|
|
68
|
+
bloomAdded = true;
|
|
69
|
+
var rect = container.getBoundingClientRect();
|
|
70
|
+
resizeBloom(rect.width, rect.height);
|
|
71
|
+
syncHalos();
|
|
72
|
+
} catch (e) {
|
|
73
|
+
// The graph and its halo fallback remain usable if the module/composer is incompatible.
|
|
74
|
+
bloomPass = null;
|
|
75
|
+
bloomError = String((e && e.message) || e);
|
|
76
|
+
console.warn("devmem: bloom unavailable", e);
|
|
77
|
+
}
|
|
78
|
+
setGlow(bloomEnabled);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function bloomFailed() {
|
|
82
|
+
bloomError = window.__devmemBloomError || "bloom module failed to load";
|
|
83
|
+
console.warn("devmem: bloom unavailable:", bloomError);
|
|
84
|
+
setGlow(bloomEnabled);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function setGlow(on) {
|
|
88
|
+
bloomEnabled = on;
|
|
89
|
+
try { localStorage.setItem("devmem.bloom", on ? "1" : "0"); } catch (e) { /* storage unavailable */ }
|
|
90
|
+
if (bloomPass) bloomPass.enabled = on;
|
|
91
|
+
syncHalos();
|
|
92
|
+
var button = document.getElementById("glow-toggle");
|
|
93
|
+
if (button) {
|
|
94
|
+
button.setAttribute("aria-pressed", on ? "true" : "false");
|
|
95
|
+
button.classList.toggle("on", on);
|
|
96
|
+
button.textContent = on ? "● Glow on" : "○ Glow off";
|
|
97
|
+
button.title = on ? "Turn off 3D bloom glow (3D view only)" : "Turn on 3D bloom glow (3D view only)";
|
|
98
|
+
// Make a missing bloom effect visible instead of a toggle that silently does nothing.
|
|
99
|
+
button.classList.toggle("unavailable", !bloomPass);
|
|
100
|
+
if (!bloomPass) button.title = "Glow effect not active: " + (bloomError || "bloom module still loading");
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
Graph.renderer().setPixelRatio(Math.min(window.devicePixelRatio || 1, 1.5));
|
|
105
|
+
Graph.backgroundColor("#030201")
|
|
106
|
+
.showNavInfo(false)
|
|
107
|
+
.enableNodeDrag(false)
|
|
108
|
+
.nodeId("id")
|
|
109
|
+
.warmupTicks(40)
|
|
110
|
+
.cooldownTime(12000)
|
|
111
|
+
.d3AlphaDecay(0.025)
|
|
112
|
+
.d3VelocityDecay(0.32)
|
|
113
|
+
.linkOpacity(0.32)
|
|
114
|
+
.linkWidth(0)
|
|
115
|
+
.linkColor(function (l) { return l.color; })
|
|
116
|
+
.linkDirectionalParticles(function (l) { return l.particles || 0; })
|
|
117
|
+
.linkDirectionalParticleColor(function (l) { return l.pcolor || "#efebe4"; })
|
|
118
|
+
.linkDirectionalParticleWidth(function (l) { return l.pwidth || 1.6; })
|
|
119
|
+
.linkDirectionalParticleSpeed(function (l) { return l.kind === "ah" ? 0.016 : 0.035; })
|
|
120
|
+
.linkDirectionalParticleResolution(6)
|
|
121
|
+
.nodeLabel(tooltip)
|
|
122
|
+
.onNodeClick(function (n) {
|
|
123
|
+
if (n.type === "project" && n.path) location.href = "/projects/detail?path=" + encodeURIComponent(n.path);
|
|
124
|
+
else if (n.type === "memory") location.href = "/memory?q=" + encodeURIComponent(n.title || "");
|
|
125
|
+
})
|
|
126
|
+
.onEngineStop(function () {
|
|
127
|
+
fit(900);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
var glowButton = document.getElementById("glow-toggle");
|
|
131
|
+
if (glowButton) glowButton.addEventListener("click", function () { setGlow(!bloomEnabled); });
|
|
132
|
+
setGlow(bloomEnabled);
|
|
133
|
+
window.addEventListener("devmem-bloom", ensureBloom);
|
|
134
|
+
window.addEventListener("devmem-bloom-failed", bloomFailed);
|
|
135
|
+
if (window.__devmemBloomError) bloomFailed();
|
|
136
|
+
ensureBloom();
|
|
137
|
+
|
|
138
|
+
// ---------------------------------------------------------------- forces: web, clusters, loose brain shape
|
|
139
|
+
var shapeNodes = [];
|
|
140
|
+
function shapeForce(alpha) {
|
|
141
|
+
for (var i = 0; i < shapeNodes.length; i++) {
|
|
142
|
+
var n = shapeNodes[i];
|
|
143
|
+
if (n.fx !== undefined && n.fx !== null) continue;
|
|
144
|
+
// ellipsoid: wider left-right, like a brain seen from the front
|
|
145
|
+
var ex = n.x / (R * 1.35), ey = n.y / (R * 0.95), ez = n.z / (R * 1.05);
|
|
146
|
+
var r = Math.sqrt(ex * ex + ey * ey + ez * ez) || 1e-6;
|
|
147
|
+
if (n.type === "agent") {
|
|
148
|
+
var shell = 1.45; // agents float on the outside
|
|
149
|
+
var k = (shell - r) * 0.08 * alpha;
|
|
150
|
+
n.vx += n.x / r * k; n.vy += n.y / r * k; n.vz += n.z / r * k;
|
|
151
|
+
} else if (r > 1) {
|
|
152
|
+
var k2 = (r - 1) * 0.12 * alpha;
|
|
153
|
+
n.vx -= n.x * k2; n.vy -= n.y * k2; n.vz -= n.z * k2;
|
|
154
|
+
}
|
|
155
|
+
if (n.type === "project") { // two loose hemispheres
|
|
156
|
+
var side = n.side || 1;
|
|
157
|
+
n.vx += (side * R * 0.5 - n.x) * 0.02 * alpha;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
shapeForce.initialize = function (ns) { shapeNodes = ns; };
|
|
162
|
+
Graph.d3Force("shape", shapeForce);
|
|
163
|
+
Graph.d3Force("charge").strength(function (n) {
|
|
164
|
+
return n.type === "project" ? -140 : n.type === "agent" ? -60 : n.type === "hub" ? -30 : -9;
|
|
165
|
+
});
|
|
166
|
+
Graph.d3Force("link")
|
|
167
|
+
.distance(function (l) { return l.kind === "ah" ? 85 : l.kind === "hp" ? 62 : l.kind === "hm" ? 26 : l.kind === "ml" ? 34 : 14 + (l.spread || 0); })
|
|
168
|
+
.strength(function (l) { return l.kind === "ah" ? 0.04 : l.kind === "hp" ? 0.16 : l.kind === "ml" ? 0.07 : 0.55; });
|
|
169
|
+
|
|
170
|
+
// ---------------------------------------------------------------- auto-rotation, paused while the user orbits/zooms
|
|
171
|
+
var controls = Graph.controls();
|
|
172
|
+
if (controls) {
|
|
173
|
+
controls.autoRotate = !REDUCED;
|
|
174
|
+
controls.autoRotateSpeed = 0.55;
|
|
175
|
+
controls.enableDamping = true;
|
|
176
|
+
controls.dampingFactor = 0.08;
|
|
177
|
+
if (controls.addEventListener) {
|
|
178
|
+
controls.addEventListener("start", function () {
|
|
179
|
+
controls.autoRotate = false;
|
|
180
|
+
clearTimeout(resumeTimer);
|
|
181
|
+
});
|
|
182
|
+
controls.addEventListener("end", function () {
|
|
183
|
+
clearTimeout(resumeTimer);
|
|
184
|
+
if (!REDUCED) resumeTimer = setTimeout(function () { controls.autoRotate = true; }, 6000);
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// ---------------------------------------------------------------- harvest three.js classes from a probe node
|
|
190
|
+
function harvest() {
|
|
191
|
+
try {
|
|
192
|
+
var probe = Graph.graphData().nodes[0];
|
|
193
|
+
var mesh = probe && probe.__threeObj;
|
|
194
|
+
var comp = Graph.postProcessingComposer();
|
|
195
|
+
var texture = comp && (comp.renderTarget1 || comp.readBuffer) && (comp.renderTarget1 || comp.readBuffer).texture;
|
|
196
|
+
if (!mesh || !mesh.geometry || !mesh.material || !texture) return false;
|
|
197
|
+
var Geo = mesh.geometry.constructor;
|
|
198
|
+
C = {
|
|
199
|
+
Mesh: mesh.constructor,
|
|
200
|
+
Object3D: Object.getPrototypeOf(mesh.constructor.prototype).constructor,
|
|
201
|
+
Sphere: Geo,
|
|
202
|
+
BufferGeometry: Object.getPrototypeOf(Geo.prototype).constructor,
|
|
203
|
+
Attr: mesh.geometry.getAttribute("position").constructor,
|
|
204
|
+
Material: mesh.material.constructor,
|
|
205
|
+
Texture: texture.constructor,
|
|
206
|
+
};
|
|
207
|
+
// smoke-construct once so a mismatch falls back instead of failing later
|
|
208
|
+
new C.Mesh(new C.Sphere(1, 6, 4), new C.Material({ color: 0xffffff }));
|
|
209
|
+
return true;
|
|
210
|
+
} catch (e) {
|
|
211
|
+
C = null;
|
|
212
|
+
return false;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function bootstrap() {
|
|
217
|
+
Graph.graphData({ nodes: [{ id: "__probe" }], links: [] });
|
|
218
|
+
var tries = 0;
|
|
219
|
+
(function poll() {
|
|
220
|
+
tries++;
|
|
221
|
+
var probe = Graph.graphData().nodes[0];
|
|
222
|
+
if ((probe && probe.__threeObj) || tries > 90) {
|
|
223
|
+
if (!harvest()) console.warn("devmem 3D: sprite classes unavailable; using default spheres");
|
|
224
|
+
if (C) Graph.nodeThreeObject(makeNode);
|
|
225
|
+
else Graph.nodeVal(function (n) { return n.val; }).nodeColor(function (n) { return n.color; }).nodeOpacity(0.9);
|
|
226
|
+
ready = true;
|
|
227
|
+
Graph.graphData({ nodes: [], links: [] });
|
|
228
|
+
if (pendingGraph) { var g = pendingGraph; pendingGraph = null; setGraph(g); }
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
requestAnimationFrame(poll);
|
|
232
|
+
})();
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// ---------------------------------------------------------------- node objects
|
|
236
|
+
function canvasTexture(canvas) {
|
|
237
|
+
var t = new C.Texture(canvas);
|
|
238
|
+
if ("colorSpace" in t) t.colorSpace = "srgb";
|
|
239
|
+
t.needsUpdate = true;
|
|
240
|
+
return t;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function haloTexture() {
|
|
244
|
+
if (texCache.halo) return texCache.halo;
|
|
245
|
+
var c = document.createElement("canvas"), s = 64;
|
|
246
|
+
c.width = c.height = s;
|
|
247
|
+
var x = c.getContext("2d"), g = x.createRadialGradient(s / 2, s / 2, 0, s / 2, s / 2, s / 2);
|
|
248
|
+
g.addColorStop(0, "rgba(255,255,255,1)");
|
|
249
|
+
g.addColorStop(0.18, "rgba(255,255,255,0.55)");
|
|
250
|
+
g.addColorStop(0.5, "rgba(255,255,255,0.12)");
|
|
251
|
+
g.addColorStop(1, "rgba(255,255,255,0)");
|
|
252
|
+
x.fillStyle = g;
|
|
253
|
+
x.fillRect(0, 0, s, s);
|
|
254
|
+
return (texCache.halo = canvasTexture(c));
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function labelTexture(text, color) {
|
|
258
|
+
var key = text + "|" + color;
|
|
259
|
+
if (texCache[key]) return texCache[key];
|
|
260
|
+
var c = document.createElement("canvas"), x = c.getContext("2d"), fs = 44;
|
|
261
|
+
x.font = "500 " + fs + "px Switzer, system-ui, -apple-system, Segoe UI, Roboto, sans-serif";
|
|
262
|
+
var w = Math.ceil(x.measureText(text).width) + 28;
|
|
263
|
+
c.width = w; c.height = fs + 22;
|
|
264
|
+
x.font = "500 " + fs + "px Switzer, system-ui, -apple-system, Segoe UI, Roboto, sans-serif";
|
|
265
|
+
x.fillStyle = "rgba(3,2,1,0.78)";
|
|
266
|
+
roundRect(x, 0, 0, w, c.height, 14);
|
|
267
|
+
x.fill();
|
|
268
|
+
x.fillStyle = color;
|
|
269
|
+
x.textBaseline = "middle";
|
|
270
|
+
x.fillText(text, 14, c.height / 2 + 2);
|
|
271
|
+
var t = canvasTexture(c);
|
|
272
|
+
t.aspect = w / c.height;
|
|
273
|
+
return (texCache[key] = t);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function roundRect(x, l, t, w, h, r) {
|
|
277
|
+
x.beginPath();
|
|
278
|
+
x.moveTo(l + r, t); x.lineTo(l + w - r, t); x.quadraticCurveTo(l + w, t, l + w, t + r);
|
|
279
|
+
x.lineTo(l + w, t + h - r); x.quadraticCurveTo(l + w, t + h, l + w - r, t + h);
|
|
280
|
+
x.lineTo(l + r, t + h); x.quadraticCurveTo(l, t + h, l, t + h - r);
|
|
281
|
+
x.lineTo(l, t + r); x.quadraticCurveTo(l, t, l + r, t);
|
|
282
|
+
x.closePath();
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function planeGeometry() {
|
|
286
|
+
if (geomCache.plane) return geomCache.plane;
|
|
287
|
+
var g = new C.BufferGeometry();
|
|
288
|
+
g.setAttribute("position", new C.Attr(new Float32Array([-0.5, -0.5, 0, 0.5, -0.5, 0, 0.5, 0.5, 0, -0.5, 0.5, 0]), 3));
|
|
289
|
+
g.setAttribute("normal", new C.Attr(new Float32Array([0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1]), 3));
|
|
290
|
+
g.setAttribute("uv", new C.Attr(new Float32Array([0, 0, 1, 0, 1, 1, 0, 1]), 2));
|
|
291
|
+
g.setIndex([0, 1, 2, 0, 2, 3]);
|
|
292
|
+
return (geomCache.plane = g);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function sphereGeometry(r, detail) {
|
|
296
|
+
var key = r + ":" + detail;
|
|
297
|
+
return geomCache[key] || (geomCache[key] = new C.Sphere(r, detail, Math.max(4, detail - 4)));
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function billboard(renderer, scene, camera) {
|
|
301
|
+
this.quaternion.copy(camera.quaternion);
|
|
302
|
+
this.updateMatrixWorld();
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// A camera-facing quad. Lambert with black diffuse + emissive map behaves like an unlit sprite.
|
|
306
|
+
function quad(tex, color, size, additive, opacity) {
|
|
307
|
+
var m = new C.Material({ color: 0x000000, emissive: color, emissiveMap: tex, map: tex, transparent: true,
|
|
308
|
+
depthWrite: false, opacity: opacity });
|
|
309
|
+
if (additive) m.blending = ADDITIVE;
|
|
310
|
+
var mesh = new C.Mesh(planeGeometry(), m);
|
|
311
|
+
mesh.scale.set(size * (tex.aspect || 1), size, 1);
|
|
312
|
+
mesh.onBeforeRender = billboard;
|
|
313
|
+
mesh.renderOrder = additive ? 1 : 2;
|
|
314
|
+
return mesh;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function makeNode(n) {
|
|
318
|
+
var g = new C.Object3D();
|
|
319
|
+
var color = n.color;
|
|
320
|
+
var core = new C.Mesh(sphereGeometry(n.radius, n.type === "memory" ? 8 : 16),
|
|
321
|
+
new C.Material({ color: color, emissive: color, emissiveIntensity: n.type === "hub" ? 1.5 : 0.85, transparent: true,
|
|
322
|
+
opacity: n.type === "hub" ? 0.98 : 0.95 }));
|
|
323
|
+
g.add(core);
|
|
324
|
+
var halo = quad(haloTexture(), color, haloBaseSize(n), true, haloOpacity(n));
|
|
325
|
+
g.add(halo);
|
|
326
|
+
var label = null;
|
|
327
|
+
if (n.type === "project" || n.type === "agent" || n.type === "hub") {
|
|
328
|
+
var text = n.type === "agent" ? agentShort(n.agent) + " · " + (n.name || "") : n.name;
|
|
329
|
+
if (text.length > 26) text = text.slice(0, 25) + "…";
|
|
330
|
+
label = quad(labelTexture(text, n.type === "agent" ? color : "#cecac3"), 0xffffff, n.type === "agent" ? 5 : n.type === "hub" ? 7.5 : 6.5, false, 0.95);
|
|
331
|
+
label.position.y = n.radius + (n.type === "agent" ? 6 : n.type === "hub" ? 10 : 8);
|
|
332
|
+
g.add(label);
|
|
333
|
+
}
|
|
334
|
+
n.parts = { core: core, halo: halo, label: label, group: g };
|
|
335
|
+
if (n.bloomAt) g.scale.setScalar(0.001);
|
|
336
|
+
return g;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function tooltip(n) {
|
|
340
|
+
if (n.type === "memory") return '<b style="color:' + n.color + '">' + esc(n.kind) + "</b> " + esc(n.title) + "<br><small>" + esc(n.project || "global") + "</small>";
|
|
341
|
+
if (n.type === "project") return "<b>" + esc(n.name) + "</b><br><small>" + (n.memories || 0) + " memories · " + (n.activity || 0) + " events 7d</small>";
|
|
342
|
+
if (n.type === "agent") return '<b style="color:' + n.color + '">' + esc(n.agent) + "</b> · " + esc(n.name || "") + "<br><small>" +
|
|
343
|
+
(n.active ? "active" : "idle") + (n.connected ? " · mcp connected" : "") + "</small>";
|
|
344
|
+
return "<b>Brain</b><br><small>where your knowledge meets the models</small>";
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// ---------------------------------------------------------------- graph merge (keeps positions and three objects)
|
|
348
|
+
function setGraph(g) {
|
|
349
|
+
if (!ready) { pendingGraph = g; return; }
|
|
350
|
+
var now = performance.now();
|
|
351
|
+
var next = [], byId = {};
|
|
352
|
+
function keep(id, props) {
|
|
353
|
+
var n = nodeById[id] || { id: id, phase: Math.random() * Math.PI * 2 };
|
|
354
|
+
Object.assign(n, props);
|
|
355
|
+
next.push(n); byId[id] = n;
|
|
356
|
+
return n;
|
|
357
|
+
}
|
|
358
|
+
keep("__brain__", { type: "hub", name: "Brain", radius: 12, color: "#efebe4", baseGlow: 1, val: 18, fx: 0, fy: 0, fz: 0 });
|
|
359
|
+
var side = 1;
|
|
360
|
+
(g.projects || []).forEach(function (p) {
|
|
361
|
+
var n = keep("p:" + p.id, { type: "project", project: p.id, name: p.name, path: p.path, memories: p.memories, activity: p.activity,
|
|
362
|
+
radius: Math.min(9, 4.2 + Math.sqrt(p.memories || 0) * 0.7), color: p.id === "other" ? "#66635d" : "#9c9890", baseGlow: 0.55, val: 10 });
|
|
363
|
+
if (!n.side) { n.side = side; side = -side; }
|
|
364
|
+
if (n.x === undefined) { n.x = n.side * R * 0.5 + (Math.random() - 0.5) * 40; n.y = (Math.random() - 0.5) * 60; n.z = (Math.random() - 0.5) * 60; }
|
|
365
|
+
});
|
|
366
|
+
(g.memories || []).forEach(function (m) {
|
|
367
|
+
var fresh = !nodeById["m:" + m.id];
|
|
368
|
+
var n = keep("m:" + m.id, { type: "memory", mid: m.id, title: m.title, kind: m.kind, cluster: m.cluster, project: m.project,
|
|
369
|
+
radius: 1.5, color: KIND_COLORS[m.kind] || "#9c9890", baseGlow: 0.42, val: 1 });
|
|
370
|
+
if (fresh) {
|
|
371
|
+
var parent = byId[m.cluster === "global" ? "__brain__" : "p:" + m.cluster];
|
|
372
|
+
if (parent && parent.x !== undefined) {
|
|
373
|
+
n.x = parent.x + (Math.random() - 0.5) * 8; n.y = parent.y + (Math.random() - 0.5) * 8; n.z = parent.z + (Math.random() - 0.5) * 8;
|
|
374
|
+
}
|
|
375
|
+
if (nodes.length) { // bloom in, except on the first load
|
|
376
|
+
n.bloomAt = now; anims.push({ node: n, kind: "bloom", start: now, dur: 1100 }); }
|
|
377
|
+
delete pendingBloom[m.id];
|
|
378
|
+
}
|
|
379
|
+
});
|
|
380
|
+
(g.agents || []).forEach(function (a) {
|
|
381
|
+
keep("a:" + a.id, { type: "agent", agent: a.agent, session: a.session_id, project: a.project, name: a.project_name,
|
|
382
|
+
active: a.active, connected: a.connected, radius: 3.2, color: agentColor(a.agent), baseGlow: a.active ? 0.9 : 0.5, val: 4 });
|
|
383
|
+
});
|
|
384
|
+
var nextLinks = [], nextKeys = {};
|
|
385
|
+
function link(src, dst, kind, color, spread, suffix) {
|
|
386
|
+
var key = src + ">" + dst + (suffix || "");
|
|
387
|
+
var l = linkByKey[key] || { source: src, target: dst };
|
|
388
|
+
l.kind = kind; l.color = color; l.spread = spread || 0; l.particles = 0;
|
|
389
|
+
nextLinks.push(l); nextKeys[key] = l;
|
|
390
|
+
}
|
|
391
|
+
var perCluster = {};
|
|
392
|
+
next.forEach(function (n) {
|
|
393
|
+
if (n.type === "memory") {
|
|
394
|
+
var parent = n.cluster === "global" ? "__brain__" : "p:" + n.cluster;
|
|
395
|
+
if (!byId[parent]) return;
|
|
396
|
+
var k = perCluster[parent] = (perCluster[parent] || 0) + 1;
|
|
397
|
+
link(parent, n.id, n.cluster === "global" ? "hm" : "pm", n.cluster === "global" ? "#4a463f" : "#3a3631", Math.sqrt(k) * 2.2);
|
|
398
|
+
} else if (n.type === "agent") {
|
|
399
|
+
link(n.id, "__brain__", "ah", n.color);
|
|
400
|
+
} else if (n.type === "project") {
|
|
401
|
+
link("__brain__", n.id, "hp", "#4a463f");
|
|
402
|
+
}
|
|
403
|
+
});
|
|
404
|
+
(g.links || []).forEach(function (l) {
|
|
405
|
+
if (!byId["m:" + l.from_id] || !byId["m:" + l.to_id]) return;
|
|
406
|
+
link("m:" + l.from_id, "m:" + l.to_id, "ml", LINK_COLORS[l.type] || "#9c9890", 0, ":" + l.type);
|
|
407
|
+
var edge = nextLinks[nextLinks.length - 1];
|
|
408
|
+
edge.particles = /^(blocks|depends_on|supersedes)$/.test(l.type) ? 1 : 0;
|
|
409
|
+
edge.pwidth = 0.8; edge.pcolor = edge.color;
|
|
410
|
+
});
|
|
411
|
+
nodes = next; nodeById = byId; links = nextLinks; linkByKey = nextKeys;
|
|
412
|
+
syncHalos();
|
|
413
|
+
Graph.graphData({ nodes: nodes, links: links });
|
|
414
|
+
if (!fitted) fitSoon(1500);
|
|
415
|
+
startLoop();
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// ---------------------------------------------------------------- pulses
|
|
419
|
+
function findAgent(p) {
|
|
420
|
+
var exact = nodeById["a:" + p.agent + ":" + p.session_id];
|
|
421
|
+
if (exact) return exact;
|
|
422
|
+
var fam = String(p.agent || "").indexOf("codex") >= 0 ? "codex" : "claude", best = null;
|
|
423
|
+
nodes.forEach(function (n) {
|
|
424
|
+
if (n.type !== "agent") return;
|
|
425
|
+
var nf = n.agent.indexOf("codex") >= 0 ? "codex" : "claude";
|
|
426
|
+
if (nf === fam && (!best || n.project === p.project)) best = n;
|
|
427
|
+
});
|
|
428
|
+
return best;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
function flash(node, dur) {
|
|
432
|
+
if (!node) return;
|
|
433
|
+
anims.push({ node: node, kind: "flash", start: performance.now(), dur: dur || 1300 });
|
|
434
|
+
startLoop();
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
function emit(l, color, width) {
|
|
438
|
+
if (!l || REDUCED) return;
|
|
439
|
+
l.pcolor = color; l.pwidth = width;
|
|
440
|
+
try { Graph.emitParticle(l); } catch (e) { /* link not yet laid out */ }
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
function pulse(p) {
|
|
444
|
+
if (!ready) return;
|
|
445
|
+
var hub = nodeById.__brain__, project = p.project ? nodeById["p:" + p.project] : null;
|
|
446
|
+
var agent = findAgent(p), now = performance.now();
|
|
447
|
+
if (p.type === "ado") {
|
|
448
|
+
flash(hub, 1600);
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
if (p.type === "memory") {
|
|
452
|
+
if (agent) flash(agent, 900);
|
|
453
|
+
var memoryLink = agent && hub && linkByKey[agent.id + ">" + hub.id];
|
|
454
|
+
emit(memoryLink, agent ? agent.color : agentColor(p.agent), 2.6);
|
|
455
|
+
setTimeout(function () {
|
|
456
|
+
flash(hub, 1300);
|
|
457
|
+
pendingBloom[p.id] = 1;
|
|
458
|
+
var existing = nodeById["m:" + p.id];
|
|
459
|
+
if (existing) {
|
|
460
|
+
existing.bloomAt = performance.now();
|
|
461
|
+
anims.push({ node: existing, kind: "bloom", start: existing.bloomAt, dur: 1100 });
|
|
462
|
+
}
|
|
463
|
+
flash(project, 1600);
|
|
464
|
+
}, memoryLink && !REDUCED ? 950 : 0);
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
var key = (agent ? agent.id : "x") + p.type;
|
|
468
|
+
if (lastImpulse[key] && now - lastImpulse[key] < 350) return;
|
|
469
|
+
lastImpulse[key] = now;
|
|
470
|
+
var color = agent ? agent.color : agentColor(p.agent);
|
|
471
|
+
var small = p.type === "usage";
|
|
472
|
+
if (agent) flash(agent, 900);
|
|
473
|
+
if (agent) emit(linkByKey[agent.id + ">" + hub.id], color, small ? 1.6 : 2.6);
|
|
474
|
+
if (!project) return;
|
|
475
|
+
var delay = agent && hub && !REDUCED ? 950 : 0;
|
|
476
|
+
setTimeout(function () {
|
|
477
|
+
flash(hub, 1300);
|
|
478
|
+
emit(hub && linkByKey[hub.id + ">" + project.id], color, small ? 1.6 : 2.6);
|
|
479
|
+
var projectDelay = hub && !REDUCED ? 950 : 0;
|
|
480
|
+
setTimeout(function () {
|
|
481
|
+
flash(project, 1300);
|
|
482
|
+
var mem = links.filter(function (l) { return l.kind === "pm" && (l.source === project || l.source === project.id); });
|
|
483
|
+
for (var i = 0; i < Math.min(2, mem.length); i++) emit(mem[Math.floor(Math.random() * mem.length)], color, small ? 1.1 : 1.6);
|
|
484
|
+
}, projectDelay);
|
|
485
|
+
}, delay);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// ---------------------------------------------------------------- light animation loop (flash, bloom, twinkle)
|
|
489
|
+
function startLoop() {
|
|
490
|
+
if (loopOn || !active || document.hidden || !C) return;
|
|
491
|
+
loopOn = true;
|
|
492
|
+
requestAnimationFrame(loop);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
function loop(now) {
|
|
496
|
+
if (!active || document.hidden) { loopOn = false; return; }
|
|
497
|
+
for (var i = anims.length - 1; i >= 0; i--) {
|
|
498
|
+
var a = anims[i], t = Math.min(1, (now - a.start) / a.dur), n = a.node, parts = n.parts;
|
|
499
|
+
if (!parts) { if (now - a.start > 4000) anims.splice(i, 1); continue; }
|
|
500
|
+
if (a.kind === "bloom") {
|
|
501
|
+
var s = t < 1 ? 1 + 2.2 * Math.pow(t - 1, 3) + 1.2 * Math.pow(t - 1, 2) : 1; // ease-out-back
|
|
502
|
+
parts.group.scale.setScalar(Math.max(0.001, s));
|
|
503
|
+
parts.halo.material.opacity = haloOpacity(n) + (1 - t) * 1.2;
|
|
504
|
+
parts.halo.scale.setScalar(haloBaseSize(n) * (1 + (1 - t) * 2.5));
|
|
505
|
+
} else {
|
|
506
|
+
var f = 1 - t;
|
|
507
|
+
parts.core.material.emissiveIntensity = (n.type === "hub" ? 1.5 : 0.85) + f * 1.6;
|
|
508
|
+
parts.halo.material.opacity = haloOpacity(n) + f * 0.9;
|
|
509
|
+
var base = haloBaseSize(n);
|
|
510
|
+
parts.halo.scale.setScalar(base * (1 + f * 1.4));
|
|
511
|
+
}
|
|
512
|
+
if (t >= 1) {
|
|
513
|
+
if (a.kind === "bloom") { parts.group.scale.setScalar(1); n.bloomAt = 0; }
|
|
514
|
+
anims.splice(i, 1);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
// subtle twinkle and active-agent breathing at ~15 fps (halo opacity only)
|
|
518
|
+
var twinkle = !REDUCED && now - lastTwinkle > 66;
|
|
519
|
+
if (twinkle) {
|
|
520
|
+
lastTwinkle = now;
|
|
521
|
+
var tt = now / 1000;
|
|
522
|
+
for (var k = 0; k < nodes.length; k++) {
|
|
523
|
+
var m = nodes[k];
|
|
524
|
+
if (!m.parts || m.bloomAt || isAnimating(m)) continue;
|
|
525
|
+
if (m.type === "memory") m.parts.halo.material.opacity = haloOpacity(m) * (0.65 + 0.35 * Math.sin(tt * (0.7 + (m.phase % 1)) + m.phase));
|
|
526
|
+
else if (m.type === "agent" && m.active) m.parts.halo.material.opacity = haloOpacity(m) + (bloomActive() ? 0.15 : 0.35) * Math.sin(tt * 3 + m.phase);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
if (anims.length || !REDUCED) requestAnimationFrame(loop);
|
|
530
|
+
else loopOn = false;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
function isAnimating(n) {
|
|
534
|
+
for (var i = 0; i < anims.length; i++) if (anims[i].node === n) return true;
|
|
535
|
+
return false;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// ---------------------------------------------------------------- lifecycle
|
|
539
|
+
// Live updates keep reheating the layout, so the engine may never stop: fit on settle, after the
|
|
540
|
+
// first data and after a resize, but leave the camera alone for 20 s after the user orbits or zooms.
|
|
541
|
+
var USER_HOLD_MS = 20000;
|
|
542
|
+
|
|
543
|
+
function fit(ms) {
|
|
544
|
+
if (nodes.length <= 2 || Date.now() - lastUserMove < USER_HOLD_MS) return;
|
|
545
|
+
// The hub sits at the origin and the camera orbits it: set the distance so the hub, projects and agents
|
|
546
|
+
// fill the view; outlying memory leaves may run to the edges instead of shrinking the web.
|
|
547
|
+
var reach = 0;
|
|
548
|
+
nodes.forEach(function (n) {
|
|
549
|
+
if (n.type === "memory" || n.x === undefined) return;
|
|
550
|
+
reach = Math.max(reach, Math.sqrt(n.x * n.x + n.y * n.y + n.z * n.z) + (n.radius || 0));
|
|
551
|
+
});
|
|
552
|
+
if (!reach) return;
|
|
553
|
+
var first = !fitted; // jump on page load, glide on later refits
|
|
554
|
+
fitted = true;
|
|
555
|
+
var camera = Graph.camera(), rect = container.getBoundingClientRect();
|
|
556
|
+
var half = Math.PI * camera.fov / 360, aspect = rect.width / Math.max(1, rect.height);
|
|
557
|
+
var fitHalf = Math.min(half, Math.atan(Math.tan(half) * aspect));
|
|
558
|
+
var distance = reach / Math.sin(fitHalf) * 1.04;
|
|
559
|
+
var pos = Graph.cameraPosition(), len = Math.sqrt(pos.x * pos.x + pos.y * pos.y + pos.z * pos.z) || 1;
|
|
560
|
+
Graph.cameraPosition({ x: pos.x / len * distance, y: pos.y / len * distance, z: pos.z / len * distance }, { x: 0, y: 0, z: 0 }, first ? 0 : ms);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
function fitSoon(delay) {
|
|
564
|
+
clearTimeout(fitTimer);
|
|
565
|
+
fitTimer = setTimeout(function () { fit(700); }, delay);
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
["pointerdown", "wheel"].forEach(function (type) {
|
|
569
|
+
container.addEventListener(type, function () { lastUserMove = Date.now(); }, { passive: true });
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
function resize() {
|
|
573
|
+
var r = container.getBoundingClientRect();
|
|
574
|
+
if (r.width > 10 && r.height > 10) {
|
|
575
|
+
Graph.width(r.width).height(r.height);
|
|
576
|
+
resizeBloom(r.width, r.height);
|
|
577
|
+
if (fitted) fitSoon(300);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
function setActive(on) {
|
|
582
|
+
active = on;
|
|
583
|
+
if (on && !document.hidden) { Graph.resumeAnimation(); resize(); startLoop(); }
|
|
584
|
+
else Graph.pauseAnimation();
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
document.addEventListener("visibilitychange", function () {
|
|
588
|
+
if (!active) return;
|
|
589
|
+
if (document.hidden) Graph.pauseAnimation();
|
|
590
|
+
else { Graph.resumeAnimation(); startLoop(); }
|
|
591
|
+
});
|
|
592
|
+
if (window.ResizeObserver) new ResizeObserver(resize).observe(container);
|
|
593
|
+
resize();
|
|
594
|
+
bootstrap();
|
|
595
|
+
|
|
596
|
+
return { setGraph: setGraph, pulse: pulse, resize: resize, setActive: setActive, graph: Graph };
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
window.DevmemBrain3D = { available: function () { return !!window.ForceGraph3D && webglAvailable(); }, create: create };
|
|
600
|
+
})();
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Opt-in live room. Deliberately uses DOM nodes and textContent for all message text.
|
|
2
|
+
(function () {
|
|
3
|
+
"use strict";
|
|
4
|
+
var dataEl = document.getElementById("live-data");
|
|
5
|
+
if (!dataEl) return;
|
|
6
|
+
var data = JSON.parse(dataEl.textContent), items = document.getElementById("live-items"), agents = document.getElementById("live-agents"), form = document.getElementById("live-form"), input = document.getElementById("live-body"), note = document.getElementById("live-note"), error = document.getElementById("live-error"), scroll = document.getElementById("live-scroll"), first = true, messageSig, agentSig;
|
|
7
|
+
function el(name, cls, value) { var node = document.createElement(name); if (cls) node.className = cls; if (value !== undefined) node.textContent = value; return node; }
|
|
8
|
+
function localTime(value) { var date = new Date(value); return isNaN(date) ? value : date.toLocaleTimeString([], {hour:"2-digit", minute:"2-digit", hour12:false}); }
|
|
9
|
+
function dot(color) { var node = el("i", "agent-dot"); if (color) node.style.setProperty("--agent", color.hex); return node; }
|
|
10
|
+
function renderMessages() { var sig = JSON.stringify(data.messages); if (!first && sig === messageSig) return; messageSig = sig; var stick = scroll.scrollHeight - scroll.scrollTop - scroll.clientHeight < 80; items.replaceChildren(); if (!data.messages.length) items.append(el("p", "muted", "No messages in the last 2 hours.")); data.messages.forEach(function (m) { var box = el("article", "bubble " + (m.sender_kind === "user" ? "mine" : "theirs") + (m.color ? " agent-bubble" : "")), foot = el("div", "b-foot", localTime(m.created_at)), who = el("div", "b-who"); if (m.color) { box.style.setProperty("--agent", m.color.hex); who.append(dot(m.color)); } who.append(document.createTextNode(m.display_name)); box.append(who); if (m.kind === "event") box.append(el("div", "b-subject", m.headline)); box.append(el("div", "b-text", m.body)); if (m.kind === "question" && m.status === "open") foot.append(document.createTextNode(" · Question open")); foot.append(document.createTextNode(" · ")); var link = el("a", "", "Open"); link.href = "/messages/thread/" + encodeURIComponent(m.thread_id); foot.append(link); box.append(foot); if (m.sender_kind === "user" && m.recipient === "#live") { var delivery = el("div", "delivery"); if (m.delivery.length) m.delivery.forEach(function (s) { var line = el("span", "", s.state === "received" ? "Received by " : "Waiting for "); if (s.color) line.append(dot(s.color)); line.append(document.createTextNode(s.name)); delivery.append(line); }); else delivery.append(document.createTextNode("Not delivered")); box.append(delivery); } items.append(box); }); if (first || stick) scroll.scrollTop = scroll.scrollHeight; first = false; }
|
|
11
|
+
function renderAgents() { var sig = JSON.stringify([data.agents, data.connected]); if (sig === agentSig) return; agentSig = sig; agents.replaceChildren(); data.agents.forEach(function (a) { var row = el("div", "live-agent"), info = el("div", "live-agent-info"), name = el("span", "", a.name), status = el("small", "", a.status); row.title = a.key; if (a.color) row.append(dot(a.color)); info.append(name); if (a.status === "idle") status.append(document.createTextNode(" · will see messages at its next action")); info.append(status); row.append(info); if (a.connectable) { var button = el("button", "", a.connected ? "Disconnect" : "Connect"); button.addEventListener("click", function () { change(a); }); row.append(button); } agents.append(row); }); note.textContent = document.querySelector('input[name="project"]') ? "Ping @Codex or @Claude anywhere in your message to call that agent. Ping both for two replies. Names are case-insensitive." : "Automatic replies are disabled. Connected agents receive messages at their next action."; }
|
|
12
|
+
function refresh(read) { fetch("/api/live" + (read ? "?read=1" : "")).then(function (r) { return r.ok ? r.json() : Promise.reject(Error("live chat refresh failed")); }).then(function (d) { data = d; renderMessages(); renderAgents(); error.hidden = true; }).catch(function (e) { error.textContent = e.message; error.hidden = false; }); }
|
|
13
|
+
function change(a) { var fd = new FormData(); fd.append("agent_key", a.key); fetch(a.connected ? "/messages/live/disconnect" : "/messages/live/connect", {method:"POST", body:fd}).then(function (r) { if (!r.ok) throw Error("connection update failed"); refresh(!document.hidden); }).catch(function (e) { error.textContent = e.message; error.hidden = false; }); }
|
|
14
|
+
form.addEventListener("submit", function (e) { e.preventDefault(); if (!input.value.trim()) return; var button = form.querySelector("button"); button.disabled = true; fetch(form.action, {method:"POST", body:new FormData(form)}).then(function (r) { if (!r.ok) return r.json().then(function (x) { throw Error(x.detail || "send failed"); }); input.value = ""; refresh(!document.hidden); }).catch(function (e) { error.textContent = e.message; error.hidden = false; }).then(function () { button.disabled = false; input.focus(); }); });
|
|
15
|
+
input.addEventListener("keydown", function (e) { if (e.key === "Enter" && !e.shiftKey && !e.isComposing) { e.preventDefault(); form.requestSubmit(); } });
|
|
16
|
+
var stream = new EventSource("/api/stream?only=messages"), timer = setInterval(function () { refresh(!document.hidden); }, 10000);
|
|
17
|
+
stream.addEventListener("messages", function () { refresh(!document.hidden); }); window.addEventListener("visibilitychange", function () { if (!document.hidden) refresh(true); }); window.addEventListener("pagehide", function () { stream.close(); clearInterval(timer); }); renderMessages(); renderAgents(); if (!document.hidden) refresh(true);
|
|
18
|
+
}());
|