@agentprojectcontext/apx 1.71.0 → 1.73.0

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.
@@ -1,14 +1,14 @@
1
- import { useMemo } from "react";
1
+ import { useMemo, useState } from "react";
2
2
  import useSWR from "swr";
3
3
  import { NavLink, useNavigate } from "react-router-dom";
4
- import { Bot, Briefcase, FileCode2, Heart, MessagesSquare, Puzzle, Zap, Crown, Activity } from "lucide-react";
5
- import { Agents, Artifacts, Mcps, Routines, Tasks } from "../../lib/api";
4
+ import { Bot, Briefcase, Brain, FileCode2, Heart, MessagesSquare, Puzzle, Zap, Crown, Activity } from "lucide-react";
5
+ import { Agents, Artifacts, Conversations, Mcps, Routines, Tasks } from "../../lib/api";
6
6
  import { Section } from "../../components/Section";
7
7
  import { StatusIcon, StatusBadge, effectiveStatus, statusLabel, TASK_STATUS_ORDER } from "../../components/tasks/taskStatus";
8
8
  import { BrainGraph, type BrainNode, type BrainEdge } from "./AgentBrainGraph";
9
9
  import { cn } from "../../lib/cn";
10
10
  import { t } from "../../i18n";
11
- import type { AgentEntry } from "../../types/daemon";
11
+ import type { AgentEntry, RoutineEntry, TaskEntry } from "../../types/daemon";
12
12
 
13
13
  // Floor / mission control: a live per-project summary — what's here (agents,
14
14
  // automation), what's in flight (task workflow), and what just happened.
@@ -59,14 +59,6 @@ export function Overview({ pid }: { pid: string }) {
59
59
  </div>
60
60
  )}
61
61
 
62
- {/* Team brain — the whole agent map: orchestrators at the core, their
63
- specialists clustered around them as satellites, all connected. */}
64
- {agentList.length > 0 && (
65
- <Section title={t("project.overview.brain_title")} description={t("project.overview.brain_desc")} className="!p-4">
66
- <TeamBrain pid={pid} agents={agentList} navigate={navigate} />
67
- </Section>
68
- )}
69
-
70
62
  <div className="grid gap-4 lg:grid-cols-2">
71
63
  {/* Agent roster */}
72
64
  <Section title={t("project.overview.roster")} className="!p-4">
@@ -129,6 +121,15 @@ export function Overview({ pid }: { pid: string }) {
129
121
  <Card title={t("project.overview.mcps")} value={mcps.data?.length ?? "…"} href={`/p/${pid}/mcps`} icon={Puzzle} />
130
122
  <Card title={t("project.overview.routines")} value={routines.data?.length ?? "…"} href={`/p/${pid}/routines`} icon={Heart} />
131
123
  </div>
124
+
125
+ {/* Team brain — full-width at the bottom. Collapsed: the agent map.
126
+ Expanded: every agent's full sub-brain (memory / threads / tasks /
127
+ heartbeats), all connected by hierarchy. */}
128
+ {agentList.length > 0 && (
129
+ <Section title={t("project.overview.brain_title")} description={t("project.overview.brain_desc")} className="!p-4">
130
+ <TeamBrain pid={pid} agents={agentList} routines={routines.data ?? []} navigate={navigate} />
131
+ </Section>
132
+ )}
132
133
  </div>
133
134
  );
134
135
  }
@@ -147,14 +148,54 @@ function groupByArea(agents: AgentEntry[]): { area: string | null; agents: Agent
147
148
  .map(([area, agents]) => ({ area, agents }));
148
149
  }
149
150
 
150
- // Whole-project agent map. The core is the team; orchestrators hang off it as
151
- // hubs, and each specialist connects to its parent orchestrator (or the core if
152
- // unparented) — so teams read as satellite clusters. Click a node to open it.
151
+ // One memory-fact-per-line, trimmed of markdown noise (mirrors the per-agent brain).
152
+ function memoryFacts(text: string): string[] {
153
+ return text
154
+ .split("\n")
155
+ .map((l) => l.replace(/^[-*#>\s]+/, "").trim())
156
+ .filter((l) => l.length > 2 && !l.startsWith("```"))
157
+ .slice(0, 5);
158
+ }
159
+
160
+ interface TeamDetail {
161
+ tasks: TaskEntry[];
162
+ perAgent: Record<string, { memory: string; threads: { title: string; id: string }[] }>;
163
+ }
164
+
165
+ // Whole-project agent map. Collapsed, the core is the team and every agent hangs
166
+ // off its parent (orchestrators → specialists) as satellite clusters. Expanded,
167
+ // each agent becomes a hub with its own sub-brain — memory / threads / tasks /
168
+ // heartbeats — so the whole company reads as one connected brain-of-brains.
153
169
  function TeamBrain({
154
- pid, agents, navigate,
170
+ pid, agents, routines, navigate,
155
171
  }: {
156
- pid: string; agents: AgentEntry[]; navigate: (to: string) => void;
172
+ pid: string; agents: AgentEntry[]; routines: RoutineEntry[]; navigate: (to: string) => void;
157
173
  }) {
174
+ const [expanded, setExpanded] = useState(false);
175
+
176
+ // Only fetch the heavy per-agent data when the user asks to expand.
177
+ const detail = useSWR<TeamDetail | null>(
178
+ expanded ? `/team-brain/${pid}/${agents.map((a) => a.slug).join(",")}` : null,
179
+ async () => {
180
+ const tasks = await Tasks.list(pid, "all");
181
+ const entries = await Promise.all(
182
+ agents.map(async (a) => {
183
+ const [d, threads] = await Promise.all([
184
+ Agents.get(pid, a.slug).catch(() => null),
185
+ Conversations.list(pid, a.slug).catch(() => []),
186
+ ]);
187
+ return [a.slug, {
188
+ memory: d?.memory || "",
189
+ threads: (threads || []).slice(0, 4).map((th) => ({ title: th.title || th.filename, id: th.id })),
190
+ }] as const;
191
+ }),
192
+ );
193
+ return { tasks, perAgent: Object.fromEntries(entries) };
194
+ },
195
+ );
196
+
197
+ const showFull = expanded && !!detail.data;
198
+
158
199
  const { nodes, edges } = useMemo(() => {
159
200
  const nodes: BrainNode[] = [];
160
201
  const edges: BrainEdge[] = [];
@@ -171,7 +212,8 @@ function TeamBrain({
171
212
  label: a.slug,
172
213
  slug: a.slug,
173
214
  kind: isOrch ? "agent" : "agentlink",
174
- role: isOrch || hasKids(a.slug) ? "hub" : "leaf",
215
+ // In full mode every agent is a hub (it carries its own sub-brain).
216
+ role: showFull || isOrch || hasKids(a.slug) ? "hub" : "leaf",
175
217
  emoji: a.emoji || undefined,
176
218
  relation: a.role || (isOrch ? t("project.agents.orchestrator") : t("project.overview.specialists")),
177
219
  detail: a.description || undefined,
@@ -181,14 +223,47 @@ function TeamBrain({
181
223
  const parent = a.parent && slugs.has(a.parent) ? a.parent : ROOT;
182
224
  edges.push({ source: parent, target: a.slug });
183
225
  }
226
+
227
+ // Expanded: graft each agent's own items as leaves off the agent node.
228
+ if (showFull && detail.data) {
229
+ const { tasks, perAgent } = detail.data;
230
+ const push = (id: string, label: string, kind: BrainNode["kind"], parent: string, detailText?: string) => {
231
+ nodes.push({ id, label, kind, detail: detailText });
232
+ edges.push({ source: parent, target: id });
233
+ };
234
+ for (const a of agents) {
235
+ const info = perAgent[a.slug];
236
+ memoryFacts(info?.memory || "").forEach((f, i) => push(`${a.slug}:m${i}`, f, "memory", a.slug, f));
237
+ (info?.threads || []).forEach((th, i) => push(`${a.slug}:th${i}`, th.title, "thread", a.slug));
238
+ tasks.filter((tk) => tk.agent === a.slug).slice(0, 4)
239
+ .forEach((tk, i) => push(`${a.slug}:ts${i}`, tk.title, "task", a.slug, tk.body || undefined));
240
+ routines.filter((r) => (r.spec as { agent?: string })?.agent === a.slug).slice(0, 2)
241
+ .forEach((r, i) => push(`${a.slug}:rt${i}`, r.name, "routine", a.slug, `schedule: ${r.schedule}`));
242
+ }
243
+ }
184
244
  return { nodes, edges };
185
- }, [agents]);
245
+ }, [agents, routines, showFull, detail.data]);
246
+
247
+ const toggle = (
248
+ <button
249
+ type="button"
250
+ onClick={() => setExpanded((e) => !e)}
251
+ className={cn(
252
+ "inline-flex items-center gap-1.5 rounded-md border px-2 py-1 text-[11px] font-medium backdrop-blur transition-colors",
253
+ expanded ? "border-primary/40 bg-primary/15 text-foreground" : "border-border bg-card/80 text-muted-fg hover:text-foreground",
254
+ )}
255
+ >
256
+ <Brain className={cn("size-3.5", detail.isLoading && "animate-pulse")} />
257
+ {expanded ? t("agents_ui.brain_collapse") : t("agents_ui.brain_expand")}
258
+ </button>
259
+ );
186
260
 
187
261
  return (
188
262
  <BrainGraph
189
263
  nodes={nodes}
190
264
  edges={edges}
191
- height={520}
265
+ height={620}
266
+ toolbar={toggle}
192
267
  onNodeClick={(n) => { if (n.slug) navigate(`/p/${pid}/agents/${n.slug}`); }}
193
268
  />
194
269
  );