@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.
- package/LICENSE +21 -0
- package/README.md +267 -0
- package/bin/gotry-inner.js +214 -0
- package/bin/gotry-stdio-ask.js +59 -0
- package/bin/gotry.js +28 -0
- package/cordis.gotry-patch.yml +86 -0
- package/data/flights_2026.json +218 -0
- package/data/golden_erhai.json +92 -0
- package/data/golden_trip_2026.json +75 -0
- package/data/hotels_2026.json +151 -0
- package/data/openflights-skeleton.json +895 -0
- package/data/time-slot-eval.json +384 -0
- package/data/yunnan-pack.json +202 -0
- package/dist/capabilities/agent-reach-bridge.py +90 -0
- package/dist/capabilities/agent-reach-deep.js +172 -0
- package/dist/capabilities/agent-reach.js +204 -0
- package/dist/capabilities/anything.js +139 -0
- package/dist/capabilities/flyai.js +131 -0
- package/dist/capabilities/hbcli.js +143 -0
- package/dist/capabilities/incident-log.js +100 -0
- package/dist/capabilities/opensky.js +74 -0
- package/dist/capabilities/session/action-cache.js +120 -0
- package/dist/capabilities/session/adapters/ctrip-flight.js +83 -0
- package/dist/capabilities/session/adapters/meituan-local.js +73 -0
- package/dist/capabilities/session/extract.js +40 -0
- package/dist/capabilities/session/read-guard.js +99 -0
- package/dist/capabilities/session/transport.js +101 -0
- package/dist/capabilities/session-search.js +93 -0
- package/dist/capabilities/weather.js +180 -0
- package/dist/scripts/action-cache-tests.js +107 -0
- package/dist/scripts/agent-reach-deep-tests.js +54 -0
- package/dist/scripts/agent-reach-tests.js +38 -0
- package/dist/scripts/agent-reach-wrapper-tests.js +79 -0
- package/dist/scripts/anything-tests.js +95 -0
- package/dist/scripts/async-collect.js +37 -0
- package/dist/scripts/companion-tests.js +112 -0
- package/dist/scripts/diff-test.js +39 -0
- package/dist/scripts/engine-run.js +14 -0
- package/dist/scripts/engine-tests.js +45 -0
- package/dist/scripts/hbcli-tests.js +101 -0
- package/dist/scripts/incident-tests.js +96 -0
- package/dist/scripts/journey-tests.js +45 -0
- package/dist/scripts/ledger-tests.js +365 -0
- package/dist/scripts/ledger-workflow-crash.js +42 -0
- package/dist/scripts/memory-capture-tests.js +77 -0
- package/dist/scripts/memory-decay-tests.js +83 -0
- package/dist/scripts/memory-metrics.js +24 -0
- package/dist/scripts/nudge-digest.js +83 -0
- package/dist/scripts/opensky-check.js +20 -0
- package/dist/scripts/opensky-tests.js +52 -0
- package/dist/scripts/probe-poi-tests.js +88 -0
- package/dist/scripts/publish-preverify.js +68 -0
- package/dist/scripts/replay-async.js +44 -0
- package/dist/scripts/replay-real.js +38 -0
- package/dist/scripts/replay.js +116 -0
- package/dist/scripts/session-attach-diagnose.js +27 -0
- package/dist/scripts/session-attach-poc.js +65 -0
- package/dist/scripts/session-extract-tests.js +81 -0
- package/dist/scripts/session-tests.js +162 -0
- package/dist/scripts/skeleton-check.js +37 -0
- package/dist/scripts/skeleton-integration-test.js +22 -0
- package/dist/scripts/skills-contract-tests.js +85 -0
- package/dist/scripts/smoke.js +328 -0
- package/dist/scripts/state-cli-tests.js +136 -0
- package/dist/scripts/state-cli.js +248 -0
- package/dist/scripts/time-eval-tests.js +318 -0
- package/dist/scripts/travel-timeline-tests.js +123 -0
- package/dist/scripts/unified-tests.js +49 -0
- package/dist/scripts/weather-tests.js +48 -0
- package/dist/src/bridge.js +34 -0
- package/dist/src/companions.js +112 -0
- package/dist/src/contracts.js +64 -0
- package/dist/src/dsh-llm.js +174 -0
- package/dist/src/engine.js +331 -0
- package/dist/src/index.js +1239 -0
- package/dist/src/journey.js +147 -0
- package/dist/src/loop.js +360 -0
- package/dist/src/memory-capture.js +40 -0
- package/dist/src/memory-decay.js +31 -0
- package/dist/src/memory-utility.js +55 -0
- package/dist/src/mock-llm.js +103 -0
- package/dist/src/model.js +134 -0
- package/dist/src/slot-spec.js +165 -0
- package/dist/src/state-ledger.js +901 -0
- package/dist/src/time-anchor.js +100 -0
- package/dist/src/tool-packet.js +12 -0
- package/dist/src/travel-slots.js +144 -0
- package/dist/src/travel-timeline.js +78 -0
- package/dist/src/unified.js +538 -0
- package/dist/src/wish-pool.js +27 -0
- package/package.json +84 -0
- package/ts/capabilities/agent-reach-deep.ts +114 -0
- package/ts/capabilities/agent-reach.ts +158 -0
- package/ts/capabilities/anything.ts +201 -0
- package/ts/capabilities/flyai.ts +151 -0
- package/ts/capabilities/hbcli.ts +152 -0
- package/ts/capabilities/incident-log.ts +148 -0
- package/ts/capabilities/opensky.ts +121 -0
- package/ts/capabilities/session/action-cache.ts +148 -0
- package/ts/capabilities/session/adapters/ctrip-flight.ts +107 -0
- package/ts/capabilities/session/adapters/meituan-local.ts +70 -0
- package/ts/capabilities/session/extract.ts +54 -0
- package/ts/capabilities/session/read-guard.ts +141 -0
- package/ts/capabilities/session/transport.ts +114 -0
- package/ts/capabilities/session-search.ts +129 -0
- package/ts/capabilities/weather.ts +176 -0
- package/ts/package.json +27 -0
- package/ts/scripts/async-collect.ts +50 -0
- package/ts/scripts/skeleton-check.ts +49 -0
- package/ts/scripts/skeleton-integration-test.ts +23 -0
- package/ts/scripts/state-cli.ts +201 -0
- package/ts/src/bridge.ts +44 -0
- package/ts/src/companions.ts +113 -0
- package/ts/src/contracts.ts +165 -0
- package/ts/src/dsh-llm.ts +155 -0
- package/ts/src/index.ts +968 -0
- package/ts/src/loop.ts +411 -0
- package/ts/src/memory-capture.ts +59 -0
- package/ts/src/memory-decay.ts +55 -0
- package/ts/src/memory-utility.ts +70 -0
- package/ts/src/mock-llm.ts +100 -0
- package/ts/src/model.ts +220 -0
- package/ts/src/slot-spec.ts +172 -0
- package/ts/src/state-ledger.ts +849 -0
- package/ts/src/time-anchor.ts +134 -0
- package/ts/src/tool-packet.ts +32 -0
- package/ts/src/travel-slots.ts +219 -0
- package/ts/src/travel-timeline.ts +83 -0
- package/ts/src/unified.ts +582 -0
- 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
|