@danceiny/gotry 0.0.1-rc.16 → 0.0.1-rc.17
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 +47 -13
- package/README.zh-CN.md +20 -10
- package/bin/gotry-booking-copilot.js +53 -0
- package/bin/gotry-bootstrap.js +111 -51
- package/bin/gotry-inner.js +442 -60
- 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 +30 -10
- 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
package/bin/gotry-inner.js
CHANGED
|
@@ -6,10 +6,10 @@
|
|
|
6
6
|
* gotry help # help
|
|
7
7
|
*
|
|
8
8
|
* 工作流程:
|
|
9
|
-
* 1. 解析 argv + .env(provider-neutral → DEEPSEEK_API_KEY)
|
|
9
|
+
* 1. 解析 argv + .env(provider-neutral → DEEPSEEK_API_KEY/DEEPSEEK_BASE_URL)
|
|
10
10
|
* 2. 定位 dsh runtime:
|
|
11
|
-
* a. repo checkout →
|
|
12
|
-
* b.
|
|
11
|
+
* a. repo checkout / npm 安装 → root package 解析锁定的 @deepseek-ai/dsh
|
|
12
|
+
* b. 非 benchmark 且 root 缺失时 → legacy vendored node_modules fallback(不走 npx:
|
|
13
13
|
* dsh cordis-loader 在子 cwd 求值 plugin name,必须绝对路径 patch)
|
|
14
14
|
* 3. 运行时生成 patch(os.tmpdir):把 gotry-tools 插件路径重写为按本包
|
|
15
15
|
* 位置解析的绝对路径 —— 仓内 cordis.gotry-patch.yml 里的 name 行只是
|
|
@@ -17,20 +17,30 @@
|
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
19
|
import { spawn, spawnSync } from 'node:child_process'
|
|
20
|
-
import { existsSync, readFileSync, writeFileSync } from 'node:fs'
|
|
21
|
-
import { fileURLToPath } from 'node:url'
|
|
20
|
+
import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
|
|
21
|
+
import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
22
22
|
import { dirname, join } from 'node:path'
|
|
23
23
|
import { tmpdir } from 'node:os'
|
|
24
24
|
import { createRequire } from 'node:module'
|
|
25
|
+
import {
|
|
26
|
+
benchmarkRuntimeSupported,
|
|
27
|
+
resolveDshPackage,
|
|
28
|
+
selectDshCwd,
|
|
29
|
+
selectDshRuntime,
|
|
30
|
+
supportsNodeVersion,
|
|
31
|
+
} from './gotry-runtime-resolution.js'
|
|
25
32
|
|
|
26
33
|
const here = dirname(fileURLToPath(import.meta.url))
|
|
27
34
|
const repoRoot = join(here, '..')
|
|
35
|
+
const sourceCheckoutMode = existsSync(join(repoRoot, '.git'))
|
|
28
36
|
const require_ = createRequire(import.meta.url)
|
|
37
|
+
const rootRequire = createRequire(join(repoRoot, 'package.json'))
|
|
29
38
|
|
|
30
39
|
// --- 环境 .env 加载(provider-neutral) ---
|
|
31
40
|
// npm 安装模式优先读用户当前目录的 .env(包目录内不该有凭证);repo 检出读仓根。
|
|
32
|
-
const
|
|
33
|
-
const
|
|
41
|
+
const sourceDshEarly = resolveDshPackage(rootRequire)
|
|
42
|
+
const vendoredDshEarly = !sourceDshEarly && existsSync(join(repoRoot, 'ts/dsh-runtime/node_modules/@deepseek-ai/dsh/lib/bin.js'))
|
|
43
|
+
const envCandidates = sourceCheckoutMode || vendoredDshEarly
|
|
34
44
|
? [join(repoRoot, '.env')]
|
|
35
45
|
: [join(process.cwd(), '.env'), join(repoRoot, '.env')]
|
|
36
46
|
const envLines = []
|
|
@@ -46,6 +56,22 @@ for (const line of envLines) {
|
|
|
46
56
|
if (process.env.LLM_API_KEY && !process.env.DEEPSEEK_API_KEY) {
|
|
47
57
|
process.env.DEEPSEEK_API_KEY = process.env.LLM_API_KEY
|
|
48
58
|
}
|
|
59
|
+
// base 同点映射(issue #48):dsh 侧 llm-deepseek 读 DEEPSEEK_BASE_URL 并拼
|
|
60
|
+
// `${base}/chat/completions`(与 ts/src/dsh-llm.ts 同语义,自定义端点一般含 /v1);
|
|
61
|
+
// 只映射 key 不映射 base 时,OpenAI 兼容 key 会被发往 DeepSeek 官方端点必然 401。
|
|
62
|
+
if (process.env.LLM_BASE_URL && !process.env.DEEPSEEK_BASE_URL) {
|
|
63
|
+
process.env.DEEPSEEK_BASE_URL = process.env.LLM_BASE_URL
|
|
64
|
+
}
|
|
65
|
+
// model 映射(issue #77):LLM_MODEL 显式存在时让它真正驱动 dsh 会话面——
|
|
66
|
+
// ① 这里映射为 GOTRY_LLM_MODEL 传给 gotry-tools 插件,在 agent/request 瀑布做
|
|
67
|
+
// 内存覆盖(dsh settings 用户层 ~/.dsh 压过 composition 层,单靠 patch 不够;
|
|
68
|
+
// 覆盖零持久化,进程退即散,不改写用户设置);
|
|
69
|
+
// ② 下方运行时 patch 追加两条 by-id 覆盖:agent-default-model 默认模型 +
|
|
70
|
+
// llm-deepseek 目录条目(web UI 模型页可见、元数据可记账)。
|
|
71
|
+
// 不设 LLM_MODEL 则两条都不动——默认路径(组合配置或用户 web 设置)面不变。
|
|
72
|
+
if (process.env.LLM_MODEL && !process.env.GOTRY_LLM_MODEL) {
|
|
73
|
+
process.env.GOTRY_LLM_MODEL = process.env.LLM_MODEL
|
|
74
|
+
}
|
|
49
75
|
|
|
50
76
|
// --- argv 解析 ---
|
|
51
77
|
const args = process.argv.slice(2)
|
|
@@ -61,8 +87,8 @@ Usage:
|
|
|
61
87
|
gotry help # this help
|
|
62
88
|
|
|
63
89
|
Prerequisites:
|
|
64
|
-
• Node 22+
|
|
65
|
-
• \`.env\` 里 LLM_API_KEY (DeepSeek / OpenAI
|
|
90
|
+
• Node 22.15+
|
|
91
|
+
• \`.env\` 里 LLM_API_KEY (DeepSeek / OpenAI 兼容均可;自定义端点另配 LLM_BASE_URL,一般以 /v1 结尾;指定模型另配 LLM_MODEL,同时驱动 dsh 会话面与仓内脚本)
|
|
66
92
|
|
|
67
93
|
Detail: https://github.com/Danceiny/gotry — README
|
|
68
94
|
`)
|
|
@@ -81,35 +107,55 @@ if (mode === 'setup') {
|
|
|
81
107
|
process.exit(r.status ?? (r.error ? 1 : 0))
|
|
82
108
|
}
|
|
83
109
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
if (existsSync(vendoredDsh)) {
|
|
89
|
-
dshBin = vendoredDsh
|
|
90
|
-
dshCwd = join(repoRoot, 'ts/dsh-runtime')
|
|
91
|
-
} else {
|
|
92
|
-
try {
|
|
93
|
-
dshBin = require_.resolve('@deepseek-ai/dsh/lib/bin.js')
|
|
94
|
-
dshCwd = process.cwd() // npm 安装:gotry-state 落在用户调用目录
|
|
95
|
-
} catch {
|
|
96
|
-
dshBin = ''
|
|
97
|
-
}
|
|
110
|
+
const benchmarkEnvironmentConfig = process.env.GOTRY_BENCHMARK_ENV_CONFIG
|
|
111
|
+
if (!supportsNodeVersion(process.versions.node)) {
|
|
112
|
+
console.error('[gotry] Node.js 22.15 or newer is required')
|
|
113
|
+
process.exit(1)
|
|
98
114
|
}
|
|
99
115
|
|
|
116
|
+
// --- dsh runtime 定位:root manifest/require.resolve 优先;旧 vendored 仅作 legacy fallback ---
|
|
117
|
+
const vendoredDsh = join(repoRoot, 'ts/dsh-runtime/node_modules/@deepseek-ai/dsh/lib/bin.js')
|
|
118
|
+
const installedPackageMode = !sourceCheckoutMode
|
|
119
|
+
const selectedDsh = selectDshRuntime({
|
|
120
|
+
repoRoot,
|
|
121
|
+
rootResolver: rootRequire,
|
|
122
|
+
benchmark: Boolean(benchmarkEnvironmentConfig),
|
|
123
|
+
})
|
|
124
|
+
let dshBin = selectedDsh?.bin ?? ''
|
|
125
|
+
const dshSource = selectedDsh?.source ?? ''
|
|
126
|
+
const dshCwd = selectedDsh
|
|
127
|
+
? selectDshCwd({
|
|
128
|
+
repoRoot,
|
|
129
|
+
invocationCwd: process.cwd(),
|
|
130
|
+
sourceCheckoutMode,
|
|
131
|
+
benchmark: Boolean(benchmarkEnvironmentConfig),
|
|
132
|
+
})
|
|
133
|
+
: ''
|
|
134
|
+
|
|
100
135
|
if (!dshBin) {
|
|
136
|
+
if (benchmarkEnvironmentConfig) {
|
|
137
|
+
console.error('[gotry] benchmark dsh runtime unavailable')
|
|
138
|
+
process.exit(1)
|
|
139
|
+
}
|
|
101
140
|
console.error(`[gotry] 找不到 dsh runtime(既无 vendored ${vendoredDsh},依赖里也没有 @deepseek-ai/dsh)。`)
|
|
102
|
-
console.error('repo checkout:
|
|
141
|
+
console.error('repo checkout: npm ci && npm --prefix ts ci && node scripts/build-dist.mjs;npm 安装: npm install(检查 node_modules)。')
|
|
142
|
+
process.exit(1)
|
|
143
|
+
}
|
|
144
|
+
if (benchmarkEnvironmentConfig && !benchmarkRuntimeSupported(selectedDsh)) {
|
|
145
|
+
console.error('[gotry] benchmark dsh runtime unavailable')
|
|
103
146
|
process.exit(1)
|
|
104
147
|
}
|
|
105
148
|
|
|
106
149
|
// --- 运行时 patch:插件路径按本包位置重写为绝对路径 ---
|
|
107
|
-
//
|
|
108
|
-
// repo
|
|
150
|
+
// 安装包始终指向 dist/ 纯 JS(Node 拒绝 strip node_modules 下的 .ts)。
|
|
151
|
+
// repo 检出仅在 vendored runtime 或显式 tsx loader 可用时走 .ts;否则
|
|
152
|
+
// 使用当前 worktree 已构建的 dist,避免 Node strip-only 拒绝参数属性语法。
|
|
109
153
|
const distEntry = join(repoRoot, 'dist/src/index.js')
|
|
110
154
|
const tsEntry = join(repoRoot, 'ts/src/index.ts')
|
|
111
|
-
const
|
|
112
|
-
const
|
|
155
|
+
const tsxLoaderActive = /(?:^|\s)--(?:import|loader)(?:=|\s)(?:"[^"]*tsx[^"]*"|'[^']*tsx[^']*'|\S*tsx\S*)/.test(process.env.NODE_OPTIONS ?? '')
|
|
156
|
+
const sourceTypeScriptMode = !installedPackageMode && (dshSource === 'legacy-vendored' || tsxLoaderActive)
|
|
157
|
+
const pluginEntry = !sourceTypeScriptMode && existsSync(distEntry) ? distEntry : tsEntry
|
|
158
|
+
const distModuleMode = pluginEntry === distEntry
|
|
113
159
|
const staticPatch = join(repoRoot, 'cordis.gotry-patch.yml')
|
|
114
160
|
if (!existsSync(staticPatch)) {
|
|
115
161
|
console.error(`[gotry] 找不到 patch 配置: ${staticPatch}`)
|
|
@@ -119,16 +165,214 @@ if (!existsSync(pluginEntry)) {
|
|
|
119
165
|
console.error(`[gotry] 找不到插件入口: ${pluginEntry}(包不完整?)`)
|
|
120
166
|
process.exit(1)
|
|
121
167
|
}
|
|
168
|
+
|
|
169
|
+
// Benchmark mode projects the patch before any optional host plugin is
|
|
170
|
+
// resolved/imported. Non-GoTry (including unknown/future) insert entries are
|
|
171
|
+
// safely discarded; a missing/duplicate gotry-tools entry fails closed.
|
|
172
|
+
function projectBenchmarkPatch(raw, entryPath, configPath) {
|
|
173
|
+
const lines = raw.split('\n')
|
|
174
|
+
const rootInsertKey = /(?:^|[,{}]\s*)(?:insert|'insert'|"insert")\s*:/
|
|
175
|
+
const rootInsertLines = lines
|
|
176
|
+
.map((line, index) => {
|
|
177
|
+
const rootItem = line.match(/^-\s+(.*)$/)?.[1]?.trim()
|
|
178
|
+
const directProperty = line.match(/^ {2}(.*)$/)?.[1]?.trim()
|
|
179
|
+
return rootInsertKey.test(rootItem ?? '') || /^(?:insert|'insert'|"insert")\s*:/.test(directProperty ?? '') ? index : -1
|
|
180
|
+
})
|
|
181
|
+
.filter(index => index >= 0)
|
|
182
|
+
const canonicalInsertStarts = lines
|
|
183
|
+
.map((line, index) => /^- insert:\s*(?:#.*)?$/.test(line) ? index : -1)
|
|
184
|
+
.filter(index => index >= 0)
|
|
185
|
+
if (rootInsertLines.length !== 1 || canonicalInsertStarts.length !== 1 || rootInsertLines[0] !== canonicalInsertStarts[0]) {
|
|
186
|
+
throw new Error('benchmark insert block violation')
|
|
187
|
+
}
|
|
188
|
+
const start = canonicalInsertStarts[0]
|
|
189
|
+
let end = start + 1
|
|
190
|
+
while (end < lines.length) {
|
|
191
|
+
const line = lines[end]
|
|
192
|
+
if (line.trim() !== '' && !/^\s/.test(line) && !line.trim().startsWith('#')) break
|
|
193
|
+
end += 1
|
|
194
|
+
}
|
|
195
|
+
const body = lines.slice(start + 1, end)
|
|
196
|
+
const firstItem = body.find(line => line.trim() !== '' && !line.trim().startsWith('#'))
|
|
197
|
+
const firstItemMatch = firstItem?.match(/^(\s*)-\s+/)
|
|
198
|
+
if (!firstItemMatch) throw new Error('invalid insert sequence')
|
|
199
|
+
const itemIndent = firstItemMatch[1].length
|
|
200
|
+
const itemStarts = []
|
|
201
|
+
for (let index = 0; index < body.length; index += 1) {
|
|
202
|
+
const line = body[index]
|
|
203
|
+
if (line.trim() === '' || line.trim().startsWith('#')) continue
|
|
204
|
+
const indent = line.match(/^\s*/)?.[0].length ?? 0
|
|
205
|
+
if (indent < itemIndent || (indent === itemIndent && !/^\s*-\s+/.test(line))) {
|
|
206
|
+
throw new Error('invalid insert sequence')
|
|
207
|
+
}
|
|
208
|
+
if (indent === itemIndent) itemStarts.push(index)
|
|
209
|
+
}
|
|
210
|
+
if (itemStarts.length === 0) throw new Error('empty insert sequence')
|
|
211
|
+
|
|
212
|
+
const parseId = value => {
|
|
213
|
+
const quoted = value.trim().match(/^(['"])([A-Za-z0-9._-]+)\1(?:\s+#.*)?$/)
|
|
214
|
+
if (quoted) return quoted[2]
|
|
215
|
+
return value.trim().match(/^([A-Za-z0-9._-]+)(?:\s+#.*)?$/)?.[1] ?? null
|
|
216
|
+
}
|
|
217
|
+
const itemId = item => {
|
|
218
|
+
const header = item[0].slice(itemIndent + 2).trim()
|
|
219
|
+
const canonical = header.match(/^id:\s*(.+)$/)
|
|
220
|
+
if (canonical) return parseId(canonical[1])
|
|
221
|
+
if (header.startsWith('{')) {
|
|
222
|
+
const inline = header.match(/(?:^\{\s*|,\s*)id\s*:\s*(['"]?)([A-Za-z0-9._-]+)\1(?=\s*[,}])/)
|
|
223
|
+
if (inline) return inline[2]
|
|
224
|
+
}
|
|
225
|
+
const propertyIndent = ' '.repeat(itemIndent + 2)
|
|
226
|
+
const directIds = item.slice(1)
|
|
227
|
+
.map(line => line.startsWith(`${propertyIndent}id:`) ? parseId(line.slice(propertyIndent.length + 3)) : null)
|
|
228
|
+
.filter(Boolean)
|
|
229
|
+
if (directIds.length > 1) throw new Error('duplicate insert id')
|
|
230
|
+
return directIds[0] ?? null
|
|
231
|
+
}
|
|
232
|
+
const items = itemStarts.map((itemStart, index) => body.slice(itemStart, itemStarts[index + 1] ?? body.length))
|
|
233
|
+
const gotryItems = items.filter(item => itemId(item) === 'gotry-tools')
|
|
234
|
+
if (gotryItems.length !== 1) throw new Error('benchmark insert allowlist violation')
|
|
235
|
+
let gotry = gotryItems[0].join('\n')
|
|
236
|
+
gotry = rewriteBenchmarkPluginEntry(gotry, entryPath)
|
|
237
|
+
gotry = injectBenchmarkEnvironmentConfig(gotry, configPath)
|
|
238
|
+
const projected = [...lines.slice(0, start + 1), ...gotry.split('\n'), ...lines.slice(end)]
|
|
239
|
+
return projectBenchmarkSystemPrompt(projected).join('\n')
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// The normal persona contains dynamic variables and product-only tool
|
|
243
|
+
// instructions. They are not meaningful in the minimal benchmark kernel and
|
|
244
|
+
// can make dsh fail while assembling the first request. Require the exact
|
|
245
|
+
// source shape before replacing it; malformed or ambiguous prompt config must
|
|
246
|
+
// fail closed rather than being partially interpreted.
|
|
247
|
+
function projectBenchmarkSystemPrompt(lines) {
|
|
248
|
+
const rootStarts = lines.map((line, index) => /^-\s+/.test(line) ? index : -1).filter(index => index >= 0)
|
|
249
|
+
const rootItems = rootStarts.map((itemStart, index) => lines.slice(itemStart, rootStarts[index + 1] ?? lines.length))
|
|
250
|
+
if (rootItems.length !== 2) throw new Error('benchmark system-prompt root shape violation')
|
|
251
|
+
const insertItems = rootItems.filter(item => /^- insert:\s*(?:#.*)?$/.test(item[0]))
|
|
252
|
+
const systemItems = rootItems.filter(item => /^- id:\s*system-prompt\s*(?:#.*)?$/.test(item[0]))
|
|
253
|
+
if (insertItems.length !== 1 || systemItems.length !== 1) throw new Error('benchmark system-prompt canonical shape violation')
|
|
254
|
+
const start = rootStarts[rootItems.indexOf(systemItems[0])]
|
|
255
|
+
let end = start + 1
|
|
256
|
+
while (end < lines.length && (lines[end].trim() === '' || /^\s/.test(lines[end]) || lines[end].trim().startsWith('#'))) end += 1
|
|
257
|
+
const item = lines.slice(start, end)
|
|
258
|
+
const configIndexes = item.map((line, index) => /^ {2}config:\s*$/.test(line) ? index : -1).filter(index => index >= 0)
|
|
259
|
+
const personaIndexes = item.map((line, index) => /^ {4}persona:\s*>-\s*$/.test(line) ? index : -1).filter(index => index >= 0)
|
|
260
|
+
if (configIndexes.length !== 1 || personaIndexes.length !== 1 || personaIndexes[0] <= configIndexes[0]) {
|
|
261
|
+
throw new Error('benchmark system-prompt config violation')
|
|
262
|
+
}
|
|
263
|
+
const persona = personaIndexes[0]
|
|
264
|
+
const blockEnd = item.slice(persona + 1).findIndex(line => line.trim() !== '' && !/^\s{6,}/.test(line))
|
|
265
|
+
const contentEnd = blockEnd < 0 ? item.length : persona + 1 + blockEnd
|
|
266
|
+
if (contentEnd === persona + 1 || item.slice(persona + 1, contentEnd).some(line => line.trim() !== '' && !/^\s{6,}/.test(line))) {
|
|
267
|
+
throw new Error('benchmark system-prompt persona violation')
|
|
268
|
+
}
|
|
269
|
+
const before = item.slice(1, configIndexes[0]).filter(line => line.trim() !== '' && !line.trim().startsWith('#'))
|
|
270
|
+
const between = item.slice(configIndexes[0] + 1, persona).filter(line => line.trim() !== '' && !line.trim().startsWith('#'))
|
|
271
|
+
const after = item.slice(contentEnd).filter(line => line.trim() !== '' && !line.trim().startsWith('#'))
|
|
272
|
+
if (before.length !== 0 || between.length !== 0 || after.length !== 0) throw new Error('benchmark system-prompt shape violation')
|
|
273
|
+
const replacement = [
|
|
274
|
+
'- id: system-prompt',
|
|
275
|
+
' config:',
|
|
276
|
+
' persona: >-',
|
|
277
|
+
' You are GoTry, a task-agnostic travel planning assistant.',
|
|
278
|
+
' Use only the current conversation and tools available in this benchmark session.',
|
|
279
|
+
]
|
|
280
|
+
return [...lines.slice(0, start), ...replacement, ...lines.slice(end)]
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function rewriteBenchmarkPluginEntry(raw, entryPath) {
|
|
284
|
+
const itemIndent = raw.match(/^(\s*)-\s+/)?.[1].length
|
|
285
|
+
if (itemIndent === undefined) throw new Error('benchmark plugin item unavailable')
|
|
286
|
+
const propertyIndent = ' '.repeat(itemIndent + 2)
|
|
287
|
+
const anchorPattern = new RegExp(`^(${propertyIndent}name:\\s*)'[^'\\r\\n]*ts\\/src\\/index\\.ts'(\\s*(?:#.*)?)$`, 'gm')
|
|
288
|
+
const anchors = [...raw.matchAll(anchorPattern)]
|
|
289
|
+
if (anchors.length !== 1 || anchors[0].index === undefined) throw new Error('benchmark plugin anchor unavailable')
|
|
290
|
+
const anchor = anchors[0]
|
|
291
|
+
const escapedEntry = entryPath.replace(/'/g, "''")
|
|
292
|
+
const replacement = `${anchor[1]}'${escapedEntry}'${anchor[2]}`
|
|
293
|
+
const rewritten = `${raw.slice(0, anchor.index)}${replacement}${raw.slice(anchor.index + anchor[0].length)}`
|
|
294
|
+
if (!rewritten.includes(replacement)) throw new Error('benchmark plugin rewrite unavailable')
|
|
295
|
+
return rewritten
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function injectBenchmarkEnvironmentConfig(raw, configPath) {
|
|
299
|
+
const itemIndent = raw.match(/^(\s*)-\s+/)?.[1].length
|
|
300
|
+
if (itemIndent === undefined) throw new Error('benchmark config item unavailable')
|
|
301
|
+
const propertyIndent = ' '.repeat(itemIndent + 2)
|
|
302
|
+
const configIndent = ' '.repeat(itemIndent + 4)
|
|
303
|
+
const configAnchors = raw.match(new RegExp(`^${propertyIndent}config:\\s*(?:#.*)?$`, 'gm')) ?? []
|
|
304
|
+
if (configAnchors.length !== 1) throw new Error('benchmark config block unavailable')
|
|
305
|
+
if ((raw.match(new RegExp(`^${configIndent}benchmarkEnvironmentConfigPath\\s*:`, 'gm')) ?? []).length !== 0) {
|
|
306
|
+
throw new Error('duplicate benchmark config path')
|
|
307
|
+
}
|
|
308
|
+
const anchorPattern = new RegExp(`^(${configIndent})hbcliBin:\\s*'[^'\\r\\n]*'\\s*(?:#.*)?$`, 'gm')
|
|
309
|
+
const anchors = [...raw.matchAll(anchorPattern)]
|
|
310
|
+
if (anchors.length !== 1 || anchors[0].index === undefined) throw new Error('benchmark config anchor unavailable')
|
|
311
|
+
const anchor = anchors[0]
|
|
312
|
+
const escapedPath = configPath.replace(/'/g, "''")
|
|
313
|
+
const insertAt = anchor.index + anchor[0].length
|
|
314
|
+
const injected = `${raw.slice(0, insertAt)}\n${anchor[1]}benchmarkEnvironmentConfigPath: '${escapedPath}'${raw.slice(insertAt)}`
|
|
315
|
+
if ((injected.match(new RegExp(`^${configIndent}benchmarkEnvironmentConfigPath\\s*:`, 'gm')) ?? []).length !== 1) {
|
|
316
|
+
throw new Error('benchmark config injection unavailable')
|
|
317
|
+
}
|
|
318
|
+
return injected
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
let benchmarkTerminalConfig = null
|
|
322
|
+
let benchmarkPatchProjection = null
|
|
323
|
+
let benchmarkChildDiagnostics = null
|
|
324
|
+
if (benchmarkEnvironmentConfig !== undefined && benchmarkEnvironmentConfig !== '') {
|
|
325
|
+
let validBenchmarkEnvironmentConfig = false
|
|
326
|
+
try {
|
|
327
|
+
const bridgeModule = distModuleMode && existsSync(join(repoRoot, 'dist/src/benchmark-environment-bridge.js'))
|
|
328
|
+
? join(repoRoot, 'dist/src/benchmark-environment-bridge.js')
|
|
329
|
+
: join(repoRoot, 'ts/src/benchmark-environment-bridge.ts')
|
|
330
|
+
const { loadBenchmarkEnvironmentConfig } = await import(pathToFileURL(bridgeModule).href)
|
|
331
|
+
const loadedConfig = benchmarkEnvironmentConfig.length > 0
|
|
332
|
+
&& benchmarkEnvironmentConfig.length <= 4096
|
|
333
|
+
&& !benchmarkEnvironmentConfig.includes('\0')
|
|
334
|
+
&& !benchmarkEnvironmentConfig.includes('\r')
|
|
335
|
+
&& !benchmarkEnvironmentConfig.includes('\n')
|
|
336
|
+
? loadBenchmarkEnvironmentConfig(benchmarkEnvironmentConfig)
|
|
337
|
+
: null
|
|
338
|
+
validBenchmarkEnvironmentConfig = loadedConfig !== null
|
|
339
|
+
benchmarkTerminalConfig = loadedConfig?.terminal_output ?? null
|
|
340
|
+
if (validBenchmarkEnvironmentConfig) {
|
|
341
|
+
benchmarkPatchProjection = projectBenchmarkPatch(readFileSync(staticPatch, 'utf-8'), pluginEntry, benchmarkEnvironmentConfig)
|
|
342
|
+
}
|
|
343
|
+
} catch { validBenchmarkEnvironmentConfig = false }
|
|
344
|
+
if (!validBenchmarkEnvironmentConfig) {
|
|
345
|
+
console.error('[gotry] benchmark environment configuration unavailable')
|
|
346
|
+
process.exit(1)
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
if (benchmarkEnvironmentConfig && mode !== 'headless') {
|
|
350
|
+
console.error('[gotry] benchmark environment requires headless mode')
|
|
351
|
+
process.exit(1)
|
|
352
|
+
}
|
|
353
|
+
if (benchmarkEnvironmentConfig) {
|
|
354
|
+
try {
|
|
355
|
+
const diagnosticModule = distModuleMode && existsSync(join(repoRoot, 'dist/src/benchmark-headless-child-diagnostics.js'))
|
|
356
|
+
? join(repoRoot, 'dist/src/benchmark-headless-child-diagnostics.js')
|
|
357
|
+
: join(repoRoot, 'ts/src/benchmark-headless-child-diagnostics.ts')
|
|
358
|
+
benchmarkChildDiagnostics = await import(pathToFileURL(diagnosticModule).href)
|
|
359
|
+
} catch {
|
|
360
|
+
console.error('[gotry] benchmark environment configuration unavailable')
|
|
361
|
+
process.exit(1)
|
|
362
|
+
}
|
|
363
|
+
}
|
|
122
364
|
// dsh-map-tools 宿主插件(地图/路线/POI,零 key 走 OSM/OSRM):repo 用 vendored,
|
|
123
365
|
// npm 用依赖解析;都找不到就整块剔除 patch 条目(缺地图不挡旅行规划)
|
|
124
366
|
let mapEntry = ''
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
367
|
+
if (!benchmarkEnvironmentConfig) {
|
|
368
|
+
const vendoredMap = join(repoRoot, 'ts/dsh-runtime/node_modules/dsh-map-tools/lib/index.js')
|
|
369
|
+
if (existsSync(vendoredMap)) {
|
|
370
|
+
mapEntry = vendoredMap
|
|
371
|
+
} else {
|
|
372
|
+
// npm 布局:子路径可能被 exports 挡(resolve 抛错不能留下旧值),裸包名返回真实入口
|
|
373
|
+
try { mapEntry = require_.resolve('dsh-map-tools/lib/index.js') } catch { mapEntry = '' }
|
|
374
|
+
if (!mapEntry) { try { mapEntry = require_.resolve('dsh-map-tools') } catch { mapEntry = '' } }
|
|
375
|
+
}
|
|
132
376
|
}
|
|
133
377
|
// 结构化澄清卡(T2):ask_user_question 工具 + user-questions 服务,从 dsh 包
|
|
134
378
|
// 上下文解析(pnpm 嵌套布局下只有 dsh 自己看得见这些依赖);失败整块剔除
|
|
@@ -138,7 +382,7 @@ if (existsSync(vendoredMap)) {
|
|
|
138
382
|
const stdioAsk = mode === 'headless' && (process.stdin.isTTY || process.env.GOTRY_ASK_STDIO === '1')
|
|
139
383
|
? join(here, 'gotry-stdio-ask.js') : ''
|
|
140
384
|
let askUserInsert = ''
|
|
141
|
-
if (mode === 'web' || stdioAsk) try {
|
|
385
|
+
if (!benchmarkEnvironmentConfig && (mode === 'web' || stdioAsk)) try {
|
|
142
386
|
const reqFromDsh = createRequire(dshBin)
|
|
143
387
|
// 裸包名解析(这些包的 exports 不暴露 ./lib/index.js 子路径,但裸名返回真实入口)
|
|
144
388
|
// userQuestions 服务默认树已注册(重复插入会崩),只插工具消费者
|
|
@@ -147,35 +391,74 @@ if (mode === 'web' || stdioAsk) try {
|
|
|
147
391
|
+ ` - id: dsh-tool-ask-user\n name: '${at}'`
|
|
148
392
|
} catch { /* 缺件不挡启动 */ }
|
|
149
393
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
394
|
+
const unboundPatch = benchmarkPatchProjection ?? readFileSync(staticPatch, 'utf-8')
|
|
395
|
+
let patchRaw = benchmarkPatchProjection ?? unboundPatch.replace(/(name:\s*)'[^']*ts\/src\/index\.ts'/, `$1'${pluginEntry}'`)
|
|
396
|
+
patchRaw = patchRaw.replace(/^\s*# \{ask-user-insert\}.*\n(\s*# .*\n)?/m, askUserInsert ? askUserInsert + '\n' : '')
|
|
397
|
+
|
|
398
|
+
// Owner-local benchmark environment opt-in. Keep the path out of tool input
|
|
399
|
+
// and logs; it is only injected into the local plugin config as YAML data.
|
|
153
400
|
if (mapEntry) {
|
|
154
401
|
patchRaw = patchRaw.replace(/(name:\s*)'placeholder\/dsh-map-tools'/, `$1'${mapEntry}'`)
|
|
155
402
|
} else {
|
|
156
403
|
patchRaw = patchRaw.replace(/\n\s*- id: dsh-map-tools\n\s*name: 'placeholder\/dsh-map-tools'\n/, '\n')
|
|
157
404
|
}
|
|
158
405
|
|
|
159
|
-
// dsh-calendar 宿主插件(CalDAV 工作窗口读取;未配置时工具报错降级,不挡启动)
|
|
406
|
+
// dsh-calendar 宿主插件(CalDAV 工作窗口读取;未配置时工具报错降级,不挡启动)。
|
|
407
|
+
// 离线预算 E2E 显式禁用可选宿主插件,避免测试依赖真实 CalDAV 账号。
|
|
160
408
|
let calEntry = ''
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
409
|
+
if (!benchmarkEnvironmentConfig) {
|
|
410
|
+
const vendoredCal = join(repoRoot, 'ts/dsh-runtime/node_modules/dsh-calendar/lib/index.js')
|
|
411
|
+
if (existsSync(vendoredCal)) {
|
|
412
|
+
calEntry = vendoredCal
|
|
413
|
+
} else {
|
|
414
|
+
try { calEntry = require_.resolve('dsh-calendar/lib/index.js') } catch { calEntry = '' }
|
|
415
|
+
if (!calEntry) { try { calEntry = require_.resolve('dsh-calendar') } catch { calEntry = '' } }
|
|
416
|
+
}
|
|
167
417
|
}
|
|
168
|
-
if (calEntry) {
|
|
418
|
+
if (calEntry && process.env.GOTRY_DISABLE_OPTIONAL_CALENDAR !== '1') {
|
|
169
419
|
patchRaw = patchRaw.replace(/(name:\s*)'placeholder\/dsh-calendar'/, `$1'${calEntry}'`)
|
|
170
420
|
} else {
|
|
171
421
|
patchRaw = patchRaw.replace(/\n\s*- id: dsh-calendar\n\s*name: 'placeholder\/dsh-calendar'\n/, '\n')
|
|
172
422
|
}
|
|
173
|
-
|
|
174
|
-
|
|
423
|
+
|
|
424
|
+
// LLM_MODEL 指定模型(issue #77)②:composition 层 by-id 覆盖。cordis patch 语义
|
|
425
|
+
// (dsh-app-boot applyEntryPatches):非 insert 的 {id, ...overrides} 对目标条目做浅层
|
|
426
|
+
// 键赋值,config 整体替换;id 不存在只 warn+skip 不崩(上游改名时优雅退化)。
|
|
427
|
+
// 两条目标在 headless/web profile 均有(dsh-base 声明 llm-deepseek、两 profile 均声明
|
|
428
|
+
// agent-default-model)。llm-deepseek 目录整体替换为单条目:显式指定模型的场景多为
|
|
429
|
+
// 中转/兼容端点,默认 v4-* 目录对其是误导;不硬编码上游 DEFAULT_MODELS 防漂移。
|
|
430
|
+
if (process.env.GOTRY_LLM_MODEL) {
|
|
431
|
+
const modelYaml = `'${process.env.GOTRY_LLM_MODEL.replace(/'/g, "''")}'`
|
|
432
|
+
patchRaw += `\n# LLM_MODEL 指定模型(issue #77):dsh 会话面默认模型 + 模型目录\n- id: agent-default-model\n config:\n provider: deepseek-official\n model: ${modelYaml}\n- id: llm-deepseek\n config:\n models:\n - id: ${modelYaml}\n name: ${modelYaml}\n`
|
|
433
|
+
}
|
|
434
|
+
// Keep the patch private and short-lived. mkdtempSync creates a 0700 directory;
|
|
435
|
+
// wx prevents accidental reuse/races if a process is started concurrently.
|
|
436
|
+
const patchDir = mkdtempSync(join(tmpdir(), 'gotry-cordis-'))
|
|
437
|
+
const patchPath = join(patchDir, 'patch.yml')
|
|
438
|
+
writeFileSync(patchPath, patchRaw, { mode: 0o600, flag: 'wx' })
|
|
439
|
+
let patchCleaned = false
|
|
440
|
+
const cleanupPatch = () => {
|
|
441
|
+
if (patchCleaned) return
|
|
442
|
+
patchCleaned = true
|
|
443
|
+
rmSync(patchDir, { recursive: true, force: true })
|
|
444
|
+
}
|
|
445
|
+
process.once('exit', cleanupPatch)
|
|
446
|
+
let child = null
|
|
447
|
+
const terminateOnSignal = (signal, listener) => {
|
|
448
|
+
cleanupPatch()
|
|
449
|
+
if (child && !child.killed) child.kill(signal)
|
|
450
|
+
process.off(signal, listener)
|
|
451
|
+
process.kill(process.pid, signal)
|
|
452
|
+
}
|
|
453
|
+
const onSigint = () => terminateOnSignal('SIGINT', onSigint)
|
|
454
|
+
const onSigterm = () => terminateOnSignal('SIGTERM', onSigterm)
|
|
455
|
+
process.once('SIGINT', onSigint)
|
|
456
|
+
process.once('SIGTERM', onSigterm)
|
|
175
457
|
if (!process.env.DEEPSEEK_API_KEY && mode !== 'help') {
|
|
176
458
|
console.error('[gotry] 缺少 LLM API key —— 两种方式任选其一后重跑:')
|
|
177
459
|
console.error(` 1) 在当前目录创建 .env 写入一行: LLM_API_KEY=<你的 DeepSeek key>(key 从 https://platform.deepseek.com 获取)`)
|
|
178
460
|
console.error(' 2) 或临时环境变量: export LLM_API_KEY=<key>')
|
|
461
|
+
cleanupPatch()
|
|
179
462
|
process.exit(1)
|
|
180
463
|
}
|
|
181
464
|
|
|
@@ -185,22 +468,116 @@ const binJs = mode === 'web'
|
|
|
185
468
|
? ['web', '--patch', patchPath, ...(process.argv.includes('--no-open') ? ['--no-open'] : [])]
|
|
186
469
|
: ['--profile', 'headless', '--patch', patchPath, ...rest]
|
|
187
470
|
|
|
188
|
-
const
|
|
189
|
-
|
|
190
|
-
|
|
471
|
+
const childEnv = { ...process.env }
|
|
472
|
+
// The benchmark config is parent-only metadata. Never make its path visible to
|
|
473
|
+
// dsh or any plugin loaded by the child.
|
|
474
|
+
delete childEnv.GOTRY_BENCHMARK_ENV_CONFIG
|
|
475
|
+
if (benchmarkEnvironmentConfig) {
|
|
476
|
+
for (const key of [
|
|
477
|
+
'GOTRY_BENCHMARK_BRIDGE_PARENT_SECRET',
|
|
478
|
+
'DATABASE_URL',
|
|
479
|
+
'SSH_AUTH_SOCK',
|
|
480
|
+
'AWS_PROFILE',
|
|
481
|
+
'HTTPS_PROXY',
|
|
482
|
+
]) delete childEnv[key]
|
|
483
|
+
}
|
|
484
|
+
const benchmarkStdout = benchmarkEnvironmentConfig ? [] : null
|
|
485
|
+
if (benchmarkEnvironmentConfig) childEnv.GOTRY_BENCHMARK_DIAGNOSTIC_FD = '3'
|
|
486
|
+
const childStdio = mode === 'web'
|
|
487
|
+
? 'inherit'
|
|
488
|
+
: benchmarkStdout
|
|
489
|
+
? ['pipe', 'pipe', 'pipe', 'pipe']
|
|
490
|
+
: process.stdin.isTTY
|
|
491
|
+
? 'inherit'
|
|
492
|
+
: ['pipe', 'inherit', 'inherit']
|
|
493
|
+
let benchmarkCapturedBytes = 0
|
|
494
|
+
let benchmarkDiagnosticBuffer = Buffer.alloc(0)
|
|
495
|
+
let benchmarkOutputTruncated = false
|
|
496
|
+
child = spawn(process.execPath, [dshBin, ...binJs], {
|
|
497
|
+
stdio: childStdio,
|
|
498
|
+
env: childEnv,
|
|
191
499
|
cwd: dshCwd,
|
|
192
500
|
})
|
|
501
|
+
if (benchmarkStdout) {
|
|
502
|
+
const maxBytes = Math.max(1, Number(benchmarkTerminalConfig?.max_bytes ?? 1))
|
|
503
|
+
child.stdout?.on('data', chunk => {
|
|
504
|
+
if (benchmarkCapturedBytes + Buffer.byteLength(chunk) > maxBytes) benchmarkOutputTruncated = true
|
|
505
|
+
if (benchmarkCapturedBytes < maxBytes) {
|
|
506
|
+
const bytes = Buffer.from(chunk)
|
|
507
|
+
benchmarkStdout.push(bytes.subarray(0, Math.max(0, maxBytes - benchmarkCapturedBytes)))
|
|
508
|
+
benchmarkCapturedBytes += Math.min(bytes.length, maxBytes - benchmarkCapturedBytes)
|
|
509
|
+
}
|
|
510
|
+
})
|
|
511
|
+
child.stderr?.resume()
|
|
512
|
+
child.stdio?.[3]?.on('data', chunk => {
|
|
513
|
+
benchmarkDiagnosticBuffer = benchmarkChildDiagnostics.appendBoundedChildDiagnostic(
|
|
514
|
+
benchmarkDiagnosticBuffer,
|
|
515
|
+
Buffer.from(chunk),
|
|
516
|
+
)
|
|
517
|
+
})
|
|
518
|
+
}
|
|
193
519
|
if (process.env.GOTRY_DEBUG) {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
520
|
+
if (benchmarkEnvironmentConfig) {
|
|
521
|
+
console.error('[gotry-debug] benchmark headless child configured(details redacted)')
|
|
522
|
+
} else {
|
|
523
|
+
console.error('[gotry-debug] exec:', process.execPath, dshBin)
|
|
524
|
+
console.error('[gotry-debug] argv:', binJs)
|
|
525
|
+
console.error('[gotry-debug] cwd:', dshCwd)
|
|
526
|
+
console.error('[gotry-debug] patch exists:', existsSync(patchPath), patchPath)
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
let benchmarkFailureReported = false
|
|
530
|
+
const reportBenchmarkFailure = reason => {
|
|
531
|
+
if (benchmarkFailureReported) return
|
|
532
|
+
benchmarkFailureReported = true
|
|
533
|
+
process.exitCode = 1
|
|
534
|
+
process.stderr.write(`[gotry] benchmark terminal output unavailable (${reason})\n`)
|
|
198
535
|
}
|
|
199
|
-
|
|
536
|
+
|
|
537
|
+
child.on('close', (code, signal) => {
|
|
538
|
+
cleanupPatch()
|
|
539
|
+
if (benchmarkStdout) {
|
|
540
|
+
const captured = Buffer.concat(benchmarkStdout).toString('utf8')
|
|
541
|
+
if (code !== 0 || signal || benchmarkOutputTruncated) {
|
|
542
|
+
const reason = benchmarkChildDiagnostics.classifyBenchmarkChildFailure({
|
|
543
|
+
code,
|
|
544
|
+
signal,
|
|
545
|
+
diagnostic: benchmarkDiagnosticBuffer.toString('utf8'),
|
|
546
|
+
outputTruncated: benchmarkOutputTruncated,
|
|
547
|
+
})
|
|
548
|
+
reportBenchmarkFailure(reason)
|
|
549
|
+
return
|
|
550
|
+
}
|
|
551
|
+
const parserModule = distModuleMode && existsSync(join(repoRoot, 'dist/src/benchmark-agent-conformance.js'))
|
|
552
|
+
? join(repoRoot, 'dist/src/benchmark-agent-conformance.js')
|
|
553
|
+
: join(repoRoot, 'ts/src/benchmark-agent-conformance.ts')
|
|
554
|
+
import(pathToFileURL(parserModule).href).then(async ({ parseBenchmarkTerminal }) => {
|
|
555
|
+
const parsed = parseBenchmarkTerminal(captured, benchmarkTerminalConfig)
|
|
556
|
+
if (!parsed || parsed.ok !== true) {
|
|
557
|
+
reportBenchmarkFailure('child_terminal_invalid')
|
|
558
|
+
return
|
|
559
|
+
}
|
|
560
|
+
await new Promise((resolve, reject) => {
|
|
561
|
+
const onError = error => {
|
|
562
|
+
process.stdout.off('error', onError)
|
|
563
|
+
reject(error)
|
|
564
|
+
}
|
|
565
|
+
process.stdout.once('error', onError)
|
|
566
|
+
process.stdout.write(captured, () => {
|
|
567
|
+
process.stdout.off('error', onError)
|
|
568
|
+
resolve()
|
|
569
|
+
})
|
|
570
|
+
})
|
|
571
|
+
process.exitCode = 0
|
|
572
|
+
}).catch(() => {
|
|
573
|
+
reportBenchmarkFailure('child_lifecycle_failure')
|
|
574
|
+
})
|
|
575
|
+
return
|
|
576
|
+
}
|
|
200
577
|
if (code !== 0 || signal) {
|
|
201
578
|
// D-NEW 护栏:dsh 异常退出也写一条 incident,留现场而非沉默
|
|
202
|
-
//
|
|
203
|
-
const incidentModule =
|
|
579
|
+
// dist 模式 import JS(node_modules 下的 .ts 会被 Node 拒 strip)
|
|
580
|
+
const incidentModule = distModuleMode && existsSync(join(repoRoot, 'dist/capabilities/incident-log.js'))
|
|
204
581
|
? '../dist/capabilities/incident-log.js'
|
|
205
582
|
: '../ts/capabilities/incident-log.ts'
|
|
206
583
|
import(incidentModule).then(({ recordIncident }) => {
|
|
@@ -215,7 +592,12 @@ child.on('exit', (code, signal) => {
|
|
|
215
592
|
process.exit(code ?? 1)
|
|
216
593
|
})
|
|
217
594
|
child.on('error', (e) => {
|
|
595
|
+
cleanupPatch()
|
|
596
|
+
if (benchmarkEnvironmentConfig) {
|
|
597
|
+
reportBenchmarkFailure('child_spawn_failure')
|
|
598
|
+
return
|
|
599
|
+
}
|
|
218
600
|
console.error(`[gotry] dsh 启动失败: ${e.message}`)
|
|
219
|
-
console.error('尝试: node -v 看 Node 版本(需 22+),或查看 ts/dsh-runtime/node_modules/。')
|
|
601
|
+
console.error('尝试: node -v 看 Node 版本(需 22.15+),或查看 ts/dsh-runtime/node_modules/。')
|
|
220
602
|
process.exit(1)
|
|
221
603
|
})
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export type DshResolver = {
|
|
2
|
+
resolve(specifier: string): string
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export type DshRuntime = {
|
|
6
|
+
bin: string
|
|
7
|
+
version: string
|
|
8
|
+
source: 'root' | 'legacy-vendored'
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const REQUIRED_BENCHMARK_DSH_VERSION: '0.1.2-alpha.3'
|
|
12
|
+
|
|
13
|
+
export function readDshPackage(bin: string): Omit<DshRuntime, 'source'> | null
|
|
14
|
+
export function resolveDshPackage(resolver: DshResolver): Omit<DshRuntime, 'source'> | null
|
|
15
|
+
export function supportsNodeVersion(version: string): boolean
|
|
16
|
+
export function selectDshRuntime(options: {
|
|
17
|
+
repoRoot: string
|
|
18
|
+
rootResolver: DshResolver
|
|
19
|
+
benchmark: boolean
|
|
20
|
+
}): DshRuntime | null
|
|
21
|
+
export function benchmarkRuntimeSupported(runtime: { version: string } | null): boolean
|
|
22
|
+
export function selectDshCwd(options: {
|
|
23
|
+
repoRoot: string
|
|
24
|
+
invocationCwd: string
|
|
25
|
+
sourceCheckoutMode: boolean
|
|
26
|
+
benchmark: boolean
|
|
27
|
+
}): string
|