Skip to content

Commit 0ff60bf

Browse files
authored
Merge branch 'main' into llagy007/google-meet-node-policy
2 parents d32a965 + a1571f2 commit 0ff60bf

221 files changed

Lines changed: 9414 additions & 800 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.agents/skills/openclaw-changelog-update/SKILL.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,13 @@ every human `Thanks @...` attribution.
4949
--write-ledger
5050
```
5151

52+
The verifier automatically reuses public GitHub GraphQL responses from an
53+
exact base/target SHA snapshot under the worktree's git metadata. Iterative
54+
rewrites at the same target avoid repeated network discovery. Use
55+
`--refresh-github-snapshot` after suspect API data, `--github-snapshot
56+
<path>` for an explicit artifact, or `--no-github-snapshot` for a live-only
57+
audit. GitHub release bodies are always read live.
58+
5259
- the manifest is the required input to the rewrite, not an after-the-fact
5360
audit; it contains every referenced PR, eligible contributor credit,
5461
inline issue context, every direct commit, and an editorial-eligibility

.agents/skills/openclaw-changelog-update/scripts/verify-release-notes.mjs

Lines changed: 174 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
#!/usr/bin/env node
22

33
import { execFileSync, spawnSync } from "node:child_process";
4-
import { readFileSync, writeFileSync } from "node:fs";
4+
import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
5+
import path from "node:path";
56
import { pathToFileURL } from "node:url";
67
import {
78
extractChangelogReleaseSections,
@@ -12,6 +13,7 @@ import {
1213
} from "../../../../scripts/render-github-release-notes.mjs";
1314

1415
const repo = "openclaw/openclaw";
16+
const githubSnapshotSchemaVersion = 1;
1517
const commitAssociationQueryBatchSize = 20;
1618
const excludedHandles = new Set(["openclaw", "clawsweeper", "claude", "codex", "steipete"]);
1719
const nonEditorialTypes = new Set([
@@ -48,6 +50,7 @@ const genericDirectCommitTerms = new Set([
4850
"restore",
4951
"update",
5052
]);
53+
let githubSnapshotState;
5154

5255
function fail(message) {
5356
throw new Error(message);
@@ -65,6 +68,11 @@ Required:
6568
6669
Options:
6770
--manifest <path> Read or write the complete contribution record ledger.
71+
--github-snapshot <path>
72+
Override the exact-range GitHub GraphQL snapshot path.
73+
--no-github-snapshot Disable GitHub GraphQL snapshot reuse.
74+
--refresh-github-snapshot
75+
Ignore an existing exact-range snapshot and rebuild it.
6876
--seed-ref <ref> Use an existing release section as editorial input.
6977
--shipped-ref <tag> Exclude PRs already recorded by this shipped tag; repeatable.
7078
--write-ledger Write the verified ledger back into CHANGELOG.md.
@@ -81,6 +89,9 @@ function parseArgs(argv) {
8189
help: false,
8290
json: false,
8391
manifestPath: undefined,
92+
githubSnapshotPath: undefined,
93+
noGithubSnapshot: false,
94+
refreshGithubSnapshot: false,
8495
seedRef: undefined,
8596
shippedRefs: [],
8697
writeLedger: false,
@@ -92,9 +103,23 @@ function parseArgs(argv) {
92103
options.help = true;
93104
continue;
94105
}
95-
if (arg === "--check-github" || arg === "--json" || arg === "--write-ledger") {
106+
if (
107+
arg === "--check-github" ||
108+
arg === "--json" ||
109+
arg === "--no-github-snapshot" ||
110+
arg === "--refresh-github-snapshot" ||
111+
arg === "--write-ledger"
112+
) {
96113
options[
97-
arg === "--check-github" ? "checkGithub" : arg === "--write-ledger" ? "writeLedger" : "json"
114+
arg === "--check-github"
115+
? "checkGithub"
116+
: arg === "--write-ledger"
117+
? "writeLedger"
118+
: arg === "--no-github-snapshot"
119+
? "noGithubSnapshot"
120+
: arg === "--refresh-github-snapshot"
121+
? "refreshGithubSnapshot"
122+
: "json"
98123
] = true;
99124
continue;
100125
}
@@ -104,6 +129,7 @@ function parseArgs(argv) {
104129
arg === "--version" ||
105130
arg === "--release-tag" ||
106131
arg === "--shipped-ref" ||
132+
arg === "--github-snapshot" ||
107133
arg === "--manifest" ||
108134
arg === "--seed-ref"
109135
) {
@@ -117,6 +143,8 @@ function parseArgs(argv) {
117143
options.shippedRefs.push(value);
118144
} else if (arg === "--manifest") {
119145
options.manifestPath = value;
146+
} else if (arg === "--github-snapshot") {
147+
options.githubSnapshotPath = value;
120148
} else if (arg === "--seed-ref") {
121149
options.seedRef = value;
122150
} else {
@@ -140,6 +168,12 @@ function parseArgs(argv) {
140168
if (!options.help && options.checkGithub && options.releaseTags.length === 0) {
141169
fail("--check-github requires at least one --release-tag");
142170
}
171+
if (options.noGithubSnapshot && options.githubSnapshotPath) {
172+
fail("--no-github-snapshot cannot be combined with --github-snapshot");
173+
}
174+
if (options.noGithubSnapshot && options.refreshGithubSnapshot) {
175+
fail("--no-github-snapshot cannot be combined with --refresh-github-snapshot");
176+
}
143177
const uniqueShippedRefs = new Set(options.shippedRefs);
144178
if (uniqueShippedRefs.size !== options.shippedRefs.length) {
145179
fail("--shipped-ref values must be unique");
@@ -184,7 +218,7 @@ function gitIsAncestor(base, target) {
184218
);
185219
}
186220

187-
function githubApi(args) {
221+
function fetchGithubApi(args) {
188222
try {
189223
return JSON.parse(run("ghx", ["api", ...args]).replace(/\u001B\[[0-?]*[ -/]*[@-~]/g, ""));
190224
} catch (error) {
@@ -195,6 +229,129 @@ function githubApi(args) {
195229
}
196230
}
197231

232+
export function createGithubSnapshotState({
233+
base,
234+
filePath,
235+
refresh = false,
236+
repository = repo,
237+
target,
238+
}) {
239+
let responses = {};
240+
if (!refresh && existsSync(filePath)) {
241+
let parsed;
242+
try {
243+
parsed = JSON.parse(readFileSync(filePath, "utf8"));
244+
} catch (error) {
245+
fail(
246+
`could not read GitHub snapshot ${filePath}: ${
247+
error instanceof Error ? error.message : String(error)
248+
}`,
249+
);
250+
}
251+
if (
252+
parsed.schemaVersion !== githubSnapshotSchemaVersion ||
253+
parsed.repository !== repository ||
254+
parsed.base !== base ||
255+
parsed.target !== target ||
256+
!parsed.responses ||
257+
typeof parsed.responses !== "object" ||
258+
Array.isArray(parsed.responses)
259+
) {
260+
fail(
261+
`GitHub snapshot ${filePath} does not match ${repository} ${base}..${target}; use --refresh-github-snapshot`,
262+
);
263+
}
264+
responses = parsed.responses;
265+
}
266+
return {
267+
base,
268+
dirty: refresh && existsSync(filePath),
269+
filePath,
270+
hits: 0,
271+
misses: 0,
272+
repository,
273+
responses,
274+
target,
275+
};
276+
}
277+
278+
export function githubApiWithSnapshot(args, fetchApi, snapshotState) {
279+
if (!snapshotState || args[0] !== "graphql") {
280+
return fetchApi(args);
281+
}
282+
const key = JSON.stringify(args);
283+
const cached = snapshotState.responses[key];
284+
if (cached !== undefined) {
285+
snapshotState.hits += 1;
286+
return structuredClone(cached);
287+
}
288+
snapshotState.misses += 1;
289+
const response = fetchApi(args);
290+
if (
291+
!response ||
292+
typeof response !== "object" ||
293+
Array.isArray(response) ||
294+
response.data === undefined ||
295+
(Array.isArray(response.errors) && response.errors.length > 0)
296+
) {
297+
return response;
298+
}
299+
snapshotState.responses[key] = structuredClone(response);
300+
snapshotState.dirty = true;
301+
return response;
302+
}
303+
304+
export function persistGithubSnapshot(snapshotState) {
305+
if (!snapshotState?.dirty) {
306+
return;
307+
}
308+
const output = `${JSON.stringify(
309+
{
310+
schemaVersion: githubSnapshotSchemaVersion,
311+
repository: snapshotState.repository,
312+
base: snapshotState.base,
313+
target: snapshotState.target,
314+
responses: snapshotState.responses,
315+
},
316+
null,
317+
2,
318+
)}\n`;
319+
mkdirSync(path.dirname(snapshotState.filePath), { recursive: true });
320+
const tempPath = `${snapshotState.filePath}.${process.pid}.tmp`;
321+
try {
322+
writeFileSync(tempPath, output);
323+
renameSync(tempPath, snapshotState.filePath);
324+
snapshotState.dirty = false;
325+
} finally {
326+
rmSync(tempPath, { force: true });
327+
}
328+
}
329+
330+
function githubApi(args) {
331+
return githubApiWithSnapshot(args, fetchGithubApi, githubSnapshotState);
332+
}
333+
334+
function initializeGithubSnapshot(options) {
335+
if (options.noGithubSnapshot) {
336+
return undefined;
337+
}
338+
const base = git(["rev-parse", `${options.base}^{commit}`]);
339+
const target = git(["rev-parse", `${options.target}^{commit}`]);
340+
const defaultName = `verify-release-notes-${base}-${target}.json`;
341+
const filePath = path.resolve(
342+
options.githubSnapshotPath ??
343+
git(["rev-parse", "--git-path", `openclaw-release-cache/${defaultName}`]),
344+
);
345+
const state = createGithubSnapshotState({
346+
base,
347+
filePath,
348+
refresh: options.refreshGithubSnapshot,
349+
target,
350+
});
351+
process.once("exit", () => persistGithubSnapshot(state));
352+
return state;
353+
}
354+
198355
function escapeRegExp(value) {
199356
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
200357
}
@@ -1719,6 +1876,7 @@ function main() {
17191876
printUsage();
17201877
return;
17211878
}
1879+
githubSnapshotState = initializeGithubSnapshot(options);
17221880
let changelog = readFileSync("CHANGELOG.md", "utf8");
17231881
let section = sectionFor(changelog, options.version);
17241882
const source = sourceCommits(options.base, options.target);
@@ -1930,13 +2088,24 @@ function main() {
19302088
unlinkedCommits: manifest.unlinkedCommits.length,
19312089
},
19322090
github,
2091+
githubSnapshot: githubSnapshotState
2092+
? {
2093+
path: githubSnapshotState.filePath,
2094+
hits: githubSnapshotState.hits,
2095+
misses: githubSnapshotState.misses,
2096+
}
2097+
: null,
19332098
errors,
19342099
};
2100+
persistGithubSnapshot(githubSnapshotState);
19352101
if (options.json) {
19362102
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
19372103
} else {
2104+
const snapshotSummary = githubSnapshotState
2105+
? `, GitHub snapshot ${githubSnapshotState.hits} hits/${githubSnapshotState.misses} misses`
2106+
: "";
19382107
process.stdout.write(
1939-
`${options.version}: ${ledger.pullRequests.length} PRs, ${ledger.issues.length} issues, ${errors.length === 0 ? "verified" : `${errors.length} errors`}\n`,
2108+
`${options.version}: ${ledger.pullRequests.length} PRs, ${ledger.issues.length} issues, ${errors.length === 0 ? "verified" : `${errors.length} errors`}${snapshotSummary}\n`,
19402109
);
19412110
}
19422111
if (errors.length > 0) {

.agents/skills/release-openclaw-maintainer/SKILL.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,19 @@ Stable publication is not complete until `main` carries the actual shipped relea
237237

238238
## Handle versions and release files consistently
239239

240+
Use the release preparation controller before manual version edits:
241+
242+
```bash
243+
pnpm release:prepare -- --version YYYY.M.PATCH-beta.N --shadow
244+
pnpm release:prepare -- --version YYYY.M.PATCH-beta.N --write
245+
pnpm release:prepare -- --version YYYY.M.PATCH-beta.N --check
246+
```
247+
248+
Shadow mode is the default and never runs mutating commands. Write mode aligns
249+
the root and macOS versions, optionally Android with `--android`, then runs only
250+
the version-owned generated metadata DAG. Every mode writes an exact
251+
HEAD/worktree-bound manifest under git metadata for cutover review.
252+
240253
- Version locations include:
241254
- `package.json`
242255
- `apps/android/app/build.gradle.kts`

.github/workflows/openclaw-performance.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ on:
5050
kova_ref:
5151
description: openclaw/Kova Git ref to install
5252
required: false
53-
default: 2ef781190516c09df9891317654a0484bf4f0d46
53+
default: 99b4b5c70fac2b13c48550c1d9bed09b795f0186
5454
type: string
5555
dispatch_id:
5656
description: Optional parent workflow dispatch identifier
@@ -153,7 +153,7 @@ jobs:
153153
include_filters: "scenario:agent-cold-warm-message"
154154
expected_release_entries: "agent-cold-warm-message:mock-openai-provider"
155155
env:
156-
KOVA_REF: ${{ inputs.kova_ref || '2ef781190516c09df9891317654a0484bf4f0d46' }}
156+
KOVA_REF: ${{ inputs.kova_ref || '99b4b5c70fac2b13c48550c1d9bed09b795f0186' }}
157157
KOVA_HOME: ${{ github.workspace }}/.artifacts/kova/home/${{ matrix.lane }}
158158
PERFORMANCE_HELPER_DIR: ${{ github.workspace }}/.artifacts/performance-workflow
159159
REPORT_DIR: ${{ github.workspace }}/.artifacts/kova/reports/${{ matrix.lane }}

.github/workflows/openclaw-release-telegram-qa.yml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -645,7 +645,13 @@ jobs:
645645
646646
const sourceRoot = process.env.SOURCE_ROOT;
647647
const candidateRoot = process.env.CANDIDATE_ROOT;
648-
const records = execFileSync("git", ["-C", sourceRoot, "ls-files", "-s", "-z"])
648+
// Node defaults to 1 MiB, smaller than this repo's tracked-file index output.
649+
const trackedFileOutput = execFileSync(
650+
"git",
651+
["-C", sourceRoot, "ls-files", "-s", "-z"],
652+
{ maxBuffer: 16 * 1024 * 1024 },
653+
);
654+
const records = trackedFileOutput
649655
.toString("utf8")
650656
.split("\0")
651657
.filter(Boolean);

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,13 @@ Docs: https://docs.openclaw.ai
2828

2929
### Fixes
3030

31+
- **QQBot token requests:** bound token acquisition with the shared 30-second guarded-fetch deadline so stalled singleflight callers fail together, clean up, and can retry. (#102897) Thanks @maweibin.
32+
- **Canvas A2UI validation:** reject malformed or unsupported JSONL at CLI, agent-tool, and final node-invoke boundaries while preserving native v0.8 dispatch. (#103713) Thanks @qingminglong.
33+
- **Twilio RCS inbound routing:** normalize RCS consumer addresses only after signed webhook validation so sender matching and sessions work without changing outbound RCS semantics. (#102373) Thanks @clawSean.
34+
- **ClickClack output sanitization:** strip internal tool and XML scaffolding at the sender boundary, suppress scaffold-only sends, and preserve optional modern delivery IDs. (#103142) Thanks @masatohoshino.
35+
- **CLI installer cleanup:** remove Node staging directories and pnpm workspace-rewrite temporary files on failure. (#103725) Thanks @SebTardif.
36+
- **Agent-core truncation:** avoid empty-output crashes when head truncation receives negative line or byte ceilings. (#103425) Thanks @qingminglong.
37+
- **Windows Node resolution:** preserve the current executable when resolving bare case-insensitive `node.exe` entries under hostile `PATH` values. (#103907) Thanks @soldforaloss.
3138
- **Codex runtime switching:** accept the bundled Codex runtime for both `codex/*` and `openai/*` model routes while keeping unsupported provider/runtime pairs rejected. (#103762)
3239
- **Agent abort cleanup:** serialize prompt lock reacquisition with terminal cleanup so canceled embedded runs do not self-contend on session locks for up to 60 seconds.
3340
- **Chutes OAuth deadlines:** bound token exchange, profile lookup, and refresh requests, and keep issued tokens when optional userinfo enrichment stalls. (#102026) Thanks @Alix-007.

0 commit comments

Comments
 (0)