fix(web): passe le nodeId réel à la custom CLI au lieu d'un id fabriqué — #225
This commit is contained in:
@ -44,7 +44,7 @@ export interface CustomAgentChatViewProps {
|
||||
profile: AgentProfile;
|
||||
cwd: string;
|
||||
layoutId?: string;
|
||||
nodeId: string;
|
||||
nodeId?: string;
|
||||
sessionId: string | null;
|
||||
conversationId: string | null;
|
||||
composerHeightPx?: number;
|
||||
@ -125,6 +125,11 @@ function describe(e: unknown): string {
|
||||
return String(e);
|
||||
}
|
||||
|
||||
function describeOpenFailure(e: unknown, mobile: boolean): string {
|
||||
if (!mobile) return describe(e);
|
||||
return "Le CLI custom n'a pas pu démarrer. Vérifiez la configuration du profil ou utilisez la TUI native.";
|
||||
}
|
||||
|
||||
function instrumentationError(e: unknown): Record<string, unknown> {
|
||||
if (e && typeof e === "object") {
|
||||
const err = e as GatewayError & { name?: string };
|
||||
@ -803,6 +808,7 @@ export function CustomAgentChatView({
|
||||
onSessionIdRef.current = onSessionId;
|
||||
const onConversationIdRef = useRef(onConversationId);
|
||||
onConversationIdRef.current = onConversationId;
|
||||
const domId = nodeId ?? `agent-${agentId}`;
|
||||
|
||||
const clampComposerHeight = useCallback(
|
||||
(heightPx: number): number => {
|
||||
@ -891,7 +897,7 @@ export function CustomAgentChatView({
|
||||
const slashPrefix = slashCommandPrefix(draft);
|
||||
const draftScope = useMemo<ChatDraftScope | null>(() => {
|
||||
if (explicitDraftScope) return explicitDraftScope;
|
||||
return layoutId ? { projectId, layoutId, nodeId } : null;
|
||||
return layoutId && nodeId ? { projectId, layoutId, nodeId } : null;
|
||||
}, [explicitDraftScope, layoutId, nodeId, projectId]);
|
||||
const currentConversationCacheKey = useMemo(
|
||||
() => conversationCacheKey(projectId, agentId, conversationId),
|
||||
@ -1173,7 +1179,7 @@ export function CustomAgentChatView({
|
||||
options.conversationId === undefined
|
||||
? conversationId ?? undefined
|
||||
: options.conversationId ?? undefined,
|
||||
nodeId,
|
||||
...(nodeId ? { nodeId } : {}),
|
||||
};
|
||||
console.debug("[ticket149] recoverStructuredSession:launchAgentChat:start", {
|
||||
timestamp: new Date().toISOString(),
|
||||
@ -1365,7 +1371,7 @@ export function CustomAgentChatView({
|
||||
sessionId: externalSessionId,
|
||||
error: instrumentationError(e),
|
||||
});
|
||||
if (!cancelled) setError(describe(e));
|
||||
if (!cancelled) setError(describeOpenFailure(e, mobile));
|
||||
} finally {
|
||||
if (!cancelled) setOpening(false);
|
||||
}
|
||||
@ -1384,6 +1390,7 @@ export function CustomAgentChatView({
|
||||
applyChatViewState,
|
||||
currentConversationCacheKey,
|
||||
conversationId,
|
||||
mobile,
|
||||
]);
|
||||
|
||||
const canSend = useMemo(
|
||||
@ -1822,7 +1829,7 @@ export function CustomAgentChatView({
|
||||
<div
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby={`profile-command-title-${nodeId}`}
|
||||
aria-labelledby={`profile-command-title-${domId}`}
|
||||
className={cn(
|
||||
"border border-border bg-surface p-3 shadow-lg",
|
||||
mobile
|
||||
@ -1833,7 +1840,7 @@ export function CustomAgentChatView({
|
||||
<div className="flex min-w-0 items-start justify-between gap-3">
|
||||
<div className="min-w-0">
|
||||
<h2
|
||||
id={`profile-command-title-${nodeId}`}
|
||||
id={`profile-command-title-${domId}`}
|
||||
className="text-sm font-semibold text-content"
|
||||
>
|
||||
Changer de profil
|
||||
@ -1979,7 +1986,7 @@ export function CustomAgentChatView({
|
||||
)}
|
||||
{slashMenuOpen && slashCommands.length > 0 && (
|
||||
<div
|
||||
id={`slash-command-menu-${nodeId}`}
|
||||
id={`slash-command-menu-${domId}`}
|
||||
role="listbox"
|
||||
aria-label="suggestions commandes slash"
|
||||
className="max-h-48 overflow-y-auto rounded-md border border-border bg-surface py-1 shadow-lg"
|
||||
@ -1991,7 +1998,7 @@ export function CustomAgentChatView({
|
||||
<button
|
||||
key={command.name}
|
||||
type="button"
|
||||
id={`slash-command-${nodeId}-${index}`}
|
||||
id={`slash-command-${domId}-${index}`}
|
||||
role="option"
|
||||
aria-selected={selected}
|
||||
aria-disabled={!available}
|
||||
@ -2019,10 +2026,10 @@ export function CustomAgentChatView({
|
||||
<div className="relative flex min-w-0 flex-1">
|
||||
<textarea
|
||||
ref={composerRef}
|
||||
aria-label={`message CLI custom ${nodeId}`}
|
||||
aria-controls={slashMenuOpen ? `slash-command-menu-${nodeId}` : undefined}
|
||||
aria-label={`message CLI custom ${domId}`}
|
||||
aria-controls={slashMenuOpen ? `slash-command-menu-${domId}` : undefined}
|
||||
aria-activedescendant={
|
||||
slashMenuOpen ? `slash-command-${nodeId}-${slashActiveIndex}` : undefined
|
||||
slashMenuOpen ? `slash-command-${domId}-${slashActiveIndex}` : undefined
|
||||
}
|
||||
className={cn(
|
||||
"min-h-10 flex-1 resize-none rounded-md border border-border bg-surface p-2 pr-7 text-sm text-content outline-none",
|
||||
@ -2069,7 +2076,7 @@ export function CustomAgentChatView({
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
aria-label={`redimensionner le composer ${nodeId}`}
|
||||
aria-label={`redimensionner le composer ${domId}`}
|
||||
title="Redimensionner le composer"
|
||||
className="absolute right-1.5 top-1.5 h-4 w-4 rounded-sm border border-border bg-raised text-[9px] leading-3 text-muted hover:text-content focus:border-primary focus:outline-none"
|
||||
style={{ cursor: "ns-resize" }}
|
||||
|
||||
@ -113,10 +113,26 @@ describe("WebAgentCell", () => {
|
||||
|
||||
expect(await screen.findByTestId("custom-agent-chat-view")).toBeTruthy();
|
||||
expect(screen.queryByTestId("terminal-key-bar")).toBeNull();
|
||||
await waitFor(() => expect(launchChatSpy).toHaveBeenCalledTimes(1));
|
||||
expect(launchChatSpy.mock.calls[0][2]).toMatchObject({ cwd: "/srv/demo" });
|
||||
expect(launchChatSpy.mock.calls[0][2]).not.toHaveProperty("nodeId");
|
||||
});
|
||||
|
||||
it("propagates the real leaf node id to the mobile custom CLI when provided", async () => {
|
||||
const { agent, profile, agentId } = await seededAgentGateway(structuredProfile);
|
||||
const launchChatSpy = vi.spyOn(agent, "launchAgentChat");
|
||||
const leafNodeId = "1efdd2dd-981d-431b-915c-1dd5e976fdd5";
|
||||
|
||||
renderCell(agent, profile, agentId, {
|
||||
fullHeight: true,
|
||||
nodeId: leafNodeId,
|
||||
});
|
||||
fireEvent.click(await screen.findByRole("button", { name: "CLI custom" }));
|
||||
|
||||
await waitFor(() => expect(launchChatSpy).toHaveBeenCalledTimes(1));
|
||||
expect(launchChatSpy.mock.calls[0][2]).toMatchObject({
|
||||
cwd: "/srv/demo",
|
||||
nodeId: `web-mobile-${agentId}`,
|
||||
nodeId: leafNodeId,
|
||||
});
|
||||
});
|
||||
|
||||
@ -149,10 +165,8 @@ describe("WebAgentCell", () => {
|
||||
|
||||
await waitFor(() => expect(launchChatSpy).toHaveBeenCalledTimes(1));
|
||||
await waitFor(() => expect(reattachChatSpy).toHaveBeenCalledTimes(1));
|
||||
expect(launchChatSpy.mock.calls[0][2]).toMatchObject({
|
||||
cwd: "/srv/demo",
|
||||
nodeId: `web-mobile-${agentId}`,
|
||||
});
|
||||
expect(launchChatSpy.mock.calls[0][2]).toMatchObject({ cwd: "/srv/demo" });
|
||||
expect(launchChatSpy.mock.calls[0][2]).not.toHaveProperty("nodeId");
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: "TUI native" }));
|
||||
await waitFor(() => expect(screen.queryByTestId("custom-agent-chat-view")).toBeNull());
|
||||
@ -183,11 +197,11 @@ describe("WebAgentCell", () => {
|
||||
expect(launchChatSpy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("surfaces a mobile custom CLI launch failure when the runtime cannot find node", async () => {
|
||||
it("surfaces a generic mobile custom CLI launch failure", async () => {
|
||||
const { agent, profile, agentId } = await seededAgentGateway(structuredProfile);
|
||||
const launchChatSpy = vi.spyOn(agent, "launchAgentChat").mockRejectedValueOnce({
|
||||
code: "PROCESS",
|
||||
message: "spawn node ENOENT: node is not installed on this runtime",
|
||||
message: "invalid node id: web-mobile-agent-1",
|
||||
});
|
||||
const reattachChatSpy = vi.spyOn(agent, "reattachAgentChat");
|
||||
|
||||
@ -195,12 +209,12 @@ describe("WebAgentCell", () => {
|
||||
fireEvent.click(await screen.findByRole("button", { name: "CLI custom" }));
|
||||
|
||||
await waitFor(() => expect(launchChatSpy).toHaveBeenCalledTimes(1));
|
||||
expect(launchChatSpy.mock.calls[0][2]).toMatchObject({
|
||||
cwd: "/srv/demo",
|
||||
nodeId: `web-mobile-${agentId}`,
|
||||
});
|
||||
expect(launchChatSpy.mock.calls[0][2]).toMatchObject({ cwd: "/srv/demo" });
|
||||
expect(launchChatSpy.mock.calls[0][2]).not.toHaveProperty("nodeId");
|
||||
expect(reattachChatSpy).not.toHaveBeenCalled();
|
||||
expect((await screen.findByRole("alert")).textContent).toContain("spawn node ENOENT");
|
||||
const alertText = (await screen.findByRole("alert")).textContent ?? "";
|
||||
expect(alertText).toContain("Le CLI custom n'a pas pu démarrer");
|
||||
expect(alertText).not.toContain("invalid node id");
|
||||
});
|
||||
|
||||
it("falls back to native TUI when the mobile agent loses custom CLI support", async () => {
|
||||
|
||||
@ -203,7 +203,7 @@ export function WebAgentCell({
|
||||
agentName={resolvedAgent.name}
|
||||
profile={resolvedProfile}
|
||||
cwd={cwd}
|
||||
nodeId={`web-mobile-${agentId}`}
|
||||
nodeId={nodeId}
|
||||
sessionId={customSessionId}
|
||||
conversationId={customConversationId}
|
||||
draftScope={draftScope}
|
||||
|
||||
Reference in New Issue
Block a user