@agentprojectcontext/apx 1.72.0 → 1.73.1

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.
@@ -18,8 +18,8 @@
18
18
  <link rel="apple-touch-icon" href="/favicon/dark/apple-touch-icon.png" media="(prefers-color-scheme: dark)" />
19
19
  <link rel="manifest" href="/favicon/white/site.webmanifest" media="(prefers-color-scheme: light)" />
20
20
  <link rel="manifest" href="/favicon/dark/site.webmanifest" media="(prefers-color-scheme: dark)" />
21
- <script type="module" crossorigin src="/assets/index-DVxJfrii.js"></script>
22
- <link rel="stylesheet" crossorigin href="/assets/index-vSNUqL56.css">
21
+ <script type="module" crossorigin src="/assets/index-CqlRznhf.js"></script>
22
+ <link rel="stylesheet" crossorigin href="/assets/index-CUeIhw7z.css">
23
23
  </head>
24
24
  <body class="bg-background text-foreground antialiased">
25
25
  <div id="root"></div>
@@ -1495,6 +1495,9 @@ export const en = {
1495
1495
  brain_pan_hint: "scroll to zoom · drag background to pan",
1496
1496
  brain_expand: "Expand brains",
1497
1497
  brain_collapse: "Collapse",
1498
+ brain_open: "Open",
1499
+ brain_part_of: "Part of",
1500
+ brain_branches: "Branches",
1498
1501
  config_def_desc: "definition (frontmatter + system prompt).",
1499
1502
  memory_durable_desc: "durable facts the agent remembers.",
1500
1503
  running: "running",
@@ -1493,6 +1493,9 @@ export const es = {
1493
1493
  brain_pan_hint: "scroll para zoom · arrastrá el fondo para mover",
1494
1494
  brain_expand: "Expandir cerebros",
1495
1495
  brain_collapse: "Colapsar",
1496
+ brain_open: "Abrir",
1497
+ brain_part_of: "Parte de",
1498
+ brain_branches: "Ramas",
1496
1499
  config_def_desc: "definición (frontmatter + system prompt).",
1497
1500
  memory_durable_desc: "hechos durables que el agente recuerda.",
1498
1501
  running: "running",
@@ -53,6 +53,12 @@ function kindLabel(k: BrainKind): string {
53
53
  const RADIUS: Record<BrainRole, number> = { core: 24, hub: 12, leaf: 6 };
54
54
  const roleOf = (n: BrainNode): BrainRole => n.role ?? "leaf";
55
55
  const clamp = (v: number, lo: number, hi: number) => Math.max(lo, Math.min(hi, v));
56
+ const uniqById = <T extends { id: string }>(arr: T[]): T[] => {
57
+ const m = new Map<string, T>();
58
+ for (const n of arr) m.set(n.id, n);
59
+ return [...m.values()];
60
+ };
61
+ const clip = (s: string, n = 26) => (s.length > n ? `${s.slice(0, n)}…` : s);
56
62
 
57
63
  export function BrainGraph({
58
64
  nodes, edges, height = 520, onNodeClick, toolbar,
@@ -72,6 +78,9 @@ export function BrainGraph({
72
78
  const linksRef = useRef<SimLink[]>([]);
73
79
  const dragRef = useRef<SimNode | null>(null);
74
80
  const panRef = useRef<{ x: number; y: number } | null>(null);
81
+ // Press bookkeeping so a drag is never mistaken for a click (which navigates).
82
+ const downRef = useRef<{ x: number; y: number } | null>(null);
83
+ const movedRef = useRef(false);
75
84
  const viewRef = useRef({ tx: 0, ty: 0, k: 1 });
76
85
  const fitRef = useRef<() => void>(() => {});
77
86
  const [, setVersion] = useState(0);
@@ -188,6 +197,8 @@ export function BrainGraph({
188
197
  if (roleOf(n) === "core") return;
189
198
  e.stopPropagation();
190
199
  dragRef.current = n;
200
+ downRef.current = { x: e.clientX, y: e.clientY };
201
+ movedRef.current = false;
191
202
  (e.target as Element).setPointerCapture?.(e.pointerId);
192
203
  simRef.current?.alphaTarget(0.3).restart();
193
204
  };
@@ -197,6 +208,8 @@ export function BrainGraph({
197
208
  };
198
209
  const onMove = (e: React.PointerEvent) => {
199
210
  if (dragRef.current) {
211
+ const d = downRef.current;
212
+ if (d && !movedRef.current && Math.hypot(e.clientX - d.x, e.clientY - d.y) > 4) movedRef.current = true;
200
213
  const w = worldFromClient(e.clientX, e.clientY);
201
214
  dragRef.current.fx = w.x; dragRef.current.fy = w.y;
202
215
  return;
@@ -216,6 +229,8 @@ export function BrainGraph({
216
229
  panRef.current = null;
217
230
  simRef.current?.alphaTarget(0);
218
231
  };
232
+ // Native click fires reliably on pointerup; skip it when the press was a drag.
233
+ const onNodeClickGuarded = (n: SimNode) => () => { if (!movedRef.current) pick(n); };
219
234
 
220
235
  const simNodes = nodesRef.current;
221
236
  const links = linksRef.current;
@@ -223,6 +238,13 @@ export function BrainGraph({
223
238
  const legendKinds = [...new Set(nodes.map((n) => n.kind))].filter((k) => k !== "agent" && k !== "hub");
224
239
  const pick = (n: SimNode) => { setSelected(n); onNodeClick?.(n); };
225
240
 
241
+ // For the detail panel: what the selected node hangs off (parents) and the
242
+ // branches that hang off it (children), derived from the live edges.
243
+ const selParents = selected ? uniqById(links.filter((l) => l.target.id === selected.id).map((l) => l.source)) : [];
244
+ const selChildren = selected ? uniqById(links.filter((l) => l.source.id === selected.id).map((l) => l.target)) : [];
245
+ // The detail line is only meaningful when it adds something beyond the title.
246
+ const selDetail = selected?.detail && selected.detail.trim() !== selected.label.trim() ? selected.detail : null;
247
+
226
248
  const CtrlBtn = ({ onClick, title, children }: { onClick: () => void; title: string; children: React.ReactNode }) => (
227
249
  <button type="button" title={title} onClick={onClick}
228
250
  className="grid size-7 place-items-center rounded-md border border-border bg-card/80 text-muted-fg backdrop-blur hover:text-foreground">
@@ -329,7 +351,7 @@ export function BrainGraph({
329
351
  const showLabel = isHub || isSel || !hideLeafLabels;
330
352
  return (
331
353
  <g key={n.id} transform={`translate(${n.x},${n.y})`} className="cursor-grab active:cursor-grabbing"
332
- onPointerDown={onNodeDown(n)} onClick={() => pick(n)}>
354
+ onPointerDown={onNodeDown(n)} onClick={onNodeClickGuarded(n)}>
333
355
  <circle r={r} fill={color} filter="url(#brain-glow)" opacity={0.3}>
334
356
  <animate attributeName="r" values={`${r};${r + 6};${r}`} dur={`${beat}s`} begin={begin} repeatCount="indefinite" />
335
357
  <animate attributeName="opacity" values="0.32;0.08;0.32" dur={`${beat}s`} begin={begin} repeatCount="indefinite" />
@@ -364,16 +386,63 @@ export function BrainGraph({
364
386
  </div>
365
387
 
366
388
  {selected && (
367
- <div className="rounded-lg border border-border bg-card p-3 text-xs">
368
- <div className="flex items-center gap-2">
369
- <span className="size-2 rounded-full" style={{ background: KIND_COLOR[selected.kind] }} />
370
- {selected.emoji && <span>{selected.emoji}</span>}
371
- <span className="font-medium">{selected.label}</span>
389
+ <div className="space-y-2.5 rounded-lg border border-border bg-card p-3 text-xs">
390
+ {/* The clicked node: title, type, relation */}
391
+ <div className="flex flex-wrap items-center gap-2">
392
+ <span className="size-2.5 rounded-full" style={{ background: KIND_COLOR[selected.kind] }} />
393
+ {selected.emoji && <span className="text-sm leading-none">{selected.emoji}</span>}
394
+ <span className="text-[13px] font-semibold">{selected.label}</span>
395
+ <span className="rounded bg-muted px-1.5 py-0.5 text-[10px] uppercase tracking-wide text-muted-fg">
396
+ {kindLabel(selected.kind)}
397
+ </span>
372
398
  {selected.relation && <span className="text-muted-fg">· {selected.relation}</span>}
399
+ <div className="ml-auto flex items-center gap-2">
400
+ {selected.slug && onNodeClick && (
401
+ <button type="button" onClick={() => onNodeClick(selected)} className="text-primary hover:underline">
402
+ {t("agents_ui.brain_open")}
403
+ </button>
404
+ )}
405
+ <button type="button" onClick={() => setSelected(null)} className="text-muted-fg hover:text-foreground">✕</button>
406
+ </div>
373
407
  </div>
374
- {selected.detail && <p className="mt-1 whitespace-pre-wrap text-muted-fg">{selected.detail}</p>}
408
+
409
+ {/* Internal info — only when it adds something beyond the title */}
410
+ {selDetail && <p className="whitespace-pre-wrap text-muted-fg">{selDetail}</p>}
411
+
412
+ {/* Where it hangs from */}
413
+ {selParents.length > 0 && (
414
+ <div className="flex flex-wrap items-center gap-1.5">
415
+ <span className="text-[10px] uppercase tracking-wide text-muted-fg/70">{t("agents_ui.brain_part_of")}</span>
416
+ {selParents.map((p) => <NeighborChip key={p.id} node={p} onClick={() => setSelected(p)} />)}
417
+ </div>
418
+ )}
419
+
420
+ {/* Branches that follow it */}
421
+ {selChildren.length > 0 && (
422
+ <div className="flex flex-wrap items-center gap-1.5">
423
+ <span className="text-[10px] uppercase tracking-wide text-muted-fg/70">
424
+ {t("agents_ui.brain_branches")} · {selChildren.length}
425
+ </span>
426
+ {selChildren.map((c) => <NeighborChip key={c.id} node={c} onClick={() => setSelected(c)} />)}
427
+ </div>
428
+ )}
375
429
  </div>
376
430
  )}
377
431
  </div>
378
432
  );
379
433
  }
434
+
435
+ // A clickable chip for a connected node in the detail panel.
436
+ function NeighborChip({ node, onClick }: { node: BrainNode; onClick: () => void }) {
437
+ return (
438
+ <button
439
+ type="button"
440
+ onClick={onClick}
441
+ className="inline-flex max-w-[220px] items-center gap-1 rounded-md border border-border bg-muted/40 px-1.5 py-0.5 text-[11px] hover:border-muted-fg/50 hover:bg-muted"
442
+ >
443
+ <span className="size-1.5 shrink-0 rounded-full" style={{ background: KIND_COLOR[node.kind] }} />
444
+ {node.emoji && <span className="leading-none">{node.emoji}</span>}
445
+ <span className="truncate">{clip(node.label, 28)}</span>
446
+ </button>
447
+ );
448
+ }