返回 DeepSeek-Reasonix
controllerProfileOwner.ts
根目录 / desktop / frontend / src / app-runtime / controllerProfileOwner.ts
1 import { composerProfileFromTab, composerProfileMode, controllerComposerProfileCollaborationMode, displayedComposerProfileCollaborationMode, type ComposerProfile } from "../lib/composerProfile";
2 import type { CollaborationMode, TabMeta, ToolApprovalMode } from "../lib/types";
3 import { sessionIdentityKey } from "./sessionTarget";
4 import type { SessionOperationAuthority, SessionResource } from "./useSessionOperations";
5
6 export type ControllerProfile = { collaboration: CollaborationMode; approval: ToolApprovalMode; goal: string };
7 export type ControllerProfileResource = { target: SessionResource; profile: ControllerProfile; remote: boolean };
8 export type ControllerProfilePorts = {
9 model(tabId: string, name: string): Promise<boolean>;
10 profile(tabId: string, collaboration: CollaborationMode, approval: ToolApprovalMode, goal: string, options: { propagateError: boolean }): Promise<boolean>;
11 };
12 const runtimeProfile = (profile: ComposerProfile): ControllerProfile => ({
13 collaboration: controllerComposerProfileCollaborationMode(profile), approval: profile.toolApprovalMode, goal: profile.goal,
14 });
15
16 /** A display-free read projection, not a second profile store. */
17 export function projectControllerProfiles(tabs: readonly TabMeta[], profiles: Readonly<Record<string, ComposerProfile>>,
18 active: { target: SessionResource; profile: ComposerProfile; remote: boolean }): ControllerProfileResource[] {
19 return [{ target: active.target, profile: runtimeProfile(active.profile), remote: active.remote },
20 ...tabs.filter(tab => tab.id !== active.target.tabId).map(tab => ({
21 target: { tabId: tab.id, sessionKey: sessionIdentityKey({ tabId: tab.id, sessionPath: tab.sessionPath,
22 session: tab.session, sessionId: tab.sessionId, remote: tab.remote, sessionGeneration: tab.sessionGeneration,
23 scope: tab.scope, workspaceRoot: tab.workspaceRoot, topicId: tab.topicId }) },
24 profile: runtimeProfile(profiles[tab.id] ?? composerProfileFromTab(tab)), remote: Boolean(tab.remote),
25 }))];
26 }
27
28 export type ControllerProfileInput = {
29 target: SessionResource;
30 read(target: SessionResource): ControllerProfileResource;
31 ports: ControllerProfilePorts;
32 };
33
34 /** Rebuild and ordinary readiness restoration use the same source-profile application. */
35 export async function executeControllerProfile(input: ControllerProfileInput, authority: SessionOperationAuthority): Promise<boolean> {
36 authority.checkpoint();
37 const resource = input.read(input.target);
38 if (resource.remote) return false;
39 // Rebuilding may outlive a profile commit. Read only this resource's committed
40 // values, never the render captured before rebuilding or the active tab.
41 const { collaboration, approval, goal } = input.read(input.target).profile;
42 const applied = await input.ports.profile(input.target.tabId, collaboration, approval, goal, { propagateError: true });
43 authority.checkpoint();
44 return applied;
45 }
46
47 export async function executeControllerModel(input: ControllerProfileInput & {
48 name: string; remote?: (name: string) => Promise<void>;
49 restore(target: SessionResource): Promise<boolean>;
50 }, authority: SessionOperationAuthority): Promise<boolean> {
51 authority.checkpoint();
52 if (input.read(input.target).remote) {
53 if (!input.remote) return false;
54 await input.remote(input.name);
55 } else {
56 if (!await input.ports.model(input.target.tabId, input.name)) return false;
57 authority.checkpoint();
58 // Startup, explicit send readiness and post-model restoration share one
59 // request channel. A coalesced Controller failure has only one UI owner.
60 if (!await input.restore(input.target)) return false;
61 }
62 authority.checkpoint();
63 return authority.ownsUI();
64 }
65
66 /** Visible tab strip projection: committed order plus profile display fields. */
67 export function projectVisibleTabs(input: {
68 tabs: readonly TabMeta[];
69 orderIds: readonly string[];
70 profiles: Readonly<Record<string, ComposerProfile>>;
71 visibleTabId: string | undefined;
72 running: boolean;
73 }) {
74 const byId = new Map(input.tabs.map((tab) => [tab.id, tab]));
75 const ordered = input.orderIds.map((id) => byId.get(id)).filter((tab): tab is TabMeta => Boolean(tab));
76 const missing = input.tabs.filter((tab) => !input.orderIds.includes(tab.id));
77 return [...ordered, ...missing].map((tab) => {
78 const profile = input.profiles[tab.id] ?? composerProfileFromTab(tab);
79 return {
80 ...tab,
81 running: tab.id === input.visibleTabId ? tab.running || input.running : tab.running,
82 mode: composerProfileMode(profile),
83 collaborationMode: displayedComposerProfileCollaborationMode(profile),
84 toolApprovalMode: profile.toolApprovalMode,
85 goal: profile.goal,
86 active: tab.id === input.visibleTabId,
87 };
88 });
89 }
90
90 lines TYPESCRIPT