@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.
Files changed (139) hide show
  1. package/README.md +10 -6
  2. package/README.zh-CN.md +10 -6
  3. package/bin/gotry-bootstrap.js +294 -5
  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 +1 -1
  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 +1 -1
  39. package/dist/scripts/bootstrap-tests.js +39 -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 +67 -4
  63. package/dist/scripts/nightly-evidence.js +59 -3
  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 +76 -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 +1 -1
  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 +1 -1
  102. package/dist/src/engine.js +1 -1
  103. package/dist/src/i18n.js +1 -1
  104. package/dist/src/index.js +189 -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 +6 -2
  128. package/ts/capabilities/fact-log.ts +54 -0
  129. package/ts/capabilities/hbcli.ts +46 -0
  130. package/ts/capabilities/session/benchmark.ts +4 -1
  131. package/ts/capabilities/session/extension-bridge.ts +354 -0
  132. package/ts/capabilities/session/extension-channel.ts +92 -0
  133. package/ts/capabilities/session/health-watch.ts +257 -0
  134. package/ts/capabilities/session/wizard.ts +282 -0
  135. package/ts/capabilities/session-login.ts +53 -2
  136. package/ts/capabilities/session-search.ts +68 -3
  137. package/ts/src/artifact-gate.ts +365 -0
  138. package/ts/src/bookable-facts.ts +557 -0
  139. package/ts/src/index.ts +102 -8
package/ts/src/index.ts CHANGED
@@ -38,6 +38,24 @@ import { sessionLogin } from '../capabilities/session-login.ts'
38
38
  import { createConsentGate, approvalFromContext } from '../capabilities/session-consent.ts'
39
39
  import { listArtifacts, readArtifact } from '../capabilities/artifacts.ts'
40
40
  import { interpretEffect, declinedObservation } from '../capabilities/effect.ts'
41
+ import { appendFacts, loadFactRegistry } from '../capabilities/fact-log.ts'
42
+ import { factsFromFlyai, factsFromSession } from './bookable-facts.ts'
43
+ import { gateArtifact, type AirlineAirportMap } from './artifact-gate.ts'
44
+
45
+ /** 航司→机场映射表(issue #46 冲突检测面;data/airline-airports.json,as_of 快照) */
46
+ let airlineAirportMapCache: AirlineAirportMap | null = null
47
+ async function loadAirlineAirportMap(): Promise<AirlineAirportMap | null> {
48
+ if (airlineAirportMapCache) return airlineAirportMapCache
49
+ try {
50
+ const { readFile } = await import('node:fs/promises')
51
+ airlineAirportMapCache = JSON.parse(
52
+ await readFile(join(import.meta.dirname, '..', '..', 'data', 'airline-airports.json'), 'utf-8'),
53
+ ) as AirlineAirportMap
54
+ return airlineAirportMapCache
55
+ } catch {
56
+ return null // 映射缺失不阻塞检索主路径;闸工具侧另行显式报错
57
+ }
58
+ }
41
59
 
42
60
  export const name = 'gotry-tools'
43
61
  export const inject = ['tools', 'systemPrompt']
@@ -737,6 +755,10 @@ export function apply(ctx: Context, config: Config): void {
737
755
  const itp = await interpretEffect({ effect: 'FLYAI_SEARCH', params: { kind, origin: q.from, destination: q.to, depDate: q.date } })
738
756
  if (!itp.result) return declinedObservation('FLYAI_SEARCH', itp.trace)
739
757
  const r = itp.result
758
+ // issue #46 事实落账(ADR-19):exact-date 检索结论(hit 正事实 / miss 负事实)追加进
759
+ // bookable-facts 侧车——产物事实闸(gotry_fact_gate)的唯一事实源;落盘失败不阻塞检索
760
+ const avMap = await loadAirlineAirportMap()
761
+ await appendFacts(config.stateRoot ?? '.', factsFromFlyai({ kind, origin: q.from, destination: q.to, date: q.date }, r, new Date().toISOString(), avMap?.city_alias))
740
762
  const top = (r.options ?? []).slice(0, 8).map(o => `${o.no} ${o.name} ${o.depDateTime.slice(11, 16)}→${o.arrDateTime.slice(11, 16)} ¥${o.price}`)
741
763
  // issue #24:miss(上游正常返回 0 条)与 error(限流/网络)分开陈述,不再混写「无结果或失败」
742
764
  // (过去日期已被上方预校验拦下,此处不会出现过期查询)
@@ -761,9 +783,10 @@ export function apply(ctx: Context, config: Config): void {
761
783
  name: 'gotry_session_login',
762
784
  description:
763
785
  'Productized login bootstrap for the account session channel (call this when gotry_session_search returns needs-login — the user never needs a terminal). ' + 'AUTO-DETECTION FIRST: it reads ticket-cookie NAMES before anything else — if the user already logged in (on the external site) it confirms instantly WITHOUT opening any page. '
764
- + 'OPENS the site login entry in the USER\'S OWN Chrome and waits for the user to finish logging in on the external site. '
786
+ + 'OPENS the site login entry in the USER\'S OWN Chrome (foreground tab, left open for the user) and waits for the user to finish logging in on the external site. '
765
787
  + 'GoTry NEVER collects, stores, or transmits credentials: no passwords, no SMS codes, no cookie values — it only checks the boolean fact "already logged in" (reads cookie NAMES only, zero values). '
766
- + 'verdict logged-in (tickets detected) | pending (login tab opened, user not done yet — offer to re-check later) | needs-attach (one-time Chrome remote-debugging switch instructions). '
788
+ + 'Transport: the GoTry Session Bridge browser extension (one-time install, ZERO Chrome system dialogs). '
789
+ + 'verdict logged-in (tickets detected) | pending (login tab opened, user not done yet — offer to re-check later) | needs-extension (one-time extension install: npx gotry setup, then chrome://extensions → load unpacked ~/.gotry/extension). '
767
790
  + 'Evidence [会话:<site>-login@ts].',
768
791
  parameters: {
769
792
  query: { type: 'json', required: true, description: '可空对象 {}: { waitSeconds?: number }(等待用户完成登录的上限秒数,默认 90,至多 300)' },
@@ -776,9 +799,11 @@ export function apply(ctx: Context, config: Config): void {
776
799
  ? `${r.site} 登录完成确认(票据 cookie 名已检出,只读名字)。说明:登录是在携程官网、用你自己的浏览器完成的——gotry 全程未接触任何密码/验证码/cookie 值。现在可以继续会话检索了。${r.evidence}`
777
800
  : r.verdict === 'pending'
778
801
  ? `登录入口已在你的 Chrome 打开;请在弹出的标签页里正常登录携程(登录由你在官网完成,不属于 gotry)。完成后说一声"继续",我再确认。gotry 只检查"是否已登录",永不收集你的账号信息。${r.evidence}`
779
- : r.verdict === 'needs-attach'
780
- ? `需要一次性开启你 Chrome 的远程调试开关:在你的 Chrome 地址栏打开 chrome://inspect/#remote-debugging 并打开开关,然后说一声"重试"。${r.evidence}`
781
- : `登录引导未完成:${r.error ?? '未知原因'} ${r.evidence}`
802
+ : r.verdict === 'needs-extension'
803
+ ? `需要一次性安装 GoTry Session Bridge 浏览器扩展(约 30 秒,装完零弹窗):跑 npx gotry setup 拿安装指引,在 chrome://extensions 开发者模式「加载已解压的扩展程序」指向 ~/.gotry/extension,装好后说一声"重试"。${r.evidence}`
804
+ : r.verdict === 'needs-attach'
805
+ ? `cdp 车道需要一次性开启你 Chrome 的远程调试开关:在你的 Chrome 地址栏打开 chrome://inspect/#remote-debugging 并打开开关,然后说一声"重试"(默认走扩展车道,无需此步)。${r.evidence}`
806
+ : `登录引导未完成:${r.error ?? '未知原因'} ${r.evidence}`
782
807
  return JSON.parse(JSON.stringify({ ...r, summary })) as Record<string, never>
783
808
  },
784
809
  presentCall: _args => ({ card: 'generic', title: '登录携程(用你自己的浏览器,不在 gotry 输入)', kind: 'fetch', rawInput: {} }),
@@ -794,9 +819,10 @@ export function apply(ctx: Context, config: Config): void {
794
819
  description:
795
820
  'Search on the USER\'S OWN logged-in browser session (Ctrip flights today; the account channel, not an anonymous instance). '
796
821
  + 'Consent gate: the FIRST call in a session asks the user via the runtime approval card; once granted it holds for the session, a refusal revokes it for the session (no repeat prompting). '
797
- + 'ReadGuard = physically read-only (write requests aborted at network layer; agent NEVER touches credentials/captcha; on captcha it stops and returns challenged). '
822
+ + 'Transport: GoTry Session Bridge browser extension (one-time install) — the agent side never talks to Chrome debugging, ZERO system dialogs; read-only by construction (the extension never issues requests; it only passively forwards the site\'s own search responses; agent NEVER touches credentials/captcha; on captcha it stops and returns challenged). '
798
823
  + 'Currently ctrip-flight: sniffs the site search API for structured options. Evidence [会话:ctrip-flight@ts]. '
799
- + 'verdict needs-login = call gotry_session_login (product tool: opens the Ctrip login entry in the user\'s own Chrome and waits for them to log in on the external site — no terminal, no credentials through GoTry); needs-attach = one-time Chrome remote-debugging switch. '
824
+ + 'verdict needs-login = call gotry_session_login (opens the Ctrip login entry in the user\'s own foreground tab — no terminal, no credentials through GoTry); '
825
+ + 'needs-extension = one-time browser-extension install (npx gotry setup for instructions) — the DEFAULT transport; cdp (chrome://inspect remote debugging) is a diagnostic fallback only via GOTRY_SESSION_TRANSPORT=cdp. '
800
826
  + 'Rate-limited (≥30s between same-site calls; a challenged/timeout verdict means STOP — never retry, fall back to other tools).',
801
827
  parameters: {
802
828
  query: { type: 'json', required: true, description: '{ from: "上海", to: "丽江", date: "2026-10-01" }' },
@@ -819,6 +845,9 @@ export function apply(ctx: Context, config: Config): void {
819
845
  })
820
846
  if (!itp.result) return declinedObservation('SESSION_FLIGHT_SEARCH', itp.trace)
821
847
  const r = itp.result
848
+ // issue #46 事实落账(ADR-19):会话面 exact-date 结论同样进事实注册表(独立 source 并列标注)
849
+ const avMapS = await loadAirlineAirportMap()
850
+ await appendFacts(config.stateRoot ?? '.', factsFromSession({ origin: q.from, destination: q.to, date: q.date }, r, new Date().toISOString(), avMapS?.city_alias))
822
851
  const top = (r.options ?? []).slice(0, 8).map(o => `${o.flightNo} ${o.airline} ${o.depDateTime.slice(11, 16)}→${o.arrDateTime.slice(11, 16)} ¥${o.price}`)
823
852
  const summary = r.verdict === 'hit'
824
853
  ? `${q.from}→${q.to} ${q.date} 会话检索(携程,用户本人登录态)前 ${top.length} 条:\n${top.join('\n')}\n${r.evidence}`
@@ -828,7 +857,13 @@ export function apply(ctx: Context, config: Config): void {
828
857
  presentCall: args => ({ card: 'generic', title: `会话检索:${String((args.query as { from?: string })?.from ?? '')}`, kind: 'fetch', rawInput: args.query }),
829
858
  presentResult: (_args, value) => {
830
859
  const r = value as { verdict?: string; options?: unknown[] }
831
- const label = r.verdict === 'hit' ? `会话 ${r.options!.length} 条` : (r.verdict === 'needs-login' ? '需登录' : r.verdict ?? '降级')
860
+ const label = r.verdict === 'hit'
861
+ ? `会话 ${r.options!.length} 条`
862
+ : r.verdict === 'needs-login'
863
+ ? '需登录'
864
+ : r.verdict === 'needs-extension'
865
+ ? '需装扩展(一次性)'
866
+ : r.verdict ?? '降级'
832
867
  return { card: 'generic', title: `会话检索:${label}`, content: [{ type: 'text', text: String((value as { summary?: string }).summary ?? '') }] }
833
868
  },
834
869
  }))
@@ -1181,4 +1216,63 @@ export function apply(ctx: Context, config: Config): void {
1181
1216
  }
1182
1217
  },
1183
1218
  }))
1219
+
1220
+ // ---- 产物事实闸(issue #46,ADR-19):含可下单事实的产物交付前必过 ----
1221
+ // 每个航班号/时刻/机场/价格/政策断言必须回溯到本会话 exact-date 工具结果
1222
+ // (bookable-facts 侧车,query_id 可重放);无法回溯即 blocked——不得宣称「已验证方案」。
1223
+
1224
+ registerGuarded(defineTool({
1225
+ name: 'gotry_fact_gate',
1226
+ description:
1227
+ 'Factuality gate for itinerary artifacts (issue #46): call BEFORE delivering any artifact that contains bookable facts '
1228
+ + '(flight numbers, times, airports, prices, visa/entry policies). Every bookable claim in the markdown is checked against '
1229
+ + 'the session fact registry (exact-date tool results recorded by gotry_flyai_search / gotry_session_search — hit AND miss). '
1230
+ + 'Blocked when: a claimed flight is absent from the exact-date source (never backfill from route pages/history/adjacent dates); '
1231
+ + 'route+date never queried; times contradict the snapshot; carrier→airport mapping conflicts (FD=DMK vs VZ=BKK are NOT interchangeable); '
1232
+ + '「联程」without protected_connection=true; policy claims without 「截至 YYYY-MM-DD」; unconditional ✓ on unverified claims. '
1233
+ + 'Optional itinerary object enables machine invariants (hotel_nights + onboard_nights = total nights, O&D segments vs flight legs, '
1234
+ + 'budget floor ≥ sum of item minimums). verdict=blocked ⇒ fix or downgrade wording — never present as a verified plan.',
1235
+ parameters: {
1236
+ query: {
1237
+ type: 'json',
1238
+ required: true,
1239
+ description: '{ markdown?: "<产物全文>", path?: "<产物路径(artifacts_list 返回的)>— 二选一", tripYear?: 2027, itinerary?: { trip_start, trip_end, stays: [{place,check_in,check_out}], onboard_nights, od_segments: [{from,to,date,mode,legs}], budget_items: [{label,min_cny,max_cny}], claimed_floor_cny? } }',
1240
+ },
1241
+ },
1242
+ output: {
1243
+ schema: { type: 'json' },
1244
+ render: (_args, value) => [{ type: 'text', text: String((value as { summary?: string }).summary ?? JSON.stringify(value).slice(0, 800)) }],
1245
+ },
1246
+ async execute(args: { query: unknown }, _exec: unknown) {
1247
+ const q = unwrapQuery<{ markdown?: string; path?: string; tripYear?: number; itinerary?: Record<string, unknown> }>(args, 'markdown')
1248
+ const avMap = await loadAirlineAirportMap()
1249
+ if (!avMap) {
1250
+ return JSON.parse(JSON.stringify({ ok: false, summary: '航司机场映射缺失(data/airline-airports.json)——事实闸不可用,fail closed:产物不得宣称已验证' })) as Record<string, never>
1251
+ }
1252
+ let markdown = q.markdown
1253
+ if (!markdown && q.path) {
1254
+ const r = await readArtifact({ stateRoot: config.stateRoot ?? '.', path: q.path })
1255
+ if (!r.ok) return JSON.parse(JSON.stringify({ ok: false, summary: `产物读取失败:${String((r as { error?: string }).error ?? '')}` })) as Record<string, never>
1256
+ markdown = r.content
1257
+ }
1258
+ if (!markdown) return JSON.parse(JSON.stringify({ ok: false, summary: '需要 markdown(产物全文)或 path(产物路径)' })) as Record<string, never>
1259
+ const itinerary = q.itinerary as import('./bookable-facts.ts').ItineraryFacts | undefined
1260
+ const registry = await loadFactRegistry(config.stateRoot ?? '.')
1261
+ const report = gateArtifact(markdown, registry, avMap, { trip_year: q.tripYear, itinerary })
1262
+ const lines = report.violations.slice(0, 20).map(v => ` L${v.line} [${v.kind}] ${v.detail}`)
1263
+ const summary = report.verdict === 'pass'
1264
+ ? `事实闸 PASS:${report.traceable}/${report.claims_checked} 可下单 claim 全部回溯到 exact-date 工具结果(query_id 可重放)——可宣称「已验证方案」。`
1265
+ : `事实闸 BLOCKED(${report.violations.length} 违例,${report.traceable}/${report.claims_checked} claim 可回溯)——不得宣称「已验证方案」:\n${lines.join('\n')}\n修正路径:逐条改成 exact-date 源返回的事实,或降级为「未确认/当前不可售,到 D-xx 复核」;exact-date miss 的 route+date 不得用历史班期/相邻日期/航线页填充。`
1266
+ return JSON.parse(JSON.stringify({ ok: true, ...report, summary })) as Record<string, never>
1267
+ },
1268
+ presentCall: args => ({ card: 'generic', title: '产物事实闸', kind: 'execute', rawInput: args.query }),
1269
+ presentResult: (_args, value) => {
1270
+ const r = value as { verdict?: string; violations?: unknown[]; summary?: string }
1271
+ return {
1272
+ card: 'generic',
1273
+ title: `事实闸:${r.verdict === 'pass' ? '✅ pass' : `⛔ blocked(${r.violations?.length ?? '?'})`}`,
1274
+ content: [{ type: 'text', text: String(r.summary ?? '') }],
1275
+ }
1276
+ },
1277
+ }))
1184
1278
  }