Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
2177 lines
64 KiB
TypeScript
2177 lines
64 KiB
TypeScript
import { describe, expect, it, vi } from "vitest";
|
|
import { act, fireEvent, render, screen } from "@testing-library/react";
|
|
import { normalizeAgentActivitySession } from "@tutti-os/agent-activity-core";
|
|
import {
|
|
Children,
|
|
createElement,
|
|
isValidElement,
|
|
type ReactElement,
|
|
type ReactNode
|
|
} from "react";
|
|
import { agentGuiDockIconUrls } from "../dockIcons.ts";
|
|
import type {
|
|
AgentGUIAgent,
|
|
AgentGUIAgentDirectoryPort,
|
|
AgentGUIProvider
|
|
} from "../types.ts";
|
|
import {
|
|
agentGuiWorkbenchDefaultCopy,
|
|
agentGuiWorkbenchProviderRailWidthPx,
|
|
buildAgentGuiDockEntries,
|
|
agentGuiWorkbenchNewWindowCascadeOffset,
|
|
createAgentGuiWorkbenchContribution,
|
|
resolveAgentGuiWorkbenchDefaultLaunchFrame,
|
|
resolveAgentGuiWorkbenchContributionCopy
|
|
} from "./contribution.ts";
|
|
import {
|
|
AGENT_GUI_WORKBENCH_COMMAND_EVENT,
|
|
type AgentGuiWorkbenchCommand
|
|
} from "./commands.ts";
|
|
import {
|
|
agentGuiWorkbenchPrefillPromptActivationType,
|
|
agentGuiWorkbenchUnifiedDockEntryId,
|
|
agentGuiWorkbenchTypeId
|
|
} from "./launch.ts";
|
|
import type { AgentGuiWorkbenchState } from "./types.ts";
|
|
import { createTestAgentSessionEngine } from "../shared/testing/createTestAgentSessionEngine.ts";
|
|
|
|
function createAgent(
|
|
provider: AgentGUIProvider,
|
|
overrides: Partial<AgentGUIAgent> = {}
|
|
): AgentGUIAgent {
|
|
return {
|
|
agentTargetId: `local:${provider}`,
|
|
name: provider,
|
|
iconUrl: `app://icons/${provider}.png`,
|
|
availability: { status: "ready" },
|
|
provider,
|
|
...overrides
|
|
};
|
|
}
|
|
|
|
function readDockEntryIconImageSrcs(icon: ReactNode): string[] {
|
|
if (!isValidElement(icon)) {
|
|
return [];
|
|
}
|
|
const props = (icon as ReactElement<{ children?: ReactNode; src?: string }>)
|
|
.props;
|
|
return [
|
|
...(typeof props.src === "string" ? [props.src] : []),
|
|
...Children.toArray(props.children).flatMap(readDockEntryIconImageSrcs)
|
|
];
|
|
}
|
|
|
|
function createTestAgentGuiWorkbenchContribution(
|
|
input: Omit<
|
|
Parameters<typeof createAgentGuiWorkbenchContribution>[0],
|
|
"agentDirectory"
|
|
> & {
|
|
agentDirectory?: AgentGUIAgentDirectoryPort;
|
|
agents?: readonly AgentGUIAgent[];
|
|
agentsLoading?: boolean;
|
|
}
|
|
) {
|
|
const {
|
|
agentDirectory,
|
|
agents = [createAgent("codex")],
|
|
agentsLoading = false,
|
|
...contributionInput
|
|
} = input;
|
|
return createAgentGuiWorkbenchContribution({
|
|
...contributionInput,
|
|
agentDirectory:
|
|
agentDirectory ?? createTestAgentDirectory(agents, agentsLoading)
|
|
});
|
|
}
|
|
|
|
function createTestAgentDirectory(
|
|
agents: readonly AgentGUIAgent[],
|
|
loading = false
|
|
): AgentGUIAgentDirectoryPort {
|
|
return {
|
|
getSnapshot: () => ({
|
|
agents,
|
|
capturedAtUnixMs: loading ? null : 1,
|
|
error: null,
|
|
status: loading ? "loading" : "ready"
|
|
}),
|
|
subscribe: () => () => {}
|
|
};
|
|
}
|
|
|
|
const testLaunchLayout = {
|
|
layoutConstraints: {
|
|
minHeight: 160,
|
|
minWidth: 280,
|
|
safeArea: {
|
|
bottom: 0,
|
|
left: 0,
|
|
right: 0,
|
|
top: 0
|
|
},
|
|
surfacePadding: 0
|
|
},
|
|
surfaceSize: {
|
|
height: 800,
|
|
width: 1200
|
|
}
|
|
};
|
|
describe("agent GUI workbench contribution copy", () => {
|
|
it("builds one unified dock entry with the selected default target payload", () => {
|
|
const claudeTarget = createAgent("claude-code");
|
|
const entries = buildAgentGuiDockEntries({
|
|
agentDirectory: createTestAgentDirectory([
|
|
createAgent("codex"),
|
|
claudeTarget
|
|
]),
|
|
defaultProvider: "codex",
|
|
label: "Agent",
|
|
providerAvailability: {
|
|
"claude-code": true,
|
|
codex: false
|
|
}
|
|
});
|
|
|
|
expect(entries).toHaveLength(1);
|
|
expect(entries[0]?.id).toBe(agentGuiWorkbenchUnifiedDockEntryId());
|
|
expect(entries[0]?.label).toBe("Agent");
|
|
expect(entries[0]?.launchPayload).toEqual({
|
|
agentTargetId: "local:claude-code",
|
|
provider: "claude-code"
|
|
});
|
|
});
|
|
|
|
it("preserves new-window behavior for a host-defined dock entry id", () => {
|
|
const [entry] = buildAgentGuiDockEntries({
|
|
agentDirectory: createTestAgentDirectory([createAgent("codex")]),
|
|
defaultProvider: "codex",
|
|
dockEntryId: "agentGui:shared",
|
|
order: 7
|
|
});
|
|
|
|
expect(entry).toMatchObject({
|
|
id: "agentGui:shared",
|
|
newWindowLaunchPayload: {
|
|
openInNewWindow: true
|
|
},
|
|
order: 7
|
|
});
|
|
});
|
|
|
|
it("uses the unified icon URL for unified dock entries", () => {
|
|
const entries = buildAgentGuiDockEntries({
|
|
agentDirectory: createTestAgentDirectory([createAgent("codex")]),
|
|
defaultProvider: "codex",
|
|
label: "Agent",
|
|
providerAvailability: {
|
|
codex: true
|
|
},
|
|
unifiedDockIconUrl: "app://icons/agent-unified.png"
|
|
});
|
|
|
|
expect(entries).toHaveLength(1);
|
|
expect(entries[0]?.icon).toMatchObject({
|
|
props: {
|
|
className:
|
|
"agent-gui-workbench-dock-icon agent-gui-workbench-dock-icon--single"
|
|
}
|
|
});
|
|
expect(readDockEntryIconImageSrcs(entries[0]?.icon)).toEqual([
|
|
"app://icons/agent-unified.png"
|
|
]);
|
|
expect(entries[0]?.launchPayload).toMatchObject({
|
|
provider: "codex"
|
|
});
|
|
});
|
|
|
|
it("uses the first enabled target in host order after an unavailable default provider", () => {
|
|
const disabledClaudeTarget = {
|
|
...createAgent("claude-code"),
|
|
agentTargetId: "disabled-claude",
|
|
availability: { status: "unavailable" as const }
|
|
};
|
|
const enabledClaudeTarget = {
|
|
...createAgent("claude-code"),
|
|
agentTargetId: "daemon-claude"
|
|
};
|
|
const entries = buildAgentGuiDockEntries({
|
|
agentDirectory: createTestAgentDirectory([
|
|
createAgent("codex"),
|
|
disabledClaudeTarget,
|
|
enabledClaudeTarget
|
|
]),
|
|
defaultProvider: "codex",
|
|
label: "Agent",
|
|
providerAvailability: {
|
|
"claude-code": true,
|
|
codex: false
|
|
}
|
|
});
|
|
|
|
expect(entries[0]?.launchPayload).toEqual({
|
|
agentTargetId: "daemon-claude",
|
|
provider: "claude-code"
|
|
});
|
|
});
|
|
|
|
it("uses host target order for an available default provider", () => {
|
|
const daemonCodexTarget = {
|
|
...createAgent("codex"),
|
|
agentTargetId: "daemon-codex"
|
|
};
|
|
const localCodexTarget = createAgent("codex");
|
|
const entries = buildAgentGuiDockEntries({
|
|
agentDirectory: createTestAgentDirectory([
|
|
daemonCodexTarget,
|
|
localCodexTarget
|
|
]),
|
|
defaultProvider: "codex",
|
|
label: "Agent",
|
|
providerAvailability: {
|
|
codex: true
|
|
}
|
|
});
|
|
|
|
expect(entries[0]?.launchPayload).toEqual({
|
|
agentTargetId: "daemon-codex",
|
|
provider: "codex"
|
|
});
|
|
});
|
|
|
|
it("uses canonical dock affinity without fallback node matchers", () => {
|
|
const [entry] = buildAgentGuiDockEntries({
|
|
agentDirectory: createTestAgentDirectory([]),
|
|
defaultProvider: "codex",
|
|
providerAvailability: {}
|
|
});
|
|
|
|
expect(entry?.id).toBe(agentGuiWorkbenchUnifiedDockEntryId());
|
|
expect(entry?.matchNode).toBeUndefined();
|
|
expect(entry?.instanceMode).toBe("single");
|
|
expect(entry?.newWindowLaunchPayload).toMatchObject({
|
|
openInNewWindow: true
|
|
});
|
|
expect(entry?.allowNewWindowInDockPopup).toBe(false);
|
|
});
|
|
|
|
it("keeps unified launch payload provider priority when opening a session", () => {
|
|
const contribution = createTestAgentGuiWorkbenchContribution({
|
|
renderBody: () => null,
|
|
workspaceId: "workspace-1"
|
|
});
|
|
|
|
const launchResult = contribution.onLaunchRequest?.({
|
|
dockEntryId: agentGuiWorkbenchUnifiedDockEntryId(),
|
|
layoutConstraints: testLaunchLayout.layoutConstraints,
|
|
payload: {
|
|
agentSessionId: "session-claude-1",
|
|
provider: "claude-code"
|
|
},
|
|
reason: "dock",
|
|
surfaceSize: testLaunchLayout.surfaceSize,
|
|
typeId: agentGuiWorkbenchTypeId,
|
|
workspaceId: "workspace-1"
|
|
}) as
|
|
| {
|
|
instanceId: string;
|
|
}
|
|
| null
|
|
| undefined;
|
|
|
|
expect(launchResult).toMatchObject({
|
|
activation: {
|
|
payload: {
|
|
agentSessionId: "session-claude-1"
|
|
},
|
|
type: "agent-gui:open-session"
|
|
},
|
|
dockEntryId: agentGuiWorkbenchUnifiedDockEntryId(),
|
|
title: "Agent"
|
|
});
|
|
expect(launchResult?.instanceId).toMatch(/^agent-gui:instance:/);
|
|
});
|
|
|
|
it("opens forced replay launches as distinct in-workspace nodes", async () => {
|
|
const contribution = createTestAgentGuiWorkbenchContribution({
|
|
renderBody: () => null,
|
|
workspaceId: "workspace-1"
|
|
});
|
|
const baseRequest = {
|
|
dockEntryId: agentGuiWorkbenchUnifiedDockEntryId(),
|
|
layoutConstraints: testLaunchLayout.layoutConstraints,
|
|
payload: {
|
|
agentTargetId: "local:codex",
|
|
forceNewInstance: true,
|
|
provider: "codex"
|
|
},
|
|
reason: "host" as const,
|
|
surfaceSize: testLaunchLayout.surfaceSize,
|
|
typeId: agentGuiWorkbenchTypeId,
|
|
workspaceId: "workspace-1"
|
|
};
|
|
|
|
const firstLaunch = await contribution.onLaunchRequest?.(baseRequest);
|
|
const secondLaunch = await contribution.onLaunchRequest?.(baseRequest);
|
|
|
|
expect(firstLaunch).toMatchObject({
|
|
reuseDockEntryNode: false
|
|
});
|
|
expect(secondLaunch).toMatchObject({
|
|
reuseDockEntryNode: false
|
|
});
|
|
expect(firstLaunch?.instanceId).not.toBe(secondLaunch?.instanceId);
|
|
});
|
|
|
|
it("clears stale target state when opening a session without a target payload", () => {
|
|
const contribution = createTestAgentGuiWorkbenchContribution({
|
|
renderBody: () => null,
|
|
workspaceId: "workspace-1"
|
|
});
|
|
const baseRequest = {
|
|
dockEntryId: agentGuiWorkbenchUnifiedDockEntryId(),
|
|
layoutConstraints: testLaunchLayout.layoutConstraints,
|
|
reason: "host" as const,
|
|
surfaceSize: testLaunchLayout.surfaceSize,
|
|
typeId: agentGuiWorkbenchTypeId,
|
|
workspaceId: "workspace-1"
|
|
};
|
|
|
|
const seededLaunch = contribution.onLaunchRequest?.({
|
|
...baseRequest,
|
|
payload: {
|
|
agentSessionId: "session-codex-1",
|
|
agentTargetId: "local:claude-code",
|
|
provider: "codex"
|
|
}
|
|
}) as
|
|
| {
|
|
instanceId: string;
|
|
}
|
|
| null
|
|
| undefined;
|
|
|
|
expect(
|
|
contribution.externalStateSource?.getSnapshotNodeState?.({
|
|
instanceId: seededLaunch?.instanceId ?? "",
|
|
typeId: agentGuiWorkbenchTypeId
|
|
} as never)
|
|
).toMatchObject({
|
|
agentTargetId: "local:claude-code",
|
|
lastActiveAgentSessionId: "session-codex-1"
|
|
});
|
|
|
|
const relaunch = contribution.onLaunchRequest?.({
|
|
...baseRequest,
|
|
payload: {
|
|
agentSessionId: "session-codex-1",
|
|
provider: "codex"
|
|
}
|
|
}) as
|
|
| {
|
|
instanceId: string;
|
|
}
|
|
| null
|
|
| undefined;
|
|
|
|
expect(relaunch?.instanceId).toBe(seededLaunch?.instanceId);
|
|
expect(
|
|
contribution.externalStateSource?.getSnapshotNodeState?.({
|
|
instanceId: relaunch?.instanceId ?? "",
|
|
typeId: agentGuiWorkbenchTypeId
|
|
} as never)
|
|
).toEqual({
|
|
conversationRailCollapsed: false,
|
|
conversationRailWidthPx: null,
|
|
lastActiveAgentSessionId: "session-codex-1"
|
|
});
|
|
});
|
|
|
|
it("opens prefill launches in a fresh node without overwriting an active session", () => {
|
|
const contribution = createTestAgentGuiWorkbenchContribution({
|
|
renderBody: () => null,
|
|
workspaceId: "workspace-1"
|
|
});
|
|
const baseRequest = {
|
|
dockEntryId: "agent-gui",
|
|
layoutConstraints: testLaunchLayout.layoutConstraints,
|
|
reason: "host" as const,
|
|
surfaceSize: testLaunchLayout.surfaceSize,
|
|
typeId: agentGuiWorkbenchTypeId,
|
|
workspaceId: "workspace-1"
|
|
};
|
|
|
|
const sessionLaunch = contribution.onLaunchRequest?.({
|
|
...baseRequest,
|
|
payload: {
|
|
agentSessionId: "session-codex-1",
|
|
agentTargetId: "local:codex",
|
|
provider: "codex"
|
|
}
|
|
}) as
|
|
| {
|
|
instanceId: string;
|
|
}
|
|
| null
|
|
| undefined;
|
|
|
|
const prefillLaunch = contribution.onLaunchRequest?.({
|
|
...baseRequest,
|
|
payload: {
|
|
agentTargetId: "local:codex",
|
|
draftPrompt: "Handle this issue",
|
|
provider: "codex"
|
|
}
|
|
}) as
|
|
| {
|
|
instanceId: string;
|
|
}
|
|
| null
|
|
| undefined;
|
|
|
|
expect(prefillLaunch?.instanceId).not.toBe(sessionLaunch?.instanceId);
|
|
expect(
|
|
contribution.externalStateSource?.getSnapshotNodeState?.({
|
|
instanceId: prefillLaunch?.instanceId ?? "",
|
|
typeId: agentGuiWorkbenchTypeId
|
|
} as never)
|
|
).toEqual({
|
|
agentTargetId: "local:codex",
|
|
conversationRailCollapsed: false,
|
|
conversationRailWidthPx: null,
|
|
lastActiveAgentSessionId: null
|
|
});
|
|
expect(
|
|
contribution.externalStateSource?.getSnapshotNodeState?.({
|
|
instanceId: sessionLaunch?.instanceId ?? "",
|
|
typeId: agentGuiWorkbenchTypeId
|
|
} as never)
|
|
).toEqual({
|
|
agentTargetId: "local:codex",
|
|
conversationRailCollapsed: false,
|
|
conversationRailWidthPx: null,
|
|
lastActiveAgentSessionId: "session-codex-1"
|
|
});
|
|
});
|
|
|
|
it("resolves unified empty dock launches lazily from current provider availability", () => {
|
|
const claudeTarget = createAgent("claude-code");
|
|
let currentAgents = [createAgent("codex")];
|
|
const agentDirectory: AgentGUIAgentDirectoryPort = {
|
|
getSnapshot: () => ({
|
|
agents: currentAgents,
|
|
capturedAtUnixMs: 1,
|
|
error: null,
|
|
status: "ready"
|
|
}),
|
|
subscribe: () => () => {}
|
|
};
|
|
const contribution = createTestAgentGuiWorkbenchContribution({
|
|
agentDirectory,
|
|
defaultProvider: "codex",
|
|
providerAvailability: {},
|
|
renderBody: () => null,
|
|
workspaceId: "workspace-1"
|
|
});
|
|
const [dockEntry] = contribution.dockEntries ?? [];
|
|
|
|
expect(dockEntry?.launchPayload).toMatchObject({
|
|
provider: "codex"
|
|
});
|
|
currentAgents = [claudeTarget];
|
|
|
|
const launchResult = contribution.onLaunchRequest?.({
|
|
dockEntryId: dockEntry?.id,
|
|
layoutConstraints: testLaunchLayout.layoutConstraints,
|
|
payload: dockEntry?.launchPayload,
|
|
reason: "dock",
|
|
surfaceSize: testLaunchLayout.surfaceSize,
|
|
typeId: agentGuiWorkbenchTypeId,
|
|
workspaceId: "workspace-1"
|
|
}) as
|
|
| {
|
|
instanceId: string;
|
|
}
|
|
| null
|
|
| undefined;
|
|
|
|
expect(launchResult).toMatchObject({
|
|
dockEntryId: agentGuiWorkbenchUnifiedDockEntryId(),
|
|
reuseDockEntryNode: true,
|
|
title: "Agent"
|
|
});
|
|
expect(launchResult?.instanceId).toMatch(/^agent-gui:instance:/);
|
|
expect(
|
|
contribution.externalStateSource?.getSnapshotNodeState?.({
|
|
instanceId: launchResult?.instanceId ?? "",
|
|
typeId: agentGuiWorkbenchTypeId
|
|
} as never)
|
|
).toMatchObject({
|
|
agentTargetId: "local:claude-code"
|
|
});
|
|
});
|
|
|
|
it("uses a default provider that becomes ready after contribution creation", () => {
|
|
let tuttiAgentReady = false;
|
|
let currentAgents = [createAgent("codex")];
|
|
const agentDirectory: AgentGUIAgentDirectoryPort = {
|
|
getSnapshot: () => ({
|
|
agents: currentAgents,
|
|
capturedAtUnixMs: 1,
|
|
error: null,
|
|
status: "ready"
|
|
}),
|
|
subscribe: () => () => {}
|
|
};
|
|
const contribution = createTestAgentGuiWorkbenchContribution({
|
|
agentDirectory,
|
|
defaultProvider: "tutti-agent",
|
|
providerAvailability: () => ({
|
|
codex: true,
|
|
"tutti-agent": tuttiAgentReady
|
|
}),
|
|
renderBody: () => null,
|
|
workspaceId: "workspace-1"
|
|
});
|
|
const [dockEntry] = contribution.dockEntries ?? [];
|
|
|
|
expect(dockEntry?.launchPayload).toMatchObject({
|
|
provider: "codex"
|
|
});
|
|
|
|
tuttiAgentReady = true;
|
|
currentAgents = [createAgent("codex"), createAgent("tutti-agent")];
|
|
|
|
const launchResult = contribution.onLaunchRequest?.({
|
|
dockEntryId: dockEntry?.id,
|
|
layoutConstraints: testLaunchLayout.layoutConstraints,
|
|
payload: dockEntry?.launchPayload,
|
|
reason: "dock",
|
|
surfaceSize: testLaunchLayout.surfaceSize,
|
|
typeId: agentGuiWorkbenchTypeId,
|
|
workspaceId: "workspace-1"
|
|
}) as
|
|
| {
|
|
instanceId: string;
|
|
}
|
|
| null
|
|
| undefined;
|
|
|
|
expect(
|
|
contribution.externalStateSource?.getSnapshotNodeState?.({
|
|
instanceId: launchResult?.instanceId ?? "",
|
|
typeId: agentGuiWorkbenchTypeId
|
|
} as never)
|
|
).toMatchObject({
|
|
agentTargetId: "local:tutti-agent"
|
|
});
|
|
});
|
|
|
|
it("keeps one contribution while dock launches and body reads follow live directory updates", () => {
|
|
let currentAgents = [createAgent("codex")];
|
|
const listeners = new Set<() => void>();
|
|
const agentDirectory: AgentGUIAgentDirectoryPort = {
|
|
getSnapshot: () => ({
|
|
agents: currentAgents,
|
|
capturedAtUnixMs: 1,
|
|
error: null,
|
|
status: "ready"
|
|
}),
|
|
subscribe: (listener) => {
|
|
listeners.add(listener);
|
|
return () => listeners.delete(listener);
|
|
}
|
|
};
|
|
const renderedAgentTargetIds: string[][] = [];
|
|
const contribution = createTestAgentGuiWorkbenchContribution({
|
|
agentDirectory,
|
|
defaultProvider: "codex",
|
|
providerAvailability: {},
|
|
renderBody: (_context, helpers) => {
|
|
renderedAgentTargetIds.push(
|
|
helpers.agentDirectory
|
|
.getSnapshot()
|
|
.agents.map((agent) => agent.agentTargetId)
|
|
);
|
|
return null;
|
|
},
|
|
workspaceId: "workspace-1"
|
|
});
|
|
const contributionIdentity = contribution;
|
|
const nodeDefinition = contribution.nodes?.[0];
|
|
const dockEntry = contribution.dockEntries?.[0];
|
|
let bodyRenderRequests = 0;
|
|
const unsubscribe = agentDirectory.subscribe(() => {
|
|
bodyRenderRequests += 1;
|
|
});
|
|
|
|
const bodyContext = {
|
|
instanceId: "agent-gui:codex:target:local%3Acodex"
|
|
} as never;
|
|
nodeDefinition?.renderBody?.(bodyContext);
|
|
expect(renderedAgentTargetIds).toEqual([["local:codex"]]);
|
|
|
|
currentAgents = [createAgent("claude-code")];
|
|
for (const listener of listeners) listener();
|
|
expect(bodyRenderRequests).toBe(1);
|
|
nodeDefinition?.renderBody?.(bodyContext);
|
|
|
|
const launchResult = contribution.onLaunchRequest?.({
|
|
dockEntryId: dockEntry?.id,
|
|
layoutConstraints: testLaunchLayout.layoutConstraints,
|
|
payload: dockEntry?.launchPayload,
|
|
reason: "dock",
|
|
surfaceSize: testLaunchLayout.surfaceSize,
|
|
typeId: agentGuiWorkbenchTypeId,
|
|
workspaceId: "workspace-1"
|
|
}) as { instanceId: string } | null | undefined;
|
|
|
|
expect(contribution).toBe(contributionIdentity);
|
|
expect(contribution.nodes?.[0]).toBe(nodeDefinition);
|
|
expect(nodeDefinition?.window?.header).toEqual({
|
|
border: "none",
|
|
layout: "overlay"
|
|
});
|
|
expect(renderedAgentTargetIds).toEqual([
|
|
["local:codex"],
|
|
["local:claude-code"]
|
|
]);
|
|
expect(launchResult?.instanceId).toMatch(/^agent-gui:instance:/);
|
|
unsubscribe();
|
|
});
|
|
|
|
it("seeds unified launch descriptor target state without changing provider identity", () => {
|
|
const claudeTarget = createAgent("claude-code");
|
|
const contribution = createTestAgentGuiWorkbenchContribution({
|
|
defaultProvider: "claude-code",
|
|
agents: [createAgent("codex"), claudeTarget],
|
|
renderBody: () => null,
|
|
workspaceId: "workspace-1"
|
|
});
|
|
const [dockEntry] = contribution.dockEntries ?? [];
|
|
|
|
const launchResult = contribution.onLaunchRequest?.({
|
|
dockEntryId: dockEntry?.id,
|
|
layoutConstraints: testLaunchLayout.layoutConstraints,
|
|
payload: dockEntry?.launchPayload,
|
|
reason: "dock",
|
|
surfaceSize: testLaunchLayout.surfaceSize,
|
|
typeId: agentGuiWorkbenchTypeId,
|
|
workspaceId: "workspace-1"
|
|
}) as
|
|
| {
|
|
instanceId: string;
|
|
}
|
|
| null
|
|
| undefined;
|
|
|
|
expect(launchResult).toMatchObject({
|
|
dockEntryId: agentGuiWorkbenchUnifiedDockEntryId(),
|
|
reuseDockEntryNode: true,
|
|
title: "Agent"
|
|
});
|
|
expect(launchResult?.instanceId).toMatch(/^agent-gui:instance:/);
|
|
expect(
|
|
contribution.externalStateSource?.getSnapshotNodeState?.({
|
|
instanceId: launchResult?.instanceId ?? "",
|
|
typeId: agentGuiWorkbenchTypeId
|
|
} as never)
|
|
).toEqual({
|
|
conversationRailCollapsed: false,
|
|
conversationRailWidthPx: null,
|
|
lastActiveAgentSessionId: null,
|
|
agentTargetId: "local:claude-code"
|
|
});
|
|
});
|
|
|
|
it("opens a fresh cascading window for the dock 'New window' payload", () => {
|
|
const claudeTarget = createAgent("claude-code");
|
|
const contribution = createTestAgentGuiWorkbenchContribution({
|
|
defaultProvider: "claude-code",
|
|
agents: [createAgent("codex"), claudeTarget],
|
|
renderBody: () => null,
|
|
workspaceId: "workspace-1"
|
|
});
|
|
const [dockEntry] = contribution.dockEntries ?? [];
|
|
|
|
// The regular launch can reuse the contribution's opaque target binding...
|
|
expect(dockEntry?.launchPayload).not.toHaveProperty("openInNewWindow");
|
|
expect(dockEntry?.allowNewWindowInDockPopup).toBe(false);
|
|
// ...while the "New window" payload forces a fresh window.
|
|
expect(dockEntry?.newWindowLaunchPayload).toMatchObject({
|
|
openInNewWindow: true
|
|
});
|
|
|
|
const launchResult = contribution.onLaunchRequest?.({
|
|
dockEntryId: dockEntry?.id,
|
|
layoutConstraints: testLaunchLayout.layoutConstraints,
|
|
payload: dockEntry?.newWindowLaunchPayload,
|
|
reason: "dock",
|
|
surfaceSize: testLaunchLayout.surfaceSize,
|
|
typeId: agentGuiWorkbenchTypeId,
|
|
workspaceId: "workspace-1"
|
|
}) as
|
|
| {
|
|
cascadeOffset?: { x: number; y: number };
|
|
instanceId: string;
|
|
}
|
|
| null
|
|
| undefined;
|
|
|
|
// A unique opaque per-launch instance so
|
|
// the workbench opens a new node instead of re-focusing the existing one.
|
|
expect(launchResult?.instanceId).toMatch(/^agent-gui:instance:/);
|
|
expect(launchResult?.cascadeOffset).toBeDefined();
|
|
// The new window still commits to the resolved target/provider.
|
|
expect(
|
|
contribution.externalStateSource?.getSnapshotNodeState?.({
|
|
instanceId: launchResult?.instanceId ?? "",
|
|
typeId: agentGuiWorkbenchTypeId
|
|
} as never)
|
|
).toMatchObject({
|
|
agentTargetId: "local:claude-code"
|
|
});
|
|
});
|
|
|
|
it("opens a fresh unified Agent node from the dock popup new-window action", () => {
|
|
const contribution = createTestAgentGuiWorkbenchContribution({
|
|
renderBody: () => null,
|
|
workspaceId: "workspace-1"
|
|
});
|
|
const [dockEntry] = contribution.dockEntries ?? [];
|
|
|
|
const launchResult = contribution.onLaunchRequest?.({
|
|
dockEntryId: dockEntry?.id,
|
|
launchSource: "dock-popup-new-window",
|
|
layoutConstraints: testLaunchLayout.layoutConstraints,
|
|
payload: dockEntry?.launchPayload,
|
|
reason: "dock",
|
|
surfaceSize: testLaunchLayout.surfaceSize,
|
|
typeId: agentGuiWorkbenchTypeId,
|
|
workspaceId: "workspace-1"
|
|
}) as
|
|
| {
|
|
cascadeOffset?: { x: number; y: number };
|
|
dockEntryId?: string;
|
|
reuseDockEntryNode?: boolean;
|
|
}
|
|
| null
|
|
| undefined;
|
|
|
|
expect(launchResult).toMatchObject({
|
|
cascadeOffset: agentGuiWorkbenchNewWindowCascadeOffset,
|
|
dockEntryId: agentGuiWorkbenchUnifiedDockEntryId(),
|
|
reuseDockEntryNode: false
|
|
});
|
|
});
|
|
|
|
it("does not seed fallback target state while provider targets are loading", () => {
|
|
const contribution = createTestAgentGuiWorkbenchContribution({
|
|
agents: [],
|
|
agentsLoading: true,
|
|
renderBody: () => null,
|
|
workspaceId: "workspace-1"
|
|
});
|
|
const [dockEntry] = contribution.dockEntries ?? [];
|
|
|
|
const launchResult = contribution.onLaunchRequest?.({
|
|
dockEntryId: dockEntry?.id,
|
|
layoutConstraints: testLaunchLayout.layoutConstraints,
|
|
payload: dockEntry?.launchPayload,
|
|
reason: "dock",
|
|
surfaceSize: testLaunchLayout.surfaceSize,
|
|
typeId: agentGuiWorkbenchTypeId,
|
|
workspaceId: "workspace-1"
|
|
}) as
|
|
| {
|
|
instanceId: string;
|
|
}
|
|
| null
|
|
| undefined;
|
|
|
|
expect(launchResult).toBeNull();
|
|
expect(
|
|
contribution.externalStateSource?.getSnapshotNodeState?.({
|
|
instanceId: launchResult?.instanceId ?? "",
|
|
typeId: agentGuiWorkbenchTypeId
|
|
} as never)
|
|
).toBeNull();
|
|
});
|
|
|
|
it("does not seed fallback target state when provider targets are explicitly empty", () => {
|
|
const contribution = createTestAgentGuiWorkbenchContribution({
|
|
agents: [],
|
|
renderBody: () => null,
|
|
workspaceId: "workspace-1"
|
|
});
|
|
const [dockEntry] = contribution.dockEntries ?? [];
|
|
|
|
const launchResult = contribution.onLaunchRequest?.({
|
|
dockEntryId: dockEntry?.id,
|
|
layoutConstraints: testLaunchLayout.layoutConstraints,
|
|
payload: dockEntry?.launchPayload,
|
|
reason: "dock",
|
|
surfaceSize: testLaunchLayout.surfaceSize,
|
|
typeId: agentGuiWorkbenchTypeId,
|
|
workspaceId: "workspace-1"
|
|
}) as
|
|
| {
|
|
instanceId: string;
|
|
}
|
|
| null
|
|
| undefined;
|
|
|
|
expect(launchResult).toBeNull();
|
|
expect(
|
|
contribution.externalStateSource?.getSnapshotNodeState?.({
|
|
instanceId: launchResult?.instanceId ?? "",
|
|
typeId: agentGuiWorkbenchTypeId
|
|
} as never)
|
|
).toBeNull();
|
|
});
|
|
|
|
it("uses package defaults when the host does not provide copy", () => {
|
|
expect(resolveAgentGuiWorkbenchContributionCopy()).toEqual(
|
|
agentGuiWorkbenchDefaultCopy
|
|
);
|
|
|
|
const contribution = createTestAgentGuiWorkbenchContribution({
|
|
renderBody: () => null,
|
|
workspaceId: "workspace-1"
|
|
});
|
|
|
|
expect(contribution.nodes?.[0]?.title).toBe(
|
|
agentGuiWorkbenchDefaultCopy.nodeTitle
|
|
);
|
|
});
|
|
|
|
it("contributes a window close effect only for an active canonical session", async () => {
|
|
const sessionEngine = createTestAgentSessionEngine("workspace-1");
|
|
sessionEngine.dispatch({
|
|
sessions: [
|
|
{
|
|
activeTurn: {
|
|
agentSessionId: "session-1",
|
|
origin: "user_prompt",
|
|
phase: "waiting",
|
|
startedAtUnixMs: 10,
|
|
turnId: "turn-1",
|
|
updatedAtUnixMs: 10
|
|
},
|
|
activeTurnId: "turn-1",
|
|
agentSessionId: "session-1",
|
|
agentTargetId: "local:codex",
|
|
createdAtUnixMs: 1,
|
|
cwd: "/workspace",
|
|
latestTurnInteractions: [],
|
|
pendingInteractions: [],
|
|
provider: "codex",
|
|
title: "Waiting session",
|
|
workspaceId: "workspace-1"
|
|
}
|
|
],
|
|
type: "session/snapshotReceived"
|
|
});
|
|
const contribution = createTestAgentGuiWorkbenchContribution({
|
|
renderBody: () => null,
|
|
sessionEngine,
|
|
workspaceId: "workspace-1"
|
|
});
|
|
const context = {
|
|
externalNodeState: {
|
|
lastActiveAgentSessionId: "session-1"
|
|
},
|
|
externalWorkspaceState: null,
|
|
instanceId: "agent-gui:codex:panel:test-1",
|
|
node: {
|
|
data: { typeId: agentGuiWorkbenchTypeId },
|
|
id: "agent-gui-node-1",
|
|
title: "Agent"
|
|
},
|
|
workspaceId: "workspace-1"
|
|
} as never;
|
|
|
|
await expect(
|
|
Promise.resolve(
|
|
contribution.nodes?.[0]?.getWindowCloseEffect?.(context)
|
|
)
|
|
).resolves.toEqual({
|
|
nodeId: "agent-gui-node-1",
|
|
title: "Agent",
|
|
typeId: agentGuiWorkbenchTypeId
|
|
});
|
|
|
|
sessionEngine.dispatch({
|
|
sessions: [createWorkbenchSession("Settled session", 20)],
|
|
type: "session/snapshotReceived"
|
|
});
|
|
|
|
await expect(
|
|
Promise.resolve(
|
|
contribution.nodes?.[0]?.getWindowCloseEffect?.(context)
|
|
)
|
|
).resolves.toBeNull();
|
|
});
|
|
|
|
it("lets hosts override only the copy they own", () => {
|
|
expect(
|
|
resolveAgentGuiWorkbenchContributionCopy({
|
|
nodeTitle: "Assistant"
|
|
})
|
|
).toEqual({
|
|
...agentGuiWorkbenchDefaultCopy,
|
|
nodeTitle: "Assistant"
|
|
});
|
|
});
|
|
|
|
it("uses packaged dock icons when the host does not provide icon URLs", () => {
|
|
const contribution = createTestAgentGuiWorkbenchContribution({
|
|
renderBody: () => null,
|
|
workspaceId: "workspace-1"
|
|
});
|
|
|
|
const dockEntry = contribution.dockEntries?.find(
|
|
(entry) => entry.id === agentGuiWorkbenchUnifiedDockEntryId()
|
|
);
|
|
|
|
expect(readDockEntryIconImageSrcs(dockEntry?.icon)).toEqual([
|
|
agentGuiDockIconUrls.codex,
|
|
agentGuiDockIconUrls["claude-code"],
|
|
agentGuiDockIconUrls["tutti-agent"]
|
|
]);
|
|
});
|
|
|
|
it("lets hosts override packaged dock icons explicitly", () => {
|
|
const contribution = createTestAgentGuiWorkbenchContribution({
|
|
dockIconUrls: {
|
|
codex: "app://icons/codex.png"
|
|
},
|
|
renderBody: () => null,
|
|
workspaceId: "workspace-1"
|
|
});
|
|
|
|
const dockEntry = contribution.dockEntries?.find(
|
|
(entry) => entry.id === agentGuiWorkbenchUnifiedDockEntryId()
|
|
);
|
|
|
|
expect(readDockEntryIconImageSrcs(dockEntry?.icon)).toEqual([
|
|
"app://icons/codex.png",
|
|
agentGuiDockIconUrls["claude-code"],
|
|
agentGuiDockIconUrls["tutti-agent"]
|
|
]);
|
|
});
|
|
|
|
it("renders the left agent window title while the rail is expanded", () => {
|
|
const contribution = createTestAgentGuiWorkbenchContribution({
|
|
dockIconUrls: {
|
|
codex: "tutti-asset://agent/codex.png"
|
|
},
|
|
renderBody: () => null,
|
|
workspaceId: "workspace-1"
|
|
});
|
|
|
|
render(
|
|
contribution.nodes?.[0]?.renderHeader?.({
|
|
activation: null,
|
|
defaultActions: null,
|
|
displayMode: "floating",
|
|
dragHandleProps: {},
|
|
externalNodeState: null,
|
|
externalWorkspaceState: null,
|
|
instanceId: "agent-gui:codex:panel:test-1",
|
|
instanceKey: null,
|
|
isFocused: true,
|
|
node: {
|
|
data: {
|
|
runtimeNodeState: null
|
|
},
|
|
displayMode: "floating",
|
|
frame: { height: 560, width: 1040, x: 0, y: 0 },
|
|
id: "agent-gui-node-1",
|
|
title: "Agent"
|
|
},
|
|
surfaceSize: { height: 800, width: 1200 },
|
|
windowActions: {
|
|
applyQuickLayout: () => {},
|
|
close: () => {},
|
|
focus: () => {},
|
|
minimize: () => {},
|
|
resize: () => {},
|
|
toggleDisplayMode: () => {}
|
|
}
|
|
} as never) ?? null
|
|
);
|
|
|
|
expect(screen.getByText("Agent")).toHaveClass(
|
|
"agent-gui-workbench-header__agent-name"
|
|
);
|
|
expect(screen.queryByText("Codex")).toBeNull();
|
|
expect(screen.queryByTestId("agent-gui-window-session-icon")).toBeNull();
|
|
});
|
|
|
|
it("uses prefill activation provider for handoff body rendering", () => {
|
|
const renderBody = vi.fn(() => null);
|
|
const contribution = createTestAgentGuiWorkbenchContribution({
|
|
renderBody,
|
|
workspaceId: "workspace-1"
|
|
});
|
|
|
|
contribution.nodes?.[0]?.renderBody?.({
|
|
activation: {
|
|
payload: {
|
|
agentTargetId: "local:codex",
|
|
draftPrompt: "Continue from this session",
|
|
provider: "codex"
|
|
},
|
|
sequence: 12,
|
|
type: agentGuiWorkbenchPrefillPromptActivationType
|
|
},
|
|
externalNodeState: null,
|
|
externalWorkspaceState: null,
|
|
instanceId: "agent-gui:claude-code:panel:handoff-source",
|
|
instanceKey: null,
|
|
node: {
|
|
data: {
|
|
runtimeNodeState: null
|
|
},
|
|
displayMode: "floating",
|
|
frame: { height: 560, width: 1040, x: 0, y: 0 },
|
|
id: "agent-gui-node-1",
|
|
title: "Agent"
|
|
}
|
|
} as Parameters<
|
|
NonNullable<(typeof contribution.nodes)[number]["renderBody"]>
|
|
>[0]);
|
|
|
|
expect(renderBody).toHaveBeenCalledWith(
|
|
expect.anything(),
|
|
expect.objectContaining({
|
|
provider: "codex"
|
|
})
|
|
);
|
|
});
|
|
|
|
it("opens at 90 percent width and height of the visible workbench area", () => {
|
|
const frame = resolveAgentGuiWorkbenchDefaultLaunchFrame({
|
|
frame: { height: 560, width: 1040, x: 140, y: 48 },
|
|
request: {
|
|
layoutConstraints: {
|
|
minHeight: 160,
|
|
minWidth: 280,
|
|
safeArea: {
|
|
bottom: 79,
|
|
left: 0,
|
|
right: 0,
|
|
top: 52
|
|
},
|
|
surfacePadding: 0
|
|
},
|
|
surfaceSize: {
|
|
height: 900,
|
|
width: 1440
|
|
}
|
|
}
|
|
});
|
|
|
|
expect(frame).toEqual({
|
|
height: 692,
|
|
width: 1296,
|
|
x: 72,
|
|
y: 91
|
|
});
|
|
});
|
|
|
|
it("keeps the 90 percent width on compact visible workbench areas", () => {
|
|
const frame = resolveAgentGuiWorkbenchDefaultLaunchFrame({
|
|
frame: { height: 560, width: 1040, x: 140, y: 48 },
|
|
request: {
|
|
layoutConstraints: {
|
|
minHeight: 160,
|
|
minWidth: 280,
|
|
safeArea: {
|
|
bottom: 79,
|
|
left: 0,
|
|
right: 0,
|
|
top: 52
|
|
},
|
|
surfacePadding: 0
|
|
},
|
|
surfaceSize: {
|
|
height: 700,
|
|
width: 980
|
|
}
|
|
}
|
|
});
|
|
|
|
expect(frame).toEqual({
|
|
height: 512,
|
|
width: 882,
|
|
x: 49,
|
|
y: 81
|
|
});
|
|
});
|
|
|
|
it("uses 90 percent of the visible height when the remaining workbench height is compact", () => {
|
|
const frame = resolveAgentGuiWorkbenchDefaultLaunchFrame({
|
|
frame: { height: 560, width: 1040, x: 140, y: 48 },
|
|
request: {
|
|
layoutConstraints: {
|
|
minHeight: 160,
|
|
minWidth: 280,
|
|
safeArea: {
|
|
bottom: 79,
|
|
left: 0,
|
|
right: 0,
|
|
top: 52
|
|
},
|
|
surfacePadding: 0
|
|
},
|
|
surfaceSize: {
|
|
height: 747,
|
|
width: 1440
|
|
}
|
|
}
|
|
});
|
|
|
|
expect(frame).toEqual({
|
|
height: 554,
|
|
width: 1296,
|
|
x: 72,
|
|
y: 83
|
|
});
|
|
});
|
|
|
|
it("preserves the visible-area frame during compact launches", () => {
|
|
const contribution = createTestAgentGuiWorkbenchContribution({
|
|
renderBody: () => null,
|
|
workspaceId: "workspace-1"
|
|
});
|
|
|
|
const launchResult = contribution.onLaunchRequest?.({
|
|
layoutConstraints: {
|
|
minHeight: 160,
|
|
minWidth: 280,
|
|
safeArea: {
|
|
bottom: 79,
|
|
left: 0,
|
|
right: 0,
|
|
top: 52
|
|
},
|
|
surfacePadding: 0
|
|
},
|
|
dockEntryId: "agent-gui:codex",
|
|
payload: { provider: "codex" },
|
|
reason: "dock",
|
|
surfaceSize: {
|
|
height: 700,
|
|
width: 980
|
|
},
|
|
typeId: "agent-gui",
|
|
workspaceId: "workspace-1"
|
|
});
|
|
|
|
expect(launchResult).toMatchObject({
|
|
defaultFrame: {
|
|
height: 512,
|
|
width: 882,
|
|
x: 49,
|
|
y: 81
|
|
},
|
|
framePolicy: "absolute"
|
|
});
|
|
});
|
|
|
|
it("opens requested sessions in new panel instances when explicitly requested", async () => {
|
|
const contribution = createTestAgentGuiWorkbenchContribution({
|
|
renderBody: () => null,
|
|
workspaceId: "workspace-1"
|
|
});
|
|
const baseRequest = {
|
|
layoutConstraints: {
|
|
minHeight: 160,
|
|
minWidth: 280,
|
|
safeArea: {
|
|
bottom: 79,
|
|
left: 0,
|
|
right: 0,
|
|
top: 52
|
|
},
|
|
surfacePadding: 0
|
|
},
|
|
reason: "host" as const,
|
|
surfaceSize: {
|
|
height: 900,
|
|
width: 1440
|
|
},
|
|
typeId: "agent-gui",
|
|
workspaceId: "workspace-1"
|
|
};
|
|
|
|
const existingLaunch = await contribution.onLaunchRequest?.({
|
|
...baseRequest,
|
|
payload: {
|
|
agentSessionId: "session-1",
|
|
provider: "codex"
|
|
}
|
|
});
|
|
const newWindowLaunch = await contribution.onLaunchRequest?.({
|
|
...baseRequest,
|
|
payload: {
|
|
agentSessionId: "session-1",
|
|
openInNewWindow: true,
|
|
provider: "codex"
|
|
}
|
|
});
|
|
|
|
expect(existingLaunch?.instanceId).toMatch(/^agent-gui:instance:/);
|
|
expect(newWindowLaunch?.instanceId).toMatch(/^agent-gui:instance:/);
|
|
expect(newWindowLaunch?.instanceId).not.toBe(existingLaunch?.instanceId);
|
|
expect(existingLaunch?.cascadeOffset).toBeUndefined();
|
|
expect(newWindowLaunch?.cascadeOffset).toEqual(
|
|
agentGuiWorkbenchNewWindowCascadeOffset
|
|
);
|
|
expect(newWindowLaunch?.activation).toEqual({
|
|
payload: {
|
|
agentSessionId: "session-1"
|
|
},
|
|
type: "agent-gui:open-session"
|
|
});
|
|
});
|
|
|
|
it("preserves the frame of an already-open session window when re-launched (e.g. from a completion notification)", async () => {
|
|
const contribution = createTestAgentGuiWorkbenchContribution({
|
|
renderBody: () => null,
|
|
workspaceId: "workspace-1"
|
|
});
|
|
const baseRequest = {
|
|
layoutConstraints: {
|
|
minHeight: 160,
|
|
minWidth: 280,
|
|
safeArea: {
|
|
bottom: 79,
|
|
left: 0,
|
|
right: 0,
|
|
top: 52
|
|
},
|
|
surfacePadding: 0
|
|
},
|
|
reason: "host" as const,
|
|
surfaceSize: {
|
|
height: 900,
|
|
width: 1440
|
|
},
|
|
typeId: "agent-gui",
|
|
workspaceId: "workspace-1"
|
|
};
|
|
const payload = {
|
|
agentSessionId: "session-1",
|
|
provider: "codex"
|
|
};
|
|
|
|
const firstLaunch = await contribution.onLaunchRequest?.({
|
|
...baseRequest,
|
|
payload
|
|
});
|
|
const relaunch = await contribution.onLaunchRequest?.({
|
|
...baseRequest,
|
|
payload
|
|
});
|
|
|
|
expect(firstLaunch?.instanceId).toMatch(/^agent-gui:instance:/);
|
|
expect(relaunch?.instanceId).toBe(firstLaunch?.instanceId);
|
|
expect(firstLaunch?.preserveExistingNodeFrame).not.toBe(true);
|
|
expect(relaunch?.preserveExistingNodeFrame).toBe(true);
|
|
});
|
|
|
|
it("keeps different sessions on the same target in different nodes", async () => {
|
|
const contribution = createTestAgentGuiWorkbenchContribution({
|
|
renderBody: () => null,
|
|
workspaceId: "workspace-1"
|
|
});
|
|
const baseRequest = {
|
|
layoutConstraints: testLaunchLayout.layoutConstraints,
|
|
reason: "host" as const,
|
|
surfaceSize: testLaunchLayout.surfaceSize,
|
|
typeId: "agent-gui",
|
|
workspaceId: "workspace-1"
|
|
};
|
|
const launchSession = (agentSessionId: string) =>
|
|
contribution.onLaunchRequest?.({
|
|
...baseRequest,
|
|
payload: {
|
|
agentSessionId,
|
|
agentTargetId: "local:codex",
|
|
provider: "codex"
|
|
}
|
|
});
|
|
|
|
const firstSessionLaunch = await launchSession("session-1");
|
|
const secondSessionLaunch = await launchSession("session-2");
|
|
const secondSessionRelaunch = await launchSession("session-2");
|
|
|
|
expect(firstSessionLaunch?.instanceId).toMatch(/^agent-gui:instance:/);
|
|
expect(secondSessionLaunch?.instanceId).toMatch(/^agent-gui:instance:/);
|
|
expect(secondSessionLaunch?.instanceId).not.toBe(
|
|
firstSessionLaunch?.instanceId
|
|
);
|
|
expect(secondSessionRelaunch?.instanceId).toBe(
|
|
secondSessionLaunch?.instanceId
|
|
);
|
|
expect(
|
|
contribution.externalStateSource?.getSnapshotNodeState?.({
|
|
instanceId: firstSessionLaunch?.instanceId ?? "",
|
|
typeId: agentGuiWorkbenchTypeId
|
|
} as never)
|
|
).toMatchObject({
|
|
agentTargetId: "local:codex",
|
|
lastActiveAgentSessionId: "session-1"
|
|
});
|
|
expect(
|
|
contribution.externalStateSource?.getSnapshotNodeState?.({
|
|
instanceId: secondSessionLaunch?.instanceId ?? "",
|
|
typeId: agentGuiWorkbenchTypeId
|
|
} as never)
|
|
).toMatchObject({
|
|
agentTargetId: "local:codex",
|
|
lastActiveAgentSessionId: "session-2"
|
|
});
|
|
});
|
|
|
|
it("does not treat a drifted session-keyed window as the requested session", async () => {
|
|
let rememberState: ((state: AgentGuiWorkbenchState) => void) | null = null;
|
|
const contribution = createTestAgentGuiWorkbenchContribution({
|
|
renderBody: (_context, helpers) => {
|
|
rememberState = helpers.onStateChange;
|
|
return null;
|
|
},
|
|
workspaceId: "workspace-1"
|
|
});
|
|
|
|
contribution.nodes?.[0]?.renderBody?.({
|
|
activation: null,
|
|
externalNodeState: null,
|
|
externalWorkspaceState: null,
|
|
instanceId: "agent-gui:codex:session:session-1",
|
|
instanceKey: null,
|
|
node: {
|
|
data: {
|
|
runtimeNodeState: null
|
|
},
|
|
displayMode: "floating",
|
|
frame: { height: 560, width: 1040, x: 0, y: 0 },
|
|
id: "legacy-session-node",
|
|
title: "Agent"
|
|
}
|
|
} as Parameters<
|
|
NonNullable<(typeof contribution.nodes)[number]["renderBody"]>
|
|
>[0]);
|
|
expect(rememberState).not.toBeNull();
|
|
(rememberState as unknown as (state: AgentGuiWorkbenchState) => void)({
|
|
conversationRailCollapsed: false,
|
|
conversationRailWidthPx: null,
|
|
lastActiveAgentSessionId: "session-2"
|
|
});
|
|
|
|
const relaunch = await contribution.onLaunchRequest?.({
|
|
layoutConstraints: testLaunchLayout.layoutConstraints,
|
|
payload: {
|
|
agentSessionId: "session-1",
|
|
provider: "codex"
|
|
},
|
|
reason: "host",
|
|
surfaceSize: testLaunchLayout.surfaceSize,
|
|
typeId: "agent-gui",
|
|
workspaceId: "workspace-1"
|
|
});
|
|
|
|
expect(relaunch?.instanceId).not.toBe("agent-gui:codex:session:session-1");
|
|
expect(relaunch?.instanceId).toMatch(/^agent-gui:instance:/);
|
|
expect(relaunch?.preserveExistingNodeFrame).not.toBe(true);
|
|
});
|
|
|
|
it("keeps compact new-window session launches on the cascade policy", async () => {
|
|
const contribution = createTestAgentGuiWorkbenchContribution({
|
|
renderBody: () => null,
|
|
workspaceId: "workspace-1"
|
|
});
|
|
|
|
const launchResult = await contribution.onLaunchRequest?.({
|
|
layoutConstraints: {
|
|
minHeight: 160,
|
|
minWidth: 280,
|
|
safeArea: {
|
|
bottom: 79,
|
|
left: 0,
|
|
right: 0,
|
|
top: 52
|
|
},
|
|
surfacePadding: 0
|
|
},
|
|
payload: {
|
|
agentSessionId: "session-1",
|
|
openInNewWindow: true,
|
|
provider: "codex"
|
|
},
|
|
reason: "host",
|
|
surfaceSize: {
|
|
height: 700,
|
|
width: 980
|
|
},
|
|
typeId: "agent-gui",
|
|
workspaceId: "workspace-1"
|
|
});
|
|
|
|
expect(launchResult).toMatchObject({
|
|
cascadeOffset: agentGuiWorkbenchNewWindowCascadeOffset,
|
|
defaultFrame: {
|
|
height: 512,
|
|
width: 882,
|
|
x: 49,
|
|
y: 81
|
|
},
|
|
framePolicy: "cascade-same-type-centered"
|
|
});
|
|
});
|
|
|
|
it("matches codex panel nodes and only renders popup previews through the host renderer", async () => {
|
|
const contribution = createTestAgentGuiWorkbenchContribution({
|
|
renderBody: () => null,
|
|
workspaceId: "workspace-1"
|
|
});
|
|
const dockEntry = contribution.dockEntries?.find(
|
|
(entry) => entry.id === agentGuiWorkbenchUnifiedDockEntryId()
|
|
);
|
|
expect(dockEntry).toBeDefined();
|
|
|
|
const node = {
|
|
data: {
|
|
dockEntryId: agentGuiWorkbenchUnifiedDockEntryId(),
|
|
instanceId: "agent-gui:codex:panel:test-1",
|
|
typeId: agentGuiWorkbenchTypeId
|
|
},
|
|
id: "agent-gui:agent-gui:codex:panel:test-1",
|
|
title: "Codex"
|
|
};
|
|
|
|
expect(dockEntry?.matchNode).toBeUndefined();
|
|
expect(
|
|
dockEntry?.providePopupItemPreview?.({
|
|
externalNodeState: {
|
|
lastActiveAgentSessionId: "session-1",
|
|
lastActiveConversationTitle: "Implement dock previews"
|
|
},
|
|
externalWorkspaceState: null,
|
|
host: {} as never,
|
|
isFocused: false,
|
|
isMinimized: false,
|
|
node: node as never
|
|
}) ?? null
|
|
).toBeNull();
|
|
});
|
|
|
|
it("leaves agent GUI dock popup previews to the host image capture", () => {
|
|
const contribution = createTestAgentGuiWorkbenchContribution({
|
|
renderBody: () => null,
|
|
workspaceId: "workspace-1"
|
|
});
|
|
const dockEntry = contribution.dockEntries?.find(
|
|
(entry) => entry.id === agentGuiWorkbenchUnifiedDockEntryId()
|
|
);
|
|
expect(dockEntry).toBeDefined();
|
|
|
|
expect(dockEntry?.providePopupItemPreview).toBeUndefined();
|
|
});
|
|
|
|
it("uses host snapshot capture for agent GUI minimized slots", () => {
|
|
const contribution = createTestAgentGuiWorkbenchContribution({
|
|
renderBody: () => null,
|
|
workspaceId: "workspace-1"
|
|
});
|
|
const minimizedDock = contribution.nodes?.[0]?.window?.minimizedDock;
|
|
expect(minimizedDock).toEqual({ kind: "snapshot" });
|
|
});
|
|
|
|
it("shows a new-session action when collapsed", () => {
|
|
const contribution = createTestAgentGuiWorkbenchContribution({
|
|
renderBody: () => null,
|
|
workspaceId: "workspace-1"
|
|
});
|
|
const events: CustomEvent[] = [];
|
|
const handler = (event: Event) => {
|
|
events.push(event as CustomEvent);
|
|
};
|
|
window.addEventListener(AGENT_GUI_WORKBENCH_COMMAND_EVENT, handler);
|
|
|
|
try {
|
|
render(
|
|
contribution.nodes?.[0]?.renderHeader?.({
|
|
activation: null,
|
|
defaultActions: null,
|
|
displayMode: "floating",
|
|
dragHandleProps: {},
|
|
externalNodeState: {
|
|
conversationRailCollapsed: true,
|
|
lastActiveAgentSessionId: null
|
|
},
|
|
externalWorkspaceState: null,
|
|
instanceId: "agent-gui:codex:panel:test-1",
|
|
instanceKey: null,
|
|
isFocused: true,
|
|
node: {
|
|
data: {
|
|
runtimeNodeState: null
|
|
},
|
|
displayMode: "floating",
|
|
frame: { height: 560, width: 1040, x: 0, y: 0 },
|
|
id: "agent-gui-node-1",
|
|
title: "Codex"
|
|
},
|
|
surfaceSize: { height: 800, width: 1200 },
|
|
windowActions: {
|
|
applyQuickLayout: () => {},
|
|
close: () => {},
|
|
focus: () => {},
|
|
minimize: () => {},
|
|
resize: () => {},
|
|
toggleDisplayMode: () => {}
|
|
}
|
|
} as never) ?? null
|
|
);
|
|
|
|
fireEvent.click(
|
|
screen.getByRole("button", {
|
|
name: agentGuiWorkbenchDefaultCopy.newConversation
|
|
})
|
|
);
|
|
} finally {
|
|
window.removeEventListener(AGENT_GUI_WORKBENCH_COMMAND_EVENT, handler);
|
|
}
|
|
|
|
expect(events).toHaveLength(1);
|
|
expect(events[0]?.detail).toEqual({
|
|
instanceId: "agent-gui:codex:panel:test-1",
|
|
type: "new-conversation"
|
|
});
|
|
});
|
|
|
|
it("publishes rail toggles through the reusable Workbench command contract", () => {
|
|
const contribution = createTestAgentGuiWorkbenchContribution({
|
|
renderBody: () => null,
|
|
workspaceId: "workspace-1"
|
|
});
|
|
const commands: AgentGuiWorkbenchCommand[] = [];
|
|
const handler = (event: Event) => {
|
|
commands.push((event as CustomEvent<AgentGuiWorkbenchCommand>).detail);
|
|
};
|
|
window.addEventListener(AGENT_GUI_WORKBENCH_COMMAND_EVENT, handler);
|
|
|
|
try {
|
|
render(
|
|
contribution.nodes?.[0]?.renderHeader?.({
|
|
activation: null,
|
|
defaultActions: null,
|
|
displayMode: "floating",
|
|
dragHandleProps: {},
|
|
externalNodeState: {
|
|
conversationRailCollapsed: false,
|
|
lastActiveAgentSessionId: null
|
|
},
|
|
externalWorkspaceState: null,
|
|
instanceId: "agent-gui:codex:panel:test-1",
|
|
instanceKey: null,
|
|
isFocused: true,
|
|
node: {
|
|
data: {
|
|
runtimeNodeState: null
|
|
},
|
|
displayMode: "floating",
|
|
frame: { height: 560, width: 1040, x: 0, y: 0 },
|
|
id: "agent-gui-node-1",
|
|
title: "Codex"
|
|
},
|
|
surfaceSize: { height: 800, width: 1200 },
|
|
windowActions: {
|
|
applyQuickLayout: () => {},
|
|
close: () => {},
|
|
focus: () => {},
|
|
minimize: () => {},
|
|
resize: () => {},
|
|
toggleDisplayMode: () => {}
|
|
}
|
|
} as never) ?? null
|
|
);
|
|
|
|
fireEvent.click(screen.getByTestId("agent-gui-toggle-conversation-rail"));
|
|
} finally {
|
|
window.removeEventListener(AGENT_GUI_WORKBENCH_COMMAND_EVENT, handler);
|
|
}
|
|
|
|
expect(commands).toEqual([
|
|
{
|
|
conversationRailCollapsed: true,
|
|
instanceId: "agent-gui:codex:panel:test-1",
|
|
type: "conversation-rail-toggle"
|
|
}
|
|
]);
|
|
});
|
|
|
|
it("shows the active session icon and title when the rail is collapsed", () => {
|
|
const contribution = createTestAgentGuiWorkbenchContribution({
|
|
renderBody: () => null,
|
|
resolveDockPopupIdentity: (state) =>
|
|
state?.lastActiveAgentSessionId === "session-1"
|
|
? {
|
|
iconUrl: "tutti-asset://agent/codex-session.png",
|
|
title: "Current session title"
|
|
}
|
|
: null,
|
|
workspaceId: "workspace-1"
|
|
});
|
|
|
|
const close = vi.fn();
|
|
const minimize = vi.fn();
|
|
const toggleDisplayMode = vi.fn();
|
|
|
|
render(
|
|
contribution.nodes?.[0]?.renderHeader?.({
|
|
activation: null,
|
|
defaultActions: null,
|
|
displayMode: "floating",
|
|
dragHandleProps: {},
|
|
externalNodeState: {
|
|
conversationRailCollapsed: true,
|
|
lastActiveAgentSessionId: "session-1"
|
|
},
|
|
externalWorkspaceState: null,
|
|
instanceId: "agent-gui:codex:panel:test-1",
|
|
instanceKey: null,
|
|
isFocused: true,
|
|
node: {
|
|
data: {
|
|
runtimeNodeState: null
|
|
},
|
|
displayMode: "floating",
|
|
frame: { height: 560, width: 1040, x: 0, y: 0 },
|
|
id: "agent-gui-node-1",
|
|
title: "Codex"
|
|
},
|
|
surfaceSize: { height: 800, width: 1200 },
|
|
windowActions: {
|
|
applyQuickLayout: () => {},
|
|
close,
|
|
focus: () => {},
|
|
minimize,
|
|
resize: () => {},
|
|
toggleDisplayMode
|
|
}
|
|
} as never) ?? null
|
|
);
|
|
|
|
const newConversationButton = screen.getByRole("button", {
|
|
name: agentGuiWorkbenchDefaultCopy.newConversation
|
|
});
|
|
const toggleButton = screen.getByTestId(
|
|
"agent-gui-toggle-conversation-rail"
|
|
);
|
|
const header = document.querySelector(
|
|
'[data-agent-gui-workbench-header="true"]'
|
|
);
|
|
const primary = document.querySelector(
|
|
"[data-agent-gui-workbench-header-primary='true']"
|
|
);
|
|
|
|
expect(header).toHaveAttribute(
|
|
"data-agent-gui-workbench-header-collapsed",
|
|
"true"
|
|
);
|
|
expect(header).toHaveAttribute(
|
|
"data-agent-gui-workbench-header-body-error",
|
|
"false"
|
|
);
|
|
expect(primary).not.toHaveTextContent("Agent");
|
|
expect(screen.queryByText("Agent")).toBeNull();
|
|
expect(screen.queryByText("Codex")).toBeNull();
|
|
expect(toggleButton).toHaveClass("agent-gui-workbench-header__icon-button");
|
|
expect(toggleButton).toHaveClass("agent-gui-workbench-header__rail-toggle");
|
|
expect(toggleButton).toHaveAttribute("data-size", "icon-sm");
|
|
expect(toggleButton.querySelector("svg")).toHaveClass(
|
|
"agent-gui-workbench-header__icon"
|
|
);
|
|
expect(newConversationButton).toHaveClass(
|
|
"agent-gui-workbench-header__icon-button"
|
|
);
|
|
expect(newConversationButton).toHaveAttribute("data-size", "icon-sm");
|
|
expect(newConversationButton.querySelector("svg")).toHaveClass(
|
|
"agent-gui-workbench-header__icon"
|
|
);
|
|
const collapsedSessionTitle = screen
|
|
.getByText("Current session title")
|
|
.closest(".agent-gui-workbench-header__session-title");
|
|
expect(collapsedSessionTitle).not.toBeNull();
|
|
expect(screen.getByTestId("agent-gui-window-session-icon")).toHaveAttribute(
|
|
"src",
|
|
"tutti-asset://agent/codex-session.png"
|
|
);
|
|
expect(screen.getByText("Current session title")).toHaveClass(
|
|
"agent-gui-workbench-header__title-text"
|
|
);
|
|
expect(screen.queryByTestId("agent-gui-window-detail-title")).toBeNull();
|
|
|
|
fireEvent.click(screen.getByTestId("agent-gui-window-close"));
|
|
fireEvent.click(screen.getByTestId("agent-gui-window-minimize"));
|
|
fireEvent.click(screen.getByTestId("agent-gui-window-toggle-display-mode"));
|
|
|
|
expect(close).toHaveBeenCalledTimes(1);
|
|
expect(minimize).toHaveBeenCalledTimes(1);
|
|
expect(toggleDisplayMode).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it("refreshes the workbench header when the canonical session title changes", () => {
|
|
const sessionEngine = createTestAgentSessionEngine("workspace-1");
|
|
sessionEngine.dispatch({
|
|
type: "activation/requested",
|
|
agentSessionId: "session-1",
|
|
agentTargetId: "local:codex",
|
|
clientSubmitId: "submit-1",
|
|
cwd: "/workspace",
|
|
expiresAtUnixMs: Date.now() + 45_000,
|
|
mode: "new",
|
|
requestedAtUnixMs: 1,
|
|
requestId: "activation-1",
|
|
optimisticTitle: "test1",
|
|
workspaceId: "workspace-1"
|
|
});
|
|
sessionEngine.dispatch({
|
|
session: createWorkbenchSession("", 1),
|
|
type: "session/upserted"
|
|
});
|
|
const contribution = createTestAgentGuiWorkbenchContribution({
|
|
renderBody: () => null,
|
|
sessionEngine,
|
|
workspaceId: "workspace-1"
|
|
});
|
|
|
|
render(
|
|
contribution.nodes?.[0]?.renderHeader?.({
|
|
activation: null,
|
|
defaultActions: null,
|
|
displayMode: "floating",
|
|
dragHandleProps: {},
|
|
externalNodeState: {
|
|
agentTargetId: "local:codex",
|
|
conversationRailCollapsed: false,
|
|
lastActiveAgentSessionId: "session-1"
|
|
},
|
|
externalWorkspaceState: null,
|
|
instanceId: "agent-gui:codex:panel:test-1",
|
|
instanceKey: null,
|
|
isFocused: true,
|
|
node: {
|
|
data: {
|
|
runtimeNodeState: null
|
|
},
|
|
displayMode: "floating",
|
|
frame: { height: 560, width: 1040, x: 0, y: 0 },
|
|
id: "agent-gui-node-1",
|
|
title: "Codex"
|
|
},
|
|
surfaceSize: { height: 800, width: 1200 },
|
|
windowActions: {
|
|
applyQuickLayout: () => {},
|
|
close: () => {},
|
|
focus: () => {},
|
|
minimize: () => {},
|
|
resize: () => {},
|
|
toggleDisplayMode: () => {}
|
|
}
|
|
} as never) ?? null
|
|
);
|
|
|
|
expect(
|
|
screen.getByTestId("agent-gui-window-detail-title")
|
|
).toHaveTextContent("test1");
|
|
expect(
|
|
screen.getByTestId("agent-gui-window-detail-title-icon")
|
|
).toBeInTheDocument();
|
|
|
|
act(() => {
|
|
sessionEngine.dispatch({
|
|
session: createWorkbenchSession("Updated session title", 2),
|
|
type: "session/upserted"
|
|
});
|
|
});
|
|
|
|
expect(
|
|
screen.getByTestId("agent-gui-window-detail-title")
|
|
).toHaveTextContent("Updated session title");
|
|
expect(screen.queryByText("test1")).toBeNull();
|
|
});
|
|
|
|
it("keeps unified header chrome free of provider window titles", () => {
|
|
const contribution = createTestAgentGuiWorkbenchContribution({
|
|
renderBody: () => null,
|
|
unifiedDockIconUrl: "app://icons/agent-unified.png",
|
|
workspaceId: "workspace-1"
|
|
});
|
|
|
|
render(
|
|
contribution.nodes?.[0]?.renderHeader?.({
|
|
activation: null,
|
|
defaultActions: null,
|
|
displayMode: "floating",
|
|
dragHandleProps: {},
|
|
externalNodeState: null,
|
|
externalWorkspaceState: null,
|
|
instanceId: "agent-gui:claude-code:panel:test-1",
|
|
instanceKey: null,
|
|
isFocused: true,
|
|
node: {
|
|
data: {
|
|
dockEntryId: agentGuiWorkbenchUnifiedDockEntryId(),
|
|
runtimeNodeState: null
|
|
},
|
|
displayMode: "floating",
|
|
frame: { height: 560, width: 1040, x: 0, y: 0 },
|
|
id: "agent-gui-node-1",
|
|
title: "Agent"
|
|
},
|
|
surfaceSize: { height: 800, width: 1200 },
|
|
windowActions: {
|
|
applyQuickLayout: () => {},
|
|
close: () => {},
|
|
focus: () => {},
|
|
minimize: () => {},
|
|
resize: () => {},
|
|
toggleDisplayMode: () => {}
|
|
}
|
|
} as never) ?? null
|
|
);
|
|
|
|
expect(screen.getByText("Agent")).toHaveClass(
|
|
"agent-gui-workbench-header__agent-name"
|
|
);
|
|
expect(screen.queryByText("Claude Code")).toBeNull();
|
|
expect(screen.queryByTestId("agent-gui-window-session-icon")).toBeNull();
|
|
expect(
|
|
screen.queryByTestId("agent-gui-window-detail-title-icon")
|
|
).toBeNull();
|
|
expect(screen.queryByTestId("agent-gui-window-detail-title")).toBeNull();
|
|
});
|
|
|
|
it("keys direct-manipulation header renders by visible rail layout", () => {
|
|
const definition = createTestAgentGuiWorkbenchContribution({
|
|
renderBody: () => null,
|
|
workspaceId: "workspace-1"
|
|
}).nodes?.[0];
|
|
const getKey = definition?.getHeaderFrameRenderKey;
|
|
const createContext = (input: { isDragging?: boolean; width: number }) =>
|
|
({
|
|
externalNodeState: {
|
|
conversationRailCollapsed: false,
|
|
conversationRailWidthPx: 520
|
|
},
|
|
isDragging: input.isDragging === true,
|
|
node: {
|
|
data: { runtimeNodeState: null },
|
|
frame: { height: 560, width: input.width, x: 0, y: 0 }
|
|
}
|
|
}) as never;
|
|
|
|
expect(getKey?.(createContext({ isDragging: true, width: 700 }))).toBe(
|
|
"dragging"
|
|
);
|
|
expect(getKey?.(createContext({ width: 600 }))).toBe("collapsed");
|
|
expect(getKey?.(createContext({ width: 650 }))).toBe("expanded:420");
|
|
expect(getKey?.(createContext({ width: 700 }))).toBe("expanded:470");
|
|
});
|
|
|
|
it("renders the expanded workbench header as a rail titlebar plus detail title", () => {
|
|
const openDetachedWindow = vi.fn();
|
|
const contribution = createTestAgentGuiWorkbenchContribution({
|
|
copy: {
|
|
sessionMenu: {
|
|
copyAsMarkdown: "Copy as Markdown",
|
|
copyAsReference: "Copy as reference",
|
|
moreSessionActions: "More session actions",
|
|
renameSession: "Rename session"
|
|
}
|
|
},
|
|
onOpenDetachedWindow: openDetachedWindow,
|
|
renderBody: () => null,
|
|
resolveDockPopupIdentity: (state) =>
|
|
state?.lastActiveAgentSessionId === "session-1"
|
|
? {
|
|
iconUrl: "tutti-asset://agent/codex-session.png",
|
|
title: "Current session title"
|
|
}
|
|
: null,
|
|
resolveDockPopupTitle: (state) =>
|
|
state?.lastActiveAgentSessionId === "session-1"
|
|
? "Current session title"
|
|
: null,
|
|
workspaceId: "workspace-1"
|
|
});
|
|
|
|
render(
|
|
contribution.nodes?.[0]?.renderHeader?.({
|
|
activation: null,
|
|
defaultActions: createElement(
|
|
"button",
|
|
{ type: "button" },
|
|
"window actions"
|
|
),
|
|
displayMode: "floating",
|
|
dragHandleProps: {},
|
|
externalNodeState: {
|
|
agentTargetId: "local:codex",
|
|
conversationRailCollapsed: false,
|
|
conversationRailWidthPx: 360,
|
|
lastActiveAgentSessionId: "session-1"
|
|
},
|
|
externalWorkspaceState: null,
|
|
instanceId: "agent-gui:codex:panel:test-1",
|
|
instanceKey: null,
|
|
isFocused: true,
|
|
node: {
|
|
data: {
|
|
runtimeNodeState: null
|
|
},
|
|
displayMode: "floating",
|
|
frame: { height: 560, width: 1040, x: 0, y: 0 },
|
|
id: "agent-gui-node-1",
|
|
title: "Codex"
|
|
},
|
|
surfaceSize: { height: 800, width: 1200 },
|
|
windowActions: {
|
|
applyQuickLayout: () => {},
|
|
close: () => {},
|
|
focus: () => {},
|
|
minimize: () => {},
|
|
resize: () => {},
|
|
toggleDisplayMode: () => {}
|
|
}
|
|
} as never) ?? null
|
|
);
|
|
|
|
const header = document.querySelector(
|
|
'[data-agent-gui-workbench-header="true"]'
|
|
);
|
|
const primary = document.querySelector(
|
|
"[data-agent-gui-workbench-header-primary='true']"
|
|
);
|
|
|
|
expect(header).toHaveClass("agent-gui-workbench-header");
|
|
expect(header).toHaveAttribute(
|
|
"data-agent-gui-workbench-header-collapsed",
|
|
"false"
|
|
);
|
|
expect(header).toHaveStyle({
|
|
"--agent-gui-workbench-header-rail-width": `${
|
|
360 + agentGuiWorkbenchProviderRailWidthPx
|
|
}px`
|
|
});
|
|
expect(primary).toHaveClass("agent-gui-workbench-header__primary");
|
|
expect(screen.getByText("Agent")).toHaveClass(
|
|
"agent-gui-workbench-header__agent-name"
|
|
);
|
|
expect(screen.queryByText("Codex")).toBeNull();
|
|
expect(
|
|
screen.getByTestId("agent-gui-toggle-conversation-rail")
|
|
).toHaveClass("agent-gui-workbench-header__rail-toggle");
|
|
const detachedWindowButton = screen.getByRole("button", {
|
|
name: agentGuiWorkbenchDefaultCopy.openDetachedWindow
|
|
});
|
|
expect(detachedWindowButton).toHaveClass(
|
|
"agent-gui-workbench-header__detached-window"
|
|
);
|
|
expect(screen.queryByTestId("agent-gui-window-session-icon")).toBeNull();
|
|
expect(
|
|
screen.getByTestId("agent-gui-window-detail-title")
|
|
).toHaveTextContent("Current session title");
|
|
expect(
|
|
screen.getByTestId("agent-gui-window-detail-title-icon")
|
|
).toHaveAttribute("src", "tutti-asset://agent/codex-session.png");
|
|
const sessionMenuTrigger = screen.getByTestId(
|
|
"agent-gui-session-menu-trigger"
|
|
);
|
|
expect(
|
|
screen.getByTestId("agent-gui-window-detail-title")
|
|
).toContainElement(sessionMenuTrigger);
|
|
expect(
|
|
screen.queryByRole("button", { name: "window actions" })
|
|
).not.toBeInTheDocument();
|
|
expect(
|
|
screen.queryByRole("button", {
|
|
name: agentGuiWorkbenchDefaultCopy.newConversation
|
|
})
|
|
).not.toBeInTheDocument();
|
|
|
|
fireEvent.click(detachedWindowButton);
|
|
expect(openDetachedWindow).toHaveBeenCalledWith({
|
|
agentSessionId: "session-1",
|
|
agentTargetId: "local:codex",
|
|
provider: "codex",
|
|
workspaceId: "workspace-1"
|
|
});
|
|
});
|
|
|
|
it("aligns the expanded unified header controls with the provider and conversation rails", () => {
|
|
const contribution = createTestAgentGuiWorkbenchContribution({
|
|
renderBody: () => null,
|
|
workspaceId: "workspace-1"
|
|
});
|
|
|
|
render(
|
|
contribution.nodes?.[0]?.renderHeader?.({
|
|
activation: null,
|
|
defaultActions: null,
|
|
displayMode: "floating",
|
|
dragHandleProps: {},
|
|
externalNodeState: {
|
|
conversationRailCollapsed: false,
|
|
conversationRailWidthPx: 360,
|
|
lastActiveAgentSessionId: null
|
|
},
|
|
externalWorkspaceState: null,
|
|
instanceId: "agent-gui:codex:panel:test-1",
|
|
instanceKey: null,
|
|
isFocused: true,
|
|
node: {
|
|
data: {
|
|
dockEntryId: agentGuiWorkbenchUnifiedDockEntryId(),
|
|
runtimeNodeState: null
|
|
},
|
|
displayMode: "floating",
|
|
frame: { height: 560, width: 1040, x: 0, y: 0 },
|
|
id: "agent-gui-node-1",
|
|
title: "Agent"
|
|
},
|
|
surfaceSize: { height: 800, width: 1200 },
|
|
windowActions: {
|
|
applyQuickLayout: () => {},
|
|
close: () => {},
|
|
focus: () => {},
|
|
minimize: () => {},
|
|
resize: () => {},
|
|
toggleDisplayMode: () => {}
|
|
}
|
|
} as never) ?? null
|
|
);
|
|
|
|
expect(
|
|
document.querySelector('[data-agent-gui-workbench-header="true"]')
|
|
).toHaveStyle({
|
|
"--agent-gui-workbench-header-rail-width": `${
|
|
360 + agentGuiWorkbenchProviderRailWidthPx
|
|
}px`
|
|
});
|
|
});
|
|
|
|
it("keeps the unified header aligned while the conversation rail is resizing", () => {
|
|
let reportConversationRailLayout:
|
|
| ((layout: {
|
|
conversationRailWidthPx: number;
|
|
leftPanelWidthPx: number;
|
|
providerRailWidthPx: number;
|
|
resizing: boolean;
|
|
}) => void)
|
|
| null = null;
|
|
const contribution = createTestAgentGuiWorkbenchContribution({
|
|
renderBody: (_context, helpers) => {
|
|
reportConversationRailLayout = helpers.onConversationRailLayoutChange;
|
|
return null;
|
|
},
|
|
workspaceId: "workspace-1"
|
|
});
|
|
|
|
contribution.nodes?.[0]?.renderBody?.({
|
|
activation: null,
|
|
externalNodeState: {
|
|
conversationRailCollapsed: false,
|
|
conversationRailWidthPx: 360,
|
|
lastActiveAgentSessionId: null
|
|
},
|
|
externalWorkspaceState: null,
|
|
instanceId: "agent-gui:codex:panel:test-1",
|
|
instanceKey: null,
|
|
node: {
|
|
data: {
|
|
runtimeNodeState: null
|
|
},
|
|
displayMode: "floating",
|
|
frame: { height: 560, width: 1040, x: 0, y: 0 },
|
|
id: "agent-gui-node-1",
|
|
title: "Agent"
|
|
}
|
|
} as never);
|
|
|
|
render(
|
|
contribution.nodes?.[0]?.renderHeader?.({
|
|
activation: null,
|
|
defaultActions: null,
|
|
displayMode: "floating",
|
|
dragHandleProps: {},
|
|
externalNodeState: {
|
|
conversationRailCollapsed: false,
|
|
conversationRailWidthPx: 360,
|
|
lastActiveAgentSessionId: null
|
|
},
|
|
externalWorkspaceState: null,
|
|
instanceId: "agent-gui:codex:panel:test-1",
|
|
instanceKey: null,
|
|
isFocused: true,
|
|
node: {
|
|
data: {
|
|
dockEntryId: agentGuiWorkbenchUnifiedDockEntryId(),
|
|
runtimeNodeState: null
|
|
},
|
|
displayMode: "floating",
|
|
frame: { height: 560, width: 1040, x: 0, y: 0 },
|
|
id: "agent-gui-node-1",
|
|
title: "Agent"
|
|
},
|
|
surfaceSize: { height: 800, width: 1200 },
|
|
windowActions: {
|
|
applyQuickLayout: () => {},
|
|
close: () => {},
|
|
focus: () => {},
|
|
minimize: () => {},
|
|
resize: () => {},
|
|
toggleDisplayMode: () => {}
|
|
}
|
|
} as never) ?? null
|
|
);
|
|
|
|
act(() => {
|
|
reportConversationRailLayout?.({
|
|
conversationRailWidthPx: 420,
|
|
leftPanelWidthPx: 420 + agentGuiWorkbenchProviderRailWidthPx,
|
|
providerRailWidthPx: agentGuiWorkbenchProviderRailWidthPx,
|
|
resizing: true
|
|
});
|
|
});
|
|
|
|
expect(
|
|
document.querySelector('[data-agent-gui-workbench-header="true"]')
|
|
).toHaveStyle({
|
|
"--agent-gui-workbench-header-rail-width": `${
|
|
420 + agentGuiWorkbenchProviderRailWidthPx
|
|
}px`
|
|
});
|
|
});
|
|
});
|
|
|
|
function createWorkbenchSession(title: string, updatedAtUnixMs: number) {
|
|
return normalizeAgentActivitySession({
|
|
activeTurnId: null,
|
|
agentSessionId: "session-1",
|
|
agentTargetId: "local:codex",
|
|
cwd: "/workspace",
|
|
latestTurnInteractions: [],
|
|
pendingInteractions: [],
|
|
provider: "codex",
|
|
title,
|
|
updatedAtUnixMs,
|
|
workspaceId: "workspace-1"
|
|
});
|
|
}
|