@ai-setting/roy-agent-core 1.7.66 → 1.7.67
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/agent-component.d.ts +29 -0
- package/dist/env/agent/agent-component.js +1 -1
- package/dist/env/agent/index.js +1 -1
- package/dist/env/agent/types.d.ts +16 -0
- package/dist/env/environment.d.ts +13 -0
- package/dist/env/event-source/event-source-handlers.js +4 -3
- package/dist/env/event-source/index.js +5 -4
- package/dist/env/event-source/timer-handler.d.ts +93 -1
- package/dist/env/index.js +10 -9
- package/dist/env/prompt/index.js +3 -2
- package/dist/env/prompt/prompts-index.d.ts +2 -7
- package/dist/env/task/delegate/delegate-tool.d.ts +21 -1
- package/dist/env/task/delegate/delegate-tool.js +3 -2
- package/dist/env/task/delegate/index.js +3 -2
- package/dist/env/task/index.js +5 -4
- package/dist/env/task/mcp/index.js +2 -1
- package/dist/env/task/plugins/__e2e__/laya-completion-e2e.d.ts +16 -0
- package/dist/env/task/plugins/index.js +3 -2
- package/dist/env/task/plugins/laya-completion-plugin.d.ts +133 -0
- package/dist/env/task/plugins/laya-port.d.ts +23 -0
- package/dist/env/task/task-component.d.ts +43 -0
- package/dist/env/task/tools/completion-copy.d.ts +27 -0
- package/dist/env/task/tools/index.js +2 -1
- package/dist/index.js +11 -10
- package/dist/shared/@ai-setting/{roy-agent-core-nr2m4xw1.js → roy-agent-core-1k83dq3a.js} +13 -3
- package/dist/shared/@ai-setting/{roy-agent-core-4882qsjx.js → roy-agent-core-1kxf910n.js} +1 -1
- package/dist/shared/@ai-setting/roy-agent-core-6m4bw8jy.js +6 -0
- package/dist/shared/@ai-setting/roy-agent-core-8zas6w5q.js +1083 -0
- package/dist/shared/@ai-setting/{roy-agent-core-v8bz7d8x.js → roy-agent-core-b7jbqfsy.js} +24 -10
- package/dist/shared/@ai-setting/{roy-agent-core-2wxbx36p.js → roy-agent-core-dnjm17sa.js} +21 -2
- package/dist/shared/@ai-setting/{roy-agent-core-t0ktaevq.js → roy-agent-core-h5vmff8w.js} +60 -4
- package/dist/shared/@ai-setting/{roy-agent-core-j1kksatb.js → roy-agent-core-hfr6bzts.js} +67 -10
- package/dist/shared/@ai-setting/{roy-agent-core-8nksrtnf.js → roy-agent-core-k4ps1hxp.js} +201 -16
- package/dist/shared/@ai-setting/{roy-agent-core-vs5gxgwh.js → roy-agent-core-ma35jyd9.js} +14 -3
- package/dist/shared/@ai-setting/{roy-agent-core-cvm1xgmg.js → roy-agent-core-wksgtqf9.js} +1 -1
- package/package.json +1 -1
- package/dist/shared/@ai-setting/roy-agent-core-8mjnb7xc.js +0 -654
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
} from "./roy-agent-core-1pg0fepg.js";
|
|
11
11
|
import {
|
|
12
12
|
WORKFLOW_EXTRACT_PROMPT
|
|
13
|
-
} from "./roy-agent-core-
|
|
13
|
+
} from "./roy-agent-core-ma35jyd9.js";
|
|
14
14
|
import {
|
|
15
15
|
assertNoLeakage,
|
|
16
16
|
buildHintText,
|
|
@@ -929,6 +929,7 @@ function truncateSnippet(text) {
|
|
|
929
929
|
var DEFAULT_TIMEOUT_MS = 2000;
|
|
930
930
|
var DEFAULT_MAX_STRIKES = 3;
|
|
931
931
|
var DEFAULT_MODEL = "task-decision";
|
|
932
|
+
var DEFAULT_GOAL_MODEL = "task-completion";
|
|
932
933
|
var SYSTEMONE_PATH = "/v1/systemone";
|
|
933
934
|
|
|
934
935
|
class LayaDecisionPort {
|
|
@@ -1058,19 +1059,32 @@ function isTimeoutError(err) {
|
|
|
1058
1059
|
}
|
|
1059
1060
|
return false;
|
|
1060
1061
|
}
|
|
1062
|
+
var LAYA_DECISION_PROVIDER = "laya-decision";
|
|
1063
|
+
var LAYA_GOAL_DECISION_PROVIDER = "laya-goal-decision";
|
|
1064
|
+
function providerNameForPurpose(purpose) {
|
|
1065
|
+
return purpose === "goal" ? LAYA_GOAL_DECISION_PROVIDER : LAYA_DECISION_PROVIDER;
|
|
1066
|
+
}
|
|
1061
1067
|
async function loadLayaAuth(authPath = `${process.env.HOME ?? "~"}/.roy-agent/auth.json`) {
|
|
1068
|
+
return loadLayaAuthForPurpose(authPath, "create");
|
|
1069
|
+
}
|
|
1070
|
+
async function loadLayaAuthForPurpose(authPath = `${process.env.HOME ?? "~"}/.roy-agent/auth.json`, purpose = "create") {
|
|
1062
1071
|
try {
|
|
1063
1072
|
const fs = await import("fs/promises");
|
|
1064
1073
|
const raw = await fs.readFile(authPath, "utf-8");
|
|
1065
1074
|
const parsed = JSON.parse(raw);
|
|
1066
|
-
const
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
baseURL
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1075
|
+
const primary = providerNameForPurpose(purpose);
|
|
1076
|
+
const candidates = purpose === "goal" ? [primary, LAYA_DECISION_PROVIDER] : [primary];
|
|
1077
|
+
for (const name of candidates) {
|
|
1078
|
+
const p = parsed.providers?.[name];
|
|
1079
|
+
if (p?.baseURL && p?.apiKey) {
|
|
1080
|
+
return {
|
|
1081
|
+
baseURL: p.baseURL,
|
|
1082
|
+
apiKey: p.apiKey,
|
|
1083
|
+
model: p.model ?? (name === LAYA_GOAL_DECISION_PROVIDER ? DEFAULT_GOAL_MODEL : DEFAULT_MODEL)
|
|
1084
|
+
};
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
return null;
|
|
1074
1088
|
} catch {
|
|
1075
1089
|
return null;
|
|
1076
1090
|
}
|
|
@@ -2937,4 +2951,4 @@ __legacyDecorateClassTS([
|
|
|
2937
2951
|
function createPerTaskGroupChatPlugin(config = {}) {
|
|
2938
2952
|
return new PerTaskGroupChatPlugin(config);
|
|
2939
2953
|
}
|
|
2940
|
-
export { createAutoTaskPlugin, LAYA_REUSE_THRESHOLD, LayaDecisionPlugin, createLayaDecisionPlugin, WORKFLOW_EXTRACTOR_PROMPT, formatExtractorInput, formatExtractorInputFromDescription, parseExtractorOutput, TaskTagPlugin, extractPerTaskChatId, extractPerTaskChatName, NotifyType, LarkCliTaskNotifyHook, createLarkCliTaskNotifyHook, PerTaskGroupChatPlugin, createPerTaskGroupChatPlugin };
|
|
2954
|
+
export { createAutoTaskPlugin, pickGateConfidence, LayaDecisionPort, loadLayaAuthForPurpose, LAYA_REUSE_THRESHOLD, LayaDecisionPlugin, createLayaDecisionPlugin, WORKFLOW_EXTRACTOR_PROMPT, formatExtractorInput, formatExtractorInputFromDescription, parseExtractorOutput, TaskTagPlugin, extractPerTaskChatId, extractPerTaskChatName, NotifyType, LarkCliTaskNotifyHook, createLarkCliTaskNotifyHook, PerTaskGroupChatPlugin, createPerTaskGroupChatPlugin };
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
} from "./roy-agent-core-1pg0fepg.js";
|
|
11
11
|
import {
|
|
12
12
|
builtInPrompts
|
|
13
|
-
} from "./roy-agent-core-
|
|
13
|
+
} from "./roy-agent-core-ma35jyd9.js";
|
|
14
14
|
import {
|
|
15
15
|
DEFAULT_SUBAGENT_PROMPT
|
|
16
16
|
} from "./roy-agent-core-07fvy74y.js";
|
|
@@ -1832,14 +1832,33 @@ ${reuseSection.markdown}`;
|
|
|
1832
1832
|
async forceStopAll() {
|
|
1833
1833
|
return this.forceStopAllAcrossParents();
|
|
1834
1834
|
}
|
|
1835
|
-
async dispose() {
|
|
1835
|
+
async dispose(options) {
|
|
1836
1836
|
for (const [, timer] of this.progressTimers) {
|
|
1837
1837
|
try {
|
|
1838
1838
|
clearInterval(timer);
|
|
1839
1839
|
} catch {}
|
|
1840
1840
|
}
|
|
1841
1841
|
this.progressTimers.clear();
|
|
1842
|
+
const skipAbortRunningTasks = options?.skipAbortRunningTasks === true;
|
|
1843
|
+
const waitForRunningTasks = options?.waitForRunningTasks === true;
|
|
1844
|
+
if (waitForRunningTasks && skipAbortRunningTasks) {
|
|
1845
|
+
const waitStart = Date.now();
|
|
1846
|
+
const WAIT_TIMEOUT_MS = 10 * 60 * 1000;
|
|
1847
|
+
while (Date.now() - waitStart < WAIT_TIMEOUT_MS) {
|
|
1848
|
+
let running = 0;
|
|
1849
|
+
for (const [, t] of this.tasks) {
|
|
1850
|
+
if (t.status === "running" || t.status === "pending")
|
|
1851
|
+
running++;
|
|
1852
|
+
}
|
|
1853
|
+
if (running === 0)
|
|
1854
|
+
break;
|
|
1855
|
+
logger3.info(`[BackgroundTaskManager] dispose waiting for ${running} running task(s)`);
|
|
1856
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
1857
|
+
}
|
|
1858
|
+
}
|
|
1842
1859
|
for (const [, controller] of this.abortControllers) {
|
|
1860
|
+
if (skipAbortRunningTasks)
|
|
1861
|
+
break;
|
|
1843
1862
|
try {
|
|
1844
1863
|
controller.abort();
|
|
1845
1864
|
} catch {}
|
|
@@ -2,6 +2,11 @@ import {
|
|
|
2
2
|
NEW_TASK_ID_HASH_PLACEHOLDER,
|
|
3
3
|
NEW_TASK_ID_PLACEHOLDER
|
|
4
4
|
} from "./roy-agent-core-tr3ep81v.js";
|
|
5
|
+
import {
|
|
6
|
+
TASK_COMPLETE_ITEM_BY_ITEM,
|
|
7
|
+
TASK_COMPLETE_VERIFY_DUTY,
|
|
8
|
+
TASK_COMPLETION_HINT_ANCHOR
|
|
9
|
+
} from "./roy-agent-core-6m4bw8jy.js";
|
|
5
10
|
import {
|
|
6
11
|
getEnvContextOrEmpty,
|
|
7
12
|
init_env_context,
|
|
@@ -38,7 +43,7 @@ var CreateTaskToolSchema = z.object({
|
|
|
38
43
|
description: z.string().optional().describe("Task description"),
|
|
39
44
|
priority: z.enum(["low", "medium", "high"]).optional().describe("Task priority"),
|
|
40
45
|
type: z.enum(["normal", "cycle", "longterm"]).optional().default("normal").describe("Task type. normal: 常规任务,需要跟踪完成. cycle: 周期任务(如每周发周报),无需 complete,定期执行. longterm: 长期任务/愿景目标(如提升公司影响力),难以一次性完成,持续跟进"),
|
|
41
|
-
goals_and_expected_deliverables: z.string().optional().describe("Goals and expected deliverables"),
|
|
46
|
+
goals_and_expected_deliverables: z.string().optional().describe("Goals and expected deliverables. " + "⚠️ 写成可逐条验收的编号子项(1. … 2. … 3. …),每条是一个可判定的交付/动作" + "(文件断言:文件存在/大小/内容;进程断言:命令 exit code/stdout;" + "测试断言:pass/fail 计数;状态断言:版本号/HTTP 200/状态字段)。" + "避免一句话模糊目标(如「做好 X」「完成调研」)——" + "任务完成时 completion hook 会按这些子项逐条对照操作记录验收。"),
|
|
42
47
|
due_date: z.string().optional().describe("Due date (ISO format)"),
|
|
43
48
|
tags: z.array(z.string()).optional().describe("Task tags (for search, stored in task.tags JSON field)"),
|
|
44
49
|
project_path: z.string().default("unknown").describe("Project root path for task positioning. " + "Used to locate the project space for this task. " + "Use 'unknown' if unknown. (Defaults to 'unknown'.)"),
|
|
@@ -84,7 +89,7 @@ var UpdateTaskToolSchema = withLegacyTaskId({
|
|
|
84
89
|
type: z.enum(["normal", "cycle", "longterm"]).optional().describe("Task type"),
|
|
85
90
|
progress: z.number().min(0).max(100).optional(),
|
|
86
91
|
current_status: z.string().optional(),
|
|
87
|
-
goals_and_expected_deliverables: z.string().optional(),
|
|
92
|
+
goals_and_expected_deliverables: z.string().optional().describe("Goals and expected deliverables. " + "⚠️ 追加/修改时同样写成可逐条验收的编号子项(1. … 2. … 3. …)," + "每条是一个可判定的交付/动作(文件断言:文件存在/大小/内容;" + "进程断言:命令 exit code/stdout;测试断言:pass/fail 计数;" + "状态断言:版本号/HTTP 200/状态字段)。" + "避免一句话模糊目标(如「做好 X」「完成调研」)——" + "任务完成时 completion hook 会按这些子项逐条对照操作记录验收。" + "(Task #4833 — 与 task_create 的 goals 规则同源)"),
|
|
88
93
|
due_date: z.string().optional(),
|
|
89
94
|
tags: z.array(z.string()).optional(),
|
|
90
95
|
project_path: z.string().optional().describe("Project path, identifies which project this task belongs to"),
|
|
@@ -316,6 +321,37 @@ function createTaskTool(taskComponent) {
|
|
|
316
321
|
- Same-theme tasks cluster under one parent
|
|
317
322
|
- Long-tail/stranded tasks should be archived or merged
|
|
318
323
|
|
|
324
|
+
## ⚠️ Goals 子项拆分规则 (Task #4791)
|
|
325
|
+
|
|
326
|
+
\`goals_and_expected_deliverables\` **MUST** be written as
|
|
327
|
+
**numbered sub-items** (编号子项) that can each be verified
|
|
328
|
+
one by one (逐条验收) — for \`type=normal\` tasks:
|
|
329
|
+
|
|
330
|
+
1. 实现 X(含文件路径)
|
|
331
|
+
2. 测试 Y 覆盖 Z 场景(pass/fail 计数)
|
|
332
|
+
3. 文档 W 落盘到 <path>
|
|
333
|
+
|
|
334
|
+
⚠️ For \`type=longterm\` / \`type=cycle\` tasks there is no
|
|
335
|
+
per-item completion acceptance(不走逐条 complete 验收), so the
|
|
336
|
+
MUST above does not apply; for those, it is **strongly recommended**
|
|
337
|
+
(建议) to split goals into staged, individually checkable sub-items
|
|
338
|
+
(阶段性可判子项) instead of one vague sentence.
|
|
339
|
+
|
|
340
|
+
Why: when the task is completed, the completion hook
|
|
341
|
+
(task:after.complete) judges the whole goal against the
|
|
342
|
+
operation-record chain, using each sub-item as fine-grained
|
|
343
|
+
evidence (逐条对照的依据). Vague one-line goals (一句话模糊目标,
|
|
344
|
+
如「做好 X」「完成调研」) give the judge nothing to check and
|
|
345
|
+
make a completion hint much more likely.
|
|
346
|
+
|
|
347
|
+
Reference the four atomic assertion forms (Task #4768
|
|
348
|
+
四原子形态) when writing each sub-item:
|
|
349
|
+
|
|
350
|
+
- 文件断言 (file assertion): file exists / size / content grep
|
|
351
|
+
- 进程断言 (process assertion): command exit code / stdout match
|
|
352
|
+
- 测试断言 (test assertion): pass/fail counts, no NEW fail
|
|
353
|
+
- 状态断言 (state assertion): version number / HTTP 200 / status field
|
|
354
|
+
|
|
319
355
|
## ⚠️ Spatial + Temporal Context (Task #2325)
|
|
320
356
|
|
|
321
357
|
A task MUST carry spatio-temporal context so it survives task-handoff
|
|
@@ -917,8 +953,28 @@ Mark a task as completed (progress=100, status=completed). Creates an operation
|
|
|
917
953
|
|
|
918
954
|
1. Read the task's \`goal\` (description) and \`delivery\` (goals_and_expected_deliverables) fields.
|
|
919
955
|
- Use \`task_get(task_id=<id>)\` to fetch the full task record.
|
|
920
|
-
2. **Validate
|
|
921
|
-
-
|
|
956
|
+
2. **Validate whether each goal sub-item was achieved** (${TASK_COMPLETE_ITEM_BY_ITEM}).
|
|
957
|
+
- Goals should be numbered sub-items (编号子项, see task_create rules). Check
|
|
958
|
+
each sub-item against the operation-record chain (操作记录链) and the
|
|
959
|
+
deliverable evidence (context.deliverable_storage_path).
|
|
960
|
+
- The completion hook (task:after.complete) judges the whole goal vs.
|
|
961
|
+
deliverables; numbered sub-items give it finer-grained evidence to
|
|
962
|
+
reason with (逐条对照的证据粒度). If it detects that the goal or
|
|
963
|
+
deliverables appear unmet, the task context will carry a
|
|
964
|
+
\`[${TASK_COMPLETION_HINT_ANCHOR}]\`.
|
|
965
|
+
- **If you receive a [${TASK_COMPLETION_HINT_ANCHOR}]**, you MUST verify it
|
|
966
|
+
(${TASK_COMPLETE_VERIFY_DUTY}): re-read the task via \`task_get\`, then do exactly one of:
|
|
967
|
+
(a) 确认未完成 → \`task_update(task_id=<id>, status=todo)\` reopen and continue; or
|
|
968
|
+
(b) 确认已完成 → \`task_operation_create\` (milestone_type=decision,
|
|
969
|
+
milestone_title=<一行结论>) state why the sub-items are actually
|
|
970
|
+
met (说明理由).
|
|
971
|
+
Never ignore it (不允许无视).
|
|
972
|
+
- If the goal and delivery mismatch **without** a hint, either:
|
|
973
|
+
(a) extend the task via \`task_update\`, or
|
|
974
|
+
(b) record the discrepancy via \`task_operation_create\`
|
|
975
|
+
(milestone_type=decision) — the reason **must** land in the
|
|
976
|
+
operation record (必须落到操作记录里), not merely a line in
|
|
977
|
+
your final report (不能只在报告里带一句).
|
|
922
978
|
3. **Provide \`deliverable_storage_path\`** parameter with the deliverable's address:
|
|
923
979
|
- Feishu doc URL (e.g., https://my.feishu.cn/docx/abc)
|
|
924
980
|
- Local file path (e.g., /tmp/report.md)
|
|
@@ -68,6 +68,8 @@ import {
|
|
|
68
68
|
|
|
69
69
|
// src/env/agent/agent-component.ts
|
|
70
70
|
init_logger();
|
|
71
|
+
import fs from "fs/promises";
|
|
72
|
+
import path from "path";
|
|
71
73
|
import { z } from "zod";
|
|
72
74
|
|
|
73
75
|
// src/env/task/plugins/max-iteration-reminder-plugin.ts
|
|
@@ -600,6 +602,7 @@ class AgentComponent extends BaseComponent {
|
|
|
600
602
|
doomLoopCaches = new Map;
|
|
601
603
|
fallbackState = new Map;
|
|
602
604
|
configComponent;
|
|
605
|
+
skipAbortRunningTasksOnStop = false;
|
|
603
606
|
configWatcher;
|
|
604
607
|
messageConverter = new SessionMessageConverter;
|
|
605
608
|
_constructorConfig;
|
|
@@ -622,6 +625,7 @@ class AgentComponent extends BaseComponent {
|
|
|
622
625
|
throw new Error("ConfigComponent is required for AgentComponent initialization");
|
|
623
626
|
}
|
|
624
627
|
this.configComponent = options.configComponent;
|
|
628
|
+
this.skipAbortRunningTasksOnStop = options.skipAbortRunningTasksOnStop === true;
|
|
625
629
|
await this.registerConfig(options);
|
|
626
630
|
await this.initRegistry(options.configComponent);
|
|
627
631
|
this.setStatus("running");
|
|
@@ -794,13 +798,19 @@ class AgentComponent extends BaseComponent {
|
|
|
794
798
|
}
|
|
795
799
|
async onStop() {
|
|
796
800
|
logger2.info("[AgentComponent] Stopping and cleaning up resources...");
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
+
const skipAbort = this.skipAbortRunningTasksOnStop === true;
|
|
802
|
+
if (!skipAbort) {
|
|
803
|
+
for (const [name, agent] of this.agents) {
|
|
804
|
+
if (agent.status === "running" || agent.status === "idle") {
|
|
805
|
+
this.abort(name);
|
|
806
|
+
logger2.debug(`[AgentComponent] Aborted agent: ${name}`);
|
|
807
|
+
}
|
|
801
808
|
}
|
|
809
|
+
} else {
|
|
810
|
+
logger2.info("[AgentComponent] skipAbortRunningTasksOnStop=true — keeping running agent runs alive");
|
|
802
811
|
}
|
|
803
812
|
this.abortControllers.clear();
|
|
813
|
+
this.aborted.clear();
|
|
804
814
|
this.doomLoopCaches.clear();
|
|
805
815
|
if (this.configWatcher) {
|
|
806
816
|
this.configWatcher();
|
|
@@ -1603,12 +1613,18 @@ class AgentComponent extends BaseComponent {
|
|
|
1603
1613
|
const session = await sessionComponent.get(sessionId);
|
|
1604
1614
|
const existingSnapshot = session?.metadata?.cacheSnapshot;
|
|
1605
1615
|
if (existingSnapshot && typeof existingSnapshot === "object" && existingSnapshot.systemPrompt) {
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1616
|
+
const stale = await this._isMemorySnapshotStale(existingSnapshot);
|
|
1617
|
+
if (stale) {
|
|
1618
|
+
logger2.info(`[AgentComponent._run] cacheSnapshot for session ${sessionId} is stale (${stale}); recreating`);
|
|
1619
|
+
} else {
|
|
1620
|
+
logger2.debug(`[AgentComponent._run] Reusing cacheSnapshot for session ${sessionId} (snapshotAt=${existingSnapshot.snapshotAt})`);
|
|
1621
|
+
return {
|
|
1622
|
+
snapshot: existingSnapshot,
|
|
1623
|
+
systemPromptOverride: existingSnapshot.systemPrompt
|
|
1624
|
+
};
|
|
1625
|
+
}
|
|
1611
1626
|
}
|
|
1627
|
+
const memoryStat = await this._collectMemoryStat();
|
|
1612
1628
|
let resolvedSystemPrompt = initialSystemPrompt;
|
|
1613
1629
|
if (resolvedSystemPrompt && resolvedSystemPrompt.includes("{{memory}}")) {
|
|
1614
1630
|
try {
|
|
@@ -1627,7 +1643,8 @@ class AgentComponent extends BaseComponent {
|
|
|
1627
1643
|
const snapshot = {
|
|
1628
1644
|
systemPrompt: resolvedSystemPrompt ?? agentConfigSystemPrompt ?? "",
|
|
1629
1645
|
additionInfo: effectiveContext.additionInfo || "",
|
|
1630
|
-
snapshotAt: Date.now()
|
|
1646
|
+
snapshotAt: Date.now(),
|
|
1647
|
+
memoryStat
|
|
1631
1648
|
};
|
|
1632
1649
|
try {
|
|
1633
1650
|
await sessionComponent.update(sessionId, {
|
|
@@ -1646,6 +1663,46 @@ class AgentComponent extends BaseComponent {
|
|
|
1646
1663
|
return { snapshot: null, systemPromptOverride: undefined };
|
|
1647
1664
|
}
|
|
1648
1665
|
}
|
|
1666
|
+
async _isMemorySnapshotStale(snapshot) {
|
|
1667
|
+
const stat = snapshot.memoryStat;
|
|
1668
|
+
if (!stat || typeof stat !== "object") {
|
|
1669
|
+
return "no-memoryStat (legacy snapshot)";
|
|
1670
|
+
}
|
|
1671
|
+
const current = await this._collectMemoryStat();
|
|
1672
|
+
if (current.globalMtime !== stat.globalMtime) {
|
|
1673
|
+
return `global memory.md mtime changed (${stat.globalMtime} -> ${current.globalMtime})`;
|
|
1674
|
+
}
|
|
1675
|
+
if (current.projectMtime !== stat.projectMtime) {
|
|
1676
|
+
return `project memory.md mtime changed (${stat.projectMtime} -> ${current.projectMtime})`;
|
|
1677
|
+
}
|
|
1678
|
+
return null;
|
|
1679
|
+
}
|
|
1680
|
+
async _collectMemoryStat() {
|
|
1681
|
+
const empty = { globalMtime: 0, projectMtime: 0 };
|
|
1682
|
+
try {
|
|
1683
|
+
const memoryComponent = this.env?.getComponent?.("memory");
|
|
1684
|
+
if (!memoryComponent?.getMemoryPaths || !memoryComponent.getMemoryFile) {
|
|
1685
|
+
return empty;
|
|
1686
|
+
}
|
|
1687
|
+
const memoryFile = memoryComponent.getMemoryFile();
|
|
1688
|
+
const paths = memoryComponent.getMemoryPaths();
|
|
1689
|
+
const result = { globalMtime: 0, projectMtime: 0 };
|
|
1690
|
+
for (const p of paths) {
|
|
1691
|
+
const filePath = path.join(p.path, memoryFile);
|
|
1692
|
+
try {
|
|
1693
|
+
const st = await fs.stat(filePath);
|
|
1694
|
+
if (p.type === "global") {
|
|
1695
|
+
result.globalMtime = st.mtimeMs;
|
|
1696
|
+
} else if (p.type === "project") {
|
|
1697
|
+
result.projectMtime = st.mtimeMs;
|
|
1698
|
+
}
|
|
1699
|
+
} catch {}
|
|
1700
|
+
}
|
|
1701
|
+
return result;
|
|
1702
|
+
} catch {
|
|
1703
|
+
return empty;
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1649
1706
|
async _fallbackReplaceMemory(systemPrompt) {
|
|
1650
1707
|
if (!systemPrompt.includes("{{memory}}"))
|
|
1651
1708
|
return systemPrompt;
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
createPerTaskGroupChatPlugin,
|
|
14
14
|
extractPerTaskChatId,
|
|
15
15
|
extractPerTaskChatName
|
|
16
|
-
} from "./roy-agent-core-
|
|
16
|
+
} from "./roy-agent-core-b7jbqfsy.js";
|
|
17
17
|
import {
|
|
18
18
|
getSharedEventStore
|
|
19
19
|
} from "./roy-agent-core-na18zwj0.js";
|
|
@@ -311,6 +311,88 @@ var SELF_REMINDER_MESSAGE = `[Timer Self-Reminder]
|
|
|
311
311
|
|
|
312
312
|
请根据分析结果,创建 1-3 个有价值的任务并执行。`;
|
|
313
313
|
var TIMER_OPTIONS_KEY_GET_MANAGER = "getBackgroundTaskManager";
|
|
314
|
+
var SCHEDULE_TICK_INTERVAL_MS = 30 * 1000;
|
|
315
|
+
var TIMER_OPTIONS_KEY_SCHEDULE = "schedule";
|
|
316
|
+
var TIMER_OPTIONS_KEY_SESSION_ID = "sessionId";
|
|
317
|
+
var TIMER_OPTIONS_KEY_SKIP_WHEN_RUNNING = "skipWhenRunning";
|
|
318
|
+
var TIME_PATTERN = /^([01]\d|2[0-3]):([0-5]\d)$/;
|
|
319
|
+
function isValidScheduleTime(time) {
|
|
320
|
+
return typeof time === "string" && TIME_PATTERN.test(time);
|
|
321
|
+
}
|
|
322
|
+
function parseSchedule(raw) {
|
|
323
|
+
if (!Array.isArray(raw))
|
|
324
|
+
return [];
|
|
325
|
+
const result = [];
|
|
326
|
+
for (const item of raw) {
|
|
327
|
+
if (!item || typeof item !== "object")
|
|
328
|
+
continue;
|
|
329
|
+
const obj = item;
|
|
330
|
+
if (!isValidScheduleTime(obj.time))
|
|
331
|
+
continue;
|
|
332
|
+
if (typeof obj.prompt !== "string" || obj.prompt.trim().length === 0)
|
|
333
|
+
continue;
|
|
334
|
+
const entry = {
|
|
335
|
+
time: obj.time,
|
|
336
|
+
prompt: obj.prompt
|
|
337
|
+
};
|
|
338
|
+
if (typeof obj.label === "string" && obj.label.length > 0) {
|
|
339
|
+
entry.label = obj.label;
|
|
340
|
+
}
|
|
341
|
+
if (typeof obj.sessionId === "string" && obj.sessionId.length > 0) {
|
|
342
|
+
entry.sessionId = obj.sessionId;
|
|
343
|
+
}
|
|
344
|
+
result.push(entry);
|
|
345
|
+
}
|
|
346
|
+
return result;
|
|
347
|
+
}
|
|
348
|
+
function validateScheduleConfig(config) {
|
|
349
|
+
const errors = [];
|
|
350
|
+
const options = config.options ?? {};
|
|
351
|
+
const raw = options[TIMER_OPTIONS_KEY_SCHEDULE];
|
|
352
|
+
if (raw === undefined || raw === null)
|
|
353
|
+
return errors;
|
|
354
|
+
if (!Array.isArray(raw)) {
|
|
355
|
+
errors.push("Timer schedule must be an array of { time, prompt } entries");
|
|
356
|
+
return errors;
|
|
357
|
+
}
|
|
358
|
+
if (raw.length === 0) {
|
|
359
|
+
errors.push("Timer schedule must not be empty");
|
|
360
|
+
return errors;
|
|
361
|
+
}
|
|
362
|
+
raw.forEach((item, index) => {
|
|
363
|
+
if (!item || typeof item !== "object") {
|
|
364
|
+
errors.push(`Timer schedule[${index}] must be an object`);
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
const obj = item;
|
|
368
|
+
if (!isValidScheduleTime(obj.time)) {
|
|
369
|
+
errors.push(`Timer schedule[${index}].time must be in HH:MM format (24-hour), got: ${String(obj.time)}`);
|
|
370
|
+
}
|
|
371
|
+
if (typeof obj.prompt !== "string" || obj.prompt.trim().length === 0) {
|
|
372
|
+
errors.push(`Timer schedule[${index}].prompt is required and must be a non-empty string`);
|
|
373
|
+
}
|
|
374
|
+
});
|
|
375
|
+
const seenTimes = new Set;
|
|
376
|
+
const duplicatedTimes = new Set;
|
|
377
|
+
for (const item of raw) {
|
|
378
|
+
if (!item || typeof item !== "object")
|
|
379
|
+
continue;
|
|
380
|
+
const time = item.time;
|
|
381
|
+
if (!isValidScheduleTime(time))
|
|
382
|
+
continue;
|
|
383
|
+
if (seenTimes.has(time)) {
|
|
384
|
+
duplicatedTimes.add(time);
|
|
385
|
+
}
|
|
386
|
+
seenTimes.add(time);
|
|
387
|
+
}
|
|
388
|
+
if (duplicatedTimes.size > 0) {
|
|
389
|
+
errors.push(`Timer schedule contains duplicate time entries: ${Array.from(duplicatedTimes).join(", ")}`);
|
|
390
|
+
}
|
|
391
|
+
if (config.interval !== undefined && config.interval !== null) {
|
|
392
|
+
errors.push("Timer schedule and interval are mutually exclusive: use schedule for fixed daily times, or interval for periodic triggering");
|
|
393
|
+
}
|
|
394
|
+
return errors;
|
|
395
|
+
}
|
|
314
396
|
|
|
315
397
|
class TimerInstance {
|
|
316
398
|
config;
|
|
@@ -318,21 +400,38 @@ class TimerInstance {
|
|
|
318
400
|
sessionId;
|
|
319
401
|
getRunningTasks;
|
|
320
402
|
customPrompt;
|
|
403
|
+
schedule;
|
|
404
|
+
scheduleMode;
|
|
405
|
+
invalidScheduleWarned = false;
|
|
406
|
+
scheduleSessionId;
|
|
407
|
+
skipWhenRunning;
|
|
408
|
+
firedKeys = new Set;
|
|
409
|
+
now;
|
|
321
410
|
status = "created";
|
|
322
411
|
timerId;
|
|
323
412
|
eventHandler;
|
|
324
|
-
constructor(config, getRunningTasks) {
|
|
413
|
+
constructor(config, getRunningTasks, now) {
|
|
325
414
|
this.config = config;
|
|
326
|
-
this.
|
|
415
|
+
this.now = now ?? (() => Date.now());
|
|
416
|
+
const options = config.options ?? {};
|
|
417
|
+
const rawSchedule = options[TIMER_OPTIONS_KEY_SCHEDULE];
|
|
418
|
+
const scheduleConfigured = rawSchedule !== undefined && rawSchedule !== null;
|
|
419
|
+
this.schedule = parseSchedule(rawSchedule);
|
|
420
|
+
this.scheduleMode = scheduleConfigured;
|
|
421
|
+
const isScheduleMode = this.scheduleMode;
|
|
422
|
+
this.interval = isScheduleMode ? SCHEDULE_TICK_INTERVAL_MS : typeof config.interval === "number" && config.interval > 0 ? config.interval : DEFAULT_TIMER_INTERVAL_MS;
|
|
327
423
|
this.sessionId = `timer-${config.id}-${process.pid}`;
|
|
328
|
-
const
|
|
424
|
+
const explicitSessionId = options[TIMER_OPTIONS_KEY_SESSION_ID];
|
|
425
|
+
this.scheduleSessionId = typeof explicitSessionId === "string" && explicitSessionId.length > 0 ? explicitSessionId : `timer-${config.id}`;
|
|
426
|
+
this.skipWhenRunning = options[TIMER_OPTIONS_KEY_SKIP_WHEN_RUNNING] === true;
|
|
427
|
+
const optionsPrompt = options.prompt;
|
|
329
428
|
if (typeof optionsPrompt === "string" && optionsPrompt.trim().length > 0) {
|
|
330
429
|
this.customPrompt = optionsPrompt;
|
|
331
430
|
}
|
|
332
431
|
if (getRunningTasks) {
|
|
333
432
|
this.getRunningTasks = getRunningTasks;
|
|
334
433
|
} else {
|
|
335
|
-
const factory =
|
|
434
|
+
const factory = options[TIMER_OPTIONS_KEY_GET_MANAGER];
|
|
336
435
|
if (typeof factory === "function") {
|
|
337
436
|
const manager = factory();
|
|
338
437
|
this.getRunningTasks = () => manager.listTasks();
|
|
@@ -341,6 +440,9 @@ class TimerInstance {
|
|
|
341
440
|
}
|
|
342
441
|
}
|
|
343
442
|
}
|
|
443
|
+
get isScheduleMode() {
|
|
444
|
+
return this.scheduleMode;
|
|
445
|
+
}
|
|
344
446
|
async start() {
|
|
345
447
|
if (this.status === "running")
|
|
346
448
|
return;
|
|
@@ -366,6 +468,10 @@ class TimerInstance {
|
|
|
366
468
|
this.eventHandler = undefined;
|
|
367
469
|
}
|
|
368
470
|
tick() {
|
|
471
|
+
if (this.isScheduleMode) {
|
|
472
|
+
this.tickSchedule();
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
369
475
|
let allTasks;
|
|
370
476
|
try {
|
|
371
477
|
allTasks = this.getRunningTasks();
|
|
@@ -380,19 +486,81 @@ class TimerInstance {
|
|
|
380
486
|
const event = this.buildSelfReminderEvent();
|
|
381
487
|
this.eventHandler?.(event);
|
|
382
488
|
}
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
489
|
+
tickSchedule() {
|
|
490
|
+
if (this.schedule.length === 0) {
|
|
491
|
+
if (!this.invalidScheduleWarned) {
|
|
492
|
+
this.invalidScheduleWarned = true;
|
|
493
|
+
console.warn(`[TimerInstance] Timer ${this.config.id} has an invalid or empty schedule configuration; ` + `no events will be fired. Check options.schedule (expected [{ time: "HH:MM", prompt: "..." }]).`);
|
|
494
|
+
}
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
const now = new Date(this.now());
|
|
498
|
+
const hh = String(now.getHours()).padStart(2, "0");
|
|
499
|
+
const mm = String(now.getMinutes()).padStart(2, "0");
|
|
500
|
+
const currentTime = `${hh}:${mm}`;
|
|
501
|
+
const dateKey = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")}`;
|
|
502
|
+
for (const entry of this.schedule) {
|
|
503
|
+
if (entry.time !== currentTime)
|
|
504
|
+
continue;
|
|
505
|
+
const firedKey = `${dateKey}#${entry.time}`;
|
|
506
|
+
if (this.firedKeys.has(firedKey)) {
|
|
507
|
+
continue;
|
|
508
|
+
}
|
|
509
|
+
if (this.skipWhenRunning) {
|
|
510
|
+
let hasRunning = false;
|
|
511
|
+
try {
|
|
512
|
+
const allTasks = this.getRunningTasks();
|
|
513
|
+
hasRunning = Array.isArray(allTasks) && allTasks.some((t) => t.status === "running");
|
|
514
|
+
} catch (error) {
|
|
515
|
+
console.warn(`[TimerInstance] Failed to get running tasks, skipping tick: ${error}`);
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
if (hasRunning) {
|
|
519
|
+
continue;
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
this.firedKeys.add(firedKey);
|
|
523
|
+
this.pruneFiredKeys(dateKey);
|
|
524
|
+
const event = this.buildSelfReminderEvent(entry);
|
|
525
|
+
this.eventHandler?.(event);
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
pruneFiredKeys(currentDateKey) {
|
|
530
|
+
if (this.firedKeys.size <= 32)
|
|
531
|
+
return;
|
|
532
|
+
for (const key of this.firedKeys) {
|
|
533
|
+
const datePart = key.split("#")[0];
|
|
534
|
+
if (datePart !== currentDateKey) {
|
|
535
|
+
this.firedKeys.delete(key);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
buildSelfReminderEvent(entry) {
|
|
540
|
+
const timestamp = this.now();
|
|
541
|
+
const message = entry?.prompt ?? this.customPrompt ?? SELF_REMINDER_MESSAGE;
|
|
542
|
+
const triggerSessionId = entry !== undefined ? entry.sessionId ?? this.scheduleSessionId : undefined;
|
|
543
|
+
const metadata = {
|
|
544
|
+
source: "event-source",
|
|
545
|
+
sourceId: this.config.id,
|
|
546
|
+
sourceMessageType: "timer.selfReminder",
|
|
547
|
+
sessionId: this.sessionId
|
|
548
|
+
};
|
|
549
|
+
if (triggerSessionId !== undefined) {
|
|
550
|
+
metadata.trigger_session_id = triggerSessionId;
|
|
551
|
+
metadata.createSessionIfMissing = true;
|
|
552
|
+
}
|
|
553
|
+
if (entry?.label !== undefined) {
|
|
554
|
+
metadata.scheduleLabel = entry.label;
|
|
555
|
+
}
|
|
556
|
+
if (entry !== undefined) {
|
|
557
|
+
metadata.scheduleTime = entry.time;
|
|
558
|
+
}
|
|
386
559
|
return {
|
|
387
560
|
id: `es-${this.config.id}-${timestamp}-${Math.random().toString(36).slice(2, 8)}`,
|
|
388
561
|
type: "event-source.event.timer",
|
|
389
562
|
timestamp,
|
|
390
|
-
metadata
|
|
391
|
-
source: "event-source",
|
|
392
|
-
sourceId: this.config.id,
|
|
393
|
-
sourceMessageType: "timer.selfReminder",
|
|
394
|
-
sessionId: this.sessionId
|
|
395
|
-
},
|
|
563
|
+
metadata,
|
|
396
564
|
payload: {
|
|
397
565
|
sourceId: this.config.id,
|
|
398
566
|
sourceType: "timer",
|
|
@@ -415,6 +583,9 @@ class TimerInstance {
|
|
|
415
583
|
__legacyDecorateClassTS([
|
|
416
584
|
TracedAs("timer.tick", { recordParams: false, log: false })
|
|
417
585
|
], TimerInstance.prototype, "tick", null);
|
|
586
|
+
__legacyDecorateClassTS([
|
|
587
|
+
TracedAs("timer.tickSchedule", { recordParams: false, log: false })
|
|
588
|
+
], TimerInstance.prototype, "tickSchedule", null);
|
|
418
589
|
__legacyDecorateClassTS([
|
|
419
590
|
TracedAs("timer.buildSelfReminderEvent", { recordParams: false, log: false })
|
|
420
591
|
], TimerInstance.prototype, "buildSelfReminderEvent", null);
|
|
@@ -423,7 +594,9 @@ var timerHandler = {
|
|
|
423
594
|
validateConfig(config) {
|
|
424
595
|
const errors = [];
|
|
425
596
|
const { interval } = config;
|
|
426
|
-
|
|
597
|
+
errors.push(...validateScheduleConfig(config));
|
|
598
|
+
const hasSchedule = config.options?.[TIMER_OPTIONS_KEY_SCHEDULE] !== undefined;
|
|
599
|
+
if (!hasSchedule && interval !== undefined && interval !== null) {
|
|
427
600
|
if (typeof interval !== "number" || !Number.isFinite(interval)) {
|
|
428
601
|
errors.push("Timer interval must be a finite number");
|
|
429
602
|
} else if (interval <= 0) {
|
|
@@ -439,7 +612,7 @@ var timerHandler = {
|
|
|
439
612
|
return [
|
|
440
613
|
{
|
|
441
614
|
name: "interval",
|
|
442
|
-
description: `Timer interval in milliseconds (default: ${DEFAULT_TIMER_INTERVAL_MS} = 10 minutes)
|
|
615
|
+
description: `Timer interval in milliseconds (default: ${DEFAULT_TIMER_INTERVAL_MS} = 10 minutes). Mutually exclusive with schedule.`,
|
|
443
616
|
required: false,
|
|
444
617
|
type: "number"
|
|
445
618
|
},
|
|
@@ -448,6 +621,18 @@ var timerHandler = {
|
|
|
448
621
|
description: "Custom prompt message for SelfReminder events (replaces default message)",
|
|
449
622
|
required: false,
|
|
450
623
|
type: "string"
|
|
624
|
+
},
|
|
625
|
+
{
|
|
626
|
+
name: TIMER_OPTIONS_KEY_SCHEDULE,
|
|
627
|
+
description: "JSON array of { time: 'HH:MM', prompt: string, label?: string, sessionId?: string } " + "for fixed daily trigger times (e.g. 10:00 / 17:00 / 20:00). Mutually exclusive with interval.",
|
|
628
|
+
required: false,
|
|
629
|
+
type: "string"
|
|
630
|
+
},
|
|
631
|
+
{
|
|
632
|
+
name: TIMER_OPTIONS_KEY_SESSION_ID,
|
|
633
|
+
description: "Explicit session ID for timer events (schedule mode). Events are routed to this " + "session instead of the main REPL session.",
|
|
634
|
+
required: false,
|
|
635
|
+
type: "string"
|
|
451
636
|
}
|
|
452
637
|
];
|
|
453
638
|
}
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
import {
|
|
2
|
+
TASK_COMPLETE_ITEM_BY_ITEM,
|
|
3
|
+
TASK_COMPLETE_VERIFY_DUTY,
|
|
4
|
+
TASK_COMPLETION_HINT_ANCHOR
|
|
5
|
+
} from "./roy-agent-core-6m4bw8jy.js";
|
|
6
|
+
|
|
1
7
|
// src/env/prompt/prompts-index.ts
|
|
2
8
|
var builtInPrompts = {
|
|
3
9
|
default: `你是 {{agent_name}} 接待智能体(Receptionist Agent)—— {{agent_name}} 系统的智能入口。
|
|
@@ -125,7 +131,7 @@ var builtInPrompts = {
|
|
|
125
131
|
- ✅ 功能开发、bug 修复、重构、文档生成
|
|
126
132
|
- ✅ 网络搜索和研究
|
|
127
133
|
|
|
128
|
-
**roy** 的提示词中内置了任务生命周期——它知道如何使用 \`task_create\`、\`task_update\`、\`task_operation_create\` 和 \`task_complete\` 来跟踪每项工作。
|
|
134
|
+
**roy** 的提示词中内置了任务生命周期——它知道如何使用 \`task_create\`、\`task_update\`、\`task_operation_create\` 和 \`task_complete\` 来跟踪每项工作。task_complete 时会${TASK_COMPLETE_VERIFY_DUTY}:按子项逐条验收(${TASK_COMPLETE_ITEM_BY_ITEM}),若收到 [${TASK_COMPLETION_HINT_ANCHOR}] 必须核实并二选一(重开 / 落 decision 说明理由)。
|
|
129
135
|
|
|
130
136
|
推荐使用:
|
|
131
137
|
\`\`\`
|
|
@@ -893,8 +899,13 @@ Follow this lifecycle for every task:
|
|
|
893
899
|
┌─────────────────────────────────────────────────────────────────┐
|
|
894
900
|
│ 6. COMPLETE (task_complete) │
|
|
895
901
|
│ - Mark task as completed (progress=100%, status=completed) │
|
|
896
|
-
│ -
|
|
897
|
-
│
|
|
902
|
+
│ - ${TASK_COMPLETE_ITEM_BY_ITEM}: check each goal sub-item │
|
|
903
|
+
│ against the operation-record chain + deliverable evidence │
|
|
904
|
+
│ - If a [${TASK_COMPLETION_HINT_ANCHOR}] appears in task │
|
|
905
|
+
│ context, you MUST ${TASK_COMPLETE_VERIFY_DUTY}: reopen │
|
|
906
|
+
│ (task_update status=todo) or record why it is met │
|
|
907
|
+
│ (task_operation_create decision). Never ignore it. │
|
|
908
|
+
│ - Create completion operation record │
|
|
898
909
|
└─────────────────────────────────────────────────────────────────┘
|
|
899
910
|
\`\`\`
|
|
900
911
|
|