* fix(sessions): stop leaking file path as prompt content on read failure
When readFileSync fails for a valid file path, resolvePromptInput returns
the raw path string as prompt content instead of undefined. This injects
filesystem paths into the LLM context. The existing console.error warning
still fires; the caller already handles undefined returns correctly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test: cover unreadable prompt paths
* test: use tracked resource loader temp dirs
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <peter@steipete.me>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(transcript-stream): log stat/open errors instead of silently returning empty iterator
streamSessionTranscriptLines and streamSessionTranscriptLinesReverse
used bare catch { return; } blocks that silently swallowed all fs errors
(permission denied, I/O error, etc.) and returned empty iterators. This
made it impossible to diagnose why transcript streaming failed.
Replace catch { return; } with catch (err) { transcriptLog.warn(...); return; }
so failures are visible in diagnostics.
* fix(transcript-stream): exclude ENOENT from warning logs in stat/open catch blocks
Both streamSessionTranscriptLines and streamSessionTranscriptLinesReverse
caught all fs errors and logged warnings, but ENOENT is an expected empty-
iterator case for these best-effort readers. Filter ENOENT before logging
so missing transcripts stay silent while permission/I/O errors surface.
Added deterministic regression tests with mocked fs.promises.stat/open:
- EACCES triggers a warning in both forward and reverse directions
- ENOENT stays silent in both directions
* fix(transcript): propagate session read failures
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* feat(gateway): deliver plugin approvals to paired iOS devices via APNs
Generalize the exec-approval iOS push delivery into a shared driver so plugin
approvals also raise an APNs notification on paired operator devices (which iOS
mirrors to a paired Apple Watch). Reuses the operator.approvals scope gate,
direct/relay transport, and stale-registration cleanup. Wires push into both
plugin-approval origination points (the plugin.approval.request RPC and the
node.invoke policy path) alongside the existing chat forwarder; adds a
plugin.approval.* APNs payload/category with a description body truncated to
256 UTF-16-safe chars and no secret-bearing fields.
* fix(gateway): drop unused export on plugin approval push category
* fix(codex): full-auto approvals survive relay unavailability, typed failure classification, usage line after fallback
- full-auto (approvalPolicy never + danger-full-access) exec approvals no
longer get declined when the native hook relay is unreachable BEFORE
invocation; an invoked relay's explicit deny, malformed output, or nonzero
exit still fails closed (#107447)
- startup timeout/abort and request-timeout classification use typed error
discriminants instead of message prose; EPIPE detection walks error causes;
compaction keeps the message-based thread-not-found gate because codex-rs
exposes no dedicated code (its own contract test asserts the message) and
the generic -32600 would over-match unrelated invalid requests (#99270)
- /status keeps the Codex usage/quota line for sessions whose persisted
agentHarnessId is codex even when the effective runtime fell back to
OpenClaw Default; never-codex sessions remain excluded (#105184)
* chore(codex): keep startup error reason type module-local
* feat(channels): render live plan checklists from typed update_plan snapshots
Codex, Copilot, CLI, and embedded update_plan events now carry typed
{step, status} snapshots end to end instead of flattened strings. Slack,
Discord, Telegram, Matrix, and MS Teams render a live checklist in
progress drafts; Slack native task cards show the real plan with
position-keyed rows and id reconciliation. The embedded update_plan tool
is default-on for every model (tools.experimental.planTool: false opts
out). The shipped onPlanUpdate steps: string[] SDK field stays populated
during a deprecation window alongside the canonical planSteps.
* fix(auto-reply): align plan bridge types with SDK callback contract
* fix(auto-reply): split agent event bridge module and fix plan type boundaries
* fix(channels): satisfy oxlint on plan checklist helpers
On git-channel installs where both extensions/<id>/index.ts and
dist/extensions/<id>/index.js exist, two registry assemblies resolved the same
plugin id to different physical files (preferBuiltPluginArtifacts drives both the
artifact choice and the load-cache key), so the path-keyed module cache
evaluated the entry twice — two live plugin instances with separate state,
binding hooks to one and registerTool to the other. Memoize the first resolved
runtime entry path per plugin-id + real root + entry-kind so both assemblies
converge on one module instance; clear the memo on activated-runtime-state and
registry-load-cache resets so reload/install/doctor re-resolve. No-op on
dist-only installs.
Closes#107933
The web terminal's per-tab close control rendered as a detached floating
square next to the tab. It now joins the tab as one surface: shared hover
background, active accent underline continuing beneath it, full tab
height, with its own rounded inner highlight on hover.
* 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 strict knip workspaces landed in #108547 flagged seven files. Two are
convention-resolved and become explicit entries (discord configured-state via
the package-state probes, qa-lab cli via the SDK facade basename resolver);
the other five were dead barrels/wrappers superseded by canonical modules and
are deleted (browser bridge/cdp barrels, discord timeouts wrapper, openai
register.runtime, qa-lab model-selection wrapper).
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