@ai-setting/roy-agent-core 1.7.18 → 1.7.31
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/event-source/index.js +2 -2
- package/dist/env/index.js +4 -4
- package/dist/env/task/index.js +1 -1
- package/dist/env/task/plugins/index.js +5 -1
- package/dist/index.js +4 -4
- package/dist/shared/@ai-setting/roy-agent-core-06wbc8km.js +1507 -0
- package/dist/shared/@ai-setting/{roy-agent-core-62mfe44h.js → roy-agent-core-1ht06trt.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-pp140n9r.js → roy-agent-core-m09a4xwh.js} +2 -1
- package/dist/shared/@ai-setting/{roy-agent-core-91rzfw8d.js → roy-agent-core-vqexb6am.js} +112 -29
- package/package.json +1 -1
- package/dist/shared/@ai-setting/roy-agent-core-h4h6n9vk.js +0 -801
|
@@ -1,801 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
TaskHookPoints
|
|
3
|
-
} from "./roy-agent-core-1pg0fepg.js";
|
|
4
|
-
import {
|
|
5
|
-
WORKFLOW_EXTRACT_PROMPT
|
|
6
|
-
} from "./roy-agent-core-skqt40yy.js";
|
|
7
|
-
import {
|
|
8
|
-
validateWorkflowDefinition
|
|
9
|
-
} from "./roy-agent-core-1df0grva.js";
|
|
10
|
-
import {
|
|
11
|
-
enqueueToAddFakeUserMessage
|
|
12
|
-
} from "./roy-agent-core-jbc1t62x.js";
|
|
13
|
-
import {
|
|
14
|
-
BasePlugin
|
|
15
|
-
} from "./roy-agent-core-az13yzmc.js";
|
|
16
|
-
import {
|
|
17
|
-
init_context
|
|
18
|
-
} from "./roy-agent-core-m2pxg5d8.js";
|
|
19
|
-
import {
|
|
20
|
-
getEnvContextOrEmpty,
|
|
21
|
-
getEventFromEnvContext,
|
|
22
|
-
getLarkMessageId,
|
|
23
|
-
isLarkEvent
|
|
24
|
-
} from "./roy-agent-core-7p6rny5m.js";
|
|
25
|
-
import {
|
|
26
|
-
TracedAs,
|
|
27
|
-
init_decorator,
|
|
28
|
-
wrapFunction
|
|
29
|
-
} from "./roy-agent-core-38rtrpk3.js";
|
|
30
|
-
import {
|
|
31
|
-
createLogger,
|
|
32
|
-
init_logger
|
|
33
|
-
} from "./roy-agent-core-e6dahpp2.js";
|
|
34
|
-
import {
|
|
35
|
-
__legacyDecorateClassTS,
|
|
36
|
-
__require
|
|
37
|
-
} from "./roy-agent-core-fs0mn2jk.js";
|
|
38
|
-
|
|
39
|
-
// src/env/task/plugins/task-tag-plugin.ts
|
|
40
|
-
init_logger();
|
|
41
|
-
|
|
42
|
-
// src/env/task/plugins/workflow-extractor-agent.ts
|
|
43
|
-
var WORKFLOW_EXTRACTOR_PROMPT = WORKFLOW_EXTRACT_PROMPT;
|
|
44
|
-
function formatExtractorInput(params) {
|
|
45
|
-
const lines = [];
|
|
46
|
-
lines.push("# 任务信息");
|
|
47
|
-
lines.push(`- 标题: ${params.task.title}`);
|
|
48
|
-
if (params.task.description) {
|
|
49
|
-
lines.push(`- 描述: ${params.task.description}`);
|
|
50
|
-
}
|
|
51
|
-
if (params.task.goals_and_expected_deliverables) {
|
|
52
|
-
lines.push(`- 目标: ${params.task.goals_and_expected_deliverables}`);
|
|
53
|
-
}
|
|
54
|
-
lines.push("");
|
|
55
|
-
if (params.operations.length > 0) {
|
|
56
|
-
lines.push("# 操作记录");
|
|
57
|
-
for (const op of params.operations) {
|
|
58
|
-
lines.push(`- [${op.actionType}] ${op.actionTitle}`);
|
|
59
|
-
if (op.actionDescription) {
|
|
60
|
-
lines.push(` ${op.actionDescription.slice(0, 200)}`);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
lines.push("");
|
|
64
|
-
}
|
|
65
|
-
lines.push("# Session 消息历史");
|
|
66
|
-
lines.push("(以下是相关的对话历史,请从中提取 Workflow)");
|
|
67
|
-
for (const msg of params.messages.slice(-50)) {
|
|
68
|
-
const content = typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content);
|
|
69
|
-
const truncated = content.slice(0, 500);
|
|
70
|
-
lines.push(`**${msg.role}**: ${truncated}${content.length > 500 ? "..." : ""}`);
|
|
71
|
-
}
|
|
72
|
-
lines.push("");
|
|
73
|
-
lines.push("## 任务");
|
|
74
|
-
lines.push("请分析以上信息,构建一个可复用的 Workflow。");
|
|
75
|
-
lines.push("使用迭代式流程:生成 YAML → validate 验证 → 修正错误 → 重复直到通过验证。");
|
|
76
|
-
return lines.join(`
|
|
77
|
-
`);
|
|
78
|
-
}
|
|
79
|
-
function formatExtractorInputFromDescription(params) {
|
|
80
|
-
const lines = [];
|
|
81
|
-
lines.push("# Workflow 描述");
|
|
82
|
-
lines.push(params.description);
|
|
83
|
-
lines.push("");
|
|
84
|
-
lines.push("## 任务");
|
|
85
|
-
lines.push("请根据以上描述,构建一个可复用的 Workflow。");
|
|
86
|
-
lines.push("使用迭代式流程:生成 YAML → validate 验证 → 修正错误 → 重复直到通过验证。");
|
|
87
|
-
return lines.join(`
|
|
88
|
-
`);
|
|
89
|
-
}
|
|
90
|
-
async function parseExtractorOutput(output) {
|
|
91
|
-
const yamlMatch = output.match(/```yaml\n?([\s\S]*?)\n?```/) || output.match(/```\n?([\s\S]*?)\n?```/) || output.match(/(\|[\s\S]*?)(?:\n\n|$)/);
|
|
92
|
-
const yamlContent = yamlMatch?.[1] || output;
|
|
93
|
-
try {
|
|
94
|
-
const YAML = await import("yaml");
|
|
95
|
-
const parsed = YAML.parse(yamlContent);
|
|
96
|
-
if (!parsed.name || !parsed.nodes || !Array.isArray(parsed.nodes)) {
|
|
97
|
-
return null;
|
|
98
|
-
}
|
|
99
|
-
if (parsed.nodes.length < 2) {
|
|
100
|
-
return null;
|
|
101
|
-
}
|
|
102
|
-
parsed.version = parsed.version || "1.0";
|
|
103
|
-
return parsed;
|
|
104
|
-
} catch {
|
|
105
|
-
return null;
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// src/env/task/plugins/task-tag-plugin.ts
|
|
110
|
-
var logger = createLogger("plugin:task-tag");
|
|
111
|
-
|
|
112
|
-
class TaskTagPlugin extends BasePlugin {
|
|
113
|
-
name = "task-tag";
|
|
114
|
-
version = "1.0.0";
|
|
115
|
-
description = "Inject similar task information into delegate prompt and extract workflow on task completion";
|
|
116
|
-
env;
|
|
117
|
-
taskComponent = null;
|
|
118
|
-
agentComponent = null;
|
|
119
|
-
tagService = null;
|
|
120
|
-
constructor() {
|
|
121
|
-
super();
|
|
122
|
-
}
|
|
123
|
-
init(env) {
|
|
124
|
-
this.env = env;
|
|
125
|
-
this.taskComponent = env.getComponent("task");
|
|
126
|
-
if (this.taskComponent && typeof this.taskComponent.getTagService === "function") {
|
|
127
|
-
this.tagService = this.taskComponent.getTagService();
|
|
128
|
-
}
|
|
129
|
-
this.agentComponent = env.getComponent("agent");
|
|
130
|
-
env.registerHook({
|
|
131
|
-
point: TaskHookPoints.DELEGATE_BEFORE,
|
|
132
|
-
priority: 20,
|
|
133
|
-
handler: this.onDelegateBefore.bind(this)
|
|
134
|
-
});
|
|
135
|
-
env.registerHook({
|
|
136
|
-
point: TaskHookPoints.OPERATION_AFTER_CREATE,
|
|
137
|
-
priority: 10,
|
|
138
|
-
handler: this.onOperationAfterCreate.bind(this)
|
|
139
|
-
});
|
|
140
|
-
logger.info("[TaskTagPlugin] Initialized");
|
|
141
|
-
}
|
|
142
|
-
dispose() {
|
|
143
|
-
this.env = null;
|
|
144
|
-
this.taskComponent = null;
|
|
145
|
-
this.agentComponent = null;
|
|
146
|
-
this.tagService = null;
|
|
147
|
-
logger.info("[TaskTagPlugin] Disposed");
|
|
148
|
-
}
|
|
149
|
-
async onDelegateBefore(ctx) {
|
|
150
|
-
const data = ctx.data;
|
|
151
|
-
const { taskId, prompt } = data;
|
|
152
|
-
if (!taskId || !this.taskComponent) {
|
|
153
|
-
return;
|
|
154
|
-
}
|
|
155
|
-
try {
|
|
156
|
-
const currentTask = await this.taskComponent.getTask(taskId);
|
|
157
|
-
if (!currentTask)
|
|
158
|
-
return;
|
|
159
|
-
const keywords = this.extractKeywords(currentTask);
|
|
160
|
-
if (keywords.length === 0)
|
|
161
|
-
return;
|
|
162
|
-
const similarTasks = await this.tagService?.findSimilarTasksByKeywords?.(keywords, {
|
|
163
|
-
limit: 5,
|
|
164
|
-
excludeTaskId: taskId
|
|
165
|
-
}) ?? [];
|
|
166
|
-
if (similarTasks.length === 0)
|
|
167
|
-
return;
|
|
168
|
-
const hint = this.formatSimilarTasks(similarTasks);
|
|
169
|
-
data.prompt = `${hint}
|
|
170
|
-
|
|
171
|
-
---
|
|
172
|
-
|
|
173
|
-
## 用户指令
|
|
174
|
-
|
|
175
|
-
${prompt}`;
|
|
176
|
-
logger.info(`[TaskTagPlugin] Injected ${similarTasks.length} similar tasks`);
|
|
177
|
-
} catch (error) {
|
|
178
|
-
logger.warn(`[TaskTagPlugin] Similar tasks injection failed: ${error}`);
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
extractKeywords(task) {
|
|
182
|
-
const keywords = [];
|
|
183
|
-
if (task.tags?.length) {
|
|
184
|
-
keywords.push(...task.tags);
|
|
185
|
-
}
|
|
186
|
-
if (task.title) {
|
|
187
|
-
const words = task.title.split(/[\s\-_,,。、#]+/).filter((w) => w.length > 2 && !/^\d+$/.test(w)).slice(0, 5);
|
|
188
|
-
keywords.push(...words);
|
|
189
|
-
}
|
|
190
|
-
return keywords;
|
|
191
|
-
}
|
|
192
|
-
formatSimilarTasks(tasks) {
|
|
193
|
-
const lines = [
|
|
194
|
-
"## \uD83D\uDD0D 类似任务参考",
|
|
195
|
-
"",
|
|
196
|
-
"_提示:可通过 `task_get <id>` 获取详情,`task_operation_list <id>` 获取操作记录_",
|
|
197
|
-
""
|
|
198
|
-
];
|
|
199
|
-
for (const task of tasks) {
|
|
200
|
-
lines.push(`### Task #${task.id}`);
|
|
201
|
-
lines.push(`**Title**: ${task.title}`);
|
|
202
|
-
if (task.description) {
|
|
203
|
-
const desc = task.description.slice(0, 100);
|
|
204
|
-
lines.push(`**Description**: ${desc}${task.description.length > 100 ? "..." : ""}`);
|
|
205
|
-
}
|
|
206
|
-
if (task.goals_and_expected_deliverables) {
|
|
207
|
-
const goal = task.goals_and_expected_deliverables.slice(0, 100);
|
|
208
|
-
lines.push(`**Goal**: ${goal}${task.goals_and_expected_deliverables.length > 100 ? "..." : ""}`);
|
|
209
|
-
}
|
|
210
|
-
lines.push("");
|
|
211
|
-
}
|
|
212
|
-
return lines.join(`
|
|
213
|
-
`);
|
|
214
|
-
}
|
|
215
|
-
async onOperationAfterCreate(ctx) {
|
|
216
|
-
const data = ctx.data;
|
|
217
|
-
const operation = data?.operation ?? data?.data?.operation;
|
|
218
|
-
if (!operation || operation?.actionType !== "completed")
|
|
219
|
-
return;
|
|
220
|
-
await this.extractWorkflow(operation.taskId, operation.sessionId);
|
|
221
|
-
}
|
|
222
|
-
async extractWorkflow(taskId, sessionId) {
|
|
223
|
-
try {
|
|
224
|
-
const task = await this.taskComponent?.getTask(taskId);
|
|
225
|
-
if (!task)
|
|
226
|
-
return;
|
|
227
|
-
const operations = await this.taskComponent?.listOperations({ taskId }) ?? [];
|
|
228
|
-
const allSessionIds = [...new Set([sessionId, ...operations.map((op) => op.sessionId).filter(Boolean)])];
|
|
229
|
-
const sessionComponent = this.env.getComponent("session");
|
|
230
|
-
const allMessages = [];
|
|
231
|
-
for (const sid of allSessionIds) {
|
|
232
|
-
try {
|
|
233
|
-
const ctx = await sessionComponent?.getContext(sid, { fullHistory: false, minUserMessages: 200 });
|
|
234
|
-
if (ctx?.messages)
|
|
235
|
-
allMessages.push(...ctx.messages);
|
|
236
|
-
} catch {}
|
|
237
|
-
}
|
|
238
|
-
if (!this.agentComponent)
|
|
239
|
-
return;
|
|
240
|
-
const input = formatExtractorInput({ task, operations, messages: allMessages });
|
|
241
|
-
const result = await this.agentComponent.run("workflow-extract", input);
|
|
242
|
-
const workflowDef = await parseExtractorOutput(result.finalText || "");
|
|
243
|
-
if (!workflowDef)
|
|
244
|
-
return;
|
|
245
|
-
const validation = this.validateWorkflowDefinition(workflowDef);
|
|
246
|
-
if (!validation.valid) {
|
|
247
|
-
logger.warn(`[TaskTagPlugin] Skipping workflow creation due to validation failure: ` + `${validation.errors.join("; ")}`);
|
|
248
|
-
await this.taskComponent?.createOperation({
|
|
249
|
-
taskId,
|
|
250
|
-
sessionId,
|
|
251
|
-
actionType: "workflow_extraction_failed",
|
|
252
|
-
actionTitle: `Workflow extraction failed validation: ${workflowDef.name}`,
|
|
253
|
-
actionDescription: validation.errors.join("; ")
|
|
254
|
-
});
|
|
255
|
-
return;
|
|
256
|
-
}
|
|
257
|
-
const workflowRepo = await this.getWorkflowRepository();
|
|
258
|
-
if (!workflowRepo)
|
|
259
|
-
return;
|
|
260
|
-
let version = workflowDef.version || "1.0";
|
|
261
|
-
const existingWf = workflowRepo.getByName(workflowDef.name);
|
|
262
|
-
if (existingWf) {
|
|
263
|
-
const parts = existingWf.version?.split(".") ?? ["1", "0"];
|
|
264
|
-
version = `${parts[0]}.${parseInt(parts[1] || "0") + 1}`;
|
|
265
|
-
}
|
|
266
|
-
const workflow = workflowRepo.create({
|
|
267
|
-
name: workflowDef.name,
|
|
268
|
-
version,
|
|
269
|
-
description: workflowDef.description,
|
|
270
|
-
definition: workflowDef,
|
|
271
|
-
config: workflowDef.config || {},
|
|
272
|
-
metadata: { author: "workflow-extract", created_at: new Date().toISOString() },
|
|
273
|
-
tags: [`task:${taskId}`]
|
|
274
|
-
});
|
|
275
|
-
await this.taskComponent?.createOperation({
|
|
276
|
-
taskId,
|
|
277
|
-
sessionId,
|
|
278
|
-
actionType: "workflow_extracted",
|
|
279
|
-
actionTitle: `Workflow extracted: ${workflow.name}`,
|
|
280
|
-
actionDescription: workflow.id
|
|
281
|
-
});
|
|
282
|
-
logger.info(`[TaskTagPlugin] Workflow extracted: ${workflow.id}`);
|
|
283
|
-
} catch (error) {
|
|
284
|
-
logger.warn(`[TaskTagPlugin] Workflow extraction failed: ${error}`);
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
validateWorkflowDefinition(workflowDef) {
|
|
288
|
-
return validateWorkflowDefinition(workflowDef);
|
|
289
|
-
}
|
|
290
|
-
_workflowRepo = null;
|
|
291
|
-
async getWorkflowRepository() {
|
|
292
|
-
const workflowComponent = this.env.getComponent("workflow");
|
|
293
|
-
if (workflowComponent?.workflowService?.workflowRepository) {
|
|
294
|
-
return workflowComponent.workflowService.workflowRepository;
|
|
295
|
-
}
|
|
296
|
-
if (!this._workflowRepo) {
|
|
297
|
-
try {
|
|
298
|
-
const sqlite = await import("./roy-agent-core-89zsa89z.js");
|
|
299
|
-
this.ensureDataDir();
|
|
300
|
-
const db = sqlite.getDatabase();
|
|
301
|
-
sqlite.initializeTables();
|
|
302
|
-
const { WorkflowRepository } = await import("./roy-agent-core-pz8jvvtn.js");
|
|
303
|
-
this._workflowRepo = new WorkflowRepository(db);
|
|
304
|
-
return this._workflowRepo;
|
|
305
|
-
} catch {
|
|
306
|
-
return null;
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
return this._workflowRepo;
|
|
310
|
-
}
|
|
311
|
-
ensureDataDir() {
|
|
312
|
-
const { join } = __require("path");
|
|
313
|
-
const dataDir = join(process.env.XDG_DATA_HOME || join(process.env.HOME || "", ".local/share"), "roy-agent");
|
|
314
|
-
__require("fs").mkdirSync(dataDir, { recursive: true });
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
// src/env/task/plugins/auto-task-plugin.ts
|
|
318
|
-
init_logger();
|
|
319
|
-
init_decorator();
|
|
320
|
-
var logger2 = createLogger("AutoTaskPlugin");
|
|
321
|
-
function createAutoTaskPlugin(config) {
|
|
322
|
-
const threshold = config?.threshold ?? 20;
|
|
323
|
-
const agentName = config?.agentName ?? "default";
|
|
324
|
-
let lastProcessedTraceId = "";
|
|
325
|
-
async function executeImpl(ctx) {
|
|
326
|
-
try {
|
|
327
|
-
logger2.info(`[AutoTaskPlugin:ENTER] agentName=${ctx.agent.name} iteration=${ctx.iteration} threshold=${threshold}`);
|
|
328
|
-
if (ctx.agent.name !== agentName)
|
|
329
|
-
return { continue: true };
|
|
330
|
-
if (ctx.iteration !== threshold)
|
|
331
|
-
return { continue: true };
|
|
332
|
-
const traceId = ctx.context?.traceId ?? ctx.context?.sessionId ?? "unknown";
|
|
333
|
-
if (traceId === lastProcessedTraceId) {
|
|
334
|
-
logger2.debug(`[AutoTaskPlugin] skip - traceId=${traceId.slice(0, 20)}... already processed`);
|
|
335
|
-
return { continue: true };
|
|
336
|
-
}
|
|
337
|
-
lastProcessedTraceId = traceId;
|
|
338
|
-
logger2.debug(`[AutoTaskPlugin] new query detected (traceId=${traceId.slice(0, 20)}...)`);
|
|
339
|
-
const reminderContent = `⚠️ **系统提醒**: 你已经调用了 ${ctx.iteration} 次工具,达到 iteration 阈值(默认 ${threshold})。
|
|
340
|
-
` + `当前请求较为复杂,建议通过 \`delegate_task\` 工具将整个任务委托给 \`roy\` sub-agent 处理。` + `注意,需要让roy为这个任务调用task create工具来创建任务进行跟踪,这个是必走程序哈。`;
|
|
341
|
-
if (!ctx.toolResult) {
|
|
342
|
-
logger2.warn(`[AutoTaskPlugin] toolResult missing, skip meta-protocol (iteration=${ctx.iteration})`);
|
|
343
|
-
return { continue: true };
|
|
344
|
-
}
|
|
345
|
-
enqueueToAddFakeUserMessage(ctx.toolResult, {
|
|
346
|
-
role: "user",
|
|
347
|
-
content: reminderContent
|
|
348
|
-
}, "delegate");
|
|
349
|
-
logger2.info(`[AutoTaskPlugin] user reminder meta-protocol set (session=${(ctx.context?.sessionId ?? "unknown").slice(0, 12)}..., iteration=${ctx.iteration})`);
|
|
350
|
-
return { continue: true };
|
|
351
|
-
} catch (err) {
|
|
352
|
-
logger2.error(`[AutoTaskPlugin] hook failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
353
|
-
return { continue: true };
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
const execute = wrapFunction(executeImpl, "plugin.auto-task.launch-agent", {
|
|
357
|
-
recordParams: false,
|
|
358
|
-
recordResult: true,
|
|
359
|
-
log: true
|
|
360
|
-
});
|
|
361
|
-
return {
|
|
362
|
-
name: "AutoTaskPlugin",
|
|
363
|
-
version: "5.2.0",
|
|
364
|
-
description: "ReAct iteration 达到阈值时通过共享 meta-protocol helper 注入 user 消息(与 MaxIterReminder 共用字段、确定性合并)",
|
|
365
|
-
hooks: [
|
|
366
|
-
{ point: "agent:after.tool", priority: 50 }
|
|
367
|
-
],
|
|
368
|
-
execute
|
|
369
|
-
};
|
|
370
|
-
}
|
|
371
|
-
// src/env/task/plugins/lark-cli-task-notify-plugin.ts
|
|
372
|
-
init_decorator();
|
|
373
|
-
init_logger();
|
|
374
|
-
init_context();
|
|
375
|
-
import { spawn } from "child_process";
|
|
376
|
-
var logger3 = createLogger("lark-cli:task-notify");
|
|
377
|
-
function isTaskNotifyDebugEnabled() {
|
|
378
|
-
return process.env.ROY_TASK_NOTIFY_DEBUG === "1" || process.env.ROY_TRACE_DEBUG === "1";
|
|
379
|
-
}
|
|
380
|
-
function extractLarkFields(env) {
|
|
381
|
-
const event = getEventFromEnvContext(env);
|
|
382
|
-
if (!isLarkEvent(event)) {
|
|
383
|
-
return { isLark: false };
|
|
384
|
-
}
|
|
385
|
-
return {
|
|
386
|
-
isLark: true,
|
|
387
|
-
source: event.source,
|
|
388
|
-
messageId: getLarkMessageId(event) ?? env.replyChannel?.messageId,
|
|
389
|
-
chatId: env.replyChannel?.chatId,
|
|
390
|
-
userId: env.replyChannel?.userId
|
|
391
|
-
};
|
|
392
|
-
}
|
|
393
|
-
var NotifyType;
|
|
394
|
-
((NotifyType2) => {
|
|
395
|
-
NotifyType2["TASK_CREATED"] = "task_created";
|
|
396
|
-
NotifyType2["TASK_COMPLETED"] = "task_completed";
|
|
397
|
-
NotifyType2["TASK_UPDATED"] = "task_updated";
|
|
398
|
-
NotifyType2["TASK_FAILED"] = "task_failed";
|
|
399
|
-
NotifyType2["OPERATION_CREATED"] = "operation_created";
|
|
400
|
-
})(NotifyType ||= {});
|
|
401
|
-
|
|
402
|
-
class LarkCliTaskNotifyHook {
|
|
403
|
-
name;
|
|
404
|
-
priority;
|
|
405
|
-
description;
|
|
406
|
-
pluginName;
|
|
407
|
-
sourceId;
|
|
408
|
-
config;
|
|
409
|
-
lastNotifyTimestamps = new Map;
|
|
410
|
-
constructor(config = {}) {
|
|
411
|
-
this.name = config.pluginName || "LarkCliTaskNotifyHook";
|
|
412
|
-
this.priority = config.priority ?? 5;
|
|
413
|
-
this.description = "LarkCli 任务通知 Hook";
|
|
414
|
-
this.pluginName = config.pluginName;
|
|
415
|
-
this.sourceId = config.sourceId;
|
|
416
|
-
this.config = {
|
|
417
|
-
enabled: true,
|
|
418
|
-
priority: 5,
|
|
419
|
-
notifyTypes: [
|
|
420
|
-
"task_created" /* TASK_CREATED */,
|
|
421
|
-
"task_updated" /* TASK_UPDATED */,
|
|
422
|
-
"task_completed" /* TASK_COMPLETED */,
|
|
423
|
-
"operation_created" /* OPERATION_CREATED */
|
|
424
|
-
],
|
|
425
|
-
...config
|
|
426
|
-
};
|
|
427
|
-
}
|
|
428
|
-
async execute(ctx) {
|
|
429
|
-
if (!this.config.enabled) {
|
|
430
|
-
return;
|
|
431
|
-
}
|
|
432
|
-
const env = getEnvContextOrEmpty();
|
|
433
|
-
if (this.pluginName && this.sourceId) {
|
|
434
|
-
const key = `${this.sourceId}:${this.pluginName}`;
|
|
435
|
-
if (env.pluginEnabled && env.pluginEnabled[key] !== true) {
|
|
436
|
-
logger3.debug(`Plugin not enabled for source: ${key}`);
|
|
437
|
-
return;
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
switch (ctx.hookPoint) {
|
|
441
|
-
case TaskHookPoints.AFTER_CREATE:
|
|
442
|
-
await this.onAfterCreate(ctx);
|
|
443
|
-
break;
|
|
444
|
-
case TaskHookPoints.AFTER_UPDATE:
|
|
445
|
-
await this.onAfterUpdate(ctx);
|
|
446
|
-
break;
|
|
447
|
-
case TaskHookPoints.OPERATION_AFTER_CREATE:
|
|
448
|
-
await this.onAfterOperationCreate(ctx);
|
|
449
|
-
break;
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
async onAfterCreate(ctx) {
|
|
453
|
-
const { task } = ctx.data;
|
|
454
|
-
if (!this.shouldNotify("task_created" /* TASK_CREATED */))
|
|
455
|
-
return;
|
|
456
|
-
await this.sendNotify({
|
|
457
|
-
type: "task_created" /* TASK_CREATED */,
|
|
458
|
-
title: `\uD83D\uDCDD 任务已创建`,
|
|
459
|
-
content: this.formatTaskCreated(task)
|
|
460
|
-
});
|
|
461
|
-
}
|
|
462
|
-
async onAfterUpdate(ctx) {
|
|
463
|
-
const data = ctx.data;
|
|
464
|
-
if (!data) {
|
|
465
|
-
return;
|
|
466
|
-
}
|
|
467
|
-
const { task, changes } = data;
|
|
468
|
-
if (!task) {
|
|
469
|
-
return;
|
|
470
|
-
}
|
|
471
|
-
let notifyType;
|
|
472
|
-
let notifyChanges = changes || {};
|
|
473
|
-
if (changes?.status === "completed" || changes?.progress === 100) {
|
|
474
|
-
notifyType = "task_completed" /* TASK_COMPLETED */;
|
|
475
|
-
} else if (changes?.status) {
|
|
476
|
-
notifyType = "task_updated" /* TASK_UPDATED */;
|
|
477
|
-
} else if (task.status === "completed" || task.progress === 100) {
|
|
478
|
-
notifyType = "task_completed" /* TASK_COMPLETED */;
|
|
479
|
-
notifyChanges = { status: task.status, progress: task.progress };
|
|
480
|
-
} else if (changes && Object.keys(changes).length > 0) {
|
|
481
|
-
notifyType = "task_updated" /* TASK_UPDATED */;
|
|
482
|
-
} else {
|
|
483
|
-
return;
|
|
484
|
-
}
|
|
485
|
-
if (!this.shouldNotify(notifyType)) {
|
|
486
|
-
return;
|
|
487
|
-
}
|
|
488
|
-
await this.sendNotify({
|
|
489
|
-
type: notifyType,
|
|
490
|
-
title: this.getNotifyTitle(notifyType),
|
|
491
|
-
content: this.formatTaskUpdate(task, notifyChanges),
|
|
492
|
-
_hookContext: ctx
|
|
493
|
-
});
|
|
494
|
-
}
|
|
495
|
-
async onAfterOperationCreate(ctx) {
|
|
496
|
-
const { operation } = ctx.data;
|
|
497
|
-
if (!operation) {
|
|
498
|
-
logger3.debug("onAfterOperationCreate: operation is missing, skip");
|
|
499
|
-
return;
|
|
500
|
-
}
|
|
501
|
-
if (!this.shouldNotify("operation_created" /* OPERATION_CREATED */)) {
|
|
502
|
-
return;
|
|
503
|
-
}
|
|
504
|
-
if (this.shouldThrottleOperation(operation)) {
|
|
505
|
-
return;
|
|
506
|
-
}
|
|
507
|
-
await this.sendNotify({
|
|
508
|
-
type: "operation_created" /* OPERATION_CREATED */,
|
|
509
|
-
title: this.getNotifyTitle("operation_created" /* OPERATION_CREATED */),
|
|
510
|
-
content: await this.formatOperationCreated(operation),
|
|
511
|
-
_hookContext: ctx
|
|
512
|
-
});
|
|
513
|
-
}
|
|
514
|
-
async sendNotify(options) {
|
|
515
|
-
const { type, title, content, _hookContext } = options;
|
|
516
|
-
const env = getEnvContextOrEmpty();
|
|
517
|
-
let replyChannel = env.replyChannel;
|
|
518
|
-
const envContextFromMetadata = env.metadata?.envContext;
|
|
519
|
-
if (!replyChannel && envContextFromMetadata) {
|
|
520
|
-
replyChannel = envContextFromMetadata.replyChannel;
|
|
521
|
-
}
|
|
522
|
-
if (!replyChannel && _hookContext?.metadata?.envContext) {
|
|
523
|
-
replyChannel = _hookContext.metadata.envContext.replyChannel;
|
|
524
|
-
}
|
|
525
|
-
const larkFields = extractLarkFields(env);
|
|
526
|
-
if (!larkFields.isLark && env.event) {
|
|
527
|
-
logger3.debug(`Skipping notification for non-lark event source: ${larkFields.source}`);
|
|
528
|
-
return;
|
|
529
|
-
}
|
|
530
|
-
const chatId = replyChannel?.chatId;
|
|
531
|
-
const messageId = replyChannel?.messageId;
|
|
532
|
-
if (!chatId && !messageId) {
|
|
533
|
-
logger3.debug(`No replyChannel available for ${type}`);
|
|
534
|
-
return;
|
|
535
|
-
}
|
|
536
|
-
const message = `${title}
|
|
537
|
-
|
|
538
|
-
${content}`;
|
|
539
|
-
try {
|
|
540
|
-
if (isTaskNotifyDebugEnabled()) {
|
|
541
|
-
logger3.debug(`Sending message - messageId: ${messageId}`);
|
|
542
|
-
}
|
|
543
|
-
const profile = replyChannel?.profile;
|
|
544
|
-
await this.sendLarkMessage({
|
|
545
|
-
chatId,
|
|
546
|
-
messageId,
|
|
547
|
-
content: message,
|
|
548
|
-
profile
|
|
549
|
-
});
|
|
550
|
-
logger3.info(`Notification sent: ${type}`);
|
|
551
|
-
} catch (error) {
|
|
552
|
-
logger3.error(`Failed to send notification: ${error}`);
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
sendLarkMessage(options) {
|
|
556
|
-
return new Promise((resolve, reject) => {
|
|
557
|
-
const { chatId, messageId, content, profile } = options;
|
|
558
|
-
let args;
|
|
559
|
-
if (messageId) {
|
|
560
|
-
args = [
|
|
561
|
-
"im",
|
|
562
|
-
"+messages-reply",
|
|
563
|
-
"--message-id",
|
|
564
|
-
messageId,
|
|
565
|
-
"--markdown",
|
|
566
|
-
content,
|
|
567
|
-
"--as",
|
|
568
|
-
"bot"
|
|
569
|
-
];
|
|
570
|
-
} else {
|
|
571
|
-
args = [
|
|
572
|
-
"im",
|
|
573
|
-
"+messages-send",
|
|
574
|
-
"--chat-id",
|
|
575
|
-
chatId,
|
|
576
|
-
"--markdown",
|
|
577
|
-
content
|
|
578
|
-
];
|
|
579
|
-
}
|
|
580
|
-
if (profile) {
|
|
581
|
-
args.unshift("--profile", profile);
|
|
582
|
-
}
|
|
583
|
-
const proc = spawn("lark-cli", args, {
|
|
584
|
-
shell: false,
|
|
585
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
586
|
-
});
|
|
587
|
-
let stdout = "";
|
|
588
|
-
let stderr = "";
|
|
589
|
-
proc.stdout?.on("data", (data) => {
|
|
590
|
-
stdout += data.toString();
|
|
591
|
-
});
|
|
592
|
-
proc.stderr?.on("data", (data) => {
|
|
593
|
-
stderr += data.toString();
|
|
594
|
-
});
|
|
595
|
-
proc.on("close", (code) => {
|
|
596
|
-
if (code === 0) {
|
|
597
|
-
logger3.debug(`lark-cli output: ${stdout}`);
|
|
598
|
-
resolve();
|
|
599
|
-
} else {
|
|
600
|
-
reject(new Error(`lark-cli exited with code ${code}: ${stderr}`));
|
|
601
|
-
}
|
|
602
|
-
});
|
|
603
|
-
proc.on("error", (error) => {
|
|
604
|
-
reject(error);
|
|
605
|
-
});
|
|
606
|
-
});
|
|
607
|
-
}
|
|
608
|
-
truncate(text, maxLen) {
|
|
609
|
-
if (text.length <= maxLen)
|
|
610
|
-
return text;
|
|
611
|
-
return text.substring(0, maxLen) + "...";
|
|
612
|
-
}
|
|
613
|
-
appendSpatiotemporalContextLines(lines, task, options = {}) {
|
|
614
|
-
if (task?.project_path) {
|
|
615
|
-
lines.push(`- \uD83D\uDCC2 项目路径: \`${task.project_path}\``);
|
|
616
|
-
}
|
|
617
|
-
const ctxStr = task?.context;
|
|
618
|
-
let parsedCtx = null;
|
|
619
|
-
if (typeof ctxStr === "string" && ctxStr.length > 0) {
|
|
620
|
-
try {
|
|
621
|
-
const obj = JSON.parse(ctxStr);
|
|
622
|
-
if (obj && typeof obj === "object" && !Array.isArray(obj)) {
|
|
623
|
-
parsedCtx = obj;
|
|
624
|
-
}
|
|
625
|
-
} catch {}
|
|
626
|
-
}
|
|
627
|
-
if (!parsedCtx) {
|
|
628
|
-
if (options.appendFullContext && typeof ctxStr === "string") {
|
|
629
|
-
lines.push(`- \uD83D\uDCCB 完整 context: \`${this.truncate(ctxStr, 200)}\``);
|
|
630
|
-
}
|
|
631
|
-
return;
|
|
632
|
-
}
|
|
633
|
-
if (parsedCtx.worktree_path && typeof parsedCtx.worktree_path === "string") {
|
|
634
|
-
lines.push(`- \uD83C\uDF3F Worktree: \`${parsedCtx.worktree_path}\``);
|
|
635
|
-
}
|
|
636
|
-
if (parsedCtx.created_at && typeof parsedCtx.created_at === "string") {
|
|
637
|
-
lines.push(`- ⏰ 时间上下文: ${parsedCtx.created_at}`);
|
|
638
|
-
}
|
|
639
|
-
if (parsedCtx.prior_context && typeof parsedCtx.prior_context === "object") {
|
|
640
|
-
const priorStr = this.formatPriorContext(parsedCtx.prior_context);
|
|
641
|
-
if (priorStr) {
|
|
642
|
-
lines.push(`- \uD83D\uDCDC 背景: ${priorStr}`);
|
|
643
|
-
}
|
|
644
|
-
}
|
|
645
|
-
if (options.appendFullContext) {
|
|
646
|
-
lines.push(`- \uD83D\uDCCB 完整 context: \`${this.truncate(JSON.stringify(parsedCtx), 300)}\``);
|
|
647
|
-
}
|
|
648
|
-
}
|
|
649
|
-
formatPriorContext(prior) {
|
|
650
|
-
if (!prior || typeof prior !== "object")
|
|
651
|
-
return "";
|
|
652
|
-
const entries = [];
|
|
653
|
-
for (const [k, v] of Object.entries(prior)) {
|
|
654
|
-
if (v === null || v === undefined)
|
|
655
|
-
continue;
|
|
656
|
-
const s = typeof v === "object" ? JSON.stringify(v) : String(v);
|
|
657
|
-
if (s === "" || s === "undefined")
|
|
658
|
-
continue;
|
|
659
|
-
entries.push(`${k}=${s}`);
|
|
660
|
-
}
|
|
661
|
-
return entries.join(", ");
|
|
662
|
-
}
|
|
663
|
-
shouldNotify(type) {
|
|
664
|
-
const { notifyTypes } = this.config;
|
|
665
|
-
if (notifyTypes && notifyTypes.length === 0)
|
|
666
|
-
return true;
|
|
667
|
-
if (notifyTypes)
|
|
668
|
-
return notifyTypes.includes(type);
|
|
669
|
-
return type === "task_completed" /* TASK_COMPLETED */;
|
|
670
|
-
}
|
|
671
|
-
getNotifyTitle(type) {
|
|
672
|
-
const template = this.config.templates?.[type];
|
|
673
|
-
if (template)
|
|
674
|
-
return template;
|
|
675
|
-
switch (type) {
|
|
676
|
-
case "task_created" /* TASK_CREATED */:
|
|
677
|
-
return "\uD83D\uDCDD 任务已创建";
|
|
678
|
-
case "task_completed" /* TASK_COMPLETED */:
|
|
679
|
-
return "✅ 任务已完成";
|
|
680
|
-
case "task_updated" /* TASK_UPDATED */:
|
|
681
|
-
return "\uD83D\uDCCB 任务已更新";
|
|
682
|
-
case "task_failed" /* TASK_FAILED */:
|
|
683
|
-
return "❌ 任务执行失败";
|
|
684
|
-
case "operation_created" /* OPERATION_CREATED */:
|
|
685
|
-
return "\uD83D\uDCCC 操作记录";
|
|
686
|
-
default:
|
|
687
|
-
return "\uD83D\uDCEC 任务通知";
|
|
688
|
-
}
|
|
689
|
-
}
|
|
690
|
-
formatTaskCreated(task) {
|
|
691
|
-
const lines = [];
|
|
692
|
-
if (task.title) {
|
|
693
|
-
lines.push(`- **标题**: ${task.title}`);
|
|
694
|
-
}
|
|
695
|
-
if (task.description) {
|
|
696
|
-
lines.push(`- **描述**: ${this.truncate(task.description, 100)}`);
|
|
697
|
-
}
|
|
698
|
-
if (task.priority) {
|
|
699
|
-
lines.push(`- **优先级**: ${task.priority}`);
|
|
700
|
-
}
|
|
701
|
-
this.appendSpatiotemporalContextLines(lines, task);
|
|
702
|
-
return lines.join(`
|
|
703
|
-
`);
|
|
704
|
-
}
|
|
705
|
-
formatTaskUpdate(task, changes) {
|
|
706
|
-
const lines = [];
|
|
707
|
-
if (task.title) {
|
|
708
|
-
lines.push(`- **标题**: ${task.title}`);
|
|
709
|
-
}
|
|
710
|
-
lines.push(`- **ID**: ${task.id}`);
|
|
711
|
-
if (changes?.status) {
|
|
712
|
-
lines.push(`- **状态**: ${changes.status}`);
|
|
713
|
-
}
|
|
714
|
-
if (changes?.progress !== undefined) {
|
|
715
|
-
lines.push(`- **进度**: ${changes.progress}%`);
|
|
716
|
-
}
|
|
717
|
-
if (changes?.current_status) {
|
|
718
|
-
lines.push(`- **当前状态**: ${changes.current_status}`);
|
|
719
|
-
}
|
|
720
|
-
this.appendSpatiotemporalContextLines(lines, task);
|
|
721
|
-
const changeKeys = Object.keys(changes || {}).filter((k) => !["status", "progress", "current_status"].includes(k));
|
|
722
|
-
if (changeKeys.length > 0) {
|
|
723
|
-
lines.push(`- **变更**: ${changeKeys.join(", ")}`);
|
|
724
|
-
}
|
|
725
|
-
return lines.join(`
|
|
726
|
-
`);
|
|
727
|
-
}
|
|
728
|
-
shouldThrottleOperation(operation) {
|
|
729
|
-
const throttleMs = this.config.operationThrottleMs ?? 5000;
|
|
730
|
-
if (throttleMs <= 0) {
|
|
731
|
-
return false;
|
|
732
|
-
}
|
|
733
|
-
const key = `op:${operation.taskId}`;
|
|
734
|
-
const now = Date.now();
|
|
735
|
-
const lastTime = this.lastNotifyTimestamps.get(key);
|
|
736
|
-
if (lastTime && now - lastTime < throttleMs) {
|
|
737
|
-
logger3.debug(`[throttle] Skipping OPERATION_CREATED notify for task #${operation.taskId}, last notified ${now - lastTime}ms ago`);
|
|
738
|
-
return true;
|
|
739
|
-
}
|
|
740
|
-
this.lastNotifyTimestamps.set(key, now);
|
|
741
|
-
return false;
|
|
742
|
-
}
|
|
743
|
-
async formatOperationCreated(operation, task) {
|
|
744
|
-
const resolvedTask = task ?? await this.resolveTaskForOperation(operation);
|
|
745
|
-
const lines = [];
|
|
746
|
-
if (resolvedTask?.title) {
|
|
747
|
-
lines.push(`- **任务**: ${resolvedTask.title}`);
|
|
748
|
-
}
|
|
749
|
-
if (operation.taskId !== undefined && operation.taskId !== null) {
|
|
750
|
-
lines.push(`- **任务 ID**: #${operation.taskId}`);
|
|
751
|
-
}
|
|
752
|
-
if (operation.milestoneType) {
|
|
753
|
-
lines.push(`- **里程碑类型**: ${operation.milestoneType}`);
|
|
754
|
-
}
|
|
755
|
-
if (operation.milestoneTitle) {
|
|
756
|
-
lines.push(`- **标题**: ${operation.milestoneTitle}`);
|
|
757
|
-
}
|
|
758
|
-
if (operation.milestoneDescription) {
|
|
759
|
-
lines.push(`- **里程碑描述**: ${this.truncate(String(operation.milestoneDescription), 200)}`);
|
|
760
|
-
}
|
|
761
|
-
if (operation.processDescription) {
|
|
762
|
-
lines.push(`- **过程概要**: ${this.truncate(String(operation.processDescription), 200)}`);
|
|
763
|
-
}
|
|
764
|
-
this.appendSpatiotemporalContextLines(lines, resolvedTask, {
|
|
765
|
-
appendFullContext: true
|
|
766
|
-
});
|
|
767
|
-
if (operation.sessionId) {
|
|
768
|
-
lines.push(`- **Session**: \`${operation.sessionId}\``);
|
|
769
|
-
}
|
|
770
|
-
return lines.join(`
|
|
771
|
-
`);
|
|
772
|
-
}
|
|
773
|
-
async resolveTaskForOperation(operation) {
|
|
774
|
-
if (operation.taskId === undefined || operation.taskId === null) {
|
|
775
|
-
return;
|
|
776
|
-
}
|
|
777
|
-
const fetch = this.config.fetchTaskById;
|
|
778
|
-
if (!fetch) {
|
|
779
|
-
return;
|
|
780
|
-
}
|
|
781
|
-
try {
|
|
782
|
-
return await fetch(operation.taskId);
|
|
783
|
-
} catch (error) {
|
|
784
|
-
logger3.warn(`fetchTaskById failed for task #${operation.taskId}: ${error}`);
|
|
785
|
-
return;
|
|
786
|
-
}
|
|
787
|
-
}
|
|
788
|
-
}
|
|
789
|
-
__legacyDecorateClassTS([
|
|
790
|
-
TracedAs("lark-cli-task-notify.execute")
|
|
791
|
-
], LarkCliTaskNotifyHook.prototype, "execute", null);
|
|
792
|
-
__legacyDecorateClassTS([
|
|
793
|
-
TracedAs("lark-cli-task-notify.sendNotify")
|
|
794
|
-
], LarkCliTaskNotifyHook.prototype, "sendNotify", null);
|
|
795
|
-
__legacyDecorateClassTS([
|
|
796
|
-
TracedAs("lark-cli-task-notify.sendLarkMessage")
|
|
797
|
-
], LarkCliTaskNotifyHook.prototype, "sendLarkMessage", null);
|
|
798
|
-
function createLarkCliTaskNotifyHook(config = {}) {
|
|
799
|
-
return new LarkCliTaskNotifyHook(config);
|
|
800
|
-
}
|
|
801
|
-
export { createAutoTaskPlugin, WORKFLOW_EXTRACTOR_PROMPT, formatExtractorInput, formatExtractorInputFromDescription, parseExtractorOutput, TaskTagPlugin, NotifyType, LarkCliTaskNotifyHook, createLarkCliTaskNotifyHook };
|