返回 DeepSeek-Reasonix
composer-profile.test.ts
根目录 / desktop / frontend / src / __tests__ / composer-profile.test.ts
1 // Run: tsx src/__tests__/composer-profile.test.ts
2
3 import {
4 composerProfileMode,
5 controllerComposerProfileCollaborationMode,
6 displayedComposerProfileCollaborationMode,
7 hydrateComposerProfileFromMeta,
8 hydrateComposerProfilesFromTabs,
9 patchComposerProfile,
10 pruneUserPlanModeIntents,
11 resolvePlanRestoreTabId,
12 shouldRestoreUserPlanMode,
13 shouldRestoreUserPlanModeForProfile,
14 updateUserPlanModeIntent,
15 type ComposerProfilesByTab,
16 type UserPlanModeIntents,
17 } from "../lib/composerProfile";
18 import type { Meta, TabMeta } from "../lib/types";
19
20 type LooseTabMeta = Omit<TabMeta, "toolApprovalMode"> & { toolApprovalMode?: TabMeta["toolApprovalMode"] | "" };
21 type LooseMeta = Omit<Meta, "toolApprovalMode"> & { toolApprovalMode?: Meta["toolApprovalMode"] | "" };
22
23 let passed = 0;
24 let failed = 0;
25
26 function eq(a: unknown, b: unknown, label: string) {
27 if (a === b) {
28 process.stdout.write(` PASS ${label}\n`);
29 passed += 1;
30 } else {
31 process.stdout.write(` FAIL ${label}: expected ${JSON.stringify(b)}, got ${JSON.stringify(a)}\n`);
32 failed += 1;
33 }
34 }
35
36 function tab(overrides: Partial<LooseTabMeta> = {}): TabMeta {
37 return {
38 id: "tab-1",
39 scope: "project",
40 workspaceRoot: "/repo",
41 workspaceName: "repo",
42 topicId: "topic-1",
43 topicTitle: "Topic",
44 label: "DeepSeek-R1",
45 ready: true,
46 running: false,
47 mode: "normal",
48 collaborationMode: "normal",
49 toolApprovalMode: "ask",
50 tokenMode: "full",
51 goal: "",
52 goalStatus: "stopped",
53 active: true,
54 cwd: "/repo",
55 ...overrides,
56 } as TabMeta;
57 }
58
59 function meta(overrides: Partial<LooseMeta> = {}): Meta {
60 return {
61 label: "DeepSeek-R1",
62 ready: true,
63 eventChannel: "events",
64 cwd: "/repo",
65 autoApproveTools: false,
66 bypass: false,
67 collaborationMode: "normal",
68 toolApprovalMode: "ask",
69 tokenMode: "full",
70 goal: "",
71 goalStatus: "stopped",
72 ...overrides,
73 } as Meta;
74 }
75
76 console.log("\ncomposer profile");
77
78 {
79 let profiles: ComposerProfilesByTab = {};
80 profiles = hydrateComposerProfilesFromTabs(profiles, [tab({ tokenMode: "delivery" })]);
81 eq(profiles["tab-1"].tokenMode, "delivery", "delivery runtime profile hydrates from persisted tabs");
82 profiles = hydrateComposerProfileFromMeta(profiles, "tab-1", meta({ tokenMode: "delivery" }));
83 eq(Boolean(profiles["tab-1"].pending.tokenMode), false, "delivery runtime profile is acknowledged by meta");
84 }
85
86 {
87 let profiles: ComposerProfilesByTab = {};
88 profiles = hydrateComposerProfilesFromTabs(profiles, [tab({ tokenMode: "economy" })]);
89 profiles = patchComposerProfile(
90 profiles,
91 "tab-1",
92 profiles["tab-1"],
93 { collaborationMode: "normal", goalDraftMode: true, goal: "" },
94 ["collaborationMode", "goal"],
95 );
96 profiles = patchComposerProfile(
97 profiles,
98 "tab-1",
99 profiles["tab-1"],
100 { collaborationMode: "plan", goalDraftMode: false, goal: "" },
101 ["collaborationMode", "goal"],
102 );
103
104 profiles = hydrateComposerProfilesFromTabs(profiles, [tab({ tokenMode: "economy" })]);
105
106 eq(displayedComposerProfileCollaborationMode(profiles["tab-1"]), "plan", "stale tab hydration keeps locally selected plan mode");
107 eq(profiles["tab-1"].tokenMode, "economy", "token saver remains independent of collaboration mode changes");
108 eq(composerProfileMode(profiles["tab-1"]), "plan", "compat mode keeps the plan axis enabled");
109 eq(Boolean(profiles["tab-1"].pending.collaborationMode), true, "pending plan stays pending until backend acknowledges it");
110
111 profiles = hydrateComposerProfilesFromTabs(profiles, [tab({ mode: "plan", collaborationMode: "plan", tokenMode: "economy" })]);
112
113 eq(displayedComposerProfileCollaborationMode(profiles["tab-1"]), "plan", "acknowledged tab hydration keeps plan visible");
114 eq(Boolean(profiles["tab-1"].pending.collaborationMode), false, "backend acknowledgement clears pending plan");
115 }
116
117 {
118 let profiles: ComposerProfilesByTab = {};
119 profiles = hydrateComposerProfilesFromTabs(profiles, [tab()]);
120 profiles = patchComposerProfile(profiles, "tab-1", profiles["tab-1"], { tokenMode: "economy" }, ["tokenMode"]);
121 profiles = hydrateComposerProfileFromMeta(profiles, "tab-1", meta({ tokenMode: "full" }));
122
123 eq(profiles["tab-1"].tokenMode, "economy", "stale meta cannot erase a pending token saver selection");
124 eq(Boolean(profiles["tab-1"].pending.tokenMode), true, "token saver stays pending while meta is stale");
125
126 profiles = hydrateComposerProfileFromMeta(profiles, "tab-1", meta({ tokenMode: "economy" }));
127
128 eq(profiles["tab-1"].tokenMode, "economy", "acknowledged token saver remains enabled");
129 eq(Boolean(profiles["tab-1"].pending.tokenMode), false, "token saver pending clears after matching meta");
130 }
131
132 {
133 let profiles: ComposerProfilesByTab = {};
134 profiles = hydrateComposerProfilesFromTabs(profiles, [tab()]);
135 profiles = patchComposerProfile(
136 profiles,
137 "tab-1",
138 profiles["tab-1"],
139 { collaborationMode: "normal", goalDraftMode: true, goal: "" },
140 ["collaborationMode", "goal"],
141 );
142 profiles = hydrateComposerProfilesFromTabs(profiles, [tab()]);
143
144 eq(displayedComposerProfileCollaborationMode(profiles["tab-1"]), "goal", "empty goal draft remains visible through stale tab hydration");
145 eq(controllerComposerProfileCollaborationMode(profiles["tab-1"]), "normal", "empty goal draft syncs to controller as normal");
146 eq(composerProfileMode(profiles["tab-1"]), "normal", "empty goal draft does not enable plan compatibility mode");
147 }
148
149 {
150 let profiles: ComposerProfilesByTab = {};
151 profiles = hydrateComposerProfilesFromTabs(profiles, [tab(), tab({ id: "tab-2" })]);
152 profiles = patchComposerProfile(profiles, "tab-2", profiles["tab-2"], { tokenMode: "economy" }, ["tokenMode"]);
153 profiles = hydrateComposerProfilesFromTabs(profiles, [tab()]);
154
155 eq(Boolean(profiles["tab-2"]), false, "tab hydration removes profiles for closed tabs");
156 }
157
158 {
159 let profiles: ComposerProfilesByTab = {};
160 profiles = hydrateComposerProfilesFromTabs(profiles, [tab({ toolApprovalMode: "auto" })]);
161 profiles = hydrateComposerProfilesFromTabs(profiles, [tab({ toolApprovalMode: "" })]);
162
163 eq(profiles["tab-1"].toolApprovalMode, "auto", "blank tab payload does not demote explicit auto approval mode to ask");
164
165 profiles = hydrateComposerProfileFromMeta(profiles, "tab-1", meta({ toolApprovalMode: "" }));
166 eq(profiles["tab-1"].toolApprovalMode, "auto", "blank meta payload does not demote explicit auto approval mode to ask");
167 }
168
169 {
170 let intents: UserPlanModeIntents = {};
171 intents = updateUserPlanModeIntent(intents, "tab-1", true);
172 intents = updateUserPlanModeIntent(intents, "tab-2", false);
173
174 eq(shouldRestoreUserPlanMode(intents, "tab-1"), true, "manual plan intent restores only the tab that enabled it");
175 eq(shouldRestoreUserPlanMode(intents, "tab-2"), false, "normal tabs do not inherit another tab's plan intent");
176
177 intents = updateUserPlanModeIntent(intents, "tab-1", false);
178 eq(shouldRestoreUserPlanMode(intents, "tab-1"), false, "manual normal mode clears plan restore intent");
179 }
180
181 {
182 let intents: UserPlanModeIntents = {};
183 intents = updateUserPlanModeIntent(intents, "tab-1", true);
184 intents = updateUserPlanModeIntent(intents, "tab-2", true);
185 intents = pruneUserPlanModeIntents(intents, ["tab-2"]);
186
187 eq(shouldRestoreUserPlanMode(intents, "tab-1"), false, "closed tabs lose plan restore intent");
188 eq(shouldRestoreUserPlanMode(intents, "tab-2"), true, "open tabs keep plan restore intent");
189 }
190
191 {
192 eq(resolvePlanRestoreTabId("finished-tab", "active-tab"), "finished-tab", "turn_done plan restore uses the event tab when present");
193 eq(resolvePlanRestoreTabId(undefined, "active-tab"), "active-tab", "turn_done plan restore falls back to the active tab for legacy events");
194 }
195
196 {
197 let intents: UserPlanModeIntents = {};
198 intents = updateUserPlanModeIntent(intents, "tab-1", true);
199
200 eq(
201 shouldRestoreUserPlanModeForProfile(intents, "tab-1", { goal: "research task" }),
202 false,
203 "active goals block remembered plan restoration",
204 );
205 eq(
206 shouldRestoreUserPlanModeForProfile(intents, "tab-1", { goal: "" }),
207 true,
208 "empty goals still allow remembered plan restoration",
209 );
210 }
211
212 console.log(`\n${passed} passed, ${failed} failed, ${passed + failed} total`);
213 if (failed > 0) process.exit(1);
214
214 lines TYPESCRIPT