@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.
- package/README.md +26 -20
- package/README.zh-CN.md +27 -21
- package/bin/gotry-bootstrap.js +331 -3
- 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 +217 -0
- 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 +187 -0
- package/dist/scripts/bootstrap-tests.js +49 -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 +186 -0
- package/dist/scripts/nightly-evidence.js +289 -0
- 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 +159 -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 +153 -0
- 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 +31 -7
- package/dist/src/engine.js +1 -1
- package/dist/src/i18n.js +1 -1
- package/dist/src/index.js +322 -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 +8 -2
- package/ts/capabilities/artifacts.ts +235 -0
- 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/booking-saga.ts +133 -0
- package/ts/src/dsh-llm.ts +40 -3
- package/ts/src/index.ts +230 -28
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,15 +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 {
|
|
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';
|
|
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
|
+
}
|
|
26
38
|
export const name = 'gotry-tools';
|
|
27
39
|
export const inject = [
|
|
28
40
|
'tools',
|
|
@@ -548,16 +560,20 @@ export function apply(ctx, config) {
|
|
|
548
560
|
if (r.raw !== r.date) dateNotes.push(`slot-resolved: ${r.raw} → ${r.date}`);
|
|
549
561
|
return r.date;
|
|
550
562
|
};
|
|
551
|
-
const
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
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
|
+
}
|
|
560
574
|
});
|
|
575
|
+
if (!itp.result) return declinedObservation('HBCLI_HOTEL_SEARCH', itp.trace);
|
|
576
|
+
const resp = itp.result;
|
|
561
577
|
const dir = await ensureStateDir(config.stateRoot);
|
|
562
578
|
const isLive = resp.via === 'hbcli-realtime';
|
|
563
579
|
const evidence = isLive ? resp.evidence : '[静态包:估算]';
|
|
@@ -671,7 +687,14 @@ export function apply(ctx, config) {
|
|
|
671
687
|
let placeLabel = q.place ?? `${q.lat},${q.lng}`;
|
|
672
688
|
if (lat === undefined || lng === undefined) {
|
|
673
689
|
if (!q.place) throw new Error('gotry_weather_check requires place name or lat/lng');
|
|
674
|
-
const
|
|
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;
|
|
675
698
|
if (!geo.ok || geo.results.length === 0) {
|
|
676
699
|
return JSON.parse(JSON.stringify({
|
|
677
700
|
ok: false,
|
|
@@ -685,15 +708,23 @@ export function apply(ctx, config) {
|
|
|
685
708
|
placeLabel = `${hit.name}(${hit.admin1 ?? hit.country ?? ''})`;
|
|
686
709
|
}
|
|
687
710
|
const isClimate = q.mode === 'climate' || q.month !== undefined && q.mode !== 'forecast';
|
|
688
|
-
const
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
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
|
+
}
|
|
696
725
|
});
|
|
726
|
+
if (!wxItp.result) return declinedObservation(isClimate ? 'WEATHER_CLIMATE' : 'WEATHER_FORECAST', wxItp.trace);
|
|
727
|
+
const r = wxItp.result;
|
|
697
728
|
const dir = await ensureStateDir(config.stateRoot);
|
|
698
729
|
await recordLatency(join(dir, 'bridge-latency.jsonl'), Date.now() - started, `weather:${r.via}`).catch(()=>{});
|
|
699
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}%` : ''}`);
|
|
@@ -761,11 +792,16 @@ export function apply(ctx, config) {
|
|
|
761
792
|
summary: 'callsign 必填'
|
|
762
793
|
}));
|
|
763
794
|
}
|
|
764
|
-
const
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
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
|
+
}
|
|
768
802
|
});
|
|
803
|
+
if (!itp.result) return declinedObservation('OPENSKY_FLIGHT_VERIFY', itp.trace);
|
|
804
|
+
const r = itp.result;
|
|
769
805
|
const dir = await ensureStateDir(config.stateRoot);
|
|
770
806
|
await recordLatency(join(dir, 'bridge-latency.jsonl'), Date.now() - started, `flight_verify:${r.via}`).catch(()=>{});
|
|
771
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}`;
|
|
@@ -841,13 +877,18 @@ export function apply(ctx, config) {
|
|
|
841
877
|
};
|
|
842
878
|
}
|
|
843
879
|
}
|
|
844
|
-
const
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
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
|
+
}
|
|
850
889
|
});
|
|
890
|
+
if (!itp.result) return declinedObservation('FLYAI_SEARCH', itp.trace);
|
|
891
|
+
const r = itp.result;
|
|
851
892
|
const top = (r.hotels ?? []).slice(0, 8).map((o)=>`${o.name}${o.star ? `(${o.star})` : ''} ${o.priceRaw ?? '价待询'}${o.poi ? ` · ${o.poi}` : ''}`);
|
|
852
893
|
const summary = r.verdict === 'hit' ? `${dest} 酒店(飞猪官方只读)前 ${top.length} 家(价格多为打码,真实价以 jumpUrl 为准):\n${top.join('\n')}\n${r.evidence}` : `${dest} 酒店无结果或失败:${r.error ?? 'miss'} ${r.evidence}`;
|
|
853
894
|
return JSON.parse(JSON.stringify({
|
|
@@ -872,12 +913,24 @@ export function apply(ctx, config) {
|
|
|
872
913
|
summary: `未发起查询:日期 ${q.date} 已是过去(今天 ${todayYmd}),过去不存在在售机/火车票。` + `多为用户时间表达未带年份所致——向用户确认年份(或按未来最近的同月日修正)后再查。`
|
|
873
914
|
}));
|
|
874
915
|
}
|
|
875
|
-
const
|
|
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({
|
|
876
929
|
kind,
|
|
877
930
|
origin: q.from,
|
|
878
931
|
destination: q.to,
|
|
879
|
-
|
|
880
|
-
});
|
|
932
|
+
date: q.date
|
|
933
|
+
}, r, new Date().toISOString(), avMap?.city_alias));
|
|
881
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}`);
|
|
882
935
|
const label = kind === 'flight' ? '机票' : '火车票';
|
|
883
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}`;
|
|
@@ -911,7 +964,7 @@ export function apply(ctx, config) {
|
|
|
911
964
|
}));
|
|
912
965
|
registerGuarded(defineTool({
|
|
913
966
|
name: 'gotry_session_login',
|
|
914
|
-
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-
|
|
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].',
|
|
915
968
|
parameters: {
|
|
916
969
|
query: {
|
|
917
970
|
type: 'json',
|
|
@@ -935,7 +988,7 @@ export function apply(ctx, config) {
|
|
|
935
988
|
const r = await sessionLogin({
|
|
936
989
|
waitMs: typeof q?.waitSeconds === 'number' ? q.waitSeconds * 1000 : undefined
|
|
937
990
|
});
|
|
938
|
-
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' ?
|
|
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}`;
|
|
939
992
|
return JSON.parse(JSON.stringify({
|
|
940
993
|
...r,
|
|
941
994
|
summary
|
|
@@ -964,7 +1017,7 @@ export function apply(ctx, config) {
|
|
|
964
1017
|
}));
|
|
965
1018
|
registerGuarded(defineTool({
|
|
966
1019
|
name: 'gotry_session_search',
|
|
967
|
-
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). ' + '
|
|
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).',
|
|
968
1021
|
parameters: {
|
|
969
1022
|
query: {
|
|
970
1023
|
type: 'json',
|
|
@@ -991,12 +1044,23 @@ export function apply(ctx, config) {
|
|
|
991
1044
|
summary: '需要 from/to(中文城市名,词表内)与 date(YYYY-MM-DD)'
|
|
992
1045
|
};
|
|
993
1046
|
}
|
|
994
|
-
const
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
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
|
+
}
|
|
999
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));
|
|
1000
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}`);
|
|
1001
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}`;
|
|
1002
1066
|
return JSON.parse(JSON.stringify({
|
|
@@ -1012,7 +1076,7 @@ export function apply(ctx, config) {
|
|
|
1012
1076
|
}),
|
|
1013
1077
|
presentResult: (_args, value)=>{
|
|
1014
1078
|
const r = value;
|
|
1015
|
-
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 ?? '降级';
|
|
1016
1080
|
return {
|
|
1017
1081
|
card: 'generic',
|
|
1018
1082
|
title: `会话检索:${label}`,
|
|
@@ -1357,7 +1421,219 @@ export function apply(ctx, config) {
|
|
|
1357
1421
|
};
|
|
1358
1422
|
}
|
|
1359
1423
|
}));
|
|
1424
|
+
registerGuarded(defineTool({
|
|
1425
|
+
name: 'gotry_artifacts_list',
|
|
1426
|
+
description: 'List GoTry artifacts — deep-planning deliverables (async runs from the state ledger) plus agent-written markdown files ' + 'in the working directory (trip plans etc.). READ-ONLY discovery. ' + 'Use when the user asks to see/open/revisit a previously generated artifact ' + '(「看看刚才生成的行程」「上次的规划在哪」「打开那个 md」) — list first, then read with gotry_artifacts_read.',
|
|
1427
|
+
parameters: {
|
|
1428
|
+
query: {
|
|
1429
|
+
type: 'json',
|
|
1430
|
+
required: true,
|
|
1431
|
+
description: '{ limit?: 20 }'
|
|
1432
|
+
}
|
|
1433
|
+
},
|
|
1434
|
+
output: {
|
|
1435
|
+
schema: {
|
|
1436
|
+
type: 'json'
|
|
1437
|
+
},
|
|
1438
|
+
render: (_args, value)=>[
|
|
1439
|
+
{
|
|
1440
|
+
type: 'text',
|
|
1441
|
+
text: String(value.summary ?? JSON.stringify(value).slice(0, 600))
|
|
1442
|
+
}
|
|
1443
|
+
]
|
|
1444
|
+
},
|
|
1445
|
+
async execute (args, _exec) {
|
|
1446
|
+
const q = unwrapQuery(args, 'limit');
|
|
1447
|
+
const r = await listArtifacts({
|
|
1448
|
+
stateRoot: config.stateRoot ?? '.',
|
|
1449
|
+
limit: q.limit
|
|
1450
|
+
});
|
|
1451
|
+
const lines = r.artifacts.map((a)=>`- [${a.source}] ${a.title}${a.status ? `(${a.status})` : ''} — ${a.path}${a.updated ? ` @ ${a.updated.slice(0, 16).replace('T', ' ')}` : ''}`);
|
|
1452
|
+
const summary = r.artifacts.length ? `在册产物 ${r.artifacts.length}/${r.total} 项${r.truncated ? '(截断,可加 limit)' : ''}:\n${lines.join('\n')}` : '无在册产物(异步深度规划交付与工作目录 md 文件都会出现在这里)';
|
|
1453
|
+
return JSON.parse(JSON.stringify({
|
|
1454
|
+
ok: true,
|
|
1455
|
+
artifacts: r.artifacts,
|
|
1456
|
+
total: r.total,
|
|
1457
|
+
truncated: r.truncated,
|
|
1458
|
+
summary
|
|
1459
|
+
}));
|
|
1460
|
+
},
|
|
1461
|
+
presentCall: ()=>({
|
|
1462
|
+
card: 'generic',
|
|
1463
|
+
title: '列出产物',
|
|
1464
|
+
kind: 'search'
|
|
1465
|
+
}),
|
|
1466
|
+
presentResult: (_args, value)=>{
|
|
1467
|
+
const r = value;
|
|
1468
|
+
return {
|
|
1469
|
+
card: 'generic',
|
|
1470
|
+
title: `产物:${r.total ?? 0} 项在册`,
|
|
1471
|
+
content: [
|
|
1472
|
+
{
|
|
1473
|
+
type: 'text',
|
|
1474
|
+
text: String(r.summary ?? '')
|
|
1475
|
+
}
|
|
1476
|
+
]
|
|
1477
|
+
};
|
|
1478
|
+
}
|
|
1479
|
+
}));
|
|
1480
|
+
registerGuarded(defineTool({
|
|
1481
|
+
name: 'gotry_artifacts_read',
|
|
1482
|
+
description: 'Read one GoTry artifact as a line-numbered file view rendered directly in the chat UI. ' + 'Input: the path from gotry_artifacts_list, or a bare async ticket id (e.g. dp-xxxx). ' + 'Optional offset (1-based) / limit window for paging large files. ' + 'READ-ONLY; text artifacts only (md/txt/json/jsonl/csv/log/yaml).',
|
|
1483
|
+
parameters: {
|
|
1484
|
+
query: {
|
|
1485
|
+
type: 'json',
|
|
1486
|
+
required: true,
|
|
1487
|
+
description: '{ path: "<list 返回的路径或工单 id>", offset?: 1, limit?: 400 }'
|
|
1488
|
+
}
|
|
1489
|
+
},
|
|
1490
|
+
output: {
|
|
1491
|
+
schema: {
|
|
1492
|
+
type: 'json'
|
|
1493
|
+
},
|
|
1494
|
+
render: (_args, value)=>[
|
|
1495
|
+
{
|
|
1496
|
+
type: 'text',
|
|
1497
|
+
text: String(value.content ?? JSON.stringify(value).slice(0, 600))
|
|
1498
|
+
}
|
|
1499
|
+
]
|
|
1500
|
+
},
|
|
1501
|
+
async execute (args, _exec) {
|
|
1502
|
+
const q = unwrapQuery(args, 'path');
|
|
1503
|
+
if (!q.path) return JSON.parse(JSON.stringify({
|
|
1504
|
+
ok: false,
|
|
1505
|
+
error: 'path 必填(来自 gotry_artifacts_list)'
|
|
1506
|
+
}));
|
|
1507
|
+
const r = await readArtifact({
|
|
1508
|
+
stateRoot: config.stateRoot ?? '.',
|
|
1509
|
+
path: q.path,
|
|
1510
|
+
offset: q.offset,
|
|
1511
|
+
limit: q.limit
|
|
1512
|
+
});
|
|
1513
|
+
if (!r.ok) return JSON.parse(JSON.stringify(r));
|
|
1514
|
+
return JSON.parse(JSON.stringify({
|
|
1515
|
+
...r,
|
|
1516
|
+
summary: `${r.path}(${r.totalLines} 行)第 ${r.offset}-${r.offset + r.lines.length - 1} 行${r.windowed ? `(共 ${r.totalLines} 行,可翻页)` : ''}`
|
|
1517
|
+
}));
|
|
1518
|
+
},
|
|
1519
|
+
presentCall: (args)=>({
|
|
1520
|
+
card: 'generic',
|
|
1521
|
+
title: `读产物:${String(args.query?.path ?? '')}`,
|
|
1522
|
+
kind: 'read',
|
|
1523
|
+
rawInput: args.query
|
|
1524
|
+
}),
|
|
1525
|
+
presentResult: (_args, value)=>{
|
|
1526
|
+
const r = value;
|
|
1527
|
+
if (!r.ok) {
|
|
1528
|
+
return {
|
|
1529
|
+
card: 'generic',
|
|
1530
|
+
title: '读产物失败',
|
|
1531
|
+
content: [
|
|
1532
|
+
{
|
|
1533
|
+
type: 'text',
|
|
1534
|
+
text: String(r.error ?? '')
|
|
1535
|
+
}
|
|
1536
|
+
]
|
|
1537
|
+
};
|
|
1538
|
+
}
|
|
1539
|
+
return {
|
|
1540
|
+
card: 'read',
|
|
1541
|
+
title: r.path?.split('/').pop() ?? r.path ?? '',
|
|
1542
|
+
path: r.path ?? '',
|
|
1543
|
+
offset: r.offset ?? 1,
|
|
1544
|
+
lines: r.lines ?? [],
|
|
1545
|
+
totalLines: r.totalLines ?? 0,
|
|
1546
|
+
lang: r.lang,
|
|
1547
|
+
content: [
|
|
1548
|
+
{
|
|
1549
|
+
type: 'text',
|
|
1550
|
+
text: r.content ?? ''
|
|
1551
|
+
}
|
|
1552
|
+
]
|
|
1553
|
+
};
|
|
1554
|
+
}
|
|
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
|
+
}));
|
|
1360
1636
|
}
|
|
1361
1637
|
|
|
1362
1638
|
|
|
1363
|
-
//# sourceURL=/Users/bytedance/work/gotry/ts/src/index.ts
|
|
1639
|
+
//# sourceURL=/Users/bytedance/work/gotry-publish/ts/src/index.ts
|
package/dist/src/journey.js
CHANGED
package/dist/src/loop.js
CHANGED
package/dist/src/memory-decay.js
CHANGED
package/dist/src/mock-llm.js
CHANGED
package/dist/src/model.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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)=>
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
|
package/dist/src/slot-spec.js
CHANGED
package/dist/src/state-ledger.js
CHANGED
package/dist/src/time-anchor.js
CHANGED
package/dist/src/tool-packet.js
CHANGED
package/dist/src/travel-slots.js
CHANGED
package/dist/src/unified.js
CHANGED
package/dist/src/wish-pool.js
CHANGED
package/dist/src/z3-shared.js
CHANGED