Embedded runs targeting a CLI runtime provider fall through to the openclaw
harness and call the provider API directly with the runtime's credentials
(cli_runtime_passthrough_openclaw). Anthropic routes direct anthropic-messages
calls on subscription OAuth tokens to metered extra-usage billing — this is
long-standing behavior, not a recent change. Without extra-usage balance,
every such run (e.g. active-memory recall) fails with a billing error; with
extra-usage enabled, the run silently draws paid metered usage instead of the
plan limits the CLI runtime was configured for. Only CLI-backed execution
runs on plan limits for those credentials.
Add an opt-in RunEmbeddedAgentParams.cliBackendDispatch: "subscription-auth"
that dispatches the run through runCliAgent as a one-shot turn when the
provider is claude-cli, a CLI backend is registered, and the ordered auth
profile selection for the passthrough resolves to a subscription (oauth/token)
credential or nothing rather than an API key; resolution stays on stored
credential metadata, with no credential materialization or refresh on the
per-turn path. The dispatch translates toolsAllow into the selectable-backend
surface (native: [], allowlisted loopback MCP tools; wildcard allowlists stay
MCP-only), runs with a fresh CLI process (no live-session reuse; session-
scoped bundle-MCP retirement on run end rather than the process-wide loopback
close), bridges CLI tool result events to onAgentToolResult with native-path
semantics (normalizeToolName + isToolResultError), and drops CLI session
bindings from the result.
The selectable-backend MCP list now also bounds the loopback MCP grant
server-side: the grant carries a per-run gateway tool allowlist enforced in
scoped tool resolution, so tools outside the run's allowlist can be neither
listed nor called even under CLI bypass permission modes where
--allowedTools is advisory.
active-memory recall opts in so recall works on claude-cli subscription-only
instances and stops drawing metered extra usage where it previously could.
Scoped to claude-cli; other CLI runtimes keep the passthrough until their
direct-API contract is verified.
The dispatch also mirrors the run into the run's session transcript through the session
accessor (user turn, tool call/result records as they stream, final assistant
snapshot at run end) so transcript consumers keep parity with embedded runs:
active-memory's persistTranscripts, timeout partial-text salvage, and the
live terminal-search watcher that polls the session file mid-run.
Post-review hardening: canonical anthropic/<model> refs whose configured
agentRuntime is claude-cli resolve through the runtime policy before the
dispatch gate (they previously stayed on the failing passthrough); restricted
dispatches serve an exclusive loopback-only MCP bundle so user/plugin MCP
servers stay outside the run's tool universe; and the transcript recorder
flushes the latest assistant snapshot the moment the run aborts, so timeout
salvage sees partial text even while the killed CLI child is still settling.
Recalls routed to the claude-cli runtime default to a 45s budget (measured
CLI-dispatched runs take 14-20s, over the plain 15s default); explicit
timeoutMs config always wins.
Transcript mirror keeps bare-array tool_result content (claude stream-json
echoes MCP results without a {content} wrapper); dropping it classified every
successful recall as no_relevant_memory.
CLI dispatch resolves inside session/global lane admission so dispatched
runs obey the same lifecycle, placement, and concurrency gates as native
embedded runs.
LOC-ratchet offsets move the loopback grant-context builders to
cli-runner/mcp-grant-context.ts and dedupe the run/prep stage-summary
emitters into attempt-stage-timing.ts; unused type exports dropped and the
now-used stream-message baseline entry removed.
The recall timeout default now consumes the runner's own dispatch
eligibility through a new plugin-runtime seam
(agent.resolveCliBackendDispatchEligibility): API-key and missing-backend
routes keep the passthrough and its plain 15s default.
Eligibility honors an explicitly pinned authProfileId (the credential the
run executes on) before ordered profile selection, in both directions.
Transcript mirror composes buildAssistantMessage + buildUsageWithNoCost
directly; main trimmed the zero-usage wrapper export (ab0ccc244b) before
this branch's usage landed.
Dispatch eligibility is provider-owned: the anthropic plugin's claude-cli
backend declares CliBackendPlugin.subscriptionAuthDispatch and core reads
the registered descriptor instead of a core provider allowlist.
Dispatch fails closed on tool policy (only non-empty named allowlists are
expressible on the CLI surface; deny-all, wildcards, absent allowlists, and
disableTools/modelRun keep the passthrough), threads the pinned
authProfileId into CLI runtime resolution, and emits onExecutionStarted at
the admitted dispatch boundary.
* feat(migrate): add Hermes memory-only import and a shared memory-import core
* feat(onboarding): offer detected memory imports during CLI setup and guided onboarding
* feat(ui): show a first-run memory-import offer in Control UI onboarding mode
* feat(linux-app): open the first-run dashboard in onboarding mode
* feat(macos): add the onboarding memory-import page
* docs: document the onboarding memory-import page across surfaces
* chore(i18n): translate onboarding memory-import strings for control-ui and native locales
* refactor: keep memory-import internals unexported for deadcode gates
* fix(ci): resolve lint findings in onboarding memory import
* chore(i18n): refresh native inventory after lint refactor
* fix(ci): skip updater artifacts in the unsigned Linux companion PR lane
* chore(i18n): reconcile locale artifacts after rebase
Without OP_LOAD_DESKTOP_APP_SETTINGS=false and OP_BIOMETRIC_UNLOCK_ENABLED=false,
op 2.35 on macOS reads the 1Password desktop app's settings even on the
service-account path and can block on a per-PID App Data Protection dialog,
hanging the broker until timeoutMs on Mac gateway hosts.
* fix(discord): open latest activity widget on fallback
* chore: remove release-owned changelog entry
* fix(discord): order activity fallback by delivery
* fix(discord): require atomic widget delivery updates
* test(discord): type activity delivery fixtures
* test(discord): model missing atomic state support
* fix(discord): order activity fallback by message snowflake
* docs(discord): clarify activity fallback order
* docs(lobster): enumerate injected workflow env vars and clarify step-output access (#82281)
* docs(lobster): refresh workflow environment docs map
Clarify how shells handle nonexistent step-output variables and regenerate the generated docs map for the new section.
Co-authored-by: Shubhankar Tripathy <reach2shubhankar@gmail.com>
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* feat(canvas): let widget buttons send prompts into web chat
Inline show_widget documents get a sendPrompt(text) bridge: a private
MessageChannel offered to the Control UI before widget code runs. The chat
adopts only the first offer per frame, requires transient user activation,
a visible focused frame, plain text (no slash commands), and rate limits
10 prompts/minute per widget. Accepted prompts run the normal user send
path of the owning chat pane.
* fix(canvas): appease lint/knip/docs gates for widget prompts
Split widget prompt tests into their own file, drive validation and rate
limits through the real port path instead of exported internals, use
addEventListener on the adopted port, and regenerate docs_map.
* fix(canvas): install widget listeners per window for non-isolated tests
Module-boolean listener flags broke in shared vitest workers where each test
file gets a fresh jsdom window; key installation by window instead and give
port flushing more headroom.
* feat(presentation): support hosted widget actions
* refactor(discord): route widget launches through presentation
* chore: keep changelog release-owned
* test(interactive): type the invalid web-app boundary case explicitly
* test: type web-app action fixtures against the split union
The two-member web-app union no longer accepts a distributed
type: "url" | "web-app" literal or an empty receipt in typed
callbacks; use explicit action casts and the Discord receipt factory.
* refactor(channels): generalize channel retry runner and retry-after parsing; drop dead configured-state probes
* fix(channels): align retry and metadata contracts
* fix(line): run post-ack webhook processing on its own admitted work root
LINE acks the webhook and dispatches event processing fire-and-forget on
the same async chain. The HTTP request admission that chain inherited is
released as soon as the route handler returns, and a released admission
refuses subordinate queue work - so every LINE inbound agent turn fails
with "GatewayDrainingError: Gateway is draining; new tasks are not
accepted" even though the gateway is healthy. DMs, group mentions, and
postbacks are all affected; the user-visible symptom is the bot replying
"Sorry, I encountered an error processing your message." to everything.
Add runDetachedWebhookWork to the plugin-sdk webhook-request-guards
surface (a thin wrapper over the gateway independent-root continuation,
the same shape core uses in gateway/server/hooks.ts) and route all three
LINE ack-first dispatch sites through it: the gateway monitor handler
(the live path), and the createLineNodeWebhookHandler / Express
middleware handlers (public webhook building blocks an embedder can
register under the gateway). #65375 unified these three into one ack-first
pattern; keeping the detach consistent avoids re-introducing the same
latent defect in the two that are not on the live gateway path today.
The continuation is reserved synchronously while the request is still
admitted, so the detached processing stays accepted and a real restart
drain can wait for it instead of stranding it mid-turn.
Tests pin every layer: the guards suite proves detached post-ack work is
admitted after the request admission is released (and that the inherited
chain without the helper is refused); the monitor lifecycle suite and the
webhook-node suite assert each dispatch site goes through the detached
root. Red/green verified: reverting any dispatch fails its test.
* fix(plugin-sdk): account for runDetachedWebhookWork in public surface budget
* fix(channels): track detached webhook processing
Co-authored-by: 許元豪 <146086744+edenfunf@users.noreply.github.com>
* docs: refresh generated docs map
* chore(plugin-sdk): refresh API baseline
* fix(webhooks): preserve post-ack ordering
* test(plugin-sdk): satisfy detached work lint
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(memory-lancedb): gate auto-recall and auto-capture on per-agent memorySearch.enabled (#103590)
The before_prompt_build auto-recall hook only checked the plugin-level
autoRecall flag, so agents configured with memorySearch.enabled: false
still received <relevant-memories> injected from the shared LanceDB store
- leaking one agent's private memories into another agent's prompts. Gate
both recall injection and agent_end auto-capture on the current agent's
memorySearch.enabled (per-agent entry wins over agents.defaults; unset
means enabled), mirroring core resolveMemorySearchConfig semantics.
* fix(memory-lancedb): normalize agent ids before the memorySearch gate
Review follow-up on #103799: a configured id like 'XiaoHuo' or one with
surrounding whitespace missed the exact-match per-agent override and
inherited the enabled default, leaving the disclosure path active.
Normalize both the hook agent id and configured entry ids with the SDK
normalizeAgentId before comparing.
* fix(memory-lancedb): resolve the per-agent memorySearch gate via resolveAgentConfig
* fix(memory): isolate LanceDB rows by agent
Co-authored-by: Shubhankar Tripathy <reach2shubhankar@gmail.com>
* fix(memory): isolate LanceDB rows by agent
Co-authored-by: Shubhankar Tripathy <reach2shubhankar@gmail.com>
* refactor(memory): keep LanceDB store types private
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* refactor(plugin-sdk): narrow wildcard barrels to explicit used exports
* refactor(tools): delete dead tool-planning module exposed by barrel narrowing
* fix(plugin-sdk): restore deprecation tag on OpenClawSchemaType alias
* test(agents): drop test for deleted runtime proxy module
* refactor(tools): trim descriptor types to cache consumers
* refactor(deadcode): harvest exports orphaned by barrel narrowing
* refactor(deadcode): harvest exports orphaned by barrel narrowing (rest)
* fix(agents): restore sdk imports and test markers via public predicate
* fix(plugin-sdk): named type re-exports in plugin-entry; trim types barrel precisely
* chore(plugin-sdk): account unmasked deprecated provider types in budgets
* fix(plugins): name star-only type rows for dts bundling
* fix(plugins): restore host-hook surface; unexport internal api compositions
* fix(plugins): named type imports for api composition; restore needed source exports
* fix(plugins): knip-visible type imports for registry surfaces
* test: adapt tests to privatized media and command internals
* fix(qa-lab): re-export snapshot conversation type
* style: format sessions sdk imports
* fix(plugins): restore smoke entry export; pin budgets to exact actuals
* fix(plugins): canonical smoke-entry import; drop orphaned root shims
* fix(plugins): allowlist manifest probe, repoint qa web import, drop dead browser barrels
* fix(plugin-sdk): pin codex auth marker and scaffold provider type
* fix(qa-lab): keep web-facing model-selection shim within boundary rules
* fix(plugin-sdk): preserve merged contracts through narrowed barrels
* chore(plugin-sdk): pin post-rebase surface budgets