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
169 lines
5.4 KiB
TypeScript
169 lines
5.4 KiB
TypeScript
import { useCallback, useEffect, useRef } from "react";
|
|
import { useOptionalAgentHostApi } from "../../../agentActivityHost";
|
|
import type { UiLanguage } from "../../../contexts/settings/domain/agentSettings";
|
|
import type { AgentGUINodeViewModel } from "../model/agentGuiNodeTypes";
|
|
import type {
|
|
AgentGUINodeViewProps,
|
|
AgentGUIViewLabels
|
|
} from "./AgentGUINodeView.types";
|
|
import { useAgentGUIConversationCopyAction } from "./AgentGUIConversationActionsMenu";
|
|
import {
|
|
AGENT_GUI_WORKBENCH_COMMAND_EVENT,
|
|
normalizeAgentGuiWorkbenchCommand
|
|
} from "../../../workbench/commands";
|
|
|
|
type Conversation = AgentGUINodeViewModel["rail"]["conversations"][number];
|
|
|
|
function resolveSessionActionConversation(
|
|
viewModel: AgentGUINodeViewModel,
|
|
agentSessionId: string | null
|
|
): Conversation | null {
|
|
const active = viewModel.rail.activeConversation;
|
|
if (!agentSessionId) {
|
|
return active;
|
|
}
|
|
if (active?.id === agentSessionId) {
|
|
return active;
|
|
}
|
|
return (
|
|
viewModel.rail.conversations.find(
|
|
(conversation) => conversation.id === agentSessionId
|
|
) ?? null
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Handles requests dispatched by the host chrome (workbench header) into the
|
|
* AgentGUI node: creating a conversation and session actions (rename, copy
|
|
* variants) targeting the conversation the header menu was rendered for.
|
|
*
|
|
* The header chrome cannot see the rail interaction lock or whether the
|
|
* target conversation is loaded, so unlike the rail menus (which disable or
|
|
* suppress actions up front) failures here surface as an error toast rather
|
|
* than a silent drop.
|
|
*
|
|
* The rail query controller owns the interaction lock and lives inside the
|
|
* rail subtree; it registers a probe through the returned callback so session
|
|
* actions honor the same lock as the rail's own menus.
|
|
*/
|
|
export function useAgentGUIExternalRequests(input: {
|
|
createConversationDisabled: boolean;
|
|
labels: Pick<
|
|
AgentGUIViewLabels,
|
|
| "conversationCopyFile"
|
|
| "conversationCopyImage"
|
|
| "conversationCopyImagesOmitted"
|
|
| "conversationCopyInProgress"
|
|
| "conversationCopyMentionPrefix"
|
|
| "conversationCopyPreviousMessages"
|
|
| "copiedToClipboard"
|
|
| "copyFailed"
|
|
| "sessionActionUnavailable"
|
|
| "untitledConversationTitle"
|
|
>;
|
|
requestCreateConversation: (options?: { source?: string }) => void;
|
|
requestRenameConversation: (conversation: Conversation) => void;
|
|
uiLanguage: UiLanguage;
|
|
viewModel: AgentGUINodeViewModel;
|
|
workbenchCommandBridge: NonNullable<
|
|
AgentGUINodeViewProps["workbenchCommandBridge"]
|
|
> | null;
|
|
}): {
|
|
registerRailInteractionLockProbe: (probe: (() => boolean) | null) => void;
|
|
} {
|
|
const {
|
|
createConversationDisabled,
|
|
labels,
|
|
requestCreateConversation,
|
|
requestRenameConversation,
|
|
uiLanguage,
|
|
viewModel,
|
|
workbenchCommandBridge
|
|
} = input;
|
|
const agentHostApi = useOptionalAgentHostApi();
|
|
const railInteractionLockProbeRef = useRef<(() => boolean) | null>(null);
|
|
const registerRailInteractionLockProbe = useCallback(
|
|
(probe: (() => boolean) | null) => {
|
|
railInteractionLockProbeRef.current = probe;
|
|
},
|
|
[]
|
|
);
|
|
const copyConversationValue = useAgentGUIConversationCopyAction(labels);
|
|
const requestContextRef = useRef({
|
|
agentHostApi,
|
|
copyConversationValue,
|
|
createConversationDisabled,
|
|
labels,
|
|
requestCreateConversation,
|
|
requestRenameConversation,
|
|
uiLanguage,
|
|
viewModel,
|
|
workbenchCommandBridge
|
|
});
|
|
requestContextRef.current = {
|
|
agentHostApi,
|
|
copyConversationValue,
|
|
createConversationDisabled,
|
|
labels,
|
|
requestCreateConversation,
|
|
requestRenameConversation,
|
|
uiLanguage,
|
|
viewModel,
|
|
workbenchCommandBridge
|
|
};
|
|
useEffect(() => {
|
|
if (!workbenchCommandBridge) {
|
|
return;
|
|
}
|
|
const instanceId = workbenchCommandBridge.instanceId;
|
|
const handleCommand = (event: Event): void => {
|
|
const command = normalizeAgentGuiWorkbenchCommand(
|
|
(event as CustomEvent<unknown>).detail
|
|
);
|
|
if (!command || command.instanceId !== instanceId) {
|
|
return;
|
|
}
|
|
const current = requestContextRef.current;
|
|
if (command.type === "conversation-rail-toggle") {
|
|
current.workbenchCommandBridge?.onConversationRailToggle?.(
|
|
command.conversationRailCollapsed
|
|
);
|
|
return;
|
|
}
|
|
if (command.type === "new-conversation") {
|
|
if (!current.createConversationDisabled) {
|
|
current.requestCreateConversation({ source: "external_request" });
|
|
}
|
|
return;
|
|
}
|
|
const conversation = resolveSessionActionConversation(
|
|
current.viewModel,
|
|
command.agentSessionId
|
|
);
|
|
const railInteractionLocked =
|
|
railInteractionLockProbeRef.current?.() ?? false;
|
|
if (!conversation || railInteractionLocked) {
|
|
current.agentHostApi?.toast?.error(
|
|
current.labels.sessionActionUnavailable
|
|
);
|
|
} else if (command.action === "rename") {
|
|
current.requestRenameConversation(conversation);
|
|
} else {
|
|
current.copyConversationValue(command.action, {
|
|
conversation,
|
|
uiLanguage: current.uiLanguage,
|
|
workspaceId: current.viewModel.shell.workspaceId
|
|
});
|
|
}
|
|
};
|
|
window.addEventListener(AGENT_GUI_WORKBENCH_COMMAND_EVENT, handleCommand);
|
|
return () => {
|
|
window.removeEventListener(
|
|
AGENT_GUI_WORKBENCH_COMMAND_EVENT,
|
|
handleCommand
|
|
);
|
|
};
|
|
}, [workbenchCommandBridge?.instanceId]);
|
|
return { registerRailInteractionLockProbe };
|
|
}
|