@ai-setting/roy-agent-core 1.5.87 → 1.5.89
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/dist/env/agent/index.js +6 -6
- package/dist/env/event-source/index.js +6 -2
- package/dist/env/index.js +17 -12
- package/dist/env/prompt/index.js +2 -1
- package/dist/env/session/index.js +1 -2
- package/dist/env/task/delegate/index.js +3 -2
- package/dist/env/task/index.js +5 -4
- package/dist/env/task/plugins/index.js +7 -4
- package/dist/env/task/tools/index.js +1 -1
- package/dist/env/tool/built-in/index.js +2 -1
- package/dist/env/tool/index.js +11 -8
- package/dist/env/workflow/engine/index.js +4 -2
- package/dist/env/workflow/index.js +5 -3
- package/dist/env/workflow/tools/index.js +7 -1
- package/dist/env/workflow/utils/index.js +5 -0
- package/dist/index.js +32 -25
- package/dist/shared/@ai-setting/{roy-agent-core-6mk0m4t3.js → roy-agent-core-2q7cshpm.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-0y64qaac.js → roy-agent-core-32m0nb9j.js} +119 -30
- package/dist/shared/@ai-setting/{roy-agent-core-9y09xfav.js → roy-agent-core-3f6k060j.js} +5 -417
- package/dist/shared/@ai-setting/roy-agent-core-4yq23m5g.js +421 -0
- package/dist/shared/@ai-setting/{roy-agent-core-mmkyydw7.js → roy-agent-core-83d035pp.js} +91 -579
- package/dist/shared/@ai-setting/roy-agent-core-8wd3qwx5.js +35 -0
- package/dist/shared/@ai-setting/{roy-agent-core-29fh9mxg.js → roy-agent-core-bwjpte58.js} +1 -2
- package/dist/shared/@ai-setting/{roy-agent-core-r1adqgaa.js → roy-agent-core-ewr1nw7t.js} +7 -8
- package/dist/shared/@ai-setting/{roy-agent-core-yx0vw1aw.js → roy-agent-core-fgpnv7dt.js} +31 -4
- package/dist/shared/@ai-setting/{roy-agent-core-jymz9fzp.js → roy-agent-core-fvfc7f6v.js} +64 -33
- package/dist/shared/@ai-setting/{roy-agent-core-9vwyj29a.js → roy-agent-core-h5867smx.js} +16 -22
- package/dist/shared/@ai-setting/roy-agent-core-hvdfgvfz.js +114 -0
- package/dist/shared/@ai-setting/{roy-agent-core-3arrpf7n.js → roy-agent-core-hxsbegfc.js} +229 -9
- package/dist/shared/@ai-setting/{roy-agent-core-7fdzfsm6.js → roy-agent-core-m3dkyprg.js} +35 -211
- package/dist/shared/@ai-setting/{roy-agent-core-bgw4dq11.js → roy-agent-core-mw4ty0ba.js} +44 -5
- package/dist/shared/@ai-setting/{roy-agent-core-7z4xtrmw.js → roy-agent-core-nqzt9ne4.js} +53 -0
- package/dist/shared/@ai-setting/roy-agent-core-pt7as39r.js +0 -0
- package/dist/shared/@ai-setting/{roy-agent-core-6n7xwv4v.js → roy-agent-core-qbq3jgrn.js} +5 -3
- package/dist/shared/@ai-setting/roy-agent-core-w6bmrgap.js +581 -0
- package/dist/shared/@ai-setting/{roy-agent-core-qhjb153z.js → roy-agent-core-xnxyzaw4.js} +27 -4
- package/dist/shared/@ai-setting/roy-agent-core-yanpq5gb.js +116 -0
- package/package.json +1 -1
- package/dist/shared/@ai-setting/roy-agent-core-x3gtyqax.js +0 -378
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AUTO_TASK_METADATA_BEGIN,
|
|
3
|
+
AUTO_TASK_METADATA_END
|
|
4
|
+
} from "./roy-agent-core-yanpq5gb.js";
|
|
1
5
|
import {
|
|
2
6
|
AskUserError,
|
|
3
7
|
init_workflow_hil
|
|
@@ -25,8 +29,6 @@ import {
|
|
|
25
29
|
import {
|
|
26
30
|
__require
|
|
27
31
|
} from "./roy-agent-core-fs0mn2jk.js";
|
|
28
|
-
// src/env/tool/tool-component.ts
|
|
29
|
-
import { z } from "zod";
|
|
30
32
|
|
|
31
33
|
// src/env/tool/registry.ts
|
|
32
34
|
class ToolRegistry {
|
|
@@ -144,6 +146,7 @@ class ToolValidator {
|
|
|
144
146
|
}
|
|
145
147
|
|
|
146
148
|
// src/env/tool/tool-component.ts
|
|
149
|
+
import { z } from "zod";
|
|
147
150
|
init_logger();
|
|
148
151
|
init_global_hook_manager();
|
|
149
152
|
|
|
@@ -178,7 +181,138 @@ var TOOL_CONFIG_REGISTRATION = {
|
|
|
178
181
|
// src/env/tool/tool-component.ts
|
|
179
182
|
init_workflow_hil();
|
|
180
183
|
init_env_context();
|
|
181
|
-
|
|
184
|
+
|
|
185
|
+
// src/utils/temp-file-manager.ts
|
|
186
|
+
init_logger();
|
|
187
|
+
import * as fs from "node:fs";
|
|
188
|
+
import * as path from "node:path";
|
|
189
|
+
import * as os from "node:os";
|
|
190
|
+
import * as crypto from "node:crypto";
|
|
191
|
+
var logger = createLogger("temp-file-manager");
|
|
192
|
+
|
|
193
|
+
class TempFileManager {
|
|
194
|
+
static _instance = null;
|
|
195
|
+
_files = new Set;
|
|
196
|
+
_hooksRegistered = false;
|
|
197
|
+
static FILE_PREFIX = "roy-agent-truncation";
|
|
198
|
+
static FILE_EXT = ".txt";
|
|
199
|
+
static LARGE_FILE_THRESHOLD = 10 * 1024 * 1024;
|
|
200
|
+
static STALE_FILE_AGE_MS = 24 * 60 * 60 * 1000;
|
|
201
|
+
constructor() {
|
|
202
|
+
this.sweepStaleFiles().catch((err) => {
|
|
203
|
+
logger.warn(`[TempFileManager] Stale file sweep failed: ${err}`);
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
static getInstance() {
|
|
207
|
+
if (!TempFileManager._instance) {
|
|
208
|
+
TempFileManager._instance = new TempFileManager;
|
|
209
|
+
}
|
|
210
|
+
return TempFileManager._instance;
|
|
211
|
+
}
|
|
212
|
+
static _resetForTesting() {
|
|
213
|
+
if (TempFileManager._instance) {
|
|
214
|
+
TempFileManager._instance.cleanupAll().catch(() => {});
|
|
215
|
+
}
|
|
216
|
+
TempFileManager._instance = null;
|
|
217
|
+
}
|
|
218
|
+
async writeTempFile(content, prefix = "default") {
|
|
219
|
+
this.ensureHooksRegistered();
|
|
220
|
+
if (content.length > TempFileManager.LARGE_FILE_THRESHOLD) {
|
|
221
|
+
logger.warn(`[TempFileManager] Writing large temp file: ${content.length} bytes (> ${TempFileManager.LARGE_FILE_THRESHOLD} bytes). Consider increasing maxOutputSize or filtering output.`);
|
|
222
|
+
}
|
|
223
|
+
const sanitizedPrefix = this.sanitizePrefix(prefix);
|
|
224
|
+
const uuid = crypto.randomUUID();
|
|
225
|
+
const filename = `${TempFileManager.FILE_PREFIX}-${process.pid}-${uuid}-${sanitizedPrefix}${TempFileManager.FILE_EXT}`;
|
|
226
|
+
const filePath = path.join(os.tmpdir(), filename);
|
|
227
|
+
await fs.promises.writeFile(filePath, content, "utf-8");
|
|
228
|
+
this._files.add(filePath);
|
|
229
|
+
logger.debug(`[TempFileManager] Created temp file: ${filePath} (${content.length} bytes)`);
|
|
230
|
+
return filePath;
|
|
231
|
+
}
|
|
232
|
+
async cleanupAll() {
|
|
233
|
+
const files = Array.from(this._files);
|
|
234
|
+
this._files.clear();
|
|
235
|
+
const results = await Promise.allSettled(files.map(async (filePath) => {
|
|
236
|
+
try {
|
|
237
|
+
await fs.promises.unlink(filePath);
|
|
238
|
+
} catch (err) {
|
|
239
|
+
if (err.code !== "ENOENT") {
|
|
240
|
+
logger.warn(`[TempFileManager] Failed to delete ${filePath}: ${err.message}`);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}));
|
|
244
|
+
const succeeded = results.filter((r) => r.status === "fulfilled").length;
|
|
245
|
+
logger.debug(`[TempFileManager] Cleanup complete: ${succeeded}/${files.length} files deleted`);
|
|
246
|
+
}
|
|
247
|
+
getActiveFileCount() {
|
|
248
|
+
return this._files.size;
|
|
249
|
+
}
|
|
250
|
+
async sweepStaleFiles(maxAgeMs = TempFileManager.STALE_FILE_AGE_MS) {
|
|
251
|
+
const tmpDir = os.tmpdir();
|
|
252
|
+
const now = Date.now();
|
|
253
|
+
let deletedCount = 0;
|
|
254
|
+
let entries;
|
|
255
|
+
try {
|
|
256
|
+
entries = await fs.promises.readdir(tmpDir);
|
|
257
|
+
} catch (err) {
|
|
258
|
+
logger.warn(`[TempFileManager] Failed to read tmpdir ${tmpDir}: ${err.message}`);
|
|
259
|
+
return 0;
|
|
260
|
+
}
|
|
261
|
+
for (const entry of entries) {
|
|
262
|
+
if (!entry.startsWith(`${TempFileManager.FILE_PREFIX}-`))
|
|
263
|
+
continue;
|
|
264
|
+
const filePath = path.join(tmpDir, entry);
|
|
265
|
+
try {
|
|
266
|
+
const stat = await fs.promises.stat(filePath);
|
|
267
|
+
const age = now - stat.mtimeMs;
|
|
268
|
+
if (age > maxAgeMs) {
|
|
269
|
+
await fs.promises.unlink(filePath);
|
|
270
|
+
deletedCount++;
|
|
271
|
+
}
|
|
272
|
+
} catch (err) {
|
|
273
|
+
if (err.code !== "ENOENT") {
|
|
274
|
+
logger.warn(`[TempFileManager] Failed to sweep ${filePath}: ${err.message}`);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
if (deletedCount > 0) {
|
|
279
|
+
logger.info(`[TempFileManager] Swept ${deletedCount} stale files from ${tmpDir}`);
|
|
280
|
+
}
|
|
281
|
+
return deletedCount;
|
|
282
|
+
}
|
|
283
|
+
ensureHooksRegistered() {
|
|
284
|
+
if (this._hooksRegistered)
|
|
285
|
+
return;
|
|
286
|
+
if (typeof process === "undefined" || !process.on) {
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
const cleanup = () => {
|
|
290
|
+
for (const filePath of this._files) {
|
|
291
|
+
try {
|
|
292
|
+
fs.unlinkSync(filePath);
|
|
293
|
+
} catch (err) {}
|
|
294
|
+
}
|
|
295
|
+
this._files.clear();
|
|
296
|
+
};
|
|
297
|
+
process.on("exit", cleanup);
|
|
298
|
+
process.on("SIGINT", () => {
|
|
299
|
+
cleanup();
|
|
300
|
+
process.exit(130);
|
|
301
|
+
});
|
|
302
|
+
process.on("SIGTERM", () => {
|
|
303
|
+
cleanup();
|
|
304
|
+
process.exit(143);
|
|
305
|
+
});
|
|
306
|
+
this._hooksRegistered = true;
|
|
307
|
+
logger.debug("[TempFileManager] Process exit hooks registered");
|
|
308
|
+
}
|
|
309
|
+
sanitizePrefix(prefix) {
|
|
310
|
+
return prefix.replace(/[\/\\<>:"|?*\x00-\x1f]/g, "-").replace(/\s+/g, "-").substring(0, 32) || "default";
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// src/env/tool/tool-component.ts
|
|
315
|
+
var logger2 = createLogger("tool");
|
|
182
316
|
var configComponentInstance = null;
|
|
183
317
|
function setToolConfigComponent(component) {
|
|
184
318
|
configComponentInstance = component;
|
|
@@ -199,6 +333,29 @@ function truncateOutput(output, maxSize) {
|
|
|
199
333
|
metadata: {}
|
|
200
334
|
};
|
|
201
335
|
}
|
|
336
|
+
const beginIdx = output.indexOf(AUTO_TASK_METADATA_BEGIN);
|
|
337
|
+
const endIdx = output.indexOf(AUTO_TASK_METADATA_END, beginIdx >= 0 ? beginIdx + AUTO_TASK_METADATA_BEGIN.length : 0);
|
|
338
|
+
if (beginIdx >= 0 && endIdx > beginIdx) {
|
|
339
|
+
const metaEnd = endIdx + AUTO_TASK_METADATA_END.length;
|
|
340
|
+
const metadataBlock = output.substring(beginIdx, metaEnd);
|
|
341
|
+
const remainder = output.substring(0, beginIdx) + output.substring(metaEnd);
|
|
342
|
+
const budget = Math.max(0, maxSize - metadataBlock.length);
|
|
343
|
+
const truncatedRemainder = remainder.substring(0, budget);
|
|
344
|
+
const isRemainderTruncated = remainder.length > budget;
|
|
345
|
+
const truncationNotice2 = isRemainderTruncated ? `
|
|
346
|
+
|
|
347
|
+
[OUTPUT TRUNCATED: ${remainder.length} → ${budget} characters. Use offset/limit parameters or pipe to file for full content.]` : "";
|
|
348
|
+
return {
|
|
349
|
+
output: metadataBlock + `
|
|
350
|
+
|
|
351
|
+
` + truncatedRemainder + truncationNotice2,
|
|
352
|
+
metadata: {
|
|
353
|
+
output_truncated: isRemainderTruncated,
|
|
354
|
+
original_output_size: output.length,
|
|
355
|
+
truncated_output_size: maxSize
|
|
356
|
+
}
|
|
357
|
+
};
|
|
358
|
+
}
|
|
202
359
|
const truncatedOutput = output.substring(0, maxSize);
|
|
203
360
|
const truncationNotice = `
|
|
204
361
|
|
|
@@ -212,6 +369,49 @@ function truncateOutput(output, maxSize) {
|
|
|
212
369
|
}
|
|
213
370
|
};
|
|
214
371
|
}
|
|
372
|
+
var TOOL_MAX_OUTPUT_SIZE_OVERRIDES = {
|
|
373
|
+
grep: 51200,
|
|
374
|
+
read_file: 204800,
|
|
375
|
+
glob: 10240
|
|
376
|
+
};
|
|
377
|
+
function getMaxOutputSizeForTool(toolName, overrideMaxSize) {
|
|
378
|
+
if (typeof overrideMaxSize === "number" && overrideMaxSize >= 0) {
|
|
379
|
+
return overrideMaxSize;
|
|
380
|
+
}
|
|
381
|
+
if (toolName && TOOL_MAX_OUTPUT_SIZE_OVERRIDES[toolName] !== undefined) {
|
|
382
|
+
return TOOL_MAX_OUTPUT_SIZE_OVERRIDES[toolName];
|
|
383
|
+
}
|
|
384
|
+
return getMaxOutputSize();
|
|
385
|
+
}
|
|
386
|
+
function truncateOutputInline(result, maxSize, overrideMaxSize, toolName) {
|
|
387
|
+
const cloned = {
|
|
388
|
+
success: result.success,
|
|
389
|
+
output: result.output,
|
|
390
|
+
error: result.error,
|
|
391
|
+
metadata: result.metadata ? { ...result.metadata } : { execution_time_ms: 0 }
|
|
392
|
+
};
|
|
393
|
+
const effectiveMax = typeof maxSize === "number" ? maxSize : getMaxOutputSizeForTool(toolName, overrideMaxSize);
|
|
394
|
+
if (effectiveMax <= 0) {
|
|
395
|
+
return cloned;
|
|
396
|
+
}
|
|
397
|
+
if (typeof cloned.output === "string") {
|
|
398
|
+
const truncated = truncateOutput(cloned.output, effectiveMax);
|
|
399
|
+
cloned.output = truncated.output;
|
|
400
|
+
cloned.metadata = {
|
|
401
|
+
...cloned.metadata,
|
|
402
|
+
...truncated.metadata
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
if (typeof cloned.error === "string" && cloned.error.length > effectiveMax) {
|
|
406
|
+
const truncated = truncateOutput(cloned.error, effectiveMax);
|
|
407
|
+
cloned.error = truncated.output;
|
|
408
|
+
cloned.metadata = {
|
|
409
|
+
...cloned.metadata,
|
|
410
|
+
...truncated.metadata
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
return cloned;
|
|
414
|
+
}
|
|
215
415
|
|
|
216
416
|
class ToolComponent extends BaseComponent {
|
|
217
417
|
name = "tool";
|
|
@@ -238,7 +438,7 @@ class ToolComponent extends BaseComponent {
|
|
|
238
438
|
if (this.config?.builtInTools?.enabled !== false) {
|
|
239
439
|
await this.loadBuiltInTools();
|
|
240
440
|
}
|
|
241
|
-
|
|
441
|
+
logger2.info(`ToolComponent initialized with ${this.getToolCount()} tools`);
|
|
242
442
|
}
|
|
243
443
|
async registerConfig(options) {
|
|
244
444
|
const configComponent = options.configComponent;
|
|
@@ -336,7 +536,7 @@ class ToolComponent extends BaseComponent {
|
|
|
336
536
|
});
|
|
337
537
|
}
|
|
338
538
|
onConfigChange(event) {
|
|
339
|
-
|
|
539
|
+
logger2.info(`Tool config changed: ${event.key}`, {
|
|
340
540
|
oldValue: event.oldValue,
|
|
341
541
|
newValue: event.newValue
|
|
342
542
|
});
|
|
@@ -354,7 +554,7 @@ class ToolComponent extends BaseComponent {
|
|
|
354
554
|
source,
|
|
355
555
|
enabled: true
|
|
356
556
|
});
|
|
357
|
-
|
|
557
|
+
logger2.debug(`Registered tool: ${tool.name}`);
|
|
358
558
|
globalHookManager.execute(ToolHookPoints.AFTER_REGISTER, { tool });
|
|
359
559
|
}
|
|
360
560
|
registerMany(tools, source = "dynamic") {
|
|
@@ -422,13 +622,32 @@ class ToolComponent extends BaseComponent {
|
|
|
422
622
|
}
|
|
423
623
|
if (typeof result.output === "string") {
|
|
424
624
|
const maxOutputSize = getMaxOutputSize();
|
|
625
|
+
let fullOutput = null;
|
|
425
626
|
if (maxOutputSize > 0) {
|
|
627
|
+
fullOutput = result.output;
|
|
426
628
|
const { output: truncatedOutput, metadata: truncationMeta } = truncateOutput(result.output, maxOutputSize);
|
|
427
629
|
result.output = truncatedOutput;
|
|
428
630
|
result.metadata = {
|
|
429
631
|
...result.metadata,
|
|
430
632
|
...truncationMeta
|
|
431
633
|
};
|
|
634
|
+
if (truncationMeta.output_truncated === true && tool.name !== "read_file" && fullOutput !== null) {
|
|
635
|
+
try {
|
|
636
|
+
const tempFilePath = await TempFileManager.getInstance().writeTempFile(fullOutput, tool.name);
|
|
637
|
+
result.metadata = {
|
|
638
|
+
...result.metadata,
|
|
639
|
+
truncation_temp_file: tempFilePath
|
|
640
|
+
};
|
|
641
|
+
result.output += `
|
|
642
|
+
|
|
643
|
+
⚠️ 输出被截断(原 ${truncationMeta.original_output_size} 字符,截断到 ${truncationMeta.truncated_output_size} 字符)
|
|
644
|
+
\uD83D\uDCC1 完整内容保存到: ${tempFilePath}
|
|
645
|
+
\uD83D\uDD0D 使用 read_file 工具按需读取`;
|
|
646
|
+
logger2.debug(`[tool-component] Truncated output saved to temp file: ${tempFilePath} (${truncationMeta.original_output_size} bytes)`);
|
|
647
|
+
} catch (err) {
|
|
648
|
+
logger2.warn(`[tool-component] Failed to write temp file for truncated output: ${err}`);
|
|
649
|
+
}
|
|
650
|
+
}
|
|
432
651
|
}
|
|
433
652
|
const currentTaskId = getCurrentTaskId();
|
|
434
653
|
if (currentTaskId !== undefined) {
|
|
@@ -471,9 +690,9 @@ class ToolComponent extends BaseComponent {
|
|
|
471
690
|
const { getAllBuiltInTools } = await import("../../env/tool/built-in/index.js");
|
|
472
691
|
const tools = getAllBuiltInTools();
|
|
473
692
|
this.registerMany(tools, "built-in");
|
|
474
|
-
|
|
693
|
+
logger2.info(`Loaded ${tools.length} built-in tools`);
|
|
475
694
|
} catch (err) {
|
|
476
|
-
|
|
695
|
+
logger2.warn(`Failed to load built-in tools: ${err.message}`);
|
|
477
696
|
}
|
|
478
697
|
}
|
|
479
698
|
getHookPoints() {
|
|
@@ -503,4 +722,5 @@ var ToolComponentConfigSchema = z.object({
|
|
|
503
722
|
maxConcurrency: z.number().int().positive().default(4)
|
|
504
723
|
}).default({})
|
|
505
724
|
});
|
|
506
|
-
|
|
725
|
+
|
|
726
|
+
export { ToolRegistry, ToolValidator, setToolConfigComponent, getMaxOutputSize, truncateOutputInline, ToolComponent };
|
|
@@ -1,6 +1,12 @@
|
|
|
1
|
+
import {
|
|
2
|
+
WORKFLOW_EXTRACT_PROMPT
|
|
3
|
+
} from "./roy-agent-core-83d035pp.js";
|
|
1
4
|
import {
|
|
2
5
|
BasePlugin
|
|
3
6
|
} from "./roy-agent-core-az13yzmc.js";
|
|
7
|
+
import {
|
|
8
|
+
validateWorkflowDefinition
|
|
9
|
+
} from "./roy-agent-core-8wd3qwx5.js";
|
|
4
10
|
import {
|
|
5
11
|
TaskHookPoints
|
|
6
12
|
} from "./roy-agent-core-92z6t4he.js";
|
|
@@ -12,8 +18,7 @@ import {
|
|
|
12
18
|
} from "./roy-agent-core-y5d04fm3.js";
|
|
13
19
|
import {
|
|
14
20
|
TracedAs,
|
|
15
|
-
init_decorator
|
|
16
|
-
wrapFunction
|
|
21
|
+
init_decorator
|
|
17
22
|
} from "./roy-agent-core-k05v31rc.js";
|
|
18
23
|
import {
|
|
19
24
|
createLogger,
|
|
@@ -28,107 +33,7 @@ import {
|
|
|
28
33
|
init_logger();
|
|
29
34
|
|
|
30
35
|
// src/env/task/plugins/workflow-extractor-agent.ts
|
|
31
|
-
var WORKFLOW_EXTRACTOR_PROMPT =
|
|
32
|
-
|
|
33
|
-
## 提取原则
|
|
34
|
-
|
|
35
|
-
1. **优先具象节点**:优先使用特定工具节点(如 read_file, write_file, grep, bash 等)
|
|
36
|
-
2. **Fallback 到通用节点**:没有对应工具时,使用 agent/llm/bash 节点
|
|
37
|
-
3. **避免条件分支**:不提取循环和条件判断,保持线性流程
|
|
38
|
-
4. **泛化但可执行**:步骤描述可泛化,但节点类型和工具要具体
|
|
39
|
-
|
|
40
|
-
## 构建流程
|
|
41
|
-
|
|
42
|
-
**重要**:构建的 Workflow 必须通过 \`roy workflow validate\` 验证才能输出。
|
|
43
|
-
|
|
44
|
-
\`\`\`
|
|
45
|
-
1. 理解用户需求
|
|
46
|
-
2. 参考 roy workflow nodes 查看可用节点类型
|
|
47
|
-
3. 参考 roy workflow get <name> 查看已有 workflow 示例
|
|
48
|
-
4. 生成 Workflow YAML
|
|
49
|
-
5. 使用 roy workflow validate --yaml "<生成的YAML>" 验证
|
|
50
|
-
6. 验证通过后输出最终结果
|
|
51
|
-
\`\`\`
|
|
52
|
-
|
|
53
|
-
## 验证检查清单
|
|
54
|
-
|
|
55
|
-
完成 Workflow 构建后,必须使用以下命令验证:
|
|
56
|
-
|
|
57
|
-
\`\`\`bash
|
|
58
|
-
roy workflow validate --yaml "<your-yaml>"
|
|
59
|
-
\`\`\`
|
|
60
|
-
|
|
61
|
-
验证通过后(无错误输出)才输出最终结果。验证失败时根据错误信息修正后重新验证。
|
|
62
|
-
|
|
63
|
-
## 可用的 Workflow 节点类型
|
|
64
|
-
|
|
65
|
-
使用以下命令查看所有节点类型的详细配置:
|
|
66
|
-
|
|
67
|
-
\`\`\`bash
|
|
68
|
-
roy workflow nodes # 列出所有节点类型
|
|
69
|
-
roy workflow nodes <type> # 查看特定节点详情(如 tool, agent, skill)
|
|
70
|
-
\`\`\`
|
|
71
|
-
|
|
72
|
-
**支持的节点类型:**
|
|
73
|
-
- **tool**:执行工具(bash, read-file, write-file, grep 等)
|
|
74
|
-
- **skill**:调用技能
|
|
75
|
-
- **agent**:AI Agent
|
|
76
|
-
- **workflow**:子工作流
|
|
77
|
-
- **condition**:条件判断
|
|
78
|
-
- **merge**:合并结果
|
|
79
|
-
- **decorator**:装饰器方法
|
|
80
|
-
|
|
81
|
-
## 查看已有 Workflow 示例
|
|
82
|
-
|
|
83
|
-
使用以下命令查看已有 workflow 的 YAML 结构:
|
|
84
|
-
|
|
85
|
-
\`\`\`bash
|
|
86
|
-
roy workflow list # 列出所有 workflow
|
|
87
|
-
roy workflow get <name> # 获取指定 workflow 详情(YAML 格式)
|
|
88
|
-
\`\`\`
|
|
89
|
-
|
|
90
|
-
## 输出要求
|
|
91
|
-
|
|
92
|
-
验证通过后,输出符合以下格式的 YAML Workflow:
|
|
93
|
-
|
|
94
|
-
\`\`\`yaml
|
|
95
|
-
name: {泛化的任务名称}
|
|
96
|
-
version: "1.0"
|
|
97
|
-
description: {一句话描述}
|
|
98
|
-
nodes:
|
|
99
|
-
- id: node-1
|
|
100
|
-
type: tool|skill|agent|workflow|condition|merge|decorator
|
|
101
|
-
name: {节点名称}
|
|
102
|
-
config:
|
|
103
|
-
# 根据类型配置必填字段:
|
|
104
|
-
# tool: tool, input/args
|
|
105
|
-
# skill: skill, input
|
|
106
|
-
# agent: agent_type, prompt, options
|
|
107
|
-
# workflow: workflow_name, input
|
|
108
|
-
# condition: condition
|
|
109
|
-
# merge: strategy, depends_on
|
|
110
|
-
# decorator: _methodName, _instance
|
|
111
|
-
depends_on: []
|
|
112
|
-
outputs: []
|
|
113
|
-
\`\`\`
|
|
114
|
-
|
|
115
|
-
## 限制
|
|
116
|
-
|
|
117
|
-
- 至少 2 个节点才有意义
|
|
118
|
-
- 不要生成条件节点(if/else/switch)
|
|
119
|
-
- 不要生成循环节点(while/for)
|
|
120
|
-
- 每个节点要有清晰的名称和描述
|
|
121
|
-
- 必须为 tool 节点指定 tool 名称
|
|
122
|
-
- 必须为 agent 节点指定 agent_type 和 prompt`;
|
|
123
|
-
function createWorkflowExtractorAgent() {
|
|
124
|
-
return {
|
|
125
|
-
type: "sub",
|
|
126
|
-
name: "workflow-extractor",
|
|
127
|
-
systemPrompt: WORKFLOW_EXTRACTOR_PROMPT,
|
|
128
|
-
maxIterations: 10,
|
|
129
|
-
filterHistory: false
|
|
130
|
-
};
|
|
131
|
-
}
|
|
36
|
+
var WORKFLOW_EXTRACTOR_PROMPT = WORKFLOW_EXTRACT_PROMPT;
|
|
132
37
|
function formatExtractorInput(params) {
|
|
133
38
|
const lines = [];
|
|
134
39
|
lines.push("# 任务信息");
|
|
@@ -225,7 +130,6 @@ class TaskTagPlugin extends BasePlugin {
|
|
|
225
130
|
priority: 10,
|
|
226
131
|
handler: this.onOperationAfterCreate.bind(this)
|
|
227
132
|
});
|
|
228
|
-
env.registerAgent(createWorkflowExtractorAgent());
|
|
229
133
|
logger.info("[TaskTagPlugin] Initialized");
|
|
230
134
|
}
|
|
231
135
|
dispose() {
|
|
@@ -327,10 +231,22 @@ ${prompt}`;
|
|
|
327
231
|
if (!this.agentComponent)
|
|
328
232
|
return;
|
|
329
233
|
const input = formatExtractorInput({ task, operations, messages: allMessages });
|
|
330
|
-
const result = await this.agentComponent.run("workflow-
|
|
234
|
+
const result = await this.agentComponent.run("workflow-extract", input);
|
|
331
235
|
const workflowDef = await parseExtractorOutput(result.finalText || "");
|
|
332
236
|
if (!workflowDef)
|
|
333
237
|
return;
|
|
238
|
+
const validation = this.validateWorkflowDefinition(workflowDef);
|
|
239
|
+
if (!validation.valid) {
|
|
240
|
+
logger.warn(`[TaskTagPlugin] Skipping workflow creation due to validation failure: ` + `${validation.errors.join("; ")}`);
|
|
241
|
+
await this.taskComponent?.createOperation({
|
|
242
|
+
taskId,
|
|
243
|
+
sessionId,
|
|
244
|
+
actionType: "workflow_extraction_failed",
|
|
245
|
+
actionTitle: `Workflow extraction failed validation: ${workflowDef.name}`,
|
|
246
|
+
actionDescription: validation.errors.join("; ")
|
|
247
|
+
});
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
334
250
|
const workflowRepo = await this.getWorkflowRepository();
|
|
335
251
|
if (!workflowRepo)
|
|
336
252
|
return;
|
|
@@ -346,7 +262,7 @@ ${prompt}`;
|
|
|
346
262
|
description: workflowDef.description,
|
|
347
263
|
definition: workflowDef,
|
|
348
264
|
config: workflowDef.config || {},
|
|
349
|
-
metadata: { author: "workflow-
|
|
265
|
+
metadata: { author: "workflow-extract", created_at: new Date().toISOString() },
|
|
350
266
|
tags: [`task:${taskId}`]
|
|
351
267
|
});
|
|
352
268
|
await this.taskComponent?.createOperation({
|
|
@@ -361,6 +277,9 @@ ${prompt}`;
|
|
|
361
277
|
logger.warn(`[TaskTagPlugin] Workflow extraction failed: ${error}`);
|
|
362
278
|
}
|
|
363
279
|
}
|
|
280
|
+
validateWorkflowDefinition(workflowDef) {
|
|
281
|
+
return validateWorkflowDefinition(workflowDef);
|
|
282
|
+
}
|
|
364
283
|
_workflowRepo = null;
|
|
365
284
|
async getWorkflowRepository() {
|
|
366
285
|
const workflowComponent = this.env.getComponent("workflow");
|
|
@@ -388,107 +307,12 @@ ${prompt}`;
|
|
|
388
307
|
__require("fs").mkdirSync(dataDir, { recursive: true });
|
|
389
308
|
}
|
|
390
309
|
}
|
|
391
|
-
// src/env/task/plugins/auto-task-plugin.ts
|
|
392
|
-
init_logger();
|
|
393
|
-
init_decorator();
|
|
394
|
-
var logger2 = createLogger("AutoTaskPlugin");
|
|
395
|
-
function createAutoTaskPlugin(config) {
|
|
396
|
-
const threshold = config?.threshold ?? 20;
|
|
397
|
-
const agentName = config?.agentName ?? "default";
|
|
398
|
-
const agentComponent = config?.agentComponent ?? null;
|
|
399
|
-
const subagentType = config?.subagentType ?? "task-agent";
|
|
400
|
-
let lastProcessedTraceId = "";
|
|
401
|
-
async function executeImpl(ctx) {
|
|
402
|
-
try {
|
|
403
|
-
logger2.debug(`[AutoTaskPlugin] agentName=${ctx.agent.name} (expected=${agentName})`);
|
|
404
|
-
if (ctx.agent.name !== agentName)
|
|
405
|
-
return { continue: true };
|
|
406
|
-
logger2.debug(`[AutoTaskPlugin] iteration=${ctx.iteration} (threshold=${threshold})`);
|
|
407
|
-
if (ctx.iteration !== threshold)
|
|
408
|
-
return { continue: true };
|
|
409
|
-
const traceId = ctx.context?.traceId ?? ctx.context?.sessionId ?? "unknown";
|
|
410
|
-
const sid = ctx.context?.sessionId ?? "unknown";
|
|
411
|
-
if (traceId === lastProcessedTraceId) {
|
|
412
|
-
logger2.debug(`[AutoTaskPlugin] skip - traceId=${traceId.slice(0, 20)}... already processed`);
|
|
413
|
-
return { continue: true };
|
|
414
|
-
}
|
|
415
|
-
lastProcessedTraceId = traceId;
|
|
416
|
-
logger2.debug(`[AutoTaskPlugin] new query detected (traceId=${traceId.slice(0, 20)}...)`);
|
|
417
|
-
if (!agentComponent || typeof agentComponent.run !== "function") {
|
|
418
|
-
logger2.warn(`[AutoTaskPlugin] agentComponent not provided or invalid (session=${sid.slice(0, 12)}...)`);
|
|
419
|
-
return { continue: true };
|
|
420
|
-
}
|
|
421
|
-
let userQuery = "";
|
|
422
|
-
for (let i = ctx.messages.length - 1;i >= 0; i--) {
|
|
423
|
-
const m = ctx.messages[i];
|
|
424
|
-
if (m.role === "user") {
|
|
425
|
-
userQuery = typeof m.content === "string" ? m.content : JSON.stringify(m.content);
|
|
426
|
-
break;
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
if (!userQuery) {
|
|
430
|
-
userQuery = "分析项目结构并执行相关任务";
|
|
431
|
-
}
|
|
432
|
-
logger2.info(`[AutoTaskPlugin] Calling agentComponent.run("${subagentType}") at iteration ${ctx.iteration} (session=${sid.slice(0, 12)}..., persistSession=false)`);
|
|
433
|
-
const shortDesc = typeof userQuery === "string" ? userQuery.slice(0, 50) : "分析需求";
|
|
434
|
-
const beforeMsg = `
|
|
435
|
-
|
|
436
|
-
---
|
|
437
|
-
⏳ **${subagentType} 正在执行**: ${shortDesc}(等待结果...)`;
|
|
438
|
-
if (ctx.toolResult?.result?.output !== undefined) {
|
|
439
|
-
ctx.toolResult.result.output += beforeMsg;
|
|
440
|
-
}
|
|
441
|
-
let taskAgentFinalText = "";
|
|
442
|
-
try {
|
|
443
|
-
const taskAgentResult = await agentComponent.run(subagentType, userQuery, {
|
|
444
|
-
sessionId: sid,
|
|
445
|
-
traceId,
|
|
446
|
-
persistSession: false
|
|
447
|
-
});
|
|
448
|
-
taskAgentFinalText = taskAgentResult?.finalText || "";
|
|
449
|
-
logger2.info(`[AutoTaskPlugin] task-agent completed (session=${sid.slice(0, 12)}..., finalTextLength=${taskAgentFinalText.length})`);
|
|
450
|
-
} catch (err) {
|
|
451
|
-
logger2.error(`[AutoTaskPlugin] task-agent failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
452
|
-
taskAgentFinalText = `❌ **${subagentType} 执行失败**: ${err instanceof Error ? err.message : String(err)}`;
|
|
453
|
-
}
|
|
454
|
-
if (taskAgentFinalText && ctx.toolResult?.result?.output !== undefined) {
|
|
455
|
-
const injection = `
|
|
456
|
-
|
|
457
|
-
---
|
|
458
|
-
${taskAgentFinalText}
|
|
459
|
-
---`;
|
|
460
|
-
ctx.toolResult.result.output += injection;
|
|
461
|
-
logger2.info(`[AutoTaskPlugin] task-agent finalText injected (session=${sid.slice(0, 12)}...)`);
|
|
462
|
-
} else if (!ctx.toolResult?.result?.output) {
|
|
463
|
-
logger2.warn(`[AutoTaskPlugin] toolResult unavailable for injection`);
|
|
464
|
-
}
|
|
465
|
-
return { continue: true };
|
|
466
|
-
} catch (err) {
|
|
467
|
-
logger2.error(`[AutoTaskPlugin] hook failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
468
|
-
return { continue: true };
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
const execute = wrapFunction(executeImpl, "plugin.auto-task.launch-agent", {
|
|
472
|
-
recordParams: false,
|
|
473
|
-
recordResult: true,
|
|
474
|
-
log: true
|
|
475
|
-
});
|
|
476
|
-
return {
|
|
477
|
-
name: "AutoTaskPlugin",
|
|
478
|
-
version: "4.5.0",
|
|
479
|
-
description: "ReAct iteration 达到阈值时 await task-agent 执行并提取 delegate_task 结果注入 tool result",
|
|
480
|
-
hooks: [
|
|
481
|
-
{ point: "agent:after.tool", priority: 50 }
|
|
482
|
-
],
|
|
483
|
-
execute
|
|
484
|
-
};
|
|
485
|
-
}
|
|
486
310
|
// src/env/task/plugins/lark-cli-task-notify-plugin.ts
|
|
487
311
|
init_decorator();
|
|
488
312
|
init_logger();
|
|
489
313
|
init_context();
|
|
490
314
|
import { spawn } from "child_process";
|
|
491
|
-
var
|
|
315
|
+
var logger2 = createLogger("lark-cli:task-notify");
|
|
492
316
|
function isTaskNotifyDebugEnabled() {
|
|
493
317
|
return process.env.ROY_TASK_NOTIFY_DEBUG === "1" || process.env.ROY_TRACE_DEBUG === "1";
|
|
494
318
|
}
|
|
@@ -535,7 +359,7 @@ class LarkCliTaskNotifyHook {
|
|
|
535
359
|
if (this.pluginName && this.sourceId) {
|
|
536
360
|
const key = `${this.sourceId}:${this.pluginName}`;
|
|
537
361
|
if (env.pluginEnabled && env.pluginEnabled[key] !== true) {
|
|
538
|
-
|
|
362
|
+
logger2.debug(`Plugin not enabled for source: ${key}`);
|
|
539
363
|
return;
|
|
540
364
|
}
|
|
541
365
|
}
|
|
@@ -597,7 +421,7 @@ class LarkCliTaskNotifyHook {
|
|
|
597
421
|
async onAfterOperationCreate(ctx) {
|
|
598
422
|
const { operation } = ctx.data;
|
|
599
423
|
if (!operation) {
|
|
600
|
-
|
|
424
|
+
logger2.debug("onAfterOperationCreate: operation is missing, skip");
|
|
601
425
|
return;
|
|
602
426
|
}
|
|
603
427
|
if (!this.shouldNotify("operation_created" /* OPERATION_CREATED */)) {
|
|
@@ -627,7 +451,7 @@ class LarkCliTaskNotifyHook {
|
|
|
627
451
|
const chatId = replyChannel?.chatId;
|
|
628
452
|
const messageId = replyChannel?.messageId;
|
|
629
453
|
if (!chatId && !messageId) {
|
|
630
|
-
|
|
454
|
+
logger2.debug(`No replyChannel available for ${type}`);
|
|
631
455
|
return;
|
|
632
456
|
}
|
|
633
457
|
const message = `${title}
|
|
@@ -635,16 +459,16 @@ class LarkCliTaskNotifyHook {
|
|
|
635
459
|
${content}`;
|
|
636
460
|
try {
|
|
637
461
|
if (isTaskNotifyDebugEnabled()) {
|
|
638
|
-
|
|
462
|
+
logger2.debug(`Sending message - messageId: ${messageId}`);
|
|
639
463
|
}
|
|
640
464
|
await this.sendLarkMessage({
|
|
641
465
|
chatId,
|
|
642
466
|
messageId,
|
|
643
467
|
content: message
|
|
644
468
|
});
|
|
645
|
-
|
|
469
|
+
logger2.info(`Notification sent: ${type}`);
|
|
646
470
|
} catch (error) {
|
|
647
|
-
|
|
471
|
+
logger2.error(`Failed to send notification: ${error}`);
|
|
648
472
|
}
|
|
649
473
|
}
|
|
650
474
|
sendLarkMessage(options) {
|
|
@@ -686,7 +510,7 @@ ${content}`;
|
|
|
686
510
|
});
|
|
687
511
|
proc.on("close", (code) => {
|
|
688
512
|
if (code === 0) {
|
|
689
|
-
|
|
513
|
+
logger2.debug(`lark-cli output: ${stdout}`);
|
|
690
514
|
resolve();
|
|
691
515
|
} else {
|
|
692
516
|
reject(new Error(`lark-cli exited with code ${code}: ${stderr}`));
|
|
@@ -770,7 +594,7 @@ ${content}`;
|
|
|
770
594
|
const now = Date.now();
|
|
771
595
|
const lastTime = this.lastNotifyTimestamps.get(key);
|
|
772
596
|
if (lastTime && now - lastTime < throttleMs) {
|
|
773
|
-
|
|
597
|
+
logger2.debug(`[throttle] Skipping OPERATION_CREATED notify for task #${operation.taskId}, last notified ${now - lastTime}ms ago`);
|
|
774
598
|
return true;
|
|
775
599
|
}
|
|
776
600
|
this.lastNotifyTimestamps.set(key, now);
|
|
@@ -811,4 +635,4 @@ __legacyDecorateClassTS([
|
|
|
811
635
|
function createLarkCliTaskNotifyHook(config = {}) {
|
|
812
636
|
return new LarkCliTaskNotifyHook(config);
|
|
813
637
|
}
|
|
814
|
-
export {
|
|
638
|
+
export { WORKFLOW_EXTRACTOR_PROMPT, formatExtractorInput, formatExtractorInputFromDescription, parseExtractorOutput, TaskTagPlugin, NotifyType, LarkCliTaskNotifyHook, createLarkCliTaskNotifyHook };
|