@danceiny/gotry 0.0.1-rc.14 → 0.0.1-rc.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -20
- package/README.zh-CN.md +27 -21
- package/bin/gotry-bootstrap.js +331 -3
- package/cordis.gotry-patch.yml +10 -0
- package/data/airline-airports.json +39 -0
- package/dist/capabilities/agent-reach-deep.js +1 -1
- package/dist/capabilities/agent-reach.js +1 -1
- package/dist/capabilities/anything.js +1 -1
- package/dist/capabilities/artifacts.js +217 -0
- package/dist/capabilities/effect.js +293 -0
- package/dist/capabilities/fact-log.js +41 -0
- package/dist/capabilities/flyai.js +1 -1
- package/dist/capabilities/hbcli.js +43 -1
- package/dist/capabilities/incident-log.js +1 -1
- package/dist/capabilities/opensky.js +1 -1
- package/dist/capabilities/resilience.js +90 -0
- package/dist/capabilities/session/action-cache.js +1 -1
- package/dist/capabilities/session/adapters/ctrip-flight.js +1 -1
- package/dist/capabilities/session/adapters/meituan-local.js +1 -1
- package/dist/capabilities/session/benchmark.js +2 -1
- package/dist/capabilities/session/extension-bridge.js +345 -0
- package/dist/capabilities/session/extension-channel.js +93 -0
- package/dist/capabilities/session/extract.js +1 -1
- package/dist/capabilities/session/health-watch.js +216 -0
- package/dist/capabilities/session/read-guard.js +1 -1
- package/dist/capabilities/session/transport.js +1 -1
- package/dist/capabilities/session/wizard.js +377 -0
- package/dist/capabilities/session-consent.js +1 -1
- package/dist/capabilities/session-login.js +77 -1
- package/dist/capabilities/session-search.js +69 -2
- package/dist/capabilities/weather.js +1 -1
- package/dist/scripts/action-cache-tests.js +1 -1
- package/dist/scripts/agent-reach-deep-tests.js +1 -1
- package/dist/scripts/agent-reach-tests.js +1 -1
- package/dist/scripts/agent-reach-wrapper-tests.js +1 -1
- package/dist/scripts/anything-tests.js +1 -1
- package/dist/scripts/async-collect.js +1 -1
- package/dist/scripts/booking-saga-tests.js +187 -0
- package/dist/scripts/bootstrap-tests.js +49 -3
- package/dist/scripts/build-changelog.js +279 -0
- package/dist/scripts/changelog-tests.js +165 -0
- package/dist/scripts/companion-tests.js +1 -1
- package/dist/scripts/diff-test.js +1 -1
- package/dist/scripts/effect-tests.js +474 -0
- package/dist/scripts/engine-run.js +1 -1
- package/dist/scripts/engine-tests.js +1 -1
- package/dist/scripts/extension-tests.js +468 -0
- package/dist/scripts/fact-gate-tests.js +318 -0
- package/dist/scripts/flyai-tests.js +1 -1
- package/dist/scripts/hbcli-e2e-tests.js +180 -0
- package/dist/scripts/hbcli-tests.js +1 -1
- package/dist/scripts/health-watch-cli.js +51 -0
- package/dist/scripts/i18n-tests.js +1 -1
- package/dist/scripts/incident-tests.js +1 -1
- package/dist/scripts/journey-tests.js +1 -1
- package/dist/scripts/ledger-tests.js +1 -1
- package/dist/scripts/ledger-workflow-crash.js +1 -1
- package/dist/scripts/memory-capture-tests.js +1 -1
- package/dist/scripts/memory-decay-tests.js +1 -1
- package/dist/scripts/memory-metrics.js +1 -1
- package/dist/scripts/memory-value-report.js +1 -1
- package/dist/scripts/nightly-evidence-tests.js +186 -0
- package/dist/scripts/nightly-evidence.js +289 -0
- package/dist/scripts/nudge-digest.js +1 -1
- package/dist/scripts/onboarding-tests.js +218 -0
- package/dist/scripts/opensky-check.js +1 -1
- package/dist/scripts/opensky-tests.js +1 -1
- package/dist/scripts/price-drift-tests.js +441 -0
- package/dist/scripts/price-drift-watch.js +491 -0
- package/dist/scripts/probe-poi-tests.js +1 -1
- package/dist/scripts/product-metrics.js +1 -1
- package/dist/scripts/publish-preverify.js +1 -1
- package/dist/scripts/realtime-pricing-tests.js +1 -1
- package/dist/scripts/replay-async.js +1 -1
- package/dist/scripts/replay-real.js +1 -1
- package/dist/scripts/replay.js +1 -1
- package/dist/scripts/session-attach-diagnose.js +1 -1
- package/dist/scripts/session-attach-poc.js +1 -1
- package/dist/scripts/session-benchmark.js +1 -1
- package/dist/scripts/session-extract-tests.js +1 -1
- package/dist/scripts/session-login.js +1 -1
- package/dist/scripts/session-tests.js +1 -1
- package/dist/scripts/skeleton-check.js +1 -1
- package/dist/scripts/skeleton-integration-test.js +1 -1
- package/dist/scripts/skills-contract-tests.js +1 -1
- package/dist/scripts/smoke.js +159 -1
- package/dist/scripts/state-cli-tests.js +1 -1
- package/dist/scripts/state-cli.js +1 -1
- package/dist/scripts/time-eval-tests.js +1 -1
- package/dist/scripts/travel-timeline-tests.js +1 -1
- package/dist/scripts/unified-tests.js +1 -1
- package/dist/scripts/weather-tests.js +1 -1
- package/dist/scripts/wizard-bootstrap.js +32 -0
- package/dist/scripts/z3-race-tests.js +1 -1
- package/dist/src/artifact-gate.js +310 -0
- package/dist/src/bookable-facts.js +349 -0
- package/dist/src/booking-saga.js +153 -0
- package/dist/src/bridge.js +1 -1
- package/dist/src/companions.js +1 -1
- package/dist/src/contracts.js +1 -1
- package/dist/src/dsh-llm.js +31 -7
- package/dist/src/engine.js +1 -1
- package/dist/src/i18n.js +1 -1
- package/dist/src/index.js +322 -46
- package/dist/src/journey.js +1 -1
- package/dist/src/loop.js +1 -1
- package/dist/src/memory-capture.js +1 -1
- package/dist/src/memory-decay.js +1 -1
- package/dist/src/memory-utility.js +1 -1
- package/dist/src/mock-llm.js +1 -1
- package/dist/src/model.js +1 -1
- package/dist/src/realtime-pricing.js +14 -7
- package/dist/src/slot-spec.js +1 -1
- package/dist/src/state-ledger.js +1 -1
- package/dist/src/time-anchor.js +1 -1
- package/dist/src/tool-packet.js +1 -1
- package/dist/src/travel-slots.js +1 -1
- package/dist/src/travel-timeline.js +1 -1
- package/dist/src/unified.js +1 -1
- package/dist/src/wish-pool.js +1 -1
- package/dist/src/z3-shared.js +1 -1
- package/extension/README.md +26 -0
- package/extension/background.js +160 -0
- package/extension/content-bridge.js +41 -0
- package/extension/content-main.js +62 -0
- package/extension/manifest.json +32 -0
- package/package.json +8 -2
- package/ts/capabilities/artifacts.ts +235 -0
- package/ts/capabilities/fact-log.ts +54 -0
- package/ts/capabilities/hbcli.ts +46 -0
- package/ts/capabilities/session/benchmark.ts +4 -1
- package/ts/capabilities/session/extension-bridge.ts +354 -0
- package/ts/capabilities/session/extension-channel.ts +92 -0
- package/ts/capabilities/session/health-watch.ts +257 -0
- package/ts/capabilities/session/wizard.ts +282 -0
- package/ts/capabilities/session-login.ts +53 -2
- package/ts/capabilities/session-search.ts +68 -3
- package/ts/src/artifact-gate.ts +365 -0
- package/ts/src/bookable-facts.ts +557 -0
- package/ts/src/booking-saga.ts +133 -0
- package/ts/src/dsh-llm.ts +40 -3
- package/ts/src/index.ts +230 -28
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
export function backoffDelayMs(policy, failedAttempt) {
|
|
2
|
+
const raw = policy.baseDelayMs * 2 ** (Math.max(1, failedAttempt) - 1);
|
|
3
|
+
return Math.min(raw, policy.maxDelayMs);
|
|
4
|
+
}
|
|
5
|
+
const defaultSleep = (ms)=>new Promise((resolve)=>setTimeout(resolve, ms));
|
|
6
|
+
export async function withRetry(fn, policy, onBackoff) {
|
|
7
|
+
const sleep = policy.sleep ?? defaultSleep;
|
|
8
|
+
const maxAttempts = Math.max(1, policy.maxAttempts);
|
|
9
|
+
let attempts = 0;
|
|
10
|
+
let backoffMs = 0;
|
|
11
|
+
for(;;){
|
|
12
|
+
attempts += 1;
|
|
13
|
+
let result = null;
|
|
14
|
+
let error = null;
|
|
15
|
+
try {
|
|
16
|
+
result = await fn();
|
|
17
|
+
} catch (e) {
|
|
18
|
+
error = e;
|
|
19
|
+
}
|
|
20
|
+
const retryable = policy.isRetryable?.(result, error) ?? (error != null || result?.ok === false);
|
|
21
|
+
if (attempts < maxAttempts && retryable) {
|
|
22
|
+
const delay = backoffDelayMs(policy, attempts);
|
|
23
|
+
onBackoff?.(attempts, delay);
|
|
24
|
+
backoffMs += delay;
|
|
25
|
+
await sleep(delay);
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
return {
|
|
29
|
+
result,
|
|
30
|
+
error,
|
|
31
|
+
attempts,
|
|
32
|
+
backoffMs
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
export class CircuitBreaker {
|
|
37
|
+
o;
|
|
38
|
+
consecutiveFailures = 0;
|
|
39
|
+
openedAt = 0;
|
|
40
|
+
probeInFlight = false;
|
|
41
|
+
constructor(o){
|
|
42
|
+
this.o = o;
|
|
43
|
+
}
|
|
44
|
+
threshold() {
|
|
45
|
+
return Math.max(1, this.o.failureThreshold);
|
|
46
|
+
}
|
|
47
|
+
now() {
|
|
48
|
+
return (this.o.now ?? Date.now)();
|
|
49
|
+
}
|
|
50
|
+
state() {
|
|
51
|
+
if (this.openedAt > 0 && this.now() - this.openedAt >= this.o.openMs) return 'half-open';
|
|
52
|
+
if (this.openedAt > 0) return 'open';
|
|
53
|
+
return 'closed';
|
|
54
|
+
}
|
|
55
|
+
canAttempt() {
|
|
56
|
+
const s = this.state();
|
|
57
|
+
if (s === 'closed') return {
|
|
58
|
+
allowed: true,
|
|
59
|
+
state: s
|
|
60
|
+
};
|
|
61
|
+
if (s === 'open') return {
|
|
62
|
+
allowed: false,
|
|
63
|
+
state: s
|
|
64
|
+
};
|
|
65
|
+
if (this.probeInFlight) return {
|
|
66
|
+
allowed: false,
|
|
67
|
+
state: s
|
|
68
|
+
};
|
|
69
|
+
this.probeInFlight = true;
|
|
70
|
+
return {
|
|
71
|
+
allowed: true,
|
|
72
|
+
state: s
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
onSuccess() {
|
|
76
|
+
this.consecutiveFailures = 0;
|
|
77
|
+
this.openedAt = 0;
|
|
78
|
+
this.probeInFlight = false;
|
|
79
|
+
}
|
|
80
|
+
onFailure() {
|
|
81
|
+
this.probeInFlight = false;
|
|
82
|
+
this.consecutiveFailures += 1;
|
|
83
|
+
if (this.openedAt > 0 || this.consecutiveFailures >= this.threshold()) {
|
|
84
|
+
this.openedAt = this.now();
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
//# sourceURL=/Users/bytedance/work/gotry-publish/ts/capabilities/resilience.ts
|
|
@@ -228,6 +228,7 @@ export function evaluateDoubleSource(input) {
|
|
|
228
228
|
}
|
|
229
229
|
if (!session) return baseEvaluation('source_unavailable', 'no_spend_stop');
|
|
230
230
|
if (session.verdict === 'needs-attach') return baseEvaluation('waiting_attach', 'no_spend_waiting_user');
|
|
231
|
+
if (session.verdict === 'needs-extension') return baseEvaluation('waiting_extension', 'no_spend_waiting_user');
|
|
231
232
|
if (session.verdict === 'needs-login') return baseEvaluation('waiting_login', 'no_spend_waiting_user');
|
|
232
233
|
if (!official || official.verdict !== 'hit' || session.verdict !== 'hit') {
|
|
233
234
|
return baseEvaluation('source_unavailable', 'no_spend_stop');
|
|
@@ -249,4 +250,4 @@ export function evaluateDoubleSource(input) {
|
|
|
249
250
|
}
|
|
250
251
|
|
|
251
252
|
|
|
252
|
-
//# sourceURL=/Users/bytedance/work/gotry/ts/capabilities/session/benchmark.ts
|
|
253
|
+
//# sourceURL=/Users/bytedance/work/gotry-publish/ts/capabilities/session/benchmark.ts
|
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { createServer } from 'node:http';
|
|
3
|
+
export const BRIDGE_PORTS = [
|
|
4
|
+
8791,
|
|
5
|
+
8792,
|
|
6
|
+
8793,
|
|
7
|
+
8794,
|
|
8
|
+
8795
|
|
9
|
+
];
|
|
10
|
+
export const BRIDGE_PROTOCOL = 'session-bridge.v1';
|
|
11
|
+
export const EXTENSION_ID = 'olpgkofjhhiiiahdkkbcninhjmegghfe';
|
|
12
|
+
export const EXTENSION_ORIGIN = `chrome-extension://${EXTENSION_ID}`;
|
|
13
|
+
export const EXTENSION_CONNECTED_WINDOW_MS = 45_000;
|
|
14
|
+
export const JOBS_LONG_POLL_MS = 20_000;
|
|
15
|
+
export const DEFAULT_EXTENSION_WAIT_MS = 6_000;
|
|
16
|
+
export const MAX_RESULT_BODY_BYTES = 8 * 1024 * 1024;
|
|
17
|
+
function readBody(req, cap) {
|
|
18
|
+
return new Promise((resolve)=>{
|
|
19
|
+
const chunks = [];
|
|
20
|
+
let size = 0;
|
|
21
|
+
let done = false;
|
|
22
|
+
req.on('data', (c)=>{
|
|
23
|
+
if (done) return;
|
|
24
|
+
size += c.length;
|
|
25
|
+
if (size > cap) {
|
|
26
|
+
done = true;
|
|
27
|
+
req.removeAllListeners('data');
|
|
28
|
+
resolve({
|
|
29
|
+
err: `body 超 ${cap} 字节上限(防失控回包)`
|
|
30
|
+
});
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
chunks.push(c);
|
|
34
|
+
});
|
|
35
|
+
req.on('end', ()=>{
|
|
36
|
+
if (!done) {
|
|
37
|
+
done = true;
|
|
38
|
+
resolve(Buffer.concat(chunks).toString('utf8'));
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
req.on('error', ()=>{
|
|
42
|
+
if (!done) {
|
|
43
|
+
done = true;
|
|
44
|
+
resolve({
|
|
45
|
+
err: 'body 读取失败'
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
export async function createSessionBridge(opts = {}) {
|
|
52
|
+
const ports = opts.ports ?? [
|
|
53
|
+
...BRIDGE_PORTS
|
|
54
|
+
];
|
|
55
|
+
const extensionOrigin = opts.extensionOrigin ?? EXTENSION_ORIGIN;
|
|
56
|
+
const now = opts.now ?? Date.now;
|
|
57
|
+
const queue = [];
|
|
58
|
+
const inFlight = new Map();
|
|
59
|
+
const parked = [];
|
|
60
|
+
let lastSeenAt = 0;
|
|
61
|
+
let closed = false;
|
|
62
|
+
let port = 0;
|
|
63
|
+
const extensionConnected = ()=>lastSeenAt > 0 && now() - lastSeenAt < EXTENSION_CONNECTED_WINDOW_MS;
|
|
64
|
+
function takeQueued() {
|
|
65
|
+
const next = queue.shift();
|
|
66
|
+
if (next) inFlight.set(next.job.jobId, next);
|
|
67
|
+
return next ?? null;
|
|
68
|
+
}
|
|
69
|
+
const server = createServer((req, res)=>{
|
|
70
|
+
if (closed) {
|
|
71
|
+
res.statusCode = 503;
|
|
72
|
+
res.end();
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
const urlPath = (req.url ?? '').split('?')[0];
|
|
76
|
+
const origin = req.headers.origin;
|
|
77
|
+
const isPost = req.method === 'POST';
|
|
78
|
+
if (isPost && origin !== extensionOrigin) {
|
|
79
|
+
res.statusCode = 403;
|
|
80
|
+
res.setHeader('content-type', 'application/json');
|
|
81
|
+
res.end(JSON.stringify({
|
|
82
|
+
ok: false,
|
|
83
|
+
error: 'origin 不在桥白名单'
|
|
84
|
+
}));
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
if (origin === extensionOrigin) {
|
|
88
|
+
lastSeenAt = now();
|
|
89
|
+
}
|
|
90
|
+
const finish = (code, body)=>{
|
|
91
|
+
res.statusCode = code;
|
|
92
|
+
res.setHeader('content-type', 'application/json');
|
|
93
|
+
res.end(JSON.stringify(body));
|
|
94
|
+
};
|
|
95
|
+
if (req.method === 'GET' && urlPath === '/health') {
|
|
96
|
+
finish(200, {
|
|
97
|
+
ok: true,
|
|
98
|
+
service: 'gotry-session-bridge',
|
|
99
|
+
protocol: BRIDGE_PROTOCOL
|
|
100
|
+
});
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
if (req.method === 'GET' && urlPath === '/status') {
|
|
104
|
+
finish(200, {
|
|
105
|
+
ok: true,
|
|
106
|
+
port,
|
|
107
|
+
protocol: BRIDGE_PROTOCOL,
|
|
108
|
+
extensionConnected: extensionConnected(),
|
|
109
|
+
lastSeenMsAgo: lastSeenAt > 0 ? now() - lastSeenAt : null,
|
|
110
|
+
queued: queue.length,
|
|
111
|
+
inFlight: inFlight.size
|
|
112
|
+
});
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
if (isPost && urlPath === '/jobs') {
|
|
116
|
+
const next = takeQueued();
|
|
117
|
+
if (next) {
|
|
118
|
+
finish(200, {
|
|
119
|
+
job: next.job
|
|
120
|
+
});
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
const parkTimer = setTimeout(()=>{
|
|
124
|
+
const i = parked.findIndex((p)=>p.res === res);
|
|
125
|
+
if (i >= 0) parked.splice(i, 1);
|
|
126
|
+
finish(200, {
|
|
127
|
+
job: null
|
|
128
|
+
});
|
|
129
|
+
}, JOBS_LONG_POLL_MS);
|
|
130
|
+
parked.push({
|
|
131
|
+
res,
|
|
132
|
+
timer: parkTimer
|
|
133
|
+
});
|
|
134
|
+
res.on('close', ()=>{
|
|
135
|
+
const i = parked.findIndex((p)=>p.res === res);
|
|
136
|
+
if (i >= 0) parked.splice(i, 1);
|
|
137
|
+
clearTimeout(parkTimer);
|
|
138
|
+
});
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
if (isPost && urlPath.startsWith('/results/')) {
|
|
142
|
+
const jobId = decodeURIComponent(urlPath.slice('/results/'.length));
|
|
143
|
+
void readBody(req, MAX_RESULT_BODY_BYTES).then((body)=>{
|
|
144
|
+
if (typeof body !== 'string') {
|
|
145
|
+
finish(400, {
|
|
146
|
+
ok: false,
|
|
147
|
+
error: body.err
|
|
148
|
+
});
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
let parsed;
|
|
152
|
+
try {
|
|
153
|
+
parsed = JSON.parse(body);
|
|
154
|
+
} catch {
|
|
155
|
+
finish(400, {
|
|
156
|
+
ok: false,
|
|
157
|
+
error: '回包不是 JSON'
|
|
158
|
+
});
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
finish(200, {
|
|
162
|
+
ok: true
|
|
163
|
+
});
|
|
164
|
+
resolveJob(jobId, parsed);
|
|
165
|
+
});
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
finish(404, {
|
|
169
|
+
ok: false,
|
|
170
|
+
error: 'unknown endpoint'
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
function resolveJob(jobId, parsed) {
|
|
174
|
+
const inFlightHit = inFlight.get(jobId);
|
|
175
|
+
if (inFlightHit) {
|
|
176
|
+
inFlight.delete(jobId);
|
|
177
|
+
if (inFlightHit.timer) clearTimeout(inFlightHit.timer);
|
|
178
|
+
inFlightHit.resolve({
|
|
179
|
+
ok: true,
|
|
180
|
+
result: parsed
|
|
181
|
+
});
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
const idx = queue.findIndex((q)=>q.job.jobId === jobId);
|
|
185
|
+
if (idx < 0) return;
|
|
186
|
+
const [entry] = queue.splice(idx, 1);
|
|
187
|
+
if (entry.timer) clearTimeout(entry.timer);
|
|
188
|
+
entry.resolve({
|
|
189
|
+
ok: true,
|
|
190
|
+
result: parsed
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
function dispatchToParked(entry) {
|
|
194
|
+
const p = parked.shift();
|
|
195
|
+
if (!p) return false;
|
|
196
|
+
clearTimeout(p.timer);
|
|
197
|
+
inFlight.set(entry.job.jobId, entry);
|
|
198
|
+
p.res.statusCode = 200;
|
|
199
|
+
p.res.setHeader('content-type', 'application/json');
|
|
200
|
+
p.res.end(JSON.stringify({
|
|
201
|
+
job: entry.job
|
|
202
|
+
}));
|
|
203
|
+
return true;
|
|
204
|
+
}
|
|
205
|
+
const bridge = {
|
|
206
|
+
port: 0,
|
|
207
|
+
extensionConnected,
|
|
208
|
+
submit (job, submitOpts = {}) {
|
|
209
|
+
const extensionWaitMs = submitOpts.extensionWaitMs ?? DEFAULT_EXTENSION_WAIT_MS;
|
|
210
|
+
const timeoutMs = submitOpts.timeoutMs ?? job.timeoutMs ?? 30_000;
|
|
211
|
+
const full = {
|
|
212
|
+
jobId: randomUUID(),
|
|
213
|
+
...job
|
|
214
|
+
};
|
|
215
|
+
return new Promise((resolve)=>{
|
|
216
|
+
if (closed) {
|
|
217
|
+
resolve({
|
|
218
|
+
ok: false,
|
|
219
|
+
reason: 'bridge-unavailable',
|
|
220
|
+
summary: '扩展桥已关闭'
|
|
221
|
+
});
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
const entry = {
|
|
225
|
+
job: full,
|
|
226
|
+
resolve: null,
|
|
227
|
+
timer: null
|
|
228
|
+
};
|
|
229
|
+
const settle = (outcome)=>{
|
|
230
|
+
if (entry.timer) clearTimeout(entry.timer);
|
|
231
|
+
const i = queue.findIndex((q)=>q.job.jobId === full.jobId);
|
|
232
|
+
if (i >= 0) queue.splice(i, 1);
|
|
233
|
+
inFlight.delete(full.jobId);
|
|
234
|
+
resolve(outcome);
|
|
235
|
+
};
|
|
236
|
+
entry.resolve = settle;
|
|
237
|
+
entry.timer = setTimeout(()=>{
|
|
238
|
+
settle({
|
|
239
|
+
ok: false,
|
|
240
|
+
reason: 'timeout',
|
|
241
|
+
summary: `扩展未在 ${timeoutMs}ms 内回包(标签页无嗅探命中或扩展已停用)`
|
|
242
|
+
});
|
|
243
|
+
}, timeoutMs);
|
|
244
|
+
if (!dispatchToParked(entry)) queue.push(entry);
|
|
245
|
+
let waited = 0;
|
|
246
|
+
const waitTimer = setInterval(()=>{
|
|
247
|
+
waited += 250;
|
|
248
|
+
if (bridge.extensionConnected()) {
|
|
249
|
+
clearInterval(waitTimer);
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
if (waited >= extensionWaitMs) {
|
|
253
|
+
clearInterval(waitTimer);
|
|
254
|
+
settle({
|
|
255
|
+
ok: false,
|
|
256
|
+
reason: 'extension-not-connected',
|
|
257
|
+
summary: 'GoTry Session Bridge 扩展未连接(未安装或已停用)。一次性安装:npx gotry setup 查看指引 → chrome://extensions 开发者模式「加载已解压的扩展程序」指向 ~/.gotry/extension'
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
}, 250);
|
|
261
|
+
});
|
|
262
|
+
},
|
|
263
|
+
close: ()=>new Promise((resolve)=>{
|
|
264
|
+
closed = true;
|
|
265
|
+
const voided = {
|
|
266
|
+
ok: false,
|
|
267
|
+
reason: 'bridge-unavailable',
|
|
268
|
+
summary: '扩展桥关闭,任务作废(零花费)'
|
|
269
|
+
};
|
|
270
|
+
for (const q of queue.splice(0)){
|
|
271
|
+
if (q.timer) clearTimeout(q.timer);
|
|
272
|
+
q.resolve(voided);
|
|
273
|
+
}
|
|
274
|
+
for (const [, q] of inFlight){
|
|
275
|
+
if (q.timer) clearTimeout(q.timer);
|
|
276
|
+
q.resolve(voided);
|
|
277
|
+
}
|
|
278
|
+
inFlight.clear();
|
|
279
|
+
for (const p of parked.splice(0)){
|
|
280
|
+
clearTimeout(p.timer);
|
|
281
|
+
p.res.destroy();
|
|
282
|
+
}
|
|
283
|
+
server.close(()=>resolve());
|
|
284
|
+
server.closeAllConnections?.();
|
|
285
|
+
})
|
|
286
|
+
};
|
|
287
|
+
const listenAt = (p)=>new Promise((resolve, reject)=>{
|
|
288
|
+
const onError = (e)=>{
|
|
289
|
+
server.off('listening', onListening);
|
|
290
|
+
reject(e);
|
|
291
|
+
};
|
|
292
|
+
const onListening = ()=>{
|
|
293
|
+
server.off('error', onError);
|
|
294
|
+
resolve();
|
|
295
|
+
};
|
|
296
|
+
server.once('error', onError);
|
|
297
|
+
server.once('listening', onListening);
|
|
298
|
+
server.listen(p, '127.0.0.1');
|
|
299
|
+
});
|
|
300
|
+
for (const p of ports){
|
|
301
|
+
try {
|
|
302
|
+
await listenAt(p);
|
|
303
|
+
if (!opts.keepBridge) {
|
|
304
|
+
server.unref();
|
|
305
|
+
}
|
|
306
|
+
port = server.address().port;
|
|
307
|
+
bridge.port = port;
|
|
308
|
+
return {
|
|
309
|
+
ok: true,
|
|
310
|
+
bridge
|
|
311
|
+
};
|
|
312
|
+
} catch {}
|
|
313
|
+
}
|
|
314
|
+
return {
|
|
315
|
+
ok: false,
|
|
316
|
+
summary: `扩展桥端口池 ${ports.join('/')} 全部被占(多为并行 gotry 实例);不影响官方通道 gotry_flyai_search。关闭多余实例后重试。`
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
let singleton = null;
|
|
320
|
+
export async function getOrCreateSessionBridge(opts = {}) {
|
|
321
|
+
if (singleton) return singleton;
|
|
322
|
+
const created = await createSessionBridge(opts);
|
|
323
|
+
const result = created.ok ? {
|
|
324
|
+
ok: true,
|
|
325
|
+
bridge: created.bridge
|
|
326
|
+
} : {
|
|
327
|
+
ok: false,
|
|
328
|
+
summary: created.summary
|
|
329
|
+
};
|
|
330
|
+
singleton = result;
|
|
331
|
+
return result;
|
|
332
|
+
}
|
|
333
|
+
export async function __resetSessionBridgeForTest() {
|
|
334
|
+
if (singleton?.ok) await singleton.bridge.close().catch(()=>{});
|
|
335
|
+
singleton = null;
|
|
336
|
+
}
|
|
337
|
+
export function __setSessionBridgeForTest(bridge) {
|
|
338
|
+
singleton = bridge ? {
|
|
339
|
+
ok: true,
|
|
340
|
+
bridge
|
|
341
|
+
} : null;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
//# sourceURL=/Users/bytedance/work/gotry-publish/ts/capabilities/session/extension-bridge.ts
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { getOrCreateSessionBridge } from './extension-bridge.js';
|
|
2
|
+
export const NEEDS_EXTENSION_HINT = '需要一次性安装 GoTry Session Bridge 浏览器扩展(约 30 秒,装完零弹窗):跑 npx gotry setup 拿指引,' + '或在 chrome://extensions 开发者模式「加载已解压的扩展程序」指向 ~/.gotry/extension';
|
|
3
|
+
export function classifyBridgeFailure(kind) {
|
|
4
|
+
return kind === 'extension-not-connected' ? 'needs-extension' : 'error';
|
|
5
|
+
}
|
|
6
|
+
function bridgeReady(b) {
|
|
7
|
+
return b.ok === true;
|
|
8
|
+
}
|
|
9
|
+
export async function extensionCookieNames(q) {
|
|
10
|
+
const bridge = await getOrCreateSessionBridge();
|
|
11
|
+
if (!bridgeReady(bridge)) return {
|
|
12
|
+
ok: false,
|
|
13
|
+
kind: 'bridge-unavailable',
|
|
14
|
+
summary: bridge.summary
|
|
15
|
+
};
|
|
16
|
+
const outcome = await bridge.bridge.submit({
|
|
17
|
+
kind: 'cookie-names',
|
|
18
|
+
site: q.site,
|
|
19
|
+
timeoutMs: q.timeoutMs ?? 8_000
|
|
20
|
+
}, {
|
|
21
|
+
timeoutMs: q.timeoutMs ?? 8_000
|
|
22
|
+
});
|
|
23
|
+
if (!outcome.ok) return {
|
|
24
|
+
ok: false,
|
|
25
|
+
kind: outcome.reason,
|
|
26
|
+
summary: outcome.summary
|
|
27
|
+
};
|
|
28
|
+
const names = Array.isArray(outcome.result.names) ? outcome.result.names.filter((n)=>typeof n === 'string') : [];
|
|
29
|
+
return {
|
|
30
|
+
ok: true,
|
|
31
|
+
tickets: names
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
export async function extensionOpenLogin(q) {
|
|
35
|
+
const bridge = await getOrCreateSessionBridge();
|
|
36
|
+
if (!bridgeReady(bridge)) return {
|
|
37
|
+
ok: false,
|
|
38
|
+
kind: 'bridge-unavailable',
|
|
39
|
+
summary: bridge.summary
|
|
40
|
+
};
|
|
41
|
+
const outcome = await bridge.bridge.submit({
|
|
42
|
+
kind: 'open-login',
|
|
43
|
+
site: q.site,
|
|
44
|
+
url: q.url
|
|
45
|
+
}, {
|
|
46
|
+
timeoutMs: q.timeoutMs ?? 15_000
|
|
47
|
+
});
|
|
48
|
+
if (!outcome.ok) return {
|
|
49
|
+
ok: false,
|
|
50
|
+
kind: outcome.reason,
|
|
51
|
+
summary: outcome.summary
|
|
52
|
+
};
|
|
53
|
+
if (!outcome.result.opened) return {
|
|
54
|
+
ok: false,
|
|
55
|
+
kind: 'job-error',
|
|
56
|
+
summary: outcome.result.error ?? '登录入口打开失败'
|
|
57
|
+
};
|
|
58
|
+
return {
|
|
59
|
+
ok: true,
|
|
60
|
+
opened: true
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
export async function extensionSearchJob(q) {
|
|
64
|
+
const bridge = await getOrCreateSessionBridge();
|
|
65
|
+
if (!bridgeReady(bridge)) return {
|
|
66
|
+
ok: false,
|
|
67
|
+
kind: 'bridge-unavailable',
|
|
68
|
+
summary: bridge.summary
|
|
69
|
+
};
|
|
70
|
+
const timeoutMs = q.timeoutMs ?? 30_000;
|
|
71
|
+
const outcome = await bridge.bridge.submit({
|
|
72
|
+
kind: 'search',
|
|
73
|
+
site: q.site,
|
|
74
|
+
url: q.url,
|
|
75
|
+
timeoutMs
|
|
76
|
+
}, {
|
|
77
|
+
timeoutMs: timeoutMs + 10_000
|
|
78
|
+
});
|
|
79
|
+
if (!outcome.ok) return {
|
|
80
|
+
ok: false,
|
|
81
|
+
kind: outcome.reason,
|
|
82
|
+
summary: outcome.summary
|
|
83
|
+
};
|
|
84
|
+
return {
|
|
85
|
+
ok: true,
|
|
86
|
+
body: typeof outcome.result.body === 'string' ? outcome.result.body : '',
|
|
87
|
+
title: typeof outcome.result.title === 'string' ? outcome.result.title : '',
|
|
88
|
+
timedOut: outcome.result.timeout === true
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
//# sourceURL=/Users/bytedance/work/gotry-publish/ts/capabilities/session/extension-channel.ts
|