@danceiny/gotry 0.0.1-rc.14 → 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.
Files changed (142) hide show
  1. package/README.md +26 -20
  2. package/README.zh-CN.md +27 -21
  3. package/bin/gotry-bootstrap.js +331 -3
  4. package/cordis.gotry-patch.yml +10 -0
  5. package/data/airline-airports.json +39 -0
  6. package/dist/capabilities/agent-reach-deep.js +1 -1
  7. package/dist/capabilities/agent-reach.js +1 -1
  8. package/dist/capabilities/anything.js +1 -1
  9. package/dist/capabilities/artifacts.js +217 -0
  10. package/dist/capabilities/effect.js +293 -0
  11. package/dist/capabilities/fact-log.js +41 -0
  12. package/dist/capabilities/flyai.js +1 -1
  13. package/dist/capabilities/hbcli.js +43 -1
  14. package/dist/capabilities/incident-log.js +1 -1
  15. package/dist/capabilities/opensky.js +1 -1
  16. package/dist/capabilities/resilience.js +90 -0
  17. package/dist/capabilities/session/action-cache.js +1 -1
  18. package/dist/capabilities/session/adapters/ctrip-flight.js +1 -1
  19. package/dist/capabilities/session/adapters/meituan-local.js +1 -1
  20. package/dist/capabilities/session/benchmark.js +2 -1
  21. package/dist/capabilities/session/extension-bridge.js +345 -0
  22. package/dist/capabilities/session/extension-channel.js +93 -0
  23. package/dist/capabilities/session/extract.js +1 -1
  24. package/dist/capabilities/session/health-watch.js +216 -0
  25. package/dist/capabilities/session/read-guard.js +1 -1
  26. package/dist/capabilities/session/transport.js +1 -1
  27. package/dist/capabilities/session/wizard.js +377 -0
  28. package/dist/capabilities/session-consent.js +1 -1
  29. package/dist/capabilities/session-login.js +77 -1
  30. package/dist/capabilities/session-search.js +69 -2
  31. package/dist/capabilities/weather.js +1 -1
  32. package/dist/scripts/action-cache-tests.js +1 -1
  33. package/dist/scripts/agent-reach-deep-tests.js +1 -1
  34. package/dist/scripts/agent-reach-tests.js +1 -1
  35. package/dist/scripts/agent-reach-wrapper-tests.js +1 -1
  36. package/dist/scripts/anything-tests.js +1 -1
  37. package/dist/scripts/async-collect.js +1 -1
  38. package/dist/scripts/booking-saga-tests.js +187 -0
  39. package/dist/scripts/bootstrap-tests.js +49 -3
  40. package/dist/scripts/build-changelog.js +279 -0
  41. package/dist/scripts/changelog-tests.js +165 -0
  42. package/dist/scripts/companion-tests.js +1 -1
  43. package/dist/scripts/diff-test.js +1 -1
  44. package/dist/scripts/effect-tests.js +474 -0
  45. package/dist/scripts/engine-run.js +1 -1
  46. package/dist/scripts/engine-tests.js +1 -1
  47. package/dist/scripts/extension-tests.js +468 -0
  48. package/dist/scripts/fact-gate-tests.js +318 -0
  49. package/dist/scripts/flyai-tests.js +1 -1
  50. package/dist/scripts/hbcli-e2e-tests.js +180 -0
  51. package/dist/scripts/hbcli-tests.js +1 -1
  52. package/dist/scripts/health-watch-cli.js +51 -0
  53. package/dist/scripts/i18n-tests.js +1 -1
  54. package/dist/scripts/incident-tests.js +1 -1
  55. package/dist/scripts/journey-tests.js +1 -1
  56. package/dist/scripts/ledger-tests.js +1 -1
  57. package/dist/scripts/ledger-workflow-crash.js +1 -1
  58. package/dist/scripts/memory-capture-tests.js +1 -1
  59. package/dist/scripts/memory-decay-tests.js +1 -1
  60. package/dist/scripts/memory-metrics.js +1 -1
  61. package/dist/scripts/memory-value-report.js +1 -1
  62. package/dist/scripts/nightly-evidence-tests.js +186 -0
  63. package/dist/scripts/nightly-evidence.js +289 -0
  64. package/dist/scripts/nudge-digest.js +1 -1
  65. package/dist/scripts/onboarding-tests.js +218 -0
  66. package/dist/scripts/opensky-check.js +1 -1
  67. package/dist/scripts/opensky-tests.js +1 -1
  68. package/dist/scripts/price-drift-tests.js +441 -0
  69. package/dist/scripts/price-drift-watch.js +491 -0
  70. package/dist/scripts/probe-poi-tests.js +1 -1
  71. package/dist/scripts/product-metrics.js +1 -1
  72. package/dist/scripts/publish-preverify.js +1 -1
  73. package/dist/scripts/realtime-pricing-tests.js +1 -1
  74. package/dist/scripts/replay-async.js +1 -1
  75. package/dist/scripts/replay-real.js +1 -1
  76. package/dist/scripts/replay.js +1 -1
  77. package/dist/scripts/session-attach-diagnose.js +1 -1
  78. package/dist/scripts/session-attach-poc.js +1 -1
  79. package/dist/scripts/session-benchmark.js +1 -1
  80. package/dist/scripts/session-extract-tests.js +1 -1
  81. package/dist/scripts/session-login.js +1 -1
  82. package/dist/scripts/session-tests.js +1 -1
  83. package/dist/scripts/skeleton-check.js +1 -1
  84. package/dist/scripts/skeleton-integration-test.js +1 -1
  85. package/dist/scripts/skills-contract-tests.js +1 -1
  86. package/dist/scripts/smoke.js +159 -1
  87. package/dist/scripts/state-cli-tests.js +1 -1
  88. package/dist/scripts/state-cli.js +1 -1
  89. package/dist/scripts/time-eval-tests.js +1 -1
  90. package/dist/scripts/travel-timeline-tests.js +1 -1
  91. package/dist/scripts/unified-tests.js +1 -1
  92. package/dist/scripts/weather-tests.js +1 -1
  93. package/dist/scripts/wizard-bootstrap.js +32 -0
  94. package/dist/scripts/z3-race-tests.js +1 -1
  95. package/dist/src/artifact-gate.js +310 -0
  96. package/dist/src/bookable-facts.js +349 -0
  97. package/dist/src/booking-saga.js +153 -0
  98. package/dist/src/bridge.js +1 -1
  99. package/dist/src/companions.js +1 -1
  100. package/dist/src/contracts.js +1 -1
  101. package/dist/src/dsh-llm.js +31 -7
  102. package/dist/src/engine.js +1 -1
  103. package/dist/src/i18n.js +1 -1
  104. package/dist/src/index.js +322 -46
  105. package/dist/src/journey.js +1 -1
  106. package/dist/src/loop.js +1 -1
  107. package/dist/src/memory-capture.js +1 -1
  108. package/dist/src/memory-decay.js +1 -1
  109. package/dist/src/memory-utility.js +1 -1
  110. package/dist/src/mock-llm.js +1 -1
  111. package/dist/src/model.js +1 -1
  112. package/dist/src/realtime-pricing.js +14 -7
  113. package/dist/src/slot-spec.js +1 -1
  114. package/dist/src/state-ledger.js +1 -1
  115. package/dist/src/time-anchor.js +1 -1
  116. package/dist/src/tool-packet.js +1 -1
  117. package/dist/src/travel-slots.js +1 -1
  118. package/dist/src/travel-timeline.js +1 -1
  119. package/dist/src/unified.js +1 -1
  120. package/dist/src/wish-pool.js +1 -1
  121. package/dist/src/z3-shared.js +1 -1
  122. package/extension/README.md +26 -0
  123. package/extension/background.js +160 -0
  124. package/extension/content-bridge.js +41 -0
  125. package/extension/content-main.js +62 -0
  126. package/extension/manifest.json +32 -0
  127. package/package.json +8 -2
  128. package/ts/capabilities/artifacts.ts +235 -0
  129. package/ts/capabilities/fact-log.ts +54 -0
  130. package/ts/capabilities/hbcli.ts +46 -0
  131. package/ts/capabilities/session/benchmark.ts +4 -1
  132. package/ts/capabilities/session/extension-bridge.ts +354 -0
  133. package/ts/capabilities/session/extension-channel.ts +92 -0
  134. package/ts/capabilities/session/health-watch.ts +257 -0
  135. package/ts/capabilities/session/wizard.ts +282 -0
  136. package/ts/capabilities/session-login.ts +53 -2
  137. package/ts/capabilities/session-search.ts +68 -3
  138. package/ts/src/artifact-gate.ts +365 -0
  139. package/ts/src/bookable-facts.ts +557 -0
  140. package/ts/src/booking-saga.ts +133 -0
  141. package/ts/src/dsh-llm.ts +40 -3
  142. package/ts/src/index.ts +230 -28
@@ -0,0 +1,217 @@
1
+ import { readdir, readFile, stat } from 'node:fs/promises';
2
+ import { existsSync } from 'node:fs';
3
+ import { isAbsolute, join, resolve, sep } from 'node:path';
4
+ import { openLedgerIfExists } from '../src/state-ledger.js';
5
+ const MAX_LIST = 50;
6
+ const MAX_WINDOW = 400;
7
+ const MAX_BYTES = 2 * 1024 * 1024;
8
+ const TEXT_EXT_LANG = {
9
+ md: 'markdown',
10
+ txt: 'text',
11
+ json: 'json',
12
+ jsonl: 'json',
13
+ csv: 'csv',
14
+ log: 'text',
15
+ yaml: 'yaml',
16
+ yml: 'yaml'
17
+ };
18
+ const DIR_DENY = [
19
+ 'node_modules',
20
+ '.git'
21
+ ];
22
+ function rootOf(stateRoot) {
23
+ return stateRoot === '.' ? process.cwd() : resolve(stateRoot);
24
+ }
25
+ function underRoot(p, root) {
26
+ const r = resolve(root);
27
+ return p === r || p.startsWith(r + sep);
28
+ }
29
+ function hasDeniedSegment(p) {
30
+ return p.split(sep).some((seg)=>DIR_DENY.includes(seg));
31
+ }
32
+ function asyncDeliverablePath(root, id) {
33
+ return join(root, 'gotry-state', 'async', `${id}.deliverable.md`);
34
+ }
35
+ function listRunsFromLedger(root, tenant, limit) {
36
+ const ledger = openLedgerIfExists(root, tenant);
37
+ if (!ledger) return [];
38
+ const rows = ledger.db.prepare('SELECT id, goal, status, deliverable, updated FROM workflow_runs WHERE tenant_id = ? ORDER BY updated DESC LIMIT ?').all(ledger.tenant, limit);
39
+ return rows.map((r)=>{
40
+ const file = asyncDeliverablePath(root, r.id);
41
+ return {
42
+ source: 'async-run',
43
+ id: r.id,
44
+ title: r.goal,
45
+ path: file,
46
+ status: r.status,
47
+ updated: r.updated,
48
+ bytes: r.deliverable?.length
49
+ };
50
+ });
51
+ }
52
+ async function listDeliverableFiles(root, limit) {
53
+ const dir = join(root, 'gotry-state', 'async');
54
+ let names = [];
55
+ try {
56
+ names = (await readdir(dir)).filter((n)=>n.endsWith('.deliverable.md'));
57
+ } catch {
58
+ return [];
59
+ }
60
+ const entries = [];
61
+ for (const n of names.slice(0, limit * 2)){
62
+ const p = join(dir, n);
63
+ const st = await stat(p).catch(()=>null);
64
+ if (!st?.isFile()) continue;
65
+ entries.push({
66
+ source: 'async-run',
67
+ id: n.replace(/\.deliverable\.md$/, ''),
68
+ title: n.replace(/\.deliverable\.md$/, ''),
69
+ path: p,
70
+ status: existsSync(p.replace(/\.deliverable\.md$/, '.json')) ? 'pending-view' : 'legacy',
71
+ updated: new Date(st.mtimeMs).toISOString(),
72
+ bytes: st.size
73
+ });
74
+ }
75
+ return entries.sort((a, b)=>String(b.updated).localeCompare(String(a.updated)));
76
+ }
77
+ async function listCwdMarkdown(cwd, limit) {
78
+ let dirents;
79
+ try {
80
+ dirents = await readdir(cwd, {
81
+ withFileTypes: true
82
+ });
83
+ } catch {
84
+ return [];
85
+ }
86
+ const entries = [];
87
+ for (const d of dirents){
88
+ if (!d.isFile() || !/\.md$/i.test(d.name) || d.name.startsWith('.')) continue;
89
+ const p = join(cwd, d.name);
90
+ const st = await stat(p).catch(()=>null);
91
+ if (!st) continue;
92
+ entries.push({
93
+ source: 'cwd-file',
94
+ id: d.name,
95
+ title: d.name.replace(/\.md$/i, ''),
96
+ path: p,
97
+ updated: new Date(st.mtimeMs).toISOString(),
98
+ bytes: st.size
99
+ });
100
+ }
101
+ return entries.sort((a, b)=>String(b.updated).localeCompare(String(a.updated))).slice(0, limit);
102
+ }
103
+ export async function listArtifacts(opts) {
104
+ const limit = Math.max(1, Math.min(opts.limit ?? 20, MAX_LIST));
105
+ const root = rootOf(opts.stateRoot);
106
+ const cwd = opts.cwd ? resolve(opts.cwd) : process.cwd();
107
+ const seenPath = new Set();
108
+ const merged = [];
109
+ for (const e of [
110
+ ...listRunsFromLedger(root, 'local', limit),
111
+ ...await listDeliverableFiles(root, limit),
112
+ ...await listCwdMarkdown(cwd, limit)
113
+ ]){
114
+ if (seenPath.has(e.path)) continue;
115
+ seenPath.add(e.path);
116
+ merged.push(e);
117
+ }
118
+ merged.sort((a, b)=>String(b.updated ?? '').localeCompare(String(a.updated ?? '')));
119
+ const total = merged.length;
120
+ return {
121
+ artifacts: merged.slice(0, limit),
122
+ total,
123
+ truncated: total > limit,
124
+ roots: [
125
+ root,
126
+ cwd
127
+ ]
128
+ };
129
+ }
130
+ export async function readArtifact(opts) {
131
+ const root = rootOf(opts.stateRoot);
132
+ const cwd = opts.cwd ? resolve(opts.cwd) : process.cwd();
133
+ const raw = String(opts.path ?? '').trim();
134
+ if (!raw) return {
135
+ ok: false,
136
+ error: 'path 必填(来自 gotry_artifacts_list 的 path,或异步工单 id)'
137
+ };
138
+ let text = null;
139
+ let filePath = '';
140
+ if (!raw.includes('/') && !raw.includes('\\') && !raw.includes('.')) {
141
+ const ledger = openLedgerIfExists(root, 'local');
142
+ const run = ledger?.db.prepare('SELECT id, goal, status, deliverable FROM workflow_runs WHERE id = ? AND tenant_id = ?').get(raw, ledger.tenant);
143
+ if (run?.deliverable) {
144
+ text = run.deliverable;
145
+ filePath = asyncDeliverablePath(root, raw);
146
+ } else {
147
+ const p = asyncDeliverablePath(root, raw);
148
+ if (existsSync(p)) {
149
+ filePath = p;
150
+ text = await readFile(p, 'utf-8');
151
+ }
152
+ }
153
+ if (text === null) return {
154
+ ok: false,
155
+ error: `工单 ${raw} 无 deliverable(未交付或不存在)`,
156
+ hint: '先 gotry_artifacts_list 看在册产物'
157
+ };
158
+ }
159
+ if (text === null) {
160
+ const candidates = isAbsolute(raw) ? [
161
+ resolve(raw)
162
+ ] : [
163
+ resolve(cwd, raw),
164
+ resolve(root, raw),
165
+ resolve(root, 'gotry-state', 'async', raw)
166
+ ];
167
+ const allowed = candidates.find((p)=>(underRoot(p, cwd) || underRoot(p, root)) && !hasDeniedSegment(p));
168
+ if (!allowed) {
169
+ return {
170
+ ok: false,
171
+ error: `路径越界:${raw}`,
172
+ hint: `只读 ${root} 与 dsh 工作目录内的文本产物`
173
+ };
174
+ }
175
+ const ext = allowed.slice(allowed.lastIndexOf('.') + 1).toLowerCase();
176
+ if (!TEXT_EXT_LANG[ext]) {
177
+ return {
178
+ ok: false,
179
+ error: `不支持的文件类型 .${ext}`,
180
+ hint: `白名单:${Object.keys(TEXT_EXT_LANG).join('/')}`
181
+ };
182
+ }
183
+ const st = await stat(allowed).catch(()=>null);
184
+ if (!st?.isFile()) return {
185
+ ok: false,
186
+ error: `文件不存在:${raw}`,
187
+ hint: '先 gotry_artifacts_list 看在册产物'
188
+ };
189
+ if (st.size > MAX_BYTES) return {
190
+ ok: false,
191
+ error: `文件过大(${st.size} bytes > ${MAX_BYTES})`
192
+ };
193
+ filePath = allowed;
194
+ text = await readFile(allowed, 'utf-8');
195
+ }
196
+ const allLines = text.split('\n');
197
+ const offset = Math.max(1, Math.min(opts.offset ?? 1, allLines.length));
198
+ const limit = Math.max(1, Math.min(opts.limit ?? MAX_WINDOW, MAX_WINDOW));
199
+ const slice = allLines.slice(offset - 1, offset - 1 + limit);
200
+ const ext = filePath.slice(filePath.lastIndexOf('.') + 1).toLowerCase();
201
+ return {
202
+ ok: true,
203
+ path: filePath,
204
+ offset,
205
+ lines: slice.map((t, i)=>({
206
+ number: offset + i,
207
+ text: t
208
+ })),
209
+ totalLines: allLines.length,
210
+ lang: TEXT_EXT_LANG[ext] ?? 'text',
211
+ content: slice.join('\n'),
212
+ windowed: allLines.length > offset - 1 + slice.length
213
+ };
214
+ }
215
+
216
+
217
+ //# sourceURL=/Users/bytedance/work/gotry-publish/ts/capabilities/artifacts.ts
@@ -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
@@ -233,4 +233,4 @@ function parseHotelItems(items) {
233
233
  }
234
234
 
235
235
 
236
- //# sourceURL=/Users/bytedance/work/gotry/ts/capabilities/flyai.ts
236
+ //# sourceURL=/Users/bytedance/work/gotry-publish/ts/capabilities/flyai.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
@@ -97,4 +97,4 @@ if (import.meta.url === `file://${fileURLToPath(import.meta.url)}` && process.ar
97
97
  }
98
98
 
99
99
 
100
- //# sourceURL=/Users/bytedance/work/gotry/ts/capabilities/incident-log.ts
100
+ //# sourceURL=/Users/bytedance/work/gotry-publish/ts/capabilities/incident-log.ts
@@ -71,4 +71,4 @@ export async function verifyFlight(query) {
71
71
  }
72
72
 
73
73
 
74
- //# sourceURL=/Users/bytedance/work/gotry/ts/capabilities/opensky.ts
74
+ //# sourceURL=/Users/bytedance/work/gotry-publish/ts/capabilities/opensky.ts