@tracecode/harness 0.9.2 → 0.9.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,19 @@ All notable changes to this project are documented here.
4
4
 
5
5
  This repo uses Git tags as release boundaries. Version notes below summarize what shipped in each tagged release.
6
6
 
7
+ ## [0.9.3] - 2026-06-05
8
+
9
+ ### Changed
10
+
11
+ - Added true browser batch execution for JavaScript, TypeScript, Python, C#, and C++ so multi-case runs prepare or compile once and execute the full input batch in one worker call.
12
+ - Kept JavaScript, TypeScript, and Python batch cases isolated with fresh globals and freshly materialized mutable inputs, including linked-list/object inputs that user code can mutate.
13
+ - Added compile-once browser batch drivers for C# and C++ named-function, solution-method, and ops-class execution paths.
14
+
15
+ ### Fixed
16
+
17
+ - Fixed Python browser batch handling for default imports, script-mode inputs, and custom class materialization.
18
+ - Added regression coverage for batch global isolation, mutable input isolation, C# browser batch execution, and C++ compile-once batch behavior.
19
+
7
20
  ## [0.9.2] - 2026-06-05
8
21
 
9
22
  ### Fixed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tracecode/harness",
3
- "version": "0.9.2",
3
+ "version": "0.9.3",
4
4
  "license": "AGPL-3.0-only",
5
5
  "homepage": "https://tracecode.app",
6
6
  "repository": {
@@ -6868,6 +6868,214 @@ ${traceReturn}
6868
6868
  `;
6869
6869
  }
6870
6870
 
6871
+ function buildBatchDriverSource(userCode, functionName, inputBatch, options = {}) {
6872
+ userCode = normalizeCppUserSource(userCode, options);
6873
+ const firstInputs = Array.isArray(inputBatch) && inputBatch.length > 0 && inputBatch[0] && typeof inputBatch[0] === 'object'
6874
+ ? inputBatch[0]
6875
+ : {};
6876
+ const aliases = collectCppTypeAliases(userCode);
6877
+ const signature = parseMethodSignature(userCode, functionName, {
6878
+ parameterCount: Object.keys(firstInputs || {}).length,
6879
+ inputNames: Object.keys(firstInputs || {}),
6880
+ });
6881
+ const typeContext = sourceDeclaresSolutionClass(userCode)
6882
+ ? buildCppDriverTypeContext(userCode, 'Solution', signature, aliases)
6883
+ : buildCppDriverTypeContext(userCode, functionName, signature, aliases);
6884
+ const driverSignature = qualifyCppSignatureForDriver(signature, typeContext, aliases);
6885
+ const usesSolutionClass = options.executionStyle !== 'function' || sourceDeclaresSolutionClass(userCode);
6886
+ const declarations = [];
6887
+ const argumentNames = [];
6888
+
6889
+ driverSignature.parameters.forEach((parameter, index) => {
6890
+ const localName = `__tc_arg_${index}`;
6891
+ const type = materializedCppType(parameter.type, aliases);
6892
+ declarations.push(` ${type} ${localName} = tracecode::read_json_input<${type}>(__tc_case, ${cppStringLiteral(parameter.name)}, ${index});`);
6893
+ argumentNames.push(localName);
6894
+ });
6895
+
6896
+ const returnsNull = isNullCppReturnType(driverSignature.returnType, aliases);
6897
+ const returnsVoid = normalizeCppType(localCppType(driverSignature.returnType), aliases) === 'void';
6898
+ const noStoredResult = returnsVoid || returnsNull;
6899
+ const voidOutputParameter = returnsVoid && driverSignature.parameters.length > 0 && isSnapshotSerializableCppType(driverSignature.parameters[0].type, aliases)
6900
+ ? driverSignature.parameters[0]
6901
+ : null;
6902
+ const resultJsonExpression = noStoredResult
6903
+ ? returnsNull
6904
+ ? '"null"'
6905
+ : voidOutputParameter
6906
+ ? cppJsonExpressionForValue('__tc_arg_0', voidOutputParameter.type, userCode)
6907
+ : '"null"'
6908
+ : cppJsonExpressionForValue('__tc_result', driverSignature.returnType, userCode);
6909
+ const callExpression = `${usesSolutionClass ? `solution.${functionName}` : functionName}(${argumentNames.join(', ')})`;
6910
+ const invokeAndStore = noStoredResult ? ` ${callExpression};` : ` auto __tc_result = ${callExpression};`;
6911
+
6912
+ return `${buildGeneratedIncludes(userCode, driverSignature)}
6913
+ using namespace std;
6914
+ ${buildTracecodeFallbackAliases(userCode)}
6915
+
6916
+ #line 1 "${CPP_USER_SOURCE_FILE}"
6917
+ ${userCode}
6918
+ ${buildCppJsonObjectAdapters(typeContext, aliases)}
6919
+
6920
+ #line 1 "TraceCodeDriver.cpp"
6921
+ int main() {
6922
+ tracecode::JsonValue __tc_cases = tracecode::parse_json(tracecode::read_stdin_all());
6923
+ if (__tc_cases.kind != tracecode::JsonValue::Kind::Array) {
6924
+ std::fputs("C++ batch input must be a JSON array.\\n", stderr);
6925
+ return 1;
6926
+ }
6927
+ std::string __tc_results = "[";
6928
+ for (std::size_t __tc_case_index = 0; __tc_case_index < __tc_cases.array_values.size(); ++__tc_case_index) {
6929
+ const tracecode::JsonValue& __tc_case = __tc_cases.array_values[__tc_case_index];
6930
+ if (__tc_case_index > 0) __tc_results += ",";
6931
+ ${usesSolutionClass ? ' Solution solution;\n' : ''}${declarations.join('\n')}
6932
+ ${invokeAndStore}
6933
+ __tc_results += ${resultJsonExpression};
6934
+ }
6935
+ __tc_results += "]";
6936
+ tracecode::write_result_json_raw(__tc_results);
6937
+ return 0;
6938
+ }
6939
+ `;
6940
+ }
6941
+
6942
+ function buildOpsClassBatchDriverSource(userCode, className, inputBatch, options = {}) {
6943
+ userCode = normalizeCppUserSource(userCode, options);
6944
+ const firstInputs = Array.isArray(inputBatch) && inputBatch.length > 0 && inputBatch[0] && typeof inputBatch[0] === 'object'
6945
+ ? inputBatch[0]
6946
+ : {};
6947
+ const aliases = collectCppTypeAliases(userCode);
6948
+ const typeContext = buildCppDriverTypeContext(userCode, className, null, aliases);
6949
+ const { operations, argumentsList } = getOpsClassInputs(firstInputs || {});
6950
+ let firstOperationIndex = 1;
6951
+ let constructorArgumentIndex = 0;
6952
+ if (operations[0] === '__init__') {
6953
+ firstOperationIndex = 1;
6954
+ constructorArgumentIndex = 0;
6955
+ } else if (operations[0] === className) {
6956
+ firstOperationIndex = 1;
6957
+ constructorArgumentIndex = 0;
6958
+ } else if (operations.length > 0) {
6959
+ firstOperationIndex = 0;
6960
+ constructorArgumentIndex = -1;
6961
+ } else {
6962
+ throw new Error(`C++ ops-class inputs must start with constructor operation "${className}".`);
6963
+ }
6964
+
6965
+ const lines = [];
6966
+ const constructorArgs = constructorArgumentIndex >= 0 ? normalizeOpsArguments(argumentsList[constructorArgumentIndex]) : [];
6967
+ const constructorSignature = qualifyCppSignatureForDriver(
6968
+ parseConstructorSignature(userCode, className, aliases, {
6969
+ parameterCount: constructorArgs.length,
6970
+ }),
6971
+ typeContext,
6972
+ aliases
6973
+ );
6974
+ if (constructorArgs.length !== constructorSignature.parameters.length) {
6975
+ throw new Error(`C++ ops-class constructor "${className}" expected ${constructorSignature.parameters.length} args, received ${constructorArgs.length}.`);
6976
+ }
6977
+ const constructorArgNames = constructorArgs.map((_value, index) => {
6978
+ const localName = `__tc_ctor_arg_${index}`;
6979
+ const type = materializedCppType(constructorSignature.parameters[index].type, aliases);
6980
+ lines.push(` ${type} ${localName} = tracecode::json_to<${type}>(__tc_ops_arg_at(__tc_ops_item_at(*__tc_arguments, ${constructorArgumentIndex}), ${index}));`);
6981
+ return localName;
6982
+ });
6983
+ lines.push(constructorArgs.length === 0
6984
+ ? ` ${className} __tc_instance;`
6985
+ : ` ${className} __tc_instance(${constructorArgNames.join(', ')});`);
6986
+ lines.push(' std::vector<std::string> __tc_case_outputs;');
6987
+ if (constructorArgumentIndex >= 0) {
6988
+ lines.push(' __tc_case_outputs.push_back("null");');
6989
+ }
6990
+
6991
+ for (let index = firstOperationIndex; index < operations.length; index += 1) {
6992
+ const operation = operations[index];
6993
+ if (typeof operation !== 'string' || !operation.trim()) {
6994
+ throw new Error(`C++ ops-class operation at index ${index} must be a method name.`);
6995
+ }
6996
+ const signatureOperation = resolveCppObjectMethodMacro(userCode, operation);
6997
+ const signature = qualifyCppSignatureForDriver(parseMethodSignature(userCode, signatureOperation), typeContext, aliases);
6998
+ const args = normalizeOpsArguments(argumentsList[index]);
6999
+ if (args.length !== signature.parameters.length) {
7000
+ throw new Error(`C++ ops-class method "${operation}" expected ${signature.parameters.length} args, received ${args.length}.`);
7001
+ }
7002
+ const argNames = [];
7003
+ signature.parameters.forEach((parameter, argIndex) => {
7004
+ const localName = `__tc_op_${index}_arg_${argIndex}`;
7005
+ const type = materializedCppType(parameter.type, aliases);
7006
+ lines.push(` ${type} ${localName} = tracecode::json_to<${type}>(__tc_ops_arg_at(__tc_ops_item_at(*__tc_arguments, ${index}), ${argIndex}));`);
7007
+ argNames.push(localName);
7008
+ });
7009
+ if (normalizeCppType(signature.returnType, aliases) === 'void' || isNullCppReturnType(signature.returnType, aliases)) {
7010
+ lines.push(` __tc_instance.${signatureOperation}(${argNames.join(', ')});`);
7011
+ lines.push(' __tc_case_outputs.push_back("null");');
7012
+ } else {
7013
+ lines.push(` auto __tc_op_${index}_result = __tc_instance.${signatureOperation}(${argNames.join(', ')});`);
7014
+ lines.push(` __tc_case_outputs.push_back(${cppJsonExpressionForValue(`__tc_op_${index}_result`, signature.returnType, userCode)});`);
7015
+ }
7016
+ }
7017
+ const operationChecks = operations.map((operation, index) => ` if (__tc_operations && __tc_operations->kind == tracecode::JsonValue::Kind::Array && __tc_operations->array_values.size() > ${index} && __tc_operations->array_values[${index}].kind == tracecode::JsonValue::Kind::String && __tc_operations->array_values[${index}].string_value != ${cppStringLiteral(String(operation))}) {
7018
+ std::fputs("C++ ops-class case operation name differs from the first case.\\n", stderr);
7019
+ return 1;
7020
+ }`);
7021
+
7022
+ return `${buildGeneratedIncludes(userCode, { parameters: [] })}
7023
+ using namespace std;
7024
+ ${buildTracecodeFallbackAliases(userCode)}
7025
+
7026
+ #line 1 "${CPP_USER_SOURCE_FILE}"
7027
+ ${userCode}
7028
+ ${buildCppJsonObjectAdapters(typeContext, aliases)}
7029
+
7030
+ #line 1 "TraceCodeDriver.cpp"
7031
+ int main() {
7032
+ tracecode::JsonValue __tc_cases = tracecode::parse_json(tracecode::read_stdin_all());
7033
+ if (__tc_cases.kind != tracecode::JsonValue::Kind::Array) {
7034
+ std::fputs("C++ ops-class batch input must be a JSON array.\\n", stderr);
7035
+ return 1;
7036
+ }
7037
+ const tracecode::JsonValue __tc_null_value;
7038
+ auto __tc_ops_item_at = [&__tc_null_value](const tracecode::JsonValue& values, std::size_t index) -> const tracecode::JsonValue& {
7039
+ if (values.kind == tracecode::JsonValue::Kind::Array && index < values.array_values.size()) return values.array_values[index];
7040
+ return __tc_null_value;
7041
+ };
7042
+ auto __tc_ops_arg_at = [&__tc_ops_item_at](const tracecode::JsonValue& values, std::size_t index) -> const tracecode::JsonValue& {
7043
+ if (values.kind == tracecode::JsonValue::Kind::Array) return __tc_ops_item_at(values, index);
7044
+ return values;
7045
+ };
7046
+ std::string __tc_results = "[";
7047
+ for (std::size_t __tc_case_index = 0; __tc_case_index < __tc_cases.array_values.size(); ++__tc_case_index) {
7048
+ const tracecode::JsonValue& __tc_case = __tc_cases.array_values[__tc_case_index];
7049
+ const tracecode::JsonValue* __tc_operations = tracecode::object_get(__tc_case, "operations");
7050
+ if (!__tc_operations) __tc_operations = tracecode::object_get(__tc_case, "ops");
7051
+ const tracecode::JsonValue* __tc_arguments = tracecode::object_get(__tc_case, "arguments");
7052
+ if (!__tc_arguments) __tc_arguments = tracecode::object_get(__tc_case, "args");
7053
+ if (!__tc_arguments || __tc_arguments->kind != tracecode::JsonValue::Kind::Array) {
7054
+ std::fputs("C++ ops-class case must include arguments or args array.\\n", stderr);
7055
+ return 1;
7056
+ }
7057
+ if (__tc_operations && __tc_operations->kind == tracecode::JsonValue::Kind::Array && __tc_operations->array_values.size() != ${operations.length}) {
7058
+ std::fputs("C++ ops-class case operations length differs from the first case.\\n", stderr);
7059
+ return 1;
7060
+ }
7061
+ ${operationChecks.join('\n')}
7062
+ if (__tc_case_index > 0) __tc_results += ",";
7063
+ ${lines.join('\n')}
7064
+ std::string __tc_case_json = "[";
7065
+ for (std::size_t __tc_i = 0; __tc_i < __tc_case_outputs.size(); ++__tc_i) {
7066
+ if (__tc_i > 0) __tc_case_json += ",";
7067
+ __tc_case_json += __tc_case_outputs[__tc_i];
7068
+ }
7069
+ __tc_case_json += "]";
7070
+ __tc_results += __tc_case_json;
7071
+ }
7072
+ __tc_results += "]";
7073
+ tracecode::write_result_json_raw(__tc_results);
7074
+ return 0;
7075
+ }
7076
+ `;
7077
+ }
7078
+
6871
7079
  function scriptLineCount(source) {
6872
7080
  return String(source || '').split(/\r?\n/).length;
6873
7081
  }
@@ -8231,7 +8439,9 @@ async function compileAndRun(source, functionName, inputs, options = {}) {
8231
8439
  }
8232
8440
  const fs = toolchain.baseFs.clone();
8233
8441
  const resourceDir = findClangResourceDir(fs);
8234
- const scriptRequest = isScriptExecutionRequest(functionName, options);
8442
+ const preparedDriverSource = typeof options.preparedDriverSource === 'string' ? options.preparedDriverSource : null;
8443
+ const stdinText = typeof options.stdinText === 'string' ? options.stdinText : JSON.stringify(inputs || {});
8444
+ const scriptRequest = !preparedDriverSource && isScriptExecutionRequest(functionName, options);
8235
8445
  const signature = scriptRequest
8236
8446
  ? { line: 1 }
8237
8447
  : options.executionStyle === 'ops-class'
@@ -8240,15 +8450,18 @@ async function compileAndRun(source, functionName, inputs, options = {}) {
8240
8450
  parameterCount: Object.keys(inputs || {}).length,
8241
8451
  inputNames: Object.keys(inputs || {}),
8242
8452
  });
8243
- const driverStartedAt = now();
8244
- emitRequestProgress('driver-build:start', { tracing: Boolean(options.tracing) });
8245
- const driverSource = scriptRequest
8246
- ? buildScriptDriverSource(source, options)
8247
- : options.executionStyle === 'ops-class'
8248
- ? buildOpsClassDriverSource(source, functionName, inputs || {}, options)
8249
- : buildDriverSource(source, functionName, inputs || {}, options);
8250
- timings.driverBuildMs = elapsedMs(driverStartedAt);
8251
- emitRequestProgress('driver-build:complete', { tracing: Boolean(options.tracing) });
8453
+ let driverSource = preparedDriverSource;
8454
+ if (!driverSource) {
8455
+ const driverStartedAt = now();
8456
+ emitRequestProgress('driver-build:start', { tracing: Boolean(options.tracing) });
8457
+ driverSource = scriptRequest
8458
+ ? buildScriptDriverSource(source, options)
8459
+ : options.executionStyle === 'ops-class'
8460
+ ? buildOpsClassDriverSource(source, functionName, inputs || {}, options)
8461
+ : buildDriverSource(source, functionName, inputs || {}, options);
8462
+ timings.driverBuildMs = elapsedMs(driverStartedAt);
8463
+ emitRequestProgress('driver-build:complete', { tracing: Boolean(options.tracing) });
8464
+ }
8252
8465
 
8253
8466
  fs.addDirectory('/tmp');
8254
8467
  fs.addFile('/tmp/TraceCodeDriver.cpp', driverSource);
@@ -8351,7 +8564,7 @@ async function compileAndRun(source, functionName, inputs, options = {}) {
8351
8564
  const runStartedAt = now();
8352
8565
  emitRequestProgress('program-run:start', { tracing: Boolean(options.tracing) });
8353
8566
  const program = await runWasi(programModule, ['program.wasm'], fs, {
8354
- stdinBytes: staticStdinBytesFromText(JSON.stringify(inputs || {})),
8567
+ stdinBytes: staticStdinBytesFromText(stdinText),
8355
8568
  });
8356
8569
  timings.runMs = elapsedMs(runStartedAt);
8357
8570
  emitRequestProgress('program-run:complete', { tracing: Boolean(options.tracing), runMs: timings.runMs, exitCode: program.exitCode });
@@ -8428,7 +8641,9 @@ async function compileAndRunWithExternalCompiler(source, functionName, inputs, s
8428
8641
  const timings = {
8429
8642
  ...(options.timings && typeof options.timings === 'object' ? options.timings : {}),
8430
8643
  };
8431
- const scriptRequest = isScriptExecutionRequest(functionName, options);
8644
+ const preparedDriverSource = typeof options.preparedDriverSource === 'string' ? options.preparedDriverSource : null;
8645
+ const stdinText = typeof options.stdinText === 'string' ? options.stdinText : JSON.stringify(inputs || {});
8646
+ const scriptRequest = !preparedDriverSource && isScriptExecutionRequest(functionName, options);
8432
8647
  const signature = scriptRequest
8433
8648
  ? { line: 1 }
8434
8649
  : options.executionStyle === 'ops-class'
@@ -8437,19 +8652,26 @@ async function compileAndRunWithExternalCompiler(source, functionName, inputs, s
8437
8652
  parameterCount: Object.keys(inputs || {}).length,
8438
8653
  inputNames: Object.keys(inputs || {}),
8439
8654
  });
8440
- const driverStartedAt = now();
8441
- emitRequestProgress('driver-build:start', { tracing: Boolean(options.tracing), compiler: 'external' });
8442
- const rawDriverSource = scriptRequest
8443
- ? buildScriptDriverSource(source, options)
8444
- : options.executionStyle === 'ops-class'
8445
- ? buildOpsClassDriverSource(source, functionName, inputs || {}, options)
8446
- : buildDriverSource(source, functionName, inputs || {}, options);
8447
- const driverSource = rawDriverSource.replace(
8655
+ let driverSource = preparedDriverSource;
8656
+ if (!driverSource) {
8657
+ const driverStartedAt = now();
8658
+ emitRequestProgress('driver-build:start', { tracing: Boolean(options.tracing), compiler: 'external' });
8659
+ const rawDriverSource = scriptRequest
8660
+ ? buildScriptDriverSource(source, options)
8661
+ : options.executionStyle === 'ops-class'
8662
+ ? buildOpsClassDriverSource(source, functionName, inputs || {}, options)
8663
+ : buildDriverSource(source, functionName, inputs || {}, options);
8664
+ driverSource = rawDriverSource.replace(
8665
+ '#include "/tracecode_runtime.hpp"',
8666
+ '#include "tracecode_runtime.hpp"'
8667
+ );
8668
+ timings.driverBuildMs = elapsedMs(driverStartedAt);
8669
+ emitRequestProgress('driver-build:complete', { tracing: Boolean(options.tracing), compiler: 'external' });
8670
+ }
8671
+ driverSource = driverSource.replace(
8448
8672
  '#include "/tracecode_runtime.hpp"',
8449
8673
  '#include "tracecode_runtime.hpp"'
8450
8674
  );
8451
- timings.driverBuildMs = elapsedMs(driverStartedAt);
8452
- emitRequestProgress('driver-build:complete', { tracing: Boolean(options.tracing), compiler: 'external' });
8453
8675
 
8454
8676
  const cacheKey = getProgramCacheKey('yowasp-worker', driverSource);
8455
8677
  let programModule = getCachedProgramModule(cacheKey);
@@ -8516,7 +8738,7 @@ async function compileAndRunWithExternalCompiler(source, functionName, inputs, s
8516
8738
  const runStartedAt = now();
8517
8739
  emitRequestProgress('program-run:start', { tracing: Boolean(options.tracing), compiler: 'external' });
8518
8740
  const program = await runWasi(programModule, ['program.wasm'], new InMemoryFileSystem(), {
8519
- stdinBytes: staticStdinBytesFromText(JSON.stringify(inputs || {})),
8741
+ stdinBytes: staticStdinBytesFromText(stdinText),
8520
8742
  });
8521
8743
  timings.runMs = elapsedMs(runStartedAt);
8522
8744
  emitRequestProgress('program-run:complete', {
@@ -8598,7 +8820,9 @@ async function compileAndRunWithYowasp(toolchain, source, functionName, inputs,
8598
8820
  const timings = {
8599
8821
  ...(options.timings && typeof options.timings === 'object' ? options.timings : {}),
8600
8822
  };
8601
- const scriptRequest = isScriptExecutionRequest(functionName, options);
8823
+ const preparedDriverSource = typeof options.preparedDriverSource === 'string' ? options.preparedDriverSource : null;
8824
+ const stdinText = typeof options.stdinText === 'string' ? options.stdinText : JSON.stringify(inputs || {});
8825
+ const scriptRequest = !preparedDriverSource && isScriptExecutionRequest(functionName, options);
8602
8826
  const signature = scriptRequest
8603
8827
  ? { line: 1 }
8604
8828
  : options.executionStyle === 'ops-class'
@@ -8607,19 +8831,26 @@ async function compileAndRunWithYowasp(toolchain, source, functionName, inputs,
8607
8831
  parameterCount: Object.keys(inputs || {}).length,
8608
8832
  inputNames: Object.keys(inputs || {}),
8609
8833
  });
8610
- const driverStartedAt = now();
8611
- emitRequestProgress('driver-build:start', { tracing: Boolean(options.tracing), compiler: 'yowasp' });
8612
- const rawDriverSource = scriptRequest
8613
- ? buildScriptDriverSource(source, options)
8614
- : options.executionStyle === 'ops-class'
8615
- ? buildOpsClassDriverSource(source, functionName, inputs || {}, options)
8616
- : buildDriverSource(source, functionName, inputs || {}, options);
8617
- const driverSource = rawDriverSource.replace(
8834
+ let driverSource = preparedDriverSource;
8835
+ if (!driverSource) {
8836
+ const driverStartedAt = now();
8837
+ emitRequestProgress('driver-build:start', { tracing: Boolean(options.tracing), compiler: 'yowasp' });
8838
+ const rawDriverSource = scriptRequest
8839
+ ? buildScriptDriverSource(source, options)
8840
+ : options.executionStyle === 'ops-class'
8841
+ ? buildOpsClassDriverSource(source, functionName, inputs || {}, options)
8842
+ : buildDriverSource(source, functionName, inputs || {}, options);
8843
+ driverSource = rawDriverSource.replace(
8844
+ '#include "/tracecode_runtime.hpp"',
8845
+ '#include "tracecode_runtime.hpp"'
8846
+ );
8847
+ timings.driverBuildMs = elapsedMs(driverStartedAt);
8848
+ emitRequestProgress('driver-build:complete', { tracing: Boolean(options.tracing), compiler: 'yowasp' });
8849
+ }
8850
+ driverSource = driverSource.replace(
8618
8851
  '#include "/tracecode_runtime.hpp"',
8619
8852
  '#include "tracecode_runtime.hpp"'
8620
8853
  );
8621
- timings.driverBuildMs = elapsedMs(driverStartedAt);
8622
- emitRequestProgress('driver-build:complete', { tracing: Boolean(options.tracing), compiler: 'yowasp' });
8623
8854
  const stdoutChunks = [];
8624
8855
  const stderrChunks = [];
8625
8856
  const collect = (chunks) => (bytes) => {
@@ -8694,7 +8925,7 @@ async function compileAndRunWithYowasp(toolchain, source, functionName, inputs,
8694
8925
  const runStartedAt = now();
8695
8926
  emitRequestProgress('program-run:start', { tracing: Boolean(options.tracing), compiler: 'yowasp' });
8696
8927
  const program = await runWasi(programModule, ['program.wasm'], new InMemoryFileSystem(), {
8697
- stdinBytes: staticStdinBytesFromText(JSON.stringify(inputs || {})),
8928
+ stdinBytes: staticStdinBytesFromText(stdinText),
8698
8929
  });
8699
8930
  timings.runMs = elapsedMs(runStartedAt);
8700
8931
  emitRequestProgress('program-run:complete', {
@@ -8885,28 +9116,121 @@ async function handleCompileRun(payload) {
8885
9116
 
8886
9117
  async function handleCompileRunBatch(payload) {
8887
9118
  const startedAt = now();
9119
+ const source = payload && typeof payload.code === 'string' ? payload.code : '';
9120
+ const functionName = payload && typeof payload.functionName === 'string' ? payload.functionName : '';
9121
+ const executionStyle = payload?.executionStyle || 'solution-method';
8888
9122
  const inputBatch = Array.isArray(payload?.inputBatch)
8889
9123
  ? payload.inputBatch.map((inputs) => (inputs && typeof inputs === 'object' ? inputs : {}))
8890
9124
  : [];
9125
+ const baseTimings = () => ({ totalMs: elapsedMs(startedAt) });
9126
+ const failedBatchResult = (message) => ({
9127
+ success: false,
9128
+ results: [],
9129
+ error: message,
9130
+ consoleOutput: [],
9131
+ timings: baseTimings(),
9132
+ });
9133
+
9134
+ if (!source.trim()) {
9135
+ return failedBatchResult('C++ source is empty.');
9136
+ }
9137
+
8891
9138
  if (inputBatch.length === 0) {
9139
+ return failedBatchResult('C++ batch execution requires a non-empty inputBatch array.');
9140
+ }
9141
+
9142
+ if (!functionName.trim() && executionStyle !== 'function') {
9143
+ return failedBatchResult('C++ named execution requires a function name.');
9144
+ }
9145
+
9146
+ if (!functionName.trim() && executionStyle === 'function') {
9147
+ // Script-style C++ has no stable per-case input contract today, so keep its
9148
+ // existing per-case behavior instead of pretending it is compile-once batch.
9149
+ const results = [];
9150
+ for (const inputs of inputBatch) {
9151
+ results.push(await handleCompileRun({ ...payload, inputs }));
9152
+ }
9153
+ return {
9154
+ success: results.every((result) => result.success === true),
9155
+ results,
9156
+ consoleOutput: results.flatMap((result) => result.consoleOutput ?? []),
9157
+ timings: { ...baseTimings(), batchMode: 'per-case-fallback', batchFallbackReason: 'script-without-function-name' },
9158
+ };
9159
+ }
9160
+
9161
+ let driverSource;
9162
+ const driverBuildStartedAt = now();
9163
+ try {
9164
+ driverSource = executionStyle === 'ops-class'
9165
+ ? buildOpsClassBatchDriverSource(source, functionName, inputBatch, { executionStyle })
9166
+ : buildBatchDriverSource(source, functionName, inputBatch, { executionStyle });
9167
+ } catch (error) {
8892
9168
  return {
8893
9169
  success: false,
8894
- results: [],
8895
- error: 'C++ batch execution requires a non-empty inputBatch array.',
9170
+ results: inputBatch.map(() => ({
9171
+ success: false,
9172
+ output: null,
9173
+ error: error instanceof Error ? error.message : String(error),
9174
+ consoleOutput: [],
9175
+ timings: baseTimings(),
9176
+ })),
9177
+ error: error instanceof Error ? error.message : String(error),
8896
9178
  consoleOutput: [],
8897
- timings: { totalMs: elapsedMs(startedAt) },
9179
+ timings: baseTimings(),
8898
9180
  };
8899
9181
  }
8900
9182
 
9183
+ const firstInputs = inputBatch[0] || {};
9184
+ const batchResult = await compileAndRun(source, functionName, firstInputs, {
9185
+ executionStyle,
9186
+ preparedDriverSource: driverSource,
9187
+ stdinText: JSON.stringify(inputBatch),
9188
+ timings: {
9189
+ driverBuildMs: elapsedMs(driverBuildStartedAt),
9190
+ batchMode: 'compile-once',
9191
+ batchCaseCount: inputBatch.length,
9192
+ },
9193
+ });
9194
+ const batchTimings = {
9195
+ ...(batchResult.timings && typeof batchResult.timings === 'object' ? batchResult.timings : {}),
9196
+ totalMs: elapsedMs(startedAt),
9197
+ };
9198
+ const consoleOutput = batchResult.consoleOutput ?? [];
9199
+ const outputs = Array.isArray(batchResult.output) ? batchResult.output : [];
9200
+ const success = batchResult.success === true && outputs.length === inputBatch.length;
9201
+ const runtimeError = batchResult.error ||
9202
+ (outputs.length !== inputBatch.length
9203
+ ? `C++ batch returned ${outputs.length} result(s) for ${inputBatch.length} case(s).`
9204
+ : 'C++ batch execution failed.');
8901
9205
  const results = [];
8902
- for (const inputs of inputBatch) {
8903
- results.push(await handleCompileRun({ ...payload, inputs }));
9206
+ for (let index = 0; index < inputBatch.length; index += 1) {
9207
+ const caseTimings = index === 0
9208
+ ? batchTimings
9209
+ : {
9210
+ compileMs: 0,
9211
+ linkMs: 0,
9212
+ wasmCompileMs: 0,
9213
+ runMs: 0,
9214
+ totalMs: 0,
9215
+ compileCacheHit: batchTimings.compileCacheHit,
9216
+ batchMode: 'compile-once',
9217
+ };
9218
+ results.push({
9219
+ success,
9220
+ output: success ? outputs[index] : null,
9221
+ ...(success ? {} : { error: runtimeError }),
9222
+ consoleOutput,
9223
+ ...(batchResult.timeoutReason ? { timeoutReason: batchResult.timeoutReason } : {}),
9224
+ ...(batchResult.diagnosticStage ? { diagnosticStage: batchResult.diagnosticStage } : {}),
9225
+ timings: caseTimings,
9226
+ });
8904
9227
  }
8905
9228
  return {
8906
- success: results.every((result) => result.success === true),
9229
+ success,
8907
9230
  results,
8908
- consoleOutput: results.flatMap((result) => result.consoleOutput ?? []),
8909
- timings: { totalMs: elapsedMs(startedAt) },
9231
+ consoleOutput,
9232
+ ...(success ? {} : { error: runtimeError }),
9233
+ timings: batchTimings,
8910
9234
  };
8911
9235
  }
8912
9236