merge(web): CLI custom indisponible via l'interface web — #229 (QA verte)
This commit is contained in:
17
.ideai/tickets/228/carnet.md
Normal file
17
.ideai/tickets/228/carnet.md
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
---
|
||||||
|
issueRef: "#228"
|
||||||
|
version: 4
|
||||||
|
updatedBy: {"kind":"agent","agent_id":"a6c6ea12-bfc6-4bdc-8031-324102dfa34d"}
|
||||||
|
updatedAt: 1787229737762
|
||||||
|
---
|
||||||
|
## 2026-08-20
|
||||||
|
- Rebuild AppImage demandé par l'utilisateur.
|
||||||
|
- Ticket créé et passé en `inProgress`.
|
||||||
|
- Décision Git préparée avant tagging : branche `develop`, HEAD `1b8534f`, tag à créer après build vert `v0.18.0`.
|
||||||
|
- Frontend build exécuté avec succès.
|
||||||
|
- Bundle Tauri AppImage exécuté avec succès via `CARGO_HOME=/tmp/idea-cargo-home APPIMAGE_EXTRACT_AND_RUN=1 NO_STRIP=1`.
|
||||||
|
- Artefact produit : `/home/anthony/Documents/Projects/IdeA/target/release/bundle/appimage/IdeA_0.3.0_amd64.AppImage`.
|
||||||
|
- Vérification artefact : taille `118745592` bytes, horodatage `2026-08-20 14:41:34 +0200`.
|
||||||
|
- Git a créé localement le tag annoté `v0.18.0` sur le commit `1b8534f` puis l'a vérifié avec `git tag --contains` et `git describe --tags`.
|
||||||
|
- Aucun push effectué.
|
||||||
|
- Hors périmètre volontaire : remplacement manuel de l'AppImage installée et relance d'IdeA.
|
||||||
17
.ideai/tickets/228/issue.md
Normal file
17
.ideai/tickets/228/issue.md
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
---
|
||||||
|
id: "ce5b03a6-deaa-4b4d-9eff-49bff8e6a79c"
|
||||||
|
number: 228
|
||||||
|
title: "Rebuild AppImage Linux"
|
||||||
|
status: "closed"
|
||||||
|
priority: "medium"
|
||||||
|
sprint: null
|
||||||
|
links: []
|
||||||
|
agentRefs: []
|
||||||
|
attachments: []
|
||||||
|
createdBy: {"kind":"agent","agent_id":"a6c6ea12-bfc6-4bdc-8031-324102dfa34d"}
|
||||||
|
updatedBy: {"kind":"agent","agent_id":"a6c6ea12-bfc6-4bdc-8031-324102dfa34d"}
|
||||||
|
createdAt: 1787229516097
|
||||||
|
updatedAt: 1787229737762
|
||||||
|
version: 4
|
||||||
|
---
|
||||||
|
Reconstruction de l'AppImage IdeA depuis le project root, puis tag de version du commit construit selon la règle Main (vx sur main, vy sur develop, vz sur autres branches).
|
||||||
13
.ideai/tickets/229/carnet.md
Normal file
13
.ideai/tickets/229/carnet.md
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
---
|
||||||
|
issueRef: "#229"
|
||||||
|
version: 4
|
||||||
|
updatedBy: {"kind":"agent","agent_id":"1ff94b51-3e17-4a39-8543-7715d1ff0f80"}
|
||||||
|
updatedAt: 1787231430402
|
||||||
|
---
|
||||||
|
2026-08-20
|
||||||
|
- Symptôme utilisateur: via l'interface web, le profil CLI custom affiche "Le CLI custom n'a pas pu démarrer. Vérifiez la configuration du profil ou utilisez la TUI native".
|
||||||
|
- Ticket créé car aucun ticket ouvert ne correspondait.
|
||||||
|
- UX: ne pas masquer l'erreur réelle sur web/mobile; afficher un échec d'ouverture avec détail exploitable, action Réessayer, fallback TUI secondaire.
|
||||||
|
- Architect: root cause probable = contrat cassé côté web-server. Le frontend web lance `cellKind: chat`, mais le web-server rejette encore les sessions structurées avec `UNSUPPORTED`; le message générique frontend masque la vraie cause.
|
||||||
|
- Git: branche locale décidée `fix/229-web-custom-cli-startup`, basée sur `develop` (tip `1b8534f`), créée sans checkout.
|
||||||
|
- À implémenter: backend web-server pour honorer `agent.launch` structuré sur web + frontend pour exposer le diagnostic utile au lieu du message générique.
|
||||||
17
.ideai/tickets/229/issue.md
Normal file
17
.ideai/tickets/229/issue.md
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
---
|
||||||
|
id: "a993f30b-b128-4b26-94ba-6301279c1bd4"
|
||||||
|
number: 229
|
||||||
|
title: "CLI custom indisponible via l'interface web"
|
||||||
|
status: "closed"
|
||||||
|
priority: "high"
|
||||||
|
sprint: null
|
||||||
|
links: []
|
||||||
|
agentRefs: []
|
||||||
|
attachments: []
|
||||||
|
createdBy: {"kind":"agent","agent_id":"a6c6ea12-bfc6-4bdc-8031-324102dfa34d"}
|
||||||
|
updatedBy: {"kind":"agent","agent_id":"1ff94b51-3e17-4a39-8543-7715d1ff0f80"}
|
||||||
|
createdAt: 1787230509856
|
||||||
|
updatedAt: 1787231430402
|
||||||
|
version: 4
|
||||||
|
---
|
||||||
|
Le profil CLI custom ne demarre pas depuis l'interface web et affiche: "le cli custom n'a pas pu demarrer. Verifiez la configuration du profil ou utilisez la TUI native". A diagnostiquer et corriger selon le cycle UX/Architect/Dev/QA.
|
||||||
@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"nextNumber": 228
|
"nextNumber": 230
|
||||||
}
|
}
|
||||||
@ -3056,6 +3056,34 @@
|
|||||||
"agent_id": "a6c6ea12-bfc6-4bdc-8031-324102dfa34d"
|
"agent_id": "a6c6ea12-bfc6-4bdc-8031-324102dfa34d"
|
||||||
},
|
},
|
||||||
"updatedAt": 1787227664912
|
"updatedAt": 1787227664912
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"issueRef": "#228",
|
||||||
|
"path": "228",
|
||||||
|
"title": "Rebuild AppImage Linux",
|
||||||
|
"status": "closed",
|
||||||
|
"priority": "medium",
|
||||||
|
"sprint": null,
|
||||||
|
"assignedAgentIds": [],
|
||||||
|
"createdBy": {
|
||||||
|
"kind": "agent",
|
||||||
|
"agent_id": "a6c6ea12-bfc6-4bdc-8031-324102dfa34d"
|
||||||
|
},
|
||||||
|
"updatedAt": 1787229737762
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"issueRef": "#229",
|
||||||
|
"path": "229",
|
||||||
|
"title": "CLI custom indisponible via l'interface web",
|
||||||
|
"status": "closed",
|
||||||
|
"priority": "high",
|
||||||
|
"sprint": null,
|
||||||
|
"assignedAgentIds": [],
|
||||||
|
"createdBy": {
|
||||||
|
"kind": "agent",
|
||||||
|
"agent_id": "a6c6ea12-bfc6-4bdc-8031-324102dfa34d"
|
||||||
|
},
|
||||||
|
"updatedAt": 1787231430402
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -1559,11 +1559,19 @@ async fn send_launch_agent_attached(
|
|||||||
owned: &Arc<Mutex<Vec<(SessionId, u64)>>>,
|
owned: &Arc<Mutex<Vec<(SessionId, u64)>>>,
|
||||||
output: application::LaunchAgentOutput,
|
output: application::LaunchAgentOutput,
|
||||||
) -> Result<(), ErrorDto> {
|
) -> Result<(), ErrorDto> {
|
||||||
if output.structured.is_some() {
|
if let Some(structured) = output.structured.as_ref() {
|
||||||
return Err(ErrorDto {
|
tx.send(ServerFrame::chat_attached(
|
||||||
code: "UNSUPPORTED".to_owned(),
|
&frame.id,
|
||||||
message: "structured agent sessions do not stream over the PTY websocket".to_owned(),
|
structured,
|
||||||
});
|
output.assigned_conversation_id.as_deref(),
|
||||||
|
output.engine_session_id.as_deref(),
|
||||||
|
))
|
||||||
|
.await
|
||||||
|
.map_err(|_| ErrorDto {
|
||||||
|
code: "WS_CLOSED".to_owned(),
|
||||||
|
message: "websocket output closed".to_owned(),
|
||||||
|
})?;
|
||||||
|
return Ok(());
|
||||||
}
|
}
|
||||||
let session_id = output.session.id;
|
let session_id = output.session.id;
|
||||||
let dto = TerminalSessionDto::from(output);
|
let dto = TerminalSessionDto::from(output);
|
||||||
@ -5430,6 +5438,28 @@ impl ServerFrame {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn chat_attached(
|
||||||
|
request_id: &str,
|
||||||
|
session: &application::StructuredSessionDescriptor,
|
||||||
|
assigned_conversation_id: Option<&str>,
|
||||||
|
engine_session_id: Option<&str>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
kind: "chat.attached".to_owned(),
|
||||||
|
reply_to: Some(request_id.to_owned()),
|
||||||
|
payload: json!({
|
||||||
|
"sessionId": session.session_id.to_string(),
|
||||||
|
"session": {
|
||||||
|
"sessionId": session.session_id.to_string(),
|
||||||
|
"nodeId": session.node_id.to_string(),
|
||||||
|
},
|
||||||
|
"cellKind": "chat",
|
||||||
|
"assignedConversationId": assigned_conversation_id,
|
||||||
|
"engineSessionId": engine_session_id,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn output(session_id: SessionId, seq: u64, bytes: Vec<u8>) -> Self {
|
fn output(session_id: SessionId, seq: u64, bytes: Vec<u8>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
kind: "terminal.output".to_owned(),
|
kind: "terminal.output".to_owned(),
|
||||||
@ -7043,7 +7073,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn websocket_launch_agent_structured_is_unsupported() {
|
async fn websocket_launch_agent_structured_emits_chat_attached() {
|
||||||
let state = state();
|
let state = state();
|
||||||
let (tx, mut rx) = mpsc::channel(4);
|
let (tx, mut rx) = mpsc::channel(4);
|
||||||
let owned = Arc::new(Mutex::new(Vec::new()));
|
let owned = Arc::new(Mutex::new(Vec::new()));
|
||||||
@ -7073,25 +7103,25 @@ mod tests {
|
|||||||
profile: None,
|
profile: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Err(err) = send_launch_agent_attached(&frame, &state, &tx, &owned, output).await {
|
send_launch_agent_attached(&frame, &state, &tx, &owned, output)
|
||||||
tx.send(ServerFrame::error(
|
|
||||||
Some(frame.id),
|
|
||||||
payload_session_id(&frame.payload),
|
|
||||||
err.code,
|
|
||||||
err.message,
|
|
||||||
))
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
let attached = recv_server_frame(&mut rx).await;
|
||||||
let error = recv_server_frame(&mut rx).await;
|
|
||||||
|
|
||||||
assert_eq!(error.kind, "error");
|
assert_eq!(attached.kind, "chat.attached");
|
||||||
assert_eq!(error.reply_to.as_deref(), Some("agent-structured"));
|
assert_eq!(attached.reply_to.as_deref(), Some("agent-structured"));
|
||||||
assert_eq!(error.payload["code"], "UNSUPPORTED");
|
assert_eq!(attached.payload["sessionId"], session_id.to_string());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
error.payload["message"],
|
attached.payload["session"]["sessionId"],
|
||||||
"structured agent sessions do not stream over the PTY websocket"
|
session_id.to_string()
|
||||||
);
|
);
|
||||||
|
assert_eq!(attached.payload["session"]["nodeId"], node_id.to_string());
|
||||||
|
assert_eq!(attached.payload["cellKind"], "chat");
|
||||||
|
assert_eq!(
|
||||||
|
attached.payload["assignedConversationId"],
|
||||||
|
"pair-conversation"
|
||||||
|
);
|
||||||
|
assert_eq!(attached.payload["engineSessionId"], "engine-session");
|
||||||
assert_eq!(state.ws_pty_bridge.active_sessions(), 0);
|
assert_eq!(state.ws_pty_bridge.active_sessions(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -89,6 +89,98 @@ describe("CustomAgentChatView", () => {
|
|||||||
__resetProjectCliInteractionActivityForTests();
|
__resetProjectCliInteractionActivityForTests();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("shows the technical open failure on mobile with retry and native TUI fallback", async () => {
|
||||||
|
const openNativeTui = vi.fn();
|
||||||
|
const agent = {
|
||||||
|
launchAgentChat: vi
|
||||||
|
.fn()
|
||||||
|
.mockRejectedValueOnce(new Error("spawn codex ENOENT"))
|
||||||
|
.mockResolvedValueOnce({ sessionId: "chat-session-1" }),
|
||||||
|
reattachAgentChat: vi.fn(async (sessionId: string) => ({
|
||||||
|
sessionId,
|
||||||
|
scrollback: [],
|
||||||
|
})),
|
||||||
|
sendAgentChat: vi.fn(async () => {}),
|
||||||
|
cancelAgentChat: vi.fn(async () => {}),
|
||||||
|
closeAgentChat: vi.fn(async () => {}),
|
||||||
|
};
|
||||||
|
|
||||||
|
render(
|
||||||
|
<DIProvider
|
||||||
|
gateways={{
|
||||||
|
agent,
|
||||||
|
system: { pickFile: vi.fn(async () => null) },
|
||||||
|
} as unknown as Gateways}
|
||||||
|
>
|
||||||
|
<CustomAgentChatView
|
||||||
|
projectId="project-1"
|
||||||
|
agentId="agent-1"
|
||||||
|
agentName="Worker"
|
||||||
|
profile={profile}
|
||||||
|
cwd="/repo"
|
||||||
|
nodeId="node-1"
|
||||||
|
sessionId={null}
|
||||||
|
conversationId={null}
|
||||||
|
mobile
|
||||||
|
onOpenNativeTui={openNativeTui}
|
||||||
|
onSessionId={vi.fn()}
|
||||||
|
onConversationId={vi.fn()}
|
||||||
|
/>
|
||||||
|
</DIProvider>,
|
||||||
|
);
|
||||||
|
|
||||||
|
const alert = await screen.findByRole("alert");
|
||||||
|
expect(within(alert).getByText("Le CLI custom n'a pas pu démarrer.")).toBeTruthy();
|
||||||
|
expect(within(alert).getByText("Cause: spawn codex ENOENT")).toBeTruthy();
|
||||||
|
expect(screen.queryByText(/Vérifiez la configuration du profil/)).toBeNull();
|
||||||
|
|
||||||
|
fireEvent.click(within(alert).getByRole("button", { name: "Ouvrir en TUI native" }));
|
||||||
|
expect(openNativeTui).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
fireEvent.click(within(alert).getByRole("button", { name: "Réessayer" }));
|
||||||
|
await waitFor(() => expect(agent.launchAgentChat).toHaveBeenCalledTimes(2));
|
||||||
|
await waitFor(() => expect(screen.queryByRole("alert")).toBeNull());
|
||||||
|
});
|
||||||
|
|
||||||
|
it("uses the same technical open failure on desktop", async () => {
|
||||||
|
const agent = {
|
||||||
|
launchAgentChat: vi.fn(async () => {
|
||||||
|
throw new Error("permission denied: /opt/codex");
|
||||||
|
}),
|
||||||
|
reattachAgentChat: vi.fn(),
|
||||||
|
sendAgentChat: vi.fn(async () => {}),
|
||||||
|
cancelAgentChat: vi.fn(async () => {}),
|
||||||
|
closeAgentChat: vi.fn(async () => {}),
|
||||||
|
};
|
||||||
|
|
||||||
|
render(
|
||||||
|
<DIProvider
|
||||||
|
gateways={{
|
||||||
|
agent,
|
||||||
|
system: { pickFile: vi.fn(async () => null) },
|
||||||
|
} as unknown as Gateways}
|
||||||
|
>
|
||||||
|
<CustomAgentChatView
|
||||||
|
projectId="project-1"
|
||||||
|
agentId="agent-1"
|
||||||
|
agentName="Worker"
|
||||||
|
profile={profile}
|
||||||
|
cwd="/repo"
|
||||||
|
nodeId="node-1"
|
||||||
|
sessionId={null}
|
||||||
|
conversationId={null}
|
||||||
|
onSessionId={vi.fn()}
|
||||||
|
onConversationId={vi.fn()}
|
||||||
|
/>
|
||||||
|
</DIProvider>,
|
||||||
|
);
|
||||||
|
|
||||||
|
const alert = await screen.findByRole("alert");
|
||||||
|
expect(within(alert).getByText("Cause: permission denied: /opt/codex")).toBeTruthy();
|
||||||
|
expect(screen.getByRole("button", { name: "Réessayer" })).toBeTruthy();
|
||||||
|
expect(screen.queryByRole("button", { name: "Ouvrir en TUI native" })).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
it("repaints attachment labels from user prompt scrollback", async () => {
|
it("repaints attachment labels from user prompt scrollback", async () => {
|
||||||
const agent = {
|
const agent = {
|
||||||
launchAgentChat: vi.fn(),
|
launchAgentChat: vi.fn(),
|
||||||
|
|||||||
@ -52,6 +52,7 @@ export interface CustomAgentChatViewProps {
|
|||||||
onComposerHeightPxChange?: (heightPx: number) => void;
|
onComposerHeightPxChange?: (heightPx: number) => void;
|
||||||
draftScope?: ChatDraftScope | null;
|
draftScope?: ChatDraftScope | null;
|
||||||
mobile?: boolean;
|
mobile?: boolean;
|
||||||
|
onOpenNativeTui?: () => void;
|
||||||
onSessionId: (sessionId: string | null) => void;
|
onSessionId: (sessionId: string | null) => void;
|
||||||
onConversationId: (conversationId: string | null) => void;
|
onConversationId: (conversationId: string | null) => void;
|
||||||
}
|
}
|
||||||
@ -118,6 +119,11 @@ interface SlashCommandInvocation {
|
|||||||
arguments: unknown[];
|
arguments: unknown[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ChatErrorState {
|
||||||
|
message: string;
|
||||||
|
openFailure: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
function describe(e: unknown): string {
|
function describe(e: unknown): string {
|
||||||
if (e && typeof e === "object" && "message" in e) {
|
if (e && typeof e === "object" && "message" in e) {
|
||||||
return String((e as GatewayError).message);
|
return String((e as GatewayError).message);
|
||||||
@ -125,9 +131,9 @@ function describe(e: unknown): string {
|
|||||||
return String(e);
|
return String(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
function describeOpenFailure(e: unknown, mobile: boolean): string {
|
function describeOpenFailure(e: unknown): string {
|
||||||
if (!mobile) return describe(e);
|
const message = describe(e).trim();
|
||||||
return "Le CLI custom n'a pas pu démarrer. Vérifiez la configuration du profil ou utilisez la TUI native.";
|
return message || "Cause technique non disponible.";
|
||||||
}
|
}
|
||||||
|
|
||||||
function instrumentationError(e: unknown): Record<string, unknown> {
|
function instrumentationError(e: unknown): Record<string, unknown> {
|
||||||
@ -734,6 +740,7 @@ export function CustomAgentChatView({
|
|||||||
onComposerHeightPxChange,
|
onComposerHeightPxChange,
|
||||||
draftScope: explicitDraftScope,
|
draftScope: explicitDraftScope,
|
||||||
mobile = false,
|
mobile = false,
|
||||||
|
onOpenNativeTui,
|
||||||
onSessionId,
|
onSessionId,
|
||||||
onConversationId,
|
onConversationId,
|
||||||
}: CustomAgentChatViewProps) {
|
}: CustomAgentChatViewProps) {
|
||||||
@ -770,9 +777,10 @@ export function CustomAgentChatView({
|
|||||||
const [profileDialog, setProfileDialog] =
|
const [profileDialog, setProfileDialog] =
|
||||||
useState<ProfileCommandDialogState | null>(null);
|
useState<ProfileCommandDialogState | null>(null);
|
||||||
const [opening, setOpening] = useState(Boolean(sessionId || conversationId));
|
const [opening, setOpening] = useState(Boolean(sessionId || conversationId));
|
||||||
|
const [openRetryNonce, setOpenRetryNonce] = useState(0);
|
||||||
const [busy, setBusy] = useState(false);
|
const [busy, setBusy] = useState(false);
|
||||||
const [stopping, setStopping] = useState(false);
|
const [stopping, setStopping] = useState(false);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<ChatErrorState | null>(null);
|
||||||
const scrollRef = useRef<HTMLDivElement | null>(null);
|
const scrollRef = useRef<HTMLDivElement | null>(null);
|
||||||
const composerRef = useRef<HTMLTextAreaElement | null>(null);
|
const composerRef = useRef<HTMLTextAreaElement | null>(null);
|
||||||
const chatRootRef = useRef<HTMLDivElement | null>(null);
|
const chatRootRef = useRef<HTMLDivElement | null>(null);
|
||||||
@ -1371,7 +1379,9 @@ export function CustomAgentChatView({
|
|||||||
sessionId: externalSessionId,
|
sessionId: externalSessionId,
|
||||||
error: instrumentationError(e),
|
error: instrumentationError(e),
|
||||||
});
|
});
|
||||||
if (!cancelled) setError(describeOpenFailure(e, mobile));
|
if (!cancelled) {
|
||||||
|
setError({ message: describeOpenFailure(e), openFailure: true });
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
if (!cancelled) setOpening(false);
|
if (!cancelled) setOpening(false);
|
||||||
}
|
}
|
||||||
@ -1390,7 +1400,7 @@ export function CustomAgentChatView({
|
|||||||
applyChatViewState,
|
applyChatViewState,
|
||||||
currentConversationCacheKey,
|
currentConversationCacheKey,
|
||||||
conversationId,
|
conversationId,
|
||||||
mobile,
|
openRetryNonce,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const canSend = useMemo(
|
const canSend = useMemo(
|
||||||
@ -1490,7 +1500,10 @@ export function CustomAgentChatView({
|
|||||||
|
|
||||||
async function executeSlashCommand(invocation: SlashCommandInvocation) {
|
async function executeSlashCommand(invocation: SlashCommandInvocation) {
|
||||||
if (!agent.executeSlashCommand) {
|
if (!agent.executeSlashCommand) {
|
||||||
setError(`Commande ${invocation.name} indisponible dans ce runtime.`);
|
setError({
|
||||||
|
message: `Commande ${invocation.name} indisponible dans ce runtime.`,
|
||||||
|
openFailure: false,
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setDraft("");
|
setDraft("");
|
||||||
@ -1546,7 +1559,7 @@ export function CustomAgentChatView({
|
|||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const message = `Commande ${invocation.name} échouée: ${describe(e)}`;
|
const message = `Commande ${invocation.name} échouée: ${describe(e)}`;
|
||||||
setError(message);
|
setError({ message, openFailure: false });
|
||||||
updateChatViewState((prev) => ({
|
updateChatViewState((prev) => ({
|
||||||
turns: [...prev.turns, { role: "error", text: message, receivedAtMs: Date.now() }],
|
turns: [...prev.turns, { role: "error", text: message, receivedAtMs: Date.now() }],
|
||||||
progressIndex: prev.progressIndex,
|
progressIndex: prev.progressIndex,
|
||||||
@ -1633,7 +1646,7 @@ export function CustomAgentChatView({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setError(describe(e));
|
setError({ message: describe(e), openFailure: false });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1704,7 +1717,7 @@ export function CustomAgentChatView({
|
|||||||
} catch (recoveryError) {
|
} catch (recoveryError) {
|
||||||
finishInteraction();
|
finishInteraction();
|
||||||
setBusy(false);
|
setBusy(false);
|
||||||
setError(describe(recoveryError));
|
setError({ message: describe(recoveryError), openFailure: false });
|
||||||
updateChatViewState((prev) => ({
|
updateChatViewState((prev) => ({
|
||||||
turns: [
|
turns: [
|
||||||
...prev.turns,
|
...prev.turns,
|
||||||
@ -1717,7 +1730,7 @@ export function CustomAgentChatView({
|
|||||||
}
|
}
|
||||||
finishInteraction();
|
finishInteraction();
|
||||||
setBusy(false);
|
setBusy(false);
|
||||||
setError(describe(e));
|
setError({ message: describe(e), openFailure: false });
|
||||||
updateChatViewState((prev) => ({
|
updateChatViewState((prev) => ({
|
||||||
turns: [...prev.turns, { role: "error", text: describe(e), receivedAtMs: Date.now() }],
|
turns: [...prev.turns, { role: "error", text: describe(e), receivedAtMs: Date.now() }],
|
||||||
progressIndex: prev.progressIndex,
|
progressIndex: prev.progressIndex,
|
||||||
@ -1779,7 +1792,7 @@ export function CustomAgentChatView({
|
|||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setError(describe(e));
|
setError({ message: describe(e), openFailure: false });
|
||||||
updateChatViewState((prev) => ({
|
updateChatViewState((prev) => ({
|
||||||
turns: [
|
turns: [
|
||||||
...prev.turns,
|
...prev.turns,
|
||||||
@ -1821,9 +1834,40 @@ export function CustomAgentChatView({
|
|||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
{error && (
|
{error && (
|
||||||
<p role="alert" className="m-3 rounded-md border border-danger/40 bg-danger/10 p-2 text-xs text-danger">
|
<div
|
||||||
{error}
|
role="alert"
|
||||||
</p>
|
className="m-3 rounded-md border border-danger/40 bg-danger/10 p-2 text-xs text-danger"
|
||||||
|
>
|
||||||
|
{error.openFailure ? (
|
||||||
|
<div className="flex min-w-0 flex-col gap-2">
|
||||||
|
<div className="font-medium">Le CLI custom n'a pas pu démarrer.</div>
|
||||||
|
<div className="break-words">
|
||||||
|
Cause: {error.message}
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="primary"
|
||||||
|
disabled={opening}
|
||||||
|
onClick={() => setOpenRetryNonce((value) => value + 1)}
|
||||||
|
>
|
||||||
|
Réessayer
|
||||||
|
</Button>
|
||||||
|
{onOpenNativeTui && (
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="secondary"
|
||||||
|
onClick={onOpenNativeTui}
|
||||||
|
>
|
||||||
|
Ouvrir en TUI native
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
error.message
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
{profileDialog && (
|
{profileDialog && (
|
||||||
<div
|
<div
|
||||||
|
|||||||
@ -1501,6 +1501,7 @@ function LeafView({
|
|||||||
composerHeightPx={customChatComposerHeight}
|
composerHeightPx={customChatComposerHeight}
|
||||||
composerDefaultHeightPx={CUSTOM_CHAT_COMPOSER_DEFAULT_HEIGHT}
|
composerDefaultHeightPx={CUSTOM_CHAT_COMPOSER_DEFAULT_HEIGHT}
|
||||||
onComposerHeightPxChange={setCustomChatComposerHeight}
|
onComposerHeightPxChange={setCustomChatComposerHeight}
|
||||||
|
onOpenNativeTui={() => requestMode("tui")}
|
||||||
onSessionId={(sid) => void setSessionWithInstrumentation("custom", sid)}
|
onSessionId={(sid) => void setSessionWithInstrumentation("custom", sid)}
|
||||||
onConversationId={(cid) => void vm.setCellConversation(id, cid)}
|
onConversationId={(cid) => void vm.setCellConversation(id, cid)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -208,6 +208,7 @@ export function WebAgentCell({
|
|||||||
conversationId={customConversationId}
|
conversationId={customConversationId}
|
||||||
draftScope={draftScope}
|
draftScope={draftScope}
|
||||||
mobile
|
mobile
|
||||||
|
onOpenNativeTui={() => setMode("tui")}
|
||||||
onSessionId={setCustomSessionId}
|
onSessionId={setCustomSessionId}
|
||||||
onConversationId={setCustomConversationId}
|
onConversationId={setCustomConversationId}
|
||||||
/>
|
/>
|
||||||
|
|||||||
Reference in New Issue
Block a user