Skip to content

Commit 2b47d0f

Browse files
ersinkocclaude
andcommitted
✅ test(claw-manager): add 7 missing unit tests + document analytics
New tests (29 → 36): - denyEscalation: resume with inbox message, non-existent claw, non-escalated - stop conditions: on_error (first failure), idle:N (consecutive idle cycles) - config hot-reload: updateClawConfig in-memory, no-op for unknown CLAUDE.md: add AnalyticsPage entry, bump test count to 26,750+ Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent b94d1f9 commit 2b47d0f

2 files changed

Lines changed: 84 additions & 1 deletion

File tree

CLAUDE.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ packages/
2222
- **User Extensions**: Native tool bundles (JS code, triggers, services) in `packages/gateway/src/services/extension-service.ts`. DB table: `user_extensions`. API: `/extensions`
2323
- **Skills (AgentSkills.io)**: Open standard SKILL.md format for agent instructions. Parser: `packages/gateway/src/services/agentskills-parser.ts`. Format field: `'ownpilot' | 'agentskills'`
2424
- **Edge/IoT**: MQTT broker (Mosquitto) integration for edge device management. Types: `packages/core/src/edge/`. Service: `packages/gateway/src/services/edge-service.ts`. Routes: `/api/v1/edge`
25-
- **Test framework**: Vitest across all packages. 26,700+ tests total (gateway: 16,500+; core: 9,714; cli: 340; ui: 141). 550 test files
25+
- **Test framework**: Vitest across all packages. 26,750+ tests total (gateway: 16,550+; core: 9,714; cli: 340; ui: 141). 550 test files
26+
- **Analytics Page**: `packages/ui/src/pages/AnalyticsPage.tsx` — recharts-powered dashboard at `/analytics`. 6 KPI cards, cost/token area+bar charts, provider donut, agent distribution bar, claw mode/state donuts, task/habit radial gauges, daily requests line chart, claw runtime summary grid, personal data overview. Period toggle (7d/30d). Uses `costsApi.usage()`, `costsApi.getBreakdown()`, `clawsApi.stats()`, `summaryApi.get()` + agent list endpoints
2627
- **Autonomous Agent Runners**: Shared utilities in `packages/gateway/src/services/agent-runner-utils.ts``createConfiguredAgent()`, `registerAllToolSources()`, `resolveProviderAndModel()`, `executeAgentPipeline()`, `calculateExecutionCost()`, `createToolCallCollector()`, `resolveToolFilter()`, `createCancellationPromise()`
2728
- **Habit Tracking**: 8 AI tools in `packages/gateway/src/tools/habit-tools.ts`, DB repo in `db/repositories/habits.ts` (645 lines), REST API in `routes/productivity.ts`, HabitsPage UI with streak heatmap
2829
- **Utilities**: `TTLCache<K,V>` in `packages/gateway/src/utils/ttl-cache.ts` — generic cache with auto-prune. `chat-post-processor.ts` in `assistant/` — extracted from conversation-service

packages/gateway/src/services/claw-manager.test.ts

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,41 @@ describe('ClawManager', () => {
369369
const approved = await manager.approveEscalation('claw-1');
370370
expect(approved).toBe(false);
371371
});
372+
373+
it('should deny escalation and resume with inbox message', async () => {
374+
const repo = setupRepo(makeConfig());
375+
await manager.startClaw('claw-1', 'user-1');
376+
377+
await manager.requestEscalation('claw-1', {
378+
id: 'esc-2',
379+
type: 'tool_access',
380+
reason: 'Need shell',
381+
requestedAt: new Date(),
382+
});
383+
expect(manager.getSession('claw-1')?.state).toBe('escalation_pending');
384+
385+
const denied = await manager.denyEscalation('claw-1', 'Too risky');
386+
expect(denied).toBe(true);
387+
expect(manager.getSession('claw-1')?.state).toBe('running');
388+
expect(manager.getSession('claw-1')?.pendingEscalation).toBeNull();
389+
expect(manager.getSession('claw-1')?.inbox).toContainEqual(
390+
expect.stringContaining('ESCALATION_DENIED')
391+
);
392+
expect(repo.appendToInbox).toHaveBeenCalled();
393+
});
394+
395+
it('should return false when denying non-existent claw', async () => {
396+
const denied = await manager.denyEscalation('claw-99');
397+
expect(denied).toBe(false);
398+
});
399+
400+
it('should return false when denying non-escalated claw', async () => {
401+
setupRepo(makeConfig());
402+
await manager.startClaw('claw-1', 'user-1');
403+
404+
const denied = await manager.denyEscalation('claw-1');
405+
expect(denied).toBe(false);
406+
});
372407
});
373408

374409
describe('stop conditions', () => {
@@ -398,6 +433,53 @@ describe('ClawManager', () => {
398433

399434
expect(manager.getSession('claw-1')).toBeNull();
400435
});
436+
437+
it('should stop on on_error condition when cycle fails', async () => {
438+
const config = makeConfig({ mode: 'continuous', stopCondition: 'on_error' });
439+
setupRepo(config);
440+
mockRunCycle.mockResolvedValue(makeCycleResult({ success: false, error: 'Something broke' }));
441+
442+
await manager.startClaw('claw-1', 'user-1');
443+
await vi.advanceTimersByTimeAsync(600);
444+
445+
// on_error stops after first failure
446+
expect(manager.getSession('claw-1')).toBeNull();
447+
});
448+
449+
it('should stop on idle:N condition after N idle cycles', async () => {
450+
const config = makeConfig({ mode: 'continuous', stopCondition: 'idle:2' });
451+
setupRepo(config);
452+
// Cycle returns 0 tool calls (idle)
453+
mockRunCycle.mockResolvedValue(makeCycleResult({ toolCalls: [] }));
454+
455+
await manager.startClaw('claw-1', 'user-1');
456+
// First idle cycle
457+
await vi.advanceTimersByTimeAsync(600);
458+
expect(manager.getSession('claw-1')).not.toBeNull();
459+
// Second idle cycle → stop
460+
await vi.advanceTimersByTimeAsync(5100);
461+
expect(manager.getSession('claw-1')).toBeNull();
462+
});
463+
});
464+
465+
describe('config hot-reload', () => {
466+
it('should update in-memory config via updateClawConfig', async () => {
467+
const config = makeConfig({ mode: 'continuous' });
468+
setupRepo(config);
469+
await manager.startClaw('claw-1', 'user-1');
470+
471+
const updated = { ...config, mode: 'interval' as const, intervalMs: 60_000 };
472+
manager.updateClawConfig('claw-1', updated);
473+
474+
expect(manager.getSession('claw-1')?.config.mode).toBe('interval');
475+
expect(manager.getSession('claw-1')?.config.intervalMs).toBe(60_000);
476+
});
477+
478+
it('should no-op for unknown claw', () => {
479+
const config = makeConfig();
480+
// Should not throw
481+
manager.updateClawConfig('claw-99', config);
482+
});
401483
});
402484

403485
describe('resource limits', () => {

0 commit comments

Comments
 (0)