* feat(agent-core): repair malformed tool args JSON
Attempt jsonrepair when tool call arguments fail JSON.parse, then continue schema validation. Return malformed JSON errors with a concise expected schema hint so the model can retry with corrected arguments.
* chore(nix): update pnpm deps hash
Update the fixed-output pnpmDeps hash after adding jsonrepair so the Nix build can fetch dependencies.
* refactor(agent-core): drop tool args JSON repair
Stop repairing malformed tool call arguments. Fall back to an empty object on JSON parse failure and let schema validation produce the retry error, while preserving valid arguments for unknown tools in the transcript.
* chore: add changeset for tool args validation
Map OAuth token-fetch failures to distinct public error codes instead of collapsing them all to auth.login_required:
- missing/revoked tokens or 401/403 from the refresh endpoint -> auth.login_required
- transport failures and 429/5xx after internal retries -> provider.connection_error
- anything else is rethrown as-is (surfaces as internal) rather than guessed
The refresh helper already retries internally, so the agent loop does not
re-retry these. Both the managed provider and the standalone SDK provider
share the same mapping.
When the user interrupts running tools or parallel subagents, the tool_result
fed back to the model was a neutral `Tool "X" was aborted` or a weak "stopped by
the user", so the model treated it as a system fault and speculated about
capacity/concurrency limits instead of recognizing a deliberate stop.
Carry a UserCancellationError as the AbortSignal reason from the cancel sites
(Turn.cancel/abortTurn, SessionSubagentHost.cancelAll) through to the message
sites (tool-call settle paths and the AgentTool catches), which now emit an
explicit "deliberate user action, not a system error/timeout/capacity limit"
message. Aborts propagated from another signal (e.g. a subagent's deadline via
waitForCurrentTurn) carry their original reason, so a timeout is not mislabeled
as a user interruption. The telemetry outcome classifier matches the new
"manually interrupted" phrase to keep counting these as cancelled.
A mid-stream SSE drop surfaces as a raw undici `TypeError: terminated`, which was classified as a non-retryable generic error and failed the turn on the first attempt. Route raw transport-layer errors through the connection-error heuristic so a dropped stream becomes a retryable APIConnectionError and is retried transparently. User aborts (ESC) are unaffected — the retry loop checks the abort signal before retrying.
Related to #149.
Merge turnId/step into a single `turnStep` field ("0.1") and
attempt/maxAttempts into `attempt` ("2/3"), and drop the
messageCount/toolCallCount fields. The per-request `llm request`
line goes from up to 8 fields down to ~3; the `llm config` line
(including thinkingEffort, logged for all providers) is unchanged.
* fix(agent-core): always pair tool.call with tool.result on malformed returns
A tool returning undefined, a primitive, or an object without a valid
output field crashed normalization, exited the batch loop without
dispatching the matching tool.result, and left the next provider
request to fail with a missing tool_call_id response.
- Validate the tool return at the boundary in runRunnableToolCall via a
new coerceToolResult helper; malformed returns become an isError
result.
- Harden normalizeToolResult and toolResultStopsTurn against
non-conformant input from synthetic results or finalize hooks.
- Track unpaired tool.call ids in runToolCallBatch and emit
compensating error results in finally for any that did not receive a
result.
* refactor(agent-core): simplify tool result pairing
Drop the unpairedCallIds tracking and finally-compensation wrapper from
runToolCallBatch. The set was updated before dispatchEvent, so a failure
between the two left it inconsistent; the per-call try/catch already
produces a paired error result, making the outer wrapper redundant.
Coerce hook returns at the synthetic and finalizeToolResult boundaries
so a malformed hook output is normalized into a paired error result the
same way a malformed tool return already is.