@xdxer/dingtalk-agent 0.1.5-beta.1 → 0.1.5-beta.11

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 (183) hide show
  1. package/CHANGELOG.md +209 -0
  2. package/README.en.md +99 -66
  3. package/README.md +99 -66
  4. package/dist/bin/dingtalk-agent.js +735 -149
  5. package/dist/bin/dingtalk-agent.js.map +1 -1
  6. package/dist/src/actions.js +3 -2
  7. package/dist/src/actions.js.map +1 -1
  8. package/dist/src/agent-audit.js +202 -85
  9. package/dist/src/agent-audit.js.map +1 -1
  10. package/dist/src/agent-bindings.js +4 -8
  11. package/dist/src/agent-bindings.js.map +1 -1
  12. package/dist/src/agent-definition.js +7 -3
  13. package/dist/src/agent-definition.js.map +1 -1
  14. package/dist/src/agent-enhance.js +51 -32
  15. package/dist/src/agent-enhance.js.map +1 -1
  16. package/dist/src/agent-platform.js +3 -3
  17. package/dist/src/agent-platform.js.map +1 -1
  18. package/dist/src/bootstrap.js +6 -2
  19. package/dist/src/bootstrap.js.map +1 -1
  20. package/dist/src/development-workspace.js +210 -34
  21. package/dist/src/development-workspace.js.map +1 -1
  22. package/dist/src/doctor.js +65 -9
  23. package/dist/src/doctor.js.map +1 -1
  24. package/dist/src/dws.js +67 -3
  25. package/dist/src/dws.js.map +1 -1
  26. package/dist/src/init.js +2 -1
  27. package/dist/src/init.js.map +1 -1
  28. package/dist/src/map.js +157 -0
  29. package/dist/src/map.js.map +1 -0
  30. package/dist/src/memory/candidates.js +8 -0
  31. package/dist/src/memory/candidates.js.map +1 -1
  32. package/dist/src/memory/noop-receipt.js +320 -0
  33. package/dist/src/memory/noop-receipt.js.map +1 -0
  34. package/dist/src/memory/operational.js +27 -3
  35. package/dist/src/memory/operational.js.map +1 -1
  36. package/dist/src/memory/remote-state.js +2 -1
  37. package/dist/src/memory/remote-state.js.map +1 -1
  38. package/dist/src/multica-deploy.js +692 -125
  39. package/dist/src/multica-deploy.js.map +1 -1
  40. package/dist/src/multica-provider.js +303 -25
  41. package/dist/src/multica-provider.js.map +1 -1
  42. package/dist/src/multica-runtime-vocabulary.js +110 -0
  43. package/dist/src/multica-runtime-vocabulary.js.map +1 -0
  44. package/dist/src/opencode-evals.js +6 -6
  45. package/dist/src/opencode-evals.js.map +1 -1
  46. package/dist/src/opencode-provider.js +21 -7
  47. package/dist/src/opencode-provider.js.map +1 -1
  48. package/dist/src/opencode-workspace.js +3 -3
  49. package/dist/src/opencode-workspace.js.map +1 -1
  50. package/dist/src/personal-event-evals.js +4 -2
  51. package/dist/src/personal-event-evals.js.map +1 -1
  52. package/dist/src/promotion.js +2 -1
  53. package/dist/src/promotion.js.map +1 -1
  54. package/dist/src/remote-semantic-state-live-evals.js +14 -8
  55. package/dist/src/remote-semantic-state-live-evals.js.map +1 -1
  56. package/dist/src/remote-state-evals.js +2 -2
  57. package/dist/src/remote-state-evals.js.map +1 -1
  58. package/dist/src/robot-evals.js +3 -3
  59. package/dist/src/robot-evals.js.map +1 -1
  60. package/dist/src/schedule-plan.js +380 -0
  61. package/dist/src/schedule-plan.js.map +1 -0
  62. package/dist/src/sessions.js +1 -1
  63. package/dist/src/sessions.js.map +1 -1
  64. package/dist/src/skill-manager.js +145 -13
  65. package/dist/src/skill-manager.js.map +1 -1
  66. package/dist/src/skills.js +2 -0
  67. package/dist/src/skills.js.map +1 -1
  68. package/dist/src/state-dir.js +120 -0
  69. package/dist/src/state-dir.js.map +1 -0
  70. package/dist/src/tui.js +369 -0
  71. package/dist/src/tui.js.map +1 -0
  72. package/dist/src/upgrade.js +113 -33
  73. package/dist/src/upgrade.js.map +1 -1
  74. package/dist/src/waits.js +2 -1
  75. package/dist/src/waits.js.map +1 -1
  76. package/dist/src/workspace.js +12 -7
  77. package/dist/src/workspace.js.map +1 -1
  78. package/docs/AGENT-IN-PRODUCTION.md +255 -0
  79. package/docs/ARCHITECTURE.md +165 -31
  80. package/docs/INSTALLATION.md +7 -7
  81. package/docs/PLATFORM-GUARDRAILS.md +188 -0
  82. package/docs/PRIOR-ART.md +4 -0
  83. package/docs/SELF-TEST.md +4 -4
  84. package/docs/architecture/agent-platform-connection-layer.svg +120 -0
  85. package/docs/architecture/digital-employee-composition.svg +92 -0
  86. package/docs/architecture/dingtalk-agent-architecture.svg +125 -0
  87. package/docs/assets/digital-employee-at-work.svg +77 -0
  88. package/docs/schemas/multica-deployment-plan.schema.json +3 -1
  89. package/docs/schemas/multica-deployment-receipt.schema.json +17 -3
  90. package/docs/schemas/multica-deployment-status.schema.json +6 -2
  91. package/docs/schemas/multica-workspace-inspection.schema.json +16 -0
  92. package/docs/schemas/multica-workspace-run-plan.schema.json +31 -0
  93. package/docs/schemas/multica-workspace-run.schema.json +161 -0
  94. package/docs/schemas/multica-workspace-status.schema.json +2 -0
  95. package/docs/schemas/project.schema.json +54 -3
  96. package/docs/schemas/workspace-scaffold.schema.json +38 -0
  97. package/examples/agents/README.md +10 -8
  98. package/examples/agents/fde-coach/AGENTS.md +2 -34
  99. package/examples/agents/fde-coach/agent/AGENTS.md +35 -0
  100. package/examples/agents/fde-coach/agent.bindings.json +10 -0
  101. package/examples/agents/release-manager/AGENTS.md +2 -34
  102. package/examples/agents/release-manager/agent/AGENTS.md +35 -0
  103. package/examples/agents/release-manager/agent.bindings.json +10 -0
  104. package/lab/agent-eval/catalog.json +5 -5
  105. package/lab/agent-eval/classic-failures.json +4 -4
  106. package/lab/agent-eval/completion-gate-regression.json +9 -9
  107. package/lab/agent-eval/personal-event-live.example.json +3 -3
  108. package/lab/agent-eval/remote-semantic-state-live.example.json +1 -1
  109. package/lab/agent-eval/workspace/opencode.json +2 -2
  110. package/lab/project-workspace/fake-multica-provider.mjs +171 -17
  111. package/lab/project-workspace/multica-deploy.fixture.json +2 -2
  112. package/lab/project-workspace/multica-readonly.fixture.json +4 -16
  113. package/lab/project-workspace/opencode-provider-suite.json +3 -3
  114. package/lab/project-workspace/project.fixture.json +2 -6
  115. package/lab/robot-eval/suite.json +1 -1
  116. package/lab/robot-eval/workspace/AGENTS.md +1 -1
  117. package/lab/robot-eval/workspace/opencode.json +2 -2
  118. package/lab/schemas/personal-event-eval.schema.json +1 -1
  119. package/package.json +12 -9
  120. package/skills/README.md +10 -8
  121. package/skills/core/{dingtalk-agent-compose → dta-agent-compose}/SKILL.md +48 -23
  122. package/skills/core/{dingtalk-agent-compose → dta-agent-compose}/assets/AGENTS.template.md +1 -1
  123. package/skills/core/dta-agent-compose/assets/REPOSITORY.template.md +10 -0
  124. package/skills/core/{dingtalk-agent-compose → dta-agent-compose}/assets/agent.bindings.dingtalk-doc.template.json +2 -2
  125. package/skills/core/{dingtalk-agent-compose → dta-agent-compose}/assets/agent.bindings.local.template.json +2 -2
  126. package/skills/core/{dingtalk-agent-compose → dta-agent-compose}/assets/hosts/opencode/opencode.template.json +3 -2
  127. package/skills/core/{dingtalk-agent-compose → dta-agent-compose}/evals/evals.json +4 -4
  128. package/skills/core/{dingtalk-agent-compose → dta-agent-compose}/references/agent-definition-contract.md +7 -7
  129. package/skills/core/dta-agent-compose/references/drive-and-schedules.md +166 -0
  130. package/skills/core/{dingtalk-agent-compose → dta-agent-compose}/references/host-loading-contract.md +11 -13
  131. package/skills/core/{dingtalk-agent-compose → dta-agent-compose}/references/hosts/claude-code.md +13 -12
  132. package/skills/core/{dingtalk-agent-compose → dta-agent-compose}/references/hosts/opencode.md +14 -13
  133. package/skills/core/{dingtalk-agent-eval → dta-agent-eval}/SKILL.md +2 -2
  134. package/skills/core/{dingtalk-agent-eval → dta-agent-eval}/assets/eval-catalog.template.json +1 -1
  135. package/skills/core/{dingtalk-agent-eval → dta-agent-eval}/evals/evals.json +1 -1
  136. package/skills/core/{dingtalk-agent-eval → dta-agent-eval}/references/eval-topology.md +3 -3
  137. package/skills/core/{dingtalk-agent-eval → dta-agent-eval}/references/interactive-debug-channels.md +10 -4
  138. package/skills/core/{dingtalk-basic-behavior → dta-basic-behavior}/SKILL.md +21 -5
  139. package/skills/core/dta-basic-behavior/references/event-to-behavior.md +38 -0
  140. package/skills/core/{dingtalk-basic-behavior → dta-basic-behavior}/references/memory-and-evolution.md +3 -1
  141. package/skills/core/dta-basic-behavior/references/perception-and-gates.md +87 -0
  142. package/skills/core/{dingtalk-basic-behavior → dta-basic-behavior}/references/risk-authority-and-privacy.md +12 -0
  143. package/skills/core/{dingtalk-basic-behavior → dta-basic-behavior}/references/truth-and-recovery.md +4 -2
  144. package/skills/core/dta-people-group-memory/COMPLETENESS.md +36 -0
  145. package/skills/core/dta-people-group-memory/SKILL.md +69 -0
  146. package/skills/core/dta-people-group-memory/references/adapters.md +273 -0
  147. package/skills/core/dta-people-group-memory/references/assembly-guidance.md +40 -0
  148. package/skills/core/dta-people-group-memory/references/binding.md +110 -0
  149. package/skills/core/dta-people-group-memory/references/cold-start.md +70 -0
  150. package/skills/core/dta-people-group-memory/references/config-binding.md +89 -0
  151. package/skills/core/dta-people-group-memory/references/consent-and-visibility.md +83 -0
  152. package/skills/core/dta-people-group-memory/references/consolidation.md +162 -0
  153. package/skills/core/dta-people-group-memory/references/event-ingest.md +103 -0
  154. package/skills/core/dta-people-group-memory/references/guided-setup.md +70 -0
  155. package/skills/core/dta-people-group-memory/references/model.md +148 -0
  156. package/skills/core/dta-people-group-memory/references/storage-port.md +107 -0
  157. package/skills/platforms/deap/PLATFORM.md +30 -1
  158. package/skills/platforms/multica-dingtalk/PLATFORM.md +34 -8
  159. package/skills/platforms/multica-dingtalk/{dingtalk-agent-deploy-multica → dta-deploy-multica}/SKILL.md +10 -8
  160. package/skills/platforms/multica-dingtalk/dta-deploy-multica/references/multica-deployment-contract.md +67 -0
  161. package/skills/platforms/multica-dingtalk/{multica-external → dta-ops-multica}/SKILL.md +81 -11
  162. package/skills/platforms/multica-dingtalk/{multica-external → dta-ops-multica}/scripts/bootstrap.sh +2 -2
  163. package/skills/platforms/multica-dingtalk/{multica-external → dta-ops-multica}/scripts/multica_ext.py +264 -16
  164. package/docs/assets/agent-delivery-lifecycle.svg +0 -103
  165. package/skills/core/dingtalk-basic-behavior/references/event-to-behavior.md +0 -24
  166. package/skills/core/dingtalk-basic-behavior/references/perception-and-gates.md +0 -28
  167. package/skills/platforms/multica-dingtalk/dingtalk-agent-boot-multica/SKILL.md +0 -40
  168. package/skills/platforms/multica-dingtalk/dingtalk-agent-deploy-multica/references/multica-deployment-contract.md +0 -49
  169. /package/examples/agents/fde-coach/{skills → agent/skills}/fde-coach/SKILL.md +0 -0
  170. /package/examples/agents/release-manager/{skills → agent/skills}/release-manager/SKILL.md +0 -0
  171. /package/skills/core/{dingtalk-agent-compose → dta-agent-compose}/assets/role-skill.template.md +0 -0
  172. /package/skills/core/{dingtalk-agent-compose → dta-agent-compose}/references/storage-routing.md +0 -0
  173. /package/skills/core/{dingtalk-agent-eval → dta-agent-eval}/references/evidence-contract.md +0 -0
  174. /package/skills/core/{dingtalk-agent-eval → dta-agent-eval}/references/failure-to-case.md +0 -0
  175. /package/skills/core/{dingtalk-agent-eval → dta-agent-eval}/references/local-connector-smoke.md +0 -0
  176. /package/skills/core/{dingtalk-agent-eval → dta-agent-eval}/references/scenario-taxonomy.md +0 -0
  177. /package/skills/core/{dingtalk-agent-eval → dta-agent-eval}/references/storage-modes.md +0 -0
  178. /package/skills/core/{dingtalk-basic-behavior → dta-basic-behavior}/assets/memory-candidate-proposal.json +0 -0
  179. /package/skills/core/{dingtalk-basic-behavior → dta-basic-behavior}/assets/task-checkpoint.json +0 -0
  180. /package/skills/core/{dingtalk-basic-behavior → dta-basic-behavior}/references/action-contract.md +0 -0
  181. /package/skills/core/{dingtalk-basic-behavior → dta-basic-behavior}/references/runtime-modes.md +0 -0
  182. /package/skills/core/{dingtalk-basic-behavior → dta-basic-behavior}/references/task-lifecycle.md +0 -0
  183. /package/skills/platforms/multica-dingtalk/{dingtalk-agent-deploy-multica → dta-deploy-multica}/references/promotion-observation-contract.md +0 -0
@@ -0,0 +1,380 @@
1
+ /**
2
+ * Schedule reconcile plan — dta's half of the driving contract.
3
+ *
4
+ * dta owns: read the declared schedules (dingtalk-agent.json#schedules), validate
5
+ * them, and map them to a provider-specific reconcile plan. dta does NOT fire or
6
+ * live-write triggers — that stays with the platform ops path (see ARCHITECTURE
7
+ * §3.1, "触发器不属于 dingtalk-agent 核心执行"). The plan is the authoritative,
8
+ * provider-mapped desired state the ops skill reconciles against live.
9
+ *
10
+ * Pure: no network, no side effects — safe to run in CI and fully testable.
11
+ */
12
+ import { PREDICATE_ID, inspectAgentProject, multicaAgentIdSource, } from './development-workspace.js';
13
+ /**
14
+ * completion 的 predicate 引用形态(issue #24):`predicate:<id>` 指向 manifest
15
+ * `predicates` 注册表里唯一的判据。声明侧与运行路径(autopilot description 里的
16
+ * `[dta-completion-predicate] <id>` 标记、Receipt)引用同一 ID——两个求值方无法
17
+ * 静默分叉(真事故:两套手写判据分叉 30 小时无报警)。
18
+ */
19
+ export const COMPLETION_PREDICATE_PREFIX = 'predicate:';
20
+ /**
21
+ * completion 的三态解析(issue #39):自由文本 / 合法引用 / malformed 引用。
22
+ * 只要出现 `predicate:` 前缀就已经表达了「我在引用注册表」的意图——id 为空或
23
+ * 格式非法时绝不能降级成 legacy 自由文本(那是 fail-open),必须单独成态去阻断。
24
+ */
25
+ export function parseCompletionRef(completion) {
26
+ const text = (completion || '').trim();
27
+ if (!text.startsWith(COMPLETION_PREDICATE_PREFIX))
28
+ return { kind: 'free-text' };
29
+ const id = text.slice(COMPLETION_PREDICATE_PREFIX.length).trim();
30
+ return PREDICATE_ID.test(id) ? { kind: 'ref', id } : { kind: 'malformed', raw: text };
31
+ }
32
+ export function completionPredicateId(completion) {
33
+ const parsed = parseCompletionRef(completion);
34
+ return parsed.kind === 'ref' ? parsed.id : '';
35
+ }
36
+ /**
37
+ * autopilot description 里 predicate 标记的【唯一】parser(issue #40)。
38
+ *
39
+ * 合同:标记是独立一行 `[dta-completion-predicate] <kebab-id>`(写入侧见
40
+ * multicaApplyCommand)。所有回读方——TS 的对账、Python 的运行时解析
41
+ * (multica_ext.py 的 parse_predicate_marker 是同一正则的镜像,改这里必须同步改
42
+ * 那边)——都必须走这个定义,不许各自手写正则:两套 parser 静默分叉正是
43
+ * 注册表要消灭的那类事故。
44
+ *
45
+ * 多枚标记不一致返回 conflict 而不是挑一个:挑选规则(首个/末个)就是第二份
46
+ * 隐式合同,回读方猜错方向时漂移会被静默放过。
47
+ */
48
+ export const COMPLETION_PREDICATE_MARKER = /^\[dta-completion-predicate\] ([a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?)$/gm;
49
+ export function parseCompletionPredicateMarker(description) {
50
+ // malformed 独立成态(issue #45,与 #39 同一课):行首出现 marker 前缀就已表达
51
+ // 「我在标注判据」的意图,id 空/非法时折成 none 会静默跳过整个回读校验——fail-open。
52
+ const ids = [];
53
+ const raws = [];
54
+ for (const line of (description || '').split(/\r?\n/)) {
55
+ if (!line.startsWith('[dta-completion-predicate]'))
56
+ continue;
57
+ const m = line.match(/^\[dta-completion-predicate\] ([a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?)$/);
58
+ if (m)
59
+ ids.push(m[1]);
60
+ else
61
+ raws.push(line);
62
+ }
63
+ if (raws.length)
64
+ return { kind: 'malformed', raws };
65
+ if (!ids.length)
66
+ return { kind: 'none' };
67
+ const unique = [...new Set(ids)];
68
+ return unique.length === 1 ? { kind: 'id', id: unique[0] } : { kind: 'conflict', ids: unique };
69
+ }
70
+ // manifest 级的静态体检,show 和 plan 共用一份,避免两处逻辑漂移。
71
+ export function analyzeSchedules(declared, predicates) {
72
+ const collidingPairs = [];
73
+ const unverifiedPairs = [];
74
+ for (let i = 0; i < declared.length; i++) {
75
+ for (let j = i + 1; j < declared.length; j++) {
76
+ const pair = `${declared[i].name}×${declared[j].name}`;
77
+ // 跨时区:本地无 tz 换算 → 判不准是否同刻 → unverified(不静默当 distinct)。
78
+ if (declared[i].timezone !== declared[j].timezone) {
79
+ unverifiedPairs.push(pair);
80
+ continue;
81
+ }
82
+ const r = firesCollide(declared[i].cron, declared[j].cron);
83
+ if (r === 'collide')
84
+ collidingPairs.push(pair);
85
+ else if (r === 'unverified')
86
+ unverifiedPairs.push(pair);
87
+ }
88
+ }
89
+ const missingCompletion = declared.filter((s) => !s.completion?.trim()).map((s) => s.name);
90
+ // predicate 引用体检(issue #24):引用必须解析到注册表;一旦项目声明了注册表,
91
+ // 自由文本判据整体阻断——「写了一句话」不是判据,注入一句「跑过了」就能过。
92
+ // 未声明注册表的存量项目维持现状,只列 unregistered 清单提示迁移(渐进门禁)。
93
+ // 「已声明」看的是 manifest 里 predicates 键存不存在,不是注册表空不空:
94
+ // 显式空注册表 = 项目已进入引用模式,自由文本照样阻断(issue #39)。
95
+ const registryDeclared = predicates !== undefined;
96
+ const registry = predicates || {};
97
+ const completionRefIssues = [];
98
+ const unregisteredCompletion = [];
99
+ for (const schedule of declared) {
100
+ const completion = (schedule.completion || '').trim();
101
+ if (!completion)
102
+ continue; // missingCompletion 已覆盖
103
+ const parsed = parseCompletionRef(completion);
104
+ if (parsed.kind === 'malformed') {
105
+ // 有 predicate: 前缀但 id 空/非法:引用意图已表达,降级成自由文本就是 fail-open。
106
+ completionRefIssues.push(`${schedule.name}: completion 的 predicate 引用格式非法("${parsed.raw}")——` +
107
+ '需要 predicate:<kebab-case-id>');
108
+ }
109
+ else if (parsed.kind === 'ref') {
110
+ if (!registry[parsed.id]) {
111
+ completionRefIssues.push(`${schedule.name}: completion 引用未注册的 predicate:${parsed.id}`);
112
+ }
113
+ }
114
+ else if (registryDeclared) {
115
+ completionRefIssues.push(`${schedule.name}: 已声明 predicates 注册表,completion 必须是 predicate:<id> 引用` +
116
+ '(自由文本判据可被一句话注入绕过)');
117
+ }
118
+ else {
119
+ unregisteredCompletion.push(schedule.name);
120
+ }
121
+ }
122
+ return {
123
+ collidingPairs, unverifiedPairs, missingCompletion,
124
+ completionRefIssues, unregisteredCompletion,
125
+ };
126
+ }
127
+ /**
128
+ * 阻断部署的问题清单(空 = 可部署)。三类都算阻断,**包括 unverified**。
129
+ *
130
+ * unverified 曾经只告警,理由是"本地证明不了 ≠ 有问题"。这条理由错在赌的方向:
131
+ * `0 9 * * *`@Asia/Shanghai 与 `0 1 * * *`@UTC 是**同一时刻**,静态比对看不出来,
132
+ * 于是两条各建一个 autopilot、每天把同一条消息重复发给第三方——正是本守卫要防的那个伤害。
133
+ * 证不出不撞车就当撞车(fail closed):挡住是一次响亮且可恢复的配置改动,放过是一次静默且发出去收不回的重复外发。
134
+ *
135
+ * 出路有两条,都不需要改这里:把两条节律写成同一个 timezone(对齐后走真实 occurrence 比对,
136
+ * 判得出不撞就自动放行),或换掉本地读不懂的 cron 扩展语法。
137
+ *
138
+ * 判据只此一份:CLI 的 show 和 plan 都调它。此前两处各自手写 exit 条件,结果都漏了 unverified——
139
+ * 抽出 analyzeSchedules 是为了防判据分叉,退出判据自己却分叉了。
140
+ */
141
+ export function scheduleBlockers(a) {
142
+ return [
143
+ ...a.collidingPairs, ...a.unverifiedPairs, ...a.missingCompletion,
144
+ ...(a.completionRefIssues || []),
145
+ ];
146
+ }
147
+ // ephemeral = run-only (no work item); work-item = one work item per fire.
148
+ const MULTICA_MODE = {
149
+ ephemeral: 'run_only',
150
+ 'work-item': 'create_issue',
151
+ };
152
+ // 只有 work-item 每次 fire 建 issue、平台才有 token 用量可查。
153
+ const isMetered = (mode) => mode === 'work-item';
154
+ // 五个字段的上界(分 时 日 月 周)。
155
+ const CRON_FIELD_MAX = [59, 23, 31, 12, 7];
156
+ // 名字字面量 → 数字(robfig/cron v3 支持,Multica 实测用 MON-FRI;不支持就会漏掉真双发)。
157
+ const DOW_NAMES = { SUN: 0, MON: 1, TUE: 2, WED: 3, THU: 4, FRI: 5, SAT: 6 };
158
+ const MON_NAMES = {
159
+ JAN: 1, FEB: 2, MAR: 3, APR: 4, MAY: 5, JUN: 6, JUL: 7, AUG: 8, SEP: 9, OCT: 10, NOV: 11, DEC: 12,
160
+ };
161
+ /**
162
+ * 把一个 cron 字段展开成它命中的整数集合。支持 `*`、列表、范围、步长,以及 dow/月的名字字面量。
163
+ * 解析不出(`L`/`W`/`#`/`@` 等扩展语法)时返回 null —— 调用方据此判 unverified,不假装读懂了。
164
+ */
165
+ function expandCronField(field, max, names) {
166
+ const out = new Set();
167
+ const num = (t) => {
168
+ const up = t.toUpperCase();
169
+ if (names && up in names)
170
+ return names[up];
171
+ return /^\d+$/.test(t) ? parseInt(t, 10) : NaN;
172
+ };
173
+ for (const part of field.split(',')) {
174
+ const [range, stepRaw] = part.split('/');
175
+ const step = stepRaw ? parseInt(stepRaw, 10) : 1;
176
+ let lo = 0;
177
+ let hi = max;
178
+ if (range !== '*') {
179
+ const [a, b] = range.split('-');
180
+ lo = num(a);
181
+ // robfig getRange 的特判:**单个数字 + 步长时上界拉到 max**——`5/15` 等价 `5-59/15` = {5,20,35,50},
182
+ // 不是只有 {5}。当成 {5} 会漏掉 20/35/50,把真撞车判成 distinct → 正是本守卫要防的重复外发。
183
+ hi = b === undefined ? (stepRaw ? max : lo) : num(b);
184
+ }
185
+ if (Number.isNaN(lo) || Number.isNaN(hi) || Number.isNaN(step) || step < 1)
186
+ return null;
187
+ for (let v = lo; v <= hi && v <= max; v += step)
188
+ out.add(v === 7 && max === 7 ? 0 : v);
189
+ }
190
+ // 空集合 = 越界(`70`)或倒序区间(`5-2`),是【没读懂】不是【永不 fire】。
191
+ // 返回空集会让 setsIntersect 判 false → distinct(fail-open);返回 null → unverified,交给人核。
192
+ return out.size === 0 ? null : out;
193
+ }
194
+ function setsIntersect(a, b) {
195
+ for (const v of a)
196
+ if (b.has(v))
197
+ return true;
198
+ return false;
199
+ }
200
+ function parseCron(cron) {
201
+ const f = cron.trim().split(/\s+/);
202
+ if (f.length !== 5)
203
+ return null;
204
+ const min = expandCronField(f[0], 59);
205
+ const hour = expandCronField(f[1], 23);
206
+ const dom = expandCronField(f[2], 31);
207
+ const mon = expandCronField(f[3], 12, MON_NAMES);
208
+ const dow = expandCronField(f[4], 7, DOW_NAMES);
209
+ if (!min || !hour || !dom || !mon || !dow)
210
+ return null;
211
+ return { min, hour, dom, mon, dow, domStar: f[2] === '*', dowStar: f[4] === '*' };
212
+ }
213
+ // robfig/cron v3 的日匹配:dom 和 dow 都受限时用 OR,否则各自 AND(* 视为全通过)。
214
+ function dayMatch(p, dom, dowV) {
215
+ if (!p.domStar && !p.dowStar)
216
+ return p.dom.has(dom) || p.dow.has(dowV);
217
+ return (p.domStar || p.dom.has(dom)) && (p.dowStar || p.dow.has(dowV));
218
+ }
219
+ const isLeap = (y) => (y % 4 === 0 && y % 100 !== 0) || y % 400 === 0;
220
+ const DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
221
+ function daysInMonth(y, m) {
222
+ return m === 2 && isLeap(y) ? 29 : DAYS_IN_MONTH[m - 1];
223
+ }
224
+ /**
225
+ * 两条节律会不会在【同一时刻】fire。返回 collide / distinct / unverified。
226
+ * 判据【按真实 occurrence】算,不做简化字段交集——robfig 的 dom/dow 是 OR 语义、支持名字字面量,
227
+ * 简化交集会误判(Multica 用 robfig/cron v3,`0 9 * * MON-FRI` 必须能识别)。
228
+ * - 分/时/月任一集合不相交 → 一定不同刻 → distinct(便宜的早退)。
229
+ * - 否则枚举【完整的 400 年格里高利周期】找同一天两条都 active。为什么必须整 400 年而不是几年窗口:
230
+ * 像 `0 9 29 2 1` 与 `0 9 30 2 3` 的首个共同 fire 落在 2040-02-29,短窗口会漏判成 distinct(假阴性→漏真双发)。
231
+ * 公历 (月,日,星期) 组合每 400 年完整重复,所以枚举一整轮 = 完备,"整轮无碰撞"才敢判 distinct。
232
+ * - 任一字段解析不出(扩展语法)→ unverified:**绝不当 distinct**,交给人核。
233
+ * export:给 scripts/check-schedule.mjs 做回归断言。
234
+ */
235
+ export function firesCollide(cronA, cronB) {
236
+ const a = parseCron(cronA);
237
+ const b = parseCron(cronB);
238
+ if (!a || !b)
239
+ return 'unverified';
240
+ if (!setsIntersect(a.min, b.min) || !setsIntersect(a.hour, b.hour) || !setsIntersect(a.mon, b.mon)) {
241
+ return 'distinct';
242
+ }
243
+ // 整 400 年周期手工走(不 new Date,避免 14 万次分配)。2000-01-01 是周六(dow=6)。
244
+ let dow = 6;
245
+ for (let y = 2000; y < 2400; y++) {
246
+ for (let m = 1; m <= 12; m++) {
247
+ const monA = a.mon.has(m);
248
+ const monB = b.mon.has(m);
249
+ const dim = daysInMonth(y, m);
250
+ if (!(monA && monB)) {
251
+ dow = (dow + dim) % 7;
252
+ continue;
253
+ } // 该月两条不同时活跃,跳过但推进 dow
254
+ for (let dom = 1; dom <= dim; dom++) {
255
+ if (dayMatch(a, dom, dow) && dayMatch(b, dom, dow))
256
+ return 'collide';
257
+ dow = (dow + 1) % 7;
258
+ }
259
+ }
260
+ }
261
+ return 'distinct';
262
+ }
263
+ function shellQuote(value) {
264
+ return `"${value.replace(/(["\\$`])/g, '\\$1')}"`;
265
+ }
266
+ function envVar(ref) {
267
+ // "env:MULTICA_FDE_AGENT_ID" -> "$MULTICA_FDE_AGENT_ID"
268
+ return ref.startsWith('env:') ? `$${ref.slice(4)}` : ref;
269
+ }
270
+ function multicaApplyCommand(schedule, profile, agentRef, completionPredicate = '') {
271
+ // 声明与运行路径引用同一 predicate ID:description(唤醒提示词)尾部携带机器可读
272
+ // 标记,Agent/运维在 autopilot 侧看到的判据 ID 与 manifest 注册表一字不差。
273
+ const description = (schedule.wake || schedule.name) + (completionPredicate
274
+ ? `\n[dta-completion-predicate] ${completionPredicate}` : '');
275
+ const parts = [
276
+ 'python3 "$MULTICA_EXT" autopilot-create',
277
+ `--profile ${profile}`,
278
+ `--agent ${agentRef}`,
279
+ `--title ${shellQuote(schedule.name)}`,
280
+ `--mode ${MULTICA_MODE[schedule.mode]}`,
281
+ `--cron ${shellQuote(schedule.cron)}`,
282
+ `--timezone ${schedule.timezone}`,
283
+ `--description ${shellQuote(description)}`,
284
+ ];
285
+ return parts.join(' ');
286
+ }
287
+ /**
288
+ * Build the reconcile plan for one workspace's provider. Returns supported:false
289
+ * (with a reason) when the target platform does not offer schedules yet — the
290
+ * declared rhythms are then a recorded gap, never a silent downgrade.
291
+ */
292
+ export function buildSchedulePlan(info, workspaceName) {
293
+ const workspace = info.workspaces.find((item) => item.name === workspaceName);
294
+ if (!workspace)
295
+ throw new Error(`Project 未声明 Workspace: ${workspaceName}`);
296
+ const declared = info.project.schedules;
297
+ const provider = workspace.provider.kind;
298
+ // 撞车/未验证/缺判据/predicate 引用——manifest 级,与 provider 无关
299
+ const analysis = analyzeSchedules(declared, info.project.predicates);
300
+ if (provider === 'multica') {
301
+ const profile = workspace.provider.profile || '';
302
+ if (!profile) {
303
+ throw new Error(`Workspace ${workspaceName} 的 multica provider 缺 profile`);
304
+ }
305
+ // Agent 绑定来源与 deploy 同一份回退链(multicaAgentIdSource):黄金路径下
306
+ // scaffold 故意省略 agentIdFrom、首次 deploy 把真实 ID 写进 state——schedule
307
+ // 此前不读 state,导致部署完的 Agent 声明节律时还被要求手工接线(E-03)。
308
+ const source = multicaAgentIdSource(workspace);
309
+ if (source.kind === 'none') {
310
+ throw new Error(`Workspace ${workspaceName} 缺 Agent 绑定:manifest 未声明 provider.agentIdFrom,` +
311
+ `且尚无部署 state(.dingtalk-agent/state/workspaces/${workspaceName}.json)。` +
312
+ `先 dta deploy --workspace ${workspaceName} 创建 Agent(ID 会写入 state 自动复用),` +
313
+ '或在 manifest 显式声明 provider.agentIdFrom');
314
+ }
315
+ const agentRef = source.kind === 'declared' ? envVar(source.ref) : source.agentId;
316
+ const agentIdSource = source.kind === 'declared' ? 'declared-env' : 'deploy-state';
317
+ const schedules = declared.map((schedule) => {
318
+ const predicateId = completionPredicateId(schedule.completion);
319
+ const registered = predicateId && info.project.predicates?.[predicateId] ? predicateId : '';
320
+ return {
321
+ ...schedule,
322
+ applyCommand: multicaApplyCommand(schedule, profile, agentRef, registered),
323
+ metered: isMetered(schedule.mode),
324
+ ...(registered ? { completionPredicate: registered } : {}),
325
+ };
326
+ });
327
+ return {
328
+ $schema: 'dingtalk-agent/schedule-plan@2',
329
+ workspace: workspaceName, provider, supported: true, count: schedules.length,
330
+ agentIdSource, schedules,
331
+ ...analysis,
332
+ reconcile: '由 dta-ops-multica 技能执行:先 autopilot-list 读线上,按 title 匹配,' +
333
+ '只建缺的、改漂移的、删多余的;$MULTICA_EXT 指向 multica_ext.py' +
334
+ (agentIdSource === 'declared-env'
335
+ ? ',$…_AGENT_ID 由受信环境注入。'
336
+ : ';Agent ID 来自本项目部署 state(首次 deploy 写入)。') +
337
+ 'autopilot-create 非幂等,务必先 list 再建,别盲建(会造重复→重复外发)。',
338
+ sideEffect: false, dingtalkSideEffect: false,
339
+ };
340
+ }
341
+ // opencode / local dev / not-yet-supported platforms: no schedule capability.
342
+ const reason = provider === 'opencode'
343
+ ? 'opencode 是本地开发运行时,不承载常驻节律;节律需部署到托管平台'
344
+ : `provider ${provider} 尚未提供 schedule 能力`;
345
+ return {
346
+ $schema: 'dingtalk-agent/schedule-plan@2',
347
+ workspace: workspaceName, provider, supported: false, reason,
348
+ count: declared.length, schedules: [],
349
+ // provider 不支持,也照样报 manifest 自身的问题(撞车/未验证/缺判据),和平台支不支持无关。
350
+ ...analysis,
351
+ reconcile: `记为缺口:声明了 ${declared.length} 条节律,但目标 provider 不支持——` +
352
+ '别静默降级成"那就不定时了"。',
353
+ sideEffect: false, dingtalkSideEffect: false,
354
+ };
355
+ }
356
+ /**
357
+ * Declared schedules + 两条与平台无关、能静态检的不变量:
358
+ * 1) 两拍不得在【同一时刻】并发 fire。按【真实 occurrence】判(见 firesCollide:robfig dom/dow OR、
359
+ * 名字字面量、闰年窗口枚举),不是简化字段交集。判不准的(跨时区/扩展语法)进 unverifiedPairs,不当 distinct。
360
+ * 2) 每条节律都要有数据派生的完成判据(completion),否则只能靠"那一拍跑没跑"判完成,漏拍就永久丢一期。
361
+ * ⚠️ 这两条都只看【本地声明的 manifest】,不查平台实际生效状态——线上真相要走 provider 回读。
362
+ */
363
+ export function buildScheduleShow(start, cliVersion = '') {
364
+ const info = inspectAgentProject(start, cliVersion);
365
+ const declared = info.project.schedules;
366
+ const { collidingPairs, unverifiedPairs, missingCompletion, completionRefIssues, unregisteredCompletion, } = analyzeSchedules(declared, info.project.predicates);
367
+ return {
368
+ $schema: 'dingtalk-agent/schedule-show@2',
369
+ count: declared.length,
370
+ schedules: declared,
371
+ firesDistinct: collidingPairs.length === 0 && unverifiedPairs.length === 0,
372
+ collidingPairs,
373
+ unverifiedPairs,
374
+ missingCompletion,
375
+ completionRefIssues,
376
+ unregisteredCompletion,
377
+ sideEffect: false, dingtalkSideEffect: false,
378
+ };
379
+ }
380
+ //# sourceMappingURL=schedule-plan.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schedule-plan.js","sourceRoot":"","sources":["../../src/schedule-plan.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Schedule reconcile plan — dta's half of the driving contract.\n *\n * dta owns: read the declared schedules (dingtalk-agent.json#schedules), validate\n * them, and map them to a provider-specific reconcile plan. dta does NOT fire or\n * live-write triggers — that stays with the platform ops path (see ARCHITECTURE\n * §3.1, \"触发器不属于 dingtalk-agent 核心执行\"). The plan is the authoritative,\n * provider-mapped desired state the ops skill reconciles against live.\n *\n * Pure: no network, no side effects — safe to run in CI and fully testable.\n */\nimport {\n PREDICATE_ID, inspectAgentProject, multicaAgentIdSource,\n type AgentProjectInfo, type DeclaredPredicate, type DeclaredSchedule,\n} from './development-workspace.js'\n\n/**\n * completion 的 predicate 引用形态(issue #24):`predicate:<id>` 指向 manifest\n * `predicates` 注册表里唯一的判据。声明侧与运行路径(autopilot description 里的\n * `[dta-completion-predicate] <id>` 标记、Receipt)引用同一 ID——两个求值方无法\n * 静默分叉(真事故:两套手写判据分叉 30 小时无报警)。\n */\nexport const COMPLETION_PREDICATE_PREFIX = 'predicate:'\n\n/**\n * completion 的三态解析(issue #39):自由文本 / 合法引用 / malformed 引用。\n * 只要出现 `predicate:` 前缀就已经表达了「我在引用注册表」的意图——id 为空或\n * 格式非法时绝不能降级成 legacy 自由文本(那是 fail-open),必须单独成态去阻断。\n */\nexport function parseCompletionRef(completion?: string):\n | { kind: 'free-text' }\n | { kind: 'ref'; id: string }\n | { kind: 'malformed'; raw: string } {\n const text = (completion || '').trim()\n if (!text.startsWith(COMPLETION_PREDICATE_PREFIX)) return { kind: 'free-text' }\n const id = text.slice(COMPLETION_PREDICATE_PREFIX.length).trim()\n return PREDICATE_ID.test(id) ? { kind: 'ref', id } : { kind: 'malformed', raw: text }\n}\n\nexport function completionPredicateId(completion?: string): string {\n const parsed = parseCompletionRef(completion)\n return parsed.kind === 'ref' ? parsed.id : ''\n}\n\n/**\n * autopilot description 里 predicate 标记的【唯一】parser(issue #40)。\n *\n * 合同:标记是独立一行 `[dta-completion-predicate] <kebab-id>`(写入侧见\n * multicaApplyCommand)。所有回读方——TS 的对账、Python 的运行时解析\n * (multica_ext.py 的 parse_predicate_marker 是同一正则的镜像,改这里必须同步改\n * 那边)——都必须走这个定义,不许各自手写正则:两套 parser 静默分叉正是\n * 注册表要消灭的那类事故。\n *\n * 多枚标记不一致返回 conflict 而不是挑一个:挑选规则(首个/末个)就是第二份\n * 隐式合同,回读方猜错方向时漂移会被静默放过。\n */\nexport const COMPLETION_PREDICATE_MARKER =\n /^\\[dta-completion-predicate\\] ([a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?)$/gm\n\nexport function parseCompletionPredicateMarker(description?: string):\n | { kind: 'none' }\n | { kind: 'id'; id: string }\n | { kind: 'conflict'; ids: string[] }\n | { kind: 'malformed'; raws: string[] } {\n // malformed 独立成态(issue #45,与 #39 同一课):行首出现 marker 前缀就已表达\n // 「我在标注判据」的意图,id 空/非法时折成 none 会静默跳过整个回读校验——fail-open。\n const ids: string[] = []\n const raws: string[] = []\n for (const line of (description || '').split(/\\r?\\n/)) {\n if (!line.startsWith('[dta-completion-predicate]')) continue\n const m = line.match(/^\\[dta-completion-predicate\\] ([a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?)$/)\n if (m) ids.push(m[1])\n else raws.push(line)\n }\n if (raws.length) return { kind: 'malformed', raws }\n if (!ids.length) return { kind: 'none' }\n const unique = [...new Set(ids)]\n return unique.length === 1 ? { kind: 'id', id: unique[0] } : { kind: 'conflict', ids: unique }\n}\n\nexport interface SchedulePlanEntry extends DeclaredSchedule {\n applyCommand: string\n // 平台是否为这一拍记账。work-item 每次 fire 建 issue → 有 token 用量可查;\n // ephemeral(run_only) 不建 issue → 平台不记账,这一拍的成本【看不见】。\n // 改 mode 就是在改计量口径,这里显式标出来,别让它变成隐形盲区。\n metered: boolean\n /** completion 的注册表 predicate id(引用形态且已注册时);运行路径引用同一 ID。 */\n completionPredicate?: string\n}\n\nexport interface SchedulePlan {\n $schema: 'dingtalk-agent/schedule-plan@2'\n workspace: string\n provider: string\n supported: boolean\n reason?: string\n count: number\n /**\n * Agent 绑定来源:manifest 声明的 env 引用(declared-env),或首次 deploy 写入\n * workspace state 的真实 ID(deploy-state)。黄金路径(scaffold 故意省略\n * agentIdFrom、由 deploy 创建 Agent 存 ID)走的是后者。\n */\n agentIdSource?: 'declared-env' | 'deploy-state'\n schedules: SchedulePlanEntry[]\n // 声明了 cron 却没写 completion(数据派生的完成判据)的节律名。\n // 没有完成判据 = 只能靠\"那一拍跑了没有\"来判完成,而平台会漏拍/跳过迟到的 fire,\n // 一次抖动就永久丢一期。这些拍记成缺口,不静默放过。\n missingCompletion: string[]\n // 会同一时刻并发 fire 的节律对 / 无法静态确认是否同刻的对。\n // ⚠️ plan 是 operator 真正照着部署的路径,所以撞车必须在这里也拦——\n // 否则 show 报了撞车、plan 照样吐出 apply 命令,两条都建成 autopilot → 重复外发。\n collidingPairs: string[]\n unverifiedPairs: string[]\n // predicate 引用问题(阻断)与未注册自由文本(提示迁移)——与 show 同源,见 analyzeSchedules。\n completionRefIssues: string[]\n unregisteredCompletion: string[]\n reconcile: string\n sideEffect: false\n dingtalkSideEffect: false\n}\n\n// manifest 级的静态体检,show 和 plan 共用一份,避免两处逻辑漂移。\nexport function analyzeSchedules(\n declared: DeclaredSchedule[],\n predicates?: Record<string, DeclaredPredicate>,\n): {\n collidingPairs: string[]; unverifiedPairs: string[]; missingCompletion: string[]\n completionRefIssues: string[]; unregisteredCompletion: string[]\n} {\n const collidingPairs: string[] = []\n const unverifiedPairs: string[] = []\n for (let i = 0; i < declared.length; i++) {\n for (let j = i + 1; j < declared.length; j++) {\n const pair = `${declared[i].name}×${declared[j].name}`\n // 跨时区:本地无 tz 换算 → 判不准是否同刻 → unverified(不静默当 distinct)。\n if (declared[i].timezone !== declared[j].timezone) { unverifiedPairs.push(pair); continue }\n const r = firesCollide(declared[i].cron, declared[j].cron)\n if (r === 'collide') collidingPairs.push(pair)\n else if (r === 'unverified') unverifiedPairs.push(pair)\n }\n }\n const missingCompletion = declared.filter((s) => !s.completion?.trim()).map((s) => s.name)\n // predicate 引用体检(issue #24):引用必须解析到注册表;一旦项目声明了注册表,\n // 自由文本判据整体阻断——「写了一句话」不是判据,注入一句「跑过了」就能过。\n // 未声明注册表的存量项目维持现状,只列 unregistered 清单提示迁移(渐进门禁)。\n // 「已声明」看的是 manifest 里 predicates 键存不存在,不是注册表空不空:\n // 显式空注册表 = 项目已进入引用模式,自由文本照样阻断(issue #39)。\n const registryDeclared = predicates !== undefined\n const registry = predicates || {}\n const completionRefIssues: string[] = []\n const unregisteredCompletion: string[] = []\n for (const schedule of declared) {\n const completion = (schedule.completion || '').trim()\n if (!completion) continue // missingCompletion 已覆盖\n const parsed = parseCompletionRef(completion)\n if (parsed.kind === 'malformed') {\n // 有 predicate: 前缀但 id 空/非法:引用意图已表达,降级成自由文本就是 fail-open。\n completionRefIssues.push(\n `${schedule.name}: completion 的 predicate 引用格式非法(\"${parsed.raw}\")——` +\n '需要 predicate:<kebab-case-id>')\n } else if (parsed.kind === 'ref') {\n if (!registry[parsed.id]) {\n completionRefIssues.push(`${schedule.name}: completion 引用未注册的 predicate:${parsed.id}`)\n }\n } else if (registryDeclared) {\n completionRefIssues.push(\n `${schedule.name}: 已声明 predicates 注册表,completion 必须是 predicate:<id> 引用` +\n '(自由文本判据可被一句话注入绕过)')\n } else {\n unregisteredCompletion.push(schedule.name)\n }\n }\n return {\n collidingPairs, unverifiedPairs, missingCompletion,\n completionRefIssues, unregisteredCompletion,\n }\n}\n\n/**\n * 阻断部署的问题清单(空 = 可部署)。三类都算阻断,**包括 unverified**。\n *\n * unverified 曾经只告警,理由是\"本地证明不了 ≠ 有问题\"。这条理由错在赌的方向:\n * `0 9 * * *`@Asia/Shanghai 与 `0 1 * * *`@UTC 是**同一时刻**,静态比对看不出来,\n * 于是两条各建一个 autopilot、每天把同一条消息重复发给第三方——正是本守卫要防的那个伤害。\n * 证不出不撞车就当撞车(fail closed):挡住是一次响亮且可恢复的配置改动,放过是一次静默且发出去收不回的重复外发。\n *\n * 出路有两条,都不需要改这里:把两条节律写成同一个 timezone(对齐后走真实 occurrence 比对,\n * 判得出不撞就自动放行),或换掉本地读不懂的 cron 扩展语法。\n *\n * 判据只此一份:CLI 的 show 和 plan 都调它。此前两处各自手写 exit 条件,结果都漏了 unverified——\n * 抽出 analyzeSchedules 是为了防判据分叉,退出判据自己却分叉了。\n */\nexport function scheduleBlockers(a: {\n collidingPairs: string[]; unverifiedPairs: string[]; missingCompletion: string[]\n completionRefIssues?: string[]\n}): string[] {\n return [\n ...a.collidingPairs, ...a.unverifiedPairs, ...a.missingCompletion,\n ...(a.completionRefIssues || []),\n ]\n}\n\n// ephemeral = run-only (no work item); work-item = one work item per fire.\nconst MULTICA_MODE: Record<DeclaredSchedule['mode'], string> = {\n ephemeral: 'run_only',\n 'work-item': 'create_issue',\n}\n\n// 只有 work-item 每次 fire 建 issue、平台才有 token 用量可查。\nconst isMetered = (mode: DeclaredSchedule['mode']): boolean => mode === 'work-item'\n\n// 五个字段的上界(分 时 日 月 周)。\nconst CRON_FIELD_MAX = [59, 23, 31, 12, 7]\n// 名字字面量 → 数字(robfig/cron v3 支持,Multica 实测用 MON-FRI;不支持就会漏掉真双发)。\nconst DOW_NAMES: Record<string, number> = { SUN: 0, MON: 1, TUE: 2, WED: 3, THU: 4, FRI: 5, SAT: 6 }\nconst MON_NAMES: Record<string, number> = {\n JAN: 1, FEB: 2, MAR: 3, APR: 4, MAY: 5, JUN: 6, JUL: 7, AUG: 8, SEP: 9, OCT: 10, NOV: 11, DEC: 12,\n}\n\n/**\n * 把一个 cron 字段展开成它命中的整数集合。支持 `*`、列表、范围、步长,以及 dow/月的名字字面量。\n * 解析不出(`L`/`W`/`#`/`@` 等扩展语法)时返回 null —— 调用方据此判 unverified,不假装读懂了。\n */\nfunction expandCronField(field: string, max: number, names?: Record<string, number>): Set<number> | null {\n const out = new Set<number>()\n const num = (t: string): number => {\n const up = t.toUpperCase()\n if (names && up in names) return names[up]\n return /^\\d+$/.test(t) ? parseInt(t, 10) : NaN\n }\n for (const part of field.split(',')) {\n const [range, stepRaw] = part.split('/')\n const step = stepRaw ? parseInt(stepRaw, 10) : 1\n let lo = 0\n let hi = max\n if (range !== '*') {\n const [a, b] = range.split('-')\n lo = num(a)\n // robfig getRange 的特判:**单个数字 + 步长时上界拉到 max**——`5/15` 等价 `5-59/15` = {5,20,35,50},\n // 不是只有 {5}。当成 {5} 会漏掉 20/35/50,把真撞车判成 distinct → 正是本守卫要防的重复外发。\n hi = b === undefined ? (stepRaw ? max : lo) : num(b)\n }\n if (Number.isNaN(lo) || Number.isNaN(hi) || Number.isNaN(step) || step < 1) return null\n for (let v = lo; v <= hi && v <= max; v += step) out.add(v === 7 && max === 7 ? 0 : v)\n }\n // 空集合 = 越界(`70`)或倒序区间(`5-2`),是【没读懂】不是【永不 fire】。\n // 返回空集会让 setsIntersect 判 false → distinct(fail-open);返回 null → unverified,交给人核。\n return out.size === 0 ? null : out\n}\n\nfunction setsIntersect(a: Set<number>, b: Set<number>): boolean {\n for (const v of a) if (b.has(v)) return true\n return false\n}\n\ninterface ParsedCron { min: Set<number>; hour: Set<number>; dom: Set<number>; mon: Set<number>; dow: Set<number>; domStar: boolean; dowStar: boolean }\n\nfunction parseCron(cron: string): ParsedCron | null {\n const f = cron.trim().split(/\\s+/)\n if (f.length !== 5) return null\n const min = expandCronField(f[0], 59)\n const hour = expandCronField(f[1], 23)\n const dom = expandCronField(f[2], 31)\n const mon = expandCronField(f[3], 12, MON_NAMES)\n const dow = expandCronField(f[4], 7, DOW_NAMES)\n if (!min || !hour || !dom || !mon || !dow) return null\n return { min, hour, dom, mon, dow, domStar: f[2] === '*', dowStar: f[4] === '*' }\n}\n\n// robfig/cron v3 的日匹配:dom 和 dow 都受限时用 OR,否则各自 AND(* 视为全通过)。\nfunction dayMatch(p: ParsedCron, dom: number, dowV: number): boolean {\n if (!p.domStar && !p.dowStar) return p.dom.has(dom) || p.dow.has(dowV)\n return (p.domStar || p.dom.has(dom)) && (p.dowStar || p.dow.has(dowV))\n}\n\nconst isLeap = (y: number): boolean => (y % 4 === 0 && y % 100 !== 0) || y % 400 === 0\nconst DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]\nfunction daysInMonth(y: number, m: number): number {\n return m === 2 && isLeap(y) ? 29 : DAYS_IN_MONTH[m - 1]\n}\n\n/**\n * 两条节律会不会在【同一时刻】fire。返回 collide / distinct / unverified。\n * 判据【按真实 occurrence】算,不做简化字段交集——robfig 的 dom/dow 是 OR 语义、支持名字字面量,\n * 简化交集会误判(Multica 用 robfig/cron v3,`0 9 * * MON-FRI` 必须能识别)。\n * - 分/时/月任一集合不相交 → 一定不同刻 → distinct(便宜的早退)。\n * - 否则枚举【完整的 400 年格里高利周期】找同一天两条都 active。为什么必须整 400 年而不是几年窗口:\n * 像 `0 9 29 2 1` 与 `0 9 30 2 3` 的首个共同 fire 落在 2040-02-29,短窗口会漏判成 distinct(假阴性→漏真双发)。\n * 公历 (月,日,星期) 组合每 400 年完整重复,所以枚举一整轮 = 完备,\"整轮无碰撞\"才敢判 distinct。\n * - 任一字段解析不出(扩展语法)→ unverified:**绝不当 distinct**,交给人核。\n * export:给 scripts/check-schedule.mjs 做回归断言。\n */\nexport function firesCollide(cronA: string, cronB: string): 'collide' | 'distinct' | 'unverified' {\n const a = parseCron(cronA)\n const b = parseCron(cronB)\n if (!a || !b) return 'unverified'\n if (!setsIntersect(a.min, b.min) || !setsIntersect(a.hour, b.hour) || !setsIntersect(a.mon, b.mon)) {\n return 'distinct'\n }\n // 整 400 年周期手工走(不 new Date,避免 14 万次分配)。2000-01-01 是周六(dow=6)。\n let dow = 6\n for (let y = 2000; y < 2400; y++) {\n for (let m = 1; m <= 12; m++) {\n const monA = a.mon.has(m)\n const monB = b.mon.has(m)\n const dim = daysInMonth(y, m)\n if (!(monA && monB)) { dow = (dow + dim) % 7; continue } // 该月两条不同时活跃,跳过但推进 dow\n for (let dom = 1; dom <= dim; dom++) {\n if (dayMatch(a, dom, dow) && dayMatch(b, dom, dow)) return 'collide'\n dow = (dow + 1) % 7\n }\n }\n }\n return 'distinct'\n}\n\nfunction shellQuote(value: string): string {\n return `\"${value.replace(/([\"\\\\$`])/g, '\\\\$1')}\"`\n}\n\nfunction envVar(ref: string): string {\n // \"env:MULTICA_FDE_AGENT_ID\" -> \"$MULTICA_FDE_AGENT_ID\"\n return ref.startsWith('env:') ? `$${ref.slice(4)}` : ref\n}\n\nfunction multicaApplyCommand(\n schedule: DeclaredSchedule, profile: string, agentRef: string, completionPredicate = '',\n): string {\n // 声明与运行路径引用同一 predicate ID:description(唤醒提示词)尾部携带机器可读\n // 标记,Agent/运维在 autopilot 侧看到的判据 ID 与 manifest 注册表一字不差。\n const description = (schedule.wake || schedule.name) + (completionPredicate\n ? `\\n[dta-completion-predicate] ${completionPredicate}` : '')\n const parts = [\n 'python3 \"$MULTICA_EXT\" autopilot-create',\n `--profile ${profile}`,\n `--agent ${agentRef}`,\n `--title ${shellQuote(schedule.name)}`,\n `--mode ${MULTICA_MODE[schedule.mode]}`,\n `--cron ${shellQuote(schedule.cron)}`,\n `--timezone ${schedule.timezone}`,\n `--description ${shellQuote(description)}`,\n ]\n return parts.join(' ')\n}\n\n/**\n * Build the reconcile plan for one workspace's provider. Returns supported:false\n * (with a reason) when the target platform does not offer schedules yet — the\n * declared rhythms are then a recorded gap, never a silent downgrade.\n */\nexport function buildSchedulePlan(info: AgentProjectInfo, workspaceName: string): SchedulePlan {\n const workspace = info.workspaces.find((item) => item.name === workspaceName)\n if (!workspace) throw new Error(`Project 未声明 Workspace: ${workspaceName}`)\n const declared = info.project.schedules\n const provider = workspace.provider.kind\n // 撞车/未验证/缺判据/predicate 引用——manifest 级,与 provider 无关\n const analysis = analyzeSchedules(declared, info.project.predicates)\n\n if (provider === 'multica') {\n const profile = workspace.provider.profile || ''\n if (!profile) {\n throw new Error(`Workspace ${workspaceName} 的 multica provider 缺 profile`)\n }\n // Agent 绑定来源与 deploy 同一份回退链(multicaAgentIdSource):黄金路径下\n // scaffold 故意省略 agentIdFrom、首次 deploy 把真实 ID 写进 state——schedule\n // 此前不读 state,导致部署完的 Agent 声明节律时还被要求手工接线(E-03)。\n const source = multicaAgentIdSource(workspace)\n if (source.kind === 'none') {\n throw new Error(\n `Workspace ${workspaceName} 缺 Agent 绑定:manifest 未声明 provider.agentIdFrom,` +\n `且尚无部署 state(.dingtalk-agent/state/workspaces/${workspaceName}.json)。` +\n `先 dta deploy --workspace ${workspaceName} 创建 Agent(ID 会写入 state 自动复用),` +\n '或在 manifest 显式声明 provider.agentIdFrom')\n }\n const agentRef = source.kind === 'declared' ? envVar(source.ref) : source.agentId\n const agentIdSource = source.kind === 'declared' ? 'declared-env' as const : 'deploy-state' as const\n const schedules = declared.map((schedule) => {\n const predicateId = completionPredicateId(schedule.completion)\n const registered = predicateId && info.project.predicates?.[predicateId] ? predicateId : ''\n return {\n ...schedule,\n applyCommand: multicaApplyCommand(schedule, profile, agentRef, registered),\n metered: isMetered(schedule.mode),\n ...(registered ? { completionPredicate: registered } : {}),\n }\n })\n return {\n $schema: 'dingtalk-agent/schedule-plan@2',\n workspace: workspaceName, provider, supported: true, count: schedules.length,\n agentIdSource, schedules,\n ...analysis,\n reconcile: '由 dta-ops-multica 技能执行:先 autopilot-list 读线上,按 title 匹配,' +\n '只建缺的、改漂移的、删多余的;$MULTICA_EXT 指向 multica_ext.py' +\n (agentIdSource === 'declared-env'\n ? ',$…_AGENT_ID 由受信环境注入。'\n : ';Agent ID 来自本项目部署 state(首次 deploy 写入)。') +\n 'autopilot-create 非幂等,务必先 list 再建,别盲建(会造重复→重复外发)。',\n sideEffect: false, dingtalkSideEffect: false,\n }\n }\n\n // opencode / local dev / not-yet-supported platforms: no schedule capability.\n const reason = provider === 'opencode'\n ? 'opencode 是本地开发运行时,不承载常驻节律;节律需部署到托管平台'\n : `provider ${provider} 尚未提供 schedule 能力`\n return {\n $schema: 'dingtalk-agent/schedule-plan@2',\n workspace: workspaceName, provider, supported: false, reason,\n count: declared.length, schedules: [],\n // provider 不支持,也照样报 manifest 自身的问题(撞车/未验证/缺判据),和平台支不支持无关。\n ...analysis,\n reconcile: `记为缺口:声明了 ${declared.length} 条节律,但目标 provider 不支持——` +\n '别静默降级成\"那就不定时了\"。',\n sideEffect: false, dingtalkSideEffect: false,\n }\n}\n\nexport interface ScheduleShow {\n $schema: 'dingtalk-agent/schedule-show@2'\n count: number\n schedules: DeclaredSchedule[]\n // 会同一时刻并发 fire 的节律对,如 \"轻拍×补拍\"。外发拍的送达回执是发完才写、没有锁,\n // 同刻并发会重复送第三方,所以这是必须静态拦下的一条。\n firesDistinct: boolean\n collidingPairs: string[]\n // 无法静态确认是否同刻的对(跨时区、或 cron 有本解析器读不懂的扩展语法)。\n // 【绝不当成 distinct】——只是本地静态检查证明不了,要人核或走 provider occurrence 预览。\n unverifiedPairs: string[]\n // 声明了 cron 却没写数据派生完成判据(completion)的节律名——见 SchedulePlan.missingCompletion。\n missingCompletion: string[]\n // predicate 引用问题(阻断):引用未注册 / 注册表模式下仍用自由文本。\n completionRefIssues: string[]\n // 未声明注册表时仍用自由文本判据的节律名(提示迁移,不阻断)。\n unregisteredCompletion: string[]\n sideEffect: false\n dingtalkSideEffect: false\n}\n\n/**\n * Declared schedules + 两条与平台无关、能静态检的不变量:\n * 1) 两拍不得在【同一时刻】并发 fire。按【真实 occurrence】判(见 firesCollide:robfig dom/dow OR、\n * 名字字面量、闰年窗口枚举),不是简化字段交集。判不准的(跨时区/扩展语法)进 unverifiedPairs,不当 distinct。\n * 2) 每条节律都要有数据派生的完成判据(completion),否则只能靠\"那一拍跑没跑\"判完成,漏拍就永久丢一期。\n * ⚠️ 这两条都只看【本地声明的 manifest】,不查平台实际生效状态——线上真相要走 provider 回读。\n */\nexport function buildScheduleShow(start: string, cliVersion = ''): ScheduleShow {\n const info = inspectAgentProject(start, cliVersion)\n const declared = info.project.schedules\n const {\n collidingPairs, unverifiedPairs, missingCompletion,\n completionRefIssues, unregisteredCompletion,\n } = analyzeSchedules(declared, info.project.predicates)\n return {\n $schema: 'dingtalk-agent/schedule-show@2',\n count: declared.length,\n schedules: declared,\n firesDistinct: collidingPairs.length === 0 && unverifiedPairs.length === 0,\n collidingPairs,\n unverifiedPairs,\n missingCompletion,\n completionRefIssues,\n unregisteredCompletion,\n sideEffect: false, dingtalkSideEffect: false,\n }\n}\n"]}
@@ -614,7 +614,7 @@ function runMarkdown(event, field, rule, contextId, sessionId, runId, resume) {
614
614
  `3. \`context/invocation.json\`、存在时的 \`context/task-checkpoint.json\`、` +
615
615
  `\`context/agent-definition/definition.json\` 和存在时的 ` +
616
616
  `\`context/agent-definition/body.md\`;\n` +
617
- `4. \`trigger.json\` 和 \`context/skills/dingtalk-basic-behavior/SKILL.md\`。\n\n` +
617
+ `4. \`trigger.json\` 和 \`context/skills/dta-basic-behavior/SKILL.md\`。\n\n` +
618
618
  `只有任务需要时才读 context/mounts/ 下的 memory、knowledge。` +
619
619
  `目标、身份、Wait 与 Event Journal 都是宿主内部控制状态,不需要理解或修改。\n\n` +
620
620
  `每一步只调用一个允许的原子动作;可选一次 ack,随后至多一个终态动作:\n\n` +