@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.
- package/dist/env/agent/agent-component.js +2 -2
- package/dist/env/agent/index.js +2 -2
- package/dist/env/event-source/event-source-handlers.js +9 -8
- package/dist/env/event-source/index.js +10 -9
- package/dist/env/index.js +16 -15
- package/dist/env/prompt/index.js +4 -3
- package/dist/env/prompt/prompts-index.d.ts +11 -9
- package/dist/env/task/backend/index.js +45 -1
- package/dist/env/task/backend/task-store-backend.d.ts +12 -0
- package/dist/env/task/backend/types.d.ts +19 -0
- package/dist/env/task/delegate/delegate-tool.js +4 -3
- package/dist/env/task/delegate/index.js +4 -3
- package/dist/env/task/hooks/contexts.d.ts +12 -1
- package/dist/env/task/hooks/task-hook-points.d.ts +8 -3
- package/dist/env/task/index.d.ts +1 -0
- package/dist/env/task/index.js +17 -7
- package/dist/env/task/mcp/index.js +6 -2
- package/dist/env/task/mcp/task-mcp-server.d.ts +1 -1
- package/dist/env/task/plugins/fake-user-message-helper.d.ts +10 -2
- package/dist/env/task/plugins/index.d.ts +1 -0
- package/dist/env/task/plugins/index.js +18 -6
- package/dist/env/task/plugins/lark-card-patch-notifier.js +1 -1
- package/dist/env/task/plugins/task-op-watermark-plugin.d.ts +112 -0
- package/dist/env/task/prompt/index.js +2 -1
- package/dist/env/task/prompt/task-discipline.d.ts +1 -1
- package/dist/env/task/storage/index.js +2 -2
- package/dist/env/task/storage/sqlite-task-store.d.ts +54 -0
- package/dist/env/task/task-component.d.ts +95 -0
- package/dist/env/task/tools/cancel-tool.d.ts +12 -4
- package/dist/env/task/tools/completion-copy.d.ts +92 -0
- package/dist/env/task/tools/index.d.ts +1 -0
- package/dist/env/task/tools/index.js +5 -2
- package/dist/env/task/tools/operation/operation-types.d.ts +13 -13
- package/dist/env/task/tools/pre-complete-tool.d.ts +32 -0
- package/dist/env/task/tools/task-types.d.ts +49 -17
- package/dist/env/task/types.d.ts +13 -1
- package/dist/env/workflow/plugins/index.js +2 -2
- package/dist/env/workflow/types/event.d.ts +4 -4
- package/dist/index.d.ts +1 -1
- package/dist/index.js +26 -16
- package/dist/shared/@ai-setting/{roy-agent-core-n9q1encx.js → roy-agent-core-0v27y99n.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-60jbrfye.js → roy-agent-core-3gpygqcx.js} +30 -6
- package/dist/shared/@ai-setting/roy-agent-core-4rtg69cn.js +26 -0
- package/dist/shared/@ai-setting/{roy-agent-core-djpq6yb0.js → roy-agent-core-9vhq7fvv.js} +6 -2
- package/dist/shared/@ai-setting/{roy-agent-core-3e6gg17d.js → roy-agent-core-9vxh6f8h.js} +169 -30
- package/dist/shared/@ai-setting/{roy-agent-core-jg65qnwa.js → roy-agent-core-aa2j7bk9.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-29b6hc39.js → roy-agent-core-cmprygrp.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-snvh4s2p.js → roy-agent-core-ew6qb5yx.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-abqhkbn3.js → roy-agent-core-ezvpreqz.js} +23 -0
- package/dist/shared/@ai-setting/roy-agent-core-h21gngxf.js +247 -0
- package/dist/shared/@ai-setting/{roy-agent-core-p3zv1mx5.js → roy-agent-core-hd3n57zs.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-11shp00p.js → roy-agent-core-m46fe3x4.js} +21 -31
- package/dist/shared/@ai-setting/{roy-agent-core-ntq0gz7q.js → roy-agent-core-q813vafd.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-4gr3k7qg.js → roy-agent-core-rhtdspx6.js} +140 -29
- package/dist/shared/@ai-setting/{roy-agent-core-y7x2qzwc.js → roy-agent-core-rsn44n9y.js} +10 -6
- package/dist/shared/@ai-setting/{roy-agent-core-hnyk1by7.js → roy-agent-core-vffccgf6.js} +152 -33
- package/dist/shared/@ai-setting/{roy-agent-core-nmx9934r.js → roy-agent-core-w0qjeq2v.js} +1 -1
- package/package.json +1 -1
- 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,12 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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\`
|
|
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% ⇒
|
|
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
|
|
167
|
-
|
|
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.
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
\`
|
|
183
|
-
|
|
184
|
-
|
|
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 {
|
|
193
|
+
export { TASK_DISCIPLINE_LIFECYCLE, TASK_DISCIPLINE_OPERATION, TASK_DISCIPLINE_GOALS, TASK_DISCIPLINE_COMPLETE, TASK_DISCIPLINE_PROMPT };
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
import {
|
|
10
10
|
BackgroundTaskManager,
|
|
11
11
|
createDelegateTool
|
|
12
|
-
} from "./roy-agent-core-
|
|
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-
|
|
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-
|
|
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
|
|
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([
|