mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-19 21:22:05 +08:00
Compare commits
331 Commits
agent-memo
...
qa-fold-ht
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1dc652394d | ||
|
|
46e0dd4bf8 | ||
|
|
2ea3f65592 | ||
|
|
31d489215e | ||
|
|
0a7017aa1d | ||
|
|
83e3c0f78c | ||
|
|
f7fd088766 | ||
|
|
1a408dad03 | ||
|
|
742b6ffee8 | ||
|
|
27f702d68f | ||
|
|
0781dae620 | ||
|
|
6256ad86c9 | ||
|
|
f7f415f26b | ||
|
|
2983edd5a2 | ||
|
|
4da36da605 | ||
|
|
92d1f04de3 | ||
|
|
611ad1a097 | ||
|
|
6ef4970988 | ||
|
|
8d9eba3f4f | ||
|
|
40dc8fd147 | ||
|
|
2257a21b7e | ||
|
|
d4833e27c7 | ||
|
|
d1bb2d5a12 | ||
|
|
eb7da0a2e5 | ||
|
|
797865c9dc | ||
|
|
7fcbfa6971 | ||
|
|
3091c13713 | ||
|
|
c159063c70 | ||
|
|
dae37a4579 | ||
|
|
2e0dfda462 | ||
|
|
5b3d652c05 | ||
|
|
b39a932112 | ||
|
|
0c76a98f10 | ||
|
|
a8b5f5d551 | ||
|
|
bbe9669926 | ||
|
|
7580c80f37 | ||
|
|
7f38b1a910 | ||
|
|
8aaf937bc0 | ||
|
|
6467c1962a | ||
|
|
0c565f3b0e | ||
|
|
7211d77553 | ||
|
|
dba291ed35 | ||
|
|
32c02e843a | ||
|
|
5e329f4065 | ||
|
|
e6743eb783 | ||
|
|
dbd5689ea1 | ||
|
|
44b0644e88 | ||
|
|
6aa85dfaa1 | ||
|
|
86b24ac2b2 | ||
|
|
d236612cc0 | ||
|
|
c3390f0bc6 | ||
|
|
a6ac8de523 | ||
|
|
ca527aad9d | ||
|
|
3a435eebc0 | ||
|
|
dfc5bd5fcc | ||
|
|
7cc66b5175 | ||
|
|
fcec95ffd7 | ||
|
|
e67f8ba459 | ||
|
|
33fa225f65 | ||
|
|
86a28636fa | ||
|
|
90ba9fc864 | ||
|
|
f5419b5bb0 | ||
|
|
14fd10f8f8 | ||
|
|
38fefc5aaf | ||
|
|
ccdec2e294 | ||
|
|
c79a5aa253 | ||
|
|
0dbac0d5f9 | ||
|
|
b972feb3f7 | ||
|
|
3c01716c82 | ||
|
|
e802fb8a9f | ||
|
|
94b710ac00 | ||
|
|
ebee101d30 | ||
|
|
5697ab810e | ||
|
|
cd98f195a7 | ||
|
|
be7d86ed80 | ||
|
|
5d6ac23086 | ||
|
|
f61ad70d3f | ||
|
|
05e70bd331 | ||
|
|
8480ef3f86 | ||
|
|
fc1bdecf08 | ||
|
|
a57e761f6b | ||
|
|
13be16d699 | ||
|
|
257b533e85 | ||
|
|
5939ab4c49 | ||
|
|
5db2f6c1fc | ||
|
|
afd9cb0c10 | ||
|
|
2e1e4167a9 | ||
|
|
3bacf96ccc | ||
|
|
433d8cbb2c | ||
|
|
37b2770071 | ||
|
|
e172f64f3f | ||
|
|
8e66d7aad3 | ||
|
|
688ecb1655 | ||
|
|
96dbd1c723 | ||
|
|
2f12755498 | ||
|
|
a37dd0210b | ||
|
|
17106b4844 | ||
|
|
93d0d2aedd | ||
|
|
82ae81f3bf | ||
|
|
2db37c2cd0 | ||
|
|
6370f2023a | ||
|
|
2dbbef46bb | ||
|
|
df261fabb3 | ||
|
|
c041a45ece | ||
|
|
089f8c7fb5 | ||
|
|
712e69dd74 | ||
|
|
13aaece8b3 | ||
|
|
32ee308f55 | ||
|
|
5776b9b4e6 | ||
|
|
6368c1173c | ||
|
|
dc9b1d5159 | ||
|
|
308fb97f7a | ||
|
|
e498fc8c3b | ||
|
|
b794d7fb58 | ||
|
|
bc7c2baa5c | ||
|
|
46c42d4a0d | ||
|
|
3a82bf5766 | ||
|
|
5e7a0b1558 | ||
|
|
38ebc24f77 | ||
|
|
af3c10626c | ||
|
|
324ad548a8 | ||
|
|
e552f97866 | ||
|
|
259f071a93 | ||
|
|
a5190f7d4a | ||
|
|
a619518ebe | ||
|
|
ea0b0ad0a0 | ||
|
|
fb25f29638 | ||
|
|
300f5e8590 | ||
|
|
f144899219 | ||
|
|
d095e2a4f5 | ||
|
|
8aaa4bf3ef | ||
|
|
06b6f7055b | ||
|
|
de95726177 | ||
|
|
e91c17947b | ||
|
|
a25c64a4e4 | ||
|
|
8888bca752 | ||
|
|
50a4bb00e5 | ||
|
|
de17d5b9ef | ||
|
|
6a0c3eaf78 | ||
|
|
78f948f768 | ||
|
|
975340fbd5 | ||
|
|
7570831ee1 | ||
|
|
f2a83a7a71 | ||
|
|
d9c66b9c6d | ||
|
|
39328ed692 | ||
|
|
8662b9de54 | ||
|
|
6504237900 | ||
|
|
fb69db6365 | ||
|
|
845ad1cf71 | ||
|
|
356a199bd4 | ||
|
|
f1cab04966 | ||
|
|
87854e841e | ||
|
|
508e3bf413 | ||
|
|
825188cb6a | ||
|
|
36bfe77db1 | ||
|
|
def4c995ac | ||
|
|
f91350485c | ||
|
|
d91766e5e1 | ||
|
|
dae06a203f | ||
|
|
52948a1726 | ||
|
|
88c92922e1 | ||
|
|
ae6e1fa4d2 | ||
|
|
f1c0d5f06f | ||
|
|
b28fda9ef8 | ||
|
|
09427aa760 | ||
|
|
107904c2c5 | ||
|
|
6c82a9fb18 | ||
|
|
741f7080a7 | ||
|
|
a1b7118d0f | ||
|
|
ca6d52e0e8 | ||
|
|
4c55e04e49 | ||
|
|
8d596aa651 | ||
|
|
1385da8d3f | ||
|
|
ad715dfdc9 | ||
|
|
d1ab308f5c | ||
|
|
aaceaf8e7c | ||
|
|
1492f9906a | ||
|
|
9ceb970a06 | ||
|
|
c33007ef58 | ||
|
|
09a159c913 | ||
|
|
eedb6678f1 | ||
|
|
459bcd6198 | ||
|
|
1bbc3b6cb6 | ||
|
|
6325a8b5f4 | ||
|
|
5805af9dc4 | ||
|
|
42bcb3ecb0 | ||
|
|
b48238aa88 | ||
|
|
dc980273e3 | ||
|
|
74f90885f3 | ||
|
|
236a0c8fe0 | ||
|
|
b306745f64 | ||
|
|
20dd2be0f2 | ||
|
|
5b030418c1 | ||
|
|
a74ce6f20c | ||
|
|
10f0588ee3 | ||
|
|
39297fb0ad | ||
|
|
3914a3638c | ||
|
|
2ef0589b76 | ||
|
|
01e562113b | ||
|
|
410a95269a | ||
|
|
2c6bf1a5d8 | ||
|
|
ccc1ad4c74 | ||
|
|
f19cae6d1d | ||
|
|
8c3185d55c | ||
|
|
a0d9f9ea45 | ||
|
|
dd5febe2aa | ||
|
|
943511674c | ||
|
|
c33cec04d9 | ||
|
|
c9605779ef | ||
|
|
c79f1e5441 | ||
|
|
15e101137d | ||
|
|
900a834c60 | ||
|
|
9765f7333a | ||
|
|
1fb11ab306 | ||
|
|
01b919aeff | ||
|
|
c654641e0c | ||
|
|
d04cedb9fe | ||
|
|
ba69d4fb03 | ||
|
|
73241d39f6 | ||
|
|
c770c7b084 | ||
|
|
e12cf72b17 | ||
|
|
e9e44bf83c | ||
|
|
843b1d6fbb | ||
|
|
09b56592d2 | ||
|
|
701687efa3 | ||
|
|
d8cc16a3e2 | ||
|
|
7f894ba2be | ||
|
|
e5de09f96a | ||
|
|
2a0e63d12b | ||
|
|
e3bab80bda | ||
|
|
7207072436 | ||
|
|
49e6f5a524 | ||
|
|
95c87e31e2 | ||
|
|
0d2102d247 | ||
|
|
55323103b9 | ||
|
|
239b4de6af | ||
|
|
a7b52ecad9 | ||
|
|
bb44c5326e | ||
|
|
4764258b3f | ||
|
|
6af1b97b1d | ||
|
|
4ca0e52d0e | ||
|
|
37eea55afa | ||
|
|
ea76a45917 | ||
|
|
84bcdaa983 | ||
|
|
4ac192deef | ||
|
|
3c9cf2d583 | ||
|
|
c4ae2be947 | ||
|
|
27310bfa34 | ||
|
|
fbc12e0879 | ||
|
|
73cdb78a1e | ||
|
|
0df60ad306 | ||
|
|
9928516a78 | ||
|
|
7845182410 | ||
|
|
aba6f7ad21 | ||
|
|
5570a10bf4 | ||
|
|
98857235d5 | ||
|
|
39e9336d40 | ||
|
|
392f5b75bf | ||
|
|
a98bfdb2b7 | ||
|
|
34d402f53c | ||
|
|
1faf8175e4 | ||
|
|
fdb042b9ce | ||
|
|
d5a27b0b96 | ||
|
|
9328f4a675 | ||
|
|
75df29c215 | ||
|
|
bf8ac0d96d | ||
|
|
bfb47a03b3 | ||
|
|
a93fc87e2c | ||
|
|
cc3d346c15 | ||
|
|
a8d60d352e | ||
|
|
1bfa2787b5 | ||
|
|
e385f6663a | ||
|
|
76bdb025d6 | ||
|
|
d2e36a176d | ||
|
|
b637414871 | ||
|
|
aa39600793 | ||
|
|
61b116d597 | ||
|
|
033162f209 | ||
|
|
dfd8a2220b | ||
|
|
74ad4f592a | ||
|
|
a14b1e05e5 | ||
|
|
8b63a3d551 | ||
|
|
f4e9a6e047 | ||
|
|
2ae84f75ef | ||
|
|
dca17477dc | ||
|
|
7f1fa65399 | ||
|
|
b6a06f0e49 | ||
|
|
9501d4dec2 | ||
|
|
d9397e5b9b | ||
|
|
d9d7766a41 | ||
|
|
0771ac8563 | ||
|
|
906174bff1 | ||
|
|
c677424edb | ||
|
|
ea4ddb2eb5 | ||
|
|
f19c5f6b2f | ||
|
|
1e83f42a64 | ||
|
|
4c9b4c32ef | ||
|
|
4fc5cf4579 | ||
|
|
8f8162704d | ||
|
|
f381dca15b | ||
|
|
9ab9469d04 | ||
|
|
a2f5ac82d5 | ||
|
|
7b7e40cb0e | ||
|
|
e1c2926628 | ||
|
|
cebe5cb94a | ||
|
|
0b14724c87 | ||
|
|
e879a67bf7 | ||
|
|
8af89b097a | ||
|
|
53bb55e023 | ||
|
|
8bcb1e05b6 | ||
|
|
317919ec52 | ||
|
|
1f71e92297 | ||
|
|
d2e847e8cf | ||
|
|
720c0ab372 | ||
|
|
70e39da00f | ||
|
|
9eb6e6d326 | ||
|
|
9de9562cb7 | ||
|
|
6b25ccc4b1 | ||
|
|
111018984c | ||
|
|
7c24de5c87 | ||
|
|
3125cdacb5 | ||
|
|
8151a547c5 | ||
|
|
940feee71b | ||
|
|
46d359237e | ||
|
|
ae655345c4 | ||
|
|
a48e5091cb | ||
|
|
e9229ab77e | ||
|
|
65e77b82f5 | ||
|
|
2c7fe6a39c | ||
|
|
7ef85bfb1d | ||
|
|
361320cd9f |
@@ -1,44 +1,34 @@
|
||||
---
|
||||
name: channel-message-flows
|
||||
description: "Use when previewing local channel message flow fixtures."
|
||||
description: "Use when running QA Lab channel message flow evidence."
|
||||
---
|
||||
|
||||
# Channel Message Flows
|
||||
|
||||
Use this from the OpenClaw repo root to send canned channel preview flows while iterating on message UX. These are real sends/edits/deletes against the configured channel target.
|
||||
Use this from the OpenClaw repo root to run the QA Lab evidence for Telegram
|
||||
draft/final delivery sequencing. This skill no longer launches a standalone
|
||||
script; the behavior is owned by the QA scenario and its Vitest-backed e2e test.
|
||||
|
||||
## Telegram
|
||||
## QA Scenario
|
||||
|
||||
Native Telegram `sendMessageDraft` tool progress, then a final answer:
|
||||
Run the scenario through QA Lab:
|
||||
|
||||
```bash
|
||||
node --import tsx scripts/dev/channel-message-flows.ts \
|
||||
--channel telegram \
|
||||
--target <telegram-chat-id> \
|
||||
--flow working-final \
|
||||
--duration-ms 20000
|
||||
pnpm openclaw qa suite --scenario channel-message-flows
|
||||
```
|
||||
|
||||
Thinking preview, then a final answer:
|
||||
Run the focused e2e test directly in a Codex worktree:
|
||||
|
||||
```bash
|
||||
node --import tsx scripts/dev/channel-message-flows.ts \
|
||||
--channel telegram \
|
||||
--target <telegram-chat-id> \
|
||||
--flow thinking-final
|
||||
node scripts/run-vitest.mjs extensions/telegram/src/channel-message-flows.qa.e2e.test.ts
|
||||
```
|
||||
|
||||
## Options
|
||||
## References
|
||||
|
||||
- `--account <accountId>`: Telegram account id when not using the default.
|
||||
- `--thread-id <id>`: Telegram forum topic/message thread id.
|
||||
- `--delay-ms <ms>`: Override preview update cadence.
|
||||
- `--duration-ms <ms>`: Simulated working duration for `working-final`.
|
||||
- `--final-text <text>`: Override the durable final message.
|
||||
- `qa/scenarios/channels/channel-message-flows.yaml`
|
||||
- `extensions/telegram/src/channel-message-flows.qa.e2e.test.ts`
|
||||
- `extensions/telegram/src/test-support/channel-message-flows.ts`
|
||||
|
||||
## Notes
|
||||
|
||||
- `--target` is the numeric Telegram chat id.
|
||||
- `working-final` exercises native Telegram `sendMessageDraft` with static `Working` status and sample tool progress.
|
||||
- `thinking-final` exercises formatted `Thinking` reasoning preview clearing before the final answer.
|
||||
- Only `--channel telegram` is implemented for now.
|
||||
The scenario covers `channels.streaming` as primary evidence and records
|
||||
secondary coverage for thread preservation, delivery ordering, and reasoning
|
||||
preview visibility.
|
||||
|
||||
@@ -16,11 +16,8 @@ This skill owns the operational workflow for:
|
||||
|
||||
- `taxonomy.yaml`
|
||||
- `docs/maturity-scores.yaml`
|
||||
- `docs/maturity-scorecard.md`
|
||||
- `docs/taxonomy.md`
|
||||
- `docs/taxonomy-outline.md`
|
||||
- `scripts/render-maturity-docs.mjs`
|
||||
- `.github/workflows/maturity-scorecard.yml`
|
||||
- `docs/concepts/qa-e2e-automation.md`
|
||||
- `qa/scenarios/index.yaml`
|
||||
|
||||
Keep person-specific, maintainer-private, Discord archive, and discrawl facts
|
||||
out of this repo. If a score needs private evidence, use the redacted
|
||||
@@ -31,12 +28,21 @@ out of this repo. If a score needs private evidence, use the redacted
|
||||
- `taxonomy.yaml` is the hand-edited source of truth for surfaces, levels,
|
||||
QA profiles, categories, feature coverage IDs, docs refs, LTS overrides, and
|
||||
completeness-instruction paths.
|
||||
- Feature `coverageIds` are ANDed proof targets, not aliases. A feature may
|
||||
list multiple IDs when each ID proves part of one capability.
|
||||
- Coverage IDs use dotted `namespace.behavior` form, with lowercase
|
||||
alphanumeric/dash segments. Profile, surface, and category IDs may remain
|
||||
dashed or dotted.
|
||||
- Keep categories and feature names unique, product-shaped, and broader than raw
|
||||
coverage IDs. Do not promote generic IDs into standalone feature names.
|
||||
- Avoid duplicate coverage-ID bundles under different feature names in one
|
||||
category.
|
||||
- `docs/maturity-scores.yaml` is the aggregate score source committed in this
|
||||
repo. It is the only committed score data; do not add generated inventory
|
||||
directories.
|
||||
- `docs/maturity-scorecard.md`, `docs/taxonomy.md`, and
|
||||
`docs/taxonomy-outline.md` are deterministic docs generated from the root
|
||||
taxonomy and aggregate score source.
|
||||
- There is no committed maturity-doc renderer or `pnpm maturity:*` script in
|
||||
this repo. Do not invent generated scorecard files; update the source YAML
|
||||
and current docs directly.
|
||||
- `qa-evidence.json` artifacts provide per-run QA scorecard evidence. They can
|
||||
enrich generated artifact docs, but they are not committed as inventory.
|
||||
|
||||
@@ -44,22 +50,28 @@ out of this repo. If a score needs private evidence, use the redacted
|
||||
|
||||
Run from the openclaw repo root.
|
||||
|
||||
Render committed docs:
|
||||
Validate YAML structure after source edits:
|
||||
|
||||
```bash
|
||||
pnpm maturity:render
|
||||
node <<'NODE'
|
||||
const fs = require("node:fs");
|
||||
const YAML = require("yaml");
|
||||
for (const file of ["taxonomy.yaml", "docs/maturity-scores.yaml", "qa/scenarios/index.yaml"]) {
|
||||
YAML.parse(fs.readFileSync(file, "utf8"));
|
||||
}
|
||||
NODE
|
||||
```
|
||||
|
||||
Check generated docs are current:
|
||||
Check docs when touching docs prose:
|
||||
|
||||
```bash
|
||||
pnpm maturity:check
|
||||
pnpm check:docs
|
||||
```
|
||||
|
||||
Render an evidence-enriched docs artifact from downloaded QA artifacts:
|
||||
Run focused QA/profile checks when changing coverage IDs or profile membership:
|
||||
|
||||
```bash
|
||||
pnpm maturity:render -- --evidence-dir .artifacts/maturity-evidence --output-dir .artifacts/maturity-docs
|
||||
pnpm openclaw qa coverage --json
|
||||
```
|
||||
|
||||
## Scoring Workflow
|
||||
@@ -75,13 +87,13 @@ When asked to score or refresh a surface:
|
||||
discrawl or unredacted private archives.
|
||||
5. Update `docs/maturity-scores.yaml` only when the score change is backed by
|
||||
public or redacted artifact evidence.
|
||||
6. Run `pnpm maturity:render`.
|
||||
7. Run `pnpm maturity:check`.
|
||||
6. Run the YAML validation command from this skill.
|
||||
7. Run `pnpm check:docs` if docs prose changed, and focused QA coverage checks
|
||||
if coverage IDs or profile membership changed.
|
||||
|
||||
For subjective score changes, make the smallest defensible edit and leave the
|
||||
evidence path in the PR or task summary. The deterministic renderer owns
|
||||
Markdown structure; manual prose tweaks belong in taxonomy, score source, or
|
||||
the renderer rather than in generated docs.
|
||||
evidence path in the PR or task summary. Keep manual prose in current docs and
|
||||
keep score data in `docs/maturity-scores.yaml`.
|
||||
|
||||
## Default Completeness Process
|
||||
|
||||
@@ -158,13 +170,9 @@ Bands:
|
||||
- `Alpha`: 50-70
|
||||
- `Experimental`: 0-50
|
||||
|
||||
## GitHub Action
|
||||
|
||||
The `Maturity scorecard` workflow verifies committed generated docs on PRs and
|
||||
pushes. Manual dispatch can also download QA artifacts from another workflow run
|
||||
with `source_run_id` and `artifact_pattern`, render evidence-enriched docs into
|
||||
`.artifacts/maturity-docs`, and upload them as a GitHub artifact.
|
||||
## Artifacts
|
||||
|
||||
Do not add the maintainer repo's `docs/kevinslin/maturity-scorecard/inventory/`
|
||||
tree to openclaw. Those generated reports are intentionally replaced here by
|
||||
short-lived artifact docs and the committed aggregate scorecard pages.
|
||||
tree to openclaw. Evidence-enriched scorecard outputs belong in short-lived
|
||||
artifacts, not committed generated docs, unless this repo adds an explicit
|
||||
renderer/check workflow first.
|
||||
|
||||
@@ -1686,7 +1686,8 @@ jobs:
|
||||
FIREWORKS_API_KEY: ${{ secrets.FIREWORKS_API_KEY }}
|
||||
OPENCLAW_LIVE_PROVIDERS: ${{ matrix.providers }}
|
||||
OPENCLAW_LIVE_IMAGE: ${{ needs.prepare_live_test_image.outputs.live_image }}
|
||||
OPENCLAW_LIVE_MAX_MODELS: "6"
|
||||
OPENCLAW_LIVE_MODELS: ${{ matrix.models || 'modern' }}
|
||||
OPENCLAW_LIVE_MAX_MODELS: ${{ matrix.max_models || '6' }}
|
||||
OPENCLAW_LIVE_MODEL_TIMEOUT_MS: "45000"
|
||||
OPENCLAW_SKIP_DOCKER_BUILD: "1"
|
||||
OPENCLAW_VITEST_MAX_WORKERS: "2"
|
||||
@@ -2000,7 +2001,7 @@ jobs:
|
||||
profiles: stable full
|
||||
- suite_id: native-live-src-gateway-profiles-minimax
|
||||
label: Native live gateway profiles MiniMax
|
||||
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=minimax,minimax-portal OPENCLAW_LIVE_GATEWAY_MODELS=minimax/MiniMax-M3,minimax-portal/MiniMax-M3 OPENCLAW_LIVE_GATEWAY_MAX_MODELS=2 node .release-harness/scripts/test-live-shard.mjs native-live-src-gateway-profiles
|
||||
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=minimax,minimax-portal OPENCLAW_LIVE_GATEWAY_MODELS=minimax/MiniMax-M2.7,minimax-portal/MiniMax-M2.7 OPENCLAW_LIVE_GATEWAY_MAX_MODELS=2 node .release-harness/scripts/test-live-shard.mjs native-live-src-gateway-profiles
|
||||
timeout_minutes: 60
|
||||
profile_env_only: false
|
||||
profiles: stable full
|
||||
@@ -2303,7 +2304,7 @@ jobs:
|
||||
profiles: stable full
|
||||
- suite_id: live-gateway-minimax-docker
|
||||
label: Docker live gateway MiniMax
|
||||
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=minimax,minimax-portal OPENCLAW_LIVE_GATEWAY_MODELS=minimax/MiniMax-M3,minimax-portal/MiniMax-M3 OPENCLAW_LIVE_GATEWAY_MAX_MODELS=1 OPENCLAW_LIVE_GATEWAY_STEP_TIMEOUT_MS=90000 OPENCLAW_LIVE_GATEWAY_MODEL_TIMEOUT_MS=180000 OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --foreground --kill-after=30s 35m bash .release-harness/scripts/test-live-gateway-models-docker.sh
|
||||
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=minimax,minimax-portal OPENCLAW_LIVE_GATEWAY_MODELS=minimax/MiniMax-M2.7,minimax-portal/MiniMax-M2.7 OPENCLAW_LIVE_GATEWAY_MAX_MODELS=2 OPENCLAW_LIVE_GATEWAY_STEP_TIMEOUT_MS=90000 OPENCLAW_LIVE_GATEWAY_MODEL_TIMEOUT_MS=180000 OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --foreground --kill-after=30s 35m bash .release-harness/scripts/test-live-gateway-models-docker.sh
|
||||
timeout_minutes: 40
|
||||
profile_env_only: false
|
||||
profiles: stable full
|
||||
|
||||
7
.github/workflows/openclaw-performance.yml
vendored
7
.github/workflows/openclaw-performance.yml
vendored
@@ -45,7 +45,7 @@ on:
|
||||
kova_ref:
|
||||
description: openclaw/Kova Git ref to install
|
||||
required: false
|
||||
default: b63b6f9e20efb23641df00487e982230d81a90ac
|
||||
default: 4f146016583018bad9e24f8e64a6af5f963bb7ee
|
||||
type: string
|
||||
dispatch_id:
|
||||
description: Optional parent workflow dispatch identifier
|
||||
@@ -66,6 +66,7 @@ env:
|
||||
OCM_LINUX_X64_SHA256: b849b8de5d77e97e0df9319703254ae95e29d7f26a7552ea79bf173ff110ea0a
|
||||
KOVA_REPOSITORY: openclaw/Kova
|
||||
PERFORMANCE_MODEL_ID: gpt-5.5
|
||||
KOVA_SCENARIO_TIMEOUT_MS: "300000"
|
||||
|
||||
jobs:
|
||||
kova:
|
||||
@@ -98,7 +99,7 @@ jobs:
|
||||
live: "true"
|
||||
include_filters: "scenario:agent-cold-warm-message"
|
||||
env:
|
||||
KOVA_REF: ${{ inputs.kova_ref || 'b63b6f9e20efb23641df00487e982230d81a90ac' }}
|
||||
KOVA_REF: ${{ inputs.kova_ref || '4f146016583018bad9e24f8e64a6af5f963bb7ee' }}
|
||||
KOVA_HOME: ${{ github.workspace }}/.artifacts/kova/home/${{ matrix.lane }}
|
||||
PERFORMANCE_HELPER_DIR: ${{ github.workspace }}/.artifacts/performance-workflow
|
||||
REPORT_DIR: ${{ github.workspace }}/.artifacts/kova/reports/${{ matrix.lane }}
|
||||
@@ -291,6 +292,7 @@ jobs:
|
||||
--auth "$AUTH_MODE"
|
||||
--parallel 1
|
||||
--repeat "$repeat"
|
||||
--timeout-ms "$KOVA_SCENARIO_TIMEOUT_MS"
|
||||
--report-dir "$REPORT_DIR"
|
||||
--execute
|
||||
--json
|
||||
@@ -361,6 +363,7 @@ jobs:
|
||||
- Kova repository: ${KOVA_REPOSITORY}
|
||||
- Kova ref: ${KOVA_REF}
|
||||
- Kova profile: ${PROFILE}
|
||||
- Kova scenario timeout: ${KOVA_SCENARIO_TIMEOUT_MS}ms
|
||||
- Lane auth: ${AUTH_MODE}
|
||||
- Lane model: ${PERFORMANCE_MODEL_ID}
|
||||
- Lane repeat: ${repeat}
|
||||
|
||||
@@ -532,6 +532,7 @@ jobs:
|
||||
OPENCLAW_QA_CONVEX_SECRET_CI: ${{ secrets.OPENCLAW_QA_CONVEX_SECRET_CI }}
|
||||
OPENCLAW_QA_CREDENTIAL_ACQUIRE_TIMEOUT_MS: "1800000"
|
||||
OPENCLAW_QA_REDACT_PUBLIC_METADATA: "1"
|
||||
OPENCLAW_QA_TRANSPORT_READY_TIMEOUT_MS: "180000"
|
||||
INPUT_SCENARIO: ${{ github.event_name == 'workflow_dispatch' && inputs.scenario || '' }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
@@ -624,6 +625,7 @@ jobs:
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
OPENCLAW_QA_CONVEX_SITE_URL: ${{ secrets.OPENCLAW_QA_CONVEX_SITE_URL }}
|
||||
OPENCLAW_QA_CONVEX_SECRET_CI: ${{ secrets.OPENCLAW_QA_CONVEX_SECRET_CI }}
|
||||
OPENCLAW_QA_CREDENTIAL_ACQUIRE_TIMEOUT_MS: "1800000"
|
||||
OPENCLAW_QA_REDACT_PUBLIC_METADATA: "1"
|
||||
OPENCLAW_QA_DISCORD_CAPTURE_CONTENT: "1"
|
||||
INPUT_SCENARIO: ${{ github.event_name == 'workflow_dispatch' && inputs.discord_scenario || '' }}
|
||||
@@ -721,6 +723,7 @@ jobs:
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
OPENCLAW_QA_CONVEX_SITE_URL: ${{ secrets.OPENCLAW_QA_CONVEX_SITE_URL }}
|
||||
OPENCLAW_QA_CONVEX_SECRET_CI: ${{ secrets.OPENCLAW_QA_CONVEX_SECRET_CI }}
|
||||
OPENCLAW_QA_CREDENTIAL_ACQUIRE_TIMEOUT_MS: "1800000"
|
||||
OPENCLAW_QA_REDACT_PUBLIC_METADATA: "1"
|
||||
OPENCLAW_QA_WHATSAPP_CAPTURE_CONTENT: "1"
|
||||
INPUT_SCENARIO: ${{ github.event_name == 'workflow_dispatch' && inputs.whatsapp_scenario || '' }}
|
||||
@@ -815,6 +818,7 @@ jobs:
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
OPENCLAW_QA_CONVEX_SITE_URL: ${{ secrets.OPENCLAW_QA_CONVEX_SITE_URL }}
|
||||
OPENCLAW_QA_CONVEX_SECRET_CI: ${{ secrets.OPENCLAW_QA_CONVEX_SECRET_CI }}
|
||||
OPENCLAW_QA_CREDENTIAL_ACQUIRE_TIMEOUT_MS: "1800000"
|
||||
OPENCLAW_QA_REDACT_PUBLIC_METADATA: "1"
|
||||
OPENCLAW_QA_SLACK_CAPTURE_CONTENT: "1"
|
||||
OPENCLAW_QA_TRANSPORT_READY_TIMEOUT_MS: "180000"
|
||||
|
||||
15
.github/workflows/windows-testbox-probe.yml
vendored
15
.github/workflows/windows-testbox-probe.yml
vendored
@@ -85,12 +85,22 @@ jobs:
|
||||
env:
|
||||
ENABLE_WSL2_FEATURES: ${{ inputs.enable_wsl2_features }}
|
||||
IMPORT_UBUNTU_WSL2: ${{ inputs.import_ubuntu_wsl2 }}
|
||||
UBUNTU_WSL_ROOTFS_URL: https://cloud-images.ubuntu.com/wsl/releases/24.04/current/ubuntu-noble-wsl-amd64-wsl.rootfs.tar.gz
|
||||
run: |
|
||||
$ErrorActionPreference = "Continue"
|
||||
$ok = $false
|
||||
$restartRequired = $false
|
||||
|
||||
function Resolve-UbuntuWslRootfsUrl {
|
||||
$osArch = ([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture).ToString().ToLowerInvariant()
|
||||
switch ($osArch) {
|
||||
"x64" { $wslArch = "amd64" }
|
||||
"arm64" { $wslArch = "arm64" }
|
||||
default { throw "Unsupported Windows architecture for Ubuntu WSL rootfs: $osArch" }
|
||||
}
|
||||
Write-Host "ubuntu_wsl_rootfs_arch=$wslArch"
|
||||
"https://cloud-images.ubuntu.com/wsl/releases/24.04/current/ubuntu-noble-wsl-$wslArch-wsl.rootfs.tar.gz"
|
||||
}
|
||||
|
||||
function Invoke-WslText {
|
||||
param([string[]] $Arguments)
|
||||
$output = & wsl.exe @Arguments 2>&1
|
||||
@@ -143,8 +153,9 @@ jobs:
|
||||
Write-Host "import_ubuntu_wsl2=true"
|
||||
$wslRoot = "C:\wsl\UbuntuProbe"
|
||||
$rootfs = "C:\wsl\ubuntu-noble-wsl.rootfs.tar.gz"
|
||||
$rootfsUrl = Resolve-UbuntuWslRootfsUrl
|
||||
New-Item -ItemType Directory -Force -Path @((Split-Path -Parent $rootfs), $wslRoot) | Out-Null
|
||||
Invoke-WebRequest -Uri $env:UBUNTU_WSL_ROOTFS_URL -OutFile $rootfs -UseBasicParsing
|
||||
Invoke-WebRequest -Uri $rootfsUrl -OutFile $rootfs -UseBasicParsing
|
||||
$import = Invoke-WslText -Arguments @("--import", "UbuntuProbe", $wslRoot, $rootfs, "--version", "2")
|
||||
Write-Host $import.Text
|
||||
Write-Host "wsl_import_exit=$($import.Code)"
|
||||
|
||||
420
CHANGELOG.md
420
CHANGELOG.md
@@ -2,6 +2,417 @@
|
||||
|
||||
Docs: https://docs.openclaw.ai
|
||||
|
||||
## 2026.6.9
|
||||
|
||||
### Highlights
|
||||
|
||||
- **Richer Telegram delivery:** Telegram now sends rich HTML, preserves rich markdown and sticker paths, renders progress drafts and command output more faithfully, and keeps mentions and spooled handlers on the right delivery path. (#93286, #93164, #93124, #93364, #93130, #93088, #93281) Thanks @obviyus, @vincentkoc, @goutamadwant, @kesslerio, @NianJiuZst, @SweetSophia, @Marvinthebored, and @aaajiao.
|
||||
- **More dependable agent recovery:** retries, terminal outcomes, usage after compaction, session history repair, and reply reconciliation now keep more interrupted or partial turns moving toward a visible final result. (#92191, #93073, #93228, #93084, #93469, #93291, #90943) Thanks @ai-hpc, @lml2468, @fuller-stack-dev, @Hollychou924, @leno23, @de1tydev, @425072024, @wuwahe3, @drvoss, @yetval, @sandieman2, and @vincentkoc.
|
||||
- **A stronger Codex integration:** Codex gains automatic plugin approvals, GPT-5.3 Spark OAuth routing, remote-node `exec` as a dynamic tool, and more reliable app-server teardown and terminal outcomes. (#92625, #89133, #93654, #91767, #93287) Thanks @kevinslin, @VACInc, @vincentkoc, @JPKay-AI, and @aliahnaf2013-max.
|
||||
- **Standalone official provider plugins:** external provider packages are now first-class npm releases, externally installed channel plugins load at Gateway startup, and StepFun is intentionally npm-only because its ClawHub package name is unavailable. (#93470) Thanks @sunlit-deng, @cxdnicole, and @vincentkoc.
|
||||
- **More capable web and native clients:** the Control UI adds a session workspace rail and extension health, iOS adds Watch controls, and Android shows chat context. (#92856, #91952, #93387, #92837) Thanks @Solvely-Colin, @jalehman, @joshavant, and @Tosko4.
|
||||
- **More useful search and skills:** Codex Hosted Search is available, key-free search providers remain deliberate opt-ins, and ClawHub skill installs retain verified source provenance. (#93446, #93616, #93283, #93506) Thanks @fuller-stack-dev, @davemorin, @momothemage, @nmccready-tars, and @vincentkoc.
|
||||
|
||||
### Changes
|
||||
|
||||
- Providers and auth: add Codex Hosted Search, improve Gemini CLI OAuth behind proxies, and keep external provider onboarding on current choices and package metadata. (#93446, #92815) Thanks @fuller-stack-dev, @yetval, @EvetteYoung, and @vincentkoc.
|
||||
- Plugins and installs: externalized official providers publish as independent npm packages, Gateway discovers installed channel plugins at startup, and StepFun installs exclusively from npm. (#93470) Thanks @sunlit-deng, @cxdnicole, and @vincentkoc.
|
||||
- Dashboard and mobile: add a session workspace rail, plugin health in status, compact cron lists, and iOS Watch controls. (#92856, #91952, #93395, #93387) Thanks @Solvely-Colin, @jalehman, @yu-xin-c, @centralpc, @joshavant, and @vincentkoc.
|
||||
- Codex and skills: add automatic plugin approvals, preserve ClawHub skill provenance, and expose remote-node execution to Codex when a node is connected. (#92625, #93283, #93654) Thanks @kevinslin, @momothemage, @nmccready-tars, @vincentkoc, and @JPKay-AI.
|
||||
- QA and release engineering: QA scenarios now use YAML, with broader profile evidence and release coverage for the plugin and channel matrix.
|
||||
|
||||
### Fixes
|
||||
|
||||
- Security and privacy: redact secrets from debug/config output, block internal HTTP session overrides, audit open-DM tool exposure, and retain plugin write ownership checks. (#93333, #88496, #93443, #92883, #93353) Thanks @Alix-007, @jason-allen-oneal, @coygeek, @RichardCao, @yu-xin-c, @cjg20ss, @eleqtrizit, and @vincentkoc.
|
||||
- Agent and session runtime: retry thinking-only and empty post-tool turns, prevent duplicate hook execution, preserve fresh usage through compaction, and repair partial JSON/history artifacts. (#92191, #93073, #93009, #93084, #93469) Thanks @ai-hpc, @lml2468, @fuller-stack-dev, @zenglingbiao, @dertbv, @Hollychou924, @leno23, @de1tydev, @425072024, @wuwahe3, @drvoss, and @vincentkoc.
|
||||
- Channels and replies: fix Telegram rich delivery and ingress recovery, preserve WhatsApp auth and media error reporting, keep Mattermost thread replies intact, and harden Discord action handling. (#93286, #93364, #93281, #93076, #93334, #93424, #93488) Thanks @obviyus, @NianJiuZst, @mcaxtr, @rushindrasinha, @amknight, @lzyyzznl, @darealgege, and @vincentkoc.
|
||||
- Storage and migrations: avoid SQLite WAL on network filesystems, clean reindex artifacts, keep setup state out of workspace dot-directories, and import default-agent auth profiles into SQLite. (#93454, #92891, #93182, #93295, #93520, #93156) Thanks @vincentkoc, @ZengWen-DT, @Zeng-wen, @potterdigital, @Alix-007, @Pick-cat, @sallyom, @1qh, and @Tazio7.
|
||||
- Provider and model behavior: fix Gemini CLI proxy OAuth, restore Codex Spark OAuth routing, correct Bedrock embedding model IDs, and preserve configured defaults in embedded runs. (#92815, #89133, #93452, #93428) Thanks @yetval, @EvetteYoung, @VACInc, @LiuwqGit, @aleck31, @zenglingbiao, @danielgerlag, and @vincentkoc.
|
||||
- CLI, TUI, and apps: accept global flags after subcommands, keep terminal output and activity indicators visible, preserve CJK IME composition, and refresh stale UI state. (#93455, #93460, #93006, #93427, #93498, #93606) Thanks @ooiuuii, @Alix-007, @ZengWen-DT, @Zeng-wen, @AlethiaQuizForge, @Zhaoqj2016, @liuhao1024, @BrianClaw1955, @vincentkoc, and @NicoBoom13.
|
||||
- Operations and updates: harden official plugin recovery, restart managed Gateways after failed update handoff, avoid Node-specific npm prefixes, and keep package validation paths reliable. (#93325, #92111, #93650) Thanks @vincentkoc, @yetval, @ofan, and @yaanfpv.
|
||||
|
||||
### Complete contribution record
|
||||
|
||||
This audited record covers the complete v2026.6.8..HEAD~1 history: 375 merged PRs. The generation manifest also supplies direct commits as editorial input; the grouped notes above prioritize user impact.
|
||||
|
||||
#### Pull requests
|
||||
|
||||
- **PR #90463** refactor: add session accessor seam with gateway consumer. Thanks @jalehman.
|
||||
- **PR #88656** Drop reasoning-only length turns from replay. Thanks @abel-zer0.
|
||||
- **PR #92856** feat(webui): add session workspace rail. Thanks @Solvely-Colin.
|
||||
- **PR #92845** docs(browser-control): document OPENCLAW_EAGER_BROWSER_CONTROL_SERVER requirement. Related #92841. Thanks @liuhao1024 and @jeugregg.
|
||||
- **PR #82366** fix: use passive periodic sqlite wal checkpoints. Related #81715. Thanks @honor2030 and @KrasimirKralev.
|
||||
- **PR #92815** fix(google): route Gemini CLI OAuth through the env proxy (#46184). Thanks @yetval and @EvetteYoung.
|
||||
- **PR #91331** fix(mattermost): merge progress preview lines by identity. Related #89761. Thanks @iloveleon19 and @leonthe8th and @vincentkoc.
|
||||
- **PR #92909** fix(tui): keep spinner active when toggling tools. Related #49763. Thanks @ZengWen-DT and @Zeng-wen and @vincentkoc and @CrimsonDump.
|
||||
- **PR #92904** fix(elevenlabs): use current TTS model ids. Thanks @vortexopenclaw and @vincentkoc.
|
||||
- **PR #92642** fix #86872: Subagent run reports success but fails to write output file. Thanks @zhangguiping-xydt and @vincentkoc and @zapper35.
|
||||
- **PR #89122** refactor: route command session reads through seam. Thanks @jalehman.
|
||||
- **PR #90943** fix(reply): deliver final reply when queued follow-up claims session; scope dedupe to routed thread. Thanks @sandieman2 and @vincentkoc.
|
||||
- **PR #92894** fix(skills): keep managed prompt paths readable. Related #92875. Thanks @kesslerio and @sallyom.
|
||||
- **PR #39617** fix: reload config in slash command routing so dmScope is respected. Related #39605. Thanks @Ciward.
|
||||
- **PR #92191** fix(agents): retry thinking-only errored turns. Related #91953. Thanks @ai-hpc and @lml2468.
|
||||
- **PR #92891** fix(memory): clean stale reindex temp files. Related #92874. Thanks @ZengWen-DT and @Zeng-wen and @vincentkoc and @potterdigital.
|
||||
- **PR #93005** Add OpenRouter Fusion guidance and prompt context. Related #92984. Thanks @sallyom.
|
||||
- **PR #88792** fix(state): harden sqlite path caching. Thanks @vincentkoc.
|
||||
- **PR #93022** fix(gateway): repair usage cost aggregation across agents. Thanks @luke-skywalker-open-claw and @stablegenius49.
|
||||
- **PR #93020** fix(telegram): cool down transient sendChatAction failures. Related #56096. Thanks @Boulea7 and @sumaiazaman and @Pick-cat and @cal-rufus.
|
||||
- **PR #89160** fix(agents): detect truncated API responses to prevent silent session hang. Related #89051. Thanks @joelnishanth and @ArthurusDent.
|
||||
- **PR #93009** fix(agents): make wrapToolWithBeforeToolCallHook idempotent to prevent double hook execution (fixes #92973). Thanks @zenglingbiao and @dertbv.
|
||||
- **PR #92991** fix(agents): tolerate missing attribution baseUrl. Related #92974. Thanks @samrusani and @Haderach-Ram.
|
||||
- **PR #92913** fix(opencode-go): register model catalog to fix context window detection. Related #92912. Thanks @kumaxs.
|
||||
- **PR #89129** refactor: route bundled plugin session callers through seam. Thanks @jalehman.
|
||||
- **PR #93084** fix(agents): preserve fresh usage after compaction. Related #50795. Thanks @Hollychou924 and @leno23 and @de1tydev and @425072024 and @vincentkoc and @wuwahe3.
|
||||
- **PR #92869** fix #90333: [Bug]: Discord image build aborts at step 66 — openclaw-build-messaging-plugins.py exits 1. Thanks @zhangguiping-xydt and @vincentkoc and @chriskosys.
|
||||
- **PR #93011** fix(gateway): accept file-only input on /v1/responses (parity with image-only). Thanks @yetval and @vincentkoc.
|
||||
- **PR #92915** Convert QA scenarios to YAML files. Thanks @RomneyDa.
|
||||
- **PR #91767** Fix one-shot Codex app-server teardown. Thanks @aliahnaf2013-max.
|
||||
- **PR #92625** feat(codex): add auto plugin approvals. Thanks @kevinslin.
|
||||
- **PR #91587** test(qa): add qa run --qa-profile and unified output summary/evidence. Thanks @RomneyDa.
|
||||
- **PR #93104** test(reply): seed channel fixtures for dedupe tests. Thanks @RomneyDa.
|
||||
- **PR #93107** test(reply): preserve telegram dedupe fallback. Thanks @RomneyDa.
|
||||
- **PR #92954** fix(memory): accept local default model path migration. Thanks @mushuiyu886 and @vincentkoc.
|
||||
- **PR #90936** fix(agents): do not misclassify client-disconnect abort as run timeout. Related #90764. Thanks @openperf and @reginaldomarcilon.
|
||||
- **PR #90812** fix(voice-call): preserve live Twilio streams in stale reaper. Related #79121. Thanks @Takhoffman and @sahibzada-allahyar and @donkeykong91.
|
||||
- **PR #93094** fix(whatsapp): bound socket operations. Thanks @mcaxtr.
|
||||
- **PR #91629** fix(scripts): add database-first legacy store guard. Related #91628. Thanks @galiniliev.
|
||||
- **PR #93124** fix(telegram): render progress drafts as rich previews. Thanks @Marvinthebored.
|
||||
- **PR #93109** test(qa): embed profile scorecard evidence. Thanks @RomneyDa.
|
||||
- **PR #87298** test: add temp directory helper guidance. Thanks @hxy91819.
|
||||
- **PR #92318** fix(cron): require explicit message target proof. Thanks @hxy91819.
|
||||
- **PR #93137** fix(imessage): honor disabled reply actions. Related #92142. Thanks @omarshahine and @dprev.
|
||||
- **PR #93134** fix(feishu): pass card_msg_content_type to get full card content (fixes #78289). Thanks @liuhao1024 and @vincentkoc and @longdoubled7.
|
||||
- **PR #93138** fix(agents): preserve literal current session resolution. Thanks @liuhao1024 and @vincentkoc.
|
||||
- **PR #91225** fix #83830: [Bug]: Dreaming diary repeats "first day" narrative every sweep — same early memories dominate snippets. Thanks @mushuiyu886 and @YinLiuLiu66.
|
||||
- **PR #93153** simplify QA evidence profile and mappings/coverage shape. Thanks @RomneyDa.
|
||||
- **PR #93164** fix(telegram): preserve rich markdown line breaks. Thanks @vincentkoc.
|
||||
- **PR #93119** fix: accept mixed source/dist bundled roots. Related #87730. Thanks @arkyu2077 and @vincentkoc and @jasonftl.
|
||||
- **PR #93130** fix(telegram): preserve sticker media paths. Related #83748. Thanks @goutamadwant and @vincentkoc and @aaajiao.
|
||||
- **PR #93073** fix(agents): retry empty post-tool final turns. Thanks @fuller-stack-dev.
|
||||
- **PR #91784** fix(voice-call): require realtime websocket path boundary. Thanks @jason-allen-oneal.
|
||||
- **PR #89133** Restore GPT-5.3 Codex Spark OAuth routing. Thanks @VACInc.
|
||||
- **PR #91996** refactor: prune unused iOS code. Thanks @zats.
|
||||
- **PR #90231** fix #69443: [Bug] Subagent RPC callback to WeChat session key routed to main session instead. Thanks @zhangguiping-xydt and @sliverp and @chen11221.
|
||||
- **PR #89920** fix(matrix): replace recovered command progress lines. Thanks @bdjben and @jesse-merhi.
|
||||
- **PR #93159** fix(tui): keep parent stdin paused after exit. Thanks @fuller-stack-dev.
|
||||
- **PR #93201** fix(auto-reply): clear pending-final state before honoring post-send abort (#89115). Thanks @amknight and @danashburn.
|
||||
- **PR #93228** fix(agents): replace prose terminal classifiers. Thanks @fuller-stack-dev.
|
||||
- **PR #93231** fix(status): correct pinned model clear hint. Thanks @hxy91819.
|
||||
- **PR #92428** fix(qqbot): keep markdown table chunks valid. Thanks @sliverp.
|
||||
- **PR #93220** fix(status): avoid stale session context windows. Thanks @hxy91819.
|
||||
- **PR #91957** perf(sessions): share one enumeration across archive retention sweeps. Thanks @amknight.
|
||||
- **PR #93281** fix(telegram): recover pid-reused ingress claims. Thanks @obviyus.
|
||||
- **PR #93287** fix(codex): preserve terminal outcome ordering.
|
||||
- **PR #93182** fix(memory): clean rollback-journal reindex temp sidecar on NFS stores. Thanks @Alix-007.
|
||||
- **PR #93283** Persist ClawHub skill install provenance. Related #92077. Thanks @momothemage and @nmccready-tars.
|
||||
- **PR #88872** fix: attribute spawned task runs to child agent. Related #66670. Thanks @Alix-007 and @Neomail2.
|
||||
- **PR #92837** fix(android): show live chat context usage. Thanks @Tosko4.
|
||||
- **PR #93325** fix(cli): harden official plugin recovery. Thanks @vincentkoc.
|
||||
- **PR #93286** feat(telegram): send rich messages as rich html. Thanks @obviyus.
|
||||
- **PR #92910** fix(memory-core): safely refresh qmd index during collection repair.
|
||||
- **PR #93329** fix(cli): allow zero Discord timeout duration. Related #93327. Thanks @rohitjavvadi.
|
||||
- **PR #91625** fix(cron): add cron edit --clear-model to clear a job's model override. Thanks @ly-wang19.
|
||||
- **PR #91691** [AI] fix(memory): prevent empty-string expectedModel in resolveMemory…. Thanks @xydt-tanshanshan.
|
||||
- **PR #93006** fix(tui): keep stderr visible when local shell stdout fills the output cap. Thanks @Alix-007.
|
||||
- **PR #93001** fix(daemon): prefer stderr over stale stdout in gateway restart diagnostics. Thanks @Alix-007.
|
||||
- **PR #91117** refactor: remove dead code and improve string concatenation. Thanks @Pommelle.
|
||||
- **PR #90893** fix(models): mask paste-token input in CLI auth prompt. Thanks @anurag-bg-neu.
|
||||
- **PR #90571** fix(configure): mask gateway password input in CLI wizard prompt. Thanks @anurag-bg-neu.
|
||||
- **PR #91768** fix(ios): respect chat header safe area. Thanks @zats.
|
||||
- **PR #93245** fix(cron): resolve lastRunStatus in cron list/show human output. Thanks @ly-wang19.
|
||||
- **PR #78765** fix(tui): avoid inserting spaces into long CJK text. Thanks @hpt.
|
||||
- **PR #91776** fix(ios): refresh permission rows after grants. Thanks @zats.
|
||||
- **PR #92817** fix(cron): trust agent output when channel is unresolved without explicit delivery. Related #90664. Thanks @fsdwen and @dertbv.
|
||||
- **PR #93297** fix(control-ui): respect agents.defaults.timeFormat for timestamps. Related #58147. Thanks @ZengWen-DT and @Zeng-wen and @TommoT2.
|
||||
- **PR #93364** Fix Telegram rich progress command output. Thanks @obviyus.
|
||||
- **PR #91952** feat(status): surface plugin health. Thanks @jalehman.
|
||||
- **PR #75025** fix(heartbeat): refresh stale Current time line on every helper call (#44993). Thanks @MoerAI and @mclee1975.
|
||||
- **PR #90992** docs(windows): fix WSL gateway-autostart recipe for WSL ≥ 2.6.1.0 idle-termination. Thanks @spencer2211.
|
||||
- **PR #86544** fix(cli): show Gemini CLI runtime auth status. Related #79585. Thanks @giodl73-repo and @fabricefoy.
|
||||
- **PR #88945** fix(plugins): serialize binding approval saves. Related #64065. Thanks @Alix-007 and @lihaokun.
|
||||
- **PR #90115** fix(gateway): pass managed inbound PDFs through chat.send. Related #90097. Thanks @harjothkhara and @joeykrug.
|
||||
- **PR #74613** docs(cli): add agent selector to CLI backend quick start. Related #68940. Thanks @vyctorbrzezowski and @drmarcopapa.
|
||||
- **PR #89121** refactor: add transcript reader seam. Thanks @jalehman.
|
||||
- **PR #84434** fix(cli): disable ScheduleWakeup/CronCreate in --print claude runs. Thanks @SkyWolfDreamer.
|
||||
- **PR #66985** fix(agents): resolve requestedNode to canonical ID before boundNode comparison. Related #87213. Thanks @mujiannan.
|
||||
- **PR #91488** fix(reply): project preflight compaction gate by next-input size on fresh tokens. Thanks @yetval.
|
||||
- **PR #93353** fix(plugins): require owner for plugin writes. Thanks @eleqtrizit.
|
||||
- **PR #91499** fix(cron): preserve scheduled turn tool policy [AI]. Thanks @mmaps.
|
||||
- **PR #90412** fix(sessions): cache warm transcript reads to avoid per-turn re-parse. Related #83943. Thanks @Alix-007 and @yyds-xxxx.
|
||||
- **PR #93118** fix(gateway): guard fast-path startup migrations. Related #93032. Thanks @openperf and @Haderach-Ram.
|
||||
- **PR #93355** fix(ci): verify performance workflow downloads. Thanks @eleqtrizit.
|
||||
- **PR #93358** fix(outbound): guard cross-context message mutations. Thanks @eleqtrizit.
|
||||
- **PR #93362** fix(flock): bind allow-always to wrapped command. Thanks @eleqtrizit.
|
||||
- **PR #92578** refactor(whatsapp): add inbound admission foundation. Thanks @mcaxtr.
|
||||
- **PR #89547** Control Telegram group history context. Thanks @mmaps.
|
||||
- **PR #89201** refactor: add transcript runtime identity contract. Thanks @jalehman.
|
||||
- **PR #93357** fix(plugins): enforce install policy in wrappers. Thanks @eleqtrizit.
|
||||
- **PR #93156** fix(doctor): import default-agent auth profiles into sqlite. Related #93145. Thanks @Pick-cat and @sallyom and @Tazio7.
|
||||
- **PR #93179** Add slim evidence mode for QA profile evidence. Thanks @RomneyDa.
|
||||
- **PR #93349** fix(control-ui): keep workboard card titles visible in overflowing columns (fixes #91717). Thanks @Pick-cat and @NicoBoom13.
|
||||
- **PR #93324** fix(cli): accept --no-color after subcommands. Thanks @ooiuuii.
|
||||
- **PR #89621** Return Google Chat thread metadata from message sends. Thanks @franco-viotti.
|
||||
- **PR #82458** fix(infra): drop duplicated "restart" word in restart-sentinel summary. Thanks @jameswniu.
|
||||
- **PR #85471** Suppress cron announce control replies. Related #85421. Thanks @TurboTheTurtle and @leatherneck-33.
|
||||
- **PR #85316** fix(auth): keep alias-compatible auth-profile overrides instead of clearing them. Thanks @SkyWolfDreamer.
|
||||
- **PR #89260** fix(doctor): separate platform-incompatible skills from missing requirements. Related #89232. Thanks @Alix-007 and @CameronWeller.
|
||||
- **PR #90846** fix(media): stop pruning media on write; let the configured timer do it. Thanks @lundog.
|
||||
- **PR #88062** fix(logging): avoid stalled warnings for active model calls. Thanks @litang9.
|
||||
- **PR #93308** fix(discord): reject malformed realtime consult calls. Thanks @khoek.
|
||||
- **PR #93334** fix(whatsapp): notify user when trailing media send fails instead of silent drop. Thanks @rushindrasinha.
|
||||
- **PR #92575** fix(sessions): preserve user behavior overrides across daily/idle rollover (#92562) [AI-assisted]. Thanks @harjothkhara and @civiltox.
|
||||
- **PR #89124** refactor: route auto-reply sessions through session seam. Thanks @jalehman.
|
||||
- **PR #93431** fix: stabilize transcript cache and CLI env isolation. Thanks @shakkernerd.
|
||||
- **PR #93412** fix(discord): suppress tool progress for message-tool replies. Thanks @mgunnin and @vincentkoc.
|
||||
- **PR #93409** fix(whatsapp): stop markdownToWhatsApp dropping code spans followed by a digit. Thanks @rushindrasinha.
|
||||
- **PR #93295** fix(memory): swap rollback-journal sidecar during atomic reindex. Thanks @Alix-007.
|
||||
- **PR #93076** fix(whatsapp): preserve auth on terminal disconnects. Thanks @mcaxtr.
|
||||
- **PR #93435** fix(agents): bound autoreview scope. Thanks @vincentkoc.
|
||||
- **PR #93279** fix(telegram): restore readable default text sends. Related #93263. Thanks @NianJiuZst and @SweetSophia.
|
||||
- **PR #93429** fix(line): cap carousel column text at 60 chars when a title or image is set. Thanks @harjothkhara and @vincentkoc.
|
||||
- **PR #93428** fix(agents): resolve configured default model in runEmbeddedAgent (fixes #93419). Thanks @zenglingbiao and @vincentkoc and @danielgerlag.
|
||||
- **PR #93427** fix(tui): show activity indicator for system-injected runs. Related #51825. Thanks @ZengWen-DT and @vincentkoc and @Zeng-wen and @AlethiaQuizForge.
|
||||
- **PR #90003** feat(policy): cover exec approvals artifact. Thanks @giodl73-repo.
|
||||
- **PR #93448** fix(guards): allow auth profile sqlite reader. Thanks @amknight.
|
||||
- **PR #93424** fix(mattermost): keep message tool replies in threads. Thanks @amknight and @vincentkoc.
|
||||
- **PR #93418** fix(telegram): forward Bot API 10.1 rich_message content to agent. Related #93410. Thanks @xzh-xydt and @vincentkoc and @0pen7ech.
|
||||
- **PR #93175** test(qa): taxonomy profiles: includeAllCategories for release profile, update some coverage. Thanks @RomneyDa.
|
||||
- **PR #93456** fix(agents): handle string assistant message content. Thanks @vincentkoc.
|
||||
- **PR #93441** fix(outbound): ignore schema-padded poll metadata on send. Related #43015. Thanks @weichengdeng and @charzhou.
|
||||
- **PR #93443** fix(gateway): block internal HTTP session overrides. Thanks @RichardCao.
|
||||
- **PR #93454** fix(sqlite): disable WAL on network filesystems. Thanks @vincentkoc.
|
||||
- **PR #90275** test: make install-safe-path symlink tests compatible with Windows. Thanks @aniruddhaadak80.
|
||||
- **PR #93464** fix(qa): suppress empty WhatsApp debug artifacts. Thanks @vincentkoc.
|
||||
- **PR #90861** fix(cli): preserve sessions_yield over MCP. Related #77426. Thanks @zhangguiping-xydt and @jarvisagimuspicard-hub.
|
||||
- **PR #90946** fix(infra): preserve inherited gateway PID across reparent during cleanup. Thanks @amittell.
|
||||
- **PR #92220** fix(media): extract large managed inbound PDFs via media-understanding. Related #90096, #90097. Thanks @amknight and @joeykrug.
|
||||
- **PR #91208** fix #91047: Plugin session-extension registry not pinned; sessions.pluginPatch fails after agent/subagent plugin-load churn. Thanks @mushuiyu886 and @teamadams.
|
||||
- **PR #92111** fix(update): restart managed gateway when update handoff fails after stop. Related #92088. Thanks @yetval and @ofan.
|
||||
- **PR #93238** fix(agents): honor disabled envelope timestamps at model boundary. Thanks @osolmaz.
|
||||
- **PR #93343** fix(codex): de-duplicate commentary notes across the raw response lane. Related #93296. Thanks @Marvinthebored and @Peetiegonzalez.
|
||||
- **PR #93361** fix(openshell): pin mirror remote mutations. Thanks @eleqtrizit.
|
||||
- **PR #93354** fix(discord): block cross-provider guild admin actions. Thanks @eleqtrizit.
|
||||
- **PR #92178** fix(gateway): normalize malformed paired access lists. Related #90654. Thanks @wangmiao0668000666 and @EmilioNicolas.
|
||||
- **PR #85254** perf(plugins): thread prepared manifestPlugins through runtime model-id normalize chain. Thanks @zeroaltitude.
|
||||
- **PR #93489** Add ClawHub content rights docs to sidebar. Thanks @Patrick-Erichsen.
|
||||
- **PR #93466** [AI] fix(feishu): guard against missing inbound in channelRuntime fallback. Thanks @xydt-tanshanshan.
|
||||
- **PR #93460** fix(cli): honor --log-level in route-first commands. Related #93457. Thanks @ooiuuii.
|
||||
- **PR #93495** fix(cron): clear delivery routing fields from cron edit. Thanks @ly-wang19 and @vincentkoc.
|
||||
- **PR #93494** docs: point PR landing at maintainer workflow. Thanks @fuller-stack-dev and @vincentkoc.
|
||||
- **PR #93487** fix(ui): add agent selector to skills page. Related #78553. Thanks @goutamadwant and @vincentkoc and @xiaobu1112.
|
||||
- **PR #93488** fix(discord): apply tool status emojis immediately to avoid override by thinking reactions. Related #92715. Thanks @lzyyzznl and @vincentkoc and @darealgege.
|
||||
- **PR #93055** fix(ui): restore provider usage pill in desktop chat composer [AI]. Thanks @harjothkhara.
|
||||
- **PR #83156** fix(matrix): accept bracketed display-name mentions. Related #83142. Thanks @wdx-agent-io and @wdongxv.
|
||||
- **PR #93333** fix(auto-reply): redact secrets in /debug show and /debug set output. Thanks @Alix-007.
|
||||
- **PR #88496** fix(auto-reply): redact secrets in config show output. Related #65623. Thanks @jason-allen-oneal and @coygeek.
|
||||
- **PR #93105** fix(doctor): repair null agents.list[].workspace values. Related #77718. Thanks @xydigit-sj and @slideshow-dingo.
|
||||
- **PR #73923** fix(ui): preserve gateway token during safe websocket url edits. Related #41545. Thanks @wsyjh8.
|
||||
- **PR #88970** fix #85871: [Bug]: Heartbeat scheduler silently fails to fire on 5.20 and all 5.x versions (regression from 4.23). Thanks @zhangguiping-xydt and @vincentkoc and @carlbjson.
|
||||
- **PR #93511** fix(imessage): normalize leading NUL echo-cache prefixes. Thanks @vincentkoc and @drvoss.
|
||||
- **PR #92594** [Bug]: ollama-cloud runtime fails DNS lookup for ai.ollama.com, while ollama/<model>:cloud works. Related #92391. Thanks @zhangguiping-xydt and @vincentkoc and @kvzsolt.
|
||||
- **PR #93512** build(docs): finish PowerShell-safe docs formatting. Related #44293. Thanks @vincentkoc and @yil337 and @aniruddhaadak80.
|
||||
- **PR #93513** fix(skills): refresh persisted snapshots after restart. Thanks @vincentkoc and @fif911 and @skadauke.
|
||||
- **PR #93517** fix(skills): quote skill-creator template description. Thanks @vincentkoc and @parubets.
|
||||
- **PR #73976** fix(memory): use per-keyword FTS search in hybrid mode #39484. Thanks @joshuakeithpa-sudo.
|
||||
- **PR #93520** fix(workspace): store setup state outside workspace dot-dir. Thanks @vincentkoc and @1qh.
|
||||
- **PR #93521** fix(onboard): skip Homebrew prompt on unsupported platforms. Related #68893. Thanks @vincentkoc and @yurivict.
|
||||
- **PR #93522** fix(feishu): send post mentions as native at elements. Thanks @vincentkoc and @gavin-ali and @YizukiAme and @Panniantong.
|
||||
- **PR #93496** fix(gateway): rotate already-stale generated transcript filename on /reset. Thanks @harjothkhara and @vincentkoc.
|
||||
- **PR #93471** fix(cron): preserve aborted isolated-run failure. Thanks @BhargavSatya and @vincentkoc.
|
||||
- **PR #93473** fix(memory): report skipped QMD embedding probe. Related #77645. Thanks @TurboTheTurtle and @vincentkoc and @aderius.
|
||||
- **PR #93498** fix(ui): preserve CJK IME composition. Related #86035. Thanks @Zhaoqj2016 and @vincentkoc.
|
||||
- **PR #93088** fix(telegram): bind bot mentions to assistant identity. Thanks @kesslerio and @vincentkoc.
|
||||
- **PR #93499** fix(nodes): return screen snapshots as media. Related #90126. Thanks @zenglingbiao and @vincentkoc and @JeffSteinbok.
|
||||
- **PR #93506** fix(skills): trust verified ClawHub source provenance. Thanks @vincentkoc.
|
||||
- **PR #93525** agents: notify chat exec empty-success completions. Thanks @vincentkoc and @wenkang-xie.
|
||||
- **PR #93446** feat: add Codex hosted web search. Thanks @fuller-stack-dev.
|
||||
- **PR #92883** fix(security): audit open dm tool exposure. Related #55612. Thanks @yu-xin-c and @vincentkoc and @cjg20ss.
|
||||
- **PR #93476** fix(mattermost): preserve Codex progress preview. Related #88766. Thanks @goutamadwant and @vincentkoc and @KelTech-Services.
|
||||
- **PR #93395** feat(cron): add compact list responses. Related #93366. Thanks @yu-xin-c and @vincentkoc and @centralpc.
|
||||
- **PR #93527** fix(cron): preserve model overrides for text payloads. Thanks @vincentkoc and @liaoandi.
|
||||
- **PR #90487** fix: harden ChatGPT Responses missing content-type streams. Thanks @anyech and @vincentkoc.
|
||||
- **PR #93528** fix(gateway): tolerate transient pre-hello clean closes. Thanks @vincentkoc and @ruanrrn.
|
||||
- **PR #93529** fix(auto-reply): allow message tool for group attachments. Related #43146. Thanks @vincentkoc and @Robcis.
|
||||
- **PR #93291** fix(reply): preserve pending thread evidence when reconciling partial send results. Thanks @yetval and @vincentkoc.
|
||||
- **PR #90572** fix(feishu): drop self-authored receive echoes. Thanks @baskduf.
|
||||
- **PR #93455** fix(cli): accept --log-level after subcommands. Thanks @ooiuuii and @vincentkoc.
|
||||
- **PR #93452** fix(bedrock): strip inference profile prefix from model ID in embedding adapter. Related #79212. Thanks @LiuwqGit and @vincentkoc and @aleck31.
|
||||
- **PR #89799** fix(cli): skip compile cache on early Node 24.x to avoid startup deadlock. Related #86550. Thanks @zhangguiping-xydt and @vincentkoc and @renyuliang000.
|
||||
- **PR #93469** fix(agents): drop partialJson streaming artifacts from session history repair. Thanks @drvoss and @vincentkoc.
|
||||
- **PR #93463** fix(codex): log app-server compaction completion. Related #83932. Thanks @goutamadwant and @vincentkoc and @aounakram.
|
||||
- **PR #93562** fix(tui): refresh after external session reset. Related #38966. Thanks @vincentkoc and @wsyjh8 and @yizhanzjz.
|
||||
- **PR #93470** fix(plugins): load externally-installed channel plugins at gateway startup. Related #93219. Thanks @sunlit-deng and @vincentkoc and @cxdnicole.
|
||||
- **PR #88796** fix(discord): resolve guildId from session channel for search actions. Related #88790. Thanks @SebTardif and @vincentkoc and @mugabuga.
|
||||
- **PR #93194** fix(agents): preserve prompt-released session metadata. Related #93193. Thanks @snowzlm.
|
||||
- **PR #89483** fix(gateway): project failed agent turns in chat history. Related #89197. Thanks @IWhatsskill and @vincentkoc and @yangiit.
|
||||
- **PR #93434** fix: avoid parent group allowlist false positive. Related #92684. Thanks @kingrubic and @vincentkoc and @motteman.
|
||||
- **PR #93449** fix(feishu): dedupe redelivered text by stable retry identity. Related #46778. Thanks @ZengWen-DT and @vincentkoc and @kingcuty.
|
||||
- **PR #93407** AGT-80 AGT-81 Fix Discord ingress ack ordering. Thanks @mgunnin and @vincentkoc.
|
||||
- **PR #93439** fix(agents): honor embedded run default model. Related #93419. Thanks @harjothkhara and @vincentkoc and @danielgerlag.
|
||||
- **PR #93565** fix(cli): summarize cleanup dry-run by label. Related #76826. Thanks @AgentArcLab and @vincentkoc and @renatomaluhy.
|
||||
- **PR #93509** fix(skills): clear orphaned idempotency pointer on corrupt-metadata re-begin. Thanks @Alix-007 and @vincentkoc.
|
||||
- **PR #93274** Clarify plugin channel config additional-property errors. Thanks @zhangguiping-xydt and @vincentkoc.
|
||||
- **PR #93555** fix(read): route text decoding through shared Windows codepage fallba…. Thanks @zhanxingxin1998 and @vincentkoc.
|
||||
- **PR #93314** fix(skills): preserve ClawHub origin provenance on readback. Thanks @Alix-007 and @vincentkoc.
|
||||
- **PR #93573** fix(acp): keep bridge sessions out of stale ACP classification [AI-assisted]. Related #38907. Thanks @eldar702 and @vincentkoc and @ninaopenclaw.
|
||||
- **PR #93398** fix(cron): emit isolated model usage diagnostics. Related #92338. Thanks @849261680 and @vincentkoc and @niks999.
|
||||
- **PR #93367** Fix SSH sandbox remote directory args. Related #93344. Thanks @dmorn and @vincentkoc.
|
||||
- **PR #93574** fix(feishu): suppress log noise for bot_p2p_chat_entered_v1 event [AI-assisted]. Related #42351. Thanks @eldar702 and @vincentkoc and @sunking0223.
|
||||
- **PR #93269** Fix tokenjuice bash results without details. Thanks @moeedahmed and @vincentkoc.
|
||||
- **PR #93575** fix(telegram): hydrate group reply-chain media into model context [AI-assisted]. Thanks @eldar702 and @vincentkoc.
|
||||
- **PR #93261** fix(plugins): resolve provider policy surface for plugin-owned CLI backends. Related #93259. Thanks @BitmapAsset and @vincentkoc.
|
||||
- **PR #93303** fix(whatsapp): bound stalled read-receipt socket operations. Thanks @Alix-007 and @vincentkoc.
|
||||
- **PR #93242** fix(mattermost): keep bare @mention with empty body instead of dropping it. Related #93205. Thanks @iloveleon19 and @vincentkoc.
|
||||
- **PR #93606** fix(ui): clear stale Talk error when session transitions to non-error state (fixes #88176). Thanks @liuhao1024 and @vincentkoc and @BrianClaw1955.
|
||||
- **PR #93607** perf(tasks): memoize reconcileInspectableTasks for same-tick calls (fixes #73531). Thanks @liuhao1024 and @vincentkoc and @slideshow-dingo.
|
||||
- **PR #93612** fix(gateway): compute sessions.usage aggregate totals from all sessions, not just the limited page (fixes #76496). Thanks @liuhao1024 and @vincentkoc and @bobsahur-robot.
|
||||
- **PR #93615** fix(telegram): recover lone active spooled handler on timeout (#84158). Thanks @0xghost42 and @vincentkoc and @crash2kx.
|
||||
- **PR #93616** Keep key-free web search providers opt-in. Thanks @davemorin and @vincentkoc.
|
||||
- **PR #93298** fix #93044: control-ui webchat double-renders agent replies when dmScope=main. Thanks @zhangguiping-xydt and @vincentkoc and @cfmilam.
|
||||
- **PR #93618** fix(feishu): filter temporary card-action-c-\* IDs from reply target to prevent Invalid open_message_id errors (fixes #56818). Thanks @liuhao1024 and @vincentkoc and @SwordImmortal.
|
||||
- **PR #93387** feat(ios): add watch action surface. Thanks @Solvely-Colin and @joshavant.
|
||||
- **PR #93648** fix(doctor): archive superseded plugin install index conflicts. Related #90418. Thanks @vincentkoc and @ramitrkar-hash.
|
||||
- **PR #93649** fix(qwen): place DashScope image prompts in user content. Related #92688. Thanks @vincentkoc and @Yachiyo404.
|
||||
- **PR #93650** fix(update): avoid per-Node npm prefixes during self-update. Related #80387. Thanks @vincentkoc and @yaanfpv.
|
||||
- **PR #93653** fix(skill-workshop): skip helper sessions during auto-capture. Thanks @vincentkoc and @zhangguiping-xydt.
|
||||
- **PR #93654** fix(codex): expose remote node exec as a Codex dynamic tool. Related #92141. Thanks @vincentkoc and @JPKay-AI.
|
||||
- **PR #93662** fix(discord): protect mention aliases in code fences. Thanks @vincentkoc and @rohitjavvadi.
|
||||
- **PR #93663** fix(clawdock): open dashboard on published port without starting deps. Related #77344. Thanks @vincentkoc and @dhoman.
|
||||
- **PR #93670** fix(browser): recover stale managed Chrome CDP listener. Related #41750. Thanks @vincentkoc and @rohitjavvadi and @kissman911.
|
||||
- **PR #93672** fix(commands): preserve multiline slash skill args. Related #79155. Thanks @vincentkoc and @web3blind.
|
||||
- **PR #93674** fix(browser): accept top-level act fields with nested requests. Related #38762. Thanks @vincentkoc and @angelusbr and @Lumos-789.
|
||||
- **PR #93678** fix(plugins): allow Dreaming sidecar through restrictive memory allowlists. Related #92536. Thanks @vincentkoc and @pradeep7127 and @resYuto.
|
||||
- **PR #93306** fix(status): ignore stale context after model switch. Thanks @hxy91819.
|
||||
- **PR #93666** fix(control-ui): copy code blocks over plain HTTP via clipboard fallback. Related #93628. Thanks @Pick-cat and @pjq2926.
|
||||
- **PR #93629** fix(reply): preserve unsent text-only finals after block pipeline streamed partial content (fixes #81078). Thanks @liuhao1024 and @Jackten.
|
||||
- **PR #93690** fix(telegram): dispatch MEDIA directives as attachments. Related #77702. Thanks @vincentkoc and @butttersbot.
|
||||
- **PR #93693** fix(gateway): ignore stale sudo scope for root user services. Related #81410. Thanks @vincentkoc and @Ericksza.
|
||||
- **PR #93646** fix(agents): return string assistant content in getLastAssistantText. Thanks @Alix-007 and @vincentkoc.
|
||||
- **PR #93687** fix(i18n): retain Codex error tails in logs. Thanks @hxy91819.
|
||||
- **PR #93630** fix(heartbeat): bootstrap plugin session targets. Thanks @ZengWen-DT and @vincentkoc.
|
||||
- **PR #93658** fix(wizard): preserve existing default model during setup auth choice [AI-assisted]. Related #64129. Thanks @ml12580 and @vegapunk9527.
|
||||
- **PR #93671** fix(respawn): rewrite pnpm versioned entry paths to stable wrapper (fixes #52313). Thanks @liuhao1024 and @vincentkoc and @RichardCao.
|
||||
- **PR #93698** Fix Telegram rich progress detail updates. Thanks @obviyus.
|
||||
- **PR #93656** fix(gateway): send approval route notices with write scope. Related #93563. Thanks @mushuiyu886 and @vincentkoc and @clawbot247-commits.
|
||||
- **PR #93665** fix(gateway): surface codex app-server returned failures. Thanks @litang9 and @vincentkoc.
|
||||
- **PR #93727** fix(context-engine): avoid turn-maintenance lane livelock. Related #77340. Thanks @vincentkoc and @baghvn and @Veda-openclaw.
|
||||
- **PR #93681** fix(llm): handle string assistant content on the OpenAI-compatible completion path. Thanks @Alix-007.
|
||||
- **PR #93722** chore(release): update appcast for 2026.6.8. Thanks @vincentkoc.
|
||||
- **PR #93677** fix(google-meet): declare realtime provider secret inputs. Related #81891. Thanks @goutamadwant and @vincentkoc and @chachi-max.
|
||||
- **PR #92947** fix(qqbot): deliver cron auto-TTS voice by trusting OpenClaw temp root. Related #92816. Thanks @ZengWen-DT and @Zeng-wen and @lewiswu1209.
|
||||
- **PR #93679** fix(whatsapp): extract GIF metadata and distinguish gifPlayback in media placeholders (fixes #49099). Thanks @liuhao1024 and @vincentkoc and @bugkill3r.
|
||||
- **PR #93688** fix(minimax): check base_resp envelope errors in TTS provider. Related #76904. Thanks @dwc1997 and @najef1979-code.
|
||||
- **PR #93714** fix: isolate async model resolution mock from sync mock in flaky test. Related #92117. Thanks @lsr911 and @wangwllu.
|
||||
- **PR #93705** test(macos): cover root command dispatch. Related #83879. Thanks @markoub and @vincentkoc and @davinci282828.
|
||||
- **PR #93711** Keep command text in progress drafts. Thanks @keshavbotagent and @vincentkoc.
|
||||
- **PR #93712** fix: scope assistant avatar override to agent ID. Related #90890. Thanks @lsr911 and @vincentkoc and @najef1979-code.
|
||||
- **PR #93725** fix(usage): prune stale usage cache temp files. Related #78939. Thanks @markoub and @Tramsrepus.
|
||||
- **PR #93726** fix(typing): start typing on reasoning deltas in thinking mode before visible text. Related #79681. Thanks @xialonglee and @novaflash82.
|
||||
- **PR #93716** fix(discord): propagate timeout through channel capabilities diagnostics. Related #77040. Thanks @xialonglee and @vincentkoc and @unicebondoc.
|
||||
- **PR #93729** fix(ollama): preserve configured API during discovery. Related #93710. Thanks @zhangguiping-xydt and @vincentkoc and @obnoxious2011-cmd.
|
||||
- **PR #93719** fix: pin plugin workspace dir for sessions.list to avoid O(rows) memo busting. Related #90814. Thanks @lsr911 and @vincentkoc and @k-l-lambda.
|
||||
- **PR #93732** fix(agents): preserve re-sent user prompt during compaction transcript rotation. Thanks @yetval.
|
||||
- **PR #93738** fix: break plugin registry type import cycle. Thanks @giodl73-repo.
|
||||
- **PR #93740** fix(sessions): release retained locks after takeover. Thanks @TurboTheTurtle.
|
||||
- **PR #93745** fix(usage): reject invalid explicit dates in usage RPC date parsing. Thanks @harjothkhara and @vincentkoc.
|
||||
- **PR #93746** fix(ui): populate realtime talk provider and transport options from talk.catalog. Thanks @shushushv and @vincentkoc.
|
||||
- **PR #93751** fix(ios): fix quick setup sheet layout design. Thanks @zats.
|
||||
- **PR #93749** fix(compaction): ignore stale persisted totalTokens in preflight gate. Thanks @yetval.
|
||||
- **PR #93753** fix: correct tautological uppercase check in tool description summarizer. Thanks @GautamKumarOffical.
|
||||
- **PR #89123** refactor: route transcript writers through session seam. Thanks @jalehman.
|
||||
- **PR #93758** feat(memory): apply outputDimensionality truncation to local GGUF embeddings (fixes #58765). Thanks @liuhao1024 and @vincentkoc and @losz5000.
|
||||
- **PR #93754** feat(inbound-meta): expose per-turn source modality. Related #50482. Thanks @liuhao1024 and @vincentkoc and @JTOrca.
|
||||
- **PR #93767** fix(reasoning-tags): strip MiniMax `mm:` namespaced reasoning tags. Thanks @DrHack1 and @vincentkoc.
|
||||
- **PR #93772** fix(feishu): recover CJK filenames from JSON file_name field (fixes #81103). Thanks @liuhao1024 and @vincentkoc and @pjuneye.
|
||||
- **PR #93773** fix(ui): scope Skill Workshop proposals to selected agent. Related #93760. Thanks @TurboTheTurtle and @vincentkoc and @hannesrudolph.
|
||||
- **PR #88750** feat(context-engine): pass runtime settings into lifecycle. Thanks @ragesaq and @jalehman.
|
||||
- **PR #93763** fix(agents): use neutral billing copy for subscription auth. Related #80877. Thanks @eldar702 and @vincentkoc and @22kyasue.
|
||||
- **PR #93818** List all ClawHub docs in sidebar. Thanks @Patrick-Erichsen.
|
||||
- **PR #93779** fix(webchat): skip textarea resize during IME composition to eliminate typing lag. Related #90800. Thanks @joelnishanth and @vincentkoc and @w10497-create.
|
||||
- **PR #93786** fix(plugins): treat refreshable catalogs as requiring runtime discovery (fixes #93775). Thanks @liuhao1024 and @St0rmz1.
|
||||
- **PR #93791** fix(memory): await search-sync before returning results to prevent stale index (fixes #52115). Thanks @liuhao1024 and @vincentkoc and @FicheallADa.
|
||||
- **PR #93780** fix(google): keep parallel Gemini tool responses in the turn after the model. Thanks @yetval and @vincentkoc.
|
||||
- **PR #93789** fix(agents): make lane suspension consistent across cooldown-precheck and embedded-runner paths. Related #93036. Thanks @joelnishanth and @vincentkoc and @kumaxs.
|
||||
- **PR #93798** fix(status): show 0 (not ?) for fresh-session context tokens. Related #93771. Thanks @Alix-007 and @vincentkoc and @anarchia-99.
|
||||
- **PR #93810** fix(cron): preserve startup overflow catch-up deferrals in start() maintenance pass. Thanks @yetval.
|
||||
- **PR #93811** Strip UTF-8 BOM when reading SKILL.md in quick_validate. Thanks @HrachShah.
|
||||
- **PR #93803** fix(ui): preserve WebChat visible messages across session switches. Related #80855. Thanks @LiuwqGit and @vincentkoc and @viagarsuker.
|
||||
- **PR #93792** fix(android): wait for node capability approval before onboarding. Thanks @Solvely-Colin and @vincentkoc.
|
||||
- **PR #93796** fix(feishu): paginate wiki node and space listing (#37626). Thanks @ZengWen-DT and @vincentkoc and @ritou11.
|
||||
- **PR #93797** fix(browser): use openTab return value to prevent wsUrl race in ensureTabAvailable (fixes #63343). Thanks @liuhao1024 and @vincentkoc and @OpenCodeEngineer.
|
||||
- **PR #93806** fix(reasoning-tags): strip MiniMax mm: tags on silent-reply and streaming paths missed by #93767. Thanks @Alix-007 and @vincentkoc.
|
||||
- **PR #93691** refactor: add gateway sessions.create lifecycle seam. Thanks @jalehman.
|
||||
- **PR #88748** fix(gemini): bridge OAuth profiles into CLI runtime. Related #88742. Thanks @jason-allen-oneal.
|
||||
- **PR #93857** fix(deps): remediate Dependabot alerts. Thanks @vincentkoc.
|
||||
- **PR #93874** fix(slack): recognize MiniMax mm: namespaced reasoning tags in monitor preview. Thanks @Alix-007.
|
||||
- **PR #93832** feat(providers): add ClawRouter managed proxy. Thanks @vincentkoc.
|
||||
- **PR #93880** fix(macos): preserve approvals migration data. Thanks @vincentkoc.
|
||||
- **PR #93903** fix(cron): reject invalid absolute timestamps. Thanks @Alix-007 and @vincentkoc.
|
||||
- **PR #93879** fix(update): use configured npm registry for update metadata. Related #79140. Thanks @vincentkoc and @sixerLiu.
|
||||
- **PR #93924** revert(providers): remove ClawRouter provider. Thanks @vincentkoc.
|
||||
- **PR #93955** fix(telegram): surface rich-message disabled state. Thanks @obviyus.
|
||||
- **PR #93881** fix(agents): route BTW through canonical Codex runtime. Related #88902. Thanks @vincentkoc and @TurboTheTurtle and @khalil-omer.
|
||||
- **PR #90192** fix(feishu): fetch quoted content before empty-message guard. Related #90177. Thanks @bladin and @sliverp and @lkxlaz.
|
||||
- **PR #93237** Fix Mattermost open DM validation. Thanks @amknight.
|
||||
- **PR #93945** feat(diagnostics): add SIEM security events. Thanks @vincentkoc.
|
||||
- **PR #87487** fix(cli): clarify mcp list registry scope. Related #65209. Thanks @Alix-007 and @slideshow-dingo.
|
||||
- **PR #24661** feat(cohere): add provider plugin. Thanks @vincentkoc.
|
||||
- **PR #93532** Expose verified ClawHub source in skill verify output. Thanks @momothemage.
|
||||
- **PR #93538** feat(codex): support app-server network proxy profiles. Thanks @vincentkoc.
|
||||
- **PR #93938** fix(telegram): guard UTF-16 surrogate pairs in outbound chunkers. Related #93921. Thanks @Nas01010101 and @vincentkoc.
|
||||
- **PR #94104** feat(agents): trace compaction summarization model calls. Thanks @amknight.
|
||||
- **PR #94108** Fix package Telegram temp root. Thanks @obviyus.
|
||||
- **PR #94113** Fix Telegram package output mount. Thanks @obviyus.
|
||||
- **PR #89062** feat(docker): support offline setup reruns. Related #70443. Thanks @Alix-007 and @safrano9999.
|
||||
- **PR #93929** fix(secrets): explicitly pass BWS_SERVER_URL to resolver for self-hosted instances. Related #93851. Thanks @Pandah97 and @vincentkoc and @AdoShan.
|
||||
- **PR #90057** Polish Workboard operations view. Thanks @fuller-stack-dev.
|
||||
- **PR #89396** fix(doctor): drop inert legacy cron notify when cron.webhook is unset. Related #44460. Thanks @Alix-007.
|
||||
- **PR #94138** fix(session): prevent stale finalizer from recreating deleted session rows. Related #40840. Thanks @xialonglee and @vincentkoc and @AL-knows.
|
||||
- **PR #93739** refactor: add session patch projection seam. Thanks @jalehman.
|
||||
- **PR #94178** fix(workspace): skip optional bootstrap files when workspace setup is already completed. Related #83593. Thanks @dwc1997 and @jsompis.
|
||||
- **PR #93363** fix(feishu): enforce account tool family gates. Thanks @eleqtrizit.
|
||||
- **PR #93813** fix(codex): keep message registered for internal turns. Related #93750. Thanks @jalehman and @hannesrudolph.
|
||||
- **PR #93659** refactor: add session reset delete lifecycle seam. Thanks @jalehman.
|
||||
- **PR #93852** ci(release): harden release controls. Thanks @vincentkoc.
|
||||
- **PR #94203** feat(codex): support remote app-server plugins. Thanks @kevinslin.
|
||||
- **PR #94263** chore: migrate claw-score skill. Thanks @RomneyDa and @kevinslin.
|
||||
- **PR #93695** refactor: add compact trim lifecycle seam. Thanks @jalehman.
|
||||
- **PR #93114** test: fold lifecycle and package proof into QA Lab. Thanks @RomneyDa.
|
||||
- **PR #93181** test: fold otel smoke into qa e2e. Thanks @RomneyDa.
|
||||
- **PR #93178** test: fold gateway smoke into qa e2e. Thanks @RomneyDa.
|
||||
- **PR #94276** qa-lab: support script-backed evidence scenarios. Thanks @Solvely-Colin and @RomneyDa.
|
||||
- **PR #94282** Support owner-qualified ClawHub skill installs. Thanks @Patrick-Erichsen.
|
||||
- **PR #93704** refactor: add session cleanup lifecycle seam. Thanks @jalehman.
|
||||
- **PR #94296** fix: require all taxonomy coverage ids for a feature - AND not OR. Thanks @RomneyDa.
|
||||
- **PR #92016** fix(plugins): compose live hook registry view for tool-call hooks. Related #91918. Thanks @amknight and @vokaplok.
|
||||
- **PR #89596** fix(policy): recognize declared tool allowlists. Thanks @giodl73-repo.
|
||||
- **PR #93713** fix: route deleted-agent session purge through lifecycle seam. Thanks @jalehman.
|
||||
- **PR #84172** fix(exec): rebuild command authorization on the Tree-sitter command planner. Thanks @jesse-merhi.
|
||||
- **PR #94332** docs: add ClawHub namespace claims to sidebar. Thanks @Patrick-Erichsen.
|
||||
- **PR #86360** fix(codex): honor bound agent exec host policy. Thanks @jesse-merhi.
|
||||
- **PR #73162** fix(slack): remove socket reconnect attempt cap so gateway stays connected indefinitely. Related #72808. Thanks @suboss87 and @tleyden.
|
||||
- **PR #94156** fix: expose OpenAI image quality and moderation CLI options. Thanks @lastguru-net and @fuller-stack-dev.
|
||||
- **PR #94350** feat: externalize GMI provider plugin. Thanks @Patrick-Erichsen and @vincentkoc.
|
||||
- **PR #94543** fix(gateway): bound config.get middleware results. Related #94265. Thanks @vincentkoc and @v-s-gusev.
|
||||
- **PR #91409** fix(update): run plugin convergence after RPC git updates. Thanks @masatohoshino.
|
||||
- **PR #94556** chore(extensions): bump tokenjuice to 0.8.1. Thanks @vincentkoc.
|
||||
- **PR #94580** fix(ci): stabilize update run gates.
|
||||
- **PR #94394** fix(infra): probe 127.0.0.1 in ensurePortAvailable to detect IPv4-only occupants. Related #94379. Thanks @Pandah97 and @wangwllu.
|
||||
- **PR #94421** fix(agents): preserve active compaction retries. Related #94391. Thanks @dexiosmb.
|
||||
- **PR #94428** fix(feishu): preserve replies before error finals. Related #94360. Thanks @xunx33.
|
||||
- **PR #93735** refactor: add restart recovery lifecycle seam. Thanks @jalehman.
|
||||
- **PR #94591** docs(release): backfill complete contribution records. Thanks @vincentkoc.
|
||||
- **PR #94588** fix(cron): retry isolated setup timeouts. Thanks @aaroneden.
|
||||
- **PR #94082** fix(cron): prevent lane timeout during long tool execution. Related #94033. Thanks @ajwan8998 and @JingWang-Star996.
|
||||
- **PR #94551** feat(firecrawl): add keyless scrape support. Thanks @vincentkoc and @developersdigest.
|
||||
- **PR #94619** test(ci): stabilize timeout-sensitive shards. Thanks @vincentkoc.
|
||||
- **PR #94048** fix(telegram): set richMessages default to false explicitly in schema. Related #93770, #93794. Thanks @Monkey-wusky and @obviyus and @Nardoa375 and @laurenceputra.
|
||||
- **PR #94118** [codex] Fix Telegram rich local Markdown link hrefs. Related #94117. Thanks @dankarization and @obviyus.
|
||||
- **PR #94646** refactor(sqlite): land database-first memory and proxy alignment. Thanks @vincentkoc.
|
||||
- **PR #94658** test(sqlite): use shared temp directory helper. Thanks @vincentkoc.
|
||||
- **PR #92135** fix(openai-embedding): preserve openai/ prefix for non-native base URLs. Related #92124. Thanks @xialonglee and @Kambrian.
|
||||
- **PR #93737** refactor: add session maintenance transaction seam. Thanks @jalehman.
|
||||
|
||||
## 2026.6.8
|
||||
|
||||
### Highlights
|
||||
@@ -234,6 +645,7 @@ This audited record covers the complete v2026.6.6..v2026.6.8 history: 192 merged
|
||||
- **PR #93159** fix(tui): keep parent stdin paused after exit. Thanks @fuller-stack-dev.
|
||||
- **PR #93616** Keep key-free web search providers opt-in. Thanks @davemorin and @vincentkoc.
|
||||
- **PR #93164** fix(telegram): preserve rich markdown line breaks. Thanks @vincentkoc.
|
||||
|
||||
## 2026.6.7
|
||||
|
||||
### Highlights
|
||||
@@ -320,6 +732,7 @@ This audited record covers the complete v2026.6.6..v2026.6.7-beta.1 history: 59
|
||||
- **PR #92605** fix(docs): pin Windows Hub download links to v2026.6.5. Related #92470. Thanks @lzyyzznl and @arjkul.
|
||||
- **PR #92593** #92589: fix(internal-runtime-context): wrap prompt-preface runtime context body in delimiters. Thanks @zhangqueping and @jovi2014-cyber.
|
||||
- **PR #92606** Run Vitest and Playwright scenarios from qa suite. Thanks @RomneyDa.
|
||||
|
||||
## 2026.6.6
|
||||
|
||||
### Highlights
|
||||
@@ -557,6 +970,7 @@ This audited record covers the complete v2026.6.5..v2026.6.6 history: 198 merged
|
||||
- **PR #92150** fix(release): gate beta publish on plugin verification. Thanks @vincentkoc.
|
||||
- **PR #92158** fix(cli): validate gateway RPC timeout inputs. Thanks @ruanrrn and @comeran.
|
||||
- **PR #91911** fix(agents): retry same model across short rate-limit windows. Thanks @lanzhi-lee.
|
||||
|
||||
## 2026.6.5
|
||||
|
||||
### Highlights
|
||||
@@ -741,6 +1155,7 @@ This audited record covers the complete v2026.6.2-beta.1..v2026.6.5 history: 142
|
||||
- **PR #89659** fix(feishu): retry on send rate-limit errors (230020/230006). Related #70879. Thanks @ladygege and @marshallm-create and @sliverp and @AxelHu.
|
||||
- **PR #91547** Fix Docker store seed target packages. Related #91035. Thanks @sallyom and @laurenceputra.
|
||||
- **PR #91423** feat(qqbot): add /bot-group-allways command to toggle mention requirement. Thanks @cxyhhhhh.
|
||||
|
||||
## 2026.6.2
|
||||
|
||||
### Highlights
|
||||
@@ -833,6 +1248,7 @@ This audited record covers the complete v2026.6.1..v2026.6.2-beta.1 history: 57
|
||||
- **PR #89176** fix(browser): honor tab timeout for Chrome MCP. Related #88213. Thanks @MonkeyLeeT and @lamkan0210.
|
||||
- **PR #90043** fix: restore Skill Workshop current chat toggle. Thanks @shakkernerd.
|
||||
- **PR #81422** fix(update): surface plugin channel fallbacks. Thanks @BKF-Gitty.
|
||||
|
||||
## 2026.6.1
|
||||
|
||||
### Highlights
|
||||
@@ -1047,6 +1463,7 @@ This audited record covers the complete v2026.5.31-beta.4..v2026.6.1 history: 11
|
||||
- **PR #88288** fix(config): skip state-dir dotenv values that are unresolved shell references. Related #88274. Thanks @Alix-007 and @mathias15010.
|
||||
- **PR #88305** fix(browser): isolate Chrome MCP pending attach aborts. Related #88304. Thanks @rohitjavvadi.
|
||||
- **PR #74089** fix(openai/tts): handle [[tts:speed]] directive in OpenAI speech provider (#12163). Thanks @stainlu and @useramuser.
|
||||
|
||||
## 2026.5.31
|
||||
|
||||
### Highlights
|
||||
@@ -1177,7 +1594,7 @@ This audited record covers the complete v2026.5.28..v2026.5.31-beta.4 history: 4
|
||||
- **PR #88346** refactor: extract web content core package.
|
||||
- **PR #71280** test(gateway): avoid brittle shutdown timer assertion. Thanks @hansolo949.
|
||||
- **PR #80686** fix(agents): extend session-write-lock payload-less orphan grace from 5s to 30s. Thanks @wAngByg.
|
||||
- **PR #88067** fix(responses): drop orphaned assistant msg_* id when reasoning is dropped (#88019). Thanks @BSG2000.
|
||||
- **PR #88067** fix(responses): drop orphaned assistant msg\_\* id when reasoning is dropped (#88019). Thanks @BSG2000.
|
||||
- **PR #88417** [codex] Route denied exec approval followups to sessions. Related #88167. Thanks @brokemac79 and @jhartman00.
|
||||
- **PR #85996** fix #85782: surface terminal TUI lifecycle errors. Thanks @zhangguiping-xydt and @vincentkoc and @shakkernerd.
|
||||
- **PR #88445** refactor: source model catalog types from core.
|
||||
@@ -1476,6 +1893,7 @@ This audited record covers the complete v2026.5.28..v2026.5.31-beta.4 history: 4
|
||||
- **PR #88978** perf(ui): skip closed slash menu rerenders. Thanks @vincentkoc.
|
||||
- **PR #88982** fix(test): wait for telegram timer flushes. Thanks @vincentkoc.
|
||||
- **PR #88989** perf(ui): guard chat transcript rerenders. Thanks @vincentkoc.
|
||||
|
||||
## 2026.5.28
|
||||
|
||||
### Highlights
|
||||
|
||||
@@ -898,32 +898,38 @@ private fun SettingsShellScreen(
|
||||
ProfilePanel(displayName = displayName.ifBlank { "OpenClaw" }, onClick = { onRouteChange(SettingsRoute.Profile) })
|
||||
}
|
||||
|
||||
item {
|
||||
SettingsGroup(
|
||||
rows =
|
||||
listOf(
|
||||
SettingsRow("Profile", displayName.ifBlank { "Local device" }, Icons.Default.Person, route = SettingsRoute.Profile),
|
||||
SettingsRow("Voice", if (speakerEnabled) "Speaker on" else "Speaker muted", Icons.Default.Mic, route = SettingsRoute.Voice),
|
||||
SettingsRow("Agents", if (agents.isEmpty()) "Load from gateway" else "${agents.size} available", Icons.Default.Person, status = agents.isNotEmpty(), route = SettingsRoute.Agents),
|
||||
SettingsRow("Approvals", approvalsSummary(pendingToolCalls.size), Icons.Default.Lock, status = approvalsStatus(pendingToolCalls.size), route = SettingsRoute.Approvals),
|
||||
SettingsRow("Cron Jobs", cronJobsSummary(cronStatus.jobs), Icons.Outlined.AccessTime, status = if (cronStatus.jobs > 0) cronStatus.enabled else null, route = SettingsRoute.CronJobs),
|
||||
SettingsRow("Usage", usageSummaryText(usageSummary.providers.size), Icons.Default.Storage, status = if (usageSummary.providers.isNotEmpty()) true else null, route = SettingsRoute.Usage),
|
||||
SettingsRow("Skills", skillsSummaryText(skillsSummary.skills), Icons.Default.Settings, status = skillsStatus(skillsSummary.skills), route = SettingsRoute.Skills),
|
||||
SettingsRow("Nodes & Devices", nodesDevicesSummaryText(nodesDevicesSummary), Icons.Default.Cloud, status = nodesDevicesStatus(nodesDevicesSummary), route = SettingsRoute.NodesDevices),
|
||||
SettingsRow("Channels", channelsSummaryText(channelsSummary), Icons.Default.Notifications, status = channelsStatus(channelsSummary), route = SettingsRoute.Channels),
|
||||
SettingsRow("Dreaming", dreamingSummaryText(dreamingSummary), Icons.Default.Storage, status = dreamingStatus(dreamingSummary), route = SettingsRoute.Dreaming),
|
||||
SettingsRow("Canvas", "Screen surface", Icons.AutoMirrored.Filled.ScreenShare, status = isConnected, route = SettingsRoute.Canvas),
|
||||
SettingsRow("Notifications", if (notificationForwardingEnabled) "Smart delivery" else "Off", Icons.Default.Notifications, route = SettingsRoute.Notifications),
|
||||
SettingsRow("Phone Capabilities", if (cameraEnabled) "Camera enabled" else "Locked", Icons.Default.Lock, status = !cameraEnabled, route = SettingsRoute.PhoneCapabilities),
|
||||
SettingsRow("Gateway", gatewaySummary(statusText, isConnected), Icons.Default.Cloud, status = isConnected, route = SettingsRoute.Gateway),
|
||||
SettingsRow("Appearance", appearanceThemeSummary(appearanceThemeMode), Icons.Default.Palette, route = SettingsRoute.Appearance),
|
||||
SettingsRow("Health", "Diagnostics", Icons.Default.Settings, status = isConnected, route = SettingsRoute.Health),
|
||||
SettingsRow("About", "Version and update", Icons.Default.Storage, route = SettingsRoute.About),
|
||||
),
|
||||
onOpen = onRouteChange,
|
||||
val settingsRows =
|
||||
listOf(
|
||||
SettingsRow("Gateway", gatewaySummary(statusText, isConnected), Icons.Default.Cloud, status = isConnected, route = SettingsRoute.Gateway),
|
||||
SettingsRow("Nodes & Devices", nodesDevicesSummaryText(nodesDevicesSummary), Icons.Default.Cloud, status = nodesDevicesStatus(nodesDevicesSummary), route = SettingsRoute.NodesDevices),
|
||||
SettingsRow("Channels", channelsSummaryText(channelsSummary), Icons.Default.Notifications, status = channelsStatus(channelsSummary), route = SettingsRoute.Channels),
|
||||
SettingsRow("Agents", if (agents.isEmpty()) "Load from gateway" else "${agents.size} available", Icons.Default.Person, status = agents.isNotEmpty(), route = SettingsRoute.Agents),
|
||||
SettingsRow("Approvals", approvalsSummary(pendingToolCalls.size), Icons.Default.Lock, status = approvalsStatus(pendingToolCalls.size), route = SettingsRoute.Approvals),
|
||||
SettingsRow("Cron Jobs", cronJobsSummary(cronStatus.jobs), Icons.Outlined.AccessTime, status = if (cronStatus.jobs > 0) cronStatus.enabled else null, route = SettingsRoute.CronJobs),
|
||||
SettingsRow("Usage", usageSummaryText(usageSummary.providers.size), Icons.Default.Storage, status = if (usageSummary.providers.isNotEmpty()) true else null, route = SettingsRoute.Usage),
|
||||
SettingsRow("Skills", skillsSummaryText(skillsSummary.skills), Icons.Default.Settings, status = skillsStatus(skillsSummary.skills), route = SettingsRoute.Skills),
|
||||
SettingsRow("Dreaming", dreamingSummaryText(dreamingSummary), Icons.Default.Storage, status = dreamingStatus(dreamingSummary), route = SettingsRoute.Dreaming),
|
||||
SettingsRow("Voice", if (speakerEnabled) "Speaker on" else "Speaker muted", Icons.Default.Mic, route = SettingsRoute.Voice),
|
||||
SettingsRow("Canvas", "Screen surface", Icons.AutoMirrored.Filled.ScreenShare, status = isConnected, route = SettingsRoute.Canvas),
|
||||
SettingsRow("Notifications", if (notificationForwardingEnabled) "Smart delivery" else "Off", Icons.Default.Notifications, route = SettingsRoute.Notifications),
|
||||
SettingsRow("Phone Capabilities", if (cameraEnabled) "Camera enabled" else "Locked", Icons.Default.Lock, status = !cameraEnabled, route = SettingsRoute.PhoneCapabilities),
|
||||
SettingsRow("Appearance", appearanceThemeSummary(appearanceThemeMode), Icons.Default.Palette, route = SettingsRoute.Appearance),
|
||||
SettingsRow("About", "Version and update", Icons.Default.Storage, route = SettingsRoute.About),
|
||||
SettingsRow("Health", "Diagnostics", Icons.Default.Settings, status = isConnected, route = SettingsRoute.Health),
|
||||
)
|
||||
|
||||
settingsSections(settingsRows).forEach { section ->
|
||||
item {
|
||||
SettingsSectionTitle(section.title)
|
||||
}
|
||||
item {
|
||||
SettingsGroup(rows = section.rows, onOpen = onRouteChange)
|
||||
}
|
||||
}
|
||||
|
||||
item {
|
||||
SettingsSectionTitle("Account")
|
||||
}
|
||||
item {
|
||||
SettingsGroup(
|
||||
rows = listOf(SettingsRow("Sign Out", "Disconnect", Icons.AutoMirrored.Filled.ExitToApp)),
|
||||
@@ -1057,7 +1063,7 @@ private fun dreamingStatus(summary: GatewayDreamingSummary): Boolean? =
|
||||
else -> null
|
||||
}
|
||||
|
||||
private data class SettingsRow(
|
||||
internal data class SettingsRow(
|
||||
val title: String,
|
||||
val value: String,
|
||||
val icon: ImageVector,
|
||||
@@ -1065,6 +1071,65 @@ private data class SettingsRow(
|
||||
val route: SettingsRoute? = null,
|
||||
)
|
||||
|
||||
internal data class SettingsSection(
|
||||
val title: String,
|
||||
val rows: List<SettingsRow>,
|
||||
)
|
||||
|
||||
internal fun settingsSections(rows: List<SettingsRow>): List<SettingsSection> =
|
||||
settingsSectionOrder.mapNotNull { title ->
|
||||
val sectionRows = rows.filter { row -> row.route?.let(::settingsSectionTitleForRoute) == title }
|
||||
if (sectionRows.isEmpty()) null else SettingsSection(title = title, rows = sectionRows)
|
||||
}
|
||||
|
||||
private val settingsSectionOrder =
|
||||
listOf(
|
||||
"Connection",
|
||||
"Agents & automation",
|
||||
"Phone context & privacy",
|
||||
"Profile & device",
|
||||
"Diagnostics",
|
||||
)
|
||||
|
||||
internal fun settingsSectionTitleForRoute(route: SettingsRoute): String =
|
||||
when (route) {
|
||||
SettingsRoute.Gateway,
|
||||
SettingsRoute.NodesDevices,
|
||||
SettingsRoute.Channels,
|
||||
-> "Connection"
|
||||
|
||||
SettingsRoute.Agents,
|
||||
SettingsRoute.Approvals,
|
||||
SettingsRoute.CronJobs,
|
||||
SettingsRoute.Usage,
|
||||
SettingsRoute.Skills,
|
||||
SettingsRoute.Dreaming,
|
||||
-> "Agents & automation"
|
||||
|
||||
SettingsRoute.Voice,
|
||||
SettingsRoute.Canvas,
|
||||
SettingsRoute.Notifications,
|
||||
SettingsRoute.PhoneCapabilities,
|
||||
-> "Phone context & privacy"
|
||||
|
||||
SettingsRoute.Profile,
|
||||
SettingsRoute.Appearance,
|
||||
SettingsRoute.About,
|
||||
-> "Profile & device"
|
||||
|
||||
SettingsRoute.Health -> "Diagnostics"
|
||||
SettingsRoute.Home -> "Diagnostics"
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SettingsSectionTitle(title: String) {
|
||||
Text(
|
||||
text = title.uppercase(),
|
||||
style = ClawTheme.type.caption.copy(fontSize = 12.sp, lineHeight = 16.sp),
|
||||
color = ClawTheme.colors.textMuted,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ProfilePanel(
|
||||
displayName: String,
|
||||
|
||||
@@ -7,6 +7,8 @@ import ai.openclaw.app.GatewayNodeApprovalState
|
||||
import ai.openclaw.app.GatewayNodeSummary
|
||||
import ai.openclaw.app.GatewayNodesDevicesSummary
|
||||
import ai.openclaw.app.GatewayPendingDeviceSummary
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Settings
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
@@ -155,7 +157,46 @@ class ShellScreenLogicTest {
|
||||
assertEquals("Node approval pending", rows.single().subtitle)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun settingsSectionTitlesGroupPowerSettingsByMeaning() {
|
||||
assertEquals("Connection", settingsSectionTitleForRoute(SettingsRoute.Gateway))
|
||||
assertEquals("Connection", settingsSectionTitleForRoute(SettingsRoute.NodesDevices))
|
||||
assertEquals("Agents & automation", settingsSectionTitleForRoute(SettingsRoute.Approvals))
|
||||
assertEquals("Agents & automation", settingsSectionTitleForRoute(SettingsRoute.CronJobs))
|
||||
assertEquals("Phone context & privacy", settingsSectionTitleForRoute(SettingsRoute.PhoneCapabilities))
|
||||
assertEquals("Phone context & privacy", settingsSectionTitleForRoute(SettingsRoute.Notifications))
|
||||
assertEquals("Profile & device", settingsSectionTitleForRoute(SettingsRoute.Appearance))
|
||||
assertEquals("Diagnostics", settingsSectionTitleForRoute(SettingsRoute.Health))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun settingsSectionsPreserveMeaningfulOrder() {
|
||||
val sections =
|
||||
settingsSections(
|
||||
listOf(
|
||||
settingsRow(SettingsRoute.Voice),
|
||||
settingsRow(SettingsRoute.Agents),
|
||||
settingsRow(SettingsRoute.Gateway),
|
||||
settingsRow(SettingsRoute.Appearance),
|
||||
settingsRow(SettingsRoute.Health),
|
||||
),
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
listOf(
|
||||
"Connection",
|
||||
"Agents & automation",
|
||||
"Phone context & privacy",
|
||||
"Profile & device",
|
||||
"Diagnostics",
|
||||
),
|
||||
sections.map { it.title },
|
||||
)
|
||||
}
|
||||
|
||||
private fun emptyChannels(): GatewayChannelsSummary = GatewayChannelsSummary(channels = emptyList())
|
||||
|
||||
private fun emptyNodesDevices(): GatewayNodesDevicesSummary = GatewayNodesDevicesSummary(nodes = emptyList(), pendingDevices = emptyList(), pairedDevices = emptyList())
|
||||
|
||||
private fun settingsRow(route: SettingsRoute): SettingsRow = SettingsRow(route.name, "Value", Icons.Default.Settings, route = route)
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ report_include:
|
||||
- Sources/**
|
||||
- ShareExtension/**
|
||||
- ActivityWidget/**
|
||||
- WatchExtension/Sources/**
|
||||
- WatchApp/Sources/**
|
||||
build_arguments:
|
||||
- -destination
|
||||
- generic/platform=iOS Simulator
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"signingRepo": "git@github.com:openclaw/apps-signing.git",
|
||||
"signingBranch": "main",
|
||||
"profileType": "appstore",
|
||||
"appGroupId": "group.ai.openclawfoundation.app.shared",
|
||||
"targets": [
|
||||
{
|
||||
"target": "OpenClaw",
|
||||
@@ -11,7 +12,8 @@
|
||||
"platform": "IOS",
|
||||
"profileKey": "OPENCLAW_APP_PROFILE",
|
||||
"profileName": "OpenClaw App Store ai.openclawfoundation.app",
|
||||
"capabilities": ["PUSH_NOTIFICATIONS"]
|
||||
"capabilities": ["PUSH_NOTIFICATIONS", "APP_GROUPS"],
|
||||
"appGroups": ["group.ai.openclawfoundation.app.shared"]
|
||||
},
|
||||
{
|
||||
"target": "OpenClawShareExtension",
|
||||
@@ -20,7 +22,8 @@
|
||||
"platform": "IOS",
|
||||
"profileKey": "OPENCLAW_SHARE_PROFILE",
|
||||
"profileName": "OpenClaw App Store ai.openclawfoundation.app.share",
|
||||
"capabilities": []
|
||||
"capabilities": ["APP_GROUPS"],
|
||||
"appGroups": ["group.ai.openclawfoundation.app.shared"]
|
||||
},
|
||||
{
|
||||
"target": "OpenClawActivityWidget",
|
||||
@@ -39,15 +42,6 @@
|
||||
"profileKey": "OPENCLAW_WATCH_APP_PROFILE",
|
||||
"profileName": "OpenClaw App Store ai.openclawfoundation.app.watchkitapp",
|
||||
"capabilities": []
|
||||
},
|
||||
{
|
||||
"target": "OpenClawWatchExtension",
|
||||
"displayName": "OpenClaw Watch Extension",
|
||||
"bundleId": "ai.openclawfoundation.app.watchkitapp.extension",
|
||||
"platform": "IOS",
|
||||
"profileKey": "OPENCLAW_WATCH_EXTENSION_PROFILE",
|
||||
"profileName": "OpenClaw App Store ai.openclawfoundation.app.watchkitapp.extension",
|
||||
"capabilities": []
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -7,12 +7,11 @@ OPENCLAW_DEVELOPMENT_TEAM = $(OPENCLAW_IOS_SELECTED_TEAM)
|
||||
OPENCLAW_CODE_SIGN_STYLE = Automatic
|
||||
OPENCLAW_CODE_SIGN_IDENTITY = Apple Development
|
||||
OPENCLAW_APP_BUNDLE_ID = ai.openclawfoundation.app
|
||||
OPENCLAW_APP_GROUP_ID = group.ai.openclawfoundation.app.shared
|
||||
OPENCLAW_WATCH_APP_BUNDLE_ID = ai.openclawfoundation.app.watchkitapp
|
||||
OPENCLAW_WATCH_EXTENSION_BUNDLE_ID = ai.openclawfoundation.app.watchkitapp.extension
|
||||
OPENCLAW_ACTIVITY_WIDGET_BUNDLE_ID = ai.openclawfoundation.app.activitywidget
|
||||
OPENCLAW_ACTIVITY_WIDGET_PROFILE =
|
||||
OPENCLAW_WATCH_APP_PROFILE =
|
||||
OPENCLAW_WATCH_EXTENSION_PROFILE =
|
||||
|
||||
// Local contributors can override this by running scripts/ios-configure-signing.sh.
|
||||
// Keep include after defaults: xcconfig is evaluated top-to-bottom.
|
||||
|
||||
@@ -7,13 +7,12 @@ OPENCLAW_DEVELOPMENT_TEAM = YOUR_TEAM_ID
|
||||
|
||||
OPENCLAW_APP_BUNDLE_ID = ai.openclawfoundation.app
|
||||
OPENCLAW_SHARE_BUNDLE_ID = ai.openclawfoundation.app.share
|
||||
OPENCLAW_APP_GROUP_ID = group.ai.openclawfoundation.app.shared
|
||||
OPENCLAW_ACTIVITY_WIDGET_BUNDLE_ID = ai.openclawfoundation.app.activitywidget
|
||||
OPENCLAW_WATCH_APP_BUNDLE_ID = ai.openclawfoundation.app.watchkitapp
|
||||
OPENCLAW_WATCH_EXTENSION_BUNDLE_ID = ai.openclawfoundation.app.watchkitapp.extension
|
||||
|
||||
// Leave empty with automatic signing.
|
||||
OPENCLAW_APP_PROFILE =
|
||||
OPENCLAW_SHARE_PROFILE =
|
||||
OPENCLAW_ACTIVITY_WIDGET_PROFILE =
|
||||
OPENCLAW_WATCH_APP_PROFILE =
|
||||
OPENCLAW_WATCH_EXTENSION_PROFILE =
|
||||
|
||||
@@ -101,6 +101,7 @@ Release-owner secrets:
|
||||
|
||||
- App Store Connect API auth uses Keychain for private key material plus non-secret `apps/ios/fastlane/.env` variables.
|
||||
- The encrypted signing repo password lives outside this repo in the release-owner vault and is exposed locally as `MATCH_PASSWORD`.
|
||||
- The share sheet requires the Apple Developer App Group in `apps/ios/Config/AppStoreSigning.json` to be associated with both the app and share-extension bundle IDs before App Store profiles are regenerated.
|
||||
- Apple Distribution private keys, certificates, provisioning profiles, and decrypted signing sync output stay under `apps/ios/build/` or Keychain and are gitignored.
|
||||
- Rotating release signing means refreshing Fastlane `match` assets and pushing a fresh encrypted sync state.
|
||||
|
||||
@@ -155,7 +156,8 @@ This should create `apps/ios/fastlane/.env` with non-secret App Store Connect va
|
||||
- `ai.openclawfoundation.app.share`
|
||||
- `ai.openclawfoundation.app.activitywidget`
|
||||
- `ai.openclawfoundation.app.watchkitapp`
|
||||
- `ai.openclawfoundation.app.watchkitapp.extension`
|
||||
|
||||
The main app and share extension must both be associated with the App Group pinned in `apps/ios/Config/AppStoreSigning.json`.
|
||||
|
||||
Use `pnpm ios:release:signing:setup` for the initial portal setup, then `MATCH_PASSWORD=... pnpm ios:release:signing:sync:push` to publish encrypted Fastlane match assets to the shared private repo.
|
||||
|
||||
|
||||
@@ -41,5 +41,7 @@
|
||||
<key>NSExtensionPrincipalClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).ShareViewController</string>
|
||||
</dict>
|
||||
<key>OpenClawAppGroupIdentifier</key>
|
||||
<string>$(OPENCLAW_APP_GROUP_ID)</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
10
apps/ios/ShareExtension/OpenClawShareExtension.entitlements
Normal file
10
apps/ios/ShareExtension/OpenClawShareExtension.entitlements
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>$(OPENCLAW_APP_GROUP_ID)</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -10,8 +10,8 @@ OPENCLAW_DEVELOPMENT_TEAM = FWJYW4S8P8
|
||||
|
||||
OPENCLAW_APP_BUNDLE_ID = ai.openclawfoundation.app
|
||||
OPENCLAW_SHARE_BUNDLE_ID = ai.openclawfoundation.app.share
|
||||
OPENCLAW_APP_GROUP_ID = group.ai.openclawfoundation.app.shared
|
||||
OPENCLAW_WATCH_APP_BUNDLE_ID = ai.openclawfoundation.app.watchkitapp
|
||||
OPENCLAW_WATCH_EXTENSION_BUNDLE_ID = ai.openclawfoundation.app.watchkitapp.extension
|
||||
OPENCLAW_ACTIVITY_WIDGET_BUNDLE_ID = ai.openclawfoundation.app.activitywidget
|
||||
OPENCLAW_APNS_ENTITLEMENT_ENVIRONMENT = development
|
||||
|
||||
@@ -19,7 +19,6 @@ OPENCLAW_APP_PROFILE = ai.openclawfoundation.app Development
|
||||
OPENCLAW_SHARE_PROFILE = ai.openclawfoundation.app.share Development
|
||||
OPENCLAW_ACTIVITY_WIDGET_PROFILE =
|
||||
OPENCLAW_WATCH_APP_PROFILE =
|
||||
OPENCLAW_WATCH_EXTENSION_PROFILE =
|
||||
|
||||
// Keep local includes after defaults: xcconfig is evaluated top-to-bottom,
|
||||
// so later assignments in local files override the defaults above.
|
||||
|
||||
@@ -53,8 +53,7 @@ struct SettingsProTab: View {
|
||||
@State var suppressCredentialPersist = false
|
||||
@State var locationStatusText: String?
|
||||
@State var previousLocationModeRaw: String = OpenClawLocationMode.off.rawValue
|
||||
@State var notificationStatusText = "Checking"
|
||||
@State var notificationActionText = "Request Access"
|
||||
@State var notificationStatus: SettingsNotificationStatus = .checking
|
||||
@State var diagnosticsLastRunText = "Not run"
|
||||
@State var diagnosticsIssueCount: Int?
|
||||
@State var showTalkIssueDetails = false
|
||||
|
||||
@@ -65,7 +65,7 @@ extension SettingsProTab {
|
||||
title: "Notifications",
|
||||
detail: "Approval and event alert channel",
|
||||
value: self.notificationStatusText,
|
||||
color: self.notificationStatusText == "Allowed" ? OpenClawBrand.ok : .secondary)
|
||||
color: self.notificationStatus.color)
|
||||
Divider().padding(.leading, 60)
|
||||
self.diagnosticCheckRow(
|
||||
icon: "rectangle.on.rectangle",
|
||||
@@ -157,7 +157,7 @@ extension SettingsProTab {
|
||||
gatewayConnected: self.gatewayDiagnosticConnected,
|
||||
discoveredGatewayCount: self.gatewayController.gateways.count,
|
||||
talkConfigLoaded: self.gatewayDiagnosticTalkConfigLoaded,
|
||||
notificationStatusText: self.notificationStatusText)
|
||||
notificationsAllowed: self.notificationStatus == .allowed)
|
||||
self.diagnosticsIssueCount = issueCount
|
||||
self.diagnosticsLastRunText = SettingsDiagnostics.timestamp(Date())
|
||||
}
|
||||
@@ -422,8 +422,8 @@ extension SettingsProTab {
|
||||
}
|
||||
|
||||
func handleNotificationAction() {
|
||||
if self.notificationStatusText == "Allowed" || self.notificationStatusText == "Not Allowed" {
|
||||
self.openSystemSettings()
|
||||
if self.notificationStatus.shouldOpenNotificationSettings {
|
||||
self.openNotificationSettings()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -434,28 +434,14 @@ extension SettingsProTab {
|
||||
.sound,
|
||||
])) ?? false
|
||||
await MainActor.run {
|
||||
self.notificationStatusText = granted ? "Allowed" : "Not Allowed"
|
||||
self.notificationActionText = granted ? "Open System Settings" : "Open System Settings"
|
||||
self.notificationStatus = granted ? .allowed : .notAllowed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func applyNotificationStatus(_ status: UNAuthorizationStatus) {
|
||||
switch status {
|
||||
case .authorized, .provisional, .ephemeral:
|
||||
self.notificationStatusText = "Allowed"
|
||||
self.notificationActionText = "Open System Settings"
|
||||
case .denied:
|
||||
self.notificationStatusText = "Not Allowed"
|
||||
self.notificationActionText = "Open System Settings"
|
||||
case .notDetermined:
|
||||
self.notificationStatusText = "Not Set"
|
||||
self.notificationActionText = "Request Access"
|
||||
@unknown default:
|
||||
self.notificationStatusText = "Unknown"
|
||||
self.notificationActionText = "Open System Settings"
|
||||
}
|
||||
self.notificationStatus = SettingsNotificationStatus(status)
|
||||
}
|
||||
|
||||
func persistGatewayToken(_ value: String) {
|
||||
@@ -476,8 +462,8 @@ extension SettingsProTab {
|
||||
instanceId: instanceId)
|
||||
}
|
||||
|
||||
func openSystemSettings() {
|
||||
guard let url = URL(string: UIApplication.openSettingsURLString) else { return }
|
||||
func openNotificationSettings() {
|
||||
guard let url = URL(string: UIApplication.openNotificationSettingsURLString) else { return }
|
||||
UIApplication.shared.open(url)
|
||||
}
|
||||
|
||||
@@ -777,4 +763,12 @@ extension SettingsProTab {
|
||||
case .always: "Always"
|
||||
}
|
||||
}
|
||||
|
||||
var notificationStatusText: String {
|
||||
self.notificationStatus.text
|
||||
}
|
||||
|
||||
var notificationActionText: String {
|
||||
self.notificationStatus.actionTitle
|
||||
}
|
||||
}
|
||||
|
||||
@@ -492,7 +492,7 @@ extension SettingsProTab {
|
||||
title: "Notifications",
|
||||
detail: "Approvals and event alerts from OpenClaw.",
|
||||
value: self.notificationStatusText,
|
||||
color: self.notificationStatusText == "Allowed" ? OpenClawBrand.ok : .secondary)
|
||||
color: self.notificationStatus.color)
|
||||
|
||||
ProCard(radius: SettingsLayout.cardRadius) {
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
@@ -501,7 +501,7 @@ extension SettingsProTab {
|
||||
} label: {
|
||||
Label(
|
||||
self.notificationActionText,
|
||||
systemImage: self.notificationStatusText == "Allowed" ? "gear" : "bell.badge")
|
||||
systemImage: self.notificationStatus.actionIcon)
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
.buttonStyle(.borderedProminent)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import Darwin
|
||||
import OpenClawKit
|
||||
import SwiftUI
|
||||
import UserNotifications
|
||||
|
||||
enum SettingsRoute: Hashable {
|
||||
case gateway
|
||||
@@ -65,6 +66,63 @@ struct SettingsApprovalRow: View {
|
||||
}
|
||||
}
|
||||
|
||||
enum SettingsNotificationStatus: Equatable {
|
||||
case checking
|
||||
case allowed
|
||||
case notAllowed
|
||||
case notSet
|
||||
case unknown
|
||||
|
||||
init(_ status: UNAuthorizationStatus) {
|
||||
switch status {
|
||||
case .authorized, .provisional, .ephemeral:
|
||||
self = .allowed
|
||||
case .denied:
|
||||
self = .notAllowed
|
||||
case .notDetermined:
|
||||
self = .notSet
|
||||
@unknown default:
|
||||
self = .unknown
|
||||
}
|
||||
}
|
||||
|
||||
var text: String {
|
||||
switch self {
|
||||
case .checking: "Checking"
|
||||
case .allowed: "Allowed"
|
||||
case .notAllowed: "Not Allowed"
|
||||
case .notSet: "Not Set"
|
||||
case .unknown: "Unknown"
|
||||
}
|
||||
}
|
||||
|
||||
var actionTitle: String {
|
||||
switch self {
|
||||
case .notSet, .checking:
|
||||
"Request Access"
|
||||
case .allowed, .notAllowed, .unknown:
|
||||
"Open System Settings"
|
||||
}
|
||||
}
|
||||
|
||||
var actionIcon: String {
|
||||
self == .allowed ? "gear" : "bell.badge"
|
||||
}
|
||||
|
||||
var color: Color {
|
||||
self == .allowed ? OpenClawBrand.ok : .secondary
|
||||
}
|
||||
|
||||
var shouldOpenNotificationSettings: Bool {
|
||||
switch self {
|
||||
case .allowed, .notAllowed, .unknown:
|
||||
true
|
||||
case .checking, .notSet:
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum SettingsDiagnosticIssue: String, Equatable, CaseIterable {
|
||||
case gatewayOffline
|
||||
case discoveryUnavailable
|
||||
@@ -77,13 +135,13 @@ enum SettingsDiagnostics {
|
||||
gatewayConnected: Bool,
|
||||
discoveredGatewayCount: Int,
|
||||
talkConfigLoaded: Bool,
|
||||
notificationStatusText: String) -> [SettingsDiagnosticIssue]
|
||||
notificationsAllowed: Bool) -> [SettingsDiagnosticIssue]
|
||||
{
|
||||
var issues: [SettingsDiagnosticIssue] = []
|
||||
if !gatewayConnected { issues.append(.gatewayOffline) }
|
||||
if discoveredGatewayCount == 0 { issues.append(.discoveryUnavailable) }
|
||||
if gatewayConnected, !talkConfigLoaded { issues.append(.talkConfigMissing) }
|
||||
if notificationStatusText != "Allowed" { issues.append(.notificationsUnavailable) }
|
||||
if !notificationsAllowed { issues.append(.notificationsUnavailable) }
|
||||
return issues
|
||||
}
|
||||
|
||||
@@ -91,13 +149,13 @@ enum SettingsDiagnostics {
|
||||
gatewayConnected: Bool,
|
||||
discoveredGatewayCount: Int,
|
||||
talkConfigLoaded: Bool,
|
||||
notificationStatusText: String) -> Int
|
||||
notificationsAllowed: Bool) -> Int
|
||||
{
|
||||
self.issues(
|
||||
gatewayConnected: gatewayConnected,
|
||||
discoveredGatewayCount: discoveredGatewayCount,
|
||||
talkConfigLoaded: talkConfigLoaded,
|
||||
notificationStatusText: notificationStatusText).count
|
||||
notificationsAllowed: notificationsAllowed).count
|
||||
}
|
||||
|
||||
static func timestamp(_ date: Date) -> String {
|
||||
|
||||
@@ -78,6 +78,8 @@
|
||||
<string>OpenClaw uses on-device speech recognition for talk mode and voice wake.</string>
|
||||
<key>NSSupportsLiveActivities</key>
|
||||
<true/>
|
||||
<key>OpenClawAppGroupIdentifier</key>
|
||||
<string>$(OPENCLAW_APP_GROUP_ID)</string>
|
||||
<key>OpenClawCanonicalVersion</key>
|
||||
<string>$(OPENCLAW_IOS_VERSION)</string>
|
||||
<key>OpenClawPushAPNsEnvironment</key>
|
||||
|
||||
@@ -4,5 +4,9 @@
|
||||
<dict>
|
||||
<key>aps-environment</key>
|
||||
<string>$(OPENCLAW_APNS_ENTITLEMENT_ENVIRONMENT)</string>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>$(OPENCLAW_APP_GROUP_ID)</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -109,10 +109,10 @@ Sources/Voice/VoiceWakePreferences.swift
|
||||
ShareExtension/ShareViewController.swift
|
||||
ActivityWidget/OpenClawActivityWidgetBundle.swift
|
||||
ActivityWidget/OpenClawLiveActivity.swift
|
||||
WatchExtension/Sources/OpenClawWatchApp.swift
|
||||
WatchExtension/Sources/WatchConnectivityReceiver.swift
|
||||
WatchExtension/Sources/WatchInboxStore.swift
|
||||
WatchExtension/Sources/WatchInboxView.swift
|
||||
WatchApp/Sources/OpenClawWatchApp.swift
|
||||
WatchApp/Sources/WatchConnectivityReceiver.swift
|
||||
WatchApp/Sources/WatchInboxStore.swift
|
||||
WatchApp/Sources/WatchInboxView.swift
|
||||
../shared/OpenClawKit/Sources/OpenClawChatUI/ChatComposer.swift
|
||||
../shared/OpenClawKit/Sources/OpenClawChatUI/ChatMarkdownRenderer.swift
|
||||
../shared/OpenClawKit/Sources/OpenClawChatUI/ChatMarkdownPreprocessor.swift
|
||||
|
||||
@@ -8,7 +8,7 @@ import Testing
|
||||
gatewayConnected: false,
|
||||
discoveredGatewayCount: 0,
|
||||
talkConfigLoaded: false,
|
||||
notificationStatusText: "Not Set") == [
|
||||
notificationsAllowed: false) == [
|
||||
.gatewayOffline,
|
||||
.discoveryUnavailable,
|
||||
.notificationsUnavailable,
|
||||
@@ -21,12 +21,12 @@ import Testing
|
||||
gatewayConnected: true,
|
||||
discoveredGatewayCount: 1,
|
||||
talkConfigLoaded: false,
|
||||
notificationStatusText: "Allowed") == [.talkConfigMissing])
|
||||
notificationsAllowed: true) == [.talkConfigMissing])
|
||||
#expect(
|
||||
SettingsDiagnostics.issueCount(
|
||||
gatewayConnected: true,
|
||||
discoveredGatewayCount: 1,
|
||||
talkConfigLoaded: true,
|
||||
notificationStatusText: "Allowed") == 0)
|
||||
notificationsAllowed: true) == 0)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,10 @@ import OpenClawKit
|
||||
import Testing
|
||||
|
||||
@Suite struct ShareToAgentDeepLinkTests {
|
||||
@Test func appGroupIdentifierUsesCanonicalOpenClawGroup() {
|
||||
#expect(OpenClawAppGroup.canonicalIdentifier == "group.ai.openclawfoundation.app.shared")
|
||||
}
|
||||
|
||||
@Test func buildMessageIncludesSharedFields() {
|
||||
let payload = SharedContentPayload(
|
||||
title: "Article",
|
||||
|
||||
|
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 5.8 KiB |
@@ -20,9 +20,9 @@
|
||||
<string>$(OPENCLAW_MARKETING_VERSION)</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(OPENCLAW_BUILD_VERSION)</string>
|
||||
<key>WKApplication</key>
|
||||
<true/>
|
||||
<key>WKCompanionAppBundleIdentifier</key>
|
||||
<string>$(OPENCLAW_APP_BUNDLE_ID)</string>
|
||||
<key>WKWatchKitApp</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -1146,7 +1146,7 @@ private enum WatchNativeTextInput {
|
||||
suggestions: [String],
|
||||
onSubmit: @escaping (String) -> Void)
|
||||
{
|
||||
WKExtension.shared().visibleInterfaceController?.presentTextInputController(
|
||||
WKApplication.shared().visibleInterfaceController?.presentTextInputController(
|
||||
withSuggestions: suggestions,
|
||||
allowedInputMode: .allowEmoji)
|
||||
{ results in
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"info": {
|
||||
"author": "xcode",
|
||||
"version": 1
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>OpenClaw</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>$(OPENCLAW_MARKETING_VERSION)</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(OPENCLAW_BUILD_VERSION)</string>
|
||||
<key>NSExtension</key>
|
||||
<dict>
|
||||
<key>NSExtensionAttributes</key>
|
||||
<dict>
|
||||
<key>WKAppBundleIdentifier</key>
|
||||
<string>$(OPENCLAW_WATCH_APP_BUNDLE_ID)</string>
|
||||
</dict>
|
||||
<key>NSExtensionPointIdentifier</key>
|
||||
<string>com.apple.watchkit</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -293,6 +293,8 @@ def capture_watch_screenshot
|
||||
Dir[File.join(output_dir, "Apple Watch*-*.png")].each { |path| FileUtils.rm_f(path) }
|
||||
FileUtils.rm_rf(derived_data_path)
|
||||
|
||||
# Single-target watch apps only expose generic simulator build destinations in Xcode.
|
||||
# Keep the selected UDID for install/launch/screenshot below.
|
||||
sh(
|
||||
xcodebuild_shell_join([
|
||||
"xcodebuild",
|
||||
@@ -303,7 +305,7 @@ def capture_watch_screenshot
|
||||
"-configuration",
|
||||
"Debug",
|
||||
"-destination",
|
||||
"platform=watchOS Simulator,id=#{udid}",
|
||||
"generic/platform=watchOS Simulator",
|
||||
"-derivedDataPath",
|
||||
derived_data_path,
|
||||
"build",
|
||||
@@ -311,10 +313,8 @@ def capture_watch_screenshot
|
||||
)
|
||||
|
||||
UI.user_error!("Watch screenshot build did not produce #{app_path}.") unless File.exist?(app_path)
|
||||
extension_path = File.join(app_path, "PlugIns", "OpenClawWatchExtension.appex")
|
||||
watch_app_identifier = bundle_identifier_for_product(app_path)
|
||||
watch_extension_identifier = bundle_identifier_for_product(extension_path)
|
||||
screenshot_mode_bundle_identifiers = [watch_app_identifier, watch_extension_identifier]
|
||||
screenshot_mode_bundle_identifiers = [watch_app_identifier]
|
||||
|
||||
sh("#{shell_join(["xcrun", "simctl", "boot", udid])} >/dev/null 2>&1 || true")
|
||||
sh(shell_join(["xcrun", "simctl", "bootstatus", udid, "-b"]))
|
||||
@@ -492,6 +492,9 @@ def produce_services_for_target(target)
|
||||
if target.fetch("capabilities").include?("PUSH_NOTIFICATIONS")
|
||||
services[:push_notification] = "on"
|
||||
end
|
||||
if target.fetch("capabilities").include?("APP_GROUPS")
|
||||
services[:app_group] = "on"
|
||||
end
|
||||
services
|
||||
end
|
||||
|
||||
@@ -567,6 +570,15 @@ def profile_plist_value(profile_path, key_path)
|
||||
end
|
||||
end
|
||||
|
||||
def profile_plist_array_values(profile_path, key_path)
|
||||
raw = profile_plist_value(profile_path, key_path)
|
||||
return [] unless raw
|
||||
|
||||
raw.lines.map(&:strip).reject do |line|
|
||||
line.empty? || line == "Array {" || line == "}"
|
||||
end
|
||||
end
|
||||
|
||||
def validate_match_profile_capabilities!(target)
|
||||
capabilities = target.fetch("capabilities")
|
||||
return if capabilities.empty?
|
||||
@@ -582,6 +594,17 @@ def validate_match_profile_capabilities!(target)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
if capabilities.include?("APP_GROUPS")
|
||||
expected_app_groups = target.fetch("appGroups")
|
||||
actual_app_groups = profile_plist_array_values(profile_path, "Entitlements:com.apple.security.application-groups")
|
||||
missing = expected_app_groups - actual_app_groups
|
||||
unless missing.empty?
|
||||
UI.user_error!(
|
||||
"Provisioning profile #{target.fetch("profileName")} for #{target.fetch("bundleId")} is missing App Groups #{missing.join(", ")}; actual groups: #{actual_app_groups.empty? ? "missing" : actual_app_groups.join(", ")}."
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def sync_app_store_signing!(readonly:)
|
||||
|
||||
@@ -65,7 +65,7 @@ pnpm ios:release:signing:check
|
||||
pnpm ios:release:signing:setup
|
||||
```
|
||||
|
||||
`signing:setup` uses Fastlane `produce` and `modify_services` to create Developer Portal bundle IDs and enable required services before running `match`. If Fastlane does not already have a valid Apple Developer Portal session, run `fastlane spaceauth` for a release-owner Apple ID and export the resulting `FASTLANE_SESSION`.
|
||||
`signing:setup` uses Fastlane `produce` and `modify_services` to create Developer Portal bundle IDs and enable required services before running `match`. The main app and share extension also require the shared App Group from `apps/ios/Config/AppStoreSigning.json`; associate that group with both bundle IDs in the Apple Developer Portal before regenerating profiles. If Fastlane does not already have a valid Apple Developer Portal session, run `fastlane spaceauth` for a release-owner Apple ID and export the resulting `FASTLANE_SESSION`.
|
||||
|
||||
Shared encrypted signing storage:
|
||||
|
||||
|
||||
@@ -65,6 +65,8 @@ targets:
|
||||
embed: true
|
||||
- target: OpenClawActivityWidget
|
||||
embed: true
|
||||
# A companion watch application belongs in the standard Watch bundle location.
|
||||
# PlugIns is for extension products and breaks paired watch installation.
|
||||
- target: OpenClawWatchApp
|
||||
- package: OpenClawKit
|
||||
- package: OpenClawKit
|
||||
@@ -88,7 +90,7 @@ targets:
|
||||
exit 1
|
||||
fi
|
||||
swiftformat --lint --config "$SRCROOT/../../config/swiftformat" \
|
||||
--unexclude "$SRCROOT/Sources,$SRCROOT/ShareExtension,$SRCROOT/ActivityWidget,$SRCROOT/WatchExtension,$SRCROOT/../shared/OpenClawKit,$SRCROOT/../swabble" \
|
||||
--unexclude "$SRCROOT/Sources,$SRCROOT/ShareExtension,$SRCROOT/ActivityWidget,$SRCROOT/WatchApp,$SRCROOT/../shared/OpenClawKit,$SRCROOT/../swabble" \
|
||||
--filelist "$SRCROOT/SwiftSources.input.xcfilelist"
|
||||
- name: SwiftLint
|
||||
basedOnDependencyAnalysis: false
|
||||
@@ -140,6 +142,7 @@ targets:
|
||||
- openclaw
|
||||
CFBundleShortVersionString: "$(OPENCLAW_MARKETING_VERSION)"
|
||||
OpenClawCanonicalVersion: "$(OPENCLAW_IOS_VERSION)"
|
||||
OpenClawAppGroupIdentifier: "$(OPENCLAW_APP_GROUP_ID)"
|
||||
CFBundleVersion: "$(OPENCLAW_BUILD_VERSION)"
|
||||
UILaunchScreen: {}
|
||||
UIApplicationSceneManifest:
|
||||
@@ -192,6 +195,7 @@ targets:
|
||||
settings:
|
||||
base:
|
||||
CODE_SIGN_IDENTITY: "$(OPENCLAW_CODE_SIGN_IDENTITY)"
|
||||
CODE_SIGN_ENTITLEMENTS: ShareExtension/OpenClawShareExtension.entitlements
|
||||
CODE_SIGN_STYLE: "$(OPENCLAW_CODE_SIGN_STYLE)"
|
||||
DEVELOPMENT_TEAM: "$(OPENCLAW_DEVELOPMENT_TEAM)"
|
||||
ENABLE_APPINTENTS_METADATA: NO
|
||||
@@ -206,6 +210,7 @@ targets:
|
||||
properties:
|
||||
CFBundleDisplayName: OpenClaw Share
|
||||
CFBundleShortVersionString: "$(OPENCLAW_MARKETING_VERSION)"
|
||||
OpenClawAppGroupIdentifier: "$(OPENCLAW_APP_GROUP_ID)"
|
||||
CFBundleVersion: "$(OPENCLAW_BUILD_VERSION)"
|
||||
NSExtension:
|
||||
NSExtensionPointIdentifier: com.apple.share-services
|
||||
@@ -251,13 +256,17 @@ targets:
|
||||
NSExtensionPointIdentifier: com.apple.widgetkit-extension
|
||||
|
||||
OpenClawWatchApp:
|
||||
type: application.watchapp2
|
||||
type: application
|
||||
platform: watchOS
|
||||
deploymentTarget: "11.0"
|
||||
sources:
|
||||
- path: WatchApp
|
||||
excludes:
|
||||
- Info.plist
|
||||
dependencies:
|
||||
- target: OpenClawWatchExtension
|
||||
- sdk: AppIntents.framework
|
||||
- sdk: WatchConnectivity.framework
|
||||
- sdk: UserNotifications.framework
|
||||
configFiles:
|
||||
Debug: Config/Signing.xcconfig
|
||||
Release: Config/Signing.xcconfig
|
||||
@@ -274,6 +283,8 @@ targets:
|
||||
ENABLE_APP_INTENTS_METADATA_GENERATION: NO
|
||||
PRODUCT_BUNDLE_IDENTIFIER: "$(OPENCLAW_WATCH_APP_BUNDLE_ID)"
|
||||
PROVISIONING_PROFILE_SPECIFIER: "$(OPENCLAW_WATCH_APP_PROFILE)"
|
||||
SWIFT_STRICT_CONCURRENCY: complete
|
||||
SWIFT_VERSION: "6.0"
|
||||
info:
|
||||
path: WatchApp/Info.plist
|
||||
properties:
|
||||
@@ -281,42 +292,7 @@ targets:
|
||||
CFBundleShortVersionString: "$(OPENCLAW_MARKETING_VERSION)"
|
||||
CFBundleVersion: "$(OPENCLAW_BUILD_VERSION)"
|
||||
WKCompanionAppBundleIdentifier: "$(OPENCLAW_APP_BUNDLE_ID)"
|
||||
WKWatchKitApp: true
|
||||
|
||||
OpenClawWatchExtension:
|
||||
type: watchkit2-extension
|
||||
platform: watchOS
|
||||
deploymentTarget: "11.0"
|
||||
sources:
|
||||
- path: WatchExtension/Sources
|
||||
- path: WatchExtension/Assets.xcassets
|
||||
dependencies:
|
||||
- sdk: AppIntents.framework
|
||||
- sdk: WatchConnectivity.framework
|
||||
- sdk: UserNotifications.framework
|
||||
configFiles:
|
||||
Debug: Config/Signing.xcconfig
|
||||
Release: Config/Signing.xcconfig
|
||||
attributes:
|
||||
DevelopmentTeam: "$(OPENCLAW_DEVELOPMENT_TEAM)"
|
||||
ProvisioningStyle: "$(OPENCLAW_CODE_SIGN_STYLE)"
|
||||
settings:
|
||||
base:
|
||||
CODE_SIGN_IDENTITY: "$(OPENCLAW_CODE_SIGN_IDENTITY)"
|
||||
CODE_SIGN_STYLE: "$(OPENCLAW_CODE_SIGN_STYLE)"
|
||||
DEVELOPMENT_TEAM: "$(OPENCLAW_DEVELOPMENT_TEAM)"
|
||||
PRODUCT_BUNDLE_IDENTIFIER: "$(OPENCLAW_WATCH_EXTENSION_BUNDLE_ID)"
|
||||
PROVISIONING_PROFILE_SPECIFIER: "$(OPENCLAW_WATCH_EXTENSION_PROFILE)"
|
||||
info:
|
||||
path: WatchExtension/Info.plist
|
||||
properties:
|
||||
CFBundleDisplayName: OpenClaw
|
||||
CFBundleShortVersionString: "$(OPENCLAW_MARKETING_VERSION)"
|
||||
CFBundleVersion: "$(OPENCLAW_BUILD_VERSION)"
|
||||
NSExtension:
|
||||
NSExtensionAttributes:
|
||||
WKAppBundleIdentifier: "$(OPENCLAW_WATCH_APP_BUNDLE_ID)"
|
||||
NSExtensionPointIdentifier: com.apple.watchkit
|
||||
WKApplication: true
|
||||
|
||||
OpenClawTests:
|
||||
type: bundle.unit-test
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import AppKit
|
||||
import WebKit
|
||||
|
||||
extension CanvasWindowController {
|
||||
// MARK: - WKUIDelegate
|
||||
|
||||
/// Bridges `<input type="file">` clicks in canvas HTML to a native `NSOpenPanel`.
|
||||
/// Without a `WKUIDelegate`, WebKit silently drops the request and file-picker
|
||||
/// buttons in canvas pages do nothing.
|
||||
@MainActor
|
||||
func webView(
|
||||
_ webView: WKWebView,
|
||||
runOpenPanelWith parameters: WKOpenPanelParameters,
|
||||
initiatedByFrame frame: WKFrameInfo,
|
||||
completionHandler: @escaping @MainActor @Sendable ([URL]?) -> Void)
|
||||
{
|
||||
let panel = NSOpenPanel()
|
||||
panel.canChooseFiles = true
|
||||
panel.canChooseDirectories = parameters.allowsDirectories
|
||||
panel.allowsMultipleSelection = parameters.allowsMultipleSelection
|
||||
panel.resolvesAliases = true
|
||||
if let window = self.window {
|
||||
panel.beginSheetModal(for: window) { response in
|
||||
completionHandler(response == .OK ? panel.urls : nil)
|
||||
}
|
||||
return
|
||||
}
|
||||
panel.begin { response in
|
||||
completionHandler(response == .OK ? panel.urls : nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import OpenClawKit
|
||||
import WebKit
|
||||
|
||||
@MainActor
|
||||
final class CanvasWindowController: NSWindowController, WKNavigationDelegate, NSWindowDelegate {
|
||||
final class CanvasWindowController: NSWindowController, WKNavigationDelegate, WKUIDelegate, NSWindowDelegate {
|
||||
let sessionKey: String
|
||||
private let root: URL
|
||||
private let sessionDir: URL
|
||||
@@ -159,6 +159,7 @@ final class CanvasWindowController: NSWindowController, WKNavigationDelegate, NS
|
||||
}
|
||||
|
||||
self.webView.navigationDelegate = self
|
||||
self.webView.uiDelegate = self
|
||||
self.window?.delegate = self
|
||||
self.container.onClose = { [weak self] in
|
||||
self?.hideCanvas()
|
||||
|
||||
@@ -19,7 +19,7 @@ private final class DashboardWindowDragRegionView: NSView {
|
||||
}
|
||||
|
||||
@MainActor
|
||||
final class DashboardWindowController: NSWindowController, WKNavigationDelegate, NSWindowDelegate {
|
||||
final class DashboardWindowController: NSWindowController, WKNavigationDelegate, WKUIDelegate, NSWindowDelegate {
|
||||
private let webView: WKWebView
|
||||
private var currentURL: URL
|
||||
private var auth: DashboardWindowAuth
|
||||
@@ -44,9 +44,37 @@ final class DashboardWindowController: NSWindowController, WKNavigationDelegate,
|
||||
super.init(window: window)
|
||||
|
||||
self.webView.navigationDelegate = self
|
||||
self.webView.uiDelegate = self
|
||||
self.window?.delegate = self
|
||||
}
|
||||
|
||||
// MARK: - WKUIDelegate
|
||||
|
||||
/// Bridges `<input type="file">` clicks in the embedded Control UI to a native
|
||||
/// `NSOpenPanel`; without a `WKUIDelegate`, WebKit silently drops the request
|
||||
/// and "Choose image" / file-picker buttons do nothing.
|
||||
func webView(
|
||||
_ webView: WKWebView,
|
||||
runOpenPanelWith parameters: WKOpenPanelParameters,
|
||||
initiatedByFrame frame: WKFrameInfo,
|
||||
completionHandler: @escaping @MainActor @Sendable ([URL]?) -> Void)
|
||||
{
|
||||
let panel = NSOpenPanel()
|
||||
panel.canChooseFiles = true
|
||||
panel.canChooseDirectories = parameters.allowsDirectories
|
||||
panel.allowsMultipleSelection = parameters.allowsMultipleSelection
|
||||
panel.resolvesAliases = true
|
||||
if let window = self.window {
|
||||
panel.beginSheetModal(for: window) { response in
|
||||
completionHandler(response == .OK ? panel.urls : nil)
|
||||
}
|
||||
return
|
||||
}
|
||||
panel.begin { response in
|
||||
completionHandler(response == .OK ? panel.urls : nil)
|
||||
}
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) is not supported")
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import Foundation
|
||||
|
||||
public enum OpenClawAppGroup {
|
||||
public static let canonicalIdentifier = "group.ai.openclawfoundation.app.shared"
|
||||
|
||||
public static var identifier: String {
|
||||
let raw = Bundle.main.object(forInfoDictionaryKey: "OpenClawAppGroupIdentifier") as? String
|
||||
let trimmed = raw?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
|
||||
return trimmed.isEmpty ? self.canonicalIdentifier : trimmed
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,7 @@ public struct ShareGatewayRelayConfig: Codable, Sendable, Equatable {
|
||||
}
|
||||
|
||||
public enum ShareGatewayRelaySettings {
|
||||
private static let suiteName = "group.ai.openclaw.shared"
|
||||
private static var suiteName: String { OpenClawAppGroup.identifier }
|
||||
private static let relayConfigKey = "share.gatewayRelay.config.v1"
|
||||
private static let lastEventKey = "share.gatewayRelay.event.v1"
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import Foundation
|
||||
|
||||
public enum ShareToAgentSettings {
|
||||
private static let suiteName = "group.ai.openclaw.shared"
|
||||
private static var suiteName: String { OpenClawAppGroup.identifier }
|
||||
private static let defaultInstructionKey = "share.defaultInstruction"
|
||||
|
||||
private static var defaults: UserDefaults {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
e78623d6eace69e46950cd5d9a5cf14aa910dac1ecdf9d054a0bd9999e936061 config-baseline.json
|
||||
5ecafa3c9a59fc0675f964f6e3238b2f20625376ebad1835278c5dd7323770d3 config-baseline.core.json
|
||||
ac06b6c20a93a8543ec1bd3748ef4f7bdae5006839dd93b3fff874d0da4244aa config-baseline.json
|
||||
e7965566fdaedef445bcd562141f4f3ea1a499cf8ea5956418af7c98049bf242 config-baseline.core.json
|
||||
2d735389858305509528e74329b6f8c65d311e1471c3b4e91dc17aaab8e63a80 config-baseline.channel.json
|
||||
7c2c51b795d32e4c4c325080d59fec8fd11317c41db7db642f70e436779738bc config-baseline.plugin.json
|
||||
0039da0cf2ba2845b37db52c4cf3a0f25e367cf3d2d507c5d6f8a5e5bdfdc4d4 config-baseline.plugin.json
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
7b0d7f0a21c91718fd05151778bb8ff1f16b622599c4dd0a868d72459ad08559 plugin-sdk-api-baseline.json
|
||||
65e710ce7c379b49abf1f5d1b4ef7b4cbabf2820be87f7f300f2988f05f63ec5 plugin-sdk-api-baseline.jsonl
|
||||
c0ead0a6a428d4517c7ee5f09aa0151ba18f7051bc5c9806562dec544dfad20b plugin-sdk-api-baseline.json
|
||||
d4a0b6915c2ec8c68371b18b7a0999e48678ee243e7e9d41932d4d96390540cf plugin-sdk-api-baseline.jsonl
|
||||
|
||||
@@ -315,7 +315,7 @@ Current existing-session limits:
|
||||
- `hover`, `scrollintoview`, `drag`, `select`, `fill`, and `evaluate` reject
|
||||
per-call timeout overrides
|
||||
- `select` supports one value only
|
||||
- `wait --load networkidle` is not supported
|
||||
- `wait --load networkidle` is not supported on existing-session profiles (works on managed and raw/remote CDP)
|
||||
- file uploads require `--ref` / `--input-ref`, do not support CSS
|
||||
`--element`, and currently support one file at a time
|
||||
- dialog hooks do not support `--timeout`
|
||||
|
||||
@@ -131,7 +131,7 @@ Dreaming is the background memory consolidation system with three cooperative
|
||||
phases: **light** (sort/stage short-term material), **deep** (promote durable
|
||||
facts into `MEMORY.md`), and **REM** (reflect and surface themes).
|
||||
|
||||
- Enable with `memory.extensions.memory-core.dreaming.enabled: true`.
|
||||
- Enable with `plugins.entries.memory-core.config.dreaming.enabled: true`.
|
||||
- Toggle from chat with `/dreaming on|off` (or inspect with `/dreaming status`).
|
||||
- Dreaming runs on one managed sweep schedule (`dreaming.frequency`) and executes phases in order: light, REM, deep.
|
||||
- Only the deep phase writes durable memory to `MEMORY.md`.
|
||||
@@ -167,7 +167,7 @@ Example:
|
||||
Notes:
|
||||
|
||||
- `memory index --verbose` prints per-phase details (provider, model, sources, batch activity).
|
||||
- `memory status` includes any extra paths configured via `memory.search.extraPaths`.
|
||||
- `memory status` includes any extra paths configured via `memorySearch.extraPaths`.
|
||||
- If effectively active memory remote API key fields are configured as SecretRefs, the command resolves those values from the active gateway snapshot. If gateway is unavailable, the command fails fast.
|
||||
- Gateway version skew note: this command path requires a gateway that supports `secrets.resolve`; older gateways return an unknown-method error.
|
||||
- Tune scheduled sweep cadence with `dreaming.frequency`. Deep promotion policy is otherwise internal except for `dreaming.phases.deep.maxPromotedSnippetTokens`, which bounds promoted snippet length while keeping provenance visible. Use CLI flags on `memory promote` when you need one-off manual threshold overrides.
|
||||
|
||||
@@ -398,12 +398,12 @@ allowlist such as `["all"]`.
|
||||
|
||||
#### Data Handling
|
||||
|
||||
| Policy field | Observed state | Use when |
|
||||
| --------------------------------------------------- | ---------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- |
|
||||
| `dataHandling.sensitiveLogging.requireRedaction` | `logging.redactSensitive` | Set to `true` to reject `logging.redactSensitive: "off"`. |
|
||||
| `dataHandling.telemetry.denyContentCapture` | `diagnostics.otel.captureContent` | Set to `true` to reject telemetry content capture. |
|
||||
| `dataHandling.retention.requireSessionMaintenance` | `session.maintenance.mode` | Set to `true` to require effective session maintenance mode `enforce`. |
|
||||
| `dataHandling.memory.denySessionTranscriptIndexing` | `agents.*.memory.qmd.sessions.enabled` and `agents.*.memory.search.experimental.sessionMemory` | Set to `true` to reject session transcript indexing into memory. |
|
||||
| Policy field | Observed state | Use when |
|
||||
| --------------------------------------------------- | ------------------------------------------------------------------------------------ | ---------------------------------------------------------------------- |
|
||||
| `dataHandling.sensitiveLogging.requireRedaction` | `logging.redactSensitive` | Set to `true` to reject `logging.redactSensitive: "off"`. |
|
||||
| `dataHandling.telemetry.denyContentCapture` | `diagnostics.otel.captureContent` | Set to `true` to reject telemetry content capture. |
|
||||
| `dataHandling.retention.requireSessionMaintenance` | `session.maintenance.mode` | Set to `true` to require effective session maintenance mode `enforce`. |
|
||||
| `dataHandling.memory.denySessionTranscriptIndexing` | `memory.qmd.sessions.enabled` and `agents.*.memorySearch.experimental.sessionMemory` | Set to `true` to reject session transcript indexing into memory. |
|
||||
|
||||
#### Secrets
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ Notes:
|
||||
|
||||
- `--deep` runs live probes (WhatsApp Web + Telegram + Discord + Slack + Signal).
|
||||
- Plain `openclaw status` stays on the fast read-only path and marks memory as `not checked` instead of unavailable when it skips memory inspection. Heavy security audit, plugin compatibility, and memory-vector probes are left to `openclaw status --all`, `openclaw status --deep`, `openclaw security audit`, and `openclaw memory status --deep`.
|
||||
- `status --json --all` reports memory details from the active memory plugin runtime selected by `plugins.slots.memory`. Custom memory plugins can leave built-in `memory.search.enabled` disabled and still report their own files, chunks, vector, and FTS state.
|
||||
- `status --json --all` reports memory details from the active memory plugin runtime selected by `plugins.slots.memory`. Custom memory plugins can leave built-in `agents.defaults.memorySearch.enabled` disabled and still report their own files, chunks, vector, and FTS state.
|
||||
- `--usage` prints normalized provider usage windows as `X% left`.
|
||||
- Session status output separates `Execution:` from `Runtime:`. `Execution` is the sandbox path (`direct`, `docker/*`), while `Runtime` tells you whether the session is using `OpenClaw Default`, `OpenAI Codex`, a CLI backend, or an ACP backend such as `codex (acp/acpx)`. See [Agent runtimes](/concepts/agent-runtimes) for the provider/model/runtime distinction.
|
||||
- MiniMax's raw `usage_percent` / `usagePercent` fields are remaining quota, so OpenClaw inverts them before display; count-based fields win when present. `model_remains` responses prefer the chat-model entry, derive the window label from timestamps when needed, and include the model name in the plan label.
|
||||
|
||||
@@ -32,7 +32,6 @@ Use `openclaw wiki` when you want a compiled knowledge vault with:
|
||||
|
||||
```bash
|
||||
openclaw wiki status
|
||||
openclaw wiki --agent research status
|
||||
openclaw wiki doctor
|
||||
openclaw wiki init
|
||||
openclaw wiki ingest ./notes/alpha.md
|
||||
@@ -267,16 +266,15 @@ These require the official `obsidian` CLI on `PATH` when
|
||||
|
||||
## Configuration tie-ins
|
||||
|
||||
`openclaw wiki` resolves config for the selected `--agent` (or the configured
|
||||
default agent) from:
|
||||
`openclaw wiki` behavior is shaped by:
|
||||
|
||||
- `memory.extensions.memory-wiki.vaultMode`
|
||||
- `memory.extensions.memory-wiki.search.backend`
|
||||
- `memory.extensions.memory-wiki.search.corpus`
|
||||
- `memory.extensions.memory-wiki.bridge.*`
|
||||
- `memory.extensions.memory-wiki.obsidian.*`
|
||||
- `memory.extensions.memory-wiki.render.*`
|
||||
- `memory.extensions.memory-wiki.context.includeCompiledDigestPrompt`
|
||||
- `plugins.entries.memory-wiki.config.vaultMode`
|
||||
- `plugins.entries.memory-wiki.config.search.backend`
|
||||
- `plugins.entries.memory-wiki.config.search.corpus`
|
||||
- `plugins.entries.memory-wiki.config.bridge.*`
|
||||
- `plugins.entries.memory-wiki.config.obsidian.*`
|
||||
- `plugins.entries.memory-wiki.config.render.*`
|
||||
- `plugins.entries.memory-wiki.config.context.includeCompiledDigestPrompt`
|
||||
|
||||
See [Memory Wiki plugin](/plugins/memory-wiki) for the full config model.
|
||||
|
||||
|
||||
@@ -819,14 +819,14 @@ confirm `config.toolsAllow` names the tools that plugin actually registers.
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="Embedding provider switched or stopped working">
|
||||
If `memory.search.provider` is unset, OpenClaw uses OpenAI embeddings. Set
|
||||
`memory.search.provider` explicitly for local, Ollama, Gemini, Voyage,
|
||||
If `memorySearch.provider` is unset, OpenClaw uses OpenAI embeddings. Set
|
||||
`memorySearch.provider` explicitly for local, Ollama, Gemini, Voyage,
|
||||
Mistral, DeepInfra, Bedrock, GitHub Copilot, or OpenAI-compatible
|
||||
embeddings. If the configured provider cannot run, `memory_search` may
|
||||
degrade to lexical-only retrieval; runtime failures after a provider is
|
||||
already selected do not fall back automatically.
|
||||
|
||||
Set an optional `memory.search.fallback` only when you want a deliberate
|
||||
Set an optional `memorySearch.fallback` only when you want a deliberate
|
||||
single fallback. See [Memory Search](/concepts/memory-search) for the full
|
||||
list of providers and examples.
|
||||
|
||||
|
||||
@@ -18,10 +18,8 @@ Dreaming is **opt-in** and disabled by default.
|
||||
|
||||
Dreaming keeps two kinds of output:
|
||||
|
||||
- **Agent-private state and artifacts** under
|
||||
`memory/.dreams/agents/<agent-id>/` (recall journals, phase output, reports,
|
||||
and the Dream Diary). Normal memory search does not index this directory.
|
||||
- **Shared durable memory** in `MEMORY.md`.
|
||||
- **Machine state** in `memory/.dreams/` (recall store, phase signals, ingestion checkpoints, locks).
|
||||
- **Human-readable output** in `DREAMS.md` (or existing `dreams.md`) and optional phase report files under `memory/dreaming/<phase>/YYYY-MM-DD.md`.
|
||||
|
||||
Long-term promotion still writes only to `MEMORY.md`.
|
||||
|
||||
@@ -54,7 +52,7 @@ These phases are internal implementation details, not separate user-configured "
|
||||
- Requires `minScore`, `minRecallCount`, and `minUniqueQueries` to pass.
|
||||
- Rehydrates snippets from live daily files before writing, so stale/deleted snippets are skipped.
|
||||
- Appends promoted entries to `MEMORY.md`.
|
||||
- Writes a `## Deep Sleep` summary into the agent's `DREAMS.md` and optionally writes an agent-private report.
|
||||
- Writes a `## Deep Sleep` summary into `DREAMS.md` and optionally writes `memory/dreaming/deep/YYYY-MM-DD.md`.
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="REM phase">
|
||||
@@ -74,12 +72,7 @@ Dreaming can ingest redacted session transcripts into the dreaming corpus. When
|
||||
|
||||
## Dream Diary
|
||||
|
||||
Dreaming also keeps a narrative **Dream Diary** in
|
||||
`memory/.dreams/agents/<agent-id>/DREAMS.md`. After each phase has enough
|
||||
material, `memory-core` runs a best-effort background subagent turn and appends
|
||||
a short diary entry. It uses the default runtime model unless `dreaming.model`
|
||||
is configured. If the configured model is unavailable, Dream Diary retries once
|
||||
with the session default model.
|
||||
Dreaming also keeps a narrative **Dream Diary** in `DREAMS.md`. After each phase has enough material, `memory-core` runs a best-effort background subagent turn and appends a short diary entry. It uses the default runtime model unless `dreaming.model` is configured. If the configured model is unavailable, Dream Diary retries once with the session default model.
|
||||
|
||||
<Note>
|
||||
This diary is for human reading in the Dreams UI, not a promotion source. Dreaming-generated diary/report artifacts are excluded from short-term promotion. Only grounded memory snippets are eligible to promote into `MEMORY.md`.
|
||||
@@ -112,8 +105,7 @@ Deep ranking uses six weighted base signals plus phase reinforcement:
|
||||
| Consolidation | 0.10 | Multi-day recurrence strength |
|
||||
| Conceptual richness | 0.06 | Concept-tag density from snippet/path |
|
||||
|
||||
Light and REM phase hits add a small recency-decayed boost from agent-scoped
|
||||
dreaming state.
|
||||
Light and REM phase hits add a small recency-decayed boost from `memory/.dreams/phase-signals.json`.
|
||||
|
||||
Shadow-trial results can be layered on top of that base score as a review
|
||||
signal before any durable write. A helpful trial gives the candidate a small
|
||||
@@ -144,18 +136,16 @@ harmful verdicts map to `reject`; none of those recommendations writes to
|
||||
|
||||
## Scheduling
|
||||
|
||||
When enabled, `memory-core` auto-manages one cron job per enabled agent. Each
|
||||
sweep runs phases in order: light → REM → deep.
|
||||
When enabled, `memory-core` auto-manages one cron job for a full dreaming sweep. Each sweep runs phases in order: light → REM → deep.
|
||||
|
||||
Each cron job runs only that agent's workspace and memory state. Agents that set
|
||||
`agents.*.memory.extensions.memory-core.dreaming.enabled: false` receive no job.
|
||||
The sweep includes the primary runtime workspace and any configured agent workspaces, deduped by path, so subagent workspace fan-out does not exclude the main agent's `DREAMS.md` and memory state.
|
||||
|
||||
Default cadence behavior:
|
||||
|
||||
| Setting | Default |
|
||||
| -------------------------------------------------- | ------------- |
|
||||
| `memory.extensions.memory-core.dreaming.frequency` | `0 3 * * *` |
|
||||
| `memory.extensions.memory-core.dreaming.model` | default model |
|
||||
| Setting | Default |
|
||||
| -------------------- | ------------- |
|
||||
| `dreaming.frequency` | `0 3 * * *` |
|
||||
| `dreaming.model` | default model |
|
||||
|
||||
## Quick start
|
||||
|
||||
@@ -163,14 +153,12 @@ Default cadence behavior:
|
||||
<Tab title="Enable dreaming">
|
||||
```json
|
||||
{
|
||||
"agents": {
|
||||
"defaults": {
|
||||
"memory": {
|
||||
"extensions": {
|
||||
"memory-core": {
|
||||
"dreaming": {
|
||||
"enabled": true
|
||||
}
|
||||
"plugins": {
|
||||
"entries": {
|
||||
"memory-core": {
|
||||
"config": {
|
||||
"dreaming": {
|
||||
"enabled": true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -182,16 +170,14 @@ Default cadence behavior:
|
||||
<Tab title="Custom sweep cadence">
|
||||
```json
|
||||
{
|
||||
"agents": {
|
||||
"defaults": {
|
||||
"memory": {
|
||||
"extensions": {
|
||||
"memory-core": {
|
||||
"dreaming": {
|
||||
"enabled": true,
|
||||
"timezone": "America/Los_Angeles",
|
||||
"frequency": "0 */6 * * *"
|
||||
}
|
||||
"plugins": {
|
||||
"entries": {
|
||||
"memory-core": {
|
||||
"config": {
|
||||
"dreaming": {
|
||||
"enabled": true,
|
||||
"timezone": "America/Los_Angeles",
|
||||
"frequency": "0 */6 * * *"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -247,7 +233,7 @@ Default cadence behavior:
|
||||
|
||||
## Key defaults
|
||||
|
||||
All settings live under `memory.extensions.memory-core.dreaming`.
|
||||
All settings live under `plugins.entries.memory-core.config.dreaming`.
|
||||
|
||||
<ParamField path="enabled" type="boolean" default="false">
|
||||
Enable or disable the dreaming sweep.
|
||||
@@ -263,7 +249,7 @@ All settings live under `memory.extensions.memory-core.dreaming`.
|
||||
</ParamField>
|
||||
|
||||
<Warning>
|
||||
`memory.extensions.memory-core.dreaming.model` requires `plugins.entries.memory-core.subagent.allowModelOverride: true`. To restrict it, also set `plugins.entries.memory-core.subagent.allowedModels`. Trust or allowlist failures stay visible instead of falling back silently; the retry only covers model-unavailable errors.
|
||||
`dreaming.model` requires `plugins.entries.memory-core.subagent.allowModelOverride: true`. To restrict it, also set `plugins.entries.memory-core.subagent.allowedModels`. Trust or allowlist failures stay visible instead of falling back silently; the retry only covers model-unavailable errors.
|
||||
</Warning>
|
||||
|
||||
<Note>
|
||||
|
||||
@@ -24,7 +24,7 @@ Treat them differently from normal config:
|
||||
| Surface | Key | Use it when | More |
|
||||
| ------------------------ | ------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- |
|
||||
| Local model runtime | `agents.defaults.experimental.localModelLean`, `agents.list[].experimental.localModelLean` | A smaller or stricter local backend chokes on OpenClaw's full default tool surface | [Local Models](/gateway/local-models) |
|
||||
| Memory search | `memory.search.experimental.sessionMemory` | You want `memory_search` to index prior session transcripts and accept the extra storage/indexing cost | [Memory configuration reference](/reference/memory-config#session-memory-search-experimental) |
|
||||
| Memory search | `agents.defaults.memorySearch.experimental.sessionMemory` | You want `memory_search` to index prior session transcripts and accept the extra storage/indexing cost | [Memory configuration reference](/reference/memory-config#session-memory-search-experimental) |
|
||||
| Codex harness | `plugins.entries.codex.config.appServer.experimental.sandboxExecServer` | You want native Codex app-server 0.132.0 or newer to target an OpenClaw sandbox-backed exec-server instead of disabling Code Mode | [Codex harness reference](/plugins/codex-harness-reference#sandboxed-native-execution) |
|
||||
| Structured planning tool | `tools.experimental.planTool` | You want the structured `update_plan` tool exposed for multi-step work tracking in compatible runtimes and UIs | [Gateway configuration reference](/gateway/config-tools#toolsexperimental) |
|
||||
|
||||
|
||||
@@ -27,9 +27,11 @@ To set a provider explicitly:
|
||||
|
||||
```json5
|
||||
{
|
||||
memory: {
|
||||
search: {
|
||||
provider: "openai",
|
||||
agents: {
|
||||
defaults: {
|
||||
memorySearch: {
|
||||
provider: "openai",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -46,12 +48,14 @@ openclaw plugins install @openclaw/llama-cpp-provider
|
||||
|
||||
```json5
|
||||
{
|
||||
memory: {
|
||||
search: {
|
||||
provider: "local",
|
||||
fallback: "none",
|
||||
local: {
|
||||
modelPath: "~/.node-llama-cpp/models/embeddinggemma-300m-qat-Q8_0.gguf",
|
||||
agents: {
|
||||
defaults: {
|
||||
memorySearch: {
|
||||
provider: "local",
|
||||
fallback: "none",
|
||||
local: {
|
||||
modelPath: "~/.node-llama-cpp/models/embeddinggemma-300m-qat-Q8_0.gguf",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -73,7 +77,7 @@ openclaw plugins install @openclaw/llama-cpp-provider
|
||||
| OpenAI-compatible | `openai-compatible` | Generic `/v1/embeddings` endpoint |
|
||||
| Voyage | `voyage` | |
|
||||
|
||||
Set `memory.search.provider` to switch away from OpenAI.
|
||||
Set `memorySearch.provider` to switch away from OpenAI.
|
||||
|
||||
## How indexing works
|
||||
|
||||
@@ -91,7 +95,7 @@ OpenClaw indexes `MEMORY.md` and `memory/*.md` into chunks (~400 tokens with
|
||||
|
||||
<Info>
|
||||
You can also index Markdown files outside the workspace with
|
||||
`memory.search.extraPaths`. See the
|
||||
`memorySearch.extraPaths`. See the
|
||||
[configuration reference](/reference/memory-config#additional-memory-paths).
|
||||
</Info>
|
||||
|
||||
@@ -123,7 +127,7 @@ openclaw memory index --force --agent main
|
||||
```
|
||||
|
||||
Both standalone CLI commands and the Gateway use the same `local` provider id.
|
||||
Set `memory.search.provider: "local"` when you want local embeddings.
|
||||
Set `memorySearch.provider: "local"` when you want local embeddings.
|
||||
|
||||
**Stale results?** Run `openclaw memory index --force` to rebuild. The watcher
|
||||
may miss changes in rare edge cases.
|
||||
|
||||
@@ -18,9 +18,11 @@ backend, set a provider explicitly:
|
||||
|
||||
```json5
|
||||
{
|
||||
memory: {
|
||||
search: {
|
||||
provider: "openai", // or "gemini", "local", "ollama", "openai-compatible", etc.
|
||||
agents: {
|
||||
defaults: {
|
||||
memorySearch: {
|
||||
provider: "openai", // or "gemini", "local", "ollama", "openai-compatible", etc.
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -37,8 +39,8 @@ may still require native build approval: `pnpm approve-builds` then
|
||||
|
||||
Some OpenAI-compatible embedding endpoints require asymmetric labels such as
|
||||
`input_type: "query"` for searches and `input_type: "document"` or `"passage"`
|
||||
for indexed chunks. Configure those with `memory.search.queryInputType` and
|
||||
`memory.search.documentInputType`; see the [Memory configuration reference](/reference/memory-config#provider-specific-config).
|
||||
for indexed chunks. Configure those with `memorySearch.queryInputType` and
|
||||
`memorySearch.documentInputType`; see the [Memory configuration reference](/reference/memory-config#provider-specific-config).
|
||||
|
||||
## Supported providers
|
||||
|
||||
@@ -80,7 +82,7 @@ If only one path is available, the other runs alone. Intentional FTS-only mode
|
||||
lexical ranking when embeddings are unavailable.
|
||||
|
||||
Explicit non-local embedding providers are different. If you set
|
||||
`memory.search.provider` to a concrete remote-backed provider and that provider
|
||||
`memorySearch.provider` to a concrete remote-backed provider and that provider
|
||||
is unavailable at runtime, `memory_search` reports memory as unavailable instead
|
||||
of silently using FTS-only results. This keeps a broken configured semantic
|
||||
provider visible. Set `provider: "none"` for deliberate FTS-only recall, or fix
|
||||
@@ -115,12 +117,14 @@ different daily notes.
|
||||
|
||||
```json5
|
||||
{
|
||||
memory: {
|
||||
search: {
|
||||
query: {
|
||||
hybrid: {
|
||||
mmr: { enabled: true },
|
||||
temporalDecay: { enabled: true },
|
||||
agents: {
|
||||
defaults: {
|
||||
memorySearch: {
|
||||
query: {
|
||||
hybrid: {
|
||||
mmr: { enabled: true },
|
||||
temporalDecay: { enabled: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -139,7 +143,7 @@ setup.
|
||||
|
||||
You can optionally index session transcripts so `memory_search` can recall
|
||||
earlier conversations. This is opt-in via
|
||||
`memory.search.experimental.sessionMemory`. See the
|
||||
`memorySearch.experimental.sessionMemory`. See the
|
||||
[configuration reference](/reference/memory-config) for details.
|
||||
|
||||
## Troubleshooting
|
||||
@@ -152,7 +156,7 @@ earlier conversations. This is opt-in via
|
||||
|
||||
**Local embeddings time out?** `ollama`, `lmstudio`, and `local` use a longer
|
||||
inline batch timeout by default. If the host is simply slow, set
|
||||
`memory.search.sync.embeddingBatchTimeoutSeconds` and rerun
|
||||
`agents.defaults.memorySearch.sync.embeddingBatchTimeoutSeconds` and rerun
|
||||
`openclaw memory index --force`.
|
||||
|
||||
**CJK text not found?** Rebuild the FTS index with
|
||||
|
||||
@@ -145,7 +145,7 @@ an API key for any supported provider.
|
||||
|
||||
<Info>
|
||||
OpenClaw uses OpenAI embeddings by default. Set
|
||||
`memory.search.provider` explicitly to use Gemini, Voyage,
|
||||
`agents.defaults.memorySearch.provider` explicitly to use Gemini, Voyage,
|
||||
Mistral, local, Ollama, Bedrock, GitHub Copilot, or OpenAI-compatible
|
||||
embeddings.
|
||||
</Info>
|
||||
@@ -238,9 +238,9 @@ For phase behavior, scoring signals, and Dream Diary details, see
|
||||
|
||||
The dreaming system now has two closely related review lanes:
|
||||
|
||||
- **Live dreaming** works from an agent-scoped short-term dreaming store under
|
||||
`memory/.dreams/agents/<agent-id>/` and is what the normal deep phase uses
|
||||
when deciding what can graduate into shared `MEMORY.md`.
|
||||
- **Live dreaming** works from the short-term dreaming store under
|
||||
`memory/.dreams/` and is what the normal deep phase uses when deciding what
|
||||
can graduate into `MEMORY.md`.
|
||||
- **Grounded backfill** reads historical `memory/YYYY-MM-DD.md` notes as
|
||||
standalone day files and writes structured review output into `DREAMS.md`.
|
||||
|
||||
|
||||
@@ -130,38 +130,36 @@ This lets **multiple people** share one Gateway server while keeping their AI "b
|
||||
|
||||
## Cross-agent QMD memory search
|
||||
|
||||
If one agent should search another agent's QMD session transcripts, add extra collections under `agents.list[].memory.search.qmd.extraCollections`. Use `memory.search.qmd.extraCollections` only when every agent should inherit the same shared transcript collections.
|
||||
If one agent should search another agent's QMD session transcripts, add extra collections under `agents.list[].memorySearch.qmd.extraCollections`. Use `agents.defaults.memorySearch.qmd.extraCollections` only when every agent should inherit the same shared transcript collections.
|
||||
|
||||
```json5
|
||||
{
|
||||
memory: {
|
||||
backend: "qmd",
|
||||
qmd: { includeDefaultMemory: false },
|
||||
search: {
|
||||
qmd: {
|
||||
extraCollections: [{ path: "~/agents/family/sessions", name: "family-sessions" }],
|
||||
},
|
||||
},
|
||||
},
|
||||
agents: {
|
||||
defaults: {
|
||||
workspace: "~/workspaces/main",
|
||||
memorySearch: {
|
||||
qmd: {
|
||||
extraCollections: [{ path: "~/agents/family/sessions", name: "family-sessions" }],
|
||||
},
|
||||
},
|
||||
},
|
||||
list: [
|
||||
{
|
||||
id: "main",
|
||||
workspace: "~/workspaces/main",
|
||||
memory: {
|
||||
search: {
|
||||
qmd: {
|
||||
extraCollections: [{ path: "notes" }], // resolves inside workspace -> collection named "notes-main"
|
||||
},
|
||||
memorySearch: {
|
||||
qmd: {
|
||||
extraCollections: [{ path: "notes" }], // resolves inside workspace -> collection named "notes-main"
|
||||
},
|
||||
},
|
||||
},
|
||||
{ id: "family", workspace: "~/workspaces/family" },
|
||||
],
|
||||
},
|
||||
memory: {
|
||||
backend: "qmd",
|
||||
qmd: { includeDefaultMemory: false },
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -57,6 +57,11 @@ the resolved scenarios through `qa suite`. `--surface` and
|
||||
The resulting `qa-evidence.json` includes a profile scorecard summary with
|
||||
selected-category counts and missing coverage IDs; the individual evidence
|
||||
entries remain the source of truth for the tests, coverage roles, and results.
|
||||
Taxonomy feature coverage IDs are exact proof targets, not aliases. Primary
|
||||
scenario coverage fulfills matching IDs; secondary coverage stays advisory.
|
||||
Coverage IDs use dotted `namespace.behavior` form with lowercase
|
||||
alphanumeric/dash segments; profile, surface, and category IDs may still use
|
||||
the existing dashed or dotted taxonomy IDs.
|
||||
Slim evidence omits per-entry `execution` and sets `evidenceMode: "slim"`;
|
||||
`smoke-ci` defaults to slim, and `--evidence-mode full` restores full entries:
|
||||
|
||||
|
||||
@@ -315,9 +315,15 @@ The same section also includes the OpenClaw source location. Git checkouts expos
|
||||
source root so the agent can inspect code directly. Package installs include the GitHub
|
||||
source URL and tell the agent to review source there whenever the docs are incomplete or
|
||||
stale. The prompt also notes the public docs mirror, community Discord, and ClawHub
|
||||
([https://clawhub.ai](https://clawhub.ai)) for skills discovery. It tells the model to
|
||||
consult docs first for OpenClaw behavior, commands, configuration, or architecture, and to
|
||||
run `openclaw status` itself when possible (asking the user only when it lacks access).
|
||||
([https://clawhub.ai](https://clawhub.ai)) for skills discovery. It frames docs as the
|
||||
authority for OpenClaw self-knowledge before the model understands how OpenClaw works,
|
||||
including memory/daily notes, sessions, tools, Gateway, config, commands, or project
|
||||
context. The prompt tells the model to use local docs (or the docs mirror when local docs
|
||||
are unavailable) first, and to treat AGENTS.md, project context, workspace/profile/memory
|
||||
notes, and `memory_search` as instruction context or user memory rather than OpenClaw
|
||||
design or implementation knowledge. If docs are silent or stale, the model should say so
|
||||
and inspect source. The prompt also tells the model to run `openclaw status` itself when
|
||||
possible, asking the user only when it lacks access.
|
||||
For configuration specifically, it points agents to the `gateway` tool action
|
||||
`config.schema.lookup` for exact field-level docs and constraints, then to
|
||||
`docs/gateway/configuration.md` and `docs/gateway/configuration-reference.md`
|
||||
|
||||
@@ -281,6 +281,14 @@ Save to `~/.openclaw/openclaw.json` and you can DM the bot from that number.
|
||||
prompt: "HEARTBEAT",
|
||||
ackMaxChars: 300,
|
||||
},
|
||||
memorySearch: {
|
||||
provider: "gemini",
|
||||
model: "gemini-embedding-001",
|
||||
remote: {
|
||||
apiKey: "${GEMINI_API_KEY}",
|
||||
},
|
||||
extraPaths: ["../team-docs", "/srv/shared-notes"],
|
||||
},
|
||||
sandbox: {
|
||||
mode: "non-main",
|
||||
scope: "session", // preferred over legacy perSession: true
|
||||
@@ -324,17 +332,6 @@ Save to `~/.openclaw/openclaw.json` and you can DM the bot from that number.
|
||||
],
|
||||
},
|
||||
|
||||
memory: {
|
||||
search: {
|
||||
provider: "gemini",
|
||||
model: "gemini-embedding-001",
|
||||
remote: {
|
||||
apiKey: "${GEMINI_API_KEY}",
|
||||
},
|
||||
extraPaths: ["../team-docs", "/srv/shared-notes"],
|
||||
},
|
||||
},
|
||||
|
||||
tools: {
|
||||
allow: ["exec", "process", "read", "write", "edit", "apply_patch"],
|
||||
deny: ["browser", "canvas"],
|
||||
|
||||
@@ -23,7 +23,7 @@ for the broader field map, defaults, and links to subsystem references.
|
||||
|
||||
Dedicated deep references:
|
||||
|
||||
- [Memory configuration reference](/reference/memory-config) for `memory.search.*`, `memory.qmd.*`, `memory.citations`, and dreaming config under `memory.extensions.memory-core.dreaming`
|
||||
- [Memory configuration reference](/reference/memory-config) for `agents.defaults.memorySearch.*`, `memory.qmd.*`, `memory.citations`, and dreaming config under `plugins.entries.memory-core.config.dreaming`
|
||||
- [Slash commands](/tools/slash-commands) for the current built-in + bundled command catalog
|
||||
- owning channel/plugin pages for channel-specific command surfaces
|
||||
|
||||
@@ -348,17 +348,17 @@ restart after changing native plugin config.
|
||||
- `plugins.entries.xai.config.xSearch`: xAI X Search (Grok web search) settings.
|
||||
- `enabled`: enable the X Search provider.
|
||||
- `model`: Grok model to use for search (e.g. `"grok-4-1-fast"`).
|
||||
- `memory.extensions.memory-core.dreaming`: memory dreaming settings. See [Dreaming](/concepts/dreaming) for phases and thresholds.
|
||||
- `plugins.entries.memory-core.config.dreaming`: memory dreaming settings. See [Dreaming](/concepts/dreaming) for phases and thresholds.
|
||||
- `enabled`: master dreaming switch (default `false`).
|
||||
- `frequency`: cron cadence for each full dreaming sweep (`"0 3 * * *"` by default).
|
||||
- `model`: optional Dream Diary subagent model override. Requires `plugins.entries.memory-core.subagent.allowModelOverride: true`; pair with `allowedModels` to restrict targets. Model-unavailable errors retry once with the session default model; trust or allowlist failures do not fall back silently.
|
||||
- phase policy and thresholds are implementation details (not user-facing config keys).
|
||||
- Full memory config lives in [Memory configuration reference](/reference/memory-config):
|
||||
- `memory.search.*`
|
||||
- `agents.defaults.memorySearch.*`
|
||||
- `memory.backend`
|
||||
- `memory.citations`
|
||||
- `memory.qmd.*`
|
||||
- `memory.extensions.memory-core.dreaming`
|
||||
- `plugins.entries.memory-core.config.dreaming`
|
||||
- Enabled Claude bundle plugins can also contribute embedded OpenClaw defaults from `settings.json`; OpenClaw applies those as sanitized agent settings, not as raw OpenClaw config patches.
|
||||
- `plugins.slots.memory`: pick the active memory plugin id, or `"none"` to disable memory plugins.
|
||||
- `plugins.slots.contextEngine`: pick the active context engine plugin id; defaults to `"legacy"` unless you install and select another engine.
|
||||
@@ -1096,6 +1096,7 @@ Notes:
|
||||
traces: true,
|
||||
metrics: true,
|
||||
logs: false,
|
||||
logsExporter: "otlp",
|
||||
sampleRate: 1.0,
|
||||
flushIntervalMs: 5000,
|
||||
captureContent: {
|
||||
@@ -1132,6 +1133,7 @@ Notes:
|
||||
- `otel.headers`: extra HTTP/gRPC metadata headers sent with OTel export requests.
|
||||
- `otel.serviceName`: service name for resource attributes.
|
||||
- `otel.traces` / `otel.metrics` / `otel.logs`: enable trace, metrics, or log export.
|
||||
- `otel.logsExporter`: log export sink: `"otlp"` (default), `"stdout"` for one JSON object per stdout line, or `"both"`.
|
||||
- `otel.sampleRate`: trace sampling rate `0`-`1`.
|
||||
- `otel.flushIntervalMs`: periodic telemetry flush interval in ms.
|
||||
- `otel.captureContent`: opt-in raw content capture for OTEL span attributes. Defaults to off. Boolean `true` captures non-system message/tool content; the object form lets you enable `inputMessages`, `outputMessages`, `toolInputs`, `toolOutputs`, `systemPrompt`, and `toolDefinitions` explicitly.
|
||||
|
||||
@@ -397,6 +397,7 @@ That stages grounded durable candidates into the short-term dreaming store while
|
||||
- **State dir permissions**: verifies writability; offers to repair permissions (and emits a `chown` hint when owner/group mismatch is detected).
|
||||
- **macOS cloud-synced state dir**: warns when state resolves under iCloud Drive (`~/Library/Mobile Documents/com~apple~CloudDocs/...`) or `~/Library/CloudStorage/...` because sync-backed paths can cause slower I/O and lock/sync races.
|
||||
- **Linux SD or eMMC state dir**: warns when state resolves to an `mmcblk*` mount source, because SD or eMMC-backed random I/O can be slower and wear faster under session and credential writes.
|
||||
- **Linux volatile state dir**: warns when state resolves to `tmpfs` or `ramfs`, because sessions, credentials, config, and SQLite state with its WAL/journal sidecars will disappear on reboot. Docker `overlay` mounts are intentionally not flagged because their writable layers persist across host reboots while the container remains.
|
||||
- **Session dirs missing**: `sessions/` and the session store directory are required to persist history and avoid `ENOENT` crashes.
|
||||
- **Transcript mismatch**: warns when recent session entries have missing transcript files.
|
||||
- **Main session "1-line JSONL"**: flags when the main transcript has only one line (history is not accumulating).
|
||||
@@ -519,7 +520,7 @@ That stages grounded durable candidates into the short-term dreaming store while
|
||||
- **QMD backend**: probes whether the `qmd` binary is available and startable. If not, prints fix guidance including the npm package and a manual binary path option.
|
||||
- **Explicit local provider**: checks for a local model file or a recognized remote/downloadable model URL. If missing, suggests switching to a remote provider.
|
||||
- **Explicit remote provider** (`openai`, `voyage`, etc.): verifies an API key is present in the environment or auth store. Prints actionable fix hints if missing.
|
||||
- **Legacy auto provider**: treats `memory.search.provider: "auto"` as OpenAI, checks OpenAI readiness, and `doctor --fix` rewrites it to `provider: "openai"`.
|
||||
- **Legacy auto provider**: treats `memorySearch.provider: "auto"` as OpenAI, checks OpenAI readiness, and `doctor --fix` rewrites it to `provider: "openai"`.
|
||||
|
||||
When a cached gateway probe result is available (gateway was healthy at the time of the check), doctor cross-references its result with the CLI-visible config and notes any discrepancy. Doctor does not start a fresh embedding ping on the default path; use the deep memory status command when you want a live provider check.
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
summary: "Export OpenClaw diagnostics to any OpenTelemetry collector via the diagnostics-otel plugin (OTLP/HTTP)"
|
||||
summary: "Export OpenClaw diagnostics to OpenTelemetry collectors or stdout JSONL via the diagnostics-otel plugin"
|
||||
title: "OpenTelemetry export"
|
||||
read_when:
|
||||
- You want to send OpenClaw model usage, message flow, or session metrics to an OpenTelemetry collector
|
||||
@@ -8,9 +8,10 @@ read_when:
|
||||
---
|
||||
|
||||
OpenClaw exports diagnostics through the official `diagnostics-otel` plugin
|
||||
using **OTLP/HTTP (protobuf)**. Any collector or backend that accepts OTLP/HTTP
|
||||
works without code changes. For local file logs and how to read them, see
|
||||
[Logging](/logging).
|
||||
using **OTLP/HTTP (protobuf)**. Logs can also be written as stdout JSONL for
|
||||
container and sandbox log pipelines. Any collector or backend that accepts
|
||||
OTLP/HTTP works without code changes. For local file logs and how to read them,
|
||||
see [Logging](/logging).
|
||||
|
||||
## How it fits together
|
||||
|
||||
@@ -18,7 +19,8 @@ works without code changes. For local file logs and how to read them, see
|
||||
Gateway and bundled plugins for model runs, message flow, sessions, queues,
|
||||
and exec.
|
||||
- **`diagnostics-otel` plugin** subscribes to those events and exports them as
|
||||
OpenTelemetry **metrics**, **traces**, and **logs** over OTLP/HTTP.
|
||||
OpenTelemetry **metrics**, **traces**, and **logs** over OTLP/HTTP. It can
|
||||
also mirror diagnostic log records to stdout JSONL.
|
||||
- **Provider calls** receive a W3C `traceparent` header from OpenClaw's
|
||||
trusted model-call span context when the provider transport accepts custom
|
||||
headers. Plugin-emitted trace context is not propagated.
|
||||
@@ -74,11 +76,13 @@ openclaw plugins enable diagnostics-otel
|
||||
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| **Metrics** | Counters and histograms for token usage, cost, run duration, failover, skill usage, message flow, Talk events, queue lanes, session state/recovery, tool execution, oversized payloads, exec, and memory pressure. |
|
||||
| **Traces** | Spans for model usage, model calls, harness lifecycle, skill usage, tool execution, exec, webhook/message processing, context assembly, and tool loops. |
|
||||
| **Logs** | Structured `logging.file` records exported over OTLP when `diagnostics.otel.logs` is enabled; log bodies are withheld unless content capture is explicitly enabled. |
|
||||
| **Logs** | Structured `logging.file` records exported over OTLP or stdout JSONL when `diagnostics.otel.logs` is enabled; log bodies are withheld unless content capture is explicitly enabled. |
|
||||
|
||||
Toggle `traces`, `metrics`, and `logs` independently. Traces and metrics
|
||||
default to on when `diagnostics.otel.enabled` is true. Logs default to off and
|
||||
are exported only when `diagnostics.otel.logs` is explicitly `true`.
|
||||
are exported only when `diagnostics.otel.logs` is explicitly `true`. Log export
|
||||
defaults to OTLP; set `diagnostics.otel.logsExporter` to `stdout` for JSONL on
|
||||
stdout, or `both` to send each diagnostic log record to OTLP and stdout.
|
||||
|
||||
## Configuration reference
|
||||
|
||||
@@ -98,6 +102,7 @@ are exported only when `diagnostics.otel.logs` is explicitly `true`.
|
||||
traces: true,
|
||||
metrics: true,
|
||||
logs: true,
|
||||
logsExporter: "otlp", // otlp | stdout | both
|
||||
sampleRate: 0.2, // root-span sampler, 0.0..1.0
|
||||
flushIntervalMs: 60000, // metric export interval (min 1000ms)
|
||||
captureContent: {
|
||||
@@ -176,6 +181,11 @@ on the public diagnostic event bus.
|
||||
- **Logs:** OTLP logs respect `logging.level` (file log level). They use the
|
||||
diagnostic log-record redaction path, not console formatting. High-volume
|
||||
installs should prefer OTLP collector sampling/filtering over local sampling.
|
||||
Set `diagnostics.otel.logsExporter: "stdout"` when your platform already
|
||||
ships stdout/stderr to a log processor and you do not have an OTLP logs
|
||||
collector. Stdout records are one JSON object per line with `ts`, `signal`,
|
||||
`service.name`, severity, body, redacted attributes, and trusted trace fields
|
||||
when available.
|
||||
- **File-log correlation:** JSONL file logs include top-level `traceId`,
|
||||
`spanId`, `parentSpanId`, and `traceFlags` when the log call carries a valid
|
||||
diagnostic trace context, which lets log processors join local log lines with
|
||||
|
||||
@@ -549,14 +549,14 @@ lives on the [First-run FAQ](/help/faq-first-run).
|
||||
still need a real API key (`OPENAI_API_KEY` or `models.providers.openai.apiKey`).
|
||||
|
||||
If you don't set a provider explicitly, OpenClaw uses OpenAI embeddings. Legacy
|
||||
configs that still say `memory.search.provider = "auto"` resolve to OpenAI too.
|
||||
configs that still say `memorySearch.provider = "auto"` resolve to OpenAI too.
|
||||
If no OpenAI API key is available, semantic memory search stays unavailable
|
||||
until you configure a key or choose another provider explicitly.
|
||||
|
||||
If you'd rather stay local, set `memory.search.provider = "local"` (and optionally
|
||||
`memory.search.fallback = "none"`). If you want Gemini embeddings, set
|
||||
`memory.search.provider = "gemini"` and provide `GEMINI_API_KEY` (or
|
||||
`memory.search.remote.apiKey`). We support **OpenAI, OpenAI-compatible, Gemini,
|
||||
If you'd rather stay local, set `memorySearch.provider = "local"` (and optionally
|
||||
`memorySearch.fallback = "none"`). If you want Gemini embeddings, set
|
||||
`memorySearch.provider = "gemini"` and provide `GEMINI_API_KEY` (or
|
||||
`memorySearch.remote.apiKey`). We support **OpenAI, OpenAI-compatible, Gemini,
|
||||
Voyage, Mistral, Bedrock, Ollama, LM Studio, GitHub Copilot, DeepInfra, or local**
|
||||
embedding models - see [Memory](/concepts/memory) for the setup details.
|
||||
|
||||
|
||||
@@ -224,8 +224,10 @@ model-call traces become children of the active request trace, so local logs,
|
||||
diagnostic snapshots, OTEL spans, and trusted provider `traceparent` headers can
|
||||
be joined by `traceId` without logging raw request or model content.
|
||||
|
||||
Talk lifecycle log records also flow to OTLP logs when OpenTelemetry log export
|
||||
is enabled, using the same bounded attributes as file logs.
|
||||
Talk lifecycle log records also flow to diagnostics-otel log export when
|
||||
OpenTelemetry log export is enabled, using the same bounded attributes as file
|
||||
logs. Configure `diagnostics.otel.logsExporter` to choose OTLP, stdout JSONL, or
|
||||
both sinks.
|
||||
|
||||
### Model call size and timing
|
||||
|
||||
|
||||
@@ -91,8 +91,8 @@ Supported `appServer` fields:
|
||||
| `command` | managed Codex binary | Executable for stdio transport. Leave unset to use the managed binary. |
|
||||
| `args` | `["app-server", "--listen", "stdio://"]` | Arguments for stdio transport. |
|
||||
| `url` | unset | WebSocket app-server URL. |
|
||||
| `authToken` | unset | Bearer token for WebSocket transport. |
|
||||
| `headers` | `{}` | Extra WebSocket headers. |
|
||||
| `authToken` | unset | Bearer token for WebSocket transport. Accepts a literal string or SecretInput such as `${CODEX_APP_SERVER_TOKEN}`. |
|
||||
| `headers` | `{}` | Extra WebSocket headers. Header values accept literal strings or SecretInput values, for example `x-codex-client-session-token: "${CODEX_CLIENT_SESSION_TOKEN}"`. |
|
||||
| `clearEnv` | `[]` | Extra environment variable names removed from the spawned stdio app-server process after OpenClaw builds its inherited environment. |
|
||||
| `remoteWorkspaceRoot` | unset | Remote Codex app-server workspace root. When set, OpenClaw infers the local workspace root from the resolved OpenClaw workspace, preserves the current cwd suffix under this remote root, and sends only the final app-server cwd to Codex. If the cwd is outside the resolved OpenClaw workspace root, OpenClaw fails closed instead of sending a gateway-local path to the remote app-server. |
|
||||
| `requestTimeoutMs` | `60000` | Timeout for app-server control-plane calls. |
|
||||
@@ -149,11 +149,15 @@ must report stable version `0.125.0` or newer.
|
||||
|
||||
OpenClaw treats non-loopback WebSocket app-server URLs as remote and requires
|
||||
identity-bearing WebSocket auth through `appServer.authToken` or an
|
||||
`Authorization` header. When native Codex plugins are configured, OpenClaw uses
|
||||
the connected app-server's plugin control plane to install or refresh those
|
||||
plugins and then refreshes app inventory so plugin-owned apps are visible to the
|
||||
Codex thread. Only connect OpenClaw to remote app-servers that are trusted to
|
||||
accept OpenClaw-managed plugin installs and app inventory refreshes.
|
||||
`Authorization` header. `appServer.authToken` and each `appServer.headers.*`
|
||||
value can be a SecretInput; the secrets runtime resolves SecretRefs and env
|
||||
shorthand before OpenClaw builds app-server start options, and unresolved
|
||||
structured SecretRefs fail before any token or header is sent. When native Codex
|
||||
plugins are configured, OpenClaw uses the connected app-server's plugin control
|
||||
plane to install or refresh those plugins and then refreshes app inventory so
|
||||
plugin-owned apps are visible to the Codex thread. Only connect OpenClaw to
|
||||
remote app-servers that are trusted to accept OpenClaw-managed plugin installs
|
||||
and app inventory refreshes.
|
||||
|
||||
## Approval and sandbox modes
|
||||
|
||||
|
||||
@@ -552,8 +552,8 @@ Supported `appServer` fields:
|
||||
| `command` | managed Codex binary | Executable for stdio transport. Leave unset to use the managed binary; set it only for an explicit override. |
|
||||
| `args` | `["app-server", "--listen", "stdio://"]` | Arguments for stdio transport. |
|
||||
| `url` | unset | WebSocket app-server URL. |
|
||||
| `authToken` | unset | Bearer token for WebSocket transport. |
|
||||
| `headers` | `{}` | Extra WebSocket headers. |
|
||||
| `authToken` | unset | Bearer token for WebSocket transport. Accepts a literal string or SecretInput such as `${CODEX_APP_SERVER_TOKEN}`. |
|
||||
| `headers` | `{}` | Extra WebSocket headers. Header values accept literal strings or SecretInput values, for example `x-codex-client-session-token: "${CODEX_CLIENT_SESSION_TOKEN}"`. |
|
||||
| `clearEnv` | `[]` | Extra environment variable names removed from the spawned stdio app-server process after OpenClaw builds its inherited environment. OpenClaw keeps per-agent `CODEX_HOME` and inherited `HOME` for local launches. |
|
||||
| `codeModeOnly` | `false` | Opt into Codex's code-mode-only tool surface. OpenClaw dynamic tools remain registered with Codex so nested `tools.*` calls return through the app-server `item/tool/call` bridge. |
|
||||
| `remoteWorkspaceRoot` | unset | Remote Codex app-server workspace root. When set, OpenClaw infers the local workspace root from the resolved OpenClaw workspace, preserves the current cwd suffix under this remote root, and sends only the final app-server cwd to Codex. If the cwd is outside the resolved OpenClaw workspace root, OpenClaw fails closed instead of sending a gateway-local path to the remote app-server. |
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
summary: "Install the official llama.cpp provider for local GGUF memory embeddings"
|
||||
read_when:
|
||||
- You want memory search embeddings from a local GGUF model
|
||||
- You are configuring memory.search.provider = "local"
|
||||
- You are configuring memorySearch.provider = "local"
|
||||
- You need the OpenClaw plugin that owns the node-llama-cpp runtime
|
||||
title: "llama.cpp Provider"
|
||||
sidebarTitle: "llama.cpp Provider"
|
||||
@@ -10,7 +10,7 @@ sidebarTitle: "llama.cpp Provider"
|
||||
|
||||
`llama-cpp` is the official external provider plugin for local GGUF embeddings.
|
||||
It owns the `node-llama-cpp` runtime dependency used by
|
||||
`memory.search.provider: "local"`.
|
||||
`memorySearch.provider: "local"`.
|
||||
|
||||
Install it before using local memory embeddings:
|
||||
|
||||
@@ -28,11 +28,13 @@ Set the memory search provider to `local`:
|
||||
|
||||
```json5
|
||||
{
|
||||
memory: {
|
||||
search: {
|
||||
provider: "local",
|
||||
local: {
|
||||
modelPath: "hf:ggml-org/embeddinggemma-300m-qat-q8_0-GGUF/embeddinggemma-300m-qat-Q8_0.gguf",
|
||||
agents: {
|
||||
defaults: {
|
||||
memorySearch: {
|
||||
provider: "local",
|
||||
local: {
|
||||
modelPath: "hf:ggml-org/embeddinggemma-300m-qat-q8_0-GGUF/embeddinggemma-300m-qat-Q8_0.gguf",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -397,78 +397,54 @@ engines or legacy prompt assembly that explicitly consume memory supplements.
|
||||
|
||||
## Configuration
|
||||
|
||||
Put config under `memory.extensions.memory-wiki`. Agent entries
|
||||
can override the same object at `agents.list[].memory.extensions.memory-wiki`.
|
||||
Enable the plugin once under `plugins.entries`; runtime state remains agent-scoped.
|
||||
Put config under `plugins.entries.memory-wiki.config`:
|
||||
|
||||
```json5
|
||||
{
|
||||
plugins: {
|
||||
entries: {
|
||||
"memory-wiki": { enabled: true },
|
||||
},
|
||||
},
|
||||
memory: {
|
||||
extensions: {
|
||||
"memory-wiki": {
|
||||
vaultMode: "isolated",
|
||||
vault: {
|
||||
renderMode: "obsidian",
|
||||
},
|
||||
obsidian: {
|
||||
enabled: true,
|
||||
useOfficialCli: true,
|
||||
vaultName: "OpenClaw Wiki",
|
||||
openAfterWrites: false,
|
||||
},
|
||||
bridge: {
|
||||
enabled: false,
|
||||
readMemoryArtifacts: true,
|
||||
indexDreamReports: true,
|
||||
indexDailyNotes: true,
|
||||
indexMemoryRoot: true,
|
||||
followMemoryEvents: true,
|
||||
},
|
||||
ingest: {
|
||||
autoCompile: true,
|
||||
maxConcurrentJobs: 1,
|
||||
allowUrlIngest: true,
|
||||
},
|
||||
search: {
|
||||
backend: "shared",
|
||||
corpus: "wiki",
|
||||
},
|
||||
context: {
|
||||
includeCompiledDigestPrompt: false,
|
||||
},
|
||||
render: {
|
||||
preserveHumanBlocks: true,
|
||||
createBacklinks: true,
|
||||
createDashboards: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Agent entries can override the same `memory.extensions.memory-wiki` object:
|
||||
|
||||
```json5
|
||||
{
|
||||
agents: {
|
||||
list: [
|
||||
{
|
||||
id: "research",
|
||||
memory: {
|
||||
extensions: {
|
||||
"memory-wiki": {
|
||||
vaultMode: "isolated",
|
||||
},
|
||||
enabled: true,
|
||||
config: {
|
||||
vaultMode: "isolated",
|
||||
vault: {
|
||||
path: "~/.openclaw/wiki/main",
|
||||
renderMode: "obsidian",
|
||||
},
|
||||
obsidian: {
|
||||
enabled: true,
|
||||
useOfficialCli: true,
|
||||
vaultName: "OpenClaw Wiki",
|
||||
openAfterWrites: false,
|
||||
},
|
||||
bridge: {
|
||||
enabled: false,
|
||||
readMemoryArtifacts: true,
|
||||
indexDreamReports: true,
|
||||
indexDailyNotes: true,
|
||||
indexMemoryRoot: true,
|
||||
followMemoryEvents: true,
|
||||
},
|
||||
ingest: {
|
||||
autoCompile: true,
|
||||
maxConcurrentJobs: 1,
|
||||
allowUrlIngest: true,
|
||||
},
|
||||
search: {
|
||||
backend: "shared",
|
||||
corpus: "wiki",
|
||||
},
|
||||
context: {
|
||||
includeCompiledDigestPrompt: false,
|
||||
},
|
||||
render: {
|
||||
preserveHumanBlocks: true,
|
||||
createBacklinks: true,
|
||||
createDashboards: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
@@ -492,30 +468,30 @@ knowledge layer:
|
||||
|
||||
```json5
|
||||
{
|
||||
plugins: {
|
||||
entries: {
|
||||
"memory-wiki": { enabled: true },
|
||||
},
|
||||
},
|
||||
memory: {
|
||||
backend: "qmd",
|
||||
extensions: {
|
||||
},
|
||||
plugins: {
|
||||
entries: {
|
||||
"memory-wiki": {
|
||||
vaultMode: "bridge",
|
||||
bridge: {
|
||||
enabled: true,
|
||||
readMemoryArtifacts: true,
|
||||
indexDreamReports: true,
|
||||
indexDailyNotes: true,
|
||||
indexMemoryRoot: true,
|
||||
followMemoryEvents: true,
|
||||
},
|
||||
search: {
|
||||
backend: "shared",
|
||||
corpus: "all",
|
||||
},
|
||||
context: {
|
||||
includeCompiledDigestPrompt: false,
|
||||
enabled: true,
|
||||
config: {
|
||||
vaultMode: "bridge",
|
||||
bridge: {
|
||||
enabled: true,
|
||||
readMemoryArtifacts: true,
|
||||
indexDreamReports: true,
|
||||
indexDailyNotes: true,
|
||||
indexMemoryRoot: true,
|
||||
followMemoryEvents: true,
|
||||
},
|
||||
search: {
|
||||
backend: "shared",
|
||||
corpus: "all",
|
||||
},
|
||||
context: {
|
||||
includeCompiledDigestPrompt: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -227,7 +227,7 @@ Each entry lists the package, distribution route, and description.
|
||||
|
||||
- **[deepseek](/plugins/reference/deepseek)** (`@openclaw/deepseek-provider`) - npm; ClawHub: `clawhub:@openclaw/deepseek-provider`. Adds DeepSeek model provider support to OpenClaw.
|
||||
|
||||
- **[diagnostics-otel](/plugins/reference/diagnostics-otel)** (`@openclaw/diagnostics-otel`) - npm; ClawHub: `clawhub:@openclaw/diagnostics-otel`. OpenClaw diagnostics OpenTelemetry exporter for metrics and traces.
|
||||
- **[diagnostics-otel](/plugins/reference/diagnostics-otel)** (`@openclaw/diagnostics-otel`) - npm; ClawHub: `clawhub:@openclaw/diagnostics-otel`. OpenClaw diagnostics OpenTelemetry exporter for metrics, traces, and logs.
|
||||
|
||||
- **[diagnostics-prometheus](/plugins/reference/diagnostics-prometheus)** (`@openclaw/diagnostics-prometheus`) - npm; ClawHub: `clawhub:@openclaw/diagnostics-prometheus`. OpenClaw diagnostics Prometheus exporter for runtime metrics.
|
||||
|
||||
@@ -291,7 +291,7 @@ Each entry lists the package, distribution route, and description.
|
||||
|
||||
- **[slack](/plugins/reference/slack)** (`@openclaw/slack`) - npm; ClawHub. OpenClaw Slack channel plugin for channels, DMs, commands, and app events.
|
||||
|
||||
- **[stepfun](/plugins/reference/stepfun)** (`@openclaw/stepfun-provider`) - npm; ClawHub: `clawhub:@openclaw/stepfun-provider`. Adds StepFun, StepFun Plan model provider support to OpenClaw.
|
||||
- **[stepfun](/plugins/reference/stepfun)** (`@openclaw/stepfun-provider`) - npm. Adds StepFun, StepFun Plan model provider support to OpenClaw.
|
||||
|
||||
- **[synology-chat](/plugins/reference/synology-chat)** (`@openclaw/synology-chat`) - npm; ClawHub. Synology Chat channel plugin for OpenClaw channels and direct messages.
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
summary: "OpenClaw diagnostics OpenTelemetry exporter for metrics and traces."
|
||||
summary: "OpenClaw diagnostics OpenTelemetry exporter for metrics, traces, and logs."
|
||||
read_when:
|
||||
- You are installing, configuring, or auditing the diagnostics-otel plugin
|
||||
title: "Diagnostics OpenTelemetry plugin"
|
||||
@@ -7,7 +7,7 @@ title: "Diagnostics OpenTelemetry plugin"
|
||||
|
||||
# Diagnostics OpenTelemetry plugin
|
||||
|
||||
OpenClaw diagnostics OpenTelemetry exporter for metrics and traces.
|
||||
OpenClaw diagnostics OpenTelemetry exporter for metrics, traces, and logs.
|
||||
|
||||
## Distribution
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ Adds StepFun, StepFun Plan model provider support to OpenClaw.
|
||||
## Distribution
|
||||
|
||||
- Package: `@openclaw/stepfun-provider`
|
||||
- Install route: npm; ClawHub: `clawhub:@openclaw/stepfun-provider`
|
||||
- Install route: npm
|
||||
|
||||
## Surface
|
||||
|
||||
|
||||
@@ -164,7 +164,9 @@ two-party event loops that do not go through the shared inbound reply runner.
|
||||
});
|
||||
```
|
||||
|
||||
Prefer `getSessionEntry(...)`, `listSessionEntries(...)`, `patchSessionEntry(...)`, or `upsertSessionEntry(...)` for session workflows. These helpers address sessions by agent/session identity so plugins do not depend on the legacy `sessions.json` storage shape. Use `preserveActivity: true` for metadata-only patches that should not refresh session activity, and `replaceEntry: true` only when the callback returns a complete entry and deleted fields must stay deleted. `loadSessionStore(...)` remains as a deprecated compatibility escape hatch for callers that intentionally need a mutable whole-store clone.
|
||||
Prefer `getSessionEntry(...)`, `listSessionEntries(...)`, `patchSessionEntry(...)`, or `upsertSessionEntry(...)` for session workflows. These helpers address sessions by agent/session identity so plugins do not depend on the legacy `sessions.json` storage shape. Use `preserveActivity: true` for metadata-only patches that should not refresh session activity, and `replaceEntry: true` only when the callback returns a complete entry and deleted fields must stay deleted.
|
||||
|
||||
`loadSessionStore(...)`, `saveSessionStore(...)`, `updateSessionStore(...)`, and `resolveSessionFilePath(...)` are kept only during the transition before SQLite migration for plugins that still intentionally depend on the legacy whole-store or transcript-file shape. New plugin code must not use those helpers, and existing callers must migrate to entry helpers before the SQLite storage flip.
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="api.runtime.agent.defaults">
|
||||
|
||||
@@ -247,7 +247,7 @@ usage endpoint failed or returned no usable usage data.
|
||||
| `plugin-sdk/reply-history` | Shared short-window reply-history helpers. New message-turn code should use `createChannelHistoryWindow`; lower-level map helpers remain deprecated compatibility exports only |
|
||||
| `plugin-sdk/reply-reference` | `createReplyReferencePlanner` |
|
||||
| `plugin-sdk/reply-chunking` | Narrow text/markdown chunking helpers |
|
||||
| `plugin-sdk/session-store-runtime` | Session workflow helpers (`getSessionEntry`, `listSessionEntries`, `patchSessionEntry`, `upsertSessionEntry`), legacy session store path/session-key helpers, updated-at reads, and deprecated whole-store mutation helpers |
|
||||
| `plugin-sdk/session-store-runtime` | Session workflow helpers (`getSessionEntry`, `listSessionEntries`, `patchSessionEntry`, `upsertSessionEntry`), legacy session store path/session-key helpers, updated-at reads, and transition-only whole-store/file-path compatibility helpers |
|
||||
| `plugin-sdk/sqlite-runtime` | Focused SQLite agent-schema, path, and transaction helpers for first-party runtime |
|
||||
| `plugin-sdk/cron-store-runtime` | Cron store path/load/save helpers |
|
||||
| `plugin-sdk/state-paths` | State/OAuth dir path helpers |
|
||||
|
||||
@@ -371,14 +371,16 @@ openclaw models list
|
||||
<Accordion title="Embeddings for memory search">
|
||||
Bedrock can also serve as the embedding provider for
|
||||
[memory search](/concepts/memory-search). This is configured separately from the
|
||||
inference provider -- set `memory.search.provider` to `"bedrock"`:
|
||||
inference provider -- set `agents.defaults.memorySearch.provider` to `"bedrock"`:
|
||||
|
||||
```json5
|
||||
{
|
||||
memory: {
|
||||
search: {
|
||||
provider: "bedrock",
|
||||
model: "amazon.titan-embed-text-v2:0", // default
|
||||
agents: {
|
||||
defaults: {
|
||||
memorySearch: {
|
||||
provider: "bedrock",
|
||||
model: "amazon.titan-embed-text-v2:0", // default
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -386,7 +388,7 @@ openclaw models list
|
||||
|
||||
Bedrock embeddings use the same AWS SDK credential chain as inference (instance
|
||||
roles, SSO, access keys, shared config, and web identity). No API key is
|
||||
needed. Set `memory.search.provider: "bedrock"` explicitly to use Bedrock
|
||||
needed. Set `memorySearch.provider: "bedrock"` explicitly to use Bedrock
|
||||
embeddings.
|
||||
|
||||
Supported embedding models include Amazon Titan Embed (v1, v2), Amazon Nova
|
||||
|
||||
@@ -65,7 +65,7 @@ static defaults below.
|
||||
| Speech-to-text | `openai/whisper-large-v3-turbo` | inbound audio transcription |
|
||||
| Text-to-speech | `hexgrad/Kokoro-82M` | `messages.tts.provider: "deepinfra"` |
|
||||
| Video generation | first `video-gen`-tagged entry from live catalog (static fallback `Pixverse/Pixverse-T2V`) | `video_generate`, `agents.defaults.videoGenerationModel` |
|
||||
| Memory embeddings | `BAAI/bge-m3` | `memory.search.provider: "deepinfra"` |
|
||||
| Memory embeddings | `BAAI/bge-m3` | `agents.defaults.memorySearch.provider: "deepinfra"` |
|
||||
|
||||
DeepInfra also exposes reranking, classification, object-detection, and other
|
||||
native model types. OpenClaw does not currently have first-class provider
|
||||
|
||||
@@ -216,17 +216,19 @@ have logged in, OpenClaw can use it for embeddings without a separate API key.
|
||||
|
||||
### Config
|
||||
|
||||
Set `memory.search.provider` explicitly to use GitHub Copilot embeddings. If a
|
||||
Set `memorySearch.provider` explicitly to use GitHub Copilot embeddings. If a
|
||||
GitHub token is available, OpenClaw discovers available embedding models from
|
||||
the Copilot API and picks the best one automatically.
|
||||
|
||||
```json5
|
||||
{
|
||||
memory: {
|
||||
search: {
|
||||
provider: "github-copilot",
|
||||
// Optional: override the auto-discovered model
|
||||
model: "text-embedding-3-small",
|
||||
agents: {
|
||||
defaults: {
|
||||
memorySearch: {
|
||||
provider: "github-copilot",
|
||||
// Optional: override the auto-discovered model
|
||||
model: "text-embedding-3-small",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -208,9 +208,7 @@ matching `sampleRate` only if your upstream stream is already raw PCM.
|
||||
|
||||
```json5
|
||||
{
|
||||
memory: {
|
||||
search: { provider: "mistral" },
|
||||
},
|
||||
memorySearch: { provider: "mistral" },
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ Ollama provider config uses `baseUrl` as the canonical key. OpenClaw also accept
|
||||
Remote public hosts and Ollama Cloud (`https://ollama.com`) require a real credential through `OLLAMA_API_KEY`, an auth profile, or the provider's `apiKey`. For direct hosted use, prefer provider `ollama-cloud`.
|
||||
</Accordion>
|
||||
<Accordion title="Custom provider ids">
|
||||
Custom provider ids that set `api: "ollama"` follow the same rules. For example, an `ollama-remote` provider that points at a private LAN Ollama host can use `apiKey: "ollama-local"` and sub-agents will resolve that marker through the Ollama provider hook instead of treating it as a missing credential. Memory search can also set `memory.search.provider` to that custom provider id so embeddings use the matching Ollama endpoint.
|
||||
Custom provider ids that set `api: "ollama"` follow the same rules. For example, an `ollama-remote` provider that points at a private LAN Ollama host can use `apiKey: "ollama-local"` and sub-agents will resolve that marker through the Ollama provider hook instead of treating it as a missing credential. Memory search can also set `agents.defaults.memorySearch.provider` to that custom provider id so embeddings use the matching Ollama endpoint.
|
||||
</Accordion>
|
||||
<Accordion title="Auth profiles">
|
||||
`auth-profiles.json` stores the credential for a provider id. Put endpoint settings (`baseUrl`, `api`, model ids, headers, timeouts) in `models.providers.<id>`. Older flat auth-profile files such as `{ "ollama-windows": { "apiKey": "ollama-local" } }` are not a runtime format; run `openclaw doctor --fix` to rewrite them to the canonical `ollama-windows:default` API-key profile with a backup. `baseUrl` in that file is compatibility noise and should be moved to provider config.
|
||||
@@ -40,7 +40,7 @@ Ollama provider config uses `baseUrl` as the canonical key. OpenClaw also accept
|
||||
When Ollama is used for memory embeddings, bearer auth is scoped to the host where it was declared:
|
||||
|
||||
- A provider-level key is sent only to that provider's Ollama host.
|
||||
- `agents.*.memory.search.remote.apiKey` is sent only to its remote embedding host.
|
||||
- `agents.*.memorySearch.remote.apiKey` is sent only to its remote embedding host.
|
||||
- A pure `OLLAMA_API_KEY` env value is treated as the Ollama Cloud convention, not sent to local or self-hosted hosts by default.
|
||||
|
||||
</Accordion>
|
||||
@@ -972,12 +972,14 @@ For the full setup and behavior details, see [Ollama Web Search](/tools/ollama-s
|
||||
|
||||
```json5
|
||||
{
|
||||
memory: {
|
||||
search: {
|
||||
provider: "ollama",
|
||||
remote: {
|
||||
// Default for Ollama. Raise on larger hosts if reindexing is too slow.
|
||||
nonBatchConcurrency: 1,
|
||||
agents: {
|
||||
defaults: {
|
||||
memorySearch: {
|
||||
provider: "ollama",
|
||||
remote: {
|
||||
// Default for Ollama. Raise on larger hosts if reindexing is too slow.
|
||||
nonBatchConcurrency: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -988,14 +990,16 @@ For the full setup and behavior details, see [Ollama Web Search](/tools/ollama-s
|
||||
|
||||
```json5
|
||||
{
|
||||
memory: {
|
||||
search: {
|
||||
provider: "ollama",
|
||||
model: "nomic-embed-text",
|
||||
remote: {
|
||||
baseUrl: "http://gpu-box.local:11434",
|
||||
apiKey: "ollama-local",
|
||||
nonBatchConcurrency: 2,
|
||||
agents: {
|
||||
defaults: {
|
||||
memorySearch: {
|
||||
provider: "ollama",
|
||||
model: "nomic-embed-text",
|
||||
remote: {
|
||||
baseUrl: "http://gpu-box.local:11434",
|
||||
apiKey: "ollama-local",
|
||||
nonBatchConcurrency: 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -123,17 +123,19 @@ OpenClaw can use OpenAI, or an OpenAI-compatible embedding endpoint, for
|
||||
|
||||
```json5
|
||||
{
|
||||
memory: {
|
||||
search: {
|
||||
provider: "openai",
|
||||
model: "text-embedding-3-small",
|
||||
agents: {
|
||||
defaults: {
|
||||
memorySearch: {
|
||||
provider: "openai",
|
||||
model: "text-embedding-3-small",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
For OpenAI-compatible endpoints that require asymmetric embedding labels, set
|
||||
`queryInputType` and `documentInputType` under `memory.search`. OpenClaw forwards
|
||||
`queryInputType` and `documentInputType` under `memorySearch`. OpenClaw forwards
|
||||
those as provider-specific `input_type` request fields: query embeddings use
|
||||
`queryInputType`; indexed memory chunks and batch indexing use
|
||||
`documentInputType`. See the [Memory configuration reference](/reference/memory-config#provider-specific-config) for the full example.
|
||||
|
||||
@@ -89,7 +89,7 @@ This migration has one canonical runtime shape:
|
||||
indexing helpers live on `memory-core-host-engine-session-transcripts`; any
|
||||
QMD re-export is compatibility only and must not be used by runtime code.
|
||||
- Built-in memory indexes live in the owning agent database. Runtime config and
|
||||
resolved runtime contracts must not expose `memory.search.store.path`; doctor
|
||||
resolved runtime contracts must not expose `memorySearch.store.path`; doctor
|
||||
deletes that legacy config key and current code passes the agent
|
||||
`databasePath` internally.
|
||||
|
||||
@@ -1557,7 +1557,7 @@ Move these into the global database:
|
||||
`plugin-state/state.sqlite` sidecar importer is deleted.
|
||||
- Builtin memory search no longer defaults to `memory/<agentId>.sqlite`; its
|
||||
index tables live in the owning agent database, and the explicit
|
||||
`memory.search.store.path` sidecar opt-in has been retired to doctor config
|
||||
`memorySearch.store.path` sidecar opt-in has been retired to doctor config
|
||||
migration.
|
||||
- Builtin memory reindex resets only memory-owned tables in the agent database.
|
||||
It must not replace the whole SQLite file, because the same database owns
|
||||
@@ -1890,7 +1890,7 @@ verified extracted payload.
|
||||
- Move Task Flow tables into the global database. Done for runtime writes;
|
||||
the unshipped legacy sidecar importer is deleted.
|
||||
- Move builtin memory-search tables into each agent database. Done; explicit
|
||||
custom `memory.search.store.path` is now removed by doctor config migration.
|
||||
custom `memorySearch.store.path` is now removed by doctor config migration.
|
||||
Full reindex runs in place against memory tables only; the old whole-file
|
||||
swap path and sidecar index swap helper are deleted.
|
||||
- Delete duplicate database openers, WAL setup, permission helpers, and
|
||||
|
||||
@@ -66,7 +66,7 @@ OpenClaw can pick up credentials from:
|
||||
- **Auth profiles** (per-agent, stored in `auth-profiles.json`).
|
||||
- **Environment variables** (e.g. `OPENAI_API_KEY`, `BRAVE_API_KEY`, `FIRECRAWL_API_KEY`).
|
||||
- **Config** (`models.providers.*.apiKey`, `plugins.entries.*.config.webSearch.apiKey`,
|
||||
`plugins.entries.firecrawl.config.webFetch.apiKey`, `memory.search.*`,
|
||||
`plugins.entries.firecrawl.config.webFetch.apiKey`, `memorySearch.*`,
|
||||
`talk.providers.*.apiKey`).
|
||||
- **Skills** (`skills.entries.<name>.apiKey`) which may export keys to the skill process env.
|
||||
|
||||
@@ -113,16 +113,16 @@ and [Models](/concepts/models).
|
||||
|
||||
Semantic memory search uses **embedding APIs** when configured for remote providers:
|
||||
|
||||
- `memory.search.provider = "openai"` → OpenAI embeddings
|
||||
- `memory.search.provider = "gemini"` → Gemini embeddings
|
||||
- `memory.search.provider = "voyage"` → Voyage embeddings
|
||||
- `memory.search.provider = "mistral"` → Mistral embeddings
|
||||
- `memory.search.provider = "deepinfra"` → DeepInfra embeddings
|
||||
- `memory.search.provider = "lmstudio"` → LM Studio embeddings (local/self-hosted)
|
||||
- `memory.search.provider = "ollama"` → Ollama embeddings (local/self-hosted; typically no hosted API billing)
|
||||
- `memorySearch.provider = "openai"` → OpenAI embeddings
|
||||
- `memorySearch.provider = "gemini"` → Gemini embeddings
|
||||
- `memorySearch.provider = "voyage"` → Voyage embeddings
|
||||
- `memorySearch.provider = "mistral"` → Mistral embeddings
|
||||
- `memorySearch.provider = "deepinfra"` → DeepInfra embeddings
|
||||
- `memorySearch.provider = "lmstudio"` → LM Studio embeddings (local/self-hosted)
|
||||
- `memorySearch.provider = "ollama"` → Ollama embeddings (local/self-hosted; typically no hosted API billing)
|
||||
- Optional fallback to a remote provider if local embeddings fail
|
||||
|
||||
You can keep it local with `memory.search.provider = "local"` (no API usage).
|
||||
You can keep it local with `memorySearch.provider = "local"` (no API usage).
|
||||
|
||||
See [Memory](/concepts/memory).
|
||||
|
||||
|
||||
@@ -29,45 +29,10 @@ This page lists every configuration knob for OpenClaw memory search. For concept
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
Memory settings live under `memory` in `openclaw.json`. It is the global
|
||||
baseline. Agent entries can override the same shape at `agents.list[].memory`.
|
||||
|
||||
When OpenClaw resolves memory for an agent, it deep-merges the global baseline
|
||||
with that agent's override. Scalar values and ordinary arrays in the agent
|
||||
override replace the global value. `memory.qmd.paths`,
|
||||
`memory.search.extraPaths`, and `memory.search.qmd.extraCollections` append and
|
||||
deduplicate so an agent can add sources without repeating the global list.
|
||||
|
||||
Global configuration is not shared memory state. Each agent still owns its own
|
||||
SQLite database, workspace roots, QMD home, dreaming artifacts, and wiki state
|
||||
unless you explicitly configure a shared path.
|
||||
|
||||
```json5
|
||||
{
|
||||
memory: {
|
||||
backend: "qmd",
|
||||
search: {
|
||||
provider: "openai",
|
||||
extraPaths: ["~/team-notes"],
|
||||
},
|
||||
},
|
||||
agents: {
|
||||
list: [
|
||||
{
|
||||
id: "research",
|
||||
memory: {
|
||||
search: {
|
||||
extraPaths: ["~/research-notes"],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
```
|
||||
All memory search settings live under `agents.defaults.memorySearch` in `openclaw.json` unless noted otherwise.
|
||||
|
||||
<Note>
|
||||
If you are looking for the **active memory** feature toggle and sub-agent config, that lives under `plugins.entries.active-memory` instead of `memory.search`.
|
||||
If you are looking for the **active memory** feature toggle and sub-agent config, that lives under `plugins.entries.active-memory` instead of `memorySearch`.
|
||||
|
||||
Active memory uses a two-gate model:
|
||||
|
||||
@@ -106,8 +71,7 @@ When `provider` is unset, legacy `provider: "auto"` is present, or
|
||||
`provider: "none"` intentionally selects FTS-only mode, memory recall can still
|
||||
use lexical FTS ranking when embeddings are unavailable.
|
||||
|
||||
Explicit non-local providers fail closed. If you set
|
||||
`memory.search.provider` to
|
||||
Explicit non-local providers fail closed. If you set `memorySearch.provider` to
|
||||
a concrete remote-backed provider such as OpenAI, Gemini, Voyage, Mistral,
|
||||
Bedrock, GitHub Copilot, DeepInfra, Ollama, LM Studio, or an OpenAI-compatible
|
||||
custom provider, and that provider is unavailable at runtime, `memory_search`
|
||||
@@ -117,13 +81,7 @@ provider/auth configuration, switch to a reachable provider, or set
|
||||
|
||||
### Custom provider ids
|
||||
|
||||
`memory.search.provider` can point at a custom
|
||||
`models.providers.<id>` entry for memory-specific provider adapters such as
|
||||
`ollama`, or for OpenAI-compatible model APIs such as `openai-responses` /
|
||||
`openai-completions`. OpenClaw resolves that provider's `api` owner for the
|
||||
embedding adapter while preserving the custom provider id for endpoint, auth,
|
||||
and model-prefix handling. This lets multi-GPU or multi-host setups dedicate
|
||||
memory embeddings to a specific local endpoint:
|
||||
`memorySearch.provider` can point at a custom `models.providers.<id>` entry for memory-specific provider adapters such as `ollama`, or for OpenAI-compatible model APIs such as `openai-responses` / `openai-completions`. OpenClaw resolves that provider's `api` owner for the embedding adapter while preserving the custom provider id for endpoint, auth, and model-prefix handling. This lets multi-GPU or multi-host setups dedicate memory embeddings to a specific local endpoint:
|
||||
|
||||
```json5
|
||||
{
|
||||
@@ -137,10 +95,12 @@ memory embeddings to a specific local endpoint:
|
||||
},
|
||||
},
|
||||
},
|
||||
memory: {
|
||||
search: {
|
||||
provider: "ollama-5080",
|
||||
model: "qwen3-embedding:0.6b",
|
||||
agents: {
|
||||
defaults: {
|
||||
memorySearch: {
|
||||
provider: "ollama-5080",
|
||||
model: "qwen3-embedding:0.6b",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -184,13 +144,15 @@ Use `provider: "openai-compatible"` for a generic OpenAI-compatible
|
||||
|
||||
```json5
|
||||
{
|
||||
memory: {
|
||||
search: {
|
||||
provider: "openai-compatible",
|
||||
model: "text-embedding-3-small",
|
||||
remote: {
|
||||
baseUrl: "https://api.example.com/v1/",
|
||||
apiKey: "YOUR_KEY",
|
||||
agents: {
|
||||
defaults: {
|
||||
memorySearch: {
|
||||
provider: "openai-compatible",
|
||||
model: "text-embedding-3-small",
|
||||
remote: {
|
||||
baseUrl: "https://api.example.com/v1/",
|
||||
apiKey: "YOUR_KEY",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -225,16 +187,18 @@ Use `provider: "openai-compatible"` for a generic OpenAI-compatible
|
||||
|
||||
```json5
|
||||
{
|
||||
memory: {
|
||||
search: {
|
||||
provider: "openai-compatible",
|
||||
remote: {
|
||||
baseUrl: "https://embeddings.example/v1",
|
||||
apiKey: "${EMBEDDINGS_API_KEY}",
|
||||
agents: {
|
||||
defaults: {
|
||||
memorySearch: {
|
||||
provider: "openai-compatible",
|
||||
remote: {
|
||||
baseUrl: "https://embeddings.example/v1",
|
||||
apiKey: "${EMBEDDINGS_API_KEY}",
|
||||
},
|
||||
model: "asymmetric-embedder",
|
||||
queryInputType: "query",
|
||||
documentInputType: "passage",
|
||||
},
|
||||
model: "asymmetric-embedder",
|
||||
queryInputType: "query",
|
||||
documentInputType: "passage",
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -250,10 +214,12 @@ Use `provider: "openai-compatible"` for a generic OpenAI-compatible
|
||||
|
||||
```json5
|
||||
{
|
||||
memory: {
|
||||
search: {
|
||||
provider: "bedrock",
|
||||
model: "amazon.titan-embed-text-v2:0",
|
||||
agents: {
|
||||
defaults: {
|
||||
memorySearch: {
|
||||
provider: "bedrock",
|
||||
model: "amazon.titan-embed-text-v2:0",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -342,7 +308,7 @@ Unset uses the provider default: 600 seconds for local/self-hosted providers suc
|
||||
|
||||
## Hybrid search config
|
||||
|
||||
All under `memory.search.query.hybrid`:
|
||||
All under `memorySearch.query.hybrid`:
|
||||
|
||||
| Key | Type | Default | Description |
|
||||
| --------------------- | --------- | ------- | ---------------------------------- |
|
||||
@@ -373,14 +339,16 @@ All under `memory.search.query.hybrid`:
|
||||
|
||||
```json5
|
||||
{
|
||||
memory: {
|
||||
search: {
|
||||
query: {
|
||||
hybrid: {
|
||||
vectorWeight: 0.7,
|
||||
textWeight: 0.3,
|
||||
mmr: { enabled: true, lambda: 0.7 },
|
||||
temporalDecay: { enabled: true, halfLifeDays: 30 },
|
||||
agents: {
|
||||
defaults: {
|
||||
memorySearch: {
|
||||
query: {
|
||||
hybrid: {
|
||||
vectorWeight: 0.7,
|
||||
textWeight: 0.3,
|
||||
mmr: { enabled: true, lambda: 0.7 },
|
||||
temporalDecay: { enabled: true, halfLifeDays: 30 },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -398,9 +366,11 @@ All under `memory.search.query.hybrid`:
|
||||
|
||||
```json5
|
||||
{
|
||||
memory: {
|
||||
search: {
|
||||
extraPaths: ["../team-docs", "/srv/shared-notes"],
|
||||
agents: {
|
||||
defaults: {
|
||||
memorySearch: {
|
||||
extraPaths: ["../team-docs", "/srv/shared-notes"],
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -408,14 +378,7 @@ All under `memory.search.query.hybrid`:
|
||||
|
||||
Paths can be absolute or workspace-relative. Directories are scanned recursively for `.md` files. Symlink handling depends on the active backend: the builtin engine ignores symlinks, while QMD follows the underlying QMD scanner behavior.
|
||||
|
||||
For agent-scoped cross-agent transcript search, use
|
||||
`agents.list[].memory.search.qmd.extraCollections` instead of
|
||||
`memory.qmd.paths`. Those extra collections follow the same
|
||||
`{ path, name, pattern? }` shape, but they are merged per agent and can preserve
|
||||
explicit shared names when the path points outside the current workspace. If the
|
||||
same resolved path appears in both `memory.qmd.paths` and
|
||||
`memory.search.qmd.extraCollections`, QMD keeps the first entry
|
||||
and skips the duplicate.
|
||||
For agent-scoped cross-agent transcript search, use `agents.list[].memorySearch.qmd.extraCollections` instead of `memory.qmd.paths`. Those extra collections follow the same `{ path, name, pattern? }` shape, but they are merged per agent and can preserve explicit shared names when the path points outside the current workspace. If the same resolved path appears in both `memory.qmd.paths` and `memorySearch.qmd.extraCollections`, QMD keeps the first entry and skips the duplicate.
|
||||
|
||||
---
|
||||
|
||||
@@ -614,10 +577,9 @@ When gateway-start QMD initialization is enabled, OpenClaw starts QMD only for e
|
||||
|
||||
## Dreaming
|
||||
|
||||
Dreaming is configured under `memory.extensions.memory-core.dreaming`, not under `memory.search`.
|
||||
Dreaming is configured under `plugins.entries.memory-core.config.dreaming`, not under `agents.defaults.memorySearch`.
|
||||
|
||||
Each enabled agent gets its own scheduled dreaming sweep. The sweep uses
|
||||
internal light/deep/REM phases as an implementation detail.
|
||||
Dreaming runs as one scheduled sweep and uses internal light/deep/REM phases as an implementation detail.
|
||||
|
||||
For conceptual behavior and slash commands, see [Dreaming](/concepts/dreaming).
|
||||
|
||||
@@ -630,50 +592,10 @@ For conceptual behavior and slash commands, see [Dreaming](/concepts/dreaming).
|
||||
| `model` | `string` | default model | Optional Dream Diary subagent model override |
|
||||
| `phases.deep.maxPromotedSnippetTokens` | `number` | `160` | Maximum estimated tokens kept from each short-term recall snippet promoted into `MEMORY.md`; provenance metadata remains visible |
|
||||
|
||||
### Per-agent dreaming control
|
||||
|
||||
Dreaming is resolved per agent. An agent can opt out with
|
||||
`agents.list[].memory.extensions.memory-core.dreaming.enabled = false`:
|
||||
|
||||
```json5
|
||||
{
|
||||
memory: {
|
||||
extensions: {
|
||||
"memory-core": {
|
||||
dreaming: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
agents: {
|
||||
list: [
|
||||
{ id: "main", memory: { extensions: { "memory-core": { dreaming: { enabled: false } } } } },
|
||||
{ id: "oracle", memory: { extensions: { "memory-core": { dreaming: { enabled: false } } } } },
|
||||
{ id: "librarian" },
|
||||
],
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
In this example, `main` and `oracle` will not get cron jobs, while `librarian`
|
||||
inherits the global enabled setting.
|
||||
|
||||
### Example
|
||||
|
||||
```json5
|
||||
{
|
||||
memory: {
|
||||
extensions: {
|
||||
"memory-core": {
|
||||
dreaming: {
|
||||
enabled: true,
|
||||
frequency: "0 3 * * *",
|
||||
model: "anthropic/claude-sonnet-4-6",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
entries: {
|
||||
"memory-core": {
|
||||
@@ -681,6 +603,13 @@ inherits the global enabled setting.
|
||||
allowModelOverride: true,
|
||||
allowedModels: ["anthropic/claude-sonnet-4-6"],
|
||||
},
|
||||
config: {
|
||||
dreaming: {
|
||||
enabled: true,
|
||||
frequency: "0 3 * * *",
|
||||
model: "anthropic/claude-sonnet-4-6",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -688,11 +617,8 @@ inherits the global enabled setting.
|
||||
```
|
||||
|
||||
<Note>
|
||||
- Dreaming writes agent-private state and artifacts to
|
||||
`memory/.dreams/agents/<agent-id>/`; normal memory search does not index
|
||||
this directory.
|
||||
- Dreaming writes each agent's human-readable narrative output to
|
||||
`memory/.dreams/agents/<agent-id>/DREAMS.md`.
|
||||
- Dreaming writes machine state to `memory/.dreams/`.
|
||||
- Dreaming writes human-readable narrative output to `DREAMS.md` (or existing `dreams.md`).
|
||||
- `dreaming.model` uses the existing plugin subagent trust gate; set `plugins.entries.memory-core.subagent.allowModelOverride: true` before enabling it.
|
||||
- Dream Diary retries once with the session default model when the configured model is unavailable. Trust or allowlist failures are logged and are not silently retried.
|
||||
- The light/deep/REM phase policy and thresholds are internal behavior, not user-facing config.
|
||||
|
||||
@@ -34,15 +34,17 @@ Scope intent:
|
||||
- `models.providers.*.request.tls.key`
|
||||
- `models.providers.*.request.tls.passphrase`
|
||||
- `skills.entries.*.apiKey`
|
||||
- `memory.search.remote.apiKey`
|
||||
- `agents.defaults.memorySearch.remote.apiKey`
|
||||
- `agents.list[].tts.providers.*.apiKey`
|
||||
- `agents.list[].memory.search.remote.apiKey`
|
||||
- `agents.list[].memorySearch.remote.apiKey`
|
||||
- `talk.providers.*.apiKey`
|
||||
- `talk.realtime.providers.*.apiKey`
|
||||
- `messages.tts.providers.*.apiKey`
|
||||
- `tools.web.fetch.firecrawl.apiKey`
|
||||
- `plugins.entries.acpx.config.mcpServers.*.env.*`
|
||||
- `plugins.entries.brave.config.webSearch.apiKey`
|
||||
- `plugins.entries.codex.config.appServer.authToken`
|
||||
- `plugins.entries.codex.config.appServer.headers.*`
|
||||
- `plugins.entries.exa.config.webSearch.apiKey`
|
||||
- `plugins.entries.google-meet.config.realtime.providers.*.apiKey`
|
||||
- `plugins.entries.google.config.webSearch.apiKey`
|
||||
|
||||
@@ -16,16 +16,16 @@
|
||||
],
|
||||
"entries": [
|
||||
{
|
||||
"id": "memory.search.remote.apiKey",
|
||||
"id": "agents.defaults.memorySearch.remote.apiKey",
|
||||
"configFile": "openclaw.json",
|
||||
"path": "memory.search.remote.apiKey",
|
||||
"path": "agents.defaults.memorySearch.remote.apiKey",
|
||||
"secretShape": "secret_input",
|
||||
"optIn": true
|
||||
},
|
||||
{
|
||||
"id": "agents.list[].memory.search.remote.apiKey",
|
||||
"id": "agents.list[].memorySearch.remote.apiKey",
|
||||
"configFile": "openclaw.json",
|
||||
"path": "agents.list[].memory.search.remote.apiKey",
|
||||
"path": "agents.list[].memorySearch.remote.apiKey",
|
||||
"secretShape": "secret_input",
|
||||
"optIn": true
|
||||
},
|
||||
@@ -554,6 +554,20 @@
|
||||
"secretShape": "secret_input",
|
||||
"optIn": true
|
||||
},
|
||||
{
|
||||
"id": "plugins.entries.codex.config.appServer.authToken",
|
||||
"configFile": "openclaw.json",
|
||||
"path": "plugins.entries.codex.config.appServer.authToken",
|
||||
"secretShape": "secret_input",
|
||||
"optIn": true
|
||||
},
|
||||
{
|
||||
"id": "plugins.entries.codex.config.appServer.headers.*",
|
||||
"configFile": "openclaw.json",
|
||||
"path": "plugins.entries.codex.config.appServer.headers.*",
|
||||
"secretShape": "secret_input",
|
||||
"optIn": true
|
||||
},
|
||||
{
|
||||
"id": "plugins.entries.exa.config.webSearch.apiKey",
|
||||
"configFile": "openclaw.json",
|
||||
|
||||
@@ -322,6 +322,7 @@ You can wait on more than just time/text:
|
||||
- `openclaw browser wait --url "**/dash"`
|
||||
- Wait for load state:
|
||||
- `openclaw browser wait --load networkidle`
|
||||
- Supported on managed `openclaw` and raw/remote CDP profiles. The `user` and `existing-session` profiles reject `networkidle`; use `--url`, `--text`, a selector, or `--fn` waits there.
|
||||
- Wait for a JS predicate:
|
||||
- `openclaw browser wait --fn "window.ready===true"`
|
||||
- Wait for a selector to become visible:
|
||||
|
||||
@@ -743,7 +743,7 @@ Compared to the managed `openclaw` profile, existing-session drivers are more co
|
||||
|
||||
- **Screenshots** - page captures and `--ref` element captures work; CSS `--element` selectors do not. `--full-page` cannot combine with `--ref` or `--element`. Playwright is not required for page or ref-based element screenshots.
|
||||
- **Actions** - `click`, `type`, `hover`, `scrollIntoView`, `drag`, and `select` require snapshot refs (no CSS selectors). `click-coords` clicks visible viewport coordinates and does not require a snapshot ref. `click` is left-button only. `type` does not support `slowly=true`; use `fill` or `press`. `press` does not support `delayMs`. `type`, `hover`, `scrollIntoView`, `drag`, `select`, `fill`, and `evaluate` do not support per-call timeouts. `select` accepts a single value.
|
||||
- **Wait / upload / dialog** - `wait --url` supports exact, substring, and glob patterns; `wait --load networkidle` is not supported. Upload hooks require `ref` or `inputRef`, one file at a time, no CSS `element`. Dialog hooks do not support timeout overrides or `dialogId`.
|
||||
- **Wait / upload / dialog** - `wait --url` supports exact, substring, and glob patterns; `wait --load networkidle` is not supported on existing-session profiles (it works on managed and raw/remote CDP profiles). Upload hooks require `ref` or `inputRef`, one file at a time, no CSS `element`. Dialog hooks do not support timeout overrides or `dialogId`.
|
||||
- **Dialog visibility** - Managed browser action responses include `blockedByDialog` and `browserState.dialogs.pending` when an action opens a modal dialog; snapshots also include pending dialog state. Respond with `browser dialog --accept/--dismiss --dialog-id <id>` while a dialog is pending. Dialogs handled outside OpenClaw appear under `browserState.dialogs.recent`.
|
||||
- **Managed-only features** - batch actions, PDF export, download interception, and `responsebody` still require the managed browser path.
|
||||
|
||||
|
||||
@@ -173,6 +173,7 @@ plugins.
|
||||
| --- | --- |
|
||||
| `/new [model]` | Archive the current session and start a fresh one |
|
||||
| `/reset [soft [message]]` | Reset the current session in place. `soft` keeps the transcript, drops reused CLI backend session ids, and reruns startup |
|
||||
| `/name <title>` | Name or rename the current session. Omit the title to see the current name and a suggestion |
|
||||
| `/compact [instructions]` | Compact the session context. See [Compaction](/concepts/compaction) |
|
||||
| `/stop` | Abort the current run |
|
||||
| `/session idle <duration\|off>` | Manage thread-binding idle expiry |
|
||||
|
||||
@@ -297,8 +297,3 @@ export function renderIsolatedCodexConfig(params: {
|
||||
.filter((line, index, lines) => !(line === "" && lines[index - 1] === ""))
|
||||
.join("\n");
|
||||
}
|
||||
|
||||
/** Render only the project trust section for a session-local Codex config. */
|
||||
export function renderIsolatedCodexProjectTrustConfig(projectPaths: string[]): string {
|
||||
return renderIsolatedCodexConfig({ projectPaths });
|
||||
}
|
||||
|
||||
@@ -1398,12 +1398,6 @@ describe("active-memory plugin", () => {
|
||||
|
||||
it("lets active memory inherit the main QMD search mode when configured", async () => {
|
||||
api.config = {
|
||||
memory: {
|
||||
backend: "qmd",
|
||||
qmd: {
|
||||
searchMode: "query",
|
||||
},
|
||||
},
|
||||
agents: {
|
||||
defaults: {
|
||||
model: {
|
||||
@@ -1411,6 +1405,12 @@ describe("active-memory plugin", () => {
|
||||
},
|
||||
},
|
||||
},
|
||||
memory: {
|
||||
backend: "qmd",
|
||||
qmd: {
|
||||
searchMode: "query",
|
||||
},
|
||||
},
|
||||
};
|
||||
api.pluginConfig = {
|
||||
agents: ["main"],
|
||||
@@ -1434,8 +1434,7 @@ describe("active-memory plugin", () => {
|
||||
);
|
||||
|
||||
const config = embeddedRunConfig();
|
||||
const agents = requireRecord(config.agents, "expected agents config");
|
||||
expect(requireRecord(agents.defaults, "expected agent defaults").memory).toEqual({
|
||||
expect(config.memory).toEqual({
|
||||
backend: "qmd",
|
||||
qmd: {
|
||||
searchMode: "query",
|
||||
|
||||
@@ -28,7 +28,7 @@ export const bedrockMemoryEmbeddingProviderAdapter: MemoryEmbeddingProviderAdapt
|
||||
"AWS credentials are not available. " +
|
||||
"Set AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY, AWS_PROFILE, or AWS_BEARER_TOKEN_BEDROCK, " +
|
||||
"configure an EC2/ECS/EKS role, " +
|
||||
"or set memory.search.provider to another provider.",
|
||||
"or set agents.defaults.memorySearch.provider to another provider.",
|
||||
);
|
||||
}
|
||||
const { provider, client } = await createBedrockEmbeddingProvider({
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
// Bonjour tests cover ciao plugin behavior.
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
const { classifyCiaoUnhandledRejection, ignoreCiaoUnhandledRejection } = await import("./ciao.js");
|
||||
const { classifyCiaoProcessError } = await import("./ciao.js");
|
||||
|
||||
describe("bonjour-ciao", () => {
|
||||
it("classifies ciao cancellation rejections separately from side effects", () => {
|
||||
expect(classifyCiaoUnhandledRejection(new Error("CIAO PROBING CANCELLED"))).toEqual({
|
||||
expect(classifyCiaoProcessError(new Error("CIAO PROBING CANCELLED"))).toEqual({
|
||||
kind: "cancellation",
|
||||
formatted: "CIAO PROBING CANCELLED",
|
||||
});
|
||||
@@ -13,7 +13,7 @@ describe("bonjour-ciao", () => {
|
||||
|
||||
it("classifies ciao interface assertions separately from side effects", () => {
|
||||
expect(
|
||||
classifyCiaoUnhandledRejection(
|
||||
classifyCiaoProcessError(
|
||||
new Error("Reached illegal state! IPV4 address change from defined to undefined!"),
|
||||
),
|
||||
).toEqual({
|
||||
@@ -24,7 +24,7 @@ describe("bonjour-ciao", () => {
|
||||
|
||||
it("classifies ciao interface assertions using changed wording", () => {
|
||||
expect(
|
||||
classifyCiaoUnhandledRejection(
|
||||
classifyCiaoProcessError(
|
||||
new Error("Reached illegal state! IPv4 address changed from undefined to defined!"),
|
||||
),
|
||||
).toEqual({
|
||||
@@ -35,7 +35,7 @@ describe("bonjour-ciao", () => {
|
||||
|
||||
it("classifies ciao netmask assertions separately from side effects", () => {
|
||||
expect(
|
||||
classifyCiaoUnhandledRejection(
|
||||
classifyCiaoProcessError(
|
||||
Object.assign(
|
||||
new Error(
|
||||
"IP address version must match. Netmask cannot have a version different from the address!",
|
||||
@@ -52,7 +52,7 @@ describe("bonjour-ciao", () => {
|
||||
|
||||
it("classifies ciao self-probe races separately from side effects", () => {
|
||||
expect(
|
||||
classifyCiaoUnhandledRejection(
|
||||
classifyCiaoProcessError(
|
||||
new Error(
|
||||
"Can't probe for a service which is announced already. Received announcing for service OpenClaw Gateway._openclaw._tcp.local.",
|
||||
),
|
||||
@@ -65,18 +65,18 @@ describe("bonjour-ciao", () => {
|
||||
});
|
||||
|
||||
it("suppresses ciao announcement cancellation rejections", () => {
|
||||
expect(ignoreCiaoUnhandledRejection(new Error("Ciao announcement cancelled by shutdown"))).toBe(
|
||||
true,
|
||||
expect(classifyCiaoProcessError(new Error("Ciao announcement cancelled by shutdown"))).not.toBe(
|
||||
null,
|
||||
);
|
||||
});
|
||||
|
||||
it("suppresses ciao probing cancellation rejections", () => {
|
||||
expect(ignoreCiaoUnhandledRejection(new Error("CIAO PROBING CANCELLED"))).toBe(true);
|
||||
expect(classifyCiaoProcessError(new Error("CIAO PROBING CANCELLED"))).not.toBe(null);
|
||||
});
|
||||
|
||||
it("suppresses wrapped ciao cancellation rejections", () => {
|
||||
expect(
|
||||
classifyCiaoUnhandledRejection({
|
||||
classifyCiaoProcessError({
|
||||
reason: new Error("CIAO ANNOUNCEMENT CANCELLED"),
|
||||
}),
|
||||
).toEqual({
|
||||
@@ -87,7 +87,7 @@ describe("bonjour-ciao", () => {
|
||||
|
||||
it("suppresses aggregate ciao assertion rejections", () => {
|
||||
expect(
|
||||
classifyCiaoUnhandledRejection(
|
||||
classifyCiaoProcessError(
|
||||
new AggregateError([
|
||||
Object.assign(
|
||||
new Error("Reached illegal state! IPV4 address change from defined to undefined!"),
|
||||
@@ -103,7 +103,7 @@ describe("bonjour-ciao", () => {
|
||||
});
|
||||
|
||||
it("suppresses lower-case string cancellation reasons too", () => {
|
||||
expect(ignoreCiaoUnhandledRejection("ciao announcement cancelled during cleanup")).toBe(true);
|
||||
expect(classifyCiaoProcessError("ciao announcement cancelled during cleanup")).not.toBe(null);
|
||||
});
|
||||
|
||||
it("suppresses ciao interface assertion rejections as non-fatal", () => {
|
||||
@@ -112,7 +112,7 @@ describe("bonjour-ciao", () => {
|
||||
{ name: "AssertionError" },
|
||||
);
|
||||
|
||||
expect(ignoreCiaoUnhandledRejection(error)).toBe(true);
|
||||
expect(classifyCiaoProcessError(error)).not.toBe(null);
|
||||
});
|
||||
|
||||
it("suppresses ciao netmask assertion errors as non-fatal", () => {
|
||||
@@ -123,7 +123,7 @@ describe("bonjour-ciao", () => {
|
||||
{ name: "AssertionError" },
|
||||
);
|
||||
|
||||
expect(ignoreCiaoUnhandledRejection(error)).toBe(true);
|
||||
expect(classifyCiaoProcessError(error)).not.toBe(null);
|
||||
});
|
||||
|
||||
it("classifies networkInterfaces SystemError failures (restricted sandboxes)", () => {
|
||||
@@ -131,7 +131,7 @@ describe("bonjour-ciao", () => {
|
||||
new Error("A system error occurred: uv_interface_addresses returned Unknown system error 1"),
|
||||
{ name: "SystemError" },
|
||||
);
|
||||
expect(classifyCiaoUnhandledRejection(err)).toEqual({
|
||||
expect(classifyCiaoProcessError(err)).toEqual({
|
||||
kind: "interface-enumeration-failure",
|
||||
formatted:
|
||||
"SystemError: A system error occurred: uv_interface_addresses returned Unknown system error 1",
|
||||
@@ -144,10 +144,10 @@ describe("bonjour-ciao", () => {
|
||||
{ name: "SystemError" },
|
||||
);
|
||||
const wrapper = new Error("ciao NetworkManager init failed", { cause: inner });
|
||||
expect(ignoreCiaoUnhandledRejection(wrapper)).toBe(true);
|
||||
expect(classifyCiaoProcessError(wrapper)).not.toBe(null);
|
||||
});
|
||||
|
||||
it("keeps unrelated rejections visible", () => {
|
||||
expect(ignoreCiaoUnhandledRejection(new Error("boom"))).toBe(false);
|
||||
expect(classifyCiaoProcessError(new Error("boom"))).toBe(null);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -55,15 +55,3 @@ export function classifyCiaoProcessError(reason: unknown): CiaoProcessErrorClass
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Backward-compatible alias for unhandled-rejection classification.
|
||||
*
|
||||
* @deprecated Use classifyCiaoProcessError.
|
||||
*/
|
||||
export const classifyCiaoUnhandledRejection = classifyCiaoProcessError;
|
||||
|
||||
/** Return whether a ciao unhandled rejection is known and ignorable. */
|
||||
export function ignoreCiaoUnhandledRejection(reason: unknown): boolean {
|
||||
return classifyCiaoProcessError(reason) !== null;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
type AriaSnapshotNode,
|
||||
captureScreenshot,
|
||||
createTargetViaCdp,
|
||||
evaluateJavaScript,
|
||||
formatAriaSnapshot,
|
||||
normalizeCdpWsUrl,
|
||||
type RawAXNode,
|
||||
@@ -329,47 +328,6 @@ describe("cdp internal", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("evaluateJavaScript", () => {
|
||||
it("throws when Runtime.evaluate returns no result", async () => {
|
||||
const server = await startMockWsServer((msg, socket) => {
|
||||
if (msg.method === "Runtime.enable") {
|
||||
socket.send(JSON.stringify({ id: msg.id, result: {} }));
|
||||
return;
|
||||
}
|
||||
if (msg.method === "Runtime.evaluate") {
|
||||
socket.send(JSON.stringify({ id: msg.id, result: {} }));
|
||||
}
|
||||
});
|
||||
wss = server.wss;
|
||||
await expect(evaluateJavaScript({ wsUrl: server.wsUrl, expression: "1" })).rejects.toThrow(
|
||||
/Runtime\.evaluate returned no result/,
|
||||
);
|
||||
});
|
||||
|
||||
it("surfaces CDP exceptionDetails alongside result", async () => {
|
||||
const server = await startMockWsServer((msg, socket) => {
|
||||
if (msg.method === "Runtime.enable") {
|
||||
socket.send(JSON.stringify({ id: msg.id, result: {} }));
|
||||
return;
|
||||
}
|
||||
if (msg.method === "Runtime.evaluate") {
|
||||
socket.send(
|
||||
JSON.stringify({
|
||||
id: msg.id,
|
||||
result: {
|
||||
result: { type: "undefined" },
|
||||
exceptionDetails: { text: "ReferenceError", lineNumber: 1 },
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
});
|
||||
wss = server.wss;
|
||||
const res = await evaluateJavaScript({ wsUrl: server.wsUrl, expression: "boom" });
|
||||
expect(res.exceptionDetails?.text).toBe("ReferenceError");
|
||||
});
|
||||
});
|
||||
|
||||
describe("formatAriaSnapshot", () => {
|
||||
it("returns an empty array when the AX tree is empty", () => {
|
||||
expect(formatAriaSnapshot([], 100)).toStrictEqual([]);
|
||||
@@ -939,27 +897,6 @@ describe("cdp internal", () => {
|
||||
expect(snap.nodes).toStrictEqual([]);
|
||||
});
|
||||
|
||||
it("swallows a failing Runtime.enable in evaluateJavaScript", async () => {
|
||||
// Exercises the `.catch(() => {})` arrow on `Runtime.enable`.
|
||||
const server = await startMockWsServer((msg, socket) => {
|
||||
if (msg.method === "Runtime.enable") {
|
||||
socket.send(JSON.stringify({ id: msg.id, error: { message: "denied" } }));
|
||||
return;
|
||||
}
|
||||
if (msg.method === "Runtime.evaluate") {
|
||||
socket.send(
|
||||
JSON.stringify({
|
||||
id: msg.id,
|
||||
result: { result: { type: "number", value: 1 } },
|
||||
}),
|
||||
);
|
||||
}
|
||||
});
|
||||
wss = server.wss;
|
||||
const res = await evaluateJavaScript({ wsUrl: server.wsUrl, expression: "1" });
|
||||
expect(res.result.value).toBe(1);
|
||||
});
|
||||
|
||||
it("swallows a failing Emulation.clearDeviceMetricsOverride in the screenshot finally", async () => {
|
||||
// Exercises the `.catch(() => {})` on clearDeviceMetricsOverride inside
|
||||
// the fullPage finally block.
|
||||
@@ -1008,5 +945,4 @@ describe("cdp internal", () => {
|
||||
expect(buf.toString("utf8")).toBe("S");
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
isWebSocketUrl,
|
||||
parseBrowserHttpUrl as parseHttpUrl,
|
||||
} from "./cdp.helpers.js";
|
||||
import { createTargetViaCdp, evaluateJavaScript, normalizeCdpWsUrl, snapshotAria } from "./cdp.js";
|
||||
import { createTargetViaCdp, normalizeCdpWsUrl, snapshotAria } from "./cdp.js";
|
||||
import {
|
||||
BROWSER_ENDPOINT_BLOCKED_MESSAGE,
|
||||
BROWSER_NAVIGATION_BLOCKED_MESSAGE,
|
||||
@@ -412,32 +412,6 @@ describe("cdp", () => {
|
||||
).rejects.toBeInstanceOf(BrowserCdpEndpointBlockedError);
|
||||
});
|
||||
|
||||
it("evaluates javascript via CDP", async () => {
|
||||
const wsPort = await startWsServerWithMessages((msg, socket) => {
|
||||
if (msg.method === "Runtime.enable") {
|
||||
socket.send(JSON.stringify({ id: msg.id, result: {} }));
|
||||
return;
|
||||
}
|
||||
if (msg.method === "Runtime.evaluate") {
|
||||
expect(msg.params?.expression).toBe("1+1");
|
||||
socket.send(
|
||||
JSON.stringify({
|
||||
id: msg.id,
|
||||
result: { result: { type: "number", value: 2 } },
|
||||
}),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
const res = await evaluateJavaScript({
|
||||
wsUrl: `ws://127.0.0.1:${wsPort}`,
|
||||
expression: "1+1",
|
||||
});
|
||||
|
||||
expect(res.result.type).toBe("number");
|
||||
expect(res.result.value).toBe(2);
|
||||
});
|
||||
|
||||
it("fails when /json/version omits webSocketDebuggerUrl for an HTTP cdpUrl", async () => {
|
||||
const httpPort = await startVersionHttpServer({});
|
||||
await expect(
|
||||
|
||||
@@ -318,37 +318,6 @@ export type CdpExceptionDetails = {
|
||||
stackTrace?: unknown;
|
||||
};
|
||||
|
||||
/** Evaluate JavaScript in a CDP target and return by value when possible. */
|
||||
export async function evaluateJavaScript(opts: {
|
||||
wsUrl: string;
|
||||
expression: string;
|
||||
awaitPromise?: boolean;
|
||||
returnByValue?: boolean;
|
||||
}): Promise<{
|
||||
result: CdpRemoteObject;
|
||||
exceptionDetails?: CdpExceptionDetails;
|
||||
}> {
|
||||
return await withCdpSocket(opts.wsUrl, async (send) => {
|
||||
await send("Runtime.enable").catch(() => {});
|
||||
const evaluated = (await send("Runtime.evaluate", {
|
||||
expression: opts.expression,
|
||||
awaitPromise: Boolean(opts.awaitPromise),
|
||||
returnByValue: opts.returnByValue ?? true,
|
||||
userGesture: true,
|
||||
includeCommandLineAPI: true,
|
||||
})) as {
|
||||
result?: CdpRemoteObject;
|
||||
exceptionDetails?: CdpExceptionDetails;
|
||||
};
|
||||
|
||||
const result = evaluated?.result;
|
||||
if (!result) {
|
||||
throw new Error("CDP Runtime.evaluate returned no result");
|
||||
}
|
||||
return { result, exceptionDetails: evaluated.exceptionDetails };
|
||||
});
|
||||
}
|
||||
|
||||
/** Normalized accessibility tree node returned by ARIA snapshots. */
|
||||
export type AriaSnapshotNode = {
|
||||
ref: string;
|
||||
|
||||
@@ -7,7 +7,6 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
clickChromeMcpCoords,
|
||||
clickChromeMcpElement,
|
||||
buildChromeMcpArgs,
|
||||
decodeChromeMcpStderrTail,
|
||||
ensureChromeMcpAvailable,
|
||||
evaluateChromeMcpScript,
|
||||
@@ -212,114 +211,6 @@ describe("chrome MCP page parsing", () => {
|
||||
).resolves.toEqual(Buffer.from("screenshot:jpeg"));
|
||||
});
|
||||
|
||||
it("adds --userDataDir when an explicit Chromium profile path is configured", () => {
|
||||
expect(buildChromeMcpArgs("/tmp/brave-profile")).toEqual([
|
||||
"-y",
|
||||
"chrome-devtools-mcp@latest",
|
||||
"--autoConnect",
|
||||
"--no-usage-statistics",
|
||||
"--experimentalStructuredContent",
|
||||
"--experimental-page-id-routing",
|
||||
"--userDataDir",
|
||||
"/tmp/brave-profile",
|
||||
]);
|
||||
});
|
||||
|
||||
it("uses browserUrl for existing-session cdpUrl without also passing userDataDir", () => {
|
||||
expect(
|
||||
buildChromeMcpArgs({
|
||||
cdpUrl: "http://127.0.0.1:9222",
|
||||
userDataDir: "/tmp/brave-profile",
|
||||
}),
|
||||
).toEqual([
|
||||
"-y",
|
||||
"chrome-devtools-mcp@latest",
|
||||
"--browserUrl",
|
||||
"http://127.0.0.1:9222",
|
||||
"--no-usage-statistics",
|
||||
"--experimentalStructuredContent",
|
||||
"--experimental-page-id-routing",
|
||||
]);
|
||||
});
|
||||
|
||||
it("uses wsEndpoint for direct existing-session websocket cdpUrl", () => {
|
||||
expect(
|
||||
buildChromeMcpArgs({
|
||||
cdpUrl: "ws://127.0.0.1:9222/devtools/browser/abc",
|
||||
}),
|
||||
).toEqual([
|
||||
"-y",
|
||||
"chrome-devtools-mcp@latest",
|
||||
"--wsEndpoint",
|
||||
"ws://127.0.0.1:9222/devtools/browser/abc",
|
||||
"--no-usage-statistics",
|
||||
"--experimentalStructuredContent",
|
||||
"--experimental-page-id-routing",
|
||||
]);
|
||||
});
|
||||
|
||||
it("appends custom Chrome MCP args and lets explicit endpoint args override auto-connect", () => {
|
||||
expect(
|
||||
buildChromeMcpArgs({
|
||||
userDataDir: "/tmp/brave-profile",
|
||||
mcpArgs: ["--browserUrl", "http://127.0.0.1:9222", "--no-usage-statistics"],
|
||||
}),
|
||||
).toEqual([
|
||||
"-y",
|
||||
"chrome-devtools-mcp@latest",
|
||||
"--experimentalStructuredContent",
|
||||
"--experimental-page-id-routing",
|
||||
"--browserUrl",
|
||||
"http://127.0.0.1:9222",
|
||||
"--no-usage-statistics",
|
||||
]);
|
||||
});
|
||||
|
||||
it("lets explicit Chrome MCP usage-statistics args override the default opt-out", () => {
|
||||
expect(
|
||||
buildChromeMcpArgs({
|
||||
mcpArgs: ["--usage-statistics"],
|
||||
}),
|
||||
).toEqual([
|
||||
"-y",
|
||||
"chrome-devtools-mcp@latest",
|
||||
"--autoConnect",
|
||||
"--experimentalStructuredContent",
|
||||
"--experimental-page-id-routing",
|
||||
"--usage-statistics",
|
||||
]);
|
||||
});
|
||||
|
||||
it("does not duplicate an explicit Chrome MCP usage-statistics opt-out", () => {
|
||||
expect(
|
||||
buildChromeMcpArgs({
|
||||
mcpArgs: ["--no-usage-statistics"],
|
||||
}),
|
||||
).toEqual([
|
||||
"-y",
|
||||
"chrome-devtools-mcp@latest",
|
||||
"--autoConnect",
|
||||
"--experimentalStructuredContent",
|
||||
"--experimental-page-id-routing",
|
||||
"--no-usage-statistics",
|
||||
]);
|
||||
});
|
||||
|
||||
it("omits the npx package prefix for a custom Chrome MCP command", () => {
|
||||
expect(
|
||||
buildChromeMcpArgs({
|
||||
mcpCommand: "/usr/local/bin/chrome-devtools-mcp",
|
||||
cdpUrl: "http://127.0.0.1:9222",
|
||||
}),
|
||||
).toEqual([
|
||||
"--browserUrl",
|
||||
"http://127.0.0.1:9222",
|
||||
"--no-usage-statistics",
|
||||
"--experimentalStructuredContent",
|
||||
"--experimental-page-id-routing",
|
||||
]);
|
||||
});
|
||||
|
||||
it("terminates the owned Chrome MCP subprocess tree when closing temporary sessions", async () => {
|
||||
const session = createFakeSession();
|
||||
Object.assign(session, { ownsProcessTree: true });
|
||||
|
||||
@@ -462,11 +462,6 @@ function buildChromeMcpArgsFromOptions(options: NormalizedChromeMcpProfileOption
|
||||
];
|
||||
}
|
||||
|
||||
/** Build command-line args for launching chrome-devtools-mcp. */
|
||||
export function buildChromeMcpArgs(input?: string | ChromeMcpProfileOptions): string[] {
|
||||
return buildChromeMcpArgsFromOptions(normalizeChromeMcpOptions(input));
|
||||
}
|
||||
|
||||
function drainStderr(transport: StdioClientTransport): () => string {
|
||||
const stream = transport.stderr;
|
||||
if (!stream) {
|
||||
|
||||
@@ -36,12 +36,133 @@ function createElementProgram(): Command {
|
||||
return program;
|
||||
}
|
||||
|
||||
function getLastActionBody(): Record<string, unknown> | undefined {
|
||||
return (mocks.callBrowserRequest.mock.calls.at(-1)?.[1] as { body?: Record<string, unknown> })
|
||||
?.body;
|
||||
}
|
||||
|
||||
describe("browser element commands", () => {
|
||||
beforeEach(() => {
|
||||
mocks.callBrowserRequest.mockClear();
|
||||
getBrowserCliRuntimeCapture().resetRuntimeCapture();
|
||||
});
|
||||
|
||||
it.each([
|
||||
{
|
||||
name: "click",
|
||||
argv: [
|
||||
"browser",
|
||||
"click",
|
||||
" ref-1 ",
|
||||
"--target-id",
|
||||
"tab-1",
|
||||
"--double",
|
||||
"--button",
|
||||
"right",
|
||||
"--modifiers",
|
||||
"Shift, Alt",
|
||||
],
|
||||
expectedBody: {
|
||||
kind: "click",
|
||||
ref: "ref-1",
|
||||
targetId: "tab-1",
|
||||
doubleClick: true,
|
||||
button: "right",
|
||||
modifiers: ["Shift", "Alt"],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "click-coords",
|
||||
argv: [
|
||||
"browser",
|
||||
"click-coords",
|
||||
"12.5",
|
||||
"42",
|
||||
"--target-id",
|
||||
"tab-2",
|
||||
"--double",
|
||||
"--button",
|
||||
"middle",
|
||||
"--delay-ms",
|
||||
"25",
|
||||
],
|
||||
expectedBody: {
|
||||
kind: "clickCoords",
|
||||
x: 12.5,
|
||||
y: 42,
|
||||
targetId: "tab-2",
|
||||
doubleClick: true,
|
||||
button: "middle",
|
||||
delayMs: 25,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "type",
|
||||
argv: ["browser", "type", "input-1", "hello", "--submit", "--slowly", "--target-id", "tab-2"],
|
||||
expectedBody: {
|
||||
kind: "type",
|
||||
ref: "input-1",
|
||||
text: "hello",
|
||||
submit: true,
|
||||
slowly: true,
|
||||
targetId: "tab-2",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "press",
|
||||
argv: ["browser", "press", "Enter", "--target-id", "tab-3"],
|
||||
expectedBody: { kind: "press", key: "Enter", targetId: "tab-3" },
|
||||
},
|
||||
{
|
||||
name: "hover",
|
||||
argv: ["browser", "hover", "node-1", "--target-id", "tab-4"],
|
||||
expectedBody: { kind: "hover", ref: "node-1", targetId: "tab-4" },
|
||||
},
|
||||
{
|
||||
name: "scrollintoview",
|
||||
argv: ["browser", "scrollintoview", "node-2", "--target-id", "tab-5"],
|
||||
expectedBody: { kind: "scrollIntoView", ref: "node-2", targetId: "tab-5" },
|
||||
},
|
||||
{
|
||||
name: "drag",
|
||||
argv: ["browser", "drag", "start-1", "end-1", "--target-id", "tab-6"],
|
||||
expectedBody: {
|
||||
kind: "drag",
|
||||
startRef: "start-1",
|
||||
endRef: "end-1",
|
||||
targetId: "tab-6",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "select",
|
||||
argv: ["browser", "select", "select-1", "alpha", "beta", "--target-id", "tab-7"],
|
||||
expectedBody: {
|
||||
kind: "select",
|
||||
ref: "select-1",
|
||||
values: ["alpha", "beta"],
|
||||
targetId: "tab-7",
|
||||
},
|
||||
},
|
||||
])("sends the expected $name action body", async ({ argv, expectedBody }) => {
|
||||
const program = createElementProgram();
|
||||
|
||||
await program.parseAsync(argv, { from: "user" });
|
||||
|
||||
expect(getLastActionBody()).toMatchObject(expectedBody);
|
||||
});
|
||||
|
||||
it("rejects a blank required ref before dispatch", async () => {
|
||||
const program = createElementProgram();
|
||||
|
||||
await expect(program.parseAsync(["browser", "click", " "], { from: "user" })).rejects.toThrow(
|
||||
"__exit__:1",
|
||||
);
|
||||
|
||||
const capture = getBrowserCliRuntimeCapture();
|
||||
expect(capture.runtimeErrors.join("\n")).toContain("ref is required");
|
||||
expect(mocks.callBrowserRequest).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("rejects non-decimal coordinate values before dispatch", async () => {
|
||||
const program = createElementProgram();
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user