From 7aef1f1cd5aade809390f85a7d3edbbd46d32cfa Mon Sep 17 00:00:00 2001 From: can1357 Date: Tue, 19 May 2026 19:16:08 +0900 Subject: [PATCH] fix(coding-agent): rewrite trailing return statement into final expression A top-level `return value;` in a JS eval cell was previously swallowed: returnFinalExpression only handled ExpressionStatement, so ReturnStatement flipped the IIFE wrapper which discarded the value. Rewrite the trailing return into __omp_set_final_expr__((expr)) so the existing final-expression channel surfaces the value just like a trailing expression. --- .../src/eval/js/shared/rewrite-imports.ts | 30 +++++++++++++------ .../src/eval/js/shared/runtime.ts | 3 +- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/packages/coding-agent/src/eval/js/shared/rewrite-imports.ts b/packages/coding-agent/src/eval/js/shared/rewrite-imports.ts index 25a5ea8c3..2afa85947 100644 --- a/packages/coding-agent/src/eval/js/shared/rewrite-imports.ts +++ b/packages/coding-agent/src/eval/js/shared/rewrite-imports.ts @@ -303,15 +303,27 @@ function returnFinalExpression(code: string): { source: string; returned: boolea let lastIndex = body.length - 1; while (lastIndex >= 0 && body[lastIndex]?.type === "EmptyStatement") lastIndex--; const last = lastIndex >= 0 ? body[lastIndex] : undefined; - if (last?.type !== "ExpressionStatement") return { source: code, returned: false }; - - const expression = last as BabelExpressionStatement; - const prefix = code.slice(0, expression.start); - const statement = code.slice(expression.start, expression.end); - const suffix = code.slice(expression.end); - const semicolonMatch = statement.match(/;\s*$/); - const trimmedStatement = semicolonMatch ? statement.slice(0, semicolonMatch.index) : statement; - return { source: `${prefix}__omp_set_final_expr__((${trimmedStatement}));${suffix}`, returned: true }; + if (last?.type === "ExpressionStatement") { + const expression = last as BabelExpressionStatement; + const prefix = code.slice(0, expression.start); + const statement = code.slice(expression.start, expression.end); + const suffix = code.slice(expression.end); + const semicolonMatch = statement.match(/;\s*$/); + const trimmedStatement = semicolonMatch ? statement.slice(0, semicolonMatch.index) : statement; + return { source: `${prefix}__omp_set_final_expr__((${trimmedStatement}));${suffix}`, returned: true }; + } + if (last?.type === "ReturnStatement") { + // Top-level `return value;` is otherwise swallowed: it forces the cell into an async IIFE + // wrapper that discards the returned value. Rewrite into `__omp_set_final_expr__((expr))` + // so the runtime can surface the value to the caller just like a trailing expression. + const ret = last as unknown as { start: number; end: number; argument?: { start: number; end: number } | null }; + if (!ret.argument) return { source: code, returned: false }; + const prefix = code.slice(0, ret.start); + const suffix = code.slice(ret.end); + const expr = code.slice(ret.argument.start, ret.argument.end); + return { source: `${prefix}__omp_set_final_expr__((${expr}));${suffix}`, returned: true }; + } + return { source: code, returned: false }; } function isExecutionBoundary(type: string): boolean { diff --git a/packages/coding-agent/src/eval/js/shared/runtime.ts b/packages/coding-agent/src/eval/js/shared/runtime.ts index ef2570931..f02761756 100644 --- a/packages/coding-agent/src/eval/js/shared/runtime.ts +++ b/packages/coding-agent/src/eval/js/shared/runtime.ts @@ -165,7 +165,8 @@ export class JsRuntime { const finalValue = this.#finalExpressionValue; this.#finalExpressionSet = false; this.#finalExpressionValue = undefined; - return await awaitMaybePromise(finalValue); + const resolved = await awaitMaybePromise(finalValue); + return resolved; } return awaited; }