mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-23 19:08:22 -06:00
Merge remote-tracking branch 'origin/main' into fix/inline-slash-skills
This commit is contained in:
@@ -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}`);
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
+350
-358
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)
|
||||
|
||||
@@ -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!",
|
||||
|
||||
@@ -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,5 +1,5 @@
|
||||
{
|
||||
"core": 2306,
|
||||
"core": 2308,
|
||||
"channel": 3575,
|
||||
"plugin": 3997
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -1 +1 @@
|
||||
{"contentHash":"3f25999ca07382c0014dd474bffffa2410b2e3c403cfba58641eae8d25f7b20c","entrypoint":"health","importSpecifier":"openclaw/plugin-sdk/health"}
|
||||
{"contentHash":"5305c7c75c86c24e1b445370136422abf1804f85a0a31294ee03af7c38d902ee","entrypoint":"health","importSpecifier":"openclaw/plugin-sdk/health"}
|
||||
|
||||
+1
-1
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
Reference in New Issue
Block a user