tiger-agent 0.2.5 → 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 +260 -4
- package/package.json +1 -1
- 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
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { chatCompletion } = require('../llmClient');
|
|
6
|
+
const {
|
|
7
|
+
swarmAgentTimeoutMs,
|
|
8
|
+
swarmRouteOnProviderError,
|
|
9
|
+
swarmDefaultFlow,
|
|
10
|
+
swarmFirstAgentPolicy,
|
|
11
|
+
swarmFirstAgent
|
|
12
|
+
} = require('../config');
|
|
13
|
+
const {
|
|
14
|
+
AGENTS_DIR,
|
|
15
|
+
ensureSwarmLayout,
|
|
16
|
+
createTask,
|
|
17
|
+
listInProgressTasks,
|
|
18
|
+
listTasks,
|
|
19
|
+
findTask,
|
|
20
|
+
appendThread,
|
|
21
|
+
claimPendingTask,
|
|
22
|
+
releaseTask,
|
|
23
|
+
cancelTask,
|
|
24
|
+
deleteTask
|
|
25
|
+
} = require('./taskBus');
|
|
26
|
+
|
|
27
|
+
const AGENT_DEFS = {
|
|
28
|
+
tiger: { label: 'Tiger', kind: 'orchestrator' },
|
|
29
|
+
designer: { label: 'Designer', kind: 'worker' },
|
|
30
|
+
senior_eng: { label: 'Senior Eng', kind: 'worker' },
|
|
31
|
+
spec_writer: { label: 'Spec Writer', kind: 'worker' },
|
|
32
|
+
scout: { label: 'Scout', kind: 'worker' },
|
|
33
|
+
coder: { label: 'Coder', kind: 'worker' },
|
|
34
|
+
critic: { label: 'Critic', kind: 'worker' }
|
|
35
|
+
};
|
|
36
|
+
const WORKER_AGENT_NAMES = Object.keys(AGENT_DEFS).filter((n) => n !== 'tiger');
|
|
37
|
+
|
|
38
|
+
function safeJsonParse(text, fallback) {
|
|
39
|
+
try {
|
|
40
|
+
return JSON.parse(text);
|
|
41
|
+
} catch (err) {
|
|
42
|
+
return fallback;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function getAgentSoul(agentName) {
|
|
47
|
+
const full = path.join(AGENTS_DIR, agentName, 'soul.md');
|
|
48
|
+
if (!fs.existsSync(full)) return '';
|
|
49
|
+
try {
|
|
50
|
+
return fs.readFileSync(full, 'utf8');
|
|
51
|
+
} catch (err) {
|
|
52
|
+
return '';
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function renderThread(task) {
|
|
57
|
+
return (Array.isArray(task.thread) ? task.thread : [])
|
|
58
|
+
.map((m) => `- [${m.at || ''}] ${m.by || 'unknown'}: ${m.msg || ''}`)
|
|
59
|
+
.join('\n');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function llmText(system, user, stepLabel) {
|
|
63
|
+
// Step-level timeout: each LLM call gets its own fresh timer (hook reset)
|
|
64
|
+
const stepTimeoutMs = Number(process.env.SWARM_AGENT_TIMEOUT_MS || swarmAgentTimeoutMs || 720000);
|
|
65
|
+
const label = stepLabel || 'llmText';
|
|
66
|
+
|
|
67
|
+
const llmCall = chatCompletion([
|
|
68
|
+
{ role: 'system', content: system },
|
|
69
|
+
{ role: 'user', content: user }
|
|
70
|
+
], {
|
|
71
|
+
fallbackOnAnyProviderError: swarmRouteOnProviderError
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// Fresh timeout per LLM step — resets on every hook call
|
|
75
|
+
const out = await withTimeout(llmCall, stepTimeoutMs, `[step:${label}] LLM call`);
|
|
76
|
+
return String(out && out.content ? out.content : '').trim();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function designerStep(task) {
|
|
80
|
+
const soul = getAgentSoul('designer');
|
|
81
|
+
const system = [
|
|
82
|
+
'You are Designer in a multi-agent swarm.',
|
|
83
|
+
'Propose or revise a solution.',
|
|
84
|
+
'Be concrete. Mention architecture, flow, risks, and tradeoffs.',
|
|
85
|
+
'Do not approve your own design.',
|
|
86
|
+
soul
|
|
87
|
+
].join('\n\n');
|
|
88
|
+
const user = [
|
|
89
|
+
`Goal: ${task.goal}`,
|
|
90
|
+
`Flow: ${task.flow || 'design'}`,
|
|
91
|
+
'Task thread so far:',
|
|
92
|
+
renderThread(task),
|
|
93
|
+
'Return a revised design proposal for Senior Eng review.'
|
|
94
|
+
].join('\n\n');
|
|
95
|
+
const text = await llmText(system, user, 'designer:propose');
|
|
96
|
+
appendThread(task, 'designer', text || 'proposed initial design');
|
|
97
|
+
task.next_agent = 'senior_eng';
|
|
98
|
+
task.status = 'pending';
|
|
99
|
+
return task;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async function seniorEngStep(task) {
|
|
103
|
+
const soul = getAgentSoul('senior_eng');
|
|
104
|
+
const system = [
|
|
105
|
+
'You are Senior Engineer reviewer in a swarm.',
|
|
106
|
+
'Review the latest proposed design critically.',
|
|
107
|
+
'Return strict JSON only: {"approved":true|false,"feedback":"...","must_fix":["..."]}.',
|
|
108
|
+
'If approving, feedback should summarize key safeguards.',
|
|
109
|
+
soul
|
|
110
|
+
].join('\n\n');
|
|
111
|
+
const user = [
|
|
112
|
+
`Goal: ${task.goal}`,
|
|
113
|
+
'Conversation thread:',
|
|
114
|
+
renderThread(task)
|
|
115
|
+
].join('\n\n');
|
|
116
|
+
const raw = await llmText(system, user, 'senior_eng:review');
|
|
117
|
+
const parsed = safeJsonParse(raw, null);
|
|
118
|
+
|
|
119
|
+
let approved = false;
|
|
120
|
+
let feedback = raw || 'review completed';
|
|
121
|
+
let mustFix = [];
|
|
122
|
+
|
|
123
|
+
if (parsed && typeof parsed === 'object') {
|
|
124
|
+
approved = Boolean(parsed.approved);
|
|
125
|
+
feedback = String(parsed.feedback || feedback).trim();
|
|
126
|
+
mustFix = Array.isArray(parsed.must_fix) ? parsed.must_fix.map((x) => String(x).trim()).filter(Boolean) : [];
|
|
127
|
+
} else {
|
|
128
|
+
approved = /approved\s*✅?|approve\b/i.test(raw) && !/reject/i.test(raw);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const msg = approved
|
|
132
|
+
? `approved ✅ ${feedback}`.trim()
|
|
133
|
+
: `rejected - ${feedback}${mustFix.length ? ` | must_fix: ${mustFix.join('; ')}` : ''}`.trim();
|
|
134
|
+
|
|
135
|
+
appendThread(task, 'senior_eng', msg);
|
|
136
|
+
task.next_agent = approved ? 'spec_writer' : 'designer';
|
|
137
|
+
task.status = 'pending';
|
|
138
|
+
return task;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async function specWriterStep(task) {
|
|
142
|
+
const soul = getAgentSoul('spec_writer');
|
|
143
|
+
const system = [
|
|
144
|
+
'You are Spec Writer in a swarm.',
|
|
145
|
+
'Write a clear formal implementation/design spec from the approved discussion.',
|
|
146
|
+
'Use structured markdown with scope, architecture, flow, edge cases, and next steps.',
|
|
147
|
+
soul
|
|
148
|
+
].join('\n\n');
|
|
149
|
+
const user = [
|
|
150
|
+
`Goal: ${task.goal}`,
|
|
151
|
+
'Thread:',
|
|
152
|
+
renderThread(task),
|
|
153
|
+
'Write the final spec now.'
|
|
154
|
+
].join('\n\n');
|
|
155
|
+
const spec = await llmText(system, user, 'spec_writer:write');
|
|
156
|
+
appendThread(task, 'spec_writer', 'formal spec written');
|
|
157
|
+
task.result = spec || '(empty spec)';
|
|
158
|
+
task.next_agent = 'tiger';
|
|
159
|
+
task.status = 'pending';
|
|
160
|
+
return task;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async function genericWorkerStep(task, agentName, roleHint) {
|
|
164
|
+
const soul = getAgentSoul(agentName);
|
|
165
|
+
const text = await llmText(
|
|
166
|
+
[
|
|
167
|
+
`You are ${agentName} in Tiger swarm.`,
|
|
168
|
+
roleHint,
|
|
169
|
+
'Respond concisely and practically.',
|
|
170
|
+
soul
|
|
171
|
+
].join('\n\n'),
|
|
172
|
+
[`Goal: ${task.goal}`, 'Thread:', renderThread(task)].join('\n\n'),
|
|
173
|
+
`${agentName}:step`
|
|
174
|
+
);
|
|
175
|
+
appendThread(task, agentName, text || `${agentName} completed step`);
|
|
176
|
+
task.status = 'pending';
|
|
177
|
+
task.next_agent =
|
|
178
|
+
agentName === 'scout' ? 'coder' :
|
|
179
|
+
agentName === 'coder' ? 'critic' :
|
|
180
|
+
agentName === 'critic' ? 'tiger' : 'tiger';
|
|
181
|
+
if (agentName === 'critic') {
|
|
182
|
+
task.result = text || task.result || '';
|
|
183
|
+
}
|
|
184
|
+
return task;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
async function processWorkerTask(agentName, task) {
|
|
188
|
+
if (agentName === 'designer') return designerStep(task);
|
|
189
|
+
if (agentName === 'senior_eng') return seniorEngStep(task);
|
|
190
|
+
if (agentName === 'spec_writer') return specWriterStep(task);
|
|
191
|
+
if (agentName === 'scout') return genericWorkerStep(task, 'scout', 'Research and verify from multiple angles.');
|
|
192
|
+
if (agentName === 'coder') return genericWorkerStep(task, 'coder', 'Propose implementation plan and code-level steps.');
|
|
193
|
+
if (agentName === 'critic') return genericWorkerStep(task, 'critic', 'Review for defects, risks, and regressions.');
|
|
194
|
+
throw new Error(`Unsupported worker: ${agentName}`);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function withTimeout(promise, timeoutMs, label) {
|
|
198
|
+
const ms = Number(timeoutMs || 0);
|
|
199
|
+
if (!Number.isFinite(ms) || ms <= 0) return promise;
|
|
200
|
+
return new Promise((resolve, reject) => {
|
|
201
|
+
const timer = setTimeout(() => reject(new Error(`${label} timeout after ${ms}ms`)), ms);
|
|
202
|
+
Promise.resolve(promise).then(
|
|
203
|
+
(value) => {
|
|
204
|
+
clearTimeout(timer);
|
|
205
|
+
resolve(value);
|
|
206
|
+
},
|
|
207
|
+
(err) => {
|
|
208
|
+
clearTimeout(timer);
|
|
209
|
+
reject(err);
|
|
210
|
+
}
|
|
211
|
+
);
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
async function runWorkerTurn(agentName) {
|
|
216
|
+
ensureSwarmLayout();
|
|
217
|
+
const claim = claimPendingTask(agentName);
|
|
218
|
+
if (!claim) return { ok: true, idle: true, agent: agentName };
|
|
219
|
+
|
|
220
|
+
// ✅ FIX: Per-agent timeout — reset independently for each agent
|
|
221
|
+
// Each agent gets its own fresh timeout (not shared global timer)
|
|
222
|
+
const perAgentTimeout = Number(
|
|
223
|
+
process.env.SWARM_AGENT_TIMEOUT_MS || swarmAgentTimeoutMs || 720000
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
let { task, filePath } = claim;
|
|
227
|
+
try {
|
|
228
|
+
task = await withTimeout(
|
|
229
|
+
processWorkerTask(agentName, task),
|
|
230
|
+
perAgentTimeout,
|
|
231
|
+
`swarm agent ${agentName}`
|
|
232
|
+
);
|
|
233
|
+
const out = releaseTask(task, filePath, task.status === 'failed' ? 'failed' : 'pending');
|
|
234
|
+
return { ok: true, idle: false, agent: agentName, task: out.task };
|
|
235
|
+
} catch (err) {
|
|
236
|
+
appendThread(task, agentName, `error: ${err.message}`);
|
|
237
|
+
if (!task.metadata || typeof task.metadata !== 'object') task.metadata = {};
|
|
238
|
+
task.metadata.last_failed_agent = agentName;
|
|
239
|
+
task.metadata.last_error = String(err && err.message ? err.message : 'unknown error');
|
|
240
|
+
task.status = 'failed';
|
|
241
|
+
task.next_agent = 'tiger';
|
|
242
|
+
const out = releaseTask(task, filePath, 'failed');
|
|
243
|
+
return { ok: false, idle: false, agent: agentName, error: err.message, task: out.task };
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function pickFlowFirstAgent(flow) {
|
|
248
|
+
return flow === 'research_build' ? 'scout' : 'designer';
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function pickAutoFirstAgent(goal, flow) {
|
|
252
|
+
const text = String(goal || '').toLowerCase();
|
|
253
|
+
if (flow === 'research_build') return 'scout';
|
|
254
|
+
if (/(research|investigate|compare|look up|search|verify|news|find out)/i.test(text)) return 'scout';
|
|
255
|
+
if (/(bug|fix|error|exception|stack trace|refactor|implement|write code|code change|patch)/i.test(text)) return 'coder';
|
|
256
|
+
if (/(review|audit|critique|risk check)/i.test(text)) return 'critic';
|
|
257
|
+
if (/(spec|prd|requirements|design doc|document)/i.test(text)) return 'designer';
|
|
258
|
+
return 'designer';
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function resolveFirstAgent(goal, flow, opts = {}) {
|
|
262
|
+
const policy = String(opts.firstAgentPolicy || swarmFirstAgentPolicy || 'auto').toLowerCase();
|
|
263
|
+
const fixed = String(opts.firstAgent || swarmFirstAgent || '').toLowerCase();
|
|
264
|
+
|
|
265
|
+
if (WORKER_AGENT_NAMES.includes(policy)) return policy;
|
|
266
|
+
if (policy === 'fixed' && WORKER_AGENT_NAMES.includes(fixed)) return fixed;
|
|
267
|
+
if (policy === 'flow') return pickFlowFirstAgent(flow);
|
|
268
|
+
return pickAutoFirstAgent(goal, flow);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function extractTigerResult(taskId) {
|
|
272
|
+
const found = findTask(taskId);
|
|
273
|
+
if (!found) return { ok: false, error: 'Task not found' };
|
|
274
|
+
const { filePath, task, bucket } = found;
|
|
275
|
+
if (task.next_agent !== 'tiger') {
|
|
276
|
+
return { ok: false, error: `Task not ready for tiger (next_agent=${task.next_agent})`, task };
|
|
277
|
+
}
|
|
278
|
+
task.status = task.status === 'failed' ? 'failed' : 'done';
|
|
279
|
+
const targetBucket = task.status === 'failed' ? 'failed' : 'done';
|
|
280
|
+
const nextPath = path.join(path.dirname(filePath), '..', targetBucket, `${task.task_id}.json`);
|
|
281
|
+
void nextPath; // path computation not used directly; move handled via releaseTask.
|
|
282
|
+
const released = releaseTask(task, filePath, targetBucket);
|
|
283
|
+
return { ok: true, task: released.task, bucketFrom: bucket, bucketTo: targetBucket };
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
async function runTaskToTiger(taskId, opts = {}) {
|
|
287
|
+
const rawMaxTurns = Number(opts.maxTurns);
|
|
288
|
+
const maxTurns = Number.isFinite(rawMaxTurns) && rawMaxTurns > 0 ? Math.floor(rawMaxTurns) : null;
|
|
289
|
+
const onProgress = typeof opts.onProgress === 'function' ? opts.onProgress : null;
|
|
290
|
+
|
|
291
|
+
for (let i = 0; maxTurns == null || i < maxTurns; i += 1) {
|
|
292
|
+
const found = findTask(taskId);
|
|
293
|
+
if (!found) return { ok: false, error: 'Task disappeared' };
|
|
294
|
+
const { task } = found;
|
|
295
|
+
if (task.next_agent === 'tiger') {
|
|
296
|
+
return { ok: true, task, readyForTiger: true };
|
|
297
|
+
}
|
|
298
|
+
if (task.status === 'failed') {
|
|
299
|
+
return { ok: false, task, error: 'Task failed' };
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const agentName = task.next_agent;
|
|
303
|
+
if (!AGENT_DEFS[agentName]) {
|
|
304
|
+
return { ok: false, task, error: `Unknown next_agent: ${agentName}` };
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if (onProgress) onProgress({ phase: 'worker_start', agent: agentName, task });
|
|
308
|
+
const turn = await runWorkerTurn(agentName);
|
|
309
|
+
const latest = findTask(taskId);
|
|
310
|
+
if (onProgress && latest) {
|
|
311
|
+
onProgress({ phase: 'worker_done', agent: agentName, task: latest.task, turn });
|
|
312
|
+
}
|
|
313
|
+
if (!turn.ok && !turn.idle) return { ok: false, task: turn.task, error: turn.error || 'Worker failed' };
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const found = findTask(taskId);
|
|
317
|
+
return { ok: false, error: `Exceeded max turns (${maxTurns})`, task: found ? found.task : null };
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
async function runTigerFlow(goal, opts = {}) {
|
|
321
|
+
ensureSwarmLayout();
|
|
322
|
+
const flow = String(opts.flow || swarmDefaultFlow || 'auto').toLowerCase();
|
|
323
|
+
const firstAgent = resolveFirstAgent(goal, flow, opts);
|
|
324
|
+
const task = createTask({
|
|
325
|
+
from: 'tiger',
|
|
326
|
+
goal,
|
|
327
|
+
nextAgent: firstAgent,
|
|
328
|
+
flow,
|
|
329
|
+
metadata: {
|
|
330
|
+
...(opts.metadata || {}),
|
|
331
|
+
first_agent_policy: String(opts.firstAgentPolicy || swarmFirstAgentPolicy || 'auto').toLowerCase(),
|
|
332
|
+
first_agent: firstAgent
|
|
333
|
+
}
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
if (typeof opts.onProgress === 'function') {
|
|
337
|
+
opts.onProgress({ phase: 'task_created', task });
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const progress = await runTaskToTiger(task.task_id, opts);
|
|
341
|
+
if (!progress.ok) return progress;
|
|
342
|
+
|
|
343
|
+
const final = extractTigerResult(task.task_id);
|
|
344
|
+
if (!final.ok) return final;
|
|
345
|
+
|
|
346
|
+
return { ok: true, task: final.task, result: final.task.result || '' };
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function inferResumeAgent(task) {
|
|
350
|
+
const meta = task && task.metadata && typeof task.metadata === 'object' ? task.metadata : {};
|
|
351
|
+
const fromMeta = String(meta.last_failed_agent || '').trim();
|
|
352
|
+
if (fromMeta && AGENT_DEFS[fromMeta] && fromMeta !== 'tiger') return fromMeta;
|
|
353
|
+
|
|
354
|
+
const thread = Array.isArray(task && task.thread) ? task.thread : [];
|
|
355
|
+
for (let i = thread.length - 1; i >= 0; i -= 1) {
|
|
356
|
+
const m = thread[i] || {};
|
|
357
|
+
const by = String(m.by || '').trim();
|
|
358
|
+
const msg = String(m.msg || '').trim();
|
|
359
|
+
if (by && by !== 'tiger' && AGENT_DEFS[by] && /^error:/i.test(msg)) return by;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
const next = String(task && task.next_agent || '').trim();
|
|
363
|
+
if (next && next !== 'tiger' && AGENT_DEFS[next]) return next;
|
|
364
|
+
return pickFlowFirstAgent(task && task.flow);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
async function continueTask(taskId, opts = {}) {
|
|
368
|
+
ensureSwarmLayout();
|
|
369
|
+
const found = findTask(taskId);
|
|
370
|
+
if (!found) return { ok: false, error: 'Task not found' };
|
|
371
|
+
|
|
372
|
+
let { task, filePath, bucket } = found;
|
|
373
|
+
if (bucket === 'done') {
|
|
374
|
+
return { ok: false, error: 'Task is already done', task };
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
if (bucket === 'failed') {
|
|
378
|
+
const resumeAgent = inferResumeAgent(task);
|
|
379
|
+
task.status = 'pending';
|
|
380
|
+
task.next_agent = resumeAgent;
|
|
381
|
+
appendThread(task, 'tiger', `resume requested: continue from ${resumeAgent}`);
|
|
382
|
+
const released = releaseTask(task, filePath, 'pending');
|
|
383
|
+
task = released.task;
|
|
384
|
+
filePath = released.filePath;
|
|
385
|
+
bucket = released.bucket;
|
|
386
|
+
void filePath;
|
|
387
|
+
void bucket;
|
|
388
|
+
} else if (bucket === 'in_progress') {
|
|
389
|
+
// Recovery path for stale stuck tasks.
|
|
390
|
+
task.status = 'pending';
|
|
391
|
+
appendThread(task, 'tiger', 'resume requested: moved stale in_progress task back to pending');
|
|
392
|
+
const released = releaseTask(task, filePath, 'pending');
|
|
393
|
+
task = released.task;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const progress = await runTaskToTiger(taskId, opts);
|
|
397
|
+
if (!progress.ok) return progress;
|
|
398
|
+
|
|
399
|
+
const final = extractTigerResult(taskId);
|
|
400
|
+
if (!final.ok) return final;
|
|
401
|
+
return { ok: true, task: final.task, result: final.task.result || '' };
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
async function askAgent(agentName, prompt) {
|
|
405
|
+
ensureSwarmLayout();
|
|
406
|
+
if (!AGENT_DEFS[agentName] || agentName === 'tiger') {
|
|
407
|
+
throw new Error(`Unknown or unsupported /ask agent: ${agentName}`);
|
|
408
|
+
}
|
|
409
|
+
const soul = getAgentSoul(agentName);
|
|
410
|
+
const system = [
|
|
411
|
+
`You are ${agentName} in Tiger's internal swarm.`,
|
|
412
|
+
'Answer as that role only.',
|
|
413
|
+
'Be concise and practical.',
|
|
414
|
+
soul
|
|
415
|
+
].join('\n\n');
|
|
416
|
+
return llmText(system, String(prompt || '').trim());
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function getAgentsStatus() {
|
|
420
|
+
ensureSwarmLayout();
|
|
421
|
+
return Object.keys(AGENT_DEFS).map((name) => {
|
|
422
|
+
const dir = path.join(AGENTS_DIR, name);
|
|
423
|
+
return {
|
|
424
|
+
name,
|
|
425
|
+
label: AGENT_DEFS[name].label,
|
|
426
|
+
kind: AGENT_DEFS[name].kind,
|
|
427
|
+
alive: fs.existsSync(dir),
|
|
428
|
+
path: dir
|
|
429
|
+
};
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
function getStatusSummary() {
|
|
434
|
+
ensureSwarmLayout();
|
|
435
|
+
return {
|
|
436
|
+
in_progress: listInProgressTasks(),
|
|
437
|
+
pending: listTasks('pending'),
|
|
438
|
+
done: listTasks('done'),
|
|
439
|
+
failed: listTasks('failed')
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
module.exports = {
|
|
444
|
+
AGENT_DEFS,
|
|
445
|
+
ensureSwarmLayout,
|
|
446
|
+
runWorkerTurn,
|
|
447
|
+
runTaskToTiger,
|
|
448
|
+
runTigerFlow,
|
|
449
|
+
continueTask,
|
|
450
|
+
deleteTask,
|
|
451
|
+
extractTigerResult,
|
|
452
|
+
cancelTask,
|
|
453
|
+
askAgent,
|
|
454
|
+
getAgentsStatus,
|
|
455
|
+
getStatusSummary
|
|
456
|
+
};
|