scalix 1.0.0 → 1.2.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.
@@ -0,0 +1,146 @@
1
+ // CJK/RTL text support utilities
2
+ const CJK_RANGES = [
3
+ [0x2E80, 0x2EFF], // CJK Radicals Supplement
4
+ [0x2F00, 0x2FDF], // Kangxi Radicals
5
+ [0x3000, 0x303F], // CJK Symbols and Punctuation
6
+ [0x3040, 0x309F], // Hiragana
7
+ [0x30A0, 0x30FF], // Katakana
8
+ [0x3100, 0x312F], // Bopomofo
9
+ [0x3130, 0x318F], // Hangul Compatibility Jamo
10
+ [0x3200, 0x32FF], // Enclosed CJK Letters and Months
11
+ [0x3400, 0x4DBF], // CJK Unified Ideographs Extension A
12
+ [0x4E00, 0x9FFF], // CJK Unified Ideographs
13
+ [0xAC00, 0xD7AF], // Hangul Syllables
14
+ [0xF900, 0xFAFF], // CJK Compatibility Ideographs
15
+ [0xFE30, 0xFE4F], // CJK Compatibility Forms
16
+ [0xFF00, 0xFFEF], // Halfwidth and Fullwidth Forms
17
+ [0x20000, 0x2A6DF], // CJK Unified Ideographs Extension B
18
+ [0x2A700, 0x2B73F], // CJK Unified Ideographs Extension C
19
+ [0x2B740, 0x2B81F], // CJK Unified Ideographs Extension D
20
+ ];
21
+ const RTL_RANGES = [
22
+ [0x0590, 0x05FF], // Hebrew
23
+ [0x0600, 0x06FF], // Arabic
24
+ [0x0700, 0x074F], // Syriac
25
+ [0x0750, 0x077F], // Arabic Supplement
26
+ [0x0780, 0x07BF], // Thaana
27
+ [0x07C0, 0x07FF], // NKo
28
+ [0x0800, 0x083F], // Samaritan
29
+ [0x0840, 0x085F], // Mandaic
30
+ [0x08A0, 0x08FF], // Arabic Extended-A
31
+ [0xFB50, 0xFDFF], // Arabic Presentation Forms-A
32
+ [0xFE70, 0xFEFF], // Arabic Presentation Forms-B
33
+ [0x10800, 0x1083F], // Cypriot Syllabary
34
+ ];
35
+ function inRange(code, ranges) {
36
+ for (const [lo, hi] of ranges) {
37
+ if (code >= lo && code <= hi)
38
+ return true;
39
+ }
40
+ return false;
41
+ }
42
+ export function isCJK(char) {
43
+ const code = char.codePointAt(0) ?? 0;
44
+ return inRange(code, CJK_RANGES);
45
+ }
46
+ export function isRTL(char) {
47
+ const code = char.codePointAt(0) ?? 0;
48
+ return inRange(code, RTL_RANGES);
49
+ }
50
+ export function getCharWidth(char) {
51
+ const code = char.codePointAt(0) ?? 0;
52
+ if (inRange(code, CJK_RANGES))
53
+ return 2;
54
+ if (code >= 0xD800 && code <= 0xDFFF)
55
+ return 0; // surrogate pair half
56
+ if (code >= 0x0300 && code <= 0x036F)
57
+ return 0; // combining diacritical marks
58
+ if (code >= 0x0900 && code <= 0x097F)
59
+ return 1; // Devanagari
60
+ if (code >= 0xFE00 && code <= 0xFE0F)
61
+ return 0; // variation selectors
62
+ if (code >= 0x200B && code <= 0x200F)
63
+ return 0; // zero-width chars
64
+ if (code >= 0x202A && code <= 0x202E)
65
+ return 0; // bidi marks
66
+ if (code >= 0x2060 && code <= 0x206F)
67
+ return 0; // invisible operators
68
+ return 1;
69
+ }
70
+ export function stringWidth(str) {
71
+ let width = 0;
72
+ for (const char of str) {
73
+ width += getCharWidth(char);
74
+ }
75
+ return width;
76
+ }
77
+ export function detectTextDirection(text) {
78
+ let rtlCount = 0;
79
+ let ltrCount = 0;
80
+ for (const char of text) {
81
+ if (isRTL(char))
82
+ rtlCount++;
83
+ else if (/[a-zA-Z]/.test(char))
84
+ ltrCount++;
85
+ }
86
+ if (rtlCount === 0 && ltrCount === 0)
87
+ return 'ltr';
88
+ if (rtlCount > 0 && ltrCount === 0)
89
+ return 'rtl';
90
+ if (rtlCount === 0 && ltrCount > 0)
91
+ return 'ltr';
92
+ return 'mixed';
93
+ }
94
+ export function wrapText(text, maxWidth) {
95
+ const lines = [];
96
+ let currentLine = '';
97
+ let currentWidth = 0;
98
+ for (const char of text) {
99
+ if (char === '\n') {
100
+ lines.push(currentLine);
101
+ currentLine = '';
102
+ currentWidth = 0;
103
+ continue;
104
+ }
105
+ const charWidth = getCharWidth(char);
106
+ if (currentWidth + charWidth > maxWidth && currentLine.length > 0) {
107
+ lines.push(currentLine);
108
+ currentLine = char;
109
+ currentWidth = charWidth;
110
+ }
111
+ else {
112
+ currentLine += char;
113
+ currentWidth += charWidth;
114
+ }
115
+ }
116
+ if (currentLine.length > 0) {
117
+ lines.push(currentLine);
118
+ }
119
+ return lines;
120
+ }
121
+ export function truncateText(text, maxWidth, ellipsis = '…') {
122
+ const ellipsisWidth = stringWidth(ellipsis);
123
+ let width = 0;
124
+ let result = '';
125
+ for (const char of text) {
126
+ const charWidth = getCharWidth(char);
127
+ if (width + charWidth + ellipsisWidth > maxWidth) {
128
+ return result + ellipsis;
129
+ }
130
+ result += char;
131
+ width += charWidth;
132
+ }
133
+ return result;
134
+ }
135
+ export function applyRTLMarks(text) {
136
+ const dir = detectTextDirection(text);
137
+ if (dir === 'rtl') {
138
+ return `‏${text}‏`;
139
+ }
140
+ if (dir === 'mixed') {
141
+ return text.replace(/[֐-׿؀-ۿ܀-ݏ]+/g, (match) => {
142
+ return `‏${match}‏`;
143
+ });
144
+ }
145
+ return text;
146
+ }
@@ -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: 'Spawn a sub-agent to handle a complex task in parallel or independently. The sub-agent has access to read/write tools but cannot spawn further agents. Returns the sub-agent\'s final response.',
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 to perform'),
176
- description: z.string().min(1).max(100).describe('Short description of the task (3-5 words)'),
177
- subagent_type: z.enum(['general', 'explore', 'plan']).optional().describe('Agent type: general (read+write), explore (read-only), plan (read-only analysis)'),
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 ({ prompt, description, subagent_type, model }) => {
181
- if (!parentAgentConfig) {
182
- return { error: 'Agent tool not configured. Parent agent config not set.' };
183
- }
184
- if (model && !parentAgentConfig.models.includes(model)) {
185
- return { error: `Unknown model "${model}". Available: ${parentAgentConfig.models.join(', ')}` };
186
- }
187
- const isReadOnly = subagent_type === 'explore' || subagent_type === 'plan';
188
- const toolLevel = isReadOnly ? 'read-only' : 'standard';
189
- // General sub-agents inherit parent's permission mode (capped at 'auto' — never yolo)
190
- // Read-only sub-agents always use 'plan' mode
191
- let subPermissionMode = 'plan';
192
- let subConfirmToolCall;
193
- if (!isReadOnly && parentAgentConfig.permissionMode) {
194
- const parentMode = parentAgentConfig.permissionMode;
195
- subPermissionMode = parentMode === 'yolo' ? 'auto' : parentMode;
196
- subConfirmToolCall = parentAgentConfig.confirmToolCall;
197
- }
198
- logger.info('Spawning sub-agent', { description, type: subagent_type || 'general', permissionMode: subPermissionMode });
199
- try {
200
- const result = await spawnSubAgent(parentAgentConfig, prompt, {
201
- model,
202
- tools: toolLevel,
203
- permissionMode: subPermissionMode,
204
- confirmToolCall: subConfirmToolCall,
205
- maxSteps: subagent_type === 'explore' ? _limits.exploreMaxSteps : _limits.subAgentMaxSteps,
206
- instructions: subagent_type === 'plan'
207
- ? 'You are a planning assistant. Analyze the codebase and design an implementation approach. Be thorough and specific.'
208
- : subagent_type === 'explore'
209
- ? 'You are a code exploration assistant. Search and read files to answer questions. Be concise and factual.'
210
- : 'You are a focused assistant. Complete the task efficiently. Be concise.',
211
- timeout: _limits.agentToolTimeoutMs,
212
- });
213
- return {
214
- description,
215
- response: result.response,
216
- model: result.model,
217
- steps: result.steps,
218
- tokenEstimate: result.tokenEstimate,
219
- };
220
- }
221
- catch (error) {
222
- return { error: `Sub-agent failed: ${error.message}` };
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
  ];