@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,123 +1,59 @@
1
+ /**
2
+ * 旅行时间线单测(memory-design P1,travel_timeline.v1):
3
+ * 守门(必填/绝对日期/幂等/重叠冲突即停)/投影/交叉一致(verified 无 timeline 缺口)。
4
+ * 运行:cd ts && npx tsx scripts/travel-timeline-tests.ts
5
+ */
1
6
  import assert from 'node:assert/strict';
2
7
  import { appendTrip, makeTripId, projectTimeline, timelineGapsForVerified, resolveTimelineDate } from '../src/travel-timeline.js';
3
8
  import { buildTimeAnchor } from '../src/time-anchor.js';
4
- const anchor = buildTimeAnchor(new Date(2026, 7, 28, 12));
9
+ const anchor = buildTimeAnchor(new Date(2026, 7, 28, 12)); // 2026-08-28
5
10
  let n = 0;
6
11
  function pass(name, body) {
7
12
  body();
8
13
  console.log(` ${++n}. ${name} OK`);
9
14
  }
10
- pass('守门:必填与绝对日期(不猜)', ()=>{
11
- const ev = {
12
- destination: '大理',
13
- start: '2025-10-01',
14
- source: 'user-verbatim',
15
- evidence: '用户原话'
16
- };
17
- assert.equal(appendTrip([], {
18
- ...ev,
19
- destination: ''
20
- }).appended, false, '无目的地拒');
21
- assert.equal(appendTrip([], {
22
- ...ev,
23
- evidence: ''
24
- }).appended, false, '无 evidence 拒(溯源 P0)');
25
- assert.ok(appendTrip([], {
26
- ...ev,
27
- start: '下周一'
28
- }).reason?.includes('YYYY-MM-DD'), '词表外日期拒收(不猜)');
29
- assert.ok(appendTrip([], {
30
- ...ev,
31
- end: '2025-09-30'
32
- }).reason?.includes('end 早于 start'), 'end<start 拒');
15
+ pass('守门:必填与绝对日期(不猜)', () => {
16
+ const ev = { destination: '大理', start: '2025-10-01', source: 'user-verbatim', evidence: '用户原话' };
17
+ assert.equal(appendTrip([], { ...ev, destination: '' }).appended, false, '无目的地拒');
18
+ assert.equal(appendTrip([], { ...ev, evidence: '' }).appended, false, '无 evidence 拒(溯源 P0)');
19
+ assert.ok(appendTrip([], { ...ev, start: '下周一' }).reason?.includes('YYYY-MM-DD'), '词表外日期拒收(不猜)');
20
+ assert.ok(appendTrip([], { ...ev, end: '2025-09-30' }).reason?.includes('end 早于 start'), 'end<start 拒');
33
21
  });
34
- pass('幂等:同目的地+start+source = 同一行程 no-op', ()=>{
35
- const ev = {
36
- destination: '大理',
37
- start: '2025-10-01',
38
- source: 'user-verbatim',
39
- evidence: '原话'
40
- };
22
+ pass('幂等:同目的地+start+source = 同一行程 no-op', () => {
23
+ const ev = { destination: '大理', start: '2025-10-01', source: 'user-verbatim', evidence: '原话' };
41
24
  const r1 = appendTrip([], ev);
42
25
  assert.equal(r1.appended, true);
43
- const r2 = appendTrip(r1.events, {
44
- ...ev,
45
- evidence: '换个说法再说一遍'
46
- });
26
+ const r2 = appendTrip(r1.events, { ...ev, evidence: '换个说法再说一遍' });
47
27
  assert.equal(r2.appended, false, '同 trip_id 重放 no-op');
48
28
  assert.equal(r2.tripId, r1.tripId);
49
29
  });
50
- pass('重叠冲突即停:同目的地日期区间重叠拒收', ()=>{
51
- const r1 = appendTrip([], {
52
- destination: '大理',
53
- start: '2025-10-01',
54
- end: '2025-10-05',
55
- source: 'user-verbatim',
56
- evidence: '原话'
57
- });
58
- const r2 = appendTrip(r1.events, {
59
- destination: '大理',
60
- start: '2025-10-04',
61
- source: 'user-verbatim',
62
- evidence: '原话2'
63
- });
30
+ pass('重叠冲突即停:同目的地日期区间重叠拒收', () => {
31
+ const r1 = appendTrip([], { destination: '大理', start: '2025-10-01', end: '2025-10-05', source: 'user-verbatim', evidence: '原话' });
32
+ const r2 = appendTrip(r1.events, { destination: '大理', start: '2025-10-04', source: 'user-verbatim', evidence: '原话2' });
64
33
  assert.equal(r2.appended, false);
65
34
  assert.ok(r2.reason?.includes('重叠'), '冲突理由说明');
66
35
  });
67
- pass('日期解析:锚点卡词表进、词表外 null(经 resolveTimelineDate)', ()=>{
36
+ pass('日期解析:锚点卡词表进、词表外 null(经 resolveTimelineDate)', () => {
68
37
  assert.equal(resolveTimelineDate('2025-10-01', anchor), '2025-10-01');
69
38
  assert.equal(resolveTimelineDate('去年国庆', anchor), null, '开放表达不猜');
70
39
  });
71
- pass('投影:start 倒序(最近在前)', ()=>{
40
+ pass('投影:start 倒序(最近在前)', () => {
72
41
  let evs = [];
73
- evs = appendTrip(evs, {
74
- destination: '普吉',
75
- start: '2026-07-10',
76
- source: 'user-verbatim',
77
- evidence: 'a'
78
- }).events;
79
- evs = appendTrip(evs, {
80
- destination: '大理',
81
- start: '2025-10-01',
82
- source: 'user-verbatim',
83
- evidence: 'b'
84
- }).events;
42
+ evs = appendTrip(evs, { destination: '普吉', start: '2026-07-10', source: 'user-verbatim', evidence: 'a' }).events;
43
+ evs = appendTrip(evs, { destination: '大理', start: '2025-10-01', source: 'user-verbatim', evidence: 'b' }).events;
85
44
  const p = projectTimeline(evs);
86
45
  assert.equal(p[0]?.destination, '普吉');
87
46
  assert.equal(p.length, 2);
88
47
  });
89
- pass('交叉一致:verified wish 无 timeline = 缺口暴露(巡检口径,不自动补写)', ()=>{
90
- const evs = appendTrip([], {
91
- destination: '大理',
92
- start: '2025-10-01',
93
- source: 'wish-confirmed',
94
- evidence: 'wish w1 confirm'
95
- }).events;
48
+ pass('交叉一致:verified wish 无 timeline = 缺口暴露(巡检口径,不自动补写)', () => {
49
+ const evs = appendTrip([], { destination: '大理', start: '2025-10-01', source: 'wish-confirmed', evidence: 'wish w1 confirm' }).events;
96
50
  const gaps = timelineGapsForVerified(evs, [
97
- {
98
- wishId: 'w1',
99
- destination: '大理'
100
- },
101
- {
102
- wishId: 'w2',
103
- destination: '清迈'
104
- }
105
- ]);
106
- assert.deepEqual(gaps, [
107
- {
108
- wishId: 'w2'
109
- }
51
+ { wishId: 'w1', destination: '大理' },
52
+ { wishId: 'w2', destination: '清迈' },
110
53
  ]);
54
+ assert.deepEqual(gaps, [{ wishId: 'w2' }]);
111
55
  });
112
- pass('trip_id 语义派生:确定性可重放', ()=>{
113
- assert.equal(makeTripId({
114
- destination: '大 理',
115
- start: '2025-10-01',
116
- source: 'user-verbatim',
117
- evidence: 'x'
118
- }), '大_理|2025-10-01|user-verbatim');
56
+ pass('trip_id 语义派生:确定性可重放', () => {
57
+ assert.equal(makeTripId({ destination: '大 理', start: '2025-10-01', source: 'user-verbatim', evidence: 'x' }), '大_理|2025-10-01|user-verbatim');
119
58
  });
120
59
  console.log(`\nTRAVEL TIMELINE TESTS: ${n}/7 OK(memory-design P1 守门面)`);
121
-
122
-
123
- //# sourceURL=ts/scripts/travel-timeline-tests.ts
@@ -1,3 +1,9 @@
1
+ /**
2
+ * turn-handoff 收集器闭环测试(ADR-24 v2 收集半段)。
3
+ * 假 planner(node 脚本)驱动:成功/失败/空输出/幂等复诵零重算/--all/
4
+ * 缺单;并断言递归防护真的传进了子进程(GOTRY_HANDOFF_CHILD=1 且父进程
5
+ * 的数值 pin/工单根被清除)。全程隔离 stateRoot,不触真实 gotry-state。
6
+ */
1
7
  import assert from 'node:assert/strict';
2
8
  import { execFileSync } from 'node:child_process';
3
9
  import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
@@ -7,41 +13,25 @@ import { listTurnHandoffTickets, settleTurnHandoffTicket, writeTurnHandoffTicket
7
13
  const TS_DIR = join(import.meta.dirname, '..');
8
14
  const TSX = join(TS_DIR, 'node_modules', 'tsx', 'dist', 'cli.mjs');
9
15
  function runCollector(args, env = {}) {
16
+ // 用 execFileSync 的同步抓取替代 spawn 手工聚合,行为等价且断言更直接。
10
17
  try {
11
- const stdout = execFileSync(process.execPath, [
12
- TSX,
13
- 'scripts/turn-handoff-collect.ts',
14
- ...args
15
- ], {
18
+ const stdout = execFileSync(process.execPath, [TSX, 'scripts/turn-handoff-collect.ts', ...args], {
16
19
  cwd: TS_DIR,
17
- env: {
18
- ...process.env,
19
- ...env
20
- },
20
+ env: { ...process.env, ...env },
21
21
  encoding: 'utf-8',
22
- stdio: [
23
- 'ignore',
24
- 'pipe',
25
- 'pipe'
26
- ]
22
+ stdio: ['ignore', 'pipe', 'pipe'],
27
23
  });
28
- return {
29
- status: 0,
30
- stdout,
31
- stderr: ''
32
- };
33
- } catch (error) {
24
+ return { status: 0, stdout, stderr: '' };
25
+ }
26
+ catch (error) {
34
27
  const e = error;
35
- return {
36
- status: e.status ?? null,
37
- stdout: e.stdout ?? '',
38
- stderr: e.stderr ?? ''
39
- };
28
+ return { status: e.status ?? null, stdout: e.stdout ?? '', stderr: e.stderr ?? '' };
40
29
  }
41
30
  }
42
31
  const root = mkdtempSync(join(tmpdir(), 'gotry-handoff-collect-'));
43
32
  const planners = mkdtempSync(join(tmpdir(), 'gotry-handoff-planners-'));
44
33
  const plannerCalls = join(planners, 'calls.log');
34
+ // ok:记录调用环境(递归防护证据)→ 输出交付物
45
35
  const okPlanner = join(planners, 'ok.mjs');
46
36
  writeFileSync(okPlanner, `import { appendFileSync } from 'node:fs'
47
37
  appendFileSync(${JSON.stringify(plannerCalls)}, [
@@ -61,20 +51,14 @@ const emptyPlanner = join(planners, 'empty.mjs');
61
51
  writeFileSync(emptyPlanner, `import { appendFileSync } from 'node:fs'
62
52
  appendFileSync(${JSON.stringify(plannerCalls)}, 'call empty\\n')
63
53
  `);
64
- const calls = ()=>existsSync(plannerCalls) ? readFileSync(plannerCalls, 'utf-8').trim().split('\n') : [];
54
+ const calls = () => (existsSync(plannerCalls) ? readFileSync(plannerCalls, 'utf-8').trim().split('\n') : []);
65
55
  try {
66
- assert.equal(runCollector([
67
- 'th-missing',
68
- root
69
- ]).status, 1);
56
+ // 1) 缺单 → exit 1
57
+ assert.equal(runCollector(['th-missing', root]).status, 1);
58
+ // 2) 成功回收:planner 恰跑一次,工单 settled,交付物含方案,终态 JSON
70
59
  const t1 = await writeTurnHandoffTicket(root, '帮我规划10.3衡阳婚礼十几天行程');
71
60
  {
72
- const r = runCollector([
73
- t1.id,
74
- root
75
- ], {
76
- GOTRY_HANDOFF_PLANNER_BIN: okPlanner
77
- });
61
+ const r = runCollector([t1.id, root], { GOTRY_HANDOFF_PLANNER_BIN: okPlanner });
78
62
  assert.equal(r.status, 0, `stderr=${r.stderr}`);
79
63
  const terminal = JSON.parse(r.stdout.trim().split('\n').at(-1));
80
64
  assert.equal(terminal['schema'], 'gotry_turn_handoff_terminal.v1');
@@ -86,37 +70,28 @@ try {
86
70
  assert.ok(t1After['settledAt']);
87
71
  const deliverable1 = readFileSync(join(root, 'gotry-state', 'turn-handoffs', `${t1.id}.deliverable.md`), 'utf8');
88
72
  assert.ok(deliverable1.includes('方案A') && deliverable1.includes(t1.id));
73
+ // 3) 幂等复诵:重跑 exit 0,同交付物,planner 零重算
89
74
  {
90
75
  const before = calls().length;
91
- const r = runCollector([
92
- t1.id,
93
- root
94
- ], {
95
- GOTRY_HANDOFF_PLANNER_BIN: okPlanner
96
- });
76
+ const r = runCollector([t1.id, root], { GOTRY_HANDOFF_PLANNER_BIN: okPlanner });
97
77
  assert.equal(r.status, 0);
98
78
  const terminal = JSON.parse(r.stdout.trim().split('\n').at(-1));
99
79
  assert.equal(terminal['status'], 'succeeded');
100
80
  assert.equal(calls().length, before, 'settled ticket must not re-run the planner');
101
81
  }
82
+ // 4) 递归防护证据:ok planner 记录的子环境
102
83
  {
103
- const last = [
104
- ...calls()
105
- ].reverse().find((l)=>l.includes('child='));
84
+ const last = [...calls()].reverse().find(l => l.includes('child='));
106
85
  assert.ok(last, 'planner ran');
107
86
  assert.ok(last.includes('child=1'), `GOTRY_HANDOFF_CHILD must be 1: ${last}`);
108
87
  assert.ok(last.includes('pin=CLEARED'), `parent deadline pins must be cleared: ${last}`);
109
88
  assert.ok(last.includes('root=CLEARED'), `parent handoff root must be cleared: ${last}`);
110
89
  assert.ok(last.includes('msg=帮我规划10.3衡阳婚礼'), 'planner receives the verbatim user ask');
111
90
  }
91
+ // 5) 失败回收:exit 2,工单 failed + error,交付物是诚实失败说明
112
92
  const t2 = await writeTurnHandoffTicket(root, '另一趟失败的规划');
113
93
  {
114
- const r = runCollector([
115
- t2.id,
116
- root
117
- ], {
118
- GOTRY_HANDOFF_PLANNER_BIN: failPlanner
119
- });
94
+ const r = runCollector([t2.id, root], { GOTRY_HANDOFF_PLANNER_BIN: failPlanner });
120
95
  assert.equal(r.status, 2);
121
96
  const terminal = JSON.parse(r.stdout.trim().split('\n').at(-1));
122
97
  assert.equal(terminal['status'], 'failed');
@@ -127,28 +102,23 @@ try {
127
102
  assert.ok(String(t2After['error']).startsWith('planner exited 3'));
128
103
  const deliverable2 = readFileSync(join(root, 'gotry-state', 'turn-handoffs', `${t2.id}.deliverable.md`), 'utf8');
129
104
  assert.ok(deliverable2.includes('没有完成') && deliverable2.includes('planner exited 3'), 'honest failure note');
105
+ // 失败复诵:exit 2,零重算
130
106
  {
131
107
  const before = calls().length;
132
- assert.equal(runCollector([
133
- t2.id,
134
- root
135
- ], {
136
- GOTRY_HANDOFF_PLANNER_BIN: failPlanner
137
- }).status, 2);
108
+ assert.equal(runCollector([t2.id, root], { GOTRY_HANDOFF_PLANNER_BIN: failPlanner }).status, 2);
138
109
  assert.equal(calls().length, before);
139
110
  }
111
+ // 6) 空输出 → failed
140
112
  const t3 = await writeTurnHandoffTicket(root, '空输出的规划');
141
113
  {
142
- const r = runCollector([
143
- t3.id,
144
- root
145
- ], {
146
- GOTRY_HANDOFF_PLANNER_BIN: emptyPlanner
147
- });
114
+ const r = runCollector([t3.id, root], { GOTRY_HANDOFF_PLANNER_BIN: emptyPlanner });
148
115
  assert.equal(r.status, 2);
149
116
  const terminal = JSON.parse(r.stdout.trim().split('\n').at(-1));
150
117
  assert.equal(terminal['error'], 'planner produced empty output');
151
118
  }
119
+ // 7) --all:只收 open(t4/t5),已终态的 t1/t2/t3 零重算。
120
+ // t4/t5 同毫秒创建会让「最新优先」断言 flaky(requestedAt 毫秒精度);
121
+ // 把 t5 钉到 now+5s,排序严格确定。
152
122
  const t4 = await writeTurnHandoffTicket(root, '批量规划一');
153
123
  const t5 = await writeTurnHandoffTicket(root, '批量规划二');
154
124
  {
@@ -156,50 +126,38 @@ try {
156
126
  const t5json = JSON.parse(readFileSync(join(dir, `${t5.id}.json`), 'utf8'));
157
127
  writeFileSync(join(dir, `${t5.id}.json`), JSON.stringify({
158
128
  ...t5json,
159
- requestedAt: new Date(Date.now() + 5_000).toISOString()
129
+ requestedAt: new Date(Date.now() + 5_000).toISOString(),
160
130
  }));
161
131
  }
162
132
  {
163
133
  const before = calls().length;
164
- const r = runCollector([
165
- '--all',
166
- root
167
- ], {
168
- GOTRY_HANDOFF_PLANNER_BIN: okPlanner
169
- });
134
+ const r = runCollector(['--all', root], { GOTRY_HANDOFF_PLANNER_BIN: okPlanner });
170
135
  assert.equal(r.status, 0, `stderr=${r.stderr}`);
171
136
  const terminal = JSON.parse(r.stdout.trim().split('\n').at(-1));
172
137
  assert.equal(terminal['status'], 'succeeded');
173
138
  assert.equal(calls().length, before + 2, 'exactly the two open tickets collected');
174
139
  }
175
- assert.equal(runCollector([
176
- '--all',
177
- root
178
- ], {
179
- GOTRY_HANDOFF_PLANNER_BIN: okPlanner
180
- }).status, 0);
140
+ // 8) --all 无 open → exit 0
141
+ assert.equal(runCollector(['--all', root], { GOTRY_HANDOFF_PLANNER_BIN: okPlanner }).status, 0);
142
+ // 9) 复访视图:倒序 + settled 摘录 + failed 错误
181
143
  {
182
144
  const views = await listTurnHandoffTickets(root);
183
145
  assert.equal(views.length, 5);
184
146
  assert.equal(views[0].id, t5.id, 'newest first');
185
- const settled = views.find((v)=>v.id === t1.id);
147
+ const settled = views.find(v => v.id === t1.id);
186
148
  assert.equal(settled.status, 'settled');
187
149
  assert.ok(settled.deliverableExcerpt?.includes('方案A'));
188
- const failed = views.find((v)=>v.id === t2.id);
150
+ const failed = views.find(v => v.id === t2.id);
189
151
  assert.equal(failed.status, 'failed');
190
152
  assert.ok(failed.error?.startsWith('planner exited 3'));
191
153
  }
192
154
  console.log('turn handoff collector tests: OK (settle/fail/empty/idempotent replay/--all/recursion guard env/revisit views)');
193
- } finally{
194
- rmSync(root, {
195
- recursive: true,
196
- force: true
197
- });
198
- rmSync(planners, {
199
- recursive: true,
200
- force: true
201
- });
202
155
  }
156
+ finally {
157
+ rmSync(root, { recursive: true, force: true });
158
+ rmSync(planners, { recursive: true, force: true });
159
+ }
160
+ // settleTurnHandoffTicket 直接契约(不经 collector):原子写 + 字段完整
203
161
  {
204
162
  const r = mkdtempSync(join(tmpdir(), 'gotry-handoff-settle-'));
205
163
  const t = await writeTurnHandoffTicket(r, '直调结算');
@@ -207,10 +165,5 @@ try {
207
165
  assert.ok(existsSync(deliverablePath));
208
166
  const views = await listTurnHandoffTickets(r);
209
167
  assert.equal(views[0].deliverableExcerpt, '交付内容');
210
- rmSync(r, {
211
- recursive: true,
212
- force: true
213
- });
168
+ rmSync(r, { recursive: true, force: true });
214
169
  }
215
-
216
- //# sourceURL=ts/scripts/turn-handoff-collect-tests.ts
@@ -1,8 +1,28 @@
1
+ /**
2
+ * 后台深度规划工单收集器(ADR-24 v2 收集闭环;async-collect 的 turn-handoff 对位)。
3
+ *
4
+ * 「一小时后回来看看」的后台半段:加载 open 工单 → 以 GOTRY_HANDOFF_CHILD=1
5
+ * 派生 headless 规划会话(唯一出口 converge,不回递归)→ 捕获最终答复为
6
+ * 交付物 → settleTurnHandoffTicket 结算(交付物 .deliverable.md + 工单状态
7
+ * 原子更新)。终态幂等:已 settled/failed 的工单复诵交付物与终态 JSON,零
8
+ * 重算、零再花。最后一行固定输出 gotry_turn_handoff_terminal.v1 JSON;
9
+ * succeeded exit 0,failed exit 2。
10
+ *
11
+ * 运行(在 ts/ 下):
12
+ * npx tsx scripts/turn-handoff-collect.ts <ticketId> [stateRoot]
13
+ * npx tsx scripts/turn-handoff-collect.ts --all [stateRoot]
14
+ * 环境变量:
15
+ * GOTRY_HANDOFF_PLANNER_BIN 规划器二进制(默认 <repo>/bin/gotry-inner.js)
16
+ * GOTRY_HANDOFF_PLANNER_TIMEOUT_MS 规划器硬上限(默认 900_000)
17
+ *
18
+ * v1 诚实边界:工单只携带用户原文,规划会话是全新上下文(fresh cwd/DSH_HOME)
19
+ * ——原会话中已读的工作区文件/日历结论不随单迁移,由规划器按需重取。
20
+ */
1
21
  import { spawn } from 'node:child_process';
2
22
  import { existsSync, mkdtempSync, readFileSync, rmSync } from 'node:fs';
3
23
  import { tmpdir } from 'node:os';
4
24
  import { join } from 'node:path';
5
- import { listTurnHandoffTickets, settleTurnHandoffTicket, TURN_HANDOFF_SCHEMA } from '../src/turn-deadline.js';
25
+ import { listTurnHandoffTickets, settleTurnHandoffTicket, TURN_HANDOFF_SCHEMA, } from '../src/turn-deadline.js';
6
26
  const TURN_HANDOFF_TERMINAL_SCHEMA = 'gotry_turn_handoff_terminal.v1';
7
27
  const ROOT = join(import.meta.dirname, '..', '..');
8
28
  function emitTerminal(outcome) {
@@ -14,7 +34,8 @@ function loadTicket(stateRoot, ticketId) {
14
34
  const path = join(stateRoot === '.' ? process.cwd() : stateRoot, 'gotry-state', 'turn-handoffs', `${ticketId}.json`);
15
35
  const ticket = JSON.parse(readFileSync(path, 'utf-8'));
16
36
  return ticket?.schema === TURN_HANDOFF_SCHEMA ? ticket : null;
17
- } catch {
37
+ }
38
+ catch {
18
39
  return null;
19
40
  }
20
41
  }
@@ -24,105 +45,53 @@ function replay(ticket, stateRoot) {
24
45
  schema: TURN_HANDOFF_TERMINAL_SCHEMA,
25
46
  ticket_id: ticket.id,
26
47
  status: ticket.status === 'settled' ? 'succeeded' : 'failed',
27
- ...ticket.deliverableFile ? {
28
- deliverable_path: join(dir, ticket.deliverableFile)
29
- } : {},
30
- ...ticket.error ? {
31
- error: ticket.error
32
- } : {}
48
+ ...(ticket.deliverableFile ? { deliverable_path: join(dir, ticket.deliverableFile) } : {}),
49
+ ...(ticket.error ? { error: ticket.error } : {}),
33
50
  });
34
51
  }
35
52
  async function runPlanner(ticket) {
36
53
  const plannerBin = process.env.GOTRY_HANDOFF_PLANNER_BIN ?? join(ROOT, 'bin', 'gotry-inner.js');
37
54
  if (!existsSync(plannerBin)) {
38
- return {
39
- ok: false,
40
- output: '',
41
- error: `planner binary unavailable: ${plannerBin}`
42
- };
55
+ return { ok: false, output: '', error: `planner binary unavailable: ${plannerBin}` };
43
56
  }
44
57
  const timeoutMs = Number.parseInt(process.env.GOTRY_HANDOFF_PLANNER_TIMEOUT_MS ?? '', 10) || 900_000;
45
58
  const dshHome = mkdtempSync(join(tmpdir(), 'gotry-handoff-child-dsh-'));
46
59
  const childCwd = mkdtempSync(join(tmpdir(), 'gotry-handoff-child-cwd-'));
47
60
  try {
48
- const childEnv = {
49
- ...process.env
50
- };
61
+ const childEnv = { ...process.env };
51
62
  childEnv.DSH_HOME = dshHome;
63
+ // 递归防护(核心):子会话唯一出口 converge + 长 leash;同时清掉父进程的
64
+ // 数值 pin 与工单根,子会话不得再 handoff、也不受测试阈值影响。
52
65
  childEnv.GOTRY_HANDOFF_CHILD = '1';
53
66
  delete childEnv.GOTRY_TURN_DEADLINE_SOFT_MS;
54
67
  delete childEnv.GOTRY_TURN_DEADLINE_HARD_MS;
55
68
  delete childEnv.GOTRY_TURN_HANDOFF_ROOT;
69
+ // .js/.mjs/.cjs 经当前 node 派生(测试假 planner 无执行位也成立);
70
+ // 其余(gotry-inner.js 之外的 .bin shim、全局命令)直接执行。
56
71
  const isJsScript = /\.(js|mjs|cjs)$/.test(plannerBin);
57
- const child = isJsScript ? spawn(process.execPath, [
58
- plannerBin,
59
- ticket.userMessage
60
- ], {
61
- cwd: childCwd,
62
- env: childEnv,
63
- stdio: [
64
- 'ignore',
65
- 'pipe',
66
- 'pipe'
67
- ]
68
- }) : spawn(plannerBin, [
69
- ticket.userMessage
70
- ], {
71
- cwd: childCwd,
72
- env: childEnv,
73
- stdio: [
74
- 'ignore',
75
- 'pipe',
76
- 'pipe'
77
- ]
78
- });
72
+ const child = isJsScript
73
+ ? spawn(process.execPath, [plannerBin, ticket.userMessage], { cwd: childCwd, env: childEnv, stdio: ['ignore', 'pipe', 'pipe'] })
74
+ : spawn(plannerBin, [ticket.userMessage], { cwd: childCwd, env: childEnv, stdio: ['ignore', 'pipe', 'pipe'] });
79
75
  let output = '';
80
- child.stdout.on('data', (chunk)=>{
81
- output += chunk.toString();
82
- });
83
- child.stderr.on('data', (chunk)=>{
84
- output += chunk.toString();
76
+ child.stdout.on('data', chunk => { output += chunk.toString(); });
77
+ child.stderr.on('data', chunk => { output += chunk.toString(); });
78
+ const code = await new Promise(resolve => {
79
+ const timer = setTimeout(() => { child.kill('SIGKILL'); resolve(null); }, timeoutMs);
80
+ child.once('exit', c => { clearTimeout(timer); resolve(c); });
85
81
  });
86
- const code = await new Promise((resolve)=>{
87
- const timer = setTimeout(()=>{
88
- child.kill('SIGKILL');
89
- resolve(null);
90
- }, timeoutMs);
91
- child.once('exit', (c)=>{
92
- clearTimeout(timer);
93
- resolve(c);
94
- });
95
- });
96
- if (code === null) return {
97
- ok: false,
98
- output: '',
99
- error: `planner timed out after ${timeoutMs}ms`
100
- };
101
- if (code !== 0) return {
102
- ok: false,
103
- output: '',
104
- error: `planner exited ${code}: ${output.trim().slice(-300)}`
105
- };
82
+ if (code === null)
83
+ return { ok: false, output: '', error: `planner timed out after ${timeoutMs}ms` };
84
+ if (code !== 0)
85
+ return { ok: false, output: '', error: `planner exited ${code}: ${output.trim().slice(-300)}` };
106
86
  const deliverable = output.trim();
107
- if (!deliverable) return {
108
- ok: false,
109
- output: '',
110
- error: 'planner produced empty output'
111
- };
112
- return {
113
- ok: true,
114
- output: deliverable
115
- };
116
- } finally{
117
- await new Promise((resolve)=>setTimeout(resolve, 200));
118
- rmSync(dshHome, {
119
- recursive: true,
120
- force: true
121
- });
122
- rmSync(childCwd, {
123
- recursive: true,
124
- force: true
125
- });
87
+ if (!deliverable)
88
+ return { ok: false, output: '', error: 'planner produced empty output' };
89
+ return { ok: true, output: deliverable };
90
+ }
91
+ finally {
92
+ await new Promise(resolve => setTimeout(resolve, 200));
93
+ rmSync(dshHome, { recursive: true, force: true });
94
+ rmSync(childCwd, { recursive: true, force: true });
126
95
  }
127
96
  }
128
97
  async function collectOne(stateRoot, ticketId) {
@@ -136,41 +105,45 @@ async function collectOne(stateRoot, ticketId) {
136
105
  replay(ticket, stateRoot);
137
106
  }
138
107
  const result = await runPlanner(ticket);
139
- const deliverable = result.ok ? `# 后台深度规划交付:${ticket.objective.slice(0, 60)}\n(工单 ${ticket.id},原请求 ${ticket.requestedAt})\n\n${result.output}` : `# 后台深度规划失败:${ticket.objective.slice(0, 60)}\n(工单 ${ticket.id},原请求 ${ticket.requestedAt})\n\n未产出交付物:${result.error ?? '未知原因'}。这趟规划没有完成——请重新发起,失败原因如上,无部分结果可交付。`;
108
+ const deliverable = result.ok
109
+ ? `# 后台深度规划交付:${ticket.objective.slice(0, 60)}\n(工单 ${ticket.id},原请求 ${ticket.requestedAt})\n\n${result.output}`
110
+ : `# 后台深度规划失败:${ticket.objective.slice(0, 60)}\n(工单 ${ticket.id},原请求 ${ticket.requestedAt})\n\n未产出交付物:${result.error ?? '未知原因'}。这趟规划没有完成——请重新发起,失败原因如上,无部分结果可交付。`;
140
111
  const { deliverablePath } = await settleTurnHandoffTicket(stateRoot, ticket, result.ok ? 'settled' : 'failed', deliverable, result.ok ? undefined : result.error);
141
112
  console.error(`工单 ${ticket.id} ${result.ok ? '已交付' : '失败'}:${deliverablePath}`);
142
113
  emitTerminal({
143
114
  schema: TURN_HANDOFF_TERMINAL_SCHEMA,
144
115
  ticket_id: ticket.id,
145
116
  status: result.ok ? 'succeeded' : 'failed',
146
- ...result.ok ? {
147
- deliverable_path: deliverablePath
148
- } : {
149
- error: result.error ?? 'unknown'
150
- }
117
+ ...(result.ok
118
+ ? { deliverable_path: deliverablePath }
119
+ : { error: result.error ?? 'unknown' }),
151
120
  });
152
121
  }
153
122
  async function collectAll(stateRoot) {
154
- const open = (await listTurnHandoffTickets(stateRoot)).filter((t)=>t.status === 'open');
123
+ const open = (await listTurnHandoffTickets(stateRoot)).filter(t => t.status === 'open');
155
124
  if (open.length === 0) {
156
125
  console.error('无待回收的后台规划工单(open=0)');
157
126
  process.exit(0);
158
127
  }
159
128
  let failed = 0;
160
- for (const view of open){
129
+ for (const view of open) {
161
130
  const ticket = loadTicket(stateRoot, view.id);
162
- if (!ticket || ticket.status !== 'open') continue;
131
+ if (!ticket || ticket.status !== 'open')
132
+ continue;
163
133
  const result = await runPlanner(ticket);
164
- const deliverable = result.ok ? `# 后台深度规划交付:${ticket.objective.slice(0, 60)}\n(工单 ${ticket.id},原请求 ${ticket.requestedAt})\n\n${result.output}` : `# 后台深度规划失败:${ticket.objective.slice(0, 60)}\n(工单 ${ticket.id},原请求 ${ticket.requestedAt})\n\n未产出交付物:${result.error ?? '未知原因'}。这趟规划没有完成——请重新发起,失败原因如上,无部分结果可交付。`;
134
+ const deliverable = result.ok
135
+ ? `# 后台深度规划交付:${ticket.objective.slice(0, 60)}\n(工单 ${ticket.id},原请求 ${ticket.requestedAt})\n\n${result.output}`
136
+ : `# 后台深度规划失败:${ticket.objective.slice(0, 60)}\n(工单 ${ticket.id},原请求 ${ticket.requestedAt})\n\n未产出交付物:${result.error ?? '未知原因'}。这趟规划没有完成——请重新发起,失败原因如上,无部分结果可交付。`;
165
137
  const { deliverablePath } = await settleTurnHandoffTicket(stateRoot, ticket, result.ok ? 'settled' : 'failed', deliverable, result.ok ? undefined : result.error);
166
138
  console.error(`工单 ${ticket.id} ${result.ok ? '已交付' : '失败'}:${deliverablePath}`);
167
- if (!result.ok) failed += 1;
139
+ if (!result.ok)
140
+ failed += 1;
168
141
  }
169
142
  console.log(JSON.stringify({
170
143
  schema: TURN_HANDOFF_TERMINAL_SCHEMA,
171
144
  ticket_id: `--all(${open.length})`,
172
145
  status: failed === 0 ? 'succeeded' : 'failed',
173
- error: failed === 0 ? undefined : `${failed}/${open.length} 张工单回收失败`
146
+ error: failed === 0 ? undefined : `${failed}/${open.length} 张工单回收失败`,
174
147
  }));
175
148
  process.exit(failed === 0 ? 0 : 2);
176
149
  }
@@ -178,12 +151,11 @@ const [target, stateRootArg] = process.argv.slice(2);
178
151
  const stateRoot = stateRootArg ?? '.';
179
152
  if (target === '--all') {
180
153
  await collectAll(stateRoot);
181
- } else if (target) {
154
+ }
155
+ else if (target) {
182
156
  await collectOne(stateRoot, target);
183
- } else {
157
+ }
158
+ else {
184
159
  console.error('用法:npx tsx scripts/turn-handoff-collect.ts <ticketId|--all> [stateRoot]');
185
160
  process.exit(1);
186
161
  }
187
-
188
-
189
- //# sourceURL=ts/scripts/turn-handoff-collect.ts