@danceiny/gotry 0.0.1-rc.16 → 0.0.1-rc.18
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 +161 -116
- package/README.zh-CN.md +173 -144
- package/bin/gotry-booking-copilot.js +53 -0
- package/bin/gotry-bootstrap.js +113 -66
- package/bin/gotry-inner.js +436 -70
- package/bin/gotry-runtime-resolution.d.ts +27 -0
- package/bin/gotry-runtime-resolution.js +50 -0
- package/bin/gotry.js +1 -1
- package/cordis.gotry-patch.yml +5 -1
- 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 +1 -1
- package/dist/capabilities/effect.js +1 -1
- package/dist/capabilities/fact-log.js +1 -1
- package/dist/capabilities/flyai.js +20 -6
- package/dist/capabilities/hbcli.js +1 -1
- package/dist/capabilities/incident-log.js +1 -1
- package/dist/capabilities/model-override.js +18 -0
- package/dist/capabilities/opensky.js +1 -1
- package/dist/capabilities/resilience.js +1 -1
- 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 +1 -1
- package/dist/capabilities/session/extension-bridge.js +19 -6
- package/dist/capabilities/session/extension-channel.js +3 -3
- package/dist/capabilities/session/extension-distribution.js +234 -0
- package/dist/capabilities/session/extract.js +1 -1
- package/dist/capabilities/session/golden-score.js +92 -0
- package/dist/capabilities/session/health-watch.js +1 -1
- package/dist/capabilities/session/read-guard.js +1 -1
- package/dist/capabilities/session/static-flight-golden.js +137 -0
- package/dist/capabilities/session/transport.js +1 -1
- package/dist/capabilities/session/wizard.js +21 -251
- package/dist/capabilities/session-consent.js +1 -1
- package/dist/capabilities/session-login.js +11 -5
- package/dist/capabilities/session-search.js +46 -4
- package/dist/capabilities/weather.js +168 -46
- package/dist/data/session-golden-20.json +25 -0
- package/dist/data/sf-golden-manifest.json +102 -0
- package/dist/data/sf-static-routes.json +91 -0
- package/dist/scripts/action-cache-tests.js +1 -1
- package/dist/scripts/agent-planning-budget-e2e.js +227 -0
- package/dist/scripts/agent-planning-budget-tests.js +173 -0
- 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/benchmark-environment-bridge-e2e.js +981 -0
- package/dist/scripts/benchmark-environment-bridge-tests.js +2544 -0
- package/dist/scripts/booking-copilot-availability-ledger-binding-tests.js +335 -0
- package/dist/scripts/booking-copilot-availability-policy-v2-tests.js +1355 -0
- package/dist/scripts/booking-copilot-bin-proof-tests.js +89 -0
- package/dist/scripts/booking-copilot-crossrepo-fixture-server.js +113 -0
- package/dist/scripts/booking-copilot-dsh-core-proof-tests.js +356 -0
- package/dist/scripts/booking-copilot-dsh-planner-proof-tests.js +557 -0
- package/dist/scripts/booking-copilot-dsh-plugin-proof-tests.js +140 -0
- package/dist/scripts/booking-copilot-event-sequence-concurrency-proof-tests.js +202 -0
- package/dist/scripts/booking-copilot-gap-code-contract-proof-tests.js +102 -0
- package/dist/scripts/booking-copilot-operation-ledger-concurrency-proof-tests.js +374 -0
- package/dist/scripts/booking-copilot-receipt-ledger-concurrency-proof-tests.js +447 -0
- package/dist/scripts/booking-copilot-runtime-proof-tests.js +205 -0
- package/dist/scripts/booking-copilot-server-proof-tests.js +317 -0
- package/dist/scripts/booking-copilot-startup-proof-tests.js +283 -0
- package/dist/scripts/booking-copilot-v2-runtime-proof-tests.js +3935 -0
- package/dist/scripts/booking-saga-tests.js +1 -1
- package/dist/scripts/booking-surface-contract-proof-tests.js +393 -0
- package/dist/scripts/booking-surface-v2-contract-proof-tests.js +1174 -0
- package/dist/scripts/bootstrap-tests.js +45 -39
- package/dist/scripts/build-changelog.js +1 -1
- package/dist/scripts/changelog-tests.js +1 -1
- package/dist/scripts/companion-tests.js +1 -1
- package/dist/scripts/diff-test.js +1 -1
- package/dist/scripts/dsh-runtime-closure-tests.js +225 -0
- package/dist/scripts/dsh-runtime-closure.js +150 -0
- package/dist/scripts/effect-tests.js +1 -1
- package/dist/scripts/engine-run.js +1 -1
- package/dist/scripts/engine-tests.js +1 -1
- package/dist/scripts/evaluation-cadence-tests.js +347 -0
- package/dist/scripts/evaluation-contract-tests.js +574 -0
- package/dist/scripts/extension-distribution-cli.js +35 -0
- package/dist/scripts/extension-distribution-tests.js +341 -0
- package/dist/scripts/extension-tests.js +128 -23
- package/dist/scripts/fact-gate-tests.js +1 -1
- package/dist/scripts/flyai-tests.js +1 -1
- package/dist/scripts/hbcli-e2e-tests.js +1 -1
- package/dist/scripts/hbcli-tests.js +1 -1
- package/dist/scripts/health-watch-cli.js +1 -1
- 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/model-override-e2e.js +176 -0
- package/dist/scripts/nightly-evidence-tests.js +1 -1
- package/dist/scripts/nightly-evidence.js +1 -1
- package/dist/scripts/nudge-digest.js +1 -1
- package/dist/scripts/onboarding-tests.js +21 -53
- package/dist/scripts/opensky-check.js +1 -1
- package/dist/scripts/opensky-tests.js +1 -1
- package/dist/scripts/pnpm-dsh-closure-proof.js +20 -0
- package/dist/scripts/price-drift-tests.js +1 -1
- package/dist/scripts/price-drift-watch.js +1 -1
- package/dist/scripts/probe-poi-tests.js +1 -1
- package/dist/scripts/product-metrics.js +1 -1
- package/dist/scripts/publish-preverify.js +40 -4
- 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 +70 -20
- package/dist/scripts/sf-live-benchmark.js +338 -0
- package/dist/scripts/sf-live-cli-tests.js +21 -0
- package/dist/scripts/sf-soft-score-tests.js +108 -0
- package/dist/scripts/sf-summary.js +93 -0
- 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-session-gate-tests.js +29 -0
- package/dist/scripts/smoke.js +79 -33
- package/dist/scripts/state-cli-tests.js +1 -1
- package/dist/scripts/state-cli.js +1 -1
- package/dist/scripts/static-golden-tests.js +299 -0
- 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 +694 -44
- package/dist/scripts/z3-race-tests.js +1 -1
- package/dist/src/artifact-gate.js +1 -1
- package/dist/src/benchmark-agent-conformance.js +370 -0
- package/dist/src/benchmark-environment-bridge.js +384 -0
- package/dist/src/benchmark-headless-child-diagnostics.js +173 -0
- package/dist/src/benchmark-tool-isolation.js +124 -0
- package/dist/src/bookable-facts.js +1 -1
- package/dist/src/booking-saga.js +1 -1
- package/dist/src/booking-surface/availability-policy-v2.js +830 -0
- package/dist/src/booking-surface/canonical-schema.js +113 -0
- package/dist/src/booking-surface/contracts-v2.js +89 -0
- package/dist/src/booking-surface/contracts.js +46 -0
- package/dist/src/booking-surface/dsh-planner.js +453 -0
- package/dist/src/booking-surface/dsh-plugin.js +93 -0
- package/dist/src/booking-surface/error-codes.js +94 -0
- package/dist/src/booking-surface/index.js +15 -0
- package/dist/src/booking-surface/profile.js +68 -0
- package/dist/src/booking-surface/runtime-v2.js +1771 -0
- package/dist/src/booking-surface/runtime.js +351 -0
- package/dist/src/booking-surface/server-v2.js +334 -0
- package/dist/src/booking-surface/server.js +302 -0
- package/dist/src/booking-surface/startup.js +159 -0
- package/dist/src/booking-surface/validation-v2.js +319 -0
- package/dist/src/booking-surface/validation.js +809 -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 +1 -1
- package/dist/src/engine.js +1 -1
- package/dist/src/evaluation-cadence.js +234 -0
- package/dist/src/evaluation-contracts.js +906 -0
- package/dist/src/i18n.js +1 -1
- package/dist/src/index.js +50 -19
- 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 +1 -1
- package/dist/src/slot-spec.js +1 -1
- package/dist/src/state-ledger.js +2 -1
- package/dist/src/time-anchor.js +1 -1
- package/dist/src/tool-budget.js +136 -0
- 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 +31 -7
- package/package.json +286 -11
- package/schemas/booking.surface.v1.schema.json +927 -0
- package/schemas/booking.surface.v2.schema.json +61 -0
- package/ts/capabilities/flyai.ts +16 -3
- package/ts/capabilities/session/extension-bridge.ts +37 -14
- package/ts/capabilities/session/extension-channel.ts +6 -3
- package/ts/capabilities/session/extension-distribution.ts +264 -0
- package/ts/capabilities/session/golden-score.ts +139 -0
- package/ts/capabilities/session/health-watch.ts +1 -1
- package/ts/capabilities/session/static-flight-golden.ts +209 -0
- package/ts/capabilities/session/wizard.ts +34 -176
- package/ts/capabilities/session-login.ts +15 -5
- package/ts/capabilities/session-search.ts +40 -3
- package/ts/capabilities/weather.ts +141 -52
- package/ts/package.json +3 -3
- package/ts/src/benchmark-agent-conformance.ts +448 -0
- package/ts/src/benchmark-environment-bridge.ts +348 -0
- package/ts/src/benchmark-headless-child-diagnostics.ts +184 -0
- package/ts/src/benchmark-tool-isolation.ts +166 -0
- package/ts/src/booking-surface/availability-policy-v2.ts +523 -0
- package/ts/src/booking-surface/canonical-schema.js +113 -0
- package/ts/src/booking-surface/contracts-v2.ts +118 -0
- package/ts/src/booking-surface/contracts.ts +380 -0
- package/ts/src/booking-surface/dsh-planner.ts +452 -0
- package/ts/src/booking-surface/dsh-plugin.js +93 -0
- package/ts/src/booking-surface/error-codes.ts +101 -0
- package/ts/src/booking-surface/index.ts +12 -0
- package/ts/src/booking-surface/profile.ts +42 -0
- package/ts/src/booking-surface/runtime-v2.ts +1466 -0
- package/ts/src/booking-surface/runtime.ts +483 -0
- package/ts/src/booking-surface/server-v2.ts +247 -0
- package/ts/src/booking-surface/server.ts +324 -0
- package/ts/src/booking-surface/startup.ts +196 -0
- package/ts/src/booking-surface/validation-v2.ts +205 -0
- package/ts/src/booking-surface/validation.ts +453 -0
- package/ts/src/index.ts +64 -11
- package/ts/src/state-ledger.ts +1 -0
- package/ts/src/tool-budget.ts +165 -0
- package/dist/scripts/wizard-bootstrap.js +0 -32
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
2
|
+
import { dirname, join } from 'node:path'
|
|
3
|
+
|
|
4
|
+
export const REQUIRED_BENCHMARK_DSH_VERSION = '0.1.2-alpha.3'
|
|
5
|
+
|
|
6
|
+
export function readDshPackage(bin) {
|
|
7
|
+
try {
|
|
8
|
+
const packageJson = join(dirname(dirname(bin)), 'package.json')
|
|
9
|
+
const metadata = JSON.parse(readFileSync(packageJson, 'utf8'))
|
|
10
|
+
return typeof metadata.version === 'string' ? { bin, version: metadata.version } : null
|
|
11
|
+
} catch {
|
|
12
|
+
return null
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function resolveDshPackage(resolver) {
|
|
17
|
+
try {
|
|
18
|
+
return readDshPackage(resolver.resolve('@deepseek-ai/dsh/lib/bin.js'))
|
|
19
|
+
} catch {
|
|
20
|
+
return null
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function supportsNodeVersion(version) {
|
|
25
|
+
const match = /^(\d+)\.(\d+)/.exec(version)
|
|
26
|
+
if (!match) return false
|
|
27
|
+
const major = Number(match[1])
|
|
28
|
+
const minor = Number(match[2])
|
|
29
|
+
return major > 22 || (major === 22 && minor >= 15)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function selectDshRuntime({ repoRoot, rootResolver, benchmark }) {
|
|
33
|
+
const root = resolveDshPackage(rootResolver)
|
|
34
|
+
if (root) return { ...root, source: 'root' }
|
|
35
|
+
if (benchmark) return null
|
|
36
|
+
|
|
37
|
+
const vendoredBin = join(repoRoot, 'ts/dsh-runtime/node_modules/@deepseek-ai/dsh/lib/bin.js')
|
|
38
|
+
if (!existsSync(vendoredBin)) return null
|
|
39
|
+
const vendored = readDshPackage(vendoredBin)
|
|
40
|
+
return vendored ? { ...vendored, source: 'legacy-vendored' } : null
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function benchmarkRuntimeSupported(runtime) {
|
|
44
|
+
return runtime?.version === REQUIRED_BENCHMARK_DSH_VERSION
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function selectDshCwd({ repoRoot, invocationCwd, sourceCheckoutMode, benchmark }) {
|
|
48
|
+
if (sourceCheckoutMode && !benchmark) return join(repoRoot, 'ts/dsh-runtime')
|
|
49
|
+
return invocationCwd
|
|
50
|
+
}
|
package/bin/gotry.js
CHANGED
|
@@ -17,7 +17,7 @@ if [ -z "$conf" ] || [ ! -x "$conf" ]; then
|
|
|
17
17
|
fi
|
|
18
18
|
if [ -z "$conf" ] || [ ! -x "$conf" ]; then
|
|
19
19
|
cat >&2 <<'EOF'
|
|
20
|
-
gotry: node not found in PATH. Install Node 22+:
|
|
20
|
+
gotry: node not found in PATH. Install Node 22.15+:
|
|
21
21
|
macOS: brew install node@22 (or use nvm)
|
|
22
22
|
Linux: https://nodejs.org/
|
|
23
23
|
|
package/cordis.gotry-patch.yml
CHANGED
|
@@ -18,7 +18,8 @@
|
|
|
18
18
|
# 工具,结构化澄清卡);解析失败整块剔除
|
|
19
19
|
|
|
20
20
|
# 注:insert 里 dsh-map-tools 的 name 是占位符,bin/gotry-inner.js 运行时重写为
|
|
21
|
-
# 绝对路径(
|
|
21
|
+
# 绝对路径(source/package 优先依赖解析;legacy vendor 仅作兼容解析,裸包名绕
|
|
22
|
+
# exports 子路径限制);
|
|
22
23
|
# 依赖缺失时该条目整块剔除,不挡启动。地图/路线/POI 工具,零 key 走 OSRM/OSRM。
|
|
23
24
|
|
|
24
25
|
# GoTry 成品人格:行为契约=产品语义
|
|
@@ -101,3 +102,6 @@
|
|
|
101
102
|
**交付任何含可下单事实的行程产物(markdown 文件或长方案)前,必须调
|
|
102
103
|
gotry_fact_gate**;闸 blocked 时逐条修正为可回溯事实或降级措辞,
|
|
103
104
|
不得宣称「已验证方案」。
|
|
105
|
+
(21)工具循环收敛:第 16 次工具调用收到 TOOL_BUDGET_SOFT 提示后立即整理并给出最终答复;
|
|
106
|
+
每个对话轮次最多允许 18 次真实工具派发,第 19 次起收到结构化 TOOL_BUDGET_EXHAUSTED
|
|
107
|
+
拒绝结果且不得再调用工具。达到闸值后只能基于已有结果输出最终答复,不得重试或改调其他工具。
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
|
+
import { closeSync, openSync, readFileSync, unlinkSync } from 'node:fs';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
2
5
|
export function parseFlyaiItemList(stdout) {
|
|
3
6
|
let start = -1;
|
|
4
7
|
let depth = 0;
|
|
@@ -40,11 +43,17 @@ export function parseFlyaiItemList(stdout) {
|
|
|
40
43
|
throw new Error(sawIncompleteObject ? 'incomplete FlyAI JSON object' : 'no complete FlyAI itemList JSON object');
|
|
41
44
|
}
|
|
42
45
|
function sh(cmd, args, opts) {
|
|
46
|
+
const outFile = join(tmpdir(), `gotry-flyai-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}.out`);
|
|
47
|
+
const outFd = openSync(outFile, 'w');
|
|
43
48
|
const child = spawn(cmd, args, {
|
|
44
49
|
env: process.env,
|
|
45
|
-
cwd: process.cwd()
|
|
50
|
+
cwd: process.cwd(),
|
|
51
|
+
stdio: [
|
|
52
|
+
'ignore',
|
|
53
|
+
outFd,
|
|
54
|
+
'pipe'
|
|
55
|
+
]
|
|
46
56
|
});
|
|
47
|
-
let stdout = '';
|
|
48
57
|
let stderr = '';
|
|
49
58
|
let error;
|
|
50
59
|
const timer = setTimeout(()=>{
|
|
@@ -53,9 +62,6 @@ function sh(cmd, args, opts) {
|
|
|
53
62
|
} catch {}
|
|
54
63
|
}, opts.timeoutMs);
|
|
55
64
|
return new Promise((resolve)=>{
|
|
56
|
-
child.stdout?.on('data', (d)=>{
|
|
57
|
-
stdout += d.toString();
|
|
58
|
-
});
|
|
59
65
|
child.stderr?.on('data', (d)=>{
|
|
60
66
|
stderr += d.toString();
|
|
61
67
|
});
|
|
@@ -64,6 +70,14 @@ function sh(cmd, args, opts) {
|
|
|
64
70
|
});
|
|
65
71
|
child.on('close', (code)=>{
|
|
66
72
|
clearTimeout(timer);
|
|
73
|
+
closeSync(outFd);
|
|
74
|
+
let stdout = '';
|
|
75
|
+
try {
|
|
76
|
+
stdout = readFileSync(outFile, 'utf8');
|
|
77
|
+
} catch {}
|
|
78
|
+
try {
|
|
79
|
+
unlinkSync(outFile);
|
|
80
|
+
} catch {}
|
|
67
81
|
resolve({
|
|
68
82
|
code: code ?? -1,
|
|
69
83
|
stdout,
|
|
@@ -233,4 +247,4 @@ function parseHotelItems(items) {
|
|
|
233
247
|
}
|
|
234
248
|
|
|
235
249
|
|
|
236
|
-
//# sourceURL
|
|
250
|
+
//# sourceURL=ts/capabilities/flyai.ts
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export const MODEL_OVERRIDE_ENV = 'GOTRY_LLM_MODEL';
|
|
2
|
+
export function installModelOverride(ctx, env = process.env) {
|
|
3
|
+
const model = env[MODEL_OVERRIDE_ENV];
|
|
4
|
+
if (!model || typeof ctx.on !== 'function') return false;
|
|
5
|
+
ctx.on('agent/request', async (_payload, next)=>{
|
|
6
|
+
const resolved = await next();
|
|
7
|
+
const { reasoningEffort: _inherited, ...rest } = resolved;
|
|
8
|
+
return {
|
|
9
|
+
...rest,
|
|
10
|
+
provider: 'deepseek-official',
|
|
11
|
+
model
|
|
12
|
+
};
|
|
13
|
+
});
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
//# sourceURL=ts/capabilities/model-override.ts
|
|
@@ -10,6 +10,13 @@ export const BRIDGE_PORTS = [
|
|
|
10
10
|
export const BRIDGE_PROTOCOL = 'session-bridge.v1';
|
|
11
11
|
export const EXTENSION_ID = 'olpgkofjhhiiiahdkkbcninhjmegghfe';
|
|
12
12
|
export const EXTENSION_ORIGIN = `chrome-extension://${EXTENSION_ID}`;
|
|
13
|
+
export const EXTENSION_ID_STORE = 'oeajpiccmonococjcegddlooeeohlbgd';
|
|
14
|
+
export const EXTENSION_ORIGIN_STORE = `chrome-extension://${EXTENSION_ID_STORE}`;
|
|
15
|
+
export const EXTENSION_STORE_URL = `https://chromewebstore.google.com/detail/gotry-session-bridge/${EXTENSION_ID_STORE}`;
|
|
16
|
+
export const EXTENSION_ORIGINS = [
|
|
17
|
+
EXTENSION_ORIGIN,
|
|
18
|
+
EXTENSION_ORIGIN_STORE
|
|
19
|
+
];
|
|
13
20
|
export const EXTENSION_CONNECTED_WINDOW_MS = 45_000;
|
|
14
21
|
export const JOBS_LONG_POLL_MS = 20_000;
|
|
15
22
|
export const DEFAULT_EXTENSION_WAIT_MS = 6_000;
|
|
@@ -52,7 +59,7 @@ export async function createSessionBridge(opts = {}) {
|
|
|
52
59
|
const ports = opts.ports ?? [
|
|
53
60
|
...BRIDGE_PORTS
|
|
54
61
|
];
|
|
55
|
-
const
|
|
62
|
+
const extensionOrigins = new Set(opts.extensionOrigins ?? EXTENSION_ORIGINS);
|
|
56
63
|
const now = opts.now ?? Date.now;
|
|
57
64
|
const queue = [];
|
|
58
65
|
const inFlight = new Map();
|
|
@@ -75,7 +82,8 @@ export async function createSessionBridge(opts = {}) {
|
|
|
75
82
|
const urlPath = (req.url ?? '').split('?')[0];
|
|
76
83
|
const origin = req.headers.origin;
|
|
77
84
|
const isPost = req.method === 'POST';
|
|
78
|
-
|
|
85
|
+
const originTrusted = typeof origin === 'string' && extensionOrigins.has(origin);
|
|
86
|
+
if (isPost && !originTrusted) {
|
|
79
87
|
res.statusCode = 403;
|
|
80
88
|
res.setHeader('content-type', 'application/json');
|
|
81
89
|
res.end(JSON.stringify({
|
|
@@ -84,7 +92,7 @@ export async function createSessionBridge(opts = {}) {
|
|
|
84
92
|
}));
|
|
85
93
|
return;
|
|
86
94
|
}
|
|
87
|
-
if (
|
|
95
|
+
if (originTrusted) {
|
|
88
96
|
lastSeenAt = now();
|
|
89
97
|
}
|
|
90
98
|
const finish = (code, body)=>{
|
|
@@ -108,7 +116,8 @@ export async function createSessionBridge(opts = {}) {
|
|
|
108
116
|
extensionConnected: extensionConnected(),
|
|
109
117
|
lastSeenMsAgo: lastSeenAt > 0 ? now() - lastSeenAt : null,
|
|
110
118
|
queued: queue.length,
|
|
111
|
-
inFlight: inFlight.size
|
|
119
|
+
inFlight: inFlight.size,
|
|
120
|
+
parked: parked.length
|
|
112
121
|
});
|
|
113
122
|
return;
|
|
114
123
|
}
|
|
@@ -127,6 +136,7 @@ export async function createSessionBridge(opts = {}) {
|
|
|
127
136
|
job: null
|
|
128
137
|
});
|
|
129
138
|
}, JOBS_LONG_POLL_MS);
|
|
139
|
+
if (!opts.keepBridge) parkTimer.unref();
|
|
130
140
|
parked.push({
|
|
131
141
|
res,
|
|
132
142
|
timer: parkTimer
|
|
@@ -170,6 +180,9 @@ export async function createSessionBridge(opts = {}) {
|
|
|
170
180
|
error: 'unknown endpoint'
|
|
171
181
|
});
|
|
172
182
|
});
|
|
183
|
+
server.on('connection', (socket)=>{
|
|
184
|
+
if (!opts.keepBridge) socket.unref();
|
|
185
|
+
});
|
|
173
186
|
function resolveJob(jobId, parsed) {
|
|
174
187
|
const inFlightHit = inFlight.get(jobId);
|
|
175
188
|
if (inFlightHit) {
|
|
@@ -254,7 +267,7 @@ export async function createSessionBridge(opts = {}) {
|
|
|
254
267
|
settle({
|
|
255
268
|
ok: false,
|
|
256
269
|
reason: 'extension-not-connected',
|
|
257
|
-
summary:
|
|
270
|
+
summary: `GoTry Session Bridge 扩展未连接(未安装或已停用)。一次性安装(装完零弹窗):推荐 Chrome 应用商店一键装(自动更新) ${EXTENSION_STORE_URL} ;或 npx gotry setup 落位后 chrome://extensions 开发者模式「加载已解压的扩展程序」指向 ~/.gotry/extension`
|
|
258
271
|
});
|
|
259
272
|
}
|
|
260
273
|
}, 250);
|
|
@@ -342,4 +355,4 @@ export function __setSessionBridgeForTest(bridge) {
|
|
|
342
355
|
}
|
|
343
356
|
|
|
344
357
|
|
|
345
|
-
//# sourceURL
|
|
358
|
+
//# sourceURL=ts/capabilities/session/extension-bridge.ts
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { getOrCreateSessionBridge } from './extension-bridge.js';
|
|
2
|
-
export const NEEDS_EXTENSION_HINT = '需要一次性安装 GoTry Session Bridge 浏览器扩展(
|
|
1
|
+
import { getOrCreateSessionBridge, EXTENSION_STORE_URL } from './extension-bridge.js';
|
|
2
|
+
export const NEEDS_EXTENSION_HINT = '需要一次性安装 GoTry Session Bridge 浏览器扩展(装完零弹窗):' + `推荐 Chrome 应用商店一键安装(自动更新) ${EXTENSION_STORE_URL} ;` + '免审核、版本更新更快的 GitHub 通道:npx gotry setup --extension-from=github(自动下载落位 ~/.gotry/extension;' + '手动下载 github.com/Danceiny/gotry/releases 标签 ext-* 的 gotry-session-bridge.tar.gz),' + '再在 chrome://extensions 开发者模式「加载已解压的扩展程序」指向解压目录';
|
|
3
3
|
export function classifyBridgeFailure(kind) {
|
|
4
4
|
return kind === 'extension-not-connected' ? 'needs-extension' : 'error';
|
|
5
5
|
}
|
|
@@ -90,4 +90,4 @@ export async function extensionSearchJob(q) {
|
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
|
|
93
|
-
//# sourceURL
|
|
93
|
+
//# sourceURL=ts/capabilities/session/extension-channel.ts
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import { cpSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
export const DIST_ASSET_TARBALL = 'gotry-session-bridge.tar.gz';
|
|
6
|
+
export const DIST_ASSET_STORE_ZIP = 'gotry-session-bridge-store.zip';
|
|
7
|
+
export const DIST_ASSET_MANIFEST = 'extension-dist-manifest.json';
|
|
8
|
+
export const DEFAULT_RELEASE_BASE = 'https://github.com/Danceiny/gotry/releases';
|
|
9
|
+
export function compareVersions(a, b) {
|
|
10
|
+
const pa = String(a).split('.').map((s)=>Number.parseInt(s, 10) || 0);
|
|
11
|
+
const pb = String(b).split('.').map((s)=>Number.parseInt(s, 10) || 0);
|
|
12
|
+
const n = Math.max(pa.length, pb.length);
|
|
13
|
+
for(let i = 0; i < n; i += 1){
|
|
14
|
+
const d = (pa[i] ?? 0) - (pb[i] ?? 0);
|
|
15
|
+
if (d !== 0) return d;
|
|
16
|
+
}
|
|
17
|
+
return 0;
|
|
18
|
+
}
|
|
19
|
+
export function distAssetUrls(releaseBase = DEFAULT_RELEASE_BASE) {
|
|
20
|
+
const base = releaseBase.replace(/\/+$/, '');
|
|
21
|
+
return {
|
|
22
|
+
tarball: `${base}/latest/download/${DIST_ASSET_TARBALL}`,
|
|
23
|
+
storeZip: `${base}/latest/download/${DIST_ASSET_STORE_ZIP}`,
|
|
24
|
+
manifest: `${base}/latest/download/${DIST_ASSET_MANIFEST}`
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
const SHA256_RE = /^[0-9a-f]{64}$/;
|
|
28
|
+
export function parseDistManifest(raw) {
|
|
29
|
+
let obj;
|
|
30
|
+
try {
|
|
31
|
+
obj = JSON.parse(raw);
|
|
32
|
+
} catch {
|
|
33
|
+
throw new Error('dist-manifest 不是合法 JSON');
|
|
34
|
+
}
|
|
35
|
+
const version = obj.version;
|
|
36
|
+
const tarball = obj.tarball;
|
|
37
|
+
const tarballSha256 = obj.tarballSha256;
|
|
38
|
+
const zip = obj.zip;
|
|
39
|
+
const zipSha256 = obj.zipSha256;
|
|
40
|
+
if (typeof version !== 'string' || !/^\d+\.\d+\.\d+$/.test(version)) throw new Error('dist-manifest.version 缺失或非 x.y.z');
|
|
41
|
+
if (tarball !== DIST_ASSET_TARBALL) throw new Error(`dist-manifest.tarball 资产名漂移(期望 ${DIST_ASSET_TARBALL})`);
|
|
42
|
+
if (zip !== DIST_ASSET_STORE_ZIP) throw new Error(`dist-manifest.zip 资产名漂移(期望 ${DIST_ASSET_STORE_ZIP})`);
|
|
43
|
+
if (typeof tarballSha256 !== 'string' || !SHA256_RE.test(tarballSha256)) throw new Error('dist-manifest.tarballSha256 非 64-hex');
|
|
44
|
+
if (typeof zipSha256 !== 'string' || !SHA256_RE.test(zipSha256)) throw new Error('dist-manifest.zipSha256 非 64-hex');
|
|
45
|
+
return {
|
|
46
|
+
version,
|
|
47
|
+
tarball,
|
|
48
|
+
tarballSha256,
|
|
49
|
+
zip,
|
|
50
|
+
zipSha256,
|
|
51
|
+
builtFromCommit: typeof obj.builtFromCommit === 'string' ? obj.builtFromCommit : undefined
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
export function sha256Hex(bytes) {
|
|
55
|
+
return createHash('sha256').update(bytes).digest('hex');
|
|
56
|
+
}
|
|
57
|
+
export function readExtensionKey(extensionDir) {
|
|
58
|
+
const p = join(extensionDir, 'manifest.json');
|
|
59
|
+
if (!existsSync(p)) return undefined;
|
|
60
|
+
try {
|
|
61
|
+
const m = JSON.parse(readFileSync(p, 'utf8'));
|
|
62
|
+
return typeof m.key === 'string' ? m.key : undefined;
|
|
63
|
+
} catch {
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
export function readExtensionVersion(extensionDir) {
|
|
68
|
+
const p = join(extensionDir, 'manifest.json');
|
|
69
|
+
if (!existsSync(p)) return undefined;
|
|
70
|
+
try {
|
|
71
|
+
const m = JSON.parse(readFileSync(p, 'utf8'));
|
|
72
|
+
return typeof m.version === 'string' ? m.version : undefined;
|
|
73
|
+
} catch {
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function runTar(args, spawnFn, timeoutMs) {
|
|
78
|
+
return new Promise((resolve)=>{
|
|
79
|
+
const child = spawnFn('tar', args, {
|
|
80
|
+
stdio: 'ignore'
|
|
81
|
+
});
|
|
82
|
+
let done = false;
|
|
83
|
+
const timer = setTimeout(()=>{
|
|
84
|
+
if (!done) {
|
|
85
|
+
done = true;
|
|
86
|
+
try {
|
|
87
|
+
child.kill('SIGKILL');
|
|
88
|
+
} catch {}
|
|
89
|
+
resolve({
|
|
90
|
+
ok: false,
|
|
91
|
+
error: `tar 超时(>${timeoutMs}ms)`
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
}, timeoutMs);
|
|
95
|
+
child.on('error', (e)=>{
|
|
96
|
+
if (!done) {
|
|
97
|
+
done = true;
|
|
98
|
+
clearTimeout(timer);
|
|
99
|
+
resolve({
|
|
100
|
+
ok: false,
|
|
101
|
+
error: `tar 不可用:${e.message}`
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
child.on('close', (code)=>{
|
|
106
|
+
if (!done) {
|
|
107
|
+
done = true;
|
|
108
|
+
clearTimeout(timer);
|
|
109
|
+
resolve(code === 0 ? {
|
|
110
|
+
ok: true
|
|
111
|
+
} : {
|
|
112
|
+
ok: false,
|
|
113
|
+
error: `tar exit ${code}`
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
async function fetchBytes(url, fetchFn, timeoutMs) {
|
|
120
|
+
try {
|
|
121
|
+
const res = await fetchFn(url, {
|
|
122
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
123
|
+
});
|
|
124
|
+
if (!res.ok) return {
|
|
125
|
+
ok: false,
|
|
126
|
+
error: `HTTP ${res.status} ${url}`
|
|
127
|
+
};
|
|
128
|
+
const buf = new Uint8Array(await res.arrayBuffer());
|
|
129
|
+
return {
|
|
130
|
+
ok: true,
|
|
131
|
+
bytes: buf
|
|
132
|
+
};
|
|
133
|
+
} catch (e) {
|
|
134
|
+
return {
|
|
135
|
+
ok: false,
|
|
136
|
+
error: `下载失败 ${e.message}`
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
export async function installExtensionFromGithub(opts) {
|
|
141
|
+
const timeoutMs = opts.timeoutMs ?? 60_000;
|
|
142
|
+
const fetchFn = opts.fetch ?? fetch;
|
|
143
|
+
const spawnFn = opts.spawnFn ?? spawn;
|
|
144
|
+
const urls = distAssetUrls(opts.releaseBase);
|
|
145
|
+
const fail = (error)=>({
|
|
146
|
+
ok: false,
|
|
147
|
+
action: 'fallback-bundled',
|
|
148
|
+
error
|
|
149
|
+
});
|
|
150
|
+
const mRes = await fetchBytes(urls.manifest, fetchFn, timeoutMs);
|
|
151
|
+
if (!mRes.ok) return fail(mRes.error);
|
|
152
|
+
let manifest;
|
|
153
|
+
try {
|
|
154
|
+
manifest = parseDistManifest(new TextDecoder().decode(mRes.bytes));
|
|
155
|
+
} catch (e) {
|
|
156
|
+
return fail(`dist-manifest 校验失败:${e.message}`);
|
|
157
|
+
}
|
|
158
|
+
const previousVersion = readExtensionVersion(opts.destDir);
|
|
159
|
+
if (previousVersion != null && compareVersions(manifest.version, previousVersion) <= 0) {
|
|
160
|
+
return {
|
|
161
|
+
ok: true,
|
|
162
|
+
action: 'up-to-date',
|
|
163
|
+
version: manifest.version,
|
|
164
|
+
previousVersion
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
if (opts.checkOnly) {
|
|
168
|
+
return {
|
|
169
|
+
ok: true,
|
|
170
|
+
action: 'up-to-date',
|
|
171
|
+
version: manifest.version,
|
|
172
|
+
previousVersion
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
const tRes = await fetchBytes(urls.tarball, fetchFn, timeoutMs);
|
|
176
|
+
if (!tRes.ok) return fail(tRes.error);
|
|
177
|
+
const actual = sha256Hex(tRes.bytes);
|
|
178
|
+
if (actual !== manifest.tarballSha256) return fail(`SHA256 不符(期望 ${manifest.tarballSha256.slice(0, 12)}… 实得 ${actual.slice(0, 12)}…)`);
|
|
179
|
+
const incoming = `${opts.destDir}.incoming-${process.pid}-${Date.now()}`;
|
|
180
|
+
const tarPath = `${incoming}.tar.gz`;
|
|
181
|
+
try {
|
|
182
|
+
rmSync(incoming, {
|
|
183
|
+
recursive: true,
|
|
184
|
+
force: true
|
|
185
|
+
});
|
|
186
|
+
mkdirSync(incoming, {
|
|
187
|
+
recursive: true
|
|
188
|
+
});
|
|
189
|
+
writeFileSync(tarPath, tRes.bytes);
|
|
190
|
+
const ext = await runTar([
|
|
191
|
+
'-xzf',
|
|
192
|
+
tarPath,
|
|
193
|
+
'-C',
|
|
194
|
+
incoming,
|
|
195
|
+
'--strip-components',
|
|
196
|
+
'1'
|
|
197
|
+
], spawnFn, timeoutMs);
|
|
198
|
+
if (!ext.ok) return fail(ext.error);
|
|
199
|
+
const incomingKey = readExtensionKey(incoming);
|
|
200
|
+
if (incomingKey == null) return fail('解压产物缺 manifest.json/key(非法包)');
|
|
201
|
+
if (opts.pinnedSourceDir) {
|
|
202
|
+
const pinnedKey = readExtensionKey(opts.pinnedSourceDir);
|
|
203
|
+
if (pinnedKey != null && incomingKey !== pinnedKey) return fail('扩展 key 漂移(通道产物与 bundled 不同 ID——拒绝安装,防端口池/host 权限漂移)');
|
|
204
|
+
}
|
|
205
|
+
const incomingVersion = readExtensionVersion(incoming);
|
|
206
|
+
if (incomingVersion !== manifest.version) return fail(`解压产物版本(${incomingVersion})与 dist-manifest(${manifest.version})不符`);
|
|
207
|
+
rmSync(opts.destDir, {
|
|
208
|
+
recursive: true,
|
|
209
|
+
force: true
|
|
210
|
+
});
|
|
211
|
+
cpSync(incoming, opts.destDir, {
|
|
212
|
+
recursive: true
|
|
213
|
+
});
|
|
214
|
+
return {
|
|
215
|
+
ok: true,
|
|
216
|
+
action: 'installed',
|
|
217
|
+
version: manifest.version,
|
|
218
|
+
previousVersion
|
|
219
|
+
};
|
|
220
|
+
} catch (e) {
|
|
221
|
+
return fail(`落位失败 ${e.message}`);
|
|
222
|
+
} finally{
|
|
223
|
+
rmSync(incoming, {
|
|
224
|
+
recursive: true,
|
|
225
|
+
force: true
|
|
226
|
+
});
|
|
227
|
+
rmSync(tarPath, {
|
|
228
|
+
force: true
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
//# sourceURL=ts/capabilities/session/extension-distribution.ts
|