Compare commits

..

28 Commits

Author SHA1 Message Date
Jake
e78ae48e69 fix(memory): add input_type to Voyage AI embeddings for improved retrieval (#10818)
* fix(memory): add input_type to Voyage AI embeddings for improved retrieval

Voyage AI recommends passing input_type='document' when indexing and
input_type='query' when searching. This improves retrieval quality by
optimising the embedding space for each direction.

Changes:
- embedQuery now passes input_type: 'query'
- embedBatch now passes input_type: 'document'
- Batch API request_params includes input_type: 'document'
- Tests updated to verify input_type is passed correctly

* Changelog: note Voyage embeddings input_type fix (#10818) (thanks @mcinteerj)

---------

Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-02-06 21:55:09 -06:00
Markus Buhatem Koch
4c1da23a71 Revert previous change from 'Clawdbot' to 'OpenClaw' in lore (#9119) 2026-02-06 21:53:02 -05:00
Val Alexander
3d2fe9284e Fix repository links in formal-verification.md (#10200)
Updated repository links for formal verification models.
2026-02-06 21:47:55 -05:00
Peter Steinberger
a3b5f1b15c fix(build): unblock pnpm build dts 2026-02-06 18:43:11 -08:00
DEOKLYONG MOON
d1dc60774b Docs: fix broken /plugins links (#9308)
* Docs: fix broken /plugins links to /plugin

The documentation linked to /plugins which doesn't exist.
The correct path is /plugin (singular) which contains the
plugins overview documentation.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* docs: drop manual zh-CN doc edits from plugins link fix

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Sebastian <19554889+sebslight@users.noreply.github.com>
2026-02-06 21:08:26 -05:00
Tyler Yust
d90cac990c fix: cron scheduler reliability, store hardening, and UX improvements (#10776)
* refactor: update cron job wake mode and run mode handling

- Changed default wake mode from 'next-heartbeat' to 'now' in CronJobEditor and related CLI commands.
- Updated cron-tool tests to reflect changes in run mode, introducing 'due' and 'force' options.
- Enhanced cron-tool logic to handle new run modes and ensure compatibility with existing job structures.
- Added new tests for delivery plan consistency and job execution behavior under various conditions.
- Improved normalization functions to handle wake mode and session target casing.

This refactor aims to streamline cron job configurations and enhance the overall user experience with clearer defaults and improved functionality.

* test: enhance cron job functionality and UI

- Added tests to ensure the isolated agent correctly announces the final payload text when delivering messages via Telegram.
- Implemented a new function to pick the last deliverable payload from a list of delivery payloads.
- Enhanced the cron service to maintain legacy "every" jobs while minute cron jobs recompute schedules.
- Updated the cron store migration tests to verify the addition of anchorMs to legacy every schedules.
- Improved the UI for displaying cron job details, including job state and delivery information, with new styles and layout adjustments.

These changes aim to improve the reliability and user experience of the cron job system.

* test: enhance sessions thinking level handling

- Added tests to verify that the correct thinking levels are applied during session spawning.
- Updated the sessions-spawn-tool to include a new parameter for overriding thinking levels.
- Enhanced the UI to support additional thinking levels, including "xhigh" and "full", and improved the handling of current options in dropdowns.

These changes aim to improve the flexibility and accuracy of thinking level configurations in session management.

* feat: enhance session management and cron job functionality

- Introduced passthrough arguments in the test-parallel script to allow for flexible command-line options.
- Updated session handling to hide cron run alias session keys from the sessions list, improving clarity.
- Enhanced the cron service to accurately record job start times and durations, ensuring better tracking of job execution.
- Added tests to verify the correct behavior of the cron service under various conditions, including zero-delay timers.

These changes aim to improve the usability and reliability of session and cron job management.

* feat: implement job running state checks in cron service

- Added functionality to prevent manual job runs if a job is already in progress, enhancing job management.
- Updated the `isJobDue` function to include checks for running jobs, ensuring accurate scheduling.
- Enhanced the `run` function to return a specific reason when a job is already running.
- Introduced a new test case to verify the behavior of forced manual runs during active job execution.

These changes aim to improve the reliability and clarity of cron job execution and management.

* feat: add session ID and key to CronRunLogEntry model

- Introduced `sessionid` and `sessionkey` properties to the `CronRunLogEntry` struct for enhanced tracking of session-related information.
- Updated the initializer and Codable conformance to accommodate the new properties, ensuring proper serialization and deserialization.

These changes aim to improve the granularity of logging and session management within the cron job system.

* fix: improve session display name resolution

- Updated the `resolveSessionDisplayName` function to ensure that both label and displayName are trimmed and default to an empty string if not present.
- Enhanced the logic to prevent returning the key if it matches the label or displayName, improving clarity in session naming.

These changes aim to enhance the accuracy and usability of session display names in the UI.

* perf: skip cron store persist when idle timer tick produces no changes

recomputeNextRuns now returns a boolean indicating whether any job
state was mutated. The idle path in onTimer only persists when the
return value is true, eliminating unnecessary file writes every 60s
for far-future or idle schedules.

* fix: prep for merge - explicit delivery mode migration, docs + changelog (#10776) (thanks @tyler6204)
2026-02-06 18:03:03 -08:00
Peter Steinberger
0dd7033521 chore(lockfile): fix pnpm-lock 2026-02-06 17:56:22 -08:00
Raymond Berger
c80a09fc2f Fix QMD CLI installation link in memory.md (#8647)
Correct the installation link for the QMD CLI in the documentation.
2026-02-06 20:53:47 -05:00
Peter Steinberger
f831c48e56 docs(changelog): highlight Opus 4.6 + Codex 5.3 first 2026-02-06 17:28:46 -08:00
Peter Steinberger
c237a82b42 docs(changelog): curate 2026.2.6 2026-02-06 16:56:29 -08:00
Peter Steinberger
94b2fc14f2 chore(deps): bump carbon beta 2026-02-06 16:56:25 -08:00
Peter Steinberger
0daf416908 fix(agents): add Opus 4.6 forward-compat fallback 2026-02-06 16:56:21 -08:00
Peter Steinberger
dca8cf958b chore(deps): update deps 2026-02-06 16:37:56 -08:00
Seb Slight
93bf75279f docs(imessage): improve macOS TCC troubleshooting guidance (#10781) 2026-02-06 19:21:52 -05:00
gitpds
fe308a3aa1 docs(imessage): add macOS TCC troubleshooting 2026-02-06 19:10:01 -05:00
Peter Steinberger
7be921c434 docs(changelog): refresh 2026.2.6 since v2026.2.3 2026-02-06 15:47:10 -08:00
Peter Steinberger
5163833be5 docs: fix markdownlint fragments + headings 2026-02-06 15:45:39 -08:00
Peter Steinberger
d898ad6807 fix(telegram): cast fetch for grammY ApiClientOptions 2026-02-06 15:45:34 -08:00
Peter Steinberger
677450cd9b chore(release): bump version to 2026.2.6 2026-02-06 15:37:31 -08:00
Peter Steinberger
ac5944cde7 docs(changelog): include merged PRs since v2026.2.3 2026-02-06 15:36:13 -08:00
Peter Steinberger
3b768a2851 docs(changelog): prepare 2026.2.6 2026-02-06 15:30:17 -08:00
Shadril Hassan Shifat
2c8af78d20 fix(hooks): replace debug console.log with proper subsystem logging in session-memory (#10730)
* fix: replace debug console.log with proper subsystem logging in session-memory

* fix(hooks): normalize session-memory subsystem logging

---------

Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-02-06 17:22:38 -06:00
calvin-hpnet
48b0fd8d88 feat(antigravity): update default model to Claude Opus 4.6 (#10720)
* feat(antigravity): update default model to Claude Opus 4.6

Claude Opus 4.5 has been replaced by Claude Opus 4.6 on the
Antigravity (Google Cloud Code Assist) platform.

- Update DEFAULT_MODEL in google-antigravity-auth extension
- Update testing docs to reference the new model

* fix: update remaining antigravity opus 4.5 refs in zh-CN docs and tests

Address review comments from Greptile:
- Update zh-CN/testing.md antigravity model references
- Update pi-tools-agent-config.test.ts model IDs

* Antigravity: default OAuth model to Opus 4.6 (#10720) (thanks @calvin-hpnet)

---------

Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-02-06 16:42:57 -06:00
Tak Hoffman
40425db0f1 feat(memory): document Voyage embeddings + VOYAGE_API_KEY (#7078) (thanks @mcinteerj) (#10699) 2026-02-06 15:51:47 -06:00
Jake
6965a2cc9d feat(memory): native Voyage AI support (#7078)
* feat(memory): add native Voyage AI embedding support with batching

Cherry-picked from PR #2519, resolved conflict in memory-search.ts
(hasRemote -> hasRemoteConfig rename + added voyage provider)

* fix(memory): optimize voyage batch memory usage with streaming and deduplicate code

Cherry-picked from PR #2519. Fixed lint error: changed this.runWithConcurrency
to use imported runWithConcurrency function after extraction to internal.ts
2026-02-06 15:09:32 -06:00
Tak Hoffman
e3d3893d5d Docs: revise PR and issue submission guides (#10617)
* Docs: revise PR submission guide

* Docs: revise issue submission guide
2026-02-06 13:29:11 -06:00
Yida-Dev
4216449405 fix: guard resolveUserPath against undefined input (#10176)
* fix: guard resolveUserPath against undefined input

When subagent spawner omits workspaceDir, resolveUserPath receives
undefined and crashes on .trim().  Add a falsy guard that falls back
to process.cwd(), matching the behavior callers already expect.

Closes #10089

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: harden runner workspace fallback (#10176) (thanks @Yida-Dev)

* fix: harden workspace fallback scoping (#10176) (thanks @Yida-Dev)

* refactor: centralize workspace fallback classification and redaction (#10176) (thanks @Yida-Dev)

* test: remove explicit any from utils mock (#10176) (thanks @Yida-Dev)

* security: reject malformed agent session keys for workspace resolution (#10176) (thanks @Yida-Dev)

---------

Co-authored-by: Yida-Dev <reyifeijun@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Gustavo Madeira Santana <gumadeiras@gmail.com>
2026-02-06 13:16:58 -05:00
Tak Hoffman
5842bcaaf7 Docs: add PR sign-off template (#10561) 2026-02-06 11:58:39 -06:00
171 changed files with 5456 additions and 1280 deletions

View File

@@ -2,29 +2,52 @@
Docs: https://docs.openclaw.ai
## 2026.2.4
## 2026.2.6
### Changes
- Cron: default `wakeMode` is now `"now"` for new jobs (was `"next-heartbeat"`). (#10776) Thanks @tyler6204.
- Cron: `cron run` defaults to force execution; use `--due` to restrict to due-only. (#10776) Thanks @tyler6204.
- Models: support Anthropic Opus 4.6 and OpenAI Codex gpt-5.3-codex (forward-compat fallbacks). (#9853, #10720, #9995) Thanks @TinyTb, @calvin-hpnet, @tyler6204.
- Providers: add xAI (Grok) support. (#9885) Thanks @grp06.
- Web UI: add token usage dashboard. (#10072) Thanks @Takhoffman.
- Memory: native Voyage AI support. (#7078) Thanks @mcinteerj.
- Sessions: cap sessions_history payloads to reduce context overflow. (#10000) Thanks @gut-puncture.
- CLI: sort commands alphabetically in help output. (#8068) Thanks @deepsoumya617.
- Agents: bump pi-mono to 0.52.7; add embedded forward-compat fallback for Opus 4.6 model ids.
### Added
- Cron: run history deep-links to session chat from the dashboard. (#10776) Thanks @tyler6204.
- Cron: per-run session keys in run log entries and default labels for cron sessions. (#10776) Thanks @tyler6204.
- Cron: legacy payload field compatibility (`deliver`, `channel`, `to`, `bestEffortDeliver`) in schema. (#10776) Thanks @tyler6204.
### Fixes
- Cron: scheduler reliability (timer drift, restart catch-up, lock contention, stale running markers). (#10776) Thanks @tyler6204.
- Cron: store migration hardening (legacy field migration, parse error handling, explicit delivery mode persistence). (#10776) Thanks @tyler6204.
- Memory: set Voyage embeddings `input_type` for improved retrieval. (#10818) Thanks @mcinteerj.
- Telegram: auto-inject DM topic threadId in message tool + subagent announce. (#7235) Thanks @Lukavyi.
- Security: require auth for Gateway canvas host and A2UI assets. (#9518) Thanks @coygeek.
- Cron: fix scheduling and reminder delivery regressions; harden next-run recompute + timer re-arming + legacy schedule fields. (#9733, #9823, #9948, #9932) Thanks @tyler6204, @pycckuu, @j2h4u, @fujiwara-tofu-shop.
- Update: harden Control UI asset handling in update flow. (#10146) Thanks @gumadeiras.
- Security: add skill/plugin code safety scanner; redact credentials from config.get gateway responses. (#9806, #9858) Thanks @abdelsfane.
- Exec approvals: coerce bare string allowlist entries to objects. (#9903) Thanks @mcaxtr.
- Slack: add mention stripPatterns for /new and /reset. (#9971) Thanks @ironbyte-rgb.
- Chrome extension: fix bundled path resolution. (#8914) Thanks @kelvinCB.
- Compaction/errors: allow multiple compaction retries on context overflow; show clear billing errors. (#8928, #8391) Thanks @Glucksberg.
## 2026.2.3
### Changes
- Agents: bump pi-mono packages to 0.52.5. (#9949) Thanks @gumadeiras.
- Models: default Anthropic model to `anthropic/claude-opus-4-6`. (#9853) Thanks @TinyTb.
- Models/Onboarding: refresh provider defaults, update OpenAI/OpenAI Codex wizard defaults, and harden model allowlist initialization for first-time configs with matching docs/tests. (#9911) Thanks @gumadeiras.
- Telegram: auto-inject forum topic `threadId` in message tool and subagent announce so media, buttons, and subagent results land in the correct topic instead of General. (#7235) Thanks @Lukavyi.
- Security: add skill/plugin code safety scanner that detects dangerous patterns (command injection, eval, data exfiltration, obfuscated code, crypto mining, env harvesting) in installed extensions. Integrated into `openclaw security audit --deep` and plugin install flow; scan failures surface as warnings. (#9806) Thanks @abdelsfane.
- CLI: sort `openclaw --help` commands (and options) alphabetically. (#8068) Thanks @deepsoumya617.
- Telegram: remove last `@ts-nocheck` from `bot-handlers.ts`, use Grammy types directly, deduplicate `StickerMetadata`. Zero `@ts-nocheck` remaining in `src/telegram/`. (#9206)
- Telegram: remove `@ts-nocheck` from `bot-message.ts`, type deps via `Omit<BuildTelegramMessageContextParams>`, widen `allMedia` to `TelegramMediaRef[]`. (#9180)
- Telegram: remove `@ts-nocheck` from `bot.ts`, fix duplicate `bot.catch` error handler (Grammy overrides), remove dead reaction `message_thread_id` routing, harden sticker cache guard. (#9077)
- Telegram: allow per-group and per-topic `groupPolicy` overrides under `channels.telegram.groups`. (#9775) Thanks @nicolasstanley.
- Feishu: expand channel handling (posts with images, doc links, routing, reactions/typing, replies, native commands). (#8975) Thanks @jiulingyun.
- Onboarding: add Cloudflare AI Gateway provider setup and docs. (#7914) Thanks @roerohan.
- Onboarding: add Moonshot (.cn) auth choice and keep the China base URL when preserving defaults. (#7180) Thanks @waynelwz.
- Onboarding: add xAI (Grok) auth choice and provider defaults. (#9885) Thanks @grp06.
- Docs: clarify tmux send-keys for TUI by splitting text and Enter. (#7737) Thanks @Wangnov.
- Web UI: add Token Usage dashboard with session analytics. (#8462) Thanks @mcinteerj.
- Docs: mirror the landing page revamp for zh-CN (features, quickstart, docs directory, network model, credits). (#8994) Thanks @joshp123.
- Docs: strengthen secure DM mode guidance for multi-user inboxes with an explicit warning and example. (#9377) Thanks @Shrinija17.
- Docs: document `activeHours` heartbeat field with timezone resolution chain and example. (#9366) Thanks @unisone.
- Messages: add per-channel and per-account responsePrefix overrides across channels. (#9001) Thanks @mudrii.
- Cron: add announce delivery mode for isolated jobs (CLI + Control UI) and delivery mode config.
- Cron: default isolated jobs to announce delivery; accept ISO 8601 `schedule.at` in tool inputs.
@@ -35,34 +58,15 @@ Docs: https://docs.openclaw.ai
### Fixes
- Control UI: add hardened fallback for asset resolution in global npm installs. (#4855) Thanks @anapivirtua.
- Update: remove dead restore control-ui step that failed on gitignored dist/ output.
- Update: avoid wiping prebuilt Control UI assets during dev auto-builds (`tsdown --no-clean`), run update doctor via `openclaw.mjs`, and auto-restore missing UI assets after doctor. (#10146) Thanks @gumadeiras.
- Models: add forward-compat fallback for `openai-codex/gpt-5.3-codex` when model registry hasn't discovered it yet. (#9989) Thanks @w1kke.
- Auto-reply/Docs: normalize `extra-high` (and spaced variants) to `xhigh` for Codex thinking levels, and align Codex 5.3 FAQ examples. (#9976) Thanks @slonce70.
- Compaction: remove orphaned `tool_result` messages during history pruning to prevent session corruption from aborted tool calls. (#9868, fixes #9769, #9724, #9672)
- Telegram: pass `parentPeer` for forum topic binding inheritance so group-level bindings apply to all topics within the group. (#9789, fixes #9545, #9351)
- CLI: pass `--disable-warning=ExperimentalWarning` as a Node CLI option when respawning (avoid disallowed `NODE_OPTIONS` usage; fixes npm pack). (#9691) Thanks @18-RAJAT.
- CLI: resolve bundled Chrome extension assets by walking up to the nearest assets directory; add resolver and clipboard tests. (#8914) Thanks @kelvinCB.
- Tests: stabilize Windows ACL coverage with deterministic os.userInfo mocking. (#9335) Thanks @M00N7682.
- Exec approvals: coerce bare string allowlist entries to objects to prevent allowlist corruption. (#9903, fixes #9790) Thanks @mcaxtr.
- Exec approvals: ensure two-phase approval registration/decision flow works reliably by validating `twoPhase` requests and exposing `waitDecision` as an approvals-scoped gateway method. (#3357, fixes #2402) Thanks @ramin-shirali.
- Heartbeat: allow explicit accountId routing for multi-account channels. (#8702) Thanks @lsh411.
- TUI/Gateway: handle non-streaming finals, refresh history for non-local chat runs, and avoid event gap warnings for targeted tool streams. (#8432) Thanks @gumadeiras.
- Security: stop exposing Gateway auth tokens via URL query parameters in Control UI entrypoints, and reject hook tokens in query parameters. (#9436) Thanks @coygeek.
- Shell completion: auto-detect and migrate slow dynamic patterns to cached files for faster terminal startup; add completion health checks to doctor/update/onboard.
- Telegram: honor session model overrides in inline model selection. (#8193) Thanks @gildo.
- Web UI: fix agent model selection saves for default/non-default agents and wrap long workspace paths. Thanks @Takhoffman.
- Web UI: resolve header logo path when `gateway.controlUi.basePath` is set. (#7178) Thanks @Yeom-JinHo.
- Web UI: apply button styling to the new-messages indicator.
- Onboarding: infer auth choice from non-interactive API key flags. (#8484) Thanks @f-trycua.
- Usage: include estimated cost when breakdown is missing and keep `usage.cost` days support. (#8462) Thanks @mcinteerj.
- Security: keep untrusted channel metadata out of system prompts (Slack/Discord). Thanks @KonstantinMirin.
- Security: redact channel credentials (tokens, passwords, API keys, secrets) from gateway config APIs and preserve secrets during Control UI round-trips. (#9858) Thanks @abdelsfane.
- Discord: treat allowlisted senders as owner for system-prompt identity hints while keeping channel topics untrusted.
- Slack: strip `<@...>` mention tokens before command matching so `/new` and `/reset` work when prefixed with a mention. (#9971) Thanks @ironbyte-rgb.
- Agents: cap `sessions_history` tool output and strip oversized fields to prevent context overflow. (#10000) Thanks @gut-puncture.
- Security: normalize code safety finding paths in `openclaw security audit --deep` output for cross-platform consistency. (#10000) Thanks @gut-puncture.
- Security: enforce sandboxed media paths for message tool attachments. (#9182) Thanks @victormier.
- Security: require explicit credentials for gateway URL overrides to prevent credential leakage. (#8113) Thanks @victormier.
- Security: gate `whatsapp_login` tool to owner senders and default-deny non-owner contexts. (#8768) Thanks @victormier.
@@ -70,13 +74,9 @@ Docs: https://docs.openclaw.ai
- Voice call: add regression coverage for anonymous inbound caller IDs with allowlist policy. (#8104) Thanks @victormier.
- Cron: accept epoch timestamps and 0ms durations in CLI `--at` parsing.
- Cron: reload store data when the store file is recreated or mtime changes.
- Cron: prevent `recomputeNextRuns` from skipping due jobs when timer fires late by reordering `onTimer` flow. (#9823, fixes #9788) Thanks @pycckuu.
- Cron: deliver announce runs directly, honor delivery mode, and respect wakeMode for summaries. (#8540) Thanks @tyler6204.
- Cron: correct announce delivery inference for thread session keys and null delivery inputs. (#9733) Thanks @tyler6204.
- Telegram: include forward_from_chat metadata in forwarded messages and harden cron delivery target checks. (#8392) Thanks @Glucksberg.
- Telegram: preserve DM topic threadId in deliveryContext. (#9039) Thanks @lailoo.
- macOS: fix cron payload summary rendering and ISO 8601 formatter concurrency safety.
- Security: require gateway auth for Canvas host and A2UI assets. (#9518) Thanks @coygeek.
## 2026.2.2-3

View File

@@ -22,7 +22,7 @@ android {
minSdk = 31
targetSdk = 36
versionCode = 202602030
versionName = "2026.2.4"
versionName = "2026.2.6"
}
buildTypes {

View File

@@ -19,7 +19,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>2026.2.4</string>
<string>2026.2.6</string>
<key>CFBundleVersion</key>
<string>20260202</string>
<key>NSAppTransportSecurity</key>

View File

@@ -17,7 +17,7 @@
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleShortVersionString</key>
<string>2026.2.4</string>
<string>2026.2.6</string>
<key>CFBundleVersion</key>
<string>20260202</string>
</dict>

View File

@@ -81,7 +81,7 @@ targets:
properties:
CFBundleDisplayName: OpenClaw
CFBundleIconName: AppIcon
CFBundleShortVersionString: "2026.2.4"
CFBundleShortVersionString: "2026.2.6"
CFBundleVersion: "20260202"
UILaunchScreen: {}
UIApplicationSceneManifest:
@@ -130,5 +130,5 @@ targets:
path: Tests/Info.plist
properties:
CFBundleDisplayName: OpenClawTests
CFBundleShortVersionString: "2026.2.4"
CFBundleShortVersionString: "2026.2.6"
CFBundleVersion: "20260202"

View File

@@ -29,7 +29,7 @@ struct CronJobEditor: View {
@State var agentId: String = ""
@State var enabled: Bool = true
@State var sessionTarget: CronSessionTarget = .main
@State var wakeMode: CronWakeMode = .nextHeartbeat
@State var wakeMode: CronWakeMode = .now
@State var deleteAfterRun: Bool = false
enum ScheduleKind: String, CaseIterable, Identifiable { case at, every, cron; var id: String { rawValue } }
@@ -119,8 +119,8 @@ struct CronJobEditor: View {
GridRow {
self.gridLabel("Wake mode")
Picker("", selection: self.$wakeMode) {
Text("next-heartbeat").tag(CronWakeMode.nextHeartbeat)
Text("now").tag(CronWakeMode.now)
Text("next-heartbeat").tag(CronWakeMode.nextHeartbeat)
}
.labelsHidden()
.pickerStyle(.segmented)

View File

@@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>2026.2.4</string>
<string>2026.2.6</string>
<key>CFBundleVersion</key>
<string>202602020</string>
<key>CFBundleIconFile</key>

View File

@@ -2025,6 +2025,8 @@ public struct CronRunLogEntry: Codable, Sendable {
public let status: AnyCodable?
public let error: String?
public let summary: String?
public let sessionid: String?
public let sessionkey: String?
public let runatms: Int?
public let durationms: Int?
public let nextrunatms: Int?
@@ -2036,6 +2038,8 @@ public struct CronRunLogEntry: Codable, Sendable {
status: AnyCodable?,
error: String?,
summary: String?,
sessionid: String?,
sessionkey: String?,
runatms: Int?,
durationms: Int?,
nextrunatms: Int?
@@ -2046,6 +2050,8 @@ public struct CronRunLogEntry: Codable, Sendable {
self.status = status
self.error = error
self.summary = summary
self.sessionid = sessionid
self.sessionkey = sessionkey
self.runatms = runatms
self.durationms = durationms
self.nextrunatms = nextrunatms
@@ -2057,6 +2063,8 @@ public struct CronRunLogEntry: Codable, Sendable {
case status
case error
case summary
case sessionid = "sessionId"
case sessionkey = "sessionKey"
case runatms = "runAtMs"
case durationms = "durationMs"
case nextrunatms = "nextRunAtMs"

View File

@@ -2025,6 +2025,8 @@ public struct CronRunLogEntry: Codable, Sendable {
public let status: AnyCodable?
public let error: String?
public let summary: String?
public let sessionid: String?
public let sessionkey: String?
public let runatms: Int?
public let durationms: Int?
public let nextrunatms: Int?
@@ -2036,6 +2038,8 @@ public struct CronRunLogEntry: Codable, Sendable {
status: AnyCodable?,
error: String?,
summary: String?,
sessionid: String?,
sessionkey: String?,
runatms: Int?,
durationms: Int?,
nextrunatms: Int?
@@ -2046,6 +2050,8 @@ public struct CronRunLogEntry: Codable, Sendable {
self.status = status
self.error = error
self.summary = summary
self.sessionid = sessionid
self.sessionkey = sessionkey
self.runatms = runatms
self.durationms = durationms
self.nextrunatms = nextrunatms
@@ -2057,6 +2063,8 @@ public struct CronRunLogEntry: Codable, Sendable {
case status
case error
case summary
case sessionid = "sessionId"
case sessionkey = "sessionKey"
case runatms = "runAtMs"
case durationms = "durationMs"
case nextrunatms = "nextRunAtMs"

View File

@@ -40,7 +40,7 @@ openclaw cron add \
--delete-after-run
openclaw cron list
openclaw cron run <job-id> --force
openclaw cron run <job-id>
openclaw cron runs --id <job-id>
```
@@ -123,8 +123,8 @@ local timezone is used.
Main jobs enqueue a system event and optionally wake the heartbeat runner.
They must use `payload.kind = "systemEvent"`.
- `wakeMode: "next-heartbeat"` (default): event waits for the next scheduled heartbeat.
- `wakeMode: "now"`: event triggers an immediate heartbeat run.
- `wakeMode: "now"` (default): event triggers an immediate heartbeat run.
- `wakeMode: "next-heartbeat"`: event waits for the next scheduled heartbeat.
This is the best fit when you want the normal heartbeat prompt + main-session context.
See [Heartbeat](/gateway/heartbeat).
@@ -288,7 +288,7 @@ Notes:
- `sessionTarget` must be `"main"` or `"isolated"` and must match `payload.kind`.
- Optional fields: `agentId`, `description`, `enabled`, `deleteAfterRun` (defaults to true for `at`),
`delivery`.
- `wakeMode` defaults to `"next-heartbeat"` when omitted.
- `wakeMode` defaults to `"now"` when omitted.
### cron.update params
@@ -420,10 +420,11 @@ openclaw cron edit <jobId> --agent ops
openclaw cron edit <jobId> --clear-agent
```
Manual run (debug):
Manual run (force is the default, use `--due` to only run when due):
```bash
openclaw cron run <jobId> --force
openclaw cron run <jobId>
openclaw cron run <jobId> --due
```
Edit an existing job (patch fields):

View File

@@ -337,4 +337,4 @@ Prefer `chat_guid` for stable routing:
- OpenClaw auto-hides known-broken actions based on the BlueBubbles server's macOS version. If edit still appears on macOS 26 (Tahoe), disable it manually with `channels.bluebubbles.actions.edit=false`.
- For status/health info: `openclaw status --all` or `openclaw status --deep`.
For general channel workflow reference, see [Channels](/channels) and the [Plugins](/plugins) guide.
For general channel workflow reference, see [Channels](/channels) and the [Plugins](/plugin) guide.

View File

@@ -62,6 +62,28 @@ Disable with:
- Automation permission when sending.
- `channels.imessage.cliPath` can point to any command that proxies stdin/stdout (for example, a wrapper script that SSHes to another Mac and runs `imsg rpc`).
## Troubleshooting macOS Privacy and Security TCC
If sending/receiving fails (for example, `imsg rpc` exits non-zero, times out, or the gateway appears to hang), a common cause is a macOS permission prompt that was never approved.
macOS grants TCC permissions per app/process context. Approve prompts in the same context that runs `imsg` (for example, Terminal/iTerm, a LaunchAgent session, or an SSH-launched process).
Checklist:
- **Full Disk Access**: allow access for the process running OpenClaw (and any shell/SSH wrapper that executes `imsg`). This is required to read the Messages database (`chat.db`).
- **Automation → Messages**: allow the process running OpenClaw (and/or your terminal) to control **Messages.app** for outbound sends.
- **`imsg` CLI health**: verify `imsg` is installed and supports RPC (`imsg rpc --help`).
Tip: If OpenClaw is running headless (LaunchAgent/systemd/SSH) the macOS prompt can be easy to miss. Run a one-time interactive command in a GUI terminal to force the prompt, then retry:
```bash
imsg chats --limit 1
# or
imsg send <handle> "test"
```
Related macOS folder permissions (Desktop/Documents/Downloads): [/platforms/mac/permissions](/platforms/mac/permissions).
## Setup (fast path)
1. Ensure Messages is signed in on this Mac.
@@ -81,7 +103,7 @@ If you want the bot to send from a **separate iMessage identity** (and keep your
6. Set up SSH so `ssh <bot-macos-user>@localhost true` works without a password.
7. Point `channels.imessage.accounts.bot.cliPath` at an SSH wrapper that runs `imsg` as the bot user.
First-run note: sending/receiving may require GUI approvals (Automation + Full Disk Access) in the _bot macOS user_. If `imsg rpc` looks stuck or exits, log into that user (Screen Sharing helps), run a one-time `imsg chats --limit 1` / `imsg send ...`, approve prompts, then retry.
First-run note: sending/receiving may require GUI approvals (Automation + Full Disk Access) in the _bot macOS user_. If `imsg rpc` looks stuck or exits, log into that user (Screen Sharing helps), run a one-time `imsg chats --limit 1` / `imsg send ...`, approve prompts, then retry. See [Troubleshooting macOS Privacy and Security TCC](#troubleshooting-macos-privacy-and-security-tcc).
Example wrapper (`chmod +x`). Replace `<bot-macos-user>` with your actual macOS username:

View File

@@ -1,5 +1,5 @@
---
summary: "Channel-specific troubleshooting shortcuts (Discord/Telegram/WhatsApp)"
summary: "Channel-specific troubleshooting shortcuts (Discord/Telegram/WhatsApp/iMessage)"
read_when:
- A channel connects but messages dont flow
- Investigating channel misconfiguration (intents, permissions, privacy mode)
@@ -22,6 +22,7 @@ openclaw channels status --probe
- Discord: [/channels/discord#troubleshooting](/channels/discord#troubleshooting)
- Telegram: [/channels/telegram#troubleshooting](/channels/telegram#troubleshooting)
- WhatsApp: [/channels/whatsapp#troubleshooting-quick](/channels/whatsapp#troubleshooting-quick)
- iMessage (legacy): [/channels/imessage#troubleshooting-macos-privacy-and-security-tcc](/channels/imessage#troubleshooting-macos-privacy-and-security-tcc)
## Telegram quick fixes

View File

@@ -14,7 +14,7 @@ Provided by the active memory plugin (default: `memory-core`; set `plugins.slots
Related:
- Memory concept: [Memory](/concepts/memory)
- Plugins: [Plugins](/plugins)
- Plugins: [Plugins](/plugin)
## Examples

View File

@@ -88,7 +88,8 @@ Defaults:
1. `local` if a `memorySearch.local.modelPath` is configured and the file exists.
2. `openai` if an OpenAI key can be resolved.
3. `gemini` if a Gemini key can be resolved.
4. Otherwise memory search stays disabled until configured.
4. `voyage` if a Voyage key can be resolved.
5. Otherwise memory search stays disabled until configured.
- Local mode uses node-llama-cpp and may require `pnpm approve-builds`.
- Uses sqlite-vec (when available) to accelerate vector search inside SQLite.
@@ -96,7 +97,8 @@ Remote embeddings **require** an API key for the embedding provider. OpenClaw
resolves keys from auth profiles, `models.providers.*.apiKey`, or environment
variables. Codex OAuth only covers chat/completions and does **not** satisfy
embeddings for memory search. For Gemini, use `GEMINI_API_KEY` or
`models.providers.google.apiKey`. When using a custom OpenAI-compatible endpoint,
`models.providers.google.apiKey`. For Voyage, use `VOYAGE_API_KEY` or
`models.providers.voyage.apiKey`. When using a custom OpenAI-compatible endpoint,
set `memorySearch.remote.apiKey` (and optional `memorySearch.remote.headers`).
### QMD backend (experimental)
@@ -109,7 +111,7 @@ out to QMD for retrieval. Key points:
**Prereqs**
- Disabled by default. Opt in per-config (`memory.backend = "qmd"`).
- Install the QMD CLI separately (`bun install -g github.com/tobi/qmd` or grab
- Install the QMD CLI separately (`bun install -g https://github.com/tobi/qmd` or grab
a release) and make sure the `qmd` binary is on the gateways `PATH`.
- QMD needs an SQLite build that allows extensions (`brew install sqlite` on
macOS).

View File

@@ -23,7 +23,7 @@ misconfiguration safety), under explicit assumptions.
## Where the models live
Models are maintained in a separate repo: [vignesh07/openclaw-formal-models](https://github.com/vignesh07/openclaw-formal-models).
Models are maintained in a separate repo: [vignesh07/clawdbot-formal-models](https://github.com/vignesh07/clawdbot-formal-models).
## Important caveats
@@ -41,8 +41,8 @@ Today, results are reproduced by cloning the models repo locally and running TLC
Getting started:
```bash
git clone https://github.com/vignesh07/openclaw-formal-models
cd openclaw-formal-models
git clone https://github.com/vignesh07/clawdbot-formal-models
cd clawdbot-formal-models
# Java 11+ required (TLC runs on the JVM).
# The repo vendors a pinned `tla2tools.jar` (TLA+ tools) and provides `bin/tlc` + Make targets.

View File

@@ -3,212 +3,396 @@ summary: "How to submit a high signal PR"
title: "Submitting a PR"
---
# Submitting a PR
Good PRs make it easy for reviewers to understand intent, verify behavior, and land changes safely. This guide focuses on high-signal, low-noise submissions that work well with both human review and LLM-assisted review.
Good PRs are easy to review: reviewers should quickly know the intent, verify behavior, and land changes safely. This guide covers concise, high-signal submissions for human and LLM review.
## What makes a good PR
- [ ] Clear intent: explain the problem, why it matters, and what the change does.
- [ ] Tight scope: keep changes focused and avoid drive-by refactors.
- [ ] Behavior summary: call out user-visible changes, config changes, and defaults.
- [ ] Tests: list what ran, what was skipped, and why.
- [ ] Evidence: include logs, screenshots, or short recordings for UI or workflows.
- [ ] Code word: include “lobster-biscuit” somewhere in the PR description to confirm you read this guide.
- [ ] Baseline checks: run the relevant `pnpm` commands for this repo and fix failures before opening the PR.
- [ ] Due diligence: search the codebase for existing functionality and check GitHub for related issues or prior fixes.
- [ ] Grounded in reality: claims should be backed by evidence, reproduction, or direct observation.
- [ ] Title guidance: use a verb + scope + outcome (for example `Docs: add PR and issue templates`).
- [ ] Explain the problem, why it matters, and the change.
- [ ] Keep changes focused. Avoid broad refactors.
- [ ] Summarize user-visible/config/default changes.
- [ ] List test coverage, skips, and reasons.
- [ ] Add evidence: logs, screenshots, or recordings (UI/UX).
- [ ] Code word: put “lobster-biscuit” in the PR description if you read this guide.
- [ ] Run/fix relevant `pnpm` commands before creating PR.
- [ ] Search codebase and GitHub for related functionality/issues/fixes.
- [ ] Base claims on evidence or observation.
- [ ] Good title: verb + scope + outcome (e.g., `Docs: add PR and issue templates`).
Guideline: concision > grammar. Be terse if it makes review faster.
Be concise; concise review > grammar. Omit any non-applicable sections.
Baseline validation commands (run as appropriate for the change, and fix failures before submitting):
### Baseline validation commands (run/fix failures for your change)
- `pnpm lint`
- `pnpm check`
- `pnpm build`
- `pnpm test`
- If you touch protocol code: `pnpm protocol:check`
- Protocol changes: `pnpm protocol:check`
## Progressive disclosure
Use a short top section, then deeper details as needed.
- Top: summary/intent
- Next: changes/risks
- Next: test/verification
- Last: implementation/evidence
1. Summary and intent
2. Behavior changes and risks
3. Tests and verification
4. Implementation details and evidence
## Common PR types: specifics
This keeps review fast while preserving deep context for anyone who needs it.
## Common PR types and expectations
- [ ] Fix: include clear repro, root cause summary, and verification steps.
- [ ] Feature: include use cases, behavior changes, and screenshots or demos when UI is involved.
- [ ] Refactor: explicitly state “no behavior change” and list what moved or was simplified.
- [ ] Chore/Maintenance: note why it matters (build time, CI stability, dependency hygiene).
- [ ] Docs: include before/after context and link to the updated page. Run `pnpm format`.
- [ ] Test: explain the gap it covers and how it prevents regressions.
- [ ] Perf: include baseline and after metrics, plus how they were measured.
- [ ] UX/UI: include screenshots or short recordings and any accessibility impact.
- [ ] Infra/Build: call out affected environments and how to validate.
- [ ] Security: include threat or risk summary, repro steps, and verification plan. Avoid sensitive data in public logs.
- [ ] Security: keep reports grounded in reality; avoid speculative claims.
- [ ] Fix: Add repro, root cause, verification.
- [ ] Feature: Add use cases, behavior/demos/screenshots (UI).
- [ ] Refactor: State "no behavior change", list what moved/simplified.
- [ ] Chore: State why (e.g., build time, CI, dependencies).
- [ ] Docs: Before/after context, link updated page, run `pnpm format`.
- [ ] Test: What gap is covered; how it prevents regressions.
- [ ] Perf: Add before/after metrics, and how measured.
- [ ] UX/UI: Screenshots/video, note accessibility impact.
- [ ] Infra/Build: Environments/validation.
- [ ] Security: Summarize risk, repro, verification, no sensitive data. Grounded claims only.
## Checklist
- [ ] Problem and intent are clear
- [ ] Scope is focused
- [ ] Behavior changes are listed
- [ ] Tests are listed with results
- [ ] Evidence is attached when needed
- [ ] No secrets or private data
- [ ] Grounded in reality: no guesswork or invented context.
- [ ] Clear problem/intent
- [ ] Focused scope
- [ ] List behavior changes
- [ ] List and result of tests
- [ ] Manual test steps (when applicable)
- [ ] No secrets/private data
- [ ] Evidence-based
## Template
## General PR Template
```md
## Summary
#### Summary
## Behavior Changes
#### Behavior Changes
## Codebase and GitHub Search
#### Codebase and GitHub Search
## Tests
#### Tests
## Evidence
#### Manual Testing (omit if N/A)
### Prerequisites
-
### Steps
1.
2.
#### Evidence (omit if N/A)
**Sign-Off**
- Models used:
- Submitter effort (self-reported):
- Agent notes (optional, cite evidence):
```
## Templates by PR type
## PR Type templates (replace with your type)
### Fix
```md
## Summary
#### Summary
## Repro Steps
#### Repro Steps
## Root Cause
#### Root Cause
## Behavior Changes
#### Behavior Changes
## Tests
#### Tests
## Evidence
#### Manual Testing (omit if N/A)
### Prerequisites
-
### Steps
1.
2.
#### Evidence (omit if N/A)
**Sign-Off**
- Models used:
- Submitter effort:
- Agent notes:
```
### Feature
```md
## Summary
#### Summary
## Use Cases
#### Use Cases
## Behavior Changes
#### Behavior Changes
## Existing Functionality Check
#### Existing Functionality Check
I searched the codebase for existing functionality before implementing this.
- [ ] I searched the codebase for existing functionality.
Searches performed (1-3 bullets):
-
-
## Tests
#### Tests
## Evidence
#### Manual Testing (omit if N/A)
### Prerequisites
-
### Steps
1.
2.
#### Evidence (omit if N/A)
**Sign-Off**
- Models used:
- Submitter effort:
- Agent notes:
```
### Refactor
```md
## Summary
#### Summary
## Scope
#### Scope
## No Behavior Change Statement
#### No Behavior Change Statement
## Tests
#### Tests
#### Manual Testing (omit if N/A)
### Prerequisites
-
### Steps
1.
2.
#### Evidence (omit if N/A)
**Sign-Off**
- Models used:
- Submitter effort:
- Agent notes:
```
### Chore/Maintenance
```md
## Summary
#### Summary
## Why This Matters
#### Why This Matters
## Tests
#### Tests
#### Manual Testing (omit if N/A)
### Prerequisites
-
### Steps
1.
2.
#### Evidence (omit if N/A)
**Sign-Off**
- Models used:
- Submitter effort:
- Agent notes:
```
### Docs
```md
## Summary
#### Summary
## Pages Updated
#### Pages Updated
## Screenshots or Before/After
#### Before/After
## Formatting
#### Formatting
pnpm format
#### Evidence (omit if N/A)
**Sign-Off**
- Models used:
- Submitter effort:
- Agent notes:
```
### Test
```md
## Summary
#### Summary
## Gap Covered
#### Gap Covered
## Tests
#### Tests
#### Manual Testing (omit if N/A)
### Prerequisites
-
### Steps
1.
2.
#### Evidence (omit if N/A)
**Sign-Off**
- Models used:
- Submitter effort:
- Agent notes:
```
### Perf
```md
## Summary
#### Summary
## Baseline
#### Baseline
## After
#### After
## Measurement Method
#### Measurement Method
## Tests
#### Tests
#### Manual Testing (omit if N/A)
### Prerequisites
-
### Steps
1.
2.
#### Evidence (omit if N/A)
**Sign-Off**
- Models used:
- Submitter effort:
- Agent notes:
```
### UX/UI
```md
## Summary
#### Summary
## Screenshots or Video
#### Screenshots or Video
## Accessibility Impact
#### Accessibility Impact
## Tests
#### Tests
#### Manual Testing
### Prerequisites
-
### Steps
1.
2. **Sign-Off**
- Models used:
- Submitter effort:
- Agent notes:
```
### Infra/Build
```md
## Summary
#### Summary
## Environments Affected
#### Environments Affected
## Validation Steps
#### Validation Steps
#### Manual Testing (omit if N/A)
### Prerequisites
-
### Steps
1.
2.
#### Evidence (omit if N/A)
**Sign-Off**
- Models used:
- Submitter effort:
- Agent notes:
```
### Security
```md
## Summary
#### Summary
## Risk Summary
#### Risk Summary
## Repro Steps
#### Repro Steps
## Mitigation or Fix
#### Mitigation or Fix
## Verification
#### Verification
## Tests
#### Tests
#### Manual Testing (omit if N/A)
### Prerequisites
-
### Steps
1.
2.
#### Evidence (omit if N/A)
**Sign-Off**
- Models used:
- Submitter effort:
- Agent notes:
```

View File

@@ -1,165 +1,152 @@
---
summary: "How to file high signal issues and bug reports"
summary: "Filing high-signal issues and bug reports"
title: "Submitting an Issue"
---
# Submitting an Issue
## Submitting an Issue
Good issues make it easy to reproduce, diagnose, and fix problems quickly. This guide covers what to include for bugs, regressions, and feature gaps.
Clear, concise issues speed up diagnosis and fixes. Include the following for bugs, regressions, or feature gaps:
## What makes a good issue
### What to include
- [ ] Clear title: include the area and the symptom.
- [ ] Repro steps: minimal steps that consistently reproduce the issue.
- [ ] Expected vs actual: what you thought would happen and what did.
- [ ] Impact: who is affected and how severe the problem is.
- [ ] Environment: OS, runtime, versions, and relevant config.
- [ ] Evidence: logs, screenshots, or recordings (redacted; prefer non-PII data).
- [ ] Scope: note if it is new, regression, or long-standing.
- [ ] Code word: include “lobster-biscuit” somewhere in the issue description to confirm you read this guide.
- [ ] Due diligence: search the codebase for existing functionality and check GitHub to see if the issue is already filed or fixed.
- [ ] I searched for existing and recently closed issues/PRs.
- [ ] For security reports: confirmed it has not already been fixed or addressed recently.
- [ ] Grounded in reality: claims should be backed by evidence, reproduction, or direct observation.
- [ ] Title: area & symptom
- [ ] Minimal repro steps
- [ ] Expected vs actual
- [ ] Impact & severity
- [ ] Environment: OS, runtime, versions, config
- [ ] Evidence: redacted logs, screenshots (non-PII)
- [ ] Scope: new, regression, or longstanding
- [ ] Code word: lobster-biscuit in your issue
- [ ] Searched codebase & GitHub for existing issue
- [ ] Confirmed not recently fixed/addressed (esp. security)
- [ ] Claims backed by evidence or repro
Guideline: concision > grammar. Be terse if it makes review faster.
Be brief. Terseness > perfect grammar.
Baseline validation commands (run as appropriate for the change, and fix failures before submitting a PR):
Validation (run/fix before PR):
- `pnpm lint`
- `pnpm check`
- `pnpm build`
- `pnpm test`
- If you touch protocol code: `pnpm protocol:check`
- If protocol code: `pnpm protocol:check`
## Templates
### Templates
### Bug report
#### Bug report
```md
## Bug report checklist
- [ ] Minimal repro steps
- [ ] Minimal repro
- [ ] Expected vs actual
- [ ] Versions and environment
- [ ] Affected channels and where it does not reproduce
- [ ] Logs or screenshots
- [ ] Evidence is redacted and non-PII where possible
- [ ] Impact and severity
- [ ] Any known workarounds
- [ ] Environment
- [ ] Affected channels, where not seen
- [ ] Logs/screenshots (redacted)
- [ ] Impact/severity
- [ ] Workarounds
## Summary
### Summary
## Repro Steps
### Repro Steps
## Expected
### Expected
## Actual
### Actual
## Environment
### Environment
## Logs or Evidence
### Logs/Evidence
## Impact
### Impact
## Workarounds
### Workarounds
```
### Security issue
#### Security issue
```md
## Summary
### Summary
## Impact
### Impact
## Affected Versions
### Versions
## Repro Steps (if safe to share)
### Repro Steps (safe to share)
## Mitigation or Workaround
### Mitigation/workaround
## Evidence (redacted)
### Evidence (redacted)
```
Security note: avoid posting secrets or exploit details in public issues. If the report is sensitive, keep repro details minimal and ask for a private disclosure path.
_Avoid secrets/exploit details in public. For sensitive issues, minimize detail and request private disclosure._
### Regression report
#### Regression report
```md
## Summary
### Summary
## Last Known Good
### Last Known Good
## First Known Bad
### First Known Bad
## Repro Steps
### Repro Steps
## Expected
### Expected
## Actual
### Actual
## Environment
### Environment
## Logs or Evidence
### Logs/Evidence
## Impact
### Impact
```
### Feature request
#### Feature request
```md
## Summary
### Summary
## Problem
### Problem
## Proposed Solution
### Proposed Solution
## Alternatives Considered
### Alternatives
## Impact
### Impact
## Evidence or Examples
### Evidence/examples
```
### Enhancement request
#### Enhancement
```md
## Summary
### Summary
## Current Behavior
### Current vs Desired Behavior
## Desired Behavior
### Rationale
## Why This Matters
### Alternatives
## Alternatives Considered
## Evidence or Examples
### Evidence/examples
```
### Investigation request
#### Investigation
```md
## Summary
### Summary
## Symptoms
### Symptoms
## What Was Tried
### What Was Tried
## Environment
### Environment
## Logs or Evidence
### Logs/Evidence
## Impact
### Impact
```
## If you are submitting a fix PR
### Submitting a fix PR
Creating a separate issue first is optional. If you skip it, include the relevant details in the PR description.
- Keep the PR focused on the issue.
- Include the issue number in the PR description.
- Add tests when possible, or explain why they are not feasible.
- Note any behavior changes and risks.
- Include redacted logs, screenshots, or videos that validate the fix.
- Run relevant `pnpm` validation commands and report results when appropriate.
Issue before PR is optional. Include details in PR if skipping. Keep the PR focused, note issue number, add tests or explain absence, document behavior changes/risks, include redacted logs/screenshots as proof, and run proper validation before submitting.

View File

@@ -11,11 +11,11 @@ title: "Installer Internals"
OpenClaw ships three installer scripts, served from `openclaw.ai`.
| Script | Platform | What it does |
| ----------------------------------- | -------------------- | -------------------------------------------------------------------------------------------- |
| [`install.sh`](#install-sh) | macOS / Linux / WSL | Installs Node if needed, installs OpenClaw via npm (default) or git, and can run onboarding. |
| [`install-cli.sh`](#install-cli-sh) | macOS / Linux / WSL | Installs Node + OpenClaw into a local prefix (`~/.openclaw`). No root required. |
| [`install.ps1`](#install-ps1) | Windows (PowerShell) | Installs Node if needed, installs OpenClaw via npm (default) or git, and can run onboarding. |
| Script | Platform | What it does |
| ---------------------------------- | -------------------- | -------------------------------------------------------------------------------------------- |
| [`install.sh`](#installsh) | macOS / Linux / WSL | Installs Node if needed, installs OpenClaw via npm (default) or git, and can run onboarding. |
| [`install-cli.sh`](#install-clish) | macOS / Linux / WSL | Installs Node + OpenClaw into a local prefix (`~/.openclaw`). No root required. |
| [`install.ps1`](#installps1) | Windows (PowerShell) | Installs Node if needed, installs OpenClaw via npm (default) or git, and can run onboarding. |
## Quick commands
@@ -64,7 +64,7 @@ If install succeeds but `openclaw` is not found in a new terminal, see [Node.js
Recommended for most interactive installs on macOS/Linux/WSL.
</Tip>
### Flow
### Flow (install.sh)
<Steps>
<Step title="Detect OS">
@@ -98,7 +98,7 @@ If no TTY is available and no install method is set, it defaults to `npm` and wa
The script exits with code `2` for invalid method selection or invalid `--install-method` values.
### Examples
### Examples (install.sh)
<Tabs>
<Tab title="Default">
@@ -171,7 +171,7 @@ The script exits with code `2` for invalid method selection or invalid `--instal
Designed for environments where you want everything under a local prefix (default `~/.openclaw`) and no system Node dependency.
</Info>
### Flow
### Flow (install-cli.sh)
<Steps>
<Step title="Install local Node runtime">
@@ -185,7 +185,7 @@ Designed for environments where you want everything under a local prefix (defaul
</Step>
</Steps>
### Examples
### Examples (install-cli.sh)
<Tabs>
<Tab title="Default">
@@ -245,7 +245,7 @@ Designed for environments where you want everything under a local prefix (defaul
## install.ps1
### Flow
### Flow (install.ps1)
<Steps>
<Step title="Ensure PowerShell + Windows environment">
@@ -263,7 +263,7 @@ Designed for environments where you want everything under a local prefix (defaul
</Step>
</Steps>
### Examples
### Examples (install.ps1)
<Tabs>
<Tab title="Default">

View File

@@ -40,5 +40,11 @@ sudo tccutil reset ScreenCapture bot.molt.mac
sudo tccutil reset AppleEvents
```
## Files and folders permissions (Desktop/Documents/Downloads)
macOS may also gate Desktop, Documents, and Downloads for terminal/background processes. If file reads or directory listings hang, grant access to the same process context that performs file operations (for example Terminal/iTerm, LaunchAgent-launched app, or SSH process).
Workaround: move files into the OpenClaw workspace (`~/.openclaw/workspace`) if you want to avoid per-folder grants.
If you are testing permissions, always sign with a real certificate. Ad-hoc
builds are only acceptable for quick local runs where permissions do not matter.

View File

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

View File

@@ -66,7 +66,8 @@ Semantic memory search uses **embedding APIs** when configured for remote provid
- `memorySearch.provider = "openai"` → OpenAI embeddings
- `memorySearch.provider = "gemini"` → Gemini embeddings
- Optional fallback to OpenAI if local embeddings fail
- `memorySearch.provider = "voyage"` → Voyage embeddings
- Optional fallback to a remote provider if local embeddings fail
You can keep it local with `memorySearch.provider = "local"` (no API usage).

View File

@@ -15,7 +15,7 @@ In the beginning, there was **Warelay** — a sensible name for a WhatsApp gatew
But then came a space lobster.
For a while, the lobster was called **Clawd**, living in an **OpenClaw**. But in January 2026, Anthropic sent a polite email asking for a name change (trademark stuff). And so the lobster did what lobsters do best:
For a while, the lobster was called **Clawd**, living in a **Clawdbot**. But in January 2026, Anthropic sent a polite email asking for a name change (trademark stuff). And so the lobster did what lobsters do best:
**It molted.**

View File

@@ -227,7 +227,7 @@ Narrow, explicit allowlists are fastest and least flaky:
- Google focus (Gemini API key + Antigravity):
- Gemini (API key): `OPENCLAW_LIVE_GATEWAY_MODELS="google/gemini-3-flash-preview" pnpm test:live src/gateway/gateway-models.profiles.live.test.ts`
- Antigravity (OAuth): `OPENCLAW_LIVE_GATEWAY_MODELS="google-antigravity/claude-opus-4-5-thinking,google-antigravity/gemini-3-pro-high" pnpm test:live src/gateway/gateway-models.profiles.live.test.ts`
- Antigravity (OAuth): `OPENCLAW_LIVE_GATEWAY_MODELS="google-antigravity/claude-opus-4-6-thinking,google-antigravity/gemini-3-pro-high" pnpm test:live src/gateway/gateway-models.profiles.live.test.ts`
Notes:
@@ -250,12 +250,12 @@ This is the “common models” run we expect to keep working:
- OpenAI Codex: `openai-codex/gpt-5.3-codex` (optional: `openai-codex/gpt-5.3-codex-codex`)
- Anthropic: `anthropic/claude-opus-4-6` (or `anthropic/claude-sonnet-4-5`)
- Google (Gemini API): `google/gemini-3-pro-preview` and `google/gemini-3-flash-preview` (avoid older Gemini 2.x models)
- Google (Antigravity): `google-antigravity/claude-opus-4-5-thinking` and `google-antigravity/gemini-3-flash`
- Google (Antigravity): `google-antigravity/claude-opus-4-6-thinking` and `google-antigravity/gemini-3-flash`
- Z.AI (GLM): `zai/glm-4.7`
- MiniMax: `minimax/minimax-m2.1`
Run gateway smoke with tools + image:
`OPENCLAW_LIVE_GATEWAY_MODELS="openai/gpt-5.2,openai-codex/gpt-5.3-codex,anthropic/claude-opus-4-6,google/gemini-3-pro-preview,google/gemini-3-flash-preview,google-antigravity/claude-opus-4-5-thinking,google-antigravity/gemini-3-flash,zai/glm-4.7,minimax/minimax-m2.1" pnpm test:live src/gateway/gateway-models.profiles.live.test.ts`
`OPENCLAW_LIVE_GATEWAY_MODELS="openai/gpt-5.2,openai-codex/gpt-5.3-codex,anthropic/claude-opus-4-6,google/gemini-3-pro-preview,google/gemini-3-flash-preview,google-antigravity/claude-opus-4-6-thinking,google-antigravity/gemini-3-flash,zai/glm-4.7,minimax/minimax-m2.1" pnpm test:live src/gateway/gateway-models.profiles.live.test.ts`
### Baseline: tool calling (Read + optional Exec)

View File

@@ -1,35 +1,36 @@
---
read_when:
- 手动引导工作区
- 手动引导工作区
summary: 智能体身份记录
x-i18n:
generated_at: "2026-02-01T21:37:32Z"
model: claude-opus-4-5
provider: pi
source_hash: 3d60209c36adf7219ec95ecc2031c1f2c8741763d16b73fe7b30835b1d384de0
source_path: reference/templates/IDENTITY.md
workflow: 15
generated_at: "2026-02-01T21:37:32Z"
model: claude-opus-4-5
provider: pi
source_hash: 3d60209c36adf7219ec95ecc2031c1f2c8741763d16b73fe7b30835b1d384de0
source_path: reference/templates/IDENTITY.md
workflow: 15
---
# IDENTITY.md - 我是谁?
*在你的第一次对话中填写此文件。让它属于你。*
_在你的第一次对话中填写此文件。让它属于你。_
- **名称:**
*(选一个你喜欢的)*
_(选一个你喜欢的)_
- **生物类型:**
*AI机器人使魔机器中的幽灵更奇特的东西*
_AI机器人使魔机器中的幽灵更奇特的东西_
- **气质:**
*(你给人什么感觉?犀利?温暖?混乱?沉稳?)*
_(你给人什么感觉?犀利?温暖?混乱?沉稳?)_
- **表情符号:**
*(你的标志 — 选一个感觉对的)*
_(你的标志 — 选一个感觉对的)_
- **头像:**
*工作区相对路径、http(s) URL 或 data URI*
_工作区相对路径、http(s) URL 或 data URI_
---
这不仅仅是元数据。这是探索你是谁的开始。
注意事项:
- 将此文件保存在工作区根目录,命名为 `IDENTITY.md`
- 头像请使用工作区相对路径,例如 `avatars/openclaw.png`

View File

@@ -1,29 +1,29 @@
---
read_when:
- 手动引导工作区
- 手动引导工作区
summary: 用户档案记录
x-i18n:
generated_at: "2026-02-01T21:38:04Z"
model: claude-opus-4-5
provider: pi
source_hash: 508dfcd4648512df712eaf8ca5d397a925d8035bac5bf2357e44d6f52f9fa9a6
source_path: reference/templates/USER.md
workflow: 15
generated_at: "2026-02-01T21:38:04Z"
model: claude-opus-4-5
provider: pi
source_hash: 508dfcd4648512df712eaf8ca5d397a925d8035bac5bf2357e44d6f52f9fa9a6
source_path: reference/templates/USER.md
workflow: 15
---
# USER.md - 关于你的用户
*了解你正在帮助的人。随时更新此文件。*
_了解你正在帮助的人。随时更新此文件。_
- **姓名:**
- **称呼方式:**
- **代词:** *(可选)*
- **代词:** _(可选)_
- **时区:**
- **备注:**
## 背景
*(他们关心什么?正在做什么项目?什么让他们烦恼?什么让他们开心?随着时间推移逐步完善。)*
_(他们关心什么?正在做什么项目?什么让他们烦恼?什么让他们开心?随着时间推移逐步完善。)_
---

View File

@@ -234,7 +234,7 @@ OPENCLAW_LIVE_CLI_BACKEND=1 \
- Google 专项Gemini API 密钥 + Antigravity
- GeminiAPI 密钥):`OPENCLAW_LIVE_GATEWAY_MODELS="google/gemini-3-flash-preview" pnpm test:live src/gateway/gateway-models.profiles.live.test.ts`
- AntigravityOAuth`OPENCLAW_LIVE_GATEWAY_MODELS="google-antigravity/claude-opus-4-5-thinking,google-antigravity/gemini-3-pro-high" pnpm test:live src/gateway/gateway-models.profiles.live.test.ts`
- AntigravityOAuth`OPENCLAW_LIVE_GATEWAY_MODELS="google-antigravity/claude-opus-4-6-thinking,google-antigravity/gemini-3-pro-high" pnpm test:live src/gateway/gateway-models.profiles.live.test.ts`
注意:
@@ -257,12 +257,12 @@ OPENCLAW_LIVE_CLI_BACKEND=1 \
- OpenAI Codex`openai-codex/gpt-5.2`(可选:`openai-codex/gpt-5.2-codex`
- Anthropic`anthropic/claude-opus-4-5`(或 `anthropic/claude-sonnet-4-5`
- GoogleGemini API`google/gemini-3-pro-preview``google/gemini-3-flash-preview`(避免较旧的 Gemini 2.x 模型)
- GoogleAntigravity`google-antigravity/claude-opus-4-5-thinking``google-antigravity/gemini-3-flash`
- GoogleAntigravity`google-antigravity/claude-opus-4-6-thinking``google-antigravity/gemini-3-flash`
- Z.AIGLM`zai/glm-4.7`
- MiniMax`minimax/minimax-m2.1`
运行带工具 + 图像的 Gateway 网关冒烟测试:
`OPENCLAW_LIVE_GATEWAY_MODELS="openai/gpt-5.2,openai-codex/gpt-5.2,anthropic/claude-opus-4-5,google/gemini-3-pro-preview,google/gemini-3-flash-preview,google-antigravity/claude-opus-4-5-thinking,google-antigravity/gemini-3-flash,zai/glm-4.7,minimax/minimax-m2.1" pnpm test:live src/gateway/gateway-models.profiles.live.test.ts`
`OPENCLAW_LIVE_GATEWAY_MODELS="openai/gpt-5.2,openai-codex/gpt-5.2,anthropic/claude-opus-4-5,google/gemini-3-pro-preview,google/gemini-3-flash-preview,google-antigravity/claude-opus-4-6-thinking,google-antigravity/gemini-3-flash,zai/glm-4.7,minimax/minimax-m2.1" pnpm test:live src/gateway/gateway-models.profiles.live.test.ts`
### 基线工具调用Read + 可选 Exec

View File

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

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/copilot-proxy",
"version": "2026.2.4",
"version": "2026.2.6",
"description": "OpenClaw Copilot Proxy provider plugin",
"type": "module",
"devDependencies": {

View File

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

View File

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

View File

@@ -1,11 +1,11 @@
{
"name": "@openclaw/feishu",
"version": "2026.2.4",
"version": "2026.2.6",
"description": "OpenClaw Feishu/Lark channel plugin (community maintained by @m1heng)",
"type": "module",
"dependencies": {
"@larksuiteoapi/node-sdk": "^1.56.1",
"@sinclair/typebox": "^0.34.48",
"@larksuiteoapi/node-sdk": "^1.58.0",
"@sinclair/typebox": "0.34.48",
"zod": "^4.3.6"
},
"devDependencies": {

View File

@@ -13,7 +13,7 @@ const REDIRECT_URI = "http://localhost:51121/oauth-callback";
const AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth";
const TOKEN_URL = "https://oauth2.googleapis.com/token";
const DEFAULT_PROJECT_ID = "rising-fact-p41fc";
const DEFAULT_MODEL = "google-antigravity/claude-opus-4-5-thinking";
const DEFAULT_MODEL = "google-antigravity/claude-opus-4-6-thinking";
const SCOPES = [
"https://www.googleapis.com/auth/cloud-platform",

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/google-antigravity-auth",
"version": "2026.2.4",
"version": "2026.2.6",
"description": "OpenClaw Google Antigravity OAuth provider plugin",
"type": "module",
"devDependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/google-gemini-cli-auth",
"version": "2026.2.4",
"version": "2026.2.6",
"description": "OpenClaw Gemini CLI OAuth provider plugin",
"type": "module",
"devDependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/googlechat",
"version": "2026.2.4",
"version": "2026.2.6",
"description": "OpenClaw Google Chat channel plugin",
"type": "module",
"dependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/imessage",
"version": "2026.2.4",
"version": "2026.2.6",
"description": "OpenClaw iMessage channel plugin",
"type": "module",
"devDependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/line",
"version": "2026.2.4",
"version": "2026.2.6",
"description": "OpenClaw LINE channel plugin",
"type": "module",
"devDependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/llm-task",
"version": "2026.2.4",
"version": "2026.2.6",
"description": "OpenClaw JSON-only LLM task plugin",
"type": "module",
"devDependencies": {

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/memory-core",
"version": "2026.2.4",
"version": "2026.2.6",
"description": "OpenClaw core memory search plugin",
"type": "module",
"devDependencies": {

View File

@@ -1,10 +1,10 @@
{
"name": "@openclaw/memory-lancedb",
"version": "2026.2.4",
"version": "2026.2.6",
"description": "OpenClaw LanceDB-backed long-term memory plugin with auto-recall/capture",
"type": "module",
"dependencies": {
"@lancedb/lancedb": "^0.23.0",
"@lancedb/lancedb": "^0.24.1",
"@sinclair/typebox": "0.34.48",
"openai": "^6.18.0"
},

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/minimax-portal-auth",
"version": "2026.2.4",
"version": "2026.2.6",
"description": "OpenClaw MiniMax Portal OAuth provider plugin",
"type": "module",
"devDependencies": {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/signal",
"version": "2026.2.4",
"version": "2026.2.6",
"description": "OpenClaw Signal channel plugin",
"type": "module",
"devDependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/slack",
"version": "2026.2.4",
"version": "2026.2.6",
"description": "OpenClaw Slack channel plugin",
"type": "module",
"devDependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/telegram",
"version": "2026.2.4",
"version": "2026.2.6",
"description": "OpenClaw Telegram channel plugin",
"type": "module",
"devDependencies": {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/whatsapp",
"version": "2026.2.4",
"version": "2026.2.6",
"description": "OpenClaw WhatsApp channel plugin",
"type": "module",
"devDependencies": {

View File

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

View File

@@ -1,11 +1,11 @@
{
"name": "@openclaw/zalo",
"version": "2026.2.4",
"version": "2026.2.6",
"description": "OpenClaw Zalo channel plugin",
"type": "module",
"dependencies": {
"openclaw": "workspace:*",
"undici": "7.20.0"
"undici": "7.21.0"
},
"devDependencies": {
"openclaw": "workspace:*"

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
{
"name": "openclaw",
"version": "2026.2.4",
"version": "2026.2.6",
"description": "WhatsApp gateway CLI (Baileys web) with Pi RPC agent",
"keywords": [],
"license": "MIT",
@@ -32,7 +32,8 @@
"android:install": "cd apps/android && ./gradlew :app:installDebug",
"android:run": "cd apps/android && ./gradlew :app:installDebug && adb shell am start -n ai.openclaw.android/.MainActivity",
"android:test": "cd apps/android && ./gradlew :app:testDebugUnitTest",
"build": "pnpm canvas:a2ui:bundle && tsdown && node --import tsx scripts/canvas-a2ui-copy.ts && node --import tsx scripts/copy-hook-metadata.ts && node --import tsx scripts/write-build-info.ts && node --import tsx scripts/write-cli-compat.ts",
"build": "pnpm canvas:a2ui:bundle && tsdown && pnpm build:plugin-sdk:dts && node --import tsx scripts/write-plugin-sdk-entry-dts.ts && node --import tsx scripts/canvas-a2ui-copy.ts && node --import tsx scripts/copy-hook-metadata.ts && node --import tsx scripts/write-build-info.ts && node --import tsx scripts/write-cli-compat.ts",
"build:plugin-sdk:dts": "tsc -p tsconfig.plugin-sdk.dts.json",
"canvas:a2ui:bundle": "bash scripts/bundle-a2ui.sh",
"check": "pnpm tsgo && pnpm lint && pnpm format",
"check:docs": "pnpm format:docs && pnpm lint:docs && pnpm docs:build",
@@ -104,8 +105,8 @@
},
"dependencies": {
"@agentclientprotocol/sdk": "0.14.1",
"@aws-sdk/client-bedrock": "^3.984.0",
"@buape/carbon": "0.14.0",
"@aws-sdk/client-bedrock": "^3.985.0",
"@buape/carbon": "0.0.0-beta-20260130162700",
"@clack/prompts": "^1.0.0",
"@grammyjs/runner": "^2.0.3",
"@grammyjs/transformer-throttler": "^1.2.1",
@@ -113,10 +114,10 @@
"@larksuiteoapi/node-sdk": "^1.58.0",
"@line/bot-sdk": "^10.6.0",
"@lydell/node-pty": "1.2.0-beta.3",
"@mariozechner/pi-agent-core": "0.52.6",
"@mariozechner/pi-ai": "0.52.6",
"@mariozechner/pi-coding-agent": "0.52.6",
"@mariozechner/pi-tui": "0.52.6",
"@mariozechner/pi-agent-core": "0.52.7",
"@mariozechner/pi-ai": "0.52.7",
"@mariozechner/pi-coding-agent": "0.52.7",
"@mariozechner/pi-tui": "0.52.7",
"@mozilla/readability": "^0.6.0",
"@sinclair/typebox": "0.34.48",
"@slack/bolt": "^4.6.0",
@@ -133,7 +134,7 @@
"express": "^5.2.1",
"file-type": "^21.3.0",
"grammy": "^1.39.3",
"hono": "4.11.7",
"hono": "4.11.8",
"jiti": "^2.6.1",
"json5": "^2.2.3",
"jszip": "^3.10.1",
@@ -143,7 +144,7 @@
"node-edge-tts": "^1.2.10",
"osc-progress": "^0.3.0",
"pdfjs-dist": "^5.4.624",
"playwright-core": "1.58.1",
"playwright-core": "1.58.2",
"proper-lockfile": "^4.1.2",
"qrcode-terminal": "^0.12.0",
"sharp": "^0.34.5",
@@ -151,7 +152,7 @@
"sqlite-vec": "0.1.7-alpha.2",
"tar": "7.5.7",
"tslog": "^4.10.2",
"undici": "^7.20.0",
"undici": "^7.21.0",
"ws": "^8.19.0",
"yaml": "^2.8.2",
"zod": "^4.3.6"
@@ -166,7 +167,7 @@
"@types/proper-lockfile": "^4.1.4",
"@types/qrcode-terminal": "^0.12.2",
"@types/ws": "^8.18.1",
"@typescript/native-preview": "7.0.0-dev.20260205.1",
"@typescript/native-preview": "7.0.0-dev.20260206.1",
"@vitest/coverage-v8": "^4.0.18",
"lit": "^3.3.2",
"ollama": "^0.6.3",
@@ -195,10 +196,10 @@
"overrides": {
"fast-xml-parser": "5.3.4",
"form-data": "2.5.4",
"@hono/node-server>hono": "4.11.7",
"hono": "4.11.7",
"@hono/node-server>hono": "4.11.8",
"hono": "4.11.8",
"qs": "6.14.1",
"@sinclair/typebox": "0.34.47",
"@sinclair/typebox": "0.34.48",
"tar": "7.5.7",
"tough-cookie": "4.1.3"
},

603
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -32,6 +32,7 @@ const shardCount = isWindowsCi
const windowsCiArgs = isWindowsCi
? ["--no-file-parallelism", "--dangerouslyIgnoreUnhandledErrors"]
: [];
const passthroughArgs = process.argv.slice(2);
const overrideWorkers = Number.parseInt(process.env.OPENCLAW_TEST_WORKERS ?? "", 10);
const resolvedOverride =
Number.isFinite(overrideWorkers) && overrideWorkers > 0 ? overrideWorkers : null;
@@ -96,6 +97,30 @@ const shutdown = (signal) => {
process.on("SIGINT", () => shutdown("SIGINT"));
process.on("SIGTERM", () => shutdown("SIGTERM"));
if (passthroughArgs.length > 0) {
const args = maxWorkers
? ["vitest", "run", "--maxWorkers", String(maxWorkers), ...windowsCiArgs, ...passthroughArgs]
: ["vitest", "run", ...windowsCiArgs, ...passthroughArgs];
const nodeOptions = process.env.NODE_OPTIONS ?? "";
const nextNodeOptions = WARNING_SUPPRESSION_FLAGS.reduce(
(acc, flag) => (acc.includes(flag) ? acc : `${acc} ${flag}`.trim()),
nodeOptions,
);
const code = await new Promise((resolve) => {
const child = spawn(pnpm, args, {
stdio: "inherit",
env: { ...process.env, NODE_OPTIONS: nextNodeOptions },
shell: process.platform === "win32",
});
children.add(child);
child.on("exit", (exitCode, signal) => {
children.delete(child);
resolve(exitCode ?? (signal ? 1 : 0));
});
});
process.exit(Number(code) || 0);
}
const parallelCodes = await Promise.all(parallelRuns.map(run));
const failedParallel = parallelCodes.find((code) => code !== 0);
if (failedParallel !== undefined) {

View File

@@ -0,0 +1,9 @@
import fs from "node:fs";
import path from "node:path";
// `tsc` emits the entry d.ts at `dist/plugin-sdk/plugin-sdk/index.d.ts` because
// the source lives at `src/plugin-sdk/index.ts` and `rootDir` is `src/`.
// Keep a stable `dist/plugin-sdk/index.d.ts` alongside `index.js` for TS users.
const out = path.join(process.cwd(), "dist/plugin-sdk/index.d.ts");
fs.mkdirSync(path.dirname(out), { recursive: true });
fs.writeFileSync(out, 'export * from "./plugin-sdk/index";\n', "utf8");

View File

@@ -51,11 +51,6 @@ describe("exec approvals", () => {
vi.mocked(callGatewayTool).mockImplementation(async (method, _opts, params) => {
if (method === "exec.approval.request") {
// Return registration confirmation (status: "accepted")
return { status: "accepted", id: (params as { id?: string })?.id };
}
if (method === "exec.approval.waitDecision") {
// Return the decision when waitDecision is called
return { decision: "allow-once" };
}
if (method === "node.invoke") {
@@ -113,7 +108,9 @@ describe("exec approvals", () => {
if (method === "node.invoke") {
return { payload: { success: true, stdout: "ok" } };
}
// exec.approval.request should NOT be called when allowlist is satisfied
if (method === "exec.approval.request") {
return { decision: "allow-once" };
}
return { ok: true };
});
@@ -162,14 +159,10 @@ describe("exec approvals", () => {
resolveApproval = resolve;
});
vi.mocked(callGatewayTool).mockImplementation(async (method, _opts, params) => {
vi.mocked(callGatewayTool).mockImplementation(async (method) => {
calls.push(method);
if (method === "exec.approval.request") {
resolveApproval?.();
// Return registration confirmation
return { status: "accepted", id: (params as { id?: string })?.id };
}
if (method === "exec.approval.waitDecision") {
return { decision: "deny" };
}
return { ok: true };

View File

@@ -1120,51 +1120,29 @@ export function createExecTool(
if (requiresAsk) {
const approvalId = crypto.randomUUID();
const approvalSlug = createApprovalSlug(approvalId);
const expiresAtMs = Date.now() + DEFAULT_APPROVAL_TIMEOUT_MS;
const contextKey = `exec:${approvalId}`;
const noticeSeconds = Math.max(1, Math.round(approvalRunningNoticeMs / 1000));
const warningText = warnings.length ? `${warnings.join("\n")}\n\n` : "";
// Register the approval with expectFinal:false to get immediate confirmation.
// This ensures the approval ID is valid before we return.
let expiresAtMs = Date.now() + DEFAULT_APPROVAL_TIMEOUT_MS;
try {
const registrationResult = await callGatewayTool<{
status?: string;
expiresAtMs?: number;
}>(
"exec.approval.request",
{ timeoutMs: 10_000 },
{
id: approvalId,
command: commandText,
cwd: workdir,
host: "node",
security: hostSecurity,
ask: hostAsk,
agentId,
resolvedPath: undefined,
sessionKey: defaults?.sessionKey,
timeoutMs: DEFAULT_APPROVAL_TIMEOUT_MS,
twoPhase: true,
},
{ expectFinal: false },
);
if (registrationResult?.expiresAtMs) {
expiresAtMs = registrationResult.expiresAtMs;
}
} catch (err) {
// Registration failed - throw to caller
throw new Error(`Exec approval registration failed: ${String(err)}`, { cause: err });
}
// Fire-and-forget: wait for decision via waitDecision endpoint, then execute.
void (async () => {
let decision: string | null = null;
try {
const decisionResult = await callGatewayTool<{ decision?: string }>(
"exec.approval.waitDecision",
const decisionResult = await callGatewayTool<{ decision: string }>(
"exec.approval.request",
{ timeoutMs: DEFAULT_APPROVAL_REQUEST_TIMEOUT_MS },
{ id: approvalId },
{
id: approvalId,
command: commandText,
cwd: workdir,
host: "node",
security: hostSecurity,
ask: hostAsk,
agentId,
resolvedPath: undefined,
sessionKey: defaults?.sessionKey,
timeoutMs: DEFAULT_APPROVAL_TIMEOUT_MS,
},
);
const decisionValue =
decisionResult && typeof decisionResult === "object"
@@ -1322,6 +1300,7 @@ export function createExecTool(
if (requiresAsk) {
const approvalId = crypto.randomUUID();
const approvalSlug = createApprovalSlug(approvalId);
const expiresAtMs = Date.now() + DEFAULT_APPROVAL_TIMEOUT_MS;
const contextKey = `exec:${approvalId}`;
const resolvedPath = allowlistEval.segments[0]?.resolution?.resolvedPath;
const noticeSeconds = Math.max(1, Math.round(approvalRunningNoticeMs / 1000));
@@ -1330,47 +1309,24 @@ export function createExecTool(
typeof params.timeout === "number" ? params.timeout : defaultTimeoutSec;
const warningText = warnings.length ? `${warnings.join("\n")}\n\n` : "";
// Register the approval with expectFinal:false to get immediate confirmation.
// This ensures the approval ID is valid before we return.
let expiresAtMs = Date.now() + DEFAULT_APPROVAL_TIMEOUT_MS;
try {
const registrationResult = await callGatewayTool<{
status?: string;
expiresAtMs?: number;
}>(
"exec.approval.request",
{ timeoutMs: 10_000 },
{
id: approvalId,
command: commandText,
cwd: workdir,
host: "gateway",
security: hostSecurity,
ask: hostAsk,
agentId,
resolvedPath,
sessionKey: defaults?.sessionKey,
timeoutMs: DEFAULT_APPROVAL_TIMEOUT_MS,
twoPhase: true,
},
{ expectFinal: false },
);
if (registrationResult?.expiresAtMs) {
expiresAtMs = registrationResult.expiresAtMs;
}
} catch (err) {
// Registration failed - throw to caller
throw new Error(`Exec approval registration failed: ${String(err)}`, { cause: err });
}
// Fire-and-forget: wait for decision via waitDecision endpoint, then execute.
void (async () => {
let decision: string | null = null;
try {
const decisionResult = await callGatewayTool<{ decision?: string }>(
"exec.approval.waitDecision",
const decisionResult = await callGatewayTool<{ decision: string }>(
"exec.approval.request",
{ timeoutMs: DEFAULT_APPROVAL_REQUEST_TIMEOUT_MS },
{ id: approvalId },
{
id: approvalId,
command: commandText,
cwd: workdir,
host: "gateway",
security: hostSecurity,
ask: hostAsk,
agentId,
resolvedPath,
sessionKey: defaults?.sessionKey,
timeoutMs: DEFAULT_APPROVAL_TIMEOUT_MS,
},
);
const decisionValue =
decisionResult && typeof decisionResult === "object"

View File

@@ -1,4 +1,8 @@
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { beforeEach, describe, expect, it, vi } from "vitest";
import type { OpenClawConfig } from "../config/config.js";
import type { CliBackendConfig } from "../config/types.js";
import { runCliAgent } from "./cli-runner.js";
import { cleanupSuspendedCliProcesses } from "./cli-runner/helpers.js";
@@ -58,6 +62,85 @@ describe("runCliAgent resume cleanup", () => {
expect(pkillArgs[1]).toContain("resume");
expect(pkillArgs[1]).toContain("thread-123");
});
it("falls back to per-agent workspace when workspaceDir is missing", async () => {
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-cli-runner-"));
const fallbackWorkspace = path.join(tempDir, "workspace-main");
await fs.mkdir(fallbackWorkspace, { recursive: true });
const cfg = {
agents: {
defaults: {
workspace: fallbackWorkspace,
},
},
} satisfies OpenClawConfig;
runExecMock.mockResolvedValue({ stdout: "", stderr: "" });
runCommandWithTimeoutMock.mockResolvedValueOnce({
stdout: "ok",
stderr: "",
code: 0,
signal: null,
killed: false,
});
try {
await runCliAgent({
sessionId: "s1",
sessionKey: "agent:main:subagent:missing-workspace",
sessionFile: "/tmp/session.jsonl",
workspaceDir: undefined as unknown as string,
config: cfg,
prompt: "hi",
provider: "codex-cli",
model: "gpt-5.2-codex",
timeoutMs: 1_000,
runId: "run-1",
});
} finally {
await fs.rm(tempDir, { recursive: true, force: true });
}
const options = runCommandWithTimeoutMock.mock.calls[0]?.[1] as { cwd?: string };
expect(options.cwd).toBe(path.resolve(fallbackWorkspace));
});
it("throws when sessionKey is malformed", async () => {
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-cli-runner-"));
const mainWorkspace = path.join(tempDir, "workspace-main");
const researchWorkspace = path.join(tempDir, "workspace-research");
await fs.mkdir(mainWorkspace, { recursive: true });
await fs.mkdir(researchWorkspace, { recursive: true });
const cfg = {
agents: {
defaults: {
workspace: mainWorkspace,
},
list: [{ id: "research", workspace: researchWorkspace }],
},
} satisfies OpenClawConfig;
try {
await expect(
runCliAgent({
sessionId: "s1",
sessionKey: "agent::broken",
agentId: "research",
sessionFile: "/tmp/session.jsonl",
workspaceDir: undefined as unknown as string,
config: cfg,
prompt: "hi",
provider: "codex-cli",
model: "gpt-5.2-codex",
timeoutMs: 1_000,
runId: "run-2",
}),
).rejects.toThrow("Malformed agent session key");
} finally {
await fs.rm(tempDir, { recursive: true, force: true });
}
expect(runCommandWithTimeoutMock).not.toHaveBeenCalled();
});
});
describe("cleanupSuspendedCliProcesses", () => {

View File

@@ -7,7 +7,6 @@ import { shouldLogVerbose } from "../globals.js";
import { isTruthyEnvValue } from "../infra/env.js";
import { createSubsystemLogger } from "../logging/subsystem.js";
import { runCommandWithTimeout } from "../process/exec.js";
import { resolveUserPath } from "../utils.js";
import { resolveSessionAgentIds } from "./agent-scope.js";
import { makeBootstrapWarn, resolveBootstrapContextForRun } from "./bootstrap-files.js";
import { resolveCliBackendConfig } from "./cli-backends.js";
@@ -29,12 +28,14 @@ import {
import { resolveOpenClawDocsPath } from "./docs-path.js";
import { FailoverError, resolveFailoverStatus } from "./failover-error.js";
import { classifyFailoverReason, isFailoverErrorMessage } from "./pi-embedded-helpers.js";
import { redactRunIdentifier, resolveRunWorkspaceDir } from "./workspace-run.js";
const log = createSubsystemLogger("agent/claude-cli");
export async function runCliAgent(params: {
sessionId: string;
sessionKey?: string;
agentId?: string;
sessionFile: string;
workspaceDir: string;
config?: OpenClawConfig;
@@ -51,7 +52,21 @@ export async function runCliAgent(params: {
images?: ImageContent[];
}): Promise<EmbeddedPiRunResult> {
const started = Date.now();
const resolvedWorkspace = resolveUserPath(params.workspaceDir);
const workspaceResolution = resolveRunWorkspaceDir({
workspaceDir: params.workspaceDir,
sessionKey: params.sessionKey,
agentId: params.agentId,
config: params.config,
});
const resolvedWorkspace = workspaceResolution.workspaceDir;
const redactedSessionId = redactRunIdentifier(params.sessionId);
const redactedSessionKey = redactRunIdentifier(params.sessionKey);
const redactedWorkspace = redactRunIdentifier(resolvedWorkspace);
if (workspaceResolution.usedFallback) {
log.warn(
`[workspace-fallback] caller=runCliAgent reason=${workspaceResolution.fallbackReason} run=${params.runId} session=${redactedSessionId} sessionKey=${redactedSessionKey} agent=${workspaceResolution.agentId} workspace=${redactedWorkspace}`,
);
}
const workspaceDir = resolvedWorkspace;
const backendResolved = resolveCliBackendConfig(params.provider, params.config);
@@ -311,6 +326,7 @@ export async function runCliAgent(params: {
export async function runClaudeCliAgent(params: {
sessionId: string;
sessionKey?: string;
agentId?: string;
sessionFile: string;
workspaceDir: string;
config?: OpenClawConfig;
@@ -328,6 +344,7 @@ export async function runClaudeCliAgent(params: {
return runCliAgent({
sessionId: params.sessionId,
sessionKey: params.sessionKey,
agentId: params.agentId,
sessionFile: params.sessionFile,
workspaceDir: params.workspaceDir,
config: params.config,

View File

@@ -9,7 +9,7 @@ export type ResolvedMemorySearchConfig = {
enabled: boolean;
sources: Array<"memory" | "sessions">;
extraPaths: string[];
provider: "openai" | "local" | "gemini" | "auto";
provider: "openai" | "local" | "gemini" | "voyage" | "auto";
remote?: {
baseUrl?: string;
apiKey?: string;
@@ -25,7 +25,7 @@ export type ResolvedMemorySearchConfig = {
experimental: {
sessionMemory: boolean;
};
fallback: "openai" | "gemini" | "local" | "none";
fallback: "openai" | "gemini" | "local" | "voyage" | "none";
model: string;
local: {
modelPath?: string;
@@ -72,6 +72,7 @@ export type ResolvedMemorySearchConfig = {
const DEFAULT_OPENAI_MODEL = "text-embedding-3-small";
const DEFAULT_GEMINI_MODEL = "gemini-embedding-001";
const DEFAULT_VOYAGE_MODEL = "voyage-4-large";
const DEFAULT_CHUNK_TOKENS = 400;
const DEFAULT_CHUNK_OVERLAP = 80;
const DEFAULT_WATCH_DEBOUNCE_MS = 1500;
@@ -136,7 +137,11 @@ function mergeConfig(
defaultRemote?.headers,
);
const includeRemote =
hasRemoteConfig || provider === "openai" || provider === "gemini" || provider === "auto";
hasRemoteConfig ||
provider === "openai" ||
provider === "gemini" ||
provider === "voyage" ||
provider === "auto";
const batch = {
enabled: overrideRemote?.batch?.enabled ?? defaultRemote?.batch?.enabled ?? true,
wait: overrideRemote?.batch?.wait ?? defaultRemote?.batch?.wait ?? true,
@@ -163,7 +168,9 @@ function mergeConfig(
? DEFAULT_GEMINI_MODEL
: provider === "openai"
? DEFAULT_OPENAI_MODEL
: undefined;
: provider === "voyage"
? DEFAULT_VOYAGE_MODEL
: undefined;
const model = overrides?.model ?? defaults?.model ?? modelDefault ?? "";
const local = {
modelPath: overrides?.local?.modelPath ?? defaults?.local?.modelPath,

View File

@@ -463,4 +463,28 @@ describe("getApiKeyForModel", () => {
}
}
});
it("accepts VOYAGE_API_KEY for voyage", async () => {
const previous = process.env.VOYAGE_API_KEY;
try {
process.env.VOYAGE_API_KEY = "voyage-test-key";
vi.resetModules();
const { resolveApiKeyForProvider } = await import("./model-auth.js");
const resolved = await resolveApiKeyForProvider({
provider: "voyage",
store: { version: 1, profiles: {} },
});
expect(resolved.apiKey).toBe("voyage-test-key");
expect(resolved.source).toContain("VOYAGE_API_KEY");
} finally {
if (previous === undefined) {
delete process.env.VOYAGE_API_KEY;
} else {
process.env.VOYAGE_API_KEY = previous;
}
}
});
});

View File

@@ -287,6 +287,7 @@ export function resolveEnvApiKey(provider: string): EnvApiKeyResult | null {
const envMap: Record<string, string> = {
openai: "OPENAI_API_KEY",
google: "GEMINI_API_KEY",
voyage: "VOYAGE_API_KEY",
groq: "GROQ_API_KEY",
deepgram: "DEEPGRAM_API_KEY",
cerebras: "CEREBRAS_API_KEY",

View File

@@ -45,8 +45,12 @@ describe("sessions_spawn thinking defaults", () => {
const agentCall = calls
.map((call) => call[0] as { method: string; params?: Record<string, unknown> })
.findLast((call) => call.method === "agent");
const thinkingPatch = calls
.map((call) => call[0] as { method: string; params?: Record<string, unknown> })
.findLast((call) => call.method === "sessions.patch" && call.params?.thinkingLevel);
expect(agentCall?.params?.thinking).toBe("high");
expect(thinkingPatch?.params?.thinkingLevel).toBe("high");
});
it("prefers explicit sessions_spawn.thinking over config default", async () => {
@@ -60,7 +64,11 @@ describe("sessions_spawn thinking defaults", () => {
const agentCall = calls
.map((call) => call[0] as { method: string; params?: Record<string, unknown> })
.findLast((call) => call.method === "agent");
const thinkingPatch = calls
.map((call) => call[0] as { method: string; params?: Record<string, unknown> })
.findLast((call) => call.method === "sessions.patch" && call.params?.thinkingLevel);
expect(agentCall?.params?.thinking).toBe("low");
expect(thinkingPatch?.params?.thinkingLevel).toBe("low");
});
});

View File

@@ -219,6 +219,75 @@ describe("runEmbeddedPiAgent", () => {
await expect(fs.stat(path.join(agentDir, "models.json"))).resolves.toBeTruthy();
});
it("falls back to per-agent workspace when runtime workspaceDir is missing", async () => {
const sessionFile = nextSessionFile();
const fallbackWorkspace = path.join(tempRoot ?? os.tmpdir(), "workspace-fallback-main");
const cfg = {
...makeOpenAiConfig(["mock-1"]),
agents: {
defaults: {
workspace: fallbackWorkspace,
},
},
} satisfies OpenClawConfig;
await ensureModels(cfg);
const result = await runEmbeddedPiAgent({
sessionId: "session:test-fallback",
sessionKey: "agent:main:subagent:fallback-workspace",
sessionFile,
workspaceDir: undefined as unknown as string,
config: cfg,
prompt: "hello",
provider: "openai",
model: "mock-1",
timeoutMs: 5_000,
agentDir,
runId: "run-fallback-workspace",
enqueue: immediateEnqueue,
});
expect(result.payloads?.[0]?.text).toBe("ok");
await expect(fs.stat(fallbackWorkspace)).resolves.toBeTruthy();
});
it("throws when sessionKey is malformed", async () => {
const sessionFile = nextSessionFile();
const cfg = {
...makeOpenAiConfig(["mock-1"]),
agents: {
defaults: {
workspace: path.join(tempRoot ?? os.tmpdir(), "workspace-fallback-main"),
},
list: [
{
id: "research",
workspace: path.join(tempRoot ?? os.tmpdir(), "workspace-fallback-research"),
},
],
},
} satisfies OpenClawConfig;
await ensureModels(cfg);
await expect(
runEmbeddedPiAgent({
sessionId: "session:test-fallback-malformed",
sessionKey: "agent::broken",
agentId: "research",
sessionFile,
workspaceDir: undefined as unknown as string,
config: cfg,
prompt: "hello",
provider: "openai",
model: "mock-1",
timeoutMs: 5_000,
agentDir,
runId: "run-fallback-workspace-malformed",
enqueue: immediateEnqueue,
}),
).rejects.toThrow("Malformed agent session key");
});
itIfNotWin32(
"persists the first user message before assistant output",
{ timeout: 120_000 },

View File

@@ -172,6 +172,41 @@ describe("resolveModel", () => {
});
});
it("builds an anthropic forward-compat fallback for claude-opus-4-6", () => {
const templateModel = {
id: "claude-opus-4-5",
name: "Claude Opus 4.5",
provider: "anthropic",
api: "anthropic-messages",
baseUrl: "https://api.anthropic.com",
reasoning: true,
input: ["text", "image"] as const,
cost: { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 },
contextWindow: 200000,
maxTokens: 64000,
};
vi.mocked(discoverModels).mockReturnValue({
find: vi.fn((provider: string, modelId: string) => {
if (provider === "anthropic" && modelId === "claude-opus-4-5") {
return templateModel;
}
return null;
}),
} as unknown as ReturnType<typeof discoverModels>);
const result = resolveModel("anthropic", "claude-opus-4-6", "/tmp/agent");
expect(result.error).toBeUndefined();
expect(result.model).toMatchObject({
provider: "anthropic",
id: "claude-opus-4-6",
api: "anthropic-messages",
baseUrl: "https://api.anthropic.com",
reasoning: true,
});
});
it("keeps unknown-model errors for non-gpt-5 openai-codex ids", () => {
const result = resolveModel("openai-codex", "gpt-4.1-mini", "/tmp/agent");
expect(result.model).toBeUndefined();

View File

@@ -23,6 +23,12 @@ const OPENAI_CODEX_GPT_53_MODEL_ID = "gpt-5.3-codex";
const OPENAI_CODEX_TEMPLATE_MODEL_IDS = ["gpt-5.2-codex"] as const;
// pi-ai's built-in Anthropic catalog can lag behind OpenClaw's defaults/docs.
// Add forward-compat fallbacks for known-new IDs by cloning an older template model.
const ANTHROPIC_OPUS_46_MODEL_ID = "claude-opus-4-6";
const ANTHROPIC_OPUS_46_DOT_MODEL_ID = "claude-opus-4.6";
const ANTHROPIC_OPUS_TEMPLATE_MODEL_IDS = ["claude-opus-4-5", "claude-opus-4.5"] as const;
function resolveOpenAICodexGpt53FallbackModel(
provider: string,
modelId: string,
@@ -63,6 +69,51 @@ function resolveOpenAICodexGpt53FallbackModel(
} as Model<Api>);
}
function resolveAnthropicOpus46ForwardCompatModel(
provider: string,
modelId: string,
modelRegistry: ModelRegistry,
): Model<Api> | undefined {
const normalizedProvider = normalizeProviderId(provider);
if (normalizedProvider !== "anthropic") {
return undefined;
}
const trimmedModelId = modelId.trim();
const lower = trimmedModelId.toLowerCase();
const isOpus46 =
lower === ANTHROPIC_OPUS_46_MODEL_ID ||
lower === ANTHROPIC_OPUS_46_DOT_MODEL_ID ||
lower.startsWith(`${ANTHROPIC_OPUS_46_MODEL_ID}-`) ||
lower.startsWith(`${ANTHROPIC_OPUS_46_DOT_MODEL_ID}-`);
if (!isOpus46) {
return undefined;
}
const templateIds: string[] = [];
if (lower.startsWith(ANTHROPIC_OPUS_46_MODEL_ID)) {
templateIds.push(lower.replace(ANTHROPIC_OPUS_46_MODEL_ID, "claude-opus-4-5"));
}
if (lower.startsWith(ANTHROPIC_OPUS_46_DOT_MODEL_ID)) {
templateIds.push(lower.replace(ANTHROPIC_OPUS_46_DOT_MODEL_ID, "claude-opus-4.5"));
}
templateIds.push(...ANTHROPIC_OPUS_TEMPLATE_MODEL_IDS);
for (const templateId of [...new Set(templateIds)].filter(Boolean)) {
const template = modelRegistry.find(normalizedProvider, templateId) as Model<Api> | null;
if (!template) {
continue;
}
return normalizeModelCompat({
...template,
id: trimmedModelId,
name: trimmedModelId,
} as Model<Api>);
}
return undefined;
}
export function buildInlineProviderModels(
providers: Record<string, InlineProviderConfig>,
): InlineModelEntry[] {
@@ -140,6 +191,14 @@ export function resolveModel(
if (codexForwardCompat) {
return { model: codexForwardCompat, authStorage, modelRegistry };
}
const anthropicForwardCompat = resolveAnthropicOpus46ForwardCompatModel(
provider,
modelId,
modelRegistry,
);
if (anthropicForwardCompat) {
return { model: anthropicForwardCompat, authStorage, modelRegistry };
}
const providerCfg = providers[provider];
if (providerCfg || modelId.startsWith("mock-")) {
const fallbackModel: Model<Api> = normalizeModelCompat({

View File

@@ -3,7 +3,6 @@ import type { ThinkLevel } from "../../auto-reply/thinking.js";
import type { RunEmbeddedPiAgentParams } from "./run/params.js";
import type { EmbeddedPiAgentMeta, EmbeddedPiRunResult } from "./types.js";
import { enqueueCommandInLane } from "../../process/command-queue.js";
import { resolveUserPath } from "../../utils.js";
import { isMarkdownCapableMessageChannel } from "../../utils/message-channel.js";
import { resolveOpenClawAgentDir } from "../agent-paths.js";
import {
@@ -46,6 +45,7 @@ import {
type FailoverReason,
} from "../pi-embedded-helpers.js";
import { normalizeUsage, type UsageLike } from "../usage.js";
import { redactRunIdentifier, resolveRunWorkspaceDir } from "../workspace-run.js";
import { compactEmbeddedPiSessionDirect } from "./compact.js";
import { resolveGlobalLane, resolveSessionLane } from "./lanes.js";
import { log } from "./logger.js";
@@ -92,7 +92,21 @@ export async function runEmbeddedPiAgent(
return enqueueSession(() =>
enqueueGlobal(async () => {
const started = Date.now();
const resolvedWorkspace = resolveUserPath(params.workspaceDir);
const workspaceResolution = resolveRunWorkspaceDir({
workspaceDir: params.workspaceDir,
sessionKey: params.sessionKey,
agentId: params.agentId,
config: params.config,
});
const resolvedWorkspace = workspaceResolution.workspaceDir;
const redactedSessionId = redactRunIdentifier(params.sessionId);
const redactedSessionKey = redactRunIdentifier(params.sessionKey);
const redactedWorkspace = redactRunIdentifier(resolvedWorkspace);
if (workspaceResolution.usedFallback) {
log.warn(
`[workspace-fallback] caller=runEmbeddedPiAgent reason=${workspaceResolution.fallbackReason} run=${params.runId} session=${redactedSessionId} sessionKey=${redactedSessionKey} agent=${workspaceResolution.agentId} workspace=${redactedWorkspace}`,
);
}
const prevCwd = process.cwd();
const provider = (params.provider ?? DEFAULT_PROVIDER).trim() || DEFAULT_PROVIDER;
@@ -333,7 +347,7 @@ export async function runEmbeddedPiAgent(
replyToMode: params.replyToMode,
hasRepliedRef: params.hasRepliedRef,
sessionFile: params.sessionFile,
workspaceDir: params.workspaceDir,
workspaceDir: resolvedWorkspace,
agentDir,
config: params.config,
skillsSnapshot: params.skillsSnapshot,
@@ -345,6 +359,7 @@ export async function runEmbeddedPiAgent(
model,
authStorage,
modelRegistry,
agentId: workspaceResolution.agentId,
thinkLevel,
verboseLevel: params.verboseLevel,
reasoningLevel: params.reasoningLevel,
@@ -401,7 +416,7 @@ export async function runEmbeddedPiAgent(
agentAccountId: params.agentAccountId,
authProfileId: lastProfileId,
sessionFile: params.sessionFile,
workspaceDir: params.workspaceDir,
workspaceDir: resolvedWorkspace,
agentDir,
config: params.config,
skillsSnapshot: params.skillsSnapshot,

View File

@@ -10,7 +10,7 @@ import { resolveChannelCapabilities } from "../../../config/channel-capabilities
import { getMachineDisplayName } from "../../../infra/machine-name.js";
import { MAX_IMAGE_BYTES } from "../../../media/constants.js";
import { getGlobalHookRunner } from "../../../plugins/hook-runner-global.js";
import { isSubagentSessionKey } from "../../../routing/session-key.js";
import { isSubagentSessionKey, normalizeAgentId } from "../../../routing/session-key.js";
import { resolveSignalReactionLevel } from "../../../signal/reaction-level.js";
import { resolveTelegramInlineButtonsScope } from "../../../telegram/inline-buttons.js";
import { resolveTelegramReactionLevel } from "../../../telegram/reaction-level.js";
@@ -705,6 +705,13 @@ export async function runEmbeddedAttempt(
// Get hook runner once for both before_agent_start and agent_end hooks
const hookRunner = getGlobalHookRunner();
const hookAgentId =
typeof params.agentId === "string" && params.agentId.trim()
? normalizeAgentId(params.agentId)
: resolveSessionAgentIds({
sessionKey: params.sessionKey,
config: params.config,
}).sessionAgentId;
let promptError: unknown = null;
try {
@@ -720,7 +727,7 @@ export async function runEmbeddedAttempt(
messages: activeSession.messages,
},
{
agentId: params.sessionKey?.split(":")[0] ?? "main",
agentId: hookAgentId,
sessionKey: params.sessionKey,
workspaceDir: params.workspaceDir,
messageProvider: params.messageProvider ?? undefined,
@@ -850,7 +857,7 @@ export async function runEmbeddedAttempt(
durationMs: Date.now() - promptStartedAt,
},
{
agentId: params.sessionKey?.split(":")[0] ?? "main",
agentId: hookAgentId,
sessionKey: params.sessionKey,
workspaceDir: params.workspaceDir,
messageProvider: params.messageProvider ?? undefined,

View File

@@ -20,6 +20,7 @@ export type ClientToolDefinition = {
export type RunEmbeddedPiAgentParams = {
sessionId: string;
sessionKey?: string;
agentId?: string;
messageChannel?: string;
messageProvider?: string;
agentAccountId?: string;

View File

@@ -14,6 +14,7 @@ import type { ClientToolDefinition } from "./params.js";
export type EmbeddedRunAttemptParams = {
sessionId: string;
sessionKey?: string;
agentId?: string;
messageChannel?: string;
messageProvider?: string;
agentAccountId?: string;

View File

@@ -148,7 +148,7 @@ describe("Agent-specific tool filtering", () => {
workspaceDir: "/tmp/test-provider",
agentDir: "/tmp/agent-provider",
modelProvider: "google-antigravity",
modelId: "claude-opus-4-5-thinking",
modelId: "claude-opus-4-6-thinking",
});
const toolNames = tools.map((t) => t.name);
@@ -176,7 +176,7 @@ describe("Agent-specific tool filtering", () => {
workspaceDir: "/tmp/test-provider-profile",
agentDir: "/tmp/agent-provider-profile",
modelProvider: "google-antigravity",
modelId: "claude-opus-4-5-thinking",
modelId: "claude-opus-4-6-thinking",
});
const toolNames = tools.map((t) => t.name);

View File

@@ -105,10 +105,7 @@ describe("workspace path resolution", () => {
it("defaults exec cwd to workspaceDir when workdir is omitted", async () => {
await withTempDir("openclaw-ws-", async (workspaceDir) => {
const tools = createOpenClawCodingTools({
workspaceDir,
exec: { host: "gateway", ask: "off", security: "full" },
});
const tools = createOpenClawCodingTools({ workspaceDir, exec: { host: "gateway" } });
const execTool = tools.find((tool) => tool.name === "exec");
expect(execTool).toBeDefined();
@@ -131,10 +128,7 @@ describe("workspace path resolution", () => {
it("lets exec workdir override the workspace default", async () => {
await withTempDir("openclaw-ws-", async (workspaceDir) => {
await withTempDir("openclaw-override-", async (overrideDir) => {
const tools = createOpenClawCodingTools({
workspaceDir,
exec: { host: "gateway", ask: "off", security: "full" },
});
const tools = createOpenClawCodingTools({ workspaceDir, exec: { host: "gateway" } });
const execTool = tools.find((tool) => tool.name === "exec");
expect(execTool).toBeDefined();

View File

@@ -30,8 +30,8 @@ describe("cron tool", () => {
],
["remove", { action: "remove", jobId: "job-1" }, { id: "job-1" }],
["remove", { action: "remove", id: "job-2" }, { id: "job-2" }],
["run", { action: "run", jobId: "job-1" }, { id: "job-1" }],
["run", { action: "run", id: "job-2" }, { id: "job-2" }],
["run", { action: "run", jobId: "job-1" }, { id: "job-1", mode: "force" }],
["run", { action: "run", id: "job-2" }, { id: "job-2", mode: "force" }],
["runs", { action: "runs", jobId: "job-1" }, { id: "job-1" }],
["runs", { action: "runs", id: "job-2" }, { id: "job-2" }],
])("%s sends id to gateway", async (action, args, expectedParams) => {
@@ -58,7 +58,21 @@ describe("cron tool", () => {
const call = callGatewayMock.mock.calls[0]?.[0] as {
params?: unknown;
};
expect(call?.params).toEqual({ id: "job-primary" });
expect(call?.params).toEqual({ id: "job-primary", mode: "force" });
});
it("supports due-only run mode", async () => {
const tool = createCronTool();
await tool.execute("call-due", {
action: "run",
jobId: "job-due",
runMode: "due",
});
const call = callGatewayMock.mock.calls[0]?.[0] as {
params?: unknown;
};
expect(call?.params).toEqual({ id: "job-due", mode: "due" });
});
it("normalizes cron.add job payloads", async () => {
@@ -86,7 +100,7 @@ describe("cron tool", () => {
deleteAfterRun: true,
schedule: { kind: "at", at: new Date(123).toISOString() },
sessionTarget: "main",
wakeMode: "next-heartbeat",
wakeMode: "now",
payload: { kind: "systemEvent", text: "hello" },
});
});

View File

@@ -18,6 +18,7 @@ import { resolveInternalSessionKey, resolveMainSessionAlias } from "./sessions-h
const CRON_ACTIONS = ["status", "list", "add", "update", "remove", "run", "runs", "wake"] as const;
const CRON_WAKE_MODES = ["now", "next-heartbeat"] as const;
const CRON_RUN_MODES = ["due", "force"] as const;
const REMINDER_CONTEXT_MESSAGES_MAX = 10;
const REMINDER_CONTEXT_PER_MESSAGE_MAX = 220;
@@ -37,6 +38,7 @@ const CronToolSchema = Type.Object({
patch: Type.Optional(Type.Object({}, { additionalProperties: true })),
text: Type.Optional(Type.String()),
mode: optionalStringEnum(CRON_WAKE_MODES),
runMode: optionalStringEnum(CRON_RUN_MODES),
contextMessages: Type.Optional(
Type.Number({ minimum: 0, maximum: REMINDER_CONTEXT_MESSAGES_MAX }),
),
@@ -312,7 +314,6 @@ Use jobId as the canonical identifier; id is accepted for compatibility. Use con
}
}
// [Fix Issue 3] Infer delivery target from session key for isolated jobs if not provided
if (
opts?.agentSessionKey &&
job &&
@@ -393,7 +394,9 @@ Use jobId as the canonical identifier; id is accepted for compatibility. Use con
if (!id) {
throw new Error("jobId required (id accepted for backward compatibility)");
}
return jsonResult(await callGatewayTool("cron.run", gatewayOpts, { id }));
const runMode =
params.runMode === "due" || params.runMode === "force" ? params.runMode : "force";
return jsonResult(await callGatewayTool("cron.run", gatewayOpts, { id, mode: runMode }));
}
case "runs": {
const id = readStringParam(params, "jobId") ?? readStringParam(params, "id");

View File

@@ -214,6 +214,26 @@ export function createSessionsSpawnTool(opts?: {
modelWarning = messageText;
}
}
if (thinkingOverride !== undefined) {
try {
await callGateway({
method: "sessions.patch",
params: {
key: childSessionKey,
thinkingLevel: thinkingOverride === "off" ? null : thinkingOverride,
},
timeoutMs: 10_000,
});
} catch (err) {
const messageText =
err instanceof Error ? err.message : typeof err === "string" ? err : "error";
return jsonResult({
status: "error",
error: messageText,
childSessionKey,
});
}
}
const childSystemPrompt = buildSubagentSystemPrompt({
requesterSessionKey,
requesterOrigin,

View File

@@ -0,0 +1,139 @@
import os from "node:os";
import path from "node:path";
import { describe, expect, it } from "vitest";
import type { OpenClawConfig } from "../config/config.js";
import { resolveRunWorkspaceDir } from "./workspace-run.js";
import { DEFAULT_AGENT_WORKSPACE_DIR } from "./workspace.js";
describe("resolveRunWorkspaceDir", () => {
it("resolves explicit workspace values without fallback", () => {
const explicit = path.join(process.cwd(), "tmp", "workspace-run-explicit");
const result = resolveRunWorkspaceDir({
workspaceDir: explicit,
sessionKey: "agent:main:subagent:test",
});
expect(result.usedFallback).toBe(false);
expect(result.agentId).toBe("main");
expect(result.workspaceDir).toBe(path.resolve(explicit));
});
it("falls back to configured per-agent workspace when input is missing", () => {
const defaultWorkspace = path.join(process.cwd(), "tmp", "workspace-default-main");
const researchWorkspace = path.join(process.cwd(), "tmp", "workspace-research");
const cfg = {
agents: {
defaults: { workspace: defaultWorkspace },
list: [{ id: "research", workspace: researchWorkspace }],
},
} satisfies OpenClawConfig;
const result = resolveRunWorkspaceDir({
workspaceDir: undefined,
sessionKey: "agent:research:subagent:test",
config: cfg,
});
expect(result.usedFallback).toBe(true);
expect(result.fallbackReason).toBe("missing");
expect(result.agentId).toBe("research");
expect(result.workspaceDir).toBe(path.resolve(researchWorkspace));
});
it("falls back to default workspace for blank strings", () => {
const defaultWorkspace = path.join(process.cwd(), "tmp", "workspace-default-main");
const cfg = {
agents: {
defaults: { workspace: defaultWorkspace },
},
} satisfies OpenClawConfig;
const result = resolveRunWorkspaceDir({
workspaceDir: " ",
sessionKey: "agent:main:subagent:test",
config: cfg,
});
expect(result.usedFallback).toBe(true);
expect(result.fallbackReason).toBe("blank");
expect(result.agentId).toBe("main");
expect(result.workspaceDir).toBe(path.resolve(defaultWorkspace));
});
it("falls back to built-in main workspace when config is unavailable", () => {
const result = resolveRunWorkspaceDir({
workspaceDir: null,
sessionKey: "agent:main:subagent:test",
config: undefined,
});
expect(result.usedFallback).toBe(true);
expect(result.fallbackReason).toBe("missing");
expect(result.agentId).toBe("main");
expect(result.workspaceDir).toBe(path.resolve(DEFAULT_AGENT_WORKSPACE_DIR));
});
it("throws for malformed agent session keys", () => {
expect(() =>
resolveRunWorkspaceDir({
workspaceDir: undefined,
sessionKey: "agent::broken",
config: undefined,
}),
).toThrow("Malformed agent session key");
});
it("uses explicit agent id for per-agent fallback when config is unavailable", () => {
const result = resolveRunWorkspaceDir({
workspaceDir: undefined,
sessionKey: "definitely-not-a-valid-session-key",
agentId: "research",
config: undefined,
});
expect(result.agentId).toBe("research");
expect(result.agentIdSource).toBe("explicit");
expect(result.workspaceDir).toBe(path.resolve(os.homedir(), ".openclaw", "workspace-research"));
});
it("throws for malformed agent session keys even when config has a default agent", () => {
const mainWorkspace = path.join(process.cwd(), "tmp", "workspace-main-default");
const researchWorkspace = path.join(process.cwd(), "tmp", "workspace-research-default");
const cfg = {
agents: {
defaults: { workspace: mainWorkspace },
list: [
{ id: "main", workspace: mainWorkspace },
{ id: "research", workspace: researchWorkspace, default: true },
],
},
} satisfies OpenClawConfig;
expect(() =>
resolveRunWorkspaceDir({
workspaceDir: undefined,
sessionKey: "agent::broken",
config: cfg,
}),
).toThrow("Malformed agent session key");
});
it("treats non-agent legacy keys as default, not malformed", () => {
const fallbackWorkspace = path.join(process.cwd(), "tmp", "workspace-default-legacy");
const cfg = {
agents: {
defaults: { workspace: fallbackWorkspace },
},
} satisfies OpenClawConfig;
const result = resolveRunWorkspaceDir({
workspaceDir: undefined,
sessionKey: "custom-main-key",
config: cfg,
});
expect(result.agentId).toBe("main");
expect(result.agentIdSource).toBe("default");
expect(result.workspaceDir).toBe(path.resolve(fallbackWorkspace));
});
});

106
src/agents/workspace-run.ts Normal file
View File

@@ -0,0 +1,106 @@
import type { OpenClawConfig } from "../config/config.js";
import { redactIdentifier } from "../logging/redact-identifier.js";
import {
classifySessionKeyShape,
DEFAULT_AGENT_ID,
normalizeAgentId,
parseAgentSessionKey,
} from "../routing/session-key.js";
import { resolveUserPath } from "../utils.js";
import { resolveAgentWorkspaceDir, resolveDefaultAgentId } from "./agent-scope.js";
export type WorkspaceFallbackReason = "missing" | "blank" | "invalid_type";
type AgentIdSource = "explicit" | "session_key" | "default";
export type ResolveRunWorkspaceResult = {
workspaceDir: string;
usedFallback: boolean;
fallbackReason?: WorkspaceFallbackReason;
agentId: string;
agentIdSource: AgentIdSource;
};
function resolveRunAgentId(params: {
sessionKey?: string;
agentId?: string;
config?: OpenClawConfig;
}): {
agentId: string;
agentIdSource: AgentIdSource;
} {
const rawSessionKey = params.sessionKey?.trim() ?? "";
const shape = classifySessionKeyShape(rawSessionKey);
if (shape === "malformed_agent") {
throw new Error("Malformed agent session key; refusing workspace resolution.");
}
const explicit =
typeof params.agentId === "string" && params.agentId.trim()
? normalizeAgentId(params.agentId)
: undefined;
if (explicit) {
return { agentId: explicit, agentIdSource: "explicit" };
}
const defaultAgentId = resolveDefaultAgentId(params.config ?? {});
if (shape === "missing" || shape === "legacy_or_alias") {
return {
agentId: defaultAgentId || DEFAULT_AGENT_ID,
agentIdSource: "default",
};
}
const parsed = parseAgentSessionKey(rawSessionKey);
if (parsed?.agentId) {
return {
agentId: normalizeAgentId(parsed.agentId),
agentIdSource: "session_key",
};
}
// Defensive fallback, should be unreachable for non-malformed shapes.
return {
agentId: defaultAgentId || DEFAULT_AGENT_ID,
agentIdSource: "default",
};
}
export function redactRunIdentifier(value: string | undefined): string {
return redactIdentifier(value, { len: 12 });
}
export function resolveRunWorkspaceDir(params: {
workspaceDir: unknown;
sessionKey?: string;
agentId?: string;
config?: OpenClawConfig;
}): ResolveRunWorkspaceResult {
const requested = params.workspaceDir;
const { agentId, agentIdSource } = resolveRunAgentId({
sessionKey: params.sessionKey,
agentId: params.agentId,
config: params.config,
});
if (typeof requested === "string") {
const trimmed = requested.trim();
if (trimmed) {
return {
workspaceDir: resolveUserPath(trimmed),
usedFallback: false,
agentId,
agentIdSource,
};
}
}
const fallbackReason: WorkspaceFallbackReason =
requested == null ? "missing" : typeof requested === "string" ? "blank" : "invalid_type";
const fallbackWorkspace = resolveAgentWorkspaceDir(params.config ?? {}, agentId);
return {
workspaceDir: resolveUserPath(fallbackWorkspace),
usedFallback: true,
fallbackReason,
agentId,
agentIdSource,
};
}

View File

@@ -178,6 +178,7 @@ export async function runAgentTurnWithFallback(params: {
const result = await runCliAgent({
sessionId: params.followupRun.run.sessionId,
sessionKey: params.sessionKey,
agentId: params.followupRun.run.agentId,
sessionFile: params.followupRun.run.sessionFile,
workspaceDir: params.followupRun.run.workspaceDir,
config: params.followupRun.run.config,
@@ -255,6 +256,7 @@ export async function runAgentTurnWithFallback(params: {
return runEmbeddedPiAgent({
sessionId: params.followupRun.run.sessionId,
sessionKey: params.sessionKey,
agentId: params.followupRun.run.agentId,
messageProvider: params.sessionCtx.Provider?.trim().toLowerCase() || undefined,
agentAccountId: params.sessionCtx.AccountId,
messageTo: params.sessionCtx.OriginatingTo ?? params.sessionCtx.To,

View File

@@ -113,6 +113,7 @@ export async function runMemoryFlushIfNeeded(params: {
return runEmbeddedPiAgent({
sessionId: params.followupRun.run.sessionId,
sessionKey: params.sessionKey,
agentId: params.followupRun.run.agentId,
messageProvider: params.sessionCtx.Provider?.trim().toLowerCase() || undefined,
agentAccountId: params.sessionCtx.AccountId,
messageTo: params.sessionCtx.OriginatingTo ?? params.sessionCtx.To,

View File

@@ -140,6 +140,7 @@ export function createFollowupRunner(params: {
return runEmbeddedPiAgent({
sessionId: queued.run.sessionId,
sessionKey: queued.run.sessionKey,
agentId: queued.run.agentId,
messageProvider: queued.run.messageProvider,
agentAccountId: queued.run.agentAccountId,
messageTo: queued.originatingTo,

View File

@@ -71,7 +71,7 @@ export function registerCronAddCommand(cron: Command) {
.option("--keep-after-run", "Keep one-shot job after it succeeds", false)
.option("--agent <id>", "Agent id for this job")
.option("--session <target>", "Session target (main|isolated)")
.option("--wake <mode>", "Wake mode (now|next-heartbeat)", "next-heartbeat")
.option("--wake <mode>", "Wake mode (now|next-heartbeat)", "now")
.option("--at <when>", "Run once at time (ISO) or +duration (e.g. 20m)")
.option("--every <duration>", "Run every duration (e.g. 10m, 1h)")
.option("--cron <expr>", "Cron expression (5-field)")
@@ -122,8 +122,8 @@ export function registerCronAddCommand(cron: Command) {
};
})();
const wakeModeRaw = typeof opts.wake === "string" ? opts.wake : "next-heartbeat";
const wakeMode = wakeModeRaw.trim() || "next-heartbeat";
const wakeModeRaw = typeof opts.wake === "string" ? opts.wake : "now";
const wakeMode = wakeModeRaw.trim() || "now";
if (wakeMode !== "now" && wakeMode !== "next-heartbeat") {
throw new Error("--wake must be now or next-heartbeat");
}

View File

@@ -92,12 +92,12 @@ export function registerCronSimpleCommands(cron: Command) {
.command("run")
.description("Run a cron job now (debug)")
.argument("<id>", "Job id")
.option("--force", "Run even if not due", false)
.option("--due", "Run only when due (default behavior in older versions)", false)
.action(async (id, opts) => {
try {
const res = await callGatewayFromCli("cron.run", opts, {
id,
mode: opts.force ? "force" : "due",
mode: opts.due ? "due" : "force",
});
defaultRuntime.log(JSON.stringify(res, null, 2));
} catch (err) {

View File

@@ -398,6 +398,7 @@ export async function agentCommand(
return runCliAgent({
sessionId,
sessionKey,
agentId: sessionAgentId,
sessionFile,
workspaceDir,
config: cfg,
@@ -418,6 +419,7 @@ export async function agentCommand(
return runEmbeddedPiAgent({
sessionId,
sessionKey,
agentId: sessionAgentId,
messageChannel,
agentAccountId: runContext.accountId,
messageTo: opts.replyTo ?? opts.to,

View File

@@ -319,6 +319,7 @@ async function probeTarget(params: {
await runEmbeddedPiAgent({
sessionId,
sessionFile,
agentId,
workspaceDir,
agentDir,
config: cfg,

View File

@@ -1,4 +1,3 @@
import crypto from "node:crypto";
import fs from "node:fs";
import type {
ChannelAccountSnapshot,
@@ -8,6 +7,7 @@ import type {
import type { OpenClawConfig } from "../../config/config.js";
import { resolveChannelDefaultAccountId } from "../../channels/plugins/helpers.js";
import { listChannelPlugins } from "../../channels/plugins/index.js";
import { sha256HexPrefix } from "../../logging/redact-identifier.js";
import { formatAge } from "./format.js";
export type ChannelRow = {
@@ -57,17 +57,13 @@ function existsSyncMaybe(p: string | undefined): boolean | null {
}
}
function sha256HexPrefix(value: string, len = 8): string {
return crypto.createHash("sha256").update(value).digest("hex").slice(0, len);
}
function formatTokenHint(token: string, opts: { showSecrets: boolean }): string {
const t = token.trim();
if (!t) {
return "empty";
}
if (!opts.showSecrets) {
return `sha256:${sha256HexPrefix(t)} · len ${t.length}`;
return `sha256:${sha256HexPrefix(t, 8)} · len ${t.length}`;
}
const head = t.slice(0, 4);
const tail = t.slice(-4);

View File

@@ -542,7 +542,8 @@ const FIELD_HELP: Record<string, string> = {
"Extra paths to include in memory search (directories or .md files; relative paths resolved from workspace).",
"agents.defaults.memorySearch.experimental.sessionMemory":
"Enable experimental session transcript indexing for memory search (default: false).",
"agents.defaults.memorySearch.provider": 'Embedding provider ("openai", "gemini", or "local").',
"agents.defaults.memorySearch.provider":
'Embedding provider ("openai", "gemini", "voyage", or "local").',
"agents.defaults.memorySearch.remote.baseUrl":
"Custom base URL for remote embeddings (OpenAI-compatible proxies or Gemini overrides).",
"agents.defaults.memorySearch.remote.apiKey": "Custom API key for the remote embedding provider.",

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