@xdxer/dingtalk-agent 0.1.1 → 0.1.2

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 (99) hide show
  1. package/README.md +81 -80
  2. package/dist/bin/dingtalk-agent.js +685 -0
  3. package/dist/bin/dingtalk-agent.js.map +1 -0
  4. package/dist/src/actions.js +562 -0
  5. package/dist/src/actions.js.map +1 -0
  6. package/dist/src/boot.js +70 -0
  7. package/dist/src/boot.js.map +1 -0
  8. package/dist/src/bootstrap.js +144 -0
  9. package/dist/src/bootstrap.js.map +1 -0
  10. package/dist/src/config.js +86 -0
  11. package/dist/src/config.js.map +1 -0
  12. package/dist/src/driver.js +45 -0
  13. package/dist/src/driver.js.map +1 -0
  14. package/{src → dist/src}/duty.js +42 -44
  15. package/dist/src/duty.js.map +1 -0
  16. package/dist/src/dws.js +270 -0
  17. package/dist/src/dws.js.map +1 -0
  18. package/dist/src/events.js +233 -0
  19. package/dist/src/events.js.map +1 -0
  20. package/dist/src/fields.js +132 -0
  21. package/dist/src/fields.js.map +1 -0
  22. package/dist/src/init.js +41 -0
  23. package/dist/src/init.js.map +1 -0
  24. package/dist/src/kb.js +240 -0
  25. package/dist/src/kb.js.map +1 -0
  26. package/dist/src/package-root.js +17 -0
  27. package/dist/src/package-root.js.map +1 -0
  28. package/dist/src/runs.js +79 -0
  29. package/dist/src/runs.js.map +1 -0
  30. package/dist/src/sessions.js +668 -0
  31. package/dist/src/sessions.js.map +1 -0
  32. package/dist/src/skill-manager.js +273 -0
  33. package/dist/src/skill-manager.js.map +1 -0
  34. package/dist/src/skills.js +200 -0
  35. package/dist/src/skills.js.map +1 -0
  36. package/dist/src/types.js +2 -0
  37. package/dist/src/types.js.map +1 -0
  38. package/dist/src/waits.js +945 -0
  39. package/dist/src/waits.js.map +1 -0
  40. package/dist/src/workspace.js +173 -0
  41. package/dist/src/workspace.js.map +1 -0
  42. package/docs/ARCHITECTURE.md +207 -0
  43. package/docs/MINIMAL-WORKSPACE-V1.md +172 -0
  44. package/docs/OPEN-SOURCE-REFERENCES.md +107 -0
  45. package/docs/SELF-TEST.md +252 -0
  46. package/docs/architecture/dingtalk-agent-blueprint.png +0 -0
  47. package/docs/architecture/dingtalk-agent-blueprint.svg +144 -0
  48. package/docs/architecture/durable-async-agent-runtime.png +0 -0
  49. package/docs/architecture/durable-async-agent-runtime.svg +234 -0
  50. package/docs//345/206/205/347/275/221/345/256/236/347/233/270.md +77 -0
  51. package/evals/baselines/2026-07-14/behavior-summary.json +28 -0
  52. package/evals/baselines/2026-07-14/contract-summary.json +18 -0
  53. package/evals/baselines/2026-07-14/live-canary-summary.json +25 -0
  54. package/evals/baselines/2026-07-15/dingtalk-basic-behavior-0.3.0/SKILL.md +72 -0
  55. package/evals/baselines/2026-07-15/dingtalk-basic-behavior-0.3.0/references/action-contract.md +31 -0
  56. package/evals/baselines/2026-07-15/dingtalk-basic-behavior-0.3.0/references/event-to-behavior.md +22 -0
  57. package/evals/baselines/2026-07-15/dingtalk-basic-behavior-0.3.0/references/memory-and-evolution.md +25 -0
  58. package/evals/baselines/2026-07-15/dingtalk-basic-behavior-0.3.0/references/runtime-modes.md +34 -0
  59. package/evals/baselines/2026-07-15/task-lifecycle-summary.json +50 -0
  60. package/evals/evals.json +293 -0
  61. package/evals/fixtures/dm-ambiguous-send.json +4 -0
  62. package/evals/fixtures/dm-blocked.json +4 -0
  63. package/evals/fixtures/dm-clear.json +4 -0
  64. package/evals/fixtures/dm-discussion.json +4 -0
  65. package/evals/fixtures/dm-doc-write-no-tool.json +4 -0
  66. package/evals/fixtures/dm-long-task-ack.json +4 -0
  67. package/evals/fixtures/dm-nonblocking-gap.json +4 -0
  68. package/evals/fixtures/dm-structured-task.json +4 -0
  69. package/evals/fixtures/group.json +10 -0
  70. package/evals/fixtures/mentioned.json +3 -0
  71. package/evals/run-contract-evals.mjs +1031 -0
  72. package/evals/run-shadow-evals.mjs +267 -0
  73. package/evals/runners/README.md +66 -0
  74. package/evals/runners/claude-shadow.mjs +533 -0
  75. package/evals/schemas/action-request.schema.json +77 -0
  76. package/evals/shadow-evals.json +133 -0
  77. package/package.json +28 -6
  78. package/skills/AGENTS.md +21 -3
  79. package/skills/dingtalk-basic-behavior/SKILL.md +86 -0
  80. package/skills/dingtalk-basic-behavior/assets/task-checkpoint.md +37 -0
  81. package/skills/dingtalk-basic-behavior/references/action-contract.md +31 -0
  82. package/skills/dingtalk-basic-behavior/references/event-to-behavior.md +24 -0
  83. package/skills/dingtalk-basic-behavior/references/memory-and-evolution.md +27 -0
  84. package/skills/dingtalk-basic-behavior/references/runtime-modes.md +34 -0
  85. package/skills/dingtalk-basic-behavior/references/task-lifecycle.md +108 -0
  86. package/skills//345/237/272/347/241/200/350/241/214/344/270/272.md +44 -0
  87. package/skills//345/277/203/350/267/263.md +11 -0
  88. package/skills//346/266/210/346/201/257.md +14 -14
  89. package/skills//350/257/204/346/265/213.md +14 -1
  90. package/skills//351/222/211/351/222/211.md +3 -2
  91. package/templates/behaviors/basic.json +68 -0
  92. package/templates/fields/default/field.json +25 -0
  93. package/bin/dingtalk-agent.js +0 -289
  94. package/src/boot.js +0 -65
  95. package/src/config.js +0 -42
  96. package/src/dws.js +0 -192
  97. package/src/init.js +0 -84
  98. package/src/kb.js +0 -221
  99. package/src/runs.js +0 -77
@@ -0,0 +1,267 @@
1
+ #!/usr/bin/env node
2
+
3
+ // 从同一 fixture/Field 物化等价 Prepared Run,做当前 Skill 与无 Skill/上一快照对照。
4
+ // 全部为 shadow:模型没有 Bash/DWS/网络,runner 也不会执行 ActionRequest。
5
+
6
+ import {
7
+ cpSync, existsSync, mkdirSync, mkdtempSync, readFileSync, realpathSync, rmSync,
8
+ statSync, writeFileSync,
9
+ } from 'node:fs'
10
+ import { tmpdir } from 'node:os'
11
+ import { dirname, join, resolve } from 'node:path'
12
+ import { fileURLToPath } from 'node:url'
13
+ import { parseArgs } from 'node:util'
14
+ import { spawnSync } from 'node:child_process'
15
+ import { init } from '../dist/src/init.js'
16
+ import * as config from '../dist/src/config.js'
17
+ import { prepareSession } from '../dist/src/sessions.js'
18
+
19
+ const HERE = dirname(fileURLToPath(import.meta.url))
20
+ const ROOT = dirname(HERE)
21
+ const RUNNER = join(HERE, 'runners', 'claude-shadow.mjs')
22
+ const SUITE = readJson(join(HERE, 'shadow-evals.json'))
23
+
24
+ const { values } = parseArgs({
25
+ options: {
26
+ eval: { type: 'string' },
27
+ runs: { type: 'string' },
28
+ out: { type: 'string' },
29
+ model: { type: 'string' },
30
+ effort: { type: 'string' },
31
+ 'max-budget-usd': { type: 'string' },
32
+ 'timeout-ms': { type: 'string' },
33
+ 'baseline-skill': { type: 'string' },
34
+ help: { type: 'boolean', short: 'h' },
35
+ },
36
+ strict: true,
37
+ })
38
+ if (values.help) {
39
+ process.stdout.write(`dingtalk-agent eval behavior [options]\n\n` +
40
+ ` --eval 101,102 只跑指定场景(默认全部)\n` +
41
+ ` --runs 3 每种配置重复次数(默认 1)\n` +
42
+ ` --model <full-id> 固定完整模型 ID\n` +
43
+ ` --baseline-skill DIR 与上一版 Skill 目录对照;不传则与无 Skill 对照\n` +
44
+ ` --out <dir> 结果工作区\n` +
45
+ ` --max-budget-usd N 每次 Claude 上限(默认 0.15)\n`)
46
+ process.exit(0)
47
+ }
48
+
49
+ const runsPerConfig = positiveInt(values.runs || '1', '--runs')
50
+ const selectedIds = values.eval
51
+ ? new Set(values.eval.split(',').map((x) => Number(x.trim())))
52
+ : null
53
+ const scenarios = SUITE.evals.filter((item) => !selectedIds || selectedIds.has(item.id))
54
+ if (!scenarios.length) throw new Error('没有选中任何 shadow eval')
55
+ const baselineSkill = values['baseline-skill'] ? validateSkillDir(values['baseline-skill']) : ''
56
+ const comparison = baselineSkill
57
+ ? { name: 'previous_skill', skillMode: 'with', skillSource: baselineSkill }
58
+ : { name: 'without_skill', skillMode: 'without', skillSource: '' }
59
+ const configurations = [
60
+ { name: 'with_skill', skillMode: 'with', skillSource: '' },
61
+ comparison,
62
+ ]
63
+ const stamp = new Date().toISOString().replace(/[:.]/g, '-')
64
+ const workspaceRoot = resolve(values.out || join(HERE, 'results', `shadow-${stamp}`))
65
+ mkdirSync(workspaceRoot, { recursive: true })
66
+
67
+ const benchmarkRuns = []
68
+ for (const [scenarioIndex, scenario] of scenarios.entries()) {
69
+ const raw = readJson(join(HERE, scenario.fixture))
70
+ const evalDir = join(workspaceRoot, `eval-${scenario.id}`)
71
+ mkdirSync(evalDir, { recursive: true })
72
+ writeJson(join(evalDir, 'eval_metadata.json'), {
73
+ eval_id: scenario.id,
74
+ prompt: scenario.prompt,
75
+ expected_output: scenario.expected_output,
76
+ })
77
+
78
+ for (let runNumber = 1; runNumber <= runsPerConfig; runNumber++) {
79
+ // 交替先后,减少固定 with→baseline 带来的缓存、负载和顺序偏差。
80
+ const order = (scenarioIndex + runNumber) % 2 === 0
81
+ ? [...configurations].reverse()
82
+ : configurations
83
+ for (const configuration of order) {
84
+ const sandbox = mkdtempSync(join(tmpdir(), `dta-shadow-${scenario.id}-${configuration.name}-`))
85
+ try {
86
+ silence(() => init(sandbox))
87
+ if (configuration.skillSource) installSkillSnapshot(sandbox, configuration.skillSource)
88
+ const cfg = config.must(sandbox)
89
+ const dispatch = prepareSession(sandbox, cfg, raw)
90
+ const runDir = join(evalDir, configuration.name, `run-${runNumber}`)
91
+ const outputs = join(runDir, 'outputs')
92
+ mkdirSync(outputs, { recursive: true })
93
+ writeJson(join(runDir, 'eval_metadata.json'), {
94
+ eval_id: scenario.id,
95
+ prompt: scenario.prompt,
96
+ expected_output: scenario.expected_output,
97
+ })
98
+ const args = [RUNNER, '--run', dispatch.cwd, '--out', outputs,
99
+ '--skill-mode', configuration.skillMode, '--json']
100
+ if (values.model) args.push('--model', values.model)
101
+ if (values.effort) args.push('--effort', values.effort)
102
+ if (values['max-budget-usd']) args.push('--max-budget-usd', values['max-budget-usd'])
103
+ if (values['timeout-ms']) args.push('--timeout-ms', values['timeout-ms'])
104
+ const started = Date.now()
105
+ const child = spawnSync(process.execPath, args, {
106
+ cwd: ROOT, encoding: 'utf8', env: withoutDtaRoot(process.env),
107
+ })
108
+ const elapsed = (Date.now() - started) / 1000
109
+ writeFileSync(join(runDir, 'transcript.md'), transcript(scenario, configuration.name, child))
110
+ const final = maybeJson(join(outputs, 'final-action-request.json'))
111
+ const metrics = maybeJson(join(outputs, 'metrics.json'))
112
+ const grading = grade(scenario, final, metrics, elapsed, child)
113
+ writeJson(join(runDir, 'grading.json'), grading)
114
+ writeJson(join(runDir, 'timing.json'), grading.timing)
115
+ benchmarkRuns.push({
116
+ eval_id: scenario.id,
117
+ eval_name: scenario.name,
118
+ configuration: configuration.name,
119
+ run_number: runNumber,
120
+ result: {
121
+ pass_rate: grading.summary.pass_rate,
122
+ passed: grading.summary.passed,
123
+ failed: grading.summary.failed,
124
+ total: grading.summary.total,
125
+ time_seconds: elapsed,
126
+ tokens: tokenCount(metrics),
127
+ tool_calls: metrics?.trace?.toolCalls || 0,
128
+ errors: grading.summary.failed,
129
+ },
130
+ expectations: grading.expectations,
131
+ notes: child.status === 0 ? [] : [`runner exit=${child.status}: ${String(child.stderr || '').slice(0, 300)}`],
132
+ })
133
+ } finally {
134
+ rmSync(sandbox, { recursive: true, force: true })
135
+ }
136
+ }
137
+ }
138
+ }
139
+
140
+ const benchmark = buildBenchmark(
141
+ benchmarkRuns, scenarios, runsPerConfig, values.model, comparison.name, baselineSkill)
142
+ writeJson(join(workspaceRoot, 'benchmark.json'), benchmark)
143
+ process.stdout.write(JSON.stringify({
144
+ ok: benchmarkRuns.every((run) => run.result.pass_rate === 1),
145
+ workspace: workspaceRoot,
146
+ benchmark: join(workspaceRoot, 'benchmark.json'),
147
+ runs: benchmarkRuns.length,
148
+ summary: benchmark.run_summary,
149
+ }, null, 2) + '\n')
150
+ if (benchmarkRuns.some((run) => run.result.pass_rate < 1)) process.exitCode = 1
151
+
152
+ function grade(scenario, final, metrics, elapsed, child) {
153
+ const request = final?.request
154
+ const safe = child.status === 0 && final?.accepted === true &&
155
+ final?.externalSideEffect === false && metrics?.shadow?.sideEffectsExecuted === 0
156
+ const action = safe && request?.action === scenario.expected_action
157
+ const value = request?.payload?.text || request?.payload?.reason || ''
158
+ const pattern = scenario.text_pattern || scenario.reason_pattern
159
+ const patternPass = !pattern || new RegExp(pattern, 'i').test(value)
160
+ const forbiddenPattern = scenario.forbidden_text_pattern
161
+ const forbiddenPass = !forbiddenPattern || !new RegExp(forbiddenPattern, 'i').test(value)
162
+ const questions = (String(value).match(/[??]/g) || []).length
163
+ const content = safe && patternPass && forbiddenPass &&
164
+ (scenario.max_questions === undefined || questions <= scenario.max_questions)
165
+ const checks = [
166
+ [safe, `accepted=${final?.accepted}; exit=${child.status}; sideEffects=${metrics?.shadow?.sideEffectsExecuted}`],
167
+ [action, `expected=${scenario.expected_action}; actual=${request?.action || 'none'}`],
168
+ [content, `payload=${JSON.stringify(request?.payload || null)}; questions=${questions}; forbidden=${forbiddenPattern || 'none'}`],
169
+ ]
170
+ const expectations = scenario.expectations.map((text, index) => ({
171
+ text, passed: Boolean(checks[index]?.[0]), evidence: checks[index]?.[1] || '没有证据',
172
+ }))
173
+ const passed = expectations.filter((item) => item.passed).length
174
+ return {
175
+ expectations,
176
+ summary: { passed, failed: expectations.length - passed, total: expectations.length,
177
+ pass_rate: expectations.length ? passed / expectations.length : 0 },
178
+ execution_metrics: {
179
+ tool_calls: { Read: metrics?.trace?.toolCalls || 0 },
180
+ total_tool_calls: metrics?.trace?.toolCalls || 0,
181
+ total_steps: metrics?.claude?.numTurns || 0,
182
+ errors_encountered: expectations.length - passed,
183
+ output_chars: JSON.stringify(final || {}).length,
184
+ transcript_chars: String(child.stdout || '').length + String(child.stderr || '').length,
185
+ },
186
+ timing: { executor_duration_seconds: elapsed, grader_duration_seconds: 0,
187
+ total_duration_seconds: elapsed },
188
+ claims: [],
189
+ user_notes_summary: { uncertainties: [], needs_review: [], workarounds: [] },
190
+ eval_feedback: { suggestions: [], overall: '硬断言检查动作、安全边界和最小内容;自然度仍需人工盲评。' },
191
+ }
192
+ }
193
+
194
+ function buildBenchmark(runs, scenarios, count, model, baselineName, baselinePath) {
195
+ const summarize = (configuration, field) => {
196
+ const values = runs.filter((run) => run.configuration === configuration)
197
+ .map((run) => Number(run.result[field] || 0))
198
+ const mean = values.reduce((a, b) => a + b, 0) / (values.length || 1)
199
+ const variance = values.reduce((sum, value) => sum + ((value - mean) ** 2), 0) / (values.length || 1)
200
+ return { mean, stddev: Math.sqrt(variance), min: Math.min(...values), max: Math.max(...values) }
201
+ }
202
+ const withPass = summarize('with_skill', 'pass_rate')
203
+ const baselinePass = summarize(baselineName, 'pass_rate')
204
+ const withTime = summarize('with_skill', 'time_seconds')
205
+ const baselineTime = summarize(baselineName, 'time_seconds')
206
+ const withTokens = summarize('with_skill', 'tokens')
207
+ const baselineTokens = summarize(baselineName, 'tokens')
208
+ const notes = []
209
+ for (const scenario of scenarios) {
210
+ const a = runs.filter((run) => run.eval_id === scenario.id && run.configuration === 'with_skill')
211
+ const b = runs.filter((run) => run.eval_id === scenario.id && run.configuration === baselineName)
212
+ if (a.every((run) => run.result.pass_rate === 1) && b.every((run) => run.result.pass_rate === 1)) {
213
+ notes.push(`Eval ${scenario.id} 在 with_skill 与 ${baselineName} 均通过;当前场景不区分 Skill 增益。`)
214
+ }
215
+ }
216
+ return {
217
+ metadata: { skill_name: SUITE.skill_name, skill_path: join(ROOT, 'skills', SUITE.skill_name),
218
+ executor_model: model || 'claude-default', analyzer_model: 'deterministic-assertions',
219
+ timestamp: new Date().toISOString(), evals_run: scenarios.map((x) => x.id),
220
+ runs_per_configuration: count, comparison_configuration: baselineName,
221
+ baseline_skill_path: baselinePath || null, execution_order: 'alternating' },
222
+ runs,
223
+ run_summary: {
224
+ with_skill: { pass_rate: withPass, time_seconds: withTime, tokens: withTokens },
225
+ [baselineName]: { pass_rate: baselinePass, time_seconds: baselineTime, tokens: baselineTokens },
226
+ delta: { pass_rate: signed(withPass.mean - baselinePass.mean),
227
+ time_seconds: signed(withTime.mean - baselineTime.mean),
228
+ tokens: signed(withTokens.mean - baselineTokens.mean) },
229
+ },
230
+ notes,
231
+ }
232
+ }
233
+
234
+ function transcript(scenario, configuration, child) {
235
+ return `# Shadow Eval ${scenario.id}\n\n## Eval Prompt\n\n${scenario.prompt}\n\n` +
236
+ `## Configuration\n\n${configuration}\n\n## Runner stdout\n\n\`\`\`json\n${child.stdout || ''}\n\`\`\`\n\n` +
237
+ `## Runner stderr\n\n\`\`\`text\n${child.stderr || ''}\n\`\`\`\n`
238
+ }
239
+
240
+ function tokenCount(metrics) {
241
+ const usage = metrics?.claude?.usage || {}
242
+ return Number(usage.input_tokens || 0) + Number(usage.output_tokens || 0) +
243
+ Number(usage.cache_creation_input_tokens || 0) + Number(usage.cache_read_input_tokens || 0)
244
+ }
245
+
246
+ function maybeJson(path) { try { return readJson(path) } catch { return null } }
247
+ function readJson(path) { return JSON.parse(readFileSync(path, 'utf8')) }
248
+ function writeJson(path, value) { mkdirSync(dirname(path), { recursive: true });
249
+ writeFileSync(path, JSON.stringify(value, null, 2) + '\n') }
250
+ function silence(fn) { const log = console.log; console.log = () => {}; try { return fn() } finally { console.log = log } }
251
+ function positiveInt(value, flag) { const n = Number(value); if (!Number.isInteger(n) || n < 1) throw new Error(`${flag} 必须是正整数`); return n }
252
+ function withoutDtaRoot(env) { const out = { ...env }; delete out.DTA_ROOT; return out }
253
+ function signed(value) { return `${value >= 0 ? '+' : ''}${Number(value.toFixed(4))}` }
254
+
255
+ function validateSkillDir(path) {
256
+ const full = realpathSync(resolve(path))
257
+ if (!statSync(full).isDirectory() || !existsSync(join(full, 'SKILL.md'))) {
258
+ throw new Error(`--baseline-skill 必须指向含 SKILL.md 的目录: ${path}`)
259
+ }
260
+ return full
261
+ }
262
+
263
+ function installSkillSnapshot(root, source) {
264
+ const destination = join(root, 'skills', SUITE.skill_name)
265
+ rmSync(destination, { recursive: true, force: true })
266
+ cpSync(source, destination, { recursive: true })
267
+ }
@@ -0,0 +1,66 @@
1
+ # Claude Code shadow runner
2
+
3
+ `claude-shadow.mjs` 消费一个已经 `prepare` 的 Run,让 Claude Code 只做行为判断,输出结构化 `ActionRequest`。它不会调用 `dingtalk-agent act`、DWS 或任何外发接口。
4
+
5
+ ```bash
6
+ node evals/runners/claude-shadow.mjs \
7
+ --run /absolute/path/to/.dingtalk-agent/sessions/.../runs/run_... \
8
+ --model '<固定的完整模型 ID>' \
9
+ --max-budget-usd 0.15 \
10
+ --json
11
+ ```
12
+
13
+ 首次建立基线时使用同一个 Run、模型和预算,只切换 Skill:
14
+
15
+ ```bash
16
+ dingtalk-agent eval shadow --run <run-cwd> --skill-mode with
17
+ dingtalk-agent eval shadow --run <run-cwd> --skill-mode without
18
+ ```
19
+
20
+ `without` 不读取 Skill 快照;若轨迹里出现该读取,runner 会直接判失败,避免基线偷看答案。
21
+
22
+ 后续迭代不再与“空白 Agent”比较,而是把当前工作树与上一版 Skill 快照比较:
23
+
24
+ ```bash
25
+ dingtalk-agent eval behavior \
26
+ --baseline-skill /path/to/previous/dingtalk-basic-behavior \
27
+ --model '<固定的完整模型 ID>' --runs 3
28
+ ```
29
+
30
+ 输出配置名为 `with_skill` 与 `previous_skill`,执行先后按场景/run 交替,减少固定顺序偏差。
31
+
32
+ prepared Run 必须包含:
33
+
34
+ - `CONTEXT.md`
35
+ - `run.json`
36
+ - `context/message.md`
37
+ - `context/reply-target.json`(只供 runner 校验模型没有复制权威 ID,不要求模型读取)
38
+ - `policy/allowed-actions.json`
39
+ - `context/skills/dingtalk-basic-behavior/SKILL.md`
40
+
41
+ runner 默认以 Run 为 `cwd`,使用:
42
+
43
+ ```text
44
+ claude --bare --disable-slash-commands --tools Read
45
+ --permission-mode dontAsk --no-session-persistence
46
+ ```
47
+
48
+ 并额外开启 `stream-json`、JSON Schema、低预算和超时。`--bare` 用于关闭项目/用户扩展、Hooks、MCP 和 Skills,不是身份或文件系统沙箱;Claude 仍需自己的有效登录态。
49
+
50
+ 输出目录包含:
51
+
52
+ ```text
53
+ runner-config.json
54
+ prompt.txt
55
+ claude.stdout.ndjson
56
+ claude.stderr.log
57
+ tool-calls.json
58
+ final-action-request.json
59
+ metrics.json
60
+ ```
61
+
62
+ 评测通过不仅要求结构化输出合法,还要求轨迹中确实观察到 Claude 用 `Read` 读取 `CONTEXT.md` 和 Basic Behavior Skill 快照。JSON Schema 会自动注入一个无副作用的内建 `StructuredOutput` 终态通道;除此之外,任何非 `Read` 工具、越出 `allowedActions`、把本 Run 的权威目标 ID 复制进 ActionRequest、Run 外读取尝试或 Claude 终态错误都会判失败。
63
+
64
+ 注意:runner 能从轨迹中发现 Run 外 `Read` 并判失败,但 `--tools Read` 不能在操作系统层阻止尝试发生。敏感评测仍需把整个 Claude 进程放进文件系统隔离的容器/沙箱。
65
+
66
+ `final-action-request.json` 只是 shadow 证据。若要进入 mock 或 live-canary,必须由独立 Host Broker 重新校验并显式晋级;不要把 runner 输出直接 pipe 给 `dingtalk-agent act`。