@danceiny/gotry 0.0.1-rc.21 → 0.0.1-rc.24

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 (327) hide show
  1. package/README.md +102 -194
  2. package/README.zh-CN.md +115 -210
  3. package/bin/gotry-backend.js +53 -0
  4. package/bin/gotry-bootstrap.js +466 -35
  5. package/bin/gotry-inner.js +255 -36
  6. package/bin/gotry-process-liveness.d.ts +27 -0
  7. package/bin/gotry-process-liveness.js +97 -0
  8. package/bin/gotry-runtime-resolution.d.ts +1 -1
  9. package/bin/gotry-runtime-resolution.js +2 -2
  10. package/client/client.js +174 -0
  11. package/cordis.gotry-patch.yml +70 -93
  12. package/dist/capabilities/agent-reach-deep.js +45 -122
  13. package/dist/capabilities/agent-reach.js +63 -148
  14. package/dist/capabilities/anything.js +89 -75
  15. package/dist/capabilities/artifacts.js +123 -104
  16. package/dist/capabilities/channel-health.js +65 -45
  17. package/dist/capabilities/channel-registry.js +108 -87
  18. package/dist/capabilities/doctor.js +357 -235
  19. package/dist/capabilities/effect.js +223 -309
  20. package/dist/capabilities/fact-log.js +28 -14
  21. package/dist/capabilities/flyai.js +245 -156
  22. package/dist/capabilities/ground-transfer.js +509 -0
  23. package/dist/capabilities/hbcli.js +144 -148
  24. package/dist/capabilities/hotelbyte-transaction.js +194 -0
  25. package/dist/capabilities/incident-log.js +131 -61
  26. package/dist/capabilities/model-override.js +30 -10
  27. package/dist/capabilities/opensky.js +54 -43
  28. package/dist/capabilities/resilience.js +50 -32
  29. package/dist/capabilities/session/action-cache.js +50 -44
  30. package/dist/capabilities/session/adapters/ctrip-flight.js +119 -62
  31. package/dist/capabilities/session/adapters/ctrip-hotel.js +125 -110
  32. package/dist/capabilities/session/adapters/dida-portal.js +206 -0
  33. package/dist/capabilities/session/adapters/meituan-local.js +35 -54
  34. package/dist/capabilities/session/adapters/rail-12306.js +169 -112
  35. package/dist/capabilities/session/benchmark.js +85 -149
  36. package/dist/capabilities/session/extension-bridge.js +320 -259
  37. package/dist/capabilities/session/extension-channel.js +57 -76
  38. package/dist/capabilities/session/extension-distribution.js +115 -119
  39. package/dist/capabilities/session/extract.js +31 -21
  40. package/dist/capabilities/session/golden-score.js +55 -33
  41. package/dist/capabilities/session/health-watch.js +146 -154
  42. package/dist/capabilities/session/read-guard.js +55 -47
  43. package/dist/capabilities/session/static-flight-golden.js +49 -36
  44. package/dist/capabilities/session/transport.js +54 -61
  45. package/dist/capabilities/session/wizard.js +63 -93
  46. package/dist/capabilities/session-consent.js +143 -47
  47. package/dist/capabilities/session-login.js +95 -126
  48. package/dist/capabilities/session-search.js +391 -253
  49. package/dist/capabilities/sponsor-plugin.js +341 -0
  50. package/dist/capabilities/visa-policy.js +87 -73
  51. package/dist/capabilities/weather.js +163 -235
  52. package/dist/scripts/action-cache-tests.js +24 -59
  53. package/dist/scripts/agent-planning-turn-deadline-e2e.js +91 -142
  54. package/dist/scripts/agent-planning-turn-deadline-tests.js +80 -176
  55. package/dist/scripts/agent-reach-deep-tests.js +30 -40
  56. package/dist/scripts/agent-reach-tests.js +26 -18
  57. package/dist/scripts/agent-reach-wrapper-tests.js +46 -50
  58. package/dist/scripts/anything-tests.js +58 -62
  59. package/dist/scripts/artifact-client-contract-tests.js +69 -0
  60. package/dist/scripts/artifacts-capability-tests.js +235 -0
  61. package/dist/scripts/async-collect.js +26 -15
  62. package/dist/scripts/benchmark-environment-bridge-e2e.js +287 -684
  63. package/dist/scripts/benchmark-environment-bridge-tests.js +818 -3123
  64. package/dist/scripts/booking-copilot-availability-ledger-binding-tests.js +66 -279
  65. package/dist/scripts/booking-copilot-availability-policy-tests.js +153 -1257
  66. package/dist/scripts/booking-copilot-bin-proof-tests.js +22 -45
  67. package/dist/scripts/booking-copilot-crossrepo-fixture-server.js +64 -76
  68. package/dist/scripts/booking-copilot-dsh-core-proof-tests.js +69 -199
  69. package/dist/scripts/booking-copilot-dsh-planner-proof-tests.js +393 -759
  70. package/dist/scripts/booking-copilot-dsh-plugin-proof-tests.js +33 -75
  71. package/dist/scripts/booking-copilot-event-sequence-concurrency-proof-tests.js +60 -110
  72. package/dist/scripts/booking-copilot-gap-code-contract-proof-tests.js +28 -66
  73. package/dist/scripts/booking-copilot-operation-ledger-concurrency-proof-tests.js +72 -271
  74. package/dist/scripts/booking-copilot-receipt-ledger-concurrency-proof-tests.js +73 -275
  75. package/dist/scripts/booking-copilot-runtime-proof-tests.js +784 -3398
  76. package/dist/scripts/booking-copilot-server-proof-tests.js +63 -168
  77. package/dist/scripts/booking-copilot-startup-proof-tests.js +38 -124
  78. package/dist/scripts/booking-executor-tests.js +80 -0
  79. package/dist/scripts/booking-recovery-chain-tests.js +182 -0
  80. package/dist/scripts/booking-saga-tests.js +61 -123
  81. package/dist/scripts/booking-surface-contract-proof-tests.js +152 -1167
  82. package/dist/scripts/bootstrap-tests.js +1383 -128
  83. package/dist/scripts/build-changelog.js +89 -130
  84. package/dist/scripts/build-metrics-report.js +135 -143
  85. package/dist/scripts/changelog-tests.js +35 -82
  86. package/dist/scripts/channel-probe-tests.js +44 -116
  87. package/dist/scripts/channel-probe.js +84 -167
  88. package/dist/scripts/channel-registry-tests.js +97 -185
  89. package/dist/scripts/companion-tests.js +25 -70
  90. package/dist/scripts/dida-cookie-probe.js +87 -0
  91. package/dist/scripts/dida-sw-probe.js +114 -0
  92. package/dist/scripts/diff-test.js +17 -12
  93. package/dist/scripts/doctor-tests.js +428 -120
  94. package/dist/scripts/dsh-artifact-e2e.js +150 -0
  95. package/dist/scripts/dsh-artifact-web-e2e.js +443 -0
  96. package/dist/scripts/dsh-http-proxy-sse-proof.js +90 -0
  97. package/dist/scripts/dsh-runtime-closure-tests.js +110 -153
  98. package/dist/scripts/dsh-runtime-closure.js +75 -63
  99. package/dist/scripts/dsh-session-v3-migration-proof.js +106 -0
  100. package/dist/scripts/dsh-subprocess-local-proof.js +149 -0
  101. package/dist/scripts/dsh-target-closure-proof.js +178 -0
  102. package/dist/scripts/effect-tests.js +165 -552
  103. package/dist/scripts/engine-run.js +1 -3
  104. package/dist/scripts/engine-tests.js +16 -10
  105. package/dist/scripts/evaluation-cadence-tests.js +69 -242
  106. package/dist/scripts/evaluation-contract-tests.js +193 -499
  107. package/dist/scripts/extension-distribution-cli.js +19 -15
  108. package/dist/scripts/extension-distribution-tests.js +98 -211
  109. package/dist/scripts/extension-tests.js +366 -389
  110. package/dist/scripts/fact-gate-tests.js +725 -263
  111. package/dist/scripts/fixtures/extension-bridge-unref-child.mjs +21 -0
  112. package/dist/scripts/flight-hotel-anchor-363-registered-e2e.js +169 -0
  113. package/dist/scripts/flight-malformed-tests.js +159 -0
  114. package/dist/scripts/flyai-tests.js +325 -68
  115. package/dist/scripts/g5-guard-tests.js +131 -0
  116. package/dist/scripts/g5-guard.js +189 -0
  117. package/dist/scripts/gen-lunar-anchors.js +106 -0
  118. package/dist/scripts/gotry-backend-kernel-tests.js +79 -0
  119. package/dist/scripts/ground-transfer-tests.js +956 -0
  120. package/dist/scripts/hbcli-e2e-tests.js +71 -90
  121. package/dist/scripts/hbcli-live-optin-tests.js +96 -0
  122. package/dist/scripts/hbcli-release-contract-tests.js +122 -0
  123. package/dist/scripts/hbcli-tests.js +41 -115
  124. package/dist/scripts/health-watch-cli.js +42 -28
  125. package/dist/scripts/hotel-date-gate-tests.js +496 -0
  126. package/dist/scripts/hotelbyte-reconcile-tests.js +141 -0
  127. package/dist/scripts/hotelbyte-spawn-e2e-tests.js +373 -0
  128. package/dist/scripts/i18n-tests.js +16 -9
  129. package/dist/scripts/incident-tests.js +281 -87
  130. package/dist/scripts/issue-194-job-id-guard-tests.js +106 -0
  131. package/dist/scripts/issue-233-cancel-refund-commission-tests.js +392 -0
  132. package/dist/scripts/issue-271-liveness-tests.js +547 -0
  133. package/dist/scripts/issue-289-model-retry-real-tests.js +296 -0
  134. package/dist/scripts/issue-289-model-retry-tests.js +441 -0
  135. package/dist/scripts/issue-289-web-retry-e2e.js +630 -0
  136. package/dist/scripts/issue-338-home-city-e2e.js +577 -0
  137. package/dist/scripts/issue343-real-entry-e2e.js +160 -0
  138. package/dist/scripts/journey-tests.js +22 -27
  139. package/dist/scripts/kernel-manifest-evidence.js +109 -0
  140. package/dist/scripts/kernel-manifest-gate.js +126 -0
  141. package/dist/scripts/kernel-manifest-tests.js +212 -0
  142. package/dist/scripts/kernel-manifest-trace-entry.js +7 -0
  143. package/dist/scripts/kernel-manifest-trace-hook.mjs +28 -0
  144. package/dist/scripts/kernel-manifest.js +369 -0
  145. package/dist/scripts/ledger-repair-apply-tests.js +316 -0
  146. package/dist/scripts/ledger-repair-plan-tests.js +375 -0
  147. package/dist/scripts/ledger-tests.js +325 -340
  148. package/dist/scripts/ledger-workflow-crash.js +10 -20
  149. package/dist/scripts/map-tools-vendor-package-proof.js +206 -134
  150. package/dist/scripts/memory-capture-tests.js +242 -61
  151. package/dist/scripts/memory-decay-tests.js +18 -52
  152. package/dist/scripts/memory-lifecycle-tests.js +657 -0
  153. package/dist/scripts/memory-lifecycle.js +196 -0
  154. package/dist/scripts/memory-metrics.js +24 -12
  155. package/dist/scripts/memory-value-report.js +474 -222
  156. package/dist/scripts/memory-value-tests.js +328 -0
  157. package/dist/scripts/metrics-report-tests.js +62 -199
  158. package/dist/scripts/model-override-e2e.js +66 -105
  159. package/dist/scripts/nightly-evidence-tests.js +64 -89
  160. package/dist/scripts/nightly-evidence.js +108 -111
  161. package/dist/scripts/nudge-digest.js +39 -28
  162. package/dist/scripts/onboarding-tests.js +63 -88
  163. package/dist/scripts/opensky-check.js +18 -13
  164. package/dist/scripts/opensky-tests.js +28 -28
  165. package/dist/scripts/persona-surface-guard-tests.js +37 -12
  166. package/dist/scripts/pnpm-dsh-closure-proof.js +2 -5
  167. package/dist/scripts/policy-anchor-359-registered-e2e.js +214 -0
  168. package/dist/scripts/price-drift-tests.js +126 -227
  169. package/dist/scripts/price-drift-watch.js +209 -253
  170. package/dist/scripts/probe-poi-tests.js +52 -45
  171. package/dist/scripts/product-metrics.js +187 -273
  172. package/dist/scripts/publish-preverify.js +47 -41
  173. package/dist/scripts/realtime-pricing-tests.js +37 -76
  174. package/dist/scripts/replay-async.js +20 -18
  175. package/dist/scripts/replay-real.js +16 -18
  176. package/dist/scripts/replay.js +52 -82
  177. package/dist/scripts/session-attach-diagnose.js +16 -12
  178. package/dist/scripts/session-attach-poc.js +34 -37
  179. package/dist/scripts/session-benchmark.js +66 -147
  180. package/dist/scripts/session-dida-live-e2e.js +151 -0
  181. package/dist/scripts/session-extract-tests.js +14 -35
  182. package/dist/scripts/session-login.js +14 -13
  183. package/dist/scripts/session-tests.js +545 -583
  184. package/dist/scripts/sf-live-benchmark.js +92 -110
  185. package/dist/scripts/sf-live-cli-tests.js +2 -9
  186. package/dist/scripts/sf-soft-score-tests.js +19 -60
  187. package/dist/scripts/sf-summary-tests.js +149 -0
  188. package/dist/scripts/sf-summary.js +466 -84
  189. package/dist/scripts/skeleton-check.js +16 -14
  190. package/dist/scripts/skeleton-integration-test.js +11 -10
  191. package/dist/scripts/skills-contract-tests.js +51 -46
  192. package/dist/scripts/smoke-session-gate-tests.js +12 -20
  193. package/dist/scripts/smoke.js +541 -422
  194. package/dist/scripts/sponsor-reuse-tests.js +262 -0
  195. package/dist/scripts/state-cli-tests.js +226 -101
  196. package/dist/scripts/state-cli.js +549 -212
  197. package/dist/scripts/static-golden-tests.js +46 -180
  198. package/dist/scripts/time-eval-tests.js +210 -192
  199. package/dist/scripts/travel-timeline-tests.js +29 -93
  200. package/dist/scripts/turn-handoff-collect-tests.js +46 -93
  201. package/dist/scripts/turn-handoff-collect.js +71 -99
  202. package/dist/scripts/turn-policy-tests.js +28 -29
  203. package/dist/scripts/typed-contract-canary.js +77 -162
  204. package/dist/scripts/tz-resolver-sanity.js +50 -0
  205. package/dist/scripts/tz-resolver-tests.js +584 -0
  206. package/dist/scripts/unified-tests.js +23 -19
  207. package/dist/scripts/visa-policy-tests.js +31 -44
  208. package/dist/scripts/weather-tests.js +121 -543
  209. package/dist/scripts/wish-channel-gate-tests.js +29 -86
  210. package/dist/scripts/write-gate-tests.js +522 -0
  211. package/dist/scripts/z3-lifecycle-fault-tests.js +188 -0
  212. package/dist/scripts/z3-lifecycle-tests.js +170 -0
  213. package/dist/scripts/z3-race-repeat-tests.js +39 -0
  214. package/dist/scripts/z3-race-tests.js +18 -12
  215. package/dist/src/artifact-gate.js +652 -221
  216. package/dist/src/backend/kernel.js +91 -0
  217. package/dist/src/backend/modules/booking-copilot.js +55 -0
  218. package/dist/src/backend/modules/booking-executor.js +124 -0
  219. package/dist/src/backend/modules/session-search.js +194 -0
  220. package/dist/src/benchmark-agent-conformance.js +296 -262
  221. package/dist/src/benchmark-environment-bridge.js +298 -413
  222. package/dist/src/benchmark-headless-child-diagnostics.js +93 -100
  223. package/dist/src/benchmark-tool-isolation.js +94 -67
  224. package/dist/src/bookable-facts.js +299 -174
  225. package/dist/src/booking-saga.js +64 -120
  226. package/dist/src/booking-surface/availability-policy.js +443 -576
  227. package/dist/src/booking-surface/cancel-refund-commission.js +379 -0
  228. package/dist/src/booking-surface/contracts.js +24 -80
  229. package/dist/src/booking-surface/dsh-planner.js +510 -281
  230. package/dist/src/booking-surface/dsh-plugin.js +17 -1
  231. package/dist/src/booking-surface/error-codes.js +82 -10
  232. package/dist/src/booking-surface/index.js +0 -3
  233. package/dist/src/booking-surface/profile.js +10 -54
  234. package/dist/src/booking-surface/recovery-chain.js +367 -0
  235. package/dist/src/booking-surface/runtime.js +765 -891
  236. package/dist/src/booking-surface/server.js +200 -251
  237. package/dist/src/booking-surface/startup.js +58 -54
  238. package/dist/src/booking-surface/validation.js +150 -218
  239. package/dist/src/bridge.js +20 -13
  240. package/dist/src/companions.js +48 -74
  241. package/dist/src/contracts.js +21 -43
  242. package/dist/src/dsh-llm.js +82 -120
  243. package/dist/src/engine.js +253 -239
  244. package/dist/src/evaluation-cadence.js +72 -209
  245. package/dist/src/evaluation-contracts.js +220 -843
  246. package/dist/src/flight-pack-adapter.js +54 -0
  247. package/dist/src/flight-pack-contract.js +2 -0
  248. package/dist/src/gotry-backend.js +70 -0
  249. package/dist/src/hotel-date-gate.js +118 -0
  250. package/dist/src/i18n.js +18 -7
  251. package/dist/src/index.js +1425 -1629
  252. package/dist/src/journey.js +132 -81
  253. package/dist/src/ledger-repair-apply.js +538 -0
  254. package/dist/src/ledger-repair-plan.js +463 -0
  255. package/dist/src/loop.js +257 -166
  256. package/dist/src/memory-capture.js +180 -32
  257. package/dist/src/memory-decay.js +30 -14
  258. package/dist/src/memory-lifecycle.js +1310 -0
  259. package/dist/src/memory-utility.js +27 -40
  260. package/dist/src/mock-llm.js +36 -61
  261. package/dist/src/model.js +81 -36
  262. package/dist/src/realtime-pricing.js +63 -72
  263. package/dist/src/slot-spec.js +84 -106
  264. package/dist/src/state-ledger.js +310 -471
  265. package/dist/src/subagent-job-id-guard.js +58 -0
  266. package/dist/src/time-anchor.js +231 -33
  267. package/dist/src/tool-packet.js +20 -8
  268. package/dist/src/travel-slots.js +58 -35
  269. package/dist/src/travel-timeline.js +44 -63
  270. package/dist/src/turn-deadline.js +121 -127
  271. package/dist/src/turn-policy.js +61 -89
  272. package/dist/src/tz-resolver.js +143 -0
  273. package/dist/src/unified.js +504 -381
  274. package/dist/src/wish-pool.js +39 -14
  275. package/dist/src/write-gate.js +495 -0
  276. package/dist/src/z3-shared.js +273 -7
  277. package/extension/README.md +4 -3
  278. package/extension/background.js +125 -14
  279. package/extension/content-bridge.js +23 -1
  280. package/extension/content-main.js +15 -4
  281. package/extension/manifest.json +16 -8
  282. package/package.json +251 -220
  283. package/ts/capabilities/agent-reach.ts +4 -4
  284. package/ts/capabilities/anything.ts +1 -1
  285. package/ts/capabilities/artifacts.ts +46 -9
  286. package/ts/capabilities/flyai.ts +156 -37
  287. package/ts/capabilities/hbcli.ts +1 -1
  288. package/ts/capabilities/incident-log.ts +88 -59
  289. package/ts/capabilities/session/adapters/ctrip-flight.ts +99 -36
  290. package/ts/capabilities/session/adapters/dida-portal.ts +250 -0
  291. package/ts/capabilities/session/adapters/rail-12306.ts +123 -24
  292. package/ts/capabilities/session/extension-bridge.ts +226 -101
  293. package/ts/capabilities/session/extension-channel.ts +22 -9
  294. package/ts/capabilities/session/wizard.ts +2 -2
  295. package/ts/capabilities/session-consent.ts +98 -14
  296. package/ts/capabilities/session-login.ts +1 -0
  297. package/ts/capabilities/session-search.ts +315 -31
  298. package/ts/dsh-runtime/vendor/README.md +8 -4
  299. package/ts/dsh-runtime/vendor/dsh-map-tools/README.en.md +7 -1
  300. package/ts/dsh-runtime/vendor/dsh-map-tools/README.md +5 -3
  301. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/settings-ns.js +1 -1
  302. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/settings-ns.d.ts +1 -1
  303. package/ts/dsh-runtime/vendor/dsh-map-tools/package.json +6 -6
  304. package/ts/package.json +236 -16
  305. package/ts/scripts/map-tools-vendor-package-proof.ts +152 -31
  306. package/ts/scripts/state-cli.ts +418 -51
  307. package/ts/src/artifact-gate.ts +561 -15
  308. package/ts/src/benchmark-environment-bridge.ts +37 -20
  309. package/ts/src/bookable-facts.ts +183 -7
  310. package/ts/src/booking-surface/cancel-refund-commission.ts +488 -0
  311. package/ts/src/booking-surface/dsh-planner.ts +240 -32
  312. package/ts/src/booking-surface/dsh-plugin.js +17 -1
  313. package/ts/src/booking-surface/error-codes.ts +75 -0
  314. package/ts/src/booking-surface/recovery-chain.ts +409 -0
  315. package/ts/src/booking-surface/server.ts +84 -51
  316. package/ts/src/booking-surface/validation.ts +2 -0
  317. package/ts/src/contracts.ts +12 -2
  318. package/ts/src/dsh-llm.ts +4 -8
  319. package/ts/src/index.ts +609 -90
  320. package/ts/src/loop.ts +116 -7
  321. package/ts/src/memory-capture.ts +227 -3
  322. package/ts/src/mock-llm.ts +4 -8
  323. package/ts/src/model.ts +88 -2
  324. package/ts/src/state-ledger.ts +47 -29
  325. package/ts/src/subagent-job-id-guard.ts +72 -0
  326. package/ts/src/time-anchor.ts +217 -13
  327. package/ts/src/unified.ts +316 -54
@@ -1,379 +1,501 @@
1
+ /**
2
+ * 可选依赖体检(doctor)——工具层与 CLI(npx @danceiny/gotry doctor)共用的状态面。
3
+ *
4
+ * 背景(2026-09-02 迪拜 session 轨迹复盘):8953be5 把 hbcli/agent-reach 装配从
5
+ * setup 挪出后,工具层仍按「包内 .venv 已装配」运行 → gotry_agent_reach /
6
+ * gotry_web_search(web.read)全量 not-installed,且报错指引指向已失效的文档。
7
+ * founder 拍板:可选依赖不撒手——doctor 统一显示状态、给精确补装指引。
8
+ *
9
+ * 契约(与 capabilities/* 同构):
10
+ * - `runDoctorChecks` 永远只读;显式 repair 另经会话 scope 审批后复用 bootstrap 幂等安装器;
11
+ * - 永不抛错:单项检查失败降级为 status='degraded',不拖垮整体报告;
12
+ * - 可注入:repoRoot/homeDir/env 可替换(CI/离线确定性测试);
13
+ * - LLM key 永不体检——那是 dsh 宿主的管辖面(founder 2026-09-02 明确)。
14
+ *
15
+ * @module capabilities/doctor
16
+ */
1
17
  import { spawn } from 'node:child_process';
2
18
  import { existsSync, readFileSync } from 'node:fs';
3
19
  import { createRequire } from 'node:module';
4
20
  import { homedir } from 'node:os';
5
21
  import { join, resolve } from 'node:path';
6
22
  import { readLatestChannelEvents } from './channel-health.js';
23
+ /** hbcli 已知安装位(与 capabilities/hbcli.ts hbcliBinCandidates 同清单) */
7
24
  function hbcliCandidates(homeDir) {
8
- return [
9
- 'hbcli',
10
- join(homeDir, '.local/bin/hbcli'),
11
- join(homeDir, '.staicli/current/hbcli')
12
- ];
25
+ return ['hbcli', join(homeDir, '.local/bin/hbcli'), join(homeDir, '.staicli/current/hbcli')];
13
26
  }
27
+ /** 静默探测命令可执行(带超时;探测失败不抛错) */
14
28
  function probe(bin, args, timeoutMs = 8_000) {
15
- return new Promise((resolveProbe)=>{
16
- const child = spawn(bin, args, {
17
- stdio: 'ignore',
18
- env: process.env
19
- });
29
+ return new Promise((resolveProbe) => {
30
+ const child = spawn(bin, args, { stdio: 'ignore', env: process.env });
20
31
  let done = false;
21
- const timer = setTimeout(()=>{
32
+ const timer = setTimeout(() => {
22
33
  if (!done) {
23
34
  done = true;
24
35
  try {
25
36
  child.kill('SIGKILL');
26
- } catch {}
37
+ }
38
+ catch { /* ignore */ }
27
39
  resolveProbe(false);
28
40
  }
29
41
  }, timeoutMs);
30
- child.on('error', ()=>{
31
- if (!done) {
32
- done = true;
33
- clearTimeout(timer);
34
- resolveProbe(false);
35
- }
36
- });
37
- child.on('exit', (code)=>{
38
- if (!done) {
39
- done = true;
40
- clearTimeout(timer);
41
- resolveProbe(code === 0);
42
- }
43
- });
42
+ child.on('error', () => { if (!done) {
43
+ done = true;
44
+ clearTimeout(timer);
45
+ resolveProbe(false);
46
+ } });
47
+ child.on('exit', (code) => { if (!done) {
48
+ done = true;
49
+ clearTimeout(timer);
50
+ resolveProbe(code === 0);
51
+ } });
44
52
  });
45
53
  }
54
+ /** 探测命令 stdout(--version),失败返回 null */
46
55
  function probeStdout(bin, args, timeoutMs = 8_000) {
47
- return new Promise((resolveProbe)=>{
48
- const child = spawn(bin, args, {
49
- stdio: [
50
- 'ignore',
51
- 'pipe',
52
- 'ignore'
53
- ],
54
- env: process.env
55
- });
56
+ return new Promise((resolveProbe) => {
57
+ const child = spawn(bin, args, { stdio: ['ignore', 'pipe', 'ignore'], env: process.env });
56
58
  let out = '';
57
59
  let done = false;
58
- const timer = setTimeout(()=>{
60
+ const timer = setTimeout(() => {
59
61
  if (!done) {
60
62
  done = true;
61
63
  try {
62
64
  child.kill('SIGKILL');
63
- } catch {}
65
+ }
66
+ catch { /* ignore */ }
64
67
  resolveProbe(null);
65
68
  }
66
69
  }, timeoutMs);
67
- child.on('error', ()=>{
68
- if (!done) {
69
- done = true;
70
- clearTimeout(timer);
71
- resolveProbe(null);
72
- }
73
- });
74
- child.stdout?.on('data', (d)=>{
75
- out += d.toString();
76
- });
77
- child.on('exit', (code)=>{
78
- if (!done) {
79
- done = true;
80
- clearTimeout(timer);
81
- resolveProbe(code === 0 ? out.trim() : null);
82
- }
83
- });
70
+ child.on('error', () => { if (!done) {
71
+ done = true;
72
+ clearTimeout(timer);
73
+ resolveProbe(null);
74
+ } });
75
+ child.stdout?.on('data', (d) => { out += d.toString(); });
76
+ child.on('exit', (code) => { if (!done) {
77
+ done = true;
78
+ clearTimeout(timer);
79
+ resolveProbe(code === 0 ? out.trim() : null);
80
+ } });
84
81
  });
85
82
  }
83
+ /** 解析语义化版本号 "1.2.3" → [1,2,3];失败 null */
86
84
  function parseVersion(v) {
87
85
  const m = String(v || '').match(/(\d+)\.(\d+)\.(\d+)/);
88
- return m ? [
89
- Number(m[1]),
90
- Number(m[2]),
91
- Number(m[3])
92
- ] : null;
86
+ return m ? [Number(m[1]), Number(m[2]), Number(m[3])] : null;
93
87
  }
94
88
  function versionAtLeast(v, min) {
95
89
  const a = v;
96
90
  const b = parseVersion(min);
97
- if (!a || !b) return false;
98
- for(let i = 0; i < 3; i++){
99
- if (a[i] !== b[i]) return a[i] > b[i];
91
+ if (!a || !b)
92
+ return false;
93
+ for (let i = 0; i < 3; i++) {
94
+ if (a[i] !== b[i])
95
+ return a[i] > b[i];
100
96
  }
101
97
  return true;
102
98
  }
99
+ /** Node ≥ 22.15(与 bin/gotry-inner.js supportsNodeVersion 同口径) */
103
100
  export function nodeOk(version) {
104
101
  const [maj = '0', min = '0'] = version.split('.');
105
- return Number(maj) > 22 || Number(maj) === 22 && Number(min) >= 15;
102
+ return Number(maj) > 22 || (Number(maj) === 22 && Number(min) >= 15);
106
103
  }
104
+ /** hbcli 最低版本(可用 GOTRY_MIN_HBCLI_VERSION 覆盖)。0.0.3 修了 portal-ticket
105
+ * fallback bug(issue #142)——低于该版本的 hbcli 会把 literal "stored-ticket"
106
+ * 当 bearer 发到 hotel-be,让 trade.* / search/checkAvail 全部 401。 */
107
107
  const MIN_HBCLI_VERSION = process.env.GOTRY_MIN_HBCLI_VERSION ?? '0.0.3';
108
+ /** 单项检查:全部只读、永不抛错;env/homeDir/repoRoot 可注入(测试确定性) */
108
109
  export async function runDoctorChecks(opts = {}) {
109
110
  const repoRoot = opts.repoRoot ? resolve(opts.repoRoot) : resolve(import.meta.dirname, '..', '..');
110
111
  const home = opts.homeDir ?? homedir();
111
112
  const env = opts.env ?? process.env;
112
113
  const items = [];
114
+ // 0. Node 运行时
113
115
  const nodeVersion = process.versions.node;
114
- items.push(nodeOk(nodeVersion) ? {
115
- id: 'node',
116
- label: 'Node 运行时',
117
- status: 'ok',
118
- detail: `Node ${nodeVersion}(≥22.15)`
119
- } : {
120
- id: 'node',
121
- label: 'Node 运行时',
122
- status: 'missing',
123
- detail: `Node ${nodeVersion} 过旧(gotry 需 ≥22.15)`,
124
- fix: '升级 Node.js 至 22.15+(https://nodejs.org)'
125
- });
116
+ items.push(nodeOk(nodeVersion)
117
+ ? { id: 'node', label: 'Node 运行时', status: 'ok', detail: `Node ${nodeVersion}(≥22.15)` }
118
+ : { id: 'node', label: 'Node 运行时', status: 'missing', detail: `Node ${nodeVersion} 过旧(gotry 需 ≥22.15)`, fix: '升级 Node.js 至 22.15+(https://nodejs.org)' });
119
+ // 1. GoTry Session Bridge 扩展(账号会话通道的传输层;装在哪由 bootstrap setup 管理)
120
+ // D-24 自适应(#117):离线只能探测本地 unpacked 通道;商店版无本地文件面——
121
+ // missing detail 明示「商店版用户可忽略本项」,不再把商店版用户误导成本地通道缺失
126
122
  const extManifest = join(home, '.gotry', 'extension', 'manifest.json');
127
- items.push(existsSync(extManifest) ? {
128
- id: 'extension',
129
- label: 'GoTry Session Bridge 扩展',
130
- status: 'ok',
131
- detail: `已就位(本地通道:${extManifest})`
132
- } : {
133
- id: 'extension',
134
- label: 'GoTry Session Bridge 扩展',
135
- status: 'missing',
136
- detail: '本地通道未落位(~/.gotry/extension/manifest.json 不存在)。若你已从 Chrome 商店安装(自动更新,商店版 ID oeajpicc…),本项可忽略——会话检索可用性以运行时为准(gotry_session_search 的 verdict)。若未安装:应用商店一键装即可',
137
- fix: '在 Chrome 应用商店一键安装(自动更新): https://chromewebstore.google.com/detail/gotry-session-bridge/oeajpiccmonococjcegddlooeeohlbgd'
138
- });
123
+ items.push(existsSync(extManifest)
124
+ ? { id: 'extension', label: 'GoTry Session Bridge 扩展', status: 'ok', detail: `已就位(本地通道:${extManifest})` }
125
+ : {
126
+ id: 'extension', label: 'GoTry Session Bridge 扩展', status: 'missing',
127
+ detail: '本地通道未落位(~/.gotry/extension/manifest.json 不存在)。若你已从 Chrome 商店安装(自动更新,商店版 ID oeajpicc…),本项可忽略——会话检索可用性以运行时为准(gotry_session_search 的 verdict)。若未安装:应用商店一键装即可',
128
+ fix: '在 Chrome 应用商店一键安装(自动更新): https://chromewebstore.google.com/detail/gotry-session-bridge/oeajpiccmonococjcegddlooeeohlbgd',
129
+ });
130
+ // 2. agent-reach(网页/社媒读取;.venv 装在包内,与 agent-reach.ts venvPython 同位)
139
131
  const venvPython = join(repoRoot, '.venv/bin/python');
140
132
  const reachBin = join(repoRoot, '.venv/bin/agent-reach');
141
133
  if (existsSync(reachBin)) {
134
+ items.push({ id: 'agent-reach', label: 'Agent Reach(网页/社媒读取)', status: 'ok', detail: `已安装(${reachBin});渠道凭证选配体检:在对话里调 gotry_agent_reach {action:"status"}` });
135
+ }
136
+ else if (existsSync(venvPython)) {
142
137
  items.push({
143
- id: 'agent-reach',
144
- label: 'Agent Reach(网页/社媒读取)',
145
- status: 'ok',
146
- detail: `已安装(${reachBin});渠道凭证选配体检:在对话里调 gotry_agent_reach {action:"status"}`
147
- });
148
- } else if (existsSync(venvPython)) {
149
- items.push({
150
- id: 'agent-reach',
151
- label: 'Agent Reach(网页/社媒读取)',
152
- status: 'degraded',
138
+ id: 'agent-reach', label: 'Agent Reach(网页/社媒读取)', status: 'degraded',
153
139
  detail: '.venv 存在但缺 agent-reach 包——gotry_agent_reach / gotry_web_search 读页会失败',
154
- fix: `npx gotry doctor --fix(或在包根执行: ${venvPython} -m pip install git+https://github.com/Panniantong/Agent-Reach.git)`
140
+ fix: `npx @danceiny/gotry doctor --fix(或在包根执行: ${venvPython} -m pip install git+https://github.com/Panniantong/Agent-Reach.git)`,
155
141
  });
156
- } else {
142
+ }
143
+ else {
157
144
  items.push({
158
- id: 'agent-reach',
159
- label: 'Agent Reach(网页/社媒读取)',
160
- status: 'missing',
145
+ id: 'agent-reach', label: 'Agent Reach(网页/社媒读取)', status: 'missing',
161
146
  detail: '未安装——gotry_agent_reach / gotry_web_search(读网页)/ gotry_video_subtitle / gotry_github_search 全部不可用',
162
- fix: 'npx gotry doctor --fix'
147
+ fix: 'npx @danceiny/gotry doctor --fix',
163
148
  });
164
149
  }
150
+ // 3. hbcli(酒店实时源;静态包自动降级,缺了不致命)
151
+ // 裸名 'hbcli' 靠 PATH 解析(existsSync 对裸名是 cwd 相对,无意义)——先探测再落位
152
+ // 版本 < MIN_HBCLI_VERSION 视为 missing(issue #142:portal-ticket fallback 401)
165
153
  let hbPresent = '';
166
- for (const p of hbcliCandidates(home)){
154
+ for (const p of hbcliCandidates(home)) {
167
155
  if (p !== 'hbcli' && existsSync(p)) {
168
156
  hbPresent = p;
169
157
  break;
170
158
  }
171
- if (p === 'hbcli' && await probe(p, [
172
- 'version'
173
- ])) {
159
+ if (p === 'hbcli' && await probe(p, ['version'])) {
174
160
  hbPresent = 'hbcli(PATH)';
175
161
  break;
176
162
  }
177
163
  }
178
164
  if (hbPresent) {
179
165
  const hbCmd = hbPresent === 'hbcli(PATH)' ? 'hbcli' : hbPresent;
180
- const v = parseVersion(await probeStdout(hbCmd, [
181
- '--version'
182
- ]));
166
+ const v = parseVersion(await probeStdout(hbCmd, ['--version']));
183
167
  if (v && !versionAtLeast(v, MIN_HBCLI_VERSION)) {
184
168
  items.push({
185
- id: 'hbcli',
186
- label: 'hbcli(酒店实时源)',
187
- status: 'missing',
169
+ id: 'hbcli', label: 'hbcli(酒店实时源)', status: 'missing',
188
170
  detail: `版本过旧(v${v.join('.')} < v${MIN_HBCLI_VERSION})——trade.* / search/checkAvail 会 401(issue #142)`,
189
- fix: 'npm install -g staicli --registry=https://registry.npmjs.org/'
190
- });
191
- } else {
192
- const whoami = await probe(hbCmd, [
193
- 'auth',
194
- 'whoami'
195
- ]);
196
- items.push(whoami ? {
197
- id: 'hbcli',
198
- label: 'hbcli(酒店实时源)',
199
- status: 'ok',
200
- detail: `已安装且凭证有效(${hbPresent}, v${v ? v.join('.') : '?'})`
201
- } : {
202
- id: 'hbcli',
203
- label: 'hbcli(酒店实时源)',
204
- status: 'degraded',
205
- detail: '二进制在,但凭证未配置/失效——酒店检索将降级静态包(非实时)',
206
- fix: 'hbcli auth set-credentials --app-key hotelbyte_api_demo --app-secret hotelbyte_api_demo(快速试用沙箱;正式 key 向 HotelByte 申请)'
171
+ fix: 'npm install -g staicli --registry=https://registry.npmjs.org/',
207
172
  });
208
173
  }
209
- } else {
174
+ else {
175
+ const whoami = await probe(hbCmd, ['auth', 'whoami']);
176
+ items.push(whoami
177
+ ? { id: 'hbcli', label: 'hbcli(酒店实时源)', status: 'ok', detail: `已安装且凭证有效(${hbPresent}, v${v ? v.join('.') : '?'})` }
178
+ : {
179
+ id: 'hbcli', label: 'hbcli(酒店实时源)', status: 'degraded',
180
+ detail: '二进制在,但凭证未配置/失效——酒店检索将降级静态包(非实时)',
181
+ fix: 'hbcli auth set-credentials --app-key hotelbyte_api_demo --app-secret hotelbyte_api_demo(快速试用沙箱;正式 key 向 HotelByte 申请)',
182
+ });
183
+ }
184
+ }
185
+ else {
210
186
  items.push({
211
- id: 'hbcli',
212
- label: 'hbcli(酒店实时源)',
213
- status: 'missing',
187
+ id: 'hbcli', label: 'hbcli(酒店实时源)', status: 'missing',
214
188
  detail: '未安装——酒店检索降级静态包(公开渠道估算,非实时,仅覆盖内置场景)',
215
- fix: 'npx gotry doctor --fix'
189
+ fix: 'npx @danceiny/gotry doctor --fix',
216
190
  });
217
191
  }
192
+ // 4. flyai(飞猪官方只读通道;匿名试用额度共享,易达限)
193
+ // 配额状态可见(通道健康持久面):最近一次达限时间进 detail——
194
+ // 「易达限却不可见」是 issue #107 的病灶之一。
218
195
  const flyaiKey = env.FLYAI_API_KEY?.trim();
219
- const flyaiQuotaNote = !flyaiKey && opts.stateRoot ? await (async ()=>{
220
- const ev = (await readLatestChannelEvents(opts.stateRoot)).get('flyai');
221
- return ev?.state === 'down' ? `;最近一次试用达限: ${ev.at}(匿名共享池,正式 key 可解除)` : '';
222
- })() : '';
223
- items.push(flyaiKey ? {
224
- id: 'flyai',
225
- label: 'FlyAI(飞猪官方检索)',
226
- status: 'ok',
227
- detail: 'FLYAI_API_KEY 已配(正式 key,无试用额度限制)'
228
- } : {
229
- id: 'flyai',
230
- label: 'FlyAI(飞猪官方检索)',
231
- status: 'degraded',
232
- detail: `未配 FLYAI_API_KEY——走匿名试用额度(共享,易达限;达限报 "Trial limit reached")${flyaiQuotaNote}`,
233
- fix: '到 flyai.open.fliggy.com 控制台申请正式 key,配进环境变量 FLYAI_API_KEY;无 key 期间机/火/酒检索请以 gotry_session_search(账号会话)为主'
234
- });
196
+ const flyaiQuotaNote = !flyaiKey && opts.stateRoot
197
+ ? await (async () => {
198
+ const ev = (await readLatestChannelEvents(opts.stateRoot)).get('flyai');
199
+ return ev?.state === 'down' ? `;最近一次试用达限: ${ev.at}(匿名共享池,正式 key 可解除)` : '';
200
+ })()
201
+ : '';
202
+ items.push(flyaiKey
203
+ ? { id: 'flyai', label: 'FlyAI(飞猪官方检索)', status: 'ok', detail: 'FLYAI_API_KEY 已配(正式 key,无试用额度限制)' }
204
+ : {
205
+ id: 'flyai', label: 'FlyAI(飞猪官方检索)', status: 'degraded',
206
+ detail: `未配 FLYAI_API_KEY——走匿名试用额度(共享,易达限;达限报 "Trial limit reached")${flyaiQuotaNote}`,
207
+ fix: '到 flyai.open.fliggy.com 控制台申请正式 key,配进环境变量 FLYAI_API_KEY;无 key 期间机/火/酒检索请以 gotry_session_search(账号会话)为主',
208
+ });
209
+ // 5. dsh-better-sidebar(dsh web 侧栏工作台;产物预览面 + doctor 报告的查看面)
235
210
  const sidebarPkg = join(home, '.dsh/profiles/web/node_modules/dsh-better-sidebar/package.json');
236
- items.push(existsSync(sidebarPkg) ? {
237
- id: 'sidebar',
238
- label: 'dsh-better-sidebar(侧栏工作台)',
239
- status: 'ok',
240
- detail: '已安装——web UI 右侧工作台可预览产物与 doctor 报告(gotry-state/doctor-report.md)'
241
- } : {
242
- id: 'sidebar',
243
- label: 'dsh-better-sidebar(侧栏工作台)',
244
- status: 'missing',
245
- detail: '未安装——dsh web 无右侧工作台,产物与 doctor 报告只能在对话里看(gotry_artifacts_list)',
246
- fix: 'npx gotry doctor --fix'
247
- });
211
+ items.push(existsSync(sidebarPkg)
212
+ ? { id: 'sidebar', label: 'dsh-better-sidebar(侧栏工作台)', status: 'ok', detail: '已安装——web UI 右侧工作台可预览产物与 doctor 报告(gotry-state/doctor-report.md)' }
213
+ : {
214
+ id: 'sidebar', label: 'dsh-better-sidebar(侧栏工作台)', status: 'missing',
215
+ detail: '未安装——dsh web 无右侧工作台,产物与 doctor 报告只能在对话里看(gotry_artifacts_list)',
216
+ fix: 'npx @danceiny/gotry doctor --fix',
217
+ });
218
+ // 6. dsh-calendar(patch 分发面宿主插件;D-9 拍板:默认不挂载)
219
+ // 未配置的日历工具是纯负资产(issue #106:会话中段才撞「未配置 username」),
220
+ // 工作窗口由 persona (1) 访谈覆盖;挂载与否由 **setup 状态面**决定
221
+ // (`~/.gotry/calendar.json`,`npx @danceiny/gotry setup calendar` on/off——founder
222
+ // 2026-09-03 纠偏:禁止环境变量控制产品行为,可选依赖进 setup 状态管理)
248
223
  const calStatePath = join(home, '.gotry', 'calendar.json');
249
- const calEnabled = (()=>{
224
+ const calEnabled = (() => {
250
225
  try {
251
226
  return JSON.parse(readFileSync(calStatePath, 'utf-8'))?.enabled === true;
252
- } catch {
227
+ }
228
+ catch {
253
229
  return false;
254
230
  }
255
231
  })();
256
232
  const calProfilePatch = join(home, '.dsh/profiles/web/cordis.patch.yml');
257
- const calConfigured = calEnabled && existsSync(calProfilePatch) && (()=>{
233
+ const calConfigured = calEnabled && existsSync(calProfilePatch) && (() => {
258
234
  try {
259
235
  const content = readFileSync(calProfilePatch, 'utf-8');
260
236
  return /calendar/.test(content) && /username\s*:/.test(content);
261
- } catch {
237
+ }
238
+ catch {
262
239
  return false;
263
240
  }
264
241
  })();
265
- items.push(!calEnabled ? {
266
- id: 'calendar',
267
- label: 'dsh-calendar(日历工作窗口)',
268
- status: 'ok',
269
- detail: '默认未挂载(D-9:未配置的日历工具不进工具箱;工作窗口由访谈覆盖,不影响任何检索)'
270
- } : calConfigured ? {
271
- id: 'calendar',
272
- label: 'dsh-calendar(日历工作窗口)',
273
- status: 'ok',
274
- detail: `已挂载且已配置(${calStatePath})`
275
- } : {
276
- id: 'calendar',
277
- label: 'dsh-calendar(日历工作窗口)',
278
- status: 'degraded',
279
- detail: '已挂载但 calendar 未配置 username——日历工具会话中会报「未配置」',
280
- fix: `npx gotry setup calendar --off(恢复默认不挂载),或在 ${calProfilePatch} 覆盖 calendar 行 config 填 username(指引: npx gotry setup calendar --status)`
281
- });
242
+ items.push(!calEnabled
243
+ ? {
244
+ id: 'calendar', label: 'dsh-calendar(日历工作窗口)', status: 'ok',
245
+ detail: '默认未挂载(D-9:未配置的日历工具不进工具箱;工作窗口由访谈覆盖,不影响任何检索)',
246
+ }
247
+ : calConfigured
248
+ ? { id: 'calendar', label: 'dsh-calendar(日历工作窗口)', status: 'ok', detail: `已挂载且已配置(${calStatePath})` }
249
+ : {
250
+ id: 'calendar', label: 'dsh-calendar(日历工作窗口)', status: 'degraded',
251
+ detail: '已挂载但 calendar 未配置 username——日历工具会话中会报「未配置」',
252
+ fix: `npx @danceiny/gotry setup calendar --off(恢复默认不挂载),或在 ${calProfilePatch} 覆盖 calendar 行 config 填 username(指引: npx @danceiny/gotry setup calendar --status)`,
253
+ });
254
+ // 7. patch 分发面宿主插件(issue #113 L1 残量,design §3.1:初始化可见取代会话中段撞错)
255
+ // 这类插件在 cordis patch 里是占位行,bin/gotry-inner.js 运行时解析——**解析失败整块
256
+ // 静默剔除,不挡启动**:模型只觉得「没有这个工具」,没人告诉它为什么。doctor 把两态照亮。
257
+ // 候选清单与 bin 解析逻辑同口径(map:tarball vendor 优先→runtime workspace→npm 提升解析;
258
+ // ask-user:dsh 闭包上下文解析)。map 保持随包 vendor 分发而非 npm 依赖:历史上游
259
+ // peerDependencies 要求 dsh-settings/dsh-tools >=0.1.2-rc.1,与锁定的 0.1.2-alpha.3 家族
260
+ // 在 npm 严格 peer 解析下 ERESOLVE;当前锁定 0.1.5-rc.1 家族,vendored 副本已对齐
261
+ // 0.1.5-rc.1,继续以 vendor 形态复用适配补丁,避免弄坏 npx 主安装路径。
282
262
  const rootRequire = createRequire(join(repoRoot, 'package.json'));
283
263
  const mapCandidates = [
284
264
  join(repoRoot, 'ts/dsh-runtime/vendor/dsh-map-tools/lib/index.js'),
285
265
  join(repoRoot, 'ts/dsh-runtime/node_modules/dsh-map-tools/lib/index.js'),
286
266
  join(repoRoot, 'node_modules/dsh-map-tools/package.json'),
287
267
  join(repoRoot, 'ts/node_modules/dsh-map-tools/package.json'),
288
- join(home, '.dsh/profiles/web/node_modules/dsh-map-tools/package.json')
268
+ join(home, '.dsh/profiles/web/node_modules/dsh-map-tools/package.json'),
289
269
  ];
290
- let mapHit = mapCandidates.find((p)=>existsSync(p));
270
+ let mapHit = mapCandidates.find(p => existsSync(p));
271
+ // npm/npx 提升布局:gotry 的依赖在包目录外的同级提升位,existsSync 候选打不中——
272
+ // 与 inner 的 require_.resolve 同机制,从包位置向上走查解析
291
273
  if (!mapHit) {
292
274
  try {
293
275
  mapHit = rootRequire.resolve('dsh-map-tools');
294
- } catch {
276
+ }
277
+ catch {
295
278
  mapHit = undefined;
296
279
  }
297
280
  }
298
- items.push(mapHit ? {
299
- id: 'map-tools',
300
- label: 'dsh-map-tools(地图/路线/POI)',
301
- status: 'ok',
302
- detail: `已就位(${mapHit})——地图工具可用(零 key,走 OSM/OSRM)`
303
- } : {
304
- id: 'map-tools',
305
- label: 'dsh-map-tools(地图/路线/POI)',
306
- status: 'missing',
307
- detail: '未随包解析——启动时该 patch 条目被静默剔除,地图/路线/POI 工具不会出现在模型工具箱(缺地图不挡旅行规划,只少能力)',
308
- fix: '重装 @danceiny/gotry(地图插件随包 vendor 分发,缺失多为安装不完整)'
309
- });
281
+ items.push(mapHit
282
+ ? { id: 'map-tools', label: 'dsh-map-tools(地图/路线/POI)', status: 'ok', detail: `已就位(${mapHit})——地图工具可用(零 key,走 OSM/OSRM)` }
283
+ : {
284
+ id: 'map-tools', label: 'dsh-map-tools(地图/路线/POI)', status: 'missing',
285
+ detail: '未随包解析——启动时该 patch 条目被静默剔除,地图/路线/POI 工具不会出现在模型工具箱(缺地图不挡旅行规划,只少能力)',
286
+ fix: '重装 @danceiny/gotry(地图插件随包 vendor 分发,缺失多为安装不完整)',
287
+ });
310
288
  const askCandidates = [
311
289
  join(repoRoot, 'ts/dsh-runtime/vendor/deepseek-ai-dsh-tool-ask-user/package.json'),
312
290
  join(repoRoot, 'node_modules/@deepseek-ai/dsh-tool-ask-user/package.json'),
313
- join(home, '.dsh/profiles/web/node_modules/@deepseek-ai/dsh-tool-ask-user/package.json')
291
+ join(home, '.dsh/profiles/web/node_modules/@deepseek-ai/dsh-tool-ask-user/package.json'),
314
292
  ];
315
- let askHit = askCandidates.find((p)=>existsSync(p));
293
+ let askHit = askCandidates.find(p => existsSync(p));
294
+ // 与 inner 同口径:优先从 dsh 包上下文解析(pnpm 嵌套布局只有 dsh 看得见闭包成员),
295
+ // 再从包根上下文(npm/npx 提升布局),最后静态候选(source vendored / profile)
316
296
  if (!askHit) {
317
297
  try {
318
298
  const reqFromDsh = createRequire(rootRequire.resolve('@deepseek-ai/dsh/lib/bin.js'));
319
299
  askHit = reqFromDsh.resolve('@deepseek-ai/dsh-tool-ask-user');
320
- } catch {
300
+ }
301
+ catch {
321
302
  try {
322
303
  askHit = rootRequire.resolve('@deepseek-ai/dsh-tool-ask-user');
323
- } catch {
304
+ }
305
+ catch {
324
306
  askHit = undefined;
325
307
  }
326
308
  }
327
309
  }
328
- items.push(askHit ? {
329
- id: 'ask-user',
330
- label: 'dsh-tool-ask-user(结构化澄清卡)',
331
- status: 'ok',
332
- detail: `已就位(${askHit})——ask_user_question 澄清卡可用(web 原生卡片;headless+TTY 用 stdio 提供方)`
333
- } : {
334
- id: 'ask-user',
335
- label: 'dsh-tool-ask-user(结构化澄清卡)',
336
- status: 'missing',
337
- detail: '未解析——启动时澄清卡注入被静默剔除,模型只能散文追问(人格契约 (5) 退化文本形态)',
338
- fix: '重装 @danceiny/gotry——该依赖随 dsh 闭包自带,缺失多为安装不完整'
310
+ items.push(askHit
311
+ ? { id: 'ask-user', label: 'dsh-tool-ask-user(结构化澄清卡)', status: 'ok', detail: `已就位(${askHit})——ask_user_question 澄清卡可用(web 原生卡片;headless+TTY 用 stdio 提供方)` }
312
+ : {
313
+ id: 'ask-user', label: 'dsh-tool-ask-user(结构化澄清卡)', status: 'missing',
314
+ detail: '未解析——启动时澄清卡注入被静默剔除,模型只能散文追问(人格契约 (5) 退化文本形态)',
315
+ fix: '重装 @danceiny/gotry——该依赖随 dsh 闭包自带,缺失多为安装不完整',
316
+ });
317
+ // 8. LLM key:显式让渡给 dsh 宿主(founder 2026-09-02:doctor 不管 key)
318
+ items.push({ id: 'llm-key', label: 'LLM key', status: 'ok', detail: '由 dsh 宿主 UI 管理——不在 doctor 体检范围(gotry 不接触、不回显凭证)' });
319
+ const broken = items.filter(i => i.status !== 'ok' && i.id !== 'llm-key');
320
+ const summary = broken.length === 0
321
+ ? `体检通过:${items.length} 项全部就绪(可选依赖齐,LLM key 归 dsh 宿主管)。`
322
+ : `体检发现 ${broken.length} 项待处理:${broken.map(i => `${i.label}(${i.status === 'missing' ? '未装' : '半可用'})`).join('、')}。补装:终端跑 npx @danceiny/gotry doctor --fix,或按各项 fix 指引逐项处理。`;
323
+ return { ok: broken.length === 0, items, summary };
324
+ }
325
+ /** 修复指引入表:命令类才加反引号(prose 类如「到控制台申请 key」原样) */
326
+ const fixCell = (fix) => (!fix ? '—' : /^(npx|hbcli|curl|pip|python|\$)/.test(fix) ? `\`${fix}\`` : fix);
327
+ /** 稳定 scope key(item ids 排序去重逗号串,空 = '')—批准缓存 / 审计 / 测试断言 */
328
+ export function scopeKeyFor(ids) {
329
+ if (!Array.isArray(ids) || ids.length === 0)
330
+ return '';
331
+ return [...new Set(ids)].sort().join(',');
332
+ }
333
+ /** DoctorItem → bootstrap 形状:status 改名为 level(关键适配,见 BootstrapItem 注释) */
334
+ function toBootstrapItem(i) {
335
+ return { label: i.label, level: i.status, detail: i.detail, fix: i.fix };
336
+ }
337
+ const KNOWN_IDS = new Set(['node', 'extension', 'agent-reach', 'hbcli', 'flyai', 'sidebar', 'llm-key', 'calendar', 'map-tools', 'ask-user']);
338
+ let cachedBootstrap = null;
339
+ async function loadBootstrapBridge() {
340
+ if (cachedBootstrap)
341
+ return cachedBootstrap;
342
+ const url = new URL('../../bin/gotry-bootstrap.js', import.meta.url);
343
+ const mod = await import(url.href);
344
+ cachedBootstrap = mod;
345
+ return mod;
346
+ }
347
+ export function createRepairApprovalGate(opts) {
348
+ const store = opts.store ?? new WeakMap();
349
+ const getState = (agent) => {
350
+ if (!agent)
351
+ return { granted: new Set(), denied: new Map() };
352
+ let s = store.get(agent);
353
+ if (!s) {
354
+ s = { granted: new Set(), denied: new Map() };
355
+ store.set(agent, s);
356
+ }
357
+ return s;
358
+ };
359
+ const has = (agent, scopeKey) => {
360
+ if (!scopeKey)
361
+ return 'not-needed';
362
+ const s = getState(agent);
363
+ if (s.granted.has(scopeKey))
364
+ return 'granted';
365
+ return s.denied.get(scopeKey) ?? 'not-needed'; // 保留具体 rejected/cancelled
366
+ };
367
+ const request = async (agent, scopeKey, reason, requestMeta = {}) => {
368
+ const cached = has(agent, scopeKey);
369
+ if (cached !== 'not-needed')
370
+ return cached;
371
+ const seam = opts.approval?.();
372
+ if (!seam || typeof seam.request !== 'function' || !agent)
373
+ return 'unavailable';
374
+ let raw;
375
+ try {
376
+ raw = await seam.request({ agent, toolName: 'gotry_doctor', callId: requestMeta.callId, reason, signal: requestMeta.signal });
377
+ }
378
+ catch {
379
+ raw = 'unavailable';
380
+ }
381
+ const mapped = raw === 'allowed-once' ? 'granted' :
382
+ raw === 'rejected' || raw === 'cancelled' ? raw : 'unavailable';
383
+ const s = getState(agent);
384
+ if (mapped === 'granted')
385
+ s.granted.add(scopeKey);
386
+ else if (mapped === 'rejected' || mapped === 'cancelled')
387
+ s.denied.set(scopeKey, mapped);
388
+ // unavailable 不记(通道回来还能再问一次)
389
+ return mapped;
390
+ };
391
+ return { has, request, store };
392
+ }
393
+ /** 修复运行(主入口):diagnosis → bootstrap.buildOnboardingPlan(status→level 适配)
394
+ * → scope-keyed 审批 → bootstrap.runOnboardingFix(真安装器 + 内部复检)
395
+ * → per-item verdict。**生产**:不传 opts,走 bootstrap 真实导出。
396
+ * **tests**:注入 installers / recheck / bridge 跑 fakes,永不真安装命令。
397
+ * user-action / unavailable 项绝不进 selected(分类器守住),scope 过滤后被剔除
398
+ * 的项从 selected 排除(永不装)。 */
399
+ export async function runDoctorRepair(report, approve, opts = {}) {
400
+ // 1. 选 bridge:tests 注入覆盖;生产用 bootstrap 真实导出(懒加载并缓存)
401
+ const bridge = opts.bridge
402
+ ? { ...await loadBootstrapBridge(), ...opts.bridge }
403
+ : await loadBootstrapBridge();
404
+ const runInstalls = opts.runOnboardingFix ?? bridge.runOnboardingFix;
405
+ // 2. status→level 适配(关键)—直传 DoctorItem 会让 bootstrap.classifyDoctorGap
406
+ // 因 !item.level 把所有项分到 ok,plan 始终为空(原版真 bug)。
407
+ const bItems = (report.items ?? []).map(toBootstrapItem);
408
+ const plan = bridge.buildOnboardingPlan(bItems, { platform: opts.platform, env: opts.env });
409
+ const labelToId = new Map();
410
+ for (const i of report.items ?? [])
411
+ if (i?.id && i?.label)
412
+ labelToId.set(i.label, i.id);
413
+ // 3. selected = plan.auto ∩ 已知 doctor item id(防御:未知 id 静默剔除不冒充 auto)
414
+ const selected = (plan.auto ?? [])
415
+ .filter((g) => KNOWN_IDS.has(labelToId.get(g.label) ?? ''))
416
+ .map((g) => ({ id: labelToId.get(g.label), label: g.label }));
417
+ const skipped = [
418
+ ...(plan.userAction ?? []).map((g) => ({ id: labelToId.get(g.label) ?? '', label: g.label, bucket: 'user-action', reason: g.fix ?? g.detail ?? '需用户本人操作' })),
419
+ ...(plan.unavailable ?? []).map((g) => ({ id: labelToId.get(g.label) ?? '', label: g.label, bucket: 'unavailable', reason: g.detail ?? '本仓无自动安装面' })),
420
+ ];
421
+ const scopeKey = scopeKeyFor(selected.map((s) => s.id));
422
+ // 4. 无 selected → 早退(零审批请求,零副作用)
423
+ if (selected.length === 0) {
424
+ const healthy = (report.items ?? []).every((item) => item.status === 'ok');
425
+ return {
426
+ ok: healthy, verdict: 'no-repair-needed', approval: 'not-needed',
427
+ selected, scopeKey,
428
+ repairs: [], skipped,
429
+ summary: healthy
430
+ ? '诊断:所选项目已经全部就位,无需修复。'
431
+ : '诊断:无可自动补装的缺项(其余项需用户本人操作或本机暂不支持)。',
432
+ };
433
+ }
434
+ // 5. scope-keyed 审批(rejected 与 cancelled 在 denial 缓存里分开记)
435
+ const approval = await approve(scopeKey, `将自动补装 ${selected.length} 项可选依赖:${selected.map((s) => s.label).join('、')};复用 doctor --fix 幂等安装器。`);
436
+ if (approval !== 'granted') {
437
+ return {
438
+ ok: false, verdict: 'approval-denied', approval,
439
+ selected, scopeKey,
440
+ repairs: [], skipped,
441
+ summary: approval === 'rejected' ? '修复未执行:用户拒绝了这次补装(本会话吊销,不再询问)。'
442
+ : approval === 'cancelled' ? '修复未执行:用户取消了这次补装(本会话吊销,不再询问)。'
443
+ : '修复未执行:当前无可用审批通道(headless / 无 UI)。',
444
+ };
445
+ }
446
+ // 6. 真执行:bootstrap.runOnboardingFix(默认 installers = real setupHbcli/etc.;
447
+ // 默认 recheck = bootstrap 内置 doctorChecks,与 runDoctorChecks 同源)。tests 注入 fakes。
448
+ const fullPlan = {
449
+ promptable: true, auto: plan.auto ?? [], userAction: plan.userAction ?? [], unavailable: plan.unavailable ?? [],
450
+ };
451
+ const runResults = await runInstalls(fullPlan, {
452
+ ...(opts.installers ? { installers: opts.installers } : {}),
453
+ ...(opts.recheck ? { recheck: opts.recheck } : {}),
339
454
  });
340
- items.push({
341
- id: 'llm-key',
342
- label: 'LLM key',
343
- status: 'ok',
344
- detail: '由 dsh 宿主 UI 管理——不在 doctor 体检范围(gotry 不接触、不回显凭证)'
455
+ // 7. per-item 终态:bootstrap 已含内部复检的 installed / needs-user-action / unavailable;
456
+ // needs-user-action 与 unavailable 都视为 failed(给重试命令)。
457
+ const repairs = selected.map((s) => {
458
+ const r = runResults.find((x) => x.label === s.label);
459
+ if (r?.status === 'installed')
460
+ return { id: s.id, label: s.label, status: 'installed', reason: r.reason ?? '已就位' };
461
+ return {
462
+ id: s.id, label: s.label, status: 'failed',
463
+ reason: r?.reason ?? '安装器未确认成功',
464
+ nextAction: r?.retry ?? 'npx @danceiny/gotry doctor --fix',
465
+ };
345
466
  });
346
- const broken = items.filter((i)=>i.status !== 'ok' && i.id !== 'llm-key');
347
- const summary = broken.length === 0 ? `体检通过:${items.length} 项全部就绪(可选依赖齐,LLM key 归 dsh 宿主管)。` : `体检发现 ${broken.length} 项待处理:${broken.map((i)=>`${i.label}(${i.status === 'missing' ? '未装' : '半可用'})`).join('、')}。补装:终端跑 npx gotry doctor --fix,或按各项 fix 指引逐项处理。`;
467
+ const failed = repairs.filter((r) => r.status === 'failed').length;
468
+ const installed = repairs.length - failed;
469
+ const verdict = failed === 0 ? 'repaired' : installed === 0 ? 'repair-blocked' : 'partial-repair';
348
470
  return {
349
- ok: broken.length === 0,
350
- items,
351
- summary
471
+ ok: failed === 0, verdict, approval, selected, scopeKey, repairs, skipped,
472
+ summary: failed === 0
473
+ ? `修复完成:${installed} 项已就位(其余项见 skipped,需要时手动处理)。`
474
+ : installed === 0
475
+ ? `修复失败:${failed} 项复检仍未就位(${repairs.filter((r) => r.status === 'failed').map((r) => r.label).join('、')});重试: npx @danceiny/gotry doctor --fix。`
476
+ : `修复部分成功:${installed}/${selected.length} 项已就位,${failed} 项失败(${repairs.filter((r) => r.status === 'failed').map((r) => r.label).join('、')});重试: npx @danceiny/gotry doctor --fix。`,
352
477
  };
353
478
  }
354
- const fixCell = (fix)=>!fix ? '—' : /^(npx|hbcli|curl|pip|python|\$)/.test(fix) ? `\`${fix}\`` : fix;
479
+ /** 报告 侧栏可预览 markdown(写盘由调用方决定;本函数纯渲染) */
355
480
  export function renderDoctorReportMd(report, now = new Date()) {
356
- const icon = (s)=>s === 'ok' ? '✅' : s === 'degraded' ? '⚠️' : '❌';
481
+ const icon = (s) => (s === 'ok' ? '✅' : s === 'degraded' ? '⚠️' : '❌');
357
482
  const lines = [
358
483
  '# GoTry 依赖体检报告(doctor)',
359
484
  '',
360
- `> 生成于 ${now.toISOString()};重新生成:终端 \`npx gotry doctor\`,或在对话里让助手调 gotry_doctor。`,
485
+ `> 生成于 ${now.toISOString()};重新生成:终端 \`npx @danceiny/gotry doctor\`,或在对话里让助手调 gotry_doctor。`,
361
486
  '',
362
487
  '| 状态 | 依赖 | 现状 | 修复指引 |',
363
488
  '|---|---|---|---|',
364
- ...report.items.map((i)=>`| ${icon(i.status)} | ${i.label} | ${i.detail} | ${fixCell(i.fix)} |`),
489
+ ...report.items.map(i => `| ${icon(i.status)} | ${i.label} | ${i.detail} | ${fixCell(i.fix)} |`),
365
490
  '',
366
491
  `**结论**:${report.summary}`,
367
492
  '',
368
493
  '---',
369
494
  '',
370
- '- `npx gotry doctor` 随时可重跑(只读,不改任何东西);',
371
- '- `npx gotry doctor --fix` 按上表补装(hbcli 官方脚本 / agent-reach pip / dsh-better-sidebar 插件);',
495
+ '- `npx @danceiny/gotry doctor` 随时可重跑(只读,不改任何东西);',
496
+ '- `npx @danceiny/gotry doctor --fix` 按上表补装(hbcli 官方脚本 / agent-reach pip / dsh-better-sidebar 插件);',
372
497
  '- LLM key 由 dsh 宿主 UI 管理,gotry 永不体检、不回显。',
373
- ''
498
+ '',
374
499
  ];
375
500
  return lines.join('\n');
376
501
  }
377
-
378
-
379
- //# sourceURL=ts/capabilities/doctor.ts