@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
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
import { accessSync, constants, lstatSync, readFileSync, statSync } from 'node:fs'
|
|
2
|
+
import { getuid } from 'node:process'
|
|
3
|
+
import { isAbsolute } from 'node:path'
|
|
4
|
+
import { defineTool } from '@deepseek-ai/dsh-tools'
|
|
5
|
+
import {
|
|
6
|
+
validateTerminalOutputConfig,
|
|
7
|
+
type BenchmarkBridgeProjection,
|
|
8
|
+
type TerminalOutputConfig,
|
|
9
|
+
} from './benchmark-agent-conformance.ts'
|
|
10
|
+
|
|
11
|
+
const SCHEMA_VERSION = 'gotry_benchmark_environment_bridge_v2'
|
|
12
|
+
const IDENTIFIER = /^[A-Za-z][A-Za-z0-9_.-]*$/
|
|
13
|
+
const CONFIG_KEYS = ['allowed_tools', 'argv_prefix', 'cwd', 'enabled', 'executable', 'isolation', 'max_output_bytes', 'schema_version', 'terminal_output', 'timeout_ms']
|
|
14
|
+
const OPTIONAL_CONFIG_KEYS = ['allowed_output_keys']
|
|
15
|
+
|
|
16
|
+
export interface BenchmarkEnvironmentBridgeConfig {
|
|
17
|
+
schema_version: typeof SCHEMA_VERSION
|
|
18
|
+
enabled: true
|
|
19
|
+
executable: string
|
|
20
|
+
cwd: string
|
|
21
|
+
argv_prefix: string[]
|
|
22
|
+
allowed_tools: string[]
|
|
23
|
+
allowed_output_keys?: Record<string, string[]>
|
|
24
|
+
timeout_ms: number
|
|
25
|
+
max_output_bytes: number
|
|
26
|
+
terminal_output: TerminalOutputConfig
|
|
27
|
+
isolation: {
|
|
28
|
+
mode: 'host-enforced'
|
|
29
|
+
writes: 'forbidden'
|
|
30
|
+
network: 'denied'
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function plainObject(value: unknown): value is Record<string, unknown> {
|
|
35
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function existingExecutable(value: unknown): value is string {
|
|
39
|
+
if (typeof value !== 'string' || !isAbsolute(value)) return false
|
|
40
|
+
try {
|
|
41
|
+
const stat = statSync(value)
|
|
42
|
+
accessSync(value, constants.X_OK)
|
|
43
|
+
return stat.isFile()
|
|
44
|
+
} catch {
|
|
45
|
+
return false
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function existingDirectory(value: unknown): value is string {
|
|
50
|
+
if (typeof value !== 'string' || !isAbsolute(value)) return false
|
|
51
|
+
try {
|
|
52
|
+
return statSync(value).isDirectory()
|
|
53
|
+
} catch {
|
|
54
|
+
return false
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function identifiers(value: unknown): value is string[] {
|
|
59
|
+
return Array.isArray(value) && value.length > 0 && value.length <= 64 && new Set(value).size === value.length && value.every(
|
|
60
|
+
item => typeof item === 'string' && item.length > 0 && IDENTIFIER.test(item),
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function allowedOutputKeys(value: unknown, allowedTools: string[]): value is Record<string, string[]> {
|
|
65
|
+
if (!plainObject(value)) return false
|
|
66
|
+
const keys = Object.keys(value)
|
|
67
|
+
return keys.length > 0 && keys.length <= 64
|
|
68
|
+
&& keys.every(tool => allowedTools.includes(tool) && identifiers(value[tool]))
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// These are passed as direct argv entries (never through a shell), so options
|
|
72
|
+
// such as "-m" and dotted module names are valid. Keep the prefix bounded and
|
|
73
|
+
// reject line/control separators that could make diagnostics ambiguous.
|
|
74
|
+
function argvPrefix(value: unknown): value is string[] {
|
|
75
|
+
return Array.isArray(value) && value.length > 0 && value.length <= 32 && value.every(
|
|
76
|
+
item => typeof item === 'string'
|
|
77
|
+
&& item.length > 0
|
|
78
|
+
&& item.length <= 256
|
|
79
|
+
&& !item.includes('\0')
|
|
80
|
+
&& !item.includes('\r')
|
|
81
|
+
&& !item.includes('\n'),
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function exactIsolation(value: unknown): value is BenchmarkEnvironmentBridgeConfig['isolation'] {
|
|
86
|
+
if (!plainObject(value)) return false
|
|
87
|
+
const keys = Object.keys(value).sort()
|
|
88
|
+
return JSON.stringify(keys) === JSON.stringify(['mode', 'network', 'writes'])
|
|
89
|
+
&& value.mode === 'host-enforced'
|
|
90
|
+
&& value.writes === 'forbidden'
|
|
91
|
+
&& value.network === 'denied'
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function validBound(value: unknown, maximum: number): value is number {
|
|
95
|
+
return typeof value === 'number' && Number.isInteger(value) && value >= 1 && value <= maximum
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function validate(value: unknown): value is BenchmarkEnvironmentBridgeConfig {
|
|
99
|
+
const configKeys = plainObject(value) ? Object.keys(value).sort() : []
|
|
100
|
+
const baseKeys = [...CONFIG_KEYS].sort()
|
|
101
|
+
const extendedKeys = [...CONFIG_KEYS, ...OPTIONAL_CONFIG_KEYS].sort()
|
|
102
|
+
if (!plainObject(value)
|
|
103
|
+
|| ![baseKeys, extendedKeys].some(keys => JSON.stringify(configKeys) === JSON.stringify(keys))
|
|
104
|
+
|| value.schema_version !== SCHEMA_VERSION
|
|
105
|
+
|| value.enabled !== true
|
|
106
|
+
|| !existingExecutable(value.executable)
|
|
107
|
+
|| !existingDirectory(value.cwd)
|
|
108
|
+
|| !argvPrefix(value.argv_prefix)
|
|
109
|
+
|| !identifiers(value.allowed_tools)
|
|
110
|
+
|| !validBound(value.timeout_ms, 120_000)
|
|
111
|
+
|| !validBound(value.max_output_bytes, 10 * 1024 * 1024)
|
|
112
|
+
|| !validateTerminalOutputConfig(value.terminal_output)
|
|
113
|
+
|| !exactIsolation(value.isolation)
|
|
114
|
+
|| (Object.hasOwn(value, 'allowed_output_keys') && !allowedOutputKeys(value.allowed_output_keys, value.allowed_tools))) return false
|
|
115
|
+
return true
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** Read and validate an owner-local bridge config without exposing its contents. */
|
|
119
|
+
export function loadBenchmarkEnvironmentConfig(path: string): BenchmarkEnvironmentBridgeConfig | null {
|
|
120
|
+
try {
|
|
121
|
+
if (!isAbsolute(path)) return null
|
|
122
|
+
const metadata = lstatSync(path)
|
|
123
|
+
if (!metadata.isFile() || metadata.isSymbolicLink() || metadata.size > 64 * 1024) return null
|
|
124
|
+
if (typeof getuid !== 'function' || metadata.uid !== getuid()) return null
|
|
125
|
+
if ((metadata.mode & 0o022) !== 0) return null
|
|
126
|
+
const parsed: unknown = JSON.parse(readFileSync(path, 'utf8'))
|
|
127
|
+
return validate(parsed) ? parsed : null
|
|
128
|
+
} catch {
|
|
129
|
+
return null
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
type Register = (tool: ReturnType<typeof defineTool>) => void
|
|
134
|
+
interface CollectedText {
|
|
135
|
+
readFrom(offset: number): { text: string; nextOffset: number; lossy?: boolean }
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
interface SubprocessHandle {
|
|
139
|
+
/** DSH reports -1 when process creation itself failed. */
|
|
140
|
+
readonly pid: number
|
|
141
|
+
collected: { stdout: CollectedText; stderr: CollectedText }
|
|
142
|
+
done: Promise<{ exitCode: number | null; signal?: string | null }>
|
|
143
|
+
terminate?: () => void
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export interface BenchmarkSubprocessService {
|
|
147
|
+
spawn(spec: {
|
|
148
|
+
argv: readonly string[]
|
|
149
|
+
cwd: string
|
|
150
|
+
stdio: {
|
|
151
|
+
stdin: 'ignore'
|
|
152
|
+
stdout: { maxBytes: number }
|
|
153
|
+
stderr: { maxBytes: number }
|
|
154
|
+
}
|
|
155
|
+
graceMs: number
|
|
156
|
+
env?: NodeJS.ProcessEnv
|
|
157
|
+
signal?: AbortSignal
|
|
158
|
+
}): SubprocessHandle
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function structuredObject(text: string): Record<string, unknown> | null {
|
|
162
|
+
try {
|
|
163
|
+
const parsed: unknown = JSON.parse(text.trim())
|
|
164
|
+
return plainObject(parsed) ? parsed : null
|
|
165
|
+
} catch {
|
|
166
|
+
return null
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function jsonObject(value: unknown): Record<string, never> {
|
|
171
|
+
return JSON.parse(JSON.stringify(value)) as Record<string, never>
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const FORBIDDEN_OUTPUT_KEY_FRAGMENTS = [
|
|
175
|
+
'answer', 'expected', 'gold', 'groundtruth', 'hiddenquery', 'label',
|
|
176
|
+
'loadermetadata', 'oracle', 'reference', 'reward', 'score',
|
|
177
|
+
]
|
|
178
|
+
|
|
179
|
+
function inspectOutput(value: unknown): 'ok' | 'forbidden_key' | 'structure_limit' {
|
|
180
|
+
const pending: Array<{ value: unknown; depth: number }> = [{ value, depth: 0 }]
|
|
181
|
+
let nodes = 0
|
|
182
|
+
while (pending.length > 0) {
|
|
183
|
+
const current = pending.pop()!
|
|
184
|
+
if (++nodes > 10_000 || current.depth > 24) return 'structure_limit'
|
|
185
|
+
if (Array.isArray(current.value)) {
|
|
186
|
+
for (const child of current.value) pending.push({ value: child, depth: current.depth + 1 })
|
|
187
|
+
continue
|
|
188
|
+
}
|
|
189
|
+
if (!plainObject(current.value)) continue
|
|
190
|
+
for (const [key, child] of Object.entries(current.value)) {
|
|
191
|
+
if (!/^[\x20-\x7E]+$/.test(key)) return 'forbidden_key'
|
|
192
|
+
const normalized = key.toLowerCase().replace(/[^a-z0-9]/g, '')
|
|
193
|
+
if (FORBIDDEN_OUTPUT_KEY_FRAGMENTS.some(fragment => normalized.includes(fragment))) return 'forbidden_key'
|
|
194
|
+
pending.push({ value: child, depth: current.depth + 1 })
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return 'ok'
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function inspectAllowedOutput(value: unknown, allowedKeys: string[]): boolean {
|
|
201
|
+
const allowed = new Set(allowedKeys)
|
|
202
|
+
const pending: Array<{ value: unknown; depth: number }> = [{ value, depth: 0 }]
|
|
203
|
+
let nodes = 0
|
|
204
|
+
while (pending.length > 0) {
|
|
205
|
+
const current = pending.pop()!
|
|
206
|
+
if (++nodes > 10_000 || current.depth > 24) return false
|
|
207
|
+
if (Array.isArray(current.value)) {
|
|
208
|
+
for (const child of current.value) pending.push({ value: child, depth: current.depth + 1 })
|
|
209
|
+
continue
|
|
210
|
+
}
|
|
211
|
+
if (!plainObject(current.value)) continue
|
|
212
|
+
for (const [key, child] of Object.entries(current.value)) {
|
|
213
|
+
if (!allowed.has(key)) return false
|
|
214
|
+
pending.push({ value: child, depth: current.depth + 1 })
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return true
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function serializedArguments(value: Record<string, unknown>): { json: string; reason?: never } | { json?: never; reason: string } {
|
|
221
|
+
const seen = new Set<object>()
|
|
222
|
+
let nodes = 0
|
|
223
|
+
let keys = 0
|
|
224
|
+
const inspect = (current: unknown, depth: number): boolean => {
|
|
225
|
+
if (++nodes > 10_000 || depth > 12) return false
|
|
226
|
+
if (current && typeof current === 'object') {
|
|
227
|
+
if (seen.has(current)) return false
|
|
228
|
+
seen.add(current)
|
|
229
|
+
if (Array.isArray(current)) return current.every(item => inspect(item, depth + 1))
|
|
230
|
+
return Object.entries(current).every(([key, child]) => {
|
|
231
|
+
if (++keys > 10_000) return false
|
|
232
|
+
return inspect(key, depth + 1) && inspect(child, depth + 1)
|
|
233
|
+
})
|
|
234
|
+
}
|
|
235
|
+
return true
|
|
236
|
+
}
|
|
237
|
+
if (!inspect(value, 0)) return { reason: 'serialization_limit' }
|
|
238
|
+
try {
|
|
239
|
+
const json = JSON.stringify(value)
|
|
240
|
+
if (typeof json !== 'string' || Buffer.byteLength(json, 'utf8') > 65_536) return { reason: 'serialization_limit' }
|
|
241
|
+
return { json }
|
|
242
|
+
} catch {
|
|
243
|
+
return { reason: 'serialization_limit' }
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/** Register the opt-in model-facing bridge. */
|
|
248
|
+
export function registerBenchmarkEnvironmentBridge(
|
|
249
|
+
path: string,
|
|
250
|
+
register: Register,
|
|
251
|
+
subprocess?: BenchmarkSubprocessService,
|
|
252
|
+
): BenchmarkBridgeProjection {
|
|
253
|
+
const bridge = loadBenchmarkEnvironmentConfig(path)
|
|
254
|
+
if (!bridge) throw new Error('benchmark environment bridge configuration unavailable')
|
|
255
|
+
if (!subprocess) throw new Error('benchmark environment bridge subprocess unavailable')
|
|
256
|
+
|
|
257
|
+
register(defineTool({
|
|
258
|
+
name: 'gotry_benchmark_environment',
|
|
259
|
+
description: 'When a prompt asks to run agent_env.cli, use action=call here with the mapped tool; arbitrary shell is not exposed.',
|
|
260
|
+
parameters: {
|
|
261
|
+
query: {
|
|
262
|
+
type: 'json',
|
|
263
|
+
required: true,
|
|
264
|
+
description: '{ action: "tools" | "call", tool?: string, arguments?: object }',
|
|
265
|
+
},
|
|
266
|
+
},
|
|
267
|
+
output: {
|
|
268
|
+
schema: { type: 'json' },
|
|
269
|
+
render: (_args, value) => [{ type: 'text', text: JSON.stringify(value) }],
|
|
270
|
+
},
|
|
271
|
+
async execute(args: { query?: unknown }): Promise<Record<string, never>> {
|
|
272
|
+
const query = plainObject(args?.query) ? args.query : {}
|
|
273
|
+
if (query.action === 'tools') return jsonObject({ ok: true, tools: bridge.allowed_tools })
|
|
274
|
+
if (query.action !== 'call') return jsonObject({ ok: false, error: 'invalid_action' })
|
|
275
|
+
if (typeof query.tool !== 'string' || !bridge.allowed_tools.includes(query.tool)) {
|
|
276
|
+
return jsonObject({ ok: false, error: 'disallowed_tool' })
|
|
277
|
+
}
|
|
278
|
+
if (query.arguments !== undefined && !plainObject(query.arguments)) {
|
|
279
|
+
return jsonObject({ ok: false, error: 'invalid_arguments' })
|
|
280
|
+
}
|
|
281
|
+
const callArguments = query.arguments === undefined ? {} : query.arguments
|
|
282
|
+
const serialized = serializedArguments(callArguments)
|
|
283
|
+
if (serialized.reason) return jsonObject({ ok: false, error: 'invalid_arguments', reason: serialized.reason })
|
|
284
|
+
const controller = new AbortController()
|
|
285
|
+
const timer = setTimeout(() => {
|
|
286
|
+
controller.abort()
|
|
287
|
+
}, bridge.timeout_ms)
|
|
288
|
+
let handle: SubprocessHandle | undefined
|
|
289
|
+
try {
|
|
290
|
+
const env: NodeJS.ProcessEnv = Object.fromEntries(
|
|
291
|
+
Object.keys(process.env).map(key => [key, undefined]),
|
|
292
|
+
)
|
|
293
|
+
for (const key of ['PATH', 'LANG', 'LC_ALL', 'LC_CTYPE', 'TZ']) {
|
|
294
|
+
if (process.env[key] !== undefined) env[key] = process.env[key]!
|
|
295
|
+
}
|
|
296
|
+
env.PYTHONDONTWRITEBYTECODE = '1'
|
|
297
|
+
env.PYTHONNOUSERSITE = '1'
|
|
298
|
+
handle = subprocess.spawn({
|
|
299
|
+
argv: [bridge.executable, ...bridge.argv_prefix, 'call', query.tool, serialized.json!],
|
|
300
|
+
cwd: bridge.cwd,
|
|
301
|
+
stdio: {
|
|
302
|
+
stdin: 'ignore',
|
|
303
|
+
stdout: { maxBytes: bridge.max_output_bytes },
|
|
304
|
+
stderr: { maxBytes: bridge.max_output_bytes },
|
|
305
|
+
},
|
|
306
|
+
graceMs: Math.min(1_000, bridge.timeout_ms),
|
|
307
|
+
env,
|
|
308
|
+
signal: controller.signal,
|
|
309
|
+
})
|
|
310
|
+
const status = await handle.done
|
|
311
|
+
if (controller.signal.aborted) return jsonObject({ ok: false, error: 'timed_out' })
|
|
312
|
+
const stdout = handle.collected.stdout.readFrom(0)
|
|
313
|
+
const stderr = handle.collected.stderr.readFrom(0)
|
|
314
|
+
if (stdout.lossy || stderr.lossy) return jsonObject({ ok: false, error: 'output_truncated' })
|
|
315
|
+
if (status.exitCode !== 0) {
|
|
316
|
+
return jsonObject({
|
|
317
|
+
ok: false,
|
|
318
|
+
error: 'runner_failed',
|
|
319
|
+
exit_code: status.exitCode,
|
|
320
|
+
signal: status.signal ?? null,
|
|
321
|
+
})
|
|
322
|
+
}
|
|
323
|
+
const parsed = structuredObject(stdout.text)
|
|
324
|
+
if (!parsed) return jsonObject({ ok: false, error: 'invalid_json' })
|
|
325
|
+
const outputInspection = inspectOutput(parsed)
|
|
326
|
+
if (outputInspection === 'forbidden_key') return jsonObject({ ok: false, error: 'forbidden_output' })
|
|
327
|
+
if (outputInspection === 'structure_limit') return jsonObject({ ok: false, error: 'invalid_output' })
|
|
328
|
+
const visibleResult = parsed.result ?? parsed
|
|
329
|
+
if (!plainObject(visibleResult) && !Array.isArray(visibleResult)) return jsonObject({ ok: false, error: 'invalid_output' })
|
|
330
|
+
const outputKeys = bridge.allowed_output_keys && Object.hasOwn(bridge.allowed_output_keys, query.tool)
|
|
331
|
+
? bridge.allowed_output_keys[query.tool]
|
|
332
|
+
: undefined
|
|
333
|
+
if (outputKeys && !inspectAllowedOutput(visibleResult, outputKeys)) return jsonObject({ ok: false, error: 'forbidden_output' })
|
|
334
|
+
return jsonObject({ ok: true, result: visibleResult })
|
|
335
|
+
} catch {
|
|
336
|
+
if (controller.signal.aborted) return jsonObject({ ok: false, error: 'timed_out' })
|
|
337
|
+
return jsonObject({ ok: false, error: !handle || handle.pid === -1 ? 'spawn_failed' : 'runner_failed' })
|
|
338
|
+
} finally {
|
|
339
|
+
clearTimeout(timer)
|
|
340
|
+
}
|
|
341
|
+
},
|
|
342
|
+
}))
|
|
343
|
+
return Object.freeze({
|
|
344
|
+
toolName: 'gotry_benchmark_environment',
|
|
345
|
+
allowedTools: Object.freeze([...bridge.allowed_tools]),
|
|
346
|
+
terminal: Object.freeze({ ...bridge.terminal_output }),
|
|
347
|
+
})
|
|
348
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/** Stable, redacted diagnostics carried on the benchmark-only control pipe. */
|
|
2
|
+
|
|
3
|
+
import { writeSync } from 'node:fs'
|
|
4
|
+
|
|
5
|
+
export const BENCHMARK_CHILD_DIAGNOSTIC_SCHEMA = 'gotry_benchmark_child_diagnostic_v1' as const
|
|
6
|
+
export const BENCHMARK_CHILD_DIAGNOSTIC_FD_ENV = 'GOTRY_BENCHMARK_DIAGNOSTIC_FD' as const
|
|
7
|
+
export const BENCHMARK_CHILD_DIAGNOSTIC_MAX_BYTES = 4 * 1024
|
|
8
|
+
|
|
9
|
+
export type BenchmarkChildFailureCode =
|
|
10
|
+
| 'child_output_truncated'
|
|
11
|
+
| 'child_signaled'
|
|
12
|
+
| 'child_lifecycle_failure'
|
|
13
|
+
| 'child_conformance_failure'
|
|
14
|
+
| 'child_bridge_timed_out'
|
|
15
|
+
| 'child_bridge_runner_failed'
|
|
16
|
+
| 'child_bridge_spawn_failed'
|
|
17
|
+
| 'child_bridge_output_truncated'
|
|
18
|
+
| 'child_bridge_failure'
|
|
19
|
+
| 'child_terminal_invalid'
|
|
20
|
+
| 'child_nonzero_exit'
|
|
21
|
+
| 'child_spawn_failure'
|
|
22
|
+
| 'child_model_auth'
|
|
23
|
+
| 'child_model_capacity'
|
|
24
|
+
| 'child_model_server'
|
|
25
|
+
| 'child_model_transport'
|
|
26
|
+
| 'child_model_stream'
|
|
27
|
+
| 'child_model_request'
|
|
28
|
+
| 'child_runtime_error'
|
|
29
|
+
| 'child_blocked'
|
|
30
|
+
| 'child_max_tokens'
|
|
31
|
+
| 'child_aborted'
|
|
32
|
+
| 'child_interrupted'
|
|
33
|
+
|
|
34
|
+
const FAILURE_CODES = new Set<BenchmarkChildFailureCode>([
|
|
35
|
+
'child_output_truncated',
|
|
36
|
+
'child_signaled',
|
|
37
|
+
'child_lifecycle_failure',
|
|
38
|
+
'child_conformance_failure',
|
|
39
|
+
'child_bridge_timed_out',
|
|
40
|
+
'child_bridge_runner_failed',
|
|
41
|
+
'child_bridge_spawn_failed',
|
|
42
|
+
'child_bridge_output_truncated',
|
|
43
|
+
'child_bridge_failure',
|
|
44
|
+
'child_terminal_invalid',
|
|
45
|
+
'child_nonzero_exit',
|
|
46
|
+
'child_spawn_failure',
|
|
47
|
+
'child_model_auth',
|
|
48
|
+
'child_model_capacity',
|
|
49
|
+
'child_model_server',
|
|
50
|
+
'child_model_transport',
|
|
51
|
+
'child_model_stream',
|
|
52
|
+
'child_model_request',
|
|
53
|
+
'child_runtime_error',
|
|
54
|
+
'child_blocked',
|
|
55
|
+
'child_max_tokens',
|
|
56
|
+
'child_aborted',
|
|
57
|
+
'child_interrupted',
|
|
58
|
+
])
|
|
59
|
+
|
|
60
|
+
export interface BenchmarkChildFailureInput {
|
|
61
|
+
readonly code?: number | null
|
|
62
|
+
readonly signal?: string | null
|
|
63
|
+
readonly diagnostic?: string
|
|
64
|
+
readonly outputTruncated?: boolean
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
type DiagnosticCode = BenchmarkChildFailureCode | undefined
|
|
68
|
+
|
|
69
|
+
const MODEL_AUTH = new Set(['AUTH', 'INVALID_CREDENTIAL', 'MISSING_CREDENTIAL'])
|
|
70
|
+
const MODEL_CAPACITY = new Set(['QUOTA', 'RATE_LIMIT'])
|
|
71
|
+
const MODEL_SERVER = new Set(['SERVER'])
|
|
72
|
+
const MODEL_TRANSPORT = new Set(['TRANSPORT', 'TIMEOUT'])
|
|
73
|
+
const MODEL_STREAM = new Set(['EMPTY_RESPONSE', 'STREAM_CLOSED', 'MALFORMED_RESPONSE', 'INVALID_RESPONSE'])
|
|
74
|
+
const MODEL_REQUEST = new Set(['INVALID_REQUEST', 'CONTEXT_WINDOW_EXCEEDED', 'NO_ADAPTER', 'UNKNOWN_MODEL', 'UNSUPPORTED_OPTION'])
|
|
75
|
+
|
|
76
|
+
/** Classify only the structured final turn/end envelope; free-form fields are ignored. */
|
|
77
|
+
export function classifyBenchmarkTurnEnd(reason: unknown): DiagnosticCode {
|
|
78
|
+
if (!plainObject(reason) || typeof reason.kind !== 'string') return 'child_runtime_error'
|
|
79
|
+
switch (reason.kind) {
|
|
80
|
+
case 'completed': return undefined
|
|
81
|
+
case 'blocked': return 'child_blocked'
|
|
82
|
+
case 'max-tokens': return 'child_max_tokens'
|
|
83
|
+
case 'aborted': return 'child_aborted'
|
|
84
|
+
case 'interrupted': return 'child_interrupted'
|
|
85
|
+
case 'error': {
|
|
86
|
+
const error = plainObject(reason.error) ? reason.error : {}
|
|
87
|
+
const code = typeof error.code === 'string' ? error.code : ''
|
|
88
|
+
const status = typeof error.status === 'number' && Number.isInteger(error.status) && error.status >= 100 && error.status <= 599
|
|
89
|
+
? error.status
|
|
90
|
+
: undefined
|
|
91
|
+
if (MODEL_AUTH.has(code)) return 'child_model_auth'
|
|
92
|
+
if (MODEL_CAPACITY.has(code)) return 'child_model_capacity'
|
|
93
|
+
if (MODEL_SERVER.has(code)) return 'child_model_server'
|
|
94
|
+
if (MODEL_TRANSPORT.has(code)) return 'child_model_transport'
|
|
95
|
+
if (MODEL_STREAM.has(code)) return 'child_model_stream'
|
|
96
|
+
if (MODEL_REQUEST.has(code)) return 'child_model_request'
|
|
97
|
+
if (code === 'ABORTED') return 'child_aborted'
|
|
98
|
+
if (status === 401 || status === 403) return 'child_model_auth'
|
|
99
|
+
if (status === 429) return 'child_model_capacity'
|
|
100
|
+
if (status !== undefined && status >= 500 && status <= 599) return 'child_model_server'
|
|
101
|
+
return 'child_runtime_error'
|
|
102
|
+
}
|
|
103
|
+
default: return 'child_runtime_error'
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** Per-session final arbiter. It emits one best closed enum, never its payload. */
|
|
108
|
+
export function createBenchmarkDiagnosticArbiter(write: (code: BenchmarkChildFailureCode) => void) {
|
|
109
|
+
const pending = new Map<string, BenchmarkChildFailureCode>()
|
|
110
|
+
const emitted = new Set<string>()
|
|
111
|
+
const rank = (code: BenchmarkChildFailureCode): number => code.startsWith('child_bridge_') ? 4 : code === 'child_conformance_failure' ? 3 : code === 'child_runtime_error' ? 1 : 2
|
|
112
|
+
return {
|
|
113
|
+
offer(session: string, code: BenchmarkChildFailureCode | undefined): void {
|
|
114
|
+
if (code === undefined || emitted.has(session)) return
|
|
115
|
+
const previous = pending.get(session)
|
|
116
|
+
if (previous === undefined || rank(code) > rank(previous)) pending.set(session, code)
|
|
117
|
+
},
|
|
118
|
+
flush(session: string): void {
|
|
119
|
+
if (emitted.has(session)) return
|
|
120
|
+
const code = pending.get(session)
|
|
121
|
+
if (code === undefined) return
|
|
122
|
+
emitted.add(session)
|
|
123
|
+
pending.delete(session)
|
|
124
|
+
write(code)
|
|
125
|
+
},
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function plainObject(value: unknown): value is Record<string, unknown> {
|
|
130
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** Keep the newest bounded bytes so a final control record cannot be displaced by noise. */
|
|
134
|
+
export function appendBoundedChildDiagnostic(current: Buffer, chunk: Buffer | string): Buffer {
|
|
135
|
+
const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)
|
|
136
|
+
if (bytes.length >= BENCHMARK_CHILD_DIAGNOSTIC_MAX_BYTES) {
|
|
137
|
+
return bytes.subarray(bytes.length - BENCHMARK_CHILD_DIAGNOSTIC_MAX_BYTES)
|
|
138
|
+
}
|
|
139
|
+
if (current.length + bytes.length <= BENCHMARK_CHILD_DIAGNOSTIC_MAX_BYTES) {
|
|
140
|
+
return Buffer.concat([current, bytes])
|
|
141
|
+
}
|
|
142
|
+
const keep = BENCHMARK_CHILD_DIAGNOSTIC_MAX_BYTES - bytes.length
|
|
143
|
+
return Buffer.concat([current.subarray(current.length - keep), bytes])
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function parseBenchmarkChildDiagnostic(raw: string): BenchmarkChildFailureCode | undefined {
|
|
147
|
+
const lines = raw.split('\n').map(line => line.trim()).filter(Boolean)
|
|
148
|
+
for (let index = lines.length - 1; index >= 0; index -= 1) {
|
|
149
|
+
try {
|
|
150
|
+
const parsed: unknown = JSON.parse(lines[index])
|
|
151
|
+
if (!plainObject(parsed)) continue
|
|
152
|
+
const keys = Object.keys(parsed).sort()
|
|
153
|
+
if (JSON.stringify(keys) !== JSON.stringify(['code', 'schema_version'])) continue
|
|
154
|
+
if (parsed.schema_version !== BENCHMARK_CHILD_DIAGNOSTIC_SCHEMA) continue
|
|
155
|
+
if (typeof parsed.code !== 'string' || !FAILURE_CODES.has(parsed.code as BenchmarkChildFailureCode)) continue
|
|
156
|
+
return parsed.code as BenchmarkChildFailureCode
|
|
157
|
+
} catch {
|
|
158
|
+
// The control pipe is fail-closed: malformed or unrelated records are ignored.
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return undefined
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** Emit one namespaced enum record without reflecting prompts, paths, or stderr. */
|
|
165
|
+
export function emitBenchmarkChildDiagnostic(code: BenchmarkChildFailureCode): void {
|
|
166
|
+
const fd = Number(process.env[BENCHMARK_CHILD_DIAGNOSTIC_FD_ENV])
|
|
167
|
+
if (!Number.isInteger(fd) || fd < 3) return
|
|
168
|
+
const record = `\n${JSON.stringify({ schema_version: BENCHMARK_CHILD_DIAGNOSTIC_SCHEMA, code })}\n`
|
|
169
|
+
if (Buffer.byteLength(record, 'utf8') > BENCHMARK_CHILD_DIAGNOSTIC_MAX_BYTES) return
|
|
170
|
+
try {
|
|
171
|
+
writeSync(fd, record)
|
|
172
|
+
} catch {
|
|
173
|
+
// Diagnostics never change the conformance decision itself.
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export function classifyBenchmarkChildFailure(input: BenchmarkChildFailureInput): BenchmarkChildFailureCode {
|
|
178
|
+
if (input.outputTruncated) return 'child_output_truncated'
|
|
179
|
+
if (input.signal) return 'child_signaled'
|
|
180
|
+
const structured = parseBenchmarkChildDiagnostic(input.diagnostic ?? '')
|
|
181
|
+
if (structured) return structured
|
|
182
|
+
if (input.code !== 0) return 'child_nonzero_exit'
|
|
183
|
+
return 'child_lifecycle_failure'
|
|
184
|
+
}
|