@danceiny/gotry 0.0.1-rc.18 → 0.0.1-rc.20
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 +42 -16
- package/README.zh-CN.md +37 -15
- package/bin/gotry-bootstrap.js +309 -30
- package/bin/gotry-inner.js +74 -23
- package/bin/gotry-runtime-resolution.d.ts +1 -1
- package/bin/gotry-runtime-resolution.js +7 -8
- package/cordis.gotry-patch.yml +22 -8
- package/data/stations-12306-verify.json +524 -0
- package/dist/capabilities/agent-reach.js +4 -4
- package/dist/capabilities/anything.js +62 -29
- package/dist/capabilities/channel-health.js +103 -0
- package/dist/capabilities/channel-registry.js +213 -0
- package/dist/capabilities/doctor.js +307 -0
- package/dist/capabilities/effect.js +178 -5
- package/dist/capabilities/flyai.js +13 -0
- package/dist/capabilities/hbcli.js +28 -2
- package/dist/capabilities/session/adapters/ctrip-hotel.js +207 -0
- package/dist/capabilities/session/adapters/rail-12306.js +285 -0
- package/dist/capabilities/session/extension-bridge.js +14 -1
- package/dist/capabilities/session/extension-channel.js +4 -2
- package/dist/capabilities/session/extension-distribution.js +1 -1
- package/dist/capabilities/session-search.js +311 -0
- package/dist/capabilities/visa-policy.js +126 -0
- package/dist/scripts/agent-planning-turn-deadline-e2e.js +305 -0
- package/dist/scripts/agent-planning-turn-deadline-tests.js +281 -0
- package/dist/scripts/anything-tests.js +70 -21
- package/dist/scripts/benchmark-environment-bridge-e2e.js +139 -37
- package/dist/scripts/benchmark-environment-bridge-tests.js +1099 -306
- package/dist/scripts/booking-copilot-availability-ledger-binding-tests.js +26 -26
- package/dist/scripts/{booking-copilot-availability-policy-v2-tests.js → booking-copilot-availability-policy-tests.js} +115 -115
- package/dist/scripts/booking-copilot-crossrepo-fixture-server.js +41 -1
- package/dist/scripts/booking-copilot-dsh-core-proof-tests.js +75 -120
- package/dist/scripts/booking-copilot-dsh-planner-proof-tests.js +370 -61
- package/dist/scripts/booking-copilot-dsh-plugin-proof-tests.js +2 -2
- package/dist/scripts/booking-copilot-event-sequence-concurrency-proof-tests.js +7 -3
- package/dist/scripts/booking-copilot-gap-code-contract-proof-tests.js +15 -9
- package/dist/scripts/booking-copilot-operation-ledger-concurrency-proof-tests.js +19 -11
- package/dist/scripts/booking-copilot-receipt-ledger-concurrency-proof-tests.js +16 -52
- package/dist/scripts/booking-copilot-runtime-proof-tests.js +3953 -155
- package/dist/scripts/booking-copilot-server-proof-tests.js +58 -23
- package/dist/scripts/booking-copilot-startup-proof-tests.js +10 -119
- package/dist/scripts/booking-surface-contract-proof-tests.js +1154 -326
- package/dist/scripts/bootstrap-tests.js +113 -2
- package/dist/scripts/build-metrics-report.js +339 -0
- package/dist/scripts/channel-probe-tests.js +148 -0
- package/dist/scripts/channel-probe.js +252 -0
- package/dist/scripts/channel-registry-tests.js +262 -0
- package/dist/scripts/doctor-tests.js +175 -0
- package/dist/scripts/effect-tests.js +274 -1
- package/dist/scripts/extension-tests.js +129 -11
- package/dist/scripts/fact-gate-tests.js +91 -2
- package/dist/scripts/flyai-tests.js +17 -1
- package/dist/scripts/hbcli-e2e-tests.js +1 -3
- package/dist/scripts/hbcli-tests.js +38 -1
- package/dist/scripts/metrics-report-tests.js +288 -0
- package/dist/scripts/persona-surface-guard-tests.js +19 -0
- package/dist/scripts/session-tests.js +401 -1
- package/dist/scripts/smoke.js +100 -83
- package/dist/scripts/turn-handoff-collect-tests.js +216 -0
- package/dist/scripts/turn-handoff-collect.js +189 -0
- package/dist/scripts/turn-policy-tests.js +56 -0
- package/dist/scripts/typed-contract-canary.js +261 -0
- package/dist/scripts/visa-policy-tests.js +75 -0
- package/dist/scripts/wish-channel-gate-tests.js +111 -0
- package/dist/src/artifact-gate.js +91 -3
- package/dist/src/benchmark-agent-conformance.js +39 -16
- package/dist/src/benchmark-environment-bridge.js +316 -52
- package/dist/src/bookable-facts.js +62 -3
- package/dist/src/booking-surface/{availability-policy-v2.js → availability-policy.js} +89 -89
- package/dist/src/booking-surface/canonical-schema.js +8 -22
- package/dist/src/booking-surface/contracts.js +70 -19
- package/dist/src/booking-surface/dsh-planner.js +310 -177
- package/dist/src/booking-surface/dsh-plugin.js +3 -3
- package/dist/src/booking-surface/index.js +1 -5
- package/dist/src/booking-surface/profile.js +1 -1
- package/dist/src/booking-surface/runtime.js +1682 -247
- package/dist/src/booking-surface/server.js +371 -185
- package/dist/src/booking-surface/startup.js +17 -27
- package/dist/src/booking-surface/validation.js +270 -760
- package/dist/src/index.js +739 -150
- package/dist/src/turn-deadline.js +292 -0
- package/dist/src/turn-policy.js +146 -0
- package/dist/src/wish-pool.js +5 -0
- package/extension/background.js +23 -4
- package/extension/content-main.js +47 -9
- package/extension/manifest.json +27 -9
- package/package.json +7 -26
- package/schemas/booking.surface.schema.json +2593 -0
- package/ts/capabilities/agent-reach.ts +4 -4
- package/ts/capabilities/anything.ts +89 -38
- package/ts/capabilities/flyai.ts +21 -3
- package/ts/capabilities/hbcli.ts +53 -4
- package/ts/capabilities/session/action-cache.ts +1 -1
- package/ts/capabilities/session/adapters/ctrip-hotel.ts +238 -0
- package/ts/capabilities/session/adapters/rail-12306.ts +293 -0
- package/ts/capabilities/session/extension-bridge.ts +32 -2
- package/ts/capabilities/session/extension-channel.ts +9 -8
- package/ts/capabilities/session/extension-distribution.ts +3 -1
- package/ts/capabilities/session/health-watch.ts +1 -1
- package/ts/capabilities/session/read-guard.ts +1 -1
- package/ts/capabilities/session/wizard.ts +1 -1
- package/ts/capabilities/session-search.ts +323 -1
- package/ts/dsh-runtime/vendor/README.md +60 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/LICENSE +21 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/README.en.md +200 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/README.md +202 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/client/client.js +216 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/cordis.patch.yml +5 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/amap.js +371 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/nominatim.js +63 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/osrm.js +56 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/photon.js +55 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/config-file.js +87 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/config-route.js +126 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/config.js +16 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/index.js +109 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/settings-ns.js +27 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/tools/geocode.js +127 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/tools/poi.js +84 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/tools/routes.js +170 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/amap.d.ts +53 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/nominatim.d.ts +17 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/osrm.d.ts +15 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/photon.d.ts +22 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/config-file.d.ts +33 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/config-route.d.ts +20 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/config.d.ts +23 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/index.d.ts +18 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/settings-ns.d.ts +14 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/tools/geocode.d.ts +11 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/tools/poi.d.ts +9 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/tools/routes.d.ts +16 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/types.d.ts +55 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types.js +16 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/package.json +91 -0
- package/ts/package.json +9 -0
- package/ts/scripts/turn-handoff-collect.ts +178 -0
- package/ts/src/artifact-gate.ts +82 -2
- package/ts/src/benchmark-agent-conformance.ts +54 -15
- package/ts/src/benchmark-environment-bridge.ts +234 -46
- package/ts/src/bookable-facts.ts +109 -5
- package/ts/src/booking-saga.ts +1 -1
- package/ts/src/booking-surface/{availability-policy-v2.ts → availability-policy.ts} +124 -125
- package/ts/src/booking-surface/canonical-schema.js +8 -22
- package/ts/src/booking-surface/contracts.ts +278 -239
- package/ts/src/booking-surface/dsh-planner.ts +333 -194
- package/ts/src/booking-surface/dsh-plugin.js +3 -3
- package/ts/src/booking-surface/index.ts +1 -5
- package/ts/src/booking-surface/profile.ts +11 -11
- package/ts/src/booking-surface/runtime.ts +1351 -351
- package/ts/src/booking-surface/server.ts +282 -175
- package/ts/src/booking-surface/startup.ts +36 -48
- package/ts/src/booking-surface/validation.ts +194 -442
- package/ts/src/contracts.ts +1 -1
- package/ts/src/index.ts +492 -172
- package/ts/src/loop.ts +1 -1
- package/ts/src/state-ledger.ts +1 -1
- package/ts/src/turn-deadline.ts +365 -0
- package/ts/src/turn-policy.ts +154 -0
- package/ts/src/wish-pool.ts +17 -4
- package/dist/scripts/agent-planning-budget-e2e.js +0 -227
- package/dist/scripts/agent-planning-budget-tests.js +0 -173
- package/dist/scripts/booking-copilot-v2-runtime-proof-tests.js +0 -3935
- package/dist/scripts/booking-surface-v2-contract-proof-tests.js +0 -1174
- package/dist/src/booking-surface/contracts-v2.js +0 -89
- package/dist/src/booking-surface/runtime-v2.js +0 -1771
- package/dist/src/booking-surface/server-v2.js +0 -334
- package/dist/src/booking-surface/validation-v2.js +0 -319
- package/dist/src/tool-budget.js +0 -136
- package/schemas/booking.surface.v1.schema.json +0 -927
- package/schemas/booking.surface.v2.schema.json +0 -61
- package/ts/src/booking-surface/contracts-v2.ts +0 -118
- package/ts/src/booking-surface/runtime-v2.ts +0 -1466
- package/ts/src/booking-surface/server-v2.ts +0 -247
- package/ts/src/booking-surface/validation-v2.ts +0 -205
- package/ts/src/tool-budget.ts +0 -165
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 12306 火车站点适配器(2026-09-03,继 ctrip-hotel 后会话面第三通道):
|
|
3
|
+
* - entry:https://kyfw.12306.cn/otn/leftTicket/init?linktypeid=dc&fs=<城市名,电报码>&ts=<...>&date=YYYY-MM-DD&flag=N,N,Y
|
|
4
|
+
* (电报码表官方站表全量校准 2026-09-03,129 城;曾纠出南宁 NIZ→NNZ 错码)
|
|
5
|
+
* - networkHints:kyfw leftTicket/query(负载均衡变体 queryG/queryZ/queryA 等全部命中)
|
|
6
|
+
* - 解析:leftTicket/query 返回 data.result[] 管道行(12306 经典 | 拼接格式)。
|
|
7
|
+
* **第一方校准(2026-09-03)**:字段索引与站名映射(data.map[电报码])逐一
|
|
8
|
+
* 取自官方前端 queryLeftTicket_end_js.js 的 cN(result,map) 转换函数——旧版
|
|
9
|
+
* 公开常识索引与官方现行映射不符(站名/座位桶全错),真会话前即纠;行级
|
|
10
|
+
* 签名校验(车次码/时刻形态)继续兜住未来漂移:失配整行跳过,fail-visible
|
|
11
|
+
*
|
|
12
|
+
* 与机/酒通道的两点刻意差异:
|
|
13
|
+
* - 无登录闸:12306 余票查询是公开面(登录只关系下单),无账号数据过手;
|
|
14
|
+
* 扩展照样只读被动嗅探(零写行为),证据链标注「公开查询面」
|
|
15
|
+
* - 列表接口不含票价:如实只呈现 车次/时刻/历时/余票,票价以 12306 落地页为准
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/** 城市电报码表(**官方口径校准 2026-09-03**:全部条目逐一核对自
|
|
19
|
+
* kyfw.12306.cn/otn/resources/js/framework/station_name.js 官方全量站表
|
|
20
|
+
* ——曾借此抓出南宁 NIZ→NNZ 错码;代表站规则=城市同名站,fs/ts 用城市码
|
|
21
|
+
* 服务端扩到市内全部站。词表外 unresolved 逐字保留,调用方可显式传电报码
|
|
22
|
+
* 覆盖——kyfw 查询页 URL 里的 fs=城市,XXX 三位码;核对快照见
|
|
23
|
+
* data/stations-12306-verify.json) */
|
|
24
|
+
export const STATION_TELECODES: Record<string, string> = {
|
|
25
|
+
"三亚": 'SEQ',
|
|
26
|
+
"上海": 'SHH',
|
|
27
|
+
"上饶": 'SRG',
|
|
28
|
+
"东莞": 'RTQ',
|
|
29
|
+
"中卫": 'ZWJ',
|
|
30
|
+
"中山": 'ZSQ',
|
|
31
|
+
"丽江": 'LHM',
|
|
32
|
+
"乌鲁木齐": 'WAR',
|
|
33
|
+
"乐山": 'IVW',
|
|
34
|
+
"九江": 'JJG',
|
|
35
|
+
"伊宁": 'YMR',
|
|
36
|
+
"佛山": 'FSQ',
|
|
37
|
+
"保山": 'BAM',
|
|
38
|
+
"六盘水": 'UMW',
|
|
39
|
+
"兰州": 'LZJ',
|
|
40
|
+
"包头": 'BTC',
|
|
41
|
+
"北京": 'BJP',
|
|
42
|
+
"北海": 'BHZ',
|
|
43
|
+
"十堰": 'SNN',
|
|
44
|
+
"南京": 'NJH',
|
|
45
|
+
"南充": 'NCW',
|
|
46
|
+
"南宁": 'NNZ',
|
|
47
|
+
"南昌": 'NCG',
|
|
48
|
+
"南通": 'NUH',
|
|
49
|
+
"南阳": 'NFF',
|
|
50
|
+
"厦门": 'XMS',
|
|
51
|
+
"台州": 'TEU',
|
|
52
|
+
"合肥": 'HFH',
|
|
53
|
+
"吉林": 'JLL',
|
|
54
|
+
"吐鲁番": 'TFR',
|
|
55
|
+
"呼和浩特": 'HHC',
|
|
56
|
+
"咸阳": 'XYY',
|
|
57
|
+
"哈密": 'HMR',
|
|
58
|
+
"哈尔滨": 'HBB',
|
|
59
|
+
"喀什": 'KSR',
|
|
60
|
+
"嘉兴": 'JXH',
|
|
61
|
+
"嘉峪关": 'JGJ',
|
|
62
|
+
"大同": 'DTV',
|
|
63
|
+
"大庆": 'DZX',
|
|
64
|
+
"大理": 'DKM',
|
|
65
|
+
"大连": 'DLT',
|
|
66
|
+
"天水": 'TSJ',
|
|
67
|
+
"天津": 'TJP',
|
|
68
|
+
"太原": 'TYV',
|
|
69
|
+
"宁波": 'NGH',
|
|
70
|
+
"安顺": 'ASW',
|
|
71
|
+
"宜宾": 'YKE',
|
|
72
|
+
"宜昌": 'YCN',
|
|
73
|
+
"宝鸡": 'BJY',
|
|
74
|
+
"岳阳": 'YYQ',
|
|
75
|
+
"常州": 'CZH',
|
|
76
|
+
"常德": 'VGQ',
|
|
77
|
+
"广州": 'GZQ',
|
|
78
|
+
"库尔勒": 'KLR',
|
|
79
|
+
"延安": 'YWY',
|
|
80
|
+
"开封": 'KFF',
|
|
81
|
+
"张掖": 'ZYJ',
|
|
82
|
+
"徐州": 'XCH',
|
|
83
|
+
"德阳": 'DYW',
|
|
84
|
+
"惠州": 'HCQ',
|
|
85
|
+
"成都": 'CDW',
|
|
86
|
+
"扬州": 'YLH',
|
|
87
|
+
"承德": 'CDP',
|
|
88
|
+
"抚顺": 'FET',
|
|
89
|
+
"拉萨": 'LSO',
|
|
90
|
+
"揭阳": 'JYA',
|
|
91
|
+
"新乡": 'XXF',
|
|
92
|
+
"无锡": 'WGH',
|
|
93
|
+
"日喀则": 'RKO',
|
|
94
|
+
"昆明": 'KMM',
|
|
95
|
+
"普洱": 'PEM',
|
|
96
|
+
"景德镇": 'JCG',
|
|
97
|
+
"曲靖": 'QJM',
|
|
98
|
+
"杭州": 'HZH',
|
|
99
|
+
"林芝": 'LZO',
|
|
100
|
+
"柳州": 'LZZ',
|
|
101
|
+
"株洲": 'ZZQ',
|
|
102
|
+
"格尔木": 'GRO',
|
|
103
|
+
"桂林": 'GLZ',
|
|
104
|
+
"梧州": 'WZZ',
|
|
105
|
+
"武汉": 'WHN',
|
|
106
|
+
"汉中": 'HOY',
|
|
107
|
+
"汕头": 'OTQ',
|
|
108
|
+
"沈阳": 'SYT',
|
|
109
|
+
"泉州": 'QYS',
|
|
110
|
+
"泸州": 'LUE',
|
|
111
|
+
"洛阳": 'LYF',
|
|
112
|
+
"济南": 'JNK',
|
|
113
|
+
"深圳": 'SZQ',
|
|
114
|
+
"温州": 'RZH',
|
|
115
|
+
"湛江": 'ZJZ',
|
|
116
|
+
"漳州": 'ZUS',
|
|
117
|
+
"潮州": 'CKQ',
|
|
118
|
+
"牡丹江": 'MDB',
|
|
119
|
+
"珠海": 'ZHQ',
|
|
120
|
+
"盐城": 'AFH',
|
|
121
|
+
"石家庄": 'SJP',
|
|
122
|
+
"福州": 'FZS',
|
|
123
|
+
"秦皇岛": 'QTP',
|
|
124
|
+
"绍兴": 'SOH',
|
|
125
|
+
"绵阳": 'MYW',
|
|
126
|
+
"芜湖": 'WHH',
|
|
127
|
+
"苏州": 'SZH',
|
|
128
|
+
"茂名": 'MDQ',
|
|
129
|
+
"莆田": 'PTS',
|
|
130
|
+
"衡阳": 'HYQ',
|
|
131
|
+
"襄阳": 'XFN',
|
|
132
|
+
"西宁": 'XNO',
|
|
133
|
+
"西安": 'XAY',
|
|
134
|
+
"西昌": 'ECW',
|
|
135
|
+
"贵阳": 'GIW',
|
|
136
|
+
"赣州": 'GZG',
|
|
137
|
+
"达州": 'RXW',
|
|
138
|
+
"遵义": 'ZYE',
|
|
139
|
+
"郑州": 'ZZF',
|
|
140
|
+
"重庆": 'CUW',
|
|
141
|
+
"金华": 'JBH',
|
|
142
|
+
"银川": 'YIJ',
|
|
143
|
+
"镇江": 'ZJH',
|
|
144
|
+
"长春": 'CCT',
|
|
145
|
+
"长沙": 'CSQ',
|
|
146
|
+
"阜阳": 'FYH',
|
|
147
|
+
"青岛": 'QDK',
|
|
148
|
+
"鞍山": 'AST',
|
|
149
|
+
"韶关": 'SNQ',
|
|
150
|
+
"香格里拉": 'EUM',
|
|
151
|
+
"香港": 'XJA',
|
|
152
|
+
"齐齐哈尔": 'QHX',
|
|
153
|
+
"龙岩": 'LYS',
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export interface TrainEntryQuery {
|
|
157
|
+
from: string
|
|
158
|
+
to: string
|
|
159
|
+
/** YYYY-MM-DD */
|
|
160
|
+
date: string
|
|
161
|
+
/** 显式城市电报码(覆盖码表;kyfw 查询页 URL fs=城市,XXX 的三位码) */
|
|
162
|
+
fromStationTelecode?: string
|
|
163
|
+
toStationTelecode?: string
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export interface AdapterEntry {
|
|
167
|
+
ok: boolean
|
|
168
|
+
url?: string
|
|
169
|
+
unresolved?: string[]
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export function buildTrainEntryUrl(q: TrainEntryQuery): AdapterEntry {
|
|
173
|
+
const unresolved: string[] = []
|
|
174
|
+
const fromTc = (q.fromStationTelecode ?? '').trim().toUpperCase() || STATION_TELECODES[q.from.trim()]
|
|
175
|
+
const toTc = (q.toStationTelecode ?? '').trim().toUpperCase() || STATION_TELECODES[q.to.trim()]
|
|
176
|
+
if (!fromTc) unresolved.push(q.from)
|
|
177
|
+
if (!toTc) unresolved.push(q.to)
|
|
178
|
+
if (unresolved.length > 0 || !/^\d{4}-\d{2}-\d{2}$/.test(q.date)) {
|
|
179
|
+
return { ok: false, unresolved: unresolved.length > 0 ? unresolved : [q.date] }
|
|
180
|
+
}
|
|
181
|
+
const fs = encodeURIComponent(`${q.from.trim()},${fromTc}`)
|
|
182
|
+
const ts = encodeURIComponent(`${q.to.trim()},${toTc}`)
|
|
183
|
+
return { ok: true, url: `https://kyfw.12306.cn/otn/leftTicket/init?linktypeid=dc&fs=${fs}&ts=${ts}&date=${q.date}&flag=N,N,Y` }
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** networkHints:余票查询 XHR(queryG/Z/A/U 等负载均衡变体全命中) */
|
|
187
|
+
export const TRAIN_NETWORK_HINTS = [/leftTicket\/query/i]
|
|
188
|
+
|
|
189
|
+
/** 12306 页域(content_scripts 注入面与 background 白名单的对账源) */
|
|
190
|
+
export const TRAIN_SITE_HOST = 'kyfw.12306.cn'
|
|
191
|
+
|
|
192
|
+
export interface SessionTrainOption {
|
|
193
|
+
/** 车次(G1375 / K79 等) */
|
|
194
|
+
trainCode: string
|
|
195
|
+
fromStation: string
|
|
196
|
+
toStation: string
|
|
197
|
+
/** 出发/到达时刻(HH:MM;跨日由到达时刻本身表达,官方 DTO 无隔日字段) */
|
|
198
|
+
depTime: string
|
|
199
|
+
arrTime: string
|
|
200
|
+
/** 历时(分钟) */
|
|
201
|
+
durationMin: number
|
|
202
|
+
/** Y=可预订 / N=不可 / 其他上游原话 */
|
|
203
|
+
canWebBuy: string
|
|
204
|
+
/** 余票分桶(第一方校准索引,见 SEAT_BUCKETS;值原样:数字 / 有 / 无 / --) */
|
|
205
|
+
seats: Record<string, string>
|
|
206
|
+
/** 查询落地页(由人选车完成预订;gotry 不碰) */
|
|
207
|
+
jumpUrl?: string
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** 座位桶索引表(**第一方校准 2026-09-03**:逐字段取自 12306 官方前端
|
|
211
|
+
* queryLeftTicket_end_js.js 的 cN(result,map) 转换函数——旧版公开常识索引
|
|
212
|
+
* (19-28 段)与官方现行映射不符,首查前即纠);label=页面席别口径,未知席别
|
|
213
|
+
* 保留上游字段 id 原样 */
|
|
214
|
+
const SEAT_BUCKETS: Array<{ index: number; field: string; label: string }> = [
|
|
215
|
+
{ index: 20, field: 'gg_num', label: '其他(通勤)' },
|
|
216
|
+
{ index: 21, field: 'gr_num', label: '高级软卧' },
|
|
217
|
+
{ index: 22, field: 'qt_num', label: '其他' },
|
|
218
|
+
{ index: 23, field: 'rw_num', label: '软卧(一等卧)' },
|
|
219
|
+
{ index: 24, field: 'rz_num', label: '软座' },
|
|
220
|
+
{ index: 25, field: 'tz_num', label: '特等座' },
|
|
221
|
+
{ index: 26, field: 'wz_num', label: '无座' },
|
|
222
|
+
{ index: 27, field: 'yb_num', label: 'yp_b(上游席别)' },
|
|
223
|
+
{ index: 28, field: 'yw_num', label: '硬卧(二等卧)' },
|
|
224
|
+
{ index: 29, field: 'yz_num', label: '硬座' },
|
|
225
|
+
{ index: 30, field: 'ze_num', label: '二等座' },
|
|
226
|
+
{ index: 31, field: 'zy_num', label: '一等座' },
|
|
227
|
+
{ index: 32, field: 'swz_num', label: '商务座' },
|
|
228
|
+
{ index: 33, field: 'srrb_num', label: '动卧' },
|
|
229
|
+
]
|
|
230
|
+
|
|
231
|
+
const TRAIN_CODE_RE = /^[GDCZTKYLSF]\d{1,5}[A-Z]?$/i
|
|
232
|
+
const HHMM_RE = /^\d{2}:\d{2}$/
|
|
233
|
+
|
|
234
|
+
/** 历时 "07:52" → 分钟 */
|
|
235
|
+
function lishiToMin(s: string | undefined): number {
|
|
236
|
+
const m = /^(\d{1,3}):(\d{2})$/.exec((s ?? '').trim())
|
|
237
|
+
if (!m) return 0
|
|
238
|
+
return Number(m[1]) * 60 + Number(m[2])
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/** 行级签名:车次码形态 + 时刻形态不符即整行跳过(索引漂移 fail-visible,不造数) */
|
|
242
|
+
function rowToOption(row: string, stationMap: Record<string, string> | undefined, entryUrl: string): SessionTrainOption | null {
|
|
243
|
+
const f = row.split('|')
|
|
244
|
+
if (f.length < 34) return null
|
|
245
|
+
const trainCode = (f[3] ?? '').trim()
|
|
246
|
+
const depTime = (f[8] ?? '').trim()
|
|
247
|
+
const arrTime = (f[9] ?? '').trim()
|
|
248
|
+
if (!TRAIN_CODE_RE.test(trainCode) || !HHMM_RE.test(depTime) || !HHMM_RE.test(arrTime)) return null
|
|
249
|
+
// 官方 cN 口径:站名 = data.map[电报码](map 缺失/缺键时退电报码原样,不猜名)
|
|
250
|
+
const fromStation = stationMap?.[f[6] ?? ''] ?? (f[6] ?? '')
|
|
251
|
+
const toStation = stationMap?.[f[7] ?? ''] ?? (f[7] ?? '')
|
|
252
|
+
const seats: Record<string, string> = {}
|
|
253
|
+
for (const b of SEAT_BUCKETS) {
|
|
254
|
+
const v = (f[b.index] ?? '').trim()
|
|
255
|
+
if (v && v !== '--') seats[b.label] = v
|
|
256
|
+
}
|
|
257
|
+
return {
|
|
258
|
+
trainCode,
|
|
259
|
+
fromStation,
|
|
260
|
+
toStation,
|
|
261
|
+
depTime,
|
|
262
|
+
arrTime,
|
|
263
|
+
durationMin: lishiToMin(f[10]),
|
|
264
|
+
canWebBuy: (f[11] ?? '').trim(),
|
|
265
|
+
seats,
|
|
266
|
+
jumpUrl: entryUrl,
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/** 解析 leftTicket/query 响应(纯函数,fixture 测试锚点;字段索引与站名映射
|
|
271
|
+
* 全部对齐 12306 官方前端 cN(result,map)——见 SEAT_BUCKETS 注释);
|
|
272
|
+
* malformed 一律返空,不抛错 */
|
|
273
|
+
export function parseLeftTicketQuery(body: string, entryUrl: string, opts: { maxItems?: number } = {}): SessionTrainOption[] {
|
|
274
|
+
let raw: { data?: { result?: unknown; map?: Record<string, string> } }
|
|
275
|
+
try {
|
|
276
|
+
raw = JSON.parse(body) as typeof raw
|
|
277
|
+
} catch {
|
|
278
|
+
return []
|
|
279
|
+
}
|
|
280
|
+
const rows = raw.data?.result
|
|
281
|
+
if (!Array.isArray(rows)) return []
|
|
282
|
+
const map = (raw.data?.map && typeof raw.data.map === 'object') ? raw.data.map : undefined
|
|
283
|
+
const out: SessionTrainOption[] = []
|
|
284
|
+
for (const r of rows) {
|
|
285
|
+
if (typeof r !== 'string') continue
|
|
286
|
+
const opt = rowToOption(r, map, entryUrl)
|
|
287
|
+
if (opt) {
|
|
288
|
+
out.push(opt)
|
|
289
|
+
if (out.length >= (opts.maxItems ?? 30)) break
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
return out
|
|
293
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* 会话扩展桥(extension-bridge,RFC user-session-data-rfc.md §2.2 通道 C——2026-08-29 founder 定案为 PRIMARY 传输)。
|
|
2
|
+
* 会话扩展桥(extension-bridge,RFC docs/rfc/user-session-data-rfc.md §2.2 通道 C——2026-08-29 founder 定案为 PRIMARY 传输)。
|
|
3
3
|
*
|
|
4
4
|
* 形态:Node http 服务,懒启动单例,只绑 127.0.0.1,端口池 8791-8795(与 extension/manifest.json
|
|
5
5
|
* host_permissions 一一对应,防漂移测试守住);**零新依赖**(node:http 手写,对齐已退役 shell 薄壳
|
|
@@ -22,6 +22,9 @@
|
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
24
|
import { randomUUID } from 'node:crypto'
|
|
25
|
+
import { existsSync } from 'node:fs'
|
|
26
|
+
import { homedir } from 'node:os'
|
|
27
|
+
import { join } from 'node:path'
|
|
25
28
|
import { createServer, type IncomingMessage, type Server, type ServerResponse } from 'node:http'
|
|
26
29
|
|
|
27
30
|
/** 桥端口池(extension/manifest.json host_permissions 必须同面;§38 断言) */
|
|
@@ -41,6 +44,32 @@ export const EXTENSION_ORIGIN_STORE = `chrome-extension://${EXTENSION_ID_STORE}`
|
|
|
41
44
|
export const EXTENSION_STORE_URL = `https://chromewebstore.google.com/detail/gotry-session-bridge/${EXTENSION_ID_STORE}`
|
|
42
45
|
/** 桥信任的扩展 Origin 白名单:unpacked(固定 key)+ 商店版,双通道同一扩展同源信任 */
|
|
43
46
|
export const EXTENSION_ORIGINS: readonly string[] = [EXTENSION_ORIGIN, EXTENSION_ORIGIN_STORE]
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* 本地 unpacked 通道是否已落位(~/.gotry/extension/manifest.json)。
|
|
50
|
+
* 商店版没有本地文件面(装在 Chrome 里),离线不可探测——本函数只回答「本地通道在不在」,
|
|
51
|
+
* 商店版用户在这里恒 false,这正是 D-24 自适应文案的判定输入。
|
|
52
|
+
*/
|
|
53
|
+
export function localExtensionInstalled(home = homedir()): boolean {
|
|
54
|
+
return existsSync(join(home, '.gotry', 'extension', 'manifest.json'))
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* needs-extension 自适应摘要(issue #117,D-24 残量):
|
|
59
|
+
* - 本地 unpacked 已落位 → 完整双通道指引(商店 + GitHub/开发者模式本地通道);
|
|
60
|
+
* - 未落位(商店版用户,或完全未装)→ 只推商店一键装,并给「已装商店版?打开 Chrome 即可」
|
|
61
|
+
* 提示——**不再出现开发者模式/本地通道文案**(已装商店版的用户不该再被指导 load unpacked)。
|
|
62
|
+
* 纯函数(默认 HOME 可注入)。
|
|
63
|
+
*/
|
|
64
|
+
export function needsExtensionSummary(opts: { localInstalled?: boolean; home?: string } = {}): string {
|
|
65
|
+
const local = opts.localInstalled ?? localExtensionInstalled(opts.home)
|
|
66
|
+
if (local) {
|
|
67
|
+
return 'GoTry Session Bridge 扩展未连接(未安装或已停用)。推荐 Chrome 应用商店一键装(自动更新) '
|
|
68
|
+
+ `${EXTENSION_STORE_URL} ;或 npx gotry setup 落位后 chrome://extensions 开发者模式「加载已解压的扩展程序」指向 ~/.gotry/extension`
|
|
69
|
+
}
|
|
70
|
+
return 'GoTry Session Bridge 扩展未连接。若你已从 Chrome 商店安装(自动更新):打开 Chrome 并确认扩展已启用即可自动连接,无需再装。'
|
|
71
|
+
+ `若尚未安装:应用商店一键装 ${EXTENSION_STORE_URL} (装完零弹窗,浏览器自己当安装器)`
|
|
72
|
+
}
|
|
44
73
|
/** 扩展在线判定:/health 心跳 ≤30s 一次 + 轮询重连,1.5 倍容差 */
|
|
45
74
|
export const EXTENSION_CONNECTED_WINDOW_MS = 45_000
|
|
46
75
|
/** /jobs 长轮询 hold 上限(必须 < MV3 SW 30s 存活窗口) */
|
|
@@ -292,7 +321,8 @@ export async function createSessionBridge(opts: SessionBridgeOptions = {}): Prom
|
|
|
292
321
|
settle({
|
|
293
322
|
ok: false,
|
|
294
323
|
reason: 'extension-not-connected',
|
|
295
|
-
|
|
324
|
+
// D-24 自适应文案(issue #117):按本地通道是否落位决定是否提及开发者模式
|
|
325
|
+
summary: needsExtensionSummary(),
|
|
296
326
|
})
|
|
297
327
|
}
|
|
298
328
|
}, 250)
|
|
@@ -10,15 +10,16 @@
|
|
|
10
10
|
* 检索页无回包不是车道失败:与 CDP 车道同语义(到点无 hints 命中 ⇒ miss,页标题命挑战 ⇒ challenged)。
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import { getOrCreateSessionBridge,
|
|
13
|
+
import { getOrCreateSessionBridge, needsExtensionSummary, type SessionJobHandle } from './extension-bridge.ts'
|
|
14
14
|
|
|
15
|
-
/**
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
15
|
+
/**
|
|
16
|
+
* needs-extension 用户门文案(D-24 自适应,issue #117):按本地通道是否落位自动跳过
|
|
17
|
+
* 开发者模式/本地通道指引——商店版用户只看到商店一键装与「已装即可」提示。
|
|
18
|
+
* 旧静态常量已退役:静态文案无法感知安装状态,正是 D-24 残量的病灶。
|
|
19
|
+
*/
|
|
20
|
+
export function needsExtensionHint(): string {
|
|
21
|
+
return needsExtensionSummary()
|
|
22
|
+
}
|
|
22
23
|
|
|
23
24
|
export type BridgeFailureKind = 'bridge-unavailable' | 'extension-not-connected' | 'timeout' | 'job-error'
|
|
24
25
|
|
|
@@ -104,7 +104,9 @@ export function parseDistManifest(raw: string): ExtensionDistManifest {
|
|
|
104
104
|
const tarballSha256 = obj.tarballSha256
|
|
105
105
|
const zip = obj.zip
|
|
106
106
|
const zipSha256 = obj.zipSha256
|
|
107
|
-
|
|
107
|
+
// Chrome manifest version 形态:1-4 段点分整数(扩展版本跟随 gotry 主版本
|
|
108
|
+
// 0.0.1-rc.N → 四段投影 0.0.1.N,展示名走 version_name 原样)
|
|
109
|
+
if (typeof version !== 'string' || !/^\d+(\.\d+){1,3}$/.test(version)) throw new Error('dist-manifest.version 缺失或非 1-4 段点分整数')
|
|
108
110
|
if (tarball !== DIST_ASSET_TARBALL) throw new Error(`dist-manifest.tarball 资产名漂移(期望 ${DIST_ASSET_TARBALL})`)
|
|
109
111
|
if (zip !== DIST_ASSET_STORE_ZIP) throw new Error(`dist-manifest.zip 资产名漂移(期望 ${DIST_ASSET_STORE_ZIP})`)
|
|
110
112
|
if (typeof tarballSha256 !== 'string' || !SHA256_RE.test(tarballSha256)) throw new Error('dist-manifest.tarballSha256 非 64-hex')
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 扩展健康检查 + 自动重放(issue #21 onboarding UX,P3.6 gotry-session-onboarding-goal,
|
|
3
|
-
* RFC user-session-data-rfc.md §3.3)。
|
|
3
|
+
* RFC docs/rfc/user-session-data-rfc.md §3.3)。
|
|
4
4
|
*
|
|
5
5
|
* 动机:首次 `sessionFlightSearch` 遇 `needs-extension` 时,旧版要求用户装完扩展后**自己重跑命令**;
|
|
6
6
|
* 本模块把「用户手工重跑」改成「后台有界轮询 + 扩展一就位自动重放同一 query_id」——
|