@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,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
@@ -0,0 +1,139 @@
1
+ import { spawn } from 'node:child_process';
2
+ function sh(cmd, args, opts) {
3
+ const ctrl = new AbortController();
4
+ const timer = setTimeout(()=>{
5
+ ctrl.abort();
6
+ }, opts.timeoutMs);
7
+ let child = null;
8
+ let killTimer = null;
9
+ return new Promise((resolve)=>{
10
+ child = spawn(cmd, args, {
11
+ env: opts.env,
12
+ cwd: process.cwd(),
13
+ signal: ctrl.signal
14
+ });
15
+ let stdout = '';
16
+ let stderr = '';
17
+ let error;
18
+ let killed = false;
19
+ child.stdout?.on('data', (d)=>{
20
+ stdout += d.toString();
21
+ });
22
+ child.stderr?.on('data', (d)=>{
23
+ stderr += d.toString();
24
+ });
25
+ child.on('error', (e)=>{
26
+ error = e.message.slice(0, 200);
27
+ });
28
+ child.on('close', (code)=>{
29
+ clearTimeout(timer);
30
+ if (killTimer) clearTimeout(killTimer);
31
+ resolve({
32
+ code: killed ? -2 : code ?? -1,
33
+ stdout,
34
+ stderr,
35
+ error
36
+ });
37
+ });
38
+ ctrl.signal.addEventListener('abort', ()=>{
39
+ if (!child) return;
40
+ try {
41
+ child.kill('SIGTERM');
42
+ } catch {}
43
+ killTimer = setTimeout(()=>{
44
+ try {
45
+ child?.kill('SIGKILL');
46
+ } catch {}
47
+ }, 500);
48
+ });
49
+ });
50
+ }
51
+ const upperFirst = (s)=>s ? s[0].toUpperCase() + s.slice(1) : s;
52
+ const lowerFirst = (s)=>s ? s[0].toLowerCase() + s.slice(1) : s;
53
+ export async function anythingSearch(q) {
54
+ const started = Date.now();
55
+ const ts = new Date().toISOString();
56
+ const kw = (q.keyword ?? '').trim();
57
+ if (!kw) {
58
+ return {
59
+ ok: false,
60
+ via: 'hbcli-anything-error',
61
+ evidence: `[实时API:hbcli-anything@error@${ts}] keyword empty`,
62
+ latencyMs: Date.now() - started,
63
+ verdict: 'error',
64
+ error: 'keyword is required'
65
+ };
66
+ }
67
+ const bin = q.hbcliBin ?? 'hbcli';
68
+ const args = [
69
+ bin,
70
+ 'search',
71
+ 'anything',
72
+ kw
73
+ ];
74
+ if (q.contentType) args.push('--content-type', q.contentType);
75
+ if (q.parentDestinationId !== undefined) {
76
+ args.push('--parent-destination-id', String(q.parentDestinationId));
77
+ }
78
+ const r = await sh(bin, args.slice(1), {
79
+ timeoutMs: q.timeoutMs ?? 12_000,
80
+ env: process.env
81
+ });
82
+ const latencyMs = Date.now() - started;
83
+ if (r.error || r.code !== 0) {
84
+ return {
85
+ ok: false,
86
+ via: 'hbcli-anything-error',
87
+ evidence: `[实时API:hbcli-anything@error@${ts}] ${r.error ?? `exit ${r.code}`}`,
88
+ latencyMs,
89
+ verdict: 'error',
90
+ error: r.error ?? `${r.stderr.slice(0, 200)} (exit ${r.code})`
91
+ };
92
+ }
93
+ let data;
94
+ try {
95
+ const jStart = r.stdout.indexOf('{') >= 0 ? r.stdout.indexOf('{') : r.stdout.indexOf('[');
96
+ const json = jStart >= 0 ? r.stdout.slice(jStart) : r.stdout;
97
+ data = JSON.parse(json);
98
+ } catch {
99
+ return {
100
+ ok: false,
101
+ via: 'hbcli-anything-error',
102
+ evidence: `[实时API:hbcli-anything@error@${ts}] parse failed`,
103
+ latencyMs,
104
+ verdict: 'error',
105
+ error: 'failed to parse hbcli output as JSON'
106
+ };
107
+ }
108
+ const rawItems = data.candidates ?? [];
109
+ const hits = rawItems.map((it)=>{
110
+ const region = it.region ?? {};
111
+ const hotel = it.hotel ?? {};
112
+ const isHotel = it.type === 'hotel' || it.type === 'SupplierHotelList' || Boolean(hotel.id);
113
+ return {
114
+ type: isHotel ? 'hotel' : it.type === 'place' || it.type === 'City' ? lowerFirst(it.type) : 'city',
115
+ name: it.name ?? region.name_en ?? region.name ?? hotel.name ?? '?',
116
+ score: it.score ?? region.typeScore,
117
+ latitude: hotel.latitude ?? region.latitude,
118
+ longitude: hotel.longitude ?? region.longitude,
119
+ destinationId: region.id,
120
+ hotelId: hotel.id
121
+ };
122
+ });
123
+ const verdict = hits.length > 0 ? 'hit' : 'miss';
124
+ return {
125
+ ok: true,
126
+ via: 'hbcli-anything',
127
+ evidence: `[实时API:hbcli-anything@${ts}] ${hits.length}/${rawItems.length} candidates`,
128
+ latencyMs,
129
+ verdict,
130
+ hits,
131
+ totalCandidates: rawItems.length
132
+ };
133
+ }
134
+ export const _ = {
135
+ upperFirst
136
+ };
137
+
138
+
139
+ //# sourceURL=/Users/bytedance/work/gotry/ts/capabilities/anything.ts
@@ -0,0 +1,131 @@
1
+ import { spawn } from 'node:child_process';
2
+ function sh(cmd, args, opts) {
3
+ const child = spawn(cmd, args, {
4
+ env: process.env,
5
+ cwd: process.cwd()
6
+ });
7
+ let stdout = '';
8
+ let stderr = '';
9
+ let error;
10
+ const timer = setTimeout(()=>{
11
+ try {
12
+ child.kill('SIGKILL');
13
+ } catch {}
14
+ }, opts.timeoutMs);
15
+ return new Promise((resolve)=>{
16
+ child.stdout?.on('data', (d)=>{
17
+ stdout += d.toString();
18
+ });
19
+ child.stderr?.on('data', (d)=>{
20
+ stderr += d.toString();
21
+ });
22
+ child.on('error', (e)=>{
23
+ error = e.message.slice(0, 200);
24
+ });
25
+ child.on('close', (code)=>{
26
+ clearTimeout(timer);
27
+ resolve({
28
+ code: code ?? -1,
29
+ stdout,
30
+ stderr,
31
+ error
32
+ });
33
+ });
34
+ }).finally(()=>clearTimeout(timer));
35
+ }
36
+ export async function flyaiSearch(q) {
37
+ const started = Date.now();
38
+ const ts = new Date().toISOString();
39
+ const base = {
40
+ kind: q.kind,
41
+ latencyMs: 0
42
+ };
43
+ const origin = (q.origin ?? '').trim();
44
+ const destination = (q.destination ?? '').trim();
45
+ if (!origin || !destination || !/^\d{4}-\d{2}-\d{2}$/.test(q.depDate ?? '')) {
46
+ return {
47
+ ...base,
48
+ ok: false,
49
+ via: 'flyai-error',
50
+ verdict: 'error',
51
+ evidence: `[实时API:flyai@error@${ts}] bad args`,
52
+ error: 'origin/destination/depDate(YYYY-MM-DD) required'
53
+ };
54
+ }
55
+ const sub = q.kind === 'flight' ? 'search-flight' : 'search-train';
56
+ const r = await sh(q.cliBin ?? 'npx', [
57
+ '-y',
58
+ '@fly-ai/flyai-cli',
59
+ sub,
60
+ '--origin',
61
+ origin,
62
+ '--destination',
63
+ destination,
64
+ '--dep-date',
65
+ q.depDate
66
+ ], {
67
+ timeoutMs: q.timeoutMs ?? 30_000
68
+ });
69
+ const latencyMs = Date.now() - started;
70
+ if (r.error || r.code !== 0) {
71
+ return {
72
+ ...base,
73
+ latencyMs,
74
+ ok: false,
75
+ via: 'flyai-error',
76
+ verdict: 'error',
77
+ evidence: `[实时API:flyai@error@${ts}] ${r.error ?? `exit ${r.code}`}`,
78
+ error: r.error ?? r.stderr.replace(/\s+/g, ' ').slice(0, 200)
79
+ };
80
+ }
81
+ let items;
82
+ try {
83
+ const jStart = r.stdout.indexOf('{');
84
+ const parsed = JSON.parse(jStart >= 0 ? r.stdout.slice(jStart) : r.stdout);
85
+ items = parsed.data?.itemList ?? [];
86
+ } catch {
87
+ const raw = r.stdout.replace(/\s+/g, ' ').slice(0, 160);
88
+ return {
89
+ ...base,
90
+ latencyMs,
91
+ ok: false,
92
+ via: 'flyai-error',
93
+ verdict: 'error',
94
+ evidence: `[实时API:flyai@error@${ts}] parse failed: ${raw}`,
95
+ error: `failed to parse flyai output as JSON: ${raw}`
96
+ };
97
+ }
98
+ const options = [];
99
+ for (const it of items){
100
+ const seg = it.journeys?.[0]?.segments?.[0];
101
+ if (!seg?.marketingTransportNo || !seg.depDateTime) continue;
102
+ const rawPrice = it.ticketPrice ?? it.price ?? '';
103
+ const numericPrice = Number(rawPrice);
104
+ options.push({
105
+ no: seg.marketingTransportNo,
106
+ name: seg.marketingTransportName ?? '',
107
+ depDateTime: seg.depDateTime ?? '',
108
+ arrDateTime: seg.arrDateTime ?? '',
109
+ depStation: seg.depStationName ?? '',
110
+ arrStation: seg.arrStationName ?? '',
111
+ durationMin: Number(seg.duration ?? 0) || 0,
112
+ price: Number.isFinite(numericPrice) && numericPrice > 0 ? numericPrice : 0,
113
+ priceRaw: /^\d+$/.test(rawPrice) ? undefined : rawPrice || undefined,
114
+ seatClass: seg.seatClassName,
115
+ jumpUrl: it.jumpUrl
116
+ });
117
+ }
118
+ const verdict = options.length > 0 ? 'hit' : 'miss';
119
+ return {
120
+ kind: q.kind,
121
+ latencyMs,
122
+ ok: true,
123
+ via: 'flyai',
124
+ verdict,
125
+ evidence: `[实时API:flyai@${ts}] ${options.length}/${items.length} ${q.kind} options`,
126
+ options
127
+ };
128
+ }
129
+
130
+
131
+ //# sourceURL=/Users/bytedance/work/gotry/ts/capabilities/flyai.ts