feat(mcp,orchestrator,frontend): tool idea_restart_self, redemarrage autonome d'un agent — #288 (tool core self-only via requester MCP handshake, retour immediat {restartId, status: scheduled} puis orchestration detachee: snapshot reprise, stop PTY/structured, liberation Busy/mailbox, relance LaunchAgent meme conversation_id fresh_start=false, injection interne invisible; events domain/wire agentRestartRequested/PhaseChanged/Completed/Failed phases stopping/releasingInput/launching/resuming; CLI custom: separateurs exacts redemarrage de l'agent + etat termine/echoue, deduplication (restartId, event), verrouillage composer; TUI native: overlay AgentRestartOverlay sans injection xterm, progress/annonces projetes jusqu'a idle; QA verte: mcp_server restart_self 1/1, restart-self 3/3, backend self_restart_contract + events wire 2/2, frontend 3 suites 92/92, npm build, cargo check --workspace, fmt --check, diff --check; reserves hors perimetre: web-server 103/110 fixture PTY preexistante, 34 echecs npm test globaux listProjectSettings/web hors #288; scripts/ hors perimetre)
This commit is contained in:
@ -31,7 +31,11 @@ import type {
|
||||
SlashCommand,
|
||||
} from "@/domain";
|
||||
import { useGateways } from "@/app/di";
|
||||
import { AnnouncementsPreview } from "@/features/announcements";
|
||||
import {
|
||||
AnnouncementsPreview,
|
||||
shortRestartFailure,
|
||||
useAgentRestart,
|
||||
} from "@/features/announcements";
|
||||
import { usePluginRuntime } from "@/features/plugins/PluginRuntimeProvider";
|
||||
import {
|
||||
markProjectCliInteractionFinished,
|
||||
@ -68,6 +72,7 @@ type ChatTurn =
|
||||
| { role: "progress"; progress: ReplyProgress; settled?: boolean; progressKey?: string }
|
||||
| { role: "final"; text: string; receivedAtMs?: number }
|
||||
| { role: "error"; text: string; receivedAtMs?: number }
|
||||
| { role: "restart"; key: string; text: string; message?: string; failed?: boolean }
|
||||
| { role: "unknown"; text: string; receivedAtMs?: number };
|
||||
|
||||
interface CachedChatView {
|
||||
@ -1127,9 +1132,14 @@ export function CustomAgentChatView({
|
||||
currentAgentBusy: false,
|
||||
delegationTargetName: null,
|
||||
});
|
||||
const { restart, events: restartEvents } = useAgentRestart(agentId);
|
||||
const delegationActive = requesterDelegation.delegationTargetName !== null;
|
||||
const effectiveBusy =
|
||||
busy || stopping || requesterDelegation.currentAgentBusy || delegationActive;
|
||||
busy ||
|
||||
stopping ||
|
||||
Boolean(restart && restart.outcome !== "failed") ||
|
||||
requesterDelegation.currentAgentBusy ||
|
||||
delegationActive;
|
||||
const delegationFallbackText = requesterDelegation.delegationTargetName
|
||||
? `${agentName} attend ${requesterDelegation.delegationTargetName}`
|
||||
: "Délégation en cours.";
|
||||
@ -1303,6 +1313,45 @@ export function CustomAgentChatView({
|
||||
};
|
||||
}, [agentId, projectId, refreshRequesterDelegation, system]);
|
||||
|
||||
useEffect(() => {
|
||||
if (restartEvents.length === 0) return;
|
||||
updateChatViewState((previous) => {
|
||||
const nextTurns = [...previous.turns];
|
||||
let changed = false;
|
||||
for (const event of restartEvents) {
|
||||
const key = `${event.restartId}:${event.type}`;
|
||||
if (nextTurns.some((turn) => turn.role === "restart" && turn.key === key)) {
|
||||
continue;
|
||||
}
|
||||
if (event.type === "agentRestartRequested") {
|
||||
changed = true;
|
||||
nextTurns.push({
|
||||
role: "restart",
|
||||
key,
|
||||
text: "------- redemarrage de l'agent -------",
|
||||
});
|
||||
} else if (event.type === "agentRestartCompleted") {
|
||||
changed = true;
|
||||
nextTurns.push({
|
||||
role: "restart",
|
||||
key,
|
||||
text: "------- redemarrage de l'agent termine -------",
|
||||
});
|
||||
} else {
|
||||
changed = true;
|
||||
nextTurns.push({
|
||||
role: "restart",
|
||||
key,
|
||||
text: "------- redemarrage de l'agent echoue -------",
|
||||
message: shortRestartFailure(event.message),
|
||||
failed: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
return changed ? { ...previous, turns: nextTurns } : previous;
|
||||
});
|
||||
}, [restartEvents, updateChatViewState]);
|
||||
|
||||
useEffect(() => {
|
||||
mountedRef.current = true;
|
||||
return () => {
|
||||
@ -2577,6 +2626,20 @@ function ChatBubble({
|
||||
expandedProgressKeys: Set<string>;
|
||||
onToggleProgressExpanded: (key: string) => void;
|
||||
}) {
|
||||
if (turn.role === "restart") {
|
||||
return (
|
||||
<div
|
||||
data-testid="agent-restart-separator"
|
||||
className={cn(
|
||||
"flex min-w-0 flex-col items-center gap-1 py-1 text-xs",
|
||||
turn.failed ? "text-danger" : "text-muted",
|
||||
)}
|
||||
>
|
||||
<span className="whitespace-pre-wrap text-center">{turn.text}</span>
|
||||
{turn.message && <span className="max-w-[86%] text-center">{turn.message}</span>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (turn.role === "tool") {
|
||||
return (
|
||||
<div className="flex min-w-0 justify-center">
|
||||
|
||||
Reference in New Issue
Block a user