@xdxer/dingtalk-agent 0.1.4-beta.1 → 0.1.4-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 (278) hide show
  1. package/CHANGELOG.md +70 -0
  2. package/README.en.md +397 -0
  3. package/README.md +500 -47
  4. package/dist/bin/dingtalk-agent.js +1263 -351
  5. package/dist/bin/dingtalk-agent.js.map +1 -1
  6. package/dist/src/actions.js +98 -14
  7. package/dist/src/actions.js.map +1 -1
  8. package/dist/src/agent-audit.js +460 -0
  9. package/dist/src/agent-audit.js.map +1 -0
  10. package/dist/src/agent-bindings.js +132 -0
  11. package/dist/src/agent-bindings.js.map +1 -0
  12. package/dist/src/agent-definition.js +182 -0
  13. package/dist/src/agent-definition.js.map +1 -0
  14. package/dist/src/agent-enhance.js +678 -0
  15. package/dist/src/agent-enhance.js.map +1 -0
  16. package/dist/src/agent-platform.js +158 -0
  17. package/dist/src/agent-platform.js.map +1 -0
  18. package/dist/src/bootstrap.js +125 -17
  19. package/dist/src/bootstrap.js.map +1 -1
  20. package/dist/src/config.js +1 -7
  21. package/dist/src/config.js.map +1 -1
  22. package/dist/src/development-workspace.js +736 -0
  23. package/dist/src/development-workspace.js.map +1 -0
  24. package/dist/src/doctor.js +49 -22
  25. package/dist/src/doctor.js.map +1 -1
  26. package/dist/src/dws.js +145 -0
  27. package/dist/src/dws.js.map +1 -1
  28. package/dist/src/eval-evidence.js +193 -0
  29. package/dist/src/eval-evidence.js.map +1 -0
  30. package/dist/src/init.js +1 -1
  31. package/dist/src/init.js.map +1 -1
  32. package/dist/src/invocation.js +36 -0
  33. package/dist/src/invocation.js.map +1 -0
  34. package/dist/src/lab.js +679 -0
  35. package/dist/src/lab.js.map +1 -0
  36. package/dist/src/lease.js +100 -0
  37. package/dist/src/lease.js.map +1 -0
  38. package/dist/src/map.js +157 -0
  39. package/dist/src/map.js.map +1 -0
  40. package/dist/src/memory/candidates.js +451 -0
  41. package/dist/src/memory/candidates.js.map +1 -0
  42. package/dist/src/memory/completion-evidence.js +536 -0
  43. package/dist/src/memory/completion-evidence.js.map +1 -0
  44. package/dist/src/memory/operational.js +263 -0
  45. package/dist/src/memory/operational.js.map +1 -0
  46. package/dist/src/memory/remote-state.js +478 -0
  47. package/dist/src/memory/remote-state.js.map +1 -0
  48. package/dist/src/memory/task-checkpoints.js +204 -0
  49. package/dist/src/memory/task-checkpoints.js.map +1 -0
  50. package/dist/src/multica-deploy.js +1480 -0
  51. package/dist/src/multica-deploy.js.map +1 -0
  52. package/dist/src/multica-provider.js +685 -0
  53. package/dist/src/multica-provider.js.map +1 -0
  54. package/dist/src/opencode-evals.js +1062 -0
  55. package/dist/src/opencode-evals.js.map +1 -0
  56. package/dist/src/opencode-provider.js +531 -0
  57. package/dist/src/opencode-provider.js.map +1 -0
  58. package/dist/src/opencode-workspace.js +197 -0
  59. package/dist/src/opencode-workspace.js.map +1 -0
  60. package/dist/src/perception.js +225 -0
  61. package/dist/src/perception.js.map +1 -0
  62. package/dist/src/personal-event-evals.js +595 -0
  63. package/dist/src/personal-event-evals.js.map +1 -0
  64. package/dist/src/promotion.js +786 -0
  65. package/dist/src/promotion.js.map +1 -0
  66. package/dist/src/remote-semantic-state-live-evals.js +888 -0
  67. package/dist/src/remote-semantic-state-live-evals.js.map +1 -0
  68. package/dist/src/remote-semantic-state-worker.js +38 -0
  69. package/dist/src/remote-semantic-state-worker.js.map +1 -0
  70. package/dist/src/remote-state-evals.js +501 -0
  71. package/dist/src/remote-state-evals.js.map +1 -0
  72. package/dist/src/response-gate.js +51 -0
  73. package/dist/src/response-gate.js.map +1 -0
  74. package/dist/src/robot-evals.js +770 -0
  75. package/dist/src/robot-evals.js.map +1 -0
  76. package/dist/src/sessions.js +66 -105
  77. package/dist/src/sessions.js.map +1 -1
  78. package/dist/src/setup.js +14 -12
  79. package/dist/src/setup.js.map +1 -1
  80. package/dist/src/skill-manager.js +128 -203
  81. package/dist/src/skill-manager.js.map +1 -1
  82. package/dist/src/skills.js.map +1 -1
  83. package/dist/src/storage-evals.js +26 -0
  84. package/dist/src/storage-evals.js.map +1 -0
  85. package/dist/src/types.js.map +1 -1
  86. package/dist/src/upgrade.js +137 -0
  87. package/dist/src/upgrade.js.map +1 -0
  88. package/dist/src/waits.js +5 -1
  89. package/dist/src/waits.js.map +1 -1
  90. package/dist/src/workspace.js +28 -3
  91. package/dist/src/workspace.js.map +1 -1
  92. package/docs/INSTALLATION.md +119 -14
  93. package/docs/SECOND-AGENT-ACCEPTANCE.md +62 -0
  94. package/docs/architecture/agent-memory-topology.png +0 -0
  95. package/docs/architecture/agent-memory-topology.svg +132 -0
  96. package/docs/architecture/general-agent-kernel-topology.png +0 -0
  97. package/docs/architecture/general-agent-kernel-topology.svg +149 -0
  98. package/docs/architecture/provider-bound-development-workspace.png +0 -0
  99. package/docs/architecture/provider-bound-development-workspace.svg +141 -0
  100. package/docs/architecture/task-completion-gate.png +0 -0
  101. package/docs/architecture/task-completion-gate.svg +191 -0
  102. package/docs/schemas/agent-audit-load-evidence.schema.json +14 -0
  103. package/docs/schemas/agent-audit.schema.json +92 -0
  104. package/docs/schemas/agent-bindings.schema.json +54 -0
  105. package/docs/schemas/agent-definition.schema.json +78 -0
  106. package/docs/schemas/agent-enhancement-plan.schema.json +88 -0
  107. package/docs/schemas/agent-enhancement-receipt.schema.json +37 -0
  108. package/docs/schemas/agent-platform.schema.json +13 -0
  109. package/docs/schemas/enriched-invocation.schema.json +46 -0
  110. package/docs/schemas/eval-candidate-plan.schema.json +28 -0
  111. package/docs/schemas/eval-candidate-result.schema.json +20 -0
  112. package/docs/schemas/eval-candidate.schema.json +30 -0
  113. package/docs/schemas/invocation.schema.json +19 -0
  114. package/docs/schemas/memory-candidate-proposal.schema.json +18 -0
  115. package/docs/schemas/memory-candidate.schema.json +76 -0
  116. package/docs/schemas/memory-publish-target.schema.json +25 -0
  117. package/docs/schemas/multica-deployment-list.schema.json +29 -0
  118. package/docs/schemas/multica-deployment-operation.schema.json +51 -0
  119. package/docs/schemas/multica-deployment-plan.schema.json +70 -0
  120. package/docs/schemas/multica-deployment-receipt.schema.json +87 -0
  121. package/docs/schemas/multica-deployment-status.schema.json +23 -0
  122. package/docs/schemas/multica-workspace-inspection.schema.json +77 -0
  123. package/docs/schemas/multica-workspace-plan.schema.json +68 -0
  124. package/docs/schemas/multica-workspace-resource-list.schema.json +27 -0
  125. package/docs/schemas/multica-workspace-status.schema.json +34 -0
  126. package/docs/schemas/observation.schema.json +21 -0
  127. package/docs/schemas/operational-memory-provider.schema.json +36 -0
  128. package/docs/schemas/operational-memory-record.schema.json +24 -0
  129. package/docs/schemas/perception-input.schema.json +56 -0
  130. package/docs/schemas/project.schema.json +113 -0
  131. package/docs/schemas/promotion-list.schema.json +36 -0
  132. package/docs/schemas/promotion-plan.schema.json +60 -0
  133. package/docs/schemas/promotion-policy.schema.json +29 -0
  134. package/docs/schemas/promotion-receipt.schema.json +43 -0
  135. package/docs/schemas/promotion-status.schema.json +23 -0
  136. package/docs/schemas/release-readiness.schema.json +66 -0
  137. package/docs/schemas/remote-semantic-state-live-eval.schema.json +60 -0
  138. package/docs/schemas/remote-semantic-state-manifest.schema.json +79 -0
  139. package/docs/schemas/remote-semantic-state-provider.schema.json +98 -0
  140. package/docs/schemas/response-gate.schema.json +20 -0
  141. package/docs/schemas/task-checkpoint.schema.json +71 -0
  142. package/docs/schemas/task-completion-evidence.schema.json +154 -0
  143. package/docs/schemas/workspace-doctor.schema.json +56 -0
  144. package/docs/schemas/workspace-state.schema.json +39 -0
  145. package/evals/baselines/2026-07-16/opencode-basic-010-completion-summary.json +123 -0
  146. package/evals/baselines/2026-07-16/opencode-basic-skill-required-summary.json +69 -0
  147. package/evals/baselines/2026-07-16/opencode-multi-surface-summary.json +63 -0
  148. package/evals/baselines/2026-07-16/remote-state-live-summary.json +70 -0
  149. package/evals/baselines/2026-07-17/agent-enhance-opencode-dogfood-summary.json +98 -0
  150. package/evals/baselines/2026-07-17/personal-event-live-readiness-summary.json +68 -0
  151. package/examples/agents/fde-coach/AGENTS.md +26 -0
  152. package/examples/agents/fde-coach/MEMORY.md +3 -0
  153. package/examples/agents/fde-coach/fields/default/field.json +24 -0
  154. package/examples/agents/fde-coach/knowledge/INDEX.md +4 -0
  155. package/examples/agents/fde-coach/skills/fde-coach/SKILL.md +13 -0
  156. package/examples/agents/release-manager/AGENTS.md +26 -0
  157. package/examples/agents/release-manager/MEMORY.md +3 -0
  158. package/examples/agents/release-manager/fields/default/field.json +24 -0
  159. package/examples/agents/release-manager/knowledge/INDEX.md +4 -0
  160. package/examples/agents/release-manager/skills/release-manager/SKILL.md +13 -0
  161. package/lab/README.md +109 -0
  162. package/lab/agent-eval/catalog.json +91 -0
  163. package/lab/agent-eval/classic-failures.json +177 -0
  164. package/lab/agent-eval/completion-gate-regression.json +99 -0
  165. package/lab/agent-eval/personal-event-live.example.json +94 -0
  166. package/lab/agent-eval/remote-semantic-state-live.example.json +70 -0
  167. package/lab/agent-eval/remote-semantic-state-provider.fixture.json +47 -0
  168. package/lab/agent-eval/remote-state-workspace/AGENTS.md +8 -0
  169. package/lab/agent-eval/remote-state-workspace/opencode.json +7 -0
  170. package/lab/agent-eval/remote-state-workspace/skills/remote-state-operator/SKILL.md +13 -0
  171. package/lab/agent-eval/remote-state.example.json +31 -0
  172. package/lab/agent-eval/workspace/AGENTS.md +7 -0
  173. package/lab/agent-eval/workspace/MEMORY.md +4 -0
  174. package/lab/agent-eval/workspace/artifacts/pending-review.md +3 -0
  175. package/lab/agent-eval/workspace/knowledge/INDEX.md +4 -0
  176. package/lab/agent-eval/workspace/opencode.json +20 -0
  177. package/lab/manifest.example.json +27 -0
  178. package/lab/manifest.personal-event.example.json +27 -0
  179. package/lab/project-workspace/README.md +11 -0
  180. package/lab/project-workspace/fake-multica-provider.mjs +266 -0
  181. package/lab/project-workspace/multica-deploy.fixture.json +29 -0
  182. package/lab/project-workspace/multica-readonly.fixture.json +69 -0
  183. package/lab/project-workspace/observation.fixture.json +14 -0
  184. package/lab/project-workspace/opencode-provider-suite.json +65 -0
  185. package/lab/project-workspace/project.fixture.json +44 -0
  186. package/lab/project-workspace/promotion-policy.fixture.json +15 -0
  187. package/lab/robot-eval/pool.example.json +30 -0
  188. package/lab/robot-eval/suite.json +123 -0
  189. package/lab/robot-eval/workspace/AGENTS.md +21 -0
  190. package/lab/robot-eval/workspace/MEMORY.md +3 -0
  191. package/lab/robot-eval/workspace/knowledge/INDEX.md +5 -0
  192. package/lab/robot-eval/workspace/opencode.json +22 -0
  193. package/lab/schemas/agent-eval-catalog.schema.json +47 -0
  194. package/lab/schemas/lab-manifest.schema.json +70 -0
  195. package/lab/schemas/personal-event-eval.schema.json +91 -0
  196. package/lab/schemas/remote-state-eval.schema.json +66 -0
  197. package/lab/schemas/robot-eval-suite.schema.json +184 -0
  198. package/lab/schemas/robot-pool.schema.json +56 -0
  199. package/package.json +26 -14
  200. package/skills/dingtalk-agent-boot-multica/SKILL.md +40 -0
  201. package/skills/dingtalk-agent-compose/SKILL.md +110 -0
  202. package/skills/dingtalk-agent-compose/assets/AGENTS.template.md +26 -0
  203. package/skills/dingtalk-agent-compose/assets/agent.bindings.dingtalk-doc.template.json +13 -0
  204. package/skills/dingtalk-agent-compose/assets/agent.bindings.local.template.json +13 -0
  205. package/skills/dingtalk-agent-compose/assets/opencode.template.json +12 -0
  206. package/skills/dingtalk-agent-compose/assets/role-skill.template.md +24 -0
  207. package/skills/dingtalk-agent-compose/evals/evals.json +94 -0
  208. package/skills/dingtalk-agent-compose/references/agent-definition-contract.md +55 -0
  209. package/skills/dingtalk-agent-compose/references/opencode-host-contract.md +65 -0
  210. package/skills/dingtalk-agent-compose/references/storage-routing.md +20 -0
  211. package/skills/dingtalk-agent-deploy/SKILL.md +60 -0
  212. package/skills/dingtalk-agent-deploy/references/multica-deployment-contract.md +49 -0
  213. package/skills/dingtalk-agent-deploy/references/promotion-observation-contract.md +49 -0
  214. package/skills/dingtalk-agent-eval/SKILL.md +116 -0
  215. package/skills/dingtalk-agent-eval/assets/eval-catalog.template.json +18 -0
  216. package/skills/dingtalk-agent-eval/evals/evals.json +61 -0
  217. package/skills/dingtalk-agent-eval/references/eval-topology.md +34 -0
  218. package/skills/dingtalk-agent-eval/references/evidence-contract.md +31 -0
  219. package/skills/dingtalk-agent-eval/references/scenario-taxonomy.md +25 -0
  220. package/skills/dingtalk-agent-eval/references/storage-modes.md +75 -0
  221. package/skills/dingtalk-basic-behavior/SKILL.md +63 -3
  222. package/skills/dingtalk-basic-behavior/assets/memory-candidate-proposal.json +10 -0
  223. package/skills/dingtalk-basic-behavior/assets/{task-checkpoint.md → task-checkpoint.json} +2 -21
  224. package/skills/dingtalk-basic-behavior/references/action-contract.md +2 -0
  225. package/skills/dingtalk-basic-behavior/references/memory-and-evolution.md +15 -1
  226. package/skills/dingtalk-basic-behavior/references/perception-and-gates.md +28 -0
  227. package/skills/dingtalk-basic-behavior/references/task-lifecycle.md +32 -7
  228. package/dist/src/boot.js +0 -70
  229. package/dist/src/boot.js.map +0 -1
  230. package/dist/src/duty.js +0 -74
  231. package/dist/src/duty.js.map +0 -1
  232. package/dist/src/kb.js +0 -240
  233. package/dist/src/kb.js.map +0 -1
  234. package/dist/src/runs.js +0 -79
  235. package/dist/src/runs.js.map +0 -1
  236. package/docs/ARCHITECTURE.md +0 -217
  237. package/docs/MINIMAL-WORKSPACE-V1.md +0 -172
  238. package/docs/OPEN-SOURCE-REFERENCES.md +0 -107
  239. package/docs/SELF-TEST.md +0 -252
  240. package/docs//345/206/205/347/275/221/345/256/236/347/233/270.md +0 -77
  241. package/evals/baselines/2026-07-14/behavior-summary.json +0 -28
  242. package/evals/baselines/2026-07-14/contract-summary.json +0 -18
  243. package/evals/baselines/2026-07-14/live-canary-summary.json +0 -25
  244. package/evals/baselines/2026-07-15/dingtalk-basic-behavior-0.3.0/SKILL.md +0 -72
  245. package/evals/baselines/2026-07-15/dingtalk-basic-behavior-0.3.0/references/action-contract.md +0 -31
  246. package/evals/baselines/2026-07-15/dingtalk-basic-behavior-0.3.0/references/event-to-behavior.md +0 -22
  247. package/evals/baselines/2026-07-15/dingtalk-basic-behavior-0.3.0/references/memory-and-evolution.md +0 -25
  248. package/evals/baselines/2026-07-15/dingtalk-basic-behavior-0.3.0/references/runtime-modes.md +0 -34
  249. package/evals/baselines/2026-07-15/task-lifecycle-summary.json +0 -50
  250. package/evals/evals.json +0 -316
  251. package/evals/fixtures/dm-ambiguous-send.json +0 -4
  252. package/evals/fixtures/dm-blocked.json +0 -4
  253. package/evals/fixtures/dm-clear.json +0 -4
  254. package/evals/fixtures/dm-discussion.json +0 -4
  255. package/evals/fixtures/dm-doc-write-no-tool.json +0 -4
  256. package/evals/fixtures/dm-long-task-ack.json +0 -4
  257. package/evals/fixtures/dm-nonblocking-gap.json +0 -4
  258. package/evals/fixtures/dm-structured-task.json +0 -4
  259. package/evals/fixtures/group.json +0 -10
  260. package/evals/fixtures/mentioned.json +0 -3
  261. package/evals/run-contract-evals.mjs +0 -1106
  262. package/evals/run-shadow-evals.mjs +0 -267
  263. package/evals/runners/README.md +0 -66
  264. package/evals/runners/claude-shadow.mjs +0 -533
  265. package/evals/schemas/action-request.schema.json +0 -77
  266. package/evals/shadow-evals.json +0 -133
  267. package/skills/AGENTS.md +0 -104
  268. package/skills//344/273/273/345/212/241.md +0 -48
  269. package/skills//345/237/272/347/241/200/350/241/214/344/270/272.md +0 -44
  270. package/skills//345/277/203/350/267/263.md +0 -79
  271. package/skills//346/266/210/346/201/257.md +0 -50
  272. package/skills//347/237/245/350/257/206.md +0 -55
  273. package/skills//350/257/204/346/265/213.md +0 -62
  274. package/skills//351/222/211/351/222/211.md +0 -64
  275. package/templates/ontology/index.md +0 -24
  276. package/templates/ontology/self/access.md +0 -38
  277. package/templates/ontology/self/role-spec.md +0 -34
  278. package/templates/ontology/self/workspace.md +0 -41
@@ -0,0 +1,679 @@
1
+ // 真实世界联调控制面:只管理测试授权、预算、marker、证据与清理。
2
+ // 业务动作仍走 prepareSession + Action Gate + typed DWS,不在这里复制命令面。
3
+ import { existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, writeFileSync, } from 'node:fs';
4
+ import { randomUUID } from 'node:crypto';
5
+ import { basename, delimiter, dirname, isAbsolute, join, relative, resolve } from 'node:path';
6
+ import { normalizeEvent, digest, stableStringify } from './events.js';
7
+ import { executeAction } from './actions.js';
8
+ import { prepareSession, readJson } from './sessions.js';
9
+ import { loadWorkspace } from './workspace.js';
10
+ import * as dws from './dws.js';
11
+ export function loadLabManifest(root, file) {
12
+ const path = safeInside(root, file, true);
13
+ let value;
14
+ try {
15
+ value = JSON.parse(readFileSync(path, 'utf8'));
16
+ }
17
+ catch (error) {
18
+ throw new Error(`Lab manifest 不是合法 JSON: ${error.message}`);
19
+ }
20
+ return validateManifest(root, value);
21
+ }
22
+ export function doctorLab(root, file) {
23
+ const checks = [];
24
+ let manifest = null;
25
+ try {
26
+ manifest = loadLabManifest(root, file);
27
+ checks.push({ id: 'manifest', passed: true, detail: `${manifest.id}/${manifest.mode}` });
28
+ }
29
+ catch (error) {
30
+ checks.push({ id: 'manifest', passed: false, detail: String(error.message || error) });
31
+ }
32
+ if (manifest) {
33
+ checks.push({
34
+ id: 'synthetic-only', passed: manifest.syntheticOnly === true,
35
+ detail: 'Lab 只允许合成数据。',
36
+ });
37
+ checks.push({
38
+ id: 'single-egress', passed: expectedEgress(manifest.mode) === manifest.egressOwner,
39
+ detail: `${manifest.mode} → ${manifest.egressOwner}`,
40
+ });
41
+ checks.push({
42
+ id: 'budget', passed: manifest.budgets.maxOutwardActions === 1 &&
43
+ manifest.budgets.maxRuns > 0 && manifest.budgets.maxRuns <= 20,
44
+ detail: JSON.stringify(manifest.budgets),
45
+ });
46
+ checks.push({
47
+ id: 'evidence-root', passed: isInside(resolve(root), resolve(root, manifest.evidenceRoot)),
48
+ detail: resolve(root, manifest.evidenceRoot),
49
+ });
50
+ try {
51
+ assertWorkspaceBindings(root, manifest);
52
+ checks.push({ id: 'workspace-binding', passed: true, detail: manifest.resources.workspace || '.' });
53
+ }
54
+ catch (error) {
55
+ checks.push({ id: 'workspace-binding', passed: false, detail: String(error.message || error) });
56
+ }
57
+ if (manifest.mode === 'mock-integration') {
58
+ let fakeDetail = '';
59
+ try {
60
+ fakeDetail = assertFakeDws();
61
+ }
62
+ catch (error) {
63
+ fakeDetail = String(error.message || error);
64
+ }
65
+ checks.push({
66
+ id: 'fake-dws', passed: fakeDetail.startsWith('verified:'),
67
+ detail: fakeDetail,
68
+ });
69
+ }
70
+ }
71
+ return {
72
+ $schema: 'dingtalk-agent/lab-doctor@1',
73
+ ready: checks.every((item) => item.passed),
74
+ sideEffect: false,
75
+ manifest: manifest ? redactManifest(manifest) : null,
76
+ checks,
77
+ };
78
+ }
79
+ export function runLab(root, cfg, manifestFile, rawEvent, options) {
80
+ const manifest = loadLabManifest(root, manifestFile);
81
+ assertWorkspaceBindings(root, manifest);
82
+ assertRunAuthorization(manifest, options);
83
+ if (manifest.mode === 'robot-connect') {
84
+ if (rawEvent)
85
+ throw new Error('robot-connect Lab 不接受 event-file;由 connector 接收合成消息');
86
+ return runRobotLab(root, manifest, options);
87
+ }
88
+ if (!rawEvent)
89
+ throw new Error(`${manifest.mode} Lab 需要 --event-file`);
90
+ return runPreparedLab(root, cfg, manifest, rawEvent, options);
91
+ }
92
+ export function verifyLab(root, evidence) {
93
+ const evidencePath = safeInside(root, evidence, true);
94
+ let manifest = {};
95
+ try {
96
+ manifest = readJson(join(evidencePath, 'manifest.redacted.json'));
97
+ }
98
+ catch { /* required-file check below reports it */ }
99
+ const required = [
100
+ 'manifest.redacted.json', 'event.json', 'invocation.json', 'enriched-invocation.json',
101
+ 'action-intent.json', 'action-attempt.json', 'action-receipt.json',
102
+ 'platform-readback.json', 'teardown.json',
103
+ ];
104
+ if (manifest.mode && manifest.mode !== 'robot-connect') {
105
+ required.push('identity.json', 'run.json', 'response-gate.json', 'resume.json', 'agent-definition.json', 'skill-manifest.json');
106
+ }
107
+ const checks = required.map((name) => ({
108
+ id: `file:${name}`,
109
+ passed: existsSync(join(evidencePath, name)),
110
+ detail: name,
111
+ }));
112
+ let marker = '';
113
+ let receipt = {};
114
+ let intent = {};
115
+ let readback = {};
116
+ let attempt = {};
117
+ try {
118
+ const event = readJson(join(evidencePath, 'event.json'));
119
+ intent = readJson(join(evidencePath, 'action-intent.json'));
120
+ attempt = readJson(join(evidencePath, 'action-attempt.json'));
121
+ receipt = readJson(join(evidencePath, 'action-receipt.json'));
122
+ readback = readJson(join(evidencePath, 'platform-readback.json'));
123
+ marker = extractMarker(String(event.message?.text || intent.payload?.text || ''));
124
+ }
125
+ catch (error) {
126
+ checks.push({ id: 'parse', passed: false, detail: String(error.message || error) });
127
+ }
128
+ const actionKind = String(intent.kind || '');
129
+ const silence = actionKind === 'silence';
130
+ checks.push({
131
+ id: 'marker', passed: Boolean(marker && (silence || stableStringify(intent).includes(marker))),
132
+ detail: marker || 'missing',
133
+ });
134
+ checks.push({
135
+ id: 'receipt', passed: receipt.outcome === 'confirmed' && receipt.verified === true,
136
+ detail: `${receipt.outcome || 'missing'}/${String(receipt.verified)}`,
137
+ });
138
+ const markerCount = countTextOccurrences(readback, marker);
139
+ const platformPassed = silence
140
+ ? readback.sideEffect === false && attempt.transportStarted === false && markerCount === 0
141
+ : manifest.mode === 'robot-connect'
142
+ ? hasQueryableReadback(readback) && markerCount >= 1
143
+ : hasQueryableReadback(readback) && markerCount === 1;
144
+ checks.push({
145
+ id: 'platform-readback', passed: platformPassed,
146
+ detail: `kind=${actionKind || manifest.mode || 'unknown'} markerCount=${markerCount}; ${JSON.stringify(readback).slice(0, 240)}`,
147
+ });
148
+ const report = {
149
+ $schema: 'dingtalk-agent/lab-assertions@1',
150
+ passed: checks.every((item) => item.passed),
151
+ engine: receipt.verified === true ? 'pass' : 'fail',
152
+ platform: hasQueryableReadback(readback) ? 'visible' : 'unverified',
153
+ userFeedback: 'not-collected',
154
+ marker,
155
+ markerCount,
156
+ action: actionKind || null,
157
+ checks,
158
+ verifiedAt: new Date().toISOString(),
159
+ };
160
+ writeJson(join(evidencePath, 'assertions.json'), report);
161
+ return report;
162
+ }
163
+ export function teardownLab(root, manifestFile, evidence, options = {}) {
164
+ const manifest = loadLabManifest(root, manifestFile);
165
+ const evidencePath = safeInside(root, evidence, true);
166
+ let external = null;
167
+ if (manifest.mode === 'robot-connect') {
168
+ assertLiveConfirmation(options);
169
+ const robot = manifest.resources.robot;
170
+ const stop = dws.run([
171
+ 'dev', 'connect', 'stop', '--robot-client-id', robot.robotClientId,
172
+ '--yes', ...profileArgs(manifest.dws.profile),
173
+ ], { allowFail: true, retries: 0 });
174
+ const status = dws.run([
175
+ 'dev', 'connect', 'status', '--robot-client-id', robot.robotClientId,
176
+ '--json', ...profileArgs(manifest.dws.profile),
177
+ ], { allowFail: true, retries: 0 });
178
+ external = { stop, status };
179
+ }
180
+ const checks = [];
181
+ if (manifest.mode === 'robot-connect') {
182
+ checks.push({
183
+ id: 'connector-released',
184
+ passed: stableStringify(external).includes('not_running') ||
185
+ stableStringify(external).includes('stopped'),
186
+ detail: stableStringify(external),
187
+ });
188
+ }
189
+ else {
190
+ checks.push({
191
+ id: 'no-lab-owned-connector', passed: external === null,
192
+ detail: 'personal-event/mock Lab does not create or retain a connector',
193
+ });
194
+ try {
195
+ const intent = readJson(join(evidencePath, 'action-intent.json'));
196
+ const attempt = readJson(join(evidencePath, 'action-attempt.json'));
197
+ const receipt = readJson(join(evidencePath, 'action-receipt.json'));
198
+ checks.push({
199
+ id: 'terminal-receipt',
200
+ passed: receipt.outcome === 'confirmed' && receipt.verified === true &&
201
+ receipt.actionId === intent.id &&
202
+ (intent.kind === 'silence' || Boolean(attempt.startedAt)),
203
+ detail: `${intent.kind || 'missing'}/${receipt.outcome || 'missing'}/${String(receipt.verified)}`,
204
+ });
205
+ }
206
+ catch (error) {
207
+ checks.push({ id: 'terminal-receipt', passed: false, detail: String(error.message || error) });
208
+ }
209
+ }
210
+ const passed = checks.every((item) => item.passed);
211
+ const report = {
212
+ $schema: 'dingtalk-agent/lab-teardown@1',
213
+ status: passed ? 'complete' : 'incomplete',
214
+ passed,
215
+ mode: manifest.mode,
216
+ external,
217
+ checks,
218
+ completedAt: new Date().toISOString(),
219
+ note: manifest.mode === 'personal-event'
220
+ ? '消息不自动删除;本次 teardown 只确认没有 Lab 持有的 connector。'
221
+ : 'Lab 控制面已清理。',
222
+ };
223
+ writeJson(join(evidencePath, 'teardown.json'), report);
224
+ return report;
225
+ }
226
+ function runPreparedLab(root, cfg, manifest, originalEvent, options) {
227
+ assertRunBudget(root, manifest);
228
+ const caseId = safeCase(options.caseId);
229
+ const generatedMarker = options.markerOverride ||
230
+ `${manifest.markerPrefix}${caseId}-${randomUUID()}]`;
231
+ if (!generatedMarker.startsWith(`${manifest.markerPrefix}${caseId}-`) ||
232
+ !/^\[DTA-LAB-[^\]\r\n]+\]$/u.test(generatedMarker)) {
233
+ throw new Error(`Lab marker 必须绑定当前 case: ${caseId}`);
234
+ }
235
+ const rawEvent = manifest.mode === 'mock-integration'
236
+ ? addMarkerAndUniqueIds(originalEvent, generatedMarker)
237
+ : originalEvent;
238
+ const event = normalizeEvent(rawEvent);
239
+ const marker = manifest.mode === 'mock-integration'
240
+ ? generatedMarker : extractMarker(event.message.text);
241
+ if (!marker || !marker.startsWith(`${manifest.markerPrefix}${caseId}-`)) {
242
+ throw new Error(`Live 入站消息必须包含 ${manifest.markerPrefix}<case>-<uuid>] marker`);
243
+ }
244
+ assertAllowedEvent(manifest, event);
245
+ const prepareOptions = { perception: options.perception };
246
+ if (!options.useEventCorrelation) {
247
+ prepareOptions.sessionKey = `lab:${caseId}:${digest(marker).slice(0, 12)}`;
248
+ }
249
+ const dispatch = prepareSession(root, cfg, rawEvent, prepareOptions);
250
+ const action = options.action || 'reply';
251
+ const actionText = `${marker} ${String(options.replyText ||
252
+ (action === 'ask' ? '请补充一个阻塞信息。' : 'Lab canary reply')).trim()}`;
253
+ if (action !== 'silence' && actionText.length > manifest.budgets.maxTextCharacters) {
254
+ throw new Error(`Lab 回复超过预算 ${manifest.budgets.maxTextCharacters}`);
255
+ }
256
+ const evidencePath = evidencePathFor(root, manifest, dispatch.runId);
257
+ mkdirSync(evidencePath, { recursive: true });
258
+ writeEvidenceCommon(evidencePath, manifest, event, readJson(join(dispatch.cwd, 'context', 'invocation.json')), readJson(join(dispatch.cwd, 'context', 'enriched-invocation.json')));
259
+ copyPreparedContextEvidence(dispatch.cwd, evidencePath);
260
+ writeJson(join(evidencePath, 'teardown.json'), {
261
+ $schema: 'dingtalk-agent/lab-teardown@1', status: 'pending', mode: manifest.mode,
262
+ });
263
+ let receipt;
264
+ try {
265
+ receipt = executeAction(dispatch.cwd, action, action === 'silence'
266
+ ? { reason: options.silenceReason || 'unmentioned' }
267
+ : { text: actionText });
268
+ }
269
+ catch (error) {
270
+ const failure = error;
271
+ if (failure.receipt?.actionId) {
272
+ const failedActionRoot = resolve(root, cfg.runtime.eventIndexRoot, 'actions', dispatch.runId, failure.receipt.actionId);
273
+ if (existsSync(failedActionRoot)) {
274
+ copyActionEvidence(failedActionRoot, evidencePath, failure.receipt, action);
275
+ }
276
+ }
277
+ writeJson(join(evidencePath, 'failure.json'), {
278
+ $schema: 'dingtalk-agent/lab-failure@1',
279
+ stage: 'action', action, runId: dispatch.runId,
280
+ error: String(failure.message || error), occurredAt: new Date().toISOString(),
281
+ });
282
+ failure.evidencePath = evidencePath;
283
+ throw failure;
284
+ }
285
+ try {
286
+ const actionRoot = findActionRoot(root, cfg, dispatch.runId, receipt.actionId);
287
+ copyActionEvidence(actionRoot, evidencePath, receipt, action);
288
+ const assertions = verifyLab(root, evidencePath);
289
+ return {
290
+ $schema: 'dingtalk-agent/lab-run@1',
291
+ mode: manifest.mode,
292
+ marker,
293
+ action,
294
+ evidencePath,
295
+ dispatch,
296
+ receipt,
297
+ assertions,
298
+ };
299
+ }
300
+ catch (error) {
301
+ const failure = error;
302
+ writeJson(join(evidencePath, 'failure.json'), {
303
+ $schema: 'dingtalk-agent/lab-failure@1',
304
+ stage: 'evidence-collection', action, runId: dispatch.runId,
305
+ error: String(failure.message || error), occurredAt: new Date().toISOString(),
306
+ });
307
+ failure.evidencePath = evidencePath;
308
+ throw failure;
309
+ }
310
+ }
311
+ function copyPreparedContextEvidence(runPath, evidencePath) {
312
+ copyJson(join(runPath, 'run.json'), join(evidencePath, 'run.json'));
313
+ copyJson(join(runPath, 'context', 'response-gate.json'), join(evidencePath, 'response-gate.json'));
314
+ copyJson(join(runPath, 'context', 'resume.json'), join(evidencePath, 'resume.json'));
315
+ copyJson(join(runPath, 'context', 'agent-definition', 'definition.json'), join(evidencePath, 'agent-definition.json'));
316
+ copyJson(join(runPath, 'context', 'skills', 'manifest.json'), join(evidencePath, 'skill-manifest.json'));
317
+ }
318
+ function copyActionEvidence(actionRoot, evidencePath, receipt, action) {
319
+ copyJson(join(actionRoot, 'intent.json'), join(evidencePath, 'action-intent.json'));
320
+ const attemptPath = join(actionRoot, 'attempt.json');
321
+ if (existsSync(attemptPath))
322
+ copyJson(attemptPath, join(evidencePath, 'action-attempt.json'));
323
+ else
324
+ writeJson(join(evidencePath, 'action-attempt.json'), {
325
+ $schema: 'dingtalk-agent/action-attempt@1', actionId: receipt.actionId,
326
+ transportStarted: false, reason: 'local-no-side-effect',
327
+ });
328
+ const identityPath = join(actionRoot, 'identity.json');
329
+ if (existsSync(identityPath))
330
+ copyJson(identityPath, join(evidencePath, 'identity.json'));
331
+ else
332
+ writeJson(join(evidencePath, 'identity.json'), {
333
+ $schema: 'dingtalk-agent/action-identity@1', required: false,
334
+ reason: 'local-no-side-effect',
335
+ });
336
+ writeJson(join(evidencePath, 'action-receipt.json'), receipt);
337
+ const transportResultPath = join(actionRoot, 'transport-result.json');
338
+ if (existsSync(transportResultPath)) {
339
+ copyJson(transportResultPath, join(evidencePath, 'transport-result.json'));
340
+ }
341
+ const verificationPath = join(actionRoot, 'verification.json');
342
+ writeJson(join(evidencePath, 'platform-readback.json'), action === 'silence'
343
+ ? { status: 'confirmed', sideEffect: false, verification: 'local-no-side-effect' }
344
+ : existsSync(verificationPath)
345
+ ? readJson(verificationPath)
346
+ : { status: 'unverified', receipt });
347
+ }
348
+ function runRobotLab(root, manifest, options) {
349
+ assertRunBudget(root, manifest);
350
+ const robot = manifest.resources.robot;
351
+ const marker = `${manifest.markerPrefix}${safeCase(options.caseId)}-${randomUUID()}]`;
352
+ const input = `${marker} ${String(options.inputText || '7 + 5 等于多少?请直接回答。').trim()}`;
353
+ const runId = `robot_${digest(marker).slice(0, 20)}`;
354
+ const evidencePath = evidencePathFor(root, manifest, runId);
355
+ mkdirSync(evidencePath, { recursive: true });
356
+ let start = null;
357
+ let send = null;
358
+ let readback = null;
359
+ let stopped = null;
360
+ try {
361
+ start = dws.run([
362
+ 'dev', 'connect', '--unified-app-id', robot.unifiedAppId,
363
+ '--channel', robot.channel, '--agent-workdir', resolve(root, manifest.resources.workspace),
364
+ '--allowed-users', robot.allowedUserId, '--agent-permission-mode', 'ask',
365
+ '--agent-approval-mode', 'ask', '--agent-memory=false', '--reply-card=false',
366
+ '--user-rate-limit', '5', '--agent-timeout', '120', '--daemon',
367
+ ...profileArgs(manifest.dws.profile),
368
+ ], { retries: 0 });
369
+ send = dws.run([
370
+ 'chat', 'message', 'send', '--open-dingtalk-id', robot.botOpenDingTalkId,
371
+ '--text', input, '--uuid', randomUUID(), '--yes', ...profileArgs(manifest.dws.profile),
372
+ ], { retries: 0 });
373
+ readback = dws.run([
374
+ 'chat', 'message', 'list', '--open-dingtalk-id', robot.botOpenDingTalkId,
375
+ '--direction', 'newer', '--limit', '20', ...profileArgs(manifest.dws.profile),
376
+ ], { retries: 1 });
377
+ }
378
+ finally {
379
+ stopped = dws.run([
380
+ 'dev', 'connect', 'stop', '--robot-client-id', robot.robotClientId,
381
+ '--yes', ...profileArgs(manifest.dws.profile),
382
+ ], { allowFail: true, retries: 0 });
383
+ }
384
+ const visible = stableStringify(readback).includes(marker);
385
+ const event = {
386
+ $schema: 'dingtalk-agent/event@1', id: runId, kind: 'robot.synthetic',
387
+ message: { text: input },
388
+ };
389
+ writeEvidenceCommon(evidencePath, manifest, event, {
390
+ $schema: 'dingtalk-agent/invocation@1', mode: 'direct', authority: 'host',
391
+ eventId: runId, sessionKey: runId, occurredAt: null,
392
+ actor: null, target: null, conversation: null,
393
+ });
394
+ writeJson(join(evidencePath, 'action-intent.json'), {
395
+ $schema: 'dingtalk-agent/lab-connector-intent@1', kind: 'robot-smoke', marker, input,
396
+ });
397
+ writeJson(join(evidencePath, 'action-attempt.json'), { started: true, start, send });
398
+ writeJson(join(evidencePath, 'action-receipt.json'), {
399
+ $schema: 'dingtalk-agent/lab-connector-receipt@1', kind: 'robot-smoke',
400
+ outcome: visible ? 'confirmed' : 'uncertain', verified: visible,
401
+ verification: 'chat-message-list',
402
+ });
403
+ writeJson(join(evidencePath, 'platform-readback.json'), readback || {});
404
+ writeJson(join(evidencePath, 'teardown.json'), {
405
+ $schema: 'dingtalk-agent/lab-teardown@1', status: 'complete', stopped,
406
+ completedAt: new Date().toISOString(),
407
+ });
408
+ const assertions = verifyLab(root, evidencePath);
409
+ return { $schema: 'dingtalk-agent/lab-run@1', mode: manifest.mode,
410
+ marker, evidencePath, assertions };
411
+ }
412
+ function writeEvidenceCommon(evidencePath, manifest, event, invocation, enrichedInvocation = null) {
413
+ writeJson(join(evidencePath, 'manifest.redacted.json'), redactManifest(manifest));
414
+ writeJson(join(evidencePath, 'event.json'), event);
415
+ writeJson(join(evidencePath, 'invocation.json'), invocation);
416
+ writeJson(join(evidencePath, 'enriched-invocation.json'), enrichedInvocation || {
417
+ $schema: 'dingtalk-agent/enriched-invocation@1',
418
+ status: 'base-only',
419
+ note: 'Phase 2 只记录基础 Invocation;Perception Enricher 在 Phase 3 接入。',
420
+ });
421
+ }
422
+ function validateManifest(root, value) {
423
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
424
+ throw new Error('Lab manifest 必须是 JSON object');
425
+ }
426
+ const input = value;
427
+ if (input.$schema !== 'dingtalk-agent/lab-manifest@1') {
428
+ throw new Error('Lab manifest schema 必须是 dingtalk-agent/lab-manifest@1');
429
+ }
430
+ if (!['mock-integration', 'robot-connect', 'personal-event'].includes(input.mode)) {
431
+ throw new Error(`Lab mode 非法: ${input.mode || '(missing)'}`);
432
+ }
433
+ if (input.syntheticOnly !== true)
434
+ throw new Error('Lab 必须声明 syntheticOnly=true');
435
+ if (input.egressOwner !== expectedEgress(input.mode)) {
436
+ throw new Error(`${input.mode} 的唯一出口必须是 ${expectedEgress(input.mode)}`);
437
+ }
438
+ if (!String(input.id || '').trim())
439
+ throw new Error('Lab manifest 缺少 id');
440
+ if (!String(input.markerPrefix || '').startsWith('[DTA-LAB-')) {
441
+ throw new Error('markerPrefix 必须以 [DTA-LAB- 开头');
442
+ }
443
+ if (!input.allowlist || !input.budgets || !input.resources || !input.dws) {
444
+ throw new Error('Lab manifest 缺少 allowlist / budgets / resources / dws');
445
+ }
446
+ for (const key of ['tenantIds', 'conversationIds', 'actorIds', 'resourceIds']) {
447
+ if (!Array.isArray(input.allowlist[key]) ||
448
+ input.allowlist[key].some((item) => typeof item !== 'string')) {
449
+ throw new Error(`Lab allowlist.${key} 必须是 string[]`);
450
+ }
451
+ }
452
+ if (input.mode !== 'robot-connect' &&
453
+ (!input.allowlist.tenantIds.length || !input.allowlist.conversationIds.length ||
454
+ !input.allowlist.actorIds.length)) {
455
+ throw new Error('Prepared Lab 必须显式 allowlist tenant / conversation / actor');
456
+ }
457
+ const budget = input.budgets;
458
+ if (!Number.isInteger(budget.maxRuns) || budget.maxRuns < 1 || budget.maxRuns > 20 ||
459
+ budget.maxOutwardActions !== 1 || !Number.isInteger(budget.maxTextCharacters) ||
460
+ budget.maxTextCharacters < 1 || budget.maxTextCharacters > 4000) {
461
+ throw new Error('Lab budgets 必须满足 maxRuns=1..20、maxOutwardActions=1、maxTextCharacters=1..4000');
462
+ }
463
+ if (!String(input.evidenceRoot || '').trim())
464
+ throw new Error('Lab 缺少 evidenceRoot');
465
+ safeInside(root, input.evidenceRoot, false);
466
+ if (!String(input.resources.workspace || '').trim())
467
+ throw new Error('Lab 缺少 resources.workspace');
468
+ safeInside(root, input.resources.workspace, true);
469
+ if (input.mode === 'robot-connect') {
470
+ const robot = input.resources.robot || {};
471
+ for (const key of ['unifiedAppId', 'robotClientId', 'botOpenDingTalkId', 'allowedUserId', 'channel']) {
472
+ if (!String(robot[key] || '').trim())
473
+ throw new Error(`robot-connect 缺少 resources.robot.${key}`);
474
+ }
475
+ const resources = input.allowlist.resourceIds;
476
+ if (![robot.unifiedAppId, robot.robotClientId, robot.botOpenDingTalkId]
477
+ .every((id) => resources.includes(id)) ||
478
+ !input.allowlist.actorIds.includes(robot.allowedUserId)) {
479
+ throw new Error('robot-connect 的 app/robot/bot 必须进入 resourceIds,allowed user 必须进入 actorIds');
480
+ }
481
+ }
482
+ else if (!String(input.dws.profile || '').trim() || !String(input.dws.expectedUserId || '').trim()) {
483
+ throw new Error('Prepared Lab 必须固定 dws.profile 与 expectedUserId');
484
+ }
485
+ return input;
486
+ }
487
+ function assertWorkspaceBindings(root, manifest) {
488
+ const workspaceRoot = safeInside(root, manifest.resources.workspace, true);
489
+ if (manifest.mode === 'robot-connect')
490
+ return;
491
+ const workspace = loadWorkspace(workspaceRoot);
492
+ if (workspace.dws.profile !== manifest.dws.profile ||
493
+ workspace.dws.expectedUserId !== manifest.dws.expectedUserId) {
494
+ throw new Error('Lab manifest 的 DWS 身份与 Workspace Definition 不一致');
495
+ }
496
+ const fieldPath = join(workspaceRoot, 'fields', 'default', 'field.json');
497
+ const field = readJson(fieldPath);
498
+ if (field.transport?.egressOwner !== manifest.egressOwner ||
499
+ field.transport?.mode !== (manifest.mode === 'mock-integration' ? 'personal-event' : manifest.mode)) {
500
+ throw new Error('Lab manifest 的 mode/egressOwner 与 Field 不一致');
501
+ }
502
+ }
503
+ function assertRunAuthorization(manifest, options) {
504
+ if (!String(options.caseId || '').trim())
505
+ throw new Error('lab run 需要 --case-id');
506
+ if (manifest.mode === 'mock-integration') {
507
+ assertFakeDws();
508
+ if (options.live)
509
+ throw new Error('mock-integration 不接受 --live');
510
+ return;
511
+ }
512
+ assertLiveConfirmation(options);
513
+ }
514
+ function assertFakeDws() {
515
+ if (process.env.DTA_LAB_FAKE_DWS !== '1') {
516
+ throw new Error('mock-integration 必须设置 DTA_LAB_FAKE_DWS=1');
517
+ }
518
+ const declared = String(process.env.DTA_LAB_FAKE_DWS_PATH || '').trim();
519
+ if (!declared || !isAbsolute(declared)) {
520
+ throw new Error('mock-integration 必须设置存在的绝对 DTA_LAB_FAKE_DWS_PATH');
521
+ }
522
+ if (!existsSync(declared))
523
+ throw new Error(`DTA_LAB_FAKE_DWS_PATH 不存在: ${declared}`);
524
+ const actual = findExecutable('dws');
525
+ if (!actual || realpathSync(actual) !== realpathSync(declared)) {
526
+ throw new Error(`PATH 中的 dws 与 DTA_LAB_FAKE_DWS_PATH 不一致: ${actual || 'missing'}`);
527
+ }
528
+ const marker = `${realpathSync(declared)}.dta-fake`;
529
+ if (!existsSync(marker) || readFileSync(marker, 'utf8').trim() !== 'dingtalk-agent/fake-dws@1') {
530
+ throw new Error('fake DWS 缺少同路径 .dta-fake 身份标记');
531
+ }
532
+ return `verified:${realpathSync(declared)}`;
533
+ }
534
+ function findExecutable(name) {
535
+ for (const directory of String(process.env.PATH || '').split(delimiter)) {
536
+ if (!directory)
537
+ continue;
538
+ const candidate = join(directory, name);
539
+ if (existsSync(candidate))
540
+ return candidate;
541
+ }
542
+ return '';
543
+ }
544
+ function assertLiveConfirmation(options) {
545
+ if (!options.live || !options.yes) {
546
+ throw new Error('Live Lab 会产生真实钉钉副作用;必须同时显式传入 --live --yes');
547
+ }
548
+ }
549
+ function assertAllowedEvent(manifest, event) {
550
+ const actorIds = [event.actor.id, event.actor.userId, event.actor.openDingTalkId].filter(Boolean);
551
+ if (!manifest.allowlist.tenantIds.includes(event.tenant.id)) {
552
+ throw new Error(`Lab tenant 不在 allowlist: ${event.tenant.id}`);
553
+ }
554
+ if (!manifest.allowlist.conversationIds.includes(event.conversation.id)) {
555
+ throw new Error(`Lab conversation 不在 allowlist: ${event.conversation.id}`);
556
+ }
557
+ if (!actorIds.some((id) => manifest.allowlist.actorIds.includes(id))) {
558
+ throw new Error(`Lab actor 不在 allowlist: ${actorIds.join('/')}`);
559
+ }
560
+ }
561
+ function addMarkerAndUniqueIds(raw, marker) {
562
+ const copy = JSON.parse(JSON.stringify(raw));
563
+ if (copy.$schema === 'dingtalk-agent/event@1') {
564
+ copy.id = `${String(copy.id || 'mock')}-${digest(marker).slice(0, 10)}`;
565
+ copy.message = { ...(copy.message || {}),
566
+ id: `${String(copy.message?.id || 'message')}-${digest(marker).slice(0, 10)}`,
567
+ text: `${marker} ${String(copy.message?.text || '')}`.trim() };
568
+ return copy;
569
+ }
570
+ if (typeof copy.data !== 'string')
571
+ throw new Error('mock event 缺少可注入 marker 的 data JSON');
572
+ const data = JSON.parse(copy.data);
573
+ const suffix = digest(marker).slice(0, 10);
574
+ data.event_id = `${String(data.event_id || 'mock')}-${suffix}`;
575
+ data.message_id = `${String(data.message_id || 'message')}-${suffix}`;
576
+ data.content = `${marker} ${String(data.content || '')}`.trim();
577
+ copy.data = JSON.stringify(data);
578
+ return copy;
579
+ }
580
+ function findActionRoot(root, cfg, runId, actionId) {
581
+ const path = resolve(root, cfg.runtime.eventIndexRoot, 'actions', runId, actionId);
582
+ if (!existsSync(path))
583
+ throw new Error(`Lab 找不到 Action journal: ${actionId}`);
584
+ return path;
585
+ }
586
+ function assertRunBudget(root, manifest) {
587
+ const evidenceRoot = safeInside(root, manifest.evidenceRoot, false);
588
+ if (!existsSync(evidenceRoot))
589
+ return;
590
+ const used = readdirSync(evidenceRoot, { withFileTypes: true })
591
+ .filter((entry) => entry.isDirectory()).length;
592
+ if (used >= manifest.budgets.maxRuns) {
593
+ throw new Error(`Lab Run 预算已用完: ${used}/${manifest.budgets.maxRuns};先 verify/teardown 并换新 Lab id`);
594
+ }
595
+ }
596
+ function evidencePathFor(root, manifest, runId) {
597
+ const evidenceRoot = safeInside(root, manifest.evidenceRoot, false);
598
+ return join(evidenceRoot, safeCase(runId));
599
+ }
600
+ function redactManifest(manifest) {
601
+ const redact = (value) => value
602
+ ? `<redacted:${digest(value).slice(0, 10)}>` : '';
603
+ return {
604
+ ...manifest,
605
+ dws: {
606
+ profile: redact(manifest.dws.profile),
607
+ expectedUserId: redact(manifest.dws.expectedUserId),
608
+ },
609
+ allowlist: Object.fromEntries(Object.entries(manifest.allowlist)
610
+ .map(([key, values]) => [key, values.map(redact)])),
611
+ resources: {
612
+ workspace: manifest.resources.workspace,
613
+ robot: manifest.resources.robot
614
+ ? Object.fromEntries(Object.entries(manifest.resources.robot)
615
+ .map(([key, value]) => [key === 'channel' ? key : key, key === 'channel' ? value : redact(value)]))
616
+ : undefined,
617
+ },
618
+ };
619
+ }
620
+ function copyJson(from, to) {
621
+ if (!existsSync(from))
622
+ throw new Error(`Lab evidence 缺少 ${basename(from)}`);
623
+ writeJson(to, readJson(from));
624
+ }
625
+ function writeJson(path, value) {
626
+ mkdirSync(dirname(path), { recursive: true });
627
+ const temp = `${path}.${process.pid}.${Date.now()}.tmp`;
628
+ writeFileSync(temp, JSON.stringify(value, null, 2) + '\n', { flag: 'wx' });
629
+ renameSync(temp, path);
630
+ }
631
+ function extractMarker(text) {
632
+ return text.match(/\[DTA-LAB-[^\]\r\n]{1,180}\]/)?.[0] || '';
633
+ }
634
+ function hasQueryableReadback(value) {
635
+ if (!value || typeof value !== 'object')
636
+ return false;
637
+ const text = stableStringify(value);
638
+ return /openMessageId|openMsgId|open_message_id|messages|records/.test(text);
639
+ }
640
+ function countTextOccurrences(value, needle) {
641
+ if (!needle || value === null || value === undefined)
642
+ return 0;
643
+ if (typeof value === 'string')
644
+ return value.split(needle).length - 1;
645
+ if (Array.isArray(value)) {
646
+ return value.reduce((sum, item) => sum + countTextOccurrences(item, needle), 0);
647
+ }
648
+ if (typeof value === 'object') {
649
+ let total = 0;
650
+ for (const item of Object.values(value)) {
651
+ total += countTextOccurrences(item, needle);
652
+ }
653
+ return total;
654
+ }
655
+ return 0;
656
+ }
657
+ function expectedEgress(mode) {
658
+ return mode === 'robot-connect' ? 'connector' : 'dingtalk-agent';
659
+ }
660
+ function safeInside(root, path, mustExist) {
661
+ const rootPath = existsSync(root) ? realpathSync(resolve(root)) : resolve(root);
662
+ const target = resolve(rootPath, path);
663
+ if (!isInside(rootPath, target))
664
+ throw new Error(`Lab 路径必须位于 Workspace 内: ${path}`);
665
+ if (mustExist && !existsSync(target))
666
+ throw new Error(`Lab 路径不存在: ${path}`);
667
+ return mustExist ? realpathSync(target) : target;
668
+ }
669
+ function isInside(root, path) {
670
+ const rel = relative(root, path);
671
+ return rel !== '..' && !rel.startsWith(`..${process.platform === 'win32' ? '\\' : '/'}`);
672
+ }
673
+ function safeCase(value) {
674
+ return String(value || '').replace(/[^a-zA-Z0-9._-]+/g, '-').replace(/^-+|-+$/g, '') || 'case';
675
+ }
676
+ function profileArgs(profile) {
677
+ return profile ? ['--profile', profile] : [];
678
+ }
679
+ //# sourceMappingURL=lab.js.map