@danceiny/gotry 0.0.1-rc.19 → 0.0.1-rc.21

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 (71) hide show
  1. package/README.md +14 -3
  2. package/README.zh-CN.md +15 -4
  3. package/bin/gotry-bootstrap.js +136 -45
  4. package/bin/gotry-inner.js +11 -6
  5. package/cordis.gotry-patch.yml +19 -8
  6. package/dist/capabilities/anything.js +62 -29
  7. package/dist/capabilities/doctor.js +113 -19
  8. package/dist/capabilities/hbcli.js +28 -2
  9. package/dist/capabilities/session/health-watch.js +10 -3
  10. package/dist/scripts/agent-planning-turn-deadline-e2e.js +2 -1
  11. package/dist/scripts/anything-tests.js +70 -21
  12. package/dist/scripts/benchmark-environment-bridge-e2e.js +178 -33
  13. package/dist/scripts/benchmark-environment-bridge-tests.js +1480 -271
  14. package/dist/scripts/booking-copilot-dsh-planner-proof-tests.js +211 -2
  15. package/dist/scripts/booking-copilot-runtime-proof-tests.js +132 -2
  16. package/dist/scripts/bootstrap-tests.js +30 -3
  17. package/dist/scripts/doctor-tests.js +55 -1
  18. package/dist/scripts/hbcli-e2e-tests.js +1 -1
  19. package/dist/scripts/hbcli-tests.js +38 -1
  20. package/dist/scripts/health-watch-cli.js +9 -1
  21. package/dist/scripts/map-tools-vendor-package-proof.js +276 -0
  22. package/dist/scripts/persona-surface-guard-tests.js +8 -3
  23. package/dist/src/benchmark-agent-conformance.js +187 -17
  24. package/dist/src/benchmark-environment-bridge.js +287 -102
  25. package/dist/src/booking-surface/dsh-planner.js +24 -8
  26. package/dist/src/index.js +5 -2
  27. package/extension/manifest.json +2 -2
  28. package/package.json +3 -1
  29. package/ts/capabilities/anything.ts +89 -38
  30. package/ts/capabilities/hbcli.ts +53 -4
  31. package/ts/capabilities/session/health-watch.ts +9 -2
  32. package/ts/dsh-runtime/vendor/README.md +60 -0
  33. package/ts/dsh-runtime/vendor/dsh-map-tools/LICENSE +21 -0
  34. package/ts/dsh-runtime/vendor/dsh-map-tools/README.en.md +200 -0
  35. package/ts/dsh-runtime/vendor/dsh-map-tools/README.md +202 -0
  36. package/ts/dsh-runtime/vendor/dsh-map-tools/client/client.js +216 -0
  37. package/ts/dsh-runtime/vendor/dsh-map-tools/cordis.patch.yml +5 -0
  38. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/amap.js +371 -0
  39. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/nominatim.js +63 -0
  40. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/osrm.js +56 -0
  41. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/photon.js +55 -0
  42. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/config-file.js +87 -0
  43. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/config-route.js +126 -0
  44. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/config.js +16 -0
  45. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/index.js +109 -0
  46. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/settings-ns.js +27 -0
  47. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/tools/geocode.js +127 -0
  48. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/tools/poi.js +84 -0
  49. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/tools/routes.js +170 -0
  50. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/amap.d.ts +53 -0
  51. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/nominatim.d.ts +17 -0
  52. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/osrm.d.ts +15 -0
  53. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/photon.d.ts +22 -0
  54. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/config-file.d.ts +33 -0
  55. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/config-route.d.ts +20 -0
  56. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/config.d.ts +23 -0
  57. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/index.d.ts +18 -0
  58. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/settings-ns.d.ts +14 -0
  59. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/tools/geocode.d.ts +11 -0
  60. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/tools/poi.d.ts +9 -0
  61. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/tools/routes.d.ts +16 -0
  62. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/types.d.ts +55 -0
  63. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types.js +16 -0
  64. package/ts/dsh-runtime/vendor/dsh-map-tools/package.json +91 -0
  65. package/ts/package.json +17 -1
  66. package/ts/scripts/map-tools-vendor-package-proof.ts +268 -0
  67. package/ts/src/benchmark-agent-conformance.ts +175 -13
  68. package/ts/src/benchmark-environment-bridge.ts +220 -66
  69. package/ts/src/booking-surface/dsh-planner.ts +25 -13
  70. package/ts/src/index.ts +4 -2
  71. package/ts/src/turn-deadline.ts +4 -0
@@ -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_v4';
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,32 @@ 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
+ ]);
23
43
  function plainObject(value) {
24
44
  return typeof value === 'object' && value !== null && !Array.isArray(value);
25
45
  }
46
+ function deepFreezeJson(value) {
47
+ if (!value || typeof value !== 'object' || Object.isFrozen(value)) return value;
48
+ for (const child of Object.values(value))deepFreezeJson(child);
49
+ return Object.freeze(value);
50
+ }
26
51
  function existingExecutable(value) {
27
52
  if (typeof value !== 'string' || !isAbsolute(value)) return false;
28
53
  try {
@@ -41,13 +66,121 @@ function existingDirectory(value) {
41
66
  return false;
42
67
  }
43
68
  }
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));
69
+ function identifiers(value, allowEmpty = false) {
70
+ 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));
71
+ }
72
+ function exactKeys(value, allowed) {
73
+ return Object.keys(value).every((key)=>allowed.includes(key));
74
+ }
75
+ function validDescription(value, required = false) {
76
+ if (value === undefined) return !required;
77
+ return typeof value === 'string' && (!required || value.trim().length > 0) && value.length <= INPUT_SCHEMA_MAX_DESCRIPTION_LENGTH && !/[\u0000-\u001f\u007f]/.test(value);
46
78
  }
47
- function allowedOutputKeys(value, allowedTools) {
79
+ function scalarMatches(type, value) {
80
+ if (type === 'string') return typeof value === 'string' && value.length <= 512;
81
+ if (type === 'number') return typeof value === 'number' && Number.isFinite(value);
82
+ if (type === 'integer') return typeof value === 'number' && Number.isSafeInteger(value);
83
+ if (type === 'boolean') return typeof value === 'boolean';
84
+ return type === 'null' && value === null;
85
+ }
86
+ function validInputSchema(value) {
48
87
  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]));
88
+ let serialized;
89
+ try {
90
+ serialized = JSON.stringify(value);
91
+ } catch {
92
+ return false;
93
+ }
94
+ if (Buffer.byteLength(serialized, 'utf8') > INPUT_SCHEMA_MAX_BYTES) return false;
95
+ const pending = [
96
+ {
97
+ node: value,
98
+ depth: 0
99
+ }
100
+ ];
101
+ const seen = new Set();
102
+ let nodes = 0;
103
+ let properties = 0;
104
+ let enumValues = 0;
105
+ while(pending.length > 0){
106
+ const { node, depth } = pending.pop();
107
+ if (seen.has(node) || ++nodes > INPUT_SCHEMA_MAX_NODES || depth > INPUT_SCHEMA_MAX_DEPTH) return false;
108
+ seen.add(node);
109
+ if (!INPUT_SCHEMA_TYPES.has(String(node.type)) || !validDescription(node.description)) return false;
110
+ if (node.type === 'object') {
111
+ if (!exactKeys(node, [
112
+ 'type',
113
+ 'description',
114
+ 'properties',
115
+ 'required',
116
+ 'additionalProperties'
117
+ ]) || !plainObject(node.properties) || !Array.isArray(node.required) || node.additionalProperties !== false) return false;
118
+ const propertyEntries = Object.entries(node.properties);
119
+ properties += propertyEntries.length;
120
+ if (properties > INPUT_SCHEMA_MAX_PROPERTIES || propertyEntries.some(([key, child])=>!IDENTIFIER.test(key) || !plainObject(child))) return false;
121
+ const required = node.required;
122
+ if (required.length > propertyEntries.length || new Set(required).size !== required.length || required.some((key)=>typeof key !== 'string' || !Object.hasOwn(node.properties, key))) return false;
123
+ for (const [, child] of propertyEntries)pending.push({
124
+ node: child,
125
+ depth: depth + 1
126
+ });
127
+ continue;
128
+ }
129
+ if (node.type === 'array') {
130
+ if (!exactKeys(node, [
131
+ 'type',
132
+ 'description',
133
+ 'items'
134
+ ]) || !plainObject(node.items)) return false;
135
+ pending.push({
136
+ node: node.items,
137
+ depth: depth + 1
138
+ });
139
+ continue;
140
+ }
141
+ if (!exactKeys(node, [
142
+ 'type',
143
+ 'description',
144
+ 'enum',
145
+ 'const'
146
+ ]) || Object.hasOwn(node, 'enum') && Object.hasOwn(node, 'const')) return false;
147
+ if (Object.hasOwn(node, 'const') && !scalarMatches(String(node.type), node.const)) return false;
148
+ if (Object.hasOwn(node, 'enum')) {
149
+ if (!Array.isArray(node.enum) || node.enum.length === 0 || node.enum.length > 64) return false;
150
+ enumValues += node.enum.length;
151
+ 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;
152
+ }
153
+ }
154
+ return value.type === 'object';
155
+ }
156
+ function toolDescriptors(value) {
157
+ if (!Array.isArray(value) || value.length === 0 || value.length > 64) return false;
158
+ const names = new Set();
159
+ return value.every((item)=>{
160
+ if (!plainObject(item)) return false;
161
+ const keys = Object.keys(item).sort();
162
+ const required = [
163
+ 'description',
164
+ 'domain_outcomes',
165
+ 'input_schema',
166
+ 'name'
167
+ ];
168
+ if (JSON.stringify(keys) !== JSON.stringify([
169
+ ...required,
170
+ 'output_keys'
171
+ ].sort())) return false;
172
+ if (typeof item.name !== 'string' || !IDENTIFIER.test(item.name) || names.has(item.name)) return false;
173
+ names.add(item.name);
174
+ if (!validDescription(item.description, true) || !validInputSchema(item.input_schema)) return false;
175
+ if (!Array.isArray(item.domain_outcomes) || item.domain_outcomes.length > 64) return false;
176
+ const outcomeKeys = new Set();
177
+ if (!item.domain_outcomes.every((outcome)=>plainObject(outcome) && JSON.stringify(Object.keys(outcome).sort()) === JSON.stringify([
178
+ 'code',
179
+ 'recovery',
180
+ 'status'
181
+ ]) && (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;
182
+ return identifiers(item.output_keys);
183
+ });
51
184
  }
52
185
  function argvPrefix(value) {
53
186
  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 +202,7 @@ function validate(value) {
69
202
  const baseKeys = [
70
203
  ...CONFIG_KEYS
71
204
  ].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;
205
+ 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
206
  return true;
81
207
  }
82
208
  export function loadBenchmarkEnvironmentConfig(path) {
@@ -87,7 +213,7 @@ export function loadBenchmarkEnvironmentConfig(path) {
87
213
  if (typeof getuid !== 'function' || metadata.uid !== getuid()) return null;
88
214
  if ((metadata.mode & 0o022) !== 0) return null;
89
215
  const parsed = JSON.parse(readFileSync(path, 'utf8'));
90
- return validate(parsed) ? parsed : null;
216
+ return validate(parsed) ? deepFreezeJson(parsed) : null;
91
217
  } catch {
92
218
  return null;
93
219
  }
@@ -116,6 +242,48 @@ const FORBIDDEN_OUTPUT_KEY_FRAGMENTS = [
116
242
  'reward',
117
243
  'score'
118
244
  ];
245
+ export const BRIDGE_ERROR_CONTRACT = Object.freeze({
246
+ invalid_action: {
247
+ recoverable: true,
248
+ remedy: 'action 只允许 tools、call 或 errors'
249
+ },
250
+ disallowed_tool: {
251
+ recoverable: true,
252
+ remedy: '先 action=tools 列出 allowed_tools 清单,再从清单内选工具'
253
+ },
254
+ invalid_arguments: {
255
+ recoverable: true,
256
+ remedy: 'arguments 必须是可序列化 JSON 对象(≤64KB、深度≤12);收窄后重试'
257
+ },
258
+ timed_out: {
259
+ recoverable: true,
260
+ remedy: '上游超时;可原样重试一次或换其他工具'
261
+ },
262
+ output_truncated: {
263
+ recoverable: true,
264
+ remedy: '输出超出上限;让被调工具收窄查询范围后重试'
265
+ },
266
+ invalid_json: {
267
+ recoverable: true,
268
+ remedy: '上游输出不是合法 JSON;重试一次或换工具'
269
+ },
270
+ invalid_output: {
271
+ recoverable: true,
272
+ remedy: '上游输出结构不符;重试或换工具'
273
+ },
274
+ runner_failed: {
275
+ recoverable: true,
276
+ remedy: '上游进程非零退出;可重试一次,持续失败换工具'
277
+ },
278
+ spawn_failed: {
279
+ recoverable: false,
280
+ remedy: '可执行文件不可用——环境问题,调用方不可恢复'
281
+ },
282
+ forbidden_output: {
283
+ recoverable: false,
284
+ remedy: '输出含未授权键(策略边界)——不可恢复,换工具或放弃'
285
+ }
286
+ });
119
287
  function inspectOutput(value) {
120
288
  const pending = [
121
289
  {
@@ -152,7 +320,8 @@ function inspectAllowedOutput(value, allowedKeys) {
152
320
  const pending = [
153
321
  {
154
322
  value,
155
- depth: 0
323
+ depth: 0,
324
+ coveredByDeclaredKey: false
156
325
  }
157
326
  ];
158
327
  let nodes = 0;
@@ -162,16 +331,21 @@ function inspectAllowedOutput(value, allowedKeys) {
162
331
  if (Array.isArray(current.value)) {
163
332
  for (const child of current.value)pending.push({
164
333
  value: child,
165
- depth: current.depth + 1
334
+ depth: current.depth + 1,
335
+ coveredByDeclaredKey: current.coveredByDeclaredKey
166
336
  });
167
337
  continue;
168
338
  }
169
- if (!plainObject(current.value)) continue;
339
+ if (!plainObject(current.value)) {
340
+ if (!current.coveredByDeclaredKey) return false;
341
+ continue;
342
+ }
170
343
  for (const [key, child] of Object.entries(current.value)){
171
344
  if (!allowed.has(key)) return false;
172
345
  pending.push({
173
346
  value: child,
174
- depth: current.depth + 1
347
+ depth: current.depth + 1,
348
+ coveredByDeclaredKey: true
175
349
  });
176
350
  }
177
351
  }
@@ -211,56 +385,14 @@ function serializedArguments(value) {
211
385
  };
212
386
  }
213
387
  }
214
- export const BRIDGE_ERROR_CONTRACT = Object.freeze({
215
- invalid_action: {
216
- recoverable: true,
217
- remedy: 'action 只允许 tools 或 call'
218
- },
219
- disallowed_tool: {
220
- recoverable: true,
221
- remedy: '先 action=tools 列出 allowed_tools 清单,再从清单内选工具'
222
- },
223
- invalid_arguments: {
224
- recoverable: true,
225
- remedy: 'arguments 必须是可序列化 JSON 对象(≤64KB、深度≤12);收窄后重试'
226
- },
227
- timed_out: {
228
- recoverable: true,
229
- remedy: '上游超时;可原样重试一次或换其他工具'
230
- },
231
- output_truncated: {
232
- recoverable: true,
233
- remedy: '输出超出上限;让被调工具收窄查询范围后重试'
234
- },
235
- invalid_json: {
236
- recoverable: true,
237
- remedy: '上游输出不是合法 JSON;重试一次或换工具'
238
- },
239
- invalid_output: {
240
- recoverable: true,
241
- remedy: '上游输出结构不符(如原始字符串);重试或换工具'
242
- },
243
- runner_failed: {
244
- recoverable: true,
245
- remedy: '上游进程非零退出;可重试一次,持续失败换工具'
246
- },
247
- spawn_failed: {
248
- recoverable: false,
249
- remedy: '可执行文件不可用——环境问题,调用方不可恢复'
250
- },
251
- forbidden_output: {
252
- recoverable: false,
253
- remedy: '输出含未授权键(策略边界)——不可恢复,换工具或放弃'
254
- }
255
- });
256
388
  export function registerBenchmarkEnvironmentBridge(path, register, subprocess) {
257
389
  const bridge = loadBenchmarkEnvironmentConfig(path);
258
390
  if (!bridge) throw new Error('benchmark environment bridge configuration unavailable');
259
391
  if (!subprocess) throw new Error('benchmark environment bridge subprocess unavailable');
260
- register(defineTool({
261
- name: 'gotry_benchmark_environment',
262
- description: 'When a prompt asks to run agent_env.cli, use action=call here with the mapped tool; arbitrary shell is not exposed. ' + 'Recoverable domain-error contract: every failure returns { ok:false, error:<code>, ... } from a closed vocabulary; ' + 'call action=errors to fetch the per-code recoverable flag and remedy before retrying.',
263
- parameters: {
392
+ const parameters = deepFreezeJson({
393
+ type: 'object',
394
+ description: 'Flat benchmark bridge wire: choose an action, then provide the mapped tool and its arguments.',
395
+ properties: {
264
396
  action: {
265
397
  type: 'string',
266
398
  enum: [
@@ -268,23 +400,31 @@ export function registerBenchmarkEnvironmentBridge(path, register, subprocess) {
268
400
  'call',
269
401
  'errors'
270
402
  ],
271
- required: true,
272
- description: 'tools=列出可调工具;call=执行被映射工具;errors=拉取可恢复错误契约表'
403
+ description: 'tools=列出可调工具;call=执行工具;errors=拉取错误契约'
273
404
  },
274
405
  tool: {
275
406
  type: 'string',
276
- description: '被调工具名(必须在 allowed_tools 清单内;action=call 时必填)'
407
+ enum: bridge.tools.map((item)=>item.name),
408
+ description: 'action=call 时选择的冻结工具名'
277
409
  },
278
410
  arguments: {
279
411
  type: 'object',
280
412
  additionalProperties: true,
281
- description: '传给被调工具的参数对象(action=call 时可选;≤64KB、深度≤12)'
413
+ description: 'action=call 时传给工具的参数对象'
282
414
  }
283
415
  },
416
+ required: [
417
+ 'action'
418
+ ],
419
+ additionalProperties: false
420
+ });
421
+ assertSupportedJsonSchema(parameters);
422
+ const definition = {
423
+ name: 'gotry_benchmark_environment',
424
+ description: 'When a prompt asks to run agent_env.cli, use action=call here with the mapped tool; arbitrary shell is not exposed.',
425
+ parameters,
284
426
  output: {
285
- schema: {
286
- type: 'json'
287
- },
427
+ schema: {},
288
428
  render: (_args, value)=>[
289
429
  {
290
430
  type: 'text',
@@ -293,33 +433,56 @@ export function registerBenchmarkEnvironmentBridge(path, register, subprocess) {
293
433
  ]
294
434
  },
295
435
  async execute (args) {
296
- const action = args?.action;
297
- if (action === 'errors') return jsonObject({
298
- ok: true,
299
- errors: BRIDGE_ERROR_CONTRACT
300
- });
301
- if (action === 'tools') return jsonObject({
302
- ok: true,
303
- tools: bridge.allowed_tools
304
- });
305
- if (action !== 'call') return jsonObject({
306
- ok: false,
307
- error: 'invalid_action'
308
- });
436
+ const violations = validateJsonSchemaValue(parameters, args, 'arguments');
437
+ if (violations.length > 0) throw new ToolArgsError(violations);
309
438
  const query = args;
310
- if (typeof query.tool !== 'string' || !bridge.allowed_tools.includes(query.tool)) {
439
+ const tool = query.tool;
440
+ const descriptor = bridge.tools.find((item)=>item.name === tool);
441
+ const keys = Object.keys(query);
442
+ if (query.action === 'tools') {
443
+ if (keys.length !== 1) throw new ToolArgsError([
444
+ 'arguments: tools action accepts no tool or arguments'
445
+ ]);
446
+ return jsonObject({
447
+ ok: true,
448
+ tools: bridge.tools
449
+ });
450
+ }
451
+ if (query.action === 'errors') {
452
+ if (keys.length !== 1) throw new ToolArgsError([
453
+ 'arguments: errors action accepts no tool or arguments'
454
+ ]);
455
+ return jsonObject({
456
+ ok: true,
457
+ errors: BRIDGE_ERROR_CONTRACT
458
+ });
459
+ }
460
+ if (query.action !== 'call') {
461
+ return jsonObject({
462
+ ok: false,
463
+ error: 'invalid_action'
464
+ });
465
+ }
466
+ if (keys.length !== 3 || !Object.hasOwn(query, 'tool') || !Object.hasOwn(query, 'arguments')) {
467
+ throw new ToolArgsError([
468
+ 'arguments: call action requires exactly tool and arguments'
469
+ ]);
470
+ }
471
+ if (typeof tool !== 'string' || !descriptor) {
311
472
  return jsonObject({
312
473
  ok: false,
313
474
  error: 'disallowed_tool'
314
475
  });
315
476
  }
316
- if (query.arguments !== undefined && !plainObject(query.arguments)) {
477
+ if (!plainObject(query.arguments)) {
317
478
  return jsonObject({
318
479
  ok: false,
319
480
  error: 'invalid_arguments'
320
481
  });
321
482
  }
322
- const callArguments = query.arguments === undefined ? {} : query.arguments;
483
+ const callArguments = query.arguments;
484
+ const argumentViolations = validateJsonSchemaValue(descriptor.input_schema, callArguments, 'arguments');
485
+ if (argumentViolations.length > 0) throw new ToolArgsError(argumentViolations);
323
486
  const serialized = serializedArguments(callArguments);
324
487
  if (serialized.reason) return jsonObject({
325
488
  ok: false,
@@ -352,7 +515,7 @@ export function registerBenchmarkEnvironmentBridge(path, register, subprocess) {
352
515
  bridge.executable,
353
516
  ...bridge.argv_prefix,
354
517
  'call',
355
- query.tool,
518
+ tool,
356
519
  serialized.json
357
520
  ],
358
521
  cwd: bridge.cwd,
@@ -402,13 +565,36 @@ export function registerBenchmarkEnvironmentBridge(path, register, subprocess) {
402
565
  ok: false,
403
566
  error: 'invalid_output'
404
567
  });
405
- const visibleResult = parsed.result ?? parsed;
568
+ if (parsed.schema_version !== BENCHMARK_TOOL_RESULT_SCHEMA_VERSION || typeof parsed.status !== 'string') {
569
+ return jsonObject({
570
+ ok: false,
571
+ error: 'invalid_output'
572
+ });
573
+ }
574
+ if (parsed.status === 'miss' || parsed.status === 'error') {
575
+ 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({
576
+ ok: false,
577
+ error: 'invalid_output'
578
+ });
579
+ if (Object.keys(parsed).sort().join(',') !== 'code,recovery,schema_version,status') return jsonObject({
580
+ ok: false,
581
+ error: 'invalid_output'
582
+ });
583
+ return jsonObject({
584
+ ok: true,
585
+ outcome: parsed
586
+ });
587
+ }
588
+ if (parsed.status !== 'ok' || !Object.hasOwn(parsed, 'result') || Object.keys(parsed).sort().join(',') !== 'result,schema_version,status') return jsonObject({
589
+ ok: false,
590
+ error: 'invalid_output'
591
+ });
592
+ const visibleResult = parsed.result;
406
593
  if (!plainObject(visibleResult) && !Array.isArray(visibleResult)) return jsonObject({
407
594
  ok: false,
408
595
  error: 'invalid_output'
409
596
  });
410
- const outputKeys = bridge.allowed_output_keys && Object.hasOwn(bridge.allowed_output_keys, query.tool) ? bridge.allowed_output_keys[query.tool] : undefined;
411
- if (outputKeys && !inspectAllowedOutput(visibleResult, outputKeys)) return jsonObject({
597
+ if (!inspectAllowedOutput(visibleResult, descriptor.output_keys)) return jsonObject({
412
598
  ok: false,
413
599
  error: 'forbidden_output'
414
600
  });
@@ -429,12 +615,11 @@ export function registerBenchmarkEnvironmentBridge(path, register, subprocess) {
429
615
  clearTimeout(timer);
430
616
  }
431
617
  }
432
- }));
618
+ };
619
+ register(definition);
433
620
  return Object.freeze({
434
621
  toolName: 'gotry_benchmark_environment',
435
- allowedTools: Object.freeze([
436
- ...bridge.allowed_tools
437
- ]),
622
+ allowedTools: Object.freeze(bridge.tools.map((item)=>item.name)),
438
623
  terminal: Object.freeze({
439
624
  ...bridge.terminal_output
440
625
  })
@@ -384,9 +384,11 @@ function recoverFinalResponseDecision(response, task) {
384
384
  }).slice(0, 600));
385
385
  return null;
386
386
  }
387
- repairPlannerFactRefs(action);
387
+ const repairedRefs = repairPlannerFactRefs(action);
388
+ const repairedValidation = validateBookingReadAction(action);
389
+ if (!repairedValidation.ok) return null;
388
390
  try {
389
- assertPlannerSafeRefs(action);
391
+ assertPlannerSafeRefs(action, repairedRefs);
390
392
  } catch (error) {
391
393
  console.error('[booking-copilot] finalResponse recovery rejected (unsafe ref):', JSON.stringify({
392
394
  actionId: action.actionId,
@@ -411,10 +413,17 @@ function recoverFinalResponseDecision(response, task) {
411
413
  const PLANNER_SAFE_REF_PATTERN = /^[A-Za-z0-9][A-Za-z0-9:._-]*$/;
412
414
  function repairPlannerFactRefs(action) {
413
415
  const factRefs = action.factRefs;
414
- if (!Array.isArray(factRefs)) return;
415
- action.factRefs = factRefs.map((ref)=>typeof ref === 'string' ? ref.replace(/#/g, ':').replace(/[^A-Za-z0-9:._-]/g, '.') : ref);
416
+ const repairedRefs = new Set();
417
+ if (!Array.isArray(factRefs)) return repairedRefs;
418
+ action.factRefs = factRefs.map((ref)=>{
419
+ if (typeof ref !== 'string' || PLANNER_SAFE_REF_PATTERN.test(ref)) return ref;
420
+ const alias = `modelref:${createHash('sha256').update(ref, 'utf8').digest('hex')}`;
421
+ repairedRefs.add(alias);
422
+ return alias;
423
+ });
424
+ return repairedRefs;
416
425
  }
417
- function assertPlannerSafeRefs(action) {
426
+ function assertPlannerSafeRefs(action, repairedRefs) {
418
427
  const actionId = action.actionId;
419
428
  if (typeof actionId !== 'string' || !PLANNER_SAFE_REF_PATTERN.test(actionId)) {
420
429
  throw new Error(`planner_invalid_action:unsafe_action_id:${String(actionId).slice(0, 60)}`);
@@ -422,7 +431,7 @@ function assertPlannerSafeRefs(action) {
422
431
  const factRefs = action.factRefs;
423
432
  if (Array.isArray(factRefs)) {
424
433
  for (const ref of factRefs){
425
- if (typeof ref !== 'string' || !PLANNER_SAFE_REF_PATTERN.test(ref)) {
434
+ if (typeof ref !== 'string' || !PLANNER_SAFE_REF_PATTERN.test(ref) || ref.startsWith('modelref:') && !repairedRefs.has(ref) || ref.length > 512) {
426
435
  throw new Error(`planner_invalid_action:unsafe_fact_ref:${String(ref).slice(0, 60)}`);
427
436
  }
428
437
  }
@@ -483,8 +492,15 @@ function parseToolDecision(event, task) {
483
492
  }).slice(0, 1200));
484
493
  throw new Error(`planner_invalid_action:${validation.errors.join('; ')}`);
485
494
  }
486
- repairPlannerFactRefs(decision.action);
487
- assertPlannerSafeRefs(decision.action);
495
+ const repairedRefs = repairPlannerFactRefs(decision.action);
496
+ const repairedValidation = validateBookingReadAction(decision.action);
497
+ if (!repairedValidation.ok) {
498
+ console.error(`[booking-copilot] repaired action rejected:`, JSON.stringify({
499
+ errors: repairedValidation.errors.slice(0, 8)
500
+ }).slice(0, 800));
501
+ throw new Error(`planner_invalid_action:${repairedValidation.errors.join('; ')}`);
502
+ }
503
+ assertPlannerSafeRefs(decision.action, repairedRefs);
488
504
  const action = decision.action;
489
505
  const capability = TOOL_TO_CAPABILITY.get(name);
490
506
  if (!capability || !actionsForEmbeddedCapability(capability).includes(action.kind)) {
package/dist/src/index.js CHANGED
@@ -1599,14 +1599,17 @@ export function apply(ctx, config) {
1599
1599
  }
1600
1600
  const itpA = await interpretEffect({
1601
1601
  effect: 'ANYTHING_SEARCH',
1602
- params: q
1602
+ params: {
1603
+ ...q,
1604
+ hbcliBin: config.hbcliBin
1605
+ }
1603
1606
  });
1604
1607
  if (!itpA.result) return declinedObservation('ANYTHING_SEARCH', itpA.trace);
1605
1608
  const r = itpA.result;
1606
1609
  const dir = await ensureStateDir(config.stateRoot);
1607
1610
  await recordLatency(join(dir, 'bridge-latency.jsonl'), Date.now() - started, `anything:${r.via}`).catch(()=>{});
1608
1611
  const top5 = (r.hits ?? []).slice(0, 5);
1609
- const summary = r.verdict === 'hit' ? `${q.keyword} → hit (${r.hits?.length ?? 0} 候选项)\n${top5.map((h, i)=>` ${i + 1}. [${h.type}] ${h.name}${h.latitude !== undefined && h.longitude !== undefined ? ` @ (${h.latitude.toFixed(3)},${h.longitude.toFixed(3)})` : ''}`).join('\n')}\n${r.evidence}` : r.verdict === 'miss' ? `${q.keyword} → miss (酒店-be 一切正常但无候选)\n${r.evidence}` : `${q.keyword} → unavailable (${r.error})\n${r.evidence}`;
1612
+ const summary = r.verdict === 'hit' ? `${q.keyword} → hit (${r.hits?.length ?? 0} 候选项)\n${top5.map((h, i)=>` ${i + 1}. [${h.type}] ${h.name}${h.star ? ` ${h.star}★` : ''}${h.latitude !== undefined && h.longitude !== undefined ? ` @ (${h.latitude.toFixed(3)},${h.longitude.toFixed(3)})` : ''}${h.hotelId ? ` hotelId=${h.hotelId}` : h.destinationId ? ` destinationId=${h.destinationId}` : ''}`).join('\n')}\n${r.evidence}` : r.verdict === 'miss' ? `${q.keyword} → miss (酒店-be 一切正常但无候选)\n${r.evidence}` : `${q.keyword} → unavailable (${r.error})\n${r.evidence}`;
1610
1613
  return JSON.parse(JSON.stringify({
1611
1614
  ok: r.ok,
1612
1615
  verdict: r.verdict,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "manifest_version": 3,
3
3
  "name": "GoTry Session Bridge",
4
- "version": "0.0.1.19",
4
+ "version": "0.0.1.21",
5
5
  "description": "GoTry 会话检索桥(issue #21):在你自己的登录态里只读嗅探检索回包(机票/酒店/火车)与登录票据 cookie 名;零凭证经手,登录在官网由你完成,检索只读不写。",
6
6
  "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnuzBX8rT8+RZKEUip5VRA0cY056pExy9PNdN9YCiuY3dgglRbcIBhjuFJoR6EX3/MaUv82vJVyItRVrowXLTwDk4WZof0vwFgKL0zBK304PC46pUaAKTSu1PgXeL1+j5KTAGz/9a9+RnPdbj7jND5rM/MOmBHNPVPpp1NlwxRGIgF9OxHSOfXORcH4Iyte1aJSFUJbFtNS/DaE5D+KjyupPLyIqVN/dCiKnNsTCEHQB9ngeli665PFp76hkccHQQZ4sVuMLS4zBPcazUsp1kcnW+xsQoUATkODTCY+Pzk3zUVF1QOESTjRCVnbNi8tkO43RMRs2q63gX/3inFivcEwIDAQAB",
7
7
  "permissions": [
@@ -46,5 +46,5 @@
46
46
  "run_at": "document_start"
47
47
  }
48
48
  ],
49
- "version_name": "0.0.1-rc.19"
49
+ "version_name": "0.0.1-rc.21"
50
50
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danceiny/gotry",
3
- "version": "0.0.1-rc.19",
3
+ "version": "0.0.1-rc.21",
4
4
  "description": "GoTry — 从出发到下一次出发的 AI 旅行 Agent(dsh 插件)。npm/源码入口共用锁定的 dsh runtime + README 安装路径。",
5
5
  "type": "module",
6
6
  "main": "ts/src/index.ts",
@@ -89,6 +89,8 @@
89
89
  "ts/capabilities/session/",
90
90
  "extension/",
91
91
  "ts/package.json",
92
+ "ts/dsh-runtime/vendor/dsh-map-tools",
93
+ "ts/scripts/map-tools-vendor-package-proof.ts",
92
94
  "cordis.gotry-patch.yml",
93
95
  "README.md",
94
96
  "README.zh-CN.md",