* fix(skills): expand explicit references on agent turns
Route generic Gateway, CLI, webhook, and local agent turns through the same explicit skill-reference renderer as channel auto-replies. Keep original transcript text, preserve unknown slash behavior, and fail visibly for allowlist-hidden skills.
Maintainer review: scoped Option 1 — generic agent turns expand both $skill-name and leading /skill-name args through shared skill rendering; they do not run the channel command dispatcher, and all other slash commands retain their existing behavior.
* fix(skills): bound explicit reference prompts
* fix(skills): prefer allowed reference collisions
* fix(skills): preserve command invocation boundaries
* fix(skills): reject hidden channel slash commands
* perf(skills): skip literal dollar discovery
* fix(delivery): unify terminal settlement ownership
Treat identityless adapter returns as potentially visible across channel, queue, and cron paths. Let recovery own terminal completion so ambiguity persists as notice debt instead of being double-settled or silently suppressed.
Co-authored-by: ruel225 <ruel225@users.noreply.github.com>
* refactor(delivery): narrow terminal internals
Remove now-unused internal exports after terminal-settlement ownership was consolidated.
* test(tts): preserve message runtime exports
Import and spread the actual message runtime so the focused mock retains every runtime binding while overriding only the durable send core.
---------
Co-authored-by: ruel225 <ruel225@users.noreply.github.com>
* refactor(agents): persist directive facts at the assistant write boundary
Assistant final text is now stripped of inline delivery directives once, at
the SessionManager append boundary, with the parsed facts persisted as a
typed openclawDelivery field on the assistant message (openclawDeliveryMirror
precedent, inside event_json — no schema change). Live session state is
replaced with the persisted canonical bytes so same-run continuations and
next-turn store rebuilds never diverge (prompt-cache invariant).
Delivery and restart recovery consume the stored facts instead of re-parsing
persisted text; pre-upgrade in-flight messages without facts intentionally get
no text-parse fallback (commented tradeoff at the read site). Directive
parsing is now code-region aware, so markers quoted in inline code or fences
neither parse nor strip — fixes the empty-code-pill display bug.
The undocumented reaction marker DSL ([[react:]], [[react_to_current:]]) is
deleted; structured message-tool reactions remain the canonical path. Stale
assertion-safety baseline entry removed with the parser.
Production net -10, tests net +38. Focused suites: 9,068 passed; pre-fix
regressions fail for the intended reasons. Codex autoreview clean.
* fix(agents): satisfy directive persistence gates
* fix(config): drop unused directive export
* fix(agents): preserve indented directive examples
enqueueFollowupRun called getFollowupQueue (which inserts into the
process-global FOLLOWUP_QUEUES registry) before the recent-message-id
dedupe peek. A provider redelivery arriving after the original queue
drained and self-deleted recreated the registry entry, was rejected by
the peek, and left an empty FollowupQueueState (AbortController, arrays,
WeakSets) in the map forever — registry entries are only deleted when a
drain finishes, and a queue with no items never drains. Long-lived
gateways leaked one state object per redelivered session key.
Root cause: mutation-before-validation ordering. The dedupe key
derivation does not need the queue, so the peek now runs first and the
registry entry is only created for admitted work.
Regression: new dedupe test drains a queue to self-deletion, replays the
message, and asserts no registry entry reappears — fails pre-fix.
Share one lifecycle-owned reservation primitive between reply admission and foreground delivery ordering, and remove the obsolete admission-wait callback plumbing.
Deliver same-target channel replies in inbound order while keeping steer admission concurrent. Record accepted steer/followup ownership so intentional deferred turns do not look silently empty.
Fixes#124618
* fix(agents): allow required-preflight native Codex compaction
Required reply-preflight compaction on a Codex app-server-backed session
returns the intentional `ok: true, compacted: false` "codex app-server owns
automatic compaction" no-op because the preflight caller never passes
`allowNonManualNativeRequest`. The reply/preflight path then misclassifies
that successful skip as a failure and throws, dropping the user's turn with
"Context is too large and auto-compaction could not recover this turn." The
equivalent CLI path was fixed by #88207; this is the second, unpatched caller.
Route required-preflight through the existing private
`compactAfterContextEngine` harness capability (which already passes
`allowNonManualNativeRequest: true`) by adding a typed
`nativeCompactionRequest: "required_preflight" | "after_context_engine"`
origin on `maybeCompactAgentHarnessSession` and the Codex compact bridge.
The non-manual skip guard is bypassed for preflight, so Codex actually
compacts the thread.
A binding change between the initial read and the native request is a
stale-binding race, not a benign skip. For `required_preflight` (and the
non-manual CLI path) it now surfaces as the canonical recoverable
`stale_thread_binding` failure so the queued harness falls back to the
context engine instead of treating an uncompacted `ok: true` result as a
completed turn. A genuine post-context-engine request may still skip,
because the context engine has already compacted. Required-preflight is also
the one scoped exception to the model-locked terminal rule: missing or stale
Codex thread bindings recover via the shared context-engine fallback while
the persisted harness lock stays intact; other locked failures remain
terminal.
Rebased onto main after #120740 restructured the guarded native compaction
block; the recoverable-binding semantics are reintroduced on the new
structure and scoped by `nativeCompactionRequest` so #120740's
post-context-engine skip behavior is preserved.
Closes#119971.
* test(evidence): commit inspectable required-preflight live proof scripts for #119971
Adds the two live codex app-server proof scripts (binding-race +
locked-preflight) so the redacted terminal traces in the PR body are
inspectable on the exact head. Both drive the real codex binary and real
maybeCompactCodexAppServerSession with nativeCompactionRequest:
"required_preflight"; neither runs in CI (no codex binary).
* fix(agents): scope locked-preflight compaction fallback to Codex
Restrict the required-preflight model-lock exception to the Codex harness
so missing/stale thread bindings in other locked native harnesses (e.g.
Copilot) stay terminal instead of escaping the persisted model-lock
boundary via context-engine fallback. Add a model-locked Copilot
required-preflight regression covering both missing and stale thread
bindings.
* fix(codex): require native preflight compaction
* chore(plugin-sdk): account for native compaction exports
* test(codex): use complete cron authority fixtures
* chore(lint): shrink compaction assertion baseline
* fix(lint): honor root boundary timeout
* fix(lint): extend package boundary timeout
* fix(plugins): verify native compaction owner
---------
Co-authored-by: fuller-stack-dev <263060202+fuller-stack-dev@users.noreply.github.com>
Hide model fallback and recovery notices in group and channel conversations while preserving direct-chat notices, persisted state, and lifecycle events.
Co-authored-by: NehoraiHadad <nehorai.hadad.projects@gmail.com>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
Mark harness-owned input prompts for source-suppression delivery so Codex choices and secret prompts reach the originating chat during message-tool-only turns.
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
Do not leave invisible ask_user prompts armed to consume later messages.
Reject only delivery outcomes proven invisible; preserve ambiguous post-transport failures and untracked dispatcher behavior.
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
* refactor(config): delete dead compat surfaces and write-only error payload
Round-5 config-lane sweep; all three deletions verified against the strict
root schema and current consumers.
- ConfigMutationConflictError.currentHash was write-only: ~20 throw sites
computed hashes (some hashing whole files on the failure path purely to
populate it) and no consumer anywhere read it — responses carry only the
message and .retryable. Field deleted, feeder expressions collapsed.
- Root-level channel-key fallbacks (cfg[provider] beside cfg.channels[...])
in chunk limits, block streaming, and channel capabilities were unreachable:
the root schema is z.strictObject, so a validated config cannot carry
top-level channel keys; doctor owns those moves. Pre-strict-schema era
leftovers, deleted.
- resolveHeartbeatAckMaxChars existed as three identical constant-returning
stubs (heartbeat-runner-config, server-chat, get-reply) left over from the
retired heartbeat.ackMaxChars key; inlined the constant, deleted the stubs.
- mergeScopedSearchConfig's legacy web-search key delete was a no-op — the
agent-runtime schema rejects those keys at validation; helper and dead
branch deleted (the schema-side Set remains the rejection boundary).
* fix(web-search): restore retired-provider strip in runtime scoped merge
The legacy-key delete in mergeScopedSearchConfig is runtime behavior, not
schema-shadowed dead code: the merge input is runtime-merged state, so
retired provider objects must still be stripped when no plugin config
exists (proven by the existing web-search.test.ts regression).
* fix(ui): fork active sessions from stable history
* fix(protocol): align active fork types
* test(ui): match main-session fork routing
* fix(sessions): unify stable fork admission
* fix(native): fork active sessions from stable history
* fix(android): fork active sessions from stable history
* style(android): simplify active fork result handling
* fix(native): preserve legacy fork transport API
* test(native): complete legacy fork transport stub
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* 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>
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.