Files
agent-radar 2ed3415fb7
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
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:17:27 +08:00

338 lines
11 KiB
TypeScript

import { useCallback, useMemo } from "react";
import type { TranslateFn } from "../../../i18n/index";
import { useEngineSelector } from "../../../shared/engine/useEngineSelector";
import { buildDockAgentProbeTooltipLines } from "../../workspaceDesktop/view/desktopDockAgentProbeTooltipModel";
import type { AgentComposerSlashStatus } from "../AgentComposer";
import type { AgentGUINodeProps } from "../AgentGUINode.types";
import {
resolveAgentGUIRailStatusTarget,
resolveAgentGUIStatusTarget,
slashStatusLimitsFromQuotas,
slashStatusUsageErrorMessage
} from "../AgentGUINode.usage";
import type { useAgentGUINodeController } from "./useAgentGUINodeController";
import {
selectAgentStatusControllerSnapshot,
type AgentStatusControllerSnapshot,
type AgentStatusRequestReason
} from "./AgentStatusController";
type AgentGUIViewModel = ReturnType<
typeof useAgentGUINodeController
>["viewModel"];
type RuntimeRequests = AgentGUINodeProps["runtimeRequests"];
const EMPTY_STATUS_SNAPSHOT: AgentStatusControllerSnapshot = {
query: null,
value: null,
phase: "idle",
isRefreshing: false,
errorCode: null
};
const EMPTY_STATUS_STORE = {
getSnapshot: () => EMPTY_STATUS_SNAPSHOT,
subscribe: () => () => {}
};
const selectStatusSnapshot = (snapshot: AgentStatusControllerSnapshot) =>
snapshot;
/**
* Owns the three AgentGUI status entry points and their projections. Hosts
* provide one controller; target resolution, transport and probing remain
* outside the package.
*/
export function useAgentGUIStatus(input: {
activeProvider: string;
agentStatusController: RuntimeRequests["agentStatusController"];
t: TranslateFn;
viewModel: AgentGUIViewModel;
}) {
const { agentStatusController, t, viewModel } = input;
const agentStatusSnapshot = useEngineSelector(
agentStatusController ?? EMPTY_STATUS_STORE,
selectStatusSnapshot
);
const activeStatusScopeKey =
viewModel.rail.activeConversation?.agentTargetId?.trim() ||
viewModel.rail.selectedAgentTarget.agentTargetId?.trim() ||
viewModel.rail.selectedAgentTarget.targetId.trim();
// Conversation identity is available before detail/raw-state hydration and
// is the caller-visible binding for shared sessions.
const activeStatusSessionId =
viewModel.rail.activeConversationId?.trim() ||
viewModel.interaction.sessionChrome.rawState?.agentSessionId?.trim() ||
null;
const activeStatusTarget = useMemo(
() =>
resolveAgentGUIStatusTarget({
agentTargets: viewModel.rail.agentTargets,
scopeKey: activeStatusScopeKey
}),
[activeStatusScopeKey, viewModel.rail.agentTargets]
);
const railStatusTarget = useMemo(
() =>
resolveAgentGUIRailStatusTarget({
conversationFilter: viewModel.rail.conversationFilter,
agentTargets: viewModel.rail.agentTargets
}),
[viewModel.rail.conversationFilter, viewModel.rail.agentTargets]
);
const railStatusProvider = railStatusTarget?.provider ?? null;
const railStatusScopeKey =
viewModel.rail.conversationFilter.kind === "agentTarget"
? viewModel.rail.conversationFilter.agentTargetId.trim()
: activeStatusScopeKey;
const activeAgentStatusSnapshot = selectAgentStatusControllerSnapshot(
agentStatusSnapshot,
{
scopeKey: activeStatusScopeKey,
agentSessionId: activeStatusSessionId,
reasons: ["slash-status", "agent-info"]
}
);
const railAgentStatusSnapshot = selectAgentStatusControllerSnapshot(
agentStatusSnapshot,
{
scopeKey: railStatusScopeKey,
agentSessionId: null,
reasons: ["agent-config"]
}
);
const agentInfoProbe = useMemo(() => {
const value = activeAgentStatusSnapshot.value;
return {
provider: input.activeProvider,
availability: {
status: "unknown" as const,
detailsVisible: false,
checks: []
},
...(value &&
(value.limitsState === "available" || value.quotas.length > 0)
? {
usage: {
accountTier: value.accountLabel ?? undefined,
quotas: [...value.quotas],
capturedAtUnixMs: value.limitsCapturedAtUnixMs ?? 0
}
}
: {}),
...(value?.limitsState === "error"
? {
lastError: {
code: value.limitsErrorCode?.trim() || "runtime_unavailable"
}
}
: {})
};
}, [activeAgentStatusSnapshot.value, input.activeProvider]);
const agentProbeLines = useMemo(
() =>
buildDockAgentProbeTooltipLines(
agentInfoProbe,
activeAgentStatusSnapshot.phase === "loading" &&
activeAgentStatusSnapshot.value === null,
t,
{
includeUsageLines: true,
isLoadingUsage:
activeAgentStatusSnapshot.isRefreshing &&
activeAgentStatusSnapshot.value === null
}
),
[
activeAgentStatusSnapshot.isRefreshing,
activeAgentStatusSnapshot.phase,
activeAgentStatusSnapshot.value,
agentInfoProbe,
t
]
);
const requestStatus = useCallback(
(request: {
scopeKey: string;
agentSessionId?: string | null;
reason: AgentStatusRequestReason;
forceRefresh?: boolean;
}): void => {
{
agentStatusController?.open(request);
}
},
[agentStatusController]
);
const closeStatus = useCallback(
(reason: AgentStatusRequestReason): void => {
if (agentStatusSnapshot.query?.reason === reason) {
agentStatusController?.close();
}
},
[agentStatusController, agentStatusSnapshot.query?.reason]
);
const handleAgentProbeInfoOpen = useCallback(() => {
requestStatus({
scopeKey: activeStatusScopeKey,
agentSessionId: activeStatusSessionId,
reason: "agent-info"
});
}, [activeStatusScopeKey, activeStatusSessionId, requestStatus]);
const handleAgentProbeInfoClose = useCallback(
() => closeStatus("agent-info"),
[closeStatus]
);
const handleAgentConfigMenuOpen = useCallback(() => {
requestStatus({
scopeKey: railStatusScopeKey,
agentSessionId: null,
reason: "agent-config"
});
}, [railStatusScopeKey, requestStatus]);
const handleAgentConfigMenuClose = useCallback(
() => closeStatus("agent-config"),
[closeStatus]
);
const handleAgentUsageRefresh = useCallback(() => {
requestStatus({
scopeKey: railStatusScopeKey,
agentSessionId: null,
reason: "agent-config",
forceRefresh: true
});
}, [railStatusScopeKey, requestStatus]);
const handleSlashStatusClose = useCallback(
() => closeStatus("slash-status"),
[closeStatus]
);
const handleSlashStatusOpen = useCallback(() => {
requestStatus({
scopeKey: activeStatusScopeKey,
agentSessionId: activeStatusSessionId,
reason: "slash-status"
});
}, [activeStatusScopeKey, activeStatusSessionId, requestStatus]);
const handleSlashStatusRefresh = useCallback(() => {
requestStatus({
scopeKey: activeStatusScopeKey,
agentSessionId: activeStatusSessionId,
reason: "slash-status",
forceRefresh: true
});
}, [activeStatusScopeKey, activeStatusSessionId, requestStatus]);
const agentStatusLimits = useMemo(
() =>
slashStatusLimitsFromQuotas(
activeAgentStatusSnapshot.value?.quotas,
viewModel.composer.composerSettings.selectedModelValue ??
viewModel.composer.composerSettings.draftSettings.model,
t
),
[
activeAgentStatusSnapshot.value?.quotas,
t,
viewModel.composer.composerSettings.draftSettings.model,
viewModel.composer.composerSettings.selectedModelValue
]
);
const railAgentStatusLimits = useMemo(
() =>
slashStatusLimitsFromQuotas(
railAgentStatusSnapshot.value?.quotas,
null,
t
),
[railAgentStatusSnapshot.value?.quotas, t]
);
const slashStatusOverride = useMemo<AgentComposerSlashStatus | null>(() => {
if (!agentStatusController) return null;
const value = activeAgentStatusSnapshot.value;
return {
agentSessionId: value?.agentSessionId ?? activeStatusSessionId,
contextWindow: value?.contextWindow ?? null,
limits: agentStatusLimits,
limitsLoading:
activeAgentStatusSnapshot.phase === "loading" && value === null,
limitsUnavailable:
activeAgentStatusSnapshot.phase === "error" ||
(value !== null && value.limitsState !== "available"),
limitsResolvedEmpty:
value?.limitsState === "available" && agentStatusLimits.length === 0,
limitsCapturedAtUnixMs: value?.limitsCapturedAtUnixMs ?? null,
limitsErrorMessage: slashStatusUsageErrorMessage(
value?.limitsErrorCode ?? activeAgentStatusSnapshot.errorCode,
t
),
refreshFailed:
activeAgentStatusSnapshot.errorCode !== null ||
value?.limitsState === "error",
isRefreshing: activeAgentStatusSnapshot.isRefreshing
};
}, [
activeAgentStatusSnapshot.errorCode,
activeAgentStatusSnapshot.isRefreshing,
activeAgentStatusSnapshot.phase,
activeAgentStatusSnapshot.value,
activeStatusSessionId,
agentStatusController,
agentStatusLimits,
t
]);
const controllerRailStatus = agentStatusController
? {
accountLabel:
railAgentStatusSnapshot.value?.accountLabel?.trim() || null,
limits: railAgentStatusLimits,
loading:
railAgentStatusSnapshot.phase === "loading" &&
railAgentStatusSnapshot.value === null,
capturedAtUnixMs:
railAgentStatusSnapshot.value?.limitsCapturedAtUnixMs ?? null,
didFail:
railAgentStatusSnapshot.errorCode !== null ||
railAgentStatusSnapshot.value?.limitsState === "error",
errorMessage: slashStatusUsageErrorMessage(
railAgentStatusSnapshot.value?.limitsErrorCode ??
railAgentStatusSnapshot.errorCode,
t
),
attempted:
railAgentStatusSnapshot.value !== null ||
railAgentStatusSnapshot.errorCode !== null,
resolvedEmpty:
railAgentStatusSnapshot.value?.limitsState === "available" &&
railAgentStatusLimits.length === 0
}
: null;
return {
agentProbeLines,
controllerRailStatus,
handleAgentConfigMenuClose,
handleAgentConfigMenuOpen,
handleAgentProbeInfoClose,
handleAgentProbeInfoOpen,
handleAgentUsageRefresh:
railStatusTarget?.providerAccountUsageApplicable === false
? undefined
: handleAgentUsageRefresh,
handleSlashStatusClose,
handleSlashStatusOpen,
handleSlashStatusRefresh:
activeStatusTarget?.providerAccountUsageApplicable === false
? undefined
: handleSlashStatusRefresh,
railStatusProvider,
slashStatusLimits: agentStatusLimits,
slashStatusLimitsUnavailable:
activeAgentStatusSnapshot.phase === "error" ||
(activeAgentStatusSnapshot.value !== null &&
activeAgentStatusSnapshot.value.limitsState !== "available"),
slashStatusOverride
};
}