@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 +2 -2
- package/OVERVIEW.zh-CN.md +2 -2
- package/README.md +1 -1
- package/client/AgentBoard.tsx +121 -26
- package/client/messages.ts +21 -3
- package/index.server.ts +3 -2
- package/package.json +1 -1
- package/server/subagents.ts +6 -1
- package/shared/board.ts +1 -1
- package/shared/subagents.ts +8 -0
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
|
|
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
|
|
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 数,以及 **工作中**、**已完成**、**出错** 或
|
|
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
|
|
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
|

|
|
6
6
|
|
package/client/AgentBoard.tsx
CHANGED
|
@@ -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
|
|
222
|
-
function useCardPan(id: string,
|
|
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(
|
|
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,
|
|
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 =
|
|
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,
|
|
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
|
|
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,
|
|
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.
|
|
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
|
-
|
|
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
|
}
|
package/client/messages.ts
CHANGED
|
@@ -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
|
|
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
package/server/subagents.ts
CHANGED
|
@@ -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":
|
package/shared/subagents.ts
CHANGED
|
@@ -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
|
+
});
|