@danceiny/gotry 0.0.1-rc.7 → 0.0.1-rc.8

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 (44) hide show
  1. package/README.md +27 -74
  2. package/bin/gotry-inner.js +50 -1
  3. package/bin/gotry-stdio-ask.js +59 -0
  4. package/cordis.gotry-patch.yml +56 -5
  5. package/data/flights_2026.json +7 -4
  6. package/data/hotels_2026.json +102 -15
  7. package/data/time-slot-eval.json +384 -0
  8. package/data/yunnan-pack.json +2 -1
  9. package/dist/capabilities/agent-reach-bridge.py +90 -0
  10. package/dist/capabilities/anything.js +2 -2
  11. package/dist/capabilities/hbcli.js +11 -6
  12. package/dist/capabilities/incident-log.js +2 -1
  13. package/dist/scripts/agent-reach-tests.js +1 -1
  14. package/dist/scripts/agent-reach-wrapper-tests.js +1 -1
  15. package/dist/scripts/hbcli-tests.js +18 -2
  16. package/dist/scripts/memory-capture-tests.js +77 -0
  17. package/dist/scripts/memory-metrics.js +38 -0
  18. package/dist/scripts/nudge-digest.js +93 -0
  19. package/dist/scripts/probe-poi-tests.js +9 -0
  20. package/dist/scripts/replay.js +73 -1
  21. package/dist/scripts/skeleton-check.js +3 -1
  22. package/dist/scripts/skills-contract-tests.js +85 -0
  23. package/dist/scripts/smoke.js +201 -3
  24. package/dist/scripts/time-eval-tests.js +318 -0
  25. package/dist/src/dsh-llm.js +27 -7
  26. package/dist/src/index.js +342 -92
  27. package/dist/src/loop.js +37 -10
  28. package/dist/src/memory-capture.js +40 -0
  29. package/dist/src/memory-utility.js +55 -0
  30. package/dist/src/mock-llm.js +6 -1
  31. package/dist/src/slot-spec.js +165 -0
  32. package/dist/src/time-anchor.js +100 -0
  33. package/dist/src/tool-packet.js +12 -0
  34. package/dist/src/travel-slots.js +144 -0
  35. package/dist/src/wish-pool.js +27 -0
  36. package/package.json +6 -2
  37. package/ts/capabilities/hbcli.ts +6 -6
  38. package/ts/capabilities/incident-log.ts +6 -3
  39. package/ts/scripts/skeleton-check.ts +5 -1
  40. package/ts/src/dsh-llm.ts +27 -7
  41. package/ts/src/index.ts +292 -77
  42. package/ts/src/loop.ts +52 -17
  43. package/ts/src/mock-llm.ts +13 -1
  44. package/ts/cordis.gotry-patch.yml +0 -36
package/ts/src/loop.ts CHANGED
@@ -8,6 +8,7 @@
8
8
 
9
9
  import type { InterviewQuestion, TripState, TravelerProfile, Turn, CalendarState, SolveResult } from './contracts.ts'
10
10
  import type { JourneySpecTS } from './unified.ts'
11
+ import type { TravelSlotExtraction } from './travel-slots.ts'
11
12
  import { anythingSearch } from '../capabilities/anything.ts'
12
13
 
13
14
  /** LLM 端口:S2 mock(确定性剧本) / S4 真(dsh 运行时)。循环不感知差异。 */
@@ -20,6 +21,12 @@ export interface LlmPort {
20
21
  }>
21
22
  /** 翻译:对话历史 → 统一模型 spec;约束未齐时返回 null(循环继续访谈) */
22
23
  extractSpec(history: Turn[], state: TripState): Promise<JourneySpecTS | null>
24
+ /**
25
+ * 槽位抽取(travel_slot_extraction.v1):差旅 intake 面。时间表达逐字保留,
26
+ * 过期判定在确定性层(flagExpiredSlots),实现方负责注入时间锚点卡。
27
+ * now 可注入(评测用固定锚点保证确定性;生产省略取系统时钟)。
28
+ */
29
+ extractSlots(history: Turn[], now?: Date): Promise<TravelSlotExtraction | null>
23
30
  /** 问句润色(ADR-9:确定性驱动,LLM 只管语气) */
24
31
  polishQuestion(q: InterviewQuestion): Promise<string>
25
32
  /** 结果解释(S3 接 solve 后启用) */
@@ -235,7 +242,7 @@ export async function runTurn(
235
242
 
236
243
  for (const q of blocking) parts.push(await llm.polishQuestion(q))
237
244
 
238
- // 约束齐备(无阻塞问题)→ 翻译 spec → **校验闸** → 求解 → 渲染
245
+ // 约束齐备(无阻塞问题)→ 翻译 spec → **校验闸 + 日期一致性闸** → 求解 → 渲染
239
246
  if (blocking.length === 0 && solve) {
240
247
  const spec = await llm.extractSpec([...history, { role: 'user', text: userMsg }], state)
241
248
  // 场景路由:erhai 候选标记 → 候选求解(洱海金标准的引擎判定)——纯 TS unify 路径
@@ -258,6 +265,20 @@ export async function runTurn(
258
265
  } else {
259
266
  const invalid = spec ? validateSpec(spec) : '翻译器未产出 spec'
260
267
  if (spec && !invalid) {
268
+ // D-10 切片 C:spec↔槽位日期一致性闸(ADR-10 翻译≠造数的执行点)。
269
+ // LLM 翻译的 spec 日期与代码层换算的槽位日期分歧时不求解、不猜,追问确认——
270
+ // 日期是求解关键输入,错日期 = 错判决。槽位缺失/unresolved 不参与(不造假阳性)。
271
+ const ext = await llm.extractSlots([...history, { role: 'user', text: userMsg }])
272
+ if (ext) {
273
+ const { resolveSlots, specDateMismatches } = await import('./slot-spec.ts')
274
+ const { buildTimeAnchor } = await import('./time-anchor.ts')
275
+ const mismatches = specDateMismatches(spec, resolveSlots(ext, buildTimeAnchor()))
276
+ if (mismatches.length > 0) {
277
+ const lines = mismatches.map(m => `- ${m.specAt}:翻译说 ${m.specDate},你的原话换算是 ${m.slotDate}(${m.field})`)
278
+ parts.push(`⚠️ 日期分歧,确认后我再算(以你为准):\n${lines.join('\n')}`)
279
+ return { reply: parts.join('\n\n'), state }
280
+ }
281
+ }
261
282
  state.spec = spec
262
283
  state.solve = await solve(spec)
263
284
  parts.push(llm.render ? await llm.render(state) : renderSolve(state))
@@ -309,28 +330,42 @@ export async function settleAsyncTicket(ticketId: string, reply: string): Promis
309
330
 
310
331
  /**
311
332
  * probePoi:从 user msg 探测"查 POI/酒店" 信号,返回关键词。
312
- * 触发的不是关键词,是**结构**:句首含"查/搜/找" + 后接地理/酒店类名词。
333
+ * 触发的不是关键词,是**结构**;关键词抓取有方向性——
334
+ * 1) 显式搜索动词(查/搜/找/看看/推荐/告诉我/检索)+ 宾语在动词后;
335
+ * 2) 住宿名词(酒店/民宿/客栈/饭店):优先取名词**之后**的名称段(须含拉丁/数字,
336
+ * 「我订了酒店:The Title…」→ The Title…);退路是名词前 2-6 字紧邻地名
337
+ * (「大理酒店」→ 大理;「机票和」类垃圾后缀拒);
338
+ * 3) 开放问(有什么/玩什么/有哪些):取名词前的地名;
339
+ * 4) 短裸地名:≤12 字、无标点、无陈述动词(「大理」✓;「明天有空」「我的工作时间是…」✗——
340
+ * 2026-08-28 巡检收紧:旧版 ≤24 直通把访谈答案整句当关键词,垃圾 hbcli 调用+证据噪音)。
313
341
  * 不是意图(改求解器),只是 datasources 编排层的"提早调 anything"提示。
314
342
  */
315
343
  export function probePoi(msg: string): string | null {
316
344
  const trimmed = msg.trim()
317
345
  if (!trimmed) return null
318
- // 短查询(<=24 字符 且非问句): 优先剥 trigger 词,再 fall-through 让内容触发也跑
319
- const isShort = trimmed.length <= 24 && !/[??!!。.,,]/.test(trimmed)
320
- if (isShort) {
321
- const stripped = trimmed.replace(/^(查一下|查|搜|找|看看|推荐|告诉我|检索)\s*/, '').trim()
322
- // 即使没 trigger 词,只要 stripped 仍是 ≥2 字符的非空串就当关键词
323
- if (stripped.length >= 2) return stripped.slice(0, 24)
324
- }
325
- // 触发模式:查/搜/找/看看/推荐/告诉我/检索 + 地理/酒店类名词(贪吃但只切前 24 字符)
346
+ const cutAtPunct = (s: string): string => s.split(/[,,。.;;!!??]/)[0]?.trim() ?? ''
347
+
348
+ // 1) 显式搜索动词 + 宾语
326
349
  const m = trimmed.match(/(查一下|查|搜|找|看看|推荐|告诉我|检索)\s*(.{2,24})/)
327
- if (m) return m[2].trim().slice(0, 24)
328
- // 另一模式:含"酒店"/"民宿"/"客栈" 触发酒店查
329
- if (/(酒店|民宿|客栈|饭店|有什么|玩什么|有哪些)/.test(trimmed)) {
330
- // 尝试提取前面的地名关键词
331
- const place = trimmed.match(/([一-龥a-zA-Z]{2,12})\s*(?:酒店|民宿|客栈|饭店|有什么|玩什么|有哪些)/)
332
- if (place) return place[1]
333
- return trimmed.replace(/(酒店|民宿|客栈|饭店|有什么|玩什么|有哪些)/, '').trim().slice(0, 24) || trimmed.slice(0, 24)
350
+ if (m?.[2]) {
351
+ const obj = cutAtPunct(m[2])
352
+ if (obj.length >= 2) return obj.slice(0, 24)
353
+ }
354
+ // 2) 住宿名词:名称段在名词后
355
+ if (/(酒店|民宿|客栈|饭店)/.test(trimmed)) {
356
+ const parts = trimmed.split(/(酒店|民宿|客栈|饭店)/)
357
+ const after = cutAtPunct((parts[2] ?? '').replace(/^[的::\s]+/, ''))
358
+ if (after.length >= 2 && /[A-Za-z0-9]/.test(after)) return after.slice(0, 24)
359
+ const nounAt = trimmed.search(/(酒店|民宿|客栈|饭店)/)
360
+ const pre = trimmed.slice(Math.max(0, nounAt - 6), nounAt).match(/[一-龥]{2,6}$/)
361
+ if (pre && !/(机票|车票|和|做|订|的)$/.test(pre[0])) return pre[0]
362
+ }
363
+ // 3) 开放问:地名在疑问词前
364
+ const q = trimmed.match(/([一-龥a-zA-Z]{2,12})\s*(?:有什么|玩什么|有哪些)/)
365
+ if (q?.[1]) return q[1]
366
+ // 4) 短裸地名(≤12 字,无标点,无陈述动词)
367
+ if (trimmed.length <= 12 && !/[,,。.;;!!??::]/.test(trimmed) && !/[是有在要想得到说订吧呢]/.test(trimmed)) {
368
+ return trimmed.slice(0, 24)
334
369
  }
335
370
  return null
336
371
  }
@@ -7,6 +7,7 @@
7
7
  import type { LlmPort } from './loop.ts'
8
8
  import type { InterviewQuestion, TravelerProfile, TripState, Turn, CalendarState } from './contracts.ts'
9
9
  import type { JourneySpecTS } from './unified.ts'
10
+ import type { TravelSlotExtraction } from './travel-slots.ts'
10
11
  import { parseFlightPackToSpec } from './unified.ts'
11
12
 
12
13
  interface ScriptStep {
@@ -16,6 +17,12 @@ interface ScriptStep {
16
17
  profile?: Partial<TravelerProfile>
17
18
  }
18
19
 
20
+ /** 槽位抽取剧本:评测 harness 把 golden 逐条挂进来,mock 原样回吐(管道自测用) */
21
+ export interface SlotScriptStep {
22
+ when: string
23
+ extraction: TravelSlotExtraction
24
+ }
25
+
19
26
  /** 剧本:来自真实 Kimi 对话里用户给出关键事实的时刻 */
20
27
  const SCRIPT: ScriptStep[] = [
21
28
  {
@@ -48,7 +55,7 @@ const SCRIPT: ScriptStep[] = [
48
55
  },
49
56
  ]
50
57
 
51
- export function createMockLlm(flightPackPath?: string): LlmPort {
58
+ export function createMockLlm(flightPackPath?: string, slotScript: SlotScriptStep[] = []): LlmPort {
52
59
  let stepIdx = 0
53
60
  return {
54
61
  async extractFacts(history: Turn[], _state: TripState) {
@@ -81,6 +88,11 @@ export function createMockLlm(flightPackPath?: string): LlmPort {
81
88
  spec.budgetCny = 9000
82
89
  return spec
83
90
  },
91
+ async extractSlots(history: Turn[]): Promise<TravelSlotExtraction | null> {
92
+ const last = history[history.length - 1]?.text ?? ''
93
+ const hit = slotScript.find(s => last.includes(s.when))
94
+ return hit ? structuredClone(hit.extraction) : null
95
+ },
84
96
  async polishQuestion(q: InterviewQuestion) {
85
97
  return `【${q.key}】${q.text}\n(为什么问:${q.why})`
86
98
  },
@@ -1,36 +0,0 @@
1
- # GoTry × dsh 成品组合(DeepSeek 原生——创始人官方 key)
2
- # 用法(在 ts/dsh-runtime 下):
3
- # DEEPSEEK_API_KEY=<key> node node_modules/@deepseek-ai/dsh/lib/bin.js \
4
- # --profile headless --patch ../cordis.gotry-patch.yml "<任务>"
5
- # (web 界面: 把 --profile headless 换成 web)
6
- - insert:
7
- - id: gotry-tools
8
- name: '/Users/bytedance/work/gotry/ts/src/index.ts'
9
- config:
10
- pythonBin: '../../.venv/bin/python'
11
- pythonPath: '../../py'
12
- stateRoot: '.'
13
- timeoutMs: 30000
14
- preferInProcess: true
15
- hbcliBin: 'hbcli'
16
-
17
- # GoTry 成品人格:行为契约=产品语义
18
- - id: system-prompt
19
- config:
20
- persona: >-
21
- 你是 GoTry——从出发到下一次出发的 AI 旅行伙伴(中国出境首发,workation 场景)。
22
- 今天是 {{current_date}}。行为契约:
23
- (1)动机先行:首轮必问工作窗口(几点到几点/什么时区)与已订资源(航班/酒店);
24
- (2)日历一次断言:日期换算 2026 年星期并记住,冲突让用户裁决,永不重算;
25
- (3)翻译不造数:班次/价格只来自工具(gotry_feasibility_check/gotry_skeleton_check/gotry_hotel_search);
26
- (4)判定归引擎:可行性/全成本由工具计算,你只解释;
27
- (5)待决=选择题:2-3 个带 trade-off 选项;
28
- (6)憧憬不被拒绝:不可行进 gotry_wish_pool_add(带条件);
29
- (7)证据链:引用工具返回的标注([骨架:openflights]/[实时API:hbcli]/[静态包:估算])。
30
- (8)时间感知:推荐季节/月份选项时,以今天为锚点——已过的月份不说「将」,
31
- 当下所在的季节直接点明(如「现在正是雨季」而非把当前月份当成远期选项)。
32
- (9)兜底链:自身工具(可行性/酒店/天气/航班/Anything)答不上来时,用
33
- gotry_agent_reach(100% follow Panniantong/Agent-Reach 路由表):web 读任意网页(r.jina.ai)/
34
- rss/v2ex/youtube 字幕(yt-dlp)/github 搜索(gh)/bilibili/exa 语义搜索;需登录态的渠道
35
- (twitter/reddit/xhs 等)会返回配置指引——如实转告用户,不编造。action=status 可跑
36
- 上游 `agent-reach doctor` 体检。答案标 [agent-reach:<渠道>@<ts>]。