fix(coding-agent): extension failures render as a calm notice; stacks go to the debug log (#4990)
CO
2d ago+961+961+012345678901234567890123456789-178-178-012345678901234567890123456789
Part of #4990
What changed
An extension handler failure used to reach the TUI as Extension "<absolute path>" error: <message> followed by every stack frame (URL-encoded senpi-extension: module frames, node:events, processTicksAndRejections). Every surface now builds one notice from the failure, and no surface renders the stack:
- Contract (
core/extensions/user-facing-error.ts, exported from the package):UserFacingError/userFacingErrorOf/UserFacingErrorInfo=code+message+ optionalaction+ optional http(s)docsUrl. Recognition is structural (an ownuserFacingproperty), so a bundled extension opts in without importing senpi. Malformed contracts fall back to the unexpected path. - One formatter (
core/extensions/error-notice.ts): coded failure -> title = what happened, detail = what to do, footer<extension> · <CODE>. Anything else ->Something went wrong in <extension>, the first line of the message with file paths andsenpi-extension:module URLs cut to file names (240 chars max),(during <event>), andDetails: <debug log>. - Stacks to the debug log:
ExtensionRunner.emitErrorrecords every failure (message + stack, through the existing redactingappendDebugLogEntry). All runner and context-dispatch capture sites (14) now go throughextensionFailureFields, which also carriesuserFacing. - Surfaces: TUI
showExtensionErrorshows a warning-tone notice box (sanitized, as before); print mode writes the same lines to stderr; RPCextension_errorkeepserrorfor existing clients and adds a stack-freenotice; startup diagnostics (provider / virtual model registration) and project_trust failures use the one-line form;senpipackage commands no longer print a settings error's stack. - Docs:
docs/extensions.md(Error Handling + example),docs/rpc.md(extension_error.notice).
Before -> after for the report in #4990:
Extension "~/.omo/omo-versions/<v>/.../omo.js" error: COMPUTER_LAUNCH_CONTEXT_REQUIRED: ...
at finish (senpi-extension:0/...omo-computer-use.js:178:40)
...
Computer use cannot start safely from this host's current macOS launch context.
Restart this host from a local terminal and keep that terminal app running, or use the desktop app (Settings > Restart engine).
computer · COMPUTER_LAUNCH_CONTEXT_REQUIRED
Tests
test/suite/regressions/issue-4990-calm-extension-error-notice.test.ts: real loader + runner +InteractiveModerenderer. RED on main (rendered the absolute path headline and full stack; debug log absent), GREEN here: coded and uncoded failures render with no stack frame, no temp path, nosenpi-extension:, noExtension "framing; inline snapshot of the calm notice; the stack is in the debug log.test/extension-error-notice.test.ts: contract parsing (class + structural, rejects malformed, http(s)-only docs), source names, first-line + path cutting (POSIX, Windows, module URLs,/loginuntouched), control-sequence stripping (the assertions of the removedextension-error-format.test.ts), RPC record shape.- Neighbours green: extensions-runner, agent-session-services-providers, extension-load-diagnostics, print-mode, interactive-mode-status, 8423/9068 regressions (163 tests).
bun run checkrc=0.
QA and the whole-suite receipt follow in comments.
Summary by cubic
Extension handler failures now render as a calm notice instead of a crash dump. The old Extension "<path>" error: headline and stack frames are gone, replaced by the same notice across TUI, print mode, RPC, and startup diagnostics. Stacks go to the debug log; no interface shows them.
- A new
UserFacingErrorcontract (recognized structurally via auserFacingproperty, so bundled extensions opt in without importing senpi) lets a failure carry a code, message, optional action, and docs URL. Everything else shows "Something went wrong in<extension>" with the first line, paths cut to file names, and the log location. - Every failure site routes through one helper, including deferred (
ctx.afterSessionStart) calls, session moves, and session-control reload. Thrown values are read through guarded property reads, so a throwing getter or Proxy falls back to the unexpected path instead of escaping handler isolation. - Uncoded reasons strip control and terminal sequences, cut quoted, UNC, and spaced home-directory paths to file names, cap at 240 chars without splitting surrogate pairs, and name slash-command sources
/<command>; the Windows debug-log path shortens to~. - RPC
extension_errorgains a stack-freenotice;errorstays for existing clients. Docs and changelog updated, with regression tests for coded and uncoded failures.