@danceiny/gotry 0.0.1-rc.17 → 0.0.1-rc.19
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 +185 -148
- package/README.zh-CN.md +195 -154
- package/bin/gotry-bootstrap.js +246 -26
- package/bin/gotry-inner.js +68 -38
- package/bin/gotry-runtime-resolution.d.ts +1 -1
- package/bin/gotry-runtime-resolution.js +7 -8
- package/cordis.gotry-patch.yml +15 -5
- package/data/stations-12306-verify.json +524 -0
- package/dist/capabilities/agent-reach.js +4 -4
- package/dist/capabilities/channel-health.js +103 -0
- package/dist/capabilities/channel-registry.js +213 -0
- package/dist/capabilities/doctor.js +285 -0
- package/dist/capabilities/effect.js +178 -5
- package/dist/capabilities/flyai.js +13 -0
- package/dist/capabilities/session/adapters/ctrip-hotel.js +207 -0
- package/dist/capabilities/session/adapters/rail-12306.js +285 -0
- package/dist/capabilities/session/extension-bridge.js +14 -1
- package/dist/capabilities/session/extension-channel.js +4 -2
- package/dist/capabilities/session/extension-distribution.js +1 -1
- package/dist/capabilities/session-search.js +311 -0
- package/dist/capabilities/visa-policy.js +126 -0
- package/dist/scripts/agent-planning-turn-deadline-e2e.js +304 -0
- package/dist/scripts/agent-planning-turn-deadline-tests.js +281 -0
- package/dist/scripts/benchmark-environment-bridge-e2e.js +5 -10
- package/dist/scripts/benchmark-environment-bridge-tests.js +148 -142
- package/dist/scripts/booking-copilot-availability-ledger-binding-tests.js +26 -26
- package/dist/scripts/{booking-copilot-availability-policy-v2-tests.js → booking-copilot-availability-policy-tests.js} +115 -115
- package/dist/scripts/booking-copilot-crossrepo-fixture-server.js +41 -1
- package/dist/scripts/booking-copilot-dsh-core-proof-tests.js +75 -120
- package/dist/scripts/booking-copilot-dsh-planner-proof-tests.js +370 -61
- package/dist/scripts/booking-copilot-dsh-plugin-proof-tests.js +2 -2
- package/dist/scripts/booking-copilot-event-sequence-concurrency-proof-tests.js +7 -3
- package/dist/scripts/booking-copilot-gap-code-contract-proof-tests.js +15 -9
- package/dist/scripts/booking-copilot-operation-ledger-concurrency-proof-tests.js +19 -11
- package/dist/scripts/booking-copilot-receipt-ledger-concurrency-proof-tests.js +16 -52
- package/dist/scripts/booking-copilot-runtime-proof-tests.js +3953 -155
- package/dist/scripts/booking-copilot-server-proof-tests.js +58 -23
- package/dist/scripts/booking-copilot-startup-proof-tests.js +10 -119
- package/dist/scripts/booking-surface-contract-proof-tests.js +1154 -326
- package/dist/scripts/bootstrap-tests.js +115 -44
- package/dist/scripts/build-metrics-report.js +339 -0
- package/dist/scripts/channel-probe-tests.js +148 -0
- package/dist/scripts/channel-probe.js +252 -0
- package/dist/scripts/channel-registry-tests.js +262 -0
- package/dist/scripts/doctor-tests.js +121 -0
- package/dist/scripts/effect-tests.js +274 -1
- package/dist/scripts/extension-tests.js +129 -11
- package/dist/scripts/fact-gate-tests.js +91 -2
- package/dist/scripts/flyai-tests.js +17 -1
- package/dist/scripts/hbcli-e2e-tests.js +1 -3
- package/dist/scripts/metrics-report-tests.js +288 -0
- package/dist/scripts/persona-surface-guard-tests.js +19 -0
- package/dist/scripts/session-tests.js +401 -1
- package/dist/scripts/smoke.js +100 -83
- package/dist/scripts/turn-handoff-collect-tests.js +216 -0
- package/dist/scripts/turn-handoff-collect.js +189 -0
- package/dist/scripts/turn-policy-tests.js +56 -0
- package/dist/scripts/typed-contract-canary.js +261 -0
- package/dist/scripts/visa-policy-tests.js +75 -0
- package/dist/scripts/wish-channel-gate-tests.js +111 -0
- package/dist/src/artifact-gate.js +91 -3
- package/dist/src/benchmark-agent-conformance.js +7 -4
- package/dist/src/benchmark-environment-bridge.js +68 -7
- package/dist/src/bookable-facts.js +62 -3
- package/dist/src/booking-surface/{availability-policy-v2.js → availability-policy.js} +89 -89
- package/dist/src/booking-surface/canonical-schema.js +8 -22
- package/dist/src/booking-surface/contracts.js +70 -19
- package/dist/src/booking-surface/dsh-planner.js +310 -177
- package/dist/src/booking-surface/dsh-plugin.js +3 -3
- package/dist/src/booking-surface/index.js +1 -5
- package/dist/src/booking-surface/profile.js +1 -1
- package/dist/src/booking-surface/runtime.js +1682 -247
- package/dist/src/booking-surface/server.js +371 -185
- package/dist/src/booking-surface/startup.js +17 -27
- package/dist/src/booking-surface/validation.js +270 -760
- package/dist/src/index.js +735 -149
- package/dist/src/turn-deadline.js +292 -0
- package/dist/src/turn-policy.js +146 -0
- package/dist/src/wish-pool.js +5 -0
- package/extension/background.js +23 -4
- package/extension/content-main.js +47 -9
- package/extension/manifest.json +27 -9
- package/package.json +6 -26
- package/schemas/booking.surface.schema.json +2593 -0
- package/ts/capabilities/agent-reach.ts +4 -4
- package/ts/capabilities/flyai.ts +21 -3
- package/ts/capabilities/session/action-cache.ts +1 -1
- package/ts/capabilities/session/adapters/ctrip-hotel.ts +238 -0
- package/ts/capabilities/session/adapters/rail-12306.ts +293 -0
- package/ts/capabilities/session/extension-bridge.ts +32 -2
- package/ts/capabilities/session/extension-channel.ts +9 -8
- package/ts/capabilities/session/extension-distribution.ts +3 -1
- package/ts/capabilities/session/health-watch.ts +1 -1
- package/ts/capabilities/session/read-guard.ts +1 -1
- package/ts/capabilities/session/wizard.ts +1 -1
- package/ts/capabilities/session-search.ts +323 -1
- package/ts/package.json +1 -0
- package/ts/scripts/turn-handoff-collect.ts +178 -0
- package/ts/src/artifact-gate.ts +82 -2
- package/ts/src/benchmark-agent-conformance.ts +14 -6
- package/ts/src/benchmark-environment-bridge.ts +29 -10
- package/ts/src/bookable-facts.ts +109 -5
- package/ts/src/booking-saga.ts +1 -1
- package/ts/src/booking-surface/{availability-policy-v2.ts → availability-policy.ts} +124 -125
- package/ts/src/booking-surface/canonical-schema.js +8 -22
- package/ts/src/booking-surface/contracts.ts +278 -239
- package/ts/src/booking-surface/dsh-planner.ts +333 -194
- package/ts/src/booking-surface/dsh-plugin.js +3 -3
- package/ts/src/booking-surface/index.ts +1 -5
- package/ts/src/booking-surface/profile.ts +11 -11
- package/ts/src/booking-surface/runtime.ts +1351 -351
- package/ts/src/booking-surface/server.ts +282 -175
- package/ts/src/booking-surface/startup.ts +36 -48
- package/ts/src/booking-surface/validation.ts +194 -442
- package/ts/src/contracts.ts +1 -1
- package/ts/src/index.ts +489 -171
- package/ts/src/loop.ts +1 -1
- package/ts/src/state-ledger.ts +1 -1
- package/ts/src/turn-deadline.ts +361 -0
- package/ts/src/turn-policy.ts +154 -0
- package/ts/src/wish-pool.ts +17 -4
- package/dist/scripts/agent-planning-budget-e2e.js +0 -227
- package/dist/scripts/agent-planning-budget-tests.js +0 -173
- package/dist/scripts/booking-copilot-v2-runtime-proof-tests.js +0 -3935
- package/dist/scripts/booking-surface-v2-contract-proof-tests.js +0 -1174
- package/dist/src/booking-surface/contracts-v2.js +0 -89
- package/dist/src/booking-surface/runtime-v2.js +0 -1771
- package/dist/src/booking-surface/server-v2.js +0 -334
- package/dist/src/booking-surface/validation-v2.js +0 -319
- package/dist/src/tool-budget.js +0 -136
- package/schemas/booking.surface.v1.schema.json +0 -927
- package/schemas/booking.surface.v2.schema.json +0 -61
- package/ts/src/booking-surface/contracts-v2.ts +0 -118
- package/ts/src/booking-surface/runtime-v2.ts +0 -1466
- package/ts/src/booking-surface/server-v2.ts +0 -247
- package/ts/src/booking-surface/validation-v2.ts +0 -205
- package/ts/src/tool-budget.ts +0 -165
package/ts/src/artifact-gate.ts
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
import {
|
|
17
17
|
flightClaimVerdict,
|
|
18
|
+
hotelClaimVerdict,
|
|
18
19
|
itineraryInvariants,
|
|
19
20
|
latestFactsForRouteDate,
|
|
20
21
|
type BookableFact,
|
|
@@ -62,12 +63,24 @@ export interface ExtractedAirportClaim {
|
|
|
62
63
|
city: string
|
|
63
64
|
}
|
|
64
65
|
|
|
66
|
+
/** 酒店 claim(D-26):断言「有房/可订/已核验」的行——目的地与档期缺失时 fail-closed */
|
|
67
|
+
export interface ExtractedHotelClaim {
|
|
68
|
+
line: number
|
|
69
|
+
text: string
|
|
70
|
+
destination?: string
|
|
71
|
+
check_in?: string
|
|
72
|
+
check_out?: string
|
|
73
|
+
}
|
|
74
|
+
|
|
65
75
|
export interface ExtractedClaims {
|
|
66
76
|
flights: ExtractedFlightClaim[]
|
|
67
77
|
policies: ExtractedPolicyClaim[]
|
|
68
78
|
airports: ExtractedAirportClaim[]
|
|
69
79
|
/** 含「直飞」断言且可解析航线的行(通用直飞规则:该日无任何在架直飞即违例) */
|
|
70
80
|
direct_lines: Array<{ line: number; text: string; origin?: string; destination?: string; date?: string }>
|
|
81
|
+
hotels: ExtractedHotelClaim[]
|
|
82
|
+
/** 渲染原语锚点(fact:<id>):命中行只走确定性回溯,启发式让位 */
|
|
83
|
+
anchors: Map<number, string>
|
|
71
84
|
}
|
|
72
85
|
|
|
73
86
|
/** 航班号:2 位承运码 + 3-4 位数字(UO784/EK328/MF1538/CZ8582/9C8781/FD597) */
|
|
@@ -94,6 +107,14 @@ function hasDirectAssertion(line: string): boolean {
|
|
|
94
107
|
|
|
95
108
|
interface SectionCtx { origin?: string; destination?: string; date?: string }
|
|
96
109
|
|
|
110
|
+
/** 行内第一个命中词表的城市中文名(酒店 claim 的目的地键;无命中=undefined) */
|
|
111
|
+
function occ1(line: string, map: AirlineAirportMap): string | undefined {
|
|
112
|
+
for (const city of Object.keys(map.city_alias)) {
|
|
113
|
+
if (line.includes(city)) return city
|
|
114
|
+
}
|
|
115
|
+
return undefined
|
|
116
|
+
}
|
|
117
|
+
|
|
97
118
|
/** 从文本(小节标题或行)解析航线上下文:箭头紧邻城市对(方向最可靠)优先,否则按出现位置取前两个;日期 M.D 或 MM-DD */
|
|
98
119
|
function routeCtxOf(text: string, map: AirlineAirportMap, defaultYear?: number): SectionCtx {
|
|
99
120
|
const occ: Array<{ city: string; iata: string; pos: number }> = []
|
|
@@ -140,12 +161,19 @@ function routeCtxOf(text: string, map: AirlineAirportMap, defaultYear?: number):
|
|
|
140
161
|
* 行内自带上下文优先。默认年份由 trip window 提供(远期行程的年份不含糊)。
|
|
141
162
|
*/
|
|
142
163
|
export function extractClaims(markdown: string, map: AirlineAirportMap, opts?: { trip_year?: number }): ExtractedClaims {
|
|
143
|
-
const claims: ExtractedClaims = { flights: [], policies: [], airports: [], direct_lines: [] }
|
|
164
|
+
const claims: ExtractedClaims = { flights: [], policies: [], airports: [], direct_lines: [], hotels: [], anchors: new Map() }
|
|
144
165
|
const lines = markdown.split('\n')
|
|
145
166
|
let section: SectionCtx = {}
|
|
167
|
+
// 渲染原语锚点(单向生成):带 fact:<id> 的行只走锚点确定性回溯,启发式抽取让位
|
|
168
|
+
const ANCHOR = /fact:([0-9a-f]{16})/
|
|
146
169
|
for (let i = 0; i < lines.length; i++) {
|
|
147
170
|
const line = lines[i]!
|
|
148
171
|
const lineNo = i + 1
|
|
172
|
+
const anchorMatch = line.match(ANCHOR)
|
|
173
|
+
if (anchorMatch) {
|
|
174
|
+
claims.anchors.set(lineNo, anchorMatch[1]!.toLowerCase())
|
|
175
|
+
continue
|
|
176
|
+
}
|
|
149
177
|
if (/^#{1,4}\s/.test(line)) {
|
|
150
178
|
const ctx = routeCtxOf(line, map, opts?.trip_year)
|
|
151
179
|
section = { origin: ctx.origin, destination: ctx.destination, date: ctx.date ?? section.date }
|
|
@@ -179,6 +207,19 @@ export function extractClaims(markdown: string, map: AirlineAirportMap, opts?: {
|
|
|
179
207
|
if (POLICY_WORD.test(line)) {
|
|
180
208
|
claims.policies.push({ line: lineNo, text: line.trim().slice(0, 160), has_as_of: AS_OF_WORD.test(line) })
|
|
181
209
|
}
|
|
210
|
+
// 酒店 claim(D-26):断言可住性(有房/可订/已核验/✓)的行——目的地与档期缺失时 fail-closed
|
|
211
|
+
const HOTEL_ASSERT = /酒店|住宿|客栈|民宿/
|
|
212
|
+
const HOTEL_BOOKABLE = /有房|可订|在架|已核验|已验证|可住|✓|✅/
|
|
213
|
+
if (HOTEL_ASSERT.test(line) && HOTEL_BOOKABLE.test(line)) {
|
|
214
|
+
const stays = [...line.matchAll(/(?<!\d)(\d{4}-\d{2}-\d{2})(?!\d)/g)].map(m => m[1]!)
|
|
215
|
+
claims.hotels.push({
|
|
216
|
+
line: lineNo,
|
|
217
|
+
text: line.trim().slice(0, 160),
|
|
218
|
+
destination: occ1(line, map),
|
|
219
|
+
check_in: stays[0],
|
|
220
|
+
check_out: stays[1],
|
|
221
|
+
})
|
|
222
|
+
}
|
|
182
223
|
// 航司→机场映射 claim:同一行出现承运码 + 机场码,且该城是双机场映射面
|
|
183
224
|
for (const [city, carriers] of Object.entries(map.carrier_airport)) {
|
|
184
225
|
const airports = [...new Set(Object.values(carriers))]
|
|
@@ -207,6 +248,8 @@ export type GateViolationKind =
|
|
|
207
248
|
| 'legs_inconsistent' // flight O&D 段与 legs 口径不一致
|
|
208
249
|
| 'budget_floor_inconsistent' // 预算下限话术低于分项最低合计
|
|
209
250
|
| 'date_order' // 住宿/段日期越窗或倒挂
|
|
251
|
+
| 'unverifiable_hotel_claim' // 酒店可住断言无 exact-date 事实回溯(D-26)
|
|
252
|
+
| 'fact_anchor_unknown' // 渲染锚点 fact:<id> 在注册表不存在(锚点被手改/伪造)
|
|
210
253
|
|
|
211
254
|
export interface GateViolation {
|
|
212
255
|
kind: GateViolationKind
|
|
@@ -242,6 +285,43 @@ export function gateArtifact(
|
|
|
242
285
|
const violations: GateViolation[] = []
|
|
243
286
|
let traceable = 0
|
|
244
287
|
|
|
288
|
+
// 渲染锚点优先(issue #118 单向生成):带 fact:<id> 的行确定性回溯注册表——
|
|
289
|
+
// 锚点在=按事实 bookability 判;锚点不存在=手改/伪造,直接违例。启发式对锚点行让位。
|
|
290
|
+
for (const [lineNo, factId] of claims.anchors) {
|
|
291
|
+
const f = facts.find(x => x.fact_id === factId)
|
|
292
|
+
if (!f) {
|
|
293
|
+
violations.push({ kind: 'fact_anchor_unknown', line: lineNo, detail: `渲染锚点 fact:${factId} 不在事实注册表——锚点被手改或伪造,产物不可信` })
|
|
294
|
+
continue
|
|
295
|
+
}
|
|
296
|
+
if (f.kind !== 'policy' && f.bookability === 'unavailable_exact_date') {
|
|
297
|
+
violations.push({ kind: 'not_in_source', line: lineNo, detail: `锚点事实为 exact-date 负事实(${(f as { fetched_at?: string }).fetched_at ?? ''})——负事实对应的可住/可订断言不得出现` })
|
|
298
|
+
continue
|
|
299
|
+
}
|
|
300
|
+
traceable++
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// 酒店 claim(D-26):目的地+档期回溯酒店事实;无事实 fail-closed
|
|
304
|
+
for (const c of claims.hotels) {
|
|
305
|
+
if (claims.anchors.has(c.line)) continue
|
|
306
|
+
if (!c.destination) {
|
|
307
|
+
violations.push({ kind: 'unverifiable_hotel_claim', line: c.line, detail: `酒店可住断言缺目的地上下文,无法回溯 exact-date 事实——fail closed 按未核验处理` })
|
|
308
|
+
continue
|
|
309
|
+
}
|
|
310
|
+
const r = hotelClaimVerdict(facts, { destination: c.destination, check_in: c.check_in, check_out: c.check_out })
|
|
311
|
+
if (r.verdict === 'traceable') {
|
|
312
|
+
traceable++
|
|
313
|
+
continue
|
|
314
|
+
}
|
|
315
|
+
if (r.verdict === 'unverified') {
|
|
316
|
+
violations.push({ kind: 'unverifiable_hotel_claim', line: c.line, detail: r.reason })
|
|
317
|
+
} else {
|
|
318
|
+
violations.push({ kind: 'not_in_source', line: c.line, detail: r.reason })
|
|
319
|
+
}
|
|
320
|
+
if (CHECK_MARK.test(c.text)) {
|
|
321
|
+
violations.push({ kind: 'unconditional_check', line: c.line, detail: '对未核验酒店使用无条件 ✓/✅——只有 bookable_exact_date 才允许确定性标记' })
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
245
325
|
for (const c of claims.flights) {
|
|
246
326
|
if (c.carrier_only) {
|
|
247
327
|
// 承运级直飞断言:注册表该 route+date 须存在该承运的 bookable 直飞
|
|
@@ -334,7 +414,7 @@ export function gateArtifact(
|
|
|
334
414
|
if (!line.includes('联程')) return
|
|
335
415
|
const nos = [...line.matchAll(FLIGHT_NO)].map(m => m[1]!.toUpperCase())
|
|
336
416
|
const matched = facts.filter((f): f is Extract<BookableFact, { kind: 'flight' | 'train' }> =>
|
|
337
|
-
f.kind
|
|
417
|
+
(f.kind === 'flight' || f.kind === 'train') && nos.includes(f.flight_no.toUpperCase()))
|
|
338
418
|
const allProtected = nos.length > 0
|
|
339
419
|
&& matched.length === nos.length
|
|
340
420
|
&& matched.every(f => f.protected_connection === true)
|
|
@@ -89,12 +89,19 @@ function invalidTerminal(): TerminalOutputValue {
|
|
|
89
89
|
return { ok: false, error: 'invalid_terminal_output' }
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
+
/** 剥离推理模型的思考标签对(如 MiniMax/DeepSeek 的 <think>…</think>):当代模型
|
|
93
|
+
* 即便被明令「只回终态包」也会以思考块前缀输出——不剥则严格终态门对推理模型
|
|
94
|
+
* 结构性失效(Round 9 治理面实测)。只剥配对的推理标签本体,其余前后缀仍 fail-closed。 */
|
|
95
|
+
function stripReasoningBlocks(raw: string): string {
|
|
96
|
+
return raw.replace(/<think>[\s\S]*?<\/think>\s*/gi, '')
|
|
97
|
+
}
|
|
98
|
+
|
|
92
99
|
/** Parse exactly one configured tag pair containing one JSON object. */
|
|
93
100
|
export function parseBenchmarkTerminal(raw: string, config: TerminalOutputConfig): TerminalOutputValue {
|
|
94
101
|
if (!validateTerminalOutputConfig(config)) return invalidTerminal()
|
|
95
102
|
if (Buffer.byteLength(raw, 'utf8') > config.max_bytes) return invalidTerminal()
|
|
96
103
|
|
|
97
|
-
const trimmed = raw.trim()
|
|
104
|
+
const trimmed = stripReasoningBlocks(raw).trim()
|
|
98
105
|
const opening = `<${config.tag}>`
|
|
99
106
|
const closing = `</${config.tag}>`
|
|
100
107
|
if (!trimmed.startsWith(opening) || !trimmed.endsWith(closing)) return invalidTerminal()
|
|
@@ -227,10 +234,11 @@ export function createBenchmarkAgentConformance(projection: BenchmarkBridgeProje
|
|
|
227
234
|
}
|
|
228
235
|
if (typeof event.data.callId !== 'string') return
|
|
229
236
|
const args = parseToolArguments(event.data.arguments)
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
&&
|
|
237
|
+
// Round 8(issue #100/#102):bridge 工具 typed 泛化后,模型出参为平铺形态
|
|
238
|
+
const flat = args && plainObject(args) ? args : undefined
|
|
239
|
+
if (flat?.action === 'call'
|
|
240
|
+
&& typeof flat.tool === 'string'
|
|
241
|
+
&& projection.allowedTools.includes(flat.tool)) {
|
|
234
242
|
state.validCallIds.add(event.data.callId)
|
|
235
243
|
}
|
|
236
244
|
return
|
|
@@ -338,7 +346,7 @@ function systemSection(projection: BenchmarkBridgeProjection): { name: string; t
|
|
|
338
346
|
text: [
|
|
339
347
|
'Benchmark execution contract:',
|
|
340
348
|
`- Translate every task instruction to use a CLI, shell, Python, or agent_env.cli into the native tool ${projection.toolName}; do not merely describe the intended command.`,
|
|
341
|
-
`- Call it with exactly {"
|
|
349
|
+
`- Call it with exactly {"action":"call","tool":"<one of: ${allowed}>","arguments":{...}}.`,
|
|
342
350
|
'- action:"tools" is discovery only and does not satisfy the required environment call.',
|
|
343
351
|
`- After a successful tool result, reply only <${projection.terminal.tag}>{...one JSON object...}</${projection.terminal.tag}> with no prose or code fence.`,
|
|
344
352
|
'- If a terminal-format correction arrives, reuse the existing result and do not call the tool again.',
|
|
@@ -244,6 +244,21 @@ function serializedArguments(value: Record<string, unknown>): { json: string; re
|
|
|
244
244
|
}
|
|
245
245
|
}
|
|
246
246
|
|
|
247
|
+
/** 可恢复 domain-error 契约(Round 8,issue #100/#102):封闭词表 + 逐码可恢复性与补救指引。
|
|
248
|
+
* 模型经 action=errors 拉取全表;失败返回的扁平形状保持不变(benchmark 诊断面依赖)。 */
|
|
249
|
+
export const BRIDGE_ERROR_CONTRACT: Record<string, { recoverable: boolean; remedy: string }> = Object.freeze({
|
|
250
|
+
invalid_action: { recoverable: true, remedy: 'action 只允许 tools 或 call' },
|
|
251
|
+
disallowed_tool: { recoverable: true, remedy: '先 action=tools 列出 allowed_tools 清单,再从清单内选工具' },
|
|
252
|
+
invalid_arguments: { recoverable: true, remedy: 'arguments 必须是可序列化 JSON 对象(≤64KB、深度≤12);收窄后重试' },
|
|
253
|
+
timed_out: { recoverable: true, remedy: '上游超时;可原样重试一次或换其他工具' },
|
|
254
|
+
output_truncated: { recoverable: true, remedy: '输出超出上限;让被调工具收窄查询范围后重试' },
|
|
255
|
+
invalid_json: { recoverable: true, remedy: '上游输出不是合法 JSON;重试一次或换工具' },
|
|
256
|
+
invalid_output: { recoverable: true, remedy: '上游输出结构不符(如原始字符串);重试或换工具' },
|
|
257
|
+
runner_failed: { recoverable: true, remedy: '上游进程非零退出;可重试一次,持续失败换工具' },
|
|
258
|
+
spawn_failed: { recoverable: false, remedy: '可执行文件不可用——环境问题,调用方不可恢复' },
|
|
259
|
+
forbidden_output: { recoverable: false, remedy: '输出含未授权键(策略边界)——不可恢复,换工具或放弃' },
|
|
260
|
+
})
|
|
261
|
+
|
|
247
262
|
/** Register the opt-in model-facing bridge. */
|
|
248
263
|
export function registerBenchmarkEnvironmentBridge(
|
|
249
264
|
path: string,
|
|
@@ -256,22 +271,26 @@ export function registerBenchmarkEnvironmentBridge(
|
|
|
256
271
|
|
|
257
272
|
register(defineTool({
|
|
258
273
|
name: 'gotry_benchmark_environment',
|
|
259
|
-
description: 'When a prompt asks to run agent_env.cli, use action=call here with the mapped tool; arbitrary shell is not exposed.'
|
|
274
|
+
description: 'When a prompt asks to run agent_env.cli, use action=call here with the mapped tool; arbitrary shell is not exposed. '
|
|
275
|
+
+ 'Recoverable domain-error contract: every failure returns { ok:false, error:<code>, ... } from a closed vocabulary; '
|
|
276
|
+
+ 'call action=errors to fetch the per-code recoverable flag and remedy before retrying.',
|
|
277
|
+
// Round 8(issue #100/#102):generic typed schema——模型可见逐字段契约(与产品面 D-30 同刀法);
|
|
278
|
+
// arguments 保持 additionalProperties:true(被调工具的参数面由其自身契约定义)
|
|
260
279
|
parameters: {
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
description: '{ action: "tools" | "call", tool?: string, arguments?: object }',
|
|
265
|
-
},
|
|
280
|
+
action: { type: 'string', enum: ['tools', 'call', 'errors'], required: true, description: 'tools=列出可调工具;call=执行被映射工具;errors=拉取可恢复错误契约表' },
|
|
281
|
+
tool: { type: 'string', description: '被调工具名(必须在 allowed_tools 清单内;action=call 时必填)' },
|
|
282
|
+
arguments: { type: 'object', additionalProperties: true, description: '传给被调工具的参数对象(action=call 时可选;≤64KB、深度≤12)' },
|
|
266
283
|
},
|
|
267
284
|
output: {
|
|
268
285
|
schema: { type: 'json' },
|
|
269
286
|
render: (_args, value) => [{ type: 'text', text: JSON.stringify(value) }],
|
|
270
287
|
},
|
|
271
|
-
async execute(args
|
|
272
|
-
const
|
|
273
|
-
if (
|
|
274
|
-
if (
|
|
288
|
+
async execute(args): Promise<Record<string, never>> {
|
|
289
|
+
const action = args?.action
|
|
290
|
+
if (action === 'errors') return jsonObject({ ok: true, errors: BRIDGE_ERROR_CONTRACT })
|
|
291
|
+
if (action === 'tools') return jsonObject({ ok: true, tools: bridge.allowed_tools })
|
|
292
|
+
if (action !== 'call') return jsonObject({ ok: false, error: 'invalid_action' })
|
|
293
|
+
const query = args as unknown as { tool?: unknown; arguments?: unknown }
|
|
275
294
|
if (typeof query.tool !== 'string' || !bridge.allowed_tools.includes(query.tool)) {
|
|
276
295
|
return jsonObject({ ok: false, error: 'disallowed_tool' })
|
|
277
296
|
}
|
package/ts/src/bookable-facts.ts
CHANGED
|
@@ -89,7 +89,34 @@ export interface PolicyFact {
|
|
|
89
89
|
review_by?: string
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
-
export type BookableFact = FlightFact | PolicyFact
|
|
92
|
+
export type BookableFact = FlightFact | PolicyFact | HotelFact
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* 酒店事实(D-26,issue #118):exact-date 酒店检索(hit/miss)的唯一事实形态。
|
|
96
|
+
* 纪律与机/火同源:①未定档期的摸底检索不落账(无 exact-date 语义);
|
|
97
|
+
* ②needs-setup/error 是传输失败,永不落负事实;③价格上游打码(¥7xx)——
|
|
98
|
+
* 本事实**不落数字价**,只记在架家数,真实价以落地页为准(打码价保真纪律)。
|
|
99
|
+
*/
|
|
100
|
+
export interface HotelFact {
|
|
101
|
+
schema: typeof BOOKABLE_FACT_SCHEMA
|
|
102
|
+
/** 语义派生 id:sha(destination|checkIn|checkOut|query_id)——同查询重放幂等 */
|
|
103
|
+
fact_id: string
|
|
104
|
+
kind: 'hotel'
|
|
105
|
+
/** 目的地中文(flyai/session 的 to;闸侧 claim 匹配键) */
|
|
106
|
+
destination: string
|
|
107
|
+
check_in?: string // YYYY-MM-DD
|
|
108
|
+
check_out?: string // YYYY-MM-DD
|
|
109
|
+
verdict: 'hit' | 'miss'
|
|
110
|
+
bookability: Bookability
|
|
111
|
+
/** 在架条数(hit 时;价格打码不落数字价) */
|
|
112
|
+
options_masked: number
|
|
113
|
+
source: string // flyai-hotel / session:ctrip-hotel
|
|
114
|
+
/** 可重放查询 id(如 flyai:hotel:大理:2026-10-01_2026-10-03) */
|
|
115
|
+
query_id: string
|
|
116
|
+
fetched_at: string
|
|
117
|
+
as_of: string
|
|
118
|
+
tier: EvidenceTier
|
|
119
|
+
}
|
|
93
120
|
|
|
94
121
|
// ---------------------------------------------------------------------------
|
|
95
122
|
// id 与查询词(可重放:query_id 即重放凭证)
|
|
@@ -133,7 +160,8 @@ export interface FlyaiLikeOption {
|
|
|
133
160
|
}
|
|
134
161
|
|
|
135
162
|
export interface FlyaiLikeResult {
|
|
136
|
-
|
|
163
|
+
/** needs-setup(试用额度达限)同 error:无结论不是证据,不落事实 */
|
|
164
|
+
verdict: 'hit' | 'miss' | 'error' | 'needs-setup'
|
|
137
165
|
options?: FlyaiLikeOption[]
|
|
138
166
|
evidence?: string
|
|
139
167
|
}
|
|
@@ -190,7 +218,7 @@ export function factsFromFlyai(q: { kind: 'flight' | 'train'; origin: string; de
|
|
|
190
218
|
const queryId = makeQueryId(source, q.kind, q.origin, q.destination, q.date)
|
|
191
219
|
const origin = iataOf(alias, q.origin)
|
|
192
220
|
const destination = iataOf(alias, q.destination)
|
|
193
|
-
if (r.verdict === 'error') return []
|
|
221
|
+
if (r.verdict === 'error' || r.verdict === 'needs-setup') return []
|
|
194
222
|
if (r.verdict === 'miss') return [negativeFact(queryId, q.kind, q.origin, q.destination, q.date, source, fetchedAt, alias)]
|
|
195
223
|
const facts: FlightFact[] = []
|
|
196
224
|
for (const o of r.options ?? []) {
|
|
@@ -278,7 +306,7 @@ export function dedupeFacts<T extends BookableFact>(facts: T[]): T[] {
|
|
|
278
306
|
/** route+date 的最新查询结论(同日多次查询:最新一次为准,避免「昨天 miss 今天 hit」陈旧否定) */
|
|
279
307
|
export function latestFactsForRouteDate(facts: BookableFact[], origin: string, destination: string, date: string): FlightFact[] {
|
|
280
308
|
const hits = facts.filter((f): f is FlightFact =>
|
|
281
|
-
f.kind
|
|
309
|
+
(f.kind === 'flight' || f.kind === 'train')
|
|
282
310
|
&& f.route.origin === origin && f.route.destination === destination && f.date === date)
|
|
283
311
|
const latestQueryTs = new Map<string, string>()
|
|
284
312
|
for (const f of hits) {
|
|
@@ -310,7 +338,7 @@ export interface FlightClaim {
|
|
|
310
338
|
export function flightClaimVerdict(facts: BookableFact[], claim: FlightClaim): { verdict: FlightClaimVerdict; fact?: FlightFact; reason: string } {
|
|
311
339
|
const no = claim.flight_no.toUpperCase().replace(/\s+/g, '')
|
|
312
340
|
const sameNo = facts.filter((f): f is FlightFact =>
|
|
313
|
-
f.kind
|
|
341
|
+
(f.kind === 'flight' || f.kind === 'train') && f.flight_no.toUpperCase() === no && f.bookability === 'bookable_exact_date')
|
|
314
342
|
const routeMatched = sameNo.filter(f =>
|
|
315
343
|
(!claim.origin || f.route.origin === claim.origin || f.route.origin_airport === claim.origin)
|
|
316
344
|
&& (!claim.destination || f.route.destination === claim.destination || f.route.dest_airport === claim.destination)
|
|
@@ -502,6 +530,12 @@ export function defaultReviewBy(tripDate: string): string {
|
|
|
502
530
|
* 永不输出无条件 ✓;证据链(source/query_id/fetched_at)随行必达。
|
|
503
531
|
*/
|
|
504
532
|
export function renderFlightFact(f: FlightFact): string {
|
|
533
|
+
return renderFlightFactInner(f) + ` <!-- fact:${f.fact_id} -->`
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
function renderFlightFactInner(f: FlightFact): string {
|
|
537
|
+
// 单向生成锚点(D-26):每条事实行内嵌 fact_id,闸侧锚点匹配 = 确定性回溯
|
|
538
|
+
const anchor = ` <!-- fact:${f.fact_id} -->`
|
|
505
539
|
const route = `${f.route.origin}→${f.route.destination}`
|
|
506
540
|
const evidence = `[${f.source}@${f.fetched_at} #${f.query_id}]`
|
|
507
541
|
if (f.bookability === 'unavailable_exact_date') {
|
|
@@ -555,3 +589,73 @@ export function renderNightsLine(it: ItineraryFacts): string {
|
|
|
555
589
|
const flightLegs = it.od_segments.filter(s => s.mode === 'flight').reduce((acc, s) => acc + s.legs, 0)
|
|
556
590
|
return `共 ${daysBetween(it.trip_start, it.trip_end) + 1} 天 ${totalNights} 晚 = ${hotelNights} 个酒店夜 + ${it.onboard_nights} 个机上夜;航班按 O&D ${flightOd} 段(共 ${flightLegs} 个 flight legs)`
|
|
557
591
|
}
|
|
592
|
+
|
|
593
|
+
// ---------------------------------------------------------------------------
|
|
594
|
+
// 酒店事实(D-26,issue #118):exact-date 酒店检索 hit/miss 落账
|
|
595
|
+
// 纪律:摸底(未定档期)不落账(无 exact-date 语义);needs-setup/error 传输失败永不落负事实
|
|
596
|
+
// ---------------------------------------------------------------------------
|
|
597
|
+
|
|
598
|
+
export function factsFromHotel(input: {
|
|
599
|
+
source: string
|
|
600
|
+
destination: string
|
|
601
|
+
checkIn?: string
|
|
602
|
+
checkOut?: string
|
|
603
|
+
verdict: 'hit' | 'miss' | 'needs-setup' | 'error'
|
|
604
|
+
options: number
|
|
605
|
+
evidence: string
|
|
606
|
+
fetchedAt: string
|
|
607
|
+
}): HotelFact[] {
|
|
608
|
+
if (!input.checkIn || !input.checkOut) return []
|
|
609
|
+
if (input.verdict !== 'hit' && input.verdict !== 'miss') return []
|
|
610
|
+
const queryId = `hotel:${input.source}:${input.destination}:${input.checkIn}_${input.checkOut}`
|
|
611
|
+
return [{
|
|
612
|
+
schema: BOOKABLE_FACT_SCHEMA,
|
|
613
|
+
kind: 'hotel',
|
|
614
|
+
fact_id: makeFactId([input.destination, input.checkIn, input.checkOut, queryId]),
|
|
615
|
+
destination: input.destination,
|
|
616
|
+
check_in: input.checkIn,
|
|
617
|
+
check_out: input.checkOut,
|
|
618
|
+
verdict: input.verdict,
|
|
619
|
+
bookability: input.verdict === 'hit' ? 'bookable_exact_date' : 'unavailable_exact_date',
|
|
620
|
+
options_masked: input.options,
|
|
621
|
+
source: input.source,
|
|
622
|
+
query_id: queryId,
|
|
623
|
+
fetched_at: input.fetchedAt,
|
|
624
|
+
as_of: input.fetchedAt.slice(0, 10),
|
|
625
|
+
tier: 'live_inventory',
|
|
626
|
+
}]
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
/** 酒店 claim 回溯:同目的地+同档期的 exact-date 事实裁决(无事实=unverified,fail-closed) */
|
|
630
|
+
export function hotelClaimVerdict(
|
|
631
|
+
facts: BookableFact[],
|
|
632
|
+
claim: { destination: string; check_in?: string; check_out?: string },
|
|
633
|
+
): { verdict: 'traceable' | 'not_in_source' | 'unverified'; fact?: HotelFact; reason: string } {
|
|
634
|
+
const scoped = facts.filter((f): f is HotelFact =>
|
|
635
|
+
f.kind === 'hotel' && f.destination === claim.destination
|
|
636
|
+
&& (!claim.check_in || f.check_in === claim.check_in)
|
|
637
|
+
&& (!claim.check_out || f.check_out === claim.check_out))
|
|
638
|
+
if (scoped.length === 0) {
|
|
639
|
+
return {
|
|
640
|
+
verdict: 'unverified',
|
|
641
|
+
reason: `酒店 claim(${claim.destination}${claim.check_in ? ` ${claim.check_in}→${claim.check_out ?? '?'}` : ''})未经 exact-date 源核验——先检索再写结论`,
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
const hit = scoped.find(f => f.bookability === 'bookable_exact_date')
|
|
645
|
+
if (hit) return { verdict: 'traceable', fact: hit, reason: 'exact-date 命中' }
|
|
646
|
+
const latest = scoped[scoped.length - 1]!
|
|
647
|
+
return { verdict: 'not_in_source', fact: latest, reason: `该档期 exact-date 源正常返回 0 条(${latest.fetched_at})——不得写有房/可订` }
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
// ---------------------------------------------------------------------------
|
|
651
|
+
// 渲染原语(单向生成,issue #118):事实 → 行,锚点内嵌 fact_id;
|
|
652
|
+
// 产物经原语渲染即自带溯源锚,闸侧锚点匹配 = 确定性回溯,不靠行文猜测
|
|
653
|
+
// ---------------------------------------------------------------------------
|
|
654
|
+
|
|
655
|
+
|
|
656
|
+
export function renderHotelFact(f: HotelFact): string {
|
|
657
|
+
if (f.verdict !== 'hit') {
|
|
658
|
+
return `- ${f.destination} ${f.check_in}→${f.check_out}:exact-date 源 0 条在架(不可订) <!-- fact:${f.fact_id} -->`
|
|
659
|
+
}
|
|
660
|
+
return `- ${f.destination} ${f.check_in}→${f.check_out}:住宿已核验(在架 ${f.options_masked} 家;价格上游打码,真实价以落地页为准) <!-- fact:${f.fact_id} -->`
|
|
661
|
+
}
|
package/ts/src/booking-saga.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 预订 saga 状态机(booking_saga_fsm.v1)——issue #17 采纳三点的具名化落点。
|
|
3
3
|
*
|
|
4
|
-
* 立场(docs/booking-saga-fsm.md / ADR-17):不引入编排框架;预订 saga 的状态字母表与边表
|
|
4
|
+
* 立场(docs/design/booking-saga-fsm.md / ADR-17):不引入编排框架;预订 saga 的状态字母表与边表
|
|
5
5
|
* 就是账本 pending_writes(status CHECK:pending|confirmed|compensated)的显式化。
|
|
6
6
|
* 本模块是纯函数词汇层,零依赖、零行为、不接线任何 seam——M5 拍板 WriteGate 生产化时
|
|
7
7
|
* 以此为唯一状态推进词汇(具名化 = 状态与边不再散落在 SQL 字符串里)。
|