@geongyu09/paseo-agent-dashboard 1.1.0 → 1.1.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.
package/OVERVIEW.md CHANGED
@@ -13,7 +13,7 @@ The switch in the header (**Haiku summaries on** or **Haiku summaries off**) tur
13
13
  - Claude Haiku writes the title and the **Now** line (**Last** when the agent is not running) from the agent's last 150 timeline items. The title (up to 40 characters) says what the session works on, and **Now** (up to 60 characters) says what the agent is actually doing. Until the first summary arrives, the card shows Paseo's title and the latest tool call, answer or thought. After that, a card keeps its last summary until the next one replaces it.
14
14
  - While an agent runs, its latest tool call shows live, in the **Tool** line once a summary fills **Now**, and in **Now** itself before that. The **Step** line shows the current step of the agent's todo list when it has one.
15
15
  - While an agent compacts its context, its status reads **Compacting** in purple and the **Compact** line reads **Compacting the conversation** until the compaction ends.
16
- - A subagent that an agent starts inside its own session, such as one from Claude's Agent tool, gets a smaller card under that agent, joined by an arrow. The card shows **Subagent** with the subagent type, the task description, Paseo's line of model, effort and tokens, and **Working**, **Done**, **Error** or **Stopped**. Pressing it opens the agent that started it, and pressing its arrow lists the messages the subagent was given. Running subagents and the ones started since the agent's latest prompt show, and older ones show with **Ended agents (24h)** on.
16
+ - A subagent that an agent starts inside its own session, such as one from Claude's Agent tool, gets a smaller card under that agent, joined by an arrow. The card shows **Subagent** with the subagent type, the task description, Paseo's line of model, effort and tokens, and **Working**, **Done**, **Error** or **Stopped**. Pressing it opens Paseo's own tab for the subagent on Paseo versions whose plugin navigation can open one, and otherwise shows the subagent's conversation over the board: the messages it was given, its answers, its tool calls and errors, refreshed every 2 seconds while it works. **Open parent agent** in that view opens the agent that started it. Pressing its arrow lists the messages the subagent was given. Running subagents and the ones started since the agent's latest prompt show, and older ones show with **Ended agents (24h)** on.
17
17
  - The header counts all visible agents, the working ones, and the ones that need attention because of a pending permission or an error. Subagents are not counted.
18
18
 
19
19
  ## Arrows and instructions
@@ -46,7 +46,7 @@ Summaries run Claude Code in print mode (`claude -p`) with the Haiku model on th
46
46
 
47
47
  Opening an instruction box with summaries on also sends each listed instruction, up to its first 8,000 characters, for a summary. This happens once per instruction text and app language while the plugin runs, and a failed call is tried again the next time the box shows that instruction. At most two Claude calls run at once, and each one stops after 60 seconds. Summaries are kept in memory only and are lost when the plugin reloads.
48
48
 
49
- For subagent cards, the plugin's server connects to the daemon on its own machine, at the `daemon.listen` address in the Paseo home's `config.json`, and reads the subagents of the shown agents and the messages each subagent was given. It reads them every 2 seconds while an agent or a subagent runs, and every 20 seconds otherwise.
49
+ For subagent cards, the plugin's server connects to the daemon on its own machine, at the `daemon.listen` address in the Paseo home's `config.json`, and reads the subagents of the shown agents, the messages each subagent was given, and the whole conversation of a subagent whose conversation you open. It reads them every 2 seconds while an agent or a subagent runs, and every 20 seconds otherwise.
50
50
 
51
51
  To color workspace labels, the plugin reads Paseo's label file, `projects/workspace-labels.json` in the Paseo home folder (`~/.paseo` unless `PASEO_HOME` is set), on the daemon machine. It uses only each label's name and color. If the file cannot be read, labels are gray.
52
52
 
package/OVERVIEW.zh-CN.md CHANGED
@@ -13,7 +13,7 @@
13
13
  - Claude Haiku 根据智能体最近 150 条时间线记录生成标题和 **当前** 行(智能体未运行时为 **最后**)。标题(最多 40 个字符)说明本次会话在做的工作,**当前**(最多 60 个字符)说明智能体实际正在做什么。第一份总结到达之前,卡片显示 Paseo 的标题以及最新的工具调用、回答或思考。之后,卡片会保留上一份总结,直到新的总结取而代之。
14
14
  - 智能体运行时,其最新的工具调用会实时显示。总结填入 **当前** 后,它显示在 **工具** 行,在此之前直接显示在 **当前** 行。智能体有待办列表时,**步骤** 行显示当前步骤。
15
15
  - 智能体压缩上下文时,状态显示为紫色的 **压缩中**,**压缩** 行显示 **正在压缩对话记录**,直到压缩结束。
16
- - 智能体在自己的会话中启动的提供方子智能体(例如 Claude 的 Agent 工具启动的子智能体)会在该智能体下方显示为一张较小的卡片,并以箭头相连。卡片显示 **子智能体** 和子智能体类型、任务说明、Paseo 给出的模型、推理强度和 token 数,以及 **工作中**、**已完成**、**出错** 或 **已中断**。点按卡片会打开启动它的智能体,点按它的箭头会列出该子智能体收到的消息。运行中的子智能体和在智能体最近一条提示之后启动的子智能体会显示,更早的子智能体在打开 **已结束的智能体(24 小时)** 时显示。
16
+ - 智能体在自己的会话中启动的提供方子智能体(例如 Claude 的 Agent 工具启动的子智能体)会在该智能体下方显示为一张较小的卡片,并以箭头相连。卡片显示 **子智能体** 和子智能体类型、任务说明、Paseo 给出的模型、推理强度和 token 数,以及 **工作中**、**已完成**、**出错** 或 **已中断**。如果 Paseo 版本的插件导航可以打开子智能体标签页,点按卡片会打开 Paseo 自带的子智能体标签页,否则会在看板上方显示该子智能体的对话,其中包括它收到的消息、它的回答、工具调用和错误,子智能体工作时每 2 秒刷新一次。在对话中点按 **打开父智能体** 会打开启动它的智能体。点按它的箭头会列出该子智能体收到的消息。运行中的子智能体和在智能体最近一条提示之后启动的子智能体会显示,更早的子智能体在打开 **已结束的智能体(24 小时)** 时显示。
17
17
  - 标题栏统计所有可见的智能体、正在工作的智能体,以及因待处理的授权或出错而需要处理的智能体。子智能体不计入统计。
18
18
 
19
19
  ## 箭头和指令
@@ -46,7 +46,7 @@
46
46
 
47
47
  在开启总结时打开指令框,还会发送框中列出的每条指令(最多取前 8,000 个字符)以生成总结。插件运行期间,每条指令文本在每种应用语言下只总结一次,调用失败时,下次框中显示该指令时会重试。同时最多运行两个 Claude 调用,每个调用 60 秒后停止。总结只保存在内存中,插件重新加载后即丢失。
48
48
 
49
- 为了显示子智能体卡片,插件的服务端会连接同一台机器上的守护进程,地址为 Paseo 主目录中 `config.json` 的 `daemon.listen`,并读取所显示智能体的子智能体,以及每个子智能体收到的消息。有智能体或子智能体在运行时每 2 秒读取一次,否则每 20 秒读取一次。
49
+ 为了显示子智能体卡片,插件的服务端会连接同一台机器上的守护进程,地址为 Paseo 主目录中 `config.json` 的 `daemon.listen`,并读取所显示智能体的子智能体、每个子智能体收到的消息,以及你打开对话的子智能体的完整对话。有智能体或子智能体在运行时每 2 秒读取一次,否则每 20 秒读取一次。
50
50
 
51
51
  为了给工作区标签着色,插件会读取守护进程所在机器上的 Paseo 标签文件,即 Paseo 主目录中的 `projects/workspace-labels.json`(未设置 `PASEO_HOME` 时为 `~/.paseo`)。插件只使用每个标签的名称和颜色。无法读取该文件时,标签显示为灰色。
52
52
 
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Agent board
2
2
 
3
- Agent board is a Paseo plugin that shows the agents of the current project as a live tree of cards. Each card shows the agent's status, provider, model, effort, permission mode and context use. Agents started by another agent sit beside their parent, joined by an arrow, and pressing the arrow shows the instructions the parent sent. Subagents an agent starts inside its own session, such as those from Claude's Agent tool, get smaller cards under it. Optional summaries from Claude Haiku give each card a short title and a line about what the agent is doing now. See [OVERVIEW.md](OVERVIEW.md) for the full description, including what data is sent where.
3
+ Agent board is a Paseo plugin that shows the agents of the current project as a live tree of cards. Each card shows the agent's status, provider, model, effort, permission mode and context use. Agents started by another agent sit beside their parent, joined by an arrow, and pressing the arrow shows the instructions the parent sent. Subagents an agent starts inside its own session, such as those from Claude's Agent tool, get smaller cards under it, and pressing one shows its conversation. Optional summaries from Claude Haiku give each card a short title and a line about what the agent is doing now. See [OVERVIEW.md](OVERVIEW.md) for the full description, including what data is sent where.
4
4
 
5
5
  ![Agent board](assets/board-dark.webp)
6
6
 
@@ -1,13 +1,13 @@
1
1
  import { usePaseo, useRpc, useSettings, useTabState, useTranslation, type PluginWorkspacePanelProps } from "@getpaseo/plugin/client";
2
- import { Icon } from "@getpaseo/plugin/client/ui";
2
+ import { Icon, Markdown, Modal } from "@getpaseo/plugin/client/ui";
3
3
  import { keepPreviousData, useQuery } from "@tanstack/react-query";
4
4
  // @getpaseo/client is not installed with the plugin; the host only resolves type imports from @getpaseo/protocol/*.
5
5
  import type { AgentSnapshotPayload as PaseoAgent, WorkspaceDescriptorPayload as PaseoWorkspace } from "@getpaseo/protocol/messages";
6
6
  import type { AgentTimelineItem } from "@getpaseo/protocol/agent-types";
7
7
  import { memo, useEffect, useMemo, useRef, useState, type RefObject } from "react";
8
8
  import { Animated, Easing, PanResponder, Platform, Pressable, ScrollView, Text, View, type GestureResponderEvent, type TextStyle } from "react-native";
9
- import { BOX_LABEL, BOX_PAD, CARD_W, DEFAULT_H, PAD, PARENT_LABEL, arrowPath, type ArrowDirection, type BoardAgent, type Box, colorIndex, describeActivity, groupBoxes, lineage, profileOf, promptOf, pushItem, subtrees, snapPoint, treeLayout, type Point, type ProfileLike } from "../shared/board";
10
- import { subAgentList, subAgentPrompts, type SubAgent } from "../shared/subagents";
9
+ import { BOX_LABEL, BOX_PAD, CARD_W, DEFAULT_H, PAD, PARENT_LABEL, arrowPath, type ArrowDirection, type BoardAgent, type Box, colorIndex, describeActivity, groupBoxes, lineage, profileOf, promptOf, pushItem, subtrees, snapPoint, toolLabel, treeLayout, type Point, type ProfileLike } from "../shared/board";
10
+ import { subAgentList, subAgentPrompts, subAgentTimeline, type SubAgent } from "../shared/subagents";
11
11
  import { CLAUDE_SVG, OPENAI_SVG } from "../shared/icons";
12
12
  import { boardLayout } from "../shared/layout";
13
13
  import { workspaceLabels } from "../shared/labels";
@@ -218,8 +218,8 @@ interface DragHandlers {
218
218
  /** React Native Web puts the mouse event's modifier keys on responder events; native touches have none. */
219
219
  const shiftHeld = (event: GestureResponderEvent) => Boolean((event.nativeEvent as { shiftKey?: boolean }).shiftKey);
220
220
 
221
- /** Dragging moves the card `id`; a tap opens the agent `opens`. */
222
- function useCardPan(id: string, opens: string, handlers: RefObject<DragHandlers>) {
221
+ /** Dragging moves the card `id`; a tap presses it. */
222
+ function useCardPan(id: string, handlers: RefObject<DragHandlers>) {
223
223
  return useMemo(
224
224
  () =>
225
225
  PanResponder.create({
@@ -228,10 +228,10 @@ function useCardPan(id: string, opens: string, handlers: RefObject<DragHandlers>
228
228
  onPanResponderTerminationRequest: () => false,
229
229
  onPanResponderMove: (e, g) => handlers.current.move(id, g.dx, g.dy, shiftHeld(e)),
230
230
  onPanResponderRelease: (e, g) =>
231
- Math.abs(g.dx) + Math.abs(g.dy) < TAP_SLOP ? handlers.current.press(opens) : handlers.current.drop(id, g.dx, g.dy, shiftHeld(e)),
231
+ Math.abs(g.dx) + Math.abs(g.dy) < TAP_SLOP ? handlers.current.press(id) : handlers.current.drop(id, g.dx, g.dy, shiftHeld(e)),
232
232
  onPanResponderTerminate: () => handlers.current.cancel(),
233
233
  }),
234
- [id, opens, handlers],
234
+ [id, handlers],
235
235
  );
236
236
  }
237
237
 
@@ -328,6 +328,25 @@ function ProfileChip({ profile }: { profile: Profile }) {
328
328
  /** The status is in the key, so a finished turn is summarized once more as it ends; the locale, so a new app language gets new summaries. */
329
329
  const summaryKey = (agent: PaseoAgent, locale: string) => ["agent-board", "summary", agent.id, agent.status, locale];
330
330
 
331
+ /** Tool and state words for cards, in the reader's language. */
332
+ function useActivityWords() {
333
+ const { t } = useTranslation(messages);
334
+ return useMemo(
335
+ () => ({
336
+ read: t("tool.read"),
337
+ edit: t("tool.edit"),
338
+ write: t("tool.write"),
339
+ search: t("tool.search"),
340
+ fetch: t("tool.fetch"),
341
+ subAgent: t("tool.subAgent"),
342
+ failed: t("tool.failed"),
343
+ thinking: t("tool.thinking"),
344
+ error: t("tool.error"),
345
+ }),
346
+ [t],
347
+ );
348
+ }
349
+
331
350
  const isSub = (node: PaseoAgent | SubAgent): node is SubAgent => "parentAgentId" in node;
332
351
  const subTitle = (sub: SubAgent) => sub.description ?? sub.title;
333
352
 
@@ -356,20 +375,7 @@ const AgentCard = memo(function AgentCard(props: {
356
375
  }) {
357
376
  const { agent, colors, handlers } = props;
358
377
  const { t, locale } = useTranslation(messages);
359
- const words = useMemo(
360
- () => ({
361
- read: t("tool.read"),
362
- edit: t("tool.edit"),
363
- write: t("tool.write"),
364
- search: t("tool.search"),
365
- fetch: t("tool.fetch"),
366
- subAgent: t("tool.subAgent"),
367
- failed: t("tool.failed"),
368
- thinking: t("tool.thinking"),
369
- error: t("tool.error"),
370
- }),
371
- [t],
372
- );
378
+ const words = useActivityWords();
373
379
  const activity = describeActivity(useTimeline(agent.id), words);
374
380
  const running = agent.status === "running";
375
381
  const readSummary = useRpc(agentSummary);
@@ -385,7 +391,7 @@ const AgentCard = memo(function AgentCard(props: {
385
391
  const summed = props.summaries ? summary.data : undefined;
386
392
  const title = summed?.title ?? agent.title ?? t("card.untitled");
387
393
  const doing = activity.compacting ? t("card.compactingNow") : (summed?.doing ?? activity.latest);
388
- const pan = useCardPan(agent.id, agent.id, handlers);
394
+ const pan = useCardPan(agent.id, handlers);
389
395
  const status = statusOf(agent, colors, activity.compacting);
390
396
  const statusLabel = t(`status.${status.key}`);
391
397
  const role = agent.labels.role;
@@ -477,7 +483,7 @@ const AgentCard = memo(function AgentCard(props: {
477
483
  );
478
484
  });
479
485
 
480
- /** A subagent's node: its type, its task, Paseo's model and token line, and its state. A tap opens the agent whose session runs it. */
486
+ /** A subagent's node: its type, its task, Paseo's model and token line, and its state. A tap shows its conversation. */
481
487
  const SubAgentCard = memo(function SubAgentCard(props: {
482
488
  sub: SubAgent;
483
489
  x: number;
@@ -490,7 +496,7 @@ const SubAgentCard = memo(function SubAgentCard(props: {
490
496
  const { sub, colors, handlers } = props;
491
497
  const live = sub.status === "running";
492
498
  const { t } = useTranslation(messages);
493
- const pan = useCardPan(sub.id, sub.parentAgentId, handlers);
499
+ const pan = useCardPan(sub.id, handlers);
494
500
  const status: { key: StatusKey; color: string } = live
495
501
  ? { key: "running", color: AMBER }
496
502
  : sub.status === "completed"
@@ -508,7 +514,7 @@ const SubAgentCard = memo(function SubAgentCard(props: {
508
514
  accessibilityRole="button"
509
515
  accessibilityLabel={`${kind}, ${title}, ${statusLabel}`}
510
516
  accessibilityHint={t("card.subHint")}
511
- onAccessibilityTap={() => handlers.current.press(sub.parentAgentId)}
517
+ onAccessibilityTap={() => handlers.current.press(sub.id)}
512
518
  onLayout={(event) => handlers.current.measure(sub.id, event.nativeEvent.layout.height)}
513
519
  style={{
514
520
  position: "absolute",
@@ -551,6 +557,75 @@ const SubAgentCard = memo(function SubAgentCard(props: {
551
557
  );
552
558
  });
553
559
 
560
+ /**
561
+ * A subagent's conversation in a modal over the board, refreshed while it runs.
562
+ * Paseo hosts whose plugin `navigation` has no `openProviderSubagent` (it arrives with getpaseo/paseo#8795) cannot open their
563
+ * own subagent tab for a plugin, so the board draws the conversation itself there.
564
+ */
565
+ function SubAgentSession(props: { sub: SubAgent; colors: Colors; onClose(): void; onOpenParent(): void }) {
566
+ const { sub, colors } = props;
567
+ const { t } = useTranslation(messages);
568
+ const words = useActivityWords();
569
+ const readTimeline = useRpc(subAgentTimeline);
570
+ const timeline = useQuery({
571
+ queryKey: ["agent-board", "session", sub.parentAgentId, sub.id],
572
+ queryFn: () => readTimeline({ agentId: sub.parentAgentId, subAgentId: sub.id }),
573
+ refetchInterval: sub.status === "running" ? 2000 : false,
574
+ retry: false,
575
+ });
576
+ // Stored rows split streamed answers into chunks and repeat a tool call per state; merge them as the live timeline does.
577
+ const items = (timeline.data?.items ?? []).reduce<AgentTimelineItem[]>((all, item) => pushItem(all, item), []);
578
+ const muted = { color: colors.foregroundMuted, fontSize: 12 };
579
+ return (
580
+ <Modal
581
+ title={subTitle(sub) ?? t("tool.subAgent")}
582
+ icon={<Icon name="Bot" size={16} color={colors.foreground} />}
583
+ open
584
+ onOpenChange={(open) => (open ? undefined : props.onClose())}
585
+ >
586
+ <Modal.Content>
587
+ <View style={{ flexDirection: "row", flexWrap: "wrap", alignItems: "center", gap: 8 }}>
588
+ {sub.subtitle ? <Text style={[muted, { flexShrink: 1 }]}>{sub.subtitle}</Text> : null}
589
+ <View style={{ marginLeft: "auto" }}>
590
+ <HeaderButton label={t("session.openParent")} onPress={props.onOpenParent} colors={colors} />
591
+ </View>
592
+ </View>
593
+ {timeline.isPending ? (
594
+ <Text style={muted}>{t("session.loading")}</Text>
595
+ ) : timeline.isError ? (
596
+ <Text style={muted}>{t("session.failed")}</Text>
597
+ ) : items.length === 0 ? (
598
+ <Text style={muted}>{t("session.empty")}</Text>
599
+ ) : (
600
+ items.map((item, index) => {
601
+ if (item.type === "user_message")
602
+ return (
603
+ <View key={index} style={{ padding: 10, borderRadius: 8, backgroundColor: colors.surface2 }}>
604
+ <Markdown>{item.text}</Markdown>
605
+ </View>
606
+ );
607
+ if (item.type === "assistant_message") return <Markdown key={index}>{item.text}</Markdown>;
608
+ if (item.type === "tool_call")
609
+ return (
610
+ <Text key={index} numberOfLines={2} style={muted}>
611
+ {toolLabel(item, words)}
612
+ {item.status === "failed" ? ` (${words.failed})` : ""}
613
+ </Text>
614
+ );
615
+ if (item.type === "error")
616
+ return (
617
+ <Text key={index} style={{ color: colors.statusDanger, fontSize: 12 }}>
618
+ {`${words.error}: ${item.message}`}
619
+ </Text>
620
+ );
621
+ return null;
622
+ })
623
+ )}
624
+ </Modal.Content>
625
+ </Modal>
626
+ );
627
+ }
628
+
554
629
  /** An axis-aligned 2px line inside a thicker pressable strip. */
555
630
  function Segment({ x1, y1, x2, y2, color, onPress }: { x1: number; y1: number; x2: number; y2: number; color: string; onPress(): void }) {
556
631
  const horizontal = y1 === y2;
@@ -1073,6 +1148,8 @@ export function AgentBoard({ theme, layout, workspaceId, navigation, tab }: Plug
1073
1148
  return () => loop.stop();
1074
1149
  }, [flow]);
1075
1150
  const [opened, setOpened] = useState<{ from: string; to: string } | null>(null);
1151
+ /** The subagent whose conversation is shown. */
1152
+ const [session, setSession] = useState<string | null>(null);
1076
1153
 
1077
1154
  const [drag, setDrag] = useState<{ id: string; dx: number; dy: number; shift: boolean } | null>(null);
1078
1155
  const basePosition = (id: string): Point => saved[id] ?? tree.positions[id] ?? { x: PAD, y: PAD };
@@ -1101,6 +1178,8 @@ export function AgentBoard({ theme, layout, workspaceId, navigation, tab }: Plug
1101
1178
  ? { x: base.x + groupDrag.dx, y: base.y + groupDrag.dy }
1102
1179
  : base;
1103
1180
  };
1181
+ // shortcut: SDK 0.15 types lack openProviderSubagent (getpaseo/paseo#8795); hosts without it get the board's own view. Drop the cast once the SDK has it.
1182
+ const subTabs = navigation as { openProviderSubagent?(input: { parentAgentId: string; subagentId: string }): void } | undefined;
1104
1183
  const handlers = useRef<DragHandlers & GroupHandlers & { undo(): void }>(null!);
1105
1184
  handlers.current = {
1106
1185
  // Gestures report screen pixels; the board is scaled by zoom.
@@ -1140,7 +1219,10 @@ export function AgentBoard({ theme, layout, workspaceId, navigation, tab }: Plug
1140
1219
  setHeights((current) => (Math.abs((current[id] ?? 0) - h) < 1 ? current : { ...current, [id]: h })),
1141
1220
  press: (id) => {
1142
1221
  setDrag(null);
1143
- navigation?.openAgent({ agentId: id });
1222
+ const sub = subNodes.find((node) => node.id === id);
1223
+ if (!sub) navigation?.openAgent({ agentId: id });
1224
+ else if (subTabs?.openProviderSubagent) subTabs.openProviderSubagent({ parentAgentId: sub.parentAgentId, subagentId: sub.id });
1225
+ else setSession(id);
1144
1226
  },
1145
1227
  };
1146
1228
  const toggleSummaries = () => {
@@ -1170,6 +1252,7 @@ export function AgentBoard({ theme, layout, workspaceId, navigation, tab }: Plug
1170
1252
  arrowPath(positionOf(edge.from), heights[edge.from] ?? DEFAULT_H, positionOf(edge.to), heights[edge.to] ?? DEFAULT_H, ARROW);
1171
1253
  const subById = new Map(subNodes.map((sub) => [sub.id, sub]));
1172
1254
  const nodeOf = (id: string) => agents.get(id) ?? subById.get(id);
1255
+ const sessionSub = session ? subById.get(session) : undefined;
1173
1256
  const openFrom = opened ? nodeOf(opened.from) : undefined;
1174
1257
  const openTo = opened ? nodeOf(opened.to) : undefined;
1175
1258
  const toggleOpened = (edge: { from: string; to: string }) =>
@@ -1339,6 +1422,18 @@ export function AgentBoard({ theme, layout, workspaceId, navigation, tab }: Plug
1339
1422
  )}
1340
1423
  </View>
1341
1424
  )}
1425
+ {sessionSub ? (
1426
+ <SubAgentSession
1427
+ key={sessionSub.id}
1428
+ sub={sessionSub}
1429
+ colors={colors}
1430
+ onClose={() => setSession(null)}
1431
+ onOpenParent={() => {
1432
+ setSession(null);
1433
+ navigation?.openAgent({ agentId: sessionSub.parentAgentId });
1434
+ }}
1435
+ />
1436
+ ) : null}
1342
1437
  </View>
1343
1438
  );
1344
1439
  }
@@ -19,7 +19,7 @@ const en = {
19
19
  untitled: "Untitled",
20
20
  compactingNow: "Compacting the conversation",
21
21
  hint: "Press to open the agent, drag to move it",
22
- subHint: "Press to open the agent that started this subagent, drag to move it",
22
+ subHint: "Press to show this subagent's conversation, drag to move it",
23
23
  focus: "Highlight this agent with its ancestors and descendants",
24
24
  unfocus: "Clear highlight",
25
25
  hide: "Hide this agent and its descendants",
@@ -47,6 +47,12 @@ const en = {
47
47
  failed: "Couldn't load the instructions.",
48
48
  none: "This agent's history has no instructions from its parent agent.",
49
49
  },
50
+ session: {
51
+ openParent: "Open parent agent",
52
+ loading: "Loading",
53
+ failed: "Couldn't load the conversation.",
54
+ empty: "This subagent's conversation is empty.",
55
+ },
50
56
  workspace: {
51
57
  none: "No workspace",
52
58
  label: "Label {{name}}",
@@ -91,7 +97,7 @@ const ko: Strings<typeof en> = {
91
97
  untitled: "제목 없음",
92
98
  compactingNow: "대화 기록을 압축하는 중",
93
99
  hint: "누르면 에이전트를 열고, 끌면 위치를 바꿉니다",
94
- subHint: "누르면 이 하위 에이전트를 실행한 에이전트를 열고, 끌면 위치를 바꿉니다",
100
+ subHint: "누르면 이 하위 에이전트의 대화를 보여 주고, 끌면 위치를 바꿉니다",
95
101
  focus: "이 에이전트와 조상, 후손 강조",
96
102
  unfocus: "강조 해제",
97
103
  hide: "이 에이전트와 후손 숨기기",
@@ -128,6 +134,12 @@ const ko: Strings<typeof en> = {
128
134
  failed: "지시 기록을 불러오지 못했습니다.",
129
135
  none: "이 에이전트의 기록에 부모 에이전트가 보낸 지시가 없습니다.",
130
136
  },
137
+ session: {
138
+ openParent: "부모 에이전트 열기",
139
+ loading: "불러오는 중",
140
+ failed: "대화를 불러오지 못했습니다.",
141
+ empty: "이 하위 에이전트의 대화가 비어 있습니다.",
142
+ },
131
143
  workspace: {
132
144
  none: "워크스페이스 없음",
133
145
  label: "레이블 {{name}}",
@@ -169,7 +181,7 @@ const zhCN: Strings<typeof en> = {
169
181
  untitled: "无标题",
170
182
  compactingNow: "正在压缩对话记录",
171
183
  hint: "点按打开智能体,拖动可移动位置",
172
- subHint: "点按打开启动此子智能体的智能体,拖动可移动位置",
184
+ subHint: "点按查看此子智能体的对话,拖动可移动位置",
173
185
  focus: "高亮此智能体及其祖先和后代",
174
186
  unfocus: "取消高亮",
175
187
  hide: "隐藏此智能体及其后代",
@@ -206,6 +218,12 @@ const zhCN: Strings<typeof en> = {
206
218
  failed: "无法加载指令记录。",
207
219
  none: "此智能体的记录中没有父智能体发送的指令。",
208
220
  },
221
+ session: {
222
+ openParent: "打开父智能体",
223
+ loading: "加载中",
224
+ failed: "无法加载对话。",
225
+ empty: "此子智能体的对话为空。",
226
+ },
209
227
  workspace: {
210
228
  none: "无工作区",
211
229
  label: "标签 {{name}}",
package/index.server.ts CHANGED
@@ -1,10 +1,10 @@
1
1
  import type { PluginServerContext } from "@getpaseo/plugin/server";
2
2
  import { listLabels } from "./server/labels";
3
- import { listSubAgentPrompts, listSubAgents, stopSubAgents } from "./server/subagents";
3
+ import { listSubAgentPrompts, listSubAgents, readSubAgentTimeline, stopSubAgents } from "./server/subagents";
4
4
  import { stopSummaries, summarize, summarizeInstruction } from "./server/summary";
5
5
  import { workspaceLabels } from "./shared/labels";
6
6
  import { boardLayout } from "./shared/layout";
7
- import { subAgentList, subAgentPrompts } from "./shared/subagents";
7
+ import { subAgentList, subAgentPrompts, subAgentTimeline } from "./shared/subagents";
8
8
  import { agentSummary, instructionSummary } from "./shared/summary";
9
9
 
10
10
  export default function contribute(server: PluginServerContext) {
@@ -14,5 +14,6 @@ export default function contribute(server: PluginServerContext) {
14
14
  server.handle(workspaceLabels, listLabels);
15
15
  server.handle(subAgentList, listSubAgents);
16
16
  server.handle(subAgentPrompts, listSubAgentPrompts);
17
+ server.handle(subAgentTimeline, readSubAgentTimeline);
17
18
  return () => Promise.all([stopSummaries(), stopSubAgents()]).then(() => {});
18
19
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geongyu09/paseo-agent-dashboard",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
4
4
  "files": [
5
5
  "paseo-plugin.json",
6
6
  "OVERVIEW.md",
@@ -3,7 +3,7 @@ import { DaemonClient } from "@getpaseo/client/internal/daemon-client";
3
3
  import { readFile } from "node:fs/promises";
4
4
  import { homedir } from "node:os";
5
5
  import { join } from "node:path";
6
- import type { subAgentList, subAgentPrompts } from "../shared/subagents";
6
+ import type { subAgentList, subAgentPrompts, subAgentTimeline } from "../shared/subagents";
7
7
 
8
8
  let client: Promise<DaemonClient> | null = null;
9
9
  let warned = false;
@@ -65,6 +65,11 @@ export async function listSubAgentPrompts({ agentId, subAgentId }: RpcInput<type
65
65
  };
66
66
  }
67
67
 
68
+ export async function readSubAgentTimeline({ agentId, subAgentId }: RpcInput<typeof subAgentTimeline>): Promise<RpcOutput<typeof subAgentTimeline>> {
69
+ const page = await (await daemon()).fetchProviderSubagentTimeline(agentId, subAgentId, { direction: "tail", limit: 0 });
70
+ return { items: page.rows.map((row) => row.item) };
71
+ }
72
+
68
73
  export async function stopSubAgents(): Promise<void> {
69
74
  const pending = client;
70
75
  client = null;
package/shared/board.ts CHANGED
@@ -259,7 +259,7 @@ export const ENGLISH_WORDS: ActivityWords = {
259
259
  error: "Error",
260
260
  };
261
261
 
262
- function toolLabel(item: Extract<AgentTimelineItem, { type: "tool_call" }>, words: ActivityWords): string {
262
+ export function toolLabel(item: Extract<AgentTimelineItem, { type: "tool_call" }>, words: ActivityWords): string {
263
263
  const detail = item.detail;
264
264
  switch (detail.type) {
265
265
  case "shell":
@@ -1,4 +1,5 @@
1
1
  import { defineRpc } from "@getpaseo/plugin";
2
+ import type { AgentTimelineItem } from "@getpaseo/protocol/agent-types";
2
3
  import { z } from "zod";
3
4
 
4
5
  const subAgent = z.object({
@@ -36,3 +37,10 @@ export const subAgentPrompts = defineRpc({
36
37
  input: z.object({ agentId: z.string(), subAgentId: z.string() }),
37
38
  output: z.object({ prompts: z.array(z.object({ text: z.string(), at: z.string() })) }),
38
39
  });
40
+
41
+ /** A subagent's whole conversation, oldest first, as its own timeline records it. */
42
+ export const subAgentTimeline = defineRpc({
43
+ name: "subagents.timeline",
44
+ input: z.object({ agentId: z.string(), subAgentId: z.string() }),
45
+ output: z.object({ items: z.array(z.custom<AgentTimelineItem>()) }),
46
+ });