skimpyclaw 0.3.14 → 0.4.0
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 +47 -37
- package/dist/__tests__/adapter-types.test.d.ts +4 -0
- package/dist/__tests__/adapter-types.test.js +63 -0
- package/dist/__tests__/anthropic-adapter.test.d.ts +4 -0
- package/dist/__tests__/anthropic-adapter.test.js +264 -0
- package/dist/__tests__/api.test.js +0 -1
- package/dist/__tests__/cli.integration.test.js +2 -4
- package/dist/__tests__/cli.test.js +0 -1
- package/dist/__tests__/code-agents-notifications.test.js +137 -0
- package/dist/__tests__/code-agents-parser.test.js +19 -1
- package/dist/__tests__/code-agents-preflight.test.js +3 -28
- package/dist/__tests__/code-agents-utils.test.js +34 -9
- package/dist/__tests__/code-agents-worktrees.test.js +116 -0
- package/dist/__tests__/codex-adapter.test.js +184 -0
- package/dist/__tests__/codex-auth.test.js +66 -0
- package/dist/__tests__/codex-provider-gating.test.js +35 -0
- package/dist/__tests__/codex-unified-loop.test.js +111 -0
- package/dist/__tests__/config-security.test.js +127 -0
- package/dist/__tests__/config.test.js +23 -0
- package/dist/__tests__/context-manager.test.js +243 -164
- package/dist/__tests__/cron-run.test.js +250 -0
- package/dist/__tests__/cron.test.js +12 -38
- package/dist/__tests__/digests.test.js +67 -0
- package/dist/__tests__/discord-attachments.test.js +211 -0
- package/dist/__tests__/discord-docs.test.d.ts +1 -0
- package/dist/__tests__/discord-docs.test.js +27 -0
- package/dist/__tests__/discord-thread-agents.test.d.ts +1 -0
- package/dist/__tests__/discord-thread-agents.test.js +115 -0
- package/dist/__tests__/discord-thread-context.test.d.ts +1 -0
- package/dist/__tests__/discord-thread-context.test.js +42 -0
- package/dist/__tests__/doctor.formatters.test.js +4 -4
- package/dist/__tests__/doctor.index.test.js +1 -1
- package/dist/__tests__/doctor.runner.test.js +3 -15
- package/dist/__tests__/env-sanitizer.test.d.ts +1 -0
- package/dist/__tests__/env-sanitizer.test.js +45 -0
- package/dist/__tests__/exec-approval.test.js +61 -0
- package/dist/__tests__/fetch-tool.test.d.ts +1 -0
- package/dist/__tests__/fetch-tool.test.js +85 -0
- package/dist/__tests__/gateway-status-auth.test.d.ts +1 -0
- package/dist/__tests__/gateway-status-auth.test.js +72 -0
- package/dist/__tests__/heartbeat.test.js +3 -3
- package/dist/__tests__/interactive-sessions.test.d.ts +1 -0
- package/dist/__tests__/interactive-sessions.test.js +96 -0
- package/dist/__tests__/langfuse.test.js +6 -18
- package/dist/__tests__/model-selection.test.js +3 -4
- package/dist/__tests__/providers-init.test.js +2 -8
- package/dist/__tests__/providers-routing.test.js +1 -1
- package/dist/__tests__/providers-utils.test.js +13 -3
- package/dist/__tests__/sessions.test.js +14 -10
- package/dist/__tests__/setup.test.js +12 -29
- package/dist/__tests__/skills.test.js +10 -7
- package/dist/__tests__/stream-formatter.test.d.ts +1 -0
- package/dist/__tests__/stream-formatter.test.js +114 -0
- package/dist/__tests__/token-efficiency.test.js +131 -15
- package/dist/__tests__/tool-loop.test.d.ts +4 -0
- package/dist/__tests__/tool-loop.test.js +505 -0
- package/dist/__tests__/tools.test.js +101 -276
- package/dist/__tests__/utils.test.d.ts +1 -0
- package/dist/__tests__/utils.test.js +14 -0
- package/dist/__tests__/voice.test.js +21 -0
- package/dist/agent.js +35 -4
- package/dist/api.js +113 -37
- package/dist/channels/discord/attachments.d.ts +50 -0
- package/dist/channels/discord/attachments.js +137 -0
- package/dist/channels/discord/delegation.d.ts +5 -0
- package/dist/channels/discord/delegation.js +136 -0
- package/dist/channels/discord/handlers.js +694 -7
- package/dist/channels/discord/index.d.ts +16 -1
- package/dist/channels/discord/index.js +64 -1
- package/dist/channels/discord/thread-agents.d.ts +54 -0
- package/dist/channels/discord/thread-agents.js +323 -0
- package/dist/channels/discord/threads.d.ts +58 -0
- package/dist/channels/discord/threads.js +192 -0
- package/dist/channels/discord/types.js +4 -2
- package/dist/channels/discord/utils.d.ts +16 -0
- package/dist/channels/discord/utils.js +86 -6
- package/dist/channels/telegram/index.d.ts +1 -1
- package/dist/channels/telegram/types.js +1 -1
- package/dist/channels/telegram/utils.js +9 -3
- package/dist/channels.d.ts +1 -1
- package/dist/cli.js +20 -400
- package/dist/code-agents/executor.d.ts +1 -1
- package/dist/code-agents/executor.js +101 -45
- package/dist/code-agents/index.d.ts +2 -7
- package/dist/code-agents/index.js +111 -80
- package/dist/code-agents/interactive-resume.d.ts +6 -0
- package/dist/code-agents/interactive-resume.js +98 -0
- package/dist/code-agents/interactive-sessions.d.ts +20 -0
- package/dist/code-agents/interactive-sessions.js +132 -0
- package/dist/code-agents/parser.js +5 -1
- package/dist/code-agents/registry.d.ts +7 -1
- package/dist/code-agents/registry.js +11 -23
- package/dist/code-agents/stream-formatter.d.ts +8 -0
- package/dist/code-agents/stream-formatter.js +92 -0
- package/dist/code-agents/types.d.ts +16 -24
- package/dist/code-agents/utils.d.ts +35 -11
- package/dist/code-agents/utils.js +349 -95
- package/dist/code-agents/worktrees.d.ts +37 -0
- package/dist/code-agents/worktrees.js +116 -0
- package/dist/config.d.ts +2 -4
- package/dist/config.js +123 -23
- package/dist/cron.d.ts +1 -6
- package/dist/cron.js +175 -82
- package/dist/dashboard/assets/index-B345aOO-.js +65 -0
- package/dist/dashboard/assets/index-ZWK4dalJ.css +1 -0
- package/dist/dashboard/index.html +2 -2
- package/dist/digests.d.ts +1 -0
- package/dist/digests.js +132 -42
- package/dist/doctor/checks.d.ts +0 -3
- package/dist/doctor/checks.js +1 -108
- package/dist/doctor/runner.js +1 -4
- package/dist/env-sanitizer.d.ts +2 -0
- package/dist/env-sanitizer.js +61 -0
- package/dist/exec-approval.d.ts +11 -1
- package/dist/exec-approval.js +17 -4
- package/dist/gateway.d.ts +3 -1
- package/dist/gateway.js +17 -7
- package/dist/heartbeat.js +1 -6
- package/dist/langfuse.js +3 -29
- package/dist/model-selection.js +3 -1
- package/dist/providers/adapter.d.ts +118 -0
- package/dist/providers/adapter.js +6 -0
- package/dist/providers/adapters/anthropic-adapter.d.ts +22 -0
- package/dist/providers/adapters/anthropic-adapter.js +204 -0
- package/dist/providers/adapters/codex-adapter.d.ts +26 -0
- package/dist/providers/adapters/codex-adapter.js +203 -0
- package/dist/providers/anthropic.d.ts +1 -0
- package/dist/providers/anthropic.js +10 -272
- package/dist/providers/codex.d.ts +21 -0
- package/dist/providers/codex.js +149 -330
- package/dist/providers/content.d.ts +1 -1
- package/dist/providers/content.js +2 -2
- package/dist/providers/context-manager.d.ts +18 -6
- package/dist/providers/context-manager.js +199 -223
- package/dist/providers/index.d.ts +9 -1
- package/dist/providers/index.js +73 -64
- package/dist/providers/loop-utils.d.ts +20 -0
- package/dist/providers/loop-utils.js +30 -0
- package/dist/providers/tool-loop.d.ts +12 -0
- package/dist/providers/tool-loop.js +251 -0
- package/dist/providers/utils.d.ts +19 -3
- package/dist/providers/utils.js +100 -29
- package/dist/secure-store.d.ts +8 -0
- package/dist/secure-store.js +80 -0
- package/dist/service.js +3 -28
- package/dist/sessions.d.ts +3 -0
- package/dist/sessions.js +147 -18
- package/dist/setup-templates.js +13 -25
- package/dist/setup.d.ts +10 -6
- package/dist/setup.js +84 -292
- package/dist/skills.js +3 -11
- package/dist/tools/agent-delegation.d.ts +19 -0
- package/dist/tools/agent-delegation.js +49 -0
- package/dist/tools/bash-tool.js +89 -34
- package/dist/tools/definitions.d.ts +199 -302
- package/dist/tools/definitions.js +70 -123
- package/dist/tools/execute-context.d.ts +13 -4
- package/dist/tools/fetch-tool.js +109 -13
- package/dist/tools/file-tools.js +7 -1
- package/dist/tools.d.ts +7 -7
- package/dist/tools.js +133 -151
- package/dist/types.d.ts +37 -30
- package/dist/utils.js +4 -6
- package/dist/voice.d.ts +1 -1
- package/dist/voice.js +17 -4
- package/package.json +33 -23
- package/templates/TOOLS.md +0 -27
- package/dist/__tests__/audit.test.js +0 -122
- package/dist/__tests__/code-agents-orchestrator.test.js +0 -216
- package/dist/__tests__/code-agents-sandbox.test.js +0 -163
- package/dist/__tests__/orchestrator.test.js +0 -425
- package/dist/__tests__/sandbox-bridge.test.js +0 -116
- package/dist/__tests__/sandbox-manager.test.js +0 -144
- package/dist/__tests__/sandbox-mount-security.test.js +0 -139
- package/dist/__tests__/sandbox-runtime.test.js +0 -176
- package/dist/__tests__/subagent.test.js +0 -240
- package/dist/__tests__/telegram.test.js +0 -42
- package/dist/code-agents/orchestrator.d.ts +0 -29
- package/dist/code-agents/orchestrator.js +0 -694
- package/dist/code-agents/worktree.d.ts +0 -40
- package/dist/code-agents/worktree.js +0 -215
- package/dist/dashboard/assets/index-BoTHPby4.js +0 -65
- package/dist/dashboard/assets/index-D4mufvBg.css +0 -1
- package/dist/dashboard.d.ts +0 -8
- package/dist/dashboard.js +0 -4071
- package/dist/discord.d.ts +0 -8
- package/dist/discord.js +0 -792
- package/dist/mcp-context-a8c.d.ts +0 -13
- package/dist/mcp-context-a8c.js +0 -34
- package/dist/orchestrator.d.ts +0 -15
- package/dist/orchestrator.js +0 -676
- package/dist/providers/openai.d.ts +0 -10
- package/dist/providers/openai.js +0 -355
- package/dist/sandbox/bridge.d.ts +0 -5
- package/dist/sandbox/bridge.js +0 -63
- package/dist/sandbox/index.d.ts +0 -5
- package/dist/sandbox/index.js +0 -4
- package/dist/sandbox/manager.d.ts +0 -7
- package/dist/sandbox/manager.js +0 -100
- package/dist/sandbox/mount-security.d.ts +0 -12
- package/dist/sandbox/mount-security.js +0 -122
- package/dist/sandbox/runtime.d.ts +0 -39
- package/dist/sandbox/runtime.js +0 -192
- package/dist/sandbox-utils.d.ts +0 -6
- package/dist/sandbox-utils.js +0 -36
- package/dist/subagent.d.ts +0 -19
- package/dist/subagent.js +0 -407
- package/dist/telegram.d.ts +0 -2
- package/dist/telegram.js +0 -11
- package/dist/tools/browser-tool.d.ts +0 -3
- package/dist/tools/browser-tool.js +0 -266
- package/sandbox/Dockerfile +0 -40
- /package/dist/__tests__/{audit.test.d.ts → code-agents-notifications.test.d.ts} +0 -0
- /package/dist/__tests__/{code-agents-orchestrator.test.d.ts → code-agents-worktrees.test.d.ts} +0 -0
- /package/dist/__tests__/{code-agents-sandbox.test.d.ts → codex-adapter.test.d.ts} +0 -0
- /package/dist/__tests__/{orchestrator.test.d.ts → codex-auth.test.d.ts} +0 -0
- /package/dist/__tests__/{sandbox-bridge.test.d.ts → codex-provider-gating.test.d.ts} +0 -0
- /package/dist/__tests__/{sandbox-manager.test.d.ts → codex-unified-loop.test.d.ts} +0 -0
- /package/dist/__tests__/{sandbox-mount-security.test.d.ts → config-security.test.d.ts} +0 -0
- /package/dist/__tests__/{sandbox-runtime.test.d.ts → cron-run.test.d.ts} +0 -0
- /package/dist/__tests__/{subagent.test.d.ts → digests.test.d.ts} +0 -0
- /package/dist/__tests__/{telegram.test.d.ts → discord-attachments.test.d.ts} +0 -0
|
@@ -1,425 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
2
|
-
import { existsSync, appendFileSync, readFileSync } from 'fs';
|
|
3
|
-
import { initOrchestrationSystem, getActiveOrchestrations, getOrchestration, listRecentOrchestrations, setCurrentPlanningSession, getCurrentPlanningSession, executeOrchestratorTool, loadSession, resetForTesting, } from '../orchestrator.js';
|
|
4
|
-
// Mock runAgentTurn
|
|
5
|
-
vi.mock('../agent.js', () => ({
|
|
6
|
-
runAgentTurn: vi.fn().mockResolvedValue('mock agent response'),
|
|
7
|
-
buildSystemPrompt: vi.fn().mockReturnValue('mock system prompt'),
|
|
8
|
-
chatWithTools: vi.fn().mockResolvedValue({ response: 'mock response', toolCalls: [] }),
|
|
9
|
-
resolveModel: vi.fn((model) => model),
|
|
10
|
-
buildSystemParam: vi.fn((content) => content),
|
|
11
|
-
}));
|
|
12
|
-
// Mock getCurrentModel
|
|
13
|
-
vi.mock('../gateway.js', () => ({
|
|
14
|
-
getCurrentModel: vi.fn().mockReturnValue('anthropic/claude-sonnet-4-5'),
|
|
15
|
-
}));
|
|
16
|
-
// Mock subagent
|
|
17
|
-
vi.mock('../subagent.js', () => ({
|
|
18
|
-
ensureAgentSetup: vi.fn(),
|
|
19
|
-
}));
|
|
20
|
-
// Mock tools
|
|
21
|
-
vi.mock('../tools.js', () => ({
|
|
22
|
-
getToolDefinitions: vi.fn().mockResolvedValue([]),
|
|
23
|
-
executeTool: vi.fn().mockResolvedValue('mock tool result'),
|
|
24
|
-
}));
|
|
25
|
-
// Mock fs operations
|
|
26
|
-
vi.mock('fs', async () => {
|
|
27
|
-
const actual = await vi.importActual('fs');
|
|
28
|
-
return {
|
|
29
|
-
...actual,
|
|
30
|
-
existsSync: vi.fn().mockReturnValue(true),
|
|
31
|
-
mkdirSync: vi.fn(),
|
|
32
|
-
writeFileSync: vi.fn(),
|
|
33
|
-
appendFileSync: vi.fn(),
|
|
34
|
-
readFileSync: vi.fn().mockReturnValue(''),
|
|
35
|
-
};
|
|
36
|
-
});
|
|
37
|
-
function createMockConfig() {
|
|
38
|
-
return {
|
|
39
|
-
gateway: { port: 18790, mode: 'local' },
|
|
40
|
-
agents: {
|
|
41
|
-
default: 'main',
|
|
42
|
-
list: {
|
|
43
|
-
main: {
|
|
44
|
-
identity: { name: 'Test', emoji: '🦞' },
|
|
45
|
-
model: 'anthropic/claude-sonnet-4-5',
|
|
46
|
-
},
|
|
47
|
-
general: {
|
|
48
|
-
identity: { name: 'General Agent', emoji: '🦞' },
|
|
49
|
-
model: 'anthropic/claude-sonnet-4-5',
|
|
50
|
-
},
|
|
51
|
-
},
|
|
52
|
-
},
|
|
53
|
-
models: {
|
|
54
|
-
providers: {
|
|
55
|
-
anthropic: { apiKey: 'test-key' },
|
|
56
|
-
},
|
|
57
|
-
aliases: {},
|
|
58
|
-
},
|
|
59
|
-
channels: {
|
|
60
|
-
telegram: {
|
|
61
|
-
enabled: false,
|
|
62
|
-
token: '',
|
|
63
|
-
allowFrom: [],
|
|
64
|
-
},
|
|
65
|
-
},
|
|
66
|
-
cron: { jobs: [] },
|
|
67
|
-
heartbeat: {
|
|
68
|
-
intervalMs: 300000,
|
|
69
|
-
prompt: 'test',
|
|
70
|
-
},
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
function createMockSession(overrides) {
|
|
74
|
-
return {
|
|
75
|
-
id: 'orch_test_123',
|
|
76
|
-
prompt: 'Test prompt',
|
|
77
|
-
status: 'planning',
|
|
78
|
-
model: 'anthropic/claude-sonnet-4-5',
|
|
79
|
-
chatId: 456,
|
|
80
|
-
tasks: [],
|
|
81
|
-
workers: [],
|
|
82
|
-
messages: [],
|
|
83
|
-
createdAt: new Date().toISOString(),
|
|
84
|
-
...overrides,
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
describe('orchestrator', () => {
|
|
88
|
-
let deliveredMessages;
|
|
89
|
-
beforeEach(() => {
|
|
90
|
-
resetForTesting();
|
|
91
|
-
deliveredMessages = [];
|
|
92
|
-
initOrchestrationSystem(async (chatId, message) => {
|
|
93
|
-
deliveredMessages.push({ chatId, message });
|
|
94
|
-
});
|
|
95
|
-
vi.mocked(existsSync).mockReturnValue(true);
|
|
96
|
-
vi.mocked(appendFileSync).mockReset();
|
|
97
|
-
vi.mocked(readFileSync).mockReturnValue('');
|
|
98
|
-
});
|
|
99
|
-
describe('initOrchestrationSystem', () => {
|
|
100
|
-
it('initializes without error', () => {
|
|
101
|
-
expect(() => initOrchestrationSystem(async () => { })).not.toThrow();
|
|
102
|
-
});
|
|
103
|
-
});
|
|
104
|
-
describe('executeOrchestratorTool', () => {
|
|
105
|
-
it('CreateTask creates a task on the planning session', () => {
|
|
106
|
-
const session = createMockSession();
|
|
107
|
-
setCurrentPlanningSession(session);
|
|
108
|
-
const result = executeOrchestratorTool('CreateTask', {
|
|
109
|
-
title: 'Write tests',
|
|
110
|
-
description: 'Write unit tests for the orchestrator',
|
|
111
|
-
agentType: 'coding',
|
|
112
|
-
});
|
|
113
|
-
expect(result).toContain('ot1');
|
|
114
|
-
expect(result).toContain('Write tests');
|
|
115
|
-
expect(session.tasks).toHaveLength(1);
|
|
116
|
-
expect(session.tasks[0].id).toBe('ot1');
|
|
117
|
-
expect(session.tasks[0].title).toBe('Write tests');
|
|
118
|
-
expect(session.tasks[0].description).toBe('Write unit tests for the orchestrator');
|
|
119
|
-
expect(session.tasks[0].status).toBe('pending');
|
|
120
|
-
expect(session.tasks[0].dependsOn).toEqual([]);
|
|
121
|
-
setCurrentPlanningSession(null);
|
|
122
|
-
});
|
|
123
|
-
it('CreateTask with dependencies', () => {
|
|
124
|
-
const session = createMockSession();
|
|
125
|
-
setCurrentPlanningSession(session);
|
|
126
|
-
executeOrchestratorTool('CreateTask', {
|
|
127
|
-
title: 'Task 1',
|
|
128
|
-
description: 'First task',
|
|
129
|
-
agentType: 'research',
|
|
130
|
-
});
|
|
131
|
-
const result = executeOrchestratorTool('CreateTask', {
|
|
132
|
-
title: 'Task 2',
|
|
133
|
-
description: 'Depends on task 1',
|
|
134
|
-
agentType: 'coding',
|
|
135
|
-
dependsOn: ['ot1'],
|
|
136
|
-
});
|
|
137
|
-
expect(result).toContain('ot2');
|
|
138
|
-
expect(session.tasks).toHaveLength(2);
|
|
139
|
-
expect(session.tasks[1].dependsOn).toEqual(['ot1']);
|
|
140
|
-
setCurrentPlanningSession(null);
|
|
141
|
-
});
|
|
142
|
-
it('CreateTask increments IDs', () => {
|
|
143
|
-
const session = createMockSession();
|
|
144
|
-
setCurrentPlanningSession(session);
|
|
145
|
-
executeOrchestratorTool('CreateTask', {
|
|
146
|
-
title: 'A', description: 'a', agentType: 'general',
|
|
147
|
-
});
|
|
148
|
-
executeOrchestratorTool('CreateTask', {
|
|
149
|
-
title: 'B', description: 'b', agentType: 'general',
|
|
150
|
-
});
|
|
151
|
-
executeOrchestratorTool('CreateTask', {
|
|
152
|
-
title: 'C', description: 'c', agentType: 'general',
|
|
153
|
-
});
|
|
154
|
-
expect(session.tasks.map(t => t.id)).toEqual(['ot1', 'ot2', 'ot3']);
|
|
155
|
-
setCurrentPlanningSession(null);
|
|
156
|
-
});
|
|
157
|
-
it('CreateTask rejects invalid agentType', () => {
|
|
158
|
-
const session = createMockSession();
|
|
159
|
-
setCurrentPlanningSession(session);
|
|
160
|
-
const result = executeOrchestratorTool('CreateTask', {
|
|
161
|
-
title: 'Bad', description: 'bad', agentType: 'invalid',
|
|
162
|
-
});
|
|
163
|
-
expect(result).toContain('Error');
|
|
164
|
-
expect(result).toContain('Invalid agentType');
|
|
165
|
-
expect(session.tasks).toHaveLength(0);
|
|
166
|
-
setCurrentPlanningSession(null);
|
|
167
|
-
});
|
|
168
|
-
it('CreateTask rejects missing fields', () => {
|
|
169
|
-
const session = createMockSession();
|
|
170
|
-
setCurrentPlanningSession(session);
|
|
171
|
-
const result = executeOrchestratorTool('CreateTask', {
|
|
172
|
-
title: 'No desc',
|
|
173
|
-
agentType: 'general',
|
|
174
|
-
});
|
|
175
|
-
expect(result).toContain('Error');
|
|
176
|
-
expect(session.tasks).toHaveLength(0);
|
|
177
|
-
setCurrentPlanningSession(null);
|
|
178
|
-
});
|
|
179
|
-
it('CreateTask errors without planning session', () => {
|
|
180
|
-
const result = executeOrchestratorTool('CreateTask', {
|
|
181
|
-
title: 'Test', description: 'test', agentType: 'general',
|
|
182
|
-
});
|
|
183
|
-
expect(result).toContain('Error');
|
|
184
|
-
expect(result).toContain('No active planning session');
|
|
185
|
-
});
|
|
186
|
-
it('FinishPlanning returns success', () => {
|
|
187
|
-
const session = createMockSession();
|
|
188
|
-
session.tasks.push({
|
|
189
|
-
id: 'ot1', sessionId: session.id, title: 'T',
|
|
190
|
-
description: 'd', status: 'pending', dependsOn: [],
|
|
191
|
-
createdAt: new Date().toISOString(),
|
|
192
|
-
});
|
|
193
|
-
setCurrentPlanningSession(session);
|
|
194
|
-
const result = executeOrchestratorTool('FinishPlanning', {});
|
|
195
|
-
expect(result).toContain('Planning complete');
|
|
196
|
-
expect(result).toContain('1 task(s)');
|
|
197
|
-
setCurrentPlanningSession(null);
|
|
198
|
-
});
|
|
199
|
-
it('FinishPlanning errors without planning session', () => {
|
|
200
|
-
const result = executeOrchestratorTool('FinishPlanning', {});
|
|
201
|
-
expect(result).toContain('Error');
|
|
202
|
-
});
|
|
203
|
-
it('GetTaskStatus returns summary', () => {
|
|
204
|
-
const session = createMockSession();
|
|
205
|
-
session.tasks.push({
|
|
206
|
-
id: 'ot1', sessionId: session.id, title: 'Research APIs',
|
|
207
|
-
description: 'd', status: 'completed', dependsOn: [],
|
|
208
|
-
createdAt: new Date().toISOString(),
|
|
209
|
-
}, {
|
|
210
|
-
id: 'ot2', sessionId: session.id, title: 'Write code',
|
|
211
|
-
description: 'd', status: 'pending', dependsOn: ['ot1'],
|
|
212
|
-
createdAt: new Date().toISOString(),
|
|
213
|
-
});
|
|
214
|
-
setCurrentPlanningSession(session);
|
|
215
|
-
const result = executeOrchestratorTool('GetTaskStatus', {});
|
|
216
|
-
expect(result).toContain('ot1');
|
|
217
|
-
expect(result).toContain('completed');
|
|
218
|
-
expect(result).toContain('Research APIs');
|
|
219
|
-
expect(result).toContain('ot2');
|
|
220
|
-
expect(result).toContain('pending');
|
|
221
|
-
expect(result).toContain('depends on: ot1');
|
|
222
|
-
setCurrentPlanningSession(null);
|
|
223
|
-
});
|
|
224
|
-
it('GetTaskStatus with no tasks', () => {
|
|
225
|
-
const session = createMockSession();
|
|
226
|
-
setCurrentPlanningSession(session);
|
|
227
|
-
const result = executeOrchestratorTool('GetTaskStatus', {});
|
|
228
|
-
expect(result).toBe('No tasks created yet.');
|
|
229
|
-
setCurrentPlanningSession(null);
|
|
230
|
-
});
|
|
231
|
-
it('unknown tool returns error', () => {
|
|
232
|
-
const result = executeOrchestratorTool('DoSomething', {});
|
|
233
|
-
expect(result).toContain('Error');
|
|
234
|
-
expect(result).toContain('Unknown orchestrator tool');
|
|
235
|
-
});
|
|
236
|
-
});
|
|
237
|
-
describe('session management', () => {
|
|
238
|
-
it('setCurrentPlanningSession and getCurrentPlanningSession', () => {
|
|
239
|
-
expect(getCurrentPlanningSession()).toBeNull();
|
|
240
|
-
const session = createMockSession();
|
|
241
|
-
setCurrentPlanningSession(session);
|
|
242
|
-
expect(getCurrentPlanningSession()).toBe(session);
|
|
243
|
-
setCurrentPlanningSession(null);
|
|
244
|
-
expect(getCurrentPlanningSession()).toBeNull();
|
|
245
|
-
});
|
|
246
|
-
it('getActiveOrchestrations returns empty initially', () => {
|
|
247
|
-
expect(getActiveOrchestrations()).toHaveLength(0);
|
|
248
|
-
});
|
|
249
|
-
it('listRecentOrchestrations returns empty initially', () => {
|
|
250
|
-
expect(listRecentOrchestrations()).toHaveLength(0);
|
|
251
|
-
});
|
|
252
|
-
it('getOrchestration returns null for unknown id', () => {
|
|
253
|
-
expect(getOrchestration('orch_nonexistent')).toBeNull();
|
|
254
|
-
});
|
|
255
|
-
});
|
|
256
|
-
describe('JSONL event storage', () => {
|
|
257
|
-
it('loadSession returns null for missing file', () => {
|
|
258
|
-
vi.mocked(existsSync).mockReturnValue(false);
|
|
259
|
-
expect(loadSession('orch_missing')).toBeNull();
|
|
260
|
-
});
|
|
261
|
-
it('loadSession reconstructs from session_created event', () => {
|
|
262
|
-
const session = createMockSession({ id: 'orch_load_test' });
|
|
263
|
-
const eventLine = JSON.stringify({
|
|
264
|
-
type: 'session_created',
|
|
265
|
-
timestamp: new Date().toISOString(),
|
|
266
|
-
sessionId: 'orch_load_test',
|
|
267
|
-
data: { session },
|
|
268
|
-
});
|
|
269
|
-
vi.mocked(existsSync).mockReturnValue(true);
|
|
270
|
-
vi.mocked(readFileSync).mockReturnValue(eventLine);
|
|
271
|
-
const loaded = loadSession('orch_load_test');
|
|
272
|
-
expect(loaded).not.toBeNull();
|
|
273
|
-
expect(loaded.id).toBe('orch_load_test');
|
|
274
|
-
expect(loaded.prompt).toBe('Test prompt');
|
|
275
|
-
expect(loaded.status).toBe('planning');
|
|
276
|
-
});
|
|
277
|
-
it('loadSession applies task_created events', () => {
|
|
278
|
-
const session = createMockSession({ id: 'orch_task_test' });
|
|
279
|
-
const task = {
|
|
280
|
-
id: 'ot1',
|
|
281
|
-
sessionId: 'orch_task_test',
|
|
282
|
-
title: 'Test Task',
|
|
283
|
-
description: 'A test',
|
|
284
|
-
status: 'pending',
|
|
285
|
-
dependsOn: [],
|
|
286
|
-
createdAt: new Date().toISOString(),
|
|
287
|
-
};
|
|
288
|
-
const lines = [
|
|
289
|
-
JSON.stringify({
|
|
290
|
-
type: 'session_created',
|
|
291
|
-
timestamp: new Date().toISOString(),
|
|
292
|
-
sessionId: 'orch_task_test',
|
|
293
|
-
data: { session },
|
|
294
|
-
}),
|
|
295
|
-
JSON.stringify({
|
|
296
|
-
type: 'task_created',
|
|
297
|
-
timestamp: new Date().toISOString(),
|
|
298
|
-
sessionId: 'orch_task_test',
|
|
299
|
-
data: { task },
|
|
300
|
-
}),
|
|
301
|
-
].join('\n');
|
|
302
|
-
vi.mocked(existsSync).mockReturnValue(true);
|
|
303
|
-
vi.mocked(readFileSync).mockReturnValue(lines);
|
|
304
|
-
const loaded = loadSession('orch_task_test');
|
|
305
|
-
expect(loaded.tasks).toHaveLength(1);
|
|
306
|
-
expect(loaded.tasks[0].id).toBe('ot1');
|
|
307
|
-
expect(loaded.tasks[0].title).toBe('Test Task');
|
|
308
|
-
});
|
|
309
|
-
it('loadSession applies task status transitions', () => {
|
|
310
|
-
const session = createMockSession({ id: 'orch_status_test' });
|
|
311
|
-
const task = {
|
|
312
|
-
id: 'ot1', sessionId: 'orch_status_test', title: 'T',
|
|
313
|
-
description: 'd', status: 'pending', dependsOn: [],
|
|
314
|
-
createdAt: new Date().toISOString(),
|
|
315
|
-
};
|
|
316
|
-
const lines = [
|
|
317
|
-
JSON.stringify({ type: 'session_created', timestamp: new Date().toISOString(), sessionId: 'orch_status_test', data: { session } }),
|
|
318
|
-
JSON.stringify({ type: 'task_created', timestamp: new Date().toISOString(), sessionId: 'orch_status_test', data: { task } }),
|
|
319
|
-
JSON.stringify({ type: 'task_started', timestamp: new Date().toISOString(), sessionId: 'orch_status_test', data: { taskId: 'ot1', workerId: 'w1' } }),
|
|
320
|
-
JSON.stringify({ type: 'task_completed', timestamp: new Date().toISOString(), sessionId: 'orch_status_test', data: { taskId: 'ot1', result: 'done' } }),
|
|
321
|
-
].join('\n');
|
|
322
|
-
vi.mocked(existsSync).mockReturnValue(true);
|
|
323
|
-
vi.mocked(readFileSync).mockReturnValue(lines);
|
|
324
|
-
const loaded = loadSession('orch_status_test');
|
|
325
|
-
expect(loaded.tasks[0].status).toBe('completed');
|
|
326
|
-
expect(loaded.tasks[0].result).toBe('done');
|
|
327
|
-
expect(loaded.tasks[0].workerId).toBe('w1');
|
|
328
|
-
});
|
|
329
|
-
it('loadSession applies session_completed', () => {
|
|
330
|
-
const session = createMockSession({ id: 'orch_complete_test' });
|
|
331
|
-
const lines = [
|
|
332
|
-
JSON.stringify({ type: 'session_created', timestamp: new Date().toISOString(), sessionId: 'orch_complete_test', data: { session } }),
|
|
333
|
-
JSON.stringify({ type: 'session_completed', timestamp: new Date().toISOString(), sessionId: 'orch_complete_test', data: { finalResult: 'All done' } }),
|
|
334
|
-
].join('\n');
|
|
335
|
-
vi.mocked(existsSync).mockReturnValue(true);
|
|
336
|
-
vi.mocked(readFileSync).mockReturnValue(lines);
|
|
337
|
-
const loaded = loadSession('orch_complete_test');
|
|
338
|
-
expect(loaded.status).toBe('completed');
|
|
339
|
-
expect(loaded.finalResult).toBe('All done');
|
|
340
|
-
});
|
|
341
|
-
it('loadSession applies session_failed', () => {
|
|
342
|
-
const session = createMockSession({ id: 'orch_fail_test' });
|
|
343
|
-
const lines = [
|
|
344
|
-
JSON.stringify({ type: 'session_created', timestamp: new Date().toISOString(), sessionId: 'orch_fail_test', data: { session } }),
|
|
345
|
-
JSON.stringify({ type: 'session_failed', timestamp: new Date().toISOString(), sessionId: 'orch_fail_test', data: { error: 'boom' } }),
|
|
346
|
-
].join('\n');
|
|
347
|
-
vi.mocked(existsSync).mockReturnValue(true);
|
|
348
|
-
vi.mocked(readFileSync).mockReturnValue(lines);
|
|
349
|
-
const loaded = loadSession('orch_fail_test');
|
|
350
|
-
expect(loaded.status).toBe('failed');
|
|
351
|
-
expect(loaded.error).toBe('boom');
|
|
352
|
-
});
|
|
353
|
-
it('loadSession applies phase_changed', () => {
|
|
354
|
-
const session = createMockSession({ id: 'orch_phase_test' });
|
|
355
|
-
const lines = [
|
|
356
|
-
JSON.stringify({ type: 'session_created', timestamp: new Date().toISOString(), sessionId: 'orch_phase_test', data: { session } }),
|
|
357
|
-
JSON.stringify({ type: 'phase_changed', timestamp: new Date().toISOString(), sessionId: 'orch_phase_test', data: { phase: 'executing' } }),
|
|
358
|
-
].join('\n');
|
|
359
|
-
vi.mocked(existsSync).mockReturnValue(true);
|
|
360
|
-
vi.mocked(readFileSync).mockReturnValue(lines);
|
|
361
|
-
const loaded = loadSession('orch_phase_test');
|
|
362
|
-
expect(loaded.status).toBe('executing');
|
|
363
|
-
});
|
|
364
|
-
it('loadSession applies task_failed', () => {
|
|
365
|
-
const session = createMockSession({ id: 'orch_taskfail_test' });
|
|
366
|
-
const task = {
|
|
367
|
-
id: 'ot1', sessionId: 'orch_taskfail_test', title: 'T',
|
|
368
|
-
description: 'd', status: 'pending', dependsOn: [],
|
|
369
|
-
createdAt: new Date().toISOString(),
|
|
370
|
-
};
|
|
371
|
-
const lines = [
|
|
372
|
-
JSON.stringify({ type: 'session_created', timestamp: new Date().toISOString(), sessionId: 'orch_taskfail_test', data: { session } }),
|
|
373
|
-
JSON.stringify({ type: 'task_created', timestamp: new Date().toISOString(), sessionId: 'orch_taskfail_test', data: { task } }),
|
|
374
|
-
JSON.stringify({ type: 'task_failed', timestamp: new Date().toISOString(), sessionId: 'orch_taskfail_test', data: { taskId: 'ot1', error: 'timeout' } }),
|
|
375
|
-
].join('\n');
|
|
376
|
-
vi.mocked(existsSync).mockReturnValue(true);
|
|
377
|
-
vi.mocked(readFileSync).mockReturnValue(lines);
|
|
378
|
-
const loaded = loadSession('orch_taskfail_test');
|
|
379
|
-
expect(loaded.tasks[0].status).toBe('failed');
|
|
380
|
-
expect(loaded.tasks[0].error).toBe('timeout');
|
|
381
|
-
});
|
|
382
|
-
it('loadSession applies message_sent', () => {
|
|
383
|
-
const session = createMockSession({ id: 'orch_msg_test' });
|
|
384
|
-
const message = { id: 'm1', from: 'w1', to: 'coordinator', content: 'done', timestamp: new Date().toISOString() };
|
|
385
|
-
const lines = [
|
|
386
|
-
JSON.stringify({ type: 'session_created', timestamp: new Date().toISOString(), sessionId: 'orch_msg_test', data: { session } }),
|
|
387
|
-
JSON.stringify({ type: 'message_sent', timestamp: new Date().toISOString(), sessionId: 'orch_msg_test', data: { message } }),
|
|
388
|
-
].join('\n');
|
|
389
|
-
vi.mocked(existsSync).mockReturnValue(true);
|
|
390
|
-
vi.mocked(readFileSync).mockReturnValue(lines);
|
|
391
|
-
const loaded = loadSession('orch_msg_test');
|
|
392
|
-
expect(loaded.messages).toHaveLength(1);
|
|
393
|
-
expect(loaded.messages[0].content).toBe('done');
|
|
394
|
-
});
|
|
395
|
-
});
|
|
396
|
-
describe('resetForTesting', () => {
|
|
397
|
-
it('clears all state', () => {
|
|
398
|
-
const session = createMockSession();
|
|
399
|
-
setCurrentPlanningSession(session);
|
|
400
|
-
resetForTesting();
|
|
401
|
-
expect(getCurrentPlanningSession()).toBeNull();
|
|
402
|
-
expect(getActiveOrchestrations()).toHaveLength(0);
|
|
403
|
-
expect(listRecentOrchestrations()).toHaveLength(0);
|
|
404
|
-
});
|
|
405
|
-
});
|
|
406
|
-
describe('CreateTask persists events', () => {
|
|
407
|
-
it('appends task_created event to JSONL', () => {
|
|
408
|
-
const session = createMockSession({ id: 'orch_persist_test' });
|
|
409
|
-
setCurrentPlanningSession(session);
|
|
410
|
-
executeOrchestratorTool('CreateTask', {
|
|
411
|
-
title: 'Persist me',
|
|
412
|
-
description: 'Test persistence',
|
|
413
|
-
agentType: 'coding',
|
|
414
|
-
});
|
|
415
|
-
expect(appendFileSync).toHaveBeenCalled();
|
|
416
|
-
const calls = vi.mocked(appendFileSync).mock.calls;
|
|
417
|
-
const lastCall = calls[calls.length - 1];
|
|
418
|
-
const eventStr = lastCall[1];
|
|
419
|
-
const event = JSON.parse(eventStr.trim());
|
|
420
|
-
expect(event.type).toBe('task_created');
|
|
421
|
-
expect(event.data.task.title).toBe('Persist me');
|
|
422
|
-
setCurrentPlanningSession(null);
|
|
423
|
-
});
|
|
424
|
-
});
|
|
425
|
-
});
|
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
-
const { mockExecInContainer } = vi.hoisted(() => ({
|
|
3
|
-
mockExecInContainer: vi.fn(),
|
|
4
|
-
}));
|
|
5
|
-
vi.mock('../sandbox/runtime.js', () => ({
|
|
6
|
-
execInContainer: mockExecInContainer,
|
|
7
|
-
}));
|
|
8
|
-
import { sandboxBash, sandboxReadFile, sandboxWriteFile, sandboxListDir, sandboxGlob, } from '../sandbox/bridge.js';
|
|
9
|
-
describe('sandbox/bridge', () => {
|
|
10
|
-
beforeEach(() => {
|
|
11
|
-
vi.clearAllMocks();
|
|
12
|
-
});
|
|
13
|
-
describe('sandboxBash', () => {
|
|
14
|
-
it('passes command through', async () => {
|
|
15
|
-
mockExecInContainer.mockResolvedValue({ stdout: 'ok', stderr: '', exitCode: 0 });
|
|
16
|
-
const result = await sandboxBash('ctr', 'echo hi');
|
|
17
|
-
expect(result).toBe('ok');
|
|
18
|
-
expect(mockExecInContainer).toHaveBeenCalledWith('ctr', ['echo hi'], expect.any(Object));
|
|
19
|
-
});
|
|
20
|
-
it('handles cwd', async () => {
|
|
21
|
-
mockExecInContainer.mockResolvedValue({ stdout: '', stderr: '', exitCode: 0 });
|
|
22
|
-
await sandboxBash('ctr', 'ls', '/workspace');
|
|
23
|
-
const args = mockExecInContainer.mock.calls[0][1][0];
|
|
24
|
-
expect(args).toContain('cd');
|
|
25
|
-
expect(args).toContain('/workspace');
|
|
26
|
-
});
|
|
27
|
-
it('handles timeout', async () => {
|
|
28
|
-
mockExecInContainer.mockResolvedValue({ stdout: '', stderr: '', exitCode: 0 });
|
|
29
|
-
await sandboxBash('ctr', 'sleep 1', undefined, 5000);
|
|
30
|
-
const opts = mockExecInContainer.mock.calls[0][2];
|
|
31
|
-
expect(opts.timeout).toBe(5000);
|
|
32
|
-
});
|
|
33
|
-
it('truncates long output', async () => {
|
|
34
|
-
const longOutput = 'x'.repeat(60 * 1024);
|
|
35
|
-
mockExecInContainer.mockResolvedValue({ stdout: longOutput, stderr: '', exitCode: 0 });
|
|
36
|
-
const result = await sandboxBash('ctr', 'cmd');
|
|
37
|
-
expect(result.length).toBeLessThan(longOutput.length);
|
|
38
|
-
expect(result).toContain('truncated');
|
|
39
|
-
});
|
|
40
|
-
it('includes exit code on failure', async () => {
|
|
41
|
-
mockExecInContainer.mockResolvedValue({ stdout: 'out', stderr: 'err', exitCode: 42 });
|
|
42
|
-
const result = await sandboxBash('ctr', 'bad');
|
|
43
|
-
expect(result).toContain('[exit code: 42]');
|
|
44
|
-
});
|
|
45
|
-
it('returns (no output) when empty', async () => {
|
|
46
|
-
mockExecInContainer.mockResolvedValue({ stdout: '', stderr: '', exitCode: 0 });
|
|
47
|
-
const result = await sandboxBash('ctr', 'true');
|
|
48
|
-
expect(result).toBe('(no output)');
|
|
49
|
-
});
|
|
50
|
-
});
|
|
51
|
-
describe('sandboxReadFile', () => {
|
|
52
|
-
it('calls cat with path', async () => {
|
|
53
|
-
mockExecInContainer.mockResolvedValue({ stdout: 'content', stderr: '', exitCode: 0 });
|
|
54
|
-
const result = await sandboxReadFile('ctr', '/workspace/file.txt');
|
|
55
|
-
expect(result).toBe('content');
|
|
56
|
-
expect(mockExecInContainer.mock.calls[0][1][0]).toContain('cat');
|
|
57
|
-
});
|
|
58
|
-
it('truncates large files', async () => {
|
|
59
|
-
const big = 'y'.repeat(120 * 1024);
|
|
60
|
-
mockExecInContainer.mockResolvedValue({ stdout: big, stderr: '', exitCode: 0 });
|
|
61
|
-
const result = await sandboxReadFile('ctr', '/f');
|
|
62
|
-
expect(result.length).toBeLessThan(big.length);
|
|
63
|
-
expect(result).toContain('truncated');
|
|
64
|
-
});
|
|
65
|
-
it('throws on failure', async () => {
|
|
66
|
-
mockExecInContainer.mockResolvedValue({ stdout: '', stderr: 'not found', exitCode: 1 });
|
|
67
|
-
await expect(sandboxReadFile('ctr', '/missing')).rejects.toThrow('Failed to read');
|
|
68
|
-
});
|
|
69
|
-
});
|
|
70
|
-
describe('sandboxWriteFile', () => {
|
|
71
|
-
it('sends content via stdin', async () => {
|
|
72
|
-
mockExecInContainer.mockResolvedValue({ stdout: '', stderr: '', exitCode: 0 });
|
|
73
|
-
const result = await sandboxWriteFile('ctr', '/workspace/f.txt', 'hello');
|
|
74
|
-
expect(result).toContain('Written');
|
|
75
|
-
expect(result).toContain('5 bytes');
|
|
76
|
-
const opts = mockExecInContainer.mock.calls[0][2];
|
|
77
|
-
expect(opts.stdin).toBe('hello');
|
|
78
|
-
});
|
|
79
|
-
it('creates parent dirs', async () => {
|
|
80
|
-
mockExecInContainer.mockResolvedValue({ stdout: '', stderr: '', exitCode: 0 });
|
|
81
|
-
await sandboxWriteFile('ctr', '/workspace/a/b/c.txt', 'data');
|
|
82
|
-
expect(mockExecInContainer.mock.calls[0][1][0]).toContain('mkdir -p');
|
|
83
|
-
});
|
|
84
|
-
it('throws on failure', async () => {
|
|
85
|
-
mockExecInContainer.mockResolvedValue({ stdout: '', stderr: 'perm denied', exitCode: 1 });
|
|
86
|
-
await expect(sandboxWriteFile('ctr', '/f', 'x')).rejects.toThrow('Failed to write');
|
|
87
|
-
});
|
|
88
|
-
});
|
|
89
|
-
describe('sandboxListDir', () => {
|
|
90
|
-
it('calls ls -la', async () => {
|
|
91
|
-
mockExecInContainer.mockResolvedValue({ stdout: 'drwxr-xr-x ...', stderr: '', exitCode: 0 });
|
|
92
|
-
const result = await sandboxListDir('ctr', '/workspace');
|
|
93
|
-
expect(result).toBe('drwxr-xr-x ...');
|
|
94
|
-
expect(mockExecInContainer.mock.calls[0][1][0]).toContain('ls -la');
|
|
95
|
-
});
|
|
96
|
-
it('throws on failure', async () => {
|
|
97
|
-
mockExecInContainer.mockResolvedValue({ stdout: '', stderr: 'no such dir', exitCode: 1 });
|
|
98
|
-
await expect(sandboxListDir('ctr', '/bad')).rejects.toThrow('Failed to list');
|
|
99
|
-
});
|
|
100
|
-
});
|
|
101
|
-
describe('sandboxGlob', () => {
|
|
102
|
-
it('calls find with correct args', async () => {
|
|
103
|
-
mockExecInContainer.mockResolvedValue({ stdout: '/workspace/a.ts\n', stderr: '', exitCode: 0 });
|
|
104
|
-
const result = await sandboxGlob('ctr', '/workspace', '*.ts');
|
|
105
|
-
expect(result).toContain('/workspace/a.ts');
|
|
106
|
-
const cmd = mockExecInContainer.mock.calls[0][1][0];
|
|
107
|
-
expect(cmd).toContain('find');
|
|
108
|
-
expect(cmd).toContain('-name');
|
|
109
|
-
expect(cmd).toContain('-maxdepth');
|
|
110
|
-
});
|
|
111
|
-
it('throws on failure', async () => {
|
|
112
|
-
mockExecInContainer.mockResolvedValue({ stdout: '', stderr: 'err', exitCode: 1 });
|
|
113
|
-
await expect(sandboxGlob('ctr', '/x', '*.js')).rejects.toThrow('Failed to glob');
|
|
114
|
-
});
|
|
115
|
-
});
|
|
116
|
-
});
|