@danceiny/gotry 0.0.1-rc.5 → 0.0.1-rc.7

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 (49) hide show
  1. package/README.md +1 -1
  2. package/bin/gotry-inner.js +73 -32
  3. package/cordis.gotry-patch.yml +2 -2
  4. package/data/flights_2026.json +215 -0
  5. package/data/golden_erhai.json +92 -0
  6. package/data/golden_trip_2026.json +75 -0
  7. package/data/hotels_2026.json +64 -0
  8. package/data/openflights-skeleton.json +895 -0
  9. package/data/yunnan-pack.json +201 -0
  10. package/dist/capabilities/agent-reach-deep.js +172 -0
  11. package/dist/capabilities/agent-reach.js +204 -0
  12. package/dist/capabilities/anything.js +139 -0
  13. package/dist/capabilities/hbcli.js +138 -0
  14. package/dist/capabilities/incident-log.js +99 -0
  15. package/dist/capabilities/opensky.js +74 -0
  16. package/dist/capabilities/weather.js +180 -0
  17. package/dist/scripts/agent-reach-deep-tests.js +54 -0
  18. package/dist/scripts/agent-reach-tests.js +38 -0
  19. package/dist/scripts/agent-reach-wrapper-tests.js +79 -0
  20. package/dist/scripts/anything-tests.js +95 -0
  21. package/dist/scripts/async-collect.js +18 -0
  22. package/dist/scripts/diff-test.js +39 -0
  23. package/dist/scripts/engine-run.js +14 -0
  24. package/dist/scripts/engine-tests.js +45 -0
  25. package/dist/scripts/hbcli-tests.js +85 -0
  26. package/dist/scripts/incident-tests.js +96 -0
  27. package/dist/scripts/journey-tests.js +45 -0
  28. package/dist/scripts/opensky-check.js +20 -0
  29. package/dist/scripts/opensky-tests.js +52 -0
  30. package/dist/scripts/probe-poi-tests.js +79 -0
  31. package/dist/scripts/replay-async.js +44 -0
  32. package/dist/scripts/replay-real.js +38 -0
  33. package/dist/scripts/replay.js +44 -0
  34. package/dist/scripts/skeleton-check.js +35 -0
  35. package/dist/scripts/skeleton-integration-test.js +22 -0
  36. package/dist/scripts/smoke.js +90 -0
  37. package/dist/scripts/unified-tests.js +49 -0
  38. package/dist/scripts/weather-tests.js +48 -0
  39. package/dist/src/bridge.js +34 -0
  40. package/dist/src/contracts.js +64 -0
  41. package/dist/src/dsh-llm.js +154 -0
  42. package/dist/src/engine.js +331 -0
  43. package/dist/src/index.js +760 -0
  44. package/dist/src/journey.js +147 -0
  45. package/dist/src/loop.js +296 -0
  46. package/dist/src/mock-llm.js +98 -0
  47. package/dist/src/model.js +134 -0
  48. package/dist/src/unified.js +536 -0
  49. package/package.json +3 -1
@@ -0,0 +1,201 @@
1
+ {
2
+ "meta": {
3
+ "note": "云南场景包(薄壳段5):带父母/请假型云南行的引擎级数据。班期来自骨架确认+公开调研(估算价);父母约束(海拔/体力/高血压红线)进 CompanionSpec——引擎的同行人感知首例。",
4
+ "sources": [
5
+ "OpenFlights 骨架:SZX↔LJG 直飞(祥鹏 8L)/SZX↔DLH(长龙 GJ)",
6
+ "dsh 验收(b0cfd97):DeepSeek 的丽江2晚+大理2晚方案经 gotry_feasibility_check 判定可行(¥5910/3 人)",
7
+ "住宿:丽江古城 ¥300-500/间,大理洱海 ¥400-900/间(web 检索,9 月淡季)"
8
+ ],
9
+ "companion": {
10
+ "type": "elderly_parents",
11
+ "ages": [
12
+ "65+",
13
+ "62+"
14
+ ],
15
+ "health": [
16
+ "父亲轻度高血压——不上 4506m 玉龙雪山"
17
+ ],
18
+ "energy": {
19
+ "max_altitude_m": 3300,
20
+ "daily_walking_hours": 4,
21
+ "pace": "slow"
22
+ },
23
+ "structural_safety": "丽江(2400m)在前、大理(1976m)在后——适应期后降海拔,高血压窗口落在低压段"
24
+ },
25
+ "reconcil": [
26
+ "M-1 工作窗口约束:FD582 8.4 08:10 起飞在 W4(周二,工作日 10:00-19:00 UTC+4)外——从 DMK 起飞,home_tz_offset_min=240 转换后 11:10 = 工作日,排除。8.4 选 FD582 真实:在迪拜继续工作到 12:00 12:00-15:00 周二午休时间,DMK 起飞后家不在出发地,工作窗口不适用,这就是为什么 Kimi 当初放弃 8.3 提前一天(demo 8.3 备选 MU6088 在 M-1 下不应排除,但 Kimi 实选晚一天的 8.4)。",
27
+ "auto-guess 2026-08-24:Rawai 实际=The Title East Wing Studio 档,约 ¥400/晚(长住+工作型;非套间)—founder 校正即改"
28
+ ]
29
+ },
30
+ "legs": [
31
+ {
32
+ "id": "yn0",
33
+ "note": "昆明→丽江(衔接 8.4 FD582 KMG 11:25 落地后,下午转飞丽江;M-1 工作窗口外)",
34
+ "date": "8.4 下午",
35
+ "services": [
36
+ {
37
+ "id": "MU5819",
38
+ "dep": "14:30",
39
+ "arr": "15:35",
40
+ "price_cny": 520,
41
+ "price_note": "东航,1h5m 直飞"
42
+ },
43
+ {
44
+ "id": "8L9662",
45
+ "dep": "16:40",
46
+ "arr": "17:45",
47
+ "price_cny": 480,
48
+ "price_note": "祥鹏,1h5m 直飞"
49
+ }
50
+ ],
51
+ "evidence": [
52
+ {
53
+ "tag": "[demo-reconcil:已]",
54
+ "note": "8.4 FD582 → 11:25 KMG → 当天转飞丽江(已挖)"
55
+ }
56
+ ]
57
+ },
58
+ {
59
+ "id": "yn1",
60
+ "note": "深圳→丽江(直飞,丽江进大理出的开口程第一段)",
61
+ "date": "flexible",
62
+ "services": [
63
+ {
64
+ "id": "HO2312",
65
+ "dep": "13:40",
66
+ "arr": "16:30",
67
+ "price_cny": 890,
68
+ "price_note": "吉祥航空,9 月淡季估算"
69
+ },
70
+ {
71
+ "id": "8L9572",
72
+ "dep": "15:20",
73
+ "arr": "18:10",
74
+ "price_cny": 780,
75
+ "price_note": "祥鹏航空"
76
+ }
77
+ ],
78
+ "buffer_min": 100,
79
+ "origin_transfer_min": 70,
80
+ "dest_transfer_min": 40,
81
+ "arrive_by": "19:00",
82
+ "origin_tz_offset_min": 480,
83
+ "weekday": "any"
84
+ },
85
+ {
86
+ "id": "yn2",
87
+ "note": "丽江→大理(动车,城际段)",
88
+ "date": "day+2",
89
+ "services": [
90
+ {
91
+ "id": "D8791",
92
+ "dep": "09:30",
93
+ "arr": "11:40",
94
+ "price_cny": 110
95
+ },
96
+ {
97
+ "id": "D8793",
98
+ "dep": "14:20",
99
+ "arr": "16:30",
100
+ "price_cny": 110
101
+ }
102
+ ],
103
+ "buffer_min": 40,
104
+ "origin_transfer_min": 30,
105
+ "dest_transfer_min": 30,
106
+ "arrive_by": "18:00",
107
+ "origin_tz_offset_min": 480,
108
+ "weekday": "any"
109
+ },
110
+ {
111
+ "id": "yn3",
112
+ "note": "大理→丽江(动车返程,衔接直飞深圳)",
113
+ "date": "day+4",
114
+ "services": [
115
+ {
116
+ "id": "D8796",
117
+ "dep": "09:30",
118
+ "arr": "11:40",
119
+ "price_cny": 110
120
+ }
121
+ ],
122
+ "buffer_min": 40,
123
+ "origin_transfer_min": 30,
124
+ "dest_transfer_min": 30,
125
+ "arrive_by": "13:00",
126
+ "origin_tz_offset_min": 480,
127
+ "weekday": "any"
128
+ },
129
+ {
130
+ "id": "yn4",
131
+ "note": "丽江→深圳(直飞返程)",
132
+ "date": "day+4",
133
+ "services": [
134
+ {
135
+ "id": "HO2311",
136
+ "dep": "14:10",
137
+ "arr": "16:55",
138
+ "price_cny": 850
139
+ },
140
+ {
141
+ "id": "8L9571",
142
+ "dep": "19:20",
143
+ "arr": "22:05",
144
+ "price_cny": 700
145
+ }
146
+ ],
147
+ "buffer_min": 100,
148
+ "origin_transfer_min": 40,
149
+ "dest_transfer_min": 50,
150
+ "arrive_by": "23:00",
151
+ "origin_tz_offset_min": 480,
152
+ "weekday": "any"
153
+ }
154
+ ],
155
+ "stays": [
156
+ {
157
+ "id": "yns1",
158
+ "place": "丽江古城内",
159
+ "nights": 2,
160
+ "options": [
161
+ {
162
+ "tier": "舒适",
163
+ "name": "木府心怡/安隅/开臣璞锦",
164
+ "price_per_night_cny": 400,
165
+ "why": "古城内步行即达,无需坡道;海拔 2400m 适应期"
166
+ }
167
+ ],
168
+ "requirements_check": [
169
+ "无高坡道(65+ 体力)",
170
+ "含早(清淡,高血压友好)",
171
+ "氧气/医疗可达"
172
+ ]
173
+ },
174
+ {
175
+ "id": "yns2",
176
+ "place": "大理洱海边",
177
+ "nights": 2,
178
+ "options": [
179
+ {
180
+ "tier": "舒适",
181
+ "name": "得乐茗墅·龙龛码头/闲来无室",
182
+ "price_per_night_cny": 450,
183
+ "why": "降到 1976m,低海拔安全垫;海景给父母"
184
+ }
185
+ ],
186
+ "requirements_check": [
187
+ "低海拔(高血压窗口)",
188
+ "电梯/一层(行李/体力)",
189
+ "近码头(慢游出发点)"
190
+ ]
191
+ }
192
+ ],
193
+ "constraints": {
194
+ "companion_hard": [
195
+ "绝不上玉龙雪山冰川公园(4506m)——父亲高血压",
196
+ "海拔单调递减结构:丽江 2400m → 大理 1976m(适应期后降)",
197
+ "每日步行 ≤4h;起床 ≥07:00(老人生物钟)"
198
+ ],
199
+ "budget_note": "3 人(2 间房);dsh 验收判定 ¥5,910 可行"
200
+ }
201
+ }
@@ -0,0 +1,172 @@
1
+ import { spawn } from 'node:child_process';
2
+ function run(bin, args, timeoutMs) {
3
+ return new Promise((resolve)=>{
4
+ const child = spawn(bin, args, {
5
+ env: process.env
6
+ });
7
+ let stdout = '';
8
+ let stderr = '';
9
+ let err;
10
+ let timedOut = false;
11
+ const timer = setTimeout(()=>{
12
+ timedOut = true;
13
+ try {
14
+ child.kill('SIGKILL');
15
+ } catch {}
16
+ }, timeoutMs);
17
+ child.stdout.on('data', (d)=>{
18
+ stdout += d.toString();
19
+ });
20
+ child.stderr.on('data', (d)=>{
21
+ stderr += d.toString();
22
+ });
23
+ child.on('error', (e)=>{
24
+ err = e.message;
25
+ });
26
+ child.on('close', (code)=>{
27
+ clearTimeout(timer);
28
+ resolve({
29
+ code,
30
+ stdout,
31
+ stderr,
32
+ err,
33
+ timedOut
34
+ });
35
+ });
36
+ });
37
+ }
38
+ const ts = ()=>new Date().toISOString();
39
+ export async function videoSubtitle(q) {
40
+ const started = Date.now();
41
+ const lang = q.lang ?? 'zh-Hans,zh,en';
42
+ const args = [
43
+ '--write-sub',
44
+ '--write-auto-sub',
45
+ '--sub-langs',
46
+ lang,
47
+ '--skip-download',
48
+ '--sub-format',
49
+ 'vtt',
50
+ '-o',
51
+ '/tmp/gotry-sub-%(id)s',
52
+ q.url
53
+ ];
54
+ const r = await run('yt-dlp', args, q.timeoutMs ?? 60_000);
55
+ const latencyMs = Date.now() - started;
56
+ if (r.err?.includes('ENOENT')) {
57
+ return {
58
+ ok: false,
59
+ verdict: 'not-installed',
60
+ evidence: `[agent-reach:yt-dlp@not-installed@${ts()}]`,
61
+ latencyMs,
62
+ stderr: 'yt-dlp 未安装;装法: brew install yt-dlp 或 pip install yt-dlp'
63
+ };
64
+ }
65
+ if (r.timedOut) {
66
+ return {
67
+ ok: false,
68
+ verdict: 'timeout',
69
+ evidence: `[agent-reach:yt-dlp@error@${ts()}] timeout`,
70
+ latencyMs
71
+ };
72
+ }
73
+ if (r.code !== 0) {
74
+ return {
75
+ ok: false,
76
+ verdict: 'error',
77
+ evidence: `[agent-reach:yt-dlp@error@${ts()}] exit ${r.code}`,
78
+ latencyMs,
79
+ stderr: r.stderr.slice(0, 400)
80
+ };
81
+ }
82
+ const m = r.stdout.match(/Writing subtitles to:\s*(\S+)/);
83
+ if (!m) {
84
+ return {
85
+ ok: false,
86
+ verdict: 'error',
87
+ evidence: `[agent-reach:yt-dlp@error@${ts()}] no subtitle file in output`,
88
+ latencyMs,
89
+ stderr: r.stdout.slice(0, 300)
90
+ };
91
+ }
92
+ const { readFile } = await import('node:fs/promises');
93
+ let subtitles;
94
+ try {
95
+ subtitles = (await readFile(m[1], 'utf-8')).slice(0, 8000);
96
+ } catch {}
97
+ return {
98
+ ok: true,
99
+ verdict: 'found',
100
+ evidence: `[agent-reach:yt-dlp@${ts()}]`,
101
+ latencyMs,
102
+ stdout: r.stdout.slice(0, 400),
103
+ subtitles
104
+ };
105
+ }
106
+ export async function githubSearch(q) {
107
+ const started = Date.now();
108
+ const limit = q.limit ?? 5;
109
+ const args = [
110
+ 'search',
111
+ 'repos',
112
+ q.query,
113
+ '--json',
114
+ 'fullName,description,stargazersCount,url',
115
+ '--limit',
116
+ String(limit)
117
+ ];
118
+ const r = await run('gh', args, q.timeoutMs ?? 30_000);
119
+ const latencyMs = Date.now() - started;
120
+ if (r.err?.includes('ENOENT')) {
121
+ return {
122
+ ok: false,
123
+ verdict: 'not-installed',
124
+ evidence: `[agent-reach:gh@not-installed@${ts()}]`,
125
+ latencyMs,
126
+ stderr: 'gh 未安装;装法: brew install gh && gh auth login'
127
+ };
128
+ }
129
+ if (r.timedOut) {
130
+ return {
131
+ ok: false,
132
+ verdict: 'timeout',
133
+ evidence: `[agent-reach:gh@error@${ts()}] timeout`,
134
+ latencyMs
135
+ };
136
+ }
137
+ if (r.code !== 0) {
138
+ return {
139
+ ok: false,
140
+ verdict: 'error',
141
+ evidence: `[agent-reach:gh@error@${ts()}] exit ${r.code}`,
142
+ latencyMs,
143
+ stderr: r.stderr.slice(0, 300)
144
+ };
145
+ }
146
+ try {
147
+ const repos = JSON.parse(r.stdout);
148
+ return {
149
+ ok: true,
150
+ verdict: 'found',
151
+ evidence: `[agent-reach:gh@${ts()}]`,
152
+ latencyMs,
153
+ repos: repos.map((x)=>({
154
+ name: x.fullName,
155
+ description: x.description,
156
+ stars: x.stargazersCount,
157
+ url: x.url
158
+ }))
159
+ };
160
+ } catch {
161
+ return {
162
+ ok: false,
163
+ verdict: 'error',
164
+ evidence: `[agent-reach:gh@error@${ts()}] parse fail`,
165
+ latencyMs,
166
+ stderr: r.stdout.slice(0, 300)
167
+ };
168
+ }
169
+ }
170
+
171
+
172
+ //# sourceURL=/Users/bytedance/work/gotry/ts/capabilities/agent-reach-deep.ts
@@ -0,0 +1,204 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { existsSync } from 'node:fs';
3
+ import { resolve } from 'node:path';
4
+ const ts = ()=>new Date().toISOString();
5
+ const repoRoot = ()=>resolve(import.meta.dirname, '..', '..');
6
+ const venvPython = ()=>resolve(repoRoot(), '.venv/bin/python');
7
+ const bridgeScript = ()=>resolve(import.meta.dirname, 'agent-reach-bridge.py');
8
+ function run(bin, args, timeoutMs) {
9
+ return new Promise((resolveRun)=>{
10
+ const child = spawn(bin, args, {
11
+ env: process.env
12
+ });
13
+ let stdout = '';
14
+ let stderr = '';
15
+ let err;
16
+ const timer = setTimeout(()=>{
17
+ try {
18
+ child.kill('SIGKILL');
19
+ } catch {}
20
+ }, timeoutMs);
21
+ child.stdout.on('data', (d)=>{
22
+ stdout += d.toString();
23
+ });
24
+ child.stderr.on('data', (d)=>{
25
+ stderr += d.toString();
26
+ });
27
+ child.on('error', (e)=>{
28
+ err = e.message;
29
+ });
30
+ child.on('close', (code)=>{
31
+ clearTimeout(timer);
32
+ resolveRun({
33
+ code,
34
+ stdout,
35
+ stderr,
36
+ err
37
+ });
38
+ });
39
+ });
40
+ }
41
+ export async function reach(q) {
42
+ const started = Date.now();
43
+ const evidence = (state)=>`[agent-reach:${q.channel}.${q.method}@${state === 'found' ? ts() : `${state}@${ts()}`}]`;
44
+ const base = {
45
+ channel: q.channel,
46
+ method: q.method
47
+ };
48
+ if (!q.channel || !q.method) {
49
+ return {
50
+ ...base,
51
+ ok: false,
52
+ verdict: 'error',
53
+ evidence: evidence('error'),
54
+ latencyMs: 0,
55
+ error: 'channel 与 method 必填(渠道/方法清单:先随便调一次,inventory 会带回上游清单)'
56
+ };
57
+ }
58
+ const py = venvPython();
59
+ if (!existsSync(py)) {
60
+ return {
61
+ ...base,
62
+ ok: false,
63
+ verdict: 'not-installed',
64
+ evidence: evidence('not-installed'),
65
+ latencyMs: 0,
66
+ setup: 'gotry .venv 缺 python — 见 docs/tokens.md(agent-reach 安装)'
67
+ };
68
+ }
69
+ const args = Array.isArray(q.args) ? q.args : q.args ? q.args.split(/\s+/).filter(Boolean) : [];
70
+ const r = await run(py, [
71
+ bridgeScript(),
72
+ q.channel,
73
+ q.method,
74
+ ...args
75
+ ], q.timeoutMs ?? 30_000);
76
+ const latencyMs = Date.now() - started;
77
+ let parsed = {};
78
+ try {
79
+ parsed = JSON.parse((r.stdout || '').trim().split('\n').filter(Boolean).pop() ?? '{}');
80
+ } catch {}
81
+ if (r.err?.includes('ENOENT')) {
82
+ return {
83
+ ...base,
84
+ ok: false,
85
+ verdict: 'not-installed',
86
+ evidence: evidence('not-installed'),
87
+ latencyMs,
88
+ setup: '.venv/bin/python 不可执行(装 agent-reach: 见 docs/tokens.md)'
89
+ };
90
+ }
91
+ if (parsed.ok === true) {
92
+ return {
93
+ ...base,
94
+ ok: true,
95
+ verdict: 'found',
96
+ evidence: evidence('found'),
97
+ latencyMs,
98
+ data: parsed.data
99
+ };
100
+ }
101
+ if (parsed.error?.includes('agent_reach 未安装')) {
102
+ return {
103
+ ...base,
104
+ ok: false,
105
+ verdict: 'not-installed',
106
+ evidence: evidence('not-installed'),
107
+ latencyMs,
108
+ setup: '.venv 缺 agent-reach(pip install agent-reach,见 docs/tokens.md)'
109
+ };
110
+ }
111
+ if (r.code === 2) {
112
+ return {
113
+ ...base,
114
+ ok: false,
115
+ verdict: 'error',
116
+ evidence: evidence('error'),
117
+ latencyMs,
118
+ error: parsed.error,
119
+ inventory: parsed.channels ? {
120
+ channels: parsed.channels
121
+ } : {
122
+ channel: parsed.channel,
123
+ methods: parsed.methods
124
+ }
125
+ };
126
+ }
127
+ const checkStatus = parsed.check?.status;
128
+ if (checkStatus === 'warn' || checkStatus === 'off') {
129
+ return {
130
+ ...base,
131
+ ok: false,
132
+ verdict: 'needs-setup',
133
+ evidence: evidence('needs-setup'),
134
+ latencyMs,
135
+ error: parsed.error,
136
+ setup: parsed.check?.message
137
+ };
138
+ }
139
+ return {
140
+ ...base,
141
+ ok: false,
142
+ verdict: 'error',
143
+ evidence: evidence('error'),
144
+ latencyMs,
145
+ error: parsed.error ?? r.stderr.slice(0, 200)
146
+ };
147
+ }
148
+ export async function readUrl(q) {
149
+ if (!/^https?:\/\//.test(q.url.trim())) {
150
+ return {
151
+ ok: false,
152
+ via: 'r.jina.ai-error',
153
+ evidence: `[agent-reach:web.read@error@${ts()}] 非法 URL(需 http(s)://)`,
154
+ latencyMs: 0,
155
+ error: 'url 必须以 http(s):// 开头'
156
+ };
157
+ }
158
+ const r = await reach({
159
+ channel: 'web',
160
+ method: 'read',
161
+ args: [
162
+ q.url.trim()
163
+ ],
164
+ timeoutMs: q.timeoutMs
165
+ });
166
+ const content = typeof r.data === 'string' ? r.data.slice(0, 8000) : undefined;
167
+ const title = content?.match(/^Title: (.+)$/m)?.[1];
168
+ return {
169
+ ok: r.ok,
170
+ via: r.ok ? 'r.jina.ai' : 'r.jina.ai-error',
171
+ title,
172
+ content,
173
+ evidence: r.evidence,
174
+ latencyMs: r.latencyMs,
175
+ error: r.error
176
+ };
177
+ }
178
+ export async function reachStatus(timeoutMs = 90_000) {
179
+ const started = Date.now();
180
+ const bin = resolve(repoRoot(), '.venv/bin/agent-reach');
181
+ if (!existsSync(bin)) {
182
+ return {
183
+ ok: false,
184
+ via: 'not-installed',
185
+ evidence: `[agent-reach:doctor@not-installed@${ts()}]`,
186
+ latencyMs: 0,
187
+ output: 'pip install agent-reach 于 .venv(见 docs/tokens.md)'
188
+ };
189
+ }
190
+ const r = await run(bin, [
191
+ 'doctor'
192
+ ], timeoutMs);
193
+ const output = (r.stdout || r.stderr).trim();
194
+ return {
195
+ ok: !r.err?.includes('ENOENT') && r.code === 0,
196
+ via: 'agent-reach-cli',
197
+ evidence: `[agent-reach:doctor@${ts()}] via .venv/bin/agent-reach`,
198
+ latencyMs: Date.now() - started,
199
+ output: output.slice(0, 4000)
200
+ };
201
+ }
202
+
203
+
204
+ //# sourceURL=/Users/bytedance/work/gotry/ts/capabilities/agent-reach.ts