@danceiny/gotry 0.0.1-rc.19 → 0.0.1-rc.21
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 +14 -3
- package/README.zh-CN.md +15 -4
- package/bin/gotry-bootstrap.js +136 -45
- package/bin/gotry-inner.js +11 -6
- package/cordis.gotry-patch.yml +19 -8
- package/dist/capabilities/anything.js +62 -29
- package/dist/capabilities/doctor.js +113 -19
- package/dist/capabilities/hbcli.js +28 -2
- package/dist/capabilities/session/health-watch.js +10 -3
- package/dist/scripts/agent-planning-turn-deadline-e2e.js +2 -1
- package/dist/scripts/anything-tests.js +70 -21
- package/dist/scripts/benchmark-environment-bridge-e2e.js +178 -33
- package/dist/scripts/benchmark-environment-bridge-tests.js +1480 -271
- package/dist/scripts/booking-copilot-dsh-planner-proof-tests.js +211 -2
- package/dist/scripts/booking-copilot-runtime-proof-tests.js +132 -2
- package/dist/scripts/bootstrap-tests.js +30 -3
- package/dist/scripts/doctor-tests.js +55 -1
- package/dist/scripts/hbcli-e2e-tests.js +1 -1
- package/dist/scripts/hbcli-tests.js +38 -1
- package/dist/scripts/health-watch-cli.js +9 -1
- package/dist/scripts/map-tools-vendor-package-proof.js +276 -0
- package/dist/scripts/persona-surface-guard-tests.js +8 -3
- package/dist/src/benchmark-agent-conformance.js +187 -17
- package/dist/src/benchmark-environment-bridge.js +287 -102
- package/dist/src/booking-surface/dsh-planner.js +24 -8
- package/dist/src/index.js +5 -2
- package/extension/manifest.json +2 -2
- package/package.json +3 -1
- package/ts/capabilities/anything.ts +89 -38
- package/ts/capabilities/hbcli.ts +53 -4
- package/ts/capabilities/session/health-watch.ts +9 -2
- 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 +17 -1
- package/ts/scripts/map-tools-vendor-package-proof.ts +268 -0
- package/ts/src/benchmark-agent-conformance.ts +175 -13
- package/ts/src/benchmark-environment-bridge.ts +220 -66
- package/ts/src/booking-surface/dsh-planner.ts +25 -13
- package/ts/src/index.ts +4 -2
- package/ts/src/turn-deadline.ts +4 -0
|
@@ -3,10 +3,17 @@
|
|
|
3
3
|
*
|
|
4
4
|
* 链路(同构 hbcli.ts):
|
|
5
5
|
* gotry capabilities/anything.ts
|
|
6
|
-
* → spawn hbcli search anything [
|
|
7
|
-
* → hotel-be /api/search/anything
|
|
6
|
+
* → spawn hbcli --json search anything <kw> [opts]
|
|
7
|
+
* → hotel-be /api/search/anything(httpdispatch 自动注册;httpdispatch 路由面)
|
|
8
8
|
* → search/service/geography.go func Anything (mixed 城市+酒店搜索)
|
|
9
9
|
*
|
|
10
|
+
* wire 契约(2026-09-07 对齐上游源码;SearchItem = FuzzySearchItem + hotel):
|
|
11
|
+
* req { keyword, contentType?, context.destinationId?, minHotelCount? }
|
|
12
|
+
* resp { candidates: [{ type: 'city'|'place'|'hotel',
|
|
13
|
+
* region?: { id, name:{en,zh,ar}, coordinates:{centerLat,centerLng}, countryCode, … },
|
|
14
|
+
* place?: { latlngCoordinator },
|
|
15
|
+
* matchScore?, hotel?: { id, name:{en,zh,ar}, star, latlngCoordinator:{google|gaode:{lat,lng} } } }] }
|
|
16
|
+
*
|
|
10
17
|
* 三值语义:
|
|
11
18
|
* - hit: 命中候选(混合 city/hotel,maybe-coords)
|
|
12
19
|
* - miss: hbcli exit 0,但 candidates 为空(查无结果)
|
|
@@ -22,33 +29,36 @@
|
|
|
22
29
|
*/
|
|
23
30
|
|
|
24
31
|
import { spawn } from 'node:child_process'
|
|
32
|
+
import { hbcliBinCandidates } from './hbcli.ts'
|
|
25
33
|
|
|
26
34
|
export interface AnythingQuery {
|
|
27
35
|
/** 多词以空格 join,与 hbcli argument-parser 一致;前后 trim。空则报错(unless contentType 强限定) */
|
|
28
36
|
keyword: string
|
|
29
37
|
/** 'city' | 'hotel' | undefined(混合) */
|
|
30
38
|
contentType?: 'city' | 'hotel'
|
|
31
|
-
/** 限定子区域 ID(
|
|
39
|
+
/** 限定子区域 ID(上游 SearchReq.context.destinationId) */
|
|
32
40
|
parentDestinationId?: string | number
|
|
33
41
|
/** 默认 12_000 ms */
|
|
34
42
|
timeoutMs?: number
|
|
35
|
-
/** 显式 hbcli 路径(默认 'hbcli',从 $PATH
|
|
43
|
+
/** 显式 hbcli 路径(默认 'hbcli',从 $PATH 找;默认名自动回退 ~/.local/bin 等已知安装位) */
|
|
36
44
|
hbcliBin?: string
|
|
37
45
|
}
|
|
38
46
|
|
|
39
47
|
export interface AnythingHit {
|
|
40
48
|
type: 'city' | 'hotel' | 'place'
|
|
41
|
-
/**
|
|
49
|
+
/** i18n name 解析(zh 优先,产品中文优先;回退 en/ar) */
|
|
42
50
|
name: string
|
|
43
|
-
/**
|
|
51
|
+
/** matchScore(文本相关度,越高越匹配) */
|
|
44
52
|
score?: number
|
|
45
|
-
/** lat/lng(
|
|
53
|
+
/** lat/lng(region.center 或 hotel/place latlngCoordinator,若给出) */
|
|
46
54
|
latitude?: number
|
|
47
55
|
longitude?: number
|
|
48
|
-
/** city id(
|
|
56
|
+
/** city id(region.id;酒店候选为所属 destinationId) */
|
|
49
57
|
destinationId?: string
|
|
50
58
|
/** hotel id(若有) */
|
|
51
59
|
hotelId?: string
|
|
60
|
+
/** 星级(酒店候选,若返回) */
|
|
61
|
+
star?: number
|
|
52
62
|
}
|
|
53
63
|
|
|
54
64
|
export interface AnythingResult {
|
|
@@ -63,16 +73,46 @@ export interface AnythingResult {
|
|
|
63
73
|
error?: string
|
|
64
74
|
}
|
|
65
75
|
|
|
76
|
+
/** 上游 i18n.I18N {en,zh,ar};tolerant 读取(字段可能缺省,老字符串形态也兜住) */
|
|
77
|
+
type I18N = { en?: string; zh?: string; ar?: string } | string | undefined
|
|
78
|
+
|
|
79
|
+
function i18nText(v: I18N): string {
|
|
80
|
+
if (!v) return ''
|
|
81
|
+
if (typeof v === 'string') return v
|
|
82
|
+
return v.zh || v.en || v.ar || ''
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** hotel-be Latlng:{lat,lng};google/gaode 双源取先非零者 */
|
|
86
|
+
function latlngOf(coord: { google?: { lat?: number; lng?: number }; gaode?: { lat?: number; lng?: number } } | undefined): { latitude?: number; longitude?: number } {
|
|
87
|
+
for (const src of [coord?.google, coord?.gaode]) {
|
|
88
|
+
const lat = typeof src?.lat === 'number' ? src.lat : undefined
|
|
89
|
+
const lng = typeof src?.lng === 'number' ? src.lng : undefined
|
|
90
|
+
if (lat !== undefined && lng !== undefined && (lat !== 0 || lng !== 0)) return { latitude: lat, longitude: lng }
|
|
91
|
+
}
|
|
92
|
+
return {}
|
|
93
|
+
}
|
|
94
|
+
|
|
66
95
|
interface RawSearchResp {
|
|
67
96
|
candidates?: RawSearchItem[]
|
|
68
97
|
}
|
|
98
|
+
/** hotel-be SearchItem wire 形状(FuzzySearchItem + hotel,2026-09-07 对齐源码) */
|
|
69
99
|
interface RawSearchItem {
|
|
70
100
|
type?: string
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
101
|
+
matchScore?: number
|
|
102
|
+
region?: {
|
|
103
|
+
id?: string | number
|
|
104
|
+
name?: I18N
|
|
105
|
+
countryCode?: string
|
|
106
|
+
coordinates?: { centerLat?: number; centerLng?: number }
|
|
107
|
+
}
|
|
108
|
+
place?: { latlngCoordinator?: { google?: { lat?: number; lng?: number }; gaode?: { lat?: number; lng?: number } } }
|
|
109
|
+
hotel?: {
|
|
110
|
+
id?: string | number
|
|
111
|
+
name?: I18N
|
|
112
|
+
star?: number
|
|
113
|
+
destinationId?: string | number
|
|
114
|
+
latlngCoordinator?: { google?: { lat?: number; lng?: number }; gaode?: { lat?: number; lng?: number } }
|
|
115
|
+
}
|
|
76
116
|
}
|
|
77
117
|
|
|
78
118
|
function sh(cmd: string, args: string[], opts: { timeoutMs: number; env: NodeJS.ProcessEnv }) {
|
|
@@ -115,8 +155,20 @@ function sh(cmd: string, args: string[], opts: { timeoutMs: number; env: NodeJS.
|
|
|
115
155
|
})
|
|
116
156
|
}
|
|
117
157
|
|
|
118
|
-
|
|
119
|
-
|
|
158
|
+
/** --json 必须在子命令前(cli.ts 全局旗标预扫描只认子命令前位置;JSON 错误也走结构化 stderr) */
|
|
159
|
+
function buildArgs(keyword: string, q: AnythingQuery): string[] {
|
|
160
|
+
const args = ['--json', 'search', 'anything', keyword]
|
|
161
|
+
if (q.contentType) args.push('--content-type', q.contentType)
|
|
162
|
+
if (q.parentDestinationId !== undefined) args.push('--destination-id', String(q.parentDestinationId))
|
|
163
|
+
return args
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** 旧版 CLI 无 search anything 子命令时的可行动升级指引(issue #195:裸 unknown command 读起来像工具坏了) */
|
|
167
|
+
function upgradeHint(stderr: string): string | null {
|
|
168
|
+
return /unknown command/i.test(stderr)
|
|
169
|
+
? 'hbcli 版本过旧(无 search anything 子命令)——请升级:hbcli update,或重跑 npx gotry setup'
|
|
170
|
+
: null
|
|
171
|
+
}
|
|
120
172
|
|
|
121
173
|
/** Anything 通用搜索 — 任何搜索失败走降级;不抛错 */
|
|
122
174
|
export async function anythingSearch(q: AnythingQuery): Promise<AnythingResult> {
|
|
@@ -131,27 +183,25 @@ export async function anythingSearch(q: AnythingQuery): Promise<AnythingResult>
|
|
|
131
183
|
error: 'keyword is required',
|
|
132
184
|
}
|
|
133
185
|
}
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
186
|
+
const args = buildArgs(kw, q)
|
|
187
|
+
// spawn 级失败(ENOENT)按已知安装位回退(hbcli.ts callHbcliJson 同款);其余失败无重试意义
|
|
188
|
+
const candidates = hbcliBinCandidates(q.hbcliBin ?? 'hbcli')
|
|
189
|
+
let r: { code: number; stdout: string; stderr: string; error?: string } = { code: -1, stdout: '', stderr: '' }
|
|
190
|
+
for (let i = 0; i < candidates.length; i++) {
|
|
191
|
+
r = await sh(candidates[i]!, args, { timeoutMs: q.timeoutMs ?? 12_000, env: process.env })
|
|
192
|
+
if (!(r.error && i < candidates.length - 1)) break
|
|
139
193
|
}
|
|
140
|
-
|
|
141
|
-
const r = await sh(bin, args.slice(1), {
|
|
142
|
-
timeoutMs: q.timeoutMs ?? 12_000,
|
|
143
|
-
env: process.env,
|
|
144
|
-
})
|
|
145
194
|
const latencyMs = Date.now() - started
|
|
146
195
|
|
|
147
196
|
if (r.error || r.code !== 0) {
|
|
197
|
+
const raw = r.error ?? `${r.stderr.slice(0, 200)} (exit ${r.code})`
|
|
148
198
|
return {
|
|
149
199
|
ok: false,
|
|
150
200
|
via: 'hbcli-anything-error',
|
|
151
|
-
evidence: `[实时API:hbcli-anything@error@${ts}] ${
|
|
201
|
+
evidence: `[实时API:hbcli-anything@error@${ts}] ${raw}`,
|
|
152
202
|
latencyMs,
|
|
153
203
|
verdict: 'error',
|
|
154
|
-
error: r.
|
|
204
|
+
error: upgradeHint(r.stderr) ?? raw,
|
|
155
205
|
}
|
|
156
206
|
}
|
|
157
207
|
|
|
@@ -174,15 +224,19 @@ export async function anythingSearch(q: AnythingQuery): Promise<AnythingResult>
|
|
|
174
224
|
const hits: AnythingHit[] = rawItems.map((it) => {
|
|
175
225
|
const region = it.region ?? {}
|
|
176
226
|
const hotel = it.hotel ?? {}
|
|
177
|
-
const isHotel = it.type === 'hotel' ||
|
|
227
|
+
const isHotel = it.type === 'hotel' || Boolean(hotel.id)
|
|
228
|
+
const hotelCoords = latlngOf(hotel.latlngCoordinator)
|
|
229
|
+
const placeCoords = latlngOf(it.place?.latlngCoordinator)
|
|
230
|
+
const center = region.coordinates ?? {}
|
|
178
231
|
return {
|
|
179
|
-
type: (isHotel ? 'hotel' : (it.type === 'place'
|
|
180
|
-
name:
|
|
181
|
-
score: it.
|
|
182
|
-
latitude:
|
|
183
|
-
longitude:
|
|
184
|
-
destinationId: region.id,
|
|
185
|
-
hotelId: hotel.id,
|
|
232
|
+
type: (isHotel ? 'hotel' : (it.type === 'place' ? 'place' : 'city')) as AnythingHit['type'],
|
|
233
|
+
name: i18nText(hotel.name) || i18nText(region.name) || '?',
|
|
234
|
+
score: it.matchScore,
|
|
235
|
+
latitude: hotelCoords.latitude ?? placeCoords.latitude ?? (typeof center.centerLat === 'number' ? center.centerLat : undefined),
|
|
236
|
+
longitude: hotelCoords.longitude ?? placeCoords.longitude ?? (typeof center.centerLng === 'number' ? center.centerLng : undefined),
|
|
237
|
+
destinationId: hotel.destinationId !== undefined ? String(hotel.destinationId) : (region.id !== undefined ? String(region.id) : undefined),
|
|
238
|
+
hotelId: hotel.id !== undefined ? String(hotel.id) : undefined,
|
|
239
|
+
star: typeof hotel.star === 'number' && hotel.star > 0 ? hotel.star : undefined,
|
|
186
240
|
}
|
|
187
241
|
})
|
|
188
242
|
|
|
@@ -196,6 +250,3 @@ export async function anythingSearch(q: AnythingQuery): Promise<AnythingResult>
|
|
|
196
250
|
totalCandidates: rawItems.length,
|
|
197
251
|
}
|
|
198
252
|
}
|
|
199
|
-
|
|
200
|
-
// noop exports to keep file shape consistent with hbcli.ts
|
|
201
|
-
export const _ = { upperFirst }
|
package/ts/capabilities/hbcli.ts
CHANGED
|
@@ -142,19 +142,66 @@ export async function callHbcliJson(
|
|
|
142
142
|
return last!
|
|
143
143
|
}
|
|
144
144
|
|
|
145
|
+
/** 上游 i18n 名称容错读取(zh 优先,中文优先产品口径;回退 en/ar) */
|
|
146
|
+
function i18nName(v: { zh?: string; en?: string; ar?: string } | string | undefined): string {
|
|
147
|
+
if (!v) return ''
|
|
148
|
+
if (typeof v === 'string') return v
|
|
149
|
+
return v.zh || v.en || v.ar || ''
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* 实时 hotelList 响应 → 模型可读紧凑行(id/名称/星级/最低价)。
|
|
154
|
+
* issue #195:render 面只透 summary,若 summary 不含数据,模型只看到
|
|
155
|
+
* 「实时返回」四个字——工具调用等于白调。id 必须在行内,hotel-rates 的入参
|
|
156
|
+
* 就来自这里。上游 wire:HotelListResp{list:[Hotel{id,name:{en,zh},star,minPrice:{amount,currency}}]}。
|
|
157
|
+
*/
|
|
158
|
+
function hotelLines(result: unknown, max = 8): string {
|
|
159
|
+
const list = (result as { list?: unknown } | null)?.list
|
|
160
|
+
if (!Array.isArray(list) || list.length === 0) return ''
|
|
161
|
+
const lines: string[] = []
|
|
162
|
+
for (const item of list.slice(0, max)) {
|
|
163
|
+
const h = item as {
|
|
164
|
+
id?: string | number
|
|
165
|
+
name?: { zh?: string; en?: string; ar?: string }
|
|
166
|
+
star?: number
|
|
167
|
+
minPrice?: { amount?: number; currency?: string }
|
|
168
|
+
}
|
|
169
|
+
const name = i18nName(h.name) || `hotel ${h.id ?? '?'}`
|
|
170
|
+
const star = typeof h.star === 'number' && h.star > 0 ? ` ${h.star}★` : ''
|
|
171
|
+
const price = h.minPrice && typeof h.minPrice.amount === 'number'
|
|
172
|
+
? ` 低至 ${h.minPrice.amount}${h.minPrice.currency ? ` ${h.minPrice.currency}` : ''}`
|
|
173
|
+
: ''
|
|
174
|
+
lines.push(`- id=${h.id ?? '?'} ${name}${star}${price}`)
|
|
175
|
+
}
|
|
176
|
+
if (list.length > max) lines.push(`…(共 ${list.length} 家,仅列前 ${max})`)
|
|
177
|
+
return lines.join('\n')
|
|
178
|
+
}
|
|
179
|
+
|
|
145
180
|
/** 高层语义化封装:酒店列表查询(down-tier to 静态包 + 证据链标注) */
|
|
146
181
|
export async function searchHotels(
|
|
147
182
|
query: { destination: string; checkIn?: string; checkOut?: string; adults?: number },
|
|
148
183
|
opts: HbcliCallOptions & { fallbackPath?: string } = {},
|
|
149
184
|
): Promise<HbcliCallResult & { hotels?: unknown; summary: string }> {
|
|
150
|
-
// 旗标对齐上游 CLI
|
|
151
|
-
// hotel-list
|
|
185
|
+
// 旗标对齐上游 CLI(hotelbyte-com/hotelbyte-cli):--destination-name + --room-occupancies;
|
|
186
|
+
// hotel-list 支持日期旗标(--check-in/--check-out,上游帮助文档在册)——issue #195 教训:
|
|
187
|
+
// 「日期不传上游」会让实时价退化为当前窗口价,用户要下周五的房给的是今天的价。
|
|
152
188
|
const hbArgs = ['search', 'hotel-list', '--json', '--page-size', '10']
|
|
153
189
|
if (query.destination) hbArgs.push('--destination-name', query.destination)
|
|
190
|
+
if (query.checkIn) hbArgs.push('--check-in', query.checkIn)
|
|
191
|
+
if (query.checkOut) hbArgs.push('--check-out', query.checkOut)
|
|
154
192
|
if (query.adults) hbArgs.push('--room-occupancies', JSON.stringify([{ adultCount: query.adults, childrenAges: [] }]))
|
|
155
193
|
const live = await callHbcliJson(hbArgs, opts)
|
|
156
194
|
if (live.via === 'hbcli-realtime') {
|
|
157
|
-
|
|
195
|
+
// summary 即模型面(render 只透 summary):必须自带紧凑数据行 + 证据链,
|
|
196
|
+
// 否则模型只看到「实时返回」占位一句话(issue #195 实锤)。
|
|
197
|
+
const list = (live.result as { list?: unknown[] } | null)?.list
|
|
198
|
+
const n = Array.isArray(list) ? list.length : 0
|
|
199
|
+
const dateTag = query.checkIn
|
|
200
|
+
? `入住 ${query.checkIn}${query.checkOut ? ` → 退房 ${query.checkOut}` : ''}`
|
|
201
|
+
: '未传日期(上游按当前窗口价)'
|
|
202
|
+
const lines = hotelLines(live.result)
|
|
203
|
+
const summary = `${query.destination}:hbcli 实时 ${n} 家(${dateTag})${lines ? `\n${lines}` : ''}\n${live.evidence}`
|
|
204
|
+
return { ...live, hotels: live.result, summary }
|
|
158
205
|
}
|
|
159
206
|
// 降级原因人话化(issue #24):hbcli 未安装时按 gotry setup 指引(npm 安装期已
|
|
160
207
|
// 自动跑过官方脚本;PATH 未含 ~/.local/bin 时上方候选路径也已兜住),
|
|
@@ -176,7 +223,9 @@ export async function searchHotels(
|
|
|
176
223
|
return {
|
|
177
224
|
...live,
|
|
178
225
|
hotels: { stays: matched },
|
|
179
|
-
summary
|
|
226
|
+
// summary 即模型面:静态块内容(note/options/recommendation)直接内联,
|
|
227
|
+
// 模型不必再猜「命中 N 块」里有什么(issue #195)
|
|
228
|
+
summary: `${query.destination}:hbcli 实时源不可用(${reason}),已降级到静态包(公开渠道估算,非实时),命中 ${matched.length} 个住宿块:\n${JSON.stringify(matched).slice(0, 1600)}`,
|
|
180
229
|
}
|
|
181
230
|
}
|
|
182
231
|
return {
|
|
@@ -155,8 +155,15 @@ export function startExtensionHealthWatch(opts: HealthWatchOptions = {}): Health
|
|
|
155
155
|
const probe = opts.probe ?? (async () => {
|
|
156
156
|
// 先确保桥存在 + keepBridge
|
|
157
157
|
const bridge = await import('./extension-bridge.ts')
|
|
158
|
-
await bridge.getOrCreateSessionBridge({ keepBridge: opts.keepBridge === true })
|
|
159
|
-
|
|
158
|
+
const created = await bridge.getOrCreateSessionBridge({ keepBridge: opts.keepBridge === true, ports: opts.ports })
|
|
159
|
+
// The default path must keep scanning the canonical pool: another GoTry
|
|
160
|
+
// process may already own the extension-connected bridge. An explicit
|
|
161
|
+
// port override is an isolation boundary, so probe only the bridge that
|
|
162
|
+
// this process actually bound (including an ephemeral `0` request).
|
|
163
|
+
const probeOpts = created.ok && opts.ports
|
|
164
|
+
? { ...opts, ports: [created.bridge.port] }
|
|
165
|
+
: opts
|
|
166
|
+
return combinedProbe(probeOpts, businessProbe)
|
|
160
167
|
})
|
|
161
168
|
const now = opts.now ?? Date.now
|
|
162
169
|
const state: InternalState = { cancelled: false, readyCount: 0, resolveOutcome: null }
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# vendored dsh runtime(`vendor/`)
|
|
2
|
+
|
|
3
|
+
本目录是 [DeepSeek Harness(dsh CLI)](https://github.com/deepseek-ai/DeepSeek-Harness)
|
|
4
|
+
dsh 发布家族的全量 vendored 成员,pnpm workspace 方式参与安装
|
|
5
|
+
(`ts/dsh-runtime/pnpm-workspace.yaml` 声明 `vendor/*`,`linkWorkspacePackages: true`)。
|
|
6
|
+
|
|
7
|
+
## 当前版本
|
|
8
|
+
|
|
9
|
+
- 上游:`dsh-v0.1.2-alpha.1`(tag;alpha 面向 monorepo 内部,尚未发布到
|
|
10
|
+
npm 公共 registry——npm latest 仍为 `0.1.1-rc.2`,等上游 publish 后本目录
|
|
11
|
+
可整体换回 npm 依赖形态)。
|
|
12
|
+
- 源 commit:`cd5ef814`(/tmp 构建树为浅克隆同源;发布 tag 见上游 releases)。
|
|
13
|
+
- License:MIT © DeepSeek(upstream LICENSE);本仓 MIT 使用。
|
|
14
|
+
|
|
15
|
+
## 为什么 vendored(而不是 npm 依赖)
|
|
16
|
+
|
|
17
|
+
- 上游只挂 GitHub 预发布 tag;依赖闭包(dsh 家族 ~187 个内部包 + cordis/
|
|
18
|
+
schemastery 等 vendor 家族)里 **dsh 家族全部 0.1.2-alpha.1 均不在公共
|
|
19
|
+
npm**,外部环境不可安装。
|
|
20
|
+
- `ts/dsh-runtime` 只影响 repo 工作副本与 `./gotry`;npm 公共分发面
|
|
21
|
+
(root `package.json`)仍钉 rc.2,不受影响;CI 不安装本目录。
|
|
22
|
+
- workspace 成员 + `linkWorkspacePackages: true` 让成员间
|
|
23
|
+
`^0.1.2-alpha.1` 的 range 直接命中本地版本,干净克隆只依赖本仓文件 +
|
|
24
|
+
公共 registry(第三方),不依赖内部镜像。
|
|
25
|
+
|
|
26
|
+
## 非 dsh 家族成员:`vendor/dsh-map-tools`
|
|
27
|
+
|
|
28
|
+
`dsh-map-tools`(dshmarket 第三方宿主插件,地图/路线/POI,零 key 走
|
|
29
|
+
OSM/OSRM)与 dsh 家族不同源,进本目录的原因是 **npm 依赖形态被上游
|
|
30
|
+
peerDependencies 否决**:它要求 `dsh-settings`/`dsh-tools`
|
|
31
|
+
`>=0.1.2-rc.1`,而 gotry 锁定的运行时家族是 `0.1.2-alpha.3`
|
|
32
|
+
(semver 上 alpha < rc)——npm/npx 严格 peer 解析直接 ERESOLVE
|
|
33
|
+
(optionalDependencies 也不豁免),硬依赖会弄坏 `npx @danceiny/gotry`
|
|
34
|
+
主安装路径。故以 vendor 副本随 tarball 分发(root `package.json`
|
|
35
|
+
`files[]` 含本目录),`bin/gotry-inner.js` 解析链 vendor 优先;
|
|
36
|
+
升级 = 从 npm 拉 `dsh-map-tools@<new>` 覆盖本目录(保留来源与
|
|
37
|
+
license:MIT,上游 package.json 里无 devDependencies 需清理)。
|
|
38
|
+
|
|
39
|
+
## 升级/复现流程(dsh 家族,下个版本照抄)
|
|
40
|
+
|
|
41
|
+
1. `git clone --depth 1 --branch <tag> https://github.com/deepseek-ai/DeepSeek-Harness.git /tmp/dsh-<tag>`
|
|
42
|
+
2. `cd /tmp/dsh-<tag> && pnpm install && pnpm build:official`
|
|
43
|
+
3. `pnpm release:pack --family dsh --out dist/dsh`(产出 241 个 tarball)
|
|
44
|
+
4. 解包 tarball(`package/` 前缀剥掉)平铺进 `vendor/<kebab-name>/`
|
|
45
|
+
5. manifest 归一处:**删除各成员的 `devDependencies`**(上游 devDeps 引用
|
|
46
|
+
未进发布家族的 experimental 私有包,npm 安装形态本就不装 devDeps;
|
|
47
|
+
唯 `@deepseek-ai/dsh-subprocess-local` 的 `postinstall` 保
|
|
48
|
+
留——恢复 node-pty spawn-helper 可执行位,终局面依赖);
|
|
49
|
+
`scripts` 里 dev 期 `bundle/watch/build` 一并丢弃。
|
|
50
|
+
6. 更新 `package.json` 的 4 个直接依赖版本 + `pnpm install` 重新生成 lockfile
|
|
51
|
+
7. 验证:`./gotry help` 报版本;`cd ts && npx tsc --noEmit && npx tsx scripts/smoke.ts`;
|
|
52
|
+
全量 `bash scripts/run-all-tests.sh` 全绿后才可提交。
|
|
53
|
+
|
|
54
|
+
## 纪律
|
|
55
|
+
|
|
56
|
+
- 本目录内容必须与上游 tag 的 tarball 逐字节一致(`devDependencies`/
|
|
57
|
+
dev 期 `scripts` 除外);不得手改 vendor 内代码——补丁面一律走 gotry 侧
|
|
58
|
+
(`cordis.gotry-patch.yml` / `bin/gotry-inner.js`)。
|
|
59
|
+
- `gotry-state/`、`node_modules/` 已 gitignore;`vendor/` 与三份 manifest
|
|
60
|
+
(package.json / pnpm-lock.yaml / pnpm-workspace.yaml / .npmrc)入 git。
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 HorusJiang
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="assets/banner.svg" width="100%" alt="dsh-map-tools — Map & routing tools for DeepSeek Harness" />
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
# dsh-map-tools
|
|
6
|
+
|
|
7
|
+
<p align="center"><a href="README.md">中文</a> | English</p>
|
|
8
|
+
|
|
9
|
+
<p align="center">Map & routing tools plugin for <a href="https://github.com/deepseek-ai/deepseek-harness">DeepSeek Harness</a>: driving/transit/walking/bicycling route planning, geocoding, reverse geocoding and POI search as <strong>native tools</strong> — the model calls them directly, no MCP server required.</p>
|
|
10
|
+
|
|
11
|
+
<p align="center">
|
|
12
|
+
<a href="https://www.npmjs.com/package/dsh-map-tools"><img src="https://img.shields.io/npm/v/dsh-map-tools?style=flat-square&label=npm&color=cb3837" alt="npm"></a>
|
|
13
|
+
<a href="https://www.npmjs.com/package/dsh-map-tools"><img src="https://img.shields.io/npm/dw/dsh-map-tools?style=flat-square&label=downloads&color=cb3837" alt="npm downloads"></a>
|
|
14
|
+
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue?style=flat-square" alt="License"></a>
|
|
15
|
+
<a href="https://github.com/HorusJiang/dsh-map-tools/actions/workflows/ci.yml"><img src="https://github.com/HorusJiang/dsh-map-tools/actions/workflows/ci.yml/badge.svg?style=flat-square" alt="CI"></a>
|
|
16
|
+
<a href="https://github.com/HorusJiang/dsh-map-tools/releases"><img src="https://img.shields.io/github/v/release/HorusJiang/dsh-map-tools?style=flat-square&label=release" alt="Release"></a>
|
|
17
|
+
<a href="https://github.com/topics/dsh-plugin"><img src="https://img.shields.io/badge/dsh--plugin-installable-2A6BE8?style=flat-square" alt="dsh-plugin"></a>
|
|
18
|
+
<a href="https://github.com/awesome-dsh-plugin/awesome-dsh-plugin"><img src="https://img.shields.io/badge/dshmarket-listed-22C55E?style=flat-square&logo=shopify&logoColor=white" alt="dshmarket"></a>
|
|
19
|
+
<img src="https://img.shields.io/badge/node-%3E%3D20-339933?style=flat-square&logo=node.js&logoColor=white" alt="Node.js >= 20">
|
|
20
|
+
</p>
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Features
|
|
25
|
+
|
|
26
|
+
- **7 native tools**: route planning (driving/transit/walking/bicycling), geocoding, reverse geocoding and POI search — called directly by the model via `map_*`.
|
|
27
|
+
- **Amap (高德) data source (recommended)**: configure a free Amap Web Service key for the best China coverage (transit, POI, reliable Chinese geocoding).
|
|
28
|
+
- **Zero-key fallback**: without a key, driving/walking/bicycling routes use free OSM/OSRM; Chinese address parsing degrades with clear guidance.
|
|
29
|
+
- **Settings card out of the box**: Settings → Plugins → dsh-map-tools, graphical config with an "How to get an Amap key?" link; save applies instantly (no restart).
|
|
30
|
+
- **China-network friendly**: clear Chinese guidance for unreachable free sources or invalid keys.
|
|
31
|
+
|
|
32
|
+
## Install
|
|
33
|
+
|
|
34
|
+
Two ways, pick one:
|
|
35
|
+
|
|
36
|
+
### Option 1: npm install (recommended, prebuilt, no build approval)
|
|
37
|
+
|
|
38
|
+
```sh
|
|
39
|
+
dsh plugin --profile web add dsh-map-tools
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Option 2: from GitHub (source build, requires approval)
|
|
43
|
+
|
|
44
|
+
```sh
|
|
45
|
+
dsh plugin --profile web add github:HorusJiang/dsh-map-tools
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
> The published package ships **no** `prepare`/`postinstall` build scripts, so pnpm ≥10 installs it without prompting to allow build scripts or configuring `allowBuilds`.
|
|
49
|
+
|
|
50
|
+
> **Version requirement**: from 0.5.0 on, this plugin is compatible with **DeepSeek Harness 0.1.2-rc.1 and newer** (including a source-built 0.1.3-alpha.1). Earlier Harness release lines are no longer supported because `@deepseek-ai/dsh-settings` removed its legacy API.
|
|
51
|
+
|
|
52
|
+
Restart `dsh web` after install (or wait for HMR), then use the `map_*` tools in a session.
|
|
53
|
+
|
|
54
|
+
> **Development mode**: `dsh plugin add <local-path>` installs via `link:`, so source edits take effect immediately — ideal for iterating on the plugin.
|
|
55
|
+
|
|
56
|
+
## Quick start
|
|
57
|
+
|
|
58
|
+
Configure an Amap key (~2 minutes):
|
|
59
|
+
|
|
60
|
+
1. Open the [Amap console](https://console.amap.com/dev/key/app) → create an app → request a **"Web Service"** key (free for individuals).
|
|
61
|
+
2. In DSH **Settings → Plugins → dsh-map-tools**, paste the key, set data source `amap`, save.
|
|
62
|
+
3. Ask in a session:
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
Plan a driving route from Beijing South Station to Capital Airport T3
|
|
66
|
+
Geocode "西湖区文三路478号" to coordinates
|
|
67
|
+
Any gas stations within 1km of 116.397428,39.90923?
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Tools
|
|
71
|
+
|
|
72
|
+
| Tool | What it does | Free OSM | Amap |
|
|
73
|
+
|---|---|---|---|
|
|
74
|
+
| `map_driving_route` | Driving route planning | ✅ | ✅ |
|
|
75
|
+
| `map_transit_route` | Transit planning | — | ✅ |
|
|
76
|
+
| `map_walking_route` | Walking route planning | ✅ | ✅ |
|
|
77
|
+
| `map_bicycling_route` | Bicycling route planning | ✅ | ✅ |
|
|
78
|
+
| `map_geocode` | Address → coordinates | CJK unreliable | ✅ |
|
|
79
|
+
| `map_reverse_geocode` | Coordinates → address | CJK unreliable | ✅ |
|
|
80
|
+
| `map_poi_search` | POI search | — | ✅ |
|
|
81
|
+
|
|
82
|
+
Origin/destination accept either **address text** or **`"lng,lat"` coordinates** — the plugin normalizes automatically.
|
|
83
|
+
|
|
84
|
+
## Configuration
|
|
85
|
+
|
|
86
|
+
### Settings card (recommended)
|
|
87
|
+
|
|
88
|
+
DSH **Settings → Plugins → dsh-map-tools** provides a graphical card: data-source selector, masked Amap key input, timeout, and apply link. Saving takes effect immediately.
|
|
89
|
+
|
|
90
|
+
Config lives in **`~/.dsh-map-tools/config.json`** (0600), decoupled from the DSH settings document and shared across profiles:
|
|
91
|
+
|
|
92
|
+
```jsonc
|
|
93
|
+
// ~/.dsh-map-tools/config.json
|
|
94
|
+
{
|
|
95
|
+
"provider": "amap", // "amap" | "osm"
|
|
96
|
+
"amapKey": "your-amap-key",
|
|
97
|
+
"timeoutMs": 15000
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
> Keys are only surfaced to the frontend as a boolean (`hasAmapKey`); the literal is **never echoed to the page or logs**.
|
|
102
|
+
|
|
103
|
+
### cordis.yml defaults
|
|
104
|
+
|
|
105
|
+
Defaults can be provided in the profile's `cordis.yml` (**config-file values win over cordis.yml**):
|
|
106
|
+
|
|
107
|
+
```yaml
|
|
108
|
+
- id: map-tools
|
|
109
|
+
name: dsh-map-tools
|
|
110
|
+
config:
|
|
111
|
+
provider: amap
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Architecture
|
|
115
|
+
|
|
116
|
+
```
|
|
117
|
+
┌─ Model ─────────────────────────────────────┐
|
|
118
|
+
│ map_driving_route / map_geocode / ... │ 7 native tools (ctx.tools)
|
|
119
|
+
└──────────────┬──────────────────────────────┘
|
|
120
|
+
│
|
|
121
|
+
┌──────────────▼──────────────────────────────┐
|
|
122
|
+
│ src/tools/ tool definitions (validate+render) │
|
|
123
|
+
│ src/clients/ provider clients │
|
|
124
|
+
│ amap.ts Amap Web Service API (recommended) │
|
|
125
|
+
│ osrm.ts OSRM free routing (fallback) │
|
|
126
|
+
│ photon.ts Photon free geocoding (fallback) │
|
|
127
|
+
│ nominatim.ts Nominatim free geocoding (fallback)│
|
|
128
|
+
└──────────────┬──────────────────────────────┘
|
|
129
|
+
│
|
|
130
|
+
┌──────────────▼──────────────────────────────┐
|
|
131
|
+
│ src/config-file.ts ~/.dsh-map-tools/config.json (0600) │
|
|
132
|
+
│ src/config-route.ts loopback route /dsh-map-tools/config │
|
|
133
|
+
│ src/settings-ns.ts settings namespace registration │
|
|
134
|
+
│ client/client.js settings card (hand-written, zero-dep)│
|
|
135
|
+
└──────────────────────────────────────────────┘
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
- **Config priority**: config file (settings card) → `cordis.yml` defaults.
|
|
139
|
+
- **Instant apply**: tools rebuild on config change; no restart.
|
|
140
|
+
- **No MCP**: all capabilities are native tools; no external MCP server process.
|
|
141
|
+
|
|
142
|
+
## Data sources
|
|
143
|
+
|
|
144
|
+
| Source | Use | Key | Notes |
|
|
145
|
+
|---|---|---|---|
|
|
146
|
+
| Amap (高德) | All tools (recommended) | Free | Best China coverage: transit, POI, Chinese geocoding |
|
|
147
|
+
| OSRM | driving/walking/bicycling routes | none | Free public server, rate-limited |
|
|
148
|
+
| Photon / Nominatim | geocoding | none | Free public; **CJK unreliable**, unreachable on some networks |
|
|
149
|
+
|
|
150
|
+
> Free-source limitations (unstable CJK geocoding) are deliberate: without a key you get clear guidance; with an Amap key the experience upgrades seamlessly.
|
|
151
|
+
|
|
152
|
+
## FAQ
|
|
153
|
+
|
|
154
|
+
**Q: I configured an Amap key but routes still use OSM?**
|
|
155
|
+
A: Check the config file's `provider` is `amap` (not `osm`) and `amapKey` is non-empty.
|
|
156
|
+
|
|
157
|
+
**Q: Why do transit/POI require a key?**
|
|
158
|
+
A: Free OSM sources don't provide transit or POI data; those need the Amap key.
|
|
159
|
+
|
|
160
|
+
**Q: My Amap key is rejected?**
|
|
161
|
+
A: Make sure it's a **"Web Service"** key (not JS API / Web key), and the services are enabled in the Amap console.
|
|
162
|
+
|
|
163
|
+
**Q: Chinese geocoding reports "free source unavailable"?**
|
|
164
|
+
A: Free sources (Photon/Nominatim) handle Chinese poorly and may be unreachable on some CN networks. This is by design — configure an Amap key and it resolves.
|
|
165
|
+
|
|
166
|
+
## Development
|
|
167
|
+
|
|
168
|
+
```sh
|
|
169
|
+
pnpm install
|
|
170
|
+
pnpm run build # tsc → lib/
|
|
171
|
+
pnpm test # vitest unit tests (mocked network)
|
|
172
|
+
node scripts/smoke.mjs # smoke: 7 tools register
|
|
173
|
+
node scripts/integration.mjs # integration: real requests (free sources)
|
|
174
|
+
node scripts/amap-e2e.mjs # Amap e2e: set AMAP_API_KEY
|
|
175
|
+
node scripts/config-e2e.mjs # config file round-trip
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Conventions: see [CONTRIBUTING.md](CONTRIBUTING.md) and [AGENTS.md](AGENTS.md).
|
|
179
|
+
|
|
180
|
+
## Publishing
|
|
181
|
+
|
|
182
|
+
```sh
|
|
183
|
+
npm config set registry https://registry.npmjs.org/
|
|
184
|
+
npm login # npm account (a bypass-2FA publish token is recommended)
|
|
185
|
+
node scripts/publish.mjs # one-shot: build → pack check → publish → verify
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Versioning follows [SemVer](https://semver.org/); changes are tracked in [CHANGELOG.md](CHANGELOG.md).
|
|
189
|
+
|
|
190
|
+
## Security
|
|
191
|
+
|
|
192
|
+
Key storage and vulnerability reporting: see [SECURITY.md](SECURITY.md).
|
|
193
|
+
|
|
194
|
+
## Contributing
|
|
195
|
+
|
|
196
|
+
Issues and PRs welcome! See [CONTRIBUTING.md](CONTRIBUTING.md) for conventions.
|
|
197
|
+
|
|
198
|
+
## License
|
|
199
|
+
|
|
200
|
+
[MIT](LICENSE) © HorusJiang
|