tiger-agent 0.2.5 → 0.3.2

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,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
+ };