Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions devlog/_plan/260827_bug_pr_merge_round/011_wp2_l1_outcome.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# wp2 — L1 lane outcome

Landed on `dev` as `2feffbdc3` (PR #2720), which carried four merges:

| PR | author | preserved as | disposition |
|---|---|---|---|
| #2672 | Ingwannu | `2a9c18dc5` | MERGED, no changes needed |
| #2674 | Ingwannu | `e3b136fb7` | MERGED, no changes needed |
| #2671 | DevonGithub | `17aadf88e` | MERGED + one added test (`4a4df12f2`) |
| #2684 | Michael-Z-Freeman | `2c85dd48d` | MERGED, checklist-only CI failures |

All four show `MERGED` on GitHub with a comment naming the landed sha.

## Evidence

- `bun run test` (full suite, local, session 77096): exit 0.
- PR #2720 CI after one rerun: 23 pass, 1 skipping, 0 fail.
- `bun x tsc --noEmit`: clean on the merged tree.
- Focused: forward-prompt-envelope + posit-continuation 9/9, muse-vision 6/6,
azure-model-router 2/2, repo-hygiene 11/11.

## The `test 1/4` failure was a flake, and it was checked rather than assumed

First CI run failed one case: `update stops the running proxy before replacing files >
npm launcher restarts the stopped runtime after a staged update failure`, at 46797ms.
The same case passes locally in 2.6s, this branch touches no update/launcher file
(`git diff --name-only 9b838d062 HEAD | grep -E 'update|launcher'` is empty), and the
test already carries two prior timing-budget repairs (`538a602af`, `34ef53966`). Rerun
of the failed job alone: green.

That is the standard this round applies — a rerun-to-green is only acceptable when the
causal question was actually asked first.

## Discovered constraint: `dev` is protected

A direct `git push origin dev` is rejected: "Changes must be made through a pull
request." So every later lane lands the same way — a `codex/` branch plus a PR, not a
local merge and push. The local merges are still how the work is built and verified;
they simply travel through a PR.

## Carried forward

- #2671's probe-evidence question (007, finding 10) is open on a MERGED change.
- #2690 is now guaranteed to conflict with `dev`, since #2684 landed. That was the
intended ordering, not an accident.
- `src/server/responses/core.ts` evidence for #2663/#2638/#2497/#2694 is unaffected by
this lane: none of the four L1 merges touched it.
45 changes: 32 additions & 13 deletions src/bridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
import { redactSecretString } from "./lib/redact";
import { repairFreeformToolInput } from "./responses/apply-patch-envelope";
import { encodeCompactionSummary } from "./responses/compaction";
import { compileCodeModeHelperInput } from "./responses/code-mode-helper-compat";
import { isTruncatedStopReason, truncationReasonFor } from "./responses/truncated-stop-reason";
import { encodeReasoningEnvelope, type ReasoningEnvelope } from "./responses/reasoning-envelope";
import { rememberReasoningForCall } from "./responses/reasoning-replay-cache";
Expand Down Expand Up @@ -244,9 +245,14 @@ export function bridgeToResponsesSSE(
// Freeform/custom tools (apply_patch, code-mode exec) carry their body in `input`; the
// model is given a function with `{input:string}`, so unwrap it here when relaying back
// as a custom_tool_call. Decorated apply_patch envelopes are repaired at this boundary.
const freeformInput = (args: string, toolName: string, namespace?: string): string => (
repairFreeformToolInput(args, toolName, namespace)
);
const freeformInput = (
args: string,
toolName: string,
namespace?: string,
codeModeHelperName?: string,
): string => codeModeHelperName
? compileCodeModeHelperInput(args, codeModeHelperName)
: repairFreeformToolInput(args, toolName, namespace);
// Best-effort unwrap of a PARTIAL freeform arg buffer for live input streaming
// (`response.custom_tool_call_input.delta` — codex-rs uses it for UI preview only;
// the completed custom_tool_call item stays authoritative). Compact `{"input":"...`
Expand Down Expand Up @@ -530,7 +536,7 @@ export function bridgeToResponsesSSE(
// synthetic compaction item's payload on done.
let compactionText = "";
let compactionTextBytes = 0;
let currentToolCall: { itemId: string; outputIndex: number; callId: string; name: string; args: string; argsBytes: number; namespace?: string; freeform?: boolean; toolSearch?: boolean; inputEmitted?: string; providerMetadata?: OcxProviderOpaqueToolCallMetadata } | null = null;
let currentToolCall: { itemId: string; outputIndex: number; callId: string; name: string; args: string; argsBytes: number; namespace?: string; freeform?: boolean; toolSearch?: boolean; inputEmitted?: string; codeModeHelperName?: string; providerMetadata?: OcxProviderOpaqueToolCallMetadata } | null = null;
// Open native web-search cell (between begin and end). Holds the output index allocated on
// begin so the matching done reuses it; closed as `failed` if the stream terminates early.
let currentWebSearch: { itemId: string; eventId: string; outputIndex: number } | null = null;
Expand Down Expand Up @@ -649,7 +655,7 @@ export function bridgeToResponsesSSE(
emit("response.custom_tool_call_input.done", {
item_id: currentToolCall.itemId, output_index: currentToolCall.outputIndex,
...(currentToolCall.namespace ? { namespace: currentToolCall.namespace } : {}),
input: freeformInput(currentToolCall.args, currentToolCall.name, currentToolCall.namespace),
input: freeformInput(currentToolCall.args, currentToolCall.name, currentToolCall.namespace, currentToolCall.codeModeHelperName),
});
}
// Freeform tools serialize as custom_tool_call without extra_content; remember the
Expand All @@ -666,7 +672,7 @@ export function bridgeToResponsesSSE(
type: "custom_tool_call", id: currentToolCall.itemId,
call_id: currentToolCall.callId, name: currentToolCall.name,
...(currentToolCall.namespace ? { namespace: currentToolCall.namespace } : {}),
input: freeformInput(currentToolCall.args, currentToolCall.name, currentToolCall.namespace), status: "completed",
input: freeformInput(currentToolCall.args, currentToolCall.name, currentToolCall.namespace, currentToolCall.codeModeHelperName), status: "completed",
}
: {
type: "function_call", id: currentToolCall.itemId,
Expand Down Expand Up @@ -706,7 +712,7 @@ export function bridgeToResponsesSSE(
type: "custom_tool_call", id: currentToolCall.itemId,
call_id: currentToolCall.callId, name: currentToolCall.name,
...(currentToolCall.namespace ? { namespace: currentToolCall.namespace } : {}),
input: freeformInput(currentToolCall.args, currentToolCall.name, currentToolCall.namespace), status: "incomplete",
input: freeformInput(currentToolCall.args, currentToolCall.name, currentToolCall.namespace, currentToolCall.codeModeHelperName), status: "incomplete",
}
: {
type: "function_call", id: currentToolCall.itemId,
Expand Down Expand Up @@ -1052,6 +1058,9 @@ export function bridgeToResponsesSSE(
}
if (currentToolCall) closeCurrentToolCall();
const effectiveName = normalizeDeclaredToolName(event.name, options?.declaredToolNames);
const codeModeHelperName = effectiveName === "exec" && event.name !== effectiveName
? event.name
: undefined;
const mapped = toolNsMap?.get(effectiveName);
const realName = mapped?.name ?? effectiveName;
if (options?.declaredToolNames && !options.declaredToolNames.has(effectiveName)) {
Expand Down Expand Up @@ -1083,7 +1092,7 @@ export function bridgeToResponsesSSE(
? { type: "custom_tool_call", id: itemId, call_id: event.id, name: realName, ...(ns ? { namespace: ns } : {}), input: "", status: "in_progress" }
: { type: "function_call", id: itemId, call_id: event.id, name: realName, arguments: "", status: "in_progress", ...(ns ? { namespace: ns } : {}) };
emit("response.output_item.added", { output_index: outputIndex, item });
currentToolCall = { itemId, outputIndex, callId: event.id, name: realName, args: "", argsBytes: 0, namespace: ns, freeform, toolSearch, providerMetadata: event.providerMetadata };
currentToolCall = { itemId, outputIndex, callId: event.id, name: realName, args: "", argsBytes: 0, namespace: ns, freeform, toolSearch, codeModeHelperName, providerMetadata: event.providerMetadata };
budget?.openCall(event.id);
break;
}
Expand All @@ -1102,7 +1111,7 @@ export function bridgeToResponsesSSE(
delta: event.arguments,
});
}
if (currentToolCall.freeform) {
if (currentToolCall.freeform && !currentToolCall.codeModeHelperName) {
// Hold while the buffer is still an ambiguous prefix of the JSON wrapper,
// then stream only the unwrapped input suffix (never rewind on mode flips).
if (!FREEFORM_WRAP_PREFIX.startsWith(currentToolCall.args)) {
Expand Down Expand Up @@ -1587,15 +1596,21 @@ function buildResponseJSONWithBudget(
let batchKiroRedactedBytes = 0;
let currentToolCallId = "";
let currentToolCallName = "";
let currentToolCallCodeModeHelperName: string | undefined;
let currentToolCallArgs = "";
let currentToolCallProviderMetadata: OcxProviderOpaqueToolCallMetadata | undefined;
let currentToolCallArgsBytes = 0;
// Web-search citations awaiting the next assistant message (attached as url_citation annotations).
let pendingWebSources: { url: string; title?: string }[] = [];

const freeformInput = (args: string, toolName: string, namespace?: string): string => (
repairFreeformToolInput(args, toolName, namespace)
);
const freeformInput = (
args: string,
toolName: string,
namespace?: string,
codeModeHelperName?: string,
): string => codeModeHelperName
? compileCodeModeHelperInput(args, codeModeHelperName)
: repairFreeformToolInput(args, toolName, namespace);
const parseArgsObj = (args: string): Record<string, unknown> => {
try { const o = JSON.parse(args); return o && typeof o === "object" ? o : {}; } catch { return {}; }
};
Expand Down Expand Up @@ -1697,7 +1712,7 @@ function buildResponseJSONWithBudget(
type: "custom_tool_call", id: `ctc_${uuid()}`,
call_id: currentToolCallId, name: realName,
...(ns ? { namespace: ns } : {}),
input: freeformInput(currentToolCallArgs, realName, ns), status,
input: freeformInput(currentToolCallArgs, realName, ns, currentToolCallCodeModeHelperName), status,
});
} else {
pushOutput({
Expand All @@ -1711,6 +1726,7 @@ function buildResponseJSONWithBudget(
budget?.closeCall(currentToolCallId);
currentToolCallId = "";
currentToolCallName = "";
currentToolCallCodeModeHelperName = undefined;
currentToolCallProviderMetadata = undefined;
currentToolCallArgs = "";
currentToolCallArgsBytes = 0;
Expand Down Expand Up @@ -1825,6 +1841,9 @@ function buildResponseJSONWithBudget(
currentToolCallId = e.id;
budget?.openCall(e.id);
currentToolCallName = effectiveName;
currentToolCallCodeModeHelperName = effectiveName === "exec" && e.name !== effectiveName
? e.name
: undefined;
currentToolCallArgs = "";
currentToolCallArgsBytes = 0;
currentToolCallProviderMetadata = e.providerMetadata;
Expand Down
50 changes: 50 additions & 0 deletions src/responses/code-mode-helper-compat.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { normalizeApplyPatchDelimiters } from "./apply-patch-envelope";

function isPlainObject(value: unknown): value is Record<string, unknown> {
return !!value && typeof value === "object" && !Array.isArray(value);
}

function unwrapPatchInput(value: string): string {
try {
const parsed: unknown = JSON.parse(value);
if (isPlainObject(parsed)) {
if (typeof parsed.input === "string") return parsed.input;
if (typeof parsed.patch === "string") return parsed.patch;
}
} catch {
// Native custom calls carry the patch body directly.
}
return value;
}

/**
* Convert a nested Code Mode helper call into unified-exec JavaScript.
*
* Parsed values are serialized as data, never interpolated as source, so command and patch text
* cannot escape the generated call. Invalid structured shell payloads are also passed as data so
* nested-tool validation can reject them without evaluating provider text as JavaScript.
*/
export function compileCodeModeHelperInput(argumentsText: unknown, toolName: string): string {
if (typeof argumentsText !== "string") return "";
if (toolName === "apply_patch") {
const patch = normalizeApplyPatchDelimiters(unwrapPatchInput(argumentsText));
return `const result = await tools.apply_patch(${JSON.stringify(patch)});\ntext(result);`;
}
let parsed: unknown = argumentsText;
try {
parsed = JSON.parse(argumentsText);
} catch {
// Keep malformed provider text as data rather than executable source.
}
const args: unknown = isPlainObject(parsed) ? { ...parsed } : parsed;
if (
toolName === "shell_command"
&& isPlainObject(args)
&& typeof args.command === "string"
&& args.cmd === undefined
) {
args.cmd = args.command;
delete args.command;
}
return `const result = await tools.exec_command(${JSON.stringify(args)});\ntext(result);`;
}
44 changes: 34 additions & 10 deletions src/responses/custom-tool-compat.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { namespacedToolName } from "../types";
import { namespacedToolName, normalizeDeclaredToolName } from "../types";
import {
normalizeApplyPatchDelimiters,
repairFreeformToolInput,
unwrapFreeformToolInput,
} from "./apply-patch-envelope";
import { compileCodeModeHelperInput } from "./code-mode-helper-compat";
import { collectResponsesToolGroups } from "./tool-groups";

const ROUTED_CUSTOM_TOOL_PASSTHROUGH = new Set(["apply_patch"]);
Expand Down Expand Up @@ -65,6 +66,20 @@ export function routedCustomToolWireName(value: unknown): string | undefined {
);
}

/** Resolve a provider-emitted wire name to the routed custom tool the client declared. */
export function routedCustomToolTargetName(
value: unknown,
names: ReadonlySet<string>,
declaredNames?: ReadonlySet<string>,
): string | undefined {
const wireName = routedCustomToolWireName(value);
if (wireName === undefined) return undefined;
if (names.has(wireName)) return wireName;
if (!isPlainObject(value) || typeof value.namespace === "string") return undefined;
const normalized = normalizeDeclaredToolName(wireName, declaredNames);
return normalized !== wireName && names.has(normalized) ? normalized : undefined;
}

/**
* Names of custom declarations after namespace lowering. The selection flag separates converted
* names from native passthrough names while keeping same-named function and custom children distinct.
Expand Down Expand Up @@ -250,29 +265,37 @@ export function restoreRoutedCustomCalls(
value: unknown,
names: ReadonlySet<string>,
repairNames: ReadonlySet<string> = new Set(),
declaredNames?: ReadonlySet<string>,
): { value: unknown; changed: boolean } {
if (!isPlainObject(value)) return { value, changed: false };

const restoreItem = (item: unknown): { value: unknown; changed: boolean } => {
if (!isPlainObject(item)) return { value: item, changed: false };
const wireName = routedCustomToolWireName(item);
const targetName = routedCustomToolTargetName(item, names, declaredNames);
if (
item.type === "function_call"
(item.type === "function_call" || item.type === "custom_tool_call")
&& typeof item.name === "string"
&& wireName !== undefined
&& names.has(wireName)
&& targetName !== undefined
) {
const sourceInput = item.type === "function_call" ? item.arguments : item.input;
const aliased = targetName !== wireName;
const restored: Record<string, unknown> = {
...item,
type: "custom_tool_call",
id: customToolItemId(item.id),
input: repairFreeformToolInput(
item.arguments,
item.name,
typeof item.namespace === "string" ? item.namespace : undefined,
),
name: aliased ? targetName : item.name,
input: aliased && sourceInput !== ""
? compileCodeModeHelperInput(sourceInput, item.name)
: repairFreeformToolInput(
sourceInput,
targetName,
typeof item.namespace === "string" ? item.namespace : undefined,
),
};
delete restored.arguments;
if (aliased) delete restored.namespace;
return { value: restored, changed: true };
}
if (
Expand Down Expand Up @@ -323,7 +346,7 @@ export function restoreRoutedCustomCalls(
&& value.type.startsWith("response.")
&& isPlainObject(value.response)
) {
const response = restoreRoutedCustomCalls(value.response, names, repairNames);
const response = restoreRoutedCustomCalls(value.response, names, repairNames, declaredNames);
if (response.changed) {
restored.response = response.value;
changed = true;
Expand All @@ -337,6 +360,7 @@ export function restoreRoutedCustomCallsInJson(
text: string,
names: ReadonlySet<string>,
repairNames: ReadonlySet<string> = new Set(),
declaredNames?: ReadonlySet<string>,
): string {
if (names.size === 0 && repairNames.size === 0) return text;
let payload: unknown;
Expand All @@ -345,7 +369,7 @@ export function restoreRoutedCustomCallsInJson(
} catch {
return text;
}
const restored = restoreRoutedCustomCalls(payload, names, repairNames);
const restored = restoreRoutedCustomCalls(payload, names, repairNames, declaredNames);
return restored.changed ? JSON.stringify(restored.value) : text;
}

Expand Down
Loading
Loading