tiger-agent 0.2.4 → 0.3.1
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 +10 -0
- package/README.md +264 -4
- package/package.json +1 -1
- package/src/agent/contextFileMirrors.js +39 -0
- package/src/agent/contextFiles.js +4 -1
- package/src/agent/mainAgent.js +4 -3
- package/src/agent/reflectionAgent.js +3 -2
- package/src/agent/skills.js +1 -1
- package/src/apiProviders.js +8 -9
- package/src/apiProviders.js.bak +222 -0
- package/src/cli.js +4 -0
- package/src/config.js +11 -0
- package/src/llmClient.js +11 -1
- package/src/swarm/agentRuntime.js +699 -0
- package/src/swarm/agentRuntime.js.bak +456 -0
- package/src/swarm/configStore.js +360 -0
- package/src/swarm/index.js +25 -0
- package/src/swarm/taskBus.js +246 -0
- package/src/telegram/bot.js +329 -1
package/src/telegram/bot.js
CHANGED
|
@@ -5,6 +5,26 @@ const { telegramBotToken } = require('../config');
|
|
|
5
5
|
const { handleMessage } = require('../agent/mainAgent');
|
|
6
6
|
const tokenManager = require('../tokenManager');
|
|
7
7
|
const { getProvider } = require('../apiProviders');
|
|
8
|
+
const {
|
|
9
|
+
ensureSwarmLayout,
|
|
10
|
+
runTigerFlow,
|
|
11
|
+
continueTask,
|
|
12
|
+
cancelTask,
|
|
13
|
+
deleteTask,
|
|
14
|
+
askAgent,
|
|
15
|
+
getAgentsStatus,
|
|
16
|
+
getStatusSummary
|
|
17
|
+
} = require('../swarm');
|
|
18
|
+
const {
|
|
19
|
+
ensureSwarmConfigLayout,
|
|
20
|
+
listArchitectureFiles,
|
|
21
|
+
listTaskStyleFiles,
|
|
22
|
+
readArchitectureText,
|
|
23
|
+
writeArchitectureText,
|
|
24
|
+
readTaskStyleText,
|
|
25
|
+
writeTaskStyleText,
|
|
26
|
+
updateDefaultStyleArchitecture
|
|
27
|
+
} = require('../swarm/configStore');
|
|
8
28
|
|
|
9
29
|
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
10
30
|
|
|
@@ -73,6 +93,16 @@ function handleApiCommand(arg) {
|
|
|
73
93
|
if (!result.ok) return `❌ Switch failed: ${result.error}`;
|
|
74
94
|
|
|
75
95
|
const p = getProvider(target);
|
|
96
|
+
const overLimit = tokenManager.isOverLimit(target);
|
|
97
|
+
if (overLimit) {
|
|
98
|
+
return [
|
|
99
|
+
`✅ Active provider set to *${p.name}* (\`${target}\`)`,
|
|
100
|
+
`Model: \`${p.chatModel}\``,
|
|
101
|
+
'',
|
|
102
|
+
`⚠️ *${target}* is currently over its daily token limit, so requests will fall back to another provider.`,
|
|
103
|
+
`Use \`/limit ${target} 0\` (unlimited) or raise its limit, then try again.`
|
|
104
|
+
].join('\n');
|
|
105
|
+
}
|
|
76
106
|
return `✅ Switched to *${p.name}* (\`${target}\`)\nModel: \`${p.chatModel}\``;
|
|
77
107
|
}
|
|
78
108
|
|
|
@@ -137,13 +167,22 @@ function startTelegramBot() {
|
|
|
137
167
|
throw new Error('TELEGRAM_BOT_TOKEN is empty.');
|
|
138
168
|
}
|
|
139
169
|
|
|
170
|
+
ensureSwarmLayout();
|
|
171
|
+
ensureSwarmConfigLayout();
|
|
140
172
|
const bot = new TelegramBot(telegramBotToken, { polling: true });
|
|
173
|
+
let swarmEnabled = true;
|
|
141
174
|
|
|
142
175
|
// Register commands so Telegram shows the list when user types /
|
|
143
176
|
bot.setMyCommands([
|
|
144
177
|
{ command: 'api', description: 'Show or switch active API provider' },
|
|
145
178
|
{ command: 'tokens', description: 'Show token usage for today' },
|
|
146
179
|
{ command: 'limit', description: 'Show or set daily token limit per provider' },
|
|
180
|
+
{ command: 'swarm', description: 'Enable or disable agent swarm' },
|
|
181
|
+
{ command: 'status', description: 'Show swarm task status' },
|
|
182
|
+
{ command: 'task', description: 'Continue a failed swarm task' },
|
|
183
|
+
{ command: 'architecture', description: 'View/update swarm architecture YAML' },
|
|
184
|
+
{ command: 'taskstyle', description: 'View/update task style YAML' },
|
|
185
|
+
{ command: 'agents', description: 'Show swarm agents' },
|
|
147
186
|
{ command: 'help', description: 'Show all available commands' }
|
|
148
187
|
]).catch((err) => {
|
|
149
188
|
process.stderr.write(`[telegram] setMyCommands failed: ${err.message}\n`);
|
|
@@ -186,6 +225,245 @@ function startTelegramBot() {
|
|
|
186
225
|
return;
|
|
187
226
|
}
|
|
188
227
|
|
|
228
|
+
if (text.startsWith('/swarm')) {
|
|
229
|
+
const arg = text.slice(6).trim().toLowerCase();
|
|
230
|
+
if (!arg) {
|
|
231
|
+
await safeSend(bot, chatId, `🐯 Swarm is currently *${swarmEnabled ? 'ON' : 'OFF'}*.\nUse \`/swarm on\` or \`/swarm off\`.`, MD);
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
if (arg === 'on') {
|
|
235
|
+
swarmEnabled = true;
|
|
236
|
+
await safeSend(bot, chatId, '✅ Swarm routing is now *ON*', MD);
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
if (arg === 'off') {
|
|
240
|
+
swarmEnabled = false;
|
|
241
|
+
await safeSend(bot, chatId, '✅ Swarm routing is now *OFF*\\.\nNew messages will go to the regular Tiger agent\\.', { parse_mode: 'MarkdownV2' });
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
await safeSend(bot, chatId, 'Usage: `/swarm on` or `/swarm off`', MD);
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (text.startsWith('/architecture')) {
|
|
249
|
+
const arg = text.slice('/architecture'.length).trim();
|
|
250
|
+
try {
|
|
251
|
+
if (!arg || /^list$/i.test(arg)) {
|
|
252
|
+
const files = listArchitectureFiles();
|
|
253
|
+
const lines = ['🧩 *Architecture Files*', ''];
|
|
254
|
+
for (const f of files) lines.push(`- \`${f}\``);
|
|
255
|
+
lines.push('');
|
|
256
|
+
lines.push('Use `/architecture show <file>`');
|
|
257
|
+
lines.push('Use `/architecture write <file>` then newline + full YAML');
|
|
258
|
+
lines.push('Use `/architecture use <file>` to set default task style architecture');
|
|
259
|
+
await safeSend(bot, chatId, lines.join('\n'), MD);
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const showMatch = arg.match(/^show\s+(\S+)$/i);
|
|
264
|
+
if (showMatch) {
|
|
265
|
+
const file = showMatch[1];
|
|
266
|
+
const yaml = readArchitectureText(file);
|
|
267
|
+
await safeSend(bot, chatId, `Architecture: ${file}\n\n${yaml}`);
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const useMatch = arg.match(/^use\s+(\S+)$/i);
|
|
272
|
+
if (useMatch) {
|
|
273
|
+
const file = useMatch[1];
|
|
274
|
+
updateDefaultStyleArchitecture(file);
|
|
275
|
+
await safeSend(bot, chatId, `✅ default task style now uses architecture \`${file}\``, MD);
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const writeMatch = text.match(/^\/architecture\s+write\s+(\S+)\s*\n([\s\S]+)$/i);
|
|
280
|
+
if (writeMatch) {
|
|
281
|
+
const file = writeMatch[1];
|
|
282
|
+
const yaml = writeMatch[2];
|
|
283
|
+
writeArchitectureText(file, yaml);
|
|
284
|
+
await safeSend(bot, chatId, `✅ wrote architecture \`${file}\``, MD);
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
await safeSend(
|
|
289
|
+
bot,
|
|
290
|
+
chatId,
|
|
291
|
+
'Usage:\n/architecture\n/architecture list\n/architecture show <file>\n/architecture use <file>\n/architecture write <file> + newline + yaml'
|
|
292
|
+
);
|
|
293
|
+
} catch (err) {
|
|
294
|
+
await safeSend(bot, chatId, `❌ /architecture failed: ${err.message}`);
|
|
295
|
+
}
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (text.startsWith('/taskstyle')) {
|
|
300
|
+
const arg = text.slice('/taskstyle'.length).trim();
|
|
301
|
+
try {
|
|
302
|
+
if (!arg || /^list$/i.test(arg)) {
|
|
303
|
+
const files = listTaskStyleFiles();
|
|
304
|
+
const lines = ['📝 *Task Style Files*', ''];
|
|
305
|
+
for (const f of files) lines.push(`- \`${f}\``);
|
|
306
|
+
lines.push('');
|
|
307
|
+
lines.push('Use `/taskstyle show <file>`');
|
|
308
|
+
lines.push('Use `/taskstyle write <file>` then newline + full YAML');
|
|
309
|
+
await safeSend(bot, chatId, lines.join('\n'), MD);
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const showMatch = arg.match(/^show\s+(\S+)$/i);
|
|
314
|
+
if (showMatch) {
|
|
315
|
+
const file = showMatch[1];
|
|
316
|
+
const yaml = readTaskStyleText(file);
|
|
317
|
+
await safeSend(bot, chatId, `Task style: ${file}\n\n${yaml}`);
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const writeMatch = text.match(/^\/taskstyle\s+write\s+(\S+)\s*\n([\s\S]+)$/i);
|
|
322
|
+
if (writeMatch) {
|
|
323
|
+
const file = writeMatch[1];
|
|
324
|
+
const yaml = writeMatch[2];
|
|
325
|
+
writeTaskStyleText(file, yaml);
|
|
326
|
+
await safeSend(bot, chatId, `✅ wrote task style \`${file}\``, MD);
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
await safeSend(
|
|
331
|
+
bot,
|
|
332
|
+
chatId,
|
|
333
|
+
'Usage:\n/taskstyle\n/taskstyle list\n/taskstyle show <file>\n/taskstyle write <file> + newline + yaml'
|
|
334
|
+
);
|
|
335
|
+
} catch (err) {
|
|
336
|
+
await safeSend(bot, chatId, `❌ /taskstyle failed: ${err.message}`);
|
|
337
|
+
}
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
if (text === '/agents') {
|
|
342
|
+
const agents = getAgentsStatus();
|
|
343
|
+
const lines = ['🤖 *Agents*', ''];
|
|
344
|
+
for (const a of agents) {
|
|
345
|
+
lines.push(`${a.alive ? '✅' : '❌'} \`${a.name}\` - ${a.label}`);
|
|
346
|
+
}
|
|
347
|
+
await safeSend(bot, chatId, lines.join('\n'), MD);
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
if (text === '/status') {
|
|
352
|
+
const status = getStatusSummary();
|
|
353
|
+
const lines = ['📋 *Swarm Status*', ''];
|
|
354
|
+
lines.push(`in_progress: *${status.in_progress.length}*`);
|
|
355
|
+
for (const t of status.in_progress.slice(0, 10)) {
|
|
356
|
+
lines.push(`- \`${t.task_id}\` → \`${t.next_agent}\` | ${t.goal}`);
|
|
357
|
+
}
|
|
358
|
+
lines.push(`pending: *${status.pending.length}*`);
|
|
359
|
+
for (const t of status.pending.slice(0, 10)) {
|
|
360
|
+
lines.push(`- \`${t.task_id}\` → \`${t.next_agent}\` | ${t.goal}`);
|
|
361
|
+
}
|
|
362
|
+
lines.push(`done: *${status.done.length}* | failed: *${status.failed.length}*`);
|
|
363
|
+
await safeSend(bot, chatId, lines.join('\n'), MD);
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
if (text.startsWith('/cancel ')) {
|
|
368
|
+
const taskId = text.slice(8).trim();
|
|
369
|
+
if (!taskId) {
|
|
370
|
+
await safeSend(bot, chatId, 'Usage: `/cancel task_xxx`', MD);
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
const out = cancelTask(taskId, 'tiger');
|
|
374
|
+
if (!out.ok) {
|
|
375
|
+
await safeSend(bot, chatId, `❌ ${out.error}`);
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
await safeSend(bot, chatId, `✅ Cancelled \`${taskId}\``, MD);
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
if (text.startsWith('/task')) {
|
|
383
|
+
const arg = text.slice(5).trim();
|
|
384
|
+
if (!arg || /^list$/i.test(arg)) {
|
|
385
|
+
const status = getStatusSummary();
|
|
386
|
+
const lines = ['🗂️ *Swarm Tasks*', ''];
|
|
387
|
+
for (const bucketName of ['in_progress', 'pending', 'failed', 'done']) {
|
|
388
|
+
const tasks = Array.isArray(status[bucketName]) ? status[bucketName] : [];
|
|
389
|
+
lines.push(`${bucketName}: *${tasks.length}*`);
|
|
390
|
+
for (const t of tasks.slice(0, 10)) {
|
|
391
|
+
lines.push(`- \`${t.task_id}\` → \`${t.next_agent}\` | ${t.goal}`);
|
|
392
|
+
}
|
|
393
|
+
if (tasks.length > 10) lines.push(`- ... and ${tasks.length - 10} more`);
|
|
394
|
+
}
|
|
395
|
+
lines.push('');
|
|
396
|
+
lines.push('Use `/task continue <id>`, `/task retry <id>`, `/task delete <id>`');
|
|
397
|
+
await safeSend(bot, chatId, lines.join('\n'), MD);
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
const actionMatch = arg.match(/^(continue|retry|delete)\s+(\S+)$/i);
|
|
402
|
+
if (!actionMatch) {
|
|
403
|
+
await safeSend(bot, chatId, 'Usage: `/task` or `/task <continue|retry|delete> task_xxx`', MD);
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
const action = actionMatch[1].toLowerCase();
|
|
407
|
+
const taskId = actionMatch[2];
|
|
408
|
+
|
|
409
|
+
if (action === 'delete') {
|
|
410
|
+
const out = deleteTask(taskId);
|
|
411
|
+
if (!out.ok) {
|
|
412
|
+
await safeSend(bot, chatId, `❌ ${out.error}`);
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
await safeSend(bot, chatId, `🗑️ Deleted \`${taskId}\` from *${out.bucket}*`, MD);
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
const progressMarks = new Set();
|
|
420
|
+
try {
|
|
421
|
+
await safeSendTyping(bot, chatId);
|
|
422
|
+
const out = await continueTask(taskId, {
|
|
423
|
+
onProgress: ({ phase, agent, task }) => {
|
|
424
|
+
if (phase === 'worker_done' && task) {
|
|
425
|
+
const key = `${agent}:${task.next_agent}:${task.thread ? task.thread.length : 0}`;
|
|
426
|
+
if (progressMarks.has(key)) return;
|
|
427
|
+
progressMarks.add(key);
|
|
428
|
+
void safeSend(bot, chatId, `Tiger: resumed task ${task.task_id} - ${agent} finished. Next: ${task.next_agent}.`);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
});
|
|
432
|
+
if (out.ok) {
|
|
433
|
+
await safeSend(bot, chatId, out.result || '(empty result)');
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
await safeSend(
|
|
437
|
+
bot,
|
|
438
|
+
chatId,
|
|
439
|
+
`⚠️ ${action} failed: ${out.error || 'unknown error'}${out.task ? `\nTask: \`${out.task.task_id}\` next=\`${out.task.next_agent}\` status=\`${out.task.status}\`` : ''}`,
|
|
440
|
+
MD
|
|
441
|
+
);
|
|
442
|
+
} catch (err) {
|
|
443
|
+
await safeSend(bot, chatId, `⚠️ /task ${action} failed: ${err.message}`);
|
|
444
|
+
}
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
if (text.startsWith('/ask ')) {
|
|
449
|
+
const parts = text.slice(5).trim();
|
|
450
|
+
const [agentNameRaw, ...rest] = parts.split(/\s+/);
|
|
451
|
+
const agentName = String(agentNameRaw || '').toLowerCase();
|
|
452
|
+
const prompt = rest.join(' ').trim();
|
|
453
|
+
if (!agentName || !prompt) {
|
|
454
|
+
await safeSend(bot, chatId, 'Usage: `/ask <designer|senior_eng|spec_writer|scout|coder|critic> <question>`', MD);
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
try {
|
|
458
|
+
await safeSendTyping(bot, chatId);
|
|
459
|
+
const answer = await askAgent(agentName, prompt);
|
|
460
|
+
await safeSend(bot, chatId, answer || '(empty reply)');
|
|
461
|
+
} catch (err) {
|
|
462
|
+
await safeSend(bot, chatId, `⚠️ /ask failed: ${err.message}`);
|
|
463
|
+
}
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
|
|
189
467
|
if (text === '/help' || text === '/start') {
|
|
190
468
|
const helpText = [
|
|
191
469
|
'🤖 *Tiger Bot Commands*',
|
|
@@ -195,6 +473,23 @@ function startTelegramBot() {
|
|
|
195
473
|
'/tokens \\- Show token usage for today',
|
|
196
474
|
'/limit \\- Show daily token limits per provider',
|
|
197
475
|
'/limit `<name> <n>` \\- Set limit \\(0 = unlimited\\)',
|
|
476
|
+
'/swarm \\- Show swarm on/off status',
|
|
477
|
+
'/swarm `<on|off>` \\- Enable or disable swarm routing',
|
|
478
|
+
'/status \\- Show swarm task status',
|
|
479
|
+
'/task \\- List swarm tasks',
|
|
480
|
+
'/task `continue <task_id>` \\- Resume a failed swarm task',
|
|
481
|
+
'/task `retry <task_id>` \\- Alias of continue',
|
|
482
|
+
'/task `delete <task_id>` \\- Delete a swarm task file',
|
|
483
|
+
'/architecture \\- List architecture YAML files',
|
|
484
|
+
'/architecture `show <file>` \\- Show architecture YAML',
|
|
485
|
+
'/architecture `use <file>` \\- Set default architecture',
|
|
486
|
+
'/architecture `write <file>` + newline + yaml \\- Save architecture YAML',
|
|
487
|
+
'/taskstyle \\- List task style YAML files',
|
|
488
|
+
'/taskstyle `show <file>` \\- Show task style YAML',
|
|
489
|
+
'/taskstyle `write <file>` + newline + yaml \\- Save task style YAML',
|
|
490
|
+
'/agents \\- Show internal swarm agents',
|
|
491
|
+
'/cancel `<task_id>` \\- Cancel a swarm task',
|
|
492
|
+
'/ask `<agent> <question>` \\- Ask a specific internal agent',
|
|
198
493
|
'/help \\- Show this message',
|
|
199
494
|
'',
|
|
200
495
|
'*Available providers:* ' + KNOWN_PROVIDERS.join(', ')
|
|
@@ -214,9 +509,42 @@ function startTelegramBot() {
|
|
|
214
509
|
try {
|
|
215
510
|
await safeSendTyping(bot, chatId);
|
|
216
511
|
typingTimer = setInterval(() => safeSendTyping(bot, chatId), 4500);
|
|
512
|
+
if (!swarmEnabled) {
|
|
513
|
+
const reply = await handleMessage({ platform: 'telegram', userId, text });
|
|
514
|
+
clearInterval(typingTimer);
|
|
515
|
+
await safeSend(bot, chatId, reply);
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
const progressMarks = new Set();
|
|
519
|
+
const flowResult = await runTigerFlow(text, {
|
|
520
|
+
metadata: { platform: 'telegram', userId, chatId },
|
|
521
|
+
onProgress: ({ phase, agent, task }) => {
|
|
522
|
+
if (phase === 'task_created' && task && !progressMarks.has('created')) {
|
|
523
|
+
progressMarks.add('created');
|
|
524
|
+
void safeSend(bot, chatId, `Tiger: task created \\(${task.task_id}\\)\\. Starting Designer\\.`, { parse_mode: 'MarkdownV2' });
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
if (phase !== 'worker_done' || !task) return;
|
|
528
|
+
const key = `${agent}:${task.next_agent}:${task.thread ? task.thread.length : 0}`;
|
|
529
|
+
if (progressMarks.has(key)) return;
|
|
530
|
+
progressMarks.add(key);
|
|
531
|
+
let msg = `Tiger: ${agent} finished. Next: ${task.next_agent}.`;
|
|
532
|
+
if (agent === 'senior_eng' && task.next_agent === 'designer') {
|
|
533
|
+
msg = 'Tiger: Senior Eng requested changes. Designer is revising.';
|
|
534
|
+
} else if (agent === 'senior_eng' && task.next_agent === 'spec_writer') {
|
|
535
|
+
msg = 'Tiger: Senior Eng approved. Spec Writer is drafting the final spec.';
|
|
536
|
+
}
|
|
537
|
+
void safeSend(bot, chatId, msg);
|
|
538
|
+
}
|
|
539
|
+
});
|
|
217
540
|
|
|
218
|
-
const reply = await handleMessage({ platform: 'telegram', userId, text });
|
|
219
541
|
clearInterval(typingTimer);
|
|
542
|
+
if (flowResult.ok) {
|
|
543
|
+
await safeSend(bot, chatId, flowResult.result || '(empty result)');
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
const reply = await handleMessage({ platform: 'telegram', userId, text });
|
|
220
548
|
await safeSend(bot, chatId, reply);
|
|
221
549
|
} catch (err) {
|
|
222
550
|
if (typingTimer) clearInterval(typingTimer);
|