Commit Graph

143 Commits

Author SHA1 Message Date
Peter Steinberger 3fa3276381 feat(nodes): opt-in container isolation for node-hosted worker sessions (#128447)
* feat(nodes): opt-in container isolation for node-hosted worker sessions

nodeHost.workerRuns.isolation=container runs each worker session inside a
Docker-compatible container (docker/OrbStack/podman): stdio launch transport,
exactly two bind mounts (bundle read-only, workspace read-write), allowlisted
env, and the container itself as the durable launch identity — created stopped
and journaled before the descriptor is delivered, killed/removed on cancel,
fence, shutdown, and recovery, with an owned-orphan sweep at startup. Missing
or changed engines disable hosting with an explicit diagnostic instead of
falling back to bare processes. Additive nullable worker_container_json column
on node_worker_launches (schema version unchanged).

* fix(test): base-shape fixture skips additive columns of stripped tables

node_worker_launches is excluded from claw-scoped state schemas, so the
read-only compat fixture cannot drop its additive column there; drop only
columns whose owning table exists.

* fix(test): guard split destructuring in base-shape fixture

* test(state): record worker_container_json in the canonical additive-column list

* fix(nodes): harden container isolation per review findings

- Revalidate the engine daemon target immediately before container creation;
  a replaced daemon receives zero create/start requests (regression proves it).
- A pending launch keeps its worker slot until the journaled container is
  killed/removed; cancellation cannot free capacity over a live container.
- Windows node hosts fail closed at startup for isolation=container with an
  actionable diagnostic (native paths cannot be container mount targets).
- Container identity moves from an additive column to the same-version
  companion table node_worker_launch_containers (bare STRICT, lazily ensured
  on first container write, pruned with the launch journal): the launch table
  is contract-optional and shipped readers reject additive columns there. An
  exact v9 predecessor now provably opens and uses the journal after the
  candidate populated container rows.
2026-08-24 00:19:45 -07:00
Vincent Koc 52533dc89e docs(nodes): clarify Mac node system commands (#128497)
Punchcard-Session: silver-summit-cedar-9k
2026-08-23 22:04:45 -07:00
Peter Steinberger 0978d55b05 feat(nodes): automatic device placement for sessions.dispatch (#128421)
* feat(nodes): automatic device placement for sessions.dispatch

sessions.dispatch gains autoDevice: true — the gateway selects the eligible
session-host node with the most available worker slots (deterministic
tie-break), retries up to three candidates when a node churns at the
pre-provisioning eligibility fence, and reports the chosen device in the
placement runner projection. Control UI offers Any available node with
actionable disabled reasons. No-eligible-host failures state why.

* fix(ui): break draft-place-state/draft-session-placement import cycle

resolveDraftSessionPlacement only needs four scalar fields; a structural
param type replaces the Pick<DraftPlaceState,...> import that created the
madge cycle.

* fix(ui): keep the devices section hidden when no devices are paired

The Any available node row lives inside the Your devices section; rendering
it with zero paired devices resurrected the section on gateway-only setups.
Gate it on device presence — Connect a machine remains the discoverability
path — and cover both the empty and non-hostable cases.

* fix(gateway): project dispatch runner state through the canonical reader

The dispatch reply no longer synthesizes an available device runner; it uses
the fenced workerPlacementRunnerAvailabilityReader (and disk-space reader)
exactly like session reads, so a node lost after durable provisioning
projects offline consistently. Documents placement.runner.deviceId in the
protocol reference.
2026-08-23 21:08:33 -07:00
MarMar Labs 99c35c85b4 docs(exec): correct host=auto per-call override semantics (#120331)
The exec docs claimed a per-call `host=node` request is always allowed
from `tools.exec.host=auto`, gating only `host=gateway` on whether a
sandbox runtime is active. The runtime treats both identically:
`isRequestedExecTargetAllowed` rejects `node` and `gateway` alike from
`auto` while a sandbox is available, and allows both when it is not.

Align the three pages that carried the asymmetric claim with the
test-locked runtime contract, and point readers at the explicit
`tools.exec.host=node` path the rejection error already recommends.

Refs #61009
2026-08-23 17:09:54 -07:00
Peter Steinberger 259a14b83b feat(nodes): derive worker capacity from CPU cores and make it configurable (#128352)
Node session hosting was capped at a hardcoded 2 concurrent worker sessions.
The default is now one worker slot per available CPU core (clamped to 1..1024),
and nodeHost.workerRuns.capacity overrides it explicitly. The capacity option
was already plumbed through the supervisor; this wires it from config and
deletes the hardcoded constant. Docs and config baselines regenerated.
2026-08-23 14:09:06 -07:00
Peter Steinberger 1353ce0995 feat: run Codex sessions on approved paired devices (#127202)
* feat(codex): execute paired-device sessions over node carrier

* fix(node-host): preserve approved invocation session identity

* fix(codex): observe paired-node execution leases before handshake

* fix: fence paired-device placement and Codex execution owners

* fix: satisfy paired-device placement CI ownership guards

* fix(codex): reject credentialed paired-node URL parameters

* fix(codex): fence nested remote HTTP session credentials

* fix(codex): scrub node process URLs and preserve plaintext HTTP

* test(codex): republish node inventory after capability approval
2026-08-21 12:50:26 -07:00
Peter Steinberger 0d555266e4 refactor(nodes): retire legacy runner inventory parsing (#126773) 2026-08-20 11:53:00 -07:00
Peter Steinberger bbbd70542b feat(sessions): recover offline device placements (#126284)
* feat(sessions): recover offline device placements

* chore(protocol): refresh session placement models

* perf(ui): lazy-load session placement recovery

* test(ui): remove dropdown timing assertion

* test(ui): await committed cloud recovery route

* fix(sessions): complete explicit abandonment locally

* fix(sessions): fence lists by runner availability

* fix(ui): preserve canonical session freshness

* fix(sessions): preserve recovery contracts after rebase

* test(ui): await durable cloud recovery entry

* test(gateway): complete current runner fixtures

* fix(gateway): publish runner availability edges

* fix(ui): preserve shared session freshness

* fix(ui): preserve canonical sidebar session state

* fix(sessions): preserve abandoned partials and run-owned replies

* fix(sessions): resume durable abandonment retries

* test(gateway): compose provisioning replay with runner availability

* fix(sessions): publish recovered move transitions
2026-08-20 09:59:59 -07:00
Peter Steinberger 2d13e16705 fix: harden node desktop carrier (#126425) 2026-08-19 12:57:26 -07:00
Peter Steinberger ae55a4090c refactor(canvas): make the panel a widget presenter (#126030)
* refactor(canvas): retire legacy host and commands

* refactor(apple): narrow shared Canvas contracts

* refactor(macos): keep Canvas as widget presenter

* refactor(ios): remove Canvas client

* refactor(android): remove Canvas client

* refactor(linux): remove Canvas client

* fix(ci): isolate native locale artifacts

* fix(linux): regenerate companion lockfile

* fix(canvas): refresh native tool display metadata

* test(canvas): align coverage with presenter surface

* test(canvas): remove obsolete asset root seam

* test(canvas): stabilize retirement CI coverage

* refactor(swift): remove orphaned resource wrapper

* test(ios): remove retired canvas layout assertion

* fix(macos): reserve retired canvas command namespace

* refactor(macos): isolate canvas command policy

* fix(canvas): select only eligible macOS panels

* fix(canvas): keep panel selection plugin-owned
2026-08-19 08:21:07 -07:00
Peter Steinberger 80934e5639 feat(ui): dispatch sessions to paired devices (#126187) 2026-08-19 01:11:49 -07:00
Peter Steinberger 15fb00eb6b fix: surface paired-device runtime requirements (#126067)
* fix: surface paired-device runtime requirements

* test: cover session runtime placement metadata

* test(gateway): stabilize runtime catalog fixtures

* fix(gateway): reject incompatible device moves
2026-08-18 19:10:41 -07:00
Peter Steinberger 2d3aaaa0d5 feat(node-host): support Cloudflare Access service tokens (#125193)
* feat(node-host): support Cloudflare Access service tokens

Carry SecretRef-backed, origin-bound Cloudflare Access credentials across node onboarding, gateway and worker connections, and node-owned worker transfers. Refs #125112.

* chore: align changed gate after rebase

* test(node-host): keep access proof within lint budgets

* style(node-host): keep supervisor within line budget

* fix(node-host): omit absent access config

* fix(node-host): reject Access credentials on plaintext routes

* fix(node-host): reject plaintext before ref resolution

* test: cover Access log redaction over TLS
2026-08-17 04:12:27 -07:00
Peter Steinberger ee86b24ac1 fix(node-host): recover MCP catalogs and sessions (#125092)
* fix(node-host): recover MCP catalogs and sessions

Share placement-neutral MCP lifecycle and result projection while keeping Gateway session ownership and node process ownership separate. Refresh node catalogs live, recover closed or expired transports without replay, and preserve MCP application errors across node.invoke.\n\nFixes #125044

* test: register node MCP CI inventory

* fix(node-host): stop MCP recovery after abort
2026-08-17 01:39:33 -07:00
Peter Steinberger 8f145789b5 fix: diagnose gateways that cannot host nodes (#125132)
* fix(doctor): expose node hosting preconditions

* fix(doctor): use canonical string coercion

* fix(auth): narrow identity header rejection
2026-08-17 00:30:21 -07:00
Peter Steinberger 4d872fbfbc feat(nodes): expose installed worker bundle status (#124640)
* feat(nodes): expose installed worker bundle status

* perf(nodes): defer bundle status validation

* fix(nodes): tighten bundle status type contracts

* docs(gateway): document node worker bundle status

* refactor(gateway): split runner inventory runtime

* test(ui): keep healthy device status quiet

* fix(ui): preserve steer target ordering

* test(ui): isolate catalog handoff lifecycle
2026-08-16 10:04:46 -07:00
Peter Steinberger 78502eda6d feat(workers): run device sessions from Gateway bundles (#124037)
* feat(workers): run device sessions from Gateway bundles

Install the current Gateway bundle before a device environment becomes ready, verify it at attach and tunnel boundaries, launch only from the immutable namespaced bundle directory, and retire stale environments for idempotent reprovisioning. Remove the local execution mode and preserve the node-local build claim only as temporary inventory metadata for the final projection/cleanup slice.

* docs(runners): record Gateway bundle cutover

* test(ci): repair runner validation fixtures

# Conflicts:
#	src/scripts/test-projects.test.ts

* fix(workers): surface outdated node recovery

Keep legacy runner inventory diagnostic-only while exposing the update-and-reconnect action through node, environment, provider, placement, and Control UI surfaces.

* fix(workers): reject legacy inventory with recovery

* fix(workers): bundle worker deploy closure

* test(workers): close bundle cutover gates

* fix(workers): compose browser runtime at build

* fix(workers): satisfy bundle cutover gates

* fix(workers): route temp runtime through infra

* docs(workers): align bundle host guidance

* fix(ui): fence outdated session destinations
2026-08-15 17:46:44 -07:00
Peter Steinberger e44dde218f fix(gateway): revoke device workers when pairing is removed (#123696)
* fix(gateway): revoke device workers on unpair

* refactor(backup): split sqlite verification

* refactor(backup): narrow sqlite verifier exports

* test(gateway): track worker startup temp state

* refactor(backup): preserve sqlite architecture boundary

* test(gateway): follow worker startup logger binding

* fix(gateway): revoke workers during pairing pruning
2026-08-14 10:10:12 -07:00
Peter Steinberger 7d8f066b81 feat(runners): expire dormant device leases (#123673) 2026-08-14 06:53:29 -07:00
Peter Steinberger 38379a9a2e feat(runners): enforce node worker capacity (#123612)
* feat(runners): enforce node worker capacity

* test(node-host): mock supervisor initialization

* fix(runners): surface capacity exhaustion
2026-08-14 04:49:47 -07:00
Peter Steinberger 7aac8ee04b fix: preserve device sessions while runner is offline (#123477)
* fix(runners): recover offline device placements

* test(runners): model reclaimed launch dispatch

* ci: serialize macOS Swift test retries

* test: wait for steering subscription readiness

* fix(gateway): restore authority-safe steering

* test(gateway): align authority projector fixture

* fix(gateway): dispatch leaf authority mismatches

* refactor: keep authority overlay contract leaf-only

* test(ui): close place popover before details
2026-08-14 03:31:19 -07:00
Peter Steinberger 23c9c04572 fix(sessions): hide unowned host catalogs on multi-user gateways (#123421)
* fix(sessions): scope catalogs by caller identity

* fix(llama-cpp): keep doctor imports lightweight
2026-08-13 19:28:22 -07:00
Peter Steinberger 6c9916a48a feat(runners): publish atomic device runner inventory (#123094)
* feat(runners): publish atomic node inventory

# Conflicts:
#	docs/.generated/plugin-sdk-api-baseline/agent-harness-runtime.json
#	src/gateway/server-methods/environments.ts
#	src/gateway/worker-environments/device-provider.test.ts

* fix(runners): refresh topology on inventory removal

* fix(runners): resolve session host type overlap

* fix(protocol): document mobile runner inventory event

* fix(runners): project authoritative session host state

* docs(nodes): clarify prepared session host status
2026-08-13 04:54:26 -07:00
Peter Steinberger 7a4b5a0a41 feat(gateway): provision paired node session hosts (#122966)
* feat(gateway): provision paired node worker installs

* docs: record node local-install PR chain

* chore(protocol): regenerate node worker manifest models

* fix(gateway): resolve node worker build through the device runtime

Repairs a rebase artifact: startup referenced a stale deviceNodeRegistry
binding; the device runtime now owns the connected-node lookup via
resolveWorkerBuild.
2026-08-13 01:56:19 -07:00
Peter Steinberger 0c824f09d5 feat(gateway): observe paired node desktops (#122724)
* feat(gateway): stream paired node desktops

* chore(protocol): refresh desktop observe model

* fix(gateway): preserve desktop stream boundaries

* fix(gateway): keep desktop streams lifetime-bound

* fix(gateway): harden node desktop stream lifecycle

* fix(gateway): stabilize node desktop lifecycle setup

* chore(plugin-sdk): refresh API baselines
2026-08-12 13:17:45 -07:00
Peter Steinberger d44f70eb4b feat(pairing): one-paste device pairing via oc-pair setup links (#120768)
* feat(pairing): one-paste device pairing via oc-pair setup links

Implements milestone 3 from docs/plan/runners.md.

* fix(pairing): sign bootstrap handshake, keep URL candidates, wire pairing countdown

* test(gateway): update client callsite guard

* fix(pairing): preserve setup URL context paths

* fix(ui): keep pairing help aligned with setup mode

* fix(pairing): isolate bootstrap credentials

* perf(ui): keep one-paste pairing within bundle budget

* refactor(pairing): isolate native pair URL prefix parsing

* fix(pairing): preserve candidate lifecycle state

* fix(pairing): retire shared credentials after bootstrap

* fix(pairing): apply rotated manifest through client owner

* test(pairing): prove bootstrap retirement across reconnect

* fix(pairing): preserve native gateway context paths

* fix(pairing): carry native context paths through reconnect

* fix(ios): preserve encoded gateway context path

* chore(plugin-sdk): refresh pairing API baselines
2026-08-12 02:01:33 -07:00
Peter Steinberger 914f73ac99 docs: replace retired config keys with canonical schema keys (#121330) 2026-08-09 20:30:43 -07:00
Peter Steinberger a5b798a88c fix(cli): avoid missing-facing camera snap failures (#120430)
* fix(cli): use node default camera for snap

* test(cli): isolate ClawHub package preflight

* test(cli): split Claws package fixture
2026-08-08 08:05:50 -07:00
Peter Steinberger 6176c0a79d feat(macos): control motorized camera pan, tilt, and zoom (#120511)
* feat(macos): add native camera PTZ controls

Add physical UVC pan, tilt, and zoom through the signed Mac app, with camera.ptz.control kept behind dangerous-command approval. Verified against real Insta360 Link 2 Pro hardware.

* refactor(agents): split message tool display config

* fix(mac): harden camera PTZ contracts
2026-08-08 08:04:29 -07:00
Paco Cartones 955c8fe4af docs(nodes): system.run parameters were documented as non-existent CLI flags (#116930)
* docs(nodes): system.run parameters were documented as non-existent CLI flags

* docs(nodes): remove retired node env flags

Co-authored-by: Paco Cartones <253313177+pacocartones@users.noreply.github.com>

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-07-31 16:11:24 -07:00
synth 151c549494 fix(browser): uploads fail when the browser runs on a remote node (#115291)
* fix(browser): resolve upload paths on the owning browser node, not the Gateway

When a browser session is proxied to a remote node, the upload action
previously ran resolveExistingUploadPaths on the Gateway, pinning paths
to a filesystem the node cannot see and rejecting node-local files. The
node-side /hooks/file-chooser route already re-resolves paths against
its own filesystem, so skip Gateway-local resolution whenever the
request is proxied and forward the requested paths as-is.

Fixes openclaw/openclaw#115251

* fix(browser): transfer uploads to remote browser nodes

* fix(browser): normalize upload abort errors

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-07-29 20:31:46 +08:00
Peter Steinberger 938749180e refactor(nodes)!: retire arming leases for durable capability enablement (#114892)
* refactor(nodes): retire phone control leases

* fix(doctor): clean retired phone control state

* docs(nodes): document durable capability enablement

* docs(changelog): defer node enablement note

* fix(doctor): harden retired phone control cleanup

* fix(doctor): fail closed on unreadable lease state

* fix(doctor): validate retired lease journals

* fix(doctor): preserve deny-shadowed consent

* docs: refresh node authorization map

* fix(doctor): keep retirement helpers private
2026-07-27 23:51:30 -04:00
Peter Steinberger 9eae43bd37 refactor(infra): move exec approvals into the shared SQLite state DB (#114063)
* refactor(infra): move exec approvals into the shared SQLite state DB

Delete the file-runtime exec-approvals store (exec-approvals.json + .lock
sidecar machinery) on both runtimes and make the reserved
exec_approvals_config singleton row canonical. Doctor owns the one-time
import with claim/verify/receipt discipline; runtime fails closed with a
doctor instruction while un-migrated legacy state exists. The wire CAS
contract, socket semantics, and gateway auth-token derivations are
unchanged. Kills the #113929 lock-contention bug class structurally and
nets around -2.9k lines.

* fix(infra): green CI gates and retire file-era exec approvals tests

Break the migration-type import cycle with a leaf contract, regenerate the
plugin-SDK API and native i18n baselines for the intentional surface change,
drop unused exports, and replace the macOS file-era approvals test suite with
SQLite-backed behavior coverage per the obsolete-internals test policy.

* chore: green max-lines ratchet, native i18n baseline, and unused-export scan
2026-07-26 06:39:23 -04:00
Peter Steinberger df3ff35277 refactor(state): move device auth tokens to SQLite (#112663)
* refactor(state): store device auth tokens in sqlite

* fix(state): keep device auth migration types acyclic

* fix(state): keep migration detection type private
2026-07-22 06:13:03 -07:00
Peter Steinberger edecdbd05e refactor(config): config-surface reduction tranche 3 — product consolidations (review request) (#111527)
* refactor(config): consolidate media model lists

* refactor(config): unify memory configuration

* refactor(config): consolidate TTS ownership

* refactor(config): move typing policy to agents

* refactor(config): retire product-level config surfaces

* refactor(config): share scoped tool policy type

* chore(config): refresh generated baselines

* fix(config): honor agent typing overrides

* fix(config): migrate sibling config consumers

* refactor(infra): keep base64url decoder private

* fix(config): strip invalid legacy TTS values

* chore(config): refresh rebased baseline hash

* fix(doctor): route legacy messages.tts.realtime voice to talk during tts move

* refactor(config): polish final layout names

* refactor(config): freeze retired tuning defaults

* feat(config): add fast mode default symmetry

* refactor(config): key agent entries by id

* docs(config): update final layout reference

* test(config): cover final layout migrations

* chore(config): refresh final layout baselines

* fix(config): align final layout runtime readers

* fix(config): align remaining readers

* fix(config): stabilize final layout migrations

* fix(config): finalize config projection proof

* fix(config): address final layout review

* docs(release): preserve historical config names

* fix(config): complete keyed agent migration

* fix(config): close final migration gaps

* fix(config): finish full-branch review

* fix(config): complete runtime secret detection

* fix(config): close final review findings

* fix(config): finish canonical docs and heartbeat migration

* fix(config): integrate latest main after rebase

* refactor(env): isolate test-only controls

* refactor(env): isolate build and development controls

* refactor(env): collapse process identity indirection

* refactor(env): remove duplicate config and temp aliases

* docs(env): define the operator-facing allowlist

* ci(env): ratchet production variable count

* fix(env): remove stale provider helper import

* fix(env): make ratchet sorting explicit

* test(env): keep test seam in dead-code audit

* test(env): cover ratchet growth and boundary; document surface budgets

* docs(config): document tier-eval consolidations

* docs(config): clarify speech preference ownership

* test(memory): align retired tuning fixtures

* refactor(memory): freeze engine heuristics

* refactor(config): apply tier-eval tranche

* refactor(tts): move persona shaping to providers

* refactor(compaction): move prompt policy to providers

* test(config): align hookified prompt fixtures

* chore(deadcode): classify test-only exports

* chore(github): remove unused spawn helper

* chore(deadcode): classify queue diagnostics

* chore(deadcode): remove unused lane snapshot export

* chore(plugin-sdk): ratchet consolidated surface

* fix(config): integrate latest main after rebase
2026-07-21 20:28:43 -07:00
Peter Steinberger c84921634d fix(macos): require explicit consent for privacy-sensitive access (#112321)
* fix(macos): avoid passive Automation prompts

* fix(macos): keep Voice Wake recognition on device

* fix(macos): require consent for activity presence

* chore(apps): refresh native i18n inventory

* fix(macos): preserve presence clears across gateway versions

* fix(macos): prioritize activity privacy opt-out

* chore(apps): refresh native i18n inventory

* fix(macos): scrub legacy presence activity

* fix(macos): migrate permission status caller

* fix(macos): preserve unknown permission state

* fix(macos): refresh privacy change artifacts

* refactor(macos): remove stale presence helper

* fix(deps): patch URI and Jaeger advisories

* test(gateway): adopt pairing-bound node sessions
2026-07-21 15:28:13 -07:00
Papilionidae 3dfb4c9cd7 fix(anthropic): accept "cli" entrypoint in Claude session catalog discovery (#105162)
* fix(anthropic): accept "cli" entrypoint in Claude session catalog discovery

Claude Code v2.x writes entrypoint: "cli" in its JSONL session files.
Previously only "sdk-cli" was recognized, causing all v2.x sessions to
be silently skipped.

Extract CLI_ENTRYPOINTS set and isCliEntrypoint() helper, then apply
the same fix to both TypeScript catalog and macOS paired-node native
catalog. Add matching regression coverage on both platforms.

Closes #105164

* fix(anthropic): refresh Claude CLI session discovery

Apply the reviewed cli/sdk-cli allowlist, mirrored negative coverage, and fallback-scope documentation to current main.

Co-authored-by: 黄攀0668000858 <huang.pan@xydigit.com>

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-07-21 07:14:42 -07:00
Peter Steinberger 40f28f8e81 feat(nodes): make computer.act eligibility capability-based for desktop nodes (#112107) 2026-07-21 00:04:04 -07:00
Peter Steinberger 98410d986e refactor(state): move device identity into canonical SQLite state (#110392)
* refactor: store device identities in SQLite

* style: satisfy Swift identity lint limits

* test: harden device identity migration fixtures

* fix: harden SQLite device identity recovery

* chore: remove stale identity helper

* refactor: isolate device identity repair

* chore: remove stale migration import

* test: validate migrated identity key types

* fix: harden device identity repair

* style: format device identity assertion

* fix: derive repaired identity key from PEM

* style: remove redundant PEM conversions

* fix: align native identity store with schema v4

* fix(state): satisfy SQLite identity CI gates

* fix(state): coordinate native identity migration

* test(doctor): include native identity claim path

* fix(infra): preserve coordinator release error

* fix(state): clear identity validation gates

* refactor(apple): remove identity test-only APIs

* refactor(apple): remove dead identity error type

* test(apple): use canonical identity test seam

* test(macos): isolate gateway readiness identity
2026-07-18 17:43:56 +01:00
Peter Steinberger da69daeb72 feat(onboarding): recommend plugins and skills from installed apps (#109668)
* feat(onboarding): recommend plugins and skills from installed apps

Scan installed macOS apps during classic onboarding (TCC-free), gather
candidates from official catalogs + ClawHub search, let the configured
model pick genuine matches, and offer an opt-in multiselect install step.
Adds a device.apps node-host command (default-off sharing, Android-parity
envelope) so remote gateways can request a paired Mac's inventory, and a
wizard.appRecommendations kill switch. Custom setup-inference completions
no longer inherit the 32-token verification-probe output cap.

* feat(onboarding): recommend apps in guided flow

* fix(onboarding): harden app recommendations against ClawHub self-promotion

Third-party ClawHub skills are never pre-selected regardless of model tier
(publisher-controlled listing text reaches the matcher prompt and could
promote itself); their labels now say they install third-party code.
Installed-app scans follow symlinked .app bundles. Matcher output stays
bounded by the resolved model's own maxTokens budget (documented invariant).

* fix(onboarding): key official catalog candidates by resolved plugin id

Real catalog entries are package manifests without a top-level id; keying the
candidate map and channel/provider classification by entry.id collapsed the
whole official catalog into one undefined-keyed entry, so no official plugin
or channel was ever recommended. Regression test runs against the bundled
catalogs.

* fix(onboarding): satisfy lint, types, deadcode, and migration gates

Split the guided-onboarding test into a self-contained custodian suite to stay
under max-lines. Narrow app-recommendation exports (drop dead node-payload
normalizer, unexport internal types/helpers, route candidate tests through the
public API), replace map-spread with a helper, unexport device.apps result
types, add installedAppsSharing to node-host migration expectations, cast the
wizard multiselect mock, and regenerate the docs map.

* test(onboarding): register new live test in the shard classifier
2026-07-17 14:07:59 +01:00
Peter Steinberger b1ef4265ea feat(ui): make sidebar catalog project groups collapsible (#109648)
Project group heads in the native session catalogs (Codex + Claude) are now
collapse toggles: chevron + aria-expanded, rows hidden while the head and
count stay visible. State persists per catalog+host+project through the
existing collapsed-sections localStorage set. Follow-up to #109575.
2026-07-16 21:49:57 -07:00
Peter Steinberger dad377cb1f feat(ui): group native session catalog rows by project in the sidebar (#109575)
* feat(ui): group native session catalog rows by project in the sidebar

Codex and Claude session catalogs in the Control UI sidebar now group each
host's sessions by project folder, mirroring Claude Code desktop: any cwd at
or under <repo>/.claude/worktrees/<name> folds into the origin repo. A folder
toggle icon in the catalog header flattens the list; grouping defaults to on
and persists via localStorage (openclaw:sidebar:sessions:catalog-grouping).

* chore(ui): drop generated i18n fallback metadata from source PR

Post-rebase main forbids catalog fallback metadata in source PRs; the
post-merge locale-refresh workflow reconciles foreign catalog parity.

* chore(ui): keep CatalogProjectGroup type module-local

knip dead-export gate: no external importers; callers rely on the inferred
return shape of groupCatalogSessionsByProject.

* test(scripts): drop retired workspace-state.json expectation

Opportunistic CI fix: b6535fb8de5/905395a3ff5 retired workspace-state
seeding from scripts/e2e/parallels/agent-workspace.ts but the smoke-model
test still asserted the removed string, breaking checks-node-compact-small-2
on every branch.

* test(scripts): restore mainline retired-state regression guard

Rebase conflict resolution wrongly kept this branch's older removal; main's
2256387885 comment plus not.toContain assertion is the canonical fix.
2026-07-16 20:58:32 -07:00
Peter Steinberger 766742c674 refactor(node-host): move configuration to shared SQLite (#108457)
* refactor(node-host): move config to shared SQLite

* test(node-host): satisfy migration gates
2026-07-15 14:02:23 -07:00
Peter Steinberger b363d5a293 feat(linux): canvas UI via CLI-node + Tauri app IPC bridge (#107633)
* feat(linux): canvas via CLI-node + Tauri app IPC bridge

* refactor: extract gateway helper modules

* build(linux-canvas): register plugin package in lockfile

* fix(linux-canvas): move canvas advertise test out of core, regen docs/protocol/deadcode

* fix(gateway): break node-catalog/registry import cycle via leaf normalize module; add canvas glossary term

* style: oxfmt invoke.ts and runtime.ts after buildNodeEventParams extraction

* fix(linux): load Canvas WebView via dedicated data_directory context

Wry's Linux/WebKitGTK incognito mode discards Tauri's registered
WebContext (wry webkitgtk/mod.rs), so the Canvas window got a fresh
ephemeral context without the openclaw-canvas:// scheme handler — the
bundled A2UI page never committed (stayed about:blank) and every A2UI
command timed out. Use an isolated cache-backed data_directory instead,
which keeps the protocol handler while still isolating Canvas storage
from the dashboard window.

* fix(linux): keep Canvas WebView ephemeral via incognito + data_directory

Autoreview flagged that a dedicated data_directory alone persists Canvas
browser state (cookies, localStorage, IndexedDB, service workers) across
restarts, so an agent that navigates Canvas to a site could leak an
authenticated session into a later session. iOS uses a non-persistent
store; Linux should match.

Add .incognito(true) alongside .data_directory(): the distinct directory
gives Tauri a fresh WebContext key so it still attaches the
openclaw-canvas:// protocol closure, and incognito makes Wry swap in a
fresh *ephemeral* context carrying those protocols. Live-verified on a
Wayland/WebKitGTK box: the bundled page still loads
(location.href=openclaw-canvas://localhost/index.html, openclawA2UI
present, A2UI renders) and the canvas-webview dir holds no persistent
cookie/storage files.
2026-07-14 16:05:14 -07:00
Peter Steinberger 084d1d9637 feat(control-ui): upload files into terminals (#107364)
* feat(control-ui): add terminal file uploads

* fix(control-ui): protect Windows terminal uploads

* fix(control-ui): satisfy terminal upload gates

* chore(ui): translate terminal upload labels

* fix(control-ui): reject unknown Windows upload shells

* fix(control-ui): harden Windows upload paths

* fix(control-ui): retry terminal upload cleanup

* chore(ui): reconcile locale translation memory

* fix(control-ui): validate terminal upload inputs

* fix(control-ui): retry failed upload cleanup

* chore(ui): refresh terminal upload locale metadata

* fix(terminal): integrate uploads with session recovery

* refactor(control-ui): isolate terminal upload transport

* fix(control-ui): keep upload helper types private
2026-07-14 09:33:39 -07:00
Peter Steinberger 644d4c3d23 fix: explain and control native session discovery (#107162)
* fix: make native session discovery configurable

* refactor: split native session discovery helpers

* chore: keep release notes in PR body

* fix: refresh generated session protocol models

* test(ui): avoid shared lazy-element mock race
2026-07-14 00:18:18 -07:00
Peter Steinberger e0b52a9618 feat: resume OpenCode and Pi sessions in terminal (#107200)
* feat: resume OpenCode and Pi sessions in terminal

* refactor: keep Pi session commands internal
2026-07-13 23:54:40 -07:00
Peter Steinberger d8d2f83cc1 feat(terminal): open Codex/Claude catalog sessions in a terminal on their owning host
Catalog session rows (sidebar context menu + click), the built-in viewer
header, and a new "Open Codex/Claude sessions in" preference can launch the
native CLI (codex resume / claude --resume) in the operator terminal on the
machine that owns the session.

- Gateway-local sessions spawn through the existing terminal launch policy
  (sandbox/enabled gates preserved) with the resume command in the session cwd.
- Paired-node sessions run through a new seq-ordered node PTY relay: a
  duplex node-host command streams PTY output via node.invoke.progress and
  receives keystrokes/resize via a new node.invoke.input event, behind the
  unchanged terminal.* client protocol (TerminalSessionManager gains a backend
  abstraction; node relay reuses the streaming-invoke controller).
- Owner boundary: each plugin owns its resume command and builds argv from a
  validated thread id; the gateway routes node opens through the node command
  allowlist and plugin invoke policy (no advertisement-only trust), and nodes
  re-verify session eligibility before spawning.
- UI setting catalogOpenTarget + canOpenTerminal capability gate every entry
  point; capability requires the owning host to actually have the CLI.

Node PATH is normalized before command-availability probes, Windows .cmd/.bat
shims spawn via ComSpec, and catalog terminal opens reattach persisted tabs
before opening the new tab.
2026-07-13 22:20:50 -07:00
Peter Steinberger 05fb55eb7b feat: discover OpenCode and Pi sessions on paired nodes (#106941)
* feat(sessions): add opencode and pi catalogs

* fix(sessions): satisfy catalog lint

* fix(sessions): validate pi storage paths

* fix(sessions): index growing pi transcripts

* refactor(sessions): split pi path resolution

* docs(sessions): refresh catalog map

* refactor(sessions): privatize pi catalog internals

* fix(sessions): validate node catalog payloads

* fix(sessions): harden external catalog discovery

* fix(sessions): normalize catalog transcript data

* refactor(gateway): keep node scope gate compact

* fix(opencode): reject option-like session ids

* fix(sessions): align external id validation
2026-07-13 21:45:53 -07:00
Peter Steinberger f167089d61 feat(nodes): continue Claude catalog sessions on paired nodes via streaming CLI agent runs (#105833)
* feat(gateway): stream node.invoke progress with idle timeouts and cancel

* feat(node-host): opt-in approval-gated claude cli agent run command

* feat(agents): run node-placed claude-cli turns through streaming node invoke

* feat(anthropic): continue claude catalog sessions on advertising nodes

* docs(nodes): document paired-node claude agent runs and continuation

* chore(plugin-sdk): admit spawn-invocation helper into surface budget

* fix(nodes): harden streaming invoke and node run policy from review findings

* test(node-host): prove multi-token tool-policy argv fails closed

* fix(nodes): allowlist cancel event for apps and break node-host type cycle

* chore(protocol): regenerate Swift gateway models for node invoke progress

* chore(node-host): drop test-only export surface flagged by deadcode ratchet

* style(node-host): merge invoke-types imports

* refactor(nodes): split node agent run modules to satisfy the LOC ratchet

* chore(protocol): regenerate android gateway models and sync export baseline

* chore(plugin-sdk): admit spawn-invocation helper and inherited deprecated drift into surface budget

* chore(plugin-sdk): tolerate inherited agent-core deprecated export

* fix(anthropic): drop node param parser orphaned by upstream catalog split

* ci: sync unused-export baseline with healed main

* test(agents): adopt renamed cli backend prepare helper

* style(node-host): format claude run spawn invocation

* ci: retrigger checks

* chore(plugin-sdk): pin surface budgets to healed-base counts

* fix(protocol): restore untyped lazy validators after upstream style change
2026-07-13 07:39:22 -07:00