@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,116 +1,93 @@
1
+ /**
2
+ * 通道注册表 + 通道健康面测试(issue #106/#107/#108 编排设计落地,
3
+ * docs/design/tool-orchestration-design.md §2/§3;全离线,隔离 tmp stateRoot):
4
+ * 1. 注册表封闭性:id 唯一/quotaClass·tier·intent 闭集/静态包不可路由
5
+ * 2. 意图顺位:证据级降序(官方API > 会话 > 网页兜底),同 tier 按效率
6
+ * 3. routingAdvice:down 通道被排除/发起通道被排除/limit 截断;hit 即恢复;
7
+ * Dida down 时推进到下一个健康酒店通道
8
+ * 4. noteChannelVerdict 映射:needs-setup→down/hit→清除/miss·error→不动/cooldown 过期
9
+ * 5. persona 路由卡:确定性渲染,含改道规则与意图顺位行
10
+ * 6. JSONL 持久面:record/readLatest 往返 + 坏行容忍 + limitDays 过滤
11
+ * 7. doctor 集成:flyai 最近达限时间可见;calendar 三态(默认不挂载/opt-in 未配置/opt-in 已配置);
12
+ * patch 宿主插件两态照亮(map-tools/ask-user,issue #113 L1 残量)
13
+ * 8. apply 接线:channel_routing_card 变量已注册;参数级拒绝不带 routing 字段;
14
+ * 工具描述首行生成(检索工具前置意图顺位卡,非检索工具原样,#113)
15
+ *
16
+ * 运行: cd ts && npx tsx scripts/channel-registry-tests.ts
17
+ */
1
18
  import assert from 'node:assert/strict';
2
19
  import { mkdtemp, mkdir, writeFile, rm, readFile } from 'node:fs/promises';
3
20
  import { tmpdir } from 'node:os';
4
21
  import { join } from 'node:path';
5
- import { CHANNELS, channelsForIntent, routingAdvice, renderRoutingCard, toolRoutingHeadline, INTENT_LABELS } from '../capabilities/channel-registry.js';
6
- import { noteChannelVerdict, channelState, clearChannel, resetChannelHealth, recordChannelEvent, readLatestChannelEvents } from '../capabilities/channel-health.js';
22
+ import { CHANNELS, channelsForIntent, routingAdvice, renderRoutingCard, toolRoutingHeadline, INTENT_LABELS, } from '../capabilities/channel-registry.js';
23
+ import { noteChannelVerdict, channelState, clearChannel, resetChannelHealth, recordChannelEvent, readLatestChannelEvents, } from '../capabilities/channel-health.js';
7
24
  import { runDoctorChecks } from '../capabilities/doctor.js';
8
25
  import { apply } from '../src/index.js';
9
26
  const NOW = 1_700_000_000_000;
10
- const QUOTA_CLASSES = [
11
- 'user-session',
12
- 'user-key',
13
- 'anonymous-trial',
14
- 'free-public',
15
- 'static'
16
- ];
17
- const TIERS = [
18
- 'realtime-api',
19
- 'session',
20
- 'best-effort',
21
- 'static'
22
- ];
27
+ // 1. 注册表封闭性
28
+ const QUOTA_CLASSES = ['user-session', 'user-key', 'anonymous-trial', 'free-public', 'static'];
29
+ const TIERS = ['realtime-api', 'session', 'best-effort', 'static'];
23
30
  const INTENTS = Object.keys(INTENT_LABELS);
24
31
  const ids = new Set();
25
- for (const c of CHANNELS){
32
+ for (const c of CHANNELS) {
26
33
  assert.ok(!ids.has(c.id), `通道 id 重复:${c.id}`);
27
34
  ids.add(c.id);
28
35
  assert.ok(QUOTA_CLASSES.includes(c.quotaClass), `${c.id} quotaClass 越界`);
29
36
  assert.ok(TIERS.includes(c.tier), `${c.id} tier 越界`);
30
- assert.ok(c.intents.length > 0 && c.intents.every((i)=>INTENTS.includes(i)), `${c.id} intents 越界`);
37
+ assert.ok(c.intents.length > 0 && c.intents.every(i => INTENTS.includes(i)), `${c.id} intents 越界`);
31
38
  assert.ok(c.tool.startsWith('gotry_'), `${c.id} 工具名形态`);
32
- if (c.quotaClass === 'static') assert.equal(c.routable, false, '静态包不进模型可选路由');
39
+ if (c.quotaClass === 'static')
40
+ assert.equal(c.routable, false, '静态包不进模型可选路由');
33
41
  }
34
42
  assert.ok(CHANNELS.length >= 9, '注册表覆盖面(≥9 通道)');
35
43
  console.log('1. 注册表封闭性 OK');
44
+ // 2. 意图顺位(证据级降序)
36
45
  resetChannelHealth();
37
- assert.deepEqual(channelsForIntent('search-flight').map((c)=>c.id), [
38
- 'flyai',
39
- 'session:ctrip-flight',
40
- 'web-read'
41
- ], '机票顺位');
42
- assert.deepEqual(channelsForIntent('search-train').map((c)=>c.id), [
43
- 'flyai',
44
- 'session:12306-train',
45
- 'web-read'
46
- ], '火车顺位');
47
- assert.deepEqual(channelsForIntent('search-hotel').map((c)=>c.id), [
48
- 'flyai',
49
- 'hbcli-hotel',
50
- 'session:ctrip-hotel',
51
- 'web-read'
52
- ], '酒店顺位');
53
- assert.deepEqual(channelsForIntent('weather').map((c)=>c.id), [
54
- 'open-meteo'
55
- ], '单通道意图');
46
+ assert.deepEqual(channelsForIntent('search-flight').map(c => c.id), ['flyai', 'session:ctrip-flight', 'web-read'], '机票顺位');
47
+ assert.deepEqual(channelsForIntent('search-train').map(c => c.id), ['flyai', 'session:12306-train', 'web-read'], '火车顺位');
48
+ assert.deepEqual(channelsForIntent('search-hotel').map(c => c.id), ['flyai', 'hbcli-hotel', 'session:ctrip-hotel', 'session:dida-portal', 'web-read'], '酒店完整顺位(Dida 登记后仍钉全序)');
49
+ assert.deepEqual(channelsForIntent('weather').map(c => c.id), ['open-meteo'], '单通道意图');
56
50
  console.log('2. 意图顺位(证据级 > 效率)OK');
51
+ // 3. routingAdvice:健康面驱动
57
52
  const base = routingAdvice('search-flight');
58
53
  assert.equal(base.alternatives[0].channel, 'flyai', '健康时 flyai 首荐(零 setup 摩擦)');
59
54
  assert.ok(base.alternatives[0].why.length > 4 && (base.alternatives[0].setup?.length ?? 0) > 0, 'why/setup 齐备');
60
- noteChannelVerdict('flyai', 'needs-setup', {
61
- now: NOW
62
- });
63
- const afterDown = routingAdvice('search-flight', {
64
- now: NOW
65
- });
66
- assert.ok(!afterDown.alternatives.some((a)=>a.channel === 'flyai'), 'flyai 达限后从建议表排除(#108 病灶)');
55
+ noteChannelVerdict('flyai', 'needs-setup', { now: NOW });
56
+ const afterDown = routingAdvice('search-flight', { now: NOW });
57
+ assert.ok(!afterDown.alternatives.some(a => a.channel === 'flyai'), 'flyai 达限后从建议表排除(#108 病灶)');
67
58
  assert.equal(afterDown.alternatives[0].channel, 'session:ctrip-flight', 'session 升首荐(优先级=健康面投影)');
68
- const excl = routingAdvice('search-flight', {
69
- excludeChannel: 'session:ctrip-flight',
70
- now: NOW
71
- });
72
- assert.ok(!excl.alternatives.some((a)=>a.channel === 'session:ctrip-flight'), '发起通道被排除');
73
- assert.equal(routingAdvice('search-flight', {
74
- limit: 1,
75
- now: NOW
76
- }).alternatives.length, 1, 'limit 截断');
77
- noteChannelVerdict('flyai', 'hit', {
78
- now: NOW
79
- });
80
- assert.equal(routingAdvice('search-flight', {
81
- now: NOW
82
- }).alternatives[0].channel, 'flyai', 'hit 即恢复(#107:补 key 不被陈旧状态锁死)');
83
- console.log('3. routingAdvice(down 排除/发起排除/limit/hit 恢复)OK');
59
+ const excl = routingAdvice('search-flight', { excludeChannel: 'session:ctrip-flight', now: NOW });
60
+ assert.ok(!excl.alternatives.some(a => a.channel === 'session:ctrip-flight'), '发起通道被排除');
61
+ assert.equal(routingAdvice('search-flight', { limit: 1, now: NOW }).alternatives.length, 1, 'limit 截断');
62
+ noteChannelVerdict('flyai', 'hit', { now: NOW });
63
+ assert.equal(routingAdvice('search-flight', { now: NOW }).alternatives[0].channel, 'flyai', 'hit 即恢复(#107:补 key 不被陈旧状态锁死)');
64
+ // Dida 已是酒店 session 通道的一员:#297 后先让更高顺位通道不可用,证明 Dida
65
+ // 成为当前首选;再把 Dida 标 down,必须推进到注册表中的后继 web-read。
84
66
  resetChannelHealth();
85
- assert.ok(noteChannelVerdict('x', 'needs-setup', {
86
- now: NOW
87
- }), 'needs-setup → down 事件');
67
+ for (const channel of ['flyai', 'hbcli-hotel', 'session:ctrip-hotel']) {
68
+ noteChannelVerdict(channel, 'needs-setup', { now: NOW });
69
+ }
70
+ assert.equal(routingAdvice('search-hotel', { now: NOW }).alternatives[0].channel, 'session:dida-portal', '前三个酒店通道 down 后 Dida 升为首荐');
71
+ noteChannelVerdict('session:dida-portal', 'needs-login', { now: NOW });
72
+ assert.equal(routingAdvice('search-hotel', { now: NOW }).alternatives[0].channel, 'web-read', 'Dida down 后推进到后继健康酒店通道');
73
+ console.log('3. routingAdvice(down 排除/发起排除/limit/hit 恢复 + Dida down→后继)OK');
74
+ // 4. noteChannelVerdict 映射闭集
75
+ resetChannelHealth();
76
+ assert.ok(noteChannelVerdict('x', 'needs-setup', { now: NOW }), 'needs-setup → down 事件');
88
77
  assert.equal(channelState('x', NOW)?.state, 'down');
89
- assert.equal(noteChannelVerdict('x', 'challenged', {
90
- now: NOW
91
- })?.reason, 'challenged', 'challenged → down');
92
- assert.equal(noteChannelVerdict('x', 'needs-login', {
93
- now: NOW
94
- })?.reason, 'needs-login', 'needs-login → down');
95
- assert.equal(noteChannelVerdict('x', 'needs-extension', {
96
- now: NOW
97
- })?.reason, 'needs-extension', 'needs-extension → down');
98
- assert.equal(noteChannelVerdict('x', 'miss', {
99
- now: NOW
100
- }), undefined, '业务 miss ≠ 通道故障');
101
- assert.equal(noteChannelVerdict('x', 'error', {
102
- now: NOW
103
- }), undefined, '业务 error ≠ 通道故障');
78
+ assert.equal(noteChannelVerdict('x', 'challenged', { now: NOW })?.reason, 'challenged', 'challenged → down');
79
+ assert.equal(noteChannelVerdict('x', 'needs-login', { now: NOW })?.reason, 'needs-login', 'needs-login → down');
80
+ assert.equal(noteChannelVerdict('x', 'needs-extension', { now: NOW })?.reason, 'needs-extension', 'needs-extension → down');
81
+ assert.equal(noteChannelVerdict('x', 'miss', { now: NOW }), undefined, '业务 miss ≠ 通道故障');
82
+ assert.equal(noteChannelVerdict('x', 'error', { now: NOW }), undefined, '业务 error ≠ 通道故障');
104
83
  clearChannel('x');
105
84
  assert.equal(channelState('x', NOW), undefined, '清除后无状态');
106
- const cd = noteChannelVerdict('y', 'cooldown', {
107
- now: NOW,
108
- cooldownMs: 30_000
109
- });
85
+ const cd = noteChannelVerdict('y', 'cooldown', { now: NOW, cooldownMs: 30_000 });
110
86
  assert.equal(cd?.state, 'cooldown', 'cooldown → cooldown 事件');
111
87
  assert.equal(channelState('y', NOW + 29_999)?.state, 'cooldown', '冷却未满仍在');
112
88
  assert.equal(channelState('y', NOW + 30_001), undefined, '冷却满惰性过期');
113
89
  console.log('4. noteChannelVerdict 映射闭集 OK');
90
+ // 5. persona 路由卡
114
91
  const card1 = renderRoutingCard(new Date(NOW));
115
92
  const card2 = renderRoutingCard(new Date(NOW));
116
93
  assert.equal(card1, card2, '同刻渲染确定');
@@ -121,130 +98,71 @@ assert.match(card1, /- 火车: .+12306 公开查询面.+kind=train/, '火车行
121
98
  assert.match(card1, /匿名试用=首次体验导流/, '额度口径脚注(D-7)');
122
99
  assert.ok(!card1.includes('- 天气'), '单通道意图不上卡');
123
100
  console.log('5. persona 路由卡 OK');
101
+ // 6. JSONL 持久面往返(事件时间用真实时钟——limitDays 窗口以读方时钟为基准)
124
102
  const stateRoot = await mkdtemp(join(tmpdir(), 'gotry-channel-test-'));
125
103
  const realNow = Date.now();
126
- await recordChannelEvent(stateRoot, {
127
- channel: 'flyai',
128
- state: 'down',
129
- reason: 'needs-setup',
130
- at: new Date(realNow - 86_400_000).toISOString()
131
- });
132
- await recordChannelEvent(stateRoot, {
133
- channel: 'flyai',
134
- state: 'down',
135
- reason: 'needs-setup',
136
- at: new Date(realNow).toISOString()
137
- });
138
- await recordChannelEvent(stateRoot, {
139
- channel: 'session:ctrip-flight',
140
- state: 'cooldown',
141
- at: new Date(realNow).toISOString()
142
- });
104
+ await recordChannelEvent(stateRoot, { channel: 'flyai', state: 'down', reason: 'needs-setup', at: new Date(realNow - 86_400_000).toISOString() });
105
+ await recordChannelEvent(stateRoot, { channel: 'flyai', state: 'down', reason: 'needs-setup', at: new Date(realNow).toISOString() });
106
+ await recordChannelEvent(stateRoot, { channel: 'session:ctrip-flight', state: 'cooldown', at: new Date(realNow).toISOString() });
143
107
  const latest = await readLatestChannelEvents(stateRoot);
144
108
  assert.equal(latest.get('flyai')?.at, new Date(realNow).toISOString(), '每通道取最新');
145
109
  assert.equal(latest.get('session:ctrip-flight')?.state, 'cooldown');
146
- await recordChannelEvent(stateRoot, {
147
- channel: 'legacy',
148
- state: 'down',
149
- at: new Date(realNow - 40 * 86_400_000).toISOString()
150
- });
151
- assert.ok(!(await readLatestChannelEvents(stateRoot)).has('legacy'), '40 天前事件被 limitDays 过滤');
110
+ await recordChannelEvent(stateRoot, { channel: 'legacy', state: 'down', at: new Date(realNow - 40 * 86_400_000).toISOString() });
111
+ assert.ok(!((await readLatestChannelEvents(stateRoot)).has('legacy')), '40 天前事件被 limitDays 过滤');
152
112
  const raw = `${stateRoot}/gotry-state/channel-health.jsonl`;
153
113
  await writeFile(raw, `${await readFile(raw, 'utf-8')}not-json\n{"half":true}\n`, 'utf-8');
154
114
  assert.equal((await readLatestChannelEvents(stateRoot)).size, 2, '坏行容忍');
155
115
  console.log('6. JSONL 持久面 OK');
116
+ // 7. doctor 集成:flyai 配额可见 + calendar 三态(setup 状态面,非 env)
156
117
  const emptyRepo = join(stateRoot, 'repo');
157
118
  const emptyHome = join(stateRoot, 'home');
158
- await mkdir(emptyRepo, {
159
- recursive: true
160
- });
161
- await mkdir(join(emptyHome, '.gotry'), {
162
- recursive: true
163
- });
119
+ await mkdir(emptyRepo, { recursive: true });
120
+ await mkdir(join(emptyHome, '.gotry'), { recursive: true });
164
121
  const calStatePath = join(emptyHome, '.gotry', 'calendar.json');
165
- const d1 = await runDoctorChecks({
166
- repoRoot: emptyRepo,
167
- homeDir: emptyHome,
168
- env: {},
169
- stateRoot
170
- });
171
- assert.match(d1.items.find((i)=>i.id === 'flyai').detail, /最近一次试用达限/, '最近达限时间进 doctor(#107 可见性)');
172
- assert.equal(d1.items.find((i)=>i.id === 'calendar').status, 'ok', 'calendar 默认不挂载=ok(D-9)');
173
- assert.match(d1.items.find((i)=>i.id === 'calendar').detail, /默认未挂载/, '默认态人话');
174
- await writeFile(calStatePath, JSON.stringify({
175
- enabled: true
176
- }), 'utf-8');
177
- const d2 = await runDoctorChecks({
178
- repoRoot: emptyRepo,
179
- homeDir: emptyHome,
180
- env: {}
181
- });
182
- assert.equal(d2.items.find((i)=>i.id === 'calendar').status, 'degraded', 'setup 开启但未配置 username=degraded');
183
- assert.match(d2.items.find((i)=>i.id === 'calendar').fix ?? '', /npx gotry setup calendar/, 'fix 指向 setup 状态面');
122
+ const d1 = await runDoctorChecks({ repoRoot: emptyRepo, homeDir: emptyHome, env: {}, stateRoot });
123
+ assert.match(d1.items.find(i => i.id === 'flyai').detail, /最近一次试用达限/, '最近达限时间进 doctor(#107 可见性)');
124
+ assert.equal(d1.items.find(i => i.id === 'calendar').status, 'ok', 'calendar 默认不挂载=ok(D-9)');
125
+ assert.match(d1.items.find(i => i.id === 'calendar').detail, /默认未挂载/, '默认态人话');
126
+ await writeFile(calStatePath, JSON.stringify({ enabled: true }), 'utf-8');
127
+ const d2 = await runDoctorChecks({ repoRoot: emptyRepo, homeDir: emptyHome, env: {} });
128
+ assert.equal(d2.items.find(i => i.id === 'calendar').status, 'degraded', 'setup 开启但未配置 username=degraded');
129
+ assert.match(d2.items.find(i => i.id === 'calendar').fix ?? '', /npx @danceiny\/gotry setup calendar/, 'fix 指向 setup 状态面');
184
130
  const profileDir = join(emptyHome, '.dsh/profiles/web');
185
- await mkdir(profileDir, {
186
- recursive: true
187
- });
131
+ await mkdir(profileDir, { recursive: true });
188
132
  await writeFile(join(profileDir, 'cordis.patch.yml'), '- id: dsh-calendar\n config:\n username: someone\n', 'utf-8');
189
- const d3 = await runDoctorChecks({
190
- repoRoot: emptyRepo,
191
- homeDir: emptyHome,
192
- env: {}
193
- });
194
- assert.equal(d3.items.find((i)=>i.id === 'calendar').status, 'ok', 'setup 开启且已配置=ok');
195
- const d4 = await runDoctorChecks({
196
- repoRoot: emptyRepo,
197
- homeDir: emptyHome,
198
- env: {}
199
- });
200
- assert.ok(!/最近一次试用达限/.test(d4.items.find((i)=>i.id === 'flyai').detail), '无 stateRoot 不读持久面(向后兼容)');
201
- const mapItem = d4.items.find((i)=>i.id === 'map-tools');
202
- const askItem = d4.items.find((i)=>i.id === 'ask-user');
133
+ const d3 = await runDoctorChecks({ repoRoot: emptyRepo, homeDir: emptyHome, env: {} });
134
+ assert.equal(d3.items.find(i => i.id === 'calendar').status, 'ok', 'setup 开启且已配置=ok');
135
+ const d4 = await runDoctorChecks({ repoRoot: emptyRepo, homeDir: emptyHome, env: {} });
136
+ assert.ok(!/最近一次试用达限/.test(d4.items.find(i => i.id === 'flyai').detail), '无 stateRoot 不读持久面(向后兼容)');
137
+ // patch 宿主插件两态照亮(issue #113):隔离环境两者皆 missing——静默剔除面进 doctor 可见
138
+ const mapItem = d4.items.find(i => i.id === 'map-tools');
139
+ const askItem = d4.items.find(i => i.id === 'ask-user');
203
140
  assert.equal(mapItem.status, 'missing', '隔离环境 map-tools=missing(静默剔除面可见)');
204
141
  assert.match(mapItem.detail, /静默剔除/, 'map-tools detail 说明静默剔除语义');
205
142
  assert.equal(askItem.status, 'missing', '隔离环境 ask-user=missing');
206
143
  assert.match(askItem.detail, /散文追问/, 'ask-user detail 说明退化面');
207
144
  console.log('7. doctor 集成(flyai 配额可见 + calendar setup 状态面三态 + 宿主插件两态)OK');
145
+ // 8. apply 接线:变量注册 + 参数级拒绝不带 routing
208
146
  resetChannelHealth();
209
147
  const variables = {};
210
148
  const registered = [];
211
149
  const ctx = {
212
- tools: {
213
- register: (t)=>registered.push(t)
214
- },
215
- systemPrompt: {
216
- variable: (name, provider)=>{
217
- variables[name] = provider;
218
- }
219
- },
220
- on: ()=>()=>{}
150
+ tools: { register: (t) => registered.push(t) },
151
+ systemPrompt: { variable: (name, provider) => { variables[name] = provider; } },
152
+ on: () => () => { },
221
153
  };
222
- apply(ctx, {
223
- stateRoot,
224
- timeoutMs: 30_000,
225
- hbcliBin: 'hbcli-not-on-path',
226
- sessionAccess: 'ask'
227
- });
154
+ apply(ctx, { stateRoot, timeoutMs: 30_000, hbcliBin: 'hbcli-not-on-path', sessionAccess: 'ask' });
228
155
  assert.equal(typeof variables.channel_routing_card, 'function', 'channel_routing_card 变量已注册');
229
156
  assert.match(variables.channel_routing_card(), /检索通道顺位/, '卡片可渲染');
230
- const pastDate = await registered.find((t)=>t.name === 'gotry_flyai_search').execute({
231
- kind: 'flight',
232
- from: '深圳',
233
- to: '普吉',
234
- date: '2026-01-01'
235
- }, null);
157
+ const pastDate = await registered.find(t => t.name === 'gotry_flyai_search').execute({ kind: 'flight', from: '深圳', to: '普吉', date: '2026-01-01' }, null);
236
158
  assert.ok(!('routing' in pastDate), '参数级预校验拒绝不带 routing(未发起检索,无改道语义)');
237
- const legacyBlob = await registered.find((t)=>t.name === 'gotry_flyai_search').execute({
238
- query: {
239
- kind: 'flight',
240
- from: '深圳',
241
- to: '普吉',
242
- date: '2026-01-01'
243
- }
244
- }, null);
159
+ // D-30 迁移锁(issue #112):typed 平铺契约下 legacy blob 包裹在宿主权校验即拒绝,
160
+ // 拒绝形状保持 ADR-13 ToolFailure(guardToolExecute 兜底:ok:false + evidence)且不带 routing
161
+ const legacyBlob = await registered.find(t => t.name === 'gotry_flyai_search').execute({ query: { kind: 'flight', from: '深圳', to: '普吉', date: '2026-01-01' } }, null);
245
162
  assert.ok(legacyBlob.ok === false && !('routing' in legacyBlob) && !!legacyBlob.evidence, 'legacy blob → 结构化拒绝(ToolFailure,无 routing)');
246
- const flyaiTool = registered.find((t)=>t.name === 'gotry_flyai_search');
247
- const doctorToolDesc = registered.find((t)=>t.name === 'gotry_doctor');
163
+ // 工具描述首行生成(issue #113):检索工具前置注册表生成的意图顺位卡;非检索工具原样
164
+ const flyaiTool = registered.find(t => t.name === 'gotry_flyai_search');
165
+ const doctorToolDesc = registered.find(t => t.name === 'gotry_doctor');
248
166
  assert.ok(flyaiTool, 'flyai 工具已注册');
249
167
  assert.match(toolRoutingHeadline('gotry_flyai_search'), /机票: 飞猪官方 API\(gotry_flyai_search\) → 携程·用户登录态/, 'flyai 首行卡:机票顺位 flyai 在前(证据级)');
250
168
  assert.match(toolRoutingHeadline('gotry_session_search'), /火车: .*12306 公开查询面\(gotry_session_search kind=train\)/, 'session 首行卡带 kind 消歧');
@@ -252,11 +170,5 @@ assert.equal(toolRoutingHeadline('gotry_doctor'), '', '非检索工具无首行
252
170
  assert.ok(flyaiTool.description?.startsWith('本工具服务的检索意图与通道顺位'), 'flyai 描述已前置首行卡(接线生效)');
253
171
  assert.ok(!doctorToolDesc.description?.startsWith('本工具服务的检索意图'), 'doctor 描述无首行卡(接线不误伤)');
254
172
  console.log('8. apply 接线 + 描述首行生成 OK');
255
- await rm(stateRoot, {
256
- recursive: true,
257
- force: true
258
- });
173
+ await rm(stateRoot, { recursive: true, force: true });
259
174
  console.log('channel-registry-tests: 全部通过');
260
-
261
-
262
- //# sourceURL=ts/scripts/channel-registry-tests.ts
@@ -1,3 +1,8 @@
1
+ /**
2
+ * 同行人档案单测(memory-design P2,companion_profile.v1):
3
+ * 负面清单守卫(证件/电话零入库)/upsert 合并(追加不删史/幂等)/brief 摘要形状。
4
+ * 运行:cd ts && npx tsx scripts/companion-tests.ts
5
+ */
1
6
  import assert from 'node:assert/strict';
2
7
  import { companionId, sensitiveViolation, upsertCompanion } from '../src/companions.js';
3
8
  let n = 0;
@@ -5,108 +10,58 @@ function pass(name, body) {
5
10
  body();
6
11
  console.log(` ${++n}. ${name} OK`);
7
12
  }
8
- pass('负面清单守卫:证件号/手机号拒收入库(红线 6 工程形态)', ()=>{
13
+ pass('负面清单守卫:证件号/手机号拒收入库(红线 6 工程形态)', () => {
9
14
  assert.ok(sensitiveViolation('护照号 E12345678')?.includes('负面清单'), '证件拒');
10
15
  assert.ok(sensitiveViolation('我电话 13812345678')?.includes('负面清单'), '手机号拒');
11
16
  assert.equal(sensitiveViolation('爸爸65,轻度高血压,走不动山路'), null, '行为约束正常');
12
17
  const r = upsertCompanion([], {
13
18
  label: '爸爸',
14
- constraints: {
15
- health: [
16
- '轻度高血压'
17
- ]
18
- },
19
- evidence: '身份证号 110101199001011234,轻度高血压'
19
+ constraints: { health: ['轻度高血压'] },
20
+ evidence: '身份证号 110101199001011234,轻度高血压',
20
21
  });
21
22
  assert.equal(r.appended, false);
22
23
  assert.ok(r.reason?.includes('负面清单'));
23
24
  });
24
- pass('新建:约束+证据落库,companion_id 语义派生', ()=>{
25
+ pass('新建:约束+证据落库,companion_id 语义派生', () => {
25
26
  const r = upsertCompanion([], {
26
27
  label: '爸 爸',
27
- constraints: {
28
- health: [
29
- '轻度高血压'
30
- ],
31
- mobility: '步行≤4h'
32
- },
33
- evidence: '爸爸65有轻度高血压,别太累'
28
+ constraints: { health: ['轻度高血压'], mobility: '步行≤4h' },
29
+ evidence: '爸爸65有轻度高血压,别太累',
34
30
  });
35
31
  assert.equal(r.appended, true);
36
32
  assert.equal(r.companionId, '爸爸');
37
33
  const p = r.profiles[0];
38
- assert.deepEqual(p.constraints.health, [
39
- '轻度高血压'
40
- ]);
41
- assert.deepEqual(p.evidence, [
42
- '爸爸65有轻度高血压,别太累'
43
- ]);
34
+ assert.deepEqual(p.constraints.health, ['轻度高血压']);
35
+ assert.deepEqual(p.evidence, ['爸爸65有轻度高血压,别太累']);
44
36
  });
45
- pass('upsert 合并:数组追加不删史/覆盖取新/幂等', ()=>{
37
+ pass('upsert 合并:数组追加不删史/覆盖取新/幂等', () => {
46
38
  let ps = [];
47
39
  ps = upsertCompanion(ps, {
48
40
  label: '爸爸',
49
- constraints: {
50
- health: [
51
- '轻度高血压'
52
- ],
53
- mobility: '步行≤4h'
54
- },
55
- evidence: '原话一'
41
+ constraints: { health: ['轻度高血压'], mobility: '步行≤4h' },
42
+ evidence: '原话一',
56
43
  }).profiles;
57
44
  ps = upsertCompanion(ps, {
58
45
  label: '爸爸',
59
- constraints: {
60
- health: [
61
- '晕车'
62
- ],
63
- mobility: '步行≤3h'
64
- },
65
- evidence: '原话二:晕车'
46
+ constraints: { health: ['晕车'], mobility: '步行≤3h' },
47
+ evidence: '原话二:晕车',
66
48
  }).profiles;
67
- const p = ps.find((x)=>x.companion_id === '爸爸');
68
- assert.deepEqual(p.constraints.health, [
69
- '轻度高血压',
70
- '晕车'
71
- ], '数组追加不删史');
49
+ const p = ps.find(x => x.companion_id === '爸爸');
50
+ assert.deepEqual(p.constraints.health, ['轻度高血压', '晕车'], '数组追加不删史');
72
51
  assert.equal(p.constraints.mobility, '步行≤3h', '标量取新值');
73
52
  assert.equal(p.evidence.length, 2, '证据两条');
74
53
  const r3 = upsertCompanion(ps, {
75
54
  label: '爸爸',
76
- constraints: {
77
- health: [
78
- '晕车'
79
- ],
80
- mobility: '步行≤3h'
81
- },
82
- evidence: '原话二:晕车'
55
+ constraints: { health: ['晕车'], mobility: '步行≤3h' },
56
+ evidence: '原话二:晕车',
83
57
  });
84
58
  assert.equal(r3.appended, false, '全量相同幂等 no-op');
85
59
  });
86
- pass('多人并存与 id 稳定性', ()=>{
60
+ pass('多人并存与 id 稳定性', () => {
87
61
  let ps = [];
88
- ps = upsertCompanion(ps, {
89
- label: '爸爸',
90
- constraints: {
91
- health: [
92
- '高血压'
93
- ]
94
- },
95
- evidence: 'a'
96
- }).profiles;
97
- ps = upsertCompanion(ps, {
98
- label: '女朋友',
99
- constraints: {
100
- prefs: [
101
- '怕吵'
102
- ]
103
- },
104
- evidence: 'b'
105
- }).profiles;
62
+ ps = upsertCompanion(ps, { label: '爸爸', constraints: { health: ['高血压'] }, evidence: 'a' }).profiles;
63
+ ps = upsertCompanion(ps, { label: '女朋友', constraints: { prefs: ['怕吵'] }, evidence: 'b' }).profiles;
106
64
  assert.equal(ps.length, 2);
107
65
  assert.equal(companionId('女 朋友'), '女朋友');
108
66
  });
109
67
  console.log(`\nCOMPANION TESTS: ${n}/4 OK(memory-design P2 守门面)`);
110
-
111
-
112
- //# sourceURL=ts/scripts/companion-tests.ts
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Dida 会话 cookie 形态探针(只打印 name/domain,永不打印值——红线)。
3
+ * 复用 live E2E 的启动与登录流程,登录后枚举 dida 相关 cookie 的名字与域。
4
+ */
5
+ import { spawn } from 'node:child_process';
6
+ import { mkdtempSync, rmSync } from 'node:fs';
7
+ import { tmpdir } from 'node:os';
8
+ import { join } from 'node:path';
9
+ import { chromium } from 'playwright-core';
10
+ const EXT_DIR = new URL('../../extension/', import.meta.url).pathname;
11
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
12
+ async function main() {
13
+ const user = process.env.DIDA_USERNAME ?? '';
14
+ const pass = process.env.DIDA_PASSWORD ?? '';
15
+ if (!user || !pass) {
16
+ console.error('DIDA_USERNAME/DIDA_PASSWORD 必填');
17
+ process.exit(1);
18
+ }
19
+ const profileDir = mkdtempSync(join(tmpdir(), 'gotry-dida-probe-'));
20
+ const chrome = spawn('/Applications/Google Chrome.app/Contents/MacOS/Google Chrome', [
21
+ '--remote-debugging-port=9234', `--user-data-dir=${profileDir}`, '--no-first-run',
22
+ `--disable-extensions-except=${EXT_DIR}`, `--load-extension=${EXT_DIR}`, 'about:blank',
23
+ ], { stdio: 'ignore' });
24
+ const cleanup = () => { try {
25
+ chrome.kill();
26
+ }
27
+ catch { } try {
28
+ rmSync(profileDir, { recursive: true, force: true });
29
+ }
30
+ catch { } };
31
+ process.on('exit', cleanup);
32
+ await sleep(10_000);
33
+ const browser = await chromium.connectOverCDP('http://127.0.0.1:9234');
34
+ const context = browser.contexts()[0];
35
+ const page = await context.newPage();
36
+ await page.goto('https://portal.dida.com/login', { waitUntil: 'networkidle', timeout: 45_000 });
37
+ await page.evaluate(`(() => {
38
+ document.querySelectorAll('#onetrust-consent-sdk, .nd-cookie-consent, .cdk-overlay-backdrop, .cdk-overlay-container').forEach(el => el.remove())
39
+ })()`).catch(() => { });
40
+ await page.waitForSelector('input[name="username"]', { timeout: 20_000 });
41
+ await page.evaluate(`(() => {
42
+ const u = document.querySelector('input[name="username"]')
43
+ const p = document.querySelector('input[name="password"]')
44
+ const setVal = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set
45
+ setVal.call(u, ${JSON.stringify(user)}); u.dispatchEvent(new Event('input', { bubbles: true }))
46
+ setVal.call(p, ${JSON.stringify(pass)}); p.dispatchEvent(new Event('input', { bubbles: true }))
47
+ document.querySelectorAll('.ant-checkbox-wrapper, label[class*="checkbox"]').forEach(w => w.click())
48
+ })()`);
49
+ await sleep(500);
50
+ await page.evaluate(`(() => {
51
+ Array.from(document.querySelectorAll('button')).find(b => b.textContent.trim().toLowerCase() === 'login')?.click()
52
+ })()`);
53
+ try {
54
+ await page.waitForURL('**/hotel/**', { timeout: 60_000 });
55
+ }
56
+ catch {
57
+ console.log('⚠️ 未跳转——等待人工完成验证码 60s');
58
+ try {
59
+ await page.waitForURL('**/hotel/**', { timeout: 60_000 });
60
+ }
61
+ catch { }
62
+ }
63
+ await sleep(4_000);
64
+ console.log('final url:', page.url());
65
+ // 全上下文 cookie(只 name+domain+httpOnly/partitioned 元数据;值不落任何输出)
66
+ const cookies = await context.cookies();
67
+ const dida = cookies.filter((c) => c.domain.includes('dida'));
68
+ console.log(`全上下文 cookie ${cookies.length} 条;dida 相关 ${dida.length} 条:`);
69
+ for (const c of dida) {
70
+ console.log(` - ${c.name} domain=${c.domain} httpOnly=${c.httpOnly} partitioned=${c.partitioned ?? false} expires=${c.expires > 0 ? new Date(c.expires * 1000).toISOString() : 'session'}`);
71
+ }
72
+ // 页面内非 HttpOnly 视角
73
+ const jsNames = await page.evaluate(`(() => document.cookie.split(';').map(s => s.trim().split('=')[0]).filter(Boolean))()`);
74
+ console.log('document.cookie 可见名(非 HttpOnly):', JSON.stringify(jsNames));
75
+ // 认证态校验
76
+ const authed = await page.evaluate(`(async () => {
77
+ try {
78
+ const r = await fetch('https://portal-webapi.dida.com/ApplicationContextApi/GetContext?init=true', { credentials: 'include' })
79
+ const j = await r.json()
80
+ return { success: j?.Success, code: j?.MessageCode, authed: j?.Data?.userIdentityInfo?.isAuthenticated }
81
+ } catch (e) { return { err: String(e) } }
82
+ })()`);
83
+ console.log('GetContext:', JSON.stringify(authed));
84
+ await browser.close();
85
+ process.exit(0);
86
+ }
87
+ main().catch((e) => { console.error('Fatal:', e); process.exit(1); });