* fix(line): keep group history recorded during a mention turn
Group history cleanup ran a whole-key clear after each mention turn. Because
webhook dispatch is fire-and-forget, a plain (unmentioned) message can be
recorded while the agent is still handling a mention; the post-turn clear then
wiped the whole window, silently dropping that concurrent message from the
next turn's context.
Snapshot the identity keys the turn consumes up front and clear only those,
retaining anything recorded concurrently. The record path now stamps the LINE
message id so entries have a stable identity. Cleanup stays after the turn, so
a failed turn still leaves history intact for the retry. The snapshot/clear
helpers live in group-history.ts, next to the other group modules.
* test(line): cover group history retention when a mention turn fails
Assert that a mention turn whose processMessage throws leaves the group
history window intact (cleanup runs only after a successful turn), so the
retry still has the ambient context. Red/green verified: moving the cleanup
before the await fails this test.
* fix(line): capture consumed group history at the context read boundary
The consumed-key snapshot ran before media download and context
construction, while buildLineMessageContext copied the window later.
A plain message recorded between those awaits was included in the
turn's InboundHistory yet missing from the consumed set, so cleanup
retained it and the next mention received it twice.
Read the window and capture its identity keys in one synchronous step
in the handler (snapshotLineGroupHistory), then pass the materialized
inboundHistory into buildLineMessageContext instead of the live map.
The consumed set is now derived from the exact entries the turn reads,
so an entry is either in the context and cleared, or recorded later
and retained - there is no window where both can be true.
Regression tests park a mention turn inside context construction,
record an ambient message mid-window, and assert it stays out of that
turn's InboundHistory, survives cleanup, and is consumed exactly once
by the next mention. Red/green verified against the previous snapshot
placement.
* test(line): point history-window guardrail at group-history.ts
The group-history cleanup fix moved the createChannelHistoryWindow facade
call out of bot-message-context.ts (which now receives inboundHistory as a
parameter) into the new group-history.ts. Update the cross-channel
historyWindowFiles ledger to match, mirroring telegram's group-history-window.ts
entry already in the same list. Fixes the message-turn-guardrails 'keeps
migrated history users on the channel history window facade' assertion that
build-artifacts flagged.
* fix(line): reserve history across concurrent turns
Co-authored-by: Eden <146086744+edenfunf@users.noreply.github.com>
* test(line): complete webhook message fixtures
* test(line): use explicit token placeholders
* refactor(line): keep reservation type private
* refactor(line): render reservations through history facade
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
The deleted text provider's usage hook was the only source for the /status
Codex subscription usage line. Harnesses can now contribute an optional
usage snapshot (provider hooks keep priority; only distinct synthetic hook
owners fall through), and the codex harness reports app-server rate limits
via account/rateLimits/read with the same conversion and account identity
as before. No text provider resurrected; deadcode and SDK surface gates
clean.
The preview test mocked codex-route-warnings wholesale; the consolidated
blocked-merge warning now flows through it, so the auto-merge case runs the
actual implementation once. Migration identity helpers stay module-local;
codex-route-model-ref formatting fixed.
- codex media substitution keys on the registered media-understanding
provider (image capability + default model) instead of the deleted text
provider's synthetic auth probe; deterministic injected route in tests
- migration helper exports without production consumers are module-local;
the blocked-provider warning assertion goes through the plan export
- null-vs-undefined runtime record type at the provider-move helper
- gateway models.list fixtures expect the new agentRuntime intent field
(codex/implicit for policy-backed OpenAI routes, openclaw/implicit for
synthetic routes without provider policy)
- split doctor cron legacy-repair into its own module to satisfy the
max-lines gate without suppressions; callers import directly
The live codex text provider was a redundant projection of the openai
catalog (exclusive provider ownership; the openai plugin's ChatGPT OAuth
discovery already serves gpt-5.6-* route-aware). Folding it:
- extensions/codex no longer registers a text provider, catalog entry, or
synthetic text auth; provider.ts/provider-catalog.ts/provider-discovery.ts
and the route-blind model-name heuristics are deleted; the narrow
post-harness reasoning fallback moves to an app-server-owned module
- openai thinking policy keys on explicit selected-route provenance
(api === openai-chatgpt-responses) instead of value-shape inference
- models.list gains an optional additive agentRuntime field (configured
intent); session agentHarnessId remains the execution proof
- doctor --fix migrates the shipped codex/* config shape end to end:
every model slot, provider-config merge with blocker-aware conflict
handling, sessions, cron payloads (two-phase: runtime policy persists
before cron refs rewrite), transcripts; migrated refs carry model-scoped
agentRuntime.id=codex preserving the shipped wizard semantics; auto
runtime policies normalize to codex with sibling fields preserved;
blocked provider conflicts retain the whole legacy namespace fail-closed
with an actionable warning
- the stale openai:default profile cleanup (#91352) was deliberately
deferred to a follow-up after review showed it needs per-agent identity
proofs; doctor keeps warning about unusable profiles
Fixes#105561Fixes#84637Fixes#90420
* refactor(qa): route live channel selectors through suite host
* fix(qa): repair live selector CI
* fix(qa): remove retired runner exports
* fix(qa): remove obsolete live evidence
* fix(agents): wake owning session after generated-media direct delivery
When a background media generation (image/music/video) completes but the
requester's agent turn cannot be woken, delivery falls back to sending the
media straight to the channel. The owning session never learned about it,
leaving the attachment orphaned with no agent follow-up.
Queue a system event plus a scoped heartbeat wake after both direct
fallback paths (announce delivery and the shared background lifecycle) so
the agent continues the conversation in its own voice. Subagent requester
sessions skip the heartbeat since their completions route via announce.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(agents): persist generated media session handoffs
Co-authored-by: Molty <steipete@gmail.com>
* chore: prune max-lines baseline
* fix(agents): harden durable media handoffs
* fix(agents): charge media retries once
* chore: refresh max-lines baseline
* fix(agents): persist delivery settlement cleanup
* fix(agents): fence interrupted media delivery
* fix(agents): fence durable handoff admission
* fix(agents): reconcile terminal handoffs at retry limit
* fix(agents): align durable handoff types
* test(agents): type durable handoff fixtures
* fix: satisfy session delivery lint
* fix: avoid payload map spreads
* fix: satisfy durable handoff gates
* test: align generated media handoff expectations
* fix: avoid serializing session recovery state during load
---------
Co-authored-by: Molty <steipete@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* fix(process): report actual UTF-8 byte count in stdin write message
Replace string.length (UTF-16 code units) with Buffer.byteLength(content, 'utf8')
so the reported byte count matches the actual bytes written to process stdin.
For ASCII content the values are identical. For non-ASCII content (CJK, emoji),
string.length underreports the actual UTF-8 byte count.
* test(process): cover UTF-8 stdin write byte count
Co-authored-by: 陈志强0668000989 <chen.zhiqiang1@xydigit.com>
* style(test): keep UTF-8 regression compact
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(process): report actual UTF-8 byte count in send-keys message
Replace string.length (UTF-16 code units) with Buffer.byteLength(data, 'utf8')
so the reported byte count matches the actual bytes sent to process stdin.
For ASCII key sequences the values are identical. For non-ASCII text
sent via literal key data, string.length underreports the actual UTF-8
byte count.
* test(process): cover UTF-8 send-keys byte count
Co-authored-by: 陈志强0668000989 <chen.zhiqiang1@xydigit.com>
* ci: prune stale max-lines baseline
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>