@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.
- package/README.md +102 -194
- package/README.zh-CN.md +115 -210
- package/bin/gotry-backend.js +53 -0
- package/bin/gotry-bootstrap.js +466 -35
- package/bin/gotry-inner.js +255 -36
- package/bin/gotry-process-liveness.d.ts +27 -0
- package/bin/gotry-process-liveness.js +97 -0
- package/bin/gotry-runtime-resolution.d.ts +1 -1
- package/bin/gotry-runtime-resolution.js +2 -2
- package/client/client.js +174 -0
- package/cordis.gotry-patch.yml +70 -93
- package/dist/capabilities/agent-reach-deep.js +45 -122
- package/dist/capabilities/agent-reach.js +63 -148
- package/dist/capabilities/anything.js +89 -75
- package/dist/capabilities/artifacts.js +123 -104
- package/dist/capabilities/channel-health.js +65 -45
- package/dist/capabilities/channel-registry.js +108 -87
- package/dist/capabilities/doctor.js +357 -235
- package/dist/capabilities/effect.js +223 -309
- package/dist/capabilities/fact-log.js +28 -14
- package/dist/capabilities/flyai.js +245 -156
- package/dist/capabilities/ground-transfer.js +509 -0
- package/dist/capabilities/hbcli.js +144 -148
- package/dist/capabilities/hotelbyte-transaction.js +194 -0
- package/dist/capabilities/incident-log.js +131 -61
- package/dist/capabilities/model-override.js +30 -10
- package/dist/capabilities/opensky.js +54 -43
- package/dist/capabilities/resilience.js +50 -32
- package/dist/capabilities/session/action-cache.js +50 -44
- package/dist/capabilities/session/adapters/ctrip-flight.js +119 -62
- package/dist/capabilities/session/adapters/ctrip-hotel.js +125 -110
- package/dist/capabilities/session/adapters/dida-portal.js +206 -0
- package/dist/capabilities/session/adapters/meituan-local.js +35 -54
- package/dist/capabilities/session/adapters/rail-12306.js +169 -112
- package/dist/capabilities/session/benchmark.js +85 -149
- package/dist/capabilities/session/extension-bridge.js +320 -259
- package/dist/capabilities/session/extension-channel.js +57 -76
- package/dist/capabilities/session/extension-distribution.js +115 -119
- package/dist/capabilities/session/extract.js +31 -21
- package/dist/capabilities/session/golden-score.js +55 -33
- package/dist/capabilities/session/health-watch.js +146 -154
- package/dist/capabilities/session/read-guard.js +55 -47
- package/dist/capabilities/session/static-flight-golden.js +49 -36
- package/dist/capabilities/session/transport.js +54 -61
- package/dist/capabilities/session/wizard.js +63 -93
- package/dist/capabilities/session-consent.js +143 -47
- package/dist/capabilities/session-login.js +95 -126
- package/dist/capabilities/session-search.js +391 -253
- package/dist/capabilities/sponsor-plugin.js +341 -0
- package/dist/capabilities/visa-policy.js +87 -73
- package/dist/capabilities/weather.js +163 -235
- package/dist/scripts/action-cache-tests.js +24 -59
- package/dist/scripts/agent-planning-turn-deadline-e2e.js +91 -142
- package/dist/scripts/agent-planning-turn-deadline-tests.js +80 -176
- package/dist/scripts/agent-reach-deep-tests.js +30 -40
- package/dist/scripts/agent-reach-tests.js +26 -18
- package/dist/scripts/agent-reach-wrapper-tests.js +46 -50
- package/dist/scripts/anything-tests.js +58 -62
- package/dist/scripts/artifact-client-contract-tests.js +69 -0
- package/dist/scripts/artifacts-capability-tests.js +235 -0
- package/dist/scripts/async-collect.js +26 -15
- package/dist/scripts/benchmark-environment-bridge-e2e.js +287 -684
- package/dist/scripts/benchmark-environment-bridge-tests.js +818 -3123
- package/dist/scripts/booking-copilot-availability-ledger-binding-tests.js +66 -279
- package/dist/scripts/booking-copilot-availability-policy-tests.js +153 -1257
- package/dist/scripts/booking-copilot-bin-proof-tests.js +22 -45
- package/dist/scripts/booking-copilot-crossrepo-fixture-server.js +64 -76
- package/dist/scripts/booking-copilot-dsh-core-proof-tests.js +69 -199
- package/dist/scripts/booking-copilot-dsh-planner-proof-tests.js +393 -759
- package/dist/scripts/booking-copilot-dsh-plugin-proof-tests.js +33 -75
- package/dist/scripts/booking-copilot-event-sequence-concurrency-proof-tests.js +60 -110
- package/dist/scripts/booking-copilot-gap-code-contract-proof-tests.js +28 -66
- package/dist/scripts/booking-copilot-operation-ledger-concurrency-proof-tests.js +72 -271
- package/dist/scripts/booking-copilot-receipt-ledger-concurrency-proof-tests.js +73 -275
- package/dist/scripts/booking-copilot-runtime-proof-tests.js +784 -3398
- package/dist/scripts/booking-copilot-server-proof-tests.js +63 -168
- package/dist/scripts/booking-copilot-startup-proof-tests.js +38 -124
- package/dist/scripts/booking-executor-tests.js +80 -0
- package/dist/scripts/booking-recovery-chain-tests.js +182 -0
- package/dist/scripts/booking-saga-tests.js +61 -123
- package/dist/scripts/booking-surface-contract-proof-tests.js +152 -1167
- package/dist/scripts/bootstrap-tests.js +1383 -128
- package/dist/scripts/build-changelog.js +89 -130
- package/dist/scripts/build-metrics-report.js +135 -143
- package/dist/scripts/changelog-tests.js +35 -82
- package/dist/scripts/channel-probe-tests.js +44 -116
- package/dist/scripts/channel-probe.js +84 -167
- package/dist/scripts/channel-registry-tests.js +97 -185
- package/dist/scripts/companion-tests.js +25 -70
- package/dist/scripts/dida-cookie-probe.js +87 -0
- package/dist/scripts/dida-sw-probe.js +114 -0
- package/dist/scripts/diff-test.js +17 -12
- package/dist/scripts/doctor-tests.js +428 -120
- package/dist/scripts/dsh-artifact-e2e.js +150 -0
- package/dist/scripts/dsh-artifact-web-e2e.js +443 -0
- package/dist/scripts/dsh-http-proxy-sse-proof.js +90 -0
- package/dist/scripts/dsh-runtime-closure-tests.js +110 -153
- package/dist/scripts/dsh-runtime-closure.js +75 -63
- package/dist/scripts/dsh-session-v3-migration-proof.js +106 -0
- package/dist/scripts/dsh-subprocess-local-proof.js +149 -0
- package/dist/scripts/dsh-target-closure-proof.js +178 -0
- package/dist/scripts/effect-tests.js +165 -552
- package/dist/scripts/engine-run.js +1 -3
- package/dist/scripts/engine-tests.js +16 -10
- package/dist/scripts/evaluation-cadence-tests.js +69 -242
- package/dist/scripts/evaluation-contract-tests.js +193 -499
- package/dist/scripts/extension-distribution-cli.js +19 -15
- package/dist/scripts/extension-distribution-tests.js +98 -211
- package/dist/scripts/extension-tests.js +366 -389
- package/dist/scripts/fact-gate-tests.js +725 -263
- package/dist/scripts/fixtures/extension-bridge-unref-child.mjs +21 -0
- package/dist/scripts/flight-hotel-anchor-363-registered-e2e.js +169 -0
- package/dist/scripts/flight-malformed-tests.js +159 -0
- package/dist/scripts/flyai-tests.js +325 -68
- package/dist/scripts/g5-guard-tests.js +131 -0
- package/dist/scripts/g5-guard.js +189 -0
- package/dist/scripts/gen-lunar-anchors.js +106 -0
- package/dist/scripts/gotry-backend-kernel-tests.js +79 -0
- package/dist/scripts/ground-transfer-tests.js +956 -0
- package/dist/scripts/hbcli-e2e-tests.js +71 -90
- package/dist/scripts/hbcli-live-optin-tests.js +96 -0
- package/dist/scripts/hbcli-release-contract-tests.js +122 -0
- package/dist/scripts/hbcli-tests.js +41 -115
- package/dist/scripts/health-watch-cli.js +42 -28
- package/dist/scripts/hotel-date-gate-tests.js +496 -0
- package/dist/scripts/hotelbyte-reconcile-tests.js +141 -0
- package/dist/scripts/hotelbyte-spawn-e2e-tests.js +373 -0
- package/dist/scripts/i18n-tests.js +16 -9
- package/dist/scripts/incident-tests.js +281 -87
- package/dist/scripts/issue-194-job-id-guard-tests.js +106 -0
- package/dist/scripts/issue-233-cancel-refund-commission-tests.js +392 -0
- package/dist/scripts/issue-271-liveness-tests.js +547 -0
- package/dist/scripts/issue-289-model-retry-real-tests.js +296 -0
- package/dist/scripts/issue-289-model-retry-tests.js +441 -0
- package/dist/scripts/issue-289-web-retry-e2e.js +630 -0
- package/dist/scripts/issue-338-home-city-e2e.js +577 -0
- package/dist/scripts/issue343-real-entry-e2e.js +160 -0
- package/dist/scripts/journey-tests.js +22 -27
- package/dist/scripts/kernel-manifest-evidence.js +109 -0
- package/dist/scripts/kernel-manifest-gate.js +126 -0
- package/dist/scripts/kernel-manifest-tests.js +212 -0
- package/dist/scripts/kernel-manifest-trace-entry.js +7 -0
- package/dist/scripts/kernel-manifest-trace-hook.mjs +28 -0
- package/dist/scripts/kernel-manifest.js +369 -0
- package/dist/scripts/ledger-repair-apply-tests.js +316 -0
- package/dist/scripts/ledger-repair-plan-tests.js +375 -0
- package/dist/scripts/ledger-tests.js +325 -340
- package/dist/scripts/ledger-workflow-crash.js +10 -20
- package/dist/scripts/map-tools-vendor-package-proof.js +206 -134
- package/dist/scripts/memory-capture-tests.js +242 -61
- package/dist/scripts/memory-decay-tests.js +18 -52
- package/dist/scripts/memory-lifecycle-tests.js +657 -0
- package/dist/scripts/memory-lifecycle.js +196 -0
- package/dist/scripts/memory-metrics.js +24 -12
- package/dist/scripts/memory-value-report.js +474 -222
- package/dist/scripts/memory-value-tests.js +328 -0
- package/dist/scripts/metrics-report-tests.js +62 -199
- package/dist/scripts/model-override-e2e.js +66 -105
- package/dist/scripts/nightly-evidence-tests.js +64 -89
- package/dist/scripts/nightly-evidence.js +108 -111
- package/dist/scripts/nudge-digest.js +39 -28
- package/dist/scripts/onboarding-tests.js +63 -88
- package/dist/scripts/opensky-check.js +18 -13
- package/dist/scripts/opensky-tests.js +28 -28
- package/dist/scripts/persona-surface-guard-tests.js +37 -12
- package/dist/scripts/pnpm-dsh-closure-proof.js +2 -5
- package/dist/scripts/policy-anchor-359-registered-e2e.js +214 -0
- package/dist/scripts/price-drift-tests.js +126 -227
- package/dist/scripts/price-drift-watch.js +209 -253
- package/dist/scripts/probe-poi-tests.js +52 -45
- package/dist/scripts/product-metrics.js +187 -273
- package/dist/scripts/publish-preverify.js +47 -41
- package/dist/scripts/realtime-pricing-tests.js +37 -76
- package/dist/scripts/replay-async.js +20 -18
- package/dist/scripts/replay-real.js +16 -18
- package/dist/scripts/replay.js +52 -82
- package/dist/scripts/session-attach-diagnose.js +16 -12
- package/dist/scripts/session-attach-poc.js +34 -37
- package/dist/scripts/session-benchmark.js +66 -147
- package/dist/scripts/session-dida-live-e2e.js +151 -0
- package/dist/scripts/session-extract-tests.js +14 -35
- package/dist/scripts/session-login.js +14 -13
- package/dist/scripts/session-tests.js +545 -583
- package/dist/scripts/sf-live-benchmark.js +92 -110
- package/dist/scripts/sf-live-cli-tests.js +2 -9
- package/dist/scripts/sf-soft-score-tests.js +19 -60
- package/dist/scripts/sf-summary-tests.js +149 -0
- package/dist/scripts/sf-summary.js +466 -84
- package/dist/scripts/skeleton-check.js +16 -14
- package/dist/scripts/skeleton-integration-test.js +11 -10
- package/dist/scripts/skills-contract-tests.js +51 -46
- package/dist/scripts/smoke-session-gate-tests.js +12 -20
- package/dist/scripts/smoke.js +541 -422
- package/dist/scripts/sponsor-reuse-tests.js +262 -0
- package/dist/scripts/state-cli-tests.js +226 -101
- package/dist/scripts/state-cli.js +549 -212
- package/dist/scripts/static-golden-tests.js +46 -180
- package/dist/scripts/time-eval-tests.js +210 -192
- package/dist/scripts/travel-timeline-tests.js +29 -93
- package/dist/scripts/turn-handoff-collect-tests.js +46 -93
- package/dist/scripts/turn-handoff-collect.js +71 -99
- package/dist/scripts/turn-policy-tests.js +28 -29
- package/dist/scripts/typed-contract-canary.js +77 -162
- package/dist/scripts/tz-resolver-sanity.js +50 -0
- package/dist/scripts/tz-resolver-tests.js +584 -0
- package/dist/scripts/unified-tests.js +23 -19
- package/dist/scripts/visa-policy-tests.js +31 -44
- package/dist/scripts/weather-tests.js +121 -543
- package/dist/scripts/wish-channel-gate-tests.js +29 -86
- package/dist/scripts/write-gate-tests.js +522 -0
- package/dist/scripts/z3-lifecycle-fault-tests.js +188 -0
- package/dist/scripts/z3-lifecycle-tests.js +170 -0
- package/dist/scripts/z3-race-repeat-tests.js +39 -0
- package/dist/scripts/z3-race-tests.js +18 -12
- package/dist/src/artifact-gate.js +652 -221
- package/dist/src/backend/kernel.js +91 -0
- package/dist/src/backend/modules/booking-copilot.js +55 -0
- package/dist/src/backend/modules/booking-executor.js +124 -0
- package/dist/src/backend/modules/session-search.js +194 -0
- package/dist/src/benchmark-agent-conformance.js +296 -262
- package/dist/src/benchmark-environment-bridge.js +298 -413
- package/dist/src/benchmark-headless-child-diagnostics.js +93 -100
- package/dist/src/benchmark-tool-isolation.js +94 -67
- package/dist/src/bookable-facts.js +299 -174
- package/dist/src/booking-saga.js +64 -120
- package/dist/src/booking-surface/availability-policy.js +443 -576
- package/dist/src/booking-surface/cancel-refund-commission.js +379 -0
- package/dist/src/booking-surface/contracts.js +24 -80
- package/dist/src/booking-surface/dsh-planner.js +510 -281
- package/dist/src/booking-surface/dsh-plugin.js +17 -1
- package/dist/src/booking-surface/error-codes.js +82 -10
- package/dist/src/booking-surface/index.js +0 -3
- package/dist/src/booking-surface/profile.js +10 -54
- package/dist/src/booking-surface/recovery-chain.js +367 -0
- package/dist/src/booking-surface/runtime.js +765 -891
- package/dist/src/booking-surface/server.js +200 -251
- package/dist/src/booking-surface/startup.js +58 -54
- package/dist/src/booking-surface/validation.js +150 -218
- package/dist/src/bridge.js +20 -13
- package/dist/src/companions.js +48 -74
- package/dist/src/contracts.js +21 -43
- package/dist/src/dsh-llm.js +82 -120
- package/dist/src/engine.js +253 -239
- package/dist/src/evaluation-cadence.js +72 -209
- package/dist/src/evaluation-contracts.js +220 -843
- package/dist/src/flight-pack-adapter.js +54 -0
- package/dist/src/flight-pack-contract.js +2 -0
- package/dist/src/gotry-backend.js +70 -0
- package/dist/src/hotel-date-gate.js +118 -0
- package/dist/src/i18n.js +18 -7
- package/dist/src/index.js +1425 -1629
- package/dist/src/journey.js +132 -81
- package/dist/src/ledger-repair-apply.js +538 -0
- package/dist/src/ledger-repair-plan.js +463 -0
- package/dist/src/loop.js +257 -166
- package/dist/src/memory-capture.js +180 -32
- package/dist/src/memory-decay.js +30 -14
- package/dist/src/memory-lifecycle.js +1310 -0
- package/dist/src/memory-utility.js +27 -40
- package/dist/src/mock-llm.js +36 -61
- package/dist/src/model.js +81 -36
- package/dist/src/realtime-pricing.js +63 -72
- package/dist/src/slot-spec.js +84 -106
- package/dist/src/state-ledger.js +310 -471
- package/dist/src/subagent-job-id-guard.js +58 -0
- package/dist/src/time-anchor.js +231 -33
- package/dist/src/tool-packet.js +20 -8
- package/dist/src/travel-slots.js +58 -35
- package/dist/src/travel-timeline.js +44 -63
- package/dist/src/turn-deadline.js +121 -127
- package/dist/src/turn-policy.js +61 -89
- package/dist/src/tz-resolver.js +143 -0
- package/dist/src/unified.js +504 -381
- package/dist/src/wish-pool.js +39 -14
- package/dist/src/write-gate.js +495 -0
- package/dist/src/z3-shared.js +273 -7
- package/extension/README.md +4 -3
- package/extension/background.js +125 -14
- package/extension/content-bridge.js +23 -1
- package/extension/content-main.js +15 -4
- package/extension/manifest.json +16 -8
- package/package.json +251 -220
- package/ts/capabilities/agent-reach.ts +4 -4
- package/ts/capabilities/anything.ts +1 -1
- package/ts/capabilities/artifacts.ts +46 -9
- package/ts/capabilities/flyai.ts +156 -37
- package/ts/capabilities/hbcli.ts +1 -1
- package/ts/capabilities/incident-log.ts +88 -59
- package/ts/capabilities/session/adapters/ctrip-flight.ts +99 -36
- package/ts/capabilities/session/adapters/dida-portal.ts +250 -0
- package/ts/capabilities/session/adapters/rail-12306.ts +123 -24
- package/ts/capabilities/session/extension-bridge.ts +226 -101
- package/ts/capabilities/session/extension-channel.ts +22 -9
- package/ts/capabilities/session/wizard.ts +2 -2
- package/ts/capabilities/session-consent.ts +98 -14
- package/ts/capabilities/session-login.ts +1 -0
- package/ts/capabilities/session-search.ts +315 -31
- package/ts/dsh-runtime/vendor/README.md +8 -4
- package/ts/dsh-runtime/vendor/dsh-map-tools/README.en.md +7 -1
- package/ts/dsh-runtime/vendor/dsh-map-tools/README.md +5 -3
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/settings-ns.js +1 -1
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/settings-ns.d.ts +1 -1
- package/ts/dsh-runtime/vendor/dsh-map-tools/package.json +6 -6
- package/ts/package.json +236 -16
- package/ts/scripts/map-tools-vendor-package-proof.ts +152 -31
- package/ts/scripts/state-cli.ts +418 -51
- package/ts/src/artifact-gate.ts +561 -15
- package/ts/src/benchmark-environment-bridge.ts +37 -20
- package/ts/src/bookable-facts.ts +183 -7
- package/ts/src/booking-surface/cancel-refund-commission.ts +488 -0
- package/ts/src/booking-surface/dsh-planner.ts +240 -32
- package/ts/src/booking-surface/dsh-plugin.js +17 -1
- package/ts/src/booking-surface/error-codes.ts +75 -0
- package/ts/src/booking-surface/recovery-chain.ts +409 -0
- package/ts/src/booking-surface/server.ts +84 -51
- package/ts/src/booking-surface/validation.ts +2 -0
- package/ts/src/contracts.ts +12 -2
- package/ts/src/dsh-llm.ts +4 -8
- package/ts/src/index.ts +609 -90
- package/ts/src/loop.ts +116 -7
- package/ts/src/memory-capture.ts +227 -3
- package/ts/src/mock-llm.ts +4 -8
- package/ts/src/model.ts +88 -2
- package/ts/src/state-ledger.ts +47 -29
- package/ts/src/subagent-job-id-guard.ts +72 -0
- package/ts/src/time-anchor.ts +217 -13
- 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))
|
|
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(
|
|
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(
|
|
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)
|
|
44
|
-
|
|
45
|
-
|
|
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')
|
|
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)
|
|
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')
|
|
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])
|
|
127
|
+
if (prices[qualified])
|
|
128
|
+
throw new Error(`duplicate price table key ${qualified}`);
|
|
100
129
|
prices[qualified] = validated;
|
|
101
|
-
if (prices[modelName])
|
|
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)
|
|
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)
|
|
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])
|
|
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)
|
|
139
|
-
|
|
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
|
-
|
|
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
|
-
|
|
200
|
-
|
|
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)
|
|
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)
|
|
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)
|
|
253
|
-
|
|
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]])
|
|
264
|
+
if (m && !process.env[m[1]])
|
|
265
|
+
process.env[m[1]] = m[2].trim();
|
|
262
266
|
}
|
|
263
|
-
}
|
|
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)
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
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(
|
|
33
|
-
}
|
|
34
|
-
|
|
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)
|
|
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
|
-
}
|
|
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
|
-
}
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
58
76
|
try {
|
|
59
77
|
const resp = await fetch(webhook, {
|
|
60
78
|
method: 'POST',
|
|
61
|
-
headers: {
|
|
62
|
-
|
|
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)
|
|
73
|
-
|
|
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
|
-
}
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
79
93
|
console.log(digest);
|
|
80
94
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
//# sourceURL=ts/scripts/nudge-digest.ts
|