Skip to content

Commit 07f376e

Browse files
authored
Merge branch 'main' into fix/openrouter-oauth-error-redirect-v2
2 parents 2a6730b + 8573f9e commit 07f376e

124 files changed

Lines changed: 3790 additions & 1448 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.

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

Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1493,6 +1493,69 @@ jobs:
14931493
pgrep -u "$SUT_UID" >/dev/null 2>&1
14941494
}
14951495
1496+
capture_live_model_config() {
1497+
local config_path="${1:?}"
1498+
local config_id diagnostics_dir diagnostics_real proof_bytes proof_path proof_tmp
1499+
umask 077
1500+
config_id="$(printf '%s' "$config_path" | sha256sum | awk '{print $1}')"
1501+
diagnostics_dir="${EVIDENCE_ROOT}/trusted-runtime-diagnostics"
1502+
install -d -o "$RUNNER_UID" -g "$RUNNER_GID" -m 0700 "$diagnostics_dir"
1503+
[[ ! -L "$diagnostics_dir" ]]
1504+
diagnostics_real="$(realpath -e "$diagnostics_dir")"
1505+
[[ "$diagnostics_real" == "$diagnostics_dir" ]]
1506+
proof_path="${diagnostics_dir}/gateway-model-config-${config_id}.json"
1507+
proof_tmp="${RUNTIME_ROOT}/gateway-model-config-${BASHPID}.json"
1508+
1509+
# Capture only bounded routing identifiers before the QA suite removes its temp config.
1510+
jq -c '
1511+
def bounded_text:
1512+
if type == "string" then .[0:256] else null end;
1513+
def bounded_texts:
1514+
if type == "array" then
1515+
[.[:64][] | bounded_text | select(. != null)]
1516+
else
1517+
[]
1518+
end;
1519+
{
1520+
agentDefaultModel: (
1521+
if (.agents.defaults.model | type) == "string" then
1522+
{
1523+
primary: (.agents.defaults.model | bounded_text),
1524+
fallbacks: []
1525+
}
1526+
elif (.agents.defaults.model | type) == "object" then
1527+
{
1528+
primary: (.agents.defaults.model.primary | bounded_text),
1529+
fallbacks: (.agents.defaults.model.fallbacks | bounded_texts)
1530+
}
1531+
else
1532+
{primary: null, fallbacks: []}
1533+
end
1534+
),
1535+
agentModelRefs: (
1536+
((.agents.defaults.models // {}) | keys | sort)[:128] |
1537+
map(bounded_text | select(. != null))
1538+
),
1539+
providers: (
1540+
(.models.providers // {}) |
1541+
to_entries |
1542+
sort_by(.key) |
1543+
.[:32] |
1544+
map({
1545+
id: (.key | bounded_text),
1546+
api: (.value.api | bounded_text),
1547+
modelIds: ([.value.models[]?.id][:128] | map(bounded_text | select(. != null)))
1548+
})
1549+
)
1550+
}
1551+
' "$config_path" >"$proof_tmp"
1552+
proof_bytes="$(stat -c '%s' "$proof_tmp")"
1553+
((proof_bytes > 0 && proof_bytes <= 65536))
1554+
chown "$RUNNER_UID:$RUNNER_GID" "$proof_tmp"
1555+
chmod 0600 "$proof_tmp"
1556+
mv -f "$proof_tmp" "$proof_path"
1557+
}
1558+
14961559
terminate_sut_uid() {
14971560
pkill -TERM -U "$SUT_UID" >/dev/null 2>&1 || true
14981561
pkill -TERM -u "$SUT_UID" >/dev/null 2>&1 || true
@@ -1642,6 +1705,7 @@ jobs:
16421705
config_path="${temp_root}/openclaw.json"
16431706
[[ -f "$config_path" && ! -L "$config_path" ]]
16441707
[[ "$(realpath -e "$config_path")" == "$config_path" ]]
1708+
capture_live_model_config "$config_path"
16451709
16461710
export OPENCLAW_QA_TEMP_ROOT="$temp_root"
16471711
export HOME="${temp_root}/home"
@@ -2164,6 +2228,151 @@ jobs:
21642228
echo "Isolated Telegram SUT UID still owns processes after terminal cleanup." >&2
21652229
exit 1
21662230
2231+
- name: Capture isolated Telegram runtime diagnostics
2232+
id: capture_diagnostics
2233+
if: always() && steps.terminate_sut.outputs.quiescent == 'true' && steps.run_lane.outputs.output_dir != ''
2234+
env:
2235+
OUTPUT_DIR: ${{ steps.run_lane.outputs.output_dir }}
2236+
RUNTIME_ROOT: ${{ steps.create_sut.outputs.runtime_root }}
2237+
shell: bash
2238+
run: |
2239+
set -euo pipefail
2240+
umask 077
2241+
[[ "$RUNTIME_ROOT" == "/var/lib/openclaw-telegram-sut-runtime-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}" ]]
2242+
diagnostics_dir="${OUTPUT_DIR}/trusted-runtime-diagnostics"
2243+
install -d -m 0700 "$diagnostics_dir"
2244+
trusted_temp_root="$(mktemp -d "${RUNNER_TEMP}/openclaw-telegram-diagnostics.XXXXXX")"
2245+
trap 'rm -rf -- "$trusted_temp_root"' EXIT
2246+
redactor_script="${trusted_temp_root}/redact-gateway-tail.mts"
2247+
cat >"$redactor_script" <<'NODE'
2248+
import fs from "node:fs";
2249+
import path from "node:path";
2250+
import { pathToFileURL } from "node:url";
2251+
2252+
const [outputPath] = process.argv.slice(2);
2253+
const redactorPath = path.resolve(
2254+
"extensions/qa-lab/src/gateway-log-redaction.ts",
2255+
);
2256+
const { redactQaGatewayDebugText } = await import(pathToFileURL(redactorPath).href);
2257+
const limitBytes = 131_072;
2258+
const maxInputRecordBytes = 1_048_576;
2259+
const oversizedMarker = "[truncated oversized gateway log record] ";
2260+
const omittedMarker = "[omitted oversized gateway log record]\n";
2261+
const retained = [];
2262+
let retainedBytes = 0;
2263+
2264+
function boundRedactedLine(line) {
2265+
const redactedLine = `${redactQaGatewayDebugText(line)}\n`;
2266+
const lineBuffer = Buffer.from(redactedLine);
2267+
if (lineBuffer.length <= limitBytes) {
2268+
return redactedLine;
2269+
}
2270+
const markerBytes = Buffer.byteLength(oversizedMarker);
2271+
const suffixBuffer = lineBuffer.subarray(
2272+
lineBuffer.length - (limitBytes - markerBytes),
2273+
);
2274+
let suffix = suffixBuffer.toString("utf8");
2275+
while (Buffer.byteLength(`${oversizedMarker}${suffix}`) > limitBytes) {
2276+
suffix = suffix.slice(1);
2277+
}
2278+
return `${oversizedMarker}${suffix}`;
2279+
}
2280+
2281+
function retainLine(boundedLine) {
2282+
const lineBytes = Buffer.byteLength(boundedLine);
2283+
while (retained.length > 0 && retainedBytes + lineBytes > limitBytes) {
2284+
const removed = retained.shift();
2285+
retainedBytes -= Buffer.byteLength(removed);
2286+
}
2287+
retained.push(boundedLine);
2288+
retainedBytes += lineBytes;
2289+
}
2290+
2291+
let recordParts = [];
2292+
let recordBytes = 0;
2293+
let recordOmitted = false;
2294+
function appendRecordPart(part) {
2295+
if (recordOmitted) {
2296+
return;
2297+
}
2298+
if (recordBytes + part.length > maxInputRecordBytes) {
2299+
recordParts = [];
2300+
recordBytes = 0;
2301+
recordOmitted = true;
2302+
return;
2303+
}
2304+
recordParts.push(Buffer.from(part));
2305+
recordBytes += part.length;
2306+
}
2307+
function finishRecord() {
2308+
if (recordOmitted) {
2309+
retainLine(omittedMarker);
2310+
} else {
2311+
const line = Buffer.concat(recordParts, recordBytes).toString("utf8").replace(/\r$/u, "");
2312+
retainLine(boundRedactedLine(line));
2313+
}
2314+
recordParts = [];
2315+
recordBytes = 0;
2316+
recordOmitted = false;
2317+
}
2318+
2319+
for await (const value of process.stdin) {
2320+
const chunk = Buffer.isBuffer(value) ? value : Buffer.from(value);
2321+
let offset = 0;
2322+
while (offset < chunk.length) {
2323+
const newline = chunk.indexOf(0x0a, offset);
2324+
const end = newline === -1 ? chunk.length : newline;
2325+
appendRecordPart(chunk.subarray(offset, end));
2326+
if (newline === -1) {
2327+
break;
2328+
}
2329+
finishRecord();
2330+
offset = newline + 1;
2331+
}
2332+
}
2333+
if (recordOmitted || recordBytes > 0) {
2334+
finishRecord();
2335+
}
2336+
fs.writeFileSync(outputPath, retained.join(""), { encoding: "utf8", mode: 0o600 });
2337+
NODE
2338+
chmod 0600 "$redactor_script"
2339+
2340+
mapfile -t gateway_logs < <(
2341+
sudo find "$RUNTIME_ROOT/tmp" -xdev -type f -name 'openclaw-*.log' -print | sort
2342+
)
2343+
((${#gateway_logs[@]} > 0 && ${#gateway_logs[@]} <= 8))
2344+
for index in "${!gateway_logs[@]}"; do
2345+
log_path="$(sudo realpath -e "${gateway_logs[$index]}")"
2346+
case "$log_path" in
2347+
"$RUNTIME_ROOT"/tmp/*) ;;
2348+
*) echo "Telegram gateway log escaped the isolated runtime root." >&2; exit 1 ;;
2349+
esac
2350+
[[ "$(sudo stat -c '%F' "$log_path")" == "regular file" ]]
2351+
output_path="${diagnostics_dir}/gateway-internal-tail-$((index + 1)).log"
2352+
sudo cat "$log_path" | node --import tsx "$redactor_script" "$output_path"
2353+
done
2354+
2355+
mapfile -t model_config_proofs < <(
2356+
find "$diagnostics_dir" -maxdepth 1 -type f -name 'gateway-model-config-*.json' -print |
2357+
sort
2358+
)
2359+
((${#model_config_proofs[@]} > 0 && ${#model_config_proofs[@]} <= 8))
2360+
for proof_path in "${model_config_proofs[@]}"; do
2361+
[[ "$(stat -c '%F:%a' "$proof_path")" == "regular file:600" ]]
2362+
proof_bytes="$(stat -c '%s' "$proof_path")"
2363+
((proof_bytes > 0 && proof_bytes <= 65536))
2364+
jq -e '
2365+
(keys | sort) == ["agentDefaultModel", "agentModelRefs", "providers"] and
2366+
(.agentDefaultModel | keys | sort) == ["fallbacks", "primary"] and
2367+
([.agentDefaultModel.primary, .agentDefaultModel.fallbacks[], .agentModelRefs[]] |
2368+
all(. == null or type == "string")) and
2369+
(.providers | all(
2370+
(keys | sort) == ["api", "id", "modelIds"] and
2371+
([.id, .api, .modelIds[]] | all(. == null or type == "string"))
2372+
))
2373+
' "$proof_path" >/dev/null
2374+
done
2375+
21672376
- name: Finalize trusted Telegram process-boundary evidence
21682377
id: finalize_boundary
21692378
if: always() && steps.terminate_sut.outputs.quiescent == 'true' && steps.run_lane.outputs.output_dir != ''
@@ -2360,6 +2569,7 @@ jobs:
23602569
${{ steps.validate_credentials.outcome }}
23612570
${{ steps.run_lane.outcome }}
23622571
${{ steps.terminate_sut.outcome }}
2572+
${{ steps.capture_diagnostics.outcome }}
23632573
${{ steps.finalize_boundary.outcome }}
23642574
${{ steps.validate_evidence.outcome }}
23652575
${{ steps.remove_sut.outcome }}

AGENTS.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ Skills own workflows; root owns hard policy and routing.
133133
- Use `$openclaw-testing` for test/CI choice and `$crabbox` for remote/full/E2E proof.
134134
- At task start, if code changes, tests, builds, typechecks, lint fan-out, Docker, packaging, E2E, or live proof are likely, classify source trust and immediately pre-warm the safe Crabbox backend in a background command session. Trusted maintainer code defaults to Blacksmith Testbox; untrusted contributor/fork code uses secretless fork CI or sanitized direct AWS Crabbox under the rule above. Continue inspection/editing while it hydrates; sync the current checkout for every run, reuse the lease, then stop it before handoff.
135135
- Warm Testbox from the task checkout; ownership is checkout-path scoped; `--reclaim` only for intentional transfer.
136+
- One Testbox lease, one active command; never sync/reclaim during a run.
136137
- Testbox `--reclaim` does not retarget the remote checkout; never cross repos.
137138
- Base/head changed: stop and rewarm Testbox; never override stale lease checks.
138139
- Compound Testbox commands: `bash -lc`, never `sh -lc`; job env uses Bash `declare`.
@@ -146,6 +147,8 @@ Skills own workflows; root owns hard policy and routing.
146147
- Crabbox wrapper `stop` has no `--timing-json`; use `node scripts/crabbox-wrapper.mjs stop --provider <provider> --id <id>`.
147148
- Repo-native PR worktree may omit `node_modules`; prove remotely, then use `git commit --no-verify`, not `scripts/committer`.
148149
- Parallel agents share the checkout; never switch its branch while sibling work runs.
150+
- Testbox status: `blacksmith testbox status --id <tbx_id>`; no `--json` flag.
151+
- QA CLI `--output-dir` must be repo-relative.
149152
- Full suites, changed gates, builds, typechecks, lint fan-out, Docker/package/E2E/live/cross-OS proof, or anything computationally intensive: Crabbox/Testbox.
150153
- If an allowed local fallback fans out or becomes expensive, stop it and move the work to the pre-warmed remote box.
151154
- Before handoff/push: prove touched surface. Before landing to `main`: issue proof plus appropriate full/broad proof unless scope is clearly narrow.
@@ -164,12 +167,15 @@ Skills own workflows; root owns hard policy and routing.
164167
- Use `$openclaw-pr-maintainer` immediately for maintainer-side OpenClaw issue/PR review, triage, duplicates, labels, comments, close, land, or evidence. Contributor PR creation/refresh follows the requested contributor workflow; linked refs alone do not require maintainer archive tooling.
165168
- Issue/PR start: `git status -sb`; if clean, `git pull --ff-only`; if dirty, yell before pull/rebase.
166169
- PR refs: `gh pr view/diff` or `gh api`, not web search. Prefer `gitcrawl` for maintainer discovery; missing/stale `gitcrawl` falls through to live `gh`, not contributor setup. Verify live with `gh` before mutation.
170+
- `gh pr view` takes the branch positionally; no `--head` flag.
167171
- zsh: quote `gh api` endpoints containing `?` or brackets; otherwise glob expansion corrupts the invocation.
168172
- GitHub Actions: resolve workflow files from `.github/workflows` or API; never infer filenames from display names.
169173
- zsh: quote command globs; unmatched patterns abort before the tool runs.
174+
- zsh: don't use `path` as a variable; it rewrites `$PATH`.
170175
- `scripts/pr` artifacts: preserve template enum values; validate before prepare.
171176
- `scripts/pr` subcommands require a PR number; no subcommand `--help` placeholder.
172177
- `scripts/pr` review: checkout main baseline, then PR, before artifact validation.
178+
- PR head changed: rerun `scripts/pr review-init`; checkout alone leaves stale guard SHA.
173179
- `rg`: options before `--`; use `--` before patterns starting with `-`.
174180
- `gh --jq` is not standalone `jq`; pipe JSON to `jq` for variables or `--arg`.
175181
- Actions checkout refs: use full 40-char SHAs; short SHAs resolve as branches/tags.
@@ -202,7 +208,7 @@ Skills own workflows; root owns hard policy and routing.
202208
- 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`.
203209
- 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.
204210
- Trusted-workflow release-branch CI: pass `target_ref` + `release_candidate_ref`; never `release_gate` (requires workflow head == target).
205-
- Agent PR landing to `main`: use only the repo-native `scripts/pr` wrapper: run `scripts/pr review-init <PR>`, follow its emitted checkout/guard guidance, initialize and complete review artifacts with `scripts/pr review-artifacts-init <PR>`, validate them with `scripts/pr review-validate-artifacts <PR>`, then run `OPENCLAW_TESTBOX=1 scripts/pr prepare-run <PR>` and `scripts/pr merge-run <PR>`. The Testbox flag is mandatory for agents so prepare verifies hosted CI/Testbox on the current head or reuses a patch-identical pre-rebase run green within 24 hours instead of running full gates locally. For owner-approved reviewed fork code without hosted Testbox, use `OPENCLAW_PR_GATES_REMOTE=testbox` instead. Do not rebase only because `main` advanced; merge drift is advisory unless strict drift is explicitly enabled, while GitHub still blocks conflicts. Do not idle on `auto-response` or `check-docs`.
211+
- Agent PR landing to `main`: use only the repo-native `scripts/pr` wrapper: run `scripts/pr review-init <PR>`, follow its emitted checkout/guard guidance, initialize and complete review artifacts with `scripts/pr review-artifacts-init <PR>`, validate them with `scripts/pr review-validate-artifacts <PR>`, then run `OPENCLAW_TESTBOX=1 scripts/pr prepare-run <PR>` and `scripts/pr merge-run <PR>`. The Testbox flag is mandatory for agents so prepare verifies hosted CI/Testbox on the current head or reuses a patch-identical pre-rebase run green within 24 hours instead of running full gates locally. `prepare-run` fails fast; invoke only after exact-head CI is complete and green. For owner-approved reviewed fork code without hosted Testbox, use `OPENCLAW_PR_GATES_REMOTE=testbox` instead. Do not rebase only because `main` advanced; merge drift is advisory unless strict drift is explicitly enabled, while GitHub still blocks conflicts. Do not idle on `auto-response` or `check-docs`.
206212
- After `scripts/pr merge-run` removes its worktree, `cd` to a persistent repo before follow-up commands.
207213
- `scripts/pr` review JSON: land-ready recommendation `READY FOR /prepare-pr`, `issueValidation.status=valid`; never `APPROVE`.
208214

@@ -253,7 +259,7 @@ Skills own workflows; root owns hard policy and routing.
253259
- Prefer injection and narrow `*.runtime.ts` mocks over broad barrels or `openclaw/plugin-sdk/*`.
254260
- Do not edit baseline/inventory/ignore/snapshot/expected-failure files to silence checks without explicit approval.
255261
- Do not run independent `pnpm test`/Vitest commands concurrently in one worktree; Vitest cache races with `ENOTEMPTY`. Group one command or use distinct `OPENCLAW_VITEST_FS_MODULE_CACHE_PATH`.
256-
- Test workers max 16. Memory pressure: `OPENCLAW_VITEST_MAX_WORKERS=1 pnpm test`.
262+
- Vitest rejects Jest `--runInBand`; use `OPENCLAW_VITEST_MAX_WORKERS=1 pnpm test` for serial proof. Test workers max 16.
257263
- Live: `OPENCLAW_LIVE_TEST=1 pnpm test:live`; verbose `OPENCLAW_LIVE_TEST_QUIET=0`.
258264
- Guide: `docs/reference/test.md`.
259265

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,7 @@ Docs: https://docs.openclaw.ai
220220
- **Discord thread-title prompts:** truncate generated-title message and channel context on UTF-16 boundaries so emoji cannot leave malformed model prompt text. (#101551) Thanks @Alix-007.
221221
- **Task state migration:** canonicalize legacy `not-requested` delivery statuses during sidecar import and existing shared-database open so upgraded task registries and linked TaskFlows recover without manual SQL, and surface rejected persisted values in compact console diagnostics. (#103946) Thanks @bek91.
222222
- **Reply pre-delivery recovery:** bound each pre-delivery callback with an owner-overridable deadline, release serialized reply lanes after hung plugin work, and preserve durable final-delivery retry state only when transport never started. (#104256) Thanks @NianJiuZst.
223+
- **Signal native quote replies:** preserve the active inbound message as a native quote across agent, explicit, durable, and chunked sends while keeping reply-mode policy inside the Signal plugin. (#105347) Thanks @jesse-merhi.
223224

224225
## 2026.7.1
225226

0 commit comments

Comments
 (0)