Files
paseo/packages/app/src/subagents/track-presentation.test.ts
T
Mohamed Boudra 91dca5702b Connect saved hosts and delegate agents across daemons (#7370)
* feat(hosts): connect to saved hosts through a daemon

* fix(hosts): validate safe targets and verify encrypted relay routes

* test(hosts): include relay coverage in existing CI contracts

* fix(cli): preserve validated addresses in daemon command hints

* fix(hosts): share daemon links across isolated routed sessions

Multiplex logical sessions over the monitored daemon connection and wrap original binary bytes in routing headers. Admit channels under their authenticated parent and current grants; preserve scoped delivery and physical send completion.

* fix(hosts): bound encrypted overload and initial acquisition

Check encrypted wire capacity before the async channel converts a resource limit into a physical connection error. Bound initial routed acquisition with the existing client timeout, then enable reconnect after admission.

* fix(hosts): keep compact row actions clear of host labels

Give compact connected-host rows separate text and action widths so long names and producer labels cannot compete with primary controls.

* refactor(hosts): remove management settings UI from phase one

Keep connected-host management in the CLI and shared SDK while preserving app saved connections and plugin access.

* test(hosts): control physical write backpressure across platforms

Hold real TCP writes until assertions finish instead of assuming a paused receiver exhausts every platform receive buffer. Preserve physical send settlement, byte identity, capacity and sibling checks.

* refactor(hosts): use the ordinary client contract over channel transports

Keep queuing, permission codes and reconnection independent of transport selection. Track submitted request outcomes generically while retaining connection error codes and structured causes.

* test(e2e): keep transient developer toasts from stalling checks

Concurrent locator handlers can wait for a dismiss button another handler removed. Dismiss existing toast DOM at document lifetime, including its open shadow root, without intercepting app actions.

* Create, prompt, and monitor agents across connected hosts (#7507)

* feat(hosts): delegate agents across connected hosts

Resolve MCP host operations through in-process commands for self and the existing routed client transport for other destinations. Keep ordinary parent labels, app sessions and idle finish notifications.

* test(app): normalize subagent fixtures through snapshots

* docs(protocol): clarify same-host parent labels

* fix(hosts): resolve subagents through destination sessions

A child address identifies its destination regardless of the connection used. Reuse the existing host candidate selection for discovered routes and direct connections, and keep navigation and row actions independent of the parent transport.

* fix(hosts): keep routed destinations distinct during discovery

A routed candidate names only the origin daemon. Match it within the named destination profile so two destinations discovered through the same origin retain separate identities and connections, including existing saved routes.

* feat(hosts): prompt and query agents on selected hosts

Extend the existing MCP host operation boundary with prompt, status, activity and list operations. Local calls retain their handlers and notification owner; selected destinations use ordinary client RPCs and the existing idle finish wait.

* test(e2e): preserve toast coverage with CSS hiding

* docs(hosts): translate connected host references
2026-10-21 16:10:47 +07:00

301 lines
9.9 KiB
TypeScript

import { beforeAll, describe, expect, it } from "vitest";
import { i18n } from "@/i18n/i18next";
import type { PaseoSubagentRow, ProviderSubagentRow, SubagentRow } from "./select";
import {
buildSubagentPillPresentation,
buildSubagentRowPresentationData,
countFinishedSubagents,
resolveRowLabel,
} from "./track-presentation";
function row(
overrides: Partial<PaseoSubagentRow> & Pick<PaseoSubagentRow, "id">,
): PaseoSubagentRow {
return {
kind: "paseo",
address: overrides.address ?? { serverId: "server-1", agentId: overrides.id },
id: overrides.id,
provider: overrides.provider ?? "codex",
title: overrides.title ?? `Agent ${overrides.id}`,
description: null,
subtitle: null,
status: overrides.status ?? "idle",
turn:
overrides.turn ??
(overrides.status === "running"
? { phase: "open", turnId: null, startedAt: null, cancellationRequestId: null }
: { phase: "idle", cancellationRequestId: null }),
requiresAttention: overrides.requiresAttention ?? false,
createdAt: overrides.createdAt ?? new Date("2026-04-20T00:00:00.000Z"),
};
}
describe("buildSubagentPillPresentation", () => {
// The real instance, so a label that names a key nobody added renders as that key and fails.
beforeAll(async () => {
if (!i18n.isInitialized) {
await i18n.init();
}
await i18n.changeLanguage("en");
});
const pill = (rows: SubagentRow[]) => buildSubagentPillPresentation(i18n.t, rows);
it("counts the children that are working, not the fan-out", () => {
expect(pill([row({ id: "a" }), row({ id: "b", status: "running" })])).toEqual({
segments: [{ bucket: "running", text: "1 working" }],
accessibilityLabel: "1 working",
});
});
it("counts every child in the state it reports", () => {
expect(
pill([
row({ id: "a", status: "running" }),
row({ id: "b", status: "running" }),
row({ id: "c" }),
]),
).toEqual({
segments: [{ bucket: "running", text: "2 working" }],
accessibilityLabel: "2 working",
});
});
it("keeps a working child visible behind a failed one instead of collapsing to the worst", () => {
expect(
pill([
row({ id: "a", status: "running" }),
row({ id: "b", status: "error", requiresAttention: true }),
row({ id: "c", status: "error" }),
]),
).toEqual({
segments: [
{ bucket: "failed", text: "2 failed" },
{ bucket: "running", text: "1 working" },
],
accessibilityLabel: "2 failed, 1 working",
});
});
it("names what it opens once every child is done", () => {
expect(pill([row({ id: "a" }), row({ id: "b" })])).toEqual({
segments: [{ bucket: null, text: "2 subagents" }],
accessibilityLabel: "2 subagents",
});
});
it("keeps the singular for a lone child", () => {
expect(pill([row({ id: "a" })])).toEqual({
segments: [{ bucket: null, text: "1 subagent" }],
accessibilityLabel: "1 subagent",
});
});
it("has nothing to mark without rows", () => {
expect(pill([])).toEqual({
segments: [{ bucket: null, text: "0 subagents" }],
accessibilityLabel: "0 subagents",
});
});
});
describe("countFinishedSubagents", () => {
it("counts eligible managed and terminal provider-owned children", () => {
const providerRows: SubagentRow[] = [
{
kind: "provider",
id: "native-running",
parentAgentId: "parent",
provider: "claude",
title: "running",
description: null,
subtitle: null,
status: "running",
requiresAttention: false,
createdAt: new Date("2026-04-20T00:00:00.000Z"),
},
{
kind: "provider",
id: "native-failed",
parentAgentId: "parent",
provider: "claude",
title: "failed",
description: null,
subtitle: null,
status: "failed",
requiresAttention: true,
createdAt: new Date("2026-04-20T00:00:01.000Z"),
},
];
expect(
countFinishedSubagents([
row({ id: "managed-running", status: "running" }),
row({ id: "managed-idle", status: "idle" }),
...providerRows,
]),
).toBe(2);
});
it("excludes running and initializing managed children", () => {
expect(
countFinishedSubagents([
row({ id: "running", status: "running" }),
row({ id: "initializing", status: "initializing" }),
row({ id: "finished", status: "idle" }),
]),
).toBe(1);
});
});
describe("resolveRowLabel", () => {
it("returns null when title is not a string", () => {
expect(resolveRowLabel(null as unknown as SubagentRow["title"])).toBe(null);
});
it("returns null for whitespace-only titles", () => {
expect(resolveRowLabel(" ")).toBe(null);
});
it("returns null for the placeholder 'new agent' regardless of case", () => {
expect(resolveRowLabel("new agent")).toBe(null);
expect(resolveRowLabel("New Agent")).toBe(null);
expect(resolveRowLabel(" NEW AGENT ")).toBe(null);
});
it("returns the trimmed title for real names", () => {
expect(resolveRowLabel(" Build the thing ")).toBe("Build the thing");
});
});
describe("buildSubagentRowPresentationData", () => {
it("namespaces the key with a subagent prefix", () => {
expect(buildSubagentRowPresentationData(row({ id: "child-a" })).key).toBe(
"paseo_subagent_child-a",
);
});
it("marks the row ready when the title resolves to a real label", () => {
const presentation = buildSubagentRowPresentationData(row({ id: "a", title: "Build it" }));
expect(presentation.titleState).toBe("ready");
expect(presentation.label).toBe("Build it");
});
it("marks the row loading and blanks the label for the placeholder title", () => {
const presentation = buildSubagentRowPresentationData(row({ id: "a", title: "new agent" }));
expect(presentation.titleState).toBe("loading");
expect(presentation.label).toBe("");
});
it("maps a running row to the running status bucket so callers render the synced loader", () => {
expect(buildSubagentRowPresentationData(row({ id: "a", status: "running" })).statusBucket).toBe(
"running",
);
});
it("maps an idle row to the done status bucket so callers render the static provider icon", () => {
expect(buildSubagentRowPresentationData(row({ id: "a", status: "idle" })).statusBucket).toBe(
"done",
);
});
it("ignores requiresAttention on the source row when computing the bucket", () => {
expect(
buildSubagentRowPresentationData(row({ id: "a", status: "idle", requiresAttention: true }))
.statusBucket,
).toBe("done");
});
});
describe("buildSubagentRowPresentationData for provider rows", () => {
function providerRow(overrides: Partial<ProviderSubagentRow> = {}): ProviderSubagentRow {
return {
kind: "provider",
id: overrides.id ?? "toolu_1",
parentAgentId: "parent",
provider: "claude",
title: "title" in overrides ? (overrides.title ?? null) : "general-purpose",
description: overrides.description ?? null,
subtitle: overrides.subtitle ?? null,
status: overrides.status ?? "running",
requiresAttention: false,
createdAt: overrides.createdAt ?? new Date("2026-07-26T00:00:00.000Z"),
};
}
it("names the row after the task and demotes the subagent type", () => {
const presentation = buildSubagentRowPresentationData(
providerRow({ title: "general-purpose", description: "Reply with banana" }),
);
expect(presentation.label).toBe("Reply with banana");
expect(presentation.subtitle).toBe("general-purpose");
});
it("tells two siblings of the same type apart", () => {
const left = buildSubagentRowPresentationData(
providerRow({ id: "a", description: "Summarize the docs" }),
);
const right = buildSubagentRowPresentationData(
providerRow({ id: "b", description: "Reply with banana" }),
);
expect(left.label).not.toBe(right.label);
});
it("keeps type-as-label and an empty subtitle when a provider reports no task", () => {
const presentation = buildSubagentRowPresentationData(
providerRow({ title: "Provider child", description: null }),
);
expect(presentation.label).toBe("Provider child");
expect(presentation.subtitle).toBe("");
});
it("stays in the loading state when neither field is known", () => {
const presentation = buildSubagentRowPresentationData(
providerRow({ title: null, description: null }),
);
expect(presentation.titleState).toBe("loading");
});
it("leaves managed subagent rows with no subtitle", () => {
expect(buildSubagentRowPresentationData(row({ id: "a", title: "Managed" })).subtitle).toBe("");
});
});
describe("provider-owned row subtitles", () => {
function providerRow(overrides: Partial<ProviderSubagentRow> = {}): ProviderSubagentRow {
return {
kind: "provider",
id: "toolu_1",
parentAgentId: "parent",
provider: "claude",
title: "general-purpose",
description: "Reply with banana",
subtitle: null,
status: "running",
requiresAttention: false,
createdAt: new Date("2026-07-26T00:00:00.000Z"),
...overrides,
};
}
it("displays provider context without interpreting it", () => {
expect(
buildSubagentRowPresentationData(
providerRow({ subtitle: "general-purpose · Opus 5 · High · 16.5k tokens" }),
).subtitle,
).toBe("general-purpose · Opus 5 · High · 16.5k tokens");
});
it("falls back to the type when an older provider sends no subtitle", () => {
expect(buildSubagentRowPresentationData(providerRow()).subtitle).toBe("general-purpose");
});
it("does not duplicate the type when it is already the primary label", () => {
expect(
buildSubagentRowPresentationData(
providerRow({ description: null, subtitle: null, title: "general-purpose" }),
).subtitle,
).toBe("");
});
});