源码快照 @ 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:
+178
@@ -0,0 +1,178 @@
|
||||
import {
|
||||
createAgentGUIConversationActivityActivation,
|
||||
reconcileAgentGUIConversationActivityActivation,
|
||||
type AgentGUIConversationActivityActivation
|
||||
} from "../model/agentGuiConversationActivityView";
|
||||
import type { AgentGUIConversationSummary } from "../model/agentGuiConversationTypes";
|
||||
|
||||
const EMPTY_DELETED_SESSION_IDS: Readonly<Record<string, true>> = {};
|
||||
|
||||
export interface AgentGUIConversationActivityControllerInput {
|
||||
available: boolean;
|
||||
conversations: readonly AgentGUIConversationSummary[];
|
||||
deletedSessionIds?: Readonly<Record<string, true>>;
|
||||
identityKey: string;
|
||||
scopeKey: string;
|
||||
}
|
||||
|
||||
export interface AgentGUIConversationActivityControllerSnapshot {
|
||||
available: boolean;
|
||||
activation: AgentGUIConversationActivityActivation | null;
|
||||
conversationCache: ReadonlyMap<string, AgentGUIConversationSummary>;
|
||||
enabled: boolean;
|
||||
identityKey: string | null;
|
||||
scopeKey: string | null;
|
||||
}
|
||||
|
||||
export interface AgentGUIConversationActivityController {
|
||||
getSnapshot: () => AgentGUIConversationActivityControllerSnapshot;
|
||||
subscribe: (listener: () => void) => () => void;
|
||||
configure: (input: AgentGUIConversationActivityControllerInput) => void;
|
||||
toggle: () => void;
|
||||
}
|
||||
|
||||
const DISABLED_SNAPSHOT: AgentGUIConversationActivityControllerSnapshot = {
|
||||
available: false,
|
||||
activation: null,
|
||||
conversationCache: new Map(),
|
||||
enabled: false,
|
||||
identityKey: null,
|
||||
scopeKey: null
|
||||
};
|
||||
const AVAILABLE_OFF_SNAPSHOT: AgentGUIConversationActivityControllerSnapshot = {
|
||||
...DISABLED_SNAPSHOT,
|
||||
available: true
|
||||
};
|
||||
|
||||
export function createAgentGUIConversationActivityController(): AgentGUIConversationActivityController {
|
||||
let snapshot = DISABLED_SNAPSHOT;
|
||||
let latestInput: AgentGUIConversationActivityControllerInput | null = null;
|
||||
const listeners = new Set<() => void>();
|
||||
|
||||
const publish = (
|
||||
next: AgentGUIConversationActivityControllerSnapshot
|
||||
): void => {
|
||||
if (next === snapshot) return;
|
||||
snapshot = next;
|
||||
for (const listener of listeners) listener();
|
||||
};
|
||||
|
||||
const configure = (
|
||||
input: AgentGUIConversationActivityControllerInput
|
||||
): void => {
|
||||
latestInput = input;
|
||||
if (!input.available) {
|
||||
if (snapshot !== DISABLED_SNAPSHOT) publish(DISABLED_SNAPSHOT);
|
||||
return;
|
||||
}
|
||||
if (!snapshot.enabled) {
|
||||
if (snapshot !== AVAILABLE_OFF_SNAPSHOT) {
|
||||
publish(AVAILABLE_OFF_SNAPSHOT);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const sameContext =
|
||||
snapshot.identityKey === input.identityKey &&
|
||||
snapshot.scopeKey === input.scopeKey;
|
||||
const deletedSessionIds =
|
||||
input.deletedSessionIds ?? EMPTY_DELETED_SESSION_IDS;
|
||||
const candidates = input.conversations.filter(
|
||||
(conversation) => !deletedSessionIds[conversation.id]
|
||||
);
|
||||
const activation =
|
||||
sameContext && snapshot.activation
|
||||
? reconcileAgentGUIConversationActivityActivation(
|
||||
snapshot.activation,
|
||||
candidates,
|
||||
deletedSessionIds
|
||||
)
|
||||
: createAgentGUIConversationActivityActivation(candidates, Date.now());
|
||||
const conversationCache = mergeConversationCache(
|
||||
sameContext ? snapshot.conversationCache : new Map(),
|
||||
candidates,
|
||||
deletedSessionIds
|
||||
);
|
||||
if (
|
||||
sameContext &&
|
||||
activation === snapshot.activation &&
|
||||
conversationCache === snapshot.conversationCache
|
||||
) {
|
||||
return;
|
||||
}
|
||||
publish({
|
||||
available: true,
|
||||
activation,
|
||||
conversationCache,
|
||||
enabled: true,
|
||||
identityKey: input.identityKey,
|
||||
scopeKey: input.scopeKey
|
||||
});
|
||||
};
|
||||
|
||||
const toggle = (): void => {
|
||||
const input = latestInput;
|
||||
if (!input) return;
|
||||
if (!input.available) return;
|
||||
if (snapshot.enabled) {
|
||||
publish(AVAILABLE_OFF_SNAPSHOT);
|
||||
return;
|
||||
}
|
||||
const deletedSessionIds =
|
||||
input.deletedSessionIds ?? EMPTY_DELETED_SESSION_IDS;
|
||||
const candidates = input.conversations.filter(
|
||||
(conversation) => !deletedSessionIds[conversation.id]
|
||||
);
|
||||
publish({
|
||||
available: true,
|
||||
activation: createAgentGUIConversationActivityActivation(
|
||||
candidates,
|
||||
Date.now()
|
||||
),
|
||||
conversationCache: mergeConversationCache(
|
||||
new Map(),
|
||||
candidates,
|
||||
deletedSessionIds
|
||||
),
|
||||
enabled: true,
|
||||
identityKey: input.identityKey,
|
||||
scopeKey: input.scopeKey
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
getSnapshot: () => snapshot,
|
||||
subscribe: (listener) => {
|
||||
listeners.add(listener);
|
||||
return () => listeners.delete(listener);
|
||||
},
|
||||
configure,
|
||||
toggle
|
||||
};
|
||||
}
|
||||
|
||||
function mergeConversationCache(
|
||||
previous: ReadonlyMap<string, AgentGUIConversationSummary>,
|
||||
conversations: readonly AgentGUIConversationSummary[],
|
||||
deletedSessionIds: Readonly<Record<string, true>>
|
||||
): ReadonlyMap<string, AgentGUIConversationSummary> {
|
||||
const next = new Map(previous);
|
||||
for (const deletedId of Object.keys(deletedSessionIds)) {
|
||||
next.delete(deletedId);
|
||||
}
|
||||
for (const conversation of conversations) {
|
||||
next.set(conversation.id, conversation);
|
||||
}
|
||||
if (conversationMapsEqual(previous, next)) return previous;
|
||||
return next;
|
||||
}
|
||||
|
||||
function conversationMapsEqual(
|
||||
left: ReadonlyMap<string, AgentGUIConversationSummary>,
|
||||
right: ReadonlyMap<string, AgentGUIConversationSummary>
|
||||
): boolean {
|
||||
return (
|
||||
left.size === right.size &&
|
||||
[...left].every(([id, conversation]) => right.get(id) === conversation)
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user