@danceiny/gotry 0.0.1-rc.19 → 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.
Files changed (60) hide show
  1. package/README.md +1 -1
  2. package/README.zh-CN.md +1 -1
  3. package/bin/gotry-bootstrap.js +83 -37
  4. package/bin/gotry-inner.js +11 -6
  5. package/cordis.gotry-patch.yml +7 -3
  6. package/dist/capabilities/anything.js +62 -29
  7. package/dist/capabilities/doctor.js +25 -3
  8. package/dist/capabilities/hbcli.js +28 -2
  9. package/dist/scripts/agent-planning-turn-deadline-e2e.js +2 -1
  10. package/dist/scripts/anything-tests.js +70 -21
  11. package/dist/scripts/benchmark-environment-bridge-e2e.js +136 -29
  12. package/dist/scripts/benchmark-environment-bridge-tests.js +1056 -269
  13. package/dist/scripts/bootstrap-tests.js +28 -2
  14. package/dist/scripts/doctor-tests.js +55 -1
  15. package/dist/scripts/hbcli-tests.js +38 -1
  16. package/dist/src/benchmark-agent-conformance.js +32 -12
  17. package/dist/src/benchmark-environment-bridge.js +317 -114
  18. package/dist/src/index.js +5 -2
  19. package/extension/manifest.json +2 -2
  20. package/package.json +2 -1
  21. package/ts/capabilities/anything.ts +89 -38
  22. package/ts/capabilities/hbcli.ts +53 -4
  23. package/ts/dsh-runtime/vendor/README.md +60 -0
  24. package/ts/dsh-runtime/vendor/dsh-map-tools/LICENSE +21 -0
  25. package/ts/dsh-runtime/vendor/dsh-map-tools/README.en.md +200 -0
  26. package/ts/dsh-runtime/vendor/dsh-map-tools/README.md +202 -0
  27. package/ts/dsh-runtime/vendor/dsh-map-tools/client/client.js +216 -0
  28. package/ts/dsh-runtime/vendor/dsh-map-tools/cordis.patch.yml +5 -0
  29. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/amap.js +371 -0
  30. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/nominatim.js +63 -0
  31. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/osrm.js +56 -0
  32. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/photon.js +55 -0
  33. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/config-file.js +87 -0
  34. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/config-route.js +126 -0
  35. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/config.js +16 -0
  36. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/index.js +109 -0
  37. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/settings-ns.js +27 -0
  38. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/tools/geocode.js +127 -0
  39. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/tools/poi.js +84 -0
  40. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/tools/routes.js +170 -0
  41. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/amap.d.ts +53 -0
  42. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/nominatim.d.ts +17 -0
  43. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/osrm.d.ts +15 -0
  44. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/photon.d.ts +22 -0
  45. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/config-file.d.ts +33 -0
  46. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/config-route.d.ts +20 -0
  47. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/config.d.ts +23 -0
  48. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/index.d.ts +18 -0
  49. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/settings-ns.d.ts +14 -0
  50. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/tools/geocode.d.ts +11 -0
  51. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/tools/poi.d.ts +9 -0
  52. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/tools/routes.d.ts +16 -0
  53. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/types.d.ts +55 -0
  54. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types.js +16 -0
  55. package/ts/dsh-runtime/vendor/dsh-map-tools/package.json +91 -0
  56. package/ts/package.json +9 -1
  57. package/ts/src/benchmark-agent-conformance.ts +40 -9
  58. package/ts/src/benchmark-environment-bridge.ts +235 -66
  59. package/ts/src/index.ts +4 -2
  60. package/ts/src/turn-deadline.ts +4 -0
package/README.md CHANGED
@@ -205,7 +205,7 @@ Current release: **v0.0.1-rc.18** (npm `latest` and `rc` both point here; regist
205
205
  - **M3 Exit not closed** — engineering & distribution are ready, but real seed-user evidence (50–200 person cohort) has not been accumulated; automated tests prove contracts and formulas, not business pass
206
206
  - **Hotel session adapters** — Ctrip-hotel / Meituan logged-in surfaces await real login-state backfill; flights are done
207
207
  - **Interface language** — English covers the deterministic solve-output layer; the dsh host UI and dialogue surface belong to the host / calibration samples
208
- - **External benchmark generalization** — every frozen external run to date remains diagnostic-only (no score, no uplift claim); the round-by-round engineering ledger lives in [`docs/evaluation/benchmark-environment-bridge.md`](docs/evaluation/benchmark-environment-bridge.md)
208
+ - **External benchmark generalization** — every frozen external run to date remains diagnostic-only (no score, no uplift claim). The bridge now combines the generic `tools/call/errors` recovery surface with per-tool typed descriptors, exact result/domain/failure conformance, and a latest-response terminal fence; the round-by-round engineering ledger lives in [`docs/evaluation/benchmark-environment-bridge.md`](docs/evaluation/benchmark-environment-bridge.md)
209
209
  - **Booking** — nothing bookable ships today; M5 opens only through WriteGate and the booking-saga FSM
210
210
 
211
211
  <details>
package/README.zh-CN.md CHANGED
@@ -201,7 +201,7 @@ node scripts/build-dist.mjs # 构建 JS runtime
201
201
  - **M3 Exit 未关闭** —— 工程与分发面就绪,但真实种子用户证据(50–200 人 cohort)尚未积累;自动化测试证明的是合同与公式,不是 business pass
202
202
  - **酒店会话适配** —— 携程酒店/美团登录态面等实测回填;机票已通
203
203
  - **界面语言** —— 英文仅覆盖求解确定性输出层;dsh 宿主界面与对话面属宿主/校准件
204
- - **外部 benchmark 泛化** —— 迄今所有冻结外部运行均仅 diagnostic(无分数、无 uplift 声明);逐轮工程台账见 [`docs/evaluation/benchmark-environment-bridge.md`](docs/evaluation/benchmark-environment-bridge.md)
204
+ - **外部 benchmark 泛化** —— 迄今所有冻结外部运行均仅 diagnostic(无分数、无 uplift 声明)。bridge 现已合并通用 `tools/call/errors` 恢复面、逐工具 typed descriptor、严格 result/domain/failure 分流与 latest-response terminal fence;逐轮工程台账见 [`docs/evaluation/benchmark-environment-bridge.md`](docs/evaluation/benchmark-environment-bridge.md)
205
205
  - **预订** —— 今天没有任何可下单路径;M5 只经 WriteGate 与 booking-saga 状态机启封
206
206
 
207
207
  <details>
@@ -58,6 +58,7 @@ import { copyFileSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSyn
58
58
  import { homedir } from 'node:os'
59
59
  import { join, dirname } from 'node:path'
60
60
  import { fileURLToPath } from 'node:url'
61
+ import { createRequire } from 'node:module'
61
62
 
62
63
  const repoRoot = join(dirname(fileURLToPath(import.meta.url)), '..')
63
64
  const AUTO = process.argv.includes('--auto')
@@ -73,9 +74,9 @@ const HBCLI_INSTALL_CMD = 'curl -fsSL https://github.com/hotelbyte-com/docs/rele
73
74
  const REACH_INSTALL_URL = 'git+https://github.com/Panniantong/Agent-Reach.git'
74
75
 
75
76
  /** 带超时的子进程(inherit stdio 让用户看见上游安装进度) */
76
- function run(cmd, args, { timeoutMs, cwd } = {}) {
77
+ function run(cmd, args, { timeoutMs, cwd, env } = {}) {
77
78
  return new Promise((resolve) => {
78
- const child = spawn(cmd, args, { stdio: 'inherit', cwd, env: process.env })
79
+ const child = spawn(cmd, args, { stdio: 'inherit', cwd, env: env ?? process.env })
79
80
  let done = false
80
81
  const timer = setTimeout(() => {
81
82
  if (!done) { done = true; try { child.kill('SIGKILL') } catch { /* ignore */ } }
@@ -143,34 +144,56 @@ async function setupReach() {
143
144
  return { ok: true }
144
145
  }
145
146
 
146
- async function setupSidebar() { say('[gotry-setup] dsh-better-sidebar(dsh web 侧栏工作台,产物查看面 issue #25)')
147
- const installed = existsSync(join(homedir(), '.dsh/profiles/web/node_modules/dsh-better-sidebar/package.json'))
148
- if (installed) { say(' ✓ 已安装(~/.dsh/profiles/web)'); return { ok: true } }
147
+ /** sidebar 落盘状态 = doctor 检查同口径的唯一事实面(profile 里的 package.json) */
148
+ function sidebarInstalled() {
149
+ return existsSync(join(homedir(), '.dsh/profiles/web/node_modules/dsh-better-sidebar/package.json'))
150
+ }
151
+
152
+ /** dsh plugin 在 profile 目录里跑 pnpm;pnpm ≥10.5 默认不执行依赖构建脚本,严格态
153
+ * (pnpm 11 默认 / strict-dep-builds)下 ERR_PNPM_IGNORED_BUILDS 直接 exit 1——
154
+ * 但包本身已完整落盘(2026-09-08 实测:exit 1 而 167 包全部就位,doctor 复检即绿)。
155
+ * 对该调用降回警告态(env 只作用于这一次 spawn,不改用户全局 pnpm 配置);
156
+ * 老版本 pnpm 不认识此配置,零影响。 */
157
+ const SIDEBAR_PNPM_ENV = { ...process.env, npm_config_strict_dep_builds: 'false' }
158
+
159
+ async function setupSidebar(attemptInstall) { say('[gotry-setup] dsh-better-sidebar(dsh web 侧栏工作台,产物查看面 issue #25)')
160
+ if (sidebarInstalled()) { say(' ✓ 已安装(~/.dsh/profiles/web)'); return { ok: true } }
149
161
  if (CHECK_ONLY) { say(' ✗ 未安装(--check-only 只报告)'); return { ok: true } }
150
162
  const SIDEBAR_PKG = 'dsh-better-sidebar@latest'
151
- // dsh CLI:本包依赖里的 @deepseek-ai/dsh 优先;解析不到走 npx 自拉(官方安装途径同款)
152
- const { createRequire } = await import('node:module')
163
+ // dsh CLI:本包依赖里的 @deepseek-ai/dsh 优先;解析不到走 npx 自拉(官方安装途径同款)
164
+ // attemptInstall 注入位供测试模拟「安装器失败但状态落盘」(pnpm 忽略构建脚本策略)
153
165
  const require_ = createRequire(join(repoRoot, 'package.json'))
154
- let launched = false
155
- try {
156
- const dshBin = require_.resolve('@deepseek-ai/dsh/lib/bin.js')
157
- say(` 安装中(dsh plugin → web profile): ${SIDEBAR_PKG}`)
158
- const r = await run(process.execPath, [dshBin, 'plugin', '--profile', 'web', 'add', SIDEBAR_PKG], { timeoutMs: 300_000 })
159
- launched = true
160
- if (r.ok) {
161
- say(' ✓ 安装完成(gotry web 刷新浏览器即见右侧工作台;工作区里的产物 md 可直接预览)')
162
- return { ok: true }
163
- }
164
- say(` ✗ dsh plugin 安装失败(${r.error})——尝试 npx 途径`)
165
- } catch { /* 本包未携带 @deepseek-ai/dsh */ }
166
- if (!launched) say(' 本包未携带 @deepseek-ai/dsh,走 npx 途径安装')
167
- const r2 = await run('npx', ['-y', '--package', '@deepseek-ai/dsh', 'dsh', 'plugin', '--profile', 'web', 'add', SIDEBAR_PKG], { timeoutMs: 300_000 })
168
- if (!r2.ok) {
169
- say(' ✗ 安装失败——不影响 gotry:产物仍可在对话里说「看看我生成的行程」经 gotry_artifacts_list/read 查看;可稍后重试: npx gotry doctor --fix')
170
- return { ok: false }
166
+ const defaultAttempt = async () => {
167
+ try {
168
+ const dshBin = require_.resolve('@deepseek-ai/dsh/lib/bin.js')
169
+ say(` 安装中(dsh plugin → web profile): ${SIDEBAR_PKG}`)
170
+ return await run(process.execPath, [dshBin, 'plugin', '--profile', 'web', 'add', SIDEBAR_PKG], { timeoutMs: 300_000, env: SIDEBAR_PNPM_ENV })
171
+ } catch { return { ok: false, noDsh: true } }
171
172
  }
172
- say(' ✓ 安装完成(gotry web 刷新浏览器即见右侧工作台)')
173
- return { ok: true }
173
+ const r = await (attemptInstall ?? defaultAttempt)()
174
+ if (r.ok) {
175
+ say(' ✓ 安装完成(gotry web 刷新浏览器即见右侧工作台;工作区里的产物 md 可直接预览)')
176
+ return { ok: true }
177
+ }
178
+ // exit 非 0 ≠ 没装上(pnpm 忽略构建脚本策略):先按落盘状态复核,再决定是否回退 npx 途径
179
+ if (sidebarInstalled()) {
180
+ say(' ✓ 安装完成(安装器 exit 非 0,但按落盘状态复核已就位;gotry web 刷新浏览器即见右侧工作台)')
181
+ say(' ⚠ pnpm 跳过了 node-pty(侧栏内嵌终端)的构建脚本——预览功能不受影响;如需内嵌终端:cd ~/.dsh/profiles/web && pnpm approve-builds 勾选 node-pty')
182
+ return { ok: true }
183
+ }
184
+ if (r.noDsh) say(' 本包未携带 @deepseek-ai/dsh,走 npx 途径安装')
185
+ else say(' ✗ dsh plugin 安装失败且未落盘——尝试 npx 途径')
186
+ const r2 = await run('npx', ['-y', '--package', '@deepseek-ai/dsh', 'dsh', 'plugin', '--profile', 'web', 'add', SIDEBAR_PKG], { timeoutMs: 300_000, env: SIDEBAR_PNPM_ENV })
187
+ if (r2.ok) {
188
+ say(' ✓ 安装完成(gotry web 刷新浏览器即见右侧工作台)')
189
+ return { ok: true }
190
+ }
191
+ if (sidebarInstalled()) {
192
+ say(' ✓ 安装完成(npx 途径 exit 非 0,但按落盘状态复核已就位)')
193
+ return { ok: true }
194
+ }
195
+ say(' ✗ 安装失败——不影响 gotry:产物仍可在对话里说「看看我生成的行程」经 gotry_artifacts_list/read 查看;可稍后重试: npx gotry doctor --fix')
196
+ return { ok: false }
174
197
  }
175
198
 
176
199
  // ---------------------------------------------------------------------------
@@ -269,9 +292,8 @@ async function doctorChecks() {
269
292
  // flyai key(匿名试用额度共享易达限;正式 key 即免)
270
293
  const flyaiKey = (process.env.FLYAI_API_KEY ?? '').trim()
271
294
  items.push({ label: 'FlyAI(飞猪官方检索)', ok: Boolean(flyaiKey), level: flyaiKey ? 'ok' : 'degraded', detail: flyaiKey ? 'FLYAI_API_KEY 已配(正式 key,无试用额度限制)' : '未配 FLYAI_API_KEY——走匿名试用额度(共享,易达限;达限报 "Trial limit reached")', fix: flyaiKey ? undefined : '到 flyai.open.fliggy.com 控制台申请正式 key,配进环境变量 FLYAI_API_KEY' })
272
- // sidebar
273
- const sidebarPkg = join(homedir(), '.dsh/profiles/web/node_modules/dsh-better-sidebar/package.json')
274
- const sbOk = existsSync(sidebarPkg)
295
+ // sidebar(状态面与 setupSidebar 的落盘复核同一口径)
296
+ const sbOk = sidebarInstalled()
275
297
  items.push({ label: 'dsh-better-sidebar(侧栏工作台)', ok: sbOk, level: sbOk ? 'ok' : 'missing', detail: sbOk ? '已安装——web UI 右侧工作台可预览产物与 doctor 报告(gotry-state/doctor-report.md)' : '未安装——dsh web 无右侧工作台,产物与 doctor 报告只能在对话里看(gotry_artifacts_list)', fix: sbOk ? undefined : 'npx gotry doctor --fix' })
276
298
  // dsh-calendar(setup 状态面;默认不挂载=ok 是合法态,opt-in 未配置才 degraded)
277
299
  const calState = readCalendarState()
@@ -279,24 +301,42 @@ async function doctorChecks() {
279
301
  const calConfigured = calOn && calendarProfileConfigured()
280
302
  items.push({ label: 'dsh-calendar(日历工作窗口)', ok: !calOn || calConfigured, level: !calOn ? 'ok' : calConfigured ? 'ok' : 'degraded', detail: calendarDetail(calState), fix: !calOn ? undefined : calConfigured ? undefined : '在 ~/.dsh/profiles/web/cordis.patch.yml 覆盖 calendar 行 config 填 username(或 npx gotry setup calendar --off 恢复默认不挂载)' })
281
303
  // dsh-map-tools(patch 分发面宿主插件,issue #139):解析失败启动时整块静默剔除,
282
- // doctor 把两态照亮。候选清单与 bin/gotry-inner.js 解析链、ts/capabilities/doctor.ts 同口径。
304
+ // doctor 把两态照亮。候选清单与 bin/gotry-inner.js 解析链、ts/capabilities/doctor.ts 同口径:
305
+ // tarball vendor 副本优先(随 files[] 分发,不进 npm 依赖——其 peerDependencies 要求
306
+ // dsh-settings/dsh-tools >=0.1.2-rc.1,与锁定的 0.1.2-alpha.3 家族在 npm 严格 peer
307
+ // 解析下 ERESOLVE,依赖形态会弄坏 npx 安装),其余覆盖 source/提升/profile 布局。
308
+ const rootRequire = createRequire(join(repoRoot, 'package.json'))
283
309
  const mapCandidates = [
310
+ join(repoRoot, 'ts/dsh-runtime/vendor/dsh-map-tools/lib/index.js'),
284
311
  join(repoRoot, 'ts/dsh-runtime/node_modules/dsh-map-tools/lib/index.js'),
285
312
  join(repoRoot, 'node_modules/dsh-map-tools/package.json'),
286
313
  join(repoRoot, 'ts/node_modules/dsh-map-tools/package.json'),
287
314
  join(homedir(), '.dsh/profiles/web/node_modules/dsh-map-tools/package.json'),
288
315
  ]
289
- const mapHit = mapCandidates.find((p) => existsSync(p))
316
+ let mapHit = mapCandidates.find((p) => existsSync(p))
317
+ // npm 提升布局:gotry 的依赖不在包目录 node_modules 里,而在同级提升位——
318
+ // 与 inner 的 require_.resolve 同机制(createRequire 从包位置向上走查)
319
+ if (!mapHit) { try { mapHit = rootRequire.resolve('dsh-map-tools') } catch { mapHit = undefined } }
290
320
  items.push(mapHit
291
321
  ? { label: 'dsh-map-tools(地图/路线/POI)', ok: true, level: 'ok', detail: `已就位(${mapHit})——地图工具可用(零 key,走 OSM/OSRM)`, fix: undefined }
292
- : { label: 'dsh-map-tools(地图/路线/POI)', ok: false, level: 'missing', detail: '未随包解析——启动时该 patch 条目被静默剔除,地图/路线/POI 工具不会出现在模型工具箱(缺地图不挡旅行规划,只少能力)', fix: 'npx gotry doctor --fix 不覆盖此项:source 布局把 dsh-map-tools 放进 ts/dsh-runtime/node_modules(或 ts/node_modules);npm 布局重装 @danceiny/gotry(随发行版依赖提供)' })
322
+ : { label: 'dsh-map-tools(地图/路线/POI)', ok: false, level: 'missing', detail: '未随包解析——启动时该 patch 条目被静默剔除,地图/路线/POI 工具不会出现在模型工具箱(缺地图不挡旅行规划,只少能力)', fix: '重装 @danceiny/gotry(地图插件随包 vendor 分发,缺失多为安装不完整)' })
293
323
  // dsh-tool-ask-user(结构化澄清卡,人格契约 (5) 的卡片形态载体)
294
324
  const askCandidates = [
295
325
  join(repoRoot, 'ts/dsh-runtime/vendor/deepseek-ai-dsh-tool-ask-user/package.json'),
296
326
  join(repoRoot, 'node_modules/@deepseek-ai/dsh-tool-ask-user/package.json'),
297
327
  join(homedir(), '.dsh/profiles/web/node_modules/@deepseek-ai/dsh-tool-ask-user/package.json'),
298
328
  ]
299
- const askHit = askCandidates.find((p) => existsSync(p))
329
+ let askHit = askCandidates.find((p) => existsSync(p))
330
+ // 与 inner 同口径:优先从 dsh 包上下文解析(pnpm 嵌套布局只有 dsh 看得见),再从包根上下文
331
+ // (npm/npx 提升布局),最后静态候选(source vendored / profile)
332
+ if (!askHit) {
333
+ try {
334
+ const reqFromDsh = createRequire(rootRequire.resolve('@deepseek-ai/dsh/lib/bin.js'))
335
+ askHit = reqFromDsh.resolve('@deepseek-ai/dsh-tool-ask-user')
336
+ } catch {
337
+ try { askHit = rootRequire.resolve('@deepseek-ai/dsh-tool-ask-user') } catch { askHit = undefined }
338
+ }
339
+ }
300
340
  items.push(askHit
301
341
  ? { label: 'dsh-tool-ask-user(结构化澄清卡)', ok: true, level: 'ok', detail: `已就位(${askHit})——ask_user_question 澄清卡可用(web 原生卡片;headless+TTY 用 stdio 提供方)`, fix: undefined }
302
342
  : { label: 'dsh-tool-ask-user(结构化澄清卡)', ok: false, level: 'missing', detail: '未解析——启动时澄清卡注入被静默剔除,模型只能散文追问(人格契约 (5) 退化文本形态)', fix: '重装 @danceiny/gotry——该依赖随 dsh 闭包自带,缺失多为安装不完整' })
@@ -728,7 +768,13 @@ if (WIZARD) {
728
768
  process.exit(0)
729
769
  }
730
770
 
731
- main().catch((e) => {
732
- say(`[gotry-setup] 异常:${e.message}(不影响 gotry 本体;可重试 npx gotry setup)`)
733
- process.exit(AUTO ? 0 : 1)
734
- })
771
+ // 直接执行才跑 main(bootstrap-tests import 本模块做 setupSidebar 单测时不触发安装面;
772
+ // bin/gotry-inner.js 始终以 `node …/gotry-bootstrap.js` 直接 spawn,不受影响)
773
+ if (process.argv[1] && process.argv[1].endsWith('gotry-bootstrap.js')) {
774
+ main().catch((e) => {
775
+ say(`[gotry-setup] 异常:${e.message}(不影响 gotry 本体;可重试 npx gotry setup)`)
776
+ process.exit(AUTO ? 0 : 1)
777
+ })
778
+ }
779
+
780
+ export { setupSidebar, sidebarInstalled }
@@ -82,10 +82,7 @@ if (help) {
82
82
  Usage:
83
83
  gotry web # dsh Web UI on http://127.0.0.1:3080
84
84
  gotry setup # 扩展就位检查/指引(商店一键装)
85
- <<<<<<< HEAD
86
85
  gotry setup calendar # 可选日历(CalDAV 工作窗口)挂载开关:默认关;--off 关闭;--status 查看
87
- =======
88
- >>>>>>> origin/main
89
86
  gotry doctor # 可选依赖体检:扩展/agent-reach/hbcli/flyai/sidebar 状态 + 补装指引
90
87
  gotry doctor --fix # 体检 + 按报告补装(hbcli 官方脚本 / agent-reach pip / sidebar 插件)
91
88
  gotry "一段完整任务..." # headless 一问一答
@@ -368,13 +365,21 @@ if (benchmarkEnvironmentConfig) {
368
365
  process.exit(1)
369
366
  }
370
367
  }
371
- // dsh-map-tools 宿主插件(地图/路线/POI,零 key 走 OSM/OSRM):repo vendored,
372
- // npm 用依赖解析;都找不到就整块剔除 patch 条目(缺地图不挡旅行规划)
368
+ // dsh-map-tools 宿主插件(地图/路线/POI,零 key 走 OSM/OSRM):tarball 随包 vendor,
369
+ // repo 工作副本走 runtime workspace 链接/依赖解析;都找不到就整块剔除 patch 条目
370
+ // (缺地图不挡旅行规划)。不能改成 npm 依赖:其 peerDependencies 要求
371
+ // dsh-settings/dsh-tools >=0.1.2-rc.1,与本包锁定的 0.1.2-alpha.3 家族在 npm
372
+ // 严格 peer 解析下 ERESOLVE,会直接弄坏 npx 安装。
373
373
  let mapEntry = ''
374
374
  if (!benchmarkEnvironmentConfig) {
375
- const vendoredMap = join(repoRoot, 'ts/dsh-runtime/node_modules/dsh-map-tools/lib/index.js')
375
+ const vendoredMap = join(repoRoot, 'ts/dsh-runtime/vendor/dsh-map-tools/lib/index.js')
376
+ // (随 tarball 分发;source 布局同一份文件)
377
+ const runtimeMap = join(repoRoot, 'ts/dsh-runtime/node_modules/dsh-map-tools/lib/index.js')
378
+ // (ts/dsh-runtime pnpm workspace vendor/* 链接形态)
376
379
  if (existsSync(vendoredMap)) {
377
380
  mapEntry = vendoredMap
381
+ } else if (existsSync(runtimeMap)) {
382
+ mapEntry = runtimeMap
378
383
  } else {
379
384
  // npm 布局:子路径可能被 exports 挡(resolve 抛错不能留下旧值),裸包名返回真实入口
380
385
  try { mapEntry = require_.resolve('dsh-map-tools/lib/index.js') } catch { mapEntry = '' }
@@ -109,9 +109,13 @@
109
109
  **交付任何含可下单事实的行程产物(markdown 文件或长方案)前,必须调
110
110
  gotry_fact_gate**;闸 blocked 时逐条修正为可回溯事实或降级措辞,
111
111
  不得宣称「已验证方案」。
112
- (21)工具循环收敛:第 16 次工具调用收到 TOOL_BUDGET_SOFT 提示后立即整理并给出最终答复;
113
- 每个对话轮次最多允许 18 次真实工具派发,第 19 次起收到结构化 TOOL_BUDGET_EXHAUSTED
114
- 拒绝结果且不得再调用工具。达到闸值后只能基于已有结果输出最终答复,不得重试或改调其他工具。
112
+ (21)工具循环收敛(wall-clock,ADR-24 v2):工具按需取用,没有「次数预算」,
113
+ 也**永远不要**向用户宣称「工具预算已耗尽」——每轮唯一的边界是运行时时间闸,
114
+ 只认三种信号:收到 TURN_DEADLINE_SOFT = 时间过半,立即停止发起新工具,开始
115
+ 整理已有结果;收到 TURN_DEADLINE_EXHAUSTED = 本轮时间到,只能基于已有结果
116
+ 输出最终答复,不得重试或改调其他工具,更不得用记忆/常识填充工具没查到的
117
+ 实时事实(如实说明哪部分未能核实);收到 TURN_DEADLINE_HANDOFF = 深规划
118
+ 转入后台,按提示告知用户预计时间与回访方式(附工单号),本轮给简短确认。
115
119
  (22)到达账必达:红眼/凌晨起飞、或落地当天有硬安排(上班/办事)的航段,解释方案时必须
116
120
  显式给出到达账——当地到达时刻(含日期偏移)、时差、到达精力与落地后可用时长,并主动给
117
121
  前一晚落脚建议;时刻表正确不等于行程可行。
@@ -1,4 +1,24 @@
1
1
  import { spawn } from 'node:child_process';
2
+ import { hbcliBinCandidates } from './hbcli.js';
3
+ function i18nText(v) {
4
+ if (!v) return '';
5
+ if (typeof v === 'string') return v;
6
+ return v.zh || v.en || v.ar || '';
7
+ }
8
+ function latlngOf(coord) {
9
+ for (const src of [
10
+ coord?.google,
11
+ coord?.gaode
12
+ ]){
13
+ const lat = typeof src?.lat === 'number' ? src.lat : undefined;
14
+ const lng = typeof src?.lng === 'number' ? src.lng : undefined;
15
+ if (lat !== undefined && lng !== undefined && (lat !== 0 || lng !== 0)) return {
16
+ latitude: lat,
17
+ longitude: lng
18
+ };
19
+ }
20
+ return {};
21
+ }
2
22
  function sh(cmd, args, opts) {
3
23
  const ctrl = new AbortController();
4
24
  const timer = setTimeout(()=>{
@@ -48,8 +68,20 @@ function sh(cmd, args, opts) {
48
68
  });
49
69
  });
50
70
  }
51
- const upperFirst = (s)=>s ? s[0].toUpperCase() + s.slice(1) : s;
52
- const lowerFirst = (s)=>s ? s[0].toLowerCase() + s.slice(1) : s;
71
+ function buildArgs(keyword, q) {
72
+ const args = [
73
+ '--json',
74
+ 'search',
75
+ 'anything',
76
+ keyword
77
+ ];
78
+ if (q.contentType) args.push('--content-type', q.contentType);
79
+ if (q.parentDestinationId !== undefined) args.push('--destination-id', String(q.parentDestinationId));
80
+ return args;
81
+ }
82
+ function upgradeHint(stderr) {
83
+ return /unknown command/i.test(stderr) ? 'hbcli 版本过旧(无 search anything 子命令)——请升级:hbcli update,或重跑 npx gotry setup' : null;
84
+ }
53
85
  export async function anythingSearch(q) {
54
86
  const started = Date.now();
55
87
  const ts = new Date().toISOString();
@@ -64,30 +96,30 @@ export async function anythingSearch(q) {
64
96
  error: 'keyword is required'
65
97
  };
66
98
  }
67
- const bin = q.hbcliBin ?? 'hbcli';
68
- const args = [
69
- bin,
70
- 'search',
71
- 'anything',
72
- kw
73
- ];
74
- if (q.contentType) args.push('--content-type', q.contentType);
75
- if (q.parentDestinationId !== undefined) {
76
- args.push('--parent-destination-id', String(q.parentDestinationId));
99
+ const args = buildArgs(kw, q);
100
+ const candidates = hbcliBinCandidates(q.hbcliBin ?? 'hbcli');
101
+ let r = {
102
+ code: -1,
103
+ stdout: '',
104
+ stderr: ''
105
+ };
106
+ for(let i = 0; i < candidates.length; i++){
107
+ r = await sh(candidates[i], args, {
108
+ timeoutMs: q.timeoutMs ?? 12_000,
109
+ env: process.env
110
+ });
111
+ if (!(r.error && i < candidates.length - 1)) break;
77
112
  }
78
- const r = await sh(bin, args.slice(1), {
79
- timeoutMs: q.timeoutMs ?? 12_000,
80
- env: process.env
81
- });
82
113
  const latencyMs = Date.now() - started;
83
114
  if (r.error || r.code !== 0) {
115
+ const raw = r.error ?? `${r.stderr.slice(0, 200)} (exit ${r.code})`;
84
116
  return {
85
117
  ok: false,
86
118
  via: 'hbcli-anything-error',
87
- evidence: `[实时API:hbcli-anything@error@${ts}] ${r.error ?? `exit ${r.code}`}`,
119
+ evidence: `[实时API:hbcli-anything@error@${ts}] ${raw}`,
88
120
  latencyMs,
89
121
  verdict: 'error',
90
- error: r.error ?? `${r.stderr.slice(0, 200)} (exit ${r.code})`
122
+ error: upgradeHint(r.stderr) ?? raw
91
123
  };
92
124
  }
93
125
  let data;
@@ -109,15 +141,19 @@ export async function anythingSearch(q) {
109
141
  const hits = rawItems.map((it)=>{
110
142
  const region = it.region ?? {};
111
143
  const hotel = it.hotel ?? {};
112
- const isHotel = it.type === 'hotel' || it.type === 'SupplierHotelList' || Boolean(hotel.id);
144
+ const isHotel = it.type === 'hotel' || Boolean(hotel.id);
145
+ const hotelCoords = latlngOf(hotel.latlngCoordinator);
146
+ const placeCoords = latlngOf(it.place?.latlngCoordinator);
147
+ const center = region.coordinates ?? {};
113
148
  return {
114
- type: isHotel ? 'hotel' : it.type === 'place' || it.type === 'City' ? lowerFirst(it.type) : 'city',
115
- name: it.name ?? region.name_en ?? region.name ?? hotel.name ?? '?',
116
- score: it.score ?? region.typeScore,
117
- latitude: hotel.latitude ?? region.latitude,
118
- longitude: hotel.longitude ?? region.longitude,
119
- destinationId: region.id,
120
- hotelId: hotel.id
149
+ type: isHotel ? 'hotel' : it.type === 'place' ? 'place' : 'city',
150
+ name: i18nText(hotel.name) || i18nText(region.name) || '?',
151
+ score: it.matchScore,
152
+ latitude: hotelCoords.latitude ?? placeCoords.latitude ?? (typeof center.centerLat === 'number' ? center.centerLat : undefined),
153
+ longitude: hotelCoords.longitude ?? placeCoords.longitude ?? (typeof center.centerLng === 'number' ? center.centerLng : undefined),
154
+ destinationId: hotel.destinationId !== undefined ? String(hotel.destinationId) : region.id !== undefined ? String(region.id) : undefined,
155
+ hotelId: hotel.id !== undefined ? String(hotel.id) : undefined,
156
+ star: typeof hotel.star === 'number' && hotel.star > 0 ? hotel.star : undefined
121
157
  };
122
158
  });
123
159
  const verdict = hits.length > 0 ? 'hit' : 'miss';
@@ -131,9 +167,6 @@ export async function anythingSearch(q) {
131
167
  totalCandidates: rawItems.length
132
168
  };
133
169
  }
134
- export const _ = {
135
- upperFirst
136
- };
137
170
 
138
171
 
139
172
  //# sourceURL=ts/capabilities/anything.ts
@@ -1,5 +1,6 @@
1
1
  import { spawn } from 'node:child_process';
2
2
  import { existsSync, readFileSync } from 'node:fs';
3
+ import { createRequire } from 'node:module';
3
4
  import { homedir } from 'node:os';
4
5
  import { join, resolve } from 'node:path';
5
6
  import { readLatestChannelEvents } from './channel-health.js';
@@ -206,13 +207,22 @@ export async function runDoctorChecks(opts = {}) {
206
207
  detail: '已挂载但 calendar 未配置 username——日历工具会话中会报「未配置」',
207
208
  fix: `npx gotry setup calendar --off(恢复默认不挂载),或在 ${calProfilePatch} 覆盖 calendar 行 config 填 username(指引: npx gotry setup calendar --status)`
208
209
  });
210
+ const rootRequire = createRequire(join(repoRoot, 'package.json'));
209
211
  const mapCandidates = [
212
+ join(repoRoot, 'ts/dsh-runtime/vendor/dsh-map-tools/lib/index.js'),
210
213
  join(repoRoot, 'ts/dsh-runtime/node_modules/dsh-map-tools/lib/index.js'),
211
214
  join(repoRoot, 'node_modules/dsh-map-tools/package.json'),
212
215
  join(repoRoot, 'ts/node_modules/dsh-map-tools/package.json'),
213
216
  join(home, '.dsh/profiles/web/node_modules/dsh-map-tools/package.json')
214
217
  ];
215
- const mapHit = mapCandidates.find((p)=>existsSync(p));
218
+ let mapHit = mapCandidates.find((p)=>existsSync(p));
219
+ if (!mapHit) {
220
+ try {
221
+ mapHit = rootRequire.resolve('dsh-map-tools');
222
+ } catch {
223
+ mapHit = undefined;
224
+ }
225
+ }
216
226
  items.push(mapHit ? {
217
227
  id: 'map-tools',
218
228
  label: 'dsh-map-tools(地图/路线/POI)',
@@ -223,14 +233,26 @@ export async function runDoctorChecks(opts = {}) {
223
233
  label: 'dsh-map-tools(地图/路线/POI)',
224
234
  status: 'missing',
225
235
  detail: '未随包解析——启动时该 patch 条目被静默剔除,地图/路线/POI 工具不会出现在模型工具箱(缺地图不挡旅行规划,只少能力)',
226
- fix: 'npx gotry doctor --fix 不覆盖此项:source 布局把 dsh-map-tools 放进 ts/dsh-runtime/node_modules(或 ts/node_modules);npm 布局重装 @danceiny/gotry(随发行版依赖提供)'
236
+ fix: '重装 @danceiny/gotry(地图插件随包 vendor 分发,缺失多为安装不完整)'
227
237
  });
228
238
  const askCandidates = [
229
239
  join(repoRoot, 'ts/dsh-runtime/vendor/deepseek-ai-dsh-tool-ask-user/package.json'),
230
240
  join(repoRoot, 'node_modules/@deepseek-ai/dsh-tool-ask-user/package.json'),
231
241
  join(home, '.dsh/profiles/web/node_modules/@deepseek-ai/dsh-tool-ask-user/package.json')
232
242
  ];
233
- const askHit = askCandidates.find((p)=>existsSync(p));
243
+ let askHit = askCandidates.find((p)=>existsSync(p));
244
+ if (!askHit) {
245
+ try {
246
+ const reqFromDsh = createRequire(rootRequire.resolve('@deepseek-ai/dsh/lib/bin.js'));
247
+ askHit = reqFromDsh.resolve('@deepseek-ai/dsh-tool-ask-user');
248
+ } catch {
249
+ try {
250
+ askHit = rootRequire.resolve('@deepseek-ai/dsh-tool-ask-user');
251
+ } catch {
252
+ askHit = undefined;
253
+ }
254
+ }
255
+ }
234
256
  items.push(askHit ? {
235
257
  id: 'ask-user',
236
258
  label: 'dsh-tool-ask-user(结构化澄清卡)',
@@ -112,6 +112,25 @@ export async function callHbcliJson(args, opts = {}) {
112
112
  }
113
113
  return last;
114
114
  }
115
+ function i18nName(v) {
116
+ if (!v) return '';
117
+ if (typeof v === 'string') return v;
118
+ return v.zh || v.en || v.ar || '';
119
+ }
120
+ function hotelLines(result, max = 8) {
121
+ const list = result?.list;
122
+ if (!Array.isArray(list) || list.length === 0) return '';
123
+ const lines = [];
124
+ for (const item of list.slice(0, max)){
125
+ const h = item;
126
+ const name = i18nName(h.name) || `hotel ${h.id ?? '?'}`;
127
+ const star = typeof h.star === 'number' && h.star > 0 ? ` ${h.star}★` : '';
128
+ const price = h.minPrice && typeof h.minPrice.amount === 'number' ? ` 低至 ${h.minPrice.amount}${h.minPrice.currency ? ` ${h.minPrice.currency}` : ''}` : '';
129
+ lines.push(`- id=${h.id ?? '?'} ${name}${star}${price}`);
130
+ }
131
+ if (list.length > max) lines.push(`…(共 ${list.length} 家,仅列前 ${max})`);
132
+ return lines.join('\n');
133
+ }
115
134
  export async function searchHotels(query, opts = {}) {
116
135
  const hbArgs = [
117
136
  'search',
@@ -121,6 +140,8 @@ export async function searchHotels(query, opts = {}) {
121
140
  '10'
122
141
  ];
123
142
  if (query.destination) hbArgs.push('--destination-name', query.destination);
143
+ if (query.checkIn) hbArgs.push('--check-in', query.checkIn);
144
+ if (query.checkOut) hbArgs.push('--check-out', query.checkOut);
124
145
  if (query.adults) hbArgs.push('--room-occupancies', JSON.stringify([
125
146
  {
126
147
  adultCount: query.adults,
@@ -129,10 +150,15 @@ export async function searchHotels(query, opts = {}) {
129
150
  ]));
130
151
  const live = await callHbcliJson(hbArgs, opts);
131
152
  if (live.via === 'hbcli-realtime') {
153
+ const list = live.result?.list;
154
+ const n = Array.isArray(list) ? list.length : 0;
155
+ const dateTag = query.checkIn ? `入住 ${query.checkIn}${query.checkOut ? ` → 退房 ${query.checkOut}` : ''}` : '未传日期(上游按当前窗口价)';
156
+ const lines = hotelLines(live.result);
157
+ const summary = `${query.destination}:hbcli 实时 ${n} 家(${dateTag})${lines ? `\n${lines}` : ''}\n${live.evidence}`;
132
158
  return {
133
159
  ...live,
134
160
  hotels: live.result,
135
- summary: `${query.destination}:hbcli 实时返回${query.checkIn || query.checkOut ? '(日期不传上游 list,以当前窗口房价返回)' : ''}`
161
+ summary
136
162
  };
137
163
  }
138
164
  const rawReason = live.error ?? live.via;
@@ -151,7 +177,7 @@ export async function searchHotels(query, opts = {}) {
151
177
  hotels: {
152
178
  stays: matched
153
179
  },
154
- summary: `${query.destination}:hbcli 实时源不可用(${reason}),已降级到静态包(公开渠道估算,非实时),命中 ${matched.length} 个住宿块`
180
+ summary: `${query.destination}:hbcli 实时源不可用(${reason}),已降级到静态包(公开渠道估算,非实时),命中 ${matched.length} 个住宿块:\n${JSON.stringify(matched).slice(0, 1600)}`
155
181
  };
156
182
  }
157
183
  return {
@@ -117,7 +117,8 @@ async function startRelay() {
117
117
  if (toolNames(body).includes(TOOL)) {
118
118
  plannerBodies.push(body);
119
119
  const plannerIndex = plannerBodies.length;
120
- const alreadyHandedOff = JSON.stringify(body).includes(HANDOFF);
120
+ const realToolMessages = (body.messages ?? []).filter((message)=>message.role === 'tool');
121
+ const alreadyHandedOff = realToolMessages.some((message)=>JSON.stringify(message).includes(HANDOFF));
121
122
  const stall = !alreadyHandedOff && plannerIndex === 1 ? RELAY_STALL_MS : 0;
122
123
  setTimeout(()=>{
123
124
  if (!alreadyHandedOff && plannerIndex === 1) {