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
607 lines
19 KiB
TypeScript
607 lines
19 KiB
TypeScript
import {
|
|
createContext,
|
|
useContext,
|
|
useMemo,
|
|
useSyncExternalStore,
|
|
type JSX,
|
|
type PropsWithChildren
|
|
} from "react";
|
|
import type {
|
|
AgentActivityCollaborationRun,
|
|
AgentActivityComposerOptions,
|
|
AgentActivityMessage,
|
|
AgentActivityCreateSessionInput,
|
|
AgentActivityDeleteSessionInput,
|
|
AgentActivityDeleteSessionResult,
|
|
AgentActivityMessageOrder,
|
|
AgentActivityMessagePage,
|
|
AgentActivityRailPlacement,
|
|
AgentActivityRenameSessionInput,
|
|
AgentActivitySendInput,
|
|
AgentActivitySession,
|
|
AgentActivitySessionSettings,
|
|
AgentActivitySetCollaborationAdoptionInput,
|
|
AgentActivitySnapshot,
|
|
AgentActivitySnapshotListener,
|
|
AgentSessionEngine
|
|
} from "@tutti-os/agent-activity-core";
|
|
import type { AgentHostAgentSessionComposerSettings } from "./shared/contracts/dto";
|
|
import type {
|
|
AgentConversationRailDeleteSessionsBatchInput,
|
|
AgentConversationRailDeleteSessionsBatchResult,
|
|
AgentConversationRailListPinnedSessionsPageInput,
|
|
AgentConversationRailListSessionSectionPageInput,
|
|
AgentConversationRailListSessionSectionsInput,
|
|
AgentConversationRailListSessionsPageInput,
|
|
AgentConversationRailSessionPage,
|
|
AgentConversationRailSessionSection,
|
|
AgentConversationRailSessionSectionDeletionCandidates,
|
|
AgentConversationRailSessionSectionScopeInput,
|
|
AgentConversationRailSessionSectionsResult,
|
|
AgentConversationRailSessionsPageResult,
|
|
AgentConversationRailUserProject
|
|
} from "./agentConversationRailContracts";
|
|
import { useEngineSelector } from "./shared/engine/useEngineSelector";
|
|
|
|
const EMPTY_SESSION_MESSAGES: readonly AgentActivityMessage[] = [];
|
|
|
|
export type AgentActivitySessionMessages = Readonly<
|
|
Record<string, readonly AgentActivityMessage[]>
|
|
>;
|
|
|
|
export interface AgentActivityRuntimeUpdateSessionSettingsResult {
|
|
agentSessionId: string;
|
|
settings: AgentHostAgentSessionComposerSettings;
|
|
session: AgentActivitySession;
|
|
}
|
|
|
|
export interface AgentActivityRuntimeListSessionMessagesInput {
|
|
afterVersion?: number;
|
|
beforeVersion?: number;
|
|
cache?: boolean;
|
|
agentSessionId: string;
|
|
limit?: number;
|
|
order?: AgentActivityMessageOrder;
|
|
signal?: AbortSignal;
|
|
workspaceId: string;
|
|
}
|
|
|
|
export interface AgentActivityRuntimeListGeneratedFilesInput {
|
|
agentTargetIds?: readonly string[];
|
|
cursor?: string;
|
|
limit?: number;
|
|
query?: string;
|
|
sectionKey: string;
|
|
signal?: AbortSignal;
|
|
workspaceId: string;
|
|
}
|
|
|
|
export type AgentActivityRuntimeListSessionsPageInput =
|
|
AgentConversationRailListSessionsPageInput;
|
|
export type AgentActivityRuntimeSessionPageResult =
|
|
AgentConversationRailSessionsPageResult;
|
|
export type AgentActivityRuntimeListSessionSectionsInput =
|
|
AgentConversationRailListSessionSectionsInput;
|
|
export type AgentActivityRuntimeListSessionSectionPageInput =
|
|
AgentConversationRailListSessionSectionPageInput;
|
|
export type AgentActivityRuntimeListPinnedSessionsPageInput =
|
|
AgentConversationRailListPinnedSessionsPageInput;
|
|
export type AgentActivityRuntimeUserProject = AgentConversationRailUserProject;
|
|
export type AgentActivityRuntimeSessionSection =
|
|
AgentConversationRailSessionSection;
|
|
export type AgentActivityRuntimeSessionPage = AgentConversationRailSessionPage;
|
|
export type AgentActivityRuntimeSessionSectionsResult =
|
|
AgentConversationRailSessionSectionsResult;
|
|
|
|
export interface AgentActivityRuntimeGeneratedFile {
|
|
label: string;
|
|
path: string;
|
|
}
|
|
|
|
export interface AgentActivityRuntimeGeneratedFileList {
|
|
entries: AgentActivityRuntimeGeneratedFile[];
|
|
hasMore?: boolean;
|
|
nextCursor?: string;
|
|
workspaceId: string;
|
|
}
|
|
|
|
export interface AgentActivityRuntimeEnsureSessionSynchronizedInput {
|
|
afterVersion?: number;
|
|
agentSessionId: string;
|
|
onError?: (error: unknown) => void;
|
|
workspaceId: string;
|
|
}
|
|
|
|
export interface AgentActivityRuntimeSetSessionPinnedInput {
|
|
agentSessionId: string;
|
|
pinned: boolean;
|
|
workspaceId: string;
|
|
}
|
|
|
|
export interface AgentActivityRuntimeTrackSettingsProjectChangeInput {
|
|
action: "clear" | "create_new" | "import_directory" | "select_existing";
|
|
agentSessionId: string | null;
|
|
provider?: string | null;
|
|
workspaceId: string;
|
|
}
|
|
|
|
export interface AgentActivityRuntimeGetComposerOptionsInput {
|
|
agentSessionId?: string | null;
|
|
agentTargetId: string;
|
|
cwd?: string | null;
|
|
force?: boolean;
|
|
waitForFreshModelCatalog?: boolean;
|
|
provider?: string;
|
|
section?: "full" | "core" | "capabilities" | "connectors";
|
|
settings?: AgentHostAgentSessionComposerSettings | null;
|
|
workspaceId: string;
|
|
}
|
|
|
|
export interface AgentActivityRuntimeUpdateSessionSettingsInput {
|
|
agentSessionId: string;
|
|
signal?: AbortSignal;
|
|
settings: AgentHostAgentSessionComposerSettings;
|
|
workspaceId: string;
|
|
}
|
|
|
|
export interface AgentActivityRuntimeTrackDraftComposerSettingsChangeInput {
|
|
nextSettings: AgentHostAgentSessionComposerSettings;
|
|
previousSettings: AgentHostAgentSessionComposerSettings;
|
|
provider: string;
|
|
workspaceId: string;
|
|
}
|
|
|
|
export interface AgentActivityRuntimeDiagnosticInput {
|
|
details?: Record<string, unknown>;
|
|
event: string;
|
|
level?: "debug" | "info" | "warn" | "error";
|
|
source?: string;
|
|
workspaceId?: string | null;
|
|
}
|
|
|
|
interface AgentActivityRuntimeActivateSessionInputBase {
|
|
activationId: string;
|
|
agentSessionId: string;
|
|
capabilityRefs?: AgentActivityCreateSessionInput["capabilityRefs"];
|
|
cwd?: string;
|
|
initialContent?: AgentActivitySendInput["content"];
|
|
/** 仅展示用首轮文本(bundle 折叠成一个 chip);initialContent 仍带展开后的文件。 */
|
|
initialDisplayPrompt?: string | null;
|
|
isolation?: AgentActivityCreateSessionInput["isolation"];
|
|
modelExplicit?: boolean;
|
|
railPlacement?: AgentActivityRailPlacement;
|
|
reasoningEffortExplicit?: boolean;
|
|
submitDiagnostics?: AgentActivitySendInput["submitDiagnostics"];
|
|
settings?: AgentActivitySessionSettings;
|
|
title?: string;
|
|
visible?: boolean;
|
|
workspaceId: string;
|
|
signal?: AbortSignal;
|
|
}
|
|
|
|
export type AgentActivityRuntimeActivateSessionInput =
|
|
| (AgentActivityRuntimeActivateSessionInputBase & {
|
|
agentTargetId: string;
|
|
clientSubmitId: string;
|
|
initialGoalControl?: AgentActivityCreateSessionInput["initialGoalControl"];
|
|
initialTuttiModeActivation?: AgentActivityCreateSessionInput["initialTuttiModeActivation"];
|
|
mode: "new";
|
|
})
|
|
| (AgentActivityRuntimeActivateSessionInputBase & {
|
|
agentTargetId?: string | null;
|
|
clientSubmitId?: never;
|
|
mode: "existing";
|
|
});
|
|
|
|
export interface AgentActivityRuntimeUnactivateSessionInput {
|
|
agentSessionId: string;
|
|
workspaceId: string;
|
|
}
|
|
|
|
export interface AgentActivityRuntimeReadSessionAttachmentInput {
|
|
agentSessionId: string;
|
|
attachmentId: string;
|
|
workspaceId: string;
|
|
}
|
|
|
|
export interface AgentActivityRuntimeReadPromptAssetInput {
|
|
agentSessionId?: string | null;
|
|
assetId?: string | null;
|
|
hostPath?: string | null;
|
|
kind?: string | null;
|
|
mimeType: string;
|
|
name?: string | null;
|
|
path?: string | null;
|
|
sha256?: string | null;
|
|
uploadStatus?: string | null;
|
|
uri?: string | null;
|
|
workspaceId: string;
|
|
}
|
|
|
|
export type AgentActivityRuntimePromptContentBlock =
|
|
AgentActivitySendInput["content"][number] & {
|
|
assetId?: string;
|
|
hostPath?: string;
|
|
kind?: string;
|
|
path?: string;
|
|
sizeBytes?: number;
|
|
uploadStatus?: string;
|
|
uri?: string;
|
|
};
|
|
|
|
export interface AgentActivityRuntimeUploadPromptContentInput {
|
|
content: AgentActivityRuntimePromptContentBlock[];
|
|
workspaceId: string;
|
|
}
|
|
|
|
export interface AgentActivityRuntimeUploadPromptContentResult {
|
|
content: AgentActivityRuntimePromptContentBlock[];
|
|
}
|
|
|
|
/**
|
|
* Dedicated host boundary for turning an in-memory text paste into a prepared
|
|
* prompt asset. The runtime owns persistence and returns one provider-readable
|
|
* locator; AgentGUI must not infer this capability from generic file-upload
|
|
* support.
|
|
*/
|
|
export interface AgentActivityRuntimeStagePastedTextInput {
|
|
name: string;
|
|
text: string;
|
|
workspaceId: string;
|
|
}
|
|
|
|
/**
|
|
* A prepared long-text asset. Local hosts return a path; remote/shared hosts
|
|
* return the same URL-backed attachment metadata used by ordinary prompt-file
|
|
* upload. Exactly one of `path` and `url` must be present.
|
|
*/
|
|
export type AgentActivityRuntimeStagePastedTextResult =
|
|
| {
|
|
name: string;
|
|
path: string;
|
|
url?: never;
|
|
assetId?: never;
|
|
mimeType?: string;
|
|
sizeBytes: number;
|
|
uploadStatus?: never;
|
|
uri?: never;
|
|
}
|
|
| {
|
|
name: string;
|
|
path?: never;
|
|
url: string;
|
|
assetId?: string;
|
|
mimeType?: string;
|
|
sizeBytes: number;
|
|
uploadStatus?: string;
|
|
uri?: string;
|
|
};
|
|
|
|
export type AgentActivityRuntimeSessionSectionScopeInput =
|
|
AgentConversationRailSessionSectionScopeInput;
|
|
export type AgentActivityRuntimeSessionSectionDeletionCandidates =
|
|
AgentConversationRailSessionSectionDeletionCandidates;
|
|
export type AgentActivityRuntimeDeleteSessionsBatchInput =
|
|
AgentConversationRailDeleteSessionsBatchInput;
|
|
export type AgentActivityRuntimeDeleteSessionsBatchResult =
|
|
AgentConversationRailDeleteSessionsBatchResult;
|
|
|
|
export interface AgentActivityRuntimeSessionAttachment {
|
|
attachmentId: string;
|
|
mimeType: string;
|
|
name?: string;
|
|
data: string;
|
|
}
|
|
|
|
export interface AgentActivityRuntimePromptAsset {
|
|
assetId?: string;
|
|
hostPath?: string;
|
|
kind?: string;
|
|
mimeType: string;
|
|
name?: string;
|
|
path: string;
|
|
uploadStatus?: string;
|
|
uri?: string;
|
|
data: string;
|
|
}
|
|
|
|
/**
|
|
* Host runtime surface consumed by AgentGUI. Session lifecycle writes are
|
|
* owned by the workspace {@link AgentSessionEngine}; this boundary contains
|
|
* only the reads, metadata actions, uploads, diagnostics, and subscriptions
|
|
* that remain host-owned.
|
|
*/
|
|
export interface AgentGUIRuntime {
|
|
/**
|
|
* Stable identity of this runtime instance (e.g. a local origin vs a
|
|
* shared/room origin). The runtime owns one session engine per workspace and
|
|
* that engine verifies this origin as part of its injected identity. Runtime
|
|
* consumers resolve only through the nearest React provider; module-global
|
|
* runtime lookup and last-mounted fallback are forbidden. An absent origin
|
|
* means the canonical local origin.
|
|
*/
|
|
origin?: string;
|
|
/**
|
|
* Enables the Codex-aligned in-memory conversation Activity View. Missing or
|
|
* false fails closed so external hosts opt in explicitly.
|
|
*/
|
|
conversationActivityViewEnabled?: boolean;
|
|
/**
|
|
* Host query limits for the Conversation Rail. Omit when the backend accepts
|
|
* AgentGUI's default limits.
|
|
*/
|
|
conversationRailQueryLimits?: {
|
|
sectionRefreshLimitMax: number;
|
|
};
|
|
/**
|
|
* The session cwd is not resolvable on the local filesystem (e.g. a
|
|
* shared/cloud sandbox not mounted locally), so AgentGUI must not run its
|
|
* local stat-based "working directory missing" existence check — it would
|
|
* always false-positive. Absent/false (default) => local, legacy behaviour.
|
|
* Only that one guard is gated; project selection/listing is unaffected.
|
|
*/
|
|
projectPathIsRemote?: boolean;
|
|
promptContentUploadSupport?: {
|
|
file?: boolean;
|
|
image?: boolean;
|
|
};
|
|
/** Set false to suppress AgentGUI diagnostics in development consoles. */
|
|
devDiagnosticConsoleSink?: boolean;
|
|
deleteSession(
|
|
input: AgentActivityDeleteSessionInput
|
|
): Promise<AgentActivityDeleteSessionResult>;
|
|
getSession(
|
|
workspaceId: string,
|
|
agentSessionId: string
|
|
): Promise<AgentActivitySession>;
|
|
getComposerOptions(
|
|
input: AgentActivityRuntimeGetComposerOptionsInput
|
|
): Promise<AgentActivityComposerOptions>;
|
|
getSnapshot(workspaceId: string): AgentActivitySnapshot;
|
|
getSessionEngine(workspaceId: string): AgentSessionEngine;
|
|
listSessionMessages(
|
|
input: AgentActivityRuntimeListSessionMessagesInput
|
|
): Promise<AgentActivityMessagePage>;
|
|
listAgentGeneratedFiles?(
|
|
input: AgentActivityRuntimeListGeneratedFilesInput
|
|
): Promise<AgentActivityRuntimeGeneratedFileList>;
|
|
listSessionsPage?(
|
|
input: AgentActivityRuntimeListSessionsPageInput
|
|
): Promise<AgentActivityRuntimeSessionPageResult>;
|
|
listSessionSections?(
|
|
input: AgentActivityRuntimeListSessionSectionsInput
|
|
): Promise<AgentActivityRuntimeSessionSectionsResult>;
|
|
listSessionSectionPage?(
|
|
input: AgentActivityRuntimeListSessionSectionPageInput
|
|
): Promise<AgentActivityRuntimeSessionSection>;
|
|
listSessionSectionDeletionCandidates?(
|
|
input: AgentActivityRuntimeSessionSectionScopeInput
|
|
): Promise<AgentActivityRuntimeSessionSectionDeletionCandidates>;
|
|
deleteSessionsBatch?(
|
|
input: AgentActivityRuntimeDeleteSessionsBatchInput
|
|
): Promise<AgentActivityRuntimeDeleteSessionsBatchResult>;
|
|
listPinnedSessionsPage?(
|
|
input: AgentActivityRuntimeListPinnedSessionsPageInput
|
|
): Promise<AgentActivityRuntimeSessionPage>;
|
|
load(
|
|
workspaceId: string,
|
|
signal?: AbortSignal
|
|
): Promise<AgentActivitySnapshot>;
|
|
ensureSessionSynchronized?(
|
|
input: AgentActivityRuntimeEnsureSessionSynchronizedInput
|
|
): () => void;
|
|
uploadPromptContent?(
|
|
input: AgentActivityRuntimeUploadPromptContentInput
|
|
): Promise<AgentActivityRuntimeUploadPromptContentResult>;
|
|
stagePastedText?(
|
|
input: AgentActivityRuntimeStagePastedTextInput
|
|
): Promise<AgentActivityRuntimeStagePastedTextResult>;
|
|
readSessionAttachment?(
|
|
input: AgentActivityRuntimeReadSessionAttachmentInput
|
|
): Promise<AgentActivityRuntimeSessionAttachment>;
|
|
readPromptAsset?(
|
|
input: AgentActivityRuntimeReadPromptAssetInput
|
|
): Promise<AgentActivityRuntimePromptAsset>;
|
|
renameSession(
|
|
input: AgentActivityRenameSessionInput
|
|
): Promise<AgentActivitySession>;
|
|
/**
|
|
* Record whether a collaboration outcome was adopted.
|
|
* Optional; hosts without support omit it and adoption controls stay hidden.
|
|
*/
|
|
setCollaborationAdoption?(
|
|
input: AgentActivitySetCollaborationAdoptionInput
|
|
): Promise<AgentActivityCollaborationRun>;
|
|
setSessionPinned(
|
|
input: AgentActivityRuntimeSetSessionPinnedInput
|
|
): Promise<AgentActivitySession>;
|
|
trackSettingsProjectChange?(
|
|
input: AgentActivityRuntimeTrackSettingsProjectChangeInput
|
|
): Promise<void>;
|
|
trackDraftComposerSettingsChange?(
|
|
input: AgentActivityRuntimeTrackDraftComposerSettingsChangeInput
|
|
): Promise<void>;
|
|
reportDiagnostic?(
|
|
input: AgentActivityRuntimeDiagnosticInput
|
|
): Promise<void> | void;
|
|
subscribeSessionEvents(
|
|
workspaceId: string,
|
|
listener: (event: unknown) => void
|
|
): () => void;
|
|
subscribe(
|
|
workspaceId: string,
|
|
listener: AgentActivitySnapshotListener
|
|
): () => void;
|
|
}
|
|
|
|
const AgentGUIRuntimeContext = createContext<AgentGUIRuntime | null>(null);
|
|
|
|
function createTestAgentGUIRuntimeHolder(): {
|
|
get: () => AgentGUIRuntime | null;
|
|
set: (runtime: AgentGUIRuntime | null) => void;
|
|
} {
|
|
let runtime: AgentGUIRuntime | null = null;
|
|
return {
|
|
get: () => runtime,
|
|
set: (nextRuntime) => {
|
|
runtime = nextRuntime;
|
|
}
|
|
};
|
|
}
|
|
|
|
const testAgentGUIRuntimeHolder = createTestAgentGUIRuntimeHolder();
|
|
|
|
export interface AgentGUIRuntimeProviderProps extends PropsWithChildren {
|
|
runtime?: AgentGUIRuntime | null;
|
|
}
|
|
|
|
export function AgentGUIRuntimeProvider({
|
|
children,
|
|
runtime
|
|
}: AgentGUIRuntimeProviderProps): JSX.Element {
|
|
return (
|
|
<AgentGUIRuntimeContext.Provider value={runtime ?? null}>
|
|
{children}
|
|
</AgentGUIRuntimeContext.Provider>
|
|
);
|
|
}
|
|
|
|
export function useAgentGUIRuntime(): AgentGUIRuntime {
|
|
const runtime =
|
|
useContext(AgentGUIRuntimeContext) ?? getTestAgentGUIRuntime();
|
|
if (!runtime) {
|
|
throw new Error(
|
|
"AgentGUIRuntimeProvider is missing an AgentGUIRuntime instance."
|
|
);
|
|
}
|
|
return runtime;
|
|
}
|
|
|
|
export function useOptionalAgentGUIRuntime(): AgentGUIRuntime | null {
|
|
return useContext(AgentGUIRuntimeContext) ?? getTestAgentGUIRuntime();
|
|
}
|
|
|
|
export function useAgentActivitySnapshot(
|
|
workspaceId: string
|
|
): AgentActivitySnapshot {
|
|
const runtime = useAgentGUIRuntime();
|
|
const normalizedWorkspaceId = workspaceId.trim();
|
|
const workspaceStore = useMemo(
|
|
() => ({
|
|
getSnapshot: () => runtime.getSnapshot(normalizedWorkspaceId),
|
|
subscribe: (listener: () => void) =>
|
|
runtime.subscribe(normalizedWorkspaceId, listener)
|
|
}),
|
|
[normalizedWorkspaceId, runtime]
|
|
);
|
|
return useSyncExternalStore(
|
|
workspaceStore.subscribe,
|
|
workspaceStore.getSnapshot,
|
|
workspaceStore.getSnapshot
|
|
);
|
|
}
|
|
|
|
export function useAgentActivitySessionMessages(
|
|
workspaceId: string,
|
|
agentSessionIds: readonly (string | null | undefined)[]
|
|
): AgentActivitySessionMessages {
|
|
const runtime = useAgentGUIRuntime();
|
|
const normalizedWorkspaceId = workspaceId.trim();
|
|
const sessionIdsKey = agentSessionIds
|
|
.map((agentSessionId) => agentSessionId?.trim() ?? "")
|
|
.filter(Boolean)
|
|
.join("\u0000");
|
|
const normalizedSessionIds = useMemo(
|
|
() => [...new Set(sessionIdsKey.split("\u0000").filter(Boolean))],
|
|
[sessionIdsKey]
|
|
);
|
|
const workspaceStore = useMemo(
|
|
() => ({
|
|
getSnapshot: () => runtime.getSnapshot(normalizedWorkspaceId),
|
|
subscribe: (listener: () => void) =>
|
|
runtime.subscribe(normalizedWorkspaceId, listener)
|
|
}),
|
|
[normalizedWorkspaceId, runtime]
|
|
);
|
|
const selectMessages = useMemo(
|
|
() => createSessionMessagesSelector(normalizedSessionIds),
|
|
[normalizedSessionIds]
|
|
);
|
|
return useEngineSelector(workspaceStore, selectMessages);
|
|
}
|
|
|
|
export function resetAgentGUIRuntimeForTests(): void {
|
|
if (process.env.NODE_ENV === "test") {
|
|
testAgentGUIRuntimeHolder.set(null);
|
|
}
|
|
}
|
|
|
|
export function setAgentGUIRuntimeForTests(
|
|
runtime: AgentGUIRuntime | null
|
|
): void {
|
|
if (process.env.NODE_ENV === "test") {
|
|
testAgentGUIRuntimeHolder.set(runtime);
|
|
}
|
|
}
|
|
|
|
function createSessionMessagesSelector(
|
|
agentSessionIds: readonly string[]
|
|
): (snapshot: AgentActivitySnapshot) => AgentActivitySessionMessages {
|
|
let previous: AgentActivitySessionMessages = {};
|
|
return (snapshot) => {
|
|
const next = Object.fromEntries(
|
|
agentSessionIds.map((agentSessionId) => [
|
|
agentSessionId,
|
|
snapshot.sessionMessagesById[agentSessionId] ?? EMPTY_SESSION_MESSAGES
|
|
])
|
|
);
|
|
if (
|
|
Object.keys(previous).length === agentSessionIds.length &&
|
|
agentSessionIds.every(
|
|
(agentSessionId) => previous[agentSessionId] === next[agentSessionId]
|
|
)
|
|
) {
|
|
return previous;
|
|
}
|
|
previous = next;
|
|
return previous;
|
|
};
|
|
}
|
|
|
|
function getTestAgentGUIRuntime(): AgentGUIRuntime | null {
|
|
if (process.env.NODE_ENV !== "test") {
|
|
return null;
|
|
}
|
|
if (typeof window === "undefined") {
|
|
return null;
|
|
}
|
|
const explicitRuntime = getExplicitWindowTestAgentGUIRuntime();
|
|
if (explicitRuntime) {
|
|
return explicitRuntime;
|
|
}
|
|
const testRuntimeOverride = testAgentGUIRuntimeHolder.get();
|
|
if (testRuntimeOverride) {
|
|
return testRuntimeOverride;
|
|
}
|
|
const testRuntime = (
|
|
window as unknown as Window & {
|
|
agentGUIRuntime?: AgentGUIRuntime;
|
|
}
|
|
).agentGUIRuntime;
|
|
return testRuntime ?? null;
|
|
}
|
|
|
|
function getExplicitWindowTestAgentGUIRuntime(): AgentGUIRuntime | null {
|
|
if (process.env.NODE_ENV !== "test" || typeof window === "undefined") {
|
|
return null;
|
|
}
|
|
const testDescriptor = Object.getOwnPropertyDescriptor(
|
|
window,
|
|
"agentGUIRuntime"
|
|
);
|
|
if (!testDescriptor || !("value" in testDescriptor)) {
|
|
return null;
|
|
}
|
|
return (testDescriptor.value as AgentGUIRuntime | undefined) ?? null;
|
|
}
|