@danceiny/gotry 0.0.1-rc.15 → 0.0.1-rc.17
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 +51 -13
- package/README.zh-CN.md +26 -12
- package/bin/gotry-booking-copilot.js +53 -0
- package/bin/gotry-bootstrap.js +357 -8
- package/bin/gotry-inner.js +442 -60
- package/bin/gotry-runtime-resolution.d.ts +27 -0
- package/bin/gotry-runtime-resolution.js +50 -0
- package/bin/gotry.js +1 -1
- package/cordis.gotry-patch.yml +15 -1
- package/data/airline-airports.json +39 -0
- package/dist/capabilities/agent-reach-deep.js +1 -1
- package/dist/capabilities/agent-reach.js +1 -1
- package/dist/capabilities/anything.js +1 -1
- package/dist/capabilities/artifacts.js +1 -1
- package/dist/capabilities/effect.js +293 -0
- package/dist/capabilities/fact-log.js +41 -0
- package/dist/capabilities/flyai.js +20 -6
- package/dist/capabilities/hbcli.js +43 -1
- package/dist/capabilities/incident-log.js +1 -1
- package/dist/capabilities/model-override.js +18 -0
- package/dist/capabilities/opensky.js +1 -1
- package/dist/capabilities/resilience.js +90 -0
- package/dist/capabilities/session/action-cache.js +1 -1
- package/dist/capabilities/session/adapters/ctrip-flight.js +1 -1
- package/dist/capabilities/session/adapters/meituan-local.js +1 -1
- package/dist/capabilities/session/benchmark.js +2 -1
- package/dist/capabilities/session/extension-bridge.js +358 -0
- package/dist/capabilities/session/extension-channel.js +93 -0
- package/dist/capabilities/session/extension-distribution.js +234 -0
- package/dist/capabilities/session/extract.js +1 -1
- package/dist/capabilities/session/golden-score.js +92 -0
- package/dist/capabilities/session/health-watch.js +216 -0
- package/dist/capabilities/session/read-guard.js +1 -1
- package/dist/capabilities/session/static-flight-golden.js +137 -0
- package/dist/capabilities/session/transport.js +1 -1
- package/dist/capabilities/session/wizard.js +147 -0
- package/dist/capabilities/session-consent.js +1 -1
- package/dist/capabilities/session-login.js +83 -1
- package/dist/capabilities/session-search.js +111 -2
- package/dist/capabilities/weather.js +168 -46
- package/dist/data/session-golden-20.json +25 -0
- package/dist/data/sf-golden-manifest.json +102 -0
- package/dist/data/sf-static-routes.json +91 -0
- package/dist/scripts/action-cache-tests.js +1 -1
- package/dist/scripts/agent-planning-budget-e2e.js +227 -0
- package/dist/scripts/agent-planning-budget-tests.js +173 -0
- package/dist/scripts/agent-reach-deep-tests.js +1 -1
- package/dist/scripts/agent-reach-tests.js +1 -1
- package/dist/scripts/agent-reach-wrapper-tests.js +1 -1
- package/dist/scripts/anything-tests.js +1 -1
- package/dist/scripts/async-collect.js +1 -1
- package/dist/scripts/benchmark-environment-bridge-e2e.js +981 -0
- package/dist/scripts/benchmark-environment-bridge-tests.js +2544 -0
- package/dist/scripts/booking-copilot-availability-ledger-binding-tests.js +335 -0
- package/dist/scripts/booking-copilot-availability-policy-v2-tests.js +1355 -0
- package/dist/scripts/booking-copilot-bin-proof-tests.js +89 -0
- package/dist/scripts/booking-copilot-crossrepo-fixture-server.js +113 -0
- package/dist/scripts/booking-copilot-dsh-core-proof-tests.js +356 -0
- package/dist/scripts/booking-copilot-dsh-planner-proof-tests.js +557 -0
- package/dist/scripts/booking-copilot-dsh-plugin-proof-tests.js +140 -0
- package/dist/scripts/booking-copilot-event-sequence-concurrency-proof-tests.js +202 -0
- package/dist/scripts/booking-copilot-gap-code-contract-proof-tests.js +102 -0
- package/dist/scripts/booking-copilot-operation-ledger-concurrency-proof-tests.js +374 -0
- package/dist/scripts/booking-copilot-receipt-ledger-concurrency-proof-tests.js +447 -0
- package/dist/scripts/booking-copilot-runtime-proof-tests.js +205 -0
- package/dist/scripts/booking-copilot-server-proof-tests.js +317 -0
- package/dist/scripts/booking-copilot-startup-proof-tests.js +283 -0
- package/dist/scripts/booking-copilot-v2-runtime-proof-tests.js +3935 -0
- package/dist/scripts/booking-saga-tests.js +1 -1
- package/dist/scripts/booking-surface-contract-proof-tests.js +393 -0
- package/dist/scripts/booking-surface-v2-contract-proof-tests.js +1174 -0
- package/dist/scripts/bootstrap-tests.js +59 -3
- package/dist/scripts/build-changelog.js +279 -0
- package/dist/scripts/changelog-tests.js +165 -0
- package/dist/scripts/companion-tests.js +1 -1
- package/dist/scripts/diff-test.js +1 -1
- package/dist/scripts/dsh-runtime-closure-tests.js +225 -0
- package/dist/scripts/dsh-runtime-closure.js +150 -0
- package/dist/scripts/effect-tests.js +474 -0
- package/dist/scripts/engine-run.js +1 -1
- package/dist/scripts/engine-tests.js +1 -1
- package/dist/scripts/evaluation-cadence-tests.js +347 -0
- package/dist/scripts/evaluation-contract-tests.js +574 -0
- package/dist/scripts/extension-distribution-cli.js +35 -0
- package/dist/scripts/extension-distribution-tests.js +341 -0
- package/dist/scripts/extension-tests.js +573 -0
- package/dist/scripts/fact-gate-tests.js +318 -0
- package/dist/scripts/flyai-tests.js +1 -1
- package/dist/scripts/hbcli-e2e-tests.js +180 -0
- package/dist/scripts/hbcli-tests.js +1 -1
- package/dist/scripts/health-watch-cli.js +51 -0
- package/dist/scripts/i18n-tests.js +1 -1
- package/dist/scripts/incident-tests.js +1 -1
- package/dist/scripts/journey-tests.js +1 -1
- package/dist/scripts/ledger-tests.js +1 -1
- package/dist/scripts/ledger-workflow-crash.js +1 -1
- package/dist/scripts/memory-capture-tests.js +1 -1
- package/dist/scripts/memory-decay-tests.js +1 -1
- package/dist/scripts/memory-metrics.js +1 -1
- package/dist/scripts/memory-value-report.js +1 -1
- package/dist/scripts/model-override-e2e.js +176 -0
- package/dist/scripts/nightly-evidence-tests.js +67 -4
- package/dist/scripts/nightly-evidence.js +59 -3
- package/dist/scripts/nudge-digest.js +1 -1
- package/dist/scripts/onboarding-tests.js +186 -0
- package/dist/scripts/opensky-check.js +1 -1
- package/dist/scripts/opensky-tests.js +1 -1
- package/dist/scripts/pnpm-dsh-closure-proof.js +20 -0
- package/dist/scripts/price-drift-tests.js +441 -0
- package/dist/scripts/price-drift-watch.js +491 -0
- package/dist/scripts/probe-poi-tests.js +1 -1
- package/dist/scripts/product-metrics.js +1 -1
- package/dist/scripts/publish-preverify.js +40 -4
- package/dist/scripts/realtime-pricing-tests.js +1 -1
- package/dist/scripts/replay-async.js +1 -1
- package/dist/scripts/replay-real.js +1 -1
- package/dist/scripts/replay.js +1 -1
- package/dist/scripts/session-attach-diagnose.js +1 -1
- package/dist/scripts/session-attach-poc.js +1 -1
- package/dist/scripts/session-benchmark.js +1 -1
- package/dist/scripts/session-extract-tests.js +1 -1
- package/dist/scripts/session-login.js +1 -1
- package/dist/scripts/session-tests.js +70 -20
- package/dist/scripts/sf-live-benchmark.js +338 -0
- package/dist/scripts/sf-live-cli-tests.js +21 -0
- package/dist/scripts/sf-soft-score-tests.js +108 -0
- package/dist/scripts/sf-summary.js +93 -0
- package/dist/scripts/skeleton-check.js +1 -1
- package/dist/scripts/skeleton-integration-test.js +1 -1
- package/dist/scripts/skills-contract-tests.js +1 -1
- package/dist/scripts/smoke-session-gate-tests.js +29 -0
- package/dist/scripts/smoke.js +154 -33
- package/dist/scripts/state-cli-tests.js +1 -1
- package/dist/scripts/state-cli.js +1 -1
- package/dist/scripts/static-golden-tests.js +299 -0
- package/dist/scripts/time-eval-tests.js +1 -1
- package/dist/scripts/travel-timeline-tests.js +1 -1
- package/dist/scripts/unified-tests.js +1 -1
- package/dist/scripts/weather-tests.js +694 -44
- package/dist/scripts/z3-race-tests.js +1 -1
- package/dist/src/artifact-gate.js +310 -0
- package/dist/src/benchmark-agent-conformance.js +370 -0
- package/dist/src/benchmark-environment-bridge.js +384 -0
- package/dist/src/benchmark-headless-child-diagnostics.js +173 -0
- package/dist/src/benchmark-tool-isolation.js +124 -0
- package/dist/src/bookable-facts.js +349 -0
- package/dist/src/booking-saga.js +1 -1
- package/dist/src/booking-surface/availability-policy-v2.js +830 -0
- package/dist/src/booking-surface/canonical-schema.js +113 -0
- package/dist/src/booking-surface/contracts-v2.js +89 -0
- package/dist/src/booking-surface/contracts.js +46 -0
- package/dist/src/booking-surface/dsh-planner.js +453 -0
- package/dist/src/booking-surface/dsh-plugin.js +93 -0
- package/dist/src/booking-surface/error-codes.js +94 -0
- package/dist/src/booking-surface/index.js +15 -0
- package/dist/src/booking-surface/profile.js +68 -0
- package/dist/src/booking-surface/runtime-v2.js +1771 -0
- package/dist/src/booking-surface/runtime.js +351 -0
- package/dist/src/booking-surface/server-v2.js +334 -0
- package/dist/src/booking-surface/server.js +302 -0
- package/dist/src/booking-surface/startup.js +159 -0
- package/dist/src/booking-surface/validation-v2.js +319 -0
- package/dist/src/booking-surface/validation.js +809 -0
- package/dist/src/bridge.js +1 -1
- package/dist/src/companions.js +1 -1
- package/dist/src/contracts.js +1 -1
- package/dist/src/dsh-llm.js +1 -1
- package/dist/src/engine.js +1 -1
- package/dist/src/evaluation-cadence.js +234 -0
- package/dist/src/evaluation-contracts.js +906 -0
- package/dist/src/i18n.js +1 -1
- package/dist/src/index.js +234 -60
- package/dist/src/journey.js +1 -1
- package/dist/src/loop.js +1 -1
- package/dist/src/memory-capture.js +1 -1
- package/dist/src/memory-decay.js +1 -1
- package/dist/src/memory-utility.js +1 -1
- package/dist/src/mock-llm.js +1 -1
- package/dist/src/model.js +1 -1
- package/dist/src/realtime-pricing.js +14 -7
- package/dist/src/slot-spec.js +1 -1
- package/dist/src/state-ledger.js +2 -1
- package/dist/src/time-anchor.js +1 -1
- package/dist/src/tool-budget.js +136 -0
- package/dist/src/tool-packet.js +1 -1
- package/dist/src/travel-slots.js +1 -1
- package/dist/src/travel-timeline.js +1 -1
- package/dist/src/unified.js +1 -1
- package/dist/src/wish-pool.js +1 -1
- package/dist/src/z3-shared.js +1 -1
- package/extension/README.md +50 -0
- package/extension/background.js +160 -0
- package/extension/content-bridge.js +41 -0
- package/extension/content-main.js +62 -0
- package/extension/manifest.json +32 -0
- package/package.json +291 -12
- package/schemas/booking.surface.v1.schema.json +927 -0
- package/schemas/booking.surface.v2.schema.json +61 -0
- package/ts/capabilities/fact-log.ts +54 -0
- package/ts/capabilities/flyai.ts +16 -3
- package/ts/capabilities/hbcli.ts +46 -0
- package/ts/capabilities/session/benchmark.ts +4 -1
- package/ts/capabilities/session/extension-bridge.ts +377 -0
- package/ts/capabilities/session/extension-channel.ts +95 -0
- package/ts/capabilities/session/extension-distribution.ts +264 -0
- package/ts/capabilities/session/golden-score.ts +139 -0
- package/ts/capabilities/session/health-watch.ts +257 -0
- package/ts/capabilities/session/static-flight-golden.ts +209 -0
- package/ts/capabilities/session/wizard.ts +140 -0
- package/ts/capabilities/session-login.ts +63 -2
- package/ts/capabilities/session-search.ts +105 -3
- package/ts/capabilities/weather.ts +141 -52
- package/ts/package.json +3 -3
- package/ts/src/artifact-gate.ts +365 -0
- package/ts/src/benchmark-agent-conformance.ts +448 -0
- package/ts/src/benchmark-environment-bridge.ts +348 -0
- package/ts/src/benchmark-headless-child-diagnostics.ts +184 -0
- package/ts/src/benchmark-tool-isolation.ts +166 -0
- package/ts/src/bookable-facts.ts +557 -0
- package/ts/src/booking-surface/availability-policy-v2.ts +523 -0
- package/ts/src/booking-surface/canonical-schema.js +113 -0
- package/ts/src/booking-surface/contracts-v2.ts +118 -0
- package/ts/src/booking-surface/contracts.ts +380 -0
- package/ts/src/booking-surface/dsh-planner.ts +452 -0
- package/ts/src/booking-surface/dsh-plugin.js +93 -0
- package/ts/src/booking-surface/error-codes.ts +101 -0
- package/ts/src/booking-surface/index.ts +12 -0
- package/ts/src/booking-surface/profile.ts +42 -0
- package/ts/src/booking-surface/runtime-v2.ts +1466 -0
- package/ts/src/booking-surface/runtime.ts +483 -0
- package/ts/src/booking-surface/server-v2.ts +247 -0
- package/ts/src/booking-surface/server.ts +324 -0
- package/ts/src/booking-surface/startup.ts +196 -0
- package/ts/src/booking-surface/validation-v2.ts +205 -0
- package/ts/src/booking-surface/validation.ts +453 -0
- package/ts/src/index.ts +161 -14
- package/ts/src/state-ledger.ts +1 -0
- package/ts/src/tool-budget.ts +165 -0
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { BOOKABLE_FACT_SCHEMA, checkSameDayDeparture, dedupeFacts, detectBacktrack, factsFromFlyai, flightClaimVerdict, itineraryInvariants, latestFactsForRouteDate, negativeFact, renderConnection, renderFlightFact, renderNightsLine, renderPolicyFact } from '../src/bookable-facts.js';
|
|
4
|
+
import { ARTIFACT_GATE_SCHEMA, gateArtifact } from '../src/artifact-gate.js';
|
|
5
|
+
let pass = 0;
|
|
6
|
+
let fail = 0;
|
|
7
|
+
function assert(cond, msg) {
|
|
8
|
+
if (cond) {
|
|
9
|
+
pass++;
|
|
10
|
+
console.log(` ok - ${msg}`);
|
|
11
|
+
} else {
|
|
12
|
+
fail++;
|
|
13
|
+
console.error(` FAIL - ${msg}`);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
const map = JSON.parse(readFileSync(join(import.meta.dirname, '..', '..', 'data', 'airline-airports.json'), 'utf-8'));
|
|
17
|
+
const alias = map.city_alias;
|
|
18
|
+
const fixture = JSON.parse(readFileSync(join(import.meta.dirname, '..', 'data', 'golden-trip-2027-facts.json'), 'utf-8'));
|
|
19
|
+
const FETCHED = '2026-08-29T09:51:00.000Z';
|
|
20
|
+
const registry = dedupeFacts(fixture.queries.flatMap((q)=>factsFromFlyai({
|
|
21
|
+
kind: q.kind,
|
|
22
|
+
origin: q.origin,
|
|
23
|
+
destination: q.destination,
|
|
24
|
+
date: q.date
|
|
25
|
+
}, {
|
|
26
|
+
verdict: q.verdict,
|
|
27
|
+
options: q.options
|
|
28
|
+
}, FETCHED, alias)));
|
|
29
|
+
const tripYear = Number(fixture.meta.trip_window[0].slice(0, 4));
|
|
30
|
+
console.log('§1 转换与注册表');
|
|
31
|
+
const hkHkt717 = latestFactsForRouteDate(registry, 'HKG', 'HKT', '2027-07-17');
|
|
32
|
+
assert(registry.every((f)=>f.schema === BOOKABLE_FACT_SCHEMA && f.fact_id.length === 16), 'schema 词唯一且 fact_id 为 16 位语义散列');
|
|
33
|
+
assert(hkHkt717.length === 2 && hkHkt717.every((f)=>f.bookability === 'bookable_exact_date' && f.tier === 'live_inventory'), 'HKG→HKT 2027-07-17 在架两班,均为 exact-date 可下单层');
|
|
34
|
+
const uo724 = hkHkt717.find((f)=>f.flight_no === 'UO724');
|
|
35
|
+
assert(uo724.dep_local === '07:55' && uo724.arr_local === '10:30' && uo724.nonstop === true && uo724.price === 793 && uo724.currency === 'CNY' && uo724.query_id === 'flyai:flight:香港-普吉:2027-07-17' && uo724.fetched_at === FETCHED && uo724.as_of === '2026-08-29', '事实字段齐全:时刻/直飞/价格/币种/query_id/fetched_at/as_of(issue 期望①的字段面)');
|
|
36
|
+
assert(uo724.marketing_carrier === '香港快运航空' && uo724.operating_carrier === undefined, '营销承运在案;实际承运上游未给 = 留空不猜(未知 ≠ 相同)');
|
|
37
|
+
const misses = registry.filter((f)=>f.bookability === 'unavailable_exact_date');
|
|
38
|
+
assert(misses.length === 1 && misses[0].route.origin === 'SZX' && misses[0].route.destination === 'HKT' && misses[0].date === '2027-07-17', 'miss → 负事实(route+date 级「当前不可售」记录,非空缺)');
|
|
39
|
+
assert(factsFromFlyai({
|
|
40
|
+
kind: 'flight',
|
|
41
|
+
origin: '香港',
|
|
42
|
+
destination: '普吉',
|
|
43
|
+
date: '2027-07-17'
|
|
44
|
+
}, {
|
|
45
|
+
verdict: 'error'
|
|
46
|
+
}, FETCHED, alias).length === 0, 'error 不落事实——通道无结论不是证据');
|
|
47
|
+
const dup = dedupeFacts([
|
|
48
|
+
...registry,
|
|
49
|
+
{
|
|
50
|
+
...uo724,
|
|
51
|
+
fetched_at: '2026-08-30T00:00:00.000Z',
|
|
52
|
+
price: 900
|
|
53
|
+
}
|
|
54
|
+
]);
|
|
55
|
+
const reFetched = dup.filter((f)=>f.fact_id === uo724.fact_id);
|
|
56
|
+
assert(reFetched.length === 1 && reFetched[0].price === 900, '同 fact_id 重查去重,保留最新 fetched_at(旧快照 append-only 在持久层)');
|
|
57
|
+
const stale = negativeFact('flyai:flight:香港-普吉:2027-07-17', 'flight', '香港', '普吉', '2027-07-17', 'flyai', '2026-08-28T00:00:00.000Z', alias);
|
|
58
|
+
const mixed = dedupeFacts([
|
|
59
|
+
stale,
|
|
60
|
+
...hkHkt717
|
|
61
|
+
]);
|
|
62
|
+
assert(latestFactsForRouteDate(mixed, 'HKG', 'HKT', '2027-07-17').every((f)=>f.bookability === 'bookable_exact_date'), '同 route+date 陈旧 miss 不遮蔽更新查询的 hit(最新批次优先)');
|
|
63
|
+
console.log('§2 UO784 与同日衔接硬约束');
|
|
64
|
+
const claim = flightClaimVerdict(registry, {
|
|
65
|
+
flight_no: 'UO784',
|
|
66
|
+
origin: 'HKG',
|
|
67
|
+
destination: 'HKT',
|
|
68
|
+
date: '2027-07-17'
|
|
69
|
+
});
|
|
70
|
+
assert(claim.verdict === 'not_in_source' && /UO724|CX771/.test(claim.reason), 'UO784 在 HKG→HKT 2027-07-17 被判 not_in_source(源内在架 UO724/CX771)');
|
|
71
|
+
const conn = checkSameDayDeparture(registry, {
|
|
72
|
+
origin: 'HKG',
|
|
73
|
+
destination: 'HKT',
|
|
74
|
+
date: '2027-07-17'
|
|
75
|
+
}, {
|
|
76
|
+
errand_end_local: '13:00',
|
|
77
|
+
airport_transit_min: 45,
|
|
78
|
+
checkin_deadline_min: 120
|
|
79
|
+
});
|
|
80
|
+
assert(!conn.feasible && conn.feasible_flights.length === 0, '「上午办事(13:00 结束)+ 当天下午直飞普吉」判定不可行');
|
|
81
|
+
assert(conn.earliest_departure_local === '15:45' && conn.excluded.length === 2 && conn.excluded.every((e)=>/最早可接时刻 15:45/.test(e.reason)), '最早可接 15:45(13:00+45m 门到机场+120m 值机);在架两班早班均被硬约束排除并带原因');
|
|
82
|
+
assert(conn.suggestions.some((s)=>s.includes('增加 1 个HKG住宿夜')) && conn.suggestions.some((s)=>s.includes('提前一天')), '不可行建议:增加 1 个香港住宿夜 / 提前一天完成事务或出发(issue 验收②原话面)');
|
|
83
|
+
console.log('§3 航司→机场映射');
|
|
84
|
+
assert(map.carrier_airport['BKK']?.['FD'] === 'DMK' && map.carrier_airport['BKK']?.['VZ'] === 'BKK', '映射快照:FD→DMK、VZ→BKK(as_of 2026-08-29)');
|
|
85
|
+
const mapGate = gateArtifact('### 段|普吉→曼谷(7.30)\n| FD/VZ(亚航/越捷) | 廊曼DMK |', registry, map, {
|
|
86
|
+
trip_year: tripYear
|
|
87
|
+
});
|
|
88
|
+
assert(mapGate.violations.some((v)=>v.kind === 'airport_mapping_conflict' && /VZ 在BKK落 BKK/.test(v.detail)) && !mapGate.violations.some((v)=>v.kind === 'airport_mapping_conflict' && /FD 在/.test(v.detail)), '「FD/VZ 均落 DMK」合并映射:VZ 被抓、FD 不误伤');
|
|
89
|
+
const mapOk = gateArtifact('### 段|普吉→曼谷(7.30)\n| FD(亚航) | 廊曼DMK |', registry, map, {
|
|
90
|
+
trip_year: tripYear
|
|
91
|
+
});
|
|
92
|
+
assert(!mapOk.violations.some((v)=>v.kind === 'airport_mapping_conflict'), 'FD→DMK 与映射一致不误报');
|
|
93
|
+
console.log('§4 SZX→DXB 与承运分离');
|
|
94
|
+
const szxDxb = latestFactsForRouteDate(registry, 'SZX', 'DXB', '2027-08-09');
|
|
95
|
+
assert(szxDxb.length === 1 && szxDxb[0].flight_no === 'EK329', 'SZX→DXB 2027-08-09 exact-date 班次 = EK329(快照原值,不是旧班 EK327)');
|
|
96
|
+
const ekLine = renderFlightFact(szxDxb[0]);
|
|
97
|
+
assert(ekLine.includes('EK329') && ekLine.includes('00:30→04:55') && ekLine.includes('[flyai@') && ekLine.includes('#flyai:flight:深圳-迪拜:2027-08-09'), '渲染行:班次/时刻/证据链(source@fetched_at #query_id)全部来自结构化事实');
|
|
98
|
+
const codeshare = {
|
|
99
|
+
...szxDxb[0],
|
|
100
|
+
fact_id: 'cs-demo000000000',
|
|
101
|
+
flight_no: 'EK8582',
|
|
102
|
+
marketing_carrier: '阿联酋航空',
|
|
103
|
+
operating_carrier: '中国南方航空'
|
|
104
|
+
};
|
|
105
|
+
const csLine = renderFlightFact(codeshare);
|
|
106
|
+
assert(csLine.includes('营销 阿联酋航空') && csLine.includes('实际承运 中国南方航空'), 'codeshare 渲染同时输出营销与实际承运(不混用)');
|
|
107
|
+
console.log('§5 protected_connection 与联程措辞');
|
|
108
|
+
const fd597 = {
|
|
109
|
+
schema: BOOKABLE_FACT_SCHEMA,
|
|
110
|
+
fact_id: 'fd597-demo0000000',
|
|
111
|
+
kind: 'flight',
|
|
112
|
+
route: {
|
|
113
|
+
origin: 'SZX',
|
|
114
|
+
destination: 'BKK'
|
|
115
|
+
},
|
|
116
|
+
date: '2027-08-08',
|
|
117
|
+
flight_no: 'FD597',
|
|
118
|
+
marketing_carrier: '泰国亚洲航空',
|
|
119
|
+
dep_local: '18:30',
|
|
120
|
+
arr_local: '20:15',
|
|
121
|
+
nonstop: true,
|
|
122
|
+
price: 700,
|
|
123
|
+
currency: 'CNY',
|
|
124
|
+
tier: 'live_inventory',
|
|
125
|
+
bookability: 'bookable_exact_date',
|
|
126
|
+
source: 'flyai',
|
|
127
|
+
query_id: 'flyai:flight:深圳-曼谷:2027-08-08',
|
|
128
|
+
fetched_at: FETCHED,
|
|
129
|
+
as_of: '2026-08-29'
|
|
130
|
+
};
|
|
131
|
+
const selfTransfer = renderConnection(fd597, szxDxb[0]);
|
|
132
|
+
assert(selfTransfer.includes('分票/自助转机') && selfTransfer.includes('误机无保护') && !/联程\(/.test(selfTransfer), '两段接得上 ≠ 联程:缺 protected_connection 渲染为分票/自助转机并标红风险');
|
|
133
|
+
const throughA = {
|
|
134
|
+
...fd597,
|
|
135
|
+
fact_id: 'fd597-protected00',
|
|
136
|
+
protected_connection: true
|
|
137
|
+
};
|
|
138
|
+
const throughB = {
|
|
139
|
+
...szxDxb[0],
|
|
140
|
+
fact_id: 'ek329-protected00',
|
|
141
|
+
protected_connection: true
|
|
142
|
+
};
|
|
143
|
+
assert(/联程\(同一票号/.test(renderConnection(throughA, throughB)), '双腿 protected_connection=true 才允许渲染「联程(同一票号/行李直挂/误机保护)」');
|
|
144
|
+
const throughGate = gateArtifact(`- 深圳→曼谷 FD597 + 曼谷→迪拜 EK329:联程(同一票号)`, [
|
|
145
|
+
throughA,
|
|
146
|
+
throughB
|
|
147
|
+
], map, {
|
|
148
|
+
trip_year: tripYear
|
|
149
|
+
});
|
|
150
|
+
assert(!throughGate.violations.some((v)=>v.kind === 'self_transfer_called_through'), 'protected_connection=true 的联程行过闸');
|
|
151
|
+
const stGate = gateArtifact(`- FD597 深圳→曼谷 + EK 曼谷→迪拜,建议买联程更稳`, [
|
|
152
|
+
fd597,
|
|
153
|
+
szxDxb[0]
|
|
154
|
+
], map, {
|
|
155
|
+
trip_year: tripYear
|
|
156
|
+
});
|
|
157
|
+
assert(stGate.violations.some((v)=>v.kind === 'self_transfer_called_through'), '「建议买联程」无 protected_connection 证据 → 违例');
|
|
158
|
+
console.log('§6 行程统计不变量');
|
|
159
|
+
assert(itineraryInvariants(fixture.good_itinerary).length === 0, 'good itinerary 全绿:23 酒店夜 + 1 机上夜 = 24 夜;O&D/legs/预算自洽');
|
|
160
|
+
const goodNights = renderNightsLine(fixture.good_itinerary);
|
|
161
|
+
assert(goodNights.includes('24 晚 = 23 个酒店夜 + 1 个机上夜') && goodNights.includes('O&D 6 段(共 6 个 flight legs)'), '夜数口径机器反算:「24 晚 = 23 酒店夜 + 1 机上夜」;航班 O&D 6 段(issue:不是 7 段)');
|
|
162
|
+
const badViol = itineraryInvariants(fixture.bad_itinerary);
|
|
163
|
+
assert(badViol.some((v)=>v.kind === 'nights_inconsistent' && /酒店夜 23 \+ 机上夜 0 = 23 ≠ 行程总夜数 24/.test(v.detail)), 'bad itinerary:23 酒店夜 + 0 机上夜 ≠ 24 总夜被抓(机上夜被吞,「24 天 23 晚」口径混用)');
|
|
164
|
+
assert(badViol.some((v)=>v.kind === 'budget_floor_inconsistent' && /35,500/.test(v.detail) && /30,000/.test(v.detail)), 'bad itinerary:分项最低合计 ¥35,500 > 声称可压到 ¥30,000 被抓(issue 预算行原值)');
|
|
165
|
+
console.log('§7 政策 as_of 与复核 gate');
|
|
166
|
+
const policy = {
|
|
167
|
+
schema: BOOKABLE_FACT_SCHEMA,
|
|
168
|
+
fact_id: 'policy-th-00000000',
|
|
169
|
+
kind: 'policy',
|
|
170
|
+
subject: '泰国入境(中国护照)',
|
|
171
|
+
statement: '免签停留(口径以泰方公告为准);UAE 居民返程应优先校验 residence visa / Emirates ID 而非游客免签口径',
|
|
172
|
+
source: 'web:official',
|
|
173
|
+
query_id: 'web:policy:泰国免签',
|
|
174
|
+
fetched_at: FETCHED,
|
|
175
|
+
as_of: '2026-08-29'
|
|
176
|
+
};
|
|
177
|
+
const policyLine = renderPolicyFact(policy, fixture.meta.trip_window[0]);
|
|
178
|
+
assert(policyLine.includes('截至 2026-08-29 的现行政策') && policyLine.includes('2027-06-16') && !/[✓✅]/.test(policyLine), '政策行恒带「截至 as_of」+ D-30 复核日期(2027-06-16),永不用无条件 ✓');
|
|
179
|
+
const policyBad = gateArtifact('| 泰国 | 中国护照免签(现行60天/次),停留2周无问题 |', registry, map, {
|
|
180
|
+
trip_year: tripYear
|
|
181
|
+
});
|
|
182
|
+
assert(policyBad.violations.some((v)=>v.kind === 'policy_without_as_of'), '「现行60天」无具体 as_of 日期 = policy_without_as_of(现行 ≠ 时间边界)');
|
|
183
|
+
const policyGood = gateArtifact(policyLine, registry, map, {
|
|
184
|
+
trip_year: tripYear
|
|
185
|
+
});
|
|
186
|
+
assert(!policyGood.violations.some((v)=>v.kind === 'policy_without_as_of'), '带「截至 YYYY-MM-DD」的政策行过闸');
|
|
187
|
+
console.log('§8 回头路检测');
|
|
188
|
+
const bt = detectBacktrack([
|
|
189
|
+
'HKT',
|
|
190
|
+
'KBV',
|
|
191
|
+
'HKT',
|
|
192
|
+
'BKK'
|
|
193
|
+
], new Set([
|
|
194
|
+
'KBV-BKK'
|
|
195
|
+
]));
|
|
196
|
+
assert(bt.length === 1 && bt[0].path.join('→') === 'HKT→KBV→HKT' && (bt[0].suggestion ?? '').includes('KBV→BKK'), 'Phuket→Krabi→Phuket 回头路被抓;KBV→BKK 直飞存在 → 最小改动建议');
|
|
197
|
+
assert(detectBacktrack([
|
|
198
|
+
'HKT',
|
|
199
|
+
'KBV',
|
|
200
|
+
'BKK'
|
|
201
|
+
], new Set([
|
|
202
|
+
'KBV-BKK'
|
|
203
|
+
])).length === 0, 'good route(普吉→甲米→曼谷)无回头路不误报');
|
|
204
|
+
console.log('§9 全产物闸 golden E2E(bad blocked / good pass)');
|
|
205
|
+
const badArtifact = `# 2027年7-8月 迪拜→深圳/香港→普吉/甲米→曼谷→云南→迪拜 24天行程规划(v2)
|
|
206
|
+
|
|
207
|
+
## 一、行程总览
|
|
208
|
+
| 日期 | 星期 | 行程 | 交通 | 住宿 |
|
|
209
|
+
|---|---|---|---|---|
|
|
210
|
+
| 07-16 | 周五 | 迪拜 → 深圳或香港(晚间到) | EK328/FZ/QR(见段1) | 香港尖沙咀 |
|
|
211
|
+
| 07-17 | 周六 | 上午香港办银行开户+保险签约 → 下午飞普吉 | UO784 香港15:35→普吉18:10 ✓ | 普吉·查龙 |
|
|
212
|
+
| 08-08 | 周日 | 丽江→深圳 → 深夜红眼回迪拜 | MF1538/8L + EK327 | 机上/钟点房 |
|
|
213
|
+
|
|
214
|
+
**共 24 天 23 晚** ✓
|
|
215
|
+
|
|
216
|
+
## 二、机票
|
|
217
|
+
### 段1|迪拜→香港/深圳(7.16 周五)
|
|
218
|
+
| 方案 | 航班 | 时长 | 说明 |
|
|
219
|
+
|---|---|---|---|
|
|
220
|
+
| 快+顺(推荐) | EK328 直飞深圳 | 迪拜10:05→深圳约22:50 | 原方案 |
|
|
221
|
+
| 省钱版(落香港) | flydubai FZ 直飞香港 | 约7h15m | 廉航直飞 |
|
|
222
|
+
|
|
223
|
+
### 段2|香港→普吉(7.17 周六)
|
|
224
|
+
| 方案 | 航班 | 时刻 |
|
|
225
|
+
|---|---|---|
|
|
226
|
+
| 首选 | UO784 香港快运直飞 | 香港15:35→普吉18:10 |
|
|
227
|
+
| 舒适 | CX787 国泰直飞 | 香港约08:40→普吉约12:20 |
|
|
228
|
+
|
|
229
|
+
### 段3|普吉→曼谷(7.30 周五晚)
|
|
230
|
+
| 航班 | 落点 |
|
|
231
|
+
|---|---|
|
|
232
|
+
| FD/VZ(亚航/越捷) | 廊曼DMK |
|
|
233
|
+
|
|
234
|
+
### 段4|曼谷→昆明(8.1 周日晚)
|
|
235
|
+
| 航班 | 说明 |
|
|
236
|
+
|---|---|
|
|
237
|
+
| MU 东航直飞(每日4-5班) | 提前出票 |
|
|
238
|
+
| 8L 祥鹏直飞(若有当日班) | 备选比价 |
|
|
239
|
+
|
|
240
|
+
### 段6|丽江→深圳(8.8 周日)
|
|
241
|
+
| 方案 | 说明 |
|
|
242
|
+
|---|---|
|
|
243
|
+
| MF1538 厦航直飞 11:13→13:19 | 首选 |
|
|
244
|
+
|
|
245
|
+
### 段7|深圳→迪拜(8.8 深夜 / 8.9 凌晨)
|
|
246
|
+
| 方案 | 时刻 |
|
|
247
|
+
|---|---|
|
|
248
|
+
| EK327 直飞(推荐) | 深圳约00:30→迪拜约04:30 |
|
|
249
|
+
| 省钱:FD597 深圳→曼谷 + EK 曼谷→迪拜,建议买 EK 联程 | 到迪拜约04:30 |
|
|
250
|
+
|
|
251
|
+
### 段8|女友去程:南京→普吉(7.17)
|
|
252
|
+
直飞:东航 南京→普吉(白天班,约17:00到)
|
|
253
|
+
|
|
254
|
+
## 五、证件与政策
|
|
255
|
+
| 目的地 | 政策 |
|
|
256
|
+
|---|---|
|
|
257
|
+
| 泰国 | 中国护照免签(现行60天/次),停留2周无问题 |
|
|
258
|
+
| 迪拜 | 中国护照免签30天 |
|
|
259
|
+
|
|
260
|
+
预算可压到 ¥30,000
|
|
261
|
+
`;
|
|
262
|
+
const badReport = gateArtifact(badArtifact, registry, map, {
|
|
263
|
+
trip_year: tripYear,
|
|
264
|
+
itinerary: fixture.bad_itinerary
|
|
265
|
+
});
|
|
266
|
+
const badKinds = new Set(badReport.violations.map((v)=>v.kind));
|
|
267
|
+
const expectedKinds = [
|
|
268
|
+
'not_in_source',
|
|
269
|
+
'contradicted',
|
|
270
|
+
'airport_mapping_conflict',
|
|
271
|
+
'self_transfer_called_through',
|
|
272
|
+
'policy_without_as_of',
|
|
273
|
+
'unconditional_check',
|
|
274
|
+
'nights_inconsistent',
|
|
275
|
+
'budget_floor_inconsistent'
|
|
276
|
+
];
|
|
277
|
+
assert(badReport.schema === ARTIFACT_GATE_SCHEMA && badReport.verdict === 'blocked' && badReport.presentation === 'verified_label_forbidden', 'bad artifact:闸 blocked 且禁止「已验证方案」措辞');
|
|
278
|
+
for (const k of expectedKinds){
|
|
279
|
+
assert(badKinds.has(k), `bad artifact 违例类别覆盖:${k}`);
|
|
280
|
+
}
|
|
281
|
+
assert(badReport.violations.filter((v)=>v.kind === 'not_in_source').some((v)=>v.detail.startsWith('UO784')), 'UO784 被逐条点名(验收②:任何产物中不得出现该班次)');
|
|
282
|
+
assert(!badReport.violations.some((v)=>v.detail.startsWith('MU9626') || /MU 东航直飞/.test(v.detail) && v.kind === 'not_in_source'), '已核验的 MU 东航直飞(BKK→KMG 在架)不误伤');
|
|
283
|
+
const goodFlights = [
|
|
284
|
+
latestFactsForRouteDate(registry, 'DXB', 'HKG', '2027-07-16')[0],
|
|
285
|
+
latestFactsForRouteDate(registry, 'HKG', 'HKT', '2027-07-18')[0],
|
|
286
|
+
latestFactsForRouteDate(registry, 'KBV', 'BKK', '2027-07-30')[0],
|
|
287
|
+
latestFactsForRouteDate(registry, 'BKK', 'KMG', '2027-08-01')[0],
|
|
288
|
+
latestFactsForRouteDate(registry, 'LJG', 'SZX', '2027-08-08')[0],
|
|
289
|
+
szxDxb[0]
|
|
290
|
+
];
|
|
291
|
+
const goodArtifact = [
|
|
292
|
+
`# 2027-07-16 → 2027-08-09 行程(事实闸渲染面)`,
|
|
293
|
+
'',
|
|
294
|
+
renderNightsLine(fixture.good_itinerary),
|
|
295
|
+
'',
|
|
296
|
+
'## 航班(逐条可回溯)',
|
|
297
|
+
...goodFlights.map(renderFlightFact),
|
|
298
|
+
'',
|
|
299
|
+
'## 政策',
|
|
300
|
+
renderPolicyFact(policy, fixture.meta.trip_window[0]),
|
|
301
|
+
renderPolicyFact({
|
|
302
|
+
...policy,
|
|
303
|
+
fact_id: 'policy-uae-0000000',
|
|
304
|
+
subject: '迪拜入境(UAE 居民返程)',
|
|
305
|
+
statement: '优先校验 residence visa / Emirates ID;游客免签口径不适用居民返程'
|
|
306
|
+
}, fixture.meta.trip_window[0])
|
|
307
|
+
].join('\n');
|
|
308
|
+
const goodReport = gateArtifact(goodArtifact, registry, map, {
|
|
309
|
+
trip_year: tripYear,
|
|
310
|
+
itinerary: fixture.good_itinerary
|
|
311
|
+
});
|
|
312
|
+
assert(goodReport.verdict === 'pass' && goodReport.presentation === 'verified_itinerary_allowed' && goodReport.traceable === 6 && goodReport.violations.length === 0, `good artifact:6/6 航班 claim 全回溯,闸 pass(违例 ${goodReport.violations.length})`);
|
|
313
|
+
assert(goodFlights.every((f)=>f.bookability === 'bookable_exact_date' && f.query_id.includes('flyai:flight:')), 'good artifact 的每个可下单事实均可回溯到 tool result/query id(验收⑧)');
|
|
314
|
+
console.log(`\nFACT GATE TESTS: ${pass} pass, ${fail} fail`);
|
|
315
|
+
if (fail > 0) process.exit(1);
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
//# sourceURL=ts/scripts/fact-gate-tests.ts
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import { mkdtempSync, rmSync } from 'node:fs';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import assert from 'node:assert/strict';
|
|
6
|
+
import { listDestinations, searchHotels, hbcliBinCandidates } from '../capabilities/hbcli.js';
|
|
7
|
+
import { makeProductionInterpreter } from '../capabilities/effect.js';
|
|
8
|
+
const UAT = 'https://api-test.hotelbyte.com';
|
|
9
|
+
const SANDBOX_APP_KEY = 'hotelbyte_api_demo';
|
|
10
|
+
const SANDBOX_APP_SECRET = 'hotelbyte_api_demo';
|
|
11
|
+
const GUIDANCE = [
|
|
12
|
+
'staicli(hbcli)账号配置指引:',
|
|
13
|
+
' 1. 安装 CLI(若缺):npx gotry setup(官方 install.sh → ~/.local/bin/hbcli)',
|
|
14
|
+
' 2. 快速试用(沙箱演示账号,来自 hotel-be 种子,user/domain/predefined_user_demo.go):',
|
|
15
|
+
' hbcli auth set-credentials --app-key hotelbyte_api_demo --app-secret hotelbyte_api_demo',
|
|
16
|
+
' 3. 正式接入:向 HotelByte 申请专属 appKey/appSecret(hbk_*/hbs_*),替换第 2 步凭证;',
|
|
17
|
+
' 门户模式则 hbcli auth login --username <email>(权限更大,搜索场景无必要)',
|
|
18
|
+
' 4. 自检:hbcli auth whoami(api_key.configured=true 即凭证就位;has_ticket 首次查询后为 true)'
|
|
19
|
+
];
|
|
20
|
+
function probeBin() {
|
|
21
|
+
for (const bin of hbcliBinCandidates('hbcli')){
|
|
22
|
+
const r = spawnSync(bin, [
|
|
23
|
+
'version'
|
|
24
|
+
], {
|
|
25
|
+
timeout: 10_000,
|
|
26
|
+
stdio: 'ignore'
|
|
27
|
+
});
|
|
28
|
+
if (!r.error && r.status === 0) return bin;
|
|
29
|
+
}
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
async function uatReachable() {
|
|
33
|
+
try {
|
|
34
|
+
await fetch(`${UAT}/api/search/destinations`, {
|
|
35
|
+
method: 'POST',
|
|
36
|
+
headers: {
|
|
37
|
+
'Content-Type': 'application/json'
|
|
38
|
+
},
|
|
39
|
+
body: '{}',
|
|
40
|
+
signal: AbortSignal.timeout(8_000)
|
|
41
|
+
});
|
|
42
|
+
return true;
|
|
43
|
+
} catch {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
async function main() {
|
|
48
|
+
console.log(GUIDANCE.join('\n'));
|
|
49
|
+
const bin = probeBin();
|
|
50
|
+
if (!bin) {
|
|
51
|
+
console.log(`SKIP: 未安装 hbcli(候选 ${hbcliBinCandidates('hbcli').join(' / ')} 均不可执行)——npx gotry setup 可按官方脚本安装`);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (!await uatReachable()) {
|
|
55
|
+
console.log(`SKIP: UAT(${UAT})不可达——全流程端到端在有网环境跑,离线降级面已由 hbcli-tests.ts(§7)覆盖`);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const staicliHome = mkdtempSync(join(tmpdir(), 'gotry-hbcli-e2e-'));
|
|
59
|
+
const stateRoot = mkdtempSync(join(tmpdir(), 'gotry-hbcli-e2e-state-'));
|
|
60
|
+
process.env.STAICLI_HOME = staicliHome;
|
|
61
|
+
const fallbackPath = join(import.meta.dirname, '..', '..', 'data', 'hotels_2026.json');
|
|
62
|
+
try {
|
|
63
|
+
const set = spawnSync(bin, [
|
|
64
|
+
'auth',
|
|
65
|
+
'set-credentials',
|
|
66
|
+
'--app-key',
|
|
67
|
+
SANDBOX_APP_KEY,
|
|
68
|
+
'--app-secret',
|
|
69
|
+
SANDBOX_APP_SECRET
|
|
70
|
+
], {
|
|
71
|
+
timeout: 30_000,
|
|
72
|
+
encoding: 'utf-8'
|
|
73
|
+
});
|
|
74
|
+
assert.equal(set.status, 0, `auth set-credentials 应成功,实际 ${set.status}: ${set.stderr?.slice(0, 200)}`);
|
|
75
|
+
console.log(`1. 沙箱账号写入隔离 STAICLI_HOME(${staicliHome.replace(tmpdir(), '$TMPDIR')})OK`);
|
|
76
|
+
const who = spawnSync(bin, [
|
|
77
|
+
'auth',
|
|
78
|
+
'whoami'
|
|
79
|
+
], {
|
|
80
|
+
timeout: 30_000,
|
|
81
|
+
encoding: 'utf-8'
|
|
82
|
+
});
|
|
83
|
+
assert.equal(who.status, 0, 'whoami 应 exit 0');
|
|
84
|
+
assert.match(who.stdout, /"configured": true/, 'whoami 应报 api_key.configured=true');
|
|
85
|
+
console.log('1b. hbcli auth whoami 自检(api_key.configured=true)OK');
|
|
86
|
+
const dest = await listDestinations({
|
|
87
|
+
timeoutMs: 30_000
|
|
88
|
+
});
|
|
89
|
+
assert.equal(dest.via, 'hbcli-realtime', `destinations 应走实时通道,实际 ${dest.via}(${dest.error ?? ''})`);
|
|
90
|
+
assert.equal(dest.exitCode, 0, 'destinations 应 exit 0');
|
|
91
|
+
assert.match(dest.evidence, /\[实时API:hbcli@\d{4}-/, '证据链应带实时时间戳标注(L4 不变量)');
|
|
92
|
+
assert.ok(dest.result !== null && typeof dest.result === 'object', '实时返回应可解析为 JSON');
|
|
93
|
+
console.log(`2. 能力层实时通道(listDestinations → ${UAT})via=hbcli-realtime,证据链 OK`);
|
|
94
|
+
const hotels = await searchHotels({
|
|
95
|
+
destination: '深圳',
|
|
96
|
+
checkIn: '2026-09-18',
|
|
97
|
+
checkOut: '2026-09-20',
|
|
98
|
+
adults: 2
|
|
99
|
+
}, {
|
|
100
|
+
timeoutMs: 30_000,
|
|
101
|
+
fallbackPath
|
|
102
|
+
});
|
|
103
|
+
if (hotels.via === 'hbcli-error') {
|
|
104
|
+
assert.ok(hotels.error, '降级结果应携带上游原话原因');
|
|
105
|
+
assert.match(hotels.summary, /降级/, 'summary 应显式声明降级(不伪装成实时)');
|
|
106
|
+
assert.match(hotels.summary, /静态包/, '降级路径应指明静态包来源');
|
|
107
|
+
assert.ok(hotels.hotels !== null, '静态包深圳块应命中');
|
|
108
|
+
assert.match(hotels.summary, /非实时/, '降级产物必须显式标注非实时(L4 反伪装红线)');
|
|
109
|
+
console.log(`3. hotel-list 真实降级链(via=hbcli-error,原因:${String(hotels.error).slice(0, 60)}…;静态包命中+非实时标注)OK`);
|
|
110
|
+
} else {
|
|
111
|
+
assert.equal(hotels.via, 'hbcli-realtime');
|
|
112
|
+
assert.match(hotels.evidence, /\[实时API:hbcli@\d{4}-/);
|
|
113
|
+
console.log('3. hotel-list 实时返回(UAT 已有目的地数据)证据链 OK');
|
|
114
|
+
}
|
|
115
|
+
const itp = await makeProductionInterpreter({
|
|
116
|
+
breakers: new Map()
|
|
117
|
+
})({
|
|
118
|
+
effect: 'HBCLI_HOTEL_SEARCH',
|
|
119
|
+
params: {
|
|
120
|
+
destination: '深圳',
|
|
121
|
+
fallbackPath,
|
|
122
|
+
timeoutMs: 30_000
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
assert.equal(itp.trace.attempts, 1, `hbcli 策略永不重试,实际 attempts=${itp.trace.attempts}`);
|
|
126
|
+
assert.equal(itp.trace.channel, 'cli');
|
|
127
|
+
assert.ok(itp.trace.evidence[0]?.startsWith('[效应:HBCLI_HOTEL_SEARCH@'), 'trace 应带解译层横切证据');
|
|
128
|
+
assert.ok(itp.result !== null, '解译产物应原样透传渠道 observation(含降级形态)');
|
|
129
|
+
const viaAfter = itp.result.via;
|
|
130
|
+
assert.ok(viaAfter === 'hbcli-error' || viaAfter === 'hbcli-realtime', `渠道 via 应保真,实际 ${viaAfter}`);
|
|
131
|
+
console.log(`4. 效应解译层(attempts=1 永不重试,breaker=${itp.trace.breaker},via=${viaAfter})OK`);
|
|
132
|
+
const { apply } = await import('../src/index.js');
|
|
133
|
+
const registered = [];
|
|
134
|
+
apply({
|
|
135
|
+
tools: {
|
|
136
|
+
register: (t)=>registered.push(t)
|
|
137
|
+
}
|
|
138
|
+
}, {
|
|
139
|
+
stateRoot,
|
|
140
|
+
timeoutMs: 30_000,
|
|
141
|
+
hbcliBin: 'hbcli',
|
|
142
|
+
sessionAccess: 'ask'
|
|
143
|
+
});
|
|
144
|
+
const tool = registered.find((t)=>t.name === 'gotry_hotel_search');
|
|
145
|
+
assert.ok(tool, 'gotry_hotel_search 应已注册');
|
|
146
|
+
const obs = await tool.execute({
|
|
147
|
+
query: {
|
|
148
|
+
destination: '深圳'
|
|
149
|
+
}
|
|
150
|
+
}, null);
|
|
151
|
+
assert.equal(obs.ok, true, '工具面应返回 ok 包络(永不抛错契约)');
|
|
152
|
+
assert.ok(obs.via === 'hbcli-error' || obs.via === 'hbcli-realtime', `工具面 via 应与渠道一致,实际 ${obs.via}`);
|
|
153
|
+
const ev = String(obs.evidence);
|
|
154
|
+
if (obs.via === 'hbcli-error') {
|
|
155
|
+
assert.match(ev, /静态包/, '降级时证据链应标注静态包(估算必须显式)');
|
|
156
|
+
} else {
|
|
157
|
+
assert.match(ev, /实时API:hbcli/, '实时时证据链应标注 hbcli 时间戳');
|
|
158
|
+
}
|
|
159
|
+
assert.ok(String(obs.summary).length > 0, 'summary 应存在');
|
|
160
|
+
console.log(`5. 工具层端到端(gotry_hotel_search → 效应解译器 → ${UAT})via=${obs.via},evidence=${ev.slice(0, 46)}… OK`);
|
|
161
|
+
console.log('HBCLI E2E TESTS: 5/5 OK(隔离凭证面/实时通道/降级诚实/解译策略/工具面全链)');
|
|
162
|
+
} finally{
|
|
163
|
+
process.env.STAICLI_HOME = undefined;
|
|
164
|
+
rmSync(staicliHome, {
|
|
165
|
+
recursive: true,
|
|
166
|
+
force: true
|
|
167
|
+
});
|
|
168
|
+
rmSync(stateRoot, {
|
|
169
|
+
recursive: true,
|
|
170
|
+
force: true
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
main().catch((e)=>{
|
|
175
|
+
console.error(e);
|
|
176
|
+
process.exitCode = 1;
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
//# sourceURL=ts/scripts/hbcli-e2e-tests.ts
|
|
@@ -144,4 +144,4 @@ console.log(`6. ENOENT 人话化 OK:${r6.summary}`);
|
|
|
144
144
|
console.log('HBCLI TESTS: 8/8 OK (happy / error / no-binary / fallback-filter / fallback-no-match / v0.3.0 旗标回归 / ENOENT 人话化 / 候选路径)');
|
|
145
145
|
|
|
146
146
|
|
|
147
|
-
//# sourceURL
|
|
147
|
+
//# sourceURL=ts/scripts/hbcli-tests.ts
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import process from 'node:process';
|
|
2
|
+
import { startExtensionHealthWatch } from '../capabilities/session/health-watch.js';
|
|
3
|
+
function parseArgs(argv) {
|
|
4
|
+
const args = {
|
|
5
|
+
timeoutMs: 120_000,
|
|
6
|
+
intervalMs: 5_000,
|
|
7
|
+
json: true
|
|
8
|
+
};
|
|
9
|
+
for(let i = 0; i < argv.length; i += 1){
|
|
10
|
+
const cur = argv[i];
|
|
11
|
+
if (cur === '--timeout') args.timeoutMs = parseInt(argv[++i] ?? '120000', 10);
|
|
12
|
+
else if (cur === '--interval') args.intervalMs = parseInt(argv[++i] ?? '5000', 10);
|
|
13
|
+
else if (cur === '--json') args.json = true;
|
|
14
|
+
else if (cur === '--no-json') args.json = false;
|
|
15
|
+
}
|
|
16
|
+
if (Number.isNaN(args.timeoutMs) || args.timeoutMs < 0) args.timeoutMs = 120_000;
|
|
17
|
+
if (Number.isNaN(args.intervalMs) || args.intervalMs < 1_000) args.intervalMs = 5_000;
|
|
18
|
+
return args;
|
|
19
|
+
}
|
|
20
|
+
async function main() {
|
|
21
|
+
const args = parseArgs(process.argv.slice(2));
|
|
22
|
+
const businessProbe = process.argv.includes('--business-probe');
|
|
23
|
+
const watch = startExtensionHealthWatch({
|
|
24
|
+
timeoutMs: args.timeoutMs,
|
|
25
|
+
intervalMs: args.intervalMs,
|
|
26
|
+
keepBridge: true,
|
|
27
|
+
businessProbe
|
|
28
|
+
});
|
|
29
|
+
process.on('SIGINT', ()=>{
|
|
30
|
+
watch.cancel('SIGINT');
|
|
31
|
+
process.exit(0);
|
|
32
|
+
});
|
|
33
|
+
process.on('SIGTERM', ()=>{
|
|
34
|
+
watch.cancel('SIGTERM');
|
|
35
|
+
process.exit(0);
|
|
36
|
+
});
|
|
37
|
+
const outcome = await watch.waitReady();
|
|
38
|
+
if (args.json) {
|
|
39
|
+
process.stdout.write(JSON.stringify(outcome) + '\n');
|
|
40
|
+
} else {
|
|
41
|
+
process.stdout.write(`ready=${outcome.ready} attempts=${outcome.attempts} waitedMs=${outcome.waitedMs} reason=${'reason' in outcome ? outcome.reason : 'ok'}\n`);
|
|
42
|
+
}
|
|
43
|
+
process.exit(0);
|
|
44
|
+
}
|
|
45
|
+
main().catch((e)=>{
|
|
46
|
+
process.stderr.write(`[health-watch-cli] error: ${e instanceof Error ? e.message : String(e)}\n`);
|
|
47
|
+
process.exit(2);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
//# sourceURL=ts/scripts/health-watch-cli.ts
|
|
@@ -42,4 +42,4 @@ assert.ok(r3.suggestions.some((sg)=>sg.relax === 'total:budget'));
|
|
|
42
42
|
console.log(`TS JOURNEY TESTS: 5/5 OK(五段全链可行,¥${r1.money_cny},与 Python oracle 结构一致)`);
|
|
43
43
|
|
|
44
44
|
|
|
45
|
-
//# sourceURL
|
|
45
|
+
//# sourceURL=ts/scripts/journey-tests.ts
|
|
@@ -80,4 +80,4 @@ pass('动机层零衰减(构造性):本模块无作用于 motivation-profile 的
|
|
|
80
80
|
console.log(`\nMEMORY DECAY TESTS: ${n}/5 OK(memory-design P3 分级窗口/单调/地板/上界/动机零衰减)`);
|
|
81
81
|
|
|
82
82
|
|
|
83
|
-
//# sourceURL
|
|
83
|
+
//# sourceURL=ts/scripts/memory-decay-tests.ts
|
|
@@ -21,4 +21,4 @@ console.log(`经验回流率基线 = ${verifiedWishes}/${recalledWishes} = ${ref
|
|
|
21
21
|
if (pool.length === 0) console.log('(wish pool 为空——首访用户,指标从首条憧憬入池开始积累)');
|
|
22
22
|
|
|
23
23
|
|
|
24
|
-
//# sourceURL
|
|
24
|
+
//# sourceURL=ts/scripts/memory-metrics.ts
|
|
@@ -376,4 +376,4 @@ const invokedPath = process.argv[1] ? pathToFileURL(resolve(process.argv[1])).hr
|
|
|
376
376
|
if (import.meta.url === invokedPath) main();
|
|
377
377
|
|
|
378
378
|
|
|
379
|
-
//# sourceURL
|
|
379
|
+
//# sourceURL=ts/scripts/memory-value-report.ts
|