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