源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
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
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
This commit is contained in:
@@ -0,0 +1,337 @@
|
||||
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
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user