rait-09_obsidian-agent-client/src/hooks/useAgent.ts
RAIT-09 f799ad1f2e feat(acp): migrate to @agentclientprotocol/sdk 0.28.1
Bump the ACP SDK from 0.14.1 to 0.28.1 (PROTOCOL_VERSION unchanged, so a
direct jump). Adapt the existing client to the SDK's breaking changes while
keeping existing features working; new 0.28 features (elicitation, NES,
providers, document lifecycle) are out of scope.

- Renames: unstable_listSessions->listSessions, unstable_resumeSession->
  resumeSession (both stabilized); KillTerminalCommand{Request,Response}->
  KillTerminal{Request,Response} (type-only).
- SessionConfigOption became a select|boolean union. The domain type now
  carries boolean as data (UI renders/sets only select for now); the
  converter and all consumers narrow on type === "select".
- The model API (unstable_setSessionModel, SessionModelState) was removed
  from the SDK; model is now a config option. Remove the legacy model path
  (setSessionModel, the model dropdown, SessionModel(State) domain types)
  but KEEP lastUsedModels + the config-option model restore/save that back
  model persistence. Mode keeps its legacy API (still in the SDK).
- zod is now a runtime (peer) dependency of the SDK for schema validation;
  add it to dependencies so it bundles into main.js.
- Restore @eslint/json devDep (legacy-peer-deps had pruned this optional
  peer that the lint config loads).

ClientSideConnection is deprecated in 0.28 (new: client() builder, scoped
connectWith) but kept for now with documented eslint-disables; migrating
the connection lifecycle is a separate follow-up.
2026-06-21 02:50:59 +09:00

245 lines
7.6 KiB
TypeScript

/**
* Hook for managing the complete agent interaction lifecycle.
*
* This is a facade that composes useAgentSession and useAgentMessages,
* providing a unified API to ChatPanel.
*/
import * as React from "react";
const { useState, useCallback, useEffect, useMemo } = React;
import type { SessionUpdate } from "../types/session";
import type { AcpClient } from "../acp/acp-client";
import type { IVaultAccess } from "../services/vault-service";
import type { ISettingsAccess } from "../services/settings-service";
import type { ErrorInfo } from "../types/errors";
import type { IMentionService } from "../utils/mention-parser";
import { useAgentSession } from "./useAgentSession";
import { useAgentMessages, type SendMessageOptions } from "./useAgentMessages";
// Re-export types that ChatPanel uses
export type { SendMessageOptions } from "./useAgentMessages";
export type { AgentDisplayInfo } from "../services/session-helpers";
// ============================================================================
// Types
// ============================================================================
import type { ChatMessage, ActivePermission } from "../types/chat";
import type {
ChatSession,
SessionModeState,
SessionConfigOption,
} from "../types/session";
import type { AgentDisplayInfo } from "../services/session-helpers";
/**
* Return type for useAgent hook.
*/
export interface UseAgentReturn {
// Session state
session: ChatSession;
isReady: boolean;
// Message state
messages: ChatMessage[];
isSending: boolean;
lastUserMessage: string | null;
// Combined error
errorInfo: ErrorInfo | null;
// Session lifecycle
createSession: (
overrideAgentId?: string,
overrideCwd?: string,
) => Promise<void>;
restartSession: (
newAgentId?: string,
overrideCwd?: string,
) => Promise<void>;
closeSession: () => Promise<void>;
forceRestartAgent: () => Promise<void>;
cancelOperation: () => Promise<void>;
getAvailableAgents: () => AgentDisplayInfo[];
updateSessionFromLoad: (
sessionId: string,
modes?: SessionModeState,
configOptions?: SessionConfigOption[],
) => Promise<void>;
// Config
setMode: (modeId: string) => Promise<void>;
setConfigOption: (configId: string, value: string) => Promise<void>;
// Message operations
sendMessage: (
content: string,
options: SendMessageOptions,
) => Promise<void>;
clearMessages: () => void;
setInitialMessages: (
history: Array<{
role: string;
content: Array<{ type: string; text: string }>;
timestamp?: string;
}>,
) => void;
setMessagesFromLocal: (localMessages: ChatMessage[]) => void;
clearError: () => void;
setIgnoreUpdates: (ignore: boolean) => void;
// Permission
activePermission: ActivePermission | null;
hasActivePermission: boolean;
approvePermission: (requestId: string, optionId: string) => Promise<void>;
approveActivePermission: () => Promise<boolean>;
rejectActivePermission: () => Promise<boolean>;
}
// ============================================================================
// Hook Implementation
// ============================================================================
/**
* @param agentClient - Agent client for communication
* @param settingsAccess - Settings access for agent configuration
* @param vaultAccess - Vault access for reading notes (also serves as IMentionService)
* @param workingDirectory - Working directory for the session
* @param initialAgentId - Optional initial agent ID (from view persistence)
*/
export function useAgent(
agentClient: AcpClient,
settingsAccess: ISettingsAccess,
vaultAccess: IVaultAccess & IMentionService,
workingDirectory: string,
initialAgentId?: string,
): UseAgentReturn {
// ============================================================
// Shared Error State
// ============================================================
const [errorInfo, setErrorInfo] = useState<ErrorInfo | null>(null);
// ============================================================
// Sub-hooks
// ============================================================
const agentSession = useAgentSession(
agentClient,
settingsAccess,
workingDirectory,
setErrorInfo,
initialAgentId,
);
const agentMessages = useAgentMessages(
agentClient,
settingsAccess,
vaultAccess,
agentSession.session,
setErrorInfo,
);
// ============================================================
// Unified Session Update Handler
// ============================================================
const handleSessionUpdate = useCallback(
(update: SessionUpdate) => {
// Session-level updates (commands, mode, config, usage, error)
agentSession.handleSessionUpdate(update);
// Message-level updates (batched via RAF, ignoreUpdates checked internally)
agentMessages.enqueueUpdate(update);
},
[agentSession.handleSessionUpdate, agentMessages.enqueueUpdate],
);
// Composed cancel: session-level cancel + message-level RAF cleanup
const cancelOperation = useCallback(async () => {
await agentSession.cancelOperation();
agentMessages.clearPendingUpdates();
}, [agentSession.cancelOperation, agentMessages.clearPendingUpdates]);
// Subscribe to all updates from agent
useEffect(() => {
const unsubscribe = agentClient.onSessionUpdate(handleSessionUpdate);
return unsubscribe;
}, [agentClient, handleSessionUpdate]);
// ============================================================
// Return
// ============================================================
return useMemo(
() => ({
// Session state
session: agentSession.session,
isReady: agentSession.isReady,
// Message state
messages: agentMessages.messages,
isSending: agentMessages.isSending,
lastUserMessage: agentMessages.lastUserMessage,
// Combined error
errorInfo,
// Session lifecycle
createSession: agentSession.createSession,
restartSession: agentSession.restartSession,
closeSession: agentSession.closeSession,
forceRestartAgent: agentSession.forceRestartAgent,
cancelOperation,
getAvailableAgents: agentSession.getAvailableAgents,
updateSessionFromLoad: agentSession.updateSessionFromLoad,
// Config
setMode: agentSession.setMode,
setConfigOption: agentSession.setConfigOption,
// Message operations
sendMessage: agentMessages.sendMessage,
clearMessages: agentMessages.clearMessages,
setInitialMessages: agentMessages.setInitialMessages,
setMessagesFromLocal: agentMessages.setMessagesFromLocal,
clearError: agentMessages.clearError,
setIgnoreUpdates: agentMessages.setIgnoreUpdates,
// Permission
activePermission: agentMessages.activePermission,
hasActivePermission: agentMessages.hasActivePermission,
approvePermission: agentMessages.approvePermission,
approveActivePermission: agentMessages.approveActivePermission,
rejectActivePermission: agentMessages.rejectActivePermission,
}),
[
agentSession.session,
agentSession.isReady,
agentMessages.messages,
agentMessages.isSending,
agentMessages.lastUserMessage,
errorInfo,
agentSession.createSession,
agentSession.restartSession,
agentSession.closeSession,
agentSession.forceRestartAgent,
cancelOperation,
agentSession.getAvailableAgents,
agentSession.updateSessionFromLoad,
agentSession.setMode,
agentSession.setConfigOption,
agentMessages.sendMessage,
agentMessages.clearMessages,
agentMessages.setInitialMessages,
agentMessages.setMessagesFromLocal,
agentMessages.clearError,
agentMessages.setIgnoreUpdates,
agentMessages.activePermission,
agentMessages.hasActivePermission,
agentMessages.approvePermission,
agentMessages.approveActivePermission,
agentMessages.rejectActivePermission,
],
);
}