@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.
Files changed (176) hide show
  1. package/README.md +42 -16
  2. package/README.zh-CN.md +37 -15
  3. package/bin/gotry-bootstrap.js +309 -30
  4. package/bin/gotry-inner.js +74 -23
  5. package/bin/gotry-runtime-resolution.d.ts +1 -1
  6. package/bin/gotry-runtime-resolution.js +7 -8
  7. package/cordis.gotry-patch.yml +22 -8
  8. package/data/stations-12306-verify.json +524 -0
  9. package/dist/capabilities/agent-reach.js +4 -4
  10. package/dist/capabilities/anything.js +62 -29
  11. package/dist/capabilities/channel-health.js +103 -0
  12. package/dist/capabilities/channel-registry.js +213 -0
  13. package/dist/capabilities/doctor.js +307 -0
  14. package/dist/capabilities/effect.js +178 -5
  15. package/dist/capabilities/flyai.js +13 -0
  16. package/dist/capabilities/hbcli.js +28 -2
  17. package/dist/capabilities/session/adapters/ctrip-hotel.js +207 -0
  18. package/dist/capabilities/session/adapters/rail-12306.js +285 -0
  19. package/dist/capabilities/session/extension-bridge.js +14 -1
  20. package/dist/capabilities/session/extension-channel.js +4 -2
  21. package/dist/capabilities/session/extension-distribution.js +1 -1
  22. package/dist/capabilities/session-search.js +311 -0
  23. package/dist/capabilities/visa-policy.js +126 -0
  24. package/dist/scripts/agent-planning-turn-deadline-e2e.js +305 -0
  25. package/dist/scripts/agent-planning-turn-deadline-tests.js +281 -0
  26. package/dist/scripts/anything-tests.js +70 -21
  27. package/dist/scripts/benchmark-environment-bridge-e2e.js +139 -37
  28. package/dist/scripts/benchmark-environment-bridge-tests.js +1099 -306
  29. package/dist/scripts/booking-copilot-availability-ledger-binding-tests.js +26 -26
  30. package/dist/scripts/{booking-copilot-availability-policy-v2-tests.js → booking-copilot-availability-policy-tests.js} +115 -115
  31. package/dist/scripts/booking-copilot-crossrepo-fixture-server.js +41 -1
  32. package/dist/scripts/booking-copilot-dsh-core-proof-tests.js +75 -120
  33. package/dist/scripts/booking-copilot-dsh-planner-proof-tests.js +370 -61
  34. package/dist/scripts/booking-copilot-dsh-plugin-proof-tests.js +2 -2
  35. package/dist/scripts/booking-copilot-event-sequence-concurrency-proof-tests.js +7 -3
  36. package/dist/scripts/booking-copilot-gap-code-contract-proof-tests.js +15 -9
  37. package/dist/scripts/booking-copilot-operation-ledger-concurrency-proof-tests.js +19 -11
  38. package/dist/scripts/booking-copilot-receipt-ledger-concurrency-proof-tests.js +16 -52
  39. package/dist/scripts/booking-copilot-runtime-proof-tests.js +3953 -155
  40. package/dist/scripts/booking-copilot-server-proof-tests.js +58 -23
  41. package/dist/scripts/booking-copilot-startup-proof-tests.js +10 -119
  42. package/dist/scripts/booking-surface-contract-proof-tests.js +1154 -326
  43. package/dist/scripts/bootstrap-tests.js +113 -2
  44. package/dist/scripts/build-metrics-report.js +339 -0
  45. package/dist/scripts/channel-probe-tests.js +148 -0
  46. package/dist/scripts/channel-probe.js +252 -0
  47. package/dist/scripts/channel-registry-tests.js +262 -0
  48. package/dist/scripts/doctor-tests.js +175 -0
  49. package/dist/scripts/effect-tests.js +274 -1
  50. package/dist/scripts/extension-tests.js +129 -11
  51. package/dist/scripts/fact-gate-tests.js +91 -2
  52. package/dist/scripts/flyai-tests.js +17 -1
  53. package/dist/scripts/hbcli-e2e-tests.js +1 -3
  54. package/dist/scripts/hbcli-tests.js +38 -1
  55. package/dist/scripts/metrics-report-tests.js +288 -0
  56. package/dist/scripts/persona-surface-guard-tests.js +19 -0
  57. package/dist/scripts/session-tests.js +401 -1
  58. package/dist/scripts/smoke.js +100 -83
  59. package/dist/scripts/turn-handoff-collect-tests.js +216 -0
  60. package/dist/scripts/turn-handoff-collect.js +189 -0
  61. package/dist/scripts/turn-policy-tests.js +56 -0
  62. package/dist/scripts/typed-contract-canary.js +261 -0
  63. package/dist/scripts/visa-policy-tests.js +75 -0
  64. package/dist/scripts/wish-channel-gate-tests.js +111 -0
  65. package/dist/src/artifact-gate.js +91 -3
  66. package/dist/src/benchmark-agent-conformance.js +39 -16
  67. package/dist/src/benchmark-environment-bridge.js +316 -52
  68. package/dist/src/bookable-facts.js +62 -3
  69. package/dist/src/booking-surface/{availability-policy-v2.js → availability-policy.js} +89 -89
  70. package/dist/src/booking-surface/canonical-schema.js +8 -22
  71. package/dist/src/booking-surface/contracts.js +70 -19
  72. package/dist/src/booking-surface/dsh-planner.js +310 -177
  73. package/dist/src/booking-surface/dsh-plugin.js +3 -3
  74. package/dist/src/booking-surface/index.js +1 -5
  75. package/dist/src/booking-surface/profile.js +1 -1
  76. package/dist/src/booking-surface/runtime.js +1682 -247
  77. package/dist/src/booking-surface/server.js +371 -185
  78. package/dist/src/booking-surface/startup.js +17 -27
  79. package/dist/src/booking-surface/validation.js +270 -760
  80. package/dist/src/index.js +739 -150
  81. package/dist/src/turn-deadline.js +292 -0
  82. package/dist/src/turn-policy.js +146 -0
  83. package/dist/src/wish-pool.js +5 -0
  84. package/extension/background.js +23 -4
  85. package/extension/content-main.js +47 -9
  86. package/extension/manifest.json +27 -9
  87. package/package.json +7 -26
  88. package/schemas/booking.surface.schema.json +2593 -0
  89. package/ts/capabilities/agent-reach.ts +4 -4
  90. package/ts/capabilities/anything.ts +89 -38
  91. package/ts/capabilities/flyai.ts +21 -3
  92. package/ts/capabilities/hbcli.ts +53 -4
  93. package/ts/capabilities/session/action-cache.ts +1 -1
  94. package/ts/capabilities/session/adapters/ctrip-hotel.ts +238 -0
  95. package/ts/capabilities/session/adapters/rail-12306.ts +293 -0
  96. package/ts/capabilities/session/extension-bridge.ts +32 -2
  97. package/ts/capabilities/session/extension-channel.ts +9 -8
  98. package/ts/capabilities/session/extension-distribution.ts +3 -1
  99. package/ts/capabilities/session/health-watch.ts +1 -1
  100. package/ts/capabilities/session/read-guard.ts +1 -1
  101. package/ts/capabilities/session/wizard.ts +1 -1
  102. package/ts/capabilities/session-search.ts +323 -1
  103. package/ts/dsh-runtime/vendor/README.md +60 -0
  104. package/ts/dsh-runtime/vendor/dsh-map-tools/LICENSE +21 -0
  105. package/ts/dsh-runtime/vendor/dsh-map-tools/README.en.md +200 -0
  106. package/ts/dsh-runtime/vendor/dsh-map-tools/README.md +202 -0
  107. package/ts/dsh-runtime/vendor/dsh-map-tools/client/client.js +216 -0
  108. package/ts/dsh-runtime/vendor/dsh-map-tools/cordis.patch.yml +5 -0
  109. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/amap.js +371 -0
  110. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/nominatim.js +63 -0
  111. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/osrm.js +56 -0
  112. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/photon.js +55 -0
  113. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/config-file.js +87 -0
  114. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/config-route.js +126 -0
  115. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/config.js +16 -0
  116. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/index.js +109 -0
  117. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/settings-ns.js +27 -0
  118. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/tools/geocode.js +127 -0
  119. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/tools/poi.js +84 -0
  120. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/tools/routes.js +170 -0
  121. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/amap.d.ts +53 -0
  122. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/nominatim.d.ts +17 -0
  123. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/osrm.d.ts +15 -0
  124. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/photon.d.ts +22 -0
  125. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/config-file.d.ts +33 -0
  126. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/config-route.d.ts +20 -0
  127. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/config.d.ts +23 -0
  128. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/index.d.ts +18 -0
  129. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/settings-ns.d.ts +14 -0
  130. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/tools/geocode.d.ts +11 -0
  131. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/tools/poi.d.ts +9 -0
  132. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/tools/routes.d.ts +16 -0
  133. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/types.d.ts +55 -0
  134. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types.js +16 -0
  135. package/ts/dsh-runtime/vendor/dsh-map-tools/package.json +91 -0
  136. package/ts/package.json +9 -0
  137. package/ts/scripts/turn-handoff-collect.ts +178 -0
  138. package/ts/src/artifact-gate.ts +82 -2
  139. package/ts/src/benchmark-agent-conformance.ts +54 -15
  140. package/ts/src/benchmark-environment-bridge.ts +234 -46
  141. package/ts/src/bookable-facts.ts +109 -5
  142. package/ts/src/booking-saga.ts +1 -1
  143. package/ts/src/booking-surface/{availability-policy-v2.ts → availability-policy.ts} +124 -125
  144. package/ts/src/booking-surface/canonical-schema.js +8 -22
  145. package/ts/src/booking-surface/contracts.ts +278 -239
  146. package/ts/src/booking-surface/dsh-planner.ts +333 -194
  147. package/ts/src/booking-surface/dsh-plugin.js +3 -3
  148. package/ts/src/booking-surface/index.ts +1 -5
  149. package/ts/src/booking-surface/profile.ts +11 -11
  150. package/ts/src/booking-surface/runtime.ts +1351 -351
  151. package/ts/src/booking-surface/server.ts +282 -175
  152. package/ts/src/booking-surface/startup.ts +36 -48
  153. package/ts/src/booking-surface/validation.ts +194 -442
  154. package/ts/src/contracts.ts +1 -1
  155. package/ts/src/index.ts +492 -172
  156. package/ts/src/loop.ts +1 -1
  157. package/ts/src/state-ledger.ts +1 -1
  158. package/ts/src/turn-deadline.ts +365 -0
  159. package/ts/src/turn-policy.ts +154 -0
  160. package/ts/src/wish-pool.ts +17 -4
  161. package/dist/scripts/agent-planning-budget-e2e.js +0 -227
  162. package/dist/scripts/agent-planning-budget-tests.js +0 -173
  163. package/dist/scripts/booking-copilot-v2-runtime-proof-tests.js +0 -3935
  164. package/dist/scripts/booking-surface-v2-contract-proof-tests.js +0 -1174
  165. package/dist/src/booking-surface/contracts-v2.js +0 -89
  166. package/dist/src/booking-surface/runtime-v2.js +0 -1771
  167. package/dist/src/booking-surface/server-v2.js +0 -334
  168. package/dist/src/booking-surface/validation-v2.js +0 -319
  169. package/dist/src/tool-budget.js +0 -136
  170. package/schemas/booking.surface.v1.schema.json +0 -927
  171. package/schemas/booking.surface.v2.schema.json +0 -61
  172. package/ts/src/booking-surface/contracts-v2.ts +0 -118
  173. package/ts/src/booking-surface/runtime-v2.ts +0 -1466
  174. package/ts/src/booking-surface/server-v2.ts +0 -247
  175. package/ts/src/booking-surface/validation-v2.ts +0 -205
  176. 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 { defineTool } from '@deepseek-ai/dsh-tools';
4
+ import { assertSupportedJsonSchema, validateJsonSchemaValue, ToolArgsError } from '@deepseek-ai/dsh-tools';
5
5
  import { validateTerminalOutputConfig } from './benchmark-agent-conformance.js';
6
- const SCHEMA_VERSION = 'gotry_benchmark_environment_bridge_v2';
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 allowedOutputKeys(value, allowedTools) {
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
- const keys = Object.keys(value);
50
- return keys.length > 0 && keys.length <= 64 && keys.every((tool)=>allowedTools.includes(tool) && identifiers(value[tool]));
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
- const extendedKeys = [
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)) continue;
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
- register(defineTool({
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 query = plainObject(args?.query) ? args.query : {};
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.allowed_tools
472
+ tools: bridge.tools
244
473
  });
245
- if (query.action !== 'call') return jsonObject({
246
- ok: false,
247
- error: 'invalid_action'
474
+ if (query.action === 'errors') return jsonObject({
475
+ ok: true,
476
+ errors: BRIDGE_ERROR_CONTRACT
248
477
  });
249
- if (typeof query.tool !== 'string' || !bridge.allowed_tools.includes(query.tool)) {
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 (query.arguments !== undefined && !plainObject(query.arguments)) {
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
- query.tool,
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
- const visibleResult = parsed.result ?? parsed;
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
- const outputKeys = bridge.allowed_output_keys && Object.hasOwn(bridge.allowed_output_keys, query.tool) ? bridge.allowed_output_keys[query.tool] : undefined;
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 !== 'policy' && f.route.origin === origin && f.route.destination === destination && f.date === date);
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 !== 'policy' && f.flight_no.toUpperCase() === no && f.bookability === 'bookable_exact_date');
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