Merge remote-tracking branch 'origin/main' into fix/inline-slash-skills

This commit is contained in:
Jesse Merhi
2026-08-13 20:06:32 +10:00
516 changed files with 14369 additions and 7321 deletions
+6 -4
View File
@@ -158,10 +158,12 @@ These are intentionally guarded by `test/scripts/ci-workflow-guards.test.ts`:
- `CI` concurrency key version, PR cancellation, and non-canceling canonical
`main` single-flight with one coalesced pending tip.
- `preflight` and hosted `security-fast` start immediately without a debounce
or standalone admission job. On Node-relevant canonical main pushes,
preflight also owns the sole dependency sticky-disk write and 8 GiB prune
before fanout; replacement visibility is proved only by a later exact-marker
restore because Blacksmith snapshot promotion can lag job completion.
or standalone admission job. On Node-relevant canonical main pushes and
same-repo pull requests, preflight owns the sole immutable semantic
dependency-cache write of workspace `node_modules` plus the local pnpm store
before fanout; all Blacksmith Node jobs are restore-only consumers and exact
misses fall back to the ordinary pnpm-store cache, while
hosted/fork/manual paths use only that store cache.
- CI matrix caps: fast/check lanes at 12, Node test shards at 28, Windows and
Android at 2.
- Canonical PR Node tests use one precise changed-target job when possible;
@@ -1,11 +0,0 @@
name: Register bind mount cleanup
description: Unmount a bind mount during post-job cleanup.
inputs:
path:
description: Absolute bind-mount target to unmount.
required: true
runs:
using: node24
main: main.cjs
post: post.cjs
post-if: always()
@@ -1,17 +0,0 @@
const fs = require("node:fs");
const path = require("node:path");
const mountPath = (process.env.INPUT_PATH ?? "").trim();
const statePath = process.env.GITHUB_STATE;
if (!mountPath || !path.isAbsolute(mountPath) || /[\r\n]/u.test(mountPath)) {
console.error("::error::Bind mount cleanup path must be an absolute single-line path");
process.exit(1);
}
if (!statePath) {
console.error("::error::GITHUB_STATE is unavailable");
process.exit(1);
}
fs.appendFileSync(statePath, `mountPath=${mountPath}\n`, "utf8");
console.log(`Registered bind mount cleanup for ${mountPath}`);
@@ -1,31 +0,0 @@
const path = require("node:path");
const { spawnSync } = require("node:child_process");
const mountPath = (process.env.STATE_mountPath ?? "").trim();
if (!mountPath || !path.isAbsolute(mountPath) || /[\r\n]/u.test(mountPath)) {
console.error("::error::Saved bind mount cleanup path is invalid");
process.exit(1);
}
const mountpoint = spawnSync("mountpoint", ["-q", mountPath], { stdio: "inherit" });
if (mountpoint.error) {
console.error(`::error::Failed to inspect bind mount: ${mountpoint.error.message}`);
process.exit(1);
}
if (mountpoint.status === 32) {
console.log(`Bind mount already absent: ${mountPath}`);
process.exit(0);
}
if (mountpoint.status !== 0) {
console.error(`::error::mountpoint exited with status ${mountpoint.status}`);
process.exit(1);
}
const unmount = spawnSync("sudo", ["umount", mountPath], { stdio: "inherit" });
if (unmount.error || unmount.status !== 0) {
const detail = unmount.error?.message ?? `status ${unmount.status}`;
console.error(`::error::Failed to unmount ${mountPath}: ${detail}`);
process.exit(1);
}
console.log(`Unmounted bind mount: ${mountPath}`);
+140 -221
View File
@@ -31,20 +31,12 @@ inputs:
description: Whether to save the pnpm store with actions/cache after install when no exact cache restored.
required: false
default: "false"
sticky-disk:
description: >
Mount a Blacksmith sticky disk for the pnpm store and bind its
node_modules directory over the checkout's stock node_modules path.
Only valid on Blacksmith Linux runners; pass use-actions-cache: "false"
alongside.
dependency-cache:
description: Whether to restore workspace node_modules and its local pnpm store from the exact semantic dependency cache.
required: false
default: "false"
save-sticky-disk:
description: >
Whether this job may commit the shared dependency snapshot. One
designated job per node-version key; only honored outside pull_request
events so feature-branch installs can never publish a protected
snapshot.
save-dependency-cache:
description: Whether to save workspace node_modules and its local pnpm store after a successful install on an exact cache miss.
required: false
default: "false"
vitest-fs-cache:
@@ -94,74 +86,85 @@ runs:
source "$GITHUB_ACTION_PATH/../setup-pnpm-store-cache/ensure-node.sh"
openclaw_ensure_node "$REQUESTED_NODE_VERSION"
- name: Configure dependency cache store
if: inputs.dependency-cache == 'true'
shell: bash
run: |
set -euo pipefail
# Keep both sides of pnpm's hard links below the workspace so one tar
# archive preserves them without relying on runner-home path depth.
echo "PNPM_CONFIG_STORE_DIR=$GITHUB_WORKSPACE/.cache/openclaw-pnpm-store" >> "$GITHUB_ENV"
- name: Resolve dependency cache key
id: dependency-cache-key
if: inputs.dependency-cache == 'true'
shell: bash
env:
FROZEN_LOCKFILE: ${{ inputs.frozen-lockfile }}
run: |
set -euo pipefail
deps_input_fingerprint="$(node "$GITHUB_ACTION_PATH/dependency-fingerprint.mjs" \
--workspace "$GITHUB_WORKSPACE" --frozen-lockfile "$FROZEN_LOCKFILE")"
cache_key="${GITHUB_REPOSITORY:?}-node-deps-v2-os-${RUNNER_OS:?}-arch-${RUNNER_ARCH:?}-node-$(node --version)-${deps_input_fingerprint:?}"
echo "key=$cache_key" >> "$GITHUB_OUTPUT"
- name: Prepare dependency cache restore
if: inputs.dependency-cache == 'true'
shell: bash
run: |
rm -rf "$GITHUB_WORKSPACE/node_modules" "$GITHUB_WORKSPACE/.cache/openclaw-pnpm-store"
find \
"$GITHUB_WORKSPACE/ui" \
"$GITHUB_WORKSPACE/packages" \
"$GITHUB_WORKSPACE/extensions" \
"$GITHUB_WORKSPACE/examples" \
-mindepth 1 -maxdepth 2 \( -type d -o -type l \) -name node_modules \
-exec rm -rf -- {} +
- name: Restore exact dependency cache
id: dependency-cache
if: inputs.dependency-cache == 'true'
continue-on-error: true
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
with:
path: |
node_modules
ui/node_modules
packages/*/node_modules
examples/*/node_modules
.cache/openclaw-pnpm-store
key: ${{ steps.dependency-cache-key.outputs.key }}
- name: Prepare dependency cache miss fallback
if: inputs.dependency-cache == 'true' && steps.dependency-cache.outputs.cache-hit != 'true'
shell: bash
run: |
# actions/cache treats service, download, and extraction failures as
# misses. Clear any partial extraction before restoring the pnpm store.
rm -rf "$GITHUB_WORKSPACE/node_modules" "$GITHUB_WORKSPACE/.cache/openclaw-pnpm-store"
find \
"$GITHUB_WORKSPACE/ui" \
"$GITHUB_WORKSPACE/packages" \
"$GITHUB_WORKSPACE/extensions" \
"$GITHUB_WORKSPACE/examples" \
-mindepth 1 -maxdepth 2 \( -type d -o -type l \) -name node_modules \
-exec rm -rf -- {} +
- name: Setup pnpm
id: setup-pnpm
uses: ./.github/actions/setup-pnpm-store-cache
with:
node-version: ${{ inputs.node-version }}
use-actions-cache: ${{ inputs.use-actions-cache }}
# On an exact dependency-cache hit, the same archive already restored
# the complete store. Every miss can seed it from the coarser cache,
# including legacy Blacksmith callers that disabled that old fallback.
use-actions-cache: ${{ ((inputs.dependency-cache == 'true' && steps.dependency-cache.outputs.cache-hit != 'true') || (inputs.dependency-cache != 'true' && inputs.use-actions-cache == 'true')) && 'true' || 'false' }}
- name: Setup TruffleHog
if: inputs.install-trufflehog == 'true'
shell: bash
run: bash scripts/install-trufflehog.sh
- name: Validate sticky pnpm layout
if: inputs.sticky-disk == 'true'
shell: bash
run: |
set -euo pipefail
for config_name in modules-dir virtual-store-dir; do
config_value="$(pnpm config get "$config_name")"
case "$config_value" in
""|undefined|null) ;;
*)
echo "::error::$config_name must be unset when sticky-disk is enabled; sticky mode requires pnpm's stock node_modules layout"
exit 2
;;
esac
done
- name: Mount dependency sticky disk
if: inputs.sticky-disk == 'true'
uses: useblacksmith/stickydisk@6d373c96a74cbde0c99fedc5ea5d3a7ba66ba494 # main (post-v1.4.0 hot-attach fix)
with:
# One stable disk per Node line. v7 starts a fresh lineage for the
# preflight-serialized writer after Blacksmith acknowledged repeated v6
# commits but kept restoring its original snapshot. The v2 per-PR/per-manifest-hash keys
# saturated Blacksmith's installation-wide sticky-disk budget. Install
# inputs, runner platform, and the exact Node patch live in the runtime
# marker below, so changes refresh this disk in place.
key: ${{ github.repository }}-node-deps-bind-v7-${{ inputs.node-version }}
path: /var/tmp/openclaw-node-deps
# Single semantic writer: only the designated trusted-push job may
# commit, so pull_request clones stay read-only. Like every sticky
# disk here, this gate binds cooperating code, not hostile code: the
# enforced trust boundary is the fork/dispatch runner gate in ci.yml,
# and same-repo PR authors already hold repository write access.
# Warm validated snapshots stay read-only so asynchronous publication
# does not perpetually chase no-op commits. The canonical writer records
# the action's allocation baseline below; after any real capture and
# store pruning, preflight forces a verified delta before this action's
# post phase. The action also skips commit after failed/cancelled steps.
commit: ${{ inputs.save-sticky-disk == 'true' && github.event_name != 'pull_request' && 'on-change' || 'false' }}
- name: Record sticky disk allocation baseline
if: inputs.sticky-disk == 'true' && inputs.save-sticky-disk == 'true' && github.event_name != 'pull_request'
shell: bash
run: |
set -euo pipefail
sticky_root=/var/tmp/openclaw-node-deps
initial_usage_bytes="$(df -B1 --output=used "$sticky_root" | tail -n1 | tr -d '[:space:]')"
if [[ ! "$initial_usage_bytes" =~ ^[0-9]+$ ]] || [[ "$initial_usage_bytes" -le 0 ]]; then
echo "::error::Could not record sticky disk allocation baseline"
exit 1
fi
rebuild_signal="${RUNNER_TEMP:?}/openclaw-sticky-deps-rebuilt"
rm -f "$rebuild_signal"
echo "OPENCLAW_STICKY_INITIAL_USAGE_BYTES=$initial_usage_bytes" >> "$GITHUB_ENV"
echo "OPENCLAW_STICKY_REBUILD_SIGNAL=$rebuild_signal" >> "$GITHUB_ENV"
- name: Restore and save Vitest transform cache
if: inputs.vitest-fs-cache == 'true' && inputs.save-vitest-fs-cache == 'true' && runner.os != 'Windows'
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
@@ -256,48 +259,6 @@ runs:
echo "NODE_COMPILE_CACHE_PORTABLE=1" >> "$GITHUB_ENV"
echo "OPENCLAW_NODE_COMPILE_CACHE_WRITER=$CACHE_WRITER" >> "$GITHUB_ENV"
# Post actions run last-in-first-out. Register after stickydisk so this bind
# is gone before stickydisk flushes, unmounts, and snapshots its filesystem.
- name: Register sticky bind cleanup
if: inputs.sticky-disk == 'true'
uses: ./.github/actions/register-bind-mount-cleanup
with:
path: ${{ github.workspace }}/node_modules
- name: Bind sticky node_modules into workspace
if: inputs.sticky-disk == 'true'
shell: bash
env:
FROZEN_LOCKFILE: ${{ inputs.frozen-lockfile }}
run: |
set -euo pipefail
sticky_root=/var/tmp/openclaw-node-deps
sticky_modules="$sticky_root/node_modules"
sticky_store="$sticky_root/store"
workspace_modules="$GITHUB_WORKSPACE/node_modules"
# Compute before mounting node_modules. The helper hashes tracked
# manifests canonically. Audited lifecycle hooks retain only install
# scripts; unaudited hook drift fails closed instead of risking stale output.
deps_input_fingerprint="$(node "$GITHUB_ACTION_PATH/dependency-fingerprint.mjs" \
--workspace "$GITHUB_WORKSPACE" --frozen-lockfile "$FROZEN_LOCKFILE")"
mkdir -p "$sticky_modules" "$sticky_store" "$workspace_modules"
sudo mount --bind "$sticky_modules" "$workspace_modules"
mountpoint -q "$workspace_modules"
if [ "$(stat -c %d "$sticky_store")" != "$(stat -c %d "$workspace_modules")" ]; then
echo "::error::pnpm store and workspace node_modules must share a filesystem"
exit 1
fi
findmnt --target "$workspace_modules"
deps_fingerprint="os-${RUNNER_OS:?}-arch-${RUNNER_ARCH:?}-node-$(node --version)-${deps_input_fingerprint:?}"
# zizmor: ignore[github-env] static trusted path defined in this composite action.
echo "PNPM_CONFIG_STORE_DIR=$sticky_store" >> "$GITHUB_ENV"
echo "OPENCLAW_STICKY_DEPS_FINGERPRINT=$deps_fingerprint" >> "$GITHUB_ENV"
# pnpm exec may reconcile a restored workspace before nested builds.
# Sticky jobs already have every build tool, so invoke their Node entrypoints directly.
echo "OPENCLAW_BUILD_ALL_NO_PNPM=1" >> "$GITHUB_ENV"
- name: Setup Bun
if: inputs.install-bun == 'true'
shell: bash
@@ -329,10 +290,9 @@ runs:
shell: bash
env:
CI: "true"
DEPENDENCY_CACHE: ${{ inputs.dependency-cache }}
DEPENDENCY_CACHE_HIT: ${{ steps.dependency-cache.outputs.cache-hit }}
FROZEN_LOCKFILE: ${{ inputs.frozen-lockfile }}
STICKY_DISK: ${{ inputs.sticky-disk }}
STICKY_ROOT: /var/tmp/openclaw-node-deps
STICKY_WRITER: ${{ inputs.save-sticky-disk == 'true' && github.event_name != 'pull_request' && 'true' || 'false' }}
run: |
set -euo pipefail
export PATH="$NODE_BIN:$PATH"
@@ -348,45 +308,19 @@ runs:
;;
esac
sticky_marker="$STICKY_ROOT/.openclaw-deps-fingerprint"
sticky_fingerprint=""
sticky_fingerprint_matches="false"
sticky_snapshot_matches="false"
if [ "$STICKY_DISK" = "true" ] && [ -f "$sticky_marker" ]; then
sticky_fingerprint="$(<"$sticky_marker")"
fi
if [ "$STICKY_DISK" = "true" ] && [ -n "$sticky_fingerprint" ] &&
[ "$sticky_fingerprint" = "${OPENCLAW_STICKY_DEPS_FINGERPRINT:?}" ]; then
sticky_fingerprint_matches="true"
if bash "$GITHUB_ACTION_PATH/sticky-importers.sh" restore "$STICKY_ROOT" "$GITHUB_WORKSPACE"; then
sticky_snapshot_matches="true"
else
echo "::warning::Sticky dependency fingerprint matches, but restored importer contents are incomplete; reinstalling"
fi
fi
if [ "$STICKY_DISK" = "true" ] && [ "$STICKY_WRITER" != "true" ] &&
[ "$sticky_snapshot_matches" != "true" ]; then
# Read-only PR clones cannot refresh a stale snapshot. Installing into
# that clone can saturate its ext4 device until short jobs time out.
# Detach only the workspace bind; the action still discards its clone.
sudo umount "$GITHUB_WORKSPACE/node_modules"
rm -rf "$GITHUB_WORKSPACE/node_modules"
mkdir -p "$GITHUB_WORKSPACE/node_modules"
ephemeral_store="${RUNNER_TEMP:?}/openclaw-pnpm-store"
mkdir -p "$ephemeral_store"
export PNPM_CONFIG_STORE_DIR="$ephemeral_store"
echo "PNPM_CONFIG_STORE_DIR=$ephemeral_store" >> "$GITHUB_ENV"
echo "Sticky dependency snapshot is unusable; using runner-local storage for this read-only run"
fi
install_args=(
install
--prefer-offline
--ignore-scripts=false
--config.engine-strict=false
--config.enable-pre-post-scripts=true
--config.side-effects-cache=true
)
if [ "$DEPENDENCY_CACHE" = "true" ]; then
# Both trees live below the workspace. Prefer real hard links so the
# single cache archive can preserve store/package identity; pnpm
# safely falls back to copies for files it cannot hard-link.
install_args+=(--package-import-method=hardlink)
fi
if [ -n "$LOCKFILE_FLAG" ]; then
install_args+=("$LOCKFILE_FLAG")
fi
@@ -403,94 +337,79 @@ runs:
append_pnpm_option_arg PNPM_CONFIG_NETWORK_CONCURRENCY network-concurrency
append_pnpm_option_arg PNPM_CONFIG_STORE_DIR store-dir
append_pnpm_option_arg PNPM_CONFIG_VIRTUAL_STORE_DIR virtual-store-dir
sticky_writer_rebuild="false"
if [ "$STICKY_DISK" = "true" ] && [ "$STICKY_WRITER" = "true" ] &&
[ "$sticky_snapshot_matches" != "true" ]; then
# Pnpm can trust stale hidden install metadata even with --force. Clear only
# the writer-owned modules tree; the warmed store remains on the sticky disk.
find "$GITHUB_WORKSPACE/node_modules" -mindepth 1 -maxdepth 1 -exec rm -rf -- {} +
install_args+=(--force)
sticky_writer_rebuild="true"
fi
run_pnpm_install() {
if [ "$sticky_writer_rebuild" = "true" ]; then
# A full relink exceeds the ordinary retry cap; restarting discards
# several minutes of progress even when the warmed store is healthy.
timeout --signal=TERM --kill-after=15s 15m \
pnpm "${install_args[@]}" --config.fetch-retries=0
elif [ "$STICKY_DISK" = "true" ]; then
# Pnpm can keep retrying optional platform tarballs after the
# required tree is linked. Retry the whole frozen transaction from
# its warmed store instead of letting minute backoffs outlive this cap.
timeout --signal=TERM --kill-after=15s 4m \
pnpm "${install_args[@]}" --config.fetch-retries=0
else
pnpm "${install_args[@]}"
fi
local fetch_mode="$1"
pnpm "${install_args[@]}" "$fetch_mode"
}
clear_dependency_modules() {
rm -rf "$GITHUB_WORKSPACE/node_modules"
find \
"$GITHUB_WORKSPACE/ui" \
"$GITHUB_WORKSPACE/packages" \
"$GITHUB_WORKSPACE/extensions" \
"$GITHUB_WORKSPACE/examples" \
-mindepth 1 -maxdepth 2 \( -type d -o -type l \) -name node_modules \
-exec rm -rf -- {} +
}
if [ -n "${PNPM_CONFIG_MODULES_DIR:-}" ]; then
mkdir -p "$PNPM_CONFIG_MODULES_DIR"
ln -sfn . "$PNPM_CONFIG_MODULES_DIR/node_modules"
export NODE_PATH="$PNPM_CONFIG_MODULES_DIR${NODE_PATH:+:$NODE_PATH}"
fi
if [ "$sticky_snapshot_matches" = "true" ]; then
echo "Sticky dependency snapshot matches the install fingerprint and importer contents; skipping pnpm install"
install_status=0
if [ "$DEPENDENCY_CACHE_HIT" = "true" ]; then
run_pnpm_install --offline || install_status="$?"
else
if [ "$sticky_fingerprint_matches" = "true" ]; then
echo "Sticky dependency snapshot importer contents are incomplete; reinstalling"
elif [ "$STICKY_DISK" = "true" ] && [ -n "$sticky_fingerprint" ]; then
echo "Sticky dependency snapshot is stale (disk: $sticky_fingerprint, want: $OPENCLAW_STICKY_DEPS_FINGERPRINT); reinstalling"
fi
# A stale marker must not survive a mid-install failure: the commit
# heuristics skip failed steps, but a wrong marker plus a partial
# tree would poison every consumer if one ever slipped through.
if [ "$STICKY_DISK" = "true" ] && [ "$STICKY_WRITER" = "true" ]; then
rm -f "$sticky_marker"
fi
install_attempts=2
if [ "$sticky_writer_rebuild" = "true" ]; then
install_attempts=1
elif [ "$STICKY_DISK" = "true" ]; then
install_attempts=3
fi
install_status=1
for (( attempt = 1; attempt <= install_attempts; attempt += 1 )); do
if run_pnpm_install; then
install_status=0
break
else
install_status="$?"
fi
if [ "$attempt" -lt "$install_attempts" ]; then
echo "::warning::pnpm install attempt ${attempt}/${install_attempts} failed or timed out; retrying"
fi
done
if [ "$install_status" -ne 0 ]; then
echo "::error::pnpm install failed after ${install_attempts} attempts"
exit "$install_status"
fi
if [ -n "${PNPM_CONFIG_MODULES_DIR:-}" ]; then
rm -rf node_modules
ln -sfn "$PNPM_CONFIG_MODULES_DIR" node_modules
ln -sfn . "$PNPM_CONFIG_MODULES_DIR/node_modules"
fi
# Only the designated trusted writer captures importer archives and
# publishes the fingerprint; read-only clones are discarded at job
# end, so capturing there would only burn shard wall clock.
if [ "$STICKY_DISK" = "true" ] && [ "$STICKY_WRITER" = "true" ]; then
bash "$GITHUB_ACTION_PATH/sticky-importers.sh" capture "$STICKY_ROOT" "$GITHUB_WORKSPACE" "$OPENCLAW_STICKY_DEPS_FINGERPRINT" "${OPENCLAW_STICKY_REBUILD_SIGNAL:?}"
fi
run_pnpm_install --prefer-offline || install_status="$?"
fi
if [ "$install_status" -ne 0 ] && [ "$DEPENDENCY_CACHE_HIT" = "true" ]; then
echo "::warning::Cached dependency tree failed pnpm reconciliation; relinking it from the restored store"
clear_dependency_modules
install_status=0
run_pnpm_install --offline || install_status="$?"
fi
if [ "$install_status" -ne 0 ] && [ "$DEPENDENCY_CACHE_HIT" = "true" ]; then
echo "::warning::Restored dependency store failed pnpm reconciliation; retrying from an empty store"
clear_dependency_modules
rm -rf "${PNPM_CONFIG_STORE_DIR:?}"
install_status=0
run_pnpm_install --prefer-offline || install_status="$?"
fi
if [ "$install_status" -ne 0 ]; then
echo "::error::pnpm install failed"
exit "$install_status"
fi
if [ -n "${PNPM_CONFIG_MODULES_DIR:-}" ]; then
rm -rf node_modules
ln -sfn "$PNPM_CONFIG_MODULES_DIR" node_modules
ln -sfn . "$PNPM_CONFIG_MODULES_DIR/node_modules"
fi
if [ "$STICKY_DISK" = "true" ]; then
# This step already establishes a content-validated snapshot or finishes
# a frozen-lockfile install. pnpm 11's redundant pre-run check treats
# our intentionally pruned plugin-local node_modules as stale and can
# launch concurrent implicit installs when later CI steps fan out.
if [ "$DEPENDENCY_CACHE" = "true" ]; then
# The exact archive includes importer links, and frozen offline
# reconciliation validates them without reaching the registry. Later
# build wrappers can use installed Node entrypoints directly.
echo "OPENCLAW_BUILD_ALL_NO_PNPM=1" >> "$GITHUB_ENV"
# Postinstall intentionally prunes plugin-local node_modules. Pnpm's
# redundant pre-run check treats that as stale and can launch unsafe
# concurrent implicit installs after CI fans out.
# zizmor: ignore[github-env] static pnpm policy owned by this action.
echo "pnpm_config_verify_deps_before_run=false" >> "$GITHUB_ENV"
fi
- name: Save exact dependency cache
if: inputs.install-deps == 'true' && inputs.dependency-cache == 'true' && inputs.save-dependency-cache == 'true' && steps.dependency-cache.outputs.cache-hit != 'true' && steps.dependency-cache.outcome != 'failure'
continue-on-error: true
uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
with:
path: |
node_modules
ui/node_modules
packages/*/node_modules
examples/*/node_modules
.cache/openclaw-pnpm-store
key: ${{ steps.dependency-cache-key.outputs.key }}
- name: Restore and save build-all cache
if: inputs.build-all-cache-scope != ''
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
@@ -503,7 +422,7 @@ runs:
${{ github.repository }}-build-all-v1-${{ inputs.build-all-cache-scope }}-${{ runner.os }}-${{ runner.arch }}-node-${{ inputs.node-version }}-
- name: Save pnpm store cache
if: ${{ inputs.install-deps == 'true' && inputs.use-actions-cache == 'true' && inputs.save-actions-cache == 'true' && runner.os != 'Windows' && steps.setup-pnpm.outputs.store-cache-hit != 'true' }}
if: ${{ inputs.install-deps == 'true' && inputs.use-actions-cache == 'true' && (inputs.dependency-cache != 'true' || steps.dependency-cache.outputs.cache-hit != 'true') && inputs.save-actions-cache == 'true' && runner.os != 'Windows' && steps.setup-pnpm.outputs.store-cache-hit != 'true' }}
uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
with:
path: ${{ steps.setup-pnpm.outputs.store-path }}
@@ -39,8 +39,6 @@ const INSTALL_INPUT_FILES = [
".pnpmfile.cjs",
"pnpmfile.cjs",
".github/actions/setup-node-env/dependency-fingerprint.mjs",
".github/actions/setup-node-env/sticky-importers.sh",
".github/actions/setup-node-env/verify-importers.mjs",
"scripts/postinstall-bundled-plugins.mjs",
"scripts/lib/package-dist-imports.mjs",
"scripts/preinstall-package-manager-warning.mjs",
@@ -1,164 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
mode="${1:?mode is required}"
sticky_root="${2:?sticky root is required}"
workspace="${3:-}"
archive="$sticky_root/importer-node-modules.tar"
archive_checksum="$sticky_root/.openclaw-importer-archive.sha256"
importer_manifest="$sticky_root/importer-node-modules.manifest"
marker="$sticky_root/.openclaw-deps-fingerprint"
force_commit_sentinel="$sticky_root/.openclaw-force-commit"
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
archive_sha256() {
if command -v sha256sum >/dev/null 2>&1; then
sha256sum -- "$1" | awk '{print $1}'
else
shasum -a 256 "$1" | awk '{print $1}'
fi
}
clear_importers() {
(
cd "$workspace"
find . \( -type d -o -type l \) -name node_modules -prune \
! -path ./node_modules -exec rm -rf -- {} +
)
}
verify_importers() {
node "$script_dir/verify-importers.mjs" "$workspace" "$1"
}
case "$mode" in
capture)
workspace="${workspace:?workspace is required}"
fingerprint="${4:?fingerprint is required}"
rebuild_signal="${5:?rebuild signal is required}"
mkdir -p "$sticky_root"
list_file="$(mktemp)"
temp_archive="$archive.tmp.$$"
temp_checksum="$archive_checksum.tmp.$$"
temp_manifest="$importer_manifest.tmp.$$"
temp_marker="$marker.tmp.$$"
cleanup() {
rm -f "$list_file" "$temp_archive" "$temp_checksum" "$temp_manifest" "$temp_marker"
}
trap cleanup EXIT
# Do not publish a fingerprint for an install whose importer resolution is
# already falling through to a wrong hoisted version.
rm -f "$marker" "$archive_checksum" "$importer_manifest"
(
cd "$workspace"
find . \( -type d -o -type l \) -name node_modules -prune \
! -path ./node_modules -print0 >"$list_file"
tar --create --file "$temp_archive" --null --files-from "$list_file"
)
tr '\0' '\n' <"$list_file" >"$temp_manifest"
# Record the exact importer set and check its live resolution before a
# writer can publish the marker.
verify_importers "$temp_manifest"
{
archive_sha256 "$temp_archive"
archive_sha256 "$temp_manifest"
} >"$temp_checksum"
mv "$temp_archive" "$archive"
mv "$temp_manifest" "$importer_manifest"
mv "$temp_checksum" "$archive_checksum"
# The marker lands last. Consumers also verify the archive bytes and every
# registry-backed importer resolution before trusting this snapshot.
printf '%s\n' "$fingerprint" >"$temp_marker"
mv "$temp_marker" "$marker"
: >"$rebuild_signal"
;;
restore)
workspace="${workspace:?workspace is required}"
if [[ ! -f "$archive" || ! -f "$archive_checksum" || ! -f "$importer_manifest" ]]; then
echo "sticky importer archive, manifest, or checksum is missing under $sticky_root" >&2
exit 1
fi
expected_archive_checksum="$(sed -n '1p' "$archive_checksum" | tr -d '[:space:]')"
expected_manifest_checksum="$(sed -n '2p' "$archive_checksum" | tr -d '[:space:]')"
actual_archive_checksum="$(archive_sha256 "$archive")"
actual_manifest_checksum="$(archive_sha256 "$importer_manifest")"
if [[ ! "$expected_archive_checksum" =~ ^[a-f0-9]{64}$ ]] || \
[[ ! "$expected_manifest_checksum" =~ ^[a-f0-9]{64}$ ]] || \
[[ "$actual_archive_checksum" != "$expected_archive_checksum" ]] || \
[[ "$actual_manifest_checksum" != "$expected_manifest_checksum" ]]; then
echo "sticky importer archive or manifest checksum mismatch" >&2
exit 1
fi
# A restored archive is authoritative for checkout-local importer links.
# Clear first so entries absent from the archive cannot survive from a
# reused workspace, and clear again when validation rejects the restore.
clear_importers
if ! tar --extract --file "$archive" --directory "$workspace"; then
clear_importers
exit 1
fi
if ! verify_importers "$importer_manifest"; then
clear_importers
exit 1
fi
;;
ensure-change)
initial_usage_bytes="${3:?initial usage bytes are required}"
if [[ ! "$initial_usage_bytes" =~ ^[0-9]+$ ]] || [[ "$initial_usage_bytes" -le 0 ]]; then
echo "invalid initial sticky disk usage: $initial_usage_bytes" >&2
exit 2
fi
current_usage_bytes() {
df -B1 --output=used "$sticky_root" | tail -n1 | tr -d '[:space:]'
}
allocation_delta() {
local current="$1"
if [[ "$current" -ge "$initial_usage_bytes" ]]; then
echo $((current - initial_usage_bytes))
else
echo $((initial_usage_bytes - current))
fi
}
# The pinned StickyDisk action commits only when the absolute whole-disk
# allocation delta exceeds 4096 bytes. Measure against the same baseline
# after store pruning, then leave a 64 KiB margin for its post phase.
target_delta_bytes=65536
max_sentinel_bytes=1048576
current="$(current_usage_bytes)"
if [[ ! "$current" =~ ^[0-9]+$ ]] || [[ "$current" -le 0 ]]; then
echo "could not read current sticky disk usage" >&2
exit 1
fi
delta="$(allocation_delta "$current")"
if [[ "$delta" -le "$target_delta_bytes" ]] &&
[[ -f "$force_commit_sentinel" ]] &&
[[ "$(stat -c %s "$force_commit_sentinel")" -ge "$max_sentinel_bytes" ]]; then
: >"$force_commit_sentinel"
sync
current="$(current_usage_bytes)"
delta="$(allocation_delta "$current")"
fi
for _ in 1 2 3; do
if [[ "$delta" -gt "$target_delta_bytes" ]]; then
echo "Sticky dependency rebuild changed allocation by ${delta} bytes"
exit 0
fi
bytes_needed=$((initial_usage_bytes + target_delta_bytes + 4096 - current))
blocks_needed=$(((bytes_needed + 4095) / 4096))
if [[ "$blocks_needed" -lt 1 ]]; then
blocks_needed=1
fi
dd if=/dev/zero bs=4096 count="$blocks_needed" status=none >>"$force_commit_sentinel"
sync
current="$(current_usage_bytes)"
delta="$(allocation_delta "$current")"
done
echo "could not force a detectable sticky disk allocation change (delta: ${delta} bytes)" >&2
exit 1
;;
*)
echo "unsupported sticky importer mode: $mode" >&2
exit 2
;;
esac
@@ -1,285 +0,0 @@
#!/usr/bin/env node
import { readFileSync, realpathSync, statSync } from "node:fs";
import path from "node:path";
import process from "node:process";
import YAML from "yaml";
const DEPENDENCY_FIELDS = [
{ name: "dependencies", optional: false },
{ name: "devDependencies", optional: false },
{ name: "optionalDependencies", optional: true },
];
const MAX_REPORTED_MISMATCHES = 12;
function readImporters(workspace) {
const lockfilePath = path.join(workspace, "pnpm-lock.yaml");
const lockfile = YAML.parse(readFileSync(lockfilePath, "utf8"));
if (!lockfile?.importers || typeof lockfile.importers !== "object") {
throw new Error(`${lockfilePath} does not contain importers`);
}
return lockfile.importers;
}
function registryResolution(dependencyName, resolution) {
if (typeof resolution !== "string") {
return undefined;
}
const locator = resolution.split("(", 1)[0];
if (locator.includes(":")) {
return undefined;
}
const versionSeparator = locator.startsWith("@")
? locator.indexOf("@", locator.indexOf("/") + 1)
: locator.indexOf("@");
if (versionSeparator > 0) {
return {
packageName: locator.slice(0, versionSeparator),
snapshotKey: resolution,
version: locator.slice(versionSeparator + 1),
};
}
return {
packageName: dependencyName,
snapshotKey: `${dependencyName}@${resolution}`,
version: locator,
};
}
function packageNameParts(packageName) {
const parts = packageName.split("/");
const valid = packageName.startsWith("@")
? parts.length === 2 && parts.every(Boolean)
: parts.length === 1 && parts[0] !== "";
if (!valid || parts.some((part) => part === "." || part === "..")) {
throw new Error(`invalid dependency name from pnpm lockfile: ${packageName}`);
}
return parts;
}
function isWithinWorkspace(workspace, candidate) {
const relative = path.relative(workspace, candidate);
return relative === "" || (!relative.startsWith(`..${path.sep}`) && relative !== "..");
}
function findInstalledManifest({ dependencyName, projectPath, workspace }) {
const dependencyParts = packageNameParts(dependencyName);
let current = projectPath;
for (;;) {
const candidate = path.join(current, "node_modules", ...dependencyParts, "package.json");
try {
if (statSync(candidate).isFile()) {
return candidate;
}
} catch (error) {
if (error.code !== "ENOENT" && error.code !== "ENOTDIR") {
throw error;
}
}
if (current === workspace) {
return undefined;
}
const parent = path.dirname(current);
if (parent === current || !isWithinWorkspace(workspace, parent)) {
return undefined;
}
current = parent;
}
}
function parseManifest(manifestPath) {
try {
return JSON.parse(readFileSync(manifestPath, "utf8"));
} catch (error) {
throw new Error(`could not read ${manifestPath}: ${error.message}`, { cause: error });
}
}
function relativeDisplayPath(workspace, absolutePath) {
const relative = path.relative(workspace, absolutePath);
return relative || ".";
}
function normalizeLocation(location) {
return location.split(path.sep).join("/");
}
function readHoistedResolutions(workspace) {
const modulesMetadataPath = path.join(workspace, "node_modules", ".modules.yaml");
const metadata = YAML.parse(readFileSync(modulesMetadataPath, "utf8"));
if (!metadata?.hoistedLocations || typeof metadata.hoistedLocations !== "object") {
throw new Error(`${modulesMetadataPath} does not contain hoistedLocations`);
}
const byLocation = new Map();
const bySnapshotKey = new Map();
for (const [snapshotKey, locations] of Object.entries(metadata.hoistedLocations)) {
if (!Array.isArray(locations)) {
throw new Error(`invalid hoistedLocations entry for ${snapshotKey}`);
}
for (const location of locations) {
if (typeof location !== "string") {
throw new Error(`invalid hoisted location for ${snapshotKey}`);
}
const normalized = normalizeLocation(location);
const keys = byLocation.get(normalized) ?? new Set();
keys.add(snapshotKey);
byLocation.set(normalized, keys);
const snapshotLocations = bySnapshotKey.get(snapshotKey) ?? new Set();
snapshotLocations.add(normalized);
bySnapshotKey.set(snapshotKey, snapshotLocations);
}
}
return { byLocation, bySnapshotKey };
}
function readCapturedImporters(workspace, manifestPath) {
const importers = new Map();
for (const entry of readFileSync(manifestPath, "utf8").split("\n")) {
if (!entry) {
continue;
}
const modulesPath = path.resolve(workspace, entry);
if (
!isWithinWorkspace(workspace, modulesPath) ||
path.basename(modulesPath) !== "node_modules" ||
modulesPath === path.join(workspace, "node_modules")
) {
throw new Error(`invalid importer manifest entry: ${entry}`);
}
const projectPath = path.dirname(modulesPath);
const importerPath = relativeDisplayPath(workspace, projectPath);
importers.set(importerPath, { modulesPath });
}
return importers;
}
function isPrunedImporter(importerPath) {
// postinstall-bundled-plugins.mjs deliberately removes every plugin source
// node_modules tree; installed plugins own those dependencies separately.
return importerPath.startsWith("extensions/");
}
function verifyImporters(workspace, manifestPath) {
const importers = readImporters(workspace);
const hoistedResolutions = readHoistedResolutions(workspace);
const capturedImporters = readCapturedImporters(workspace, manifestPath);
const mismatches = [];
let checked = 0;
for (const [importerPath, { modulesPath }] of capturedImporters) {
if (!importers[importerPath] || typeof importers[importerPath] !== "object") {
throw new Error(`importer manifest entry is absent from pnpm-lock.yaml: ${importerPath}`);
}
try {
if (!statSync(modulesPath).isDirectory()) {
mismatches.push(`${importerPath}: captured node_modules path is not a directory`);
}
} catch (error) {
if (error.code !== "ENOENT" && error.code !== "ENOTDIR") {
throw error;
}
mismatches.push(`${importerPath}: captured node_modules directory is missing`);
}
}
// The lockfile, not the captured manifest, owns the validation universe. A
// missing importer must still be checked against the version Node falls back to.
for (const [importerPath, importer] of Object.entries(importers)) {
if (isPrunedImporter(importerPath)) {
continue;
}
const projectPath = path.resolve(workspace, importerPath);
if (!isWithinWorkspace(workspace, projectPath)) {
throw new Error(`pnpm lockfile contains an importer outside the workspace: ${importerPath}`);
}
for (const field of DEPENDENCY_FIELDS) {
const dependencies = importer[field.name] ?? {};
for (const [dependencyName, expected] of Object.entries(dependencies)) {
// Workspace, file, and git locators do not map directly to installed
// manifest versions; registry versions and aliases do.
const expectedResolution = registryResolution(dependencyName, expected?.version);
if (!expectedResolution) {
continue;
}
const resolvedManifestPath = findInstalledManifest({
dependencyName,
projectPath,
workspace,
});
const importerDisplay = relativeDisplayPath(workspace, projectPath);
if (!resolvedManifestPath) {
if (field.optional) {
continue;
}
mismatches.push(
`${importerDisplay}: ${dependencyName} ${expectedResolution.version} is not resolvable`,
);
continue;
}
checked += 1;
const installedLocation = normalizeLocation(
relativeDisplayPath(workspace, path.dirname(resolvedManifestPath)),
);
const expectedLocation = normalizeLocation(
path.join(
importerPath === "." ? "" : importerPath,
"node_modules",
...packageNameParts(dependencyName),
),
);
const exactImporterSlot = hoistedResolutions.bySnapshotKey
.get(expectedResolution.snapshotKey)
?.has(expectedLocation);
const installedSnapshotKeys = hoistedResolutions.byLocation.get(installedLocation);
// Hoisted pnpm installs may intentionally share a different peer-context
// variant from the root. Exact identity is required when pnpm metadata
// says this importer owns the lockfile snapshot in its local slot.
if (exactImporterSlot && !installedSnapshotKeys?.has(expectedResolution.snapshotKey)) {
const actualKeys = installedSnapshotKeys
? [...installedSnapshotKeys]
.toSorted((left, right) => (left < right ? -1 : left > right ? 1 : 0))
.join(", ")
: "<missing metadata>";
mismatches.push(
`${importerDisplay}: ${dependencyName} expected pnpm snapshot ${expectedResolution.snapshotKey}, resolved ${actualKeys} from ${installedLocation}`,
);
}
const actual = parseManifest(resolvedManifestPath);
if (
actual.name !== expectedResolution.packageName ||
actual.version !== expectedResolution.version
) {
const resolvedFrom = relativeDisplayPath(
workspace,
realpathSync(path.dirname(resolvedManifestPath)),
);
mismatches.push(
`${importerDisplay}: ${dependencyName} expected ${expectedResolution.packageName}@${expectedResolution.version}, resolved ${actual.name ?? "<missing>"}@${actual.version ?? "<missing>"} from ${resolvedFrom}`,
);
}
}
}
}
if (mismatches.length > 0) {
const visible = mismatches.slice(0, MAX_REPORTED_MISMATCHES);
const remainder = mismatches.length - visible.length;
const suffix = remainder > 0 ? `\n... and ${remainder} more` : "";
throw new Error(
`sticky importer dependency validation failed (${mismatches.length} mismatch${mismatches.length === 1 ? "" : "es"}):\n${visible.join("\n")}${suffix}`,
);
}
console.log(`Verified ${checked} registry-backed importer dependency resolutions`);
}
const workspace = path.resolve(process.argv[2] ?? process.cwd());
const manifestPath = path.resolve(process.argv[3] ?? "");
try {
if (!process.argv[3]) {
throw new Error("importer manifest path is required");
}
verifyImporters(workspace, manifestPath);
} catch (error) {
console.error(error instanceof Error ? error.message : String(error));
process.exitCode = 1;
}
+5
View File
@@ -24,6 +24,11 @@
"architecture": "amd64",
"region": "eu-west"
},
{
"key": "openclaw/openclaw-node-deps-bind-v7-24.x",
"architecture": "amd64",
"region": "eu-west"
},
{
"key": "openclaw/openclaw-vitest-fs-v2-protected-Linux-X64-node-24.x",
"architecture": "amd64",
+3 -4
View File
@@ -54,10 +54,9 @@ jobs:
with:
install-bun: "false"
install-trufflehog: "true"
# Real Testbox hydration reuses the protected dependency snapshot.
# Pull-request validation runs on GitHub-hosted runners instead.
sticky-disk: ${{ github.event_name == 'workflow_dispatch' && 'true' || 'false' }}
use-actions-cache: ${{ github.event_name == 'workflow_dispatch' && 'false' || 'true' }}
# Testbox hydration uses the ordinary pnpm store cache. Canonical CI
# owns the exact dependency archive and never delegates here.
use-actions-cache: "true"
- name: Ensure Testbox base commit
if: github.event_name == 'pull_request'
uses: ./.github/actions/ensure-base-commit
+56 -108
View File
@@ -869,61 +869,18 @@ jobs:
if: steps.manifest.outputs.run_protocol_event_coverage == 'true'
run: node scripts/check-protocol-event-coverage.mjs
# Canonical main cannot be cancelled by a newer push. Publish the sole
# dependency snapshot here before fanout. Blacksmith may expose a fresh
# commit only to a later run; readers retain the marker-checked fallback.
- name: Refresh sticky dependency snapshot
if: github.repository == 'openclaw/openclaw' && github.event_name == 'push' && github.ref == 'refs/heads/main' && steps.manifest.outputs.run_node == 'true'
# Publish one immutable semantic dependency archive before same-repo
# Blacksmith jobs fan out. Pull-request archives remain merge-ref scoped;
# main archives seed later pull requests through the default-branch scope.
- name: Publish exact dependency cache
if: github.repository == 'openclaw/openclaw' && steps.manifest.outputs.run_node == 'true' && ((github.event_name == 'push' && github.ref == 'refs/heads/main') || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository))
uses: ./.github/actions/setup-node-env
with:
dependency-cache: "true"
install-bun: "false"
save-sticky-disk: "true"
sticky-disk: "true"
use-actions-cache: "false"
- name: Maintain sticky dependency store budget
if: github.repository == 'openclaw/openclaw' && github.event_name == 'push' && github.ref == 'refs/heads/main' && steps.manifest.outputs.run_node == 'true'
shell: bash
env:
OPENCLAW_PNPM_STORE_MAX_KIB: "8388608"
run: |
set -euo pipefail
store_dir="${PNPM_CONFIG_STORE_DIR:?}"
before_kib="$(du -sk "$store_dir" | cut -f1)"
after_kib="$before_kib"
pruned=false
if [ "$before_kib" -gt "$OPENCLAW_PNPM_STORE_MAX_KIB" ]; then
echo "pnpm store is ${before_kib} KiB; pruning above ${OPENCLAW_PNPM_STORE_MAX_KIB} KiB ceiling"
PNPM_CONFIG_STORE_DIR="$store_dir" pnpm store prune
after_kib="$(du -sk "$store_dir" | cut -f1)"
pruned=true
else
echo "pnpm store is ${before_kib} KiB; below ${OPENCLAW_PNPM_STORE_MAX_KIB} KiB ceiling"
fi
{
echo "### Dependency store maintenance"
echo
echo "- Before: ${before_kib} KiB"
echo "- After: ${after_kib} KiB"
echo "- Pruned: ${pruned}"
} >> "$GITHUB_STEP_SUMMARY"
if [ "$after_kib" -gt "$OPENCLAW_PNPM_STORE_MAX_KIB" ]; then
echo "::warning::pnpm store remains above its 8 GiB maintenance ceiling after prune"
fi
# StickyDisk's pinned on-change mode compares whole-filesystem
# allocation to its mount-time baseline. Only a successful real
# dependency capture creates this runner-local signal. Force and
# verify the delta after pruning so a same-size rebuild commits while
# validated warm restores remain read-only.
if [ -f "${OPENCLAW_STICKY_REBUILD_SIGNAL:?}" ]; then
bash "$GITHUB_WORKSPACE/.github/actions/setup-node-env/sticky-importers.sh" \
ensure-change /var/tmp/openclaw-node-deps \
"${OPENCLAW_STICKY_INITIAL_USAGE_BYTES:?}"
fi
save-actions-cache: "true"
save-dependency-cache: "true"
use-actions-cache: "true"
# Run dependency-free security checks on a hosted runner in parallel with
# scope detection. No downstream job waits for Python/pre-commit setup.
@@ -1129,13 +1086,14 @@ jobs:
- name: Audit production dependencies
run: node scripts/pre-commit/pnpm-audit-prod.mjs --audit-level=high
# Warm the lockfile- and pnpm-pinned Actions cache for PR and manual runs.
# Canonical main publishes its sticky snapshot in preflight before fanout.
# Warm the lockfile- and pnpm-pinned Actions cache for fork PRs, manual runs,
# and docs-only same-repo PRs. Node-relevant canonical main and same-repo PRs
# already publish it through the exact dependency-cache writer in preflight.
pnpm-store-warmup:
permissions:
contents: read
needs: [preflight]
if: ${{ (needs.preflight.outputs.run_node == 'true' || needs.preflight.outputs.run_check_docs == 'true') && !(github.repository == 'openclaw/openclaw' && github.event_name == 'push' && github.ref == 'refs/heads/main') }}
if: ${{ (needs.preflight.outputs.run_node == 'true' || needs.preflight.outputs.run_check_docs == 'true') && !(github.repository == 'openclaw/openclaw' && github.event_name == 'push' && github.ref == 'refs/heads/main') && !(github.repository == 'openclaw/openclaw' && github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && needs.preflight.outputs.run_node == 'true') }}
runs-on: ${{ github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-24.04') }}
timeout-minutes: 20
steps:
@@ -1228,10 +1186,9 @@ jobs:
install-bun: "false"
node-compile-cache: "true"
node-compile-cache-scope: "build"
# Blacksmith same-repo runs clone dependencies from a sticky disk.
# Fork PRs must keep actions/cache: sticky snapshots are writable,
# repository-global state and must never be produced by fork code.
sticky-disk: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'true' || 'false' }}
# Same-repo Blacksmith runs restore the dependency cache published by
# preflight; hosted paths use the pnpm store cache instead.
dependency-cache: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'true' || 'false' }}
use-actions-cache: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'false' || 'true' }}
save-node-compile-cache: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' && 'true' || 'false' }}
@@ -1498,7 +1455,7 @@ jobs:
uses: ./.github/actions/setup-node-env
with:
install-bun: "false"
sticky-disk: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'true' || 'false' }}
dependency-cache: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'true' || 'false' }}
use-actions-cache: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'false' || 'true' }}
- name: Download exact-run built runtime
@@ -1534,10 +1491,9 @@ jobs:
uses: ./.github/actions/setup-node-env
with:
install-bun: "false"
# Blacksmith same-repo runs clone dependencies from a sticky disk.
# Fork PRs must keep actions/cache: sticky snapshots are writable,
# repository-global state and must never be produced by fork code.
sticky-disk: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'true' || 'false' }}
# Same-repo Blacksmith runs restore the dependency cache published by
# preflight; hosted paths use the pnpm store cache instead.
dependency-cache: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'true' || 'false' }}
use-actions-cache: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'false' || 'true' }}
- name: Verify native app i18n source
@@ -1577,10 +1533,9 @@ jobs:
with:
node-version: "24.x"
install-bun: "false"
# Blacksmith same-repo runs clone dependencies from a sticky disk.
# Fork PRs must keep actions/cache: sticky snapshots are writable,
# repository-global state and must never be produced by fork code.
sticky-disk: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'true' || 'false' }}
# Same-repo Blacksmith runs restore the dependency cache published by
# preflight; hosted paths use the pnpm store cache instead.
dependency-cache: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'true' || 'false' }}
use-actions-cache: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'false' || 'true' }}
- &install_playwright_chromium
@@ -1645,8 +1600,9 @@ jobs:
with:
node-version: "24.x"
install-bun: "false"
# Hosted paths use Actions cache and never touch repository-global sticky snapshots.
sticky-disk: ${{ (github.event_name == 'workflow_dispatch' || (github.event_name == 'pull_request' && github.run_attempt > 1)) && 'false' || (github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'true' || 'false') }}
# Hosted paths use the pnpm store cache; first-attempt same-repo
# Blacksmith runs restore the preflight-published dependency tree.
dependency-cache: ${{ (github.event_name == 'workflow_dispatch' || (github.event_name == 'pull_request' && github.run_attempt > 1)) && 'false' || (github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'true' || 'false') }}
use-actions-cache: ${{ (github.event_name == 'workflow_dispatch' || (github.event_name == 'pull_request' && github.run_attempt > 1)) && 'true' || (github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'false' || 'true') }}
- *install_playwright_chromium
@@ -1682,8 +1638,9 @@ jobs:
with:
node-version: "24.x"
install-bun: "false"
# Hosted paths use Actions cache and never touch repository-global sticky snapshots.
sticky-disk: ${{ (github.event_name == 'workflow_dispatch' || (github.event_name == 'pull_request' && github.run_attempt > 1)) && 'false' || (github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'true' || 'false') }}
# Hosted paths use the pnpm store cache; first-attempt same-repo
# Blacksmith runs restore the preflight-published dependency tree.
dependency-cache: ${{ (github.event_name == 'workflow_dispatch' || (github.event_name == 'pull_request' && github.run_attempt > 1)) && 'false' || (github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'true' || 'false') }}
use-actions-cache: ${{ (github.event_name == 'workflow_dispatch' || (github.event_name == 'pull_request' && github.run_attempt > 1)) && 'true' || (github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'false' || 'true') }}
- *install_playwright_chromium
@@ -1719,10 +1676,9 @@ jobs:
with:
node-version: "24.x"
install-bun: "false"
# Blacksmith same-repo runs clone dependencies from a sticky disk.
# Fork PRs must keep actions/cache: sticky snapshots are writable,
# repository-global state and must never be produced by fork code.
sticky-disk: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'true' || 'false' }}
# Same-repo Blacksmith runs restore the dependency cache published by
# preflight; hosted paths use the pnpm store cache instead.
dependency-cache: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'true' || 'false' }}
use-actions-cache: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'false' || 'true' }}
- name: Verify Control UI i18n source
@@ -1808,10 +1764,9 @@ jobs:
uses: ./.github/actions/setup-node-env
with:
install-bun: ${{ matrix.task == 'bun-launcher' && 'true' || 'false' }}
# Blacksmith same-repo runs clone dependencies from a sticky disk.
# Fork PRs must keep actions/cache: sticky snapshots are writable,
# repository-global state and must never be produced by fork code.
sticky-disk: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'true' || 'false' }}
# Same-repo Blacksmith runs restore the dependency cache published by
# preflight; hosted paths use the pnpm store cache instead.
dependency-cache: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'true' || 'false' }}
use-actions-cache: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'false' || 'true' }}
- name: Run ${{ matrix.task }} (${{ matrix.runtime }})
@@ -1914,10 +1869,9 @@ jobs:
uses: ./.github/actions/setup-node-env
with:
install-bun: "false"
# Blacksmith same-repo runs clone dependencies from a sticky disk.
# Fork PRs must keep actions/cache: sticky snapshots are writable,
# repository-global state and must never be produced by fork code.
sticky-disk: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'true' || 'false' }}
# Same-repo Blacksmith runs restore the dependency cache published by
# preflight; hosted paths use the pnpm store cache instead.
dependency-cache: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'true' || 'false' }}
use-actions-cache: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'false' || 'true' }}
- name: Set up Blacksmith Docker layer cache
@@ -2095,10 +2049,9 @@ jobs:
uses: ./.github/actions/setup-node-env
with:
install-bun: "false"
# Blacksmith same-repo runs clone dependencies from a sticky disk.
# Fork PRs must keep actions/cache: sticky snapshots are writable,
# repository-global state and must never be produced by fork code.
sticky-disk: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'true' || 'false' }}
# Same-repo Blacksmith runs restore the dependency cache published by
# preflight; hosted paths use the pnpm store cache instead.
dependency-cache: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'true' || 'false' }}
use-actions-cache: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'false' || 'true' }}
- name: Run plugin contract shard
@@ -2138,10 +2091,9 @@ jobs:
uses: ./.github/actions/setup-node-env
with:
install-bun: "false"
# Blacksmith same-repo runs clone dependencies from a sticky disk.
# Fork PRs must keep actions/cache: sticky snapshots are writable,
# repository-global state and must never be produced by fork code.
sticky-disk: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'true' || 'false' }}
# Same-repo Blacksmith runs restore the dependency cache published by
# preflight; hosted paths use the pnpm store cache instead.
dependency-cache: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'true' || 'false' }}
use-actions-cache: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'false' || 'true' }}
- name: Run channel contract shard
@@ -2215,10 +2167,9 @@ jobs:
with:
node-version: "${{ matrix.node_version || '24.x' }}"
install-bun: "false"
# Blacksmith same-repo runs clone dependencies from a sticky disk.
# Fork PRs must keep actions/cache: sticky snapshots are writable,
# repository-global state and must never be produced by fork code.
sticky-disk: ${{ (matrix.node_version == null || matrix.node_version == '24.x') && github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'true' || 'false' }}
# Only the preflight writer saves an archive. Same-repo Blacksmith
# shards restore it; hosted and non-Node-24 paths use the pnpm store.
dependency-cache: ${{ (matrix.node_version == null || matrix.node_version == '24.x') && github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'true' || 'false' }}
use-actions-cache: ${{ (matrix.node_version == null || matrix.node_version == '24.x') && github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'false' || 'true' }}
vitest-fs-cache: "true"
node-compile-cache: "true"
@@ -2352,10 +2303,9 @@ jobs:
uses: ./.github/actions/setup-node-env
with:
install-bun: "false"
# Blacksmith same-repo runs clone dependencies from a sticky disk.
# Fork PRs must keep actions/cache: sticky snapshots are writable,
# repository-global state and must never be produced by fork code.
sticky-disk: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'true' || 'false' }}
# Same-repo Blacksmith runs restore the dependency cache published by
# preflight; hosted paths use the pnpm store cache instead.
dependency-cache: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'true' || 'false' }}
use-actions-cache: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'false' || 'true' }}
# check-lint's shard runner rebuilds the same plugin-sdk boundary
@@ -2645,10 +2595,9 @@ jobs:
uses: ./.github/actions/setup-node-env
with:
install-bun: "false"
# Blacksmith same-repo runs clone dependencies from a sticky disk.
# Fork PRs must keep actions/cache: sticky snapshots are writable,
# repository-global state and must never be produced by fork code.
sticky-disk: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'true' || 'false' }}
# Same-repo Blacksmith runs restore the dependency cache published by
# preflight; hosted paths use the pnpm store cache instead.
dependency-cache: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'true' || 'false' }}
use-actions-cache: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'false' || 'true' }}
# Same-repo runs carry boundary artifacts on a Blacksmith sticky disk:
@@ -2911,10 +2860,9 @@ jobs:
uses: ./.github/actions/setup-node-env
with:
install-bun: "false"
# Blacksmith same-repo runs clone dependencies from a sticky disk.
# Fork PRs must keep actions/cache: sticky snapshots are writable,
# repository-global state and must never be produced by fork code.
sticky-disk: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'true' || 'false' }}
# Same-repo Blacksmith runs restore the dependency cache published by
# preflight; hosted paths use the pnpm store cache instead.
dependency-cache: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'true' || 'false' }}
use-actions-cache: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'false' || 'true' }}
- name: Check formatting
+4 -1
View File
@@ -9,6 +9,7 @@ on:
- ".github/workflows/plugin-npm-release.yml"
- "extensions/**"
- "package.json"
- "packages/normalization-core/**"
- "scripts/generate-npm-package-lock.mjs"
- "scripts/generate-npm-package-lock.mts"
- "scripts/lib/npm-publish-plan.mjs"
@@ -340,7 +341,9 @@ jobs:
ref: ${{ github.workflow_sha }}
path: .release-tooling
fetch-depth: 1
sparse-checkout: scripts
sparse-checkout: |
packages/normalization-core
scripts
- name: Setup Node environment
uses: ./.github/actions/setup-node-env
-1
View File
@@ -33,7 +33,6 @@ jobs:
save-actions-cache: "true"
save-node-compile-cache: "true"
save-vitest-fs-cache: "true"
sticky-disk: "false"
use-actions-cache: "true"
vitest-fs-cache: "true"
File diff suppressed because it is too large Load Diff
@@ -578,6 +578,9 @@ enum class GatewayMethod(
DesktopLaunch("desktop.launch"),
DeviceScopesRequestUpgrade("device.scopes.requestUpgrade"),
DeviceScopesWaitUpgrade("device.scopes.waitUpgrade"),
PortalList("portal.list"),
PortalOpen("portal.open"),
PortalClose("portal.close"),
}
enum class GatewayEvent(
@@ -629,4 +632,5 @@ enum class GatewayEvent(
TerminalData("terminal.data"),
TerminalExit("terminal.exit"),
UpdateAvailable("update.available"),
PortalChanged("portal.changed"),
}
@@ -87,6 +87,7 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
@@ -121,9 +122,11 @@ import androidx.compose.material.icons.filled.Menu
import androidx.compose.material.icons.filled.Mic
import androidx.compose.material.icons.filled.MoreHoriz
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material.icons.filled.Photo
import androidx.compose.material.icons.filled.Refresh
import androidx.compose.material.icons.filled.Star
import androidx.compose.material.icons.filled.StarBorder
import androidx.compose.material.icons.filled.Stop
import androidx.compose.material.icons.filled.Videocam
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
@@ -153,11 +156,14 @@ import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.input.key.onPreInterceptKeyBeforeSoftKeyboard
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.clearAndSetSemantics
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.font.FontWeight
@@ -216,16 +222,19 @@ internal fun chatReaderListBottomInset(showJumpToLatest: Boolean): Dp =
internal enum class ChatComposerTrailingAction {
StartTalk,
StopTalk,
Stop,
Send,
}
/** Talk must remain stoppable even when the active session adds text to the draft. */
internal fun resolveChatComposerTrailingAction(
talkActive: Boolean,
runActive: Boolean,
sendEnabled: Boolean,
): ChatComposerTrailingAction =
when {
talkActive -> ChatComposerTrailingAction.StopTalk
runActive -> ChatComposerTrailingAction.Stop
sendEnabled -> ChatComposerTrailingAction.Send
else -> ChatComposerTrailingAction.StartTalk
}
@@ -2280,26 +2289,6 @@ private fun ChatComposer(
AttachmentStrip(attachments = attachments, onRemoveAttachment = onRemoveAttachment)
}
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(6.dp),
) {
ChatModelChip(
label = selectedModelLabel,
enabled = modelPickerEnabled,
onClick = onOpenModelPicker,
modifier = Modifier.weight(1f),
)
ChatContextMeter(
thinkingLevel = thinkingLevel,
thinkingSupported = thinkingSupported,
expanded = thinkingSelectorExpanded,
contextUsage = contextUsage,
onClick = { thinkingSelectorExpanded = !thinkingSelectorExpanded },
)
}
if (thinkingSelectorExpanded && thinkingSupported) {
ChatThinkingLevelSelector(
options = thinkingOptions,
@@ -2343,8 +2332,17 @@ private fun ChatComposer(
onToggleDictation = onToggleDictation,
talkActive = talkActive,
onToggleTalk = onToggleTalk,
runActive = pendingRunCount > 0,
onAbort = onAbort,
sendEnabled = sendEnabled,
onSend = onSend,
selectedModelLabel = selectedModelLabel,
modelPickerEnabled = modelPickerEnabled,
onOpenModelPicker = onOpenModelPicker,
thinkingLevel = thinkingLevel,
thinkingSupported = thinkingSupported,
onToggleThinkingSelector = { thinkingSelectorExpanded = !thinkingSelectorExpanded },
contextUsage = contextUsage,
modifier = Modifier.weight(1f),
)
}
@@ -2360,27 +2358,6 @@ private fun ChatComposer(
onCopyDiagnostics = onCopyDiagnostics,
)
}
if (pendingRunCount > 0) {
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) {
Surface(
onClick = onAbort,
modifier = Modifier.heightIn(min = ClawTheme.spacing.touchTarget),
shape = RoundedCornerShape(ClawTheme.radii.pill),
color = ClawTheme.colors.canvas,
contentColor = ClawTheme.colors.text,
) {
Row(
modifier = Modifier.padding(horizontal = 14.dp, vertical = 8.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
Box(modifier = Modifier.size(8.dp).background(ClawTheme.colors.danger, RoundedCornerShape(2.dp)))
Text(text = nativeString("Stop"), style = ClawTheme.type.label)
}
}
}
}
}
}
@@ -2416,38 +2393,6 @@ private fun ChatThinkingLevelSelector(
}
}
@Composable
private fun ChatModelChip(
label: String,
enabled: Boolean,
onClick: () -> Unit,
modifier: Modifier = Modifier,
) {
Surface(
onClick = onClick,
enabled = enabled,
modifier = modifier.heightIn(min = ClawTheme.spacing.touchTarget),
shape = RoundedCornerShape(ClawTheme.radii.pill),
color = ClawTheme.colors.canvas,
contentColor = if (enabled) ClawTheme.colors.text else ClawTheme.colors.textMuted,
) {
Row(
modifier = Modifier.padding(horizontal = 8.dp, vertical = 8.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(6.dp),
) {
Icon(imageVector = Icons.Default.ArrowDropDown, contentDescription = null, modifier = Modifier.size(13.dp), tint = ClawTheme.colors.textSubtle)
Text(
text = label,
style = ClawTheme.type.caption.copy(fontSize = 12.5.sp, lineHeight = 16.sp),
color = if (enabled) ClawTheme.colors.textMuted else ClawTheme.colors.textSubtle,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun BranchSwitcherSheet(
@@ -2712,68 +2657,6 @@ private fun ChatOfflineNotice(
}
}
@Composable
private fun ChatContextMeter(
thinkingLevel: String,
thinkingSupported: Boolean,
expanded: Boolean,
contextUsage: ChatContextUsage,
onClick: () -> Unit,
) {
val contextFraction = contextMeterWidth(contextUsage) ?: 0f
Row(
modifier = Modifier.width(178.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(7.dp),
) {
Surface(
onClick = onClick,
enabled = thinkingSupported,
modifier = Modifier.heightIn(min = ClawTheme.spacing.touchTarget),
shape = RoundedCornerShape(ClawTheme.radii.pill),
color = ClawTheme.colors.canvas,
contentColor = ClawTheme.colors.text,
) {
Row(
modifier = Modifier.padding(horizontal = 8.dp, vertical = 8.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(6.dp),
) {
if (thinkingSupported) {
Icon(
imageVector = if (expanded) Icons.Default.KeyboardArrowUp else Icons.Default.KeyboardArrowDown,
contentDescription = if (expanded) nativeString("Close thinking level selector") else nativeString("Open thinking level selector"),
modifier = Modifier.size(13.dp),
tint = ClawTheme.colors.textSubtle,
)
}
Text(
text = contextMeterLabel(contextUsage, thinkingLevel, thinkingSupported),
style = ClawTheme.type.caption.copy(fontSize = 12.5.sp, lineHeight = 16.sp),
color = ClawTheme.colors.textMuted,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
}
Box(
modifier =
Modifier
.weight(1f)
.height(3.dp)
.background(ClawTheme.colors.surfacePressed, RoundedCornerShape(999.dp)),
) {
Box(
modifier =
Modifier
.fillMaxWidth(contextFraction)
.height(3.dp)
.background(ClawTheme.colors.primary, RoundedCornerShape(999.dp)),
)
}
}
}
@Composable
private fun ChatInputPill(
value: String,
@@ -2788,11 +2671,21 @@ private fun ChatInputPill(
onToggleDictation: () -> Unit,
talkActive: Boolean,
onToggleTalk: () -> Unit,
runActive: Boolean,
onAbort: () -> Unit,
sendEnabled: Boolean,
onSend: () -> Unit,
selectedModelLabel: String,
modelPickerEnabled: Boolean,
onOpenModelPicker: () -> Unit,
thinkingLevel: String,
thinkingSupported: Boolean,
onToggleThinkingSelector: () -> Unit,
contextUsage: ChatContextUsage,
modifier: Modifier = Modifier,
) {
val hardwareEnterHandler = remember { PhysicalChatSendKeyHandler() }
var attachmentMenuExpanded by rememberSaveable { mutableStateOf(false) }
Surface(
modifier = modifier.heightIn(min = ClawTheme.spacing.touchTarget),
@@ -2801,78 +2694,177 @@ private fun ChatInputPill(
contentColor = ClawTheme.colors.text,
border = BorderStroke(1.dp, ClawTheme.colors.border),
) {
Row(
modifier = Modifier.padding(horizontal = 9.dp, vertical = 4.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(7.dp),
) {
Surface(onClick = onPickImages, modifier = Modifier.size(ClawTheme.spacing.touchTarget), shape = CircleShape, color = ClawTheme.colors.surfaceRaised, contentColor = ClawTheme.colors.text) {
Box(contentAlignment = Alignment.Center) {
Icon(imageVector = Icons.Default.Add, contentDescription = nativeString("Attach image"), modifier = Modifier.size(20.dp))
Column {
Row(
modifier = Modifier.padding(horizontal = 9.dp, vertical = 4.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(7.dp),
) {
Box {
Surface(onClick = { attachmentMenuExpanded = true }, modifier = Modifier.size(ClawTheme.spacing.touchTarget), shape = CircleShape, color = ClawTheme.colors.surfaceRaised, contentColor = ClawTheme.colors.text) {
Box(contentAlignment = Alignment.Center) {
Icon(imageVector = Icons.Default.Add, contentDescription = nativeString("Add attachment"), modifier = Modifier.size(20.dp))
}
}
DropdownMenu(expanded = attachmentMenuExpanded, onDismissRequest = { attachmentMenuExpanded = false }) {
DropdownMenuItem(text = { Text(nativeString("Photos")) }, leadingIcon = { Icon(Icons.Default.Photo, contentDescription = null) }, onClick = {
attachmentMenuExpanded = false
onPickImages()
})
DropdownMenuItem(text = { Text(nativeString("Videos")) }, leadingIcon = { Icon(Icons.Default.Videocam, contentDescription = null) }, onClick = {
attachmentMenuExpanded = false
onPickVideo()
})
DropdownMenuItem(text = { Text(nativeString("Files")) }, leadingIcon = { Icon(Icons.Default.AttachFile, contentDescription = null) }, onClick = {
attachmentMenuExpanded = false
onPickAudioOrDocument()
})
}
}
}
Surface(onClick = onPickAudioOrDocument, modifier = Modifier.size(ClawTheme.spacing.touchTarget), shape = CircleShape, color = ClawTheme.colors.surfaceRaised, contentColor = ClawTheme.colors.text) {
Box(contentAlignment = Alignment.Center) {
Icon(imageVector = Icons.Default.AttachFile, contentDescription = nativeString("Attachment"), modifier = Modifier.size(20.dp))
}
}
Surface(onClick = onPickVideo, modifier = Modifier.size(ClawTheme.spacing.touchTarget), shape = CircleShape, color = ClawTheme.colors.surfaceRaised, contentColor = ClawTheme.colors.text) {
Box(contentAlignment = Alignment.Center) {
Icon(imageVector = Icons.Default.Videocam, contentDescription = nativeString("Attach video"), modifier = Modifier.size(20.dp))
}
}
Box(modifier = Modifier.weight(1f)) {
ChatTextFieldValueAdapter(
value = value,
onValueChange = onValueChange,
keyHandler = hardwareEnterHandler,
) { textFieldValue, updateTextFieldValue ->
BasicTextField(
value = textFieldValue,
onValueChange = updateTextFieldValue,
textStyle = ClawTheme.type.body.copy(color = ClawTheme.colors.text),
cursorBrush = SolidColor(ClawTheme.colors.primary),
minLines = 1,
maxLines = 4,
modifier =
Modifier
.fillMaxWidth()
.onPreInterceptKeyBeforeSoftKeyboard { event ->
hardwareEnterHandler.handle(
event = event,
sendEnabled = sendEnabled,
textEmpty = textFieldValue.text.isEmpty(),
compositionActive = textFieldValue.composition != null,
onSend = onSend,
)
},
decorationBox = { innerTextField ->
Box(modifier = Modifier.fillMaxWidth(), contentAlignment = Alignment.CenterStart) {
if (value.isEmpty()) {
Text(text = nativeString("Message OpenClaw"), style = ClawTheme.type.body, color = ClawTheme.colors.textSubtle)
Box(modifier = Modifier.weight(1f)) {
ChatTextFieldValueAdapter(
value = value,
onValueChange = onValueChange,
keyHandler = hardwareEnterHandler,
) { textFieldValue, updateTextFieldValue ->
BasicTextField(
value = textFieldValue,
onValueChange = updateTextFieldValue,
textStyle = ClawTheme.type.body.copy(color = ClawTheme.colors.text),
cursorBrush = SolidColor(ClawTheme.colors.primary),
minLines = 1,
maxLines = 4,
modifier =
Modifier
.fillMaxWidth()
.onPreInterceptKeyBeforeSoftKeyboard { event ->
hardwareEnterHandler.handle(
event = event,
sendEnabled = sendEnabled,
textEmpty = textFieldValue.text.isEmpty(),
compositionActive = textFieldValue.composition != null,
onSend = onSend,
)
},
decorationBox = { innerTextField ->
Box(modifier = Modifier.fillMaxWidth(), contentAlignment = Alignment.CenterStart) {
if (value.isEmpty()) {
Text(text = nativeString("Message OpenClaw"), style = ClawTheme.type.body, color = ClawTheme.colors.textSubtle)
}
innerTextField()
}
innerTextField()
}
},
)
},
)
}
}
ChatComposerMicButton(
dictationActive = dictationActive,
dictationEnabled = dictationEnabled,
voiceNoteEnabled = recordVoiceNoteEnabled,
onToggleDictation = onToggleDictation,
onStartVoiceNote = onStartVoiceNote,
)
when (resolveChatComposerTrailingAction(talkActive = talkActive, runActive = runActive, sendEnabled = sendEnabled)) {
ChatComposerTrailingAction.Send -> SendButton(enabled = true, onClick = onSend)
ChatComposerTrailingAction.StartTalk -> LiveTalkButton(active = false, onClick = onToggleTalk)
ChatComposerTrailingAction.StopTalk -> {
// Talk keeps the morph slot, but run abort must stay reachable while both overlap.
if (runActive) StopButton(onClick = onAbort)
LiveTalkButton(active = true, onClick = onToggleTalk)
}
ChatComposerTrailingAction.Stop -> StopButton(onClick = onAbort)
}
}
ChatComposerMicButton(
dictationActive = dictationActive,
dictationEnabled = dictationEnabled,
voiceNoteEnabled = recordVoiceNoteEnabled,
onToggleDictation = onToggleDictation,
onStartVoiceNote = onStartVoiceNote,
ChatComposerFooter(
selectedModelLabel = selectedModelLabel,
modelPickerEnabled = modelPickerEnabled,
onOpenModelPicker = onOpenModelPicker,
thinkingLevel = thinkingLevel,
thinkingSupported = thinkingSupported,
onToggleThinkingSelector = onToggleThinkingSelector,
contextUsage = contextUsage,
)
when (resolveChatComposerTrailingAction(talkActive = talkActive, sendEnabled = sendEnabled)) {
ChatComposerTrailingAction.Send -> SendButton(enabled = true, onClick = onSend)
ChatComposerTrailingAction.StartTalk -> LiveTalkButton(active = false, onClick = onToggleTalk)
ChatComposerTrailingAction.StopTalk -> LiveTalkButton(active = true, onClick = onToggleTalk)
}
}
}
@Composable
private fun ChatComposerFooter(
selectedModelLabel: String,
modelPickerEnabled: Boolean,
onOpenModelPicker: () -> Unit,
thinkingLevel: String,
thinkingSupported: Boolean,
onToggleThinkingSelector: () -> Unit,
contextUsage: ChatContextUsage,
) {
val contextFraction = contextMeterWidth(contextUsage)
val contextPercent = contextFraction?.let { (it * 100).roundToInt() }
Row(
modifier = Modifier.fillMaxWidth().padding(start = 9.dp, end = 9.dp, bottom = 2.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(2.dp),
) {
ChatComposerFooterChip(
label = selectedModelLabel,
enabled = modelPickerEnabled,
onClick = onOpenModelPicker,
modifier = Modifier.weight(1f, fill = false),
)
if (thinkingSupported) {
ChatComposerFooterChip(
label = contextMeterThinkingLabel(thinkingLevel),
enabled = true,
onClick = onToggleThinkingSelector,
)
}
Spacer(modifier = Modifier.weight(1f))
if (contextFraction != null && contextPercent != null) {
val description = nativeString("Context \${contextPercent}% used", contextPercent)
val trackColor = ClawTheme.colors.surfacePressed
val progressColor = ClawTheme.colors.primary
Row(
modifier = Modifier.clearAndSetSemantics { contentDescription = description },
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(5.dp),
) {
Canvas(modifier = Modifier.size(14.dp)) {
val stroke = Stroke(width = 2.dp.toPx(), cap = StrokeCap.Round)
drawCircle(color = trackColor, style = stroke)
drawArc(color = progressColor, startAngle = -90f, sweepAngle = contextFraction * 360f, useCenter = false, style = stroke)
}
Text(text = nativeString("\${contextPercent}%", contextPercent), style = ClawTheme.type.caption, color = ClawTheme.colors.textMuted)
}
}
}
}
@Composable
private fun ChatComposerFooterChip(
label: String,
enabled: Boolean,
onClick: () -> Unit,
modifier: Modifier = Modifier,
) {
Surface(
onClick = onClick,
enabled = enabled,
modifier = modifier.heightIn(min = ClawTheme.spacing.touchTarget),
shape = RoundedCornerShape(ClawTheme.radii.pill),
color = Color.Transparent,
contentColor = if (enabled) ClawTheme.colors.textMuted else ClawTheme.colors.textSubtle,
) {
Row(
modifier = Modifier.padding(horizontal = 6.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(2.dp),
) {
Text(text = label, modifier = Modifier.weight(1f, fill = false), style = ClawTheme.type.caption, maxLines = 1, overflow = TextOverflow.Ellipsis)
Icon(imageVector = Icons.Default.ArrowDropDown, contentDescription = null, modifier = Modifier.size(13.dp), tint = ClawTheme.colors.textSubtle)
}
}
}
@Composable
private fun LiveTalkButton(
active: Boolean,
@@ -2886,8 +2878,8 @@ private fun LiveTalkButton(
.size(ClawTheme.spacing.touchTarget)
.semantics { contentDescription = buttonDescription },
shape = CircleShape,
color = ClawTheme.colors.danger,
contentColor = Color.White,
color = if (active) ClawTheme.colors.danger else ClawTheme.colors.surfaceRaised,
contentColor = if (active) Color.White else ClawTheme.colors.text,
) {
Box(contentAlignment = Alignment.Center) {
if (active) {
@@ -2903,6 +2895,21 @@ private fun LiveTalkButton(
}
}
@Composable
private fun StopButton(onClick: () -> Unit) {
Surface(
onClick = onClick,
modifier = Modifier.size(ClawTheme.spacing.touchTarget),
shape = CircleShape,
color = ClawTheme.colors.danger,
contentColor = Color.White,
) {
Box(contentAlignment = Alignment.Center) {
Icon(imageVector = Icons.Default.Stop, contentDescription = nativeString("Stop"), modifier = Modifier.size(20.dp))
}
}
}
@Composable
private fun LiveTalkWaveform(modifier: Modifier = Modifier) {
val transition = rememberInfiniteTransition()
@@ -3100,18 +3107,6 @@ internal fun contextMeterWidth(usage: ChatContextUsage): Float? {
return (total.toDouble() / context.toDouble()).coerceIn(0.0, 1.0).toFloat()
}
internal fun contextMeterLabel(
usage: ChatContextUsage,
thinkingLevel: String,
thinkingSupported: Boolean = true,
): String {
val contextLabel =
contextMeterWidth(usage)?.let {
nativeString("Context \${(it * 100).roundToInt()}%", (it * 100).roundToInt())
} ?: nativeString("Context --")
return if (thinkingSupported) nativeString("\$contextLabel · \${contextMeterThinkingLabel(thinkingLevel)}", contextLabel, contextMeterThinkingLabel(thinkingLevel)) else contextLabel
}
internal fun contextMeterThinkingLabel(value: String): String {
val normalized = value.trim().lowercase(Locale.US).ifEmpty { "off" }
return when (normalized) {
@@ -47,7 +47,6 @@ class ChatContextMeterTest {
assertEquals(ChatContextUsage(totalTokens = 1_250L, totalTokensFresh = true, contextTokens = 5_000L), usage)
assertEquals(0.25f, contextMeterWidth(usage))
assertEquals("Context 25% · High", contextMeterLabel(usage, "high"))
}
@Test
@@ -72,7 +71,6 @@ class ChatContextMeterTest {
)
assertEquals(ChatContextUsage(totalTokens = 41_000L, totalTokensFresh = true, contextTokens = 100_000L), usage)
assertEquals("Context 41% · Off", contextMeterLabel(usage, "off"))
}
@Test
@@ -80,7 +78,6 @@ class ChatContextMeterTest {
val usage = ChatContextUsage(totalTokens = 8_200L, totalTokensFresh = true, contextTokens = null)
assertNull(contextMeterWidth(usage))
assertEquals("Context -- · Medium", contextMeterLabel(usage, "medium"))
}
@Test
@@ -88,7 +85,6 @@ class ChatContextMeterTest {
val usage = ChatContextUsage(totalTokens = 150_000L, totalTokensFresh = true, contextTokens = 100_000L)
assertEquals(1.0f, contextMeterWidth(usage))
assertEquals("Context 100% · Low", contextMeterLabel(usage, "low"))
}
@Test
@@ -96,23 +92,17 @@ class ChatContextMeterTest {
val usage = ChatContextUsage(totalTokens = 82_000L, totalTokensFresh = false, contextTokens = 100_000L)
assertNull(contextMeterWidth(usage))
assertEquals("Context -- · High", contextMeterLabel(usage, "high"))
}
@Test
fun contextMeterHidesThinkingLabelWhenUnsupported() {
val usage = ChatContextUsage(totalTokens = 2_500L, totalTokensFresh = true, contextTokens = 10_000L)
assertEquals("Context 25%", contextMeterLabel(usage, "high", thinkingSupported = false))
}
@Test
fun contextMeterPreservesGatewayThinkingLevelIds() {
val usage = ChatContextUsage(totalTokens = null, totalTokensFresh = null, contextTokens = null)
assertEquals("Context -- · xhigh", contextMeterLabel(usage, "xhigh"))
assertEquals("Context -- · adaptive", contextMeterLabel(usage, "adaptive"))
assertEquals("Context -- · ultra", contextMeterLabel(usage, "ultra"))
fun thinkingLabelsMapKnownLevelsAndPreserveGatewayIds() {
assertEquals("Off", contextMeterThinkingLabel("off"))
assertEquals("Low", contextMeterThinkingLabel("low"))
assertEquals("Medium", contextMeterThinkingLabel("medium"))
assertEquals("High", contextMeterThinkingLabel("high"))
assertEquals("xhigh", contextMeterThinkingLabel("xhigh"))
assertEquals("adaptive", contextMeterThinkingLabel("adaptive"))
assertEquals("ultra", contextMeterThinkingLabel("ultra"))
}
@Test
@@ -73,18 +73,22 @@ class ChatScreenTest {
}
@Test
fun activeTalkAlwaysKeepsTheStopControlVisible() {
fun composerTrailingActionPreservesTalkAndRunStopPrecedence() {
assertEquals(
ChatComposerTrailingAction.StopTalk,
resolveChatComposerTrailingAction(talkActive = true, sendEnabled = true),
resolveChatComposerTrailingAction(talkActive = true, runActive = true, sendEnabled = true),
)
assertEquals(
ChatComposerTrailingAction.Stop,
resolveChatComposerTrailingAction(talkActive = false, runActive = true, sendEnabled = true),
)
assertEquals(
ChatComposerTrailingAction.Send,
resolveChatComposerTrailingAction(talkActive = false, sendEnabled = true),
resolveChatComposerTrailingAction(talkActive = false, runActive = false, sendEnabled = true),
)
assertEquals(
ChatComposerTrailingAction.StartTalk,
resolveChatComposerTrailingAction(talkActive = false, sendEnabled = false),
resolveChatComposerTrailingAction(talkActive = false, runActive = false, sendEnabled = false),
)
}
@@ -74,6 +74,17 @@
"cwd"
]
},
"portal": {
"emoji": "🌐",
"title": "Portal",
"detailKeys": [
"action",
"port",
"id",
"title",
"path"
]
},
"process": {
"emoji": "🧰",
"title": "Process",
@@ -1139,6 +1139,7 @@ public struct ConnectParams: Codable, Sendable {
public let client: [String: AnyCodable]
public let caps: [String]?
public let commands: [String]?
public let workerruns: WorkerAdmissionHandshake?
public let permissions: [String: AnyCodable]?
public let pathenv: String?
public let role: String?
@@ -1154,6 +1155,7 @@ public struct ConnectParams: Codable, Sendable {
client: [String: AnyCodable],
caps: [String]? = nil,
commands: [String]? = nil,
workerruns: WorkerAdmissionHandshake? = nil,
permissions: [String: AnyCodable]? = nil,
pathenv: String? = nil,
role: String? = nil,
@@ -1168,6 +1170,7 @@ public struct ConnectParams: Codable, Sendable {
self.client = client
self.caps = caps
self.commands = commands
self.workerruns = workerruns
self.permissions = permissions
self.pathenv = pathenv
self.role = role
@@ -1184,6 +1187,7 @@ public struct ConnectParams: Codable, Sendable {
case client
case caps
case commands
case workerruns = "workerRuns"
case permissions
case pathenv = "pathEnv"
case role
@@ -19228,6 +19232,190 @@ public struct ShutdownEvent: Codable, Sendable {
}
}
public struct PortalSummary: Codable, Sendable {
public let id: String
public let title: String
public let port: Int
public let listenport: Int
public let tokenquery: String?
public let url: String?
public let publicurl: String
public let path: String?
public let description: String?
public let createdatms: Int
public init(
id: String,
title: String,
port: Int,
listenport: Int,
tokenquery: String? = nil,
url: String? = nil,
publicurl: String,
path: String? = nil,
description: String? = nil,
createdatms: Int)
{
self.id = id
self.title = title
self.port = port
self.listenport = listenport
self.tokenquery = tokenquery
self.url = url
self.publicurl = publicurl
self.path = path
self.description = description
self.createdatms = createdatms
}
private enum CodingKeys: String, CodingKey {
case id
case title
case port
case listenport = "listenPort"
case tokenquery = "tokenQuery"
case url
case publicurl = "publicUrl"
case path
case description
case createdatms = "createdAtMs"
}
}
public struct PortalListParams: Codable, Sendable {}
public struct PortalListResult: Codable, Sendable {
public let portals: [PortalSummary]
public init(
portals: [PortalSummary])
{
self.portals = portals
}
private enum CodingKeys: String, CodingKey {
case portals
}
}
public struct PortalOpenParams: Codable, Sendable {
public let port: Int
public let title: String?
public let description: String?
public let path: String?
public init(
port: Int,
title: String? = nil,
description: String? = nil,
path: String? = nil)
{
self.port = port
self.title = title
self.description = description
self.path = path
}
private enum CodingKeys: String, CodingKey {
case port
case title
case description
case path
}
}
public struct PortalOpenResult: Codable, Sendable {
public let id: String
public let title: String
public let port: Int
public let listenport: Int
public let tokenquery: String
public let url: String
public let publicurl: String
public let path: String?
public let description: String?
public let createdatms: Int
public init(
id: String,
title: String,
port: Int,
listenport: Int,
tokenquery: String,
url: String,
publicurl: String,
path: String? = nil,
description: String? = nil,
createdatms: Int)
{
self.id = id
self.title = title
self.port = port
self.listenport = listenport
self.tokenquery = tokenquery
self.url = url
self.publicurl = publicurl
self.path = path
self.description = description
self.createdatms = createdatms
}
private enum CodingKeys: String, CodingKey {
case id
case title
case port
case listenport = "listenPort"
case tokenquery = "tokenQuery"
case url
case publicurl = "publicUrl"
case path
case description
case createdatms = "createdAtMs"
}
}
public struct PortalCloseParams: Codable, Sendable {
public let id: String
public init(
id: String)
{
self.id = id
}
private enum CodingKeys: String, CodingKey {
case id
}
}
public struct PortalCloseResult: Codable, Sendable {
public let closed: Bool
public init(
closed: Bool)
{
self.closed = closed
}
private enum CodingKeys: String, CodingKey {
case closed
}
}
public struct PortalChangedEvent: Codable, Sendable {
public let portals: [PortalSummary]
public init(
portals: [PortalSummary])
{
self.portals = portals
}
private enum CodingKeys: String, CodingKey {
case portals
}
}
public enum BoardOp: Codable, Sendable {
case tabCreate(BoardTabCreateOp)
case tabUpdate(BoardTabUpdateOp)
-3
View File
@@ -15,9 +15,6 @@ function bundledPluginFile(pluginId: string, relativePath: string, suffix = ""):
const repositoryScriptEntries = [
// setup-node-env invokes this helper from composite-action YAML.
".github/actions/setup-node-env/dependency-fingerprint.mjs!",
".github/actions/setup-node-env/verify-importers.mjs!",
".github/actions/register-bind-mount-cleanup/main.cjs!",
".github/actions/register-bind-mount-cleanup/post.cjs!",
"apps/android/scripts/build-release-artifacts.ts!",
"scripts/bundle-a2ui.mts!",
"scripts/build-discord-activity-sdk.mts!",
-2
View File
@@ -24,8 +24,6 @@ const scriptEntries = productionConfig.workspaces["."].entry.filter(
const repositoryToolEntries = [
".github/actions/setup-node-env/dependency-fingerprint.mjs!",
".github/actions/register-bind-mount-cleanup/main.cjs!",
".github/actions/register-bind-mount-cleanup/post.cjs!",
"apps/android/scripts/build-release-artifacts.ts!",
"security/opengrep/check-rule-metadata.mjs!",
"security/opengrep/compile-rules.mjs!",
+1 -1
View File
@@ -1,5 +1,5 @@
{
"core": 2306,
"core": 2308,
"channel": 3575,
"plugin": 3997
}
+2 -2
View File
@@ -1,4 +1,4 @@
aa6a639288bfde08ea8b8938f68ae6c414b45a9b976744b1cdb75aa590661cbb config-baseline.json
ce74623d1b19b178aee681a4d1e15eee17297f3a5d939ed1f0fba838abfabe6c config-baseline.core.json
ff3a4f23b20f40e493f2c5d4da1a8c6fba0206cf763a372e6437a3cf515d1099 config-baseline.json
d834d843e6f65a87490964c1e7a089ec8421b546a9274b84c5d2ef35ec14f5a1 config-baseline.core.json
1144184911193a239dd0e6415335a2a95771af27c4b1f5974e64851d6b2ed65d config-baseline.channel.json
250f573a93619d8a2f554028288af84551e572ef8bfdc820a43024572b55175a config-baseline.plugin.json
+1 -1
View File
@@ -1 +1 @@
{"contentHash":"ca54bfaaa3b3a4360b8fc5777d74ee94add20d789861ad8f84c564324b72d34a","entrypoint":"account-core","importSpecifier":"openclaw/plugin-sdk/account-core"}
{"contentHash":"76643cb5737109f0cac8ea83133774ba1254dc66eecc5fc8f74fabdd8356796a","entrypoint":"account-core","importSpecifier":"openclaw/plugin-sdk/account-core"}
@@ -1 +1 @@
{"contentHash":"de22787449794b11be3d6a69fe5e4d33e21369febee2a3c4f1eb62d8105d8fb5","entrypoint":"account-helpers","importSpecifier":"openclaw/plugin-sdk/account-helpers"}
{"contentHash":"f7012931478c6fe373daa77468fa03d52043a0e65c3bd45dd94cb841222d81e2","entrypoint":"account-helpers","importSpecifier":"openclaw/plugin-sdk/account-helpers"}
@@ -1 +1 @@
{"contentHash":"5011920cb3bc682df7980e84b82f25399e6fd220b569f9b42758b275f3666e66","entrypoint":"account-resolution","importSpecifier":"openclaw/plugin-sdk/account-resolution"}
{"contentHash":"a681a00e79eccd3f3b2fab17c5d929677d94ae942543688b3b128bed988398e2","entrypoint":"account-resolution","importSpecifier":"openclaw/plugin-sdk/account-resolution"}
@@ -1 +1 @@
{"contentHash":"812e818c8d7c013b2287c4502426123222037a4235e130a69481272912062866","entrypoint":"agent-harness-runtime","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime"}
{"contentHash":"ae84192abc0c0c343008e51e9292f03c3719b1c57f15d5a868c0db3898bf9a4c","entrypoint":"agent-harness-runtime","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime"}
+1 -1
View File
@@ -1 +1 @@
{"contentHash":"f639fea8b8ee53626452bdbce156724b09a3a29bb05a134eb8e8f8fb8062d4da","entrypoint":"agent-harness","importSpecifier":"openclaw/plugin-sdk/agent-harness"}
{"contentHash":"809729267555a298853438ac9d5a30e3667accc9dd20500d0434fd1cb86c45ec","entrypoint":"agent-harness","importSpecifier":"openclaw/plugin-sdk/agent-harness"}
@@ -1 +1 @@
{"contentHash":"7b5bf5348d9f0df4e056c9787ea8952f67c9c1f654922528a196ced5420c1f7f","entrypoint":"agent-media-payload","importSpecifier":"openclaw/plugin-sdk/agent-media-payload"}
{"contentHash":"5bd6b57d5b5fafc9504efd61f1033c0e7d9523fce35de52957523341bed18007","entrypoint":"agent-media-payload","importSpecifier":"openclaw/plugin-sdk/agent-media-payload"}
+1 -1
View File
@@ -1 +1 @@
{"contentHash":"0a014810b900e53959679fa292ed45cb0f1a1a3cc6b1129021bfa63b871f8e68","entrypoint":"agent-runtime","importSpecifier":"openclaw/plugin-sdk/agent-runtime"}
{"contentHash":"333c6c2b268b36450e24838f07f17d6a63f7807d4541c7a6eada12c344082b93","entrypoint":"agent-runtime","importSpecifier":"openclaw/plugin-sdk/agent-runtime"}
@@ -1 +1 @@
{"contentHash":"4ffa76fb2b34757ef4f6bd0ccc55182f4e963f7b9f055160b26d567584cc9bc6","entrypoint":"agent-scope-runtime","importSpecifier":"openclaw/plugin-sdk/agent-scope-runtime"}
{"contentHash":"31a2629e002e53ad33ccd08e5a2b78523aa24236256e0aec0700b6db8129c623","entrypoint":"agent-scope-runtime","importSpecifier":"openclaw/plugin-sdk/agent-scope-runtime"}
@@ -1 +1 @@
{"contentHash":"4406fa069d6376725fe9c113dd65aeee2ebfcd4accdefb9aa888a989ddab2429","entrypoint":"allowlist-config-edit","importSpecifier":"openclaw/plugin-sdk/allowlist-config-edit"}
{"contentHash":"01b10ef1e03b58d5fd391bd8a4f3b0421be26314c8bca49d8a224ccbcbafbed1","entrypoint":"allowlist-config-edit","importSpecifier":"openclaw/plugin-sdk/allowlist-config-edit"}
@@ -1 +1 @@
{"contentHash":"0db493ed0935458bfdba48f1d2f854561331768ecc13bcc65d16581eb212d4f8","entrypoint":"approval-auth-runtime","importSpecifier":"openclaw/plugin-sdk/approval-auth-runtime"}
{"contentHash":"496edef91c62e94fc29d9d666c1b832dec36315a05a383db2f0eea9623cbc464","entrypoint":"approval-auth-runtime","importSpecifier":"openclaw/plugin-sdk/approval-auth-runtime"}
@@ -1 +1 @@
{"contentHash":"9f38d8917387011027bd285233a15cb53723a9948f6c26472da2d45f397b561d","entrypoint":"approval-client-runtime","importSpecifier":"openclaw/plugin-sdk/approval-client-runtime"}
{"contentHash":"78e308f6e23dbac85cf8b9eb8ecad607fae673502432edab25ef27e650d5b81e","entrypoint":"approval-client-runtime","importSpecifier":"openclaw/plugin-sdk/approval-client-runtime"}
@@ -1 +1 @@
{"contentHash":"c2170d7ee8ba6a9d7b816312b652f131b4fbf3952fd6cbfa649582c0106feda9","entrypoint":"approval-delivery-runtime","importSpecifier":"openclaw/plugin-sdk/approval-delivery-runtime"}
{"contentHash":"ef139545fa43dfa5e14a57dea50bca6c8a650debc0bd8e42cb0fb00564e8e7f0","entrypoint":"approval-delivery-runtime","importSpecifier":"openclaw/plugin-sdk/approval-delivery-runtime"}
@@ -1 +1 @@
{"contentHash":"bd864f089ba285e271f9a98d149a85efad03bd687ddc50fd72da5b970501a492","entrypoint":"approval-gateway-runtime","importSpecifier":"openclaw/plugin-sdk/approval-gateway-runtime"}
{"contentHash":"289ccd9f5a88bceb9e201d7851c1f110f59a8e115c4f07f63d1a2703df20df2a","entrypoint":"approval-gateway-runtime","importSpecifier":"openclaw/plugin-sdk/approval-gateway-runtime"}
@@ -1 +1 @@
{"contentHash":"c5bceaafa22ff897cd09dcf90712b4a756979aed8fab1bb1ca13ac9d4471a6a9","entrypoint":"approval-handler-adapter-runtime","importSpecifier":"openclaw/plugin-sdk/approval-handler-adapter-runtime"}
{"contentHash":"e0c7d7a5e247c45c2f7e169c775542c78055f0acc1eda3b60b84b0f8440bfaf1","entrypoint":"approval-handler-adapter-runtime","importSpecifier":"openclaw/plugin-sdk/approval-handler-adapter-runtime"}
@@ -1 +1 @@
{"contentHash":"0f099da585bd9b5fa887c0c3e572512debb5d69ee1ad0f1d011d182ffe300a5c","entrypoint":"approval-handler-runtime","importSpecifier":"openclaw/plugin-sdk/approval-handler-runtime"}
{"contentHash":"8435538ee0ab09ff5a1d7c6aab5b414439a1d5607210069917c2974acb168764","entrypoint":"approval-handler-runtime","importSpecifier":"openclaw/plugin-sdk/approval-handler-runtime"}
@@ -1 +1 @@
{"contentHash":"3600f68d632471ecee191ffe0f2a1b419bb4ba544455d18930c4b96b22d8c2d2","entrypoint":"approval-native-runtime","importSpecifier":"openclaw/plugin-sdk/approval-native-runtime"}
{"contentHash":"e39cb3cbbd7828deec5c7088b48d028f9c46aca215aa18090dabc55a061c428d","entrypoint":"approval-native-runtime","importSpecifier":"openclaw/plugin-sdk/approval-native-runtime"}
@@ -1 +1 @@
{"contentHash":"e84e112bb8043500c4802580af7f41e73d3371411eae4fa661f28b65474fb026","entrypoint":"approval-runtime","importSpecifier":"openclaw/plugin-sdk/approval-runtime"}
{"contentHash":"dcc0b4622948450eab19bc2b8ae706a1b71a9b4a4969ff78f52027a39d8e606f","entrypoint":"approval-runtime","importSpecifier":"openclaw/plugin-sdk/approval-runtime"}
@@ -1 +1 @@
{"contentHash":"dc308dc325f2fb6192ecead080dbba4f84214091101fdd07c0f3f2297b500cf5","entrypoint":"channel-config-helpers","importSpecifier":"openclaw/plugin-sdk/channel-config-helpers"}
{"contentHash":"bba3b4b62186570b01446bd10ab2a2e97255f2549a50f59acad53ed34a4d73f5","entrypoint":"channel-config-helpers","importSpecifier":"openclaw/plugin-sdk/channel-config-helpers"}
@@ -1 +1 @@
{"contentHash":"44234eb25f54df45dc999e0c4314dbe36bf043b2d972e82732c7f9a0283862ab","entrypoint":"channel-contract","importSpecifier":"openclaw/plugin-sdk/channel-contract"}
{"contentHash":"9e07df547f6ac4df96d9f14864c6f1c3cb637b52891d9f9abefa7ccefe8721dd","entrypoint":"channel-contract","importSpecifier":"openclaw/plugin-sdk/channel-contract"}
+1 -1
View File
@@ -1 +1 @@
{"contentHash":"02db4abe2f1f4578d438f7afacd68d44114e46b48b9582711e7e10191353e6c5","entrypoint":"channel-core","importSpecifier":"openclaw/plugin-sdk/channel-core"}
{"contentHash":"ed030f8bf72ca1b07419980b050fc25bbd285a35c808b888aeb29dd7ccbf30d3","entrypoint":"channel-core","importSpecifier":"openclaw/plugin-sdk/channel-core"}
@@ -1 +1 @@
{"contentHash":"9aa8d47743a80bd09d292a3b0f21529919e8e51f1aa765be513370b6e80706ef","entrypoint":"channel-dm-policy","importSpecifier":"openclaw/plugin-sdk/channel-dm-policy"}
{"contentHash":"6de14733b76cec50b27ddcdcb30222c0bc53eb2bbac9e9bb625c2150df94b70e","entrypoint":"channel-dm-policy","importSpecifier":"openclaw/plugin-sdk/channel-dm-policy"}
@@ -1 +1 @@
{"contentHash":"6826237a93cc52b6039fddbd4b4e0e00e82b5c83cbadd36a055a50ab0480879c","entrypoint":"channel-entry-contract","importSpecifier":"openclaw/plugin-sdk/channel-entry-contract"}
{"contentHash":"844d124f9581a338da76e92b9ad728ae43d451cdc5ca9284d7a8c33486f5859d","entrypoint":"channel-entry-contract","importSpecifier":"openclaw/plugin-sdk/channel-entry-contract"}
@@ -1 +1 @@
{"contentHash":"4f1089cba4f061a6df633d593523eebf7645e19bc88fe152a5c7da5e7f213cbe","entrypoint":"channel-feedback","importSpecifier":"openclaw/plugin-sdk/channel-feedback"}
{"contentHash":"1d4d62e5f9885bf2263192488e721646ceb7183aff7400f3a8a975b5e9f9fcde","entrypoint":"channel-feedback","importSpecifier":"openclaw/plugin-sdk/channel-feedback"}
@@ -1 +1 @@
{"contentHash":"b931541242c239ef45e02e794fbeabf2ca69e4071a261266aacc698f738b32bd","entrypoint":"channel-inbound-debounce","importSpecifier":"openclaw/plugin-sdk/channel-inbound-debounce"}
{"contentHash":"68e07ef5558e83fe34a66f9803453cbe81659ce53e8668296e70ac029185d99f","entrypoint":"channel-inbound-debounce","importSpecifier":"openclaw/plugin-sdk/channel-inbound-debounce"}
@@ -1 +1 @@
{"contentHash":"e0eefe9ad871dbfe2bb8dac626e44ecfcd55c8b35667e9fd041799f2f45ea6ba","entrypoint":"channel-inbound","importSpecifier":"openclaw/plugin-sdk/channel-inbound"}
{"contentHash":"d8345346a5e695d6d95d794180ae157ec304e28e3a4a542dcb700205010f2702","entrypoint":"channel-inbound","importSpecifier":"openclaw/plugin-sdk/channel-inbound"}
@@ -1 +1 @@
{"contentHash":"6f32863c74ad4ea076404754618d38015ad48281964f1fc24692aae8cd0fcad6","entrypoint":"channel-ingress-runtime","importSpecifier":"openclaw/plugin-sdk/channel-ingress-runtime"}
{"contentHash":"b88cf53042923dfc06236ab45e2faa4da3ba21a85188722f667262cdab5c465f","entrypoint":"channel-ingress-runtime","importSpecifier":"openclaw/plugin-sdk/channel-ingress-runtime"}
@@ -1 +1 @@
{"contentHash":"bc8881a906f40f0a3ede29eb83efc1e4d3e59b62c00154b2480d85d69cbe4010","entrypoint":"channel-message","importSpecifier":"openclaw/plugin-sdk/channel-message"}
{"contentHash":"a962a9f1b15cfa053e51e2bff3406b8ff9bbefde5deee78178c1e4b4bb61e4d1","entrypoint":"channel-message","importSpecifier":"openclaw/plugin-sdk/channel-message"}
@@ -1 +1 @@
{"contentHash":"f4b35d03ac9df9788462f3e50b64819ff85aba0245b1a51208a288bd994edb8e","entrypoint":"channel-outbound","importSpecifier":"openclaw/plugin-sdk/channel-outbound"}
{"contentHash":"494057368f1ee648156548195c02103b884c5a98bc038b0986bd3f29519c2b69","entrypoint":"channel-outbound","importSpecifier":"openclaw/plugin-sdk/channel-outbound"}
@@ -1 +1 @@
{"contentHash":"d1c4d7478b9dcb6c4567cb599839c84b843e005ff39f5d31bc317f26d6745853","entrypoint":"channel-pairing","importSpecifier":"openclaw/plugin-sdk/channel-pairing"}
{"contentHash":"60c244e385d96d8142a1d2b713715610ae9441f0717a98aaa6b511a4addf38ab","entrypoint":"channel-pairing","importSpecifier":"openclaw/plugin-sdk/channel-pairing"}
@@ -1 +1 @@
{"contentHash":"80b5ab5fedd16c952c83747f4093db46e49640b3cfa1fdaa5a9727ff31edf0a7","entrypoint":"channel-plugin-common","importSpecifier":"openclaw/plugin-sdk/channel-plugin-common"}
{"contentHash":"b84d3c182569569f83b54fcdead3ee77743a56bd949a393794ea547f4895de72","entrypoint":"channel-plugin-common","importSpecifier":"openclaw/plugin-sdk/channel-plugin-common"}
+1 -1
View File
@@ -1 +1 @@
{"contentHash":"ce25306e19a7463a8f79bebe822c436a7064088e89fdb3a4f51fdff3d96a9070","entrypoint":"channel-policy","importSpecifier":"openclaw/plugin-sdk/channel-policy"}
{"contentHash":"101b45f041cfbd36fc54cfe169a00eb52305bd962d9d6866c9d631f974bc80d3","entrypoint":"channel-policy","importSpecifier":"openclaw/plugin-sdk/channel-policy"}
@@ -1 +1 @@
{"contentHash":"d1c85fca4678c83f45606bf39c2304a1ebed29a0b12899b011a7c2d04fcf9553","entrypoint":"channel-reply-pipeline","importSpecifier":"openclaw/plugin-sdk/channel-reply-pipeline"}
{"contentHash":"df634c3e8c3ea6d44b600fe7f8b76699e55e6bb5725c67a0c05b3b6a9f4d64dc","entrypoint":"channel-reply-pipeline","importSpecifier":"openclaw/plugin-sdk/channel-reply-pipeline"}
@@ -1 +1 @@
{"contentHash":"ea6b1a3fe6e37494ff269ead50dd8749a389f5d51db6933a07210dc2d7820e1c","entrypoint":"channel-secret-basic-runtime","importSpecifier":"openclaw/plugin-sdk/channel-secret-basic-runtime"}
{"contentHash":"d3d9c4258a5cdfb5db16610d9312d20e7ee3620869fc78b637cc4f759043589f","entrypoint":"channel-secret-basic-runtime","importSpecifier":"openclaw/plugin-sdk/channel-secret-basic-runtime"}
@@ -1 +1 @@
{"contentHash":"4dc96cbce5c2379f9bf2b72f124e310e8bc84c740b414d0c5a33a22f6314d17f","entrypoint":"channel-send-result","importSpecifier":"openclaw/plugin-sdk/channel-send-result"}
{"contentHash":"93cabcf483dbb7c28992d9c142ed08ea9233a68f59510215b46a3ab19eb6ec15","entrypoint":"channel-send-result","importSpecifier":"openclaw/plugin-sdk/channel-send-result"}
+1 -1
View File
@@ -1 +1 @@
{"contentHash":"191b5318cf8fae32b4122cf6fcaa2e2a21bb3e93a1e223098bf508bee8c5323b","entrypoint":"channel-setup","importSpecifier":"openclaw/plugin-sdk/channel-setup"}
{"contentHash":"510f3965e8109bba757d69d4b103ad514b4b40c7839d97d34a3cdb683edebdf3","entrypoint":"channel-setup","importSpecifier":"openclaw/plugin-sdk/channel-setup"}
@@ -1 +1 @@
{"contentHash":"aebe7a9c65a23ade6f6546e50384ad136e69feb3062ea326f4b9d044af7ab017","entrypoint":"command-auth-native","importSpecifier":"openclaw/plugin-sdk/command-auth-native"}
{"contentHash":"284e36bdc35a14d9d6fcbc54ba330cc8cbb4f776bca22bba18803727704ed750","entrypoint":"command-auth-native","importSpecifier":"openclaw/plugin-sdk/command-auth-native"}
+1 -1
View File
@@ -1 +1 @@
{"contentHash":"fc8c5f2fedb947a0911e3496b8a14695a1f697aa801f8c59fdb3dddf0f95b171","entrypoint":"command-auth","importSpecifier":"openclaw/plugin-sdk/command-auth"}
{"contentHash":"3a88f903879a4e8bf99742cb05c56e68f057ebc22283eb0787763559b58343ae","entrypoint":"command-auth","importSpecifier":"openclaw/plugin-sdk/command-auth"}
@@ -1 +1 @@
{"contentHash":"d259a6cdf11d9ec8687ffb426347afc978e259813c47681d81cd61bd68443757","entrypoint":"command-detection","importSpecifier":"openclaw/plugin-sdk/command-detection"}
{"contentHash":"c962c27e1df6e286fa99fdb55266c77c34b56993290ad034066ef8a8b4698307","entrypoint":"command-detection","importSpecifier":"openclaw/plugin-sdk/command-detection"}
+1 -1
View File
@@ -1 +1 @@
{"contentHash":"fd6eac0297be07a4ac68eb1a12f091c9fabc36e257c022998f69cd91fb765726","entrypoint":"command-status","importSpecifier":"openclaw/plugin-sdk/command-status"}
{"contentHash":"76ff5bbad5f25af7608995790a386ee3eccd3a34db6eab66827642e180773551","entrypoint":"command-status","importSpecifier":"openclaw/plugin-sdk/command-status"}
@@ -1 +1 @@
{"contentHash":"657d052b745a903daaa80b7ae2525d89cf093e803bc2b69bc91073fd20ceb106","entrypoint":"config-contracts","importSpecifier":"openclaw/plugin-sdk/config-contracts"}
{"contentHash":"8ccbfcb14ef710956045cb6d53bcf566a9b7f6cd202668497d65e87222410b7c","entrypoint":"config-contracts","importSpecifier":"openclaw/plugin-sdk/config-contracts"}
@@ -1 +1 @@
{"contentHash":"9bcbdee2499e17f5411a42bd45919dfcd7cac92f12e7b0d034b519bbc46c70c5","entrypoint":"config-mutation","importSpecifier":"openclaw/plugin-sdk/config-mutation"}
{"contentHash":"3221e540f383fdcb9b3d4ac87c1c8433ccd3c33b25be3e5c7e55477c8fa24eca","entrypoint":"config-mutation","importSpecifier":"openclaw/plugin-sdk/config-mutation"}
+1 -1
View File
@@ -1 +1 @@
{"contentHash":"770e079b242b9c56e8876f8fd08b402a78f1870367dca19d006ef147f9d493a4","entrypoint":"config-runtime","importSpecifier":"openclaw/plugin-sdk/config-runtime"}
{"contentHash":"45a369a5ffbe0aa84263993881dc03feaf16b32567ebcb5545f06c24f57b80b7","entrypoint":"config-runtime","importSpecifier":"openclaw/plugin-sdk/config-runtime"}
@@ -1 +1 @@
{"contentHash":"1f751e446e40a43c6bdf3ef1c13dfd86e6b543d804858a145f7a59d458ce04a9","entrypoint":"conversation-runtime","importSpecifier":"openclaw/plugin-sdk/conversation-runtime"}
{"contentHash":"b2f5a9cffda3217d1ffd41fb811b2ee13c6fe817536e88c07d0db14173029759","entrypoint":"conversation-runtime","importSpecifier":"openclaw/plugin-sdk/conversation-runtime"}
+1 -1
View File
@@ -1 +1 @@
{"contentHash":"edfa6b7a219aef521935ac17a20c5cd74e91bf4988eb3fad5ac75fc2e5015596","entrypoint":"core","importSpecifier":"openclaw/plugin-sdk/core"}
{"contentHash":"b64946f34d1ab9c7263958543ddc48532563d75b2182060a803a7a400dbba60f","entrypoint":"core","importSpecifier":"openclaw/plugin-sdk/core"}
@@ -1 +1 @@
{"contentHash":"3a22e4de719dcc7055c6cc3771b72b1366b41b2d43613bcd904fd11283ca49a4","entrypoint":"diagnostic-runtime","importSpecifier":"openclaw/plugin-sdk/diagnostic-runtime"}
{"contentHash":"76f03283b5d78aca66035a95c695cf8549e1ae4a6c1c48fd7a00e44422bfe510","entrypoint":"diagnostic-runtime","importSpecifier":"openclaw/plugin-sdk/diagnostic-runtime"}
@@ -1 +1 @@
{"contentHash":"5cb3edfc636dd00bf8a6fb5d577c057c6fc38a535fb473fa042244087a2c407b","entrypoint":"directory-runtime","importSpecifier":"openclaw/plugin-sdk/directory-runtime"}
{"contentHash":"5afab775380f00070113c9e463a8bf4b0f1ac1d9b09f8948425aba5a178f2ac5","entrypoint":"directory-runtime","importSpecifier":"openclaw/plugin-sdk/directory-runtime"}
+1 -1
View File
@@ -1 +1 @@
{"contentHash":"6490014377554ea6b62c657ab22c53fcec385b4ad24cdfccd95ebd9a79717e59","entrypoint":"discord","importSpecifier":"openclaw/plugin-sdk/discord"}
{"contentHash":"5dceb33d4f128b96962d6038b92f6dd50d501983d14224236d04da410e9de474","entrypoint":"discord","importSpecifier":"openclaw/plugin-sdk/discord"}
@@ -1 +1 @@
{"contentHash":"21749b994b61953b5a3f274b26ae9c497616521659b8dd02b6ab59cecc881f77","entrypoint":"extension-shared","importSpecifier":"openclaw/plugin-sdk/extension-shared"}
{"contentHash":"f404d619a327692c0ff6e6c82ea592e93aa193dd08be98a84c33096951690caa","entrypoint":"extension-shared","importSpecifier":"openclaw/plugin-sdk/extension-shared"}
@@ -1 +1 @@
{"contentHash":"b691fcfb34a5f228938d06c50f9f8a26bb63ce06644e9df25f44876601e4cebb","entrypoint":"gateway-runtime","importSpecifier":"openclaw/plugin-sdk/gateway-runtime"}
{"contentHash":"ab874857dc00ef64368085dc0e2ef13d4abcc52dd40cb3c9baeb275681ebd04a","entrypoint":"gateway-runtime","importSpecifier":"openclaw/plugin-sdk/gateway-runtime"}
+1 -1
View File
@@ -1 +1 @@
{"contentHash":"3f25999ca07382c0014dd474bffffa2410b2e3c403cfba58641eae8d25f7b20c","entrypoint":"health","importSpecifier":"openclaw/plugin-sdk/health"}
{"contentHash":"5305c7c75c86c24e1b445370136422abf1804f85a0a31294ee03af7c38d902ee","entrypoint":"health","importSpecifier":"openclaw/plugin-sdk/health"}
+1 -1
View File
@@ -1 +1 @@
{"contentHash":"a726635b09ec7e6bd252582b7400b234bbd70d9f816675fe632bf1702b0c9f3b","entrypoint":"hook-runtime","importSpecifier":"openclaw/plugin-sdk/hook-runtime"}
{"contentHash":"9eeaa0de58ffbd07e63d6c6c25ec345d2178e822d40e1fe58f36872f172436b7","entrypoint":"hook-runtime","importSpecifier":"openclaw/plugin-sdk/hook-runtime"}
@@ -1 +1 @@
{"contentHash":"bb5122c6ac5f4dfe381493b9d128a303108434edca9434ad5528781d005271b8","entrypoint":"inbound-reply-dispatch","importSpecifier":"openclaw/plugin-sdk/inbound-reply-dispatch"}
{"contentHash":"a8c1c70f72a1c4d860682f9a00d09c9634f9c3b8044eadf5cf7f65475ecad73d","entrypoint":"inbound-reply-dispatch","importSpecifier":"openclaw/plugin-sdk/inbound-reply-dispatch"}
+1 -1
View File
@@ -1 +1 @@
{"contentHash":"4d37083b179c1ea8adf47c13a224d6b05ef817a7b23920a02c836b0e64e54f5b","entrypoint":"infra-runtime","importSpecifier":"openclaw/plugin-sdk/infra-runtime"}
{"contentHash":"f467cd02e663b2913d114e43133616aeb493024caf120f6359622e1e9ef41427","entrypoint":"infra-runtime","importSpecifier":"openclaw/plugin-sdk/infra-runtime"}
+1 -1
View File
@@ -1 +1 @@
{"contentHash":"0559d15dde2d56ea677302ef074ddfe2c09c0dfbc2b84a5ad1e077d375f53e9a","entrypoint":"logging-core","importSpecifier":"openclaw/plugin-sdk/logging-core"}
{"contentHash":"0da8056fdf977ab74c89cbebb186895b5a1955302853a2f15784d552d7c30487","entrypoint":"logging-core","importSpecifier":"openclaw/plugin-sdk/logging-core"}
@@ -1 +1 @@
{"contentHash":"df3587c8c0300c86bcace11bcd3bd42f263ec1b527dd02fd5dded32496dd804d","entrypoint":"media-local-roots","importSpecifier":"openclaw/plugin-sdk/media-local-roots"}
{"contentHash":"654fa60879292e92a574e62daee2efdc0d26bd10dd1ba954dbf3843d8021b82a","entrypoint":"media-local-roots","importSpecifier":"openclaw/plugin-sdk/media-local-roots"}
+1 -1
View File
@@ -1 +1 @@
{"contentHash":"22eacf52b20db9ad9fe809b750911c6b137c53a4d766d7404ab60013a53ec901","entrypoint":"media-runtime","importSpecifier":"openclaw/plugin-sdk/media-runtime"}
{"contentHash":"3dbf4bdcaf3b0545cca9ac8f4dfcde947d698f6a457d51fa001974598d016c6d","entrypoint":"media-runtime","importSpecifier":"openclaw/plugin-sdk/media-runtime"}
@@ -1 +1 @@
{"contentHash":"86454131178316a2ba29ac4f871cbfe2223c71f584cfc10baac91f3541d57442","entrypoint":"media-understanding-runtime","importSpecifier":"openclaw/plugin-sdk/media-understanding-runtime"}
{"contentHash":"a7ed309dd80eff0eff856d6c24b924cb15c127e1e2e6a69279e45abbd6120aed","entrypoint":"media-understanding-runtime","importSpecifier":"openclaw/plugin-sdk/media-understanding-runtime"}
@@ -1 +1 @@
{"contentHash":"957d3fa32636f080a63823c0aec4247ed9d02baf39c7860879296c428e17d48f","entrypoint":"media-understanding","importSpecifier":"openclaw/plugin-sdk/media-understanding"}
{"contentHash":"ebfcda0b59c34586370fc234f07f80152fbc44d1c78bd4032363315826ad6c9b","entrypoint":"media-understanding","importSpecifier":"openclaw/plugin-sdk/media-understanding"}
@@ -1 +1 @@
{"contentHash":"9060d4011e1249ca8aa0b01f2e2825d71b29eff14dc84f7f702440dc01e73460","entrypoint":"meeting-runtime","importSpecifier":"openclaw/plugin-sdk/meeting-runtime"}
{"contentHash":"2811b02c2aebc989cdf8f83af563e260da85b2d3bfcbf9b71ea8c4a29a12cb5b","entrypoint":"meeting-runtime","importSpecifier":"openclaw/plugin-sdk/meeting-runtime"}
@@ -1 +1 @@
{"contentHash":"fe951309f828ff94718b092d0017d40eeabf407663bc01179ce4a49309f60773","entrypoint":"memory-core-host-engine-foundation","importSpecifier":"openclaw/plugin-sdk/memory-core-host-engine-foundation"}
{"contentHash":"d7ecd46ea448722f37a5e17628d33a30b53d22ed4e2b8daa74f22e939f12fe32","entrypoint":"memory-core-host-engine-foundation","importSpecifier":"openclaw/plugin-sdk/memory-core-host-engine-foundation"}
@@ -1 +1 @@
{"contentHash":"2dcdb96ebaa91c8e938598fad3b3f5ecbd6999802ca7528c3f3c35aedb67c454","entrypoint":"memory-host-core","importSpecifier":"openclaw/plugin-sdk/memory-host-core"}
{"contentHash":"9c179624b39b9a4e7ae1847d9ca42ebfc34668bcf73246d06ea3558591d0f4d1","entrypoint":"memory-host-core","importSpecifier":"openclaw/plugin-sdk/memory-host-core"}
@@ -1 +1 @@
{"contentHash":"034bb7d9d0e7b1d8f0176c30c409db9f9f013a1c507eea53beb5c3da1d807340","entrypoint":"model-session-runtime","importSpecifier":"openclaw/plugin-sdk/model-session-runtime"}
{"contentHash":"96db0eb66488c8b02cb903e87c72a3c15d449d03b814c1bbf625772ff9eef938","entrypoint":"model-session-runtime","importSpecifier":"openclaw/plugin-sdk/model-session-runtime"}
@@ -1 +1 @@
{"contentHash":"5afa8f742a472a31c3cbdbff8bebab674109049148bc24248874e813057a0c46","entrypoint":"models-provider-runtime","importSpecifier":"openclaw/plugin-sdk/models-provider-runtime"}
{"contentHash":"a41b2cb57c392c10e0c16d4b0f8b4d9c9df82c5b8cb1ecac6223881645ce74bb","entrypoint":"models-provider-runtime","importSpecifier":"openclaw/plugin-sdk/models-provider-runtime"}
@@ -1 +1 @@
{"contentHash":"8a51693c575603e3c263703a4162d91019e74c004c1ed26e6a6e6c590a784503","entrypoint":"native-command-config-runtime","importSpecifier":"openclaw/plugin-sdk/native-command-config-runtime"}
{"contentHash":"8f061f10edc2f699b8ad6d7c607c7d69d1a4d9a36d42989ab304c183caba3366","entrypoint":"native-command-config-runtime","importSpecifier":"openclaw/plugin-sdk/native-command-config-runtime"}
@@ -1 +1 @@
{"contentHash":"086b6a9f959c896ecbb529cd2e44054306a69c9ad4c331f323a948b524643188","entrypoint":"native-command-registry","importSpecifier":"openclaw/plugin-sdk/native-command-registry"}
{"contentHash":"b8b59df62269a2619eeb2f64a5714690124ec432d98ba23df4aa037d6971a7a1","entrypoint":"native-command-registry","importSpecifier":"openclaw/plugin-sdk/native-command-registry"}
@@ -1 +1 @@
{"contentHash":"45c491f92e76d0523e2fafc47f362620e2598a5fdef95aed50ce4e8ff108423d","entrypoint":"plugin-command-runtime","importSpecifier":"openclaw/plugin-sdk/plugin-command-runtime"}
{"contentHash":"9c0b8ea3e147d1de8173cdde492358c6780a399f7b0aa1ad3b6931fa46a78a5a","entrypoint":"plugin-command-runtime","importSpecifier":"openclaw/plugin-sdk/plugin-command-runtime"}
@@ -1 +1 @@
{"contentHash":"fc14a640d5328c2d317eef0e9e5e11f40c81f31fb3f44e4f31587f725ba0e22c","entrypoint":"plugin-config-runtime","importSpecifier":"openclaw/plugin-sdk/plugin-config-runtime"}
{"contentHash":"6ddb8caaf90a1087c7144354b07bf2b62cbc83173ffc0cad5f09cad8fc1b6d1e","entrypoint":"plugin-config-runtime","importSpecifier":"openclaw/plugin-sdk/plugin-config-runtime"}
+1 -1
View File
@@ -1 +1 @@
{"contentHash":"a36c13699a318b3fb1b6701ab3280c45e5a4f5ca982cef29778d10af8a5ae97a","entrypoint":"plugin-entry","importSpecifier":"openclaw/plugin-sdk/plugin-entry"}
{"contentHash":"f27f40ef92fc2dc6eebae6bdfc581c938fd654a76cd281f64669481ebf058684","entrypoint":"plugin-entry","importSpecifier":"openclaw/plugin-sdk/plugin-entry"}
+1 -1
View File
@@ -1 +1 @@
{"contentHash":"bdf0b57a425cac872d156021006cce6813893bee30e77c5ef4c055343697dba0","entrypoint":"plugin-runtime","importSpecifier":"openclaw/plugin-sdk/plugin-runtime"}
{"contentHash":"2095966e155ab87c76a45bf0c5ca7d82ac9a25bde638537aa8263416acb264e6","entrypoint":"plugin-runtime","importSpecifier":"openclaw/plugin-sdk/plugin-runtime"}
+1 -1
View File
@@ -1 +1 @@
{"contentHash":"2c97bf2bccffb72065e3432432788926559cdc97f77a129786c4cc91246882a4","entrypoint":"provider-auth","importSpecifier":"openclaw/plugin-sdk/provider-auth"}
{"contentHash":"1a53b2916dad0237deb2042c038fd1828957c514bf559a1639ef078ec2ff2077","entrypoint":"provider-auth","importSpecifier":"openclaw/plugin-sdk/provider-auth"}
@@ -1 +1 @@
{"contentHash":"62f6dc31a2b1789b3667fe16c2d465681aa4c01e2cd9b2a6da062fd995235963","entrypoint":"provider-catalog-runtime","importSpecifier":"openclaw/plugin-sdk/provider-catalog-runtime"}
{"contentHash":"13af1c1667ef2cb15b5a6d6770cc2c43948dda902120871015f26aed0d7a6cc5","entrypoint":"provider-catalog-runtime","importSpecifier":"openclaw/plugin-sdk/provider-catalog-runtime"}
@@ -1 +1 @@
{"contentHash":"1271e5eec56d6d72fcb47498dfa7b0eef0e25f136190f6ee103bc11930e4cdd9","entrypoint":"question-gateway-runtime","importSpecifier":"openclaw/plugin-sdk/question-gateway-runtime"}
{"contentHash":"68b4d6f2a48e54a5db9fe5655a3ca0e46351f7a4caa21d065d7ccf3b628abde3","entrypoint":"question-gateway-runtime","importSpecifier":"openclaw/plugin-sdk/question-gateway-runtime"}
+1 -1
View File
@@ -1 +1 @@
{"contentHash":"12c9cb08e664eca320085e6ab274fd70008686e709da50f8d0b1db60ed706f90","entrypoint":"reply-chunking","importSpecifier":"openclaw/plugin-sdk/reply-chunking"}
{"contentHash":"7059cece2048a14ea093cdcaf9d45ce5f578f1d8522b1b600a5aeb60376a66ae","entrypoint":"reply-chunking","importSpecifier":"openclaw/plugin-sdk/reply-chunking"}
@@ -1 +1 @@
{"contentHash":"8dc220fc0bdf4ab69de389162dfe792335b83ddc36b900868fda4d418e3e0586","entrypoint":"reply-dispatch-runtime","importSpecifier":"openclaw/plugin-sdk/reply-dispatch-runtime"}
{"contentHash":"35c993147ea09db79c62c87822fb923039f87323d244ece52d872a2b5f19a3cd","entrypoint":"reply-dispatch-runtime","importSpecifier":"openclaw/plugin-sdk/reply-dispatch-runtime"}
+1 -1
View File
@@ -1 +1 @@
{"contentHash":"560a2f2591955d9d1bf27498825ce6585ecca0528dcfecd02ea81a55e06164a1","entrypoint":"reply-payload","importSpecifier":"openclaw/plugin-sdk/reply-payload"}
{"contentHash":"aa91cf06f0ac5fb25ded9c937a3cf3e6e7ba692da0c8d4c1ceb659f13e17e706","entrypoint":"reply-payload","importSpecifier":"openclaw/plugin-sdk/reply-payload"}
+1 -1
View File
@@ -1 +1 @@
{"contentHash":"664a2e42bd0e6ed65f9f8ae72bb79a7d0b89b3bd30e0f01390f3b5f4075cbf7a","entrypoint":"reply-runtime","importSpecifier":"openclaw/plugin-sdk/reply-runtime"}
{"contentHash":"88e867535fca9357f158882f981694d76d9d29034b1a591c66a709c7e3e7a9fe","entrypoint":"reply-runtime","importSpecifier":"openclaw/plugin-sdk/reply-runtime"}

Some files were not shown because too many files have changed in this diff Show More