@xdxer/dingtalk-agent 0.1.5-beta.2 → 0.1.5-beta.3

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 (46) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.en.md +6 -4
  3. package/README.md +6 -4
  4. package/dist/src/agent-audit.js +69 -55
  5. package/dist/src/agent-audit.js.map +1 -1
  6. package/dist/src/agent-enhance.js +49 -30
  7. package/dist/src/agent-enhance.js.map +1 -1
  8. package/dist/src/bootstrap.js +6 -2
  9. package/dist/src/bootstrap.js.map +1 -1
  10. package/dist/src/development-workspace.js +42 -15
  11. package/dist/src/development-workspace.js.map +1 -1
  12. package/dist/src/multica-deploy.js +27 -9
  13. package/dist/src/multica-deploy.js.map +1 -1
  14. package/dist/src/opencode-provider.js +17 -5
  15. package/dist/src/opencode-provider.js.map +1 -1
  16. package/dist/src/skills.js +2 -0
  17. package/dist/src/skills.js.map +1 -1
  18. package/dist/src/workspace.js +11 -6
  19. package/dist/src/workspace.js.map +1 -1
  20. package/docs/ARCHITECTURE.md +10 -7
  21. package/docs/INSTALLATION.md +1 -1
  22. package/docs/schemas/project.schema.json +5 -0
  23. package/examples/agents/README.md +8 -6
  24. package/examples/agents/fde-coach/AGENTS.md +2 -34
  25. package/examples/agents/fde-coach/agent/AGENTS.md +35 -0
  26. package/examples/agents/fde-coach/agent.bindings.json +10 -0
  27. package/examples/agents/release-manager/AGENTS.md +2 -34
  28. package/examples/agents/release-manager/agent/AGENTS.md +35 -0
  29. package/examples/agents/release-manager/agent.bindings.json +10 -0
  30. package/lab/project-workspace/fake-multica-provider.mjs +26 -7
  31. package/lab/robot-eval/suite.json +1 -1
  32. package/package.json +1 -1
  33. package/skills/core/dingtalk-agent-compose/SKILL.md +21 -15
  34. package/skills/core/dingtalk-agent-compose/assets/REPOSITORY.template.md +10 -0
  35. package/skills/core/dingtalk-agent-compose/assets/agent.bindings.dingtalk-doc.template.json +2 -2
  36. package/skills/core/dingtalk-agent-compose/assets/agent.bindings.local.template.json +2 -2
  37. package/skills/core/dingtalk-agent-compose/assets/hosts/opencode/opencode.template.json +2 -1
  38. package/skills/core/dingtalk-agent-compose/evals/evals.json +1 -1
  39. package/skills/core/dingtalk-agent-compose/references/agent-definition-contract.md +6 -6
  40. package/skills/core/dingtalk-agent-compose/references/host-loading-contract.md +10 -12
  41. package/skills/core/dingtalk-agent-compose/references/hosts/claude-code.md +13 -12
  42. package/skills/core/dingtalk-agent-compose/references/hosts/opencode.md +13 -12
  43. package/skills/core/dingtalk-basic-behavior/SKILL.md +9 -6
  44. package/skills/core/dingtalk-basic-behavior/references/perception-and-gates.md +2 -0
  45. /package/examples/agents/fde-coach/{skills → agent/skills}/fde-coach/SKILL.md +0 -0
  46. /package/examples/agents/release-manager/{skills → agent/skills}/release-manager/SKILL.md +0 -0
package/CHANGELOG.md CHANGED
@@ -2,6 +2,21 @@
2
2
 
3
3
  All notable changes to `@xdxer/dingtalk-agent` are documented here. The project follows semantic versioning; prerelease entries describe release candidates and do not imply that Live canaries or registry publication have completed.
4
4
 
5
+ ## 0.1.5-beta.3 - 2026-07-22
6
+
7
+ Agent delivery-package single-source layout and Multica Issue-Adapter receipt compatibility (PR #11). Ships on the `beta` dist-tag; `latest` stays at 0.1.4.
8
+
9
+ ### Changed
10
+
11
+ - Agent delivery packages now have a single source of truth: new projects generate `agent/AGENTS.md` and `agent/skills/`, and Host exposure is materialized only into temporary isolated workspaces instead of being committed alongside the source. The project manifest gains `skillsRoot`; explicit paths and the legacy root layout keep working.
12
+ - Basic Behavior 0.11.11 makes IM history file-identifier recovery mandatory: rich-message recovery follows a fixed readback-before-gap-judgment order, and a fileId present in a history file card must go through `drive info`/read-only download instead of the Agent claiming it cannot read the file. A deterministic history-file-card fixture locks the behavior.
13
+ - Golden Path and load-evidence fixtures moved to the `agent/AGENTS.md` + `agent/skills` single-source layout; load evidence now verifies resolved instructions against the trusted `definitionInstruction` exactly, while the legacy implicit root-`AGENTS.md` rule remains accepted.
14
+
15
+ ### Fixed
16
+
17
+ - The Multica deployment load-smoke gate no longer misjudges the platform's constrained Issue-Adapter receipts as arbitrary Shell. It accepts the combined here-doc write/comment/cleanup control-plane command only when content, filename and target Issue all match exactly, extracts the response evidence from that command, adds `result.md` to the approved result filenames (alongside `reply.md`/`result.json`), and allows `rm`/`rm -f` cleanup only for those three whitelisted files. DWS, network and any other Shell use still fail closed.
18
+ - Contract scenario 54's injected-timeout legs used a 1-second per-call provider budget, which a loaded machine could exhaust on an innocent preflight read (each fake-provider call spawns a Node process) before ever reaching the write the timeout was injected on — the pre-release `--full` run failed exactly this way while the isolated rerun passed. The runtime behaved correctly (read timeout → fail closed before any remote write); the eval budgets were raised to 4 seconds with the simulated hang raised to 8, and scenario 54's expectation texts in `evals.json` were reordered to match the runner's check order, which had drifted and mislabeled the failing assertion.
19
+
5
20
  ## 0.1.5-beta.2 - 2026-07-21
6
21
 
7
22
  Multica native-Skill refactor and DingTalk IM context recovery (PR #9, #10). Ships on the `beta` dist-tag; `latest` stays at 0.1.4.
package/README.en.md CHANGED
@@ -53,7 +53,9 @@ A model that talks is not a digital employee that works reliably. `dta` turns th
53
53
 
54
54
  ## What an Agent is made of
55
55
 
56
- Four things: the **Definition** (`AGENTS.md` — who I am, where my duties stop), **shared behavior** (the `dingtalk-basic-behavior` Skill — when to respond, ask, stay silent, close out), **role capability** (`skills/<role>/SKILL.md`), and **storage routing** (`agent.bindings.json` — optional; the same configuration can come from host context, environment variables, or a Workspace manifest). The Agent Host, DWS, managed platform, and event source it depends on are all outside this project.
56
+ Four things: the **Definition** (`agent/AGENTS.md` — who I am, where my duties stop), **shared behavior** (`agent/skills/dingtalk-basic-behavior`), **role capability** (`agent/skills/<role>/SKILL.md`), and **storage routing** (`agent.bindings.json` — optional; the same configuration can come from host context, environment variables, or a Workspace manifest). The repository-root `AGENTS.md` only governs coding agents and is never the remote System Prompt.
57
+
58
+ For a new project, `agent/` is the only managed-platform delivery boundary; do not commit duplicate Skills under root `skills/` or `.agents/skills/`. Existing code repositories can retain custom layouts by explicitly declaring `agent.definition` and `agent.skillsRoot` in `dingtalk-agent.json`.
57
59
 
58
60
  Full composition, dependencies, and boundaries: [Architecture](docs/ARCHITECTURE.md#2-一个-agent-由什么构成模型-a).
59
61
 
@@ -104,7 +106,7 @@ The placeholders above are not yours to invent — each has a definite source:
104
106
  | `<plan-id>` | Top-level `.planId` in the preceding `--dry-run --json` output. Any source or config change invalidates it, so re-run the dry run |
105
107
  | `local-dev` / `multica-dev` | Workspace names **you** declare in `dingtalk-agent.json#workspaces` — not built-in values. Sample: [`lab/project-workspace/project.fixture.json`](lab/project-workspace/project.fixture.json) |
106
108
  | `<promotion-id>` | `.promotionId` on the `promotion-receipt@1` produced by `dta promote --yes` |
107
- | `<role-skill-name>` | The `name` in your `skills/<role>/SKILL.md` frontmatter |
109
+ | `<role-skill-name>` | The `name` in your `agent/skills/<role>/SKILL.md` frontmatter |
108
110
 
109
111
  ### Minimal runnable path (local only, three commands)
110
112
 
@@ -116,9 +118,9 @@ dta bootstrap --json # Was the body recognized? Check definition.status and
116
118
  dta agent audit --json # Expect partial — the role semantics are still template text, which is correct
117
119
  ```
118
120
 
119
- None of this needs a DingTalk account or a managed platform, and none of it has side effects. Fill in the real role semantics in `AGENTS.md`, run `agent audit` again, and the only remaining gap is `host.load-probe` — which needs an Agent Host, see the [Compose Skill](skills/core/dingtalk-agent-compose/SKILL.md). The third command group above only applies once you have a Multica binding and declared Workspaces.
121
+ None of this needs a DingTalk account or a managed platform, and none of it has side effects. Fill in the real role semantics in `agent/AGENTS.md`, run `agent audit` again, and the only remaining gap is `host.load-probe` — which needs an Agent Host, see the [Compose Skill](skills/core/dingtalk-agent-compose/SKILL.md). The third command group above only applies once you have a Multica binding and declared Workspaces.
120
122
 
121
- When deploying to Multica, the remote System Prompt must equal the versioned `AGENTS.md` source. Deployment hashes, the Skill manifest, and assignment evidence stay in the plan/operation/Receipt control plane. Basic and Role Skills use the platform's native assignment instead of an extra startup prompt or startup Skill.
123
+ When deploying to Multica, the remote System Prompt must equal the manifest-selected `agent/AGENTS.md` source. Deployment hashes, the Skill manifest, and assignment evidence stay in the plan/operation/Receipt control plane. Basic and Role Skills use the platform's native assignment instead of an extra startup prompt or startup Skill.
122
124
 
123
125
  ## Support
124
126
 
package/README.md CHANGED
@@ -52,7 +52,9 @@ npm install --global --prefix "$HOME/.local" \
52
52
 
53
53
  ## 一个 Agent 由什么构成
54
54
 
55
- 四件东西:**本体**(`AGENTS.md`,我是谁、职责边界)、**公共行为**(`dingtalk-basic-behavior` Skill,何时响应/追问/沉默/收口)、**岗位能力**(`skills/<role>/SKILL.md`)、**存储路由**(`agent.bindings.json`,可选——等价配置也可来自宿主 context、环境变量或 Workspace manifest)。它依赖的 Agent Host、DWS、托管平台和事件源都不属于本项目。
55
+ 四件东西:**本体**(`agent/AGENTS.md`,我是谁、职责边界)、**公共行为**(`agent/skills/dingtalk-basic-behavior`,何时响应/追问/沉默/收口)、**岗位能力**(`agent/skills/<role>/SKILL.md`)、**存储路由**(`agent.bindings.json`,可选——等价配置也可来自宿主 context、环境变量或 Workspace manifest)。仓库根 `AGENTS.md` 只约束 Coding Agent 开发,不属于远端本体。Agent Host、DWS、托管平台和事件源也不属于 Agent 交付包。
56
+
57
+ 从零创建时,`agent/` 是 managed agent platform 的唯一同步边界,Skill 不能再复制到根 `skills/` 或 `.agents/skills/`。已有代码仓库不强制迁目录:在 `dingtalk-agent.json#agent` 显式声明 `definition` 与 `skillsRoot` 即可继续按原布局同步。
56
58
 
57
59
  完整的构成、依赖与边界见 [Architecture](docs/ARCHITECTURE.md#2-一个-agent-由什么构成模型-a)。
58
60
 
@@ -103,7 +105,7 @@ dta observe --promotion-id <promotion-id> --input <observation.json> --dry-run -
103
105
  | `<plan-id>` | 上一条 `--dry-run --json` 输出的顶层 `.planId`。源码或配置一变它就失效,必须重新 dry-run |
104
106
  | `local-dev` / `multica-dev` | **你自己**在 `dingtalk-agent.json#workspaces` 里声明的 Workspace 名,不是内置值。样例见 [`lab/project-workspace/project.fixture.json`](lab/project-workspace/project.fixture.json) |
105
107
  | `<promotion-id>` | `dta promote --yes` 产出的 `promotion-receipt@1` 的 `.promotionId` |
106
- | `<role-skill-name>` | 你 `skills/<role>/SKILL.md` 里 frontmatter 的 `name` |
108
+ | `<role-skill-name>` | 你 `agent/skills/<role>/SKILL.md` 里 frontmatter 的 `name` |
107
109
 
108
110
  ### 最小可运行路径(纯本地,三条命令)
109
111
 
@@ -115,9 +117,9 @@ dta bootstrap --json # 本体被识别了吗:看 definition.status 与 st
115
117
  dta agent audit --json # 预期 partial —— 岗位语义还是模板,这是正确结果
116
118
  ```
117
119
 
118
- 到这一步不需要钉钉账号、不需要托管平台、不产生任何副作用。把 `AGENTS.md` 里的岗位语义填成真的,再跑一次 `agent audit`,缺口就只剩 `host.load-probe`——那需要一个 Agent Host,见 [Compose Skill](skills/core/dingtalk-agent-compose/SKILL.md)。上面第 3 组云端命令要等你有了 Multica 归属和已声明的 Workspace 才用得上。
120
+ 到这一步不需要钉钉账号、不需要托管平台、不产生任何副作用。把 `agent/AGENTS.md` 里的岗位语义填成真的,再跑一次 `agent audit`,缺口就只剩 `host.load-probe`——那需要一个 Agent Host,见 [Compose Skill](skills/core/dingtalk-agent-compose/SKILL.md)。上面第 3 组云端命令要等你有了 Multica 归属和已声明的 Workspace 才用得上。
119
121
 
120
- 发布到 Multica 时,远端 System Prompt 必须与版本化的 `AGENTS.md` 原文一致;部署哈希、Skill 清单和 assignment 证据只存在于 plan/operation/Receipt 控制面。Basic 与 Role Skills 通过平台原生 assignment 挂载,不再用额外启动 Prompt 或启动 Skill 承载机器协议。
122
+ 发布到 Multica 时,远端 System Prompt 必须与 manifest 指定的 `agent/AGENTS.md` 原文一致;部署哈希、Skill 清单和 assignment 证据只存在于 plan/operation/Receipt 控制面。Basic 与 Role Skills 通过平台原生 assignment 挂载,不再用额外启动 Prompt 或启动 Skill 承载机器协议。
121
123
 
122
124
  ## 支持范围
123
125
 
@@ -12,6 +12,7 @@ import { resolvePackageRoot } from './package-root.js';
12
12
  import { bundledSkillPath } from './skill-manager.js';
13
13
  import { createIsolatedOpenCodeHost } from './opencode-isolation.js';
14
14
  const COMPOSE_TEMPLATE_MARKER = /<(?:Agent 名称|岗位\/角色|人群、团队或工作场域|长期目标|主动负责的事项|可观察的交付物|明确不做或必须升级确认的事项|岗位稳定的输入、判断、交付与验收范式|何时自行推进、何时交给谁|本体级常见错误 [12]|纠偏动作 [12]|Role Skill names|role-skill-name|Role Skill 名称|岗位职责或工作流|领域判断、输入与交付物|相邻职责或明确交接边界|岗位特有的禁区或必须升级条件|领域输入|领域方法\/SOP|领域常见错误|领域纠偏动作|可观察结果 [12]|route)>/u;
15
+ const BASIC = 'dingtalk-basic-behavior';
15
16
  const BASIC_INSTRUCTION = '.agents/skills/dingtalk-basic-behavior/SKILL.md';
16
17
  const DEFINITION_PROBE_PROMPT = '这是 Agent Definition 继承探针。只依据已强制加载的角色宪法末尾注释回答;' +
17
18
  '严格输出纯文本 dta-definition-probe=<值>,若看不到则输出纯文本 ' +
@@ -77,28 +78,33 @@ export function auditAgent(root, options = {}) {
77
78
  }
78
79
  }
79
80
  const basic = readCanonicalBasicSkill();
80
- const basicExposurePath = join(rootReal, '.agents', 'skills', 'dingtalk-basic-behavior', 'SKILL.md');
81
+ const packagedBasic = definition.skills.find((item) => item.name === BASIC);
82
+ const basicExposurePath = packagedBasic?.path || join(rootReal, '.agents', 'skills', BASIC, 'SKILL.md');
81
83
  const basicExposure = inspectSkill(basicExposurePath);
82
84
  const basicExposureTree = inspectSkillTree(basicExposurePath);
83
85
  add('host.basic-exposure', 'host', basicExposure.exists && basicExposure.name === basic.name &&
84
86
  basicExposure.version === basic.version && basicExposure.hash === basic.hash &&
85
87
  basicExposureTree.hash === basic.treeHash ? 'pass' : 'fail', true, basicExposure.exists && basicExposure.hash === basic.hash &&
86
88
  basicExposureTree.hash === basic.treeHash
87
- ? 'OpenCode Basic Skill 全树 exposure 与 canonical source 一致'
88
- : 'OpenCode Basic Skill 入口、references 或 assets 缺失或发生漂移', { expected: basic, actual: { ...basicExposure, treeHash: basicExposureTree.hash,
89
+ ? 'Agent package 的 Basic Skill 全树与 canonical source 一致'
90
+ : 'Agent package 的 Basic Skill 入口、references 或 assets 缺失或发生漂移', { expected: basic, actual: { ...basicExposure, treeHash: basicExposureTree.hash,
89
91
  files: basicExposureTree.files, error: basicExposureTree.error } });
90
92
  const config = inspectOpenCodeConfig(rootReal);
91
93
  const definitionInstruction = definition.body?.path
92
94
  ? relative(rootReal, definition.body.path).split('\\').join('/') : '';
93
95
  const definitionInstructionCount = countInstructionsForPath(rootReal, config.instructions, definitionInstruction);
94
- const definitionRuleReady = !config.error && definitionInstruction === 'AGENTS.md' &&
95
- definitionInstructionCount === 0;
96
+ const definitionInstructionExpected = definitionInstruction === 'AGENTS.md' ? 0 : 1;
97
+ const definitionRuleReady = !config.error && Boolean(definitionInstruction) &&
98
+ definitionInstructionCount === definitionInstructionExpected;
96
99
  add('host.definition-rule', 'host', definitionRuleReady
97
100
  ? 'pass' : 'fail', true, definitionRuleReady
98
- ? 'OpenCode 使用项目根 AGENTS.md 原生 rule,且未重复声明 custom instruction'
99
- : 'Agent Definition 必须是项目根 AGENTS.md,且不能重复加入 opencode instructions', { path: config.path, error: config.error, instruction: definitionInstruction,
101
+ ? definitionInstruction === 'AGENTS.md'
102
+ ? 'OpenCode 使用项目根开发兼容模式的原生 Agent Definition'
103
+ : 'OpenCode 以唯一 custom instruction 加载独立 Agent Definition'
104
+ : 'Agent Definition 未形成唯一加载路径', { path: config.path, error: config.error, instruction: definitionInstruction,
100
105
  instructionCount: definitionInstructionCount });
101
- const instructionCount = countInstructionsForPath(rootReal, config.instructions, '.agents/skills/dingtalk-basic-behavior/SKILL.md');
106
+ const basicInstruction = relative(rootReal, basicExposurePath).split('\\').join('/');
107
+ const instructionCount = countInstructionsForPath(rootReal, config.instructions, basicInstruction);
102
108
  add('host.basic-instruction', 'host', !config.error && instructionCount === 1 &&
103
109
  config.permissions['dingtalk-basic-behavior'] === 'allow' ? 'pass' : 'fail', true, !config.error && instructionCount === 1 &&
104
110
  config.permissions['dingtalk-basic-behavior'] === 'allow'
@@ -107,12 +113,10 @@ export function auditAgent(root, options = {}) {
107
113
  permission: config.permissions['dingtalk-basic-behavior'] || '' });
108
114
  for (const name of requiredSkills) {
109
115
  const source = roleSkills.get(name);
110
- const exposure = inspectSkill(join(rootReal, '.agents', 'skills', name, 'SKILL.md'));
111
116
  const permission = config.permissions[name] || '';
112
- const matched = Boolean(source?.hash && exposure.exists && exposure.name === name &&
113
- exposure.hash === source.hash && permission === 'allow');
114
- add(`host.role-exposure.${name}`, 'host', matched ? 'pass' : 'fail', true, matched ? `Role Skill ${name} exposure 与 Definition 一致`
115
- : `Role Skill ${name} exposure、hash 或 permission 不一致`, { sourceHash: source?.hash || '', exposure, permission });
117
+ const matched = Boolean(source?.hash && source.path && permission === 'allow');
118
+ add(`host.role-exposure.${name}`, 'host', matched ? 'pass' : 'fail', true, matched ? `Role Skill ${name} 单一发布源与 Host permission 一致`
119
+ : `Role Skill ${name} 发布源或 permission 不一致`, { sourceHash: source?.hash || '', sourcePath: source?.path || '', permission });
116
120
  }
117
121
  const staticPrerequisitesReady = checks
118
122
  .filter((item) => item.blocking)
@@ -129,7 +133,7 @@ export function auditAgent(root, options = {}) {
129
133
  if (!options.yes)
130
134
  throw new Error('agent audit --verify-load 会调用 OpenCode;必须同时传 --yes');
131
135
  if (staticPrerequisitesReady) {
132
- const execution = executeLoadAudit(rootReal, options, loadTargetHash, definitionInstruction, effectiveModel, () => currentLoadTargetHash(rootReal, options, requiredSkills, effectiveModel));
136
+ const execution = executeLoadAudit(rootReal, options, loadTargetHash, definitionInstruction, basicInstruction, effectiveModel, () => currentLoadTargetHash(rootReal, options, requiredSkills, effectiveModel));
133
137
  loadReport = execution.report;
134
138
  loadReportPath = execution.evidencePath;
135
139
  verifiedReportPath = execution.reportPath;
@@ -374,17 +378,14 @@ function currentLoadTargetHash(root, options, requiredSkills, model) {
374
378
  const hydrated = bootstrap(root, { ...(options.bootstrap || {}), deferRemote: true });
375
379
  const definition = hydrated.definition;
376
380
  const basic = readCanonicalBasicSkill();
377
- const exposedPath = join(root, '.agents', 'skills', 'dingtalk-basic-behavior', 'SKILL.md');
381
+ const packagedBasic = definition.skills.find((item) => item.name === BASIC);
382
+ const exposedPath = packagedBasic?.path || join(root, '.agents', 'skills', BASIC, 'SKILL.md');
378
383
  const exposed = inspectSkill(exposedPath);
379
384
  const exposedTree = inspectSkillTree(exposedPath);
380
385
  const config = inspectOpenCodeConfig(root);
381
386
  const roleSkills = new Map(definition.skills
382
387
  .filter((item) => item.name !== 'dingtalk-basic-behavior')
383
388
  .map((item) => [item.name, item]));
384
- const roleExposureHashes = Object.fromEntries(requiredSkills.map((name) => [
385
- name,
386
- inspectSkill(join(root, '.agents', 'skills', name, 'SKILL.md')).hash || '',
387
- ]));
388
389
  const roleSourceHashes = Object.fromEntries(requiredSkills.map((name) => [
389
390
  name, roleSkills.get(name)?.hash || '',
390
391
  ]));
@@ -402,11 +403,10 @@ function currentLoadTargetHash(root, options, requiredSkills, model) {
402
403
  opencodeConfigHash: config.hash,
403
404
  requiredSkills,
404
405
  roleSourceHashes,
405
- roleExposureHashes,
406
406
  model,
407
407
  }));
408
408
  }
409
- function executeLoadAudit(root, options, targetHash, definitionInstruction, model, revalidate) {
409
+ function executeLoadAudit(root, options, targetHash, definitionInstruction, basicInstruction, model, revalidate) {
410
410
  const runId = `agent_audit_${Date.now()}_${randomUUID().slice(0, 8)}`;
411
411
  const outputInspection = inspectContainedPath(root, options.out || join('.dingtalk-agent', 'agent-audit', 'results', runId));
412
412
  const outputRoot = outputInspection.path;
@@ -424,8 +424,8 @@ function executeLoadAudit(root, options, targetHash, definitionInstruction, mode
424
424
  const host = createIsolatedOpenCodeHost(model.split('/')[0]);
425
425
  try {
426
426
  const command = process.env.DTA_OPENCODE_BIN || 'opencode';
427
- const sourcePreflight = inspectSourceOpenCodeRuntime(command, root, host.environment, definitionInstruction);
428
- materializeAuditProbeWorkspace(root, auditWorkspace, definitionInstruction);
427
+ const sourcePreflight = inspectSourceOpenCodeRuntime(command, root, host.environment, definitionInstruction, basicInstruction);
428
+ materializeAuditProbeWorkspace(root, auditWorkspace, definitionInstruction, basicInstruction);
429
429
  const suitePath = join(auditWorkspace, 'suite.json');
430
430
  writeAtomic(suitePath, JSON.stringify({
431
431
  $schema: 'dingtalk-agent/robot-eval-suite@1',
@@ -494,19 +494,19 @@ function executeLoadAudit(root, options, targetHash, definitionInstruction, mode
494
494
  rmSync(auditWorkspace, { recursive: true, force: true });
495
495
  }
496
496
  }
497
- function materializeAuditProbeWorkspace(sourceRoot, targetRoot, definitionInstruction) {
498
- if (definitionInstruction !== 'AGENTS.md') {
499
- throw new Error('OpenCode 原生 Definition 探针只接受项目根 AGENTS.md');
500
- }
497
+ function materializeAuditProbeWorkspace(sourceRoot, targetRoot, definitionInstruction, basicInstruction) {
501
498
  mkdirSync(targetRoot, { recursive: true });
502
- writeAtomic(join(targetRoot, 'AGENTS.md'), readFileSync(join(sourceRoot, 'AGENTS.md'), 'utf8'));
503
- const sourceBasic = join(sourceRoot, '.agents', 'skills', 'dingtalk-basic-behavior');
499
+ writeAtomic(join(targetRoot, definitionInstruction), readFileSync(join(sourceRoot, definitionInstruction), 'utf8'));
500
+ const sourceBasic = dirname(join(sourceRoot, basicInstruction));
504
501
  const targetBasic = join(targetRoot, '.agents', 'skills', 'dingtalk-basic-behavior');
505
502
  mkdirSync(dirname(targetBasic), { recursive: true });
506
503
  cpSync(sourceBasic, targetBasic, { recursive: true });
507
504
  const config = {
508
505
  $schema: 'https://opencode.ai/config.json',
509
- instructions: [BASIC_INSTRUCTION],
506
+ instructions: [
507
+ ...(definitionInstruction === 'AGENTS.md' ? [] : [definitionInstruction]),
508
+ BASIC_INSTRUCTION,
509
+ ],
510
510
  permission: { skill: { 'dingtalk-basic-behavior': 'allow' } },
511
511
  plugin: [],
512
512
  mcp: {},
@@ -515,19 +515,21 @@ function materializeAuditProbeWorkspace(sourceRoot, targetRoot, definitionInstru
515
515
  };
516
516
  writeAtomic(join(targetRoot, 'opencode.json'), JSON.stringify(config, null, 2) + '\n');
517
517
  }
518
- function inspectSourceOpenCodeRuntime(command, root, environment, definitionInstruction) {
518
+ function inspectSourceOpenCodeRuntime(command, root, environment, definitionInstruction, basicInstruction) {
519
519
  const parent = mkdtempSync(join(tmpdir(), 'dta-source-config-preflight-'));
520
520
  const workspace = join(parent, 'workspace');
521
521
  let stdout = '';
522
522
  try {
523
523
  mkdirSync(workspace, { recursive: true });
524
524
  cpSync(join(root, 'opencode.json'), join(workspace, 'opencode.json'));
525
- if (existsSync(join(root, 'AGENTS.md'))) {
526
- cpSync(join(root, 'AGENTS.md'), join(workspace, 'AGENTS.md'));
525
+ if (existsSync(join(root, definitionInstruction))) {
526
+ const target = join(workspace, definitionInstruction);
527
+ mkdirSync(dirname(target), { recursive: true });
528
+ cpSync(join(root, definitionInstruction), target);
527
529
  }
528
- const basic = join(root, '.agents', 'skills', 'dingtalk-basic-behavior');
530
+ const basic = dirname(join(root, basicInstruction));
529
531
  if (existsSync(basic)) {
530
- const target = join(workspace, '.agents', 'skills', 'dingtalk-basic-behavior');
532
+ const target = dirname(join(workspace, basicInstruction));
531
533
  mkdirSync(dirname(target), { recursive: true });
532
534
  cpSync(basic, target, {
533
535
  recursive: true,
@@ -564,9 +566,10 @@ function inspectSourceOpenCodeRuntime(command, root, environment, definitionInst
564
566
  throw new Error('OpenCode 原项目 resolved instructions 不是 string array');
565
567
  }
566
568
  const definitionCount = countInstructionsForPath(root, instructions, definitionInstruction);
567
- const basicCount = countInstructionsForPath(root, instructions, BASIC_INSTRUCTION);
568
- if (definitionInstruction !== 'AGENTS.md' || definitionCount !== 0 || basicCount !== 1) {
569
- throw new Error(`OpenCode 原项目 resolved config 未形成原生 AGENTS.md + 唯一 Basic: ` +
569
+ const basicCount = countInstructionsForPath(root, instructions, basicInstruction);
570
+ const expectedDefinitionCount = definitionInstruction === 'AGENTS.md' ? 0 : 1;
571
+ if (definitionCount !== expectedDefinitionCount || basicCount !== 1) {
572
+ throw new Error(`OpenCode 原项目 resolved config 未形成唯一 Agent Definition + Basic: ` +
570
573
  `definition=${definitionCount} basic=${basicCount}`);
571
574
  }
572
575
  if (value.agent?.['dta-eval'] !== undefined) {
@@ -625,7 +628,10 @@ function executeInheritanceProbe(sourceRoot, model, command, timeoutMs, reportEv
625
628
  !Array.isArray(sourceConfig.permission) ? sourceConfig.permission : {};
626
629
  const skill = permission.skill && typeof permission.skill === 'object' &&
627
630
  !Array.isArray(permission.skill) ? permission.skill : {};
628
- sourceConfig.instructions = [BASIC_INSTRUCTION];
631
+ sourceConfig.instructions = [
632
+ ...(definitionInstruction === 'AGENTS.md' ? [] : [definitionInstruction]),
633
+ BASIC_INSTRUCTION,
634
+ ];
629
635
  delete sourceConfig.provider;
630
636
  sourceConfig.plugin = [];
631
637
  sourceConfig.mcp = {};
@@ -665,7 +671,7 @@ function executeInheritanceProbe(sourceRoot, model, command, timeoutMs, reportEv
665
671
  const definitionRaw = persistProbeExecution(reportEvidenceRoot, 'definition', definitionExecution);
666
672
  const referenceRaw = persistProbeExecution(reportEvidenceRoot, 'reference', referenceExecution);
667
673
  const result = {
668
- passed: definitionInstruction === 'AGENTS.md' && basicResolvedCount === 1 &&
674
+ passed: Boolean(definitionInstruction) && basicResolvedCount === 1 &&
669
675
  definitionPermissionPassed && referencePermissionPassed &&
670
676
  definitionExecution.exitCode === 0 && definitionExecution.directoryMatched &&
671
677
  definitionExecution.toolCalls === 0 &&
@@ -830,7 +836,7 @@ function validRandomProbe(value, name) {
830
836
  const count = name === 'dta-reference-probe' ? 2 : 1;
831
837
  return new RegExp(`^${name}=${uuid}${count === 2 ? `:${uuid}` : ''}$`).test(value);
832
838
  }
833
- function verifyLoadProbeSet(root, reportPath, report, primaryName, comparisonName, basic, expectedRuns) {
839
+ function verifyLoadProbeSet(root, reportPath, report, primaryName, comparisonName, basic, definitionInstruction, expectedRuns) {
834
840
  const all = Array.isArray(report.loadProbes) ? report.loadProbes : [];
835
841
  const declaredRuns = Number(report.summary?.runsPerConfiguration);
836
842
  const primary = all.filter((item) => item.configuration === primaryName);
@@ -841,7 +847,12 @@ function verifyLoadProbeSet(root, reportPath, report, primaryName, comparisonNam
841
847
  items.every((item) => Number.isInteger(item.runNumber) &&
842
848
  item.runNumber >= 1 && item.runNumber <= expectedRuns);
843
849
  const common = (item) => {
844
- const expectedInstructions = item.expectsLoad === true ? [BASIC_INSTRUCTION] : [];
850
+ const expectedInstructions = item.expectsLoad === true
851
+ ? [
852
+ BASIC_INSTRUCTION,
853
+ ...(definitionInstruction === 'AGENTS.md' ? [] : [definitionInstruction]),
854
+ ].sort()
855
+ : [];
845
856
  return loadProbeCorePassed(item, version) &&
846
857
  stableStringify(item.resolvedInstructions) === stableStringify(expectedInstructions) &&
847
858
  verifyLoadProbeRaw(root, reportPath, item, String(report.engine?.model || ''));
@@ -954,7 +965,7 @@ function readLoadEvidence(root, input, expectedTargetHash) {
954
965
  function verifyLoadReport(root, reportPath, report, basic, definitionInstruction, model, currentHostVersion) {
955
966
  const configuration = 'with_skill';
956
967
  const comparison = 'without_skill';
957
- const probeSet = verifyLoadProbeSet(root, reportPath, report, configuration, comparison, basic, 1);
968
+ const probeSet = verifyLoadProbeSet(root, reportPath, report, configuration, comparison, basic, definitionInstruction, 1);
958
969
  const probes = probeSet.primary;
959
970
  const source = report.skills?.[configuration];
960
971
  const gate = report.summary?.configurationLoadGates?.[configuration];
@@ -966,7 +977,7 @@ function verifyLoadReport(root, reportPath, report, basic, definitionInstruction
966
977
  Array.isArray(definition.toolNames) && definition.toolNames.length === 0 &&
967
978
  Array.isArray(definition.toolPaths) && definition.toolPaths.length === 0 &&
968
979
  Array.isArray(definition.toolTrace) && definition.toolTrace.length === 0 &&
969
- definition.nativeRule === 'AGENTS.md' && definitionInstruction === 'AGENTS.md' &&
980
+ definition.nativeRule === definitionInstruction && Boolean(definitionInstruction) &&
970
981
  definition.permissionPassed === true &&
971
982
  zeroToolPermission(definition.evalAgent || {});
972
983
  const referenceTrace = Array.isArray(reference.toolTrace) ? reference.toolTrace : [];
@@ -1017,7 +1028,7 @@ function verifyLoadReport(root, reportPath, report, basic, definitionInstruction
1017
1028
  baselineAntiGuessProbes: probeSet.passedComparison,
1018
1029
  expectedRunsPerConfiguration: probeSet.expectedRuns,
1019
1030
  definitionInstruction,
1020
- definitionNativeRule: definition.nativeRule === 'AGENTS.md',
1031
+ definitionNativeRule: definition.nativeRule === definitionInstruction,
1021
1032
  totalProbes: probes.length,
1022
1033
  definitionProbePassed: definitionPassed,
1023
1034
  referenceProbePassed: referencePassed,
@@ -1037,8 +1048,11 @@ function verifySourceConfigEvidence(root, reportPath, summary, evidence) {
1037
1048
  return false;
1038
1049
  const value = summary;
1039
1050
  const manifest = evidence;
1040
- if (value.passed !== true || value.definitionInstruction !== 'AGENTS.md' ||
1041
- value.definitionInstructionCount !== 0 || value.basicInstructionCount !== 1 ||
1051
+ const expectedDefinitionCount = value.definitionInstruction === 'AGENTS.md' ? 0 : 1;
1052
+ if (value.passed !== true || typeof value.definitionInstruction !== 'string' ||
1053
+ !value.definitionInstruction ||
1054
+ value.definitionInstructionCount !== expectedDefinitionCount ||
1055
+ value.basicInstructionCount !== 1 ||
1042
1056
  typeof value.rawHash !== 'string' || !value.rawHash ||
1043
1057
  manifest.rawHash !== value.rawHash)
1044
1058
  return false;
@@ -1227,12 +1241,12 @@ function repairFor(id) {
1227
1241
  if (id === 'definition.semantic-contract')
1228
1242
  return {
1229
1243
  id, why: '结构化模板不能替代真实的本体定义、岗位底线、做事范式和常犯错误。',
1230
- actions: ['评审并替换 AGENTS.md 中所有 <...> 占位符,再复跑 audit。'],
1244
+ actions: ['评审并替换 manifest 中 agent.definition 指向文件的所有 <...> 占位符,再复跑 audit。'],
1231
1245
  };
1232
1246
  if (id === 'definition.body')
1233
1247
  return {
1234
1248
  id, why: 'Agent 必须有长期定义、岗位底线、做事范式和常犯错误。',
1235
- actions: ['从 compose Skill 的 AGENTS.template.md 创建 AGENTS.md,再复跑 audit。'],
1249
+ actions: ['从 compose Skill 的 AGENTS.template.md 创建 agent/AGENTS.md,并由 manifest 的 agent.definition 指向它,再复跑 audit。'],
1236
1250
  };
1237
1251
  if (id === 'storage.memory')
1238
1252
  return {
@@ -1257,22 +1271,22 @@ function repairFor(id) {
1257
1271
  if (id.startsWith('skill.role.') && id.endsWith('.semantic'))
1258
1272
  return {
1259
1273
  id, why: 'Role Skill 必须包含真实的领域输入、方法、产物和验收,而不是模板提示。',
1260
- actions: [`评审并替换 skills/${id.slice('skill.role.'.length, -'.semantic'.length)}/SKILL.md 中所有 <...> 占位符。`],
1274
+ actions: [`评审并替换 agent.skillsRoot 下 ${id.slice('skill.role.'.length, -'.semantic'.length)}/SKILL.md 中所有 <...> 占位符。`],
1261
1275
  };
1262
1276
  if (id.startsWith('skill.role.'))
1263
1277
  return {
1264
1278
  id, why: '领域职责需要独立 Role Skill,不能写回 Basic Behavior。',
1265
- actions: [`创建 skills/${id.slice('skill.role.'.length)}/SKILL.md,并确保目录名等于 frontmatter name。`],
1279
+ actions: [`在 manifest 声明的 agent.skillsRoot 下创建 ${id.slice('skill.role.'.length)}/SKILL.md,并确保目录名等于 frontmatter name。`],
1266
1280
  };
1267
1281
  if (id === 'host.basic-exposure')
1268
1282
  return {
1269
1283
  id, why: 'OpenCode 只能从项目 exposure 完整继承本次装配的 Basic Skill 入口、references 与 assets。',
1270
- actions: ['把 canonical dingtalk-basic-behavior 全树物化到 .agents/skills/dingtalk-basic-behavior。'],
1284
+ actions: ['把 canonical dingtalk-basic-behavior 全树放入 agent.skillsRoot;由 dta 在隔离 Host 工作区临时物化 exposure。'],
1271
1285
  };
1272
1286
  if (id === 'host.definition-rule')
1273
1287
  return {
1274
- id, why: 'OpenCode 只会把项目根 AGENTS.md 作为本地原生 rule;重复 custom instruction 会形成冗余通道。',
1275
- actions: ['把项目本体统一为根目录 AGENTS.md,并从 opencode.json#instructions 删除其等价路径。'],
1288
+ id, why: 'Agent Definition 与仓库开发约束必须分离,重复 custom instruction 会形成冗余通道。',
1289
+ actions: [' managed Host 只加载一次 manifest 的 agent.definition;仓库根 AGENTS.md 仅保留 Coding Agent 开发约束。'],
1276
1290
  };
1277
1291
  if (id === 'host.basic-instruction')
1278
1292
  return {
@@ -1282,7 +1296,7 @@ function repairFor(id) {
1282
1296
  if (id.startsWith('host.role-exposure.'))
1283
1297
  return {
1284
1298
  id, why: 'Role Skill 的发布源、Host exposure 与权限必须指向同一版本。',
1285
- actions: [`物化 .agents/skills/${id.slice('host.role-exposure.'.length)},校验 hash 并设置 permission=allow。`],
1299
+ actions: [`修复 agent.skillsRoot 下 ${id.slice('host.role-exposure.'.length)} 的唯一发布源;由 dta 临时物化 Host exposure、校验 hash 并设置 permission=allow。`],
1286
1300
  };
1287
1301
  if (id === 'host.load-probe')
1288
1302
  return {