@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
@@ -1,13 +1,17 @@
1
1
  #!/usr/bin/env node
2
2
  // dingtalk-agent —— 钉钉数字员工的标准范式(TypeScript source)。
3
3
  //
4
- // CLI = 拦 标准动作:可程序化、可强制、可验证。撞上去没得商量。
5
4
  // Skill = 劝 判断:只有 LLM 能做的(该不该答、话题归属、怎么写点评)。
6
- // 本体 = 它是谁 + 它知道什么。git clone 就带走。
5
+ // CLI = 标准动作:可程序化、可强制、可验证。撞上去没得商量。
6
+ // DWS = 做 真正的钉钉产品能力;本文件只在需要冻结事务边界时包一层。
7
7
  //
8
- // **分界线:凡是能程序化的,收进 CLI;凡是需要判断的,留在 Skill。**
8
+ // 本体(AGENTS.md + Role Skills)负责"它是谁、它知道什么",git clone 就带走;
9
+ // 它是被上面三者加载和校验的对象,不是第四个执行者。
9
10
  //
11
+ // **分界线:凡是能程序化的,收进 CLI;凡是需要判断的,留在 Skill。**
10
12
  // 因为:文档会被漏读、误读、自我说服;**撞到闸门上就没得商量。**
13
+ //
14
+ // 完整架构见 docs/ARCHITECTURE.md。
11
15
  import { parseArgs } from 'node:util';
12
16
  import { join, relative, resolve } from 'node:path';
13
17
  import { existsSync, readFileSync, realpathSync } from 'node:fs';
@@ -27,16 +31,20 @@ import { runRobotEvaluation } from '../src/robot-evals.js';
27
31
  import { runOpenCodeEvaluation } from '../src/opencode-evals.js';
28
32
  import { runStorageEvaluation } from '../src/storage-evals.js';
29
33
  import { runPersonalEventEvaluation } from '../src/personal-event-evals.js';
30
- import { doctor } from '../src/doctor.js';
34
+ import { CHECK_LABELS, doctor } from '../src/doctor.js';
31
35
  import { doctorDevelopmentWorkspace, inspectAgentProject, showDevelopmentWorkspace, } from '../src/development-workspace.js';
36
+ import { buildScheduleShow, buildSchedulePlan, scheduleBlockers } from '../src/schedule-plan.js';
32
37
  import { applyOpenCodeWorkspace, evalOpenCodeWorkspace, planOpenCodeWorkspace, runOpenCodeWorkspace, useOpenCodeWorkspace, } from '../src/opencode-provider.js';
33
- import { inspectMulticaWorkspace, listMulticaWorkspaceResources, planMulticaWorkspace, statusMulticaWorkspace, } from '../src/multica-provider.js';
34
- import { applyMulticaDeployment, listMulticaDeployments, planMulticaDeployment, statusMulticaDeployment, } from '../src/multica-deploy.js';
38
+ import { inspectMulticaWorkspace, listMulticaWorkspaceResources, planMulticaWorkspace, scaffoldMulticaWorkspace, statusMulticaWorkspace, } from '../src/multica-provider.js';
39
+ import { applyMulticaDeployment, listMulticaDeployments, planMulticaDeployment, runMulticaWorkspaceIssue, statusMulticaDeployment, statusMulticaWorkspaceRun, } from '../src/multica-deploy.js';
35
40
  import { applyObservation, applyPromotion, listPromotions, planObservation, planPromotion, statusPromotion, } from '../src/promotion.js';
36
41
  import { setup } from '../src/setup.js';
37
- import { upgradeAgent } from '../src/upgrade.js';
42
+ import { checkUpgrade, upgradeAgent } from '../src/upgrade.js';
43
+ // out/err 在本文件里改名导入:printX 的形参普遍叫 out,直接同名会被遮蔽。
44
+ import { configureTui, confirm, displayWidth, padRight, renderPairs, StepReporter, err as stderrUi, out as stdoutUi, } from '../src/tui.js';
38
45
  import { checkpointTask, showTaskCheckpoint } from '../src/memory/task-checkpoints.js';
39
46
  import { upsertOperationalMemory } from '../src/memory/operational.js';
47
+ import { listMemoryNoops, recordMemoryNoop, traceMemoryNoop } from '../src/memory/noop-receipt.js';
40
48
  import { proposeMemoryCandidate, publishMemoryCandidate, reviewMemoryCandidate, showMemoryCandidate, } from '../src/memory/candidates.js';
41
49
  import { installBundledSkills, installGlobalSkill, skillStatus, skillSuiteStatus, uninstallBundledSkills, uninstallGlobalSkill, upgradeBundledSkills, upgradeGlobalSkill, } from '../src/skill-manager.js';
42
50
  import { agentPlatformReadiness, MANAGED_AGENT_PLATFORMS, multicaTargets, platformDocPath, platformSkillForRole, requireSupportedAgentPlatform, resolveAgentPlatform, resolveEndpoint, statusLabel as platformStatusLabel, useAgentPlatform, } from '../src/agent-platform.js';
@@ -60,7 +68,7 @@ dta —— 给 Agent 安装钉钉员工的基础行为和工作环境
60
68
  dta setup 首次配置 CLI、DWS 和内置 Skill(基础行为 + Agent 装配 + 交付后评测)
61
69
  dta bootstrap --json 为当前 Agent 会话加载工作区
62
70
  dta agent enhance / audit 安全增强已有仓库,再审计定义、存储与 Skill 真加载
63
- dta info / dta workspace doctor <name> 查看 Project 并只读诊断 Workspace
71
+ dta info / workspace doctor / schedule show <name> 只读诊断 Project、Workspace 与声明的常驻节律
64
72
  dta agent-platform use <platform> → dta deploy / dta promote 声明平台归属并装平台技能包,再受控部署与晋级
65
73
 
66
74
  维护环境:
@@ -82,6 +90,7 @@ dingtalk-agent 可信事件运行时(可选宿主能力)
82
90
  task checkpoint --input checkpoint.json --expect-revision N
83
91
  memory operational upsert --provider provider.json --input record.json [--live --yes]
84
92
  memory candidate propose|show|review|publish
93
+ memory noop record|list|trace
85
94
  act ack|reply|ask|silence
86
95
 
87
96
  这些命令负责目标冻结、Session/Run、Wait、幂等和回执;普通 Agent Session 不必使用。
@@ -151,7 +160,7 @@ const DEPLOY_HELP = `
151
160
  dingtalk-agent Multica 受控部署
152
161
 
153
162
  deploy --workspace <name> --dry-run [--json]
154
- deploy --workspace <name> --plan-id <id> --yes [--no-wait] [--json]
163
+ deploy --workspace <name> --plan-id <id> --yes [--wait | --no-wait] [--json]
155
164
  deploy --workspace <name> --status [--operation-id <id>] [--json]
156
165
  deploy --workspace <name> --status --operation-id <id> --execute --yes [--json]
157
166
  deploy --workspace <name> --list [--json]
@@ -159,6 +168,9 @@ dingtalk-agent Multica 受控部署
159
168
  deploy --workspace <name> --retire --plan-id <id> --yes [--json]
160
169
 
161
170
  dry-run 不启动 Multica。apply/retire 必须携带当前 planId;scope 或源码漂移会在写入前拒绝。
171
+ apply 默认在同一条命令内等 load smoke 到 ~30 秒;云端 runtime 冷启常超 30 秒会返回 verifying,
172
+ 再用 --status --operation-id 独立 reconcile。加 --wait 把等待预算放宽到 4 分钟,一条命令直接到 ready;
173
+ --no-wait 立即返回 verifying(异步/CI)。--smoke-timeout <ms> 精确覆盖等待预算(上限 5 分钟)。
162
174
  status 默认只验本地脱敏 operation/Receipt;--execute --yes 才做远端独立回读。
163
175
  deploy 只管理 Agent、完整 Skill tree、assignment 和 load smoke,不创建机器人、Webhook、Autopilot 或定时 Trigger。
164
176
  `;
@@ -175,13 +187,100 @@ dingtalk-agent 已验版本晋级与反馈候选
175
187
  Promotion 只接受指定 suite 的 load/hard/surface gate 与精确 W2 evidence hash,并委托 W4 deploy。
176
188
  Observe 只写 gitignored、proposed、不可直接发布的 Eval candidate;不修改 AGENTS.md、Prompt、Skill 或 Trigger。
177
189
  `;
190
+ const UPGRADE_HELP = `
191
+ dta upgrade —— 自升级 CLI,再重新检查完整环境
192
+
193
+ 用法:
194
+ dta upgrade 升级到当前通道的最新版本
195
+ dta upgrade --check 只查有没有新版本,不安装
196
+ dta upgrade --dry-run 只预览将执行的命令,不安装
197
+ dta upgrade --channel beta 指定通道(beta | latest)
198
+ dta upgrade --version <x.y.z> 升级到指定版本
199
+ dta upgrade --version <x.y.z> --force 允许降级(两者必须同时给)
200
+ dta upgrade --yes 跳过确认提示(AI Agent 模式)
201
+ dta upgrade --json 机器通道;输出 upgrade@1,不打任何进度
202
+
203
+ 参数:
204
+ --check 只解析升级目标并对比当前版本
205
+ --channel <name> beta 或 latest;不给时按当前版本是否带 - 推断
206
+ --version <ver> 精确版本;与 --channel 互斥
207
+ --prefix <dir> 安装前缀,默认 ~/.local
208
+ --dry-run 不安装,只回报将执行的 install/verify 命令
209
+ --force 强制重装当前版本;配合 --version 才允许降级
210
+ --yes 跳过确认;非交互环境本来就不提示
211
+ --json 输出 JSON,不混入任何日志
212
+ --no-color 关闭颜色(也支持 NO_COLOR 环境变量)
213
+
214
+ 升级过程分 7 步:冻结 Skill 版本 → 解析目标 → 检查版本关系 → 安装
215
+ → 定位新 CLI → 校验版本 → 复核环境。前 3 步失败不会改动机器;从第 4 步
216
+ (安装)起失败可能留下已更新但未复核的 CLI,此时按提示重跑 setup 或
217
+ 用 --version <旧版本> --force 回退。
218
+
219
+ 非交互环境(管道 / CI / Agent 调用)不提示确认,直接执行。
220
+ 颜色只在终端里出现;管道里想要颜色需显式 --color。
221
+ `;
222
+ /**
223
+ * 帮助文本的打印时装饰。**不把 ANSI 写进常量**——常量一旦带色就没法降级,
224
+ * 而这里 color=false 时原样返回,NO_COLOR / 管道下与改造前逐字节相同。
225
+ *
226
+ * 只染整段连续短语:命令名从行首吃到两个以上空格为止,所以
227
+ * `dta setup`、`dta skill status` 这类断言目标始终完整。行数不变。
228
+ */
229
+ function renderHelp(text) {
230
+ const { ctx, theme } = stdoutUi();
231
+ if (!ctx.color)
232
+ return text;
233
+ return text.split('\n').map((line, index) => {
234
+ if (index === 1 && line.trim())
235
+ return theme.bold(line);
236
+ if (/^\S.*[::]$/.test(line))
237
+ return theme.bold(line);
238
+ if (/^\s+·\s/.test(line))
239
+ return theme.dim(line);
240
+ const command = line.match(/^(\s+)((?:dta|dingtalk-agent)\s\S.*?)(\s{2,})(.*)$/);
241
+ if (command)
242
+ return `${command[1]}${theme.noun(command[2])}${command[3]}${theme.dim(command[4])}`;
243
+ return line;
244
+ }).join('\n');
245
+ }
178
246
  function fail(msg, code = 1, hint = '') {
179
- console.error(`错误: ${msg}`);
247
+ // 整行染色,绝不做 danger('错误') + ': ' + msg 这种切分——
248
+ // 契约 eval 断言的是 stderr.includes('错误:'),切开就再也匹配不上。
249
+ const { theme } = stderrUi();
250
+ console.error(theme.danger(`错误: ${msg}`));
180
251
  if (hint)
181
- console.error(`提示: ${hint}`);
252
+ console.error(theme.dim(`提示: ${hint}`));
182
253
  process.exit(code);
183
254
  }
184
- const argv = process.argv.slice(2);
255
+ // --color / --no-color 在 dispatch 前预剥:parseArgs 是 strict 模式,
256
+ // 想让它成为全局参数就得改 23 个 options 块,预剥只需要这一处。
257
+ //
258
+ // 三条边界:`--` 之后的 token 是 positional,一律不碰;非法取值报错而不是
259
+ // 静默吞掉;同一参数出现多次以最后一次为准(与 parseArgs 的口径一致)。
260
+ const rawArgv = process.argv.slice(2);
261
+ const COLOR_VALUES = new Set(['auto', 'always', 'never']);
262
+ const terminator = rawArgv.indexOf('--');
263
+ const scannable = terminator === -1 ? rawArgv : rawArgv.slice(0, terminator);
264
+ let colorFlag = 'auto';
265
+ for (const item of scannable) {
266
+ if (item === '--no-color')
267
+ colorFlag = 'never';
268
+ else if (item === '--color')
269
+ colorFlag = 'always';
270
+ else if (item.startsWith('--color=')) {
271
+ const value = item.slice('--color='.length);
272
+ if (!COLOR_VALUES.has(value)) {
273
+ console.error(`错误: --color 只接受 auto | always | never,收到 ${JSON.stringify(value)}`);
274
+ process.exit(1);
275
+ }
276
+ colorFlag = value;
277
+ }
278
+ }
279
+ configureTui(colorFlag);
280
+ const isColorToken = (item) => item === '--color' || item === '--no-color' || item.startsWith('--color=');
281
+ const argv = terminator === -1
282
+ ? rawArgv.filter((item) => !isColorToken(item))
283
+ : [...rawArgv.slice(0, terminator).filter((item) => !isColorToken(item)), ...rawArgv.slice(terminator)];
185
284
  const cmd = argv[0];
186
285
  const sub = argv[1];
187
286
  try {
@@ -194,38 +293,38 @@ catch (e) {
194
293
  }
195
294
  async function main() {
196
295
  if (cmd === 'help' && sub === 'runtime') {
197
- console.log(RUNTIME_HELP);
296
+ console.log(renderHelp(RUNTIME_HELP));
198
297
  return;
199
298
  }
200
299
  if (cmd === 'help' && sub === 'adapters') {
201
- console.log(ADAPTER_HELP);
300
+ console.log(renderHelp(ADAPTER_HELP));
202
301
  return;
203
302
  }
204
303
  if (cmd === 'help' && sub === 'lab') {
205
- console.log(LAB_HELP);
304
+ console.log(renderHelp(LAB_HELP));
206
305
  return;
207
306
  }
208
307
  if (cmd === 'help' && sub === 'agent') {
209
- console.log(AGENT_HELP);
308
+ console.log(renderHelp(AGENT_HELP));
210
309
  return;
211
310
  }
212
311
  if (cmd === 'help' && sub === 'workspace') {
213
- console.log(WORKSPACE_HELP);
312
+ console.log(renderHelp(WORKSPACE_HELP));
214
313
  return;
215
314
  }
216
315
  if (cmd === 'help' && sub === 'deploy') {
217
- console.log(DEPLOY_HELP);
316
+ console.log(renderHelp(DEPLOY_HELP));
218
317
  return;
219
318
  }
220
319
  if (cmd === 'help' && ['promote', 'observe'].includes(sub || '')) {
221
- console.log(PROMOTION_HELP);
320
+ console.log(renderHelp(PROMOTION_HELP));
222
321
  return;
223
322
  }
224
323
  if (cmd === 'help' && sub) {
225
324
  fail(`不认识的帮助主题: ${sub}`, 1, '运行 dta --help 回到产品入口;事件宿主可用 dta help runtime。');
226
325
  }
227
326
  if (!cmd || cmd === '--help' || cmd === '-h' || cmd === 'help') {
228
- console.log(HELP);
327
+ console.log(renderHelp(HELP));
229
328
  return;
230
329
  }
231
330
  if (cmd === '--version' || cmd === '-V' || cmd === 'version') {
@@ -241,10 +340,11 @@ async function main() {
241
340
  workspace: { type: 'string' }, json: { type: 'boolean' },
242
341
  'dry-run': { type: 'boolean' }, yes: { type: 'boolean' },
243
342
  'plan-id': { type: 'string' }, 'no-wait': { type: 'boolean' },
343
+ wait: { type: 'boolean' },
244
344
  status: { type: 'boolean' }, list: { type: 'boolean' },
245
345
  execute: { type: 'boolean' }, 'operation-id': { type: 'string' },
246
346
  timeout: { type: 'string' }, 'smoke-timeout': { type: 'string' },
247
- retire: { type: 'boolean' },
347
+ retire: { type: 'boolean' }, 'rebind-runtime': { type: 'boolean' },
248
348
  },
249
349
  });
250
350
  if (positionals.length)
@@ -261,16 +361,21 @@ async function main() {
261
361
  const timeoutMs = parseOptionalTimeout(o.timeout, '--timeout');
262
362
  const smokeTimeoutMs = parseOptionalTimeout(o['smoke-timeout'], '--smoke-timeout');
263
363
  let out;
364
+ if (o.wait && o['no-wait'])
365
+ fail('deploy --wait 与 --no-wait 互斥');
366
+ if (o['rebind-runtime'] && o.retire)
367
+ fail('deploy --rebind-runtime 与 --retire 互斥');
264
368
  if (o.list) {
265
- if (o.execute || o['operation-id'] || o.retire || o['plan-id'] || o['no-wait'] ||
266
- o.timeout || o['smoke-timeout']) {
369
+ if (o.execute || o['operation-id'] || o.retire || o['plan-id'] || o['no-wait'] || o.wait ||
370
+ o.timeout || o['smoke-timeout'] || o['rebind-runtime']) {
267
371
  fail('deploy --list 只接受 --workspace 和 --json');
268
372
  }
269
373
  out = listMulticaDeployments(PROJECT_START, o.workspace, PACKAGE_JSON.version);
270
374
  }
271
375
  else if (o.status) {
272
- if (o.retire || o['plan-id'] || o['no-wait'] || o['dry-run'] || o['smoke-timeout']) {
273
- fail('deploy --status 不接受 retire/plan-id/no-wait/dry-run/smoke-timeout');
376
+ if (o.retire || o['plan-id'] || o['no-wait'] || o.wait || o['dry-run'] || o['smoke-timeout'] ||
377
+ o['rebind-runtime']) {
378
+ fail('deploy --status 不接受 retire/plan-id/no-wait/wait/dry-run/smoke-timeout/rebind-runtime');
274
379
  }
275
380
  if (Boolean(o.execute) !== Boolean(o.yes)) {
276
381
  fail('deploy --status 远端 reconcile 必须同时传 --execute --yes');
@@ -281,24 +386,30 @@ async function main() {
281
386
  });
282
387
  }
283
388
  else if (o['dry-run']) {
284
- if (o.execute || o['operation-id'] || o['plan-id'] || o['no-wait'] ||
389
+ if (o.execute || o['operation-id'] || o['plan-id'] || o['no-wait'] || o.wait ||
285
390
  o.timeout || o['smoke-timeout']) {
286
- fail('deploy --dry-run 不接受 execute/operation-id/plan-id/no-wait/timeout/smoke-timeout');
391
+ fail('deploy --dry-run 不接受 execute/operation-id/plan-id/no-wait/wait/timeout/smoke-timeout');
287
392
  }
288
393
  out = planMulticaDeployment(PROJECT_START, o.workspace, {
289
394
  action: o.retire ? 'retire' : 'apply', cliVersion: PACKAGE_JSON.version,
395
+ rebindRuntime: o['rebind-runtime'],
290
396
  });
291
397
  }
292
398
  else {
293
399
  if (o.execute || o['operation-id'] || o.status || o.list || o['dry-run']) {
294
400
  fail('deploy apply 不接受 execute/operation-id/status/list/dry-run');
295
401
  }
296
- if (o.retire && (o['no-wait'] || o['smoke-timeout'])) {
297
- fail('deploy --retire 不接受 no-wait/smoke-timeout');
402
+ if (o.retire && (o['no-wait'] || o.wait || o['smoke-timeout'])) {
403
+ fail('deploy --retire 不接受 no-wait/wait/smoke-timeout');
298
404
  }
405
+ // --wait widens the in-band smoke-wait budget so one command reaches ready across a
406
+ // cold-start runtime; an explicit --smoke-timeout still wins. Default stays ~30s.
407
+ const effectiveSmokeTimeoutMs = smokeTimeoutMs !== undefined
408
+ ? smokeTimeoutMs : o.wait ? 240_000 : undefined;
299
409
  out = applyMulticaDeployment(PROJECT_START, o.workspace, {
300
410
  yes: o.yes, planId: o['plan-id'], noWait: o['no-wait'],
301
- timeoutMs, smokeTimeoutMs, cliVersion: PACKAGE_JSON.version,
411
+ timeoutMs, smokeTimeoutMs: effectiveSmokeTimeoutMs, cliVersion: PACKAGE_JSON.version,
412
+ rebindRuntime: o['rebind-runtime'],
302
413
  }, o.retire ? 'retire' : 'apply');
303
414
  }
304
415
  if (o.json)
@@ -409,6 +520,106 @@ async function main() {
409
520
  printProjectInfo(out);
410
521
  return;
411
522
  }
523
+ if (cmd === 'schedule') {
524
+ const sub = argv[1];
525
+ const { values: o, positionals } = parseArgs({
526
+ args: argv.slice(2), allowPositionals: true,
527
+ options: { json: { type: 'boolean' }, workspace: { type: 'string' } },
528
+ });
529
+ if (sub === 'show') {
530
+ const out = buildScheduleShow(PROJECT_START, PACKAGE_JSON.version);
531
+ // 有阻断问题 = 非 0 退出,json 和人类两条路都设,否则自动化用 --json 拿到 exit 0 照样部署 → fail-open。
532
+ // 判据来自 scheduleBlockers(含 unverified),不在这手写,避免和 plan 那边分叉。
533
+ if (scheduleBlockers(out).length)
534
+ process.exitCode = 2;
535
+ if (o.json) {
536
+ console.log(JSON.stringify(out, null, 2));
537
+ return;
538
+ }
539
+ if (out.count === 0) {
540
+ console.log(muted('本 Project 未声明任何常驻节律(schedules)。'));
541
+ return;
542
+ }
543
+ console.log(`${strong('声明的常驻节律(本地 manifest 声明,非线上实态)')} · ${out.count} 条`);
544
+ for (const s of out.schedules) {
545
+ const metered = s.mode === 'work-item' ? '记账' : '不记账';
546
+ console.log(`\n ${noun(s.name)} ${s.cron} ${s.timezone} ${muted(`[${s.mode}·${metered}]`)}`);
547
+ if (s.wake)
548
+ console.log(muted(` 唤醒 ${s.wake}`));
549
+ if (s.completion)
550
+ console.log(muted(` ②判据 ${s.completion}`));
551
+ }
552
+ if (out.collidingPairs.length) {
553
+ console.log(`\n${mark('fail', `✗ 同刻并发: ${out.collidingPairs.join('、')} —— 外发拍同一时刻 fire 会重复送第三方,请错开`)}`);
554
+ }
555
+ if (out.missingCompletion.length) {
556
+ console.log(`${mark('fail', `✗ 缺完成判据: ${out.missingCompletion.join('、')} —— 没有数据派生的 completion,只能靠"跑没跑"判完成,漏拍就永久丢一期`)}`);
557
+ }
558
+ if (out.unverifiedPairs.length) {
559
+ console.log(`${mark('fail', `✗ 证不出是否同刻: ${out.unverifiedPairs.join('、')} —— 跨时区或 cron 扩展语法本地读不懂,一律当撞车挡住(0 9 * * * @Asia/Shanghai 和 0 1 * * * @UTC 就是同一时刻);把两条写成同一个 timezone,或换掉扩展语法`)}`);
560
+ }
561
+ for (const issue of out.completionRefIssues) {
562
+ console.log(`${mark('fail', `✗ 判据引用: ${issue}`)}`);
563
+ }
564
+ // 绿灯条件必须和退出码同源(scheduleBlockers),否则新增一类 blocker 时
565
+ // 人类输出继续报绿而 exit 2——手工操作者只看文案就会照常部署(issue #38)。
566
+ if (!scheduleBlockers(out).length) {
567
+ console.log(`\n${mark('pass', '✓ 各节律不同刻 fire、完成判据齐备且引用合法')}`);
568
+ }
569
+ return;
570
+ }
571
+ if (sub === 'plan') {
572
+ const workspaceName = o.workspace || positionals[0];
573
+ if (!workspaceName)
574
+ fail('schedule plan 需要 --workspace <name>');
575
+ const info = inspectAgentProject(PROJECT_START, PACKAGE_JSON.version);
576
+ const plan = buildSchedulePlan(info, workspaceName);
577
+ // fail closed(json 和人类两条路都设):plan 是 operator 真正照着部署的路径,
578
+ // 撞车不在这拦,两条会各建一个 autopilot → 重复外发。判据同 show,见 scheduleBlockers。
579
+ if (scheduleBlockers(plan).length)
580
+ process.exitCode = 2;
581
+ if (o.json) {
582
+ console.log(JSON.stringify(plan, null, 2));
583
+ return;
584
+ }
585
+ console.log(`${strong('schedule 部署计划')} · workspace=${noun(plan.workspace)} · provider=${plan.provider}`);
586
+ if (!plan.supported) {
587
+ console.log(`\n${mark('fail', `✗ 不支持: ${plan.reason}`)}`);
588
+ console.log(` ${noun(plan.reconcile)}`);
589
+ process.exitCode = 2;
590
+ return;
591
+ }
592
+ // 阻断时不打印任何可执行命令:手工操作者会复制粘贴人类输出里的命令,
593
+ // 「成功版面 + 可执行命令 + exit 2」等于把 fail-closed 只留给脚本(issue #38)。
594
+ const blocked = scheduleBlockers(plan).length > 0;
595
+ if (!blocked) {
596
+ console.log(` ${plan.count} 条声明 → provider ops:\n`);
597
+ for (const s of plan.schedules) {
598
+ console.log(` ${muted(`# ${s.name} [${s.metered ? '记账' : '不记账·平台看不到成本'}]`)}\n ${noun(s.applyCommand)}\n`);
599
+ }
600
+ }
601
+ else {
602
+ console.log(`\n${mark('fail', `✗ 已阻断:manifest 存在必须先修复的问题,本计划不可执行(apply 命令已隐藏)`)}`);
603
+ }
604
+ if (plan.collidingPairs.length) {
605
+ console.log(`${mark('fail', `✗ 同刻并发: ${plan.collidingPairs.join('、')} —— 别照此部署,两条会各建一个 autopilot → 重复外发;先错开 fire 时刻`)}`);
606
+ }
607
+ if (plan.missingCompletion.length) {
608
+ console.log(`${mark('fail', `✗ 缺完成判据: ${plan.missingCompletion.join('、')} —— 补上数据派生的 completion,别靠"跑没跑"判完成`)}`);
609
+ }
610
+ if (plan.unverifiedPairs.length) {
611
+ console.log(`${mark('fail', `✗ 证不出是否同刻: ${plan.unverifiedPairs.join('、')} —— 别照此部署。跨时区或扩展语法本地判不了,一律当撞车;把两条写成同一个 timezone 再 plan,判得出不撞就自动放行`)}`);
612
+ }
613
+ for (const issue of plan.completionRefIssues) {
614
+ console.log(`${mark('fail', `✗ 判据引用: ${issue}`)}`);
615
+ }
616
+ if (!blocked)
617
+ console.log(`执行: ${noun(plan.reconcile)}`);
618
+ return;
619
+ }
620
+ fail(`可用: dta schedule show | plan --workspace <name>${suggest(sub || '', ['show', 'plan'], 'dta schedule')}`);
621
+ return;
622
+ }
412
623
  if (cmd === 'workspace') {
413
624
  const { values: o, positionals } = parseArgs({
414
625
  args: argv.slice(2), allowPositionals: true,
@@ -419,6 +630,8 @@ async function main() {
419
630
  cases: { type: 'string' }, lanes: { type: 'string' }, runs: { type: 'string' },
420
631
  out: { type: 'string' }, timeout: { type: 'string' },
421
632
  'previous-skill': { type: 'string' },
633
+ status: { type: 'boolean' }, 'run-id': { type: 'string' },
634
+ provider: { type: 'string' }, stage: { type: 'string' }, profile: { type: 'string' },
422
635
  },
423
636
  });
424
637
  if (sub === 'list') {
@@ -540,16 +753,46 @@ async function main() {
540
753
  return;
541
754
  }
542
755
  if (sub === 'create') {
543
- rejectWorkspaceOptions(o, ['json', 'yes', 'dry-run'], 'workspace create');
544
756
  if (positionals.length !== 1)
545
757
  fail('workspace create 需要且只接受一个 Workspace name');
546
758
  if (o.yes && o['dry-run'])
547
759
  fail('workspace create 的 --dry-run 与 --yes 不能同时使用');
760
+ const wsName = positionals[0];
761
+ // Resolve the provider kind to scaffold/build: explicit --provider wins, else an already
762
+ // declared Workspace keeps its kind, else the attributed platform's provider (registry-
763
+ // driven so deap inherits it), else opencode. multica has no local host — create authors
764
+ // its config entry; opencode keeps building the managed host.
765
+ if (o.provider && !['opencode', 'multica'].includes(String(o.provider))) {
766
+ fail('workspace create --provider 只支持 opencode 或 multica');
767
+ }
768
+ const declaredWs = inspectAgentProject(PROJECT_START, PACKAGE_JSON.version)
769
+ .workspaces.find((item) => item.name === wsName);
770
+ const platformProvider = resolveAgentPlatform(PROJECT_START).definition?.provider;
771
+ const kind = String(o.provider || declaredWs?.provider.kind || platformProvider || 'opencode');
772
+ if (declaredWs && o.provider && declaredWs.provider.kind !== kind) {
773
+ fail(`Workspace ${wsName} 已声明为 ${declaredWs.provider.kind},与 --provider ${kind} 冲突`);
774
+ }
775
+ if (kind === 'multica') {
776
+ rejectWorkspaceOptions(o, ['json', 'yes', 'dry-run', 'provider', 'stage', 'profile'], 'workspace create');
777
+ const out = scaffoldMulticaWorkspace(PROJECT_START, wsName, {
778
+ stage: o.stage, profile: o.profile, apply: o.yes, cliVersion: PACKAGE_JSON.version,
779
+ });
780
+ if (o.json)
781
+ console.log(JSON.stringify(out, null, 2));
782
+ else {
783
+ const tag = out.alreadyDeclared ? '已存在' : out.applied ? '已写入' : 'DRY-RUN';
784
+ console.log(`${tag} workspace ${out.workspace} · provider=multica · profile=${out.profile}`);
785
+ for (const step of out.nextSteps)
786
+ console.log(` ${step}`);
787
+ }
788
+ return;
789
+ }
790
+ rejectWorkspaceOptions(o, ['json', 'yes', 'dry-run', 'provider'], 'workspace create');
548
791
  const out = o.yes
549
- ? applyOpenCodeWorkspace(PROJECT_START, positionals[0], {
792
+ ? applyOpenCodeWorkspace(PROJECT_START, wsName, {
550
793
  yes: true, cliVersion: PACKAGE_JSON.version,
551
794
  })
552
- : planOpenCodeWorkspace(PROJECT_START, positionals[0], PACKAGE_JSON.version);
795
+ : planOpenCodeWorkspace(PROJECT_START, wsName, PACKAGE_JSON.version);
553
796
  if (o.json)
554
797
  console.log(JSON.stringify(out, null, 2));
555
798
  else
@@ -564,30 +807,65 @@ async function main() {
564
807
  if (o.json)
565
808
  console.log(JSON.stringify(out, null, 2));
566
809
  else
567
- console.log(`✅ current workspace: ${out.previous || '-'} → ${out.current}`);
810
+ console.log(`${mark('pass', '✅')} current workspace: ${muted(out.previous || '-')} → ${noun(out.current)}`);
568
811
  return;
569
812
  }
570
813
  if (sub === 'run') {
814
+ const timeoutMs = o.timeout === undefined ? undefined : Number(o.timeout);
815
+ if (o.status) {
816
+ // Resume a timed-out multica run by re-reading the SAME Issue; no new Issue is created.
817
+ rejectWorkspaceOptions(o, ['json', 'yes', 'execute', 'out', 'timeout', 'status', 'run-id'], 'workspace run --status');
818
+ if (positionals.length !== 1)
819
+ fail('workspace run --status 需要一个 Workspace name');
820
+ if (o['prompt-file'])
821
+ fail('workspace run --status 不接受 --prompt-file;用 --run-id 续读');
822
+ if (!o['run-id'])
823
+ fail('workspace run --status 需要 --run-id <上次 run 的 runId>');
824
+ const out = statusMulticaWorkspaceRun(PROJECT_START, positionals[0], {
825
+ runId: o['run-id'], execute: o.execute, yes: o.yes, timeoutMs, out: o.out,
826
+ cliVersion: PACKAGE_JSON.version,
827
+ });
828
+ if (o.json)
829
+ console.log(JSON.stringify(out, null, 2));
830
+ else {
831
+ console.log(`${out.passed ? '✅' : '❌'} workspace run ${out.runId} (resumed)`);
832
+ if (out.answer)
833
+ console.log(out.answer);
834
+ }
835
+ if (!out.passed)
836
+ process.exitCode = 2;
837
+ return;
838
+ }
571
839
  rejectWorkspaceOptions(o, ['json', 'yes', 'execute', 'prompt-file', 'out', 'timeout'], 'workspace run');
572
840
  if (positionals.length !== 1 || !o['prompt-file']) {
573
841
  fail('workspace run 需要一个 Workspace name 和 --prompt-file <file>');
574
842
  }
575
- const timeoutMs = o.timeout === undefined ? undefined : Number(o.timeout);
576
843
  const prompt = readProjectText(PROJECT_START, o['prompt-file'], 'workspace run prompt');
577
- const out = runOpenCodeWorkspace(PROJECT_START, positionals[0], prompt, {
578
- execute: o.execute, yes: o.yes, timeoutMs, out: o.out,
579
- });
844
+ const workspace = showDevelopmentWorkspace(PROJECT_START, positionals[0], PACKAGE_JSON.version);
845
+ const out = workspace.provider.kind === 'multica'
846
+ ? runMulticaWorkspaceIssue(PROJECT_START, positionals[0], prompt, {
847
+ execute: o.execute, yes: o.yes, timeoutMs, out: o.out,
848
+ cliVersion: PACKAGE_JSON.version,
849
+ })
850
+ : runOpenCodeWorkspace(PROJECT_START, positionals[0], prompt, {
851
+ execute: o.execute, yes: o.yes, timeoutMs, out: o.out,
852
+ });
580
853
  if (o.json)
581
854
  console.log(JSON.stringify(out, null, 2));
582
855
  else if (out.$schema === 'dingtalk-agent/opencode-workspace-run-plan@1') {
583
- console.log(`DRY-RUN workspace=${out.workspace} model=${out.model} prompt=${out.promptHash}`);
856
+ console.log(`${mark('muted', 'DRY-RUN')} workspace=${noun(out.workspace)} model=${out.model} prompt=${out.promptHash}`);
857
+ }
858
+ else if (out.$schema === 'dingtalk-agent/multica-workspace-run-plan@1') {
859
+ console.log(`${mark('muted', 'DRY-RUN')} workspace=${noun(out.workspace)} agent=${out.agentId || '-'} prompt=${out.promptHash}`);
584
860
  }
585
861
  else {
586
- console.log(`${out.passed ? '✅' : '❌'} workspace run ${out.runId}`);
587
- if (out.execution?.answer)
862
+ console.log(`${mark(levelOf(Boolean(out.passed)), out.passed ? '✅' : '❌')} workspace run ${noun(out.runId)}`);
863
+ if ('execution' in out && out.execution?.answer)
588
864
  console.log(out.execution.answer);
865
+ if ('answer' in out && out.answer)
866
+ console.log(out.answer);
589
867
  if (out.evidencePath)
590
- console.log(`evidence=${out.evidencePath}`);
868
+ console.log(`evidence=${noun(out.evidencePath)}`);
591
869
  }
592
870
  if (o.execute && !out.passed)
593
871
  process.exitCode = 2;
@@ -656,23 +934,72 @@ async function main() {
656
934
  return;
657
935
  }
658
936
  if (cmd === 'upgrade') {
937
+ // parseArgs strict 模式会把 --help 当成未知参数抛掉,必须先拦。
938
+ if (argv.includes('--help') || argv.includes('-h')) {
939
+ console.log(renderHelp(UPGRADE_HELP));
940
+ return;
941
+ }
659
942
  const { values: o } = parseArgs({
660
943
  args: argv.slice(1),
661
944
  options: {
662
945
  channel: { type: 'string' }, version: { type: 'string' }, prefix: { type: 'string' },
663
946
  'dry-run': { type: 'boolean' }, force: { type: 'boolean' }, json: { type: 'boolean' },
947
+ check: { type: 'boolean' }, yes: { type: 'boolean' },
664
948
  },
665
949
  });
666
- const out = upgradeAgent(PACKAGE_ROOT, {
667
- channel: o.channel,
668
- version: o.version, prefix: o.prefix,
950
+ const channel = o.channel;
951
+ const json = Boolean(o.json);
952
+ if (o.check) {
953
+ if (o['dry-run'] || o.force || o.prefix || o.yes) {
954
+ fail('upgrade --check 只接受 --channel / --version / --json');
955
+ }
956
+ const checked = checkUpgrade(PACKAGE_ROOT, { channel, version: o.version });
957
+ if (json)
958
+ console.log(JSON.stringify(checked, null, 2));
959
+ else
960
+ printUpgradeCheck(checked);
961
+ return;
962
+ }
963
+ // --json 是机器通道:结构性绕开 tui,连回调都不构造。
964
+ // 非交互(管道/CI/Agent)也不提示,保持"敲下去就装"的既有行为。
965
+ const interactive = !json && Boolean(process.stdin.isTTY);
966
+ let known = null;
967
+ if (interactive) {
968
+ known = checkUpgrade(PACKAGE_ROOT, { channel, version: o.version });
969
+ printUpgradePreamble(known, { dryRun: Boolean(o['dry-run']), force: Boolean(o.force) });
970
+ const proceed = await confirm(o['dry-run'] ? '是否预览升级?' : '是否升级?', { yes: Boolean(o.yes), fallback: true });
971
+ if (!proceed) {
972
+ console.error(stderrUi().theme.dim(' 已取消'));
973
+ return;
974
+ }
975
+ }
976
+ const reporter = json ? null : new StepReporter({ total: o['dry-run'] ? 3 : 7 });
977
+ const result = upgradeAgent(PACKAGE_ROOT, {
978
+ channel, version: o.version, prefix: o.prefix,
669
979
  dryRun: o['dry-run'], force: o.force,
980
+ // 已经解析过就别再问一次 registry;**同时把安装源钉死到精确版本**——
981
+ // 只冻结期望版本而仍按 `pkg@latest` 安装,会在用户思考的这几秒里
982
+ // 被移动的 tag 装进另一个版本,然后按旧期望校验失败,而机器已经改了。
983
+ env: known
984
+ ? {
985
+ DTA_UPGRADE_TARGET_VERSION: known.targetVersion,
986
+ DTA_UPGRADE_PACKAGE_SOURCE: known.pinnedSource,
987
+ }
988
+ : undefined,
989
+ onEvent: reporter
990
+ ? (event) => {
991
+ if (event.phase === 'begin')
992
+ reporter.begin(event.label);
993
+ else
994
+ reporter.end(event.ok !== false, event.meta || '');
995
+ }
996
+ : undefined,
670
997
  });
671
- if (o.json)
672
- console.log(JSON.stringify(out, null, 2));
998
+ if (json)
999
+ console.log(JSON.stringify(result, null, 2));
673
1000
  else
674
- printUpgrade(out);
675
- if (!out.dryRun && !out.ready)
1001
+ printUpgrade(result);
1002
+ if (!result.dryRun && !result.ready)
676
1003
  process.exitCode = 2;
677
1004
  return;
678
1005
  }
@@ -715,9 +1042,16 @@ async function main() {
715
1042
  }
716
1043
  if (o.json)
717
1044
  console.log(JSON.stringify(out, null, 2));
718
- else if ('skills' in out)
1045
+ else if ('skills' in out) {
719
1046
  for (const item of out.skills)
720
1047
  printSkillStatus(item, sub, dryRun);
1048
+ // 静默删目录会让人以为什么都没发生;说清楚删了哪几个旧名。
1049
+ for (const item of out.prunedLegacy || []) {
1050
+ console.log(item.replacedBy
1051
+ ? ` 已迁移 ${item.name} → ${item.replacedBy}`
1052
+ : ` 已清理停用的 Skill ${item.name}`);
1053
+ }
1054
+ }
721
1055
  else
722
1056
  printSkillStatus(out, sub, dryRun);
723
1057
  return;
@@ -772,7 +1106,7 @@ async function main() {
772
1106
  if (o.json)
773
1107
  console.log(JSON.stringify({ ...out, endpoint, readiness, targets, doc }, null, 2));
774
1108
  else {
775
- console.log(`✅ 工作区已归属 ${out.definition?.label}(写入 ${out.written})`);
1109
+ console.log(`${mark('pass', '✅')} 工作区已归属 ${strong(out.definition?.label)}(写入 ${noun(out.written)})`);
776
1110
  for (const install of out.skillInstalls)
777
1111
  printSkillStatus(install, 'install', false);
778
1112
  if (!out.skillInstalls.length) {
@@ -812,14 +1146,14 @@ async function main() {
812
1146
  if (o.json)
813
1147
  console.log(JSON.stringify(out, null, 2));
814
1148
  else {
815
- console.log(`✅ ${out.mode === 'workspace' ? '已装载 Workspace' : '直接会话模式'}`);
1149
+ console.log(`${mark('pass', '✅')} ${strong(out.mode === 'workspace' ? '已装载 Workspace' : '直接会话模式')}`);
816
1150
  console.log(` scope=${out.scopeId} initialized=${out.initialized}`);
817
1151
  console.log(` agent=${out.definition.id} definition=${out.definition.status}`);
818
1152
  if (!out.mounts.length)
819
- console.log(' 没有发现持久上下文;这不是错误,也不需要自动 init。');
1153
+ console.log(muted(' 没有发现持久上下文;这不是错误,也不需要自动 init。'));
820
1154
  for (const mount of out.mounts)
821
- console.log(` ${mount.slot}: ${mount.path}`);
822
- console.log(` ${out.note}`);
1155
+ console.log(` ${mount.slot}: ${noun(mount.path)}`);
1156
+ console.log(` ${muted(out.note)}`);
823
1157
  }
824
1158
  return;
825
1159
  }
@@ -873,6 +1207,7 @@ async function main() {
873
1207
  'state-dir': { type: 'string' }, profile: { type: 'string' },
874
1208
  'expected-user-id': { type: 'string' }, 'require-skill': { type: 'string' },
875
1209
  'verify-load': { type: 'boolean' }, yes: { type: 'boolean' },
1210
+ 'strict-single-run': { type: 'boolean' },
876
1211
  model: { type: 'string' }, out: { type: 'string' },
877
1212
  'load-report': { type: 'string' }, 'remote-report': { type: 'string' },
878
1213
  json: { type: 'boolean' },
@@ -889,6 +1224,7 @@ async function main() {
889
1224
  bindings: loaded ? { path: loaded.path, hash: loaded.hash } : undefined,
890
1225
  requiredSkills: o['require-skill']?.split(','),
891
1226
  verifyLoad: o['verify-load'], yes: o.yes, model: o.model, out: o.out,
1227
+ strictSingleRun: o['strict-single-run'],
892
1228
  loadReport: o['load-report'], remoteReport: o['remote-report'],
893
1229
  });
894
1230
  if (o.json)
@@ -1205,9 +1541,9 @@ async function main() {
1205
1541
  if (o.json)
1206
1542
  console.log(JSON.stringify(out, null, 2));
1207
1543
  else if (!out.found)
1208
- console.log('当前 Session 尚无 Task checkpoint。');
1544
+ console.log(muted('当前 Session 尚无 Task checkpoint。'));
1209
1545
  else {
1210
- console.log(`✅ Task ${out.checkpoint.taskId} · ${out.checkpoint.status}`);
1546
+ console.log(`${mark('pass', '✅')} Task ${noun(out.checkpoint.taskId)} · ${out.checkpoint.status}`);
1211
1547
  console.log(` revision=${out.checkpoint.revision} hash=${out.hash}`);
1212
1548
  console.log(` next=${out.checkpoint.nextAction || '-'}`);
1213
1549
  }
@@ -1232,7 +1568,7 @@ async function main() {
1232
1568
  if (o.json || !o.live)
1233
1569
  console.log(JSON.stringify(out, null, 2));
1234
1570
  else {
1235
- console.log(`${out.verified ? '✅' : '⚠️'} Operational Memory · ${out.mode}`);
1571
+ console.log(`${mark(out.verified ? 'pass' : 'warn', out.verified ? '✅' : '⚠️')} ${strong('Operational Memory')} · ${out.mode}`);
1236
1572
  console.log(` record=${out.recordId || '-'} verification=${out.verification}`);
1237
1573
  }
1238
1574
  if (out.mode === 'uncertain')
@@ -1286,18 +1622,78 @@ async function main() {
1286
1622
  if (o.json)
1287
1623
  console.log(JSON.stringify(out, null, 2));
1288
1624
  else if (action === 'publish') {
1289
- console.log(`${out.verified ? '✅' : out.mode === 'dry-run' ? '🔎' : '⚠️'} Candidate ${o.id}`);
1625
+ console.log(`${mark(out.verified ? 'pass' : out.mode === 'dry-run' ? 'muted' : 'warn', out.verified ? '✅' : out.mode === 'dry-run' ? '🔎' : '⚠️')} Candidate ${noun(o.id)}`);
1290
1626
  console.log(` mode=${out.mode} targetHash=${out.currentTargetHash}`);
1291
1627
  }
1292
1628
  else {
1293
- console.log(`✅ Candidate ${out.candidate.id} · ${out.candidate.status}`);
1294
- console.log(` revision=${out.candidate.revision} path=${out.path}`);
1629
+ console.log(`${mark('pass', '✅')} Candidate ${noun(out.candidate.id)} · ${out.candidate.status}`);
1630
+ console.log(` revision=${out.candidate.revision} path=${noun(out.path)}`);
1295
1631
  }
1296
1632
  if (action === 'publish' && out.mode === 'uncertain')
1297
1633
  process.exitCode = 2;
1298
1634
  return;
1299
1635
  }
1300
- fail('memory 当前支持: operational upsert / candidate propose|show|review|publish');
1636
+ if (sub === 'noop') {
1637
+ const { values: o } = parseArgs({
1638
+ args: argv.slice(3),
1639
+ options: {
1640
+ input: { type: 'string' }, scope: { type: 'string' }, step: { type: 'string' },
1641
+ occurrence: { type: 'string' }, 'state-dir': { type: 'string' },
1642
+ session: { type: 'string' }, json: { type: 'boolean' },
1643
+ },
1644
+ });
1645
+ const noopCfg = cfgmod.load(ROOT);
1646
+ const options = {
1647
+ stateDir: o['state-dir'], runPath: process.env.DTA_RUN,
1648
+ sessionPath: o.session || process.env.DTA_SESSION,
1649
+ sessionRoot: noopCfg?.runtime?.sessionRoot,
1650
+ };
1651
+ if (action === 'record') {
1652
+ if (!o.input)
1653
+ fail('memory noop record 需要 --input(memory-noop-claim@1 JSON)');
1654
+ const out = recordMemoryNoop(ROOT, readEventFile(o.input), options);
1655
+ if (o.json)
1656
+ console.log(JSON.stringify(out, null, 2));
1657
+ else {
1658
+ console.log(`${mark('pass', '✅')} No-op Receipt ${noun(out.receipt.id)}${out.duplicate ? ' · duplicate(幂等命中既有痕迹)' : ''}`);
1659
+ console.log(` step=${out.receipt.step} occurrence=${out.receipt.occurrence} reason=${out.receipt.reason}`);
1660
+ }
1661
+ }
1662
+ else if (action === 'list') {
1663
+ const out = listMemoryNoops(ROOT, { scopeId: o.scope, step: o.step, occurrence: o.occurrence }, options);
1664
+ if (o.json)
1665
+ console.log(JSON.stringify(out, null, 2));
1666
+ else {
1667
+ console.log(`${mark('pass', '✅')} No-op Receipts · ${out.count} 条`);
1668
+ for (const receipt of out.receipts) {
1669
+ console.log(` ${receipt.id} ${receipt.step} ${receipt.occurrence} ${receipt.reason}`);
1670
+ }
1671
+ }
1672
+ }
1673
+ else if (action === 'trace') {
1674
+ if (!o.scope || !o.occurrence)
1675
+ fail('memory noop trace 需要 --scope 和 --occurrence');
1676
+ const out = traceMemoryNoop(ROOT, { scopeId: o.scope, occurrence: o.occurrence, step: o.step }, options);
1677
+ if (o.json)
1678
+ console.log(JSON.stringify(out, null, 2));
1679
+ else {
1680
+ const ran = out.verdict === 'ran-empty';
1681
+ console.log(`${mark(ran ? 'pass' : 'warn', ran ? '✅' : '⚠️')} ${strong(out.verdict)} · step=${out.step} occurrence=${noun(out.occurrence)}`);
1682
+ if (ran)
1683
+ console.log(` receipt=${out.receiptId}`);
1684
+ else
1685
+ console.log(' 没有任何痕迹——「判过无内容」会留 no-op Receipt,这里连它都没有');
1686
+ }
1687
+ // 判存步骤没跑就是没跑:给脚本一个可断言的非零退出码(区别于 uncertain 的 2)。
1688
+ if (out.verdict === 'no-trace')
1689
+ process.exitCode = 3;
1690
+ }
1691
+ else {
1692
+ fail('memory noop 的子命令: record / list / trace');
1693
+ }
1694
+ return;
1695
+ }
1696
+ fail('memory 当前支持: operational upsert / candidate propose|show|review|publish / noop record|list|trace');
1301
1697
  }
1302
1698
  // ── act:元语化动作;目标/身份/预算/状态转移都由 Run 固定 ──
1303
1699
  if (cmd === 'act') {
@@ -1322,10 +1718,10 @@ async function main() {
1322
1718
  if (o.json || o['dry-run'])
1323
1719
  console.log(JSON.stringify(out, null, 2));
1324
1720
  else {
1325
- console.log(`${out.verified ? '✅' : '⚠️'} ${out.kind} · ${out.outcome} · ${out.toState}`);
1326
- console.log(` action=${out.actionId} run=${out.runId}`);
1721
+ console.log(`${mark(out.verified ? 'pass' : 'warn', out.verified ? '✅' : '⚠️')} ${strong(out.kind)} · ${out.outcome} · ${out.toState}`);
1722
+ console.log(` action=${noun(out.actionId)} run=${noun(out.runId)}`);
1327
1723
  if (!out.verified)
1328
- console.log(` ${out.verification}`);
1724
+ console.log(` ${muted(out.verification)}`);
1329
1725
  }
1330
1726
  if (out.outcome === 'uncertain')
1331
1727
  process.exitCode = 2;
@@ -1397,11 +1793,41 @@ function editDistance(a, b) {
1397
1793
  }
1398
1794
  return row[b.length];
1399
1795
  }
1796
+ /** 按语义给状态标记上色;字形原样返回,不做任何替换。 */
1797
+ function mark(level, text) {
1798
+ const { theme } = stdoutUi();
1799
+ return level === 'pass' ? theme.success(text)
1800
+ : level === 'warn' ? theme.warn(text)
1801
+ : level === 'fail' ? theme.danger(text)
1802
+ : theme.dim(text);
1803
+ }
1804
+ /** 可复制的名词:路径、版本号、ID、命令。 */
1805
+ function noun(text) {
1806
+ return stdoutUi().theme.noun(text);
1807
+ }
1808
+ /** 次要信息:补充说明、括注、下一步提示。 */
1809
+ function muted(text) {
1810
+ return stdoutUi().theme.dim(text);
1811
+ }
1812
+ /** 标题行 / key=value 的结构性强调。 */
1813
+ function strong(text) {
1814
+ return stdoutUi().theme.bold(text);
1815
+ }
1816
+ /** ready 布尔 → 语义级别,printX 里到处都是这个三态。 */
1817
+ function levelOf(state) {
1818
+ if (state === true)
1819
+ return 'pass';
1820
+ if (state === false)
1821
+ return 'fail';
1822
+ return state;
1823
+ }
1400
1824
  function printDispatch(out, json = false) {
1401
1825
  if (json)
1402
1826
  return console.log(JSON.stringify(out, null, 2));
1403
- const mark = out.duplicate ? (out.launch ? '🔁 REDELIVER' : '↩️ DUPLICATE') : '✅ READY';
1404
- console.log(`${mark} ${out.runId}`);
1827
+ const state = out.duplicate
1828
+ ? mark(out.launch ? 'warn' : 'muted', out.launch ? '🔁 REDELIVER' : '↩️ DUPLICATE')
1829
+ : mark('pass', '✅ READY');
1830
+ console.log(`${state} ${noun(out.runId)}`);
1405
1831
  console.log(` Field=${out.fieldId} Session=${out.sessionId} Event=${out.eventKind}`);
1406
1832
  console.log(` cwd=${out.cwd}`);
1407
1833
  console.log(` entrypoint=${out.entrypoint} queue=${out.queue.key} (max=${out.queue.maxConcurrency})`);
@@ -1411,7 +1837,7 @@ function printDispatch(out, json = false) {
1411
1837
  }
1412
1838
  function printLab(out) {
1413
1839
  const passed = out.ready ?? out.passed ?? out.assertions?.passed;
1414
- console.log(`${passed === false ? '❌' : '✅'} ${out.$schema || 'dingtalk-agent/lab'}`);
1840
+ console.log(`${mark(levelOf(passed !== false), passed === false ? '❌' : '✅')} ${out.$schema || 'dingtalk-agent/lab'}`);
1415
1841
  if (out.mode)
1416
1842
  console.log(` mode=${out.mode}`);
1417
1843
  if (out.marker)
@@ -1420,17 +1846,33 @@ function printLab(out) {
1420
1846
  console.log(` evidence=${out.evidencePath}`);
1421
1847
  if (Array.isArray(out.checks)) {
1422
1848
  for (const check of out.checks) {
1423
- console.log(` ${check.passed ? 'PASS' : 'FAIL'} ${check.id}: ${check.detail}`);
1849
+ console.log(` ${mark(levelOf(Boolean(check.passed)), check.passed ? 'PASS' : 'FAIL')} ${check.id}: ${check.detail}`);
1424
1850
  }
1425
1851
  }
1426
1852
  }
1427
1853
  function printAgentAudit(out) {
1428
- console.log(`dingtalk-agent agent audit · ${out.ready ? 'READY' : 'PARTIAL'}`);
1854
+ console.log(`${strong('dingtalk-agent agent audit')} · ${mark(out.ready ? 'pass' : 'warn', out.ready ? 'READY' : 'PARTIAL')}`);
1429
1855
  console.log(` definition=${out.definition.hash} storage=${out.storageMode}`);
1430
1856
  console.log(` dingtalkSideEffect=${out.dingtalkSideEffect} sideEffect=${out.sideEffect}`);
1431
1857
  for (const check of out.checks) {
1432
- const mark = check.level === 'pass' ? 'PASS' : check.level === 'warn' ? 'WARN' : 'FAIL';
1433
- console.log(` ${mark} ${check.id}: ${check.summary}`);
1858
+ const word = check.level === 'pass' ? 'PASS' : check.level === 'warn' ? 'WARN' : 'FAIL';
1859
+ console.log(` ${mark(levelOf(check.level), word)} ${check.id}: ${check.summary}`);
1860
+ const evidence = check.evidence;
1861
+ // 失败可归因:把 failureSummary 的前几条与证据路径直接摆到失败行下面,
1862
+ // 不再让人去 JSON 深处找「为什么」。flaky 通过(第二次 attempt 才过)也如实标注。
1863
+ if (check.level !== 'pass' && check.blocking && Array.isArray(evidence?.failureSummary)) {
1864
+ for (const line of evidence.failureSummary.slice(0, 3)) {
1865
+ console.log(` · ${line}`);
1866
+ }
1867
+ if (typeof evidence.report === 'string' && evidence.report) {
1868
+ console.log(` evidence: ${evidence.report}`);
1869
+ }
1870
+ }
1871
+ const verification = evidence?.verification;
1872
+ if (verification && Array.isArray(verification.attempts) && verification.attempts.length > 1) {
1873
+ console.log(` attempts: ${verification.attempts.length}` +
1874
+ `(passedOnAttempt=${verification.passedOnAttempt || '-'})`);
1875
+ }
1434
1876
  }
1435
1877
  if (out.repairs.length) {
1436
1878
  console.log('\n修复建议:');
@@ -1443,7 +1885,7 @@ function printAgentAudit(out) {
1443
1885
  }
1444
1886
  function printAgentEnhance(out) {
1445
1887
  if (out.$schema === 'dingtalk-agent/agent-enhancement-plan@1') {
1446
- console.log(`dingtalk-agent agent enhance · ${out.ready ? 'READY PLAN' : 'BLOCKED'}`);
1888
+ console.log(`${strong('dingtalk-agent agent enhance')} · ${mark(out.ready ? 'pass' : 'fail', out.ready ? 'READY PLAN' : 'BLOCKED')}`);
1447
1889
  console.log(` planId=${out.planId} project=${out.projectName}`);
1448
1890
  console.log(` sideEffect=${out.sideEffect} dingtalkSideEffect=${out.dingtalkSideEffect}`);
1449
1891
  for (const operation of out.operations) {
@@ -1453,12 +1895,12 @@ function printAgentEnhance(out) {
1453
1895
  console.log(` REVIEW ${out.semanticReview.files.join(', ')}: ${out.semanticReview.why}`);
1454
1896
  }
1455
1897
  for (const blocker of out.blockers)
1456
- console.log(` BLOCK ${blocker}`);
1898
+ console.log(` ${mark('fail', 'BLOCK')} ${blocker}`);
1457
1899
  if (out.ready)
1458
1900
  console.log(`\nApply:\n ${out.apply.command}`);
1459
1901
  return;
1460
1902
  }
1461
- console.log('dingtalk-agent agent enhance · APPLIED');
1903
+ console.log(`${strong('dingtalk-agent agent enhance')} · ${mark('pass', 'APPLIED')}`);
1462
1904
  console.log(` operation=${out.operationId} planId=${out.planId}`);
1463
1905
  console.log(` changes=${out.changes.length} idempotent=${out.idempotent}`);
1464
1906
  if (out.backupRoot)
@@ -1466,8 +1908,8 @@ function printAgentEnhance(out) {
1466
1908
  console.log(` audit=${out.audit.status} missing=${out.audit.missing.join(', ') || '-'}`);
1467
1909
  }
1468
1910
  function printAgentPlatform(platform) {
1469
- console.log(`${platform.status === 'supported' ? '✅' : '⏳'} ${platform.name} · ${platform.label} · ${platformStatusLabel(platform.status)}`);
1470
- console.log(` ${platform.description}`);
1911
+ console.log(`${mark(platform.status === 'supported' ? 'pass' : 'muted', platform.status === 'supported' ? '✅' : '⏳')} ${strong(platform.name)} · ${platform.label} · ${platformStatusLabel(platform.status)}`);
1912
+ console.log(` ${muted(platform.description)}`);
1471
1913
  for (const skill of platform.skills)
1472
1914
  console.log(` 技能[${skill.role}] ${skill.name} —— ${skill.purpose}`);
1473
1915
  if (platform.commands.length)
@@ -1475,12 +1917,12 @@ function printAgentPlatform(platform) {
1475
1917
  }
1476
1918
  function printAgentPlatformResolution(out) {
1477
1919
  if (!out.platform) {
1478
- console.log('⚠️ 当前工作区未声明 managed agent platform');
1920
+ console.log(mark('warn', '⚠️ 当前工作区未声明 managed agent platform'));
1479
1921
  console.log(' 运行 `dta agent-platform use multica-dingtalk`,或设置 DTA_AGENT_PLATFORM 环境变量。');
1480
1922
  return;
1481
1923
  }
1482
1924
  const known = out.definition;
1483
- console.log(`${known ? '✅' : '❌'} 当前 platform: ${out.platform}${known ? ` · ${known.label} · ${platformStatusLabel(known.status)}` : '(未知)'}`);
1925
+ console.log(`${mark(levelOf(Boolean(known)), known ? '✅' : '❌')} 当前 platform: ${strong(out.platform)}${known ? ` · ${known.label} · ${platformStatusLabel(known.status)}` : '(未知)'}`);
1484
1926
  console.log(` 来源: ${out.source}${out.file ? ` · ${out.file}` : ''}`);
1485
1927
  for (const skill of known?.skills || [])
1486
1928
  console.log(` 技能[${skill.role}] ${skill.name} —— ${skill.purpose}`);
@@ -1488,8 +1930,9 @@ function printAgentPlatformResolution(out) {
1488
1930
  // 披露:dta 层平台命令关联到平台内确切的 Skill(诊断信息走 stderr,不污染 --json stdout)。
1489
1931
  function disclosePlatformSkill(resolution, role) {
1490
1932
  const skillName = resolution.definition ? platformSkillForRole(resolution.definition.name, role) : null;
1491
- if (skillName)
1492
- console.error(`提示: 平台 ${resolution.definition.name} 的该命令方法学由 Skill ${skillName} 承载`);
1933
+ if (skillName) {
1934
+ console.error(stderrUi().theme.dim(`提示: 平台 ${resolution.definition.name} 的该命令方法学由 Skill ${skillName} 承载`));
1935
+ }
1493
1936
  }
1494
1937
  function printEndpoint(endpoint) {
1495
1938
  if (!endpoint)
@@ -1503,9 +1946,9 @@ function printEndpoint(endpoint) {
1503
1946
  console.log(` Endpoint: 未解析——设 ${endpoint.envVar} 或登录 multica 后生效`);
1504
1947
  return;
1505
1948
  }
1506
- const mark = endpoint.confirmed ? '✅' : '⚠️';
1949
+ const glyph = mark(endpoint.confirmed ? 'pass' : 'warn', endpoint.confirmed ? '✅' : '⚠️');
1507
1950
  const note = endpoint.confirmed ? '' : '(建议/未确认,deploy 前需显式确认是预发还是生产)';
1508
- console.log(` ${mark} Endpoint: ${endpoint.endpoint} · 来源: ${endpoint.source}${note}`);
1951
+ console.log(` ${glyph} Endpoint: ${noun(endpoint.endpoint)} · 来源: ${endpoint.source}${muted(note)}`);
1509
1952
  console.log(` (切换:设 ${endpoint.envVar}=<url>、写 dingtalk-agent.json#multicaEndpoint、或登录对应 profile;无需改代码)`);
1510
1953
  }
1511
1954
  function printPlatformDoc(platformName) {
@@ -1513,27 +1956,27 @@ function printPlatformDoc(platformName) {
1513
1956
  return;
1514
1957
  const path = platformDocPath(PACKAGE_ROOT, platformName);
1515
1958
  if (path)
1516
- console.log(` 平台说明: ${path}\n (切换到该平台后请阅读,了解各技能用途与交付链)`);
1959
+ console.log(` 平台说明: ${noun(path)}\n${muted(' (切换到该平台后请阅读,了解各技能用途与交付链)')}`);
1517
1960
  }
1518
1961
  function printMulticaTargets(targets) {
1519
1962
  if (!targets.length)
1520
1963
  return;
1521
- console.log(' 本机 Multica 登录目标(发布前必须与用户确认 endpoint 与 workspace):');
1964
+ console.log(strong(' 本机 Multica 登录目标(发布前必须与用户确认 endpoint 与 workspace):'));
1522
1965
  for (const target of targets) {
1523
1966
  console.log(` - profile=${target.profile} · ${target.serverUrl}${target.workspaceId ? ` · workspace=${target.workspaceId}` : ''}`);
1524
1967
  }
1525
1968
  }
1526
1969
  function printAgentPlatformReadiness(checks) {
1527
1970
  for (const check of checks) {
1528
- console.log(` ${check.ok ? '✅' : '⚠️'} ${check.summary}`);
1971
+ console.log(` ${mark(check.ok ? 'pass' : 'warn', check.ok ? '✅' : '⚠️')} ${check.summary}`);
1529
1972
  if (check.hint)
1530
- console.log(` ${check.hint}`);
1973
+ console.log(` ${muted(check.hint)}`);
1531
1974
  }
1532
1975
  }
1533
1976
  function printSkillStatus(out, action, dryRun) {
1534
- const prefix = dryRun ? 'DRY-RUN' : '✅';
1977
+ const prefix = dryRun ? mark('muted', 'DRY-RUN') : mark('pass', '✅');
1535
1978
  const verb = action === 'status' ? '全局 Skill 状态' : `skill ${action}`;
1536
- console.log(`${prefix} ${verb}: ${out.name}`);
1979
+ console.log(`${prefix} ${verb}: ${strong(out.name)}`);
1537
1980
  console.log(` manager=${out.manager} bundled=${out.bundled.skillVersion || '-'}`);
1538
1981
  console.log(` canonical=${out.canonical.path}`);
1539
1982
  console.log(` installed=${out.canonical.exists} ` +
@@ -1544,71 +1987,110 @@ function printSkillStatus(out, action, dryRun) {
1544
1987
  ` → ${out.operation.after.installedVersion}(${out.operation.after.state})`);
1545
1988
  }
1546
1989
  if (out.operation)
1547
- console.log(` delegated: ${out.operation.command}`);
1990
+ console.log(` delegated: ${noun(out.operation.command)}`);
1548
1991
  for (const client of out.clients) {
1549
- console.log(` ${client.label}=${client.state} (${client.discovery}: ${client.path})`);
1992
+ console.log(` ${client.label}=${mark(client.state === 'ok' ? 'pass' : 'warn', client.state)} (${client.discovery}: ${noun(client.path)})`);
1550
1993
  }
1551
1994
  if (action === 'install' && !dryRun) {
1552
- console.log(' 新 Agent 会话将自动发现;当前会话未发现时请重启 Claude Code/Codex。');
1995
+ console.log(muted(' 新 Agent 会话将自动发现;当前会话未发现时请重启 Claude Code/Codex。'));
1553
1996
  }
1554
1997
  }
1998
+ /**
1999
+ * doctor 的版面。改造前的问题不是"不够花哨",是三条实打实的:
2000
+ * 1. 同一事实说两遍——⚠️ 摘要和它的 detail 重复平台 Skill 漂移;三个客户端
2001
+ * 的 Skill 可见性在 check 区和「本机 Coding Agent」区各说一次。
2002
+ * 2. 一行塞太多——detail 用 ` | ` 把四条 canonical 路径和平台 Skill 状态拼在
2003
+ * 一起,200+ 字符,终端里必然折行。
2004
+ * 3. 分隔符混用 `·` `;` `|` 三种,没有一列是对齐的。
2005
+ * 所以这里按「一行一件事、明细缩进、列对齐」重排,并把客户端合并成一节。
2006
+ */
1555
2007
  function printDoctor(out) {
1556
- console.log(`dingtalk-agent doctor · ${out.ready ? 'READY' : 'NOT READY'}`);
1557
- for (const check of out.checks) {
1558
- const mark = check.level === 'pass' ? '✅' : check.level === 'warn' ? '⚠️' : '❌';
1559
- console.log(`${mark} ${check.summary}`);
1560
- if (check.detail)
1561
- console.log(` ${check.detail}`);
1562
- if (check.fix)
1563
- console.log(` 修复: ${check.fix}`);
1564
- }
1565
- if (out.hosts.length) {
1566
- // Host 面回答"本地能在哪跑",不改变上面的 ready 结论。
1567
- console.log('\n本机 Coding Agent:');
1568
- const width = Math.max(...out.hosts.map((host) => displayWidth(host.label)));
1569
- for (const host of out.hosts) {
1570
- const label = host.label + ' '.repeat(width - displayWidth(host.label));
1571
- const where = host.cli.found
1572
- ? [host.cli.path, host.cli.version].filter(Boolean).join(' ')
1573
- : `未找到 ${host.cli.command}`;
1574
- console.log(` ${host.cli.found ? '' : '❌'} ${label} ${where}`);
1575
- console.log(` Skill 可见性: ${host.skillExposure.exists ? '已暴露' : '未暴露'} · ${host.skillExposureRoot}`);
1576
- }
1577
- }
1578
- if (out.nextSteps.length) {
1579
- console.log('\n下一步:');
1580
- for (const step of out.nextSteps)
1581
- console.log(` ${step}`);
2008
+ const { theme } = stdoutUi();
2009
+ // 字形沿用全 CLI 既定的 ✅/⚠️/❌(bin 里各出现 24/11/5 次)。上一版换成
2010
+ // ✔/!/✗ doctor 成了唯一的异类,也违反 AGENTS.md「stdout 是稳定的人/
2011
+ // 机器接口」——颜色可以加,承载语义的字形不能动。三者都是 2 列宽,对齐不受影响。
2012
+ const glyph = (level) => level === 'pass' ? mark('pass', '✅')
2013
+ : level === 'warn' ? mark('warn', '⚠️') : mark('fail', '❌');
2014
+ const clientIds = new Set(['claude-code', 'codex', 'opencode']);
2015
+ const envChecks = out.checks.filter((check) => !clientIds.has(check.id));
2016
+ const clientChecks = out.checks.filter((check) => clientIds.has(check.id));
2017
+ console.log(`${strong('dingtalk-agent doctor')} · ${out.ready ? mark('pass', 'READY') : mark('fail', 'NOT READY')}`);
2018
+ console.log('');
2019
+ // 左列按显示宽度对齐——中文标签用 String.length 补齐必然错位
2020
+ const labelWidth = Math.max(...envChecks.map((check) => displayWidth(CHECK_LABELS[check.id])));
2021
+ for (const check of envChecks) {
2022
+ const label = padRight(CHECK_LABELS[check.id], labelWidth);
2023
+ console.log(` ${glyph(check.level)} ${strong(label)} ${check.value || check.summary}`);
2024
+ const items = check.items || [];
2025
+ const itemWidth = items.length ? Math.max(...items.map((i) => displayWidth(i.label))) : 0;
2026
+ const valueWidth = items.length ? Math.max(...items.map((i) => displayWidth(i.value || ''))) : 0;
2027
+ for (const item of items) {
2028
+ console.log(` ${noun(padRight(item.label, itemWidth))} ${padRight(item.value || '', valueWidth)}${item.note ? ` ${muted(item.note)}` : ''}`);
2029
+ }
2030
+ if (check.level !== 'pass' && check.fix) {
2031
+ // 一条命令一行。挤在同一行里用「与」串起来的命令没法直接复制。
2032
+ const commands = plain(check.fix).replace(/^运行\s*/, '').split(/\s*与\s*/);
2033
+ commands.forEach((command, index) => {
2034
+ console.log(` ${muted(index === 0 ? '修复' : ' ')} ${noun(command)}`);
2035
+ });
2036
+ }
2037
+ }
2038
+ if (clientChecks.length) {
2039
+ console.log('');
2040
+ console.log(` ${strong('Coding Agent')}`);
2041
+ const nameWidth = Math.max(...clientChecks.map((c) => displayWidth(CHECK_LABELS[c.id])));
2042
+ const hostOf = (id) => out.hosts.find((host) => host.name === id);
2043
+ // 版本列去掉冗余括注与前缀:claude 报 "2.1.218 (Claude Code)"、codex 报 "codex-cli 0.144.6"
2044
+ const versions = clientChecks.map((c) => (hostOf(c.id)?.cli.version || '')
2045
+ .replace(/\s*\(.*\)\s*$/, '').replace(/^\S+-cli\s+/, ''));
2046
+ const versionWidth = Math.max(...versions.map(displayWidth));
2047
+ clientChecks.forEach((check, index) => {
2048
+ const host = hostOf(check.id);
2049
+ const name = padRight(CHECK_LABELS[check.id], nameWidth);
2050
+ const version = padRight(versions[index], versionWidth);
2051
+ const where = host?.cli.found ? tilde(host.skillExposureRoot) : `未安装 ${host?.cli.command || ''}`;
2052
+ console.log(` ${glyph(check.level)} ${strong(name)} ${muted(version)} ${check.value || check.summary} ${noun(where)}`);
2053
+ });
2054
+ }
2055
+ // 「下一步」此前逐字重复上面已经打过的修复命令。只留没展示过的。
2056
+ const shown = new Set(out.checks.filter((c) => c.level !== 'pass' && c.fix).map((c) => c.fix));
2057
+ const remaining = out.nextSteps.filter((step) => !shown.has(step));
2058
+ if (remaining.length) {
2059
+ console.log('');
2060
+ for (const step of remaining)
2061
+ console.log(` ${strong('下一步')} ${noun(plain(step))}`);
1582
2062
  }
1583
2063
  }
1584
- /** 全角字符按两列计宽,避免中英混排的 Host 列表错位。 */
1585
- function displayWidth(text) {
1586
- let width = 0;
1587
- for (const char of text)
1588
- width += /[ᄀ-ᅟ⺀-꓏가-힣豈-﫿︰-﹯＀-⦆¢-₩]/.test(char) ? 2 : 1;
1589
- return width;
2064
+ /** 反引号在终端里不是强调,是噪声;着色已经承担了同一职责。 */
2065
+ function plain(text) {
2066
+ return text.replace(/`/g, '');
2067
+ }
2068
+ /** 家目录缩成 ~/,省掉对读者零信息量的前缀。 */
2069
+ function tilde(path) {
2070
+ const home = process.env.HOME || '';
2071
+ return home && path.startsWith(home) ? `~${path.slice(home.length)}` : path;
1590
2072
  }
1591
2073
  function printProjectInfo(out) {
1592
- console.log(`dingtalk-agent Project · ${out.project.name}`);
2074
+ console.log(`${strong('dingtalk-agent Project')} · ${noun(out.project.name)}`);
1593
2075
  console.log(` root=${out.root}`);
1594
2076
  console.log(` source=${out.source} manifest=${out.manifest || '(synthetic legacy)'}`);
1595
2077
  console.log(` hash=${out.manifestHash} dta=${out.project.dtaVersion}`);
1596
2078
  console.log(` current=${out.currentWorkspace || '(not selected)'}`);
1597
2079
  for (const workspace of out.workspaces) {
1598
- console.log(` ${workspace.current ? '*' : '-'} ${workspace.name} · ${workspace.provider.kind} · ${workspace.status}`);
2080
+ console.log(` ${workspace.current ? strong('*') : muted('-')} ${workspace.name} · ${workspace.provider.kind} · ${workspace.status}`);
1599
2081
  }
1600
2082
  }
1601
2083
  function printWorkspaceList(out) {
1602
- console.log(`dingtalk-agent workspaces · ${out.project.name}`);
2084
+ console.log(`${strong('dingtalk-agent workspaces')} · ${noun(out.project.name)}`);
1603
2085
  if (!out.workspaces.length)
1604
- console.log(' 尚未声明 Development Workspace。');
2086
+ console.log(muted(' 尚未声明 Development Workspace。'));
1605
2087
  for (const workspace of out.workspaces) {
1606
- console.log(` ${workspace.current ? '*' : '-'} ${workspace.name} provider=${workspace.provider.kind} ` +
2088
+ console.log(` ${workspace.current ? strong('*') : muted('-')} ${workspace.name} provider=${workspace.provider.kind} ` +
1607
2089
  `stage=${workspace.stage} status=${workspace.status}`);
1608
2090
  }
1609
2091
  }
1610
2092
  function printDevelopmentWorkspace(out) {
1611
- console.log(`dingtalk-agent workspace · ${out.name}`);
2093
+ console.log(`${strong('dingtalk-agent workspace')} · ${noun(out.name)}`);
1612
2094
  console.log(` provider=${out.provider.kind} stage=${out.stage} status=${out.status}`);
1613
2095
  console.log(` desired=${out.desiredHash} observed=${out.observedHash || '-'}`);
1614
2096
  console.log(` current=${out.current} legacy=${out.legacy}`);
@@ -1618,19 +2100,22 @@ function printDevelopmentWorkspace(out) {
1618
2100
  console.log(` source=${source}`);
1619
2101
  }
1620
2102
  function printDevelopmentWorkspaceDoctor(out) {
1621
- console.log(`dingtalk-agent workspace doctor · ${out.workspace} · ${out.ready ? 'READY' : 'PARTIAL'}`);
2103
+ console.log(`${strong('dingtalk-agent workspace doctor')} · ${noun(out.workspace)} · ${mark(out.ready ? 'pass' : 'warn', out.ready ? 'READY' : 'PARTIAL')}`);
1622
2104
  console.log(` provider=${out.provider} status=${out.status}`);
1623
2105
  console.log(` sideEffect=${out.sideEffect} dingtalkSideEffect=${out.dingtalkSideEffect}`);
1624
2106
  for (const check of out.checks) {
1625
- const mark = check.level === 'pass' ? 'PASS' : check.level === 'warn' ? 'WARN' : 'FAIL';
1626
- console.log(` ${mark} ${check.id}: ${check.summary}`);
2107
+ const word = check.level === 'pass' ? 'PASS' : check.level === 'warn' ? 'WARN' : 'FAIL';
2108
+ console.log(` ${mark(levelOf(check.level), word)} ${check.id}: ${check.summary}`);
1627
2109
  if (check.fix && check.level !== 'pass')
1628
- console.log(` fix: ${check.fix}`);
2110
+ console.log(` fix: ${noun(check.fix)}`);
1629
2111
  }
1630
2112
  }
1631
2113
  function printOpenCodeWorkspaceOperation(out) {
1632
2114
  const applied = out.$schema === 'dingtalk-agent/opencode-workspace-apply@1';
1633
- console.log(`${applied ? out.applied ? '✅' : '↩️' : 'DRY-RUN'} OpenCode Workspace ${out.workspace}`);
2115
+ const glyph = applied
2116
+ ? mark(out.applied ? 'pass' : 'muted', out.applied ? '✅' : '↩️')
2117
+ : mark('muted', 'DRY-RUN');
2118
+ console.log(`${glyph} OpenCode Workspace ${noun(out.workspace)}`);
1634
2119
  console.log(` action=${out.action} model=${out.model}`);
1635
2120
  console.log(` desired=${out.desiredHash} observed=${out.observedHash || '-'}`);
1636
2121
  console.log(` managed=${out.managedPath}`);
@@ -1640,7 +2125,7 @@ function printOpenCodeWorkspaceOperation(out) {
1640
2125
  function printMulticaWorkspaceOperation(out) {
1641
2126
  const passed = out.passed ?? out.readyForApply ?? out.blocking?.length === 0;
1642
2127
  const mode = out.remoteRead ? 'REMOTE-READ' : 'DRY-RUN';
1643
- console.log(`${passed ? '✅' : '⚠️'} ${mode} Multica Workspace ${out.workspace}`);
2128
+ console.log(`${mark(passed ? 'pass' : 'warn', passed ? '✅' : '⚠️')} ${mode} Multica Workspace ${noun(out.workspace)}`);
1644
2129
  console.log(` schema=${out.$schema} profile=${out.profile || '-'}`);
1645
2130
  if (out.planId)
1646
2131
  console.log(` plan=${out.planId}`);
@@ -1656,12 +2141,41 @@ function printMulticaWorkspaceOperation(out) {
1656
2141
  console.log(` blocking=${out.blocking.join(', ')}`);
1657
2142
  if (out.failures?.length)
1658
2143
  console.log(` failures=${out.failures.join(', ')}`);
2144
+ printFailureDetails(out.failureDetails, ' ');
2145
+ // 同名候选就在 JSON 里,人类输出却只给一个枚举串——把出路直接摆出来(B10/C3)。
2146
+ if (out.failures?.includes('agent.unbound-name-candidates') &&
2147
+ Array.isArray(out.resources?.agentCandidates) && out.resources.agentCandidates.length) {
2148
+ for (const candidate of out.resources.agentCandidates) {
2149
+ console.log(` candidate: ${candidate.id} ${candidate.name}${candidate.archived ? ' (archived)' : ''}`);
2150
+ }
2151
+ console.log(' fix: 核对后在 manifest 声明 provider.agentIdFrom 指向正确 ID' +
2152
+ '(或依赖首次 deploy 写入的 state.binding.agentId);不要按名字猜绑定');
2153
+ }
1659
2154
  if (out.evidencePath)
1660
2155
  console.log(` evidence=${out.evidencePath}`);
1661
2156
  console.log(` remoteWrite=${out.remoteWrite} dingtalkSideEffect=${out.dingtalkSideEffect}`);
1662
2157
  }
2158
+ /** 期望 vs 实际 + 下一步:把 fail-closed 从「正确但沉默」升级为「正确且能自救」。 */
2159
+ function printFailureDetails(details, indent) {
2160
+ if (!Array.isArray(details))
2161
+ return;
2162
+ for (const detail of details) {
2163
+ if (!detail || typeof detail !== 'object')
2164
+ continue;
2165
+ const item = detail;
2166
+ const format = (value) => Array.isArray(value) ? value.join(', ') : typeof value === 'string' ? value : '';
2167
+ const expected = format(item.expected);
2168
+ const actual = format(item.actual);
2169
+ const parts = [
2170
+ expected ? `expected=${expected}` : '', actual ? `actual=${actual}` : '',
2171
+ ].filter(Boolean).join(' ');
2172
+ console.log(`${indent}- ${item.code}${parts ? `: ${parts}` : ''}`);
2173
+ if (typeof item.hint === 'string' && item.hint)
2174
+ console.log(`${indent} → ${item.hint}`);
2175
+ }
2176
+ }
1663
2177
  function printMulticaDeployment(out) {
1664
- console.log(`Multica deploy: ${out.workspace || '<unknown>'}`);
2178
+ console.log(`${strong('Multica deploy')}: ${noun(out.workspace || '<unknown>')}`);
1665
2179
  if (out.planId)
1666
2180
  console.log(` plan: ${out.planId}`);
1667
2181
  if (out.operationId)
@@ -1674,17 +2188,42 @@ function printMulticaDeployment(out) {
1674
2188
  console.log(` status: ${out.status}`);
1675
2189
  if (typeof out.providerReady === 'boolean')
1676
2190
  console.log(` provider ready: ${out.providerReady}`);
2191
+ // 首次创建 Agent 后把「二次部署怎么绑定」讲在当下(B10):正常路径 ID 已存 state
2192
+ // 自动复用;换机器/state 丢失才需要显式声明。
2193
+ if (out.action === 'apply' && out.agent?.action === 'create' && out.agent?.id) {
2194
+ console.log(` agent created: ${out.agent.id}`);
2195
+ console.log(` ID 已写入 .dingtalk-agent/state/workspaces/${out.workspace}.json,二次部署与 schedule 自动复用`);
2196
+ console.log(' 换机器或 state 丢失时:在 manifest 声明 provider.agentIdFrom=env:<VAR> 并 export 该 ID');
2197
+ }
2198
+ if (out.rebind?.fromRuntimeId) {
2199
+ console.log(` rebind: ${out.rebind.fromRuntimeId} -> ${out.rebind.toRuntimeId}`);
2200
+ }
2201
+ const smoke = out.smoke || out.receipt?.smoke;
2202
+ if (smoke && smoke.status && smoke.status !== 'not-run') {
2203
+ const classified = smoke.classification ? ` (${smoke.classification})` : '';
2204
+ console.log(` smoke: ${smoke.status}${classified} task=${smoke.taskId || '-'}${smoke.taskStatus ? ` (${smoke.taskStatus})` : ''}`);
2205
+ console.log(` loaded: ${smoke.loadedSkills?.length ?? 0}/${smoke.requiredSkills?.length ?? 0} response: ${smoke.responsePassed === true}`);
2206
+ if (Array.isArray(smoke.unrecognizedTools) && smoke.unrecognizedTools.length) {
2207
+ console.log(` unrecognized tools: ${smoke.unrecognizedTools.join(', ')}`);
2208
+ }
2209
+ }
1677
2210
  if (Array.isArray(out.blocking) && out.blocking.length) {
1678
2211
  console.log(` blocking: ${out.blocking.join(', ')}`);
1679
2212
  }
1680
2213
  if (Array.isArray(out.failures) && out.failures.length) {
1681
2214
  console.log(` failures: ${out.failures.join(', ')}`);
1682
2215
  }
2216
+ printFailureDetails(out.failureDetails || out.receipt?.failureDetails, ' ');
2217
+ if (Array.isArray(smoke?.failureDetails))
2218
+ printFailureDetails(smoke.failureDetails, ' ');
2219
+ if (out.receiptFrozen && out.nextAction) {
2220
+ console.log(muted(` smoke 字段为 apply 时刻的冻结值;刷新: ${out.nextAction}`));
2221
+ }
1683
2222
  if (out.triggerWrite === false)
1684
2223
  console.log(' triggers: unchanged');
1685
2224
  }
1686
2225
  function printPromotion(out) {
1687
- console.log(`Promotion: ${out.promotionId || out.candidateId || out.candidate?.candidateId || '<none>'}`);
2226
+ console.log(`${strong('Promotion')}: ${noun(out.promotionId || out.candidateId || out.candidate?.candidateId || '<none>')}`);
1688
2227
  if (out.planId)
1689
2228
  console.log(` plan: ${out.planId}`);
1690
2229
  if (out.routeId)
@@ -1735,33 +2274,84 @@ function rejectWorkspaceOptions(values, allowed, command) {
1735
2274
  fail(`${command} 不接受参数: ${extras.join(', ')}`);
1736
2275
  }
1737
2276
  function printSetup(out) {
1738
- console.log(`${out.dryRun ? 'DRY-RUN' : '✅'} dingtalk-agent setup`);
1739
- console.log(` CLI: ${out.cli.executable}`);
1740
- console.log(` 短命令: ${out.cli.alias}`);
1741
- console.log(` Skill: ${out.skill.canonical.path}`);
2277
+ console.log(`${out.dryRun ? mark('muted', 'DRY-RUN') : mark('pass', '✅')} ${strong('dingtalk-agent setup')}`);
2278
+ console.log(` CLI: ${noun(out.cli.executable)}`);
2279
+ console.log(` 短命令: ${noun(out.cli.alias)}`);
2280
+ console.log(` Skill: ${noun(out.skill.canonical.path)}`);
1742
2281
  if (!out.shell.pathReady) {
1743
- console.log(` 当前 shell 尚未加载 PATH;本次终端执行: ${out.shell.activateCommand}`);
2282
+ console.log(` 当前 shell 尚未加载 PATH;本次终端执行: ${noun(out.shell.activateCommand)}`);
1744
2283
  if (out.shell.rcFile)
1745
- console.log(` 新终端会从 ${out.shell.rcFile} 自动加载`);
2284
+ console.log(muted(` 新终端会从 ${out.shell.rcFile} 自动加载`));
1746
2285
  }
1747
2286
  printDoctor(out.doctor);
1748
- console.log(`\n开始使用: ${out.nextCommand}`);
2287
+ console.log(`\n开始使用: ${noun(out.nextCommand)}`);
2288
+ }
2289
+ /**
2290
+ * 确认之前的抬头:告诉人「将要去哪」,好让 [y/N] 是个有信息的决定。
2291
+ *
2292
+ * 三态必须分开说。只判 outdated 会让 `--version <旧版> --force` 显示成
2293
+ * "已是最新版本",用户确认之后却真的降级——在唯一的不可逆动作前给了反的信息。
2294
+ */
2295
+ function printUpgradePreamble(checked, options) {
2296
+ const { theme } = stderrUi();
2297
+ const arrow = (from, to, paint) => `${theme.dim(from)} ${theme.bold('→')} ${paint(to)}`;
2298
+ if (checked.relation === 'same' && !options.force && !options.dryRun) {
2299
+ console.error(` ${theme.success('✔')} 已是最新版本 ${theme.bold(checked.currentVersion)}`);
2300
+ console.error(theme.dim(' 仍会重新校验环境;只想查版本可用 dta upgrade --check'));
2301
+ return;
2302
+ }
2303
+ const label = checked.relation === 'upgrade' ? '新版本可用'
2304
+ : checked.relation === 'downgrade' ? '降级' : '重装';
2305
+ const value = checked.relation === 'upgrade'
2306
+ ? arrow(checked.currentVersion, checked.targetVersion, theme.success)
2307
+ : checked.relation === 'downgrade'
2308
+ ? `${arrow(checked.currentVersion, checked.targetVersion, theme.warn)}${theme.warn('(降级)')}`
2309
+ : `${theme.bold(checked.targetVersion)}${theme.dim('(与当前版本相同,将强制重装)')}`;
2310
+ for (const line of renderPairs([
2311
+ [label, value],
2312
+ ['通道', `${checked.channel} ${theme.dim(checked.pinnedSource)}`],
2313
+ ]))
2314
+ console.error(line);
2315
+ console.error('');
2316
+ }
2317
+ function printUpgradeCheck(checked) {
2318
+ const { theme } = stdoutUi();
2319
+ const glyph = checked.relation === 'upgrade' ? theme.warn('⬆️')
2320
+ : checked.relation === 'downgrade' ? theme.warn('⬇️') : theme.success('✅');
2321
+ const summary = checked.relation === 'upgrade'
2322
+ ? `${theme.dim(checked.currentVersion)} → ${theme.success(checked.targetVersion)}`
2323
+ : checked.relation === 'downgrade'
2324
+ ? `${theme.dim(checked.currentVersion)} → ${theme.warn(checked.targetVersion)}(目标更旧)`
2325
+ : `${theme.bold(checked.currentVersion)}(已是最新)`;
2326
+ console.log(`${glyph} dta upgrade --check: ${summary}`);
2327
+ console.log(` target=${theme.noun(checked.pinnedSource)} channel=${checked.channel}`);
2328
+ if (checked.relation === 'upgrade')
2329
+ console.log(` ${theme.dim('运行 dta upgrade 执行升级')}`);
2330
+ if (checked.relation === 'downgrade') {
2331
+ console.log(` ${theme.dim(`降级需显式确认: dta upgrade --version ${checked.targetVersion} --force`)}`);
2332
+ }
1749
2333
  }
1750
2334
  function printUpgrade(out) {
1751
- const mark = out.dryRun ? 'DRY-RUN' : out.ready ? '✅' : '⚠️';
1752
- const relation = out.changed ? `${out.currentVersion} ${out.targetVersion}` : `${out.targetVersion}(已是当前版本)`;
2335
+ const { theme } = stdoutUi();
2336
+ const mark = out.dryRun ? theme.dim('DRY-RUN') : out.ready ? theme.success('✅') : theme.warn('⚠️');
2337
+ const relation = out.changed
2338
+ ? `${theme.dim(out.currentVersion)} → ${theme.success(out.targetVersion)}`
2339
+ : `${out.targetVersion}(已是当前版本)`;
1753
2340
  console.log(`${mark} dta upgrade: ${relation}`);
1754
- console.log(` target=${out.target}`);
1755
- console.log(` CLI=${out.executable}`);
2341
+ console.log(` target=${theme.noun(out.target)}`);
2342
+ console.log(` CLI=${theme.noun(out.executable)}`);
1756
2343
  if (out.dryRun) {
1757
- console.log(` 将执行: ${out.commands.install}`);
1758
- console.log(` 将复核: ${out.commands.verify}`);
2344
+ console.log(` 将执行: ${theme.noun(out.commands.install)}`);
2345
+ console.log(` 将复核: ${theme.noun(out.commands.verify)}`);
2346
+ console.log(` ${theme.dim('移除 --dry-run 以实际执行升级')}`);
1759
2347
  }
1760
2348
  else if (out.ready) {
1761
2349
  console.log(' setup / DWS / Skill / Claude Code / Codex / OpenCode 全部 ready');
2350
+ // 出口指引:验证命令 + 后悔药,都是可以直接复制的
2351
+ console.log(` ${theme.dim(`验证: dta --version${out.changed ? ` 回退: dta upgrade --version ${out.currentVersion} --force` : ''}`)}`);
1762
2352
  }
1763
2353
  else {
1764
- console.log(` CLI 已更新;环境复核未完全 ready,请运行: ${out.commands.verify}`);
2354
+ console.log(` CLI 已更新;环境复核未完全 ready,请运行: ${theme.noun(out.commands.verify)}`);
1765
2355
  }
1766
2356
  }
1767
2357
  //# sourceMappingURL=dingtalk-agent.js.map