@xdxer/dingtalk-agent 0.1.4 → 0.1.5-beta.10

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 (186) hide show
  1. package/CHANGELOG.md +232 -0
  2. package/README.en.md +115 -89
  3. package/README.md +111 -86
  4. package/dist/bin/dingtalk-agent.js +742 -152
  5. package/dist/bin/dingtalk-agent.js.map +1 -1
  6. package/dist/src/actions.js +3 -2
  7. package/dist/src/actions.js.map +1 -1
  8. package/dist/src/agent-audit.js +202 -85
  9. package/dist/src/agent-audit.js.map +1 -1
  10. package/dist/src/agent-definition.js +7 -3
  11. package/dist/src/agent-definition.js.map +1 -1
  12. package/dist/src/agent-enhance.js +51 -32
  13. package/dist/src/agent-enhance.js.map +1 -1
  14. package/dist/src/agent-platform.js +4 -4
  15. package/dist/src/agent-platform.js.map +1 -1
  16. package/dist/src/bootstrap.js +6 -2
  17. package/dist/src/bootstrap.js.map +1 -1
  18. package/dist/src/development-workspace.js +210 -34
  19. package/dist/src/development-workspace.js.map +1 -1
  20. package/dist/src/doctor.js +65 -9
  21. package/dist/src/doctor.js.map +1 -1
  22. package/dist/src/dws.js +67 -3
  23. package/dist/src/dws.js.map +1 -1
  24. package/dist/src/init.js +2 -1
  25. package/dist/src/init.js.map +1 -1
  26. package/dist/src/memory/noop-receipt.js +306 -0
  27. package/dist/src/memory/noop-receipt.js.map +1 -0
  28. package/dist/src/memory/operational.js +27 -3
  29. package/dist/src/memory/operational.js.map +1 -1
  30. package/dist/src/memory/remote-state.js +2 -1
  31. package/dist/src/memory/remote-state.js.map +1 -1
  32. package/dist/src/multica-deploy.js +692 -125
  33. package/dist/src/multica-deploy.js.map +1 -1
  34. package/dist/src/multica-provider.js +303 -25
  35. package/dist/src/multica-provider.js.map +1 -1
  36. package/dist/src/multica-runtime-vocabulary.js +110 -0
  37. package/dist/src/multica-runtime-vocabulary.js.map +1 -0
  38. package/dist/src/opencode-evals.js +6 -6
  39. package/dist/src/opencode-evals.js.map +1 -1
  40. package/dist/src/opencode-provider.js +21 -7
  41. package/dist/src/opencode-provider.js.map +1 -1
  42. package/dist/src/opencode-workspace.js +3 -3
  43. package/dist/src/opencode-workspace.js.map +1 -1
  44. package/dist/src/personal-event-evals.js +4 -2
  45. package/dist/src/personal-event-evals.js.map +1 -1
  46. package/dist/src/promotion.js +2 -1
  47. package/dist/src/promotion.js.map +1 -1
  48. package/dist/src/remote-semantic-state-live-evals.js +14 -8
  49. package/dist/src/remote-semantic-state-live-evals.js.map +1 -1
  50. package/dist/src/remote-state-evals.js +2 -2
  51. package/dist/src/remote-state-evals.js.map +1 -1
  52. package/dist/src/robot-evals.js +3 -3
  53. package/dist/src/robot-evals.js.map +1 -1
  54. package/dist/src/schedule-plan.js +380 -0
  55. package/dist/src/schedule-plan.js.map +1 -0
  56. package/dist/src/sessions.js +1 -1
  57. package/dist/src/sessions.js.map +1 -1
  58. package/dist/src/skill-manager.js +145 -13
  59. package/dist/src/skill-manager.js.map +1 -1
  60. package/dist/src/skills.js +2 -0
  61. package/dist/src/skills.js.map +1 -1
  62. package/dist/src/tui.js +369 -0
  63. package/dist/src/tui.js.map +1 -0
  64. package/dist/src/upgrade.js +113 -33
  65. package/dist/src/upgrade.js.map +1 -1
  66. package/dist/src/waits.js +2 -1
  67. package/dist/src/waits.js.map +1 -1
  68. package/dist/src/workspace.js +12 -7
  69. package/dist/src/workspace.js.map +1 -1
  70. package/docs/AGENT-IN-PRODUCTION.md +255 -0
  71. package/docs/ARCHITECTURE.md +366 -0
  72. package/docs/INSTALLATION.md +8 -8
  73. package/docs/PLATFORM-GUARDRAILS.md +188 -0
  74. package/docs/PRIOR-ART.md +126 -0
  75. package/docs/SELF-TEST.md +182 -0
  76. package/docs/architecture/agent-platform-connection-layer.svg +120 -0
  77. package/docs/architecture/digital-employee-composition.svg +92 -0
  78. package/docs/architecture/dingtalk-agent-architecture.svg +125 -0
  79. package/docs/assets/digital-employee-at-work.svg +77 -0
  80. package/docs/schemas/multica-deployment-plan.schema.json +3 -1
  81. package/docs/schemas/multica-deployment-receipt.schema.json +17 -3
  82. package/docs/schemas/multica-deployment-status.schema.json +6 -2
  83. package/docs/schemas/multica-workspace-inspection.schema.json +16 -0
  84. package/docs/schemas/multica-workspace-run-plan.schema.json +31 -0
  85. package/docs/schemas/multica-workspace-run.schema.json +161 -0
  86. package/docs/schemas/multica-workspace-status.schema.json +2 -0
  87. package/docs/schemas/project.schema.json +54 -3
  88. package/docs/schemas/workspace-scaffold.schema.json +38 -0
  89. package/examples/agents/README.md +45 -0
  90. package/examples/agents/fde-coach/AGENTS.md +2 -25
  91. package/examples/agents/fde-coach/agent/AGENTS.md +35 -0
  92. package/examples/agents/fde-coach/agent.bindings.json +10 -0
  93. package/examples/agents/release-manager/AGENTS.md +2 -25
  94. package/examples/agents/release-manager/agent/AGENTS.md +35 -0
  95. package/examples/agents/release-manager/agent.bindings.json +10 -0
  96. package/lab/agent-eval/catalog.json +5 -5
  97. package/lab/agent-eval/classic-failures.json +4 -4
  98. package/lab/agent-eval/completion-gate-regression.json +9 -9
  99. package/lab/agent-eval/personal-event-live.example.json +3 -3
  100. package/lab/agent-eval/remote-semantic-state-live.example.json +1 -1
  101. package/lab/agent-eval/workspace/opencode.json +2 -2
  102. package/lab/project-workspace/fake-multica-provider.mjs +171 -17
  103. package/lab/project-workspace/multica-deploy.fixture.json +2 -2
  104. package/lab/project-workspace/multica-readonly.fixture.json +4 -16
  105. package/lab/project-workspace/opencode-provider-suite.json +3 -3
  106. package/lab/project-workspace/project.fixture.json +2 -6
  107. package/lab/robot-eval/suite.json +1 -1
  108. package/lab/robot-eval/workspace/AGENTS.md +1 -1
  109. package/lab/robot-eval/workspace/opencode.json +2 -2
  110. package/lab/schemas/personal-event-eval.schema.json +1 -1
  111. package/package.json +18 -11
  112. package/skills/README.md +10 -8
  113. package/skills/core/{dingtalk-agent-compose → dta-agent-compose}/SKILL.md +49 -24
  114. package/skills/core/{dingtalk-agent-compose → dta-agent-compose}/assets/AGENTS.template.md +1 -1
  115. package/skills/core/dta-agent-compose/assets/REPOSITORY.template.md +10 -0
  116. package/skills/core/{dingtalk-agent-compose → dta-agent-compose}/assets/agent.bindings.dingtalk-doc.template.json +2 -2
  117. package/skills/core/{dingtalk-agent-compose → dta-agent-compose}/assets/agent.bindings.local.template.json +2 -2
  118. package/skills/core/{dingtalk-agent-compose → dta-agent-compose}/assets/hosts/opencode/opencode.template.json +3 -2
  119. package/skills/core/{dingtalk-agent-compose → dta-agent-compose}/evals/evals.json +4 -4
  120. package/skills/core/{dingtalk-agent-compose → dta-agent-compose}/references/agent-definition-contract.md +7 -7
  121. package/skills/core/dta-agent-compose/references/drive-and-schedules.md +166 -0
  122. package/skills/core/{dingtalk-agent-compose → dta-agent-compose}/references/host-loading-contract.md +11 -13
  123. package/skills/core/{dingtalk-agent-compose → dta-agent-compose}/references/hosts/claude-code.md +13 -12
  124. package/skills/core/{dingtalk-agent-compose → dta-agent-compose}/references/hosts/opencode.md +14 -13
  125. package/skills/core/{dingtalk-agent-eval → dta-agent-eval}/SKILL.md +28 -3
  126. package/skills/core/{dingtalk-agent-eval → dta-agent-eval}/assets/eval-catalog.template.json +1 -1
  127. package/skills/core/{dingtalk-agent-eval → dta-agent-eval}/evals/evals.json +1 -1
  128. package/skills/core/{dingtalk-agent-eval → dta-agent-eval}/references/eval-topology.md +3 -3
  129. package/skills/core/{dingtalk-agent-eval → dta-agent-eval}/references/interactive-debug-channels.md +10 -4
  130. package/skills/core/{dingtalk-basic-behavior → dta-basic-behavior}/SKILL.md +21 -5
  131. package/skills/core/dta-basic-behavior/references/event-to-behavior.md +38 -0
  132. package/skills/core/{dingtalk-basic-behavior → dta-basic-behavior}/references/memory-and-evolution.md +3 -1
  133. package/skills/core/dta-basic-behavior/references/perception-and-gates.md +87 -0
  134. package/skills/core/{dingtalk-basic-behavior → dta-basic-behavior}/references/risk-authority-and-privacy.md +12 -0
  135. package/skills/core/{dingtalk-basic-behavior → dta-basic-behavior}/references/truth-and-recovery.md +4 -2
  136. package/skills/core/dta-people-group-memory/COMPLETENESS.md +36 -0
  137. package/skills/core/dta-people-group-memory/SKILL.md +69 -0
  138. package/skills/core/dta-people-group-memory/references/adapters.md +273 -0
  139. package/skills/core/dta-people-group-memory/references/assembly-guidance.md +40 -0
  140. package/skills/core/dta-people-group-memory/references/binding.md +110 -0
  141. package/skills/core/dta-people-group-memory/references/cold-start.md +70 -0
  142. package/skills/core/dta-people-group-memory/references/config-binding.md +89 -0
  143. package/skills/core/dta-people-group-memory/references/consent-and-visibility.md +83 -0
  144. package/skills/core/dta-people-group-memory/references/consolidation.md +162 -0
  145. package/skills/core/dta-people-group-memory/references/event-ingest.md +103 -0
  146. package/skills/core/dta-people-group-memory/references/guided-setup.md +70 -0
  147. package/skills/core/dta-people-group-memory/references/model.md +148 -0
  148. package/skills/core/dta-people-group-memory/references/storage-port.md +107 -0
  149. package/skills/platforms/deap/PLATFORM.md +30 -1
  150. package/skills/platforms/multica-dingtalk/PLATFORM.md +35 -9
  151. package/skills/platforms/multica-dingtalk/{dingtalk-agent-deploy-multica → dta-deploy-multica}/SKILL.md +10 -8
  152. package/skills/platforms/multica-dingtalk/dta-deploy-multica/references/multica-deployment-contract.md +67 -0
  153. package/skills/platforms/multica-dingtalk/{multica-external → dta-ops-multica}/SKILL.md +81 -11
  154. package/skills/platforms/multica-dingtalk/{multica-external → dta-ops-multica}/scripts/bootstrap.sh +2 -2
  155. package/skills/platforms/multica-dingtalk/{multica-external → dta-ops-multica}/scripts/multica_ext.py +264 -16
  156. package/dist/src/map.js +0 -157
  157. package/dist/src/map.js.map +0 -1
  158. package/docs/SECOND-AGENT-ACCEPTANCE.md +0 -62
  159. package/docs/architecture/agent-memory-topology.png +0 -0
  160. package/docs/architecture/agent-memory-topology.svg +0 -132
  161. package/docs/architecture/dingtalk-agent-blueprint.png +0 -0
  162. package/docs/architecture/durable-async-agent-runtime.png +0 -0
  163. package/docs/architecture/general-agent-kernel-topology.png +0 -0
  164. package/docs/architecture/provider-bound-development-workspace.png +0 -0
  165. package/docs/architecture/task-completion-gate.png +0 -0
  166. package/docs/assets/agent-delivery-lifecycle.svg +0 -103
  167. package/skills/core/dingtalk-basic-behavior/references/event-to-behavior.md +0 -24
  168. package/skills/core/dingtalk-basic-behavior/references/perception-and-gates.md +0 -28
  169. package/skills/platforms/deap/README.md +0 -3
  170. package/skills/platforms/multica-dingtalk/dingtalk-agent-boot-multica/SKILL.md +0 -40
  171. package/skills/platforms/multica-dingtalk/dingtalk-agent-deploy-multica/references/multica-deployment-contract.md +0 -49
  172. /package/examples/agents/fde-coach/{skills → agent/skills}/fde-coach/SKILL.md +0 -0
  173. /package/examples/agents/release-manager/{skills → agent/skills}/release-manager/SKILL.md +0 -0
  174. /package/skills/core/{dingtalk-agent-compose → dta-agent-compose}/assets/role-skill.template.md +0 -0
  175. /package/skills/core/{dingtalk-agent-compose → dta-agent-compose}/references/storage-routing.md +0 -0
  176. /package/skills/core/{dingtalk-agent-eval → dta-agent-eval}/references/evidence-contract.md +0 -0
  177. /package/skills/core/{dingtalk-agent-eval → dta-agent-eval}/references/failure-to-case.md +0 -0
  178. /package/skills/core/{dingtalk-agent-eval → dta-agent-eval}/references/local-connector-smoke.md +0 -0
  179. /package/skills/core/{dingtalk-agent-eval → dta-agent-eval}/references/scenario-taxonomy.md +0 -0
  180. /package/skills/core/{dingtalk-agent-eval → dta-agent-eval}/references/storage-modes.md +0 -0
  181. /package/skills/core/{dingtalk-basic-behavior → dta-basic-behavior}/assets/memory-candidate-proposal.json +0 -0
  182. /package/skills/core/{dingtalk-basic-behavior → dta-basic-behavior}/assets/task-checkpoint.json +0 -0
  183. /package/skills/core/{dingtalk-basic-behavior → dta-basic-behavior}/references/action-contract.md +0 -0
  184. /package/skills/core/{dingtalk-basic-behavior → dta-basic-behavior}/references/runtime-modes.md +0 -0
  185. /package/skills/core/{dingtalk-basic-behavior → dta-basic-behavior}/references/task-lifecycle.md +0 -0
  186. /package/skills/platforms/multica-dingtalk/{dingtalk-agent-deploy-multica → dta-deploy-multica}/references/promotion-observation-contract.md +0 -0
@@ -2,15 +2,33 @@ import { createHash } from 'node:crypto';
2
2
  import { spawnSync } from 'node:child_process';
3
3
  import { existsSync, lstatSync, mkdirSync, readFileSync, realpathSync, renameSync, writeFileSync, } from 'node:fs';
4
4
  import { dirname, join, relative, resolve } from 'node:path';
5
- import { WORKSPACE_STATE_ROOT, inspectAgentProject, showDevelopmentWorkspace, } from './development-workspace.js';
5
+ import { RESERVED_MULTICA_ENV, WORKSPACE_STATE_ROOT, inspectAgentProject, multicaAgentIdSource, showDevelopmentWorkspace, } from './development-workspace.js';
6
6
  import { digest, stableStringify } from './events.js';
7
+ import { supportedRuntimeProviders, vocabulariesForRuntimeProvider, } from './multica-runtime-vocabulary.js';
7
8
  export const MULTICA_EVIDENCE_ROOT = '.dingtalk-agent/development-workspace-evidence';
8
- export const MULTICA_BOOT_SKILL = 'dingtalk-agent-boot-multica';
9
+ /**
10
+ * Strip env variables the provider CLI reinterprets as mode switches (see
11
+ * RESERVED_MULTICA_ENV) from a child-process environment. dta reads manifest env refs from
12
+ * the raw env *before* this runs, so a ref named after a reserved variable still resolves —
13
+ * only the multica subprocess stops seeing it.
14
+ */
15
+ export function sanitizeProviderEnv(env) {
16
+ const removed = RESERVED_MULTICA_ENV
17
+ .filter((key) => env[key] !== undefined).sort();
18
+ if (!removed.length)
19
+ return { env, removed };
20
+ const child = { ...env };
21
+ for (const key of removed)
22
+ delete child[key];
23
+ return { env: child, removed };
24
+ }
9
25
  class MulticaReadFailure extends Error {
10
26
  category;
11
- constructor(category) {
27
+ detail;
28
+ constructor(category, detail) {
12
29
  super(category);
13
30
  this.category = category;
31
+ this.detail = detail;
14
32
  }
15
33
  }
16
34
  export function planMulticaWorkspace(start, name, options = {}) {
@@ -72,6 +90,172 @@ export function planMulticaWorkspace(start, name, options = {}) {
72
90
  dingtalkSideEffect: false,
73
91
  };
74
92
  }
93
+ const SCAFFOLD_STAGES = new Set(['dev', 'test', 'staging', 'prod']);
94
+ const SCAFFOLD_NAME = /^[a-z0-9](?:[a-z0-9._-]{0,62}[a-z0-9])?$/;
95
+ /**
96
+ * Author a multica-provider Workspace entry into dingtalk-agent.json so callers never hand-copy
97
+ * the schema. Env-referenced IDs are namespaced by Workspace name; authority is filled in and
98
+ * `agentIdFrom` deliberately omitted (first deploy creates the Agent and saves its ID). The
99
+ * written manifest is re-parsed to fail closed on any invalid result.
100
+ */
101
+ export function scaffoldMulticaWorkspace(start, name, options = {}) {
102
+ const cliVersion = options.cliVersion || '';
103
+ if (!SCAFFOLD_NAME.test(name))
104
+ throw new Error(`Workspace 名不合法: ${name}`);
105
+ if (options.stage !== undefined && !SCAFFOLD_STAGES.has(options.stage)) {
106
+ throw new Error(`--stage 只支持 dev/test/staging/prod,收到 ${options.stage}`);
107
+ }
108
+ const info = inspectAgentProject(start, cliVersion);
109
+ const existing = info.workspaces.find((item) => item.name === name);
110
+ if (existing && existing.provider.kind !== 'multica') {
111
+ throw new Error(`Workspace ${name} 已声明为 ${existing.provider.kind},不能改写成 multica`);
112
+ }
113
+ const alreadyDeclared = Boolean(existing);
114
+ let stage;
115
+ let profile;
116
+ let envRefs;
117
+ let block;
118
+ if (existing) {
119
+ // Report the REAL declared config, not this call's args: a manually-authored Workspace or one
120
+ // using non-default env names must not be misrepresented (nor its --profile/--stage silently
121
+ // "changed" in the output while nothing is written).
122
+ const stripEnv = (ref) => (ref.startsWith('env:') ? ref.slice(4) : ref);
123
+ stage = existing.stage;
124
+ profile = existing.provider.profile || '';
125
+ envRefs = {
126
+ workspaceId: stripEnv(existing.provider.workspaceIdFrom || ''),
127
+ runtimeId: stripEnv(existing.provider.runtimeIdFrom || ''),
128
+ dwsProfile: stripEnv(existing.authority?.dwsProfileFrom || ''),
129
+ expectedUserId: stripEnv(existing.authority?.expectedUserIdFrom || ''),
130
+ };
131
+ block = {
132
+ stage: existing.stage,
133
+ provider: { ...existing.provider },
134
+ ...(existing.authority ? { authority: { ...existing.authority } } : {}),
135
+ storage: { ...existing.storage },
136
+ };
137
+ }
138
+ else {
139
+ stage = options.stage || 'test';
140
+ profile = String(options.profile || '').trim();
141
+ if (!profile) {
142
+ throw new Error('multica Workspace 需要 --profile <multica CLI profile>(profile 不接受 env 引用)');
143
+ }
144
+ // Namespace env refs by Workspace name AND a stable hash of the raw name so names differing
145
+ // only in separators (foo-bar / foo_bar / foo.bar) never collapse to the same env variables.
146
+ const sanitized = name.replace(/[^a-zA-Z0-9]+/g, '_').toUpperCase();
147
+ const envPrefix = `DTA_${sanitized}_${digest(name).slice(0, 6).toUpperCase()}`;
148
+ envRefs = {
149
+ workspaceId: `${envPrefix}_WORKSPACE_ID`,
150
+ runtimeId: `${envPrefix}_RUNTIME_ID`,
151
+ dwsProfile: `${envPrefix}_DWS_PROFILE`,
152
+ expectedUserId: `${envPrefix}_EXPECTED_USER_ID`,
153
+ };
154
+ block = {
155
+ stage,
156
+ provider: {
157
+ kind: 'multica',
158
+ profile,
159
+ workspaceIdFrom: `env:${envRefs.workspaceId}`,
160
+ runtimeIdFrom: `env:${envRefs.runtimeId}`,
161
+ },
162
+ authority: {
163
+ dwsProfileFrom: `env:${envRefs.dwsProfile}`,
164
+ expectedUserIdFrom: `env:${envRefs.expectedUserId}`,
165
+ },
166
+ storage: {
167
+ memory: 'local-md:MEMORY.md',
168
+ knowledge: 'local-md:knowledge/INDEX.md',
169
+ artifacts: `local-dir:.dingtalk-agent/artifacts/${name}`,
170
+ },
171
+ };
172
+ }
173
+ const result = (applied, createdPaths = []) => ({
174
+ $schema: 'dingtalk-agent/workspace-scaffold@1',
175
+ workspace: name,
176
+ provider: 'multica',
177
+ stage,
178
+ profile,
179
+ applied,
180
+ alreadyDeclared,
181
+ manifest: 'dingtalk-agent.json',
182
+ block,
183
+ envRefs,
184
+ createdPaths,
185
+ nextSteps: alreadyDeclared
186
+ ? [
187
+ `Workspace ${name} 已声明(未改写);以下是它当前真实的配置`,
188
+ ...(options.profile && options.profile.trim() && options.profile.trim() !== profile
189
+ ? [`注意:本次 --profile=${options.profile.trim()} 与已声明 profile=${profile} 不同,未生效;要改请手动编辑 dingtalk-agent.json`]
190
+ : []),
191
+ ...(options.stage && options.stage !== stage
192
+ ? [`注意:本次 --stage=${options.stage} 与已声明 stage=${stage} 不同,未生效`]
193
+ : []),
194
+ `设置 4 个 env 后运行 dta workspace doctor ${name}`,
195
+ `export ${envRefs.workspaceId}=<multica workspace uuid>`,
196
+ `export ${envRefs.runtimeId}=<multica runtime uuid>`,
197
+ `export ${envRefs.dwsProfile}=<dws profile 或 corpId>`,
198
+ `export ${envRefs.expectedUserId}=<dws user id>`,
199
+ ]
200
+ : [
201
+ applied
202
+ ? '已写入 dingtalk-agent.json;只改了本地 manifest(含 artifacts 目录),' +
203
+ '未创建任何 Multica 远端资源——远端 workspace/runtime 用 ops 技能供给,Agent 由 dta deploy 创建'
204
+ : '预览(加 --yes 落盘);本命令只写本地 manifest,不创建任何 Multica 远端资源',
205
+ `export ${envRefs.workspaceId}=<multica workspace uuid>`,
206
+ `export ${envRefs.runtimeId}=<multica runtime uuid>`,
207
+ `export ${envRefs.dwsProfile}=<dws profile 或 corpId>`,
208
+ `export ${envRefs.expectedUserId}=<dws user id>`,
209
+ `dta workspace doctor ${name}`,
210
+ `dta workspace plan ${name} --json`,
211
+ `dta workspace inspect ${name} --execute --yes --json # 代理环境先套 env -u ...PROXY`,
212
+ `dta deploy --workspace ${name} --dry-run --json`,
213
+ ],
214
+ sideEffect: applied ? 'local-manifest' : false,
215
+ dingtalkSideEffect: false,
216
+ });
217
+ if (alreadyDeclared || !options.apply)
218
+ return result(false);
219
+ const manifestPath = join(info.root, 'dingtalk-agent.json');
220
+ if (!existsSync(manifestPath) || lstatSync(manifestPath).isSymbolicLink()) {
221
+ throw new Error('未找到可写的 dingtalk-agent.json(或它是 symlink);无法脚手架 Workspace');
222
+ }
223
+ const raw = readFileSync(manifestPath, 'utf8');
224
+ const manifest = JSON.parse(raw);
225
+ if (!manifest.workspaces || typeof manifest.workspaces !== 'object')
226
+ manifest.workspaces = {};
227
+ if (name in manifest.workspaces)
228
+ return result(false);
229
+ manifest.workspaces[name] = block;
230
+ const stamp = `${process.pid}-${digest(raw).slice(0, 8)}`;
231
+ const staging = `${manifestPath}.tmp-${stamp}`;
232
+ writeFileSync(staging, `${JSON.stringify(manifest, null, 2)}\n`);
233
+ renameSync(staging, manifestPath);
234
+ try {
235
+ showDevelopmentWorkspace(info.root, name, cliVersion);
236
+ }
237
+ catch (error) {
238
+ // Restore the exact original bytes atomically (staging + rename), never a partial write:
239
+ // a kill mid-restore must not leave a truncated shared manifest.
240
+ const restore = `${manifestPath}.restore-${stamp}`;
241
+ writeFileSync(restore, raw);
242
+ renameSync(restore, manifestPath);
243
+ throw error;
244
+ }
245
+ // 声明的路径必须原子性地存在:manifest 里写了 artifacts 目录却不创建它,新项目
246
+ // 第一次 doctor 就挂在 storage.artifacts(E-01)——doctor 不该要求用户补脚手架遗漏。
247
+ // .gitkeep 让空目录进 git,fresh clone 后 doctor 依然通过。回滚路径不清理该目录
248
+ // (幂等无害,保持回滚逻辑简单)。
249
+ const artifactsDir = join(info.root, '.dingtalk-agent', 'artifacts', name);
250
+ mkdirSync(artifactsDir, { recursive: true });
251
+ const gitkeep = join(artifactsDir, '.gitkeep');
252
+ if (!existsSync(gitkeep))
253
+ writeFileSync(gitkeep, '');
254
+ return result(true, [
255
+ relative(info.root, artifactsDir).split('\\').join('/'),
256
+ relative(info.root, gitkeep).split('\\').join('/'),
257
+ ]);
258
+ }
75
259
  export function inspectMulticaWorkspace(start, name, options = {}) {
76
260
  const plan = planMulticaWorkspace(start, name, options);
77
261
  if (!options.execute)
@@ -148,6 +332,8 @@ export function statusMulticaWorkspace(start, name, cliVersion = '') {
148
332
  observedHash: report.observedHash,
149
333
  diff: [...report.diff],
150
334
  failures: [...report.failures],
335
+ failureDetails: Array.isArray(report.failureDetails) ? [...report.failureDetails] : [],
336
+ observedAgentRuntimeId: report.resources?.agent?.runtimeId || '',
151
337
  remoteRead: false,
152
338
  remoteWrite: false,
153
339
  sideEffect: false,
@@ -155,9 +341,11 @@ export function statusMulticaWorkspace(start, name, cliVersion = '') {
155
341
  };
156
342
  }
157
343
  function executeInspection(start, name, plan, options, persist) {
158
- const env = options.env || process.env;
159
- const target = resolveMulticaTarget(start, name, env, options.cliVersion || '', options.agentIdOverride);
160
- const command = options.command || env.DTA_MULTICA_BIN || 'multica';
344
+ const rawEnv = options.env || process.env;
345
+ const target = resolveMulticaTarget(start, name, rawEnv, options.cliVersion || '', options.agentIdOverride);
346
+ const sanitized = sanitizeProviderEnv(rawEnv);
347
+ const env = sanitized.env;
348
+ const command = options.command || rawEnv.DTA_MULTICA_BIN || 'multica';
161
349
  const timeoutMs = positiveTimeout(options.timeoutMs);
162
350
  if (persist) {
163
351
  const plannedEvidence = options.out || join(MULTICA_EVIDENCE_ROOT, safeName(name), 'multica-inspections', 'planned.json');
@@ -167,6 +355,7 @@ function executeInspection(start, name, plan, options, persist) {
167
355
  }
168
356
  const calls = [];
169
357
  const failures = [];
358
+ const failureDetails = [];
170
359
  const diff = [];
171
360
  let cliVersion = '';
172
361
  let principalHash = '';
@@ -179,12 +368,23 @@ function executeInspection(start, name, plan, options, persist) {
179
368
  const parsedConfig = parseConfig(config.stdout, target.profile);
180
369
  serverHash = parsedConfig.server ? digest(parsedConfig.server) : '';
181
370
  if (parsedConfig.workspaceId && parsedConfig.workspaceId !== target.workspaceId) {
182
- throw new MulticaReadFailure('scope.profile-workspace-mismatch');
371
+ throw new MulticaReadFailure('scope.profile-workspace-mismatch', {
372
+ code: 'scope.profile-workspace-mismatch',
373
+ expected: target.workspaceId,
374
+ actual: safeDisplay(parsedConfig.workspaceId),
375
+ hint: `profile ${target.profile} 的 workspace_id 指向另一个 Workspace;` +
376
+ '为目标 Workspace 建专属具名 profile(复制 config 只改 workspace_id),或修正现有 profile',
377
+ });
183
378
  }
184
379
  const auth = runRead(command, profileArgs(target, ['auth', 'status']), 'auth.status', target.root, env, timeoutMs, calls);
185
380
  const principal = parseAuth(auth.stdout + '\n' + auth.stderr);
186
381
  if (!principal)
187
- throw new MulticaReadFailure('auth.not-authenticated');
382
+ throw new MulticaReadFailure('auth.not-authenticated', {
383
+ code: 'auth.not-authenticated',
384
+ hint: `确认 multica --profile ${target.profile} 已登录;` +
385
+ 'dta 调用 CLI 时已剔除保留变量(MULTICA_AGENT_ID/MULTICA_SERVER_URL/MULTICA_TOKEN),' +
386
+ '身份与 endpoint 只认具名 profile',
387
+ });
188
388
  principalHash = digest(principal);
189
389
  const workspaceList = jsonArray(runRead(command, profileArgs(target, ['workspace', 'list', '--output', 'json']), 'workspace.list', target.root, env, timeoutMs, calls).stdout, 'workspace list');
190
390
  const workspaceMatches = workspaceList.filter((item) => idOf(item) === target.workspaceId);
@@ -215,6 +415,27 @@ function executeInspection(start, name, plan, options, persist) {
215
415
  provider: safeDisplay(runtime.provider),
216
416
  status: safeDisplay(runtime.status),
217
417
  };
418
+ // Skill 装载合同只在注册过 trace 词汇的 runtime provider 上成立。hermes 上部署会
419
+ // "成功"、Skill 显示 enabled,agent 一加载却 100% `Skill not found` 且零告警——
420
+ // 这里 fail-closed,把确定性死局挡在任何远端写入之前。
421
+ const runtimeProvider = resources.runtime.provider;
422
+ if (!runtimeProvider) {
423
+ throw new MulticaReadFailure('scope.runtime-provider-unknown', {
424
+ code: 'scope.runtime-provider-unknown',
425
+ expected: supportedRuntimeProviders(),
426
+ actual: '(empty)',
427
+ hint: '远端 runtime 未报告 provider 字段;用 multica runtime get 核实后再部署',
428
+ });
429
+ }
430
+ if (!vocabulariesForRuntimeProvider(runtimeProvider).length) {
431
+ throw new MulticaReadFailure('scope.runtime-provider-unsupported', {
432
+ code: 'scope.runtime-provider-unsupported',
433
+ expected: supportedRuntimeProviders(),
434
+ actual: runtimeProvider,
435
+ hint: 'dta 的 Skill 装载合同只在这些 runtime provider 上成立' +
436
+ '(如 hermes 上 workspace skill 一律加载不到);换用受支持 provider 的 runtime',
437
+ });
438
+ }
218
439
  const agents = jsonArray(runRead(command, scopedArgs(target, ['agent', 'list', '--include-archived', '--output', 'json']), 'agent.list', target.root, env, timeoutMs, calls).stdout, 'agent list');
219
440
  resources.agentCandidates = agents
220
441
  .filter((item) => optionalField(item.name) === target.projectName)
@@ -240,9 +461,26 @@ function executeInspection(start, name, plan, options, persist) {
240
461
  const agent = jsonObject(runRead(command, scopedArgs(target, ['agent', 'get', target.agentId, '--output', 'json']), 'agent.get', target.root, env, timeoutMs, calls).stdout, 'agent get');
241
462
  const agentWorkspace = optionalField(agent.workspace_id);
242
463
  const agentRuntime = optionalField(agent.runtime_id);
243
- if (idOf(agent) !== target.agentId || agentWorkspace !== target.workspaceId ||
244
- agentRuntime !== target.runtimeId) {
245
- throw new MulticaReadFailure('scope.agent-binding-mismatch');
464
+ if (idOf(agent) !== target.agentId || agentWorkspace !== target.workspaceId) {
465
+ throw new MulticaReadFailure('scope.agent-binding-mismatch', {
466
+ code: 'scope.agent-binding-mismatch',
467
+ expected: `workspace=${target.workspaceId}`,
468
+ actual: `workspace=${safeDisplay(agentWorkspace) || '(empty)'}`,
469
+ hint: 'Agent 不属于目标 Workspace;Workspace 边界不允许跨越',
470
+ });
471
+ }
472
+ // Runtime 归属漂移不再当 inspection 硬失败:那会把 deploy 与 --retire 一起锁死
473
+ // (retire 被它要解开的锁挡住)。漂移记录成 diff;apply 层负责在没有显式
474
+ // --rebind-runtime 时拒绝,retire 不受它阻挡。
475
+ if (agentRuntime !== target.runtimeId) {
476
+ diff.push('agent.runtime-drift');
477
+ failureDetails.push({
478
+ code: 'agent.runtime-drift',
479
+ expected: target.runtimeId,
480
+ actual: safeDisplay(agentRuntime) || '(empty)',
481
+ hint: '迁移 runtime 用 dta deploy --rebind-runtime(先 --dry-run);' +
482
+ '退役用 deploy --retire,不受漂移阻挡',
483
+ });
246
484
  }
247
485
  resources.agent = {
248
486
  id: target.agentId,
@@ -260,7 +498,12 @@ function executeInspection(start, name, plan, options, persist) {
260
498
  })).sort(compareNamedResource);
261
499
  }
262
500
  else if (resources.agentCandidates.length > 0) {
263
- throw new MulticaReadFailure('agent.unbound-name-candidates');
501
+ throw new MulticaReadFailure('agent.unbound-name-candidates', {
502
+ code: 'agent.unbound-name-candidates',
503
+ actual: resources.agentCandidates.map((item) => `${item.id}${item.archived ? ' (archived)' : ''}`),
504
+ hint: '远端已有同名 Agent,dta 不按名字猜绑定;核对候选后在 manifest 声明 ' +
505
+ 'provider.agentIdFrom 指向正确 ID(或依赖首次 deploy 写入的 state.binding.agentId)',
506
+ });
264
507
  }
265
508
  else {
266
509
  diff.push('agent.create');
@@ -279,7 +522,13 @@ function executeInspection(start, name, plan, options, persist) {
279
522
  }
280
523
  }
281
524
  catch (error) {
282
- failures.push(error instanceof MulticaReadFailure ? error.category : 'provider.read-failed');
525
+ if (error instanceof MulticaReadFailure) {
526
+ failures.push(error.category);
527
+ if (error.detail)
528
+ failureDetails.push(error.detail);
529
+ }
530
+ else
531
+ failures.push('provider.read-failed');
283
532
  }
284
533
  const canonical = {
285
534
  cliVersion,
@@ -319,6 +568,8 @@ function executeInspection(start, name, plan, options, persist) {
319
568
  resources,
320
569
  diff: [...diff].sort(),
321
570
  failures: [...failures].sort(),
571
+ failureDetails,
572
+ envSanitized: sanitized.removed,
322
573
  calls,
323
574
  evidencePath,
324
575
  remoteRead: true,
@@ -408,20 +659,28 @@ function resolveMulticaTarget(start, name, env, cliVersion, agentIdOverride = ''
408
659
  blocking.push('agent-id-override.invalid');
409
660
  }
410
661
  }
411
- else if (provider.agentIdFrom)
412
- agentId = resolveRef(provider.agentIdFrom, 'agent-id');
413
- else if (workspace.state?.binding.agentId) {
414
- try {
415
- agentId = resourceId(workspace.state.binding.agentId, 'state.agent-id');
416
- }
417
- catch {
418
- blocking.push('state.agent-id.invalid');
662
+ else {
663
+ // 来源选择收口到 multicaAgentIdSource(schedule 共用同一份,防两处漂移)。
664
+ // A declared-but-empty agentIdFrom means "not yet bound" on a workspace whose Agent
665
+ // has not been created. Treat it like an omitted agentIdFrom (createable, no blocker):
666
+ // the deploy W4 create path saves the real ID to state, and the run/retire paths add
667
+ // their own `agent-id.missing` blocker. A malformed (non-empty, invalid) value still blocks.
668
+ const source = multicaAgentIdSource(workspace);
669
+ if (source.kind === 'declared')
670
+ agentId = resolveRef(source.ref, 'agent-id', true);
671
+ else if (source.kind === 'state') {
672
+ try {
673
+ agentId = resourceId(source.agentId, 'state.agent-id');
674
+ }
675
+ catch {
676
+ blocking.push('state.agent-id.invalid');
677
+ }
419
678
  }
420
679
  }
421
680
  return {
422
681
  root: info.root,
423
682
  projectName: info.project.displayName,
424
- skills: [...new Set([...info.project.skills, MULTICA_BOOT_SKILL])].sort(),
683
+ skills: [...new Set(info.project.skills)].sort(),
425
684
  workspace,
426
685
  profile: provider.profile,
427
686
  workspaceId,
@@ -461,7 +720,7 @@ function scopedArgs(target, args) {
461
720
  function runRead(command, argv, id, cwd, env, timeoutMs, calls) {
462
721
  const run = spawnSync(command, argv, {
463
722
  cwd,
464
- env,
723
+ env: sanitizeProviderEnv(env).env,
465
724
  encoding: 'utf8',
466
725
  timeout: timeoutMs,
467
726
  maxBuffer: 16 * 1024 * 1024,
@@ -479,10 +738,27 @@ function runRead(command, argv, id, cwd, env, timeoutMs, calls) {
479
738
  stderrHash: sha256(stderr),
480
739
  passed,
481
740
  });
482
- if (!passed)
483
- throw new MulticaReadFailure(`command.${id}`);
741
+ if (!passed) {
742
+ emitProviderDiagnostic(id, exitCode, stderr || stdout);
743
+ throw new MulticaReadFailure(`command.${id}`, {
744
+ code: `command.${id}`,
745
+ actual: `exit=${exitCode}`,
746
+ hint: 'provider CLI 原始 stderr 已透传到终端(证据文件只存 hash)',
747
+ });
748
+ }
484
749
  return { stdout, stderr };
485
750
  }
751
+ /**
752
+ * Provider CLI failures used to vanish into a hash: the receipt recorded `stderrHash` and the
753
+ * user saw only `command.<id>`. The raw text still must never enter evidence files, so it goes
754
+ * straight to dta's own stderr — visible in the moment, never persisted.
755
+ */
756
+ export function emitProviderDiagnostic(id, exitCode, output) {
757
+ const text = output.trim();
758
+ const body = !text ? '(无 stderr 输出)'
759
+ : text.length > 2048 ? `${text.slice(0, 2048)}…` : text;
760
+ process.stderr.write(`[multica ${id}] exit=${exitCode} ${body}\n`);
761
+ }
486
762
  function parseMulticaVersion(value) {
487
763
  const match = value.match(/\bmultica\s+([0-9]+\.[0-9]+\.[0-9]+(?:[-+][A-Za-z0-9.-]+)?)/i);
488
764
  if (!match)
@@ -603,6 +879,8 @@ function emptyStatus(workspace) {
603
879
  observedHash: workspace.observedHash,
604
880
  diff: [],
605
881
  failures: ['inspection.missing'],
882
+ failureDetails: [],
883
+ observedAgentRuntimeId: '',
606
884
  remoteRead: false,
607
885
  remoteWrite: false,
608
886
  sideEffect: false,