@ai-setting/roy-agent-core 1.7.64 → 1.7.65
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/environment.d.ts +27 -0
- package/dist/env/event-source/event-source-handlers.js +3 -2
- package/dist/env/event-source/index.js +4 -3
- package/dist/env/index.js +7 -6
- package/dist/env/task/delegate/delegate-tool.d.ts +2 -2
- package/dist/env/task/hooks/contexts.d.ts +34 -0
- package/dist/env/task/hooks/verdict.d.ts +170 -0
- package/dist/env/task/index.js +3 -2
- package/dist/env/task/mcp/index.js +2 -1
- package/dist/env/task/plugins/__e2e__/laya-chain-validate.d.ts +15 -0
- package/dist/env/task/plugins/__e2e__/laya-decision-branches.d.ts +18 -0
- package/dist/env/task/plugins/__e2e__/laya-decision-e2e-live.d.ts +27 -0
- package/dist/env/task/plugins/__e2e__/laya-e2e-verify.d.ts +17 -0
- package/dist/env/task/plugins/decision-hint-plugin.d.ts +144 -0
- package/dist/env/task/plugins/decision-port.d.ts +119 -0
- package/dist/env/task/plugins/decision-renderers.d.ts +33 -0
- package/dist/env/task/plugins/index.d.ts +1 -0
- package/dist/env/task/plugins/index.js +8 -1
- package/dist/env/task/plugins/laya-decision-plugin.d.ts +136 -0
- package/dist/env/task/plugins/laya-port.d.ts +143 -0
- package/dist/env/task/plugins/laya-render.d.ts +100 -0
- package/dist/env/task/tools/index.js +2 -1
- package/dist/index.js +7 -6
- package/dist/shared/@ai-setting/{roy-agent-core-1h5bbqcg.js → roy-agent-core-18am1h07.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-vncwfzw5.js → roy-agent-core-6z412atw.js} +39 -2
- package/dist/shared/@ai-setting/{roy-agent-core-vd9zak15.js → roy-agent-core-e42v9bcq.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-ry2eztft.js → roy-agent-core-f6c0nadn.js} +958 -94
- package/dist/shared/@ai-setting/{roy-agent-core-e4hehcdr.js → roy-agent-core-hmdcz3g9.js} +3 -2
- package/dist/shared/@ai-setting/{roy-agent-core-xw4wsj38.js → roy-agent-core-t0ktaevq.js} +48 -2
- package/dist/shared/@ai-setting/roy-agent-core-tr3ep81v.js +397 -0
- package/package.json +1 -1
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
searchTasksTool,
|
|
23
23
|
treeTasksTool,
|
|
24
24
|
updateTaskTool
|
|
25
|
-
} from "./roy-agent-core-
|
|
25
|
+
} from "./roy-agent-core-t0ktaevq.js";
|
|
26
26
|
import {
|
|
27
27
|
createOperationTool,
|
|
28
28
|
deleteOperationTool,
|
|
@@ -410,7 +410,8 @@ class TaskComponent extends BaseComponent {
|
|
|
410
410
|
const createCtx = {
|
|
411
411
|
options: { ...options },
|
|
412
412
|
sessionId,
|
|
413
|
-
tagService: this.tagService
|
|
413
|
+
tagService: this.tagService,
|
|
414
|
+
taskComponent: this
|
|
414
415
|
};
|
|
415
416
|
await this.executeHook(TaskHookPoints.BEFORE_CREATE, createCtx);
|
|
416
417
|
const task = await this.store.createTask(createCtx.options);
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import {
|
|
2
|
+
NEW_TASK_ID_HASH_PLACEHOLDER,
|
|
3
|
+
NEW_TASK_ID_PLACEHOLDER
|
|
4
|
+
} from "./roy-agent-core-tr3ep81v.js";
|
|
1
5
|
import {
|
|
2
6
|
getEnvContextOrEmpty,
|
|
3
7
|
init_env_context,
|
|
@@ -47,7 +51,7 @@ var CreateTaskToolSchema = z.object({
|
|
|
47
51
|
` + ` C. 新建根任务 → 省略 parent_task_id(让树根 ≤10)
|
|
48
52
|
` + `如何找到 parent_task_id:使用 task_tree(depth=2) 获取的树形结构,
|
|
49
53
|
` + "根据节点 id 选择合适的父任务 ID(来自 tree 输出,**不要硬编码**数字)。"),
|
|
50
|
-
context: z.string().default('{"type":"unknown"}').describe("JSON string with time-space positioning information for task continuity. " + "This field is crucial for Agent task handoff. " + "Core fields: worktree_path (git worktree path), branch (current branch), type (feature/bug/refactor/chore). " + 'Example for coding: {"worktree_path": "/path/worktree", "branch": "feature/xyz", "type": "feature"}. ' + 'Example for other: {"type": "general"}. ' + `If unknown, use '{}' or '{"type":"unknown"}'. ` + "IMPORTANT: Must be a valid JSON string!")
|
|
54
|
+
context: z.string().default('{"type":"unknown"}').describe("JSON string with time-space positioning information for task continuity. " + "This field is crucial for Agent task handoff. " + "Core fields: worktree_path (git worktree path), branch (current branch), type (feature/bug/refactor/chore). " + 'Example for coding: {"worktree_path": "/path/worktree", "branch": "feature/xyz", "type": "feature"}. ' + 'Example for other: {"type": "general"}. ' + `If unknown, use '{}' or '{"type":"unknown"}'. ` + "IMPORTANT: Must be a valid JSON string! " + "**Task #4467 — similar_task_ids (REQUIRED)**: array of existing task ids that " + "cover the same ground as this request, used by the task:before.create decision " + "check to suggest reusing/reopening instead of creating a duplicate. " + "Search first (task_search / task_get), then pass the ids. " + "It may be an empty array [] if you searched and found nothing related — " + "but the field itself must be present. " + 'Example: {"type":"feature", "similar_task_ids": [4433, 4445]}')
|
|
51
55
|
});
|
|
52
56
|
var GetTaskToolSchema = withLegacyTaskId({
|
|
53
57
|
include_operations: z.boolean().optional().default(true).describe("Include operation records (milestones / process log) with the task. " + "Defaults to true — the task's operation history is part of its detail. " + "Pass false for a lean task-only response.")
|
|
@@ -208,6 +212,19 @@ function inheritChildContext(args) {
|
|
|
208
212
|
|
|
209
213
|
// src/env/task/tools/create-tool.ts
|
|
210
214
|
var logger2 = createLogger("task:tools:create");
|
|
215
|
+
function extractDecisionHint(contextJson) {
|
|
216
|
+
if (!contextJson)
|
|
217
|
+
return;
|
|
218
|
+
try {
|
|
219
|
+
const parsed = JSON.parse(contextJson);
|
|
220
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
221
|
+
return;
|
|
222
|
+
const hint = parsed.decision_hint;
|
|
223
|
+
return typeof hint === "string" && hint.length > 0 ? hint : undefined;
|
|
224
|
+
} catch {
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
211
228
|
function detectWorktree() {
|
|
212
229
|
try {
|
|
213
230
|
const toplevel = execSync("git rev-parse --show-toplevel", {
|
|
@@ -247,6 +264,30 @@ function createTaskTool(taskComponent) {
|
|
|
247
264
|
step 2: read the tree, then decide per "Decision Tree" below
|
|
248
265
|
step 3: task_create({...})
|
|
249
266
|
|
|
267
|
+
## ⚠️ STEP 1 — FIND SIMILAR TASKS FIRST (Task #4467)
|
|
268
|
+
|
|
269
|
+
Before creating a task, you **MUST** first look for existing tasks
|
|
270
|
+
that cover the same ground, and record them in
|
|
271
|
+
\`context.similar_task_ids\`. This lets the decision model detect
|
|
272
|
+
duplicates and suggest reusing / reopening an existing task instead
|
|
273
|
+
of piling up another root.
|
|
274
|
+
|
|
275
|
+
Workflow:
|
|
276
|
+
step 1: task_search({keywords: [...]}) or task_get(...) to find
|
|
277
|
+
existing tasks related to this request
|
|
278
|
+
step 2: collect the relevant task ids (0 or more)
|
|
279
|
+
step 3: task_create({..., context: {..., similar_task_ids: [id1, id2]}})
|
|
280
|
+
|
|
281
|
+
\`context.similar_task_ids\` is **required** (may be an empty array \`[]\`
|
|
282
|
+
if you searched and found nothing related). It is an array of task ids.
|
|
283
|
+
|
|
284
|
+
The decision check runs on \`task:before.create\` and only **advises** —
|
|
285
|
+
the task is always created. If the output contains a
|
|
286
|
+
\`[TASK_CREATE_HINT]\` section, you **MUST** read and verify it:
|
|
287
|
+
verify with \`task_get\`, then either execute the suggested
|
|
288
|
+
\`task_update\` (reusing / reopening / re-parenting) or explicitly
|
|
289
|
+
state why it was not adopted. Never ignore it.
|
|
290
|
+
|
|
250
291
|
## Decision Tree — How to choose parent_task_id
|
|
251
292
|
|
|
252
293
|
Walk these paths in order:
|
|
@@ -454,9 +495,14 @@ function createTaskTool(taskComponent) {
|
|
|
454
495
|
});
|
|
455
496
|
setCurrentTaskId(task.id);
|
|
456
497
|
logger2.info(`[createTask] Trace: Task #${task.id} created, currentTaskId=${task.id} set in EnvContext`);
|
|
498
|
+
const decisionHint = extractDecisionHint(task.context);
|
|
499
|
+
const renderedHint = decisionHint ? decisionHint.replaceAll(NEW_TASK_ID_HASH_PLACEHOLDER, `#${task.id}`).replaceAll(NEW_TASK_ID_PLACEHOLDER, String(task.id)) : "";
|
|
500
|
+
const output = `Task created successfully: #${task.id} - ${task.title}` + (renderedHint ? `
|
|
501
|
+
|
|
502
|
+
${renderedHint}` : "");
|
|
457
503
|
return {
|
|
458
504
|
success: true,
|
|
459
|
-
output
|
|
505
|
+
output,
|
|
460
506
|
metadata: {
|
|
461
507
|
execution_time_ms: 0,
|
|
462
508
|
task_id: task.id,
|
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
import {
|
|
2
|
+
init_decorator,
|
|
3
|
+
wrapFunction
|
|
4
|
+
} from "./roy-agent-core-qq96p0tj.js";
|
|
5
|
+
|
|
6
|
+
// src/env/task/hooks/verdict.ts
|
|
7
|
+
init_decorator();
|
|
8
|
+
var MAX_SIMILAR_TASK_IDS = 20;
|
|
9
|
+
var CANDIDATE_TITLE_MAX = 40;
|
|
10
|
+
var CANDIDATE_DESC_MAX = 45;
|
|
11
|
+
var CANDIDATE_TAGS_MAX = 4;
|
|
12
|
+
var LEAKAGE_DENY_KEYS = [
|
|
13
|
+
"expected_decision",
|
|
14
|
+
"expected_root",
|
|
15
|
+
"expected_",
|
|
16
|
+
"case_id",
|
|
17
|
+
"category",
|
|
18
|
+
"suggested_",
|
|
19
|
+
"recommended_",
|
|
20
|
+
"best_match",
|
|
21
|
+
"answer",
|
|
22
|
+
"answer_key",
|
|
23
|
+
"label",
|
|
24
|
+
"should_",
|
|
25
|
+
"target",
|
|
26
|
+
"gold",
|
|
27
|
+
"ground_truth",
|
|
28
|
+
"hint",
|
|
29
|
+
"solution",
|
|
30
|
+
"duplicate_of",
|
|
31
|
+
"similar_task_ids",
|
|
32
|
+
"candidate_order",
|
|
33
|
+
"verdict",
|
|
34
|
+
"decision"
|
|
35
|
+
];
|
|
36
|
+
function parseSimilarTaskIdsImpl(contextJson) {
|
|
37
|
+
if (!contextJson)
|
|
38
|
+
return [];
|
|
39
|
+
let parsed;
|
|
40
|
+
try {
|
|
41
|
+
parsed = JSON.parse(contextJson);
|
|
42
|
+
} catch {
|
|
43
|
+
return [];
|
|
44
|
+
}
|
|
45
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
46
|
+
return [];
|
|
47
|
+
const raw = parsed.similar_task_ids;
|
|
48
|
+
if (!Array.isArray(raw))
|
|
49
|
+
return [];
|
|
50
|
+
const out = [];
|
|
51
|
+
const seen = new Set;
|
|
52
|
+
for (const item of raw) {
|
|
53
|
+
if (typeof item !== "number")
|
|
54
|
+
continue;
|
|
55
|
+
if (!Number.isInteger(item) || item <= 0)
|
|
56
|
+
continue;
|
|
57
|
+
if (seen.has(item))
|
|
58
|
+
continue;
|
|
59
|
+
seen.add(item);
|
|
60
|
+
out.push(item);
|
|
61
|
+
if (out.length >= MAX_SIMILAR_TASK_IDS)
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
return out;
|
|
65
|
+
}
|
|
66
|
+
function deriveActionFromStatusImpl(status) {
|
|
67
|
+
switch (status) {
|
|
68
|
+
case "completed":
|
|
69
|
+
case "cancelled":
|
|
70
|
+
case "paused":
|
|
71
|
+
case "archived":
|
|
72
|
+
return "reopen";
|
|
73
|
+
case "todo":
|
|
74
|
+
case "active":
|
|
75
|
+
default:
|
|
76
|
+
return "enhance";
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function deriveReopenStatus(status) {
|
|
80
|
+
return status === "paused" ? "active" : "todo";
|
|
81
|
+
}
|
|
82
|
+
var STATUS_LETTER = {
|
|
83
|
+
todo: "t",
|
|
84
|
+
active: "a",
|
|
85
|
+
completed: "c",
|
|
86
|
+
paused: "p",
|
|
87
|
+
cancelled: "x",
|
|
88
|
+
archived: "r"
|
|
89
|
+
};
|
|
90
|
+
var NEW_TASK_ID_PLACEHOLDER = "__NEW_TASK_ID__";
|
|
91
|
+
var NEW_TASK_ID_HASH_PLACEHOLDER = "#__NEW_TASK_ID__";
|
|
92
|
+
function buildHintTextImpl(input) {
|
|
93
|
+
switch (input.scenario) {
|
|
94
|
+
case "reuse":
|
|
95
|
+
return buildReuseHint(input);
|
|
96
|
+
case "reopen":
|
|
97
|
+
return buildReopenHint(input);
|
|
98
|
+
case "reopen_enhance":
|
|
99
|
+
return buildReopenEnhanceHint(input);
|
|
100
|
+
case "remount":
|
|
101
|
+
return buildRemountHint(input);
|
|
102
|
+
case "soft_reuse":
|
|
103
|
+
return buildSoftReuseHint(input);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function newTaskRef(input) {
|
|
107
|
+
return input.newTaskId > 0 ? String(input.newTaskId) : NEW_TASK_ID_PLACEHOLDER;
|
|
108
|
+
}
|
|
109
|
+
function newTaskHashRef(input) {
|
|
110
|
+
return input.newTaskId > 0 ? `#${input.newTaskId}` : NEW_TASK_ID_HASH_PLACEHOLDER;
|
|
111
|
+
}
|
|
112
|
+
function targetLine(input) {
|
|
113
|
+
const t = input.targetTask;
|
|
114
|
+
if (!t)
|
|
115
|
+
return "";
|
|
116
|
+
return `#${input.targetTaskId} [${t.type}] ${t.title}
|
|
117
|
+
状态:${t.status}`;
|
|
118
|
+
}
|
|
119
|
+
function buildReuseHint(input) {
|
|
120
|
+
const goalsPart = input.includeGoals ? ", goals_and_expected_deliverables=<追加本次目标>" : "";
|
|
121
|
+
return [
|
|
122
|
+
"[TASK_CREATE_HINT] 检测到与已有任务高度相似,建议复用而非新建。",
|
|
123
|
+
"",
|
|
124
|
+
targetLine(input),
|
|
125
|
+
"状态:" + (input.targetTask?.status ?? "") + " | 建议动作:enhance(补充目标/描述)",
|
|
126
|
+
"",
|
|
127
|
+
`如需合并到 #${input.targetTaskId},可执行:`,
|
|
128
|
+
` task_update(task_id=${input.targetTaskId}, description=<追加本次请求的描述>${goalsPart})`,
|
|
129
|
+
"",
|
|
130
|
+
`说明:本次已创建新任务 ${newTaskHashRef(input)}。若确认应复用 #${input.targetTaskId},请调用上述 task_update 并按需归档 ${newTaskHashRef(input)}。`,
|
|
131
|
+
"",
|
|
132
|
+
"核实要点:本次请求是否就是 #" + input.targetTaskId + " 的「同一件事」,而不是同主题下新的一步?",
|
|
133
|
+
" - 若只是同主题的新一步(需要独立跟踪的新子任务),建子任务不算不采纳,但需在回复中说明为何不直接合并。",
|
|
134
|
+
" - 若确实是同一件事,应采纳 (a) 执行复用。",
|
|
135
|
+
"",
|
|
136
|
+
`⚠️ 你必须核实本提示:先用 task_get(${input.targetTaskId}) 确认目标任务的当前状态与内容是否与本次请求实质相同。`,
|
|
137
|
+
"核实后二选一执行:",
|
|
138
|
+
`(a) 确认实质相同 → 按上面的 task_update(task_id=${input.targetTaskId}, ...) 执行复用,并归档本次新建的任务 ${newTaskHashRef(input)};`,
|
|
139
|
+
"(b) 确认不相似 → 在本次回复中明确说明不采纳的理由。",
|
|
140
|
+
"不允许无视本提示。"
|
|
141
|
+
].join(`
|
|
142
|
+
`);
|
|
143
|
+
}
|
|
144
|
+
function buildReopenHint(input) {
|
|
145
|
+
const targetStatus = input.targetTask?.status ?? "completed";
|
|
146
|
+
const reopenTo = deriveReopenStatus(targetStatus);
|
|
147
|
+
const goalsPart = input.includeGoals ? ", goals_and_expected_deliverables=<追加本次目标>" : "";
|
|
148
|
+
return [
|
|
149
|
+
"[TASK_CREATE_HINT] 检测到本次请求与已有任务实质相同,建议重开该任务而不是新建。",
|
|
150
|
+
"",
|
|
151
|
+
targetLine(input),
|
|
152
|
+
`状态:${targetStatus} | 建议动作:reopen(恢复为 ${reopenTo})`,
|
|
153
|
+
"",
|
|
154
|
+
`如需重开并承接本次请求,可执行:`,
|
|
155
|
+
` task_update(task_id=${input.targetTaskId}, status=${reopenTo}, description=<追加本次请求的描述>${goalsPart})`,
|
|
156
|
+
"",
|
|
157
|
+
`说明:本次已创建新任务 ${newTaskHashRef(input)}。若确认应复用 #${input.targetTaskId},请调用上述 task_update 并按需归档 ${newTaskHashRef(input)}。`,
|
|
158
|
+
"",
|
|
159
|
+
`核实要点:#${input.targetTaskId} 是否真的已完结(completed / cancelled),但其语义与本次请求仍然相同?`,
|
|
160
|
+
" - 若已完结但语义仍相同(本次请求是该任务的延续)→ 应采纳 (a) 重开。",
|
|
161
|
+
" - 若目标已完结且语义不同(只是主题相近)→ 应采纳 (b) 说明不采纳的理由。",
|
|
162
|
+
"",
|
|
163
|
+
`⚠️ 你必须核实本提示:先用 task_get(${input.targetTaskId}) 确认目标任务的当前状态与内容是否与本次请求实质相同。`,
|
|
164
|
+
"核实后二选一执行:",
|
|
165
|
+
`(a) 确认实质相同 → 按上面的 task_update(task_id=${input.targetTaskId}, status=${reopenTo}, ...) 执行重开,并归档本次新建的任务 ${newTaskHashRef(input)};`,
|
|
166
|
+
"(b) 确认不相似 → 在本次回复中明确说明不采纳的理由。",
|
|
167
|
+
"不允许无视本提示。"
|
|
168
|
+
].join(`
|
|
169
|
+
`);
|
|
170
|
+
}
|
|
171
|
+
function buildReopenEnhanceHint(input) {
|
|
172
|
+
const targetStatus = input.targetTask?.status ?? "completed";
|
|
173
|
+
const reopenTo = deriveReopenStatus(targetStatus);
|
|
174
|
+
return [
|
|
175
|
+
"[TASK_CREATE_HINT] 检测到本次请求与已有任务实质相同,且本次新增了目标,建议重开并增强该任务。",
|
|
176
|
+
"",
|
|
177
|
+
targetLine(input),
|
|
178
|
+
`状态:${targetStatus} | 建议动作:reopen + enhance(恢复为 ${reopenTo} 并追加目标)`,
|
|
179
|
+
"",
|
|
180
|
+
"如需重开并承接本次请求,可执行:",
|
|
181
|
+
` task_update(task_id=${input.targetTaskId}, status=${reopenTo}, description=<追加本次请求的描述>, goals_and_expected_deliverables=<追加本次目标>)`,
|
|
182
|
+
"",
|
|
183
|
+
`说明:本次已创建新任务 ${newTaskHashRef(input)}。若确认应复用 #${input.targetTaskId},请调用上述 task_update 并按需归档 ${newTaskHashRef(input)}。`,
|
|
184
|
+
"",
|
|
185
|
+
`核实要点:① #${input.targetTaskId} 是否真的已完结(completed / cancelled)但语义仍与本次请求相同;② 本次新增的目标是否确实属于该任务的范围内?`,
|
|
186
|
+
" - 两点都成立 → 应采纳 (a) 重开并增强。",
|
|
187
|
+
" - 新增目标超出原任务范围(是独立的新工作)→ 应采纳 (b) 说明不采纳的理由。",
|
|
188
|
+
"",
|
|
189
|
+
`⚠️ 你必须核实本提示:先用 task_get(${input.targetTaskId}) 确认目标任务的当前状态与内容是否与本次请求实质相同。`,
|
|
190
|
+
"核实后二选一执行:",
|
|
191
|
+
`(a) 确认实质相同 → 按上面的 task_update(task_id=${input.targetTaskId}, status=${reopenTo}, ..., goals_and_expected_deliverables=...) 执行重开并增强,并归档本次新建的任务 ${newTaskHashRef(input)};`,
|
|
192
|
+
"(b) 确认不相似 → 在本次回复中明确说明不采纳的理由。",
|
|
193
|
+
"不允许无视本提示。"
|
|
194
|
+
].join(`
|
|
195
|
+
`);
|
|
196
|
+
}
|
|
197
|
+
function buildRemountHint(input) {
|
|
198
|
+
return [
|
|
199
|
+
"[TASK_CREATE_HINT] 检测到本次任务的挂载位置可能不合理。",
|
|
200
|
+
"",
|
|
201
|
+
`当前挂载:parent #${input.currentParentId} → root 祖先 #${input.currentRootAncestorId}`,
|
|
202
|
+
`建议归属:root #${input.recommendedRootId} ${input.recommendedRootTitle ?? ""}`.trimEnd(),
|
|
203
|
+
"",
|
|
204
|
+
"如需改挂到建议的 root 下,可执行:",
|
|
205
|
+
` task_update(task_id=${newTaskRef(input)}, parent_task_id=${input.recommendedRootId})`,
|
|
206
|
+
"",
|
|
207
|
+
`说明:本次已创建新任务 ${newTaskHashRef(input)},parent 未被自动修改。`,
|
|
208
|
+
"",
|
|
209
|
+
`核实要点:当前 parent #${input.currentParentId} 的 root 祖先 #${input.currentRootAncestorId} 与建议的 root #${input.recommendedRootId},哪一条才是本次任务的正确归属线?(可参考兄弟任务的主题分布)`,
|
|
210
|
+
" - 建议的 root 更贴合本次任务主题 → 应采纳 (a) 执行改挂。",
|
|
211
|
+
" - 当前归属线已正确 → 应采纳 (b) 说明不采纳的理由。",
|
|
212
|
+
"",
|
|
213
|
+
`⚠️ 你必须核实本提示:先用 task_get(${newTaskRef(input)}) 确认本次新建任务的实际 parent,并用 task_get(${input.recommendedRootId})(必要时 task_tree)确认归属线是否真的不对。`,
|
|
214
|
+
"核实后二选一执行:",
|
|
215
|
+
`(a) 确认挂载不合理 → 执行改挂 task_update(task_id=${newTaskRef(input)}, parent_task_id=${input.recommendedRootId});`,
|
|
216
|
+
"(b) 确认当前挂载合理 → 在本次回复中明确说明不采纳的理由。",
|
|
217
|
+
"不允许无视本提示。"
|
|
218
|
+
].join(`
|
|
219
|
+
`);
|
|
220
|
+
}
|
|
221
|
+
function buildSoftReuseHint(input) {
|
|
222
|
+
const goalsPart = input.includeGoals ? ", goals_and_expected_deliverables=<追加本次目标>" : "";
|
|
223
|
+
return [
|
|
224
|
+
"[TASK_CREATE_HINT] 检测到本次请求可能与已有任务有关联(置信度偏低,仅供参考)。",
|
|
225
|
+
"",
|
|
226
|
+
targetLine(input),
|
|
227
|
+
"",
|
|
228
|
+
"若确认是同一件事,可执行:",
|
|
229
|
+
` task_update(task_id=${input.targetTaskId}, description=<追加本次请求的描述>${goalsPart})`,
|
|
230
|
+
"",
|
|
231
|
+
`说明:本提示基于较低置信度,可能误判。本次已创建新任务 ${newTaskHashRef(input)}。`,
|
|
232
|
+
"",
|
|
233
|
+
`核实要点:#${input.targetTaskId} 与本次请求是否确为同一件事?(本提示置信度低,核实权重相应降低,但不得跳过表态)`,
|
|
234
|
+
"",
|
|
235
|
+
`⚠️ 你必须核实本提示:先用 task_get(${input.targetTaskId}) 确认目标任务的当前状态与内容是否与本次请求实质相同。`,
|
|
236
|
+
"核实后二选一执行:",
|
|
237
|
+
`(a) 确认实质相同 → 按上面的 task_update(task_id=${input.targetTaskId}, ...) 执行复用,并归档本次新建的任务 ${newTaskHashRef(input)};`,
|
|
238
|
+
"(b) 确认不相似 → 在本次回复中明确说明不采纳的理由。",
|
|
239
|
+
"不允许无视本提示。"
|
|
240
|
+
].join(`
|
|
241
|
+
`);
|
|
242
|
+
}
|
|
243
|
+
function isLeakingKey(key) {
|
|
244
|
+
const lower = key.toLowerCase();
|
|
245
|
+
return LEAKAGE_DENY_KEYS.some((deny) => deny.endsWith("_") || deny.endsWith("*") ? lower.startsWith(deny.slice(0, -1)) : lower === deny);
|
|
246
|
+
}
|
|
247
|
+
function assertNoLeakageImpl(state, questions) {
|
|
248
|
+
const walk = (node, path) => {
|
|
249
|
+
if (Array.isArray(node)) {
|
|
250
|
+
node.forEach((item, i) => walk(item, `${path}[${i}]`));
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
if (!node || typeof node !== "object")
|
|
254
|
+
return;
|
|
255
|
+
for (const [key, value] of Object.entries(node)) {
|
|
256
|
+
const childPath = path ? `${path}.${key}` : key;
|
|
257
|
+
if (isLeakingKey(key)) {
|
|
258
|
+
throw new Error(`[assertNoLeakage] leakage key detected at ${childPath}: "${key}"`);
|
|
259
|
+
}
|
|
260
|
+
walk(value, childPath);
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
walk(state, "state");
|
|
264
|
+
walk(questions, "questions");
|
|
265
|
+
}
|
|
266
|
+
function writeDecisionHintImpl(options, hint) {
|
|
267
|
+
let parsed;
|
|
268
|
+
try {
|
|
269
|
+
parsed = JSON.parse(options.context);
|
|
270
|
+
} catch {
|
|
271
|
+
return false;
|
|
272
|
+
}
|
|
273
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
274
|
+
return false;
|
|
275
|
+
parsed.decision_hint = hint;
|
|
276
|
+
options.context = JSON.stringify(parsed);
|
|
277
|
+
return true;
|
|
278
|
+
}
|
|
279
|
+
function truncate(text, max) {
|
|
280
|
+
if (text.length <= max)
|
|
281
|
+
return text;
|
|
282
|
+
return text.slice(0, max) + "…";
|
|
283
|
+
}
|
|
284
|
+
function renderCandidateKey(c) {
|
|
285
|
+
return `#${c.id} [${c.type}] ${truncate(c.title, CANDIDATE_TITLE_MAX)}`;
|
|
286
|
+
}
|
|
287
|
+
function renderCandidateValue(c) {
|
|
288
|
+
const letter = STATUS_LETTER[c.status] ?? (c.status.charAt(0) || "t");
|
|
289
|
+
const desc = truncate(c.description, CANDIDATE_DESC_MAX);
|
|
290
|
+
const tags = (c.tags ?? []).slice(0, CANDIDATE_TAGS_MAX);
|
|
291
|
+
const tagsPart = tags.length > 0 ? ` [tags: ${tags.join(",")}]` : "";
|
|
292
|
+
return `${letter}|${c.ago}|${desc}${tagsPart}`;
|
|
293
|
+
}
|
|
294
|
+
function formatRelativeAge(isoTimestamp, now = Date.now()) {
|
|
295
|
+
const ts = Date.parse(isoTimestamp);
|
|
296
|
+
if (Number.isNaN(ts))
|
|
297
|
+
return "0m";
|
|
298
|
+
const diffMs = now - ts;
|
|
299
|
+
if (diffMs <= 0)
|
|
300
|
+
return "0m";
|
|
301
|
+
const minutes = Math.floor(diffMs / 60000);
|
|
302
|
+
if (minutes < 60)
|
|
303
|
+
return `${minutes}m`;
|
|
304
|
+
const hours = Math.floor(minutes / 60);
|
|
305
|
+
if (hours < 24)
|
|
306
|
+
return `${hours}h`;
|
|
307
|
+
const days = Math.floor(hours / 24);
|
|
308
|
+
return `${days}d`;
|
|
309
|
+
}
|
|
310
|
+
var parseSimilarTaskIds = wrapFunction(parseSimilarTaskIdsImpl, "task.decision.parseSimilarTaskIds", {
|
|
311
|
+
recordParams: true,
|
|
312
|
+
recordResult: true,
|
|
313
|
+
log: false,
|
|
314
|
+
paramFilter: (args) => {
|
|
315
|
+
const raw = args[0];
|
|
316
|
+
const ctx = typeof raw === "string" ? raw : "";
|
|
317
|
+
return {
|
|
318
|
+
contextLen: ctx.length,
|
|
319
|
+
contextPreview: ctx.length > 80 ? ctx.slice(0, 80) + "…" : ctx
|
|
320
|
+
};
|
|
321
|
+
},
|
|
322
|
+
resultFilter: (res) => ({ count: res.length, ids: res.join(",") })
|
|
323
|
+
});
|
|
324
|
+
var deriveActionFromStatus = wrapFunction(deriveActionFromStatusImpl, "task.decision.deriveActionFromStatus", {
|
|
325
|
+
recordParams: true,
|
|
326
|
+
recordResult: true,
|
|
327
|
+
log: false
|
|
328
|
+
});
|
|
329
|
+
var buildHintText = wrapFunction(buildHintTextImpl, "task.decision.buildHintText", {
|
|
330
|
+
recordParams: true,
|
|
331
|
+
recordResult: true,
|
|
332
|
+
log: false,
|
|
333
|
+
paramFilter: (args) => {
|
|
334
|
+
const i = args[0] ?? {};
|
|
335
|
+
return {
|
|
336
|
+
scenario: i.scenario,
|
|
337
|
+
newTaskId: i.newTaskId,
|
|
338
|
+
targetTaskId: i.targetTaskId ?? null,
|
|
339
|
+
recommendedRootId: i.recommendedRootId ?? null,
|
|
340
|
+
currentParentId: i.currentParentId ?? null,
|
|
341
|
+
confidence: i.confidence ?? null
|
|
342
|
+
};
|
|
343
|
+
},
|
|
344
|
+
resultFilter: (res) => ({
|
|
345
|
+
hintLen: res.length,
|
|
346
|
+
hintPreview: res.slice(0, 240),
|
|
347
|
+
hasTaskUpdate: /task_update\s*\(/.test(res),
|
|
348
|
+
hasTaskId: /"task_id"\s*:/.test(res),
|
|
349
|
+
hasParentTaskId: /"parent_task_id"\s*:/.test(res)
|
|
350
|
+
})
|
|
351
|
+
});
|
|
352
|
+
var assertNoLeakage = wrapFunction(assertNoLeakageImpl, "task.decision.assertNoLeakage", {
|
|
353
|
+
recordParams: true,
|
|
354
|
+
recordResult: false,
|
|
355
|
+
log: false,
|
|
356
|
+
paramFilter: (args) => ({
|
|
357
|
+
stateKeys: collectKeyNames(args[0]),
|
|
358
|
+
questionKeys: collectKeyNames(args[1])
|
|
359
|
+
})
|
|
360
|
+
});
|
|
361
|
+
var writeDecisionHint = wrapFunction(writeDecisionHintImpl, "task.decision.writeDecisionHint", {
|
|
362
|
+
recordParams: true,
|
|
363
|
+
recordResult: true,
|
|
364
|
+
log: false,
|
|
365
|
+
paramFilter: (args) => {
|
|
366
|
+
const opts = args[0] ?? {};
|
|
367
|
+
const hint = typeof args[1] === "string" ? args[1] : "";
|
|
368
|
+
return {
|
|
369
|
+
contextLen: typeof opts.context === "string" ? opts.context.length : 0,
|
|
370
|
+
hintLen: hint.length,
|
|
371
|
+
hintScenario: /task_update\s*\(/.test(hint) ? "actionable" : "plain"
|
|
372
|
+
};
|
|
373
|
+
},
|
|
374
|
+
resultFilter: (res) => ({ written: res }),
|
|
375
|
+
errorIf: (res) => res === false ? new Error("writeDecisionHint returned false (context not a JSON object)") : undefined
|
|
376
|
+
});
|
|
377
|
+
function collectKeyNames(node, out = [], depth = 0) {
|
|
378
|
+
if (out.length >= 40 || depth > 4)
|
|
379
|
+
return out;
|
|
380
|
+
if (Array.isArray(node)) {
|
|
381
|
+
for (const item of node.slice(0, 3))
|
|
382
|
+
collectKeyNames(item, out, depth + 1);
|
|
383
|
+
return out;
|
|
384
|
+
}
|
|
385
|
+
if (!node || typeof node !== "object")
|
|
386
|
+
return out;
|
|
387
|
+
for (const key of Object.keys(node)) {
|
|
388
|
+
if (out.length >= 40)
|
|
389
|
+
break;
|
|
390
|
+
if (!out.includes(key))
|
|
391
|
+
out.push(key);
|
|
392
|
+
collectKeyNames(node[key], out, depth + 1);
|
|
393
|
+
}
|
|
394
|
+
return out;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
export { NEW_TASK_ID_PLACEHOLDER, NEW_TASK_ID_HASH_PLACEHOLDER, renderCandidateKey, renderCandidateValue, formatRelativeAge, parseSimilarTaskIds, deriveActionFromStatus, buildHintText, assertNoLeakage, writeDecisionHint };
|