@ai-setting/roy-agent-core 1.7.75 → 1.7.77

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.
Files changed (59) hide show
  1. package/dist/env/agent/agent-component.js +2 -2
  2. package/dist/env/agent/index.js +2 -2
  3. package/dist/env/event-source/event-source-handlers.js +9 -8
  4. package/dist/env/event-source/index.js +10 -9
  5. package/dist/env/index.js +16 -15
  6. package/dist/env/prompt/index.js +4 -3
  7. package/dist/env/prompt/prompts-index.d.ts +11 -9
  8. package/dist/env/task/backend/index.js +45 -1
  9. package/dist/env/task/backend/task-store-backend.d.ts +12 -0
  10. package/dist/env/task/backend/types.d.ts +19 -0
  11. package/dist/env/task/delegate/delegate-tool.js +4 -3
  12. package/dist/env/task/delegate/index.js +4 -3
  13. package/dist/env/task/hooks/contexts.d.ts +12 -1
  14. package/dist/env/task/hooks/task-hook-points.d.ts +8 -3
  15. package/dist/env/task/index.d.ts +1 -0
  16. package/dist/env/task/index.js +17 -7
  17. package/dist/env/task/mcp/index.js +6 -2
  18. package/dist/env/task/mcp/task-mcp-server.d.ts +1 -1
  19. package/dist/env/task/plugins/fake-user-message-helper.d.ts +10 -2
  20. package/dist/env/task/plugins/index.d.ts +1 -0
  21. package/dist/env/task/plugins/index.js +18 -6
  22. package/dist/env/task/plugins/lark-card-patch-notifier.js +1 -1
  23. package/dist/env/task/plugins/task-op-watermark-plugin.d.ts +112 -0
  24. package/dist/env/task/prompt/index.js +2 -1
  25. package/dist/env/task/prompt/task-discipline.d.ts +1 -1
  26. package/dist/env/task/storage/index.js +2 -2
  27. package/dist/env/task/storage/sqlite-task-store.d.ts +54 -0
  28. package/dist/env/task/task-component.d.ts +95 -0
  29. package/dist/env/task/tools/cancel-tool.d.ts +12 -4
  30. package/dist/env/task/tools/completion-copy.d.ts +92 -0
  31. package/dist/env/task/tools/index.d.ts +1 -0
  32. package/dist/env/task/tools/index.js +5 -2
  33. package/dist/env/task/tools/operation/operation-types.d.ts +13 -13
  34. package/dist/env/task/tools/pre-complete-tool.d.ts +32 -0
  35. package/dist/env/task/tools/task-types.d.ts +49 -17
  36. package/dist/env/task/types.d.ts +13 -1
  37. package/dist/env/workflow/plugins/index.js +2 -2
  38. package/dist/env/workflow/types/event.d.ts +4 -4
  39. package/dist/index.d.ts +1 -1
  40. package/dist/index.js +26 -16
  41. package/dist/shared/@ai-setting/{roy-agent-core-n9q1encx.js → roy-agent-core-0v27y99n.js} +1 -1
  42. package/dist/shared/@ai-setting/{roy-agent-core-60jbrfye.js → roy-agent-core-3gpygqcx.js} +30 -6
  43. package/dist/shared/@ai-setting/roy-agent-core-4rtg69cn.js +26 -0
  44. package/dist/shared/@ai-setting/{roy-agent-core-djpq6yb0.js → roy-agent-core-9vhq7fvv.js} +6 -2
  45. package/dist/shared/@ai-setting/{roy-agent-core-3e6gg17d.js → roy-agent-core-9vxh6f8h.js} +169 -30
  46. package/dist/shared/@ai-setting/{roy-agent-core-jg65qnwa.js → roy-agent-core-aa2j7bk9.js} +1 -1
  47. package/dist/shared/@ai-setting/{roy-agent-core-29b6hc39.js → roy-agent-core-cmprygrp.js} +1 -1
  48. package/dist/shared/@ai-setting/{roy-agent-core-snvh4s2p.js → roy-agent-core-ew6qb5yx.js} +1 -1
  49. package/dist/shared/@ai-setting/{roy-agent-core-abqhkbn3.js → roy-agent-core-ezvpreqz.js} +23 -0
  50. package/dist/shared/@ai-setting/roy-agent-core-h21gngxf.js +247 -0
  51. package/dist/shared/@ai-setting/{roy-agent-core-p3zv1mx5.js → roy-agent-core-hd3n57zs.js} +1 -1
  52. package/dist/shared/@ai-setting/{roy-agent-core-11shp00p.js → roy-agent-core-m46fe3x4.js} +21 -31
  53. package/dist/shared/@ai-setting/{roy-agent-core-ntq0gz7q.js → roy-agent-core-q813vafd.js} +1 -1
  54. package/dist/shared/@ai-setting/{roy-agent-core-4gr3k7qg.js → roy-agent-core-rhtdspx6.js} +140 -29
  55. package/dist/shared/@ai-setting/{roy-agent-core-y7x2qzwc.js → roy-agent-core-rsn44n9y.js} +10 -6
  56. package/dist/shared/@ai-setting/{roy-agent-core-hnyk1by7.js → roy-agent-core-vffccgf6.js} +152 -33
  57. package/dist/shared/@ai-setting/{roy-agent-core-nmx9934r.js → roy-agent-core-w0qjeq2v.js} +1 -1
  58. package/package.json +1 -1
  59. package/dist/shared/@ai-setting/roy-agent-core-f77k3gqt.js +0 -71
@@ -709,6 +709,22 @@ var init_sqlite_task_store = __esm(() => {
709
709
  const limit = options.limit || 20;
710
710
  return ops.slice(offset, offset + limit);
711
711
  }
712
+ async listOperationsFresh(options) {
713
+ await this.initialize();
714
+ const stmt = this.db.prepare("SELECT * FROM task_operation WHERE task_id = ? ORDER BY id DESC");
715
+ const rows = stmt.all(options.taskId);
716
+ let ops = rows.map((row) => this.rowToOperation(row));
717
+ const filterType = options.milestoneType ?? options.actionType;
718
+ if (filterType) {
719
+ ops = ops.filter((op) => op.milestoneType === filterType);
720
+ }
721
+ const offset = options.offset || 0;
722
+ const limit = options.limit || 20;
723
+ return ops.slice(offset, offset + limit);
724
+ }
725
+ invalidateOperationsCache(taskId) {
726
+ this.operationsCache.delete(taskId);
727
+ }
712
728
  async getTaskIdBySessionId(sessionId) {
713
729
  await this.initialize();
714
730
  if (!sessionId)
@@ -716,6 +732,13 @@ var init_sqlite_task_store = __esm(() => {
716
732
  const row = this.db.prepare("SELECT task_id FROM task_operation WHERE session_id = ? AND milestone_type = 'create' ORDER BY timestamp ASC LIMIT 1").get(sessionId);
717
733
  return typeof row?.task_id === "number" ? row.task_id : undefined;
718
734
  }
735
+ async getTaskIdsBySessionId(sessionId) {
736
+ await this.initialize();
737
+ if (!sessionId)
738
+ return [];
739
+ const rows = this.db.prepare("SELECT DISTINCT task_id FROM task_operation WHERE session_id = ? ORDER BY task_id ASC").all(sessionId);
740
+ return rows.map((r) => r.task_id).filter((id) => typeof id === "number");
741
+ }
719
742
  rowToTask(row) {
720
743
  return {
721
744
  id: row.id,
@@ -0,0 +1,247 @@
1
+ import {
2
+ init_decorator,
3
+ wrapFunction
4
+ } from "./roy-agent-core-qq96p0tj.js";
5
+ import {
6
+ createLogger,
7
+ init_logger
8
+ } from "./roy-agent-core-7f7t9wwp.js";
9
+
10
+ // src/env/task/plugins/task-op-watermark-plugin.ts
11
+ init_logger();
12
+
13
+ // src/env/task/plugins/fake-user-message-helper.ts
14
+ init_decorator();
15
+ var KIND_PRIORITY = {
16
+ delegate: 10,
17
+ "task-ops": 20,
18
+ "max-iter": 30
19
+ };
20
+ var KIND_FIELD = "__fakeUserMessageKind";
21
+ function ensureMetadata(toolResult) {
22
+ if (!toolResult)
23
+ return;
24
+ if (!toolResult.result)
25
+ toolResult.result = {};
26
+ if (!toolResult.result.metadata)
27
+ toolResult.result.metadata = {};
28
+ return toolResult.result.metadata;
29
+ }
30
+ function stringContent(msg) {
31
+ const c = msg?.content;
32
+ return typeof c === "string" ? c : JSON.stringify(c);
33
+ }
34
+ function enqueueToAddFakeUserMessageImpl(toolResult, newMsg, newKind) {
35
+ const meta = ensureMetadata(toolResult);
36
+ if (!meta)
37
+ return { willMerge: false, finalKind: newKind };
38
+ const existing = meta.toAddFakeUserMessage;
39
+ const existingKind = meta[KIND_FIELD];
40
+ if (!existing) {
41
+ meta.toAddFakeUserMessage = newMsg;
42
+ meta[KIND_FIELD] = newKind;
43
+ return { willMerge: false, finalKind: newKind };
44
+ }
45
+ let merged;
46
+ if (KIND_PRIORITY[newKind] >= KIND_PRIORITY[existingKind ?? newKind]) {
47
+ merged = {
48
+ role: "user",
49
+ content: `${stringContent(existing)}
50
+
51
+ ${stringContent(newMsg)}`
52
+ };
53
+ } else {
54
+ merged = {
55
+ role: "user",
56
+ content: `${stringContent(newMsg)}
57
+
58
+ ${stringContent(existing)}`
59
+ };
60
+ }
61
+ meta.toAddFakeUserMessage = merged;
62
+ const finalKind = KIND_PRIORITY[newKind] >= KIND_PRIORITY[existingKind ?? newKind] ? newKind : existingKind ?? newKind;
63
+ meta[KIND_FIELD] = finalKind;
64
+ return { willMerge: true, finalKind };
65
+ }
66
+ var enqueueToAddFakeUserMessage = wrapFunction(enqueueToAddFakeUserMessageImpl, "plugin.fake-user-message.enqueue", {
67
+ recordParams: false,
68
+ recordResult: false,
69
+ log: true,
70
+ resultFilter: (result) => ({
71
+ "fake-user-message.will_merge": result.willMerge,
72
+ "fake-user-message.final_kind": result.finalKind
73
+ })
74
+ });
75
+
76
+ // src/env/task/plugins/task-op-watermark-plugin.ts
77
+ var logger = createLogger("TaskOpWatermarkPlugin");
78
+ var TASK_OP_WATERMARK_MAX_REMINDERS = 5;
79
+ var TASK_OP_WATERMARK_MESSAGE_LIMIT = 1500;
80
+ var TASK_OP_WATERMARK_AUDIT_INTERVAL = 15;
81
+ var MAX_DETAIL_ITEMS = 3;
82
+ var MAX_DESC_LENGTH = 200;
83
+ var UNKNOWN_SESSION = "unknown";
84
+ var TRUNCATION_SUFFIX = "…(已截断)";
85
+ function truncate(s, max) {
86
+ if (s.length <= max)
87
+ return s;
88
+ return `${s.slice(0, max)}…(已截断)`;
89
+ }
90
+ function buildReminderMessage(taskId, taskTitle, ops) {
91
+ const count = ops.length;
92
+ const lines = [];
93
+ lines.push(`[task-ops] Task #${taskId}${taskTitle ? `「${taskTitle}」` : ""} 有 ${count} 条由**其他 agent** 新增的操作记录,请读取并按需调整你的行为。`);
94
+ lines.push("");
95
+ lines.push("最新记录:");
96
+ const details = ops.slice(0, MAX_DETAIL_ITEMS);
97
+ for (const op of details) {
98
+ const desc = op.milestoneDescription ? truncate(op.milestoneDescription.split(`
99
+ `)[0], MAX_DESC_LENGTH) : "";
100
+ lines.push(`- [${op.milestoneType}] ${op.milestoneTitle}${desc ? `
101
+ ${desc}` : ""}`);
102
+ }
103
+ if (count > MAX_DETAIL_ITEMS) {
104
+ lines.push(`(其余 ${count - MAX_DETAIL_ITEMS} 条较旧记录省略,可用 task_operation_list 查看全部)`);
105
+ }
106
+ lines.push("");
107
+ lines.push(`请调用 task_operation_list(task_id=${taskId}) 读取全部新记录,判断是否需要修改你的计划、产出或下一步动作。`);
108
+ lines.push("若这些记录与你的当前工作无关,忽略即可,无需回复。");
109
+ return lines.join(`
110
+ `);
111
+ }
112
+
113
+ class TaskOpWatermarkPlugin {
114
+ name = "TaskOpWatermarkPlugin";
115
+ version = "1.0.0";
116
+ description = "Task #5081 — agent:after.tool 水位比对:同一 task 出现他人(sessionId 过滤 + unknown 过滤)新增的 operation 时,经共享 meta-protocol 注入 user role 提醒(N 聚 1 / 限流 5 / 长度 1500 / 15 次兜底对账 / fresh 旁路绕 operationsCache)。";
117
+ priority = 40;
118
+ watermarks = new Map;
119
+ resolveTaskComponent;
120
+ constructor(config) {
121
+ this.resolveTaskComponent = config?.resolveTaskComponent;
122
+ }
123
+ async execute(ctx) {
124
+ try {
125
+ await this.check(ctx);
126
+ } catch (err) {
127
+ logger.warn(`[TaskOpWatermark] check failed (fail-open): ${err?.message}`);
128
+ }
129
+ return { continue: true };
130
+ }
131
+ async check(ctx) {
132
+ const sessionId = ctx.context?.sessionId;
133
+ if (!sessionId)
134
+ return;
135
+ const taskComponent = this.resolveTaskComponent?.();
136
+ if (!taskComponent)
137
+ return;
138
+ const taskIds = await this.resolveTaskIds(taskComponent, sessionId);
139
+ if (taskIds.length === 0)
140
+ return;
141
+ const taskKey = taskIds.join(",");
142
+ let wm = this.watermarks.get(sessionId);
143
+ if (!wm || wm.taskIds.join(",") !== taskKey) {
144
+ wm = {
145
+ lastSeenOpId: 0,
146
+ lastNotifiedOpId: 0,
147
+ taskIds,
148
+ reminderCount: 0,
149
+ toolsSinceAudit: 0
150
+ };
151
+ this.watermarks.set(sessionId, wm);
152
+ }
153
+ wm.toolsSinceAudit++;
154
+ const isAuditTurn = wm.toolsSinceAudit >= TASK_OP_WATERMARK_AUDIT_INTERVAL;
155
+ if (isAuditTurn)
156
+ wm.toolsSinceAudit = 0;
157
+ const lowerBound = isAuditTurn || wm.lastSeenOpId === 0 ? wm.lastNotifiedOpId : wm.lastSeenOpId;
158
+ let newOps = [];
159
+ let maxIdSeen = wm.lastSeenOpId;
160
+ for (const taskId of taskIds) {
161
+ const ops = await this.listOps(taskComponent, taskId);
162
+ const othersNew = ops.filter((op) => op.id > lowerBound && !this.isSelf(op, sessionId));
163
+ for (const op of othersNew) {
164
+ newOps.push(op);
165
+ if (op.id > maxIdSeen)
166
+ maxIdSeen = op.id;
167
+ }
168
+ const selfMax = ops.filter((op) => this.isSelf(op, sessionId)).reduce((m, op) => Math.max(m, op.id), 0);
169
+ maxIdSeen = Math.max(maxIdSeen, selfMax);
170
+ }
171
+ const seen = new Set;
172
+ newOps = newOps.filter((op) => seen.has(op.id) ? false : (seen.add(op.id), true)).sort((a, b) => b.id - a.id);
173
+ wm.lastSeenOpId = Math.max(wm.lastSeenOpId, maxIdSeen);
174
+ if (newOps.length === 0)
175
+ return;
176
+ if (wm.reminderCount >= TASK_OP_WATERMARK_MAX_REMINDERS) {
177
+ logger.info(`[TaskOpWatermark] session=${sessionId.slice(0, 12)}... reminder limit reached (${wm.reminderCount}) — watermark advanced silently`);
178
+ return;
179
+ }
180
+ if (!ctx.toolResult)
181
+ return;
182
+ const byTask = new Map;
183
+ for (const op of newOps) {
184
+ const arr = byTask.get(op.taskId) ?? [];
185
+ arr.push(op);
186
+ byTask.set(op.taskId, arr);
187
+ }
188
+ const sections = [];
189
+ for (const [taskId, ops] of byTask) {
190
+ const title = (await safeGetTask(taskComponent, taskId))?.title;
191
+ sections.push(buildReminderMessage(taskId, title, ops));
192
+ }
193
+ const content = sections.join(`
194
+
195
+ `);
196
+ const finalContent = content.length > TASK_OP_WATERMARK_MESSAGE_LIMIT ? `${content.slice(0, TASK_OP_WATERMARK_MESSAGE_LIMIT - TRUNCATION_SUFFIX.length)}${TRUNCATION_SUFFIX}` : content;
197
+ enqueueToAddFakeUserMessage(ctx.toolResult, { role: "user", content: finalContent }, "task-ops");
198
+ wm.reminderCount++;
199
+ const maxNotified = newOps.reduce((m, op) => Math.max(m, op.id), 0);
200
+ wm.lastNotifiedOpId = Math.max(wm.lastNotifiedOpId, maxNotified);
201
+ logger.info(`[TaskOpWatermark] reminder injected: session=${sessionId.slice(0, 12)}..., tasks=${[...byTask.keys()].join(",")}, newOps=${newOps.length}, reminderCount=${wm.reminderCount}`);
202
+ }
203
+ isSelf(op, sessionId) {
204
+ return op.sessionId === sessionId || op.sessionId === UNKNOWN_SESSION;
205
+ }
206
+ async resolveTaskIds(tc, sessionId) {
207
+ if (typeof tc.getTaskIdsBySessionId === "function") {
208
+ const ids = await tc.getTaskIdsBySessionId(sessionId);
209
+ return Array.isArray(ids) ? ids : [];
210
+ }
211
+ if (typeof tc.getTaskIdBySessionId === "function") {
212
+ const id = await tc.getTaskIdBySessionId(sessionId);
213
+ return typeof id === "number" ? [id] : [];
214
+ }
215
+ return [];
216
+ }
217
+ async listOps(tc, taskId) {
218
+ if (typeof tc.listOperationsFresh === "function") {
219
+ return await tc.listOperationsFresh({ taskId, limit: 50 });
220
+ }
221
+ if (typeof tc.invalidateOperationsCache === "function") {
222
+ tc.invalidateOperationsCache(taskId);
223
+ }
224
+ return await tc.listOperations({ taskId, limit: 50 });
225
+ }
226
+ }
227
+ async function safeGetTask(tc, taskId) {
228
+ try {
229
+ return await tc.getTask(taskId);
230
+ } catch {
231
+ return;
232
+ }
233
+ }
234
+ function createTaskOpWatermarkPlugin(config) {
235
+ const inst = new TaskOpWatermarkPlugin(config);
236
+ return {
237
+ name: inst.name,
238
+ version: inst.version,
239
+ description: inst.description,
240
+ hooks: [{ point: "agent:after.tool", priority: inst.priority }],
241
+ execute: async (ctx) => {
242
+ return inst.execute(ctx);
243
+ }
244
+ };
245
+ }
246
+
247
+ export { enqueueToAddFakeUserMessage, TASK_OP_WATERMARK_MAX_REMINDERS, TASK_OP_WATERMARK_MESSAGE_LIMIT, TASK_OP_WATERMARK_AUDIT_INTERVAL, TaskOpWatermarkPlugin, createTaskOpWatermarkPlugin };
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  LarkCardPatchNotifier,
3
3
  formatDurationMs
4
- } from "./roy-agent-core-nmx9934r.js";
4
+ } from "./roy-agent-core-w0qjeq2v.js";
5
5
  import {
6
6
  WorkflowHookPoints,
7
7
  init_workflow_hook_points
@@ -1,12 +1,8 @@
1
- // src/env/task/tools/completion-copy.ts
2
- var TASK_COMPLETE_ITEM_BY_ITEM = "按子项逐条验收";
3
- var TASK_COMPLETION_HINT_ANCHOR = "TASK_COMPLETION_HINT";
4
- var TASK_COMPLETE_VERIFY_DUTY = "核实义务";
5
- var TASK_COMPLETE_ACCEPTANCE = {
6
- itemByItem: TASK_COMPLETE_ITEM_BY_ITEM,
7
- hintAnchor: TASK_COMPLETION_HINT_ANCHOR,
8
- verifyDuty: TASK_COMPLETE_VERIFY_DUTY
9
- };
1
+ import {
2
+ TASK_COMPLETE_ITEM_BY_ITEM,
3
+ TASK_COMPLETE_VERIFY_DUTY,
4
+ TASK_COMPLETION_HINT_ANCHOR
5
+ } from "./roy-agent-core-4rtg69cn.js";
10
6
 
11
7
  // src/env/task/prompt/task-discipline.ts
12
8
  var TASK_DISCIPLINE_LIFECYCLE = `### Task Lifecycle (7 steps, 0-6)
@@ -59,12 +55,15 @@ var TASK_DISCIPLINE_LIFECYCLE = `### Task Lifecycle (7 steps, 0-6)
59
55
  - Update progress (0-100%) as the work advances
60
56
  - Update status: todo → active
61
57
  - Record \`current_status\` with human-readable progress
62
- - ⚠️ **不要**把 status 直接置为 \`completed\` —— 终态由验收方落(见步骤 6)
58
+ - ⚠️ **不要**把 status 直接置为 \`completed\` 或 \`to_validate\` ——
59
+ 终态由验收方落、待验证由 \`task_pre_complete\` 提交(见步骤 6)
63
60
 
64
61
  5. RECORD — operation records (see Operation Discipline below)
65
62
 
66
- 6. COMPLETE — 收尾分两种角色(Task #5007,验收权分离)
67
- - **执行方(roy sub-agent)**:进度达到你判断的 100% ⇒ \`task_update(progress=100)\`
63
+ 6. COMPLETE — 收尾分两种角色(Task #5007/#5073,验收权分离)
64
+ - **执行方(roy sub-agent)**:进度达到你判断的 100% ⇒
65
+ \`task_pre_complete\` **提交待验证**(\`deliverable_storage_path\`
66
+ 传交付物地址;status → to_validate,并触发一次 goal 检查自查)
68
67
  + 最后一条 \`task_operation_create\` ⇒ **结束并返回结果**。
69
68
  ❌ **不要调用 \`task_complete\`**(你无此权限)。
70
69
  - **验收方(default agent / 接待员)**:\`task_complete\` after item-by-item verification
@@ -163,8 +162,8 @@ Before calling \`task_complete\`, you MUST:
163
162
  (b) 确认已完成 → **用你手上有权限的写工具**落一条 \`decision\` 类操作记录
164
163
  (\`task_operation_create\`,milestone_type=decision,milestone_title=<一行结论>)
165
164
  说明子项实际已达成(说明理由)。若你没有该工具的权限(验收方场景),
166
- 就通过你的**委托工具**让 sub-agent 代写这条记录,或在 \`task_complete\`
167
- 的 \`deliverable_storage_path\` / 最终汇报里明确写出核实结论与证据。
165
+ 就通过你的**委托工具**让 sub-agent 代写这条记录,或在最终汇报里
166
+ 明确写出核实结论与证据。
168
167
  Never ignore it (不允许无视).
169
168
  ⚠️ **核实义务的两条路径都依赖写权限**(\`task_update\` / \`task_operation_create\`)。
170
169
  若这些工具对你**不可用**(如验收方不持有执行类工具),必须**改用委托**把动作
@@ -175,22 +174,13 @@ Before calling \`task_complete\`, you MUST:
175
174
  (milestone_type=decision) — the reason **must** land in the
176
175
  operation record (必须落到操作记录里), not merely a line in
177
176
  your final report (不能只在报告里带一句).
178
- 3. **Provide \`deliverable_storage_path\`** parameter with the deliverable's address.
179
- It may name **multiple paths** (multiple deliverables), space-separated
180
- (\`/tmp/report.md /tmp/diff.patch\`) — the judge splits on whitespace as
181
- well as \`,\` \`;\` \`、\` and \` + \`. A local path may also be written in its
182
- \`file://\` form (\`file:///tmp/report.md\`) — the scheme is stripped and the
183
- path kept. Declare **every** artefact you produced: a path left out reads
184
- as "not produced".
185
- - Feishu doc URL (e.g., https://my.feishu.cn/docx/abc)
186
- - Local file path (e.g., /tmp/report.md)
187
- - Git branch or worktree path (e.g., feature/my-feature)
188
- - GitHub commit URL or PR URL
189
- - Cloud drive URL (e.g., https://my.feishu.cn/file/abc)
190
- - Or any other locator that points to where the work product lives
191
-
192
- The \`deliverable_storage_path\` will be persisted to the task's
193
- \`context.deliverable_storage_path\` for future reference and traceability.`;
177
+ 3. Deliverables were submitted by the executor via \`task_pre_complete\`
178
+ (Task #5073 two-phase acceptance): they live in
179
+ \`context.deliverable_storage_path\` and the operation trail
180
+ (\`Deliverable: <path>\` lines). Cross-check them in step 2.
181
+ \`task_complete\` takes **no** deliverable parameter — if the
182
+ deliverables are missing from the record, re-delegate instead of
183
+ asking for a path here.`;
194
184
  var TASK_DISCIPLINE_PROMPT = [
195
185
  TASK_DISCIPLINE_LIFECYCLE,
196
186
  TASK_DISCIPLINE_OPERATION,
@@ -200,4 +190,4 @@ var TASK_DISCIPLINE_PROMPT = [
200
190
 
201
191
  `);
202
192
 
203
- export { TASK_COMPLETE_ITEM_BY_ITEM, TASK_COMPLETION_HINT_ANCHOR, TASK_COMPLETE_VERIFY_DUTY, TASK_COMPLETE_ACCEPTANCE, TASK_DISCIPLINE_LIFECYCLE, TASK_DISCIPLINE_OPERATION, TASK_DISCIPLINE_GOALS, TASK_DISCIPLINE_COMPLETE, TASK_DISCIPLINE_PROMPT };
193
+ export { TASK_DISCIPLINE_LIFECYCLE, TASK_DISCIPLINE_OPERATION, TASK_DISCIPLINE_GOALS, TASK_DISCIPLINE_COMPLETE, TASK_DISCIPLINE_PROMPT };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  init_sqlite_task_store
3
- } from "./roy-agent-core-abqhkbn3.js";
3
+ } from "./roy-agent-core-ezvpreqz.js";
4
4
 
5
5
  // src/env/task/storage/index.ts
6
6
  init_sqlite_task_store();
@@ -9,7 +9,7 @@ import {
9
9
  import {
10
10
  BackgroundTaskManager,
11
11
  createDelegateTool
12
- } from "./roy-agent-core-jg65qnwa.js";
12
+ } from "./roy-agent-core-aa2j7bk9.js";
13
13
  import {
14
14
  TaskEntityEventTypes
15
15
  } from "./roy-agent-core-8gxth0eh.js";
@@ -22,10 +22,11 @@ import {
22
22
  deleteTaskTool,
23
23
  getTaskTool,
24
24
  listTasksTool,
25
+ preCompleteTaskTool,
25
26
  searchTasksTool,
26
27
  treeTasksTool,
27
28
  updateTaskTool
28
- } from "./roy-agent-core-hnyk1by7.js";
29
+ } from "./roy-agent-core-vffccgf6.js";
29
30
  import {
30
31
  createOperationTool,
31
32
  deleteOperationTool,
@@ -36,10 +37,20 @@ import {
36
37
  import {
37
38
  SQLiteTaskStore,
38
39
  getDefaultTaskDbPath
39
- } from "./roy-agent-core-abqhkbn3.js";
40
+ } from "./roy-agent-core-ezvpreqz.js";
40
41
  import {
41
42
  TaskHookPoints
42
43
  } from "./roy-agent-core-1pg0fepg.js";
44
+ import {
45
+ TASK_ARCHIVED_STATUS_WORD,
46
+ TASK_CANCELLED_STATUS_WORD,
47
+ TASK_COMPLETED_STATUS_WORD,
48
+ TASK_DELIVERABLE_RECORDED,
49
+ TASK_REOPENED_STATUS_WORD,
50
+ TASK_TERMINAL_STATUS_WORDS,
51
+ TASK_TO_VALIDATE_STATUS_WORD,
52
+ TERMINAL_NON_ACCEPTABLE_STATUSES
53
+ } from "./roy-agent-core-4rtg69cn.js";
43
54
  import {
44
55
  envKeyToConfigKey
45
56
  } from "./roy-agent-core-7hh0brvs.js";
@@ -87,16 +98,6 @@ var TASK_CONFIG_REGISTRATION = {
87
98
 
88
99
  // src/env/task/task-component.ts
89
100
  init_global_hook_manager();
90
-
91
- // src/env/task/hooks/contexts.ts
92
- var TASK_COMPLETE_TERMINAL_STATUSES = [
93
- "completed",
94
- "failed",
95
- "cancelled",
96
- "timeout"
97
- ];
98
-
99
- // src/env/task/task-component.ts
100
101
  init_env_context();
101
102
 
102
103
  // src/env/task/tag-service.ts
@@ -187,6 +188,17 @@ class DefaultTagService {
187
188
  // src/env/task/task-component.ts
188
189
  init_decorator();
189
190
  var logger2 = createLogger("task");
191
+ function isTerminalTaskStatus(status) {
192
+ return status === "completed" || status === "cancelled" || status === "archived";
193
+ }
194
+ function looksLikeSystemTerminalWording(currentStatus) {
195
+ if (!currentStatus)
196
+ return false;
197
+ const trimmed = currentStatus.trim();
198
+ if (!trimmed)
199
+ return false;
200
+ return TASK_TERMINAL_STATUS_WORDS.some((word) => trimmed.startsWith(word));
201
+ }
190
202
  var DELIVERABLE_OPERATION_PREFIX = "Deliverable: ";
191
203
  function formatDeliverableForOperation(deliverableStoragePath) {
192
204
  if (!deliverableStoragePath)
@@ -391,7 +403,8 @@ class TaskComponent extends BaseComponent {
391
403
  updateTaskTool(this),
392
404
  deleteTaskTool(this),
393
405
  completeTaskTool(this),
394
- cancelTaskTool(this)
406
+ cancelTaskTool(this),
407
+ preCompleteTaskTool(this)
395
408
  ];
396
409
  const operationToolsList = [
397
410
  createOperationTool(this),
@@ -458,6 +471,15 @@ class TaskComponent extends BaseComponent {
458
471
  async getTaskIdBySessionId(sessionId) {
459
472
  return this.store.getTaskIdBySessionId(sessionId);
460
473
  }
474
+ async getTaskIdsBySessionId(sessionId) {
475
+ const store = this.store;
476
+ if (typeof store.getTaskIdsBySessionId === "function") {
477
+ const ids = await store.getTaskIdsBySessionId(sessionId);
478
+ return Array.isArray(ids) ? ids : [];
479
+ }
480
+ const first = await this.store.getTaskIdBySessionId(sessionId);
481
+ return typeof first === "number" ? [first] : [];
482
+ }
461
483
  async getTaskWithOperations(id) {
462
484
  const task = await this.store.getTask(id);
463
485
  if (!task)
@@ -465,6 +487,31 @@ class TaskComponent extends BaseComponent {
465
487
  const operations = await this.store.listOperations({ taskId: id });
466
488
  return { ...task, operations, task };
467
489
  }
490
+ withTerminalCurrentStatus(options, previousTask) {
491
+ const status = options.status;
492
+ if (!status)
493
+ return options;
494
+ if (options.current_status !== undefined)
495
+ return options;
496
+ let terminalWord;
497
+ switch (status) {
498
+ case "completed":
499
+ terminalWord = TASK_COMPLETED_STATUS_WORD;
500
+ break;
501
+ case "cancelled":
502
+ terminalWord = TASK_CANCELLED_STATUS_WORD;
503
+ break;
504
+ case "archived":
505
+ terminalWord = TASK_ARCHIVED_STATUS_WORD;
506
+ break;
507
+ default:
508
+ if (previousTask && isTerminalTaskStatus(previousTask.status) && looksLikeSystemTerminalWording(previousTask.current_status)) {
509
+ return { ...options, current_status: TASK_REOPENED_STATUS_WORD };
510
+ }
511
+ return options;
512
+ }
513
+ return { ...options, current_status: terminalWord };
514
+ }
468
515
  async updateTask(id, options) {
469
516
  const updateCtx = {
470
517
  id,
@@ -472,7 +519,13 @@ class TaskComponent extends BaseComponent {
472
519
  tagService: this.tagService
473
520
  };
474
521
  await this.executeHook(TaskHookPoints.BEFORE_UPDATE, updateCtx);
475
- const task = await this.store.updateTask(id, updateCtx.options);
522
+ const requestedStatus = updateCtx.options.status;
523
+ let previousTask;
524
+ if (requestedStatus && !isTerminalTaskStatus(requestedStatus)) {
525
+ previousTask = await this.store.getTask(id);
526
+ }
527
+ const effectiveOptions = this.withTerminalCurrentStatus(updateCtx.options, previousTask);
528
+ const task = await this.store.updateTask(id, effectiveOptions);
476
529
  if (task) {
477
530
  const resultCtx = {
478
531
  task,
@@ -482,18 +535,6 @@ class TaskComponent extends BaseComponent {
482
535
  await this.executeHook(TaskHookPoints.AFTER_UPDATE, resultCtx);
483
536
  this.publishEvent(TaskEntityEventTypes.UPDATED, { task, changes: options, sessionId: "" }, "");
484
537
  logger2.info(`[TaskComponent] Task updated: ${id}`);
485
- if (options.status && TASK_COMPLETE_TERMINAL_STATUSES.includes(options.status)) {
486
- const env = getEnvContextOrEmpty();
487
- const completeCtx = {
488
- task,
489
- changes: options,
490
- terminalStatus: options.status,
491
- sessionId: env.sessionId ?? "",
492
- tagService: this.tagService
493
- };
494
- await this.executeHook(TaskHookPoints.AFTER_COMPLETE, completeCtx);
495
- logger2.info(`[TaskComponent] Task completed (AFTER_COMPLETE fired): ${id} -> ${options.status}`);
496
- }
497
538
  }
498
539
  return task;
499
540
  }
@@ -526,9 +567,13 @@ class TaskComponent extends BaseComponent {
526
567
  if (existing && existing.type === "cycle") {
527
568
  throw new Error(`Cannot complete cycle task: #${id} - ${existing.title}. Cycle tasks are not meant to be completed.`);
528
569
  }
570
+ if (existing && TERMINAL_NON_ACCEPTABLE_STATUSES.includes(existing.status)) {
571
+ throw new Error(`Cannot complete task: #${id} - ${existing.title}. ` + `It is already in terminal status "${existing.status}". ` + `Reopen it first via task_update(status="active") if acceptance should proceed.`);
572
+ }
529
573
  const updateOptions = {
530
574
  status: "completed",
531
- progress: 100
575
+ progress: 100,
576
+ current_status: deliverableStoragePath ? `${TASK_COMPLETED_STATUS_WORD}(${TASK_DELIVERABLE_RECORDED}:${deliverableStoragePath})` : TASK_COMPLETED_STATUS_WORD
532
577
  };
533
578
  if (deliverableStoragePath) {
534
579
  let existingContext = {};
@@ -562,6 +607,59 @@ class TaskComponent extends BaseComponent {
562
607
  async updateTaskContext(id, context) {
563
608
  return this.store.updateTask(id, { context });
564
609
  }
610
+ async preCompleteTask(id, sessionId, deliverableStoragePath, progressSummary) {
611
+ const existing = await this.store.getTask(id);
612
+ if (existing && existing.type === "cycle") {
613
+ throw new Error(`Cannot pre-complete cycle task: #${id} - ${existing.title}. Cycle tasks are not meant to be completed or submitted for acceptance.`);
614
+ }
615
+ const toValidateWording = progressSummary ? `${TASK_TO_VALIDATE_STATUS_WORD}:${progressSummary}` : TASK_TO_VALIDATE_STATUS_WORD;
616
+ const updateOptions = {
617
+ status: "to_validate",
618
+ current_status: toValidateWording
619
+ };
620
+ if (deliverableStoragePath) {
621
+ let existingContext = {};
622
+ if (existing?.context) {
623
+ try {
624
+ const parsed = JSON.parse(existing.context);
625
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
626
+ existingContext = parsed;
627
+ }
628
+ } catch {
629
+ logger2.warn(`[TaskComponent] Task #${id} context is not valid JSON, ignoring when merging deliverable`);
630
+ }
631
+ }
632
+ existingContext.deliverable_storage_path = deliverableStoragePath;
633
+ updateOptions.context = JSON.stringify(existingContext);
634
+ }
635
+ const task = await this.updateTask(id, updateOptions);
636
+ if (task) {
637
+ const env = getEnvContextOrEmpty();
638
+ const completeCtx = {
639
+ task,
640
+ changes: updateOptions,
641
+ terminalStatus: "to_validate",
642
+ sessionId: env.sessionId ?? sessionId,
643
+ tagService: this.tagService
644
+ };
645
+ await this.executeHook(TaskHookPoints.AFTER_COMPLETE, completeCtx);
646
+ const descParts = [];
647
+ const deliverableLine = formatDeliverableForOperation(deliverableStoragePath);
648
+ if (deliverableLine)
649
+ descParts.push(deliverableLine);
650
+ if (progressSummary)
651
+ descParts.push(progressSummary);
652
+ await this.createOperation({
653
+ taskId: id,
654
+ sessionId,
655
+ milestoneType: "milestone",
656
+ milestoneTitle: `${TASK_TO_VALIDATE_STATUS_WORD}(to_validate):Task #${id} submitted for acceptance`,
657
+ milestoneDescription: descParts.join(" / ") || TASK_TO_VALIDATE_STATUS_WORD
658
+ });
659
+ logger2.info(`[TaskComponent] Task submitted for acceptance (to_validate): ${id}, deliverable=${deliverableStoragePath || "(none)"}`);
660
+ }
661
+ return task;
662
+ }
565
663
  async createOperation(options) {
566
664
  const createCtx = {
567
665
  options: { ...options }
@@ -587,6 +685,16 @@ class TaskComponent extends BaseComponent {
587
685
  async listOperations(options) {
588
686
  return this.store.listOperations(options);
589
687
  }
688
+ async listOperationsFresh(options) {
689
+ const store = this.store;
690
+ if (typeof store.listOperationsFresh === "function") {
691
+ return await store.listOperationsFresh(options);
692
+ }
693
+ if (typeof store.invalidateOperationsCache === "function") {
694
+ store.invalidateOperationsCache(options.taskId);
695
+ }
696
+ return await this.store.listOperations(options);
697
+ }
590
698
  getTagService() {
591
699
  return this.tagService;
592
700
  }
@@ -630,12 +738,15 @@ __legacyDecorateClassTS([
630
738
  __legacyDecorateClassTS([
631
739
  TracedAs("task.component.completeTask", { recordParams: true, recordResult: true, log: true })
632
740
  ], TaskComponent.prototype, "completeTask", null);
741
+ __legacyDecorateClassTS([
742
+ TracedAs("task.component.preCompleteTask", { recordParams: true, recordResult: true, log: true })
743
+ ], TaskComponent.prototype, "preCompleteTask", null);
633
744
  __legacyDecorateClassTS([
634
745
  TracedAs("task.component.createOperation", { recordParams: true, recordResult: true, log: true })
635
746
  ], TaskComponent.prototype, "createOperation", null);
636
747
  // src/env/task/types.ts
637
748
  import { z } from "zod";
638
- var TaskStatusEnum = z.enum(["todo", "active", "completed", "paused", "cancelled", "archived"]);
749
+ var TaskStatusEnum = z.enum(["todo", "active", "to_validate", "completed", "paused", "cancelled", "archived"]);
639
750
  var TaskPriorityEnum = z.enum(["low", "medium", "high"]);
640
751
  var TaskTypeEnum = z.enum(["normal", "cycle", "longterm"]);
641
752
  var ActionTypeEnum = z.enum([