@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
@@ -0,0 +1,384 @@
1
+ {
2
+ "schema": "time-slot-eval/v1",
3
+ "anchor_date": "2026-08-27",
4
+ "note": "时间感评测集(25 题,来自外部差旅槽位评测表 2026-08-27 转录)。golden 约定:时间表达逐字保留(换算归工具/锚点层);过期判定只依赖 anchor_date——换锚点只需复核 time_expired 一题;评测集只增不改语义(tech-strategy §4 纪律)。",
5
+ "cases": [
6
+ {
7
+ "id": "time_accurate",
8
+ "priority": "P0",
9
+ "note": "给出准确日期,最终目标完成机酒预定",
10
+ "utterance": "9.1-9.10 去广州 安排行程",
11
+ "expected": {
12
+ "schema_version": "travel_slot_extraction.v1",
13
+ "language": "zh",
14
+ "domains": ["requisition"],
15
+ "slots": {
16
+ "requisition": { "mode": "create", "destination": "广州", "start_date": "9.1", "end_date": "9.10", "trip_type": "one_way" }
17
+ },
18
+ "missing_slots": []
19
+ }
20
+ },
21
+ {
22
+ "id": "time_expired",
23
+ "priority": "P1",
24
+ "note": "存在过期时间,最终目标完成行程安排",
25
+ "utterance": "8.1-8.10 去广州,安排行程",
26
+ "expected": {
27
+ "schema_version": "travel_slot_extraction.v1",
28
+ "language": "zh",
29
+ "domains": ["requisition"],
30
+ "slots": {
31
+ "requisition": { "mode": "create", "destination": "广州", "start_date": "8.1", "end_date": "8.10", "trip_type": "one_way" }
32
+ },
33
+ "missing_slots": ["requisition.start_date (date is expired)"]
34
+ }
35
+ },
36
+ {
37
+ "id": "time_weekday_range",
38
+ "priority": "P0",
39
+ "note": "日期周表述,最终目标完成机票预定",
40
+ "utterance": "下周一到下周五在广州",
41
+ "expected": {
42
+ "schema_version": "travel_slot_extraction.v1",
43
+ "language": "zh",
44
+ "domains": ["requisition"],
45
+ "slots": {
46
+ "requisition": { "mode": "create", "destination": "广州", "start_date": "下周一", "end_date": "下周五", "trip_type": "one_way" }
47
+ },
48
+ "missing_slots": []
49
+ }
50
+ },
51
+ {
52
+ "id": "time_weekday_english",
53
+ "priority": "P1",
54
+ "note": "日期英文下星期表述,最终目的完成机票预定",
55
+ "utterance": "next Thursday 去北京,预定酒店",
56
+ "expected": {
57
+ "schema_version": "travel_slot_extraction.v1",
58
+ "language": "mixed",
59
+ "domains": ["hotel"],
60
+ "slots": {
61
+ "hotel": { "action": "search", "city": "北京", "check_in_date": "next Thursday" }
62
+ },
63
+ "missing_slots": []
64
+ }
65
+ },
66
+ {
67
+ "id": "time_exact_same_day",
68
+ "priority": "P0",
69
+ "note": "具体日期表述,当天往返,最终目标完成行程安排",
70
+ "utterance": "9.10 日去北京,当天往返",
71
+ "expected": {
72
+ "schema_version": "travel_slot_extraction.v1",
73
+ "language": "zh",
74
+ "domains": ["requisition"],
75
+ "slots": {
76
+ "requisition": { "mode": "create", "destination": "北京", "start_date": "9.10", "trip_type": "round_trip" }
77
+ },
78
+ "missing_slots": []
79
+ }
80
+ },
81
+ {
82
+ "id": "time_weekday_multi_dest",
83
+ "priority": "P1",
84
+ "note": "日期周表述,多目的地行程,最终目标完成行程安排",
85
+ "utterance": "下周四去北京,待到周日,周日去广州待两天然后回上海",
86
+ "expected": {
87
+ "schema_version": "travel_slot_extraction.v1",
88
+ "language": "zh",
89
+ "domains": ["requisition", "flight"],
90
+ "slots": {
91
+ "requisition": { "mode": "create", "destination": ["北京", "广州", "上海"], "start_date": "下周四", "trip_type": "round_trip" },
92
+ "flight": { "action": "search", "destination": ["北京", "广州", "上海"], "departure_date": "下周四", "multi_destination": true, "trip_type": "round_trip" }
93
+ },
94
+ "missing_slots": []
95
+ }
96
+ },
97
+ {
98
+ "id": "time_english_date_range",
99
+ "priority": "P1",
100
+ "note": "英文日期表述,最终目标完成行程推荐",
101
+ "utterance": "Aug-5 to Aug-10, go to shanghai, recommend me trip",
102
+ "expected": {
103
+ "schema_version": "travel_slot_extraction.v1",
104
+ "language": "en",
105
+ "domains": ["requisition"],
106
+ "slots": {
107
+ "requisition": { "mode": "create", "destination": "上海", "start_date": "Aug-5", "end_date": "Aug-10", "trip_type": "one_way" }
108
+ },
109
+ "missing_slots": []
110
+ }
111
+ },
112
+ {
113
+ "id": "time_month_vague",
114
+ "priority": "P1",
115
+ "note": "模糊月份日期表述,最终目标完成行程安排",
116
+ "utterance": "下个月去广州,帮我安排行程",
117
+ "expected": {
118
+ "schema_version": "travel_slot_extraction.v1",
119
+ "language": "zh",
120
+ "domains": ["requisition"],
121
+ "slots": {
122
+ "requisition": { "mode": "create", "destination": "广州", "start_date": "下个月", "trip_type": "one_way" }
123
+ },
124
+ "missing_slots": []
125
+ }
126
+ },
127
+ {
128
+ "id": "time_vague_query",
129
+ "priority": "P0",
130
+ "note": "模糊日期查询,最终目标完成机酒预定",
131
+ "utterance": "下周初飞北京开会,哪天机票比较便宜",
132
+ "expected": {
133
+ "schema_version": "travel_slot_extraction.v1",
134
+ "language": "zh",
135
+ "domains": ["flight"],
136
+ "slots": {
137
+ "flight": { "action": "search", "destination": "北京", "departure_date": "下周初", "sort_name": "PRICE", "sort_type": "ASC", "trip_type": "one_way" }
138
+ },
139
+ "missing_slots": []
140
+ }
141
+ },
142
+ {
143
+ "id": "time_weekday_stay_duration",
144
+ "priority": "P1",
145
+ "note": "日期周表述,含住宿天数,最终目标完成酒店预定",
146
+ "utterance": "下周二入住北京,住十天回上海",
147
+ "expected": {
148
+ "schema_version": "travel_slot_extraction.v1",
149
+ "language": "zh",
150
+ "domains": ["hotel", "flight"],
151
+ "slots": {
152
+ "hotel": { "action": "search", "city": "北京", "check_in_date": "下周二", "check_out_date": "下周二+10" },
153
+ "flight": { "action": "search", "origin": "北京", "destination": "上海", "departure_date": "下周二+10", "trip_type": "one_way" }
154
+ },
155
+ "missing_slots": []
156
+ }
157
+ },
158
+ {
159
+ "id": "time_relative_week",
160
+ "priority": "P0",
161
+ "note": "相对日期表述,最终目标完成行程安排",
162
+ "utterance": "明天去北京,出差一周,帮我安排行程",
163
+ "expected": {
164
+ "schema_version": "travel_slot_extraction.v1",
165
+ "language": "zh",
166
+ "domains": ["requisition"],
167
+ "slots": {
168
+ "requisition": { "mode": "create", "destination": "北京", "start_date": "明天", "duration_days": 7, "trip_type": "one_way" }
169
+ },
170
+ "missing_slots": []
171
+ }
172
+ },
173
+ {
174
+ "id": "time_relative_departure_pref",
175
+ "priority": "P1",
176
+ "note": "相对日期表述,含出发时间偏好,最终目标完成机票预定",
177
+ "utterance": "上海到北京 大后天 希望早上七点后出发",
178
+ "expected": {
179
+ "schema_version": "travel_slot_extraction.v1",
180
+ "language": "zh",
181
+ "domains": ["flight"],
182
+ "slots": {
183
+ "flight": { "action": "search", "origin": "上海", "destination": "北京", "departure_date": "大后天", "departure_time_pref": "07:00后", "trip_type": "one_way" }
184
+ },
185
+ "missing_slots": []
186
+ }
187
+ },
188
+ {
189
+ "id": "time_month_vague_stay_duration",
190
+ "priority": "P1",
191
+ "note": "模糊月份日期表述,含停留天数,最终目标完成行程安排",
192
+ "utterance": "下个月初去北京,待 10 天,帮我安排行程",
193
+ "expected": {
194
+ "schema_version": "travel_slot_extraction.v1",
195
+ "language": "zh",
196
+ "domains": ["requisition"],
197
+ "slots": {
198
+ "requisition": { "mode": "create", "destination": "北京", "start_date": "下个月初", "duration_days": 10, "trip_type": "one_way" }
199
+ },
200
+ "missing_slots": []
201
+ }
202
+ },
203
+ {
204
+ "id": "time_weekday_return",
205
+ "priority": "P1",
206
+ "note": "日期周表述,最终目标完成机票预定",
207
+ "utterance": "下下周四去北京,周日回",
208
+ "expected": {
209
+ "schema_version": "travel_slot_extraction.v1",
210
+ "language": "zh",
211
+ "domains": ["flight"],
212
+ "slots": {
213
+ "flight": { "action": "search", "destination": "北京", "departure_date": "下下周四", "return_date": "周日", "trip_type": "round_trip" }
214
+ },
215
+ "missing_slots": []
216
+ }
217
+ },
218
+ {
219
+ "id": "time_english_weekday_intl_multi_dest",
220
+ "priority": "P1",
221
+ "note": "英文日期周表述,国际多目的地行程,最终目标完成机票预定",
222
+ "utterance": "I need to check flights for an open-jaw trip: Beijing to Tokyo next Monday, and Osaka back to Beijing next Friday. Can you handle this kind of itinerary?",
223
+ "expected": {
224
+ "schema_version": "travel_slot_extraction.v1",
225
+ "language": "en",
226
+ "domains": ["flight"],
227
+ "slots": {
228
+ "flight": { "action": "search", "origin": "Beijing", "destination": ["Tokyo", "Osaka", "Beijing"], "departure_date": "next Monday", "return_date": "next Friday", "multi_destination": true, "trip_type": "round_trip" }
229
+ },
230
+ "missing_slots": []
231
+ }
232
+ },
233
+ {
234
+ "id": "time_vague_few_days",
235
+ "priority": "P0",
236
+ "note": "模糊日期表述,最终目标完成机票预定",
237
+ "utterance": "过几天要去北京,帮我看下机票",
238
+ "expected": {
239
+ "schema_version": "travel_slot_extraction.v1",
240
+ "language": "zh",
241
+ "domains": ["flight"],
242
+ "slots": {
243
+ "flight": { "action": "search", "destination": "北京", "departure_date": "过几天", "trip_type": "one_way" }
244
+ },
245
+ "missing_slots": []
246
+ }
247
+ },
248
+ {
249
+ "id": "time_ambiguous_semantics",
250
+ "priority": "P1",
251
+ "note": "日期表述(语义歧义),最终目标完成行程安排",
252
+ "utterance": "明年一月份要去北京 3 天,帮我安排行程",
253
+ "expected": {
254
+ "schema_version": "travel_slot_extraction.v1",
255
+ "language": "zh",
256
+ "domains": ["requisition"],
257
+ "slots": {
258
+ "requisition": { "mode": "create", "destination": "北京", "start_date": "明年一月份", "duration_days": 3, "trip_type": "one_way" }
259
+ },
260
+ "missing_slots": []
261
+ }
262
+ },
263
+ {
264
+ "id": "time_month_mid_vague_hotel",
265
+ "priority": "P1",
266
+ "note": "模糊月份日期表述,最终目标完成酒店预定",
267
+ "utterance": "下个月中旬要去广州,待 4 天,帮我看看酒店",
268
+ "expected": {
269
+ "schema_version": "travel_slot_extraction.v1",
270
+ "language": "zh",
271
+ "domains": ["hotel"],
272
+ "slots": {
273
+ "hotel": { "action": "search", "city": "广州", "check_in_date": "下个月中旬", "check_out_date": "下个月中旬+4" }
274
+ },
275
+ "missing_slots": []
276
+ }
277
+ },
278
+ {
279
+ "id": "time_relative_short",
280
+ "priority": "P1",
281
+ "note": "相对日期表述,最终目标完成机票预定",
282
+ "utterance": "后天去深圳,帮我看看机票",
283
+ "expected": {
284
+ "schema_version": "travel_slot_extraction.v1",
285
+ "language": "zh",
286
+ "domains": ["flight"],
287
+ "slots": {
288
+ "flight": { "action": "search", "destination": "深圳", "departure_date": "后天", "trip_type": "one_way" }
289
+ },
290
+ "missing_slots": []
291
+ }
292
+ },
293
+ {
294
+ "id": "time_relative_biweekly",
295
+ "priority": "P0",
296
+ "note": "相对日期表述(隔周),最终目标完成行程安排",
297
+ "utterance": "隔周要去北京三天,帮我安排行程",
298
+ "expected": {
299
+ "schema_version": "travel_slot_extraction.v1",
300
+ "language": "zh",
301
+ "domains": ["requisition"],
302
+ "slots": {
303
+ "requisition": { "mode": "create", "destination": "北京", "start_date": "隔周", "duration_days": 3, "trip_type": "one_way" }
304
+ },
305
+ "missing_slots": []
306
+ }
307
+ },
308
+ {
309
+ "id": "time_weekday_hotel",
310
+ "priority": "P0",
311
+ "note": "日期周表述,最终目标完成酒店预定",
312
+ "utterance": "本周三要去广州,帮我看看酒店",
313
+ "expected": {
314
+ "schema_version": "travel_slot_extraction.v1",
315
+ "language": "zh",
316
+ "domains": ["hotel"],
317
+ "slots": {
318
+ "hotel": { "action": "search", "city": "广州", "check_in_date": "本周三" }
319
+ },
320
+ "missing_slots": []
321
+ }
322
+ },
323
+ {
324
+ "id": "time_quarter_departure_pref",
325
+ "priority": "P1",
326
+ "note": "季度日期表述,含出发时间偏好,最终目标完成机票预定",
327
+ "utterance": "下个季度初去北京,帮我看看机票,最好是 8 点后出发",
328
+ "expected": {
329
+ "schema_version": "travel_slot_extraction.v1",
330
+ "language": "zh",
331
+ "domains": ["flight"],
332
+ "slots": {
333
+ "flight": { "action": "search", "destination": "北京", "departure_date": "下个季度初", "departure_time_pref": "08:00后", "trip_type": "one_way" }
334
+ },
335
+ "missing_slots": []
336
+ }
337
+ },
338
+ {
339
+ "id": "time_vague_recent_query",
340
+ "priority": "P1",
341
+ "note": "模糊日期表述,最终目标完成机票查询",
342
+ "utterance": "近期打算去北京出差,帮我看看上海到北京的机票价格",
343
+ "expected": {
344
+ "schema_version": "travel_slot_extraction.v1",
345
+ "language": "zh",
346
+ "domains": ["flight"],
347
+ "slots": {
348
+ "flight": { "action": "search", "origin": "上海", "destination": "北京", "departure_date": "近期", "trip_type": "one_way" }
349
+ },
350
+ "missing_slots": []
351
+ }
352
+ },
353
+ {
354
+ "id": "time_holiday_before",
355
+ "priority": "P1",
356
+ "note": "节日日期表述,最终目标完成行程安排",
357
+ "utterance": "国庆前需要去北京出差,帮我安排行程",
358
+ "expected": {
359
+ "schema_version": "travel_slot_extraction.v1",
360
+ "language": "zh",
361
+ "domains": ["requisition"],
362
+ "slots": {
363
+ "requisition": { "mode": "create", "destination": "北京", "start_date": "国庆前", "trip_type": "one_way" }
364
+ },
365
+ "missing_slots": []
366
+ }
367
+ },
368
+ {
369
+ "id": "time_weekday_short_trip",
370
+ "priority": "P1",
371
+ "note": "日期星期表述,最终目标完成机票预定",
372
+ "utterance": "星期四要去广州出差,三天行程,帮我安排",
373
+ "expected": {
374
+ "schema_version": "travel_slot_extraction.v1",
375
+ "language": "zh",
376
+ "domains": ["requisition"],
377
+ "slots": {
378
+ "requisition": { "mode": "create", "destination": "广州", "start_date": "星期四", "duration_days": 3, "trip_type": "one_way" }
379
+ },
380
+ "missing_slots": []
381
+ }
382
+ }
383
+ ]
384
+ }
@@ -24,7 +24,8 @@
24
24
  },
25
25
  "reconcil": [
26
26
  "M-1 工作窗口约束:FD582 8.4 08:10 起飞在 W4(周二,工作日 10:00-19:00 UTC+4)外——从 DMK 起飞,home_tz_offset_min=240 转换后 11:10 = 工作日,排除。8.4 选 FD582 真实:在迪拜继续工作到 12:00 12:00-15:00 周二午休时间,DMK 起飞后家不在出发地,工作窗口不适用,这就是为什么 Kimi 当初放弃 8.3 提前一天(demo 8.3 备选 MU6088 在 M-1 下不应排除,但 Kimi 实选晚一天的 8.4)。",
27
- "auto-guess 2026-08-24:Rawai 实际=The Title East Wing Studio 档,约 ¥400/晚(长住+工作型;非套间)—founder 校正即改"
27
+ "auto-guess 2026-08-24:Rawai 实际=The Title East Wing Studio 档,约 ¥400/晚(长住+工作型;非套间)—founder 校正即改",
28
+ "Rawai 住宿真值(founder 2026-08-26):首晚 The Title 公寓失败——Airbnb 房东(实为物业人员)失联、找不到房与钥匙、语言不通,吵一架后入住但条件差性价比低;次日即换普通酒店,全方位更好(更便宜/位置更好/有泳池)。auto-guess 的『Studio ¥400 长住』仅首夜成立"
28
29
  ]
29
30
  },
30
31
  "legs": [
@@ -0,0 +1,90 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ agent-reach 通用反射桥(gotry wrapper 的唯一胶水)。
4
+
5
+ gotry 不持有任何渠道知识:渠道清单、方法清单、setup 指引全部来自上游
6
+ agent_reach 包(channel registry + Channel.check + guides)。
7
+
8
+ 用法: python agent-reach-bridge.py <channel> <method> [args...]
9
+ 退出码: 0=调用完成(看 JSON ok 字段) / 2=channel 或 method 不存在(附上游清单) / 3=调用抛错
10
+ """
11
+
12
+ import importlib
13
+ import inspect
14
+ import json
15
+ import sys
16
+
17
+
18
+ def _public_methods(obj) -> dict:
19
+ return {
20
+ name: str(inspect.signature(getattr(obj, name)))
21
+ for name in sorted(dir(obj))
22
+ if not name.startswith("_") and callable(getattr(obj, name))
23
+ }
24
+
25
+
26
+ def main() -> int:
27
+ try:
28
+ from agent_reach.channels import ALL_CHANNELS, get_channel
29
+ except Exception as e: # agent-reach 未装
30
+ print(json.dumps({"ok": False, "error": f"agent_reach 未安装: {e}"}, ensure_ascii=False))
31
+ return 3
32
+
33
+ if len(sys.argv) < 3:
34
+ print(json.dumps({
35
+ "ok": False,
36
+ "channels": {c.name: c.description for c in ALL_CHANNELS},
37
+ "hint": "用法: bridge <channel> <method> [args...];渠道体检用 method=check",
38
+ }, ensure_ascii=False))
39
+ return 2
40
+
41
+ channel_name, method_name = sys.argv[1], sys.argv[2]
42
+ args = sys.argv[3:]
43
+
44
+ ch = get_channel(channel_name)
45
+ if ch is None:
46
+ print(json.dumps({
47
+ "ok": False,
48
+ "error": f"未知渠道 '{channel_name}'",
49
+ "channels": {c.name: c.description for c in ALL_CHANNELS},
50
+ }, ensure_ascii=False))
51
+ return 2
52
+
53
+ fn = getattr(ch, method_name, None)
54
+ if fn is None or method_name.startswith("_") or not callable(fn):
55
+ print(json.dumps({
56
+ "ok": False,
57
+ "error": f"渠道 '{channel_name}' 没有方法 '{method_name}'",
58
+ "channel": {"name": ch.name, "description": ch.description, "tier": ch.tier, "backends": ch.backends},
59
+ "methods": _public_methods(ch),
60
+ }, ensure_ascii=False))
61
+ return 2
62
+
63
+ try:
64
+ try:
65
+ out = fn(*args)
66
+ except TypeError:
67
+ # LLM 常把数值参数传成字符串("3" vs 3):数字 coercion 重试一次
68
+ coerced = [int(a) if isinstance(a, str) and a.isdigit() else a for a in args]
69
+ if coerced == args:
70
+ raise
71
+ out = fn(*coerced)
72
+ print(json.dumps({"ok": True, "data": out}, ensure_ascii=False, default=str))
73
+ return 0
74
+ except Exception as e:
75
+ # 失败时带上上游自己的体检结论(check 返回 (status, message)),不自行转述
76
+ check_status, check_msg = "unknown", ""
77
+ try:
78
+ check_status, check_msg = ch.check()
79
+ except Exception:
80
+ pass
81
+ print(json.dumps({
82
+ "ok": False,
83
+ "error": f"{type(e).__name__}: {e}",
84
+ "check": {"status": check_status, "message": check_msg},
85
+ }, ensure_ascii=False))
86
+ return 3
87
+
88
+
89
+ if __name__ == "__main__":
90
+ sys.exit(main())
@@ -16,10 +16,10 @@ function sh(cmd, args, opts) {
16
16
  let stderr = '';
17
17
  let error;
18
18
  let killed = false;
19
- child.stdout.on('data', (d)=>{
19
+ child.stdout?.on('data', (d)=>{
20
20
  stdout += d.toString();
21
21
  });
22
- child.stderr.on('data', (d)=>{
22
+ child.stderr?.on('data', (d)=>{
23
23
  stderr += d.toString();
24
24
  });
25
25
  child.on('error', (e)=>{
@@ -91,18 +91,23 @@ export async function searchHotels(query, opts = {}) {
91
91
  const hbArgs = [
92
92
  'search',
93
93
  'hotel-list',
94
- '--json'
94
+ '--json',
95
+ '--page-size',
96
+ '10'
95
97
  ];
96
- if (query.destination) hbArgs.push('--destination', query.destination);
97
- if (query.checkIn) hbArgs.push('--check-in', query.checkIn);
98
- if (query.checkOut) hbArgs.push('--check-out', query.checkOut);
99
- if (query.adults) hbArgs.push('--adults', String(query.adults));
98
+ if (query.destination) hbArgs.push('--destination-name', query.destination);
99
+ if (query.adults) hbArgs.push('--room-occupancies', JSON.stringify([
100
+ {
101
+ adultCount: query.adults,
102
+ childrenAges: []
103
+ }
104
+ ]));
100
105
  const live = await callHbcliJson(hbArgs, opts);
101
106
  if (live.via === 'hbcli-realtime') {
102
107
  return {
103
108
  ...live,
104
109
  hotels: live.result,
105
- summary: `${query.destination}:hbcli 实时返回`
110
+ summary: `${query.destination}:hbcli 实时返回${query.checkIn || query.checkOut ? '(日期不传上游 list,以当前窗口房价返回)' : ''}`
106
111
  };
107
112
  }
108
113
  const fallback = opts.fallbackPath;
@@ -73,11 +73,12 @@ export function guardToolExecute(name, stateRoot, execute) {
73
73
  stack: (err.stack ?? '').slice(0, 4000),
74
74
  source: name
75
75
  }, stateRoot);
76
- return {
76
+ const failure = {
77
77
  ok: false,
78
78
  summary: `gotry_${name} 内部错误(已隔离,会话继续): ${err.message.slice(0, 300)}`,
79
79
  evidence: `[incident:tool_execute_error@${new Date().toISOString()}]`
80
80
  };
81
+ return failure;
81
82
  }
82
83
  };
83
84
  }
@@ -26,7 +26,7 @@ import { readUrl } from '../capabilities/agent-reach.js';
26
26
  assert.ok(r.evidence.includes('[agent-reach:web.read@'), '证据链');
27
27
  assert.match(r.evidence, /\[agent-reach:web\.read@2\d{3}-\d{2}-\d{2}T/, '证据链带 ISO 时间戳');
28
28
  assert.ok(typeof r.content === 'string' && r.content.length > 0, 'content 非空');
29
- assert.equal(r.title, 'Example Domain', `Title 提取,实际 ${r.title}`);
29
+ assert.ok(typeof r.title === 'string' && r.title.length > 0, `Title 提取,实际 ${r.title}`);
30
30
  console.log(`3. readUrl example.com → ok (title=${r.title}) OK`);
31
31
  } else {
32
32
  assert.equal(r.via, 'r.jina.ai-error');
@@ -17,7 +17,7 @@ import { reach, reachStatus } from '../capabilities/agent-reach.js';
17
17
  });
18
18
  assert.equal(r.verdict, 'found');
19
19
  assert.ok(r.evidence.includes('[agent-reach:web.read@'), '证据链 [agent-reach:web.read@ts]');
20
- assert.ok(typeof r.data === 'string' && r.data.includes('Example Domain'), 'content Example Domain');
20
+ assert.ok(typeof r.data === 'string' && r.data.length > 200, 'content 非空 markdown');
21
21
  console.log(`2. web.read example.com → found (${r.latencyMs}ms) OK`);
22
22
  }{
23
23
  const r = await reach({
@@ -66,7 +66,23 @@ await writeFile(fallback, JSON.stringify({
66
66
  }
67
67
  ]
68
68
  }));
69
- const r4 = await searchHotels({
69
+ {
70
+ const { writeFileSync, chmodSync } = await import('node:fs');
71
+ const echoBin = join(tmp, 'hbcli-echo');
72
+ writeFileSync(echoBin, '#!/bin/sh\necho "ARGS:$@"; echo \'{}\' ');
73
+ chmodSync(echoBin, 0o755);
74
+ const re = await searchHotels({
75
+ destination: '普吉',
76
+ adults: 2
77
+ }, {
78
+ hbcliBin: echoBin
79
+ });
80
+ const argLine = JSON.stringify(re);
81
+ if (!argLine.includes('destination-name') || !argLine.includes('room-occupancies')) {
82
+ throw new Error(`FAIL: v0.3.0 旗标未对齐,实际 ${argLine.slice(0, 200)}`);
83
+ }
84
+ console.log('5. v0.3.0 旗标(--destination-name/--room-occupancies)对齐 OK');
85
+ }const r4 = await searchHotels({
70
86
  destination: '普吉'
71
87
  }, {
72
88
  hbcliBin: failBin2,
@@ -79,7 +95,7 @@ await rm(tmp, {
79
95
  recursive: true,
80
96
  force: true
81
97
  });
82
- console.log('HBCLI TESTS: 4/4 OK (happy path / error path / no-binary / fallback)');
98
+ console.log('HBCLI TESTS: 5/5 OK (happy / error / no-binary / fallback / v0.3.0 旗标回归)');
83
99
 
84
100
 
85
101
  //# sourceURL=/Users/bytedance/work/gotry/ts/scripts/hbcli-tests.ts