Files
tutti/packages/agent/gui/agent-gui/agentGuiNode/controller/AgentGUITargetConnectionController.ts
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

108 lines
3.2 KiB
TypeScript

import type { AgentGUITargetConnectionState } from "../../../types";
import {
agentGuiScheduler,
type AgentGuiScheduledTask,
type AgentGuiScheduler
} from "../agentGuiScheduler";
export const AGENT_GUI_TARGET_CONNECTING_NOTICE_DELAY_MS = 300;
export interface AgentGUITargetConnectionBinding {
getSnapshot(): AgentGUITargetConnectionState | null;
subscribe(listener: () => void): () => void;
}
interface AgentGUITargetConnectionControllerInput {
scheduler?: AgentGuiScheduler;
source: AgentGUITargetConnectionBinding;
}
export class AgentGUITargetConnectionController {
readonly getSnapshot = (): AgentGUITargetConnectionState | null =>
this.snapshot;
readonly subscribe = (listener: () => void): (() => void) => {
this.listeners.add(listener);
if (this.listeners.size === 1) {
this.unsubscribeSource = this.source.subscribe(this.handleSourceChange);
this.reconcile();
}
return () => {
this.listeners.delete(listener);
if (this.listeners.size === 0) {
this.unsubscribeSource?.();
this.unsubscribeSource = null;
this.cancelReveal();
}
};
};
private readonly listeners = new Set<() => void>();
private readonly scheduler: AgentGuiScheduler;
private readonly source: AgentGUITargetConnectionBinding;
private revealTask: AgentGuiScheduledTask | null = null;
private snapshot: AgentGUITargetConnectionState | null;
private unsubscribeSource: (() => void) | null = null;
constructor(input: AgentGUITargetConnectionControllerInput) {
this.scheduler = input.scheduler ?? agentGuiScheduler;
this.source = input.source;
this.snapshot = this.projectImmediateStatus();
}
private readonly handleSourceChange = (): void => this.reconcile();
private reconcile(): void {
const state = this.source.getSnapshot();
if (state?.status === "unavailable") {
this.cancelReveal();
this.publish(state);
return;
}
if (state?.status !== "connecting") {
this.cancelReveal();
this.publish(null);
return;
}
if (this.snapshot?.status === "connecting") {
this.publish(state);
return;
}
if (this.revealTask) return;
this.revealTask = this.scheduler.schedule(
AGENT_GUI_TARGET_CONNECTING_NOTICE_DELAY_MS,
() => {
this.revealTask = null;
const current = this.source.getSnapshot();
if (this.listeners.size > 0 && current?.status === "connecting") {
this.publish(current);
}
}
);
}
private projectImmediateStatus(): AgentGUITargetConnectionState | null {
const state = this.source.getSnapshot();
return state?.status === "unavailable" ? state : null;
}
private cancelReveal(): void {
this.revealTask?.cancel();
this.revealTask = null;
}
private publish(next: AgentGUITargetConnectionState | null): void {
if (targetConnectionStatesEqual(this.snapshot, next)) return;
this.snapshot = next;
for (const listener of this.listeners) listener();
}
}
function targetConnectionStatesEqual(
left: AgentGUITargetConnectionState | null,
right: AgentGUITargetConnectionState | null
): boolean {
return (
left?.status === right?.status && left?.retryAttempt === right?.retryAttempt
);
}