Compare commits

..

1 Commits

Author SHA1 Message Date
Tak Hoffman
3269e1ad7e Config: add compaction identifier policy with strict default 2026-02-27 07:58:59 -06:00
72 changed files with 340 additions and 1771 deletions

View File

@@ -7,7 +7,6 @@ registries:
npm-npmjs:
type: npm-registry
url: https://registry.npmjs.org
token: ${{secrets.NPM_NPMJS_TOKEN}}
replaces-base: true
updates:
@@ -15,9 +14,9 @@ updates:
- package-ecosystem: npm
directory: /
schedule:
interval: daily
interval: weekly
cooldown:
default-days: 2
default-days: 7
groups:
production:
dependency-type: production
@@ -37,9 +36,9 @@ updates:
- package-ecosystem: github-actions
directory: /
schedule:
interval: daily
interval: weekly
cooldown:
default-days: 2
default-days: 7
groups:
actions:
patterns:
@@ -53,9 +52,9 @@ updates:
- package-ecosystem: swift
directory: /apps/macos
schedule:
interval: daily
interval: weekly
cooldown:
default-days: 2
default-days: 7
groups:
swift-deps:
patterns:
@@ -69,9 +68,9 @@ updates:
- package-ecosystem: swift
directory: /apps/shared/MoltbotKit
schedule:
interval: daily
interval: weekly
cooldown:
default-days: 2
default-days: 7
groups:
swift-deps:
patterns:
@@ -85,9 +84,9 @@ updates:
- package-ecosystem: swift
directory: /Swabble
schedule:
interval: daily
interval: weekly
cooldown:
default-days: 2
default-days: 7
groups:
swift-deps:
patterns:
@@ -101,9 +100,9 @@ updates:
- package-ecosystem: gradle
directory: /apps/android
schedule:
interval: daily
interval: weekly
cooldown:
default-days: 2
default-days: 7
groups:
android-deps:
patterns:
@@ -119,7 +118,7 @@ updates:
schedule:
interval: weekly
cooldown:
default-days: 2
default-days: 7
groups:
docker-images:
patterns:

View File

@@ -404,7 +404,6 @@ jobs:
needs: [docs-scope, changed-scope, build-artifacts, check]
if: needs.docs-scope.outputs.docs_only != 'true' && (github.event_name == 'push' || needs.changed-scope.outputs.run_node == 'true')
runs-on: blacksmith-16vcpu-windows-2025
timeout-minutes: 45
env:
NODE_OPTIONS: --max-old-space-size=4096
# Keep total concurrency predictable on the 16 vCPU runner:

View File

@@ -2,51 +2,32 @@
Docs: https://docs.openclaw.ai
## 2026.2.27
### Changes
- Web UI/i18n: add German (`de`) locale support and auto-render language options from supported locale constants in Overview settings. (#28495) thanks @dsantoreis.
- Discord/Thread bindings: replace fixed TTL lifecycle with inactivity (`idleHours`, default 24h) plus optional hard `maxAgeHours` lifecycle controls, and add `/session idle` + `/session max-age` commands for focused thread-bound sessions. (#27845) Thanks @osolmaz.
- Android/Nodes: add `camera.list`, `device.permissions`, `device.health`, and `notifications.actions` (`open`/`dismiss`/`reply`) on Android nodes, plus first-class node-tool actions for the new device/notification commands. (#28260) Thanks @obviyus.
- Android/Gateway capability refresh: add live Android capability integration coverage and node canvas capability refresh wiring, plus runtime hardening for A2UI readiness retries, scoped canvas URL normalization, debug diagnostics JSON, and JavaScript MIME delivery. (#28388) Thanks @obviyus.
### Fixes
- Telegram/Reply media context: include replied media files in inbound context when replying to media, defer reply-media downloads to debounce flush, gate reply-media fetch behind DM authorization, and preserve replied media when non-vision sticker fallback runs (including cached-sticker paths). (#28488) Thanks @obviyus.
- Gateway/WS: close repeated post-handshake `unauthorized role:*` request floods per connection and sample duplicate rejection logs, preventing a single misbehaving client from degrading gateway responsiveness. (#20168) Thanks @acy103, @vibecodooor, and @vincentkoc.
- Gateway/Auth: improve device-auth v2 migration diagnostics so operators get clearer guidance when legacy clients connect. (#28305) Thanks @vincentkoc.
- CLI/Install: add an npm-link fallback to fix CLI startup `Permission denied` failures (`exit 127`) on affected installs. (#17151) Thanks @sskyu and @vincentkoc.
- Agents/Ollama: demote empty-discovery logging from `warn` to `debug` to reduce noisy warnings in normal edge-case discovery flows. (#26379) Thanks @byungsker.
- Install/npm: fix npm global install deprecation warnings. (#28318) Thanks @vincentkoc.
- Android/Nodes reliability: reject `facing=both` when `deviceId` is set to avoid mislabeled duplicate captures, allow notification `open`/`reply` on non-clearable entries while still gating dismiss, trigger listener rebind before notification actions, and scale invoke-result ack timeout to invoke budget for large clip payloads. (#28260) Thanks @obviyus.
- Android/Camera clip: remove `camera.clip` HTTP-upload fallback to base64 so clip transport is deterministic and fail-loud, and reject non-positive `maxWidth` values so invalid inputs fall back to the safe resize default. (#28229) Thanks @obviyus.
- Android/Gateway canvas capability refresh: send `node.canvas.capability.refresh` with object `params` (`{}`) from Android node runtime so gateway object-schema validation accepts refresh retries and A2UI host recovery works after scoped capability expiry. (#28413) Thanks @obviyus.
- Daemon/macOS TLS certs: default LaunchAgent service env `NODE_EXTRA_CA_CERTS` to `/etc/ssl/cert.pem` (while preserving explicit overrides) so HTTPS clients no longer fail with local-issuer errors under launchd. (#27915) Thanks @Lukavyi.
- Update/Global npm: fallback to `--omit=optional` when global `npm update` fails so optional dependency install failures no longer abort update flows. (#24896) Thanks @xinhuagu and @vincentkoc.
- Plugins/NPM spec install: fix npm-spec plugin installs when `npm pack` output is empty by detecting newly created `.tgz` archives in the pack directory. (#21039) Thanks @graysurf and @vincentkoc.
- Plugins/Install: clear stale install errors when an npm package is not found so follow-up install attempts report current state correctly. (#25073) Thanks @dalefrieswthat.
- OpenAI Responses/Compaction: rewrite and unify the OpenAI Responses store patches to treat empty `baseUrl` as non-direct, honor `compat.supportsStore=false`, and auto-inject server-side compaction `context_management` for compatible direct OpenAI models (with per-model opt-out/threshold overrides). Landed from contributor PRs #16930 (@OiPunk), #22441 (@EdwardWu7), and #25088 (@MoerAI). Thanks @OiPunk, @EdwardWu7, and @MoerAI.
## 2026.2.26
### Changes
- Highlight: External Secrets Management introduces a full `openclaw secrets` workflow (`audit`, `configure`, `apply`, `reload`) with runtime snapshot activation, strict `secrets apply` target-path validation, safer migration scrubbing, ref-only auth-profile support, and dedicated docs. (#26155) Thanks @joshavant.
- ACP/Thread-bound agents: make ACP agents first-class runtimes for thread sessions with `acp` spawn/send dispatch integration, acpx backend bridging, lifecycle controls, startup reconciliation, runtime cleanup, and coalesced thread replies. (#23580) thanks @osolmaz.
- Discord/Thread bindings: replace fixed TTL lifecycle with inactivity (`idleHours`, default 24h) plus optional hard `maxAgeHours` lifecycle controls, and add `/session idle` + `/session max-age` commands for focused thread-bound sessions. (#27845) Thanks @osolmaz.
- Agents/Routing CLI: add `openclaw agents bindings`, `openclaw agents bind`, and `openclaw agents unbind` for account-scoped route management, including channel-only to account-scoped binding upgrades, role-aware binding identity handling, plugin-resolved binding account IDs, and optional account-binding prompts in `openclaw channels add`. (#27195) thanks @gumadeiras.
- Codex/WebSocket transport: make `openai-codex` WebSocket-first by default (`transport: "auto"` with SSE fallback), keep explicit per-model/runtime transport overrides, and add regression coverage + docs for transport selection.
- Onboarding/Plugins: let channel plugins own interactive onboarding flows with optional `configureInteractive` and `configureWhenConfigured` hooks while preserving the generic fallback path. (#27191) thanks @gumadeiras.
- Auth/Onboarding: add an explicit account-risk warning and confirmation gate before starting Gemini CLI OAuth, and document the caution in provider docs and the Gemini CLI auth plugin README. (#16683) Thanks @vincentkoc.
- Android/Nodes: add Android `device` capability plus `device.status` and `device.info` node commands, including runtime handler wiring and protocol/registry coverage for device status/info payloads. (#27664) Thanks @obviyus.
- Android/Nodes: add `notifications.list` support on Android nodes and expose `nodes notifications_list` in agent tooling for listing active device notifications. (#27344) thanks @obviyus.
- Android/Nodes: add `camera.list`, `device.permissions`, `device.health`, and `notifications.actions` (`open`/`dismiss`/`reply`) on Android nodes, plus first-class node-tool actions for the new device/notification commands. (#28260) Thanks @obviyus.
- Android/Gateway capability refresh: add live Android capability integration coverage and node canvas capability refresh wiring, plus runtime hardening for A2UI readiness retries, scoped canvas URL normalization, debug diagnostics JSON, and JavaScript MIME delivery. (#28388) Thanks @obviyus.
- Docs/Contributing: add Nimrod Gutman to the maintainer roster in `CONTRIBUTING.md`. (#27840) Thanks @ngutman.
- Web UI/i18n: add German (`de`) locale support and auto-render language options from supported locale constants in Overview settings. (#28495) thanks @dsantoreis.
### Fixes
- Telegram/DM allowlist runtime inheritance: enforce `dmPolicy: "allowlist"` `allowFrom` requirements using effective account-plus-parent config across account-capable channels (Telegram, Discord, Slack, Signal, iMessage, IRC, BlueBubbles, WhatsApp), and align `openclaw doctor` checks to the same inheritance logic so DM traffic is not silently dropped after upgrades. (#27936) Thanks @widingmarcus-cyber.
- Delivery queue/recovery backoff: prevent retry starvation by persisting `lastAttemptAt` on failed sends and deferring recovery retries until each entry's `lastAttemptAt + backoff` window is eligible, while continuing to recover ready entries behind deferred ones. Landed from contributor PR #27710 by @Jimmy-xuzimo. Thanks @Jimmy-xuzimo.
- Gemini OAuth/Auth flow: align OAuth project discovery metadata and endpoint fallback handling for Gemini CLI auth, including fallback coverage for environment-provided project IDs. (#16684) Thanks @vincentkoc.
- Update/Global npm: fallback to `--omit=optional` when global `npm update` fails so optional dependency install failures no longer abort update flows. (#24896) Thanks @xinhuagu and @vincentkoc.
- Plugins/NPM spec install: fix npm-spec plugin installs when `npm pack` output is empty by detecting newly created `.tgz` archives in the pack directory. (#21039) Thanks @graysurf and @vincentkoc.
- Plugins/Install: clear stale install errors when an npm package is not found so follow-up install attempts report current state correctly. (#25073) Thanks @dalefrieswthat.
- Google Chat/Lifecycle: keep Google Chat `startAccount` pending until abort in webhook mode so startup is no longer interpreted as immediate exit, preventing auto-restart loops and webhook-target churn. (#27384) thanks @junsuwhy.
- Temp dirs/Linux umask: force `0700` permissions after temp-dir creation and self-heal existing writable temp dirs before trust checks so `umask 0002` installs no longer crash-loop on startup. Landed from contributor PR #27860 by @stakeswky. (#27853) Thanks @stakeswky.
- Nextcloud Talk/Lifecycle: keep `startAccount` pending until abort and stop the webhook monitor on shutdown, preventing `EADDRINUSE` restart loops when the gateway manages account lifecycle. (#27897)
@@ -57,6 +38,7 @@ Docs: https://docs.openclaw.ai
- Typing/Cross-channel leakage: unify run-scoped typing suppression for cross-channel/internal-webchat routes, preserve current inbound origin as embedded run message channel context, harden shared typing keepalive with consecutive-failure circuit breaker edge-case handling, and enforce dispatcher completion/idle waits in extension dispatcher callsites (Feishu, Matrix, Mattermost, MSTeams) so typing indicators always clean up on success/error paths. Related: #27647, #27493, #27598. Supersedes/replaces draft PRs: #27640, #27593, #27540.
- Telegram/sendChatAction 401 handling: add bounded exponential backoff + temporary local typing suppression after repeated unauthorized failures to stop unbounded `sendChatAction` retry loops that can trigger Telegram abuse enforcement and bot deletion. (#27415) Thanks @widingmarcus-cyber.
- Telegram/Webhook startup: clarify webhook config guidance, allow `channels.telegram.webhookPort: 0` for ephemeral listener binding, and log both the local listener URL and Telegram-advertised webhook URL with the bound port. (#25732) thanks @huntharo.
- Telegram/Reply media context: include replied media files in inbound context when replying to media, defer reply-media downloads to debounce flush, gate reply-media fetch behind DM authorization, and preserve replied media when non-vision sticker fallback runs (including cached-sticker paths). (#28488) Thanks @obviyus.
- Config/Doctor allowlist safety: reject `dmPolicy: "allowlist"` configs with empty `allowFrom`, add Telegram account-level inheritance-aware validation, and teach `openclaw doctor --fix` to restore missing `allowFrom` entries from pairing-store files when present, preventing silent DM drops after upgrades. (#27936) Thanks @widingmarcus-cyber.
- Browser/Chrome extension handshake: bind relay WS message handling before `onopen` and add non-blocking `connect.challenge` response handling for gateway-style handshake frames, avoiding stuck `…` badge states when challenge frames arrive immediately on connect. Landed from contributor PR #22571 by @pandego. (#22553)
- Browser/Extension relay init: dedupe concurrent same-port relay startup with shared in-flight initialization promises so callers await one startup lifecycle and receive consistent success/failure results. Landed from contributor PR #21277 by @HOYALIM. (Related #20688)
@@ -95,6 +77,7 @@ Docs: https://docs.openclaw.ai
- CLI/Daemon status TLS probe: use `wss://` and forward local TLS certificate fingerprint for TLS-enabled gateway daemon probes so `openclaw daemon status` works with `gateway.bind=lan` + `gateway.tls.enabled=true`. (#24234) thanks @liuy.
- Podman/Default bind: change `run-openclaw-podman.sh` default gateway bind from `lan` to `loopback` and document explicit LAN opt-in with Control UI origin configuration. (#27491) thanks @robbyczgw-cla.
- Daemon/macOS launchd: forward proxy env vars into supervised service environments, keep LaunchAgent `KeepAlive=true` semantics, and harden restart sequencing to `print -> bootout -> wait old pid exit -> bootstrap -> kickstart`. (#27276) thanks @frankekn.
- Daemon/macOS TLS certs: default LaunchAgent service env `NODE_EXTRA_CA_CERTS` to `/etc/ssl/cert.pem` (while preserving explicit overrides) so HTTPS clients no longer fail with local-issuer errors under launchd. (#27915) Thanks @Lukavyi.
- Gateway/macOS restart-loop hardening: detect OpenClaw-managed supervisor markers during SIGUSR1 restart handoff, clean stale gateway PIDs before `/restart` launchctl/systemctl triggers, and set LaunchAgent `ThrottleInterval=60` to bound launchd retry storms during lock-release races. Landed from contributor PRs #27655 (@taw0002), #27448 (@Sid-Qin), and #27650 (@kevinWangSheng). (#27605, #27590, #26904, #26736)
- Models/MiniMax auth header defaults: set `authHeader: true` for both onboarding-generated MiniMax API providers and implicit built-in MiniMax (`minimax`, `minimax-portal`) provider templates so first requests no longer fail with MiniMax `401 authentication_error` due to missing `Authorization` header. Landed from contributor PRs #27622 by @riccoyuanft and #27631 by @kevinWangSheng. (#27600, #15303)
- Models/Google Antigravity IDs: normalize bare `gemini-3-pro`, `gemini-3.1-pro`, and `gemini-3-1-pro` model IDs to the default `-low` thinking tier so provider requests no longer fail with 404 when the tier suffix is omitted. (#24145) Thanks @byungsker.
@@ -115,11 +98,13 @@ Docs: https://docs.openclaw.ai
- Security/Voice Call (Twilio): bind webhook replay + manager dedupe identity to authenticated request material, remove unsigned `i-twilio-idempotency-token` trust from replay/dedupe keys, and thread verified request identity through provider parse flow to harden cross-provider event dedupe. This ships in the next npm release (`2026.2.26`). Thanks @tdjackey for reporting.
- Security/Exec approvals forwarding: prefer turn-source channel/account/thread metadata when resolving approval delivery targets so stale session routes do not misroute approval prompts.
- Security/Pairing multi-account isolation: enforce account-scoped pairing allowlists and pending-request storage across core + extension message channels while preserving channel-scoped defaults for the default account. This ships in the next npm release (`2026.2.26`). Thanks @tdjackey for reporting and @gumadeiras for implementation.
- Memory/SQLite: deduplicate concurrent memory-manager initialization and auto-reopen stale SQLite handles after atomic reindex swaps, preventing repeated `attempt to write a readonly database` sync failures until gateway restart.
- Config/Plugins entries: treat unknown `plugins.entries.*` ids as startup warnings (ignored stale keys) instead of hard validation failures that can crash-loop gateway boot. Landed from contributor PR #27506 by @Sid-Qin. (#27455)
- Telegram native commands: degrade command registration on `BOT_COMMANDS_TOO_MUCH` by retrying with fewer commands instead of crash-looping startup sync. Landed from contributor PR #27512 by @Sid-Qin. (#27456)
- Web tools/Proxy: route `web_search` provider HTTP calls (Brave, Perplexity, xAI, Gemini, Kimi), redirect resolution, and `web_fetch` through a shared proxy-aware SSRF guard path so gateway installs behind `HTTP_PROXY`/`HTTPS_PROXY`/`ALL_PROXY` no longer fail with transport `fetch failed` errors. (#27430) thanks @kevinWangSheng.
- Android/Node invoke: remove native gateway WebSocket `Origin` header to avoid false origin rejections, unify invoke command registry/policy/error parsing paths, and keep command availability checks centralized to reduce dispatcher/advertisement drift. (#27257) Thanks @obviyus.
- Android/Camera clip: remove `camera.clip` HTTP-upload fallback to base64 so clip transport is deterministic and fail-loud, and reject non-positive `maxWidth` values so invalid inputs fall back to the safe resize default. (#28229) Thanks @obviyus.
- Android/Nodes reliability: reject `facing=both` when `deviceId` is set to avoid mislabeled duplicate captures, allow notification `open`/`reply` on non-clearable entries while still gating dismiss, trigger listener rebind before notification actions, and scale invoke-result ack timeout to invoke budget for large clip payloads. (#28260) Thanks @obviyus.
- Android/Gateway canvas capability refresh: send `node.canvas.capability.refresh` with object `params` (`{}`) from Android node runtime so gateway object-schema validation accepts refresh retries and A2UI host recovery works after scoped capability expiry. (#28413) Thanks @obviyus.
- Gateway shared-auth scopes: preserve requested operator scopes for shared-token clients when device identity is unavailable, instead of clearing scopes during auth handling. Landed from contributor PR #27498 by @kevinWangSheng. (#27494)
- Cron/Hooks isolated routing: preserve canonical `agent:*` session keys in isolated runs so already-qualified keys are not double-prefixed (for example `agent:main:main` no longer becomes `agent:main:agent:main:main`). Landed from contributor PR #27333 by @MaheshBhushan. (#27289, #27282)
- Channels/Multi-account config: when adding a non-default channel account to a single-account top-level channel setup, move existing account-scoped top-level single-account values into `channels.<channel>.accounts.default` before writing the new account so the original account keeps working without duplicated account values at channel root; `openclaw doctor --fix` now repairs previously mixed channel account shapes the same way. (#27334) thanks @gumadeiras.
@@ -306,7 +291,6 @@ Docs: https://docs.openclaw.ai
- Exec approvals: treat bare allowlist `*` as a true wildcard for parsed executables, including unresolved PATH lookups, so global opt-in allowlists work as configured. (#25250) Thanks @widingmarcus-cyber.
- Gateway/Auth: allow trusted-proxy authenticated Control UI websocket sessions to skip device pairing when device identity is absent, preventing false `pairing required` failures behind trusted reverse proxies. (#25428) Thanks @SidQin-cyber.
- Agents/Tool dispatch: await block-reply flush before tool execution starts so buffered block replies preserve message ordering around tool calls. (#25427) Thanks @SidQin-cyber.
- Agents/Compaction: harden summarization prompts to preserve opaque identifiers verbatim (UUIDs, IDs, tokens, host/IP/port, URLs), reducing post-compaction identifier drift and hallucinated identifier reconstruction.
- iOS/Signing: improve `scripts/ios-team-id.sh` for Xcode 16+ by falling back to Xcode-managed provisioning profiles, add actionable guidance when an Apple account exists but no Team ID can be resolved, and ignore Xcode `xcodebuild` output directories (`apps/ios/build`, `apps/shared/OpenClawKit/build`, `Swabble/build`). (#22773) Thanks @brianleach.
- macOS/Menu bar: stop reusing the injector delegate for the "Usage cost (30 days)" submenu to prevent recursive submenu injection loops when opening cost history. (#25341) Thanks @yingchunbai.
- Control UI/Chat images: route image-click opens through a shared safe-open helper (allowing only safe URL schemes) and open new tabs with opener isolation to block tabnabbing. (#18685, #25444, #25847) Thanks @Mariana-Codebase and @shakkernerd.

View File

@@ -58,9 +58,6 @@ Welcome to the lobster tank! 🦞
- **Jonathan Taylor** - ACP subsystem, Gateway features/bugs, Gog/Mog/Sog CLI's, SEDMAT
- Github [@visionik](https://github.com/visionik) · X: [@visionik](https://x.com/visionik)
- **Josh Lehman** - Compaction, Tlon/Urbit subsystem
- Github [@jalehman](https://github.com/jalehman) · X: [@jlehman_](https://x.com/jlehman_)
## How to Contribute

View File

@@ -20,8 +20,8 @@ android {
applicationId = "ai.openclaw.android"
minSdk = 31
targetSdk = 36
versionCode = 202602270
versionName = "2026.2.27"
versionCode = 202602260
versionName = "2026.2.26"
ndk {
// Support all major ABIs — native libs are tiny (~47 KB per ABI)
abiFilters += listOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64")

View File

@@ -17,9 +17,9 @@
<key>CFBundlePackageType</key>
<string>XPC!</string>
<key>CFBundleShortVersionString</key>
<string>2026.2.27</string>
<string>2026.2.26</string>
<key>CFBundleVersion</key>
<string>20260227</string>
<string>20260226</string>
<key>NSExtension</key>
<dict>
<key>NSExtensionAttributes</key>

View File

@@ -19,7 +19,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>2026.2.27</string>
<string>2026.2.26</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
@@ -32,7 +32,7 @@
</dict>
</array>
<key>CFBundleVersion</key>
<string>20260227</string>
<string>20260226</string>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoadsInWebContent</key>

View File

@@ -17,8 +17,8 @@
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleShortVersionString</key>
<string>2026.2.27</string>
<string>2026.2.26</string>
<key>CFBundleVersion</key>
<string>20260227</string>
<string>20260226</string>
</dict>
</plist>

View File

@@ -17,9 +17,9 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>2026.2.27</string>
<string>2026.2.26</string>
<key>CFBundleVersion</key>
<string>20260227</string>
<string>20260226</string>
<key>WKCompanionAppBundleIdentifier</key>
<string>$(OPENCLAW_APP_BUNDLE_ID)</string>
<key>WKWatchKitApp</key>

View File

@@ -15,9 +15,9 @@
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundleShortVersionString</key>
<string>2026.2.27</string>
<string>2026.2.26</string>
<key>CFBundleVersion</key>
<string>20260227</string>
<string>20260226</string>
<key>NSExtension</key>
<dict>
<key>NSExtensionAttributes</key>

View File

@@ -92,8 +92,8 @@ targets:
- CFBundleURLName: ai.openclaw.ios
CFBundleURLSchemes:
- openclaw
CFBundleShortVersionString: "2026.2.27"
CFBundleVersion: "20260227"
CFBundleShortVersionString: "2026.2.26"
CFBundleVersion: "20260226"
UILaunchScreen: {}
UIApplicationSceneManifest:
UIApplicationSupportsMultipleScenes: false
@@ -148,8 +148,8 @@ targets:
path: ShareExtension/Info.plist
properties:
CFBundleDisplayName: OpenClaw Share
CFBundleShortVersionString: "2026.2.27"
CFBundleVersion: "20260227"
CFBundleShortVersionString: "2026.2.26"
CFBundleVersion: "20260226"
NSExtension:
NSExtensionPointIdentifier: com.apple.share-services
NSExtensionPrincipalClass: "$(PRODUCT_MODULE_NAME).ShareViewController"
@@ -179,8 +179,8 @@ targets:
path: WatchApp/Info.plist
properties:
CFBundleDisplayName: OpenClaw
CFBundleShortVersionString: "2026.2.27"
CFBundleVersion: "20260227"
CFBundleShortVersionString: "2026.2.26"
CFBundleVersion: "20260226"
WKCompanionAppBundleIdentifier: "$(OPENCLAW_APP_BUNDLE_ID)"
WKWatchKitApp: true
@@ -203,8 +203,8 @@ targets:
path: WatchExtension/Info.plist
properties:
CFBundleDisplayName: OpenClaw
CFBundleShortVersionString: "2026.2.27"
CFBundleVersion: "20260227"
CFBundleShortVersionString: "2026.2.26"
CFBundleVersion: "20260226"
NSExtension:
NSExtensionAttributes:
WKAppBundleIdentifier: "$(OPENCLAW_WATCH_APP_BUNDLE_ID)"
@@ -237,5 +237,5 @@ targets:
path: Tests/Info.plist
properties:
CFBundleDisplayName: OpenClawTests
CFBundleShortVersionString: "2026.2.27"
CFBundleVersion: "20260227"
CFBundleShortVersionString: "2026.2.26"
CFBundleVersion: "20260226"

View File

@@ -15,9 +15,9 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>2026.2.27</string>
<string>2026.2.26</string>
<key>CFBundleVersion</key>
<string>202602270</string>
<string>202602260</string>
<key>CFBundleIconFile</key>
<string>OpenClaw</string>
<key>CFBundleURLTypes</key>

View File

@@ -40,61 +40,6 @@ It warns when `gateway.auth.mode="none"` leaves Gateway HTTP APIs reachable with
Settings prefixed with `dangerous`/`dangerously` are explicit break-glass operator overrides; enabling one is not, by itself, a security vulnerability report.
For the complete dangerous-parameter inventory, see the "Insecure or dangerous flags summary" section in [Security](/gateway/security).
## Skill security
Community skills (installed from ClawHub) are subject to additional security enforcement:
- **SKILL.md scanning**: content is scanned for prompt injection patterns, capability inflation, and boundary spoofing before entering the system prompt. Skills with critical findings are blocked from loading.
- **Capability declarations**: community skills should declare `capabilities` (e.g., `shell`, `network`) in frontmatter for visibility and policy checks.
- **Current rollout scope**: command-dispatch safety checks and SKILL.md scanning are active in this phase; broader runtime capability gating is rolling out in stages.
- **Command dispatch gating**: community skills using `command-dispatch: tool` can't dispatch to dangerous tools without the matching capability.
- **Audit logging**: all security events are tagged with `category: "security"` and include session context for forensics. View in the web UI Logs tab using the Security filter.
See `openclaw skills check` for a runtime security overview, `openclaw skills info <name>` for per-skill details, and [Skills — Tool enforcement matrix](/tools/skills#tool-enforcement-matrix) for the complete tool-by-tool breakdown.
### Tool enforcement matrix
Every tool falls into one of three tiers when community skills are loaded:
**Always denied** — blocked unconditionally, no capability can override:
| Tool | Reason |
| --------- | --------------------------------------------------------------- |
| `gateway` | Control-plane reconfiguration (restart, shutdown, auth changes) |
| `nodes` | Cluster node management (add/remove compute, redirect traffic) |
**Capability-gated** — blocked by default, allowed if the skill declares the matching capability:
| Capability | Tools | What it unlocks |
| ------------ | ---------------------------------------------- | --------------------------------------- |
| `shell` | `exec`, `process` | Run shell commands and manage processes |
| `filesystem` | `write`, `edit`, `apply_patch` | File mutations (read is always allowed) |
| `network` | `web_fetch`, `web_search` | Outbound HTTP requests |
| `browser` | `browser` | Browser automation |
| `sessions` | `sessions_spawn`, `sessions_send`, `subagents` | Cross-session orchestration |
| `messaging` | `message` | Send messages to configured channels |
| `scheduling` | `cron` | Schedule recurring jobs |
**Always allowed** — safe read-only or output-only tools, no capability required:
| Tool | Why safe |
| ----------------------------------------------------- | --------------------------------- |
| `read` | Read-only file access |
| `memory_search`, `memory_get` | Read-only memory access |
| `agents_list` | List agents (read-only) |
| `sessions_list`, `sessions_history`, `session_status` | Session introspection (read-only) |
| `canvas` | UI rendering (output-only) |
| `image` | Image generation (output-only) |
| `tts` | Text-to-speech (output-only) |
A community skill with no capabilities declared gets access only to the always-allowed tier. Declare capabilities in SKILL.md frontmatter:
```yaml
metadata:
openclaw:
capabilities: [shell, filesystem, network]
```
## JSON output
Use `--json` for CI/policy checks:

View File

@@ -18,175 +18,9 @@ Related:
## Commands
Quick command list:
```bash
openclaw skills list
openclaw skills list --eligible
openclaw skills info <name>
openclaw skills check
openclaw skills check --json
```
### `openclaw skills list`
List all skills with status, capabilities, and source.
```bash
openclaw skills list # all skills
openclaw skills list --eligible # only ready-to-use skills
openclaw skills list --json # JSON output
openclaw skills list -v # verbose (show missing requirements)
```
Output columns: **Status** (`+ ready`, `x missing`, `x blocked`), **Skill** (name + capability icons), **Description**, **Source**.
Capability icons displayed next to skill names:
| Icon | Capability |
| ---- | ---------------------------------------- |
| `>_` | `shell` — run shell commands |
| `📂` | `filesystem` — read/write files |
| `🌐` | `network` — outbound HTTP |
| `🔍` | `browser` — browser automation |
| `⚡` | `sessions` — cross-session orchestration |
| `✉️` | `messaging` — send channel messages |
| `⏰` | `scheduling` — recurring jobs |
Skills blocked by security scanning show `x blocked` instead of `x missing`.
Example output:
```
Skills (10/12 ready)
Status Skill Description Source
+ ready git-autopush >_ 🌐 Automate git workflows openclaw-managed
+ ready think Extended thinking bundled
+ ready peekaboo 🔍 ⚡ Browser peek and screenshot bundled
x missing summarize >_ Summarize with CLI tool bundled
x blocked evil-injector >_ Totally harmless skill openclaw-managed
- disabled old-skill Deprecated skill workspace
```
With `-v` (verbose), the **Missing** column appears:
```
Status Skill Description Source Missing
+ ready git-autopush >_ 🌐 Automate git wor... openclaw-managed
x missing summarize >_ Summarize with... bundled bins: summarize
x blocked evil-injector >_ Totally harmless... openclaw-managed
+ ready sketch-tool 🌐 >_ Generate sketches openclaw-managed
```
### `openclaw skills info <name>`
Show detailed information about a single skill including security status.
```bash
openclaw skills info git-helper
openclaw skills info git-helper --json
```
Displays: description, source, file path, capabilities (with descriptions), security scan results, requirements (met/unmet), and install options.
Example output:
```
git-autopush + Ready
Automate git commit, push, and PR workflows.
Source openclaw-managed
Path ~/.openclaw/skills/git-autopush/SKILL.md
Homepage https://github.com/example/git-autopush
Primary env GH_TOKEN
Capabilities
>_ shell Run shell commands
🌐 network Make outbound HTTP requests
Security
Scan + clean
Requirements
bin git + ok
bin gh + ok
env GH_TOKEN + ok
```
For a skill with missing requirements:
```
summarize x Missing requirements
Summarize URLs and files using the summarize CLI.
Source bundled
Path /opt/openclaw/skills/summarize/SKILL.md
Capabilities
>_ shell Run shell commands
Security
Scan + clean
Requirements
bin summarize x missing
Install options
brew Install summarize (brew install summarize)
```
For a skill blocked by scanning:
```
evil-injector x Blocked (security)
Totally harmless skill.
Source openclaw-managed
Path ~/.openclaw/skills/evil-injector/SKILL.md
Capabilities
>_ shell Run shell commands
Security
Scan [blocked] prompt injection detected
```
### `openclaw skills check`
Security-focused overview of all skills.
```bash
openclaw skills check
openclaw skills check --json
```
Shows: total/eligible/disabled/blocked/missing counts, capabilities requested by community skills, runtime policy restrictions, and scan result summary.
Example output:
```
Skills Status Check
Status Count
Total 12
Eligible 10
Disabled 1
Blocked (allowlist) 0
Missing requirements 1
Community skill capabilities
Icon Capability # Skills
>_ shell 3 git-autopush, deploy-helper, node-runner
📂 filesystem 2 git-autopush, file-editor
🌐 network 2 git-autopush, sketch-tool
Scan results
Result #
Clean 11
Warning 1
Blocked 0
```

View File

@@ -55,18 +55,6 @@ Context window is model-specific. OpenClaw uses the model definition from the co
See [/concepts/session-pruning](/concepts/session-pruning) for pruning details.
## OpenAI server-side compaction
OpenClaw also supports OpenAI Responses server-side compaction hints for
compatible direct OpenAI models. This is separate from local OpenClaw
compaction and can run alongside it.
- Local compaction: OpenClaw summarizes and persists into session JSONL.
- Server-side compaction: OpenAI compacts context on the provider side when
`store` + `context_management` are enabled.
See [OpenAI provider](/providers/openai) for model params and overrides.
## Tips
- Use `/compact` when sessions feel stale or context is bloated.

View File

@@ -373,14 +373,6 @@ OpenClaw can refresh the skills list mid-session:
- **Skills watcher**: changes to `SKILL.md` can update the skills snapshot on the next agent turn.
- **Remote nodes**: connecting a macOS node can make macOS-only skills eligible (based on bin probing).
Community skills (installed from ClawHub) are subject to runtime security controls:
- **Capabilities**: skills declare required system access (`shell`, `filesystem`, `network`, `browser`, `sessions`, `messaging`, `scheduling`) in `metadata.openclaw.capabilities`. No capabilities means read-only metadata declaration; capability rollout is staged and currently used for visibility and policy checks.
- **SKILL.md scanning**: content is scanned for prompt injection patterns, capability inflation, and boundary spoofing before entering the system prompt. Skills with critical findings are blocked from loading.
- **Trust tiers**: `community` skills are enforced, while `builtin` and local/workspace skills are treated as trusted by default.
- **Command dispatch gating**: community skills using `command-dispatch: tool` cannot dispatch to dangerous tools without declaring the matching capability.
- **Audit logging**: security events are tagged with `category: "security"` and include session context.
Treat skill folders as **trusted code** and restrict who can modify them.
## The Threat Model
@@ -694,10 +686,10 @@ Set a token so **all** WS clients must authenticate:
Doctor can generate one for you: `openclaw doctor --generate-gateway-token`.
Note: in local mode, OpenClaw still accepts `gateway.remote.token` /
`gateway.remote.password` as fallback credentials when `gateway.auth.*` is
unset. Prefer setting `gateway.auth.token` (or password mode) explicitly so
auth behavior is clear.
Note: `gateway.remote.token` / `.password` are client credential sources. They
do **not** protect local WS access by themselves.
Local call paths can use `gateway.remote.*` as fallback when `gateway.auth.*`
is unset.
Optional: pin remote TLS with `gateway.remote.tlsFingerprint` when using `wss://`.
Local device pairing:

View File

@@ -34,17 +34,17 @@ Notes:
# From repo root; set release IDs so Sparkle feed is enabled.
# APP_BUILD must be numeric + monotonic for Sparkle compare.
BUNDLE_ID=ai.openclaw.mac \
APP_VERSION=2026.2.27 \
APP_VERSION=2026.2.26 \
APP_BUILD="$(git rev-list --count HEAD)" \
BUILD_CONFIG=release \
SIGN_IDENTITY="Developer ID Application: <Developer Name> (<TEAMID>)" \
scripts/package-mac-app.sh
# Zip for distribution (includes resource forks for Sparkle delta support)
ditto -c -k --sequesterRsrc --keepParent dist/OpenClaw.app dist/OpenClaw-2026.2.27.zip
ditto -c -k --sequesterRsrc --keepParent dist/OpenClaw.app dist/OpenClaw-2026.2.26.zip
# Optional: also build a styled DMG for humans (drag to /Applications)
scripts/create-dmg.sh dist/OpenClaw.app dist/OpenClaw-2026.2.27.dmg
scripts/create-dmg.sh dist/OpenClaw.app dist/OpenClaw-2026.2.26.dmg
# Recommended: build + notarize/staple zip + DMG
# First, create a keychain profile once:
@@ -52,14 +52,14 @@ scripts/create-dmg.sh dist/OpenClaw.app dist/OpenClaw-2026.2.27.dmg
# --apple-id "<apple-id>" --team-id "<team-id>" --password "<app-specific-password>"
NOTARIZE=1 NOTARYTOOL_PROFILE=openclaw-notary \
BUNDLE_ID=ai.openclaw.mac \
APP_VERSION=2026.2.27 \
APP_VERSION=2026.2.26 \
APP_BUILD="$(git rev-list --count HEAD)" \
BUILD_CONFIG=release \
SIGN_IDENTITY="Developer ID Application: <Developer Name> (<TEAMID>)" \
scripts/package-mac-dist.sh
# Optional: ship dSYM alongside the release
ditto -c -k --keepParent apps/macos/.build/release/OpenClaw.app.dSYM dist/OpenClaw-2026.2.27.dSYM.zip
ditto -c -k --keepParent apps/macos/.build/release/OpenClaw.app.dSYM dist/OpenClaw-2026.2.26.dSYM.zip
```
## Appcast entry
@@ -67,7 +67,7 @@ ditto -c -k --keepParent apps/macos/.build/release/OpenClaw.app.dSYM dist/OpenCl
Use the release note generator so Sparkle renders formatted HTML notes:
```bash
SPARKLE_PRIVATE_KEY_FILE=/path/to/ed25519-private-key scripts/make_appcast.sh dist/OpenClaw-2026.2.27.zip https://raw.githubusercontent.com/openclaw/openclaw/main/appcast.xml
SPARKLE_PRIVATE_KEY_FILE=/path/to/ed25519-private-key scripts/make_appcast.sh dist/OpenClaw-2026.2.26.zip https://raw.githubusercontent.com/openclaw/openclaw/main/appcast.xml
```
Generates HTML release notes from `CHANGELOG.md` (via [`scripts/changelog-to-html.sh`](https://github.com/openclaw/openclaw/blob/main/scripts/changelog-to-html.sh)) and embeds them in the appcast entry.
@@ -75,7 +75,7 @@ Commit the updated `appcast.xml` alongside the release assets (zip + dSYM) when
## Publish & verify
- Upload `OpenClaw-2026.2.27.zip` (and `OpenClaw-2026.2.27.dSYM.zip`) to the GitHub release for tag `v2026.2.27`.
- Upload `OpenClaw-2026.2.26.zip` (and `OpenClaw-2026.2.26.dSYM.zip`) to the GitHub release for tag `v2026.2.26`.
- Ensure the raw appcast URL matches the baked feed: `https://raw.githubusercontent.com/openclaw/openclaw/main/appcast.xml`.
- Sanity checks:
- `curl -I https://raw.githubusercontent.com/openclaw/openclaw/main/appcast.xml` returns 200.

View File

@@ -83,80 +83,6 @@ OpenClaw uses `pi-ai` for model streaming. For `openai-codex/*` models you can s
}
```
### OpenAI Responses server-side compaction
For direct OpenAI Responses models (`openai/*` using `api: "openai-responses"` with
`baseUrl` on `api.openai.com`), OpenClaw now auto-enables OpenAI server-side
compaction payload hints:
- Forces `store: true` (unless model compat sets `supportsStore: false`)
- Injects `context_management: [{ type: "compaction", compact_threshold: ... }]`
By default, `compact_threshold` is `70%` of model `contextWindow` (or `80000`
when unavailable).
### Enable server-side compaction explicitly
Use this when you want to force `context_management` injection on compatible
Responses models (for example Azure OpenAI Responses):
```json5
{
agents: {
defaults: {
models: {
"azure-openai-responses/gpt-4o": {
params: {
responsesServerCompaction: true,
},
},
},
},
},
}
```
### Enable with a custom threshold
```json5
{
agents: {
defaults: {
models: {
"openai/gpt-5": {
params: {
responsesServerCompaction: true,
responsesCompactThreshold: 120000,
},
},
},
},
},
}
```
### Disable server-side compaction
```json5
{
agents: {
defaults: {
models: {
"openai/gpt-5": {
params: {
responsesServerCompaction: false,
},
},
},
},
},
}
```
`responsesServerCompaction` only controls `context_management` injection.
Direct OpenAI Responses models still force `store: true` unless compat sets
`supportsStore: false`.
## Notes
- Model refs always use `provider/model` (see [/concepts/models](/concepts/models)).

View File

@@ -11,7 +11,7 @@ title: "ClawHub"
ClawHub is the **public skill registry for OpenClaw**. It is a free service: all skills are public, open, and visible to everyone for sharing and reuse. A skill is just a folder with a `SKILL.md` file (plus supporting text files). You can browse skills in the web app or use the CLI to search, install, update, and publish skills.
Site: [clawhub.com](https://clawhub.com)
Site: [clawhub.ai](https://clawhub.ai)
## What ClawHub is
@@ -81,15 +81,9 @@ A typical skill includes:
- A `SKILL.md` file with the primary description and usage.
- Optional configs, scripts, or supporting files used by the skill.
- Metadata such as tags, summary, install requirements, and capabilities.
ClawHub uses metadata to power discovery and display skill capabilities.
Skills declare what system access they need via `capabilities` in frontmatter
(e.g., `shell`, `filesystem`, `network`). OpenClaw enforces these at runtime —
community skills that use tools without declaring the matching capability are
blocked. See [Skills](/tools/skills#gating-load-time-filters) for the
full capability reference.
- Metadata such as tags, summary, and install requirements.
ClawHub uses metadata to power discovery and safely expose skill capabilities.
The registry also tracks usage signals (such as stars and downloads) to improve
ranking and visibility.
@@ -109,17 +103,7 @@ ClawHub is open by default. Anyone can upload skills, but a GitHub account must
be at least one week old to publish. This helps slow down abuse without blocking
legitimate contributors.
### Capabilities and enforcement
Skills declare `capabilities` in their SKILL.md frontmatter to describe what
system access they need. ClawHub displays these to users before install.
OpenClaw uses these declarations for visibility and policy checks as capability
enforcement rolls out in stages. Skills with no capabilities are treated as
read-only metadata declarations.
Available capabilities: `shell`, `filesystem`, `network`, `browser`, `sessions`, `messaging`, `scheduling`.
### Reporting and moderation
Reporting and moderation:
- Any signed in user can report a skill.
- Report reasons are required and recorded.

View File

@@ -39,47 +39,11 @@ description: A simple skill that says hello.
When the user asks for a greeting, use the `echo` tool to say "Hello from your custom skill!".
```
### 3. Declare Capabilities
If your skill uses system tools, declare them in the `metadata.openclaw.capabilities` field:
```markdown
---
name: deploy_helper
description: Automate deployment workflows.
metadata: { "openclaw": { "capabilities": ["shell", "filesystem"] } }
---
```
Available capabilities: `shell`, `filesystem`, `network`, `browser`, `sessions`, `messaging`, `scheduling`.
You can use either a flat list or a 2-layer object shape under the same key:
```markdown
---
name: deploy_helper
description: Automate deployment workflows.
metadata:
{
"openclaw":
{
"capabilities":
{
"shell": { "mode": "restricted", "allow": ["git", "gh"] },
"network": { "web_search": true, "web_fetch": true },
},
},
}
---
```
Skills without capabilities are treated as read-only (model-only instructions). Community skills published to ClawHub should declare capabilities matching their tool usage so policy checks and command-dispatch safety can be applied consistently.
### 4. Add Tools (Optional)
### 3. Add Tools (Optional)
You can define custom tools in the frontmatter or instruct the agent to use existing system tools (like `bash` or `browser`).
### 5. Refresh OpenClaw
### 4. Refresh OpenClaw
Ask your agent to "refresh skills" or restart the gateway. OpenClaw will discover the new directory and index the `SKILL.md`.

View File

@@ -68,202 +68,12 @@ that up as `<workspace>/skills` on the next session.
## Security notes
- Treat third-party skills as **untrusted** until you have reviewed them. Runtime safeguards reduce blast radius but do not eliminate risk — read a skill's SKILL.md and declared capabilities before enabling it.
- **Capabilities**: Community skills (from ClawHub) should declare `capabilities` in `metadata.openclaw` to describe required system access. Skills without capabilities are treated as read-only metadata declarations. SKILL.md content is scanned for prompt injection before entering the system prompt.
- **Current rollout scope**: capability declarations are used for visibility, review, and command-dispatch safety checks in this phase. Broader runtime per-tool capability gating is being rolled out in stages.
- Local and workspace skills are treated as trusted by default. If someone can write to your skill folders, they can inject instructions into the system prompt — restrict who can modify them.
- Treat third-party skills as **untrusted code**. Read them before enabling.
- Prefer sandboxed runs for untrusted inputs and risky tools. See [Sandboxing](/gateway/sandboxing).
- `skills.entries.*.env` and `skills.entries.*.apiKey` inject secrets into the **host** process
for that agent turn (not the sandbox). Keep secrets out of prompts and logs.
- For a broader threat model and checklists, see [Security](/gateway/security).
### Tool enforcement matrix
Capability declarations map to three policy tiers below. This matrix is the enforcement model and migration target for staged rollout.
**Always denied** — blocked unconditionally when community skills are loaded, regardless of capability declarations:
| Tool | Reason |
| --------- | --------------------------------------------------------------- |
| `gateway` | Control-plane reconfiguration (restart, shutdown, auth changes) |
| `nodes` | Cluster node management (add/remove compute, redirect traffic) |
**Capability-gated** — tools intended to be governed by capability declarations in `metadata.openclaw.capabilities`:
| Capability | Tools | What it unlocks |
| ------------ | ---------------------------------------------- | ----------------------------------------- |
| `shell` | `exec`, `process` | Run shell commands and manage processes |
| `filesystem` | `write`, `edit`, `apply_patch` | File mutations (`read` is always allowed) |
| `network` | `web_fetch`, `web_search` | Outbound HTTP requests |
| `browser` | `browser` | Browser automation |
| `sessions` | `sessions_spawn`, `sessions_send`, `subagents` | Cross-session orchestration |
| `messaging` | `message` | Send messages to configured channels |
| `scheduling` | `cron` | Schedule recurring jobs |
**Always allowed** — safe read-only or output-only tools, no capability required:
| Tool | Why safe |
| ----------------------------------------------------- | --------------------------------- |
| `read` | Read-only file access |
| `memory_search`, `memory_get` | Read-only memory access |
| `agents_list` | List agents (read-only) |
| `sessions_list`, `sessions_history`, `session_status` | Session introspection (read-only) |
| `canvas` | UI rendering (output-only) |
| `image` | Image generation (output-only) |
| `tts` | Text-to-speech (output-only) |
A community skill with no capabilities declared gets access only to the always-allowed tier.
### Example: correct capability declaration
This skill runs shell commands and makes HTTP requests. It declares both capabilities, so operators and tooling can clearly see intended access:
```markdown
---
name: git-autopush
description: Automate git commit, push, and PR workflows.
metadata:
{ "openclaw": { "capabilities": ["shell", "network"], "requires": { "bins": ["git", "gh"] } } }
---
# git-autopush
When the user asks to push their changes:
1. Run `git add -A && git commit` via the exec tool.
2. Run `git push` via the exec tool.
3. If requested, create a PR using `gh pr create`.
```
`openclaw skills info git-autopush` shows:
```
git-autopush + Ready
Automate git commit, push, and PR workflows.
Source openclaw-managed
Path ~/.openclaw/skills/git-autopush/SKILL.md
Capabilities
>_ shell Run shell commands
🌐 network Make outbound HTTP requests
Security
Scan + clean
```
### Example: missing capability declaration
This skill runs shell commands but doesn't declare `shell`:
```markdown
---
name: deploy-helper
description: Deploy to production.
metadata: { "openclaw": { "requires": { "bins": ["rsync"] } } }
---
# deploy-helper
When the user asks to deploy, run `rsync -avz ./dist/ user@host:/var/www/` via the exec tool.
```
This skill has no `capabilities` declared, so it's flagged as incomplete capability metadata. `openclaw skills info deploy-helper` shows:
```
deploy-helper + Ready
Deploy to production.
Source openclaw-managed
Path ~/.openclaw/skills/deploy-helper/SKILL.md
Capabilities
(none — read-only skill)
Security
Scan + clean
```
The fix is to add `"capabilities": ["shell"]` to the metadata.
### Example: blocked skill (failed security scan)
If a SKILL.md contains prompt injection patterns, the scan blocks it from loading entirely:
```
evil-injector x Blocked (security)
Totally harmless skill.
Source openclaw-managed
Path ~/.openclaw/skills/evil-injector/SKILL.md
Capabilities
>_ shell Run shell commands
Security
Scan [blocked] prompt injection detected
```
This skill never enters the system prompt. It shows as `x blocked` in `openclaw skills list`.
### How the model sees skills
The model does not see the full SKILL.md in the system prompt. It only sees a compact XML listing with three fields per skill: `name`, `description`, and `location` (the file path). The model then uses the `read` tool to load the full SKILL.md on demand when the task matches.
This is what the model receives in the system prompt:
```
## Skills (mandatory)
Before replying: scan <available_skills> <description> entries.
- If exactly one skill clearly applies: read its SKILL.md at <location> with `read`, then follow it.
- If multiple could apply: choose the most specific one, then read/follow it.
- If none clearly apply: do not read any SKILL.md.
Constraints: never read more than one skill up front; only read after selecting.
The following skills provide specialized instructions for specific tasks.
Use the read tool to load a skill's file when the task matches its description.
When a skill file references a relative path, resolve it against the skill
directory (parent of SKILL.md / dirname of the path) and use that absolute
path in tool commands.
<available_skills>
<skill>
<name>git-autopush</name>
<description>Automate git commit, push, and PR workflows.</description>
<location>/home/user/.openclaw/skills/git-autopush/SKILL.md</location>
</skill>
<skill>
<name>todoist-cli</name>
<description>Manage Todoist tasks, projects, and labels.</description>
<location>/home/user/.openclaw/skills/todoist-cli/SKILL.md</location>
</skill>
</available_skills>
```
**What this means for skill authors:**
- **`description` is your pitch** — it's the only thing the model reads to decide whether to load your skill. Make it specific and task-oriented. "Manage Todoist tasks, projects, and labels from the command line" is better than "Todoist integration."
- **`name` must be lowercase `[a-z0-9-]`**, max 64 characters, must match the parent directory name.
- **`description` max 1024 characters.**
- **Your SKILL.md body is loaded on demand** — it needs to be self-contained instructions the model can follow after reading.
- **Relative paths in SKILL.md** are resolved against the skill directory. Use relative paths to reference supporting files.
The `Skill` type from `@mariozechner/pi-coding-agent`:
```typescript
interface Skill {
name: string; // from frontmatter (or parent dir name)
description: string; // from frontmatter (required, max 1024 chars)
filePath: string; // absolute path to SKILL.md
baseDir: string; // parent directory of SKILL.md
source: string; // origin identifier
disableModelInvocation: boolean; // if true, excluded from prompt
}
```
## Format (AgentSkills + Pi-compatible)
`SKILL.md` must include at least:
@@ -306,7 +116,6 @@ metadata:
{
"requires": { "bins": ["uv"], "env": ["GEMINI_API_KEY"], "config": ["browser.enabled"] },
"primaryEnv": "GEMINI_API_KEY",
"capabilities": ["browser", "network"],
},
}
---
@@ -316,82 +125,14 @@ Fields under `metadata.openclaw`:
- `always: true` — always include the skill (skip other gates).
- `emoji` — optional emoji used by the macOS Skills UI.
- `homepage` — optional URL shown as "Website" in the macOS Skills UI.
- `homepage` — optional URL shown as Website in the macOS Skills UI.
- `os` — optional list of platforms (`darwin`, `linux`, `win32`). If set, the skill is only eligible on those OSes.
- `capabilities` — list of system access the skill needs. Used for security enforcement and user-facing display. Allowed values:
- `shell` — run shell commands (maps to `exec`, `process`)
- `filesystem` — read/write/edit files (maps to `write`, `edit`, `apply_patch`; `read` is always allowed)
- `network` — outbound HTTP (maps to `web_search`, `web_fetch`)
- `browser` — browser automation (maps to `browser`)
- `sessions` — cross-session orchestration (maps to `sessions_spawn`, `sessions_send`, `subagents`)
- `messaging` — send messages to configured channels (maps to `message`)
- `scheduling` — schedule recurring jobs (maps to `cron`)
No capabilities declared = read-only, model-only skill metadata. See [Tool enforcement matrix](#tool-enforcement-matrix) below and [Security](/gateway/security) for rollout and hardening details.
### Capability shape and normalization
OpenClaw accepts both styles under the same `capabilities` key:
Flat list:
```json
{
"openclaw": {
"capabilities": ["shell", "network", "sessions"]
}
}
```
Two-layer object with optional constraints:
```jsonc
{
"openclaw": {
"capabilities": {
"shell": { "mode": "restricted", "allow": ["git", "gh"] }, // key/value constraints
"network": { "web_search": true, "web_fetch": true }, // granular switches
"sessions": { "maxDepth": 2 }, // future-safe metadata
},
},
}
```
Array-of-objects also works:
```json
{
"openclaw": {
"capabilities": [
{ "type": "network.search", "constraints": { "provider": "brave" } },
{ "name": "shell.exec", "constraints": { "mode": "restricted" } }
]
}
}
```
Normalization behavior:
- OpenClaw normalizes external naming to canonical values (`shell`, `filesystem`, `network`, `browser`, `sessions`, `messaging`, `scheduling`).
- Examples:
- `web_fetch`, `web_search`, `webfetch` -> `network`
- `terminal`, `bash`, `exec` -> `shell`
- `subagent`, `sessions_spawn` -> `sessions`
- `message` -> `messaging`
- `cron`, `schedule` -> `scheduling`
- Constraints are currently advisory metadata (not enforced by the runtime gate yet). Keep them simple key/value pairs for forward compatibility.
- `requires.bins` — list; each must exist on `PATH`.
- `requires.anyBins` — list; at least one must exist on `PATH`.
- `requires.env` — list; env var must exist **or** be provided in config.
- `requires.config` — list of `openclaw.json` paths that must be truthy.
- `primaryEnv` — env var name associated with `skills.entries.<name>.apiKey`.
- `install` — optional array of installer specs used by the macOS Skills UI (brew/node/go/uv/download).
- `cliHelp` — optional CLI help output captured for richer skill details in registry/UI surfaces.
- `envVars` — optional structured environment declarations (`name`, `required`, `description`).
- `dependencies` — optional structured dependency declarations (`name`, `type`, optional version/url/repository).
- `author` — optional author string for display/attribution.
- `links` — optional link metadata (`homepage`, `repository`, `documentation`, `changelog`).
Note on sandboxing:
@@ -454,7 +195,7 @@ Bundled/managed skills can be toggled and supplied with env values:
entries: {
"nano-banana-pro": {
enabled: true,
apiKey: "GEMINI_KEY_HERE",
apiKey: { source: "env", provider: "default", id: "GEMINI_API_KEY" }, // or plaintext string
env: {
GEMINI_API_KEY: "GEMINI_KEY_HERE",
},
@@ -480,6 +221,7 @@ Rules:
- `enabled: false` disables the skill even if its bundled/installed.
- `env`: injected **only if** the variable isnt already set in the process.
- `apiKey`: convenience for skills that declare `metadata.openclaw.primaryEnv`.
Supports plaintext string or SecretRef object (`{ source, provider, id }`).
- `config`: optional bag for custom per-skill fields; custom keys must live here.
- `allowBundled`: optional allowlist for **bundled** skills only. If set, only
bundled skills in the list are eligible (managed/workspace skills unaffected).

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/acpx",
"version": "2026.2.27",
"version": "2026.2.26",
"description": "OpenClaw ACP runtime backend via acpx",
"type": "module",
"dependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/bluebubbles",
"version": "2026.2.27",
"version": "2026.2.26",
"description": "OpenClaw BlueBubbles channel plugin",
"type": "module",
"openclaw": {

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/copilot-proxy",
"version": "2026.2.27",
"version": "2026.2.26",
"private": true,
"description": "OpenClaw Copilot Proxy provider plugin",
"type": "module",

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/diagnostics-otel",
"version": "2026.2.27",
"version": "2026.2.26",
"description": "OpenClaw diagnostics OpenTelemetry exporter",
"type": "module",
"dependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/discord",
"version": "2026.2.27",
"version": "2026.2.26",
"description": "OpenClaw Discord channel plugin",
"type": "module",
"openclaw": {

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/feishu",
"version": "2026.2.27",
"version": "2026.2.26",
"description": "OpenClaw Feishu/Lark channel plugin (community maintained by @m1heng)",
"type": "module",
"dependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/google-gemini-cli-auth",
"version": "2026.2.27",
"version": "2026.2.26",
"private": true,
"description": "OpenClaw Gemini CLI OAuth provider plugin",
"type": "module",

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/googlechat",
"version": "2026.2.27",
"version": "2026.2.26",
"private": true,
"description": "OpenClaw Google Chat channel plugin",
"type": "module",

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/imessage",
"version": "2026.2.27",
"version": "2026.2.26",
"private": true,
"description": "OpenClaw iMessage channel plugin",
"type": "module",

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/irc",
"version": "2026.2.27",
"version": "2026.2.26",
"description": "OpenClaw IRC channel plugin",
"type": "module",
"openclaw": {

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/line",
"version": "2026.2.27",
"version": "2026.2.26",
"private": true,
"description": "OpenClaw LINE channel plugin",
"type": "module",

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/llm-task",
"version": "2026.2.27",
"version": "2026.2.26",
"private": true,
"description": "OpenClaw JSON-only LLM task plugin",
"type": "module",

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/lobster",
"version": "2026.2.27",
"version": "2026.2.26",
"description": "Lobster workflow tool plugin (typed pipelines + resumable approvals)",
"type": "module",
"openclaw": {

View File

@@ -1,11 +1,5 @@
# Changelog
## 2026.2.27
### Changes
- Version alignment with core OpenClaw release numbers.
## 2026.2.26
### Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/matrix",
"version": "2026.2.27",
"version": "2026.2.26",
"description": "OpenClaw Matrix channel plugin",
"type": "module",
"dependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/mattermost",
"version": "2026.2.27",
"version": "2026.2.26",
"description": "OpenClaw Mattermost channel plugin",
"type": "module",
"openclaw": {

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/memory-core",
"version": "2026.2.27",
"version": "2026.2.26",
"private": true,
"description": "OpenClaw core memory search plugin",
"type": "module",

View File

@@ -5,10 +5,8 @@ import { join } from "node:path";
export type MemoryConfig = {
embedding: {
provider: "openai";
model: string;
model?: string;
apiKey: string;
baseUrl?: string;
dimensions?: number;
};
dbPath?: string;
autoCapture?: boolean;
@@ -83,9 +81,7 @@ function resolveEnvVars(value: string): string {
function resolveEmbeddingModel(embedding: Record<string, unknown>): string {
const model = typeof embedding.model === "string" ? embedding.model : DEFAULT_MODEL;
if (typeof embedding.dimensions !== "number") {
vectorDimsForModel(model);
}
vectorDimsForModel(model);
return model;
}
@@ -105,7 +101,7 @@ export const memoryConfigSchema = {
if (!embedding || typeof embedding.apiKey !== "string") {
throw new Error("embedding.apiKey is required");
}
assertAllowedKeys(embedding, ["apiKey", "model", "baseUrl", "dimensions"], "embedding config");
assertAllowedKeys(embedding, ["apiKey", "model"], "embedding config");
const model = resolveEmbeddingModel(embedding);
@@ -123,9 +119,6 @@ export const memoryConfigSchema = {
provider: "openai",
model,
apiKey: resolveEnvVars(embedding.apiKey),
baseUrl:
typeof embedding.baseUrl === "string" ? resolveEnvVars(embedding.baseUrl) : undefined,
dimensions: typeof embedding.dimensions === "number" ? embedding.dimensions : undefined,
},
dbPath: typeof cfg.dbPath === "string" ? cfg.dbPath : DEFAULT_DB_PATH,
autoCapture: cfg.autoCapture === true,
@@ -140,18 +133,6 @@ export const memoryConfigSchema = {
placeholder: "sk-proj-...",
help: "API key for OpenAI embeddings (or use ${OPENAI_API_KEY})",
},
"embedding.baseUrl": {
label: "Base URL",
placeholder: "https://api.openai.com/v1",
help: "Base URL for compatible providers (e.g. http://localhost:11434/v1)",
advanced: true,
},
"embedding.dimensions": {
label: "Dimensions",
placeholder: "1536",
help: "Vector dimensions for custom models (required for non-standard models)",
advanced: true,
},
"embedding.model": {
label: "Embedding Model",
placeholder: DEFAULT_MODEL,

View File

@@ -166,9 +166,8 @@ class Embeddings {
constructor(
apiKey: string,
private model: string,
baseUrl?: string,
) {
this.client = new OpenAI({ apiKey, baseURL: baseUrl });
this.client = new OpenAI({ apiKey });
}
async embed(text: string): Promise<number[]> {
@@ -294,11 +293,9 @@ const memoryPlugin = {
register(api: OpenClawPluginApi) {
const cfg = memoryConfigSchema.parse(api.pluginConfig);
const resolvedDbPath = api.resolvePath(cfg.dbPath!);
const { model, dimensions, apiKey, baseUrl } = cfg.embedding;
const vectorDim = dimensions ?? vectorDimsForModel(model);
const vectorDim = vectorDimsForModel(cfg.embedding.model ?? "text-embedding-3-small");
const db = new MemoryDB(resolvedDbPath, vectorDim);
const embeddings = new Embeddings(apiKey, model, baseUrl);
const embeddings = new Embeddings(cfg.embedding.apiKey, cfg.embedding.model!);
api.logger.info(`memory-lancedb: plugin registered (db: ${resolvedDbPath}, lazy init)`);

View File

@@ -13,18 +13,6 @@
"placeholder": "text-embedding-3-small",
"help": "OpenAI embedding model to use"
},
"embedding.baseUrl": {
"label": "Base URL",
"placeholder": "https://api.openai.com/v1",
"help": "Base URL for compatible providers (e.g. http://localhost:11434/v1)",
"advanced": true
},
"embedding.dimensions": {
"label": "Dimensions",
"placeholder": "1536",
"help": "Vector dimensions for custom models (required for non-standard models)",
"advanced": true
},
"dbPath": {
"label": "Database Path",
"placeholder": "~/.openclaw/memory/lancedb",
@@ -57,13 +45,8 @@
"type": "string"
},
"model": {
"type": "string"
},
"baseUrl": {
"type": "string"
},
"dimensions": {
"type": "number"
"type": "string",
"enum": ["text-embedding-3-small", "text-embedding-3-large"]
}
},
"required": ["apiKey"]

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/memory-lancedb",
"version": "2026.2.27",
"version": "2026.2.26",
"private": true,
"description": "OpenClaw LanceDB-backed long-term memory plugin with auto-recall/capture",
"type": "module",

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/minimax-portal-auth",
"version": "2026.2.27",
"version": "2026.2.26",
"private": true,
"description": "OpenClaw MiniMax Portal OAuth provider plugin",
"type": "module",

View File

@@ -1,11 +1,5 @@
# Changelog
## 2026.2.27
### Changes
- Version alignment with core OpenClaw release numbers.
## 2026.2.26
### Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/msteams",
"version": "2026.2.27",
"version": "2026.2.26",
"description": "OpenClaw Microsoft Teams channel plugin",
"type": "module",
"dependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/nextcloud-talk",
"version": "2026.2.27",
"version": "2026.2.26",
"description": "OpenClaw Nextcloud Talk channel plugin",
"type": "module",
"openclaw": {

View File

@@ -1,11 +1,5 @@
# Changelog
## 2026.2.27
### Changes
- Version alignment with core OpenClaw release numbers.
## 2026.2.26
### Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/nostr",
"version": "2026.2.27",
"version": "2026.2.26",
"description": "OpenClaw Nostr channel plugin for NIP-04 encrypted DMs",
"type": "module",
"dependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/open-prose",
"version": "2026.2.27",
"version": "2026.2.26",
"private": true,
"description": "OpenProse VM skill pack plugin (slash command + telemetry).",
"type": "module",

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/signal",
"version": "2026.2.27",
"version": "2026.2.26",
"private": true,
"description": "OpenClaw Signal channel plugin",
"type": "module",

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/slack",
"version": "2026.2.27",
"version": "2026.2.26",
"private": true,
"description": "OpenClaw Slack channel plugin",
"type": "module",

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/synology-chat",
"version": "2026.2.27",
"version": "2026.2.26",
"description": "Synology Chat channel plugin for OpenClaw",
"type": "module",
"dependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/telegram",
"version": "2026.2.27",
"version": "2026.2.26",
"private": true,
"description": "OpenClaw Telegram channel plugin",
"type": "module",

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/tlon",
"version": "2026.2.27",
"version": "2026.2.26",
"description": "OpenClaw Tlon/Urbit channel plugin",
"type": "module",
"dependencies": {

View File

@@ -1,11 +1,5 @@
# Changelog
## 2026.2.27
### Changes
- Version alignment with core OpenClaw release numbers.
## 2026.2.26
### Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/twitch",
"version": "2026.2.27",
"version": "2026.2.26",
"description": "OpenClaw Twitch channel plugin",
"type": "module",
"dependencies": {

View File

@@ -1,11 +1,5 @@
# Changelog
## 2026.2.27
### Changes
- Version alignment with core OpenClaw release numbers.
## 2026.2.26
### Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/voice-call",
"version": "2026.2.27",
"version": "2026.2.26",
"description": "OpenClaw voice-call plugin",
"type": "module",
"dependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/whatsapp",
"version": "2026.2.27",
"version": "2026.2.26",
"private": true,
"description": "OpenClaw WhatsApp channel plugin",
"type": "module",

View File

@@ -1,11 +1,5 @@
# Changelog
## 2026.2.27
### Changes
- Version alignment with core OpenClaw release numbers.
## 2026.2.26
### Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/zalo",
"version": "2026.2.27",
"version": "2026.2.26",
"description": "OpenClaw Zalo channel plugin",
"type": "module",
"dependencies": {

View File

@@ -1,11 +1,5 @@
# Changelog
## 2026.2.27
### Changes
- Version alignment with core OpenClaw release numbers.
## 2026.2.26
### Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/zalouser",
"version": "2026.2.27",
"version": "2026.2.26",
"description": "OpenClaw Zalo Personal Account plugin via zca-cli",
"type": "module",
"dependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "openclaw",
"version": "2026.2.27",
"version": "2026.2.26",
"description": "Multi-channel AI gateway with extensible messaging integrations",
"keywords": [],
"homepage": "https://github.com/openclaw/openclaw#readme",
@@ -180,8 +180,6 @@
"dotenv": "^17.3.1",
"express": "^5.2.1",
"file-type": "^21.3.0",
"gaxios": "7.1.2",
"google-auth-library": "10.5.0",
"grammy": "^1.40.1",
"https-proxy-agent": "^7.0.6",
"ipaddr.js": "^2.3.0",
@@ -191,7 +189,6 @@
"linkedom": "^0.18.12",
"long": "^5.3.2",
"markdown-it": "^14.1.1",
"node-domexception": "npm:@nolyfill/domexception@^1.0.28",
"node-edge-tts": "^1.2.10",
"opusscript": "^0.1.1",
"osc-progress": "^0.3.0",
@@ -230,7 +227,7 @@
},
"peerDependencies": {
"@napi-rs/canvas": "^0.1.89",
"node-llama-cpp": "3.16.2"
"node-llama-cpp": "3.15.1"
},
"optionalDependencies": {
"@discordjs/opus": "^0.10.0"
@@ -249,7 +246,6 @@
"form-data": "2.5.4",
"minimatch": "10.2.4",
"qs": "6.14.2",
"node-domexception": "npm:@nolyfill/domexception@^1.0.28",
"@sinclair/typebox": "0.34.48",
"tar": "7.5.9",
"tough-cookie": "4.1.3"

391
pnpm-lock.yaml generated
View File

@@ -12,7 +12,6 @@ overrides:
form-data: 2.5.4
minimatch: 10.2.4
qs: 6.14.2
node-domexception: npm:@nolyfill/domexception@^1.0.28
'@sinclair/typebox': 0.34.48
tar: 7.5.9
tough-cookie: 4.1.3
@@ -117,12 +116,6 @@ importers:
file-type:
specifier: ^21.3.0
version: 21.3.0
gaxios:
specifier: 7.1.2
version: 7.1.2
google-auth-library:
specifier: 10.5.0
version: 10.5.0
grammy:
specifier: ^1.40.1
version: 1.40.1
@@ -150,15 +143,12 @@ importers:
markdown-it:
specifier: ^14.1.1
version: 14.1.1
node-domexception:
specifier: npm:@nolyfill/domexception@^1.0.28
version: '@nolyfill/domexception@1.0.28'
node-edge-tts:
specifier: ^1.2.10
version: 1.2.10
node-llama-cpp:
specifier: 3.16.2
version: 3.16.2(typescript@5.9.3)
specifier: 3.15.1
version: 3.15.1(typescript@5.9.3)
opusscript:
specifier: ^0.1.1
version: 0.1.1
@@ -330,7 +320,7 @@ importers:
version: 10.6.1
openclaw:
specifier: '>=2026.1.26'
version: 2026.2.24(@napi-rs/canvas@0.1.95)(@types/express@5.0.6)(audio-decode@2.2.3)(hono@4.11.10)(node-llama-cpp@3.16.2(typescript@5.9.3))
version: 2026.2.24(@napi-rs/canvas@0.1.95)(@types/express@5.0.6)(audio-decode@2.2.3)(hono@4.11.10)(node-llama-cpp@3.15.1(typescript@5.9.3))
extensions/imessage: {}
@@ -366,7 +356,7 @@ importers:
dependencies:
openclaw:
specifier: '>=2026.1.26'
version: 2026.2.24(@napi-rs/canvas@0.1.95)(@types/express@5.0.6)(audio-decode@2.2.3)(hono@4.11.10)(node-llama-cpp@3.16.2(typescript@5.9.3))
version: 2026.2.24(@napi-rs/canvas@0.1.95)(@types/express@5.0.6)(audio-decode@2.2.3)(hono@4.11.10)(node-llama-cpp@3.15.1(typescript@5.9.3))
extensions/memory-lancedb:
dependencies:
@@ -1532,88 +1522,84 @@ packages:
resolution: {integrity: sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw==}
engines: {node: '>= 20.19.0'}
'@node-llama-cpp/linux-arm64@3.16.2':
resolution: {integrity: sha512-CxzgPsS84wL3W5sZRgxP3c9iJKEW+USrak1SmX6EAJxW/v9QGzehvT6W/aR1FyfidiIyQtOp3ga0Gg/9xfJPGw==}
'@node-llama-cpp/linux-arm64@3.15.1':
resolution: {integrity: sha512-g7JC/WwDyyBSmkIjSvRF2XLW+YA0z2ZVBSAKSv106mIPO4CzC078woTuTaPsykWgIaKcQRyXuW5v5XQMcT1OOA==}
engines: {node: '>=20.0.0'}
cpu: [arm64, x64]
os: [linux]
'@node-llama-cpp/linux-armv7l@3.16.2':
resolution: {integrity: sha512-9G6W/MkQ/DLwGmpcj143NQ50QJg5gQZfzVf5RYx77VczBqhgwkgYHILekYrOs4xanOeqeJ8jnOnQQSp1YaJZUg==}
'@node-llama-cpp/linux-armv7l@3.15.1':
resolution: {integrity: sha512-MSxR3A0vFSVWbmVSkNqNXQnI45L2Vg7/PRgJukcjChk7YzRxs9L+oQMeycVW3BsQ03mIZ0iORsZ9MNIBEbdS3g==}
engines: {node: '>=20.0.0'}
cpu: [arm, x64]
os: [linux]
'@node-llama-cpp/linux-x64-cuda-ext@3.16.2':
resolution: {integrity: sha512-47d9myCJauZyzAlN7IK1eIt/4CcBMslF+yHy4q+yJotD/RV/S6qRpK2kGn+ybtdVjkPGNCoPkHKcyla9iIVjbw==}
'@node-llama-cpp/linux-x64-cuda-ext@3.15.1':
resolution: {integrity: sha512-toepvLcXjgaQE/QGIThHBD58jbHGBWT1jhblJkCjYBRHfVOO+6n/PmVsJt+yMfu5Z93A2gF8YOvVyZXNXmGo5g==}
engines: {node: '>=20.0.0'}
cpu: [x64]
os: [linux]
'@node-llama-cpp/linux-x64-cuda@3.16.2':
resolution: {integrity: sha512-LTBQFqjin7tyrLNJz0XWTB5QAHDsZV71/qiiRRjXdBKSZHVVaPLfdgxypGu7ggPeBNsv+MckRXdlH5C7yMtE4A==}
'@node-llama-cpp/linux-x64-cuda@3.15.1':
resolution: {integrity: sha512-kngwoq1KdrqSr/b6+tn5jbtGHI0tZnW5wofKssZy+Il2ge3eN9FowKbXG4FH452g6qSSVoDccAoTvYOxyLyX+w==}
engines: {node: '>=20.0.0'}
cpu: [x64]
os: [linux]
'@node-llama-cpp/linux-x64-vulkan@3.16.2':
resolution: {integrity: sha512-HDLAw4ZhwJuhKuF6n4x520yZXAQZahUOXtCGvPubjfpmIOElKrfDvCVlRsthAP0JwcwINzIQlVys3boMIXfBgw==}
'@node-llama-cpp/linux-x64-vulkan@3.15.1':
resolution: {integrity: sha512-CMsyQkGKpHKeOH9+ZPxo0hO0usg8jabq5/aM3JwdX9CiuXhXUa3nu3NH4RObiNi596Zwn/zWzlps0HRwcpL8rw==}
engines: {node: '>=20.0.0'}
cpu: [x64]
os: [linux]
'@node-llama-cpp/linux-x64@3.16.2':
resolution: {integrity: sha512-OXYf8rVfoDyvN+YrfKk8F9An9a5GOxVIM8OcR1U911tc0oRNf8yfJrQ8KrM75R26gwq0Y6YZwVTP0vRCInwWOw==}
'@node-llama-cpp/linux-x64@3.15.1':
resolution: {integrity: sha512-w4SdxJaA9eJLVYWX+Jv48hTP4oO79BJQIFURMi7hXIFXbxyyOov/r6sVaQ1WiL83nVza37U5Qg4L9Gb/KRdNWQ==}
engines: {node: '>=20.0.0'}
cpu: [x64]
os: [linux]
'@node-llama-cpp/mac-arm64-metal@3.16.2':
resolution: {integrity: sha512-nEZ74qB0lUohF88yR741YUrUqz/qD+FJFzUTHj0FwxAynSZCjvwtzEDtavRlh3qd3yLD/0ChNn00/RQ54ISImw==}
'@node-llama-cpp/mac-arm64-metal@3.15.1':
resolution: {integrity: sha512-ePTweqohcy6Gjs1agXWy4FxAw5W4Avr7NeqqiFWJ5ngZ1U3ZXdruUHB8L/vDxyn3FzKvstrFyN7UScbi0pzXrA==}
engines: {node: '>=20.0.0'}
cpu: [arm64, x64]
os: [darwin]
'@node-llama-cpp/mac-x64@3.16.2':
resolution: {integrity: sha512-BjA+DgeDt+kRxVMV6kChb9XVXm7U5b90jUif7Z/s6ZXtOOnV6exrTM2W09kbSqAiNhZmctcVY83h2dwNTZ/yIw==}
'@node-llama-cpp/mac-x64@3.15.1':
resolution: {integrity: sha512-NAetSQONxpNXTBnEo7oOkKZ84wO2avBy6V9vV9ntjJLb/07g7Rar8s/jVaicc/rVl6C+8ljZNwqJeynirgAC5w==}
engines: {node: '>=20.0.0'}
cpu: [x64]
os: [darwin]
'@node-llama-cpp/win-arm64@3.16.2':
resolution: {integrity: sha512-XHNFQzUjYODtkZjIn4NbQVrBtGB9RI9TpisiALryqfrIqagQmjBh6dmxZWlt5uduKAfT7M2/2vrABGR490FACA==}
'@node-llama-cpp/win-arm64@3.15.1':
resolution: {integrity: sha512-1O9tNSUgvgLL5hqgEuYiz7jRdA3+9yqzNJyPW1jExlQo442OA0eIpHBmeOtvXLwMkY7qv7wE75FdOPR7NVEnvg==}
engines: {node: '>=20.0.0'}
cpu: [arm64, x64]
os: [win32]
'@node-llama-cpp/win-x64-cuda-ext@3.16.2':
resolution: {integrity: sha512-sdv4Kzn9bOQWNBRvw6B/zcn8dQRfZhjIHv5AfDBIOfRlSCgjebFpBeYUoU4wZPpjr3ISwcqO5MEWsw+AbUdV3Q==}
'@node-llama-cpp/win-x64-cuda-ext@3.15.1':
resolution: {integrity: sha512-mO3Tf6D3UlFkjQF5J96ynTkjdF7dac/f5f61cEh6oU4D3hdx+cwnmBWT1gVhDSLboJYzCHtx7U2EKPP6n8HoWA==}
engines: {node: '>=20.0.0'}
cpu: [x64]
os: [win32]
'@node-llama-cpp/win-x64-cuda@3.16.2':
resolution: {integrity: sha512-jStDELHrU3rKQMOk5Hs5bWEazyjE2hzHwpNf6SblOpaGkajM/HJtxEZoL0mLHJx5qeXs4oOVkr7AzuLy0WPpNA==}
'@node-llama-cpp/win-x64-cuda@3.15.1':
resolution: {integrity: sha512-swoyx0/dY4ixiu3mEWrIAinx0ffHn9IncELDNREKG+iIXfx6w0OujOMQ6+X+lGj+sjE01yMUP/9fv6GEp2pmBw==}
engines: {node: '>=20.0.0'}
cpu: [x64]
os: [win32]
'@node-llama-cpp/win-x64-vulkan@3.16.2':
resolution: {integrity: sha512-9xuHFCOhCQjZgQSFrk79EuSKn9nGWt/SAq/3wujQSQLtgp8jGdtZgwcmuDUoemInf10en2dcOmEt7t8dQdC3XA==}
'@node-llama-cpp/win-x64-vulkan@3.15.1':
resolution: {integrity: sha512-BPBjUEIkFTdcHSsQyblP0v/aPPypi6uqQIq27mo4A49CYjX22JDmk4ncdBLk6cru+UkvwEEe+F2RomjoMt32aQ==}
engines: {node: '>=20.0.0'}
cpu: [x64]
os: [win32]
'@node-llama-cpp/win-x64@3.16.2':
resolution: {integrity: sha512-etrivzbyLNVhZlUosFW8JSL0OSiuKQf9qcI3dNdehD907sHquQbBJrG7lXcdL6IecvXySp3oAwCkM87VJ0b3Fg==}
'@node-llama-cpp/win-x64@3.15.1':
resolution: {integrity: sha512-jtoXBa6h+VPsQgefrO7HDjYv4WvxfHtUO30ABwCUDuEgM0e05YYhxMZj1z2Ns47UrquNvd/LUPCyjHKqHUN+5Q==}
engines: {node: '>=20.0.0'}
cpu: [x64]
os: [win32]
'@nolyfill/domexception@1.0.28':
resolution: {integrity: sha512-tlc/FcYIv5i8RYsl2iDil4A0gOihaas1R5jPcIC4Zw3GhjKsVilw90aHcVlhZPTBLGBzd379S+VcnsDjd9ChiA==}
engines: {node: '>=12.4.0'}
'@octokit/app@16.1.2':
resolution: {integrity: sha512-8j7sEpUYVj18dxvh0KWj6W/l6uAiVRBl1JBDVRqH1VHKAO/G5eRVl4yEoYACjakWers1DjUkcCHyJNQK47JqyQ==}
engines: {node: '>= 20'}
@@ -3169,6 +3155,11 @@ packages:
engines: {node: '>=10'}
deprecated: This package is no longer supported.
are-we-there-yet@3.0.1:
resolution: {integrity: sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==}
engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0}
deprecated: This package is no longer supported.
argparse@2.0.1:
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
@@ -3388,10 +3379,6 @@ packages:
resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==}
engines: {node: '>=6'}
cli-spinners@3.4.0:
resolution: {integrity: sha512-bXfOC4QcT1tKXGorxL3wbJm6XJPDqEnij2gQ2m7ESQuE+/z9YFIWnl/5RpTiKWbMq3EVKR4fRLJGn6DVfu0mpw==}
engines: {node: '>=18.20'}
cliui@7.0.4:
resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==}
@@ -3399,9 +3386,9 @@ packages:
resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
engines: {node: '>=12'}
cmake-js@8.0.0:
resolution: {integrity: sha512-YbUP88RDwCvoQkZhRtGURYm9RIpWdtvZuhT87fKNoLjk8kIFIFeARpKfuZQGdwfH99GZpUmqSfcDrK62X7lTgg==}
engines: {node: ^20.17.0 || >=22.9.0}
cmake-js@7.4.0:
resolution: {integrity: sha512-Lw0JxEHrmk+qNj1n9W9d4IvkDdYTBn7l2BW6XmtLj7WPpIo2shvxUy+YokfjMxAAOELNonQwX3stkPhM5xSC2Q==}
engines: {node: '>= 14.15.0'}
hasBin: true
codec-parser@2.5.0:
@@ -3863,9 +3850,10 @@ packages:
engines: {node: '>=10'}
deprecated: This package is no longer supported.
gaxios@7.1.2:
resolution: {integrity: sha512-/Szrn8nr+2TsQT1Gp8iIe/BEytJmbyfrbFh419DfGQSkEgNEhbPi7JRJuughjkTzPWgU9gBQf5AVu3DbHt0OXA==}
engines: {node: '>=18'}
gauge@4.0.4:
resolution: {integrity: sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==}
engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0}
deprecated: This package is no longer supported.
gaxios@7.1.3:
resolution: {integrity: sha512-YGGyuEdVIjqxkxVH1pUTMY/XtmmsApXrCVv5EU25iX6inEPbV+VakJfLealkBtJN69AQmh1eGOdCl9Sm1UP6XQ==}
@@ -3921,10 +3909,6 @@ packages:
resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
google-auth-library@10.5.0:
resolution: {integrity: sha512-7ABviyMOlX5hIVD60YOfHw4/CxOfBhyduaYB+wbFWCWoni4N7SLcV46hrVRktuBbZjFC9ONyqamZITN7q3n32w==}
engines: {node: '>=18'}
google-auth-library@10.6.1:
resolution: {integrity: sha512-5awwuLrzNol+pFDmKJd0dKtZ0fPLAtoA5p7YO4ODsDu6ONJUVqbYwvv8y2ZBO5MBNp9TJXigB19710kYpBPdtA==}
engines: {node: '>=18'}
@@ -3944,10 +3928,6 @@ packages:
resolution: {integrity: sha512-bTe8SWXD8/Sdt2LGAAAsFGhuxI9RG8zL2gGk3V42A/RxriPqBQqwMGoNSldNK1qIFD2EaVuq7NQM8+ZAmNgHLw==}
engines: {node: ^12.20.0 || >=14.13.1}
gtoken@8.0.0:
resolution: {integrity: sha512-+CqsMbHPiSTdtSO14O51eMNlrp9N79gmeqmXeouJOhfucAedHw9noVe/n5uJk3tbKE6a+6ZCQg3RPhVhHByAIw==}
engines: {node: '>=18'}
has-flag@4.0.0:
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
engines: {node: '>=8'}
@@ -4121,6 +4101,10 @@ packages:
is-typedarray@1.0.0:
resolution: {integrity: sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==}
is-unicode-supported@1.3.0:
resolution: {integrity: sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==}
engines: {node: '>=12'}
is-unicode-supported@2.1.0:
resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==}
engines: {node: '>=18'}
@@ -4131,9 +4115,9 @@ packages:
isexe@2.0.0:
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
isexe@4.0.0:
resolution: {integrity: sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==}
engines: {node: '>=20'}
isexe@3.1.5:
resolution: {integrity: sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==}
engines: {node: '>=18'}
isstream@0.1.2:
resolution: {integrity: sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==}
@@ -4381,6 +4365,10 @@ packages:
lodash@4.17.23:
resolution: {integrity: sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==}
log-symbols@6.0.0:
resolution: {integrity: sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==}
engines: {node: '>=18'}
log-symbols@7.0.1:
resolution: {integrity: sha512-ja1E3yCr9i/0hmBVaM0bfwDjnGy8I/s6PP4DFp+yP+a+mrHO4Rm7DtmnqROTUkHIkqffC84YY7AeqX6oFk0WFg==}
engines: {node: '>=18'}
@@ -4460,6 +4448,9 @@ packages:
resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==}
engines: {node: '>= 0.8'}
memory-stream@1.0.0:
resolution: {integrity: sha512-Wm13VcsPIMdG96dzILfij09PvuS3APtcKNh7M28FsCA/w6+1mjR7hhPmfFNoilX9xU7wTdhsH5lJAm6XNzdtww==}
merge-descriptors@1.0.3:
resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==}
@@ -4575,6 +4566,11 @@ packages:
node-api-headers@1.8.0:
resolution: {integrity: sha512-jfnmiKWjRAGbdD1yQS28bknFM1tbHC1oucyuMPjmkEs+kpiu76aRs40WlTmBmyEgzDM76ge1DQ7XJ3R5deiVjQ==}
node-domexception@1.0.0:
resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==}
engines: {node: '>=10.5.0'}
deprecated: Use your platform's native DOMException instead
node-downloader-helper@2.1.10:
resolution: {integrity: sha512-8LdieUd4Bqw/CzfZLf30h+1xSAq3riWSDfWKsPJYz8EULoWxjS1vw6BGLYFZDxQgXjDR7UmC9UpQ0oV93U98Fg==}
engines: {node: '>=14.18'}
@@ -4597,8 +4593,8 @@ packages:
resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
node-llama-cpp@3.16.2:
resolution: {integrity: sha512-ovhuTaXSWfcoyfI8ljWxO2Rg63mNxqQQAbDGkXRhlgsL7UjPqm2Nsy1bTNa0ZaQRg3vezG4agnCJTImrICY/0A==}
node-llama-cpp@3.15.1:
resolution: {integrity: sha512-/fBNkuLGR2Q8xj2eeV12KXKZ9vCS2+o6aP11lW40pB9H6f0B3wOALi/liFrjhHukAoiH6C9wFTPzv6039+5DRA==}
engines: {node: '>=20.0.0'}
hasBin: true
peerDependencies:
@@ -4634,6 +4630,11 @@ packages:
resolution: {integrity: sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==}
deprecated: This package is no longer supported.
npmlog@6.0.2:
resolution: {integrity: sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==}
engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0}
deprecated: This package is no longer supported.
nth-check@2.1.1:
resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==}
@@ -4720,9 +4721,9 @@ packages:
opusscript@0.1.1:
resolution: {integrity: sha512-mL0fZZOUnXdZ78woRXp18lApwpp0lF5tozJOD1Wut0dgrA9WuQTgSels/CSmFleaAZrJi/nci5KOVtbuxeWoQA==}
ora@9.3.0:
resolution: {integrity: sha512-lBX72MWFduWEf7v7uWf5DHp9Jn5BI8bNPGuFgtXMmr2uDz2Gz2749y3am3agSDdkhHPHYmmxEGSKH85ZLGzgXw==}
engines: {node: '>=20'}
ora@8.2.0:
resolution: {integrity: sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==}
engines: {node: '>=18'}
osc-progress@0.3.0:
resolution: {integrity: sha512-4/8JfsetakdeEa4vAYV45FW20aY+B/+K8NEXp5Eiar3wR8726whgHrbSg5Ar/ZY1FLJ/AGtUqV7W2IVF+Gvp9A==}
@@ -5235,10 +5236,6 @@ packages:
resolution: {integrity: sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==}
engines: {node: '>=18'}
slice-ansi@8.0.0:
resolution: {integrity: sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==}
engines: {node: '>=20'}
smart-buffer@4.2.0:
resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==}
engines: {node: '>= 6.0.0', npm: '>= 3.0.0'}
@@ -5312,8 +5309,8 @@ packages:
std-env@3.10.0:
resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==}
stdin-discarder@0.3.1:
resolution: {integrity: sha512-reExS1kSGoElkextOcPkel4NE99S0BWxjUHQeDFnR8S993JxpPX7KU4MNmO19NXhlJp+8dmdCbKQVNgLJh2teA==}
stdin-discarder@0.2.2:
resolution: {integrity: sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==}
engines: {node: '>=18'}
stdout-update@4.0.1:
@@ -5346,10 +5343,6 @@ packages:
resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==}
engines: {node: '>=18'}
string-width@8.2.0:
resolution: {integrity: sha512-6hJPQ8N0V0P3SNmP6h2J99RLuzrWz2gvT7VnK5tKvrNqJoyS9W4/Fb8mo31UiPvy00z7DQXkP2hnKBVav76thw==}
engines: {node: '>=20'}
string_decoder@1.1.1:
resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==}
@@ -5360,6 +5353,10 @@ packages:
resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
engines: {node: '>=8'}
strip-ansi@7.1.2:
resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==}
engines: {node: '>=12'}
strip-ansi@7.2.0:
resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==}
engines: {node: '>=12'}
@@ -5596,9 +5593,9 @@ packages:
resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==}
hasBin: true
validate-npm-package-name@7.0.2:
resolution: {integrity: sha512-hVDIBwsRruT73PbK7uP5ebUt+ezEtCmzZz3F59BSr2F6OVFnJ/6h8liuvdLrQ88Xmnk6/+xGGuq+pG9WwTuy3A==}
engines: {node: ^20.17.0 || >=22.9.0}
validate-npm-package-name@6.0.2:
resolution: {integrity: sha512-IUoow1YUtvoBBC06dXs8bR8B9vuA3aJfmQNKMoaPG/OFsPmoQvw8xh+6Ye25Gx9DQhoEom3Pcu9MKHerm/NpUQ==}
engines: {node: ^18.17.0 || >=20.5.0}
vary@1.1.2:
resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
@@ -5697,9 +5694,9 @@ packages:
engines: {node: '>= 8'}
hasBin: true
which@6.0.1:
resolution: {integrity: sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==}
engines: {node: ^20.17.0 || >=22.9.0}
which@5.0.0:
resolution: {integrity: sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==}
engines: {node: ^18.17.0 || >=20.5.0}
hasBin: true
why-is-node-running@2.3.0:
@@ -6729,7 +6726,7 @@ snapshots:
dependencies:
string-width: 5.1.2
string-width-cjs: string-width@4.2.3
strip-ansi: 7.2.0
strip-ansi: 7.1.2
strip-ansi-cjs: strip-ansi@6.0.1
wrap-ansi: 8.1.0
wrap-ansi-cjs: wrap-ansi@7.0.0
@@ -6806,7 +6803,7 @@ snapshots:
'@larksuiteoapi/node-sdk@1.59.0':
dependencies:
axios: 1.13.5
axios: 1.13.5(debug@4.4.3)
lodash.identity: 3.0.0
lodash.merge: 4.6.2
lodash.pickby: 4.6.0
@@ -6822,7 +6819,7 @@ snapshots:
dependencies:
'@types/node': 24.10.14
optionalDependencies:
axios: 1.13.5
axios: 1.13.5(debug@4.4.3)
transitivePeerDependencies:
- debug
@@ -7086,7 +7083,7 @@ snapshots:
'@azure/core-auth': 1.10.1
'@azure/msal-node': 5.0.5
'@microsoft/agents-activity': 1.3.1
axios: 1.13.5
axios: 1.13.5(debug@4.4.3)
jsonwebtoken: 9.0.3
jwks-rsa: 3.2.2
object-path: 0.11.8
@@ -7166,47 +7163,45 @@ snapshots:
'@noble/hashes@2.0.1': {}
'@node-llama-cpp/linux-arm64@3.16.2':
'@node-llama-cpp/linux-arm64@3.15.1':
optional: true
'@node-llama-cpp/linux-armv7l@3.16.2':
'@node-llama-cpp/linux-armv7l@3.15.1':
optional: true
'@node-llama-cpp/linux-x64-cuda-ext@3.16.2':
'@node-llama-cpp/linux-x64-cuda-ext@3.15.1':
optional: true
'@node-llama-cpp/linux-x64-cuda@3.16.2':
'@node-llama-cpp/linux-x64-cuda@3.15.1':
optional: true
'@node-llama-cpp/linux-x64-vulkan@3.16.2':
'@node-llama-cpp/linux-x64-vulkan@3.15.1':
optional: true
'@node-llama-cpp/linux-x64@3.16.2':
'@node-llama-cpp/linux-x64@3.15.1':
optional: true
'@node-llama-cpp/mac-arm64-metal@3.16.2':
'@node-llama-cpp/mac-arm64-metal@3.15.1':
optional: true
'@node-llama-cpp/mac-x64@3.16.2':
'@node-llama-cpp/mac-x64@3.15.1':
optional: true
'@node-llama-cpp/win-arm64@3.16.2':
'@node-llama-cpp/win-arm64@3.15.1':
optional: true
'@node-llama-cpp/win-x64-cuda-ext@3.16.2':
'@node-llama-cpp/win-x64-cuda-ext@3.15.1':
optional: true
'@node-llama-cpp/win-x64-cuda@3.16.2':
'@node-llama-cpp/win-x64-cuda@3.15.1':
optional: true
'@node-llama-cpp/win-x64-vulkan@3.16.2':
'@node-llama-cpp/win-x64-vulkan@3.15.1':
optional: true
'@node-llama-cpp/win-x64@3.16.2':
'@node-llama-cpp/win-x64@3.15.1':
optional: true
'@nolyfill/domexception@1.0.28': {}
'@octokit/app@16.1.2':
dependencies:
'@octokit/auth-app': 8.2.0
@@ -7943,7 +7938,7 @@ snapshots:
'@slack/types': 2.20.0
'@slack/web-api': 7.14.1
'@types/express': 5.0.6
axios: 1.13.5
axios: 1.13.5(debug@4.4.3)
express: 5.2.1
path-to-regexp: 8.3.0
raw-body: 3.0.2
@@ -7989,7 +7984,7 @@ snapshots:
'@slack/types': 2.20.0
'@types/node': 25.3.1
'@types/retry': 0.12.0
axios: 1.13.5
axios: 1.13.5(debug@4.4.3)
eventemitter3: 5.0.4
form-data: 2.5.4
is-electron: 2.2.2
@@ -8876,8 +8871,7 @@ snapshots:
json-bignum: 0.0.3
tslib: 2.8.1
aproba@2.1.0:
optional: true
aproba@2.1.0: {}
are-we-there-yet@2.0.0:
dependencies:
@@ -8885,6 +8879,11 @@ snapshots:
readable-stream: 3.6.2
optional: true
are-we-there-yet@3.0.1:
dependencies:
delegates: 1.0.0
readable-stream: 3.6.2
argparse@2.0.1: {}
array-back@3.1.0: {}
@@ -8953,9 +8952,9 @@ snapshots:
aws4@1.13.2: {}
axios@1.13.5:
axios@1.13.5(debug@4.4.3):
dependencies:
follow-redirects: 1.15.11
follow-redirects: 1.15.11(debug@4.4.3)
form-data: 2.5.4
proxy-from-env: 1.1.0
transitivePeerDependencies:
@@ -9103,8 +9102,6 @@ snapshots:
cli-spinners@2.9.2: {}
cli-spinners@3.4.0: {}
cliui@7.0.4:
dependencies:
string-width: 4.2.3
@@ -9117,16 +9114,19 @@ snapshots:
strip-ansi: 6.0.1
wrap-ansi: 7.0.0
cmake-js@8.0.0:
cmake-js@7.4.0:
dependencies:
axios: 1.13.5(debug@4.4.3)
debug: 4.4.3
fs-extra: 11.3.3
memory-stream: 1.0.0
node-api-headers: 1.8.0
npmlog: 6.0.2
rc: 1.2.8
semver: 7.7.4
tar: 7.5.9
url-join: 4.0.1
which: 6.0.1
which: 2.0.2
yargs: 17.7.2
transitivePeerDependencies:
- supports-color
@@ -9140,8 +9140,7 @@ snapshots:
color-name@1.1.4: {}
color-support@1.1.3:
optional: true
color-support@1.1.3: {}
combined-stream@1.0.8:
dependencies:
@@ -9167,8 +9166,7 @@ snapshots:
commander@14.0.3: {}
console-control-strings@1.1.0:
optional: true
console-control-strings@1.1.0: {}
content-disposition@0.5.4:
dependencies:
@@ -9240,8 +9238,7 @@ snapshots:
delayed-stream@1.0.0: {}
delegates@1.0.0:
optional: true
delegates@1.0.0: {}
depd@2.0.0: {}
@@ -9511,7 +9508,7 @@ snapshots:
fetch-blob@3.2.0:
dependencies:
node-domexception: '@nolyfill/domexception@1.0.28'
node-domexception: 1.0.0
web-streams-polyfill: 3.3.3
file-type@21.3.0:
@@ -9558,7 +9555,9 @@ snapshots:
flatbuffers@24.12.23: {}
follow-redirects@1.15.11: {}
follow-redirects@1.15.11(debug@4.4.3):
optionalDependencies:
debug: 4.4.3
foreground-child@3.3.1:
dependencies:
@@ -9616,13 +9615,16 @@ snapshots:
wide-align: 1.1.5
optional: true
gaxios@7.1.2:
gauge@4.0.4:
dependencies:
extend: 3.0.2
https-proxy-agent: 7.0.6
node-fetch: 3.3.2
transitivePeerDependencies:
- supports-color
aproba: 2.1.0
color-support: 1.1.3
console-control-strings: 1.1.0
has-unicode: 2.0.1
signal-exit: 3.0.7
string-width: 4.2.3
strip-ansi: 6.0.1
wide-align: 1.1.5
gaxios@7.1.3:
dependencies:
@@ -9635,7 +9637,7 @@ snapshots:
gcp-metadata@8.1.2:
dependencies:
gaxios: 7.1.2
gaxios: 7.1.3
google-logging-utils: 1.1.3
json-bigint: 1.0.0
transitivePeerDependencies:
@@ -9710,18 +9712,6 @@ snapshots:
path-is-absolute: 1.0.1
optional: true
google-auth-library@10.5.0:
dependencies:
base64-js: 1.5.1
ecdsa-sig-formatter: 1.0.11
gaxios: 7.1.2
gcp-metadata: 8.1.2
google-logging-utils: 1.1.3
gtoken: 8.0.0
jws: 4.0.1
transitivePeerDependencies:
- supports-color
google-auth-library@10.6.1:
dependencies:
base64-js: 1.5.1
@@ -9749,13 +9739,6 @@ snapshots:
- encoding
- supports-color
gtoken@8.0.0:
dependencies:
gaxios: 7.1.2
jws: 4.0.1
transitivePeerDependencies:
- supports-color
has-flag@4.0.0: {}
has-own@1.0.1: {}
@@ -9766,8 +9749,7 @@ snapshots:
dependencies:
has-symbols: 1.1.0
has-unicode@2.0.1:
optional: true
has-unicode@2.0.1: {}
hash.js@1.1.7:
dependencies:
@@ -9955,13 +9937,15 @@ snapshots:
is-typedarray@1.0.0: {}
is-unicode-supported@1.3.0: {}
is-unicode-supported@2.1.0: {}
isarray@1.0.0: {}
isexe@2.0.0: {}
isexe@4.0.0: {}
isexe@3.1.5: {}
isstream@0.1.2: {}
@@ -10195,6 +10179,11 @@ snapshots:
lodash@4.17.23: {}
log-symbols@6.0.0:
dependencies:
chalk: 5.6.2
is-unicode-supported: 1.3.0
log-symbols@7.0.1:
dependencies:
is-unicode-supported: 2.1.0
@@ -10271,6 +10260,10 @@ snapshots:
media-typer@1.1.0: {}
memory-stream@1.0.0:
dependencies:
readable-stream: 3.6.2
merge-descriptors@1.0.3: {}
merge-descriptors@2.0.0: {}
@@ -10367,6 +10360,8 @@ snapshots:
node-api-headers@1.8.0: {}
node-domexception@1.0.0: {}
node-downloader-helper@2.1.10: {}
node-edge-tts@1.2.10:
@@ -10389,14 +10384,14 @@ snapshots:
fetch-blob: 3.2.0
formdata-polyfill: 4.0.10
node-llama-cpp@3.16.2(typescript@5.9.3):
node-llama-cpp@3.15.1(typescript@5.9.3):
dependencies:
'@huggingface/jinja': 0.5.5
async-retry: 1.3.3
bytes: 3.1.2
chalk: 5.6.2
chmodrp: 1.0.2
cmake-js: 8.0.0
cmake-js: 7.4.0
cross-spawn: 7.0.6
env-var: 7.5.0
filenamify: 6.0.0
@@ -10409,31 +10404,31 @@ snapshots:
nanoid: 5.1.6
node-addon-api: 8.5.0
octokit: 5.0.5
ora: 9.3.0
ora: 8.2.0
pretty-ms: 9.3.0
proper-lockfile: 4.1.2
semver: 7.7.4
simple-git: 3.32.3
slice-ansi: 8.0.0
slice-ansi: 7.1.2
stdout-update: 4.0.1
strip-ansi: 7.2.0
validate-npm-package-name: 7.0.2
which: 6.0.1
validate-npm-package-name: 6.0.2
which: 5.0.0
yargs: 17.7.2
optionalDependencies:
'@node-llama-cpp/linux-arm64': 3.16.2
'@node-llama-cpp/linux-armv7l': 3.16.2
'@node-llama-cpp/linux-x64': 3.16.2
'@node-llama-cpp/linux-x64-cuda': 3.16.2
'@node-llama-cpp/linux-x64-cuda-ext': 3.16.2
'@node-llama-cpp/linux-x64-vulkan': 3.16.2
'@node-llama-cpp/mac-arm64-metal': 3.16.2
'@node-llama-cpp/mac-x64': 3.16.2
'@node-llama-cpp/win-arm64': 3.16.2
'@node-llama-cpp/win-x64': 3.16.2
'@node-llama-cpp/win-x64-cuda': 3.16.2
'@node-llama-cpp/win-x64-cuda-ext': 3.16.2
'@node-llama-cpp/win-x64-vulkan': 3.16.2
'@node-llama-cpp/linux-arm64': 3.15.1
'@node-llama-cpp/linux-armv7l': 3.15.1
'@node-llama-cpp/linux-x64': 3.15.1
'@node-llama-cpp/linux-x64-cuda': 3.15.1
'@node-llama-cpp/linux-x64-cuda-ext': 3.15.1
'@node-llama-cpp/linux-x64-vulkan': 3.15.1
'@node-llama-cpp/mac-arm64-metal': 3.15.1
'@node-llama-cpp/mac-x64': 3.15.1
'@node-llama-cpp/win-arm64': 3.15.1
'@node-llama-cpp/win-x64': 3.15.1
'@node-llama-cpp/win-x64-cuda': 3.15.1
'@node-llama-cpp/win-x64-cuda-ext': 3.15.1
'@node-llama-cpp/win-x64-vulkan': 3.15.1
typescript: 5.9.3
transitivePeerDependencies:
- supports-color
@@ -10471,6 +10466,13 @@ snapshots:
set-blocking: 2.0.0
optional: true
npmlog@6.0.2:
dependencies:
are-we-there-yet: 3.0.1
console-control-strings: 1.1.0
gauge: 4.0.4
set-blocking: 2.0.0
nth-check@2.1.1:
dependencies:
boolbase: 1.0.0
@@ -10535,7 +10537,7 @@ snapshots:
ws: 8.19.0
zod: 4.3.6
openclaw@2026.2.24(@napi-rs/canvas@0.1.95)(@types/express@5.0.6)(audio-decode@2.2.3)(hono@4.11.10)(node-llama-cpp@3.16.2(typescript@5.9.3)):
openclaw@2026.2.24(@napi-rs/canvas@0.1.95)(@types/express@5.0.6)(audio-decode@2.2.3)(hono@4.11.10)(node-llama-cpp@3.15.1(typescript@5.9.3)):
dependencies:
'@agentclientprotocol/sdk': 0.14.1(zod@4.3.6)
'@aws-sdk/client-bedrock': 3.998.0
@@ -10579,7 +10581,7 @@ snapshots:
long: 5.3.2
markdown-it: 14.1.1
node-edge-tts: 1.2.10
node-llama-cpp: 3.16.2(typescript@5.9.3)
node-llama-cpp: 3.15.1(typescript@5.9.3)
opusscript: 0.1.1
osc-progress: 0.3.0
pdfjs-dist: 5.4.624
@@ -10619,16 +10621,17 @@ snapshots:
opusscript@0.1.1: {}
ora@9.3.0:
ora@8.2.0:
dependencies:
chalk: 5.6.2
cli-cursor: 5.0.0
cli-spinners: 3.4.0
cli-spinners: 2.9.2
is-interactive: 2.0.0
is-unicode-supported: 2.1.0
log-symbols: 7.0.1
stdin-discarder: 0.3.1
string-width: 8.2.0
log-symbols: 6.0.0
stdin-discarder: 0.2.2
string-width: 7.2.0
strip-ansi: 7.2.0
osc-progress@0.3.0: {}
@@ -10999,7 +11002,6 @@ snapshots:
inherits: 2.0.4
string_decoder: 1.3.0
util-deprecate: 1.0.2
optional: true
readdirp@5.0.0: {}
@@ -11201,8 +11203,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
set-blocking@2.0.0:
optional: true
set-blocking@2.0.0: {}
setimmediate@1.0.5: {}
@@ -11319,11 +11320,6 @@ snapshots:
ansi-styles: 6.2.3
is-fullwidth-code-point: 5.1.0
slice-ansi@8.0.0:
dependencies:
ansi-styles: 6.2.3
is-fullwidth-code-point: 5.1.0
smart-buffer@4.2.0: {}
socks-proxy-agent@8.0.5:
@@ -11395,7 +11391,7 @@ snapshots:
std-env@3.10.0: {}
stdin-discarder@0.3.1: {}
stdin-discarder@0.2.2: {}
stdout-update@4.0.1:
dependencies:
@@ -11431,7 +11427,7 @@ snapshots:
dependencies:
eastasianwidth: 0.2.0
emoji-regex: 9.2.2
strip-ansi: 7.2.0
strip-ansi: 7.1.2
string-width@7.2.0:
dependencies:
@@ -11439,11 +11435,6 @@ snapshots:
get-east-asian-width: 1.5.0
strip-ansi: 7.2.0
string-width@8.2.0:
dependencies:
get-east-asian-width: 1.5.0
strip-ansi: 7.2.0
string_decoder@1.1.1:
dependencies:
safe-buffer: 5.1.2
@@ -11451,12 +11442,15 @@ snapshots:
string_decoder@1.3.0:
dependencies:
safe-buffer: 5.2.1
optional: true
strip-ansi@6.0.1:
dependencies:
ansi-regex: 5.0.1
strip-ansi@7.1.2:
dependencies:
ansi-regex: 6.2.2
strip-ansi@7.2.0:
dependencies:
ansi-regex: 6.2.2
@@ -11662,7 +11656,7 @@ snapshots:
uuid@8.3.2: {}
validate-npm-package-name@7.0.2: {}
validate-npm-package-name@6.0.2: {}
vary@1.1.2: {}
@@ -11740,9 +11734,9 @@ snapshots:
dependencies:
isexe: 2.0.0
which@6.0.1:
which@5.0.0:
dependencies:
isexe: 4.0.0
isexe: 3.1.5
why-is-node-running@2.3.0:
dependencies:
@@ -11752,7 +11746,6 @@ snapshots:
wide-align@1.1.5:
dependencies:
string-width: 4.2.3
optional: true
win-guid@0.2.1: {}
@@ -11768,7 +11761,7 @@ snapshots:
dependencies:
ansi-styles: 6.2.3
string-width: 5.1.2
strip-ansi: 7.2.0
strip-ansi: 7.1.2
wrappy@1.0.2: {}

View File

@@ -1,128 +0,0 @@
import type { AgentMessage } from "@mariozechner/pi-agent-core";
import type { ExtensionContext } from "@mariozechner/pi-coding-agent";
import * as piCodingAgent from "@mariozechner/pi-coding-agent";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { buildCompactionSummarizationInstructions, summarizeInStages } from "./compaction.js";
vi.mock("@mariozechner/pi-coding-agent", async (importOriginal) => {
const actual = await importOriginal<typeof piCodingAgent>();
return {
...actual,
generateSummary: vi.fn(),
};
});
const mockGenerateSummary = vi.mocked(piCodingAgent.generateSummary);
function makeMessage(index: number, size = 1200): AgentMessage {
return {
role: "user",
content: `m${index}-${"x".repeat(size)}`,
timestamp: index,
};
}
describe("compaction identifier-preservation instructions", () => {
const testModel = {
provider: "anthropic",
model: "claude-3-opus",
contextWindow: 200_000,
} as unknown as NonNullable<ExtensionContext["model"]>;
beforeEach(() => {
mockGenerateSummary.mockReset();
mockGenerateSummary.mockResolvedValue("summary");
});
it("injects identifier-preservation guidance even without custom instructions", async () => {
await summarizeInStages({
messages: [makeMessage(1), makeMessage(2)],
model: testModel,
apiKey: "test-key",
signal: new AbortController().signal,
reserveTokens: 4000,
maxChunkTokens: 8000,
contextWindow: 200_000,
});
expect(mockGenerateSummary).toHaveBeenCalled();
const firstCall = mockGenerateSummary.mock.calls[0];
expect(firstCall?.[5]).toContain("Preserve all opaque identifiers exactly as written");
expect(firstCall?.[5]).toContain("UUIDs");
expect(firstCall?.[5]).toContain("IPs");
expect(firstCall?.[5]).toContain("ports");
});
it("keeps identifier-preservation guidance when custom instructions are provided", async () => {
await summarizeInStages({
messages: [makeMessage(1), makeMessage(2)],
model: testModel,
apiKey: "test-key",
signal: new AbortController().signal,
reserveTokens: 4000,
maxChunkTokens: 8000,
contextWindow: 200_000,
customInstructions: "Focus on release-impacting bugs.",
});
const firstCall = mockGenerateSummary.mock.calls[0];
expect(firstCall?.[5]).toContain("Preserve all opaque identifiers exactly as written");
expect(firstCall?.[5]).toContain("Additional focus:");
expect(firstCall?.[5]).toContain("Focus on release-impacting bugs.");
});
it("applies identifier-preservation guidance on staged split + merge summarization", async () => {
await summarizeInStages({
messages: [makeMessage(1), makeMessage(2), makeMessage(3), makeMessage(4)],
model: testModel,
apiKey: "test-key",
signal: new AbortController().signal,
reserveTokens: 4000,
maxChunkTokens: 1000,
contextWindow: 200_000,
parts: 2,
minMessagesForSplit: 4,
});
expect(mockGenerateSummary.mock.calls.length).toBeGreaterThan(1);
for (const call of mockGenerateSummary.mock.calls) {
expect(call[5]).toContain("Preserve all opaque identifiers exactly as written");
}
});
it("avoids duplicate additional-focus headers in split+merge path", async () => {
await summarizeInStages({
messages: [makeMessage(1), makeMessage(2), makeMessage(3), makeMessage(4)],
model: testModel,
apiKey: "test-key",
signal: new AbortController().signal,
reserveTokens: 4000,
maxChunkTokens: 1000,
contextWindow: 200_000,
parts: 2,
minMessagesForSplit: 4,
customInstructions: "Prioritize customer-visible regressions.",
});
const mergedCall = mockGenerateSummary.mock.calls.at(-1);
const instructions = mergedCall?.[5] ?? "";
expect(instructions).toContain("Merge these partial summaries into a single cohesive summary.");
expect(instructions).toContain("Prioritize customer-visible regressions.");
expect((instructions.match(/Additional focus:/g) ?? []).length).toBe(1);
});
});
describe("buildCompactionSummarizationInstructions", () => {
it("returns base instructions when no custom text is provided", () => {
const result = buildCompactionSummarizationInstructions();
expect(result).toContain("Preserve all opaque identifiers exactly as written");
expect(result).not.toContain("Additional focus:");
});
it("appends custom instructions in a stable format", () => {
const result = buildCompactionSummarizationInstructions("Keep deployment details.");
expect(result).toContain("Preserve all opaque identifiers exactly as written");
expect(result).toContain("Additional focus:");
expect(result).toContain("Keep deployment details.");
});
});

View File

@@ -220,6 +220,7 @@ async function summarizeChunks(params: {
params.customInstructions,
params.summarizationInstructions,
);
for (const chunk of chunks) {
summary = await retryAsync(
() =>

View File

@@ -161,7 +161,7 @@ describe("applyExtraParamsToAgent", () => {
};
}
function runResponsesPayloadMutationCase(params: {
function runStoreMutationCase(params: {
applyProvider: string;
applyModelId: string;
model:
@@ -169,21 +169,14 @@ describe("applyExtraParamsToAgent", () => {
| Model<"openai-codex-responses">
| Model<"openai-completions">;
options?: SimpleStreamOptions;
cfg?: Record<string, unknown>;
payload?: Record<string, unknown>;
}) {
const payload = params.payload ?? { store: false };
const payload = { store: false };
const baseStreamFn: StreamFn = (_model, _context, options) => {
options?.onPayload?.(payload);
return {} as ReturnType<StreamFn>;
};
const agent = { streamFn: baseStreamFn };
applyExtraParamsToAgent(
agent,
params.cfg as Parameters<typeof applyExtraParamsToAgent>[1],
params.applyProvider,
params.applyModelId,
);
applyExtraParamsToAgent(agent, undefined, params.applyProvider, params.applyModelId);
const context: Context = { messages: [] };
void agent.streamFn?.(params.model, context, params.options ?? {});
return payload;
@@ -821,7 +814,7 @@ describe("applyExtraParamsToAgent", () => {
});
it("forces store=true for direct OpenAI Responses payloads", () => {
const payload = runResponsesPayloadMutationCase({
const payload = runStoreMutationCase({
applyProvider: "openai",
applyModelId: "gpt-5",
model: {
@@ -835,7 +828,7 @@ describe("applyExtraParamsToAgent", () => {
});
it("does not force store for OpenAI Responses routed through non-OpenAI base URLs", () => {
const payload = runResponsesPayloadMutationCase({
const payload = runStoreMutationCase({
applyProvider: "openai",
applyModelId: "gpt-5",
model: {
@@ -848,152 +841,11 @@ describe("applyExtraParamsToAgent", () => {
expect(payload.store).toBe(false);
});
it("does not force store for OpenAI Responses when baseUrl is empty", () => {
const payload = runResponsesPayloadMutationCase({
applyProvider: "openai",
applyModelId: "gpt-5",
model: {
api: "openai-responses",
provider: "openai",
id: "gpt-5",
baseUrl: "",
} as Model<"openai-responses">,
});
expect(payload.store).toBe(false);
});
it("does not force store for models that declare supportsStore=false", () => {
const payload = runResponsesPayloadMutationCase({
applyProvider: "azure-openai-responses",
applyModelId: "gpt-4o",
model: {
api: "openai-responses",
provider: "azure-openai-responses",
id: "gpt-4o",
baseUrl: "https://example.openai.azure.com/openai/v1",
compat: { supportsStore: false },
} as Model<"openai-responses">,
});
expect(payload.store).toBe(false);
});
it("auto-injects OpenAI Responses context_management compaction for direct OpenAI models", () => {
const payload = runResponsesPayloadMutationCase({
applyProvider: "openai",
applyModelId: "gpt-5",
model: {
api: "openai-responses",
provider: "openai",
id: "gpt-5",
baseUrl: "https://api.openai.com/v1",
contextWindow: 200_000,
} as Model<"openai-responses">,
});
expect(payload.context_management).toEqual([
{
type: "compaction",
compact_threshold: 140_000,
},
]);
});
it("does not auto-inject OpenAI Responses context_management for Azure by default", () => {
const payload = runResponsesPayloadMutationCase({
applyProvider: "azure-openai-responses",
applyModelId: "gpt-4o",
model: {
api: "openai-responses",
provider: "azure-openai-responses",
id: "gpt-4o",
baseUrl: "https://example.openai.azure.com/openai/v1",
} as Model<"openai-responses">,
});
expect(payload).not.toHaveProperty("context_management");
});
it("allows explicitly enabling OpenAI Responses context_management compaction", () => {
const payload = runResponsesPayloadMutationCase({
applyProvider: "azure-openai-responses",
applyModelId: "gpt-4o",
cfg: {
agents: {
defaults: {
models: {
"azure-openai-responses/gpt-4o": {
params: {
responsesServerCompaction: true,
responsesCompactThreshold: 42_000,
},
},
},
},
},
},
model: {
api: "openai-responses",
provider: "azure-openai-responses",
id: "gpt-4o",
baseUrl: "https://example.openai.azure.com/openai/v1",
} as Model<"openai-responses">,
});
expect(payload.context_management).toEqual([
{
type: "compaction",
compact_threshold: 42_000,
},
]);
});
it("preserves existing context_management payload values", () => {
const payload = runResponsesPayloadMutationCase({
applyProvider: "openai",
applyModelId: "gpt-5",
model: {
api: "openai-responses",
provider: "openai",
id: "gpt-5",
baseUrl: "https://api.openai.com/v1",
} as Model<"openai-responses">,
payload: {
store: false,
context_management: [{ type: "compaction", compact_threshold: 12_345 }],
},
});
expect(payload.context_management).toEqual([{ type: "compaction", compact_threshold: 12_345 }]);
});
it("allows disabling OpenAI Responses context_management compaction via model params", () => {
const payload = runResponsesPayloadMutationCase({
applyProvider: "openai",
applyModelId: "gpt-5",
cfg: {
agents: {
defaults: {
models: {
"openai/gpt-5": {
params: {
responsesServerCompaction: false,
},
},
},
},
},
},
model: {
api: "openai-responses",
provider: "openai",
id: "gpt-5",
baseUrl: "https://api.openai.com/v1",
} as Model<"openai-responses">,
});
expect(payload).not.toHaveProperty("context_management");
});
it.each([
{
name: "with openai-codex provider config",
run: () =>
runResponsesPayloadMutationCase({
runStoreMutationCase({
applyProvider: "openai-codex",
applyModelId: "codex-mini-latest",
model: {
@@ -1007,7 +859,7 @@ describe("applyExtraParamsToAgent", () => {
{
name: "without config via provider/model hints",
run: () =>
runResponsesPayloadMutationCase({
runStoreMutationCase({
applyProvider: "openai-codex",
applyModelId: "codex-mini-latest",
model: {

View File

@@ -186,7 +186,7 @@ function createBedrockNoCacheWrapper(baseStreamFn: StreamFn | undefined): Stream
function isDirectOpenAIBaseUrl(baseUrl: unknown): boolean {
if (typeof baseUrl !== "string" || !baseUrl.trim()) {
return false;
return true;
}
try {
@@ -208,13 +208,7 @@ function shouldForceResponsesStore(model: {
api?: unknown;
provider?: unknown;
baseUrl?: unknown;
compat?: { supportsStore?: boolean };
}): boolean {
// Never force store=true when the model explicitly declares supportsStore=false
// (e.g. Azure OpenAI Responses API without server-side persistence).
if (model.compat?.supportsStore === false) {
return false;
}
if (typeof model.api !== "string" || typeof model.provider !== "string") {
return false;
}
@@ -227,82 +221,19 @@ function shouldForceResponsesStore(model: {
return isDirectOpenAIBaseUrl(model.baseUrl);
}
function parsePositiveInteger(value: unknown): number | undefined {
if (typeof value === "number" && Number.isFinite(value) && value > 0) {
return Math.floor(value);
}
if (typeof value === "string") {
const parsed = Number.parseInt(value, 10);
if (Number.isFinite(parsed) && parsed > 0) {
return parsed;
}
}
return undefined;
}
function resolveOpenAIResponsesCompactThreshold(model: { contextWindow?: unknown }): number {
const contextWindow = parsePositiveInteger(model.contextWindow);
if (contextWindow) {
return Math.max(1_000, Math.floor(contextWindow * 0.7));
}
return 80_000;
}
function shouldEnableOpenAIResponsesServerCompaction(
model: {
api?: unknown;
provider?: unknown;
baseUrl?: unknown;
compat?: { supportsStore?: boolean };
},
extraParams: Record<string, unknown> | undefined,
): boolean {
const configured = extraParams?.responsesServerCompaction;
if (configured === false) {
return false;
}
if (!shouldForceResponsesStore(model)) {
return false;
}
if (configured === true) {
return true;
}
// Auto-enable for direct OpenAI Responses models.
return model.provider === "openai";
}
function createOpenAIResponsesContextManagementWrapper(
baseStreamFn: StreamFn | undefined,
extraParams: Record<string, unknown> | undefined,
): StreamFn {
function createOpenAIResponsesStoreWrapper(baseStreamFn: StreamFn | undefined): StreamFn {
const underlying = baseStreamFn ?? streamSimple;
return (model, context, options) => {
const forceStore = shouldForceResponsesStore(model);
const useServerCompaction = shouldEnableOpenAIResponsesServerCompaction(model, extraParams);
if (!forceStore && !useServerCompaction) {
if (!shouldForceResponsesStore(model)) {
return underlying(model, context, options);
}
const compactThreshold =
parsePositiveInteger(extraParams?.responsesCompactThreshold) ??
resolveOpenAIResponsesCompactThreshold(model);
const originalOnPayload = options?.onPayload;
return underlying(model, context, {
...options,
onPayload: (payload) => {
if (payload && typeof payload === "object") {
const payloadObj = payload as Record<string, unknown>;
if (forceStore) {
payloadObj.store = true;
}
if (useServerCompaction && payloadObj.context_management === undefined) {
payloadObj.context_management = [
{
type: "compaction",
compact_threshold: compactThreshold,
},
];
}
(payload as { store?: unknown }).store = true;
}
originalOnPayload?.(payload);
},
@@ -803,7 +734,7 @@ export function applyExtraParamsToAgent(
agent.streamFn = createGoogleThinkingPayloadWrapper(agent.streamFn, thinkingLevel);
// Work around upstream pi-ai hardcoding `store: false` for Responses API.
// Force `store=true` for direct OpenAI Responses models and auto-enable
// server-side compaction for compatible OpenAI Responses payloads.
agent.streamFn = createOpenAIResponsesContextManagementWrapper(agent.streamFn, merged);
// Force `store=true` for direct OpenAI/OpenAI Codex providers so multi-turn
// server-side conversation state is preserved.
agent.streamFn = createOpenAIResponsesStoreWrapper(agent.streamFn);
}

View File

@@ -1,81 +0,0 @@
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import type { OpenClawConfig } from "../config/config.js";
import { getMemorySearchManager, type MemoryIndexManager } from "./index.js";
import "./test-runtime-mocks.js";
const hoisted = vi.hoisted(() => ({
providerCreateCalls: 0,
providerDelayMs: 0,
}));
vi.mock("./embeddings.js", () => ({
createEmbeddingProvider: async () => {
hoisted.providerCreateCalls += 1;
if (hoisted.providerDelayMs > 0) {
await new Promise((resolve) => setTimeout(resolve, hoisted.providerDelayMs));
}
return {
requestedProvider: "openai",
provider: {
id: "mock",
model: "mock-embed",
maxInputTokens: 8192,
embedQuery: async () => [0, 1, 0],
embedBatch: async (texts: string[]) => texts.map(() => [0, 1, 0]),
},
};
},
}));
describe("memory manager cache hydration", () => {
let workspaceDir = "";
beforeEach(async () => {
workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-mem-concurrent-"));
await fs.mkdir(path.join(workspaceDir, "memory"), { recursive: true });
await fs.writeFile(path.join(workspaceDir, "MEMORY.md"), "Hello memory.");
hoisted.providerCreateCalls = 0;
hoisted.providerDelayMs = 50;
});
afterEach(async () => {
await fs.rm(workspaceDir, { recursive: true, force: true });
});
it("deduplicates concurrent manager creation for the same cache key", async () => {
const indexPath = path.join(workspaceDir, "index.sqlite");
const cfg = {
agents: {
defaults: {
workspace: workspaceDir,
memorySearch: {
provider: "openai",
model: "mock-embed",
store: { path: indexPath, vector: { enabled: false } },
sync: { watch: false, onSessionStart: false, onSearch: false },
},
},
list: [{ id: "main", default: true }],
},
} as OpenClawConfig;
const results = await Promise.all(
Array.from(
{ length: 12 },
async () => await getMemorySearchManager({ cfg, agentId: "main" }),
),
);
const managers = results
.map((result) => result.manager)
.filter((manager): manager is MemoryIndexManager => Boolean(manager));
expect(managers).toHaveLength(12);
expect(new Set(managers).size).toBe(1);
expect(hoisted.providerCreateCalls).toBe(1);
await managers[0].close();
});
});

View File

@@ -1,154 +0,0 @@
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import type { DatabaseSync } from "node:sqlite";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import type { OpenClawConfig } from "../config/config.js";
import { resetEmbeddingMocks } from "./embedding.test-mocks.js";
import type { MemoryIndexManager } from "./index.js";
import { getRequiredMemoryIndexManager } from "./test-manager-helpers.js";
describe("memory manager readonly recovery", () => {
let workspaceDir = "";
let indexPath = "";
let manager: MemoryIndexManager | null = null;
beforeEach(async () => {
resetEmbeddingMocks();
workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-mem-readonly-"));
indexPath = path.join(workspaceDir, "index.sqlite");
await fs.mkdir(path.join(workspaceDir, "memory"), { recursive: true });
await fs.writeFile(path.join(workspaceDir, "MEMORY.md"), "Hello memory.");
});
afterEach(async () => {
if (manager) {
await manager.close();
manager = null;
}
await fs.rm(workspaceDir, { recursive: true, force: true });
});
it("reopens sqlite and retries once when sync hits SQLITE_READONLY", async () => {
const cfg = {
agents: {
defaults: {
workspace: workspaceDir,
memorySearch: {
provider: "openai",
model: "mock-embed",
store: { path: indexPath },
sync: { watch: false, onSessionStart: false, onSearch: false },
},
},
list: [{ id: "main", default: true }],
},
} as OpenClawConfig;
manager = await getRequiredMemoryIndexManager({ cfg, agentId: "main" });
const runSyncSpy = vi.spyOn(
manager as unknown as {
runSync: (params?: { reason?: string; force?: boolean }) => Promise<void>;
},
"runSync",
);
runSyncSpy
.mockRejectedValueOnce(new Error("attempt to write a readonly database"))
.mockResolvedValueOnce(undefined);
const openDatabaseSpy = vi.spyOn(
manager as unknown as { openDatabase: () => DatabaseSync },
"openDatabase",
);
await manager.sync({ reason: "test" });
expect(runSyncSpy).toHaveBeenCalledTimes(2);
expect(openDatabaseSpy).toHaveBeenCalledTimes(1);
expect(manager.status().custom?.readonlyRecovery).toEqual({
attempts: 1,
successes: 1,
failures: 0,
lastError: "attempt to write a readonly database",
});
});
it("reopens sqlite and retries when readonly appears in error code", async () => {
const cfg = {
agents: {
defaults: {
workspace: workspaceDir,
memorySearch: {
provider: "openai",
model: "mock-embed",
store: { path: indexPath },
sync: { watch: false, onSessionStart: false, onSearch: false },
},
},
list: [{ id: "main", default: true }],
},
} as OpenClawConfig;
manager = await getRequiredMemoryIndexManager({ cfg, agentId: "main" });
const runSyncSpy = vi.spyOn(
manager as unknown as {
runSync: (params?: { reason?: string; force?: boolean }) => Promise<void>;
},
"runSync",
);
runSyncSpy
.mockRejectedValueOnce({ message: "write failed", code: "SQLITE_READONLY" })
.mockResolvedValueOnce(undefined);
const openDatabaseSpy = vi.spyOn(
manager as unknown as { openDatabase: () => DatabaseSync },
"openDatabase",
);
await manager.sync({ reason: "test" });
expect(runSyncSpy).toHaveBeenCalledTimes(2);
expect(openDatabaseSpy).toHaveBeenCalledTimes(1);
expect(manager.status().custom?.readonlyRecovery).toEqual({
attempts: 1,
successes: 1,
failures: 0,
lastError: "write failed",
});
});
it("does not retry non-readonly sync errors", async () => {
const cfg = {
agents: {
defaults: {
workspace: workspaceDir,
memorySearch: {
provider: "openai",
model: "mock-embed",
store: { path: indexPath },
sync: { watch: false, onSessionStart: false, onSearch: false },
},
},
list: [{ id: "main", default: true }],
},
} as OpenClawConfig;
manager = await getRequiredMemoryIndexManager({ cfg, agentId: "main" });
const runSyncSpy = vi.spyOn(
manager as unknown as {
runSync: (params?: { reason?: string; force?: boolean }) => Promise<void>;
},
"runSync",
);
runSyncSpy.mockRejectedValueOnce(new Error("embedding timeout"));
const openDatabaseSpy = vi.spyOn(
manager as unknown as { openDatabase: () => DatabaseSync },
"openDatabase",
);
await expect(manager.sync({ reason: "test" })).rejects.toThrow("embedding timeout");
expect(runSyncSpy).toHaveBeenCalledTimes(1);
expect(openDatabaseSpy).toHaveBeenCalledTimes(0);
});
});

View File

@@ -39,7 +39,6 @@ const BATCH_FAILURE_LIMIT = 2;
const log = createSubsystemLogger("memory");
const INDEX_CACHE = new Map<string, MemoryIndexManager>();
const INDEX_CACHE_PENDING = new Map<string, Promise<MemoryIndexManager>>();
export class MemoryIndexManager extends MemoryManagerEmbeddingOps implements MemorySearchManager {
private readonly cacheKey: string;
@@ -100,10 +99,6 @@ export class MemoryIndexManager extends MemoryManagerEmbeddingOps implements Mem
>();
private sessionWarm = new Set<string>();
private syncing: Promise<void> | null = null;
private readonlyRecoveryAttempts = 0;
private readonlyRecoverySuccesses = 0;
private readonlyRecoveryFailures = 0;
private readonlyRecoveryLastError?: string;
static async get(params: {
cfg: OpenClawConfig;
@@ -121,44 +116,26 @@ export class MemoryIndexManager extends MemoryManagerEmbeddingOps implements Mem
if (existing) {
return existing;
}
const pending = INDEX_CACHE_PENDING.get(key);
if (pending) {
return pending;
}
const createPromise = (async () => {
const providerResult = await createEmbeddingProvider({
config: cfg,
agentDir: resolveAgentDir(cfg, agentId),
provider: settings.provider,
remote: settings.remote,
model: settings.model,
fallback: settings.fallback,
local: settings.local,
});
const refreshed = INDEX_CACHE.get(key);
if (refreshed) {
return refreshed;
}
const manager = new MemoryIndexManager({
cacheKey: key,
cfg,
agentId,
workspaceDir,
settings,
providerResult,
purpose: params.purpose,
});
INDEX_CACHE.set(key, manager);
return manager;
})();
INDEX_CACHE_PENDING.set(key, createPromise);
try {
return await createPromise;
} finally {
if (INDEX_CACHE_PENDING.get(key) === createPromise) {
INDEX_CACHE_PENDING.delete(key);
}
}
const providerResult = await createEmbeddingProvider({
config: cfg,
agentDir: resolveAgentDir(cfg, agentId),
provider: settings.provider,
remote: settings.remote,
model: settings.model,
fallback: settings.fallback,
local: settings.local,
});
const manager = new MemoryIndexManager({
cacheKey: key,
cfg,
agentId,
workspaceDir,
settings,
providerResult,
purpose: params.purpose,
});
INDEX_CACHE.set(key, manager);
return manager;
}
private constructor(params: {
@@ -411,97 +388,12 @@ export class MemoryIndexManager extends MemoryManagerEmbeddingOps implements Mem
if (this.syncing) {
return this.syncing;
}
this.syncing = this.runSyncWithReadonlyRecovery(params).finally(() => {
this.syncing = this.runSync(params).finally(() => {
this.syncing = null;
});
return this.syncing ?? Promise.resolve();
}
private isReadonlyDbError(err: unknown): boolean {
const readonlyPattern =
/attempt to write a readonly database|database is read-only|SQLITE_READONLY/i;
const messages = new Set<string>();
const pushValue = (value: unknown): void => {
if (typeof value !== "string") {
return;
}
const normalized = value.trim();
if (!normalized) {
return;
}
messages.add(normalized);
};
pushValue(err instanceof Error ? err.message : String(err));
if (err && typeof err === "object") {
const record = err as Record<string, unknown>;
pushValue(record.message);
pushValue(record.code);
pushValue(record.name);
if (record.cause && typeof record.cause === "object") {
const cause = record.cause as Record<string, unknown>;
pushValue(cause.message);
pushValue(cause.code);
pushValue(cause.name);
}
}
return [...messages].some((value) => readonlyPattern.test(value));
}
private extractErrorReason(err: unknown): string {
if (err instanceof Error && err.message.trim()) {
return err.message;
}
if (err && typeof err === "object") {
const record = err as Record<string, unknown>;
if (typeof record.message === "string" && record.message.trim()) {
return record.message;
}
if (typeof record.code === "string" && record.code.trim()) {
return record.code;
}
}
return String(err);
}
private async runSyncWithReadonlyRecovery(params?: {
reason?: string;
force?: boolean;
progress?: (update: MemorySyncProgressUpdate) => void;
}): Promise<void> {
try {
await this.runSync(params);
return;
} catch (err) {
if (!this.isReadonlyDbError(err) || this.closed) {
throw err;
}
const reason = this.extractErrorReason(err);
this.readonlyRecoveryAttempts += 1;
this.readonlyRecoveryLastError = reason;
log.warn(`memory sync readonly handle detected; reopening sqlite connection`, { reason });
try {
this.db.close();
} catch {}
this.db = this.openDatabase();
this.vectorReady = null;
this.vector.available = null;
this.vector.loadError = undefined;
this.ensureSchema();
const meta = this.readMeta();
this.vector.dims = meta?.vectorDims;
try {
await this.runSync(params);
this.readonlyRecoverySuccesses += 1;
} catch (retryErr) {
this.readonlyRecoveryFailures += 1;
throw retryErr;
}
}
}
async readFile(params: {
relPath: string;
from?: number;
@@ -679,12 +571,6 @@ export class MemoryIndexManager extends MemoryManagerEmbeddingOps implements Mem
custom: {
searchMode,
providerUnavailableReason: this.providerUnavailableReason,
readonlyRecovery: {
attempts: this.readonlyRecoveryAttempts,
successes: this.readonlyRecoverySuccesses,
failures: this.readonlyRecoveryFailures,
lastError: this.readonlyRecoveryLastError,
},
},
};
}