@danceiny/gotry 0.0.1-rc.15 → 0.0.1-rc.16
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 +10 -6
- package/README.zh-CN.md +10 -6
- package/bin/gotry-bootstrap.js +294 -5
- package/cordis.gotry-patch.yml +10 -0
- package/data/airline-airports.json +39 -0
- package/dist/capabilities/agent-reach-deep.js +1 -1
- package/dist/capabilities/agent-reach.js +1 -1
- package/dist/capabilities/anything.js +1 -1
- package/dist/capabilities/artifacts.js +1 -1
- package/dist/capabilities/effect.js +293 -0
- package/dist/capabilities/fact-log.js +41 -0
- package/dist/capabilities/flyai.js +1 -1
- package/dist/capabilities/hbcli.js +43 -1
- package/dist/capabilities/incident-log.js +1 -1
- package/dist/capabilities/opensky.js +1 -1
- package/dist/capabilities/resilience.js +90 -0
- package/dist/capabilities/session/action-cache.js +1 -1
- package/dist/capabilities/session/adapters/ctrip-flight.js +1 -1
- package/dist/capabilities/session/adapters/meituan-local.js +1 -1
- package/dist/capabilities/session/benchmark.js +2 -1
- package/dist/capabilities/session/extension-bridge.js +345 -0
- package/dist/capabilities/session/extension-channel.js +93 -0
- package/dist/capabilities/session/extract.js +1 -1
- package/dist/capabilities/session/health-watch.js +216 -0
- package/dist/capabilities/session/read-guard.js +1 -1
- package/dist/capabilities/session/transport.js +1 -1
- package/dist/capabilities/session/wizard.js +377 -0
- package/dist/capabilities/session-consent.js +1 -1
- package/dist/capabilities/session-login.js +77 -1
- package/dist/capabilities/session-search.js +69 -2
- package/dist/capabilities/weather.js +1 -1
- package/dist/scripts/action-cache-tests.js +1 -1
- package/dist/scripts/agent-reach-deep-tests.js +1 -1
- package/dist/scripts/agent-reach-tests.js +1 -1
- package/dist/scripts/agent-reach-wrapper-tests.js +1 -1
- package/dist/scripts/anything-tests.js +1 -1
- package/dist/scripts/async-collect.js +1 -1
- package/dist/scripts/booking-saga-tests.js +1 -1
- package/dist/scripts/bootstrap-tests.js +39 -3
- package/dist/scripts/build-changelog.js +279 -0
- package/dist/scripts/changelog-tests.js +165 -0
- package/dist/scripts/companion-tests.js +1 -1
- package/dist/scripts/diff-test.js +1 -1
- package/dist/scripts/effect-tests.js +474 -0
- package/dist/scripts/engine-run.js +1 -1
- package/dist/scripts/engine-tests.js +1 -1
- package/dist/scripts/extension-tests.js +468 -0
- package/dist/scripts/fact-gate-tests.js +318 -0
- package/dist/scripts/flyai-tests.js +1 -1
- package/dist/scripts/hbcli-e2e-tests.js +180 -0
- package/dist/scripts/hbcli-tests.js +1 -1
- package/dist/scripts/health-watch-cli.js +51 -0
- package/dist/scripts/i18n-tests.js +1 -1
- package/dist/scripts/incident-tests.js +1 -1
- package/dist/scripts/journey-tests.js +1 -1
- package/dist/scripts/ledger-tests.js +1 -1
- package/dist/scripts/ledger-workflow-crash.js +1 -1
- package/dist/scripts/memory-capture-tests.js +1 -1
- package/dist/scripts/memory-decay-tests.js +1 -1
- package/dist/scripts/memory-metrics.js +1 -1
- package/dist/scripts/memory-value-report.js +1 -1
- package/dist/scripts/nightly-evidence-tests.js +67 -4
- package/dist/scripts/nightly-evidence.js +59 -3
- package/dist/scripts/nudge-digest.js +1 -1
- package/dist/scripts/onboarding-tests.js +218 -0
- package/dist/scripts/opensky-check.js +1 -1
- package/dist/scripts/opensky-tests.js +1 -1
- package/dist/scripts/price-drift-tests.js +441 -0
- package/dist/scripts/price-drift-watch.js +491 -0
- package/dist/scripts/probe-poi-tests.js +1 -1
- package/dist/scripts/product-metrics.js +1 -1
- package/dist/scripts/publish-preverify.js +1 -1
- package/dist/scripts/realtime-pricing-tests.js +1 -1
- package/dist/scripts/replay-async.js +1 -1
- package/dist/scripts/replay-real.js +1 -1
- package/dist/scripts/replay.js +1 -1
- package/dist/scripts/session-attach-diagnose.js +1 -1
- package/dist/scripts/session-attach-poc.js +1 -1
- package/dist/scripts/session-benchmark.js +1 -1
- package/dist/scripts/session-extract-tests.js +1 -1
- package/dist/scripts/session-login.js +1 -1
- package/dist/scripts/session-tests.js +1 -1
- package/dist/scripts/skeleton-check.js +1 -1
- package/dist/scripts/skeleton-integration-test.js +1 -1
- package/dist/scripts/skills-contract-tests.js +1 -1
- package/dist/scripts/smoke.js +76 -1
- package/dist/scripts/state-cli-tests.js +1 -1
- package/dist/scripts/state-cli.js +1 -1
- package/dist/scripts/time-eval-tests.js +1 -1
- package/dist/scripts/travel-timeline-tests.js +1 -1
- package/dist/scripts/unified-tests.js +1 -1
- package/dist/scripts/weather-tests.js +1 -1
- package/dist/scripts/wizard-bootstrap.js +32 -0
- package/dist/scripts/z3-race-tests.js +1 -1
- package/dist/src/artifact-gate.js +310 -0
- package/dist/src/bookable-facts.js +349 -0
- package/dist/src/booking-saga.js +1 -1
- package/dist/src/bridge.js +1 -1
- package/dist/src/companions.js +1 -1
- package/dist/src/contracts.js +1 -1
- package/dist/src/dsh-llm.js +1 -1
- package/dist/src/engine.js +1 -1
- package/dist/src/i18n.js +1 -1
- package/dist/src/index.js +189 -46
- package/dist/src/journey.js +1 -1
- package/dist/src/loop.js +1 -1
- package/dist/src/memory-capture.js +1 -1
- package/dist/src/memory-decay.js +1 -1
- package/dist/src/memory-utility.js +1 -1
- package/dist/src/mock-llm.js +1 -1
- package/dist/src/model.js +1 -1
- package/dist/src/realtime-pricing.js +14 -7
- package/dist/src/slot-spec.js +1 -1
- package/dist/src/state-ledger.js +1 -1
- package/dist/src/time-anchor.js +1 -1
- package/dist/src/tool-packet.js +1 -1
- package/dist/src/travel-slots.js +1 -1
- package/dist/src/travel-timeline.js +1 -1
- package/dist/src/unified.js +1 -1
- package/dist/src/wish-pool.js +1 -1
- package/dist/src/z3-shared.js +1 -1
- package/extension/README.md +26 -0
- package/extension/background.js +160 -0
- package/extension/content-bridge.js +41 -0
- package/extension/content-main.js +62 -0
- package/extension/manifest.json +32 -0
- package/package.json +6 -2
- package/ts/capabilities/fact-log.ts +54 -0
- package/ts/capabilities/hbcli.ts +46 -0
- package/ts/capabilities/session/benchmark.ts +4 -1
- package/ts/capabilities/session/extension-bridge.ts +354 -0
- package/ts/capabilities/session/extension-channel.ts +92 -0
- package/ts/capabilities/session/health-watch.ts +257 -0
- package/ts/capabilities/session/wizard.ts +282 -0
- package/ts/capabilities/session-login.ts +53 -2
- package/ts/capabilities/session-search.ts +68 -3
- package/ts/src/artifact-gate.ts +365 -0
- package/ts/src/bookable-facts.ts +557 -0
- package/ts/src/index.ts +102 -8
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
import { CircuitBreaker, withRetry } from './resilience.js';
|
|
2
|
+
import { flyaiSearch } from './flyai.js';
|
|
3
|
+
import { checkAvail, hotelRates, searchHotels } from './hbcli.js';
|
|
4
|
+
import { sessionFlightSearch } from './session-search.js';
|
|
5
|
+
import { geocodePlace, getClimate, getForecast } from './weather.js';
|
|
6
|
+
import { verifyFlight } from './opensky.js';
|
|
7
|
+
const DEFAULT_HANDLERS = {
|
|
8
|
+
FLYAI_SEARCH: (p)=>flyaiSearch(p),
|
|
9
|
+
HBCLI_HOTEL_SEARCH: (p)=>searchHotels({
|
|
10
|
+
destination: p.destination,
|
|
11
|
+
checkIn: p.checkIn,
|
|
12
|
+
checkOut: p.checkOut,
|
|
13
|
+
adults: p.adults
|
|
14
|
+
}, {
|
|
15
|
+
hbcliBin: p.hbcliBin,
|
|
16
|
+
timeoutMs: p.timeoutMs,
|
|
17
|
+
fallbackPath: p.fallbackPath
|
|
18
|
+
}),
|
|
19
|
+
HBCLI_HOTEL_RATES: (p)=>hotelRates({
|
|
20
|
+
hotelId: p.hotelId,
|
|
21
|
+
checkIn: p.checkIn,
|
|
22
|
+
checkOut: p.checkOut,
|
|
23
|
+
adults: p.adults,
|
|
24
|
+
countryCode: p.countryCode,
|
|
25
|
+
nationalityCode: p.nationalityCode,
|
|
26
|
+
residencyCode: p.residencyCode
|
|
27
|
+
}, {
|
|
28
|
+
hbcliBin: p.hbcliBin,
|
|
29
|
+
timeoutMs: p.timeoutMs
|
|
30
|
+
}),
|
|
31
|
+
HBCLI_CHECK_AVAIL: (p)=>checkAvail({
|
|
32
|
+
ratePkgId: p.ratePkgId
|
|
33
|
+
}, {
|
|
34
|
+
hbcliBin: p.hbcliBin,
|
|
35
|
+
timeoutMs: p.timeoutMs
|
|
36
|
+
}),
|
|
37
|
+
SESSION_FLIGHT_SEARCH: (p)=>sessionFlightSearch(p),
|
|
38
|
+
WEATHER_GEOCODE: (p)=>geocodePlace(p.name, {
|
|
39
|
+
count: p.count,
|
|
40
|
+
timeoutMs: p.timeoutMs
|
|
41
|
+
}),
|
|
42
|
+
WEATHER_FORECAST: (p)=>getForecast({
|
|
43
|
+
latitude: p.latitude,
|
|
44
|
+
longitude: p.longitude
|
|
45
|
+
}, {
|
|
46
|
+
days: p.days,
|
|
47
|
+
timeoutMs: p.timeoutMs
|
|
48
|
+
}),
|
|
49
|
+
WEATHER_CLIMATE: (p)=>getClimate({
|
|
50
|
+
latitude: p.latitude,
|
|
51
|
+
longitude: p.longitude
|
|
52
|
+
}, p.month, {
|
|
53
|
+
timeoutMs: p.timeoutMs
|
|
54
|
+
}),
|
|
55
|
+
OPENSKY_FLIGHT_VERIFY: (p)=>verifyFlight(p)
|
|
56
|
+
};
|
|
57
|
+
const defaultIsFailure = (r)=>r?.ok === false;
|
|
58
|
+
const flyaiTransient = (msg)=>msg.length > 0 && !/Sentinel/i.test(msg) && /timeout|timed out|ECONNRESET|ECONNREFUSED|ETIMEDOUT|socket|HTTP 5/i.test(msg);
|
|
59
|
+
const API_RETRY = {
|
|
60
|
+
maxAttempts: 2,
|
|
61
|
+
baseDelayMs: 400,
|
|
62
|
+
maxDelayMs: 1_600
|
|
63
|
+
};
|
|
64
|
+
const API_BREAKER = {
|
|
65
|
+
failureThreshold: 3,
|
|
66
|
+
openMs: 30_000
|
|
67
|
+
};
|
|
68
|
+
const SPECS = {
|
|
69
|
+
FLYAI_SEARCH: {
|
|
70
|
+
channel: 'cli',
|
|
71
|
+
retry: {
|
|
72
|
+
maxAttempts: 2,
|
|
73
|
+
baseDelayMs: 500,
|
|
74
|
+
maxDelayMs: 2_000
|
|
75
|
+
},
|
|
76
|
+
breaker: {
|
|
77
|
+
failureThreshold: 3,
|
|
78
|
+
openMs: 60_000
|
|
79
|
+
},
|
|
80
|
+
isRetryable: (_r, e)=>{
|
|
81
|
+
const msg = e != null ? String(e.message ?? e) : String((_r ?? {}).error ?? '');
|
|
82
|
+
return flyaiTransient(msg);
|
|
83
|
+
},
|
|
84
|
+
isFailure: defaultIsFailure
|
|
85
|
+
},
|
|
86
|
+
HBCLI_HOTEL_SEARCH: {
|
|
87
|
+
channel: 'cli',
|
|
88
|
+
retry: null,
|
|
89
|
+
breaker: {
|
|
90
|
+
failureThreshold: 3,
|
|
91
|
+
openMs: 60_000
|
|
92
|
+
},
|
|
93
|
+
isFailure: (r)=>r.via === 'hbcli-error'
|
|
94
|
+
},
|
|
95
|
+
HBCLI_HOTEL_RATES: {
|
|
96
|
+
channel: 'cli',
|
|
97
|
+
retry: null,
|
|
98
|
+
breaker: {
|
|
99
|
+
failureThreshold: 3,
|
|
100
|
+
openMs: 60_000
|
|
101
|
+
},
|
|
102
|
+
isFailure: (r)=>r.via === 'hbcli-error'
|
|
103
|
+
},
|
|
104
|
+
HBCLI_CHECK_AVAIL: {
|
|
105
|
+
channel: 'cli',
|
|
106
|
+
retry: null,
|
|
107
|
+
breaker: {
|
|
108
|
+
failureThreshold: 3,
|
|
109
|
+
openMs: 60_000
|
|
110
|
+
},
|
|
111
|
+
isFailure: (r)=>r.via === 'hbcli-error'
|
|
112
|
+
},
|
|
113
|
+
SESSION_FLIGHT_SEARCH: {
|
|
114
|
+
channel: 'browser',
|
|
115
|
+
retry: null,
|
|
116
|
+
breaker: null,
|
|
117
|
+
isFailure: defaultIsFailure
|
|
118
|
+
},
|
|
119
|
+
WEATHER_GEOCODE: {
|
|
120
|
+
channel: 'api',
|
|
121
|
+
retry: API_RETRY,
|
|
122
|
+
breaker: API_BREAKER,
|
|
123
|
+
isFailure: defaultIsFailure
|
|
124
|
+
},
|
|
125
|
+
WEATHER_FORECAST: {
|
|
126
|
+
channel: 'api',
|
|
127
|
+
retry: API_RETRY,
|
|
128
|
+
breaker: API_BREAKER,
|
|
129
|
+
isFailure: defaultIsFailure
|
|
130
|
+
},
|
|
131
|
+
WEATHER_CLIMATE: {
|
|
132
|
+
channel: 'api',
|
|
133
|
+
retry: API_RETRY,
|
|
134
|
+
breaker: API_BREAKER,
|
|
135
|
+
isFailure: defaultIsFailure
|
|
136
|
+
},
|
|
137
|
+
OPENSKY_FLIGHT_VERIFY: {
|
|
138
|
+
channel: 'api',
|
|
139
|
+
retry: API_RETRY,
|
|
140
|
+
breaker: API_BREAKER,
|
|
141
|
+
isRetryable: (_r, e)=>e != null || (_r ?? {}).verdict === 'unavailable',
|
|
142
|
+
isFailure: (r)=>r.via === 'opensky-error'
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
export function declinedObservation(effect, trace) {
|
|
146
|
+
return {
|
|
147
|
+
ok: false,
|
|
148
|
+
verdict: 'error',
|
|
149
|
+
summary: trace.declined === 'circuit-open' ? `${effect} 通道被断路器开启保护(连续失败达到阈值,冷却中)——不要立即重试,换其他工具或稍后再试;原因见 trace 证据链` : `${effect} 未登记效应(effect_interpreter.v1 注册表外,生产解译器拒绝)`,
|
|
150
|
+
evidence: trace.evidence.join(';')
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
const defaultBreakers = new Map();
|
|
154
|
+
const defaultSleep = (ms)=>new Promise((resolve)=>setTimeout(resolve, ms));
|
|
155
|
+
export function __resetEffectBreakersForTest() {
|
|
156
|
+
defaultBreakers.clear();
|
|
157
|
+
}
|
|
158
|
+
export function makeProductionInterpreter(opts = {}) {
|
|
159
|
+
const breakers = opts.breakers ?? defaultBreakers;
|
|
160
|
+
const sleep = opts.sleep ?? defaultSleep;
|
|
161
|
+
return async (fx)=>{
|
|
162
|
+
const ts = new Date().toISOString();
|
|
163
|
+
const name = fx.effect;
|
|
164
|
+
const spec = SPECS[name];
|
|
165
|
+
const handler = opts.handlers?.[fx.effect] ?? DEFAULT_HANDLERS[name];
|
|
166
|
+
if (!spec || !handler) {
|
|
167
|
+
return {
|
|
168
|
+
result: null,
|
|
169
|
+
trace: {
|
|
170
|
+
effect: fx.effect,
|
|
171
|
+
channel: 'api',
|
|
172
|
+
attempts: 0,
|
|
173
|
+
backoffMs: 0,
|
|
174
|
+
breaker: 'off',
|
|
175
|
+
declined: 'unknown-effect',
|
|
176
|
+
evidence: [
|
|
177
|
+
`[效应:${fx.effect}@${ts}] unknown effect`
|
|
178
|
+
]
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
const br = (()=>{
|
|
183
|
+
if (!spec.breaker) return null;
|
|
184
|
+
const existing = breakers.get(fx.effect);
|
|
185
|
+
if (existing) return existing;
|
|
186
|
+
const created = new CircuitBreaker({
|
|
187
|
+
...spec.breaker,
|
|
188
|
+
...opts.now ? {
|
|
189
|
+
now: opts.now
|
|
190
|
+
} : {}
|
|
191
|
+
});
|
|
192
|
+
breakers.set(fx.effect, created);
|
|
193
|
+
return created;
|
|
194
|
+
})();
|
|
195
|
+
const gate = br?.canAttempt() ?? {
|
|
196
|
+
allowed: true,
|
|
197
|
+
state: 'closed'
|
|
198
|
+
};
|
|
199
|
+
if (!gate.allowed) {
|
|
200
|
+
const trace = {
|
|
201
|
+
effect: fx.effect,
|
|
202
|
+
channel: spec.channel,
|
|
203
|
+
attempts: 0,
|
|
204
|
+
backoffMs: 0,
|
|
205
|
+
breaker: gate.state,
|
|
206
|
+
declined: 'circuit-open',
|
|
207
|
+
evidence: [
|
|
208
|
+
`[效应:${fx.effect}@${ts}] breaker=${gate.state} 拒绝(冷却中,不重试)`
|
|
209
|
+
]
|
|
210
|
+
};
|
|
211
|
+
return {
|
|
212
|
+
result: null,
|
|
213
|
+
trace
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
const policy = spec.retry ? {
|
|
217
|
+
maxAttempts: spec.retry.maxAttempts,
|
|
218
|
+
baseDelayMs: spec.retry.baseDelayMs,
|
|
219
|
+
maxDelayMs: spec.retry.maxDelayMs,
|
|
220
|
+
isRetryable: spec.isRetryable,
|
|
221
|
+
sleep
|
|
222
|
+
} : {
|
|
223
|
+
maxAttempts: 1,
|
|
224
|
+
baseDelayMs: 0,
|
|
225
|
+
maxDelayMs: 0,
|
|
226
|
+
sleep
|
|
227
|
+
};
|
|
228
|
+
const dispatch = handler;
|
|
229
|
+
const outcome = await withRetry(()=>dispatch(fx.params), policy);
|
|
230
|
+
const failed = outcome.error != null || spec.isFailure(outcome.result);
|
|
231
|
+
if (br) failed ? br.onFailure() : br.onSuccess();
|
|
232
|
+
const breakerState = br?.state() ?? 'off';
|
|
233
|
+
const evidence = [
|
|
234
|
+
`[效应:${fx.effect}@${ts}] attempts=${outcome.attempts} backoff=${outcome.backoffMs}ms breaker=${breakerState}`
|
|
235
|
+
];
|
|
236
|
+
return {
|
|
237
|
+
result: outcome.error != null ? null : outcome.result,
|
|
238
|
+
trace: {
|
|
239
|
+
effect: fx.effect,
|
|
240
|
+
channel: spec.channel,
|
|
241
|
+
attempts: outcome.attempts,
|
|
242
|
+
backoffMs: outcome.backoffMs,
|
|
243
|
+
breaker: breakerState,
|
|
244
|
+
evidence
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
export const productionInterpreter = makeProductionInterpreter();
|
|
250
|
+
export async function interpretEffect(fx) {
|
|
251
|
+
return productionInterpreter(fx);
|
|
252
|
+
}
|
|
253
|
+
export function makeMockInterpreter(fixtures) {
|
|
254
|
+
return async (fx)=>{
|
|
255
|
+
const ts = new Date().toISOString();
|
|
256
|
+
const registered = Object.prototype.hasOwnProperty.call(fixtures, fx.effect);
|
|
257
|
+
if (!registered) {
|
|
258
|
+
return {
|
|
259
|
+
result: null,
|
|
260
|
+
trace: {
|
|
261
|
+
effect: fx.effect,
|
|
262
|
+
channel: 'mock',
|
|
263
|
+
attempts: 0,
|
|
264
|
+
backoffMs: 0,
|
|
265
|
+
breaker: 'off',
|
|
266
|
+
declined: 'unknown-effect',
|
|
267
|
+
evidence: [
|
|
268
|
+
`[效应:mock@${ts}] ${fx.effect} 未登记夹具`
|
|
269
|
+
]
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
return {
|
|
274
|
+
result: fixtures[fx.effect],
|
|
275
|
+
trace: {
|
|
276
|
+
effect: fx.effect,
|
|
277
|
+
channel: 'mock',
|
|
278
|
+
attempts: 1,
|
|
279
|
+
backoffMs: 0,
|
|
280
|
+
breaker: 'off',
|
|
281
|
+
evidence: [
|
|
282
|
+
`[效应:mock@${ts}] ${fx.effect} 夹具回放(确定性,无网络)`
|
|
283
|
+
]
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
export function selectInterpreter(env, opts = {}) {
|
|
289
|
+
return env === 'mock' ? makeMockInterpreter(opts.fixtures ?? {}) : makeProductionInterpreter(opts);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
//# sourceURL=/Users/bytedance/work/gotry-publish/ts/capabilities/effect.ts
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { appendFile, mkdir, readFile } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { dedupeFacts } from '../src/bookable-facts.js';
|
|
4
|
+
export const FACT_LOG_NAME = 'bookable-facts.jsonl';
|
|
5
|
+
export function factLogPath(stateRoot) {
|
|
6
|
+
const root = stateRoot === '.' ? process.cwd() : stateRoot;
|
|
7
|
+
return join(root, 'gotry-state', FACT_LOG_NAME);
|
|
8
|
+
}
|
|
9
|
+
export async function appendFacts(stateRoot, facts) {
|
|
10
|
+
if (facts.length === 0) return true;
|
|
11
|
+
try {
|
|
12
|
+
const p = factLogPath(stateRoot);
|
|
13
|
+
await mkdir(join(p, '..'), {
|
|
14
|
+
recursive: true
|
|
15
|
+
});
|
|
16
|
+
await appendFile(p, facts.map((f)=>JSON.stringify(f)).join('\n') + '\n', 'utf-8');
|
|
17
|
+
return true;
|
|
18
|
+
} catch {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export async function loadFactRegistry(stateRoot) {
|
|
23
|
+
try {
|
|
24
|
+
const raw = await readFile(factLogPath(stateRoot), 'utf-8');
|
|
25
|
+
const facts = [];
|
|
26
|
+
for (const line of raw.split('\n')){
|
|
27
|
+
const t = line.trim();
|
|
28
|
+
if (!t) continue;
|
|
29
|
+
try {
|
|
30
|
+
const f = JSON.parse(t);
|
|
31
|
+
if (f.schema === 'gotry_bookable_fact.v1' && f.fact_id) facts.push(f);
|
|
32
|
+
} catch {}
|
|
33
|
+
}
|
|
34
|
+
return dedupeFacts(facts);
|
|
35
|
+
} catch {
|
|
36
|
+
return [];
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
//# sourceURL=/Users/bytedance/work/gotry-publish/ts/capabilities/fact-log.ts
|
|
@@ -177,6 +177,48 @@ export async function listDestinations(opts = {}) {
|
|
|
177
177
|
destinations: r.result
|
|
178
178
|
};
|
|
179
179
|
}
|
|
180
|
+
export async function hotelRates(query, opts = {}) {
|
|
181
|
+
const hbArgs = [
|
|
182
|
+
'search',
|
|
183
|
+
'hotel-rates',
|
|
184
|
+
'--json',
|
|
185
|
+
'--hotel-id',
|
|
186
|
+
query.hotelId
|
|
187
|
+
];
|
|
188
|
+
if (query.checkIn) hbArgs.push('--check-in', query.checkIn);
|
|
189
|
+
if (query.checkOut) hbArgs.push('--check-out', query.checkOut);
|
|
190
|
+
if (query.countryCode) hbArgs.push('--country-code', query.countryCode);
|
|
191
|
+
if (query.nationalityCode) hbArgs.push('--nationality-code', query.nationalityCode);
|
|
192
|
+
if (query.residencyCode) hbArgs.push('--residency-code', query.residencyCode);
|
|
193
|
+
if (query.adults) hbArgs.push('--room-occupancies', JSON.stringify([
|
|
194
|
+
{
|
|
195
|
+
adultCount: query.adults,
|
|
196
|
+
childrenAges: []
|
|
197
|
+
}
|
|
198
|
+
]));
|
|
199
|
+
const live = await callHbcliJson(hbArgs, opts);
|
|
200
|
+
const summary = live.via === 'hbcli-realtime' ? `hotel ${query.hotelId}:hbcli 实时报价${query.checkIn ? `(${query.checkIn} → ${query.checkOut ?? '?'})` : ''}` : `hotel ${query.hotelId}:hbcli 实时报价不可用(${live.error ?? live.via});价格面无静态降级,fail-closed 不估算`;
|
|
201
|
+
return {
|
|
202
|
+
...live,
|
|
203
|
+
rates: live.result,
|
|
204
|
+
summary
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
export async function checkAvail(query, opts = {}) {
|
|
208
|
+
const live = await callHbcliJson([
|
|
209
|
+
'search',
|
|
210
|
+
'check-avail',
|
|
211
|
+
'--json',
|
|
212
|
+
'--rate-pkg-id',
|
|
213
|
+
query.ratePkgId
|
|
214
|
+
], opts);
|
|
215
|
+
const summary = live.via === 'hbcli-realtime' ? `ratePkg ${query.ratePkgId}:hbcli 实时验价(库存与价格以后端为准)` : `ratePkg ${query.ratePkgId}:hbcli 实时验价不可用(${live.error ?? live.via});价格面无静态降级,fail-closed`;
|
|
216
|
+
return {
|
|
217
|
+
...live,
|
|
218
|
+
avail: live.result,
|
|
219
|
+
summary
|
|
220
|
+
};
|
|
221
|
+
}
|
|
180
222
|
|
|
181
223
|
|
|
182
|
-
//# sourceURL=/Users/bytedance/work/gotry/ts/capabilities/hbcli.ts
|
|
224
|
+
//# sourceURL=/Users/bytedance/work/gotry-publish/ts/capabilities/hbcli.ts
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
export function backoffDelayMs(policy, failedAttempt) {
|
|
2
|
+
const raw = policy.baseDelayMs * 2 ** (Math.max(1, failedAttempt) - 1);
|
|
3
|
+
return Math.min(raw, policy.maxDelayMs);
|
|
4
|
+
}
|
|
5
|
+
const defaultSleep = (ms)=>new Promise((resolve)=>setTimeout(resolve, ms));
|
|
6
|
+
export async function withRetry(fn, policy, onBackoff) {
|
|
7
|
+
const sleep = policy.sleep ?? defaultSleep;
|
|
8
|
+
const maxAttempts = Math.max(1, policy.maxAttempts);
|
|
9
|
+
let attempts = 0;
|
|
10
|
+
let backoffMs = 0;
|
|
11
|
+
for(;;){
|
|
12
|
+
attempts += 1;
|
|
13
|
+
let result = null;
|
|
14
|
+
let error = null;
|
|
15
|
+
try {
|
|
16
|
+
result = await fn();
|
|
17
|
+
} catch (e) {
|
|
18
|
+
error = e;
|
|
19
|
+
}
|
|
20
|
+
const retryable = policy.isRetryable?.(result, error) ?? (error != null || result?.ok === false);
|
|
21
|
+
if (attempts < maxAttempts && retryable) {
|
|
22
|
+
const delay = backoffDelayMs(policy, attempts);
|
|
23
|
+
onBackoff?.(attempts, delay);
|
|
24
|
+
backoffMs += delay;
|
|
25
|
+
await sleep(delay);
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
return {
|
|
29
|
+
result,
|
|
30
|
+
error,
|
|
31
|
+
attempts,
|
|
32
|
+
backoffMs
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
export class CircuitBreaker {
|
|
37
|
+
o;
|
|
38
|
+
consecutiveFailures = 0;
|
|
39
|
+
openedAt = 0;
|
|
40
|
+
probeInFlight = false;
|
|
41
|
+
constructor(o){
|
|
42
|
+
this.o = o;
|
|
43
|
+
}
|
|
44
|
+
threshold() {
|
|
45
|
+
return Math.max(1, this.o.failureThreshold);
|
|
46
|
+
}
|
|
47
|
+
now() {
|
|
48
|
+
return (this.o.now ?? Date.now)();
|
|
49
|
+
}
|
|
50
|
+
state() {
|
|
51
|
+
if (this.openedAt > 0 && this.now() - this.openedAt >= this.o.openMs) return 'half-open';
|
|
52
|
+
if (this.openedAt > 0) return 'open';
|
|
53
|
+
return 'closed';
|
|
54
|
+
}
|
|
55
|
+
canAttempt() {
|
|
56
|
+
const s = this.state();
|
|
57
|
+
if (s === 'closed') return {
|
|
58
|
+
allowed: true,
|
|
59
|
+
state: s
|
|
60
|
+
};
|
|
61
|
+
if (s === 'open') return {
|
|
62
|
+
allowed: false,
|
|
63
|
+
state: s
|
|
64
|
+
};
|
|
65
|
+
if (this.probeInFlight) return {
|
|
66
|
+
allowed: false,
|
|
67
|
+
state: s
|
|
68
|
+
};
|
|
69
|
+
this.probeInFlight = true;
|
|
70
|
+
return {
|
|
71
|
+
allowed: true,
|
|
72
|
+
state: s
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
onSuccess() {
|
|
76
|
+
this.consecutiveFailures = 0;
|
|
77
|
+
this.openedAt = 0;
|
|
78
|
+
this.probeInFlight = false;
|
|
79
|
+
}
|
|
80
|
+
onFailure() {
|
|
81
|
+
this.probeInFlight = false;
|
|
82
|
+
this.consecutiveFailures += 1;
|
|
83
|
+
if (this.openedAt > 0 || this.consecutiveFailures >= this.threshold()) {
|
|
84
|
+
this.openedAt = this.now();
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
//# sourceURL=/Users/bytedance/work/gotry-publish/ts/capabilities/resilience.ts
|
|
@@ -228,6 +228,7 @@ export function evaluateDoubleSource(input) {
|
|
|
228
228
|
}
|
|
229
229
|
if (!session) return baseEvaluation('source_unavailable', 'no_spend_stop');
|
|
230
230
|
if (session.verdict === 'needs-attach') return baseEvaluation('waiting_attach', 'no_spend_waiting_user');
|
|
231
|
+
if (session.verdict === 'needs-extension') return baseEvaluation('waiting_extension', 'no_spend_waiting_user');
|
|
231
232
|
if (session.verdict === 'needs-login') return baseEvaluation('waiting_login', 'no_spend_waiting_user');
|
|
232
233
|
if (!official || official.verdict !== 'hit' || session.verdict !== 'hit') {
|
|
233
234
|
return baseEvaluation('source_unavailable', 'no_spend_stop');
|
|
@@ -249,4 +250,4 @@ export function evaluateDoubleSource(input) {
|
|
|
249
250
|
}
|
|
250
251
|
|
|
251
252
|
|
|
252
|
-
//# sourceURL=/Users/bytedance/work/gotry/ts/capabilities/session/benchmark.ts
|
|
253
|
+
//# sourceURL=/Users/bytedance/work/gotry-publish/ts/capabilities/session/benchmark.ts
|