groove-dev 0.27.14 → 0.27.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -1
- package/developerID_application.cer +0 -0
- package/node_modules/@groove-dev/daemon/src/api.js +587 -68
- package/node_modules/@groove-dev/daemon/src/classifier.js +24 -0
- package/node_modules/@groove-dev/daemon/src/credentials.js +12 -2
- package/node_modules/@groove-dev/daemon/src/federation/ambassador.js +204 -0
- package/node_modules/@groove-dev/daemon/src/federation/connection.js +359 -0
- package/node_modules/@groove-dev/daemon/src/federation/contracts.js +112 -0
- package/node_modules/@groove-dev/daemon/src/federation/whitelist.js +190 -0
- package/node_modules/@groove-dev/daemon/src/federation.js +166 -7
- package/node_modules/@groove-dev/daemon/src/index.js +172 -19
- package/node_modules/@groove-dev/daemon/src/introducer.js +52 -7
- package/node_modules/@groove-dev/daemon/src/journalist.js +46 -1
- package/node_modules/@groove-dev/daemon/src/memory.js +36 -16
- package/node_modules/@groove-dev/daemon/src/process.js +140 -23
- package/node_modules/@groove-dev/daemon/src/providers/base.js +1 -0
- package/node_modules/@groove-dev/daemon/src/providers/claude-code.js +1 -0
- package/node_modules/@groove-dev/daemon/src/providers/codex.js +124 -28
- package/node_modules/@groove-dev/daemon/src/providers/gemini.js +104 -17
- package/node_modules/@groove-dev/daemon/src/providers/index.js +17 -0
- package/node_modules/@groove-dev/daemon/src/registry.js +10 -1
- package/node_modules/@groove-dev/daemon/src/rotator.js +93 -30
- package/node_modules/@groove-dev/daemon/src/skills.js +33 -3
- package/node_modules/@groove-dev/daemon/src/terminal-pty.js +9 -1
- package/node_modules/@groove-dev/daemon/src/tool-executor.js +11 -5
- package/node_modules/@groove-dev/daemon/src/toys.js +69 -0
- package/node_modules/@groove-dev/daemon/src/tunnel-manager.js +24 -5
- package/node_modules/@groove-dev/daemon/templates/toys-catalog.json +242 -0
- package/node_modules/@groove-dev/daemon/test/classifier.test.js +98 -0
- package/node_modules/@groove-dev/daemon/test/introducer.test.js +72 -1
- package/node_modules/@groove-dev/daemon/test/journalist.test.js +117 -0
- package/node_modules/@groove-dev/daemon/test/memory.test.js +37 -1
- package/node_modules/@groove-dev/daemon/test/rotator.test.js +183 -4
- package/node_modules/@groove-dev/gui/dist/assets/index-BglPgjlu.js +8607 -0
- package/node_modules/@groove-dev/gui/dist/assets/index-CGcwmmJv.css +1 -0
- package/node_modules/@groove-dev/gui/dist/index.html +3 -2
- package/node_modules/@groove-dev/gui/index.html +1 -0
- package/node_modules/@groove-dev/gui/src/app.css +7 -0
- package/node_modules/@groove-dev/gui/src/app.jsx +37 -10
- package/node_modules/@groove-dev/gui/src/components/agents/agent-chat.jsx +21 -31
- package/node_modules/@groove-dev/gui/src/components/agents/agent-config.jsx +11 -6
- package/node_modules/@groove-dev/gui/src/components/agents/agent-feed.jsx +2 -2
- package/node_modules/@groove-dev/gui/src/components/agents/spawn-wizard.jsx +42 -1
- package/node_modules/@groove-dev/gui/src/components/editor/breadcrumbs.jsx +30 -0
- package/node_modules/@groove-dev/gui/src/components/editor/code-editor.jsx +33 -2
- package/node_modules/@groove-dev/gui/src/components/editor/editor-status-bar.jsx +26 -0
- package/node_modules/@groove-dev/gui/src/components/editor/editor-tabs.jsx +113 -34
- package/node_modules/@groove-dev/gui/src/components/editor/goto-line.jsx +35 -0
- package/node_modules/@groove-dev/gui/src/components/editor/terminal.jsx +12 -6
- package/node_modules/@groove-dev/gui/src/components/layout/activity-bar.jsx +13 -3
- package/node_modules/@groove-dev/gui/src/components/layout/app-shell.jsx +0 -1
- package/node_modules/@groove-dev/gui/src/components/layout/breadcrumb-bar.jsx +165 -47
- package/node_modules/@groove-dev/gui/src/components/layout/command-palette.jsx +6 -2
- package/node_modules/@groove-dev/gui/src/components/layout/terminal-panel.jsx +10 -9
- package/node_modules/@groove-dev/gui/src/components/marketplace/repo-import.jsx +9 -1
- package/node_modules/@groove-dev/gui/src/components/onboarding/provider-card.jsx +134 -0
- package/node_modules/@groove-dev/gui/src/components/onboarding/setup-wizard.jsx +819 -0
- package/node_modules/@groove-dev/gui/src/components/pro/pro-gate.jsx +12 -5
- package/node_modules/@groove-dev/gui/src/components/pro/upgrade-card.jsx +15 -8
- package/node_modules/@groove-dev/gui/src/components/pro/upgrade-modal.jsx +151 -0
- package/node_modules/@groove-dev/gui/src/components/settings/federation-activity.jsx +98 -0
- package/node_modules/@groove-dev/gui/src/components/settings/federation-panel.jsx +290 -0
- package/node_modules/@groove-dev/gui/src/components/settings/federation-peers.jsx +126 -0
- package/node_modules/@groove-dev/gui/src/components/settings/federation-wizard.jsx +293 -0
- package/node_modules/@groove-dev/gui/src/components/settings/quick-connect.jsx +110 -67
- package/node_modules/@groove-dev/gui/src/components/settings/remote-server-card.jsx +3 -3
- package/node_modules/@groove-dev/gui/src/components/settings/server-detail.jsx +310 -0
- package/node_modules/@groove-dev/gui/src/components/settings/server-dialog.jsx +4 -1
- package/node_modules/@groove-dev/gui/src/components/settings/server-list.jsx +59 -0
- package/node_modules/@groove-dev/gui/src/components/settings/ssh-wizard.jsx +549 -0
- package/node_modules/@groove-dev/gui/src/components/toys/toy-card.jsx +78 -0
- package/node_modules/@groove-dev/gui/src/components/toys/toy-creator.jsx +144 -0
- package/node_modules/@groove-dev/gui/src/components/toys/toy-launcher.jsx +187 -0
- package/node_modules/@groove-dev/gui/src/components/ui/toast.jsx +2 -2
- package/node_modules/@groove-dev/gui/src/lib/electron.js +15 -0
- package/node_modules/@groove-dev/gui/src/lib/format.js +1 -0
- package/node_modules/@groove-dev/gui/src/stores/groove.js +373 -58
- package/node_modules/@groove-dev/gui/src/views/agents.jsx +148 -42
- package/node_modules/@groove-dev/gui/src/views/editor.jsx +92 -2
- package/node_modules/@groove-dev/gui/src/views/federation.jsx +37 -0
- package/node_modules/@groove-dev/gui/src/views/marketplace.jsx +2 -42
- package/node_modules/@groove-dev/gui/src/views/settings.jsx +32 -132
- package/node_modules/@groove-dev/gui/src/views/subscription-panel.jsx +327 -0
- package/node_modules/@groove-dev/gui/src/views/teams.jsx +3 -3
- package/node_modules/@groove-dev/gui/src/views/toys.jsx +162 -0
- package/package.json +1 -1
- package/packages/daemon/src/api.js +587 -68
- package/packages/daemon/src/classifier.js +24 -0
- package/packages/daemon/src/credentials.js +12 -2
- package/packages/daemon/src/federation/ambassador.js +204 -0
- package/packages/daemon/src/federation/connection.js +359 -0
- package/packages/daemon/src/federation/contracts.js +112 -0
- package/packages/daemon/src/federation/whitelist.js +190 -0
- package/packages/daemon/src/federation.js +166 -7
- package/packages/daemon/src/index.js +172 -19
- package/packages/daemon/src/introducer.js +52 -7
- package/packages/daemon/src/journalist.js +46 -1
- package/packages/daemon/src/memory.js +36 -16
- package/packages/daemon/src/process.js +140 -23
- package/packages/daemon/src/providers/base.js +1 -0
- package/packages/daemon/src/providers/claude-code.js +1 -0
- package/packages/daemon/src/providers/codex.js +124 -28
- package/packages/daemon/src/providers/gemini.js +104 -17
- package/packages/daemon/src/providers/index.js +17 -0
- package/packages/daemon/src/registry.js +10 -1
- package/packages/daemon/src/rotator.js +93 -30
- package/packages/daemon/src/skills.js +33 -3
- package/packages/daemon/src/terminal-pty.js +9 -1
- package/packages/daemon/src/tool-executor.js +11 -5
- package/packages/daemon/src/toys.js +69 -0
- package/packages/daemon/src/tunnel-manager.js +24 -5
- package/packages/daemon/templates/toys-catalog.json +242 -0
- package/packages/gui/dist/assets/index-BglPgjlu.js +8607 -0
- package/packages/gui/dist/assets/index-CGcwmmJv.css +1 -0
- package/packages/gui/dist/index.html +3 -2
- package/packages/gui/index.html +1 -0
- package/packages/gui/src/app.css +7 -0
- package/packages/gui/src/app.jsx +37 -10
- package/packages/gui/src/components/agents/agent-chat.jsx +21 -31
- package/packages/gui/src/components/agents/agent-config.jsx +11 -6
- package/packages/gui/src/components/agents/agent-feed.jsx +2 -2
- package/packages/gui/src/components/agents/spawn-wizard.jsx +42 -1
- package/packages/gui/src/components/editor/breadcrumbs.jsx +30 -0
- package/packages/gui/src/components/editor/code-editor.jsx +33 -2
- package/packages/gui/src/components/editor/editor-status-bar.jsx +26 -0
- package/packages/gui/src/components/editor/editor-tabs.jsx +113 -34
- package/packages/gui/src/components/editor/goto-line.jsx +35 -0
- package/packages/gui/src/components/editor/terminal.jsx +12 -6
- package/packages/gui/src/components/layout/activity-bar.jsx +13 -3
- package/packages/gui/src/components/layout/app-shell.jsx +0 -1
- package/packages/gui/src/components/layout/breadcrumb-bar.jsx +165 -47
- package/packages/gui/src/components/layout/command-palette.jsx +6 -2
- package/packages/gui/src/components/layout/terminal-panel.jsx +10 -9
- package/packages/gui/src/components/marketplace/repo-import.jsx +9 -1
- package/packages/gui/src/components/onboarding/provider-card.jsx +134 -0
- package/packages/gui/src/components/onboarding/setup-wizard.jsx +819 -0
- package/packages/gui/src/components/pro/pro-gate.jsx +12 -5
- package/packages/gui/src/components/pro/upgrade-card.jsx +15 -8
- package/packages/gui/src/components/pro/upgrade-modal.jsx +151 -0
- package/packages/gui/src/components/settings/federation-activity.jsx +98 -0
- package/packages/gui/src/components/settings/federation-panel.jsx +290 -0
- package/packages/gui/src/components/settings/federation-peers.jsx +126 -0
- package/packages/gui/src/components/settings/federation-wizard.jsx +293 -0
- package/packages/gui/src/components/settings/quick-connect.jsx +110 -67
- package/packages/gui/src/components/settings/remote-server-card.jsx +3 -3
- package/packages/gui/src/components/settings/server-detail.jsx +310 -0
- package/packages/gui/src/components/settings/server-dialog.jsx +4 -1
- package/packages/gui/src/components/settings/server-list.jsx +59 -0
- package/packages/gui/src/components/settings/ssh-wizard.jsx +549 -0
- package/packages/gui/src/components/toys/toy-card.jsx +78 -0
- package/packages/gui/src/components/toys/toy-creator.jsx +144 -0
- package/packages/gui/src/components/toys/toy-launcher.jsx +187 -0
- package/packages/gui/src/components/ui/toast.jsx +2 -2
- package/packages/gui/src/lib/electron.js +15 -0
- package/packages/gui/src/lib/format.js +1 -0
- package/packages/gui/src/stores/groove.js +373 -58
- package/packages/gui/src/views/agents.jsx +148 -42
- package/packages/gui/src/views/editor.jsx +92 -2
- package/packages/gui/src/views/federation.jsx +37 -0
- package/packages/gui/src/views/marketplace.jsx +2 -42
- package/packages/gui/src/views/settings.jsx +32 -132
- package/packages/gui/src/views/subscription-panel.jsx +327 -0
- package/packages/gui/src/views/teams.jsx +3 -3
- package/packages/gui/src/views/toys.jsx +162 -0
- package/plans/chat-persistence-refactor.md +154 -0
- package/node_modules/@groove-dev/gui/dist/assets/index-BE6lYcd7.css +0 -1
- package/node_modules/@groove-dev/gui/dist/assets/index-zdzOLAZM.js +0 -677
- package/packages/gui/dist/assets/index-BE6lYcd7.css +0 -1
- package/packages/gui/dist/assets/index-zdzOLAZM.js +0 -677
|
@@ -16,6 +16,7 @@ describe('Rotator', () => {
|
|
|
16
16
|
get(id) { return this.agents.find((a) => a.id === id) || null; },
|
|
17
17
|
getAll() { return this.agents; },
|
|
18
18
|
update(id, updates) { const a = this.agents.find((x) => x.id === id); if (a) Object.assign(a, updates); return a; },
|
|
19
|
+
remove(id) { this.agents = this.agents.filter((a) => a.id !== id); return true; },
|
|
19
20
|
agents: [],
|
|
20
21
|
};
|
|
21
22
|
|
|
@@ -36,6 +37,7 @@ describe('Rotator', () => {
|
|
|
36
37
|
adaptive: {
|
|
37
38
|
extractSignals() { return { errorCount: 0, repetitions: 0, scopeViolations: 0, toolCalls: 0, toolFailures: 0, filesWritten: 0 }; },
|
|
38
39
|
recordSession() { return { score: 70, threshold: 0.75, converged: false }; },
|
|
40
|
+
getThreshold() { return 0.65; },
|
|
39
41
|
},
|
|
40
42
|
classifier: {
|
|
41
43
|
agentWindows: {},
|
|
@@ -166,8 +168,185 @@ describe('Rotator', () => {
|
|
|
166
168
|
assert.equal(stats.totalTokensSaved, 8000);
|
|
167
169
|
});
|
|
168
170
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
171
|
+
it('should include cooldown and token ceiling in stats', () => {
|
|
172
|
+
const stats = rotator.getStats();
|
|
173
|
+
assert.equal(stats.cooldownMs, 5 * 60 * 1000);
|
|
174
|
+
assert.equal(stats.tokenCeiling, 5_000_000);
|
|
175
|
+
assert.deepEqual(stats.roleMultipliers, { planner: 10, fullstack: 4, security: 4, analyst: 5 });
|
|
176
|
+
assert.equal(stats.tokenCeilingRotations, 0);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it('should enforce cooldown — skip context rotation within 5 minutes', async () => {
|
|
180
|
+
// Use gemini provider (non-self-managing)
|
|
181
|
+
const agent = {
|
|
182
|
+
id: 'g1', name: 'gemini-1', role: 'backend', status: 'running',
|
|
183
|
+
provider: 'gemini', scope: [], model: null,
|
|
184
|
+
tokensUsed: 1000, contextUsage: 0.70, workingDir: '/tmp',
|
|
185
|
+
lastActivity: new Date(Date.now() - 30_000).toISOString(),
|
|
186
|
+
spawnedAt: new Date(Date.now() - 300_000).toISOString(),
|
|
187
|
+
};
|
|
188
|
+
mockDaemon.registry.agents = [agent];
|
|
189
|
+
mockDaemon.adaptive.getThreshold = () => 0.65;
|
|
190
|
+
|
|
191
|
+
// Manually set a recent rotation time
|
|
192
|
+
rotator.lastRotationTime.set('g1', Date.now() - 60_000); // 1 min ago (within 5-min cooldown)
|
|
193
|
+
|
|
194
|
+
await rotator.check();
|
|
195
|
+
|
|
196
|
+
// Should NOT rotate — cooldown is active
|
|
197
|
+
const history = rotator.getHistory();
|
|
198
|
+
assert.equal(history.length, 0);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it('should allow rotation after cooldown expires', async () => {
|
|
202
|
+
const agent = {
|
|
203
|
+
id: 'g2', name: 'gemini-2', role: 'backend', status: 'running',
|
|
204
|
+
provider: 'gemini', scope: [], model: null,
|
|
205
|
+
tokensUsed: 1000, contextUsage: 0.70, workingDir: '/tmp',
|
|
206
|
+
lastActivity: new Date(Date.now() - 30_000).toISOString(),
|
|
207
|
+
spawnedAt: new Date(Date.now() - 300_000).toISOString(),
|
|
208
|
+
};
|
|
209
|
+
mockDaemon.registry.agents = [agent];
|
|
210
|
+
mockDaemon.adaptive.getThreshold = () => 0.65;
|
|
211
|
+
|
|
212
|
+
// Set rotation time well in the past (6 min ago — cooldown expired)
|
|
213
|
+
rotator.lastRotationTime.set('g2', Date.now() - 6 * 60 * 1000);
|
|
214
|
+
|
|
215
|
+
await rotator.check();
|
|
216
|
+
|
|
217
|
+
// Should rotate — cooldown expired
|
|
218
|
+
const history = rotator.getHistory();
|
|
219
|
+
assert.equal(history.length, 1);
|
|
220
|
+
assert.equal(history[0].reason, 'context_threshold');
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it('should trigger token ceiling rotation for non-self-managing provider', async () => {
|
|
224
|
+
const agent = {
|
|
225
|
+
id: 'g3', name: 'gemini-3', role: 'backend', status: 'running',
|
|
226
|
+
provider: 'gemini', scope: [], model: null,
|
|
227
|
+
tokensUsed: 5_500_000, contextUsage: 0.50, workingDir: '/tmp',
|
|
228
|
+
lastActivity: new Date(Date.now() - 30_000).toISOString(),
|
|
229
|
+
spawnedAt: new Date(Date.now() - 300_000).toISOString(),
|
|
230
|
+
};
|
|
231
|
+
mockDaemon.registry.agents = [agent];
|
|
232
|
+
|
|
233
|
+
await rotator.check();
|
|
234
|
+
|
|
235
|
+
const history = rotator.getHistory();
|
|
236
|
+
assert.equal(history.length, 1);
|
|
237
|
+
assert.equal(history[0].reason, 'token_ceiling');
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it('should respect role multipliers — planner gets 50M ceiling', async () => {
|
|
241
|
+
const agent = {
|
|
242
|
+
id: 'g4', name: 'gemini-planner', role: 'planner', status: 'running',
|
|
243
|
+
provider: 'gemini', scope: [], model: null,
|
|
244
|
+
tokensUsed: 8_000_000, contextUsage: 0.50, workingDir: '/tmp',
|
|
245
|
+
lastActivity: new Date(Date.now() - 30_000).toISOString(),
|
|
246
|
+
spawnedAt: new Date(Date.now() - 300_000).toISOString(),
|
|
247
|
+
};
|
|
248
|
+
mockDaemon.registry.agents = [agent];
|
|
249
|
+
|
|
250
|
+
await rotator.check();
|
|
251
|
+
|
|
252
|
+
// 8M < 50M (planner ceiling = 5M * 10), should NOT token-ceiling rotate
|
|
253
|
+
const history = rotator.getHistory();
|
|
254
|
+
const tokenCeilingRotations = history.filter((r) => r.reason === 'token_ceiling');
|
|
255
|
+
assert.equal(tokenCeilingRotations.length, 0);
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
it('should token ceiling rotate planner at 50M+', async () => {
|
|
259
|
+
const agent = {
|
|
260
|
+
id: 'g5', name: 'gemini-planner-2', role: 'planner', status: 'running',
|
|
261
|
+
provider: 'gemini', scope: [], model: null,
|
|
262
|
+
tokensUsed: 55_000_000, contextUsage: 0.50, workingDir: '/tmp',
|
|
263
|
+
lastActivity: new Date(Date.now() - 30_000).toISOString(),
|
|
264
|
+
spawnedAt: new Date(Date.now() - 300_000).toISOString(),
|
|
265
|
+
};
|
|
266
|
+
mockDaemon.registry.agents = [agent];
|
|
267
|
+
|
|
268
|
+
await rotator.check();
|
|
269
|
+
|
|
270
|
+
const history = rotator.getHistory();
|
|
271
|
+
assert.equal(history.length, 1);
|
|
272
|
+
assert.equal(history[0].reason, 'token_ceiling');
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
it('should bypass cooldown for hard ceiling', async () => {
|
|
276
|
+
const agent = {
|
|
277
|
+
id: 'g6', name: 'gemini-hot', role: 'backend', status: 'running',
|
|
278
|
+
provider: 'gemini', scope: [], model: null,
|
|
279
|
+
tokensUsed: 1000, contextUsage: 0.85, workingDir: '/tmp',
|
|
280
|
+
lastActivity: new Date(Date.now() - 30_000).toISOString(),
|
|
281
|
+
spawnedAt: new Date(Date.now() - 300_000).toISOString(),
|
|
282
|
+
};
|
|
283
|
+
mockDaemon.registry.agents = [agent];
|
|
284
|
+
|
|
285
|
+
// Set recent cooldown
|
|
286
|
+
rotator.lastRotationTime.set('g6', Date.now() - 60_000); // 1 min ago
|
|
287
|
+
|
|
288
|
+
await rotator.check();
|
|
289
|
+
|
|
290
|
+
// Hard ceiling (85% >= 80%) should bypass cooldown
|
|
291
|
+
const history = rotator.getHistory();
|
|
292
|
+
assert.equal(history.length, 1);
|
|
293
|
+
assert.equal(history[0].reason, 'hard_ceiling');
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
it('should bypass cooldown for token ceiling', async () => {
|
|
297
|
+
const agent = {
|
|
298
|
+
id: 'g7', name: 'gemini-tokens', role: 'backend', status: 'running',
|
|
299
|
+
provider: 'gemini', scope: [], model: null,
|
|
300
|
+
tokensUsed: 6_000_000, contextUsage: 0.50, workingDir: '/tmp',
|
|
301
|
+
lastActivity: new Date(Date.now() - 30_000).toISOString(),
|
|
302
|
+
spawnedAt: new Date(Date.now() - 300_000).toISOString(),
|
|
303
|
+
};
|
|
304
|
+
mockDaemon.registry.agents = [agent];
|
|
305
|
+
|
|
306
|
+
// Set recent cooldown
|
|
307
|
+
rotator.lastRotationTime.set('g7', Date.now() - 60_000); // 1 min ago
|
|
308
|
+
|
|
309
|
+
await rotator.check();
|
|
310
|
+
|
|
311
|
+
// Token ceiling (6M >= 5M) should bypass cooldown
|
|
312
|
+
const history = rotator.getHistory();
|
|
313
|
+
assert.equal(history.length, 1);
|
|
314
|
+
assert.equal(history[0].reason, 'token_ceiling');
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
it('should NOT apply token ceiling to self-managing providers (Claude Code)', async () => {
|
|
318
|
+
const agent = {
|
|
319
|
+
id: 'c1', name: 'claude-heavy', role: 'backend', status: 'running',
|
|
320
|
+
provider: 'claude-code', scope: [], model: null,
|
|
321
|
+
tokensUsed: 10_000_000, contextUsage: 0.50, workingDir: '/tmp',
|
|
322
|
+
lastActivity: new Date(Date.now() - 30_000).toISOString(),
|
|
323
|
+
spawnedAt: new Date(Date.now() - 300_000).toISOString(),
|
|
324
|
+
};
|
|
325
|
+
mockDaemon.registry.agents = [agent];
|
|
326
|
+
|
|
327
|
+
await rotator.check();
|
|
328
|
+
|
|
329
|
+
// Claude Code self-manages — token ceiling should NOT fire
|
|
330
|
+
const history = rotator.getHistory();
|
|
331
|
+
const tokenCeilingRotations = history.filter((r) => r.reason === 'token_ceiling');
|
|
332
|
+
assert.equal(tokenCeilingRotations.length, 0);
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
it('should record lastRotationTime after successful rotation', async () => {
|
|
336
|
+
const agent = {
|
|
337
|
+
id: 'a3', name: 'backend-3', role: 'backend',
|
|
338
|
+
provider: 'claude-code', scope: [], model: null,
|
|
339
|
+
tokensUsed: 3000, contextUsage: 0.9, workingDir: '/tmp',
|
|
340
|
+
};
|
|
341
|
+
mockDaemon.registry.agents = [agent];
|
|
342
|
+
|
|
343
|
+
assert.equal(rotator.lastRotationTime.has('a3'), false);
|
|
344
|
+
|
|
345
|
+
const newAgent = await rotator.rotate('a3');
|
|
346
|
+
|
|
347
|
+
assert.equal(rotator.lastRotationTime.has('a3'), false);
|
|
348
|
+
assert.equal(rotator.lastRotationTime.has(newAgent.id), true);
|
|
349
|
+
const elapsed = Date.now() - rotator.lastRotationTime.get(newAgent.id);
|
|
350
|
+
assert.ok(elapsed < 1000); // Should be very recent
|
|
351
|
+
});
|
|
173
352
|
});
|