Generated titles came back in Title Case because none of the title prompts stated a capitalization style; asked for a "title" with no case rule, models default to Title Case and nothing downstream corrects it. The tool-call purpose titles already ask for sentence case, so session titles, Discord auto-thread titles, and Telegram DM topic labels were the outliers.
Sets sentence case in each title prompt rather than post-processing: the titles follow the user's language, and sentence-case rules are language-specific, so a mechanical normalizer would mangle German nouns and proper nouns like OpenClaw or GitHub.
* fix(slack): keep commentary progress on draft lane
* fix(channels): route commentary through one progress owner
Keep non-verbose commentary in Slack and Discord draft lanes while preserving one durable commentary payload when verbose progress makes those drafts yield. Freeze that owner decision for the turn so session changes apply on the next turn.
Co-authored-by: Dallin Romney <dallinromney@gmail.com>
Punchcard-Session: amber-workshop-workshop-36
* fix(channels): refresh queued commentary owner
Recompute the frozen draft-versus-durable commentary owner for every queued follow-up turn and carry that decision into final payload projection.
Punchcard-Session: amber-workshop-workshop-36
* fix(channels): clarify commentary owner opt-in
* fix(channels): preserve queued draft preambles
* fix(channels): require explicit queued progress ownership
* refactor(channels): record commentary progress owner
* fix(telegram): tolerate unscoped queued updates
* test(discord): use canonical draft fixture after rebase
---------
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
Reject reserved /healthz webhook configurations before registration, preventing silent Telegram update loss. Warn through Doctor and document recovery.
Co-authored-by: Peter Lee <22994703+xialonglee@users.noreply.github.com>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
Preserve reply and selected-quote metadata from later Telegram debounce and fragment-batch messages while keeping reply-chain recovery bounded.
Co-authored-by: iridescentWen <whatmakesbetter@outlook.com>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
Telegram native-command registration resolved provider names through the bundled channel-plugin fallback, which jiti-transpiles the entire plugin plus core graph from source when no registry entry is loaded (~190s silent CPU) — tripping the 300s no-output CI watchdog on the two Telegram test files that register at module scope. Telegram defines no resolveNativeCommandName hook, so the load resolved nothing. Thread the existing includeBundledChannelFallback option through the spec-listing helpers and use loaded-registry-only lookups in the Telegram plugin, matching the Discord sibling. Runtime names are byte-identical; both files drop from 200s to ~5s.
Reject unbound foreign-channel fallback at shared approval-account selection while preserving recorded bindings and explicit forwarding targets. Cover Telegram and Matrix routing contracts.
Co-authored-by: vatsalgargg <vatsalg80@gmail.com>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
Preserve raw query separators in Telegram rich-message bare URLs by leaving parser-generated links to Telegram entity detection. Authored Markdown links remain typed, while file references remain code-formatted.
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
Treat <internal> blocks as private reasoning in the shared parser and remove Telegram raw-reasoning fallbacks. This keeps model reflections out of user-visible replies while preserving surrounding answer text.
Closes#122623
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
Co-authored-by: WangYan <wang.yan29@xydigit.com>
Class provenance: #114388 made multi-agent ownership explicit; prior partial sweeps #122883 and #122978 repaired Codex, Copilot, ClickClack, and policy fixtures.
Per-file changes:
- extensions/telegram/src/bot.create-telegram-bot.test.ts: declare startup owners for the four reload fixtures and add the default Telegram binding for the topic override case while preserving dynamic account/topic routing assertions.
* fix(channels): validate headless channel setup
* docs(channels): document headless provisioning
* fix(channels): repair setup metadata typing
* chore(channels): regenerate official channel catalog for env metadata
* fix(slack): keep mode-conditional env contract plugin-owned
Static --use-env declaration keeps only the unconditional SLACK_BOT_TOKEN;
socket-vs-HTTP conditional requirements (app token, signing secret) stay in
Slack's own setup validation so HTTP mode no longer demands an irrelevant
SLACK_APP_TOKEN.
* chore(sdk): regenerate api baselines and catalog after rebase
* fix(slack): align manifest env declaration with runtime contract
* chore(sdk): regenerate api baselines after rebase
* chore(sdk): regenerate api baselines after rebase
* chore(sdk): regenerate api baselines after rebase
The bot-handlers family was 22 files behind a 22-line fan-out: six files holding one callback switch, four holding one inbound pipeline, every interface a derived ReturnType, processMessage taking 8 positional args, and its params type declared inside bot-native-commands.ts. Three named coordinators (inbound pipeline, callback router, event bindings) now sit behind the same registerTelegramHandlers entry with hand-written leaf contracts; the factory graph, 16 slice/barrel files, and the type shim are deleted. Behavior-neutral: bot.test.ts and the ingress e2e byte-identical to main (SHA-256-pinned) and green; +185 production LOC accepted as the written-contract tradeoff.
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
The command-menu locale ledger reads state.openKeyedStore during
hydration; the fixture's partial runtime stub lacked it, so the
degrade warning broke both claim-watchdog cancellation tests on main.
Provision it like the dispatch harness does. Surfaced by #122091's
follow-up work; suite runs locally only (see #122029 decision).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The four Telegram dispatch controllers were partitions of one closure: ~75 factory parameter slots, 7 post-construction back-edge setters, a shared mutable state bag, and load-bearing construction order. One turn record now carries the once-resolved config and all state; the four files remain as implementation with a hand-written leaf type contract (four state-slice types). Rides along: dead generation fence deleted (constant-0 from birth), queuedFinal ||= fix with regression (suppressed exec-approval turns no longer trigger a spurious fallback), collapse resolver/mutator split. Dispatch tests and harness byte-identical to main; live E2E lifecycle proof on the PR.
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
Centralize Telegram text delivery: page planning and reply-target progression move into telegram-text-delivery.ts, shared by the durable send, edit, and streaming-final paths; bot/reply-threading.ts and per-path split/page logic are deleted. Formatted-to-plain recovery routes through withTelegramPlainFallback everywhere. Restores the projection-cursor invalidation contract on terminal multi-page failures (with regression). Net -321 production LOC. Live E2E on the PR: formatted entity path, plain-fallback recovery, and 2-page chaining.
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
#121600 (4c951398ef) made default command progress status-only — raw
command text stays out of chat previews. This assertion still expected
the pre-#121600 preview with the raw command line; bisect-confirmed
first-bad at 4c951398ef. Pre-existing failure surfaced by the
dispatch-suite CI gap (#122029). Dispatch suite now 247/247.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
resolveDeliveryTarget (src/channels/turn/durable-delivery.ts:57) reads
OriginatingTo/To — channel-prefixed refs — as its own fallback chain, so
telegram:-100555 is the contract shape for the durable 'to', not the
bare chat id this assertion expected. Pre-existing failure surfaced by
the dispatch-suite CI gap (#122029).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
#121908 (210aca6de3) added the onPlatformSendDispatch options bag to
final draft-stream updates and updated draft-failures-progress.test.ts,
but missed 19 sibling assertion sites across 6 dispatch test files.
Those files do not currently execute in any CI lane, so main's local
dispatch suite has been red since. Two remaining failures
(context-recovery session-target prefix, draft-failures-progress
preview shape) predate #121908 and are tracked separately.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The Telegram send-funnel parity invariant (durable and streaming funnels must degrade formatted sends identically) was prose-enforced and had drifted: draft previews and message edits only degraded on HTML-parse 400s, not empty-content 400s. One orchestrator (withTelegramPlainFallback) now owns classification, guard, logging, and plain-chunk planning for all 11 call sites; withTelegramHtmlParseFallback and buildTelegramPlainFallbackPlan are deleted. Net -29 production LOC. Behavior change: previews/edits recover empty-content 400s as plain text; degrade logs unify to `degrade=plain-fallback:<trigger>`.
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A final reply whose platform send was accepted but whose response was lost
previously ended in silence. Custody that stays unknown after a claimed send
now records durable pendingDeliveryNotice debt; the next same-route turn
delivers one "could not confirm delivery" notice and acknowledges it into the
transcript. Restart recovery completes ambiguous sessions with the same debt
instead of a fire-and-forget notice; the debt survives reset and rollover, and
suppressed notice sends retain it instead of faking delivery. Permanent typed
no-send rejections settle as terminal suppression (no replay, no false
notice); retryable ones restore prepared custody for safe replay. Google Chat
media-only rejections use the typed no-send contract; Telegram native-command
replies join pending-final custody.
Fixes#80362
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
Re-lands the reverted #121507 on the current custody contracts. The reply dispatcher now owns direct-send pending-final custody: claim before provider I/O, terminal settlement for delivered/suppressed/failed outcomes, proven no-send stays replayable, ambiguous evidence fails closed — so Gateway restarts can no longer duplicate an already-accepted final reply.
Proof: ClawSweeper local review clean, exact-head ci-gate green, live Telegram E2E (one turn, one final, no duplicates).
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
Native approval delivery and resolution now stay bound to the originating or explicitly targeted channel account. Unbound requests fail closed across multiple eligible accounts; trusted reviewer-less SDK callers remain compatible.
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
Keep raw commands, paths, and provider errors out of ordinary chat while preserving explicit raw diagnostics and structured admin history.
Default command progress is status-only; `/verbose full` and `commandText: "raw"` retain diagnostic detail.
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
* fix(codex): report harness context window as session contextTokens
Codex app-server reports model_context_window per turn. Carry it through the projector into the run result meta so session rows show the real window instead of the catalog's standard-tier input cap (272k vs 1M for gpt-5.6 models).
* improve(ui): compact chat context popover
Inline stat rows replace boxed tiles; zero-value cost rows and the whole cost section when empty are omitted; provider/model provenance lines are removed because the footer already shows the model; and the popover is narrowed to 300px.
* refactor(codex): split attempt-result assembly out of event projector
* fix(codex): seed attempt context window from startup binding
App-server v2 turn/started omits the core model_context_window, so thread/tokenUsage/updated is the only live carrier. Seed usage-less attempts from the retained startup binding rollout/session window so session metadata cannot regress to the catalog fallback.
* fix(codex): prefer native startup context window
Persisted session contextTokens has no source provenance and may contain the catalog fallback. Keep the minimum window for the conservative rotation fuse, but seed the projector from the native rollout when it is available.
* chore(plugin-sdk): regenerate api baseline (new format)
* revert(gateway): "prevent restart replay after final delivery" (broke 5 CI jobs)
Record channel delivery custody before recipient-visible sends so restart recovery cannot duplicate accepted finals.
Ambiguous legacy markers now fail closed with a visible interruption instead of blind replay.
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
* fix(telegram): preserve unsent media while deduplicating streamed replies
## What Problem This Solves
A Telegram final reply could resend an attachment already delivered in a streamed block when the legacy mediaUrl still referenced that sent attachment while another mediaUrls item remained. Conversely, unsent legacy-only attachments must not be discarded.
## Why This Change Was Made
The Telegram media-deduplication owner now clears mediaUrl only when its normalized attachment was actually sent, preserving independently unsent legacy attachments and preventing downstream outbound planning from restoring delivered media.
## User Impact
Telegram replies retain every unsent attachment exactly once and no longer duplicate already streamed images in mixed final-message payloads.
## Context
Seven zero-dependency scenarios passed against the actual media owner and outbound planner, including mixed legacy/vector attachments, whitespace normalization, visible-send handling, and remaining-media preservation. The exact frozen campaign baseline passed 844 tests. Existing oxfmt formatting and staged whitespace checks passed; focused Vitest CI remains pending.
* refactor(telegram): inline legacy media dedup check
Use the canonical topic → group → account → default policy for native commands. Disabled scopes now stop before routing and dispatch; sender-specific authorization remains explicit.
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
Co-authored-by: JuniperTheDev <juniperthedev@users.noreply.github.com>