@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
@@ -12,6 +12,22 @@ const BIN = join(ROOT, 'bin', 'gotry-inner.js');
12
12
  const TOOL = 'gotry_benchmark_environment';
13
13
  const MARKER = 'BENCHMARK_BRIDGE_LOOKUP_OK';
14
14
  const TIMEOUT_MS = 30_000;
15
+ const LOOKUP_INPUT_SCHEMA = {
16
+ type: 'object',
17
+ properties: {
18
+ city: {
19
+ type: 'string',
20
+ enum: [
21
+ 'Dubai',
22
+ 'Singapore'
23
+ ]
24
+ }
25
+ },
26
+ required: [
27
+ 'city'
28
+ ],
29
+ additionalProperties: false
30
+ };
15
31
  const TSX_LOADER = pathToFileURL(createRequire(import.meta.url).resolve('tsx')).href;
16
32
  function runRuntimeProbe(options) {
17
33
  const fixture = mkdtempSync(join(tmpdir(), 'gotry-runtime-probe-'));
@@ -61,7 +77,7 @@ function assertRuntimeSelectionAndVersionGuards() {
61
77
  const legacyFallback = runRuntimeProbe({
62
78
  vendorVersion: '0.1.2-alpha.1'
63
79
  });
64
- assert.equal(legacyFallback, null, 'D-27 removal (#120): legacy vendored fallback no longer resolves even outside benchmark — fail-closed');
80
+ assert.deepEqual(legacyFallback, null, 'non-benchmark source checkout fail-closes instead of using the removed legacy vendored dsh fallback');
65
81
  const wrongBenchmarkVersion = runRuntimeProbe({
66
82
  rootVersion: '0.1.2-alpha.1',
67
83
  vendorVersion: '0.1.2-alpha.1',
@@ -183,7 +199,7 @@ function finalText(text) {
183
199
  ]
184
200
  }) + 'data: [DONE]\n\n';
185
201
  }
186
- function toolCall(callId = 'bridge-call-1') {
202
+ function toolCall(callId = 'bridge-call-1', city = 'Dubai') {
187
203
  return sse({
188
204
  id: `bridge-${callId}`,
189
205
  object: 'chat.completion.chunk',
@@ -202,7 +218,7 @@ function toolCall(callId = 'bridge-call-1') {
202
218
  action: 'call',
203
219
  tool: 'lookup',
204
220
  arguments: {
205
- city: 'Dubai'
221
+ city
206
222
  }
207
223
  })
208
224
  }
@@ -235,9 +251,25 @@ function toolResultPresent(body) {
235
251
  function anyToolResultPresent(body) {
236
252
  return (body.messages ?? []).some((m)=>m.role === 'tool');
237
253
  }
254
+ const BRIDGE_E2E_BODY_SCHEMA = {
255
+ type: 'object',
256
+ properties: {
257
+ status: {
258
+ type: 'string'
259
+ },
260
+ payload: {
261
+ type: 'string'
262
+ }
263
+ },
264
+ required: [],
265
+ additionalProperties: false
266
+ };
267
+ const TERMINAL_OUTLINE = 'object{?status:string,?payload:string}';
238
268
  async function runCase(mode, executableOverride, extraEnv = {}) {
239
269
  const requests = [];
270
+ let servedToolCalls = 0;
240
271
  let spawnTarget = '';
272
+ const domainRecoveryMode = mode === 'domain-recovery' || mode === 'domain-recovery-failed';
241
273
  const server = createServer((req, res)=>{
242
274
  const chunks = [];
243
275
  req.on('data', (c)=>chunks.push(Buffer.from(c)));
@@ -253,8 +285,15 @@ async function runCase(mode, executableOverride, extraEnv = {}) {
253
285
  if (mode === 'spawn-failed' && names(body).includes(TOOL) && !anyToolResultPresent(body) && spawnTarget) rmSync(spawnTarget, {
254
286
  force: true
255
287
  });
256
- if (mode !== 'disabled' && mode !== 'invalid-path' && mode !== 'invalid-schema' && mode !== 'unsafe-config' && names(body).includes(TOOL) && !anyToolResultPresent(body)) res.end(toolCall());
257
- else res.end(finalText(mode === 'enabled' ? '<benchmark_terminal>{"status":"succeeded"}</benchmark_terminal>' : '<benchmark_terminal>{"status":"succeeded"}</benchmark_terminal>'));
288
+ if (domainRecoveryMode && servedToolCalls === 1 && names(body).includes(TOOL) && anyToolResultPresent(body) && !toolResultPresent(body)) {
289
+ servedToolCalls += 1;
290
+ res.end(toolCall('bridge-call-2', 'Singapore'));
291
+ } else if (mode !== 'disabled' && mode !== 'invalid-path' && mode !== 'invalid-schema' && mode !== 'unsafe-config' && names(body).includes(TOOL) && !anyToolResultPresent(body)) {
292
+ servedToolCalls += 1;
293
+ res.end(toolCall());
294
+ } else {
295
+ res.end(finalText('<benchmark_terminal>{"status":"succeeded"}</benchmark_terminal>'));
296
+ }
258
297
  });
259
298
  });
260
299
  await new Promise((resolve)=>server.listen(0, '127.0.0.1', resolve));
@@ -269,15 +308,16 @@ async function runCase(mode, executableOverride, extraEnv = {}) {
269
308
  });
270
309
  writeResolutionProbe(probe, probeResult, mode === 'disabled');
271
310
  const runner = join(cwd, 'synthetic-runner.js');
311
+ const runnerTrace = join(cwd, 'synthetic-runner-trace.jsonl');
272
312
  spawnTarget = join(cwd, 'synthetic-spawn-target.js');
273
313
  const configPath = join(cwd, mode === 'invalid-path' ? 'benchmark-env-config-\n.json' : 'benchmark-env-config.json');
274
- const runnerBody = mode === 'timeout' ? `setTimeout(() => {}, 60_000)` : mode === 'runner-failed' ? `process.stderr.write('PRIVATE_RUNNER_DIAGNOSTIC_DO_NOT_REFLECT'); process.exit(17)` : mode === 'output-truncated' ? `process.stdout.write('x'.repeat(20_000))` : mode === 'unexpected-output' ? `process.stdout.write(JSON.stringify({ result: { marker: '${MARKER}', leaked: [], unexpected: 'must-not-reflect' } }))` : `const forbidden = ['GOTRY_BENCHMARK_ENV_CONFIG', 'GOTRY_BENCHMARK_BRIDGE_PARENT_SECRET', 'LLM_API_KEY', 'LLM_BASE_URL', 'LLM_MODEL', 'DEEPSEEK_BASE_URL', 'GOTRY_LLM_MODEL', 'DATABASE_URL', 'SSH_AUTH_SOCK', 'AWS_PROFILE', 'HTTPS_PROXY']; const leaked = forbidden.filter(name => process.env[name] !== undefined); process.stdout.write(JSON.stringify({ result: { marker: '${MARKER}', leaked } }))`;
275
- writeFileSync(runner, `if (process.argv.length !== 5 || process.argv[2] !== 'call' || process.argv[3] !== 'lookup' || JSON.parse(process.argv[4]).city !== 'Dubai') process.exit(2); ${runnerBody}`);
314
+ const runnerBody = mode === 'domain-recovery-failed' ? `if (args.city === 'Dubai') process.stdout.write(JSON.stringify({ schema_version: 'gotry_benchmark_tool_result_v1', status: 'miss', code: 'NOT_FOUND', recovery: 'revise_arguments' })); else { process.stderr.write('PRIVATE_RECOVERY_RUNNER_DIAGNOSTIC_DO_NOT_REFLECT'); process.exit(17) }` : mode === 'domain-recovery' ? `if (args.city === 'Dubai') process.stdout.write(JSON.stringify({ schema_version: 'gotry_benchmark_tool_result_v1', status: 'miss', code: 'NOT_FOUND', recovery: 'revise_arguments' })); else process.stdout.write(JSON.stringify({ schema_version: 'gotry_benchmark_tool_result_v1', status: 'ok', result: { marker: '${MARKER}', leaked: [] } }))` : mode === 'timeout' ? `setTimeout(() => {}, 60_000)` : mode === 'runner-failed' ? `process.stderr.write('PRIVATE_RUNNER_DIAGNOSTIC_DO_NOT_REFLECT'); process.exit(17)` : mode === 'output-truncated' ? `process.stdout.write('x'.repeat(20_000))` : mode === 'unexpected-output' ? `process.stdout.write(JSON.stringify({ schema_version: 'gotry_benchmark_tool_result_v1', status: 'ok', result: { marker: '${MARKER}', leaked: [], unexpected: 'must-not-reflect' } }))` : `const forbidden = ['GOTRY_BENCHMARK_ENV_CONFIG', 'GOTRY_BENCHMARK_BRIDGE_PARENT_SECRET', 'LLM_API_KEY', 'LLM_BASE_URL', 'LLM_MODEL', 'DEEPSEEK_BASE_URL', 'GOTRY_LLM_MODEL', 'DATABASE_URL', 'SSH_AUTH_SOCK', 'AWS_PROFILE', 'HTTPS_PROXY']; const leaked = forbidden.filter(name => process.env[name] !== undefined); process.stdout.write(JSON.stringify({ schema_version: 'gotry_benchmark_tool_result_v1', status: 'ok', result: { marker: '${MARKER}', leaked } }))`;
315
+ writeFileSync(runner, `if (process.argv.length !== 5 || process.argv[2] !== 'call' || process.argv[3] !== 'lookup' || (!${JSON.stringify(domainRecoveryMode)} && JSON.parse(process.argv[4]).city !== 'Dubai') || (${JSON.stringify(domainRecoveryMode)} && !['Dubai', 'Singapore'].includes(JSON.parse(process.argv[4]).city))) process.exit(2); const fs = require('node:fs'); const args = JSON.parse(process.argv[4]); fs.appendFileSync(${JSON.stringify(runnerTrace)}, JSON.stringify({ city: args.city }) + '\\n'); ${runnerBody}`);
276
316
  writeFileSync(spawnTarget, '#!/usr/bin/env node\nprocess.exit(0)\n', {
277
317
  mode: 0o700
278
318
  });
279
319
  writeFileSync(configPath, JSON.stringify({
280
- schema_version: mode === 'invalid-schema' ? 'invalid' : 'gotry_benchmark_environment_bridge_v2',
320
+ schema_version: mode === 'invalid-schema' ? 'invalid' : 'gotry_benchmark_environment_bridge_v4',
281
321
  enabled: true,
282
322
  executable: mode === 'spawn-failed' ? spawnTarget : process.execPath,
283
323
  cwd,
@@ -286,20 +326,30 @@ async function runCase(mode, executableOverride, extraEnv = {}) {
286
326
  ] : [
287
327
  runner
288
328
  ],
289
- allowed_tools: [
290
- 'lookup'
329
+ tools: [
330
+ {
331
+ name: 'lookup',
332
+ description: 'Lookup.',
333
+ input_schema: LOOKUP_INPUT_SCHEMA,
334
+ output_keys: [
335
+ 'marker',
336
+ 'leaked'
337
+ ],
338
+ domain_outcomes: [
339
+ {
340
+ status: 'miss',
341
+ code: 'NOT_FOUND',
342
+ recovery: domainRecoveryMode ? 'revise_arguments' : 'none'
343
+ }
344
+ ]
345
+ }
291
346
  ],
292
- allowed_output_keys: {
293
- lookup: [
294
- 'marker',
295
- 'leaked'
296
- ]
297
- },
298
347
  timeout_ms: mode === 'timeout' ? 50 : 10_000,
299
348
  max_output_bytes: mode === 'output-truncated' ? 1_024 : 4_096,
300
349
  terminal_output: {
301
350
  tag: 'benchmark_terminal',
302
- max_bytes: 4_096
351
+ max_bytes: 4_096,
352
+ body_schema: BRIDGE_E2E_BODY_SCHEMA
303
353
  },
304
354
  isolation: {
305
355
  mode: 'host-enforced',
@@ -398,13 +448,16 @@ async function runCase(mode, executableOverride, extraEnv = {}) {
398
448
  calendar: 0,
399
449
  map: 0
400
450
  });
451
+ const runnerArguments = existsSync(runnerTrace) ? readFileSync(runnerTrace, 'utf8').split('\n').filter(Boolean).map((line)=>JSON.parse(line)) : [];
401
452
  return {
402
453
  exit,
403
454
  stdout,
404
455
  stderr,
405
456
  output: stdout + stderr,
406
457
  requests,
407
- optionalResolutionHits
458
+ optionalResolutionHits,
459
+ servedToolCalls,
460
+ runnerArguments
408
461
  };
409
462
  } finally{
410
463
  await new Promise((resolve)=>server.close(()=>resolve()));
@@ -437,6 +490,31 @@ async function assertRuntimeContract(executableOverride) {
437
490
  assert.deepEqual(enabledToolNames, [
438
491
  TOOL
439
492
  ], `${target} enabled runtime must expose exactly the benchmark tool; observed tool names=${JSON.stringify(enabledToolNames)}`);
493
+ const bridgeTool = enabled.requests.find((request)=>names(request).includes(TOOL))?.tools?.find((tool)=>names({
494
+ tools: [
495
+ tool
496
+ ]
497
+ }).includes(TOOL));
498
+ const flatSchema = bridgeTool?.function?.parameters;
499
+ assert.equal(flatSchema?.type, 'object', `${target} bridge exposes an object-root wire schema`);
500
+ assert.equal(flatSchema?.oneOf, undefined, `${target} bridge wire has no top-level oneOf`);
501
+ assert.deepEqual(flatSchema?.required, [
502
+ 'action'
503
+ ]);
504
+ assert.equal(flatSchema?.additionalProperties, false);
505
+ assert.deepEqual(flatSchema?.properties?.action?.enum, [
506
+ 'tools',
507
+ 'call',
508
+ 'errors'
509
+ ]);
510
+ assert.deepEqual(flatSchema?.properties?.tool?.enum, [
511
+ 'lookup'
512
+ ]);
513
+ assert.deepEqual(flatSchema?.properties?.arguments, {
514
+ type: 'object',
515
+ additionalProperties: true,
516
+ description: 'action=call 时传给工具的参数对象'
517
+ });
440
518
  assert.equal(enabledToolNames.some((name)=>name.startsWith('calendar_') || name.startsWith('map_')), false, `${target} benchmark projection must not expose calendar/map tools`);
441
519
  assert.deepEqual(enabled.optionalResolutionHits, {
442
520
  calendar: 0,
@@ -460,6 +538,37 @@ async function assertRuntimeContract(executableOverride) {
460
538
  return (prompt.match(/You are GoTry, a task-agnostic travel planning assistant\./g) ?? []).length === 1 && (prompt.match(/Use only the current conversation and tools available in this benchmark session\./g) ?? []).length === 1;
461
539
  }), `${target} benchmark persona has each stable sentence exactly once per request`);
462
540
  assert.match(enabled.output, /benchmark_terminal/);
541
+ const recovered = await runCase('domain-recovery', executableOverride);
542
+ assert.equal(recovered.exit, 0, `${target} model-driven domain miss recovery exits successfully; output=${recovered.output.slice(-2_000)}`);
543
+ assert.equal(recovered.servedToolCalls, 2, `${target} model emits exactly two tool calls around one declared miss`);
544
+ assert.deepEqual(recovered.runnerArguments, [
545
+ {
546
+ city: 'Dubai'
547
+ },
548
+ {
549
+ city: 'Singapore'
550
+ }
551
+ ], `${target} model revises the declared city before the second adapter invocation`);
552
+ assert.ok(recovered.requests.some((request)=>(request.messages ?? []).some((message)=>{
553
+ if (message.role !== 'tool') return false;
554
+ const serialized = JSON.stringify(message);
555
+ return /status\\?":\\?"miss/.test(serialized) && /recovery\\?":\\?"revise_arguments/.test(serialized);
556
+ })), `${target} declared typed miss reaches model history`);
557
+ assert.match(recovered.stdout, /<benchmark_terminal>/, `${target} corrected second call reaches tagged terminal output`);
558
+ const failedRecovery = await runCase('domain-recovery-failed', executableOverride);
559
+ assert.equal(failedRecovery.exit, 1, `${target} infrastructure failure after a declared miss cannot be masked by the earlier domain outcome`);
560
+ assert.equal(failedRecovery.servedToolCalls, 2, `${target} failed recovery still exercises exactly two model-owned tool calls`);
561
+ assert.deepEqual(failedRecovery.runnerArguments, [
562
+ {
563
+ city: 'Dubai'
564
+ },
565
+ {
566
+ city: 'Singapore'
567
+ }
568
+ ], `${target} failed recovery reaches the revised second adapter invocation`);
569
+ assert.equal(failedRecovery.stdout, '', `${target} failed recovery releases no terminal stdout`);
570
+ assert.match(failedRecovery.stderr, /benchmark terminal output unavailable \(child_bridge_runner_failed\)/, `${target} failed recovery preserves the second runner failure classification`);
571
+ assert.equal(failedRecovery.output.includes('PRIVATE_RECOVERY_RUNNER_DIAGNOSTIC_DO_NOT_REFLECT'), false, `${target} failed recovery never reflects private runner stderr`);
463
572
  const debugRedaction = await runCase('debug-redaction', executableOverride);
464
573
  assert.equal(debugRedaction.exit, 0, `${target} benchmark debug mode preserves successful execution`);
465
574
  assert.equal(debugRedaction.output.includes('PRIVATE_QUERY_SENTINEL_DO_NOT_REFLECT'), false, `${target} benchmark debug output never reflects the private task`);
@@ -661,7 +770,8 @@ function conformanceResponse(mode, request, plannerCount) {
661
770
  'b',
662
771
  'd',
663
772
  'f',
664
- 'large'
773
+ 'large',
774
+ 'schema'
665
775
  ].includes(mode) && plannerCount === 1;
666
776
  if (call && !hasToolResult) return toolCall();
667
777
  if (mode === 'f' && plannerCount === 3) return toolCall('bridge-call-retry');
@@ -670,6 +780,9 @@ function conformanceResponse(mode, request, plannerCount) {
670
780
  payload: LARGE_TERMINAL_PAYLOAD
671
781
  })}</benchmark_terminal>`);
672
782
  }
783
+ if (mode === 'schema') {
784
+ return finalText('<benchmark_terminal>{"status":"succeeded","budget":{"total_cost":1}}</benchmark_terminal>');
785
+ }
673
786
  const valid = mode === 'a' ? hasToolResult : mode === 'b' ? plannerCount >= 3 : mode === 'e' ? true : false;
674
787
  return finalText(mode === 'c' || mode === 'd' ? 'bad benchmark body' : taggedTerminal(valid));
675
788
  }
@@ -729,28 +842,38 @@ async function runConformanceCase(mode, executableOverride) {
729
842
  const runner = join(cwd, 'synthetic-runner.js');
730
843
  const runnerCount = join(cwd, 'runner-count.txt');
731
844
  const configPath = join(cwd, 'benchmark-env-config.json');
732
- writeFileSync(runner, `const fs = require('node:fs'); const path = ${JSON.stringify(runnerCount)}; const count = fs.existsSync(path) ? Number(fs.readFileSync(path, 'utf8')) : 0; fs.writeFileSync(path, String(count + 1)); process.stdout.write(JSON.stringify({ result: { marker: '${MARKER}' } }))`);
845
+ writeFileSync(runner, `const fs = require('node:fs'); const path = ${JSON.stringify(runnerCount)}; const count = fs.existsSync(path) ? Number(fs.readFileSync(path, 'utf8')) : 0; fs.writeFileSync(path, String(count + 1)); process.stdout.write(JSON.stringify({ schema_version: 'gotry_benchmark_tool_result_v1', status: 'ok', result: { marker: '${MARKER}' } }))`);
733
846
  writeFileSync(configPath, JSON.stringify({
734
- schema_version: 'gotry_benchmark_environment_bridge_v2',
847
+ schema_version: 'gotry_benchmark_environment_bridge_v4',
735
848
  enabled: true,
736
849
  executable: process.execPath,
737
850
  cwd,
738
851
  argv_prefix: [
739
852
  runner
740
853
  ],
741
- allowed_tools: [
742
- 'lookup'
854
+ tools: [
855
+ {
856
+ name: 'lookup',
857
+ description: 'Lookup.',
858
+ input_schema: LOOKUP_INPUT_SCHEMA,
859
+ output_keys: [
860
+ 'marker'
861
+ ],
862
+ domain_outcomes: [
863
+ {
864
+ status: 'miss',
865
+ code: 'NOT_FOUND',
866
+ recovery: 'none'
867
+ }
868
+ ]
869
+ }
743
870
  ],
744
- allowed_output_keys: {
745
- lookup: [
746
- 'marker'
747
- ]
748
- },
749
871
  timeout_ms: 2_000,
750
872
  max_output_bytes: 4_096,
751
873
  terminal_output: {
752
874
  tag: 'benchmark_terminal',
753
- max_bytes: mode === 'large' ? 128 * 1024 : 4_096
875
+ max_bytes: mode === 'large' ? 128 * 1024 : 4_096,
876
+ body_schema: BRIDGE_E2E_BODY_SCHEMA
754
877
  },
755
878
  isolation: {
756
879
  mode: 'host-enforced',
@@ -888,6 +1011,7 @@ async function assertOutputConformance(executableOverride) {
888
1011
  assert.equal(a.servedToolCalls, 1, 'A exposes exactly one bridge call');
889
1012
  assert.equal(a.runnerInvocations, 1, 'A executes the bridge subprocess exactly once');
890
1013
  assert.ok(a.stdout.includes('<benchmark_terminal>'), 'A forwards only tagged terminal output');
1014
+ assert.ok(a.requests.some((request)=>JSON.stringify(request).includes(`matching exactly ${TERMINAL_OUTLINE}`)), 'A system prompt projects the exact terminal schema outline');
891
1015
  const b = await runConformanceCase('b', executableOverride);
892
1016
  assert.equal(b.exit, 0, 'B malformed terminal correction then valid terminal exits 0');
893
1017
  assert.equal(b.servedToolCalls, 1, `B exposes exactly one bridge call; request shapes=${JSON.stringify(b.requests.map((request)=>({
@@ -895,6 +1019,12 @@ async function assertOutputConformance(executableOverride) {
895
1019
  roles: (request.messages ?? []).map((message)=>message.role)
896
1020
  })))}`);
897
1021
  assert.equal(b.runnerInvocations, 1, 'B format-only correction does not rerun the bridge subprocess');
1022
+ assert.ok(b.requests.some((request)=>JSON.stringify(request).includes('BENCHMARK_CONFORMANCE_TERMINAL') && JSON.stringify(request).includes(`matching exactly ${TERMINAL_OUTLINE}`)), 'B terminal correction projects the same schema outline as the system prompt');
1023
+ const schema = await runConformanceCase('schema', executableOverride);
1024
+ assert.notEqual(schema.exit, 0, 'schema-invalid terminal body (extra root key) is rejected after the single correction');
1025
+ assert.match(schema.stderr, /benchmark terminal output unavailable \(child_conformance_failure\)/, 'schema-invalid terminal emits the stable conformance reason code');
1026
+ assert.equal(schema.runnerInvocations, 1, 'schema-invalid terminal still executed the bridge exactly once');
1027
+ assert.equal(schema.stdout.includes('<benchmark_terminal>'), false, 'schema-invalid terminal body is never released to stdout');
898
1028
  for (const mode of [
899
1029
  'c',
900
1030
  'd'
@@ -939,7 +1069,7 @@ if (packaged) {
939
1069
  try {
940
1070
  const configPath = join(missingServiceRoot, 'bridge.json');
941
1071
  writeFileSync(configPath, JSON.stringify({
942
- schema_version: 'gotry_benchmark_environment_bridge_v2',
1072
+ schema_version: 'gotry_benchmark_environment_bridge_v4',
943
1073
  enabled: true,
944
1074
  executable: process.execPath,
945
1075
  cwd: missingServiceRoot,
@@ -947,14 +1077,29 @@ if (packaged) {
947
1077
  '-e',
948
1078
  'process.exit(0)'
949
1079
  ],
950
- allowed_tools: [
951
- 'lookup'
1080
+ tools: [
1081
+ {
1082
+ name: 'lookup',
1083
+ description: 'Lookup.',
1084
+ input_schema: LOOKUP_INPUT_SCHEMA,
1085
+ output_keys: [
1086
+ 'marker'
1087
+ ],
1088
+ domain_outcomes: [
1089
+ {
1090
+ status: 'miss',
1091
+ code: 'NOT_FOUND',
1092
+ recovery: 'none'
1093
+ }
1094
+ ]
1095
+ }
952
1096
  ],
953
1097
  timeout_ms: 100,
954
1098
  max_output_bytes: 4_096,
955
1099
  terminal_output: {
956
1100
  tag: 'benchmark_terminal',
957
- max_bytes: 4_096
1101
+ max_bytes: 4_096,
1102
+ body_schema: BRIDGE_E2E_BODY_SCHEMA
958
1103
  },
959
1104
  isolation: {
960
1105
  mode: 'host-enforced',