@xdxer/dingtalk-agent 0.1.4-beta.17 → 0.1.4-beta.19

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 (27) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/dist/bin/dingtalk-agent.js +20 -0
  3. package/dist/bin/dingtalk-agent.js.map +1 -1
  4. package/dist/src/agent-audit.js +1 -1
  5. package/dist/src/agent-audit.js.map +1 -1
  6. package/dist/src/doctor.js +9 -1
  7. package/dist/src/doctor.js.map +1 -1
  8. package/dist/src/host-detect.js +146 -0
  9. package/dist/src/host-detect.js.map +1 -0
  10. package/lab/agent-eval/catalog.json +1 -1
  11. package/lab/agent-eval/classic-failures.json +1 -1
  12. package/package.json +1 -1
  13. package/skills/core/dingtalk-agent-compose/SKILL.md +24 -23
  14. package/skills/core/dingtalk-agent-compose/evals/evals.json +20 -8
  15. package/skills/core/dingtalk-agent-compose/references/agent-definition-contract.md +1 -1
  16. package/skills/core/dingtalk-agent-compose/references/host-loading-contract.md +58 -0
  17. package/skills/core/dingtalk-agent-compose/references/hosts/claude-code.md +48 -0
  18. package/skills/core/dingtalk-agent-compose/references/{opencode-host-contract.md → hosts/opencode.md} +4 -0
  19. package/skills/core/dingtalk-agent-eval/SKILL.md +17 -5
  20. package/skills/core/dingtalk-agent-eval/assets/eval-catalog.template.json +1 -1
  21. package/skills/core/dingtalk-agent-eval/references/eval-topology.md +12 -0
  22. package/skills/core/dingtalk-agent-eval/references/evidence-contract.md +17 -0
  23. package/skills/core/dingtalk-agent-eval/references/failure-to-case.md +35 -0
  24. package/skills/core/dingtalk-agent-eval/references/interactive-debug-channels.md +4 -4
  25. package/skills/core/dingtalk-agent-eval/references/local-connector-smoke.md +75 -0
  26. package/skills/platforms/multica-dingtalk/PLATFORM.md +1 -1
  27. /package/skills/core/dingtalk-agent-compose/assets/{opencode.template.json → hosts/opencode/opencode.template.json} +0 -0
@@ -3,6 +3,7 @@ import { delimiter, dirname, join, resolve } from 'node:path';
3
3
  import { homedir } from 'node:os';
4
4
  import { spawnSync } from 'node:child_process';
5
5
  import { BUNDLED_SKILL_NAMES, GLOBAL_SKILL_NAME, INSTALLABLE_SKILL_NAMES, skillStatus, skillSuiteStatus, } from './skill-manager.js';
6
+ import { detectHosts } from './host-detect.js';
6
7
  export function doctor(packageRoot, options = {}) {
7
8
  const home = options.home || homedir();
8
9
  const env = options.env || process.env;
@@ -117,6 +118,8 @@ export function doctor(packageRoot, options = {}) {
117
118
  ? '运行 `dingtalk-agent skill install`'
118
119
  : '运行 `dingtalk-agent skill status`,确认后执行 `dingtalk-agent skill upgrade`',
119
120
  });
121
+ // 本机 Host 观测独立于 Skill 可见性:只补 detail,不参与 level,也不参与 ready。
122
+ const hosts = detectHosts({ home, env, skills: [...INSTALLABLE_SKILL_NAMES] });
120
123
  for (const client of skill.clients) {
121
124
  const states = suite.skills.map((item) => ({
122
125
  name: item.name,
@@ -125,11 +128,15 @@ export function doctor(packageRoot, options = {}) {
125
128
  const level = states.some((item) => item.state === 'conflict')
126
129
  ? 'fail'
127
130
  : states.some((item) => item.state === 'missing') ? 'warn' : 'pass';
131
+ const host = hosts.find((item) => item.name === client.name);
128
132
  checks.push({
129
133
  id: client.name,
130
134
  level,
131
135
  summary: `${client.label}: ${level === 'pass' ? '可发现内置 Skill' : level === 'fail' ? 'Skill 路径冲突' : '尚不可发现全部内置 Skill'}`,
132
- detail: `${client.discovery} · ${states.map((item) => `${item.name}=${item.state}`).join('; ')}`,
136
+ detail: [
137
+ `${client.discovery} · ${states.map((item) => `${item.name}=${item.state}`).join('; ')}`,
138
+ `CLI ${host?.cli.found ? host.cli.path : `未安装(${host?.cli.command || client.name})`}`,
139
+ ].join(' | '),
133
140
  fix: level === 'pass' ? undefined : '运行 `dingtalk-agent skill install`',
134
141
  });
135
142
  }
@@ -144,6 +151,7 @@ export function doctor(packageRoot, options = {}) {
144
151
  nextSteps,
145
152
  skill,
146
153
  skills: suite.skills,
154
+ hosts,
147
155
  };
148
156
  }
149
157
  /** 把漂移状态渲染成"本机版本→包内版本(状态)",让 doctor 输出能直接指出差距。 */
@@ -1 +1 @@
1
- {"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../src/doctor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AACzE,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAC7D,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AACjC,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAE9C,OAAO,EACL,mBAAmB,EAAE,iBAAiB,EAAE,uBAAuB,EAAE,WAAW,EAAE,gBAAgB,GAE/F,MAAM,oBAAoB,CAAA;AA0B3B,MAAM,UAAU,MAAM,CAAC,WAAmB,EAAE,UAAyB,EAAE;IACrE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,OAAO,EAAE,CAAA;IACtC,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,CAAA;IACtC,MAAM,MAAM,GAAkB,EAAE,CAAA;IAChC,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,WAAW,EAAE,cAAc,CAAC,EAAE,MAAM,CAAC,CAAC,CAAA;IAC/E,MAAM,OAAO,GAAG,MAAM,CAAC,GAAG,CAAC,OAAO,IAAI,EAAE,CAAC,CAAA;IAEzC,MAAM,WAAW,GAAG,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAA;IACzC,MAAM,CAAC,IAAI,CAAC,cAAc,CAAC,WAAW,EAAE,QAAQ,CAAC;QAC/C,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,WAAW,EAAE,EAAE;QAClE,CAAC,CAAC;YACE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,WAAW,KAAK;YAC/D,GAAG,EAAE,wBAAwB;SAC9B,CAAC,CAAA;IAEN,MAAM,GAAG,GAAG,cAAc,CAAC,gBAAgB,EAAE,GAAG,CAAC,CAAA;IACjD,MAAM,KAAK,GAAG,cAAc,CAAC,KAAK,EAAE,GAAG,CAAC,CAAA;IACxC,IAAI,GAAG,IAAI,KAAK,EAAE,CAAC;QACjB,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,WAAW,CAAC,EAAE,GAAG,CAAC,CAAA;QAClD,MAAM,QAAQ,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC,WAAW,CAAC,EAAE,GAAG,CAAC,CAAA;QACnD,MAAM,gBAAgB,GAAG,SAAS,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;QACxE,MAAM,YAAY,GAAG,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;QAClE,MAAM,CAAC,IAAI,CAAC,gBAAgB,KAAK,OAAO,IAAI,YAAY,KAAK,OAAO;YAClE,CAAC,CAAC;gBACE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,gBAAgB,oBAAoB;gBAC9E,MAAM,EAAE,GAAG,GAAG,MAAM,KAAK,EAAE;aAC5B;YACH,CAAC,CAAC;gBACE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,uBAAuB;gBAC1D,MAAM,EAAE,GAAG,GAAG,KAAK,gBAAgB,IAAI,QAAQ,OAAO,KAAK,KAAK,YAAY,IAAI,QAAQ,GAAG;gBAC3F,GAAG,EAAE,4EAA4E,OAAO,QAAQ;aACjG,CAAC,CAAA;IACR,CAAC;SAAM,IAAI,GAAG,IAAI,KAAK,EAAE,CAAC;QACxB,MAAM,CAAC,IAAI,CAAC;YACV,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,sCAAsC;YACzE,MAAM,EAAE,kBAAkB,GAAG,IAAI,SAAS,UAAU,KAAK,IAAI,SAAS,EAAE;YACxE,GAAG,EAAE,4EAA4E,OAAO,QAAQ;SACjG,CAAC,CAAA;IACJ,CAAC;SAAM,CAAC;QACN,MAAM,CAAC,IAAI,CAAC;YACR,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,yBAAyB;YAC5D,MAAM,EAAE,WAAW,GAAG,CAAC,IAAI,IAAI,EAAE,EAAE;YACnC,GAAG,EAAE,4EAA4E,OAAO,QAAQ;SACjG,CAAC,CAAA;IACN,CAAC;IAED,MAAM,GAAG,GAAG,cAAc,CAAC,KAAK,EAAE,GAAG,CAAC,CAAA;IACtC,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,MAAM,CAAC,IAAI,CAAC;YACV,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc;YACjD,GAAG,EAAE,6CAA6C;SACnD,CAAC,CAAA;QACF,MAAM,CAAC,IAAI,CAAC;YACV,EAAE,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa;YACrD,GAAG,EAAE,4BAA4B;SAClC,CAAC,CAAA;IACJ,CAAC;SAAM,CAAC;QACN,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,SAAS,EAAE,UAAU,EAAE,MAAM,CAAC,EAAE,GAAG,CAAC,CAAA;QACrE,MAAM,UAAU,GAAG,UAAU,CAAC,IAAI,EAAE,OAAO,IAAI,SAAS,CAAC,UAAU,CAAC,MAAM,CAAC,CAAA;QAC3E,MAAM,SAAS,GAAG,UAAU,CAAC,EAAE,IAAI,cAAc,CAAC,MAAM,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,EAAE,QAAQ,CAAC,CAAA;QACvG,MAAM,CAAC,IAAI,CAAC,SAAS;YACnB,CAAC,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,UAAU,IAAI,KAAK,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE;YAClF,CAAC,CAAC;gBACE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM;gBACxB,OAAO,EAAE,UAAU,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,UAAU,IAAI,EAAE,gBAAgB,CAAC,CAAC,CAAC,aAAa;gBAChF,MAAM,EAAE,UAAU,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,UAAU,CAAC,KAAK;gBAC9C,GAAG,EAAE,UAAU,CAAC,EAAE,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,sBAAsB;aACjE,CAAC,CAAA;QAEN,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,CAAC,EAAE,GAAG,CAAC,CAAA;QACzE,MAAM,aAAa,GAAG,OAAO,CAAC,EAAE,IAAI,OAAO,CAAC,IAAI,EAAE,aAAa,KAAK,IAAI;YACtE,OAAO,CAAC,IAAI,EAAE,WAAW,KAAK,KAAK,CAAA;QACrC,MAAM,CAAC,IAAI,CAAC,aAAa;YACvB,CAAC,CAAC;gBACE,EAAE,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS;gBACjD,MAAM,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,SAAS,EAAE,OAAO,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC;aACvF;YACH,CAAC,CAAC;gBACE,EAAE,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,gBAAgB;gBACxD,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,qBAAqB,EAAE,GAAG,EAAE,qBAAqB;aAC3E,CAAC,CAAA;IACR,CAAC;IAED,MAAM,KAAK,GAAG,gBAAgB,CAAC,WAAW,EAAE,EAAE,IAAI,EAAE,CAAC,CAAA;IACrD,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,iBAAiB,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAA;IAC7F,MAAM,UAAU,GAAG,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAA;IACvE,MAAM,UAAU,GAAG,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAA;IACtE,MAAM,gBAAgB,GAAG,KAAK,CAAC,MAAM;SAClC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,GAAG,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IACzE,MAAM,YAAY,GAAG,KAAK,CAAC,MAAM;SAC9B,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC;SACzC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,GAAG,IAAI,CAAC,IAAI,IAAI,WAAW,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAChE,+CAA+C;IAC/C,MAAM,aAAa,GAAG,uBAAuB,CAAC,WAAW,EAAE,IAAI,CAAC;SAC7D,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC;SACzC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,GAAG,IAAI,CAAC,IAAI,IAAI,WAAW,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IACrD,MAAM,MAAM,GAAG;QACb,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC;QAC3D,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC,aAAa,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE;KACpE,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IAC7B,MAAM,CAAC,IAAI,CAAC,UAAU,IAAI,CAAC,aAAa,CAAC,MAAM;QAC7C,CAAC,CAAC;YACE,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM;YAC1B,OAAO,EAAE,iBAAiB,gBAAgB,EAAE;YAC5C,MAAM;SACP;QACH,CAAC,CAAC;YACE,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM;YAChD,OAAO,EAAE,UAAU;gBACjB,CAAC,CAAC,kBAAkB,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;gBAC9C,CAAC,CAAC,iBAAiB,YAAY,EAAE;YACnC,MAAM;YACN,GAAG,EAAE,UAAU;gBACb,CAAC,CAAC,mCAAmC;gBACrC,CAAC,CAAC,uEAAuE;SAC5E,CAAC,CAAA;IAEN,KAAK,MAAM,MAAM,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;QACnC,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;YACzC,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,KAAK,EAAE,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,IAAI,SAAS;SACpF,CAAC,CAAC,CAAA;QACH,MAAM,KAAK,GAAe,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,KAAK,UAAU,CAAC;YACxE,CAAC,CAAC,MAAM;YACR,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAA;QACrE,MAAM,CAAC,IAAI,CAAC;YACV,EAAE,EAAE,MAAM,CAAC,IAAI;YACf,KAAK;YACL,OAAO,EAAE,GAAG,MAAM,CAAC,KAAK,KAAK,KAAK,KAAK,MAAM,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,KAAK,KAAK,MAAM,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,iBAAiB,EAAE;YACrH,MAAM,EAAE,GAAG,MAAM,CAAC,SAAS,MAAM,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,GAAG,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;YAChG,GAAG,EAAE,KAAK,KAAK,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,mCAAmC;SACxE,CAAC,CAAA;IACJ,CAAC;IAED,MAAM,SAAS,GAAG,MAAM;SACrB,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,KAAK,MAAM,IAAI,KAAK,CAAC,GAAG,CAAC;SACtD,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,GAAa,CAAC;SACnC,MAAM,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,KAAK,CAAC,CAAA;IAC5D,OAAO;QACL,OAAO,EAAE,yBAAyB;QAClC,KAAK,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,KAAK,MAAM,CAAC;QACtD,MAAM;QACN,SAAS;QACT,KAAK;QACL,MAAM,EAAE,KAAK,CAAC,MAAM;KACrB,CAAA;AACH,CAAC;AAED,kDAAkD;AAClD,SAAS,WAAW,CAAC,IAAuB;IAC1C,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC,gBAAgB,IAAI,GAAG,CAAA;IACxD,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,KAAK,SAAS;QACvC,CAAC,CAAC,cAAc;QAChB,CAAC,CAAC,GAAG,SAAS,IAAI,IAAI,CAAC,OAAO,CAAC,YAAY,IAAI,IAAI,CAAC,SAAS,CAAC,KAAK,GAAG,CAAA;AAC1E,CAAC;AAED,8CAA8C;AAC9C,SAAS,uBAAuB,CAAC,WAAmB,EAAE,IAAa;IACjE,MAAM,IAAI,GAAG,IAAI,GAAG,CAAS,mBAAmB,CAAC,CAAA;IACjD,MAAM,MAAM,GAAwB,EAAE,CAAA;IACtC,KAAK,MAAM,IAAI,IAAI,uBAAuB,EAAE,CAAC;QAC3C,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,SAAQ;QAC5B,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,WAAW,CAAC,WAAW,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAA;YACvD,IAAI,MAAM,CAAC,SAAS,CAAC,MAAM;gBAAE,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;QAClD,CAAC;QAAC,MAAM,CAAC;YACP,wDAAwD;QAC1D,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAA;AACf,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,OAAe,EAAE,MAAyB,OAAO,CAAC,GAAG;IAClF,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,IAAI,EAAE,CAAA;IAC3B,MAAM,UAAU,GAAG,OAAO,CAAC,QAAQ,KAAK,OAAO;QAC7C,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,IAAI,gBAAgB,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC;QAC9C,CAAC,CAAC,CAAC,EAAE,CAAC,CAAA;IACR,KAAK,MAAM,SAAS,IAAI,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC;QAC9D,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;YACnC,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,EAAE,OAAO,GAAG,SAAS,CAAC,WAAW,EAAE,CAAC,CAAA;YACvE,IAAI,CAAC;gBACH,UAAU,CAAC,SAAS,EAAE,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,CAAA;gBACrF,OAAO,SAAS,CAAA;YAClB,CAAC;YAAC,MAAM,CAAC,CAAC,cAAc,CAAC,CAAC;QAC5B,CAAC;IACH,CAAC;IACD,OAAO,EAAE,CAAA;AACX,CAAC;AAED,SAAS,OAAO,CAAC,OAAe,EAAE,IAAc,EAAE,GAAsB;IACtE,MAAM,GAAG,GAAG,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAA;IAC/E,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAA;IAC9C,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAA;IAC9C,IAAI,IAAI,GAAQ,IAAI,CAAA;IACpB,IAAI,CAAC;QAAC,IAAI,GAAG,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;IAAC,CAAC;IAAC,MAAM,CAAC,CAAC,6BAA6B,CAAC,CAAC;IACzF,OAAO;QACL,EAAE,EAAE,GAAG,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK;QAClC,IAAI;QACJ,MAAM;QACN,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,OAAO,IAAI,MAAM,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,OAAO,GAAG,CAAC,MAAM,EAAE,CAAC;KACjG,CAAA;AACH,CAAC;AAED,SAAS,OAAO,CAAC,OAAe,EAAE,IAAc,EAAE,GAAsB;IACtE,MAAM,GAAG,GAAG,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAA;IAC/E,OAAO;QACL,EAAE,EAAE,GAAG,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK;QAClC,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE;KACxC,CAAA;AACH,CAAC;AAED,SAAS,SAAS,CAAC,IAAY;IAC7B,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAA;AAChD,CAAC;AAED,SAAS,cAAc,CAAC,MAAc,EAAE,OAAe;IACrD,MAAM,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,CAAA;IACzE,MAAM,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,CAAA;IAC1E,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QACzD,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;YAAE,OAAO,IAAI,CAAA;QAC1C,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;YAAE,OAAO,KAAK,CAAA;IAC7C,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC","sourcesContent":["import { accessSync, constants, existsSync, readFileSync } from 'node:fs'\nimport { delimiter, dirname, join, resolve } from 'node:path'\nimport { homedir } from 'node:os'\nimport { spawnSync } from 'node:child_process'\n\nimport {\n BUNDLED_SKILL_NAMES, GLOBAL_SKILL_NAME, INSTALLABLE_SKILL_NAMES, skillStatus, skillSuiteStatus,\n type GlobalSkillStatus,\n} from './skill-manager.js'\n\nexport type CheckLevel = 'pass' | 'warn' | 'fail'\n\nexport interface DoctorCheck {\n id: 'node' | 'cli' | 'dws' | 'dws-auth' | 'skill' | 'claude-code' | 'codex' | 'opencode'\n level: CheckLevel\n summary: string\n detail?: string\n fix?: string\n}\n\nexport interface DoctorReport {\n $schema: 'dingtalk-agent/doctor@1'\n ready: boolean\n checks: DoctorCheck[]\n nextSteps: string[]\n skill: GlobalSkillStatus\n skills: GlobalSkillStatus[]\n}\n\nexport interface DoctorOptions {\n home?: string\n env?: NodeJS.ProcessEnv\n}\n\nexport function doctor(packageRoot: string, options: DoctorOptions = {}): DoctorReport {\n const home = options.home || homedir()\n const env = options.env || process.env\n const checks: DoctorCheck[] = []\n const pkg = JSON.parse(readFileSync(join(packageRoot, 'package.json'), 'utf8'))\n const version = String(pkg.version || '')\n\n const nodeVersion = process.versions.node\n checks.push(versionAtLeast(nodeVersion, '18.3.0')\n ? { id: 'node', level: 'pass', summary: `Node.js ${nodeVersion}` }\n : {\n id: 'node', level: 'fail', summary: `Node.js ${nodeVersion} 过旧`,\n fix: '升级到 Node.js 18.3 或更高版本',\n })\n\n const cli = findExecutable('dingtalk-agent', env)\n const alias = findExecutable('dta', env)\n if (cli && alias) {\n const installed = runText(cli, ['--version'], env)\n const aliasRun = runText(alias, ['--version'], env)\n const installedVersion = installed.ok ? firstLine(installed.stdout) : ''\n const aliasVersion = aliasRun.ok ? firstLine(aliasRun.stdout) : ''\n checks.push(installedVersion === version && aliasVersion === version\n ? {\n id: 'cli', level: 'pass', summary: `CLI ${installedVersion} 已在 PATH 中(dta 可用)`,\n detail: `${cli} · ${alias}`,\n }\n : {\n id: 'cli', level: 'warn', summary: 'PATH 中的 CLI 版本与当前包不一致',\n detail: `${cli} (${installedVersion || '无法识别版本'}) · ${alias} (${aliasVersion || '无法识别版本'})`,\n fix: `运行 npx --yes --registry=https://registry.npmjs.org @xdxer/dingtalk-agent@${version} setup`,\n })\n } else if (cli || alias) {\n checks.push({\n id: 'cli', level: 'fail', summary: 'CLI 入口不完整:dingtalk-agent 与 dta 应同时存在',\n detail: `dingtalk-agent=${cli || 'missing'} · dta=${alias || 'missing'}`,\n fix: `运行 npx --yes --registry=https://registry.npmjs.org @xdxer/dingtalk-agent@${version} setup`,\n })\n } else {\n checks.push({\n id: 'cli', level: 'fail', summary: 'PATH 找不到 dingtalk-agent',\n detail: `当前 PATH=${env.PATH || ''}`,\n fix: `运行 npx --yes --registry=https://registry.npmjs.org @xdxer/dingtalk-agent@${version} setup`,\n })\n }\n\n const dws = findExecutable('dws', env)\n if (!dws) {\n checks.push({\n id: 'dws', level: 'fail', summary: 'PATH 找不到 dws',\n fix: '安装 DWS,并确认 `dws version --format json` 可以执行',\n })\n checks.push({\n id: 'dws-auth', level: 'fail', summary: '无法检查 DWS 认证',\n fix: 'DWS 可用后运行 `dws auth login`',\n })\n } else {\n const versionRun = runJson(dws, ['version', '--format', 'json'], env)\n const dwsVersion = versionRun.json?.version || firstLine(versionRun.stdout)\n const supported = versionRun.ok && versionAtLeast(String(dwsVersion || '').replace(/^v/, ''), '1.0.15')\n checks.push(supported\n ? { id: 'dws', level: 'pass', summary: `DWS ${dwsVersion || '可执行'}`, detail: dws }\n : {\n id: 'dws', level: 'fail',\n summary: versionRun.ok ? `DWS ${dwsVersion || ''} 低于最低版本 1.0.15` : 'DWS 存在但无法运行',\n detail: versionRun.ok ? dws : versionRun.error,\n fix: versionRun.ok ? '运行 `dws upgrade`' : '运行 `dws doctor` 检查安装',\n })\n\n const authRun = runJson(dws, ['auth', 'status', '--format', 'json'], env)\n const authenticated = authRun.ok && authRun.json?.authenticated === true &&\n authRun.json?.token_valid !== false\n checks.push(authenticated\n ? {\n id: 'dws-auth', level: 'pass', summary: 'DWS 已认证',\n detail: [authRun.json?.corp_name, authRun.json?.user_name].filter(Boolean).join(' · '),\n }\n : {\n id: 'dws-auth', level: 'fail', summary: 'DWS 尚未认证或凭据已失效',\n detail: authRun.error || 'authenticated=false', fix: '运行 `dws auth login`',\n })\n }\n\n const suite = skillSuiteStatus(packageRoot, { home })\n const skill = suite.skills.find((item) => item.name === GLOBAL_SKILL_NAME) || suite.skills[0]\n const allCurrent = suite.skills.every((item) => item.canonical.current)\n const anyMissing = suite.skills.some((item) => !item.canonical.exists)\n const installedSummary = suite.skills\n .map((item) => `${item.name} ${item.bundled.skillVersion}`).join(' + ')\n const staleSummary = suite.skills\n .filter((item) => !item.canonical.current)\n .map((item) => `${item.name} ${driftPhrase(item)}`).join('; ')\n // 平台 Skill 不属于默认套装:已安装才纳入报告,未安装既不提示也不影响 ready。\n const platformDrift = installedPlatformSkills(packageRoot, home)\n .filter((item) => !item.canonical.current)\n .map((item) => `${item.name} ${driftPhrase(item)}`)\n const detail = [\n suite.skills.map((item) => item.canonical.path).join(' · '),\n platformDrift.length ? `平台 Skill: ${platformDrift.join('; ')}` : '',\n ].filter(Boolean).join(' | ')\n checks.push(allCurrent && !platformDrift.length\n ? {\n id: 'skill', level: 'pass',\n summary: `内置 Skill 已安装: ${installedSummary}`,\n detail,\n }\n : {\n id: 'skill', level: anyMissing ? 'fail' : 'warn',\n summary: allCurrent\n ? `平台 Skill 需要更新: ${platformDrift.join('; ')}`\n : `内置 Skill 不完整: ${staleSummary}`,\n detail,\n fix: anyMissing\n ? '运行 `dingtalk-agent skill install`'\n : '运行 `dingtalk-agent skill status`,确认后执行 `dingtalk-agent skill upgrade`',\n })\n\n for (const client of skill.clients) {\n const states = suite.skills.map((item) => ({\n name: item.name,\n state: item.clients.find((entry) => entry.name === client.name)?.state || 'missing',\n }))\n const level: CheckLevel = states.some((item) => item.state === 'conflict')\n ? 'fail'\n : states.some((item) => item.state === 'missing') ? 'warn' : 'pass'\n checks.push({\n id: client.name,\n level,\n summary: `${client.label}: ${level === 'pass' ? '可发现内置 Skill' : level === 'fail' ? 'Skill 路径冲突' : '尚不可发现全部内置 Skill'}`,\n detail: `${client.discovery} · ${states.map((item) => `${item.name}=${item.state}`).join('; ')}`,\n fix: level === 'pass' ? undefined : '运行 `dingtalk-agent skill install`',\n })\n }\n\n const nextSteps = checks\n .filter((check) => check.level !== 'pass' && check.fix)\n .map((check) => check.fix as string)\n .filter((step, index, all) => all.indexOf(step) === index)\n return {\n $schema: 'dingtalk-agent/doctor@1',\n ready: !checks.some((check) => check.level === 'fail'),\n checks,\n nextSteps,\n skill,\n skills: suite.skills,\n }\n}\n\n/** 把漂移状态渲染成\"本机版本→包内版本(状态)\",让 doctor 输出能直接指出差距。 */\nfunction driftPhrase(item: GlobalSkillStatus): string {\n const installed = item.canonical.installedVersion || '—'\n return item.canonical.state === 'missing'\n ? '未安装(missing)'\n : `${installed}→${item.bundled.skillVersion}(${item.canonical.state})`\n}\n\n/** 已落地的平台 Skill;未安装的不返回,避免把 opt-in 能力报成缺失。 */\nfunction installedPlatformSkills(packageRoot: string, home?: string): GlobalSkillStatus[] {\n const core = new Set<string>(BUNDLED_SKILL_NAMES)\n const result: GlobalSkillStatus[] = []\n for (const name of INSTALLABLE_SKILL_NAMES) {\n if (core.has(name)) continue\n try {\n const status = skillStatus(packageRoot, { home, name })\n if (status.canonical.exists) result.push(status)\n } catch {\n // 平台 Skill 不可用不应让 doctor 失败;核心套装的问题由上面的 skill check 负责。\n }\n }\n return result\n}\n\nexport function findExecutable(command: string, env: NodeJS.ProcessEnv = process.env): string {\n const path = env.PATH || ''\n const extensions = process.platform === 'win32'\n ? (env.PATHEXT || '.EXE;.CMD;.BAT').split(';')\n : ['']\n for (const directory of path.split(delimiter).filter(Boolean)) {\n for (const extension of extensions) {\n const candidate = resolve(directory, command + extension.toLowerCase())\n try {\n accessSync(candidate, process.platform === 'win32' ? constants.F_OK : constants.X_OK)\n return candidate\n } catch { /* continue */ }\n }\n }\n return ''\n}\n\nfunction runJson(command: string, args: string[], env: NodeJS.ProcessEnv) {\n const run = spawnSync(command, args, { encoding: 'utf8', env, timeout: 8_000 })\n const stdout = String(run.stdout || '').trim()\n const stderr = String(run.stderr || '').trim()\n let json: any = null\n try { json = stdout ? JSON.parse(stdout) : null } catch { /* human output is allowed */ }\n return {\n ok: run.status === 0 && !run.error,\n json,\n stdout,\n error: run.error?.message || stderr || (run.signal ? `被 ${run.signal} 终止` : `退出码 ${run.status}`),\n }\n}\n\nfunction runText(command: string, args: string[], env: NodeJS.ProcessEnv) {\n const run = spawnSync(command, args, { encoding: 'utf8', env, timeout: 8_000 })\n return {\n ok: run.status === 0 && !run.error,\n stdout: String(run.stdout || '').trim(),\n }\n}\n\nfunction firstLine(text: string): string {\n return text.split(/\\r?\\n/, 1)[0]?.trim() || ''\n}\n\nfunction versionAtLeast(actual: string, minimum: string): boolean {\n const a = actual.split('.').map((part) => Number.parseInt(part, 10) || 0)\n const b = minimum.split('.').map((part) => Number.parseInt(part, 10) || 0)\n for (let i = 0; i < Math.max(a.length, b.length); i += 1) {\n if ((a[i] || 0) > (b[i] || 0)) return true\n if ((a[i] || 0) < (b[i] || 0)) return false\n }\n return true\n}\n"]}
1
+ {"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../src/doctor.ts"],"names":[],"mappings":"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","sourcesContent":["import { accessSync, constants, existsSync, readFileSync } from 'node:fs'\nimport { delimiter, dirname, join, resolve } from 'node:path'\nimport { homedir } from 'node:os'\nimport { spawnSync } from 'node:child_process'\n\nimport {\n BUNDLED_SKILL_NAMES, GLOBAL_SKILL_NAME, INSTALLABLE_SKILL_NAMES, skillStatus, skillSuiteStatus,\n type GlobalSkillStatus,\n} from './skill-manager.js'\nimport { detectHosts, type HostDetection } from './host-detect.js'\n\nexport type CheckLevel = 'pass' | 'warn' | 'fail'\n\nexport interface DoctorCheck {\n id: 'node' | 'cli' | 'dws' | 'dws-auth' | 'skill' | 'claude-code' | 'codex' | 'opencode'\n level: CheckLevel\n summary: string\n detail?: string\n fix?: string\n}\n\nexport interface DoctorReport {\n $schema: 'dingtalk-agent/doctor@1'\n ready: boolean\n checks: DoctorCheck[]\n nextSteps: string[]\n skill: GlobalSkillStatus\n skills: GlobalSkillStatus[]\n /**\n * 本机 coding agent 观测面:哪些 Host CLI 装了、Skill 对它们是否可见。\n * 它只是选路依据,不参与 `ready` 判定——没装 Claude Code 不等于环境不可用。\n */\n hosts: HostDetection[]\n}\n\nexport interface DoctorOptions {\n home?: string\n env?: NodeJS.ProcessEnv\n}\n\nexport function doctor(packageRoot: string, options: DoctorOptions = {}): DoctorReport {\n const home = options.home || homedir()\n const env = options.env || process.env\n const checks: DoctorCheck[] = []\n const pkg = JSON.parse(readFileSync(join(packageRoot, 'package.json'), 'utf8'))\n const version = String(pkg.version || '')\n\n const nodeVersion = process.versions.node\n checks.push(versionAtLeast(nodeVersion, '18.3.0')\n ? { id: 'node', level: 'pass', summary: `Node.js ${nodeVersion}` }\n : {\n id: 'node', level: 'fail', summary: `Node.js ${nodeVersion} 过旧`,\n fix: '升级到 Node.js 18.3 或更高版本',\n })\n\n const cli = findExecutable('dingtalk-agent', env)\n const alias = findExecutable('dta', env)\n if (cli && alias) {\n const installed = runText(cli, ['--version'], env)\n const aliasRun = runText(alias, ['--version'], env)\n const installedVersion = installed.ok ? firstLine(installed.stdout) : ''\n const aliasVersion = aliasRun.ok ? firstLine(aliasRun.stdout) : ''\n checks.push(installedVersion === version && aliasVersion === version\n ? {\n id: 'cli', level: 'pass', summary: `CLI ${installedVersion} 已在 PATH 中(dta 可用)`,\n detail: `${cli} · ${alias}`,\n }\n : {\n id: 'cli', level: 'warn', summary: 'PATH 中的 CLI 版本与当前包不一致',\n detail: `${cli} (${installedVersion || '无法识别版本'}) · ${alias} (${aliasVersion || '无法识别版本'})`,\n fix: `运行 npx --yes --registry=https://registry.npmjs.org @xdxer/dingtalk-agent@${version} setup`,\n })\n } else if (cli || alias) {\n checks.push({\n id: 'cli', level: 'fail', summary: 'CLI 入口不完整:dingtalk-agent 与 dta 应同时存在',\n detail: `dingtalk-agent=${cli || 'missing'} · dta=${alias || 'missing'}`,\n fix: `运行 npx --yes --registry=https://registry.npmjs.org @xdxer/dingtalk-agent@${version} setup`,\n })\n } else {\n checks.push({\n id: 'cli', level: 'fail', summary: 'PATH 找不到 dingtalk-agent',\n detail: `当前 PATH=${env.PATH || ''}`,\n fix: `运行 npx --yes --registry=https://registry.npmjs.org @xdxer/dingtalk-agent@${version} setup`,\n })\n }\n\n const dws = findExecutable('dws', env)\n if (!dws) {\n checks.push({\n id: 'dws', level: 'fail', summary: 'PATH 找不到 dws',\n fix: '安装 DWS,并确认 `dws version --format json` 可以执行',\n })\n checks.push({\n id: 'dws-auth', level: 'fail', summary: '无法检查 DWS 认证',\n fix: 'DWS 可用后运行 `dws auth login`',\n })\n } else {\n const versionRun = runJson(dws, ['version', '--format', 'json'], env)\n const dwsVersion = versionRun.json?.version || firstLine(versionRun.stdout)\n const supported = versionRun.ok && versionAtLeast(String(dwsVersion || '').replace(/^v/, ''), '1.0.15')\n checks.push(supported\n ? { id: 'dws', level: 'pass', summary: `DWS ${dwsVersion || '可执行'}`, detail: dws }\n : {\n id: 'dws', level: 'fail',\n summary: versionRun.ok ? `DWS ${dwsVersion || ''} 低于最低版本 1.0.15` : 'DWS 存在但无法运行',\n detail: versionRun.ok ? dws : versionRun.error,\n fix: versionRun.ok ? '运行 `dws upgrade`' : '运行 `dws doctor` 检查安装',\n })\n\n const authRun = runJson(dws, ['auth', 'status', '--format', 'json'], env)\n const authenticated = authRun.ok && authRun.json?.authenticated === true &&\n authRun.json?.token_valid !== false\n checks.push(authenticated\n ? {\n id: 'dws-auth', level: 'pass', summary: 'DWS 已认证',\n detail: [authRun.json?.corp_name, authRun.json?.user_name].filter(Boolean).join(' · '),\n }\n : {\n id: 'dws-auth', level: 'fail', summary: 'DWS 尚未认证或凭据已失效',\n detail: authRun.error || 'authenticated=false', fix: '运行 `dws auth login`',\n })\n }\n\n const suite = skillSuiteStatus(packageRoot, { home })\n const skill = suite.skills.find((item) => item.name === GLOBAL_SKILL_NAME) || suite.skills[0]\n const allCurrent = suite.skills.every((item) => item.canonical.current)\n const anyMissing = suite.skills.some((item) => !item.canonical.exists)\n const installedSummary = suite.skills\n .map((item) => `${item.name} ${item.bundled.skillVersion}`).join(' + ')\n const staleSummary = suite.skills\n .filter((item) => !item.canonical.current)\n .map((item) => `${item.name} ${driftPhrase(item)}`).join('; ')\n // 平台 Skill 不属于默认套装:已安装才纳入报告,未安装既不提示也不影响 ready。\n const platformDrift = installedPlatformSkills(packageRoot, home)\n .filter((item) => !item.canonical.current)\n .map((item) => `${item.name} ${driftPhrase(item)}`)\n const detail = [\n suite.skills.map((item) => item.canonical.path).join(' · '),\n platformDrift.length ? `平台 Skill: ${platformDrift.join('; ')}` : '',\n ].filter(Boolean).join(' | ')\n checks.push(allCurrent && !platformDrift.length\n ? {\n id: 'skill', level: 'pass',\n summary: `内置 Skill 已安装: ${installedSummary}`,\n detail,\n }\n : {\n id: 'skill', level: anyMissing ? 'fail' : 'warn',\n summary: allCurrent\n ? `平台 Skill 需要更新: ${platformDrift.join('; ')}`\n : `内置 Skill 不完整: ${staleSummary}`,\n detail,\n fix: anyMissing\n ? '运行 `dingtalk-agent skill install`'\n : '运行 `dingtalk-agent skill status`,确认后执行 `dingtalk-agent skill upgrade`',\n })\n\n // 本机 Host 观测独立于 Skill 可见性:只补 detail,不参与 level,也不参与 ready。\n const hosts = detectHosts({ home, env, skills: [...INSTALLABLE_SKILL_NAMES] })\n for (const client of skill.clients) {\n const states = suite.skills.map((item) => ({\n name: item.name,\n state: item.clients.find((entry) => entry.name === client.name)?.state || 'missing',\n }))\n const level: CheckLevel = states.some((item) => item.state === 'conflict')\n ? 'fail'\n : states.some((item) => item.state === 'missing') ? 'warn' : 'pass'\n const host = hosts.find((item) => item.name === client.name)\n checks.push({\n id: client.name,\n level,\n summary: `${client.label}: ${level === 'pass' ? '可发现内置 Skill' : level === 'fail' ? 'Skill 路径冲突' : '尚不可发现全部内置 Skill'}`,\n detail: [\n `${client.discovery} · ${states.map((item) => `${item.name}=${item.state}`).join('; ')}`,\n `CLI ${host?.cli.found ? host.cli.path : `未安装(${host?.cli.command || client.name})`}`,\n ].join(' | '),\n fix: level === 'pass' ? undefined : '运行 `dingtalk-agent skill install`',\n })\n }\n\n const nextSteps = checks\n .filter((check) => check.level !== 'pass' && check.fix)\n .map((check) => check.fix as string)\n .filter((step, index, all) => all.indexOf(step) === index)\n return {\n $schema: 'dingtalk-agent/doctor@1',\n ready: !checks.some((check) => check.level === 'fail'),\n checks,\n nextSteps,\n skill,\n skills: suite.skills,\n hosts,\n }\n}\n\n/** 把漂移状态渲染成\"本机版本→包内版本(状态)\",让 doctor 输出能直接指出差距。 */\nfunction driftPhrase(item: GlobalSkillStatus): string {\n const installed = item.canonical.installedVersion || '—'\n return item.canonical.state === 'missing'\n ? '未安装(missing)'\n : `${installed}→${item.bundled.skillVersion}(${item.canonical.state})`\n}\n\n/** 已落地的平台 Skill;未安装的不返回,避免把 opt-in 能力报成缺失。 */\nfunction installedPlatformSkills(packageRoot: string, home?: string): GlobalSkillStatus[] {\n const core = new Set<string>(BUNDLED_SKILL_NAMES)\n const result: GlobalSkillStatus[] = []\n for (const name of INSTALLABLE_SKILL_NAMES) {\n if (core.has(name)) continue\n try {\n const status = skillStatus(packageRoot, { home, name })\n if (status.canonical.exists) result.push(status)\n } catch {\n // 平台 Skill 不可用不应让 doctor 失败;核心套装的问题由上面的 skill check 负责。\n }\n }\n return result\n}\n\nexport function findExecutable(command: string, env: NodeJS.ProcessEnv = process.env): string {\n const path = env.PATH || ''\n const extensions = process.platform === 'win32'\n ? (env.PATHEXT || '.EXE;.CMD;.BAT').split(';')\n : ['']\n for (const directory of path.split(delimiter).filter(Boolean)) {\n for (const extension of extensions) {\n const candidate = resolve(directory, command + extension.toLowerCase())\n try {\n accessSync(candidate, process.platform === 'win32' ? constants.F_OK : constants.X_OK)\n return candidate\n } catch { /* continue */ }\n }\n }\n return ''\n}\n\nfunction runJson(command: string, args: string[], env: NodeJS.ProcessEnv) {\n const run = spawnSync(command, args, { encoding: 'utf8', env, timeout: 8_000 })\n const stdout = String(run.stdout || '').trim()\n const stderr = String(run.stderr || '').trim()\n let json: any = null\n try { json = stdout ? JSON.parse(stdout) : null } catch { /* human output is allowed */ }\n return {\n ok: run.status === 0 && !run.error,\n json,\n stdout,\n error: run.error?.message || stderr || (run.signal ? `被 ${run.signal} 终止` : `退出码 ${run.status}`),\n }\n}\n\nfunction runText(command: string, args: string[], env: NodeJS.ProcessEnv) {\n const run = spawnSync(command, args, { encoding: 'utf8', env, timeout: 8_000 })\n return {\n ok: run.status === 0 && !run.error,\n stdout: String(run.stdout || '').trim(),\n }\n}\n\nfunction firstLine(text: string): string {\n return text.split(/\\r?\\n/, 1)[0]?.trim() || ''\n}\n\nfunction versionAtLeast(actual: string, minimum: string): boolean {\n const a = actual.split('.').map((part) => Number.parseInt(part, 10) || 0)\n const b = minimum.split('.').map((part) => Number.parseInt(part, 10) || 0)\n for (let i = 0; i < Math.max(a.length, b.length); i += 1) {\n if ((a[i] || 0) > (b[i] || 0)) return true\n if ((a[i] || 0) < (b[i] || 0)) return false\n }\n return true\n}\n"]}
@@ -0,0 +1,146 @@
1
+ import { existsSync, readdirSync, statSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { homedir } from 'node:os';
4
+ import { spawnSync } from 'node:child_process';
5
+ import { findExecutable } from './doctor.js';
6
+ /** 版本探测的硬上限:Host CLI 卡住不能拖住 doctor。 */
7
+ const VERSION_TIMEOUT_MS = 5_000;
8
+ const HOSTS = [
9
+ {
10
+ name: 'claude-code',
11
+ label: 'Claude Code',
12
+ command: 'claude',
13
+ // Claude Code 读 ~/.claude/skills 下的 symlink,不读 ~/.agents/skills。
14
+ discovery: 'symlink',
15
+ configRoots: (context) => [claudeHome(context)],
16
+ skillRoot: (context) => join(claudeHome(context), 'skills'),
17
+ },
18
+ {
19
+ name: 'codex',
20
+ label: 'Codex',
21
+ command: 'codex',
22
+ // Codex 与 OpenCode 共用 ~/.agents/skills,因此二者的可见性永远同状态。
23
+ discovery: 'shared-agent-skills',
24
+ configRoots: (context) => [
25
+ overrideDir(context, context.env.CODEX_HOME) || join(context.home, '.codex'),
26
+ ],
27
+ skillRoot: (context) => join(context.home, '.agents', 'skills'),
28
+ },
29
+ {
30
+ name: 'opencode',
31
+ label: 'OpenCode',
32
+ command: 'opencode',
33
+ discovery: 'shared-agent-skills',
34
+ configRoots: (context) => [
35
+ join(overrideDir(context, context.env.XDG_CONFIG_HOME) || join(context.home, '.config'), 'opencode'),
36
+ join(context.home, '.opencode'),
37
+ ],
38
+ skillRoot: (context) => join(context.home, '.agents', 'skills'),
39
+ },
40
+ ];
41
+ function claudeHome(context) {
42
+ return overrideDir(context, context.env.CLAUDE_CONFIG_DIR) || join(context.home, '.claude');
43
+ }
44
+ /**
45
+ * 探测本机装了哪些 coding agent CLI,以及内置 Skill 对它们是否可见。
46
+ *
47
+ * 这是只读观测:除 `<cli> --version` 外不执行任何命令,不写盘,也不改变任何 Host 的配置。
48
+ * 它给出选择依据,不替用户选 Host,也不等于该 Host 已经能跑通一次真实 Run。
49
+ */
50
+ export function detectHosts(options = {}) {
51
+ const env = options.env || process.env;
52
+ const home = options.home || env.HOME || homedir();
53
+ // 调用方钉住的 home 与 env 描述的 home 不一致时,env 的目录覆盖属于另一台环境,不予采信。
54
+ const context = {
55
+ home,
56
+ env,
57
+ trustEnvDirs: !options.home || !env.HOME || env.HOME === options.home,
58
+ };
59
+ const probeVersion = options.probeVersion !== false;
60
+ const watched = options.skills?.length ? new Set(options.skills) : null;
61
+ const roots = new Map();
62
+ for (const spec of HOSTS) {
63
+ const root = spec.skillRoot(context);
64
+ roots.set(root, [...(roots.get(root) || []), spec.name]);
65
+ }
66
+ return HOSTS.map((spec) => {
67
+ const root = spec.skillRoot(context);
68
+ const path = safeFind(spec.command, env);
69
+ const present = listSkills(root);
70
+ return {
71
+ name: spec.name,
72
+ label: spec.label,
73
+ cli: {
74
+ command: spec.command,
75
+ found: Boolean(path),
76
+ path,
77
+ version: path && probeVersion ? probeCliVersion(path, env) : '',
78
+ },
79
+ configRoots: spec.configRoots(context).filter((candidate) => isDirectory(candidate)),
80
+ skillExposureRoot: root,
81
+ skillExposure: {
82
+ root,
83
+ exists: isDirectory(root),
84
+ discovery: spec.discovery,
85
+ sharedWith: (roots.get(root) || []).filter((name) => name !== spec.name),
86
+ skillCount: present.length,
87
+ skills: watched ? present.filter((name) => watched.has(name)) : present,
88
+ },
89
+ };
90
+ });
91
+ }
92
+ /**
93
+ * 环境变量覆盖只在 `trustEnvDirs` 成立时生效;空串、纯空白或不存在的目录一律退回默认路径。
94
+ * 覆盖来自另一个 home 上下文时必须失效,否则被钉住的探测会报出调用方的真实配置目录。
95
+ */
96
+ function overrideDir(context, value) {
97
+ if (!context.trustEnvDirs)
98
+ return '';
99
+ const trimmed = String(value || '').trim();
100
+ return trimmed && isDirectory(trimmed) ? trimmed : '';
101
+ }
102
+ /** PATH 为空或不可解析时返回空串;探测不能让 doctor 崩溃。 */
103
+ function safeFind(command, env) {
104
+ try {
105
+ return findExecutable(command, env);
106
+ }
107
+ catch {
108
+ return '';
109
+ }
110
+ }
111
+ /** 超时、非零退出、二进制损坏都记为空版本;只记录,不上报为 Host 缺失。 */
112
+ function probeCliVersion(path, env) {
113
+ try {
114
+ const run = spawnSync(path, ['--version'], {
115
+ encoding: 'utf8', env, timeout: VERSION_TIMEOUT_MS, windowsHide: true,
116
+ });
117
+ if (run.error || run.status !== 0)
118
+ return '';
119
+ return String(run.stdout || '').split(/\r?\n/, 1)[0]?.trim() || '';
120
+ }
121
+ catch {
122
+ return '';
123
+ }
124
+ }
125
+ /** 只有含 SKILL.md 的目录才算一个可被发现的 Skill;散落文件与空目录不计入。 */
126
+ function listSkills(root) {
127
+ try {
128
+ return readdirSync(root, { withFileTypes: true })
129
+ .filter((entry) => entry.isDirectory() || entry.isSymbolicLink())
130
+ .map((entry) => entry.name)
131
+ .filter((name) => existsSync(join(root, name, 'SKILL.md')))
132
+ .sort();
133
+ }
134
+ catch {
135
+ return [];
136
+ }
137
+ }
138
+ function isDirectory(path) {
139
+ try {
140
+ return statSync(path).isDirectory();
141
+ }
142
+ catch {
143
+ return false;
144
+ }
145
+ }
146
+ //# sourceMappingURL=host-detect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"host-detect.js","sourceRoot":"","sources":["../../src/host-detect.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, readdirSync, statSync } from 'node:fs'\nimport { join } from 'node:path'\nimport { homedir } from 'node:os'\nimport { spawnSync } from 'node:child_process'\n\nimport { findExecutable } from './doctor.js'\n\nexport type HostName = 'claude-code' | 'codex' | 'opencode'\n\n/** 二进制是否在 PATH 上。这只回答\"能不能启动这个 Host\",不回答它能不能看见 Skill。 */\nexport interface HostCli {\n command: string\n found: boolean\n path: string\n /** `<cli> --version` 的首行;探测失败、超时或不可执行时为空串,不抛错也不降级成 found=false。 */\n version: string\n}\n\n/**\n * Skill 对该 Host 的可见性。与 `cli.found` 是两条独立轨迹:\n * 装了 CLI 不代表 Skill 已暴露,暴露了 Skill 也不代表本机装过 CLI。\n */\nexport interface HostSkillExposure {\n root: string\n exists: boolean\n discovery: 'symlink' | 'shared-agent-skills'\n /** 与该 Host 共用同一个 exposure root 的其它 Host;共用意味着它们的可见性天然同状态。 */\n sharedWith: HostName[]\n /** root 下含 SKILL.md 的目录总数,包含与本项目无关的第三方 Skill。 */\n skillCount: number\n /** 其中命中 `skills` 关注名单的目录名,按字典序;未指定名单时等于全部。 */\n skills: string[]\n}\n\nexport interface HostDetection {\n name: HostName\n label: string\n cli: HostCli\n /** 本机真实存在的配置目录;不存在的候选路径不进入该数组。 */\n configRoots: string[]\n /** `skillExposure.root` 的平铺别名,便于调用方直接引用。 */\n skillExposureRoot: string\n skillExposure: HostSkillExposure\n}\n\nexport interface HostDetectOptions {\n home?: string\n env?: NodeJS.ProcessEnv\n /** 默认 true。置 false 只做文件系统与 PATH 观测,不 spawn 任何子进程。 */\n probeVersion?: boolean\n /** 关注的 Skill 名单;缺省时列出 exposure root 下的全部 Skill。 */\n skills?: string[]\n}\n\n/**\n * 解析出的路径上下文。`env` 只在它确实描述 `home` 这台环境时才参与解析:\n * 调用方钉住一个与 `env.HOME` 不同的 home 时,环境变量属于另一个上下文,必须失效。\n */\ninterface HostContext {\n home: string\n env: NodeJS.ProcessEnv\n /** true 表示可以采信 CLAUDE_CONFIG_DIR / CODEX_HOME / XDG_CONFIG_HOME。 */\n trustEnvDirs: boolean\n}\n\ninterface HostSpec {\n name: HostName\n label: string\n command: string\n discovery: 'symlink' | 'shared-agent-skills'\n configRoots: (context: HostContext) => string[]\n skillRoot: (context: HostContext) => string\n}\n\n/** 版本探测的硬上限:Host CLI 卡住不能拖住 doctor。 */\nconst VERSION_TIMEOUT_MS = 5_000\n\nconst HOSTS: HostSpec[] = [\n {\n name: 'claude-code',\n label: 'Claude Code',\n command: 'claude',\n // Claude Code 读 ~/.claude/skills 下的 symlink,不读 ~/.agents/skills。\n discovery: 'symlink',\n configRoots: (context) => [claudeHome(context)],\n skillRoot: (context) => join(claudeHome(context), 'skills'),\n },\n {\n name: 'codex',\n label: 'Codex',\n command: 'codex',\n // Codex 与 OpenCode 共用 ~/.agents/skills,因此二者的可见性永远同状态。\n discovery: 'shared-agent-skills',\n configRoots: (context) => [\n overrideDir(context, context.env.CODEX_HOME) || join(context.home, '.codex'),\n ],\n skillRoot: (context) => join(context.home, '.agents', 'skills'),\n },\n {\n name: 'opencode',\n label: 'OpenCode',\n command: 'opencode',\n discovery: 'shared-agent-skills',\n configRoots: (context) => [\n join(\n overrideDir(context, context.env.XDG_CONFIG_HOME) || join(context.home, '.config'),\n 'opencode',\n ),\n join(context.home, '.opencode'),\n ],\n skillRoot: (context) => join(context.home, '.agents', 'skills'),\n },\n]\n\nfunction claudeHome(context: HostContext): string {\n return overrideDir(context, context.env.CLAUDE_CONFIG_DIR) || join(context.home, '.claude')\n}\n\n/**\n * 探测本机装了哪些 coding agent CLI,以及内置 Skill 对它们是否可见。\n *\n * 这是只读观测:除 `<cli> --version` 外不执行任何命令,不写盘,也不改变任何 Host 的配置。\n * 它给出选择依据,不替用户选 Host,也不等于该 Host 已经能跑通一次真实 Run。\n */\nexport function detectHosts(options: HostDetectOptions = {}): HostDetection[] {\n const env = options.env || process.env\n const home = options.home || env.HOME || homedir()\n // 调用方钉住的 home 与 env 描述的 home 不一致时,env 的目录覆盖属于另一台环境,不予采信。\n const context: HostContext = {\n home,\n env,\n trustEnvDirs: !options.home || !env.HOME || env.HOME === options.home,\n }\n const probeVersion = options.probeVersion !== false\n const watched = options.skills?.length ? new Set(options.skills) : null\n const roots = new Map<string, HostName[]>()\n for (const spec of HOSTS) {\n const root = spec.skillRoot(context)\n roots.set(root, [...(roots.get(root) || []), spec.name])\n }\n return HOSTS.map((spec) => {\n const root = spec.skillRoot(context)\n const path = safeFind(spec.command, env)\n const present = listSkills(root)\n return {\n name: spec.name,\n label: spec.label,\n cli: {\n command: spec.command,\n found: Boolean(path),\n path,\n version: path && probeVersion ? probeCliVersion(path, env) : '',\n },\n configRoots: spec.configRoots(context).filter((candidate) => isDirectory(candidate)),\n skillExposureRoot: root,\n skillExposure: {\n root,\n exists: isDirectory(root),\n discovery: spec.discovery,\n sharedWith: (roots.get(root) || []).filter((name) => name !== spec.name),\n skillCount: present.length,\n skills: watched ? present.filter((name) => watched.has(name)) : present,\n },\n }\n })\n}\n\n/**\n * 环境变量覆盖只在 `trustEnvDirs` 成立时生效;空串、纯空白或不存在的目录一律退回默认路径。\n * 覆盖来自另一个 home 上下文时必须失效,否则被钉住的探测会报出调用方的真实配置目录。\n */\nfunction overrideDir(context: HostContext, value: string | undefined): string {\n if (!context.trustEnvDirs) return ''\n const trimmed = String(value || '').trim()\n return trimmed && isDirectory(trimmed) ? trimmed : ''\n}\n\n/** PATH 为空或不可解析时返回空串;探测不能让 doctor 崩溃。 */\nfunction safeFind(command: string, env: NodeJS.ProcessEnv): string {\n try {\n return findExecutable(command, env)\n } catch {\n return ''\n }\n}\n\n/** 超时、非零退出、二进制损坏都记为空版本;只记录,不上报为 Host 缺失。 */\nfunction probeCliVersion(path: string, env: NodeJS.ProcessEnv): string {\n try {\n const run = spawnSync(path, ['--version'], {\n encoding: 'utf8', env, timeout: VERSION_TIMEOUT_MS, windowsHide: true,\n })\n if (run.error || run.status !== 0) return ''\n return String(run.stdout || '').split(/\\r?\\n/, 1)[0]?.trim() || ''\n } catch {\n return ''\n }\n}\n\n/** 只有含 SKILL.md 的目录才算一个可被发现的 Skill;散落文件与空目录不计入。 */\nfunction listSkills(root: string): string[] {\n try {\n return readdirSync(root, { withFileTypes: true })\n .filter((entry) => entry.isDirectory() || entry.isSymbolicLink())\n .map((entry) => entry.name)\n .filter((name) => existsSync(join(root, name, 'SKILL.md')))\n .sort()\n } catch {\n return []\n }\n}\n\nfunction isDirectory(path: string): boolean {\n try {\n return statSync(path).isDirectory()\n } catch {\n return false\n }\n}\n"]}
@@ -13,7 +13,7 @@
13
13
  { "suite": "lab/robot-eval/suite.json", "ids": ["basic-skill-load"] },
14
14
  { "suite": "lab/agent-eval/classic-failures.json", "ids": ["basic-skill-load"] }
15
15
  ],
16
- "sourceRefs": ["skills/core/dingtalk-agent-compose/references/opencode-host-contract.md"]
16
+ "sourceRefs": ["skills/core/dingtalk-agent-compose/references/hosts/opencode.md"]
17
17
  },
18
18
  {
19
19
  "id": "conversation-contract",
@@ -14,7 +14,7 @@
14
14
  "capability": "basic-skill",
15
15
  "prompt": "只回显当前上下文中的随机加载探针。",
16
16
  "expectations": [{ "id": "placeholder", "type": "equals", "value": "runner-owned" }],
17
- "sourceRefs": ["skills/core/dingtalk-agent-compose/references/opencode-host-contract.md"],
17
+ "sourceRefs": ["skills/core/dingtalk-agent-compose/references/hosts/opencode.md"],
18
18
  "manualChecks": []
19
19
  },
20
20
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xdxer/dingtalk-agent",
3
- "version": "0.1.4-beta.17",
3
+ "version": "0.1.4-beta.19",
4
4
  "description": "钉钉数字员工的 Skill-first 行为范式:全局 Skill 决策,CLI 固定事务边界,Workspace 按需。",
5
5
  "keywords": [
6
6
  "dingtalk",
@@ -3,7 +3,7 @@ name: dingtalk-agent-compose
3
3
  description: 当用户要创建、新建、装配一个 Agent 或钉钉数字员工——包括把 GitHub 仓库、本地文件夹或钉钉文档定义成 Agent,或要审计、补齐、优化 Agent 的 AGENTS.md、本体职责、岗位 Skills、记忆/知识/产物存储与 DWS 权限绑定时使用。即使尚未 init Workspace,也按 dingtalk-agent 的 AgentDefinition 范式给出可运行的最小装配方案;不负责事件触发器。
4
4
  compatibility: Requires dingtalk-agent on PATH; remote DingTalk documents require authenticated dws.
5
5
  metadata:
6
- version: "0.11.0"
6
+ version: "0.12.0"
7
7
  ---
8
8
 
9
9
  # 装配一个可工作的钉钉数字员工 Agent
@@ -14,12 +14,13 @@ metadata:
14
14
 
15
15
  1. 识别来源和运行方式:GitHub 先由宿主 clone/checkout,本 Skill 不接管凭证;本地目录直接读取;钉钉文档只承担 memory/knowledge 等远端语义状态。本体 `AGENTS.md` 与 Role Skills 保持在本地、可版本化。
16
16
  2. **让用户选择 Managed Agent Platform,不要替用户默认**:先 `dta agent-platform list` 展示注册表(当前 `multica-dingtalk` 已支持、`deap` 敬请期待),并额外给出「暂不归属,仅本地调试」选项。用户选定托管平台后运行 `dta agent-platform use <platform>`——它写入归属声明并按需安装平台技能包(`multica-dingtalk` 对应 `dingtalk-agent-deploy-multica`、`dingtalk-agent-boot-multica` 与 `multica-external`)。命令会同时输出 readiness 检查:multica CLI 未安装时按提示安装(`curl -fsSL https://raw.githubusercontent.com/multica-ai/multica/main/scripts/install.sh | bash`),未登录时 readiness 会给出带解析 endpoint 的完整登录命令(endpoint 来源见 `dta agent-platform show` 的 Endpoint 行:env `MULTICA_SERVER_URL` > 项目 config > profile > 建议值;建议值为预发测试环境,标「未确认」),检测到代理环境变量时提醒连接失败可用 `env -u` 剥离。readiness 未过先引导用户补齐,再继续装配;选「暂不归属」则跳过,后续仍可随时归属。切换到某平台后,`agent-platform use/show` 会给出该平台的 `平台说明: <PLATFORM.md 路径>`——先读它,了解该平台各技能(deploy/boot/ops 各角色)的用途、完整交付链与绑定/验收/解绑方式,再开始平台侧操作。
17
- 3. 对已有仓库优先运行 `dta agent enhance --project-name <name> --role-skill <role> --dry-run --json`。它只生成 `agent-enhancement-plan@1`,不会写文件、访问 DWS 或创建 Trigger。审阅 operations、blockers semanticReview 后,才复制计划给出的命令,用同一组参数、当前 `planId` `--yes` 落盘。
18
- 4. apply 只允许本地文件副作用:先把被更新的旧文件备份到 `.dingtalk-agent/backups/agent-enhance/<operationId>/`,再写入并按 hash 回读;自定义 private state 目录必须同步进入 `.gitignore`。输入漂移、planId 过期、非法 Role 路径、未知 OpenCode instruction、路径越界或 symlink 都必须 fail closed。不要跳过 plan,也不要把 `--yes` 写进无人审阅的默认脚本。
19
- 5. 审核本体:优先使用 `AGENTS.md` 精简表达四块长期语义——定义、不能做的底线、做事标准范式、常犯错误。模板必须保留一条 Basic 启动继承声明,以及不猜身份/目标/权限、不扩大副作用、不虚报完成、隐私不越界四条最小公共底线;完整协议仍只在 Basic Skill。CLI 只补骨架,绝不虚构岗位语义;只要 `AGENTS.md` Role Skill 仍含模板 `<...>`,`agent audit` 必须保持 `partial`。
20
- 6. 审核能力:Basic Behavior 是所有钉钉员工共享且**每个 Session 必须加载**的协议;岗位知识和流程拆到独立 Role/Workflow Skill。目录可发现不等于正文已加载,必须为目标 Agent Host 生成可验证的加载合同。不要把 FDE、招聘、事故处理等岗位方法写回 Basic Skill。
21
- 7. 审核存储:明确工作记忆、业务事实、长期知识、产物和宿主私有控制状态分别去哪。介质可以换,语义层与控制状态不能混。远端 memory/knowledge plan 必须显式绑定 profile expected user,但 enhance 本身仍不读写远端。
22
- 8. 运行 `dta bootstrap --bindings agent.bindings.json --json` `dta agent audit --bindings agent.bindings.json --require-skill <role> --json`。静态配置和真实语义通过后再加 `--verify-load --yes`;不能把“写出了文件”或“目录存在”当成装配完成。
17
+ 3. **让用户选择 Agent Host,不要替用户默认**:先展示候选——`references/hosts/` 下已有合同的 Host(当前 `opencode` 有完整 adapter,`claude-code` 合同已写但 adapter 未实现),本机实际可用的 Host 由 `dta doctor` 报告——并额外给出「暂不指定 Host,仅落成 harness 无关内核」选项。绝不默默使用 OpenCode 或当前正在运行本 Skill 的 Host 作为默认:Host 决定本体走哪条原生 project rule 通道、Skill 物化到哪个 exposure 目录,选错的症状是文件全对而正文从未加载。用户选定后按 `references/hosts/<host>.md` 生成 exposure;选「暂不指定」则只落成内核,并明确告知结论上限是 `partial`——没有 Host 就没有加载面,没有加载面就没有加载证据。
18
+ 4. 对已有仓库优先运行 `dta agent enhance --project-name <name> --role-skill <role> --dry-run --json`。它只生成 `agent-enhancement-plan@1`,不会写文件、访问 DWS 或创建 Trigger。审阅 operations、blockers semanticReview 后,才复制计划给出的命令,用同一组参数、当前 `planId` `--yes` 落盘。
19
+ 5. apply 只允许本地文件副作用:先把被更新的旧文件备份到 `.dingtalk-agent/backups/agent-enhance/<operationId>/`,再写入并按 hash 回读;自定义 private state 目录必须同步进入 `.gitignore`。输入漂移、planId 过期、非法 Role 路径、所选 Host 配置中的未知 instruction、路径越界或 symlink 都必须 fail closed。不要跳过 plan,也不要把 `--yes` 写进无人审阅的默认脚本。
20
+ 6. 审核本体:优先使用 `AGENTS.md` 精简表达四块长期语义——定义、不能做的底线、做事标准范式、常犯错误。模板必须保留一条 Basic 启动继承声明,以及不猜身份/目标/权限、不扩大副作用、不虚报完成、隐私不越界四条最小公共底线;完整协议仍只在 Basic Skill。CLI 只补骨架,绝不虚构岗位语义;只要 `AGENTS.md` 或 Role Skill 仍含模板 `<...>`,`agent audit` 必须保持 `partial`。
21
+ 7. 审核能力:Basic Behavior 是所有钉钉员工共享且**每个 Session 必须加载**的协议;岗位知识和流程拆到独立 Role/Workflow Skill。目录可发现不等于正文已加载,必须为目标 Agent Host 生成可验证的加载合同。不要把 FDE、招聘、事故处理等岗位方法写回 Basic Skill。
22
+ 8. 审核存储:明确工作记忆、业务事实、长期知识、产物和宿主私有控制状态分别去哪。介质可以换,语义层与控制状态不能混。远端 memory/knowledge plan 必须显式绑定 profile expected user,但 enhance 本身仍不读写远端。
23
+ 9. 运行 `dta bootstrap --bindings agent.bindings.json --json` 和 `dta agent audit --bindings agent.bindings.json --require-skill <role> --json`。静态配置和真实语义通过后再加 `--verify-load --yes`;不能把“写出了文件”或“目录存在”当成装配完成。
23
24
 
24
25
  ## 本体、Skill 与 Gate 怎么分
25
26
 
@@ -32,27 +33,26 @@ metadata:
32
33
 
33
34
  ## Agent Host 加载合同
34
35
 
35
- 装配结果必须同时存在两层:`skills/` 是 dta 的 Definition/发布源,目标 Host 的原生目录是运行时 exposure。只写一句“请应用某 Skill”或只把 `SKILL.md` 放进目录都不是完成。
36
+ 装配结果必须同时存在两层:`skills/` 是 dta 的 Definition/发布源,所选 Host 的原生目录是运行时 exposure。只写一句“请应用某 Skill”或只把 `SKILL.md` 放进目录都不是完成。
36
37
 
37
- OpenCode 的最小合同是:
38
+ 加载合同与 Host 分离:**四条不变量对所有 Host 相同,机制由所选 Host 决定**。
38
39
 
39
40
  ```text
40
- Agent Definition = AGENTS.md
41
- .agents/skills/dingtalk-basic-behavior/SKILL.md
42
- OpenCode native project rule = AGENTS.md
43
- opencode.json#instructions = [".agents/skills/dingtalk-basic-behavior/SKILL.md"]
44
- opencode.json#permission.skill.dingtalk-basic-behavior = "allow"
41
+ 1 Definition Host 原生 project rule 加载,不重复进入 custom instructions
42
+ 2 Basic Skill 正文是每 Session 唯一的 resolved 强制指令,不是“可发现”
43
+ 3 Basic exposure 整树物化,tree hash canonical 一致,不丢 references / assets
44
+ 4 Role Skill 可发现且已授权,但不进强制加载集
45
45
  ```
46
46
 
47
- Why:OpenCode 会把项目根 `AGENTS.md` 作为原生 project rule;不要再把它重复写进 custom instructions。受管的本体与 Basic 使用精确路径;已有 glob/extglob 只有在可证明不命中二者及其 alias 时才保留。发现 `.agents/skills/<name>/SKILL.md` 也不等于正文已加载,Basic 必须成为每 Session 唯一的 resolved custom instruction,Role Skill 保持按任务触发。Basic exposure 必须整树物化,不能只复制入口而丢掉 references / assets。完整配置、Role Skill exposure 和验收命令见 [opencode-host-contract.md](references/opencode-host-contract.md),可从 [opencode.template.json](assets/opencode.template.json) 裁剪。
47
+ 完整判据、三类加载证据与「没有 adapter 只能 partial」的规则见 [host-loading-contract.md](references/host-loading-contract.md);具体配置、exposure 目录与验收命令见 `references/hosts/<所选 Host>.md`。当前 [opencode.md](references/hosts/opencode.md) 是唯一有完整 adapter、可签发加载证据从而支持 `ready` 的 Host,配置可从 [opencode.template.json](assets/hosts/opencode/opencode.template.json) 裁剪;[claude-code.md](references/hosts/claude-code.md) 已写明加载机制(`CLAUDE.md` 的 `@AGENTS.md` 引用或符号链接、`.claude/skills` exposure),但 adapter 未实现,结论上限是 `partial`。不要把某个 Host 的机制当成通用规则,也不要把一个 Host 上取得的 load evidence 转述成另一个 Host 的结论。
48
48
 
49
- 任何 `ready` 结论都必须保留以下证据:Agent Definition 是根 `AGENTS.md` 原生 rule、未重复进入 custom instructions,且隔离副本中的随机 canary 被零工具精确回显;Basic Skill 的 name/version/SKILL hash/全树 hash 与项目内发现路径一致,入口随机 probe 与 anti-guess baseline 都通过;风险/授权代表性场景只允许目标文件 read、拒绝 external directory,用一次从首行覆盖全文的读取取得首尾随机值。证据还要绑定原始轨迹、模型与当前 OpenCode 版本,并在签发前复验 Definition、Bindings、Host config 与 Skill 未漂移。全树 hash 证明所有 references/assets 完整存在;代表性 canary 不等于每条 reference 路由都已逐项模型验证。`agent-audit@1` 会把 Definition、Storage、Host exposure 与 load gate 分开报告;任一 load gate 失败时,后续回答再像员工也只能算碰巧命中,不能算本体与 Skill 已继承。
49
+ 任何 `ready` 结论都必须保留三类证据:Agent Definition 经原生 rule 加载、未重复进入 custom instructions,且隔离副本中的随机 canary 被零工具精确回显;Basic Skill 的 name/version/SKILL hash/全树 hash 与项目内发现路径一致,入口随机 probe 与 anti-guess baseline 都通过;风险/授权代表性场景只允许目标文件 read、拒绝 external directory,用一次从首行覆盖全文的读取取得首尾随机值。证据形态通用,绑定项(Host 名、Host 版本、模型、原始轨迹)由该 Host 的 adapter 提供,并在签发前复验 Definition、Bindings、Host config 与 Skill 未漂移。全树 hash 证明所有 references/assets 完整存在;代表性 canary 不等于每条 reference 路由都已逐项模型验证。`agent-audit@1` 会把 Definition、Storage、Host exposure 与 load gate 分开报告;任一 load gate 失败时,后续回答再像员工也只能算碰巧命中,不能算本体与 Skill 已继承。未选定 Host 或 Host 无 adapter 时只能 `partial`,不能因为“没声明 Host”就跳过检查拿到 `ready`。
50
50
 
51
51
  ## 连接钉钉机器人的两条路径
52
52
 
53
53
  装配通过 audit `ready` 后,把 Agent 接到钉钉聊天有两条路径,必须呈现给用户选择:
54
54
 
55
- - **本地调试(不需要托管平台)**:直接在宿主(OpenCode 等)里基于 `AGENTS.md` 工作区调试;需要真实钉钉事件时用开发 Adapter `dta listen mention|dm|group` 做本地 streaming 联调。适合开发期验证行为,不适合常驻服务。
55
+ - **本地调试(不需要托管平台)**:直接在所选 Host(当前唯一有完整 adapter 的是 OpenCode)里基于 `AGENTS.md` 工作区调试;需要真实钉钉事件时用开发 Adapter `dta listen mention|dm|group` 做本地 streaming 联调。适合开发期验证行为,不适合常驻服务。
56
56
  - **发布到 Multica 托管平台(推荐正式使用)**:归属 `multica-dingtalk` 后,用平台技能包 `multica-external`(`python3 scripts/multica_ext.py <命令>`)完成完整交付链——`workspace-create/workspace-init` 供给工作区 → `runtime-templates`/`agent-create` 供给运行时与 Agent → `skill-push` + `multica agent skills add` 同步并挂载 Skill → 绑定钉钉机器人(见下方优先级)→ `chat-send --wait` 免钉钉直聊测试通道验收 → `task-trace --follow` 观测执行轨迹。绑定完成后用户在钉钉向机器人发消息即可到达该 Agent。
57
57
 
58
58
  装配或部署完成后,用户下一句通常是“怎么测一下”。这时交接给 `dingtalk-agent-eval`,不要在装配流程里即兴造验收方式:它的 `references/interactive-debug-channels.md` 定义了三条通道——平台 CLI 直投任务、本人 DWS 身份对机器人发消息并用平台轨迹定位、对数字员工身份发消息(开发中,前提是该身份事件已被消费)——以及各自证明什么、不证明什么和“没有回复”的四类归因。该 Skill 不在默认套装内,未安装时先 `dta skill install --name dingtalk-agent-eval`。装配侧只负责把机器人绑好并交出 Agent ID,不负责给行为打分。
@@ -94,7 +94,7 @@ dta bootstrap --bindings agent.bindings.json --json
94
94
  dta agent audit --bindings agent.bindings.json \
95
95
  --require-skill <role-skill-name> --json
96
96
 
97
- # 取得 Basic Skill 真实加载证据;只调用本地 OpenCode,不访问钉钉
97
+ # 取得 Basic Skill 真实加载证据;只调用本地 Host adapter(当前仅 OpenCode 可用),不访问钉钉
98
98
  dta agent audit --bindings agent.bindings.json \
99
99
  --require-skill <role-skill-name> --verify-load --yes --json
100
100
 
@@ -107,7 +107,7 @@ dta agent audit --bindings agent.bindings.json \
107
107
 
108
108
  `agent.bindings.json` 是可版本化的推荐入口,schema 为 `dingtalk-agent/agent-bindings@1`;它不保存凭据,只保存 Provider 路由和可信身份约束。等价配置仍可由宿主 `context`、环境变量或 Workspace manifest 提供。优先级见 [agent-definition-contract.md](references/agent-definition-contract.md),介质选择见 [storage-routing.md](references/storage-routing.md)。
109
109
 
110
- 空目录或手工装配仍可从本目录的 bindings、AGENTS、Role Skill 与 OpenCode assets 开始,但已有仓库必须优先用 enhance 做合并、备份和漂移保护,避免机械复制覆盖用户内容。
110
+ 空目录或手工装配仍可从本目录的 bindings、AGENTS、Role Skill 与 `assets/hosts/<所选 Host>/` 开始,但已有仓库必须优先用 enhance 做合并、备份和漂移保护,避免机械复制覆盖用户内容。
111
111
 
112
112
  ## 从材料落成 Prepared Agent
113
113
 
@@ -117,14 +117,14 @@ dta agent audit --bindings agent.bindings.json \
117
117
  my-agent/
118
118
  ├── AGENTS.md 定义、岗位底线、做事范式、常犯错误
119
119
  ├── agent.bindings.json Definition 与语义存储路由
120
- ├── opencode.json OpenCode 原生本体 + 强制 Basic Skill 的 Host 合同
121
- ├── .agents/skills/ OpenCode 的 Skill exposure
122
120
  ├── MEMORY.md 已评审的长期语义记忆
123
121
  ├── knowledge/INDEX.md 知识入口
124
122
  ├── skills/<role>/SKILL.md 一个或多个岗位 Skill
125
123
  └── fields/default/field.json 可信会话、出口与演进边界
126
124
  ```
127
125
 
126
+ 这个内核 harness 无关:它不含任何 Host 的配置文件或 exposure 目录,换 Host 不改动其中任何一个文件。**Host exposure 按所选 Host 追加,不属于本体**——所选 Host 的原生配置文件与 Skill exposure 目录(OpenCode 是 `opencode.json` + `.agents/skills/`,Claude Code 是 `CLAUDE.md` 引用 + `.claude/skills/`)由 `references/hosts/<host>.md` 规定,是运行时可见面,不是 Agent 定义的一部分。把 Host 文件写进内核,等于把本体绑死在一个 harness 上。
127
+
128
128
  普通本地 Agent 到这里即可工作,不需要 `dta init`。只有开发者明确要 Prepared Run、可信事件与长期 Workspace 时才初始化;初始化不得覆盖已有材料。创建 Session 后 Definition 和 Skill manifest 已冻结:新加 Skill 由新 Session 生效,绝不热注入当前 Run。
129
129
 
130
130
  仓库内的 `examples/agents/fde-coach` 与 `examples/agents/release-manager` 是同一内核的两份最小实例。复制时只替换本体、Role Skills、Field/存储绑定和 DWS authority;Basic Behavior、Session/Run/Wait、双半闸门与 Receipt 协议保持共享。
@@ -139,12 +139,13 @@ my-agent/
139
139
  状态:ready / partial
140
140
  本体:来源、缺口、建议改动
141
141
  能力:Basic Skill 强制加载合同 + Role Skills(可叠加项)
142
+ Host:用户选定的 Host 与 adapter 状态;exposure 目录(不属于本体)
142
143
  存储:memory / knowledge / artifacts / private state
143
144
  权限:DWS profile、expected user、写入 allowlist(若有)
144
145
  命令:一组可复制的 bootstrap / agent audit 命令
145
- 验收:`agent-audit@1` readyHost 原生 rule 命中根 AGENTS.md 且 custom instructions 不重复,resolved config 唯一命中 Basic SkillBasic 全树 hash 一致;Definition、Basic 入口/anti-guess 与代表性风险/授权 reference 三类随机 probe 精确通过;Definition hash 稳定;远端模式另有身份与独立 readback;两 Agent 不串 Skill/Session/存储/权限;无可信 target 不外发
146
+ 验收:`agent-audit@1` ready;四条不变量成立——Host 原生 rule 命中根 AGENTS.md 且 custom instructions 不重复,resolved config 唯一命中 Basic SkillBasic 全树 hash 一致,Role Skill 已授权但不进强制集;Definition、Basic 入口/anti-guess 与代表性风险/授权 reference 三类随机 probe 精确通过;Definition hash 稳定;远端模式另有身份与独立 readback;两 Agent 不串 Skill/Session/存储/权限;无可信 target 不外发
146
147
  下一步:交给 `dingtalk-agent-eval` 选联调通道(CLI 直投 / 机器人 / 数字员工)并留证据
147
148
  非范围:事件监听、定时器、Webhook、行为评分
148
149
  ```
149
150
 
150
- 若来源不完整,默认给出 `partial` 和最小缺口;不要为了得到 `ready` 擅自虚构身份、职责、资源 ID 或权限。`enhance plan ready` 只表示“结构改造可以安全执行”,不等于 `agent-audit@1 ready`。
151
+ 若来源不完整,默认给出 `partial` 和最小缺口;不要为了得到 `ready` 擅自虚构身份、职责、资源 ID 或权限。`enhance plan ready` 只表示“结构改造可以安全执行”,不等于 `agent-audit@1 ready`;未选定 Host 或所选 Host 无 adapter 时同样只能 `partial`,缺口写成“该 Host 的加载证据不可得”,不是“无需检查”。
@@ -36,23 +36,24 @@
36
36
  },
37
37
  {
38
38
  "id": 4,
39
- "prompt": "我要把这个招聘 Agent 交给 OpenCode 跑,仓库里已经有 AGENTS.md 和 skills/recruiting/SKILL.md。请完成 Host 组装并告诉我怎么证明 Basic Skill 每轮真的加载了。",
40
- "expected_output": "创建 .agents/skills exposure;让项目根 AGENTS.md 只由 OpenCode 原生 project rule 加载,从 custom instructions 删除其等价路径;仅将 dingtalk-basic-behavior 作为受管 custom instruction 显式注入,并允许 Role Skill;通过 resolved config、项目发现路径、SKILL/full-tree hashversion、Definition/Basic 入口随机 probe 和代表性风险/授权 reference read canary 验收,不能只说文件或目录存在,也不把代表性 canary 冒充全路由验证。",
39
+ "prompt": "我要把这个招聘 Agent 跑起来,仓库里已经有 AGENTS.md 和 skills/recruiting/SKILL.md。请完成 Host 组装并告诉我怎么证明 Basic Skill 每轮真的加载了。",
40
+ "expected_output": "先呈现 Host 候选(opencode 有完整 adapter、claude-code 合同已写但 adapter 未实现,本机可用 Host 由 dta doctor 报告)加「暂不指定」选项让用户选,不替用户默认;再按四条不变量组装——本体由所选 Host 原生 project rule 加载且等价路径不进 custom instructions,Basic 正文是每 Session 唯一 resolved 强制指令,Basic exposure 整树物化且 tree hash 与 canonical 一致,Role Skill 可发现已授权但不进强制集;用 Definition 零工具 canary、Basic 入口 probe + anti-guess baseline、代表性风险/授权 reference 首尾随机值三类证据验收,不能只说文件或目录存在,也不把代表性 canary 冒充全路由验证。",
41
41
  "files": [],
42
42
  "expectations": [
43
- "项目根 AGENTS.md 由原生 project rule 加载且不进入 custom instructions,Basic 是唯一受管 custom instruction",
44
- "Basic source exposure 的 name/version、SKILL hash 和全树 hash 一致",
45
- "Basic permission=allow",
46
- "dta agent audit --verify-load --yes 在隔离 Workspace、屏蔽用户级/全局 instruction/config 面但保留 provider 认证的条件下取得 Definition、Basic 入口和代表性 reference read 三类随机证据"
43
+ "先列 Host 候选并给出「暂不指定」选项让用户选择,不默认某个 Host",
44
+ "按四条不变量而不是某个 Host 的具体 exposure 路径判定组装是否完成",
45
+ "Basic source 与 exposure 的 name/version、SKILL hash 和全树 hash 一致,Role Skill 已授权但不进强制加载集",
46
+ "dta agent audit --verify-load --yes 在隔离 Workspace、屏蔽用户级/全局 instruction/config 面但保留 provider 认证的条件下取得 Definition、Basic 入口和代表性 reference read 三类随机证据,证据绑定 Host 名/版本/模型/轨迹"
47
47
  ]
48
48
  },
49
49
  {
50
50
  "id": 5,
51
51
  "prompt": "我在一个空目录里,想在一小时内做出本地发布经理 Agent。请给我从文件到验收通过的最短路径。",
52
- "expected_output": "创建 AGENTS.md、一个 release-manager Role Skill、MEMORY.md、knowledge/INDEX.md、agent.bindings.json、OpenCode exposure/config;先静态 audit,再 verify-load;不要求 Workspace init 或 DWS。",
52
+ "expected_output": "先落成 harness 无关内核——AGENTS.md、agent.bindings.json、MEMORY.md、knowledge/INDEX.md、skills/release-manager/SKILL.md、fields/default/field.json;再让用户选 Host 并按所选 Host 追加 exposure(不属于内核);先静态 audit,再 verify-load;不要求 Workspace init 或 DWS。",
53
53
  "files": [],
54
54
  "expectations": [
55
- "给出本地 bindings 与最小文件树",
55
+ "最小文件树只含 harness 无关内核,不把任何 Host 的配置文件或 exposure 目录列进本体",
56
+ "Host exposure 作为选定 Host 后的追加步骤单列",
56
57
  "先 partial 修复再进行有模型的 load 验收",
57
58
  "最终 ready 以 Definition、Storage、Host 和 load 四类证据共同决定"
58
59
  ]
@@ -112,6 +113,17 @@
112
113
  "给出通道顺序:先用不碰钉钉的 CLI 直投,再走真实钉钉往返",
113
114
  "说明回复读起来对不等于 Skill 已加载,须用执行轨迹核对"
114
115
  ]
116
+ },
117
+ {
118
+ "id": 11,
119
+ "prompt": "我不想绑定任何编辑器,Agent 定义要 harness 无关。文件都齐了也没声明 Host,能不能直接判 ready?",
120
+ "expected_output": "内核确实 harness 无关(AGENTS.md、agent.bindings.json、MEMORY.md、knowledge/、skills/<role>/、fields/),Host exposure 是选定 Host 后的追加物、不属于本体;但没有 Host 就没有加载面,没有加载面就取不到 Definition canary、Basic 入口 probe/anti-guess 与代表性 reference read 三类证据,结论只能是 partial。列出 Host 候选与「暂不指定」选项让用户选;选 claude-code 也只能 partial,因为其 adapter 未实现。",
121
+ "files": [],
122
+ "expectations": [
123
+ "确认内核 harness 无关且 Host exposure 不进本体",
124
+ "拒绝把「没声明 Host」当成跳过加载检查的豁免,结论保持 partial",
125
+ "无 adapter 的 Host(含 claude-code)不签发加载证据、不给 ready,也不把 OpenCode 的 evidence 迁移过去"
126
+ ]
115
127
  }
116
128
  ]
117
129
  }
@@ -39,7 +39,7 @@ apply 需要当前 planId 与显式 `--yes`,只允许本地文件副作用。
39
39
 
40
40
  在没有 `.dingtalk-agent/workspace.json` 时,`bootstrap` 仍可从 `AGENTS.md`、`skills/`、`MEMORY.md` 与 `knowledge/INDEX.md` 组成 Direct/Mounted Definition;此时没有可信事件 target,不能伪外发。
41
41
 
42
- Definition 能发现 Skill、磁盘上存在 `AGENTS.md`,都不代表模型 Host 已加载正文。compose 必须额外生成 Host contract:OpenCode 由原生 project rule 加载项目根 `AGENTS.md`,并从 custom instructions 删除其等价路径;在本体与 Basic 这两个受管加载面中,只有 Basic exposure 的规范路径唯一写入 `opencode.json#instructions`,并以 Basic 全树 hash 证明 references / assets 完整;其它 Host 使用各自确定性系统指令/Skill 注入机制。没有 resolved-config、全树完整性与 load-probe 证据时只能判为 `partial`。
42
+ Definition 能发现 Skill、磁盘上存在 `AGENTS.md`,都不代表模型 Host 已加载正文。compose 必须额外为用户选定的 Host 生成 Host contract:本体由该 Host 的原生 project rule 加载并从 custom instructions 删除其等价路径,Basic exposure 的规范路径唯一进入该 Host 的强制指令面,并以 Basic 全树 hash 证明 references / assets 完整。四条不变量与三类证据见 [host-loading-contract.md](host-loading-contract.md),各 Host 的具体机制见 `hosts/<host>.md`。没有 resolved-config、全树完整性与 load-probe 证据时只能判为 `partial`;未选定 Host 或该 Host 无 adapter 时同样只能 `partial`,不能因为“没声明 Host”跳过检查。
43
43
 
44
44
  只有需要 Prepared Run 或稳定 Workspace 时才显式执行 `dta init`。首次初始化遵循:
45
45