@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,3 +1,32 @@
1
+ /**
2
+ * M3 nightly real-LLM 证据生产器(issue #22 验收「nightly real-LLM 与成本质量证据可复跑」)。
3
+ * ADR-11 巡检层的 nightly 形态:固定 prompt 集 × 真实 LLM,产出被 product-metrics.ts
4
+ * 消费的 `gotry_m3_nightly_run_v1` 记录,追加进私有证据账本 gotry-state/evidence/m3/cohort.jsonl
5
+ * (gitignored;绝不写真实产品状态 ts/dsh-runtime/gotry-state/)。
6
+ *
7
+ * 运行(在 ts/ 下):
8
+ * npx tsx scripts/nightly-evidence.ts # 真跑:需 LLM_API_KEY,追加证据记录
9
+ * npx tsx scripts/nightly-evidence.ts --dry-run # mock 全链演练,绝不写证据
10
+ * npx tsx scripts/nightly-evidence.ts --format json # 机器可读状态(heartbeat 消费)
11
+ * npx tsx scripts/nightly-evidence.ts --no-env-file # 不读仓根 .env(等待态测试用)
12
+ *
13
+ * 纪律:
14
+ * - 无凭证 = waiting_external_evidence + backoff/no-spend:退出码 0,状态 JSON 明示,不写任何文件。
15
+ * - cost_usd 只能来自 dsh-llm 实测 usage × `data/llm-price-table.json` 封存价(peak 保守上界);
16
+ * usage 缺失/模型无价目 = fail-closed,不写记录、不猜价格;价目调整走 PR 改封存文件。
17
+ * - prompt_set_sha256 锚定 `data/m3-nightly-prompts.json` 内容;output_sha256 锚定本轮对话全文。
18
+ * - 预算闸 GOTRY_NIGHTLY_BUDGET_USD(默认 1.00 美元):超闸时记录照写(已发生的花费是事实),
19
+ * 但退出码 3,heartbeat 应 backoff。
20
+ * - 写入前先过消费方 parseNightlyRun 校验——生产器自己写出的必须是评分器读得懂的记录。
21
+ *
22
+ * 价表 schema 演进(issue #49 落地):
23
+ * - `gotry_llm_price_table_v1`:仅 DeepSeek,flat 字段直接挂在 prices.<model> 下,本 loader 兼容读取。
24
+ * - `gotry_llm_price_table_v2`(本仓现状):providers.<id>.models.<model>,provider-aware。
25
+ * v2 引入 provider 别名/价格策略(tiered_peak_offpeak vs flat_no_offpeak),允许 MiniMax/等
26
+ * 没有 off-peak 列的 provider 以同价占位填入 _offpeak 列(peak_conservative_upper_bound 纪律)。
27
+ * - reader 兼容 v1/v2:loadPriceTable 检测 schema_version;v1 走旧 PriceEntry 形态,v2 走
28
+ * ProviderEntry 形态;priceRunCost 与外暴露 API 不变。
29
+ */
1
30
  import { createHash } from 'node:crypto';
2
31
  import { appendFileSync, mkdirSync, readFileSync } from 'node:fs';
3
32
  import { join } from 'node:path';
@@ -10,10 +39,11 @@ import { parseNightlyRun } from './product-metrics.js';
10
39
  const SHA256 = /^[0-9a-f]{64}$/;
11
40
  const DEFAULT_BUDGET_USD = 1;
12
41
  function canonical(value) {
13
- if (Array.isArray(value)) return `[${value.map(canonical).join(',')}]`;
42
+ if (Array.isArray(value))
43
+ return `[${value.map(canonical).join(',')}]`;
14
44
  if (typeof value === 'object' && value !== null) {
15
45
  const record = value;
16
- return `{${Object.keys(record).sort().map((key)=>`${JSON.stringify(key)}:${canonical(record[key])}`).join(',')}}`;
46
+ return `{${Object.keys(record).sort().map(key => `${JSON.stringify(key)}:${canonical(record[key])}`).join(',')}}`;
17
47
  }
18
48
  return JSON.stringify(value);
19
49
  }
@@ -29,23 +59,16 @@ export function loadPromptSet(path) {
29
59
  throw new Error(`prompt set schema_version must be gotry_m3_nightly_prompt_set_v1, got: ${String(raw['schema_version'])}`);
30
60
  }
31
61
  const turns = raw['turns'];
32
- if (!Array.isArray(turns) || turns.length === 0 || turns.some((t)=>typeof t !== 'string' || t.length === 0)) {
62
+ if (!Array.isArray(turns) || turns.length === 0 || turns.some(t => typeof t !== 'string' || t.length === 0)) {
33
63
  throw new Error('prompt set turns must be a non-empty array of non-empty strings');
34
64
  }
35
- return {
36
- schema_version: 'gotry_m3_nightly_prompt_set_v1',
37
- description: raw['description'],
38
- turns: turns
39
- };
65
+ return { schema_version: 'gotry_m3_nightly_prompt_set_v1', description: raw['description'], turns: turns };
40
66
  }
41
67
  function assertPriceEntry(label, value) {
42
68
  const entry = typeof value === 'object' && value !== null ? value : null;
43
- if (!entry) throw new Error(`${label} must be an object`);
44
- for (const field of [
45
- 'input_cache_miss_usd_per_1m_peak',
46
- 'input_cache_hit_usd_per_1m_peak',
47
- 'output_usd_per_1m_peak'
48
- ]){
69
+ if (!entry)
70
+ throw new Error(`${label} must be an object`);
71
+ for (const field of ['input_cache_miss_usd_per_1m_peak', 'input_cache_hit_usd_per_1m_peak', 'output_usd_per_1m_peak']) {
49
72
  const num = entry[field];
50
73
  if (typeof num !== 'number' || !Number.isFinite(num) || num < 0) {
51
74
  throw new Error(`${label}.${field} must be a non-negative finite number`);
@@ -66,53 +89,58 @@ export function loadPriceTable(path) {
66
89
  }
67
90
  function loadPriceTableV1(raw) {
68
91
  const aliases = typeof raw['aliases'] === 'object' && raw['aliases'] !== null ? raw['aliases'] : {};
69
- for (const [from, to] of Object.entries(aliases)){
70
- if (typeof to !== 'string') throw new Error(`price table alias ${from} must map to a model name`);
92
+ for (const [from, to] of Object.entries(aliases)) {
93
+ if (typeof to !== 'string')
94
+ throw new Error(`price table alias ${from} must map to a model name`);
71
95
  }
72
96
  const prices = {};
73
- for (const [name, entry] of Object.entries(typeof raw['prices'] === 'object' && raw['prices'] !== null ? raw['prices'] : {})){
97
+ for (const [name, entry] of Object.entries(typeof raw['prices'] === 'object' && raw['prices'] !== null ? raw['prices'] : {})) {
74
98
  prices[name] = assertPriceEntry(`price table entry ${name}`, entry);
75
99
  }
76
- return {
77
- schema_version: 'gotry_llm_price_table_v1',
78
- aliases,
79
- prices
80
- };
100
+ return { schema_version: 'gotry_llm_price_table_v1', aliases, prices };
81
101
  }
102
+ /** v2 reader:providers.<id>.models.<model> 平铺为统一的 aliases/prices 双表,外暴露 API 不变。
103
+ * lookup 规则:给定 model 名 →
104
+ * 1. 若在 aliases 表中,跳到别名目标(qualified `providerId:model`);
105
+ * 2. 否则直接以 qualified `providerId:model` 在 prices 中查找;
106
+ * 3. 否则尝试裸 `model` 键(单 provider 时透明,如 MiniMax-M3 在 minimax:models 下唯一);
107
+ * 4. 仍找不到 → fail-closed。 */
82
108
  function loadPriceTableV2(raw) {
83
109
  const providersObj = typeof raw['providers'] === 'object' && raw['providers'] !== null ? raw['providers'] : null;
84
- if (!providersObj) throw new Error('price table v2 requires providers object');
110
+ if (!providersObj)
111
+ throw new Error('price table v2 requires providers object');
85
112
  const aliases = {};
86
113
  const prices = {};
87
- for (const [providerId, rawProvider] of Object.entries(providersObj)){
114
+ for (const [providerId, rawProvider] of Object.entries(providersObj)) {
88
115
  const provider = assertProviderEntry(`price table provider ${providerId}`, rawProvider);
89
- for (const [aliasName, target] of Object.entries(provider.aliases)){
90
- if (typeof target !== 'string') throw new Error(`provider ${providerId} alias ${aliasName} must map to a model name`);
116
+ for (const [aliasName, target] of Object.entries(provider.aliases)) {
117
+ if (typeof target !== 'string')
118
+ throw new Error(`provider ${providerId} alias ${aliasName} must map to a model name`);
91
119
  if (!provider.models[target]) {
92
120
  throw new Error(`provider ${providerId} alias ${aliasName} -> ${target} has no entry; update data/llm-price-table.json via PR`);
93
121
  }
94
122
  aliases[aliasName] = target;
95
123
  }
96
- for (const [modelName, entry] of Object.entries(provider.models)){
124
+ for (const [modelName, entry] of Object.entries(provider.models)) {
97
125
  const qualified = `${providerId}:${modelName}`;
98
126
  const validated = assertPriceEntry(`price table provider ${providerId} model ${modelName}`, entry);
99
- if (prices[qualified]) throw new Error(`duplicate price table key ${qualified}`);
127
+ if (prices[qualified])
128
+ throw new Error(`duplicate price table key ${qualified}`);
100
129
  prices[qualified] = validated;
101
- if (prices[modelName]) throw new Error(`duplicate price table key ${modelName} (across providers, disambiguate via aliases or qualified lookup)`);
130
+ if (prices[modelName])
131
+ throw new Error(`duplicate price table key ${modelName} (across providers, disambiguate via aliases or qualified lookup)`);
102
132
  prices[modelName] = validated;
103
133
  }
104
134
  }
105
- return {
106
- schema_version: 'gotry_llm_price_table_v2',
107
- aliases,
108
- prices
109
- };
135
+ return { schema_version: 'gotry_llm_price_table_v2', aliases, prices };
110
136
  }
111
137
  function assertProviderEntry(label, value) {
112
138
  const entry = typeof value === 'object' && value !== null ? value : null;
113
- if (!entry) throw new Error(`${label} must be an object`);
139
+ if (!entry)
140
+ throw new Error(`${label} must be an object`);
114
141
  const family = entry['family'];
115
- if (typeof family !== 'string' || family.length === 0) throw new Error(`${label}.family must be a non-empty string`);
142
+ if (typeof family !== 'string' || family.length === 0)
143
+ throw new Error(`${label}.family must be a non-empty string`);
116
144
  const strategy = entry['price_strategy'];
117
145
  if (strategy !== 'tiered_peak_offpeak' && strategy !== 'flat_no_offpeak') {
118
146
  throw new Error(`${label}.price_strategy must be 'tiered_peak_offpeak' or 'flat_no_offpeak', got: ${String(strategy)}`);
@@ -125,37 +153,33 @@ function assertProviderEntry(label, value) {
125
153
  api_base: typeof entry['api_base'] === 'string' ? entry['api_base'] : undefined,
126
154
  source_url: typeof entry['source_url'] === 'string' ? entry['source_url'] : undefined,
127
155
  aliases,
128
- models
156
+ models,
129
157
  };
130
158
  }
159
+ /** 换算单次运行成本(peak 保守上界:全部输入按 cache miss 峰价计,只高不低)。 */
131
160
  export function priceRunCost(model, usage, table) {
132
161
  if (usage.responsesMissingUsage > 0) {
133
162
  throw new Error(`usage provider omitted usage on ${usage.responsesMissingUsage} response(s) — cost unprovable, refusing to price (fail-closed)`);
134
163
  }
135
164
  const alias = table.aliases[model];
136
- if (alias && !table.prices[alias]) throw new Error(`price table alias ${model} -> ${alias} has no entry; update data/llm-price-table.json via PR`);
165
+ if (alias && !table.prices[alias])
166
+ throw new Error(`price table alias ${model} -> ${alias} has no entry; update data/llm-price-table.json via PR`);
137
167
  const entry = table.prices[alias ?? model];
138
- if (!entry) throw new Error(`price table has no entry for model ${model}; update data/llm-price-table.json via PR (fail-closed, no guessed price)`);
139
- const cost = (usage.inputCacheMissTokens * entry.input_cache_miss_usd_per_1m_peak + usage.inputCacheHitTokens * entry.input_cache_hit_usd_per_1m_peak + usage.outputTokens * entry.output_usd_per_1m_peak) / 1_000_000;
168
+ if (!entry)
169
+ throw new Error(`price table has no entry for model ${model}; update data/llm-price-table.json via PR (fail-closed, no guessed price)`);
170
+ const cost = (usage.inputCacheMissTokens * entry.input_cache_miss_usd_per_1m_peak
171
+ + usage.inputCacheHitTokens * entry.input_cache_hit_usd_per_1m_peak
172
+ + usage.outputTokens * entry.output_usd_per_1m_peak) / 1_000_000;
140
173
  return round6(cost);
141
174
  }
175
+ /** 记录构建(确定性:同内容同 executed_at => 同 run_key)。写入前由消费方 parseNightlyRun 校验。 */
142
176
  export function buildNightlyRunRecord(input) {
143
177
  const { schema_version, executed_at, real_llm, prompt_set_sha256, output_sha256, cost_usd } = input;
144
- const core = {
145
- schema_version,
146
- executed_at,
147
- real_llm,
148
- prompt_set_sha256,
149
- output_sha256,
150
- cost_usd
151
- };
178
+ const core = { schema_version, executed_at, real_llm, prompt_set_sha256, output_sha256, cost_usd };
152
179
  if (!SHA256.test(prompt_set_sha256) || !SHA256.test(output_sha256)) {
153
180
  throw new Error('nightly record hashes must be lowercase SHA-256 digests');
154
181
  }
155
- const record = {
156
- ...core,
157
- run_key: `hmac-sha256:${createHash('sha256').update(canonical(core)).digest('hex')}`
158
- };
182
+ const record = { ...core, run_key: `hmac-sha256:${createHash('sha256').update(canonical(core)).digest('hex')}` };
159
183
  parseNightlyRun(JSON.parse(JSON.stringify(record)), 0);
160
184
  return record;
161
185
  }
@@ -167,14 +191,10 @@ function arg(name) {
167
191
  return index >= 0 ? process.argv[index + 1] : undefined;
168
192
  }
169
193
  export async function runNightlyEvidence(options) {
170
- const clock = options.clock ?? (()=>new Date());
194
+ const clock = options.clock ?? (() => new Date());
171
195
  const llmKey = process.env['LLM_API_KEY'] ?? process.env['DEEPSEEK_API_KEY'];
172
196
  if (!options.dryRun && !llmKey) {
173
- return {
174
- state: 'waiting_external_evidence',
175
- record: null,
176
- reason: 'no real LLM credential (LLM_API_KEY/DEEPSEEK_API_KEY); waiting & backoff & no-spend per issue #22 停机纪律'
177
- };
197
+ return { state: 'waiting_external_evidence', record: null, reason: 'no real LLM credential (LLM_API_KEY/DEEPSEEK_API_KEY); waiting & backoff & no-spend per issue #22 停机纪律' };
178
198
  }
179
199
  const promptSet = loadPromptSet('data/m3-nightly-prompts.json');
180
200
  const priceTable = loadPriceTable('data/llm-price-table.json');
@@ -183,24 +203,13 @@ export async function runNightlyEvidence(options) {
183
203
  const port = options.dryRun ? createMockLlm(packPath) : createOpenAICompatLlm(packPath);
184
204
  const state = newState();
185
205
  const history = [];
186
- for (const turn of promptSet.turns){
187
- const { reply } = await runTurn(state, turn, port, [
188
- ...history
189
- ], solvePort);
190
- history.push({
191
- role: 'user',
192
- text: turn
193
- }, {
194
- role: 'assistant',
195
- text: reply
196
- });
206
+ for (const turn of promptSet.turns) {
207
+ const { reply } = await runTurn(state, turn, port, [...history], solvePort);
208
+ history.push({ role: 'user', text: turn }, { role: 'assistant', text: reply });
197
209
  }
198
210
  const promptSetSha = sha256Of(promptSet);
199
- const turnsTranscript = promptSet.turns.map((turn, index)=>({
200
- turn_index: index,
201
- user: turn,
202
- reply: history[index * 2 + 1]?.text ?? ''
203
- }));
211
+ // history user/assistant 交错序列;assistant 回复落在每个 user 之后(index*2+1)
212
+ const turnsTranscript = promptSet.turns.map((turn, index) => ({ turn_index: index, user: turn, reply: history[index * 2 + 1]?.text ?? '' }));
204
213
  const outputSha = sha256Of(turnsTranscript);
205
214
  let record;
206
215
  if (options.dryRun) {
@@ -210,16 +219,13 @@ export async function runNightlyEvidence(options) {
210
219
  real_llm: false,
211
220
  prompt_set_sha256: promptSetSha,
212
221
  output_sha256: outputSha,
213
- cost_usd: 0
222
+ cost_usd: 0,
214
223
  });
215
- return {
216
- state: 'dry_run',
217
- record,
218
- reason: 'dry-run exercises the pipeline against mock LLM; no evidence written'
219
- };
224
+ return { state: 'dry_run', record, reason: 'dry-run exercises the pipeline against mock LLM; no evidence written' };
220
225
  }
221
226
  const usage = port.usage;
222
- if (!usage) throw new Error('real LLM port did not expose usage tracker (dsh-llm regression) — refusing to write cost evidence');
227
+ if (!usage)
228
+ throw new Error('real LLM port did not expose usage tracker (dsh-llm regression) — refusing to write cost evidence');
223
229
  if (usage.calls !== promptSet.turns.length) {
224
230
  throw new Error(`usage calls ${usage.calls} != expected turns ${promptSet.turns.length} — fail-closed, no evidence written`);
225
231
  }
@@ -230,60 +236,51 @@ export async function runNightlyEvidence(options) {
230
236
  real_llm: true,
231
237
  prompt_set_sha256: promptSetSha,
232
238
  output_sha256: outputSha,
233
- cost_usd: costUsd
234
- });
235
- mkdirSync(options.evidenceRoot, {
236
- recursive: true
239
+ cost_usd: costUsd,
237
240
  });
241
+ mkdirSync(options.evidenceRoot, { recursive: true });
238
242
  appendFileSync(join(options.evidenceRoot, 'cohort.jsonl'), `${JSON.stringify(record)}\n`, 'utf8');
239
243
  const budget = Number(process.env['GOTRY_NIGHTLY_BUDGET_USD'] ?? DEFAULT_BUDGET_USD);
240
244
  const overBudget = Number.isFinite(budget) && budget > 0 ? costUsd > budget : false;
241
- return {
242
- state: 'evidence_written',
243
- record,
244
- cost_over_budget: overBudget,
245
- reason: overBudget ? `cost_usd ${costUsd} exceeds GOTRY_NIGHTLY_BUDGET_USD ${budget} — backoff suggested` : undefined
246
- };
245
+ return { state: 'evidence_written', record, cost_over_budget: overBudget, reason: overBudget ? `cost_usd ${costUsd} exceeds GOTRY_NIGHTLY_BUDGET_USD ${budget} — backoff suggested` : undefined };
247
246
  }
248
247
  function output(payload, asJson) {
249
- if (asJson) console.log(JSON.stringify(payload));
248
+ if (asJson)
249
+ console.log(JSON.stringify(payload));
250
250
  else {
251
251
  console.log(`nightly-evidence state: ${payload.state}${payload.error ? ` — ${payload.error}` : ''}`);
252
- if (payload.record) console.log(JSON.stringify(payload.record, null, 2));
253
- if (payload.reason) console.log(payload.reason);
252
+ if (payload.record)
253
+ console.log(JSON.stringify(payload.record, null, 2));
254
+ if (payload.reason)
255
+ console.log(payload.reason);
254
256
  }
255
257
  }
256
258
  async function main() {
259
+ // 仓根 .env(gitignored):每行 KEY=VALUE,不覆盖已有环境变量;--no-env-file 供等待态测试绕过
257
260
  if (!process.argv.includes('--no-env-file')) {
258
261
  try {
259
- for (const line of readFileSync(join('..', '.env'), 'utf-8').split('\n')){
262
+ for (const line of readFileSync(join('..', '.env'), 'utf-8').split('\n')) {
260
263
  const m = line.match(/^([A-Z_]+)=(.*)$/);
261
- if (m && !process.env[m[1]]) process.env[m[1]] = m[2].trim();
264
+ if (m && !process.env[m[1]])
265
+ process.env[m[1]] = m[2].trim();
262
266
  }
263
- } catch {}
267
+ }
268
+ catch { /* .env 可选 */ }
264
269
  }
265
270
  const asJson = (arg('--format') ?? 'markdown') === 'json';
266
271
  const evidenceRoot = arg('--evidence-root') ?? 'gotry-state/evidence/m3';
267
272
  const dryRun = process.argv.includes('--dry-run');
268
273
  try {
269
- const result = await runNightlyEvidence({
270
- evidenceRoot,
271
- dryRun
272
- });
274
+ const result = await runNightlyEvidence({ evidenceRoot, dryRun });
273
275
  output(result, asJson);
274
- if (result.cost_over_budget) process.exitCode = 3;
275
- } catch (error) {
276
- output({
277
- state: 'error',
278
- record: null,
279
- error: error.message
280
- }, asJson);
276
+ if (result.cost_over_budget)
277
+ process.exitCode = 3;
278
+ }
279
+ catch (error) {
280
+ output({ state: 'error', record: null, error: error.message }, asJson);
281
281
  process.exitCode = 1;
282
282
  }
283
283
  }
284
284
  if (process.argv[1]?.endsWith('nightly-evidence.ts')) {
285
285
  await main();
286
286
  }
287
-
288
-
289
- //# sourceURL=ts/scripts/nightly-evidence.ts
@@ -1,3 +1,20 @@
1
+ /**
2
+ * 「下一次出发」回访摘要(roadmap M4 交付「主动回访(可关闭)」的骨架):
3
+ * 读 wish pool + 效用 sidecar,按当前窗口条件匹配 0..1 条建议,渲染成摘要,
4
+ * 经可插拔通道送出——通道 key 未配时骨架降级为 stdout/file,即插即用。
5
+ *
6
+ * 通道(环境变量,无新依赖,fetch 为 Node 18+ 全局):
7
+ * GOTRY_NUDGE_ENABLED=false 全局关闭(契约:可关闭;其余配置再对也直接退出)
8
+ * GOTRY_NUDGE_CHANNEL=stdout|file|lark 默认 stdout
9
+ * GOTRY_NUDGE_FILE=<path> file 通道输出位置,默认 <stateRoot>/gotry-state/nudge-digest.md
10
+ * GOTRY_LARK_WEBHOOK=<url> lark 通道 webhook(founder 侧配置即生效)
11
+ *
12
+ * 纪律:0..1(muted 永不召回,无命中不硬推);lark 投递失败降级 stdout 不抛
13
+ * (回访是 sidecar 性质,永不阻塞主路径);本脚本只读状态文件。
14
+ *
15
+ * 运行:cd ts && npx tsx scripts/nudge-digest.ts [--days N] [--budget N] [--month M] [--state-root DIR]
16
+ * 无参数时 days/budget 不参与评分,month 取系统当月。
17
+ */
1
18
  import { writeFileSync, mkdirSync } from 'node:fs';
2
19
  import { join, dirname } from 'node:path';
3
20
  import { pickNudgeWish } from '../src/wish-pool.js';
@@ -11,6 +28,7 @@ function arg(name) {
11
28
  const stateRoot = arg('--state-root') ?? '.';
12
29
  const stateDir = join(stateRoot, 'gotry-state');
13
30
  function readEvents() {
31
+ // ADR-15:账本优先,未迁移 root 回退旧文件(只读)
14
32
  return readUtilityEventsWithFallback(stateRoot);
15
33
  }
16
34
  if (process.env['GOTRY_NUDGE_ENABLED'] === 'false') {
@@ -22,20 +40,20 @@ const anchor = buildTimeAnchor(new Date());
22
40
  const ctx = {
23
41
  days: arg('--days') ? Number(arg('--days')) : undefined,
24
42
  budgetCny: arg('--budget') ? Number(arg('--budget')) : undefined,
25
- month: arg('--month') ? Number(arg('--month')) : new Date().getMonth() + 1
43
+ month: arg('--month') ? Number(arg('--month')) : new Date().getMonth() + 1,
26
44
  };
27
45
  const match = pickNudgeWish(pool, ctx);
28
- const lines = [
29
- `# 「下一次出发」回访摘要(${anchor.today} ${anchor.todayWeekdayZh})`
30
- ];
46
+ const lines = [`# 「下一次出发」回访摘要(${anchor.today} ${anchor.todayWeekdayZh})`];
31
47
  if (!match) {
32
- lines.push(`在册 ${pool.filter((p)=>!p.muted).length} 条憧憬,当前窗口无可成行匹配——不打扰(0..1 纪律:不硬推)。`);
33
- } else {
34
- const c = match.entry.conditions ?? {};
48
+ lines.push(`在册 ${pool.filter(p => !p.muted).length} 条憧憬,当前窗口无可成行匹配——不打扰(0..1 纪律:不硬推)。`);
49
+ }
50
+ else {
51
+ const c = (match.entry.conditions ?? {});
35
52
  const u = projectUtility(readEvents())[match.wishId];
36
53
  lines.push(`**${String(match.entry.name ?? match.wishId)}**(${match.hits.join(' + ')},命中 ${match.score}/3 项)`);
37
54
  lines.push(`- 成行条件: ${JSON.stringify(c)}`);
38
- if (match.entry.reason) lines.push(`- 当初为什么: ${String(match.entry.reason).slice(0, 160)}`);
55
+ if (match.entry.reason)
56
+ lines.push(`- 当初为什么: ${String(match.entry.reason).slice(0, 160)}`);
39
57
  lines.push(`- 效用状态: ${u?.status ?? 'unknown'}(被召回 ${u?.recalled ?? 0} 次)`);
40
58
  lines.push('');
41
59
  lines.push('_想安排就说一声;不想被打扰说「mute <名字>」即可休眠,憧憬不被拒绝。_');
@@ -44,40 +62,33 @@ const digest = lines.join('\n');
44
62
  const channel = process.env['GOTRY_NUDGE_CHANNEL'] ?? 'stdout';
45
63
  if (channel === 'file') {
46
64
  const out = process.env['GOTRY_NUDGE_FILE'] ?? join(stateDir, 'nudge-digest.md');
47
- mkdirSync(dirname(out), {
48
- recursive: true
49
- });
65
+ mkdirSync(dirname(out), { recursive: true });
50
66
  writeFileSync(out, digest + '\n', 'utf-8');
51
67
  console.log(`摘要已写入 ${out}`);
52
- } else if (channel === 'lark') {
68
+ }
69
+ else if (channel === 'lark') {
53
70
  const webhook = process.env['GOTRY_LARK_WEBHOOK'];
54
71
  if (!webhook) {
55
72
  console.log('lark 通道未配置(GOTRY_LARK_WEBHOOK 缺失)——降级 stdout,即插即用等 key:');
56
73
  console.log(digest);
57
- } else {
74
+ }
75
+ else {
58
76
  try {
59
77
  const resp = await fetch(webhook, {
60
78
  method: 'POST',
61
- headers: {
62
- 'Content-Type': 'application/json'
63
- },
64
- body: JSON.stringify({
65
- msg_type: 'text',
66
- content: {
67
- text: digest
68
- }
69
- })
79
+ headers: { 'Content-Type': 'application/json' },
80
+ body: JSON.stringify({ msg_type: 'text', content: { text: digest } }),
70
81
  });
71
82
  console.log(`lark 投递 HTTP ${resp.status}`);
72
- if (!resp.ok) console.log(digest);
73
- } catch (e) {
83
+ if (!resp.ok)
84
+ console.log(digest); // 失败降级 stdout,不丢摘要
85
+ }
86
+ catch (e) {
74
87
  console.log(`lark 投递失败(${e.message})——降级 stdout:`);
75
88
  console.log(digest);
76
89
  }
77
90
  }
78
- } else {
91
+ }
92
+ else {
79
93
  console.log(digest);
80
94
  }
81
-
82
-
83
- //# sourceURL=ts/scripts/nudge-digest.ts