Skip to content

tasks: add detached runtime plugin registration contract#68915

Merged
mbelinky merged 5 commits intomainfrom
mariano/detached-runtime-contract
Apr 19, 2026
Merged

tasks: add detached runtime plugin registration contract#68915
mbelinky merged 5 commits intomainfrom
mariano/detached-runtime-contract

Conversation

@mbelinky
Copy link
Copy Markdown
Contributor

@mbelinky mbelinky commented Apr 19, 2026

Summary

  • Problem: PR1 added the detached-task lifecycle seam, but there was still no real core-owned way for an external executor to register itself against that seam.
  • Why it matters: without an explicit registration contract, detached execution still has to reach into task internals ad hoc, which blocks pushing durable executor mechanics out of core.
  • What changed: this PR adds detached runtime registration state, exposes plugin registration for that runtime, routes detached cancellation through the registered runtime, and preserves loader cache/rollback semantics around that registration.
  • What did NOT change: this does not add a durable queue, retry engine, crash recovery, stall detection, or any minions-style executor implementation inside core.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor required for the fix
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

User-visible / Behavior Changes

None.

Diagram (if applicable)

Before:
[detached caller] -> [core seam] -> [legacy core executor]
[plugin runtime] -> [no real ownership contract]

After:
[detached caller] -> [core seam] -> [registered runtime if present | legacy core executor fallback]
[plugin loader/registry] -> [register detached runtime] -> [owned lifecycle + cancel dispatch]

Security Impact (required)

  • New permissions/capabilities? (Yes/No) No
  • Secrets/tokens handling changed? (Yes/No) No
  • New/changed network calls? (Yes/No) No
  • Command/tool execution surface changed? (Yes/No) No
  • Data access scope changed? (Yes/No) No
  • If any Yes, explain risk + mitigation:

Human Verification (required)

What I personally verified:

  • detached runtime registration through the plugin API
  • detached runtime lifecycle dispatch through the seam
  • detached cancel and TaskFlow child cancel through the same runtime contract
  • duplicate runtime registration rejection
  • runtime ownership rollback/preservation across cache hits, non-activating loads, register failures, and active reload clearing
  • fallback to core cancel when a registered runtime declines ownership

What I did not verify:

  • no durable external executor implementation
  • no retry/recovery/stall semantics
  • no end-to-end plugin shipping flow beyond core registration + contract behavior

Authoritative validation on mb-server, rerun after rebasing onto current main:

  • OPENCLAW_TEST_PROFILE=serial OPENCLAW_TEST_SERIAL_GATEWAY=1 pnpm test -- src/tasks/task-executor.test.ts src/plugins/loader.test.ts src/plugins/runtime/runtime-tasks.test.ts src/tasks/detached-task-runtime.test.ts
  • pnpm tsgo:core
  • pnpm tsgo:core:test

Known unrelated mb-server baseline noise, not used as the authoritative gate for this narrow PR:

  • pnpm tsgo:all still fails in existing extension lanes on current main
  • pnpm build still hits the known runtime-postbuild bundled dependency staging issue on mb-server

Risks and Mitigations

  • Risk: runtime ownership could become ambiguous if more than one plugin tries to register a detached runtime.
    • Mitigation: registry rejects later registrations and records a plugin diagnostic instead of silently replacing the active runtime.
  • Risk: loader rollback/cache behavior could leak stale runtime ownership across plugin load attempts.
    • Mitigation: loader snapshots and restores detached runtime registration during cache hits, non-activating loads, register failures, and active reload clearing.
  • Risk: cancel behavior could drift from lifecycle dispatch if cancel bypasses the seam.
    • Mitigation: detached cancel and TaskFlow-driven child-task cancel now route through the same registered runtime contract, with explicit fallback only when the runtime declines ownership.

Compatibility / Migration

  • Backward compatible? (Yes/No) Yes
  • Config/env changes? (Yes/No) No
  • Migration needed? (Yes/No) No
  • If yes, exact upgrade steps:

Review Conversations

  • I replied to or resolved every bot review conversation I addressed in this PR.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

@openclaw-barnacle openclaw-barnacle Bot added commands Command implementations size: L maintainer Maintainer-authored PR labels Apr 19, 2026
@mbelinky mbelinky force-pushed the mariano/detached-runtime-contract branch from 6e5b5d8 to 888f9f4 Compare April 19, 2026 10:41
@mbelinky mbelinky marked this pull request as ready for review April 19, 2026 10:43
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 19, 2026

Greptile Summary

This PR adds the explicit plugin registration contract for detached task runtimes: a new state module (detached-task-runtime-state.ts) holds the singleton registration, a dispatch layer (detached-task-runtime.ts) routes lifecycle calls through the registered runtime with core fallbacks, and cancelDetachedTaskRunById in task-executor.ts is the new external-facing cancel entry point that consults the registered runtime before falling back to core. The loader correctly snapshots/restores the registration across cache hits, non-activating loads, register failures, and active-reload clearing. Two minor observations are noted inline: same-plugin silent re-registration in registry.ts, and the registered runtime being bypassed in cancelDetachedTaskRunById when the task is not found in the core registry.

Confidence Score: 5/5

Safe to merge; all remaining findings are P2 style/design suggestions with no blocking correctness issues.

The implementation is well-structured with clean separation between state, dispatch, and routing layers. Loader rollback/restore coverage is thorough across all four identified paths. Both open observations are P2 and do not affect correctness under the stated design invariants.

src/plugins/registry.ts (same-plugin re-registration), src/tasks/task-executor.ts (early-return in cancelDetachedTaskRunById)

Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/plugins/registry.ts
Line: 1176-1188

Comment:
**Same-plugin silent re-registration**

The guard `existing.pluginId !== record.id` allows the same plugin to call `registerDetachedTaskRuntime` multiple times, silently replacing the prior runtime without any diagnostic. If a plugin's activation path calls this twice (e.g., due to a misconfigured re-entrant load), the second registration overwrites the first with no warning — the same protection offered to *other* plugins is absent for the registering plugin itself. Consider emitting a warning-level diagnostic or making the second call a no-op when the plugin id already matches.

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: src/tasks/task-executor.ts
Line: 711-724

Comment:
**Registered runtime bypassed for unknown tasks**

When `getTaskById` returns `undefined` the function immediately falls back to `cancelTaskById`, never consulting the registered runtime. If a task were ever created through the registered runtime before it appeared in the core registry (or after it was removed from it), the registered runtime's cancel logic would be skipped entirely. The fallback is correct by current design invariants, but the early-return bypasses the runtime contract the rest of the function enforces.

```typescript
// current
if (!task) {
  return cancelTaskById(params);
}
// would be more consistent with the contract:
if (!task) {
  const registeredRuntime = getRegisteredDetachedTaskLifecycleRuntime();
  if (registeredRuntime) {
    const result = await registeredRuntime.cancelDetachedTaskRunById(params);
    if (result.found) return result;
  }
  return cancelTaskById(params);
}
```
This is a design-level call — fine to leave as-is if the invariant "task in registry ⟺ runtime owns it" holds firmly, but worth a comment in the code.

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "tasks: harden detached runtime ownership" | Re-trigger Greptile

Comment thread src/plugins/registry.ts
Comment on lines +1176 to +1188
registerDetachedTaskRuntime: (runtime) => {
const existing = getDetachedTaskLifecycleRuntimeRegistration();
if (existing && existing.pluginId !== record.id) {
pushDiagnostic({
level: "error",
pluginId: record.id,
source: record.source,
message: `detached task runtime already registered by ${existing.pluginId}`,
});
return;
}
registerDetachedTaskLifecycleRuntime(record.id, runtime);
},
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Same-plugin silent re-registration

The guard existing.pluginId !== record.id allows the same plugin to call registerDetachedTaskRuntime multiple times, silently replacing the prior runtime without any diagnostic. If a plugin's activation path calls this twice (e.g., due to a misconfigured re-entrant load), the second registration overwrites the first with no warning — the same protection offered to other plugins is absent for the registering plugin itself. Consider emitting a warning-level diagnostic or making the second call a no-op when the plugin id already matches.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/plugins/registry.ts
Line: 1176-1188

Comment:
**Same-plugin silent re-registration**

The guard `existing.pluginId !== record.id` allows the same plugin to call `registerDetachedTaskRuntime` multiple times, silently replacing the prior runtime without any diagnostic. If a plugin's activation path calls this twice (e.g., due to a misconfigured re-entrant load), the second registration overwrites the first with no warning — the same protection offered to *other* plugins is absent for the registering plugin itself. Consider emitting a warning-level diagnostic or making the second call a no-op when the plugin id already matches.

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines 711 to 724
export async function cancelDetachedTaskRunById(params: { cfg: OpenClawConfig; taskId: string }) {
const task = getTaskById(params.taskId);
if (!task) {
return cancelTaskById(params);
}
const registeredRuntime = getRegisteredDetachedTaskLifecycleRuntime();
if (registeredRuntime) {
const cancelled = await registeredRuntime.cancelDetachedTaskRunById(params);
if (cancelled.found) {
return cancelled;
}
}
return cancelTaskById(params);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Registered runtime bypassed for unknown tasks

When getTaskById returns undefined the function immediately falls back to cancelTaskById, never consulting the registered runtime. If a task were ever created through the registered runtime before it appeared in the core registry (or after it was removed from it), the registered runtime's cancel logic would be skipped entirely. The fallback is correct by current design invariants, but the early-return bypasses the runtime contract the rest of the function enforces.

// current
if (!task) {
  return cancelTaskById(params);
}
// would be more consistent with the contract:
if (!task) {
  const registeredRuntime = getRegisteredDetachedTaskLifecycleRuntime();
  if (registeredRuntime) {
    const result = await registeredRuntime.cancelDetachedTaskRunById(params);
    if (result.found) return result;
  }
  return cancelTaskById(params);
}

This is a design-level call — fine to leave as-is if the invariant "task in registry ⟺ runtime owns it" holds firmly, but worth a comment in the code.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/tasks/task-executor.ts
Line: 711-724

Comment:
**Registered runtime bypassed for unknown tasks**

When `getTaskById` returns `undefined` the function immediately falls back to `cancelTaskById`, never consulting the registered runtime. If a task were ever created through the registered runtime before it appeared in the core registry (or after it was removed from it), the registered runtime's cancel logic would be skipped entirely. The fallback is correct by current design invariants, but the early-return bypasses the runtime contract the rest of the function enforces.

```typescript
// current
if (!task) {
  return cancelTaskById(params);
}
// would be more consistent with the contract:
if (!task) {
  const registeredRuntime = getRegisteredDetachedTaskLifecycleRuntime();
  if (registeredRuntime) {
    const result = await registeredRuntime.cancelDetachedTaskRunById(params);
    if (result.found) return result;
  }
  return cancelTaskById(params);
}
```
This is a design-level call — fine to leave as-is if the invariant "task in registry ⟺ runtime owns it" holds firmly, but worth a comment in the code.

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 888f9f44a0

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +718 to +719
const cancelled = await registeredRuntime.cancelDetachedTaskRunById(params);
if (cancelled.found) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Handle runtime cancel exceptions before returning

When a registered detached runtime throws inside cancelDetachedTaskRunById (for example due to a transient RPC failure), this function currently propagates the exception instead of returning the structured cancel result used by the rest of the task APIs. That means callers like task/flow cancellation can fail hard and skip both the legacy fallback and normal reason reporting, leaving cancellation in a partially applied state. Wrap the runtime call in a try/catch and return a non-throwing result (or fallback) to preserve existing cancellation semantics.

Useful? React with 👍 / 👎.

@aisle-research-bot
Copy link
Copy Markdown

aisle-research-bot Bot commented Apr 19, 2026

🔒 Aisle Security Analysis

We found 2 potential security issue(s) in this PR:

# Severity Title
1 🟠 High Detached task lifecycle runtime can be overwritten via internal state registration (plugin isolation bypass)
2 🟡 Medium Untrusted detached-task runtime can return arbitrary TaskRecord in cancel response (data leakage)
1. 🟠 Detached task lifecycle runtime can be overwritten via internal state registration (plugin isolation bypass)
Property Value
Severity High
CWE CWE-284
Location src/tasks/detached-task-runtime-state.ts:10-18

Description

The new global detached task runtime state allows unguarded overwrites.

  • createPluginRegistry().api.registerDetachedTaskRuntime() enforces exclusivity by checking the existing registration and refusing if another plugin already registered.
  • However, registerDetachedTaskLifecycleRuntime() in src/tasks/detached-task-runtime-state.ts unconditionally overwrites the global registration.
  • Any code that can import/call this internal function (or registerDetachedTaskRuntime() in src/tasks/detached-task-runtime.ts, which forwards to it) can replace the detached task runtime even when another plugin already registered it.

Impact (if third-party plugins can access these internal modules at runtime): a malicious or compromised plugin could hijack detached task lifecycle operations (create/start/complete/fail/cancel), enabling task cancellation or manipulation across plugins.

Vulnerable code:

export function registerDetachedTaskLifecycleRuntime(pluginId: string, runtime: DetachedTaskLifecycleRuntime): void {
  detachedTaskLifecycleRuntimeRegistration = { pluginId, runtime };
}

Recommendation

Enforce exclusivity at the state layer as the single source of truth, so callers cannot bypass checks.

Example:

export function registerDetachedTaskLifecycleRuntime(
  pluginId: string,
  runtime: DetachedTaskLifecycleRuntime,
): void {
  const existing = detachedTaskLifecycleRuntimeRegistration;
  if (existing && existing.pluginId !== pluginId) {
    throw new Error(`detached task runtime already registered by ${existing.pluginId}`);
  }
  detachedTaskLifecycleRuntimeRegistration = { pluginId, runtime };
}

Additionally:

  • Avoid exporting internal registration functions on any public surface.
  • If plugins run in-process, consider hardening the plugin execution environment to prevent importing/using non-SDK internal modules (or treat plugins as fully trusted).
2. 🟡 Untrusted detached-task runtime can return arbitrary TaskRecord in cancel response (data leakage)
Property Value
Severity Medium
CWE CWE-200
Location src/tasks/task-executor.ts:711-721

Description

cancelDetachedTaskRunById delegates task cancellation to a registered (plugin-provided) detached task lifecycle runtime and returns its result directly when found is true.

Because DetachedTaskCancelResult allows task?: TaskRecord, an untrusted/buggy runtime can:

  • Return a different task record than the one requested (or a record belonging to another owner)
  • Cause higher layers (e.g., plugin runtime mapCancelledTaskResult in src/plugins/runtime/runtime-tasks.ts) to map and surface that record to callers
  • Potentially leak sensitive fields from TaskRecord (task content, ownerKey, error strings, summaries, etc.) across tenants/owners

Vulnerable flow:

  • input: taskId passed to cancelDetachedTaskRunById (ultimately user-controlled via APIs/CLI)
  • trust boundary: registeredRuntime.cancelDetachedTaskRunById is plugin-supplied
  • sink: returned cancelled.task is forwarded to callers without validation

Vulnerable code:

const cancelled = await registeredRuntime.cancelDetachedTaskRunById(params);
if (cancelled.found) {
  return cancelled;
}

Recommendation

Do not trust task (or other mutable fields) coming back from a plugin runtime.

Recommended mitigations (apply one or more):

  1. Validate the returned task record matches the requested task:

    • cancelled.task.taskId === params.taskId
    • and (if applicable) cancelled.task.ownerKey === expectedOwnerKey (requires passing/deriving owner context)
  2. Prefer to ignore cancelled.task from the runtime and instead re-load the task from core storage after cancellation:

const cancelled = await registeredRuntime.cancelDetachedTaskRunById(params);
if (cancelled.found) {
  const task = getTaskById(params.taskId);
  return {
    ...cancelled,
    task: task ?? undefined,
  };
}
  1. If this API must be exposed to plugins/users, change the contract to return a minimal representation (e.g., {taskId, status}) rather than a full TaskRecord.

Analyzed PR: #68915 at commit 68717e7

Last updated on: 2026-04-19T11:21:37Z

@mbelinky mbelinky force-pushed the mariano/detached-runtime-contract branch 2 times, most recently from 3c942b2 to 3bcd35f Compare April 19, 2026 11:09
@mbelinky mbelinky force-pushed the mariano/detached-runtime-contract branch from 3bcd35f to 68717e7 Compare April 19, 2026 11:12
@mbelinky mbelinky merged commit bd3ad34 into main Apr 19, 2026
30 checks passed
@mbelinky mbelinky deleted the mariano/detached-runtime-contract branch April 19, 2026 11:13
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 68717e7317

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +717 to +719
if (registeredRuntime) {
const cancelled = await registeredRuntime.cancelDetachedTaskRunById(params);
if (cancelled.found) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Handle runtime cancel exceptions in detached cancel path

cancelDetachedTaskRunById directly awaits registeredRuntime.cancelDetachedTaskRunById(...) without a guard, so any runtime-side exception (for example transient RPC/network failures) will throw out of this API instead of returning the structured cancel result that callers expect. Since this function now fronts flow and task cancellation paths, one thrown runtime error can abort cancellation mid-operation and skip both legacy fallback and reason reporting.

Useful? React with 👍 / 👎.

jinon86 pushed a commit to jinon86/openclaw that referenced this pull request Apr 19, 2026
* test: stabilize standalone Parallels smoke lanes

* fix: strip orphaned OpenAI reasoning blocks before responses API call (openclaw#55787)

Merged via squash.

Prepared head SHA: 263b952
Co-authored-by: suboss87 <11032439+suboss87@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman

* fix: stabilize release smoke reruns

* test: tolerate empty fireworks live responses

* test: make OpenWebUI smoke deterministic

* tasks: extract detached task lifecycle runtime (openclaw#68886)

* tasks: extract detached task lifecycle runtime

* tests: relax gateway seam expectation

---------

Co-authored-by: Mariano Belinky <mariano@mb-server-643.local>

* test: complete workspace setup in update smokes

* fix: parse PowerShell cron tools allow-list (openclaw#68858) (thanks @chen-zhang-cs-code)

* fix(cron): parse PowerShell tools allow list

* fix(cron): clarify tools allow-list help

* fix: parse PowerShell cron tools allow-list (openclaw#68858) (thanks @chen-zhang-cs-code)

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>

* fix(browser): discover CDP websocket from bare ws:// URL before attach (openclaw#68715)

* fix(browser): discover CDP websocket from bare ws:// URL before attach

When browser.cdpUrl is set to a bare ws://host:port (no /devtools/ path), ensureBrowserAvailable would call isChromeReachable -> canOpenWebSocket against the URL verbatim. Chrome only accepts WebSocket upgrades at the specific path returned by /json/version, so the handshake failed immediately with HTTP 400. With attachOnly: true, that surfaced as:

  Browser attachOnly is enabled and profile "openclaw" is not running.

even though the CDP endpoint was reachable and the profile was healthy. Reproduced by the new tests in chrome.test.ts and cdp.test.ts (openclaw#68027).

Fix: introduce isDirectCdpWebSocketEndpoint(url) — true only when a ws/wss URL has a /devtools/<kind>/<id> handshake path. Route any other ws/wss cdpUrl (including the bare ws://host:port shape) through HTTP /json/version discovery by normalising the scheme via the existing normalizeCdpHttpBaseForJsonEndpoints helper. Apply this in isChromeReachable, getChromeWebSocketUrl, and createTargetViaCdp. Direct WS endpoints with a /devtools/ path are still opened without an extra discovery round-trip.

Fixes openclaw#68027

* test(browser): add seeded fuzz coverage for CDP URL helpers

Adds property-based / seeded-fuzz tests for the URL helpers the
attachOnly CDP fix depends on (openclaw#68027):

  - isWebSocketUrl
  - isDirectCdpWebSocketEndpoint
  - normalizeCdpHttpBaseForJsonEndpoints
  - parseBrowserHttpUrl
  - redactCdpUrl
  - appendCdpPath
  - getHeadersWithAuth

Follows the existing repo convention (see
src/gateway/http-common.fuzz.test.ts): no fast-check dep, small
mulberry32 PRNG + hand-rolled generators, deterministic per-describe
seeds so failures are reproducible.

Lifts cdp.helpers.ts coverage from 77.77% -> 89.54% statements,
67.9% -> 80.24% branches, 78% -> 90% lines. Remaining uncovered
lines are inside the WS sender internals (createCdpSender,
withCdpSocket, fetchCdpChecked rate-limit branch), which require
integration-style mocks and are unrelated to the attachOnly fix.

* test(browser): drive cdp.helpers/cdp/chrome to 100% coverage

Lifts the three files touched by the openclaw#68027 attachOnly fix to 100% statements/branches/functions/lines across the extensions test suite. Adds cdp.helpers.internal.test.ts, cdp.internal.test.ts, and chrome.internal.test.ts covering error paths, branch matrices, CDP session helpers, Chrome spawn/launch/stop flows, and canRunCdpHealthCommand. Defensively unreachable guards are annotated with c8 ignore + inline justifications.

* fix(browser): restore WS fallback for non-/devtools ws:// CDP URLs

When /json/version discovery is unavailable (or returns no
webSocketDebuggerUrl), fall back to treating the original bare ws/wss
URL as a direct WebSocket endpoint. This preserves the openclaw#68027 fix for
Chrome's debug port while restoring compatibility with Browserless/
Browserbase-style providers that expose a direct WebSocket root without
a /json/version endpoint.

Priority order for bare ws/wss cdpUrl inputs:
  1. /devtools/<kind>/<id> URL \u2192 direct handshake, no discovery (unchanged)
  2. bare ws/wss root \u2192 try HTTP discovery first; if discovery returns a
     webSocketDebuggerUrl use it; otherwise fall back to the original URL
     as a direct WS endpoint
  3. HTTP/HTTPS URL \u2192 HTTP discovery only, no fallback (unchanged)

Affected call sites: isChromeReachable, getChromeWebSocketUrl,
createTargetViaCdp.

Also renames a misleading test ('still enforces SSRF policy for direct
WebSocket URLs') to accurately describe what it tests: SSRF enforcement
on the navigation target URL, not on the CDP endpoint.

New tests added for all three fallback paths. Coverage remains 100% on
all three touched files (238 tests).

* fix: browser attachOnly bare ws CDP follow-ups (openclaw#68715) (thanks @visionik)

* test(tasks): align detached runtime mock return types

* browser: route existing-session user profile through browser nodes (openclaw#68891)

* browser: route user profile through browser nodes

* browser: align existing-session node docs

* browser: preserve host fallback on node discovery errors

* browser: preserve configured node pin errors

* browser: widen config mock in node pin test

* fix: default kimi thinking to off (openclaw#68907)

Co-authored-by: termtek <termtek@ubuntu.tail2b72cd.ts.net>

* docs(changelog): note kimi thinking default fix

* docs(changelog): add thanks for kimi fix

* tasks: add detached runtime plugin registration contract (openclaw#68915)

* tasks: register detached runtime plugins

* tasks: harden detached runtime ownership

* tasks: extract detached runtime contract types

* changelog: note detached runtime contract

* changelog: attribute detached runtime contract

* feat(ui): add a2a operator dashboard shell

* fix(ui): align a2a baseline with current read contract

* feat(ui): add A2A case inspector panels

* CI: route small workflows to ubuntu-latest

* CI: route fork-blocked workflows to GitHub-hosted runners

* CI: fall back to GITHUB_TOKEN for fork automation

* CI: skip app-token steps when fork secrets are absent

* CI: gate fork app-token steps through env guards

* CI: fix stale fallback env guard

* fix(ui): sync A2A locale snapshots

* fix(ui): sync A2A locale metadata

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
Co-authored-by: Subash Natarajan <suboss87@gmail.com>
Co-authored-by: suboss87 <11032439+suboss87@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Co-authored-by: Mariano <132747814+mbelinky@users.noreply.github.com>
Co-authored-by: Mariano Belinky <mariano@mb-server-643.local>
Co-authored-by: ZC <chenzhangcode@163.com>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
Co-authored-by: Viz <visionik@pobox.com>
Co-authored-by: Frank Yang <frank.ekn@gmail.com>
Co-authored-by: termtek <termtek@ubuntu.tail2b72cd.ts.net>
Co-authored-by: bangtong-ai <bangtong-ai@users.noreply.github.com>
Co-authored-by: OpenClaw Agent <openclaw-agent@users.noreply.github.com>
Co-authored-by: OpenClaw Bot <bot@openclaw.local>
Mquarmoc pushed a commit to Mquarmoc/openclaw that referenced this pull request Apr 20, 2026
)

* tasks: register detached runtime plugins

* tasks: harden detached runtime ownership

* tasks: extract detached runtime contract types

* changelog: note detached runtime contract

* changelog: attribute detached runtime contract
lovewanwan pushed a commit to lovewanwan/openclaw that referenced this pull request Apr 28, 2026
)

* tasks: register detached runtime plugins

* tasks: harden detached runtime ownership

* tasks: extract detached runtime contract types

* changelog: note detached runtime contract

* changelog: attribute detached runtime contract
ogt-redknie pushed a commit to ogt-redknie/OPENX that referenced this pull request May 2, 2026
)

* tasks: register detached runtime plugins

* tasks: harden detached runtime ownership

* tasks: extract detached runtime contract types

* changelog: note detached runtime contract

* changelog: attribute detached runtime contract
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 9, 2026
)

* tasks: register detached runtime plugins

* tasks: harden detached runtime ownership

* tasks: extract detached runtime contract types

* changelog: note detached runtime contract

* changelog: attribute detached runtime contract
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

commands Command implementations maintainer Maintainer-authored PR size: L

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant