scalix 1.0.0 → 1.1.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/.env.example +29 -0
- package/README.md +1 -1
- package/dist/agent-definitions.js +100 -0
- package/dist/agent-registry.js +226 -0
- package/dist/agent.js +352 -74
- package/dist/api-client.js +77 -0
- package/dist/cli.js +160 -35
- package/dist/constants.js +40 -6
- package/dist/context-builder.js +8 -1
- package/dist/context-retriever.js +257 -0
- package/dist/git-context.js +32 -29
- package/dist/grounding-checker.js +154 -0
- package/dist/headless.js +109 -20
- package/dist/hooks.js +7 -1
- package/dist/injection-detector.js +86 -0
- package/dist/ipc-server.js +6 -2
- package/dist/project-index.js +2 -0
- package/dist/sandbox.js +135 -11
- package/dist/sessions.js +37 -2
- package/dist/settings.js +111 -6
- package/dist/sub-agent.js +83 -8
- package/dist/task-board.js +105 -0
- package/dist/tools-advanced.js +304 -49
- package/dist/tools-browser.js +280 -19
- package/dist/tools-extra.js +7 -0
- package/dist/tools-vision.js +4 -3
- package/dist/tools.js +13 -3
- package/dist/update-checker.js +111 -0
- package/dist/worktree.js +172 -0
- package/package.json +6 -2
- package/app.py +0 -55
package/dist/tools-advanced.js
CHANGED
|
@@ -3,12 +3,47 @@ import { z } from 'zod';
|
|
|
3
3
|
import fs from 'node:fs/promises';
|
|
4
4
|
import { sandboxPath } from './sandbox.js';
|
|
5
5
|
import { backgroundProcesses } from './tools.js';
|
|
6
|
-
import { spawnSubAgent } from './sub-agent.js';
|
|
6
|
+
import { spawnSubAgent, spawnParallelAgents } from './sub-agent.js';
|
|
7
7
|
import { logger } from './logger.js';
|
|
8
8
|
import { TOOL_DEFAULTS } from './constants.js';
|
|
9
9
|
import { DEFAULT_LIMITS } from './settings.js';
|
|
10
|
+
import { AgentRegistry } from './agent-registry.js';
|
|
11
|
+
import { TaskBoard } from './task-board.js';
|
|
12
|
+
import { getAgentDefinitions, isBuiltInAgentType } from './agent-definitions.js';
|
|
10
13
|
let _limits = DEFAULT_LIMITS;
|
|
11
14
|
export function setAdvancedToolLimits(limits) { _limits = limits; }
|
|
15
|
+
const BUILT_IN_INSTRUCTIONS = {
|
|
16
|
+
plan: 'You are a planning assistant. Analyze the codebase and design an implementation approach. Be thorough and specific.',
|
|
17
|
+
explore: 'You are a code exploration assistant. Search and read files to answer questions. Be concise and factual.',
|
|
18
|
+
general: 'You are a focused assistant with full capabilities. Complete the task efficiently. You can spawn sub-agents if needed.',
|
|
19
|
+
};
|
|
20
|
+
async function resolveAgentType(subagentType) {
|
|
21
|
+
const agentType = subagentType || 'general';
|
|
22
|
+
if (isBuiltInAgentType(agentType)) {
|
|
23
|
+
const isReadOnly = agentType === 'explore' || agentType === 'plan';
|
|
24
|
+
return {
|
|
25
|
+
toolLevel: isReadOnly ? 'read-only' : 'all',
|
|
26
|
+
isReadOnly,
|
|
27
|
+
instructions: BUILT_IN_INSTRUCTIONS[agentType] || BUILT_IN_INSTRUCTIONS.general,
|
|
28
|
+
maxSteps: agentType === 'explore' ? _limits.exploreMaxSteps : _limits.subAgentMaxSteps,
|
|
29
|
+
allowSpawning: true,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
const definitions = await getAgentDefinitions(process.cwd());
|
|
33
|
+
const def = definitions.get(agentType);
|
|
34
|
+
if (!def) {
|
|
35
|
+
throw new Error(`Unknown agent type "${agentType}". Built-in types: general, explore, plan. Check .scalix/agents/ for custom definitions.`);
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
toolLevel: def.tools,
|
|
39
|
+
isReadOnly: def.tools === 'read-only',
|
|
40
|
+
instructions: def.instructions,
|
|
41
|
+
maxSteps: def.maxSteps ?? _limits.subAgentMaxSteps,
|
|
42
|
+
model: def.model,
|
|
43
|
+
allowSpawning: def.allowSpawning ?? true,
|
|
44
|
+
description: def.description,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
12
47
|
let currentTodos = [];
|
|
13
48
|
export function getTodos() {
|
|
14
49
|
return [...currentTodos];
|
|
@@ -64,6 +99,10 @@ export const notebookEditTool = tool({
|
|
|
64
99
|
execute: async ({ notebook_path, command, cell_type, cell_number, new_source, position }) => {
|
|
65
100
|
try {
|
|
66
101
|
const safePath = sandboxPath(notebook_path);
|
|
102
|
+
const stat = await fs.stat(safePath);
|
|
103
|
+
if (stat.size > TOOL_DEFAULTS.readFileMaxSize) {
|
|
104
|
+
return { error: `Notebook too large (${Math.round(stat.size / 1024 / 1024)}MB). Max: ${Math.round(TOOL_DEFAULTS.readFileMaxSize / 1024 / 1024)}MB.` };
|
|
105
|
+
}
|
|
67
106
|
const raw = await fs.readFile(safePath, 'utf-8');
|
|
68
107
|
const notebook = JSON.parse(raw);
|
|
69
108
|
if (notebook.nbformat !== 4) {
|
|
@@ -115,7 +154,6 @@ export const notebookEditTool = tool({
|
|
|
115
154
|
return { error: `Invalid cell_number: ${cell_number}. Notebook has ${cells.length} cells.` };
|
|
116
155
|
}
|
|
117
156
|
const [cell] = cells.splice(cell_number, 1);
|
|
118
|
-
// After splice, validate position against the reduced array
|
|
119
157
|
const maxPos = cells.length;
|
|
120
158
|
const targetPos = position !== undefined ? Math.min(position, maxPos) : maxPos;
|
|
121
159
|
cells.splice(targetPos, 0, cell);
|
|
@@ -168,65 +206,282 @@ let parentAgentConfig = null;
|
|
|
168
206
|
export function setAgentToolConfig(config) {
|
|
169
207
|
parentAgentConfig = config;
|
|
170
208
|
}
|
|
209
|
+
function resolveAgentConfig(context) {
|
|
210
|
+
if (context?.agentConfig)
|
|
211
|
+
return context.agentConfig;
|
|
212
|
+
return parentAgentConfig;
|
|
213
|
+
}
|
|
171
214
|
export const agentTool = tool({
|
|
172
215
|
name: 'agent',
|
|
173
|
-
description:
|
|
216
|
+
description: `Spawn agents for complex tasks. Supports multiple actions:
|
|
217
|
+
- spawn (default): Create a sub-agent to handle a task independently. Sub-agents have full tool access including spawning further agents.
|
|
218
|
+
- message: Send a message to another active agent by ID.
|
|
219
|
+
- spawn_parallel: Spawn multiple agents in parallel. Returns results from all agents.
|
|
220
|
+
- list_agents: List all active and completed agents with their status and resource usage.`,
|
|
174
221
|
inputSchema: z.object({
|
|
175
|
-
prompt: z.string().min(1).max(10000).describe('The task for the sub-agent
|
|
176
|
-
description: z.string().min(1).max(100).describe('Short description of the task (3-5 words)'),
|
|
177
|
-
subagent_type: z.
|
|
222
|
+
prompt: z.string().min(1).max(10000).optional().describe('The task for the sub-agent (required for spawn)'),
|
|
223
|
+
description: z.string().min(1).max(100).optional().describe('Short description of the task (3-5 words)'),
|
|
224
|
+
subagent_type: z.string().max(50).optional().describe('Agent type: general (full access), explore (read-only), plan (read-only analysis), or a custom type from .scalix/agents/'),
|
|
178
225
|
model: z.string().max(100).optional().describe('Model override for the sub-agent'),
|
|
226
|
+
action: z.enum(['spawn', 'message', 'spawn_parallel', 'list_agents']).optional().describe('Action to perform (default: spawn)'),
|
|
227
|
+
target_agent_id: z.string().optional().describe('Target agent ID for message action'),
|
|
228
|
+
message_content: z.string().max(5000).optional().describe('Message content for message action'),
|
|
229
|
+
parallel_tasks: z.array(z.object({
|
|
230
|
+
prompt: z.string().min(1).max(10000),
|
|
231
|
+
description: z.string().min(1).max(100),
|
|
232
|
+
subagent_type: z.string().max(50).optional(),
|
|
233
|
+
model: z.string().max(100).optional(),
|
|
234
|
+
})).optional().describe('Array of tasks for spawn_parallel action'),
|
|
179
235
|
}),
|
|
180
|
-
execute: async (
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
:
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
236
|
+
execute: async (args, context) => {
|
|
237
|
+
const config = resolveAgentConfig(context);
|
|
238
|
+
const agentId = context?.agentId;
|
|
239
|
+
const action = args.action || 'spawn';
|
|
240
|
+
switch (action) {
|
|
241
|
+
case 'list_agents': {
|
|
242
|
+
const registry = AgentRegistry.getInstance();
|
|
243
|
+
const active = registry.listActive();
|
|
244
|
+
const summary = registry.getUsageSummary();
|
|
245
|
+
return {
|
|
246
|
+
activeAgents: active.length,
|
|
247
|
+
totalTokens: summary.totalTokens,
|
|
248
|
+
tree: registry.getAgentTree(),
|
|
249
|
+
agents: summary.byAgent,
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
case 'message': {
|
|
253
|
+
if (!args.target_agent_id || !args.message_content) {
|
|
254
|
+
return { error: 'target_agent_id and message_content are required for message action' };
|
|
255
|
+
}
|
|
256
|
+
const registry = AgentRegistry.getInstance();
|
|
257
|
+
const result = registry.sendMessage(agentId || 'unknown', args.target_agent_id, args.message_content);
|
|
258
|
+
if ('error' in result)
|
|
259
|
+
return result;
|
|
260
|
+
return { success: true, messageId: result.id, to: args.target_agent_id };
|
|
261
|
+
}
|
|
262
|
+
case 'spawn_parallel': {
|
|
263
|
+
if (!config) {
|
|
264
|
+
return { error: 'Agent tool not configured. Parent agent config not set.' };
|
|
265
|
+
}
|
|
266
|
+
if (!args.parallel_tasks || args.parallel_tasks.length === 0) {
|
|
267
|
+
return { error: 'parallel_tasks array is required for spawn_parallel action' };
|
|
268
|
+
}
|
|
269
|
+
const tasks = await Promise.all(args.parallel_tasks.map(async (task) => {
|
|
270
|
+
let resolved;
|
|
271
|
+
try {
|
|
272
|
+
resolved = await resolveAgentType(task.subagent_type);
|
|
273
|
+
}
|
|
274
|
+
catch (err) {
|
|
275
|
+
return { error: `Invalid agent type "${task.subagent_type}": ${err.message}` };
|
|
276
|
+
}
|
|
277
|
+
const effectiveModel = task.model || resolved.model;
|
|
278
|
+
const subPermissionMode = resolved.isReadOnly ? 'plan' : (config.permissionMode ?? 'default');
|
|
279
|
+
const subConfirmToolCall = resolved.isReadOnly ? undefined : config.confirmToolCall;
|
|
280
|
+
return {
|
|
281
|
+
prompt: task.prompt,
|
|
282
|
+
description: task.description,
|
|
283
|
+
options: {
|
|
284
|
+
model: effectiveModel,
|
|
285
|
+
tools: resolved.toolLevel,
|
|
286
|
+
permissionMode: subPermissionMode,
|
|
287
|
+
confirmToolCall: subConfirmToolCall,
|
|
288
|
+
allowAgentTool: resolved.allowSpawning,
|
|
289
|
+
parentAgentId: agentId,
|
|
290
|
+
agentDescription: task.description,
|
|
291
|
+
maxSteps: resolved.maxSteps,
|
|
292
|
+
instructions: resolved.instructions,
|
|
293
|
+
timeout: _limits.agentToolTimeoutMs,
|
|
294
|
+
},
|
|
295
|
+
};
|
|
296
|
+
}));
|
|
297
|
+
const validTasks = tasks.filter((t) => !t.error);
|
|
298
|
+
const errorTasks = tasks.filter((t) => t.error);
|
|
299
|
+
if (validTasks.length === 0) {
|
|
300
|
+
return { error: 'All parallel tasks failed validation', details: errorTasks };
|
|
301
|
+
}
|
|
302
|
+
logger.info('Spawning parallel agents', { count: validTasks.length, skipped: errorTasks.length });
|
|
303
|
+
const results = await spawnParallelAgents(config, validTasks);
|
|
304
|
+
const allResults = [
|
|
305
|
+
...errorTasks.map((t, i) => ({
|
|
306
|
+
index: i,
|
|
307
|
+
description: t.error,
|
|
308
|
+
status: 'failed',
|
|
309
|
+
error: t.error,
|
|
310
|
+
})),
|
|
311
|
+
...results.map((r, i) => {
|
|
312
|
+
if (r.status === 'fulfilled') {
|
|
313
|
+
return {
|
|
314
|
+
index: errorTasks.length + i,
|
|
315
|
+
description: validTasks[i].description,
|
|
316
|
+
status: 'completed',
|
|
317
|
+
response: r.value.response,
|
|
318
|
+
model: r.value.model,
|
|
319
|
+
steps: r.value.steps,
|
|
320
|
+
tokens: r.value.tokenEstimate,
|
|
321
|
+
agentId: r.value.agentId,
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
return {
|
|
325
|
+
index: errorTasks.length + i,
|
|
326
|
+
description: validTasks[i].description,
|
|
327
|
+
status: 'failed',
|
|
328
|
+
error: r.reason?.message || String(r.reason),
|
|
329
|
+
};
|
|
330
|
+
}),
|
|
331
|
+
];
|
|
332
|
+
return {
|
|
333
|
+
total: allResults.length,
|
|
334
|
+
results: allResults,
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
case 'spawn':
|
|
338
|
+
default: {
|
|
339
|
+
if (!config) {
|
|
340
|
+
return { error: 'Agent tool not configured. Parent agent config not set.' };
|
|
341
|
+
}
|
|
342
|
+
if (!args.prompt) {
|
|
343
|
+
return { error: 'prompt is required for spawn action' };
|
|
344
|
+
}
|
|
345
|
+
if (!args.description) {
|
|
346
|
+
return { error: 'description is required for spawn action' };
|
|
347
|
+
}
|
|
348
|
+
let resolved;
|
|
349
|
+
try {
|
|
350
|
+
resolved = await resolveAgentType(args.subagent_type);
|
|
351
|
+
}
|
|
352
|
+
catch (err) {
|
|
353
|
+
return { error: err.message };
|
|
354
|
+
}
|
|
355
|
+
const effectiveModel = args.model || resolved.model;
|
|
356
|
+
if (effectiveModel && !config.models.includes(effectiveModel)) {
|
|
357
|
+
return { error: `Unknown model "${effectiveModel}". Available: ${config.models.join(', ')}` };
|
|
358
|
+
}
|
|
359
|
+
let subPermissionMode = resolved.isReadOnly ? 'plan' : (config.permissionMode ?? 'default');
|
|
360
|
+
let subConfirmToolCall;
|
|
361
|
+
if (!resolved.isReadOnly) {
|
|
362
|
+
subConfirmToolCall = config.confirmToolCall;
|
|
363
|
+
}
|
|
364
|
+
logger.info('Spawning sub-agent', {
|
|
365
|
+
description: args.description,
|
|
366
|
+
type: args.subagent_type || 'general',
|
|
367
|
+
permissionMode: subPermissionMode,
|
|
368
|
+
});
|
|
369
|
+
try {
|
|
370
|
+
const result = await spawnSubAgent(config, args.prompt, {
|
|
371
|
+
model: effectiveModel,
|
|
372
|
+
tools: resolved.toolLevel,
|
|
373
|
+
permissionMode: subPermissionMode,
|
|
374
|
+
confirmToolCall: subConfirmToolCall,
|
|
375
|
+
allowAgentTool: resolved.allowSpawning,
|
|
376
|
+
parentAgentId: agentId,
|
|
377
|
+
agentDescription: args.description,
|
|
378
|
+
maxSteps: resolved.maxSteps,
|
|
379
|
+
instructions: resolved.instructions,
|
|
380
|
+
timeout: _limits.agentToolTimeoutMs,
|
|
381
|
+
});
|
|
382
|
+
return {
|
|
383
|
+
description: args.description,
|
|
384
|
+
response: result.response,
|
|
385
|
+
model: result.model,
|
|
386
|
+
steps: result.steps,
|
|
387
|
+
tokenEstimate: result.tokenEstimate,
|
|
388
|
+
agentId: result.agentId,
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
catch (error) {
|
|
392
|
+
return { error: `Sub-agent failed: ${error.message}` };
|
|
393
|
+
}
|
|
394
|
+
}
|
|
223
395
|
}
|
|
224
396
|
},
|
|
225
397
|
});
|
|
398
|
+
// ─── Send Message Tool ───
|
|
399
|
+
export const sendMessageTool = tool({
|
|
400
|
+
name: 'send_message',
|
|
401
|
+
description: 'Send a message to another active agent by ID. Use this for inter-agent communication and coordination.',
|
|
402
|
+
inputSchema: z.object({
|
|
403
|
+
to: z.string().min(1).describe('Target agent ID'),
|
|
404
|
+
content: z.string().min(1).max(5000).describe('Message content'),
|
|
405
|
+
}),
|
|
406
|
+
execute: async ({ to, content }, context) => {
|
|
407
|
+
const from = context?.agentId || 'unknown';
|
|
408
|
+
const registry = AgentRegistry.getInstance();
|
|
409
|
+
const result = registry.sendMessage(from, to, content);
|
|
410
|
+
if ('error' in result)
|
|
411
|
+
return result;
|
|
412
|
+
return { success: true, messageId: result.id, to, from };
|
|
413
|
+
},
|
|
414
|
+
});
|
|
415
|
+
// ─── Task Board Tools ───
|
|
416
|
+
export const taskCreateTool = tool({
|
|
417
|
+
name: 'task_create',
|
|
418
|
+
description: 'Create a new task on the shared task board. Tasks can be claimed and worked on by any agent.',
|
|
419
|
+
inputSchema: z.object({
|
|
420
|
+
title: z.string().min(1).max(200).describe('Task title'),
|
|
421
|
+
description: z.string().max(2000).optional().describe('Detailed task description'),
|
|
422
|
+
}),
|
|
423
|
+
execute: async ({ title, description }, context) => {
|
|
424
|
+
const agentId = context?.agentId || 'unknown';
|
|
425
|
+
const board = TaskBoard.getInstance();
|
|
426
|
+
const task = board.create(agentId, title, description);
|
|
427
|
+
return { success: true, task };
|
|
428
|
+
},
|
|
429
|
+
});
|
|
430
|
+
export const taskClaimTool = tool({
|
|
431
|
+
name: 'task_claim',
|
|
432
|
+
description: 'Claim an open task from the shared task board. Only open tasks can be claimed.',
|
|
433
|
+
inputSchema: z.object({
|
|
434
|
+
taskId: z.string().min(1).describe('Task ID to claim'),
|
|
435
|
+
}),
|
|
436
|
+
execute: async ({ taskId }, context) => {
|
|
437
|
+
const agentId = context?.agentId || 'unknown';
|
|
438
|
+
const board = TaskBoard.getInstance();
|
|
439
|
+
const result = board.claim(taskId, agentId);
|
|
440
|
+
if ('error' in result)
|
|
441
|
+
return result;
|
|
442
|
+
return { success: true, task: result };
|
|
443
|
+
},
|
|
444
|
+
});
|
|
445
|
+
export const taskUpdateTool = tool({
|
|
446
|
+
name: 'task_update',
|
|
447
|
+
description: 'Update a task\'s status or result on the shared task board. Use to mark tasks as in_progress, completed, or failed.',
|
|
448
|
+
inputSchema: z.object({
|
|
449
|
+
taskId: z.string().min(1).describe('Task ID to update'),
|
|
450
|
+
status: z.enum(['in_progress', 'completed', 'failed']).optional().describe('New task status'),
|
|
451
|
+
result: z.string().max(5000).optional().describe('Task result or output'),
|
|
452
|
+
}),
|
|
453
|
+
execute: async ({ taskId, status, result }, context) => {
|
|
454
|
+
const agentId = context?.agentId || 'unknown';
|
|
455
|
+
const board = TaskBoard.getInstance();
|
|
456
|
+
const updated = board.update(taskId, agentId, { status, result });
|
|
457
|
+
if ('error' in updated)
|
|
458
|
+
return updated;
|
|
459
|
+
return { success: true, task: updated };
|
|
460
|
+
},
|
|
461
|
+
});
|
|
462
|
+
export const taskListTool = tool({
|
|
463
|
+
name: 'task_list',
|
|
464
|
+
description: 'List tasks from the shared task board. Optionally filter by status or assignee.',
|
|
465
|
+
inputSchema: z.object({
|
|
466
|
+
status: z.enum(['open', 'claimed', 'in_progress', 'completed', 'failed']).optional().describe('Filter by status'),
|
|
467
|
+
assignee: z.string().optional().describe('Filter by assignee agent ID'),
|
|
468
|
+
}),
|
|
469
|
+
execute: async ({ status, assignee }) => {
|
|
470
|
+
const board = TaskBoard.getInstance();
|
|
471
|
+
const tasks = board.list({ status, assignee });
|
|
472
|
+
const stats = board.getStats();
|
|
473
|
+
return { tasks, stats };
|
|
474
|
+
},
|
|
475
|
+
});
|
|
226
476
|
// ─── Export ───
|
|
227
477
|
export const advancedTools = [
|
|
228
478
|
todoWriteTool,
|
|
229
479
|
notebookEditTool,
|
|
230
480
|
monitorTool,
|
|
231
481
|
agentTool,
|
|
482
|
+
sendMessageTool,
|
|
483
|
+
taskCreateTool,
|
|
484
|
+
taskClaimTool,
|
|
485
|
+
taskUpdateTool,
|
|
486
|
+
taskListTool,
|
|
232
487
|
];
|