Keep readable native images when another attachment fails resolution.
Suppress only unresolved media facts across persisted and transient runner paths.
Co-authored-by: SunnyShu0925 <shu.zongyu@xydigit.com>
* fix(agents): resolve session-stable reply mode for direct resolver turns
Heartbeat wakes and other direct getReplyFromConfig callers bypass
dispatch, so no session-stable delivery mode reached their CLI binding
facts and prepare computed no messageToolPolicyHash, while dispatched
chat turns hashed the stable mode. resolveCliSessionReuse treats that
one-sided hash as a policy change, hard-invalidating the binding on
every chat<->heartbeat transition and leaving heartbeats to run in
fresh CLI sessions with no conversation history (historyPrompt=none).
Extract the synthetic-turn stable-mode resolution that
prepareAgentCommandExecution already used into
resolveSessionStableReplyMode, and apply it in prepareReplyRunContext
whenever a synthetic turn arrives without dispatch's injected mode, so
every turn kind on a session derives the same binding facts and hash.
Fixes#121485
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(agents): derive synthetic binding facts from the dispatch policy owner
Consolidate the synthetic-turn stable-mode resolution onto dispatch's own
policy pieces instead of a simplified parallel resolver:
- resolveVisibleRepliesPolicy is extracted from dispatch prepare-context
(configured visible-replies guard + harness default chain) and shared, so
the fallback and dispatch cannot drift on harness defaults, live-vs-entry
chat facts, or ExplicitDeliverRoute.
- The stable fact now applies dispatch's messageToolAvailable downgrade via
the canonical tool-policy resolvers: tool-only delivery with a policy-denied
message tool records automatic, matching dispatch.
- Synthetic turns no longer fall back to their effective turn mode: a
response-tool heartbeat's message_tool_only is per-turn enforcement, not
session policy, and previously bypassed the stable fallback entirely.
Regression coverage: response-tool heartbeat facts match dispatched turns,
and a message-tool-denied config downgrades the synthetic stable fact.
Refs #121485
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(agents): keep the session-stable reply mode sender-independent
Dispatch's stable-mode downgrade used the turn's sender-aware message-tool
availability, so a sender-scoped message denial hashed the stable policy as
automatic on chat turns while sender-less synthetic turns hashed tool-only —
the same binding reset loop on another axis.
resolveStableMessageToolAvailability becomes the one sender-independent
owner: dispatch passes it as sessionStableMessageToolAvailable for the
stable-mode resolution only (effective per-turn enforcement keeps the
sender-aware verdict), and synthetic binding facts already consume it.
Computed only when the visible-replies candidate is message_tool.
Refs #121485
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(agents): derive stable reply facts from session surfaces, not wake plumbing
Third-pass review fixes on the #121485 consolidation:
- Strip system-event wake providers ("heartbeat", "cron-event") from the
stable context and resolve origin-less entries as internal, so synthetic
facts take the same internal-channel branch dispatch's live webchat turns
do instead of falling through to harness defaults.
- Fall back to persisted session facts (delivery channel/origin, groupId,
groupChannel/subject, accountId) for the sender-independent availability
stack, so bare-ctx callers like command prepare resolve the same
group/account-scoped policies as dispatched turns; dispatch now passes its
session entry too.
- Collapse the redundant second mode resolution into the availability
downgrade, surface dispatch's injected stable mode separately from
resolvePromptSourceReplyMode so the synthetic slot has one owner, and
reuse resolveTurnModelOverride instead of an inline copy.
- Refresh docs/.generated/plugin-sdk-api-baseline.jsonl: closure hashes only
(17 exports, zero declaration changes, verified) — plugin-sdk reply/meeting
entrypoints transitively bundle the reply pipeline this branch refactors.
Regression: origin-less entry heartbeat resolves internal-automatic facts.
Refs #121485
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* chore: adapt to main module moves and split API baseline
Mechanical rebase refresh onto d3696f8d76: subagent-capabilities moved to
src/agents/subagents/spawn/, and the plugin-sdk API baseline is now
per-entrypoint content hashes (#122082) — regenerated for the reply-pipeline
closure this branch touches.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
Use successful per-attachment staging results in unsupported-document guidance so sandboxed agents receive only workspace-readable paths. Failed or partial staging keeps the existing fallback.
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
* refactor(agents): delete dead model-selection surface, consolidate compaction target assembly
Removes production surface with zero live callers: buildConfiguredAllowlistKeys
(orphaned since fallback allowlist filtering was split out), the
retryTransientProviderRuntimeMiss resolveModelAsync option (its gateway startup
prewarming caller was replaced by prepared runtime snapshots), the ignored
useAsyncModelResolution plumbing (kept only as a deprecated no-op field on the
plugin-SDK-shipped prepareSimpleCompletionModelForAgent), and dead facade
re-exports (inferUniqueProviderFromCatalog, ThinkLevel, ModelRefStatus).
Consolidates resolveEmbeddedCompactionTarget's five hand-built result sites into
one assembleTarget helper owning the auth-profile-drop-on-provider-change rule;
resolution precedence is unchanged and now pinned by new table-driven cases
(unique-provider inference, ambiguous literal, profile-suffix preservation).
Net -93 production LOC, -106 test LOC.
* chore(sdk): regenerate plugin SDK API baseline after facade export removals
Remove Discord's duplicate whole-turn session serialization so corrections reach shared reply admission and steer the active run. Preserve shared steer, followup, collect, and interrupt behavior.
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
Let eligible embedded host runs inspect root-approved unsupported documents after final sandbox, filesystem, provider, owner, and tool-policy gates. Generic ACP, sandboxed, URL-only, and restricted-tool paths retain the plain marker.
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
Use the selected harness for isolated title generation, including native Codex auth.
Retry failed dashboard titles from the first user message without overriding manual names.
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
* refactor: consolidate coercion ownership
Centralize four canonical coercion helpers, migrate exact core and plugin duplicates through narrow Plugin SDK facades, and enforce declaration and plugin-normalization ownership boundaries.
The sweep adds eight focused SDK exports while deleting more production and tooling code than it adds. User-visible behavior is unchanged except for safer equivalent object and UI parsing at existing boundaries.
* fix: guard integer option ownership
Register resolveIntegerOption with the canonical function owner and extend the declaration-guard fixture so future local duplicates fail validation.
* fix: keep integer helpers on numeric facade
Remove the unshipped duplicate string-coerce exports and route every affected plugin consumer through the existing number-runtime contract.
* fix: point numeric coercion to number runtime
Make boundary and declaration diagnostics recommend the canonical numeric facade, with failing-before coverage for both guidance paths.
Per-requester OAuth for HTTP MCP servers: in shared channels each trusted sender connects their own account. New config: mcp.servers.<name>.oauth.identity ("shared" default, byte-identical behavior) and gateway.publicOrigin (HTTPS except loopback) for the new GET /oauth/mcp/callback served on the Gateway HTTP server. Requester tokens are isolated per (channel, account, sender) in mcp_oauth_stores rows (no schema bump); callbacks correlate through a durable state-keyed one-time index with a 10-minute TTL; per-requester servers are fail-closed out of static/scheduled runtimes; unauthenticated senders get a sign-in link with a portable URL button; MCP config mutations clear requester credentials at the canonical owner. mcp status --json keeps the legacy authStatus fields additively. Proven by a two-user self-hosted-Executor E2E through the real gateway callback. Part of #122034.
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
* feat(ui): unify system notices in chat with a minimal line family
Class A (model-facing system turns: restart recovery, gateway restarted,
generic internal_system fallback) and class B (lifecycle facts: compaction)
now share one hairline+icon+label rendering; known kinds show operator
summaries instead of raw injected prompts. Core producers share one
formatSystemTurnPrompt owner for the [System] prefix; subagent resume copy
unified from "gateway reload" to "gateway restart". No protocol, channel,
or marker changes.
* fix(ui): keep system-turn prompt helper out of the plugin SDK surface
Relocate formatSystemTurnPrompt to src/sessions/system-turn-prompt.ts so the
core-internal [System] convention does not widen the SDK API baseline; drop
the unused prefix/type exports; use var(--cursor-action) per cursor policy.
Inbound image, audio, and video attachments could vanish with no recorded outcome and no model-visible explanation: attachments dropped by first-only selection, disabled or unconfigured capabilities, stage failures, URL-only images, and ACP-undelivered images all left the model unaware the media existed. Fixes#122044; completes the outcome custody started for documents in #122025.
Media capabilities now record one closed disposition per pre-truncation candidate at the decision site that owns the reason, with a memoized lazy native-vision probe (fires only when a marker could render; failure yields unknown and never alters outcomes; explicit image models never pay a catalog lookup). One late reader renders bounded, actionable markers from recorded facts only: native-vision and ACP-delivered images suppress per verified index, recorded failures always render, and document plus media markers share one five-marker budget with a reason-neutral overflow summary. The shipped SDK decision type stays additive (optional dispositions map).
Proof: 906 + 103 focused tests including nine adversarial-review regressions, ClawSweeper local review clean (round 9, zero findings, security cleared), live Telegram image drive showing ref preservation with no false marker.
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
* refactor(sessions): drop the Sqlite infix from session-accessor exports
The storage-neutral seam completed its file->SQLite swap on 2026-07-27;
the Sqlite-infixed export names and their X-as-Y rename blocks were the
fossil of that migration, giving every accessor operation two greppable
names. Rename ~148 exports to their canonical names at the definition
sites, delete the message-cut facade (its conflict->failed mask had one
consumer, which already handles conflict), collapse the remaining pure
delegates, and keep honest SQLite-mechanics names (scope resolution,
canonical repair, in-transaction primitives, storage-row types). The main
barrel's exported surface is byte-identical. Net -203 production LOC.
Part 2 of the session-accessor dual-layer collapse (part 1: #121316).
* fix: repair main-breaking lint/type/test failures blocking PR CI
Three breakages landed on main that push CI's changed-scope never runs:
a caught-error cause missing in the release-validation script and two
type errors in the package-acceptance test (direct commit 1f591bba56),
and a memory-host regression where fs-safe 0.5.4 (#121508) started
throwing FsSafeError("not-file") for extra-path reads whose parent chain
hits a regular file, breaking the missing-file empty-text contract that
rejection unchanged.
Item 4 repairs managed-image action E2E narrowing from bad30d5a74 (#77017).
Item 5's chat-message mock repair was superseded upstream by 750d0dcd9e, whose broader fetch typing and explicit download capture preserve the same contract.
Item 6 restores #121258's cursor token contract after #77017 hard-coded pointer.
Item 7's managed-image thumbnail-path repair was superseded upstream by 750d0dcd9e, which preserves the same contract in main's reorganized URL structure.
Item 8 records #121600's explicit command-bearing lifecycle classification.
Item 9's cron assertion repair was superseded upstream by 3cd034f7a8, whose exact diagnostic rewrite includes the same redacted alert contract.
Item 10 forwards fs-safe 0.5.4 bigint lstat options through the snapshot mock.
Item 11 refreshes release-matrix test ownership after 1f591bba56.
Item 12 awaits f3e1efead48's setup-admission settlement in direct session tests.
Item 13's media FileStore-key repair was superseded upstream by cf432ec871, which fixes the same Windows root cause at the owner boundary.
Item 14 regenerates Swift approval reviewer fields added by 9935ca3b30.