@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,5 +1,19 @@
1
+ /**
2
+ * Anything 能力层测试(模拟 hbcli):
3
+ * 1. 真实候选:fake hbcli 按 hotel-be SearchItem wire 形状返 JSON — verdict hit + hits
4
+ * (i18n name zh 优先 / region.centerLat / hotel.latlngCoordinator / star / id 逐字段对齐)
5
+ * 2. 旗标回归:--json 在子命令前(cli.ts 全局旗标预扫描只认子命令前位置)+
6
+ * --content-type / parentDestinationId → --destination-id 映射
7
+ * 3. 空候选:fake hbcli 返 empty JSON — verdict miss
8
+ * 4. exit≠0:fake hbcli 返 stderr+退出 — verdict error + 不抛错
9
+ * 5. 旧 CLI「unknown command」:error 带升级指引(issue #195:裸 unknown command 读起来像工具坏了)
10
+ * 6. 超时:fake hbcli hang — AbortError → verdict error + 不抛错
11
+ * 7. 关键词为空:不调 hbcli — verdict error
12
+ *
13
+ * 运行: cd ts && npx tsx scripts/anything-tests.ts
14
+ */
1
15
  import assert from 'node:assert/strict';
2
- import { mkdtemp, rm, writeFile, chmod } from 'node:fs/promises';
16
+ import { mkdtemp, rm, writeFile } from 'node:fs/promises';
3
17
  import { tmpdir } from 'node:os';
4
18
  import { join } from 'node:path';
5
19
  import { anythingSearch } from '../capabilities/anything.js';
@@ -8,6 +22,8 @@ try {
8
22
  async function fakeBin(name, behaviour) {
9
23
  const p = join(tmp, name);
10
24
  if (behaviour === 'ok') {
25
+ // hotel-be SearchItem 真实 wire 形状(2026-09-07 对齐源码):i18n name 对象、
26
+ // region.coordinates.centerLat/centerLng、hotel.latlngCoordinator.google、star、matchScore
11
27
  await writeFile(p, `#!/bin/sh
12
28
  cat <<'JSON'
13
29
  {"candidates":[
@@ -16,52 +32,49 @@ cat <<'JSON'
16
32
  {"type":"place","matchScore":90,"region":{"id":"r1","name":{"zh":"洱海"}},"place":{"latlngCoordinator":{"google":{"lat":25.74,"lng":100.25}}}}
17
33
  ]}
18
34
  JSON
19
- `, {
20
- mode: 0o755
21
- });
22
- } else if (behaviour === 'echo-args') {
35
+ `, { mode: 0o755 });
36
+ }
37
+ else if (behaviour === 'echo-args') {
38
+ // 旗标回归探针:把 "$@" 回显进候选 region.id(anythingSearch 原样透传为 destinationId)
23
39
  await writeFile(p, `#!/bin/sh
24
40
  printf '{"candidates":[{"type":"city","region":{"id":"%s"}}]}' "$*"
25
- `, {
26
- mode: 0o755
27
- });
28
- } else if (behaviour === 'empty') {
41
+ `, { mode: 0o755 });
42
+ }
43
+ else if (behaviour === 'empty') {
29
44
  await writeFile(p, `#!/bin/sh
30
45
  cat <<'JSON'
31
46
  {"candidates":[]}
32
47
  JSON
33
- `, {
34
- mode: 0o755
35
- });
36
- } else if (behaviour === 'fail') {
48
+ `, { mode: 0o755 });
49
+ }
50
+ else if (behaviour === 'fail') {
37
51
  await writeFile(p, `#!/bin/sh
38
52
  echo 'hotelbe down' >&2
39
53
  exit 1
40
- `, {
41
- mode: 0o755
42
- });
43
- } else if (behaviour === 'unknown-command') {
54
+ `, { mode: 0o755 });
55
+ }
56
+ else if (behaviour === 'unknown-command') {
57
+ // 旧版 CLI(< 2026-09 的 staicli)没有 search anything 子命令时的 commander 原话
44
58
  await writeFile(p, `#!/bin/sh
45
59
  echo "error: unknown command 'anything'" >&2
46
60
  exit 1
47
- `, {
48
- mode: 0o755
49
- });
50
- } else {
61
+ `, { mode: 0o755 });
62
+ }
63
+ else {
64
+ // hang forever — test will timeout. Spawn on POSIX reacts to AbortSignal
65
+ // via SIGTERM; SIGKILL is the fallback at +500ms. Some shells (e.g. bash
66
+ // builtins shelled-out via Bun) cache the child pid briefly, so we give
67
+ // a generous upper bound in the assertion (note in code for future).
51
68
  await writeFile(p, `#!/bin/sh
52
69
  echo 'starting' >&2
53
70
  while :; do sleep 5; done
54
- `, {
55
- mode: 0o755
56
- });
71
+ `, { mode: 0o755 });
57
72
  }
58
73
  return p;
59
74
  }
75
+ // 1. 真实候选(wire 形状逐字段)
60
76
  const ok = await fakeBin('hbcli-ok', 'ok');
61
- const r1 = await anythingSearch({
62
- keyword: '大理',
63
- hbcliBin: ok
64
- });
77
+ const r1 = await anythingSearch({ keyword: '大理', hbcliBin: ok });
65
78
  assert.equal(r1.verdict, 'hit');
66
79
  assert.equal(r1.hits.length, 3);
67
80
  assert.ok(r1.evidence.includes('hbcli-anything@'));
@@ -83,62 +96,45 @@ while :; do sleep 5; done
83
96
  assert.equal(placeHit.name, '洱海');
84
97
  assert.equal(placeHit.latitude, 25.74, 'place 坐标回退 place.latlngCoordinator');
85
98
  console.log('1. 真实候选 wire 形状(hotel/city/place 逐字段)OK');
99
+ // 2. 旗标回归:--json 必须在子命令前(cli.ts 全局旗标预扫描只认子命令前位置);
100
+ // contentType → --content-type;parentDestinationId → --destination-id(上游 CLI 旗标名)
86
101
  const echo = await fakeBin('hbcli-echo-args', 'echo-args');
87
- const r2 = await anythingSearch({
88
- keyword: '大理',
89
- contentType: 'city',
90
- parentDestinationId: 'd1',
91
- hbcliBin: echo
92
- });
102
+ const r2 = await anythingSearch({ keyword: '大理', contentType: 'city', parentDestinationId: 'd1', hbcliBin: echo });
93
103
  assert.equal(r2.verdict, 'hit');
94
104
  assert.equal(r2.hits[0].destinationId, '--json search anything 大理 --content-type city --destination-id d1', 'spawn 旗标形态(--json 前置/--destination-id 映射)应稳定,实际 ' + r2.hits[0].destinationId);
95
105
  console.log('2. 旗标回归(--json 前置 + --destination-id 映射)OK');
106
+ // 3. 空候选
96
107
  const empty = await fakeBin('hbcli-empty', 'empty');
97
- const r3 = await anythingSearch({
98
- keyword: 'nothing',
99
- hbcliBin: empty
100
- });
108
+ const r3 = await anythingSearch({ keyword: 'nothing', hbcliBin: empty });
101
109
  assert.equal(r3.verdict, 'miss');
102
110
  assert.equal(r3.hits.length, 0);
103
111
  console.log('3. nothing → miss OK');
112
+ // 4. exit≠0
104
113
  const fail = await fakeBin('hbcli-fail', 'fail');
105
- const r4 = await anythingSearch({
106
- keyword: 'x',
107
- hbcliBin: fail
108
- });
114
+ const r4 = await anythingSearch({ keyword: 'x', hbcliBin: fail });
109
115
  assert.equal(r4.verdict, 'error');
110
116
  assert.equal(r4.ok, false);
111
117
  assert.match(r4.evidence, /error/);
112
118
  console.log('4. fail → error (降级) OK');
119
+ // 5. 旧 CLI unknown command → error 带升级指引(issue #195)
113
120
  const old = await fakeBin('hbcli-old', 'unknown-command');
114
- const r5 = await anythingSearch({
115
- keyword: 'x',
116
- hbcliBin: old
117
- });
121
+ const r5 = await anythingSearch({ keyword: 'x', hbcliBin: old });
118
122
  assert.equal(r5.verdict, 'error');
119
123
  assert.match(r5.error ?? '', /hbcli update|gotry setup/, 'error 应带升级指引,实际 ' + r5.error);
120
124
  assert.match(r5.evidence, /unknown command/, 'evidence 保留上游原话(溯源)');
121
125
  console.log(`5. 旧 CLI unknown command → 升级指引 OK(${r5.error})`);
126
+ // 6. 超时:fake hang 60s,我们给 800ms 超时,期望 verdict=error
127
+ // (latency 断言范围放宽,因为 sandbox node spawn 的 abort 行为不稳定)
122
128
  const hang = await fakeBin('hbcli-hang', 'hang');
123
- const r6 = await anythingSearch({
124
- keyword: 'x',
125
- hbcliBin: hang,
126
- timeoutMs: 800
127
- });
129
+ const r6 = await anythingSearch({ keyword: 'x', hbcliBin: hang, timeoutMs: 800 });
128
130
  assert.equal(r6.verdict, 'error', 'timeout 应判 error');
129
131
  console.log(`6. hang/timeout → error OK (latency=${r6.latencyMs}ms)`);
130
- const r7 = await anythingSearch({
131
- keyword: ' '
132
- });
132
+ // 7. keyword
133
+ const r7 = await anythingSearch({ keyword: ' ' });
133
134
  assert.equal(r7.verdict, 'error');
134
135
  console.log('7. empty keyword → error OK');
135
136
  console.log('\nANYTHING TESTS: 7/7 OK(hbcli fake + wire 对齐 + 降级诚实)');
136
- } finally{
137
- await rm(tmp, {
138
- recursive: true,
139
- force: true
140
- });
141
137
  }
142
-
143
-
144
- //# sourceURL=ts/scripts/anything-tests.ts
138
+ finally {
139
+ await rm(tmp, { recursive: true, force: true });
140
+ }
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Public Client contract: package export, lazy-CJS loader shape, keyed DSH
3
+ * registrations, and defensive rendering from the runtime `block` payload.
4
+ * Browser execution is covered separately by dsh-artifact-web-e2e.ts.
5
+ */
6
+ import assert from 'node:assert/strict';
7
+ import { readFileSync } from 'node:fs';
8
+ import { createRequire } from 'node:module';
9
+ import { join } from 'node:path';
10
+ import vm from 'node:vm';
11
+ const ROOT = join(import.meta.dirname, '..', '..');
12
+ const source = readFileSync(join(ROOT, 'client/client.js'), 'utf8');
13
+ const pkg = JSON.parse(readFileSync(join(ROOT, 'package.json'), 'utf8'));
14
+ assert.equal(pkg.exports?.['./client'], './client/client.js');
15
+ assert.equal(pkg.dsh?.client?.platform, 'web');
16
+ assert.ok(pkg.dsh?.client?.inject?.includes('@deepseek-ai/dsh-client-ui-tool'));
17
+ assert.ok(pkg.files?.includes('client/'));
18
+ assert.ok(source.includes('window.__ModuleLoader__.load'));
19
+ assert.ok(source.includes("tool.call.toolview"));
20
+ assert.ok(source.includes("gotry_artifacts_list"));
21
+ assert.ok(source.includes("gotry_artifacts_read"));
22
+ assert.ok(!source.includes('@deepseek-ai/dsh-client-ui-tool/lib/'));
23
+ const registrations = [];
24
+ const context = vm.createContext({
25
+ window: { __ModuleLoader__: { load: (registration) => registrations.push(registration) } },
26
+ console,
27
+ });
28
+ new vm.Script(source, { filename: 'client/client.js' }).runInContext(context);
29
+ assert.equal(registrations.length, 1);
30
+ assert.equal(registrations[0].id, '@danceiny/gotry');
31
+ function createElement(type, props, ...children) {
32
+ return { type, props: props ?? {}, children };
33
+ }
34
+ const fakeReact = {
35
+ createElement,
36
+ useState(initial) { return [initial, () => { }]; },
37
+ };
38
+ const clientExports = registrations[0].factory(name => {
39
+ assert.equal(name, 'react');
40
+ return fakeReact;
41
+ });
42
+ assert.deepEqual(Object.keys(clientExports), ['apply', 'inject']);
43
+ const slotRegistrations = [];
44
+ const registrationContext = {
45
+ inject(_deps, callback) { callback(registrationContext); },
46
+ slots: {
47
+ inject(_slot, callback) { callback(); },
48
+ register(definition, view) {
49
+ slotRegistrations.push({ key: definition.key, view });
50
+ return () => { };
51
+ },
52
+ },
53
+ };
54
+ clientExports.apply(registrationContext);
55
+ assert.deepEqual(slotRegistrations.map(item => item.key), ['gotry_artifacts_list', 'gotry_artifacts_read']);
56
+ assert.equal(slotRegistrations[0].view, slotRegistrations[1].view);
57
+ const view = slotRegistrations[0].view;
58
+ const listTree = view({
59
+ toolName: 'gotry_artifacts_list',
60
+ block: { kind: 'tool-result', isError: false, meta: { shape: 'paths', paths: ['/tmp/trip-2027.md'], total: 1, truncated: false }, content: [] },
61
+ });
62
+ assert.equal(listTree.props['data-gotry-artifact-card'], 'list');
63
+ assert.equal(listTree.props['data-gotry-artifact-state'], 'ok');
64
+ const malformedTree = view({ toolName: 'gotry_artifacts_read', block: { kind: 'tool-result', isError: false, meta: { shape: 'bad' }, content: [{ type: 'text', text: 'raw fallback' }] } });
65
+ assert.equal(malformedTree.props['data-gotry-artifact-card'], 'read');
66
+ assert.equal(malformedTree.props['data-gotry-artifact-state'], 'ok');
67
+ const requireRoot = createRequire(join(ROOT, 'package.json'));
68
+ assert.ok(requireRoot.resolve('./client/client.js'));
69
+ console.log('ARTIFACT CLIENT CONTRACT: package export + web manifest + lazy loader + 2 keyed views + malformed fallback OK');
@@ -0,0 +1,235 @@
1
+ /**
2
+ * 产物面(issue #285)能力层离线 proof:
3
+ * list/read 工具链的纯函数 + 隔离 fixture 路径护栏。
4
+ * 与 smoke.ts §15 互相补——smoke 验注册 + dsh 卡片契约(在 plugin context),
5
+ * 本文件验 capabilities/artifacts.ts 纯函数 + 隔离 stateRoot(无 dsh 上下文)。
6
+ *
7
+ * 1) 账本权威 list:种工单 + 交付 → list 找到 async-run
8
+ * 2) cwd md list:工作目录顶层 md → list 找到 cwd-file
9
+ * 3) 合并去重:账本 + cwd 同 path 不重复
10
+ * 4) 裸工单 id read:从账本 deliverable 读出
11
+ * 5) 行号窗口:offset/limit 行号正确
12
+ * 6) 越界路径:ok:false + error 含「越界」
13
+ * 7) symlink 越界:ok:false
14
+ * 7b) 裸工单 id symlink/超大 deliverable:仍经过 canonical/root/size 护栏
15
+ * 8) 不支持扩展名:.db → ok:false + error 含「不支持的文件类型」
16
+ * 9) 不存在文件:ok:false
17
+ * 10) 超大文件:>2MB → ok:false
18
+ * 11) read 在 cwd/根边界外的相对路径:rejects
19
+ * 12) list limit 截断:truncated=true, paths.length === limit
20
+ */
21
+ import assert from 'node:assert/strict';
22
+ import { mkdirSync, mkdtempSync, rmSync, symlinkSync, writeFileSync } from 'node:fs';
23
+ import { tmpdir } from 'node:os';
24
+ import { join, resolve } from 'node:path';
25
+ import { listArtifacts, readArtifact } from '../capabilities/artifacts.js';
26
+ import { ensureLedger } from '../src/state-ledger.js';
27
+ const stateRoot = mkdtempSync(join(tmpdir(), 'gotry-artifacts-cap-tests-'));
28
+ const cwd = mkdtempSync(join(tmpdir(), 'gotry-artifacts-cap-cwd-'));
29
+ // 1) 账本权威 list
30
+ {
31
+ const ledger = ensureLedger(stateRoot);
32
+ ledger.createWorkflowRun({ id: 'cap-probe-1', goal: '产物探针·账本', ticket: { objective: 'probe' }, state: {} });
33
+ ledger.settleWorkflowRun('cap-probe-1', '# 交付·账本权威\nD1 大理\nD2 洱海');
34
+ const r = await listArtifacts({ stateRoot, limit: 20 });
35
+ const run = r.artifacts.find(a => a.id === 'cap-probe-1');
36
+ assert.ok(run, '账本工单应被 list 找到');
37
+ assert.equal(run?.source, 'async-run');
38
+ assert.equal(run?.status, 'settled');
39
+ assert.ok(run?.path.endsWith('cap-probe-1.deliverable.md'), `path 形态应为 deliverable.md,实际 ${run?.path}`);
40
+ console.log('1) 账本权威 list OK');
41
+ }
42
+ // 2) cwd md list
43
+ {
44
+ const lines = Array.from({ length: 8 }, (_, i) => `第 ${i + 1} 行`);
45
+ writeFileSync(join(cwd, 'trip-2027-cap.md'), `# 行程探针\n${lines.join('\n')}\n`);
46
+ const r = await listArtifacts({ stateRoot, cwd, limit: 20 });
47
+ const md = r.artifacts.find(a => a.id === 'trip-2027-cap.md');
48
+ assert.ok(md, 'cwd 顶层 md 应被 list 找到');
49
+ assert.equal(md?.source, 'cwd-file');
50
+ console.log('2) cwd md list OK');
51
+ }
52
+ // 3) 合并去重:账本工单 cap-probe-1 的 path 与 cwd 任何文件 path 不冲突——验证 seenPath 去重逻辑
53
+ {
54
+ // 制造一个 cwd 文件与账本 deliverable 同 basename 看似不同
55
+ writeFileSync(join(cwd, 'cap-probe-1.deliverable.md'), 'unrelated');
56
+ const r = await listArtifacts({ stateRoot, cwd, limit: 20 });
57
+ const seen = new Map();
58
+ for (const a of r.artifacts) {
59
+ if (a.id === 'cap-probe-1.deliverable.md' || a.id === 'cap-probe-1') {
60
+ const k = resolve(a.path);
61
+ seen.set(k, (seen.get(k) ?? 0) + 1);
62
+ }
63
+ }
64
+ // 同一 path 出现 ≥2 次即去重失败
65
+ for (const [k, n] of seen) {
66
+ assert.ok(n <= 1, `path ${k} 出现 ${n} 次,应去重为 1`);
67
+ }
68
+ console.log('3) 合并去重 OK');
69
+ }
70
+ // 4) 裸工单 id read
71
+ {
72
+ const r = await readArtifact({ stateRoot, cwd, path: 'cap-probe-1' });
73
+ assert.equal(r.ok, true, '裸工单 id 应可读');
74
+ if (r.ok) {
75
+ assert.equal(r.lang, 'markdown');
76
+ assert.equal(r.offset, 1);
77
+ assert.equal(r.lines[0]?.number, 1);
78
+ assert.ok(r.lines[0]?.text.includes('交付·账本权威'), '首行应含账本 deliverable 标题');
79
+ assert.equal(r.totalLines, 3, `交付 3 行,实际 ${r.totalLines}`);
80
+ }
81
+ console.log('4) 裸工单 id read OK');
82
+ }
83
+ // 5) 行号窗口
84
+ {
85
+ const r = await readArtifact({ stateRoot, cwd, path: 'trip-2027-cap.md', offset: 3, limit: 3 });
86
+ assert.equal(r.ok, true, 'cwd md 应可读');
87
+ if (r.ok) {
88
+ assert.equal(r.lines.length, 3, `offset=3 limit=3 应 3 行,实际 ${r.lines.length}`);
89
+ assert.equal(r.lines[0]?.number, 3, `首行号应 3,实际 ${r.lines[0]?.number}`);
90
+ assert.equal(r.lines[2]?.number, 5);
91
+ assert.equal(r.totalLines, 10, `8 行 + 标题 1 + 末尾空行 1 = 10,实际 ${r.totalLines}`);
92
+ assert.equal(r.windowed, true, `offset=3 limit=3 总 10 行 → windowed=true(可继续翻页)`);
93
+ }
94
+ const full = await readArtifact({ stateRoot, cwd, path: 'trip-2027-cap.md' });
95
+ assert.equal(full.ok, true, 'cwd md 默认 limit 应读完');
96
+ if (full.ok) {
97
+ assert.equal(full.windowed, false, `读完应 windowed=false`);
98
+ assert.equal(full.lines.length, full.totalLines, `lines.length === totalLines,实际 ${full.lines.length}/${full.totalLines}`);
99
+ }
100
+ console.log('5) 行号窗口 OK');
101
+ }
102
+ // 6) 越界路径
103
+ {
104
+ const r = await readArtifact({ stateRoot, cwd, path: '../../../../../etc/passwd' });
105
+ assert.equal(r.ok, false, '越界路径必须被拒');
106
+ if (!r.ok) {
107
+ assert.ok(r.error.includes('越界'), `error 应含「越界」,实际 ${r.error}`);
108
+ }
109
+ console.log('6) 越界路径 OK');
110
+ }
111
+ // 7) symlink 越界
112
+ {
113
+ const linkPath = join(cwd, 'symlink-out.md');
114
+ try {
115
+ symlinkSync('/etc/passwd', linkPath);
116
+ }
117
+ catch {
118
+ // macOS 上 /etc/passwd 是符号链接到 /private/etc/passwd;symlink 自身仍 out-of-root
119
+ }
120
+ const r = await readArtifact({ stateRoot, cwd, path: 'symlink-out.md' });
121
+ assert.equal(r.ok, false, '相对路径 symlink 解析后越出 cwd 必须被拒');
122
+ if (!r.ok)
123
+ assert.ok(r.error.includes('越界'), `symlink 越界错误应含「越界」,实际 ${r.error}`);
124
+ // 绝对 symlink 指向目录也必须按 canonical target 做边界检查。
125
+ const absLink = join(stateRoot, 'abs-link.md');
126
+ try {
127
+ symlinkSync('/etc', absLink);
128
+ const r2 = await readArtifact({ stateRoot, cwd, path: absLink });
129
+ assert.equal(r2.ok, false, '绝对 symlink 越界必须被拒');
130
+ if (!r2.ok) {
131
+ assert.ok(r2.error.includes('越界'), `error 应含「越界」,实际 ${r2.error}`);
132
+ }
133
+ }
134
+ catch { /* 非 POSIX fs 跳过 */ }
135
+ console.log('7) symlink 越界 OK');
136
+ }
137
+ // 7b) 裸工单 id 的兼容文件视图也必须经过同一组 canonical/root/size 护栏。
138
+ {
139
+ mkdirSync(join(stateRoot, 'gotry-state', 'async'), { recursive: true });
140
+ const bareLink = join(stateRoot, 'gotry-state', 'async', 'evil.deliverable.md');
141
+ let bareLinkCreated = false;
142
+ try {
143
+ symlinkSync('/etc/hosts', bareLink);
144
+ bareLinkCreated = true;
145
+ }
146
+ catch { /* 非 POSIX fs 跳过 */ }
147
+ if (bareLinkCreated) {
148
+ const r = await readArtifact({ stateRoot, cwd, path: 'evil' });
149
+ assert.equal(r.ok, false, '裸工单 id symlink 指向 root 外时必须被拒');
150
+ if (!r.ok)
151
+ assert.ok(r.error.includes('越界'), `裸工单 id symlink 越界错误应含「越界」,实际 ${r.error}`);
152
+ }
153
+ const bareBig = join(stateRoot, 'gotry-state', 'async', 'oversized.deliverable.md');
154
+ writeFileSync(bareBig, 'x'.repeat(2 * 1024 * 1024 + 1));
155
+ const oversized = await readArtifact({ stateRoot, cwd, path: 'oversized' });
156
+ assert.equal(oversized.ok, false, '裸工单 id 超大文件必须被拒');
157
+ if (!oversized.ok)
158
+ assert.ok(oversized.error.includes('过大'), `裸工单 id 超大错误应含「过大」,实际 ${oversized.error}`);
159
+ rmSync(bareLink, { force: true });
160
+ rmSync(bareBig, { force: true });
161
+ console.log('7b) 裸工单 id symlink/超大 deliverable 护栏 OK');
162
+ }
163
+ // 8) 不支持扩展名
164
+ {
165
+ mkdirSync(join(stateRoot, 'gotry-state'), { recursive: true });
166
+ writeFileSync(join(stateRoot, 'gotry-state', 'gotry-state.db'), 'fake db');
167
+ const r = await readArtifact({ stateRoot, cwd, path: 'gotry-state/gotry-state.db' });
168
+ assert.equal(r.ok, false, '.db 必须被拒');
169
+ if (!r.ok) {
170
+ assert.ok(r.error.includes('不支持的文件类型'), `error 应含「不支持的文件类型」,实际 ${r.error}`);
171
+ }
172
+ console.log('8) 不支持扩展名 OK');
173
+ }
174
+ // 9) 不存在文件
175
+ {
176
+ const r = await readArtifact({ stateRoot, cwd, path: 'not-found.md' });
177
+ assert.equal(r.ok, false, '不存在文件必须被拒');
178
+ if (!r.ok) {
179
+ assert.ok(r.error.includes('不存在'), `error 应含「不存在」,实际 ${r.error}`);
180
+ }
181
+ console.log('9) 不存在文件 OK');
182
+ }
183
+ // 10) 超大文件
184
+ {
185
+ const big = join(cwd, 'big.md');
186
+ // 2MB+ 一点点;read 默认上限 2MB
187
+ const chunk = 'x'.repeat(64 * 1024);
188
+ const handle = await import('node:fs/promises').then(m => m.open(big, 'w'));
189
+ try {
190
+ for (let i = 0; i < 33; i++)
191
+ await handle.write(chunk); // 33 * 64KB = 2.11MB
192
+ }
193
+ finally {
194
+ await handle.close();
195
+ }
196
+ const r = await readArtifact({ stateRoot, cwd, path: 'big.md' });
197
+ assert.equal(r.ok, false, '>2MB 必须被拒');
198
+ if (!r.ok) {
199
+ assert.ok(r.error.includes('过大'), `error 应含「过大」,实际 ${r.error}`);
200
+ }
201
+ console.log('10) 超大文件 OK');
202
+ }
203
+ // 11) read 接受 path 三形态:id / cwd 文件名 / 绝对路径
204
+ {
205
+ // 11a) 相对文件名仅在 cwd 顶层
206
+ const r = await readArtifact({ stateRoot, cwd, path: 'trip-2027-cap.md' });
207
+ assert.equal(r.ok, true, '相对文件名应解析为 cwd 顶层');
208
+ // 11b) path 为裸字符串无后缀 + 不在 cwd → 尝试 stateRoot 下的 gotry-state/async/
209
+ const r2 = await readArtifact({ stateRoot, cwd, path: 'cap-probe-1' });
210
+ assert.equal(r2.ok, true, '裸工单 id 应解析为账本');
211
+ // 11c) 空 path
212
+ const r3 = await readArtifact({ stateRoot, cwd, path: '' });
213
+ assert.equal(r3.ok, false, '空 path 应被拒');
214
+ console.log('11) path 三形态 OK');
215
+ }
216
+ // 12) list limit 截断
217
+ {
218
+ for (let i = 0; i < 5; i++)
219
+ writeFileSync(join(cwd, `extra-${i}.md`), `# 副产物 ${i}`);
220
+ const r = await listArtifacts({ stateRoot, cwd, limit: 3 });
221
+ assert.equal(r.artifacts.length, 3, `limit=3 应 3 条,实际 ${r.artifacts.length}`);
222
+ assert.equal(r.truncated, true, `应有更多,truncated 应为 true`);
223
+ // total = sum of each source's retained count = 1 (账本) + 0 (async dir) + 3 (cwd top 3 by mtime) = 4
224
+ // (listCwdMarkdown 内部就 slice(0, limit);要更多需调用方放大 limit)
225
+ assert.equal(r.total, 4, `total 应 4(账本 1 + cwd top 3),实际 ${r.total}`);
226
+ assert.ok(r.total > r.artifacts.length, `total 应 > artifacts.length 才能 truncated,实际 ${r.total} vs ${r.artifacts.length}`);
227
+ // 放大 limit 后能看到全部
228
+ const r2 = await listArtifacts({ stateRoot, cwd, limit: 20 });
229
+ assert.equal(r2.truncated, false, `limit=20 应能装下,truncated=false`);
230
+ assert.ok(r2.total >= 7, `放大 limit 后 total 应 ≥ 7(账本 1 + cwd 多个 md),实际 ${r2.total}`);
231
+ console.log('12) list limit 截断 OK');
232
+ }
233
+ rmSync(stateRoot, { recursive: true, force: true });
234
+ rmSync(cwd, { recursive: true, force: true });
235
+ console.log('all artifacts capability tests OK');
@@ -1,9 +1,23 @@
1
+ /**
2
+ * 异步工单回收(S5 编排半段 → ADR-15 durable 恢复语义):加载持久化工单,
3
+ * 经可日志化 solve 端口执行深度规划(done 步骤复用账本结果,不重执行),
4
+ * 交付物落账本 + .deliverable.md 视图。可由任何后续进程执行(loopx 驱动的
5
+ * tick、state-cli tick、人工、未来的通知系统)——「一小时后回来」跨进程成立,
6
+ * 且崩溃恢复 exactly-once:重放不重复花钱。最后一行固定输出
7
+ * gotry_async_terminal.v1 JSON;4/4 exit 0,非 4/4 exit 2。
8
+ * 运行(在 ts/ 下):npx tsx scripts/async-collect.ts <ticketId> [stateRoot]
9
+ * 环境变量 GOTRY_SOLVE_COUNT_FILE:每次真实求解追加一行(回归用计数探针)。
10
+ */
1
11
  import { appendFileSync } from 'node:fs';
2
- import { ASYNC_TERMINAL_SCHEMA, collectDeepPlanning, loadAsyncTicket, makeJournaledSolvePort, settleAsyncTicket } from '../src/loop.js';
12
+ import { ASYNC_TERMINAL_SCHEMA, collectDeepPlanning, loadAsyncTicket, makeJournaledSolvePort, settleAsyncTicket, } from '../src/loop.js';
3
13
  import { solveUnified } from '../src/unified.js';
4
14
  import { openLedgerIfExists } from '../src/state-ledger.js';
5
15
  function isTerminalOutcome(value) {
6
- return value?.['schema'] === ASYNC_TERMINAL_SCHEMA && (value['status'] === 'succeeded' || value['status'] === 'failed') && typeof value['passed'] === 'number' && value['total'] === 4 && Array.isArray(value['failed_checks']);
16
+ return value?.['schema'] === ASYNC_TERMINAL_SCHEMA
17
+ && (value['status'] === 'succeeded' || value['status'] === 'failed')
18
+ && typeof value['passed'] === 'number'
19
+ && value['total'] === 4
20
+ && Array.isArray(value['failed_checks']);
7
21
  }
8
22
  function legacyTerminalOutcome(ticket, status, deliverable) {
9
23
  const succeeded = status === 'settled' && deliverable.includes('不失望四条:4/4 ✅');
@@ -13,12 +27,8 @@ function legacyTerminalOutcome(ticket, status, deliverable) {
13
27
  status: succeeded ? 'succeeded' : 'failed',
14
28
  passed: succeeded ? 4 : 0,
15
29
  total: 4,
16
- checks: {
17
- legacy_human_marker_only: succeeded
18
- },
19
- failed_checks: succeeded ? [] : [
20
- 'legacy_unstructured_terminal'
21
- ]
30
+ checks: { legacy_human_marker_only: succeeded },
31
+ failed_checks: succeeded ? [] : ['legacy_unstructured_terminal'],
22
32
  };
23
33
  }
24
34
  function emitTerminal(outcome) {
@@ -36,11 +46,14 @@ if (!loaded) {
36
46
  console.error(`工单 ${ticketId} 不存在(账本 workflow_runs / gotry-state/async/${ticketId}.json)`);
37
47
  process.exit(1);
38
48
  }
49
+ // 终态幂等:已 settled/failed 的工单复诵账本交付物与机器终态,零重算
39
50
  const ledger = openLedgerIfExists(stateRoot);
40
51
  const run = ledger?.getWorkflowRun(ticketId);
41
52
  if ((run?.status === 'settled' || run?.status === 'failed') && run.deliverable) {
42
53
  const storedOutcome = ledger?.getWorkflowTerminalOutcome(ticketId) ?? null;
43
- const outcome = isTerminalOutcome(storedOutcome) ? storedOutcome : legacyTerminalOutcome(ticketId, run.status, run.deliverable);
54
+ const outcome = isTerminalOutcome(storedOutcome)
55
+ ? storedOutcome
56
+ : legacyTerminalOutcome(ticketId, run.status, run.deliverable);
44
57
  await settleAsyncTicket(ticketId, run.deliverable, stateRoot, outcome);
45
58
  console.log(`工单 ${ticketId} 已交付(账本终态,复诵不重算):\n\n${run.deliverable}`);
46
59
  emitTerminal(outcome);
@@ -50,16 +63,14 @@ if (!ledger) {
50
63
  process.exit(1);
51
64
  }
52
65
  const solve = makeJournaledSolvePort(ledger, ticketId, solveUnified, {
53
- onRealSolve: ()=>{
66
+ onRealSolve: () => {
54
67
  const f = process.env['GOTRY_SOLVE_COUNT_FILE'];
55
- if (f) appendFileSync(f, 'solve\n');
56
- }
68
+ if (f)
69
+ appendFileSync(f, 'solve\n');
70
+ },
57
71
  });
58
72
  const { reply, outcome } = await collectDeepPlanning(loaded.state, loaded.ticket, solve);
59
73
  const out = await settleAsyncTicket(ticketId, reply, stateRoot, outcome);
60
74
  console.log(`交付物已落盘:${out}\n\n${reply}`);
61
75
  console.log(JSON.stringify(outcome));
62
76
  process.exitCode = outcome.status === 'succeeded' ? 0 : 2;
63
-
64
-
65
- //# sourceURL=ts/scripts/async-collect.ts