Skip to content

fix(output): unify sanitize/budget layer across Discord, TUI, CLI, and agents (salvage #758+#775+#782) - #787

Merged
unohee merged 1 commit into
mainfrom
salvage/output-sanitize
Sep 28, 2026
Merged

unohee merged 1 commit into
mainfrom
salvage/output-sanitize

Conversation

@unohee

@unohee unohee commented Sep 28, 2026

Copy link
Copy Markdown
Collaborator

Salvage of draft PRs #758, #775, #782 into one coherent sanitize/budget layer.

All three drafts were abandoned by the broken autonomous loop (172–247 commits
behind, bodies saying "failed 4 times"). They overlap on discordHandlers.ts,
tui/sanitize.ts, and cliRunner.ts, so they are reconciled here into a single
layer instead of three competing ones.

Kept

src/support/outputBudget.ts (new) — from #782. Destination-specific
field/message/aggregate limits (Discord embed per-field + 6000 aggregate +
1900 message, Linear, prompt, audit, TUI, CLI, pipeline) and the helpers
truncate / truncateWithSuffix / capArray / codeList /
boundedFieldValue / boundedDescription / boundedMessageContent /
boundedLinearText / flattenToSingleLine / sanitizeException /
genericUserError / paginateEmbedFields. Wired into 9 consumers:
pipelineFormat, discordHandlers, cliRunner, tester, workerAuditLog,
workflowLinear, AuditBoard, DataTable, plus reviewer / skillDocumenter.

src/discord/embedUtils.ts (new) — from #758. EMBED_LIMITS plus
safeAddField / safeSetTitle / safeSetDescription / safeSetFooter /
enforceAggregateBudget / truncateField. The limits are re-exported from
outputBudget.ts rather than restated, so the embed layer and the
destination-agnostic helpers cannot drift apart.

src/tui/sanitize.ts — #758 and #775 reconciled. Adds
sanitizeAndBoundTerminalText + MAX_RENDERED_LINE_LENGTH /
MAX_TOTAL_RENDERED_CONTENT (#758), escapeHtml / formatMonitorError
(#758), and sanitizeAndNeutralize / clampAndSanitize (#775).
sanitizeAndNeutralize now delegates its control-character pass to
sanitizeTerminalText instead of a second control-character regex.

src/adapters/codexResponses.ts — #775's createResponsesReducer, a
bounded incremental reducer that replaces events.push(ev) +
reduceResponsesEvents(events) on the live stream path, so the full parsed
event history is never retained. reduceResponsesEvents remains as a thin
wrapper for the batch/test path. Folds in #762's MAX_FRAME_LENGTH and
MAX_REASONING_BUF frame bounds from the streaming-bounds group.

src/discord/discordCore.ts — #775's withDisabledMentions /
allowedMentions: { parse: [] } applied to every reply/send/thread.send, so
externally derived content cannot trigger mention parsing.

#758 render-path wiring — discordHandlers, discordPair, cliRunner,
ChatLog, MonitorPanel, and the en/ko prompt catalogs (evidence bounding,
aggregate feedback cap, MAX_FEEDBACK_ITEMS).

Dropped

Conflicts resolved

Verification

  • npx tsc --noEmit → clean.
  • npx oxlint on the touched files → 0 warnings, 0 errors.
  • npx vitest run src/discord/ src/tui/ src/support/outputBudget.test.ts src/support/workflowLinear*.test.ts src/runners/ src/adapters/codexResponses.test.ts src/agents/{pipelineFormat,tester,reviewer,skillDocumenter} src/automation/workerAuditLog src/locale/
    → 47 files passed, 512 passed | 1 skipped.
  • outputBudget.test.ts is new (13 cases); sanitize.test.ts gains 18 cases
    covering the merged surface.

Two tests from the drafts were rewritten rather than carried over: one asserted
a behaviour the drafts' own code did not have (sanitizeTerminalText on
a\x00b\x01c\x07d\ne\tf yields abcd\ne\tf, not ab\nd\ne\tf), and the
codexResponses "bounded memory" case compared two ~800 KB heap deltas, which
measures GC timing rather than retention; it now asserts the streaming and batch
paths agree on a 10k-event stream.

Original drafts

Supersedes #758, #775, #782. Close them once this lands.

…gents

Salvaged from draft PRs #758, #775, #782 (all abandoned mid-loop).

- support/outputBudget.ts: destination-specific field/message/aggregate limits
  plus truncate/capArray/paginateEmbedFields/genericUserError helpers.
- discord/embedUtils.ts: EMBED_LIMITS + safeAddField/safeSetTitle/
  safeSetDescription/safeSetFooter/enforceAggregateBudget/truncateField.
- tui/sanitize.ts: sanitizeAndBoundTerminalText, sanitizeAndNeutralize,
  clampAndSanitize, escapeHtml, formatMonitorError.
- codexResponses: incremental createResponsesReducer (no event-history
  retention) + MAX_FRAME_LENGTH/MAX_REASONING_BUF stream bounds.
- discordCore: allowedMentions parse:[] on every reply/send.
- Wired: discordHandlers, discordPair, cliRunner, pipelineFormat, tester,
  reviewer, skillDocumenter, workerAuditLog, workflowLinear, AuditBoard,
  DataTable, ChatLog, MonitorPanel, en/ko prompts.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant