Compare commits
395 Commits
v2026.5.25
...
codex/spli
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dde017027c | ||
|
|
2b63eb2825 | ||
|
|
6930538500 | ||
|
|
cd46057b90 | ||
|
|
8c575bd3c8 | ||
|
|
598aad4f66 | ||
|
|
1fd8de8495 | ||
|
|
564e0bb5c1 | ||
|
|
c867ecb136 | ||
|
|
9fd8158c06 | ||
|
|
7a147419db | ||
|
|
a5eee8f1c6 | ||
|
|
3c6fd49d74 | ||
|
|
e8f584e400 | ||
|
|
7e6837bc07 | ||
|
|
0ec29289c6 | ||
|
|
82dae95c76 | ||
|
|
c147e27f5a | ||
|
|
081e29595e | ||
|
|
6c18c212e9 | ||
|
|
9e43d0327f | ||
|
|
5535eef6b0 | ||
|
|
84b9704ccc | ||
|
|
27359ec417 | ||
|
|
cf21c8abcb | ||
|
|
c84f61cd2e | ||
|
|
fdb7848a7c | ||
|
|
496fd8f853 | ||
|
|
373b3bfe54 | ||
|
|
d5bf325126 | ||
|
|
645cbf6c33 | ||
|
|
12b81d8978 | ||
|
|
06afc57102 | ||
|
|
c7821bd2a8 | ||
|
|
9ced76a4bb | ||
|
|
7671068daf | ||
|
|
ead847f606 | ||
|
|
b7c461af7b | ||
|
|
0973a7e4e4 | ||
|
|
d001d35ea2 | ||
|
|
d6fcb562f4 | ||
|
|
6118f3f615 | ||
|
|
fb853de554 | ||
|
|
e96cde7e14 | ||
|
|
5ef812293b | ||
|
|
0f605ee003 | ||
|
|
e89afa6afa | ||
|
|
dc0d4c263e | ||
|
|
d54c90699f | ||
|
|
4ff5a6152c | ||
|
|
cf6f9ad8a3 | ||
|
|
19e4c37c37 | ||
|
|
35310dce8c | ||
|
|
8685dbd547 | ||
|
|
d1c8f09b00 | ||
|
|
42ba297b0a | ||
|
|
4d4e2ec256 | ||
|
|
cac0b2db18 | ||
|
|
45feb37b13 | ||
|
|
ce61d224d8 | ||
|
|
c38b5033e6 | ||
|
|
0cca7861c1 | ||
|
|
d0dd8b8a41 | ||
|
|
295b5ea9ab | ||
|
|
8c7f226401 | ||
|
|
e37ac22fdd | ||
|
|
50c7d780dc | ||
|
|
4c6aeb9bb2 | ||
|
|
9777526eaa | ||
|
|
84e4bff73b | ||
|
|
13f72e4102 | ||
|
|
a17ac3ec9d | ||
|
|
e549d0c235 | ||
|
|
8d6a6e9f89 | ||
|
|
df13d3a724 | ||
|
|
a07dc3896b | ||
|
|
30e59b4090 | ||
|
|
dfe94ff048 | ||
|
|
419178b9bc | ||
|
|
efebf6bfcf | ||
|
|
cb34175dfd | ||
|
|
884d346999 | ||
|
|
13c6a3332c | ||
|
|
a3bb4fe814 | ||
|
|
31a8fe7462 | ||
|
|
92fb79ee69 | ||
|
|
30c4489af4 | ||
|
|
94a04e1aa6 | ||
|
|
8307e2f762 | ||
|
|
5b49433535 | ||
|
|
c2b1d20c25 | ||
|
|
18ff19e043 | ||
|
|
f0599fddac | ||
|
|
fe9f28f520 | ||
|
|
71e7a1fd7d | ||
|
|
92082723f7 | ||
|
|
e20b8d70a6 | ||
|
|
198d0a56d3 | ||
|
|
11512b1257 | ||
|
|
d1f2eb0709 | ||
|
|
e8cb2b5ab3 | ||
|
|
dcf0941cd6 | ||
|
|
da16a966c3 | ||
|
|
4ebc13abe1 | ||
|
|
f1ceed94db | ||
|
|
68f877ef66 | ||
|
|
1c5b8353d6 | ||
|
|
7aedff8fbb | ||
|
|
f2ad94ec9a | ||
|
|
8e110a2122 | ||
|
|
4c8e9da033 | ||
|
|
55af31e0c6 | ||
|
|
4f1cd8eb00 | ||
|
|
e295c86dbc | ||
|
|
91080fde68 | ||
|
|
4838e704a0 | ||
|
|
21aebd5fbc | ||
|
|
29919cbec5 | ||
|
|
90bcec9fa4 | ||
|
|
0e733795f4 | ||
|
|
99032f0354 | ||
|
|
f63754b314 | ||
|
|
b34e1b32d8 | ||
|
|
9434228cdc | ||
|
|
21000a3da7 | ||
|
|
3f6b63aa1d | ||
|
|
c5530c798c | ||
|
|
d3bbfa1f5a | ||
|
|
a5653c0ce9 | ||
|
|
b93cee45d0 | ||
|
|
3548cff14b | ||
|
|
b377618fae | ||
|
|
437a9e9171 | ||
|
|
abc7b7b331 | ||
|
|
2e17003165 | ||
|
|
918472a27b | ||
|
|
4a1d772f3d | ||
|
|
4beadbf951 | ||
|
|
6c5b39291f | ||
|
|
3b023e9bdb | ||
|
|
a3cd90fb5a | ||
|
|
17f7ef5c0f | ||
|
|
41eef4a796 | ||
|
|
a46556a6c2 | ||
|
|
81f62a689b | ||
|
|
083377adb8 | ||
|
|
4b03e07294 | ||
|
|
16d137dce6 | ||
|
|
3452382cc0 | ||
|
|
11b1b7c888 | ||
|
|
5c3fb1f9d1 | ||
|
|
c04c03f8e9 | ||
|
|
505aca9ef7 | ||
|
|
5174d9744e | ||
|
|
23e9bc8c0b | ||
|
|
711e963723 | ||
|
|
7db4b3db41 | ||
|
|
c14c043be7 | ||
|
|
3bb4be23c0 | ||
|
|
72a7d6a8dc | ||
|
|
e752f9bca1 | ||
|
|
c43ed9e4fe | ||
|
|
1e9b6b7627 | ||
|
|
a9bf582684 | ||
|
|
21aefb877a | ||
|
|
c4f0682396 | ||
|
|
4118a32aad | ||
|
|
4fdf61753a | ||
|
|
bc3d6bafae | ||
|
|
17ab9b967c | ||
|
|
947febb2fb | ||
|
|
bee8ad34a0 | ||
|
|
7fbca96a0c | ||
|
|
bcde7b138a | ||
|
|
0d23c3b4e1 | ||
|
|
a695c28bfb | ||
|
|
c9d0464ed1 | ||
|
|
5a33378f9c | ||
|
|
609d70d35e | ||
|
|
4738d0a296 | ||
|
|
34d862d45d | ||
|
|
f32273257c | ||
|
|
eab8d29db2 | ||
|
|
93015982d3 | ||
|
|
6f57286678 | ||
|
|
0c5f622f9a | ||
|
|
3d0659433e | ||
|
|
fddca995e8 | ||
|
|
2e6ba44706 | ||
|
|
6984a823af | ||
|
|
743bce2c27 | ||
|
|
f824e1596a | ||
|
|
592f192bf0 | ||
|
|
010a79b5d8 | ||
|
|
8f1f7901b9 | ||
|
|
c410658725 | ||
|
|
e049105891 | ||
|
|
f2142ebf3a | ||
|
|
669df88249 | ||
|
|
c9364f03dc | ||
|
|
24d58af560 | ||
|
|
6421808c27 | ||
|
|
80aa6d77fc | ||
|
|
d00d0a21c2 | ||
|
|
321f06ad0e | ||
|
|
ee51169b20 | ||
|
|
9e93431ae9 | ||
|
|
56633e4f3c | ||
|
|
ea2496b00c | ||
|
|
ef8619d5f5 | ||
|
|
71e9eaab14 | ||
|
|
c59635ae97 | ||
|
|
6814525867 | ||
|
|
1514cc84cb | ||
|
|
6defcb0a40 | ||
|
|
60afca187d | ||
|
|
719ce7f96f | ||
|
|
57748a66fd | ||
|
|
2a6b4ed3e2 | ||
|
|
978a2d01da | ||
|
|
3a4f2b17fc | ||
|
|
6c7b3f3f23 | ||
|
|
068924e2d4 | ||
|
|
5dc704361f | ||
|
|
bef0ba8f5a | ||
|
|
84929e4265 | ||
|
|
c87957db5e | ||
|
|
65a210553b | ||
|
|
fe3374789f | ||
|
|
da831e2b8a | ||
|
|
399c692895 | ||
|
|
fc2d2d595c | ||
|
|
342bde2af6 | ||
|
|
d7361eff66 | ||
|
|
c1a026a976 | ||
|
|
1d21224de3 | ||
|
|
a4f12699cf | ||
|
|
acbdb8c373 | ||
|
|
00f9809531 | ||
|
|
bec7d56b73 | ||
|
|
68ab48b179 | ||
|
|
ec7ad3b4ac | ||
|
|
1531fe2525 | ||
|
|
0164fd5e99 | ||
|
|
5e8a9a905d | ||
|
|
75ac0b5ed9 | ||
|
|
0f35ec29d3 | ||
|
|
fda0141a01 | ||
|
|
48adcb162c | ||
|
|
3a48366f3e | ||
|
|
75c6cf2966 | ||
|
|
0f54221f86 | ||
|
|
0a38932ed9 | ||
|
|
94968c83c6 | ||
|
|
2ffd7a7172 | ||
|
|
7b30291cc4 | ||
|
|
116c600f60 | ||
|
|
9c79a0f8f4 | ||
|
|
16702496c6 | ||
|
|
6e85869161 | ||
|
|
1cc0a96df1 | ||
|
|
c4c80cea35 | ||
|
|
9cb1e4799c | ||
|
|
63dee51dfb | ||
|
|
cd96542d37 | ||
|
|
55c9a6beea | ||
|
|
9be760fb37 | ||
|
|
99d96c1ff2 | ||
|
|
3b0805414e | ||
|
|
5b6d03e3e2 | ||
|
|
0d4575a241 | ||
|
|
a122d804dd | ||
|
|
4424dafe64 | ||
|
|
0f67dfd074 | ||
|
|
f4cfa012e1 | ||
|
|
5dccba7405 | ||
|
|
f6a49a4e8a | ||
|
|
cda7c30150 | ||
|
|
9f7485e182 | ||
|
|
c51fa0d127 | ||
|
|
148db14736 | ||
|
|
5a9673ecd7 | ||
|
|
f1197ed6fc | ||
|
|
4e9dac5e00 | ||
|
|
b30f8e5290 | ||
|
|
2afb8198c1 | ||
|
|
009b18c1f4 | ||
|
|
77d9ac30bb | ||
|
|
a98660eebd | ||
|
|
c55bee5ec7 | ||
|
|
fe14bcecee | ||
|
|
aa05c5c9dd | ||
|
|
e7c7ee4385 | ||
|
|
36f269d60b | ||
|
|
117e08240b | ||
|
|
9a6c16130a | ||
|
|
aff8e644fc | ||
|
|
fe8d99d421 | ||
|
|
78a1e7dfe6 | ||
|
|
623a60a2b7 | ||
|
|
2aa5f1771f | ||
|
|
778fa8705c | ||
|
|
fef57f99ba | ||
|
|
74f3a1eee2 | ||
|
|
c88f660258 | ||
|
|
a0023fbfa0 | ||
|
|
d0ab0d9922 | ||
|
|
170e0aac2a | ||
|
|
423f7d22bc | ||
|
|
5b6d409248 | ||
|
|
f00a912c25 | ||
|
|
baab4cf045 | ||
|
|
e844d1d6e5 | ||
|
|
a61d5308b5 | ||
|
|
9b9d8970b0 | ||
|
|
8351556059 | ||
|
|
bdc6b32828 | ||
|
|
985bc934a1 | ||
|
|
c916906584 | ||
|
|
9330b76a51 | ||
|
|
1e188bcda9 | ||
|
|
407cf8e328 | ||
|
|
c0f2d89c20 | ||
|
|
915c820c38 | ||
|
|
cd7994f227 | ||
|
|
44bb0be033 | ||
|
|
cf275676f3 | ||
|
|
baf469f02e | ||
|
|
f01b2a8eab | ||
|
|
f5d2db2a60 | ||
|
|
9445960d9d | ||
|
|
207a5a2983 | ||
|
|
48532227d5 | ||
|
|
804a31ec5c | ||
|
|
6ccd4e72f0 | ||
|
|
b5ada806dd | ||
|
|
177ebdc24c | ||
|
|
b0c8a4d11d | ||
|
|
bc12e04993 | ||
|
|
6e8d2dbbbc | ||
|
|
8129dba5d8 | ||
|
|
7cd15d2493 | ||
|
|
822ee62947 | ||
|
|
aafed830a5 | ||
|
|
f87aa0ff1b | ||
|
|
8061d66713 | ||
|
|
17954a4f33 | ||
|
|
c5b987274a | ||
|
|
b83dfcb953 | ||
|
|
bd65b4232a | ||
|
|
5ae91f01fa | ||
|
|
3eb06e305e | ||
|
|
5cfa577778 | ||
|
|
d967760b41 | ||
|
|
d5b0174eb1 | ||
|
|
313762282c | ||
|
|
a11d4e6871 | ||
|
|
1b64ccbfff | ||
|
|
159e4406ab | ||
|
|
f271f003d4 | ||
|
|
dd375f9fc3 | ||
|
|
4012ae4f42 | ||
|
|
fc93af5637 | ||
|
|
a9c91ca81f | ||
|
|
657b246e56 | ||
|
|
fbb6340542 | ||
|
|
abe99230df | ||
|
|
dc17412c3a | ||
|
|
f0b6f70053 | ||
|
|
99997e4441 | ||
|
|
633e4b8a7c | ||
|
|
69d728ac4f | ||
|
|
2cac9e54b4 | ||
|
|
50d6611c10 | ||
|
|
8a93851ee2 | ||
|
|
e97e831c12 | ||
|
|
3f363e0450 | ||
|
|
89aea9b843 | ||
|
|
c4bce00727 | ||
|
|
bc10fad79c | ||
|
|
8f260de3e7 | ||
|
|
276ba1090e | ||
|
|
16ffc2507a | ||
|
|
8da8bc4aad | ||
|
|
bb6f37e777 | ||
|
|
aa702cf3db | ||
|
|
6f695c1864 | ||
|
|
277d8fece2 | ||
|
|
026cfb6ba1 | ||
|
|
e7ad116b9b | ||
|
|
2e3b59bc58 | ||
|
|
489e415339 | ||
|
|
459e89ada8 | ||
|
|
0ab63e2b18 | ||
|
|
f0bfb3fc33 |
@@ -17,7 +17,9 @@ Best-effort local-only provenance for OpenClaw PR/issue bodies. Use during agent
|
||||
- Fail closed on unresolved secrets, private keys, browser/session/cookie details, or auth URLs.
|
||||
- Drop system/developer prompts, raw tool outputs, reasoning, env, cookies, tokens, and broad local paths.
|
||||
- Keep user prompts, assistant visible decisions, terse tool summaries, and test/proof outcomes.
|
||||
- Remove session turns unrelated to the PR/issue work. Use the PR/issue title, branch name, changed files, and stated goal as scope; omit earlier/later unrelated tasks even when they are in the same session log.
|
||||
- Best effort only: PR/issue creation must continue if no safe transcript is found.
|
||||
- Add the `## Agent Transcript` section only when inserting a real transcript. Never add a placeholder transcript heading or text such as "A sanitized local transcript preview was generated but not included."
|
||||
- Use a collapsed `<details>` section and update existing markers instead of duplicating sections.
|
||||
|
||||
## Helper
|
||||
@@ -35,6 +37,8 @@ Find a likely local session:
|
||||
--since-days 14
|
||||
```
|
||||
|
||||
`find` scans the newest 400 matching local JSONL logs by default across Codex, Claude, Pi, and OpenClaw agent sessions. Use `--max-files N` for a wider local search.
|
||||
|
||||
Render a PR/issue body section:
|
||||
|
||||
```bash
|
||||
@@ -68,9 +72,10 @@ Append/update a body file before `gh pr create --body-file` or connector PR crea
|
||||
3. If a high-confidence session is found, ask:
|
||||
`Include a redacted agent transcript? It helps reviewers and can make the PR easier to prioritize. I can open a local preview first.`
|
||||
4. If the user wants preview, run `preview`, open the HTML with `open`, and wait for confirmation.
|
||||
5. If the user approves, run `append-body`.
|
||||
6. Use the enriched body file for creation/update.
|
||||
7. If no safe session is found, say nothing and continue without transcript. If the user declines, continue without transcript.
|
||||
5. Before insertion, trim unrelated session turns from the generated section. Keep only turns that explain this PR/issue's goal, implementation choices, files, tests, proof, blockers, and final outcome.
|
||||
6. If the user approves, run `append-body`.
|
||||
7. Use the enriched body file for creation/update.
|
||||
8. If no safe session is found, say nothing and continue without transcript. If the user declines, continue without transcript and do not add any transcript placeholder section.
|
||||
|
||||
## Review Artifacts
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ const DEFAULT_ENTRY_MAX_CHARS = 6000;
|
||||
|
||||
function usage() {
|
||||
console.log(`Usage:
|
||||
agent-transcript find --query TEXT [--cwd PATH] [--since-days N] [--root PATH...]
|
||||
agent-transcript find --query TEXT [--cwd PATH] [--since-days N] [--max-files N] [--root PATH...]
|
||||
agent-transcript render --session FILE [--out FILE] [--max-chars N] [--entry-max-chars N] [--title TEXT] [--url URL]
|
||||
agent-transcript preview --session FILE [--out FILE] [--max-chars N] [--entry-max-chars N] [--title TEXT] [--url URL]
|
||||
agent-transcript append-body --body FILE --session FILE [--out FILE] [--max-chars N] [--entry-max-chars N]
|
||||
@@ -51,11 +51,35 @@ function homePath(...parts) {
|
||||
return path.join(os.homedir(), ...parts);
|
||||
}
|
||||
|
||||
function openClawSessionRoots() {
|
||||
const stateDir = process.env.OPENCLAW_STATE_DIR || homePath(".openclaw");
|
||||
const agentsDir = path.join(stateDir, "agents");
|
||||
if (!fs.existsSync(agentsDir)) return [];
|
||||
try {
|
||||
const roots = fs
|
||||
.readdirSync(agentsDir, { withFileTypes: true })
|
||||
.filter((entry) => entry.isDirectory())
|
||||
.flatMap((entry) => {
|
||||
const agentDir = path.join(agentsDir, entry.name);
|
||||
return [
|
||||
path.join(agentDir, "sessions"),
|
||||
path.join(agentDir, "agent", "sessions"),
|
||||
path.join(agentDir, "agent", "codex-home", "sessions"),
|
||||
];
|
||||
})
|
||||
.filter((root) => fs.existsSync(root));
|
||||
return [...new Set(roots)];
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function defaultRoots() {
|
||||
return [
|
||||
homePath(".codex", "sessions"),
|
||||
homePath(".claude", "projects"),
|
||||
homePath(".pi", "agent", "sessions"),
|
||||
...openClawSessionRoots(),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -110,6 +134,12 @@ function detectAgent(file, rows) {
|
||||
if (file.includes(`${path.sep}.codex${path.sep}`)) return "codex";
|
||||
if (file.includes(`${path.sep}.claude${path.sep}`)) return "claude";
|
||||
if (file.includes(`${path.sep}.pi${path.sep}`)) return "pi";
|
||||
if (
|
||||
file.includes(`${path.sep}.openclaw${path.sep}`) ||
|
||||
(file.includes(`${path.sep}agents${path.sep}`) && file.includes(`${path.sep}sessions${path.sep}`))
|
||||
) {
|
||||
return "openclaw";
|
||||
}
|
||||
if (rows.some((row) => row?.type === "session_meta" || row?.type === "response_item")) return "codex";
|
||||
if (rows.some((row) => row?.sessionId && row?.userType)) return "claude";
|
||||
return "agent";
|
||||
@@ -425,17 +455,14 @@ function readBoundedText(file, maxBytes = 220000) {
|
||||
}
|
||||
}
|
||||
|
||||
function sessionScanRecord(file) {
|
||||
function sessionScanRecord(file, maxBytes) {
|
||||
const stat = fs.statSync(file);
|
||||
let agent = "agent";
|
||||
try {
|
||||
agent = detectAgent(file, readJsonl(file, 50));
|
||||
} catch {}
|
||||
const agent = detectAgent(file, []);
|
||||
return {
|
||||
file,
|
||||
agent,
|
||||
mtime: new Date(stat.mtimeMs).toISOString(),
|
||||
haystack: `${file}\n${readBoundedText(file)}`.toLowerCase(),
|
||||
haystack: `${file}\n${readBoundedText(file, maxBytes)}`.toLowerCase(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -461,6 +488,25 @@ function scoreScanRecord(record, terms, cwd) {
|
||||
return { file: record.file, score, reasons, mtime: record.mtime, agent: record.agent };
|
||||
}
|
||||
|
||||
function recentFiles(files, maxFiles) {
|
||||
return files
|
||||
.map((file) => {
|
||||
try {
|
||||
return { file, mtimeMs: fs.statSync(file).mtimeMs };
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.filter(Boolean)
|
||||
.sort((a, b) => b.mtimeMs - a.mtimeMs)
|
||||
.slice(0, maxFiles)
|
||||
.map((entry) => entry.file);
|
||||
}
|
||||
|
||||
function candidateFiles(roots, terms, sinceMs, options = {}) {
|
||||
return recentFiles(roots.flatMap((root) => walkJsonl(root, sinceMs)), Number(options["max-files"] || 400));
|
||||
}
|
||||
|
||||
function findSessions(options) {
|
||||
const sinceDays = Number(options["since-days"] || 14);
|
||||
const sinceMs = Date.now() - sinceDays * 24 * 60 * 60 * 1000;
|
||||
@@ -470,9 +516,10 @@ function findSessions(options) {
|
||||
.split(/\s+/)
|
||||
.concat(query.match(/https?:\/\/\S+/g) || [])
|
||||
.filter(Boolean);
|
||||
const files = roots.flatMap((root) => walkJsonl(root, sinceMs));
|
||||
const files = candidateFiles(roots, terms, sinceMs, options);
|
||||
const scanBytes = Number(options["scan-bytes"] || 60000);
|
||||
const results = files
|
||||
.map((file) => scoreScanRecord(sessionScanRecord(file), terms, options.cwd))
|
||||
.map((file) => scoreScanRecord(sessionScanRecord(file, scanBytes), terms, options.cwd))
|
||||
.filter((result) => result.score > 0)
|
||||
.sort((a, b) => b.score - a.score || b.mtime.localeCompare(a.mtime))
|
||||
.slice(0, Number(options.limit || 10));
|
||||
@@ -487,7 +534,7 @@ function sessionScanRecords(options) {
|
||||
return roots
|
||||
.flatMap((root) => walkJsonl(root, sinceMs))
|
||||
.filter((file) => !excluded.has(path.resolve(file)))
|
||||
.map(sessionScanRecord);
|
||||
.map((file) => sessionScanRecord(file, Number(options["scan-bytes"] || 90000)));
|
||||
}
|
||||
|
||||
function replaceSection(body, section) {
|
||||
|
||||
@@ -26,8 +26,9 @@ Use when:
|
||||
- If a review-triggered fix changes code, rerun focused tests and rerun the structured review helper.
|
||||
- For security-audit suppression changes, verify accepted findings remain auditable: suppressed findings stay in structured output, active output keeps an unsuppressible suppression notice, and aggregate findings cannot hide unrelated active risk.
|
||||
- Never switch or override the requested review engine/model. If the review hits model capacity, retry the same command a few times with the same engine/model.
|
||||
- Be patient with large bundles. Structured review can be silent for several minutes while the model call is active, especially with Codex tools or web search. Treat `review still running: ... elapsed=... pid=...` as healthy progress, not a hang.
|
||||
- Do not kill a review just because it has been quiet for 2-5 minutes. Inspect the process only after multiple heartbeat intervals or an obviously failed subprocess; prefer letting the same helper command finish.
|
||||
- Be patient with large bundles. Structured review can take up to 30 minutes while the model call is active, especially with Codex tools or web search.
|
||||
- Treat heartbeat lines like `review still running: ... elapsed=... pid=...` as healthy progress, not a hang. Let the helper continue while heartbeats are advancing.
|
||||
- Do not kill a review just because it has been quiet for 2-5 minutes, or because it is still running under the 30-minute window. Inspect the process only after missing multiple expected heartbeats, after 30 minutes, or after an obviously failed subprocess; prefer letting the same helper command finish.
|
||||
- Tools are useful in review mode. The helper allows read-only inspection tools and web search by default so reviewers can check dependency contracts, upstream docs, and current behavior.
|
||||
- Security perspective is always included, but it should not cripple legitimate functionality. Report security findings only when the change creates a concrete, actionable risk or removes an important safety check.
|
||||
- Do not invoke built-in `codex review`, nested reviewers, or reviewer panels from inside the review. The helper builds one bundle, calls one selected engine, validates one structured result, and stops.
|
||||
|
||||
@@ -149,7 +149,7 @@ pnpm crabbox:run -- \
|
||||
--ttl 240m \
|
||||
--timing-json \
|
||||
--shell -- \
|
||||
"env CI=1 NODE_OPTIONS=--max-old-space-size=4096 OPENCLAW_TEST_PROJECTS_PARALLEL=6 OPENCLAW_VITEST_MAX_WORKERS=1 OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS=900000 pnpm test:changed"
|
||||
"pnpm test:changed"
|
||||
```
|
||||
|
||||
Full suite:
|
||||
@@ -160,7 +160,7 @@ pnpm crabbox:run -- \
|
||||
--ttl 240m \
|
||||
--timing-json \
|
||||
--shell -- \
|
||||
"env CI=1 NODE_OPTIONS=--max-old-space-size=4096 OPENCLAW_TEST_PROJECTS_PARALLEL=6 OPENCLAW_VITEST_MAX_WORKERS=1 OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS=900000 pnpm test"
|
||||
"pnpm test"
|
||||
```
|
||||
|
||||
Focused rerun:
|
||||
@@ -171,7 +171,7 @@ pnpm crabbox:run -- \
|
||||
--ttl 240m \
|
||||
--timing-json \
|
||||
--shell -- \
|
||||
"env CI=1 NODE_OPTIONS=--max-old-space-size=4096 OPENCLAW_VITEST_MAX_WORKERS=1 OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS=900000 pnpm test <path-or-filter>"
|
||||
"pnpm test <path-or-filter>"
|
||||
```
|
||||
|
||||
Read the JSON summary. Useful fields:
|
||||
@@ -206,7 +206,7 @@ node scripts/crabbox-wrapper.mjs run \
|
||||
--ttl 240m \
|
||||
--timing-json \
|
||||
-- \
|
||||
CI=1 NODE_OPTIONS=--max-old-space-size=4096 OPENCLAW_TEST_PROJECTS_PARALLEL=6 OPENCLAW_VITEST_MAX_WORKERS=1 OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS=900000 OPENCLAW_TESTBOX=1 OPENCLAW_TESTBOX_REMOTE_RUN=1 pnpm check:changed
|
||||
corepack pnpm check:changed
|
||||
```
|
||||
|
||||
Read the JSON summary and the Testbox line. Useful fields:
|
||||
@@ -544,14 +544,14 @@ If brokered AWS cannot dispatch, sync, attach, or stop, retry once with
|
||||
|
||||
```sh
|
||||
pnpm crabbox:run -- --debug --timing-json -- \
|
||||
CI=1 NODE_OPTIONS=--max-old-space-size=4096 OPENCLAW_TEST_PROJECTS_PARALLEL=6 OPENCLAW_VITEST_MAX_WORKERS=1 OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS=900000 pnpm test:changed
|
||||
pnpm test:changed
|
||||
```
|
||||
|
||||
Full suite:
|
||||
|
||||
```sh
|
||||
pnpm crabbox:run -- --debug --timing-json -- \
|
||||
CI=1 NODE_OPTIONS=--max-old-space-size=4096 OPENCLAW_TEST_PROJECTS_PARALLEL=6 OPENCLAW_VITEST_MAX_WORKERS=1 OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS=900000 pnpm test
|
||||
pnpm test
|
||||
```
|
||||
|
||||
Auth fallback, only when `blacksmith` says auth is missing:
|
||||
@@ -591,7 +591,7 @@ Minimal Blacksmith-backed Crabbox run, from repo root:
|
||||
|
||||
```sh
|
||||
pnpm crabbox:run -- --provider blacksmith-testbox --timing-json -- \
|
||||
CI=1 NODE_OPTIONS=--max-old-space-size=4096 OPENCLAW_TEST_PROJECTS_PARALLEL=6 OPENCLAW_VITEST_MAX_WORKERS=1 pnpm test:changed
|
||||
corepack pnpm test:changed
|
||||
```
|
||||
|
||||
Use direct Blacksmith only when Crabbox is the broken layer and you are
|
||||
@@ -617,7 +617,7 @@ provider deliberately.
|
||||
```sh
|
||||
pnpm crabbox:warmup -- --class beast --market on-demand --idle-timeout 90m
|
||||
pnpm crabbox:hydrate -- --id <cbx_id-or-slug>
|
||||
pnpm crabbox:run -- --id <cbx_id-or-slug> --timing-json --shell -- "env NODE_OPTIONS=--max-old-space-size=4096 OPENCLAW_TEST_PROJECTS_PARALLEL=6 OPENCLAW_VITEST_MAX_WORKERS=1 OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS=900000 pnpm test:changed"
|
||||
pnpm crabbox:run -- --id <cbx_id-or-slug> --timing-json --shell -- "pnpm test:changed"
|
||||
pnpm crabbox:stop -- <cbx_id-or-slug>
|
||||
```
|
||||
|
||||
|
||||
@@ -89,11 +89,11 @@ Reject:
|
||||
- if unwritable or wrong shape, create own PR and preserve useful contributor credit
|
||||
- if no PR exists, create one
|
||||
- add regression test when it fits
|
||||
- changelog for user-facing fixes; thank credited human reporter/contributor
|
||||
- release-note context for user-facing fixes in PR body or commit message; credit human reporter/contributor when known
|
||||
6. Review, refresh, and publish:
|
||||
- rebase or otherwise refresh the PR branch on current `origin/main`
|
||||
- resolve drift, including newly exposed CI failures, rather than counting the PR as ready
|
||||
- changelog-only conflicts are routine on busy `main`; resolve them mechanically when already refreshing, but do not treat them as a real code conflict, a reason to reject the PR, or evidence that the branch needs extra fixup beyond the changelog entry order
|
||||
- do not add `CHANGELOG.md` during normal sweep PRs; release automation generates it from PRs and commits
|
||||
- left-test the rebased head with the smallest meaningful local/Testbox/live command that proves the bug
|
||||
- run `$autoreview` until no accepted/actionable findings remain before creating, updating, or presenting the PR URL
|
||||
- create/update PR with real body and proof fields
|
||||
|
||||
@@ -139,12 +139,12 @@ Issue triage is review/prove/patch-local by default:
|
||||
2. Fix only issues that are easy, high-confidence, and narrowly owned by the implicated path.
|
||||
3. Add focused regression proof when practical.
|
||||
4. Stop with the dirty diff, touched files, and test/gate output for maintainer review.
|
||||
5. After maintainer approval to ship, make one commit per accepted fix, with its own changelog entry when user-facing.
|
||||
5. After maintainer approval to ship, make one commit per accepted fix, with release-note context in the PR body or commit message when user-facing.
|
||||
6. Pull/rebase, push, then comment and close only the issues that were fixed or explicitly triaged closed.
|
||||
|
||||
Do not batch unrelated issue fixes into one commit. Do not publish, comment, close, or label during the review/prove phase.
|
||||
|
||||
Missing changelog is not a PR review finding or merge blocker. If landing/fixing a user-visible change, add/update changelog automatically when practical; never ask or block solely on it.
|
||||
Missing `CHANGELOG.md` is not a PR review finding or merge blocker. If landing/fixing a user-visible change, make sure the PR body or commit message captures the release-note context; never ask or block solely on it.
|
||||
|
||||
Only list candidates that pass all gates:
|
||||
|
||||
@@ -244,9 +244,8 @@ gh search issues --repo openclaw/openclaw --match title,body --limit 50 \
|
||||
|
||||
## Follow PR review and landing hygiene
|
||||
|
||||
- Never mention merge conflicts that are relatively easy to resolve, such as
|
||||
`CHANGELOG.md` entries, in review-only output. These are landing mechanics,
|
||||
not correctness findings.
|
||||
- Never mention release-note bookkeeping in review-only output. It is landing
|
||||
or release-generation mechanics, not a correctness finding.
|
||||
- If bot review conversations exist on your PR, address them and resolve them yourself once fixed.
|
||||
- Leave a review conversation unresolved only when reviewer or maintainer judgment is still needed.
|
||||
- Before landing any PR with non-trivial code changes, run `$autoreview` until no accepted/actionable findings remain, unless equivalent manual review already covered it, the change is trivial/docs-only, or the user opts out.
|
||||
|
||||
@@ -23,7 +23,8 @@ Use this skill for release and publish-time workflow. Load `$release-private` if
|
||||
green. Then branch from that commit so regular development can continue on
|
||||
`main` while release validation runs.
|
||||
- Before release branching, commit any dirty files in coherent groups, push,
|
||||
pull/rebase, then run `/changelog` on `main` and commit/push/pull that
|
||||
pull/rebase, then generate `CHANGELOG.md` on `main` from merged PRs and all
|
||||
direct commits since the last reachable release tag. Commit/push/pull that
|
||||
changelog rewrite immediately before creating the release branch.
|
||||
- During release planning, inspect both `src/plugins/compat/registry.ts` and
|
||||
`src/commands/doctor/shared/deprecation-compat.ts` before branching and again
|
||||
@@ -68,8 +69,8 @@ Use this skill for release and publish-time workflow. Load `$release-private` if
|
||||
or clawgrit reports. Report regressions explicitly. A major regression is a
|
||||
release blocker unless the operator waives it or the data clearly proves
|
||||
infrastructure noise.
|
||||
- Use `/changelog` before version/tag preparation so the top changelog section
|
||||
is deduped and ordered by user impact.
|
||||
- Generate the changelog before version/tag preparation so the top changelog
|
||||
section is deduped and ordered by user impact.
|
||||
- Do not create beta-specific `CHANGELOG.md` headings. Beta releases use the
|
||||
stable base version section, for example `v2026.4.20-beta.1` uses
|
||||
`## 2026.4.20` release notes.
|
||||
@@ -136,11 +137,25 @@ Use this skill for release and publish-time workflow. Load `$release-private` if
|
||||
|
||||
## Build changelog-backed release notes
|
||||
|
||||
- `CHANGELOG.md` is release-owned. Normal PRs and direct `main` fixes should
|
||||
not edit it.
|
||||
- Before release branching or tagging, rewrite the target `CHANGELOG.md`
|
||||
section from commit history, not just from existing notes: scan commits since
|
||||
the last reachable release tag, add missed user-facing changes, dedupe
|
||||
overlapping entries, and sort each section from most to least interesting for
|
||||
users.
|
||||
section from history, not existing notes. Use the last reachable stable or
|
||||
beta release tag as the base, then inspect every commit through the target
|
||||
release SHA.
|
||||
- Include both merged PR commits and direct commits on `main`. Direct commits
|
||||
matter: infer notes from their subject, body, touched files, linked issues,
|
||||
tests, and nearby code when no PR body exists.
|
||||
- Prefer PR bodies, issue links, review proof, and commit bodies over commit
|
||||
subjects alone. If a commit fixed an issue directly, the commit body should
|
||||
name the user-visible behavior, affected surface, issue ref, and credited
|
||||
reporter/contributor when known.
|
||||
- Treat missing context as a release-note audit gap: inspect the diff and linked
|
||||
issue, draft the best accurate entry, and note the uncertainty for maintainer
|
||||
review rather than inventing impact.
|
||||
- Add missed user-facing changes, remove internal-only noise, dedupe overlapping
|
||||
PR/direct-commit entries, and sort each section from most to least interesting
|
||||
for users.
|
||||
- Changelog entries should be user-facing, not internal release-process notes.
|
||||
- GitHub release and prerelease bodies must use the full matching
|
||||
`CHANGELOG.md` version section, not highlights or an excerpt. When creating
|
||||
|
||||
4
.github/actions/docker-e2e-plan/action.yml
vendored
@@ -123,14 +123,14 @@ runs:
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
docker pull "${OPENCLAW_DOCKER_E2E_BARE_IMAGE}"
|
||||
bash scripts/ci-docker-pull-retry.sh "${OPENCLAW_DOCKER_E2E_BARE_IMAGE}"
|
||||
|
||||
- name: Pull shared functional Docker E2E image
|
||||
if: inputs.hydrate-artifacts == 'true' && steps.plan.outputs.needs_functional_image == '1'
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
docker pull "${OPENCLAW_DOCKER_E2E_FUNCTIONAL_IMAGE}"
|
||||
bash scripts/ci-docker-pull-retry.sh "${OPENCLAW_DOCKER_E2E_FUNCTIONAL_IMAGE}"
|
||||
|
||||
- name: Validate Docker E2E credentials
|
||||
if: inputs.hydrate-artifacts == 'true'
|
||||
|
||||
27
.github/actions/setup-node-env/action.yml
vendored
@@ -26,11 +26,23 @@ inputs:
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Normalize container toolcache
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [[ -d /__t && ! -e /opt/hostedtoolcache ]]; then
|
||||
mkdir -p /opt
|
||||
ln -s /__t /opt/hostedtoolcache
|
||||
fi
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: ${{ inputs.node-version }}
|
||||
check-latest: false
|
||||
shell: bash
|
||||
env:
|
||||
REQUESTED_NODE_VERSION: ${{ inputs.node-version }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
source "$GITHUB_ACTION_PATH/../setup-pnpm-store-cache/ensure-node.sh"
|
||||
openclaw_ensure_node "$REQUESTED_NODE_VERSION"
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: ./.github/actions/setup-pnpm-store-cache
|
||||
@@ -40,9 +52,10 @@ runs:
|
||||
|
||||
- name: Setup Bun
|
||||
if: inputs.install-bun == 'true'
|
||||
uses: oven-sh/setup-bun@v2.2.0
|
||||
with:
|
||||
bun-version: "1.3.13"
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
npm install -g bun@1.3.13
|
||||
|
||||
- name: Runtime versions
|
||||
shell: bash
|
||||
|
||||
@@ -14,7 +14,7 @@ inputs:
|
||||
required: false
|
||||
default: ""
|
||||
use-actions-cache:
|
||||
description: Whether pnpm/action-setup should cache the pnpm store.
|
||||
description: Whether actions/cache should cache the pnpm store.
|
||||
required: false
|
||||
default: "true"
|
||||
outputs:
|
||||
@@ -47,12 +47,42 @@ runs:
|
||||
openclaw_ensure_node "$requested_node"
|
||||
|
||||
- name: Setup pnpm from packageManager
|
||||
uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093
|
||||
shell: bash
|
||||
env:
|
||||
COREPACK_ENABLE_DOWNLOAD_PROMPT: "0"
|
||||
PACKAGE_MANAGER_FILE: ${{ inputs.package-manager-file }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
package_manager="$(node -e "const fs = require('node:fs'); const path = require('node:path'); const pkg = JSON.parse(fs.readFileSync(path.resolve(process.argv[1]), 'utf8')); process.stdout.write(pkg.packageManager || '')" "$PACKAGE_MANAGER_FILE")"
|
||||
case "$package_manager" in
|
||||
pnpm@*) ;;
|
||||
*)
|
||||
echo "::error::Expected packageManager to pin pnpm, got '${package_manager:-<empty>}'"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
corepack enable
|
||||
corepack prepare "$package_manager" --activate
|
||||
|
||||
- name: Resolve pnpm store path
|
||||
id: pnpm-store
|
||||
if: ${{ inputs.use-actions-cache == 'true' && runner.os != 'Windows' }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
store_path="$(pnpm store path --silent)"
|
||||
node -e "require('node:fs').mkdirSync(process.argv[1], { recursive: true })" "$store_path"
|
||||
echo "path=$store_path" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Restore pnpm store cache
|
||||
if: ${{ inputs.use-actions-cache == 'true' && runner.os != 'Windows' }}
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
package_json_file: ${{ inputs.package-manager-file }}
|
||||
run_install: false
|
||||
cache: ${{ inputs.use-actions-cache }}
|
||||
cache_dependency_path: ${{ inputs.lockfile-path }}
|
||||
path: ${{ steps.pnpm-store.outputs.path }}
|
||||
key: pnpm-store-${{ runner.os }}-${{ inputs.node-version }}-${{ hashFiles(inputs.lockfile-path) }}
|
||||
restore-keys: |
|
||||
pnpm-store-${{ runner.os }}-${{ inputs.node-version }}-
|
||||
pnpm-store-${{ runner.os }}-
|
||||
|
||||
- name: Record pnpm version
|
||||
id: pnpm-version
|
||||
|
||||
@@ -28,9 +28,17 @@ openclaw_active_node_version() {
|
||||
|
||||
openclaw_prepend_node_bin() {
|
||||
local node_bin_dir="$1"
|
||||
export PATH="$node_bin_dir:$PATH"
|
||||
local shell_node_bin_dir="$node_bin_dir"
|
||||
if command -v cygpath >/dev/null 2>&1; then
|
||||
shell_node_bin_dir="$(cygpath -u "$node_bin_dir" 2>/dev/null || printf '%s' "$node_bin_dir")"
|
||||
fi
|
||||
export PATH="$shell_node_bin_dir:$PATH"
|
||||
if [[ -n "${GITHUB_PATH:-}" ]]; then
|
||||
echo "$node_bin_dir" >> "$GITHUB_PATH"
|
||||
local github_node_bin_dir="$shell_node_bin_dir"
|
||||
if command -v cygpath >/dev/null 2>&1; then
|
||||
github_node_bin_dir="$(cygpath -w "$shell_node_bin_dir" 2>/dev/null || printf '%s' "$shell_node_bin_dir")"
|
||||
fi
|
||||
echo "$github_node_bin_dir" >> "$GITHUB_PATH"
|
||||
fi
|
||||
hash -r
|
||||
}
|
||||
@@ -43,6 +51,7 @@ openclaw_find_toolcache_node() {
|
||||
"${RUNNER_TOOL_CACHE:-}" \
|
||||
"${AGENT_TOOLSDIRECTORY:-}" \
|
||||
"${ACTIONS_RUNNER_TOOL_CACHE:-}" \
|
||||
"${OPENCLAW_CONTAINER_TOOL_CACHE:-/__t}" \
|
||||
"/opt/hostedtoolcache" \
|
||||
"/home/runner/_work/_tool" \
|
||||
"/Users/runner/hostedtoolcache" \
|
||||
@@ -68,6 +77,56 @@ openclaw_find_toolcache_node() {
|
||||
return 1
|
||||
}
|
||||
|
||||
openclaw_resolve_node_download_version() {
|
||||
local requested_node="$1"
|
||||
if [[ "$requested_node" =~ ^v?[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||
[[ "$requested_node" == v* ]] && printf '%s\n' "$requested_node" || printf 'v%s\n' "$requested_node"
|
||||
return 0
|
||||
fi
|
||||
|
||||
local prefix="${requested_node#v}"
|
||||
prefix="${prefix%%[xX]*}"
|
||||
prefix="v${prefix}"
|
||||
[[ "$prefix" == *. ]] || prefix="${prefix}."
|
||||
curl -fsSL https://nodejs.org/dist/index.json |
|
||||
OPENCLAW_NODE_PREFIX="$prefix" python3 -c 'import json, os, sys
|
||||
prefix = os.environ["OPENCLAW_NODE_PREFIX"]
|
||||
for item in json.load(sys.stdin):
|
||||
version = item.get("version", "")
|
||||
if version.startswith(prefix):
|
||||
print(version)
|
||||
break
|
||||
'
|
||||
}
|
||||
|
||||
openclaw_node_download_platform() {
|
||||
local os_name arch_name
|
||||
os_name="$(uname -s)"
|
||||
arch_name="$(uname -m)"
|
||||
case "$os_name:$arch_name" in
|
||||
Linux:x86_64) printf 'linux-x64\n' ;;
|
||||
Linux:aarch64 | Linux:arm64) printf 'linux-arm64\n' ;;
|
||||
Darwin:x86_64) printf 'darwin-x64\n' ;;
|
||||
Darwin:arm64) printf 'darwin-arm64\n' ;;
|
||||
*)
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
openclaw_download_node() {
|
||||
local requested_node="$1"
|
||||
local version platform archive_url install_root
|
||||
version="$(openclaw_resolve_node_download_version "$requested_node")"
|
||||
platform="$(openclaw_node_download_platform)" || return 1
|
||||
install_root="${RUNNER_TEMP:-/tmp}/openclaw-node-${version}-${platform}"
|
||||
archive_url="https://nodejs.org/dist/${version}/node-${version}-${platform}.tar.xz"
|
||||
mkdir -p "$install_root"
|
||||
echo "Downloading Node ${version} from ${archive_url}"
|
||||
curl -fsSL "$archive_url" | tar -xJ -C "$install_root" --strip-components=1
|
||||
openclaw_prepend_node_bin "$install_root/bin"
|
||||
}
|
||||
|
||||
openclaw_ensure_node() {
|
||||
local requested_node="${1:-}"
|
||||
requested_node="${requested_node#v}"
|
||||
@@ -86,6 +145,8 @@ openclaw_ensure_node() {
|
||||
if [[ -n "$node_bin" ]]; then
|
||||
echo "Using Node $("$node_bin" -p 'process.versions.node') from $node_bin"
|
||||
openclaw_prepend_node_bin "$(dirname "$node_bin")"
|
||||
else
|
||||
openclaw_download_node "$requested_node" || true
|
||||
fi
|
||||
|
||||
active_node_version="$(openclaw_active_node_version)"
|
||||
|
||||
2
.github/codex/prompts/docs-agent.md
vendored
@@ -12,7 +12,7 @@ Hard limits:
|
||||
- Do not change production code, tests, package metadata, generated baselines, lockfiles, or CI config.
|
||||
- Keep changes minimal and factual.
|
||||
- Use "plugin/plugins" in user-facing docs/UI/changelog; `extensions/` is only the internal workspace layout.
|
||||
- Do not add a changelog entry unless the docs update describes a user-facing behavior/API change from the triggering commit.
|
||||
- Do not add `CHANGELOG.md` entries during normal docs work. Capture user-facing release-note context in the PR body or commit message instead.
|
||||
|
||||
Allowed paths:
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ jobs:
|
||||
git -C "$workdir" remote add origin "https://github.com/${CHECKOUT_REPO}"
|
||||
git -C "$workdir" config gc.auto 0
|
||||
|
||||
timeout --signal=TERM 30s git -C "$workdir" \
|
||||
timeout --signal=TERM --kill-after=10s 30s git -C "$workdir" \
|
||||
-c protocol.version=2 \
|
||||
-c "http.extraheader=AUTHORIZATION: basic ${auth_header}" \
|
||||
fetch --no-tags --prune --no-recurse-submodules --depth=1 origin \
|
||||
|
||||
3
.github/workflows/ci-check-testbox.yml
vendored
@@ -59,7 +59,7 @@ jobs:
|
||||
git -C "$workdir" remote add origin "https://github.com/${CHECKOUT_REPO}"
|
||||
git -C "$workdir" config gc.auto 0
|
||||
|
||||
timeout --signal=TERM 30s git -C "$workdir" \
|
||||
timeout --signal=TERM --kill-after=10s 30s git -C "$workdir" \
|
||||
-c protocol.version=2 \
|
||||
-c "http.extraheader=AUTHORIZATION: basic ${auth_header}" \
|
||||
fetch --no-tags --prune --no-recurse-submodules --depth=1 origin \
|
||||
@@ -132,6 +132,5 @@ jobs:
|
||||
- name: Run Testbox
|
||||
uses: useblacksmith/run-testbox@5ca05834db1d3813554d1dd109e5f2087a8d7cbc
|
||||
if: success()
|
||||
continue-on-error: true
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
||||
|
||||
220
.github/workflows/ci.yml
vendored
@@ -76,13 +76,16 @@ jobs:
|
||||
android_matrix: ${{ steps.manifest.outputs.android_matrix }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ inputs.target_ref || github.sha }}
|
||||
fetch-depth: 1
|
||||
fetch-tags: false
|
||||
persist-credentials: true
|
||||
submodules: false
|
||||
env:
|
||||
CHECKOUT_REPO: ${{ github.repository }}
|
||||
CHECKOUT_REF: ${{ inputs.target_ref || github.sha }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
git init "$GITHUB_WORKSPACE"
|
||||
git -C "$GITHUB_WORKSPACE" config gc.auto 0
|
||||
git -C "$GITHUB_WORKSPACE" remote add origin "https://github.com/${CHECKOUT_REPO}.git"
|
||||
git -C "$GITHUB_WORKSPACE" fetch --no-tags --depth=1 origin "+${CHECKOUT_REF}:refs/remotes/origin/checkout"
|
||||
git -C "$GITHUB_WORKSPACE" checkout --detach refs/remotes/origin/checkout
|
||||
|
||||
- name: Resolve checkout SHA
|
||||
id: checkout_ref
|
||||
@@ -299,13 +302,16 @@ jobs:
|
||||
PRE_COMMIT_HOME: .cache/pre-commit-security-fast
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ inputs.target_ref || github.sha }}
|
||||
fetch-depth: 1
|
||||
fetch-tags: false
|
||||
persist-credentials: true
|
||||
submodules: false
|
||||
env:
|
||||
CHECKOUT_REPO: ${{ github.repository }}
|
||||
CHECKOUT_REF: ${{ inputs.target_ref || github.sha }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
git init "$GITHUB_WORKSPACE"
|
||||
git -C "$GITHUB_WORKSPACE" config gc.auto 0
|
||||
git -C "$GITHUB_WORKSPACE" remote add origin "https://github.com/${CHECKOUT_REPO}.git"
|
||||
git -C "$GITHUB_WORKSPACE" fetch --no-tags --depth=1 origin "+${CHECKOUT_REF}:refs/remotes/origin/checkout"
|
||||
git -C "$GITHUB_WORKSPACE" checkout --detach refs/remotes/origin/checkout
|
||||
|
||||
- name: Ensure security base commit
|
||||
if: github.event_name != 'workflow_dispatch'
|
||||
@@ -335,22 +341,20 @@ jobs:
|
||||
fi
|
||||
echo "PRE_COMMIT_CONFIG_PATH=$trusted_config" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Setup Python
|
||||
- name: Resolve Python runtime
|
||||
id: setup-python
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: "3.12"
|
||||
|
||||
- name: Restore pre-commit cache
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: .cache/pre-commit-security-fast
|
||||
key: pre-commit-security-fast-${{ runner.os }}-${{ steps.setup-python.outputs.python-version }}-${{ hashFiles('.pre-commit-config.yaml') }}
|
||||
restore-keys: |
|
||||
pre-commit-security-fast-${{ runner.os }}-${{ steps.setup-python.outputs.python-version }}-
|
||||
run: |
|
||||
set -euo pipefail
|
||||
python3 --version
|
||||
version="$(python3 - <<'PY'
|
||||
import platform
|
||||
print(platform.python_version())
|
||||
PY
|
||||
)"
|
||||
echo "python-version=${version}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Install pre-commit
|
||||
run: python -m pip install --disable-pip-version-check pre-commit==4.2.0
|
||||
run: python3 -m pip install --disable-pip-version-check pre-commit==4.2.0
|
||||
|
||||
- name: Detect committed private keys
|
||||
run: pre-commit run --config "${PRE_COMMIT_CONFIG_PATH:-.pre-commit-config.yaml}" --all-files detect-private-key
|
||||
@@ -383,10 +387,12 @@ jobs:
|
||||
pre-commit run --config "${PRE_COMMIT_CONFIG_PATH:-.pre-commit-config.yaml}" zizmor --files "${workflow_files[@]}"
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: "24.x"
|
||||
check-latest: false
|
||||
env:
|
||||
REQUESTED_NODE_VERSION: "24.x"
|
||||
run: |
|
||||
set -euo pipefail
|
||||
source .github/actions/setup-pnpm-store-cache/ensure-node.sh
|
||||
openclaw_ensure_node "$REQUESTED_NODE_VERSION"
|
||||
|
||||
- name: Audit production dependencies
|
||||
run: node scripts/pre-commit/pnpm-audit-prod.mjs --audit-level=high
|
||||
@@ -411,7 +417,6 @@ jobs:
|
||||
env:
|
||||
CHECKOUT_REPO: ${{ github.repository }}
|
||||
CHECKOUT_SHA: ${{ needs.preflight.outputs.checkout_revision }}
|
||||
CHECKOUT_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
@@ -427,10 +432,10 @@ jobs:
|
||||
reset_checkout_dir
|
||||
git init "$workdir" >/dev/null
|
||||
git config --global --add safe.directory "$workdir"
|
||||
git -C "$workdir" remote add origin "https://x-access-token:${CHECKOUT_TOKEN}@github.com/${CHECKOUT_REPO}.git"
|
||||
git -C "$workdir" remote add origin "https://github.com/${CHECKOUT_REPO}.git"
|
||||
git -C "$workdir" config gc.auto 0
|
||||
|
||||
timeout --signal=TERM 30s git -C "$workdir" \
|
||||
timeout --signal=TERM --kill-after=10s 30s git -C "$workdir" \
|
||||
-c protocol.version=2 \
|
||||
fetch --no-tags --prune --no-recurse-submodules --depth=1 origin \
|
||||
"+${CHECKOUT_SHA}:refs/remotes/origin/ci-target" || return 1
|
||||
@@ -513,7 +518,24 @@ jobs:
|
||||
run: pnpm test:build:singleton
|
||||
|
||||
- name: Check CLI startup memory
|
||||
run: pnpm test:startup:memory
|
||||
shell: bash
|
||||
run: |
|
||||
set +e
|
||||
pnpm test:startup:memory
|
||||
status=$?
|
||||
if [[ -f .artifacts/startup-memory/summary.md ]]; then
|
||||
cat .artifacts/startup-memory/summary.md >> "$GITHUB_STEP_SUMMARY"
|
||||
fi
|
||||
exit "$status"
|
||||
|
||||
- name: Upload startup memory report
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: startup-memory
|
||||
path: .artifacts/startup-memory/
|
||||
if-no-files-found: ignore
|
||||
retention-days: 7
|
||||
|
||||
- name: Run built artifact checks
|
||||
id: built_artifact_checks
|
||||
@@ -619,7 +641,6 @@ jobs:
|
||||
env:
|
||||
CHECKOUT_REPO: ${{ github.repository }}
|
||||
CHECKOUT_SHA: ${{ needs.preflight.outputs.checkout_revision }}
|
||||
CHECKOUT_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
@@ -635,10 +656,10 @@ jobs:
|
||||
reset_checkout_dir
|
||||
git init "$workdir" >/dev/null
|
||||
git config --global --add safe.directory "$workdir"
|
||||
git -C "$workdir" remote add origin "https://x-access-token:${CHECKOUT_TOKEN}@github.com/${CHECKOUT_REPO}.git"
|
||||
git -C "$workdir" remote add origin "https://github.com/${CHECKOUT_REPO}.git"
|
||||
git -C "$workdir" config gc.auto 0
|
||||
|
||||
timeout --signal=TERM 30s git -C "$workdir" \
|
||||
timeout --signal=TERM --kill-after=10s 30s git -C "$workdir" \
|
||||
-c protocol.version=2 \
|
||||
fetch --no-tags --prune --no-recurse-submodules --depth=1 origin \
|
||||
"+${CHECKOUT_SHA}:refs/remotes/origin/ci-target" || return 1
|
||||
@@ -706,7 +727,6 @@ jobs:
|
||||
env:
|
||||
CHECKOUT_REPO: ${{ github.repository }}
|
||||
CHECKOUT_SHA: ${{ needs.preflight.outputs.checkout_revision }}
|
||||
CHECKOUT_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
@@ -722,10 +742,10 @@ jobs:
|
||||
reset_checkout_dir
|
||||
git init "$workdir" >/dev/null
|
||||
git config --global --add safe.directory "$workdir"
|
||||
git -C "$workdir" remote add origin "https://x-access-token:${CHECKOUT_TOKEN}@github.com/${CHECKOUT_REPO}.git"
|
||||
git -C "$workdir" remote add origin "https://github.com/${CHECKOUT_REPO}.git"
|
||||
git -C "$workdir" config gc.auto 0
|
||||
|
||||
timeout --signal=TERM 30s git -C "$workdir" \
|
||||
timeout --signal=TERM --kill-after=10s 30s git -C "$workdir" \
|
||||
-c protocol.version=2 \
|
||||
fetch --no-tags --prune --no-recurse-submodules --depth=1 origin \
|
||||
"+${CHECKOUT_SHA}:refs/remotes/origin/ci-target" || return 1
|
||||
@@ -787,7 +807,6 @@ jobs:
|
||||
env:
|
||||
CHECKOUT_REPO: ${{ github.repository }}
|
||||
CHECKOUT_SHA: ${{ needs.preflight.outputs.checkout_revision }}
|
||||
CHECKOUT_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
@@ -803,10 +822,10 @@ jobs:
|
||||
reset_checkout_dir
|
||||
git init "$workdir" >/dev/null
|
||||
git config --global --add safe.directory "$workdir"
|
||||
git -C "$workdir" remote add origin "https://x-access-token:${CHECKOUT_TOKEN}@github.com/${CHECKOUT_REPO}.git"
|
||||
git -C "$workdir" remote add origin "https://github.com/${CHECKOUT_REPO}.git"
|
||||
git -C "$workdir" config gc.auto 0
|
||||
|
||||
timeout --signal=TERM 30s git -C "$workdir" \
|
||||
timeout --signal=TERM --kill-after=10s 30s git -C "$workdir" \
|
||||
-c protocol.version=2 \
|
||||
fetch --no-tags --prune --no-recurse-submodules --depth=1 origin \
|
||||
"+${CHECKOUT_SHA}:refs/remotes/origin/ci-target" || return 1
|
||||
@@ -865,7 +884,6 @@ jobs:
|
||||
env:
|
||||
CHECKOUT_REPO: ${{ github.repository }}
|
||||
CHECKOUT_SHA: ${{ needs.preflight.outputs.checkout_revision }}
|
||||
CHECKOUT_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
@@ -881,10 +899,10 @@ jobs:
|
||||
reset_checkout_dir
|
||||
git init "$workdir" >/dev/null
|
||||
git config --global --add safe.directory "$workdir"
|
||||
git -C "$workdir" remote add origin "https://x-access-token:${CHECKOUT_TOKEN}@github.com/${CHECKOUT_REPO}.git"
|
||||
git -C "$workdir" remote add origin "https://github.com/${CHECKOUT_REPO}.git"
|
||||
git -C "$workdir" config gc.auto 0
|
||||
|
||||
timeout --signal=TERM 30s git -C "$workdir" \
|
||||
timeout --signal=TERM --kill-after=10s 30s git -C "$workdir" \
|
||||
-c protocol.version=2 \
|
||||
fetch --no-tags --prune --no-recurse-submodules --depth=1 origin \
|
||||
"+${CHECKOUT_SHA}:refs/remotes/origin/ci-target" || return 1
|
||||
@@ -941,7 +959,6 @@ jobs:
|
||||
env:
|
||||
CHECKOUT_REPO: ${{ github.repository }}
|
||||
CHECKOUT_SHA: ${{ needs.preflight.outputs.checkout_revision }}
|
||||
CHECKOUT_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
@@ -957,10 +974,10 @@ jobs:
|
||||
reset_checkout_dir
|
||||
git init "$workdir" >/dev/null
|
||||
git config --global --add safe.directory "$workdir"
|
||||
git -C "$workdir" remote add origin "https://x-access-token:${CHECKOUT_TOKEN}@github.com/${CHECKOUT_REPO}.git"
|
||||
git -C "$workdir" remote add origin "https://github.com/${CHECKOUT_REPO}.git"
|
||||
git -C "$workdir" config gc.auto 0
|
||||
|
||||
timeout --signal=TERM 30s git -C "$workdir" \
|
||||
timeout --signal=TERM --kill-after=10s 30s git -C "$workdir" \
|
||||
-c protocol.version=2 \
|
||||
fetch --no-tags --prune --no-recurse-submodules --depth=1 origin \
|
||||
"+${CHECKOUT_SHA}:refs/remotes/origin/ci-target" || return 1
|
||||
@@ -1064,7 +1081,6 @@ jobs:
|
||||
env:
|
||||
CHECKOUT_REPO: ${{ github.repository }}
|
||||
CHECKOUT_SHA: ${{ needs.preflight.outputs.checkout_revision }}
|
||||
CHECKOUT_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
@@ -1080,10 +1096,10 @@ jobs:
|
||||
reset_checkout_dir
|
||||
git init "$workdir" >/dev/null
|
||||
git config --global --add safe.directory "$workdir"
|
||||
git -C "$workdir" remote add origin "https://x-access-token:${CHECKOUT_TOKEN}@github.com/${CHECKOUT_REPO}.git"
|
||||
git -C "$workdir" remote add origin "https://github.com/${CHECKOUT_REPO}.git"
|
||||
git -C "$workdir" config gc.auto 0
|
||||
|
||||
timeout --signal=TERM 30s git -C "$workdir" \
|
||||
timeout --signal=TERM --kill-after=10s 30s git -C "$workdir" \
|
||||
-c protocol.version=2 \
|
||||
fetch --no-tags --prune --no-recurse-submodules --depth=1 origin \
|
||||
"+${CHECKOUT_SHA}:refs/remotes/origin/ci-target" || return 1
|
||||
@@ -1195,7 +1211,6 @@ jobs:
|
||||
env:
|
||||
CHECKOUT_REPO: ${{ github.repository }}
|
||||
CHECKOUT_SHA: ${{ needs.preflight.outputs.checkout_revision }}
|
||||
CHECKOUT_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
@@ -1211,10 +1226,10 @@ jobs:
|
||||
reset_checkout_dir
|
||||
git init "$workdir" >/dev/null
|
||||
git config --global --add safe.directory "$workdir"
|
||||
git -C "$workdir" remote add origin "https://x-access-token:${CHECKOUT_TOKEN}@github.com/${CHECKOUT_REPO}.git"
|
||||
git -C "$workdir" remote add origin "https://github.com/${CHECKOUT_REPO}.git"
|
||||
git -C "$workdir" config gc.auto 0
|
||||
|
||||
timeout --signal=TERM 30s git -C "$workdir" \
|
||||
timeout --signal=TERM --kill-after=10s 30s git -C "$workdir" \
|
||||
-c protocol.version=2 \
|
||||
fetch --no-tags --prune --no-recurse-submodules --depth=1 origin \
|
||||
"+${CHECKOUT_SHA}:refs/remotes/origin/ci-target" || return 1
|
||||
@@ -1345,7 +1360,6 @@ jobs:
|
||||
env:
|
||||
CHECKOUT_REPO: ${{ github.repository }}
|
||||
CHECKOUT_SHA: ${{ needs.preflight.outputs.checkout_revision }}
|
||||
CHECKOUT_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
@@ -1361,10 +1375,10 @@ jobs:
|
||||
reset_checkout_dir
|
||||
git init "$workdir" >/dev/null
|
||||
git config --global --add safe.directory "$workdir"
|
||||
git -C "$workdir" remote add origin "https://x-access-token:${CHECKOUT_TOKEN}@github.com/${CHECKOUT_REPO}.git"
|
||||
git -C "$workdir" remote add origin "https://github.com/${CHECKOUT_REPO}.git"
|
||||
git -C "$workdir" config gc.auto 0
|
||||
|
||||
timeout --signal=TERM 30s git -C "$workdir" \
|
||||
timeout --signal=TERM --kill-after=10s 30s git -C "$workdir" \
|
||||
-c protocol.version=2 \
|
||||
fetch --no-tags --prune --no-recurse-submodules --depth=1 origin \
|
||||
"+${CHECKOUT_SHA}:refs/remotes/origin/ci-target" || return 1
|
||||
@@ -1391,12 +1405,13 @@ jobs:
|
||||
install-bun: "false"
|
||||
|
||||
- name: Checkout ClawHub docs source
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
repository: openclaw/clawhub
|
||||
path: clawhub-source
|
||||
fetch-depth: 1
|
||||
persist-credentials: true
|
||||
run: |
|
||||
set -euo pipefail
|
||||
git init clawhub-source
|
||||
git -C clawhub-source config gc.auto 0
|
||||
git -C clawhub-source remote add origin "https://github.com/openclaw/clawhub.git"
|
||||
git -C clawhub-source fetch --no-tags --depth=1 origin "+HEAD:refs/remotes/origin/checkout"
|
||||
git -C clawhub-source checkout --detach refs/remotes/origin/checkout
|
||||
|
||||
- name: Check docs
|
||||
env:
|
||||
@@ -1412,11 +1427,16 @@ jobs:
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ needs.preflight.outputs.checkout_revision }}
|
||||
persist-credentials: true
|
||||
submodules: false
|
||||
env:
|
||||
CHECKOUT_REPO: ${{ github.repository }}
|
||||
CHECKOUT_SHA: ${{ needs.preflight.outputs.checkout_revision }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
git init "$GITHUB_WORKSPACE"
|
||||
git -C "$GITHUB_WORKSPACE" config gc.auto 0
|
||||
git -C "$GITHUB_WORKSPACE" remote add origin "https://github.com/${CHECKOUT_REPO}.git"
|
||||
git -C "$GITHUB_WORKSPACE" fetch --no-tags --depth=1 origin "+${CHECKOUT_SHA}:refs/remotes/origin/checkout"
|
||||
git -C "$GITHUB_WORKSPACE" checkout --detach refs/remotes/origin/checkout
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v6
|
||||
@@ -1455,11 +1475,16 @@ jobs:
|
||||
matrix: ${{ fromJson(needs.preflight.outputs.checks_windows_matrix) }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ needs.preflight.outputs.checkout_revision }}
|
||||
persist-credentials: true
|
||||
submodules: false
|
||||
env:
|
||||
CHECKOUT_REPO: ${{ github.repository }}
|
||||
CHECKOUT_SHA: ${{ needs.preflight.outputs.checkout_revision }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
git init "$GITHUB_WORKSPACE"
|
||||
git -C "$GITHUB_WORKSPACE" config gc.auto 0
|
||||
git -C "$GITHUB_WORKSPACE" remote add origin "https://github.com/${CHECKOUT_REPO}.git"
|
||||
git -C "$GITHUB_WORKSPACE" fetch --no-tags --depth=1 origin "+${CHECKOUT_SHA}:refs/remotes/origin/checkout"
|
||||
git -C "$GITHUB_WORKSPACE" checkout --detach refs/remotes/origin/checkout
|
||||
|
||||
- name: Try to exclude workspace from Windows Defender (best-effort)
|
||||
shell: pwsh
|
||||
@@ -1481,10 +1506,12 @@ jobs:
|
||||
}
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 24.x
|
||||
check-latest: false
|
||||
env:
|
||||
REQUESTED_NODE_VERSION: "24.x"
|
||||
run: |
|
||||
set -euo pipefail
|
||||
source .github/actions/setup-pnpm-store-cache/ensure-node.sh
|
||||
openclaw_ensure_node "$REQUESTED_NODE_VERSION"
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: ./.github/actions/setup-pnpm-store-cache
|
||||
@@ -1548,11 +1575,16 @@ jobs:
|
||||
matrix: ${{ fromJson(needs.preflight.outputs.macos_node_matrix) }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ needs.preflight.outputs.checkout_revision }}
|
||||
persist-credentials: true
|
||||
submodules: false
|
||||
env:
|
||||
CHECKOUT_REPO: ${{ github.repository }}
|
||||
CHECKOUT_SHA: ${{ needs.preflight.outputs.checkout_revision }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
git init "$GITHUB_WORKSPACE"
|
||||
git -C "$GITHUB_WORKSPACE" config gc.auto 0
|
||||
git -C "$GITHUB_WORKSPACE" remote add origin "https://github.com/${CHECKOUT_REPO}.git"
|
||||
git -C "$GITHUB_WORKSPACE" fetch --no-tags --depth=1 origin "+${CHECKOUT_SHA}:refs/remotes/origin/checkout"
|
||||
git -C "$GITHUB_WORKSPACE" checkout --detach refs/remotes/origin/checkout
|
||||
|
||||
- name: Setup Node environment
|
||||
uses: ./.github/actions/setup-node-env
|
||||
@@ -1589,11 +1621,16 @@ jobs:
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ needs.preflight.outputs.checkout_revision }}
|
||||
persist-credentials: true
|
||||
submodules: false
|
||||
env:
|
||||
CHECKOUT_REPO: ${{ github.repository }}
|
||||
CHECKOUT_SHA: ${{ needs.preflight.outputs.checkout_revision }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
git init "$GITHUB_WORKSPACE"
|
||||
git -C "$GITHUB_WORKSPACE" config gc.auto 0
|
||||
git -C "$GITHUB_WORKSPACE" remote add origin "https://github.com/${CHECKOUT_REPO}.git"
|
||||
git -C "$GITHUB_WORKSPACE" fetch --no-tags --depth=1 origin "+${CHECKOUT_SHA}:refs/remotes/origin/checkout"
|
||||
git -C "$GITHUB_WORKSPACE" checkout --detach refs/remotes/origin/checkout
|
||||
|
||||
- name: Install XcodeGen / SwiftLint / SwiftFormat
|
||||
run: brew install xcodegen swiftlint swiftformat
|
||||
@@ -1693,7 +1730,6 @@ jobs:
|
||||
env:
|
||||
CHECKOUT_REPO: ${{ github.repository }}
|
||||
CHECKOUT_SHA: ${{ needs.preflight.outputs.checkout_revision }}
|
||||
CHECKOUT_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
@@ -1709,10 +1745,10 @@ jobs:
|
||||
reset_checkout_dir
|
||||
git init "$workdir" >/dev/null
|
||||
git config --global --add safe.directory "$workdir"
|
||||
git -C "$workdir" remote add origin "https://x-access-token:${CHECKOUT_TOKEN}@github.com/${CHECKOUT_REPO}.git"
|
||||
git -C "$workdir" remote add origin "https://github.com/${CHECKOUT_REPO}.git"
|
||||
git -C "$workdir" config gc.auto 0
|
||||
|
||||
timeout --signal=TERM 30s git -C "$workdir" \
|
||||
timeout --signal=TERM --kill-after=10s 30s git -C "$workdir" \
|
||||
-c protocol.version=2 \
|
||||
fetch --no-tags --prune --no-recurse-submodules --depth=1 origin \
|
||||
"+${CHECKOUT_SHA}:refs/remotes/origin/ci-target" || return 1
|
||||
|
||||
30
.github/workflows/crabbox-hydrate.yml
vendored
@@ -141,7 +141,13 @@ jobs:
|
||||
|
||||
if ! command -v docker >/dev/null 2>&1; then
|
||||
echo "docker not found; installing fallback engine"
|
||||
curl -fsSL https://get.docker.com | sudo sh
|
||||
curl --fail --show-error --location \
|
||||
--connect-timeout "${OPENCLAW_CRABBOX_HYDRATE_DOWNLOAD_CONNECT_TIMEOUT_SECONDS:-15}" \
|
||||
--max-time "${OPENCLAW_CRABBOX_HYDRATE_DOWNLOAD_TIMEOUT_SECONDS:-300}" \
|
||||
--retry "${OPENCLAW_CRABBOX_HYDRATE_DOWNLOAD_RETRIES:-3}" \
|
||||
--retry-delay "${OPENCLAW_CRABBOX_HYDRATE_DOWNLOAD_RETRY_DELAY_SECONDS:-5}" \
|
||||
--retry-all-errors \
|
||||
https://get.docker.com | sudo sh
|
||||
fi
|
||||
|
||||
if command -v systemctl >/dev/null 2>&1; then
|
||||
@@ -166,7 +172,12 @@ jobs:
|
||||
esac
|
||||
buildx_version="${DOCKER_BUILDX_VERSION:-v0.15.1}"
|
||||
mkdir -p "$HOME/.docker/cli-plugins"
|
||||
curl -fsSL \
|
||||
curl --fail --show-error --location \
|
||||
--connect-timeout "${OPENCLAW_CRABBOX_HYDRATE_DOWNLOAD_CONNECT_TIMEOUT_SECONDS:-15}" \
|
||||
--max-time "${OPENCLAW_CRABBOX_HYDRATE_DOWNLOAD_TIMEOUT_SECONDS:-300}" \
|
||||
--retry "${OPENCLAW_CRABBOX_HYDRATE_DOWNLOAD_RETRIES:-3}" \
|
||||
--retry-delay "${OPENCLAW_CRABBOX_HYDRATE_DOWNLOAD_RETRY_DELAY_SECONDS:-5}" \
|
||||
--retry-all-errors \
|
||||
"https://github.com/docker/buildx/releases/download/${buildx_version}/buildx-${buildx_version}.linux-${buildx_arch}" \
|
||||
-o "$HOME/.docker/cli-plugins/docker-buildx"
|
||||
chmod 0755 "$HOME/.docker/cli-plugins/docker-buildx"
|
||||
@@ -307,7 +318,13 @@ jobs:
|
||||
|
||||
if ! command -v docker >/dev/null 2>&1; then
|
||||
echo "docker not found; installing fallback engine"
|
||||
curl -fsSL https://get.docker.com | sudo sh
|
||||
curl --fail --show-error --location \
|
||||
--connect-timeout "${OPENCLAW_CRABBOX_HYDRATE_DOWNLOAD_CONNECT_TIMEOUT_SECONDS:-15}" \
|
||||
--max-time "${OPENCLAW_CRABBOX_HYDRATE_DOWNLOAD_TIMEOUT_SECONDS:-300}" \
|
||||
--retry "${OPENCLAW_CRABBOX_HYDRATE_DOWNLOAD_RETRIES:-3}" \
|
||||
--retry-delay "${OPENCLAW_CRABBOX_HYDRATE_DOWNLOAD_RETRY_DELAY_SECONDS:-5}" \
|
||||
--retry-all-errors \
|
||||
https://get.docker.com | sudo sh
|
||||
fi
|
||||
|
||||
if command -v systemctl >/dev/null 2>&1; then
|
||||
@@ -332,7 +349,12 @@ jobs:
|
||||
esac
|
||||
buildx_version="${DOCKER_BUILDX_VERSION:-v0.15.1}"
|
||||
mkdir -p "$HOME/.docker/cli-plugins"
|
||||
curl -fsSL \
|
||||
curl --fail --show-error --location \
|
||||
--connect-timeout "${OPENCLAW_CRABBOX_HYDRATE_DOWNLOAD_CONNECT_TIMEOUT_SECONDS:-15}" \
|
||||
--max-time "${OPENCLAW_CRABBOX_HYDRATE_DOWNLOAD_TIMEOUT_SECONDS:-300}" \
|
||||
--retry "${OPENCLAW_CRABBOX_HYDRATE_DOWNLOAD_RETRIES:-3}" \
|
||||
--retry-delay "${OPENCLAW_CRABBOX_HYDRATE_DOWNLOAD_RETRY_DELAY_SECONDS:-5}" \
|
||||
--retry-all-errors \
|
||||
"https://github.com/docker/buildx/releases/download/${buildx_version}/buildx-${buildx_version}.linux-${buildx_arch}" \
|
||||
-o "$HOME/.docker/cli-plugins/docker-buildx"
|
||||
chmod 0755 "$HOME/.docker/cli-plugins/docker-buildx"
|
||||
|
||||
48
.github/workflows/full-release-validation.yml
vendored
@@ -245,7 +245,7 @@ jobs:
|
||||
DOCKER_BUILDKIT: "1"
|
||||
run: |
|
||||
set -euo pipefail
|
||||
timeout --foreground --kill-after=30s 35m docker build \
|
||||
timeout --kill-after=30s 35m docker build \
|
||||
--target runtime-assets \
|
||||
--build-arg OPENCLAW_EXTENSIONS="diagnostics-otel,codex" \
|
||||
.
|
||||
@@ -287,7 +287,7 @@ jobs:
|
||||
printf '%s\n' "$output"
|
||||
return 0
|
||||
fi
|
||||
if [[ "$output" == *"Bad credentials"* || "$output" == *"HTTP 401"* || "$output" == *"secondary rate limit"* || "$output" == *"API rate limit"* ]]; then
|
||||
if [[ "$output" == *"Bad credentials"* || "$output" == *"HTTP 401"* || "$output" == *"secondary rate limit"* || "$output" == *"API rate limit"* || "$output" == *"Sorry. Your account was suspended"* ]]; then
|
||||
echo "::warning::gh $* failed on attempt ${attempt}: ${output}" >&2
|
||||
sleep $((attempt * 10))
|
||||
continue
|
||||
@@ -417,7 +417,7 @@ jobs:
|
||||
printf '%s\n' "$output"
|
||||
return 0
|
||||
fi
|
||||
if [[ "$output" == *"Bad credentials"* || "$output" == *"HTTP 401"* || "$output" == *"secondary rate limit"* || "$output" == *"API rate limit"* ]]; then
|
||||
if [[ "$output" == *"Bad credentials"* || "$output" == *"HTTP 401"* || "$output" == *"secondary rate limit"* || "$output" == *"API rate limit"* || "$output" == *"Sorry. Your account was suspended"* ]]; then
|
||||
echo "::warning::gh $* failed on attempt ${attempt}: ${output}" >&2
|
||||
sleep $((attempt * 10))
|
||||
continue
|
||||
@@ -557,7 +557,7 @@ jobs:
|
||||
printf '%s\n' "$output"
|
||||
return 0
|
||||
fi
|
||||
if [[ "$output" == *"Bad credentials"* || "$output" == *"HTTP 401"* || "$output" == *"secondary rate limit"* || "$output" == *"API rate limit"* ]]; then
|
||||
if [[ "$output" == *"Bad credentials"* || "$output" == *"HTTP 401"* || "$output" == *"secondary rate limit"* || "$output" == *"API rate limit"* || "$output" == *"Sorry. Your account was suspended"* ]]; then
|
||||
echo "::warning::gh $* failed on attempt ${attempt}: ${output}" >&2
|
||||
sleep $((attempt * 10))
|
||||
continue
|
||||
@@ -859,7 +859,7 @@ jobs:
|
||||
printf '%s\n' "$output"
|
||||
return 0
|
||||
fi
|
||||
if [[ "$output" == *"Bad credentials"* || "$output" == *"HTTP 401"* || "$output" == *"secondary rate limit"* || "$output" == *"API rate limit"* ]]; then
|
||||
if [[ "$output" == *"Bad credentials"* || "$output" == *"HTTP 401"* || "$output" == *"secondary rate limit"* || "$output" == *"API rate limit"* || "$output" == *"Sorry. Your account was suspended"* ]]; then
|
||||
echo "::warning::gh $* failed on attempt ${attempt}: ${output}" >&2
|
||||
sleep $((attempt * 10))
|
||||
continue
|
||||
@@ -975,7 +975,7 @@ jobs:
|
||||
printf '%s\n' "$output"
|
||||
return 0
|
||||
fi
|
||||
if [[ "$output" == *"Bad credentials"* || "$output" == *"HTTP 401"* || "$output" == *"secondary rate limit"* || "$output" == *"API rate limit"* ]]; then
|
||||
if [[ "$output" == *"Bad credentials"* || "$output" == *"HTTP 401"* || "$output" == *"secondary rate limit"* || "$output" == *"API rate limit"* || "$output" == *"Sorry. Your account was suspended"* ]]; then
|
||||
echo "::warning::gh $* failed on attempt ${attempt}: ${output}" >&2
|
||||
sleep $((attempt * 10))
|
||||
continue
|
||||
@@ -1089,6 +1089,29 @@ jobs:
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
gh_with_retry() {
|
||||
local output status attempt
|
||||
for attempt in 1 2 3 4 5 6; do
|
||||
set +e
|
||||
output="$(gh "$@" 2>&1)"
|
||||
status=$?
|
||||
set -e
|
||||
if [[ "$status" -eq 0 ]]; then
|
||||
printf '%s\n' "$output"
|
||||
return 0
|
||||
fi
|
||||
if [[ "$output" == *"Bad credentials"* || "$output" == *"HTTP 401"* || "$output" == *"secondary rate limit"* || "$output" == *"API rate limit"* || "$output" == *"Sorry. Your account was suspended"* ]]; then
|
||||
echo "::warning::gh $* failed on attempt ${attempt}: ${output}" >&2
|
||||
sleep $((attempt * 10))
|
||||
continue
|
||||
fi
|
||||
printf '%s\n' "$output" >&2
|
||||
return "$status"
|
||||
done
|
||||
printf '%s\n' "$output" >&2
|
||||
return "$status"
|
||||
}
|
||||
|
||||
release_check_blocking_job() {
|
||||
case "$1" in
|
||||
"resolve_target" | \
|
||||
@@ -1145,7 +1168,7 @@ jobs:
|
||||
fi
|
||||
|
||||
local run_json status conclusion url attempt head_sha
|
||||
run_json="$(gh run view "$run_id" --json status,conclusion,url,attempt,headSha,jobs)"
|
||||
run_json="$(gh_with_retry run view "$run_id" --json status,conclusion,url,attempt,headSha,jobs)"
|
||||
status="$(jq -r '.status' <<< "$run_json")"
|
||||
conclusion="$(jq -r '.conclusion' <<< "$run_json")"
|
||||
url="$(jq -r '.url' <<< "$run_json")"
|
||||
@@ -1192,7 +1215,7 @@ jobs:
|
||||
fi
|
||||
|
||||
local run_json row
|
||||
run_json="$(gh run view "$run_id" --json status,conclusion,url,createdAt,updatedAt,headSha)"
|
||||
run_json="$(gh_with_retry run view "$run_id" --json status,conclusion,url,createdAt,updatedAt,headSha)"
|
||||
row="$(
|
||||
jq -r --arg label "$label" '
|
||||
def ts: fromdateiso8601;
|
||||
@@ -1228,7 +1251,7 @@ jobs:
|
||||
echo
|
||||
echo "### Slowest jobs: ${label}"
|
||||
echo
|
||||
gh run view "$run_id" --json jobs --jq '
|
||||
gh_with_retry run view "$run_id" --json jobs --jq '
|
||||
def ts: fromdateiso8601;
|
||||
"| Job | Result | Minutes |",
|
||||
"| --- | --- | ---: |",
|
||||
@@ -1245,7 +1268,7 @@ jobs:
|
||||
echo
|
||||
echo "### Longest queues: ${label}"
|
||||
echo
|
||||
gh api --paginate "repos/${GITHUB_REPOSITORY}/actions/runs/${run_id}/jobs?per_page=100" --jq ".jobs[] | @json" | jq -sr '
|
||||
gh_with_retry api --paginate "repos/${GITHUB_REPOSITORY}/actions/runs/${run_id}/jobs?per_page=100" --jq ".jobs[] | @json" | jq -sr '
|
||||
def ts: fromdateiso8601;
|
||||
"| Job | Result | Queue minutes | Run minutes |",
|
||||
"| --- | --- | ---: | ---: |",
|
||||
@@ -1274,7 +1297,7 @@ jobs:
|
||||
fi
|
||||
|
||||
local run_json status conclusion artifacts_json
|
||||
run_json="$(gh run view "$run_id" --json status,conclusion,url,jobs)"
|
||||
run_json="$(gh_with_retry run view "$run_id" --json status,conclusion,url,jobs)"
|
||||
status="$(jq -r '.status' <<< "$run_json")"
|
||||
conclusion="$(jq -r '.conclusion' <<< "$run_json")"
|
||||
if [[ "$status" == "completed" && "$conclusion" == "success" ]]; then
|
||||
@@ -1297,7 +1320,7 @@ jobs:
|
||||
echo
|
||||
echo "Artifacts:"
|
||||
artifacts_json="$(
|
||||
gh api "repos/${GITHUB_REPOSITORY}/actions/runs/${run_id}/artifacts?per_page=100" 2>/dev/null || true
|
||||
gh_with_retry api "repos/${GITHUB_REPOSITORY}/actions/runs/${run_id}/artifacts?per_page=100" 2>/dev/null || true
|
||||
)"
|
||||
if [[ -n "${artifacts_json// }" ]]; then
|
||||
jq -r '
|
||||
@@ -1471,6 +1494,7 @@ jobs:
|
||||
PLUGIN_PRERELEASE_RUN_ID: ${{ needs.plugin_prerelease.outputs.run_id }}
|
||||
RELEASE_CHECKS_RUN_ID: ${{ needs.release_checks.outputs.run_id }}
|
||||
NPM_TELEGRAM_RUN_ID: ${{ needs.npm_telegram.outputs.run_id }}
|
||||
PERFORMANCE_RUN_ID: ${{ needs.performance.outputs.run_id }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
manifest_dir="${RUNNER_TEMP}/full-release-validation"
|
||||
|
||||
35
.github/workflows/install-smoke.yml
vendored
@@ -121,7 +121,7 @@ jobs:
|
||||
# builder stalls; an explicit buildx invocation fails closed instead.
|
||||
- name: Build root Dockerfile smoke image
|
||||
run: |
|
||||
timeout 45m docker buildx build \
|
||||
timeout --kill-after=30s 45m docker buildx build \
|
||||
--progress=plain \
|
||||
--load \
|
||||
--build-arg OPENCLAW_EXTENSIONS=matrix \
|
||||
@@ -132,7 +132,7 @@ jobs:
|
||||
|
||||
- name: Run root Dockerfile CLI smoke
|
||||
run: |
|
||||
docker run --rm --entrypoint sh openclaw-dockerfile-smoke:local -lc '
|
||||
timeout --kill-after=30s 20m docker run --rm --entrypoint sh openclaw-dockerfile-smoke:local -lc '
|
||||
which openclaw &&
|
||||
openclaw --version &&
|
||||
node -e "
|
||||
@@ -163,7 +163,7 @@ jobs:
|
||||
|
||||
- name: Smoke test Dockerfile with matrix extension build arg
|
||||
run: |
|
||||
docker run --rm --entrypoint sh openclaw-ext-smoke:local -lc '
|
||||
timeout --kill-after=30s 20m docker run --rm --entrypoint sh openclaw-ext-smoke:local -lc '
|
||||
which openclaw &&
|
||||
openclaw --version &&
|
||||
node -e "
|
||||
@@ -235,7 +235,7 @@ jobs:
|
||||
IMAGE_REF: ${{ needs.preflight.outputs.dockerfile_image }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if timeout 180s docker pull "$IMAGE_REF"; then
|
||||
if timeout --kill-after=30s 180s docker pull "$IMAGE_REF"; then
|
||||
echo "exists=true" >> "$GITHUB_OUTPUT"
|
||||
echo "Using existing root Dockerfile smoke image: \`$IMAGE_REF\`" >> "$GITHUB_STEP_SUMMARY"
|
||||
else
|
||||
@@ -256,7 +256,7 @@ jobs:
|
||||
env:
|
||||
IMAGE_REF: ${{ needs.preflight.outputs.dockerfile_image }}
|
||||
run: |
|
||||
timeout 45m docker buildx build \
|
||||
timeout --kill-after=30s 45m docker buildx build \
|
||||
--progress=plain \
|
||||
--push \
|
||||
--build-arg OPENCLAW_EXTENSIONS=matrix \
|
||||
@@ -320,13 +320,13 @@ jobs:
|
||||
- name: Pull root Dockerfile smoke image
|
||||
env:
|
||||
IMAGE_REF: ${{ needs.root_dockerfile_image.outputs.image_ref }}
|
||||
run: timeout 600s docker pull "$IMAGE_REF"
|
||||
run: timeout --kill-after=30s 600s docker pull "$IMAGE_REF"
|
||||
|
||||
- name: Run root Dockerfile CLI smoke
|
||||
env:
|
||||
IMAGE_REF: ${{ needs.root_dockerfile_image.outputs.image_ref }}
|
||||
run: |
|
||||
docker run --rm --entrypoint sh "$IMAGE_REF" -lc '
|
||||
timeout --kill-after=30s 20m docker run --rm --entrypoint sh "$IMAGE_REF" -lc '
|
||||
which openclaw &&
|
||||
openclaw --version &&
|
||||
node -e "
|
||||
@@ -359,7 +359,7 @@ jobs:
|
||||
env:
|
||||
IMAGE_REF: ${{ needs.root_dockerfile_image.outputs.image_ref }}
|
||||
run: |
|
||||
docker run --rm --entrypoint sh "$IMAGE_REF" -lc '
|
||||
timeout --kill-after=30s 20m docker run --rm --entrypoint sh "$IMAGE_REF" -lc '
|
||||
which openclaw &&
|
||||
openclaw --version &&
|
||||
node -e "
|
||||
@@ -426,7 +426,7 @@ jobs:
|
||||
- name: Pull root Dockerfile smoke image
|
||||
env:
|
||||
IMAGE_REF: ${{ needs.root_dockerfile_image.outputs.image_ref }}
|
||||
run: timeout 600s docker pull "$IMAGE_REF"
|
||||
run: timeout --kill-after=30s 600s docker pull "$IMAGE_REF"
|
||||
|
||||
- name: Set up Blacksmith Docker Builder
|
||||
uses: useblacksmith/setup-docker-builder@722e97d12b1d06a961800dd6c05d79d951ad3c80 # v1
|
||||
@@ -435,7 +435,7 @@ jobs:
|
||||
|
||||
- name: Build installer smoke image
|
||||
run: |
|
||||
timeout 20m docker buildx build \
|
||||
timeout --kill-after=30s 20m docker buildx build \
|
||||
--progress=plain \
|
||||
--load \
|
||||
-t openclaw-install-smoke:local \
|
||||
@@ -444,7 +444,7 @@ jobs:
|
||||
|
||||
- name: Build installer non-root image
|
||||
run: |
|
||||
timeout 20m docker buildx build \
|
||||
timeout --kill-after=30s 20m docker buildx build \
|
||||
--progress=plain \
|
||||
--load \
|
||||
-t openclaw-install-nonroot:local \
|
||||
@@ -475,13 +475,22 @@ jobs:
|
||||
|
||||
- name: Run Rocky Linux installer smoke
|
||||
run: |
|
||||
timeout 20m docker run --rm \
|
||||
timeout --kill-after=30s 20m docker run --rm \
|
||||
-e OPENCLAW_NO_ONBOARD=1 \
|
||||
-e OPENCLAW_NO_PROMPT=1 \
|
||||
-v "$PWD/scripts/install.sh:/tmp/install.sh:ro" \
|
||||
rockylinux:9@sha256:d7be1c094cc5845ee815d4632fe377514ee6ebcf8efaed6892889657e5ddaaa6 \
|
||||
bash -lc 'dnf install -y -q ca-certificates tar gzip xz findutils which sudo >/dev/null && bash /tmp/install.sh --install-method npm --version latest --no-onboard --no-prompt --verify && openclaw --version'
|
||||
|
||||
- name: Run Rocky Linux CLI installer smoke
|
||||
run: |
|
||||
timeout --kill-after=30s 20m docker run --rm \
|
||||
-e OPENCLAW_NO_ONBOARD=1 \
|
||||
-e OPENCLAW_NO_PROMPT=1 \
|
||||
-v "$PWD/scripts/install-cli.sh:/tmp/install-cli.sh:ro" \
|
||||
rockylinux:9@sha256:d7be1c094cc5845ee815d4632fe377514ee6ebcf8efaed6892889657e5ddaaa6 \
|
||||
bash -lc 'dnf install -y -q ca-certificates tar gzip xz findutils which sudo >/dev/null && bash /tmp/install-cli.sh --prefix /tmp/openclaw-cli --version latest --no-onboard && /tmp/openclaw-cli/bin/openclaw --version'
|
||||
|
||||
bun_global_install_smoke:
|
||||
needs: [preflight, root_dockerfile_image]
|
||||
if: needs.preflight.outputs.run_full_install_smoke == 'true' && needs.preflight.outputs.run_bun_global_install_smoke == 'true'
|
||||
@@ -503,7 +512,7 @@ jobs:
|
||||
- name: Pull root Dockerfile smoke image
|
||||
env:
|
||||
IMAGE_REF: ${{ needs.root_dockerfile_image.outputs.image_ref }}
|
||||
run: timeout 600s docker pull "$IMAGE_REF"
|
||||
run: timeout --kill-after=30s 600s docker pull "$IMAGE_REF"
|
||||
|
||||
- name: Setup Node environment for Bun smoke
|
||||
uses: ./.github/actions/setup-node-env
|
||||
|
||||
@@ -48,6 +48,7 @@ env:
|
||||
OPENCLAW_BUILD_PRIVATE_QA: "1"
|
||||
OPENCLAW_ENABLE_PRIVATE_QA_CLI: "1"
|
||||
CRABBOX_REF: main
|
||||
CRABBOX_CAPACITY_REGIONS: eu-west-1,eu-west-2,eu-central-1,us-east-1,us-west-2
|
||||
MANTIS_OUTPUT_DIR: .artifacts/qa-e2e/mantis/telegram-desktop-proof
|
||||
|
||||
jobs:
|
||||
@@ -422,7 +423,7 @@ jobs:
|
||||
{
|
||||
printf '%s\n' 'Defaults env_keep += "CODEX_HOME CODEX_INTERNAL_ORIGINATOR_OVERRIDE"'
|
||||
printf '%s\n' 'Defaults env_keep += "BASELINE_REF BASELINE_SHA CANDIDATE_REF CANDIDATE_SHA"'
|
||||
printf '%s\n' 'Defaults env_keep += "CRABBOX_ACCESS_CLIENT_ID CRABBOX_ACCESS_CLIENT_SECRET CRABBOX_COORDINATOR CRABBOX_COORDINATOR_TOKEN CRABBOX_LEASE_ID CRABBOX_PROVIDER"'
|
||||
printf '%s\n' 'Defaults env_keep += "CRABBOX_ACCESS_CLIENT_ID CRABBOX_ACCESS_CLIENT_SECRET CRABBOX_COORDINATOR CRABBOX_COORDINATOR_TOKEN CRABBOX_LEASE_ID CRABBOX_PROVIDER CRABBOX_CAPACITY_REGIONS"'
|
||||
printf '%s\n' 'Defaults env_keep += "GH_TOKEN MANTIS_CANDIDATE_TRUST MANTIS_INSTRUCTIONS MANTIS_OUTPUT_DIR MANTIS_PR_NUMBER"'
|
||||
printf '%s\n' 'Defaults env_keep += "OPENCLAW_BUILD_PRIVATE_QA OPENCLAW_ENABLE_PRIVATE_QA_CLI OPENCLAW_QA_CONVEX_SECRET_CI OPENCLAW_QA_CONVEX_SITE_URL OPENCLAW_QA_CREDENTIAL_OWNER_ID OPENCLAW_QA_MANTIS_CRABBOX_COORDINATOR OPENCLAW_QA_MANTIS_CRABBOX_COORDINATOR_TOKEN"'
|
||||
printf '%s\n' 'Defaults env_keep += "OPENCLAW_TELEGRAM_USER_CRABBOX_BIN OPENCLAW_TELEGRAM_USER_CRABBOX_PROVIDER OPENCLAW_TELEGRAM_USER_DRIVER_SCRIPT OPENCLAW_TELEGRAM_USER_PROOF_CMD"'
|
||||
@@ -451,6 +452,7 @@ jobs:
|
||||
CRABBOX_ACCESS_CLIENT_SECRET: ${{ secrets.CRABBOX_ACCESS_CLIENT_SECRET }}
|
||||
CRABBOX_COORDINATOR: ${{ secrets.CRABBOX_COORDINATOR || secrets.OPENCLAW_QA_MANTIS_CRABBOX_COORDINATOR }}
|
||||
CRABBOX_COORDINATOR_TOKEN: ${{ secrets.CRABBOX_COORDINATOR_TOKEN || secrets.OPENCLAW_QA_MANTIS_CRABBOX_COORDINATOR_TOKEN }}
|
||||
CRABBOX_CAPACITY_REGIONS: ${{ env.CRABBOX_CAPACITY_REGIONS }}
|
||||
CRABBOX_LEASE_ID: ${{ needs.resolve_request.outputs.lease_id }}
|
||||
CRABBOX_PROVIDER: ${{ needs.resolve_request.outputs.crabbox_provider }}
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
@@ -492,8 +494,11 @@ jobs:
|
||||
exit 0
|
||||
fi
|
||||
status=0
|
||||
mapfile -d '' session_files < <(sudo find .artifacts/qa-e2e -path '*/telegram-user-crabbox/*/session.json' -type f -print0)
|
||||
mapfile -d '' session_files < <(sudo find .artifacts/qa-e2e -name session.json -type f -print0)
|
||||
for session_file in "${session_files[@]}"; do
|
||||
if ! sudo -u codex node -e 'const fs = require("fs"); const session = JSON.parse(fs.readFileSync(process.argv[1], "utf8")); process.exit(session.command === "telegram-user-crabbox-session" ? 0 : 1);' "$session_file"; then
|
||||
continue
|
||||
fi
|
||||
lease_file="${session_file%/session.json}/.session/lease.json"
|
||||
if [[ ! -f "$lease_file" ]]; then
|
||||
continue
|
||||
@@ -508,8 +513,11 @@ jobs:
|
||||
status=1
|
||||
fi
|
||||
done
|
||||
mapfile -d '' lease_files < <(sudo find .artifacts/qa-e2e -path '*/telegram-user-crabbox/*/.session/lease.json' -type f -print0)
|
||||
mapfile -d '' lease_files < <(sudo find .artifacts/qa-e2e -path '*/.session/lease.json' -type f -print0)
|
||||
for lease_file in "${lease_files[@]}"; do
|
||||
if ! sudo -u codex node -e 'const fs = require("fs"); const lease = JSON.parse(fs.readFileSync(process.argv[1], "utf8")); process.exit(lease.kind === "telegram-user" ? 0 : 1);' "$lease_file"; then
|
||||
continue
|
||||
fi
|
||||
if ! sudo -u codex env \
|
||||
OPENCLAW_QA_CONVEX_SECRET_CI="$OPENCLAW_QA_CONVEX_SECRET_CI" \
|
||||
OPENCLAW_QA_CONVEX_SITE_URL="$OPENCLAW_QA_CONVEX_SITE_URL" \
|
||||
|
||||
@@ -553,6 +553,15 @@ jobs:
|
||||
use-actions-cache: "false"
|
||||
|
||||
- name: Download candidate artifact
|
||||
id: download_candidate
|
||||
continue-on-error: true
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: openclaw-cross-os-release-checks-candidate-${{ github.run_id }}
|
||||
path: ${{ runner.temp }}/openclaw-cross-os-release-checks/candidate
|
||||
|
||||
- name: Retry candidate artifact download
|
||||
if: ${{ steps.download_candidate.outcome == 'failure' }}
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: openclaw-cross-os-release-checks-candidate-${{ github.run_id }}
|
||||
@@ -560,11 +569,38 @@ jobs:
|
||||
|
||||
- name: Download baseline artifact
|
||||
if: ${{ matrix.suite == 'packaged-upgrade' }}
|
||||
id: download_baseline
|
||||
continue-on-error: true
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: openclaw-cross-os-release-checks-baseline-${{ github.run_id }}
|
||||
path: ${{ runner.temp }}/openclaw-cross-os-release-checks/baseline
|
||||
|
||||
- name: Retry baseline artifact download
|
||||
if: ${{ matrix.suite == 'packaged-upgrade' && steps.download_baseline.outcome == 'failure' }}
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: openclaw-cross-os-release-checks-baseline-${{ github.run_id }}
|
||||
path: ${{ runner.temp }}/openclaw-cross-os-release-checks/baseline
|
||||
|
||||
- name: Verify release-check inputs
|
||||
shell: bash
|
||||
env:
|
||||
CANDIDATE_TGZ: ${{ runner.temp }}/openclaw-cross-os-release-checks/candidate/${{ needs.prepare.outputs.candidate_file_name }}
|
||||
BASELINE_TGZ: ${{ runner.temp }}/openclaw-cross-os-release-checks/baseline/${{ needs.prepare.outputs.baseline_file_name }}
|
||||
OUTPUT_DIR: ${{ runner.temp }}/openclaw-cross-os-release-checks/${{ matrix.artifact_name }}-${{ matrix.suite }}
|
||||
SUITE: ${{ matrix.suite }}
|
||||
run: |
|
||||
mkdir -p "${OUTPUT_DIR}"
|
||||
if [[ ! -f "${CANDIDATE_TGZ}" ]]; then
|
||||
echo "::error::candidate artifact missing: ${CANDIDATE_TGZ}"
|
||||
exit 1
|
||||
fi
|
||||
if [[ "${SUITE}" == "packaged-upgrade" ]] && [[ ! -f "${BASELINE_TGZ}" ]]; then
|
||||
echo "::error::baseline artifact missing: ${BASELINE_TGZ}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Run cross-OS release checks
|
||||
shell: bash
|
||||
env:
|
||||
@@ -615,7 +651,8 @@ jobs:
|
||||
if [[ -f "${SUMMARY_PATH}" ]]; then
|
||||
cat "${SUMMARY_PATH}" >> "$GITHUB_STEP_SUMMARY"
|
||||
else
|
||||
echo "No summary generated." >> "$GITHUB_STEP_SUMMARY"
|
||||
mkdir -p "$(dirname "${SUMMARY_PATH}")"
|
||||
echo "No summary generated." | tee "${SUMMARY_PATH}" >> "$GITHUB_STEP_SUMMARY"
|
||||
fi
|
||||
|
||||
- name: Upload release-check artifacts
|
||||
|
||||
@@ -102,6 +102,11 @@ on:
|
||||
- beta
|
||||
- stable
|
||||
- full
|
||||
use_github_hosted_runners:
|
||||
description: Use GitHub-hosted runners instead of Blacksmith runners
|
||||
required: false
|
||||
default: false
|
||||
type: boolean
|
||||
advisory:
|
||||
description: Treat failures as advisory for the caller
|
||||
required: false
|
||||
@@ -208,6 +213,11 @@ on:
|
||||
required: false
|
||||
default: stable
|
||||
type: string
|
||||
use_github_hosted_runners:
|
||||
description: Use GitHub-hosted runners instead of Blacksmith runners
|
||||
required: false
|
||||
default: true
|
||||
type: boolean
|
||||
secrets:
|
||||
OPENAI_API_KEY:
|
||||
required: false
|
||||
@@ -474,7 +484,7 @@ jobs:
|
||||
needs: validate_selected_ref
|
||||
if: inputs.include_live_suites && !inputs.live_models_only && (inputs.live_suite_filter == '' || inputs.live_suite_filter == 'live-cache')
|
||||
continue-on-error: ${{ inputs.advisory }}
|
||||
runs-on: ${{ github.event_name == 'workflow_call' && 'ubuntu-24.04' || 'blacksmith-8vcpu-ubuntu-2404' }}
|
||||
runs-on: ${{ inputs.use_github_hosted_runners && 'ubuntu-24.04' || 'blacksmith-8vcpu-ubuntu-2404' }}
|
||||
timeout-minutes: 20
|
||||
env:
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
@@ -511,7 +521,7 @@ jobs:
|
||||
set -euo pipefail
|
||||
for attempt in 1 2; do
|
||||
echo "live-cache attempt ${attempt}/2"
|
||||
if timeout --foreground --kill-after=30s 8m pnpm test:live:cache; then
|
||||
if timeout --kill-after=30s 8m pnpm test:live:cache; then
|
||||
exit 0
|
||||
fi
|
||||
if [[ "$attempt" == "2" ]]; then
|
||||
@@ -524,7 +534,7 @@ jobs:
|
||||
needs: validate_selected_ref
|
||||
if: inputs.include_repo_e2e && inputs.live_suite_filter == ''
|
||||
continue-on-error: ${{ inputs.advisory }}
|
||||
runs-on: ${{ github.event_name == 'workflow_call' && 'ubuntu-24.04' || 'blacksmith-8vcpu-ubuntu-2404' }}
|
||||
runs-on: ${{ inputs.use_github_hosted_runners && 'ubuntu-24.04' || 'blacksmith-8vcpu-ubuntu-2404' }}
|
||||
timeout-minutes: ${{ inputs.release_test_profile == 'full' && 90 || 60 }}
|
||||
env:
|
||||
OPENCLAW_VITEST_MAX_WORKERS: "2"
|
||||
@@ -556,7 +566,7 @@ jobs:
|
||||
needs: validate_selected_ref
|
||||
if: inputs.include_repo_e2e && (inputs.live_suite_filter == '' || inputs.live_suite_filter == 'openshell-e2e')
|
||||
continue-on-error: ${{ inputs.advisory }}
|
||||
runs-on: ${{ github.event_name == 'workflow_call' && 'ubuntu-24.04' || 'blacksmith-8vcpu-ubuntu-2404' }}
|
||||
runs-on: ${{ inputs.use_github_hosted_runners && 'ubuntu-24.04' || 'blacksmith-8vcpu-ubuntu-2404' }}
|
||||
timeout-minutes: ${{ matrix.timeout_minutes }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -630,7 +640,7 @@ jobs:
|
||||
if: inputs.include_release_path_suites && inputs.docker_lanes == ''
|
||||
name: Docker E2E (${{ matrix.label }})
|
||||
continue-on-error: ${{ inputs.advisory }}
|
||||
runs-on: ${{ github.event_name == 'workflow_call' && 'ubuntu-24.04' || 'blacksmith-32vcpu-ubuntu-2404' }}
|
||||
runs-on: ${{ inputs.use_github_hosted_runners && 'ubuntu-24.04' || 'blacksmith-32vcpu-ubuntu-2404' }}
|
||||
timeout-minutes: ${{ matrix.timeout_minutes }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -921,7 +931,7 @@ jobs:
|
||||
needs: validate_selected_ref
|
||||
if: inputs.docker_lanes != ''
|
||||
continue-on-error: ${{ inputs.advisory }}
|
||||
runs-on: ${{ github.event_name == 'workflow_call' && 'ubuntu-24.04' || 'blacksmith-4vcpu-ubuntu-2404' }}
|
||||
runs-on: ${{ inputs.use_github_hosted_runners && 'ubuntu-24.04' || 'blacksmith-4vcpu-ubuntu-2404' }}
|
||||
timeout-minutes: 5
|
||||
outputs:
|
||||
groups_json: ${{ steps.groups.outputs.groups_json }}
|
||||
@@ -950,7 +960,7 @@ jobs:
|
||||
if: inputs.docker_lanes != ''
|
||||
name: Docker E2E targeted lanes (${{ matrix.group.label }})
|
||||
continue-on-error: ${{ inputs.advisory }}
|
||||
runs-on: ${{ github.event_name == 'workflow_call' && 'ubuntu-24.04' || 'blacksmith-32vcpu-ubuntu-2404' }}
|
||||
runs-on: ${{ inputs.use_github_hosted_runners && 'ubuntu-24.04' || 'blacksmith-32vcpu-ubuntu-2404' }}
|
||||
timeout-minutes: 60
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -1182,7 +1192,7 @@ jobs:
|
||||
if: inputs.include_openwebui && !inputs.include_release_path_suites && inputs.docker_lanes == ''
|
||||
name: Docker E2E (openwebui)
|
||||
continue-on-error: ${{ inputs.advisory }}
|
||||
runs-on: ${{ github.event_name == 'workflow_call' && 'ubuntu-24.04' || 'blacksmith-32vcpu-ubuntu-2404' }}
|
||||
runs-on: ${{ inputs.use_github_hosted_runners && 'ubuntu-24.04' || 'blacksmith-32vcpu-ubuntu-2404' }}
|
||||
timeout-minutes: 60
|
||||
env:
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
@@ -1308,7 +1318,7 @@ jobs:
|
||||
needs: validate_selected_ref
|
||||
if: inputs.include_release_path_suites || inputs.include_openwebui || inputs.docker_lanes != ''
|
||||
continue-on-error: ${{ inputs.advisory }}
|
||||
runs-on: ${{ github.event_name == 'workflow_call' && 'ubuntu-24.04' || 'blacksmith-32vcpu-ubuntu-2404' }}
|
||||
runs-on: ${{ inputs.use_github_hosted_runners && 'ubuntu-24.04' || 'blacksmith-32vcpu-ubuntu-2404' }}
|
||||
timeout-minutes: ${{ inputs.release_test_profile == 'full' && 90 || 60 }}
|
||||
permissions:
|
||||
actions: read
|
||||
@@ -1424,7 +1434,7 @@ jobs:
|
||||
fi
|
||||
echo "Validating Docker E2E package tarball: $target"
|
||||
started_at="$(date +%s)"
|
||||
timeout --foreground 5m node scripts/check-openclaw-package-tarball.mjs "$target"
|
||||
timeout --kill-after=30s 5m node scripts/check-openclaw-package-tarball.mjs "$target"
|
||||
finished_at="$(date +%s)"
|
||||
echo "Docker E2E package tarball validation finished in $((finished_at - started_at))s."
|
||||
digest="$(sha256sum "$target" | awk '{print $1}')"
|
||||
@@ -1551,7 +1561,7 @@ jobs:
|
||||
needs: validate_selected_ref
|
||||
if: inputs.include_live_suites && (inputs.live_suite_filter == '' || startsWith(inputs.live_suite_filter, 'live-') || startsWith(inputs.live_suite_filter, 'docker-live-models'))
|
||||
continue-on-error: ${{ inputs.advisory }}
|
||||
runs-on: ${{ github.event_name == 'workflow_call' && 'ubuntu-24.04' || 'blacksmith-32vcpu-ubuntu-2404' }}
|
||||
runs-on: ${{ inputs.use_github_hosted_runners && 'ubuntu-24.04' || 'blacksmith-32vcpu-ubuntu-2404' }}
|
||||
timeout-minutes: 60
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -1624,7 +1634,7 @@ jobs:
|
||||
needs: [validate_selected_ref, prepare_live_test_image]
|
||||
if: inputs.include_live_suites && inputs.live_model_providers == '' && (inputs.live_suite_filter == '' || inputs.live_suite_filter == 'docker-live-models')
|
||||
continue-on-error: ${{ inputs.advisory }}
|
||||
runs-on: ${{ github.event_name == 'workflow_call' && 'ubuntu-24.04' || 'blacksmith-32vcpu-ubuntu-2404' }}
|
||||
runs-on: ${{ inputs.use_github_hosted_runners && 'ubuntu-24.04' || 'blacksmith-32vcpu-ubuntu-2404' }}
|
||||
timeout-minutes: 45
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -1768,14 +1778,14 @@ jobs:
|
||||
|
||||
- name: Run Docker live model sweep
|
||||
if: contains(matrix.profiles, inputs.release_test_profile)
|
||||
run: OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --foreground --kill-after=30s 35m bash .release-harness/scripts/test-live-models-docker.sh
|
||||
run: OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --kill-after=30s 35m bash .release-harness/scripts/test-live-models-docker.sh
|
||||
|
||||
validate_live_models_docker_targeted:
|
||||
name: Docker live models (selected providers)
|
||||
needs: [validate_selected_ref, prepare_live_test_image]
|
||||
if: inputs.include_live_suites && inputs.live_model_providers != '' && (inputs.live_suite_filter == '' || inputs.live_suite_filter == 'docker-live-models')
|
||||
continue-on-error: ${{ inputs.advisory }}
|
||||
runs-on: ${{ github.event_name == 'workflow_call' && 'ubuntu-24.04' || 'blacksmith-32vcpu-ubuntu-2404' }}
|
||||
runs-on: ${{ inputs.use_github_hosted_runners && 'ubuntu-24.04' || 'blacksmith-32vcpu-ubuntu-2404' }}
|
||||
timeout-minutes: 45
|
||||
env:
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
@@ -1943,13 +1953,13 @@ jobs:
|
||||
done
|
||||
|
||||
- name: Run Docker live model sweep
|
||||
run: OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --foreground --kill-after=30s 35m bash .release-harness/scripts/test-live-models-docker.sh
|
||||
run: OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --kill-after=30s 35m bash .release-harness/scripts/test-live-models-docker.sh
|
||||
|
||||
validate_live_provider_suites:
|
||||
needs: validate_selected_ref
|
||||
if: inputs.include_live_suites && !inputs.live_models_only && (inputs.live_suite_filter == '' || (startsWith(inputs.live_suite_filter, 'native-live-') && !startsWith(inputs.live_suite_filter, 'native-live-extensions-media') && inputs.live_suite_filter != 'native-live-extensions-a-k'))
|
||||
continue-on-error: ${{ inputs.advisory }}
|
||||
runs-on: ${{ github.event_name == 'workflow_call' && 'ubuntu-24.04' || 'blacksmith-8vcpu-ubuntu-2404' }}
|
||||
runs-on: ${{ inputs.use_github_hosted_runners && 'ubuntu-24.04' || 'blacksmith-8vcpu-ubuntu-2404' }}
|
||||
timeout-minutes: ${{ matrix.timeout_minutes }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -2251,6 +2261,7 @@ jobs:
|
||||
env:
|
||||
OPENCLAW_LIVE_COMMAND: ${{ matrix.command }}
|
||||
OPENCLAW_LIVE_SUITE_ADVISORY: ${{ matrix.advisory }}
|
||||
shell: bash
|
||||
run: |
|
||||
set +e
|
||||
bash .release-harness/scripts/ci-live-command-retry.sh
|
||||
@@ -2270,7 +2281,7 @@ jobs:
|
||||
needs: [validate_selected_ref, prepare_live_test_image]
|
||||
if: inputs.include_live_suites && !inputs.live_models_only && (inputs.live_suite_filter == '' || startsWith(inputs.live_suite_filter, 'live-'))
|
||||
continue-on-error: ${{ inputs.advisory }}
|
||||
runs-on: ${{ github.event_name == 'workflow_call' && 'ubuntu-24.04' || 'blacksmith-32vcpu-ubuntu-2404' }}
|
||||
runs-on: ${{ inputs.use_github_hosted_runners && 'ubuntu-24.04' || 'blacksmith-32vcpu-ubuntu-2404' }}
|
||||
timeout-minutes: ${{ matrix.timeout_minutes }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -2278,32 +2289,32 @@ jobs:
|
||||
include:
|
||||
- suite_id: live-gateway-docker
|
||||
label: Docker live gateway OpenAI
|
||||
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=openai OPENCLAW_LIVE_GATEWAY_MODELS=openai/gpt-5.5 OPENCLAW_LIVE_GATEWAY_MAX_MODELS=1 OPENCLAW_LIVE_GATEWAY_STEP_TIMEOUT_MS=90000 OPENCLAW_LIVE_GATEWAY_MODEL_TIMEOUT_MS=300000 OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --foreground --kill-after=30s 35m bash .release-harness/scripts/test-live-gateway-models-docker.sh
|
||||
command: OPENCLAW_LIVE_GATEWAY_THINKING=low OPENCLAW_LIVE_GATEWAY_PROVIDERS=openai OPENCLAW_LIVE_GATEWAY_MODELS=openai/gpt-5.5 OPENCLAW_LIVE_GATEWAY_MAX_MODELS=1 OPENCLAW_LIVE_GATEWAY_STEP_TIMEOUT_MS=90000 OPENCLAW_LIVE_GATEWAY_MODEL_TIMEOUT_MS=600000 OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --kill-after=30s 35m bash .release-harness/scripts/test-live-gateway-models-docker.sh
|
||||
timeout_minutes: 40
|
||||
profile_env_only: false
|
||||
profiles: beta minimum stable full
|
||||
- suite_id: live-gateway-anthropic-docker
|
||||
label: Docker live gateway Anthropic
|
||||
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=anthropic OPENCLAW_LIVE_GATEWAY_MAX_MODELS=2 OPENCLAW_LIVE_GATEWAY_STEP_TIMEOUT_MS=90000 OPENCLAW_LIVE_GATEWAY_MODEL_TIMEOUT_MS=180000 OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --foreground --kill-after=30s 35m bash .release-harness/scripts/test-live-gateway-models-docker.sh
|
||||
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=anthropic OPENCLAW_LIVE_GATEWAY_MODELS=anthropic/claude-sonnet-4-6 OPENCLAW_LIVE_GATEWAY_MAX_MODELS=1 OPENCLAW_LIVE_GATEWAY_STEP_TIMEOUT_MS=90000 OPENCLAW_LIVE_GATEWAY_MODEL_TIMEOUT_MS=180000 OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --kill-after=30s 35m bash .release-harness/scripts/test-live-gateway-models-docker.sh
|
||||
timeout_minutes: 40
|
||||
profile_env_only: false
|
||||
profiles: stable full
|
||||
- suite_id: live-gateway-google-docker
|
||||
label: Docker live gateway Google
|
||||
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=google OPENCLAW_LIVE_GATEWAY_MODELS=google/gemini-3.1-pro-preview,google/gemini-3-flash-preview OPENCLAW_LIVE_GATEWAY_MAX_MODELS=2 OPENCLAW_LIVE_GATEWAY_STEP_TIMEOUT_MS=90000 OPENCLAW_LIVE_GATEWAY_MODEL_TIMEOUT_MS=180000 OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --foreground --kill-after=30s 35m bash .release-harness/scripts/test-live-gateway-models-docker.sh
|
||||
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=google OPENCLAW_LIVE_GATEWAY_MODELS=google/gemini-3.1-pro-preview,google/gemini-3-flash-preview OPENCLAW_LIVE_GATEWAY_MAX_MODELS=2 OPENCLAW_LIVE_GATEWAY_STEP_TIMEOUT_MS=90000 OPENCLAW_LIVE_GATEWAY_MODEL_TIMEOUT_MS=180000 OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --kill-after=30s 35m bash .release-harness/scripts/test-live-gateway-models-docker.sh
|
||||
timeout_minutes: 40
|
||||
profile_env_only: false
|
||||
profiles: stable full
|
||||
- suite_id: live-gateway-minimax-docker
|
||||
label: Docker live gateway MiniMax
|
||||
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=minimax,minimax-portal OPENCLAW_LIVE_GATEWAY_MAX_MODELS=1 OPENCLAW_LIVE_GATEWAY_STEP_TIMEOUT_MS=90000 OPENCLAW_LIVE_GATEWAY_MODEL_TIMEOUT_MS=180000 OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --foreground --kill-after=30s 35m bash .release-harness/scripts/test-live-gateway-models-docker.sh
|
||||
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=minimax,minimax-portal OPENCLAW_LIVE_GATEWAY_MAX_MODELS=1 OPENCLAW_LIVE_GATEWAY_STEP_TIMEOUT_MS=90000 OPENCLAW_LIVE_GATEWAY_MODEL_TIMEOUT_MS=180000 OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --kill-after=30s 35m bash .release-harness/scripts/test-live-gateway-models-docker.sh
|
||||
timeout_minutes: 40
|
||||
profile_env_only: false
|
||||
profiles: stable full
|
||||
- suite_id: live-gateway-advisory-docker-deepseek-fireworks
|
||||
suite_group: live-gateway-advisory-docker
|
||||
label: Docker live gateway advisory DeepSeek/Fireworks
|
||||
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=deepseek,fireworks OPENCLAW_LIVE_GATEWAY_MAX_MODELS=2 OPENCLAW_LIVE_GATEWAY_STEP_TIMEOUT_MS=90000 OPENCLAW_LIVE_GATEWAY_MODEL_TIMEOUT_MS=180000 OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --foreground --kill-after=30s 35m bash .release-harness/scripts/test-live-gateway-models-docker.sh
|
||||
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=deepseek,fireworks OPENCLAW_LIVE_GATEWAY_MAX_MODELS=2 OPENCLAW_LIVE_GATEWAY_STEP_TIMEOUT_MS=90000 OPENCLAW_LIVE_GATEWAY_MODEL_TIMEOUT_MS=180000 OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --kill-after=30s 35m bash .release-harness/scripts/test-live-gateway-models-docker.sh
|
||||
timeout_minutes: 40
|
||||
profile_env_only: false
|
||||
advisory: true
|
||||
@@ -2311,7 +2322,7 @@ jobs:
|
||||
- suite_id: live-gateway-advisory-docker-opencode-openrouter
|
||||
suite_group: live-gateway-advisory-docker
|
||||
label: Docker live gateway advisory OpenCode/OpenRouter
|
||||
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=opencode-go,openrouter OPENCLAW_LIVE_GATEWAY_MAX_MODELS=2 OPENCLAW_LIVE_GATEWAY_STEP_TIMEOUT_MS=90000 OPENCLAW_LIVE_GATEWAY_MODEL_TIMEOUT_MS=180000 OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --foreground --kill-after=30s 35m bash .release-harness/scripts/test-live-gateway-models-docker.sh
|
||||
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=opencode-go,openrouter OPENCLAW_LIVE_GATEWAY_MAX_MODELS=2 OPENCLAW_LIVE_GATEWAY_STEP_TIMEOUT_MS=90000 OPENCLAW_LIVE_GATEWAY_MODEL_TIMEOUT_MS=180000 OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --kill-after=30s 35m bash .release-harness/scripts/test-live-gateway-models-docker.sh
|
||||
timeout_minutes: 40
|
||||
profile_env_only: false
|
||||
advisory: true
|
||||
@@ -2319,32 +2330,32 @@ jobs:
|
||||
- suite_id: live-gateway-advisory-docker-xai-zai
|
||||
suite_group: live-gateway-advisory-docker
|
||||
label: Docker live gateway advisory xAI/Z.ai
|
||||
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=xai,zai OPENCLAW_LIVE_GATEWAY_MAX_MODELS=2 OPENCLAW_LIVE_GATEWAY_STEP_TIMEOUT_MS=90000 OPENCLAW_LIVE_GATEWAY_MODEL_TIMEOUT_MS=180000 OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --foreground --kill-after=30s 35m bash .release-harness/scripts/test-live-gateway-models-docker.sh
|
||||
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=xai,zai OPENCLAW_LIVE_GATEWAY_MAX_MODELS=2 OPENCLAW_LIVE_GATEWAY_STEP_TIMEOUT_MS=90000 OPENCLAW_LIVE_GATEWAY_MODEL_TIMEOUT_MS=180000 OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --kill-after=30s 35m bash .release-harness/scripts/test-live-gateway-models-docker.sh
|
||||
timeout_minutes: 40
|
||||
profile_env_only: false
|
||||
advisory: true
|
||||
profiles: full
|
||||
- suite_id: live-cli-backend-docker
|
||||
label: Docker live CLI backend
|
||||
command: OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --foreground --kill-after=30s 45m bash .release-harness/scripts/test-live-cli-backend-docker.sh
|
||||
command: OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --kill-after=30s 45m bash .release-harness/scripts/test-live-cli-backend-docker.sh
|
||||
timeout_minutes: 50
|
||||
profile_env_only: false
|
||||
profiles: stable full
|
||||
- suite_id: live-acp-bind-docker
|
||||
label: Docker live ACP bind
|
||||
command: OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --foreground --kill-after=30s 45m bash .release-harness/scripts/test-live-acp-bind-docker.sh
|
||||
command: OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --kill-after=30s 45m bash .release-harness/scripts/test-live-acp-bind-docker.sh
|
||||
timeout_minutes: 50
|
||||
profile_env_only: false
|
||||
profiles: stable full
|
||||
- suite_id: live-codex-harness-docker
|
||||
label: Docker live Codex harness
|
||||
command: OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --foreground --kill-after=30s 35m bash .release-harness/scripts/test-live-codex-harness-docker.sh
|
||||
command: OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --kill-after=30s 35m bash .release-harness/scripts/test-live-codex-harness-docker.sh
|
||||
timeout_minutes: 40
|
||||
profile_env_only: false
|
||||
profiles: stable full
|
||||
- suite_id: live-subagent-announce-docker
|
||||
label: Docker live subagent announce
|
||||
command: OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --foreground --kill-after=30s 20m bash .release-harness/scripts/test-live-subagent-announce-docker.sh
|
||||
command: OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --kill-after=30s 20m bash .release-harness/scripts/test-live-subagent-announce-docker.sh
|
||||
timeout_minutes: 25
|
||||
profile_env_only: false
|
||||
profiles: stable full
|
||||
@@ -2469,6 +2480,7 @@ jobs:
|
||||
env:
|
||||
OPENCLAW_LIVE_COMMAND: ${{ matrix.command }}
|
||||
OPENCLAW_LIVE_SUITE_ADVISORY: ${{ matrix.advisory }}
|
||||
shell: bash
|
||||
run: |
|
||||
set +e
|
||||
bash .release-harness/scripts/ci-live-command-retry.sh
|
||||
@@ -2488,7 +2500,7 @@ jobs:
|
||||
needs: validate_selected_ref
|
||||
if: inputs.include_live_suites && !inputs.live_models_only && (inputs.live_suite_filter == '' || startsWith(inputs.live_suite_filter, 'native-live-extensions-media') || inputs.live_suite_filter == 'native-live-extensions-a-k')
|
||||
continue-on-error: ${{ inputs.advisory }}
|
||||
runs-on: ${{ github.event_name == 'workflow_call' && 'ubuntu-24.04' || 'blacksmith-8vcpu-ubuntu-2404' }}
|
||||
runs-on: ${{ inputs.use_github_hosted_runners && 'ubuntu-24.04' || 'blacksmith-8vcpu-ubuntu-2404' }}
|
||||
container:
|
||||
image: ghcr.io/openclaw/openclaw-live-media-runner:ubuntu-24.04
|
||||
credentials:
|
||||
@@ -2656,6 +2668,7 @@ jobs:
|
||||
if: contains(matrix.profiles, inputs.release_test_profile) && (inputs.live_suite_filter == '' || inputs.live_suite_filter == matrix.suite_id || (inputs.live_suite_filter == 'native-live-extensions-media-video' && startsWith(matrix.suite_id, 'native-live-extensions-media-video-')))
|
||||
env:
|
||||
OPENCLAW_LIVE_SUITE_ADVISORY: ${{ matrix.advisory }}
|
||||
shell: bash
|
||||
run: |
|
||||
set +e
|
||||
${{ matrix.command }}
|
||||
|
||||
83
.github/workflows/openclaw-performance.yml
vendored
@@ -307,7 +307,36 @@ jobs:
|
||||
exit 1
|
||||
fi
|
||||
report_md="${report_json%.json}.md"
|
||||
effective_status="$status"
|
||||
if [[ "$FAIL_ON_REGRESSION" == "true" && "$status" != "0" ]]; then
|
||||
if REPORT_JSON="$report_json" node <<'NODE'
|
||||
const fs = require("node:fs");
|
||||
const report = JSON.parse(fs.readFileSync(process.env.REPORT_JSON, "utf8"));
|
||||
const statuses = report.summary?.statuses ?? {};
|
||||
const nonPassStatuses = Object.entries(statuses)
|
||||
.filter(([status, count]) => status !== "PASS" && Number(count) > 0);
|
||||
const baselineRegressionCount =
|
||||
Number(report.baseline?.comparison?.regressionCount ?? report.gate?.baseline?.regressionCount ?? 0);
|
||||
const gate = report.gate;
|
||||
const toleratedPartial =
|
||||
gate?.verdict === "PARTIAL" &&
|
||||
Number(gate.blockingCount ?? 0) === 0 &&
|
||||
baselineRegressionCount === 0 &&
|
||||
nonPassStatuses.length === 0;
|
||||
if (!toleratedPartial) {
|
||||
process.exit(1);
|
||||
}
|
||||
NODE
|
||||
then
|
||||
effective_status=0
|
||||
{
|
||||
echo "Kova returned a partial release-gate verdict for filtered performance coverage, but all selected scenarios passed and no baseline regression was reported."
|
||||
echo
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
fi
|
||||
fi
|
||||
echo "status=$status" >> "$GITHUB_OUTPUT"
|
||||
echo "effective_status=$effective_status" >> "$GITHUB_OUTPUT"
|
||||
echo "report_json=$report_json" >> "$GITHUB_OUTPUT"
|
||||
echo "report_md=$report_md" >> "$GITHUB_OUTPUT"
|
||||
|
||||
@@ -344,8 +373,43 @@ jobs:
|
||||
EOF
|
||||
cat "$summary_path" >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
if [[ "$FAIL_ON_REGRESSION" == "true" && "$status" != "0" ]]; then
|
||||
exit "$status"
|
||||
if [[ "$FAIL_ON_REGRESSION" == "true" && "$effective_status" != "0" ]]; then
|
||||
exit "$effective_status"
|
||||
fi
|
||||
|
||||
- name: Fetch previous source performance baseline
|
||||
if: ${{ steps.lane.outputs.run == 'true' && matrix.lane == 'mock-provider' && steps.clawgrit.outputs.present == 'true' }}
|
||||
env:
|
||||
CLAWGRIT_REPORTS_TOKEN: ${{ secrets.CLAWGRIT_REPORTS_TOKEN }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
reports_root=".artifacts/clawgrit-baseline"
|
||||
mkdir -p "$reports_root"
|
||||
git -C "$reports_root" init -b main
|
||||
git -C "$reports_root" remote add origin "https://x-access-token:${CLAWGRIT_REPORTS_TOKEN}@github.com/openclaw/clawgrit-reports.git"
|
||||
if ! git -C "$reports_root" fetch --depth=1 origin main; then
|
||||
echo "No previous source performance baseline could be fetched." >> "$GITHUB_STEP_SUMMARY"
|
||||
exit 0
|
||||
fi
|
||||
git -C "$reports_root" checkout -B main FETCH_HEAD
|
||||
ref_slug="$(printf '%s' "${TESTED_REF}" | tr -c 'A-Za-z0-9._-' '-')"
|
||||
pointer="${reports_root}/openclaw-performance/${ref_slug}/latest-mock-provider.json"
|
||||
if [[ ! -f "$pointer" ]]; then
|
||||
echo "No previous source performance baseline exists for ${TESTED_REF}." >> "$GITHUB_STEP_SUMMARY"
|
||||
exit 0
|
||||
fi
|
||||
if ! latest_path="$(node -e "const fs=require('node:fs'); const data=JSON.parse(fs.readFileSync(process.argv[1],'utf8')); const value=String(data.path || ''); if (!/^openclaw-performance\\/[A-Za-z0-9._-]+\\/[0-9]+-[0-9]+\\/mock-provider$/u.test(value)) process.exit(1); process.stdout.write(value);" "$pointer")"; then
|
||||
echo "Previous source performance baseline pointer is invalid." >> "$GITHUB_STEP_SUMMARY"
|
||||
exit 0
|
||||
fi
|
||||
baseline_source="${reports_root}/${latest_path}/source"
|
||||
if [[ -d "$baseline_source" ]]; then
|
||||
baseline_source="$(realpath "$baseline_source")"
|
||||
echo "SOURCE_PERF_BASELINE_DIR=$baseline_source" >> "$GITHUB_ENV"
|
||||
echo "Using source performance baseline: ${latest_path}/source" >> "$GITHUB_STEP_SUMMARY"
|
||||
else
|
||||
echo "Previous source performance baseline has no source directory." >> "$GITHUB_STEP_SUMMARY"
|
||||
fi
|
||||
|
||||
- name: Run OpenClaw source performance probes
|
||||
@@ -359,7 +423,7 @@ jobs:
|
||||
fi
|
||||
|
||||
mkdir -p "$SOURCE_PERF_DIR/mock-hello"
|
||||
if ! node -e "const fs=require('node:fs'); const scripts=require('./package.json').scripts||{}; process.exit(scripts['test:gateway:cpu-scenarios'] && scripts.openclaw && fs.existsSync('scripts/bench-cli-startup.ts') ? 0 : 1)"; then
|
||||
if ! node -e "const fs=require('node:fs'); const scripts=require('./package.json').scripts||{}; process.exit(scripts['test:gateway:cpu-scenarios'] && scripts['test:extensions:memory'] && scripts.openclaw && fs.existsSync('scripts/bench-cli-startup.ts') && fs.existsSync('scripts/profile-extension-memory.mjs') ? 0 : 1)"; then
|
||||
cat > "$SOURCE_PERF_DIR/index.md" <<EOF
|
||||
# OpenClaw Source Performance
|
||||
|
||||
@@ -371,7 +435,7 @@ jobs:
|
||||
|
||||
- Tested ref: ${TESTED_REF}
|
||||
- Tested SHA: ${TESTED_SHA}
|
||||
- Required scripts: test:gateway:cpu-scenarios, openclaw, scripts/bench-cli-startup.ts
|
||||
- Required scripts: test:gateway:cpu-scenarios, test:extensions:memory, openclaw, scripts/bench-cli-startup.ts, scripts/profile-extension-memory.mjs
|
||||
EOF
|
||||
cat "$SOURCE_PERF_DIR/index.md" >> "$GITHUB_STEP_SUMMARY"
|
||||
exit 0
|
||||
@@ -391,6 +455,9 @@ jobs:
|
||||
--startup-case fiftyPlugins \
|
||||
--startup-case fiftyStartupLazyPlugins
|
||||
|
||||
pnpm test:extensions:memory \
|
||||
-- --json "$SOURCE_PERF_DIR/extension-memory.json"
|
||||
|
||||
for run_index in $(seq 1 "$source_runs"); do
|
||||
run_dir="$SOURCE_PERF_DIR/mock-hello/run-$(printf '%03d' "$run_index")"
|
||||
pnpm openclaw qa suite \
|
||||
@@ -460,9 +527,13 @@ jobs:
|
||||
cleanup_gateway
|
||||
trap - EXIT
|
||||
|
||||
node "$PERFORMANCE_HELPER_DIR/scripts/openclaw-performance-source-summary.mjs" \
|
||||
summary_args=(node "$PERFORMANCE_HELPER_DIR/scripts/openclaw-performance-source-summary.mjs" \
|
||||
--source-dir "$SOURCE_PERF_DIR" \
|
||||
--output "$SOURCE_PERF_DIR/index.md"
|
||||
--output "$SOURCE_PERF_DIR/index.md")
|
||||
if [[ -n "${SOURCE_PERF_BASELINE_DIR:-}" && -d "$SOURCE_PERF_BASELINE_DIR" ]]; then
|
||||
summary_args+=(--baseline-source-dir "$SOURCE_PERF_BASELINE_DIR")
|
||||
fi
|
||||
"${summary_args[@]}"
|
||||
|
||||
cat "$SOURCE_PERF_DIR/index.md" >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
|
||||
2
.github/workflows/plugin-prerelease.yml
vendored
@@ -344,7 +344,7 @@ jobs:
|
||||
OPENCLAW_EXTENSION_BATCH_PARALLEL: 2
|
||||
OPENCLAW_VITEST_MAX_WORKERS: 1
|
||||
OPENCLAW_EXTENSION_BATCH: ${{ matrix.extensions_csv }}
|
||||
run: pnpm test:extensions:batch "$OPENCLAW_EXTENSION_BATCH"
|
||||
run: pnpm test:extensions:batch "$OPENCLAW_EXTENSION_BATCH" -- --exclude extensions/codex/src/app-server/run-attempt.test.ts
|
||||
|
||||
plugin-prerelease-inspector:
|
||||
permissions:
|
||||
|
||||
4
.github/workflows/sandbox-common-smoke.yml
vendored
@@ -42,7 +42,7 @@ jobs:
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
docker build -t openclaw-sandbox-smoke-base:bookworm-slim - <<'EOF'
|
||||
timeout --kill-after=30s 5m docker build -t openclaw-sandbox-smoke-base:bookworm-slim - <<'EOF'
|
||||
FROM debian:bookworm-slim
|
||||
RUN useradd --create-home --shell /bin/bash sandbox
|
||||
USER sandbox
|
||||
@@ -63,5 +63,5 @@ jobs:
|
||||
FINAL_USER=sandbox \
|
||||
scripts/sandbox-common-setup.sh
|
||||
|
||||
u="$(docker run --rm openclaw-sandbox-common-smoke:bookworm-slim sh -lc 'id -un')"
|
||||
u="$(timeout --kill-after=30s 2m docker run --rm openclaw-sandbox-common-smoke:bookworm-slim sh -lc 'id -un')"
|
||||
test "$u" = "sandbox"
|
||||
|
||||
2
.github/workflows/tui-pty.yml
vendored
@@ -38,4 +38,4 @@ jobs:
|
||||
install-bun: "false"
|
||||
|
||||
- name: Run TUI PTY tests
|
||||
run: timeout 120s node scripts/run-vitest.mjs run --config test/vitest/vitest.tui-pty.config.ts
|
||||
run: timeout --kill-after=30s 120s node scripts/run-vitest.mjs run --config test/vitest/vitest.tui-pty.config.ts
|
||||
|
||||
4
.github/workflows/website-installer-sync.yml
vendored
@@ -75,14 +75,14 @@ jobs:
|
||||
|
||||
- name: install.sh in Docker
|
||||
run: |
|
||||
docker run --rm \
|
||||
timeout --kill-after=30s 20m docker run --rm \
|
||||
-v "$PWD/scripts/install.sh:/tmp/install.sh:ro" \
|
||||
node:24-bookworm-slim \
|
||||
bash -lc 'bash /tmp/install.sh --version latest && openclaw --version'
|
||||
|
||||
- name: install-cli.sh in Docker
|
||||
run: |
|
||||
docker run --rm \
|
||||
timeout --kill-after=30s 20m docker run --rm \
|
||||
-e OPENCLAW_NO_ONBOARD=1 \
|
||||
-e OPENCLAW_NO_PROMPT=1 \
|
||||
-v "$PWD/scripts/install-cli.sh:/tmp/install-cli.sh:ro" \
|
||||
|
||||
33
.github/workflows/workflow-sanity.yml
vendored
@@ -26,7 +26,16 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
env:
|
||||
CHECKOUT_REPO: ${{ github.repository }}
|
||||
CHECKOUT_SHA: ${{ github.sha }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
git init "$GITHUB_WORKSPACE"
|
||||
git -C "$GITHUB_WORKSPACE" config gc.auto 0
|
||||
git -C "$GITHUB_WORKSPACE" remote add origin "https://github.com/${CHECKOUT_REPO}.git"
|
||||
git -C "$GITHUB_WORKSPACE" fetch --no-tags --depth=1 origin "+${CHECKOUT_SHA}:refs/remotes/origin/checkout"
|
||||
git -C "$GITHUB_WORKSPACE" checkout --detach refs/remotes/origin/checkout
|
||||
|
||||
- name: Fail on tabs in workflow files
|
||||
run: |
|
||||
@@ -58,7 +67,16 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
env:
|
||||
CHECKOUT_REPO: ${{ github.repository }}
|
||||
CHECKOUT_SHA: ${{ github.sha }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
git init "$GITHUB_WORKSPACE"
|
||||
git -C "$GITHUB_WORKSPACE" config gc.auto 0
|
||||
git -C "$GITHUB_WORKSPACE" remote add origin "https://github.com/${CHECKOUT_REPO}.git"
|
||||
git -C "$GITHUB_WORKSPACE" fetch --no-tags --depth=1 origin "+${CHECKOUT_SHA}:refs/remotes/origin/checkout"
|
||||
git -C "$GITHUB_WORKSPACE" checkout --detach refs/remotes/origin/checkout
|
||||
|
||||
- name: Install actionlint
|
||||
shell: bash
|
||||
@@ -90,7 +108,16 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
env:
|
||||
CHECKOUT_REPO: ${{ github.repository }}
|
||||
CHECKOUT_SHA: ${{ github.sha }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
git init "$GITHUB_WORKSPACE"
|
||||
git -C "$GITHUB_WORKSPACE" config gc.auto 0
|
||||
git -C "$GITHUB_WORKSPACE" remote add origin "https://github.com/${CHECKOUT_REPO}.git"
|
||||
git -C "$GITHUB_WORKSPACE" fetch --no-tags --depth=1 origin "+${CHECKOUT_SHA}:refs/remotes/origin/checkout"
|
||||
git -C "$GITHUB_WORKSPACE" checkout --detach refs/remotes/origin/checkout
|
||||
|
||||
- name: Setup Node environment
|
||||
uses: ./.github/actions/setup-node-env
|
||||
|
||||
35
AGENTS.md
@@ -27,7 +27,7 @@ Skills own workflows; root owns hard policy and routing.
|
||||
- For PRs that add, remove, or change config/default surfaces with possible compatibility, upgrade, provider/plugin, operator, setup, startup, or fallback impact, ClawSweeper review should emit a `reviewMetrics` entry when practical. The metric should name the count and direction of the changes, such as added, changed, or removed config/default surfaces, and explain why the metric matters before merge. When the metric indicates concrete merge risk, also surface the concern in `risks`, use `mergeRiskLabels` when the risk matches the label rubric, make `bestSolution` name the desired pre-merge state, and ensure `labelJustifications` explain the specific reason rather than restating the label.
|
||||
- Review whole decision surfaces, not only the touched runtime, provider, channel, harness, plugin seam, or context path. Check sibling Codex/Pi-style runtimes, provider/model routing, channel delivery, gateway/protocol, plugin SDK, and context-management paths when relevant.
|
||||
- One-sided fixes need sibling-surface proof, an explanation for why siblings are unaffected, or explicit follow-up work.
|
||||
- User-facing `fix`, `feat`, and `perf` changes need `CHANGELOG.md` before landing; contributor PR authors are not blocked solely on maintainer-owned changelog work. Never request thanks for bot/forbidden handles: `@openclaw`, `@clawsweeper`, `@codex`, `@steipete`.
|
||||
- Changelog findings: see Docs / Changelog.
|
||||
- Public ClawSweeper comments prefer `https://docs.openclaw.ai/...` when a public docs page exists; structured evidence still cites repo files, lines, SHAs.
|
||||
- Findings need current source, shipped/current behavior, tests/CI evidence, and dependency contract proof when dependency-backed behavior is involved. Validation is judged against touched and sibling surfaces plus this file's commands; real behavior proof matters for user-visible changes, with Telegram/Desktop proof for Telegram-visible behavior when feasible.
|
||||
- Prefer findings for concrete behavior regressions, missing changed-surface proof, owner-boundary violations, security/API contract issues, or docs/config mismatches.
|
||||
@@ -58,9 +58,17 @@ Skills own workflows; root owns hard policy and routing.
|
||||
- Externalizing a bundled plugin: update package excludes, official catalogs, docs, tests, and prove core runtime paths resolve installed plugin roots before root-dep removal.
|
||||
- Runtime reads canonical config only. No silent compat for old/malformed config keys. If a config change invalidates existing files, add a matching `openclaw doctor --fix` migration. Core/auth config repairs live in core doctor; plugin-owned config repairs live in that plugin's doctor contract (`legacyConfigRules` / `normalizeCompatibilityConfig`).
|
||||
- Fix shape: default to clean bounded refactor, not smallest patch. Move ownership to right boundary; delete stale abstractions, duplicate policy, dead branches, wrappers, fallback stacks.
|
||||
- Fix observed local failures with generic product rules; do not hardcode names, ids, log phrases, or user examples in prod code unless they are an explicit contract.
|
||||
- Tests may use observed examples, but prod literals need a short contract reason.
|
||||
- Compatibility is opt-in. "Shipped" means reachable from a release Git tag; main/GitHub/PR/unreleased code is not shipped.
|
||||
- Refactor default: one canonical path. Delete the old path unless user explicitly wants compat or the shipped public contract is obvious and cited.
|
||||
- Keep old behavior only for an explicit public API/config/plugin SDK/data contract, tagged upgrade path, security/migration boundary, dependency contract, or observed prod state.
|
||||
- If unsure, ask before preserving compat. Do not keep aliases, shims, fallback stacks, stale names, or obsolete tests just in case.
|
||||
- Tests alone do not make internals contracts. If compat stays, name the contract and migration/removal plan in code, test, or PR.
|
||||
- Lean code is a goal. No internal shims, aliases, legacy names, broad fallbacks, or defensive branches just to reduce diff or handle unrealistic edge cases.
|
||||
- Handle real production states, shipped upgrade paths, security boundaries, and dependency contracts. Public/hostile/observed malformed input gets care; hypothetical malformed input does not.
|
||||
- Public plugin SDK/API is the compat exception. New API first, old path only via named compat/deprecation metadata, docs, warnings when useful, tests for old+new, planned removal.
|
||||
- Handle real production states, tagged upgrade paths, security boundaries, and dependency contracts. Public/hostile/observed malformed input gets care; hypothetical malformed input does not.
|
||||
- Deprecate shipped public contracts only.
|
||||
- Plugin SDK exception: shipped external API gets new API first plus named compat/deprecation, small tests/docs if useful, removal plan.
|
||||
- Migrate internal/bundled callers to modern API in the same change. Do not let internal compat become permanent architecture.
|
||||
- Channels are implementation under `src/channels/**`; plugin authors get SDK seams. Providers own auth/catalog/runtime hooks; core owns generic loop.
|
||||
- Hot paths should carry prepared facts forward: provider id, model ref, channel id, target, capability family, attachment class. Do not rediscover with broad plugin/provider/channel/capability loaders.
|
||||
@@ -68,7 +76,8 @@ Skills own workflows; root owns hard policy and routing.
|
||||
- Gateway/plugin metadata is process-stable: installs, manifests, catalogs, generated paths, bundled metadata. Changes require restart or explicit owner reload/install/doctor flow.
|
||||
- Runtime hot paths: no freshness polling (`stat`/`realpath`/JSON reread/hash). Reuse current snapshots, install records, discovery, lookup tables, root scopes, resolved paths.
|
||||
- Process-local metadata caches ok when lifecycle-owned and bounded/single-slot. Freshness exceptions need named owner + tests.
|
||||
- Inline code comments: brief notes for tricky, bug-prone, or previously buggy logic.
|
||||
- Inline comments: preserve reviewer context at the code site. Use for cross-path/state invariants, platform/dependency caps, deterministic ordering, compact encoded state, lifecycle ordering, ownership boundaries, session/id adoption, queue-depth symmetry, fallbacks, or intentional caller differences.
|
||||
- Comment shape: 1-3 short lines; state why the branch/helper exists, what contract it protects, and the bad outcome if removed. Cite nearby constants/helpers when useful. No syntax narration, PR/user-specific lore, or obvious mechanics.
|
||||
- Gateway protocol changes: additive first; incompatible needs versioning/docs/client follow-through.
|
||||
- Protocol version bumps: explicit owner confirmation only; never automatic/generated.
|
||||
- Config contract: exported types, schema/help, metadata, baselines, docs aligned. Retired public keys stay retired; compat in raw migration/doctor only.
|
||||
@@ -116,7 +125,6 @@ Skills own workflows; root owns hard policy and routing.
|
||||
- Do not leave associated issues open for hypothetical future repros. Close with rationale; ask for a new issue or reopen only if concrete new evidence appears. Close comment states: decision, why, supported alternative, and what evidence would change the decision.
|
||||
- PR review answer: bug/behavior, URL(s), affected surface, provenance for regressions when traceable, best-fix judgment, evidence from code/tests/CI/current or shipped behavior.
|
||||
- Issue/PR final answer: last line is the full GitHub URL.
|
||||
- Changelog: PR landings/fixes need one unless pure test/internal. Do not mention missing changelog as a review finding; Codex handles it during fix/landing.
|
||||
- PR verification: before merge, post exact local commands, CI/Testbox run IDs, before/after proof when used, and known proof gaps.
|
||||
- Issue fixed on `main` with proof: comment proof + commit/PR, then close.
|
||||
- After landing or requested close/sweep: search duplicates; comment proof + canonical commit/PR/release before closing.
|
||||
@@ -124,8 +132,10 @@ Skills own workflows; root owns hard policy and routing.
|
||||
- `ship` that fixes an issue: after push, comment proof + commit link, then close the issue.
|
||||
- GH comments with backticks, `$`, or shell snippets: use heredoc/body file, not inline double-quoted `--body`.
|
||||
- PR create: real body required. Include Summary + Verification; mention refs, behavior, and proof.
|
||||
- PR create/refresh: keep PR branches takeover-ready. Use a branch maintainers can push to, or for fork PRs ensure `maintainer_can_modify` / GitHub's `Allow edits by maintainers` is enabled unless explicitly told otherwise or GitHub's Actions/secrets warning makes that unsafe.
|
||||
- GitHub issue/PR create: read `$agent-transcript`; ask about sanitized transcript logs when available.
|
||||
- Real behavior proof section is parsed. Use exact `field: value` labels: `Behavior addressed`, `Real environment tested`, `Exact steps or command run after this patch`, `Evidence after fix`, `Observed result after fix`, `What was not tested`.
|
||||
- PR artifacts/screenshots: attach to PR/comment/external artifact store. Do not commit `.github/pr-assets`.
|
||||
- PR artifacts/screenshots: attach to PR/comment/external artifact store. Never push screenshots, videos, proof images, or proof assets to OpenClaw or any product repo branch, including temp artifact branches. Use Crabbox artifact publishing plus the manifest URL. Do not commit `.github/pr-assets`.
|
||||
- CI polling: exact SHA, relevant checks only, minimal fields. Skip routine noise (`Auto response`, `Labeler`, docs agents, performance/stale). Logs only after failure/completion or concrete need.
|
||||
- Maintainers: may skip/ignore `Real behavior proof` when local tests or Crabbox verified behavior; record proof in PR verification.
|
||||
- `/landpr`: use `~/.codex/prompts/landpr.md`; do not idle on `auto-response` or `check-docs`.
|
||||
@@ -149,10 +159,13 @@ Skills own workflows; root owns hard policy and routing.
|
||||
- Inline simple one-use objects/spreads when clearer. Extract only when it removes duplication or hard logic.
|
||||
- Tests prove behavior/regressions, not every internal branch.
|
||||
- For non-trivial refactors, check `git diff --numstat` before closeout. If LOC grew, trim or explain why.
|
||||
- Prefer existing narrow helpers over repeated casts/guards. Add local helpers when 2+ nearby call sites share real boundary logic.
|
||||
- Prefer ctor parameter properties for injected deps/config. Do not ban them for erasable-syntax purity.
|
||||
- Prefer `satisfies` for registries/config maps; derive types from schemas when a runtime schema already exists.
|
||||
- Table-drive repetitive tests when it reduces code and keeps failure names clear.
|
||||
- Dynamic import: no static+dynamic import for same prod module. Use `*.runtime.ts` lazy boundary. After edits: `pnpm build`; check `[INEFFECTIVE_DYNAMIC_IMPORT]`.
|
||||
- Cycles: keep `pnpm check:import-cycles` + architecture/madge green.
|
||||
- Classes: no prototype mixins/mutations. Prefer inheritance/composition. Tests prefer per-instance stubs.
|
||||
- Comments: brief, only non-obvious logic.
|
||||
- Split files around ~700 LOC when clarity/testability improves.
|
||||
- Naming: **OpenClaw** product/docs; `openclaw` CLI/package/path/config.
|
||||
- English: American spelling.
|
||||
@@ -174,9 +187,9 @@ Skills own workflows; root owns hard policy and routing.
|
||||
- Use `$technical-documentation` for docs writing/review. Docs change with behavior/API.
|
||||
- Codex harness upgrade (`extensions/codex/package.json` `@openai/codex`): refresh `docs/plugins/codex-harness.md` model snapshot from the new harness `model/list`.
|
||||
- Docs final answers: include relevant full `https://docs.openclaw.ai/...` URL(s). If issue/PR work too, GitHub URL last.
|
||||
- Changelog entries: active version `### Changes`/`### Fixes`; single-line bullets only.
|
||||
- Contributor PR authors should not edit `CHANGELOG.md`; maintainer/AI adds entries during landing/merge.
|
||||
- Contributor-facing changelog entries thank credited human `@author`. Never thank bots, `@openclaw`, `@clawsweeper`, or `@steipete`; if unknown, omit thanks.
|
||||
- `CHANGELOG.md`: release-owned. Do not edit for normal PRs, direct `main` fixes, or `ship it`; only explicit release/changelog generation may rewrite it. Do not ask contributors/agents for changelog edits.
|
||||
- User-facing `fix`/`feat`/`perf`: put release-note context in PR body, squash message, or direct commit: behavior, surface, issue/PR refs, credited human author/reporter.
|
||||
- Release generation: derive `CHANGELOG.md` from merged PRs + all direct `main` commits. Entries: active `### Changes`/`### Fixes`, single-line, thank credited humans; never thank bots/forbidden handles: `@openclaw`, `@clawsweeper`, `@codex`, `@steipete`.
|
||||
|
||||
## Git
|
||||
|
||||
@@ -185,7 +198,7 @@ Skills own workflows; root owns hard policy and routing.
|
||||
- No manual stash/autostash unless explicit. No branch/worktree changes unless requested.
|
||||
- `main`: no merge commits; rebase on latest `origin/main` before push. After one green run plus clean rebase sanity, do not chase moving `main` with repeated full gates.
|
||||
- User says `commit`: your changes only. `commit all`: all changes in grouped chunks. `push`: may `git pull --rebase` first.
|
||||
- User says `ship it`: changelog if needed, commit intended changes, pull --rebase, push.
|
||||
- User says `ship it`: commit intended changes, pull --rebase, push.
|
||||
- Do not delete/rename unexpected files; ask if blocking, else ignore.
|
||||
- Bulk PR close/reopen >5: ask with count/scope.
|
||||
|
||||
|
||||
78
CHANGELOG.md
@@ -6,6 +6,12 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
### Changes
|
||||
|
||||
- Voice: expose shared realtime turn-context tracking through the realtime voice SDK and reuse it for Discord speaker attribution and wake-name context recovery.
|
||||
- Voice: reuse shared realtime output activity tracking in Google Meet command and node audio bridges, including recent-output checks for local barge-in detection.
|
||||
- Voice: expose shared realtime output activity tracking through the realtime voice SDK and reuse it for Discord playback activity and barge-in decisions.
|
||||
- Voice: expose shared realtime consult question matching, speakable-result extraction, and alias-aware forced-consult coordination through the realtime voice SDK, then reuse it in Gateway Talk, Voice Call, and Discord voice paths.
|
||||
- Voice: share activation-name matching and consult-transcript screening through the realtime voice SDK so Discord, browser voice, and meeting surfaces can reuse one implementation.
|
||||
- Cron: default `cron.maxConcurrentRuns` to 8 so scheduled automations and their isolated agent turns can make progress in parallel without explicit configuration.
|
||||
- QA-Lab: add `qa coverage --match <query>` so focused proof selection can discover matching scenarios from existing metadata before running live or remote lanes.
|
||||
- Control UI: add an ephemeral Activity tab for sanitized live tool activity summaries without persisting raw telemetry. Fixes #12831. Thanks @BunsDev.
|
||||
- Build: include `ui:build` in the `full` and `ciArtifacts` profiles of `scripts/build-all.mjs` so `pnpm build` always rebuilds `dist/control-ui` after `tsdown` cleans `dist`, removing the second-command requirement and the missing-asset failure mode for source/runtime installs and CI artifact uploads. (#85206)
|
||||
@@ -15,33 +21,96 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
### Fixes
|
||||
|
||||
- Telegram/network: treat `ENETDOWN` as a transient pre-connect network failure so Telegram sends, gateway unhandled-rejection handling, and cron network retries follow the same recovery path as sibling network outages. (#86762) Thanks @TurboTheTurtle.
|
||||
- Agents/sessions: include visibility metadata on restricted `sessions_list` results so scoped counts are clearly reported without widening access or exposing hidden-session counts. (#86944) Thanks @ferminquant.
|
||||
- Gateway/DNS: validate wide-area discovery domains before deriving zone paths or writing zone files, so invalid `discovery.wideArea.domain` and `dns setup --domain` values fail with a DNS-name diagnostic instead of falling through to unrelated configuration errors. Thanks @mmaps.
|
||||
- Agents/BTW: route fallback side-question streams through the embedded stream resolver so Anthropic-compatible MiniMax requests use the same capped transport as normal chat. (#86312) Thanks @neeravmakwana.
|
||||
- Telegram: treat `/command@TargetBot` bot-command entities as explicit mentions for the addressed bot so `requireMention` groups no longer drop targeted commands or captions. Fixes #84462. (#86553) Thanks @luoyanglang.
|
||||
- CI: bound Docker/Bash E2E tarball npm installs with `OPENCLAW_E2E_NPM_INSTALL_TIMEOUT` so package, onboarding, plugin, and upgrade lanes fail instead of hanging on a stuck npm install.
|
||||
- CI: keep `OPENCLAW_TESTBOX=1 pnpm check:changed` delegating to Blacksmith Testbox through Crabbox without forwarding local Testbox or worker env into the remote command.
|
||||
- CI: send KILL after the TERM grace period for manual checkout fetch timeouts so stuck Testbox and workflow checkout retries cannot hang behind a wedged `git fetch`.
|
||||
- iMessage: thread current channel/account inbound attachment roots into the image tool so iMessage-saved attachments under `~/Library/Messages/Attachments` (including the wildcard `/Users/*/Library/Messages/Attachments` root) are read through the existing inbound path policy instead of being rejected as `path-not-allowed`. Literal `localRoots` stays workspace-scoped. Fixes #30170. (#86569)
|
||||
- QQ Bot: respect `OPENCLAW_HOME` for outbound media path resolution so `<qqmedia>` sends no longer silently fail when `HOME` and `OPENCLAW_HOME` differ (Docker / multi-user hosts). Persisted QQ Bot data (sessions, known users, refs) stays anchored on the OS home for upgrade compatibility. Fixes #83562. Thanks @sliverp.
|
||||
- Update: report the primary malformed `openclaw.extensions` payload error without adding a duplicate missing-main diagnostic. (#86596) Thanks @ferminquant.
|
||||
- Control UI: keep host-local Markdown file paths inert while preserving app-relative links. (#86620) Thanks @BryanTegomoh.
|
||||
- Gateway: dampen repeated unauthenticated device-required probes per URL while preserving explicit-auth and paired recovery paths. (#86575) Thanks @ferminquant.
|
||||
- IRC: store inbound channel routes with the canonical `channel:#name` target and join transient channel sends before writing. (#85906) Thanks @Kailigithub.
|
||||
- Usage: surface unknown all-zero model pricing as missing cost entries instead of a confident `$0` total. (#85882) Thanks @MichaelZelbel.
|
||||
- Agents/Codex: honor yolo app-server approval policy only for the full `never` plus `danger-full-access` case. (#85909) Thanks @earlvanze.
|
||||
- Gateway/Gmail: clear Gmail watcher renewal intervals on re-entry so hot reloads do not leak lifecycle timers. (#82947) Thanks @SebTardif.
|
||||
- Logging: exit cleanly on broken stdout/stderr pipes without masking existing failure exit codes. (#80059) Thanks @pavelzak.
|
||||
- Gateway/security: escape transcript metadata field names while extracting oversized session line prefixes. (#85934) Thanks @SebTardif.
|
||||
- Plugins/security: validate manifest model pattern regexes with the safe-regex compiler so unsafe patterns are ignored before matching. (#86046) Thanks @SebTardif.
|
||||
- Discord: route gateway metadata REST lookups through the configured Discord proxy so proxied accounts do not fall back to direct `discord.com` connections before opening the WebSocket. Fixes #80227. Thanks @Clivilwalker.
|
||||
- Agents/media: hydrate current-turn image attachments from filename-derived MIME types so active vision can see generated or forwarded images whose source omitted an image content type. (#84812) Thanks @marchpure.
|
||||
- Agents/fs: point workspace-only scratch-path guidance at in-workspace temp directories while keeping host-root writes rejected by the tool guard. (#86501) Thanks @tianxiaochannel-oss88.
|
||||
- Agents/media: keep async cron media completions scoped to their run session while preserving direct delivery for stale generated-media success and failure notifications. (#86529) Thanks @ai-hpc.
|
||||
- Gateway: emit plugin `session_end`/`session_start` hooks when `agent.send` rotates or replaces a session id, keeping hook lifecycle state aligned with `sessions.changed` notifications. Fixes #83507. (#85875) Thanks @brokemac79.
|
||||
- OpenShell/SSH: reject malformed generated exec commands before sandbox/session setup so unresolved workflow placeholders fail fast instead of reaching the remote shell. Fixes #72373. Thanks @brokemac79.
|
||||
- Google: stop normalizing `gemini-3.1-flash-lite` to the retired preview endpoint and update Flash Lite alias guidance to the GA model id. Fixes #86151. (#86240) Thanks @SebTardif.
|
||||
- Installer: make Alpine apk installs cover Git, verify the Node runtime floor, try `nodejs-current`, and report Alpine version guidance when repositories only provide older Node packages.
|
||||
- Agents/status: prefer the active Claude CLI OAuth auth label over an unused Anthropic env API-key label for equivalent runtime aliases. Fixes #80184. (#86570) Thanks @brokemac79.
|
||||
- Agents/media: send direct fallback for generated media still missing after an active requester wake fails. (#85489) Thanks @fuller-stack-dev.
|
||||
- Agents: derive overflow compaction budgets from provider-reported and synthetic over-budget token counts so confirmed context overflows compact before retrying. (#70473) Thanks @fuller-stack-dev.
|
||||
- Agents/Codex: recover Codex context-window prompt errors through overflow compaction and surface reset guidance when recovery is exhausted. (#85542) Thanks @fuller-stack-dev.
|
||||
- Agents/Codex: allow Codex app-server runs to bootstrap from `CODEX_API_KEY` or `OPENAI_API_KEY` when no Codex auth profile is configured.
|
||||
- Agents/Codex: keep selected Codex runtime routing on OpenAI-Codex while preserving direct OpenAI API-key compaction fallback. (#86408) Thanks @funmerlin and @VACInc.
|
||||
- Agent transcript: include OpenClaw agent session logs when finding local transcript candidates.
|
||||
- Crabbox: bootstrap raw AWS macOS shell commands wrapped in absolute `time` paths so RSS probes can run Node and pnpm on fresh macOS runners.
|
||||
- Crabbox: bootstrap raw AWS macOS shell commands even when setup statements precede Node or pnpm usage.
|
||||
- TUI/local: skip unnecessary secret resolution, gateway model catalog loading, bootstrap, and skill scans in explicit local-model runs so startup reaches the model request faster.
|
||||
- Sessions/doctor: load large session stores without clone amplification during read-only doctor checks and reclaim stale `sessions.json.*.tmp` sidecars. Fixes #56827. Thanks @openperf.
|
||||
- Tests: clean successful plugin gateway gauntlet isolated temp roots while keeping an explicit preservation switch for failed/debug runs.
|
||||
- Plugins/perf: reuse derived plugin metadata snapshots for the lifetime of the process so reply-time skill setup no longer rescans plugin metadata on every turn.
|
||||
- Discord/OpenAI voice: keep wake-name master consults using the current speaker context after ignored ambient transcripts and shorten the default capture silence grace.
|
||||
- Doctor: skip redundant Gateway restart prompts when a recent supervisor restart leaves the Gateway healthy. Fixes #86518. (#86533) Thanks @liaoyl830.
|
||||
- Cron: restore suspended cron lanes to the configured/default concurrency instead of falling back to one after quota or circuit-breaker auto-resume.
|
||||
- Gateway: keep session-only Control UI tool-start mirrors flowing during diagnostic queue pressure instead of silently dropping non-terminal tool updates.
|
||||
- Agents/memory: return optional not-found context for missing date-only daily memory reads instead of logging benign first-run `ENOENT` failures. Fixes #82928. Thanks @galiniliev.
|
||||
- Discord: merge streamed text captions into following media block replies so captions and attachments send as one message. (#86487) Thanks @neeravmakwana.
|
||||
- Gateway: avoid sending duplicate tool-event frames to Control UI connections that are subscribed by both run and session.
|
||||
- Discord/OpenAI voice: accept broader edge-position fuzzy wake-name transcripts while keeping ambient speech gated.
|
||||
- Discord/OpenAI voice: accept longer leading wake-name mistranscripts such as "Open Club" for OpenClaw.
|
||||
- Agents/OpenAI-compatible: stop ModelStudio-compatible chat requests before sending system/tool-only payloads that have no usable user or assistant turn. (#86177) Thanks @TurboTheTurtle.
|
||||
- Gateway/plugins: reuse plugin package realpath checks while building installed plugin indexes so startup avoids repeated filesystem resolution work.
|
||||
- Kilo Gateway: send string `stop` sequences as arrays so Kilo accepts OpenAI-compatible chat completions. (#86461) Thanks @SebTardif.
|
||||
- Discord/OpenAI voice: accept leading fuzzy wake-name transcripts such as "Monty" or "Moti" for a Molty agent while keeping ambient speech gated.
|
||||
- Media understanding: convert HEIC and HEIF images to JPEG before image description providers run so iPhone photos work in direct and configured image-description flows. (#86037)
|
||||
- Agents: release embedded-attempt session locks from outer teardown so post-prompt exceptions cannot wedge later requests behind `SessionWriteLockTimeoutError`. Fixes #86014. Thanks @openperf.
|
||||
- Discord/OpenAI voice: rotate Realtime sessions at provider max duration without logging the expected session-expiry event as an error.
|
||||
- Sessions: skip metadata-only entries during QMD-slugified session lookup so one incomplete row does not block transcript hit resolution. (#86327) Thanks @abnershang.
|
||||
- Agents/media: derive bundled plugin local-media trust from plugin tool metadata instead of importing the full plugin registry on subscription paths. (#84409) Thanks @samzong.
|
||||
- Image tool: keep config-backed custom-provider API keys usable for auto-discovered vision models, including deferred image-tool execution without env keys or auth profiles. (#85733)
|
||||
- Memory/local embeddings: run local GGUF embeddings in an isolated worker sidecar and degrade to configured fallback or keyword search on worker failure so native embedding crashes do not take down the Gateway. (#85348) Thanks @osolmaz.
|
||||
- Gateway: clear the runtime config snapshot before `SIGUSR1` in-process restarts so config changes survive the next gateway loop. (#86388) Thanks @XuZehan-iCenter.
|
||||
- Models: show OAuth delegation markers as configured `models.json` auth while keeping runtime route usability checks strict. (#86378) Thanks @rohitjavvadi.
|
||||
- Cron: seed active scheduled and manual cron task rows with a progress summary so status surfaces do not look blank while jobs run. (#86313) Thanks @ferminquant.
|
||||
- Cron: preserve unsupported persisted cron payload rows during routine store writes while keeping those rows non-runnable. Fixes #84922. (#86415) Thanks @IWhatsskill.
|
||||
- Updater: exclude prerelease git tags from stable channel resolution so source updates do not check out newer alpha/rc/preview/canary tags. (#86260) Thanks @stevenepalmer.
|
||||
- Security/Audit: flag webhook `hooks.token` reuse of active Gateway password auth in `openclaw security audit` while keeping password-mode startup compatibility. (#84338) Thanks @coygeek.
|
||||
- QQBot: derive the outbound reply watchdog from configured agent and provider timeouts so slow local model replies are not cut off at five minutes. Fixes #85267. (#85271) Thanks @SymbolStar.
|
||||
- Agents/heartbeat: stop heartbeat turns after the first valid `heartbeat_respond` so repeated response loops do not burn tokens. (#86357) Thanks @udaymanish6.
|
||||
- Tasks: keep retained lost tasks out of default status health counts, explain their cleanup window during maintenance, and prune lost task records after 24 hours instead of the general 7-day terminal retention.
|
||||
- Memory-core: keep REM dreaming focused on live light-staged memories and mark staged entries as considered so old recall history no longer dominates fresh candidates. (#86302) Thanks @SebTardif.
|
||||
- Memory: abort sync instead of downgrading an existing semantic vector index to FTS-only when the configured embedding provider is temporarily unavailable. (#85704) Thanks @yaaboo-gif.
|
||||
- Telegram: propagate forum topic names through the account-scoped topic cache for native command context and topic create/edit actions. (#86299) Thanks @SebTardif.
|
||||
- Slack: keep downloaded read-only files out of reply media so Slack file reads do not echo files back to the conversation. (#86318) Thanks @neeravmakwana.
|
||||
- Cron: accept leading-plus relative durations such as `+5m` for one-shot `--at` schedules. (#86341) Thanks @mushuiyu886.
|
||||
- Agents/media: preserve async-started media tool metadata so background generation starts no longer surface generic incomplete-turn warnings while replay stays unsafe. (#85933) Thanks @fuller-stack-dev.
|
||||
- Docker E2E: dedupe scheduler lane resources so npm/service package lanes are not over-counted and serialized unnecessarily.
|
||||
- QA/diagnostics: add a collector-backed OpenTelemetry smoke lane, make the OTLP payload leak check scenario-aware, and keep source QA builds from failing on optional dependency imports resolved through pnpm's temp module path.
|
||||
- Crabbox: bootstrap Git metadata for sparse remote changed gates so raw synced workspaces can run `pnpm check:changed` from the intended diff.
|
||||
- xAI/LM Studio: avoid buffering ordinary bracketed or `final` prose until stream completion while watching for plain-text tool-call fallbacks.
|
||||
- Doctor: warn and continue when the cron job store exists but cannot be read so later health checks still run. Fixes #86102. (#86384) Thanks @1052326311.
|
||||
- Discord: suppress a bot's previous reply body and referenced media from prompt context when a user replies to that bot message, while keeping reply metadata for routing. (#86238) Thanks @fuller-stack-dev.
|
||||
- Discord: restore bare numeric channel IDs for outbound message-tool sends while keeping explicit DM targets unambiguous. (#86571) Thanks @joshavant.
|
||||
- Docker E2E: avoid rebuilding the Control UI twice while preparing the shared OpenClaw package tarball for package-backed scenario runs.
|
||||
- Tests: avoid rebuilding the Control UI twice during the installer Docker smoke now that `pnpm build` includes `ui:build`.
|
||||
- Tests: give QA config mutation RPCs enough native Windows budget to finish gateway config writes and restart settle after hot scenario runs.
|
||||
- Tests: keep the gateway restart-inflight QA scenario focused on restart recovery on native Windows by allowing expected embedded prompt handoff errors and using the Windows-safe timeout budget.
|
||||
- QA-Lab: make the synthetic OpenAI provider honor generic `reply exactly:` directives after required kickoff reads so restart-recovery scenarios do not fall through to generic repo-summary prose.
|
||||
- Gateway: abort active `agent` RPC runs during forced restart shutdown so stale in-process turns cannot keep writing a session after the Gateway lifecycle restarts.
|
||||
- Crabbox: sync clean sparse worktrees through a temporary full checkout even when reusing an existing lease so tracked build-time files are not omitted.
|
||||
- Build: route `scripts/ui.js` through the shared pnpm runner and keep Control UI chunking helpers in sparse-included source so native Windows Corepack builds can produce `dist/control-ui`.
|
||||
- Tests: give the memory fallback QA scenario enough turn budget to exercise native Windows gateway runs instead of failing on the client timeout while the mock agent is still dispatching.
|
||||
- Tests: collect QA gateway CPU/RSS metrics on native Windows and give the channel baseline enough turn budget to report slow gateway runs instead of timing out before proof.
|
||||
@@ -58,18 +127,23 @@ Docs: https://docs.openclaw.ai
|
||||
- Checks: keep intentional Knip unused-file findings optional so full CI and sparse proof workspaces stay aligned.
|
||||
- Docker: restore writable `~/.config` in runtime images. Fixes #85968. Thanks @hkoessler and @Bartok9.
|
||||
- Plugin SDK: keep legacy root diagnostic subscriptions connected when built plugin SDK aliases resolve diagnostic helpers through a separate module graph.
|
||||
- Diagnostics: export alertable OTel and Prometheus signals for blocked tools, model failover, stale sessions, liveness warnings, oversized payloads, and webhook ingress while fixing shared OTLP endpoints with query strings.
|
||||
- Tests: normalize macOS canonical temp paths in exec allowlists, fs-safe trash assertions, installed plugin matching, Telegram topic-name stores, and built ACPX MCP server expectations so native macOS proof runners cover the intended behavior.
|
||||
- Codex/app-server: preserve message-tool-only source reply delivery mode on active runs so sub-agent completion wakeups can steer the active Codex turn instead of being rejected. (#86287) Thanks @ferminquant.
|
||||
- Tests: sample the Windows kitchen-sink RPC gateway directly and serialize RSS probes so native runs keep the memory guard active.
|
||||
- Tests: normalize bundled plugin lifecycle probe paths and state-root lookup so native Windows release sweeps accept valid packaged plugin installs.
|
||||
- Agents/Claude CLI: route live native Bash permission requests through OpenClaw exec policy so Claude turns no longer stall on `control_request`, and document that OpenClaw exec policy is authoritative. Fixes #80819. (#86330, from #81971) Thanks @guthirry and @sallyom.
|
||||
- Security audit: warn when YOLO OpenClaw exec policy overrides a restrictive raw Claude `--permission-mode` for managed live sessions. (#86557) Thanks @sallyom.
|
||||
- Config: keep benign legacy metadata write anomalies out of default doctor and config command output while preserving explicit anomaly logging for diagnostics.
|
||||
- Codex: log when implicit app-server `never` approvals are promoted for OpenClaw tool policy, including whether the trigger was a `before_tool_call` hook or trusted tool policy.
|
||||
- Codex harness: make subscription usage-limit errors without reset times explain that OpenClaw cannot determine the reset and point users to wait until Codex is available, use another Codex account, or switch to another configured model/provider. Thanks @amknight.
|
||||
- Google Vertex: support production ADC modes such as Workload Identity Federation, service-account credentials, and metadata-server ADC for the native Vertex transport. (#83971) Thanks @damianFelixPago.
|
||||
- Telegram: route normal `[telegram][diag]` polling diagnostics through `runtime.log` while keeping non-diag warnings and persistence failures on `runtime.error`, so healthy polling startup no longer looks like an error. Fixes #82957. (#82958) Thanks @galiniliev.
|
||||
- Providers/Ollama: strip inline Kimi cloud reasoning prefixes from streamed and final visible replies while keeping ordinary Kimi answers append-only. (#86286) Thanks @jason-allen-oneal.
|
||||
|
||||
- Gateway: require Talk secret authority before setup-code handoff can include Talk secrets. (#85690) Thanks @ngutman.
|
||||
- Agents: keep fallback error reporting scoped to the active model candidate so stale prior-provider quota/auth text is not reported for later fallback attempts. (#86134) thanks @zhangguiping-xydt.
|
||||
- iMessage: dedupe watcher startup when `channels.imessage.accounts` lists both `default` and a named account that point at the same local Messages source, so the gateway no longer spawns two `imsg rpc` processes or doubles inbound replies; the dedupe is scoped to watcher startup, leaving duplicate accounts addressable for outbound sends, status, and capability listings, and `openclaw doctor` flags the redundant account with a rebinding hint. Fixes #65141. (#86705) Thanks @swang430.
|
||||
|
||||
## 2026.5.25
|
||||
|
||||
@@ -118,6 +192,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Crabbox: install Corepack shims into the writable hydration `PNPM_HOME` so local AWS runner hydration no longer tries to overwrite `/usr/local/bin/pnpm`.
|
||||
- Live tests: fail Gateway live model sweeps when selected coverage is lost to timeouts or stale high-signal filters instead of reporting false missing-profile coverage, and pin Docker OpenAI gateway coverage to the current `gpt-5.5` lane.
|
||||
- Tests: fail Docker resource-ceiling checks when stats samples or configured limits are invalid instead of silently reporting zero peaks.
|
||||
- Auth/Codex: emit a one-shot actionable `log.warn` from the embedded legacy Codex OAuth sidecar loader when the only available seed lives in the macOS Keychain, naming `openclaw doctor --fix` and macOS Keychain instead of letting the credential silently fall through to a downstream `No API key found for provider "openai-codex"`. Thanks @romneyda.
|
||||
- Agents: fail closed when provider-less session models match multiple provider-prefixed runtime policies so CLI runtime routing no longer depends on config order. (#85970) Thanks @potterdigital.
|
||||
- Control UI/agents: keep collapsed tool rows readable without early ellipses, preserve raw expanded tool details, and make post-compaction AGENTS.md reinjection opt-in to avoid duplicated project context. Fixes #45649 and #45488. Thanks @BunsDev.
|
||||
|
||||
@@ -166,6 +241,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Gateway/plugins: reuse a compatible Gateway startup plugin registry during dispatch so safe plugin dispatches avoid redundant registry loading. (#84324) Thanks @ai-hpc.
|
||||
- Plugins/SDK: add a general `embeddingProviders` capability contract and registration API so embeddings can become a reusable provider surface outside memory-specific adapters.
|
||||
- Dependencies: refresh provider, plugin, UI, and tooling packages, update `protobufjs` to 8.4.0 to clear the current npm advisory, and carry the Claude ACP completion patch forward to `@agentclientprotocol/claude-agent-acp` 0.36.1.
|
||||
- ACPX: bump the bundled ACP backend to `acpx` 0.10.0 for session export/import support.
|
||||
- Agents/tools: remove the old sender-owner tool gating path so configured tools stay visible for trusted sessions while command and channel-action auth still carry real sender identity.
|
||||
- QA-Lab: add curated mock JSONL replay fixtures and first-drift reporting for runtime-parity audits. (#80323, refs #80176) Thanks @100yenadmin.
|
||||
- QA-Lab: add a QA bus tool-trace visibility scenario for sanitized tool-call assertions.
|
||||
@@ -185,6 +261,7 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
### Fixes
|
||||
|
||||
- CLI/update: allow package-manager-managed hardlinked package roots during global update swaps while keeping generic plugin, hook, and dependency-free install moves fail-closed. (#85569) Thanks @ai-hpc.
|
||||
- Gateway/update: avoid fetching unrelated tags during dev-channel git updates so moved release tags do not block branch-based updates. (#84737) Thanks @rubencu.
|
||||
- CLI/update: suppress the expected future-config warning while an old update parent hands off to the freshly installed post-core process.
|
||||
- MiniMax: store OAuth token expiry as an absolute millisecond timestamp so OAuth profiles no longer appear expired on every request. (#83480) Thanks @NianJiuZst.
|
||||
@@ -2096,6 +2173,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Telegram/groups: include the recent local chat window and nearby reply-target window as generic inbound context so stale reply ancestry does not overshadow the live group conversation.
|
||||
- Plugins/Nix: allow externally configured plugin roots under `/nix/store` to load in `OPENCLAW_NIX_MODE=1` while keeping normal external plugin hardlink rejection unchanged. Thanks @joshp123.
|
||||
- Nextcloud Talk: include the required bot `response` feature in setup, explain missing `--feature response` on rejected sends, and surface missing response capability in doctor/status checks. Fixes #78935. (#79657) Thanks @joshavant.
|
||||
- Cron/diagnostics: emit the existing `message.queued`, `session.state` (processing/idle), and `message.processed` lifecycle events for isolated-cron agent turns in `runCronIsolatedAgentTurn`, matching the dispatch and embedded-runner paths so subscribers (diagnostics OTLP, OTel exporters, custom observability plugins) get per-run session attribution instead of bucketing isolated cron LLM calls under static fallback ids. Events are gated on `isDiagnosticsEnabled(cfg)` so the documented `diagnostics.enabled: false` master toggle continues to silence the recorder. (#79214) Thanks @arniesaha.
|
||||
- fix(discord): gate user allowlist name resolution [AI]. (#79002) Thanks @pgondhi987.
|
||||
- fix(msteams): gate startup user allowlist resolution [AI]. (#79003) Thanks @pgondhi987.
|
||||
- Infra/fetch-timeout: pass `operation` and `url` context to `buildTimeoutAbortSignal` from the music-generate reference fetch and the Matrix guarded redirect transport, so the `fetch timeout reached; aborting operation` warning carries actionable structured fields instead of a bare line. Fixes #79195. Thanks @pandadev66.
|
||||
|
||||
@@ -107,6 +107,7 @@ For coordinated change sets that genuinely need more than 20 PRs, join the **#cl
|
||||
|
||||
- Test locally with your OpenClaw instance
|
||||
- External PRs must include a filled **Real behavior proof** section in the PR body. Show the real setup you tested, the exact command or steps you ran after the patch, after-fix evidence, the observed result, and anything you did not test. Screenshots, recordings, terminal screenshots, console output, copied live output, linked artifacts, and redacted runtime logs all count. Unit tests, mocks, snapshots, lint, typechecks, and CI are useful but do not satisfy this requirement by themselves. Maintainers may apply `proof: override` only when the proof gate should not apply.
|
||||
- Keep PRs takeover-ready: open them from a branch maintainers can push to. For fork PRs, leave GitHub's **Allow edits by maintainers** option enabled so maintainers can finish urgent fixes, changelog entries, or merge prep when needed. If GitHub shows **Allow edits and access to secrets by maintainers**, enable it only when that workflow/secrets access is acceptable and say so in the PR.
|
||||
- Do not edit `CHANGELOG.md` in contributor PRs. Maintainers or ClawSweeper add the changelog entry when landing user-facing changes.
|
||||
- Run tests: `pnpm build && pnpm check && pnpm test`
|
||||
- For iterative local commits, `scripts/committer --fast "message" <files...>` passes `FAST_COMMIT=1` through to the pre-commit hook so it skips the repo-wide `pnpm check`. Only use it when you've already run equivalent targeted validation for the touched surface.
|
||||
|
||||
@@ -65,8 +65,8 @@ android {
|
||||
applicationId = "ai.openclaw.app"
|
||||
minSdk = 31
|
||||
targetSdk = 36
|
||||
versionCode = 2026052501
|
||||
versionName = "2026.5.25"
|
||||
versionCode = 2026052601
|
||||
versionName = "2026.5.26"
|
||||
ndk {
|
||||
// Support all major ABIs — native libs are tiny (~47 KB per ABI)
|
||||
abiFilters += listOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# OpenClaw iOS Changelog
|
||||
|
||||
## 2026.5.26 - 2026-05-26
|
||||
|
||||
Maintenance update for the current OpenClaw release.
|
||||
|
||||
## 2026.5.25 - 2026-05-25
|
||||
|
||||
Maintenance update for the current OpenClaw release.
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
// Source of truth: apps/ios/version.json
|
||||
// Generated by scripts/ios-sync-versioning.ts.
|
||||
|
||||
OPENCLAW_IOS_VERSION = 2026.5.25
|
||||
OPENCLAW_MARKETING_VERSION = 2026.5.25
|
||||
OPENCLAW_IOS_VERSION = 2026.5.26
|
||||
OPENCLAW_MARKETING_VERSION = 2026.5.26
|
||||
OPENCLAW_BUILD_VERSION = 1
|
||||
|
||||
#include? "../build/Version.xcconfig"
|
||||
|
||||
@@ -85,6 +85,8 @@ struct TalkToolbarTray: View {
|
||||
var isSpeaking: Bool
|
||||
var isUserSpeechDetected: Bool
|
||||
var permissionState: TalkGatewayPermissionState
|
||||
var voiceModeTitle: String
|
||||
var voiceModeSubtitle: String?
|
||||
var onEnableTalk: () -> Void
|
||||
var onStopTalk: () -> Void
|
||||
|
||||
@@ -135,6 +137,13 @@ struct TalkToolbarTray: View {
|
||||
.foregroundStyle(.secondary)
|
||||
.lineLimit(1)
|
||||
}
|
||||
|
||||
if let voiceModeText = self.voiceModeText {
|
||||
Text(voiceModeText)
|
||||
.font(.caption2.weight(.semibold))
|
||||
.foregroundStyle(.secondary)
|
||||
.lineLimit(1)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(minLength: 0)
|
||||
@@ -193,7 +202,22 @@ struct TalkToolbarTray: View {
|
||||
}
|
||||
.accessibilityElement(children: .combine)
|
||||
.accessibilityLabel("Talk Mode")
|
||||
.accessibilityValue("\(self.state.title), \(self.subtitle)")
|
||||
.accessibilityValue(self.accessibilityValue)
|
||||
}
|
||||
|
||||
private var accessibilityValue: String {
|
||||
if let voiceModeText {
|
||||
return "\(self.state.title), \(self.subtitle), \(voiceModeText)"
|
||||
}
|
||||
return "\(self.state.title), \(self.subtitle)"
|
||||
}
|
||||
|
||||
private var voiceModeText: String? {
|
||||
guard !self.state.prefersPermissionCopy else { return nil }
|
||||
let title = self.voiceModeTitle.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
guard !title.isEmpty, title != "Not loaded" else { return nil }
|
||||
let subtitle = (self.voiceModeSubtitle ?? "").trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
return subtitle.isEmpty ? title : "\(title) • \(subtitle)"
|
||||
}
|
||||
|
||||
private var subtitle: String {
|
||||
|
||||
@@ -519,6 +519,8 @@ private struct CanvasContent: View {
|
||||
isSpeaking: self.appModel.talkMode.isSpeaking,
|
||||
isUserSpeechDetected: self.appModel.talkMode.isUserSpeechDetected,
|
||||
permissionState: self.appModel.talkMode.gatewayTalkPermissionState,
|
||||
voiceModeTitle: self.appModel.talkMode.gatewayTalkVoiceModeTitle,
|
||||
voiceModeSubtitle: self.appModel.talkMode.gatewayTalkVoiceModeSubtitle,
|
||||
onEnableTalk: {
|
||||
self.showTalkPermissionPrompt = true
|
||||
},
|
||||
|
||||
@@ -612,7 +612,7 @@ struct SettingsTab: View {
|
||||
private var shouldShowRealtimeVoicePicker: Bool {
|
||||
let providerSelection = TalkModeProviderSelection.resolved(self.talkProviderSelectionRaw)
|
||||
return providerSelection == .openAIRealtime
|
||||
|| self.appModel.talkMode.gatewayTalkUsesRealtimeRelay
|
||||
|| self.appModel.talkMode.gatewayTalkUsesRealtime
|
||||
}
|
||||
|
||||
private func talkVoiceSettingsView() -> AnyView {
|
||||
@@ -633,6 +633,16 @@ struct SettingsTab: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
LabeledContent("Voice Mode") {
|
||||
VStack(alignment: .trailing, spacing: 2) {
|
||||
Text(self.appModel.talkMode.gatewayTalkVoiceModeTitle)
|
||||
if let subtitle = self.appModel.talkMode.gatewayTalkVoiceModeSubtitle {
|
||||
Text(subtitle)
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
}
|
||||
}
|
||||
LabeledContent(
|
||||
"Active Provider",
|
||||
value: self.appModel.talkMode.gatewayTalkProviderLabel)
|
||||
|
||||
@@ -3,9 +3,107 @@ import OpenClawKit
|
||||
|
||||
enum TalkModeExecutionMode {
|
||||
case native
|
||||
case realtimeClient
|
||||
case realtimeRelay
|
||||
}
|
||||
|
||||
struct TalkVoiceModeDescriptor: Equatable {
|
||||
let title: String
|
||||
let subtitle: String?
|
||||
let providerId: String?
|
||||
let modelId: String?
|
||||
let voiceId: String?
|
||||
let transport: String?
|
||||
let isRealtime: Bool
|
||||
|
||||
var accessibilityValue: String {
|
||||
if let subtitle, !subtitle.isEmpty {
|
||||
return "\(self.title), \(subtitle)"
|
||||
}
|
||||
return self.title
|
||||
}
|
||||
}
|
||||
|
||||
enum TalkVoiceModeDescriptorBuilder {
|
||||
static func build(
|
||||
providerId: String,
|
||||
providerLabel: String,
|
||||
modelId: String?,
|
||||
voiceId: String?,
|
||||
transport: String?,
|
||||
isRealtime: Bool) -> TalkVoiceModeDescriptor
|
||||
{
|
||||
let normalizedProvider = providerId.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
|
||||
let trimmedModel = Self.trimmed(modelId)
|
||||
let trimmedVoice = Self.trimmed(voiceId)
|
||||
let trimmedTransport = Self.trimmed(transport)
|
||||
let title = if isRealtime, normalizedProvider == "openai", trimmedModel == "gpt-realtime-2" {
|
||||
"GPT Realtime 2.0"
|
||||
} else if isRealtime, normalizedProvider == "openai" {
|
||||
"OpenAI Realtime"
|
||||
} else if isRealtime {
|
||||
providerLabel.isEmpty ? "Realtime Voice" : providerLabel
|
||||
} else if normalizedProvider == "system" {
|
||||
"iOS System Voice"
|
||||
} else {
|
||||
providerLabel.isEmpty ? "Talk Voice" : providerLabel
|
||||
}
|
||||
|
||||
var details: [String] = []
|
||||
if isRealtime, normalizedProvider != "openai", !providerLabel.isEmpty, providerLabel != title {
|
||||
details.append(providerLabel)
|
||||
}
|
||||
if let trimmedTransport {
|
||||
details.append(Self.transportLabel(trimmedTransport))
|
||||
}
|
||||
if let trimmedModel, title != "GPT Realtime 2.0" || trimmedModel != "gpt-realtime-2" {
|
||||
details.append(trimmedModel)
|
||||
}
|
||||
if let trimmedVoice {
|
||||
details.append(Self.voiceLabel(trimmedVoice))
|
||||
}
|
||||
|
||||
return TalkVoiceModeDescriptor(
|
||||
title: title,
|
||||
subtitle: details.isEmpty ? nil : details.joined(separator: " • "),
|
||||
providerId: normalizedProvider.isEmpty ? nil : normalizedProvider,
|
||||
modelId: trimmedModel,
|
||||
voiceId: trimmedVoice,
|
||||
transport: trimmedTransport,
|
||||
isRealtime: isRealtime)
|
||||
}
|
||||
|
||||
private static func trimmed(_ value: String?) -> String? {
|
||||
let trimmed = (value ?? "").trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
return trimmed.isEmpty ? nil : trimmed
|
||||
}
|
||||
|
||||
private static func voiceLabel(_ voice: String) -> String {
|
||||
TalkModeRealtimeVoiceSelection.voices.contains(voice)
|
||||
? TalkModeRealtimeVoiceSelection.label(for: voice)
|
||||
: voice
|
||||
}
|
||||
|
||||
private static func transportLabel(_ transport: String) -> String {
|
||||
switch transport.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() {
|
||||
case "webrtc":
|
||||
"Native WebRTC"
|
||||
case "gateway-relay":
|
||||
"Gateway Relay"
|
||||
case "provider-websocket":
|
||||
"Provider WebSocket"
|
||||
case "managed-room":
|
||||
"Managed Room"
|
||||
case "native":
|
||||
"Native"
|
||||
case let value where !value.isEmpty:
|
||||
value
|
||||
default:
|
||||
"Native"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum TalkModeProviderSelection: String, CaseIterable, Identifiable {
|
||||
case gatewayDefault = "gateway"
|
||||
case nativeElevenLabs = "elevenlabs"
|
||||
@@ -112,8 +210,9 @@ enum TalkModeGatewayConfigParser {
|
||||
let defaultVoiceId = Self.firstString(activeConfig, keys: ["voiceId", "voice"])
|
||||
let defaultOutputFormat = Self.firstString(activeConfig, keys: ["outputFormat"])
|
||||
let realtime = talk?["realtime"]?.dictionaryValue
|
||||
let realtimeProvider = Self.firstString(realtime, keys: ["provider"])
|
||||
let realtimeProviders = realtime?["providers"]?.dictionaryValue
|
||||
let realtimeProvider = Self.firstString(realtime, keys: ["provider"])
|
||||
?? Self.singleRealtimeProviderId(realtimeProviders)
|
||||
let realtimeProviderConfig = Self.realtimeProviderConfig(
|
||||
providers: realtimeProviders,
|
||||
provider: realtimeProvider)
|
||||
@@ -164,12 +263,24 @@ enum TalkModeGatewayConfigParser {
|
||||
let mode = Self.firstString(realtime, keys: ["mode"])?.lowercased()
|
||||
let transport = Self.firstString(realtime, keys: ["transport"])?.lowercased()
|
||||
let brain = Self.firstString(realtime, keys: ["brain"])?.lowercased()
|
||||
if mode == "realtime", transport == "gateway-relay", brain == nil || brain == "agent-consult" {
|
||||
guard mode == "realtime", brain == nil || brain == "agent-consult" else {
|
||||
return .native
|
||||
}
|
||||
if transport == "gateway-relay" {
|
||||
return .realtimeRelay
|
||||
}
|
||||
if transport == nil || transport == "webrtc" {
|
||||
return .realtimeClient
|
||||
}
|
||||
return .native
|
||||
}
|
||||
|
||||
private static func singleRealtimeProviderId(_ providers: [String: AnyCodable]?) -> String? {
|
||||
guard let providers, providers.count == 1 else { return nil }
|
||||
let provider = providers.keys.first?.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
return provider?.isEmpty == false ? provider : nil
|
||||
}
|
||||
|
||||
private static func realtimeProviderConfig(
|
||||
providers: [String: AnyCodable]?,
|
||||
provider: String?) -> [String: AnyCodable]?
|
||||
|
||||
@@ -116,10 +116,14 @@ final class TalkModeManager: NSObject {
|
||||
var gatewayTalkDefaultVoiceId: String?
|
||||
var gatewayTalkProviderLabel: String = "Not loaded"
|
||||
var gatewayTalkTransportLabel: String = "Not loaded"
|
||||
var gatewayTalkUsesRealtime: Bool = false
|
||||
var gatewayTalkUsesRealtimeRelay: Bool = false
|
||||
var gatewayTalkRealtimeProviderLabel: String?
|
||||
var gatewayTalkRealtimeModelId: String?
|
||||
var gatewayTalkRealtimeVoiceId: String?
|
||||
var gatewayTalkVoiceModeTitle: String = "Not loaded"
|
||||
var gatewayTalkVoiceModeSubtitle: String?
|
||||
var gatewayTalkVoiceModeAccessibilityValue: String = "Not loaded"
|
||||
var gatewayTalkPermissionState: TalkGatewayPermissionState = .unknown
|
||||
|
||||
private enum CaptureMode {
|
||||
@@ -145,6 +149,7 @@ final class TalkModeManager: NSObject {
|
||||
private var recognitionTask: SFSpeechRecognitionTask?
|
||||
private var silenceTask: Task<Void, Never>?
|
||||
private var realtimeSession: TalkRealtimeWebRTCSession?
|
||||
private var realtimeRelaySession: RealtimeTalkRelaySession?
|
||||
private var prefetchedRealtimeSession: TalkRealtimeClientSession?
|
||||
private var realtimePrefetchTask: Task<Void, Never>?
|
||||
|
||||
@@ -167,6 +172,14 @@ final class TalkModeManager: NSObject {
|
||||
private var realtimeProvider: String?
|
||||
private var realtimeModelId: String?
|
||||
private var realtimeVoiceId: String?
|
||||
private var configuredVoiceModeDescriptor = TalkVoiceModeDescriptor(
|
||||
title: "Not loaded",
|
||||
subtitle: nil,
|
||||
providerId: nil,
|
||||
modelId: nil,
|
||||
voiceId: nil,
|
||||
transport: nil,
|
||||
isRealtime: false)
|
||||
private var apiKey: String?
|
||||
private var voiceAliases: [String: String] = [:]
|
||||
private var interruptOnSpeech: Bool = true
|
||||
@@ -304,8 +317,13 @@ final class TalkModeManager: NSObject {
|
||||
GatewayDiagnostics.log("talk.timeline manager start blocked gateway permission")
|
||||
return
|
||||
}
|
||||
if self.realtimeWebRTCEnabled, await self.startRealtimeIfAvailable() {
|
||||
return
|
||||
if self.realtimeWebRTCEnabled {
|
||||
let started = self.executionMode == .realtimeRelay
|
||||
? await self.startRealtimeRelayIfAvailable()
|
||||
: await self.startRealtimeIfAvailable()
|
||||
if started {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
let speechOk = await Self.requestSpeechPermission()
|
||||
@@ -419,6 +437,7 @@ final class TalkModeManager: NSObject {
|
||||
if let realtimeSession {
|
||||
realtimeSession.cancelResponse()
|
||||
}
|
||||
self.realtimeRelaySession?.cancelOutput()
|
||||
self.stopSpeaking()
|
||||
}
|
||||
|
||||
@@ -1046,9 +1065,68 @@ final class TalkModeManager: NSObject {
|
||||
}
|
||||
}
|
||||
|
||||
private func startRealtimeRelayIfAvailable() async -> Bool {
|
||||
guard let gateway else { return false }
|
||||
GatewayDiagnostics.log("talk.timeline realtime relay start attempt sessionKey=\(self.mainSessionKey)")
|
||||
let startedAt = Self.nowSeconds()
|
||||
let relaySession = RealtimeTalkRelaySession(
|
||||
gateway: gateway,
|
||||
options: RealtimeTalkRelaySession.Options(
|
||||
sessionKey: self.mainSessionKey,
|
||||
provider: self.realtimeProvider,
|
||||
model: self.realtimeModelId,
|
||||
voice: self.realtimeVoiceId),
|
||||
pcmPlayer: self.pcmPlayer,
|
||||
onStatus: { [weak self] status in
|
||||
guard let self else { return }
|
||||
self.statusText = status
|
||||
self.isListening = status.localizedCaseInsensitiveContains("listening")
|
||||
if status.localizedCaseInsensitiveContains("thinking") {
|
||||
self.isListening = false
|
||||
self.isSpeaking = false
|
||||
self.isUserSpeechDetected = false
|
||||
}
|
||||
},
|
||||
onSpeakingChanged: { [weak self] speaking in
|
||||
guard let self else { return }
|
||||
self.isSpeaking = speaking
|
||||
if speaking {
|
||||
self.isListening = false
|
||||
}
|
||||
})
|
||||
self.realtimeRelaySession = relaySession
|
||||
do {
|
||||
try Self.configureAudioSession()
|
||||
try await relaySession.start()
|
||||
guard self.realtimeRelaySession === relaySession, self.isEnabled else {
|
||||
relaySession.stop()
|
||||
return true
|
||||
}
|
||||
self.isListening = true
|
||||
self.captureMode = .continuous
|
||||
GatewayDiagnostics.log(
|
||||
"talk.timeline realtime relay start ready elapsedMs=\(Self.elapsedMs(since: startedAt))")
|
||||
return true
|
||||
} catch {
|
||||
guard self.realtimeRelaySession === relaySession, self.isEnabled else {
|
||||
relaySession.stop()
|
||||
return true
|
||||
}
|
||||
self.realtimeRelaySession = nil
|
||||
GatewayDiagnostics.log(
|
||||
"talk.timeline realtime relay start failed elapsedMs=\(Self.elapsedMs(since: startedAt)) "
|
||||
+ "error=\(error.localizedDescription)")
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func prefetchRealtimeSessionIfReady(reason: String) async {
|
||||
guard self.gatewayConnected, self.realtimeSession == nil, !self.isEnabled else { return }
|
||||
guard self.realtimeWebRTCEnabled else { return }
|
||||
guard self.gatewayConnected,
|
||||
self.realtimeSession == nil,
|
||||
self.realtimeRelaySession == nil,
|
||||
!self.isEnabled
|
||||
else { return }
|
||||
guard self.realtimeWebRTCEnabled, self.executionMode != .realtimeRelay else { return }
|
||||
guard self.gatewayTalkPermissionState == .ready else { return }
|
||||
guard self.consumePrefetchedRealtimeSession(peekOnly: true) == nil else { return }
|
||||
guard self.realtimePrefetchTask == nil else { return }
|
||||
@@ -1116,6 +1194,8 @@ final class TalkModeManager: NSObject {
|
||||
private func stopRealtimeSession() {
|
||||
self.realtimeSession?.stop()
|
||||
self.realtimeSession = nil
|
||||
self.realtimeRelaySession?.stop()
|
||||
self.realtimeRelaySession = nil
|
||||
}
|
||||
|
||||
private func subscribeChatIfNeeded(sessionKey: String) async {
|
||||
@@ -1305,6 +1385,14 @@ final class TalkModeManager: NSObject {
|
||||
|
||||
if canUseElevenLabs, let voiceId, let apiKey {
|
||||
GatewayDiagnostics.log("talk tts: provider=elevenlabs voiceId=\(voiceId)")
|
||||
let modelId = directive?.modelId ?? self.currentModelId ?? self.defaultModelId
|
||||
self.applyVoiceModeDescriptor(TalkVoiceModeDescriptorBuilder.build(
|
||||
providerId: "elevenlabs",
|
||||
providerLabel: Self.displayName(forProvider: "elevenlabs"),
|
||||
modelId: modelId,
|
||||
voiceId: voiceId,
|
||||
transport: "native",
|
||||
isRealtime: false))
|
||||
let desiredOutputFormat = (directive?.outputFormat ?? self.defaultOutputFormat)?
|
||||
.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
let requestedOutputFormat = (desiredOutputFormat?.isEmpty == false) ? desiredOutputFormat : nil
|
||||
@@ -1315,7 +1403,6 @@ final class TalkModeManager: NSObject {
|
||||
"talk output_format unsupported for local playback: \(requestedOutputFormat, privacy: .public)")
|
||||
}
|
||||
|
||||
let modelId = directive?.modelId ?? self.currentModelId ?? self.defaultModelId
|
||||
if let modelId {
|
||||
GatewayDiagnostics.log("talk tts: modelId=\(modelId)")
|
||||
}
|
||||
@@ -1384,6 +1471,13 @@ final class TalkModeManager: NSObject {
|
||||
} else {
|
||||
self.logger.warning("tts unavailable; falling back to system voice (missing key or voiceId)")
|
||||
GatewayDiagnostics.log("talk tts: provider=system (missing key or voiceId)")
|
||||
self.applyVoiceModeDescriptor(TalkVoiceModeDescriptorBuilder.build(
|
||||
providerId: "system",
|
||||
providerLabel: Self.displayName(forProvider: "system"),
|
||||
modelId: nil,
|
||||
voiceId: language,
|
||||
transport: "native",
|
||||
isRealtime: false))
|
||||
if self.interruptOnSpeech {
|
||||
do {
|
||||
try self.startRecognition()
|
||||
@@ -1400,6 +1494,14 @@ final class TalkModeManager: NSObject {
|
||||
"tts failed: \(error.localizedDescription, privacy: .public); falling back to system voice")
|
||||
GatewayDiagnostics.log("talk tts: provider=system (error) msg=\(error.localizedDescription)")
|
||||
do {
|
||||
let language = ElevenLabsTTSClient.validatedLanguage(directive?.language)
|
||||
self.applyVoiceModeDescriptor(TalkVoiceModeDescriptorBuilder.build(
|
||||
providerId: "system",
|
||||
providerLabel: Self.displayName(forProvider: "system"),
|
||||
modelId: nil,
|
||||
voiceId: language,
|
||||
transport: "native",
|
||||
isRealtime: false))
|
||||
if self.interruptOnSpeech {
|
||||
do {
|
||||
try self.startRecognition()
|
||||
@@ -1409,7 +1511,6 @@ final class TalkModeManager: NSObject {
|
||||
}
|
||||
}
|
||||
self.statusText = "Speaking (System)…"
|
||||
let language = ElevenLabsTTSClient.validatedLanguage(directive?.language)
|
||||
try await TalkSystemSpeechSynthesizer.shared.speak(text: cleaned, language: language)
|
||||
} catch {
|
||||
self.statusText = "Speak failed: \(error.localizedDescription)"
|
||||
@@ -1419,6 +1520,7 @@ final class TalkModeManager: NSObject {
|
||||
|
||||
self.stopRecognition()
|
||||
self.isSpeaking = false
|
||||
self.restoreConfiguredVoiceModeDescriptor()
|
||||
}
|
||||
|
||||
private func stopSpeaking(storeInterruption: Bool = true) {
|
||||
@@ -1441,6 +1543,7 @@ final class TalkModeManager: NSObject {
|
||||
TalkSystemSpeechSynthesizer.shared.stop()
|
||||
self.cancelIncrementalSpeech()
|
||||
self.isSpeaking = false
|
||||
self.restoreConfiguredVoiceModeDescriptor()
|
||||
}
|
||||
|
||||
private func shouldInterrupt(with transcript: String) -> Bool {
|
||||
@@ -2256,6 +2359,10 @@ extension TalkModeManager {
|
||||
"OpenAI"
|
||||
case "google":
|
||||
"Google"
|
||||
case "system":
|
||||
"iOS System Voice"
|
||||
case "realtime":
|
||||
"Realtime Voice"
|
||||
case let provider where !provider.isEmpty:
|
||||
provider
|
||||
default:
|
||||
@@ -2263,6 +2370,36 @@ extension TalkModeManager {
|
||||
}
|
||||
}
|
||||
|
||||
private func applyVoiceModeDescriptor(_ descriptor: TalkVoiceModeDescriptor, persistAsConfigured: Bool = false) {
|
||||
if persistAsConfigured {
|
||||
self.configuredVoiceModeDescriptor = descriptor
|
||||
}
|
||||
self.gatewayTalkVoiceModeTitle = descriptor.title
|
||||
self.gatewayTalkVoiceModeSubtitle = descriptor.subtitle
|
||||
self.gatewayTalkVoiceModeAccessibilityValue = descriptor.accessibilityValue
|
||||
}
|
||||
|
||||
private func restoreConfiguredVoiceModeDescriptor() {
|
||||
self.applyVoiceModeDescriptor(self.configuredVoiceModeDescriptor)
|
||||
}
|
||||
|
||||
private func buildConfiguredVoiceModeDescriptor(
|
||||
provider: String,
|
||||
providerLabel: String,
|
||||
modelId: String?,
|
||||
voiceId: String?,
|
||||
transport: String,
|
||||
isRealtime: Bool) -> TalkVoiceModeDescriptor
|
||||
{
|
||||
TalkVoiceModeDescriptorBuilder.build(
|
||||
providerId: provider,
|
||||
providerLabel: providerLabel,
|
||||
modelId: modelId,
|
||||
voiceId: voiceId,
|
||||
transport: transport,
|
||||
isRealtime: isRealtime)
|
||||
}
|
||||
|
||||
private func ensureTalkConfigLoadedForStart() async {
|
||||
if self.gatewayTalkConfigLoaded || self.gatewayTalkPermissionState.isApprovalRequestInProgress {
|
||||
GatewayDiagnostics.log(
|
||||
@@ -2341,7 +2478,7 @@ extension TalkModeManager {
|
||||
realtimeProvider = realtimeProvider ?? "openai"
|
||||
realtimeModelId = realtimeModelId ?? Self.defaultRealtimeModelIdFallback
|
||||
}
|
||||
let usesRealtimeConfig = activeProvider != Self.defaultTalkProvider || executionMode == .realtimeRelay
|
||||
let usesRealtimeConfig = activeProvider != Self.defaultTalkProvider || executionMode != .native
|
||||
self.activeTalkProvider = activeProvider
|
||||
self.executionMode = executionMode
|
||||
self.realtimeWebRTCEnabled = usesRealtimeConfig
|
||||
@@ -2377,14 +2514,31 @@ extension TalkModeManager {
|
||||
}
|
||||
self.gatewayTalkDefaultVoiceId = usesRealtimeConfig ? realtimeVoiceId : self.defaultVoiceId
|
||||
self.gatewayTalkDefaultModelId = usesRealtimeConfig ? realtimeModelId : self.defaultModelId
|
||||
self.gatewayTalkProviderLabel = providerSelection == .gatewayDefault
|
||||
let providerLabel = providerSelection == .gatewayDefault
|
||||
? Self.displayName(forProvider: activeProvider)
|
||||
: providerSelection.label
|
||||
self.gatewayTalkUsesRealtimeRelay = false
|
||||
self.gatewayTalkTransportLabel = usesRealtimeConfig ? "Native WebRTC" : "Native"
|
||||
let usesRealtimeRelay = executionMode == .realtimeRelay
|
||||
let transport = usesRealtimeConfig ? (usesRealtimeRelay ? "gateway-relay" : "webrtc") : "native"
|
||||
let transportLabel = usesRealtimeRelay ? "Gateway Relay" : (usesRealtimeConfig ? "Native WebRTC" : "Native")
|
||||
self.gatewayTalkProviderLabel = providerLabel
|
||||
self.gatewayTalkUsesRealtime = usesRealtimeConfig
|
||||
self.gatewayTalkUsesRealtimeRelay = usesRealtimeRelay
|
||||
self.gatewayTalkTransportLabel = transportLabel
|
||||
self.gatewayTalkRealtimeProviderLabel = realtimeProvider.map { Self.displayName(forProvider: $0) }
|
||||
self.gatewayTalkRealtimeModelId = realtimeModelId
|
||||
self.gatewayTalkRealtimeVoiceId = realtimeVoiceId
|
||||
let voiceModeProvider = usesRealtimeConfig ? (realtimeProvider ?? "realtime") : activeProvider
|
||||
let voiceModeLabel = usesRealtimeConfig
|
||||
? Self.displayName(forProvider: voiceModeProvider)
|
||||
: Self.displayName(forProvider: activeProvider)
|
||||
let voiceModeDescriptor = self.buildConfiguredVoiceModeDescriptor(
|
||||
provider: voiceModeProvider,
|
||||
providerLabel: voiceModeLabel,
|
||||
modelId: usesRealtimeConfig ? realtimeModelId : self.defaultModelId,
|
||||
voiceId: usesRealtimeConfig ? realtimeVoiceId : self.defaultVoiceId,
|
||||
transport: transport,
|
||||
isRealtime: usesRealtimeConfig)
|
||||
self.applyVoiceModeDescriptor(voiceModeDescriptor, persistAsConfigured: true)
|
||||
self.gatewayTalkApiKeyConfigured = gatewayOwnedVoiceProvider || (self.apiKey?.isEmpty == false)
|
||||
self.gatewayTalkConfigLoaded = true
|
||||
self.talkConfigLoadedAt = Date()
|
||||
@@ -2416,10 +2570,19 @@ extension TalkModeManager {
|
||||
self.realtimeVoiceId = nil
|
||||
self.gatewayTalkProviderLabel = "Not loaded"
|
||||
self.gatewayTalkTransportLabel = "Not loaded"
|
||||
self.gatewayTalkUsesRealtime = false
|
||||
self.gatewayTalkUsesRealtimeRelay = false
|
||||
self.gatewayTalkRealtimeProviderLabel = nil
|
||||
self.gatewayTalkRealtimeModelId = nil
|
||||
self.gatewayTalkRealtimeVoiceId = nil
|
||||
self.applyVoiceModeDescriptor(TalkVoiceModeDescriptor(
|
||||
title: "Not loaded",
|
||||
subtitle: nil,
|
||||
providerId: nil,
|
||||
modelId: nil,
|
||||
voiceId: nil,
|
||||
transport: nil,
|
||||
isRealtime: false), persistAsConfigured: true)
|
||||
self.defaultModelId = Self.defaultModelIdFallback
|
||||
if !self.modelOverrideActive {
|
||||
self.currentModelId = self.defaultModelId
|
||||
|
||||
@@ -48,6 +48,46 @@ import Testing
|
||||
#expect(parsed.realtimeVoiceId == "marin")
|
||||
}
|
||||
|
||||
@Test func infersRealtimeProviderWhenProviderMapHasSingleEntry() {
|
||||
let config: [String: Any] = [
|
||||
"talk": [
|
||||
"realtime": [
|
||||
"mode": "realtime",
|
||||
"transport": "webrtc",
|
||||
"providers": [
|
||||
"openai": [
|
||||
"model": "gpt-realtime-2",
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
]
|
||||
|
||||
let parsed = TalkModeGatewayConfigParser.parse(
|
||||
config: config,
|
||||
defaultProvider: "elevenlabs",
|
||||
defaultModelIdFallback: "eleven_v3",
|
||||
defaultRealtimeModelIdFallback: "gpt-realtime-2",
|
||||
defaultSilenceTimeoutMs: 900)
|
||||
|
||||
#expect(parsed.executionMode == .realtimeClient)
|
||||
#expect(parsed.realtimeProvider == "openai")
|
||||
#expect(parsed.realtimeModelId == "gpt-realtime-2")
|
||||
}
|
||||
|
||||
@Test func formatsGenericRealtimeVoiceModeWithoutNativeProviderFallback() {
|
||||
let descriptor = TalkVoiceModeDescriptorBuilder.build(
|
||||
providerId: "realtime",
|
||||
providerLabel: "Realtime Voice",
|
||||
modelId: "gpt-realtime-2",
|
||||
voiceId: nil,
|
||||
transport: "webrtc",
|
||||
isRealtime: true)
|
||||
|
||||
#expect(descriptor.title == "Realtime Voice")
|
||||
#expect(descriptor.subtitle == "Native WebRTC • gpt-realtime-2")
|
||||
}
|
||||
|
||||
@Test func defaultsOpenAIRealtimeModelWhenProviderOmitsModel() {
|
||||
let config: [String: Any] = [
|
||||
"talk": [
|
||||
@@ -79,7 +119,60 @@ import Testing
|
||||
#expect(TalkModeRealtimeVoiceSelection.resolvedOverride("unknown") == nil)
|
||||
}
|
||||
|
||||
@Test func leavesNativeModeWhenRealtimeTransportIsNotGatewayRelay() {
|
||||
@Test func formatsOpenAIRealtimeVoiceMode() {
|
||||
let descriptor = TalkVoiceModeDescriptorBuilder.build(
|
||||
providerId: "openai",
|
||||
providerLabel: "OpenAI",
|
||||
modelId: "gpt-realtime-2",
|
||||
voiceId: "marin",
|
||||
transport: "webrtc",
|
||||
isRealtime: true)
|
||||
|
||||
#expect(descriptor.title == "GPT Realtime 2.0")
|
||||
#expect(descriptor.subtitle == "Native WebRTC • Marin")
|
||||
#expect(descriptor.accessibilityValue == "GPT Realtime 2.0, Native WebRTC • Marin")
|
||||
}
|
||||
|
||||
@Test func formatsGatewayRelayRealtimeVoiceMode() {
|
||||
let descriptor = TalkVoiceModeDescriptorBuilder.build(
|
||||
providerId: "google",
|
||||
providerLabel: "Google Live Voice",
|
||||
modelId: "gemini-live-2.5-flash-preview",
|
||||
voiceId: nil,
|
||||
transport: "gateway-relay",
|
||||
isRealtime: true)
|
||||
|
||||
#expect(descriptor.title == "Google Live Voice")
|
||||
#expect(descriptor.subtitle == "Gateway Relay • gemini-live-2.5-flash-preview")
|
||||
}
|
||||
|
||||
@Test func formatsElevenLabsVoiceMode() {
|
||||
let descriptor = TalkVoiceModeDescriptorBuilder.build(
|
||||
providerId: "elevenlabs",
|
||||
providerLabel: "ElevenLabs",
|
||||
modelId: "eleven_v3",
|
||||
voiceId: "voice-id",
|
||||
transport: "native",
|
||||
isRealtime: false)
|
||||
|
||||
#expect(descriptor.title == "ElevenLabs")
|
||||
#expect(descriptor.subtitle == "Native • eleven_v3 • voice-id")
|
||||
}
|
||||
|
||||
@Test func formatsSystemVoiceFallbackMode() {
|
||||
let descriptor = TalkVoiceModeDescriptorBuilder.build(
|
||||
providerId: "system",
|
||||
providerLabel: "iOS System Voice",
|
||||
modelId: nil,
|
||||
voiceId: "en-US",
|
||||
transport: "native",
|
||||
isRealtime: false)
|
||||
|
||||
#expect(descriptor.title == "iOS System Voice")
|
||||
#expect(descriptor.subtitle == "Native • en-US")
|
||||
}
|
||||
|
||||
@Test func usesRealtimeClientModeForWebRTCTransport() {
|
||||
let config: [String: Any] = [
|
||||
"talk": [
|
||||
"realtime": [
|
||||
@@ -97,7 +190,7 @@ import Testing
|
||||
defaultRealtimeModelIdFallback: "gpt-realtime-2",
|
||||
defaultSilenceTimeoutMs: 900)
|
||||
|
||||
#expect(parsed.executionMode == .native)
|
||||
#expect(parsed.executionMode == .realtimeClient)
|
||||
}
|
||||
|
||||
@Test func detectsPCMFormatRejectionFromElevenLabsError() {
|
||||
|
||||
|
Before Width: | Height: | Size: 2.9 MiB |
|
Before Width: | Height: | Size: 154 KiB |
|
Before Width: | Height: | Size: 300 KiB |
|
Before Width: | Height: | Size: 1.5 MiB |
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"version": "2026.5.25"
|
||||
"version": "2026.5.26"
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import Foundation
|
||||
|
||||
enum HostEnvSecurityPolicy {
|
||||
static let blockedInheritedKeys: Set<String> = [
|
||||
"_JAVA_OPTIONS",
|
||||
"AMQP_URL",
|
||||
"ANSIBLE_CALLBACK_PLUGINS",
|
||||
"ANSIBLE_COLLECTIONS_PATH",
|
||||
@@ -31,12 +30,11 @@ enum HostEnvSecurityPolicy {
|
||||
"AZURE_CLIENT_SECRET",
|
||||
"BASH_ENV",
|
||||
"BROWSER",
|
||||
"BUN_CONFIG_REGISTRY",
|
||||
"BUNDLE_GEMFILE",
|
||||
"BUN_CONFIG_REGISTRY",
|
||||
"BZR_EDITOR",
|
||||
"BZR_PLUGIN_PATH",
|
||||
"BZR_SSH",
|
||||
"C_INCLUDE_PATH",
|
||||
"CARGO_BUILD_RUSTC",
|
||||
"CARGO_BUILD_RUSTC_WRAPPER",
|
||||
"CARGO_HOME",
|
||||
@@ -46,8 +44,8 @@ enum HostEnvSecurityPolicy {
|
||||
"CGO_CFLAGS",
|
||||
"CGO_LDFLAGS",
|
||||
"CLASSPATH",
|
||||
"CMAKE_C_COMPILER",
|
||||
"CMAKE_CXX_COMPILER",
|
||||
"CMAKE_C_COMPILER",
|
||||
"CMAKE_TOOLCHAIN_FILE",
|
||||
"COMPOSER_HOME",
|
||||
"CONFIG_SHELL",
|
||||
@@ -58,6 +56,7 @@ enum HostEnvSecurityPolicy {
|
||||
"CPLUS_INCLUDE_PATH",
|
||||
"CURL_HOME",
|
||||
"CXX",
|
||||
"C_INCLUDE_PATH",
|
||||
"DATABASE_URL",
|
||||
"DENO_DIR",
|
||||
"DOTNET_ADDITIONAL_DEPS",
|
||||
@@ -75,6 +74,8 @@ enum HostEnvSecurityPolicy {
|
||||
"GEM_HOME",
|
||||
"GEM_PATH",
|
||||
"GH_TOKEN",
|
||||
"GITHUB_TOKEN",
|
||||
"GITLAB_TOKEN",
|
||||
"GIT_ALTERNATE_OBJECT_DIRECTORIES",
|
||||
"GIT_ASKPASS",
|
||||
"GIT_COMMON_DIR",
|
||||
@@ -95,8 +96,6 @@ enum HostEnvSecurityPolicy {
|
||||
"GIT_SSL_NO_VERIFY",
|
||||
"GIT_TEMPLATE_DIR",
|
||||
"GIT_WORK_TREE",
|
||||
"GITHUB_TOKEN",
|
||||
"GITLAB_TOKEN",
|
||||
"GLIBC_TUNABLES",
|
||||
"GOENV",
|
||||
"GOFLAGS",
|
||||
@@ -145,8 +144,8 @@ enum HostEnvSecurityPolicy {
|
||||
"PERL5DBCMD",
|
||||
"PERL5LIB",
|
||||
"PERL5OPT",
|
||||
"PHP_INI_SCAN_DIR",
|
||||
"PHPRC",
|
||||
"PHP_INI_SCAN_DIR",
|
||||
"PIP_CONFIG_FILE",
|
||||
"PIP_EXTRA_INDEX_URL",
|
||||
"PIP_FIND_LINKS",
|
||||
@@ -160,17 +159,17 @@ enum HostEnvSecurityPolicy {
|
||||
"PYTHONPATH",
|
||||
"PYTHONSTARTUP",
|
||||
"PYTHONUSERBASE",
|
||||
"R_ENVIRON",
|
||||
"R_ENVIRON_USER",
|
||||
"R_LIBS_USER",
|
||||
"R_PROFILE",
|
||||
"R_PROFILE_USER",
|
||||
"REDIS_URL",
|
||||
"RUBYLIB",
|
||||
"RUBYOPT",
|
||||
"RUBYSHELL",
|
||||
"RUSTC_WRAPPER",
|
||||
"RUSTFLAGS",
|
||||
"R_ENVIRON",
|
||||
"R_ENVIRON_USER",
|
||||
"R_LIBS_USER",
|
||||
"R_PROFILE",
|
||||
"R_PROFILE_USER",
|
||||
"SBT_OPTS",
|
||||
"SHELL",
|
||||
"SHELLOPTS",
|
||||
@@ -192,7 +191,8 @@ enum HostEnvSecurityPolicy {
|
||||
"VIRTUAL_ENV",
|
||||
"VISUAL",
|
||||
"WGETRC",
|
||||
"YARN_RC_FILENAME"
|
||||
"YARN_RC_FILENAME",
|
||||
"_JAVA_OPTIONS"
|
||||
]
|
||||
|
||||
static let blockedInheritedPrefixes: [String] = [
|
||||
@@ -202,7 +202,6 @@ enum HostEnvSecurityPolicy {
|
||||
]
|
||||
|
||||
static let blockedKeys: Set<String> = [
|
||||
"_JAVA_OPTIONS",
|
||||
"ANT_OPTS",
|
||||
"BASH_ENV",
|
||||
"BROWSER",
|
||||
@@ -213,8 +212,8 @@ enum HostEnvSecurityPolicy {
|
||||
"CARGO_BUILD_RUSTC_WRAPPER",
|
||||
"CATALINA_OPTS",
|
||||
"CC",
|
||||
"CMAKE_C_COMPILER",
|
||||
"CMAKE_CXX_COMPILER",
|
||||
"CMAKE_C_COMPILER",
|
||||
"CMAKE_TOOLCHAIN_FILE",
|
||||
"CONFIG_SHELL",
|
||||
"CONFIG_SITE",
|
||||
@@ -275,14 +274,14 @@ enum HostEnvSecurityPolicy {
|
||||
"PYTHONBREAKPOINT",
|
||||
"PYTHONHOME",
|
||||
"PYTHONPATH",
|
||||
"R_ENVIRON",
|
||||
"R_ENVIRON_USER",
|
||||
"R_PROFILE",
|
||||
"R_PROFILE_USER",
|
||||
"RUBYLIB",
|
||||
"RUBYOPT",
|
||||
"RUBYSHELL",
|
||||
"RUSTC_WRAPPER",
|
||||
"R_ENVIRON",
|
||||
"R_ENVIRON_USER",
|
||||
"R_PROFILE",
|
||||
"R_PROFILE_USER",
|
||||
"SBT_OPTS",
|
||||
"SHELL",
|
||||
"SHELLOPTS",
|
||||
@@ -291,7 +290,8 @@ enum HostEnvSecurityPolicy {
|
||||
"SVN_EDITOR",
|
||||
"SVN_SSH",
|
||||
"VAGRANT_VAGRANTFILE",
|
||||
"VIMINIT"
|
||||
"VIMINIT",
|
||||
"_JAVA_OPTIONS"
|
||||
]
|
||||
|
||||
static let blockedOverrideKeys: Set<String> = [
|
||||
@@ -321,9 +321,8 @@ enum HostEnvSecurityPolicy {
|
||||
"AZURE_AUTH_LOCATION",
|
||||
"AZURE_CLIENT_ID",
|
||||
"AZURE_CLIENT_SECRET",
|
||||
"BUN_CONFIG_REGISTRY",
|
||||
"BUNDLE_GEMFILE",
|
||||
"C_INCLUDE_PATH",
|
||||
"BUN_CONFIG_REGISTRY",
|
||||
"CARGO_BUILD_RUSTC_WRAPPER",
|
||||
"CARGO_HOME",
|
||||
"CFLAGS",
|
||||
@@ -336,6 +335,7 @@ enum HostEnvSecurityPolicy {
|
||||
"CPLUS_INCLUDE_PATH",
|
||||
"CURL_CA_BUNDLE",
|
||||
"CURL_HOME",
|
||||
"C_INCLUDE_PATH",
|
||||
"DATABASE_URL",
|
||||
"DENO_DIR",
|
||||
"DOCKER_CERT_PATH",
|
||||
@@ -347,6 +347,8 @@ enum HostEnvSecurityPolicy {
|
||||
"GEM_HOME",
|
||||
"GEM_PATH",
|
||||
"GH_TOKEN",
|
||||
"GITHUB_TOKEN",
|
||||
"GITLAB_TOKEN",
|
||||
"GIT_ALTERNATE_OBJECT_DIRECTORIES",
|
||||
"GIT_ASKPASS",
|
||||
"GIT_COMMON_DIR",
|
||||
@@ -362,8 +364,6 @@ enum HostEnvSecurityPolicy {
|
||||
"GIT_SSL_CAPATH",
|
||||
"GIT_SSL_NO_VERIFY",
|
||||
"GIT_WORK_TREE",
|
||||
"GITHUB_TOKEN",
|
||||
"GITLAB_TOKEN",
|
||||
"GOENV",
|
||||
"GOFLAGS",
|
||||
"GONOPROXY",
|
||||
@@ -378,8 +378,8 @@ enum HostEnvSecurityPolicy {
|
||||
"HGRCPATH",
|
||||
"HISTFILE",
|
||||
"HOME",
|
||||
"HTTP_PROXY",
|
||||
"HTTPS_PROXY",
|
||||
"HTTP_PROXY",
|
||||
"KUBECONFIG",
|
||||
"LDFLAGS",
|
||||
"LESSCLOSE",
|
||||
@@ -391,10 +391,10 @@ enum HostEnvSecurityPolicy {
|
||||
"MANPAGER",
|
||||
"MFLAGS",
|
||||
"MONGODB_URI",
|
||||
"NO_PROXY",
|
||||
"NODE_AUTH_TOKEN",
|
||||
"NODE_EXTRA_CA_CERTS",
|
||||
"NODE_TLS_REJECT_UNAUTHORIZED",
|
||||
"NO_PROXY",
|
||||
"NPM_TOKEN",
|
||||
"OBJC_INCLUDE_PATH",
|
||||
"OPENSSL_CONF",
|
||||
@@ -402,8 +402,8 @@ enum HostEnvSecurityPolicy {
|
||||
"PAGER",
|
||||
"PERL5DB",
|
||||
"PERL5DBCMD",
|
||||
"PHP_INI_SCAN_DIR",
|
||||
"PHPRC",
|
||||
"PHP_INI_SCAN_DIR",
|
||||
"PIP_CONFIG_FILE",
|
||||
"PIP_EXTRA_INDEX_URL",
|
||||
"PIP_FIND_LINKS",
|
||||
@@ -413,11 +413,11 @@ enum HostEnvSecurityPolicy {
|
||||
"PROMPT_COMMAND",
|
||||
"PYTHONSTARTUP",
|
||||
"PYTHONUSERBASE",
|
||||
"R_LIBS_USER",
|
||||
"REDIS_URL",
|
||||
"REQUESTS_CA_BUNDLE",
|
||||
"RUSTC_WRAPPER",
|
||||
"RUSTFLAGS",
|
||||
"R_LIBS_USER",
|
||||
"SSH_ASKPASS",
|
||||
"SSH_AUTH_SOCK",
|
||||
"SSL_CERT_DIR",
|
||||
|
||||
@@ -15,9 +15,9 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>2026.5.25</string>
|
||||
<string>2026.5.26</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>2026052500</string>
|
||||
<string>2026052600</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>OpenClaw</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
|
||||
@@ -499,6 +499,45 @@
|
||||
"lines"
|
||||
]
|
||||
},
|
||||
"transcripts": {
|
||||
"emoji": "📝",
|
||||
"title": "Transcripts",
|
||||
"actions": {
|
||||
"start": {
|
||||
"label": "start",
|
||||
"detailKeys": [
|
||||
"providerId",
|
||||
"accountId",
|
||||
"guildId",
|
||||
"channelId",
|
||||
"title"
|
||||
]
|
||||
},
|
||||
"stop": {
|
||||
"label": "stop",
|
||||
"detailKeys": [
|
||||
"sessionId"
|
||||
]
|
||||
},
|
||||
"status": {
|
||||
"label": "status"
|
||||
},
|
||||
"import": {
|
||||
"label": "import",
|
||||
"detailKeys": [
|
||||
"providerId",
|
||||
"title",
|
||||
"speakerLabel"
|
||||
]
|
||||
},
|
||||
"summarize": {
|
||||
"label": "summarize",
|
||||
"detailKeys": [
|
||||
"sessionId"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"web_search": {
|
||||
"emoji": "🔎",
|
||||
"title": "Web Search",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
e8e71a715bd33405280b5a9f6b6e788abed636fba66ec5f3a4f9b9a768a1637f config-baseline.json
|
||||
003183db53a41905c540f37b1d30b3006ef8906915e13eac844b643cd210fdfe config-baseline.core.json
|
||||
859b021f65400df22c95ae55b074cf26c83d3a0bfadb3fceeaca522f6ea391ae config-baseline.channel.json
|
||||
1d2c2fa07a2d3c4d046d2defe2eb48b27011be25e75db205b19e3da37e9fd0a0 config-baseline.json
|
||||
5b12e247f4375de2d454802d3af188a840851dd69e9d15a1a92a4815c7ef7d97 config-baseline.core.json
|
||||
c766614db5c416910fb6cdd454efb0738779af80ddd58a4fb06d8b1ca6484ce2 config-baseline.channel.json
|
||||
74441e331aabb3026784c148d4ee5ce3f489a15ed87ffd9b7ba0c5e2a7bc93be config-baseline.plugin.json
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
390681a3d97af8c004db89ead136bd6cff693af5a0ddfe86a8e3c55a29a077eb plugin-sdk-api-baseline.json
|
||||
8dfaf69ee3d0a946bfdd1d8d97ef85262824d52c20854249f900db61f2a7f7b4 plugin-sdk-api-baseline.jsonl
|
||||
1f1824af61c8885360ff99dad3fe1d7b75565e540cdef57474e9f220f9876f78 plugin-sdk-api-baseline.json
|
||||
4f29099d81398cb76331b618c39d298b3c9398efd84291dfb93c2098cb4ae443 plugin-sdk-api-baseline.jsonl
|
||||
|
||||
|
Before Width: | Height: | Size: 122 KiB |
@@ -1,11 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="800" height="420" viewBox="0 0 800 420" role="img" aria-label="padel-cli availability output">
|
||||
<rect width="800" height="420" rx="24" fill="#0b0f14" />
|
||||
<rect x="24" y="24" width="752" height="372" rx="18" fill="#0f172a" stroke="#263246" stroke-width="2" />
|
||||
<text x="48" y="72" fill="#9ca3af" font-size="18" font-family="Fragment Mono, ui-monospace, SFMono-Regular, Menlo, monospace">
|
||||
<tspan x="48" dy="0">$ padel search --location "Barcelona" --date 2026-01-08 --time 18:00-22:00</tspan>
|
||||
<tspan x="48" dy="30" fill="#e5e7eb">Available courts (3):</tspan>
|
||||
<tspan x="48" dy="28" fill="#e5e7eb">- Vall d'Hebron 19:00 Court 2 (90m) EUR 34</tspan>
|
||||
<tspan x="48" dy="28" fill="#e5e7eb">- Badalona 20:30 Court 1 (60m) EUR 28</tspan>
|
||||
<tspan x="48" dy="28" fill="#e5e7eb">- Gracia 21:00 Court 4 (90m) EUR 36</tspan>
|
||||
</text>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 897 B |
@@ -1,13 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="800" height="420" viewBox="0 0 800 420" role="img" aria-label="GoHome Roborock status output">
|
||||
<rect width="800" height="420" rx="24" fill="#0b0f14" />
|
||||
<rect x="24" y="24" width="752" height="372" rx="18" fill="#111827" stroke="#263246" stroke-width="2" />
|
||||
<text x="48" y="72" fill="#9ca3af" font-size="18" font-family="Fragment Mono, ui-monospace, SFMono-Regular, Menlo, monospace">
|
||||
<tspan x="48" dy="0">$ gohome roborock status --device "Living Room"</tspan>
|
||||
<tspan x="48" dy="30" fill="#e5e7eb">Device: Roborock Q Revo</tspan>
|
||||
<tspan x="48" dy="28" fill="#e5e7eb">State: cleaning (zone)</tspan>
|
||||
<tspan x="48" dy="28" fill="#e5e7eb">Battery: 78%</tspan>
|
||||
<tspan x="48" dy="28" fill="#e5e7eb">Dustbin: 42%</tspan>
|
||||
<tspan x="48" dy="28" fill="#e5e7eb">Water tank: 61%</tspan>
|
||||
<tspan x="48" dy="28" fill="#e5e7eb">Last clean: 2026-01-06 19:42</tspan>
|
||||
</text>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 947 B |
@@ -436,7 +436,7 @@ Model override note:
|
||||
cron: {
|
||||
enabled: true,
|
||||
store: "~/.openclaw/cron/jobs.json",
|
||||
maxConcurrentRuns: 1,
|
||||
maxConcurrentRuns: 8,
|
||||
retry: {
|
||||
maxAttempts: 3,
|
||||
backoffMs: [60000, 120000, 300000],
|
||||
@@ -449,7 +449,7 @@ Model override note:
|
||||
}
|
||||
```
|
||||
|
||||
`maxConcurrentRuns` limits both scheduled cron dispatch and isolated agent-turn execution. Isolated cron agent turns use the queue's dedicated `cron-nested` execution lane internally, so raising this value lets independent cron LLM runs progress in parallel instead of only starting their outer cron wrappers. The shared non-cron `nested` lane is not widened by this setting.
|
||||
`maxConcurrentRuns` limits both scheduled cron dispatch and isolated agent-turn execution, and defaults to 8. Isolated cron agent turns use the queue's dedicated `cron-nested` execution lane internally, so raising this value lets independent cron LLM runs progress in parallel instead of only starting their outer cron wrappers. The shared non-cron `nested` lane is not widened by this setting.
|
||||
|
||||
The runtime state sidecar is derived from `cron.store`: a `.json` store such as `~/clawd/cron/jobs.json` uses `~/clawd/cron/jobs-state.json`, while a store path without a `.json` suffix appends `-state.json`.
|
||||
|
||||
|
||||
@@ -102,7 +102,7 @@ Not every agent run creates a task. Heartbeat turns and normal interactive chat
|
||||
<Accordion title="Notify defaults for cron and media">
|
||||
Main-session cron tasks use `silent` notify policy by default - they create records for tracking but do not generate notifications. Isolated cron tasks also default to `silent` but are more visible because they run in their own session.
|
||||
|
||||
Session-backed `image_generate`, `music_generate`, and `video_generate` runs also use `silent` notify policy. They still create task records, but completion is handed back to the original agent session as an internal wake so the agent can write the follow-up message and attach the finished media itself. Generated-media completion events require message-tool delivery: the agent must send the finished media with the `message` tool, then reply `NO_REPLY`. If the requester session is no longer active and the completion agent misses some or all generated media, OpenClaw sends an idempotent direct fallback with only the missing media to the original channel target.
|
||||
Session-backed `image_generate`, `music_generate`, and `video_generate` runs also use `silent` notify policy. They still create task records, but completion is handed back to the original agent session as an internal wake so the agent can write the follow-up message and attach the finished media itself. Generated-media completion events require message-tool delivery: the agent must send the finished media with the `message` tool, then reply `NO_REPLY`. If the requester session is no longer active or its active wake fails, and the completion agent misses some or all generated media, OpenClaw sends an idempotent direct fallback with only the missing media to the original channel target.
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="Concurrent media-generation guardrail">
|
||||
|
||||
@@ -1234,7 +1234,7 @@ Notes:
|
||||
- In `stt-tts` mode, STT uses `tools.media.audio`; `voice.model` does not affect transcription.
|
||||
- In realtime modes, `voice.realtime.provider`, `voice.realtime.model`, and `voice.realtime.voice` configure the realtime audio session. For OpenAI Realtime 2 plus the Codex brain, use `voice.realtime.model: "gpt-realtime-2"` and `voice.model: "openai-codex/gpt-5.5"`.
|
||||
- Realtime voice modes include small `IDENTITY.md`, `USER.md`, and `SOUL.md` profile files in the realtime provider instructions by default so fast direct turns keep the same identity, user grounding, and persona as the routed OpenClaw agent. Set `voice.realtime.bootstrapContextFiles` to a subset to customize this, or `[]` to disable it. The supported realtime bootstrap files are limited to those profile files; `AGENTS.md` stays in the normal agent context. The injected profile context does not replace `openclaw_agent_consult` for workspace work, current facts, memory lookup, or tool-backed actions.
|
||||
- In OpenAI `agent-proxy` realtime mode, set `voice.realtime.requireWakeName: true` to keep Discord realtime voice silent until a transcript contains a wake name. If `voice.realtime.wakeNames` is unset, OpenClaw uses the routed agent `name` plus `OpenClaw`, falling back to the agent id plus `OpenClaw`. Wake-name gating disables realtime provider auto-response and routes accepted turns through the OpenClaw agent consult path.
|
||||
- In OpenAI `agent-proxy` realtime mode, set `voice.realtime.requireWakeName: true` to keep Discord realtime voice silent until a transcript starts or ends with a wake name. Configured wake names must be one or two words. If `voice.realtime.wakeNames` is unset, OpenClaw uses the routed agent `name` plus `OpenClaw`, falling back to the agent id plus `OpenClaw`. Wake-name gating disables realtime provider auto-response, routes accepted turns through the OpenClaw agent consult path, and gives a short spoken acknowledgement when a leading wake name is recognized from partial transcription before the final transcript arrives.
|
||||
- The OpenAI realtime provider accepts current Realtime 2 event names and legacy Codex-compatible aliases for output audio and transcript events, so compatible provider snapshots can drift without dropping assistant audio.
|
||||
- `voice.realtime.bargeIn` controls whether Discord speaker-start events interrupt active realtime playback. If unset, it follows the realtime provider's input-audio interruption setting.
|
||||
- `voice.realtime.minBargeInAudioEndMs` controls the minimum assistant playback duration before an OpenAI realtime barge-in truncates audio. Default: `250`. Set `0` for immediate interruption in low-echo rooms, or raise it for echo-heavy speaker setups.
|
||||
@@ -1247,12 +1247,12 @@ Notes:
|
||||
- `voice.allowedChannels` is an optional residency allowlist. Leave it unset to allow `/vc join` into any authorized Discord voice channel. When set, `/vc join`, startup auto-join, and bot voice-state moves are restricted to the listed `{ guildId, channelId }` entries. Set it to an empty array to deny all Discord voice joins. If Discord moves the bot outside the allowlist, OpenClaw leaves that channel and rejoins the configured auto-join target when one is available.
|
||||
- `voice.daveEncryption` and `voice.decryptionFailureTolerance` pass through to `@discordjs/voice` join options.
|
||||
- `@discordjs/voice` defaults are `daveEncryption=true` and `decryptionFailureTolerance=24` if unset.
|
||||
- OpenClaw defaults to the pure-JS `opusscript` decoder for Discord voice receive. The optional native `@discordjs/opus` package is ignored by the repo pnpm install policy so normal installs, Docker lanes, and unrelated tests do not compile a native addon. Dedicated voice-performance hosts can opt in with `OPENCLAW_DISCORD_OPUS_DECODER=native` after installing the native addon.
|
||||
- OpenClaw uses the bundled `libopus-wasm` codec for Discord voice receive and realtime raw PCM playback. It ships a pinned libopus WebAssembly build and does not require native opus addons.
|
||||
- `voice.connectTimeoutMs` controls the initial `@discordjs/voice` Ready wait for `/vc join` and auto-join attempts. Default: `30000`.
|
||||
- `voice.reconnectGraceMs` controls how long OpenClaw waits for a disconnected voice session to begin reconnecting before destroying it. Default: `15000`.
|
||||
- In `stt-tts` mode, voice playback does not stop just because another user starts speaking. To avoid feedback loops, OpenClaw ignores new voice capture while TTS is playing; speak after playback finishes for the next turn. Realtime modes forward speaker starts as barge-in signals to the realtime provider.
|
||||
- In realtime modes, echo from speakers into an open mic can look like barge-in and interrupt playback. For echo-heavy Discord rooms, set `voice.realtime.providers.openai.interruptResponseOnInputAudio: false` to keep OpenAI from auto-interrupting on input audio. Add `voice.realtime.bargeIn: true` if you still want Discord speaker-start events to interrupt active playback. The OpenAI realtime bridge ignores playback truncations shorter than `voice.realtime.minBargeInAudioEndMs` as likely echo/noise and logs them as skipped instead of clearing Discord playback.
|
||||
- `voice.captureSilenceGraceMs` controls how long OpenClaw waits after Discord reports a speaker has stopped before finalizing that audio segment for STT. Default: `2500`; raise this if Discord splits normal pauses into choppy partial transcripts.
|
||||
- `voice.captureSilenceGraceMs` controls how long OpenClaw waits after Discord reports a speaker has stopped before finalizing that audio segment for STT. Default: `2000`; raise this if Discord splits normal pauses into choppy partial transcripts.
|
||||
- When ElevenLabs is the selected TTS provider, Discord voice playback uses streaming TTS and starts from the provider response stream. Providers without streaming support fall back to the synthesized temp-file path.
|
||||
- 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.
|
||||
@@ -1301,22 +1301,11 @@ Choose between the join modes:
|
||||
- Use `autoJoin` for fixed-room bots that should be present even when no tracked user is in voice.
|
||||
- Use `/vc join` for one-off joins or rooms where automatic voice presence would be surprising.
|
||||
|
||||
Native opus setup for source checkouts:
|
||||
Discord voice codec:
|
||||
|
||||
```bash
|
||||
pnpm install
|
||||
mise exec node@22 -- pnpm discord:opus:install
|
||||
```
|
||||
|
||||
Use Node 22 for the gateway when you want the upstream macOS arm64 prebuilt native addon. If you use another Node runtime, the opt-in installer may need a local `node-gyp` source-build toolchain.
|
||||
|
||||
After installing the native addon, start the Gateway with:
|
||||
|
||||
```bash
|
||||
OPENCLAW_DISCORD_OPUS_DECODER=native pnpm gateway:watch
|
||||
```
|
||||
|
||||
Verbose voice logs should show `discord voice: opus decoder: @discordjs/opus`. Without the env opt-in, or if the native addon is missing or cannot load on the host, OpenClaw logs `discord voice: opus decoder: opusscript` and keeps receiving voice through the pure-JS fallback.
|
||||
- Voice receive logs show `discord voice: opus decoder: libopus-wasm`.
|
||||
- Realtime playback encodes raw 48 kHz stereo PCM to Opus with the same bundled `libopus-wasm` package before handing packets to `@discordjs/voice`.
|
||||
- File and provider-stream playback transcodes to raw 48 kHz stereo PCM with ffmpeg, then uses `libopus-wasm` for the Opus packet stream sent to Discord.
|
||||
|
||||
STT plus TTS pipeline:
|
||||
|
||||
|
||||
@@ -442,6 +442,13 @@ See [ACP Agents](/tools/acp-agents) for shared ACP binding behavior.
|
||||
Each account can override fields such as `cliPath`, `dbPath`, `allowFrom`, `groupPolicy`, `mediaMaxMb`, history settings, and attachment root allowlists.
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Direct-message history">
|
||||
Set `channels.imessage.dmHistoryLimit` to seed new direct-message sessions with recent decoded `imsg` history for that conversation. Use `channels.imessage.dms["<sender>"].historyLimit` for per-sender overrides, including `0` to disable history for a sender.
|
||||
|
||||
iMessage DM history is fetched on demand from `imsg`. Leaving `dmHistoryLimit` unset disables global DM history seeding, but a positive per-sender `channels.imessage.dms["<sender>"].historyLimit` still enables seeding for that sender.
|
||||
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
## Media, chunking, and delivery targets
|
||||
|
||||
@@ -306,6 +306,21 @@ Config:
|
||||
- `minimal`/`extensive` enables agent reactions and sets the guidance level.
|
||||
- Per-account overrides: `channels.signal.accounts.<id>.actions.reactions`, `channels.signal.accounts.<id>.reactionLevel`.
|
||||
|
||||
## Approval reactions
|
||||
|
||||
Signal exec and plugin approval prompts use the top-level `approvals.exec` and
|
||||
`approvals.plugin` routing blocks. Signal does not have a
|
||||
`channels.signal.execApprovals` block.
|
||||
|
||||
- `👍` approves once.
|
||||
- `👎` denies.
|
||||
- Use `/approve <id> allow-always` when a request offers persistent approval.
|
||||
|
||||
Approval reaction resolution requires explicit Signal approvers from
|
||||
`channels.signal.allowFrom`, `channels.signal.defaultTo`, or the matching account-level fields.
|
||||
Direct same-chat exec approval prompts can still suppress the duplicate local `/approve` fallback
|
||||
without explicit approvers; no-approver group approvals keep the local fallback visible.
|
||||
|
||||
## Delivery targets (CLI/cron)
|
||||
|
||||
- DMs: `signal:+15551234567` (or plain E.164).
|
||||
|
||||
@@ -522,6 +522,7 @@ Ack reactions are gated by `reactionLevel` — they are suppressed when `reactio
|
||||
Behavior notes:
|
||||
|
||||
- sent immediately after inbound is accepted (pre-reply)
|
||||
- if `ackReaction` is present without `emoji`, WhatsApp uses the routed agent's identity emoji, falling back to "👀"; omit `ackReaction` or set `emoji: ""` to send no ack reaction
|
||||
- failures are logged but do not block normal reply delivery
|
||||
- group mode `mentions` reacts on mention-triggered turns; group activation `always` acts as bypass for this check
|
||||
- WhatsApp uses `channels.whatsapp.ackReaction` (legacy `messages.ackReaction` is not used here)
|
||||
@@ -548,6 +549,7 @@ Set `messages.statusReactions.enabled: true` to let WhatsApp replace the ack rea
|
||||
Behavior notes:
|
||||
|
||||
- `channels.whatsapp.ackReaction` still controls whether status reactions are eligible for direct messages and groups.
|
||||
- The queued status reaction uses the same effective ack emoji as plain ack reactions.
|
||||
- WhatsApp has one bot reaction slot per message, so lifecycle updates replace the current reaction in place.
|
||||
- `messages.removeAckAfterReply: true` clears the final status reaction after the configured done/error hold.
|
||||
- Tool emoji categories include `tool`, `coding`, `web`, `deploy`, `build`, and `concierge`.
|
||||
|
||||
16
docs/ci.md
@@ -43,7 +43,7 @@ OpenClaw CI runs on every push to `main` and every pull request. The `preflight`
|
||||
|
||||
GitHub may mark superseded jobs as `cancelled` when a newer push lands on the same PR or `main` ref. Treat that as CI noise unless the newest run for the same ref is also failing. Matrix jobs use `fail-fast: false`, and `build-artifacts` reports embedded channel, core-support-boundary, and gateway-watch failures directly instead of queuing tiny verifier jobs. The automatic CI concurrency key is versioned (`CI-v7-*`) so a GitHub-side zombie in an old queue group cannot indefinitely block newer main runs. Manual full-suite runs use `CI-manual-v1-*` and do not cancel in-progress runs.
|
||||
|
||||
The `ci-timings-summary` job uploads a compact `ci-timings-summary` artifact for each non-draft CI run. It records wall time, queue time, slowest jobs, and failed jobs for the current run, so CI health checks do not need to scrape the full Actions payload repeatedly.
|
||||
The `ci-timings-summary` job uploads a compact `ci-timings-summary` artifact for each non-draft CI run. It records wall time, queue time, slowest jobs, and failed jobs for the current run, so CI health checks do not need to scrape the full Actions payload repeatedly. The `build-artifacts` job also runs the blocking startup-memory smoke and uploads a `startup-memory` artifact with per-command RSS values for `--help`, `status --json`, and `gateway status`.
|
||||
|
||||
## Real behavior proof
|
||||
|
||||
@@ -157,6 +157,8 @@ node scripts/ci-run-timings.mjs --latest-main # ignore issue/comment noise and c
|
||||
node scripts/ci-run-timings.mjs --recent 10 # compare recent successful main CI runs
|
||||
pnpm test:perf:groups --full-suite --allow-failures --output .artifacts/test-perf/baseline-before.json
|
||||
pnpm test:perf:groups:compare .artifacts/test-perf/baseline-before.json .artifacts/test-perf/after-agent.json
|
||||
pnpm test:startup:memory
|
||||
pnpm test:extensions:memory -- --json .artifacts/openclaw-performance/source/mock-provider/extension-memory.json
|
||||
pnpm perf:kova:summary --report .artifacts/kova/reports/mock-provider/report.json --output .artifacts/kova/summary.md
|
||||
```
|
||||
|
||||
@@ -178,7 +180,7 @@ The workflow installs OCM from a pinned release and Kova from `openclaw/Kova` at
|
||||
- `mock-deep-profile`: CPU/heap/trace profiling for startup, gateway, and agent-turn hotspots.
|
||||
- `live-openai-candidate`: a real OpenAI `openai/gpt-5.5` agent turn, skipped when `OPENAI_API_KEY` is unavailable.
|
||||
|
||||
The mock-provider lane also runs OpenClaw-native source probes after the Kova pass: gateway boot timing and memory across default, hook, and 50-plugin startup cases; repeated mock-OpenAI `channel-chat-baseline` hello loops; and CLI startup commands against the booted gateway. The source probe Markdown summary lives at `source/index.md` in the report bundle, with raw JSON beside it.
|
||||
The mock-provider lane also runs OpenClaw-native source probes after the Kova pass: gateway boot timing and memory across default, hook, and 50-plugin startup cases; bundled plugin import RSS, repeated mock-OpenAI `channel-chat-baseline` hello loops, and CLI startup commands against the booted gateway. When the previous published mock-provider source report is available for the tested ref, the source summary compares current RSS and heap values against that baseline and marks large RSS increases as `watch`. The source probe Markdown summary lives at `source/index.md` in the report bundle, with raw JSON beside it.
|
||||
|
||||
Every lane uploads GitHub artifacts. When `CLAWGRIT_REPORTS_TOKEN` is configured, the workflow also commits `report.json`, `report.md`, bundles, `index.md`, and source-probe artifacts into `openclaw/clawgrit-reports` under `openclaw-performance/<tested-ref>/<run-id>-<attempt>/<lane>/`. The current tested-ref pointer is written as `openclaw-performance/<tested-ref>/latest-<lane>.json`.
|
||||
|
||||
@@ -503,7 +505,7 @@ The `Docs Agent` workflow is an event-driven Codex maintenance lane for keeping
|
||||
|
||||
### Test Performance Agent
|
||||
|
||||
The `Test Performance Agent` workflow is an event-driven Codex maintenance lane for slow tests. It has no pure schedule: a successful non-bot push CI run on `main` can trigger it, but it skips if another workflow-run invocation already ran or is running that UTC day. Manual dispatch bypasses that daily activity gate. The lane builds a full-suite grouped Vitest performance report, lets Codex make only small coverage-preserving test performance fixes instead of broad refactors, then reruns the full-suite report and rejects changes that reduce the passing baseline test count. If the baseline has failing tests, Codex may fix only obvious failures and the after-agent full-suite report must pass before anything is committed. When `main` advances before the bot push lands, the lane rebases the validated patch, reruns `pnpm check:changed`, and retries the push; conflicting stale patches are skipped. It uses GitHub-hosted Ubuntu so the Codex action can keep the same drop-sudo safety posture as the docs agent.
|
||||
The `Test Performance Agent` workflow is an event-driven Codex maintenance lane for slow tests. It has no pure schedule: a successful non-bot push CI run on `main` can trigger it, but it skips if another workflow-run invocation already ran or is running that UTC day. Manual dispatch bypasses that daily activity gate. The lane builds a full-suite grouped Vitest performance report, lets Codex make only small coverage-preserving test performance fixes instead of broad refactors, then reruns the full-suite report and rejects changes that reduce the passing baseline test count. The grouped report records per-config wall time and max RSS on Linux and macOS, so the before/after comparison surfaces test memory deltas beside duration deltas. If the baseline has failing tests, Codex may fix only obvious failures and the after-agent full-suite report must pass before anything is committed. When `main` advances before the bot push lands, the lane rebases the validated patch, reruns `pnpm check:changed`, and retries the push; conflicting stale patches are skipped. It uses GitHub-hosted Ubuntu so the Codex action can keep the same drop-sudo safety posture as the docs agent.
|
||||
|
||||
### Duplicate PRs After Merge
|
||||
|
||||
@@ -574,7 +576,7 @@ pnpm crabbox:run -- --provider blacksmith-testbox \
|
||||
--ttl 240m \
|
||||
--timing-json \
|
||||
--shell -- \
|
||||
"env CI=1 NODE_OPTIONS=--max-old-space-size=4096 OPENCLAW_TEST_PROJECTS_PARALLEL=6 OPENCLAW_VITEST_MAX_WORKERS=1 OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS=900000 pnpm check:changed"
|
||||
"corepack pnpm check:changed"
|
||||
```
|
||||
|
||||
Focused test rerun:
|
||||
@@ -589,7 +591,7 @@ pnpm crabbox:run -- --provider blacksmith-testbox \
|
||||
--ttl 240m \
|
||||
--timing-json \
|
||||
--shell -- \
|
||||
"env CI=1 NODE_OPTIONS=--max-old-space-size=4096 OPENCLAW_VITEST_MAX_WORKERS=1 OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS=900000 pnpm test <path-or-filter>"
|
||||
"corepack pnpm test <path-or-filter>"
|
||||
```
|
||||
|
||||
Full suite:
|
||||
@@ -604,7 +606,7 @@ pnpm crabbox:run -- --provider blacksmith-testbox \
|
||||
--ttl 240m \
|
||||
--timing-json \
|
||||
--shell -- \
|
||||
"env CI=1 NODE_OPTIONS=--max-old-space-size=4096 OPENCLAW_TEST_PROJECTS_PARALLEL=6 OPENCLAW_VITEST_MAX_WORKERS=1 OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS=900000 pnpm test"
|
||||
"corepack pnpm test"
|
||||
```
|
||||
|
||||
Read the final JSON summary. The useful fields are `provider`, `leaseId`, `syncDelegated`, `exitCode`, `commandMs`, and `totalMs`. One-shot Blacksmith-backed Crabbox runs should stop the Testbox automatically; if a run is interrupted or cleanup is unclear, inspect live boxes and stop only the boxes you created:
|
||||
@@ -639,7 +641,7 @@ Escalate to owned Crabbox capacity only when Blacksmith is down, quota-limited,
|
||||
CRABBOX_CAPACITY_REGIONS=eu-west-1,eu-west-2,eu-central-1,us-east-1,us-west-2 \
|
||||
pnpm crabbox:warmup -- --provider aws --class standard --market on-demand --idle-timeout 90m
|
||||
pnpm crabbox:hydrate -- --id <cbx_id-or-slug>
|
||||
pnpm crabbox:run -- --id <cbx_id-or-slug> --timing-json --shell -- "env NODE_OPTIONS=--max-old-space-size=4096 OPENCLAW_TEST_PROJECTS_PARALLEL=6 OPENCLAW_VITEST_MAX_WORKERS=1 OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS=900000 pnpm check:changed"
|
||||
pnpm crabbox:run -- --id <cbx_id-or-slug> --timing-json --shell -- "pnpm check:changed"
|
||||
pnpm crabbox:stop -- <cbx_id-or-slug>
|
||||
```
|
||||
|
||||
|
||||
@@ -30,12 +30,12 @@ Use the setup commands by intent:
|
||||
| Models and inference | [`models`](/cli/models) · [`infer`](/cli/infer) · `capability` (alias for [`infer`](/cli/infer)) · [`memory`](/cli/memory) · [`commitments`](/cli/commitments) · [`wiki`](/cli/wiki) |
|
||||
| Network and nodes | [`directory`](/cli/directory) · [`nodes`](/cli/nodes) · [`devices`](/cli/devices) · [`node`](/cli/node) |
|
||||
| Runtime and sandbox | [`approvals`](/cli/approvals) · `exec-policy` (see [`approvals`](/cli/approvals)) · [`sandbox`](/cli/sandbox) · [`tui`](/cli/tui) · `chat`/`terminal` (aliases for [`tui --local`](/cli/tui)) · [`browser`](/cli/browser) |
|
||||
| Automation | [`cron`](/cli/cron) · [`tasks`](/cli/tasks) · [`hooks`](/cli/hooks) · [`webhooks`](/cli/webhooks) |
|
||||
| Automation | [`cron`](/cli/cron) · [`tasks`](/cli/tasks) · [`hooks`](/cli/hooks) · [`webhooks`](/cli/webhooks) · [`transcripts`](/cli/transcripts) |
|
||||
| Discovery and docs | [`dns`](/cli/dns) · [`docs`](/cli/docs) |
|
||||
| Pairing and channels | [`pairing`](/cli/pairing) · [`qr`](/cli/qr) · [`channels`](/cli/channels) |
|
||||
| Security and plugins | [`security`](/cli/security) · [`secrets`](/cli/secrets) · [`skills`](/cli/skills) · [`plugins`](/cli/plugins) · [`proxy`](/cli/proxy) |
|
||||
| Legacy aliases | [`daemon`](/cli/daemon) (gateway service) · [`clawbot`](/cli/clawbot) (namespace) |
|
||||
| Plugins (optional) | [`meeting-notes`](/cli/meeting-notes) · [`path`](/cli/path) · [`policy`](/cli/policy) · [`voicecall`](/cli/voicecall) (if installed) |
|
||||
| Plugins (optional) | [`path`](/cli/path) · [`policy`](/cli/policy) · [`voicecall`](/cli/voicecall) (if installed) |
|
||||
|
||||
## Global flags
|
||||
|
||||
@@ -128,7 +128,7 @@ openclaw [--dev] [--profile <name>] <command>
|
||||
status
|
||||
index
|
||||
search
|
||||
meeting-notes
|
||||
transcripts
|
||||
list
|
||||
show
|
||||
path
|
||||
|
||||
@@ -172,6 +172,22 @@ present in `policy.jsonc`. The observed state is existing OpenClaw config or
|
||||
workspace metadata; policy reports drift but does not rewrite runtime behavior
|
||||
unless a repair path is explicitly available and enabled.
|
||||
|
||||
Agent-specific policy overlays keep broad `tools.*` and `agents.workspace`
|
||||
posture global, then let named scope blocks add stricter normal policy sections
|
||||
for explicit `agentIds` under `scopes.<scopeName>`. The initial scoped
|
||||
sections are `tools` and `agents.workspace`; sandbox and ingress can use the
|
||||
same container once their evidence is attributable to an agent. Scoped fields
|
||||
carry strictness metadata such as allowlist subset, denylist superset, required
|
||||
boolean, and exact-list semantics so future policy-file conformance can reuse
|
||||
the same rule inventory instead of guessing. The overlay is additive: global
|
||||
claims still run, and a scoped claim can emit its own finding against the same
|
||||
observed config. See [Agent-scoped policy overlays](/plan/policy-agent-scoped-overlays).
|
||||
Every scope present in `policy.jsonc` must be valid and enforceable. Scopes
|
||||
currently require `agentIds`, and that selector supports only `tools.*` and
|
||||
`agents.workspace.*`. If an `agentIds` entry is not present in `agents.list[]`,
|
||||
the scoped rule is evaluated against the inherited global/default posture for
|
||||
that runtime agent id instead of being skipped.
|
||||
|
||||
#### Channels
|
||||
|
||||
| Policy field | Observed state | Use when |
|
||||
@@ -250,6 +266,7 @@ unless a repair path is explicitly available and enabled.
|
||||
| `tools.exec.requireAsk` | `tools.exec.ask` and per-agent exec ask mode | Require approval posture such as `always`. |
|
||||
| `tools.exec.allowHosts` | `tools.exec.host` and per-agent exec host routing | Allow only exec host routing modes such as `sandbox`. |
|
||||
| `tools.elevated.allow` | `tools.elevated.enabled` and per-agent elevated posture | Set to `false` to require elevated tool mode to stay disabled. |
|
||||
| `tools.alsoAllow.expected` | `tools.alsoAllow` and per-agent `tools.alsoAllow` | Require exact `alsoAllow` entries and report missing or unexpected additive tool grants. |
|
||||
| `tools.denyTools` | `tools.deny` and `agents.list[].tools.deny` | Require configured tool deny lists to include tool ids or groups such as `group:runtime` and `group:fs`. |
|
||||
|
||||
Run policy-only checks during authoring:
|
||||
@@ -533,6 +550,8 @@ Policy currently verifies:
|
||||
| `policy/tools-exec-ask-unapproved` | Exec ask mode is outside the policy allowlist. |
|
||||
| `policy/tools-exec-host-unapproved` | Exec host routing is outside the policy allowlist. |
|
||||
| `policy/tools-elevated-enabled` | Elevated tool mode is enabled when policy denies it. |
|
||||
| `policy/tools-also-allow-missing` | A configured `alsoAllow` list is missing an entry required by policy. |
|
||||
| `policy/tools-also-allow-unexpected` | A configured `alsoAllow` list includes an entry not expected by policy. |
|
||||
| `policy/tools-required-deny-missing` | A global or per-agent tool deny list does not include a required denied tool. |
|
||||
| `policy/secrets-unmanaged-provider` | A config SecretRef references a provider not declared under `secrets.providers`. |
|
||||
| `policy/secrets-denied-provider-source` | A config secret provider or SecretRef uses a source denied by policy. |
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
---
|
||||
summary: "CLI reference for `openclaw meeting-notes` (list, show, and locate stored meeting notes)"
|
||||
summary: "CLI reference for `openclaw transcripts` (list, show, and locate stored transcripts)"
|
||||
read_when:
|
||||
- You want to read stored meeting note summaries from the terminal
|
||||
- You need the path to a meeting notes markdown summary
|
||||
- You are debugging the meeting-notes plugin storage layout
|
||||
title: "Meeting Notes CLI"
|
||||
- You want to read stored transcript summaries from the terminal
|
||||
- You need the path to a transcripts markdown summary
|
||||
- You are debugging the core transcripts storage layout
|
||||
title: "Transcripts CLI"
|
||||
---
|
||||
|
||||
# `openclaw meeting-notes`
|
||||
# `openclaw transcripts`
|
||||
|
||||
Inspect meeting notes written by the external `meeting-notes` plugin. This CLI
|
||||
is read-only and is available when that plugin is installed or loaded from
|
||||
source. Capture, import, and summarization are owned by the `meeting_notes`
|
||||
agent tool and by configured auto-start sources.
|
||||
Inspect transcripts written by OpenClaw's core `transcripts` tool. This CLI is
|
||||
read-only; capture, import, and summarization are owned by the agent tool and
|
||||
configured auto-start sources.
|
||||
|
||||
Use the CLI when you want to find yesterday's notes, open the Markdown file in
|
||||
an editor, feed a transcript to another tool, or debug where a session landed on
|
||||
@@ -21,7 +20,7 @@ disk. It does not start or stop capture.
|
||||
Artifacts live under the OpenClaw state directory:
|
||||
|
||||
```text
|
||||
$OPENCLAW_STATE_DIR/meeting-notes/YYYY-MM-DD/<session>/
|
||||
$OPENCLAW_STATE_DIR/transcripts/YYYY-MM-DD/<session>/
|
||||
metadata.json
|
||||
transcript.jsonl
|
||||
summary.json
|
||||
@@ -35,17 +34,17 @@ session directory is a safe filesystem segment derived from the session id.
|
||||
## Commands
|
||||
|
||||
```bash
|
||||
openclaw meeting-notes list
|
||||
openclaw meeting-notes show <session>
|
||||
openclaw meeting-notes show YYYY-MM-DD/<session>
|
||||
openclaw meeting-notes path <session>
|
||||
openclaw meeting-notes path YYYY-MM-DD/<session>
|
||||
openclaw meeting-notes path <session> --dir
|
||||
openclaw meeting-notes path <session> --metadata
|
||||
openclaw meeting-notes path <session> --transcript
|
||||
openclaw meeting-notes list --json
|
||||
openclaw meeting-notes show <session> --json
|
||||
openclaw meeting-notes path <session> --json
|
||||
openclaw transcripts list
|
||||
openclaw transcripts show <session>
|
||||
openclaw transcripts show YYYY-MM-DD/<session>
|
||||
openclaw transcripts path <session>
|
||||
openclaw transcripts path YYYY-MM-DD/<session>
|
||||
openclaw transcripts path <session> --dir
|
||||
openclaw transcripts path <session> --metadata
|
||||
openclaw transcripts path <session> --transcript
|
||||
openclaw transcripts list --json
|
||||
openclaw transcripts show <session> --json
|
||||
openclaw transcripts path <session> --json
|
||||
```
|
||||
|
||||
- `list`: list stored sessions, date-qualified selector, start time, title, and `summary.md` path.
|
||||
@@ -57,7 +56,7 @@ openclaw meeting-notes path <session> --json
|
||||
- `--json`: print machine-readable output.
|
||||
|
||||
When a human session id repeats across days, use the date-qualified selector
|
||||
from `list`, for example `openclaw meeting-notes show 2026-05-22/standup`.
|
||||
from `list`, for example `openclaw transcripts show 2026-05-22/standup`.
|
||||
Default session ids include a timestamp and random suffix; configure fixed
|
||||
session ids only when they are unique within the day.
|
||||
|
||||
@@ -66,7 +65,7 @@ session ids only when they are unique within the day.
|
||||
`list` prints one session per line:
|
||||
|
||||
```text
|
||||
2026-05-22/standup 2026-05-22T09:00:00.000Z Weekly standup /Users/alex/.openclaw/meeting-notes/2026-05-22/standup/summary.md
|
||||
2026-05-22/standup 2026-05-22T09:00:00.000Z Weekly standup /Users/alex/.openclaw/transcripts/2026-05-22/standup/summary.md
|
||||
```
|
||||
|
||||
The output is tab-separated. The columns are selector, start time, title, and
|
||||
@@ -91,13 +90,13 @@ and whether that file exists.
|
||||
|
||||
## Many meetings per day
|
||||
|
||||
Meeting Notes groups sessions by date, then by session id. Ten meetings on one
|
||||
Transcripts groups sessions by date, then by session id. Ten meetings on one
|
||||
day become ten sibling folders:
|
||||
|
||||
```text
|
||||
~/.openclaw/meeting-notes/2026-05-22/
|
||||
meeting-2026-05-22T09-00-00-000Z-a1b2c3d4/
|
||||
meeting-2026-05-22T10-30-00-000Z-b2c3d4e5/
|
||||
~/.openclaw/transcripts/2026-05-22/
|
||||
transcript-2026-05-22T09-00-00-000Z-a1b2c3d4/
|
||||
transcript-2026-05-22T10-30-00-000Z-b2c3d4e5/
|
||||
standup/
|
||||
```
|
||||
|
||||
@@ -112,7 +111,41 @@ write `summary.md` immediately after import. A session can still appear in
|
||||
or metadata was written before any utterances arrived.
|
||||
|
||||
Use `path <session> --transcript` to inspect the append-only transcript, and use
|
||||
the `meeting_notes` tool action `summarize` to regenerate the Markdown summary.
|
||||
the `transcripts` tool action `summarize` to regenerate the Markdown summary.
|
||||
|
||||
See [Meeting Notes](/plugins/meeting-notes) for configuration, auto-start, and
|
||||
source-provider details.
|
||||
## Configuration
|
||||
|
||||
Transcript capture is opt-in because live sources can join and record meeting
|
||||
audio. Enable the tool with top-level `transcripts.enabled`:
|
||||
|
||||
```json
|
||||
{
|
||||
"transcripts": {
|
||||
"enabled": true,
|
||||
"maxUtterances": 2000
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Configure auto-start sources with `transcripts.autoStart` in `openclaw.json`.
|
||||
Each entry is enabled by being present; omit an entry to disable that source.
|
||||
|
||||
```json
|
||||
{
|
||||
"transcripts": {
|
||||
"enabled": true,
|
||||
"autoStart": [
|
||||
{
|
||||
"providerId": "discord-voice",
|
||||
"guildId": "1234567890",
|
||||
"channelId": "2345678901"
|
||||
},
|
||||
{
|
||||
"providerId": "slack-huddle",
|
||||
"accountId": "workspace",
|
||||
"channelId": "C123"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -106,14 +106,14 @@ The byte guard requires `truncateAfterCompaction: true`. Without transcript rota
|
||||
|
||||
### Successor transcripts
|
||||
|
||||
When `agents.defaults.compaction.truncateAfterCompaction` is enabled, OpenClaw does not rewrite the existing transcript in place. It creates a new active successor transcript from the compaction summary, preserved state, and unsummarized tail, then keeps the previous JSONL as the archived checkpoint source.
|
||||
When `agents.defaults.compaction.truncateAfterCompaction` is enabled, OpenClaw does not rewrite the existing transcript in place. It creates a new active successor transcript from the compaction summary, preserved state, and unsummarized tail, then records checkpoint metadata that points branch/restore flows at that compacted successor.
|
||||
Successor transcripts also drop exact duplicate long user turns that arrive
|
||||
inside a short retry window, so channel retry storms are not carried into the
|
||||
next active transcript after compaction.
|
||||
|
||||
Pre-compaction checkpoints are retained only while they stay below OpenClaw's
|
||||
checkpoint size cap; oversized active transcripts still compact, but OpenClaw
|
||||
skips the large debug snapshot instead of doubling disk usage.
|
||||
OpenClaw no longer writes separate `.checkpoint.*.jsonl` copies for new
|
||||
compactions. Existing legacy checkpoint files can still be used while referenced
|
||||
and are pruned by normal session cleanup.
|
||||
|
||||
### Compaction notices
|
||||
|
||||
|
||||
@@ -99,7 +99,7 @@ OpenClaw ships with the pi-ai catalog. These providers require **no** `models.pr
|
||||
- Use `params.serviceTier` when you want an explicit tier instead of the shared `/fast` toggle
|
||||
- Hidden OpenClaw attribution headers (`originator`, `version`, `User-Agent`) apply only on native OpenAI traffic to `api.openai.com`, not generic OpenAI-compatible proxies
|
||||
- Native OpenAI routes also keep Responses `store`, prompt-cache hints, and OpenAI reasoning-compat payload shaping; proxy routes do not
|
||||
- `openai/gpt-5.3-codex-spark` is intentionally suppressed in OpenClaw because live OpenAI API requests reject it and the current Codex catalog does not expose it
|
||||
- `openai/gpt-5.3-codex-spark` is intentionally suppressed in OpenClaw because live OpenAI API requests reject it; use `openai-codex/gpt-5.3-codex-spark` only when the Codex catalog exposes it for your account
|
||||
|
||||
```json5
|
||||
{
|
||||
@@ -150,6 +150,7 @@ Anthropic staff told us OpenClaw-style Claude CLI usage is allowed again, so Ope
|
||||
- For the common subscription plus native Codex runtime route, sign in with `openai-codex` auth but configure `openai/gpt-5.5`; OpenAI agent turns select Codex by default.
|
||||
- Use provider/model `agentRuntime.id: "pi"` only when you want a compatibility route through PI; otherwise keep `openai/gpt-5.5` on the default Codex harness.
|
||||
- `openai-codex/gpt-*` refs remain a legacy PI route. Prefer `openai/gpt-5.5` on the native Codex runtime for new agent config, and run `openclaw doctor --fix` when you want to migrate old `openai-codex/*` refs to canonical `openai/*` refs.
|
||||
- `openai-codex/gpt-5.3-codex-spark` remains available only through Codex catalog discovery when the signed-in account advertises it; direct `openai/*` and Azure refs for that model stay suppressed.
|
||||
|
||||
```json5
|
||||
{
|
||||
|
||||
@@ -93,13 +93,26 @@ That script starts a local OTLP/HTTP receiver, runs the `otel-trace-smoke` QA
|
||||
scenario with the `diagnostics-otel` plugin enabled, then asserts traces,
|
||||
metrics, and logs are exported. It decodes the exported protobuf trace spans
|
||||
and checks the release-critical shape:
|
||||
`openclaw.run`, `openclaw.harness.run`, `openclaw.model.call`,
|
||||
`openclaw.context.assembled`, and `openclaw.message.delivery` must be present;
|
||||
`openclaw.run`, `openclaw.harness.run`, a latest GenAI semantic-convention
|
||||
model-call span, `openclaw.context.assembled`, and `openclaw.message.delivery`
|
||||
must be present. The smoke forces
|
||||
`OTEL_SEMCONV_STABILITY_OPT_IN=gen_ai_latest_experimental`, so the model-call
|
||||
span must use the `{gen_ai.operation.name} {gen_ai.request.model}` name;
|
||||
model calls must not export `StreamAbandoned` on successful turns; raw diagnostic IDs and
|
||||
`openclaw.content.*` attributes must stay out of the trace. The raw OTLP
|
||||
payloads must not contain the prompt sentinel, response sentinel, or QA session
|
||||
key. It writes `otel-smoke-summary.json` next to the QA suite artifacts.
|
||||
|
||||
For a collector-backed OpenTelemetry smoke, run:
|
||||
|
||||
```bash
|
||||
pnpm qa:otel:collector-smoke
|
||||
```
|
||||
|
||||
That lane puts a real OpenTelemetry Collector Docker container in front of the
|
||||
same local receiver. Use it when changing endpoint wiring, collector
|
||||
compatibility, or OTLP export behavior that the in-process receiver could mask.
|
||||
|
||||
For the protected Prometheus scrape smoke, run:
|
||||
|
||||
```bash
|
||||
@@ -118,6 +131,13 @@ To run both observability smokes back to back, use:
|
||||
pnpm qa:observability:smoke
|
||||
```
|
||||
|
||||
For the collector-backed OpenTelemetry lane plus the protected Prometheus scrape
|
||||
smoke, use:
|
||||
|
||||
```bash
|
||||
pnpm qa:observability:collector-smoke
|
||||
```
|
||||
|
||||
Observability QA stays source-checkout only. The npm tarball intentionally omits
|
||||
QA Lab, so package Docker release lanes do not run `qa` commands. Use
|
||||
`pnpm qa:otel:smoke`, `pnpm qa:prometheus:smoke`, or
|
||||
|
||||
@@ -49,10 +49,12 @@ effective tool list.
|
||||
token counts, and timestamps. Filter by kind (`main`, `group`, `cron`, `hook`,
|
||||
`node`), exact `label`, exact `agentId`, search text, or recency
|
||||
(`activeMinutes`). When you need mailbox-style triage, it can also ask for a
|
||||
visibility-scoped derived title, a last-message preview snippet, or bounded
|
||||
recent messages on each row. Derived titles and previews are produced only for
|
||||
sessions the caller can already see under the configured session tool
|
||||
visibility policy, so unrelated sessions stay hidden.
|
||||
visibility-scoped derived title, a last-message preview snippet, or bounded recent
|
||||
messages on each row. Derived titles and previews are produced only for sessions
|
||||
the caller can already see under the configured session tool visibility policy, so
|
||||
unrelated sessions stay hidden. When visibility is restricted, `sessions_list`
|
||||
returns optional `visibility` metadata showing the effective mode and a warning that
|
||||
results may be scope-limited.
|
||||
|
||||
`sessions_history` fetches the conversation transcript for a specific session.
|
||||
By default, tool results are excluded -- pass `includeTools: true` to see them.
|
||||
|
||||
@@ -50,6 +50,50 @@ Disable all flags:
|
||||
OPENCLAW_DIAGNOSTICS=0
|
||||
```
|
||||
|
||||
`OPENCLAW_DIAGNOSTICS=0` is a process-level disable override: it disables
|
||||
flags from both env and config for that process.
|
||||
|
||||
## Profiling flags
|
||||
|
||||
Profiler flags enable targeted timing spans without raising global logging
|
||||
levels. They are disabled by default.
|
||||
|
||||
Enable all profiler-gated spans for one gateway run:
|
||||
|
||||
```bash
|
||||
OPENCLAW_DIAGNOSTICS=profiler openclaw gateway run
|
||||
```
|
||||
|
||||
Enable only reply-dispatch profiler spans:
|
||||
|
||||
```bash
|
||||
OPENCLAW_DIAGNOSTICS=reply.profiler openclaw gateway run
|
||||
```
|
||||
|
||||
Enable only Codex app-server startup/tool/thread profiler spans:
|
||||
|
||||
```bash
|
||||
OPENCLAW_DIAGNOSTICS=codex.profiler openclaw gateway run
|
||||
```
|
||||
|
||||
Enable profiler flags from config:
|
||||
|
||||
```json
|
||||
{
|
||||
"diagnostics": {
|
||||
"flags": ["reply.profiler", "codex.profiler"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Restart the gateway after changing config flags. To disable a profiler flag,
|
||||
remove it from `diagnostics.flags` and restart. To temporarily disable every
|
||||
diagnostics flag even when config enables profiler flags, start the process with:
|
||||
|
||||
```bash
|
||||
OPENCLAW_DIAGNOSTICS=0 openclaw gateway run
|
||||
```
|
||||
|
||||
## Timeline artifacts
|
||||
|
||||
The `timeline` flag writes structured startup and runtime timing events for
|
||||
|
||||
@@ -1212,7 +1212,6 @@
|
||||
"plugins/codex-native-plugins",
|
||||
"plugins/codex-computer-use",
|
||||
"plugins/google-meet",
|
||||
"plugins/meeting-notes",
|
||||
"plugins/webhooks",
|
||||
"plugins/admin-http-rpc",
|
||||
"plugins/voice-call",
|
||||
|
||||
@@ -170,13 +170,15 @@ the prompt. Skill env/API key overrides are still applied by OpenClaw to the
|
||||
child process environment for the run.
|
||||
|
||||
Claude CLI also has its own noninteractive permission mode. OpenClaw maps that
|
||||
to the existing exec policy instead of adding Claude-specific config: when the
|
||||
effective requested exec policy is YOLO (`tools.exec.security: "full"` and
|
||||
`tools.exec.ask: "off"`), OpenClaw adds `--permission-mode bypassPermissions`.
|
||||
Per-agent `agents.list[].tools.exec` settings override global `tools.exec` for
|
||||
that agent. To force a different Claude mode, set explicit raw backend args
|
||||
such as `--permission-mode default` or `--permission-mode acceptEdits` under
|
||||
`agents.defaults.cliBackends.claude-cli.args` and matching `resumeArgs`.
|
||||
to the existing exec policy instead of adding Claude-specific policy config.
|
||||
For OpenClaw-managed Claude live sessions, the effective OpenClaw exec policy is
|
||||
authoritative: YOLO (`tools.exec.security: "full"` and
|
||||
`tools.exec.ask: "off"`) launches Claude with
|
||||
`--permission-mode bypassPermissions`, while restrictive effective exec policy
|
||||
launches Claude with `--permission-mode default`. Per-agent
|
||||
`agents.list[].tools.exec` settings override global `tools.exec` for that
|
||||
agent. Raw Claude backend args may still include `--permission-mode`, but live
|
||||
Claude launches normalize that flag to match the effective OpenClaw exec policy.
|
||||
|
||||
The bundled Anthropic `claude-cli` backend also maps OpenClaw `/think` levels
|
||||
to Claude Code's native `--effort` flag for non-off levels. `minimal` and
|
||||
|
||||
@@ -235,7 +235,6 @@ Shared defaults for bounded runtime context surfaces.
|
||||
contextLimits: {
|
||||
memoryGetMaxChars: 12000,
|
||||
memoryGetDefaultLines: 120,
|
||||
toolResultMaxChars: 16000,
|
||||
postCompactionMaxChars: 1800,
|
||||
},
|
||||
},
|
||||
@@ -247,8 +246,12 @@ Shared defaults for bounded runtime context surfaces.
|
||||
metadata and continuation notice are added.
|
||||
- `memoryGetDefaultLines`: default `memory_get` line window when `lines` is
|
||||
omitted.
|
||||
- `toolResultMaxChars`: live tool-result cap used for persisted results and
|
||||
overflow recovery.
|
||||
- `toolResultMaxChars`: advanced live tool-result ceiling used for persisted
|
||||
results and overflow recovery. Leave unset for the model-context auto cap:
|
||||
`16000` chars below 100K tokens, `32000` chars at 100K+ tokens, and `64000`
|
||||
chars at 200K+ tokens. The effective cap is still limited to about 30% of the
|
||||
model context window. `openclaw doctor --deep` prints the effective cap, and
|
||||
doctor warns only when an explicit override is stale or has no effect.
|
||||
- `postCompactionMaxChars`: AGENTS.md excerpt cap used during post-compaction
|
||||
refresh injection.
|
||||
|
||||
@@ -263,7 +266,6 @@ from `agents.defaults.contextLimits`.
|
||||
defaults: {
|
||||
contextLimits: {
|
||||
memoryGetMaxChars: 12000,
|
||||
toolResultMaxChars: 16000,
|
||||
},
|
||||
},
|
||||
list: [
|
||||
@@ -271,7 +273,7 @@ from `agents.defaults.contextLimits`.
|
||||
id: "tiny-local",
|
||||
contextLimits: {
|
||||
memoryGetMaxChars: 6000,
|
||||
toolResultMaxChars: 8000,
|
||||
toolResultMaxChars: 8000, // advanced ceiling for this agent
|
||||
},
|
||||
},
|
||||
],
|
||||
@@ -503,16 +505,16 @@ Time format in system prompt. Default: `auto` (OS preference).
|
||||
|
||||
**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` |
|
||||
| `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` |
|
||||
|
||||
Your configured aliases always win over defaults.
|
||||
|
||||
@@ -575,7 +577,7 @@ Replace the entire OpenClaw-assembled system prompt with a fixed string. Set at
|
||||
|
||||
### `agents.defaults.promptOverlays`
|
||||
|
||||
Provider-independent prompt overlays applied by model family on OpenClaw-assembled prompt surfaces. GPT-5-family model ids receive the shared behavior contract across PI/provider routes; `personality` controls only the friendly interaction-style layer. Native Codex app-server routes keep Codex-owned base/model/personality instructions instead of this OpenClaw GPT-5 overlay.
|
||||
Provider-independent prompt overlays applied by model family on OpenClaw-assembled prompt surfaces. GPT-5-family model ids receive the shared behavior contract across PI/provider routes; `personality` controls only the friendly interaction-style layer. Native Codex app-server routes keep Codex-owned base/model instructions instead of this OpenClaw GPT-5 overlay, and OpenClaw disables Codex's built-in personality for native threads.
|
||||
|
||||
```json5
|
||||
{
|
||||
|
||||
@@ -357,6 +357,9 @@ Default: `tree` (current session + sessions spawned by it, such as subagents).
|
||||
- `agent`: any session belonging to the current agent id (can include other users if you run per-sender sessions under the same agent id).
|
||||
- `all`: any session. Cross-agent targeting still requires `tools.agentToAgent`.
|
||||
- Sandbox clamp: when the current session is sandboxed and `agents.defaults.sandbox.sessionToolsVisibility="spawned"`, visibility is forced to `tree` even if `tools.sessions.visibility="all"`.
|
||||
- When not `all`, `sessions_list` includes a compact `visibility` field
|
||||
describing the effective mode and a warning that some sessions may be
|
||||
omitted outside the current scope.
|
||||
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
@@ -385,7 +385,7 @@ Save to `~/.openclaw/openclaw.json` and you can DM the bot from that number.
|
||||
cron: {
|
||||
enabled: true,
|
||||
store: "~/.openclaw/cron/cron.json",
|
||||
maxConcurrentRuns: 2, // cron dispatch + isolated cron agent-turn execution
|
||||
maxConcurrentRuns: 8, // default; cron dispatch + isolated cron agent-turn execution
|
||||
sessionRetention: "24h",
|
||||
runLog: {
|
||||
maxBytes: "2mb",
|
||||
|
||||
@@ -1052,6 +1052,7 @@ Notes:
|
||||
toolInputs: false,
|
||||
toolOutputs: false,
|
||||
systemPrompt: false,
|
||||
toolDefinitions: false,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1080,8 +1081,8 @@ Notes:
|
||||
- `otel.traces` / `otel.metrics` / `otel.logs`: enable trace, metrics, or log export.
|
||||
- `otel.sampleRate`: trace sampling rate `0`-`1`.
|
||||
- `otel.flushIntervalMs`: periodic telemetry flush interval in ms.
|
||||
- `otel.captureContent`: opt-in raw content capture for OTEL span attributes. Defaults to off. Boolean `true` captures non-system message/tool content; the object form lets you enable `inputMessages`, `outputMessages`, `toolInputs`, `toolOutputs`, and `systemPrompt` explicitly.
|
||||
- `OTEL_SEMCONV_STABILITY_OPT_IN=gen_ai_latest_experimental`: environment toggle for latest experimental GenAI span provider attributes. By default spans keep the legacy `gen_ai.system` attribute for compatibility; GenAI metrics use bounded semantic attributes.
|
||||
- `otel.captureContent`: opt-in raw content capture for OTEL span attributes. Defaults to off. Boolean `true` captures non-system message/tool content; the object form lets you enable `inputMessages`, `outputMessages`, `toolInputs`, `toolOutputs`, `systemPrompt`, and `toolDefinitions` explicitly.
|
||||
- `OTEL_SEMCONV_STABILITY_OPT_IN=gen_ai_latest_experimental`: environment toggle for latest experimental GenAI inference span shape, including `{gen_ai.operation.name} {gen_ai.request.model}` span names, `CLIENT` span kind, and `gen_ai.provider.name` instead of legacy `gen_ai.system`. By default spans keep `openclaw.model.call` and `gen_ai.system` for compatibility; GenAI metrics use bounded semantic attributes.
|
||||
- `OPENCLAW_OTEL_PRELOADED=1`: environment toggle for hosts that already registered a global OpenTelemetry SDK. OpenClaw then skips plugin-owned SDK startup/shutdown while keeping diagnostic listeners active.
|
||||
- `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT`, `OTEL_EXPORTER_OTLP_METRICS_ENDPOINT`, and `OTEL_EXPORTER_OTLP_LOGS_ENDPOINT`: signal-specific endpoint env vars used when the matching config key is unset.
|
||||
- `cacheTrace.enabled`: log cache trace snapshots for embedded runs (default: `false`).
|
||||
@@ -1240,7 +1241,7 @@ Current builds no longer include the TCP bridge. Nodes connect over the Gateway
|
||||
{
|
||||
cron: {
|
||||
enabled: true,
|
||||
maxConcurrentRuns: 2, // cron dispatch + isolated cron agent-turn execution
|
||||
maxConcurrentRuns: 8, // default; cron dispatch + isolated cron agent-turn execution
|
||||
webhook: "https://example.invalid/legacy", // deprecated fallback for stored notify:true jobs
|
||||
webhookToken: "replace-with-dedicated-token", // optional bearer token for outbound webhook auth
|
||||
sessionRetention: "24h", // duration string or false
|
||||
|
||||
@@ -419,7 +419,7 @@ candidate contains redacted secret placeholders such as `***`.
|
||||
{
|
||||
cron: {
|
||||
enabled: true,
|
||||
maxConcurrentRuns: 2, // cron dispatch + isolated cron agent-turn execution
|
||||
maxConcurrentRuns: 8, // default; cron dispatch + isolated cron agent-turn execution
|
||||
sessionRetention: "24h",
|
||||
runLog: {
|
||||
maxBytes: "2mb",
|
||||
|
||||
@@ -413,7 +413,7 @@ That stages grounded durable candidates into the short-term dreaming store while
|
||||
- short cooldowns (rate limits/timeouts/auth failures)
|
||||
- longer disables (billing/credit failures)
|
||||
|
||||
Legacy Codex OAuth profiles whose tokens live in macOS Keychain (older onboarding before the file-based sidecar layout) are not picked up by the embedded runtime path — that path runs with `allowKeychainPrompt: false` and cannot trigger a Keychain prompt. Run `openclaw doctor --fix` once to migrate Keychain-backed legacy tokens inline into `auth-profiles.json`; after that, embedded turns (Telegram, cron, sub-agent dispatch) resolve them like any other inline OAuth profile.
|
||||
Legacy Codex OAuth profiles whose tokens live in macOS Keychain (older onboarding before the file-based sidecar layout) are not picked up by the embedded runtime path — that path runs with `allowKeychainPrompt: false` and cannot trigger a Keychain prompt. Affected users will see a one-shot `log.warn` from the legacy sidecar loader naming `openclaw doctor --fix` and macOS Keychain (instead of the credential silently falling through to a downstream `No API key found for provider "openai-codex"`). Run `openclaw doctor --fix` once from an interactive terminal to migrate Keychain-backed legacy tokens inline into `auth-profiles.json`; after that, embedded turns (Telegram, cron, sub-agent dispatch) resolve them like any other inline OAuth profile.
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="6. Hooks model validation">
|
||||
|
||||
@@ -219,8 +219,11 @@ Set `stream: true` to receive Server-Sent Events (SSE):
|
||||
- `max_tokens`: number; legacy alias accepted for backwards compatibility. Ignored when `max_completion_tokens` is also present.
|
||||
- `temperature`: number; best-effort sampling temperature forwarded to the upstream provider via the agent stream-param channel.
|
||||
- `top_p`: number; best-effort nucleus sampling forwarded to the upstream provider via the agent stream-param channel.
|
||||
- `frequency_penalty`: number; best-effort frequency penalty forwarded to the upstream provider via the agent stream-param channel. Validated range: -2.0 to 2.0. Returns `400 invalid_request_error` for out-of-range values.
|
||||
- `presence_penalty`: number; best-effort presence penalty forwarded to the upstream provider via the agent stream-param channel. Validated range: -2.0 to 2.0. Returns `400 invalid_request_error` for out-of-range values.
|
||||
- `seed`: number (integer); best-effort seed forwarded to the upstream provider via the agent stream-param channel. Returns `400 invalid_request_error` for non-integer values.
|
||||
|
||||
When either token-cap field is set, the value is forwarded to the upstream provider via the agent stream-param channel. The actual wire field name sent to the upstream provider is chosen by the provider transport: `max_completion_tokens` for OpenAI-family endpoints, and `max_tokens` for providers that only accept the legacy name (such as Mistral and Chutes). Sampling fields (`temperature`, `top_p`) follow the same stream-param channel; the ChatGPT-based Codex Responses backend strips them server-side since it uses fixed sampling.
|
||||
When either token-cap field is set, the value is forwarded to the upstream provider via the agent stream-param channel. The actual wire field name sent to the upstream provider is chosen by the provider transport: `max_completion_tokens` for OpenAI-family endpoints, and `max_tokens` for providers that only accept the legacy name (such as Mistral and Chutes). Sampling fields (`temperature`, `top_p`, `frequency_penalty`, `presence_penalty`, `seed`) follow the same stream-param channel; the ChatGPT-based Codex Responses backend strips them server-side since it uses fixed sampling.
|
||||
|
||||
### Unsupported variants
|
||||
|
||||
|
||||
@@ -70,14 +70,15 @@ openclaw plugins enable diagnostics-otel
|
||||
|
||||
## Signals exported
|
||||
|
||||
| Signal | What goes in it |
|
||||
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| **Metrics** | Counters and histograms for token usage, cost, run duration, skill usage, message flow, Talk events, queue lanes, session state/recovery, tool execution, exec, and memory pressure. |
|
||||
| **Traces** | Spans for model usage, model calls, harness lifecycle, skill usage, tool execution, exec, webhook/message processing, context assembly, and tool loops. |
|
||||
| **Logs** | Structured `logging.file` records exported over OTLP when `diagnostics.otel.logs` is enabled; log bodies are withheld unless content capture is explicitly enabled. |
|
||||
| Signal | What goes in it |
|
||||
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| **Metrics** | Counters and histograms for token usage, cost, run duration, failover, skill usage, message flow, Talk events, queue lanes, session state/recovery, tool execution, oversized payloads, exec, and memory pressure. |
|
||||
| **Traces** | Spans for model usage, model calls, harness lifecycle, skill usage, tool execution, exec, webhook/message processing, context assembly, and tool loops. |
|
||||
| **Logs** | Structured `logging.file` records exported over OTLP when `diagnostics.otel.logs` is enabled; log bodies are withheld unless content capture is explicitly enabled. |
|
||||
|
||||
Toggle `traces`, `metrics`, and `logs` independently. All three default to on
|
||||
when `diagnostics.otel.enabled` is true.
|
||||
Toggle `traces`, `metrics`, and `logs` independently. Traces and metrics
|
||||
default to on when `diagnostics.otel.enabled` is true. Logs default to off and
|
||||
are exported only when `diagnostics.otel.logs` is explicitly `true`.
|
||||
|
||||
## Configuration reference
|
||||
|
||||
@@ -106,6 +107,7 @@ when `diagnostics.otel.enabled` is true.
|
||||
toolInputs: false,
|
||||
toolOutputs: false,
|
||||
systemPrompt: false,
|
||||
toolDefinitions: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -114,14 +116,14 @@ when `diagnostics.otel.enabled` is true.
|
||||
|
||||
### Environment variables
|
||||
|
||||
| Variable | Purpose |
|
||||
| ----------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `OTEL_EXPORTER_OTLP_ENDPOINT` | Override `diagnostics.otel.endpoint`. If the value already contains `/v1/traces`, `/v1/metrics`, or `/v1/logs`, it is used as-is. |
|
||||
| `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT` / `OTEL_EXPORTER_OTLP_METRICS_ENDPOINT` / `OTEL_EXPORTER_OTLP_LOGS_ENDPOINT` | Signal-specific endpoint overrides used when the matching `diagnostics.otel.*Endpoint` config key is unset. Signal-specific config wins over signal-specific env, which wins over the shared endpoint. |
|
||||
| `OTEL_SERVICE_NAME` | Override `diagnostics.otel.serviceName`. |
|
||||
| `OTEL_EXPORTER_OTLP_PROTOCOL` | Override the wire protocol (only `http/protobuf` is honored today). |
|
||||
| `OTEL_SEMCONV_STABILITY_OPT_IN` | Set to `gen_ai_latest_experimental` to emit the latest experimental GenAI span attribute (`gen_ai.provider.name`) instead of the legacy `gen_ai.system`. GenAI metrics always use bounded, low-cardinality semantic attributes regardless. |
|
||||
| `OPENCLAW_OTEL_PRELOADED` | Set to `1` when another preload or host process already registered the global OpenTelemetry SDK. The plugin then skips its own NodeSDK lifecycle but still wires diagnostic listeners and honors `traces`/`metrics`/`logs`. |
|
||||
| Variable | Purpose |
|
||||
| ----------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `OTEL_EXPORTER_OTLP_ENDPOINT` | Override `diagnostics.otel.endpoint`. If the value already contains `/v1/traces`, `/v1/metrics`, or `/v1/logs`, it is used as-is. |
|
||||
| `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT` / `OTEL_EXPORTER_OTLP_METRICS_ENDPOINT` / `OTEL_EXPORTER_OTLP_LOGS_ENDPOINT` | Signal-specific endpoint overrides used when the matching `diagnostics.otel.*Endpoint` config key is unset. Signal-specific config wins over signal-specific env, which wins over the shared endpoint. |
|
||||
| `OTEL_SERVICE_NAME` | Override `diagnostics.otel.serviceName`. |
|
||||
| `OTEL_EXPORTER_OTLP_PROTOCOL` | Override the wire protocol (only `http/protobuf` is honored today). |
|
||||
| `OTEL_SEMCONV_STABILITY_OPT_IN` | Set to `gen_ai_latest_experimental` to emit the latest experimental GenAI inference span shape, including `{gen_ai.operation.name} {gen_ai.request.model}` span names, `CLIENT` span kind, and `gen_ai.provider.name` instead of the legacy `gen_ai.system`. GenAI metrics always use bounded, low-cardinality semantic attributes regardless. |
|
||||
| `OPENCLAW_OTEL_PRELOADED` | Set to `1` when another preload or host process already registered the global OpenTelemetry SDK. The plugin then skips its own NodeSDK lifecycle but still wires diagnostic listeners and honors `traces`/`metrics`/`logs`. |
|
||||
|
||||
## Privacy and content capture
|
||||
|
||||
@@ -152,6 +154,7 @@ text. Each subkey is opt-in independently:
|
||||
- `toolInputs` - tool argument payloads.
|
||||
- `toolOutputs` - tool result payloads.
|
||||
- `systemPrompt` - assembled system/developer prompt.
|
||||
- `toolDefinitions` - model tool names, descriptions, and schemas.
|
||||
|
||||
When any subkey is enabled, model and tool spans get bounded, redacted
|
||||
`openclaw.content.*` attributes for that class only. Use boolean
|
||||
@@ -189,6 +192,7 @@ message bodies are also approved for export.
|
||||
- `openclaw.model_call.request_bytes` (histogram, UTF-8 byte size of the final model request payload; no raw payload content)
|
||||
- `openclaw.model_call.response_bytes` (histogram, UTF-8 byte size of streamed model response events; no raw response content)
|
||||
- `openclaw.model_call.time_to_first_byte_ms` (histogram, elapsed time before the first streamed response event)
|
||||
- `openclaw.model.failover` (counter, attrs: `openclaw.provider`, `openclaw.model`, `openclaw.failover.to_provider`, `openclaw.failover.to_model`, `openclaw.failover.reason`, `openclaw.failover.suspended`, `openclaw.lane`)
|
||||
- `openclaw.skill.used` (counter, attrs: `openclaw.skill.name`, `openclaw.skill.source`, `openclaw.skill.activation`, optional `openclaw.agent`, optional `openclaw.toolName`)
|
||||
|
||||
### Message flow
|
||||
@@ -260,16 +264,31 @@ unchanged, so dashboards should alert on sustained increases rather than every
|
||||
heartbeat tick. For the config knob and defaults, see
|
||||
[Configuration reference](/gateway/configuration-reference#diagnostics).
|
||||
|
||||
Liveness warnings also emit:
|
||||
|
||||
- `openclaw.liveness.warning` (counter, attrs: `openclaw.liveness.reason`)
|
||||
- `openclaw.liveness.event_loop_delay_p99_ms` (histogram, attrs: `openclaw.liveness.reason`)
|
||||
- `openclaw.liveness.event_loop_delay_max_ms` (histogram, attrs: `openclaw.liveness.reason`)
|
||||
- `openclaw.liveness.event_loop_utilization` (histogram, attrs: `openclaw.liveness.reason`)
|
||||
- `openclaw.liveness.cpu_core_ratio` (histogram, attrs: `openclaw.liveness.reason`)
|
||||
|
||||
### Harness lifecycle
|
||||
|
||||
- `openclaw.harness.duration_ms` (histogram, attrs: `openclaw.harness.id`, `openclaw.harness.plugin`, `openclaw.outcome`, `openclaw.harness.phase` on errors)
|
||||
|
||||
### Tool execution
|
||||
|
||||
- `openclaw.tool.execution.duration_ms` (histogram, attrs: `gen_ai.tool.name`, `openclaw.toolName`, `openclaw.tool.source`, `openclaw.tool.owner`, `openclaw.tool.params.kind`, plus `openclaw.errorCategory` on errors)
|
||||
- `openclaw.tool.execution.blocked` (counter, attrs: `gen_ai.tool.name`, `openclaw.toolName`, `openclaw.tool.source`, `openclaw.tool.owner`, `openclaw.tool.params.kind`, `openclaw.deniedReason`)
|
||||
|
||||
### Exec
|
||||
|
||||
- `openclaw.exec.duration_ms` (histogram, attrs: `openclaw.exec.target`, `openclaw.exec.mode`, `openclaw.outcome`, `openclaw.failureKind`)
|
||||
|
||||
### Diagnostics internals (memory and tool loop)
|
||||
|
||||
- `openclaw.payload.large` (counter, attrs: `openclaw.payload.surface`, `openclaw.payload.action`, `openclaw.channel`, `openclaw.plugin`, `openclaw.reason`)
|
||||
- `openclaw.payload.large_bytes` (histogram, attrs: same as `openclaw.payload.large`)
|
||||
- `openclaw.memory.heap_used_bytes` (histogram, attrs: `openclaw.memory.kind`)
|
||||
- `openclaw.memory.rss_bytes` (histogram)
|
||||
- `openclaw.memory.pressure` (counter, attrs: `openclaw.memory.level`)
|
||||
@@ -291,6 +310,7 @@ heartbeat tick. For the config knob and defaults, see
|
||||
- `openclaw.errorCategory` and optional `openclaw.failureKind` on errors
|
||||
- `openclaw.model_call.request_bytes`, `openclaw.model_call.response_bytes`, `openclaw.model_call.time_to_first_byte_ms`
|
||||
- `openclaw.provider.request_id_hash` (bounded SHA-based hash of the upstream provider request id; raw ids are not exported)
|
||||
- With `OTEL_SEMCONV_STABILITY_OPT_IN=gen_ai_latest_experimental`, model-call spans use the latest GenAI inference span name `{gen_ai.operation.name} {gen_ai.request.model}` and `CLIENT` span kind instead of `openclaw.model.call`.
|
||||
- `openclaw.harness.run`
|
||||
- `openclaw.harness.id`, `openclaw.harness.plugin`, `openclaw.outcome`, `openclaw.provider`, `openclaw.model`, `openclaw.channel`
|
||||
- On completion: `openclaw.harness.result_classification`, `openclaw.harness.yield_detected`, `openclaw.harness.items.started`, `openclaw.harness.items.completed`, `openclaw.harness.items.active`
|
||||
|
||||
@@ -8,7 +8,7 @@ read_when:
|
||||
- You want metrics without running an OpenTelemetry collector
|
||||
---
|
||||
|
||||
OpenClaw can expose diagnostics metrics through the official `diagnostics-prometheus` plugin. It listens to trusted internal diagnostics and renders a Prometheus text endpoint at:
|
||||
OpenClaw can expose diagnostics metrics through the official `diagnostics-prometheus` plugin. It listens to trusted diagnostics plus core-emitted gateway stability events, then renders a Prometheus text endpoint at:
|
||||
|
||||
```text
|
||||
GET /api/diagnostics/prometheus
|
||||
@@ -87,44 +87,59 @@ For traces, logs, OTLP push, and OpenTelemetry GenAI semantic attributes, see [O
|
||||
|
||||
## Metrics exported
|
||||
|
||||
| Metric | Type | Labels |
|
||||
| --------------------------------------------- | --------- | ----------------------------------------------------------------------------------------- |
|
||||
| `openclaw_run_completed_total` | counter | `channel`, `model`, `outcome`, `provider`, `trigger` |
|
||||
| `openclaw_run_duration_seconds` | histogram | `channel`, `model`, `outcome`, `provider`, `trigger` |
|
||||
| `openclaw_model_call_total` | counter | `api`, `error_category`, `model`, `outcome`, `provider`, `transport` |
|
||||
| `openclaw_model_call_duration_seconds` | histogram | `api`, `error_category`, `model`, `outcome`, `provider`, `transport` |
|
||||
| `openclaw_model_tokens_total` | counter | `agent`, `channel`, `model`, `provider`, `token_type` |
|
||||
| `openclaw_gen_ai_client_token_usage` | histogram | `model`, `provider`, `token_type` |
|
||||
| `openclaw_model_cost_usd_total` | counter | `agent`, `channel`, `model`, `provider` |
|
||||
| `openclaw_skill_used_total` | counter | `activation`, `agent`, `skill`, `source` |
|
||||
| `openclaw_tool_execution_total` | counter | `error_category`, `outcome`, `params_kind`, `tool`, `tool_owner`, `tool_source` |
|
||||
| `openclaw_tool_execution_duration_seconds` | histogram | `error_category`, `outcome`, `params_kind`, `tool`, `tool_owner`, `tool_source` |
|
||||
| `openclaw_harness_run_total` | counter | `channel`, `error_category`, `harness`, `model`, `outcome`, `phase`, `plugin`, `provider` |
|
||||
| `openclaw_harness_run_duration_seconds` | histogram | `channel`, `error_category`, `harness`, `model`, `outcome`, `phase`, `plugin`, `provider` |
|
||||
| `openclaw_message_received_total` | counter | `channel`, `source` |
|
||||
| `openclaw_message_dispatch_started_total` | counter | `channel`, `source` |
|
||||
| `openclaw_message_dispatch_completed_total` | counter | `channel`, `outcome`, `reason`, `source` |
|
||||
| `openclaw_message_dispatch_duration_seconds` | histogram | `channel`, `outcome`, `reason`, `source` |
|
||||
| `openclaw_message_processed_total` | counter | `channel`, `outcome`, `reason` |
|
||||
| `openclaw_message_processed_duration_seconds` | histogram | `channel`, `outcome`, `reason` |
|
||||
| `openclaw_message_delivery_started_total` | counter | `channel`, `delivery_kind` |
|
||||
| `openclaw_message_delivery_total` | counter | `channel`, `delivery_kind`, `error_category`, `outcome` |
|
||||
| `openclaw_message_delivery_duration_seconds` | histogram | `channel`, `delivery_kind`, `error_category`, `outcome` |
|
||||
| `openclaw_talk_event_total` | counter | `brain`, `event_type`, `mode`, `provider`, `transport` |
|
||||
| `openclaw_talk_event_duration_seconds` | histogram | `brain`, `event_type`, `mode`, `provider`, `transport` |
|
||||
| `openclaw_talk_audio_bytes` | histogram | `brain`, `event_type`, `mode`, `provider`, `transport` |
|
||||
| `openclaw_queue_lane_size` | gauge | `lane` |
|
||||
| `openclaw_queue_lane_wait_seconds` | histogram | `lane` |
|
||||
| `openclaw_session_state_total` | counter | `reason`, `state` |
|
||||
| `openclaw_session_queue_depth` | gauge | `state` |
|
||||
| `openclaw_session_turn_created_total` | counter | `agent`, `channel`, `trigger` |
|
||||
| `openclaw_session_recovery_total` | counter | `action`, `active_work_kind`, `state`, `status` |
|
||||
| `openclaw_session_recovery_age_seconds` | histogram | `action`, `active_work_kind`, `state`, `status` |
|
||||
| `openclaw_memory_bytes` | gauge | `kind` |
|
||||
| `openclaw_memory_rss_bytes` | histogram | none |
|
||||
| `openclaw_memory_pressure_total` | counter | `level`, `reason` |
|
||||
| `openclaw_telemetry_exporter_total` | counter | `exporter`, `reason`, `signal`, `status` |
|
||||
| `openclaw_prometheus_series_dropped_total` | counter | none |
|
||||
| Metric | Type | Labels |
|
||||
| ------------------------------------------------ | --------- | ----------------------------------------------------------------------------------------- |
|
||||
| `openclaw_run_completed_total` | counter | `channel`, `model`, `outcome`, `provider`, `trigger` |
|
||||
| `openclaw_run_duration_seconds` | histogram | `channel`, `model`, `outcome`, `provider`, `trigger` |
|
||||
| `openclaw_model_call_total` | counter | `api`, `error_category`, `model`, `outcome`, `provider`, `transport` |
|
||||
| `openclaw_model_call_duration_seconds` | histogram | `api`, `error_category`, `model`, `outcome`, `provider`, `transport` |
|
||||
| `openclaw_model_failover_total` | counter | `from_model`, `from_provider`, `lane`, `reason`, `suspended`, `to_model`, `to_provider` |
|
||||
| `openclaw_model_tokens_total` | counter | `agent`, `channel`, `model`, `provider`, `token_type` |
|
||||
| `openclaw_gen_ai_client_token_usage` | histogram | `model`, `provider`, `token_type` |
|
||||
| `openclaw_model_cost_usd_total` | counter | `agent`, `channel`, `model`, `provider` |
|
||||
| `openclaw_skill_used_total` | counter | `activation`, `agent`, `skill`, `source` |
|
||||
| `openclaw_tool_execution_total` | counter | `error_category`, `outcome`, `params_kind`, `tool`, `tool_owner`, `tool_source` |
|
||||
| `openclaw_tool_execution_duration_seconds` | histogram | `error_category`, `outcome`, `params_kind`, `tool`, `tool_owner`, `tool_source` |
|
||||
| `openclaw_tool_execution_blocked_total` | counter | `denied_reason`, `params_kind`, `tool`, `tool_owner`, `tool_source` |
|
||||
| `openclaw_harness_run_total` | counter | `channel`, `error_category`, `harness`, `model`, `outcome`, `phase`, `plugin`, `provider` |
|
||||
| `openclaw_harness_run_duration_seconds` | histogram | `channel`, `error_category`, `harness`, `model`, `outcome`, `phase`, `plugin`, `provider` |
|
||||
| `openclaw_webhook_received_total` | counter | `channel`, `webhook` |
|
||||
| `openclaw_webhook_error_total` | counter | `channel`, `webhook` |
|
||||
| `openclaw_webhook_duration_seconds` | histogram | `channel`, `webhook` |
|
||||
| `openclaw_message_received_total` | counter | `channel`, `source` |
|
||||
| `openclaw_message_dispatch_started_total` | counter | `channel`, `source` |
|
||||
| `openclaw_message_dispatch_completed_total` | counter | `channel`, `outcome`, `reason`, `source` |
|
||||
| `openclaw_message_dispatch_duration_seconds` | histogram | `channel`, `outcome`, `reason`, `source` |
|
||||
| `openclaw_message_processed_total` | counter | `channel`, `outcome`, `reason` |
|
||||
| `openclaw_message_processed_duration_seconds` | histogram | `channel`, `outcome`, `reason` |
|
||||
| `openclaw_message_delivery_started_total` | counter | `channel`, `delivery_kind` |
|
||||
| `openclaw_message_delivery_total` | counter | `channel`, `delivery_kind`, `error_category`, `outcome` |
|
||||
| `openclaw_message_delivery_duration_seconds` | histogram | `channel`, `delivery_kind`, `error_category`, `outcome` |
|
||||
| `openclaw_talk_event_total` | counter | `brain`, `event_type`, `mode`, `provider`, `transport` |
|
||||
| `openclaw_talk_event_duration_seconds` | histogram | `brain`, `event_type`, `mode`, `provider`, `transport` |
|
||||
| `openclaw_talk_audio_bytes` | histogram | `brain`, `event_type`, `mode`, `provider`, `transport` |
|
||||
| `openclaw_queue_lane_size` | gauge | `lane` |
|
||||
| `openclaw_queue_lane_wait_seconds` | histogram | `lane` |
|
||||
| `openclaw_session_state_total` | counter | `reason`, `state` |
|
||||
| `openclaw_session_queue_depth` | gauge | `state` |
|
||||
| `openclaw_session_turn_created_total` | counter | `agent`, `channel`, `trigger` |
|
||||
| `openclaw_session_stuck_total` | counter | `reason`, `state` |
|
||||
| `openclaw_session_stuck_age_seconds` | histogram | `reason`, `state` |
|
||||
| `openclaw_session_recovery_total` | counter | `action`, `active_work_kind`, `state`, `status` |
|
||||
| `openclaw_session_recovery_age_seconds` | histogram | `action`, `active_work_kind`, `state`, `status` |
|
||||
| `openclaw_liveness_warning_total` | counter | `reason` |
|
||||
| `openclaw_liveness_sessions` | gauge | `state` |
|
||||
| `openclaw_liveness_event_loop_delay_p99_seconds` | histogram | `reason` |
|
||||
| `openclaw_liveness_event_loop_delay_max_seconds` | histogram | `reason` |
|
||||
| `openclaw_liveness_event_loop_utilization_ratio` | histogram | `reason` |
|
||||
| `openclaw_liveness_cpu_core_ratio` | histogram | `reason` |
|
||||
| `openclaw_payload_large_total` | counter | `action`, `channel`, `plugin`, `reason`, `surface` |
|
||||
| `openclaw_payload_large_bytes` | histogram | `action`, `channel`, `plugin`, `reason`, `surface` |
|
||||
| `openclaw_memory_bytes` | gauge | `kind` |
|
||||
| `openclaw_memory_rss_bytes` | histogram | none |
|
||||
| `openclaw_memory_pressure_total` | counter | `level`, `reason` |
|
||||
| `openclaw_telemetry_exporter_total` | counter | `exporter`, `reason`, `signal`, `status` |
|
||||
| `openclaw_prometheus_series_dropped_total` | counter | none |
|
||||
|
||||
## Label policy
|
||||
|
||||
|
||||
@@ -289,7 +289,7 @@ troubleshooting, see the main [FAQ](/help/faq).
|
||||
- `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`
|
||||
- `gemini-flash-lite` → `google/gemini-3.1-flash-lite`
|
||||
|
||||
If you set your own alias with the same name, your value wins.
|
||||
|
||||
|
||||
@@ -509,8 +509,8 @@ Think of the suites as "increasing realism" (and increasing flakiness/cost):
|
||||
|
||||
Native dependency policy:
|
||||
|
||||
- Default test installs skip optional native Discord opus builds. Discord voice receive uses the pure-JS `opusscript` decoder, and `@discordjs/opus` stays disabled in `allowBuilds` so local tests and Testbox lanes do not compile the native addon.
|
||||
- Use a dedicated Discord voice performance or live lane if you intentionally need to compare a native opus build. Do not set `@discordjs/opus` to `true` in the default `allowBuilds`; that makes unrelated install/test loops compile native code.
|
||||
- Default test installs skip optional native Discord opus builds. Discord voice uses bundled `libopus-wasm`, and `@discordjs/opus` stays disabled in `allowBuilds` so local tests and Testbox lanes do not compile the native addon.
|
||||
- Compare native opus performance in the `libopus-wasm` benchmark repo, not in default OpenClaw install/test loops. Do not set `@discordjs/opus` to `true` in the default `allowBuilds`; that makes unrelated install/test loops compile native code.
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="Projects, shards, and scoped lanes">
|
||||
@@ -557,6 +557,10 @@ Native dependency policy:
|
||||
processes by default to reduce V8 compile churn during big local runs.
|
||||
Set `OPENCLAW_VITEST_ENABLE_MAGLEV=1` to compare against stock V8
|
||||
behavior.
|
||||
- `scripts/run-vitest.mjs` terminates explicit non-watch Vitest runs after
|
||||
5 minutes with no stdout or stderr output. Set
|
||||
`OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS=0` to disable the watchdog for an
|
||||
intentionally silent investigation.
|
||||
|
||||
</Accordion>
|
||||
|
||||
@@ -746,6 +750,7 @@ These Docker runners split into two buckets:
|
||||
- Build and release checks run `scripts/check-cli-bootstrap-imports.mjs` after tsdown. The guard walks the static built graph from `dist/entry.js` and `dist/cli/run-main.js` and fails if pre-dispatch startup imports package dependencies such as Commander, prompt UI, undici, or logging before command dispatch; it also keeps the bundled gateway run chunk under budget and rejects static imports of known cold gateway paths. Packaged CLI smoke also covers root help, onboard help, doctor help, status, config schema, and a model-list command.
|
||||
- Package Acceptance legacy compatibility is capped at `2026.4.25` (`2026.4.25-beta.*` included). Through that cutoff, the harness tolerates only shipped-package metadata gaps: omitted private QA inventory entries, missing `gateway install --wrapper`, missing patch files in the tarball-derived git fixture, missing persisted `update.channel`, legacy plugin install-record locations, missing marketplace install-record persistence, and config metadata migration during `plugins update`. For packages after `2026.4.25`, those paths are strict failures.
|
||||
- Container smoke runners: `test:docker:openwebui`, `test:docker:onboard`, `test:docker:npm-onboard-channel-agent`, `test:docker:release-user-journey`, `test:docker:release-typed-onboarding`, `test:docker:release-media-memory`, `test:docker:release-upgrade-user-journey`, `test:docker:release-plugin-marketplace`, `test:docker:skill-install`, `test:docker:update-channel-switch`, `test:docker:upgrade-survivor`, `test:docker:published-upgrade-survivor`, `test:docker:session-runtime-context`, `test:docker:agents-delete-shared-workspace`, `test:docker:gateway-network`, `test:docker:browser-cdp-snapshot`, `test:docker:mcp-channels`, `test:docker:pi-bundle-mcp-tools`, `test:docker:cron-mcp-cleanup`, `test:docker:plugins`, `test:docker:plugin-update`, `test:docker:plugin-lifecycle-matrix`, and `test:docker:config-reload` boot one or more real containers and verify higher-level integration paths.
|
||||
- Docker/Bash E2E lanes that install the packed OpenClaw tarball through `scripts/lib/openclaw-e2e-instance.sh` cap `npm install` at `OPENCLAW_E2E_NPM_INSTALL_TIMEOUT` (default `600s`; set `0` to disable the wrapper for debugging).
|
||||
|
||||
The live-model Docker runners also bind-mount only the needed CLI auth homes (or all supported ones when the run is not narrowed), then copy them into the container home before the run so external-CLI OAuth can refresh tokens without mutating the host auth store:
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 657 KiB |
|
Before Width: | Height: | Size: 105 KiB |
@@ -72,9 +72,10 @@ Recommended for most interactive installs on macOS/Linux/WSL.
|
||||
</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.19+`, for compatibility.
|
||||
On Alpine/musl Linux, the installer uses apk packages instead of NodeSource; the configured Alpine repositories must provide Node `22.19+` (Alpine 3.21 or newer at the time of writing).
|
||||
</Step>
|
||||
<Step title="Ensure Git">
|
||||
Installs Git if missing.
|
||||
Installs Git if missing using the detected package manager, including apk on Alpine.
|
||||
</Step>
|
||||
<Step title="Install OpenClaw">
|
||||
- `npm` method (default): global npm install
|
||||
@@ -187,10 +188,10 @@ by default, plus git-checkout installs under the same prefix flow.
|
||||
<Steps>
|
||||
<Step title="Install local Node runtime">
|
||||
Downloads a pinned supported Node LTS tarball (the version is embedded in the script and updated independently) to `<prefix>/tools/node-v<version>` and verifies SHA-256.
|
||||
On Alpine/musl Linux, where Node does not publish compatible tarballs for the pinned runtime, installs `nodejs` and `npm` with `apk` and links that runtime into the prefix wrapper path.
|
||||
On Alpine/musl Linux, where Node does not publish compatible tarballs for the pinned runtime, installs `nodejs` and `npm` with `apk` and links that runtime into the prefix wrapper path. The Alpine repositories must provide Node `22.19+`; use Alpine 3.21 or newer if older repositories only provide Node 20 or 21.
|
||||
</Step>
|
||||
<Step title="Ensure Git">
|
||||
If Git is missing, attempts install via apt/dnf/yum on Linux or Homebrew on macOS.
|
||||
If Git is missing, attempts install via apt/dnf/yum/apk on Linux or Homebrew on macOS.
|
||||
</Step>
|
||||
<Step title="Install OpenClaw under prefix">
|
||||
- `npm` method (default): installs under the prefix with npm, then writes wrapper to `<prefix>/bin/openclaw`
|
||||
|
||||
205
docs/plan/policy-agent-scoped-overlays.md
Normal file
@@ -0,0 +1,205 @@
|
||||
---
|
||||
summary: "Per-agent Policy plugin overlays layered on top of global policy rules."
|
||||
read_when:
|
||||
- You are designing per-agent policy requirements
|
||||
- You need to distinguish tool posture policy from workspace policy
|
||||
- You are configuring stricter policy for one named agent
|
||||
title: "Agent-scoped policy overlays"
|
||||
---
|
||||
|
||||
# Agent-scoped policy overlays
|
||||
|
||||
OpenClaw policy supports global requirements and stricter requirements for
|
||||
explicit runtime agent ids. Some deployments need one agent to use a tighter
|
||||
workspace and tool posture than other agents, but deployment-wide rules should
|
||||
not force every agent to use the same posture.
|
||||
|
||||
This page describes the agent-scoped overlay model. The field reference remains
|
||||
[`openclaw policy`](/cli/policy).
|
||||
|
||||
## Design goals
|
||||
|
||||
- Keep global policy as the deployment baseline.
|
||||
- Let a named agent add stricter requirements without weakening global rules.
|
||||
- Reuse existing policy section shapes where the evidence can be attributed to
|
||||
an agent.
|
||||
- Avoid making `agents.workspace` a second tool-permission system.
|
||||
- Leave global-only checks global until their evidence can be mapped to an
|
||||
agent.
|
||||
|
||||
## Shape
|
||||
|
||||
Use `scopes.<scopeName>` for purpose-named agent policy scopes. Each
|
||||
scope lists the runtime `agentIds` it applies to, then reuses the normal
|
||||
top-level policy section grammar where the section evidence can be attributed to
|
||||
those agents. The initial shipped scoped sections are `tools` and
|
||||
`agents.workspace`; sandbox and ingress stay out of this PR and can join the
|
||||
same container once those policy PRs land and their evidence carries agent
|
||||
identity. The scoped field inventory is backed by policy rule metadata that
|
||||
records each field's strictness semantics for later policy-file conformance.
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"tools": {
|
||||
"denyTools": ["process"],
|
||||
},
|
||||
"agents": {
|
||||
"workspace": {
|
||||
"allowedAccess": ["none", "ro"],
|
||||
},
|
||||
},
|
||||
"scopes": {
|
||||
"release-agent-lockdown": {
|
||||
"agentIds": ["release-agent"],
|
||||
"agents": {
|
||||
"workspace": {
|
||||
"allowedAccess": ["none", "ro"],
|
||||
},
|
||||
},
|
||||
"tools": {
|
||||
"profiles": { "allow": ["minimal", "messaging"] },
|
||||
"fs": { "requireWorkspaceOnly": true },
|
||||
"exec": {
|
||||
"allowSecurity": ["deny", "allowlist"],
|
||||
"requireAsk": ["always"],
|
||||
"allowHosts": ["sandbox"],
|
||||
},
|
||||
"elevated": { "allow": false },
|
||||
"alsoAllow": { "expected": ["message", "read"] },
|
||||
"denyTools": ["exec", "process", "write", "edit", "apply_patch"],
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
`agents.workspace` remains the existing all-agent workspace baseline.
|
||||
`scopes.<scopeName>` is a scoped overlay, not a replacement for global
|
||||
policy. The scope name is descriptive only; matching uses `agentIds`, not
|
||||
display names. It deliberately contains normal section names instead of a
|
||||
bespoke per-agent mini-grammar.
|
||||
Every scope present in `policy.jsonc` must be valid and enforceable. In this
|
||||
PR, the only supported selector is `agentIds`, and it supports only `tools.*`
|
||||
and `agents.workspace.*`.
|
||||
|
||||
## Layering semantics
|
||||
|
||||
Policy evaluation is additive:
|
||||
|
||||
1. Top-level policy applies to all matching evidence.
|
||||
2. Existing `agents.workspace` applies to defaults and every listed agent.
|
||||
3. `scopes.<scopeName>` applies to evidence for each normalized runtime
|
||||
id in `agentIds`.
|
||||
4. Multiple scope blocks may target the same agent when they govern
|
||||
different fields, or when a later value for the same field is equally or
|
||||
more restrictive according to policy metadata.
|
||||
5. A named-agent overlay can tighten policy, but it cannot make a global
|
||||
violation acceptable.
|
||||
|
||||
If both global and agent-scoped rules fail, findings should point at the rule
|
||||
that was violated:
|
||||
|
||||
```text
|
||||
oc://policy.jsonc/tools/denyTools
|
||||
oc://policy.jsonc/scopes/release-agent-lockdown/tools/denyTools
|
||||
oc://policy.jsonc/scopes/release-agent-lockdown/agents/workspace/allowedAccess
|
||||
```
|
||||
|
||||
That keeps broad tool posture, named-agent tool posture, and workspace posture
|
||||
auditable as separate requirements even when they observe the same config
|
||||
fields.
|
||||
|
||||
Exact-list claims such as `tools.alsoAllow.expected` compare the configured list
|
||||
to the expected list and report both missing expected entries and unexpected
|
||||
extra entries. This is intended for additive posture such as `alsoAllow`, where
|
||||
one extra entry can widen an agent beyond its reviewed role.
|
||||
|
||||
## Policy and config layering
|
||||
|
||||
The overlay model separates where policy is authored from where OpenClaw config
|
||||
is observed:
|
||||
|
||||
| Policy scope | Observed config | Applies to | Example result |
|
||||
| --------------------------------------- | ---------------------------------------------------- | --------------------------------- | ----------------------------------------------------------------------------- |
|
||||
| Top-level `tools.*` | Global `tools.*` and inherited agent tool posture | All agents using matching posture | Deny `gateway` exec host for every agent unless the global policy allows it. |
|
||||
| Top-level `tools.*` | `agents.list[].tools.*` overrides | Any agent with an override | Flag one agent that overrides `tools.exec.host` to an unapproved value. |
|
||||
| `scopes.<scopeName>.tools.*` | Matching `agents.list[]` entry and inherited posture | Only that named agent | Let most agents use `node` exec host while one agent must use only `sandbox`. |
|
||||
| `agents.workspace` | Defaults and every listed agent workspace posture | Defaults and all listed agents | Require every agent workspace access to be `none` or `ro`. |
|
||||
| `scopes.<scopeName>.agents.workspace.*` | Matching `agents.list[]` workspace posture | Only that named agent | Require one agent to be read-only without requiring the same for `main`. |
|
||||
|
||||
Per-agent overlays are additive. A named-agent rule can be stricter than the
|
||||
top-level rule, but it cannot make a global violation acceptable. For allow-list
|
||||
rules, the effective allowed set is the intersection of the global rule and the
|
||||
named-agent overlay when both are present.
|
||||
|
||||
For example, if top-level `tools.exec.allowHosts` permits `["sandbox", "node"]`
|
||||
and `scopes.release-agent-lockdown.tools.exec.allowHosts` permits only
|
||||
`["sandbox"]`, `release-agent` fails when its effective exec host is `node`;
|
||||
another agent can still pass
|
||||
with `node`.
|
||||
|
||||
## Tool posture versus workspace posture
|
||||
|
||||
Tool posture belongs under `tools` because it describes what tool behavior a
|
||||
configuration may expose. The existing `tools.*` policy observes both global
|
||||
`tools.*` config and per-agent `agents.list[].tools.*` overrides.
|
||||
|
||||
Workspace posture belongs under `workspace` because it describes sandbox mode
|
||||
and workspace access. The workspace section should not grow into a general tool
|
||||
policy namespace. If one agent needs stricter tool restrictions to make its
|
||||
workspace posture meaningful, put those restrictions in the same agent overlay
|
||||
under `scopes.<scopeName>.tools`.
|
||||
|
||||
For a restricted release agent, the intended split is:
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"scopes": {
|
||||
"release-agent-lockdown": {
|
||||
"agentIds": ["release-agent"],
|
||||
"agents": {
|
||||
"workspace": { "allowedAccess": ["none", "ro"] },
|
||||
},
|
||||
"tools": {
|
||||
"denyTools": ["exec", "process", "write", "edit", "apply_patch"],
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Section eligibility
|
||||
|
||||
An agent-scoped section should be added only when policy evidence carries an
|
||||
agent id or can be attributed to one without guessing.
|
||||
|
||||
| Section | Initial agent-scoped status | Reason |
|
||||
| ----------- | --------------------------- | ------------------------------------------------------------------------ |
|
||||
| `workspace` | Include | Agent sandbox/workspace evidence already has agent identity. |
|
||||
| `tools` | Include | Tool posture evidence includes global and per-agent tool config. |
|
||||
| `sandbox` | Pipeline follow-up | Keep out until the sandbox posture PR lands and evidence can be scoped. |
|
||||
| `ingress` | Pipeline follow-up | Keep out until ingress/channel posture lands with agent attribution. |
|
||||
| `models` | Include when mapped | Selected model refs can be agent-specific. |
|
||||
| `mcp` | Include when mapped | Use only when MCP server evidence is attributable to an agent. |
|
||||
| `auth` | Defer | Auth profile metadata is a config catalog unless agent binding is clear. |
|
||||
| `channels` | Defer | Channel provider posture is deployment-level until routing is scoped. |
|
||||
| `gateway` | Keep global | Gateway exposure/auth/http posture is process-level. |
|
||||
| `network` | Keep global | Private-network SSRF posture is runtime-level. |
|
||||
| `secrets` | Keep global first | Secret provider posture is shared unless refs are agent-attributed. |
|
||||
|
||||
## Compatibility
|
||||
|
||||
The implementation is additive:
|
||||
|
||||
- keep all existing top-level policy fields valid;
|
||||
- keep `agents.workspace` semantics unchanged;
|
||||
- validate `scopes` before evaluating scoped rules;
|
||||
- reject unsupported scoped sections clearly until their evidence and policy
|
||||
contracts are implemented;
|
||||
- do not reinterpret top-level `tools.requireMetadata` as agent-scoped, because
|
||||
tool metadata describes the declared workspace tool catalog;
|
||||
- include agent-scoped evidence in the attestation hash when any scoped rule is
|
||||
present.
|
||||
|
||||
This lets broad tool posture remain a top-level policy contract while named
|
||||
agents add stricter observable claims without weakening the global baseline.
|
||||
@@ -42,7 +42,7 @@ Capabilities are the public **native plugin** model inside OpenClaw. Every nativ
|
||||
| Realtime transcription | `api.registerRealtimeTranscriptionProvider(...)` | `openai` |
|
||||
| Realtime voice | `api.registerRealtimeVoiceProvider(...)` | `openai` |
|
||||
| Media understanding | `api.registerMediaUnderstandingProvider(...)` | `openai`, `google` |
|
||||
| Meeting notes source | `api.registerMeetingNotesSourceProvider(...)` | `discord`, `meeting-notes` |
|
||||
| Transcripts source | `api.registerTranscriptSourceProvider(...)` | `discord` |
|
||||
| Image generation | `api.registerImageGenerationProvider(...)` | `openai`, `google`, `fal`, `minimax` |
|
||||
| Music generation | `api.registerMusicGenerationProvider(...)` | `google`, `minimax` |
|
||||
| Video generation | `api.registerVideoGenerationProvider(...)` | `qwen` |
|
||||
|
||||
@@ -353,17 +353,17 @@ If discovery fails or times out, OpenClaw uses a bundled fallback catalog for:
|
||||
- GPT-5.4 mini
|
||||
- GPT-5.2
|
||||
|
||||
The current bundled harness is `@openai/codex` `0.132.0`. A `model/list` probe
|
||||
The current bundled harness is `@openai/codex` `0.133.0`. A `model/list` probe
|
||||
against that bundled app-server returned:
|
||||
|
||||
| Model id | Default | Hidden | Input modalities | Reasoning efforts |
|
||||
| ------------------- | ------- | ------ | ---------------- | ------------------------ |
|
||||
| `gpt-5.5` | Yes | No | text, image | low, medium, high, xhigh |
|
||||
| `gpt-5.4` | No | No | text, image | low, medium, high, xhigh |
|
||||
| `gpt-5.4-mini` | No | No | text, image | low, medium, high, xhigh |
|
||||
| `gpt-5.3-codex` | No | No | text, image | low, medium, high, xhigh |
|
||||
| `gpt-5.2` | No | No | text, image | low, medium, high, xhigh |
|
||||
| `codex-auto-review` | No | Yes | text, image | low, medium, high, xhigh |
|
||||
| Model id | Default | Hidden | Input modalities | Reasoning efforts |
|
||||
| --------------------- | ------- | ------ | ---------------- | ------------------------ |
|
||||
| `gpt-5.5` | Yes | No | text, image | low, medium, high, xhigh |
|
||||
| `gpt-5.4` | No | No | text, image | low, medium, high, xhigh |
|
||||
| `gpt-5.4-mini` | No | No | text, image | low, medium, high, xhigh |
|
||||
| `gpt-5.3-codex` | No | No | text, image | low, medium, high, xhigh |
|
||||
| `gpt-5.3-codex-spark` | No | No | text | low, medium, high, xhigh |
|
||||
| `gpt-5.2` | No | No | text, image | low, medium, high, xhigh |
|
||||
|
||||
Hidden models can be returned by the app-server catalog for internal or
|
||||
specialized flows, but they are not normal model-picker choices.
|
||||
|
||||
@@ -27,8 +27,10 @@ native Codex turn receives Codex app-server developer instructions, while an
|
||||
explicit PI compatibility route keeps the normal OpenClaw/PI system prompt even
|
||||
when it uses Codex-flavored OpenAI auth or transport.
|
||||
|
||||
Native Codex keeps Codex-owned base/model/personality instructions and
|
||||
project-doc behavior according to the active Codex thread config. Lightweight
|
||||
Native Codex keeps Codex-owned base/model instructions and project-doc behavior
|
||||
according to the active Codex thread config. OpenClaw starts and resumes native
|
||||
Codex threads with Codex's built-in personality disabled so workspace
|
||||
personality files and OpenClaw agent identity stay authoritative. Lightweight
|
||||
OpenClaw runs still preserve their existing project-doc suppression. OpenClaw
|
||||
developer instructions cover OpenClaw runtime concerns such as source-channel
|
||||
delivery, OpenClaw dynamic tools, ACP delegation, adapter context, and the
|
||||
@@ -115,19 +117,19 @@ They do not invoke OpenClaw plugin hooks.
|
||||
|
||||
Supported in Codex runtime v1:
|
||||
|
||||
| Surface | Support | Why |
|
||||
| --------------------------------------------- | -------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| OpenAI model loop through Codex | Supported | Codex app-server owns the OpenAI turn, native thread resume, and native tool continuation. |
|
||||
| OpenClaw channel routing and delivery | Supported | Telegram, Discord, Slack, WhatsApp, iMessage, and other channels stay outside the model runtime. |
|
||||
| OpenClaw dynamic tools | Supported | Codex asks OpenClaw to execute these tools, so OpenClaw stays in the execution path. |
|
||||
| Prompt and context plugins | Supported | OpenClaw projects OpenClaw-specific prompt/context into the Codex turn while leaving Codex-owned base, model, personality, and configured project-doc prompts in the native Codex lane. Native Codex developer instructions accept only command guidance explicitly scoped to `codex_app_server`; legacy global command hints remain for non-Codex prompt surfaces. |
|
||||
| Context engine lifecycle | Supported | Assemble, ingest, and after-turn maintenance run around Codex turns. Context engines do not replace native Codex compaction. |
|
||||
| Dynamic tool hooks | Supported | `before_tool_call`, `after_tool_call`, and tool-result middleware run around OpenClaw-owned dynamic tools. |
|
||||
| Lifecycle hooks | Supported as adapter observations | `llm_input`, `llm_output`, `agent_end`, `before_compaction`, and `after_compaction` fire with honest Codex-mode payloads. |
|
||||
| Final-answer revision gate | Supported through native hook relay | Codex `Stop` is relayed to `before_agent_finalize`; `revise` asks Codex for one more model pass before finalization. |
|
||||
| Native shell, patch, and MCP block or observe | Supported through native hook relay | Codex `PreToolUse` and `PostToolUse` are relayed for committed native tool surfaces, including MCP payloads on Codex app-server `0.125.0` or newer. Blocking is supported; argument rewriting is not. |
|
||||
| Native permission policy | Supported through Codex app-server approvals and compatibility native hook relay | Codex app-server approval requests route through OpenClaw after Codex review. The `PermissionRequest` native hook relay is opt-in for native approval modes because Codex emits it before guardian review. |
|
||||
| App-server trajectory capture | Supported | OpenClaw records the request it sent to app-server and the app-server notifications it receives. |
|
||||
| Surface | Support | Why |
|
||||
| --------------------------------------------- | -------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| OpenAI model loop through Codex | Supported | Codex app-server owns the OpenAI turn, native thread resume, and native tool continuation. |
|
||||
| OpenClaw channel routing and delivery | Supported | Telegram, Discord, Slack, WhatsApp, iMessage, and other channels stay outside the model runtime. |
|
||||
| OpenClaw dynamic tools | Supported | Codex asks OpenClaw to execute these tools, so OpenClaw stays in the execution path. |
|
||||
| Prompt and context plugins | Supported | OpenClaw projects OpenClaw-specific prompt/context into the Codex turn while leaving Codex-owned base, model, and configured project-doc prompts in the native Codex lane. OpenClaw disables Codex's built-in personality for native threads so agent workspace personality files remain authoritative. Native Codex developer instructions accept only command guidance explicitly scoped to `codex_app_server`; legacy global command hints remain for non-Codex prompt surfaces. |
|
||||
| Context engine lifecycle | Supported | Assemble, ingest, and after-turn maintenance run around Codex turns. Context engines do not replace native Codex compaction. |
|
||||
| Dynamic tool hooks | Supported | `before_tool_call`, `after_tool_call`, and tool-result middleware run around OpenClaw-owned dynamic tools. |
|
||||
| Lifecycle hooks | Supported as adapter observations | `llm_input`, `llm_output`, `agent_end`, `before_compaction`, and `after_compaction` fire with honest Codex-mode payloads. |
|
||||
| Final-answer revision gate | Supported through native hook relay | Codex `Stop` is relayed to `before_agent_finalize`; `revise` asks Codex for one more model pass before finalization. |
|
||||
| Native shell, patch, and MCP block or observe | Supported through native hook relay | Codex `PreToolUse` and `PostToolUse` are relayed for committed native tool surfaces, including MCP payloads on Codex app-server `0.125.0` or newer. Blocking is supported; argument rewriting is not. |
|
||||
| Native permission policy | Supported through Codex app-server approvals and compatibility native hook relay | Codex app-server approval requests route through OpenClaw after Codex review. The `PermissionRequest` native hook relay is opt-in for native approval modes because Codex emits it before guardian review. |
|
||||
| App-server trajectory capture | Supported | OpenClaw records the request it sent to app-server and the app-server notifications it receives. |
|
||||
|
||||
Not supported in Codex runtime v1:
|
||||
|
||||
|
||||
@@ -649,7 +649,7 @@ Each list is optional:
|
||||
| `realtimeVoiceProviders` | `string[]` | Realtime-voice provider ids this plugin owns. |
|
||||
| `memoryEmbeddingProviders` | `string[]` | Memory embedding provider ids this plugin owns. |
|
||||
| `mediaUnderstandingProviders` | `string[]` | Media-understanding provider ids this plugin owns. |
|
||||
| `meetingNotesSourceProviders` | `string[]` | Meeting-notes source provider ids this plugin owns. |
|
||||
| `transcriptSourceProviders` | `string[]` | Transcript source provider ids this plugin owns. |
|
||||
| `imageGenerationProviders` | `string[]` | Image-generation provider ids this plugin owns. |
|
||||
| `videoGenerationProviders` | `string[]` | Video-generation provider ids this plugin owns. |
|
||||
| `webFetchProviders` | `string[]` | Web-fetch provider ids this plugin owns. |
|
||||
@@ -865,6 +865,11 @@ Fields:
|
||||
| `modelPrefixes` | `string[]` | Prefixes matched with `startsWith` against shorthand model ids. |
|
||||
| `modelPatterns` | `string[]` | Regex sources matched against shorthand model ids after profile suffix removal. |
|
||||
|
||||
`modelPatterns` entries are compiled through `compileSafeRegex`, which rejects
|
||||
patterns containing nested repetition (for example `(a+)+$`). Patterns that fail
|
||||
the safety check are silently skipped, the same as syntactically invalid regex.
|
||||
Keep patterns simple and avoid nested quantifiers.
|
||||
|
||||
## modelCatalog reference
|
||||
|
||||
Use `modelCatalog` when OpenClaw should know provider model metadata before
|
||||
|
||||
@@ -1,359 +0,0 @@
|
||||
---
|
||||
summary: "Meeting Notes plugin: capture transcripts from Discord voice and imported meeting sources, then write summaries"
|
||||
read_when:
|
||||
- You want OpenClaw to take meeting notes
|
||||
- You are wiring Discord voice, Google Meet, Slack huddles, or another meeting source into notes
|
||||
- You need the meeting_notes tool contract
|
||||
title: "Meeting Notes plugin"
|
||||
---
|
||||
|
||||
The Meeting Notes plugin is the generic notes layer for live calls and imported
|
||||
meeting transcripts. It owns transcript storage, summary rendering, and the
|
||||
`meeting_notes` tool. Channel plugins own capture, authentication, and
|
||||
platform-specific meeting joins.
|
||||
|
||||
Use this page when you want OpenClaw to capture Discord voice notes today, when
|
||||
you want to import a transcript from another meeting system, or when you are
|
||||
building a Google Meet, Slack huddle, Zoom, or calendar-owned source provider.
|
||||
|
||||
## Source model
|
||||
|
||||
Meeting sources register `meetingNotesSourceProviders` through the plugin SDK.
|
||||
The first live provider is `discord-voice`; the built-in `manual-transcript`
|
||||
provider imports post-meeting transcripts.
|
||||
|
||||
- `live-audio`: source joins or listens to a call and streams final utterances.
|
||||
- `live-caption`: source reads captions from a browser or meeting surface.
|
||||
- `posthoc-transcript`: source imports a transcript or notes artifact after the meeting.
|
||||
- `recording-stt`: source transcribes a recording before importing utterances.
|
||||
|
||||
This keeps Discord, Google Meet, Slack huddles, and future meeting surfaces out
|
||||
of the notes engine. Each source supplies speaker-labeled utterances; Meeting
|
||||
Notes writes the artifacts and summary.
|
||||
|
||||
## Install and enable
|
||||
|
||||
Meeting Notes is an external source plugin in this repository. It is not part of
|
||||
the core OpenClaw npm package and becomes available only when the plugin is
|
||||
installed as a plugin or loaded from a source checkout that contains
|
||||
`extensions/meeting-notes`.
|
||||
|
||||
Once the plugin is loaded, it is enabled by default unless one of these settings
|
||||
blocks it:
|
||||
|
||||
- `plugins.enabled: false` disables all plugins.
|
||||
- `plugins.deny` contains `meeting-notes`.
|
||||
- `plugins.allow` is set and does not contain `meeting-notes`.
|
||||
- `plugins.entries.meeting-notes.enabled: false` disables this plugin entry.
|
||||
- `plugins.entries.meeting-notes.config.enabled: false` keeps the plugin loaded
|
||||
but disables the `meeting_notes` tool and auto-start service.
|
||||
|
||||
The normal user config file is `~/.openclaw/openclaw.json`. The `plugins`
|
||||
section controls plugin loading, and the nested `entries.<pluginId>.config`
|
||||
object is passed to that plugin as plugin-specific config. A separate
|
||||
`config: { ... }` block under `meeting-notes` is expected; it is how plugins
|
||||
receive their own options without adding core config keys.
|
||||
|
||||
Use this shape when your config has a plugin allowlist:
|
||||
|
||||
```json5
|
||||
{
|
||||
plugins: {
|
||||
allow: ["discord", "meeting-notes"],
|
||||
entries: {
|
||||
"meeting-notes": {
|
||||
enabled: true,
|
||||
config: {
|
||||
enabled: true,
|
||||
maxUtterances: 2000,
|
||||
autoStart: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Run a config check after editing:
|
||||
|
||||
```bash
|
||||
openclaw config validate
|
||||
```
|
||||
|
||||
Gateway config hot reload applies plugin allowlist and plugin-entry changes.
|
||||
Restart the Gateway if you are also changing the source plugin itself, installing
|
||||
new plugin files, or changing Discord voice credentials.
|
||||
|
||||
## Configuration
|
||||
|
||||
Meeting Notes has three plugin config fields:
|
||||
|
||||
- `enabled`: `true` by default. Set `false` to leave the plugin installed but
|
||||
disable the tool and auto-start service.
|
||||
- `maxUtterances`: `2000` by default. Summary generation reads only the newest
|
||||
N utterances from `transcript.jsonl`; valid values are clamped to `1` through
|
||||
`10000`.
|
||||
- `autoStart`: empty by default. Each entry starts a live notes source when the
|
||||
Gateway starts or reloads the plugin.
|
||||
|
||||
An `autoStart` entry accepts:
|
||||
|
||||
- `providerId`: required. Use `discord-voice` for Discord voice.
|
||||
- `enabled`: optional, default `true`. Set `false` to keep an entry without
|
||||
starting it.
|
||||
- `sessionId`: optional. If omitted, OpenClaw generates a timestamped id.
|
||||
- `title`: optional human-readable title for summaries and CLI output.
|
||||
- `accountId`: optional source account id when more than one account exists.
|
||||
- `guildId`: provider-specific Discord guild id.
|
||||
- `channelId`: provider-specific Discord voice channel id.
|
||||
- `meetingUrl`: provider-specific meeting URL for browser or calendar sources.
|
||||
|
||||
Use `autoStart` when OpenClaw should begin notes capture automatically on
|
||||
gateway startup:
|
||||
|
||||
```json5
|
||||
{
|
||||
plugins: {
|
||||
entries: {
|
||||
"meeting-notes": {
|
||||
config: {
|
||||
autoStart: [
|
||||
{
|
||||
providerId: "discord-voice",
|
||||
guildId: "123",
|
||||
channelId: "456",
|
||||
title: "Weekly planning",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Auto-start retries startup failures up to 12 times with a five-second delay.
|
||||
This lets the notes service wait for channel plugins such as Discord to finish
|
||||
initializing. Sessions that were started by auto-start are stopped and summarized
|
||||
when the plugin service stops cleanly.
|
||||
|
||||
Discord voice capture still needs normal Discord voice setup and permissions.
|
||||
See [Discord voice](/channels/discord#voice-mode).
|
||||
|
||||
## Discord voice
|
||||
|
||||
Discord is the first live source. The Discord plugin owns the voice connection,
|
||||
speaker detection, audio decoding, and transcription. Meeting Notes receives
|
||||
final speaker-labeled utterances and persists them.
|
||||
|
||||
For Discord live capture:
|
||||
|
||||
- Enable and configure the Discord plugin first.
|
||||
- Configure Discord voice mode so OpenClaw can join the target voice channel.
|
||||
- Use `providerId: "discord-voice"`.
|
||||
- Provide `guildId` and `channelId`.
|
||||
- Add `accountId` only when you run more than one Discord account.
|
||||
|
||||
The transcription model is not chosen by Meeting Notes. In Discord `stt-tts`
|
||||
voice mode, STT uses `tools.media.audio`; `voice.model` controls the agent reply
|
||||
model, not transcription. In realtime voice modes, transcription follows the
|
||||
configured realtime provider and model. See [Discord voice](/channels/discord#voice-mode)
|
||||
for the current Discord voice model and provider knobs.
|
||||
|
||||
## Google Meet, Slack huddles, and other sources
|
||||
|
||||
Meeting Notes is intentionally source-neutral. Google Meet, Slack huddles, Zoom,
|
||||
calendar recordings, or browser caption capture should be separate source
|
||||
providers that register with the plugin SDK.
|
||||
|
||||
Recommended source choices:
|
||||
|
||||
- Google Meet live browser/caption support: implement a `live-caption` provider
|
||||
that accepts `meetingUrl` and emits final caption utterances.
|
||||
- Google Meet recordings or downloaded transcripts: implement
|
||||
`posthoc-transcript` or use `manual-transcript` until a provider exists.
|
||||
- Slack huddles today: import post-meeting huddle notes or transcript artifacts.
|
||||
Slack does not expose a general bot-join live huddle audio API.
|
||||
- Slack huddles later: keep the Slack-owned source provider responsible for
|
||||
Slack auth, artifact lookup, and transcript normalization.
|
||||
|
||||
The notes engine should not contain platform joins, browser automation, Slack
|
||||
API polling, or Discord voice logic. Those belong to the owning source plugin.
|
||||
|
||||
## Tool
|
||||
|
||||
Use `meeting_notes` with an `action`:
|
||||
|
||||
- `status`: list registered providers and active sessions.
|
||||
- `start`: start a live notes session.
|
||||
- `stop`: stop a live session and write `summary.md`.
|
||||
- `import`: import a transcript and write `summary.md`.
|
||||
- `summarize`: regenerate a summary for an existing session.
|
||||
|
||||
Discord live notes require `providerId: "discord-voice"`, plus `guildId` and
|
||||
`channelId`. `accountId` is optional when only one Discord account is active.
|
||||
|
||||
```json
|
||||
{
|
||||
"action": "start",
|
||||
"providerId": "discord-voice",
|
||||
"guildId": "123",
|
||||
"channelId": "456",
|
||||
"title": "Weekly planning"
|
||||
}
|
||||
```
|
||||
|
||||
Stop by session id:
|
||||
|
||||
```json
|
||||
{
|
||||
"action": "stop",
|
||||
"sessionId": "meeting-2026-05-22T10-00-00-000Z-a1b2c3d4"
|
||||
}
|
||||
```
|
||||
|
||||
Import a transcript:
|
||||
|
||||
```json
|
||||
{
|
||||
"action": "import",
|
||||
"providerId": "manual-transcript",
|
||||
"title": "Design review",
|
||||
"transcript": "Alex: We decided to ship the Discord source first.\nSam: Action item: add Slack huddle import later."
|
||||
}
|
||||
```
|
||||
|
||||
`manual-transcript` splits plain transcript text into utterances. Use it for
|
||||
copied Google Meet notes, Slack huddle summaries, calendar transcripts, or any
|
||||
source that already produced text.
|
||||
|
||||
## Storage layout
|
||||
|
||||
Artifacts are stored under the OpenClaw state directory:
|
||||
|
||||
```text
|
||||
$OPENCLAW_STATE_DIR/meeting-notes/YYYY-MM-DD/<session>/
|
||||
metadata.json
|
||||
transcript.jsonl
|
||||
summary.json
|
||||
summary.md
|
||||
```
|
||||
|
||||
If `OPENCLAW_STATE_DIR` is unset, the default state directory is
|
||||
`~/.openclaw`. A normal local install therefore writes notes under
|
||||
`~/.openclaw/meeting-notes/...`.
|
||||
|
||||
Each file has one job:
|
||||
|
||||
- `metadata.json`: session id, source provider, title, start time, stop time,
|
||||
and provider metadata.
|
||||
- `transcript.jsonl`: append-only speaker utterances. Each line is one JSON
|
||||
object with the utterance text and the session id.
|
||||
- `summary.json`: structured summary data used by tooling, including the
|
||||
speaker-labeled transcript window used for the generated summary.
|
||||
- `summary.md`: human-readable notes for terminals, editors, and document
|
||||
workflows, including a speaker-labeled transcript section.
|
||||
|
||||
The date directory comes from the session start time, so multiple meetings per
|
||||
day stay grouped. If a human session id repeats across days, use the
|
||||
date-qualified selector from `openclaw meeting-notes list`, such as
|
||||
`2026-05-22/standup`.
|
||||
|
||||
By default, OpenClaw generates timestamped session ids:
|
||||
|
||||
```text
|
||||
meeting-2026-05-22T10-00-00-000Z-a1b2c3d4
|
||||
```
|
||||
|
||||
That means ten meetings on the same day become ten sibling directories:
|
||||
|
||||
```text
|
||||
~/.openclaw/meeting-notes/2026-05-22/
|
||||
meeting-2026-05-22T09-00-00-000Z-a1b2c3d4/
|
||||
meeting-2026-05-22T10-30-00-000Z-b2c3d4e5/
|
||||
meeting-2026-05-22T13-00-00-000Z-c3d4e5f6/
|
||||
```
|
||||
|
||||
Configure `sessionId` only when that id is unique for the day. Human ids such as
|
||||
`standup` are fine for one recurring meeting per day. If the same id appears on
|
||||
multiple days, use the date-qualified selector in the CLI.
|
||||
|
||||
## CLI access
|
||||
|
||||
Use the read-only CLI to find or print stored summaries:
|
||||
|
||||
```bash
|
||||
openclaw meeting-notes list
|
||||
openclaw meeting-notes show <session>
|
||||
openclaw meeting-notes path <session>
|
||||
openclaw meeting-notes path <session> --transcript
|
||||
```
|
||||
|
||||
See [Meeting Notes CLI](/cli/meeting-notes) for the full command reference.
|
||||
|
||||
## Long meetings
|
||||
|
||||
For long meetings, utterances are appended to `transcript.jsonl` as they arrive.
|
||||
Summary generation reads a bounded window controlled by
|
||||
`plugins.entries.meeting-notes.config.maxUtterances` (default: `2000`) so a
|
||||
multi-hour call does not require unbounded summary memory.
|
||||
|
||||
This means the transcript can keep growing on disk, while summarization stays
|
||||
bounded. Increase `maxUtterances` when you need more of a multi-hour meeting in
|
||||
the generated summary and speaker-labeled transcript section. Decrease it when
|
||||
summaries are too slow or too large.
|
||||
|
||||
Current summaries are generated when a session stops, after an import, or when
|
||||
the `summarize` action runs. They are not continuously rewritten for every
|
||||
utterance.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### `meeting_notes` is missing
|
||||
|
||||
Check that the plugin is installed or loaded from source, and that plugin
|
||||
loading does not exclude it:
|
||||
|
||||
```bash
|
||||
openclaw config validate
|
||||
openclaw meeting-notes list
|
||||
```
|
||||
|
||||
If `plugins.allow` is set, it must include `meeting-notes`. If `plugins.deny`
|
||||
contains `meeting-notes`, remove it.
|
||||
|
||||
### Auto-start does not join Discord
|
||||
|
||||
Confirm the `autoStart` entry uses `providerId: "discord-voice"` and includes
|
||||
both `guildId` and `channelId`. If you run multiple Discord accounts, include
|
||||
`accountId`. Also verify Discord voice works outside Meeting Notes by joining
|
||||
the same voice channel through the Discord voice commands.
|
||||
|
||||
### Summary is missing
|
||||
|
||||
Live sessions write `summary.md` when stopped. Stop the session with
|
||||
`meeting_notes` action `stop`, then inspect it:
|
||||
|
||||
```bash
|
||||
openclaw meeting-notes list
|
||||
openclaw meeting-notes path <session>
|
||||
```
|
||||
|
||||
Use `meeting_notes` action `summarize` to regenerate `summary.md` for an
|
||||
existing stored session.
|
||||
|
||||
### Selector is ambiguous
|
||||
|
||||
If you reused a human session id such as `standup`, use the date-qualified
|
||||
selector shown by `openclaw meeting-notes list`:
|
||||
|
||||
```bash
|
||||
openclaw meeting-notes show 2026-05-22/standup
|
||||
```
|
||||
|
||||
## Related
|
||||
|
||||
- [Meeting Notes CLI](/cli/meeting-notes)
|
||||
- [Discord voice](/channels/discord#voice-mode)
|
||||
- [Plugin management](/tools/plugin)
|
||||
- [Plugin architecture](/plugins/architecture)
|
||||
@@ -151,7 +151,7 @@ commands.
|
||||
| [diagnostics-otel](/plugins/reference/diagnostics-otel) | OpenClaw diagnostics OpenTelemetry exporter. | `@openclaw/diagnostics-otel`<br />npm; ClawHub: `clawhub:@openclaw/diagnostics-otel` | plugin |
|
||||
| [diagnostics-prometheus](/plugins/reference/diagnostics-prometheus) | OpenClaw diagnostics Prometheus exporter. | `@openclaw/diagnostics-prometheus`<br />npm; ClawHub: `clawhub:@openclaw/diagnostics-prometheus` | plugin |
|
||||
| [diffs](/plugins/reference/diffs) | Read-only diff viewer and file renderer for agents. | `@openclaw/diffs`<br />npm; ClawHub | contracts: tools; skills |
|
||||
| [discord](/plugins/reference/discord) | Adds the Discord channel surface for sending and receiving OpenClaw messages. | `@openclaw/discord`<br />npm; ClawHub | channels: discord; contracts: meetingNotesSourceProviders |
|
||||
| [discord](/plugins/reference/discord) | Adds the Discord channel surface for sending and receiving OpenClaw messages. | `@openclaw/discord`<br />npm; ClawHub | channels: discord; contracts: transcriptSourceProviders |
|
||||
| [feishu](/plugins/reference/feishu) | Adds the Feishu channel surface for sending and receiving OpenClaw messages. | `@openclaw/feishu`<br />npm; ClawHub | channels: feishu; contracts: tools; skills |
|
||||
| [google-meet](/plugins/reference/google-meet) | Join Google Meet calls through Chrome or Twilio transports. | `@openclaw/google-meet`<br />npm; ClawHub | contracts: tools |
|
||||
| [googlechat](/plugins/reference/googlechat) | Adds the Google Chat channel surface for sending and receiving OpenClaw messages. | `@openclaw/googlechat`<br />npm; ClawHub | channels: googlechat |
|
||||
@@ -175,9 +175,8 @@ commands.
|
||||
|
||||
## Source checkout only
|
||||
|
||||
| Plugin | Description | Distribution | Surface |
|
||||
| ------------------------------------------------- | --------------------------------------------------------------------------- | --------------------------------------------------- | --------------------------------------------- |
|
||||
| [meeting-notes](/plugins/reference/meeting-notes) | Capture meeting transcripts from channel-owned sources and write summaries. | `@openclaw/meeting-notes`<br />source checkout only | contracts: meetingNotesSourceProviders, tools |
|
||||
| [qa-channel](/plugins/reference/qa-channel) | Adds the QA Channel surface for sending and receiving OpenClaw messages. | `@openclaw/qa-channel`<br />source checkout only | channels: qa-channel |
|
||||
| [qa-lab](/plugins/reference/qa-lab) | OpenClaw QA lab plugin with private debugger UI and scenario runner. | `@openclaw/qa-lab`<br />source checkout only | plugin |
|
||||
| [qa-matrix](/plugins/reference/qa-matrix) | Matrix QA transport runner and substrate. | `@openclaw/qa-matrix`<br />source checkout only | plugin |
|
||||
| Plugin | Description | Distribution | Surface |
|
||||
| ------------------------------------------- | ------------------------------------------------------------------------ | ------------------------------------------------ | -------------------- |
|
||||
| [qa-channel](/plugins/reference/qa-channel) | Adds the QA Channel surface for sending and receiving OpenClaw messages. | `@openclaw/qa-channel`<br />source checkout only | channels: qa-channel |
|
||||
| [qa-lab](/plugins/reference/qa-lab) | OpenClaw QA lab plugin with private debugger UI and scenario runner. | `@openclaw/qa-lab`<br />source checkout only | plugin |
|
||||
| [qa-matrix](/plugins/reference/qa-matrix) | Matrix QA transport runner and substrate. | `@openclaw/qa-matrix`<br />source checkout only | plugin |
|
||||
|
||||
@@ -44,7 +44,7 @@ pnpm plugins:inventory:gen
|
||||
| [diagnostics-otel](/plugins/reference/diagnostics-otel) | OpenClaw diagnostics OpenTelemetry exporter. | `@openclaw/diagnostics-otel`<br />npm; ClawHub: `clawhub:@openclaw/diagnostics-otel` | plugin |
|
||||
| [diagnostics-prometheus](/plugins/reference/diagnostics-prometheus) | OpenClaw diagnostics Prometheus exporter. | `@openclaw/diagnostics-prometheus`<br />npm; ClawHub: `clawhub:@openclaw/diagnostics-prometheus` | plugin |
|
||||
| [diffs](/plugins/reference/diffs) | Read-only diff viewer and file renderer for agents. | `@openclaw/diffs`<br />npm; ClawHub | contracts: tools; skills |
|
||||
| [discord](/plugins/reference/discord) | Adds the Discord channel surface for sending and receiving OpenClaw messages. | `@openclaw/discord`<br />npm; ClawHub | channels: discord; contracts: meetingNotesSourceProviders |
|
||||
| [discord](/plugins/reference/discord) | Adds the Discord channel surface for sending and receiving OpenClaw messages. | `@openclaw/discord`<br />npm; ClawHub | channels: discord; contracts: transcriptSourceProviders |
|
||||
| [document-extract](/plugins/reference/document-extract) | Extract text and fallback page images from local document attachments. | `@openclaw/document-extract-plugin`<br />included in OpenClaw | contracts: documentExtractors |
|
||||
| [duckduckgo](/plugins/reference/duckduckgo) | Adds web search provider support. | `@openclaw/duckduckgo-plugin`<br />included in OpenClaw | contracts: webSearchProviders |
|
||||
| [elevenlabs](/plugins/reference/elevenlabs) | Adds media understanding provider support. Adds realtime transcription provider support. Adds text-to-speech provider support. | `@openclaw/elevenlabs-speech`<br />included in OpenClaw | contracts: mediaUnderstandingProviders, realtimeTranscriptionProviders, speechProviders |
|
||||
@@ -73,7 +73,6 @@ pnpm plugins:inventory:gen
|
||||
| [lobster](/plugins/reference/lobster) | Typed workflow tool with resumable approvals. | `@openclaw/lobster`<br />npm; ClawHub | contracts: tools |
|
||||
| [matrix](/plugins/reference/matrix) | Adds the Matrix channel surface for sending and receiving OpenClaw messages. | `@openclaw/matrix`<br />ClawHub: `clawhub:@openclaw/matrix`; npm | channels: matrix |
|
||||
| [mattermost](/plugins/reference/mattermost) | Adds the Mattermost channel surface for sending and receiving OpenClaw messages. | `@openclaw/mattermost`<br />included in OpenClaw | channels: mattermost |
|
||||
| [meeting-notes](/plugins/reference/meeting-notes) | Capture meeting transcripts from channel-owned sources and write summaries. | `@openclaw/meeting-notes`<br />source checkout only | contracts: meetingNotesSourceProviders, tools |
|
||||
| [memory-core](/plugins/reference/memory-core) | Adds memory embedding provider support. Adds agent-callable tools. | `@openclaw/memory-core`<br />included in OpenClaw | contracts: memoryEmbeddingProviders, tools |
|
||||
| [memory-lancedb](/plugins/reference/memory-lancedb) | Adds agent-callable tools. | `@openclaw/memory-lancedb`<br />npm; ClawHub | contracts: tools |
|
||||
| [memory-wiki](/plugins/reference/memory-wiki) | Persistent wiki compiler and Obsidian-friendly knowledge vault for OpenClaw. | `@openclaw/memory-wiki`<br />included in OpenClaw | contracts: tools; skills |
|
||||
|
||||
@@ -16,7 +16,7 @@ Adds the Discord channel surface for sending and receiving OpenClaw messages.
|
||||
|
||||
## Surface
|
||||
|
||||
channels: discord; contracts: meetingNotesSourceProviders
|
||||
channels: discord; contracts: transcriptSourceProviders
|
||||
|
||||
## Related docs
|
||||
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
---
|
||||
summary: "Capture meeting transcripts from channel-owned sources and write summaries."
|
||||
read_when:
|
||||
- You are installing, configuring, or auditing the meeting-notes plugin
|
||||
title: "Meeting Notes plugin"
|
||||
---
|
||||
|
||||
# Meeting Notes plugin
|
||||
|
||||
Capture meeting transcripts from channel-owned sources and write summaries.
|
||||
|
||||
## Distribution
|
||||
|
||||
- Package: `@openclaw/meeting-notes`
|
||||
- Install route: source checkout only
|
||||
|
||||
## Surface
|
||||
|
||||
contracts: meetingNotesSourceProviders, tools
|
||||
|
||||
## Related docs
|
||||
|
||||
- [meeting-notes](/plugins/meeting-notes)
|
||||
@@ -626,7 +626,7 @@ releases.
|
||||
| `plugin-sdk/speech` | Speech helpers | Speech provider types plus provider-facing directive, registry, validation helpers, and OpenAI-compatible TTS builder |
|
||||
| `plugin-sdk/speech-core` | Shared speech core | Speech provider types, registry, directives, normalization |
|
||||
| `plugin-sdk/realtime-transcription` | Realtime transcription helpers | Provider types, registry helpers, and shared WebSocket session helper |
|
||||
| `plugin-sdk/realtime-voice` | Realtime voice helpers | Provider types, registry/resolution helpers, bridge session helpers, shared agent talk-back queues, active-run voice control, transcript/event health, echo suppression, and fast context consult helpers |
|
||||
| `plugin-sdk/realtime-voice` | Realtime voice helpers | Provider types, registry/resolution helpers, bridge session helpers, shared agent talk-back queues, active-run voice control, transcript/event health, echo suppression, consult question matching, forced-consult coordination, turn-context tracking, output activity tracking, and fast context consult helpers |
|
||||
| `plugin-sdk/image-generation` | Image-generation helpers | Image generation provider types plus image asset/data URL helpers and the OpenAI-compatible image provider builder |
|
||||
| `plugin-sdk/image-generation-core` | Shared image-generation core | Image-generation types, failover, auth, and registry helpers |
|
||||
| `plugin-sdk/music-generation` | Music-generation helpers | Music-generation provider/request/result types |
|
||||
|
||||
@@ -264,6 +264,7 @@ focused channel/runtime subpaths, `config-contracts`, `string-coerce-runtime`,
|
||||
| `plugin-sdk/tool-plugin` | Define a simple typed agent-tool plugin and expose static metadata for manifest generation |
|
||||
| `plugin-sdk/tool-payload` | Extract normalized payloads from tool result objects |
|
||||
| `plugin-sdk/tool-send` | Extract canonical send target fields from tool args |
|
||||
| `plugin-sdk/sandbox` | Sandbox backend types and SSH/OpenShell command helpers, including fail-fast exec command preflight |
|
||||
| `plugin-sdk/temp-path` | Shared temp-download path helpers and private secure temp workspaces |
|
||||
| `plugin-sdk/logging-core` | Subsystem logger and redaction helpers |
|
||||
| `plugin-sdk/markdown-table-runtime` | Markdown table mode and conversion helpers |
|
||||
@@ -321,23 +322,21 @@ focused channel/runtime subpaths, `config-contracts`, `string-coerce-runtime`,
|
||||
| `plugin-sdk/media-mime` | Narrow MIME normalization, file-extension mapping, MIME detection, and media-kind helpers |
|
||||
| `plugin-sdk/media-store` | Narrow media store helpers such as `saveMediaBuffer` and `saveMediaStream` |
|
||||
| `plugin-sdk/media-generation-runtime` | Shared media-generation failover helpers, candidate selection, and missing-model messaging |
|
||||
| `plugin-sdk/meeting-notes` | Meeting notes source provider types, registry lookup, and provider id normalization helpers |
|
||||
| `plugin-sdk/media-understanding` | Media understanding provider types plus provider-facing image/audio/structured-extraction helper exports |
|
||||
| `plugin-sdk/meeting-notes` | Meeting notes source provider types, registry helpers, and provider id normalization |
|
||||
| `plugin-sdk/text-chunking` | Text and markdown chunking/render helpers, markdown table conversion, directive-tag stripping, and safe-text utilities |
|
||||
| `plugin-sdk/text-chunking` | Outbound text chunking helper |
|
||||
| `plugin-sdk/speech` | Speech provider types plus provider-facing directive, registry, validation, OpenAI-compatible TTS builder, and speech helper exports |
|
||||
| `plugin-sdk/speech-core` | Shared speech provider types, registry, directive, normalization, and speech helper exports |
|
||||
| `plugin-sdk/realtime-transcription` | Realtime transcription provider types, registry helpers, and shared WebSocket session helper |
|
||||
| `plugin-sdk/realtime-bootstrap-context` | Realtime profile bootstrap helper for bounded `IDENTITY.md`, `USER.md`, and `SOUL.md` context injection |
|
||||
| `plugin-sdk/realtime-voice` | Realtime voice provider types and registry helpers |
|
||||
| `plugin-sdk/realtime-voice` | Realtime voice provider types, registry helpers, and shared realtime voice behavior helpers, including output activity tracking |
|
||||
| `plugin-sdk/image-generation` | Image generation provider types plus image asset/data URL helpers and the OpenAI-compatible image provider builder |
|
||||
| `plugin-sdk/image-generation-core` | Shared image-generation types, failover, auth, and registry helpers |
|
||||
| `plugin-sdk/music-generation` | Music generation provider/request/result types |
|
||||
| `plugin-sdk/music-generation-core` | Shared music-generation types, failover helpers, provider lookup, and model-ref parsing |
|
||||
| `plugin-sdk/video-generation` | Video generation provider/request/result types |
|
||||
| `plugin-sdk/video-generation-core` | Shared video-generation types, failover helpers, provider lookup, and model-ref parsing |
|
||||
| `plugin-sdk/meeting-notes` | Shared meeting-notes source provider types, registry helpers, session descriptors, and utterance metadata |
|
||||
| `plugin-sdk/transcripts` | Shared transcripts source provider types, registry helpers, session descriptors, and utterance metadata |
|
||||
| `plugin-sdk/webhook-targets` | Webhook target registry and route-install helpers |
|
||||
| `plugin-sdk/webhook-path` | Deprecated compatibility alias; use `plugin-sdk/webhook-ingress` |
|
||||
| `plugin-sdk/web-media` | Shared remote/local media loading helpers |
|
||||
|
||||
@@ -260,6 +260,25 @@ OPENCLAW_LIVE_TEST=1 OPENCLAW_LIVE_OLLAMA=1 OPENCLAW_LIVE_OLLAMA_WEB_SEARCH=0 \
|
||||
pnpm test:live -- extensions/ollama/ollama.live.test.ts
|
||||
```
|
||||
|
||||
For Ollama Cloud API-key smoke tests, point the live test at `https://ollama.com`
|
||||
and choose a hosted model from the current catalog:
|
||||
|
||||
```bash
|
||||
export OLLAMA_API_KEY='<your-ollama-cloud-api-key>'
|
||||
|
||||
OPENCLAW_LIVE_TEST=1 \
|
||||
OPENCLAW_LIVE_OLLAMA=1 \
|
||||
OPENCLAW_LIVE_OLLAMA_BASE_URL=https://ollama.com \
|
||||
OPENCLAW_LIVE_OLLAMA_MODEL=glm-5.1:cloud \
|
||||
OPENCLAW_LIVE_OLLAMA_WEB_SEARCH=1 \
|
||||
pnpm test:live -- extensions/ollama/ollama.live.test.ts
|
||||
```
|
||||
|
||||
The cloud smoke runs text, native stream, and web search. It skips embeddings by
|
||||
default for `https://ollama.com` because Ollama Cloud API keys may not authorize
|
||||
`/api/embed`. Set `OPENCLAW_LIVE_OLLAMA_EMBEDDINGS=1` when you explicitly want
|
||||
the live test to fail if the configured cloud key cannot use the embed endpoint.
|
||||
|
||||
To add a new model, simply pull it with Ollama:
|
||||
|
||||
```bash
|
||||
|
||||
@@ -193,7 +193,7 @@ Choose your preferred auth method and follow the setup steps.
|
||||
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.
|
||||
OpenClaw does **not** expose `openai/gpt-5.3-codex-spark`. Live OpenAI API requests reject that direct provider route. Use `openai-codex/gpt-5.3-codex-spark` only when the Codex catalog exposes it for your signed-in account.
|
||||
</Warning>
|
||||
|
||||
</Tab>
|
||||
@@ -251,7 +251,9 @@ Choose your preferred auth method and follow the setup steps.
|
||||
Prefer `openai/gpt-5.5` for new subscription-backed agent config. Older
|
||||
`openai-codex/gpt-*` refs are legacy PI routes, not the native Codex runtime
|
||||
path; run `openclaw doctor --fix` when you want to migrate them to canonical
|
||||
`openai/*` refs.
|
||||
`openai/*` refs. `openai-codex/gpt-5.3-codex-spark` is the exception for
|
||||
accounts whose Codex catalog advertises that model; direct `openai/*` and
|
||||
Azure refs for it remain suppressed.
|
||||
</Warning>
|
||||
|
||||
<Note>
|
||||
@@ -549,7 +551,7 @@ See [Video Generation](/tools/video-generation) for shared tool parameters, prov
|
||||
|
||||
OpenClaw adds a shared GPT-5 prompt contribution for GPT-5-family runs on OpenClaw-assembled prompt surfaces. It applies by model id, so PI/provider routes such as legacy pre-repair refs (`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 does not receive this OpenClaw GPT-5 overlay through Codex app-server developer instructions. Native Codex keeps Codex-owned base, model, personality, and project-doc behavior; OpenClaw contributes only runtime context such as channel delivery, OpenClaw dynamic tools, ACP delegation, workspace context, and OpenClaw skills.
|
||||
The bundled native Codex harness does not receive this OpenClaw GPT-5 overlay through Codex app-server developer instructions. Native Codex keeps Codex-owned base, model, and project-doc behavior, while OpenClaw disables Codex's built-in personality for native threads so agent workspace personality files stay authoritative. OpenClaw contributes only runtime context such as channel delivery, OpenClaw dynamic tools, ACP delegation, workspace context, and OpenClaw skills.
|
||||
|
||||
The GPT-5 contribution adds a tagged behavior contract for persona persistence, execution safety, tool discipline, output shape, completion checks, and verification on matching OpenClaw-assembled prompts. Channel-specific reply and silent-message behavior stays in the shared OpenClaw system prompt and outbound delivery policy. The friendly interaction-style layer is separate and configurable.
|
||||
|
||||
|
||||
@@ -49,9 +49,10 @@ the maintainer-only release runbook.
|
||||
|
||||
1. Start from current `main`: pull latest, confirm the target commit is pushed,
|
||||
and confirm current `main` CI is green enough to branch from it.
|
||||
2. Rewrite the top `CHANGELOG.md` section from real commit history with
|
||||
`/changelog`, keep entries user-facing, commit it, push it, and rebase/pull
|
||||
once more before branching.
|
||||
2. Generate the top `CHANGELOG.md` section from merged PRs and all direct
|
||||
commits since the last reachable release tag. Keep entries user-facing,
|
||||
dedupe overlapping PR/direct-commit entries, commit the rewrite, push it,
|
||||
and rebase/pull once more before branching.
|
||||
3. Review release compatibility records in
|
||||
`src/plugins/compat/registry.ts` and
|
||||
`src/commands/doctor/shared/deprecation-compat.ts`. Remove expired
|
||||
@@ -196,6 +197,9 @@ vYYYY.M.D-beta.N` from the matching `release/YYYY.M.D` branch. The helper runs
|
||||
QA-lab through a local OTLP/HTTP receiver and verifies trace, metric, and log
|
||||
export plus bounded trace attributes and content/identifier redaction without
|
||||
requiring Opik, Langfuse, or another external collector.
|
||||
- Run `pnpm qa:otel:collector-smoke` when validating collector compatibility.
|
||||
It routes the same QA-lab OTLP export through a real OpenTelemetry Collector
|
||||
Docker container before the local receiver assertions.
|
||||
- Run `pnpm qa:prometheus:smoke` when validating protected Prometheus scraping.
|
||||
It exercises QA-lab, rejects unauthenticated scrapes, and verifies
|
||||
release-critical metric families stay free of prompt content, raw identifiers,
|
||||
@@ -528,7 +532,8 @@ Release QA Lab coverage includes:
|
||||
baseline using the agentic parity pack
|
||||
- fast live Matrix QA profile using the `qa-live-shared` environment
|
||||
- live Telegram QA lane using Convex CI credential leases
|
||||
- `pnpm qa:otel:smoke`, `pnpm qa:prometheus:smoke`, or
|
||||
- `pnpm qa:otel:smoke`, `pnpm qa:otel:collector-smoke`,
|
||||
`pnpm qa:prometheus:smoke`, or
|
||||
`pnpm qa:observability:smoke` when release telemetry needs explicit local
|
||||
proof
|
||||
|
||||
|
||||
@@ -50,9 +50,9 @@ OpenClaw persists sessions in two layers:
|
||||
- Append-only transcript with tree structure (entries have `id` + `parentId`)
|
||||
- Stores the actual conversation + tool calls + compaction summaries
|
||||
- Used to rebuild the model context for future turns
|
||||
- Large pre-compaction debug checkpoints are skipped once the active
|
||||
transcript exceeds the checkpoint size cap, avoiding a second giant
|
||||
`.checkpoint.*.jsonl` copy.
|
||||
- Compaction checkpoints are metadata over the compacted successor
|
||||
transcript. New compactions do not write a second `.checkpoint.*.jsonl`
|
||||
copy.
|
||||
|
||||
Gateway history readers should avoid materializing the whole transcript unless
|
||||
the surface explicitly needs arbitrary historical access. First-page history,
|
||||
@@ -278,6 +278,10 @@ In the embedded Pi agent, auto-compaction triggers in two cases:
|
||||
number of tokens`, `input token count exceeds the maximum number of input
|
||||
tokens`, `input is too long for the model`, `ollama error: context length
|
||||
exceeded`, and similar provider-shaped variants) → compact → retry.
|
||||
When the provider reports the attempted token count, OpenClaw forwards that
|
||||
observed count into overflow recovery compaction. If the provider confirms
|
||||
overflow but does not expose a parseable count, OpenClaw passes a minimally
|
||||
over-budget synthetic count to compaction engines and diagnostics.
|
||||
If overflow recovery still fails, OpenClaw surfaces explicit guidance to the
|
||||
user and preserves the current session mapping instead of silently rotating
|
||||
the session key to a fresh session id. The next step is operator-controlled:
|
||||
@@ -353,8 +357,8 @@ OpenClaw also enforces a safety floor for embedded runs:
|
||||
disable.
|
||||
- When `agents.defaults.compaction.truncateAfterCompaction` is enabled,
|
||||
OpenClaw rotates the active transcript to a compacted successor JSONL after
|
||||
compaction. The old full transcript remains archived and linked from the
|
||||
compaction checkpoint instead of being rewritten in place.
|
||||
compaction. Branch/restore checkpoint actions use that compacted successor;
|
||||
legacy pre-compaction checkpoint files remain readable while referenced.
|
||||
|
||||
Why: leave enough headroom for multi-turn "housekeeping" (like memory writes) before compaction becomes unavoidable.
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ title: "Tests"
|
||||
- `pnpm test:e2e`: Runs the repo E2E aggregate: gateway end-to-end smoke tests plus the Control UI mocked browser E2E lane.
|
||||
- `pnpm test:e2e:gateway`: Runs gateway end-to-end smoke tests (multi-instance WS/HTTP/node pairing). Defaults to `threads` + `isolate: false` with adaptive workers in `vitest.e2e.config.ts`; tune with `OPENCLAW_E2E_WORKERS=<n>` and set `OPENCLAW_E2E_VERBOSE=1` for verbose logs.
|
||||
- `pnpm test:live`: Runs provider live tests (minimax/zai). Requires API keys and `LIVE=1` (or provider-specific `*_LIVE_TEST=1`) to unskip.
|
||||
- `pnpm test:docker:all`: Builds the shared live-test image, packs OpenClaw once as an npm tarball, builds/reuses a bare Node/Git runner image plus a functional image that installs that tarball into `/app`, then runs Docker smoke lanes with `OPENCLAW_SKIP_DOCKER_BUILD=1` through a weighted scheduler. The bare image (`OPENCLAW_DOCKER_E2E_BARE_IMAGE`) is used for installer/update/plugin-dependency lanes; those lanes mount the prebuilt tarball instead of using copied repo sources. The functional image (`OPENCLAW_DOCKER_E2E_FUNCTIONAL_IMAGE`) is used for normal built-app functionality lanes. `scripts/package-openclaw-for-docker.mjs` is the single local/CI package packer and validates the tarball plus `dist/postinstall-inventory.json` before Docker consumes it. Docker lane definitions live in `scripts/lib/docker-e2e-scenarios.mjs`; planner logic lives in `scripts/lib/docker-e2e-plan.mjs`; `scripts/test-docker-all.mjs` executes the selected plan. `node scripts/test-docker-all.mjs --plan-json` emits the scheduler-owned CI plan for selected lanes, image kinds, package/live-image needs, state scenarios, and credential checks without building or running Docker. `OPENCLAW_DOCKER_ALL_PARALLELISM=<n>` controls process slots and defaults to 10; `OPENCLAW_DOCKER_ALL_TAIL_PARALLELISM=<n>` controls the provider-sensitive tail pool and defaults to 10. Heavy lane caps default to `OPENCLAW_DOCKER_ALL_LIVE_LIMIT=9`, `OPENCLAW_DOCKER_ALL_NPM_LIMIT=10`, and `OPENCLAW_DOCKER_ALL_SERVICE_LIMIT=7`; provider caps default to one heavy lane per provider via `OPENCLAW_DOCKER_ALL_LIVE_CLAUDE_LIMIT=4`, `OPENCLAW_DOCKER_ALL_LIVE_CODEX_LIMIT=4`, and `OPENCLAW_DOCKER_ALL_LIVE_GEMINI_LIMIT=4`. Use `OPENCLAW_DOCKER_ALL_WEIGHT_LIMIT` or `OPENCLAW_DOCKER_ALL_DOCKER_LIMIT` for larger hosts. If one lane exceeds the effective weight or resource cap on a low-parallelism host, it can still start from an empty pool and will run alone until it releases capacity. Lane starts are staggered by 2 seconds by default to avoid local Docker daemon create storms; override with `OPENCLAW_DOCKER_ALL_START_STAGGER_MS=<ms>`. The runner preflights Docker by default, cleans stale OpenClaw E2E containers, emits active-lane status every 30 seconds, shares provider CLI tool caches between compatible lanes, retries transient live-provider failures once by default (`OPENCLAW_DOCKER_ALL_LIVE_RETRIES=<n>`), and stores lane timings in `.artifacts/docker-tests/lane-timings.json` for longest-first ordering on later runs. Use `OPENCLAW_DOCKER_ALL_DRY_RUN=1` to print the lane manifest without running Docker, `OPENCLAW_DOCKER_ALL_STATUS_INTERVAL_MS=<ms>` to tune status output, or `OPENCLAW_DOCKER_ALL_TIMINGS=0` to disable timing reuse. Use `OPENCLAW_DOCKER_ALL_LIVE_MODE=skip` for deterministic/local lanes only or `OPENCLAW_DOCKER_ALL_LIVE_MODE=only` for live-provider lanes only; package aliases are `pnpm test:docker:local:all` and `pnpm test:docker:live:all`. Live-only mode merges main and tail live lanes into one longest-first pool so provider buckets can pack Claude, Codex, and Gemini work together. The runner stops scheduling new pooled lanes after the first failure unless `OPENCLAW_DOCKER_ALL_FAIL_FAST=0` is set, and each lane has a 120-minute fallback timeout overrideable with `OPENCLAW_DOCKER_ALL_LANE_TIMEOUT_MS`; selected live/tail lanes use tighter per-lane caps. CLI backend Docker setup commands have their own timeout via `OPENCLAW_LIVE_CLI_BACKEND_SETUP_TIMEOUT_SECONDS` (default 180). Per-lane logs, `summary.json`, `failures.json`, and phase timings are written under `.artifacts/docker-tests/<run-id>/`; use `pnpm test:docker:timings <summary.json>` to inspect slow lanes and `pnpm test:docker:rerun <run-id|summary.json|failures.json>` to print cheap targeted rerun commands.
|
||||
- `pnpm test:docker:all`: Builds the shared live-test image, packs OpenClaw once as an npm tarball, builds/reuses a bare Node/Git runner image plus a functional image that installs that tarball into `/app`, then runs Docker smoke lanes with `OPENCLAW_SKIP_DOCKER_BUILD=1` through a weighted scheduler. The bare image (`OPENCLAW_DOCKER_E2E_BARE_IMAGE`) is used for installer/update/plugin-dependency lanes; those lanes mount the prebuilt tarball instead of using copied repo sources. The functional image (`OPENCLAW_DOCKER_E2E_FUNCTIONAL_IMAGE`) is used for normal built-app functionality lanes. `scripts/package-openclaw-for-docker.mjs` is the single local/CI package packer and validates the tarball plus `dist/postinstall-inventory.json` before Docker consumes it. Docker lane definitions live in `scripts/lib/docker-e2e-scenarios.mjs`; planner logic lives in `scripts/lib/docker-e2e-plan.mjs`; `scripts/test-docker-all.mjs` executes the selected plan. `node scripts/test-docker-all.mjs --plan-json` emits the scheduler-owned CI plan for selected lanes, image kinds, package/live-image needs, state scenarios, and credential checks without building or running Docker. `OPENCLAW_DOCKER_ALL_PARALLELISM=<n>` controls process slots and defaults to 10; `OPENCLAW_DOCKER_ALL_TAIL_PARALLELISM=<n>` controls the provider-sensitive tail pool and defaults to 10. Heavy lane caps default to `OPENCLAW_DOCKER_ALL_LIVE_LIMIT=9`, `OPENCLAW_DOCKER_ALL_NPM_LIMIT=10`, and `OPENCLAW_DOCKER_ALL_SERVICE_LIMIT=7`; provider caps default to one heavy lane per provider via `OPENCLAW_DOCKER_ALL_LIVE_CLAUDE_LIMIT=4`, `OPENCLAW_DOCKER_ALL_LIVE_CODEX_LIMIT=4`, and `OPENCLAW_DOCKER_ALL_LIVE_GEMINI_LIMIT=4`. Use `OPENCLAW_DOCKER_ALL_WEIGHT_LIMIT` or `OPENCLAW_DOCKER_ALL_DOCKER_LIMIT` for larger hosts. If one lane exceeds the effective weight or resource cap on a low-parallelism host, it can still start from an empty pool and will run alone until it releases capacity. Lane starts are staggered by 2 seconds by default to avoid local Docker daemon create storms; override with `OPENCLAW_DOCKER_ALL_START_STAGGER_MS=<ms>`. The runner preflights Docker by default, cleans stale OpenClaw E2E containers, emits active-lane status every 30 seconds, shares provider CLI tool caches between compatible lanes, retries transient live-provider failures once by default (`OPENCLAW_DOCKER_ALL_LIVE_RETRIES=<n>`), and stores lane timings in `.artifacts/docker-tests/lane-timings.json` for longest-first ordering on later runs. Use `OPENCLAW_DOCKER_ALL_DRY_RUN=1` to print the lane manifest without running Docker, `OPENCLAW_DOCKER_ALL_STATUS_INTERVAL_MS=<ms>` to tune status output, or `OPENCLAW_DOCKER_ALL_TIMINGS=0` to disable timing reuse. Use `OPENCLAW_DOCKER_ALL_LIVE_MODE=skip` for deterministic/local lanes only or `OPENCLAW_DOCKER_ALL_LIVE_MODE=only` for live-provider lanes only; package aliases are `pnpm test:docker:local:all` and `pnpm test:docker:live:all`. Live-only mode merges main and tail live lanes into one longest-first pool so provider buckets can pack Claude, Codex, and Gemini work together. The runner stops scheduling new pooled lanes after the first failure unless `OPENCLAW_DOCKER_ALL_FAIL_FAST=0` is set, and each lane has a 120-minute fallback timeout overridable with `OPENCLAW_DOCKER_ALL_LANE_TIMEOUT_MS`; selected live/tail lanes use tighter per-lane caps. CLI backend Docker setup commands have their own timeout via `OPENCLAW_LIVE_CLI_BACKEND_SETUP_TIMEOUT_SECONDS` (default 180). Per-lane logs, `summary.json`, `failures.json`, and phase timings are written under `.artifacts/docker-tests/<run-id>/`; use `pnpm test:docker:timings <summary.json>` to inspect slow lanes and `pnpm test:docker:rerun <run-id|summary.json|failures.json>` to print cheap targeted rerun commands.
|
||||
- `pnpm test:docker:browser-cdp-snapshot`: Builds a Chromium-backed source E2E container, starts raw CDP plus an isolated Gateway, runs `browser doctor --deep`, and verifies CDP role snapshots include link URLs, cursor-promoted clickables, iframe refs, and frame metadata.
|
||||
- `pnpm test:docker:skill-install`: Installs the packed OpenClaw tarball in a bare Docker runner, disables `skills.install.allowUploadedArchives`, resolves a current skill slug from live ClawHub search, installs it through `openclaw skills install`, and verifies `SKILL.md`, `.clawhub/origin.json`, `.clawhub/lock.json`, and `skills info --json`.
|
||||
- CLI backend live Docker probes can be run as focused lanes, for example `pnpm test:docker:live-cli-backend:claude`, `pnpm test:docker:live-cli-backend:claude:resume`, or `pnpm test:docker:live-cli-backend:claude:mcp`. Gemini has matching `:resume` and `:mcp` aliases.
|
||||
|
||||