@danceiny/gotry 0.0.1-rc.18 → 0.0.1-rc.20
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 +42 -16
- package/README.zh-CN.md +37 -15
- package/bin/gotry-bootstrap.js +309 -30
- package/bin/gotry-inner.js +74 -23
- package/bin/gotry-runtime-resolution.d.ts +1 -1
- package/bin/gotry-runtime-resolution.js +7 -8
- package/cordis.gotry-patch.yml +22 -8
- package/data/stations-12306-verify.json +524 -0
- package/dist/capabilities/agent-reach.js +4 -4
- package/dist/capabilities/anything.js +62 -29
- package/dist/capabilities/channel-health.js +103 -0
- package/dist/capabilities/channel-registry.js +213 -0
- package/dist/capabilities/doctor.js +307 -0
- package/dist/capabilities/effect.js +178 -5
- package/dist/capabilities/flyai.js +13 -0
- package/dist/capabilities/hbcli.js +28 -2
- package/dist/capabilities/session/adapters/ctrip-hotel.js +207 -0
- package/dist/capabilities/session/adapters/rail-12306.js +285 -0
- package/dist/capabilities/session/extension-bridge.js +14 -1
- package/dist/capabilities/session/extension-channel.js +4 -2
- package/dist/capabilities/session/extension-distribution.js +1 -1
- package/dist/capabilities/session-search.js +311 -0
- package/dist/capabilities/visa-policy.js +126 -0
- package/dist/scripts/agent-planning-turn-deadline-e2e.js +305 -0
- package/dist/scripts/agent-planning-turn-deadline-tests.js +281 -0
- package/dist/scripts/anything-tests.js +70 -21
- package/dist/scripts/benchmark-environment-bridge-e2e.js +139 -37
- package/dist/scripts/benchmark-environment-bridge-tests.js +1099 -306
- package/dist/scripts/booking-copilot-availability-ledger-binding-tests.js +26 -26
- package/dist/scripts/{booking-copilot-availability-policy-v2-tests.js → booking-copilot-availability-policy-tests.js} +115 -115
- package/dist/scripts/booking-copilot-crossrepo-fixture-server.js +41 -1
- package/dist/scripts/booking-copilot-dsh-core-proof-tests.js +75 -120
- package/dist/scripts/booking-copilot-dsh-planner-proof-tests.js +370 -61
- package/dist/scripts/booking-copilot-dsh-plugin-proof-tests.js +2 -2
- package/dist/scripts/booking-copilot-event-sequence-concurrency-proof-tests.js +7 -3
- package/dist/scripts/booking-copilot-gap-code-contract-proof-tests.js +15 -9
- package/dist/scripts/booking-copilot-operation-ledger-concurrency-proof-tests.js +19 -11
- package/dist/scripts/booking-copilot-receipt-ledger-concurrency-proof-tests.js +16 -52
- package/dist/scripts/booking-copilot-runtime-proof-tests.js +3953 -155
- package/dist/scripts/booking-copilot-server-proof-tests.js +58 -23
- package/dist/scripts/booking-copilot-startup-proof-tests.js +10 -119
- package/dist/scripts/booking-surface-contract-proof-tests.js +1154 -326
- package/dist/scripts/bootstrap-tests.js +113 -2
- package/dist/scripts/build-metrics-report.js +339 -0
- package/dist/scripts/channel-probe-tests.js +148 -0
- package/dist/scripts/channel-probe.js +252 -0
- package/dist/scripts/channel-registry-tests.js +262 -0
- package/dist/scripts/doctor-tests.js +175 -0
- package/dist/scripts/effect-tests.js +274 -1
- package/dist/scripts/extension-tests.js +129 -11
- package/dist/scripts/fact-gate-tests.js +91 -2
- package/dist/scripts/flyai-tests.js +17 -1
- package/dist/scripts/hbcli-e2e-tests.js +1 -3
- package/dist/scripts/hbcli-tests.js +38 -1
- package/dist/scripts/metrics-report-tests.js +288 -0
- package/dist/scripts/persona-surface-guard-tests.js +19 -0
- package/dist/scripts/session-tests.js +401 -1
- package/dist/scripts/smoke.js +100 -83
- package/dist/scripts/turn-handoff-collect-tests.js +216 -0
- package/dist/scripts/turn-handoff-collect.js +189 -0
- package/dist/scripts/turn-policy-tests.js +56 -0
- package/dist/scripts/typed-contract-canary.js +261 -0
- package/dist/scripts/visa-policy-tests.js +75 -0
- package/dist/scripts/wish-channel-gate-tests.js +111 -0
- package/dist/src/artifact-gate.js +91 -3
- package/dist/src/benchmark-agent-conformance.js +39 -16
- package/dist/src/benchmark-environment-bridge.js +316 -52
- package/dist/src/bookable-facts.js +62 -3
- package/dist/src/booking-surface/{availability-policy-v2.js → availability-policy.js} +89 -89
- package/dist/src/booking-surface/canonical-schema.js +8 -22
- package/dist/src/booking-surface/contracts.js +70 -19
- package/dist/src/booking-surface/dsh-planner.js +310 -177
- package/dist/src/booking-surface/dsh-plugin.js +3 -3
- package/dist/src/booking-surface/index.js +1 -5
- package/dist/src/booking-surface/profile.js +1 -1
- package/dist/src/booking-surface/runtime.js +1682 -247
- package/dist/src/booking-surface/server.js +371 -185
- package/dist/src/booking-surface/startup.js +17 -27
- package/dist/src/booking-surface/validation.js +270 -760
- package/dist/src/index.js +739 -150
- package/dist/src/turn-deadline.js +292 -0
- package/dist/src/turn-policy.js +146 -0
- package/dist/src/wish-pool.js +5 -0
- package/extension/background.js +23 -4
- package/extension/content-main.js +47 -9
- package/extension/manifest.json +27 -9
- package/package.json +7 -26
- package/schemas/booking.surface.schema.json +2593 -0
- package/ts/capabilities/agent-reach.ts +4 -4
- package/ts/capabilities/anything.ts +89 -38
- package/ts/capabilities/flyai.ts +21 -3
- package/ts/capabilities/hbcli.ts +53 -4
- package/ts/capabilities/session/action-cache.ts +1 -1
- package/ts/capabilities/session/adapters/ctrip-hotel.ts +238 -0
- package/ts/capabilities/session/adapters/rail-12306.ts +293 -0
- package/ts/capabilities/session/extension-bridge.ts +32 -2
- package/ts/capabilities/session/extension-channel.ts +9 -8
- package/ts/capabilities/session/extension-distribution.ts +3 -1
- package/ts/capabilities/session/health-watch.ts +1 -1
- package/ts/capabilities/session/read-guard.ts +1 -1
- package/ts/capabilities/session/wizard.ts +1 -1
- package/ts/capabilities/session-search.ts +323 -1
- package/ts/dsh-runtime/vendor/README.md +60 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/LICENSE +21 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/README.en.md +200 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/README.md +202 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/client/client.js +216 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/cordis.patch.yml +5 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/amap.js +371 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/nominatim.js +63 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/osrm.js +56 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/photon.js +55 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/config-file.js +87 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/config-route.js +126 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/config.js +16 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/index.js +109 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/settings-ns.js +27 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/tools/geocode.js +127 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/tools/poi.js +84 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/tools/routes.js +170 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/amap.d.ts +53 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/nominatim.d.ts +17 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/osrm.d.ts +15 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/photon.d.ts +22 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/config-file.d.ts +33 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/config-route.d.ts +20 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/config.d.ts +23 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/index.d.ts +18 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/settings-ns.d.ts +14 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/tools/geocode.d.ts +11 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/tools/poi.d.ts +9 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/tools/routes.d.ts +16 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/types.d.ts +55 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types.js +16 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/package.json +91 -0
- package/ts/package.json +9 -0
- package/ts/scripts/turn-handoff-collect.ts +178 -0
- package/ts/src/artifact-gate.ts +82 -2
- package/ts/src/benchmark-agent-conformance.ts +54 -15
- package/ts/src/benchmark-environment-bridge.ts +234 -46
- package/ts/src/bookable-facts.ts +109 -5
- package/ts/src/booking-saga.ts +1 -1
- package/ts/src/booking-surface/{availability-policy-v2.ts → availability-policy.ts} +124 -125
- package/ts/src/booking-surface/canonical-schema.js +8 -22
- package/ts/src/booking-surface/contracts.ts +278 -239
- package/ts/src/booking-surface/dsh-planner.ts +333 -194
- package/ts/src/booking-surface/dsh-plugin.js +3 -3
- package/ts/src/booking-surface/index.ts +1 -5
- package/ts/src/booking-surface/profile.ts +11 -11
- package/ts/src/booking-surface/runtime.ts +1351 -351
- package/ts/src/booking-surface/server.ts +282 -175
- package/ts/src/booking-surface/startup.ts +36 -48
- package/ts/src/booking-surface/validation.ts +194 -442
- package/ts/src/contracts.ts +1 -1
- package/ts/src/index.ts +492 -172
- package/ts/src/loop.ts +1 -1
- package/ts/src/state-ledger.ts +1 -1
- package/ts/src/turn-deadline.ts +365 -0
- package/ts/src/turn-policy.ts +154 -0
- package/ts/src/wish-pool.ts +17 -4
- package/dist/scripts/agent-planning-budget-e2e.js +0 -227
- package/dist/scripts/agent-planning-budget-tests.js +0 -173
- package/dist/scripts/booking-copilot-v2-runtime-proof-tests.js +0 -3935
- package/dist/scripts/booking-surface-v2-contract-proof-tests.js +0 -1174
- package/dist/src/booking-surface/contracts-v2.js +0 -89
- package/dist/src/booking-surface/runtime-v2.js +0 -1771
- package/dist/src/booking-surface/server-v2.js +0 -334
- package/dist/src/booking-surface/validation-v2.js +0 -319
- package/dist/src/tool-budget.js +0 -136
- package/schemas/booking.surface.v1.schema.json +0 -927
- package/schemas/booking.surface.v2.schema.json +0 -61
- package/ts/src/booking-surface/contracts-v2.ts +0 -118
- package/ts/src/booking-surface/runtime-v2.ts +0 -1466
- package/ts/src/booking-surface/server-v2.ts +0 -247
- package/ts/src/booking-surface/validation-v2.ts +0 -205
- package/ts/src/tool-budget.ts +0 -165
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
import { accessSync, constants, lstatSync, readFileSync, statSync } from 'node:fs';
|
|
2
2
|
import { getuid } from 'node:process';
|
|
3
3
|
import { isAbsolute } from 'node:path';
|
|
4
|
-
import {
|
|
4
|
+
import { assertSupportedJsonSchema, validateJsonSchemaValue, ToolArgsError } from '@deepseek-ai/dsh-tools';
|
|
5
5
|
import { validateTerminalOutputConfig } from './benchmark-agent-conformance.js';
|
|
6
|
-
const SCHEMA_VERSION = '
|
|
6
|
+
const SCHEMA_VERSION = 'gotry_benchmark_environment_bridge_v3';
|
|
7
|
+
export const BENCHMARK_TOOL_RESULT_SCHEMA_VERSION = 'gotry_benchmark_tool_result_v1';
|
|
8
|
+
export const BENCHMARK_DOMAIN_RECOVERIES = [
|
|
9
|
+
'none',
|
|
10
|
+
'retry_same',
|
|
11
|
+
'revise_arguments',
|
|
12
|
+
'choose_alternative'
|
|
13
|
+
];
|
|
7
14
|
const IDENTIFIER = /^[A-Za-z][A-Za-z0-9_.-]*$/;
|
|
15
|
+
const RECOVERIES = new Set(BENCHMARK_DOMAIN_RECOVERIES);
|
|
8
16
|
const CONFIG_KEYS = [
|
|
9
|
-
'allowed_tools',
|
|
10
17
|
'argv_prefix',
|
|
11
18
|
'cwd',
|
|
12
19
|
'enabled',
|
|
@@ -15,14 +22,87 @@ const CONFIG_KEYS = [
|
|
|
15
22
|
'max_output_bytes',
|
|
16
23
|
'schema_version',
|
|
17
24
|
'terminal_output',
|
|
18
|
-
'timeout_ms'
|
|
19
|
-
|
|
20
|
-
const OPTIONAL_CONFIG_KEYS = [
|
|
21
|
-
'allowed_output_keys'
|
|
25
|
+
'timeout_ms',
|
|
26
|
+
'tools'
|
|
22
27
|
];
|
|
28
|
+
const INPUT_SCHEMA_MAX_BYTES = 16 * 1024;
|
|
29
|
+
const INPUT_SCHEMA_MAX_DEPTH = 8;
|
|
30
|
+
const INPUT_SCHEMA_MAX_NODES = 256;
|
|
31
|
+
const INPUT_SCHEMA_MAX_PROPERTIES = 256;
|
|
32
|
+
const INPUT_SCHEMA_MAX_ENUM_VALUES = 256;
|
|
33
|
+
const INPUT_SCHEMA_MAX_DESCRIPTION_LENGTH = 512;
|
|
34
|
+
const INPUT_SCHEMA_TYPES = new Set([
|
|
35
|
+
'string',
|
|
36
|
+
'number',
|
|
37
|
+
'integer',
|
|
38
|
+
'boolean',
|
|
39
|
+
'null',
|
|
40
|
+
'array',
|
|
41
|
+
'object'
|
|
42
|
+
]);
|
|
43
|
+
function descriptorSchema(tools) {
|
|
44
|
+
return {
|
|
45
|
+
oneOf: [
|
|
46
|
+
{
|
|
47
|
+
type: 'object',
|
|
48
|
+
description: 'List the frozen benchmark tool descriptors.',
|
|
49
|
+
properties: {
|
|
50
|
+
action: {
|
|
51
|
+
type: 'string',
|
|
52
|
+
const: 'tools'
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
required: [
|
|
56
|
+
'action'
|
|
57
|
+
],
|
|
58
|
+
additionalProperties: false
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
type: 'object',
|
|
62
|
+
description: 'List the frozen bridge error contract.',
|
|
63
|
+
properties: {
|
|
64
|
+
action: {
|
|
65
|
+
type: 'string',
|
|
66
|
+
const: 'errors'
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
required: [
|
|
70
|
+
'action'
|
|
71
|
+
],
|
|
72
|
+
additionalProperties: false
|
|
73
|
+
},
|
|
74
|
+
...tools.map((tool)=>({
|
|
75
|
+
type: 'object',
|
|
76
|
+
description: tool.description,
|
|
77
|
+
properties: {
|
|
78
|
+
action: {
|
|
79
|
+
type: 'string',
|
|
80
|
+
const: 'call'
|
|
81
|
+
},
|
|
82
|
+
tool: {
|
|
83
|
+
type: 'string',
|
|
84
|
+
const: tool.name
|
|
85
|
+
},
|
|
86
|
+
arguments: tool.input_schema
|
|
87
|
+
},
|
|
88
|
+
required: [
|
|
89
|
+
'action',
|
|
90
|
+
'tool',
|
|
91
|
+
'arguments'
|
|
92
|
+
],
|
|
93
|
+
additionalProperties: false
|
|
94
|
+
}))
|
|
95
|
+
]
|
|
96
|
+
};
|
|
97
|
+
}
|
|
23
98
|
function plainObject(value) {
|
|
24
99
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
25
100
|
}
|
|
101
|
+
function deepFreezeJson(value) {
|
|
102
|
+
if (!value || typeof value !== 'object' || Object.isFrozen(value)) return value;
|
|
103
|
+
for (const child of Object.values(value))deepFreezeJson(child);
|
|
104
|
+
return Object.freeze(value);
|
|
105
|
+
}
|
|
26
106
|
function existingExecutable(value) {
|
|
27
107
|
if (typeof value !== 'string' || !isAbsolute(value)) return false;
|
|
28
108
|
try {
|
|
@@ -41,13 +121,121 @@ function existingDirectory(value) {
|
|
|
41
121
|
return false;
|
|
42
122
|
}
|
|
43
123
|
}
|
|
44
|
-
function identifiers(value) {
|
|
45
|
-
return Array.isArray(value) && value.length > 0 && value.length <= 64 && new Set(value).size === value.length && value.every((item)=>typeof item === 'string' && item.length > 0 && IDENTIFIER.test(item));
|
|
124
|
+
function identifiers(value, allowEmpty = false) {
|
|
125
|
+
return Array.isArray(value) && (allowEmpty || value.length > 0) && value.length <= 64 && new Set(value).size === value.length && value.every((item)=>typeof item === 'string' && item.length > 0 && IDENTIFIER.test(item));
|
|
126
|
+
}
|
|
127
|
+
function exactKeys(value, allowed) {
|
|
128
|
+
return Object.keys(value).every((key)=>allowed.includes(key));
|
|
129
|
+
}
|
|
130
|
+
function validDescription(value, required = false) {
|
|
131
|
+
if (value === undefined) return !required;
|
|
132
|
+
return typeof value === 'string' && (!required || value.trim().length > 0) && value.length <= INPUT_SCHEMA_MAX_DESCRIPTION_LENGTH && !/[\u0000-\u001f\u007f]/.test(value);
|
|
46
133
|
}
|
|
47
|
-
function
|
|
134
|
+
function scalarMatches(type, value) {
|
|
135
|
+
if (type === 'string') return typeof value === 'string' && value.length <= 512;
|
|
136
|
+
if (type === 'number') return typeof value === 'number' && Number.isFinite(value);
|
|
137
|
+
if (type === 'integer') return typeof value === 'number' && Number.isSafeInteger(value);
|
|
138
|
+
if (type === 'boolean') return typeof value === 'boolean';
|
|
139
|
+
return type === 'null' && value === null;
|
|
140
|
+
}
|
|
141
|
+
function validInputSchema(value) {
|
|
48
142
|
if (!plainObject(value)) return false;
|
|
49
|
-
|
|
50
|
-
|
|
143
|
+
let serialized;
|
|
144
|
+
try {
|
|
145
|
+
serialized = JSON.stringify(value);
|
|
146
|
+
} catch {
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
if (Buffer.byteLength(serialized, 'utf8') > INPUT_SCHEMA_MAX_BYTES) return false;
|
|
150
|
+
const pending = [
|
|
151
|
+
{
|
|
152
|
+
node: value,
|
|
153
|
+
depth: 0
|
|
154
|
+
}
|
|
155
|
+
];
|
|
156
|
+
const seen = new Set();
|
|
157
|
+
let nodes = 0;
|
|
158
|
+
let properties = 0;
|
|
159
|
+
let enumValues = 0;
|
|
160
|
+
while(pending.length > 0){
|
|
161
|
+
const { node, depth } = pending.pop();
|
|
162
|
+
if (seen.has(node) || ++nodes > INPUT_SCHEMA_MAX_NODES || depth > INPUT_SCHEMA_MAX_DEPTH) return false;
|
|
163
|
+
seen.add(node);
|
|
164
|
+
if (!INPUT_SCHEMA_TYPES.has(String(node.type)) || !validDescription(node.description)) return false;
|
|
165
|
+
if (node.type === 'object') {
|
|
166
|
+
if (!exactKeys(node, [
|
|
167
|
+
'type',
|
|
168
|
+
'description',
|
|
169
|
+
'properties',
|
|
170
|
+
'required',
|
|
171
|
+
'additionalProperties'
|
|
172
|
+
]) || !plainObject(node.properties) || !Array.isArray(node.required) || node.additionalProperties !== false) return false;
|
|
173
|
+
const propertyEntries = Object.entries(node.properties);
|
|
174
|
+
properties += propertyEntries.length;
|
|
175
|
+
if (properties > INPUT_SCHEMA_MAX_PROPERTIES || propertyEntries.some(([key, child])=>!IDENTIFIER.test(key) || !plainObject(child))) return false;
|
|
176
|
+
const required = node.required;
|
|
177
|
+
if (required.length > propertyEntries.length || new Set(required).size !== required.length || required.some((key)=>typeof key !== 'string' || !Object.hasOwn(node.properties, key))) return false;
|
|
178
|
+
for (const [, child] of propertyEntries)pending.push({
|
|
179
|
+
node: child,
|
|
180
|
+
depth: depth + 1
|
|
181
|
+
});
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
if (node.type === 'array') {
|
|
185
|
+
if (!exactKeys(node, [
|
|
186
|
+
'type',
|
|
187
|
+
'description',
|
|
188
|
+
'items'
|
|
189
|
+
]) || !plainObject(node.items)) return false;
|
|
190
|
+
pending.push({
|
|
191
|
+
node: node.items,
|
|
192
|
+
depth: depth + 1
|
|
193
|
+
});
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
if (!exactKeys(node, [
|
|
197
|
+
'type',
|
|
198
|
+
'description',
|
|
199
|
+
'enum',
|
|
200
|
+
'const'
|
|
201
|
+
]) || Object.hasOwn(node, 'enum') && Object.hasOwn(node, 'const')) return false;
|
|
202
|
+
if (Object.hasOwn(node, 'const') && !scalarMatches(String(node.type), node.const)) return false;
|
|
203
|
+
if (Object.hasOwn(node, 'enum')) {
|
|
204
|
+
if (!Array.isArray(node.enum) || node.enum.length === 0 || node.enum.length > 64) return false;
|
|
205
|
+
enumValues += node.enum.length;
|
|
206
|
+
if (enumValues > INPUT_SCHEMA_MAX_ENUM_VALUES || new Set(node.enum).size !== node.enum.length || node.enum.some((item)=>!scalarMatches(String(node.type), item))) return false;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return value.type === 'object';
|
|
210
|
+
}
|
|
211
|
+
function toolDescriptors(value) {
|
|
212
|
+
if (!Array.isArray(value) || value.length === 0 || value.length > 64) return false;
|
|
213
|
+
const names = new Set();
|
|
214
|
+
return value.every((item)=>{
|
|
215
|
+
if (!plainObject(item)) return false;
|
|
216
|
+
const keys = Object.keys(item).sort();
|
|
217
|
+
const required = [
|
|
218
|
+
'description',
|
|
219
|
+
'domain_outcomes',
|
|
220
|
+
'input_schema',
|
|
221
|
+
'name'
|
|
222
|
+
];
|
|
223
|
+
if (JSON.stringify(keys) !== JSON.stringify([
|
|
224
|
+
...required,
|
|
225
|
+
'output_keys'
|
|
226
|
+
].sort())) return false;
|
|
227
|
+
if (typeof item.name !== 'string' || !IDENTIFIER.test(item.name) || names.has(item.name)) return false;
|
|
228
|
+
names.add(item.name);
|
|
229
|
+
if (!validDescription(item.description, true) || !validInputSchema(item.input_schema)) return false;
|
|
230
|
+
if (!Array.isArray(item.domain_outcomes) || item.domain_outcomes.length > 64) return false;
|
|
231
|
+
const outcomeKeys = new Set();
|
|
232
|
+
if (!item.domain_outcomes.every((outcome)=>plainObject(outcome) && JSON.stringify(Object.keys(outcome).sort()) === JSON.stringify([
|
|
233
|
+
'code',
|
|
234
|
+
'recovery',
|
|
235
|
+
'status'
|
|
236
|
+
]) && (outcome.status === 'miss' || outcome.status === 'error') && typeof outcome.code === 'string' && IDENTIFIER.test(outcome.code) && typeof outcome.recovery === 'string' && RECOVERIES.has(outcome.recovery) && !outcomeKeys.has(outcome.code) && (outcomeKeys.add(outcome.code), true))) return false;
|
|
237
|
+
return identifiers(item.output_keys);
|
|
238
|
+
});
|
|
51
239
|
}
|
|
52
240
|
function argvPrefix(value) {
|
|
53
241
|
return Array.isArray(value) && value.length > 0 && value.length <= 32 && value.every((item)=>typeof item === 'string' && item.length > 0 && item.length <= 256 && !item.includes('\0') && !item.includes('\r') && !item.includes('\n'));
|
|
@@ -69,14 +257,7 @@ function validate(value) {
|
|
|
69
257
|
const baseKeys = [
|
|
70
258
|
...CONFIG_KEYS
|
|
71
259
|
].sort();
|
|
72
|
-
|
|
73
|
-
...CONFIG_KEYS,
|
|
74
|
-
...OPTIONAL_CONFIG_KEYS
|
|
75
|
-
].sort();
|
|
76
|
-
if (!plainObject(value) || ![
|
|
77
|
-
baseKeys,
|
|
78
|
-
extendedKeys
|
|
79
|
-
].some((keys)=>JSON.stringify(configKeys) === JSON.stringify(keys)) || value.schema_version !== SCHEMA_VERSION || value.enabled !== true || !existingExecutable(value.executable) || !existingDirectory(value.cwd) || !argvPrefix(value.argv_prefix) || !identifiers(value.allowed_tools) || !validBound(value.timeout_ms, 120_000) || !validBound(value.max_output_bytes, 10 * 1024 * 1024) || !validateTerminalOutputConfig(value.terminal_output) || !exactIsolation(value.isolation) || Object.hasOwn(value, 'allowed_output_keys') && !allowedOutputKeys(value.allowed_output_keys, value.allowed_tools)) return false;
|
|
260
|
+
if (!plainObject(value) || JSON.stringify(configKeys) !== JSON.stringify(baseKeys) || value.schema_version !== SCHEMA_VERSION || value.enabled !== true || !existingExecutable(value.executable) || !existingDirectory(value.cwd) || !argvPrefix(value.argv_prefix) || !toolDescriptors(value.tools) || !validBound(value.timeout_ms, 120_000) || !validBound(value.max_output_bytes, 10 * 1024 * 1024) || !validateTerminalOutputConfig(value.terminal_output) || !exactIsolation(value.isolation)) return false;
|
|
80
261
|
return true;
|
|
81
262
|
}
|
|
82
263
|
export function loadBenchmarkEnvironmentConfig(path) {
|
|
@@ -87,7 +268,7 @@ export function loadBenchmarkEnvironmentConfig(path) {
|
|
|
87
268
|
if (typeof getuid !== 'function' || metadata.uid !== getuid()) return null;
|
|
88
269
|
if ((metadata.mode & 0o022) !== 0) return null;
|
|
89
270
|
const parsed = JSON.parse(readFileSync(path, 'utf8'));
|
|
90
|
-
return validate(parsed) ? parsed : null;
|
|
271
|
+
return validate(parsed) ? deepFreezeJson(parsed) : null;
|
|
91
272
|
} catch {
|
|
92
273
|
return null;
|
|
93
274
|
}
|
|
@@ -116,6 +297,48 @@ const FORBIDDEN_OUTPUT_KEY_FRAGMENTS = [
|
|
|
116
297
|
'reward',
|
|
117
298
|
'score'
|
|
118
299
|
];
|
|
300
|
+
export const BRIDGE_ERROR_CONTRACT = Object.freeze({
|
|
301
|
+
invalid_action: {
|
|
302
|
+
recoverable: true,
|
|
303
|
+
remedy: 'action 只允许 tools、call 或 errors'
|
|
304
|
+
},
|
|
305
|
+
disallowed_tool: {
|
|
306
|
+
recoverable: true,
|
|
307
|
+
remedy: '先 action=tools 列出 allowed_tools 清单,再从清单内选工具'
|
|
308
|
+
},
|
|
309
|
+
invalid_arguments: {
|
|
310
|
+
recoverable: true,
|
|
311
|
+
remedy: 'arguments 必须是可序列化 JSON 对象(≤64KB、深度≤12);收窄后重试'
|
|
312
|
+
},
|
|
313
|
+
timed_out: {
|
|
314
|
+
recoverable: true,
|
|
315
|
+
remedy: '上游超时;可原样重试一次或换其他工具'
|
|
316
|
+
},
|
|
317
|
+
output_truncated: {
|
|
318
|
+
recoverable: true,
|
|
319
|
+
remedy: '输出超出上限;让被调工具收窄查询范围后重试'
|
|
320
|
+
},
|
|
321
|
+
invalid_json: {
|
|
322
|
+
recoverable: true,
|
|
323
|
+
remedy: '上游输出不是合法 JSON;重试一次或换工具'
|
|
324
|
+
},
|
|
325
|
+
invalid_output: {
|
|
326
|
+
recoverable: true,
|
|
327
|
+
remedy: '上游输出结构不符;重试或换工具'
|
|
328
|
+
},
|
|
329
|
+
runner_failed: {
|
|
330
|
+
recoverable: true,
|
|
331
|
+
remedy: '上游进程非零退出;可重试一次,持续失败换工具'
|
|
332
|
+
},
|
|
333
|
+
spawn_failed: {
|
|
334
|
+
recoverable: false,
|
|
335
|
+
remedy: '可执行文件不可用——环境问题,调用方不可恢复'
|
|
336
|
+
},
|
|
337
|
+
forbidden_output: {
|
|
338
|
+
recoverable: false,
|
|
339
|
+
remedy: '输出含未授权键(策略边界)——不可恢复,换工具或放弃'
|
|
340
|
+
}
|
|
341
|
+
});
|
|
119
342
|
function inspectOutput(value) {
|
|
120
343
|
const pending = [
|
|
121
344
|
{
|
|
@@ -152,7 +375,8 @@ function inspectAllowedOutput(value, allowedKeys) {
|
|
|
152
375
|
const pending = [
|
|
153
376
|
{
|
|
154
377
|
value,
|
|
155
|
-
depth: 0
|
|
378
|
+
depth: 0,
|
|
379
|
+
coveredByDeclaredKey: false
|
|
156
380
|
}
|
|
157
381
|
];
|
|
158
382
|
let nodes = 0;
|
|
@@ -162,16 +386,21 @@ function inspectAllowedOutput(value, allowedKeys) {
|
|
|
162
386
|
if (Array.isArray(current.value)) {
|
|
163
387
|
for (const child of current.value)pending.push({
|
|
164
388
|
value: child,
|
|
165
|
-
depth: current.depth + 1
|
|
389
|
+
depth: current.depth + 1,
|
|
390
|
+
coveredByDeclaredKey: current.coveredByDeclaredKey
|
|
166
391
|
});
|
|
167
392
|
continue;
|
|
168
393
|
}
|
|
169
|
-
if (!plainObject(current.value))
|
|
394
|
+
if (!plainObject(current.value)) {
|
|
395
|
+
if (!current.coveredByDeclaredKey) return false;
|
|
396
|
+
continue;
|
|
397
|
+
}
|
|
170
398
|
for (const [key, child] of Object.entries(current.value)){
|
|
171
399
|
if (!allowed.has(key)) return false;
|
|
172
400
|
pending.push({
|
|
173
401
|
value: child,
|
|
174
|
-
depth: current.depth + 1
|
|
402
|
+
depth: current.depth + 1,
|
|
403
|
+
coveredByDeclaredKey: true
|
|
175
404
|
});
|
|
176
405
|
}
|
|
177
406
|
}
|
|
@@ -215,20 +444,16 @@ export function registerBenchmarkEnvironmentBridge(path, register, subprocess) {
|
|
|
215
444
|
const bridge = loadBenchmarkEnvironmentConfig(path);
|
|
216
445
|
if (!bridge) throw new Error('benchmark environment bridge configuration unavailable');
|
|
217
446
|
if (!subprocess) throw new Error('benchmark environment bridge subprocess unavailable');
|
|
218
|
-
|
|
447
|
+
const parameters = deepFreezeJson({
|
|
448
|
+
oneOf: descriptorSchema(bridge.tools).oneOf
|
|
449
|
+
});
|
|
450
|
+
assertSupportedJsonSchema(parameters);
|
|
451
|
+
const definition = {
|
|
219
452
|
name: 'gotry_benchmark_environment',
|
|
220
453
|
description: 'When a prompt asks to run agent_env.cli, use action=call here with the mapped tool; arbitrary shell is not exposed.',
|
|
221
|
-
parameters
|
|
222
|
-
query: {
|
|
223
|
-
type: 'json',
|
|
224
|
-
required: true,
|
|
225
|
-
description: '{ action: "tools" | "call", tool?: string, arguments?: object }'
|
|
226
|
-
}
|
|
227
|
-
},
|
|
454
|
+
parameters,
|
|
228
455
|
output: {
|
|
229
|
-
schema: {
|
|
230
|
-
type: 'json'
|
|
231
|
-
},
|
|
456
|
+
schema: {},
|
|
232
457
|
render: (_args, value)=>[
|
|
233
458
|
{
|
|
234
459
|
type: 'text',
|
|
@@ -237,28 +462,45 @@ export function registerBenchmarkEnvironmentBridge(path, register, subprocess) {
|
|
|
237
462
|
]
|
|
238
463
|
},
|
|
239
464
|
async execute (args) {
|
|
240
|
-
const
|
|
465
|
+
const violations = validateJsonSchemaValue(parameters, args, 'arguments');
|
|
466
|
+
if (violations.length > 0) throw new ToolArgsError(violations);
|
|
467
|
+
const query = args;
|
|
468
|
+
const tool = query.tool;
|
|
469
|
+
const descriptor = bridge.tools.find((item)=>item.name === tool);
|
|
241
470
|
if (query.action === 'tools') return jsonObject({
|
|
242
471
|
ok: true,
|
|
243
|
-
tools: bridge.
|
|
472
|
+
tools: bridge.tools
|
|
244
473
|
});
|
|
245
|
-
if (query.action
|
|
246
|
-
ok:
|
|
247
|
-
|
|
474
|
+
if (query.action === 'errors') return jsonObject({
|
|
475
|
+
ok: true,
|
|
476
|
+
errors: BRIDGE_ERROR_CONTRACT
|
|
248
477
|
});
|
|
249
|
-
if (
|
|
478
|
+
if (query.action !== 'call') {
|
|
479
|
+
return jsonObject({
|
|
480
|
+
ok: false,
|
|
481
|
+
error: 'invalid_action'
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
if (typeof tool !== 'string' || !descriptor) {
|
|
250
485
|
return jsonObject({
|
|
251
486
|
ok: false,
|
|
252
487
|
error: 'disallowed_tool'
|
|
253
488
|
});
|
|
254
489
|
}
|
|
255
|
-
if (
|
|
490
|
+
if (!plainObject(query.arguments)) {
|
|
491
|
+
return jsonObject({
|
|
492
|
+
ok: false,
|
|
493
|
+
error: 'invalid_arguments'
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
const callArguments = query.arguments;
|
|
497
|
+
const argumentViolations = validateJsonSchemaValue(descriptor.input_schema, callArguments, 'arguments');
|
|
498
|
+
if (argumentViolations.length > 0) {
|
|
256
499
|
return jsonObject({
|
|
257
500
|
ok: false,
|
|
258
501
|
error: 'invalid_arguments'
|
|
259
502
|
});
|
|
260
503
|
}
|
|
261
|
-
const callArguments = query.arguments === undefined ? {} : query.arguments;
|
|
262
504
|
const serialized = serializedArguments(callArguments);
|
|
263
505
|
if (serialized.reason) return jsonObject({
|
|
264
506
|
ok: false,
|
|
@@ -291,7 +533,7 @@ export function registerBenchmarkEnvironmentBridge(path, register, subprocess) {
|
|
|
291
533
|
bridge.executable,
|
|
292
534
|
...bridge.argv_prefix,
|
|
293
535
|
'call',
|
|
294
|
-
|
|
536
|
+
tool,
|
|
295
537
|
serialized.json
|
|
296
538
|
],
|
|
297
539
|
cwd: bridge.cwd,
|
|
@@ -341,13 +583,36 @@ export function registerBenchmarkEnvironmentBridge(path, register, subprocess) {
|
|
|
341
583
|
ok: false,
|
|
342
584
|
error: 'invalid_output'
|
|
343
585
|
});
|
|
344
|
-
|
|
586
|
+
if (parsed.schema_version !== BENCHMARK_TOOL_RESULT_SCHEMA_VERSION || typeof parsed.status !== 'string') {
|
|
587
|
+
return jsonObject({
|
|
588
|
+
ok: false,
|
|
589
|
+
error: 'invalid_output'
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
if (parsed.status === 'miss' || parsed.status === 'error') {
|
|
593
|
+
if (typeof parsed.code !== 'string' || typeof parsed.recovery !== 'string' || !descriptor.domain_outcomes.some((outcome)=>outcome.status === parsed.status && outcome.code === parsed.code && outcome.recovery === parsed.recovery)) return jsonObject({
|
|
594
|
+
ok: false,
|
|
595
|
+
error: 'invalid_output'
|
|
596
|
+
});
|
|
597
|
+
if (Object.keys(parsed).sort().join(',') !== 'code,recovery,schema_version,status') return jsonObject({
|
|
598
|
+
ok: false,
|
|
599
|
+
error: 'invalid_output'
|
|
600
|
+
});
|
|
601
|
+
return jsonObject({
|
|
602
|
+
ok: true,
|
|
603
|
+
outcome: parsed
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
if (parsed.status !== 'ok' || !Object.hasOwn(parsed, 'result') || Object.keys(parsed).sort().join(',') !== 'result,schema_version,status') return jsonObject({
|
|
607
|
+
ok: false,
|
|
608
|
+
error: 'invalid_output'
|
|
609
|
+
});
|
|
610
|
+
const visibleResult = parsed.result;
|
|
345
611
|
if (!plainObject(visibleResult) && !Array.isArray(visibleResult)) return jsonObject({
|
|
346
612
|
ok: false,
|
|
347
613
|
error: 'invalid_output'
|
|
348
614
|
});
|
|
349
|
-
|
|
350
|
-
if (outputKeys && !inspectAllowedOutput(visibleResult, outputKeys)) return jsonObject({
|
|
615
|
+
if (!inspectAllowedOutput(visibleResult, descriptor.output_keys)) return jsonObject({
|
|
351
616
|
ok: false,
|
|
352
617
|
error: 'forbidden_output'
|
|
353
618
|
});
|
|
@@ -368,12 +633,11 @@ export function registerBenchmarkEnvironmentBridge(path, register, subprocess) {
|
|
|
368
633
|
clearTimeout(timer);
|
|
369
634
|
}
|
|
370
635
|
}
|
|
371
|
-
}
|
|
636
|
+
};
|
|
637
|
+
register(definition);
|
|
372
638
|
return Object.freeze({
|
|
373
639
|
toolName: 'gotry_benchmark_environment',
|
|
374
|
-
allowedTools: Object.freeze(
|
|
375
|
-
...bridge.allowed_tools
|
|
376
|
-
]),
|
|
640
|
+
allowedTools: Object.freeze(bridge.tools.map((item)=>item.name)),
|
|
377
641
|
terminal: Object.freeze({
|
|
378
642
|
...bridge.terminal_output
|
|
379
643
|
})
|
|
@@ -52,7 +52,7 @@ export function factsFromFlyai(q, r, fetchedAt, alias) {
|
|
|
52
52
|
const queryId = makeQueryId(source, q.kind, q.origin, q.destination, q.date);
|
|
53
53
|
const origin = iataOf(alias, q.origin);
|
|
54
54
|
const destination = iataOf(alias, q.destination);
|
|
55
|
-
if (r.verdict === 'error') return [];
|
|
55
|
+
if (r.verdict === 'error' || r.verdict === 'needs-setup') return [];
|
|
56
56
|
if (r.verdict === 'miss') return [
|
|
57
57
|
negativeFact(queryId, q.kind, q.origin, q.destination, q.date, source, fetchedAt, alias)
|
|
58
58
|
];
|
|
@@ -159,7 +159,7 @@ export function dedupeFacts(facts) {
|
|
|
159
159
|
].sort((a, b)=>a.fetched_at.localeCompare(b.fetched_at) || a.fact_id.localeCompare(b.fact_id));
|
|
160
160
|
}
|
|
161
161
|
export function latestFactsForRouteDate(facts, origin, destination, date) {
|
|
162
|
-
const hits = facts.filter((f)=>f.kind
|
|
162
|
+
const hits = facts.filter((f)=>(f.kind === 'flight' || f.kind === 'train') && f.route.origin === origin && f.route.destination === destination && f.date === date);
|
|
163
163
|
const latestQueryTs = new Map();
|
|
164
164
|
for (const f of hits){
|
|
165
165
|
const prev = latestQueryTs.get(f.query_id);
|
|
@@ -173,7 +173,7 @@ export function latestFactsForRouteDate(facts, origin, destination, date) {
|
|
|
173
173
|
}
|
|
174
174
|
export function flightClaimVerdict(facts, claim) {
|
|
175
175
|
const no = claim.flight_no.toUpperCase().replace(/\s+/g, '');
|
|
176
|
-
const sameNo = facts.filter((f)=>f.kind
|
|
176
|
+
const sameNo = facts.filter((f)=>(f.kind === 'flight' || f.kind === 'train') && f.flight_no.toUpperCase() === no && f.bookability === 'bookable_exact_date');
|
|
177
177
|
const routeMatched = sameNo.filter((f)=>(!claim.origin || f.route.origin === claim.origin || f.route.origin_airport === claim.origin) && (!claim.destination || f.route.destination === claim.destination || f.route.dest_airport === claim.destination) && (!claim.date || f.date === claim.date));
|
|
178
178
|
if (routeMatched.length > 0) return {
|
|
179
179
|
verdict: 'traceable',
|
|
@@ -310,6 +310,10 @@ export function defaultReviewBy(tripDate) {
|
|
|
310
310
|
return d.toISOString().slice(0, 10);
|
|
311
311
|
}
|
|
312
312
|
export function renderFlightFact(f) {
|
|
313
|
+
return renderFlightFactInner(f) + ` <!-- fact:${f.fact_id} -->`;
|
|
314
|
+
}
|
|
315
|
+
function renderFlightFactInner(f) {
|
|
316
|
+
const anchor = ` <!-- fact:${f.fact_id} -->`;
|
|
313
317
|
const route = `${f.route.origin}→${f.route.destination}`;
|
|
314
318
|
const evidence = `[${f.source}@${f.fetched_at} #${f.query_id}]`;
|
|
315
319
|
if (f.bookability === 'unavailable_exact_date') {
|
|
@@ -344,6 +348,61 @@ export function renderNightsLine(it) {
|
|
|
344
348
|
const flightLegs = it.od_segments.filter((s)=>s.mode === 'flight').reduce((acc, s)=>acc + s.legs, 0);
|
|
345
349
|
return `共 ${daysBetween(it.trip_start, it.trip_end) + 1} 天 ${totalNights} 晚 = ${hotelNights} 个酒店夜 + ${it.onboard_nights} 个机上夜;航班按 O&D ${flightOd} 段(共 ${flightLegs} 个 flight legs)`;
|
|
346
350
|
}
|
|
351
|
+
export function factsFromHotel(input) {
|
|
352
|
+
if (!input.checkIn || !input.checkOut) return [];
|
|
353
|
+
if (input.verdict !== 'hit' && input.verdict !== 'miss') return [];
|
|
354
|
+
const queryId = `hotel:${input.source}:${input.destination}:${input.checkIn}_${input.checkOut}`;
|
|
355
|
+
return [
|
|
356
|
+
{
|
|
357
|
+
schema: BOOKABLE_FACT_SCHEMA,
|
|
358
|
+
kind: 'hotel',
|
|
359
|
+
fact_id: makeFactId([
|
|
360
|
+
input.destination,
|
|
361
|
+
input.checkIn,
|
|
362
|
+
input.checkOut,
|
|
363
|
+
queryId
|
|
364
|
+
]),
|
|
365
|
+
destination: input.destination,
|
|
366
|
+
check_in: input.checkIn,
|
|
367
|
+
check_out: input.checkOut,
|
|
368
|
+
verdict: input.verdict,
|
|
369
|
+
bookability: input.verdict === 'hit' ? 'bookable_exact_date' : 'unavailable_exact_date',
|
|
370
|
+
options_masked: input.options,
|
|
371
|
+
source: input.source,
|
|
372
|
+
query_id: queryId,
|
|
373
|
+
fetched_at: input.fetchedAt,
|
|
374
|
+
as_of: input.fetchedAt.slice(0, 10),
|
|
375
|
+
tier: 'live_inventory'
|
|
376
|
+
}
|
|
377
|
+
];
|
|
378
|
+
}
|
|
379
|
+
export function hotelClaimVerdict(facts, claim) {
|
|
380
|
+
const scoped = facts.filter((f)=>f.kind === 'hotel' && f.destination === claim.destination && (!claim.check_in || f.check_in === claim.check_in) && (!claim.check_out || f.check_out === claim.check_out));
|
|
381
|
+
if (scoped.length === 0) {
|
|
382
|
+
return {
|
|
383
|
+
verdict: 'unverified',
|
|
384
|
+
reason: `酒店 claim(${claim.destination}${claim.check_in ? ` ${claim.check_in}→${claim.check_out ?? '?'}` : ''})未经 exact-date 源核验——先检索再写结论`
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
const hit = scoped.find((f)=>f.bookability === 'bookable_exact_date');
|
|
388
|
+
if (hit) return {
|
|
389
|
+
verdict: 'traceable',
|
|
390
|
+
fact: hit,
|
|
391
|
+
reason: 'exact-date 命中'
|
|
392
|
+
};
|
|
393
|
+
const latest = scoped[scoped.length - 1];
|
|
394
|
+
return {
|
|
395
|
+
verdict: 'not_in_source',
|
|
396
|
+
fact: latest,
|
|
397
|
+
reason: `该档期 exact-date 源正常返回 0 条(${latest.fetched_at})——不得写有房/可订`
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
export function renderHotelFact(f) {
|
|
401
|
+
if (f.verdict !== 'hit') {
|
|
402
|
+
return `- ${f.destination} ${f.check_in}→${f.check_out}:exact-date 源 0 条在架(不可订) <!-- fact:${f.fact_id} -->`;
|
|
403
|
+
}
|
|
404
|
+
return `- ${f.destination} ${f.check_in}→${f.check_out}:住宿已核验(在架 ${f.options_masked} 家;价格上游打码,真实价以落地页为准) <!-- fact:${f.fact_id} -->`;
|
|
405
|
+
}
|
|
347
406
|
|
|
348
407
|
|
|
349
408
|
//# sourceURL=ts/src/bookable-facts.ts
|