@danceiny/gotry 0.0.1-rc.10

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 (130) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +267 -0
  3. package/bin/gotry-inner.js +214 -0
  4. package/bin/gotry-stdio-ask.js +59 -0
  5. package/bin/gotry.js +28 -0
  6. package/cordis.gotry-patch.yml +86 -0
  7. package/data/flights_2026.json +218 -0
  8. package/data/golden_erhai.json +92 -0
  9. package/data/golden_trip_2026.json +75 -0
  10. package/data/hotels_2026.json +151 -0
  11. package/data/openflights-skeleton.json +895 -0
  12. package/data/time-slot-eval.json +384 -0
  13. package/data/yunnan-pack.json +202 -0
  14. package/dist/capabilities/agent-reach-bridge.py +90 -0
  15. package/dist/capabilities/agent-reach-deep.js +172 -0
  16. package/dist/capabilities/agent-reach.js +204 -0
  17. package/dist/capabilities/anything.js +139 -0
  18. package/dist/capabilities/flyai.js +131 -0
  19. package/dist/capabilities/hbcli.js +143 -0
  20. package/dist/capabilities/incident-log.js +100 -0
  21. package/dist/capabilities/opensky.js +74 -0
  22. package/dist/capabilities/session/action-cache.js +120 -0
  23. package/dist/capabilities/session/adapters/ctrip-flight.js +83 -0
  24. package/dist/capabilities/session/adapters/meituan-local.js +73 -0
  25. package/dist/capabilities/session/extract.js +40 -0
  26. package/dist/capabilities/session/read-guard.js +99 -0
  27. package/dist/capabilities/session/transport.js +101 -0
  28. package/dist/capabilities/session-search.js +93 -0
  29. package/dist/capabilities/weather.js +180 -0
  30. package/dist/scripts/action-cache-tests.js +107 -0
  31. package/dist/scripts/agent-reach-deep-tests.js +54 -0
  32. package/dist/scripts/agent-reach-tests.js +38 -0
  33. package/dist/scripts/agent-reach-wrapper-tests.js +79 -0
  34. package/dist/scripts/anything-tests.js +95 -0
  35. package/dist/scripts/async-collect.js +37 -0
  36. package/dist/scripts/companion-tests.js +112 -0
  37. package/dist/scripts/diff-test.js +39 -0
  38. package/dist/scripts/engine-run.js +14 -0
  39. package/dist/scripts/engine-tests.js +45 -0
  40. package/dist/scripts/hbcli-tests.js +101 -0
  41. package/dist/scripts/incident-tests.js +96 -0
  42. package/dist/scripts/journey-tests.js +45 -0
  43. package/dist/scripts/ledger-tests.js +365 -0
  44. package/dist/scripts/ledger-workflow-crash.js +42 -0
  45. package/dist/scripts/memory-capture-tests.js +77 -0
  46. package/dist/scripts/memory-decay-tests.js +83 -0
  47. package/dist/scripts/memory-metrics.js +24 -0
  48. package/dist/scripts/nudge-digest.js +83 -0
  49. package/dist/scripts/opensky-check.js +20 -0
  50. package/dist/scripts/opensky-tests.js +52 -0
  51. package/dist/scripts/probe-poi-tests.js +88 -0
  52. package/dist/scripts/publish-preverify.js +68 -0
  53. package/dist/scripts/replay-async.js +44 -0
  54. package/dist/scripts/replay-real.js +38 -0
  55. package/dist/scripts/replay.js +116 -0
  56. package/dist/scripts/session-attach-diagnose.js +27 -0
  57. package/dist/scripts/session-attach-poc.js +65 -0
  58. package/dist/scripts/session-extract-tests.js +81 -0
  59. package/dist/scripts/session-tests.js +162 -0
  60. package/dist/scripts/skeleton-check.js +37 -0
  61. package/dist/scripts/skeleton-integration-test.js +22 -0
  62. package/dist/scripts/skills-contract-tests.js +85 -0
  63. package/dist/scripts/smoke.js +328 -0
  64. package/dist/scripts/state-cli-tests.js +136 -0
  65. package/dist/scripts/state-cli.js +248 -0
  66. package/dist/scripts/time-eval-tests.js +318 -0
  67. package/dist/scripts/travel-timeline-tests.js +123 -0
  68. package/dist/scripts/unified-tests.js +49 -0
  69. package/dist/scripts/weather-tests.js +48 -0
  70. package/dist/src/bridge.js +34 -0
  71. package/dist/src/companions.js +112 -0
  72. package/dist/src/contracts.js +64 -0
  73. package/dist/src/dsh-llm.js +174 -0
  74. package/dist/src/engine.js +331 -0
  75. package/dist/src/index.js +1239 -0
  76. package/dist/src/journey.js +147 -0
  77. package/dist/src/loop.js +360 -0
  78. package/dist/src/memory-capture.js +40 -0
  79. package/dist/src/memory-decay.js +31 -0
  80. package/dist/src/memory-utility.js +55 -0
  81. package/dist/src/mock-llm.js +103 -0
  82. package/dist/src/model.js +134 -0
  83. package/dist/src/slot-spec.js +165 -0
  84. package/dist/src/state-ledger.js +901 -0
  85. package/dist/src/time-anchor.js +100 -0
  86. package/dist/src/tool-packet.js +12 -0
  87. package/dist/src/travel-slots.js +144 -0
  88. package/dist/src/travel-timeline.js +78 -0
  89. package/dist/src/unified.js +538 -0
  90. package/dist/src/wish-pool.js +27 -0
  91. package/package.json +84 -0
  92. package/ts/capabilities/agent-reach-deep.ts +114 -0
  93. package/ts/capabilities/agent-reach.ts +158 -0
  94. package/ts/capabilities/anything.ts +201 -0
  95. package/ts/capabilities/flyai.ts +151 -0
  96. package/ts/capabilities/hbcli.ts +152 -0
  97. package/ts/capabilities/incident-log.ts +148 -0
  98. package/ts/capabilities/opensky.ts +121 -0
  99. package/ts/capabilities/session/action-cache.ts +148 -0
  100. package/ts/capabilities/session/adapters/ctrip-flight.ts +107 -0
  101. package/ts/capabilities/session/adapters/meituan-local.ts +70 -0
  102. package/ts/capabilities/session/extract.ts +54 -0
  103. package/ts/capabilities/session/read-guard.ts +141 -0
  104. package/ts/capabilities/session/transport.ts +114 -0
  105. package/ts/capabilities/session-search.ts +129 -0
  106. package/ts/capabilities/weather.ts +176 -0
  107. package/ts/package.json +27 -0
  108. package/ts/scripts/async-collect.ts +50 -0
  109. package/ts/scripts/skeleton-check.ts +49 -0
  110. package/ts/scripts/skeleton-integration-test.ts +23 -0
  111. package/ts/scripts/state-cli.ts +201 -0
  112. package/ts/src/bridge.ts +44 -0
  113. package/ts/src/companions.ts +113 -0
  114. package/ts/src/contracts.ts +165 -0
  115. package/ts/src/dsh-llm.ts +155 -0
  116. package/ts/src/index.ts +968 -0
  117. package/ts/src/loop.ts +411 -0
  118. package/ts/src/memory-capture.ts +59 -0
  119. package/ts/src/memory-decay.ts +55 -0
  120. package/ts/src/memory-utility.ts +70 -0
  121. package/ts/src/mock-llm.ts +100 -0
  122. package/ts/src/model.ts +220 -0
  123. package/ts/src/slot-spec.ts +172 -0
  124. package/ts/src/state-ledger.ts +849 -0
  125. package/ts/src/time-anchor.ts +134 -0
  126. package/ts/src/tool-packet.ts +32 -0
  127. package/ts/src/travel-slots.ts +219 -0
  128. package/ts/src/travel-timeline.ts +83 -0
  129. package/ts/src/unified.ts +582 -0
  130. package/ts/src/wish-pool.ts +66 -0
@@ -0,0 +1,20 @@
1
+ const BASE = 'https://opensky-network.org/api';
2
+ async function checkArrival(callsign, airport) {
3
+ const now = Math.floor(Date.now() / 1000);
4
+ const url = airport ? `${BASE}/arrivals?airport=${airport}&begin=${now - 7 * 86400}&end=${now}` : `${BASE}/arrivals?airport=OMDB&begin=${now - 7 * 86400}&end=${now}`;
5
+ const res = await fetch(url, {
6
+ headers: {
7
+ Accept: 'application/json'
8
+ }
9
+ });
10
+ if (!res.ok) return `[校验不可用:opensky ${res.status}]`;
11
+ const data = await res.json();
12
+ const hit = data.find((a)=>(a.callsign ?? '').trim().toUpperCase().includes(callsign.toUpperCase()));
13
+ const ts = new Date().toISOString();
14
+ return hit ? `[实时API:opensky@${ts}] ✅ ${callsign} 近 7 天执飞记录:到达 ${hit.arrivalAirport ?? '?'}(callsign ${hit.callsign?.trim()})` : `[实时API:opensky@${ts}] ○ 近 7 天 ${airport ?? 'OMDB'} 到达列表未见 ${callsign}(ADS-B 覆盖有限,不作否定结论)`;
15
+ }
16
+ const target = process.argv[2] ?? 'EK329';
17
+ console.log(await checkArrival(target, process.argv[3] ?? null));
18
+
19
+
20
+ //# sourceURL=/Users/bytedance/work/gotry/ts/scripts/opensky-check.ts
@@ -0,0 +1,52 @@
1
+ import assert from 'node:assert/strict';
2
+ import { verifyFlight } from '../capabilities/opensky.js';
3
+ const live = await verifyFlight({
4
+ callsign: 'EK329',
5
+ airport: 'OMDB'
6
+ });
7
+ assert.ok(live.sampleSize >= 0, 'sampleSize ≥ 0(空数组也合法)');
8
+ assert.ok([
9
+ 'observed',
10
+ 'not_observed',
11
+ 'unavailable'
12
+ ].includes(live.verdict), `verdict 三值之一:${live.verdict}`);
13
+ if (live.verdict === 'observed') {
14
+ assert.ok((live.hits?.length ?? 0) > 0, 'observed 必须配 hits');
15
+ const h = live.hits[0];
16
+ assert.ok(typeof h.icao24 === 'string' && h.icao24.length > 0, 'icao24 存在');
17
+ assert.match(live.evidence, /\[实时API:opensky@/, '证据链标注');
18
+ console.log(`1. EK329 OMDB → ${live.verdict} (sample=${live.sampleSize}, hits=${live.hits?.length}) OK`);
19
+ } else if (live.verdict === 'not_observed') {
20
+ assert.match(live.evidence, /○|not_observed|未见/, 'not_observed 标注 ○');
21
+ console.log(`1. EK329 OMDB → not_observed 当前观测窗没飞 (o) OK`);
22
+ } else {
23
+ assert.match(live.evidence, /error/, 'unavailable 标注 error');
24
+ console.log(`1. EK329 OMDB → unavailable (API 异常,降级合法) OK`);
25
+ }
26
+ const fake = await verifyFlight({
27
+ callsign: 'XXDODO1234',
28
+ airport: 'OMDB'
29
+ });
30
+ assert.ok([
31
+ 'not_observed',
32
+ 'unavailable'
33
+ ].includes(fake.verdict), `期望 not_observed/unavailable,实际 ${fake.verdict}`);
34
+ if (fake.verdict === 'not_observed') {
35
+ assert.match(fake.evidence, /○/, 'not_observed 标 ○');
36
+ console.log('2. XXDODO1234 OMDB → not_observed (○) OK');
37
+ } else {
38
+ console.log('2. XXDODO1234 OMDB → unavailable (API 限流 OK) OK');
39
+ }
40
+ import { verifyFlight as vf } from '../capabilities/opensky.js';
41
+ const to = await vf({
42
+ callsign: 'EK329',
43
+ airport: 'OMDB',
44
+ timeoutMs: 1
45
+ });
46
+ assert.equal(to.verdict, 'unavailable', `timeout 应 unavailable,实际 ${to.verdict}`);
47
+ assert.match(to.evidence, /error/, 'unavailable 证据带 error');
48
+ console.log('3. timeout → unavailable OK');
49
+ console.log('\nOPEN-SKY TESTS: 3/3 OK(免费匿名 OpenSky API)');
50
+
51
+
52
+ //# sourceURL=/Users/bytedance/work/gotry/ts/scripts/opensky-tests.ts
@@ -0,0 +1,88 @@
1
+ import assert from 'node:assert/strict';
2
+ import { probePoi } from '../src/loop.js';
3
+ function pass(name, body) {
4
+ body();
5
+ console.log(` ${name} OK`);
6
+ }
7
+ pass('1. 不抛错 + 空串/纯空白返 null', ()=>{
8
+ assert.doesNotThrow(()=>probePoi(''));
9
+ assert.equal(probePoi(''), null);
10
+ assert.equal(probePoi(' '), null);
11
+ });
12
+ pass('2. 短纯地名(<24 字符非问句)触发', ()=>{
13
+ for (const t of [
14
+ '普吉',
15
+ '清迈',
16
+ '大理',
17
+ '北京 酒店',
18
+ '丽江市'
19
+ ]){
20
+ const r = probePoi(t);
21
+ if (r === null) throw new Error(`${JSON.stringify(t)} 应触发,得 null`);
22
+ if (typeof r !== 'string') throw new Error(`返类型错: ${typeof r}`);
23
+ if (r.length < 2 || r.length > 24) throw new Error(`长度越界: ${r.length}`);
24
+ }
25
+ });
26
+ pass('3. 结构触发 — 含"查/搜/找/看看/推荐/告诉我/检索"', ()=>{
27
+ for (const t of [
28
+ '查一下 大理',
29
+ '搜 普吉',
30
+ '找清迈',
31
+ '看看曼谷',
32
+ '推荐 沙巴'
33
+ ]){
34
+ const r = probePoi(t);
35
+ if (r === null) throw new Error(`${JSON.stringify(t)} 应触发,得 null`);
36
+ }
37
+ });
38
+ pass('4. 内容触发 — 含"酒店/民宿/客栈/有什么/玩什么/有哪些"', ()=>{
39
+ for (const t of [
40
+ '大理酒店',
41
+ '清迈民宿',
42
+ '曼谷有什么',
43
+ '丽江有哪些'
44
+ ]){
45
+ const r = probePoi(t);
46
+ if (r === null) throw new Error(`${JSON.stringify(t)} 应触发,得 null`);
47
+ }
48
+ });
49
+ pass('5. 不触发 — 长问句(>24 字符 触发上限 isShort 拒)', ()=>{
50
+ for (const t of [
51
+ 'lorem ipsum dolor sit amet consect',
52
+ '我想查询从北京到上海的高铁票价格'
53
+ ]){
54
+ const r = probePoi(t);
55
+ if (t.length > 24) {
56
+ if (r !== null) throw new Error(`>24 字符 ${JSON.stringify(t)} 应不触发,得 ${JSON.stringify(r)}`);
57
+ } else {
58
+ if (r === null) throw new Error(`<=24 字符 ${JSON.stringify(t)} 应触发,得 null`);
59
+ }
60
+ }
61
+ });
62
+ pass('6. 不抛错 + 返回类型是 string 或 null(边界)', ()=>{
63
+ for (const t of [
64
+ 'a'.repeat(200),
65
+ 'a?'.repeat(50),
66
+ '!@#',
67
+ '中文' + 'a'.repeat(100),
68
+ ' 前后空格 '
69
+ ]){
70
+ const r = probePoi(t);
71
+ if (r !== null && typeof r !== 'string') {
72
+ throw new Error(`返类型错: ${typeof r}`);
73
+ }
74
+ }
75
+ });
76
+ pass('7. 金标准对话噪音回归(2026-08-28 巡检:访谈答案/多段首查不再误触发)', ()=>{
77
+ assert.equal(probePoi('我的工作时间是UTC+4的早上10点到下午7点'), null, '访谈答案(>12 字含陈述动词)不触发');
78
+ assert.equal(probePoi('明天有空'), null, '短句含陈述动词不触发');
79
+ assert.equal(probePoi('7.17周五22:40落地深圳,7.18早上去香港办银行开户&保险签约;争取7.18当天飞泰国普吉岛……8.10周一凌晨从深圳起飞,周一上班前到迪拜。请给我做机票和酒店的行程规划和推荐。'), null, '多段首查无 POI 信号不触发');
80
+ const booking = probePoi('我订了酒店:The Title East Wing Rawai,7.18入住 7.23 退房');
81
+ if (!booking || !booking.includes('Title')) throw new Error(`订酒店应抓名称段,得 ${JSON.stringify(booking)}`);
82
+ console.log(' 7a. 访谈答案/多段首查 → null OK');
83
+ console.log(' 7b. 订酒店抓名称段(The Title…) OK');
84
+ });
85
+ console.log('\nprobePoi TESTS: 6 类 OK(不崩 + 5 类触发 + 1 类不触发返 null)');
86
+
87
+
88
+ //# sourceURL=/Users/bytedance/work/gotry/ts/scripts/probe-poi-tests.ts
@@ -0,0 +1,68 @@
1
+ import { execSync } from 'node:child_process';
2
+ import { mkdtempSync, rmSync, existsSync } from 'node:fs';
3
+ import { tmpdir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ const repoRoot = join(import.meta.dirname, '..', '..');
6
+ process.chdir(repoRoot);
7
+ const pkg = JSON.parse(execSync('cat package.json', {
8
+ encoding: 'utf-8'
9
+ }));
10
+ const version = pkg.version;
11
+ console.log(`pre-verify: @danceiny/gotry@${version}`);
12
+ let tgz = '';
13
+ let dest = '';
14
+ try {
15
+ const outDir = mkdtempSync(join(tmpdir(), 'gotry-preverify-out-'));
16
+ execSync(`npm pack --pack-destination ${outDir}`, {
17
+ stdio: 'ignore'
18
+ });
19
+ const tgzPath = `danceiny-gotry-${version}.tgz`;
20
+ if (!existsSync(join(outDir, tgzPath))) throw new Error('npm pack 未产出 tarball');
21
+ tgz = join(outDir, tgzPath);
22
+ console.log(`tarball: ${tgzPath}`);
23
+ dest = mkdtempSync(join(tmpdir(), 'gotry-preverify-'));
24
+ execSync(`tar -xzf "${tgz}" -C "${dest}"`, {
25
+ stdio: 'pipe'
26
+ });
27
+ const pkgDir = join(dest, 'package');
28
+ const installedPkg = JSON.parse(execSync(`cat ${join(pkgDir, 'package.json')}`, {
29
+ encoding: 'utf-8'
30
+ }));
31
+ if (installedPkg.version !== version) throw new Error(`版本不符:${installedPkg.version}`);
32
+ const deps = Object.keys(installedPkg.dependencies ?? {});
33
+ console.log(`dependencies: ${deps.join(', ')}`);
34
+ const runtimeImports = [
35
+ 'better-sqlite3'
36
+ ];
37
+ for (const dep of runtimeImports){
38
+ if (!deps.includes(dep)) throw new Error(`运行时依赖 ${dep} 未声明进 dependencies(rc.9 缺陷形态)`);
39
+ }
40
+ console.log('deps resolvable: OK');
41
+ for (const e of [
42
+ 'dist/src/state-ledger.js',
43
+ 'dist/src/index.js',
44
+ 'bin/gotry-inner.js'
45
+ ]){
46
+ if (!existsSync(join(pkgDir, e))) throw new Error(`发布缺文件: ${e}`);
47
+ }
48
+ if (!execSync(`grep -c "better-sqlite3" ${join(pkgDir, 'dist/src/state-ledger.js')}`, {
49
+ encoding: 'utf-8'
50
+ }).trim().match(/^[1-9]/)) {
51
+ throw new Error('dist/src/state-ledger.js 未引用 better-sqlite3(声明与实现不符)');
52
+ }
53
+ console.log('PUBLISH PREVERIFY: OK');
54
+ } catch (e) {
55
+ console.error(`PREVERIFY FAILED: ${e.message}`);
56
+ process.exit(1);
57
+ } finally{
58
+ if (tgz) rmSync(tgz, {
59
+ force: true
60
+ });
61
+ if (dest) rmSync(dest, {
62
+ recursive: true,
63
+ force: true
64
+ });
65
+ }
66
+
67
+
68
+ //# sourceURL=/Users/bytedance/work/gotry/ts/scripts/publish-preverify.ts
@@ -0,0 +1,44 @@
1
+ import { join } from 'node:path';
2
+ import { createMockLlm } from '../src/mock-llm.js';
3
+ import { collectDeepPlanning, isComplex, newState, persistAsyncTicket, requestDeepPlanning, runTurn } from '../src/loop.js';
4
+ import { solveUnified } from '../src/unified.js';
5
+ const llm = createMockLlm(join('..', 'data', 'flights_2026.json'));
6
+ const state = newState();
7
+ const history = [];
8
+ const turns = [
9
+ '7.17周五22:40落地深圳……8.10周一凌晨从深圳起飞周一上班前到迪拜。请给我做机票和酒店的行程规划和推荐。',
10
+ '我的工作时间是UTC+4的早上10点到下午7点',
11
+ '我订了酒店:The Title East Wing Rawai,7.18入住 7.23 退房'
12
+ ];
13
+ let lastReply = '';
14
+ for (const t of turns){
15
+ const { reply } = await runTurn(state, t, llm, [
16
+ ...history
17
+ ], solveUnified);
18
+ history.push({
19
+ role: 'user',
20
+ text: t
21
+ }, {
22
+ role: 'assistant',
23
+ text: reply
24
+ });
25
+ lastReply = reply;
26
+ }
27
+ if (!isComplex(state)) throw new Error('复杂度判据未命中,重放脚本与判据不一致');
28
+ const { reply: asyncReply, ticket } = await requestDeepPlanning(state);
29
+ const saved = await persistAsyncTicket(ticket, state);
30
+ console.log('用户> (约束齐备,行程复杂)\n');
31
+ console.log(`GoTry> ${asyncReply}\n`);
32
+ console.log(`(工单已持久化:${saved}——任意后续进程可执行回收)\n`);
33
+ if (process.argv.includes('--request-only')) {
34
+ console.log(`(request-only 模式结束;回收命令:npx tsx scripts/async-collect.ts ${ticket.id})`);
35
+ } else {
36
+ console.log('……(一小时后,另一个进程执行回收)……\n');
37
+ const { reply: deliverable } = await collectDeepPlanning(state, ticket, solveUnified);
38
+ console.log(`GoTry> ${deliverable}`);
39
+ if (!deliverable.includes('4/4')) throw new Error('FAIL: 不失望四条未全过');
40
+ console.log(`\n(真实形态命令:npx tsx scripts/async-collect.ts ${ticket.id})`);
41
+ }
42
+
43
+
44
+ //# sourceURL=/Users/bytedance/work/gotry/ts/scripts/replay-async.ts
@@ -0,0 +1,38 @@
1
+ import { join } from 'node:path';
2
+ import { readFileSync } from 'node:fs';
3
+ try {
4
+ for (const line of readFileSync(join('..', '.env'), 'utf-8').split('\n')){
5
+ const m = line.match(/^([A-Z_]+)=(.*)$/);
6
+ if (m && !process.env[m[1]]) process.env[m[1]] = m[2].trim();
7
+ }
8
+ } catch {}
9
+ import { createMockLlm } from '../src/mock-llm.js';
10
+ import { createOpenAICompatLlm } from '../src/dsh-llm.js';
11
+ import { newState, runTurn } from '../src/loop.js';
12
+ import { solveUnified } from '../src/unified.js';
13
+ const useReal = Boolean(process.env['LLM_API_KEY'] ?? process.env['DEEPSEEK_API_KEY']);
14
+ const llm = useReal ? createOpenAICompatLlm(join('..', 'data', 'flights_2026.json')) : createMockLlm(join('..', 'data', 'flights_2026.json'));
15
+ console.log(useReal ? `=== 真 LLM 模式(${process.env['LLM_MODEL'] ?? 'MiniMax-M2'})===` : '=== 无 LLM_API_KEY,回退 mock(ADR-8)===\n');
16
+ const state = newState();
17
+ const history = [];
18
+ const turns = [
19
+ '7.17周五22:40落地深圳,7.18早上去香港办银行开户&保险签约;争取7.18当天飞泰国普吉岛……8.10周一凌晨从深圳起飞,周一上班前到迪拜。请给我做机票和酒店的行程规划和推荐。',
20
+ '我的工作时间是UTC+4的早上10点到下午7点',
21
+ '我订了酒店:The Title East Wing Rawai,7.18入住 7.23 退房'
22
+ ];
23
+ for (const t of turns){
24
+ const { reply } = await runTurn(state, t, llm, [
25
+ ...history
26
+ ], solveUnified);
27
+ history.push({
28
+ role: 'user',
29
+ text: t
30
+ }, {
31
+ role: 'assistant',
32
+ text: reply
33
+ });
34
+ console.log(`\n用户> ${t.slice(0, 46)}…\nGoTry> ${reply}`);
35
+ }
36
+
37
+
38
+ //# sourceURL=/Users/bytedance/work/gotry/ts/scripts/replay-real.ts
@@ -0,0 +1,116 @@
1
+ import { createMockLlm } from '../src/mock-llm.js';
2
+ import { interviewNext, newState, runTurn } from '../src/loop.js';
3
+ import { solveUnified } from '../src/unified.js';
4
+ import { join } from 'node:path';
5
+ const llm = createMockLlm(join('..', 'data', 'flights_2026.json'));
6
+ let state = newState();
7
+ const userTurns = [
8
+ '7.17周五22:40落地深圳,7.18早上去香港办银行开户&保险签约;争取7.18当天飞泰国普吉岛……8.10周一凌晨从深圳起飞,周一上班前到迪拜。请给我做机票和酒店的行程规划和推荐。',
9
+ '我的工作时间是UTC+4的早上10点到下午7点,晚上不一定有时间(至少得带电脑保持在线)',
10
+ '我订了酒店:The Title East Wing Rawai,7.18入住 7.23 退房'
11
+ ];
12
+ const history = [];
13
+ for (const turn of userTurns){
14
+ const { reply } = await runTurn(state, turn, llm, [
15
+ ...history
16
+ ], (spec)=>{
17
+ spec.skeletonHub = true;
18
+ return solveUnified(spec);
19
+ });
20
+ history.push({
21
+ role: 'user',
22
+ text: turn
23
+ }, {
24
+ role: 'assistant',
25
+ text: reply
26
+ });
27
+ console.log(`\n用户> ${turn.slice(0, 50)}…`);
28
+ console.log(`GoTry> ${reply}`);
29
+ }
30
+ console.log('\n===== 终态校验 =====');
31
+ console.log('日历断言数:', Object.keys(state.calendar.assertedWeekdays).length, '| 2026-07-17 =', state.calendar.assertedWeekdays['2026-07-17']);
32
+ console.log('workWindow:', state.profile.workWindow ? `${state.profile.workWindow.startMin / 60}:00-${state.profile.workWindow.endMin / 60}:00 UTC+${state.profile.workWindow.homeTzOffsetMin / 60}(证据:${state.profile.workWindow.evidence.slice(0, 24)}…)` : '缺失');
33
+ console.log('bookedResources:', state.profile.bookedResources?.map((b)=>b.ref).join('; ') ?? '缺失');
34
+ const { missing } = interviewNext(state);
35
+ console.log('剩余待问:', missing.length === 0 ? '无(除预算档未答,见下)' : missing.join(','));
36
+ const weekday = state.calendar.assertedWeekdays['2026-07-17'];
37
+ if (weekday !== 'fri') throw new Error(`FAIL: 2026-07-17 应为 fri,实为 ${weekday}`);
38
+ if (!state.profile.workWindow?.evidence) throw new Error('FAIL: workWindow 缺失或无证据');
39
+ if (!state.profile.bookedResources?.length) throw new Error('FAIL: bookedResources 缺失');
40
+ if (!(missing.length === 1 && missing[0] === 'budgetTier')) throw new Error(`FAIL: 剩余待问应为 [budgetTier],实为 [${missing.join(',')}]`);
41
+ console.log('REPLAY ASSERTS OK');
42
+ {
43
+ const conflictScript = [
44
+ {
45
+ when: '重新算一下',
46
+ extraction: {
47
+ schema_version: 'travel_slot_extraction.v1',
48
+ language: 'zh',
49
+ domains: [
50
+ 'requisition'
51
+ ],
52
+ slots: {
53
+ requisition: {
54
+ mode: 'create',
55
+ destination: '普吉岛',
56
+ start_date: '2026-12-25',
57
+ trip_type: 'round_trip'
58
+ }
59
+ },
60
+ missing_slots: []
61
+ }
62
+ }
63
+ ];
64
+ const solvePort = (spec)=>{
65
+ spec.skeletonHub = true;
66
+ return solveUnified(spec);
67
+ };
68
+ const base = createMockLlm(join('..', 'data', 'flights_2026.json'), conflictScript);
69
+ const singleSpecLlm = {
70
+ ...base,
71
+ extractSpec: async ()=>({
72
+ segments: [
73
+ {
74
+ id: 's1',
75
+ role: 'choice',
76
+ date: '2026-07-01',
77
+ options: [
78
+ {
79
+ id: 'o1',
80
+ label: 'o1',
81
+ move: {
82
+ hub: 'SZX',
83
+ services: [
84
+ {
85
+ id: 'f1',
86
+ depMin: 600,
87
+ arrMin: 700
88
+ }
89
+ ],
90
+ bufferMin: 60,
91
+ originTransferMin: 30,
92
+ destTransferMin: 30
93
+ }
94
+ }
95
+ ]
96
+ }
97
+ ]
98
+ })
99
+ };
100
+ const state1 = structuredClone(state);
101
+ const solveBefore = state1.solve;
102
+ const r1 = await runTurn(state1, '12月25日出发,重新算一下', singleSpecLlm, [
103
+ ...history
104
+ ], solvePort);
105
+ if (!r1.reply.includes('日期分歧')) throw new Error(`FAIL: 单日期段分歧应被闸拦下,实际:${r1.reply.slice(0, 160)}`);
106
+ if (r1.state.solve !== solveBefore) throw new Error('FAIL: 分歧时不应求解(spec 日期未确认)');
107
+ const state2 = structuredClone(state);
108
+ const r2 = await runTurn(state2, '重新算一下', base, [
109
+ ...history
110
+ ], solvePort);
111
+ if (r2.reply.includes('日期分歧')) throw new Error(`FAIL: 多段行程应旁路日期闸(无逐段真值不判),实际:${r2.reply.slice(0, 160)}`);
112
+ if (!r2.state.solve) throw new Error('FAIL: 多段旁路后应正常求解');
113
+ console.log('SPEC-SLOT DATE GATE OK(单日期段分歧拦截 + 多段旁路不误伤)');
114
+ }
115
+
116
+ //# sourceURL=/Users/bytedance/work/gotry/ts/scripts/replay.ts
@@ -0,0 +1,27 @@
1
+ import { openSession } from '../capabilities/session/transport.js';
2
+ import { LOGIN_COOKIE_NAMES } from '../capabilities/session/adapters/ctrip-flight.js';
3
+ const t = await openSession({
4
+ mode: 'cdp'
5
+ });
6
+ if (!t.ok) {
7
+ console.log('ATTACH 失败:', t.summary);
8
+ process.exit(1);
9
+ }
10
+ try {
11
+ const all = await t.browser.cookies();
12
+ const byDom = {};
13
+ for (const c of all){
14
+ const d = c.domain.replace(/^\./, '');
15
+ if (/ctrip\.com$|meituan\.com$/.test(d)) (byDom[d] ??= []).push(c.name);
16
+ }
17
+ console.log('ATTACH ok,共', all.length, '条 cookie;相关域:');
18
+ for (const [d, names] of Object.entries(byDom))console.log(' ', d, '=>', names.sort().join(','));
19
+ const ctripNames = new Set(Object.entries(byDom).filter(([d])=>d.includes('ctrip')).flatMap(([, n])=>n));
20
+ const known = LOGIN_COOKIE_NAMES.filter((k)=>ctripNames.has(k));
21
+ console.log(known.length > 0 ? `携程登录态:在(${known.join(',')})` : '携程登录态:未检出——在你日常 Chrome 里正常登录携程即可(无需专门窗口)');
22
+ } finally{
23
+ await t.close();
24
+ }
25
+
26
+
27
+ //# sourceURL=/Users/bytedance/work/gotry/ts/scripts/session-attach-diagnose.ts
@@ -0,0 +1,65 @@
1
+ import { chromium } from "playwright-core";
2
+ const route = process.argv[2] ?? "sha-ljg";
3
+ const date = process.argv[3] ?? "2026-10-01";
4
+ const url = `https://flights.ctrip.com/online/list/oneway-${route}?depdate=${date}`;
5
+ const PROFILE = "/tmp/gotry-session-poc-profile";
6
+ async function main() {
7
+ const ctx = await chromium.launchPersistentContext(PROFILE, {
8
+ channel: "chrome",
9
+ headless: false,
10
+ viewport: {
11
+ width: 1440,
12
+ height: 900
13
+ },
14
+ args: [
15
+ "--disable-blink-features=AutomationControlled"
16
+ ]
17
+ });
18
+ const page = ctx.pages()[0] ?? await ctx.newPage();
19
+ const hits = [];
20
+ const cap = (s)=>s.replace(/\s+/g, " ").slice(0, 600);
21
+ ctx.on("response", async (res)=>{
22
+ const u = res.url();
23
+ const ct = res.headers()["content-type"] ?? "";
24
+ if (!ct.includes("json") || u.length > 400) return;
25
+ if (!/search|list|flight|itinerary|poll/i.test(u)) return;
26
+ try {
27
+ const body = await res.text();
28
+ if (body.length < 200) return;
29
+ hits.push({
30
+ url: u,
31
+ bytes: body.length
32
+ });
33
+ if (hits.length <= 3) {
34
+ console.log(`\n[HIT ${hits.length}] ${res.status()} ${cap(u)}\n body(${body.length}B): ${cap(body)}`);
35
+ }
36
+ } catch {}
37
+ });
38
+ console.log(`goto ${url}`);
39
+ await page.goto(url, {
40
+ waitUntil: "domcontentloaded",
41
+ timeout: 30_000
42
+ });
43
+ await page.waitForTimeout(15_000);
44
+ const title = await page.title().catch(()=>"");
45
+ console.log(`\ntitle: ${cap(title)}`);
46
+ const html = await page.content().catch(()=>"");
47
+ const challenged = /验证|滑块|captcha|verify/i.test(title + html.slice(0, 5000));
48
+ console.log(`challenge-detected: ${challenged}`);
49
+ console.log(`\nsummary: ${hits.length} 条搜索类 JSON XHR;总命中 ${hits.reduce((a, b)=>a + b.bytes, 0)}B`);
50
+ console.log("按体积 top5(P1 适配器要嗅探的接口面):");
51
+ for (const h of [
52
+ ...hits
53
+ ].sort((a, b)=>b.bytes - a.bytes).slice(0, 5)){
54
+ console.log(` ${h.bytes}B ${cap(h.url)}`);
55
+ }
56
+ console.log(challenged ? "结论:触发风控——按红线不重试不绕过,如实记录" : "结论:嗅探链路成立(只读,零交互)");
57
+ await ctx.close();
58
+ }
59
+ main().catch((e)=>{
60
+ console.error("PoC 失败:", e instanceof Error ? e.message : e);
61
+ process.exit(1);
62
+ });
63
+
64
+
65
+ //# sourceURL=/Users/bytedance/work/gotry/ts/scripts/session-attach-poc.ts
@@ -0,0 +1,81 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { extractA11yEntries, filterSubmitEntries, nameAffinity } from '../capabilities/session/extract.js';
4
+ import { buildMeituanEntry, extractListings, parseMeituanSearch } from '../capabilities/session/adapters/meituan-local.js';
5
+ let pass = 0;
6
+ let fail = 0;
7
+ function assert(cond, label, detail) {
8
+ if (cond) {
9
+ pass += 1;
10
+ console.log(` ok - ${label}`);
11
+ } else {
12
+ fail += 1;
13
+ console.log(` FAIL - ${label}${detail !== undefined ? ' :: ' + String(detail) : ''}`);
14
+ }
15
+ }
16
+ console.log('A. extractA11yEntries(快照形状抽取)');
17
+ const snap = `- button "搜索" [ref=s1e5]
18
+ - textbox "出发城市" [ref=s1e6]
19
+ - heading "上海到丽江机票" [level=1]
20
+ - listitem "吉祥航空 HO5577 ¥1610 起"
21
+ - generic "广告"`;
22
+ const entries = extractA11yEntries(snap);
23
+ assert(entries.length === 5 && entries[0].role === 'button' && entries[0].name === '搜索', 'role+name 提取(ref 行)');
24
+ assert(entries[2].name === '上海到丽江机票' && entries[3].role === 'listitem', 'heading/listitem 归位');
25
+ const noName = extractA11yEntries('- img\n- separator');
26
+ assert(noName.every((e)=>typeof e.name === 'string'), '无 name 行保留空占位不炸');
27
+ assert(extractA11yEntries('').length === 0, '空快照返空');
28
+ console.log('B. filterSubmitEntries(DOM 提交件剔除)');
29
+ const kept = filterSubmitEntries(entries, (n)=>/下单|支付/.test(n));
30
+ assert(kept.length === entries.length, '无提交词:全保留');
31
+ const snap2 = extractA11yEntries('- button "立即下单" [ref=s1e9]\n- button "搜索" [ref=s2e1]');
32
+ assert(filterSubmitEntries(snap2, (n)=>/下单|支付/.test(n)).length === 1, 'button 命中提交黑名单剔除,搜索保留');
33
+ assert(filterSubmitEntries(extractA11yEntries('- heading "支付说明"'), (n)=>/下单|支付/.test(n)).length === 1, '非交互 role(heading)不剔除');
34
+ console.log('C. nameAffinity(自愈候选排序底座)');
35
+ assert(nameAffinity('吉祥航空 HO5577', '吉祥航空 HO5577') === 1, '全等=1');
36
+ assert(nameAffinity('上海到丽江机票', '机票') === 0.8, '包含=0.8');
37
+ assert(nameAffinity('abc', 'xyz') < 0.5, '无关串低分');
38
+ console.log('D. 美团适配器骨架(词表边界/占位合同)');
39
+ const mt = buildMeituanEntry('大理', 'minsu', '洱海');
40
+ assert(mt.ok === true && mt.url === 'https://minsu.meituan.com/dali/?q=%E6%B4%B1%E6%B5%B7', '民宿 entry URL(拼音+关键词编码)');
41
+ assert(buildMeituanEntry('乌兰巴托', 'hotel').ok === false, '词表外城市 unresolved');
42
+ assert(parseMeituanSearch('{"any":1}').length === 0, 'XHR 解析占位:接口实测回填前恒空(miss 语义)');
43
+ console.log('E. extractListings(a11y 兜底:名称+¥价格)');
44
+ const listings = extractListings(entries);
45
+ assert(listings.length === 1 && listings[0].name === '吉祥航空 HO5577' && listings[0].price === 1610, 'listitem 抽名称+价格,截 ¥ 后缀');
46
+ assert(extractListings(extractA11yEntries('- heading "无价格标题"')).length === 0, '无价格行不产出');
47
+ console.log('F. 金标准 20 查询 schema(只增不改语义)');
48
+ const golden = JSON.parse(readFileSync(join(import.meta.dirname, '../data/session-golden-20.json'), 'utf8'));
49
+ assert(golden.queries.length === 20, '恰 20 条');
50
+ assert(new Set(golden.queries.map((q)=>q.id)).size === 20, 'id 唯一');
51
+ const ctripCities = new Set([
52
+ '上海',
53
+ '北京',
54
+ '广州',
55
+ '深圳',
56
+ '成都',
57
+ '昆明',
58
+ '大理',
59
+ '丽江',
60
+ '西安',
61
+ '杭州',
62
+ '三亚',
63
+ '厦门',
64
+ '重庆',
65
+ '青岛',
66
+ '长沙',
67
+ '武汉',
68
+ '南京',
69
+ '郑州',
70
+ '贵阳',
71
+ '桂林',
72
+ '西双版纳',
73
+ '香格里拉'
74
+ ]);
75
+ assert(golden.queries.filter((q)=>q.kind === 'flight').every((q)=>ctripCities.has(q.from) && ctripCities.has(q.to)), 'flight 城市全在双适配器词表内');
76
+ assert(golden.queries.filter((q)=>q.kind.startsWith('meituan') || q.kind.startsWith('flyai')).every((q)=>q.id), '每条带 id');
77
+ console.log(`\nSESSION-EXTRACT: ${pass} pass, ${fail} fail`);
78
+ process.exit(fail > 0 ? 1 : 0);
79
+
80
+
81
+ //# sourceURL=/Users/bytedance/work/gotry/ts/scripts/session-extract-tests.ts