@ai-setting/roy-agent-core 1.7.65 → 1.7.67

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/dist/env/agent/agent-component.d.ts +56 -3
  2. package/dist/env/agent/agent-component.js +2 -2
  3. package/dist/env/agent/index.js +2 -2
  4. package/dist/env/agent/types.d.ts +28 -0
  5. package/dist/env/environment.d.ts +13 -0
  6. package/dist/env/event-source/command-card-action.d.ts +105 -0
  7. package/dist/env/event-source/command-cards.d.ts +72 -0
  8. package/dist/env/event-source/command-registry.d.ts +181 -0
  9. package/dist/env/event-source/command-types.d.ts +8 -0
  10. package/dist/env/event-source/event-source-handlers.d.ts +66 -0
  11. package/dist/env/event-source/event-source-handlers.js +7 -4
  12. package/dist/env/event-source/fallback-model-command.d.ts +80 -0
  13. package/dist/env/event-source/index.d.ts +7 -0
  14. package/dist/env/event-source/index.js +48 -7
  15. package/dist/env/event-source/timer-handler.d.ts +93 -1
  16. package/dist/env/index.js +15 -14
  17. package/dist/env/llm/index.js +1 -1
  18. package/dist/env/prompt/index.js +3 -2
  19. package/dist/env/prompt/prompts-index.d.ts +3 -8
  20. package/dist/env/task/delegate/delegate-tool.d.ts +58 -2
  21. package/dist/env/task/delegate/delegate-tool.js +5 -2
  22. package/dist/env/task/delegate/index.js +3 -2
  23. package/dist/env/task/index.js +9 -8
  24. package/dist/env/task/mcp/index.js +3 -2
  25. package/dist/env/task/plugins/__e2e__/laya-completion-e2e.d.ts +16 -0
  26. package/dist/env/task/plugins/index.js +4 -3
  27. package/dist/env/task/plugins/laya-completion-plugin.d.ts +133 -0
  28. package/dist/env/task/plugins/laya-port.d.ts +23 -0
  29. package/dist/env/task/storage/index.js +2 -2
  30. package/dist/env/task/storage/migrations/003_add_process_description.d.ts +1 -1
  31. package/dist/env/task/storage/migrations/log-migration.d.ts +49 -3
  32. package/dist/env/task/task-component.d.ts +43 -0
  33. package/dist/env/task/tools/completion-copy.d.ts +27 -0
  34. package/dist/env/task/tools/index.js +2 -1
  35. package/dist/env/task/tools/operation/create-tool.d.ts +1 -1
  36. package/dist/env/task/tools/operation/index.js +1 -1
  37. package/dist/env/task/tools/operation/operation-types.d.ts +10 -8
  38. package/dist/env/task/types.d.ts +3 -3
  39. package/dist/env/tool/built-in/index.js +2 -2
  40. package/dist/env/tool/built-in/query-background-process-tool.d.ts +10 -0
  41. package/dist/env/tool/index.js +3 -3
  42. package/dist/env/tool/types.d.ts +6 -0
  43. package/dist/index.js +60 -19
  44. package/dist/shared/@ai-setting/{roy-agent-core-rjzdj0xb.js → roy-agent-core-02j24hrg.js} +2 -0
  45. package/dist/shared/@ai-setting/{roy-agent-core-hmdcz3g9.js → roy-agent-core-1k83dq3a.js} +16 -6
  46. package/dist/shared/@ai-setting/{roy-agent-core-e42v9bcq.js → roy-agent-core-1kxf910n.js} +1 -1
  47. package/dist/shared/@ai-setting/{roy-agent-core-amkqnyzy.js → roy-agent-core-58804s3j.js} +10 -0
  48. package/dist/shared/@ai-setting/roy-agent-core-6m4bw8jy.js +6 -0
  49. package/dist/shared/@ai-setting/roy-agent-core-8zas6w5q.js +1083 -0
  50. package/dist/shared/@ai-setting/{roy-agent-core-9bs6xrav.js → roy-agent-core-abqhkbn3.js} +6 -3
  51. package/dist/shared/@ai-setting/{roy-agent-core-f6c0nadn.js → roy-agent-core-b7jbqfsy.js} +25 -11
  52. package/dist/shared/@ai-setting/{roy-agent-core-hjejpcg5.js → roy-agent-core-dnjm17sa.js} +51 -9
  53. package/dist/shared/@ai-setting/{roy-agent-core-t0ktaevq.js → roy-agent-core-h5vmff8w.js} +60 -4
  54. package/dist/shared/@ai-setting/{roy-agent-core-xhrrgfdy.js → roy-agent-core-hfr6bzts.js} +168 -52
  55. package/dist/shared/@ai-setting/{roy-agent-core-18am1h07.js → roy-agent-core-k4ps1hxp.js} +1085 -46
  56. package/dist/shared/@ai-setting/{roy-agent-core-jz8dsypm.js → roy-agent-core-kpg25s2q.js} +1 -1
  57. package/dist/shared/@ai-setting/{roy-agent-core-6masakz4.js → roy-agent-core-ma35jyd9.js} +19 -8
  58. package/dist/shared/@ai-setting/{roy-agent-core-gxzxq1sf.js → roy-agent-core-ntq0gz7q.js} +1 -1
  59. package/dist/shared/@ai-setting/{roy-agent-core-rn57psp0.js → roy-agent-core-p5cz9m8v.js} +3 -3
  60. package/dist/shared/@ai-setting/{roy-agent-core-w07ymg29.js → roy-agent-core-wksgtqf9.js} +1 -1
  61. package/package.json +1 -1
  62. package/dist/shared/@ai-setting/roy-agent-core-6z412atw.js +0 -654
  63. /package/dist/shared/@ai-setting/{roy-agent-core-5enxfga2.js → roy-agent-core-b4x6x4k2.js} +0 -0
  64. /package/dist/shared/@ai-setting/{roy-agent-core-es467wsz.js → roy-agent-core-dt104qa1.js} +0 -0
@@ -16,10 +16,13 @@ import {
16
16
 
17
17
  // src/env/task/storage/migrations/log-migration.ts
18
18
  function logMigration(message) {
19
- process.stderr.write(`${message}
20
- `);
19
+ migrationLogger.warn(message);
21
20
  }
22
- var init_log_migration = () => {};
21
+ var migrationLogger;
22
+ var init_log_migration = __esm(() => {
23
+ init_logger();
24
+ migrationLogger = createLogger("task:migration");
25
+ });
23
26
 
24
27
  // src/env/task/storage/migrations/001_add_project_path_context.ts
25
28
  var exports_001_add_project_path_context = {};
@@ -4,13 +4,13 @@ import {
4
4
  import {
5
5
  createCompatibleDatabase,
6
6
  init_sqlite_task_store
7
- } from "./roy-agent-core-9bs6xrav.js";
7
+ } from "./roy-agent-core-abqhkbn3.js";
8
8
  import {
9
9
  TaskHookPoints
10
10
  } from "./roy-agent-core-1pg0fepg.js";
11
11
  import {
12
12
  WORKFLOW_EXTRACT_PROMPT
13
- } from "./roy-agent-core-6masakz4.js";
13
+ } from "./roy-agent-core-ma35jyd9.js";
14
14
  import {
15
15
  assertNoLeakage,
16
16
  buildHintText,
@@ -929,6 +929,7 @@ function truncateSnippet(text) {
929
929
  var DEFAULT_TIMEOUT_MS = 2000;
930
930
  var DEFAULT_MAX_STRIKES = 3;
931
931
  var DEFAULT_MODEL = "task-decision";
932
+ var DEFAULT_GOAL_MODEL = "task-completion";
932
933
  var SYSTEMONE_PATH = "/v1/systemone";
933
934
 
934
935
  class LayaDecisionPort {
@@ -1058,19 +1059,32 @@ function isTimeoutError(err) {
1058
1059
  }
1059
1060
  return false;
1060
1061
  }
1062
+ var LAYA_DECISION_PROVIDER = "laya-decision";
1063
+ var LAYA_GOAL_DECISION_PROVIDER = "laya-goal-decision";
1064
+ function providerNameForPurpose(purpose) {
1065
+ return purpose === "goal" ? LAYA_GOAL_DECISION_PROVIDER : LAYA_DECISION_PROVIDER;
1066
+ }
1061
1067
  async function loadLayaAuth(authPath = `${process.env.HOME ?? "~"}/.roy-agent/auth.json`) {
1068
+ return loadLayaAuthForPurpose(authPath, "create");
1069
+ }
1070
+ async function loadLayaAuthForPurpose(authPath = `${process.env.HOME ?? "~"}/.roy-agent/auth.json`, purpose = "create") {
1062
1071
  try {
1063
1072
  const fs = await import("fs/promises");
1064
1073
  const raw = await fs.readFile(authPath, "utf-8");
1065
1074
  const parsed = JSON.parse(raw);
1066
- const p = parsed.providers?.["laya-decision"];
1067
- if (!p?.baseURL || !p?.apiKey)
1068
- return null;
1069
- return {
1070
- baseURL: p.baseURL,
1071
- apiKey: p.apiKey,
1072
- model: p.model ?? DEFAULT_MODEL
1073
- };
1075
+ const primary = providerNameForPurpose(purpose);
1076
+ const candidates = purpose === "goal" ? [primary, LAYA_DECISION_PROVIDER] : [primary];
1077
+ for (const name of candidates) {
1078
+ const p = parsed.providers?.[name];
1079
+ if (p?.baseURL && p?.apiKey) {
1080
+ return {
1081
+ baseURL: p.baseURL,
1082
+ apiKey: p.apiKey,
1083
+ model: p.model ?? (name === LAYA_GOAL_DECISION_PROVIDER ? DEFAULT_GOAL_MODEL : DEFAULT_MODEL)
1084
+ };
1085
+ }
1086
+ }
1087
+ return null;
1074
1088
  } catch {
1075
1089
  return null;
1076
1090
  }
@@ -2937,4 +2951,4 @@ __legacyDecorateClassTS([
2937
2951
  function createPerTaskGroupChatPlugin(config = {}) {
2938
2952
  return new PerTaskGroupChatPlugin(config);
2939
2953
  }
2940
- export { createAutoTaskPlugin, LAYA_REUSE_THRESHOLD, LayaDecisionPlugin, createLayaDecisionPlugin, WORKFLOW_EXTRACTOR_PROMPT, formatExtractorInput, formatExtractorInputFromDescription, parseExtractorOutput, TaskTagPlugin, extractPerTaskChatId, extractPerTaskChatName, NotifyType, LarkCliTaskNotifyHook, createLarkCliTaskNotifyHook, PerTaskGroupChatPlugin, createPerTaskGroupChatPlugin };
2954
+ export { createAutoTaskPlugin, pickGateConfidence, LayaDecisionPort, loadLayaAuthForPurpose, LAYA_REUSE_THRESHOLD, LayaDecisionPlugin, createLayaDecisionPlugin, WORKFLOW_EXTRACTOR_PROMPT, formatExtractorInput, formatExtractorInputFromDescription, parseExtractorOutput, TaskTagPlugin, extractPerTaskChatId, extractPerTaskChatName, NotifyType, LarkCliTaskNotifyHook, createLarkCliTaskNotifyHook, PerTaskGroupChatPlugin, createPerTaskGroupChatPlugin };
@@ -10,7 +10,7 @@ import {
10
10
  } from "./roy-agent-core-1pg0fepg.js";
11
11
  import {
12
12
  builtInPrompts
13
- } from "./roy-agent-core-6masakz4.js";
13
+ } from "./roy-agent-core-ma35jyd9.js";
14
14
  import {
15
15
  DEFAULT_SUBAGENT_PROMPT
16
16
  } from "./roy-agent-core-07fvy74y.js";
@@ -897,7 +897,7 @@ var WORKFLOW_COMPAT_ALIASES = new Set([
897
897
  "test-workflow-agent"
898
898
  ]);
899
899
  var DEFAULT_TIMEOUT = 1800000;
900
- var PROGRESS_INTERVAL = 1200000;
900
+ var PROGRESS_INTERVAL = 600000;
901
901
 
902
902
  class BackgroundTaskManager {
903
903
  env;
@@ -1113,7 +1113,7 @@ class BackgroundTaskManager {
1113
1113
  return null;
1114
1114
  }
1115
1115
  async createTask(options) {
1116
- const { parentSessionId, description, prompt, subagentType, timeout, similarTaskIds, existingSubSessionId, triggerEventId, model, fallbackModel } = options;
1116
+ const { parentSessionId, description, prompt, subagentType, timeout, similarTaskIds, existingSubSessionId, triggerEventId, model, fallbackModel, fallbackModels } = options;
1117
1117
  const taskIdGen = `process_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`;
1118
1118
  const sessionComponent = this.getSessionComponent();
1119
1119
  if (!sessionComponent) {
@@ -1213,6 +1213,7 @@ class BackgroundTaskManager {
1213
1213
  triggerEventId,
1214
1214
  model,
1215
1215
  fallbackModel,
1216
+ fallbackModels,
1216
1217
  replyChannel: replyChannelSnapshot
1217
1218
  };
1218
1219
  this.tasks.set(taskIdGen, task);
@@ -1627,6 +1628,7 @@ ${reuseSection.markdown}`;
1627
1628
  const invokeHandleQuery = () => {
1628
1629
  const inheritedModel = task?.model;
1629
1630
  const inheritedFallbackModel = task?.fallbackModel;
1631
+ const inheritedFallbackModels = task?.fallbackModels;
1630
1632
  return this.env.handle_query?.(delegateQuery, {
1631
1633
  sessionId,
1632
1634
  deniedTools: deniedTools.length > 0 ? deniedTools : undefined,
@@ -1634,7 +1636,8 @@ ${reuseSection.markdown}`;
1634
1636
  abort: signal,
1635
1637
  runId: capturedRunId,
1636
1638
  ...inheritedModel ? { model: inheritedModel } : {},
1637
- ...inheritedFallbackModel ? { fallbackModel: inheritedFallbackModel } : {}
1639
+ ...inheritedFallbackModel ? { fallbackModel: inheritedFallbackModel } : {},
1640
+ ...Array.isArray(inheritedFallbackModels) ? { fallbackModels: inheritedFallbackModels } : {}
1638
1641
  });
1639
1642
  };
1640
1643
  const wrappedPromise = processRegistry ? runWithProcessRegistryAsync(processRegistry, () => runWithBgTaskIdAsync(bgTaskId, () => invokeHandleQuery())) : runWithBgTaskIdAsync(bgTaskId, () => invokeHandleQuery());
@@ -1725,6 +1728,25 @@ ${reuseSection.markdown}`;
1725
1728
  getTask(taskId) {
1726
1729
  return this.tasks.get(taskId) ?? this.terminalTasks.get(taskId);
1727
1730
  }
1731
+ async getSubSessionActivity(bgProcessId) {
1732
+ const task = this.getTask(bgProcessId);
1733
+ if (!task)
1734
+ return;
1735
+ const sessionComponent = this.getSessionComponent();
1736
+ if (!sessionComponent?.get)
1737
+ return;
1738
+ try {
1739
+ const session = await sessionComponent.get(task.subSessionId);
1740
+ if (!session)
1741
+ return;
1742
+ return {
1743
+ messageCount: session.messageCount ?? 0,
1744
+ updatedAt: session.updatedAt ?? task.lastUpdatedAt ?? task.createdAt
1745
+ };
1746
+ } catch {
1747
+ return;
1748
+ }
1749
+ }
1728
1750
  listTasks() {
1729
1751
  return Array.from(this.tasks.values()).concat(Array.from(this.terminalTasks.values()));
1730
1752
  }
@@ -1810,14 +1832,33 @@ ${reuseSection.markdown}`;
1810
1832
  async forceStopAll() {
1811
1833
  return this.forceStopAllAcrossParents();
1812
1834
  }
1813
- async dispose() {
1835
+ async dispose(options) {
1814
1836
  for (const [, timer] of this.progressTimers) {
1815
1837
  try {
1816
1838
  clearInterval(timer);
1817
1839
  } catch {}
1818
1840
  }
1819
1841
  this.progressTimers.clear();
1842
+ const skipAbortRunningTasks = options?.skipAbortRunningTasks === true;
1843
+ const waitForRunningTasks = options?.waitForRunningTasks === true;
1844
+ if (waitForRunningTasks && skipAbortRunningTasks) {
1845
+ const waitStart = Date.now();
1846
+ const WAIT_TIMEOUT_MS = 10 * 60 * 1000;
1847
+ while (Date.now() - waitStart < WAIT_TIMEOUT_MS) {
1848
+ let running = 0;
1849
+ for (const [, t] of this.tasks) {
1850
+ if (t.status === "running" || t.status === "pending")
1851
+ running++;
1852
+ }
1853
+ if (running === 0)
1854
+ break;
1855
+ logger3.info(`[BackgroundTaskManager] dispose waiting for ${running} running task(s)`);
1856
+ await new Promise((resolve) => setTimeout(resolve, 200));
1857
+ }
1858
+ }
1820
1859
  for (const [, controller] of this.abortControllers) {
1860
+ if (skipAbortRunningTasks)
1861
+ break;
1821
1862
  try {
1822
1863
  controller.abort();
1823
1864
  } catch {}
@@ -2012,7 +2053,7 @@ No need to poll — just wait for the notification and use \`task_get\` with the
2012
2053
  };
2013
2054
  await globalHookManager.execute(TaskHookPoints.DELEGATE_BEFORE, delegateCtx, { sessionId: parentSessionId });
2014
2055
  promptWithTaskInfo = delegateCtx.prompt;
2015
- return await handleBackgroundTask(backgroundTaskManager, parentSessionId, description, promptWithTaskInfo, delegateCtx.subagentType, delegateCtx.timeout, similar_task_ids, params.sub_session_id, resolvedTriggerEventId, ctx.model, ctx.fallbackModel);
2056
+ return await handleBackgroundTask(backgroundTaskManager, parentSessionId, description, promptWithTaskInfo, delegateCtx.subagentType, delegateCtx.timeout, similar_task_ids, params.sub_session_id, resolvedTriggerEventId, ctx.model, ctx.fallbackModel, ctx.fallbackModels);
2016
2057
  }
2017
2058
  };
2018
2059
  tool.execute = wrapFunction(tool.execute, "delegate.execute", {
@@ -2056,7 +2097,7 @@ function _captureReplyChannelSnapshot() {
2056
2097
  };
2057
2098
  }
2058
2099
  var captureReplyChannelSnapshot = wrapFunction(_captureReplyChannelSnapshot, "delegate.captureReplyChannel", { recordParams: false, recordResult: true, recordError: true });
2059
- async function handleBackgroundTask(backgroundTaskManager, parentSessionId, description, prompt, subagentType, timeout, similarTaskIds, existingSubSessionId, triggerEventId, model, fallbackModel) {
2100
+ async function handleBackgroundTask(backgroundTaskManager, parentSessionId, description, prompt, subagentType, timeout, similarTaskIds, existingSubSessionId, triggerEventId, model, fallbackModel, fallbackModels) {
2060
2101
  const startTime = Date.now();
2061
2102
  try {
2062
2103
  let actualSubagentType = subagentType;
@@ -2084,7 +2125,8 @@ async function handleBackgroundTask(backgroundTaskManager, parentSessionId, desc
2084
2125
  existingSubSessionId,
2085
2126
  triggerEventId,
2086
2127
  model,
2087
- fallbackModel
2128
+ fallbackModel,
2129
+ fallbackModels
2088
2130
  });
2089
2131
  const output = [
2090
2132
  `✅ Background task accepted`,
@@ -2117,4 +2159,4 @@ async function handleBackgroundTask(backgroundTaskManager, parentSessionId, desc
2117
2159
  };
2118
2160
  }
2119
2161
  }
2120
- export { BackgroundTaskEventTypes, builtInSubAgents, getSubAgentSpec, DelegateToolParameters, buildEntryAgentPrompt, WORKFLOW_COMPAT_ALIASES, DEFAULT_TIMEOUT, BackgroundTaskManager, createDelegateTool, deriveTriggerEventIdFromEnvContext, captureReplyChannelSnapshot, handleBackgroundTask };
2162
+ export { BackgroundTaskEventTypes, builtInSubAgents, getSubAgentSpec, DelegateToolParameters, buildEntryAgentPrompt, WORKFLOW_COMPAT_ALIASES, DEFAULT_TIMEOUT, PROGRESS_INTERVAL, BackgroundTaskManager, createDelegateTool, deriveTriggerEventIdFromEnvContext, captureReplyChannelSnapshot, handleBackgroundTask };
@@ -2,6 +2,11 @@ import {
2
2
  NEW_TASK_ID_HASH_PLACEHOLDER,
3
3
  NEW_TASK_ID_PLACEHOLDER
4
4
  } from "./roy-agent-core-tr3ep81v.js";
5
+ import {
6
+ TASK_COMPLETE_ITEM_BY_ITEM,
7
+ TASK_COMPLETE_VERIFY_DUTY,
8
+ TASK_COMPLETION_HINT_ANCHOR
9
+ } from "./roy-agent-core-6m4bw8jy.js";
5
10
  import {
6
11
  getEnvContextOrEmpty,
7
12
  init_env_context,
@@ -38,7 +43,7 @@ var CreateTaskToolSchema = z.object({
38
43
  description: z.string().optional().describe("Task description"),
39
44
  priority: z.enum(["low", "medium", "high"]).optional().describe("Task priority"),
40
45
  type: z.enum(["normal", "cycle", "longterm"]).optional().default("normal").describe("Task type. normal: 常规任务,需要跟踪完成. cycle: 周期任务(如每周发周报),无需 complete,定期执行. longterm: 长期任务/愿景目标(如提升公司影响力),难以一次性完成,持续跟进"),
41
- goals_and_expected_deliverables: z.string().optional().describe("Goals and expected deliverables"),
46
+ goals_and_expected_deliverables: z.string().optional().describe("Goals and expected deliverables. " + "⚠️ 写成可逐条验收的编号子项(1. … 2. … 3. …),每条是一个可判定的交付/动作" + "(文件断言:文件存在/大小/内容;进程断言:命令 exit code/stdout;" + "测试断言:pass/fail 计数;状态断言:版本号/HTTP 200/状态字段)。" + "避免一句话模糊目标(如「做好 X」「完成调研」)——" + "任务完成时 completion hook 会按这些子项逐条对照操作记录验收。"),
42
47
  due_date: z.string().optional().describe("Due date (ISO format)"),
43
48
  tags: z.array(z.string()).optional().describe("Task tags (for search, stored in task.tags JSON field)"),
44
49
  project_path: z.string().default("unknown").describe("Project root path for task positioning. " + "Used to locate the project space for this task. " + "Use 'unknown' if unknown. (Defaults to 'unknown'.)"),
@@ -84,7 +89,7 @@ var UpdateTaskToolSchema = withLegacyTaskId({
84
89
  type: z.enum(["normal", "cycle", "longterm"]).optional().describe("Task type"),
85
90
  progress: z.number().min(0).max(100).optional(),
86
91
  current_status: z.string().optional(),
87
- goals_and_expected_deliverables: z.string().optional(),
92
+ goals_and_expected_deliverables: z.string().optional().describe("Goals and expected deliverables. " + "⚠️ 追加/修改时同样写成可逐条验收的编号子项(1. … 2. … 3. …)," + "每条是一个可判定的交付/动作(文件断言:文件存在/大小/内容;" + "进程断言:命令 exit code/stdout;测试断言:pass/fail 计数;" + "状态断言:版本号/HTTP 200/状态字段)。" + "避免一句话模糊目标(如「做好 X」「完成调研」)——" + "任务完成时 completion hook 会按这些子项逐条对照操作记录验收。" + "(Task #4833 — 与 task_create 的 goals 规则同源)"),
88
93
  due_date: z.string().optional(),
89
94
  tags: z.array(z.string()).optional(),
90
95
  project_path: z.string().optional().describe("Project path, identifies which project this task belongs to"),
@@ -316,6 +321,37 @@ function createTaskTool(taskComponent) {
316
321
  - Same-theme tasks cluster under one parent
317
322
  - Long-tail/stranded tasks should be archived or merged
318
323
 
324
+ ## ⚠️ Goals 子项拆分规则 (Task #4791)
325
+
326
+ \`goals_and_expected_deliverables\` **MUST** be written as
327
+ **numbered sub-items** (编号子项) that can each be verified
328
+ one by one (逐条验收) — for \`type=normal\` tasks:
329
+
330
+ 1. 实现 X(含文件路径)
331
+ 2. 测试 Y 覆盖 Z 场景(pass/fail 计数)
332
+ 3. 文档 W 落盘到 <path>
333
+
334
+ ⚠️ For \`type=longterm\` / \`type=cycle\` tasks there is no
335
+ per-item completion acceptance(不走逐条 complete 验收), so the
336
+ MUST above does not apply; for those, it is **strongly recommended**
337
+ (建议) to split goals into staged, individually checkable sub-items
338
+ (阶段性可判子项) instead of one vague sentence.
339
+
340
+ Why: when the task is completed, the completion hook
341
+ (task:after.complete) judges the whole goal against the
342
+ operation-record chain, using each sub-item as fine-grained
343
+ evidence (逐条对照的依据). Vague one-line goals (一句话模糊目标,
344
+ 如「做好 X」「完成调研」) give the judge nothing to check and
345
+ make a completion hint much more likely.
346
+
347
+ Reference the four atomic assertion forms (Task #4768
348
+ 四原子形态) when writing each sub-item:
349
+
350
+ - 文件断言 (file assertion): file exists / size / content grep
351
+ - 进程断言 (process assertion): command exit code / stdout match
352
+ - 测试断言 (test assertion): pass/fail counts, no NEW fail
353
+ - 状态断言 (state assertion): version number / HTTP 200 / status field
354
+
319
355
  ## ⚠️ Spatial + Temporal Context (Task #2325)
320
356
 
321
357
  A task MUST carry spatio-temporal context so it survives task-handoff
@@ -917,8 +953,28 @@ Mark a task as completed (progress=100, status=completed). Creates an operation
917
953
 
918
954
  1. Read the task's \`goal\` (description) and \`delivery\` (goals_and_expected_deliverables) fields.
919
955
  - Use \`task_get(task_id=<id>)\` to fetch the full task record.
920
- 2. **Validate** whether the goal and delivery match what you actually delivered.
921
- - If mismatch, either: (a) extend the task via \`task_update\`, or (b) honestly note the discrepancy in your final report.
956
+ 2. **Validate whether each goal sub-item was achieved** (${TASK_COMPLETE_ITEM_BY_ITEM}).
957
+ - Goals should be numbered sub-items (编号子项, see task_create rules). Check
958
+ each sub-item against the operation-record chain (操作记录链) and the
959
+ deliverable evidence (context.deliverable_storage_path).
960
+ - The completion hook (task:after.complete) judges the whole goal vs.
961
+ deliverables; numbered sub-items give it finer-grained evidence to
962
+ reason with (逐条对照的证据粒度). If it detects that the goal or
963
+ deliverables appear unmet, the task context will carry a
964
+ \`[${TASK_COMPLETION_HINT_ANCHOR}]\`.
965
+ - **If you receive a [${TASK_COMPLETION_HINT_ANCHOR}]**, you MUST verify it
966
+ (${TASK_COMPLETE_VERIFY_DUTY}): re-read the task via \`task_get\`, then do exactly one of:
967
+ (a) 确认未完成 → \`task_update(task_id=<id>, status=todo)\` reopen and continue; or
968
+ (b) 确认已完成 → \`task_operation_create\` (milestone_type=decision,
969
+ milestone_title=<一行结论>) state why the sub-items are actually
970
+ met (说明理由).
971
+ Never ignore it (不允许无视).
972
+ - If the goal and delivery mismatch **without** a hint, either:
973
+ (a) extend the task via \`task_update\`, or
974
+ (b) record the discrepancy via \`task_operation_create\`
975
+ (milestone_type=decision) — the reason **must** land in the
976
+ operation record (必须落到操作记录里), not merely a line in
977
+ your final report (不能只在报告里带一句).
922
978
  3. **Provide \`deliverable_storage_path\`** parameter with the deliverable's address:
923
979
  - Feishu doc URL (e.g., https://my.feishu.cn/docx/abc)
924
980
  - Local file path (e.g., /tmp/report.md)