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.
Files changed (222) hide show
  1. package/README.md +47 -37
  2. package/dist/__tests__/adapter-types.test.d.ts +4 -0
  3. package/dist/__tests__/adapter-types.test.js +63 -0
  4. package/dist/__tests__/anthropic-adapter.test.d.ts +4 -0
  5. package/dist/__tests__/anthropic-adapter.test.js +264 -0
  6. package/dist/__tests__/api.test.js +0 -1
  7. package/dist/__tests__/cli.integration.test.js +2 -4
  8. package/dist/__tests__/cli.test.js +0 -1
  9. package/dist/__tests__/code-agents-notifications.test.js +137 -0
  10. package/dist/__tests__/code-agents-parser.test.js +19 -1
  11. package/dist/__tests__/code-agents-preflight.test.js +3 -28
  12. package/dist/__tests__/code-agents-utils.test.js +34 -9
  13. package/dist/__tests__/code-agents-worktrees.test.js +116 -0
  14. package/dist/__tests__/codex-adapter.test.js +184 -0
  15. package/dist/__tests__/codex-auth.test.js +66 -0
  16. package/dist/__tests__/codex-provider-gating.test.js +35 -0
  17. package/dist/__tests__/codex-unified-loop.test.js +111 -0
  18. package/dist/__tests__/config-security.test.js +127 -0
  19. package/dist/__tests__/config.test.js +23 -0
  20. package/dist/__tests__/context-manager.test.js +243 -164
  21. package/dist/__tests__/cron-run.test.js +250 -0
  22. package/dist/__tests__/cron.test.js +12 -38
  23. package/dist/__tests__/digests.test.js +67 -0
  24. package/dist/__tests__/discord-attachments.test.js +211 -0
  25. package/dist/__tests__/discord-docs.test.d.ts +1 -0
  26. package/dist/__tests__/discord-docs.test.js +27 -0
  27. package/dist/__tests__/discord-thread-agents.test.d.ts +1 -0
  28. package/dist/__tests__/discord-thread-agents.test.js +115 -0
  29. package/dist/__tests__/discord-thread-context.test.d.ts +1 -0
  30. package/dist/__tests__/discord-thread-context.test.js +42 -0
  31. package/dist/__tests__/doctor.formatters.test.js +4 -4
  32. package/dist/__tests__/doctor.index.test.js +1 -1
  33. package/dist/__tests__/doctor.runner.test.js +3 -15
  34. package/dist/__tests__/env-sanitizer.test.d.ts +1 -0
  35. package/dist/__tests__/env-sanitizer.test.js +45 -0
  36. package/dist/__tests__/exec-approval.test.js +61 -0
  37. package/dist/__tests__/fetch-tool.test.d.ts +1 -0
  38. package/dist/__tests__/fetch-tool.test.js +85 -0
  39. package/dist/__tests__/gateway-status-auth.test.d.ts +1 -0
  40. package/dist/__tests__/gateway-status-auth.test.js +72 -0
  41. package/dist/__tests__/heartbeat.test.js +3 -3
  42. package/dist/__tests__/interactive-sessions.test.d.ts +1 -0
  43. package/dist/__tests__/interactive-sessions.test.js +96 -0
  44. package/dist/__tests__/langfuse.test.js +6 -18
  45. package/dist/__tests__/model-selection.test.js +3 -4
  46. package/dist/__tests__/providers-init.test.js +2 -8
  47. package/dist/__tests__/providers-routing.test.js +1 -1
  48. package/dist/__tests__/providers-utils.test.js +13 -3
  49. package/dist/__tests__/sessions.test.js +14 -10
  50. package/dist/__tests__/setup.test.js +12 -29
  51. package/dist/__tests__/skills.test.js +10 -7
  52. package/dist/__tests__/stream-formatter.test.d.ts +1 -0
  53. package/dist/__tests__/stream-formatter.test.js +114 -0
  54. package/dist/__tests__/token-efficiency.test.js +131 -15
  55. package/dist/__tests__/tool-loop.test.d.ts +4 -0
  56. package/dist/__tests__/tool-loop.test.js +505 -0
  57. package/dist/__tests__/tools.test.js +101 -276
  58. package/dist/__tests__/utils.test.d.ts +1 -0
  59. package/dist/__tests__/utils.test.js +14 -0
  60. package/dist/__tests__/voice.test.js +21 -0
  61. package/dist/agent.js +35 -4
  62. package/dist/api.js +113 -37
  63. package/dist/channels/discord/attachments.d.ts +50 -0
  64. package/dist/channels/discord/attachments.js +137 -0
  65. package/dist/channels/discord/delegation.d.ts +5 -0
  66. package/dist/channels/discord/delegation.js +136 -0
  67. package/dist/channels/discord/handlers.js +694 -7
  68. package/dist/channels/discord/index.d.ts +16 -1
  69. package/dist/channels/discord/index.js +64 -1
  70. package/dist/channels/discord/thread-agents.d.ts +54 -0
  71. package/dist/channels/discord/thread-agents.js +323 -0
  72. package/dist/channels/discord/threads.d.ts +58 -0
  73. package/dist/channels/discord/threads.js +192 -0
  74. package/dist/channels/discord/types.js +4 -2
  75. package/dist/channels/discord/utils.d.ts +16 -0
  76. package/dist/channels/discord/utils.js +86 -6
  77. package/dist/channels/telegram/index.d.ts +1 -1
  78. package/dist/channels/telegram/types.js +1 -1
  79. package/dist/channels/telegram/utils.js +9 -3
  80. package/dist/channels.d.ts +1 -1
  81. package/dist/cli.js +20 -400
  82. package/dist/code-agents/executor.d.ts +1 -1
  83. package/dist/code-agents/executor.js +101 -45
  84. package/dist/code-agents/index.d.ts +2 -7
  85. package/dist/code-agents/index.js +111 -80
  86. package/dist/code-agents/interactive-resume.d.ts +6 -0
  87. package/dist/code-agents/interactive-resume.js +98 -0
  88. package/dist/code-agents/interactive-sessions.d.ts +20 -0
  89. package/dist/code-agents/interactive-sessions.js +132 -0
  90. package/dist/code-agents/parser.js +5 -1
  91. package/dist/code-agents/registry.d.ts +7 -1
  92. package/dist/code-agents/registry.js +11 -23
  93. package/dist/code-agents/stream-formatter.d.ts +8 -0
  94. package/dist/code-agents/stream-formatter.js +92 -0
  95. package/dist/code-agents/types.d.ts +16 -24
  96. package/dist/code-agents/utils.d.ts +35 -11
  97. package/dist/code-agents/utils.js +349 -95
  98. package/dist/code-agents/worktrees.d.ts +37 -0
  99. package/dist/code-agents/worktrees.js +116 -0
  100. package/dist/config.d.ts +2 -4
  101. package/dist/config.js +123 -23
  102. package/dist/cron.d.ts +1 -6
  103. package/dist/cron.js +175 -82
  104. package/dist/dashboard/assets/index-B345aOO-.js +65 -0
  105. package/dist/dashboard/assets/index-ZWK4dalJ.css +1 -0
  106. package/dist/dashboard/index.html +2 -2
  107. package/dist/digests.d.ts +1 -0
  108. package/dist/digests.js +132 -42
  109. package/dist/doctor/checks.d.ts +0 -3
  110. package/dist/doctor/checks.js +1 -108
  111. package/dist/doctor/runner.js +1 -4
  112. package/dist/env-sanitizer.d.ts +2 -0
  113. package/dist/env-sanitizer.js +61 -0
  114. package/dist/exec-approval.d.ts +11 -1
  115. package/dist/exec-approval.js +17 -4
  116. package/dist/gateway.d.ts +3 -1
  117. package/dist/gateway.js +17 -7
  118. package/dist/heartbeat.js +1 -6
  119. package/dist/langfuse.js +3 -29
  120. package/dist/model-selection.js +3 -1
  121. package/dist/providers/adapter.d.ts +118 -0
  122. package/dist/providers/adapter.js +6 -0
  123. package/dist/providers/adapters/anthropic-adapter.d.ts +22 -0
  124. package/dist/providers/adapters/anthropic-adapter.js +204 -0
  125. package/dist/providers/adapters/codex-adapter.d.ts +26 -0
  126. package/dist/providers/adapters/codex-adapter.js +203 -0
  127. package/dist/providers/anthropic.d.ts +1 -0
  128. package/dist/providers/anthropic.js +10 -272
  129. package/dist/providers/codex.d.ts +21 -0
  130. package/dist/providers/codex.js +149 -330
  131. package/dist/providers/content.d.ts +1 -1
  132. package/dist/providers/content.js +2 -2
  133. package/dist/providers/context-manager.d.ts +18 -6
  134. package/dist/providers/context-manager.js +199 -223
  135. package/dist/providers/index.d.ts +9 -1
  136. package/dist/providers/index.js +73 -64
  137. package/dist/providers/loop-utils.d.ts +20 -0
  138. package/dist/providers/loop-utils.js +30 -0
  139. package/dist/providers/tool-loop.d.ts +12 -0
  140. package/dist/providers/tool-loop.js +251 -0
  141. package/dist/providers/utils.d.ts +19 -3
  142. package/dist/providers/utils.js +100 -29
  143. package/dist/secure-store.d.ts +8 -0
  144. package/dist/secure-store.js +80 -0
  145. package/dist/service.js +3 -28
  146. package/dist/sessions.d.ts +3 -0
  147. package/dist/sessions.js +147 -18
  148. package/dist/setup-templates.js +13 -25
  149. package/dist/setup.d.ts +10 -6
  150. package/dist/setup.js +84 -292
  151. package/dist/skills.js +3 -11
  152. package/dist/tools/agent-delegation.d.ts +19 -0
  153. package/dist/tools/agent-delegation.js +49 -0
  154. package/dist/tools/bash-tool.js +89 -34
  155. package/dist/tools/definitions.d.ts +199 -302
  156. package/dist/tools/definitions.js +70 -123
  157. package/dist/tools/execute-context.d.ts +13 -4
  158. package/dist/tools/fetch-tool.js +109 -13
  159. package/dist/tools/file-tools.js +7 -1
  160. package/dist/tools.d.ts +7 -7
  161. package/dist/tools.js +133 -151
  162. package/dist/types.d.ts +37 -30
  163. package/dist/utils.js +4 -6
  164. package/dist/voice.d.ts +1 -1
  165. package/dist/voice.js +17 -4
  166. package/package.json +33 -23
  167. package/templates/TOOLS.md +0 -27
  168. package/dist/__tests__/audit.test.js +0 -122
  169. package/dist/__tests__/code-agents-orchestrator.test.js +0 -216
  170. package/dist/__tests__/code-agents-sandbox.test.js +0 -163
  171. package/dist/__tests__/orchestrator.test.js +0 -425
  172. package/dist/__tests__/sandbox-bridge.test.js +0 -116
  173. package/dist/__tests__/sandbox-manager.test.js +0 -144
  174. package/dist/__tests__/sandbox-mount-security.test.js +0 -139
  175. package/dist/__tests__/sandbox-runtime.test.js +0 -176
  176. package/dist/__tests__/subagent.test.js +0 -240
  177. package/dist/__tests__/telegram.test.js +0 -42
  178. package/dist/code-agents/orchestrator.d.ts +0 -29
  179. package/dist/code-agents/orchestrator.js +0 -694
  180. package/dist/code-agents/worktree.d.ts +0 -40
  181. package/dist/code-agents/worktree.js +0 -215
  182. package/dist/dashboard/assets/index-BoTHPby4.js +0 -65
  183. package/dist/dashboard/assets/index-D4mufvBg.css +0 -1
  184. package/dist/dashboard.d.ts +0 -8
  185. package/dist/dashboard.js +0 -4071
  186. package/dist/discord.d.ts +0 -8
  187. package/dist/discord.js +0 -792
  188. package/dist/mcp-context-a8c.d.ts +0 -13
  189. package/dist/mcp-context-a8c.js +0 -34
  190. package/dist/orchestrator.d.ts +0 -15
  191. package/dist/orchestrator.js +0 -676
  192. package/dist/providers/openai.d.ts +0 -10
  193. package/dist/providers/openai.js +0 -355
  194. package/dist/sandbox/bridge.d.ts +0 -5
  195. package/dist/sandbox/bridge.js +0 -63
  196. package/dist/sandbox/index.d.ts +0 -5
  197. package/dist/sandbox/index.js +0 -4
  198. package/dist/sandbox/manager.d.ts +0 -7
  199. package/dist/sandbox/manager.js +0 -100
  200. package/dist/sandbox/mount-security.d.ts +0 -12
  201. package/dist/sandbox/mount-security.js +0 -122
  202. package/dist/sandbox/runtime.d.ts +0 -39
  203. package/dist/sandbox/runtime.js +0 -192
  204. package/dist/sandbox-utils.d.ts +0 -6
  205. package/dist/sandbox-utils.js +0 -36
  206. package/dist/subagent.d.ts +0 -19
  207. package/dist/subagent.js +0 -407
  208. package/dist/telegram.d.ts +0 -2
  209. package/dist/telegram.js +0 -11
  210. package/dist/tools/browser-tool.d.ts +0 -3
  211. package/dist/tools/browser-tool.js +0 -266
  212. package/sandbox/Dockerfile +0 -40
  213. /package/dist/__tests__/{audit.test.d.ts → code-agents-notifications.test.d.ts} +0 -0
  214. /package/dist/__tests__/{code-agents-orchestrator.test.d.ts → code-agents-worktrees.test.d.ts} +0 -0
  215. /package/dist/__tests__/{code-agents-sandbox.test.d.ts → codex-adapter.test.d.ts} +0 -0
  216. /package/dist/__tests__/{orchestrator.test.d.ts → codex-auth.test.d.ts} +0 -0
  217. /package/dist/__tests__/{sandbox-bridge.test.d.ts → codex-provider-gating.test.d.ts} +0 -0
  218. /package/dist/__tests__/{sandbox-manager.test.d.ts → codex-unified-loop.test.d.ts} +0 -0
  219. /package/dist/__tests__/{sandbox-mount-security.test.d.ts → config-security.test.d.ts} +0 -0
  220. /package/dist/__tests__/{sandbox-runtime.test.d.ts → cron-run.test.d.ts} +0 -0
  221. /package/dist/__tests__/{subagent.test.d.ts → digests.test.d.ts} +0 -0
  222. /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
- });