mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-11 16:41:22 +08:00
Compare commits
89 Commits
fix/launch
...
vincentkoc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
99de26761d | ||
|
|
0225e4c110 | ||
|
|
162232ae2f | ||
|
|
ae824ab269 | ||
|
|
4808bf526e | ||
|
|
75c71eb18e | ||
|
|
e5af902dda | ||
|
|
2209cc5832 | ||
|
|
fce77e2d45 | ||
|
|
d0df6f3a4c | ||
|
|
e5ef7cbdab | ||
|
|
d59eb6db5b | ||
|
|
6fac513119 | ||
|
|
57b90adbf2 | ||
|
|
0cf4c46004 | ||
|
|
1695b9203c | ||
|
|
7cc3cc0687 | ||
|
|
0413f9576e | ||
|
|
559b38d507 | ||
|
|
2ca87d06f7 | ||
|
|
09f678599d | ||
|
|
2b8828ea46 | ||
|
|
dbabaa0fb2 | ||
|
|
daf0ade96b | ||
|
|
6c11b4378a | ||
|
|
ddc3a3fc71 | ||
|
|
56218dcc21 | ||
|
|
38068de8e9 | ||
|
|
83b453f48a | ||
|
|
98d52062e7 | ||
|
|
64910240b9 | ||
|
|
bd4d7f6137 | ||
|
|
1b8f800487 | ||
|
|
8cb688c44d | ||
|
|
9bde7ef39f | ||
|
|
3c4377651e | ||
|
|
41a39085d3 | ||
|
|
2220a58ff7 | ||
|
|
e383257552 | ||
|
|
fa6436eaf3 | ||
|
|
1809b92f15 | ||
|
|
4006e388e7 | ||
|
|
b22d596e59 | ||
|
|
38f192a29c | ||
|
|
95ed5781f6 | ||
|
|
d275df82a2 | ||
|
|
32f6501dbd | ||
|
|
ce44b366db | ||
|
|
2cb063b72e | ||
|
|
5d82c2cc89 | ||
|
|
78a1644a8a | ||
|
|
d6b26e22d5 | ||
|
|
0d607942d5 | ||
|
|
cc919d3856 | ||
|
|
8948ed8e33 | ||
|
|
648213653d | ||
|
|
cec7006abb | ||
|
|
2d8c8e7f26 | ||
|
|
e2c8c27c8c | ||
|
|
dad107630e | ||
|
|
0d597ab800 | ||
|
|
03bc43a503 | ||
|
|
8e506634b1 | ||
|
|
27d2ac8460 | ||
|
|
fbc8c19e52 | ||
|
|
a7e5c7c18b | ||
|
|
def4b221d9 | ||
|
|
40542aba96 | ||
|
|
f34b0e6c42 | ||
|
|
84064d08d3 | ||
|
|
022923be15 | ||
|
|
a95cce9f2f | ||
|
|
81bd382e6c | ||
|
|
5b28093b01 | ||
|
|
f1449a5590 | ||
|
|
3fe81fdb96 | ||
|
|
ccd6043240 | ||
|
|
f3d6a3018a | ||
|
|
df31d0e8b6 | ||
|
|
65623e1559 | ||
|
|
b0973880b4 | ||
|
|
98aa2a8cf0 | ||
|
|
f7eccaee4a | ||
|
|
4b694d565d | ||
|
|
7875fb6c27 | ||
|
|
43451ebab7 | ||
|
|
017b389549 | ||
|
|
0504fb35fe | ||
|
|
817fa5462b |
@@ -41,3 +41,5 @@ pattern = grep -q 'N[O]DE_COMPILE_CACHE=/var/tmp/openclaw-compile-cache' ~/.bash
|
||||
pattern = env: \{ MISTRAL_API_K[E]Y: "sk-\.\.\." \},
|
||||
pattern = "ap[i]Key": "xxxxx",
|
||||
pattern = ap[i]Key: "A[I]za\.\.\.",
|
||||
# Sparkle appcast signatures are release metadata, not credentials.
|
||||
pattern = sparkle:edSignature="[A-Za-z0-9+/=]+"
|
||||
|
||||
@@ -71,6 +71,8 @@ repos:
|
||||
- 'ap[i]Key: "A[I]za\.\.\.",'
|
||||
- --exclude-lines
|
||||
- '"ap[i]Key": "(resolved|normalized|legacy)-key"(,)?'
|
||||
- --exclude-lines
|
||||
- 'sparkle:edSignature="[A-Za-z0-9+/=]+"'
|
||||
# Shell script linting
|
||||
- repo: https://github.com/koalaman/shellcheck-precommit
|
||||
rev: v0.11.0
|
||||
|
||||
@@ -153,7 +153,8 @@
|
||||
"env: \\{ MISTRAL_API_K[E]Y: \"sk-\\.\\.\\.\" \\},",
|
||||
"\"ap[i]Key\": \"xxxxx\"(,)?",
|
||||
"ap[i]Key: \"A[I]za\\.\\.\\.\",",
|
||||
"\"ap[i]Key\": \"(resolved|normalized|legacy)-key\"(,)?"
|
||||
"\"ap[i]Key\": \"(resolved|normalized|legacy)-key\"(,)?",
|
||||
"sparkle:edSignature=\"[A-Za-z0-9+/=]+\""
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -180,29 +181,6 @@
|
||||
"line_number": 15
|
||||
}
|
||||
],
|
||||
"appcast.xml": [
|
||||
{
|
||||
"type": "Base64 High Entropy String",
|
||||
"filename": "appcast.xml",
|
||||
"hashed_secret": "7afea670e53d801f1f881c99c40aa177e3395bfa",
|
||||
"is_verified": false,
|
||||
"line_number": 365
|
||||
},
|
||||
{
|
||||
"type": "Base64 High Entropy String",
|
||||
"filename": "appcast.xml",
|
||||
"hashed_secret": "6e1ba26139ac4e73427e68a7eec2abf96bcf1fd4",
|
||||
"is_verified": false,
|
||||
"line_number": 584
|
||||
},
|
||||
{
|
||||
"type": "Base64 High Entropy String",
|
||||
"filename": "appcast.xml",
|
||||
"hashed_secret": "c0baa9660a8d3b11874c63a535d8369f4a8fa8fa",
|
||||
"is_verified": false,
|
||||
"line_number": 723
|
||||
}
|
||||
],
|
||||
"apps/android/app/src/test/java/ai/openclaw/android/node/AppUpdateHandlerTest.kt": [
|
||||
{
|
||||
"type": "Hex High Entropy String",
|
||||
@@ -12933,14 +12911,14 @@
|
||||
"filename": "src/telegram/monitor.test.ts",
|
||||
"hashed_secret": "e5e9fa1ba31ecd1ae84f75caaa474f3a663f05f4",
|
||||
"is_verified": false,
|
||||
"line_number": 450
|
||||
"line_number": 497
|
||||
},
|
||||
{
|
||||
"type": "Secret Keyword",
|
||||
"filename": "src/telegram/monitor.test.ts",
|
||||
"hashed_secret": "5934c4d4a4fa5d66ddb3d3fc0bba84996c17a5b7",
|
||||
"is_verified": false,
|
||||
"line_number": 641
|
||||
"line_number": 688
|
||||
}
|
||||
],
|
||||
"src/telegram/webhook.test.ts": [
|
||||
@@ -13035,5 +13013,5 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"generated_at": "2026-03-09T01:11:58Z"
|
||||
"generated_at": "2026-03-09T08:37:13Z"
|
||||
}
|
||||
|
||||
@@ -48,4 +48,4 @@
|
||||
--allman false
|
||||
|
||||
# Exclusions
|
||||
--exclude .build,.swiftpm,DerivedData,node_modules,dist,coverage,xcuserdata,Peekaboo,Swabble,apps/android,apps/ios,apps/shared,apps/macos/Sources/MoltbotProtocol
|
||||
--exclude .build,.swiftpm,DerivedData,node_modules,dist,coverage,xcuserdata,Peekaboo,Swabble,apps/android,apps/ios,apps/shared,apps/macos/Sources/MoltbotProtocol,apps/macos/Sources/OpenClaw/HostEnvSecurityPolicy.generated.swift
|
||||
|
||||
@@ -19,6 +19,8 @@ excluded:
|
||||
- "*.playground"
|
||||
# Generated (protocol-gen-swift.ts)
|
||||
- apps/macos/Sources/MoltbotProtocol/GatewayModels.swift
|
||||
# Generated (generate-host-env-security-policy-swift.mjs)
|
||||
- apps/macos/Sources/OpenClaw/HostEnvSecurityPolicy.generated.swift
|
||||
|
||||
analyzer_rules:
|
||||
- unused_declaration
|
||||
|
||||
91
CHANGELOG.md
91
CHANGELOG.md
@@ -4,70 +4,85 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
## Unreleased
|
||||
|
||||
### Fixes
|
||||
|
||||
- Browser/SSRF: block private-network intermediate redirect hops in strict browser navigation flows and fail closed when remote tab-open paths cannot inspect redirect chains. Thanks @zpbrent.
|
||||
- MS Teams/authz: keep `groupPolicy: "allowlist"` enforcing sender allowlists even when a team/channel route allowlist is configured, so route matches no longer widen group access to every sender in that route. Thanks @zpbrent.
|
||||
|
||||
## 2026.3.8
|
||||
|
||||
### Changes
|
||||
|
||||
- Extensions/ACPX tests: move the shared runtime fixture helper from `src/runtime-internals/` to `src/test-utils/` so the test-only helper no longer looks like shipped runtime code.
|
||||
- TUI: infer the active agent from the current workspace when launched inside a configured agent workspace, while preserving explicit `agent:` session targets. (#39591) thanks @arceus77-7.
|
||||
- Tools/Brave web search: add opt-in `tools.web.search.brave.mode: "llm-context"` so `web_search` can call Brave's LLM Context endpoint and return extracted grounding snippets with source metadata, plus config/docs/test coverage. (#33383) Thanks @thirumaleshp.
|
||||
- Talk mode: add top-level `talk.silenceTimeoutMs` config so Talk waits a configurable amount of silence before auto-sending the current transcript, while keeping each platform's existing default pause window when unset. (#39607) Thanks @danodoesdesign. Fixes #17147.
|
||||
- CLI/install: include the short git commit hash in `openclaw --version` output when metadata is available, and keep installer version checks compatible with the decorated format. (#39712) thanks @sourman.
|
||||
- Docs/Web search: restore $5/month free-credit details, replace defunct "Data for Search"/"Data for AI" plan names with current "Search" plan, and note legacy subscription validity in Brave setup docs. Follows up on #26860. (#40111) Thanks @remusao.
|
||||
- macOS/onboarding: add a remote gateway token field for remote mode, preserve existing non-plaintext `gateway.remote.token` config values until explicitly replaced, and warn when the loaded token shape cannot be used directly from the macOS app. (#40187, supersedes #34614) Thanks @cgdusek.
|
||||
- CLI/backup: add `openclaw backup create` and `openclaw backup verify` for local state archives, including `--only-config`, `--no-include-workspace`, manifest/payload validation, and backup guidance in destructive flows. (#40163) thanks @shichangs.
|
||||
- CLI/backup: improve archive naming for date sorting, add config-only backup mode, and harden backup planning, publication, and verification edge cases. (#40163) Thanks @gumadeiras.
|
||||
- ACP/Provenance: add optional ACP ingress provenance metadata and visible receipt injection (`openclaw acp --provenance off|meta|meta+receipt`) so OpenClaw agents can retain and report ACP-origin context with session trace IDs. (#40473) thanks @mbelinky.
|
||||
- Tools/web search: alphabetize provider ordering across runtime selection, onboarding/configure pickers, and config metadata, so provider lists stay neutral and multi-key auto-detect now prefers Grok before Kimi. (#40259) thanks @kesku.
|
||||
|
||||
### Breaking
|
||||
|
||||
### Fixes
|
||||
|
||||
- Docker/runtime image: prune dev dependencies, strip build-only dist metadata for smaller Docker images. (#40307) Thanks @vincentkoc.
|
||||
- Plugins/channel onboarding: prefer bundled channel plugins over duplicate npm-installed copies during onboarding and release-channel sync, preventing bundled plugins from being shadowed by npm installs with the same plugin ID. (#40092)
|
||||
- Feishu/plugin onboarding: clear the short-lived plugin discovery cache before reloading the registry after installing a channel plugin, so onboarding no longer re-prompts to download Feishu immediately after a successful install. Fixes #39642. (#39752) Thanks @GazeKingNuWu.
|
||||
- macOS/LaunchAgent install: tighten LaunchAgent directory and plist permissions during install so launchd bootstrap does not fail when the target home path or generated plist inherited group/world-writable modes.
|
||||
- Gateway/Control UI: keep dashboard auth tokens in session-scoped browser storage so same-tab refreshes preserve remote token auth without restoring long-lived localStorage token persistence, while scoping tokens to the selected gateway URL and fragment-only bootstrap flow. (#40892) thanks @velvet-shark.
|
||||
- Models/Kimi Coding: send `anthropic-messages` tools in native Anthropic format again so `kimi-coding` stops degrading tool calls into XML/plain-text pseudo invocations instead of real `tool_use` blocks. (#38669, #39907, #40552) Thanks @opriz.
|
||||
- Context engine/tests: add bundled-registry regression coverage for cross-chunk resolution, plugin-sdk re-exports, and concurrent chunk registration. (#40460) thanks @dsantoreis.
|
||||
- Agents/embedded runner: bound compaction retry waiting and drain embedded runs during SIGUSR1 restart so session lanes recover instead of staying blocked behind compaction. (#40324) thanks @cgdusek.
|
||||
- ACP/sessions.patch: allow `spawnedBy` and `spawnDepth` lineage fields on ACP session keys so `sessions_spawn` with `runtime: "acp"` no longer fails during child-session setup. Fixes #40971. (#40995) thanks @xaeon2026.
|
||||
- ACP/stop reason mapping: resolve gateway chat `state: "error"` completions as ACP `end_turn` instead of `refusal` so transient backend failures are not surfaced as deliberate refusals. (#41187) thanks @pejmanjohn.
|
||||
- ACP/setSessionMode: propagate gateway `sessions.patch` failures back to ACP clients so rejected mode changes no longer return silent success. (#41185) thanks @pejmanjohn.
|
||||
|
||||
## 2026.3.8
|
||||
|
||||
### Changes
|
||||
|
||||
- CLI/backup: add `openclaw backup create` and `openclaw backup verify` for local state archives, including `--only-config`, `--no-include-workspace`, manifest/payload validation, and backup guidance in destructive flows. (#40163) thanks @shichangs.
|
||||
- macOS/onboarding: add a remote gateway token field for remote mode, preserve existing non-plaintext `gateway.remote.token` config values until explicitly replaced, and warn when the loaded token shape cannot be used directly from the macOS app. (#40187, supersedes #34614) Thanks @cgdusek.
|
||||
- Talk mode: add top-level `talk.silenceTimeoutMs` config so Talk waits a configurable amount of silence before auto-sending the current transcript, while keeping each platform's existing default pause window when unset. (#39607) Thanks @danodoesdesign. Fixes #17147.
|
||||
- TUI: infer the active agent from the current workspace when launched inside a configured agent workspace, while preserving explicit `agent:` session targets. (#39591) thanks @arceus77-7.
|
||||
- Tools/Brave web search: add opt-in `tools.web.search.brave.mode: "llm-context"` so `web_search` can call Brave's LLM Context endpoint and return extracted grounding snippets with source metadata, plus config/docs/test coverage. (#33383) Thanks @thirumaleshp.
|
||||
- CLI/install: include the short git commit hash in `openclaw --version` output when metadata is available, and keep installer version checks compatible with the decorated format. (#39712) thanks @sourman.
|
||||
- CLI/backup: improve archive naming for date sorting, add config-only backup mode, and harden backup planning, publication, and verification edge cases. (#40163) Thanks @gumadeiras.
|
||||
- ACP/Provenance: add optional ACP ingress provenance metadata and visible receipt injection (`openclaw acp --provenance off|meta|meta+receipt`) so OpenClaw agents can retain and report ACP-origin context with session trace IDs. (#40473) thanks @mbelinky.
|
||||
- Tools/web search: alphabetize provider ordering across runtime selection, onboarding/configure pickers, and config metadata, so provider lists stay neutral and multi-key auto-detect now prefers Grok before Kimi. (#40259) thanks @kesku.
|
||||
- Docs/Web search: restore $5/month free-credit details, replace defunct "Data for Search"/"Data for AI" plan names with current "Search" plan, and note legacy subscription validity in Brave setup docs. Follows up on #26860. (#40111) Thanks @remusao.
|
||||
- Extensions/ACPX tests: move the shared runtime fixture helper from `src/runtime-internals/` to `src/test-utils/` so the test-only helper no longer looks like shipped runtime code.
|
||||
|
||||
### Fixes
|
||||
|
||||
- Update/macOS launchd restart: re-enable disabled LaunchAgent services before updater bootstrap so `openclaw update` can recover from a disabled gateway service instead of leaving the restart step stuck.
|
||||
- macOS app/chat UI: route browser proxy through the local node browser service, preserve plain-text paste semantics, strip completed assistant trace/debug wrapper noise from transcripts, refresh permission state after returning from System Settings, and tolerate malformed cron rows in the macOS tab. (#39516) Thanks @Imhermes1.
|
||||
- Mattermost replies: keep `root_id` pinned to the existing thread root when an agent replies inside a thread, while still using reply-target threading for top-level posts. (#27744) thanks @hnykda.
|
||||
- Agents/failover: detect Amazon Bedrock `Too many tokens per day` quota errors as rate limits across fallback, cron retry, and memory embeddings while keeping context-window `too many tokens per request` errors out of the rate-limit lane. (#39377) Thanks @gambletan.
|
||||
- Android/Play distribution: remove self-update, background location, `screen.record`, and background mic capture from the Android app, narrow the foreground service to `dataSync` only, and clean up the legacy `location.enabledMode=always` preference migration. (#39660) Thanks @obviyus.
|
||||
- Telegram/DM routing: dedupe inbound Telegram DMs per agent instead of per session key so the same DM cannot trigger duplicate replies when both `agent:main:main` and `agent:main:telegram:direct:<id>` resolve for one agent. Fixes #40005. Supersedes #40116. (#40519) thanks @obviyus.
|
||||
- Cron/Telegram announce delivery: route text-only announce jobs through the real outbound adapters after finalizing descendant output so plain Telegram targets no longer report `delivered: true` when no message actually reached Telegram. (#40575) thanks @obviyus.
|
||||
- Matrix/DM routing: add safer fallback detection for broken `m.direct` homeservers, honor explicit room bindings over DM classification, and preserve room-bound agent selection for Matrix DM rooms. (#19736) Thanks @derbronko.
|
||||
- Feishu/plugin onboarding: clear the short-lived plugin discovery cache before reloading the registry after installing a channel plugin, so onboarding no longer re-prompts to download Feishu immediately after a successful install. Fixes #39642. (#39752) Thanks @GazeKingNuWu.
|
||||
- Plugins/channel onboarding: prefer bundled channel plugins over duplicate npm-installed copies during onboarding and release-channel sync, preventing bundled plugins from being shadowed by npm installs with the same plugin ID. (#40092)
|
||||
- Config/runtime snapshots: keep secrets-runtime-resolved config and auth-profile snapshots intact after config writes so follow-up reads still see file-backed secret values while picking up the persisted config update. (#37313) thanks @bbblending.
|
||||
- Gateway/Control UI: resolve bundled dashboard assets through symlinked global wrappers and auto-detected package roots, while keeping configured and custom roots on the strict hardlink boundary. (#40385) Thanks @LarytheLord.
|
||||
- Browser/extension relay: add `browser.relayBindHost` so the Chrome relay can bind to an explicit non-loopback address for WSL2 and other cross-namespace setups, while preserving loopback-only defaults. (#39364) Thanks @mvanhorn.
|
||||
- Browser/CDP: normalize loopback direct WebSocket CDP URLs back to HTTP(S) for `/json/*` tab operations so local `ws://` / `wss://` profiles can still list, focus, open, and close tabs after the new direct-WS support lands. (#31085) Thanks @shrey150.
|
||||
- Browser/CDP: rewrite wildcard `ws://0.0.0.0` and `ws://[::]` debugger URLs from remote `/json/version` responses back to the external CDP host/port, fixing Browserless-style container endpoints. (#17760) Thanks @joeharouni.
|
||||
- Browser/extension relay: wait briefly for a previously attached Chrome tab to reappear after transient relay drops before failing with `tab not found`, reducing noisy reconnect flakes. (#32461) Thanks @AaronWander.
|
||||
- macOS/Tailscale gateway discovery: keep Tailscale Serve probing alive when other remote gateways are already discovered, prefer direct transport for resolved `.ts.net` and Tailscale Serve gateways, and set `TERM=dumb` for GUI-launched Tailscale CLI discovery. (#40167) thanks @ngutman.
|
||||
- TUI/theme: detect light terminal backgrounds via `COLORFGBG` and pick a WCAG AA-compliant light palette, with `OPENCLAW_THEME=light|dark` override for terminals without auto-detection. (#38636) Thanks @ademczuk and @vincentkoc.
|
||||
- Agents/openai-codex: normalize `gpt-5.4` fallback transport back to `openai-codex-responses` on `chatgpt.com/backend-api` when config drifts to the generic OpenAI responses endpoint. (#38736) Thanks @0xsline.
|
||||
- Models/openai-codex GPT-5.4 forward-compat: use the GPT-5.4 1,050,000-token context window and 128,000 max tokens for `openai-codex/gpt-5.4` instead of inheriting stale legacy Codex limits in resolver fallbacks and model listing. (#37876) thanks @yuweuii.
|
||||
- Tools/web search: restore Perplexity OpenRouter/Sonar compatibility for legacy `OPENROUTER_API_KEY`, `sk-or-...`, and explicit `perplexity.baseUrl` / `model` setups while keeping direct Perplexity keys on the native Search API path. (#39937) Thanks @obviyus.
|
||||
- Agents/failover: detect Amazon Bedrock `Too many tokens per day` quota errors as rate limits across fallback, cron retry, and memory embeddings while keeping context-window `too many tokens per request` errors out of the rate-limit lane. (#39377) Thanks @gambletan.
|
||||
- Mattermost replies: keep `root_id` pinned to the existing thread root when an agent replies inside a thread, while still using reply-target threading for top-level posts. (#27744) thanks @hnykda.
|
||||
- Telegram/DM partial streaming: keep DM preview lanes on real message edits instead of native draft materialization so final replies no longer flash a second duplicate copy before collapsing back to one.
|
||||
- macOS overlays: fix VoiceWake, Talk, and Notify overlay exclusivity crashes by removing shared `inout` visibility mutation from `OverlayPanelFactory.present`, and add a repeated Talk overlay smoke test. (#39275, #39321) Thanks @fellanH.
|
||||
- macOS Talk Mode: set the speech recognition request `taskHint` to `.dictation` for mic capture, and add regression coverage for the request defaults. (#38445) Thanks @dmiv.
|
||||
- macOS release packaging: default `scripts/package-mac-app.sh` to universal binaries for `BUILD_CONFIG=release`, and clarify that `scripts/package-mac-dist.sh` already produces the release zip + DMG. (#33891) Thanks @cgdusek.
|
||||
- Tools/web search: restore Perplexity OpenRouter/Sonar compatibility for legacy `OPENROUTER_API_KEY`, `sk-or-...`, and explicit `perplexity.baseUrl` / `model` setups while keeping direct Perplexity keys on the native Search API path. (#39937) Thanks @obviyus.
|
||||
- Hooks/session-memory: keep `/new` and `/reset` memory artifacts in the bound agent workspace and align saved reset session keys with that workspace when stale main-agent keys leak into the hook path. (#39875) thanks @rbutera.
|
||||
- Sessions/model switch: clear stale cached `contextTokens` when a session changes models so status and runtime paths recompute against the active model window. (#38044) thanks @yuweuii.
|
||||
- ACP/session history: persist transcripts for successful ACP child runs, preserve exact transcript text, record ACP spawned-session lineage, and keep spawn-time transcript-path persistence best-effort so history storage failures do not block execution. (#40137) thanks @mbelinky.
|
||||
- Agents/openai-codex: normalize `gpt-5.4` fallback transport back to `openai-codex-responses` on `chatgpt.com/backend-api` when config drifts to the generic OpenAI responses endpoint. (#38736) Thanks @0xsline.
|
||||
- Browser/CDP: normalize loopback direct WebSocket CDP URLs back to HTTP(S) for `/json/*` tab operations so local `ws://` / `wss://` profiles can still list, focus, open, and close tabs after the new direct-WS support lands. (#31085) Thanks @shrey150.
|
||||
- Browser/CDP: rewrite wildcard `ws://0.0.0.0` and `ws://[::]` debugger URLs from remote `/json/version` responses back to the external CDP host/port, fixing Browserless-style container endpoints. (#17760) Thanks @joeharouni.
|
||||
- Browser/extension relay: wait briefly for a previously attached Chrome tab to reappear after transient relay drops before failing with `tab not found`, reducing noisy reconnect flakes. (#32461) Thanks @AaronWander.
|
||||
- Browser/extension relay: add `browser.relayBindHost` so the Chrome relay can bind to an explicit non-loopback address for WSL2 and other cross-namespace setups, while preserving loopback-only defaults. (#39364) Thanks @mvanhorn.
|
||||
- Docs/browser: add a layered WSL2 + Windows remote Chrome CDP troubleshooting guide, including Control UI origin pitfalls and extension-relay bind-address guidance. (#39407) Thanks @Owlock.
|
||||
- Context engine registry/bundled builds: share the registry state through a `globalThis` singleton so duplicated bundled module copies can resolve engines registered by each other at runtime, with regression coverage for duplicate-module imports. (#40115) thanks @jalehman.
|
||||
- macOS/Tailscale gateway discovery: keep Tailscale Serve probing alive when other remote gateways are already discovered, prefer direct transport for resolved `.ts.net` and Tailscale Serve gateways, and set `TERM=dumb` for GUI-launched Tailscale CLI discovery. (#40167) thanks @ngutman.
|
||||
- Podman/setup: fix `cannot chdir: Permission denied` in `run_as_user` when `setup-podman.sh` is invoked from a directory the target user cannot access, by wrapping user-switch calls in a subshell that cd's to `/tmp` with `/` fallback. (#39435) Thanks @langdon and @jlcbk.
|
||||
- Podman/SELinux: auto-detect SELinux enforcing/permissive mode and add `:Z` relabel to bind mounts in `run-openclaw-podman.sh` and the Quadlet template, fixing `EACCES` on Fedora/RHEL hosts. Supports `OPENCLAW_BIND_MOUNT_OPTIONS` override. (#39449) Thanks @langdon and @githubbzxs.
|
||||
- TUI/theme: detect light terminal backgrounds via `COLORFGBG` and pick a WCAG AA-compliant light palette, with `OPENCLAW_THEME=light|dark` override for terminals without auto-detection. (#38636) Thanks @ademczuk and @vincentkoc.
|
||||
- Agents/context-engine plugins: bootstrap runtime plugins once at embedded-run, compaction, and subagent boundaries so plugin-provided context engines and hooks load from the active workspace before runtime resolution. (#40232)
|
||||
- Config/runtime snapshots: keep secrets-runtime-resolved config and auth-profile snapshots intact after config writes so follow-up reads still see file-backed secret values while picking up the persisted config update. (#37313) thanks @bbblending.
|
||||
- Gateway/Control UI: resolve bundled dashboard assets through symlinked global wrappers and auto-detected package roots, while keeping configured and custom roots on the strict hardlink boundary. (#40385) Thanks @LarytheLord.
|
||||
- Docs/Changelog: correct the contributor credit for the bundled Control UI global-install fix to @LarytheLord. (#40420) Thanks @velvet-shark.
|
||||
- Models/openai-codex GPT-5.4 forward-compat: use the GPT-5.4 1,050,000-token context window and 128,000 max tokens for `openai-codex/gpt-5.4` instead of inheriting stale legacy Codex limits in resolver fallbacks and model listing. (#37876) thanks @yuweuii.
|
||||
- Telegram/media downloads: time out only stalled body reads so polling recovers from hung file downloads without aborting slow downloads that are still streaming data. (#40098) thanks @tysoncung.
|
||||
- Telegram/DM routing: dedupe inbound Telegram DMs per agent instead of per session key so the same DM cannot trigger duplicate replies when both `agent:main:main` and `agent:main:telegram:direct:<id>` resolve for one agent. Fixes #40005. Supersedes #40116. (#40519) thanks @obviyus.
|
||||
- Matrix/DM routing: add safer fallback detection for broken `m.direct` homeservers, honor explicit room bindings over DM classification, and preserve room-bound agent selection for Matrix DM rooms. (#19736) Thanks @derbronko.
|
||||
- Cron/Telegram announce delivery: route text-only announce jobs through the real outbound adapters after finalizing descendant output so plain Telegram targets no longer report `delivered: true` when no message actually reached Telegram. (#40575) thanks @obviyus.
|
||||
- Docker/runtime image: prune dev dependencies, strip build-only dist metadata for smaller Docker images. (#40307) Thanks @vincentkoc.
|
||||
- Gateway/restart timeout recovery: exit non-zero when restart-triggered shutdown drains time out so launchd/systemd restart the gateway instead of treating the failed restart as a clean stop. Landed from contributor PR #40380 by @dsantoreis. Thanks @dsantoreis.
|
||||
- Gateway/config restart guard: validate config before service start/restart and keep post-SIGUSR1 startup failures from crashing the gateway process, reducing invalid-config restart loops and macOS permission loss. Landed from contributor PR #38699 by @lml2468. Thanks @lml2468.
|
||||
- Gateway/launchd respawn detection: treat `XPC_SERVICE_NAME` as a launchd supervision hint so macOS restarts exit cleanly under launchd instead of attempting detached self-respawn. Landed from contributor PR #20555 by @dimat. Thanks @dimat.
|
||||
- Telegram/poll restart cleanup: abort the in-flight Telegram API fetch when shutdown or forced polling restarts stop a runner, preventing stale `getUpdates` long polls from colliding with the replacement runner. Landed from contributor PR #23950 by @Gkinthecodeland. Thanks @Gkinthecodeland.
|
||||
- Cron/restart catch-up staggering: limit immediate missed-job replay on startup and reschedule the deferred remainder from the post-catchup clock so restart bursts do not starve the gateway or silently skip overdue recurring jobs. Landed from contributor PR #18925 by @rexlunae. Thanks @rexlunae.
|
||||
- Cron/owner-only tools: pass trusted isolated cron runs into the embedded agent with owner context so `cron`/`gateway` tooling remains available after the owner-auth hardening narrowed direct-message ownership inference.
|
||||
- Browser/SSRF: block private-network intermediate redirect hops in strict browser navigation flows and fail closed when remote tab-open paths cannot inspect redirect chains. Thanks @zpbrent.
|
||||
- MS Teams/authz: keep `groupPolicy: "allowlist"` enforcing sender allowlists even when a team/channel route allowlist is configured, so route matches no longer widen group access to every sender in that route. Thanks @zpbrent.
|
||||
- Security/system.run: bind approved `bun` and `deno run` script operands to on-disk file snapshots so post-approval script rewrites are denied before execution.
|
||||
- Skills/download installs: pin the validated per-skill tools root before writing downloaded archives, so rebinding the lexical tools path cannot redirect download writes outside the intended tools directory. Thanks @tdjackey.
|
||||
|
||||
## 2026.3.7
|
||||
|
||||
|
||||
@@ -57,9 +57,21 @@ Welcome to the lobster tank! 🦞
|
||||
- GitHub: [@joshavant](https://github.com/joshavant) · X: [@joshavant](https://x.com/joshavant)
|
||||
|
||||
- **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)
|
||||
- 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_)
|
||||
- GitHub [@jalehman](https://github.com/jalehman) · X: [@jlehman\_](https://x.com/jlehman_)
|
||||
|
||||
- **Radek Sienkiewicz** - Control UI + WebChat correctness
|
||||
- GitHub [@velvet-shark](https://github.com/velvet-shark) · X: [@velvet_shark](https://twitter.com/velvet_shark)
|
||||
|
||||
- **Muhammed Mukhthar** - Mattermost, CLI
|
||||
- GitHub [@mukhtharcm](https://github.com/mukhtharcm) · X: [@mukhtharcm](https://x.com/mukhtharcm)
|
||||
|
||||
- **Altay** - Agents, CLI, error handling
|
||||
- GitHub [@altaywtf](https://github.com/altaywtf) · X: [@altaywtf](https://x.com/altaywtf)
|
||||
|
||||
- **Robin Waslander** - Security, PR triage, bug fixes
|
||||
- GitHub: [@hydro13](https://github.com/hydro13) · X: [@Robin_waslander](https://x.com/Robin_waslander)
|
||||
|
||||
## How to Contribute
|
||||
|
||||
|
||||
213
appcast.xml
213
appcast.xml
@@ -2,6 +2,80 @@
|
||||
<rss xmlns:sparkle="http://www.andymatuschak.org/xml-namespaces/sparkle" version="2.0">
|
||||
<channel>
|
||||
<title>OpenClaw</title>
|
||||
<item>
|
||||
<title>2026.3.8-beta.1</title>
|
||||
<pubDate>Mon, 09 Mar 2026 07:19:57 +0000</pubDate>
|
||||
<link>https://raw.githubusercontent.com/openclaw/openclaw/main/appcast.xml</link>
|
||||
<sparkle:version>2026030801</sparkle:version>
|
||||
<sparkle:shortVersionString>2026.3.8-beta.1</sparkle:shortVersionString>
|
||||
<sparkle:minimumSystemVersion>15.0</sparkle:minimumSystemVersion>
|
||||
<description><![CDATA[<h2>OpenClaw 2026.3.8-beta.1</h2>
|
||||
<h3>Changes</h3>
|
||||
<ul>
|
||||
<li>CLI/backup: add <code>openclaw backup create</code> and <code>openclaw backup verify</code> for local state archives, including <code>--only-config</code>, <code>--no-include-workspace</code>, manifest/payload validation, and backup guidance in destructive flows. (#40163) thanks @shichangs.</li>
|
||||
<li>macOS/onboarding: add a remote gateway token field for remote mode, preserve existing non-plaintext <code>gateway.remote.token</code> config values until explicitly replaced, and warn when the loaded token shape cannot be used directly from the macOS app. (#40187, supersedes #34614) Thanks @cgdusek.</li>
|
||||
<li>Talk mode: add top-level <code>talk.silenceTimeoutMs</code> config so Talk waits a configurable amount of silence before auto-sending the current transcript, while keeping each platform's existing default pause window when unset. (#39607) Thanks @danodoesdesign. Fixes #17147.</li>
|
||||
<li>TUI: infer the active agent from the current workspace when launched inside a configured agent workspace, while preserving explicit <code>agent:</code> session targets. (#39591) thanks @arceus77-7.</li>
|
||||
<li>Tools/Brave web search: add opt-in <code>tools.web.search.brave.mode: "llm-context"</code> so <code>web_search</code> can call Brave's LLM Context endpoint and return extracted grounding snippets with source metadata, plus config/docs/test coverage. (#33383) Thanks @thirumaleshp.</li>
|
||||
<li>CLI/install: include the short git commit hash in <code>openclaw --version</code> output when metadata is available, and keep installer version checks compatible with the decorated format. (#39712) thanks @sourman.</li>
|
||||
<li>CLI/backup: improve archive naming for date sorting, add config-only backup mode, and harden backup planning, publication, and verification edge cases. (#40163) Thanks @gumadeiras.</li>
|
||||
<li>ACP/Provenance: add optional ACP ingress provenance metadata and visible receipt injection (<code>openclaw acp --provenance off|meta|meta+receipt</code>) so OpenClaw agents can retain and report ACP-origin context with session trace IDs. (#40473) thanks @mbelinky.</li>
|
||||
<li>Tools/web search: alphabetize provider ordering across runtime selection, onboarding/configure pickers, and config metadata, so provider lists stay neutral and multi-key auto-detect now prefers Grok before Kimi. (#40259) thanks @kesku.</li>
|
||||
<li>Docs/Web search: restore $5/month free-credit details, replace defunct "Data for Search"/"Data for AI" plan names with current "Search" plan, and note legacy subscription validity in Brave setup docs. Follows up on #26860. (#40111) Thanks @remusao.</li>
|
||||
<li>Extensions/ACPX tests: move the shared runtime fixture helper from <code>src/runtime-internals/</code> to <code>src/test-utils/</code> so the test-only helper no longer looks like shipped runtime code.</li>
|
||||
</ul>
|
||||
<h3>Fixes</h3>
|
||||
<ul>
|
||||
<li>macOS app/chat UI: route browser proxy through the local node browser service, preserve plain-text paste semantics, strip completed assistant trace/debug wrapper noise from transcripts, refresh permission state after returning from System Settings, and tolerate malformed cron rows in the macOS tab. (#39516) Thanks @Imhermes1.</li>
|
||||
<li>Android/Play distribution: remove self-update, background location, <code>screen.record</code>, and background mic capture from the Android app, narrow the foreground service to <code>dataSync</code> only, and clean up the legacy <code>location.enabledMode=always</code> preference migration. (#39660) Thanks @obviyus.</li>
|
||||
<li>Telegram/DM routing: dedupe inbound Telegram DMs per agent instead of per session key so the same DM cannot trigger duplicate replies when both <code>agent:main:main</code> and <code>agent:main:telegram:direct:<id></code> resolve for one agent. Fixes #40005. Supersedes #40116. (#40519) thanks @obviyus.</li>
|
||||
<li>Cron/Telegram announce delivery: route text-only announce jobs through the real outbound adapters after finalizing descendant output so plain Telegram targets no longer report <code>delivered: true</code> when no message actually reached Telegram. (#40575) thanks @obviyus.</li>
|
||||
<li>Matrix/DM routing: add safer fallback detection for broken <code>m.direct</code> homeservers, honor explicit room bindings over DM classification, and preserve room-bound agent selection for Matrix DM rooms. (#19736) Thanks @derbronko.</li>
|
||||
<li>Feishu/plugin onboarding: clear the short-lived plugin discovery cache before reloading the registry after installing a channel plugin, so onboarding no longer re-prompts to download Feishu immediately after a successful install. Fixes #39642. (#39752) Thanks @GazeKingNuWu.</li>
|
||||
<li>Plugins/channel onboarding: prefer bundled channel plugins over duplicate npm-installed copies during onboarding and release-channel sync, preventing bundled plugins from being shadowed by npm installs with the same plugin ID. (#40092)</li>
|
||||
<li>Config/runtime snapshots: keep secrets-runtime-resolved config and auth-profile snapshots intact after config writes so follow-up reads still see file-backed secret values while picking up the persisted config update. (#37313) thanks @bbblending.</li>
|
||||
<li>Gateway/Control UI: resolve bundled dashboard assets through symlinked global wrappers and auto-detected package roots, while keeping configured and custom roots on the strict hardlink boundary. (#40385) Thanks @LarytheLord.</li>
|
||||
<li>Browser/extension relay: add <code>browser.relayBindHost</code> so the Chrome relay can bind to an explicit non-loopback address for WSL2 and other cross-namespace setups, while preserving loopback-only defaults. (#39364) Thanks @mvanhorn.</li>
|
||||
<li>Browser/CDP: normalize loopback direct WebSocket CDP URLs back to HTTP(S) for <code>/json/*</code> tab operations so local <code>ws://</code> / <code>wss://</code> profiles can still list, focus, open, and close tabs after the new direct-WS support lands. (#31085) Thanks @shrey150.</li>
|
||||
<li>Browser/CDP: rewrite wildcard <code>ws://0.0.0.0</code> and <code>ws://[::]</code> debugger URLs from remote <code>/json/version</code> responses back to the external CDP host/port, fixing Browserless-style container endpoints. (#17760) Thanks @joeharouni.</li>
|
||||
<li>Browser/extension relay: wait briefly for a previously attached Chrome tab to reappear after transient relay drops before failing with <code>tab not found</code>, reducing noisy reconnect flakes. (#32461) Thanks @AaronWander.</li>
|
||||
<li>macOS/Tailscale gateway discovery: keep Tailscale Serve probing alive when other remote gateways are already discovered, prefer direct transport for resolved <code>.ts.net</code> and Tailscale Serve gateways, and set <code>TERM=dumb</code> for GUI-launched Tailscale CLI discovery. (#40167) thanks @ngutman.</li>
|
||||
<li>TUI/theme: detect light terminal backgrounds via <code>COLORFGBG</code> and pick a WCAG AA-compliant light palette, with <code>OPENCLAW_THEME=light|dark</code> override for terminals without auto-detection. (#38636) Thanks @ademczuk and @vincentkoc.</li>
|
||||
<li>Agents/openai-codex: normalize <code>gpt-5.4</code> fallback transport back to <code>openai-codex-responses</code> on <code>chatgpt.com/backend-api</code> when config drifts to the generic OpenAI responses endpoint. (#38736) Thanks @0xsline.</li>
|
||||
<li>Models/openai-codex GPT-5.4 forward-compat: use the GPT-5.4 1,050,000-token context window and 128,000 max tokens for <code>openai-codex/gpt-5.4</code> instead of inheriting stale legacy Codex limits in resolver fallbacks and model listing. (#37876) thanks @yuweuii.</li>
|
||||
<li>Tools/web search: restore Perplexity OpenRouter/Sonar compatibility for legacy <code>OPENROUTER_API_KEY</code>, <code>sk-or-...</code>, and explicit <code>perplexity.baseUrl</code> / <code>model</code> setups while keeping direct Perplexity keys on the native Search API path. (#39937) Thanks @obviyus.</li>
|
||||
<li>Agents/failover: detect Amazon Bedrock <code>Too many tokens per day</code> quota errors as rate limits across fallback, cron retry, and memory embeddings while keeping context-window <code>too many tokens per request</code> errors out of the rate-limit lane. (#39377) Thanks @gambletan.</li>
|
||||
<li>Mattermost replies: keep <code>root_id</code> pinned to the existing thread root when an agent replies inside a thread, while still using reply-target threading for top-level posts. (#27744) thanks @hnykda.</li>
|
||||
<li>Telegram/DM partial streaming: keep DM preview lanes on real message edits instead of native draft materialization so final replies no longer flash a second duplicate copy before collapsing back to one.</li>
|
||||
<li>macOS overlays: fix VoiceWake, Talk, and Notify overlay exclusivity crashes by removing shared <code>inout</code> visibility mutation from <code>OverlayPanelFactory.present</code>, and add a repeated Talk overlay smoke test. (#39275, #39321) Thanks @fellanH.</li>
|
||||
<li>macOS Talk Mode: set the speech recognition request <code>taskHint</code> to <code>.dictation</code> for mic capture, and add regression coverage for the request defaults. (#38445) Thanks @dmiv.</li>
|
||||
<li>macOS release packaging: default <code>scripts/package-mac-app.sh</code> to universal binaries for <code>BUILD_CONFIG=release</code>, and clarify that <code>scripts/package-mac-dist.sh</code> already produces the release zip + DMG. (#33891) Thanks @cgdusek.</li>
|
||||
<li>Hooks/session-memory: keep <code>/new</code> and <code>/reset</code> memory artifacts in the bound agent workspace and align saved reset session keys with that workspace when stale main-agent keys leak into the hook path. (#39875) thanks @rbutera.</li>
|
||||
<li>Sessions/model switch: clear stale cached <code>contextTokens</code> when a session changes models so status and runtime paths recompute against the active model window. (#38044) thanks @yuweuii.</li>
|
||||
<li>ACP/session history: persist transcripts for successful ACP child runs, preserve exact transcript text, record ACP spawned-session lineage, and keep spawn-time transcript-path persistence best-effort so history storage failures do not block execution. (#40137) thanks @mbelinky.</li>
|
||||
<li>Docs/browser: add a layered WSL2 + Windows remote Chrome CDP troubleshooting guide, including Control UI origin pitfalls and extension-relay bind-address guidance. (#39407) Thanks @Owlock.</li>
|
||||
<li>Context engine registry/bundled builds: share the registry state through a <code>globalThis</code> singleton so duplicated bundled module copies can resolve engines registered by each other at runtime, with regression coverage for duplicate-module imports. (#40115) thanks @jalehman.</li>
|
||||
<li>Podman/setup: fix <code>cannot chdir: Permission denied</code> in <code>run_as_user</code> when <code>setup-podman.sh</code> is invoked from a directory the target user cannot access, by wrapping user-switch calls in a subshell that cd's to <code>/tmp</code> with <code>/</code> fallback. (#39435) Thanks @langdon and @jlcbk.</li>
|
||||
<li>Podman/SELinux: auto-detect SELinux enforcing/permissive mode and add <code>:Z</code> relabel to bind mounts in <code>run-openclaw-podman.sh</code> and the Quadlet template, fixing <code>EACCES</code> on Fedora/RHEL hosts. Supports <code>OPENCLAW_BIND_MOUNT_OPTIONS</code> override. (#39449) Thanks @langdon and @githubbzxs.</li>
|
||||
<li>Agents/context-engine plugins: bootstrap runtime plugins once at embedded-run, compaction, and subagent boundaries so plugin-provided context engines and hooks load from the active workspace before runtime resolution. (#40232)</li>
|
||||
<li>Docs/Changelog: correct the contributor credit for the bundled Control UI global-install fix to @LarytheLord. (#40420) Thanks @velvet-shark.</li>
|
||||
<li>Telegram/media downloads: time out only stalled body reads so polling recovers from hung file downloads without aborting slow downloads that are still streaming data. (#40098) thanks @tysoncung.</li>
|
||||
<li>Docker/runtime image: prune dev dependencies, strip build-only dist metadata for smaller Docker images. (#40307) Thanks @vincentkoc.</li>
|
||||
<li>Gateway/restart timeout recovery: exit non-zero when restart-triggered shutdown drains time out so launchd/systemd restart the gateway instead of treating the failed restart as a clean stop. Landed from contributor PR #40380 by @dsantoreis. Thanks @dsantoreis.</li>
|
||||
<li>Gateway/config restart guard: validate config before service start/restart and keep post-SIGUSR1 startup failures from crashing the gateway process, reducing invalid-config restart loops and macOS permission loss. Landed from contributor PR #38699 by @lml2468. Thanks @lml2468.</li>
|
||||
<li>Gateway/launchd respawn detection: treat <code>XPC_SERVICE_NAME</code> as a launchd supervision hint so macOS restarts exit cleanly under launchd instead of attempting detached self-respawn. Landed from contributor PR #20555 by @dimat. Thanks @dimat.</li>
|
||||
<li>Telegram/poll restart cleanup: abort the in-flight Telegram API fetch when shutdown or forced polling restarts stop a runner, preventing stale <code>getUpdates</code> long polls from colliding with the replacement runner. Landed from contributor PR #23950 by @Gkinthecodeland. Thanks @Gkinthecodeland.</li>
|
||||
<li>Cron/restart catch-up staggering: limit immediate missed-job replay on startup and reschedule the deferred remainder from the post-catchup clock so restart bursts do not starve the gateway or silently skip overdue recurring jobs. Landed from contributor PR #18925 by @rexlunae. Thanks @rexlunae.</li>
|
||||
<li>Cron/owner-only tools: pass trusted isolated cron runs into the embedded agent with owner context so <code>cron</code>/<code>gateway</code> tooling remains available after the owner-auth hardening narrowed direct-message ownership inference.</li>
|
||||
<li>Browser/SSRF: block private-network intermediate redirect hops in strict browser navigation flows and fail closed when remote tab-open paths cannot inspect redirect chains. Thanks @zpbrent.</li>
|
||||
<li>MS Teams/authz: keep <code>groupPolicy: "allowlist"</code> enforcing sender allowlists even when a team/channel route allowlist is configured, so route matches no longer widen group access to every sender in that route. Thanks @zpbrent.</li>
|
||||
<li>Security/system.run: bind approved <code>bun</code> and <code>deno run</code> script operands to on-disk file snapshots so post-approval script rewrites are denied before execution.</li>
|
||||
<li>Skills/download installs: pin the validated per-skill tools root before writing downloaded archives, so rebinding the lexical tools path cannot redirect download writes outside the intended tools directory. Thanks @tdjackey.</li>
|
||||
</ul>
|
||||
<p><a href="https://github.com/openclaw/openclaw/blob/main/CHANGELOG.md">View full changelog</a></p>
|
||||
]]></description>
|
||||
<enclosure url="https://github.com/openclaw/openclaw/releases/download/v2026.3.8-beta.1/OpenClaw-2026.3.8-beta.1.zip" length="23407015" type="application/octet-stream" sparkle:edSignature="KCqhSmu4b0tHf55RqcQOHorsc55CgBI5BUmK/NTizxNq04INn/7QvsamHYQou9DbB2IW6B2nawBC4nn4au5yDA=="/>
|
||||
</item>
|
||||
<item>
|
||||
<title>2026.3.7</title>
|
||||
<pubDate>Sun, 08 Mar 2026 04:42:35 +0000</pubDate>
|
||||
@@ -584,144 +658,5 @@
|
||||
<enclosure url="https://github.com/openclaw/openclaw/releases/download/v2026.3.2/OpenClaw-2026.3.2.zip" length="23181513" type="application/octet-stream" sparkle:edSignature="THMgkcoMgz2vv5zse3Po3K7l3Or2RhBKurXZIi8iYVXN76yJy1YXAY6kXi6ovD+dbYn68JKYDIKA1Ya78bO7BQ=="/>
|
||||
<!-- pragma: allowlist secret -->
|
||||
</item>
|
||||
<item>
|
||||
<title>2026.3.1</title>
|
||||
<pubDate>Mon, 02 Mar 2026 04:40:59 +0000</pubDate>
|
||||
<link>https://raw.githubusercontent.com/openclaw/openclaw/main/appcast.xml</link>
|
||||
<sparkle:version>2026030190</sparkle:version>
|
||||
<sparkle:shortVersionString>2026.3.1</sparkle:shortVersionString>
|
||||
<sparkle:minimumSystemVersion>15.0</sparkle:minimumSystemVersion>
|
||||
<description><![CDATA[<h2>OpenClaw 2026.3.1</h2>
|
||||
<h3>Changes</h3>
|
||||
<ul>
|
||||
<li>Agents/Thinking defaults: set <code>adaptive</code> as the default thinking level for Anthropic Claude 4.6 models (including Bedrock Claude 4.6 refs) while keeping other reasoning-capable models at <code>low</code> unless explicitly configured.</li>
|
||||
<li>Gateway/Container probes: add built-in HTTP liveness/readiness endpoints (<code>/health</code>, <code>/healthz</code>, <code>/ready</code>, <code>/readyz</code>) for Docker/Kubernetes health checks, with fallback routing so existing handlers on those paths are not shadowed. (#31272) Thanks @vincentkoc.</li>
|
||||
<li>Android/Nodes: add <code>camera.list</code>, <code>device.permissions</code>, <code>device.health</code>, and <code>notifications.actions</code> (<code>open</code>/<code>dismiss</code>/<code>reply</code>) on Android nodes, plus first-class node-tool actions for the new device/notification commands. (#28260) Thanks @obviyus.</li>
|
||||
<li>Discord/Thread bindings: replace fixed TTL lifecycle with inactivity (<code>idleHours</code>, default 24h) plus optional hard <code>maxAgeHours</code> lifecycle controls, and add <code>/session idle</code> + <code>/session max-age</code> commands for focused thread-bound sessions. (#27845) Thanks @osolmaz.</li>
|
||||
<li>Telegram/DM topics: add per-DM <code>direct</code> + topic config (allowlists, <code>dmPolicy</code>, <code>skills</code>, <code>systemPrompt</code>, <code>requireTopic</code>), route DM topics as distinct inbound/outbound sessions, and enforce topic-aware authorization/debounce for messages, callbacks, commands, and reactions. Landed from contributor PR #30579 by @kesor. Thanks @kesor.</li>
|
||||
<li>Web UI/Cron i18n: localize cron page labels, filters, form help text, and validation/error messaging in English and zh-CN. (#29315) Thanks @BUGKillerKing.</li>
|
||||
<li>OpenAI/Streaming transport: make <code>openai</code> Responses WebSocket-first by default (<code>transport: "auto"</code> with SSE fallback), add shared OpenAI WS stream/connection runtime wiring with per-session cleanup, and preserve server-side compaction payload mutation (<code>store</code> + <code>context_management</code>) on the WS path.</li>
|
||||
<li>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.</li>
|
||||
<li>Android/Nodes parity: add <code>system.notify</code>, <code>photos.latest</code>, <code>contacts.search</code>/<code>contacts.add</code>, <code>calendar.events</code>/<code>calendar.add</code>, and <code>motion.activity</code>/<code>motion.pedometer</code>, with motion sensor-aware command gating and improved activity sampling reliability. (#29398) Thanks @obviyus.</li>
|
||||
<li>CLI/Config: add <code>openclaw config file</code> to print the active config file path resolved from <code>OPENCLAW_CONFIG_PATH</code> or the default location. (#26256) thanks @cyb1278588254.</li>
|
||||
<li>Feishu/Docx tables + uploads: add <code>feishu_doc</code> actions for Docx table creation/cell writing (<code>create_table</code>, <code>write_table_cells</code>, <code>create_table_with_values</code>) and image/file uploads (<code>upload_image</code>, <code>upload_file</code>) with stricter create/upload error handling for missing <code>document_id</code> and placeholder cleanup failures. (#20304) Thanks @xuhao1.</li>
|
||||
<li>Feishu/Reactions: add inbound <code>im.message.reaction.created_v1</code> handling, route verified reactions through synthetic inbound turns, and harden verification with timeout + fail-closed filtering so non-bot or unverified reactions are dropped. (#16716) Thanks @schumilin.</li>
|
||||
<li>Feishu/Chat tooling: add <code>feishu_chat</code> tool actions for chat info and member queries, with configurable enablement under <code>channels.feishu.tools.chat</code>. (#14674) Thanks @liuweifly.</li>
|
||||
<li>Feishu/Doc permissions: support optional owner permission grant fields on <code>feishu_doc</code> create and report permission metadata only when the grant call succeeds, with regression coverage for success/failure/omitted-owner paths. (#28295) Thanks @zhoulongchao77.</li>
|
||||
<li>Web UI/i18n: add German (<code>de</code>) locale support and auto-render language options from supported locale constants in Overview settings. (#28495) thanks @dsantoreis.</li>
|
||||
<li>Tools/Diffs: add a new optional <code>diffs</code> plugin tool for read-only diff rendering from before/after text or unified patches, with gateway viewer URLs for canvas and PNG image output. Thanks @gumadeiras.</li>
|
||||
<li>Memory/LanceDB: support custom OpenAI <code>baseUrl</code> and embedding dimensions for LanceDB memory. (#17874) Thanks @rish2jain and @vincentkoc.</li>
|
||||
<li>ACP/ACPX streaming: pin ACPX plugin support to <code>0.1.15</code>, add configurable ACPX command/version probing, and streamline ACP stream delivery (<code>final_only</code> default + reduced tool-event noise) with matching runtime and test updates. (#30036) Thanks @osolmaz.</li>
|
||||
<li>Shell env markers: set <code>OPENCLAW_SHELL</code> across shell-like runtimes (<code>exec</code>, <code>acp</code>, <code>acp-client</code>, <code>tui-local</code>) so shell startup/config rules can target OpenClaw contexts consistently, and document the markers in env/exec/acp/TUI docs. Thanks @vincentkoc.</li>
|
||||
<li>Cron/Heartbeat light bootstrap context: add opt-in lightweight bootstrap mode for automation runs (<code>--light-context</code> for cron agent turns and <code>agents.*.heartbeat.lightContext</code> for heartbeat), keeping only <code>HEARTBEAT.md</code> for heartbeat runs and skipping bootstrap-file injection for cron lightweight runs. (#26064) Thanks @jose-velez.</li>
|
||||
<li>OpenAI/WebSocket warm-up: add optional OpenAI Responses WebSocket warm-up (<code>response.create</code> with <code>generate:false</code>), enable it by default for <code>openai/*</code>, and expose <code>params.openaiWsWarmup</code> for per-model enable/disable control.</li>
|
||||
<li>Agents/Subagents runtime events: replace ad-hoc subagent completion system-message handoff with typed internal completion events (<code>task_completion</code>) that are rendered consistently across direct and queued announce paths, with gateway/CLI plumbing for structured <code>internalEvents</code>.</li>
|
||||
</ul>
|
||||
<h3>Breaking</h3>
|
||||
<ul>
|
||||
<li><strong>BREAKING:</strong> Node exec approval payloads now require <code>systemRunPlan</code>. <code>host=node</code> approval requests without that plan are rejected.</li>
|
||||
<li><strong>BREAKING:</strong> Node <code>system.run</code> execution now pins path-token commands to the canonical executable path (<code>realpath</code>) in both allowlist and approval execution flows. Integrations/tests that asserted token-form argv (for example <code>tr</code>) must now accept canonical paths (for example <code>/usr/bin/tr</code>).</li>
|
||||
</ul>
|
||||
<h3>Fixes</h3>
|
||||
<ul>
|
||||
<li>Android/Nodes reliability: reject <code>facing=both</code> when <code>deviceId</code> is set to avoid mislabeled duplicate captures, allow notification <code>open</code>/<code>reply</code> 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.</li>
|
||||
<li>Windows/Plugin install: avoid <code>spawn EINVAL</code> on Windows npm/npx invocations by resolving to <code>node</code> + npm CLI scripts instead of spawning <code>.cmd</code> directly. Landed from contributor PR #31147 by @codertony. Thanks @codertony.</li>
|
||||
<li>LINE/Voice transcription: classify M4A voice media as <code>audio/mp4</code> (not <code>video/mp4</code>) by checking the MPEG-4 <code>ftyp</code> major brand (<code>M4A </code> / <code>M4B </code>), restoring voice transcription for LINE voice messages. Landed from contributor PR #31151 by @scoootscooob. Thanks @scoootscooob.</li>
|
||||
<li>Slack/Announce target account routing: enable session-backed announce-target lookup for Slack so multi-account announces resolve the correct <code>accountId</code> instead of defaulting to bot-token context. Landed from contributor PR #31028 by @taw0002. Thanks @taw0002.</li>
|
||||
<li>Android/Voice screen TTS: stream assistant speech via ElevenLabs WebSocket in Talk Mode, stop cleanly on speaker mute/barge-in, and ignore stale out-of-order stream events. (#29521) Thanks @gregmousseau.</li>
|
||||
<li>Android/Photos permissions: declare Android 14+ selected-photo access permission (<code>READ_MEDIA_VISUAL_USER_SELECTED</code>) and align Android permission/settings paths with current minSdk behavior for more reliable permission state handling.</li>
|
||||
<li>Web UI/Cron: include configured agent model defaults/fallbacks in cron model suggestions so scheduled-job model autocomplete reflects configured models. (#29709) Thanks @Sid-Qin.</li>
|
||||
<li>Cron/Delivery: disable the agent messaging tool when <code>delivery.mode</code> is <code>"none"</code> so cron output is not sent to Telegram or other channels. (#21808) Thanks @lailoo.</li>
|
||||
<li>CLI/Cron: clarify <code>cron list</code> output by renaming <code>Agent</code> to <code>Agent ID</code> and adding a <code>Model</code> column for isolated agent-turn jobs. (#26259) Thanks @openperf.</li>
|
||||
<li>Feishu/Reply media attachments: send Feishu reply <code>mediaUrl</code>/<code>mediaUrls</code> payloads as attachments alongside text/streamed replies in the reply dispatcher, including legacy fallback when <code>mediaUrls</code> is empty. (#28959) Thanks @icesword0760.</li>
|
||||
<li>Slack/User-token resolution: normalize Slack account user-token sourcing through resolved account metadata (<code>SLACK_USER_TOKEN</code> env + config) so monitor reads, Slack actions, directory lookups, onboarding allow-from resolution, and capabilities probing consistently use the effective user token. (#28103) Thanks @Glucksberg.</li>
|
||||
<li>Feishu/Outbound session routing: stop assuming bare <code>oc_</code> identifiers are always group chats, honor explicit <code>dm:</code>/<code>group:</code> prefixes for <code>oc_</code> chat IDs, and default ambiguous bare <code>oc_</code> targets to direct routing to avoid DM session misclassification. (#10407) Thanks @Bermudarat.</li>
|
||||
<li>Feishu/Group session routing: add configurable group session scopes (<code>group</code>, <code>group_sender</code>, <code>group_topic</code>, <code>group_topic_sender</code>) with legacy <code>topicSessionMode=enabled</code> compatibility so Feishu group conversations can isolate sessions by sender/topic as configured. (#17798) Thanks @yfge.</li>
|
||||
<li>Feishu/Reply-in-thread routing: add <code>replyInThread</code> config (<code>disabled|enabled</code>) for group replies, propagate <code>reply_in_thread</code> across text/card/media/streaming sends, and align topic-scoped session routing so newly created reply threads stay on the same session root. (#27325) Thanks @kcinzgg.</li>
|
||||
<li>Feishu/Probe status caching: cache successful <code>probeFeishu()</code> bot-info results for 10 minutes (bounded cache with per-account keying) to reduce repeated status/onboarding probe API calls, while bypassing cache for failures and exceptions. (#28907) Thanks @Glucksberg.</li>
|
||||
<li>Feishu/Opus media send type: send <code>.opus</code> attachments with <code>msg_type: "audio"</code> (instead of <code>"media"</code>) so Feishu voice messages deliver correctly while <code>.mp4</code> remains <code>msg_type: "media"</code> and documents remain <code>msg_type: "file"</code>. (#28269) Thanks @Glucksberg.</li>
|
||||
<li>Feishu/Mobile video media type: treat inbound <code>message_type: "media"</code> as video-equivalent for media key extraction, placeholder inference, and media download resolution so mobile-app video sends ingest correctly. (#25502) Thanks @4ier.</li>
|
||||
<li>Feishu/Inbound sender fallback: fall back to <code>sender_id.user_id</code> when <code>sender_id.open_id</code> is missing on inbound events, and use ID-type-aware sender lookup so mobile-delivered messages keep stable sender identity/routing. (#26703) Thanks @NewdlDewdl.</li>
|
||||
<li>Feishu/Reply context metadata: include inbound <code>parent_id</code> and <code>root_id</code> as <code>ReplyToId</code>/<code>RootMessageId</code> in inbound context, and parse interactive-card quote bodies into readable text when fetching replied messages. (#18529) Thanks @qiangu.</li>
|
||||
<li>Feishu/Post embedded media: extract <code>media</code> tags from inbound rich-text (<code>post</code>) messages and download embedded video/audio files alongside existing embedded-image handling, with regression coverage. (#21786) Thanks @laopuhuluwa.</li>
|
||||
<li>Feishu/Local media sends: propagate <code>mediaLocalRoots</code> through Feishu outbound media sending into <code>loadWebMedia</code> so local path attachments work with post-CVE local-root enforcement. (#27884) Thanks @joelnishanth.</li>
|
||||
<li>Feishu/Group wildcard policy fallback: honor <code>channels.feishu.groups["*"]</code> when no explicit group match exists so unmatched groups inherit wildcard reply-policy settings instead of falling back to global defaults. (#29456) Thanks @WaynePika.</li>
|
||||
<li>Feishu/Inbound media regression coverage: add explicit tests for message resource type mapping (<code>image</code> stays <code>image</code>, non-image maps to <code>file</code>) to prevent reintroducing unsupported Feishu <code>type=audio</code> fetches. (#16311, #8746) Thanks @Yaxuan42.</li>
|
||||
<li>TTS/Voice bubbles: use opus output and enable <code>audioAsVoice</code> routing for Feishu and WhatsApp (in addition to Telegram) so supported channels receive voice-bubble playback instead of file-style audio attachments. (#27366) Thanks @smthfoxy.</li>
|
||||
<li>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.</li>
|
||||
<li>Android/Nodes notification wake flow: enable Android <code>system.notify</code> default allowlist, emit <code>notifications.changed</code> events for posted/removed notifications (excluding OpenClaw app-owned notifications), canonicalize notification session keys before enqueue/wake routing, and skip heartbeat wakes when consecutive notification summaries dedupe. (#29440) Thanks @obviyus.</li>
|
||||
<li>Telegram/Voice fallback reply chunking: apply reply reference, quote text, and inline buttons only to the first fallback text chunk when voice delivery is blocked, preventing over-quoted multi-chunk replies. Landed from contributor PR #31067 by @xdanger. Thanks @xdanger.</li>
|
||||
<li>Feishu/Multi-account + reply reliability: add <code>channels.feishu.defaultAccount</code> outbound routing support with schema validation, keep quoted-message extraction text-first (post/interactive/file placeholders instead of raw JSON), route Feishu video sends as <code>msg_type: "file"</code>, and avoid websocket event blocking by using non-blocking event handling in monitor dispatch. Landed from contributor PRs #29610, #30432, #30331, and #29501. Thanks @hclsys, @bmendonca3, @patrick-yingxi-pan, and @zwffff.</li>
|
||||
<li>Cron/Delivery: disable the agent messaging tool when <code>delivery.mode</code> is <code>"none"</code> so cron output is not sent to Telegram or other channels. (#21808) Thanks @lailoo.</li>
|
||||
<li>Feishu/Inbound rich-text parsing: preserve <code>share_chat</code> payload summaries when available and add explicit parsing for rich-text <code>code</code>/<code>code_block</code>/<code>pre</code> tags so forwarded and code-heavy messages keep useful context in agent input. (#28591) Thanks @kevinWangSheng.</li>
|
||||
<li>Feishu/Post markdown parsing: parse rich-text <code>post</code> payloads through a shared markdown-aware parser with locale-wrapper support, preserved mention/image metadata extraction, and inline/fenced code fidelity for agent input rendering. (#12755) Thanks @WilsonLiu95.</li>
|
||||
<li>Telegram/Outbound chunking: route oversize splitting through the shared outbound pipeline (including subagents), retry Telegram sends when escaped HTML exceeds limits, and preserve boundary whitespace when retry re-splitting rendered chunks so plain-text/transcript fidelity is retained. (#29342, #27317; follow-up to #27461) Thanks @obviyus.</li>
|
||||
<li>Slack/Native commands: register Slack native status as <code>/agentstatus</code> (Slack-reserved <code>/status</code>) so manifest slash command registration stays valid while text <code>/status</code> still works. Landed from contributor PR #29032 by @maloqab. Thanks @maloqab.</li>
|
||||
<li>Android/Camera clip: remove <code>camera.clip</code> HTTP-upload fallback to base64 so clip transport is deterministic and fail-loud, and reject non-positive <code>maxWidth</code> values so invalid inputs fall back to the safe resize default. (#28229) Thanks @obviyus.</li>
|
||||
<li>Android/Gateway canvas capability refresh: send <code>node.canvas.capability.refresh</code> with object <code>params</code> (<code>{}</code>) from Android node runtime so gateway object-schema validation accepts refresh retries and A2UI host recovery works after scoped capability expiry. (#28413) Thanks @obviyus.</li>
|
||||
<li>Gateway/Control UI origins: honor <code>gateway.controlUi.allowedOrigins: ["*"]</code> wildcard entries (including trimmed values) and lock behavior with regression tests. Landed from contributor PR #31058 by @byungsker. Thanks @byungsker.</li>
|
||||
<li>Web UI/Cron: include configured agent model defaults/fallbacks in cron model suggestions so scheduled-job model autocomplete reflects configured models. (#29709) Thanks @Sid-Qin.</li>
|
||||
<li>Agents/Sessions list transcript paths: handle missing/non-string/relative <code>sessions.list.path</code> values and per-agent <code>{agentId}</code> templates when deriving <code>transcriptPath</code>, so cross-agent session listings resolve to concrete agent session files instead of workspace-relative paths. (#24775) Thanks @martinfrancois.</li>
|
||||
<li>Gateway/Control UI CSP: allow required Google Fonts origins in Control UI CSP. (#29279) Thanks @Glucksberg and @vincentkoc.</li>
|
||||
<li>CLI/Install: add an npm-link fallback to fix CLI startup <code>Permission denied</code> failures (<code>exit 127</code>) on affected installs. (#17151) Thanks @sskyu and @vincentkoc.</li>
|
||||
<li>Onboarding/Custom providers: improve verification reliability for slower local endpoints (for example Ollama) during setup. (#27380) Thanks @Sid-Qin.</li>
|
||||
<li>Plugins/NPM spec install: fix npm-spec plugin installs when <code>npm pack</code> output is empty by detecting newly created <code>.tgz</code> archives in the pack directory. (#21039) Thanks @graysurf and @vincentkoc.</li>
|
||||
<li>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.</li>
|
||||
<li>Security/Feishu webhook ingress: bound unauthenticated webhook rate-limit state with stale-window pruning and a hard key cap to prevent unbounded pre-auth memory growth from rotating source keys. (#26050) Thanks @bmendonca3.</li>
|
||||
<li>Gateway/macOS supervised restart: actively <code>launchctl kickstart -k</code> during intentional supervised restarts to bypass LaunchAgent <code>ThrottleInterval</code> delays, and fall back to in-process restart when kickstart fails. Landed from contributor PR #29078 by @cathrynlavery. Thanks @cathrynlavery.</li>
|
||||
<li>Daemon/macOS TLS certs: default LaunchAgent service env <code>NODE_EXTRA_CA_CERTS</code> to <code>/etc/ssl/cert.pem</code> (while preserving explicit overrides) so HTTPS clients no longer fail with local-issuer errors under launchd. (#27915) Thanks @Lukavyi.</li>
|
||||
<li>Discord/Components wildcard handlers: use distinct internal registration sentinel IDs and parse those sentinels as wildcard keys so select/user/role/channel/mentionable/modal interactions are not dropped by raw customId dedupe paths. Landed from contributor PR #29459 by @Sid-Qin. Thanks @Sid-Qin.</li>
|
||||
<li>Feishu/Reaction notifications: add <code>channels.feishu.reactionNotifications</code> (<code>off | own | all</code>, default <code>own</code>) so operators can disable reaction ingress or allow all verified reaction events (not only bot-authored message reactions). (#28529) Thanks @cowboy129.</li>
|
||||
<li>Feishu/Typing backoff: re-throw Feishu typing add/remove rate-limit and quota errors (<code>429</code>, <code>99991400</code>, <code>99991403</code>) and detect SDK non-throwing backoff responses so the typing keepalive circuit breaker can stop retries instead of looping indefinitely. (#28494) Thanks @guoqunabc.</li>
|
||||
<li>Feishu/Zalo runtime logging: replace direct <code>console.log/error</code> usage in Feishu typing-indicator paths and Zalo monitor paths with runtime-gated logger calls so verbosity controls are respected while preserving typing backoff behavior. (#18841) Thanks @Clawborn.</li>
|
||||
<li>Feishu/Group sender allowlist fallback: add global <code>channels.feishu.groupSenderAllowFrom</code> sender authorization for group chats, with per-group <code>groups.<id>.allowFrom</code> precedence and regression coverage for allow/block/precedence behavior. (#29174) Thanks @1MoreBuild.</li>
|
||||
<li>Feishu/Docx append/write ordering: insert converted Docx blocks sequentially (single-block creates) so Feishu append/write preserves markdown block order instead of returning shuffled sections in asynchronous batch inserts. (#26172, #26022) Thanks @echoVic.</li>
|
||||
<li>Feishu/Docx convert fallback chunking: recursively split oversized markdown chunks (including long no-heading sections) when <code>document.convert</code> hits content limits, while keeping fenced-code-aware split boundaries whenever possible. (#14402) Thanks @lml2468.</li>
|
||||
<li>Feishu/API quota controls: add <code>typingIndicator</code> and <code>resolveSenderNames</code> config flags (top-level and per-account) so operators can disable typing reactions and sender-name lookup requests while keeping default behavior unchanged. (#10513) Thanks @BigUncle.</li>
|
||||
<li>Feishu/System preview prompt leakage: stop enqueuing inbound Feishu message previews as system events so user preview text is not injected into later turns as trusted <code>System:</code> context. Landed from contributor PR #31209 by @stakeswky. Thanks @stakeswky.</li>
|
||||
<li>Feishu/Typing replay suppression: skip typing indicators for stale replayed inbound messages after compaction using message-age checks with second/millisecond timestamp normalization, preventing old-message reaction floods while preserving typing for fresh messages. Landed from contributor PR #30709 by @arkyu2077. Thanks @arkyu2077.</li>
|
||||
<li>Sessions/Internal routing: preserve established external <code>lastTo</code>/<code>lastChannel</code> routes for internal/non-deliverable turns, with added coverage for no-fallback internal routing behavior. Landed from contributor PR #30941 by @graysurf. Thanks @graysurf.</li>
|
||||
<li>Control UI/Debug log layout: render Debug Event Log payloads at full width to prevent payload JSON from being squeezed into a narrow side column. Landed from contributor PR #30978 by @stozo04. Thanks @stozo04.</li>
|
||||
<li>Auto-reply/NO_REPLY: strip <code>NO_REPLY</code> token from mixed-content messages instead of leaking raw control text to end users. Landed from contributor PR #31080 by @scoootscooob. Thanks @scoootscooob.</li>
|
||||
<li>Install/npm: fix npm global install deprecation warnings. (#28318) Thanks @vincentkoc.</li>
|
||||
<li>Update/Global npm: fallback to <code>--omit=optional</code> when global <code>npm update</code> fails so optional dependency install failures no longer abort update flows. (#24896) Thanks @xinhuagu and @vincentkoc.</li>
|
||||
<li>Inbound metadata/Multi-account routing: include <code>account_id</code> in trusted inbound metadata so multi-account channel sessions can reliably disambiguate the receiving account in prompt context. Landed from contributor PR #30984 by @Stxle2. Thanks @Stxle2.</li>
|
||||
<li>Model directives/Auth profiles: split <code>/model</code> profile suffixes at the first <code>@</code> after the last slash so email-based auth profile IDs (for example OAuth profile IDs) resolve correctly. Landed from contributor PR #30932 by @haosenwang1018. Thanks @haosenwang1018.</li>
|
||||
<li>Cron/Delivery mode none: send explicit <code>delivery: { mode: "none" }</code> from cron editor for both add and update flows so previous announce delivery is actually cleared. Landed from contributor PR #31145 by @byungsker. Thanks @byungsker.</li>
|
||||
<li>Cron editor viewport: make the sticky cron edit form independently scrollable with viewport-bounded height so lower fields/actions are reachable on shorter screens. Landed from contributor PR #31133 by @Sid-Qin. Thanks @Sid-Qin.</li>
|
||||
<li>Agents/Thinking fallback: when providers reject unsupported thinking levels without enumerating alternatives, retry with <code>think=off</code> to avoid hard failure during model/provider fallback chains. Landed from contributor PR #31002 by @yfge. Thanks @yfge.</li>
|
||||
<li>Ollama/Embedded runner base URL precedence: prioritize configured provider <code>baseUrl</code> over model defaults for embedded Ollama runs so Docker and remote-host setups avoid localhost fetch failures. (#30964) Thanks @stakeswky.</li>
|
||||
<li>Agents/Failover reason classification: avoid false rate-limit classification from incidental <code>tpm</code> substrings by matching TPM as a standalone token/phrase and keeping auth-context errors on the auth path. Landed from contributor PR #31007 by @HOYALIM. Thanks @HOYALIM.</li>
|
||||
<li>CLI/Cron: clarify <code>cron list</code> output by renaming <code>Agent</code> to <code>Agent ID</code> and adding a <code>Model</code> column for isolated agent-turn jobs. (#26259) Thanks @openperf.</li>
|
||||
<li>Gateway/WS: close repeated post-handshake <code>unauthorized role:*</code> request floods per connection and sample duplicate rejection logs, preventing a single misbehaving client from degrading gateway responsiveness. (#20168) Thanks @acy103, @vibecodooor, and @vincentkoc.</li>
|
||||
<li>Gateway/Auth: improve device-auth v2 migration diagnostics so operators get clearer guidance when legacy clients connect. (#28305) Thanks @vincentkoc.</li>
|
||||
<li>CLI/Ollama config: allow <code>config set</code> for Ollama <code>apiKey</code> without predeclared provider config. (#29299) Thanks @vincentkoc.</li>
|
||||
<li>Ollama/Autodiscovery: harden autodiscovery and warning behavior. (#29201) Thanks @marcodelpin and @vincentkoc.</li>
|
||||
<li>Ollama/Context window: unify context window handling across discovery, merge, and OpenAI-compatible transport paths. (#29205) Thanks @Sid-Qin, @jimmielightner, and @vincentkoc.</li>
|
||||
<li>Agents/Ollama: demote empty-discovery logging from <code>warn</code> to <code>debug</code> to reduce noisy warnings in normal edge-case discovery flows. (#26379) Thanks @byungsker.</li>
|
||||
<li>fix(model): preserve reasoning in provider fallback resolution. (#29285) Fixes #25636. Thanks @vincentkoc.</li>
|
||||
<li>Docker/Image permissions: normalize <code>/app/extensions</code>, <code>/app/.agent</code>, and <code>/app/.agents</code> to directory mode <code>755</code> and file mode <code>644</code> during image build so plugin discovery does not block inherited world-writable paths. (#30191) Fixes #30139. Thanks @edincampara.</li>
|
||||
<li>OpenAI Responses/Compaction: rewrite and unify the OpenAI Responses store patches to treat empty <code>baseUrl</code> as non-direct, honor <code>compat.supportsStore=false</code>, and auto-inject server-side compaction <code>context_management</code> 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.</li>
|
||||
<li>Sandbox/Browser Docker: pass <code>OPENCLAW_BROWSER_NO_SANDBOX=1</code> to sandbox browser containers and bump sandbox browser security hash epoch so existing containers are recreated and pick up the env on upgrade. (#29879) Thanks @Lukavyi.</li>
|
||||
<li>Usage normalization: clamp negative prompt/input token values to zero (including <code>prompt_tokens</code> alias inputs) so <code>/usage</code> and TUI usage displays cannot show nonsensical negative counts. Landed from contributor PR #31211 by @scoootscooob. Thanks @scoootscooob.</li>
|
||||
<li>Secrets/Auth profiles: normalize inline SecretRef <code>token</code>/<code>key</code> values to canonical <code>tokenRef</code>/<code>keyRef</code> before persistence, and keep explicit <code>keyRef</code> precedence when inline refs are also present. Landed from contributor PR #31047 by @minupla. Thanks @minupla.</li>
|
||||
<li>Tools/Edit workspace boundary errors: preserve the real <code>Path escapes workspace root</code> failure path instead of surfacing a misleading access/file-not-found error when editing outside workspace roots. Landed from contributor PR #31015 by @haosenwang1018. Thanks @haosenwang1018.</li>
|
||||
<li>Browser/Open & navigate: accept <code>url</code> as an alias parameter for <code>open</code> and <code>navigate</code>. (#29260) Thanks @vincentkoc.</li>
|
||||
<li>Codex/Usage window: label weekly usage window as <code>Week</code> instead of <code>Day</code>. (#26267) Thanks @Sid-Qin.</li>
|
||||
<li>Signal/Sync message null-handling: treat <code>syncMessage</code> presence (including <code>null</code>) as sync envelope traffic so replayed sentTranscript payloads cannot bypass loop guards after daemon restart. Landed from contributor PR #31138 by @Sid-Qin. Thanks @Sid-Qin.</li>
|
||||
<li>Infra/fs-safe: sanitize directory-read failures so raw <code>EISDIR</code> text never leaks to messaging surfaces, with regression tests for both root-scoped and direct safe reads. Landed from contributor PR #31205 by @polooooo. Thanks @polooooo.</li>
|
||||
<li>Sandbox/mkdirp boundary checks: allow directory-safe boundary validation for existing in-boundary subdirectories, preventing false <code>cannot create directories</code> failures in sandbox write mode. (#30610) Thanks @glitch418x.</li>
|
||||
<li>Security/Compaction audit: remove the post-compaction audit injection message. (#28507) Thanks @fuller-stack-dev and @vincentkoc.</li>
|
||||
<li>Web tools/RFC2544 fake-IP compatibility: allow RFC2544 benchmark range (<code>198.18.0.0/15</code>) for trusted web-tool fetch endpoints so proxy fake-IP networking modes do not trigger false SSRF blocks. Landed from contributor PR #31176 by @sunkinux. Thanks @sunkinux.</li>
|
||||
<li>Telegram/Voice fallback reply chunking: apply reply reference, quote text, and inline buttons only to the first fallback text chunk when voice delivery is blocked, preventing over-quoted multi-chunk replies. Landed from contributor PR #31067 by @xdanger. Thanks @xdanger.</li>
|
||||
<li>Feishu/System preview prompt leakage: stop enqueuing inbound Feishu message previews as system events so user preview text is not injected into later turns as trusted <code>System:</code> context. Landed from contributor PR #31209 by @stakeswky. Thanks @stakeswky.</li>
|
||||
<li>Feishu/Multi-account + reply reliability: add <code>channels.feishu.defaultAccount</code> outbound routing support with schema validation, keep quoted-message extraction text-first (post/interactive/file placeholders instead of raw JSON), route Feishu video sends as <code>msg_type: "file"</code>, and avoid websocket event blocking by using non-blocking event handling in monitor dispatch. Landed from contributor PRs #29610, #30432, #30331, and #29501. Thanks @hclsys, @bmendonca3, @patrick-yingxi-pan, and @zwffff.</li>
|
||||
<li>Feishu/Typing replay suppression: skip typing indicators for stale replayed inbound messages after compaction using message-age checks with second/millisecond timestamp normalization, preventing old-message reaction floods while preserving typing for fresh messages. Landed from contributor PR #30709 by @arkyu2077. Thanks @arkyu2077.</li>
|
||||
</ul>
|
||||
<p><a href="https://github.com/openclaw/openclaw/blob/main/CHANGELOG.md">View full changelog</a></p>
|
||||
]]></description>
|
||||
<enclosure url="https://github.com/openclaw/openclaw/releases/download/v2026.3.1/OpenClaw-2026.3.1.zip" length="12804155" type="application/octet-stream" sparkle:edSignature="TF1otD4Vk3pG0iViX7mvix5DQEgAsk4JkSFvH7opjf9aawV16f29SUa2wRmiCFU6HEgyNgnGI/078O+A27eXCA=="/>
|
||||
<!-- pragma: allowlist secret -->
|
||||
</item>
|
||||
</channel>
|
||||
</rss>
|
||||
@@ -63,8 +63,8 @@ android {
|
||||
applicationId = "ai.openclaw.app"
|
||||
minSdk = 31
|
||||
targetSdk = 36
|
||||
versionCode = 202603081
|
||||
versionName = "2026.3.8"
|
||||
versionCode = 202603090
|
||||
versionName = "2026.3.9"
|
||||
ndk {
|
||||
// Support all major ABIs — native libs are tiny (~47 KB per ABI)
|
||||
abiFilters += listOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>XPC!</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>2026.3.8</string>
|
||||
<string>2026.3.9</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>20260308</string>
|
||||
<key>NSExtension</key>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>XPC!</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>2026.3.8</string>
|
||||
<string>2026.3.9</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>20260308</string>
|
||||
<key>NSExtension</key>
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>2026.3.8</string>
|
||||
<string>2026.3.9</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>BNDL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>2026.3.8</string>
|
||||
<string>2026.3.9</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>20260308</string>
|
||||
</dict>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>2026.3.8</string>
|
||||
<string>2026.3.9</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>20260308</string>
|
||||
<key>WKCompanionAppBundleIdentifier</key>
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>2026.3.8</string>
|
||||
<string>2026.3.9</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>20260308</string>
|
||||
<key>NSExtension</key>
|
||||
|
||||
@@ -107,7 +107,7 @@ targets:
|
||||
- CFBundleURLName: ai.openclaw.ios
|
||||
CFBundleURLSchemes:
|
||||
- openclaw
|
||||
CFBundleShortVersionString: "2026.3.8"
|
||||
CFBundleShortVersionString: "2026.3.9"
|
||||
CFBundleVersion: "20260308"
|
||||
UILaunchScreen: {}
|
||||
UIApplicationSceneManifest:
|
||||
@@ -168,7 +168,7 @@ targets:
|
||||
path: ShareExtension/Info.plist
|
||||
properties:
|
||||
CFBundleDisplayName: OpenClaw Share
|
||||
CFBundleShortVersionString: "2026.3.8"
|
||||
CFBundleShortVersionString: "2026.3.9"
|
||||
CFBundleVersion: "20260308"
|
||||
NSExtension:
|
||||
NSExtensionPointIdentifier: com.apple.share-services
|
||||
@@ -205,7 +205,7 @@ targets:
|
||||
path: ActivityWidget/Info.plist
|
||||
properties:
|
||||
CFBundleDisplayName: OpenClaw Activity
|
||||
CFBundleShortVersionString: "2026.3.8"
|
||||
CFBundleShortVersionString: "2026.3.9"
|
||||
CFBundleVersion: "20260308"
|
||||
NSSupportsLiveActivities: true
|
||||
NSExtension:
|
||||
@@ -231,7 +231,7 @@ targets:
|
||||
path: WatchApp/Info.plist
|
||||
properties:
|
||||
CFBundleDisplayName: OpenClaw
|
||||
CFBundleShortVersionString: "2026.3.8"
|
||||
CFBundleShortVersionString: "2026.3.9"
|
||||
CFBundleVersion: "20260308"
|
||||
WKCompanionAppBundleIdentifier: "$(OPENCLAW_APP_BUNDLE_ID)"
|
||||
WKWatchKitApp: true
|
||||
@@ -256,7 +256,7 @@ targets:
|
||||
path: WatchExtension/Info.plist
|
||||
properties:
|
||||
CFBundleDisplayName: OpenClaw
|
||||
CFBundleShortVersionString: "2026.3.8"
|
||||
CFBundleShortVersionString: "2026.3.9"
|
||||
CFBundleVersion: "20260308"
|
||||
NSExtension:
|
||||
NSExtensionAttributes:
|
||||
@@ -293,7 +293,7 @@ targets:
|
||||
path: Tests/Info.plist
|
||||
properties:
|
||||
CFBundleDisplayName: OpenClawTests
|
||||
CFBundleShortVersionString: "2026.3.8"
|
||||
CFBundleShortVersionString: "2026.3.9"
|
||||
CFBundleVersion: "20260308"
|
||||
|
||||
OpenClawLogicTests:
|
||||
@@ -319,5 +319,5 @@ targets:
|
||||
path: Tests/Info.plist
|
||||
properties:
|
||||
CFBundleDisplayName: OpenClawLogicTests
|
||||
CFBundleShortVersionString: "2026.3.8"
|
||||
CFBundleShortVersionString: "2026.3.9"
|
||||
CFBundleVersion: "20260308"
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>2026.3.8</string>
|
||||
<string>2026.3.9</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>202603080</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
|
||||
@@ -2504,7 +2504,7 @@ Your gateway is running with auth enabled (`gateway.auth.*`), but the UI is not
|
||||
|
||||
Facts (from code):
|
||||
|
||||
- The Control UI keeps the token in memory for the current tab; it no longer persists gateway tokens in browser localStorage.
|
||||
- The Control UI keeps the token in `sessionStorage` for the current browser tab session and selected gateway URL, so same-tab refreshes keep working without restoring long-lived localStorage token persistence.
|
||||
|
||||
Fix:
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ Notes:
|
||||
# Default is auto-derived from APP_VERSION when omitted.
|
||||
SKIP_NOTARIZE=1 \
|
||||
BUNDLE_ID=ai.openclaw.mac \
|
||||
APP_VERSION=2026.3.8 \
|
||||
APP_VERSION=2026.3.9 \
|
||||
BUILD_CONFIG=release \
|
||||
SIGN_IDENTITY="Developer ID Application: <Developer Name> (<TEAMID>)" \
|
||||
scripts/package-mac-dist.sh
|
||||
@@ -47,10 +47,10 @@ scripts/package-mac-dist.sh
|
||||
# `package-mac-dist.sh` already creates the zip + DMG.
|
||||
# If you used `package-mac-app.sh` directly instead, create them manually:
|
||||
# If you want notarization/stapling in this step, use the NOTARIZE command below.
|
||||
ditto -c -k --sequesterRsrc --keepParent dist/OpenClaw.app dist/OpenClaw-2026.3.8.zip
|
||||
ditto -c -k --sequesterRsrc --keepParent dist/OpenClaw.app dist/OpenClaw-2026.3.9.zip
|
||||
|
||||
# Optional: build a styled DMG for humans (drag to /Applications)
|
||||
scripts/create-dmg.sh dist/OpenClaw.app dist/OpenClaw-2026.3.8.dmg
|
||||
scripts/create-dmg.sh dist/OpenClaw.app dist/OpenClaw-2026.3.9.dmg
|
||||
|
||||
# Recommended: build + notarize/staple zip + DMG
|
||||
# First, create a keychain profile once:
|
||||
@@ -58,13 +58,13 @@ scripts/create-dmg.sh dist/OpenClaw.app dist/OpenClaw-2026.3.8.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.3.8 \
|
||||
APP_VERSION=2026.3.9 \
|
||||
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.3.8.dSYM.zip
|
||||
ditto -c -k --keepParent apps/macos/.build/release/OpenClaw.app.dSYM dist/OpenClaw-2026.3.9.dSYM.zip
|
||||
```
|
||||
|
||||
## Appcast entry
|
||||
@@ -72,7 +72,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.3.8.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.3.9.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.
|
||||
@@ -80,7 +80,7 @@ Commit the updated `appcast.xml` alongside the release assets (zip + dSYM) when
|
||||
|
||||
## Publish & verify
|
||||
|
||||
- Upload `OpenClaw-2026.3.8.zip` (and `OpenClaw-2026.3.8.dSYM.zip`) to the GitHub release for tag `v2026.3.8`.
|
||||
- Upload `OpenClaw-2026.3.9.zip` (and `OpenClaw-2026.3.9.dSYM.zip`) to the GitHub release for tag `v2026.3.9`.
|
||||
- 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.
|
||||
|
||||
@@ -27,7 +27,7 @@ Auth is supplied during the WebSocket handshake via:
|
||||
|
||||
- `connect.params.auth.token`
|
||||
- `connect.params.auth.password`
|
||||
The dashboard settings panel lets you store a token; passwords are not persisted.
|
||||
The dashboard settings panel keeps a token for the current browser tab session and selected gateway URL; passwords are not persisted.
|
||||
The onboarding wizard generates a gateway token by default, so paste it here on first connect.
|
||||
|
||||
## Device pairing (first connection)
|
||||
@@ -237,7 +237,7 @@ http://localhost:5173/?gatewayUrl=wss://<gateway-host>:18789#token=<gateway-toke
|
||||
Notes:
|
||||
|
||||
- `gatewayUrl` is stored in localStorage after load and removed from the URL.
|
||||
- `token` is imported into memory for the current tab and stripped from the URL; it is not stored in localStorage.
|
||||
- `token` is imported from the URL fragment, stored in sessionStorage for the current browser tab session and selected gateway URL, and stripped from the URL; it is not stored in localStorage.
|
||||
- `password` is kept in memory only.
|
||||
- When `gatewayUrl` is set, the UI does not fall back to config or environment credentials.
|
||||
Provide `token` (or `password`) explicitly. Missing explicit credentials is an error.
|
||||
|
||||
@@ -24,8 +24,8 @@ Authentication is enforced at the WebSocket handshake via `connect.params.auth`
|
||||
(token or password). See `gateway.auth` in [Gateway configuration](/gateway/configuration).
|
||||
|
||||
Security note: the Control UI is an **admin surface** (chat, config, exec approvals).
|
||||
Do not expose it publicly. The UI keeps dashboard URL tokens in memory for the current tab
|
||||
and strips them from the URL after load.
|
||||
Do not expose it publicly. The UI keeps dashboard URL tokens in sessionStorage
|
||||
for the current browser tab session and selected gateway URL, and strips them from the URL after load.
|
||||
Prefer localhost, Tailscale Serve, or an SSH tunnel.
|
||||
|
||||
## Fast path (recommended)
|
||||
@@ -37,7 +37,7 @@ Prefer localhost, Tailscale Serve, or an SSH tunnel.
|
||||
## Token basics (local vs remote)
|
||||
|
||||
- **Localhost**: open `http://127.0.0.1:18789/`.
|
||||
- **Token source**: `gateway.auth.token` (or `OPENCLAW_GATEWAY_TOKEN`); `openclaw dashboard` can pass it via URL fragment for one-time bootstrap, but the Control UI does not persist gateway tokens in localStorage.
|
||||
- **Token source**: `gateway.auth.token` (or `OPENCLAW_GATEWAY_TOKEN`); `openclaw dashboard` can pass it via URL fragment for one-time bootstrap, and the Control UI keeps it in sessionStorage for the current browser tab session and selected gateway URL instead of localStorage.
|
||||
- If `gateway.auth.token` is SecretRef-managed, `openclaw dashboard` prints/copies/opens a non-tokenized URL by design. This avoids exposing externally managed tokens in shell logs, clipboard history, or browser-launch arguments.
|
||||
- If `gateway.auth.token` is configured as a SecretRef and is unresolved in your current shell, `openclaw dashboard` still prints a non-tokenized URL plus actionable auth setup guidance.
|
||||
- **Not localhost**: use Tailscale Serve (tokenless for Control UI/WebSocket if `gateway.auth.allowTailscale: true`, assumes trusted gateway host; HTTP APIs still need token/password), tailnet bind with a token, or an SSH tunnel. See [Web surfaces](/web).
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@openclaw/acpx",
|
||||
"version": "2026.3.8",
|
||||
"version": "2026.3.9",
|
||||
"description": "OpenClaw ACP runtime backend via acpx",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@openclaw/bluebubbles",
|
||||
"version": "2026.3.8",
|
||||
"version": "2026.3.9",
|
||||
"description": "OpenClaw BlueBubbles channel plugin",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@openclaw/copilot-proxy",
|
||||
"version": "2026.3.8",
|
||||
"version": "2026.3.9",
|
||||
"private": true,
|
||||
"description": "OpenClaw Copilot Proxy provider plugin",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@openclaw/diagnostics-otel",
|
||||
"version": "2026.3.8",
|
||||
"version": "2026.3.9",
|
||||
"description": "OpenClaw diagnostics OpenTelemetry exporter",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@openclaw/diffs",
|
||||
"version": "2026.3.8",
|
||||
"version": "2026.3.9",
|
||||
"private": true,
|
||||
"description": "OpenClaw diff viewer plugin",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@openclaw/discord",
|
||||
"version": "2026.3.8",
|
||||
"version": "2026.3.9",
|
||||
"description": "OpenClaw Discord channel plugin",
|
||||
"type": "module",
|
||||
"openclaw": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@openclaw/feishu",
|
||||
"version": "2026.3.8",
|
||||
"version": "2026.3.9",
|
||||
"description": "OpenClaw Feishu/Lark channel plugin (community maintained by @m1heng)",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@openclaw/google-gemini-cli-auth",
|
||||
"version": "2026.3.8",
|
||||
"version": "2026.3.9",
|
||||
"private": true,
|
||||
"description": "OpenClaw Gemini CLI OAuth provider plugin",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@openclaw/googlechat",
|
||||
"version": "2026.3.8",
|
||||
"version": "2026.3.9",
|
||||
"private": true,
|
||||
"description": "OpenClaw Google Chat channel plugin",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@openclaw/imessage",
|
||||
"version": "2026.3.8",
|
||||
"version": "2026.3.9",
|
||||
"private": true,
|
||||
"description": "OpenClaw iMessage channel plugin",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@openclaw/irc",
|
||||
"version": "2026.3.8",
|
||||
"version": "2026.3.9",
|
||||
"description": "OpenClaw IRC channel plugin",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@openclaw/line",
|
||||
"version": "2026.3.8",
|
||||
"version": "2026.3.9",
|
||||
"private": true,
|
||||
"description": "OpenClaw LINE channel plugin",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@openclaw/llm-task",
|
||||
"version": "2026.3.8",
|
||||
"version": "2026.3.9",
|
||||
"private": true,
|
||||
"description": "OpenClaw JSON-only LLM task plugin",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@openclaw/lobster",
|
||||
"version": "2026.3.8",
|
||||
"version": "2026.3.9",
|
||||
"description": "Lobster workflow tool plugin (typed pipelines + resumable approvals)",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
# Changelog
|
||||
|
||||
## 2026.3.9
|
||||
|
||||
### Changes
|
||||
|
||||
- Version alignment with core OpenClaw release numbers.
|
||||
|
||||
## 2026.3.8-beta.1
|
||||
|
||||
### Changes
|
||||
|
||||
- Version alignment with core OpenClaw release numbers.
|
||||
|
||||
## 2026.3.8
|
||||
|
||||
### Changes
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"name": "@openclaw/matrix",
|
||||
"version": "2026.3.8",
|
||||
"version": "2026.3.9",
|
||||
"description": "OpenClaw Matrix channel plugin",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@mariozechner/pi-agent-core": "0.55.3",
|
||||
"@mariozechner/pi-agent-core": "0.57.1",
|
||||
"@matrix-org/matrix-sdk-crypto-nodejs": "^0.4.0",
|
||||
"@vector-im/matrix-bot-sdk": "0.8.0-element.3",
|
||||
"markdown-it": "14.1.1",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@openclaw/mattermost",
|
||||
"version": "2026.3.8",
|
||||
"version": "2026.3.9",
|
||||
"description": "OpenClaw Mattermost channel plugin",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@openclaw/memory-core",
|
||||
"version": "2026.3.8",
|
||||
"version": "2026.3.9",
|
||||
"private": true,
|
||||
"description": "OpenClaw core memory search plugin",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@openclaw/memory-lancedb",
|
||||
"version": "2026.3.8",
|
||||
"version": "2026.3.9",
|
||||
"private": true,
|
||||
"description": "OpenClaw LanceDB-backed long-term memory plugin with auto-recall/capture",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@openclaw/minimax-portal-auth",
|
||||
"version": "2026.3.8",
|
||||
"version": "2026.3.9",
|
||||
"private": true,
|
||||
"description": "OpenClaw MiniMax Portal OAuth provider plugin",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
# Changelog
|
||||
|
||||
## 2026.3.9
|
||||
|
||||
### Changes
|
||||
|
||||
- Version alignment with core OpenClaw release numbers.
|
||||
|
||||
## 2026.3.8-beta.1
|
||||
|
||||
### Changes
|
||||
|
||||
- Version alignment with core OpenClaw release numbers.
|
||||
|
||||
## 2026.3.8
|
||||
|
||||
### Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@openclaw/msteams",
|
||||
"version": "2026.3.8",
|
||||
"version": "2026.3.9",
|
||||
"description": "OpenClaw Microsoft Teams channel plugin",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@openclaw/nextcloud-talk",
|
||||
"version": "2026.3.8",
|
||||
"version": "2026.3.9",
|
||||
"description": "OpenClaw Nextcloud Talk channel plugin",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
# Changelog
|
||||
|
||||
## 2026.3.9
|
||||
|
||||
### Changes
|
||||
|
||||
- Version alignment with core OpenClaw release numbers.
|
||||
|
||||
## 2026.3.8-beta.1
|
||||
|
||||
### Changes
|
||||
|
||||
- Version alignment with core OpenClaw release numbers.
|
||||
|
||||
## 2026.3.8
|
||||
|
||||
### Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@openclaw/nostr",
|
||||
"version": "2026.3.8",
|
||||
"version": "2026.3.9",
|
||||
"description": "OpenClaw Nostr channel plugin for NIP-04 encrypted DMs",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@openclaw/open-prose",
|
||||
"version": "2026.3.8",
|
||||
"version": "2026.3.9",
|
||||
"private": true,
|
||||
"description": "OpenProse VM skill pack plugin (slash command + telemetry).",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@openclaw/signal",
|
||||
"version": "2026.3.8",
|
||||
"version": "2026.3.9",
|
||||
"private": true,
|
||||
"description": "OpenClaw Signal channel plugin",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@openclaw/slack",
|
||||
"version": "2026.3.8",
|
||||
"version": "2026.3.9",
|
||||
"private": true,
|
||||
"description": "OpenClaw Slack channel plugin",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@openclaw/synology-chat",
|
||||
"version": "2026.3.8",
|
||||
"version": "2026.3.9",
|
||||
"description": "Synology Chat channel plugin for OpenClaw",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@openclaw/telegram",
|
||||
"version": "2026.3.8",
|
||||
"version": "2026.3.9",
|
||||
"private": true,
|
||||
"description": "OpenClaw Telegram channel plugin",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@openclaw/tlon",
|
||||
"version": "2026.3.8",
|
||||
"version": "2026.3.9",
|
||||
"description": "OpenClaw Tlon/Urbit channel plugin",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
# Changelog
|
||||
|
||||
## 2026.3.9
|
||||
|
||||
### Changes
|
||||
|
||||
- Version alignment with core OpenClaw release numbers.
|
||||
|
||||
## 2026.3.8-beta.1
|
||||
|
||||
### Changes
|
||||
|
||||
- Version alignment with core OpenClaw release numbers.
|
||||
|
||||
## 2026.3.8
|
||||
|
||||
### Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@openclaw/twitch",
|
||||
"version": "2026.3.8",
|
||||
"version": "2026.3.9",
|
||||
"description": "OpenClaw Twitch channel plugin",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
# Changelog
|
||||
|
||||
## 2026.3.9
|
||||
|
||||
### Changes
|
||||
|
||||
- Version alignment with core OpenClaw release numbers.
|
||||
|
||||
## 2026.3.8-beta.1
|
||||
|
||||
### Changes
|
||||
|
||||
- Version alignment with core OpenClaw release numbers.
|
||||
|
||||
## 2026.3.8
|
||||
|
||||
### Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@openclaw/voice-call",
|
||||
"version": "2026.3.8",
|
||||
"version": "2026.3.9",
|
||||
"description": "OpenClaw voice-call plugin",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@openclaw/whatsapp",
|
||||
"version": "2026.3.8",
|
||||
"version": "2026.3.9",
|
||||
"private": true,
|
||||
"description": "OpenClaw WhatsApp channel plugin",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
# Changelog
|
||||
|
||||
## 2026.3.9
|
||||
|
||||
### Changes
|
||||
|
||||
- Version alignment with core OpenClaw release numbers.
|
||||
|
||||
## 2026.3.8-beta.1
|
||||
|
||||
### Changes
|
||||
|
||||
- Version alignment with core OpenClaw release numbers.
|
||||
|
||||
## 2026.3.8
|
||||
|
||||
### Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@openclaw/zalo",
|
||||
"version": "2026.3.8",
|
||||
"version": "2026.3.9",
|
||||
"description": "OpenClaw Zalo channel plugin",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
# Changelog
|
||||
|
||||
## 2026.3.9
|
||||
|
||||
### Changes
|
||||
|
||||
- Version alignment with core OpenClaw release numbers.
|
||||
|
||||
## 2026.3.8-beta.1
|
||||
|
||||
### Changes
|
||||
|
||||
- Version alignment with core OpenClaw release numbers.
|
||||
|
||||
## 2026.3.8
|
||||
|
||||
### Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@openclaw/zalouser",
|
||||
"version": "2026.3.8",
|
||||
"version": "2026.3.9",
|
||||
"description": "OpenClaw Zalo Personal Account plugin via native zca-js integration",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
|
||||
14
package.json
14
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "openclaw",
|
||||
"version": "2026.3.8",
|
||||
"version": "2026.3.9",
|
||||
"description": "Multi-channel AI gateway with extensible messaging integrations",
|
||||
"keywords": [],
|
||||
"homepage": "https://github.com/openclaw/openclaw#readme",
|
||||
@@ -344,10 +344,10 @@
|
||||
"@larksuiteoapi/node-sdk": "^1.59.0",
|
||||
"@line/bot-sdk": "^10.6.0",
|
||||
"@lydell/node-pty": "1.2.0-beta.3",
|
||||
"@mariozechner/pi-agent-core": "0.55.3",
|
||||
"@mariozechner/pi-ai": "0.55.3",
|
||||
"@mariozechner/pi-coding-agent": "0.55.3",
|
||||
"@mariozechner/pi-tui": "0.55.3",
|
||||
"@mariozechner/pi-agent-core": "0.57.1",
|
||||
"@mariozechner/pi-ai": "0.57.1",
|
||||
"@mariozechner/pi-coding-agent": "0.57.1",
|
||||
"@mariozechner/pi-tui": "0.57.1",
|
||||
"@mozilla/readability": "^0.6.0",
|
||||
"@sinclair/typebox": "0.34.48",
|
||||
"@slack/bolt": "^4.6.0",
|
||||
@@ -380,7 +380,7 @@
|
||||
"qrcode-terminal": "^0.12.0",
|
||||
"sharp": "^0.34.5",
|
||||
"sqlite-vec": "0.1.7-alpha.2",
|
||||
"tar": "7.5.10",
|
||||
"tar": "7.5.11",
|
||||
"tslog": "^4.10.2",
|
||||
"undici": "^7.22.0",
|
||||
"ws": "^8.19.0",
|
||||
@@ -396,7 +396,7 @@
|
||||
"@types/node": "^25.3.5",
|
||||
"@types/qrcode-terminal": "^0.12.2",
|
||||
"@types/ws": "^8.18.1",
|
||||
"@typescript/native-preview": "7.0.0-dev.20260307.1",
|
||||
"@typescript/native-preview": "7.0.0-dev.20260308.1",
|
||||
"@vitest/coverage-v8": "^4.0.18",
|
||||
"jscpd": "4.0.8",
|
||||
"lit": "^3.3.2",
|
||||
|
||||
481
pnpm-lock.yaml
generated
481
pnpm-lock.yaml
generated
@@ -58,17 +58,17 @@ importers:
|
||||
specifier: 1.2.0-beta.3
|
||||
version: 1.2.0-beta.3
|
||||
'@mariozechner/pi-agent-core':
|
||||
specifier: 0.55.3
|
||||
version: 0.55.3(ws@8.19.0)(zod@4.3.6)
|
||||
specifier: 0.57.1
|
||||
version: 0.57.1(ws@8.19.0)(zod@4.3.6)
|
||||
'@mariozechner/pi-ai':
|
||||
specifier: 0.55.3
|
||||
version: 0.55.3(ws@8.19.0)(zod@4.3.6)
|
||||
specifier: 0.57.1
|
||||
version: 0.57.1(ws@8.19.0)(zod@4.3.6)
|
||||
'@mariozechner/pi-coding-agent':
|
||||
specifier: 0.55.3
|
||||
version: 0.55.3(ws@8.19.0)(zod@4.3.6)
|
||||
specifier: 0.57.1
|
||||
version: 0.57.1(ws@8.19.0)(zod@4.3.6)
|
||||
'@mariozechner/pi-tui':
|
||||
specifier: 0.55.3
|
||||
version: 0.55.3
|
||||
specifier: 0.57.1
|
||||
version: 0.57.1
|
||||
'@mozilla/readability':
|
||||
specifier: ^0.6.0
|
||||
version: 0.6.0
|
||||
@@ -215,8 +215,8 @@ importers:
|
||||
specifier: ^8.18.1
|
||||
version: 8.18.1
|
||||
'@typescript/native-preview':
|
||||
specifier: 7.0.0-dev.20260307.1
|
||||
version: 7.0.0-dev.20260307.1
|
||||
specifier: 7.0.0-dev.20260308.1
|
||||
version: 7.0.0-dev.20260308.1
|
||||
'@vitest/coverage-v8':
|
||||
specifier: ^4.0.18
|
||||
version: 4.0.18(@vitest/browser@4.0.18(vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2))(vitest@4.0.18))(vitest@4.0.18)
|
||||
@@ -240,7 +240,7 @@ importers:
|
||||
version: 0.21.1(signal-polyfill@0.2.2)
|
||||
tsdown:
|
||||
specifier: 0.21.0
|
||||
version: 0.21.0(@typescript/native-preview@7.0.0-dev.20260307.1)(typescript@5.9.3)
|
||||
version: 0.21.0(@typescript/native-preview@7.0.0-dev.20260308.1)(typescript@5.9.3)
|
||||
tsx:
|
||||
specifier: ^4.21.0
|
||||
version: 4.21.0
|
||||
@@ -369,8 +369,8 @@ importers:
|
||||
extensions/matrix:
|
||||
dependencies:
|
||||
'@mariozechner/pi-agent-core':
|
||||
specifier: 0.55.3
|
||||
version: 0.55.3(ws@8.19.0)(zod@4.3.6)
|
||||
specifier: 0.57.1
|
||||
version: 0.57.1(ws@8.19.0)(zod@4.3.6)
|
||||
'@matrix-org/matrix-sdk-crypto-nodejs':
|
||||
specifier: ^0.4.0
|
||||
version: 0.4.0
|
||||
@@ -622,6 +622,10 @@ packages:
|
||||
resolution: {integrity: sha512-GA96wgTFB4Z5vhysm+hErbgiEWZ9JqAl09BxARajL7Oanpf0KvdIjxuLp2rD/XqEIks9yG/5Rh9XIAoCUUTZXw==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
|
||||
'@aws-sdk/client-bedrock-runtime@3.1004.0':
|
||||
resolution: {integrity: sha512-t8cl+bPLlHZQD2Sw1a4hSLUybqJZU71+m8znkyeU8CHntFqEp2mMbuLKdHKaAYQ1fAApXMsvzenCAkDzNeeJlw==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
|
||||
'@aws-sdk/client-bedrock@3.1000.0':
|
||||
resolution: {integrity: sha512-wGU8uJXrPW/hZuHdPNVe1kAFIBiKcslBcoDBN0eYBzS13um8p5jJiQJ9WsD1nSpKCmyx7qZXc6xjcbIQPyOrrA==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
@@ -710,6 +714,10 @@ packages:
|
||||
resolution: {integrity: sha512-8aiVJh6fTdl8gcyL+sVNcNwTtWpmoFa1Sh7xlj6Z7L/cZ/tYMEBHq44wTYG8Kt0z/PpGNopD89nbj3FHl9QmTA==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
|
||||
'@aws-sdk/eventstream-handler-node@3.972.10':
|
||||
resolution: {integrity: sha512-g2Z9s6Y4iNh0wICaEqutgYgt/Pmhv5Ev9G3eKGFe2w9VuZDhc76vYdop6I5OocmpHV79d4TuLG+JWg5rQIVDVA==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
|
||||
'@aws-sdk/eventstream-handler-node@3.972.9':
|
||||
resolution: {integrity: sha512-mKPiiVssgFDWkAXdEDh8+wpr2pFSX/fBn2onXXnrfIAYbdZhYb4WilKbZ3SJMUnQi+Y48jZMam5J0RrgARluaA==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
@@ -722,6 +730,10 @@ packages:
|
||||
resolution: {integrity: sha512-mB2+3G/oxRC+y9WRk0KCdradE2rSfxxJpcOSmAm+vDh3ex3WQHVLZ1catNIe1j5NQ+3FLBsNMRPVGkZ43PRpjw==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
|
||||
'@aws-sdk/middleware-eventstream@3.972.7':
|
||||
resolution: {integrity: sha512-VWndapHYCfwLgPpCb/xwlMKG4imhFzKJzZcKOEioGn7OHY+6gdr0K7oqy1HZgbLa3ACznZ9fku+DzmAi8fUC0g==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
|
||||
'@aws-sdk/middleware-expect-continue@3.972.6':
|
||||
resolution: {integrity: sha512-QMdffpU+GkSGC+bz6WdqlclqIeCsOfgX8JFZ5xvwDtX+UTj4mIXm3uXu7Ko6dBseRcJz1FA6T9OmlAAY6JgJUg==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
@@ -778,6 +790,10 @@ packages:
|
||||
resolution: {integrity: sha512-uNqRpbL6djE+XXO4cQ+P8ra37cxNNBP+2IfkVOXu1xFdGMfW+uOTxBQuDPpP43i40PBRBXK5un79l/oYpbzYkA==}
|
||||
engines: {node: '>= 14.0.0'}
|
||||
|
||||
'@aws-sdk/middleware-websocket@3.972.12':
|
||||
resolution: {integrity: sha512-iyPP6FVDKe/5wy5ojC0akpDFG1vX3FeCUU47JuwN8xfvT66xlEI8qUJZPtN55TJVFzzWZJpWL78eqUE31md08Q==}
|
||||
engines: {node: '>= 14.0.0'}
|
||||
|
||||
'@aws-sdk/nested-clients@3.996.3':
|
||||
resolution: {integrity: sha512-AU5TY1V29xqwg/MxmA2odwysTez+ccFAhmfRJk+QZT5HNv90UTA9qKd1J9THlsQkvmH7HWTEV1lDNxkQO5PzNw==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
@@ -838,6 +854,10 @@ packages:
|
||||
resolution: {integrity: sha512-0YNVNgFyziCejXJx0rzxPiD2rkxTWco4c9wiMF6n37Tb9aQvIF8+t7GyEyIFCwQHZ0VMQaAl+nCZHOYz5I5EKw==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
|
||||
'@aws-sdk/util-format-url@3.972.7':
|
||||
resolution: {integrity: sha512-V+PbnWfUl93GuFwsOHsAq7hY/fnm9kElRqR8IexIJr5Rvif9e614X5sGSyz3mVSf1YAZ+VTy63W1/pGdA55zyA==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
|
||||
'@aws-sdk/util-locate-window@3.965.4':
|
||||
resolution: {integrity: sha512-H1onv5SkgPBK2P6JR2MjGgbOnttoNzSPIRoeZTNPZYyaplwGg50zS3amXvXqF0/qfXpWEC9rLWU564QTB9bSog==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
@@ -1211,6 +1231,15 @@ packages:
|
||||
'@modelcontextprotocol/sdk':
|
||||
optional: true
|
||||
|
||||
'@google/genai@1.44.0':
|
||||
resolution: {integrity: sha512-kRt9ZtuXmz+tLlcNntN/VV4LRdpl6ZOu5B1KbfNgfR65db15O6sUQcwnwLka8sT/V6qysD93fWrgJHF2L7dA9A==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
peerDependencies:
|
||||
'@modelcontextprotocol/sdk': ^1.25.2
|
||||
peerDependenciesMeta:
|
||||
'@modelcontextprotocol/sdk':
|
||||
optional: true
|
||||
|
||||
'@grammyjs/runner@2.0.3':
|
||||
resolution: {integrity: sha512-nckmTs1dPWfVQteK9cxqxzE+0m1VRvluLWB8UgFzsjg62w3qthPJt0TYtJBEdG7OedvfQq4vnFAyE6iaMkR42A==}
|
||||
engines: {node: '>=12.20.0 || >=14.13.1'}
|
||||
@@ -1619,20 +1648,38 @@ packages:
|
||||
resolution: {integrity: sha512-rqbfpQ9BrP6BDiW+Ps3A8Z/p9+Md/pAfc/ECq8JP6cwnZL/jQgU355KWZKtF8zM9az1p0Q9hIWi9cQygVo6Auw==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
|
||||
'@mariozechner/pi-agent-core@0.57.1':
|
||||
resolution: {integrity: sha512-WXsBbkNWOObFGHkhixaT8GXJpHDd3+fn8QntYF+4R8Sa9WB90ENXWidO6b7vcKX+JX0jjO5dIsQxmzosARJKlg==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
|
||||
'@mariozechner/pi-ai@0.55.3':
|
||||
resolution: {integrity: sha512-f9jWoDzJR9Wy/H8JPMbjoM4WvVUeFZ65QdYA9UHIfoOopDfwWE8F8JHQOj5mmmILMacXuzsqA3J7MYqNWZRvvQ==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
hasBin: true
|
||||
|
||||
'@mariozechner/pi-ai@0.57.1':
|
||||
resolution: {integrity: sha512-Bd/J4a3YpdzJVyHLih0vDSdB0QPL4ti0XsAwtHOK/8eVhB0fHM1CpcgIrcBFJ23TMcKXMi0qamz18ERfp8tmgg==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
hasBin: true
|
||||
|
||||
'@mariozechner/pi-coding-agent@0.55.3':
|
||||
resolution: {integrity: sha512-5SFbB7/BIp/Crjre7UNjUeNfpoU1KSW/i6LXa+ikJTBqI5LukWq2avE5l0v0M8Pg/dt1go2XCLrNFlQJiQDSPQ==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
hasBin: true
|
||||
|
||||
'@mariozechner/pi-coding-agent@0.57.1':
|
||||
resolution: {integrity: sha512-u5MQEduj68rwVIsRsqrWkJYiJCyPph/a6bMoJAQKo1sb+Pc17Y/ojwa+wGssnUMjEB38AQKofWTVe8NFEpSWNw==}
|
||||
engines: {node: '>=20.6.0'}
|
||||
hasBin: true
|
||||
|
||||
'@mariozechner/pi-tui@0.55.3':
|
||||
resolution: {integrity: sha512-Gh4wkYgiSPCJJaB/4wEWSL7Ga8bxSq1Crp1RPRT4vKybE/DG0W/MQr5VJDvktarxtJrD16ixScwE4dzdox/PIA==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
|
||||
'@mariozechner/pi-tui@0.57.1':
|
||||
resolution: {integrity: sha512-cjoRghLbeAHV0tTJeHgZXaryUi5zzBZofeZ7uJun1gztnckLLRjoVeaPTujNlc5BIfyKvFqhh1QWCZng/MXlpg==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
|
||||
'@matrix-org/matrix-sdk-crypto-nodejs@0.4.0':
|
||||
resolution: {integrity: sha512-+qqgpn39XFSbsD0dFjssGO9vHEP7sTyfs8yTpt8vuqWpUpF20QMwpCZi0jpYw7GxjErNTsMshopuo8677DfGEA==}
|
||||
engines: {node: '>= 22'}
|
||||
@@ -1648,6 +1695,9 @@ packages:
|
||||
'@mistralai/mistralai@1.10.0':
|
||||
resolution: {integrity: sha512-tdIgWs4Le8vpvPiUEWne6tK0qbVc+jMenujnvTqOjogrJUsCSQhus0tHTU1avDDh5//Rq2dFgP9mWRAdIEoBqg==}
|
||||
|
||||
'@mistralai/mistralai@1.14.1':
|
||||
resolution: {integrity: sha512-IiLmmZFCCTReQgPAT33r7KQ1nYo5JPdvGkrkZqA8qQ2qB1GHgs5LoP5K2ICyrjnpw2n8oSxMM/VP+liiKcGNlQ==}
|
||||
|
||||
'@mozilla/readability@0.6.0':
|
||||
resolution: {integrity: sha512-juG5VWh4qAivzTAeMzvY9xs9HY5rAcr2E4I7tiSSCokRFi7XIZCAu92ZkSTsIj1OPceCifL3cpfteP3pDT9/QQ==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
@@ -2800,22 +2850,42 @@ packages:
|
||||
resolution: {integrity: sha512-A4ynrsFFfSXUHicfTcRehytppFBcY3HQxEGYiyGktPIOye3Ot7fxpiy4VR42WmtGI4Wfo6OXt/c1Ky1nUFxYYQ==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@smithy/eventstream-codec@4.2.11':
|
||||
resolution: {integrity: sha512-Sf39Ml0iVX+ba/bgMPxaXWAAFmHqYLTmbjAPfLPLY8CrYkRDEqZdUsKC1OwVMCdJXfAt0v4j49GIJ8DoSYAe6w==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@smithy/eventstream-serde-browser@4.2.10':
|
||||
resolution: {integrity: sha512-0xupsu9yj9oDVuQ50YCTS9nuSYhGlrwqdaKQel9y2Fz7LU9fNErVlw9N0o4pm4qqvWEGbSTI4HKc6XJfB30MVw==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@smithy/eventstream-serde-browser@4.2.11':
|
||||
resolution: {integrity: sha512-3rEpo3G6f/nRS7fQDsZmxw/ius6rnlIpz4UX6FlALEzz8JoSxFmdBt0SZnthis+km7sQo6q5/3e+UJcuQivoXA==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@smithy/eventstream-serde-config-resolver@4.3.10':
|
||||
resolution: {integrity: sha512-8kn6sinrduk0yaYHMJDsNuiFpXwQwibR7n/4CDUqn4UgaG+SeBHu5jHGFdU9BLFAM7Q4/gvr9RYxBHz9/jKrhA==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@smithy/eventstream-serde-config-resolver@4.3.11':
|
||||
resolution: {integrity: sha512-XeNIA8tcP/GDWnnKkO7qEm/bg0B/bP9lvIXZBXcGZwZ+VYM8h8k9wuDvUODtdQ2Wcp2RcBkPTCSMmaniVHrMlA==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@smithy/eventstream-serde-node@4.2.10':
|
||||
resolution: {integrity: sha512-uUrxPGgIffnYfvIOUmBM5i+USdEBRTdh7mLPttjphgtooxQ8CtdO1p6K5+Q4BBAZvKlvtJ9jWyrWpBJYzBKsyQ==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@smithy/eventstream-serde-node@4.2.11':
|
||||
resolution: {integrity: sha512-fzbCh18rscBDTQSCrsp1fGcclLNF//nJyhjldsEl/5wCYmgpHblv5JSppQAyQI24lClsFT0wV06N1Porn0IsEw==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@smithy/eventstream-serde-universal@4.2.10':
|
||||
resolution: {integrity: sha512-aArqzOEvcs2dK+xQVCgLbpJQGfZihw8SD4ymhkwNTtwKbnrzdhJsFDKuMQnam2kF69WzgJYOU5eJlCx+CA32bw==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@smithy/eventstream-serde-universal@4.2.11':
|
||||
resolution: {integrity: sha512-MJ7HcI+jEkqoWT5vp+uoVaAjBrmxBtKhZTeynDRG/seEjJfqyg3SiqMMqyPnAMzmIfLaeJ/uiuSDP/l9AnMy/Q==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@smithy/fetch-http-handler@5.3.11':
|
||||
resolution: {integrity: sha512-wbTRjOxdFuyEg0CpumjZO0hkUl+fetJFqxNROepuLIoijQh51aMBmzFLfoQdwRjxsuuS2jizzIUTjPWgd8pd7g==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
@@ -3221,12 +3291,12 @@ packages:
|
||||
'@swc/helpers@0.5.19':
|
||||
resolution: {integrity: sha512-QamiFeIK3txNjgUTNppE6MiG3p7TdninpZu0E0PbqVh1a9FNLT2FRhisaa4NcaX52XVhA5l7Pk58Ft7Sqi/2sA==}
|
||||
|
||||
'@thi.ng/bitstream@2.4.41':
|
||||
resolution: {integrity: sha512-treRzw3+7I1YCuilFtznwT3SGtceS9spUXhyBqeuKNTm4nIfMuvg4fNqx4GgpuS6cGPQNPMUJm0OyzKnSe2Emw==}
|
||||
'@thi.ng/bitstream@2.4.43':
|
||||
resolution: {integrity: sha512-tObOEr+osboa0kqQPk7Ny0E3vVfBRch13YJO5RpaDDSkMQmoXK/pw3yW/6kKJIObt27YQol6pGlOZBvB8MsghQ==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
'@thi.ng/errors@2.6.3':
|
||||
resolution: {integrity: sha512-owkOOKHf7MrAPN2jNpKWDdY/vjtPFiJf6oxZ3jkkhV6ICTu2iY1fXIR2wQ7kVEeybdtb0w24k2PtrU43OYCWdg==}
|
||||
'@thi.ng/errors@2.6.5':
|
||||
resolution: {integrity: sha512-XKfcJzxikMI1+MKSiABcLzI2WIsm4SxGEdLIIQjYqew3q3CoypGe+w5W/DMvMWF6eFWT6ONINbiJ6QMHFTfVzA==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
'@tinyhttp/content-disposition@2.2.4':
|
||||
@@ -3297,8 +3367,8 @@ packages:
|
||||
'@tybys/wasm-util@0.10.1':
|
||||
resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==}
|
||||
|
||||
'@types/aws-lambda@8.10.160':
|
||||
resolution: {integrity: sha512-uoO4QVQNWFPJMh26pXtmtrRfGshPUSpMZGUyUQY20FhfHEElEBOPKgVmFs1z+kbpyBsRs2JnoOPT7++Z4GA9pA==}
|
||||
'@types/aws-lambda@8.10.161':
|
||||
resolution: {integrity: sha512-rUYdp+MQwSFocxIOcSsYSF3YYYC/uUpMbCY/mbO21vGqfrEYvNSoPyKYDj6RhXXpPfS0KstW9RwG3qXh9sL7FQ==}
|
||||
|
||||
'@types/body-parser@1.19.6':
|
||||
resolution: {integrity: sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==}
|
||||
@@ -3378,8 +3448,8 @@ packages:
|
||||
'@types/node@10.17.60':
|
||||
resolution: {integrity: sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==}
|
||||
|
||||
'@types/node@20.19.35':
|
||||
resolution: {integrity: sha512-Uarfe6J91b9HAUXxjvSOdiO2UPOKLm07Q1oh0JHxoZ1y8HoqxDAu3gVrsrOHeiio0kSsoVBt4wFrKOm0dKxVPQ==}
|
||||
'@types/node@20.19.37':
|
||||
resolution: {integrity: sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw==}
|
||||
|
||||
'@types/node@24.11.0':
|
||||
resolution: {integrity: sha512-fPxQqz4VTgPI/IQ+lj9r0h+fDR66bzoeMGHp8ASee+32OSGIkeASsoZuJixsQoVef1QJbeubcPBxKk22QVoWdw==}
|
||||
@@ -3432,43 +3502,43 @@ packages:
|
||||
'@types/yauzl@2.10.3':
|
||||
resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==}
|
||||
|
||||
'@typescript/native-preview-darwin-arm64@7.0.0-dev.20260307.1':
|
||||
resolution: {integrity: sha512-VpnrMP4iDLSTT9Hg/KrHwuIHLZr5dxYPMFErfv3ZDA0tv48u2H1lBhHVVMMopCuskuX3C35EOJbxLkxCJd6zDw==}
|
||||
'@typescript/native-preview-darwin-arm64@7.0.0-dev.20260308.1':
|
||||
resolution: {integrity: sha512-mywkctYr45fUBUYD35poInc9HEjup0zyCO5z3ZU2QC9eCQShpwYSDceoSCwxVKB/b/f/CU6H3LqINFeIz5CvrQ==}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@typescript/native-preview-darwin-x64@7.0.0-dev.20260307.1':
|
||||
resolution: {integrity: sha512-+4akGPxwfrPy2AYmQO1bp6CXxUVlBPrL0lSv+wY/E8vNGqwF0UtJCwAcR54ae1+k9EmoirT7Xn6LE3Io6mXntg==}
|
||||
'@typescript/native-preview-darwin-x64@7.0.0-dev.20260308.1':
|
||||
resolution: {integrity: sha512-iF+Y4USbCiD5BxmXI6xYuy+S6d2BhxKDb3YHjchzqg3AgleDNTd2rqSzlWv4ku26V2iOSfpM9t1H/xluL9pgNw==}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@typescript/native-preview-linux-arm64@7.0.0-dev.20260307.1':
|
||||
resolution: {integrity: sha512-u4kXuHN2p+HeWsnTixoEOwALsCoS+n3/ukWdnV/mwyg6BKuuU69qCv3/miY6YPFtE7mUwzPdflEXsvkZJbJ/RA==}
|
||||
'@typescript/native-preview-linux-arm64@7.0.0-dev.20260308.1':
|
||||
resolution: {integrity: sha512-uEIIbW1JYPGEesVh/P5xA+xox7pQ6toeFPeke2X2H2bs5YkWHVaUQtVZuKNmGelw+2PCG6XRrXvMgMp056ebuQ==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@typescript/native-preview-linux-arm@7.0.0-dev.20260307.1':
|
||||
resolution: {integrity: sha512-E0Pve6BjTVvPiHq9cPVQu6fbW/Qo/CEs1VN2NMILd0xzFVpVd9FIvzV+Ft6pZilu1SBcihThW3sQ92l03Cw2+Q==}
|
||||
'@typescript/native-preview-linux-arm@7.0.0-dev.20260308.1':
|
||||
resolution: {integrity: sha512-vg8hwfwIhT8CmYJI5lG3PP8IoNzKKBGbq1cKjxQabSZTPuQKwVFVity2XKTKZKd+qRGL7xW4UWMJZLFgSx3b2Q==}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
||||
'@typescript/native-preview-linux-x64@7.0.0-dev.20260307.1':
|
||||
resolution: {integrity: sha512-MzuRjTYQIS7XrJcH0As18SbaQU+rFhf9LCpXs2QeHjhXQ33wjuFDNhQeurg2eKm6A0xE0GoW9K+sKsm8bhzzPg==}
|
||||
'@typescript/native-preview-linux-x64@7.0.0-dev.20260308.1':
|
||||
resolution: {integrity: sha512-Yd/ht0CGE4NYUAjuHa1u4VbiJbyUgvDh+b2o+Zcb2h5t8B761DIzDm24QqVXh+KhvGUoEodXWg3g3APxLHqj8Q==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@typescript/native-preview-win32-arm64@7.0.0-dev.20260307.1':
|
||||
resolution: {integrity: sha512-UNZl8Q6lx1njEPU8+FNjYvqii5PtDjk6cyxmVPwwJI2Snz5T5qY6oadkUds6CJsLkt7s4UB3P5XgLu1+vwoYGw==}
|
||||
'@typescript/native-preview-win32-arm64@7.0.0-dev.20260308.1':
|
||||
resolution: {integrity: sha512-Klk6BoiHegfPmkO0YYrXmbYVdPjOfN25lRkzenqDIwbyzPlABHvICCyo5YRvWD3HU4EeDfLisIFU9wEd/0duCw==}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@typescript/native-preview-win32-x64@7.0.0-dev.20260307.1':
|
||||
resolution: {integrity: sha512-aPJb4v0Df9GzWFWbO4YbLg0OjmjxZgXngkF1M746r4CgOdydWgosNPWypzzAwiliGKvCLwfAWYiV+T5Jf1vQ3g==}
|
||||
'@typescript/native-preview-win32-x64@7.0.0-dev.20260308.1':
|
||||
resolution: {integrity: sha512-4LrXmaMfzedwczANIkD/M9guPD4EWuQnCxOJsJkdYi3ExWQDjIFwfmxTtAmfPBWxVExLfn7UUkz/yCtcv2Wd+w==}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@typescript/native-preview@7.0.0-dev.20260307.1':
|
||||
resolution: {integrity: sha512-NcKdPiGjxxxdh7fLgRKTrn5hLntbt89NOodNaSrMChTfJwvLaDkgrRlnO7v5x+m7nQc87Qf1y7UoT1ZEZUBB4Q==}
|
||||
'@typescript/native-preview@7.0.0-dev.20260308.1':
|
||||
resolution: {integrity: sha512-8a3oe5IAfBkEfMouRheNhOXUScBSHIUknPvUdsbxx7s+Ja1lxFNA1X1TTl2T18vu72Q/mM86vxefw5eW8/ps3g==}
|
||||
hasBin: true
|
||||
|
||||
'@typespec/ts-http-runtime@0.3.3':
|
||||
@@ -3825,8 +3895,8 @@ packages:
|
||||
bowser@2.14.1:
|
||||
resolution: {integrity: sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==}
|
||||
|
||||
brace-expansion@5.0.3:
|
||||
resolution: {integrity: sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==}
|
||||
brace-expansion@5.0.4:
|
||||
resolution: {integrity: sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==}
|
||||
engines: {node: 18 || 20 || >=22}
|
||||
|
||||
braces@3.0.3:
|
||||
@@ -3981,8 +4051,8 @@ packages:
|
||||
resolution: {integrity: sha512-H4UfQhZyakIjC74I9d34fGYDwk3XpSr17QhEd0Q3I9Xq1CETHo4Hcuo87WyWHpAF1aSLjLRf5lD9ZGX2qStUvg==}
|
||||
engines: {node: '>=4.0.0'}
|
||||
|
||||
command-line-usage@7.0.3:
|
||||
resolution: {integrity: sha512-PqMLy5+YGwhMh1wS04mVG44oqDsgyLRSKJBdOo1bnYhMKBW65gZF1dRp2OZRhiTjgUHljy99qkO7bsctLaw35Q==}
|
||||
command-line-usage@7.0.4:
|
||||
resolution: {integrity: sha512-85UdvzTNx/+s5CkSgBm/0hzP80RFHAa7PsfeADE5ezZF3uHz3/Tqj9gIKGT9PTtpycc3Ua64T0oVulGfKxzfqg==}
|
||||
engines: {node: '>=12.20.0'}
|
||||
|
||||
commander@10.0.1:
|
||||
@@ -4442,6 +4512,10 @@ packages:
|
||||
resolution: {integrity: sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg==}
|
||||
engines: {node: '>=14.14'}
|
||||
|
||||
fs-extra@11.3.4:
|
||||
resolution: {integrity: sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==}
|
||||
engines: {node: '>=14.14'}
|
||||
|
||||
fs.realpath@1.0.0:
|
||||
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
|
||||
|
||||
@@ -4838,8 +4912,8 @@ packages:
|
||||
json-stringify-safe@5.0.1:
|
||||
resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==}
|
||||
|
||||
json-with-bigint@3.5.3:
|
||||
resolution: {integrity: sha512-QObKu6nxy7NsxqR0VK4rkXnsNr5L9ElJaGEg+ucJ6J7/suoKZ0n+p76cu9aCqowytxEbwYNzvrMerfMkXneF5A==}
|
||||
json-with-bigint@3.5.7:
|
||||
resolution: {integrity: sha512-7ei3MdAI5+fJPVnKlW77TKNKwQ5ppSzWvhPuSuINT/GYW9ZOC1eRKOuhV9yHG5aEsUPj9BBx5JIekkmoLHxZOw==}
|
||||
|
||||
json5@2.2.3:
|
||||
resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==}
|
||||
@@ -5260,8 +5334,8 @@ packages:
|
||||
resolution: {integrity: sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==}
|
||||
engines: {node: '>= 0.4.0'}
|
||||
|
||||
node-addon-api@8.5.0:
|
||||
resolution: {integrity: sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A==}
|
||||
node-addon-api@8.6.0:
|
||||
resolution: {integrity: sha512-gBVjCaqDlRUk0EwoPNKzIr9KkS9041G/q31IBShPs1Xz6UTA+EXdZADbzqAJQrpDRq71CIMnOP5VMut3SL0z5Q==}
|
||||
engines: {node: ^18 || ^20 || >= 21}
|
||||
|
||||
node-api-headers@1.8.0:
|
||||
@@ -5404,6 +5478,18 @@ packages:
|
||||
zod:
|
||||
optional: true
|
||||
|
||||
openai@6.26.0:
|
||||
resolution: {integrity: sha512-zd23dbWTjiJ6sSAX6s0HrCZi41JwTA1bQVs0wLQPZ2/5o2gxOJA5wh7yOAUgwYybfhDXyhwlpeQf7Mlgx8EOCA==}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
ws: ^8.18.0
|
||||
zod: ^3.25 || ^4.0
|
||||
peerDependenciesMeta:
|
||||
ws:
|
||||
optional: true
|
||||
zod:
|
||||
optional: true
|
||||
|
||||
openai@6.27.0:
|
||||
resolution: {integrity: sha512-osTKySlrdYrLYTt0zjhY8yp0JUBmWDCN+Q+QxsV4xMQnnoVFpylgKGgxwN8sSdTNw0G4y+WUXs4eCMWpyDNWZQ==}
|
||||
hasBin: true
|
||||
@@ -6784,6 +6870,58 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- aws-crt
|
||||
|
||||
'@aws-sdk/client-bedrock-runtime@3.1004.0':
|
||||
dependencies:
|
||||
'@aws-crypto/sha256-browser': 5.2.0
|
||||
'@aws-crypto/sha256-js': 5.2.0
|
||||
'@aws-sdk/core': 3.973.18
|
||||
'@aws-sdk/credential-provider-node': 3.972.18
|
||||
'@aws-sdk/eventstream-handler-node': 3.972.10
|
||||
'@aws-sdk/middleware-eventstream': 3.972.7
|
||||
'@aws-sdk/middleware-host-header': 3.972.7
|
||||
'@aws-sdk/middleware-logger': 3.972.7
|
||||
'@aws-sdk/middleware-recursion-detection': 3.972.7
|
||||
'@aws-sdk/middleware-user-agent': 3.972.19
|
||||
'@aws-sdk/middleware-websocket': 3.972.12
|
||||
'@aws-sdk/region-config-resolver': 3.972.7
|
||||
'@aws-sdk/token-providers': 3.1004.0
|
||||
'@aws-sdk/types': 3.973.5
|
||||
'@aws-sdk/util-endpoints': 3.996.4
|
||||
'@aws-sdk/util-user-agent-browser': 3.972.7
|
||||
'@aws-sdk/util-user-agent-node': 3.973.4
|
||||
'@smithy/config-resolver': 4.4.10
|
||||
'@smithy/core': 3.23.9
|
||||
'@smithy/eventstream-serde-browser': 4.2.11
|
||||
'@smithy/eventstream-serde-config-resolver': 4.3.11
|
||||
'@smithy/eventstream-serde-node': 4.2.11
|
||||
'@smithy/fetch-http-handler': 5.3.13
|
||||
'@smithy/hash-node': 4.2.11
|
||||
'@smithy/invalid-dependency': 4.2.11
|
||||
'@smithy/middleware-content-length': 4.2.11
|
||||
'@smithy/middleware-endpoint': 4.4.23
|
||||
'@smithy/middleware-retry': 4.4.40
|
||||
'@smithy/middleware-serde': 4.2.12
|
||||
'@smithy/middleware-stack': 4.2.11
|
||||
'@smithy/node-config-provider': 4.3.11
|
||||
'@smithy/node-http-handler': 4.4.14
|
||||
'@smithy/protocol-http': 5.3.11
|
||||
'@smithy/smithy-client': 4.12.3
|
||||
'@smithy/types': 4.13.0
|
||||
'@smithy/url-parser': 4.2.11
|
||||
'@smithy/util-base64': 4.3.2
|
||||
'@smithy/util-body-length-browser': 4.2.2
|
||||
'@smithy/util-body-length-node': 4.2.3
|
||||
'@smithy/util-defaults-mode-browser': 4.3.39
|
||||
'@smithy/util-defaults-mode-node': 4.2.42
|
||||
'@smithy/util-endpoints': 3.3.2
|
||||
'@smithy/util-middleware': 4.2.11
|
||||
'@smithy/util-retry': 4.2.11
|
||||
'@smithy/util-stream': 4.5.17
|
||||
'@smithy/util-utf8': 4.2.2
|
||||
tslib: 2.8.1
|
||||
transitivePeerDependencies:
|
||||
- aws-crt
|
||||
|
||||
'@aws-sdk/client-bedrock@3.1000.0':
|
||||
dependencies:
|
||||
'@aws-crypto/sha256-browser': 5.2.0
|
||||
@@ -7179,6 +7317,13 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- aws-crt
|
||||
|
||||
'@aws-sdk/eventstream-handler-node@3.972.10':
|
||||
dependencies:
|
||||
'@aws-sdk/types': 3.973.5
|
||||
'@smithy/eventstream-codec': 4.2.11
|
||||
'@smithy/types': 4.13.0
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-sdk/eventstream-handler-node@3.972.9':
|
||||
dependencies:
|
||||
'@aws-sdk/types': 3.973.4
|
||||
@@ -7203,6 +7348,13 @@ snapshots:
|
||||
'@smithy/types': 4.13.0
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-sdk/middleware-eventstream@3.972.7':
|
||||
dependencies:
|
||||
'@aws-sdk/types': 3.973.5
|
||||
'@smithy/protocol-http': 5.3.11
|
||||
'@smithy/types': 4.13.0
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-sdk/middleware-expect-continue@3.972.6':
|
||||
dependencies:
|
||||
'@aws-sdk/types': 3.973.4
|
||||
@@ -7334,6 +7486,21 @@ snapshots:
|
||||
'@smithy/util-utf8': 4.2.1
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-sdk/middleware-websocket@3.972.12':
|
||||
dependencies:
|
||||
'@aws-sdk/types': 3.973.5
|
||||
'@aws-sdk/util-format-url': 3.972.7
|
||||
'@smithy/eventstream-codec': 4.2.11
|
||||
'@smithy/eventstream-serde-browser': 4.2.11
|
||||
'@smithy/fetch-http-handler': 5.3.13
|
||||
'@smithy/protocol-http': 5.3.11
|
||||
'@smithy/signature-v4': 5.3.11
|
||||
'@smithy/types': 4.13.0
|
||||
'@smithy/util-base64': 4.3.2
|
||||
'@smithy/util-hex-encoding': 4.2.2
|
||||
'@smithy/util-utf8': 4.2.2
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-sdk/nested-clients@3.996.3':
|
||||
dependencies:
|
||||
'@aws-crypto/sha256-browser': 5.2.0
|
||||
@@ -7529,6 +7696,13 @@ snapshots:
|
||||
'@smithy/types': 4.13.0
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-sdk/util-format-url@3.972.7':
|
||||
dependencies:
|
||||
'@aws-sdk/types': 3.973.5
|
||||
'@smithy/querystring-builder': 4.2.11
|
||||
'@smithy/types': 4.13.0
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-sdk/util-locate-window@3.965.4':
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
@@ -7808,7 +7982,7 @@ snapshots:
|
||||
'@discordjs/opus@0.10.0':
|
||||
dependencies:
|
||||
'@discordjs/node-pre-gyp': 0.4.5
|
||||
node-addon-api: 8.5.0
|
||||
node-addon-api: 8.6.0
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
- supports-color
|
||||
@@ -7937,6 +8111,17 @@ snapshots:
|
||||
- supports-color
|
||||
- utf-8-validate
|
||||
|
||||
'@google/genai@1.44.0':
|
||||
dependencies:
|
||||
google-auth-library: 10.6.1
|
||||
p-retry: 4.6.2
|
||||
protobufjs: 7.5.4
|
||||
ws: 8.19.0
|
||||
transitivePeerDependencies:
|
||||
- bufferutil
|
||||
- supports-color
|
||||
- utf-8-validate
|
||||
|
||||
'@grammyjs/runner@2.0.3(grammy@1.41.0)':
|
||||
dependencies:
|
||||
abort-controller: 3.0.0
|
||||
@@ -8328,6 +8513,18 @@ snapshots:
|
||||
- ws
|
||||
- zod
|
||||
|
||||
'@mariozechner/pi-agent-core@0.57.1(ws@8.19.0)(zod@4.3.6)':
|
||||
dependencies:
|
||||
'@mariozechner/pi-ai': 0.57.1(ws@8.19.0)(zod@4.3.6)
|
||||
transitivePeerDependencies:
|
||||
- '@modelcontextprotocol/sdk'
|
||||
- aws-crt
|
||||
- bufferutil
|
||||
- supports-color
|
||||
- utf-8-validate
|
||||
- ws
|
||||
- zod
|
||||
|
||||
'@mariozechner/pi-ai@0.55.3(ws@8.19.0)(zod@4.3.6)':
|
||||
dependencies:
|
||||
'@anthropic-ai/sdk': 0.73.0(zod@4.3.6)
|
||||
@@ -8352,6 +8549,30 @@ snapshots:
|
||||
- ws
|
||||
- zod
|
||||
|
||||
'@mariozechner/pi-ai@0.57.1(ws@8.19.0)(zod@4.3.6)':
|
||||
dependencies:
|
||||
'@anthropic-ai/sdk': 0.73.0(zod@4.3.6)
|
||||
'@aws-sdk/client-bedrock-runtime': 3.1004.0
|
||||
'@google/genai': 1.44.0
|
||||
'@mistralai/mistralai': 1.14.1
|
||||
'@sinclair/typebox': 0.34.48
|
||||
ajv: 8.18.0
|
||||
ajv-formats: 3.0.1(ajv@8.18.0)
|
||||
chalk: 5.6.2
|
||||
openai: 6.26.0(ws@8.19.0)(zod@4.3.6)
|
||||
partial-json: 0.1.7
|
||||
proxy-agent: 6.5.0
|
||||
undici: 7.22.0
|
||||
zod-to-json-schema: 3.25.1(zod@4.3.6)
|
||||
transitivePeerDependencies:
|
||||
- '@modelcontextprotocol/sdk'
|
||||
- aws-crt
|
||||
- bufferutil
|
||||
- supports-color
|
||||
- utf-8-validate
|
||||
- ws
|
||||
- zod
|
||||
|
||||
'@mariozechner/pi-coding-agent@0.55.3(ws@8.19.0)(zod@4.3.6)':
|
||||
dependencies:
|
||||
'@mariozechner/jiti': 2.6.5
|
||||
@@ -8383,6 +8604,38 @@ snapshots:
|
||||
- ws
|
||||
- zod
|
||||
|
||||
'@mariozechner/pi-coding-agent@0.57.1(ws@8.19.0)(zod@4.3.6)':
|
||||
dependencies:
|
||||
'@mariozechner/jiti': 2.6.5
|
||||
'@mariozechner/pi-agent-core': 0.57.1(ws@8.19.0)(zod@4.3.6)
|
||||
'@mariozechner/pi-ai': 0.57.1(ws@8.19.0)(zod@4.3.6)
|
||||
'@mariozechner/pi-tui': 0.57.1
|
||||
'@silvia-odwyer/photon-node': 0.3.4
|
||||
chalk: 5.6.2
|
||||
cli-highlight: 2.1.11
|
||||
diff: 8.0.3
|
||||
extract-zip: 2.0.1
|
||||
file-type: 21.3.0
|
||||
glob: 13.0.6
|
||||
hosted-git-info: 9.0.2
|
||||
ignore: 7.0.5
|
||||
marked: 15.0.12
|
||||
minimatch: 10.2.4
|
||||
proper-lockfile: 4.1.2
|
||||
strip-ansi: 7.2.0
|
||||
undici: 7.22.0
|
||||
yaml: 2.8.2
|
||||
optionalDependencies:
|
||||
'@mariozechner/clipboard': 0.3.2
|
||||
transitivePeerDependencies:
|
||||
- '@modelcontextprotocol/sdk'
|
||||
- aws-crt
|
||||
- bufferutil
|
||||
- supports-color
|
||||
- utf-8-validate
|
||||
- ws
|
||||
- zod
|
||||
|
||||
'@mariozechner/pi-tui@0.55.3':
|
||||
dependencies:
|
||||
'@types/mime-types': 2.1.4
|
||||
@@ -8392,6 +8645,16 @@ snapshots:
|
||||
marked: 15.0.12
|
||||
mime-types: 3.0.2
|
||||
|
||||
'@mariozechner/pi-tui@0.57.1':
|
||||
dependencies:
|
||||
'@types/mime-types': 2.1.4
|
||||
chalk: 5.6.2
|
||||
get-east-asian-width: 1.5.0
|
||||
marked: 15.0.12
|
||||
mime-types: 3.0.2
|
||||
optionalDependencies:
|
||||
koffi: 2.15.1
|
||||
|
||||
'@matrix-org/matrix-sdk-crypto-nodejs@0.4.0':
|
||||
dependencies:
|
||||
https-proxy-agent: 7.0.6
|
||||
@@ -8426,6 +8689,15 @@ snapshots:
|
||||
zod: 3.25.76
|
||||
zod-to-json-schema: 3.25.1(zod@3.25.76)
|
||||
|
||||
'@mistralai/mistralai@1.14.1':
|
||||
dependencies:
|
||||
ws: 8.19.0
|
||||
zod: 4.3.6
|
||||
zod-to-json-schema: 3.25.1(zod@4.3.6)
|
||||
transitivePeerDependencies:
|
||||
- bufferutil
|
||||
- utf-8-validate
|
||||
|
||||
'@mozilla/readability@0.6.0': {}
|
||||
|
||||
'@napi-rs/canvas-android-arm64@0.1.95':
|
||||
@@ -8625,7 +8897,7 @@ snapshots:
|
||||
'@octokit/core': 7.0.6
|
||||
'@octokit/oauth-authorization-url': 8.0.0
|
||||
'@octokit/oauth-methods': 6.0.2
|
||||
'@types/aws-lambda': 8.10.160
|
||||
'@types/aws-lambda': 8.10.161
|
||||
universal-user-agent: 7.0.3
|
||||
|
||||
'@octokit/oauth-authorization-url@8.0.0': {}
|
||||
@@ -8678,7 +8950,7 @@ snapshots:
|
||||
'@octokit/request-error': 7.1.0
|
||||
'@octokit/types': 16.0.0
|
||||
fast-content-type-parse: 3.0.0
|
||||
json-with-bigint: 3.5.3
|
||||
json-with-bigint: 3.5.7
|
||||
universal-user-agent: 7.0.3
|
||||
|
||||
'@octokit/types@16.0.0':
|
||||
@@ -9481,29 +9753,59 @@ snapshots:
|
||||
'@smithy/util-hex-encoding': 4.2.1
|
||||
tslib: 2.8.1
|
||||
|
||||
'@smithy/eventstream-codec@4.2.11':
|
||||
dependencies:
|
||||
'@aws-crypto/crc32': 5.2.0
|
||||
'@smithy/types': 4.13.0
|
||||
'@smithy/util-hex-encoding': 4.2.2
|
||||
tslib: 2.8.1
|
||||
|
||||
'@smithy/eventstream-serde-browser@4.2.10':
|
||||
dependencies:
|
||||
'@smithy/eventstream-serde-universal': 4.2.10
|
||||
'@smithy/types': 4.13.0
|
||||
tslib: 2.8.1
|
||||
|
||||
'@smithy/eventstream-serde-browser@4.2.11':
|
||||
dependencies:
|
||||
'@smithy/eventstream-serde-universal': 4.2.11
|
||||
'@smithy/types': 4.13.0
|
||||
tslib: 2.8.1
|
||||
|
||||
'@smithy/eventstream-serde-config-resolver@4.3.10':
|
||||
dependencies:
|
||||
'@smithy/types': 4.13.0
|
||||
tslib: 2.8.1
|
||||
|
||||
'@smithy/eventstream-serde-config-resolver@4.3.11':
|
||||
dependencies:
|
||||
'@smithy/types': 4.13.0
|
||||
tslib: 2.8.1
|
||||
|
||||
'@smithy/eventstream-serde-node@4.2.10':
|
||||
dependencies:
|
||||
'@smithy/eventstream-serde-universal': 4.2.10
|
||||
'@smithy/types': 4.13.0
|
||||
tslib: 2.8.1
|
||||
|
||||
'@smithy/eventstream-serde-node@4.2.11':
|
||||
dependencies:
|
||||
'@smithy/eventstream-serde-universal': 4.2.11
|
||||
'@smithy/types': 4.13.0
|
||||
tslib: 2.8.1
|
||||
|
||||
'@smithy/eventstream-serde-universal@4.2.10':
|
||||
dependencies:
|
||||
'@smithy/eventstream-codec': 4.2.10
|
||||
'@smithy/types': 4.13.0
|
||||
tslib: 2.8.1
|
||||
|
||||
'@smithy/eventstream-serde-universal@4.2.11':
|
||||
dependencies:
|
||||
'@smithy/eventstream-codec': 4.2.11
|
||||
'@smithy/types': 4.13.0
|
||||
tslib: 2.8.1
|
||||
|
||||
'@smithy/fetch-http-handler@5.3.11':
|
||||
dependencies:
|
||||
'@smithy/protocol-http': 5.3.10
|
||||
@@ -10056,12 +10358,12 @@ snapshots:
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
|
||||
'@thi.ng/bitstream@2.4.41':
|
||||
'@thi.ng/bitstream@2.4.43':
|
||||
dependencies:
|
||||
'@thi.ng/errors': 2.6.3
|
||||
'@thi.ng/errors': 2.6.5
|
||||
optional: true
|
||||
|
||||
'@thi.ng/errors@2.6.3':
|
||||
'@thi.ng/errors@2.6.5':
|
||||
optional: true
|
||||
|
||||
'@tinyhttp/content-disposition@2.2.4': {}
|
||||
@@ -10172,7 +10474,7 @@ snapshots:
|
||||
tslib: 2.8.1
|
||||
optional: true
|
||||
|
||||
'@types/aws-lambda@8.10.160': {}
|
||||
'@types/aws-lambda@8.10.161': {}
|
||||
|
||||
'@types/body-parser@1.19.6':
|
||||
dependencies:
|
||||
@@ -10266,7 +10568,7 @@ snapshots:
|
||||
|
||||
'@types/node@10.17.60': {}
|
||||
|
||||
'@types/node@20.19.35':
|
||||
'@types/node@20.19.37':
|
||||
dependencies:
|
||||
undici-types: 6.21.0
|
||||
|
||||
@@ -10330,36 +10632,36 @@ snapshots:
|
||||
'@types/node': 25.3.5
|
||||
optional: true
|
||||
|
||||
'@typescript/native-preview-darwin-arm64@7.0.0-dev.20260307.1':
|
||||
'@typescript/native-preview-darwin-arm64@7.0.0-dev.20260308.1':
|
||||
optional: true
|
||||
|
||||
'@typescript/native-preview-darwin-x64@7.0.0-dev.20260307.1':
|
||||
'@typescript/native-preview-darwin-x64@7.0.0-dev.20260308.1':
|
||||
optional: true
|
||||
|
||||
'@typescript/native-preview-linux-arm64@7.0.0-dev.20260307.1':
|
||||
'@typescript/native-preview-linux-arm64@7.0.0-dev.20260308.1':
|
||||
optional: true
|
||||
|
||||
'@typescript/native-preview-linux-arm@7.0.0-dev.20260307.1':
|
||||
'@typescript/native-preview-linux-arm@7.0.0-dev.20260308.1':
|
||||
optional: true
|
||||
|
||||
'@typescript/native-preview-linux-x64@7.0.0-dev.20260307.1':
|
||||
'@typescript/native-preview-linux-x64@7.0.0-dev.20260308.1':
|
||||
optional: true
|
||||
|
||||
'@typescript/native-preview-win32-arm64@7.0.0-dev.20260307.1':
|
||||
'@typescript/native-preview-win32-arm64@7.0.0-dev.20260308.1':
|
||||
optional: true
|
||||
|
||||
'@typescript/native-preview-win32-x64@7.0.0-dev.20260307.1':
|
||||
'@typescript/native-preview-win32-x64@7.0.0-dev.20260308.1':
|
||||
optional: true
|
||||
|
||||
'@typescript/native-preview@7.0.0-dev.20260307.1':
|
||||
'@typescript/native-preview@7.0.0-dev.20260308.1':
|
||||
optionalDependencies:
|
||||
'@typescript/native-preview-darwin-arm64': 7.0.0-dev.20260307.1
|
||||
'@typescript/native-preview-darwin-x64': 7.0.0-dev.20260307.1
|
||||
'@typescript/native-preview-linux-arm': 7.0.0-dev.20260307.1
|
||||
'@typescript/native-preview-linux-arm64': 7.0.0-dev.20260307.1
|
||||
'@typescript/native-preview-linux-x64': 7.0.0-dev.20260307.1
|
||||
'@typescript/native-preview-win32-arm64': 7.0.0-dev.20260307.1
|
||||
'@typescript/native-preview-win32-x64': 7.0.0-dev.20260307.1
|
||||
'@typescript/native-preview-darwin-arm64': 7.0.0-dev.20260308.1
|
||||
'@typescript/native-preview-darwin-x64': 7.0.0-dev.20260308.1
|
||||
'@typescript/native-preview-linux-arm': 7.0.0-dev.20260308.1
|
||||
'@typescript/native-preview-linux-arm64': 7.0.0-dev.20260308.1
|
||||
'@typescript/native-preview-linux-x64': 7.0.0-dev.20260308.1
|
||||
'@typescript/native-preview-win32-arm64': 7.0.0-dev.20260308.1
|
||||
'@typescript/native-preview-win32-x64': 7.0.0-dev.20260308.1
|
||||
|
||||
'@typespec/ts-http-runtime@0.3.3':
|
||||
dependencies:
|
||||
@@ -10613,9 +10915,9 @@ snapshots:
|
||||
'@swc/helpers': 0.5.19
|
||||
'@types/command-line-args': 5.2.3
|
||||
'@types/command-line-usage': 5.0.4
|
||||
'@types/node': 20.19.35
|
||||
'@types/node': 20.19.37
|
||||
command-line-args: 5.2.1
|
||||
command-line-usage: 7.0.3
|
||||
command-line-usage: 7.0.4
|
||||
flatbuffers: 24.12.23
|
||||
json-bignum: 0.0.3
|
||||
tslib: 2.8.1
|
||||
@@ -10785,7 +11087,7 @@ snapshots:
|
||||
|
||||
bowser@2.14.1: {}
|
||||
|
||||
brace-expansion@5.0.3:
|
||||
brace-expansion@5.0.4:
|
||||
dependencies:
|
||||
balanced-match: 4.0.4
|
||||
|
||||
@@ -10908,7 +11210,7 @@ snapshots:
|
||||
cmake-js@8.0.0:
|
||||
dependencies:
|
||||
debug: 4.4.3
|
||||
fs-extra: 11.3.3
|
||||
fs-extra: 11.3.4
|
||||
node-api-headers: 1.8.0
|
||||
rc: 1.2.8
|
||||
semver: 7.7.4
|
||||
@@ -10946,7 +11248,7 @@ snapshots:
|
||||
lodash.camelcase: 4.3.0
|
||||
typical: 4.0.0
|
||||
|
||||
command-line-usage@7.0.3:
|
||||
command-line-usage@7.0.4:
|
||||
dependencies:
|
||||
array-back: 6.2.2
|
||||
chalk-template: 0.4.0
|
||||
@@ -11435,6 +11737,12 @@ snapshots:
|
||||
jsonfile: 6.2.0
|
||||
universalify: 2.0.1
|
||||
|
||||
fs-extra@11.3.4:
|
||||
dependencies:
|
||||
graceful-fs: 4.2.11
|
||||
jsonfile: 6.2.0
|
||||
universalify: 2.0.1
|
||||
|
||||
fs.realpath@1.0.0:
|
||||
optional: true
|
||||
|
||||
@@ -11762,7 +12070,7 @@ snapshots:
|
||||
commander: 10.0.1
|
||||
eventemitter3: 5.0.4
|
||||
filenamify: 6.0.0
|
||||
fs-extra: 11.3.3
|
||||
fs-extra: 11.3.4
|
||||
is-unicode-supported: 2.1.0
|
||||
lifecycle-utils: 2.1.0
|
||||
lodash.debounce: 4.0.8
|
||||
@@ -11910,7 +12218,7 @@ snapshots:
|
||||
|
||||
json-stringify-safe@5.0.1: {}
|
||||
|
||||
json-with-bigint@3.5.3: {}
|
||||
json-with-bigint@3.5.7: {}
|
||||
|
||||
json5@2.2.3: {}
|
||||
|
||||
@@ -12249,7 +12557,7 @@ snapshots:
|
||||
|
||||
minimatch@10.2.4:
|
||||
dependencies:
|
||||
brace-expansion: 5.0.3
|
||||
brace-expansion: 5.0.4
|
||||
|
||||
minimist@1.2.8: {}
|
||||
|
||||
@@ -12315,7 +12623,7 @@ snapshots:
|
||||
|
||||
netmask@2.0.2: {}
|
||||
|
||||
node-addon-api@8.5.0: {}
|
||||
node-addon-api@8.6.0: {}
|
||||
|
||||
node-api-headers@1.8.0: {}
|
||||
|
||||
@@ -12352,14 +12660,14 @@ snapshots:
|
||||
cross-spawn: 7.0.6
|
||||
env-var: 7.5.0
|
||||
filenamify: 6.0.0
|
||||
fs-extra: 11.3.3
|
||||
fs-extra: 11.3.4
|
||||
ignore: 7.0.5
|
||||
ipull: 3.9.5
|
||||
is-unicode-supported: 2.1.0
|
||||
lifecycle-utils: 3.1.1
|
||||
log-symbols: 7.0.1
|
||||
nanoid: 5.1.6
|
||||
node-addon-api: 8.5.0
|
||||
node-addon-api: 8.6.0
|
||||
octokit: 5.0.5
|
||||
ora: 9.3.0
|
||||
pretty-ms: 9.3.0
|
||||
@@ -12503,6 +12811,11 @@ snapshots:
|
||||
ws: 8.19.0
|
||||
zod: 4.3.6
|
||||
|
||||
openai@6.26.0(ws@8.19.0)(zod@4.3.6):
|
||||
optionalDependencies:
|
||||
ws: 8.19.0
|
||||
zod: 4.3.6
|
||||
|
||||
openai@6.27.0(ws@8.19.0)(zod@4.3.6):
|
||||
optionalDependencies:
|
||||
ws: 8.19.0
|
||||
@@ -12987,7 +13300,7 @@ snapshots:
|
||||
|
||||
qoa-format@1.0.1:
|
||||
dependencies:
|
||||
'@thi.ng/bitstream': 2.4.41
|
||||
'@thi.ng/bitstream': 2.4.43
|
||||
optional: true
|
||||
|
||||
qrcode-terminal@0.12.0: {}
|
||||
@@ -13117,7 +13430,7 @@ snapshots:
|
||||
dependencies:
|
||||
glob: 10.5.0
|
||||
|
||||
rolldown-plugin-dts@0.22.4(@typescript/native-preview@7.0.0-dev.20260307.1)(rolldown@1.0.0-rc.7)(typescript@5.9.3):
|
||||
rolldown-plugin-dts@0.22.4(@typescript/native-preview@7.0.0-dev.20260308.1)(rolldown@1.0.0-rc.7)(typescript@5.9.3):
|
||||
dependencies:
|
||||
'@babel/generator': 8.0.0-rc.2
|
||||
'@babel/helper-validator-identifier': 8.0.0-rc.2
|
||||
@@ -13130,7 +13443,7 @@ snapshots:
|
||||
obug: 2.1.1
|
||||
rolldown: 1.0.0-rc.7
|
||||
optionalDependencies:
|
||||
'@typescript/native-preview': 7.0.0-dev.20260307.1
|
||||
'@typescript/native-preview': 7.0.0-dev.20260308.1
|
||||
typescript: 5.9.3
|
||||
transitivePeerDependencies:
|
||||
- oxc-resolver
|
||||
@@ -13665,7 +13978,7 @@ snapshots:
|
||||
|
||||
ts-algebra@2.0.0: {}
|
||||
|
||||
tsdown@0.21.0(@typescript/native-preview@7.0.0-dev.20260307.1)(typescript@5.9.3):
|
||||
tsdown@0.21.0(@typescript/native-preview@7.0.0-dev.20260308.1)(typescript@5.9.3):
|
||||
dependencies:
|
||||
ansis: 4.2.0
|
||||
cac: 7.0.0
|
||||
@@ -13676,7 +13989,7 @@ snapshots:
|
||||
obug: 2.1.1
|
||||
picomatch: 4.0.3
|
||||
rolldown: 1.0.0-rc.7
|
||||
rolldown-plugin-dts: 0.22.4(@typescript/native-preview@7.0.0-dev.20260307.1)(rolldown@1.0.0-rc.7)(typescript@5.9.3)
|
||||
rolldown-plugin-dts: 0.22.4(@typescript/native-preview@7.0.0-dev.20260308.1)(rolldown@1.0.0-rc.7)(typescript@5.9.3)
|
||||
semver: 7.7.4
|
||||
tinyexec: 1.0.2
|
||||
tinyglobby: 0.2.15
|
||||
|
||||
@@ -104,11 +104,11 @@ const hostMemoryGiB = Math.floor(os.totalmem() / 1024 ** 3);
|
||||
const highMemLocalHost = !isCI && hostMemoryGiB >= 96;
|
||||
const lowMemLocalHost = !isCI && hostMemoryGiB < 64;
|
||||
const nodeMajor = Number.parseInt(process.versions.node.split(".")[0] ?? "", 10);
|
||||
// vmForks is a big win for transform/import heavy suites, but Node 24 had
|
||||
// regressions with Vitest's vm runtime in this repo, and low-memory local hosts
|
||||
// vmForks is a big win for transform/import heavy suites, but Node 24+
|
||||
// regressed with Vitest's vm runtime in this repo, and low-memory local hosts
|
||||
// are more likely to hit per-worker V8 heap ceilings. Keep it opt-out via
|
||||
// OPENCLAW_TEST_VM_FORKS=0, and let users force-enable with =1.
|
||||
const supportsVmForks = Number.isFinite(nodeMajor) ? nodeMajor !== 24 : true;
|
||||
const supportsVmForks = Number.isFinite(nodeMajor) ? nodeMajor < 24 : true;
|
||||
const useVmForks =
|
||||
process.env.OPENCLAW_TEST_VM_FORKS === "1" ||
|
||||
(process.env.OPENCLAW_TEST_VM_FORKS !== "0" && !isWindows && supportsVmForks && !lowMemLocalHost);
|
||||
|
||||
61
src/acp/translator.set-session-mode.test.ts
Normal file
61
src/acp/translator.set-session-mode.test.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import type { SetSessionModeRequest } from "@agentclientprotocol/sdk";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import type { GatewayClient } from "../gateway/client.js";
|
||||
import { createInMemorySessionStore } from "./session.js";
|
||||
import { AcpGatewayAgent } from "./translator.js";
|
||||
import { createAcpConnection, createAcpGateway } from "./translator.test-helpers.js";
|
||||
|
||||
function createSetSessionModeRequest(modeId: string): SetSessionModeRequest {
|
||||
return {
|
||||
sessionId: "session-1",
|
||||
modeId,
|
||||
} as unknown as SetSessionModeRequest;
|
||||
}
|
||||
|
||||
function createAgentWithSession(request: GatewayClient["request"]) {
|
||||
const sessionStore = createInMemorySessionStore();
|
||||
sessionStore.createSession({
|
||||
sessionId: "session-1",
|
||||
sessionKey: "agent:main:main",
|
||||
cwd: "/tmp",
|
||||
});
|
||||
return new AcpGatewayAgent(createAcpConnection(), createAcpGateway(request), {
|
||||
sessionStore,
|
||||
});
|
||||
}
|
||||
|
||||
describe("acp setSessionMode", () => {
|
||||
it("setSessionMode propagates gateway error", async () => {
|
||||
const request = vi.fn(async () => {
|
||||
throw new Error("gateway rejected mode change");
|
||||
}) as GatewayClient["request"];
|
||||
const agent = createAgentWithSession(request);
|
||||
|
||||
await expect(agent.setSessionMode(createSetSessionModeRequest("high"))).rejects.toThrow(
|
||||
"gateway rejected mode change",
|
||||
);
|
||||
expect(request).toHaveBeenCalledWith("sessions.patch", {
|
||||
key: "agent:main:main",
|
||||
thinkingLevel: "high",
|
||||
});
|
||||
});
|
||||
|
||||
it("setSessionMode succeeds when gateway accepts", async () => {
|
||||
const request = vi.fn(async () => ({ ok: true })) as GatewayClient["request"];
|
||||
const agent = createAgentWithSession(request);
|
||||
|
||||
await expect(agent.setSessionMode(createSetSessionModeRequest("low"))).resolves.toEqual({});
|
||||
expect(request).toHaveBeenCalledWith("sessions.patch", {
|
||||
key: "agent:main:main",
|
||||
thinkingLevel: "low",
|
||||
});
|
||||
});
|
||||
|
||||
it("setSessionMode returns early for empty modeId", async () => {
|
||||
const request = vi.fn(async () => ({ ok: true })) as GatewayClient["request"];
|
||||
const agent = createAgentWithSession(request);
|
||||
|
||||
await expect(agent.setSessionMode(createSetSessionModeRequest(""))).resolves.toEqual({});
|
||||
expect(request).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
111
src/acp/translator.stop-reason.test.ts
Normal file
111
src/acp/translator.stop-reason.test.ts
Normal file
@@ -0,0 +1,111 @@
|
||||
import type { PromptRequest } from "@agentclientprotocol/sdk";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import type { GatewayClient } from "../gateway/client.js";
|
||||
import type { EventFrame } from "../gateway/protocol/index.js";
|
||||
import { createInMemorySessionStore } from "./session.js";
|
||||
import { AcpGatewayAgent } from "./translator.js";
|
||||
import { createAcpConnection, createAcpGateway } from "./translator.test-helpers.js";
|
||||
|
||||
type PendingPromptHarness = {
|
||||
agent: AcpGatewayAgent;
|
||||
promptPromise: ReturnType<AcpGatewayAgent["prompt"]>;
|
||||
runId: string;
|
||||
};
|
||||
|
||||
async function createPendingPromptHarness(): Promise<PendingPromptHarness> {
|
||||
const sessionId = "session-1";
|
||||
const sessionKey = "agent:main:main";
|
||||
|
||||
let runId: string | undefined;
|
||||
const request = vi.fn(async (method: string, params?: Record<string, unknown>) => {
|
||||
if (method === "chat.send") {
|
||||
runId = params?.idempotencyKey as string | undefined;
|
||||
return new Promise<never>(() => {});
|
||||
}
|
||||
return {};
|
||||
}) as GatewayClient["request"];
|
||||
|
||||
const sessionStore = createInMemorySessionStore();
|
||||
sessionStore.createSession({
|
||||
sessionId,
|
||||
sessionKey,
|
||||
cwd: "/tmp",
|
||||
});
|
||||
|
||||
const agent = new AcpGatewayAgent(
|
||||
createAcpConnection(),
|
||||
createAcpGateway(request as unknown as GatewayClient["request"]),
|
||||
{ sessionStore },
|
||||
);
|
||||
const promptPromise = agent.prompt({
|
||||
sessionId,
|
||||
prompt: [{ type: "text", text: "hello" }],
|
||||
_meta: {},
|
||||
} as unknown as PromptRequest);
|
||||
|
||||
await vi.waitFor(() => {
|
||||
expect(runId).toBeDefined();
|
||||
});
|
||||
|
||||
return {
|
||||
agent,
|
||||
promptPromise,
|
||||
runId: runId!,
|
||||
};
|
||||
}
|
||||
|
||||
function createChatEvent(payload: Record<string, unknown>): EventFrame {
|
||||
return {
|
||||
type: "event",
|
||||
event: "chat",
|
||||
payload,
|
||||
} as EventFrame;
|
||||
}
|
||||
|
||||
describe("acp translator stop reason mapping", () => {
|
||||
it("error state resolves as end_turn, not refusal", async () => {
|
||||
const { agent, promptPromise, runId } = await createPendingPromptHarness();
|
||||
|
||||
await agent.handleGatewayEvent(
|
||||
createChatEvent({
|
||||
runId,
|
||||
sessionKey: "agent:main:main",
|
||||
seq: 1,
|
||||
state: "error",
|
||||
errorMessage: "gateway timeout",
|
||||
}),
|
||||
);
|
||||
|
||||
await expect(promptPromise).resolves.toEqual({ stopReason: "end_turn" });
|
||||
});
|
||||
|
||||
it("error state with no errorMessage resolves as end_turn", async () => {
|
||||
const { agent, promptPromise, runId } = await createPendingPromptHarness();
|
||||
|
||||
await agent.handleGatewayEvent(
|
||||
createChatEvent({
|
||||
runId,
|
||||
sessionKey: "agent:main:main",
|
||||
seq: 1,
|
||||
state: "error",
|
||||
}),
|
||||
);
|
||||
|
||||
await expect(promptPromise).resolves.toEqual({ stopReason: "end_turn" });
|
||||
});
|
||||
|
||||
it("aborted state resolves as cancelled", async () => {
|
||||
const { agent, promptPromise, runId } = await createPendingPromptHarness();
|
||||
|
||||
await agent.handleGatewayEvent(
|
||||
createChatEvent({
|
||||
runId,
|
||||
sessionKey: "agent:main:main",
|
||||
seq: 1,
|
||||
state: "aborted",
|
||||
}),
|
||||
);
|
||||
|
||||
await expect(promptPromise).resolves.toEqual({ stopReason: "cancelled" });
|
||||
});
|
||||
});
|
||||
@@ -256,6 +256,7 @@ export class AcpGatewayAgent implements Agent {
|
||||
this.log(`setSessionMode: ${session.sessionId} -> ${params.modeId}`);
|
||||
} catch (err) {
|
||||
this.log(`setSessionMode error: ${String(err)}`);
|
||||
throw err;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
@@ -473,7 +474,11 @@ export class AcpGatewayAgent implements Agent {
|
||||
return;
|
||||
}
|
||||
if (state === "error") {
|
||||
this.finishPrompt(pending.sessionId, pending, "refusal");
|
||||
// ACP has no explicit "server_error" stop reason. Use "end_turn" so clients
|
||||
// do not treat transient backend errors (timeouts, rate-limits) as deliberate
|
||||
// refusals. TODO: when ChatEventSchema gains a structured errorKind field
|
||||
// (e.g. "refusal" | "timeout" | "rate_limit"), use it to distinguish here.
|
||||
this.finishPrompt(pending.sessionId, pending, "end_turn");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,8 +28,8 @@ describe("createAnthropicPayloadLogger", () => {
|
||||
},
|
||||
],
|
||||
};
|
||||
const streamFn: StreamFn = ((_, __, options) => {
|
||||
options?.onPayload?.(payload);
|
||||
const streamFn: StreamFn = ((model, __, options) => {
|
||||
options?.onPayload?.(payload, model);
|
||||
return {} as never;
|
||||
}) as StreamFn;
|
||||
|
||||
|
||||
@@ -136,7 +136,7 @@ export function createAnthropicPayloadLogger(params: {
|
||||
if (!isAnthropicModel(model)) {
|
||||
return streamFn(model, context, options);
|
||||
}
|
||||
const nextOnPayload = (payload: unknown) => {
|
||||
const nextOnPayload = (payload: unknown, payloadModel: Parameters<StreamFn>[0]) => {
|
||||
const redactedPayload = redactImageDataForDiagnostics(payload);
|
||||
record({
|
||||
...base,
|
||||
@@ -145,7 +145,7 @@ export function createAnthropicPayloadLogger(params: {
|
||||
payload: redactedPayload,
|
||||
payloadDigest: digest(redactedPayload),
|
||||
});
|
||||
options?.onPayload?.(payload);
|
||||
return options?.onPayload?.(payload, payloadModel);
|
||||
};
|
||||
return streamFn(model, context, {
|
||||
...options,
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
import {
|
||||
getOAuthApiKey,
|
||||
getOAuthProviders,
|
||||
type OAuthCredentials,
|
||||
type OAuthProvider,
|
||||
} from "@mariozechner/pi-ai";
|
||||
import type { OAuthCredentials, OAuthProvider } from "@mariozechner/pi-ai/oauth";
|
||||
import { getOAuthApiKey, getOAuthProviders } from "@mariozechner/pi-ai/oauth";
|
||||
import { loadConfig, type OpenClawConfig } from "../../config/config.js";
|
||||
import { coerceSecretRef } from "../../config/types.secrets.js";
|
||||
import { withFileLock } from "../../infra/file-lock.js";
|
||||
|
||||
@@ -233,9 +233,6 @@ export function buildKimiCodingProvider(): ProviderConfig {
|
||||
cost: KIMI_CODING_DEFAULT_COST,
|
||||
contextWindow: KIMI_CODING_DEFAULT_CONTEXT_WINDOW,
|
||||
maxTokens: KIMI_CODING_DEFAULT_MAX_TOKENS,
|
||||
compat: {
|
||||
requiresOpenAiAnthropicToolPayload: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@@ -604,7 +604,7 @@ export function createOpenAIWebSocketStreamFn(
|
||||
...(prevResponseId ? { previous_response_id: prevResponseId } : {}),
|
||||
...extraParams,
|
||||
};
|
||||
options?.onPayload?.(payload);
|
||||
options?.onPayload?.(payload, model);
|
||||
|
||||
try {
|
||||
session.manager.send(payload as Parameters<OpenAIWebSocketManager["send"]>[0]);
|
||||
|
||||
@@ -101,7 +101,7 @@ describeGeminiLive("pi embedded extra params (gemini live)", () => {
|
||||
oneByOneRedPngBase64: string;
|
||||
includeImage?: boolean;
|
||||
prompt: string;
|
||||
onPayload?: (payload: Record<string, unknown>) => void;
|
||||
onPayload?: (payload: Record<string, unknown>, model: Model<"google-generative-ai">) => void;
|
||||
}): Promise<{ sawDone: boolean; stopReason?: string; errorMessage?: string }> {
|
||||
const userContent: Array<
|
||||
{ type: "text"; text: string } | { type: "image"; mimeType: string; data: string }
|
||||
@@ -129,8 +129,11 @@ describeGeminiLive("pi embedded extra params (gemini live)", () => {
|
||||
apiKey: params.apiKey,
|
||||
reasoning: "high",
|
||||
maxTokens: 64,
|
||||
onPayload: (payload) => {
|
||||
params.onPayload?.(payload as Record<string, unknown>);
|
||||
onPayload: (payload, streamModel) => {
|
||||
params.onPayload?.(
|
||||
payload as Record<string, unknown>,
|
||||
streamModel as Model<"google-generative-ai">,
|
||||
);
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
@@ -207,8 +207,8 @@ describe("applyExtraParamsToAgent", () => {
|
||||
payload?: Record<string, unknown>;
|
||||
}) {
|
||||
const payload = params.payload ?? { store: false };
|
||||
const baseStreamFn: StreamFn = (_model, _context, options) => {
|
||||
options?.onPayload?.(payload);
|
||||
const baseStreamFn: StreamFn = (model, _context, options) => {
|
||||
options?.onPayload?.(payload, model);
|
||||
return {} as ReturnType<StreamFn>;
|
||||
};
|
||||
const agent = { streamFn: baseStreamFn };
|
||||
@@ -232,8 +232,8 @@ describe("applyExtraParamsToAgent", () => {
|
||||
payload?: Record<string, unknown>;
|
||||
}) {
|
||||
const payload = params.payload ?? {};
|
||||
const baseStreamFn: StreamFn = (_model, _context, options) => {
|
||||
options?.onPayload?.(payload);
|
||||
const baseStreamFn: StreamFn = (model, _context, options) => {
|
||||
options?.onPayload?.(payload, model);
|
||||
return {} as ReturnType<StreamFn>;
|
||||
};
|
||||
const agent = { streamFn: baseStreamFn };
|
||||
@@ -276,7 +276,7 @@ describe("applyExtraParamsToAgent", () => {
|
||||
const payloads: Record<string, unknown>[] = [];
|
||||
const baseStreamFn: StreamFn = (_model, _context, options) => {
|
||||
const payload: Record<string, unknown> = { model: "deepseek/deepseek-r1" };
|
||||
options?.onPayload?.(payload);
|
||||
options?.onPayload?.(payload, model);
|
||||
payloads.push(payload);
|
||||
return {} as ReturnType<StreamFn>;
|
||||
};
|
||||
@@ -308,7 +308,7 @@ describe("applyExtraParamsToAgent", () => {
|
||||
const payloads: Record<string, unknown>[] = [];
|
||||
const baseStreamFn: StreamFn = (_model, _context, options) => {
|
||||
const payload: Record<string, unknown> = {};
|
||||
options?.onPayload?.(payload);
|
||||
options?.onPayload?.(payload, model);
|
||||
payloads.push(payload);
|
||||
return {} as ReturnType<StreamFn>;
|
||||
};
|
||||
@@ -332,7 +332,7 @@ describe("applyExtraParamsToAgent", () => {
|
||||
const payloads: Record<string, unknown>[] = [];
|
||||
const baseStreamFn: StreamFn = (_model, _context, options) => {
|
||||
const payload: Record<string, unknown> = { reasoning_effort: "high" };
|
||||
options?.onPayload?.(payload);
|
||||
options?.onPayload?.(payload, model);
|
||||
payloads.push(payload);
|
||||
return {} as ReturnType<StreamFn>;
|
||||
};
|
||||
@@ -357,7 +357,7 @@ describe("applyExtraParamsToAgent", () => {
|
||||
const payloads: Record<string, unknown>[] = [];
|
||||
const baseStreamFn: StreamFn = (_model, _context, options) => {
|
||||
const payload: Record<string, unknown> = { reasoning: { max_tokens: 256 } };
|
||||
options?.onPayload?.(payload);
|
||||
options?.onPayload?.(payload, model);
|
||||
payloads.push(payload);
|
||||
return {} as ReturnType<StreamFn>;
|
||||
};
|
||||
@@ -381,7 +381,7 @@ describe("applyExtraParamsToAgent", () => {
|
||||
const payloads: Record<string, unknown>[] = [];
|
||||
const baseStreamFn: StreamFn = (_model, _context, options) => {
|
||||
const payload: Record<string, unknown> = { reasoning_effort: "medium" };
|
||||
options?.onPayload?.(payload);
|
||||
options?.onPayload?.(payload, model);
|
||||
payloads.push(payload);
|
||||
return {} as ReturnType<StreamFn>;
|
||||
};
|
||||
@@ -588,7 +588,7 @@ describe("applyExtraParamsToAgent", () => {
|
||||
const payloads: Record<string, unknown>[] = [];
|
||||
const baseStreamFn: StreamFn = (_model, _context, options) => {
|
||||
const payload: Record<string, unknown> = { thinking: "off" };
|
||||
options?.onPayload?.(payload);
|
||||
options?.onPayload?.(payload, model);
|
||||
payloads.push(payload);
|
||||
return {} as ReturnType<StreamFn>;
|
||||
};
|
||||
@@ -619,7 +619,7 @@ describe("applyExtraParamsToAgent", () => {
|
||||
const payloads: Record<string, unknown>[] = [];
|
||||
const baseStreamFn: StreamFn = (_model, _context, options) => {
|
||||
const payload: Record<string, unknown> = { thinking: "off" };
|
||||
options?.onPayload?.(payload);
|
||||
options?.onPayload?.(payload, model);
|
||||
payloads.push(payload);
|
||||
return {} as ReturnType<StreamFn>;
|
||||
};
|
||||
@@ -650,7 +650,7 @@ describe("applyExtraParamsToAgent", () => {
|
||||
const payloads: Record<string, unknown>[] = [];
|
||||
const baseStreamFn: StreamFn = (_model, _context, options) => {
|
||||
const payload: Record<string, unknown> = {};
|
||||
options?.onPayload?.(payload);
|
||||
options?.onPayload?.(payload, model);
|
||||
payloads.push(payload);
|
||||
return {} as ReturnType<StreamFn>;
|
||||
};
|
||||
@@ -674,7 +674,7 @@ describe("applyExtraParamsToAgent", () => {
|
||||
const payloads: Record<string, unknown>[] = [];
|
||||
const baseStreamFn: StreamFn = (_model, _context, options) => {
|
||||
const payload: Record<string, unknown> = { tool_choice: "required" };
|
||||
options?.onPayload?.(payload);
|
||||
options?.onPayload?.(payload, model);
|
||||
payloads.push(payload);
|
||||
return {} as ReturnType<StreamFn>;
|
||||
};
|
||||
@@ -699,7 +699,7 @@ describe("applyExtraParamsToAgent", () => {
|
||||
const payloads: Record<string, unknown>[] = [];
|
||||
const baseStreamFn: StreamFn = (_model, _context, options) => {
|
||||
const payload: Record<string, unknown> = {};
|
||||
options?.onPayload?.(payload);
|
||||
options?.onPayload?.(payload, model);
|
||||
payloads.push(payload);
|
||||
return {} as ReturnType<StreamFn>;
|
||||
};
|
||||
@@ -732,7 +732,7 @@ describe("applyExtraParamsToAgent", () => {
|
||||
expect(payloads[0]?.thinking).toEqual({ type: "disabled" });
|
||||
});
|
||||
|
||||
it("normalizes kimi-coding anthropic tools to OpenAI function format", () => {
|
||||
it("does not rewrite tool schema for kimi-coding (native Anthropic format)", () => {
|
||||
const payloads: Record<string, unknown>[] = [];
|
||||
const baseStreamFn: StreamFn = (_model, _context, options) => {
|
||||
const payload: Record<string, unknown> = {
|
||||
@@ -746,18 +746,10 @@ describe("applyExtraParamsToAgent", () => {
|
||||
required: ["path"],
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "function",
|
||||
function: {
|
||||
name: "exec",
|
||||
description: "Run command",
|
||||
parameters: { type: "object", properties: {} },
|
||||
},
|
||||
},
|
||||
],
|
||||
tool_choice: { type: "tool", name: "read" },
|
||||
};
|
||||
options?.onPayload?.(payload);
|
||||
options?.onPayload?.(payload, model);
|
||||
payloads.push(payload);
|
||||
return {} as ReturnType<StreamFn>;
|
||||
};
|
||||
@@ -777,68 +769,16 @@ describe("applyExtraParamsToAgent", () => {
|
||||
expect(payloads).toHaveLength(1);
|
||||
expect(payloads[0]?.tools).toEqual([
|
||||
{
|
||||
type: "function",
|
||||
function: {
|
||||
name: "read",
|
||||
description: "Read file",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: { path: { type: "string" } },
|
||||
required: ["path"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "function",
|
||||
function: {
|
||||
name: "exec",
|
||||
description: "Run command",
|
||||
parameters: { type: "object", properties: {} },
|
||||
name: "read",
|
||||
description: "Read file",
|
||||
input_schema: {
|
||||
type: "object",
|
||||
properties: { path: { type: "string" } },
|
||||
required: ["path"],
|
||||
},
|
||||
},
|
||||
]);
|
||||
expect(payloads[0]?.tool_choice).toEqual({
|
||||
type: "function",
|
||||
function: { name: "read" },
|
||||
});
|
||||
});
|
||||
|
||||
it.each([
|
||||
{ input: { type: "auto" }, expected: "auto" },
|
||||
{ input: { type: "none" }, expected: "none" },
|
||||
{ input: { type: "required" }, expected: "required" },
|
||||
])("normalizes anthropic tool_choice %j for kimi-coding endpoints", ({ input, expected }) => {
|
||||
const payloads: Record<string, unknown>[] = [];
|
||||
const baseStreamFn: StreamFn = (_model, _context, options) => {
|
||||
const payload: Record<string, unknown> = {
|
||||
tools: [
|
||||
{
|
||||
name: "read",
|
||||
description: "Read file",
|
||||
input_schema: { type: "object", properties: {} },
|
||||
},
|
||||
],
|
||||
tool_choice: input,
|
||||
};
|
||||
options?.onPayload?.(payload);
|
||||
payloads.push(payload);
|
||||
return {} as ReturnType<StreamFn>;
|
||||
};
|
||||
const agent = { streamFn: baseStreamFn };
|
||||
|
||||
applyExtraParamsToAgent(agent, undefined, "kimi-coding", "k2p5", undefined, "low");
|
||||
|
||||
const model = {
|
||||
api: "anthropic-messages",
|
||||
provider: "kimi-coding",
|
||||
id: "k2p5",
|
||||
baseUrl: "https://api.kimi.com/coding/",
|
||||
} as Model<"anthropic-messages">;
|
||||
const context: Context = { messages: [] };
|
||||
void agent.streamFn?.(model, context, {});
|
||||
|
||||
expect(payloads).toHaveLength(1);
|
||||
expect(payloads[0]?.tool_choice).toBe(expected);
|
||||
expect(payloads[0]?.tool_choice).toEqual({ type: "tool", name: "read" });
|
||||
});
|
||||
|
||||
it("does not rewrite anthropic tool schema for non-kimi endpoints", () => {
|
||||
@@ -853,7 +793,7 @@ describe("applyExtraParamsToAgent", () => {
|
||||
},
|
||||
],
|
||||
};
|
||||
options?.onPayload?.(payload);
|
||||
options?.onPayload?.(payload, model);
|
||||
payloads.push(payload);
|
||||
return {} as ReturnType<StreamFn>;
|
||||
};
|
||||
@@ -892,7 +832,7 @@ describe("applyExtraParamsToAgent", () => {
|
||||
},
|
||||
],
|
||||
};
|
||||
options?.onPayload?.(payload);
|
||||
options?.onPayload?.(payload, model);
|
||||
payloads.push(payload);
|
||||
return {} as ReturnType<StreamFn>;
|
||||
};
|
||||
@@ -956,7 +896,7 @@ describe("applyExtraParamsToAgent", () => {
|
||||
},
|
||||
},
|
||||
};
|
||||
options?.onPayload?.(payload);
|
||||
options?.onPayload?.(payload, model);
|
||||
payloads.push(payload);
|
||||
return {} as ReturnType<StreamFn>;
|
||||
};
|
||||
@@ -1003,7 +943,7 @@ describe("applyExtraParamsToAgent", () => {
|
||||
},
|
||||
},
|
||||
};
|
||||
options?.onPayload?.(payload);
|
||||
options?.onPayload?.(payload, model);
|
||||
payloads.push(payload);
|
||||
return {} as ReturnType<StreamFn>;
|
||||
};
|
||||
|
||||
@@ -277,7 +277,7 @@ export function createAnthropicToolPayloadCompatibilityWrapper(
|
||||
const originalOnPayload = options?.onPayload;
|
||||
return underlying(model, context, {
|
||||
...options,
|
||||
onPayload: (payload) => {
|
||||
onPayload: (payload, payloadModel) => {
|
||||
if (
|
||||
payload &&
|
||||
typeof payload === "object" &&
|
||||
@@ -298,7 +298,7 @@ export function createAnthropicToolPayloadCompatibilityWrapper(
|
||||
);
|
||||
}
|
||||
}
|
||||
originalOnPayload?.(payload);
|
||||
return originalOnPayload?.(payload, payloadModel);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -19,7 +19,7 @@ function applyAndCapture(params: {
|
||||
|
||||
const baseStreamFn: StreamFn = (_model, _context, options) => {
|
||||
captured.headers = options?.headers;
|
||||
options?.onPayload?.({});
|
||||
options?.onPayload?.({}, model);
|
||||
return createAssistantMessageEventStream();
|
||||
};
|
||||
const agent = { streamFn: baseStreamFn };
|
||||
@@ -97,7 +97,7 @@ describe("extra-params: Kilocode kilo/auto reasoning", () => {
|
||||
|
||||
const baseStreamFn: StreamFn = (_model, _context, options) => {
|
||||
const payload: Record<string, unknown> = { reasoning_effort: "high" };
|
||||
options?.onPayload?.(payload);
|
||||
options?.onPayload?.(payload, model);
|
||||
capturedPayload = payload;
|
||||
return createAssistantMessageEventStream();
|
||||
};
|
||||
@@ -125,7 +125,7 @@ describe("extra-params: Kilocode kilo/auto reasoning", () => {
|
||||
|
||||
const baseStreamFn: StreamFn = (_model, _context, options) => {
|
||||
const payload: Record<string, unknown> = {};
|
||||
options?.onPayload?.(payload);
|
||||
options?.onPayload?.(payload, model);
|
||||
capturedPayload = payload;
|
||||
return createAssistantMessageEventStream();
|
||||
};
|
||||
@@ -158,7 +158,7 @@ describe("extra-params: Kilocode kilo/auto reasoning", () => {
|
||||
|
||||
const baseStreamFn: StreamFn = (_model, _context, options) => {
|
||||
const payload: Record<string, unknown> = { reasoning_effort: "high" };
|
||||
options?.onPayload?.(payload);
|
||||
options?.onPayload?.(payload, model);
|
||||
capturedPayload = payload;
|
||||
return createAssistantMessageEventStream();
|
||||
};
|
||||
|
||||
@@ -13,7 +13,7 @@ type StreamPayload = {
|
||||
|
||||
function runOpenRouterPayload(payload: StreamPayload, modelId: string) {
|
||||
const baseStreamFn: StreamFn = (_model, _context, options) => {
|
||||
options?.onPayload?.(payload);
|
||||
options?.onPayload?.(payload, model);
|
||||
return createAssistantMessageEventStream();
|
||||
};
|
||||
const agent = { streamFn: baseStreamFn };
|
||||
|
||||
@@ -222,7 +222,7 @@ function createGoogleThinkingPayloadWrapper(
|
||||
const onPayload = options?.onPayload;
|
||||
return underlying(model, context, {
|
||||
...options,
|
||||
onPayload: (payload) => {
|
||||
onPayload: (payload, payloadModel) => {
|
||||
if (model.api === "google-generative-ai") {
|
||||
sanitizeGoogleThinkingPayload({
|
||||
payload,
|
||||
@@ -230,7 +230,7 @@ function createGoogleThinkingPayloadWrapper(
|
||||
thinkingLevel,
|
||||
});
|
||||
}
|
||||
onPayload?.(payload);
|
||||
return onPayload?.(payload, payloadModel);
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -258,12 +258,12 @@ function createZaiToolStreamWrapper(
|
||||
const originalOnPayload = options?.onPayload;
|
||||
return underlying(model, context, {
|
||||
...options,
|
||||
onPayload: (payload) => {
|
||||
onPayload: (payload, payloadModel) => {
|
||||
if (payload && typeof payload === "object") {
|
||||
// Inject tool_stream: true for Z.AI API
|
||||
(payload as Record<string, unknown>).tool_stream = true;
|
||||
}
|
||||
originalOnPayload?.(payload);
|
||||
return originalOnPayload?.(payload, payloadModel);
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -306,11 +306,11 @@ function createParallelToolCallsWrapper(
|
||||
const originalOnPayload = options?.onPayload;
|
||||
return underlying(model, context, {
|
||||
...options,
|
||||
onPayload: (payload) => {
|
||||
onPayload: (payload, payloadModel) => {
|
||||
if (payload && typeof payload === "object") {
|
||||
(payload as Record<string, unknown>).parallel_tool_calls = enabled;
|
||||
}
|
||||
originalOnPayload?.(payload);
|
||||
return originalOnPayload?.(payload, payloadModel);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -21,8 +21,8 @@ type ToolStreamCase = {
|
||||
|
||||
function runToolStreamCase(params: ToolStreamCase) {
|
||||
const payload: Record<string, unknown> = { model: params.model.id, messages: [] };
|
||||
const baseStreamFn: StreamFn = (_model, _context, options) => {
|
||||
options?.onPayload?.(payload);
|
||||
const baseStreamFn: StreamFn = (model, _context, options) => {
|
||||
options?.onPayload?.(payload, model);
|
||||
return {} as ReturnType<StreamFn>;
|
||||
};
|
||||
const agent = { streamFn: baseStreamFn };
|
||||
|
||||
@@ -53,14 +53,14 @@ export function createSiliconFlowThinkingWrapper(baseStreamFn: StreamFn | undefi
|
||||
const originalOnPayload = options?.onPayload;
|
||||
return underlying(model, context, {
|
||||
...options,
|
||||
onPayload: (payload) => {
|
||||
onPayload: (payload, payloadModel) => {
|
||||
if (payload && typeof payload === "object") {
|
||||
const payloadObj = payload as Record<string, unknown>;
|
||||
if (payloadObj.thinking === "off") {
|
||||
payloadObj.thinking = null;
|
||||
}
|
||||
}
|
||||
originalOnPayload?.(payload);
|
||||
return originalOnPayload?.(payload, payloadModel);
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -89,7 +89,7 @@ export function createMoonshotThinkingWrapper(
|
||||
const originalOnPayload = options?.onPayload;
|
||||
return underlying(model, context, {
|
||||
...options,
|
||||
onPayload: (payload) => {
|
||||
onPayload: (payload, payloadModel) => {
|
||||
if (payload && typeof payload === "object") {
|
||||
const payloadObj = payload as Record<string, unknown>;
|
||||
let effectiveThinkingType = normalizeMoonshotThinkingType(payloadObj.thinking);
|
||||
@@ -106,7 +106,7 @@ export function createMoonshotThinkingWrapper(
|
||||
payloadObj.tool_choice = "auto";
|
||||
}
|
||||
}
|
||||
originalOnPayload?.(payload);
|
||||
return originalOnPayload?.(payload, payloadModel);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -187,7 +187,7 @@ export function createOpenAIResponsesContextManagementWrapper(
|
||||
const originalOnPayload = options?.onPayload;
|
||||
return underlying(model, context, {
|
||||
...options,
|
||||
onPayload: (payload) => {
|
||||
onPayload: (payload, payloadModel) => {
|
||||
if (payload && typeof payload === "object") {
|
||||
applyOpenAIResponsesPayloadOverrides({
|
||||
payloadObj: payload as Record<string, unknown>,
|
||||
@@ -197,7 +197,7 @@ export function createOpenAIResponsesContextManagementWrapper(
|
||||
compactThreshold,
|
||||
});
|
||||
}
|
||||
originalOnPayload?.(payload);
|
||||
return originalOnPayload?.(payload, payloadModel);
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -219,14 +219,14 @@ export function createOpenAIServiceTierWrapper(
|
||||
const originalOnPayload = options?.onPayload;
|
||||
return underlying(model, context, {
|
||||
...options,
|
||||
onPayload: (payload) => {
|
||||
onPayload: (payload, payloadModel) => {
|
||||
if (payload && typeof payload === "object") {
|
||||
const payloadObj = payload as Record<string, unknown>;
|
||||
if (payloadObj.service_tier === undefined) {
|
||||
payloadObj.service_tier = serviceTier;
|
||||
}
|
||||
}
|
||||
originalOnPayload?.(payload);
|
||||
return originalOnPayload?.(payload, payloadModel);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -73,7 +73,7 @@ export function createOpenRouterSystemCacheWrapper(baseStreamFn: StreamFn | unde
|
||||
const originalOnPayload = options?.onPayload;
|
||||
return underlying(model, context, {
|
||||
...options,
|
||||
onPayload: (payload) => {
|
||||
onPayload: (payload, payloadModel) => {
|
||||
const messages = (payload as Record<string, unknown>)?.messages;
|
||||
if (Array.isArray(messages)) {
|
||||
for (const msg of messages as Array<{ role?: string; content?: unknown }>) {
|
||||
@@ -92,7 +92,7 @@ export function createOpenRouterSystemCacheWrapper(baseStreamFn: StreamFn | unde
|
||||
}
|
||||
}
|
||||
}
|
||||
originalOnPayload?.(payload);
|
||||
return originalOnPayload?.(payload, payloadModel);
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -111,9 +111,9 @@ export function createOpenRouterWrapper(
|
||||
...OPENROUTER_APP_HEADERS,
|
||||
...options?.headers,
|
||||
},
|
||||
onPayload: (payload) => {
|
||||
onPayload: (payload, payloadModel) => {
|
||||
normalizeProxyReasoningPayload(payload, thinkingLevel);
|
||||
onPayload?.(payload);
|
||||
return onPayload?.(payload, payloadModel);
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -136,9 +136,9 @@ export function createKilocodeWrapper(
|
||||
...options?.headers,
|
||||
...resolveKilocodeAppHeaders(),
|
||||
},
|
||||
onPayload: (payload) => {
|
||||
onPayload: (payload, payloadModel) => {
|
||||
normalizeProxyReasoningPayload(payload, thinkingLevel);
|
||||
onPayload?.(payload);
|
||||
return onPayload?.(payload, payloadModel);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -520,7 +520,7 @@ describe("wrapOllamaCompatNumCtx", () => {
|
||||
let payloadSeen: Record<string, unknown> | undefined;
|
||||
const baseFn = vi.fn((_model, _context, options) => {
|
||||
const payload: Record<string, unknown> = { options: { temperature: 0.1 } };
|
||||
options?.onPayload?.(payload);
|
||||
options?.onPayload?.(payload, _model);
|
||||
payloadSeen = payload;
|
||||
return {} as never;
|
||||
});
|
||||
|
||||
@@ -124,6 +124,7 @@ import { installToolResultContextGuard } from "../tool-result-context-guard.js";
|
||||
import { splitSdkTools } from "../tool-split.js";
|
||||
import { describeUnknownError, mapThinkingLevel } from "../utils.js";
|
||||
import { flushPendingToolResultsAfterIdle } from "../wait-for-idle-before-flush.js";
|
||||
import { waitForCompactionRetryWithAggregateTimeout } from "./compaction-retry-aggregate-timeout.js";
|
||||
import {
|
||||
selectCompactionTimeoutSnapshot,
|
||||
shouldFlagCompactionTimeout,
|
||||
@@ -227,17 +228,16 @@ export function wrapOllamaCompatNumCtx(baseFn: StreamFn | undefined, numCtx: num
|
||||
return (model, context, options) =>
|
||||
streamFn(model, context, {
|
||||
...options,
|
||||
onPayload: (payload: unknown) => {
|
||||
onPayload: (payload: unknown, payloadModel) => {
|
||||
if (!payload || typeof payload !== "object") {
|
||||
options?.onPayload?.(payload);
|
||||
return;
|
||||
return options?.onPayload?.(payload, payloadModel);
|
||||
}
|
||||
const payloadRecord = payload as Record<string, unknown>;
|
||||
if (!payloadRecord.options || typeof payloadRecord.options !== "object") {
|
||||
payloadRecord.options = {};
|
||||
}
|
||||
(payloadRecord.options as Record<string, unknown>).num_ctx = numCtx;
|
||||
options?.onPayload?.(payload);
|
||||
return options?.onPayload?.(payload, payloadModel);
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -1538,6 +1538,7 @@ export async function runEmbeddedAttempt(
|
||||
toolMetas,
|
||||
unsubscribe,
|
||||
waitForCompactionRetry,
|
||||
isCompactionInFlight,
|
||||
getMessagingToolSentTexts,
|
||||
getMessagingToolSentMediaUrls,
|
||||
getMessagingToolSentTargets,
|
||||
@@ -1799,6 +1800,7 @@ export async function runEmbeddedAttempt(
|
||||
// Only trust snapshot if compaction wasn't running before or after capture
|
||||
const preCompactionSnapshot = wasCompactingBefore || wasCompactingAfter ? null : snapshot;
|
||||
const preCompactionSessionId = activeSession.sessionId;
|
||||
const COMPACTION_RETRY_AGGREGATE_TIMEOUT_MS = 60_000;
|
||||
|
||||
try {
|
||||
// Flush buffered block replies before waiting for compaction so the
|
||||
@@ -1809,7 +1811,21 @@ export async function runEmbeddedAttempt(
|
||||
await params.onBlockReplyFlush();
|
||||
}
|
||||
|
||||
await abortable(waitForCompactionRetry());
|
||||
const compactionRetryWait = await waitForCompactionRetryWithAggregateTimeout({
|
||||
waitForCompactionRetry,
|
||||
abortable,
|
||||
aggregateTimeoutMs: COMPACTION_RETRY_AGGREGATE_TIMEOUT_MS,
|
||||
isCompactionStillInFlight: isCompactionInFlight,
|
||||
});
|
||||
if (compactionRetryWait.timedOut) {
|
||||
timedOutDuringCompaction = true;
|
||||
if (!isProbeSession) {
|
||||
log.warn(
|
||||
`compaction retry aggregate timeout (${COMPACTION_RETRY_AGGREGATE_TIMEOUT_MS}ms): ` +
|
||||
`proceeding with pre-compaction state runId=${params.runId} sessionId=${params.sessionId}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
if (isRunnerAbortError(err)) {
|
||||
if (!promptError) {
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { waitForCompactionRetryWithAggregateTimeout } from "./compaction-retry-aggregate-timeout.js";
|
||||
|
||||
describe("waitForCompactionRetryWithAggregateTimeout", () => {
|
||||
it("times out and fires callback when compaction retry never resolves", async () => {
|
||||
vi.useFakeTimers();
|
||||
try {
|
||||
const onTimeout = vi.fn();
|
||||
const waitForCompactionRetry = vi.fn(async () => await new Promise<void>(() => {}));
|
||||
|
||||
const resultPromise = waitForCompactionRetryWithAggregateTimeout({
|
||||
waitForCompactionRetry,
|
||||
abortable: async (promise) => await promise,
|
||||
aggregateTimeoutMs: 60_000,
|
||||
onTimeout,
|
||||
});
|
||||
|
||||
await vi.advanceTimersByTimeAsync(60_000);
|
||||
const result = await resultPromise;
|
||||
|
||||
expect(result.timedOut).toBe(true);
|
||||
expect(onTimeout).toHaveBeenCalledTimes(1);
|
||||
expect(vi.getTimerCount()).toBe(0);
|
||||
} finally {
|
||||
await vi.runOnlyPendingTimersAsync();
|
||||
vi.useRealTimers();
|
||||
}
|
||||
});
|
||||
|
||||
it("keeps waiting while compaction remains in flight", async () => {
|
||||
vi.useFakeTimers();
|
||||
try {
|
||||
const onTimeout = vi.fn();
|
||||
let compactionInFlight = true;
|
||||
const waitForCompactionRetry = vi.fn(
|
||||
async () =>
|
||||
await new Promise<void>((resolve) => {
|
||||
setTimeout(() => {
|
||||
compactionInFlight = false;
|
||||
resolve();
|
||||
}, 170_000);
|
||||
}),
|
||||
);
|
||||
|
||||
const resultPromise = waitForCompactionRetryWithAggregateTimeout({
|
||||
waitForCompactionRetry,
|
||||
abortable: async (promise) => await promise,
|
||||
aggregateTimeoutMs: 60_000,
|
||||
onTimeout,
|
||||
isCompactionStillInFlight: () => compactionInFlight,
|
||||
});
|
||||
|
||||
await vi.advanceTimersByTimeAsync(170_000);
|
||||
const result = await resultPromise;
|
||||
|
||||
expect(result.timedOut).toBe(false);
|
||||
expect(onTimeout).not.toHaveBeenCalled();
|
||||
expect(vi.getTimerCount()).toBe(0);
|
||||
} finally {
|
||||
await vi.runOnlyPendingTimersAsync();
|
||||
vi.useRealTimers();
|
||||
}
|
||||
});
|
||||
|
||||
it("times out after an idle timeout window", async () => {
|
||||
vi.useFakeTimers();
|
||||
try {
|
||||
const onTimeout = vi.fn();
|
||||
let compactionInFlight = true;
|
||||
const waitForCompactionRetry = vi.fn(async () => await new Promise<void>(() => {}));
|
||||
setTimeout(() => {
|
||||
compactionInFlight = false;
|
||||
}, 90_000);
|
||||
|
||||
const resultPromise = waitForCompactionRetryWithAggregateTimeout({
|
||||
waitForCompactionRetry,
|
||||
abortable: async (promise) => await promise,
|
||||
aggregateTimeoutMs: 60_000,
|
||||
onTimeout,
|
||||
isCompactionStillInFlight: () => compactionInFlight,
|
||||
});
|
||||
|
||||
await vi.advanceTimersByTimeAsync(120_000);
|
||||
const result = await resultPromise;
|
||||
|
||||
expect(result.timedOut).toBe(true);
|
||||
expect(onTimeout).toHaveBeenCalledTimes(1);
|
||||
expect(vi.getTimerCount()).toBe(0);
|
||||
} finally {
|
||||
await vi.runOnlyPendingTimersAsync();
|
||||
vi.useRealTimers();
|
||||
}
|
||||
});
|
||||
|
||||
it("does not time out when compaction retry resolves", async () => {
|
||||
vi.useFakeTimers();
|
||||
try {
|
||||
const onTimeout = vi.fn();
|
||||
const waitForCompactionRetry = vi.fn(async () => {});
|
||||
|
||||
const result = await waitForCompactionRetryWithAggregateTimeout({
|
||||
waitForCompactionRetry,
|
||||
abortable: async (promise) => await promise,
|
||||
aggregateTimeoutMs: 60_000,
|
||||
onTimeout,
|
||||
});
|
||||
|
||||
expect(result.timedOut).toBe(false);
|
||||
expect(onTimeout).not.toHaveBeenCalled();
|
||||
expect(vi.getTimerCount()).toBe(0);
|
||||
} finally {
|
||||
await vi.runOnlyPendingTimersAsync();
|
||||
vi.useRealTimers();
|
||||
}
|
||||
});
|
||||
|
||||
it("propagates abort errors from abortable and clears timer", async () => {
|
||||
vi.useFakeTimers();
|
||||
try {
|
||||
const abortError = new Error("aborted");
|
||||
abortError.name = "AbortError";
|
||||
const onTimeout = vi.fn();
|
||||
const waitForCompactionRetry = vi.fn(async () => await new Promise<void>(() => {}));
|
||||
|
||||
await expect(
|
||||
waitForCompactionRetryWithAggregateTimeout({
|
||||
waitForCompactionRetry,
|
||||
abortable: async () => {
|
||||
throw abortError;
|
||||
},
|
||||
aggregateTimeoutMs: 60_000,
|
||||
onTimeout,
|
||||
}),
|
||||
).rejects.toThrow("aborted");
|
||||
|
||||
expect(onTimeout).not.toHaveBeenCalled();
|
||||
expect(vi.getTimerCount()).toBe(0);
|
||||
} finally {
|
||||
await vi.runOnlyPendingTimersAsync();
|
||||
vi.useRealTimers();
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* Wait for compaction retry completion with an aggregate timeout to avoid
|
||||
* holding a session lane indefinitely when retry resolution is lost.
|
||||
*/
|
||||
export async function waitForCompactionRetryWithAggregateTimeout(params: {
|
||||
waitForCompactionRetry: () => Promise<void>;
|
||||
abortable: <T>(promise: Promise<T>) => Promise<T>;
|
||||
aggregateTimeoutMs: number;
|
||||
onTimeout?: () => void;
|
||||
isCompactionStillInFlight?: () => boolean;
|
||||
}): Promise<{ timedOut: boolean }> {
|
||||
const timeoutMsRaw = params.aggregateTimeoutMs;
|
||||
const timeoutMs = Number.isFinite(timeoutMsRaw) ? Math.max(1, Math.floor(timeoutMsRaw)) : 1;
|
||||
|
||||
let timedOut = false;
|
||||
const waitPromise = params.waitForCompactionRetry().then(() => "done" as const);
|
||||
|
||||
while (true) {
|
||||
let timer: ReturnType<typeof setTimeout> | undefined;
|
||||
try {
|
||||
const result = await params.abortable(
|
||||
Promise.race([
|
||||
waitPromise,
|
||||
new Promise<"timeout">((resolve) => {
|
||||
timer = setTimeout(() => resolve("timeout"), timeoutMs);
|
||||
}),
|
||||
]),
|
||||
);
|
||||
|
||||
if (result === "done") {
|
||||
break;
|
||||
}
|
||||
|
||||
// Keep extending the timeout window while compaction is actively running.
|
||||
// We only trigger the fallback timeout once compaction appears idle.
|
||||
if (params.isCompactionStillInFlight?.()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
timedOut = true;
|
||||
params.onTimeout?.();
|
||||
break;
|
||||
} finally {
|
||||
if (timer !== undefined) {
|
||||
clearTimeout(timer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { timedOut };
|
||||
}
|
||||
108
src/agents/pi-embedded-runner/runs.test.ts
Normal file
108
src/agents/pi-embedded-runner/runs.test.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
__testing,
|
||||
abortEmbeddedPiRun,
|
||||
clearActiveEmbeddedRun,
|
||||
setActiveEmbeddedRun,
|
||||
waitForActiveEmbeddedRuns,
|
||||
} from "./runs.js";
|
||||
|
||||
describe("pi-embedded runner run registry", () => {
|
||||
afterEach(() => {
|
||||
__testing.resetActiveEmbeddedRuns();
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("aborts only compacting runs in compacting mode", () => {
|
||||
const abortCompacting = vi.fn();
|
||||
const abortNormal = vi.fn();
|
||||
|
||||
setActiveEmbeddedRun("session-compacting", {
|
||||
queueMessage: async () => {},
|
||||
isStreaming: () => true,
|
||||
isCompacting: () => true,
|
||||
abort: abortCompacting,
|
||||
});
|
||||
|
||||
setActiveEmbeddedRun("session-normal", {
|
||||
queueMessage: async () => {},
|
||||
isStreaming: () => true,
|
||||
isCompacting: () => false,
|
||||
abort: abortNormal,
|
||||
});
|
||||
|
||||
const aborted = abortEmbeddedPiRun(undefined, { mode: "compacting" });
|
||||
expect(aborted).toBe(true);
|
||||
expect(abortCompacting).toHaveBeenCalledTimes(1);
|
||||
expect(abortNormal).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("aborts every active run in all mode", () => {
|
||||
const abortA = vi.fn();
|
||||
const abortB = vi.fn();
|
||||
|
||||
setActiveEmbeddedRun("session-a", {
|
||||
queueMessage: async () => {},
|
||||
isStreaming: () => true,
|
||||
isCompacting: () => true,
|
||||
abort: abortA,
|
||||
});
|
||||
|
||||
setActiveEmbeddedRun("session-b", {
|
||||
queueMessage: async () => {},
|
||||
isStreaming: () => true,
|
||||
isCompacting: () => false,
|
||||
abort: abortB,
|
||||
});
|
||||
|
||||
const aborted = abortEmbeddedPiRun(undefined, { mode: "all" });
|
||||
expect(aborted).toBe(true);
|
||||
expect(abortA).toHaveBeenCalledTimes(1);
|
||||
expect(abortB).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("waits for active runs to drain", async () => {
|
||||
vi.useFakeTimers();
|
||||
try {
|
||||
const handle = {
|
||||
queueMessage: async () => {},
|
||||
isStreaming: () => true,
|
||||
isCompacting: () => false,
|
||||
abort: vi.fn(),
|
||||
};
|
||||
setActiveEmbeddedRun("session-a", handle);
|
||||
setTimeout(() => {
|
||||
clearActiveEmbeddedRun("session-a", handle);
|
||||
}, 500);
|
||||
|
||||
const waitPromise = waitForActiveEmbeddedRuns(1_000, { pollMs: 100 });
|
||||
await vi.advanceTimersByTimeAsync(500);
|
||||
const result = await waitPromise;
|
||||
|
||||
expect(result.drained).toBe(true);
|
||||
} finally {
|
||||
await vi.runOnlyPendingTimersAsync();
|
||||
vi.useRealTimers();
|
||||
}
|
||||
});
|
||||
|
||||
it("returns drained=false when timeout elapses", async () => {
|
||||
vi.useFakeTimers();
|
||||
try {
|
||||
setActiveEmbeddedRun("session-a", {
|
||||
queueMessage: async () => {},
|
||||
isStreaming: () => true,
|
||||
isCompacting: () => false,
|
||||
abort: vi.fn(),
|
||||
});
|
||||
|
||||
const waitPromise = waitForActiveEmbeddedRuns(1_000, { pollMs: 100 });
|
||||
await vi.advanceTimersByTimeAsync(1_000);
|
||||
const result = await waitPromise;
|
||||
expect(result.drained).toBe(false);
|
||||
} finally {
|
||||
await vi.runOnlyPendingTimersAsync();
|
||||
vi.useRealTimers();
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -37,15 +37,70 @@ export function queueEmbeddedPiMessage(sessionId: string, text: string): boolean
|
||||
return true;
|
||||
}
|
||||
|
||||
export function abortEmbeddedPiRun(sessionId: string): boolean {
|
||||
const handle = ACTIVE_EMBEDDED_RUNS.get(sessionId);
|
||||
if (!handle) {
|
||||
diag.debug(`abort failed: sessionId=${sessionId} reason=no_active_run`);
|
||||
return false;
|
||||
/**
|
||||
* Abort embedded PI runs.
|
||||
*
|
||||
* - With a sessionId, aborts that single run.
|
||||
* - With no sessionId, supports targeted abort modes (for example, compacting runs only).
|
||||
*/
|
||||
export function abortEmbeddedPiRun(sessionId: string): boolean;
|
||||
export function abortEmbeddedPiRun(
|
||||
sessionId: undefined,
|
||||
opts: { mode: "all" | "compacting" },
|
||||
): boolean;
|
||||
export function abortEmbeddedPiRun(
|
||||
sessionId?: string,
|
||||
opts?: { mode?: "all" | "compacting" },
|
||||
): boolean {
|
||||
if (typeof sessionId === "string" && sessionId.length > 0) {
|
||||
const handle = ACTIVE_EMBEDDED_RUNS.get(sessionId);
|
||||
if (!handle) {
|
||||
diag.debug(`abort failed: sessionId=${sessionId} reason=no_active_run`);
|
||||
return false;
|
||||
}
|
||||
diag.debug(`aborting run: sessionId=${sessionId}`);
|
||||
try {
|
||||
handle.abort();
|
||||
} catch (err) {
|
||||
diag.warn(`abort failed: sessionId=${sessionId} err=${String(err)}`);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
diag.debug(`aborting run: sessionId=${sessionId}`);
|
||||
handle.abort();
|
||||
return true;
|
||||
|
||||
const mode = opts?.mode;
|
||||
if (mode === "compacting") {
|
||||
let aborted = false;
|
||||
for (const [id, handle] of ACTIVE_EMBEDDED_RUNS) {
|
||||
if (!handle.isCompacting()) {
|
||||
continue;
|
||||
}
|
||||
diag.debug(`aborting compacting run: sessionId=${id}`);
|
||||
try {
|
||||
handle.abort();
|
||||
aborted = true;
|
||||
} catch (err) {
|
||||
diag.warn(`abort failed: sessionId=${id} err=${String(err)}`);
|
||||
}
|
||||
}
|
||||
return aborted;
|
||||
}
|
||||
|
||||
if (mode === "all") {
|
||||
let aborted = false;
|
||||
for (const [id, handle] of ACTIVE_EMBEDDED_RUNS) {
|
||||
diag.debug(`aborting run: sessionId=${id}`);
|
||||
try {
|
||||
handle.abort();
|
||||
aborted = true;
|
||||
} catch (err) {
|
||||
diag.warn(`abort failed: sessionId=${id} err=${String(err)}`);
|
||||
}
|
||||
}
|
||||
return aborted;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export function isEmbeddedPiRunActive(sessionId: string): boolean {
|
||||
@@ -68,6 +123,36 @@ export function getActiveEmbeddedRunCount(): number {
|
||||
return ACTIVE_EMBEDDED_RUNS.size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for active embedded runs to drain.
|
||||
*
|
||||
* Used during restarts so in-flight compaction runs can release session write
|
||||
* locks before the next lifecycle starts.
|
||||
*/
|
||||
export async function waitForActiveEmbeddedRuns(
|
||||
timeoutMs = 15_000,
|
||||
opts?: { pollMs?: number },
|
||||
): Promise<{ drained: boolean }> {
|
||||
const pollMsRaw = opts?.pollMs ?? 250;
|
||||
const pollMs = Math.max(10, Math.floor(pollMsRaw));
|
||||
const maxWaitMs = Math.max(pollMs, Math.floor(timeoutMs));
|
||||
|
||||
const startedAt = Date.now();
|
||||
while (true) {
|
||||
if (ACTIVE_EMBEDDED_RUNS.size === 0) {
|
||||
return { drained: true };
|
||||
}
|
||||
const elapsedMs = Date.now() - startedAt;
|
||||
if (elapsedMs >= maxWaitMs) {
|
||||
diag.warn(
|
||||
`wait for active embedded runs timed out: activeRuns=${ACTIVE_EMBEDDED_RUNS.size} timeoutMs=${maxWaitMs}`,
|
||||
);
|
||||
return { drained: false };
|
||||
}
|
||||
await new Promise<void>((resolve) => setTimeout(resolve, pollMs));
|
||||
}
|
||||
}
|
||||
|
||||
export function waitForEmbeddedPiRunEnd(sessionId: string, timeoutMs = 15_000): Promise<boolean> {
|
||||
if (!sessionId || !ACTIVE_EMBEDDED_RUNS.has(sessionId)) {
|
||||
return Promise.resolve(true);
|
||||
@@ -150,4 +235,17 @@ export function clearActiveEmbeddedRun(
|
||||
}
|
||||
}
|
||||
|
||||
export const __testing = {
|
||||
resetActiveEmbeddedRuns() {
|
||||
for (const waiters of EMBEDDED_RUN_WAITERS.values()) {
|
||||
for (const waiter of waiters) {
|
||||
clearTimeout(waiter.timer);
|
||||
waiter.resolve(true);
|
||||
}
|
||||
}
|
||||
EMBEDDED_RUN_WAITERS.clear();
|
||||
ACTIVE_EMBEDDED_RUNS.clear();
|
||||
},
|
||||
};
|
||||
|
||||
export type { EmbeddedPiQueueHandle };
|
||||
|
||||
@@ -31,8 +31,8 @@ describe("resolveProviderCapabilities", () => {
|
||||
resolveProviderCapabilities("kimi-code"),
|
||||
);
|
||||
expect(resolveProviderCapabilities("kimi-code")).toEqual({
|
||||
anthropicToolSchemaMode: "openai-functions",
|
||||
anthropicToolChoiceMode: "openai-string-modes",
|
||||
anthropicToolSchemaMode: "native",
|
||||
anthropicToolChoiceMode: "native",
|
||||
providerFamily: "default",
|
||||
preserveAnthropicThinkingSignatures: false,
|
||||
openAiCompatTurnValidation: true,
|
||||
@@ -66,9 +66,9 @@ describe("resolveProviderCapabilities", () => {
|
||||
expect(resolveTranscriptToolCallIdMode("mistral", "mistral-large-latest")).toBe("strict9");
|
||||
});
|
||||
|
||||
it("treats kimi aliases as anthropic tool payload compatibility providers", () => {
|
||||
expect(requiresOpenAiCompatibleAnthropicToolPayload("kimi-coding")).toBe(true);
|
||||
expect(requiresOpenAiCompatibleAnthropicToolPayload("kimi-code")).toBe(true);
|
||||
it("treats kimi aliases as native anthropic tool payload providers", () => {
|
||||
expect(requiresOpenAiCompatibleAnthropicToolPayload("kimi-coding")).toBe(false);
|
||||
expect(requiresOpenAiCompatibleAnthropicToolPayload("kimi-code")).toBe(false);
|
||||
expect(requiresOpenAiCompatibleAnthropicToolPayload("anthropic")).toBe(false);
|
||||
});
|
||||
|
||||
|
||||
@@ -33,9 +33,9 @@ const PROVIDER_CAPABILITIES: Record<string, Partial<ProviderCapabilities>> = {
|
||||
"amazon-bedrock": {
|
||||
providerFamily: "anthropic",
|
||||
},
|
||||
// kimi-coding natively supports Anthropic tool framing (input_schema);
|
||||
// converting to OpenAI format causes XML text fallback instead of tool_use blocks.
|
||||
"kimi-coding": {
|
||||
anthropicToolSchemaMode: "openai-functions",
|
||||
anthropicToolChoiceMode: "openai-string-modes",
|
||||
preserveAnthropicThinkingSignatures: false,
|
||||
},
|
||||
mistral: {
|
||||
|
||||
@@ -130,22 +130,33 @@ export async function installDownloadSpec(params: {
|
||||
filename = "download";
|
||||
}
|
||||
|
||||
let canonicalSafeRoot = "";
|
||||
let targetDir = "";
|
||||
try {
|
||||
targetDir = resolveDownloadTargetDir(entry, spec);
|
||||
await ensureDir(targetDir);
|
||||
await ensureDir(safeRoot);
|
||||
await assertCanonicalPathWithinBase({
|
||||
baseDir: safeRoot,
|
||||
candidatePath: targetDir,
|
||||
candidatePath: safeRoot,
|
||||
boundaryLabel: "skill tools directory",
|
||||
});
|
||||
canonicalSafeRoot = await fs.promises.realpath(safeRoot);
|
||||
|
||||
const requestedTargetDir = resolveDownloadTargetDir(entry, spec);
|
||||
await ensureDir(requestedTargetDir);
|
||||
await assertCanonicalPathWithinBase({
|
||||
baseDir: safeRoot,
|
||||
candidatePath: requestedTargetDir,
|
||||
boundaryLabel: "skill tools directory",
|
||||
});
|
||||
const targetRelativePath = path.relative(safeRoot, requestedTargetDir);
|
||||
targetDir = path.join(canonicalSafeRoot, targetRelativePath);
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
return { ok: false, message, stdout: "", stderr: message, code: null };
|
||||
}
|
||||
|
||||
const archivePath = path.join(targetDir, filename);
|
||||
const archiveRelativePath = path.relative(safeRoot, archivePath);
|
||||
const archiveRelativePath = path.relative(canonicalSafeRoot, archivePath);
|
||||
if (
|
||||
!archiveRelativePath ||
|
||||
archiveRelativePath === ".." ||
|
||||
@@ -164,7 +175,7 @@ export async function installDownloadSpec(params: {
|
||||
try {
|
||||
const result = await downloadFile({
|
||||
url,
|
||||
rootDir: safeRoot,
|
||||
rootDir: canonicalSafeRoot,
|
||||
relativePath: archiveRelativePath,
|
||||
timeoutMs,
|
||||
});
|
||||
@@ -198,7 +209,7 @@ export async function installDownloadSpec(params: {
|
||||
|
||||
try {
|
||||
await assertCanonicalPathWithinBase({
|
||||
baseDir: safeRoot,
|
||||
baseDir: canonicalSafeRoot,
|
||||
candidatePath: targetDir,
|
||||
boundaryLabel: "skill tools directory",
|
||||
});
|
||||
|
||||
@@ -251,6 +251,47 @@ describe("installDownloadSpec extraction safety", () => {
|
||||
),
|
||||
).toBe("hi");
|
||||
});
|
||||
|
||||
it.runIf(process.platform !== "win32")(
|
||||
"fails closed when the lexical tools root is rebound before the final copy",
|
||||
async () => {
|
||||
const entry = buildEntry("base-rebind");
|
||||
const safeRoot = resolveSkillToolsRootDir(entry);
|
||||
const outsideRoot = path.join(workspaceDir, "outside-root");
|
||||
await fs.mkdir(outsideRoot, { recursive: true });
|
||||
|
||||
fetchWithSsrFGuardMock.mockResolvedValue({
|
||||
response: new Response(
|
||||
new ReadableStream({
|
||||
async start(controller) {
|
||||
controller.enqueue(new Uint8Array(Buffer.from("payload")));
|
||||
const reboundRoot = `${safeRoot}-rebound`;
|
||||
await fs.rename(safeRoot, reboundRoot);
|
||||
await fs.symlink(outsideRoot, safeRoot);
|
||||
controller.close();
|
||||
},
|
||||
}),
|
||||
{ status: 200 },
|
||||
),
|
||||
release: async () => undefined,
|
||||
});
|
||||
|
||||
const result = await installDownloadSpec({
|
||||
entry,
|
||||
spec: {
|
||||
kind: "download",
|
||||
id: "dl",
|
||||
url: "https://example.invalid/payload.bin",
|
||||
extract: false,
|
||||
targetDir: "runtime",
|
||||
},
|
||||
timeoutMs: 30_000,
|
||||
});
|
||||
|
||||
expect(result.ok).toBe(false);
|
||||
expect(await fileExists(path.join(outsideRoot, "runtime", "payload.bin"))).toBe(false);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe("installDownloadSpec extraction safety (tar.bz2)", () => {
|
||||
|
||||
@@ -17,11 +17,15 @@ vi.mock("../infra/agent-events.js", () => ({
|
||||
onAgentEvent: vi.fn((_handler: unknown) => noop),
|
||||
}));
|
||||
|
||||
vi.mock("../config/config.js", () => ({
|
||||
loadConfig: vi.fn(() => ({
|
||||
agents: { defaults: { subagents: { archiveAfterMinutes: 60 } } },
|
||||
})),
|
||||
}));
|
||||
vi.mock("../config/config.js", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("../config/config.js")>();
|
||||
return {
|
||||
...actual,
|
||||
loadConfig: vi.fn(() => ({
|
||||
agents: { defaults: { subagents: { archiveAfterMinutes: 60 } } },
|
||||
})),
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("./subagent-announce.js", () => ({
|
||||
runSubagentAnnounceFlow: vi.fn(async () => true),
|
||||
|
||||
@@ -49,9 +49,13 @@ vi.mock("../infra/agent-events.js", () => ({
|
||||
onAgentEvent: onAgentEventMock,
|
||||
}));
|
||||
|
||||
vi.mock("../config/config.js", () => ({
|
||||
loadConfig: loadConfigMock,
|
||||
}));
|
||||
vi.mock("../config/config.js", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("../config/config.js")>();
|
||||
return {
|
||||
...actual,
|
||||
loadConfig: loadConfigMock,
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("./subagent-announce.js", () => ({
|
||||
runSubagentAnnounceFlow: announceSpy,
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
import { afterEach, beforeAll, describe, expect, it, vi } from "vitest";
|
||||
import "./subagent-registry.mocks.shared.js";
|
||||
|
||||
vi.mock("../config/config.js", () => ({
|
||||
loadConfig: vi.fn(() => ({
|
||||
agents: { defaults: { subagents: { archiveAfterMinutes: 0 } } },
|
||||
})),
|
||||
}));
|
||||
vi.mock("../config/config.js", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("../config/config.js")>();
|
||||
return {
|
||||
...actual,
|
||||
loadConfig: vi.fn(() => ({
|
||||
agents: { defaults: { subagents: { archiveAfterMinutes: 0 } } },
|
||||
})),
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("./subagent-announce.js", () => ({
|
||||
runSubagentAnnounceFlow: vi.fn(async () => true),
|
||||
|
||||
@@ -15,6 +15,11 @@ const resetAllLanes = vi.fn();
|
||||
const restartGatewayProcessWithFreshPid = vi.fn<
|
||||
() => { mode: "spawned" | "supervised" | "disabled" | "failed"; pid?: number; detail?: string }
|
||||
>(() => ({ mode: "disabled" }));
|
||||
const abortEmbeddedPiRun = vi.fn(
|
||||
(_sessionId?: string, _opts?: { mode?: "all" | "compacting" }) => false,
|
||||
);
|
||||
const getActiveEmbeddedRunCount = vi.fn(() => 0);
|
||||
const waitForActiveEmbeddedRuns = vi.fn(async (_timeoutMs: number) => ({ drained: true }));
|
||||
const DRAIN_TIMEOUT_LOG = "drain timeout reached; proceeding with restart";
|
||||
const gatewayLog = {
|
||||
info: vi.fn(),
|
||||
@@ -43,14 +48,21 @@ vi.mock("../../process/command-queue.js", () => ({
|
||||
resetAllLanes: () => resetAllLanes(),
|
||||
}));
|
||||
|
||||
vi.mock("../../agents/pi-embedded-runner/runs.js", () => ({
|
||||
abortEmbeddedPiRun: (sessionId?: string, opts?: { mode?: "all" | "compacting" }) =>
|
||||
abortEmbeddedPiRun(sessionId, opts),
|
||||
getActiveEmbeddedRunCount: () => getActiveEmbeddedRunCount(),
|
||||
waitForActiveEmbeddedRuns: (timeoutMs: number) => waitForActiveEmbeddedRuns(timeoutMs),
|
||||
}));
|
||||
|
||||
vi.mock("../../logging/subsystem.js", () => ({
|
||||
createSubsystemLogger: () => gatewayLog,
|
||||
}));
|
||||
|
||||
function removeNewSignalListeners(
|
||||
signal: NodeJS.Signals,
|
||||
existing: Set<(...args: unknown[]) => void>,
|
||||
) {
|
||||
const LOOP_SIGNALS = ["SIGTERM", "SIGINT", "SIGUSR1"] as const;
|
||||
type LoopSignal = (typeof LOOP_SIGNALS)[number];
|
||||
|
||||
function removeNewSignalListeners(signal: LoopSignal, existing: Set<(...args: unknown[]) => void>) {
|
||||
for (const listener of process.listeners(signal)) {
|
||||
const fn = listener as (...args: unknown[]) => void;
|
||||
if (!existing.has(fn)) {
|
||||
@@ -59,20 +71,42 @@ function removeNewSignalListeners(
|
||||
}
|
||||
}
|
||||
|
||||
async function withIsolatedSignals(run: () => Promise<void>) {
|
||||
const beforeSigterm = new Set(
|
||||
process.listeners("SIGTERM") as Array<(...args: unknown[]) => void>,
|
||||
);
|
||||
const beforeSigint = new Set(process.listeners("SIGINT") as Array<(...args: unknown[]) => void>);
|
||||
const beforeSigusr1 = new Set(
|
||||
process.listeners("SIGUSR1") as Array<(...args: unknown[]) => void>,
|
||||
);
|
||||
function addedSignalListener(
|
||||
signal: LoopSignal,
|
||||
existing: Set<(...args: unknown[]) => void>,
|
||||
): (() => void) | null {
|
||||
const listeners = process.listeners(signal) as Array<(...args: unknown[]) => void>;
|
||||
for (let i = listeners.length - 1; i >= 0; i -= 1) {
|
||||
const listener = listeners[i];
|
||||
if (listener && !existing.has(listener)) {
|
||||
return listener as () => void;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async function withIsolatedSignals(
|
||||
run: (helpers: { captureSignal: (signal: LoopSignal) => () => void }) => Promise<void>,
|
||||
) {
|
||||
const existingListeners = Object.fromEntries(
|
||||
LOOP_SIGNALS.map((signal) => [
|
||||
signal,
|
||||
new Set(process.listeners(signal) as Array<(...args: unknown[]) => void>),
|
||||
]),
|
||||
) as Record<LoopSignal, Set<(...args: unknown[]) => void>>;
|
||||
const captureSignal = (signal: LoopSignal) => {
|
||||
const listener = addedSignalListener(signal, existingListeners[signal]);
|
||||
if (!listener) {
|
||||
throw new Error(`expected new ${signal} listener`);
|
||||
}
|
||||
return () => listener();
|
||||
};
|
||||
try {
|
||||
await run();
|
||||
await run({ captureSignal });
|
||||
} finally {
|
||||
removeNewSignalListeners("SIGTERM", beforeSigterm);
|
||||
removeNewSignalListeners("SIGINT", beforeSigint);
|
||||
removeNewSignalListeners("SIGUSR1", beforeSigusr1);
|
||||
for (const signal of LOOP_SIGNALS) {
|
||||
removeNewSignalListeners(signal, existingListeners[signal]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,10 +178,11 @@ describe("runGatewayLoop", () => {
|
||||
it("exits 0 on SIGTERM after graceful close", async () => {
|
||||
vi.clearAllMocks();
|
||||
|
||||
await withIsolatedSignals(async () => {
|
||||
await withIsolatedSignals(async ({ captureSignal }) => {
|
||||
const { close, runtime, exited } = await createSignaledLoopHarness();
|
||||
const sigterm = captureSignal("SIGTERM");
|
||||
|
||||
process.emit("SIGTERM");
|
||||
sigterm();
|
||||
|
||||
await expect(exited).resolves.toBe(0);
|
||||
expect(close).toHaveBeenCalledWith({
|
||||
@@ -161,9 +196,11 @@ describe("runGatewayLoop", () => {
|
||||
it("restarts after SIGUSR1 even when drain times out, and resets lanes for the new iteration", async () => {
|
||||
vi.clearAllMocks();
|
||||
|
||||
await withIsolatedSignals(async () => {
|
||||
await withIsolatedSignals(async ({ captureSignal }) => {
|
||||
getActiveTaskCount.mockReturnValueOnce(2).mockReturnValueOnce(0);
|
||||
getActiveEmbeddedRunCount.mockReturnValueOnce(1).mockReturnValueOnce(0);
|
||||
waitForActiveTasks.mockResolvedValueOnce({ drained: false });
|
||||
waitForActiveEmbeddedRuns.mockResolvedValueOnce({ drained: true });
|
||||
|
||||
type StartServer = () => Promise<{
|
||||
close: (opts: { reason: string; restartExpectedMs: number | null }) => Promise<void>;
|
||||
@@ -171,6 +208,8 @@ describe("runGatewayLoop", () => {
|
||||
|
||||
const closeFirst = vi.fn(async () => {});
|
||||
const closeSecond = vi.fn(async () => {});
|
||||
const closeThird = vi.fn(async () => {});
|
||||
const { runtime, exited } = createRuntimeWithExitSignal();
|
||||
|
||||
const start = vi.fn<StartServer>();
|
||||
let resolveFirst: (() => void) | null = null;
|
||||
@@ -191,30 +230,37 @@ describe("runGatewayLoop", () => {
|
||||
return { close: closeSecond };
|
||||
});
|
||||
|
||||
start.mockRejectedValueOnce(new Error("stop-loop"));
|
||||
let resolveThird: (() => void) | null = null;
|
||||
const startedThird = new Promise<void>((resolve) => {
|
||||
resolveThird = resolve;
|
||||
});
|
||||
start.mockImplementationOnce(async () => {
|
||||
resolveThird?.();
|
||||
return { close: closeThird };
|
||||
});
|
||||
|
||||
const { runGatewayLoop } = await import("./run-loop.js");
|
||||
const runtime = {
|
||||
log: vi.fn(),
|
||||
error: vi.fn(),
|
||||
exit: vi.fn(),
|
||||
};
|
||||
const loopPromise = runGatewayLoop({
|
||||
void runGatewayLoop({
|
||||
start: start as unknown as Parameters<typeof runGatewayLoop>[0]["start"],
|
||||
runtime: runtime as unknown as Parameters<typeof runGatewayLoop>[0]["runtime"],
|
||||
});
|
||||
|
||||
await startedFirst;
|
||||
const sigusr1 = captureSignal("SIGUSR1");
|
||||
const sigterm = captureSignal("SIGTERM");
|
||||
expect(start).toHaveBeenCalledTimes(1);
|
||||
await new Promise<void>((resolve) => setImmediate(resolve));
|
||||
|
||||
process.emit("SIGUSR1");
|
||||
sigusr1();
|
||||
|
||||
await startedSecond;
|
||||
expect(start).toHaveBeenCalledTimes(2);
|
||||
await new Promise<void>((resolve) => setImmediate(resolve));
|
||||
|
||||
expect(waitForActiveTasks).toHaveBeenCalledWith(30_000);
|
||||
expect(abortEmbeddedPiRun).toHaveBeenCalledWith(undefined, { mode: "compacting" });
|
||||
expect(waitForActiveTasks).toHaveBeenCalledWith(90_000);
|
||||
expect(waitForActiveEmbeddedRuns).toHaveBeenCalledWith(90_000);
|
||||
expect(abortEmbeddedPiRun).toHaveBeenCalledWith(undefined, { mode: "all" });
|
||||
expect(markGatewayDraining).toHaveBeenCalledTimes(1);
|
||||
expect(gatewayLog.warn).toHaveBeenCalledWith(DRAIN_TIMEOUT_LOG);
|
||||
expect(closeFirst).toHaveBeenCalledWith({
|
||||
@@ -224,9 +270,10 @@ describe("runGatewayLoop", () => {
|
||||
expect(markGatewaySigusr1RestartHandled).toHaveBeenCalledTimes(1);
|
||||
expect(resetAllLanes).toHaveBeenCalledTimes(1);
|
||||
|
||||
process.emit("SIGUSR1");
|
||||
sigusr1();
|
||||
|
||||
await expect(loopPromise).rejects.toThrow("stop-loop");
|
||||
await startedThird;
|
||||
await new Promise<void>((resolve) => setImmediate(resolve));
|
||||
expect(closeSecond).toHaveBeenCalledWith({
|
||||
reason: "gateway restarting",
|
||||
restartExpectedMs: 1500,
|
||||
@@ -235,13 +282,20 @@ describe("runGatewayLoop", () => {
|
||||
expect(markGatewayDraining).toHaveBeenCalledTimes(2);
|
||||
expect(resetAllLanes).toHaveBeenCalledTimes(2);
|
||||
expect(acquireGatewayLock).toHaveBeenCalledTimes(3);
|
||||
|
||||
sigterm();
|
||||
await expect(exited).resolves.toBe(0);
|
||||
expect(closeThird).toHaveBeenCalledWith({
|
||||
reason: "gateway stopping",
|
||||
restartExpectedMs: null,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("releases the lock before exiting on spawned restart", async () => {
|
||||
vi.clearAllMocks();
|
||||
|
||||
await withIsolatedSignals(async () => {
|
||||
await withIsolatedSignals(async ({ captureSignal }) => {
|
||||
const lockRelease = vi.fn(async () => {});
|
||||
acquireGatewayLock.mockResolvedValueOnce({
|
||||
release: lockRelease,
|
||||
@@ -255,11 +309,12 @@ describe("runGatewayLoop", () => {
|
||||
|
||||
const exitCallOrder: string[] = [];
|
||||
const { runtime, exited } = await createSignaledLoopHarness(exitCallOrder);
|
||||
const sigusr1 = captureSignal("SIGUSR1");
|
||||
lockRelease.mockImplementation(async () => {
|
||||
exitCallOrder.push("lockRelease");
|
||||
});
|
||||
|
||||
process.emit("SIGUSR1");
|
||||
sigusr1();
|
||||
|
||||
await exited;
|
||||
expect(lockRelease).toHaveBeenCalled();
|
||||
@@ -271,40 +326,45 @@ describe("runGatewayLoop", () => {
|
||||
it("forwards lockPort to initial and restart lock acquisitions", async () => {
|
||||
vi.clearAllMocks();
|
||||
|
||||
await withIsolatedSignals(async () => {
|
||||
await withIsolatedSignals(async ({ captureSignal }) => {
|
||||
const closeFirst = vi.fn(async () => {});
|
||||
const closeSecond = vi.fn(async () => {});
|
||||
restartGatewayProcessWithFreshPid.mockReturnValueOnce({ mode: "disabled" });
|
||||
const closeThird = vi.fn(async () => {});
|
||||
const { runtime, exited } = createRuntimeWithExitSignal();
|
||||
|
||||
const start = vi
|
||||
.fn()
|
||||
.mockResolvedValueOnce({ close: closeFirst })
|
||||
.mockResolvedValueOnce({ close: closeSecond })
|
||||
.mockRejectedValueOnce(new Error("stop-loop"));
|
||||
const runtime = { log: vi.fn(), error: vi.fn(), exit: vi.fn() };
|
||||
.mockResolvedValueOnce({ close: closeThird });
|
||||
const { runGatewayLoop } = await import("./run-loop.js");
|
||||
const loopPromise = runGatewayLoop({
|
||||
void runGatewayLoop({
|
||||
start: start as unknown as Parameters<typeof runGatewayLoop>[0]["start"],
|
||||
runtime: runtime as unknown as Parameters<typeof runGatewayLoop>[0]["runtime"],
|
||||
lockPort: 18789,
|
||||
});
|
||||
await new Promise<void>((resolve) => setImmediate(resolve));
|
||||
const sigusr1 = captureSignal("SIGUSR1");
|
||||
const sigterm = captureSignal("SIGTERM");
|
||||
|
||||
sigusr1();
|
||||
await new Promise<void>((resolve) => setImmediate(resolve));
|
||||
sigusr1();
|
||||
|
||||
await new Promise<void>((resolve) => setImmediate(resolve));
|
||||
process.emit("SIGUSR1");
|
||||
await new Promise<void>((resolve) => setImmediate(resolve));
|
||||
process.emit("SIGUSR1");
|
||||
|
||||
await expect(loopPromise).rejects.toThrow("stop-loop");
|
||||
expect(acquireGatewayLock).toHaveBeenNthCalledWith(1, { port: 18789 });
|
||||
expect(acquireGatewayLock).toHaveBeenNthCalledWith(2, { port: 18789 });
|
||||
expect(acquireGatewayLock).toHaveBeenNthCalledWith(3, { port: 18789 });
|
||||
|
||||
sigterm();
|
||||
await expect(exited).resolves.toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
it("exits when lock reacquire fails during in-process restart fallback", async () => {
|
||||
vi.clearAllMocks();
|
||||
|
||||
await withIsolatedSignals(async () => {
|
||||
await withIsolatedSignals(async ({ captureSignal }) => {
|
||||
const lockRelease = vi.fn(async () => {});
|
||||
acquireGatewayLock
|
||||
.mockResolvedValueOnce({
|
||||
@@ -317,7 +377,8 @@ describe("runGatewayLoop", () => {
|
||||
});
|
||||
|
||||
const { start, exited } = await createSignaledLoopHarness();
|
||||
process.emit("SIGUSR1");
|
||||
const sigusr1 = captureSignal("SIGUSR1");
|
||||
sigusr1();
|
||||
|
||||
await expect(exited).resolves.toBe(1);
|
||||
expect(acquireGatewayLock).toHaveBeenCalledTimes(2);
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
import {
|
||||
abortEmbeddedPiRun,
|
||||
getActiveEmbeddedRunCount,
|
||||
waitForActiveEmbeddedRuns,
|
||||
} from "../../agents/pi-embedded-runner/runs.js";
|
||||
import type { startGatewayServer } from "../../gateway/server.js";
|
||||
import { acquireGatewayLock } from "../../infra/gateway-lock.js";
|
||||
import { restartGatewayProcessWithFreshPid } from "../../infra/process-respawn.js";
|
||||
@@ -90,7 +95,7 @@ export async function runGatewayLoop(params: {
|
||||
exitProcess(0);
|
||||
};
|
||||
|
||||
const DRAIN_TIMEOUT_MS = 30_000;
|
||||
const DRAIN_TIMEOUT_MS = 90_000;
|
||||
const SHUTDOWN_TIMEOUT_MS = 5_000;
|
||||
|
||||
const request = (action: GatewayRunSignalAction, signal: string) => {
|
||||
@@ -121,15 +126,33 @@ export async function runGatewayLoop(params: {
|
||||
// sessions get an explicit restart error instead of silent task loss.
|
||||
markGatewayDraining();
|
||||
const activeTasks = getActiveTaskCount();
|
||||
if (activeTasks > 0) {
|
||||
const activeRuns = getActiveEmbeddedRunCount();
|
||||
|
||||
// Best-effort abort for compacting runs so long compaction operations
|
||||
// don't hold session write locks across restart boundaries.
|
||||
if (activeRuns > 0) {
|
||||
abortEmbeddedPiRun(undefined, { mode: "compacting" });
|
||||
}
|
||||
|
||||
if (activeTasks > 0 || activeRuns > 0) {
|
||||
gatewayLog.info(
|
||||
`draining ${activeTasks} active task(s) before restart (timeout ${DRAIN_TIMEOUT_MS}ms)`,
|
||||
`draining ${activeTasks} active task(s) and ${activeRuns} active embedded run(s) before restart (timeout ${DRAIN_TIMEOUT_MS}ms)`,
|
||||
);
|
||||
const { drained } = await waitForActiveTasks(DRAIN_TIMEOUT_MS);
|
||||
if (drained) {
|
||||
gatewayLog.info("all active tasks drained");
|
||||
const [tasksDrain, runsDrain] = await Promise.all([
|
||||
activeTasks > 0
|
||||
? waitForActiveTasks(DRAIN_TIMEOUT_MS)
|
||||
: Promise.resolve({ drained: true }),
|
||||
activeRuns > 0
|
||||
? waitForActiveEmbeddedRuns(DRAIN_TIMEOUT_MS)
|
||||
: Promise.resolve({ drained: true }),
|
||||
]);
|
||||
if (tasksDrain.drained && runsDrain.drained) {
|
||||
gatewayLog.info("all active work drained");
|
||||
} else {
|
||||
gatewayLog.warn("drain timeout reached; proceeding with restart");
|
||||
// Final best-effort abort to avoid carrying active runs into the
|
||||
// next lifecycle when drain time budget is exhausted.
|
||||
abortEmbeddedPiRun(undefined, { mode: "all" });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,7 +98,8 @@ describe("restart-helper", () => {
|
||||
expect(scriptPath.endsWith(".sh")).toBe(true);
|
||||
expect(content).toContain("#!/bin/sh");
|
||||
expect(content).toContain("launchctl kickstart -k 'gui/501/ai.openclaw.gateway'");
|
||||
// Should fall back to bootstrap when kickstart fails (service deregistered after bootout)
|
||||
// Should clear disabled state and fall back to bootstrap when kickstart fails.
|
||||
expect(content).toContain("launchctl enable 'gui/501/ai.openclaw.gateway'");
|
||||
expect(content).toContain("launchctl bootstrap 'gui/501'");
|
||||
expect(content).toContain('rm -f "$0"');
|
||||
await cleanupScript(scriptPath);
|
||||
|
||||
@@ -95,8 +95,10 @@ rm -f "$0"
|
||||
# Wait briefly to ensure file locks are released after update.
|
||||
sleep 1
|
||||
# Try kickstart first (works when the service is still registered).
|
||||
# If it fails (e.g. after bootout), re-register via bootstrap then kickstart.
|
||||
# If it fails (e.g. after bootout), clear any persisted disabled state,
|
||||
# then re-register via bootstrap and kickstart.
|
||||
if ! launchctl kickstart -k 'gui/${uid}/${escaped}' 2>/dev/null; then
|
||||
launchctl enable 'gui/${uid}/${escaped}' 2>/dev/null
|
||||
launchctl bootstrap 'gui/${uid}' '${escapedPlistPath}' 2>/dev/null
|
||||
launchctl kickstart -k 'gui/${uid}/${escaped}' 2>/dev/null || true
|
||||
fi
|
||||
|
||||
@@ -42,6 +42,11 @@ let upsertAuthProfile: typeof import("../agents/auth-profiles.js").upsertAuthPro
|
||||
type ProviderAuthConfigSnapshot = {
|
||||
auth?: { profiles?: Record<string, { provider?: string; mode?: string }> };
|
||||
agents?: { defaults?: { model?: { primary?: string } } };
|
||||
talk?: {
|
||||
provider?: string;
|
||||
apiKey?: string | { source?: string; id?: string };
|
||||
providers?: Record<string, { apiKey?: string | { source?: string; id?: string } }>;
|
||||
};
|
||||
models?: {
|
||||
providers?: Record<
|
||||
string,
|
||||
@@ -357,6 +362,38 @@ describe("onboard (non-interactive): provider auth", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("does not persist talk fallback secrets when OpenAI ref onboarding starts from an empty config", async () => {
|
||||
await withOnboardEnv("openclaw-onboard-openai-ref-no-talk-leak-", async (env) => {
|
||||
await withEnvAsync(
|
||||
{
|
||||
OPENAI_API_KEY: "sk-openai-env-key", // pragma: allowlist secret
|
||||
ELEVENLABS_API_KEY: "elevenlabs-env-key", // pragma: allowlist secret
|
||||
},
|
||||
async () => {
|
||||
const cfg = await runOnboardingAndReadConfig(env, {
|
||||
authChoice: "openai-api-key",
|
||||
secretInputMode: "ref", // pragma: allowlist secret
|
||||
});
|
||||
|
||||
expect(cfg.agents?.defaults?.model?.primary).toBe(OPENAI_DEFAULT_MODEL);
|
||||
expect(cfg.talk).toBeUndefined();
|
||||
|
||||
const store = ensureAuthProfileStore();
|
||||
const profile = store.profiles["openai:default"];
|
||||
expect(profile?.type).toBe("api_key");
|
||||
if (profile?.type === "api_key") {
|
||||
expect(profile.key).toBeUndefined();
|
||||
expect(profile.keyRef).toEqual({
|
||||
source: "env",
|
||||
provider: "default",
|
||||
id: "OPENAI_API_KEY",
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it.each([
|
||||
{
|
||||
name: "anthropic",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user