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
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
const ROOT_DIR = path.resolve(process.env.TIGER_HOME || process.cwd());
|
|
7
|
+
const SWARM_DIR = path.join(ROOT_DIR, 'swarm');
|
|
8
|
+
const ARCHITECTURE_DIR = path.join(SWARM_DIR, 'architecture');
|
|
9
|
+
const TASK_STYLE_DIR = path.join(ROOT_DIR, 'tasks', 'styles');
|
|
10
|
+
const DEFAULT_ARCHITECTURE_FILE = 'tiger_parallel_design.yaml';
|
|
11
|
+
const DEFAULT_TASK_STYLE_FILE = 'default.yaml';
|
|
12
|
+
|
|
13
|
+
const DEFAULT_ARCHITECTURE_YAML = `version: 1
|
|
14
|
+
name: tiger_parallel_design
|
|
15
|
+
main_orchestrator: tiger
|
|
16
|
+
start_stage: design_parallel
|
|
17
|
+
agents:
|
|
18
|
+
- id: designer_a
|
|
19
|
+
runtime_agent: designer
|
|
20
|
+
role: designer
|
|
21
|
+
- id: designer_b
|
|
22
|
+
runtime_agent: designer
|
|
23
|
+
role: designer
|
|
24
|
+
- id: designer_c
|
|
25
|
+
runtime_agent: designer
|
|
26
|
+
role: designer
|
|
27
|
+
- id: reviewer
|
|
28
|
+
runtime_agent: senior_eng
|
|
29
|
+
role: reviewer
|
|
30
|
+
- id: spec_writer
|
|
31
|
+
runtime_agent: spec_writer
|
|
32
|
+
role: spec_writer
|
|
33
|
+
stages:
|
|
34
|
+
- id: design_parallel
|
|
35
|
+
type: parallel
|
|
36
|
+
roles:
|
|
37
|
+
- designer_a
|
|
38
|
+
- designer_b
|
|
39
|
+
- designer_c
|
|
40
|
+
store_as: design_candidates
|
|
41
|
+
next: review_best
|
|
42
|
+
- id: review_best
|
|
43
|
+
type: judge
|
|
44
|
+
role: reviewer
|
|
45
|
+
candidates_from: design_candidates
|
|
46
|
+
selected_role_key: selected_role
|
|
47
|
+
feedback_key: reviewer_feedback
|
|
48
|
+
pass_next: final_spec
|
|
49
|
+
fail_next: revise_selected
|
|
50
|
+
- id: revise_selected
|
|
51
|
+
type: revise
|
|
52
|
+
role_from_context: selected_role
|
|
53
|
+
feedback_from_context: reviewer_feedback
|
|
54
|
+
candidates_from: design_candidates
|
|
55
|
+
next: review_best
|
|
56
|
+
- id: final_spec
|
|
57
|
+
type: final
|
|
58
|
+
role: spec_writer
|
|
59
|
+
source_from_context: design_candidates
|
|
60
|
+
next: tiger_done
|
|
61
|
+
judgment_matrix:
|
|
62
|
+
criteria:
|
|
63
|
+
- name: objective_fit
|
|
64
|
+
weight: 0.35
|
|
65
|
+
description: How well the design satisfies the objective.
|
|
66
|
+
- name: feasibility
|
|
67
|
+
weight: 0.25
|
|
68
|
+
description: Delivery realism and technical viability.
|
|
69
|
+
- name: clarity
|
|
70
|
+
weight: 0.2
|
|
71
|
+
description: Readability and implementation clarity.
|
|
72
|
+
- name: risk
|
|
73
|
+
weight: 0.2
|
|
74
|
+
description: Risk exposure and mitigation quality.
|
|
75
|
+
pass_rule: reviewer_approval
|
|
76
|
+
`;
|
|
77
|
+
|
|
78
|
+
const DEFAULT_TASK_STYLE_YAML = `version: 1
|
|
79
|
+
name: default
|
|
80
|
+
architecture: tiger_parallel_design.yaml
|
|
81
|
+
flow: architecture
|
|
82
|
+
objective_prefix: "Objective:"
|
|
83
|
+
`;
|
|
84
|
+
|
|
85
|
+
function ensureDir(dir) {
|
|
86
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function safeName(name) {
|
|
90
|
+
const text = String(name || '').trim();
|
|
91
|
+
if (!/^[a-zA-Z0-9._-]+\.ya?ml$/.test(text)) {
|
|
92
|
+
throw new Error('File name must be a simple .yaml/.yml file name');
|
|
93
|
+
}
|
|
94
|
+
return text;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function stripComment(line) {
|
|
98
|
+
const trimmed = line.trim();
|
|
99
|
+
if (!trimmed || trimmed.startsWith('#')) return '';
|
|
100
|
+
return line;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function parseScalar(text) {
|
|
104
|
+
const value = String(text || '').trim();
|
|
105
|
+
if (value === '') return '';
|
|
106
|
+
if (/^".*"$/.test(value) || /^'.*'$/.test(value)) {
|
|
107
|
+
return value.slice(1, -1);
|
|
108
|
+
}
|
|
109
|
+
if (/^\[(.*)\]$/.test(value)) {
|
|
110
|
+
const inner = value.slice(1, -1).trim();
|
|
111
|
+
if (!inner) return [];
|
|
112
|
+
return inner.split(',').map((x) => parseScalar(x.trim()));
|
|
113
|
+
}
|
|
114
|
+
if (/^(true|false)$/i.test(value)) return /^true$/i.test(value);
|
|
115
|
+
if (/^null$/i.test(value)) return null;
|
|
116
|
+
if (/^-?\d+(\.\d+)?$/.test(value)) return Number(value);
|
|
117
|
+
return value;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function splitKeyValue(content) {
|
|
121
|
+
let inSingle = false;
|
|
122
|
+
let inDouble = false;
|
|
123
|
+
for (let i = 0; i < content.length; i += 1) {
|
|
124
|
+
const ch = content[i];
|
|
125
|
+
if (ch === "'" && !inDouble) inSingle = !inSingle;
|
|
126
|
+
if (ch === '"' && !inSingle) inDouble = !inDouble;
|
|
127
|
+
if (ch === ':' && !inSingle && !inDouble) {
|
|
128
|
+
return {
|
|
129
|
+
key: content.slice(0, i).trim(),
|
|
130
|
+
rest: content.slice(i + 1).trim()
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function parseYaml(text, label = 'yaml') {
|
|
138
|
+
const lines = String(text || '').replace(/\t/g, ' ').split(/\r?\n/);
|
|
139
|
+
const tokens = [];
|
|
140
|
+
for (const raw of lines) {
|
|
141
|
+
const line = stripComment(raw);
|
|
142
|
+
if (!line.trim()) continue;
|
|
143
|
+
const indent = line.match(/^\s*/)[0].length;
|
|
144
|
+
tokens.push({ indent, content: line.trim() });
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
let idx = 0;
|
|
148
|
+
|
|
149
|
+
function parseNode(indent) {
|
|
150
|
+
if (idx >= tokens.length) return {};
|
|
151
|
+
const token = tokens[idx];
|
|
152
|
+
if (token.indent < indent) return {};
|
|
153
|
+
if (token.content.startsWith('- ') && token.indent === indent) return parseSeq(indent);
|
|
154
|
+
return parseMap(indent);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function parseMap(indent) {
|
|
158
|
+
const out = {};
|
|
159
|
+
while (idx < tokens.length) {
|
|
160
|
+
const token = tokens[idx];
|
|
161
|
+
if (token.indent < indent) break;
|
|
162
|
+
if (token.indent > indent) {
|
|
163
|
+
throw new Error(`Invalid indentation near "${token.content}" in ${label}`);
|
|
164
|
+
}
|
|
165
|
+
if (token.content.startsWith('- ')) break;
|
|
166
|
+
const kv = splitKeyValue(token.content);
|
|
167
|
+
if (!kv || !kv.key) throw new Error(`Invalid mapping line "${token.content}" in ${label}`);
|
|
168
|
+
idx += 1;
|
|
169
|
+
if (kv.rest === '') {
|
|
170
|
+
if (idx < tokens.length && tokens[idx].indent > indent) {
|
|
171
|
+
out[kv.key] = parseNode(tokens[idx].indent);
|
|
172
|
+
} else {
|
|
173
|
+
out[kv.key] = null;
|
|
174
|
+
}
|
|
175
|
+
} else {
|
|
176
|
+
out[kv.key] = parseScalar(kv.rest);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return out;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function parseSeq(indent) {
|
|
183
|
+
const arr = [];
|
|
184
|
+
while (idx < tokens.length) {
|
|
185
|
+
const token = tokens[idx];
|
|
186
|
+
if (token.indent < indent) break;
|
|
187
|
+
if (token.indent !== indent || !token.content.startsWith('- ')) break;
|
|
188
|
+
const rest = token.content.slice(2).trim();
|
|
189
|
+
idx += 1;
|
|
190
|
+
|
|
191
|
+
if (!rest) {
|
|
192
|
+
if (idx < tokens.length && tokens[idx].indent > indent) {
|
|
193
|
+
arr.push(parseNode(tokens[idx].indent));
|
|
194
|
+
} else {
|
|
195
|
+
arr.push(null);
|
|
196
|
+
}
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const kv = splitKeyValue(rest);
|
|
201
|
+
if (kv && kv.key) {
|
|
202
|
+
const item = {};
|
|
203
|
+
if (kv.rest === '') {
|
|
204
|
+
if (idx < tokens.length && tokens[idx].indent > indent) {
|
|
205
|
+
item[kv.key] = parseNode(tokens[idx].indent);
|
|
206
|
+
} else {
|
|
207
|
+
item[kv.key] = null;
|
|
208
|
+
}
|
|
209
|
+
} else {
|
|
210
|
+
item[kv.key] = parseScalar(kv.rest);
|
|
211
|
+
}
|
|
212
|
+
if (idx < tokens.length && tokens[idx].indent > indent) {
|
|
213
|
+
const extra = parseNode(tokens[idx].indent);
|
|
214
|
+
if (extra && typeof extra === 'object' && !Array.isArray(extra)) {
|
|
215
|
+
Object.assign(item, extra);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
arr.push(item);
|
|
219
|
+
} else {
|
|
220
|
+
arr.push(parseScalar(rest));
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return arr;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (!tokens.length) return {};
|
|
227
|
+
return parseNode(tokens[0].indent);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function validateArchitectureObject(obj) {
|
|
231
|
+
const data = obj && typeof obj === 'object' ? obj : {};
|
|
232
|
+
const stages = Array.isArray(data.stages) ? data.stages : [];
|
|
233
|
+
const agents = Array.isArray(data.agents) ? data.agents : [];
|
|
234
|
+
if (!data.main_orchestrator) throw new Error('architecture.main_orchestrator is required');
|
|
235
|
+
if (!stages.length) throw new Error('architecture.stages must contain at least one stage');
|
|
236
|
+
if (!agents.length) throw new Error('architecture.agents must contain at least one agent');
|
|
237
|
+
for (const stage of stages) {
|
|
238
|
+
if (!stage || typeof stage !== 'object') throw new Error('Each stage must be an object');
|
|
239
|
+
if (!stage.id || !stage.type) throw new Error('Each stage requires id and type');
|
|
240
|
+
const type = String(stage.type).toLowerCase();
|
|
241
|
+
if (type === 'judge' && (!stage.pass_next || !stage.fail_next)) {
|
|
242
|
+
throw new Error(`judge stage "${stage.id}" requires pass_next and fail_next`);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function validateTaskStyleObject(obj) {
|
|
248
|
+
const data = obj && typeof obj === 'object' ? obj : {};
|
|
249
|
+
if (!data.architecture) throw new Error('task_style.architecture is required');
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function ensureSwarmConfigLayout() {
|
|
253
|
+
ensureDir(ARCHITECTURE_DIR);
|
|
254
|
+
ensureDir(TASK_STYLE_DIR);
|
|
255
|
+
const architecturePath = path.join(ARCHITECTURE_DIR, DEFAULT_ARCHITECTURE_FILE);
|
|
256
|
+
if (!fs.existsSync(architecturePath)) {
|
|
257
|
+
fs.writeFileSync(architecturePath, DEFAULT_ARCHITECTURE_YAML, 'utf8');
|
|
258
|
+
}
|
|
259
|
+
const taskStylePath = path.join(TASK_STYLE_DIR, DEFAULT_TASK_STYLE_FILE);
|
|
260
|
+
if (!fs.existsSync(taskStylePath)) {
|
|
261
|
+
fs.writeFileSync(taskStylePath, DEFAULT_TASK_STYLE_YAML, 'utf8');
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function listArchitectureFiles() {
|
|
266
|
+
ensureSwarmConfigLayout();
|
|
267
|
+
return fs
|
|
268
|
+
.readdirSync(ARCHITECTURE_DIR)
|
|
269
|
+
.filter((name) => /\.ya?ml$/i.test(name))
|
|
270
|
+
.sort();
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function listTaskStyleFiles() {
|
|
274
|
+
ensureSwarmConfigLayout();
|
|
275
|
+
return fs
|
|
276
|
+
.readdirSync(TASK_STYLE_DIR)
|
|
277
|
+
.filter((name) => /\.ya?ml$/i.test(name))
|
|
278
|
+
.sort();
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function readArchitectureText(name = DEFAULT_ARCHITECTURE_FILE) {
|
|
282
|
+
ensureSwarmConfigLayout();
|
|
283
|
+
const fileName = safeName(name);
|
|
284
|
+
return fs.readFileSync(path.join(ARCHITECTURE_DIR, fileName), 'utf8');
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function writeArchitectureText(name, text) {
|
|
288
|
+
ensureSwarmConfigLayout();
|
|
289
|
+
const fileName = safeName(name);
|
|
290
|
+
const parsed = parseYaml(text, `architecture (${fileName})`);
|
|
291
|
+
validateArchitectureObject(parsed);
|
|
292
|
+
fs.writeFileSync(path.join(ARCHITECTURE_DIR, fileName), String(text || ''), 'utf8');
|
|
293
|
+
return { fileName, parsed };
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function readTaskStyleText(name = DEFAULT_TASK_STYLE_FILE) {
|
|
297
|
+
ensureSwarmConfigLayout();
|
|
298
|
+
const fileName = safeName(name);
|
|
299
|
+
return fs.readFileSync(path.join(TASK_STYLE_DIR, fileName), 'utf8');
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function writeTaskStyleText(name, text) {
|
|
303
|
+
ensureSwarmConfigLayout();
|
|
304
|
+
const fileName = safeName(name);
|
|
305
|
+
const parsed = parseYaml(text, `task style (${fileName})`);
|
|
306
|
+
validateTaskStyleObject(parsed);
|
|
307
|
+
fs.writeFileSync(path.join(TASK_STYLE_DIR, fileName), String(text || ''), 'utf8');
|
|
308
|
+
return { fileName, parsed };
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function loadTaskStyle(name = DEFAULT_TASK_STYLE_FILE) {
|
|
312
|
+
const text = readTaskStyleText(name);
|
|
313
|
+
const parsed = parseYaml(text, `task style (${name})`);
|
|
314
|
+
validateTaskStyleObject(parsed);
|
|
315
|
+
return parsed;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function loadArchitecture(name = DEFAULT_ARCHITECTURE_FILE) {
|
|
319
|
+
const text = readArchitectureText(name);
|
|
320
|
+
const parsed = parseYaml(text, `architecture (${name})`);
|
|
321
|
+
validateArchitectureObject(parsed);
|
|
322
|
+
return parsed;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function updateDefaultStyleArchitecture(architectureFile) {
|
|
326
|
+
const fileName = safeName(architectureFile);
|
|
327
|
+
ensureSwarmConfigLayout();
|
|
328
|
+
const stylePath = path.join(TASK_STYLE_DIR, DEFAULT_TASK_STYLE_FILE);
|
|
329
|
+
const text = fs.readFileSync(stylePath, 'utf8');
|
|
330
|
+
const lines = text.split(/\r?\n/);
|
|
331
|
+
let replaced = false;
|
|
332
|
+
const out = lines.map((line) => {
|
|
333
|
+
if (/^\s*architecture\s*:/.test(line)) {
|
|
334
|
+
replaced = true;
|
|
335
|
+
return `architecture: ${fileName}`;
|
|
336
|
+
}
|
|
337
|
+
return line;
|
|
338
|
+
});
|
|
339
|
+
if (!replaced) out.push(`architecture: ${fileName}`);
|
|
340
|
+
const next = out.join('\n').replace(/\n*$/, '\n');
|
|
341
|
+
writeTaskStyleText(DEFAULT_TASK_STYLE_FILE, next);
|
|
342
|
+
return loadTaskStyle(DEFAULT_TASK_STYLE_FILE);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
module.exports = {
|
|
346
|
+
ARCHITECTURE_DIR,
|
|
347
|
+
TASK_STYLE_DIR,
|
|
348
|
+
DEFAULT_ARCHITECTURE_FILE,
|
|
349
|
+
DEFAULT_TASK_STYLE_FILE,
|
|
350
|
+
ensureSwarmConfigLayout,
|
|
351
|
+
listArchitectureFiles,
|
|
352
|
+
listTaskStyleFiles,
|
|
353
|
+
readArchitectureText,
|
|
354
|
+
writeArchitectureText,
|
|
355
|
+
readTaskStyleText,
|
|
356
|
+
writeTaskStyleText,
|
|
357
|
+
loadTaskStyle,
|
|
358
|
+
loadArchitecture,
|
|
359
|
+
updateDefaultStyleArchitecture
|
|
360
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { ensureSwarmLayout } = require('./taskBus');
|
|
4
|
+
const {
|
|
5
|
+
runTigerFlow,
|
|
6
|
+
continueTask,
|
|
7
|
+
runWorkerTurn,
|
|
8
|
+
cancelTask,
|
|
9
|
+
deleteTask,
|
|
10
|
+
askAgent,
|
|
11
|
+
getAgentsStatus,
|
|
12
|
+
getStatusSummary
|
|
13
|
+
} = require('./agentRuntime');
|
|
14
|
+
|
|
15
|
+
module.exports = {
|
|
16
|
+
ensureSwarmLayout,
|
|
17
|
+
runTigerFlow,
|
|
18
|
+
continueTask,
|
|
19
|
+
runWorkerTurn,
|
|
20
|
+
cancelTask,
|
|
21
|
+
deleteTask,
|
|
22
|
+
askAgent,
|
|
23
|
+
getAgentsStatus,
|
|
24
|
+
getStatusSummary
|
|
25
|
+
};
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
const ROOT_DIR = path.resolve(process.env.TIGER_HOME || process.cwd());
|
|
7
|
+
const TASKS_DIR = path.join(ROOT_DIR, 'tasks');
|
|
8
|
+
const AGENTS_DIR = path.join(ROOT_DIR, 'agents');
|
|
9
|
+
|
|
10
|
+
const TASK_BUCKETS = ['pending', 'in_progress', 'done', 'failed'];
|
|
11
|
+
const DEFAULT_AGENT_DIRS = [
|
|
12
|
+
'tiger',
|
|
13
|
+
'designer',
|
|
14
|
+
'designer_a',
|
|
15
|
+
'designer_b',
|
|
16
|
+
'designer_c',
|
|
17
|
+
'senior_eng',
|
|
18
|
+
'spec_writer',
|
|
19
|
+
'scout',
|
|
20
|
+
'coder',
|
|
21
|
+
'critic'
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
function nowIso() {
|
|
25
|
+
return new Date().toISOString();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function ensureDir(dir) {
|
|
29
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function writeJsonAtomic(filePath, value) {
|
|
33
|
+
const dir = path.dirname(filePath);
|
|
34
|
+
ensureDir(dir);
|
|
35
|
+
const tmp = `${filePath}.tmp-${process.pid}-${Date.now()}`;
|
|
36
|
+
fs.writeFileSync(tmp, `${JSON.stringify(value, null, 2)}\n`, 'utf8');
|
|
37
|
+
fs.renameSync(tmp, filePath);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function safeReadJson(filePath) {
|
|
41
|
+
const raw = fs.readFileSync(filePath, 'utf8');
|
|
42
|
+
return JSON.parse(raw);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function defaultAgentFileContent(agentName, fileName) {
|
|
46
|
+
if (fileName === 'soul.md') {
|
|
47
|
+
return `# ${agentName}\n\n## Identity\n${agentName} worker for Tiger swarm.\n`;
|
|
48
|
+
}
|
|
49
|
+
if (fileName === 'ownskill.md') {
|
|
50
|
+
return `# ownskill\n\n- Role: ${agentName}\n`;
|
|
51
|
+
}
|
|
52
|
+
if (fileName === 'memory.md') {
|
|
53
|
+
return `# memory\n\n`;
|
|
54
|
+
}
|
|
55
|
+
if (fileName === 'human.md' && agentName === 'tiger') {
|
|
56
|
+
return '# human\n\n';
|
|
57
|
+
}
|
|
58
|
+
if (fileName === 'experience.json') {
|
|
59
|
+
return JSON.stringify(
|
|
60
|
+
{
|
|
61
|
+
total_tasks: 0,
|
|
62
|
+
success_rate: 0,
|
|
63
|
+
lessons: [],
|
|
64
|
+
collaboration: {}
|
|
65
|
+
},
|
|
66
|
+
null,
|
|
67
|
+
2
|
|
68
|
+
) + '\n';
|
|
69
|
+
}
|
|
70
|
+
return '';
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function ensureAgentFolder(agentName) {
|
|
74
|
+
const dir = path.join(AGENTS_DIR, agentName);
|
|
75
|
+
ensureDir(dir);
|
|
76
|
+
const baseFiles = ['soul.md', 'ownskill.md', 'experience.json', 'memory.md'];
|
|
77
|
+
if (agentName === 'tiger') baseFiles.push('human.md');
|
|
78
|
+
for (const fileName of baseFiles) {
|
|
79
|
+
const full = path.join(dir, fileName);
|
|
80
|
+
if (!fs.existsSync(full)) {
|
|
81
|
+
fs.writeFileSync(full, defaultAgentFileContent(agentName, fileName), 'utf8');
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function ensureSwarmLayout() {
|
|
87
|
+
ensureDir(TASKS_DIR);
|
|
88
|
+
ensureDir(AGENTS_DIR);
|
|
89
|
+
for (const bucket of TASK_BUCKETS) ensureDir(path.join(TASKS_DIR, bucket));
|
|
90
|
+
for (const agentName of DEFAULT_AGENT_DIRS) ensureAgentFolder(agentName);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function taskFilePath(bucket, taskId) {
|
|
94
|
+
return path.join(TASKS_DIR, bucket, `${taskId}.json`);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function makeTaskId() {
|
|
98
|
+
const ts = Date.now().toString(36);
|
|
99
|
+
const rand = Math.random().toString(36).slice(2, 8);
|
|
100
|
+
return `task_${ts}_${rand}`;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function formatThreadMsg(by, msg, at = nowIso()) {
|
|
104
|
+
return { by, at, msg: String(msg || '').trim() };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function createTask({ from = 'tiger', goal, nextAgent = 'designer', flow = 'design', metadata = {} }) {
|
|
108
|
+
ensureSwarmLayout();
|
|
109
|
+
const task = {
|
|
110
|
+
task_id: makeTaskId(),
|
|
111
|
+
created_at: nowIso(),
|
|
112
|
+
status: 'pending',
|
|
113
|
+
from,
|
|
114
|
+
goal: String(goal || '').trim(),
|
|
115
|
+
flow,
|
|
116
|
+
next_agent: nextAgent,
|
|
117
|
+
thread: [],
|
|
118
|
+
result: null,
|
|
119
|
+
metadata
|
|
120
|
+
};
|
|
121
|
+
task.thread.push(formatThreadMsg(from, `task created: ${task.goal}`));
|
|
122
|
+
writeJsonAtomic(taskFilePath('pending', task.task_id), task);
|
|
123
|
+
return task;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function listTaskFiles(bucket) {
|
|
127
|
+
ensureSwarmLayout();
|
|
128
|
+
const dir = path.join(TASKS_DIR, bucket);
|
|
129
|
+
return fs
|
|
130
|
+
.readdirSync(dir)
|
|
131
|
+
.filter((name) => name.endsWith('.json'))
|
|
132
|
+
.sort()
|
|
133
|
+
.map((name) => path.join(dir, name));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function readTaskAt(filePath) {
|
|
137
|
+
const task = safeReadJson(filePath);
|
|
138
|
+
return { task, filePath };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function listTasks(bucket) {
|
|
142
|
+
return listTaskFiles(bucket).map((filePath) => readTaskAt(filePath).task);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function findTask(taskId) {
|
|
146
|
+
for (const bucket of TASK_BUCKETS) {
|
|
147
|
+
const filePath = taskFilePath(bucket, taskId);
|
|
148
|
+
if (fs.existsSync(filePath)) {
|
|
149
|
+
const task = safeReadJson(filePath);
|
|
150
|
+
return { bucket, filePath, task };
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function saveTaskInPlace(filePath, task) {
|
|
157
|
+
writeJsonAtomic(filePath, task);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function moveTaskFile(filePath, targetBucket, task) {
|
|
161
|
+
const nextPath = taskFilePath(targetBucket, task.task_id);
|
|
162
|
+
writeJsonAtomic(nextPath, task);
|
|
163
|
+
if (path.resolve(nextPath) !== path.resolve(filePath) && fs.existsSync(filePath)) {
|
|
164
|
+
fs.unlinkSync(filePath);
|
|
165
|
+
}
|
|
166
|
+
return nextPath;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function appendThread(task, by, msg) {
|
|
170
|
+
if (!Array.isArray(task.thread)) task.thread = [];
|
|
171
|
+
const text = String(msg || '').trim();
|
|
172
|
+
if (!text) return task;
|
|
173
|
+
task.thread.push(formatThreadMsg(by, text));
|
|
174
|
+
return task;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function claimPendingTask(agentName) {
|
|
178
|
+
const candidates = listTaskFiles('pending');
|
|
179
|
+
for (const filePath of candidates) {
|
|
180
|
+
const task = safeReadJson(filePath);
|
|
181
|
+
if (task.next_agent !== agentName) continue;
|
|
182
|
+
task.status = 'in_progress';
|
|
183
|
+
const nextPath = moveTaskFile(filePath, 'in_progress', task);
|
|
184
|
+
return { task, filePath: nextPath, bucket: 'in_progress' };
|
|
185
|
+
}
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function releaseTask(task, filePath, bucket) {
|
|
190
|
+
const targetBucket = bucket || task.status || 'pending';
|
|
191
|
+
const nextPath = moveTaskFile(filePath, targetBucket, task);
|
|
192
|
+
return { task, filePath: nextPath, bucket: targetBucket };
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function cancelTask(taskId, by = 'tiger') {
|
|
196
|
+
const found = findTask(taskId);
|
|
197
|
+
if (!found) return { ok: false, error: 'Task not found' };
|
|
198
|
+
const { filePath, task } = found;
|
|
199
|
+
task.status = 'failed';
|
|
200
|
+
appendThread(task, by, 'task cancelled');
|
|
201
|
+
moveTaskFile(filePath, 'failed', task);
|
|
202
|
+
return { ok: true, task };
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function deleteTask(taskId) {
|
|
206
|
+
const found = findTask(taskId);
|
|
207
|
+
if (!found) return { ok: false, error: 'Task not found' };
|
|
208
|
+
const { filePath, task, bucket } = found;
|
|
209
|
+
if (fs.existsSync(filePath)) fs.unlinkSync(filePath);
|
|
210
|
+
return { ok: true, task, bucket };
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function listInProgressTasks() {
|
|
214
|
+
return listTasks('in_progress');
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function listAgentFolders() {
|
|
218
|
+
ensureSwarmLayout();
|
|
219
|
+
return fs
|
|
220
|
+
.readdirSync(AGENTS_DIR, { withFileTypes: true })
|
|
221
|
+
.filter((d) => d.isDirectory())
|
|
222
|
+
.map((d) => d.name)
|
|
223
|
+
.sort();
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
module.exports = {
|
|
227
|
+
ROOT_DIR,
|
|
228
|
+
TASKS_DIR,
|
|
229
|
+
AGENTS_DIR,
|
|
230
|
+
TASK_BUCKETS,
|
|
231
|
+
ensureSwarmLayout,
|
|
232
|
+
ensureAgentFolder,
|
|
233
|
+
createTask,
|
|
234
|
+
listTasks,
|
|
235
|
+
listInProgressTasks,
|
|
236
|
+
findTask,
|
|
237
|
+
saveTaskInPlace,
|
|
238
|
+
moveTaskFile,
|
|
239
|
+
appendThread,
|
|
240
|
+
claimPendingTask,
|
|
241
|
+
releaseTask,
|
|
242
|
+
cancelTask,
|
|
243
|
+
deleteTask,
|
|
244
|
+
listAgentFolders,
|
|
245
|
+
nowIso
|
|
246
|
+
};
|