@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/dist/src/index.js CHANGED
@@ -5,7 +5,6 @@ import { ensureStateDir, recordLatency } from './bridge.js';
5
5
  import { segmentsFromCandidate, solveChoiceSegment } from './unified.js';
6
6
  import { checkConnectivity } from '../scripts/skeleton-check.js';
7
7
  import { parseCandidate, parseRequest } from './model.js';
8
- import { searchHotels as hbcliSearchHotels } from '../capabilities/hbcli.js';
9
8
  import { installProcessGuards, guardToolExecute } from '../capabilities/incident-log.js';
10
9
  import { interpretArgs } from './tool-packet.js';
11
10
  import { projectUtility } from './memory-utility.js';
@@ -14,16 +13,28 @@ import { resolveTimelineDate } from './travel-timeline.js';
14
13
  import { ensureLedger, readCompanionsWithFallback, readMotivationWithFallback, readTripsWithFallback, readWishPoolWithFallback } from './state-ledger.js';
15
14
  import { buildTimeAnchor } from './time-anchor.js';
16
15
  import { resolveSlotDate } from './slot-spec.js';
17
- import { geocodePlace, getForecast, getClimate, wmoLabel } from '../capabilities/weather.js';
18
- import { verifyFlight } from '../capabilities/opensky.js';
16
+ import { wmoLabel } from '../capabilities/weather.js';
19
17
  import { anythingSearch } from '../capabilities/anything.js';
20
18
  import { readUrl, reach, reachStatus } from '../capabilities/agent-reach.js';
21
19
  import { videoSubtitle, githubSearch } from '../capabilities/agent-reach-deep.js';
22
- import { flyaiSearch } from '../capabilities/flyai.js';
23
- import { sessionFlightSearch } from '../capabilities/session-search.js';
24
20
  import { sessionLogin } from '../capabilities/session-login.js';
25
21
  import { createConsentGate, approvalFromContext } from '../capabilities/session-consent.js';
26
22
  import { listArtifacts, readArtifact } from '../capabilities/artifacts.js';
23
+ import { interpretEffect, declinedObservation } from '../capabilities/effect.js';
24
+ import { appendFacts, loadFactRegistry } from '../capabilities/fact-log.js';
25
+ import { factsFromFlyai, factsFromSession } from './bookable-facts.js';
26
+ import { gateArtifact } from './artifact-gate.js';
27
+ let airlineAirportMapCache = null;
28
+ async function loadAirlineAirportMap() {
29
+ if (airlineAirportMapCache) return airlineAirportMapCache;
30
+ try {
31
+ const { readFile } = await import('node:fs/promises');
32
+ airlineAirportMapCache = JSON.parse(await readFile(join(import.meta.dirname, '..', '..', 'data', 'airline-airports.json'), 'utf-8'));
33
+ return airlineAirportMapCache;
34
+ } catch {
35
+ return null;
36
+ }
37
+ }
27
38
  export const name = 'gotry-tools';
28
39
  export const inject = [
29
40
  'tools',
@@ -549,16 +560,20 @@ export function apply(ctx, config) {
549
560
  if (r.raw !== r.date) dateNotes.push(`slot-resolved: ${r.raw} → ${r.date}`);
550
561
  return r.date;
551
562
  };
552
- const resp = await hbcliSearchHotels({
553
- destination: q.destination,
554
- checkIn: resolveDate(q.checkIn),
555
- checkOut: resolveDate(q.checkOut),
556
- adults: q.adults
557
- }, {
558
- hbcliBin: config.hbcliBin,
559
- timeoutMs: config.timeoutMs,
560
- fallbackPath
563
+ const itp = await interpretEffect({
564
+ effect: 'HBCLI_HOTEL_SEARCH',
565
+ params: {
566
+ destination: q.destination,
567
+ checkIn: resolveDate(q.checkIn),
568
+ checkOut: resolveDate(q.checkOut),
569
+ adults: q.adults,
570
+ hbcliBin: config.hbcliBin,
571
+ timeoutMs: config.timeoutMs,
572
+ fallbackPath
573
+ }
561
574
  });
575
+ if (!itp.result) return declinedObservation('HBCLI_HOTEL_SEARCH', itp.trace);
576
+ const resp = itp.result;
562
577
  const dir = await ensureStateDir(config.stateRoot);
563
578
  const isLive = resp.via === 'hbcli-realtime';
564
579
  const evidence = isLive ? resp.evidence : '[静态包:估算]';
@@ -672,7 +687,14 @@ export function apply(ctx, config) {
672
687
  let placeLabel = q.place ?? `${q.lat},${q.lng}`;
673
688
  if (lat === undefined || lng === undefined) {
674
689
  if (!q.place) throw new Error('gotry_weather_check requires place name or lat/lng');
675
- const geo = await geocodePlace(q.place);
690
+ const geoItp = await interpretEffect({
691
+ effect: 'WEATHER_GEOCODE',
692
+ params: {
693
+ name: q.place
694
+ }
695
+ });
696
+ if (!geoItp.result) return declinedObservation('WEATHER_GEOCODE', geoItp.trace);
697
+ const geo = geoItp.result;
676
698
  if (!geo.ok || geo.results.length === 0) {
677
699
  return JSON.parse(JSON.stringify({
678
700
  ok: false,
@@ -686,15 +708,23 @@ export function apply(ctx, config) {
686
708
  placeLabel = `${hit.name}(${hit.admin1 ?? hit.country ?? ''})`;
687
709
  }
688
710
  const isClimate = q.mode === 'climate' || q.month !== undefined && q.mode !== 'forecast';
689
- const r = isClimate ? await getClimate({
690
- latitude: lat,
691
- longitude: lng
692
- }, q.month ?? new Date().getMonth() + 1) : await getForecast({
693
- latitude: lat,
694
- longitude: lng
695
- }, {
696
- days: q.days
711
+ const wxItp = isClimate ? await interpretEffect({
712
+ effect: 'WEATHER_CLIMATE',
713
+ params: {
714
+ latitude: lat,
715
+ longitude: lng,
716
+ month: q.month ?? new Date().getMonth() + 1
717
+ }
718
+ }) : await interpretEffect({
719
+ effect: 'WEATHER_FORECAST',
720
+ params: {
721
+ latitude: lat,
722
+ longitude: lng,
723
+ days: q.days
724
+ }
697
725
  });
726
+ if (!wxItp.result) return declinedObservation(isClimate ? 'WEATHER_CLIMATE' : 'WEATHER_FORECAST', wxItp.trace);
727
+ const r = wxItp.result;
698
728
  const dir = await ensureStateDir(config.stateRoot);
699
729
  await recordLatency(join(dir, 'bridge-latency.jsonl'), Date.now() - started, `weather:${r.via}`).catch(()=>{});
700
730
  const dailyLines = (r.daily ?? []).slice(0, 7).map((d)=>`${d.date} ${d.tempMinC.toFixed(0)}–${d.tempMaxC.toFixed(0)}°C ${wmoLabel(d.weatherCode)}${d.precipProbMaxPct !== null ? ` 降水概率${d.precipProbMaxPct}%` : ''}`);
@@ -762,11 +792,16 @@ export function apply(ctx, config) {
762
792
  summary: 'callsign 必填'
763
793
  }));
764
794
  }
765
- const r = await verifyFlight({
766
- callsign: q.callsign,
767
- airport: q.airport,
768
- timeoutMs: q.timeoutMs
795
+ const itp = await interpretEffect({
796
+ effect: 'OPENSKY_FLIGHT_VERIFY',
797
+ params: {
798
+ callsign: q.callsign,
799
+ airport: q.airport,
800
+ timeoutMs: q.timeoutMs
801
+ }
769
802
  });
803
+ if (!itp.result) return declinedObservation('OPENSKY_FLIGHT_VERIFY', itp.trace);
804
+ const r = itp.result;
770
805
  const dir = await ensureStateDir(config.stateRoot);
771
806
  await recordLatency(join(dir, 'bridge-latency.jsonl'), Date.now() - started, `flight_verify:${r.via}`).catch(()=>{});
772
807
  const summary = r.verdict === 'observed' ? `${r.callsign} 当前 ADS-B 观测命中 (${r.hits?.length ?? 0} 架)${r.airport ? ` 在 ${r.airport}` : ''}\n${r.evidence}` : r.verdict === 'not_observed' ? `${r.callsign} 当前观测列表未见(ADS-B 覆盖有限,不否定该航班存在)\n${r.evidence}` : `${r.callsign} OpenSky 不可用:${r.error}\n${r.evidence}`;
@@ -842,13 +877,18 @@ export function apply(ctx, config) {
842
877
  };
843
878
  }
844
879
  }
845
- const r = await flyaiSearch({
846
- kind: 'hotel',
847
- destName: dest,
848
- checkInDate: q.checkIn,
849
- checkOutDate: q.checkOut,
850
- keyWords: q.keyWords
880
+ const itp = await interpretEffect({
881
+ effect: 'FLYAI_SEARCH',
882
+ params: {
883
+ kind: 'hotel',
884
+ destName: dest,
885
+ checkInDate: q.checkIn,
886
+ checkOutDate: q.checkOut,
887
+ keyWords: q.keyWords
888
+ }
851
889
  });
890
+ if (!itp.result) return declinedObservation('FLYAI_SEARCH', itp.trace);
891
+ const r = itp.result;
852
892
  const top = (r.hotels ?? []).slice(0, 8).map((o)=>`${o.name}${o.star ? `(${o.star})` : ''} ${o.priceRaw ?? '价待询'}${o.poi ? ` · ${o.poi}` : ''}`);
853
893
  const summary = r.verdict === 'hit' ? `${dest} 酒店(飞猪官方只读)前 ${top.length} 家(价格多为打码,真实价以 jumpUrl 为准):\n${top.join('\n')}\n${r.evidence}` : `${dest} 酒店无结果或失败:${r.error ?? 'miss'} ${r.evidence}`;
854
894
  return JSON.parse(JSON.stringify({
@@ -873,12 +913,24 @@ export function apply(ctx, config) {
873
913
  summary: `未发起查询:日期 ${q.date} 已是过去(今天 ${todayYmd}),过去不存在在售机/火车票。` + `多为用户时间表达未带年份所致——向用户确认年份(或按未来最近的同月日修正)后再查。`
874
914
  }));
875
915
  }
876
- const r = await flyaiSearch({
916
+ const itp = await interpretEffect({
917
+ effect: 'FLYAI_SEARCH',
918
+ params: {
919
+ kind,
920
+ origin: q.from,
921
+ destination: q.to,
922
+ depDate: q.date
923
+ }
924
+ });
925
+ if (!itp.result) return declinedObservation('FLYAI_SEARCH', itp.trace);
926
+ const r = itp.result;
927
+ const avMap = await loadAirlineAirportMap();
928
+ await appendFacts(config.stateRoot ?? '.', factsFromFlyai({
877
929
  kind,
878
930
  origin: q.from,
879
931
  destination: q.to,
880
- depDate: q.date
881
- });
932
+ date: q.date
933
+ }, r, new Date().toISOString(), avMap?.city_alias));
882
934
  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}`);
883
935
  const label = kind === 'flight' ? '机票' : '火车票';
884
936
  const summary = r.verdict === 'hit' ? `${q.from}→${q.to} ${q.date} ${label}(飞猪官方只读)前 ${top.length} 条:\n${top.join('\n')}\n${r.evidence}` : r.verdict === 'miss' ? `${q.from}→${q.to} ${q.date} ${label}官方通道正常返回 0 条(常见原因:航线未开放/当日售罄)。${r.evidence}` : `${q.from}→${q.to} ${q.date} ${label}检索失败(可能限流/网络):${r.error ?? ''} ${r.evidence}`;
@@ -912,7 +964,7 @@ export function apply(ctx, config) {
912
964
  }));
913
965
  registerGuarded(defineTool({
914
966
  name: 'gotry_session_login',
915
- description: '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. ' + 'OPENS the site login entry in the USER\'S OWN Chrome and waits for the user to finish logging in on the external site. ' + '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). ' + '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). ' + 'Evidence [会话:<site>-login@ts].',
967
+ description: '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. ' + '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. ' + '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). ' + 'Transport: the GoTry Session Bridge browser extension (one-time install, ZERO Chrome system dialogs). ' + '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). ' + 'Evidence [会话:<site>-login@ts].',
916
968
  parameters: {
917
969
  query: {
918
970
  type: 'json',
@@ -936,7 +988,7 @@ export function apply(ctx, config) {
936
988
  const r = await sessionLogin({
937
989
  waitMs: typeof q?.waitSeconds === 'number' ? q.waitSeconds * 1000 : undefined
938
990
  });
939
- const summary = r.verdict === 'logged-in' ? `${r.site} 登录完成确认(票据 cookie 名已检出,只读名字)。说明:登录是在携程官网、用你自己的浏览器完成的——gotry 全程未接触任何密码/验证码/cookie 值。现在可以继续会话检索了。${r.evidence}` : r.verdict === 'pending' ? `登录入口已在你的 Chrome 打开;请在弹出的标签页里正常登录携程(登录由你在官网完成,不属于 gotry)。完成后说一声"继续",我再确认。gotry 只检查"是否已登录",永不收集你的账号信息。${r.evidence}` : r.verdict === 'needs-attach' ? `需要一次性开启你 Chrome 的远程调试开关:在你的 Chrome 地址栏打开 chrome://inspect/#remote-debugging 并打开开关,然后说一声"重试"。${r.evidence}` : `登录引导未完成:${r.error ?? '未知原因'} ${r.evidence}`;
991
+ const summary = r.verdict === 'logged-in' ? `${r.site} 登录完成确认(票据 cookie 名已检出,只读名字)。说明:登录是在携程官网、用你自己的浏览器完成的——gotry 全程未接触任何密码/验证码/cookie 值。现在可以继续会话检索了。${r.evidence}` : r.verdict === 'pending' ? `登录入口已在你的 Chrome 打开;请在弹出的标签页里正常登录携程(登录由你在官网完成,不属于 gotry)。完成后说一声"继续",我再确认。gotry 只检查"是否已登录",永不收集你的账号信息。${r.evidence}` : r.verdict === 'needs-extension' ? `需要一次性安装 GoTry Session Bridge 浏览器扩展(约 30 秒,装完零弹窗):跑 npx gotry setup 拿安装指引,在 chrome://extensions 开发者模式「加载已解压的扩展程序」指向 ~/.gotry/extension,装好后说一声"重试"。${r.evidence}` : r.verdict === 'needs-attach' ? `cdp 车道需要一次性开启你 Chrome 的远程调试开关:在你的 Chrome 地址栏打开 chrome://inspect/#remote-debugging 并打开开关,然后说一声"重试"(默认走扩展车道,无需此步)。${r.evidence}` : `登录引导未完成:${r.error ?? '未知原因'} ${r.evidence}`;
940
992
  return JSON.parse(JSON.stringify({
941
993
  ...r,
942
994
  summary
@@ -965,7 +1017,7 @@ export function apply(ctx, config) {
965
1017
  }));
966
1018
  registerGuarded(defineTool({
967
1019
  name: 'gotry_session_search',
968
- description: 'Search on the USER\'S OWN logged-in browser session (Ctrip flights today; the account channel, not an anonymous instance). ' + '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). ' + 'ReadGuard = physically read-only (write requests aborted at network layer; agent NEVER touches credentials/captcha; on captcha it stops and returns challenged). ' + 'Currently ctrip-flight: sniffs the site search API for structured options. Evidence [会话:ctrip-flight@ts]. ' + '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. ' + 'Rate-limited (≥30s between same-site calls; a challenged/timeout verdict means STOP — never retry, fall back to other tools).',
1020
+ description: 'Search on the USER\'S OWN logged-in browser session (Ctrip flights today; the account channel, not an anonymous instance). ' + '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). ' + '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). ' + 'Currently ctrip-flight: sniffs the site search API for structured options. Evidence [会话:ctrip-flight@ts]. ' + '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); ' + '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. ' + 'Rate-limited (≥30s between same-site calls; a challenged/timeout verdict means STOP — never retry, fall back to other tools).',
969
1021
  parameters: {
970
1022
  query: {
971
1023
  type: 'json',
@@ -992,12 +1044,23 @@ export function apply(ctx, config) {
992
1044
  summary: '需要 from/to(中文城市名,词表内)与 date(YYYY-MM-DD)'
993
1045
  };
994
1046
  }
995
- const r = await sessionFlightSearch({
996
- from: q.from,
997
- to: q.to,
998
- date: q.date,
999
- auditPath: join(config.stateRoot ?? '.', 'gotry-state', 'session-incidents.jsonl')
1047
+ const itp = await interpretEffect({
1048
+ effect: 'SESSION_FLIGHT_SEARCH',
1049
+ params: {
1050
+ from: q.from,
1051
+ to: q.to,
1052
+ date: q.date,
1053
+ auditPath: join(config.stateRoot ?? '.', 'gotry-state', 'session-incidents.jsonl')
1054
+ }
1000
1055
  });
1056
+ if (!itp.result) return declinedObservation('SESSION_FLIGHT_SEARCH', itp.trace);
1057
+ const r = itp.result;
1058
+ const avMapS = await loadAirlineAirportMap();
1059
+ await appendFacts(config.stateRoot ?? '.', factsFromSession({
1060
+ origin: q.from,
1061
+ destination: q.to,
1062
+ date: q.date
1063
+ }, r, new Date().toISOString(), avMapS?.city_alias));
1001
1064
  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}`);
1002
1065
  const summary = r.verdict === 'hit' ? `${q.from}→${q.to} ${q.date} 会话检索(携程,用户本人登录态)前 ${top.length} 条:\n${top.join('\n')}\n${r.evidence}` : `会话检索未取回(${r.verdict}):${r.error ?? ''} ${r.evidence}`;
1003
1066
  return JSON.parse(JSON.stringify({
@@ -1013,7 +1076,7 @@ export function apply(ctx, config) {
1013
1076
  }),
1014
1077
  presentResult: (_args, value)=>{
1015
1078
  const r = value;
1016
- const label = r.verdict === 'hit' ? `会话 ${r.options.length} 条` : r.verdict === 'needs-login' ? '需登录' : r.verdict ?? '降级';
1079
+ const label = r.verdict === 'hit' ? `会话 ${r.options.length} 条` : r.verdict === 'needs-login' ? '需登录' : r.verdict === 'needs-extension' ? '需装扩展(一次性)' : r.verdict ?? '降级';
1017
1080
  return {
1018
1081
  card: 'generic',
1019
1082
  title: `会话检索:${label}`,
@@ -1490,7 +1553,87 @@ export function apply(ctx, config) {
1490
1553
  };
1491
1554
  }
1492
1555
  }));
1556
+ registerGuarded(defineTool({
1557
+ name: 'gotry_fact_gate',
1558
+ description: 'Factuality gate for itinerary artifacts (issue #46): call BEFORE delivering any artifact that contains bookable facts ' + '(flight numbers, times, airports, prices, visa/entry policies). Every bookable claim in the markdown is checked against ' + 'the session fact registry (exact-date tool results recorded by gotry_flyai_search / gotry_session_search — hit AND miss). ' + 'Blocked when: a claimed flight is absent from the exact-date source (never backfill from route pages/history/adjacent dates); ' + 'route+date never queried; times contradict the snapshot; carrier→airport mapping conflicts (FD=DMK vs VZ=BKK are NOT interchangeable); ' + '「联程」without protected_connection=true; policy claims without 「截至 YYYY-MM-DD」; unconditional ✓ on unverified claims. ' + 'Optional itinerary object enables machine invariants (hotel_nights + onboard_nights = total nights, O&D segments vs flight legs, ' + 'budget floor ≥ sum of item minimums). verdict=blocked ⇒ fix or downgrade wording — never present as a verified plan.',
1559
+ parameters: {
1560
+ query: {
1561
+ type: 'json',
1562
+ required: true,
1563
+ 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? } }'
1564
+ }
1565
+ },
1566
+ output: {
1567
+ schema: {
1568
+ type: 'json'
1569
+ },
1570
+ render: (_args, value)=>[
1571
+ {
1572
+ type: 'text',
1573
+ text: String(value.summary ?? JSON.stringify(value).slice(0, 800))
1574
+ }
1575
+ ]
1576
+ },
1577
+ async execute (args, _exec) {
1578
+ const q = unwrapQuery(args, 'markdown');
1579
+ const avMap = await loadAirlineAirportMap();
1580
+ if (!avMap) {
1581
+ return JSON.parse(JSON.stringify({
1582
+ ok: false,
1583
+ summary: '航司机场映射缺失(data/airline-airports.json)——事实闸不可用,fail closed:产物不得宣称已验证'
1584
+ }));
1585
+ }
1586
+ let markdown = q.markdown;
1587
+ if (!markdown && q.path) {
1588
+ const r = await readArtifact({
1589
+ stateRoot: config.stateRoot ?? '.',
1590
+ path: q.path
1591
+ });
1592
+ if (!r.ok) return JSON.parse(JSON.stringify({
1593
+ ok: false,
1594
+ summary: `产物读取失败:${String(r.error ?? '')}`
1595
+ }));
1596
+ markdown = r.content;
1597
+ }
1598
+ if (!markdown) return JSON.parse(JSON.stringify({
1599
+ ok: false,
1600
+ summary: '需要 markdown(产物全文)或 path(产物路径)'
1601
+ }));
1602
+ const itinerary = q.itinerary;
1603
+ const registry = await loadFactRegistry(config.stateRoot ?? '.');
1604
+ const report = gateArtifact(markdown, registry, avMap, {
1605
+ trip_year: q.tripYear,
1606
+ itinerary
1607
+ });
1608
+ const lines = report.violations.slice(0, 20).map((v)=>` L${v.line} [${v.kind}] ${v.detail}`);
1609
+ const summary = report.verdict === 'pass' ? `事实闸 PASS:${report.traceable}/${report.claims_checked} 可下单 claim 全部回溯到 exact-date 工具结果(query_id 可重放)——可宣称「已验证方案」。` : `事实闸 BLOCKED(${report.violations.length} 违例,${report.traceable}/${report.claims_checked} claim 可回溯)——不得宣称「已验证方案」:\n${lines.join('\n')}\n修正路径:逐条改成 exact-date 源返回的事实,或降级为「未确认/当前不可售,到 D-xx 复核」;exact-date miss 的 route+date 不得用历史班期/相邻日期/航线页填充。`;
1610
+ return JSON.parse(JSON.stringify({
1611
+ ok: true,
1612
+ ...report,
1613
+ summary
1614
+ }));
1615
+ },
1616
+ presentCall: (args)=>({
1617
+ card: 'generic',
1618
+ title: '产物事实闸',
1619
+ kind: 'execute',
1620
+ rawInput: args.query
1621
+ }),
1622
+ presentResult: (_args, value)=>{
1623
+ const r = value;
1624
+ return {
1625
+ card: 'generic',
1626
+ title: `事实闸:${r.verdict === 'pass' ? '✅ pass' : `⛔ blocked(${r.violations?.length ?? '?'})`}`,
1627
+ content: [
1628
+ {
1629
+ type: 'text',
1630
+ text: String(r.summary ?? '')
1631
+ }
1632
+ ]
1633
+ };
1634
+ }
1635
+ }));
1493
1636
  }
1494
1637
 
1495
1638
 
1496
- //# sourceURL=/Users/bytedance/work/gotry/ts/src/index.ts
1639
+ //# sourceURL=/Users/bytedance/work/gotry-publish/ts/src/index.ts
@@ -140,4 +140,4 @@ export function parseFlightPackLegs(pack) {
140
140
  }
141
141
 
142
142
 
143
- //# sourceURL=/Users/bytedance/work/gotry/ts/src/journey.ts
143
+ //# sourceURL=/Users/bytedance/work/gotry-publish/ts/src/journey.ts
package/dist/src/loop.js CHANGED
@@ -408,4 +408,4 @@ export function probePoi(msg) {
408
408
  }
409
409
 
410
410
 
411
- //# sourceURL=/Users/bytedance/work/gotry/ts/src/loop.ts
411
+ //# sourceURL=/Users/bytedance/work/gotry-publish/ts/src/loop.ts
@@ -37,4 +37,4 @@ export function mergeProfile(current, patch) {
37
37
  }
38
38
 
39
39
 
40
- //# sourceURL=/Users/bytedance/work/gotry/ts/src/memory-capture.ts
40
+ //# sourceURL=/Users/bytedance/work/gotry-publish/ts/src/memory-capture.ts
@@ -28,4 +28,4 @@ function round4(x) {
28
28
  }
29
29
 
30
30
 
31
- //# sourceURL=/Users/bytedance/work/gotry/ts/src/memory-decay.ts
31
+ //# sourceURL=/Users/bytedance/work/gotry-publish/ts/src/memory-decay.ts
@@ -52,4 +52,4 @@ export function projectUtility(events) {
52
52
  }
53
53
 
54
54
 
55
- //# sourceURL=/Users/bytedance/work/gotry/ts/src/memory-utility.ts
55
+ //# sourceURL=/Users/bytedance/work/gotry-publish/ts/src/memory-utility.ts
@@ -100,4 +100,4 @@ export function createMockLlm(flightPackPath, slotScript = []) {
100
100
  }
101
101
 
102
102
 
103
- //# sourceURL=/Users/bytedance/work/gotry/ts/src/mock-llm.ts
103
+ //# sourceURL=/Users/bytedance/work/gotry-publish/ts/src/mock-llm.ts
package/dist/src/model.js CHANGED
@@ -131,4 +131,4 @@ export function trueCostToDict(t) {
131
131
  }
132
132
 
133
133
 
134
- //# sourceURL=/Users/bytedance/work/gotry/ts/src/model.ts
134
+ //# sourceURL=/Users/bytedance/work/gotry-publish/ts/src/model.ts
@@ -1,4 +1,4 @@
1
- import { flyaiSearch } from '../capabilities/flyai.js';
1
+ import { interpretEffect } from '../capabilities/effect.js';
2
2
  const IATA_TO_CITY = {
3
3
  HKG: '香港',
4
4
  HKT: '普吉',
@@ -76,12 +76,19 @@ export async function overlayRealtimeFlightPrices(spec, opts = {}) {
76
76
  }
77
77
  return out;
78
78
  }
79
- const defaultQuery = (q)=>flyaiSearch({
80
- kind: q.kind,
81
- origin: q.origin,
82
- destination: q.destination,
83
- depDate: q.depDate
79
+ const defaultQuery = async (q)=>{
80
+ const itp = await interpretEffect({
81
+ effect: 'FLYAI_SEARCH',
82
+ params: {
83
+ kind: q.kind,
84
+ origin: q.origin,
85
+ destination: q.destination,
86
+ depDate: q.depDate
87
+ }
84
88
  });
89
+ if (!itp.result) throw new Error(itp.trace.evidence.join(';'));
90
+ return itp.result;
91
+ };
85
92
  export function realtimeSolvePort(base, opts = {}) {
86
93
  return async (spec)=>{
87
94
  const enabled = opts.enabled ?? process.env['GOTRY_REALTIME_PRICING'] === '1';
@@ -102,4 +109,4 @@ export function realtimeSolvePort(base, opts = {}) {
102
109
  }
103
110
 
104
111
 
105
- //# sourceURL=/Users/bytedance/work/gotry/ts/src/realtime-pricing.ts
112
+ //# sourceURL=/Users/bytedance/work/gotry-publish/ts/src/realtime-pricing.ts
@@ -162,4 +162,4 @@ export function specDateMismatches(spec, resolved) {
162
162
  }
163
163
 
164
164
 
165
- //# sourceURL=/Users/bytedance/work/gotry/ts/src/slot-spec.ts
165
+ //# sourceURL=/Users/bytedance/work/gotry-publish/ts/src/slot-spec.ts
@@ -924,4 +924,4 @@ export function readCompanionsWithFallback(stateRoot) {
924
924
  }
925
925
 
926
926
 
927
- //# sourceURL=/Users/bytedance/work/gotry/ts/src/state-ledger.ts
927
+ //# sourceURL=/Users/bytedance/work/gotry-publish/ts/src/state-ledger.ts
@@ -97,4 +97,4 @@ export function parseAbsoluteDate(expr, anchorYear) {
97
97
  }
98
98
 
99
99
 
100
- //# sourceURL=/Users/bytedance/work/gotry/ts/src/time-anchor.ts
100
+ //# sourceURL=/Users/bytedance/work/gotry-publish/ts/src/time-anchor.ts
@@ -9,4 +9,4 @@ export function interpretArgs(args, stringKey) {
9
9
  }
10
10
 
11
11
 
12
- //# sourceURL=/Users/bytedance/work/gotry/ts/src/tool-packet.ts
12
+ //# sourceURL=/Users/bytedance/work/gotry-publish/ts/src/tool-packet.ts
@@ -141,4 +141,4 @@ export function scoreExtraction(expected, actual) {
141
141
  }
142
142
 
143
143
 
144
- //# sourceURL=/Users/bytedance/work/gotry/ts/src/travel-slots.ts
144
+ //# sourceURL=/Users/bytedance/work/gotry-publish/ts/src/travel-slots.ts
@@ -75,4 +75,4 @@ export function timelineGapsForVerified(timeline, verifiedWishes) {
75
75
  export { ymd };
76
76
 
77
77
 
78
- //# sourceURL=/Users/bytedance/work/gotry/ts/src/travel-timeline.ts
78
+ //# sourceURL=/Users/bytedance/work/gotry-publish/ts/src/travel-timeline.ts
@@ -588,4 +588,4 @@ function renderCandidateMarkdown(req, verdicts, recommended) {
588
588
  export { minToHhmm };
589
589
 
590
590
 
591
- //# sourceURL=/Users/bytedance/work/gotry/ts/src/unified.ts
591
+ //# sourceURL=/Users/bytedance/work/gotry-publish/ts/src/unified.ts
@@ -24,4 +24,4 @@ export function pickNudgeWish(pool, ctx) {
24
24
  }
25
25
 
26
26
 
27
- //# sourceURL=/Users/bytedance/work/gotry/ts/src/wish-pool.ts
27
+ //# sourceURL=/Users/bytedance/work/gotry-publish/ts/src/wish-pool.ts
@@ -15,4 +15,4 @@ export async function withZ3(label, session) {
15
15
  }
16
16
 
17
17
 
18
- //# sourceURL=/Users/bytedance/work/gotry/ts/src/z3-shared.ts
18
+ //# sourceURL=/Users/bytedance/work/gotry-publish/ts/src/z3-shared.ts
@@ -0,0 +1,26 @@
1
+ # GoTry Session Bridge(浏览器扩展)
2
+
3
+ 一次性安装的会话检索数据面(issue #21 传输层方案 C,2026-08-29 founder 定案):在你**自己的** Chrome 登录态里,只读嗅探携程机票检索回包([会话:ctrip-flight]。gotry 不再需要逐连接弹 Chrome 权限框,也不需要开 `chrome://inspect` 远程调试。
4
+
5
+ ## 它做什么 / 不做什么
6
+
7
+ - ✅ 检索:你(或 agent)发起会话检索时,后台标签页打开携程机票列表页,被动嗅探页面自己发出的 `batchSearch` 响应,文本回传给本机的 gotry。收尾自动关闭自己开的标签。
8
+ - ✅ 登录检查:只读登录票据 cookie 的**名字**(cticket/uid/uname/passport),判断「是否已登录」这个布尔事实。
9
+ - ✅ 登录引导:把登录入口页**置前台**打开,登录由你在携程官网完成。
10
+ - ❌ **不读取、不存储、不回传任何 cookie 值,不接触密码/验证码**;
11
+ - ❌ 不向任何站点发起请求(检索请求由站点页面自己发出,扩展只听不写);
12
+ - ❌ 不动你已打开的任何标签页(只创建/关闭自己的检索标签;登录标签留给你)。
13
+
14
+ ## 安装(每台浏览器一次,约 30 秒)
15
+
16
+ 1. 跑一次 `npx gotry setup`(或手动:把本目录整体拷到任意固定位置,建议 `~/.gotry/extension/`);
17
+ 2. Chrome 地址栏打开 `chrome://extensions`;
18
+ 3. 右上角开启「开发者模式」;
19
+ 4. 点「加载已解压的扩展程序」,选择本目录(manifest.json 所在目录)。
20
+
21
+ 装好即生效,无任何系统弹窗。卸载/随时可停:扩展卡片上的开关即是总闸(仍受 gotry 授权闸 `sessionAccess` 双重控制)。
22
+
23
+ ## 自检
24
+
25
+ - 扩展 ID 应为固定的 `olpgkofjhhiiiahdkkbcninhjmegghfe`(manifest 带 `key`,跨机器稳定)——若不一致,说明 manifest 被改动过,不要安装;
26
+ - gotry 侧运行 `npx gotry` 后发起一次会话检索,扩展图标应出现(无需点击);桥状态可看 `curl http://127.0.0.1:8791/status`(仅本机回环)。