mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-15 18:49:31 +08:00
Compare commits
102 Commits
codex/agen
...
codex/runt
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fc26444f6d | ||
|
|
5df08201ff | ||
|
|
70eabd3b08 | ||
|
|
ab3a3d14f0 | ||
|
|
6f4272bd04 | ||
|
|
830a72d2ee | ||
|
|
139122f655 | ||
|
|
e74347bbe7 | ||
|
|
a95d7ab1c8 | ||
|
|
36835592df | ||
|
|
6785633d13 | ||
|
|
70717c50fc | ||
|
|
5a4676bd64 | ||
|
|
fa8a85586c | ||
|
|
56fe64e8e3 | ||
|
|
6a8b4e422e | ||
|
|
0fca665497 | ||
|
|
2597723dfc | ||
|
|
7f4c0b3192 | ||
|
|
91ed1604b0 | ||
|
|
84638bfbb0 | ||
|
|
4ad4be9aff | ||
|
|
07bf572f35 | ||
|
|
c97998ce21 | ||
|
|
f482e4d335 | ||
|
|
484a289be3 | ||
|
|
95a1c91531 | ||
|
|
b6c9ed66c3 | ||
|
|
cf9e9cd119 | ||
|
|
dd0a9bf869 | ||
|
|
9cc5e49e65 | ||
|
|
f05e2222f3 | ||
|
|
9eaadcdf29 | ||
|
|
f4797921ac | ||
|
|
8e88c7b297 | ||
|
|
fcb9dcc886 | ||
|
|
237fcbcbf1 | ||
|
|
9b279ef173 | ||
|
|
11a038207b | ||
|
|
3a89e20b7b | ||
|
|
a68ad39877 | ||
|
|
c41a73b828 | ||
|
|
238e72d74d | ||
|
|
11d6a3f892 | ||
|
|
c967628816 | ||
|
|
923ea990fd | ||
|
|
53efb6747d | ||
|
|
6554e85ad6 | ||
|
|
a966303216 | ||
|
|
dd09e6fe40 | ||
|
|
a85261932e | ||
|
|
6ce1c98b61 | ||
|
|
347b51be4b | ||
|
|
548b55676f | ||
|
|
772034d741 | ||
|
|
c65f3bc70e | ||
|
|
be33b68fd4 | ||
|
|
955b025697 | ||
|
|
037174141e | ||
|
|
897bac5b8c | ||
|
|
01dd593cfd | ||
|
|
64514a6548 | ||
|
|
e867ab7e16 | ||
|
|
f2bf925a38 | ||
|
|
530e4f93de | ||
|
|
113761ab57 | ||
|
|
2f69c40a62 | ||
|
|
55a8f56a15 | ||
|
|
56636dfe57 | ||
|
|
6ef7fa08af | ||
|
|
2c0f8a0beb | ||
|
|
0fd6607d56 | ||
|
|
7ad53cefee | ||
|
|
1c33990108 | ||
|
|
8b701ce1c7 | ||
|
|
a6159bb60d | ||
|
|
b165c0d10a | ||
|
|
c676cd4dcf | ||
|
|
e1fec3c892 | ||
|
|
bf3b994378 | ||
|
|
f2b01bb7b1 | ||
|
|
5852f5d15c | ||
|
|
f4b2a08c85 | ||
|
|
b5d434db61 | ||
|
|
758051322d | ||
|
|
55bff24973 | ||
|
|
283c957fdc | ||
|
|
8de5a55317 | ||
|
|
129b9dad9e | ||
|
|
9170243f92 | ||
|
|
45778c66f4 | ||
|
|
8e17910191 | ||
|
|
8974a78f47 | ||
|
|
afdf03b563 | ||
|
|
3a901b5e95 | ||
|
|
61386055b1 | ||
|
|
34ca9adbf5 | ||
|
|
c8f3fecad6 | ||
|
|
1831e124b2 | ||
|
|
c25f319d49 | ||
|
|
8a66694c5e | ||
|
|
6b4ff8be81 |
@@ -32,6 +32,14 @@ pnpm crabbox:run -- --help | sed -n '1,120p'
|
||||
Even if config still says AWS, maintainer validation should normally pass
|
||||
`--provider blacksmith-testbox`.
|
||||
- Prefer local targeted tests for tight edit loops. Broad gates belong remote.
|
||||
- Do not treat inherited shell env as operator intent. In particular,
|
||||
`OPENCLAW_LOCAL_CHECK_MODE=throttled` from the local shell is not permission
|
||||
to move broad `pnpm check:changed`, `pnpm test:changed`, full `pnpm test`, or
|
||||
lint/typecheck fan-out onto the laptop.
|
||||
- Only use `OPENCLAW_LOCAL_CHECK_MODE=throttled|full` when the user explicitly
|
||||
asks for local proof in the current task. If Testbox is queued or capacity is
|
||||
constrained, report the blocker and keep only targeted local edit-loop checks
|
||||
running.
|
||||
|
||||
## macOS And Windows Targets
|
||||
|
||||
@@ -198,6 +206,10 @@ Common Crabbox-only failures:
|
||||
printed Actions URL.
|
||||
- Cleanup uncertainty: run `blacksmith testbox list` and stop only boxes you
|
||||
created.
|
||||
- Testbox queued/capacity pressure: do not convert a broad changed gate or full
|
||||
suite into local `OPENCLAW_LOCAL_CHECK_MODE=throttled pnpm ...`. Leave the
|
||||
remote lane queued, switch to a narrower targeted local check, or stop and
|
||||
report the capacity blocker.
|
||||
|
||||
If Crabbox cannot dispatch, sync, attach, or stop but Blacksmith itself works,
|
||||
use direct Blacksmith from the repo root:
|
||||
@@ -284,9 +296,27 @@ Install/auth for owned Crabbox if needed:
|
||||
|
||||
```sh
|
||||
brew install openclaw/tap/crabbox
|
||||
printf '%s' "$CRABBOX_COORDINATOR_TOKEN" | crabbox login --url https://crabbox.openclaw.ai --provider aws --token-stdin
|
||||
crabbox login --url https://crabbox.openclaw.ai --provider aws
|
||||
```
|
||||
|
||||
New users should self-resolve broker auth before anyone asks for AWS keys:
|
||||
|
||||
```sh
|
||||
crabbox config show
|
||||
crabbox doctor
|
||||
crabbox whoami
|
||||
```
|
||||
|
||||
- If broker auth is missing, run `crabbox login --url https://crabbox.openclaw.ai --provider aws`.
|
||||
- If the CLI asks for `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, or AWS
|
||||
profile setup during normal OpenClaw validation, assume the agent selected
|
||||
the wrong path. Use brokered `crabbox login`, `--provider blacksmith-testbox`,
|
||||
or an existing brokered lease before asking the user for cloud credentials.
|
||||
- Ask for AWS keys only for explicit direct-provider/account administration,
|
||||
not for normal brokered OpenClaw proof.
|
||||
- Trusted automation may still use
|
||||
`printf '%s' "$CRABBOX_COORDINATOR_TOKEN" | crabbox login --url https://crabbox.openclaw.ai --provider aws --token-stdin`.
|
||||
|
||||
macOS config lives at:
|
||||
|
||||
```text
|
||||
|
||||
@@ -14,7 +14,6 @@ query-filters:
|
||||
- security
|
||||
|
||||
paths:
|
||||
- extensions/bluebubbles/src
|
||||
- extensions/discord/src
|
||||
- extensions/feishu/src
|
||||
- extensions/googlechat/src
|
||||
|
||||
28
.github/codeql/codeql-network-runtime-boundary-critical-quality.yml
vendored
Normal file
28
.github/codeql/codeql-network-runtime-boundary-critical-quality.yml
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
name: openclaw-codeql-network-runtime-boundary-critical-quality
|
||||
|
||||
disable-default-queries: true
|
||||
|
||||
queries:
|
||||
- uses: ./.github/codeql/openclaw-boundary/queries/raw-socket-callsite-classification.ql
|
||||
- uses: ./.github/codeql/openclaw-boundary/queries/managed-proxy-runtime-mutation.ql
|
||||
|
||||
paths:
|
||||
- src
|
||||
- extensions
|
||||
|
||||
paths-ignore:
|
||||
- "**/node_modules"
|
||||
- "**/coverage"
|
||||
- "**/*.generated.ts"
|
||||
- "**/*.bundle.js"
|
||||
- "**/*-runtime.js"
|
||||
- "**/*.test.ts"
|
||||
- "**/*.test.tsx"
|
||||
- "**/*.e2e.test.ts"
|
||||
- "**/*.e2e.test.tsx"
|
||||
- "**/*test-support*"
|
||||
- "**/*test-helper*"
|
||||
- "**/*mock*"
|
||||
- "**/*fixture*"
|
||||
- "**/*bench*"
|
||||
- "extensions/diffs/assets/**"
|
||||
30
.github/codeql/openclaw-boundary/codeql-pack.lock.yml
vendored
Normal file
30
.github/codeql/openclaw-boundary/codeql-pack.lock.yml
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
---
|
||||
lockVersion: 1.0.0
|
||||
dependencies:
|
||||
codeql/concepts:
|
||||
version: 0.0.22
|
||||
codeql/controlflow:
|
||||
version: 2.0.32
|
||||
codeql/dataflow:
|
||||
version: 2.1.4
|
||||
codeql/javascript-all:
|
||||
version: 2.6.28
|
||||
codeql/mad:
|
||||
version: 1.0.48
|
||||
codeql/regex:
|
||||
version: 1.0.48
|
||||
codeql/ssa:
|
||||
version: 2.0.24
|
||||
codeql/threat-models:
|
||||
version: 1.0.48
|
||||
codeql/tutorial:
|
||||
version: 1.0.48
|
||||
codeql/typetracking:
|
||||
version: 2.0.32
|
||||
codeql/util:
|
||||
version: 2.0.35
|
||||
codeql/xml:
|
||||
version: 1.0.48
|
||||
codeql/yaml:
|
||||
version: 1.0.48
|
||||
compiled: false
|
||||
6
.github/codeql/openclaw-boundary/qlpack.yml
vendored
Normal file
6
.github/codeql/openclaw-boundary/qlpack.yml
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
name: openclaw/codeql-boundary-queries
|
||||
version: 0.0.0
|
||||
library: false
|
||||
dependencies:
|
||||
codeql/javascript-all: 2.6.28
|
||||
extractor: javascript
|
||||
325
.github/codeql/openclaw-boundary/queries/managed-proxy-runtime-mutation.ql
vendored
Normal file
325
.github/codeql/openclaw-boundary/queries/managed-proxy-runtime-mutation.ql
vendored
Normal file
@@ -0,0 +1,325 @@
|
||||
/**
|
||||
* @name Managed proxy runtime mutation
|
||||
* @description Proxy-related process.env and GLOBAL_AGENT runtime mutations must stay in managed proxy owner scopes.
|
||||
* @kind problem
|
||||
* @problem.severity error
|
||||
* @precision high
|
||||
* @id js/openclaw/managed-proxy-runtime-mutation
|
||||
* @tags maintainability
|
||||
* security
|
||||
* external/cwe/cwe-441
|
||||
*/
|
||||
|
||||
import javascript
|
||||
|
||||
predicate forbiddenEnvKey(string key) {
|
||||
key =
|
||||
[
|
||||
"HTTP_PROXY",
|
||||
"HTTPS_PROXY",
|
||||
"http_proxy",
|
||||
"https_proxy",
|
||||
"NO_PROXY",
|
||||
"no_proxy",
|
||||
"GLOBAL_AGENT_HTTP_PROXY",
|
||||
"GLOBAL_AGENT_HTTPS_PROXY",
|
||||
"GLOBAL_AGENT_NO_PROXY",
|
||||
"GLOBAL_AGENT_FORCE_GLOBAL_AGENT",
|
||||
"OPENCLAW_PROXY_ACTIVE",
|
||||
"OPENCLAW_PROXY_LOOPBACK_MODE"
|
||||
]
|
||||
}
|
||||
|
||||
predicate forbiddenGlobalAgentKey(string key) { key = ["HTTP_PROXY", "HTTPS_PROXY", "NO_PROXY"] }
|
||||
|
||||
predicate relevantSourceFile(File file) {
|
||||
exists(string path |
|
||||
path = file.getRelativePath() and
|
||||
path.regexpMatch("^(src|extensions)/.*\\.(ts|mts|js|mjs)$") and
|
||||
not path.regexpMatch(".*\\.(test|spec)\\.(ts|mts|js|mjs)$") and
|
||||
not path.regexpMatch(".*\\.(test-utils|test-harness|e2e-harness)\\.ts$") and
|
||||
not path.regexpMatch(".*/test-support/.*") and
|
||||
not path.regexpMatch(".*/vendor/.*") and
|
||||
not path.regexpMatch(".*\\.min\\.js$") and
|
||||
not path.regexpMatch("^extensions/diffs/assets/.*")
|
||||
)
|
||||
}
|
||||
|
||||
predicate namedExpr(Expr expr, string name) {
|
||||
expr.getUnderlyingValue().(Identifier).getName() = name
|
||||
}
|
||||
|
||||
predicate directProcessEnvExpr(Expr expr) {
|
||||
exists(PropAccess access |
|
||||
expr.getUnderlyingValue() = access and
|
||||
access.getPropertyName() = "env" and
|
||||
namedExpr(access.getBase(), "process")
|
||||
)
|
||||
}
|
||||
|
||||
predicate envAlias(Variable variable) {
|
||||
exists(VariableDeclarator decl |
|
||||
decl.getBindingPattern().getAVariable() = variable and
|
||||
directProcessEnvExpr(decl.getInit())
|
||||
)
|
||||
or
|
||||
exists(VariableDeclarator decl, ObjectPattern pattern, PropertyPattern property |
|
||||
decl.getBindingPattern() = pattern and
|
||||
namedExpr(decl.getInit(), "process") and
|
||||
property = pattern.getAPropertyPattern() and
|
||||
property.getName() = "env" and
|
||||
property.getValuePattern().(BindingPattern).getAVariable() = variable
|
||||
)
|
||||
}
|
||||
|
||||
predicate processEnvExpr(Expr expr) {
|
||||
directProcessEnvExpr(expr)
|
||||
or
|
||||
exists(VarAccess access |
|
||||
expr.getUnderlyingValue() = access and
|
||||
envAlias(access.getVariable())
|
||||
)
|
||||
}
|
||||
|
||||
predicate stringConst(Variable variable, string value) {
|
||||
exists(VariableDeclarator decl |
|
||||
decl.getBindingPattern().getAVariable() = variable and
|
||||
value = decl.getInit().getStringValue()
|
||||
)
|
||||
}
|
||||
|
||||
predicate stringArrayContains(Variable variable, string value) {
|
||||
exists(VariableDeclarator decl, ArrayExpr array, Expr element |
|
||||
decl.getBindingPattern().getAVariable() = variable and
|
||||
decl.getInit().getUnderlyingValue() = array and
|
||||
element = array.getAnElement().getUnderlyingValue() and
|
||||
value = element.getStringValue()
|
||||
)
|
||||
or
|
||||
exists(VariableDeclarator decl, ArrayExpr array, SpreadElement spread, VarAccess access |
|
||||
decl.getBindingPattern().getAVariable() = variable and
|
||||
decl.getInit().getUnderlyingValue() = array and
|
||||
spread = array.getAnElement().getUnderlyingValue() and
|
||||
spread.getOperand().getUnderlyingValue() = access and
|
||||
stringArrayContains(access.getVariable(), value)
|
||||
)
|
||||
}
|
||||
|
||||
predicate forbiddenEnvLoopVariable(Variable variable) {
|
||||
exists(ForOfStmt loop, VarAccess domain, string key |
|
||||
variable = loop.getAnIterationVariable() and
|
||||
loop.getIterationDomain().getUnderlyingValue() = domain and
|
||||
stringArrayContains(domain.getVariable(), key) and
|
||||
forbiddenEnvKey(key)
|
||||
)
|
||||
}
|
||||
|
||||
predicate envKeyExprForbidden(Expr keyExpr) {
|
||||
forbiddenEnvKey(keyExpr.getStringValue())
|
||||
or
|
||||
exists(VarAccess access, string key |
|
||||
keyExpr.getUnderlyingValue() = access and
|
||||
stringConst(access.getVariable(), key) and
|
||||
forbiddenEnvKey(key)
|
||||
)
|
||||
or
|
||||
exists(VarAccess access |
|
||||
keyExpr.getUnderlyingValue() = access and
|
||||
forbiddenEnvLoopVariable(access.getVariable())
|
||||
)
|
||||
}
|
||||
|
||||
predicate globalAgentKeyExprForbidden(Expr keyExpr) {
|
||||
forbiddenGlobalAgentKey(keyExpr.getStringValue())
|
||||
or
|
||||
exists(VarAccess access, string key |
|
||||
keyExpr.getUnderlyingValue() = access and
|
||||
stringConst(access.getVariable(), key) and
|
||||
forbiddenGlobalAgentKey(key)
|
||||
)
|
||||
}
|
||||
|
||||
predicate directGlobalExpr(Expr expr) {
|
||||
namedExpr(expr, "global")
|
||||
or
|
||||
namedExpr(expr, "globalThis")
|
||||
}
|
||||
|
||||
predicate globalAlias(Variable variable) {
|
||||
exists(VariableDeclarator decl |
|
||||
decl.getBindingPattern().getAVariable() = variable and
|
||||
directGlobalExpr(decl.getInit())
|
||||
)
|
||||
}
|
||||
|
||||
predicate globalExpr(Expr expr) {
|
||||
directGlobalExpr(expr)
|
||||
or
|
||||
exists(VarAccess access |
|
||||
expr.getUnderlyingValue() = access and
|
||||
globalAlias(access.getVariable())
|
||||
)
|
||||
}
|
||||
|
||||
predicate directGlobalAgentExpr(Expr expr) {
|
||||
exists(PropAccess access |
|
||||
expr.getUnderlyingValue() = access and
|
||||
access.getPropertyName() = "GLOBAL_AGENT" and
|
||||
globalExpr(access.getBase())
|
||||
)
|
||||
}
|
||||
|
||||
predicate globalAgentAlias(Variable variable) {
|
||||
exists(VariableDeclarator decl |
|
||||
decl.getBindingPattern().getAVariable() = variable and
|
||||
directGlobalAgentExpr(decl.getInit())
|
||||
)
|
||||
}
|
||||
|
||||
predicate globalAgentExpr(Expr expr) {
|
||||
directGlobalAgentExpr(expr)
|
||||
or
|
||||
exists(VarAccess access |
|
||||
expr.getUnderlyingValue() = access and
|
||||
globalAgentAlias(access.getVariable())
|
||||
)
|
||||
}
|
||||
|
||||
predicate envMutationTarget(Expr target) {
|
||||
exists(PropAccess access |
|
||||
target.getUnderlyingReference() = access and
|
||||
processEnvExpr(access.getBase()) and
|
||||
(
|
||||
forbiddenEnvKey(access.getPropertyName())
|
||||
or
|
||||
envKeyExprForbidden(access.getPropertyNameExpr())
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
predicate globalAgentMutationTarget(Expr target) {
|
||||
globalAgentExpr(target)
|
||||
or
|
||||
exists(PropAccess access |
|
||||
target.getUnderlyingReference() = access and
|
||||
globalAgentExpr(access.getBase()) and
|
||||
(
|
||||
forbiddenGlobalAgentKey(access.getPropertyName())
|
||||
or
|
||||
globalAgentKeyExprForbidden(access.getPropertyNameExpr())
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
predicate objectPropertyWithKey(Expr expr, string key) {
|
||||
exists(ObjectExpr object, Property property |
|
||||
expr.getUnderlyingValue() = object and
|
||||
property = object.getAProperty() and
|
||||
property.getName() = key
|
||||
)
|
||||
}
|
||||
|
||||
Expr managedProxyRuntimeMutation() {
|
||||
exists(Assignment assignment |
|
||||
result = assignment and
|
||||
(
|
||||
envMutationTarget(assignment.getTarget())
|
||||
or
|
||||
globalAgentMutationTarget(assignment.getTarget())
|
||||
)
|
||||
)
|
||||
or
|
||||
exists(DeleteExpr delete |
|
||||
result = delete and
|
||||
(
|
||||
envMutationTarget(delete.getOperand())
|
||||
or
|
||||
globalAgentMutationTarget(delete.getOperand())
|
||||
)
|
||||
)
|
||||
or
|
||||
exists(MethodCallExpr call |
|
||||
result = call and
|
||||
namedExpr(call.getReceiver(), "Object") and
|
||||
call.getMethodName() = "assign" and
|
||||
(
|
||||
processEnvExpr(call.getArgument(0)) and
|
||||
exists(string key |
|
||||
forbiddenEnvKey(key) and
|
||||
objectPropertyWithKey(call.getArgument(1), key)
|
||||
)
|
||||
or
|
||||
globalAgentExpr(call.getArgument(0)) and
|
||||
exists(string key |
|
||||
forbiddenGlobalAgentKey(key) and
|
||||
objectPropertyWithKey(call.getArgument(1), key)
|
||||
)
|
||||
)
|
||||
)
|
||||
or
|
||||
exists(MethodCallExpr call |
|
||||
result = call and
|
||||
namedExpr(call.getReceiver(), "Object") and
|
||||
call.getMethodName() = "defineProperty" and
|
||||
(
|
||||
processEnvExpr(call.getArgument(0)) and
|
||||
envKeyExprForbidden(call.getArgument(1))
|
||||
or
|
||||
globalAgentExpr(call.getArgument(0)) and
|
||||
globalAgentKeyExprForbidden(call.getArgument(1))
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
predicate allowedFunctionOwnerScope(Expr mutation, string path, string functionName) {
|
||||
exists(Function owner |
|
||||
mutation.getFile().getRelativePath() = path and
|
||||
owner.getFile() = mutation.getFile() and
|
||||
owner.getName() = functionName and
|
||||
mutation.getParent*() = owner.getBody()
|
||||
)
|
||||
}
|
||||
|
||||
predicate allowedMethodOwnerScope(Expr mutation, string path, string methodName) {
|
||||
exists(MethodDeclaration method |
|
||||
mutation.getFile().getRelativePath() = path and
|
||||
method.getFile() = mutation.getFile() and
|
||||
method.getDeclaringType().getName() + "." + method.getName() = methodName and
|
||||
mutation.getParent*() = method.getBody().getBody()
|
||||
)
|
||||
}
|
||||
|
||||
predicate allowedManagedProxyRuntimeMutation(Expr mutation) {
|
||||
allowedFunctionOwnerScope(mutation, "src/infra/net/proxy/proxy-lifecycle.ts", "applyProxyEnv")
|
||||
or
|
||||
allowedFunctionOwnerScope(mutation, "src/infra/net/proxy/proxy-lifecycle.ts", "restoreProxyEnv")
|
||||
or
|
||||
allowedFunctionOwnerScope(mutation, "src/infra/net/proxy/proxy-lifecycle.ts",
|
||||
"restoreGlobalAgentRuntime")
|
||||
or
|
||||
allowedFunctionOwnerScope(mutation, "src/infra/net/proxy/proxy-lifecycle.ts",
|
||||
"restoreNodeHttpStack")
|
||||
or
|
||||
allowedFunctionOwnerScope(mutation, "src/infra/net/proxy/proxy-lifecycle.ts",
|
||||
"bootstrapNodeHttpStack")
|
||||
or
|
||||
allowedFunctionOwnerScope(mutation, "src/infra/net/proxy/proxy-lifecycle.ts",
|
||||
"writeGlobalAgentNoProxy")
|
||||
or
|
||||
allowedFunctionOwnerScope(mutation, "src/infra/net/proxy/proxy-lifecycle.ts",
|
||||
"disableGlobalAgentProxyForIpv6GatewayLoopback")
|
||||
or
|
||||
allowedMethodOwnerScope(mutation, "extensions/browser/src/browser/cdp-proxy-bypass.ts",
|
||||
"NoProxyLeaseManager.acquire")
|
||||
or
|
||||
allowedMethodOwnerScope(mutation, "extensions/browser/src/browser/cdp-proxy-bypass.ts",
|
||||
"NoProxyLeaseManager.release")
|
||||
}
|
||||
|
||||
from Expr mutation
|
||||
where
|
||||
managedProxyRuntimeMutation() = mutation and
|
||||
relevantSourceFile(mutation.getFile()) and
|
||||
not allowedManagedProxyRuntimeMutation(mutation)
|
||||
select mutation,
|
||||
"Only managed proxy owner scopes may mutate proxy-related process.env or GLOBAL_AGENT runtime state."
|
||||
92
.github/codeql/openclaw-boundary/queries/raw-socket-callsite-classification.ql
vendored
Normal file
92
.github/codeql/openclaw-boundary/queries/raw-socket-callsite-classification.ql
vendored
Normal file
@@ -0,0 +1,92 @@
|
||||
/**
|
||||
* @name Raw socket client callsite classification
|
||||
* @description Raw net/tls/http2 client egress must be classified before landing.
|
||||
* @kind problem
|
||||
* @problem.severity error
|
||||
* @precision high
|
||||
* @id js/openclaw/raw-socket-callsite-classification
|
||||
* @tags maintainability
|
||||
* security
|
||||
* external/cwe/cwe-441
|
||||
*/
|
||||
|
||||
import javascript
|
||||
|
||||
predicate rawModule(string moduleName) {
|
||||
moduleName = ["net", "node:net", "tls", "node:tls", "http2", "node:http2"]
|
||||
}
|
||||
|
||||
predicate netModule(string moduleName) { moduleName = ["net", "node:net"] }
|
||||
|
||||
predicate rawConnectMember(string memberName) { memberName = ["connect", "createConnection"] }
|
||||
|
||||
predicate relevantSourceFile(File file) {
|
||||
exists(string path |
|
||||
path = file.getRelativePath() and
|
||||
path.regexpMatch("^(src|extensions)/.*\\.ts$") and
|
||||
not path.regexpMatch(".*\\.(test|spec|test-utils|test-harness|e2e-harness)\\.ts$") and
|
||||
not path.regexpMatch(".*/test-support/.*") and
|
||||
not path.regexpMatch("^extensions/diffs/assets/.*")
|
||||
)
|
||||
}
|
||||
|
||||
Expr rawSocketClientCall() {
|
||||
exists(API::CallNode call, string moduleName, string memberName |
|
||||
rawModule(moduleName) and
|
||||
rawConnectMember(memberName) and
|
||||
call = API::moduleImport(moduleName).getMember(memberName).getACall() and
|
||||
result = call.asExpr()
|
||||
)
|
||||
or
|
||||
exists(string moduleName |
|
||||
netModule(moduleName) and
|
||||
result =
|
||||
DataFlow::moduleMember(moduleName, "Socket")
|
||||
.getAnInstantiation()
|
||||
.getAMethodCall("connect")
|
||||
.asExpr()
|
||||
)
|
||||
}
|
||||
|
||||
predicate allowedOwnerScope(Expr call, string path, string functionName) {
|
||||
exists(Function owner |
|
||||
call.getFile().getRelativePath() = path and
|
||||
owner.getFile() = call.getFile() and
|
||||
owner.getName() = functionName and
|
||||
call.getParent*() = owner.getBody()
|
||||
)
|
||||
}
|
||||
|
||||
predicate allowedRawSocketClientCall(Expr call) {
|
||||
allowedOwnerScope(call, "src/cli/gateway-cli/run-loop.ts", "waitForGatewayPortReady")
|
||||
or
|
||||
allowedOwnerScope(call, "src/infra/ssh-tunnel.ts", "canConnectLocal")
|
||||
or
|
||||
allowedOwnerScope(call, "src/infra/gateway-lock.ts", "checkPortFree")
|
||||
or
|
||||
allowedOwnerScope(call, "src/infra/jsonl-socket.ts", "requestJsonlSocket")
|
||||
or
|
||||
allowedOwnerScope(call, "src/infra/net/http-connect-tunnel.ts", "connectToProxy")
|
||||
or
|
||||
allowedOwnerScope(call, "src/infra/net/http-connect-tunnel.ts", "startTargetTls")
|
||||
or
|
||||
allowedOwnerScope(call, "src/infra/push-apns-http2.ts", "openProxiedApnsHttp2Session")
|
||||
or
|
||||
allowedOwnerScope(call, "src/infra/push-apns-http2.ts", "connectApnsHttp2Session")
|
||||
or
|
||||
allowedOwnerScope(call, "src/proxy-capture/proxy-server.ts", "startDebugProxyServer")
|
||||
or
|
||||
allowedOwnerScope(call, "extensions/irc/src/client.ts", "connectIrcClient")
|
||||
or
|
||||
allowedOwnerScope(call, "extensions/qa-lab/src/lab-server-capture.ts", "probeTcpReachability")
|
||||
or
|
||||
allowedOwnerScope(call, "extensions/qa-lab/src/lab-server-ui.ts", "proxyUpgradeRequest")
|
||||
}
|
||||
|
||||
from Expr call
|
||||
where
|
||||
rawSocketClientCall() = call and
|
||||
relevantSourceFile(call.getFile()) and
|
||||
not allowedRawSocketClientCall(call)
|
||||
select call,
|
||||
"Classify raw net/tls/http2 client egress as managed/proxied, local-only, diagnostic guarded, or documented unsupported before adding this callsite."
|
||||
5
.github/labeler.yml
vendored
5
.github/labeler.yml
vendored
@@ -1,8 +1,3 @@
|
||||
"channel: bluebubbles":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "extensions/bluebubbles/**"
|
||||
- "docs/channels/bluebubbles.md"
|
||||
"plugin: azure-speech":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
|
||||
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -854,7 +854,7 @@ jobs:
|
||||
name: ${{ matrix.checkName }}
|
||||
needs: [preflight]
|
||||
if: needs.preflight.outputs.run_checks_fast == 'true'
|
||||
runs-on: ${{ github.repository == 'openclaw/openclaw' && needs.preflight.outputs.runner_4vcpu_ubuntu || 'ubuntu-24.04' }}
|
||||
runs-on: ${{ github.repository == 'openclaw/openclaw' && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-24.04' }}
|
||||
timeout-minutes: 60
|
||||
strategy:
|
||||
fail-fast: false
|
||||
|
||||
75
.github/workflows/codeql-critical-quality.yml
vendored
75
.github/workflows/codeql-critical-quality.yml
vendored
@@ -21,17 +21,21 @@ on:
|
||||
- plugin-sdk-package-contract
|
||||
- plugin-sdk-reply-runtime
|
||||
- provider-runtime-boundary
|
||||
- network-runtime-boundary
|
||||
- session-diagnostics-boundary
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened, ready_for_review]
|
||||
paths:
|
||||
- ".github/codeql/**"
|
||||
- ".github/workflows/codeql-critical-quality.yml"
|
||||
- "extensions/*.ts"
|
||||
- "extensions/**/*.ts"
|
||||
- "packages/plugin-package-contract/**"
|
||||
- "packages/plugin-sdk/**"
|
||||
- "packages/memory-host-sdk/**"
|
||||
- "src/*.ts"
|
||||
- "src/**/*.ts"
|
||||
- "src/config/**"
|
||||
- "extensions/bluebubbles/src/**"
|
||||
- "extensions/discord/src/**"
|
||||
- "extensions/feishu/src/**"
|
||||
- "extensions/googlechat/src/**"
|
||||
@@ -159,6 +163,7 @@ jobs:
|
||||
plugin_sdk_package: ${{ steps.detect.outputs.plugin_sdk_package }}
|
||||
plugin_sdk_reply: ${{ steps.detect.outputs.plugin_sdk_reply }}
|
||||
provider: ${{ steps.detect.outputs.provider }}
|
||||
network_runtime: ${{ steps.detect.outputs.network_runtime }}
|
||||
session_diagnostics: ${{ steps.detect.outputs.session_diagnostics }}
|
||||
steps:
|
||||
- name: Detect PR shard paths
|
||||
@@ -182,6 +187,7 @@ jobs:
|
||||
plugin_sdk_package=false
|
||||
plugin_sdk_reply=false
|
||||
provider=false
|
||||
network_runtime=false
|
||||
session_diagnostics=false
|
||||
|
||||
if [[ "${EVENT_NAME}" != "pull_request" ]]; then
|
||||
@@ -196,6 +202,7 @@ jobs:
|
||||
plugin_sdk_package=true
|
||||
plugin_sdk_reply=true
|
||||
provider=true
|
||||
network_runtime=true
|
||||
session_diagnostics=true
|
||||
else
|
||||
while IFS= read -r file; do
|
||||
@@ -212,6 +219,7 @@ jobs:
|
||||
plugin_sdk_package=true
|
||||
plugin_sdk_reply=true
|
||||
provider=true
|
||||
network_runtime=true
|
||||
session_diagnostics=true
|
||||
;;
|
||||
src/acp/control-plane/*|src/agents/cli-runner/*|src/agents/command/*|src/agents/pi-embedded-runner/*|src/agents/tools/*|src/agents/*completion*.ts|src/agents/*transport*.ts|src/agents/model-*.ts|src/agents/openclaw-tools*.ts|src/agents/provider-*.ts|src/agents/session*.ts|src/agents/tool-call*.ts|src/auto-reply/reply/agent-runner*.ts|src/auto-reply/reply/commands*.ts|src/auto-reply/reply/directive-handling*.ts|src/auto-reply/reply/dispatch-*.ts|src/auto-reply/reply/get-reply-run*.ts|src/auto-reply/reply/provider-dispatcher*.ts|src/auto-reply/reply/queue*.ts|src/auto-reply/reply/reply-run-registry*.ts|src/auto-reply/reply/session*.ts)
|
||||
@@ -220,7 +228,7 @@ jobs:
|
||||
src/auto-reply/reply/post-compaction-context.ts|src/auto-reply/reply/queue/*|src/auto-reply/reply/startup-context.ts|src/commands/doctor-session-*.ts|src/commands/session-store-targets.ts|src/commands/sessions*.ts|src/infra/diagnostic-*.ts|src/infra/diagnostics-timeline.ts|src/infra/session-delivery-queue*.ts|src/logging/diagnostic*.ts)
|
||||
session_diagnostics=true
|
||||
;;
|
||||
extensions/bluebubbles/src/*|extensions/discord/src/*|extensions/feishu/src/*|extensions/googlechat/src/*|extensions/imessage/src/*|extensions/irc/src/*|extensions/line/src/*|extensions/matrix/src/*|extensions/mattermost/src/*|extensions/msteams/src/*|extensions/nextcloud-talk/src/*|extensions/nostr/src/*|extensions/qa-channel/src/*|extensions/qqbot/src/*|extensions/signal/src/*|extensions/slack/src/*|extensions/synology-chat/src/*|extensions/telegram/src/*|extensions/tlon/src/*|extensions/twitch/src/*|extensions/whatsapp/src/*|extensions/zalo/src/*|extensions/zalouser/src/*|src/channels/*)
|
||||
extensions/discord/src/*|extensions/feishu/src/*|extensions/googlechat/src/*|extensions/imessage/src/*|extensions/irc/src/*|extensions/line/src/*|extensions/matrix/src/*|extensions/mattermost/src/*|extensions/msteams/src/*|extensions/nextcloud-talk/src/*|extensions/nostr/src/*|extensions/qa-channel/src/*|extensions/qqbot/src/*|extensions/signal/src/*|extensions/slack/src/*|extensions/synology-chat/src/*|extensions/telegram/src/*|extensions/tlon/src/*|extensions/twitch/src/*|extensions/whatsapp/src/*|extensions/zalo/src/*|extensions/zalouser/src/*|src/channels/*)
|
||||
channel=true
|
||||
;;
|
||||
src/config/*)
|
||||
@@ -281,6 +289,12 @@ jobs:
|
||||
plugin_sdk_package=true
|
||||
;;
|
||||
esac
|
||||
|
||||
case "${file}" in
|
||||
src/*.ts|src/**/*.ts|extensions/*.ts|extensions/**/*.ts)
|
||||
network_runtime=true
|
||||
;;
|
||||
esac
|
||||
done < <(gh api --paginate "repos/${REPOSITORY}/pulls/${PR_NUMBER}/files" --jq '.[].filename')
|
||||
fi
|
||||
|
||||
@@ -296,6 +310,7 @@ jobs:
|
||||
echo "plugin_sdk_package=${plugin_sdk_package}"
|
||||
echo "plugin_sdk_reply=${plugin_sdk_reply}"
|
||||
echo "provider=${provider}"
|
||||
echo "network_runtime=${network_runtime}"
|
||||
echo "session_diagnostics=${session_diagnostics}"
|
||||
} >> "${GITHUB_OUTPUT}"
|
||||
|
||||
@@ -391,6 +406,62 @@ jobs:
|
||||
with:
|
||||
category: "/codeql-critical-quality/channel-runtime-boundary"
|
||||
|
||||
network-runtime-boundary:
|
||||
name: Critical Quality (network-runtime-boundary)
|
||||
needs: quality-shards
|
||||
if: ${{ needs.quality-shards.outputs.network_runtime == 'true' && (github.event_name != 'pull_request' || !github.event.pull_request.draft) && (github.event_name == 'pull_request' || github.event_name != 'workflow_dispatch' || inputs.profile == 'all' || inputs.profile == 'network-runtime-boundary') }}
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
timeout-minutes: 25
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
submodules: false
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4
|
||||
with:
|
||||
languages: javascript-typescript
|
||||
config-file: ./.github/codeql/codeql-network-runtime-boundary-critical-quality.yml
|
||||
|
||||
- name: Analyze
|
||||
id: analyze
|
||||
uses: github/codeql-action/analyze@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4
|
||||
with:
|
||||
output: sarif-results
|
||||
category: "/codeql-critical-quality/network-runtime-boundary"
|
||||
|
||||
- name: Fail on network runtime boundary findings
|
||||
env:
|
||||
SARIF_OUTPUT: sarif-results
|
||||
run: |
|
||||
set -euo pipefail
|
||||
shopt -s nullglob
|
||||
|
||||
files=("$SARIF_OUTPUT"/*.sarif)
|
||||
if [ "${#files[@]}" -eq 0 ]; then
|
||||
echo "No SARIF files found in $SARIF_OUTPUT" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
findings="$(jq -s '[.[].runs[]?.results[]?] | length' "${files[@]}")"
|
||||
if [ "$findings" = "0" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Found ${findings} network runtime boundary finding(s):" >&2
|
||||
jq -r '
|
||||
.runs[]?.results[]?
|
||||
| .locations[0].physicalLocation as $location
|
||||
| "- "
|
||||
+ ($location.artifactLocation.uri // "unknown")
|
||||
+ ":"
|
||||
+ (($location.region.startLine // 0) | tostring)
|
||||
+ " "
|
||||
+ (.message.text // .ruleId)
|
||||
' "${files[@]}" >&2
|
||||
exit 1
|
||||
|
||||
agent-runtime-boundary:
|
||||
name: Critical Quality (agent-runtime-boundary)
|
||||
needs: quality-shards
|
||||
|
||||
96
.github/workflows/openclaw-release-publish.yml
vendored
96
.github/workflows/openclaw-release-publish.yml
vendored
@@ -37,10 +37,15 @@ on:
|
||||
required: true
|
||||
default: true
|
||||
type: boolean
|
||||
wait_for_clawhub:
|
||||
description: Wait for ClawHub plugin publish before marking this workflow complete
|
||||
required: true
|
||||
default: false
|
||||
type: boolean
|
||||
|
||||
permissions:
|
||||
actions: write
|
||||
contents: read
|
||||
contents: write
|
||||
|
||||
concurrency:
|
||||
group: openclaw-release-publish-${{ inputs.tag }}
|
||||
@@ -166,6 +171,7 @@ jobs:
|
||||
PLUGIN_PUBLISH_SCOPE: ${{ inputs.plugin_publish_scope }}
|
||||
PLUGINS: ${{ inputs.plugins }}
|
||||
PUBLISH_OPENCLAW_NPM: ${{ inputs.publish_openclaw_npm && 'true' || 'false' }}
|
||||
WAIT_FOR_CLAWHUB: ${{ inputs.wait_for_clawhub && 'true' || 'false' }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
@@ -203,19 +209,31 @@ jobs:
|
||||
fi
|
||||
|
||||
echo "Dispatched ${workflow}: https://github.com/${GITHUB_REPOSITORY}/actions/runs/${run_id}" >&2
|
||||
{
|
||||
echo "- ${workflow}: dispatched (https://github.com/${GITHUB_REPOSITORY}/actions/runs/${run_id})"
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
printf '%s\n' "${run_id}"
|
||||
}
|
||||
|
||||
wait_for_run() {
|
||||
local workflow="$1"
|
||||
local run_id="$2"
|
||||
local status conclusion url
|
||||
local status conclusion url updated_at last_state
|
||||
|
||||
last_state=""
|
||||
while true; do
|
||||
status="$(gh run view --repo "$GITHUB_REPOSITORY" "$run_id" --json status --jq '.status')"
|
||||
run_json="$(gh run view --repo "$GITHUB_REPOSITORY" "$run_id" --json status,url,updatedAt)"
|
||||
status="$(printf '%s' "$run_json" | jq -r '.status')"
|
||||
if [[ "$status" == "completed" ]]; then
|
||||
break
|
||||
fi
|
||||
url="$(printf '%s' "$run_json" | jq -r '.url')"
|
||||
updated_at="$(printf '%s' "$run_json" | jq -r '.updatedAt')"
|
||||
state="${status}:${updated_at}"
|
||||
if [[ "$state" != "$last_state" ]]; then
|
||||
echo "${workflow} still ${status} (updated ${updated_at}): ${url}"
|
||||
last_state="$state"
|
||||
fi
|
||||
sleep 30
|
||||
done
|
||||
|
||||
@@ -245,6 +263,53 @@ jobs:
|
||||
wait_run_pid="$!"
|
||||
}
|
||||
|
||||
create_or_update_github_release() {
|
||||
local release_version notes_version title notes_file changelog_file latest_arg prerelease_args
|
||||
release_version="${RELEASE_TAG#v}"
|
||||
notes_version="${release_version}"
|
||||
if [[ "${notes_version}" =~ ^([0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*)-(alpha|beta)\.[1-9][0-9]*$ ]]; then
|
||||
notes_version="${BASH_REMATCH[1]}"
|
||||
fi
|
||||
title="openclaw ${release_version}"
|
||||
changelog_file="${RUNNER_TEMP}/CHANGELOG.md"
|
||||
notes_file="${RUNNER_TEMP}/release-notes.md"
|
||||
|
||||
gh api --repo "$GITHUB_REPOSITORY" "repos/${GITHUB_REPOSITORY}/contents/CHANGELOG.md?ref=${TARGET_SHA}" \
|
||||
--jq '.content' | base64 --decode > "${changelog_file}"
|
||||
awk -v version="${notes_version}" '
|
||||
$0 == "## " version { in_section = 1; next }
|
||||
/^## / && in_section { exit }
|
||||
in_section { print }
|
||||
' "${changelog_file}" > "${notes_file}"
|
||||
if [[ ! -s "${notes_file}" ]]; then
|
||||
echo "CHANGELOG.md does not contain release notes for ${notes_version}." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
prerelease_args=()
|
||||
latest_arg="--latest=false"
|
||||
if [[ "${RELEASE_TAG}" == *"-alpha."* || "${RELEASE_TAG}" == *"-beta."* ]]; then
|
||||
prerelease_args=(--prerelease)
|
||||
elif [[ "${RELEASE_NPM_DIST_TAG}" == "latest" ]]; then
|
||||
latest_arg="--latest"
|
||||
fi
|
||||
|
||||
if gh release view "${RELEASE_TAG}" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
|
||||
gh release edit "${RELEASE_TAG}" --repo "$GITHUB_REPOSITORY" \
|
||||
--title "${title}" \
|
||||
--notes-file "${notes_file}" \
|
||||
"${prerelease_args[@]}"
|
||||
else
|
||||
gh release create "${RELEASE_TAG}" --repo "$GITHUB_REPOSITORY" \
|
||||
--verify-tag \
|
||||
--title "${title}" \
|
||||
--notes-file "${notes_file}" \
|
||||
"${prerelease_args[@]}" \
|
||||
"${latest_arg}"
|
||||
fi
|
||||
echo "- GitHub release: https://github.com/${GITHUB_REPOSITORY}/releases/tag/${RELEASE_TAG}" >> "$GITHUB_STEP_SUMMARY"
|
||||
}
|
||||
|
||||
{
|
||||
echo "### Publish sequence"
|
||||
echo
|
||||
@@ -257,6 +322,11 @@ jobs:
|
||||
else
|
||||
echo "- OpenClaw npm publish: skipped by input"
|
||||
fi
|
||||
if [[ "${WAIT_FOR_CLAWHUB}" == "true" ]]; then
|
||||
echo "- Workflow completion waits for ClawHub"
|
||||
else
|
||||
echo "- Workflow completion does not wait for ClawHub; monitor the dispatched ClawHub run separately"
|
||||
fi
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
npm_args=(-f publish_scope="${PLUGIN_PUBLISH_SCOPE}" -f ref="${TARGET_SHA}")
|
||||
@@ -286,10 +356,16 @@ jobs:
|
||||
echo "- OpenClaw npm publish: skipped by input" >> "$GITHUB_STEP_SUMMARY"
|
||||
fi
|
||||
|
||||
clawhub_result="$RUNNER_TEMP/clawhub-result.txt"
|
||||
wait_run_pid=""
|
||||
wait_for_run_background plugin-clawhub-release.yml "${plugin_clawhub_run_id}" "${clawhub_result}"
|
||||
clawhub_pid="${wait_run_pid}"
|
||||
clawhub_result=""
|
||||
clawhub_pid=""
|
||||
if [[ "${WAIT_FOR_CLAWHUB}" == "true" ]]; then
|
||||
clawhub_result="$RUNNER_TEMP/clawhub-result.txt"
|
||||
wait_run_pid=""
|
||||
wait_for_run_background plugin-clawhub-release.yml "${plugin_clawhub_run_id}" "${clawhub_result}"
|
||||
clawhub_pid="${wait_run_pid}"
|
||||
else
|
||||
echo "- plugin-clawhub-release.yml: not awaited (${plugin_clawhub_run_id})" >> "$GITHUB_STEP_SUMMARY"
|
||||
fi
|
||||
|
||||
openclaw_result=""
|
||||
openclaw_pid=""
|
||||
@@ -301,7 +377,7 @@ jobs:
|
||||
fi
|
||||
|
||||
failed=0
|
||||
if ! wait "${clawhub_pid}"; then
|
||||
if [[ -n "${clawhub_pid}" ]] && ! wait "${clawhub_pid}"; then
|
||||
failed=1
|
||||
fi
|
||||
if [[ -n "${openclaw_pid}" ]] && ! wait "${openclaw_pid}"; then
|
||||
@@ -316,3 +392,7 @@ jobs:
|
||||
if [[ "${failed}" != "0" ]]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -n "${openclaw_npm_run_id}" ]]; then
|
||||
create_or_update_github_release
|
||||
fi
|
||||
|
||||
@@ -37,6 +37,11 @@ Telegraph style. Root rules only. Read scoped `AGENTS.md` before subtree work.
|
||||
- New seams: backwards-compatible, documented, versioned. Third-party plugins exist.
|
||||
- Channels: `src/channels/**` is implementation; plugin authors get SDK seams.
|
||||
- Providers: core owns generic loop; provider plugins own auth/catalog/runtime hooks.
|
||||
- Request-time runtime resolution: when a path already knows the provider id, model ref, channel id, outbound target, capability family, or attachment class, carry that as a prepared runtime fact instead of rediscovering it later.
|
||||
- Prepared runtime facts should be small typed values produced once near startup, reply dispatch, model selection, tool planning, or channel resolution, then passed through context to consumers. Prefer `AgentRuntimePlan`, `ProviderRuntimePluginHandle`, scoped model/catalog helpers, active/runtime registries, manifest/public-artifact lookups, single-provider resolvers, and lazy registry construction.
|
||||
- Avoid broad request-time rediscovery: hot reply/tool/outbound/media paths should not call broad plugin/provider/channel/capability loaders such as `loadOpenClawPlugins`, `resolveProviderPluginsForHooks`, `resolvePluginCapabilityProviders`, `resolvePluginDiscoveryProvidersRuntime`, `getChannelPlugin`, or broad model/tool/media registry builders just to answer a question the caller already knows. Do not build multimodal/provider registries for document-only or otherwise non-participating paths.
|
||||
- Compatibility fallbacks are allowed only for startup/setup/admin/standalone/legacy callers that genuinely lack prepared facts. Keep them explicit, tested, and outside migrated hot reply/tool/outbound paths.
|
||||
- Do not fix repeated request-time discovery by adding scattered cache layers. Move the canonical fact earlier, reuse the existing prepared-runtime object, and delete duplicate lookup branches when the last migrated caller stops needing them.
|
||||
- Gateway protocol changes: additive first; incompatible needs versioning/docs/client follow-through.
|
||||
- Config contract: exported types, schema/help, metadata, baselines, docs aligned. Retired public keys stay retired; compat in raw migration/doctor.
|
||||
- Direction: manifest-first control plane; targeted runtime loaders; no hidden contract bypasses; broad mutable registries transitional.
|
||||
|
||||
47
CHANGELOG.md
47
CHANGELOG.md
@@ -6,15 +6,22 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
### Changes
|
||||
|
||||
- Telegram: preserve the channel-specific 10-option poll cap in the unified outbound adapter so over-limit polls are rejected before send. (#78762) Thanks @obviyus.
|
||||
- Runtime/install: raise the supported Node 22 floor to `22.16+` so native SQLite query handling can rely on the `node:sqlite` statement metadata API while continuing to recommend Node 24. (#78921)
|
||||
- Discord/voice: include a bounded one-line STT transcript preview in verbose voice logs so live voice debugging shows what speakers said before the agent reply.
|
||||
- Discord/voice: stream ElevenLabs TTS directly into Discord playback and send ElevenLabs latency optimization as the documented query parameter so spoken replies can start sooner.
|
||||
- Discord/voice: keep TTS playback running when another user starts speaking, ignore new capture during playback to avoid feedback loops, and downgrade expected receive-stream aborts to verbose diagnostics.
|
||||
- Telegram: treat successful same-chat `message` tool outbound sends during an inbound telegram turn as delivered when deciding whether to emit the rewritten silent reply fallback (#78685). Thanks @neeravmakwana.
|
||||
- Gateway/tasks: reconcile stale CLI run-context tasks whose live run context disappeared even when a child session row remains, and apply the default bounded reload deferral timeout to channel hot reloads so stale task records cannot block Discord/Slack/Telegram reloads forever.
|
||||
- Gateway/sessions: keep session-store index writes atomic while skipping durable fsync inside the writer lock, reducing cron and channel-turn starvation on slow filesystems and addressing the session-store strand of #73655. Thanks @mmartoccia.
|
||||
- Discord/voice: make `openclaw channels capabilities --channel discord --target channel:<id>` and `channels status --probe` audit voice-channel permissions, including auto-join targets, so missing Connect/Speak/Read Message History permissions show up before `/vc join`.
|
||||
- Docs/iMessage: deprecate BlueBubbles for new OpenClaw setups, document the upstream server-release rationale, and point new iMessage deployments toward the native `imsg` path while keeping BlueBubbles as a supported legacy fallback.
|
||||
- Channels CLI: make `openclaw channels list` channel-only — drop the `Auth providers (OAuth + API keys)` block (use `openclaw models auth list`), drop the per-provider usage/quota fetch and the `--no-usage` flag (use `openclaw status` or `openclaw models list`), add `--all` to surface bundled-unconfigured, catalog-not-installed, and catalog-installed-but-unconfigured channels, and render explicit `installed` / `configured` / `enabled` tags per row plus an `origin` + `installed` field in JSON. Fixes WeCom-class catalog channels disappearing from `--all` when installed on disk but not yet configured. (#78456) Thanks @sliverp.
|
||||
- CLI/cron: add computed `status` field to `cron list --json` and `cron show <id> --json` output, mirroring the human-readable status column (disabled/running/ok/error/skipped/idle) so external tooling can determine job state without re-deriving it from raw state fields. (#78701) Thanks @aweiker.
|
||||
- Discord/voice: make voice capture less choppy by extending the default post-speech silence grace to 2.5s, add `voice.captureSilenceGraceMs` for noisy Discord sessions, and tighten the spoken-output prompt around live STT fragments. Thanks @vincentkoc.
|
||||
- Discord/streaming: default Discord replies to progress draft previews so tool/work activity appears in one edited Discord message unless `channels.discord.streaming.mode` is set to `off`.
|
||||
- OpenAI: support `openai/chat-latest` as an explicit direct API-key model override for trying the moving ChatGPT Instant API alias without changing the stable default model.
|
||||
- Plugins/install: add `npm-pack:<path.tgz>` installs so local npm pack artifacts run through the same managed npm-root install, lockfile verification, dependency scan, and install-record path as registry npm plugins.
|
||||
- Channels/plugins: show configured official external channels as missing-plugin status rows and send errors with exact install/doctor repair commands after raw package-manager upgrades leave Feishu or WhatsApp uninstalled. Fixes #78702 and #78593. Thanks @MarkMa84 and @mkupiainen.
|
||||
- Codex app-server: disarm the short post-tool completion watchdog after current-turn activity, expose `appServer.turnCompletionIdleTimeoutMs`, and include raw assistant item context in idle-timeout diagnostics so status-only post-tool stalls stop failing as idle. Fixes #77984. Thanks @roseware-dev and @rubencu.
|
||||
- Plugin skills/Windows: publish plugin-provided skill directories as junctions on Windows so standard users without Developer Mode can register plugin skills without symlink EPERM failures. Fixes #77958. (#77971) Thanks @hclsys and @jarro.
|
||||
- MS Teams: surface blocked Bot Framework egress by logging JWKS fetch network failures and adding a Bot Connector send hint for transport-level reply failures. Fixes #77674. (#78081) Thanks @Beandon13.
|
||||
@@ -25,6 +32,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Sessions CLI: show the selected agent runtime in the `openclaw sessions` table so terminal output matches the runtime visibility already present in JSON/status surfaces. Thanks @vincentkoc.
|
||||
- ACPX/Codex: preserve trusted Codex project declarations when launching isolated Codex ACP sessions, avoiding interactive trust prompts in headless runs. Thanks @Stedyclaw.
|
||||
- ACPX/Codex: reap stale OpenClaw-owned ACPX/Codex ACP process trees on startup and after ACP session close, preventing orphaned harness processes from slowing the Gateway. Thanks @91wan.
|
||||
- ACP bridge: implement stable session list, resume, and close handlers so ACP clients can page Gateway sessions, rebind existing sessions without replay, and close bridge sessions cleanly. Thanks @amknight.
|
||||
- ACP sessions: allow parent agents to inspect and message their own spawned cross-agent ACP sessions without enabling broad agent-to-agent visibility. Thanks @barronlroth.
|
||||
- Talk/voice: unify realtime relay, transcription relay, managed-room handoff, Voice Call, Google Meet, VoiceClaw, and native clients around a shared Talk session controller and add the Gateway-managed `talk.session.*` RPC surface.
|
||||
- Diagnostics/Talk: export bounded Talk lifecycle/audio metrics and session recovery metrics through OpenTelemetry and Prometheus without exposing transcripts, audio payloads, room ids, turn ids, or session ids.
|
||||
@@ -49,11 +57,11 @@ Docs: https://docs.openclaw.ai
|
||||
- Slack/streaming: add `streaming.progress.render: "rich"` for Block Kit progress drafts backed by structured progress line data.
|
||||
- Slack/streaming: keep the newest rich progress lines when Block Kit limits trim long progress drafts. Thanks @vincentkoc.
|
||||
- Slack/performance: reduce message preparation, stream recipient lookup, and thread-context allocation overhead on Slack reply hot paths. Thanks @vincentkoc.
|
||||
- Agents/performance: trim OpenAI WebSocket stream queues, tool-call id queues, and agent/channel helper assembly hot paths while preserving event ordering, tool id ordering, and channel reply payloads. Thanks @vincentkoc.
|
||||
- Channels/streaming: cap progress-draft tool lines by default so edited progress boxes avoid jumpy reflow from long wrapped lines.
|
||||
- Control UI/chat: add an agent-first filter to the chat session picker, keep chat controls/composer responsive across phone/tablet/desktop widths, keep desktop chat controls on one row, avoid duplicate avatar refreshes during initial chat load, and hide that row while scrolling down the transcript. Thanks @BunsDev.
|
||||
- Control UI/chat: collapse consecutive duplicate text messages into one bubble with a count so repeated text-only messages stay compact without hiding nearby context.
|
||||
- Control UI/chat and Sessions: label inherited thinking defaults separately from explicit overrides while preserving provider-supplied option labels. Fixes #77581. Thanks @BunsDev and @Beandon13.
|
||||
- Agents/runtime: add prepared runtime foundation contracts for carrying provider, model, tool, TTS, and outbound runtime facts through later reply-path migrations. Thanks @mcaxtr.
|
||||
- Agents/subagents: preserve every grouped child result when direct completion fallback has to bypass the requester-agent announce turn. Thanks @vincentkoc.
|
||||
- TTS/telephony: honor provider voice/model overrides in telephony synthesis providers so Google Meet agent speech logs match the backend that actually produced the audio. Thanks @vincentkoc.
|
||||
- Voice Call/realtime: bound the paced Twilio audio queue and close overloaded realtime streams before provider audio can pile up behind the websocket backpressure guard. Thanks @vincentkoc.
|
||||
@@ -90,6 +98,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Plugins/update: repair stale managed npm-root `openclaw` peer packages before plugin installs, so beta-channel official plugin updates are not downgraded by old core package-lock state. Thanks @vincentkoc.
|
||||
- Plugins/install: run managed npm-root install, rollback, repair, and uninstall mutations with legacy peer resolution so removing one plugin cannot rehydrate a stale registry `openclaw` package into the shared root. Thanks @vincentkoc.
|
||||
- Plugins/install: reassert managed npm plugin `openclaw` peer links after shared-root npm installs, updates, and uninstalls, so mutating one plugin does not leave previously installed SDK-using plugins unable to resolve `openclaw/plugin-sdk/*`.
|
||||
- Plugins/install: use the same absolute POSIX npm lifecycle shell for managed plugin install, rollback, repair, and uninstall npm operations as staged package updates, preventing restricted PATH shells from breaking cleanup. Thanks @vincentkoc.
|
||||
- Plugins/update: make package upgrades swap pnpm/npm-prefix installs cleanly, keep legacy plugin install runtime chunks working, and on the beta channel fall back default-line npm plugins to default/latest when plugin beta releases are missing or fail install validation. Thanks @vincentkoc and @joshavant.
|
||||
- Plugins/active-memory: skip session-store channel entries that contain `:` when resolving the recall subagent's channel, so QQ c2c agent IDs (e.g. `c2c:10D4F7C2…`) and other scoped conversation IDs do not reach bundled-plugin `dirName` validation and crash the recall run. The same guard already applied to explicit `channelId` params (#76704); this extends it to store-derived channels. (#77396) Thanks @hclsys.
|
||||
- Sandbox/Windows: accept drive-absolute Docker bind sources while keeping sandbox blocked-path and allowed-root policy comparisons Windows-case-insensitive. (#42174) Thanks @6607changchun.
|
||||
@@ -140,41 +149,68 @@ Docs: https://docs.openclaw.ai
|
||||
- Config/Nix: keep startup-derived plugin enablement, gateway auth tokens, control UI origins, and owner-display secrets runtime-only instead of rewriting `openclaw.json`; in Nix mode, config writers, mutating `openclaw update`, plugin lifecycle mutators, and doctor repair/token-generation now refuse with agent-first nix-openclaw guidance. (#78047) Thanks @joshp123.
|
||||
- Agents/context engine: invalidate cached assembled context views when source history shrinks or assembly fails, preventing stale pre-reset history from being reused. Fixes #77968. (#78163) Thanks @brokemac79 and @ChrisBot2026.
|
||||
|
||||
### Breaking
|
||||
|
||||
- Channels/iMessage: remove the bundled BlueBubbles channel surface and deprecate BlueBubbles-backed iMessage setup in OpenClaw. Existing `channels.bluebubbles` configs must migrate to `channels.imessage` using `imsg` on a signed-in Mac or an SSH wrapper, and non-macOS default `imsg` configs now report remote-Mac wrapper guidance.
|
||||
|
||||
### Fixes
|
||||
|
||||
- TUI/local runs: keep stable runtime plugin aliases present when legacy compatibility wrappers already exist in dist, so sending a message no longer fails with a missing `runtime-plugins.runtime.js` module.
|
||||
- Providers: preserve non-OK `text/event-stream` response bodies so provider HTTP errors keep their JSON detail instead of collapsing to generic streaming failures. Fixes #78180.
|
||||
- Chat commands: make `/model default` reset the session model override instead of treating it as a literal model name. Fixes #78182.
|
||||
- Cron: make rejected `payload.model` errors show the configured `agents.defaults.models` allowlist instead of echoing the rejected model twice. Fixes #79058.
|
||||
- Agents/subagents: retry parent wake announces when the announce-summary model run fails with fallback cooldown exhaustion instead of dropping the wake on the first transient provider overload. Refs #78581.
|
||||
- Providers/network: honor IPv4 CIDR and octet-wildcard `NO_PROXY` entries such as `100.64.0.0/10` and `100.64.*` before enabling trusted env-proxy mode for model-provider requests. Fixes #79030.
|
||||
- Docs/Docker: document a local Compose override for Docker Desktop DNS failures in the shared-network `openclaw-cli` sidecar, keeping the default compose setup hardened while unblocking `openclaw plugins install` when users opt in. Fixes #79018. Thanks @Jason-Vaughan.
|
||||
- Installer: when npm installs `openclaw` outside the parent shell PATH, print follow-up commands with the resolved binary path instead of telling users to run `openclaw` from a shell that will report `command not found`. Fixes #72382. Thanks @jbob762.
|
||||
- Compute plugin callback authorization dynamically [AI]. (#78866) Thanks @pgondhi987.
|
||||
- fix(active-memory): require admin scope for global toggles [AI]. (#78863) Thanks @pgondhi987.
|
||||
- Honor owner enforcement for native commands [AI]. (#78864) Thanks @pgondhi987.
|
||||
- Tavily: resolve dedicated `tavily_search` and `tavily_extract` tool credentials from the active runtime config snapshot, so `exec` SecretRef-backed API keys do not reach the tools unresolved. (#78610) Thanks @VACInc.
|
||||
- Gateway/sessions: clear cached skills snapshots during `/new` and `sessions.reset` so long-lived channel sessions rebuild the visible skill list after skills change. (#78873) Thanks @Evizero.
|
||||
- fix(auto-reply): gate inline skill tool dispatch [AI]. (#78517) Thanks @pgondhi987.
|
||||
- Canvas plugin: keep legacy root `canvasHost` configs valid until `openclaw doctor --fix` migrates them into `plugins.entries.canvas.config.host`, move Canvas/A2UI clients to gateway protocol v4 plugin surfaces, and refresh the generated A2UI bundle hash so normal builds stay clean.
|
||||
- feishu: honor config write policy for dynamic agents [AI]. (#78520) Thanks @pgondhi987.
|
||||
- fix(skill-workshop): honor pending approval for tool suggestions [AI]. (#78516) Thanks @pgondhi987.
|
||||
- BytePlus: mark Kimi K2.5 and Kimi K2 Thinking catalog entries as reasoning-capable, raise their output cap to 32k tokens, and fill Kimi cache-read pricing. Fixes #54149.
|
||||
- Control UI/chat: wait for an in-flight model dropdown patch before sending the next chat message, so immediate sends use the selected session model instead of racing the previous override. Fixes #54240.
|
||||
- Native chat: decode gateway-provided thinking metadata for the iOS/macOS picker so provider-specific levels such as `adaptive`, `xhigh`, and `max` appear without leaking unsupported default-model options. Thanks @BunsDev.
|
||||
- Agents/compaction: cap summarization output reserve tokens to the selected model's `maxTokens` so 1M-context Anthropic compactions do not request more output than the API permits. Fixes #54383.
|
||||
- Agents/tools: fail `exec host=node` before `system.run` when the selected node is known to be disconnected, with an actionable reconnect message instead of a raw node invoke failure. Thanks @BunsDev.
|
||||
- Agents/models: accept legacy `anthropic-cli/*` model refs as Claude CLI runtime refs instead of failing model resolution with `Unknown model`. Thanks @BunsDev.
|
||||
- Agents/tools: keep restrictive-profile tool-section warnings scoped to the configured sections whose tools are still missing from `alsoAllow`, so already re-allowed filesystem tools do not make exec-only fixes look broader than they are. Thanks @BunsDev.
|
||||
- Agents/tools: avoid warning messaging-only agents about inherited global `tools.exec` or `tools.fs` sections when the agent profile did not configure those tool sections itself. Thanks @BunsDev.
|
||||
- Codex dynamic tools: normalize runtime `toolsAllow` entries the same way as Pi tool policy, so aliases like `bash` and `apply-patch` still expose the intended OpenClaw tools. Thanks @BunsDev.
|
||||
- Memory/dreaming: read OpenAI-style `output_text` assistant parts from narrative subagent transcripts, so light-phase Dream Diary entries are not dropped as empty. Thanks @BunsDev.
|
||||
- OpenAI-compatible providers: honor `compat.supportsTools=false` by stripping tool payload fields before dispatch to chat-only endpoints. Fixes #74664.
|
||||
- OpenAI-compatible providers: apply model-declared unsupported tool-schema keyword stripping to native OpenAI transport payloads and mark Fireworks Kimi K2.5 as rejecting `not` schemas. Fixes #75467.
|
||||
- OpenAI-compatible gateway: sanitize images supplied through request content even when the prompt text contains no image file references, preventing oversized attachment payloads from bypassing the resize/drop pipeline. Fixes #59913.
|
||||
- Auth profiles: normalize inline API keys and tokens loaded from `auth-profiles.json` so masked or rich-text credential artifacts fail as auth errors instead of crashing HTTP header construction. Fixes #77624.
|
||||
- llm-task: resolve configured model aliases before embedded dispatch so `model="gemini-flash"` and other aliases route to the intended provider instead of the agent default. Fixes #54166.
|
||||
- Media generation: resolve slash-containing model-only overrides like `fal-ai/flux/dev` through registered provider model metadata so FAL image/video models do not get misparsed as provider `fal-ai`. Fixes #77444.
|
||||
- Commands/BTW: show the `/btw` missing-question usage placeholder with brackets so outbound channel sanitization keeps it visible. Fixes #62877. Thanks @RajvardhanPatil07.
|
||||
- CLI backends: keep versioned OAuth identity matches reusable when auth profile ids rotate, so Claude CLI sessions do not reset and lose continuity during same-account OAuth refresh/profile alias changes. Fixes #78541.
|
||||
- Model providers: normalize APNG sniffed PNG uploads, preserve Gemini 3 tool-call thought-signature replay with documented fallback signatures, accept legacy `__env__:VAR` custom-provider keys, and repair snake_case tool-call transcript sanitization. Fixes #51881, #48915, #77566, and #42858.
|
||||
- Telegram/models: parse provider ids containing dots in `/models` callback buttons so `hf.co` model lists render as inline keyboard buttons. Fixes #38745.
|
||||
- Auth profiles/Bedrock: accept persisted `type: "aws-sdk"` auth profiles so EC2/IMDS and shared AWS credential-chain Bedrock setups are not dropped as `invalid_type`. Fixes #69708.
|
||||
- Amazon Bedrock: refresh shared AWS profile/config file credentials before Bedrock model, discovery, and embedding requests so long-running Gateway processes pick up renewed profile credentials without restart. Fixes #77551.
|
||||
- Amazon Bedrock: treat named `aws-sdk` auth profiles as config routing metadata instead of stored credentials, and let `doctor --fix` move legacy markers out of `auth-profiles.json`. Fixes #69708.
|
||||
- Anthropic: reject uppercase provider-prefixed forward-compat model ids locally instead of sending malformed dynamic ids upstream. Fixes #73715.
|
||||
- OpenAI/embeddings: pass configured output dimensionality through single and batched embedding requests so memory embedding indexes can request smaller vectors. Fixes #55126.
|
||||
- CLI/infer: normalize HEIC/HEIF image files to JPEG before model-run requests, avoiding providers that reject Apple image container formats. Fixes #50081.
|
||||
- CLI/infer: fall back to macOS `sips` when optional image tooling cannot decode HEIC/HEIF input files before model-run requests. Refs #50081.
|
||||
- OpenRouter: keep the default `openrouter/auto` model ref canonical while preventing TUI and Control UI catalog pickers from displaying or submitting `openrouter/openrouter/auto`. Fixes #62655.
|
||||
- Status/Claude CLI: show `oauth (claude-cli)` for working Claude CLI OAuth runtime sessions instead of `unknown` when no local auth profile exists. Fixes #78632. Thanks @gorkem2020.
|
||||
- Memory search: preserve keyword-only hybrid FTS matches when vector scoring is unavailable or below the configured minimum score, so exact lexical hits are not dropped by weighted min-score filtering.
|
||||
- Exec approvals/node: let trusted backend node invokes complete no-device Control UI approvals after the original request connection changes, while keeping node, command, cwd, env, and allow-once replay bindings enforced. Fixes #78569. Thanks @naturedogdog.
|
||||
- Agents/subagents: keep background completion delivery on the requester-agent handoff/queue-retry path instead of raw-sending child results directly, and strip child-result wrapper or OpenClaw runtime-context scaffolding from queued outbound retries. Fixes #78531. Thanks @EthanSK.
|
||||
- Sandbox: recreate cached browser bridges when JavaScript-evaluation permission changes, keep failed prune removals tracked for retry, and make cross-device directory moves copy-then-commit without partially emptying the source on failure.
|
||||
- CLI/completion: guard the shell-profile source line written by `openclaw completion --install` with a file existence check (`[ -f ... ] && source ...` for bash/zsh, `test -f ...; and source ...` for fish) so uninstalling OpenClaw no longer makes new login shells error on a missing completion cache. (#78659) Thanks @sjf.
|
||||
- Cron/doctor: repair persisted cron jobs whose `payload.model` was stored as `"default"`, `"null"`, blank, or JSON `null` by removing the bad override during `openclaw doctor --fix` while keeping cron runtime model validation strict. Fixes #78549. Thanks @bizzle12368239.
|
||||
- Telegram: honor `accessGroup:*` sender allowlists for DMs, groups, native commands, and callback authorization before applying Telegram's numeric sender-ID checks. Fixes #78660. Thanks @manugc.
|
||||
- Agent delivery: report `deliverySucceeded=false` when outbound delivery returns no adapter result, so claimed/empty delivery paths no longer masquerade as successful sends. Fixes #78532. Thanks @joeyfrasier.
|
||||
- Cron/isolated runs: fail implicit announce delivery before model execution when `delivery.channel=last` has no previous route, so recurring jobs do not spend tokens before hitting a permanent delivery-target error. Fixes #78608. Thanks @sallyom.
|
||||
- Gateway/sessions: persist a new generated transcript file when daily gateway-agent session rollover changes the session id, while preserving custom transcript paths. Fixes #78607. Thanks @nailujac, @zerone0x, and @sallyom.
|
||||
- Doctor/OpenAI Codex: revert the 2026.5.5 `doctor --fix` repair that rewrote valid `openai-codex/*` ChatGPT/Codex OAuth routes to `openai/*`, which could break OAuth-only GPT-5.5 setups or accidentally move users onto the OpenAI API-key route. If 2026.5.5 already changed your default model, run `openclaw models set openai-codex/gpt-5.5 && openclaw config validate` to switch the default agent back to the Codex OAuth PI route. Fixes #78407.
|
||||
- Doctor/OpenAI Codex: repair legacy `openai-codex/*` agent model refs and stale OpenAI PI session pins to `openai/*` with the Codex runtime, preserving existing `openai-codex` auth profiles so ChatGPT/Codex OAuth users do not fall back to OpenAI API-key routing. Fixes #78407.
|
||||
- Telegram: keep the polling watchdog tied to `getUpdates` liveness so unrelated outbound Bot API calls cannot mask a wedged inbound poller. Fixes #78422. Thanks @ai-hpc.
|
||||
- Discord/groups: instruct group-chat agents to stay silent when a message is addressed to someone else, replying only when invited or correcting key facts. (#78615)
|
||||
- Discord/groups: tell Discord-channel agents to wrap bare URLs as `<https://example.com>` so link previews do not expand into uninvited embeds. (#78614)
|
||||
@@ -268,6 +304,7 @@ Docs: https://docs.openclaw.ai
|
||||
- CLI/update: make dev-channel preflight lint opt-in and constrained when enabled, so `openclaw update --channel dev` no longer walks back otherwise-good main commits when Ubuntu hosts OOM-kill or fail parallel oxlint shards. Thanks @vincentkoc.
|
||||
- Google Meet: fork the caller's current agent transcript into agent-mode meeting consultant sessions, so Meet replies inherit the context from the tool call that joined the meeting.
|
||||
- Google Meet: log the concrete agent-mode TTS provider, model, voice, output format, and sample rate after speech synthesis, so Meet logs show which voice backend spoke each reply.
|
||||
- Control UI/Sessions: hide disk-discovered unregistered-agent sessions by default and fall back from restored unconfigured agent session keys before chat refresh, preventing deleted-agent stores from reopening the wrong workspace. Fixes #41685. Thanks @BunsDev.
|
||||
- Google Meet: log the resolved audio provider model when starting Chrome and paired-node Meet talk-back bridges, so agent-mode joins show the STT model and bidi joins show the realtime voice model.
|
||||
- Google Meet: stop advertising legacy `mode: "realtime"` to agents and config UIs, while keeping it as a hidden compatibility alias for `mode: "agent"`, so new joins use the STT -> OpenClaw agent -> TTS path instead of selecting the direct realtime voice fallback.
|
||||
- Google Meet: add `chrome.audioBufferBytes` for generated command-pair SoX audio commands and lower the default buffer from SoX's 8192 bytes to 4096 bytes to reduce Chrome talk-back latency.
|
||||
@@ -530,6 +567,7 @@ Docs: https://docs.openclaw.ai
|
||||
- WhatsApp: route proactive phone-number sends through Baileys LID forward mappings when available, so LID-addressed contacts receive agent messages instead of creating sender-only ghost chats. Fixes #67378. (#74925) Thanks @edenfunf.
|
||||
- WhatsApp: send captioned `MEDIA:` directive auto-replies once instead of emitting an empty media message before the captioned media reply. (#78770) Thanks @ai-hpc.
|
||||
- Hooks/cron: log returned `/hooks/agent` isolated-run errors and failed cron jobs with cron diagnostic summaries, so rejected `payload.model` values are visible instead of looking like accepted-but-missing runs. Fixes #78597. (#78655) Thanks @kevinslin.
|
||||
- Managed proxy/security: classify raw socket callsites and proxy runtime mutations in boundary checks so new direct egress or unmanaged proxy-state changes cannot land without explicit review. (#77126) Thanks @jesse-merhi.
|
||||
|
||||
## 2026.5.3-1
|
||||
|
||||
@@ -555,6 +593,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Tools/BTW: add `/side` as a text and native slash-command alias for `/btw` side questions.
|
||||
- Doctor/config: `doctor --fix` now commits safe legacy migrations even when unrelated validation issues (e.g. a missing plugin) prevent full validation from passing, so `agents.defaults.llm` and other known-legacy keys are always cleaned up by `doctor --fix` regardless of other config problems. Fixes #76798. (#76800) Thanks @hclsys.
|
||||
- Agents/tools: skip optional media and PDF tool factories when the effective tool denylist already blocks them, avoiding unnecessary hot-path setup for tools that will be filtered out before model use. (#76773) Thanks @dorukardahan.
|
||||
- Agents/compaction: ignore pre-usage transcript metadata bytes when stale token snapshots estimate preflight compaction pressure, while still counting post-usage transcript tail pressure. Fixes #78604. Thanks @amknight.
|
||||
- Discord/status: let explicit reaction tool calls opt into tracking subsequent tool progress on the reacted message with `trackToolCalls: true`, and use the shared tool display emoji table for status reactions.
|
||||
- Gateway/config: stop Gateway startup and hot reload from auto-restoring invalid config; invalid config now fails closed and `openclaw doctor --fix` owns last-known-good repair.
|
||||
- Gateway/performance: lazy-load early runtime discovery and shutdown-hook helpers, defer maintenance timers until after readiness, and trim duplicate plugin auto-enable work during Gateway startup.
|
||||
|
||||
@@ -29,7 +29,7 @@ Welcome to the lobster tank! 🦞
|
||||
- **Ayaan Zaidi** - Telegram subsystem, Android app
|
||||
- GitHub: [@obviyus](https://github.com/obviyus) · X: [@obviyus](https://x.com/obviyus)
|
||||
|
||||
- **Tyler Yust** - Agents/subagents, cron, BlueBubbles, macOS app
|
||||
- **Tyler Yust** - Agents/subagents, cron, iMessage, macOS app
|
||||
- GitHub: [@tyler6204](https://github.com/tyler6204) · X: [@tyleryust](https://x.com/tyleryust)
|
||||
|
||||
- **Mariano Belinky** - iOS app, Security
|
||||
|
||||
10
README.md
10
README.md
@@ -23,7 +23,7 @@ It answers you on the channels you already use. It can speak and listen on macOS
|
||||
|
||||
If you want a personal, single-user assistant that feels local, fast, and always-on, this is it.
|
||||
|
||||
Supported channels include: WhatsApp, Telegram, Slack, Discord, Google Chat, Signal, iMessage, BlueBubbles, IRC, Microsoft Teams, Matrix, Feishu, LINE, Mattermost, Nextcloud Talk, Nostr, Synology Chat, Tlon, Twitch, Zalo, Zalo Personal, WeChat, QQ, WebChat.
|
||||
Supported channels include: WhatsApp, Telegram, Slack, Discord, Google Chat, Signal, iMessage, IRC, Microsoft Teams, Matrix, Feishu, LINE, Mattermost, Nextcloud Talk, Nostr, Synology Chat, Tlon, Twitch, Zalo, Zalo Personal, WeChat, QQ, WebChat.
|
||||
|
||||
[Website](https://openclaw.ai) · [Docs](https://docs.openclaw.ai) · [Vision](VISION.md) · [DeepWiki](https://deepwiki.com/openclaw/openclaw) · [Getting Started](https://docs.openclaw.ai/start/getting-started) · [Updating](https://docs.openclaw.ai/install/updating) · [Showcase](https://docs.openclaw.ai/start/showcase) · [FAQ](https://docs.openclaw.ai/help/faq) · [Onboarding](https://docs.openclaw.ai/start/wizard) · [Nix](https://github.com/openclaw/nix-openclaw) · [Docker](https://docs.openclaw.ai/install/docker) · [Discord](https://discord.gg/clawd)
|
||||
|
||||
@@ -96,7 +96,7 @@ Model note: while many providers and models are supported, prefer a current flag
|
||||
|
||||
## Install (recommended)
|
||||
|
||||
Runtime: **Node 24 (recommended) or Node 22.14+**.
|
||||
Runtime: **Node 24 (recommended) or Node 22.16+**.
|
||||
|
||||
```bash
|
||||
npm install -g openclaw@latest
|
||||
@@ -109,7 +109,7 @@ OpenClaw Onboard installs the Gateway daemon (launchd/systemd user service) so i
|
||||
|
||||
## Quick start (TL;DR)
|
||||
|
||||
Runtime: **Node 24 (recommended) or Node 22.14+**.
|
||||
Runtime: **Node 24 (recommended) or Node 22.16+**.
|
||||
|
||||
Full beginner guide (auth, pairing, channels): [Getting started](https://docs.openclaw.ai/start/getting-started)
|
||||
|
||||
@@ -121,7 +121,7 @@ openclaw gateway --port 18789 --verbose
|
||||
# Send a message
|
||||
openclaw message send --target +1234567890 --message "Hello from OpenClaw"
|
||||
|
||||
# Talk to the assistant (optionally deliver back to any connected channel: WhatsApp/Telegram/Slack/Discord/Google Chat/Signal/iMessage/BlueBubbles/IRC/Microsoft Teams/Matrix/Feishu/LINE/Mattermost/Nextcloud Talk/Nostr/Synology Chat/Tlon/Twitch/Zalo/Zalo Personal/WeChat/QQ/WebChat)
|
||||
# Talk to the assistant (optionally deliver back to any connected channel: WhatsApp/Telegram/Slack/Discord/Google Chat/Signal/iMessage/IRC/Microsoft Teams/Matrix/Feishu/LINE/Mattermost/Nextcloud Talk/Nostr/Synology Chat/Tlon/Twitch/Zalo/Zalo Personal/WeChat/QQ/WebChat)
|
||||
openclaw agent --message "Ship checklist" --thinking high
|
||||
```
|
||||
|
||||
@@ -146,7 +146,7 @@ Run `openclaw doctor` to surface risky/misconfigured DM policies.
|
||||
## Highlights
|
||||
|
||||
- **[Local-first Gateway](https://docs.openclaw.ai/gateway)** — single control plane for sessions, channels, tools, and events.
|
||||
- **[Multi-channel inbox](https://docs.openclaw.ai/channels)** — WhatsApp, Telegram, Slack, Discord, Google Chat, Signal, BlueBubbles (iMessage), iMessage (legacy), IRC, Microsoft Teams, Matrix, Feishu, LINE, Mattermost, Nextcloud Talk, Nostr, Synology Chat, Tlon, Twitch, Zalo, Zalo Personal, WeChat, QQ, WebChat, macOS, iOS/Android.
|
||||
- **[Multi-channel inbox](https://docs.openclaw.ai/channels)** — WhatsApp, Telegram, Slack, Discord, Google Chat, Signal, iMessage, IRC, Microsoft Teams, Matrix, Feishu, LINE, Mattermost, Nextcloud Talk, Nostr, Synology Chat, Tlon, Twitch, Zalo, Zalo Personal, WeChat, QQ, WebChat, macOS, iOS/Android.
|
||||
- **[Multi-agent routing](https://docs.openclaw.ai/gateway/configuration)** — route inbound channels/accounts/peers to isolated agents (workspaces + per-agent sessions).
|
||||
- **[Voice Wake](https://docs.openclaw.ai/nodes/voicewake) + [Talk Mode](https://docs.openclaw.ai/nodes/talk)** — wake words on macOS/iOS and continuous voice on Android (ElevenLabs + system TTS fallback).
|
||||
- **[Live Canvas](https://docs.openclaw.ai/platforms/mac/canvas)** — agent-driven visual workspace with [A2UI](https://docs.openclaw.ai/platforms/mac/canvas#canvas-a2ui).
|
||||
|
||||
@@ -312,7 +312,7 @@ OpenClaw's web interface (Gateway Control UI + HTTP endpoints) is intended for *
|
||||
|
||||
### Node.js Version
|
||||
|
||||
OpenClaw requires **Node.js 22.14.0 or later** (LTS). This version includes important security patches:
|
||||
OpenClaw requires **Node.js 22.16.0 or later** (LTS). This version includes important security patches:
|
||||
|
||||
- CVE-2025-59466: async_hooks DoS vulnerability
|
||||
- CVE-2026-21636: Permission model bypass vulnerability
|
||||
@@ -320,7 +320,7 @@ OpenClaw requires **Node.js 22.14.0 or later** (LTS). This version includes impo
|
||||
Verify your Node.js version:
|
||||
|
||||
```bash
|
||||
node --version # Should be v22.14.0 or later
|
||||
node --version # Should be v22.16.0 or later
|
||||
```
|
||||
|
||||
### Docker Security
|
||||
|
||||
@@ -1517,6 +1517,7 @@ public struct SessionsListParams: Codable, Sendable {
|
||||
public let activeminutes: Int?
|
||||
public let includeglobal: Bool?
|
||||
public let includeunknown: Bool?
|
||||
public let configuredagentsonly: Bool?
|
||||
public let includederivedtitles: Bool?
|
||||
public let includelastmessage: Bool?
|
||||
public let label: String?
|
||||
@@ -1529,6 +1530,7 @@ public struct SessionsListParams: Codable, Sendable {
|
||||
activeminutes: Int?,
|
||||
includeglobal: Bool?,
|
||||
includeunknown: Bool?,
|
||||
configuredagentsonly: Bool?,
|
||||
includederivedtitles: Bool?,
|
||||
includelastmessage: Bool?,
|
||||
label: String?,
|
||||
@@ -1540,6 +1542,7 @@ public struct SessionsListParams: Codable, Sendable {
|
||||
self.activeminutes = activeminutes
|
||||
self.includeglobal = includeglobal
|
||||
self.includeunknown = includeunknown
|
||||
self.configuredagentsonly = configuredagentsonly
|
||||
self.includederivedtitles = includederivedtitles
|
||||
self.includelastmessage = includelastmessage
|
||||
self.label = label
|
||||
@@ -1553,6 +1556,7 @@ public struct SessionsListParams: Codable, Sendable {
|
||||
case activeminutes = "activeMinutes"
|
||||
case includeglobal = "includeGlobal"
|
||||
case includeunknown = "includeUnknown"
|
||||
case configuredagentsonly = "configuredAgentsOnly"
|
||||
case includederivedtitles = "includeDerivedTitles"
|
||||
case includelastmessage = "includeLastMessage"
|
||||
case label
|
||||
|
||||
@@ -9,6 +9,7 @@ const rootEntries = [
|
||||
"src/index.ts!",
|
||||
"src/entry.ts!",
|
||||
"src/cli/daemon-cli.ts!",
|
||||
"src/infra/kysely-node-sqlite.ts!",
|
||||
"src/infra/warning-filter.ts!",
|
||||
"src/infra/command-explainer/index.ts!",
|
||||
bundledPluginFile("telegram", "src/audit.ts", "!"),
|
||||
@@ -30,10 +31,12 @@ const bundledPluginEntries = [
|
||||
|
||||
const bundledPluginIgnoredRuntimeDependencies = [
|
||||
"@agentclientprotocol/claude-agent-acp",
|
||||
"@a2ui/lit",
|
||||
"@azure/identity",
|
||||
"@clawdbot/lobster",
|
||||
"@discordjs/opus",
|
||||
"@homebridge/ciao",
|
||||
"@lit/context",
|
||||
"@matrix-org/matrix-sdk-crypto-wasm",
|
||||
"@mozilla/readability",
|
||||
"@openai/codex",
|
||||
@@ -42,6 +45,7 @@ const bundledPluginIgnoredRuntimeDependencies = [
|
||||
"@zed-industries/codex-acp",
|
||||
"jiti",
|
||||
"json5",
|
||||
"lit",
|
||||
"linkedom",
|
||||
"openclaw",
|
||||
"pdfjs-dist",
|
||||
@@ -169,7 +173,7 @@ const config = {
|
||||
// Bundled plugins often load their public surface via string specifiers in
|
||||
// `index.ts` contracts, so Knip needs these convention-based entry files.
|
||||
entry: bundledPluginEntries,
|
||||
project: ["index.ts!", "src/**/*.ts!"],
|
||||
project: ["index.ts!", "src/**/*.{js,mjs,ts}!"],
|
||||
ignoreDependencies: bundledPluginIgnoredRuntimeDependencies,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
b14178c6945e0d9da9b35a12cc1fecd1406165bb440619c33bfec59fe5e0287d config-baseline.json
|
||||
f860a7d43d3bd15379d8c3dfccbc6fcbf47b9bec8d8b67b29dd7313946905645 config-baseline.core.json
|
||||
7238265b921affbb481198f603293c9b1c988025713c55ee19fdbf132a8339ab config-baseline.json
|
||||
97579293de31bc607194bce3e22c16d140c08ab9e6f1e38298f3ce47fbc9d68b config-baseline.core.json
|
||||
463c45a79d02598184caccbc6f316692df962fe6b0e84d1a3e3cc1809f862b15 config-baseline.channel.json
|
||||
3094eba68b507a852a73952179e5f6decddfbb1ec377a2bc65409f92aff647a3 config-baseline.plugin.json
|
||||
b6d36d17e554a2ec5a1a6c6d32107a9a1113c274a700100962d97b6afbdafb25 config-baseline.plugin.json
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
a2a671522a9855594b011c86425911f2297e756c666f4ceb1cc453f613983725 plugin-sdk-api-baseline.json
|
||||
b939b18ab3cbd21f338fe2a7cd8783b612c0956e79831d66dae4a49f9ba85014 plugin-sdk-api-baseline.jsonl
|
||||
28e280d21693216c99cfa8da553589b41741d37c0ada956e316ee01d3d6c202c plugin-sdk-api-baseline.json
|
||||
633dae33da97f6a073c5561709c57d5c0b7ff67af0512d0261f05455c24b38de plugin-sdk-api-baseline.jsonl
|
||||
|
||||
@@ -76,7 +76,6 @@
|
||||
{
|
||||
"group": "消息平台",
|
||||
"pages": [
|
||||
"zh-CN/channels/bluebubbles",
|
||||
"zh-CN/channels/discord",
|
||||
"zh-CN/channels/feishu",
|
||||
"zh-CN/channels/grammy",
|
||||
|
||||
@@ -62,6 +62,18 @@ Explicit copy flows, such as `openclaw agents add`, use this portability policy:
|
||||
Non-portable profiles remain available through read-through inheritance unless
|
||||
the target agent signs in separately and creates its own local profile.
|
||||
|
||||
## Config-only auth routes
|
||||
|
||||
`auth.profiles` entries with `mode: "aws-sdk"` are routing metadata, not stored
|
||||
credentials. They are valid when the target provider uses
|
||||
`models.providers.<id>.auth: "aws-sdk"` or the built-in Amazon Bedrock default
|
||||
AWS SDK route. These profile ids may appear in `auth.order` and session
|
||||
overrides even when no matching entry exists in `auth-profiles.json`.
|
||||
|
||||
Do not write `type: "aws-sdk"` into `auth-profiles.json`. If a legacy install
|
||||
has such a marker, `openclaw doctor --fix` moves it to `auth.profiles` and
|
||||
removes the marker from the credential store.
|
||||
|
||||
## Explicit auth order filtering
|
||||
|
||||
- When `auth.order.<provider>` or the auth-store order override is set for a
|
||||
|
||||
@@ -90,7 +90,7 @@ openclaw cron add \
|
||||
--tz America/New_York \
|
||||
--timeout-seconds 300 \
|
||||
--announce \
|
||||
--channel bluebubbles \
|
||||
--channel imessage \
|
||||
--to "+1XXXXXXXXXX" \
|
||||
--message "Execute daily inbox triage per standing orders. Check mail for new alerts. Parse, categorize, and persist each item. Report summary to owner. Escalate unknowns."
|
||||
```
|
||||
|
||||
@@ -1,638 +0,0 @@
|
||||
---
|
||||
summary: "Legacy iMessage support via the BlueBubbles macOS server (REST send/receive, typing, reactions, pairing, advanced actions)."
|
||||
read_when:
|
||||
- Setting up BlueBubbles channel
|
||||
- Troubleshooting webhook pairing
|
||||
- Configuring iMessage on macOS
|
||||
title: "BlueBubbles"
|
||||
sidebarTitle: "BlueBubbles"
|
||||
---
|
||||
|
||||
Status: bundled legacy plugin that talks to the BlueBubbles macOS server over HTTP. Existing BlueBubbles setups continue to work, but new OpenClaw iMessage deployments should prefer the native [iMessage](/channels/imessage) plugin when its requirements fit your host.
|
||||
|
||||
<Warning>
|
||||
BlueBubbles is deprecated for new OpenClaw setups.
|
||||
|
||||
The upstream BlueBubbles ecosystem is still active, but OpenClaw depends on the BlueBubbles macOS server API. As of May 6, 2026, the official [`bluebubbles-server`](https://github.com/BlueBubblesApp/bluebubbles-server) development branch last changed on [January 22, 2026](https://github.com/BlueBubblesApp/bluebubbles-server/commit/88a4921bbd5a8111f1e9582b83715cf877171037), and the latest server release ([`v1.9.9`](https://github.com/BlueBubblesApp/bluebubbles-server/releases/tag/v1.9.9)) was published on May 16, 2025. The client app and helper repositories have newer activity, so this is not an abandonment claim; the deprecation is about reducing OpenClaw's dependency on an external HTTP server, webhooks, and private-API compatibility surface when the native `imsg` path keeps the integration on a local stdio contract.
|
||||
</Warning>
|
||||
|
||||
<Note>
|
||||
Current OpenClaw releases bundle BlueBubbles, so normal packaged builds do not need a separate `openclaw plugins install` step.
|
||||
</Note>
|
||||
|
||||
## Overview
|
||||
|
||||
- Runs on macOS via the BlueBubbles helper app ([bluebubbles.app](https://bluebubbles.app)).
|
||||
- Legacy fallback for installations that already rely on BlueBubbles channel IDs, webhook state, group targets, cron delivery, or workspace routing.
|
||||
- Recommended/tested: macOS Sequoia (15). macOS Tahoe (26) works; edit is currently broken on Tahoe, and group icon updates may report success but not sync.
|
||||
- OpenClaw talks to it through its REST API (`GET /api/v1/ping`, `POST /message/text`, `POST /chat/:id/*`).
|
||||
- Incoming messages arrive via webhooks; outgoing replies, typing indicators, read receipts, and tapbacks are REST calls.
|
||||
- Attachments and stickers are ingested as inbound media (and surfaced to the agent when possible).
|
||||
- Auto-TTS replies that synthesize MP3 or CAF audio are delivered as iMessage voice memo bubbles instead of plain file attachments.
|
||||
- Pairing/allowlist works the same way as other channels (`/channels/pairing` etc) with `channels.bluebubbles.allowFrom` + pairing codes.
|
||||
- Reactions are surfaced as system events just like Slack/Telegram so agents can "mention" them before replying.
|
||||
- Advanced features: edit, unsend, reply threading, message effects, group management.
|
||||
|
||||
## Quick start
|
||||
|
||||
<Steps>
|
||||
<Step title="Install BlueBubbles">
|
||||
Install the BlueBubbles server on your Mac (follow the instructions at [bluebubbles.app/install](https://bluebubbles.app/install)).
|
||||
</Step>
|
||||
<Step title="Enable the web API">
|
||||
In the BlueBubbles config, enable the web API and set a password.
|
||||
</Step>
|
||||
<Step title="Configure OpenClaw">
|
||||
Run `openclaw onboard` and select BlueBubbles, or configure manually:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
bluebubbles: {
|
||||
enabled: true,
|
||||
serverUrl: "http://192.168.1.100:1234",
|
||||
password: "example-password",
|
||||
webhookPath: "/bluebubbles-webhook",
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
</Step>
|
||||
<Step title="Point webhooks at the gateway">
|
||||
Point BlueBubbles webhooks to your gateway (example: `https://your-gateway-host:3000/bluebubbles-webhook?password=<password>`).
|
||||
</Step>
|
||||
<Step title="Start the gateway">
|
||||
Start the gateway; it will register the webhook handler and start pairing.
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
<Warning>
|
||||
**Security**
|
||||
|
||||
- Always set a webhook password.
|
||||
- Webhook authentication is always required. OpenClaw rejects BlueBubbles webhook requests unless they include a password/guid that matches `channels.bluebubbles.password` (for example `?password=<password>` or `x-password`), regardless of loopback/proxy topology.
|
||||
- Password authentication is checked before reading/parsing full webhook bodies.
|
||||
|
||||
</Warning>
|
||||
|
||||
## Keeping Messages.app alive (VM / headless setups)
|
||||
|
||||
Some macOS VM / always-on setups can end up with Messages.app going "idle" (incoming events stop until the app is opened/foregrounded). A simple workaround is to **poke Messages every 5 minutes** using an AppleScript + LaunchAgent.
|
||||
|
||||
<Steps>
|
||||
<Step title="Save the AppleScript">
|
||||
Save this as `~/Scripts/poke-messages.scpt`:
|
||||
|
||||
```applescript
|
||||
try
|
||||
tell application "Messages"
|
||||
if not running then
|
||||
launch
|
||||
end if
|
||||
|
||||
-- Touch the scripting interface to keep the process responsive.
|
||||
set _chatCount to (count of chats)
|
||||
end tell
|
||||
on error
|
||||
-- Ignore transient failures (first-run prompts, locked session, etc).
|
||||
end try
|
||||
```
|
||||
|
||||
</Step>
|
||||
<Step title="Install a LaunchAgent">
|
||||
Save this as `~/Library/LaunchAgents/com.user.poke-messages.plist`:
|
||||
|
||||
```xml
|
||||
<?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>Label</key>
|
||||
<string>com.user.poke-messages</string>
|
||||
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>/bin/bash</string>
|
||||
<string>-lc</string>
|
||||
<string>/usr/bin/osascript "$HOME/Scripts/poke-messages.scpt"</string>
|
||||
</array>
|
||||
|
||||
<key>RunAtLoad</key>
|
||||
<true/>
|
||||
|
||||
<key>StartInterval</key>
|
||||
<integer>300</integer>
|
||||
|
||||
<key>StandardOutPath</key>
|
||||
<string>/tmp/poke-messages.log</string>
|
||||
<key>StandardErrorPath</key>
|
||||
<string>/tmp/poke-messages.err</string>
|
||||
</dict>
|
||||
</plist>
|
||||
```
|
||||
|
||||
This runs **every 300 seconds** and **on login**. The first run may trigger macOS **Automation** prompts (`osascript` → Messages). Approve them in the same user session that runs the LaunchAgent.
|
||||
|
||||
</Step>
|
||||
<Step title="Load it">
|
||||
```bash
|
||||
launchctl unload ~/Library/LaunchAgents/com.user.poke-messages.plist 2>/dev/null || true
|
||||
launchctl load ~/Library/LaunchAgents/com.user.poke-messages.plist
|
||||
```
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
## Onboarding
|
||||
|
||||
BlueBubbles is available in interactive onboarding:
|
||||
|
||||
```
|
||||
openclaw onboard
|
||||
```
|
||||
|
||||
The wizard prompts for:
|
||||
|
||||
<ParamField path="Server URL" type="string" required>
|
||||
BlueBubbles server address (e.g., `http://192.168.1.100:1234`).
|
||||
</ParamField>
|
||||
<ParamField path="Password" type="string" required>
|
||||
API password from BlueBubbles Server settings.
|
||||
</ParamField>
|
||||
<ParamField path="Webhook path" type="string" default="/bluebubbles-webhook">
|
||||
Webhook endpoint path.
|
||||
</ParamField>
|
||||
<ParamField path="DM policy" type="string">
|
||||
`pairing`, `allowlist`, `open`, or `disabled`.
|
||||
</ParamField>
|
||||
<ParamField path="Allow list" type="string[]">
|
||||
Phone numbers, emails, or chat targets.
|
||||
</ParamField>
|
||||
|
||||
You can also add BlueBubbles via CLI:
|
||||
|
||||
```
|
||||
openclaw channels add bluebubbles --http-url http://192.168.1.100:1234 --password <password>
|
||||
```
|
||||
|
||||
## Access control (DMs + groups)
|
||||
|
||||
<Tabs>
|
||||
<Tab title="DMs">
|
||||
- Default: `channels.bluebubbles.dmPolicy = "pairing"`.
|
||||
- Unknown senders receive a pairing code; messages are ignored until approved (codes expire after 1 hour).
|
||||
- Approve via:
|
||||
- `openclaw pairing list bluebubbles`
|
||||
- `openclaw pairing approve bluebubbles <CODE>`
|
||||
- Pairing is the default token exchange. Details: [Pairing](/channels/pairing)
|
||||
|
||||
</Tab>
|
||||
<Tab title="Groups">
|
||||
- `channels.bluebubbles.groupPolicy = open | allowlist | disabled` (default: `allowlist`).
|
||||
- `channels.bluebubbles.groupAllowFrom` controls who can trigger in groups when `allowlist` is set.
|
||||
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
### Contact name enrichment (macOS, optional)
|
||||
|
||||
BlueBubbles group webhooks often only include raw participant addresses. If you want `GroupMembers` context to show local contact names instead, you can opt in to local Contacts enrichment on macOS:
|
||||
|
||||
- `channels.bluebubbles.enrichGroupParticipantsFromContacts = true` enables the lookup. Default: `false`.
|
||||
- Lookups run only after group access, command authorization, and mention gating have allowed the message through.
|
||||
- Only unnamed phone participants are enriched.
|
||||
- Raw phone numbers remain as the fallback when no local match is found.
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
bluebubbles: {
|
||||
enrichGroupParticipantsFromContacts: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### Mention gating (groups)
|
||||
|
||||
BlueBubbles supports mention gating for group chats, matching iMessage/WhatsApp behavior:
|
||||
|
||||
- Uses `agents.list[].groupChat.mentionPatterns` (or `messages.groupChat.mentionPatterns`) to detect mentions.
|
||||
- When `requireMention` is enabled for a group, the agent only responds when mentioned.
|
||||
- Control commands from authorized senders bypass mention gating.
|
||||
|
||||
Per-group configuration:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
bluebubbles: {
|
||||
groupPolicy: "allowlist",
|
||||
groupAllowFrom: ["+15555550123"],
|
||||
groups: {
|
||||
"*": { requireMention: true }, // default for all groups
|
||||
"iMessage;-;chat123": { requireMention: false }, // override for specific group
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### Command gating
|
||||
|
||||
- Control commands (e.g., `/config`, `/model`) require authorization.
|
||||
- Uses `allowFrom` and `groupAllowFrom` to determine command authorization.
|
||||
- Authorized senders can run control commands even without mentioning in groups.
|
||||
|
||||
### Per-group system prompt
|
||||
|
||||
Each entry under `channels.bluebubbles.groups.*` accepts an optional `systemPrompt` string. The value is injected into the agent's system prompt on every turn that handles a message in that group, so you can set per-group persona or behavioral rules without editing agent prompts:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
bluebubbles: {
|
||||
groups: {
|
||||
"iMessage;-;chat123": {
|
||||
systemPrompt: "Keep responses under 3 sentences. Mirror the group's casual tone.",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
The key matches whatever BlueBubbles reports as `chatGuid` / `chatIdentifier` / numeric `chatId` for the group, and a `"*"` wildcard entry provides a default for every group without an exact match (same pattern used by `requireMention` and per-group tool policies). Exact matches always win over the wildcard. DMs ignore this field; use agent-level or account-level prompt customization instead.
|
||||
|
||||
#### Worked example: threaded replies and tapback reactions (Private API)
|
||||
|
||||
With the BlueBubbles Private API enabled, inbound messages arrive with short message IDs (for example `[[reply_to:5]]`) and the agent can call `action=reply` to thread into a specific message or `action=react` to drop a tapback. A per-group `systemPrompt` is a reliable way to keep the agent choosing the right tool:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
bluebubbles: {
|
||||
groups: {
|
||||
"iMessage;+;chat-family": {
|
||||
systemPrompt: "When replying in this group, always call action=reply with the [[reply_to:N]] messageId from context so your response threads under the triggering message. Never send a new unlinked message. For short acknowledgements ('ok', 'got it', 'on it'), use action=react with an appropriate tapback emoji (❤️, 👍, 😂, ‼️, ❓) instead of sending a text reply.",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Tapback reactions and threaded replies both require the BlueBubbles Private API; see [Advanced actions](#advanced-actions) and [Message IDs](#message-ids-short-vs-full) for the underlying mechanics.
|
||||
|
||||
## ACP conversation bindings
|
||||
|
||||
BlueBubbles chats can be turned into durable ACP workspaces without changing the transport layer.
|
||||
|
||||
Fast operator flow:
|
||||
|
||||
- Run `/acp spawn codex --bind here` inside the DM or allowed group chat.
|
||||
- Future messages in that same BlueBubbles conversation route to the spawned ACP session.
|
||||
- `/new` and `/reset` reset the same bound ACP session in place.
|
||||
- `/acp close` closes the ACP session and removes the binding.
|
||||
|
||||
Configured persistent bindings are also supported through top-level `bindings[]` entries with `type: "acp"` and `match.channel: "bluebubbles"`.
|
||||
|
||||
`match.peer.id` can use any supported BlueBubbles target form:
|
||||
|
||||
- normalized DM handle such as `+15555550123` or `user@example.com`
|
||||
- `chat_id:<id>`
|
||||
- `chat_guid:<guid>`
|
||||
- `chat_identifier:<identifier>`
|
||||
|
||||
For stable group bindings, prefer `chat_id:*` or `chat_identifier:*`.
|
||||
|
||||
Example:
|
||||
|
||||
```json5
|
||||
{
|
||||
agents: {
|
||||
list: [
|
||||
{
|
||||
id: "codex",
|
||||
runtime: {
|
||||
type: "acp",
|
||||
acp: { agent: "codex", backend: "acpx", mode: "persistent" },
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
bindings: [
|
||||
{
|
||||
type: "acp",
|
||||
agentId: "codex",
|
||||
match: {
|
||||
channel: "bluebubbles",
|
||||
accountId: "default",
|
||||
peer: { kind: "dm", id: "+15555550123" },
|
||||
},
|
||||
acp: { label: "codex-imessage" },
|
||||
},
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
See [ACP Agents](/tools/acp-agents) for shared ACP binding behavior.
|
||||
|
||||
## Typing + read receipts
|
||||
|
||||
- **Typing indicators**: Sent automatically before and during response generation.
|
||||
- **Read receipts**: Controlled by `channels.bluebubbles.sendReadReceipts` (default: `true`).
|
||||
- **Typing indicators**: OpenClaw sends typing start events; BlueBubbles clears typing automatically on send or timeout (manual stop via DELETE is unreliable).
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
bluebubbles: {
|
||||
sendReadReceipts: false, // disable read receipts
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Advanced actions
|
||||
|
||||
BlueBubbles supports advanced message actions when enabled in config:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
bluebubbles: {
|
||||
actions: {
|
||||
reactions: true, // tapbacks (default: true)
|
||||
edit: true, // edit sent messages (macOS 13+, broken on macOS 26 Tahoe)
|
||||
unsend: true, // unsend messages (macOS 13+)
|
||||
reply: true, // reply threading by message GUID
|
||||
sendWithEffect: true, // message effects (slam, loud, etc.)
|
||||
renameGroup: true, // rename group chats
|
||||
setGroupIcon: true, // set group chat icon/photo (flaky on macOS 26 Tahoe)
|
||||
addParticipant: true, // add participants to groups
|
||||
removeParticipant: true, // remove participants from groups
|
||||
leaveGroup: true, // leave group chats
|
||||
sendAttachment: true, // send attachments/media
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="Available actions">
|
||||
- **react**: Add/remove tapback reactions (`messageId`, `emoji`, `remove`). iMessage's native tapback set is `love`, `like`, `dislike`, `laugh`, `emphasize`, and `question`. When an agent picks an emoji outside that set (for example `👀`), the reaction tool falls back to `love` so the tapback still renders instead of failing the whole request. Configured ack reactions still validate strictly and error on unknown values.
|
||||
- **edit**: Edit a sent message (`messageId`, `text`).
|
||||
- **unsend**: Unsend a message (`messageId`).
|
||||
- **reply**: Reply to a specific message (`messageId`, `text`, `to`).
|
||||
- **sendWithEffect**: Send with iMessage effect (`text`, `to`, `effectId`).
|
||||
- **renameGroup**: Rename a group chat (`chatGuid`, `displayName`).
|
||||
- **setGroupIcon**: Set a group chat's icon/photo (`chatGuid`, `media`) - flaky on macOS 26 Tahoe (API may return success but the icon does not sync).
|
||||
- **addParticipant**: Add someone to a group (`chatGuid`, `address`).
|
||||
- **removeParticipant**: Remove someone from a group (`chatGuid`, `address`).
|
||||
- **leaveGroup**: Leave a group chat (`chatGuid`).
|
||||
- **upload-file**: Send media/files (`to`, `buffer`, `filename`, `asVoice`).
|
||||
- Voice memos: set `asVoice: true` with **MP3** or **CAF** audio to send as an iMessage voice message. BlueBubbles converts MP3 → CAF when sending voice memos.
|
||||
- Legacy alias: `sendAttachment` still works, but `upload-file` is the canonical action name.
|
||||
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
### Message IDs (short vs full)
|
||||
|
||||
OpenClaw may surface _short_ message IDs (e.g., `1`, `2`) to save tokens.
|
||||
|
||||
- `MessageSid` / `ReplyToId` can be short IDs.
|
||||
- `MessageSidFull` / `ReplyToIdFull` contain the provider full IDs.
|
||||
- Short IDs are in-memory; they can expire on restart or cache eviction.
|
||||
- Actions accept short or full `messageId`, but short IDs will error if no longer available.
|
||||
|
||||
Use full IDs for durable automations and storage:
|
||||
|
||||
- Templates: `{{MessageSidFull}}`, `{{ReplyToIdFull}}`
|
||||
- Context: `MessageSidFull` / `ReplyToIdFull` in inbound payloads
|
||||
|
||||
See [Configuration](/gateway/configuration) for template variables.
|
||||
|
||||
<a id="coalescing-split-send-dms-command--url-in-one-composition"></a>
|
||||
|
||||
## Coalescing split-send DMs (command + URL in one composition)
|
||||
|
||||
When a user types a command and a URL together in iMessage - e.g. `Dump https://example.com/article` - Apple splits the send into **two separate webhook deliveries**:
|
||||
|
||||
1. A text message (`"Dump"`).
|
||||
2. A URL-preview balloon (`"https://..."`) with OG-preview images as attachments.
|
||||
|
||||
The two webhooks arrive at OpenClaw ~0.8-2.0 s apart on most setups. Without coalescing, the agent receives the command alone on turn 1, replies (often "send me the URL"), and only sees the URL on turn 2 - at which point the command context is already lost.
|
||||
|
||||
`channels.bluebubbles.coalesceSameSenderDms` opts a DM into merging consecutive same-sender webhooks into a single agent turn. Group chats continue to key per-message so multi-user turn structure is preserved.
|
||||
|
||||
<Tabs>
|
||||
<Tab title="When to enable">
|
||||
Enable when:
|
||||
|
||||
- You ship skills that expect `command + payload` in one message (dump, paste, save, queue, etc.).
|
||||
- Your users paste URLs, images, or long content alongside commands.
|
||||
- You can accept the added DM turn latency (see below).
|
||||
|
||||
Leave disabled when:
|
||||
|
||||
- You need minimum command latency for single-word DM triggers.
|
||||
- All your flows are one-shot commands without payload follow-ups.
|
||||
|
||||
</Tab>
|
||||
<Tab title="Enabling">
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
bluebubbles: {
|
||||
coalesceSameSenderDms: true, // opt in (default: false)
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
With the flag on and no explicit `messages.inbound.byChannel.bluebubbles`, the debounce window widens to **2500 ms** (the default for non-coalescing is 500 ms). The wider window is required - Apple's split-send cadence of 0.8-2.0 s does not fit in the tighter default.
|
||||
|
||||
To tune the window yourself:
|
||||
|
||||
```json5
|
||||
{
|
||||
messages: {
|
||||
inbound: {
|
||||
byChannel: {
|
||||
// 2500 ms works for most setups; raise to 4000 ms if your Mac is slow
|
||||
// or under memory pressure (observed gap can stretch past 2 s then).
|
||||
bluebubbles: 2500,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
</Tab>
|
||||
<Tab title="Trade-offs">
|
||||
- **Added latency for DM control commands.** With the flag on, DM control-command messages (like `Dump`, `Save`, etc.) now wait up to the debounce window before dispatching, in case a payload webhook is coming. Group-chat commands keep instant dispatch.
|
||||
- **Merged output is bounded** - merged text caps at 4000 chars with an explicit `…[truncated]` marker; attachments cap at 20; source entries cap at 10 (first-plus-latest retained beyond that). Every source `messageId` still reaches inbound-dedupe so a later MessagePoller replay of any individual event is recognized as a duplicate.
|
||||
- **Opt-in, per-channel.** Other channels (Telegram, WhatsApp, Slack, …) are unaffected.
|
||||
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
### Scenarios and what the agent sees
|
||||
|
||||
| User composes | Apple delivers | Flag off (default) | Flag on + 2500 ms window |
|
||||
| ------------------------------------------------------------------ | ------------------------- | --------------------------------------- | ----------------------------------------------------------------------- |
|
||||
| `Dump https://example.com` (one send) | 2 webhooks ~1 s apart | Two agent turns: "Dump" alone, then URL | One turn: merged text `Dump https://example.com` |
|
||||
| `Save this 📎image.jpg caption` (attachment + text) | 2 webhooks | Two turns | One turn: text + image |
|
||||
| `/status` (standalone command) | 1 webhook | Instant dispatch | **Wait up to window, then dispatch** |
|
||||
| URL pasted alone | 1 webhook | Instant dispatch | Instant dispatch (only one entry in bucket) |
|
||||
| Text + URL sent as two deliberate separate messages, minutes apart | 2 webhooks outside window | Two turns | Two turns (window expires between them) |
|
||||
| Rapid flood (>10 small DMs inside window) | N webhooks | N turns | One turn, bounded output (first + latest, text/attachment caps applied) |
|
||||
|
||||
### Split-send coalescing troubleshooting
|
||||
|
||||
If the flag is on and split-sends still arrive as two turns, check each layer:
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="Config actually loaded">
|
||||
```
|
||||
grep coalesceSameSenderDms ~/.openclaw/openclaw.json
|
||||
```
|
||||
|
||||
Then `openclaw gateway restart` - the flag is read at debouncer-registry creation.
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="Debounce window wide enough for your setup">
|
||||
Look at the BlueBubbles server log under `~/Library/Logs/bluebubbles-server/main.log`:
|
||||
|
||||
```
|
||||
grep -E "Dispatching event to webhook" main.log | tail -20
|
||||
```
|
||||
|
||||
Measure the gap between the `"Dump"`-style text dispatch and the `"https://..."; Attachments:` dispatch that follows. Raise `messages.inbound.byChannel.bluebubbles` to comfortably cover that gap.
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="Session JSONL timestamps ≠ webhook arrival">
|
||||
Session event timestamps (`~/.openclaw/agents/<id>/sessions/*.jsonl`) reflect when the gateway hands a message to the agent, **not** when the webhook arrived. A queued-second message tagged `[Queued messages while agent was busy]` means the first turn was still running when the second webhook arrived - the coalesce bucket had already flushed. Tune the window against the BB server log, not the session log.
|
||||
</Accordion>
|
||||
<Accordion title="Memory pressure slowing reply dispatch">
|
||||
On smaller machines (8 GB), agent turns can take long enough that the coalesce bucket flushes before the reply completes, and the URL lands as a queued second turn. Check `memory_pressure` and `ps -o rss -p $(pgrep openclaw-gateway)`; if the gateway is over ~500 MB RSS and the compressor is active, close other heavy processes or bump to a larger host.
|
||||
</Accordion>
|
||||
<Accordion title="Reply-quote sends are a different path">
|
||||
If the user tapped `Dump` as a **reply** to an existing URL-balloon (iMessage shows a "1 Reply" badge on the Dump bubble), the URL lives in `replyToBody`, not in a second webhook. Coalescing does not apply - that's a skill/prompt concern, not a debouncer concern.
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
## Block streaming
|
||||
|
||||
Control whether responses are sent as a single message or streamed in blocks:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
bluebubbles: {
|
||||
blockStreaming: true, // enable block streaming (off by default)
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Media + limits
|
||||
|
||||
- Inbound attachments are downloaded and stored in the media cache.
|
||||
- Media cap via `channels.bluebubbles.mediaMaxMb` for inbound and outbound media (default: 8 MB).
|
||||
- Outbound text is chunked to `channels.bluebubbles.textChunkLimit` (default: 4000 chars).
|
||||
|
||||
## Configuration reference
|
||||
|
||||
Full configuration: [Configuration](/gateway/configuration)
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="Connection and webhook">
|
||||
- `channels.bluebubbles.enabled`: Enable/disable the channel.
|
||||
- `channels.bluebubbles.serverUrl`: BlueBubbles REST API base URL.
|
||||
- `channels.bluebubbles.password`: API password.
|
||||
- `channels.bluebubbles.webhookPath`: Webhook endpoint path (default: `/bluebubbles-webhook`).
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="Access policy">
|
||||
- `channels.bluebubbles.dmPolicy`: `pairing | allowlist | open | disabled` (default: `pairing`).
|
||||
- `channels.bluebubbles.allowFrom`: DM allowlist (handles, emails, E.164 numbers, `chat_id:*`, `chat_guid:*`).
|
||||
- `channels.bluebubbles.groupPolicy`: `open | allowlist | disabled` (default: `allowlist`).
|
||||
- `channels.bluebubbles.groupAllowFrom`: Group sender allowlist.
|
||||
- `channels.bluebubbles.enrichGroupParticipantsFromContacts`: On macOS, optionally enrich unnamed group participants from local Contacts after gating passes. Default: `false`.
|
||||
- `channels.bluebubbles.groups`: Per-group config (`requireMention`, etc.).
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="Delivery and chunking">
|
||||
- `channels.bluebubbles.sendReadReceipts`: Send read receipts (default: `true`).
|
||||
- `channels.bluebubbles.blockStreaming`: Enable block streaming (default: `false`; required for streaming replies).
|
||||
- `channels.bluebubbles.textChunkLimit`: Outbound chunk size in chars (default: 4000).
|
||||
- `channels.bluebubbles.sendTimeoutMs`: Per-request timeout in ms for outbound text sends via `/api/v1/message/text` (default: 30000). Raise on macOS 26 setups where Private API iMessage sends can stall for 60+ seconds inside the iMessage framework; for example `45000` or `60000`. Probes, chat lookups, reactions, edits, and health checks currently keep the shorter 10s default; broadening coverage to reactions and edits is planned as a follow-up. Per-account override: `channels.bluebubbles.accounts.<accountId>.sendTimeoutMs`.
|
||||
- `channels.bluebubbles.chunkMode`: `length` (default) splits only when exceeding `textChunkLimit`; `newline` splits on blank lines (paragraph boundaries) before length chunking.
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="Media and history">
|
||||
- `channels.bluebubbles.mediaMaxMb`: Inbound/outbound media cap in MB (default: 8).
|
||||
- `channels.bluebubbles.mediaLocalRoots`: Explicit allowlist of absolute local directories permitted for outbound local media paths. Local path sends are denied by default unless this is configured. Per-account override: `channels.bluebubbles.accounts.<accountId>.mediaLocalRoots`.
|
||||
- `channels.bluebubbles.coalesceSameSenderDms`: Merge consecutive same-sender DM webhooks into one agent turn so Apple's text+URL split-send arrives as a single message (default: `false`). See [Coalescing split-send DMs](#coalescing-split-send-dms-command--url-in-one-composition) for scenarios, window tuning, and trade-offs. Widens the default inbound debounce window from 500 ms to 2500 ms when enabled without an explicit `messages.inbound.byChannel.bluebubbles`.
|
||||
- `channels.bluebubbles.historyLimit`: Max group messages for context (0 disables).
|
||||
- `channels.bluebubbles.dmHistoryLimit`: DM history limit.
|
||||
- `channels.bluebubbles.replyContextApiFallback`: When an inbound reply lands without `replyToBody`/`replyToSender` and the in-memory reply-context cache misses, fetch the original message from the BlueBubbles HTTP API as a best-effort fallback (default: `false`). Useful for multi-instance deployments sharing one BlueBubbles account, after process restarts, or after long-lived TTL/LRU cache eviction. The fetch is SSRF-guarded by the same policy as every other BlueBubbles client request, never throws, and populates the cache so subsequent replies amortize. Per-account override: `channels.bluebubbles.accounts.<accountId>.replyContextApiFallback`. A channel-level setting propagates to accounts that omit the flag.
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="Actions and accounts">
|
||||
- `channels.bluebubbles.actions`: Enable/disable specific actions.
|
||||
- `channels.bluebubbles.accounts`: Multi-account configuration.
|
||||
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
Related global options:
|
||||
|
||||
- `agents.list[].groupChat.mentionPatterns` (or `messages.groupChat.mentionPatterns`).
|
||||
- `messages.responsePrefix`.
|
||||
|
||||
## Addressing / delivery targets
|
||||
|
||||
Prefer `chat_guid` for stable routing:
|
||||
|
||||
- `chat_guid:iMessage;-;+15555550123` (preferred for groups)
|
||||
- `chat_id:123`
|
||||
- `chat_identifier:...`
|
||||
- Direct handles: `+15555550123`, `user@example.com`
|
||||
- If a direct handle does not have an existing DM chat, OpenClaw will create one via `POST /api/v1/chat/new`. This requires the BlueBubbles Private API to be enabled.
|
||||
|
||||
### iMessage vs SMS routing
|
||||
|
||||
When the same handle has both an iMessage and an SMS chat on the Mac (for example a phone number that is iMessage-registered but has also received green-bubble fallbacks), OpenClaw prefers the iMessage chat and never silently downgrades to SMS. To force the SMS chat, use an explicit `sms:` target prefix (for example `sms:+15555550123`). Handles without a matching iMessage chat still send through whatever chat BlueBubbles reports.
|
||||
|
||||
## Security
|
||||
|
||||
- Webhook requests are authenticated by comparing `guid`/`password` query params or headers against `channels.bluebubbles.password`.
|
||||
- Keep the API password and webhook endpoint secret (treat them like credentials).
|
||||
- There is no localhost bypass for BlueBubbles webhook auth. If you proxy webhook traffic, keep the BlueBubbles password on the request end-to-end. `gateway.trustedProxies` does not replace `channels.bluebubbles.password` here. See [Gateway security](/gateway/security#reverse-proxy-configuration).
|
||||
- Enable HTTPS + firewall rules on the BlueBubbles server if exposing it outside your LAN.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- If typing/read events stop working, check the BlueBubbles webhook logs and verify the gateway path matches `channels.bluebubbles.webhookPath`.
|
||||
- Pairing codes expire after one hour; use `openclaw pairing list bluebubbles` and `openclaw pairing approve bluebubbles <code>`.
|
||||
- Reactions require the BlueBubbles private API (`POST /api/v1/message/react`); ensure the server version exposes it.
|
||||
- Edit/unsend require macOS 13+ and a compatible BlueBubbles server version. On macOS 26 (Tahoe), edit is currently broken due to private API changes.
|
||||
- Group icon updates can be flaky on macOS 26 (Tahoe): the API may return success but the new icon does not sync.
|
||||
- OpenClaw auto-hides known-broken actions based on the BlueBubbles server's macOS version. If edit still appears on macOS 26 (Tahoe), disable it manually with `channels.bluebubbles.actions.edit=false`.
|
||||
- `coalesceSameSenderDms` enabled but split-sends (e.g. `Dump` + URL) still arrive as two turns: see the [split-send coalescing troubleshooting](#split-send-coalescing-troubleshooting) checklist - common causes are too-tight debounce window, session-log timestamps misread as webhook arrival, or a reply-quote send (which uses `replyToBody`, not a second webhook).
|
||||
- For status/health info: `openclaw status --all` or `openclaw status --deep`.
|
||||
|
||||
For general channel workflow reference, see [Channels](/channels) and the [Plugins](/tools/plugin) guide.
|
||||
|
||||
## Related
|
||||
|
||||
- [Channel Routing](/channels/channel-routing) - session routing for messages
|
||||
- [Channels Overview](/channels) - all supported channels
|
||||
- [Groups](/channels/groups) - group chat behavior and mention gating
|
||||
- [Pairing](/channels/pairing) - DM authentication and pairing flow
|
||||
- [Security](/gateway/security) - access model and hardening
|
||||
@@ -1211,6 +1211,7 @@ Notes:
|
||||
- OpenClaw also watches receive decrypt failures and auto-recovers by leaving/rejoining the voice channel after repeated failures in a short window.
|
||||
- If receive logs repeatedly show `DecryptionFailed(UnencryptedWhenPassthroughDisabled)` after updating, collect a dependency report and logs. The bundled `@discordjs/voice` line includes the upstream padding fix from discord.js PR #11449, which closed discord.js issue #11419.
|
||||
- `The operation was aborted` receive events are expected when OpenClaw finalizes a captured speaker segment; they are verbose diagnostics, not warnings.
|
||||
- Verbose Discord voice logs include a bounded one-line STT transcript preview for each accepted speaker segment, so debugging shows both the user side and the agent reply side without dumping unbounded transcript text.
|
||||
|
||||
Voice channel pipeline:
|
||||
|
||||
|
||||
@@ -482,10 +482,6 @@ Group inbound payloads set:
|
||||
- `WasMentioned` (mention gating result)
|
||||
- Telegram forum topics also include `MessageThreadId` and `IsForum`.
|
||||
|
||||
Channel-specific notes:
|
||||
|
||||
- BlueBubbles can optionally enrich unnamed macOS group participants from the local Contacts database before populating `GroupMembers`. This is off by default and only runs after normal group gating passes.
|
||||
|
||||
The agent system prompt includes a group intro on the first turn of a new group session. It reminds the model to respond like a human, avoid Markdown tables, minimize empty lines and follow normal chat spacing, and avoid typing literal `\n` sequences. Channel-sourced group names and participant labels are rendered as fenced untrusted metadata, not inline system instructions.
|
||||
|
||||
## iMessage specifics
|
||||
|
||||
@@ -7,18 +7,22 @@ title: "iMessage"
|
||||
---
|
||||
|
||||
<Note>
|
||||
For new OpenClaw iMessage deployments, start here when you can run `imsg` on a signed-in macOS Messages host. BlueBubbles remains available as a legacy fallback for existing setups that depend on its HTTP server, webhooks, or richer private-API actions.
|
||||
For OpenClaw iMessage deployments, use `imsg` on a signed-in macOS Messages host. If your Gateway runs on Linux or Windows, point `channels.imessage.cliPath` at an SSH wrapper that runs `imsg` on the Mac.
|
||||
</Note>
|
||||
|
||||
<Warning>
|
||||
BlueBubbles is deprecated and no longer ships as a bundled OpenClaw channel. Migrate `channels.bluebubbles` configs to `channels.imessage`; OpenClaw now supports iMessage through `imsg` only. If you still need a BlueBubbles-backed bridge, publish or install it as a third-party plugin outside core.
|
||||
</Warning>
|
||||
|
||||
Status: native external CLI integration. Gateway spawns `imsg rpc` and communicates over JSON-RPC on stdio (no separate daemon/port).
|
||||
|
||||
<CardGroup cols={3}>
|
||||
<Card title="BlueBubbles (legacy fallback)" icon="message-circle" href="/channels/bluebubbles">
|
||||
Keep using it for existing BlueBubbles-backed routing; avoid it for new setups when imsg fits.
|
||||
</Card>
|
||||
<Card title="Pairing" icon="link" href="/channels/pairing">
|
||||
iMessage DMs default to pairing mode.
|
||||
</Card>
|
||||
<Card title="Remote Mac" icon="terminal" href="#remote-mac-over-ssh">
|
||||
Use an SSH wrapper when the Gateway is not running on the Messages Mac.
|
||||
</Card>
|
||||
<Card title="Configuration reference" icon="settings" href="/gateway/config-channels#imessage">
|
||||
Full iMessage field reference.
|
||||
</Card>
|
||||
@@ -362,7 +366,23 @@ imsg rpc --help
|
||||
openclaw channels status --probe
|
||||
```
|
||||
|
||||
If probe reports RPC unsupported, update `imsg`.
|
||||
If probe reports RPC unsupported, update `imsg`. If the Gateway is not running on macOS, use the Remote Mac over SSH setup above instead of the default local `imsg` path.
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Gateway is not running on macOS">
|
||||
The default `cliPath: "imsg"` must run on the Mac signed into Messages. On Linux or Windows, set `channels.imessage.cliPath` to a wrapper script that SSHes to that Mac and runs `imsg "$@"`.
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
exec ssh -T messages-mac imsg "$@"
|
||||
```
|
||||
|
||||
Then run:
|
||||
|
||||
```bash
|
||||
openclaw channels status --probe --channel imessage
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
|
||||
@@ -414,7 +434,6 @@ imsg send <handle> "test"
|
||||
- [Configuration reference - iMessage](/gateway/config-channels#imessage)
|
||||
- [Gateway configuration](/gateway/configuration)
|
||||
- [Pairing](/channels/pairing)
|
||||
- [BlueBubbles](/channels/bluebubbles)
|
||||
|
||||
## Related
|
||||
|
||||
|
||||
@@ -21,11 +21,10 @@ Text is supported everywhere; media and reactions vary by channel.
|
||||
|
||||
## Supported channels
|
||||
|
||||
- [BlueBubbles](/channels/bluebubbles) - Legacy iMessage bridge via the BlueBubbles macOS server REST API; deprecated for new OpenClaw setups but still supported for existing configs and richer private-API actions.
|
||||
- [Discord](/channels/discord) - Discord Bot API + Gateway; supports servers, channels, and DMs.
|
||||
- [Feishu](/channels/feishu) - Feishu/Lark bot via WebSocket (bundled plugin).
|
||||
- [Google Chat](/channels/googlechat) - Google Chat API app via HTTP webhook (downloadable plugin).
|
||||
- [iMessage](/channels/imessage) - Native macOS integration via the imsg CLI; preferred for new OpenClaw iMessage setups when host permissions and Messages access fit.
|
||||
- [iMessage](/channels/imessage) - Native macOS integration via the `imsg` CLI on a signed-in Mac; use an SSH wrapper when the Gateway runs elsewhere.
|
||||
- [IRC](/channels/irc) - Classic IRC servers; channels + DMs with pairing/allowlist controls.
|
||||
- [LINE](/channels/line) - LINE Messaging API bot (downloadable plugin).
|
||||
- [Matrix](/channels/matrix) - Matrix protocol (downloadable plugin).
|
||||
|
||||
@@ -45,7 +45,7 @@ That gives first-time setups an explicit owner for privileged commands and exec
|
||||
approval prompts. After an owner exists, later pairing approvals only grant DM
|
||||
access; they do not add more owners.
|
||||
|
||||
Supported channels: `bluebubbles`, `discord`, `feishu`, `googlechat`, `imessage`, `irc`, `line`, `matrix`, `mattermost`, `msteams`, `nextcloud-talk`, `nostr`, `openclaw-weixin`, `signal`, `slack`, `synology-chat`, `telegram`, `twitch`, `whatsapp`, `zalo`, `zalouser`.
|
||||
Supported channels: `discord`, `feishu`, `googlechat`, `imessage`, `irc`, `line`, `matrix`, `mattermost`, `msteams`, `nextcloud-talk`, `nostr`, `openclaw-weixin`, `signal`, `slack`, `synology-chat`, `telegram`, `twitch`, `whatsapp`, `zalo`, `zalouser`.
|
||||
|
||||
### Reusable sender groups
|
||||
|
||||
@@ -209,6 +209,5 @@ Stored under `~/.openclaw/devices/`:
|
||||
- WhatsApp: [WhatsApp](/channels/whatsapp)
|
||||
- Signal: [Signal](/channels/signal)
|
||||
- iMessage: [iMessage](/channels/imessage)
|
||||
- BlueBubbles (legacy iMessage bridge): [BlueBubbles](/channels/bluebubbles)
|
||||
- Discord: [Discord](/channels/discord)
|
||||
- Slack: [Slack](/channels/slack)
|
||||
|
||||
@@ -82,20 +82,19 @@ Full troubleshooting: [Discord troubleshooting](/channels/discord#troubleshootin
|
||||
|
||||
Full troubleshooting: [Slack troubleshooting](/channels/slack#troubleshooting)
|
||||
|
||||
## iMessage and BlueBubbles
|
||||
## iMessage
|
||||
|
||||
### iMessage and BlueBubbles failure signatures
|
||||
### iMessage failure signatures
|
||||
|
||||
| Symptom | Fastest check | Fix |
|
||||
| -------------------------------- | ----------------------------------------------------------------------- | ----------------------------------------------------- |
|
||||
| No inbound events | Verify webhook/server reachability and app permissions | Fix webhook URL or BlueBubbles server state. |
|
||||
| Can send but no receive on macOS | Check macOS privacy permissions for Messages automation | Re-grant TCC permissions and restart channel process. |
|
||||
| DM sender blocked | `openclaw pairing list imessage` or `openclaw pairing list bluebubbles` | Approve pairing or update allowlist. |
|
||||
| Symptom | Fastest check | Fix |
|
||||
| ------------------------------------ | ------------------------------------------------------- | --------------------------------------------------------------------- |
|
||||
| `imsg` missing or fails on non-macOS | `openclaw channels status --probe --channel imessage` | Run OpenClaw on the Messages Mac or use an SSH wrapper for `cliPath`. |
|
||||
| Can send but no receive on macOS | Check macOS privacy permissions for Messages automation | Re-grant TCC permissions and restart channel process. |
|
||||
| DM sender blocked | `openclaw pairing list imessage` | Approve pairing or update allowlist. |
|
||||
|
||||
Full troubleshooting:
|
||||
|
||||
- [iMessage troubleshooting](/channels/imessage#troubleshooting)
|
||||
- [BlueBubbles troubleshooting](/channels/bluebubbles#troubleshooting)
|
||||
|
||||
## Signal
|
||||
|
||||
|
||||
@@ -42,7 +42,8 @@ Quick rule:
|
||||
| ACP area | Status | Notes |
|
||||
| --------------------------------------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `initialize`, `newSession`, `prompt`, `cancel` | Implemented | Core bridge flow over stdio to Gateway chat/send + abort. |
|
||||
| `listSessions`, slash commands | Implemented | Session list works against Gateway session state; commands are advertised via `available_commands_update`. |
|
||||
| `listSessions`, slash commands | Implemented | Session list works against Gateway session state with bounded cursor pagination and `cwd` filtering where Gateway session rows carry workspace metadata; commands are advertised via `available_commands_update`. |
|
||||
| `resumeSession`, `closeSession` | Implemented | Resume rebinds an ACP session to an existing Gateway session without replaying history. Close cancels active bridge work, resolves pending prompts as cancelled, and releases bridge session state. |
|
||||
| `loadSession` | Partial | Rebinds the ACP session to a Gateway session key and replays stored user/assistant text history. Tool/system history is not reconstructed yet. |
|
||||
| Prompt content (`text`, embedded `resource`, images) | Partial | Text/resources are flattened into chat input; images become Gateway attachments. |
|
||||
| Session modes | Partial | `session/set_mode` is supported and the bridge exposes initial Gateway-backed session controls for thought level, tool verbosity, reasoning, usage detail, and elevated actions. Broader ACP-native mode/config surfaces are still out of scope. |
|
||||
@@ -120,6 +121,50 @@ Permission model (client debug mode):
|
||||
- Server-provided `toolCall.kind` is treated as untrusted metadata (not an authorization source).
|
||||
- This ACP bridge policy is separate from ACPX harness permissions. If you run OpenClaw through the `acpx` backend, `plugins.entries.acpx.config.permissionMode=approve-all` is the break-glass "yolo" switch for that harness session.
|
||||
|
||||
## Protocol smoke testing
|
||||
|
||||
For protocol-level debugging, start a Gateway with isolated state and drive
|
||||
`openclaw acp` over stdio with an ACP JSON-RPC client. Cover `initialize`,
|
||||
`session/new`, `session/list` with an absolute `cwd`, `session/resume`,
|
||||
`session/close`, duplicate close, and missing resume.
|
||||
|
||||
The proof should include the advertised lifecycle capabilities, a Gateway-backed
|
||||
session row, update notifications, and the Gateway `sessions.list` log:
|
||||
|
||||
```json
|
||||
{
|
||||
"initialize": {
|
||||
"protocolVersion": 1,
|
||||
"agentCapabilities": {
|
||||
"sessionCapabilities": {
|
||||
"list": {},
|
||||
"resume": {},
|
||||
"close": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"listSessions": {
|
||||
"sessions": [
|
||||
{
|
||||
"sessionId": "agent:main:acp-smoke",
|
||||
"cwd": "/path/to/workspace",
|
||||
"_meta": {
|
||||
"sessionKey": "agent:main:acp-smoke",
|
||||
"kind": "direct"
|
||||
}
|
||||
}
|
||||
],
|
||||
"nextCursor": null
|
||||
},
|
||||
"notifications": ["session_info_update", "available_commands_update", "usage_update"],
|
||||
"gatewayLogTail": ["[gateway] ready", "[ws] ⇄ res ✓ sessions.list 305ms"]
|
||||
}
|
||||
```
|
||||
|
||||
Avoid using `openclaw gateway call sessions.list` as the only ACP proof. That
|
||||
CLI path may request a fresh-token operator scope upgrade; ACP bridge
|
||||
correctness is proven by ACP stdio frames plus the Gateway `sessions.list` log.
|
||||
|
||||
## How to use this
|
||||
|
||||
Use ACP when an IDE (or other client) speaks Agent Client Protocol and you want
|
||||
|
||||
@@ -19,6 +19,7 @@ Related docs:
|
||||
|
||||
```bash
|
||||
openclaw channels list
|
||||
openclaw channels list --all
|
||||
openclaw channels status
|
||||
openclaw channels capabilities
|
||||
openclaw channels capabilities --channel discord --target channel:123
|
||||
@@ -27,6 +28,8 @@ openclaw channels resolve --channel slack "#general" "@jane"
|
||||
openclaw channels logs --channel all
|
||||
```
|
||||
|
||||
`channels list` shows chat channels only: configured accounts by default, with `installed`, `configured`, and `enabled` status tags per account. Pass `--all` to also surface bundled channels that have no configured account yet and installable catalog channels that are not yet on disk. Auth providers (OAuth + API keys) and model-provider usage/quota snapshots are no longer printed here; use `openclaw models auth list` for provider auth profiles and `openclaw status` or `openclaw models list` for usage.
|
||||
|
||||
## Status / capabilities / resolve / logs
|
||||
|
||||
- `channels status`: `--probe`, `--timeout <ms>`, `--json`
|
||||
@@ -109,7 +112,7 @@ openclaw channels logout --channel whatsapp
|
||||
|
||||
- Run `openclaw status --deep` for a broad probe.
|
||||
- Use `openclaw doctor` for guided fixes.
|
||||
- `openclaw channels list` prints `Claude: HTTP 403 ... user:profile` → usage snapshot needs the `user:profile` scope. Use `--no-usage`, or provide a claude.ai session key (`CLAUDE_WEB_SESSION_KEY` / `CLAUDE_WEB_COOKIE`), or re-auth via Claude CLI.
|
||||
- `openclaw channels list` no longer prints model provider usage/quota snapshots. For those, use `openclaw status` (overview) or `openclaw models list` (per-provider).
|
||||
- `openclaw channels status` falls back to config-only summaries when the gateway is unreachable. If a supported channel credential is configured via SecretRef but unavailable in the current command path, it reports that account as configured with degraded notes instead of showing it as not configured.
|
||||
|
||||
## Capabilities probe
|
||||
|
||||
@@ -220,6 +220,8 @@ openclaw cron runs --id <job-id> --limit 50
|
||||
|
||||
`openclaw cron list` shows all matching jobs by default. Pass `--agent <id>` to show only jobs whose effective normalized agent id matches; jobs without a stored agent id count as the configured default agent.
|
||||
|
||||
`cron list --json` and `cron show <job-id> --json` include a top-level `status` field on each job, computed from `enabled`, `state.runningAtMs`, and `state.lastRunStatus`. Values: `disabled`, `running`, `ok`, `error`, `skipped`, or `idle`. This mirrors the human-readable status column so external tooling can read job state without re-deriving it.
|
||||
|
||||
`cron runs` entries include delivery diagnostics with the intended cron target, the resolved target, message-tool sends, fallback use, and delivered state.
|
||||
|
||||
Agent and session retargeting:
|
||||
|
||||
@@ -43,8 +43,8 @@ Probe rows can come from auth profiles, env credentials, or `models.json`.
|
||||
For Codex OAuth troubleshooting, `openclaw models status`,
|
||||
`openclaw models auth list --provider openai-codex`, and
|
||||
`openclaw config get agents.defaults.model --json` are the quickest way to
|
||||
confirm whether an agent is using `openai-codex/*` through PI or `openai/*`
|
||||
through the native Codex runtime. See [OpenAI provider setup](/providers/openai#check-and-recover-codex-oauth-routing).
|
||||
confirm whether an agent has a usable `openai-codex` auth profile for
|
||||
`openai/*` through the native Codex runtime. See [OpenAI provider setup](/providers/openai#check-and-recover-codex-oauth-routing).
|
||||
|
||||
Notes:
|
||||
|
||||
|
||||
@@ -23,6 +23,11 @@ event loop. The CLI returns the newest 100 sessions by default; pass
|
||||
need the full store. JSON responses include `totalCount`, `limitApplied`, and
|
||||
`hasMore` when callers need to show that more rows exist.
|
||||
|
||||
RPC clients can pass `configuredAgentsOnly: true` to keep the broad combined
|
||||
discovery source but return only rows for agents currently present in config.
|
||||
Control UI uses that mode by default so deleted or disk-only agent stores do
|
||||
not reappear in the Sessions view.
|
||||
|
||||
```bash
|
||||
openclaw sessions
|
||||
openclaw sessions --agent work
|
||||
|
||||
@@ -41,19 +41,19 @@ There are two runtime families:
|
||||
|
||||
Most confusion comes from several different surfaces sharing the Codex name:
|
||||
|
||||
| Surface | OpenClaw name/config | What it does |
|
||||
| ---------------------------------------------------- | ------------------------------------------ | ---------------------------------------------------------------------------------------------------------- |
|
||||
| Native Codex app-server runtime | `openai/*` plus `agentRuntime.id: "codex"` | Runs the embedded agent turn through Codex app-server. This is the usual ChatGPT/Codex subscription setup. |
|
||||
| Codex OAuth provider route | `openai-codex/*` model refs | Uses ChatGPT/Codex subscription OAuth through the normal OpenClaw PI runner. |
|
||||
| Codex ACP adapter | `runtime: "acp"`, `agentId: "codex"` | Runs Codex through the external ACP/acpx control plane. Use only when ACP/acpx is explicitly asked. |
|
||||
| Native Codex chat-control command set | `/codex ...` | Binds, resumes, steers, stops, and inspects Codex app-server threads from chat. |
|
||||
| OpenAI Platform API route for GPT/Codex-style models | `openai/*` model refs | Uses OpenAI API-key auth unless a runtime override, such as `agentRuntime.id: "codex"`, runs the turn. |
|
||||
| Surface | OpenClaw name/config | What it does |
|
||||
| ------------------------------------------------ | ------------------------------------ | -------------------------------------------------------------------------------------------------------------- |
|
||||
| Native Codex app-server runtime | `openai/*` model refs | Runs OpenAI embedded agent turns through Codex app-server. This is the usual ChatGPT/Codex subscription setup. |
|
||||
| Codex OAuth auth profiles | `openai-codex` auth provider | Stores ChatGPT/Codex subscription auth that the Codex app-server harness consumes. |
|
||||
| Codex ACP adapter | `runtime: "acp"`, `agentId: "codex"` | Runs Codex through the external ACP/acpx control plane. Use only when ACP/acpx is explicitly asked. |
|
||||
| Native Codex chat-control command set | `/codex ...` | Binds, resumes, steers, stops, and inspects Codex app-server threads from chat. |
|
||||
| OpenAI Platform API route for non-agent surfaces | `openai/*` plus API-key auth | Used for direct OpenAI APIs such as images, embeddings, speech, and realtime. |
|
||||
|
||||
Those surfaces are intentionally independent. Enabling the `codex` plugin makes
|
||||
the native app-server features available; it does not rewrite
|
||||
`openai-codex/*` into `openai/*`, does not change existing sessions, and does
|
||||
not make ACP the Codex default. Selecting `openai-codex/*` means "use the Codex
|
||||
OAuth provider route" unless you separately force a runtime.
|
||||
the native app-server features available; `openclaw doctor --fix` owns legacy
|
||||
`openai-codex/*` route repair and stale session pin cleanup. Selecting
|
||||
`openai/*` for an agent model now means "run this through Codex" unless a
|
||||
non-agent OpenAI API surface is being used.
|
||||
|
||||
The common ChatGPT/Codex subscription setup uses Codex OAuth for auth, but keeps
|
||||
the model ref as `openai/*` and selects the `codex` runtime:
|
||||
@@ -63,9 +63,6 @@ the model ref as `openai/*` and selects the `codex` runtime:
|
||||
agents: {
|
||||
defaults: {
|
||||
model: "openai/gpt-5.5",
|
||||
agentRuntime: {
|
||||
id: "codex",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -88,20 +85,23 @@ This is the agent-facing decision tree:
|
||||
1. If the user asks for **Codex bind/control/thread/resume/steer/stop**, use the
|
||||
native `/codex` command surface when the bundled `codex` plugin is enabled.
|
||||
2. If the user asks for **Codex as the embedded runtime** or wants the normal
|
||||
subscription-backed Codex agent experience, use
|
||||
`openai/<model>` with `agentRuntime.id: "codex"`.
|
||||
3. If the user asks for **Codex OAuth/subscription auth on the normal OpenClaw
|
||||
runner**, use `openai-codex/<model>` and leave the runtime as PI.
|
||||
4. If the user explicitly says **ACP**, **acpx**, or **Codex ACP adapter**, use
|
||||
subscription-backed Codex agent experience, use `openai/<model>`.
|
||||
3. If the user explicitly chooses **PI for an OpenAI model**, keep the model ref
|
||||
as `openai/<model>` and set `agentRuntime.id: "pi"`. A selected
|
||||
`openai-codex` auth profile is routed internally through PI's legacy
|
||||
Codex-auth transport.
|
||||
4. If legacy config still contains **`openai-codex/*` model refs**, repair it to
|
||||
`openai/<model>` with `openclaw doctor --fix`.
|
||||
5. If the user explicitly says **ACP**, **acpx**, or **Codex ACP adapter**, use
|
||||
ACP with `runtime: "acp"` and `agentId: "codex"`.
|
||||
5. If the request is for **Claude Code, Gemini CLI, OpenCode, Cursor, Droid, or
|
||||
6. If the request is for **Claude Code, Gemini CLI, OpenCode, Cursor, Droid, or
|
||||
another external harness**, use ACP/acpx, not the native sub-agent runtime.
|
||||
|
||||
| You mean... | Use... |
|
||||
| --------------------------------------- | -------------------------------------------- |
|
||||
| Codex app-server chat/thread control | `/codex ...` from the bundled `codex` plugin |
|
||||
| Codex app-server embedded agent runtime | `agentRuntime.id: "codex"` |
|
||||
| OpenAI Codex OAuth on the PI runner | `openai-codex/*` model refs |
|
||||
| Codex app-server embedded agent runtime | `openai/*` agent model refs |
|
||||
| OpenAI Codex OAuth | `openai-codex` auth profiles |
|
||||
| Claude Code or other external harness | ACP/acpx |
|
||||
|
||||
For the OpenAI-family prefix split, see [OpenAI](/providers/openai) and
|
||||
@@ -166,17 +166,17 @@ Legacy refs such as `claude-cli/claude-opus-4-7` remain supported for
|
||||
compatibility, but new config should keep the provider/model canonical and put
|
||||
the execution backend in `agentRuntime.id`.
|
||||
|
||||
`auto` mode is intentionally conservative. Plugin runtimes can claim
|
||||
provider/model pairs they understand, but the Codex plugin does not claim the
|
||||
`openai-codex` provider in `auto` mode. That keeps
|
||||
`openai-codex/*` as the explicit PI Codex OAuth route and avoids silently
|
||||
moving subscription-auth configs onto the native app-server harness.
|
||||
`auto` mode is intentionally conservative for most providers. OpenAI agent
|
||||
models are the exception: unset runtime and `auto` both resolve to the Codex
|
||||
harness. Explicit PI runtime config remains an opt-in compatibility route for
|
||||
`openai/*` agent turns; when paired with a selected `openai-codex` auth profile,
|
||||
OpenClaw routes PI internally through the legacy Codex-auth transport while
|
||||
keeping the public model ref as `openai/*`. Stale OpenAI PI session pins without
|
||||
explicit config are repaired back to Codex.
|
||||
|
||||
If `openclaw doctor` warns that the `codex` plugin is enabled while
|
||||
`openai-codex/*` still routes through PI, treat that as a diagnosis, not a
|
||||
migration. Keep the config unchanged when PI Codex OAuth is what you want.
|
||||
Switch to `openai/<model>` plus `agentRuntime.id: "codex"` only when you want native
|
||||
Codex app-server execution.
|
||||
`openai-codex/*` remains in config, treat that as legacy route state. Run
|
||||
`openclaw doctor --fix` to rewrite it to `openai/*` with the Codex runtime.
|
||||
|
||||
## Compatibility contract
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ title: "Features"
|
||||
**Channels:**
|
||||
|
||||
- Built-in channels include Discord, Google Chat, iMessage, IRC, Signal, Slack, Telegram, WebChat, and WhatsApp
|
||||
- Bundled plugin channels include BlueBubbles as a legacy iMessage bridge, Feishu, LINE, Matrix, Mattermost, Microsoft Teams, Nextcloud Talk, Nostr, QQ Bot, Synology Chat, Tlon, Twitch, Zalo, and Zalo Personal
|
||||
- Bundled plugin channels include Feishu, LINE, Matrix, Mattermost, Microsoft Teams, Nextcloud Talk, Nostr, QQ Bot, Synology Chat, Tlon, Twitch, Zalo, and Zalo Personal
|
||||
- Optional separately installed channel plugins include Voice Call and third-party packages such as WeChat
|
||||
- Third-party channel plugins can extend the Gateway further, such as WeChat
|
||||
- Group chat support with mention-based activation
|
||||
|
||||
@@ -763,7 +763,7 @@ Concrete migration hazards to preserve:
|
||||
- Telegram silent fallback delivery must deliver the full projected payload
|
||||
array. A single-payload shortcut can drop additional fallback payloads after
|
||||
projection.
|
||||
- LINE, BlueBubbles, Zalo, Nostr, and other existing assembled/helper paths may
|
||||
- LINE, Zalo, Nostr, and other existing assembled/helper paths may
|
||||
have reply-token handling, media proxying, sent-message caches, loading/status
|
||||
cleanup, or callback-only targets. They stay on channel-owned delivery until
|
||||
those semantics are represented by the send adapter and verified by tests.
|
||||
@@ -854,30 +854,30 @@ Core policy:
|
||||
|
||||
## Channel mapping
|
||||
|
||||
| Channel | Target migration |
|
||||
| ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Telegram | Receive ack policy plus durable final sends. Live adapter owns send plus edit preview, stale preview final send, topics, quote-reply preview skip, media fallback, and retry-after handling. |
|
||||
| Discord | Send adapter wraps existing durable payload delivery. Live adapter owns draft edit, progress draft, media/error preview cancel, reply target preservation, and message id receipts. Audit bot-authored gateway-failure echoes in shared rooms; use an outbound registry or other native equivalent if Discord cannot carry origin metadata on normal messages. |
|
||||
| Slack | Send adapter handles normal chat posts. Live adapter chooses native stream when thread shape supports it, otherwise draft preview. Receipts preserve thread timestamps. Origin adapter maps OpenClaw gateway failures to Slack `chat.postMessage.metadata` and drops tagged bot-room echoes before `allowBots` authorization. |
|
||||
| WhatsApp | Send adapter owns text/media send with durable final intents. Receive adapter handles group mention and sender identity. Live can stay absent until WhatsApp has an editable transport. |
|
||||
| Matrix | Live adapter owns draft event edits, finalization, redaction, encrypted media constraints, and reply-target mismatch fallback. Receive adapter owns encrypted event hydration and dedupe. Origin adapter should encode OpenClaw gateway-failure origin into Matrix event content and drop configured-bot room echoes before `allowBots` handling. |
|
||||
| Mattermost | Live adapter owns one draft post, progress/tool folding, finalization in place, and fresh-send fallback. |
|
||||
| Microsoft Teams | Live adapter owns native progress and block stream behavior. Send adapter owns activities and attachment/card receipts. |
|
||||
| Feishu | Render adapter owns text/card/raw rendering. Live adapter owns streaming cards and duplicate final suppression. Send adapter owns comments, topic sessions, media, and voice suppression. |
|
||||
| QQ Bot | Live adapter owns C2C streaming, accumulator timeout, and fallback final send. Render adapter owns media tags and text-as-voice. |
|
||||
| Signal | Simple receive plus send adapter. No live adapter unless signal-cli adds reliable edit support. |
|
||||
| iMessage and BlueBubbles | Simple receive plus send adapter. iMessage send must preserve monitor echo-cache population before durable finals can bypass monitor delivery. BlueBubbles-specific typing, reactions, and attachments remain adapter capabilities. |
|
||||
| Google Chat | Simple receive plus send adapter with thread relation mapped to spaces and thread ids. Audit `allowBots=true` room behavior for tagged OpenClaw gateway-failure echoes. |
|
||||
| LINE | Simple receive plus send adapter with reply-token constraints modeled as target/relation capability. |
|
||||
| Nextcloud Talk | SDK receive bridge plus send adapter. |
|
||||
| IRC | Simple receive plus send adapter, no durable edit receipts. |
|
||||
| Nostr | Receive plus send adapter for encrypted DMs; receipts are event ids. |
|
||||
| QA Channel | Contract-test adapter for receive, send, live, retry, and recovery behavior. |
|
||||
| Synology Chat | Simple receive plus send adapter. |
|
||||
| Tlon | Send adapter must preserve model-signature rendering and participated-thread tracking before generic durable final delivery is enabled. |
|
||||
| Twitch | Simple receive plus send adapter with rate-limit classification. |
|
||||
| Zalo | Simple receive plus send adapter. |
|
||||
| Zalo Personal | Simple receive plus send adapter. |
|
||||
| Channel | Target migration |
|
||||
| --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Telegram | Receive ack policy plus durable final sends. Live adapter owns send plus edit preview, stale preview final send, topics, quote-reply preview skip, media fallback, and retry-after handling. |
|
||||
| Discord | Send adapter wraps existing durable payload delivery. Live adapter owns draft edit, progress draft, media/error preview cancel, reply target preservation, and message id receipts. Audit bot-authored gateway-failure echoes in shared rooms; use an outbound registry or other native equivalent if Discord cannot carry origin metadata on normal messages. |
|
||||
| Slack | Send adapter handles normal chat posts. Live adapter chooses native stream when thread shape supports it, otherwise draft preview. Receipts preserve thread timestamps. Origin adapter maps OpenClaw gateway failures to Slack `chat.postMessage.metadata` and drops tagged bot-room echoes before `allowBots` authorization. |
|
||||
| WhatsApp | Send adapter owns text/media send with durable final intents. Receive adapter handles group mention and sender identity. Live can stay absent until WhatsApp has an editable transport. |
|
||||
| Matrix | Live adapter owns draft event edits, finalization, redaction, encrypted media constraints, and reply-target mismatch fallback. Receive adapter owns encrypted event hydration and dedupe. Origin adapter should encode OpenClaw gateway-failure origin into Matrix event content and drop configured-bot room echoes before `allowBots` handling. |
|
||||
| Mattermost | Live adapter owns one draft post, progress/tool folding, finalization in place, and fresh-send fallback. |
|
||||
| Microsoft Teams | Live adapter owns native progress and block stream behavior. Send adapter owns activities and attachment/card receipts. |
|
||||
| Feishu | Render adapter owns text/card/raw rendering. Live adapter owns streaming cards and duplicate final suppression. Send adapter owns comments, topic sessions, media, and voice suppression. |
|
||||
| QQ Bot | Live adapter owns C2C streaming, accumulator timeout, and fallback final send. Render adapter owns media tags and text-as-voice. |
|
||||
| Signal | Simple receive plus send adapter. No live adapter unless signal-cli adds reliable edit support. |
|
||||
| iMessage | Simple receive plus send adapter. iMessage send must preserve monitor echo-cache population before durable finals can bypass monitor delivery. |
|
||||
| Google Chat | Simple receive plus send adapter with thread relation mapped to spaces and thread ids. Audit `allowBots=true` room behavior for tagged OpenClaw gateway-failure echoes. |
|
||||
| LINE | Simple receive plus send adapter with reply-token constraints modeled as target/relation capability. |
|
||||
| Nextcloud Talk | SDK receive bridge plus send adapter. |
|
||||
| IRC | Simple receive plus send adapter, no durable edit receipts. |
|
||||
| Nostr | Receive plus send adapter for encrypted DMs; receipts are event ids. |
|
||||
| QA Channel | Contract-test adapter for receive, send, live, retry, and recovery behavior. |
|
||||
| Synology Chat | Simple receive plus send adapter. |
|
||||
| Tlon | Send adapter must preserve model-signature rendering and participated-thread tracking before generic durable final delivery is enabled. |
|
||||
| Twitch | Simple receive plus send adapter with rate-limit classification. |
|
||||
| Zalo | Simple receive plus send adapter. |
|
||||
| Zalo Personal | Simple receive plus send adapter. |
|
||||
|
||||
## Migration plan
|
||||
|
||||
@@ -1035,7 +1035,7 @@ Channel tests:
|
||||
- Discord prepared dispatcher finals route through the send context before docs
|
||||
or changelog claim Discord final-reply durability.
|
||||
- iMessage durable final sends populate the monitor sent-message echo cache.
|
||||
- LINE, BlueBubbles, Zalo, and Nostr legacy delivery paths are not bypassed by
|
||||
- LINE, Zalo, and Nostr legacy delivery paths are not bypassed by
|
||||
generic durable send until their adapter parity tests exist.
|
||||
- Direct-DM/Nostr callback delivery remains authoritative unless explicitly
|
||||
migrated to a complete message target and replay-safe send adapter.
|
||||
|
||||
@@ -59,7 +59,7 @@ Config (global default + per-channel overrides):
|
||||
Notes:
|
||||
|
||||
- Debounce applies to **text-only** messages; media/attachments flush immediately.
|
||||
- Control commands bypass debouncing so they remain standalone — **except** when a channel explicitly opts in to same-sender DM coalescing (e.g. [BlueBubbles `coalesceSameSenderDms`](/channels/bluebubbles#coalescing-split-send-dms-command--url-in-one-composition)), where DM commands wait inside the debounce window so a split-send payload can join the same agent turn.
|
||||
- Control commands bypass debouncing so they remain standalone. Channels that explicitly opt in to same-sender DM coalescing can keep DM commands inside the debounce window so a split-send payload can join the same agent turn.
|
||||
|
||||
## Sessions and devices
|
||||
|
||||
|
||||
@@ -262,7 +262,7 @@ Common channels supporting this pattern include:
|
||||
|
||||
- `whatsapp`, `telegram`, `discord`, `slack`, `signal`, `imessage`
|
||||
- `irc`, `line`, `googlechat`, `mattermost`, `matrix`, `nextcloud-talk`
|
||||
- `bluebubbles`, `zalo`, `zalouser`, `nostr`, `feishu`
|
||||
- `zalo`, `zalouser`, `nostr`, `feishu`
|
||||
|
||||
## Concepts
|
||||
|
||||
|
||||
@@ -52,6 +52,10 @@
|
||||
]
|
||||
},
|
||||
"redirects": [
|
||||
{
|
||||
"source": "/channels/bluebubbles",
|
||||
"destination": "/channels/imessage"
|
||||
},
|
||||
{
|
||||
"source": "/install/migrating-matrix",
|
||||
"destination": "/channels/matrix-migration"
|
||||
@@ -1059,7 +1063,6 @@
|
||||
"channels/msteams",
|
||||
"channels/googlechat",
|
||||
"channels/imessage",
|
||||
"channels/bluebubbles",
|
||||
"channels/matrix",
|
||||
"channels/matrix-migration",
|
||||
"channels/matrix-push-rules"
|
||||
|
||||
@@ -110,6 +110,8 @@ openclaw models auth paste-token --provider openrouter
|
||||
|
||||
OpenClaw expects the canonical `version` + `profiles` shape at runtime. If an older install still has a flat file such as `{ "openrouter": { "apiKey": "..." } }`, run `openclaw doctor --fix` to rewrite it as an `openrouter:default` API-key profile; doctor keeps a `.legacy-flat.*.bak` copy beside the original. Endpoint details such as `baseUrl`, `api`, model ids, headers, and timeouts belong under `models.providers.<id>` in `openclaw.json` or `models.json`, not in `auth-profiles.json`.
|
||||
|
||||
External auth routes such as Bedrock `auth: "aws-sdk"` are also not credentials. If you want a named Bedrock route, put `auth.profiles.<id>.mode: "aws-sdk"` in `openclaw.json`; do not write `type: "aws-sdk"` into `auth-profiles.json`. `openclaw doctor --fix` moves legacy AWS SDK markers from the credential store into config metadata.
|
||||
|
||||
Auth profile refs are also supported for static credentials:
|
||||
|
||||
- `api_key` credentials can use `keyRef: { source, provider, id }`
|
||||
|
||||
@@ -387,7 +387,7 @@ Time format in system prompt. Default: `auto` (OS preference).
|
||||
- `toolProgressDetail`: detail mode for `/verbose` tool summaries and progress-draft tool lines. Values: `"explain"` (default, compact human labels) or `"raw"` (append raw command/detail when available). Per-agent `agents.list[].toolProgressDetail` overrides this default.
|
||||
- `reasoningDefault`: default reasoning visibility for agents. Values: `"off"`, `"on"`, `"stream"`. Per-agent `agents.list[].reasoningDefault` overrides this default. Configured reasoning defaults are only applied for owners, authorized senders, or operator-admin gateway contexts when no per-message or session reasoning override is set.
|
||||
- `elevatedDefault`: default elevated-output level for agents. Values: `"off"`, `"on"`, `"ask"`, `"full"`. Default: `"on"`.
|
||||
- `model.primary`: format `provider/model` (e.g. `openai/gpt-5.5` for API-key access or `openai-codex/gpt-5.5` for Codex OAuth). If you omit the provider, OpenClaw tries an alias first, then a unique configured-provider match for that exact model id, and only then falls back to the configured default provider (deprecated compatibility behavior, so prefer explicit `provider/model`). If that provider no longer exposes the configured default model, OpenClaw falls back to the first configured provider/model instead of surfacing a stale removed-provider default.
|
||||
- `model.primary`: format `provider/model` (e.g. `openai/gpt-5.5` for OpenAI API-key or Codex OAuth access). If you omit the provider, OpenClaw tries an alias first, then a unique configured-provider match for that exact model id, and only then falls back to the configured default provider (deprecated compatibility behavior, so prefer explicit `provider/model`). If that provider no longer exposes the configured default model, OpenClaw falls back to the first configured provider/model instead of surfacing a stale removed-provider default.
|
||||
- `models`: the configured model catalog and allowlist for `/model`. Each entry can include `alias` (shortcut) and `params` (provider-specific, for example `temperature`, `maxTokens`, `cacheRetention`, `context1m`, `responsesServerCompaction`, `responsesCompactThreshold`, `chat_template_kwargs`, `extra_body`/`extraBody`).
|
||||
- Safe edits: use `openclaw config set agents.defaults.models '<json>' --strict-json --merge` to add entries. `config set` refuses replacements that would remove existing allowlist entries unless you pass `--replace`.
|
||||
- Provider-scoped configure/onboarding flows merge selected provider models into this map and preserve unrelated providers already configured.
|
||||
@@ -426,24 +426,24 @@ model, see [Agent runtimes](/concepts/agent-runtimes).
|
||||
- `id`: `"auto"`, `"pi"`, a registered plugin harness id, or a supported CLI backend alias. The bundled Codex plugin registers `codex`; the bundled Anthropic plugin provides the `claude-cli` CLI backend.
|
||||
- `id: "auto"` lets registered plugin harnesses claim supported turns and uses PI when no harness matches. An explicit plugin runtime such as `id: "codex"` requires that harness and fails closed if it is unavailable or fails.
|
||||
- Environment override: `OPENCLAW_AGENT_RUNTIME=<id|auto|pi>` overrides `id` for that process.
|
||||
- For Codex-only deployments, set `model: "openai/gpt-5.5"` and `agentRuntime.id: "codex"`.
|
||||
- OpenAI agent models use the Codex harness by default; `agentRuntime.id: "codex"` remains valid when you want to make that explicit.
|
||||
- For Claude CLI deployments, prefer `model: "anthropic/claude-opus-4-7"` plus `agentRuntime.id: "claude-cli"`. Legacy `claude-cli/claude-opus-4-7` model refs still work for compatibility, but new config should keep provider/model selection canonical and put the execution backend in `agentRuntime.id`.
|
||||
- Older runtime-policy keys are rewritten to `agentRuntime` by `openclaw doctor --fix`.
|
||||
- Harness choice is pinned per session id after the first embedded run. Config/env changes affect new or reset sessions, not an existing transcript. Legacy sessions with transcript history but no recorded pin are treated as PI-pinned. `/status` reports the effective runtime, for example `Runtime: OpenClaw Pi Default` or `Runtime: OpenAI Codex`.
|
||||
- Harness choice is pinned per session id after the first embedded run. Config/env changes affect new or reset sessions, not an existing transcript. Legacy OpenAI sessions with transcript history but no recorded pin use Codex; stale OpenAI PI pins can be repaired with `openclaw doctor --fix`. `/status` reports the effective runtime, for example `Runtime: OpenClaw Pi Default` or `Runtime: OpenAI Codex`.
|
||||
- This only controls text agent-turn execution. Media generation, vision, PDF, music, video, and TTS still use their provider/model settings.
|
||||
|
||||
**Built-in alias shorthands** (only apply when the model is in `agents.defaults.models`):
|
||||
|
||||
| Alias | Model |
|
||||
| ------------------- | ------------------------------------------ |
|
||||
| `opus` | `anthropic/claude-opus-4-6` |
|
||||
| `sonnet` | `anthropic/claude-sonnet-4-6` |
|
||||
| `gpt` | `openai/gpt-5.5` or `openai-codex/gpt-5.5` |
|
||||
| `gpt-mini` | `openai/gpt-5.4-mini` |
|
||||
| `gpt-nano` | `openai/gpt-5.4-nano` |
|
||||
| `gemini` | `google/gemini-3.1-pro-preview` |
|
||||
| `gemini-flash` | `google/gemini-3-flash-preview` |
|
||||
| `gemini-flash-lite` | `google/gemini-3.1-flash-lite-preview` |
|
||||
| Alias | Model |
|
||||
| ------------------- | -------------------------------------- |
|
||||
| `opus` | `anthropic/claude-opus-4-6` |
|
||||
| `sonnet` | `anthropic/claude-sonnet-4-6` |
|
||||
| `gpt` | `openai/gpt-5.5` |
|
||||
| `gpt-mini` | `openai/gpt-5.4-mini` |
|
||||
| `gpt-nano` | `openai/gpt-5.4-nano` |
|
||||
| `gemini` | `google/gemini-3.1-pro-preview` |
|
||||
| `gemini-flash` | `google/gemini-3-flash-preview` |
|
||||
| `gemini-flash-lite` | `google/gemini-3.1-flash-lite-preview` |
|
||||
|
||||
Your configured aliases always win over defaults.
|
||||
|
||||
@@ -1290,7 +1290,7 @@ Variables are case-insensitive. `{think}` is an alias for `{thinkingLevel}`.
|
||||
- Per-channel overrides: `channels.<channel>.ackReaction`, `channels.<channel>.accounts.<id>.ackReaction`.
|
||||
- Resolution order: account → channel → `messages.ackReaction` → identity fallback.
|
||||
- Scope: `group-mentions` (default), `group-all`, `direct`, `all`.
|
||||
- `removeAckAfterReply`: removes ack after reply on reaction-capable channels such as Slack, Discord, Telegram, WhatsApp, and BlueBubbles.
|
||||
- `removeAckAfterReply`: removes ack after reply on reaction-capable channels such as Slack, Discord, Telegram, WhatsApp, and iMessage.
|
||||
- `messages.statusReactions.enabled`: enables lifecycle status reactions on Slack, Discord, and Telegram.
|
||||
On Slack and Discord, unset keeps status reactions enabled when ack reactions are active.
|
||||
On Telegram, set it explicitly to `true` to enable lifecycle status reactions.
|
||||
|
||||
@@ -581,32 +581,14 @@ When Mattermost native commands are enabled:
|
||||
- `channels.signal.configWrites`: allow or deny Signal-initiated config writes.
|
||||
- Optional `channels.signal.defaultAccount` overrides default account selection when it matches a configured account id.
|
||||
|
||||
### BlueBubbles
|
||||
|
||||
BlueBubbles is the legacy iMessage bridge (plugin-backed, configured under `channels.bluebubbles`). Existing setups remain supported, but new OpenClaw iMessage deployments should prefer `channels.imessage` when `imsg` can run on the Messages host.
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
bluebubbles: {
|
||||
enabled: true,
|
||||
dmPolicy: "pairing",
|
||||
// serverUrl, password, webhookPath, group controls, and advanced actions:
|
||||
// see /channels/bluebubbles
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
- Core key paths covered here: `channels.bluebubbles`, `channels.bluebubbles.dmPolicy`.
|
||||
- Optional `channels.bluebubbles.defaultAccount` overrides default account selection when it matches a configured account id.
|
||||
- Top-level `bindings[]` entries with `type: "acp"` can bind BlueBubbles conversations to persistent ACP sessions. Use a BlueBubbles handle or target string (`chat_id:*`, `chat_guid:*`, `chat_identifier:*`) in `match.peer.id`. Shared field semantics: [ACP Agents](/tools/acp-agents#persistent-channel-bindings).
|
||||
- Full BlueBubbles channel configuration and deprecation rationale are documented in [BlueBubbles](/channels/bluebubbles).
|
||||
|
||||
### iMessage
|
||||
|
||||
OpenClaw spawns `imsg rpc` (JSON-RPC over stdio). No daemon or port required. This is the preferred path for new OpenClaw iMessage setups when the host can grant Messages database and Automation permissions.
|
||||
|
||||
BlueBubbles is deprecated and no longer ships as a bundled OpenClaw channel. Migrate `channels.bluebubbles` configs to `channels.imessage`; third-party BlueBubbles bridges belong outside core.
|
||||
|
||||
If the Gateway is not running on the signed-in Messages Mac, keep `channels.imessage.enabled=true` and set `channels.imessage.cliPath` to an SSH wrapper that runs `imsg "$@"` on that Mac. The default local `imsg` path is macOS-only.
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
|
||||
@@ -264,7 +264,7 @@ That stages grounded durable candidates into the short-term dreaming store while
|
||||
If you previously added legacy OpenAI transport settings under `models.providers.openai-codex`, they can shadow the built-in Codex OAuth provider path that newer releases use automatically. Doctor warns when it sees those old transport settings alongside Codex OAuth so you can remove or rewrite the stale transport override and get the built-in routing/fallback behavior back. Custom proxies and header-only overrides are still supported and do not trigger this warning.
|
||||
</Accordion>
|
||||
<Accordion title="2f. Codex route repair">
|
||||
Doctor checks for legacy `openai-codex/*` model refs. Native Codex harness routing uses canonical `openai/*` model refs plus `agentRuntime.id: "codex"` so the turn goes through the Codex app-server harness instead of the OpenClaw PI OpenAI path.
|
||||
Doctor checks for legacy `openai-codex/*` model refs. Native Codex harness routing uses canonical `openai/*` model refs; OpenAI agent turns go through the Codex app-server harness instead of the OpenClaw PI OpenAI path.
|
||||
|
||||
In `--fix` / `--repair` mode, doctor rewrites affected default-agent and per-agent refs, including primary models, fallbacks, heartbeat/subagent/compaction overrides, hooks, channel model overrides, and stale persisted session route state:
|
||||
|
||||
|
||||
@@ -102,7 +102,7 @@ Outside heartbeats, stray `HEARTBEAT_OK` at the start/end of a message is stripp
|
||||
lightContext: false, // default: false; true keeps only HEARTBEAT.md from workspace bootstrap files
|
||||
isolatedSession: false, // default: false; true runs each heartbeat in a fresh session (no conversation history)
|
||||
skipWhenBusy: false, // default: false; true also waits for subagent/nested lanes
|
||||
target: "last", // default: none | options: last | none | <channel id> (core or plugin, e.g. "bluebubbles")
|
||||
target: "last", // default: none | options: last | none | <channel id> (core or plugin, e.g. "imessage")
|
||||
to: "+15551234567", // optional channel-specific override
|
||||
accountId: "ops-bot", // optional multi-account channel id
|
||||
prompt: "Read HEARTBEAT.md if it exists (workspace context). Follow it strictly. Do not infer or repeat old tasks from prior chats. If nothing needs attention, reply HEARTBEAT_OK.",
|
||||
|
||||
@@ -597,26 +597,26 @@ and troubleshooting see the main [FAQ](/help/faq).
|
||||
`openai/gpt-5.5` with `agentRuntime.id: "codex"` for the common setup:
|
||||
ChatGPT/Codex subscription auth plus native Codex app-server execution. Use
|
||||
`openai-codex/gpt-5.5` only when you want Codex OAuth through the default
|
||||
PI runner. Use `openai/gpt-5.5` without the Codex runtime override for
|
||||
direct OpenAI API-key access.
|
||||
Codex runtime. Direct OpenAI API-key access remains available for non-agent
|
||||
OpenAI API surfaces and for agent models through an ordered
|
||||
`openai-codex` API-key profile.
|
||||
See [Model providers](/concepts/model-providers) and [Onboarding (CLI)](/start/wizard).
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Why does OpenClaw still mention openai-codex?">
|
||||
`openai-codex` is the provider and auth-profile id for ChatGPT/Codex OAuth.
|
||||
It is also the explicit PI model prefix for Codex OAuth:
|
||||
Older configs also used it as a model prefix:
|
||||
|
||||
- `openai/gpt-5.5` + `agentRuntime.id: "codex"` = ChatGPT/Codex subscription auth with native Codex runtime
|
||||
- `openai-codex/gpt-5.5` = Codex OAuth route in PI
|
||||
- `openai/gpt-5.5` without a Codex runtime override = direct OpenAI API-key route in PI
|
||||
- `openai/gpt-5.5` = ChatGPT/Codex subscription auth with native Codex runtime for agent turns
|
||||
- `openai-codex/gpt-5.5` = legacy model route repaired by `openclaw doctor --fix`
|
||||
- `openai/gpt-5.5` plus an ordered `openai-codex` API-key profile = API-key auth for an OpenAI agent model
|
||||
- `openai-codex:...` = auth profile id, not a model ref
|
||||
|
||||
If you want the direct OpenAI Platform billing/limit path, set
|
||||
`OPENAI_API_KEY`. If you want ChatGPT/Codex subscription auth, sign in with
|
||||
`openclaw models auth login --provider openai-codex`. For native Codex
|
||||
runtime, keep the model ref as `openai/gpt-5.5` and set
|
||||
`agentRuntime.id: "codex"`. Use `openai-codex/*` model refs only for PI
|
||||
runs.
|
||||
`openclaw models auth login --provider openai-codex`. Keep the model ref as
|
||||
`openai/gpt-5.5`; `openai-codex/*` model refs are legacy config that
|
||||
`openclaw doctor --fix` rewrites.
|
||||
|
||||
</Accordion>
|
||||
|
||||
@@ -670,22 +670,22 @@ and troubleshooting see the main [FAQ](/help/faq).
|
||||
No. OpenClaw runs on macOS or Linux (Windows via WSL2). A Mac mini is optional - some people
|
||||
buy one as an always-on host, but a small VPS, home server, or Raspberry Pi-class box works too.
|
||||
|
||||
You only need a Mac **for macOS-only tools**. For iMessage, use [BlueBubbles](/channels/bluebubbles) (recommended) - the BlueBubbles server runs on any Mac, and the Gateway can run on Linux or elsewhere. If you want other macOS-only tools, run the Gateway on a Mac or pair a macOS node.
|
||||
You only need a Mac **for macOS-only tools**. For iMessage, use [iMessage](/channels/imessage) with `imsg` on any Mac signed into Messages. If the Gateway runs on Linux or elsewhere, set `channels.imessage.cliPath` to an SSH wrapper that runs `imsg` on that Mac. If you want other macOS-only tools, run the Gateway on a Mac or pair a macOS node.
|
||||
|
||||
Docs: [BlueBubbles](/channels/bluebubbles), [Nodes](/nodes), [Mac remote mode](/platforms/mac/remote).
|
||||
Docs: [iMessage](/channels/imessage), [Nodes](/nodes), [Mac remote mode](/platforms/mac/remote).
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Do I need a Mac mini for iMessage support?">
|
||||
You need **some macOS device** signed into Messages. It does **not** have to be a Mac mini -
|
||||
any Mac works. **Use [BlueBubbles](/channels/bluebubbles)** (recommended) for iMessage - the BlueBubbles server runs on macOS, while the Gateway can run on Linux or elsewhere.
|
||||
any Mac works. **Use [iMessage](/channels/imessage)** with `imsg`; the Gateway can run on that Mac, or it can run elsewhere with an SSH wrapper `cliPath`.
|
||||
|
||||
Common setups:
|
||||
|
||||
- Run the Gateway on Linux/VPS, and run the BlueBubbles server on any Mac signed into Messages.
|
||||
- Run the Gateway on Linux/VPS, and set `channels.imessage.cliPath` to an SSH wrapper that runs `imsg` on a Mac signed into Messages.
|
||||
- Run everything on the Mac if you want the simplest single-machine setup.
|
||||
|
||||
Docs: [BlueBubbles](/channels/bluebubbles), [Nodes](/nodes),
|
||||
Docs: [iMessage](/channels/imessage), [Nodes](/nodes),
|
||||
[Mac remote mode](/platforms/mac/remote).
|
||||
|
||||
</Accordion>
|
||||
|
||||
@@ -21,7 +21,7 @@ troubleshooting, see the main [FAQ](/help/faq).
|
||||
agents.defaults.model.primary
|
||||
```
|
||||
|
||||
Models are referenced as `provider/model` (example: `openai/gpt-5.5` or `openai-codex/gpt-5.5`). If you omit the provider, OpenClaw first tries an alias, then a unique configured-provider match for that exact model id, and only then falls back to the configured default provider as a deprecated compatibility path. If that provider no longer exposes the configured default model, OpenClaw falls back to the first configured provider/model instead of surfacing a stale removed-provider default. You should still **explicitly** set `provider/model`.
|
||||
Models are referenced as `provider/model` (example: `openai/gpt-5.5` or `anthropic/claude-sonnet-4-6`). If you omit the provider, OpenClaw first tries an alias, then a unique configured-provider match for that exact model id, and only then falls back to the configured default provider as a deprecated compatibility path. If that provider no longer exposes the configured default model, OpenClaw falls back to the first configured provider/model instead of surfacing a stale removed-provider default. You should still **explicitly** set `provider/model`.
|
||||
|
||||
</Accordion>
|
||||
|
||||
@@ -147,9 +147,9 @@ troubleshooting, see the main [FAQ](/help/faq).
|
||||
<Accordion title="Can I use GPT 5.5 for daily tasks and Codex 5.5 for coding?">
|
||||
Yes. Treat model choice and runtime choice separately:
|
||||
|
||||
- **Native Codex coding agent:** set `agents.defaults.model.primary` to `openai/gpt-5.5` and `agents.defaults.agentRuntime.id` to `"codex"`. Sign in with `openclaw models auth login --provider openai-codex` when you want ChatGPT/Codex subscription auth.
|
||||
- **Direct OpenAI API tasks through PI:** use `/model openai/gpt-5.5` without a Codex runtime override and configure `OPENAI_API_KEY`.
|
||||
- **Codex OAuth through PI:** use `/model openai-codex/gpt-5.5` only when you intentionally want the normal PI runner with Codex OAuth.
|
||||
- **Native Codex coding agent:** set `agents.defaults.model.primary` to `openai/gpt-5.5`. Sign in with `openclaw models auth login --provider openai-codex` when you want ChatGPT/Codex subscription auth.
|
||||
- **Direct OpenAI API tasks outside the agent loop:** configure `OPENAI_API_KEY` for images, embeddings, speech, realtime, and other non-agent OpenAI API surfaces.
|
||||
- **OpenAI agent API-key auth:** use `/model openai/gpt-5.5` with an ordered `openai-codex` API-key profile.
|
||||
- **Sub-agents:** route coding tasks to a Codex-only agent with its own model and `agentRuntime` default.
|
||||
|
||||
See [Models](/concepts/models) and [Slash commands](/tools/slash-commands).
|
||||
@@ -159,8 +159,8 @@ troubleshooting, see the main [FAQ](/help/faq).
|
||||
<Accordion title="How do I configure fast mode for GPT 5.5?">
|
||||
Use either a session toggle or a config default:
|
||||
|
||||
- **Per session:** send `/fast on` while the session is using `openai/gpt-5.5` or `openai-codex/gpt-5.5`.
|
||||
- **Per model default:** set `agents.defaults.models["openai/gpt-5.5"].params.fastMode` or `agents.defaults.models["openai-codex/gpt-5.5"].params.fastMode` to `true`.
|
||||
- **Per session:** send `/fast on` while the session is using `openai/gpt-5.5`.
|
||||
- **Per model default:** set `agents.defaults.models["openai/gpt-5.5"].params.fastMode` to `true`.
|
||||
|
||||
Example:
|
||||
|
||||
@@ -271,7 +271,7 @@ troubleshooting, see the main [FAQ](/help/faq).
|
||||
|
||||
- `opus` → `anthropic/claude-opus-4-6`
|
||||
- `sonnet` → `anthropic/claude-sonnet-4-6`
|
||||
- `gpt` → `openai/gpt-5.5` for API-key setups, or `openai-codex/gpt-5.5` when configured for Codex OAuth
|
||||
- `gpt` → `openai/gpt-5.5`
|
||||
- `gpt-mini` → `openai/gpt-5.4-mini`
|
||||
- `gpt-nano` → `openai/gpt-5.4-nano`
|
||||
- `gemini` → `google/gemini-3.1-pro-preview`
|
||||
|
||||
@@ -54,7 +54,7 @@ OpenClaw is a **self-hosted gateway** that connects your favorite chat apps and
|
||||
- **Agent-native**: built for coding agents with tool use, sessions, memory, and multi-agent routing
|
||||
- **Open source**: MIT licensed, community-driven
|
||||
|
||||
**What do you need?** Node 24 (recommended), or Node 22 LTS (`22.14+`) for compatibility, an API key from your chosen provider, and 5 minutes. For best quality and security, use the strongest latest-generation model available.
|
||||
**What do you need?** Node 24 (recommended), or Node 22 LTS (`22.16+`) for compatibility, an API key from your chosen provider, and 5 minutes. For best quality and security, use the strongest latest-generation model available.
|
||||
|
||||
## How it works
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ The Ansible playbook installs and configures:
|
||||
1. **Tailscale** -- mesh VPN for secure remote access
|
||||
2. **UFW firewall** -- SSH + Tailscale ports only
|
||||
3. **Docker CE + Compose V2** -- for the default agent sandbox backend
|
||||
4. **Node.js 24 + pnpm** -- runtime dependencies (Node 22 LTS, currently `22.14+`, remains supported)
|
||||
4. **Node.js 24 + pnpm** -- runtime dependencies (Node 22 LTS, currently `22.16+`, remains supported)
|
||||
5. **OpenClaw** -- host-based, not containerized
|
||||
6. **Systemd service** -- auto-start with security hardening
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ Bun is an optional local runtime for running TypeScript directly (`bun run ...`,
|
||||
|
||||
Bun blocks dependency lifecycle scripts unless explicitly trusted. For this repo, the commonly blocked scripts are not required:
|
||||
|
||||
- `@whiskeysockets/baileys` `preinstall` -- checks Node major >= 20 (OpenClaw defaults to Node 24 and still supports Node 22 LTS, currently `22.14+`)
|
||||
- `@whiskeysockets/baileys` `preinstall` -- checks Node major >= 20 (OpenClaw defaults to Node 24 and still supports Node 22 LTS, currently `22.16+`)
|
||||
- `protobufjs` `postinstall` -- emits warnings about incompatible version schemes (no build artifacts)
|
||||
|
||||
If you hit a runtime issue that requires these scripts, trust them explicitly:
|
||||
|
||||
@@ -335,6 +335,32 @@ See [ClawDock](/install/clawdock) for the full helper guide.
|
||||
`no-new-privileges` on both `openclaw-gateway` and `openclaw-cli`.
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Docker Desktop DNS failures in openclaw-cli">
|
||||
Some Docker Desktop setups fail DNS lookups from the shared-network
|
||||
`openclaw-cli` sidecar after `NET_RAW` is dropped, which shows up as
|
||||
`EAI_AGAIN` during npm-backed commands such as `openclaw plugins install`.
|
||||
Keep the default hardened compose file for normal gateway operation. The
|
||||
local override below loosens the CLI container's security posture by
|
||||
restoring Docker's default capabilities, so use it only for the one-off CLI
|
||||
command that needs package registry access, not as your default Compose
|
||||
invocation:
|
||||
|
||||
```bash
|
||||
printf '%s\n' \
|
||||
'services:' \
|
||||
' openclaw-cli:' \
|
||||
' cap_drop: !reset []' \
|
||||
> docker-compose.cli-no-dropped-caps.local.yml
|
||||
|
||||
docker compose -f docker-compose.yml -f docker-compose.cli-no-dropped-caps.local.yml run --rm openclaw-cli plugins install <package>
|
||||
```
|
||||
|
||||
If you already created a long-running `openclaw-cli` container, recreate it
|
||||
with the same override. `docker compose exec` and `docker exec` cannot
|
||||
change Linux capabilities on an already-created container.
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Permissions and EACCES">
|
||||
The image runs as `node` (uid 1000). If you see permission errors on
|
||||
`/home/node/.openclaw`, make sure your host bind mounts are owned by uid 1000:
|
||||
|
||||
@@ -9,7 +9,7 @@ title: "Install"
|
||||
|
||||
## System requirements
|
||||
|
||||
- **Node 24** (recommended) or Node 22.14+ - the installer script handles this automatically
|
||||
- **Node 24** (recommended) or Node 22.16+ - the installer script handles this automatically
|
||||
- **macOS, Linux, or Windows** - both native Windows and WSL2 are supported; WSL2 is more stable. See [Windows](/platforms/windows).
|
||||
- `pnpm` is only needed if you build from source
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@ Recommended for most interactive installs on macOS/Linux/WSL.
|
||||
Supports macOS and Linux (including WSL). If macOS is detected, installs Homebrew if missing.
|
||||
</Step>
|
||||
<Step title="Ensure Node.js 24 by default">
|
||||
Checks Node version and installs Node 24 if needed (Homebrew on macOS, NodeSource setup scripts on Linux apt/dnf/yum). OpenClaw still supports Node 22 LTS, currently `22.14+`, for compatibility.
|
||||
Checks Node version and installs Node 24 if needed (Homebrew on macOS, NodeSource setup scripts on Linux apt/dnf/yum). OpenClaw still supports Node 22 LTS, currently `22.16+`, for compatibility.
|
||||
</Step>
|
||||
<Step title="Ensure Git">
|
||||
Installs Git if missing.
|
||||
@@ -284,7 +284,7 @@ by default, plus git-checkout installs under the same prefix flow.
|
||||
Requires PowerShell 5+.
|
||||
</Step>
|
||||
<Step title="Ensure Node.js 24 by default">
|
||||
If missing, attempts install via winget, then Chocolatey, then Scoop. Node 22 LTS, currently `22.14+`, remains supported for compatibility.
|
||||
If missing, attempts install via winget, then Chocolatey, then Scoop. Node 22 LTS, currently `22.16+`, remains supported for compatibility.
|
||||
</Step>
|
||||
<Step title="Install OpenClaw">
|
||||
- `npm` method (default): global npm install using selected `-Tag`, launched from a writable installer temp directory so shells opened in protected folders such as `C:\` still work
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
summary: "Run OpenClaw in a sandboxed macOS VM (local or hosted) when you need isolation or iMessage"
|
||||
read_when:
|
||||
- You want OpenClaw isolated from your main macOS environment
|
||||
- You want iMessage integration (BlueBubbles) in a sandbox
|
||||
- You want iMessage integration in a sandbox
|
||||
- You want a resettable macOS environment you can clone
|
||||
- You want to compare local vs hosted macOS VM options
|
||||
title: "macOS VMs"
|
||||
@@ -14,7 +14,7 @@ title: "macOS VMs"
|
||||
- **Dedicated hardware** (Mac mini or Linux box) if you want full control and a **residential IP** for browser automation. Many sites block data center IPs, so local browsing often works better.
|
||||
- **Hybrid:** keep the Gateway on a cheap VPS, and connect your Mac as a **node** when you need browser/UI automation. See [Nodes](/nodes) and [Gateway remote](/gateway/remote).
|
||||
|
||||
Use a macOS VM when you specifically need macOS-only capabilities (iMessage/BlueBubbles) or want strict isolation from your daily Mac.
|
||||
Use a macOS VM when you specifically need macOS-only capabilities such as iMessage or want strict isolation from your daily Mac.
|
||||
|
||||
## macOS VM options
|
||||
|
||||
@@ -25,7 +25,7 @@ Run OpenClaw in a sandboxed macOS VM on your existing Apple Silicon Mac using [L
|
||||
This gives you:
|
||||
|
||||
- Full macOS environment in isolation (your host stays clean)
|
||||
- iMessage support via BlueBubbles (impossible on Linux/Windows)
|
||||
- iMessage support via `imsg` (the default local path is impossible on Linux/Windows)
|
||||
- Instant reset by cloning VMs
|
||||
- No extra hardware or cloud costs
|
||||
|
||||
@@ -198,24 +198,24 @@ ssh youruser@192.168.64.X "openclaw status"
|
||||
|
||||
## Bonus: iMessage integration
|
||||
|
||||
This is the killer feature of running on macOS. Use [BlueBubbles](https://bluebubbles.app) to add iMessage to OpenClaw.
|
||||
This is the killer feature of running on macOS. Use [iMessage](/channels/imessage) with `imsg` to add Messages to OpenClaw.
|
||||
|
||||
Inside the VM:
|
||||
|
||||
1. Download BlueBubbles from bluebubbles.app
|
||||
2. Sign in with your Apple ID
|
||||
3. Enable the Web API and set a password
|
||||
4. Point BlueBubbles webhooks at your gateway (example: `https://your-gateway-host:3000/bluebubbles-webhook?password=<password>`)
|
||||
1. Sign in to Messages.
|
||||
2. Install `imsg`.
|
||||
3. Grant Full Disk Access and Automation permission for the process running OpenClaw/`imsg`.
|
||||
4. Verify RPC support with `imsg rpc --help`.
|
||||
|
||||
Add to your OpenClaw config:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
bluebubbles: {
|
||||
serverUrl: "http://localhost:1234",
|
||||
password: "your-api-password",
|
||||
webhookPath: "/bluebubbles-webhook",
|
||||
imessage: {
|
||||
enabled: true,
|
||||
cliPath: "imsg",
|
||||
dbPath: "~/Library/Messages/chat.db",
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -223,7 +223,7 @@ Add to your OpenClaw config:
|
||||
|
||||
Restart the gateway. Now your agent can send and receive iMessages.
|
||||
|
||||
Full setup details: [BlueBubbles channel](/channels/bluebubbles)
|
||||
Full setup details: [iMessage channel](/channels/imessage)
|
||||
|
||||
---
|
||||
|
||||
@@ -274,7 +274,7 @@ For true always-on, consider a dedicated Mac mini or a small VPS. See [VPS hosti
|
||||
- [VPS hosting](/vps)
|
||||
- [Nodes](/nodes)
|
||||
- [Gateway remote](/gateway/remote)
|
||||
- [BlueBubbles channel](/channels/bluebubbles)
|
||||
- [iMessage channel](/channels/imessage)
|
||||
- [Lume Quickstart](https://cua.ai/docs/lume/guide/getting-started/quickstart)
|
||||
- [Lume CLI Reference](https://cua.ai/docs/lume/reference/cli-reference)
|
||||
- [Unattended VM Setup](https://cua.ai/docs/lume/guide/fundamentals/unattended-setup) (advanced)
|
||||
|
||||
@@ -7,7 +7,7 @@ read_when:
|
||||
- "npm install -g fails with permissions or PATH issues"
|
||||
---
|
||||
|
||||
OpenClaw requires **Node 22.14 or newer**. **Node 24 is the default and recommended runtime** for installs, CI, and release workflows. Node 22 remains supported via the active LTS line. The [installer script](/install#alternative-install-methods) will detect and install Node automatically - this page is for when you want to set up Node yourself and make sure everything is wired up correctly (versions, PATH, global installs).
|
||||
OpenClaw requires **Node 22.16 or newer**. **Node 24 is the default and recommended runtime** for installs, CI, and release workflows. Node 22 remains supported via the active LTS line. The [installer script](/install#alternative-install-methods) will detect and install Node automatically - this page is for when you want to set up Node yourself and make sure everything is wired up correctly (versions, PATH, global installs).
|
||||
|
||||
## Check your version
|
||||
|
||||
@@ -15,7 +15,7 @@ OpenClaw requires **Node 22.14 or newer**. **Node 24 is the default and recommen
|
||||
node -v
|
||||
```
|
||||
|
||||
If this prints `v24.x.x` or higher, you're on the recommended default. If it prints `v22.14.x` or higher, you're on the supported Node 22 LTS path, but we still recommend upgrading to Node 24 when convenient. If Node isn't installed or the version is too old, pick an install method below.
|
||||
If this prints `v24.x.x` or higher, you're on the recommended default. If it prints `v22.16.x` or higher, you're on the supported Node 22 LTS path, but we still recommend upgrading to Node 24 when convenient. If Node isn't installed or the version is too old, pick an install method below.
|
||||
|
||||
## Install Node
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ Native Linux companion apps are planned. Contributions are welcome if you want t
|
||||
|
||||
## Beginner quick path (VPS)
|
||||
|
||||
1. Install Node 24 (recommended; Node 22 LTS, currently `22.14+`, still works for compatibility)
|
||||
1. Install Node 24 (recommended; Node 22 LTS, currently `22.16+`, still works for compatibility)
|
||||
2. `npm i -g openclaw@latest`
|
||||
3. `openclaw onboard --install-daemon`
|
||||
4. From your laptop: `ssh -N -L 18789:127.0.0.1:18789 <user>@<host>`
|
||||
|
||||
@@ -14,7 +14,7 @@ running (or attaches to an existing local Gateway if one is already running).
|
||||
|
||||
## Install the CLI (required for local mode)
|
||||
|
||||
Node 24 is the default runtime on the Mac. Node 22 LTS, currently `22.14+`, still works for compatibility. Then install `openclaw` globally:
|
||||
Node 24 is the default runtime on the Mac. Node 22 LTS, currently `22.16+`, still works for compatibility. Then install `openclaw` globally:
|
||||
|
||||
```bash
|
||||
npm install -g openclaw@<version>
|
||||
|
||||
@@ -14,7 +14,7 @@ Build and run the OpenClaw macOS application from source.
|
||||
Before building the app, ensure you have the following installed:
|
||||
|
||||
1. **Xcode 26.2+**: Required for Swift development.
|
||||
2. **Node.js 24 & pnpm**: Recommended for the gateway, CLI, and packaging scripts. Node 22 LTS, currently `22.14+`, remains supported for compatibility.
|
||||
2. **Node.js 24 & pnpm**: Recommended for the gateway, CLI, and packaging scripts. Node 22 LTS, currently `22.16+`, remains supported for compatibility.
|
||||
|
||||
## 1. Install Dependencies
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ This app is usually built from [`scripts/package-mac-app.sh`](https://github.com
|
||||
- calls [`scripts/codesign-mac-app.sh`](https://github.com/openclaw/openclaw/blob/main/scripts/codesign-mac-app.sh) to sign the main binary and app bundle so macOS treats each rebuild as the same signed bundle and keeps TCC permissions (notifications, accessibility, screen recording, mic, speech). For stable permissions, use a real signing identity; ad-hoc is opt-in and fragile (see [macOS permissions](/platforms/mac/permissions)).
|
||||
- uses `CODESIGN_TIMESTAMP=auto` by default; it enables trusted timestamps for Developer ID signatures. Set `CODESIGN_TIMESTAMP=off` to skip timestamping (offline debug builds).
|
||||
- inject build metadata into Info.plist: `OpenClawBuildTimestamp` (UTC) and `OpenClawGitCommit` (short hash) so the About pane can show build, git, and debug/release channel.
|
||||
- **Packaging defaults to Node 24**: the script runs TS builds and the Control UI build. Node 22 LTS, currently `22.14+`, remains supported for compatibility.
|
||||
- **Packaging defaults to Node 24**: the script runs TS builds and the Control UI build. Node 22 LTS, currently `22.16+`, remains supported for compatibility.
|
||||
- reads `SIGN_IDENTITY` from the environment. Add `export SIGN_IDENTITY="Apple Development: Your Name (TEAMID)"` (or your Developer ID Application cert) to your shell rc to always sign with your cert. Ad-hoc signing requires explicit opt-in via `ALLOW_ADHOC_SIGNING=1` or `SIGN_IDENTITY="-"` (not recommended for permission testing).
|
||||
- runs a Team ID audit after signing and fails if any Mach-O inside the app bundle is signed by a different Team ID. Set `SKIP_TEAM_ID_CHECK=1` to bypass.
|
||||
|
||||
|
||||
@@ -106,10 +106,9 @@ The bundled `codex` plugin contributes several separate capabilities:
|
||||
|
||||
Enabling the plugin makes those capabilities available. It does **not**:
|
||||
|
||||
- start using Codex for every OpenAI model
|
||||
- convert `openai-codex/*` model refs into the native runtime without doctor
|
||||
verifying that Codex is installed, enabled, contributes the `codex` harness,
|
||||
and is OAuth-ready
|
||||
- replace direct OpenAI API-key surfaces such as images, embeddings, speech, or
|
||||
realtime
|
||||
- convert `openai-codex/*` model refs without `openclaw doctor --fix`
|
||||
- make ACP/acpx the default Codex path
|
||||
- hot-switch existing sessions that already recorded a PI runtime
|
||||
- replace OpenClaw channel delivery, session files, auth-profile storage, or
|
||||
@@ -141,29 +140,28 @@ tool-result writes.
|
||||
For the plugin hook semantics themselves, see [Plugin hooks](/plugins/hooks)
|
||||
and [Plugin guard behavior](/tools/plugin).
|
||||
|
||||
The harness is off by default. New configs should keep OpenAI model refs
|
||||
canonical as `openai/gpt-*` and explicitly force
|
||||
`agentRuntime.id: "codex"` or `OPENCLAW_AGENT_RUNTIME=codex` when they
|
||||
want native app-server execution. Legacy `codex/*` model refs still auto-select
|
||||
the harness for compatibility, but runtime-backed legacy provider prefixes are
|
||||
not shown as normal model/provider choices.
|
||||
OpenAI agent model refs use the harness by default. New configs should keep
|
||||
OpenAI model refs canonical as `openai/gpt-*`; `agentRuntime.id: "codex"` is
|
||||
still valid but no longer required for OpenAI agent turns. Legacy `codex/*`
|
||||
model refs still auto-select the harness for compatibility, but
|
||||
runtime-backed legacy provider prefixes are not shown as normal model/provider
|
||||
choices.
|
||||
|
||||
If any configured model route is still `openai-codex/*`, `openclaw doctor --fix`
|
||||
rewrites it to `openai/*`. For matching agent routes, it sets the agent runtime
|
||||
to `codex` only when the Codex plugin is installed, enabled, contributes the
|
||||
`codex` harness, and has usable OAuth; otherwise it sets the runtime to `pi`.
|
||||
to `codex` and preserves existing `openai-codex` auth profile overrides.
|
||||
|
||||
## Route map
|
||||
|
||||
Use this table before changing config:
|
||||
|
||||
| Desired behavior | Model ref | Runtime config | Auth/profile route | Expected status label |
|
||||
| ---------------------------------------------------- | -------------------------- | -------------------------------------- | ---------------------------- | ------------------------------ |
|
||||
| ChatGPT/Codex subscription with native Codex runtime | `openai/gpt-*` | `agentRuntime.id: "codex"` | Codex OAuth or Codex account | `Runtime: OpenAI Codex` |
|
||||
| OpenAI API through normal OpenClaw runner | `openai/gpt-*` | omitted or `runtime: "pi"` | OpenAI API key | `Runtime: OpenClaw Pi Default` |
|
||||
| Legacy config that needs doctor repair | `openai-codex/gpt-*` | repaired to `codex` or `pi` | Existing configured auth | Recheck after `doctor --fix` |
|
||||
| Mixed providers with conservative auto mode | provider-specific refs | `agentRuntime.id: "auto"` | Per selected provider | Depends on selected runtime |
|
||||
| Explicit Codex ACP adapter session | ACP prompt/model dependent | `sessions_spawn` with `runtime: "acp"` | ACP backend auth | ACP task/session status |
|
||||
| Desired behavior | Model ref | Runtime config | Auth/profile route | Expected status label |
|
||||
| ---------------------------------------------------- | -------------------------- | -------------------------------------- | ------------------------------ | ---------------------------- |
|
||||
| ChatGPT/Codex subscription with native Codex runtime | `openai/gpt-*` | omitted or `agentRuntime.id: "codex"` | Codex OAuth or Codex account | `Runtime: OpenAI Codex` |
|
||||
| OpenAI API-key auth for agent models | `openai/gpt-*` | omitted or `agentRuntime.id: "codex"` | `openai-codex` API-key profile | `Runtime: OpenAI Codex` |
|
||||
| Legacy config that needs doctor repair | `openai-codex/gpt-*` | repaired to `codex` | Existing configured auth | Recheck after `doctor --fix` |
|
||||
| Mixed providers with conservative auto mode | provider-specific refs | `agentRuntime.id: "auto"` | Per selected provider | Depends on selected runtime |
|
||||
| Explicit Codex ACP adapter session | ACP prompt/model dependent | `sessions_spawn` with `runtime: "acp"` | ACP backend auth | ACP task/session status |
|
||||
|
||||
The important split is provider versus runtime:
|
||||
|
||||
@@ -171,8 +169,7 @@ The important split is provider versus runtime:
|
||||
- `agentRuntime.id: "codex"` requires the Codex harness and fails closed if it
|
||||
is unavailable.
|
||||
- `agentRuntime.id: "auto"` lets registered harnesses claim matching provider
|
||||
routes, but canonical OpenAI refs are still PI-owned unless a harness supports
|
||||
that provider/model pair.
|
||||
routes; OpenAI agent refs resolve to Codex instead of PI.
|
||||
- `/codex ...` answers "which native Codex conversation should this chat bind
|
||||
or control?"
|
||||
- ACP answers "which external harness process should acpx launch?"
|
||||
@@ -180,14 +177,14 @@ The important split is provider versus runtime:
|
||||
## Pick the right model prefix
|
||||
|
||||
OpenAI-family routes are prefix-specific. For the common subscription plus
|
||||
native Codex runtime setup, use `openai/*` with `agentRuntime.id: "codex"`.
|
||||
native Codex runtime setup, use `openai/*`.
|
||||
Treat `openai-codex/*` as legacy config that doctor should rewrite:
|
||||
|
||||
| Model ref | Runtime path | Use when |
|
||||
| --------------------------------------------- | -------------------------------------------- | ------------------------------------------------------------------------- |
|
||||
| `openai/gpt-5.4` | OpenAI provider through OpenClaw/PI plumbing | You want current direct OpenAI Platform API access with `OPENAI_API_KEY`. |
|
||||
| `openai-codex/gpt-5.5` | Legacy route repaired by doctor | You are on old config; run `openclaw doctor --fix` to rewrite it. |
|
||||
| `openai/gpt-5.5` + `agentRuntime.id: "codex"` | Codex app-server harness | You want ChatGPT/Codex subscription auth with native Codex execution. |
|
||||
| Model ref | Runtime path | Use when |
|
||||
| ------------------------------------------------- | ---------------------------------------- | ----------------------------------------------------------------- |
|
||||
| `openai/gpt-5.4` | Codex app-server harness for agent turns | You want OpenAI agent models through Codex. |
|
||||
| `openai-codex/gpt-5.5` | Legacy route repaired by doctor | You are on old config; run `openclaw doctor --fix` to rewrite it. |
|
||||
| `openai/gpt-5.5` + `openai-codex` API-key profile | Codex app-server harness | You want API-key auth for an OpenAI agent model. |
|
||||
|
||||
GPT-5.5 can appear on both direct OpenAI API-key and Codex subscription routes
|
||||
when your account exposes them. Use `openai/gpt-5.5` with the Codex app-server
|
||||
@@ -219,13 +216,10 @@ state still use `openai-codex/*`. `openclaw doctor --fix` rewrites those routes
|
||||
to:
|
||||
|
||||
- `openai/<model>`
|
||||
- `agentRuntime.id: "codex"` when Codex is installed, enabled, contributes the
|
||||
`codex` harness, and has usable OAuth
|
||||
- `agentRuntime.id: "pi"` otherwise
|
||||
- `agentRuntime.id: "codex"`
|
||||
|
||||
The `codex` route forces the native Codex harness. The `pi` route keeps the
|
||||
agent on the default OpenClaw runner instead of enabling or installing Codex as
|
||||
a side effect of legacy-route cleanup.
|
||||
The `codex` route forces the native Codex harness. PI runtime config is not
|
||||
allowed for OpenAI agent model turns.
|
||||
Doctor also repairs stale persisted session pins across discovered agent session
|
||||
stores so old conversations do not stay wedged on the removed route.
|
||||
|
||||
@@ -349,7 +343,7 @@ Agents should route user requests by intent, not by the word "Codex" alone:
|
||||
| "Show Codex threads" | `/codex threads` |
|
||||
| "File a support report for a bad Codex run" | `/diagnostics [note]` |
|
||||
| "Only send Codex feedback for this attached thread" | `/codex diagnostics [note]` |
|
||||
| "Use my ChatGPT/Codex subscription with Codex runtime" | `openai/*` plus `agentRuntime.id: "codex"` |
|
||||
| "Use my ChatGPT/Codex subscription with Codex runtime" | `openai/*` |
|
||||
| "Repair old `openai-codex/*` config/session pins" | `openclaw doctor --fix` |
|
||||
| "Run Codex through ACP/acpx" | ACP `sessions_spawn({ runtime: "acp", ... })` |
|
||||
| "Start Claude Code/Gemini/OpenCode/Cursor in a thread" | ACP/acpx, not `/codex` and not native sub-agents |
|
||||
|
||||
@@ -144,7 +144,6 @@ uninstall, and publishing commands.
|
||||
| Plugin | Description | Distribution | Surface |
|
||||
| ------------------------------------------------------------------- | ------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------- |
|
||||
| [acpx](/plugins/reference/acpx) | Embedded ACP runtime backend with plugin-owned session and transport management. | `@openclaw/acpx`<br />npm; ClawHub | skills |
|
||||
| [bluebubbles](/plugins/reference/bluebubbles) | Adds the BlueBubbles channel surface for sending and receiving OpenClaw messages. | `@openclaw/bluebubbles`<br />npm; ClawHub | channels: bluebubbles |
|
||||
| [brave](/plugins/reference/brave) | Adds web search provider support. | `@openclaw/brave-plugin`<br />npm; ClawHub | contracts: webSearchProviders |
|
||||
| [codex](/plugins/reference/codex) | Codex app-server harness and Codex-managed GPT model catalog. | `@openclaw/codex`<br />npm; ClawHub | providers: codex; contracts: mediaUnderstandingProviders, migrationProviders |
|
||||
| [diagnostics-otel](/plugins/reference/diagnostics-otel) | OpenClaw diagnostics OpenTelemetry exporter. | `@openclaw/diagnostics-otel`<br />npm; ClawHub: `clawhub:@openclaw/diagnostics-otel` | plugin |
|
||||
|
||||
@@ -25,7 +25,6 @@ pnpm plugins:inventory:gen
|
||||
| [anthropic-vertex](/plugins/reference/anthropic-vertex) | Adds Anthropic Vertex model provider support to OpenClaw. | `@openclaw/anthropic-vertex-provider`<br />included in OpenClaw | providers: anthropic-vertex |
|
||||
| [arcee](/plugins/reference/arcee) | Adds Arcee model provider support to OpenClaw. | `@openclaw/arcee-provider`<br />included in OpenClaw | providers: arcee |
|
||||
| [azure-speech](/plugins/reference/azure-speech) | Azure AI Speech text-to-speech (MP3, native Ogg/Opus voice notes, PCM telephony). | `@openclaw/azure-speech`<br />included in OpenClaw | contracts: speechProviders |
|
||||
| [bluebubbles](/plugins/reference/bluebubbles) | Adds the BlueBubbles channel surface for sending and receiving OpenClaw messages. | `@openclaw/bluebubbles`<br />npm; ClawHub | channels: bluebubbles |
|
||||
| [bonjour](/plugins/reference/bonjour) | Advertise the local OpenClaw gateway over Bonjour/mDNS. | `@openclaw/bonjour`<br />included in OpenClaw | plugin |
|
||||
| [brave](/plugins/reference/brave) | Adds web search provider support. | `@openclaw/brave-plugin`<br />npm; ClawHub | contracts: webSearchProviders |
|
||||
| [browser](/plugins/reference/browser) | Adds agent-callable tools. | `@openclaw/browser-plugin`<br />included in OpenClaw | contracts: tools; skills |
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
---
|
||||
summary: "Adds the BlueBubbles channel surface for sending and receiving OpenClaw messages."
|
||||
read_when:
|
||||
- You are installing, configuring, or auditing the bluebubbles plugin
|
||||
title: "BlueBubbles plugin"
|
||||
---
|
||||
|
||||
# BlueBubbles plugin
|
||||
|
||||
Adds the BlueBubbles channel surface for sending and receiving OpenClaw messages.
|
||||
|
||||
## Distribution
|
||||
|
||||
- Package: `@openclaw/bluebubbles`
|
||||
- Install route: npm; ClawHub
|
||||
|
||||
## Surface
|
||||
|
||||
channels: bluebubbles
|
||||
|
||||
## Related docs
|
||||
|
||||
- [bluebubbles](/channels/bluebubbles)
|
||||
@@ -194,9 +194,10 @@ intentional silent replies such as `NO_REPLY` unclassified.
|
||||
The bundled `codex` harness is the native Codex mode for embedded OpenClaw
|
||||
agent turns. Enable the bundled `codex` plugin first, and include `codex` in
|
||||
`plugins.allow` if your config uses a restrictive allowlist. Native app-server
|
||||
configs should use `openai/gpt-*` with `agentRuntime.id: "codex"`.
|
||||
Use `openai-codex/*` for Codex OAuth through PI instead. Legacy `codex/*`
|
||||
model refs remain compatibility aliases for the native harness.
|
||||
configs should use `openai/gpt-*`; OpenAI agent turns select the Codex harness
|
||||
by default. Legacy `openai-codex/*` routes should be repaired with
|
||||
`openclaw doctor --fix`, and legacy `codex/*` model refs remain compatibility
|
||||
aliases for the native harness.
|
||||
|
||||
When this mode runs, Codex owns the native thread id, resume behavior,
|
||||
compaction, and app-server execution. OpenClaw still owns the chat channel,
|
||||
|
||||
@@ -253,9 +253,9 @@ OpenClaw supports Anthropic's prompt caching feature for API-key auth.
|
||||
auto-resolves media capabilities from the configured Anthropic auth — no
|
||||
additional config is needed.
|
||||
|
||||
| Property | Value |
|
||||
| -------------- | -------------------- |
|
||||
| Default model | `claude-opus-4-6` |
|
||||
| Property | Value |
|
||||
| --------------- | --------------------- |
|
||||
| Default model | `claude-opus-4-7` |
|
||||
| Supported input | Images, PDF documents |
|
||||
|
||||
When an image or PDF is attached to a conversation, OpenClaw automatically
|
||||
|
||||
@@ -110,12 +110,12 @@ The onboarding preset sets `arcee/trinity-large-thinking` as the default model.
|
||||
|
||||
## Supported features
|
||||
|
||||
| Feature | Supported |
|
||||
| --------------------------------------------- | ---------------------------- |
|
||||
| Streaming | Yes |
|
||||
| Tool use / function calling | Yes |
|
||||
| Structured output (JSON mode and JSON schema) | Yes |
|
||||
| Extended thinking | Yes (Trinity Large Thinking) |
|
||||
| Feature | Supported |
|
||||
| --------------------------------------------- | -------------------------------------------- |
|
||||
| Streaming | Yes |
|
||||
| Tool use / function calling | Yes (Trinity Mini, Trinity Large Preview) |
|
||||
| Structured output (JSON mode and JSON schema) | Yes |
|
||||
| Extended thinking | Yes (Trinity Large Thinking; tools disabled) |
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="Environment note">
|
||||
|
||||
@@ -11,14 +11,18 @@ OpenAI provides developer APIs for GPT models, and Codex is also available as a
|
||||
ChatGPT-plan coding agent through OpenAI's Codex clients. OpenClaw keeps those
|
||||
surfaces separate so config stays predictable.
|
||||
|
||||
OpenClaw supports three OpenAI-family routes. Most ChatGPT/Codex subscribers
|
||||
who want Codex behavior should use the native Codex app-server runtime. The
|
||||
model prefix selects the provider/model name; a separate runtime setting selects
|
||||
who executes the embedded agent loop:
|
||||
OpenClaw uses `openai/*` as the canonical OpenAI model route. Embedded agent
|
||||
turns on OpenAI models run through the native Codex app-server runtime by
|
||||
default; direct OpenAI API-key auth remains available for non-agent OpenAI
|
||||
surfaces such as images, embeddings, speech, and realtime.
|
||||
|
||||
- **API key** - direct OpenAI Platform access with usage-based billing (`openai/*` models)
|
||||
- **Codex subscription with native Codex runtime** - ChatGPT/Codex sign-in plus Codex app-server execution (`openai/*` models plus `agents.defaults.agentRuntime.id: "codex"`)
|
||||
- **Codex subscription through PI** - ChatGPT/Codex sign-in with the normal OpenClaw PI runner (`openai-codex/*` models)
|
||||
- **Agent models** - `openai/*` models through the Codex runtime; sign in with
|
||||
`openai-codex` auth for ChatGPT/Codex subscription use, or configure an
|
||||
`openai-codex` API-key profile when you intentionally want API-key auth.
|
||||
- **Non-agent OpenAI APIs** - direct OpenAI Platform access with usage-based
|
||||
billing through `OPENAI_API_KEY` or OpenAI API-key onboarding.
|
||||
- **Legacy config** - `openai-codex/*` model refs are repaired by
|
||||
`openclaw doctor --fix` to `openai/*` plus the Codex runtime.
|
||||
|
||||
OpenAI explicitly supports subscription OAuth usage in external tools and workflows like OpenClaw.
|
||||
|
||||
@@ -28,64 +32,67 @@ changing config.
|
||||
|
||||
## Quick choice
|
||||
|
||||
| Goal | Use | Notes |
|
||||
| ---------------------------------------------------- | ------------------------------------------------ | ------------------------------------------------------------------------- |
|
||||
| ChatGPT/Codex subscription with native Codex runtime | `openai/gpt-5.5` plus `agentRuntime.id: "codex"` | Recommended Codex setup for most users. Sign in with `openai-codex` auth. |
|
||||
| Direct API-key billing | `openai/gpt-5.5` | Set `OPENAI_API_KEY` or run OpenAI API-key onboarding. |
|
||||
| ChatGPT/Codex subscription auth through PI | `openai-codex/gpt-5.5` | Use only when you intentionally want the normal PI runner. |
|
||||
| Image generation or editing | `openai/gpt-image-2` | Works with either `OPENAI_API_KEY` or OpenAI Codex OAuth. |
|
||||
| Transparent-background images | `openai/gpt-image-1.5` | Use `outputFormat=png` or `webp` and `openai.background=transparent`. |
|
||||
| Goal | Use | Notes |
|
||||
| ---------------------------------------------------- | ------------------------------------------------------- | --------------------------------------------------------------------- |
|
||||
| ChatGPT/Codex subscription with native Codex runtime | `openai/gpt-5.5` | Default OpenAI agent setup. Sign in with `openai-codex` auth. |
|
||||
| Direct API-key billing for agent models | `openai/gpt-5.5` plus an `openai-codex` API-key profile | Use `auth.order.openai-codex` to prefer that profile. |
|
||||
| Direct API-key billing through explicit PI | `openai/gpt-5.5` plus `agentRuntime.id: "pi"` | Select a normal `openai` API-key profile. |
|
||||
| Latest ChatGPT Instant API alias | `openai/chat-latest` | Direct API-key only. Moving alias for experiments, not the default. |
|
||||
| ChatGPT/Codex subscription auth through explicit PI | `openai/gpt-5.5` plus `agentRuntime.id: "pi"` | Select an `openai-codex` auth profile for the compatibility route. |
|
||||
| Image generation or editing | `openai/gpt-image-2` | Works with either `OPENAI_API_KEY` or OpenAI Codex OAuth. |
|
||||
| Transparent-background images | `openai/gpt-image-1.5` | Use `outputFormat=png` or `webp` and `openai.background=transparent`. |
|
||||
|
||||
## Naming map
|
||||
|
||||
The names are similar but not interchangeable:
|
||||
|
||||
| Name you see | Layer | Meaning |
|
||||
| ---------------------------------- | ----------------- | ------------------------------------------------------------------------------------------------- |
|
||||
| `openai` | Provider prefix | Direct OpenAI Platform API route. |
|
||||
| `openai-codex` | Provider prefix | OpenAI Codex OAuth/subscription route through the normal OpenClaw PI runner. |
|
||||
| `codex` plugin | Plugin | Bundled OpenClaw plugin that provides native Codex app-server runtime and `/codex` chat controls. |
|
||||
| `agentRuntime.id: codex` | Agent runtime | Force the native Codex app-server harness for embedded turns. |
|
||||
| `/codex ...` | Chat command set | Bind/control Codex app-server threads from a conversation. |
|
||||
| `runtime: "acp", agentId: "codex"` | ACP session route | Explicit fallback path that runs Codex through ACP/acpx. |
|
||||
| Name you see | Layer | Meaning |
|
||||
| ---------------------------------- | ------------------- | ------------------------------------------------------------------------------------------------- |
|
||||
| `openai` | Provider prefix | Canonical OpenAI model route; agent turns use the Codex runtime. |
|
||||
| `openai-codex` | Auth/profile prefix | OpenAI Codex OAuth/subscription auth profile provider. |
|
||||
| `codex` plugin | Plugin | Bundled OpenClaw plugin that provides native Codex app-server runtime and `/codex` chat controls. |
|
||||
| `agentRuntime.id: codex` | Agent runtime | Force the native Codex app-server harness for embedded turns. |
|
||||
| `/codex ...` | Chat command set | Bind/control Codex app-server threads from a conversation. |
|
||||
| `runtime: "acp", agentId: "codex"` | ACP session route | Explicit fallback path that runs Codex through ACP/acpx. |
|
||||
|
||||
This means a config can intentionally contain both `openai-codex/*` and the
|
||||
`codex` plugin. That is valid when you want Codex OAuth through PI and also want
|
||||
native `/codex` chat controls available. `openclaw doctor` warns about that
|
||||
combination so you can confirm it is intentional; it does not rewrite it.
|
||||
This means a config can intentionally contain both `openai/*` model refs and
|
||||
`openai-codex` auth profiles. `openclaw doctor --fix` rewrites legacy
|
||||
`openai-codex/*` model refs to the canonical OpenAI model route.
|
||||
|
||||
<Note>
|
||||
GPT-5.5 is available through both direct OpenAI Platform API-key access and
|
||||
subscription/OAuth routes. For ChatGPT/Codex subscription plus native Codex
|
||||
execution, use `openai/gpt-5.5` with `agentRuntime.id: "codex"`. Use
|
||||
`openai-codex/gpt-5.5` only for Codex OAuth through PI, or `openai/gpt-5.5`
|
||||
without a Codex runtime override for direct `OPENAI_API_KEY` traffic.
|
||||
execution, use `openai/gpt-5.5`; unset runtime config now selects the Codex
|
||||
harness for OpenAI agent turns. Use OpenAI API-key profiles only when you want
|
||||
direct API-key auth for an OpenAI agent model.
|
||||
</Note>
|
||||
|
||||
<Note>
|
||||
Enabling the OpenAI plugin, or selecting an `openai-codex/*` model, does not
|
||||
enable the bundled Codex app-server plugin. OpenClaw enables that plugin only
|
||||
when you explicitly select the native Codex harness with
|
||||
`agentRuntime.id: "codex"` or use a legacy `codex/*` model ref.
|
||||
If the bundled `codex` plugin is enabled but `openai-codex/*` still resolves
|
||||
through PI, `openclaw doctor` warns and leaves the route unchanged.
|
||||
OpenAI agent model turns require the bundled Codex app-server plugin. Explicit
|
||||
PI runtime config remains available as an opt-in compatibility route. When PI is
|
||||
explicitly selected with an `openai-codex` auth profile, OpenClaw keeps the
|
||||
public model ref as `openai/*` and routes PI internally through the legacy
|
||||
Codex-auth transport. Run `openclaw doctor --fix` to repair stale
|
||||
`openai-codex/*` model refs or old PI session pins that do not come from
|
||||
explicit runtime config.
|
||||
</Note>
|
||||
|
||||
## OpenClaw feature coverage
|
||||
|
||||
| OpenAI capability | OpenClaw surface | Status |
|
||||
| ------------------------- | ---------------------------------------------------------- | ------------------------------------------------------ |
|
||||
| Chat / Responses | `openai/<model>` model provider | Yes |
|
||||
| Codex subscription models | `openai-codex/<model>` with `openai-codex` OAuth | Yes |
|
||||
| Codex app-server harness | `openai/<model>` with `agentRuntime.id: codex` | Yes |
|
||||
| Server-side web search | Native OpenAI Responses tool | Yes, when web search is enabled and no provider pinned |
|
||||
| Images | `image_generate` | Yes |
|
||||
| Videos | `video_generate` | Yes |
|
||||
| Text-to-speech | `messages.tts.provider: "openai"` / `tts` | Yes |
|
||||
| Batch speech-to-text | `tools.media.audio` / media understanding | Yes |
|
||||
| Streaming speech-to-text | Voice Call `streaming.provider: "openai"` | Yes |
|
||||
| Realtime voice | Voice Call `realtime.provider: "openai"` / Control UI Talk | Yes |
|
||||
| Embeddings | memory embedding provider | Yes |
|
||||
| OpenAI capability | OpenClaw surface | Status |
|
||||
| ------------------------- | ----------------------------------------------------------------- | ------------------------------------------------------ |
|
||||
| Chat / Responses | `openai/<model>` model provider | Yes |
|
||||
| Codex subscription models | `openai/<model>` with `openai-codex` OAuth | Yes |
|
||||
| Legacy Codex model refs | `openai-codex/<model>` | Repaired by doctor to `openai/<model>` |
|
||||
| Codex app-server harness | `openai/<model>` with omitted runtime or `agentRuntime.id: codex` | Yes |
|
||||
| Server-side web search | Native OpenAI Responses tool | Yes, when web search is enabled and no provider pinned |
|
||||
| Images | `image_generate` | Yes |
|
||||
| Videos | `video_generate` | Yes |
|
||||
| Text-to-speech | `messages.tts.provider: "openai"` / `tts` | Yes |
|
||||
| Batch speech-to-text | `tools.media.audio` / media understanding | Yes |
|
||||
| Streaming speech-to-text | Voice Call `streaming.provider: "openai"` | Yes |
|
||||
| Realtime voice | Voice Call `realtime.provider: "openai"` / Control UI Talk | Yes |
|
||||
| Embeddings | memory embedding provider | Yes |
|
||||
|
||||
## Memory embeddings
|
||||
|
||||
@@ -145,15 +152,15 @@ Choose your preferred auth method and follow the setup steps.
|
||||
|
||||
| Model ref | Runtime config | Route | Auth |
|
||||
| ---------------------- | -------------------------- | --------------------------- | ---------------- |
|
||||
| `openai/gpt-5.5` | omitted / `agentRuntime.id: "pi"` | Direct OpenAI Platform API | `OPENAI_API_KEY` |
|
||||
| `openai/gpt-5.4-mini` | omitted / `agentRuntime.id: "pi"` | Direct OpenAI Platform API | `OPENAI_API_KEY` |
|
||||
| `openai/gpt-5.5` | `agentRuntime.id: "codex"` | Codex app-server harness | Codex app-server |
|
||||
| `openai/gpt-5.5` | omitted / `agentRuntime.id: "codex"` | Codex app-server harness | `openai-codex` profile |
|
||||
| `openai/gpt-5.4-mini` | omitted / `agentRuntime.id: "codex"` | Codex app-server harness | `openai-codex` profile |
|
||||
| `openai/gpt-5.5` | `agentRuntime.id: "pi"` | PI embedded runtime | `openai` profile or selected `openai-codex` profile |
|
||||
|
||||
<Note>
|
||||
`openai/*` is the direct OpenAI API-key route unless you explicitly force
|
||||
the Codex app-server harness. Use `openai-codex/*` for Codex OAuth through
|
||||
the default PI runner, or use `openai/gpt-5.5` with
|
||||
`agentRuntime.id: "codex"` for native Codex app-server execution.
|
||||
`openai/*` agent models use the Codex app-server harness. To use API-key
|
||||
auth for an agent model, create an `openai-codex` API-key profile and order
|
||||
it with `auth.order.openai-codex`; `OPENAI_API_KEY` remains the direct
|
||||
fallback for non-agent OpenAI API surfaces.
|
||||
</Note>
|
||||
|
||||
### Config example
|
||||
@@ -165,6 +172,23 @@ Choose your preferred auth method and follow the setup steps.
|
||||
}
|
||||
```
|
||||
|
||||
To try ChatGPT's current Instant model from the OpenAI API, set the model
|
||||
to `openai/chat-latest`:
|
||||
|
||||
```json5
|
||||
{
|
||||
env: { OPENAI_API_KEY: "sk-..." },
|
||||
agents: { defaults: { model: { primary: "openai/chat-latest" } } },
|
||||
}
|
||||
```
|
||||
|
||||
`chat-latest` is a moving alias. OpenAI documents it as the latest Instant
|
||||
model used in ChatGPT and recommends `gpt-5.5` for production API usage, so
|
||||
keep `openai/gpt-5.5` as the stable default unless you explicitly want that
|
||||
alias behavior. The alias currently accepts only `medium` text verbosity, so
|
||||
OpenClaw normalizes incompatible OpenAI text-verbosity overrides for this
|
||||
model.
|
||||
|
||||
<Warning>
|
||||
OpenClaw does **not** expose `openai/gpt-5.3-codex-spark`. Live OpenAI API requests reject that model, and the current Codex catalog does not expose it either.
|
||||
</Warning>
|
||||
@@ -192,12 +216,14 @@ Choose your preferred auth method and follow the setup steps.
|
||||
openclaw models auth login --provider openai-codex --device-code
|
||||
```
|
||||
</Step>
|
||||
<Step title="Use the native Codex runtime">
|
||||
<Step title="Use the canonical OpenAI model route">
|
||||
```bash
|
||||
openclaw config set plugins.entries.codex '{"enabled":true}' --strict-json --merge
|
||||
openclaw config set agents.defaults.model.primary openai/gpt-5.5
|
||||
openclaw config set agents.defaults.agentRuntime '{"id":"codex"}' --strict-json
|
||||
```
|
||||
|
||||
No runtime config is required for the default path. OpenAI agent turns
|
||||
select the native Codex app-server runtime automatically, and OpenClaw
|
||||
installs or repairs the bundled Codex plugin when this route is chosen.
|
||||
</Step>
|
||||
<Step title="Verify Codex auth is available">
|
||||
```bash
|
||||
@@ -213,26 +239,22 @@ Choose your preferred auth method and follow the setup steps.
|
||||
|
||||
| Model ref | Runtime config | Route | Auth |
|
||||
|-----------|----------------|-------|------|
|
||||
| `openai/gpt-5.5` | `agentRuntime.id: "codex"` | Native Codex app-server harness | Codex sign-in or selected `openai-codex` profile |
|
||||
| `openai-codex/gpt-5.5` | omitted / `runtime: "pi"` | ChatGPT/Codex OAuth through PI | Codex sign-in |
|
||||
| `openai-codex/gpt-5.4-mini` | omitted / `runtime: "pi"` | ChatGPT/Codex OAuth through PI | Codex sign-in |
|
||||
| `openai-codex/gpt-5.5` | `runtime: "auto"` | Still PI unless a plugin explicitly claims `openai-codex` | Codex sign-in |
|
||||
| `openai/gpt-5.5` | omitted / `agentRuntime.id: "codex"` | Native Codex app-server harness | Codex sign-in or selected `openai-codex` profile |
|
||||
| `openai/gpt-5.5` | `agentRuntime.id: "pi"` | PI embedded runtime with internal Codex-auth transport | Selected `openai-codex` profile |
|
||||
| `openai-codex/gpt-5.5` | repaired by doctor | Legacy route rewritten to `openai/gpt-5.5` | Existing `openai-codex` profile |
|
||||
|
||||
<Warning>
|
||||
Do not configure older `openai-codex/gpt-5.1*`, `openai-codex/gpt-5.2*`, or
|
||||
`openai-codex/gpt-5.3*` model refs. ChatGPT/Codex OAuth accounts now reject
|
||||
those models. Use `openai-codex/gpt-5.5` for the PI OAuth route, or
|
||||
`openai/gpt-5.5` with `agentRuntime.id: "codex"` for native Codex runtime
|
||||
execution.
|
||||
those models. Use `openai/gpt-5.5`; OpenAI agent turns now select the Codex
|
||||
runtime by default.
|
||||
</Warning>
|
||||
|
||||
<Note>
|
||||
Keep using the `openai-codex` provider id for auth/profile commands. The
|
||||
`openai-codex/*` model prefix is also the explicit PI route for Codex OAuth.
|
||||
It does not select or auto-enable the bundled Codex app-server harness. For
|
||||
the common subscription plus native runtime setup, sign in with
|
||||
`openai-codex` but keep the model ref as `openai/gpt-5.5` and set
|
||||
`agentRuntime.id: "codex"`.
|
||||
`openai-codex/*` model prefix is legacy config repaired by doctor. For the
|
||||
common subscription plus native runtime setup, sign in with `openai-codex`
|
||||
but keep the model ref as `openai/gpt-5.5`.
|
||||
</Note>
|
||||
|
||||
### Config example
|
||||
@@ -249,9 +271,6 @@ Choose your preferred auth method and follow the setup steps.
|
||||
}
|
||||
```
|
||||
|
||||
To keep Codex OAuth on the normal PI runner instead, use
|
||||
`openai-codex/gpt-5.5` and omit the Codex runtime override.
|
||||
|
||||
<Note>
|
||||
Onboarding no longer imports OAuth material from `~/.codex`. Sign in with browser OAuth (default) or the device-code flow above — OpenClaw manages the resulting credentials in its own agent auth store.
|
||||
</Note>
|
||||
@@ -275,12 +294,11 @@ Choose your preferred auth method and follow the setup steps.
|
||||
openclaw models auth list --agent <id> --provider openai-codex
|
||||
```
|
||||
|
||||
If a 2026.5.5 `doctor --fix` run changed a GPT-5.5 subscription setup from
|
||||
`openai-codex/gpt-5.5` to `openai/gpt-5.5`, switch the default agent back
|
||||
to the Codex OAuth PI route:
|
||||
If an older config still has `openai-codex/gpt-*` or a stale OpenAI PI
|
||||
session pin without explicit runtime config, repair it:
|
||||
|
||||
```bash
|
||||
openclaw models set openai-codex/gpt-5.5
|
||||
openclaw doctor --fix
|
||||
openclaw config validate
|
||||
```
|
||||
|
||||
@@ -292,31 +310,27 @@ Choose your preferred auth method and follow the setup steps.
|
||||
openclaw models status --probe --probe-provider openai-codex
|
||||
```
|
||||
|
||||
`openai-codex/*` means ChatGPT/Codex OAuth through PI. `openai/*` with
|
||||
`agentRuntime.id: "codex"` means native Codex app-server execution.
|
||||
`openai-codex` remains the auth/profile provider id. `openai/*` is the
|
||||
model route for OpenAI agent turns through Codex.
|
||||
|
||||
### Status indicator
|
||||
|
||||
Chat `/status` shows which model runtime is active for the current session.
|
||||
The default PI harness appears as `Runtime: OpenClaw Pi Default`. When the
|
||||
bundled Codex app-server harness is selected, `/status` shows
|
||||
`Runtime: OpenAI Codex`. Existing sessions keep their recorded harness id, so use
|
||||
`/new` or `/reset` after changing `agentRuntime` if you want `/status` to
|
||||
reflect a new PI/Codex choice.
|
||||
The bundled Codex app-server harness appears as `Runtime: OpenAI Codex` for
|
||||
OpenAI agent model turns. Stale PI session pins are repaired to Codex unless
|
||||
config explicitly pins PI.
|
||||
|
||||
### Doctor warning
|
||||
|
||||
If the bundled `codex` plugin is enabled while an `openai-codex/*` route is
|
||||
selected, `openclaw doctor` warns that the model still resolves through PI.
|
||||
Keep the config unchanged only when that PI subscription-auth route is
|
||||
intentional. Switch to `openai/<model>` plus `agentRuntime.id: "codex"` when
|
||||
you want native Codex app-server execution.
|
||||
If `openai-codex/*` routes or stale OpenAI PI pins remain in config or
|
||||
session state, `openclaw doctor --fix` rewrites them to `openai/*` with the
|
||||
Codex runtime unless PI is explicitly configured.
|
||||
|
||||
### Context window cap
|
||||
|
||||
OpenClaw treats model metadata and the runtime context cap as separate values.
|
||||
|
||||
For `openai-codex/gpt-5.5` through Codex OAuth:
|
||||
For `openai/gpt-5.5` through the Codex OAuth catalog:
|
||||
|
||||
- Native `contextWindow`: `1000000`
|
||||
- Default runtime `contextTokens` cap: `272000`
|
||||
@@ -342,7 +356,7 @@ Choose your preferred auth method and follow the setup steps.
|
||||
### Catalog recovery
|
||||
|
||||
OpenClaw uses upstream Codex catalog metadata for `gpt-5.5` when it is
|
||||
present. If live Codex discovery omits the `openai-codex/gpt-5.5` row while
|
||||
present. If live Codex discovery omits the `gpt-5.5` row while
|
||||
the account is authenticated, OpenClaw synthesizes that OAuth model row so
|
||||
cron, sub-agent, and configured default-model runs do not fail with
|
||||
`Unknown model`.
|
||||
@@ -352,8 +366,9 @@ Choose your preferred auth method and follow the setup steps.
|
||||
|
||||
## Native Codex app-server auth
|
||||
|
||||
The native Codex app-server harness uses `openai/*` model refs plus
|
||||
`agentRuntime.id: "codex"`, but its auth is still account-based. OpenClaw
|
||||
The native Codex app-server harness uses `openai/*` model refs plus omitted
|
||||
runtime config or `agentRuntime.id: "codex"`, but its auth is still
|
||||
account-based. OpenClaw
|
||||
selects auth in this order:
|
||||
|
||||
1. An explicit OpenClaw `openai-codex` auth profile bound to the agent.
|
||||
@@ -487,7 +502,7 @@ See [Video Generation](/tools/video-generation) for shared tool parameters, prov
|
||||
|
||||
## GPT-5 prompt contribution
|
||||
|
||||
OpenClaw adds a shared GPT-5 prompt contribution for GPT-5-family runs across providers. It applies by model id, so `openai-codex/gpt-5.5`, `openai/gpt-5.5`, `openrouter/openai/gpt-5.5`, `opencode/gpt-5.5`, and other compatible GPT-5 refs receive the same overlay. Older GPT-4.x models do not.
|
||||
OpenClaw adds a shared GPT-5 prompt contribution for GPT-5-family runs across providers. It applies by model id, so `openai/gpt-5.5`, legacy pre-repair refs such as `openai-codex/gpt-5.5`, `openrouter/openai/gpt-5.5`, `opencode/gpt-5.5`, and other compatible GPT-5 refs receive the same overlay. Older GPT-4.x models do not.
|
||||
|
||||
The bundled native Codex harness uses the same GPT-5 behavior and heartbeat overlay through Codex app-server developer instructions, so `openai/gpt-5.x` sessions forced through `agentRuntime.id: "codex"` keep the same follow-through and proactive heartbeat guidance even though Codex owns the rest of the harness prompt.
|
||||
|
||||
@@ -775,7 +790,7 @@ the Server-side compaction accordion below.
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="Transport (WebSocket vs SSE)">
|
||||
OpenClaw uses WebSocket-first with SSE fallback (`"auto"`) for both `openai/*` and `openai-codex/*`.
|
||||
OpenClaw uses WebSocket-first with SSE fallback (`"auto"`) for `openai/*`.
|
||||
|
||||
In `"auto"` mode, OpenClaw:
|
||||
- Retries one early WebSocket failure before falling back to SSE
|
||||
@@ -797,9 +812,6 @@ the Server-side compaction accordion below.
|
||||
"openai/gpt-5.5": {
|
||||
params: { transport: "auto" },
|
||||
},
|
||||
"openai-codex/gpt-5.5": {
|
||||
params: { transport: "auto" },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -813,7 +825,7 @@ the Server-side compaction accordion below.
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="WebSocket warm-up">
|
||||
OpenClaw enables WebSocket warm-up by default for `openai/*` and `openai-codex/*` to reduce first-turn latency.
|
||||
OpenClaw enables WebSocket warm-up by default for `openai/*` to reduce first-turn latency.
|
||||
|
||||
```json5
|
||||
// Disable warm-up
|
||||
@@ -833,7 +845,7 @@ the Server-side compaction accordion below.
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Fast mode">
|
||||
OpenClaw exposes a shared fast-mode toggle for `openai/*` and `openai-codex/*`:
|
||||
OpenClaw exposes a shared fast-mode toggle for `openai/*`:
|
||||
|
||||
- **Chat/UI:** `/fast status|on|off`
|
||||
- **Config:** `agents.defaults.models["<provider>/<model>"].params.fastMode`
|
||||
|
||||
@@ -122,7 +122,7 @@ Use targeted local checks while iterating:
|
||||
pnpm test extensions/canvas/src/host/server.test.ts extensions/canvas/src/host/server.state-dir.test.ts extensions/canvas/src/host/file-resolver.test.ts
|
||||
pnpm test src/gateway/server.plugin-node-capability-auth.test.ts src/gateway/server-import-boundary.test.ts
|
||||
pnpm test extensions/canvas/src/config-migration.test.ts src/commands/doctor-legacy-config.migrations.test.ts
|
||||
pnpm test test/scripts/changed-lanes.test.ts test/scripts/build-all.test.ts test/scripts/bundle-a2ui.test.ts test/scripts/bundled-plugin-assets.test.ts src/scripts/canvas-a2ui-copy.test.ts src/infra/run-node.test.ts
|
||||
pnpm test test/scripts/changed-lanes.test.ts test/scripts/build-all.test.ts extensions/canvas/scripts/bundle-a2ui.test.ts test/scripts/bundled-plugin-assets.test.ts extensions/canvas/scripts/copy-a2ui.test.ts src/infra/run-node.test.ts
|
||||
pnpm tsgo:extensions
|
||||
pnpm plugins:inventory:check
|
||||
pnpm plugin-sdk:api:check
|
||||
|
||||
@@ -59,12 +59,13 @@ the maintainer-only release runbook.
|
||||
intentionally carried.
|
||||
4. Create `release/YYYY.M.D` from current `main`; do not do normal release work
|
||||
directly on `main`.
|
||||
5. Bump every required version location for the intended tag, run
|
||||
`pnpm plugins:sync` so publishable plugin packages share the release
|
||||
version and compatibility metadata, then run the local deterministic preflight:
|
||||
5. Bump every required version location for the intended tag, then run
|
||||
`pnpm release:prep`. It refreshes plugin versions, plugin inventory, config
|
||||
schema, bundled channel config metadata, config docs baseline, plugin SDK
|
||||
exports, and plugin SDK API baseline in the right order. Commit any generated
|
||||
drift before tagging. Then run the local deterministic preflight:
|
||||
`pnpm check:test-types`, `pnpm check:architecture`,
|
||||
`pnpm build && pnpm ui:build`, `pnpm plugins:sync:check`, and
|
||||
`pnpm release:check`.
|
||||
`pnpm build && pnpm ui:build`, and `pnpm release:check`.
|
||||
6. Run `OpenClaw NPM Release` with `preflight_only=true`. Before a tag exists,
|
||||
a full 40-character release-branch SHA is allowed for validation-only
|
||||
preflight. Save the successful `preflight_run_id`.
|
||||
@@ -80,9 +81,20 @@ the maintainer-only release runbook.
|
||||
dispatches all publishable plugin packages to npm and the same set to
|
||||
ClawHub in parallel, and then promotes the prepared OpenClaw npm preflight
|
||||
artifact with the matching dist-tag as soon as plugin npm publish succeeds.
|
||||
After the OpenClaw npm publish child succeeds, it creates or updates the
|
||||
matching GitHub release/prerelease page from the complete matching
|
||||
`CHANGELOG.md` section. Stable releases published to npm `latest` become the
|
||||
GitHub latest release; stable maintenance releases kept on npm `beta` are
|
||||
created with GitHub `latest=false`.
|
||||
ClawHub publishing may still be running while OpenClaw npm publishes, but the
|
||||
release publish workflow does not finish until both plugin publish paths and
|
||||
the OpenClaw npm publish path have completed successfully. After publish, run
|
||||
release publish workflow prints the child run IDs immediately. By default it
|
||||
does not wait for ClawHub after dispatching it, so OpenClaw npm availability
|
||||
is not blocked by slower ClawHub approvals or registry work; set
|
||||
`wait_for_clawhub=true` when ClawHub must block workflow completion. The
|
||||
ClawHub path retries transient CLI dependency install failures, publishes
|
||||
preview-passing plugins even when one preview cell flakes, and ends with
|
||||
registry verification for every expected plugin version so partial publishes
|
||||
remain visible and retryable. After publish, run
|
||||
the post-publish package
|
||||
acceptance against the published `openclaw@YYYY.M.D-beta.N` or
|
||||
`openclaw@beta` package. If a pushed or published prerelease needs a fix,
|
||||
@@ -95,9 +107,8 @@ the maintainer-only release runbook.
|
||||
packaged `.zip`, `.dmg`, `.dSYM.zip`, and updated `appcast.xml` on `main`.
|
||||
11. After publish, run the npm post-publish verifier, optional standalone
|
||||
published-npm Telegram E2E when you need post-publish channel proof,
|
||||
dist-tag promotion when needed, GitHub release/prerelease notes from the
|
||||
complete matching `CHANGELOG.md` section, and the release announcement
|
||||
steps.
|
||||
dist-tag promotion when needed, verify the generated GitHub release page,
|
||||
and run the release announcement steps.
|
||||
|
||||
## Release preflight
|
||||
|
||||
@@ -108,12 +119,13 @@ the maintainer-only release runbook.
|
||||
- Run `pnpm build && pnpm ui:build` before `pnpm release:check` so the expected
|
||||
`dist/*` release artifacts and Control UI bundle exist for the pack
|
||||
validation step
|
||||
- Run `pnpm plugins:sync` after the root version bump and before tagging. It
|
||||
updates publishable plugin package versions, OpenClaw peer/API compatibility
|
||||
metadata, build metadata, and plugin changelog stubs to match the core
|
||||
release version. `pnpm plugins:sync:check` is the non-mutating release guard;
|
||||
the publish workflow fails before any registry mutation if this step was
|
||||
forgotten.
|
||||
- Run `pnpm release:prep` after the root version bump and before tagging. It
|
||||
runs every deterministic release generator that commonly drifts after a
|
||||
version/config/API change: plugin versions, plugin inventory, base config
|
||||
schema, bundled channel config metadata, config docs baseline, plugin SDK
|
||||
exports, and plugin SDK API baseline. `pnpm release:check` re-runs those
|
||||
guards in check mode and reports every generated drift failure it finds in one
|
||||
pass before running package release checks.
|
||||
- Run the manual `Full Release Validation` workflow before release approval to
|
||||
kick off all pre-release test boxes from one entrypoint. It accepts a branch,
|
||||
tag, or full commit SHA, dispatches manual `CI`, and dispatches
|
||||
|
||||
@@ -17,11 +17,9 @@ OpenClaw integrates external CLIs via JSON-RPC. Two patterns are used today.
|
||||
|
||||
See [Signal](/channels/signal) for setup and endpoints.
|
||||
|
||||
## Pattern B: stdio child process (legacy: imsg)
|
||||
## Pattern B: stdio child process (imsg)
|
||||
|
||||
> **Note:** For new iMessage setups, use [BlueBubbles](/channels/bluebubbles) instead.
|
||||
|
||||
- OpenClaw spawns `imsg rpc` as a child process (legacy iMessage integration).
|
||||
- OpenClaw spawns `imsg rpc` as a child process for [iMessage](/channels/imessage).
|
||||
- JSON-RPC is line-delimited over stdin/stdout (one JSON object per line).
|
||||
- No TCP port, no daemon required.
|
||||
|
||||
|
||||
@@ -82,8 +82,6 @@ Scope intent:
|
||||
- `channels.irc.nickserv.password`
|
||||
- `channels.irc.accounts.*.password`
|
||||
- `channels.irc.accounts.*.nickserv.password`
|
||||
- `channels.bluebubbles.password`
|
||||
- `channels.bluebubbles.accounts.*.password`
|
||||
- `channels.feishu.appSecret`
|
||||
- `channels.feishu.encryptKey`
|
||||
- `channels.feishu.verificationToken`
|
||||
|
||||
@@ -60,20 +60,6 @@
|
||||
"optIn": true,
|
||||
"notes": "Compatibility exception: sibling ref field remains canonical."
|
||||
},
|
||||
{
|
||||
"id": "channels.bluebubbles.accounts.*.password",
|
||||
"configFile": "openclaw.json",
|
||||
"path": "channels.bluebubbles.accounts.*.password",
|
||||
"secretShape": "secret_input",
|
||||
"optIn": true
|
||||
},
|
||||
{
|
||||
"id": "channels.bluebubbles.password",
|
||||
"configFile": "openclaw.json",
|
||||
"path": "channels.bluebubbles.password",
|
||||
"secretShape": "secret_input",
|
||||
"optIn": true
|
||||
},
|
||||
{
|
||||
"id": "channels.discord.accounts.*.pluralkit.token",
|
||||
"configFile": "openclaw.json",
|
||||
|
||||
@@ -102,8 +102,7 @@ For a high-level overview, see [Onboarding (CLI)](/start/wizard).
|
||||
- [Google Chat](/channels/googlechat): service account JSON + webhook audience.
|
||||
- [Mattermost](/channels/mattermost) (plugin): bot token + base URL.
|
||||
- [Signal](/channels/signal): optional `signal-cli` install + account config.
|
||||
- [BlueBubbles](/channels/bluebubbles): **recommended for iMessage**; server URL + password + webhook.
|
||||
- [iMessage](/channels/imessage): legacy `imsg` CLI path + DB access.
|
||||
- [iMessage](/channels/imessage): `imsg` CLI path + Messages DB access; use an SSH wrapper when the Gateway runs off-Mac.
|
||||
- DM security: default is pairing. First DM sends a code; approve via `openclaw pairing approve <channel> <code>` or use allowlists.
|
||||
|
||||
</Step>
|
||||
@@ -249,5 +248,5 @@ will prompt to install it (npm or a local path) before it can be configured.
|
||||
- Onboarding overview: [Onboarding (CLI)](/start/wizard)
|
||||
- macOS app onboarding: [Onboarding](/start/onboarding)
|
||||
- Config reference: [Gateway configuration](/gateway/configuration)
|
||||
- Providers: [WhatsApp](/channels/whatsapp), [Telegram](/channels/telegram), [Discord](/channels/discord), [Google Chat](/channels/googlechat), [Signal](/channels/signal), [BlueBubbles](/channels/bluebubbles) (iMessage), [iMessage](/channels/imessage) (legacy)
|
||||
- Providers: [WhatsApp](/channels/whatsapp), [Telegram](/channels/telegram), [Discord](/channels/discord), [Google Chat](/channels/googlechat), [Signal](/channels/signal), [iMessage](/channels/imessage)
|
||||
- Skills: [Skills](/tools/skills), [Skills config](/tools/skills-config)
|
||||
|
||||
@@ -220,3 +220,12 @@ proxy:
|
||||
- Gateway control-plane proxy bypass is intentionally limited to `localhost` and literal loopback IP URLs. Use `ws://127.0.0.1:18789`, `ws://[::1]:18789`, or `ws://localhost:18789` for local direct Gateway control-plane connections; other hostnames route like ordinary hostname-based traffic.
|
||||
- OpenClaw does not inspect, test, or certify your proxy policy.
|
||||
- Treat proxy policy changes as security-sensitive operational changes.
|
||||
|
||||
| Surface | Managed proxy status |
|
||||
| ------------------------------------------------------------ | -------------------------------------------------------------------------------------------------- |
|
||||
| `fetch`, `node:http`, `node:https`, common WebSocket clients | Routed through managed proxy hooks when configured. |
|
||||
| APNs direct HTTP/2 | Routed through the APNs managed CONNECT helper. |
|
||||
| Gateway control-plane loopback | Direct only for the configured local loopback Gateway URL. |
|
||||
| Debug proxy upstream forwarding | Disabled while managed proxy mode is active unless explicitly enabled for local diagnostics. |
|
||||
| IRC | Raw TCP/TLS; not proxied by managed HTTP proxy mode. Disable unless direct IRC egress is approved. |
|
||||
| Other raw `net`, `tls`, or `http2` client calls | Must be classified by the raw socket guard before landing. |
|
||||
|
||||
@@ -39,7 +39,6 @@ For a complete map of the docs, see [Docs hubs](/start/hubs).
|
||||
- [Telegram](/channels/telegram)
|
||||
- [Discord](/channels/discord)
|
||||
- [Mattermost](/channels/mattermost)
|
||||
- [BlueBubbles (legacy iMessage bridge)](/channels/bluebubbles)
|
||||
- [QQ Bot](/channels/qqbot)
|
||||
- [iMessage](/channels/imessage)
|
||||
- [Groups](/channels/groups)
|
||||
|
||||
@@ -12,7 +12,7 @@ and a working chat session.
|
||||
|
||||
## What you need
|
||||
|
||||
- **Node.js** — Node 24 recommended (Node 22.14+ also supported)
|
||||
- **Node.js** — Node 24 recommended (Node 22.16+ also supported)
|
||||
- **An API key** from a model provider (Anthropic, OpenAI, Google, etc.) — onboarding will prompt you
|
||||
|
||||
<Tip>
|
||||
|
||||
@@ -73,7 +73,6 @@ Use these hubs to discover every page, including deep dives and reference docs t
|
||||
- [Discord](/channels/discord)
|
||||
- [Mattermost](/channels/mattermost)
|
||||
- [Signal](/channels/signal)
|
||||
- [BlueBubbles (legacy iMessage bridge)](/channels/bluebubbles)
|
||||
- [QQ Bot](/channels/qqbot)
|
||||
- [iMessage](/channels/imessage)
|
||||
- [Location parsing](/channels/location)
|
||||
|
||||
@@ -31,7 +31,7 @@ Regardless of which path you choose, onboarding sets up:
|
||||
2. **Workspace** — directory for agent files, bootstrap templates, and memory
|
||||
3. **Gateway** — port, bind address, auth mode
|
||||
4. **Channels** (optional) — built-in and bundled chat channels such as
|
||||
BlueBubbles, Discord, Feishu, Google Chat, Mattermost, Microsoft Teams,
|
||||
iMessage, Discord, Feishu, Google Chat, Mattermost, Microsoft Teams,
|
||||
Telegram, WhatsApp, and more
|
||||
5. **Daemon** (optional) — background service so the Gateway starts automatically
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ Pick a setup workflow based on how often you want updates and whether you want t
|
||||
|
||||
## Prereqs (from source)
|
||||
|
||||
- Node 24 recommended (Node 22 LTS, currently `22.14+`, still supported)
|
||||
- Node 24 recommended (Node 22 LTS, currently `22.16+`, still supported)
|
||||
- `pnpm` required for source checkouts. OpenClaw loads bundled plugins from the
|
||||
`extensions/*` pnpm workspace packages in dev mode, so root `npm install` does
|
||||
not prepare the full source tree.
|
||||
|
||||
@@ -17,7 +17,7 @@ Local mode (default) walks you through:
|
||||
- Model and auth setup (OpenAI Code subscription OAuth, Anthropic Claude CLI or API key, plus MiniMax, GLM, Ollama, Moonshot, StepFun, and AI Gateway options)
|
||||
- Workspace location and bootstrap files
|
||||
- Gateway settings (port, bind, auth, tailscale)
|
||||
- Channels and providers (Telegram, WhatsApp, Discord, Google Chat, Mattermost, Signal, BlueBubbles, and other bundled channel plugins)
|
||||
- Channels and providers (Telegram, WhatsApp, Discord, Google Chat, Mattermost, Signal, iMessage, and other bundled channel plugins)
|
||||
- Daemon install (LaunchAgent, systemd user unit, or native Windows Scheduled Task with Startup-folder fallback)
|
||||
- Health check
|
||||
- Skills setup
|
||||
@@ -70,8 +70,7 @@ It does not install or modify anything on the remote host.
|
||||
- [Google Chat](/channels/googlechat): service account JSON + webhook audience
|
||||
- [Mattermost](/channels/mattermost): bot token + base URL
|
||||
- [Signal](/channels/signal): optional `signal-cli` install + account config
|
||||
- [BlueBubbles](/channels/bluebubbles): recommended for iMessage; server URL + password + webhook
|
||||
- [iMessage](/channels/imessage): legacy `imsg` CLI path + DB access
|
||||
- [iMessage](/channels/imessage): `imsg` CLI path + Messages DB access; use an SSH wrapper when the Gateway runs off-Mac
|
||||
- DM security: default is pairing. First DM sends a code; approve via
|
||||
`openclaw pairing approve <channel> <code>` or use allowlists.
|
||||
</Step>
|
||||
|
||||
@@ -77,7 +77,7 @@ Onboarding starts with **QuickStart** (defaults) vs **Advanced** (full control).
|
||||
3. **Gateway** — Port, bind address, auth mode, Tailscale exposure.
|
||||
In interactive token mode, choose default plaintext token storage or opt into SecretRef.
|
||||
Non-interactive token SecretRef path: `--gateway-token-ref-env <ENV_VAR>`.
|
||||
4. **Channels** — built-in and bundled chat channels such as BlueBubbles, Discord, Feishu, Google Chat, Mattermost, Microsoft Teams, QQ Bot, Signal, Slack, Telegram, WhatsApp, and more.
|
||||
4. **Channels** — built-in and bundled chat channels such as iMessage, Discord, Feishu, Google Chat, Mattermost, Microsoft Teams, QQ Bot, Signal, Slack, Telegram, WhatsApp, and more.
|
||||
5. **Daemon** — Installs a LaunchAgent (macOS), systemd user unit (Linux/WSL2), or native Windows Scheduled Task with per-user Startup-folder fallback.
|
||||
If token auth requires a token and `gateway.auth.token` is SecretRef-managed, daemon install validates it but does not persist the resolved token into supervisor service environment metadata.
|
||||
If token auth requires a token and the configured token SecretRef is unresolved, daemon install is blocked with actionable guidance.
|
||||
|
||||
@@ -184,8 +184,8 @@ Quick `/acp` flow from chat:
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="Model / provider / runtime selection cheat sheet">
|
||||
- `openai-codex/*` - PI Codex OAuth/subscription route.
|
||||
- `openai/*` plus `agentRuntime.id: "codex"` - native Codex app-server embedded runtime.
|
||||
- `openai-codex/*` - legacy Codex OAuth/subscription model route repaired by doctor.
|
||||
- `openai/*` - native Codex app-server embedded runtime for OpenAI agent turns.
|
||||
- `/codex ...` - native Codex conversation control.
|
||||
- `/acp ...` or `runtime: "acp"` - explicit ACP/acpx control.
|
||||
|
||||
@@ -336,7 +336,6 @@ top-level `bindings[]` entries.
|
||||
|
||||
- **Discord channel/thread:** `match.channel="discord"` + `match.peer.id="<channelOrThreadId>"`
|
||||
- **Telegram forum topic:** `match.channel="telegram"` + `match.peer.id="<chatId>:topic:<topicId>"`
|
||||
- **BlueBubbles DM/group:** `match.channel="bluebubbles"` + `match.peer.id="<handle|chat_id:*|chat_guid:*|chat_identifier:*>"`. Prefer `chat_id:*` or `chat_identifier:*` for stable group bindings.
|
||||
- **iMessage DM/group:** `match.channel="imessage"` + `match.peer.id="<handle|chat_id:*|chat_guid:*|chat_identifier:*>"`. Prefer `chat_id:*` for stable group bindings.
|
||||
|
||||
</ParamField>
|
||||
|
||||
@@ -85,6 +85,23 @@ Returns `details.json` containing the parsed JSON (and validates against
|
||||
|
||||
## Example: Lobster workflow step
|
||||
|
||||
### Important limitation
|
||||
|
||||
The example below assumes the **standalone Lobster CLI** is running in an environment where `openclaw.invoke` already has the correct gateway URL/auth context.
|
||||
|
||||
For the bundled **embedded** Lobster runner inside OpenClaw, this nested CLI pattern is **not currently reliable**:
|
||||
|
||||
```lobster
|
||||
openclaw.invoke --tool llm-task --action json --args-json '{ ... }'
|
||||
```
|
||||
|
||||
Until embedded Lobster has a supported bridge for this flow, prefer either:
|
||||
|
||||
- direct `llm-task` tool calls outside Lobster, or
|
||||
- Lobster steps that do not rely on nested `openclaw.invoke` calls.
|
||||
|
||||
Standalone Lobster CLI example:
|
||||
|
||||
```lobster
|
||||
openclaw.invoke --tool llm-task --action json --args-json '{
|
||||
"prompt": "Given the input email, return intent and draft.",
|
||||
|
||||
@@ -100,7 +100,19 @@ Enable the tool:
|
||||
}
|
||||
```
|
||||
|
||||
Use it in a pipeline:
|
||||
### Important limitation: embedded Lobster vs `openclaw.invoke`
|
||||
|
||||
The bundled Lobster plugin runs workflows **in-process** inside the gateway. In that embedded mode, `openclaw.invoke` does **not** automatically inherit a gateway URL/auth context for nested OpenClaw CLI tool calls.
|
||||
|
||||
That means this pattern is **not currently reliable in the embedded runner**:
|
||||
|
||||
```lobster
|
||||
openclaw.invoke --tool llm-task --action json --args-json '{ ... }'
|
||||
```
|
||||
|
||||
Use the example below only when running the **standalone Lobster CLI** in an environment where `openclaw.invoke` is already configured with the correct gateway/auth context.
|
||||
|
||||
Use it in a standalone Lobster CLI pipeline:
|
||||
|
||||
```lobster
|
||||
openclaw.invoke --tool llm-task --action json --args-json '{
|
||||
@@ -119,6 +131,11 @@ openclaw.invoke --tool llm-task --action json --args-json '{
|
||||
}'
|
||||
```
|
||||
|
||||
If you are using the embedded Lobster plugin today, prefer either:
|
||||
|
||||
- a direct `llm-task` tool call outside Lobster, or
|
||||
- non-`openclaw.invoke` steps inside the Lobster pipeline until a supported embedded bridge is added.
|
||||
|
||||
See [LLM Task](/tools/llm-task) for details and configuration options.
|
||||
|
||||
## Workflow files (.lobster)
|
||||
|
||||
@@ -235,7 +235,6 @@ current OpenClaw or a local checkout until a newer npm package is published.
|
||||
|
||||
| Plugin | Package | Docs |
|
||||
| --------------- | -------------------------- | ------------------------------------------ |
|
||||
| BlueBubbles | `@openclaw/bluebubbles` | [BlueBubbles](/channels/bluebubbles) |
|
||||
| Discord | `@openclaw/discord` | [Discord](/channels/discord) |
|
||||
| Feishu | `@openclaw/feishu` | [Feishu](/channels/feishu) |
|
||||
| Matrix | `@openclaw/matrix` | [Matrix](/channels/matrix) |
|
||||
|
||||
@@ -709,8 +709,6 @@ delivery.
|
||||
`audio/ogg; codecs=opus`. If conversion fails, Feishu receives the original
|
||||
file as an attachment; WhatsApp send fails rather than posting an incompatible
|
||||
PTT payload.
|
||||
- **BlueBubbles**: keeps provider synthesis on the normal audio-file path; MP3
|
||||
and CAF outputs are marked for iMessage voice memo delivery.
|
||||
- **Other channels**: MP3 (`mp3_44100_128` from ElevenLabs, `mp3` from OpenAI).
|
||||
- 44.1kHz / 128kbps is the default balance for speech clarity.
|
||||
- **MiniMax**: MP3 (`speech-2.8-hd` model, 32kHz sample rate) for normal audio attachments. For channel-advertised voice-note targets, OpenClaw transcodes the MiniMax MP3 to 48kHz Opus with `ffmpeg` before delivery when the channel advertises transcoding.
|
||||
|
||||
@@ -105,7 +105,7 @@ Imported themes are stored only in the current browser profile. They are not wri
|
||||
- Channels: built-in plus bundled/external plugin channels status, QR login, and per-channel config (`channels.status`, `web.login.*`, `config.patch`).
|
||||
- Channel probe refreshes keep the previous snapshot visible while slow provider checks finish, and partial snapshots are labeled when a probe or audit exceeds its UI budget.
|
||||
- Instances: presence list + refresh (`system-presence`).
|
||||
- Sessions: list + per-session model/thinking/fast/verbose/trace/reasoning overrides (`sessions.list`, `sessions.patch`).
|
||||
- Sessions: list configured-agent sessions by default, fall back from stale unconfigured agent session keys, and apply per-session model/thinking/fast/verbose/trace/reasoning overrides (`sessions.list`, `sessions.patch`).
|
||||
- Dreams: dreaming status, enable/disable toggle, and Dream Diary reader (`doctor.memory.status`, `doctor.memory.dreamDiary`, `config.patch`).
|
||||
|
||||
</Accordion>
|
||||
@@ -164,6 +164,7 @@ Imported themes are stored only in the current browser profile. They are not wri
|
||||
- On desktop widths, chat controls stay on one compact row and collapse while scrolling down the transcript; scrolling up, returning to the top, or reaching the bottom restores the controls.
|
||||
- Consecutive duplicate text-only messages render as one bubble with a count badge. Messages that carry images, attachments, tool output, or canvas previews are left uncollapsed.
|
||||
- The chat header model and thinking pickers patch the active session immediately through `sessions.patch`; they are persistent session overrides, not one-turn-only send options.
|
||||
- If you send a message while a model picker change for the same session is still saving, the composer waits for that session patch before calling `chat.send` so the send uses the selected model.
|
||||
- Typing `/new` in the Control UI creates and switches to the same fresh dashboard session as New Chat. Typing `/reset` keeps the Gateway's explicit in-place reset for the current session.
|
||||
- The chat model picker requests the Gateway's configured model view. If `agents.defaults.models` is present, that allowlist drives the picker. Otherwise the picker shows explicit `models.providers.*.models` entries plus providers with usable auth. The full catalog stays available through the debug `models.list` RPC with `view: "all"`.
|
||||
- When fresh Gateway session usage reports include current context tokens, the chat composer area shows a compact context usage indicator. It switches to warning styling at high context pressure and, at recommended compaction levels, shows a compact button that runs the normal session compaction path. Stale token snapshots are hidden until the Gateway reports fresh usage again.
|
||||
|
||||
@@ -440,6 +440,108 @@ describe("active-memory plugin", () => {
|
||||
expect(runEmbeddedPiAgent).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("blocks gateway callers without admin scope from changing global active-memory config", async () => {
|
||||
const command = registeredCommands["active-memory"];
|
||||
|
||||
for (const { args, gatewayClientScopes } of [
|
||||
{ args: "off --global", gatewayClientScopes: ["operator.write"] },
|
||||
{ args: "on --global", gatewayClientScopes: ["operator.write"] },
|
||||
{ args: "disable --global", gatewayClientScopes: ["operator.write"] },
|
||||
{ args: "enable --global", gatewayClientScopes: ["operator.write"] },
|
||||
{ args: "disabled --global", gatewayClientScopes: ["operator.write"] },
|
||||
{ args: "enabled --global", gatewayClientScopes: ["operator.write"] },
|
||||
{ args: "off --global", gatewayClientScopes: [] },
|
||||
]) {
|
||||
const result = await command.handler({
|
||||
channel: "gateway",
|
||||
isAuthorizedSender: true,
|
||||
gatewayClientScopes,
|
||||
args,
|
||||
commandBody: `/active-memory ${args}`,
|
||||
config: {},
|
||||
requestConversationBinding: async () => ({ status: "error", message: "unsupported" }),
|
||||
detachConversationBinding: async () => ({ removed: false }),
|
||||
getCurrentConversationBinding: async () => null,
|
||||
});
|
||||
|
||||
expect(result.text).toContain("global enable/disable changes require operator.admin");
|
||||
}
|
||||
|
||||
expect(api.runtime.config.replaceConfigFile).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("allows admin-scoped gateway callers to change global active-memory config", async () => {
|
||||
const command = registeredCommands["active-memory"];
|
||||
|
||||
const result = await command.handler({
|
||||
channel: "gateway",
|
||||
isAuthorizedSender: true,
|
||||
gatewayClientScopes: ["operator.admin"],
|
||||
args: "off --global",
|
||||
commandBody: "/active-memory off --global",
|
||||
config: {},
|
||||
requestConversationBinding: async () => ({ status: "error", message: "unsupported" }),
|
||||
detachConversationBinding: async () => ({ removed: false }),
|
||||
getCurrentConversationBinding: async () => null,
|
||||
});
|
||||
|
||||
expect(result.text).toBe("Active Memory: off globally.");
|
||||
expect(api.runtime.config.replaceConfigFile).toHaveBeenCalledTimes(1);
|
||||
expect(configFile).toMatchObject({
|
||||
plugins: {
|
||||
entries: {
|
||||
"active-memory": {
|
||||
enabled: true,
|
||||
config: {
|
||||
enabled: false,
|
||||
agents: ["main"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps write-scoped gateway callers on non-global-write active-memory paths", async () => {
|
||||
const command = registeredCommands["active-memory"];
|
||||
const sessionKey = "agent:main:write-scoped-active-memory";
|
||||
hoisted.sessionStore[sessionKey] = {
|
||||
sessionId: "s-write-scoped-active-memory",
|
||||
updatedAt: 0,
|
||||
};
|
||||
|
||||
const globalStatusResult = await command.handler({
|
||||
channel: "gateway",
|
||||
isAuthorizedSender: true,
|
||||
gatewayClientScopes: ["operator.write"],
|
||||
args: "status --global",
|
||||
commandBody: "/active-memory status --global",
|
||||
config: {},
|
||||
requestConversationBinding: async () => ({ status: "error", message: "unsupported" }),
|
||||
detachConversationBinding: async () => ({ removed: false }),
|
||||
getCurrentConversationBinding: async () => null,
|
||||
});
|
||||
|
||||
expect(globalStatusResult.text).toBe("Active Memory: on globally.");
|
||||
expect(api.runtime.config.replaceConfigFile).not.toHaveBeenCalled();
|
||||
|
||||
const sessionOffResult = await command.handler({
|
||||
channel: "gateway",
|
||||
isAuthorizedSender: true,
|
||||
gatewayClientScopes: ["operator.write"],
|
||||
sessionKey,
|
||||
args: "off",
|
||||
commandBody: "/active-memory off",
|
||||
config: {},
|
||||
requestConversationBinding: async () => ({ status: "error", message: "unsupported" }),
|
||||
detachConversationBinding: async () => ({ removed: false }),
|
||||
getCurrentConversationBinding: async () => null,
|
||||
});
|
||||
|
||||
expect(sessionOffResult.text).toBe("Active Memory: off for this session.");
|
||||
expect(api.runtime.config.replaceConfigFile).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("uses live runtime config for before_prompt_build enablement", async () => {
|
||||
configFile = {
|
||||
plugins: {
|
||||
|
||||
@@ -782,6 +782,13 @@ function updateActiveMemoryGlobalEnabledInConfig(
|
||||
};
|
||||
}
|
||||
|
||||
function requiresAdminToMutateActiveMemoryGlobal(gatewayClientScopes?: readonly string[]): boolean {
|
||||
return Array.isArray(gatewayClientScopes) && !gatewayClientScopes.includes("operator.admin");
|
||||
}
|
||||
|
||||
const ACTIVE_MEMORY_GLOBAL_MUTATION_ADMIN_REQUIRED_TEXT =
|
||||
"⚠️ /active-memory global enable/disable changes require operator.admin for gateway clients.";
|
||||
|
||||
function normalizePluginConfig(pluginConfig: unknown): ResolvedActiveRecallPluginConfig {
|
||||
const raw = (
|
||||
pluginConfig && typeof pluginConfig === "object" ? pluginConfig : {}
|
||||
@@ -2819,6 +2826,11 @@ export default definePluginEntry({
|
||||
text: `Active Memory: ${isActiveMemoryGloballyEnabled(currentConfig) ? "on" : "off"} globally.`,
|
||||
};
|
||||
}
|
||||
if (requiresAdminToMutateActiveMemoryGlobal(ctx.gatewayClientScopes)) {
|
||||
return {
|
||||
text: ACTIVE_MEMORY_GLOBAL_MUTATION_ADMIN_REQUIRED_TEXT,
|
||||
};
|
||||
}
|
||||
if (action === "on" || action === "enable" || action === "enabled") {
|
||||
const nextConfig = updateActiveMemoryGlobalEnabledInConfig(currentConfig, true);
|
||||
await api.runtime.config.replaceConfigFile({
|
||||
|
||||
@@ -92,6 +92,12 @@ describe("arcee provider plugin", () => {
|
||||
"trinity-large-preview",
|
||||
"trinity-large-thinking",
|
||||
]);
|
||||
expect(
|
||||
catalogProvider.models?.find((model) => model.id === "trinity-large-thinking")?.compat,
|
||||
).toMatchObject({
|
||||
supportsTools: false,
|
||||
supportsReasoningEffort: false,
|
||||
});
|
||||
});
|
||||
|
||||
it("builds the OpenRouter-backed Arcee AI model catalog", async () => {
|
||||
@@ -112,6 +118,12 @@ describe("arcee provider plugin", () => {
|
||||
"arcee/trinity-large-preview",
|
||||
"arcee/trinity-large-thinking",
|
||||
]);
|
||||
expect(
|
||||
catalogProvider.models?.find((model) => model.id === "arcee/trinity-large-thinking")?.compat,
|
||||
).toMatchObject({
|
||||
supportsTools: false,
|
||||
supportsReasoningEffort: false,
|
||||
});
|
||||
});
|
||||
|
||||
it("normalizes Arcee OpenRouter models to vendor-prefixed runtime ids", async () => {
|
||||
|
||||
@@ -45,6 +45,7 @@ export const ARCEE_MODEL_CATALOG: ModelDefinitionConfig[] = [
|
||||
cacheWrite: 0.25,
|
||||
},
|
||||
compat: {
|
||||
supportsTools: false,
|
||||
supportsReasoningEffort: false,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
# BlueBubbles extension (developer reference)
|
||||
|
||||
This package contains the **BlueBubbles external channel plugin** for OpenClaw.
|
||||
|
||||
If you’re looking for **how to use BlueBubbles as an agent/tool user**, see:
|
||||
|
||||
- `skills/bluebubbles/SKILL.md`
|
||||
|
||||
## Layout
|
||||
|
||||
- Package entry: `index.ts`.
|
||||
- Channel implementation: `src/channel.ts`.
|
||||
- Webhook handling: `src/monitor.ts` (register per-account route via `registerPluginHttpRoute`).
|
||||
- REST helpers: `src/send.ts` + `src/probe.ts`.
|
||||
- Runtime bridge: `src/runtime.ts` (set via `api.runtime`).
|
||||
- Catalog entry for setup selection: `src/channels/plugins/catalog.ts`.
|
||||
|
||||
## Internal helpers (use these, not raw API calls)
|
||||
|
||||
- `probeBlueBubbles` in `src/probe.ts` for health checks.
|
||||
- `sendMessageBlueBubbles` in `src/send.ts` for text delivery.
|
||||
- `resolveChatGuidForTarget` in `src/send.ts` for chat lookup.
|
||||
- `sendBlueBubblesReaction` in `src/reactions.ts` for tapbacks.
|
||||
- `sendBlueBubblesTyping` + `markBlueBubblesChatRead` in `src/chat.ts`.
|
||||
- `downloadBlueBubblesAttachment` in `src/attachments.ts` for inbound media.
|
||||
- `buildBlueBubblesApiUrl` + `blueBubblesFetchWithTimeout` in `src/types.ts` for shared REST plumbing.
|
||||
|
||||
## Webhooks
|
||||
|
||||
- BlueBubbles posts JSON to the gateway HTTP server.
|
||||
- Normalize sender/chat IDs defensively (payloads vary by version).
|
||||
- Skip messages marked as from self.
|
||||
- Route into core reply pipeline via the plugin runtime (`api.runtime`) and `openclaw/plugin-sdk` helpers.
|
||||
- For attachments/stickers, use `<media:...>` placeholders when text is empty and attach media paths via `MediaUrl(s)` in the inbound context.
|
||||
|
||||
## Config (core)
|
||||
|
||||
- `channels.bluebubbles.serverUrl` (base URL), `channels.bluebubbles.password`, `channels.bluebubbles.webhookPath`.
|
||||
- Action gating: `channels.bluebubbles.actions.reactions` (default true).
|
||||
|
||||
## Message tool notes
|
||||
|
||||
- **Reactions:** the `react` action requires a `target` (phone number or chat identifier) in addition to `messageId`.
|
||||
Example:
|
||||
`action=react target=+15551234567 messageId=ABC123 emoji=❤️`
|
||||
@@ -1,18 +0,0 @@
|
||||
export { bluebubblesPlugin } from "./src/channel.js";
|
||||
export { bluebubblesSetupPlugin } from "./src/channel.setup.js";
|
||||
export {
|
||||
matchBlueBubblesAcpConversation,
|
||||
normalizeBlueBubblesAcpConversationId,
|
||||
resolveBlueBubblesConversationIdFromTarget,
|
||||
resolveBlueBubblesInboundConversationId,
|
||||
} from "./src/conversation-id.js";
|
||||
export {
|
||||
__testing,
|
||||
createBlueBubblesConversationBindingManager,
|
||||
} from "./src/conversation-bindings.js";
|
||||
export { collectBlueBubblesStatusIssues } from "./src/status-issues.js";
|
||||
export {
|
||||
resolveBlueBubblesGroupRequireMention,
|
||||
resolveBlueBubblesGroupToolPolicy,
|
||||
} from "./src/group-policy.js";
|
||||
export { isAllowedBlueBubblesSender } from "./src/targets.js";
|
||||
@@ -1 +0,0 @@
|
||||
export { BlueBubblesChannelConfigSchema } from "./src/config-schema.js";
|
||||
@@ -1,3 +0,0 @@
|
||||
// Keep bundled channel entry imports narrow so bootstrap/discovery paths do
|
||||
// not drag setup-only BlueBubbles surfaces into lightweight channel plugin loads.
|
||||
export { bluebubblesPlugin } from "./src/channel.js";
|
||||
@@ -1,8 +0,0 @@
|
||||
export {
|
||||
collectRuntimeConfigAssignments,
|
||||
secretTargetRegistryEntries,
|
||||
} from "./src/secret-contract.js";
|
||||
export {
|
||||
__testing as blueBubblesConversationBindingTesting,
|
||||
createBlueBubblesConversationBindingManager,
|
||||
} from "./src/conversation-bindings.js";
|
||||
@@ -1 +0,0 @@
|
||||
export { normalizeCompatibilityConfig, legacyConfigRules } from "./src/doctor-contract.js";
|
||||
@@ -1,20 +0,0 @@
|
||||
import { defineBundledChannelEntry } from "openclaw/plugin-sdk/channel-entry-contract";
|
||||
|
||||
export default defineBundledChannelEntry({
|
||||
id: "bluebubbles",
|
||||
name: "BlueBubbles",
|
||||
description: "BlueBubbles channel plugin (macOS app)",
|
||||
importMetaUrl: import.meta.url,
|
||||
plugin: {
|
||||
specifier: "./channel-plugin-api.js",
|
||||
exportName: "bluebubblesPlugin",
|
||||
},
|
||||
secrets: {
|
||||
specifier: "./secret-contract-api.js",
|
||||
exportName: "channelSecrets",
|
||||
},
|
||||
runtime: {
|
||||
specifier: "./runtime-api.js",
|
||||
exportName: "setBlueBubblesRuntime",
|
||||
},
|
||||
});
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"id": "bluebubbles",
|
||||
"activation": {
|
||||
"onStartup": false
|
||||
},
|
||||
"channels": ["bluebubbles"],
|
||||
"configSchema": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user