mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-17 19:48:59 +08:00
Compare commits
4 Commits
fix/securi
...
script-to-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4fa026e46a | ||
|
|
5316b1dbad | ||
|
|
a61ba9e140 | ||
|
|
3c2f21e9b6 |
@@ -91,32 +91,6 @@ attribution.
|
||||
- if any compatibility `removeAfter` is on/before release date, resolve it
|
||||
or explicitly record the blocker before shipping
|
||||
10. Validate and ship:
|
||||
- generate and verify the complete contribution ledger before committing:
|
||||
```bash
|
||||
node .agents/skills/openclaw-changelog-update/scripts/verify-release-notes.mjs \
|
||||
--base <base-tag> \
|
||||
--target <target-ref> \
|
||||
--version <YYYY.M.PATCH> \
|
||||
--write-ledger
|
||||
```
|
||||
- the command fails when any `#NNN` reference in release history or the
|
||||
rendered release section is absent from the ledger, when reverted work is
|
||||
presented as shipped, or when an eligible PR author, issue reporter, or
|
||||
known co-author is missing from that entry's `Thanks @...` credit
|
||||
- after the GitHub release or prerelease is published, verify every matching
|
||||
release page against the same source section:
|
||||
```bash
|
||||
node .agents/skills/openclaw-changelog-update/scripts/verify-release-notes.mjs \
|
||||
--base <base-tag> \
|
||||
--target <target-ref> \
|
||||
--version <YYYY.M.PATCH> \
|
||||
--release-tag v<YYYY.M.PATCH> \
|
||||
--check-github
|
||||
```
|
||||
- add one `--release-tag` for every beta and stable page in the train; a
|
||||
`### Release verification` tail is permitted, but any other body drift
|
||||
fails the check; the GitHub body must begin with the complete
|
||||
`## YYYY.M.PATCH` changelog section, including its heading
|
||||
- `git diff --check`
|
||||
- for docs/changelog-only changes, no broad tests are required
|
||||
- commit with `scripts/committer "docs(changelog): refresh YYYY.M.PATCH notes" CHANGELOG.md`
|
||||
|
||||
@@ -1,443 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { execFileSync } from "node:child_process";
|
||||
import { readFileSync, writeFileSync } from "node:fs";
|
||||
|
||||
const repo = "openclaw/openclaw";
|
||||
const excludedHandles = new Set(["openclaw", "clawsweeper", "codex", "steipete"]);
|
||||
|
||||
function fail(message) {
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
function parseArgs(argv) {
|
||||
const options = {
|
||||
releaseTags: [],
|
||||
checkGithub: false,
|
||||
json: false,
|
||||
writeLedger: false,
|
||||
};
|
||||
|
||||
for (let index = 0; index < argv.length; index += 1) {
|
||||
const arg = argv[index];
|
||||
if (arg === "--check-github" || arg === "--json" || arg === "--write-ledger") {
|
||||
options[
|
||||
arg === "--check-github"
|
||||
? "checkGithub"
|
||||
: arg === "--write-ledger"
|
||||
? "writeLedger"
|
||||
: "json"
|
||||
] = true;
|
||||
continue;
|
||||
}
|
||||
if (arg === "--base" || arg === "--target" || arg === "--version" || arg === "--release-tag") {
|
||||
const value = argv[index + 1];
|
||||
if (!value || value.startsWith("--")) {
|
||||
fail(`missing value for ${arg}`);
|
||||
}
|
||||
if (arg === "--release-tag") {
|
||||
options.releaseTags.push(value);
|
||||
} else {
|
||||
options[arg.slice(2)] = value;
|
||||
}
|
||||
index += 1;
|
||||
continue;
|
||||
}
|
||||
fail(`unknown argument: ${arg}`);
|
||||
}
|
||||
|
||||
for (const name of ["base", "target", "version"]) {
|
||||
if (!options[name]) {
|
||||
fail(`--${name} is required`);
|
||||
}
|
||||
}
|
||||
if (options.checkGithub && options.releaseTags.length === 0) {
|
||||
fail("--check-github requires at least one --release-tag");
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
function run(command, args) {
|
||||
return execFileSync(command, args, {
|
||||
encoding: "utf8",
|
||||
env: { ...process.env, NO_COLOR: "1" },
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
});
|
||||
}
|
||||
|
||||
function git(args) {
|
||||
return run("git", args).trimEnd();
|
||||
}
|
||||
|
||||
function githubApi(args) {
|
||||
try {
|
||||
return JSON.parse(run("ghx", ["api", ...args]).replace(/\u001B\[[0-?]*[ -/]*[@-~]/g, ""));
|
||||
} catch (error) {
|
||||
if (typeof error.stdout === "string" && error.stdout.trim() !== "") {
|
||||
return JSON.parse(error.stdout.replace(/\u001B\[[0-?]*[ -/]*[@-~]/g, ""));
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
function escapeRegExp(value) {
|
||||
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||
}
|
||||
|
||||
function isEligibleHandle(handle) {
|
||||
return Boolean(handle) && !handle.endsWith("[bot]") && !excludedHandles.has(handle.toLowerCase());
|
||||
}
|
||||
|
||||
function sectionFor(changelog, version) {
|
||||
const heading = new RegExp(`^## ${escapeRegExp(version)}\\r?$`, "m").exec(changelog);
|
||||
if (!heading || heading.index === undefined) {
|
||||
fail(`CHANGELOG.md does not contain ## ${version}`);
|
||||
}
|
||||
const start = heading.index;
|
||||
const bodyStart = changelog.indexOf("\n", start) + 1;
|
||||
const next = /^## /gm;
|
||||
next.lastIndex = bodyStart;
|
||||
const nextHeading = next.exec(changelog);
|
||||
const end = nextHeading?.index ?? changelog.length;
|
||||
return {
|
||||
start,
|
||||
end,
|
||||
source: changelog.slice(start, end).trimEnd(),
|
||||
body: changelog.slice(bodyStart, end).trim(),
|
||||
};
|
||||
}
|
||||
|
||||
function referencesIn(text) {
|
||||
return [...text.matchAll(/#(\d+)/g)].map((match) => Number(match[1]));
|
||||
}
|
||||
|
||||
function appendReferences(references, additions) {
|
||||
const seen = new Set(references);
|
||||
for (const number of additions) {
|
||||
if (!seen.has(number)) {
|
||||
references.push(number);
|
||||
seen.add(number);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function sourceCommits(base, target) {
|
||||
const mergeBase = git(["merge-base", base, target]);
|
||||
const output = git([
|
||||
"log",
|
||||
"--first-parent",
|
||||
"--reverse",
|
||||
"--format=%H%x1f%s%x1f%B%x1e",
|
||||
`${mergeBase}..${target}`,
|
||||
]);
|
||||
const commits = new Map();
|
||||
const revertsByTarget = new Map();
|
||||
for (const record of output.split("\x1e")) {
|
||||
if (!record) {
|
||||
continue;
|
||||
}
|
||||
const [rawHash, subject, ...bodyParts] = record.split("\x1f");
|
||||
const hash = rawHash.trim();
|
||||
const body = bodyParts.join("\x1f");
|
||||
const revertedHash = body.match(/This reverts commit ([0-9a-f]{7,40})\./i)?.[1];
|
||||
const isRevert = subject.startsWith('Revert "') || Boolean(revertedHash);
|
||||
commits.set(hash, { body, hash, isRevert, revertedHash, subject });
|
||||
}
|
||||
for (const commit of commits.values()) {
|
||||
if (!commit.revertedHash) {
|
||||
continue;
|
||||
}
|
||||
const targetHash = [...commits.keys()].find((candidate) => candidate.startsWith(commit.revertedHash));
|
||||
if (targetHash) {
|
||||
const reverts = revertsByTarget.get(targetHash) ?? [];
|
||||
reverts.push(commit.hash);
|
||||
revertsByTarget.set(targetHash, reverts);
|
||||
}
|
||||
}
|
||||
const active = new Map();
|
||||
function isActive(hash) {
|
||||
if (active.has(hash)) {
|
||||
return active.get(hash);
|
||||
}
|
||||
const cancellingReverts = revertsByTarget.get(hash) ?? [];
|
||||
const value = !cancellingReverts.some((revertHash) => isActive(revertHash));
|
||||
active.set(hash, value);
|
||||
return value;
|
||||
}
|
||||
|
||||
const references = [];
|
||||
const revertedReferences = new Set();
|
||||
const coauthorsByReference = new Map();
|
||||
for (const commit of commits.values()) {
|
||||
if (commit.isRevert) {
|
||||
continue;
|
||||
}
|
||||
const uniqueReferences = [...new Set(referencesIn(`${commit.subject}\n${commit.body}`))];
|
||||
if (!isActive(commit.hash)) {
|
||||
for (const number of uniqueReferences) {
|
||||
revertedReferences.add(number);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
appendReferences(references, uniqueReferences);
|
||||
const coauthors = [...commit.body.matchAll(/<(?:(?:\d+)\+)?([^@<>\s]+)@users\.noreply\.github\.com>/gi)]
|
||||
.map((match) => match[1])
|
||||
.filter(isEligibleHandle);
|
||||
for (const number of uniqueReferences) {
|
||||
if (coauthors.length > 0) {
|
||||
const handles = coauthorsByReference.get(number) ?? new Set();
|
||||
for (const handle of coauthors) {
|
||||
handles.add(handle);
|
||||
}
|
||||
coauthorsByReference.set(number, handles);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { mergeBase, references, revertedReferences, coauthorsByReference };
|
||||
}
|
||||
|
||||
function graphql(query) {
|
||||
return githubApi(["graphql", "-f", `query=${query}`]).data;
|
||||
}
|
||||
|
||||
function resolveReferences(numbers) {
|
||||
const nodes = new Map();
|
||||
for (let index = 0; index < numbers.length; index += 40) {
|
||||
const chunk = numbers.slice(index, index + 40);
|
||||
const fields = chunk
|
||||
.map(
|
||||
(number) => `n${number}: repository(owner: "openclaw", name: "openclaw") {
|
||||
issueOrPullRequest(number: ${number}) {
|
||||
__typename
|
||||
... on Issue { number title author { __typename login } }
|
||||
... on PullRequest { number title author { __typename login } }
|
||||
}
|
||||
}`,
|
||||
)
|
||||
.join("\n");
|
||||
const data = graphql(`query { ${fields} }`);
|
||||
for (const number of chunk) {
|
||||
const node = data[`n${number}`]?.issueOrPullRequest;
|
||||
if (node) {
|
||||
nodes.set(number, node);
|
||||
}
|
||||
}
|
||||
}
|
||||
return nodes;
|
||||
}
|
||||
|
||||
function resolveCoauthors(handles) {
|
||||
const resolved = new Map();
|
||||
const uniqueHandles = [...new Set(handles)];
|
||||
for (let index = 0; index < uniqueHandles.length; index += 80) {
|
||||
const chunk = uniqueHandles.slice(index, index + 80);
|
||||
const fields = chunk
|
||||
.map(
|
||||
(handle, offset) =>
|
||||
`u${index + offset}: user(login: ${JSON.stringify(handle)}) { __typename login }`,
|
||||
)
|
||||
.join("\n");
|
||||
const data = graphql(`query { ${fields} }`);
|
||||
for (let offset = 0; offset < chunk.length; offset += 1) {
|
||||
const user = data[`u${index + offset}`];
|
||||
if (user?.__typename === "User" && isEligibleHandle(user.login)) {
|
||||
resolved.set(chunk[offset].toLowerCase(), user.login);
|
||||
}
|
||||
}
|
||||
}
|
||||
return resolved;
|
||||
}
|
||||
|
||||
function thanksFor(node, coauthorHandles) {
|
||||
const handles = [];
|
||||
if (node.author?.__typename === "User" && isEligibleHandle(node.author.login)) {
|
||||
handles.push(node.author.login);
|
||||
}
|
||||
for (const handle of coauthorHandles) {
|
||||
if (!handles.some((candidate) => candidate.toLowerCase() === handle.toLowerCase())) {
|
||||
handles.push(handle);
|
||||
}
|
||||
}
|
||||
return handles;
|
||||
}
|
||||
|
||||
function ledgerFor(base, target, references, nodes, coauthorsByReference, resolvedCoauthors) {
|
||||
const missing = references.filter((number) => !nodes.has(number));
|
||||
if (missing.length > 0) {
|
||||
fail(`GitHub could not resolve source references: ${missing.map((number) => `#${number}`).join(", ")}`);
|
||||
}
|
||||
|
||||
const entries = references.map((number) => {
|
||||
const node = nodes.get(number);
|
||||
const rawCoauthors = coauthorsByReference.get(number) ?? new Set();
|
||||
const coauthors = [...rawCoauthors]
|
||||
.map((handle) => resolvedCoauthors.get(handle.toLowerCase()))
|
||||
.filter(Boolean);
|
||||
return {
|
||||
number,
|
||||
title: node.title.replace(/#(\d+)/g, "issue $1").replace(/\s+/g, " ").trim(),
|
||||
type: node.__typename,
|
||||
thanks: thanksFor(node, coauthors),
|
||||
};
|
||||
});
|
||||
|
||||
const pullRequests = entries.filter((entry) => entry.type === "PullRequest");
|
||||
const issues = entries.filter((entry) => entry.type === "Issue");
|
||||
const renderEntry = (entry, issue = false) => {
|
||||
const attribution = entry.thanks.length > 0 ? ` Thanks ${entry.thanks.map((handle) => `@${handle}`).join(" and ")}.` : "";
|
||||
return `- ${issue ? "Reported: " : ""}${entry.title} (#${entry.number}).${attribution}`;
|
||||
};
|
||||
const ledger = [
|
||||
"### Complete contribution ledger",
|
||||
"",
|
||||
`This audited record covers the complete ${base}..${target} history: ${pullRequests.length} PRs and ${issues.length} linked issues. The grouped notes above prioritize user impact; this ledger preserves every contribution reference and eligible human credit.`,
|
||||
"",
|
||||
"#### Pull requests",
|
||||
"",
|
||||
...pullRequests.map((entry) => renderEntry(entry)),
|
||||
"",
|
||||
"#### Linked issues",
|
||||
"",
|
||||
...issues.map((entry) => renderEntry(entry, true)),
|
||||
].join("\n");
|
||||
return { entries, issues, ledger, pullRequests };
|
||||
}
|
||||
|
||||
function replaceLedger(changelog, section, ledger) {
|
||||
const beforeLedger = section.source.replace(/\n+### Complete contribution ledger[\s\S]*$/m, "").trimEnd();
|
||||
const replacement = `${beforeLedger}\n\n${ledger}\n`;
|
||||
return `${changelog.slice(0, section.start)}${replacement}${changelog.slice(section.end)}`;
|
||||
}
|
||||
|
||||
function ledgerChecks(section, entries) {
|
||||
const errors = [];
|
||||
if (!section.source.includes("### Highlights")) {
|
||||
errors.push("missing ### Highlights");
|
||||
}
|
||||
if (!section.source.includes("### Changes")) {
|
||||
errors.push("missing ### Changes");
|
||||
}
|
||||
if (!section.source.includes("### Fixes")) {
|
||||
errors.push("missing ### Fixes");
|
||||
}
|
||||
const ledgerStart = section.source.indexOf("### Complete contribution ledger");
|
||||
if (ledgerStart < 0) {
|
||||
errors.push("missing ### Complete contribution ledger");
|
||||
return errors;
|
||||
}
|
||||
const ledger = section.source.slice(ledgerStart);
|
||||
const entryNumbers = new Set(entries.map((entry) => entry.number));
|
||||
for (const number of new Set(referencesIn(section.source))) {
|
||||
if (!entryNumbers.has(number)) {
|
||||
errors.push(`missing ledger entry for #${number}`);
|
||||
}
|
||||
}
|
||||
for (const entry of entries) {
|
||||
const prefix = entry.type === "Issue" ? "- Reported: " : "- ";
|
||||
const line = ledger
|
||||
.split("\n")
|
||||
.find((candidate) => candidate.startsWith(prefix) && candidate.includes(`(#${entry.number})`));
|
||||
if (!line) {
|
||||
errors.push(`missing ledger entry for #${entry.number}`);
|
||||
continue;
|
||||
}
|
||||
for (const handle of entry.thanks) {
|
||||
if (!line.toLowerCase().includes(`@${handle.toLowerCase()}`)) {
|
||||
errors.push(`missing Thanks @${handle} for #${entry.number}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
return errors;
|
||||
}
|
||||
|
||||
function releaseChecks(section, releaseTags) {
|
||||
const expected = section.source;
|
||||
const checks = [];
|
||||
for (const tag of releaseTags) {
|
||||
const release = githubApi([`repos/${repo}/releases/tags/${encodeURIComponent(tag)}`]);
|
||||
const suffix = release.body.slice(expected.length).trimStart();
|
||||
const matches =
|
||||
release.body === expected ||
|
||||
(release.body.startsWith(expected) && (suffix === "" || suffix.startsWith("### Release verification")));
|
||||
checks.push({
|
||||
tag,
|
||||
releaseId: release.id,
|
||||
matches,
|
||||
bodyLength: release.body.length,
|
||||
});
|
||||
}
|
||||
return checks;
|
||||
}
|
||||
|
||||
function main() {
|
||||
const options = parseArgs(process.argv.slice(2));
|
||||
let changelog = readFileSync("CHANGELOG.md", "utf8");
|
||||
let section = sectionFor(changelog, options.version);
|
||||
const source = sourceCommits(options.base, options.target);
|
||||
const preexistingNotes = section.source.replace(/\n+### Complete contribution ledger[\s\S]*$/m, "");
|
||||
const noteReferences = referencesIn(preexistingNotes);
|
||||
const revertedNoteReferences = noteReferences.filter((number) => source.revertedReferences.has(number));
|
||||
if (revertedNoteReferences.length > 0) {
|
||||
fail(
|
||||
`release notes reference reverted work: ${[
|
||||
...new Set(revertedNoteReferences),
|
||||
]
|
||||
.map((number) => `#${number}`)
|
||||
.join(", ")}`,
|
||||
);
|
||||
}
|
||||
const references = [...source.references];
|
||||
appendReferences(references, noteReferences);
|
||||
const nodes = resolveReferences(references);
|
||||
const coauthorHandles = [...source.coauthorsByReference.values()].flatMap((handles) => [...handles]);
|
||||
const resolvedCoauthors = resolveCoauthors(coauthorHandles);
|
||||
const ledger = ledgerFor(
|
||||
options.base,
|
||||
options.target,
|
||||
references,
|
||||
nodes,
|
||||
source.coauthorsByReference,
|
||||
resolvedCoauthors,
|
||||
);
|
||||
|
||||
if (options.writeLedger) {
|
||||
changelog = replaceLedger(changelog, section, ledger.ledger);
|
||||
writeFileSync("CHANGELOG.md", changelog);
|
||||
section = sectionFor(changelog, options.version);
|
||||
}
|
||||
|
||||
const errors = ledgerChecks(section, ledger.entries);
|
||||
const github = options.checkGithub ? releaseChecks(section, options.releaseTags) : [];
|
||||
for (const check of github) {
|
||||
if (!check.matches) {
|
||||
errors.push(`GitHub release ${check.tag} does not match the ${options.version} CHANGELOG section`);
|
||||
}
|
||||
}
|
||||
|
||||
const result = {
|
||||
base: options.base,
|
||||
target: options.target,
|
||||
mergeBase: source.mergeBase,
|
||||
version: options.version,
|
||||
source: {
|
||||
references: references.length,
|
||||
pullRequests: ledger.pullRequests.length,
|
||||
issues: ledger.issues.length,
|
||||
},
|
||||
github,
|
||||
errors,
|
||||
};
|
||||
if (options.json) {
|
||||
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
||||
} else {
|
||||
process.stdout.write(
|
||||
`${options.version}: ${ledger.pullRequests.length} PRs, ${ledger.issues.length} issues, ${errors.length === 0 ? "verified" : `${errors.length} errors`}\n`,
|
||||
);
|
||||
}
|
||||
if (errors.length > 0) {
|
||||
process.exitCode = 1;
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -100,26 +100,6 @@ Use this skill for release and publish-time workflow. Load `$release-private` if
|
||||
- `dev`: moving head on `main`
|
||||
- When using a beta Git tag, publish npm with the matching beta version suffix so the plain version is not consumed or blocked
|
||||
|
||||
## Close stable releases on main
|
||||
|
||||
Stable publication is not complete until `main` carries the actual shipped release state.
|
||||
|
||||
1. Start from fresh latest `main`. Audit `release/YYYY.M.PATCH` against it and
|
||||
forward-port real fixes that are absent from `main`. Do not blindly merge
|
||||
release-only compatibility, test, or validation adapters into newer `main`.
|
||||
2. Set `main` to the shipped stable version, not a speculative next train. Run
|
||||
`pnpm release:prep` after the root version change, then
|
||||
`pnpm deps:shrinkwrap:generate`.
|
||||
3. Make `CHANGELOG.md`'s `## YYYY.M.PATCH` section on `main` exactly match the
|
||||
tagged release branch. Include the stable `appcast.xml` update when the mac
|
||||
release published one.
|
||||
4. Do not add `YYYY.M.PATCH+1`, a beta version, or an empty future changelog
|
||||
section to `main` until the operator explicitly starts that release train.
|
||||
5. Run `pnpm release:generated:check`, `pnpm deps:shrinkwrap:check`, and
|
||||
`OPENCLAW_TESTBOX=1 pnpm check:changed`. Push, then verify `origin/main`
|
||||
contains the shipped version and changelog before calling the stable release
|
||||
done.
|
||||
|
||||
## Handle versions and release files consistently
|
||||
|
||||
- Version locations include:
|
||||
@@ -225,11 +205,6 @@ Stable publication is not complete until `main` carries the actual shipped relea
|
||||
`CHANGELOG.md` version section, not highlights or an excerpt. When creating
|
||||
or editing a release, extract from `## YYYY.M.PATCH` through the line before the
|
||||
next level-2 heading and use that complete block as the release notes.
|
||||
- Before publishing or closing a release, run
|
||||
`$openclaw-changelog-update`'s `verify-release-notes.mjs` with every stable
|
||||
and beta release tag in the train. Do not publish or leave a page live when
|
||||
it is missing a source-history reference, eligible human credit, or the
|
||||
complete matching changelog body.
|
||||
- To update an existing GitHub Release body, resolve the numeric release id and
|
||||
patch that resource with the notes file as the `body` field:
|
||||
`gh api repos/openclaw/openclaw/releases/tags/vYYYY.M.PATCH --jq .id`, then
|
||||
@@ -798,13 +773,13 @@ node --import tsx scripts/openclaw-npm-postpublish-verify.ts <published-version>
|
||||
and `.dSYM.zip` artifacts to the existing GitHub release in
|
||||
`openclaw/openclaw`.
|
||||
32. For stable releases, download `macos-appcast-<tag>` from the successful
|
||||
private mac run, update `appcast.xml` on `main`, verify the feed, then
|
||||
complete the **Close stable releases on main** gate.
|
||||
private mac run, update `appcast.xml` on `main`, and verify the feed. Merge
|
||||
or cherry-pick release branch changes back to `main` after stable succeeds.
|
||||
33. For beta releases, publish the mac assets only when intentionally requested;
|
||||
expect no shared production
|
||||
`appcast.xml` artifact and do not update the shared production feed unless a
|
||||
separate beta feed exists.
|
||||
34. After stable main closeout, verify npm and the attached release artifacts.
|
||||
34. After publish, verify npm and the attached release artifacts.
|
||||
|
||||
## GHSA advisory work
|
||||
|
||||
|
||||
6
.github/workflows/ci-check-testbox.yml
vendored
6
.github/workflows/ci-check-testbox.yml
vendored
@@ -6,10 +6,6 @@ on:
|
||||
type: string
|
||||
description: "Testbox session ID"
|
||||
required: true
|
||||
timeout_minutes:
|
||||
type: number
|
||||
description: "Maximum GitHub job runtime for long Testbox commands"
|
||||
default: 120
|
||||
pull_request:
|
||||
paths:
|
||||
- ".github/workflows/**"
|
||||
@@ -29,7 +25,7 @@ jobs:
|
||||
contents: read
|
||||
name: "check"
|
||||
runs-on: blacksmith-32vcpu-ubuntu-2404
|
||||
timeout-minutes: ${{ fromJSON(inputs.timeout_minutes || '30') }}
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: Begin Testbox
|
||||
uses: useblacksmith/begin-testbox@233448af4bfdc6fca509a7f0974411ac6d8a8043
|
||||
|
||||
@@ -407,28 +407,12 @@ jobs:
|
||||
const path = require("node:path");
|
||||
|
||||
const packageDir = process.env.PACKAGE_DIR;
|
||||
function resolveTarballFileName(value, label) {
|
||||
const fileName = typeof value === "string" ? value.trim() : "";
|
||||
if (
|
||||
!fileName.endsWith(".tgz") ||
|
||||
fileName.includes("\0") ||
|
||||
fileName !== path.basename(fileName) ||
|
||||
fileName !== path.win32.basename(fileName)
|
||||
) {
|
||||
throw new Error(`${label} must be a local .tgz filename.`);
|
||||
}
|
||||
return fileName;
|
||||
}
|
||||
const requestedFileName = process.env.INPUT_CANDIDATE_FILE_NAME.trim();
|
||||
const files = fs.readdirSync(packageDir).filter((file) => file.endsWith(".tgz"));
|
||||
const selectedCandidateFileName = requestedFileName || (files.length === 1 ? files[0] : "");
|
||||
if (!selectedCandidateFileName) {
|
||||
const candidateFileName = requestedFileName || (files.length === 1 ? files[0] : "");
|
||||
if (!candidateFileName) {
|
||||
throw new Error(`Expected exactly one candidate .tgz in ${packageDir}; found ${files.length}.`);
|
||||
}
|
||||
const candidateFileName = resolveTarballFileName(
|
||||
selectedCandidateFileName,
|
||||
"candidate_file_name",
|
||||
);
|
||||
if (!fs.existsSync(path.join(packageDir, candidateFileName))) {
|
||||
throw new Error(`Provided candidate artifact does not contain ${candidateFileName}.`);
|
||||
}
|
||||
@@ -490,23 +474,12 @@ jobs:
|
||||
run: |
|
||||
node <<'NODE' >>"$GITHUB_OUTPUT"
|
||||
const fs = require("node:fs");
|
||||
const path = require("node:path");
|
||||
function resolveTarballFileName(value, label) {
|
||||
const fileName = typeof value === "string" ? value.trim() : "";
|
||||
if (
|
||||
!fileName.endsWith(".tgz") ||
|
||||
fileName.includes("\0") ||
|
||||
fileName !== path.basename(fileName) ||
|
||||
fileName !== path.win32.basename(fileName)
|
||||
) {
|
||||
throw new Error(`${label} must be a local .tgz filename.`);
|
||||
}
|
||||
return fileName;
|
||||
}
|
||||
const payload = JSON.parse(fs.readFileSync(process.env.BASELINE_PACK_JSON, "utf8"));
|
||||
const entry = Array.isArray(payload) ? payload.at(-1) : null;
|
||||
const fileName = resolveTarballFileName(entry?.filename, "Baseline npm pack filename");
|
||||
process.stdout.write(`file_name=${fileName}\n`);
|
||||
if (!entry?.filename) {
|
||||
throw new Error("Baseline npm pack did not produce a filename.");
|
||||
}
|
||||
process.stdout.write(`file_name=${entry.filename}\n`);
|
||||
NODE
|
||||
|
||||
- name: Upload candidate artifact
|
||||
|
||||
24
.github/workflows/openclaw-npm-release.yml
vendored
24
.github/workflows/openclaw-npm-release.yml
vendored
@@ -223,25 +223,10 @@ jobs:
|
||||
set -euo pipefail
|
||||
PACK_OUTPUT="$RUNNER_TEMP/npm-pack-output.txt"
|
||||
npm pack --json 2>&1 | tee "$PACK_OUTPUT"
|
||||
PACK_NAME="$(node - "$PACK_OUTPUT" <<'NODE'
|
||||
PACK_PATH="$(node - "$PACK_OUTPUT" <<'NODE'
|
||||
const fs = require("node:fs");
|
||||
const path = require("node:path");
|
||||
const input = fs.readFileSync(process.argv[2], "utf8");
|
||||
|
||||
function resolveTarballFileName(value) {
|
||||
const fileName = typeof value === "string" ? value.trim() : "";
|
||||
if (
|
||||
!fileName.endsWith(".tgz") ||
|
||||
fileName.includes("\0") ||
|
||||
fileName !== path.basename(fileName) ||
|
||||
fileName !== path.win32.basename(fileName)
|
||||
) {
|
||||
console.error(`npm pack reported unsafe tarball filename ${JSON.stringify(fileName)}.`);
|
||||
process.exit(1);
|
||||
}
|
||||
return fileName;
|
||||
}
|
||||
|
||||
function arrayEndFrom(start) {
|
||||
let depth = 0;
|
||||
let inString = false;
|
||||
@@ -281,8 +266,8 @@ jobs:
|
||||
try {
|
||||
const parsed = JSON.parse(input.slice(start, end));
|
||||
const first = Array.isArray(parsed) ? parsed[0] : null;
|
||||
if (first && Object.prototype.hasOwnProperty.call(first, "filename")) {
|
||||
process.stdout.write(resolveTarballFileName(first.filename));
|
||||
if (first && typeof first.filename === "string" && first.filename) {
|
||||
process.stdout.write(first.filename);
|
||||
process.exit(0);
|
||||
}
|
||||
} catch {
|
||||
@@ -294,7 +279,6 @@ jobs:
|
||||
process.exit(1);
|
||||
NODE
|
||||
)"
|
||||
PACK_PATH="$PWD/$PACK_NAME"
|
||||
if [[ -z "$PACK_PATH" || ! -f "$PACK_PATH" ]]; then
|
||||
echo "npm pack did not produce a tarball file." >&2
|
||||
exit 1
|
||||
@@ -306,7 +290,7 @@ jobs:
|
||||
else
|
||||
RELEASE_TAG="${RELEASE_REF}"
|
||||
fi
|
||||
TARBALL_NAME="$PACK_NAME"
|
||||
TARBALL_NAME="$(basename "$PACK_PATH")"
|
||||
TARBALL_SHA256="$(sha256sum "$PACK_PATH" | awk '{print $1}')"
|
||||
ARTIFACT_DIR="$RUNNER_TEMP/openclaw-npm-preflight"
|
||||
rm -rf "$ARTIFACT_DIR"
|
||||
|
||||
1339
CHANGELOG.md
1339
CHANGELOG.md
File diff suppressed because it is too large
Load Diff
@@ -111,8 +111,6 @@ class MainViewModel(
|
||||
|
||||
val isConnected: StateFlow<Boolean> = runtimeState(initial = false) { it.isConnected }
|
||||
val isNodeConnected: StateFlow<Boolean> = runtimeState(initial = false) { it.nodeConnected }
|
||||
val nodeCapabilityApprovalState: StateFlow<GatewayNodeApprovalState> =
|
||||
runtimeState(initial = GatewayNodeApprovalState.Loading) { it.nodeCapabilityApprovalState }
|
||||
val statusText: StateFlow<String> = runtimeState(initial = "Offline") { it.statusText }
|
||||
val gatewayConnectionProblem: StateFlow<GatewayConnectionProblem?> = runtimeState(initial = null) { it.gatewayConnectionProblem }
|
||||
val serverName: StateFlow<String?> = runtimeState(initial = null) { it.serverName }
|
||||
|
||||
@@ -69,7 +69,6 @@ import kotlinx.coroutines.withTimeout
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.JsonArray
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
import kotlinx.serialization.json.JsonPrimitive
|
||||
import kotlinx.serialization.json.buildJsonObject
|
||||
@@ -302,8 +301,6 @@ class NodeRuntime(
|
||||
val isConnected: StateFlow<Boolean> = _isConnected.asStateFlow()
|
||||
private val _nodeConnected = MutableStateFlow(false)
|
||||
val nodeConnected: StateFlow<Boolean> = _nodeConnected.asStateFlow()
|
||||
private val _nodeCapabilityApprovalState = MutableStateFlow(GatewayNodeApprovalState.Loading)
|
||||
val nodeCapabilityApprovalState: StateFlow<GatewayNodeApprovalState> = _nodeCapabilityApprovalState.asStateFlow()
|
||||
|
||||
private val _statusText = MutableStateFlow("Offline")
|
||||
val statusText: StateFlow<String> = _statusText.asStateFlow()
|
||||
@@ -398,7 +395,6 @@ class NodeRuntime(
|
||||
val nodesDevicesRefreshing: StateFlow<Boolean> = _nodesDevicesRefreshing.asStateFlow()
|
||||
private val _nodesDevicesErrorText = MutableStateFlow<String?>(null)
|
||||
val nodesDevicesErrorText: StateFlow<String?> = _nodesDevicesErrorText.asStateFlow()
|
||||
private val nodeApprovalRefreshGuard = GatewayNodeApprovalRefreshGuard()
|
||||
private val _channelsSummary = MutableStateFlow(GatewayChannelsSummary(channels = emptyList()))
|
||||
val channelsSummary: StateFlow<GatewayChannelsSummary> = _channelsSummary.asStateFlow()
|
||||
private val _channelsRefreshing = MutableStateFlow(false)
|
||||
@@ -456,7 +452,6 @@ class NodeRuntime(
|
||||
},
|
||||
onDisconnected = { message ->
|
||||
operatorConnected = false
|
||||
invalidateNodeCapabilityApprovalState()
|
||||
operatorStatusText = message
|
||||
_serverName.value = null
|
||||
_remoteAddress.value = null
|
||||
@@ -517,15 +512,12 @@ class NodeRuntime(
|
||||
publishNodePresenceAliveBeacon(NodePresenceAliveBeacon.Trigger.Connect)
|
||||
val endpoint = connectedEndpoint
|
||||
val auth = activeGatewayAuth
|
||||
if (operatorConnected) {
|
||||
scope.launch { refreshNodesDevicesFromGateway() }
|
||||
} else if (endpoint != null && auth != null) {
|
||||
if (endpoint != null && auth != null) {
|
||||
maybeStartOperatorSessionAfterNodeConnect(endpoint, auth)
|
||||
}
|
||||
},
|
||||
onDisconnected = { message ->
|
||||
_nodeConnected.value = false
|
||||
invalidateNodeCapabilityApprovalState()
|
||||
nodeStatusText = message
|
||||
didAutoRequestCanvasRehydrate = false
|
||||
_canvasA2uiHydrated.value = false
|
||||
@@ -2017,42 +2009,21 @@ class NodeRuntime(
|
||||
}
|
||||
|
||||
private suspend fun refreshNodesDevicesFromGateway() {
|
||||
val refreshGeneration = nodeApprovalRefreshGuard.begin()
|
||||
val refreshStarted =
|
||||
nodeApprovalRefreshGuard.publishIfCurrent(refreshGeneration) {
|
||||
_nodesDevicesRefreshing.value = true
|
||||
_nodesDevicesErrorText.value = null
|
||||
_nodeCapabilityApprovalState.value = GatewayNodeApprovalState.Loading
|
||||
}
|
||||
if (!refreshStarted) return
|
||||
_nodesDevicesRefreshing.value = true
|
||||
_nodesDevicesErrorText.value = null
|
||||
if (!operatorConnected) {
|
||||
nodeApprovalRefreshGuard.publishIfCurrent(refreshGeneration) {
|
||||
_nodesDevicesSummary.value =
|
||||
GatewayNodesDevicesSummary(
|
||||
nodes = emptyList(),
|
||||
pendingDevices = emptyList(),
|
||||
pairedDevices = emptyList(),
|
||||
)
|
||||
_nodesDevicesRefreshing.value = false
|
||||
}
|
||||
_nodesDevicesSummary.value =
|
||||
GatewayNodesDevicesSummary(
|
||||
nodes = emptyList(),
|
||||
pendingDevices = emptyList(),
|
||||
pairedDevices = emptyList(),
|
||||
)
|
||||
_nodesDevicesRefreshing.value = false
|
||||
return
|
||||
}
|
||||
try {
|
||||
val nodesRes = operatorSession.request("node.list", "{}")
|
||||
val nodesRoot = json.parseToJsonElement(nodesRes).asObjectOrNull()
|
||||
val nodes = parseGatewayNodes(nodesRoot?.get("nodes") as? JsonArray)
|
||||
val approvalState =
|
||||
currentNodeCapabilityApprovalState(
|
||||
nodes = nodes,
|
||||
selfNodeId = identityStore.loadOrCreate().deviceId,
|
||||
)
|
||||
val publishedApproval =
|
||||
nodeApprovalRefreshGuard.publishIfCurrent(refreshGeneration) {
|
||||
_nodeCapabilityApprovalState.value = approvalState
|
||||
}
|
||||
if (!publishedApproval) {
|
||||
return
|
||||
}
|
||||
val devicesRoot =
|
||||
try {
|
||||
val devicesRes = operatorSession.request("device.pair.list", "{}")
|
||||
@@ -2060,30 +2031,16 @@ class NodeRuntime(
|
||||
} catch (_: Throwable) {
|
||||
null
|
||||
}
|
||||
nodeApprovalRefreshGuard.publishIfCurrent(refreshGeneration) {
|
||||
_nodesDevicesSummary.value =
|
||||
GatewayNodesDevicesSummary(
|
||||
nodes = nodes,
|
||||
pendingDevices = parsePendingDevices(devicesRoot?.get("pending") as? JsonArray),
|
||||
pairedDevices = parsePairedDevices(devicesRoot?.get("paired") as? JsonArray),
|
||||
devicePairingAvailable = devicesRoot != null,
|
||||
)
|
||||
}
|
||||
_nodesDevicesSummary.value =
|
||||
GatewayNodesDevicesSummary(
|
||||
nodes = parseGatewayNodes(nodesRoot?.get("nodes") as? JsonArray),
|
||||
pendingDevices = parsePendingDevices(devicesRoot?.get("pending") as? JsonArray),
|
||||
pairedDevices = parsePairedDevices(devicesRoot?.get("paired") as? JsonArray),
|
||||
devicePairingAvailable = devicesRoot != null,
|
||||
)
|
||||
} catch (_: Throwable) {
|
||||
nodeApprovalRefreshGuard.publishIfCurrent(refreshGeneration) {
|
||||
_nodesDevicesErrorText.value = "Could not load nodes and devices."
|
||||
}
|
||||
_nodesDevicesErrorText.value = "Could not load nodes and devices."
|
||||
} finally {
|
||||
nodeApprovalRefreshGuard.publishIfCurrent(refreshGeneration) {
|
||||
_nodesDevicesRefreshing.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun invalidateNodeCapabilityApprovalState() {
|
||||
val refreshGeneration = nodeApprovalRefreshGuard.begin()
|
||||
nodeApprovalRefreshGuard.publishIfCurrent(refreshGeneration) {
|
||||
_nodeCapabilityApprovalState.value = GatewayNodeApprovalState.Loading
|
||||
_nodesDevicesRefreshing.value = false
|
||||
}
|
||||
}
|
||||
@@ -2332,8 +2289,22 @@ class NodeRuntime(
|
||||
|
||||
private fun parseGatewayNodes(nodes: JsonArray?): List<GatewayNodeSummary> =
|
||||
nodes
|
||||
?.mapNotNull(::parseGatewayNodeSummary)
|
||||
.orEmpty()
|
||||
?.mapNotNull { item ->
|
||||
val obj = item.asObjectOrNull() ?: return@mapNotNull null
|
||||
val id = obj["nodeId"].asStringOrNull()?.trim().orEmpty()
|
||||
if (id.isEmpty()) return@mapNotNull null
|
||||
GatewayNodeSummary(
|
||||
id = id,
|
||||
displayName = obj["displayName"].asStringOrNull()?.trim()?.takeIf { it.isNotEmpty() },
|
||||
remoteIp = obj["remoteIp"].asStringOrNull()?.trim()?.takeIf { it.isNotEmpty() },
|
||||
version = obj["version"].asStringOrNull()?.trim()?.takeIf { it.isNotEmpty() },
|
||||
deviceFamily = obj["deviceFamily"].asStringOrNull()?.trim()?.takeIf { it.isNotEmpty() },
|
||||
paired = obj.boolean("paired"),
|
||||
connected = obj.boolean("connected"),
|
||||
capabilities = parseStringArray(obj["caps"] as? JsonArray),
|
||||
commands = parseStringArray(obj["commands"] as? JsonArray),
|
||||
)
|
||||
}.orEmpty()
|
||||
|
||||
private fun parsePendingDevices(devices: JsonArray?): List<GatewayPendingDeviceSummary> =
|
||||
devices
|
||||
@@ -2861,81 +2832,6 @@ data class GatewayNodesDevicesSummary(
|
||||
val devicePairingAvailable: Boolean = true,
|
||||
)
|
||||
|
||||
enum class GatewayNodeApprovalState {
|
||||
Loading,
|
||||
Unsupported,
|
||||
Approved,
|
||||
PendingApproval,
|
||||
PendingReapproval,
|
||||
Unapproved,
|
||||
}
|
||||
|
||||
/** Prevents older node.list responses from overwriting newer approval state. */
|
||||
internal class GatewayNodeApprovalRefreshGuard {
|
||||
private val lock = Any()
|
||||
private var generation = 0L
|
||||
|
||||
fun begin(): Long =
|
||||
synchronized(lock) {
|
||||
generation += 1
|
||||
generation
|
||||
}
|
||||
|
||||
fun publishIfCurrent(
|
||||
refreshGeneration: Long,
|
||||
publish: () -> Unit,
|
||||
): Boolean =
|
||||
synchronized(lock) {
|
||||
if (refreshGeneration != generation) return@synchronized false
|
||||
publish()
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
internal fun parseGatewayNodeApprovalState(raw: String?): GatewayNodeApprovalState =
|
||||
when (raw?.trim()?.lowercase()) {
|
||||
null, "" -> GatewayNodeApprovalState.Loading
|
||||
"approved" -> GatewayNodeApprovalState.Approved
|
||||
"pending-approval" -> GatewayNodeApprovalState.PendingApproval
|
||||
"pending-reapproval" -> GatewayNodeApprovalState.PendingReapproval
|
||||
"unapproved" -> GatewayNodeApprovalState.Unapproved
|
||||
else -> GatewayNodeApprovalState.Loading
|
||||
}
|
||||
|
||||
internal fun currentNodeCapabilityApprovalState(
|
||||
nodes: List<GatewayNodeSummary>,
|
||||
selfNodeId: String,
|
||||
): GatewayNodeApprovalState =
|
||||
nodes
|
||||
.firstOrNull { it.id == selfNodeId }
|
||||
?.approvalState
|
||||
?: GatewayNodeApprovalState.Loading
|
||||
|
||||
internal fun parseGatewayNodeSummary(item: JsonElement): GatewayNodeSummary? {
|
||||
val obj = item.asObjectOrNull() ?: return null
|
||||
val id = obj["nodeId"].asStringOrNull()?.trim().orEmpty()
|
||||
if (id.isEmpty()) return null
|
||||
return GatewayNodeSummary(
|
||||
id = id,
|
||||
displayName = obj["displayName"].asStringOrNull()?.trim()?.takeIf { it.isNotEmpty() },
|
||||
remoteIp = obj["remoteIp"].asStringOrNull()?.trim()?.takeIf { it.isNotEmpty() },
|
||||
version = obj["version"].asStringOrNull()?.trim()?.takeIf { it.isNotEmpty() },
|
||||
deviceFamily = obj["deviceFamily"].asStringOrNull()?.trim()?.takeIf { it.isNotEmpty() },
|
||||
paired = obj.boolean("paired"),
|
||||
connected = obj.boolean("connected"),
|
||||
// Only an omitted field identifies a legacy gateway; malformed and future values stay fail-closed.
|
||||
approvalState =
|
||||
if (obj.containsKey("approvalState")) {
|
||||
parseGatewayNodeApprovalState(obj["approvalState"].asStringOrNull())
|
||||
} else {
|
||||
GatewayNodeApprovalState.Unsupported
|
||||
},
|
||||
pendingRequestId = obj["pendingRequestId"].asStringOrNull()?.trim()?.takeIf { it.isNotEmpty() },
|
||||
capabilities = parseGatewayStringArray(obj["caps"] as? JsonArray),
|
||||
commands = parseGatewayStringArray(obj["commands"] as? JsonArray),
|
||||
)
|
||||
}
|
||||
|
||||
data class GatewayNodeSummary(
|
||||
val id: String,
|
||||
val displayName: String?,
|
||||
@@ -2944,8 +2840,6 @@ data class GatewayNodeSummary(
|
||||
val deviceFamily: String?,
|
||||
val paired: Boolean,
|
||||
val connected: Boolean,
|
||||
val approvalState: GatewayNodeApprovalState,
|
||||
val pendingRequestId: String?,
|
||||
val capabilities: List<String>,
|
||||
val commands: List<String>,
|
||||
)
|
||||
@@ -3068,11 +2962,6 @@ private fun JsonObject?.cronStatus(key: String): String? =
|
||||
?.trim()
|
||||
?.takeIf { it.isNotEmpty() }
|
||||
|
||||
private fun parseGatewayStringArray(items: JsonArray?): List<String> =
|
||||
items
|
||||
?.mapNotNull { it.asStringOrNull()?.trim()?.takeIf { value -> value.isNotEmpty() } }
|
||||
.orEmpty()
|
||||
|
||||
fun providerDisplayName(provider: String): String =
|
||||
when (provider.trim().lowercase()) {
|
||||
"openai" -> "OpenAI"
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package ai.openclaw.app.ui
|
||||
|
||||
import ai.openclaw.app.GatewayDeviceTokenSummary
|
||||
import ai.openclaw.app.GatewayNodeApprovalState
|
||||
import ai.openclaw.app.GatewayNodeSummary
|
||||
import ai.openclaw.app.GatewayNodesDevicesSummary
|
||||
import ai.openclaw.app.GatewayPairedDeviceSummary
|
||||
@@ -156,8 +155,8 @@ private fun NodeRow(node: GatewayNodeSummary) {
|
||||
badge = nodeBadge(node.displayName ?: node.id),
|
||||
title = node.displayName ?: node.id,
|
||||
subtitle = nodeSubtitle(node),
|
||||
statusText = nodeStatusText(node),
|
||||
status = nodeStatus(node),
|
||||
statusText = if (node.connected) "Online" else "Offline",
|
||||
status = if (node.connected) ClawStatus.Success else ClawStatus.Warning,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -206,46 +205,14 @@ private fun nodeSubtitle(node: GatewayNodeSummary): String {
|
||||
val kind = node.deviceFamily ?: "Node host"
|
||||
val version = node.version?.let { "OpenClaw $it" }
|
||||
val status = if (node.paired) "Paired" else "Unpaired"
|
||||
val approval = nodeApprovalSubtitle(node.approvalState)
|
||||
val commands =
|
||||
node.commands
|
||||
.take(2)
|
||||
.joinToString(", ")
|
||||
.takeIf { it.isNotBlank() }
|
||||
return listOfNotNull(kind, version, status, approval, commands).joinToString(" · ")
|
||||
return listOfNotNull(kind, version, status, commands).joinToString(" · ")
|
||||
}
|
||||
|
||||
private fun nodeStatusText(node: GatewayNodeSummary): String =
|
||||
when (node.approvalState) {
|
||||
GatewayNodeApprovalState.PendingApproval -> "Needs approval"
|
||||
GatewayNodeApprovalState.PendingReapproval -> "Needs reapproval"
|
||||
GatewayNodeApprovalState.Unapproved -> "Unapproved"
|
||||
else -> if (node.connected) "Online" else "Offline"
|
||||
}
|
||||
|
||||
private fun nodeStatus(node: GatewayNodeSummary): ClawStatus =
|
||||
when (node.approvalState) {
|
||||
GatewayNodeApprovalState.Approved -> if (node.connected) ClawStatus.Success else ClawStatus.Warning
|
||||
GatewayNodeApprovalState.PendingApproval,
|
||||
GatewayNodeApprovalState.PendingReapproval,
|
||||
GatewayNodeApprovalState.Unapproved,
|
||||
-> ClawStatus.Warning
|
||||
GatewayNodeApprovalState.Loading,
|
||||
GatewayNodeApprovalState.Unsupported,
|
||||
-> if (node.connected) ClawStatus.Neutral else ClawStatus.Warning
|
||||
}
|
||||
|
||||
private fun nodeApprovalSubtitle(approvalState: GatewayNodeApprovalState): String? =
|
||||
when (approvalState) {
|
||||
GatewayNodeApprovalState.Approved -> "Approved"
|
||||
GatewayNodeApprovalState.PendingApproval -> "Capability approval pending"
|
||||
GatewayNodeApprovalState.PendingReapproval -> "Capability reapproval pending"
|
||||
GatewayNodeApprovalState.Unapproved -> "Capability unapproved"
|
||||
GatewayNodeApprovalState.Loading,
|
||||
GatewayNodeApprovalState.Unsupported,
|
||||
-> null
|
||||
}
|
||||
|
||||
private fun pendingDeviceSubtitle(device: GatewayPendingDeviceSummary): String {
|
||||
val roles = formatDeviceList(device.roles, "role")
|
||||
val scopes = formatDeviceList(device.scopes, "scope")
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package ai.openclaw.app.ui
|
||||
|
||||
import ai.openclaw.app.GatewayConnectionProblem
|
||||
import ai.openclaw.app.GatewayNodeApprovalState
|
||||
import ai.openclaw.app.LocationMode
|
||||
import ai.openclaw.app.MainViewModel
|
||||
import ai.openclaw.app.R
|
||||
@@ -140,7 +139,6 @@ fun OnboardingFlow(
|
||||
val gatewayConnectionProblem by viewModel.gatewayConnectionProblem.collectAsState()
|
||||
val isConnected by viewModel.isConnected.collectAsState()
|
||||
val isNodeConnected by viewModel.isNodeConnected.collectAsState()
|
||||
val nodeCapabilityApprovalState by viewModel.nodeCapabilityApprovalState.collectAsState()
|
||||
val runtimeInitialized by viewModel.runtimeInitialized.collectAsState()
|
||||
val serverName by viewModel.serverName.collectAsState()
|
||||
val remoteAddress by viewModel.remoteAddress.collectAsState()
|
||||
@@ -149,12 +147,7 @@ fun OnboardingFlow(
|
||||
val savedToken by viewModel.gatewayToken.collectAsState()
|
||||
val pendingTrust by viewModel.pendingGatewayTrust.collectAsState()
|
||||
val startAtGatewaySetup by viewModel.startOnboardingAtGatewaySetup.collectAsState()
|
||||
val ready =
|
||||
canFinishOnboarding(
|
||||
isConnected = isConnected,
|
||||
isNodeConnected = isNodeConnected,
|
||||
nodeCapabilityApprovalState = nodeCapabilityApprovalState,
|
||||
)
|
||||
val ready = canFinishOnboarding(isConnected = isConnected, isNodeConnected = isNodeConnected)
|
||||
|
||||
var step by rememberSaveable { mutableStateOf(OnboardingStep.Welcome) }
|
||||
var setupCode by rememberSaveable { mutableStateOf("") }
|
||||
@@ -334,7 +327,6 @@ fun OnboardingFlow(
|
||||
attemptedGatewayName = attemptedGatewayName,
|
||||
remoteAddress = remoteAddress,
|
||||
ready = ready,
|
||||
nodeCapabilityApprovalState = nodeCapabilityApprovalState,
|
||||
gatewayConnectionProblem = gatewayConnectionProblem,
|
||||
connectSettling = recoveryNowMs - connectAttemptStartedAtMs < GATEWAY_CONNECT_SETTLING_MS,
|
||||
onBack = { step = OnboardingStep.Gateway },
|
||||
@@ -617,7 +609,6 @@ private fun GatewayRecoveryScreen(
|
||||
attemptedGatewayName: String?,
|
||||
remoteAddress: String?,
|
||||
ready: Boolean,
|
||||
nodeCapabilityApprovalState: GatewayNodeApprovalState,
|
||||
gatewayConnectionProblem: GatewayConnectionProblem?,
|
||||
connectSettling: Boolean,
|
||||
onBack: () -> Unit,
|
||||
@@ -626,14 +617,7 @@ private fun GatewayRecoveryScreen(
|
||||
onContinue: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val recoveryState =
|
||||
gatewayRecoveryUiState(
|
||||
ready = ready,
|
||||
statusText = statusText,
|
||||
connectSettling = connectSettling,
|
||||
nodeCapabilityApprovalState = nodeCapabilityApprovalState,
|
||||
gatewayConnectionProblem = gatewayConnectionProblem,
|
||||
)
|
||||
val recoveryState = gatewayRecoveryUiState(ready = ready, statusText = statusText, connectSettling = connectSettling, gatewayConnectionProblem = gatewayConnectionProblem)
|
||||
val context = LocalContext.current
|
||||
|
||||
ClawScaffold(modifier = modifier, contentPadding = PaddingValues(horizontal = 18.dp, vertical = 16.dp)) {
|
||||
@@ -645,7 +629,6 @@ private fun GatewayRecoveryScreen(
|
||||
imageVector =
|
||||
when (recoveryState) {
|
||||
GatewayRecoveryUiState.Connected -> Icons.Default.CheckCircle
|
||||
GatewayRecoveryUiState.NodeCapabilityApprovalPending -> Icons.Default.WifiTethering
|
||||
GatewayRecoveryUiState.ApprovalRequired -> Icons.Default.WifiTethering
|
||||
GatewayRecoveryUiState.Pairing -> Icons.Default.WifiTethering
|
||||
GatewayRecoveryUiState.Finishing -> Icons.Default.WifiTethering
|
||||
@@ -656,7 +639,6 @@ private fun GatewayRecoveryScreen(
|
||||
tint =
|
||||
when (recoveryState) {
|
||||
GatewayRecoveryUiState.Connected -> ClawTheme.colors.success
|
||||
GatewayRecoveryUiState.NodeCapabilityApprovalPending -> ClawTheme.colors.warning
|
||||
GatewayRecoveryUiState.ApprovalRequired -> ClawTheme.colors.warning
|
||||
GatewayRecoveryUiState.Pairing -> ClawTheme.colors.text
|
||||
GatewayRecoveryUiState.Finishing -> ClawTheme.colors.text
|
||||
@@ -676,18 +658,7 @@ private fun GatewayRecoveryScreen(
|
||||
Column(verticalArrangement = Arrangement.spacedBy(10.dp)) {
|
||||
Text(text = "Last gateway", style = ClawTheme.type.caption, color = ClawTheme.colors.textMuted)
|
||||
Text(text = recoveryGatewayName(serverName = serverName, attemptedGatewayName = attemptedGatewayName), style = ClawTheme.type.section, color = ClawTheme.colors.text)
|
||||
Text(
|
||||
text =
|
||||
recoveryGatewayDetail(
|
||||
ready = ready,
|
||||
remoteAddress = remoteAddress,
|
||||
statusText = statusText,
|
||||
nodeCapabilityApprovalState = nodeCapabilityApprovalState,
|
||||
gatewayConnectionProblem = gatewayConnectionProblem,
|
||||
),
|
||||
style = ClawTheme.type.body,
|
||||
color = ClawTheme.colors.textMuted,
|
||||
)
|
||||
Text(text = recoveryGatewayDetail(ready = ready, remoteAddress = remoteAddress, statusText = statusText, gatewayConnectionProblem = gatewayConnectionProblem), style = ClawTheme.type.body, color = ClawTheme.colors.textMuted)
|
||||
recoveryGatewayApprovalCommand(gatewayConnectionProblem)?.let { command ->
|
||||
ApprovalCommandBlock(command = command, onCopy = { copyApprovalCommand(context, command) })
|
||||
}
|
||||
@@ -695,7 +666,6 @@ private fun GatewayRecoveryScreen(
|
||||
text =
|
||||
when (recoveryState) {
|
||||
GatewayRecoveryUiState.Connected -> "Healthy"
|
||||
GatewayRecoveryUiState.NodeCapabilityApprovalPending -> "Node approval"
|
||||
GatewayRecoveryUiState.ApprovalRequired -> "Needs approval"
|
||||
GatewayRecoveryUiState.Pairing -> "Pairing"
|
||||
GatewayRecoveryUiState.Finishing -> "Connecting"
|
||||
@@ -704,7 +674,6 @@ private fun GatewayRecoveryScreen(
|
||||
status =
|
||||
when (recoveryState) {
|
||||
GatewayRecoveryUiState.Connected -> ClawStatus.Success
|
||||
GatewayRecoveryUiState.NodeCapabilityApprovalPending -> ClawStatus.Warning
|
||||
GatewayRecoveryUiState.ApprovalRequired -> ClawStatus.Warning
|
||||
GatewayRecoveryUiState.Pairing -> ClawStatus.Neutral
|
||||
GatewayRecoveryUiState.Finishing -> ClawStatus.Neutral
|
||||
@@ -1053,10 +1022,6 @@ internal enum class GatewayRecoveryUiState(
|
||||
title = "Pairing Gateway",
|
||||
message = "Approve this phone on the gateway.\nThen retry the connection.",
|
||||
),
|
||||
NodeCapabilityApprovalPending(
|
||||
title = "Node Approval Pending",
|
||||
message = "Gateway pairing worked.\nApprove this phone's node capabilities from an operator UI.",
|
||||
),
|
||||
Pairing(
|
||||
title = "Pairing Gateway",
|
||||
message = "Approval is in progress.\nOpenClaw will reconnect automatically.",
|
||||
@@ -1114,19 +1079,14 @@ internal fun gatewayRecoveryUiState(
|
||||
ready: Boolean,
|
||||
statusText: String,
|
||||
connectSettling: Boolean,
|
||||
nodeCapabilityApprovalState: GatewayNodeApprovalState = GatewayNodeApprovalState.Loading,
|
||||
gatewayConnectionProblem: GatewayConnectionProblem? = null,
|
||||
): GatewayRecoveryUiState =
|
||||
when {
|
||||
ready -> GatewayRecoveryUiState.Connected
|
||||
nodeCapabilityApprovalState == GatewayNodeApprovalState.PendingApproval ||
|
||||
nodeCapabilityApprovalState == GatewayNodeApprovalState.PendingReapproval ||
|
||||
nodeCapabilityApprovalState == GatewayNodeApprovalState.Unapproved -> GatewayRecoveryUiState.NodeCapabilityApprovalPending
|
||||
gatewayConnectionProblem?.isPairingRequired == true &&
|
||||
!gatewayConnectionProblem.canAutoRetry -> GatewayRecoveryUiState.ApprovalRequired
|
||||
gatewayConnectionProblem?.isPairingRequired == true -> GatewayRecoveryUiState.Pairing
|
||||
gatewayConnectionProblem?.pauseReconnect == true -> GatewayRecoveryUiState.Failed
|
||||
nodeCapabilityApprovalState == GatewayNodeApprovalState.Loading -> GatewayRecoveryUiState.Finishing
|
||||
connectSettling -> GatewayRecoveryUiState.Finishing
|
||||
gatewayStatusLooksLikePairing(statusText) -> GatewayRecoveryUiState.Pairing
|
||||
gatewayStatusLooksLikePartialConnect(statusText) -> GatewayRecoveryUiState.Finishing
|
||||
@@ -1210,21 +1170,12 @@ private fun recoveryGatewayDetail(
|
||||
ready: Boolean,
|
||||
remoteAddress: String?,
|
||||
statusText: String,
|
||||
nodeCapabilityApprovalState: GatewayNodeApprovalState,
|
||||
gatewayConnectionProblem: GatewayConnectionProblem?,
|
||||
): String =
|
||||
remoteAddress
|
||||
?.takeIf { it.isNotBlank() }
|
||||
?: if (ready) {
|
||||
"Ready for chat and voice"
|
||||
} else if (
|
||||
nodeCapabilityApprovalState == GatewayNodeApprovalState.PendingApproval ||
|
||||
nodeCapabilityApprovalState == GatewayNodeApprovalState.PendingReapproval ||
|
||||
nodeCapabilityApprovalState == GatewayNodeApprovalState.Unapproved
|
||||
) {
|
||||
"Gateway paired. Waiting for node capability approval."
|
||||
} else if (nodeCapabilityApprovalState == GatewayNodeApprovalState.Loading) {
|
||||
"Gateway paired. Checking node capability approval."
|
||||
} else if (gatewayConnectionProblem?.isPairingRequired == true && !gatewayConnectionProblem.canAutoRetry) {
|
||||
recoveryGatewayApprovalCommand(gatewayConnectionProblem)
|
||||
?.let { "Gateway approval is pending. Run this on the gateway host:" }
|
||||
@@ -1297,24 +1248,11 @@ private class PermissionState(
|
||||
val applyToViewModel: () -> Unit,
|
||||
)
|
||||
|
||||
/** Onboarding finishes only after the gateway resolves node capability approval. */
|
||||
/** Onboarding can finish only after gateway and node channels are both ready. */
|
||||
internal fun canFinishOnboarding(
|
||||
isConnected: Boolean,
|
||||
isNodeConnected: Boolean,
|
||||
nodeCapabilityApprovalState: GatewayNodeApprovalState,
|
||||
): Boolean =
|
||||
isConnected &&
|
||||
isNodeConnected &&
|
||||
when (nodeCapabilityApprovalState) {
|
||||
GatewayNodeApprovalState.PendingApproval,
|
||||
GatewayNodeApprovalState.PendingReapproval,
|
||||
GatewayNodeApprovalState.Unapproved,
|
||||
GatewayNodeApprovalState.Loading,
|
||||
-> false
|
||||
GatewayNodeApprovalState.Approved,
|
||||
GatewayNodeApprovalState.Unsupported,
|
||||
-> true
|
||||
}
|
||||
): Boolean = isConnected && isNodeConnected
|
||||
|
||||
/** Builds permission rows and applies granted feature toggles after onboarding. */
|
||||
@Composable
|
||||
|
||||
@@ -3,7 +3,6 @@ package ai.openclaw.app.ui
|
||||
import ai.openclaw.app.BuildConfig
|
||||
import ai.openclaw.app.GatewayChannelsSummary
|
||||
import ai.openclaw.app.GatewayDreamingSummary
|
||||
import ai.openclaw.app.GatewayNodeApprovalState
|
||||
import ai.openclaw.app.GatewayNodesDevicesSummary
|
||||
import ai.openclaw.app.GatewaySkillSummary
|
||||
import ai.openclaw.app.HomeDestination
|
||||
@@ -567,7 +566,7 @@ internal fun homeAttentionRows(
|
||||
} else {
|
||||
null
|
||||
},
|
||||
if (nodesDevicesSummary.pendingDevices.isNotEmpty() || nodesDevicesSummary.hasNodeCapabilityApprovalPending()) {
|
||||
if (nodesDevicesSummary.pendingDevices.isNotEmpty()) {
|
||||
HomeAttentionRow("Nodes & Devices", nodesDevicesSummaryText(nodesDevicesSummary), Icons.Default.Cloud, Tab.Settings, SettingsRoute.NodesDevices)
|
||||
} else {
|
||||
null
|
||||
@@ -998,7 +997,6 @@ private fun nodesDevicesSummaryText(summary: GatewayNodesDevicesSummary): String
|
||||
val devices = summary.pairedDevices.size
|
||||
return when {
|
||||
summary.pendingDevices.isNotEmpty() -> "${summary.pendingDevices.size} pending"
|
||||
summary.hasNodeCapabilityApprovalPending() -> "Node approval pending"
|
||||
summary.nodes.isNotEmpty() -> "$online/${summary.nodes.size} online"
|
||||
devices > 0 -> "$devices paired"
|
||||
else -> "No devices"
|
||||
@@ -1009,19 +1007,11 @@ private fun nodesDevicesSummaryText(summary: GatewayNodesDevicesSummary): String
|
||||
private fun nodesDevicesStatus(summary: GatewayNodesDevicesSummary): Boolean? =
|
||||
when {
|
||||
summary.pendingDevices.isNotEmpty() -> false
|
||||
summary.hasNodeCapabilityApprovalPending() -> false
|
||||
summary.nodes.any { it.connected } -> true
|
||||
summary.pairedDevices.isNotEmpty() -> true
|
||||
else -> null
|
||||
}
|
||||
|
||||
private fun GatewayNodesDevicesSummary.hasNodeCapabilityApprovalPending(): Boolean =
|
||||
nodes.any { node ->
|
||||
node.approvalState == GatewayNodeApprovalState.PendingApproval ||
|
||||
node.approvalState == GatewayNodeApprovalState.PendingReapproval ||
|
||||
node.approvalState == GatewayNodeApprovalState.Unapproved
|
||||
}
|
||||
|
||||
/** Summarizes channel connection state, surfacing errors before connected counts. */
|
||||
private fun channelsSummaryText(summary: GatewayChannelsSummary): String {
|
||||
val connected = summary.channels.count { it.connected }
|
||||
|
||||
@@ -1,118 +0,0 @@
|
||||
package ai.openclaw.app
|
||||
|
||||
import kotlinx.serialization.json.Json
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
|
||||
class GatewayNodeApprovalStateTest {
|
||||
@Test
|
||||
fun parsesGatewayNodeApprovalState() {
|
||||
assertEquals(GatewayNodeApprovalState.Approved, parseGatewayNodeApprovalState("approved"))
|
||||
assertEquals(GatewayNodeApprovalState.PendingApproval, parseGatewayNodeApprovalState("pending-approval"))
|
||||
assertEquals(GatewayNodeApprovalState.PendingReapproval, parseGatewayNodeApprovalState("pending-reapproval"))
|
||||
assertEquals(GatewayNodeApprovalState.Unapproved, parseGatewayNodeApprovalState("unapproved"))
|
||||
assertEquals(GatewayNodeApprovalState.Loading, parseGatewayNodeApprovalState(null))
|
||||
assertEquals(GatewayNodeApprovalState.Loading, parseGatewayNodeApprovalState("future-state"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parsesNodeListApprovalFields() {
|
||||
val node =
|
||||
parseGatewayNodeSummary(
|
||||
Json.parseToJsonElement(
|
||||
"""
|
||||
{
|
||||
"nodeId": "android-node",
|
||||
"paired": true,
|
||||
"connected": true,
|
||||
"approvalState": "pending-approval",
|
||||
"pendingRequestId": "request-1",
|
||||
"caps": ["device"],
|
||||
"commands": ["device.status"]
|
||||
}
|
||||
""".trimIndent(),
|
||||
),
|
||||
)
|
||||
|
||||
requireNotNull(node)
|
||||
assertEquals(GatewayNodeApprovalState.PendingApproval, node.approvalState)
|
||||
assertEquals("request-1", node.pendingRequestId)
|
||||
assertEquals(listOf("device"), node.capabilities)
|
||||
assertEquals(listOf("device.status"), node.commands)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun treatsMissingNodeApprovalStateAsUnsupported() {
|
||||
val node =
|
||||
parseGatewayNodeSummary(
|
||||
Json.parseToJsonElement("""{"nodeId":"android-node","paired":true,"connected":true}"""),
|
||||
)
|
||||
|
||||
requireNotNull(node)
|
||||
assertEquals(GatewayNodeApprovalState.Unsupported, node.approvalState)
|
||||
assertEquals(
|
||||
GatewayNodeApprovalState.Unsupported,
|
||||
currentNodeCapabilityApprovalState(nodes = listOf(node), selfNodeId = "android-node"),
|
||||
)
|
||||
assertNull(node.pendingRequestId)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun resolvesCurrentPhoneNodeApprovalState() {
|
||||
val nodes =
|
||||
listOf(
|
||||
GatewayNodeSummary(
|
||||
id = "other",
|
||||
displayName = null,
|
||||
remoteIp = null,
|
||||
version = null,
|
||||
deviceFamily = null,
|
||||
paired = true,
|
||||
connected = false,
|
||||
approvalState = GatewayNodeApprovalState.Approved,
|
||||
pendingRequestId = null,
|
||||
capabilities = emptyList(),
|
||||
commands = emptyList(),
|
||||
),
|
||||
GatewayNodeSummary(
|
||||
id = "self",
|
||||
displayName = null,
|
||||
remoteIp = null,
|
||||
version = null,
|
||||
deviceFamily = null,
|
||||
paired = true,
|
||||
connected = true,
|
||||
approvalState = GatewayNodeApprovalState.PendingApproval,
|
||||
pendingRequestId = null,
|
||||
capabilities = emptyList(),
|
||||
commands = emptyList(),
|
||||
),
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
GatewayNodeApprovalState.PendingApproval,
|
||||
currentNodeCapabilityApprovalState(nodes = nodes, selfNodeId = "self"),
|
||||
)
|
||||
assertEquals(
|
||||
GatewayNodeApprovalState.Loading,
|
||||
currentNodeCapabilityApprovalState(nodes = nodes, selfNodeId = "missing"),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun ignoresStaleNodeApprovalRefreshResults() {
|
||||
val guard = GatewayNodeApprovalRefreshGuard()
|
||||
var approvalState = GatewayNodeApprovalState.Loading
|
||||
val staleRefresh = guard.begin()
|
||||
val currentRefresh = guard.begin()
|
||||
|
||||
assertFalse(guard.publishIfCurrent(staleRefresh) { approvalState = GatewayNodeApprovalState.Approved })
|
||||
assertTrue(
|
||||
guard.publishIfCurrent(currentRefresh) { approvalState = GatewayNodeApprovalState.PendingReapproval },
|
||||
)
|
||||
assertEquals(GatewayNodeApprovalState.PendingReapproval, approvalState)
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,6 @@
|
||||
package ai.openclaw.app.ui
|
||||
|
||||
import ai.openclaw.app.GatewayConnectionProblem
|
||||
import ai.openclaw.app.GatewayNodeApprovalState
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
@@ -13,48 +9,22 @@ import org.junit.Test
|
||||
class OnboardingFlowLogicTest {
|
||||
@Test
|
||||
fun blocksFinishWhenOnlyOperatorIsConnected() {
|
||||
assertFalse(canFinishOnboarding(isConnected = true, isNodeConnected = false, nodeCapabilityApprovalState = GatewayNodeApprovalState.Approved))
|
||||
assertFalse(canFinishOnboarding(isConnected = true, isNodeConnected = false))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun blocksFinishWhenDisconnected() {
|
||||
assertFalse(canFinishOnboarding(isConnected = false, isNodeConnected = false, nodeCapabilityApprovalState = GatewayNodeApprovalState.Approved))
|
||||
assertFalse(canFinishOnboarding(isConnected = false, isNodeConnected = false))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun blocksFinishWhenOnlyNodeIsConnected() {
|
||||
assertFalse(canFinishOnboarding(isConnected = false, isNodeConnected = true, nodeCapabilityApprovalState = GatewayNodeApprovalState.Approved))
|
||||
assertFalse(canFinishOnboarding(isConnected = false, isNodeConnected = true))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun blocksFinishWhenNodeCapabilityApprovalIsPending() {
|
||||
assertFalse(canFinishOnboarding(isConnected = true, isNodeConnected = true, nodeCapabilityApprovalState = GatewayNodeApprovalState.PendingApproval))
|
||||
assertFalse(canFinishOnboarding(isConnected = true, isNodeConnected = true, nodeCapabilityApprovalState = GatewayNodeApprovalState.PendingReapproval))
|
||||
assertFalse(canFinishOnboarding(isConnected = true, isNodeConnected = true, nodeCapabilityApprovalState = GatewayNodeApprovalState.Unapproved))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun allowsFinishWhenOperatorNodeAndCapabilityApprovalAreReady() {
|
||||
assertTrue(canFinishOnboarding(isConnected = true, isNodeConnected = true, nodeCapabilityApprovalState = GatewayNodeApprovalState.Approved))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun blocksFinishWhileDelayedNodeListResolvesPendingApproval() =
|
||||
runTest {
|
||||
val delayedNodeList = CompletableDeferred<GatewayNodeApprovalState>()
|
||||
var approvalState = GatewayNodeApprovalState.Loading
|
||||
val refresh = launch { approvalState = delayedNodeList.await() }
|
||||
|
||||
assertFalse(canFinishOnboarding(isConnected = true, isNodeConnected = true, nodeCapabilityApprovalState = approvalState))
|
||||
|
||||
delayedNodeList.complete(GatewayNodeApprovalState.PendingApproval)
|
||||
refresh.join()
|
||||
assertFalse(canFinishOnboarding(isConnected = true, isNodeConnected = true, nodeCapabilityApprovalState = approvalState))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun allowsFinishWhenSuccessfulLegacyNodeListOmitsApprovalState() {
|
||||
assertTrue(canFinishOnboarding(isConnected = true, isNodeConnected = true, nodeCapabilityApprovalState = GatewayNodeApprovalState.Unsupported))
|
||||
fun allowsFinishOnlyWhenOperatorAndNodeAreConnected() {
|
||||
assertTrue(canFinishOnboarding(isConnected = true, isNodeConnected = true))
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -128,32 +98,6 @@ class OnboardingFlowLogicTest {
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun showsNodeApprovalStateWhenCapabilityApprovalIsPending() {
|
||||
assertEquals(
|
||||
GatewayRecoveryUiState.NodeCapabilityApprovalPending,
|
||||
gatewayRecoveryUiState(
|
||||
ready = false,
|
||||
statusText = "Connected",
|
||||
connectSettling = false,
|
||||
nodeCapabilityApprovalState = GatewayNodeApprovalState.PendingApproval,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun showsFinishingStateWhileNodeApprovalLoads() {
|
||||
assertEquals(
|
||||
GatewayRecoveryUiState.Finishing,
|
||||
gatewayRecoveryUiState(
|
||||
ready = false,
|
||||
statusText = "Connected",
|
||||
connectSettling = false,
|
||||
nodeCapabilityApprovalState = GatewayNodeApprovalState.Loading,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun showsApprovalRequiredForPausedPairingProblem() {
|
||||
assertEquals(
|
||||
|
||||
@@ -3,8 +3,6 @@ package ai.openclaw.app.ui
|
||||
import ai.openclaw.app.AppearanceThemeMode
|
||||
import ai.openclaw.app.GatewayChannelSummary
|
||||
import ai.openclaw.app.GatewayChannelsSummary
|
||||
import ai.openclaw.app.GatewayNodeApprovalState
|
||||
import ai.openclaw.app.GatewayNodeSummary
|
||||
import ai.openclaw.app.GatewayNodesDevicesSummary
|
||||
import ai.openclaw.app.GatewayPendingDeviceSummary
|
||||
import org.junit.Assert.assertEquals
|
||||
@@ -120,41 +118,6 @@ class ShellScreenLogicTest {
|
||||
assertEquals(emptyList<String>(), rows.map { it.title })
|
||||
}
|
||||
|
||||
@Test
|
||||
fun homeAttentionRowsSurfacePendingNodeCapabilityApproval() {
|
||||
val rows =
|
||||
homeAttentionRows(
|
||||
isConnected = true,
|
||||
pendingApprovals = 0,
|
||||
channelsSummary = emptyChannels(),
|
||||
nodesDevicesSummary =
|
||||
GatewayNodesDevicesSummary(
|
||||
nodes =
|
||||
listOf(
|
||||
GatewayNodeSummary(
|
||||
id = "android-node",
|
||||
displayName = "Android",
|
||||
remoteIp = null,
|
||||
version = null,
|
||||
deviceFamily = "Android",
|
||||
paired = true,
|
||||
connected = true,
|
||||
approvalState = GatewayNodeApprovalState.PendingApproval,
|
||||
pendingRequestId = null,
|
||||
capabilities = emptyList(),
|
||||
commands = emptyList(),
|
||||
),
|
||||
),
|
||||
pendingDevices = emptyList(),
|
||||
pairedDevices = emptyList(),
|
||||
),
|
||||
readyProviderCount = 1,
|
||||
)
|
||||
|
||||
assertEquals(listOf("Nodes & Devices"), rows.map { it.title })
|
||||
assertEquals("Node approval pending", rows.single().subtitle)
|
||||
}
|
||||
|
||||
private fun emptyChannels(): GatewayChannelsSummary = GatewayChannelsSummary(channels = emptyList())
|
||||
|
||||
private fun emptyNodesDevices(): GatewayNodesDevicesSummary = GatewayNodesDevicesSummary(nodes = emptyList(), pendingDevices = emptyList(), pairedDevices = emptyList())
|
||||
|
||||
@@ -368,7 +368,7 @@ enum ExecApprovalsStore {
|
||||
tempURL.path,
|
||||
targetURL.path,
|
||||
nil,
|
||||
copyfile_flags_t(COPYFILE_DATA | COPYFILE_EXCL))
|
||||
copyfile_flags_t(COPYFILE_EXCL))
|
||||
if copied == -1 {
|
||||
if errno == EEXIST {
|
||||
try? FileManager().removeItem(at: tempURL)
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
e2a646aa93124c089fcfed3c3ef982c88d1fdd2170fcdec274446f3d02f20d2b plugin-sdk-api-baseline.json
|
||||
f1762c7b4bbaea4a3ce47ab943daaa6ca3dbc58322cc5d39688da66b3d483a2d plugin-sdk-api-baseline.jsonl
|
||||
99a18e1e8e3af265e233504b6cf1ff8a227a6466dd0d515c56f823503f0b7bc7 plugin-sdk-api-baseline.json
|
||||
930a414cf783baa2bedb21a85af6fcaa02a12073d9e06cc49c827e7379f85646 plugin-sdk-api-baseline.jsonl
|
||||
|
||||
@@ -422,7 +422,7 @@ curl "https://api.telegram.org/bot<bot_token>/getUpdates"
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Rich message formatting">
|
||||
Outbound text uses standard Telegram HTML messages by default so replies remain readable across current Telegram clients. This compatibility mode supports normal bold, italic, links, code, spoilers, and quotes, but not Bot API 10.1 rich-only blocks such as native tables, details, rich media, and formulas.
|
||||
Outbound text uses standard Telegram HTML messages by default so replies remain readable across current Telegram clients.
|
||||
|
||||
Set `channels.telegram.richMessages: true` to opt into Bot API 10.1 rich messages:
|
||||
|
||||
@@ -436,16 +436,13 @@ curl "https://api.telegram.org/bot<bot_token>/getUpdates"
|
||||
}
|
||||
```
|
||||
|
||||
When enabled:
|
||||
|
||||
- The agent is told that Telegram rich messages are available for this bot/account.
|
||||
- Markdown text is rendered through OpenClaw's Markdown IR and sent as Telegram rich HTML.
|
||||
- Explicit rich HTML payloads preserve supported Bot API 10.1 tags such as headings, tables, details, rich media, and formulas.
|
||||
- Media captions still use Telegram HTML captions because rich messages do not replace captions.
|
||||
|
||||
This keeps model text away from Telegram Rich Markdown sigils, so currency like `$400-600K` is not parsed as math. Long rich text is split automatically across Telegram's rich text and rich block limits. Tables over Telegram's column limit are sent as code blocks.
|
||||
|
||||
Default: off for client compatibility. Rich messages require compatible Telegram clients; some current Desktop, Web, Android, and third-party clients display accepted rich messages as unsupported. Keep this option disabled unless every client used with the bot can render them. `/status` shows whether the current Telegram session has rich messages on or off.
|
||||
Rich messages require compatible Telegram clients. Some current Desktop, Web, Android, and third-party clients display accepted rich messages as unsupported, so keep this option disabled unless every client used with the bot can render them.
|
||||
|
||||
Link previews are enabled by default. `channels.telegram.linkPreview: false` skips automatic entity detection for rich text.
|
||||
|
||||
|
||||
@@ -224,29 +224,6 @@ Optional members:
|
||||
| `onSubagentEnded(params)` | Method | Clean up after a subagent ends. |
|
||||
| `dispose()` | Method | Release resources. Called during gateway shutdown or plugin reload - not per-session. |
|
||||
|
||||
### Runtime settings
|
||||
|
||||
Lifecycle hooks that run inside OpenClaw receive an optional
|
||||
`runtimeSettings` object. It is a versioned, read-only internal
|
||||
producer/consumer API surface: OpenClaw produces it for the selected context
|
||||
engine, and the context engine consumes it inside lifecycle hooks. It is not
|
||||
rendered directly to users and does not create a dedicated reporting surface.
|
||||
|
||||
- `schemaVersion`: currently `1`
|
||||
- `runtime`: OpenClaw host, runtime mode (`normal`, `fallback`, or
|
||||
`degraded`), and optional harness/runtime ids
|
||||
- `contextEngineSelection`: selected context engine id and selection source
|
||||
- `executionHost`: host id and label for the surface invoking the hook
|
||||
- `model`: requested model, resolved model, provider, and optional model family
|
||||
- `limits`: prompt token budget and max output tokens when known
|
||||
- `diagnostics`: closed fallback and degraded reason codes when known
|
||||
|
||||
Fields that can be unknown are represented as `null`; discriminator fields such
|
||||
as runtime mode and selection source remain non-nullable. Older engines remain
|
||||
compatible: if a strict legacy engine rejects `runtimeSettings` as an unknown
|
||||
property, OpenClaw retries the lifecycle call without it instead of quarantining
|
||||
the engine.
|
||||
|
||||
### Host requirements
|
||||
|
||||
Context engines can declare host capability requirements on `info.hostRequirements`.
|
||||
|
||||
@@ -258,9 +258,7 @@ Gemini CLI OAuth is shipped as part of the bundled `google` plugin.
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
Gemini CLI uses `stream-json` by default. OpenClaw reads assistant stream
|
||||
messages and normalizes `stats.cached` into `cacheRead`; legacy
|
||||
`--output-format json` overrides still read reply text from `response`.
|
||||
Gemini CLI JSON replies are parsed from `response`; usage falls back to `stats`, with `stats.cached` normalized into OpenClaw `cacheRead`.
|
||||
|
||||
### Z.AI (GLM)
|
||||
|
||||
|
||||
@@ -1386,11 +1386,7 @@
|
||||
"clawhub/api",
|
||||
"clawhub/http-api",
|
||||
"clawhub/acceptable-usage",
|
||||
"clawhub/moderation",
|
||||
"clawhub/security",
|
||||
"clawhub/security-audits",
|
||||
"clawhub/content-rights",
|
||||
"clawhub/plugin-validation-fixes"
|
||||
"clawhub/content-rights"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
@@ -287,10 +287,8 @@ load local files from plain paths.
|
||||
## Inputs / outputs
|
||||
|
||||
- `output: "json"` (default) tries to parse JSON and extract text + session id.
|
||||
- For Gemini CLI JSON output, OpenClaw reads reply text from `response` and usage
|
||||
from `stats` when `usage` is missing or empty. The bundled Gemini CLI default
|
||||
uses `stream-json`, but old `--output-format json` overrides still use the
|
||||
JSON parser.
|
||||
- For Gemini CLI JSON output, OpenClaw reads reply text from `response` and
|
||||
usage from `stats` when `usage` is missing or empty.
|
||||
- `output: "jsonl"` parses JSONL streams and extracts the final agent message plus session
|
||||
identifiers when present.
|
||||
- `output: "text"` treats stdout as the final response.
|
||||
@@ -320,11 +318,8 @@ The bundled Anthropic plugin registers a default for `claude-cli`:
|
||||
The bundled Google plugin also registers a default for `google-gemini-cli`:
|
||||
|
||||
- `command: "gemini"`
|
||||
- `args: ["--skip-trust", "--approval-mode", "auto_edit", "--output-format", "stream-json", "--prompt", "{prompt}"]`
|
||||
- `resumeArgs: ["--skip-trust", "--approval-mode", "auto_edit", "--resume", "{sessionId}", "--output-format", "stream-json", "--prompt", "{prompt}"]`
|
||||
- `output: "jsonl"`
|
||||
- `resumeOutput: "jsonl"`
|
||||
- `jsonlDialect: "gemini-stream-json"`
|
||||
- `args: ["--output-format", "json", "--prompt", "{prompt}"]`
|
||||
- `resumeArgs: ["--resume", "{sessionId}", "--output-format", "json", "--prompt", "{prompt}"]`
|
||||
- `imageArg: "@"`
|
||||
- `imagePathScope: "workspace"`
|
||||
- `modelArg: "--model"`
|
||||
@@ -335,13 +330,9 @@ Prerequisite: the local Gemini CLI must be installed and available as
|
||||
`gemini` on `PATH` (`brew install gemini-cli` or
|
||||
`npm install -g @google/gemini-cli`).
|
||||
|
||||
Gemini CLI output notes:
|
||||
Gemini CLI JSON notes:
|
||||
|
||||
- The default `stream-json` parser reads assistant `message` events, tool events,
|
||||
final `result` usage, and fatal Gemini error events.
|
||||
- If you override Gemini args to `--output-format json`, OpenClaw normalizes that
|
||||
backend back to `output: "json"` and reads reply text from the JSON `response`
|
||||
field.
|
||||
- Reply text is read from the JSON `response` field.
|
||||
- Usage falls back to `stats` when `usage` is absent or empty.
|
||||
- `stats.cached` is normalized into OpenClaw `cacheRead`.
|
||||
- If `stats.input` is missing, OpenClaw derives input tokens from
|
||||
@@ -381,10 +372,8 @@ api.registerTextTransforms({
|
||||
rewrites streamed assistant deltas and parsed final text before OpenClaw handles
|
||||
its own control markers and channel delivery.
|
||||
|
||||
For CLIs that emit provider-specific JSONL events, set `jsonlDialect` on that
|
||||
backend's config. Supported dialects are `claude-stream-json` for Claude
|
||||
Code-compatible streams and `gemini-stream-json` for Gemini CLI `stream-json`
|
||||
events.
|
||||
For CLIs that emit Claude Code stream-json compatible JSONL, set
|
||||
`jsonlDialect: "claude-stream-json"` on that backend's config.
|
||||
|
||||
## Native compaction ownership
|
||||
|
||||
|
||||
@@ -388,13 +388,13 @@ For an end-to-end authoring guide, see
|
||||
|
||||
### Exclusive slots
|
||||
|
||||
| Method | What it registers |
|
||||
| ------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `api.registerContextEngine(id, factory)` | Context engine (one active at a time). Lifecycle callbacks receive `runtimeSettings` when the host can provide model/provider/mode diagnostics; older strict engines are retried without that key. |
|
||||
| `api.registerMemoryCapability(capability)` | Unified memory capability |
|
||||
| `api.registerMemoryPromptSection(builder)` | Memory prompt section builder |
|
||||
| `api.registerMemoryFlushPlan(resolver)` | Memory flush plan resolver |
|
||||
| `api.registerMemoryRuntime(runtime)` | Memory runtime adapter |
|
||||
| Method | What it registers |
|
||||
| ------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `api.registerContextEngine(id, factory)` | Context engine (one active at a time). The `assemble()` callback receives `availableTools` and `citationsMode` so the engine can tailor prompt additions. |
|
||||
| `api.registerMemoryCapability(capability)` | Unified memory capability |
|
||||
| `api.registerMemoryPromptSection(builder)` | Memory prompt section builder |
|
||||
| `api.registerMemoryFlushPlan(resolver)` | Memory flush plan resolver |
|
||||
| `api.registerMemoryRuntime(runtime)` | Memory runtime adapter |
|
||||
|
||||
### Deprecated memory embedding adapters
|
||||
|
||||
|
||||
@@ -435,14 +435,11 @@ WebSocket endpoint, sends the initial setup payload, and waits for
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Gemini CLI usage notes">
|
||||
When using the `google-gemini-cli` OAuth provider, OpenClaw uses Gemini
|
||||
CLI `stream-json` output by default and normalizes usage from the final
|
||||
`stats` payload. Legacy `--output-format json` overrides still use the
|
||||
JSON parser.
|
||||
<Accordion title="Gemini CLI JSON usage notes">
|
||||
When using the `google-gemini-cli` OAuth provider, OpenClaw normalizes
|
||||
the CLI JSON output as follows:
|
||||
|
||||
- Streamed reply text comes from assistant `message` events.
|
||||
- For legacy JSON output, reply text comes from the CLI JSON `response` field.
|
||||
- Reply text comes from the CLI JSON `response` field.
|
||||
- Usage falls back to `stats` when the CLI leaves `usage` empty.
|
||||
- `stats.cached` is normalized into OpenClaw `cacheRead`.
|
||||
- If `stats.input` is missing, OpenClaw derives input tokens from
|
||||
|
||||
@@ -155,29 +155,7 @@ the maintainer-only release runbook.
|
||||
11. After publish, run the npm post-publish verifier, optional standalone
|
||||
published-npm Telegram E2E when you need post-publish channel proof,
|
||||
dist-tag promotion when needed, verify the generated GitHub release page,
|
||||
run the release announcement steps, then complete [Stable main
|
||||
closeout](#stable-main-closeout) before calling a stable release finished.
|
||||
|
||||
## Stable main closeout
|
||||
|
||||
Stable publication is not complete until `main` carries the actual shipped
|
||||
release state.
|
||||
|
||||
1. Start from fresh latest `main`. Audit `release/YYYY.M.PATCH` against it and
|
||||
forward-port real fixes that are absent from `main`. Do not blindly merge
|
||||
release-only compatibility, test, or validation adapters into newer `main`.
|
||||
2. Set `main` to the shipped stable version, not a speculative next train. Run
|
||||
`pnpm release:prep` after the root version change, then
|
||||
`pnpm deps:shrinkwrap:generate`.
|
||||
3. Make `CHANGELOG.md`'s `## YYYY.M.PATCH` section on `main` exactly match the
|
||||
tagged release branch. Include the stable `appcast.xml` update when the mac
|
||||
release published one.
|
||||
4. Do not add `YYYY.M.PATCH+1`, a beta version, or an empty future changelog
|
||||
section to `main` until the operator explicitly starts that release train.
|
||||
5. Run `pnpm release:generated:check`, `pnpm deps:shrinkwrap:check`, and
|
||||
`OPENCLAW_TESTBOX=1 pnpm check:changed`. Push, then verify `origin/main`
|
||||
contains the shipped version and changelog before calling the stable release
|
||||
done.
|
||||
and run the release announcement steps.
|
||||
|
||||
## Release preflight
|
||||
|
||||
|
||||
@@ -31,9 +31,9 @@ OpenClaw features that can generate provider usage or paid API calls.
|
||||
- `/usage tokens` shows tokens only; subscription-style OAuth/token and CLI flows
|
||||
still show tokens only unless that runtime supplies compatible usage metadata
|
||||
and an explicit local price is configured.
|
||||
- Gemini CLI note: the default `stream-json` output and legacy JSON overrides
|
||||
both read usage from `stats`, normalize `stats.cached` into `cacheRead`, and
|
||||
derive input tokens from `stats.input_tokens - stats.cached` when needed.
|
||||
- Gemini CLI note: when the CLI returns JSON output, OpenClaw reads usage from
|
||||
`stats`, normalizes `stats.cached` into `cacheRead`, and derives input tokens
|
||||
from `stats.input_tokens - stats.cached` when needed.
|
||||
|
||||
Anthropic note: Anthropic staff told us OpenClaw-style Claude CLI usage is
|
||||
allowed again, so OpenClaw treats Claude CLI reuse and `claude -p` usage as
|
||||
|
||||
@@ -163,11 +163,10 @@ If the provider does not support this cache mode, `cacheRetention` has no effect
|
||||
OpenClaw manages a provider-native `cachedContents` resource rather than
|
||||
injecting cache markers into the request.
|
||||
|
||||
### Gemini CLI usage
|
||||
### Gemini CLI JSON usage
|
||||
|
||||
- Gemini CLI `stream-json` output can surface cache hits through `stats.cached`;
|
||||
OpenClaw maps that to `cacheRead`. Legacy `--output-format json` overrides use
|
||||
the same usage normalization.
|
||||
- Gemini CLI JSON output can also surface cache hits through `stats.cached`;
|
||||
OpenClaw maps that to `cacheRead`.
|
||||
- If the CLI omits a direct `stats.input` value, OpenClaw derives input tokens
|
||||
from `stats.input_tokens - stats.cached`.
|
||||
- This is usage normalization only. It does not mean OpenClaw is creating
|
||||
|
||||
@@ -362,8 +362,8 @@ OpenClaw also enforces a safety floor for embedded runs:
|
||||
|
||||
Why: leave enough headroom for multi-turn "housekeeping" (like memory writes) before compaction becomes unavoidable.
|
||||
|
||||
Implementation: `applyAgentCompactionSettingsFromConfig()` in `src/agents/agent-settings.ts`
|
||||
(called from embedded-runner turn and compaction setup paths).
|
||||
Implementation: `ensureAgentCompactionReserveTokens()` in `src/agents/agent-settings.ts`
|
||||
(called from `src/agents/embedded-agent-runner.ts`).
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -92,11 +92,9 @@ Usage surfaces normalize common provider-native field aliases before display.
|
||||
For OpenAI-family Responses traffic, that includes both `input_tokens` /
|
||||
`output_tokens` and `prompt_tokens` / `completion_tokens`, so transport-specific
|
||||
field names do not change `/status`, `/usage`, or session summaries.
|
||||
Gemini CLI usage is normalized too: the default `stream-json` parser reads
|
||||
assistant `message` events, and `stats.cached` maps to `cacheRead` with
|
||||
`stats.input_tokens - stats.cached` used when the CLI omits an explicit
|
||||
`stats.input` field. Legacy JSON overrides still read reply text from
|
||||
`response`.
|
||||
Gemini CLI JSON usage is normalized too: reply text comes from `response`, and
|
||||
`stats.cached` maps to `cacheRead` with `stats.input_tokens - stats.cached`
|
||||
used when the CLI omits an explicit `stats.input` field.
|
||||
For native OpenAI-family Responses traffic, WebSocket/SSE usage aliases are
|
||||
normalized the same way, and totals fall back to normalized input + output when
|
||||
`total_tokens` is missing or `0`.
|
||||
|
||||
4
extensions/acpx/npm-shrinkwrap.json
generated
4
extensions/acpx/npm-shrinkwrap.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@openclaw/acpx",
|
||||
"version": "2026.6.8",
|
||||
"version": "2026.6.9",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@openclaw/acpx",
|
||||
"version": "2026.6.8",
|
||||
"version": "2026.6.9",
|
||||
"dependencies": {
|
||||
"@agentclientprotocol/claude-agent-acp": "0.39.0",
|
||||
"@zed-industries/codex-acp": "0.15.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@openclaw/acpx",
|
||||
"version": "2026.6.8",
|
||||
"version": "2026.6.9",
|
||||
"description": "OpenClaw ACP runtime backend with plugin-owned session and transport management.",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -26,10 +26,10 @@
|
||||
"minHostVersion": ">=2026.4.25"
|
||||
},
|
||||
"compat": {
|
||||
"pluginApi": ">=2026.6.8"
|
||||
"pluginApi": ">=2026.6.9"
|
||||
},
|
||||
"build": {
|
||||
"openclawVersion": "2026.6.8",
|
||||
"openclawVersion": "2026.6.9",
|
||||
"staticAssets": [
|
||||
{
|
||||
"source": "./src/runtime-internals/mcp-proxy.mjs",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@openclaw/admin-http-rpc",
|
||||
"version": "2026.6.8",
|
||||
"version": "2026.6.9",
|
||||
"private": true,
|
||||
"description": "OpenClaw admin HTTP RPC endpoint",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@openclaw/alibaba-provider",
|
||||
"version": "2026.6.8",
|
||||
"version": "2026.6.9",
|
||||
"private": true,
|
||||
"description": "OpenClaw Alibaba Model Studio video provider plugin",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@openclaw/amazon-bedrock-mantle-provider",
|
||||
"version": "2026.6.8",
|
||||
"version": "2026.6.9",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@openclaw/amazon-bedrock-mantle-provider",
|
||||
"version": "2026.6.8",
|
||||
"version": "2026.6.9",
|
||||
"dependencies": {
|
||||
"@anthropic-ai/sdk": "0.100.1",
|
||||
"@aws/bedrock-token-generator": "1.1.0"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@openclaw/amazon-bedrock-mantle-provider",
|
||||
"version": "2026.6.8",
|
||||
"version": "2026.6.9",
|
||||
"description": "OpenClaw Amazon Bedrock Mantle provider plugin for OpenAI-compatible model routing.",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -24,10 +24,10 @@
|
||||
"minHostVersion": ">=2026.5.12-beta.1"
|
||||
},
|
||||
"compat": {
|
||||
"pluginApi": ">=2026.6.8"
|
||||
"pluginApi": ">=2026.6.9"
|
||||
},
|
||||
"build": {
|
||||
"openclawVersion": "2026.6.8",
|
||||
"openclawVersion": "2026.6.9",
|
||||
"bundledDist": false
|
||||
},
|
||||
"release": {
|
||||
|
||||
4
extensions/amazon-bedrock/npm-shrinkwrap.json
generated
4
extensions/amazon-bedrock/npm-shrinkwrap.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@openclaw/amazon-bedrock-provider",
|
||||
"version": "2026.6.8",
|
||||
"version": "2026.6.9",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@openclaw/amazon-bedrock-provider",
|
||||
"version": "2026.6.8",
|
||||
"version": "2026.6.9",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-bedrock": "3.1056.0",
|
||||
"@aws-sdk/client-bedrock-runtime": "3.1056.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@openclaw/amazon-bedrock-provider",
|
||||
"version": "2026.6.8",
|
||||
"version": "2026.6.9",
|
||||
"description": "OpenClaw Amazon Bedrock provider plugin with model discovery, embeddings, and guardrail support.",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -28,10 +28,10 @@
|
||||
"minHostVersion": ">=2026.5.12-beta.1"
|
||||
},
|
||||
"compat": {
|
||||
"pluginApi": ">=2026.6.8"
|
||||
"pluginApi": ">=2026.6.9"
|
||||
},
|
||||
"build": {
|
||||
"openclawVersion": "2026.6.8",
|
||||
"openclawVersion": "2026.6.9",
|
||||
"bundledDist": false
|
||||
},
|
||||
"release": {
|
||||
|
||||
4
extensions/anthropic-vertex/npm-shrinkwrap.json
generated
4
extensions/anthropic-vertex/npm-shrinkwrap.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@openclaw/anthropic-vertex-provider",
|
||||
"version": "2026.6.8",
|
||||
"version": "2026.6.9",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@openclaw/anthropic-vertex-provider",
|
||||
"version": "2026.6.8",
|
||||
"version": "2026.6.9",
|
||||
"dependencies": {
|
||||
"@anthropic-ai/vertex-sdk": "0.16.1"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@openclaw/anthropic-vertex-provider",
|
||||
"version": "2026.6.8",
|
||||
"version": "2026.6.9",
|
||||
"description": "OpenClaw Anthropic Vertex provider plugin for Claude models on Google Vertex AI.",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -23,10 +23,10 @@
|
||||
"minHostVersion": ">=2026.5.12-beta.1"
|
||||
},
|
||||
"compat": {
|
||||
"pluginApi": ">=2026.6.8"
|
||||
"pluginApi": ">=2026.6.9"
|
||||
},
|
||||
"build": {
|
||||
"openclawVersion": "2026.6.8",
|
||||
"openclawVersion": "2026.6.9",
|
||||
"bundledDist": false
|
||||
},
|
||||
"release": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@openclaw/anthropic-provider",
|
||||
"version": "2026.6.8",
|
||||
"version": "2026.6.9",
|
||||
"private": true,
|
||||
"description": "OpenClaw Anthropic provider plugin",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@openclaw/arcee-provider",
|
||||
"version": "2026.6.8",
|
||||
"version": "2026.6.9",
|
||||
"private": true,
|
||||
"description": "OpenClaw Arcee provider plugin",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@openclaw/azure-speech",
|
||||
"version": "2026.6.8",
|
||||
"version": "2026.6.9",
|
||||
"private": true,
|
||||
"description": "OpenClaw Azure Speech plugin",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@openclaw/bonjour",
|
||||
"version": "2026.6.8",
|
||||
"version": "2026.6.9",
|
||||
"description": "OpenClaw Bonjour/mDNS gateway discovery",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
|
||||
4
extensions/brave/npm-shrinkwrap.json
generated
4
extensions/brave/npm-shrinkwrap.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@openclaw/brave-plugin",
|
||||
"version": "2026.6.8",
|
||||
"version": "2026.6.9",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@openclaw/brave-plugin",
|
||||
"version": "2026.6.8"
|
||||
"version": "2026.6.9"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@openclaw/brave-plugin",
|
||||
"version": "2026.6.8",
|
||||
"version": "2026.6.9",
|
||||
"description": "OpenClaw Brave Search provider plugin for web search.",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -21,10 +21,10 @@
|
||||
"allowInvalidConfigRecovery": true
|
||||
},
|
||||
"compat": {
|
||||
"pluginApi": ">=2026.6.8"
|
||||
"pluginApi": ">=2026.6.9"
|
||||
},
|
||||
"build": {
|
||||
"openclawVersion": "2026.6.8"
|
||||
"openclawVersion": "2026.6.9"
|
||||
},
|
||||
"release": {
|
||||
"publishToClawHub": true,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@openclaw/browser-plugin",
|
||||
"version": "2026.6.8",
|
||||
"version": "2026.6.9",
|
||||
"private": true,
|
||||
"description": "OpenClaw browser tool plugin",
|
||||
"type": "module",
|
||||
|
||||
@@ -27,9 +27,9 @@ export const CHROME_STOP_TIMEOUT_MS = 2500;
|
||||
export const CHROME_STOP_PROBE_TIMEOUT_MS = 200;
|
||||
export const CHROME_STDERR_HINT_MAX_CHARS = 2000;
|
||||
|
||||
const PROFILE_HTTP_REACHABILITY_TIMEOUT_MS = 300;
|
||||
const PROFILE_WS_REACHABILITY_MIN_TIMEOUT_MS = 200;
|
||||
const PROFILE_WS_REACHABILITY_MAX_TIMEOUT_MS = 2000;
|
||||
export const PROFILE_HTTP_REACHABILITY_TIMEOUT_MS = 300;
|
||||
export const PROFILE_WS_REACHABILITY_MIN_TIMEOUT_MS = 200;
|
||||
export const PROFILE_WS_REACHABILITY_MAX_TIMEOUT_MS = 2000;
|
||||
export const PROFILE_ATTACH_RETRY_TIMEOUT_MS = 1200;
|
||||
export const PROFILE_POST_RESTART_WS_TIMEOUT_MS = 600;
|
||||
export const CHROME_MCP_ATTACH_READY_WINDOW_MS = 8000;
|
||||
|
||||
@@ -2,14 +2,15 @@
|
||||
import { MAX_TIMER_TIMEOUT_MS } from "openclaw/plugin-sdk/number-runtime";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { resolveCdpReachabilityPolicy } from "./cdp-reachability-policy.js";
|
||||
import { resolveCdpReachabilityTimeouts } from "./cdp-timeouts.js";
|
||||
import {
|
||||
PROFILE_HTTP_REACHABILITY_TIMEOUT_MS,
|
||||
PROFILE_WS_REACHABILITY_MAX_TIMEOUT_MS,
|
||||
PROFILE_WS_REACHABILITY_MIN_TIMEOUT_MS,
|
||||
resolveCdpReachabilityTimeouts,
|
||||
} from "./cdp-timeouts.js";
|
||||
import type { ResolvedBrowserProfile } from "./config.js";
|
||||
import { assertBrowserNavigationAllowed } from "./navigation-guard.js";
|
||||
|
||||
const PROFILE_HTTP_REACHABILITY_TIMEOUT_MS = 300;
|
||||
const PROFILE_WS_REACHABILITY_MIN_TIMEOUT_MS = 200;
|
||||
const PROFILE_WS_REACHABILITY_MAX_TIMEOUT_MS = 2000;
|
||||
|
||||
const fetchWithSsrFGuardMock = vi.hoisted(() => vi.fn());
|
||||
|
||||
vi.mock("openclaw/plugin-sdk/ssrf-runtime", async (importOriginal) => {
|
||||
|
||||
@@ -41,21 +41,18 @@ function profileContext(tabs: Array<{ targetId: string; url: string }>) {
|
||||
};
|
||||
}
|
||||
|
||||
function routeContextForTab(
|
||||
url: string,
|
||||
ensureTabAvailable = vi.fn(async () => ({
|
||||
targetId: "tab-1",
|
||||
title: "Tab",
|
||||
url,
|
||||
type: "page",
|
||||
})),
|
||||
): BrowserRouteContext {
|
||||
function routeContextForTab(url: string): BrowserRouteContext {
|
||||
const profileCtx = {
|
||||
profile: {
|
||||
cdpUrl: "http://127.0.0.1:9222",
|
||||
name: "default",
|
||||
},
|
||||
ensureTabAvailable,
|
||||
ensureTabAvailable: vi.fn(async () => ({
|
||||
targetId: "tab-1",
|
||||
title: "Tab",
|
||||
url,
|
||||
type: "page",
|
||||
})),
|
||||
} as unknown as ProfileContext;
|
||||
|
||||
return {
|
||||
@@ -135,27 +132,6 @@ describe("browser route shared helpers", () => {
|
||||
});
|
||||
|
||||
describe("withRouteTabContext", () => {
|
||||
it("opts agent routes into Playwright target-id fallback", async () => {
|
||||
const response = createBrowserRouteResponse();
|
||||
const ensureTabAvailable = vi.fn(async () => ({
|
||||
targetId: "tab-1",
|
||||
title: "Tab",
|
||||
url: "https://example.com",
|
||||
type: "page",
|
||||
}));
|
||||
|
||||
await withRouteTabContext({
|
||||
req: requestWithBody({}),
|
||||
res: response.res,
|
||||
ctx: routeContextForTab("https://example.com", ensureTabAvailable),
|
||||
run: async () => {},
|
||||
});
|
||||
|
||||
expect(ensureTabAvailable).toHaveBeenCalledWith(undefined, {
|
||||
allowPlaywrightFallback: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("does not enforce current-tab URL policy unless requested", async () => {
|
||||
const response = createBrowserRouteResponse();
|
||||
const run = vi.fn(async () => {
|
||||
|
||||
@@ -147,10 +147,7 @@ export async function withRouteTabContext<T>(
|
||||
return undefined;
|
||||
}
|
||||
try {
|
||||
// Agent routes can address local-managed tabs through Playwright when per-tab WS discovery lags.
|
||||
const tab = await profileCtx.ensureTabAvailable(params.targetId, {
|
||||
allowPlaywrightFallback: true,
|
||||
});
|
||||
const tab = await profileCtx.ensureTabAvailable(params.targetId);
|
||||
if (params.enforceCurrentUrlAllowed) {
|
||||
await assertBrowserNavigationResultAllowed({
|
||||
url: tab.url,
|
||||
|
||||
@@ -128,9 +128,6 @@ describe("local-managed browser snapshot routes", () => {
|
||||
|
||||
expect(response.statusCode).toBe(400);
|
||||
expect(response.body).toEqual({ error: "browser navigation blocked by policy" });
|
||||
expect(routeState.profileCtx.ensureTabAvailable).toHaveBeenCalledWith(undefined, {
|
||||
allowPlaywrightFallback: false,
|
||||
});
|
||||
expect(navigationGuardMocks.assertBrowserNavigationResultAllowed).toHaveBeenCalledWith({
|
||||
url: "http://127.0.0.1:8080/admin",
|
||||
ssrfPolicy: { dangerouslyAllowPrivateNetwork: false },
|
||||
|
||||
@@ -594,9 +594,7 @@ export function registerBrowserAgentSnapshotRoutes(
|
||||
});
|
||||
|
||||
try {
|
||||
const tab = await profileCtx.ensureTabAvailable(targetId || undefined, {
|
||||
allowPlaywrightFallback: hasPlaywright,
|
||||
});
|
||||
const tab = await profileCtx.ensureTabAvailable(targetId || undefined);
|
||||
const usesChromeMcp = getBrowserProfileCapabilities(profileCtx.profile).usesChromeMcp;
|
||||
const ssrfPolicyOpts = browserNavigationPolicyForProfile(ctx, profileCtx);
|
||||
if ((plan.labels || plan.mode === "efficient") && plan.format === "aria") {
|
||||
|
||||
@@ -3,14 +3,15 @@ import type { ChildProcessWithoutNullStreams } from "node:child_process";
|
||||
import { EventEmitter } from "node:events";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import "./server-context.chrome-test-harness.js";
|
||||
import { PROFILE_ATTACH_RETRY_TIMEOUT_MS } from "./cdp-timeouts.js";
|
||||
import {
|
||||
PROFILE_ATTACH_RETRY_TIMEOUT_MS,
|
||||
PROFILE_HTTP_REACHABILITY_TIMEOUT_MS,
|
||||
} from "./cdp-timeouts.js";
|
||||
import * as chromeModule from "./chrome.js";
|
||||
import { BrowserProfileUnavailableError } from "./errors.js";
|
||||
import { createBrowserRouteContext } from "./server-context.js";
|
||||
import { makeBrowserServerState, mockLaunchedChrome } from "./server-context.test-harness.js";
|
||||
|
||||
const PROFILE_HTTP_REACHABILITY_TIMEOUT_MS = 300;
|
||||
|
||||
function setupEnsureBrowserAvailableHarness() {
|
||||
vi.useFakeTimers();
|
||||
|
||||
|
||||
@@ -1,202 +0,0 @@
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import type { ResolvedBrowserProfile } from "./config.js";
|
||||
import {
|
||||
OPEN_TAB_DISCOVERY_POLL_MS,
|
||||
OPEN_TAB_DISCOVERY_WINDOW_MS,
|
||||
} from "./server-context.constants.js";
|
||||
import { createProfileSelectionOps } from "./server-context.selection.js";
|
||||
import type { BrowserTab, ProfileRuntimeState } from "./server-context.types.js";
|
||||
|
||||
const LOCAL_PROFILE: ResolvedBrowserProfile = {
|
||||
name: "openclaw",
|
||||
cdpPort: 18800,
|
||||
cdpUrl: "http://127.0.0.1:18800",
|
||||
cdpHost: "127.0.0.1",
|
||||
cdpIsLoopback: true,
|
||||
color: "#FF4500",
|
||||
driver: "openclaw",
|
||||
headless: true,
|
||||
headlessSource: "config",
|
||||
attachOnly: false,
|
||||
};
|
||||
|
||||
function tab(targetId: string, wsUrl?: string): BrowserTab {
|
||||
return {
|
||||
targetId,
|
||||
title: targetId,
|
||||
url: `https://${targetId.toLowerCase()}.example`,
|
||||
type: "page",
|
||||
...(wsUrl ? { wsUrl } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
function createSelectionHarness(params: {
|
||||
snapshots: Array<BrowserTab[] | Error>;
|
||||
openedTab?: BrowserTab;
|
||||
}) {
|
||||
const snapshots = [...params.snapshots];
|
||||
let lastSnapshot: BrowserTab[] = [];
|
||||
const listTabs = vi.fn(async () => {
|
||||
const next = snapshots.shift();
|
||||
if (next instanceof Error) {
|
||||
throw next;
|
||||
}
|
||||
if (next) {
|
||||
lastSnapshot = next;
|
||||
}
|
||||
return lastSnapshot;
|
||||
});
|
||||
const profileState: ProfileRuntimeState = {
|
||||
profile: LOCAL_PROFILE,
|
||||
running: null,
|
||||
lastTargetId: null,
|
||||
reconcile: null,
|
||||
};
|
||||
const openTab = vi.fn(async () => {
|
||||
const openedTab = params.openedTab ?? tab("OPENED");
|
||||
profileState.lastTargetId = openedTab.targetId;
|
||||
return openedTab;
|
||||
});
|
||||
const selection = createProfileSelectionOps({
|
||||
profile: LOCAL_PROFILE,
|
||||
getProfileState: () => profileState,
|
||||
getCdpControlPolicy: () => undefined,
|
||||
ensureBrowserAvailable: async () => {},
|
||||
listTabs,
|
||||
openTab,
|
||||
});
|
||||
return { selection, listTabs, openTab, profileState };
|
||||
}
|
||||
|
||||
async function advancePastDiscoveryWindow(): Promise<void> {
|
||||
await vi.advanceTimersByTimeAsync(OPEN_TAB_DISCOVERY_WINDOW_MS + OPEN_TAB_DISCOVERY_POLL_MS);
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
describe("browser profile tab selection", () => {
|
||||
it("preserves the opened tab when the immediate relist omits it", async () => {
|
||||
const openedTab = tab("OPENED", "ws://127.0.0.1/devtools/page/OPENED");
|
||||
const { selection, listTabs, openTab } = createSelectionHarness({
|
||||
snapshots: [[], []],
|
||||
openedTab,
|
||||
});
|
||||
|
||||
await expect(selection.ensureTabAvailable()).resolves.toEqual(openedTab);
|
||||
expect(openTab).toHaveBeenCalledOnce();
|
||||
expect(listTabs).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it("preserves a target-id-only opened tab for a Playwright-backed caller", async () => {
|
||||
vi.useFakeTimers();
|
||||
const openedTab = tab("OPENED");
|
||||
const otherWithWs = tab("OTHER", "ws://127.0.0.1/devtools/page/OTHER");
|
||||
const { selection } = createSelectionHarness({
|
||||
snapshots: [[], [otherWithWs]],
|
||||
openedTab,
|
||||
});
|
||||
|
||||
const selected = selection.ensureTabAvailable(undefined, {
|
||||
allowPlaywrightFallback: true,
|
||||
});
|
||||
await advancePastDiscoveryWindow();
|
||||
|
||||
await expect(selected).resolves.toEqual(openedTab);
|
||||
});
|
||||
|
||||
it("polls until delayed wsUrl discovery makes an existing tab selectable", async () => {
|
||||
vi.useFakeTimers();
|
||||
const withoutWs = tab("LAGGING");
|
||||
const withWs = tab("LAGGING", "ws://127.0.0.1/devtools/page/LAGGING");
|
||||
const { selection, listTabs, openTab } = createSelectionHarness({
|
||||
snapshots: [[withoutWs], [withoutWs], [withWs]],
|
||||
});
|
||||
|
||||
const selected = selection.ensureTabAvailable();
|
||||
await vi.advanceTimersByTimeAsync(OPEN_TAB_DISCOVERY_POLL_MS);
|
||||
|
||||
await expect(selected).resolves.toEqual(withWs);
|
||||
expect(listTabs).toHaveBeenCalledTimes(3);
|
||||
expect(openTab).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("allows an existing target-id-only tab only for Playwright-backed callers", async () => {
|
||||
vi.useFakeTimers();
|
||||
const withoutWs = tab("PLAYWRIGHT_TARGET");
|
||||
const otherWithWs = tab("OTHER", "ws://127.0.0.1/devtools/page/OTHER");
|
||||
const { selection } = createSelectionHarness({
|
||||
snapshots: [[withoutWs, otherWithWs]],
|
||||
});
|
||||
|
||||
const selected = selection.ensureTabAvailable("PLAYWRIGHT_TARGET", {
|
||||
allowPlaywrightFallback: true,
|
||||
});
|
||||
await advancePastDiscoveryWindow();
|
||||
|
||||
await expect(selected).resolves.toEqual(withoutWs);
|
||||
});
|
||||
|
||||
it("preserves a sticky target-id-only tab instead of switching to another tab", async () => {
|
||||
vi.useFakeTimers();
|
||||
const stickyWithoutWs = tab("STICKY");
|
||||
const otherWithWs = tab("OTHER", "ws://127.0.0.1/devtools/page/OTHER");
|
||||
const { selection, profileState } = createSelectionHarness({
|
||||
snapshots: [[stickyWithoutWs, otherWithWs]],
|
||||
});
|
||||
profileState.lastTargetId = stickyWithoutWs.targetId;
|
||||
|
||||
const selected = selection.ensureTabAvailable(undefined, {
|
||||
allowPlaywrightFallback: true,
|
||||
});
|
||||
await advancePastDiscoveryWindow();
|
||||
|
||||
await expect(selected).resolves.toEqual(stickyWithoutWs);
|
||||
});
|
||||
|
||||
it("keeps polling after a transient tab-list rejection", async () => {
|
||||
vi.useFakeTimers();
|
||||
const withoutWs = tab("RECOVERED");
|
||||
const withWs = tab("RECOVERED", "ws://127.0.0.1/devtools/page/RECOVERED");
|
||||
const { selection, listTabs } = createSelectionHarness({
|
||||
snapshots: [[withoutWs], new Error("transient list failure"), [withWs]],
|
||||
});
|
||||
|
||||
const selected = selection.ensureTabAvailable();
|
||||
await vi.advanceTimersByTimeAsync(OPEN_TAB_DISCOVERY_POLL_MS);
|
||||
|
||||
await expect(selected).resolves.toEqual(withWs);
|
||||
expect(listTabs).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
|
||||
it("falls back to the last nonempty unfiltered snapshot after empty relists", async () => {
|
||||
vi.useFakeTimers();
|
||||
const withoutWs = tab("LAST_NONEMPTY");
|
||||
const { selection, openTab } = createSelectionHarness({
|
||||
snapshots: [[withoutWs], [], new Error("transient list failure")],
|
||||
});
|
||||
|
||||
const selected = selection.ensureTabAvailable(undefined, {
|
||||
allowPlaywrightFallback: true,
|
||||
});
|
||||
await advancePastDiscoveryWindow();
|
||||
|
||||
await expect(selected).resolves.toEqual(withoutWs);
|
||||
expect(openTab).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("rejects a target-id-only local tab when the caller cannot use Playwright", async () => {
|
||||
vi.useFakeTimers();
|
||||
const { selection } = createSelectionHarness({
|
||||
snapshots: [[tab("NO_PLAYWRIGHT")]],
|
||||
});
|
||||
|
||||
const selected = expect(selection.ensureTabAvailable("NO_PLAYWRIGHT")).rejects.toThrow(
|
||||
/tab not found/i,
|
||||
);
|
||||
await advancePastDiscoveryWindow();
|
||||
|
||||
await selected;
|
||||
});
|
||||
});
|
||||
@@ -2,7 +2,6 @@
|
||||
* Browser tab selection operations for default tab choice, focus, and close.
|
||||
*/
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { formatErrorMessage } from "../infra/errors.js";
|
||||
import type { SsrFPolicy } from "../infra/net/ssrf.js";
|
||||
import { fetchOk, normalizeCdpHttpBaseForJsonEndpoints } from "./cdp.helpers.js";
|
||||
import { appendCdpPath } from "./cdp.js";
|
||||
@@ -12,15 +11,7 @@ import { BrowserTabNotFoundError, BrowserTargetAmbiguousError } from "./errors.j
|
||||
import { getBrowserProfileCapabilities } from "./profile-capabilities.js";
|
||||
import type { PwAiModule } from "./pw-ai-module.js";
|
||||
import { getPwAiModule } from "./pw-ai-module.js";
|
||||
import {
|
||||
OPEN_TAB_DISCOVERY_POLL_MS,
|
||||
OPEN_TAB_DISCOVERY_WINDOW_MS,
|
||||
} from "./server-context.constants.js";
|
||||
import type {
|
||||
BrowserTab,
|
||||
EnsureTabAvailableOptions,
|
||||
ProfileRuntimeState,
|
||||
} from "./server-context.types.js";
|
||||
import type { BrowserTab, ProfileRuntimeState } from "./server-context.types.js";
|
||||
import { resolveTargetIdFromTabs } from "./target-id.js";
|
||||
|
||||
type SelectionDeps = {
|
||||
@@ -33,40 +24,11 @@ type SelectionDeps = {
|
||||
};
|
||||
|
||||
type SelectionOps = {
|
||||
ensureTabAvailable: (
|
||||
targetId?: string,
|
||||
options?: EnsureTabAvailableOptions,
|
||||
) => Promise<BrowserTab>;
|
||||
ensureTabAvailable: (targetId?: string) => Promise<BrowserTab>;
|
||||
focusTab: (targetId: string) => Promise<void>;
|
||||
closeTab: (targetId: string) => Promise<void>;
|
||||
};
|
||||
|
||||
function mergeOpenedTabSnapshot(
|
||||
tabs: BrowserTab[],
|
||||
openedTab: BrowserTab | undefined,
|
||||
): BrowserTab[] {
|
||||
if (!openedTab) {
|
||||
return tabs;
|
||||
}
|
||||
const index = tabs.findIndex((tab) => tab.targetId === openedTab.targetId);
|
||||
if (index < 0) {
|
||||
return [...tabs, openedTab];
|
||||
}
|
||||
const listedTab = tabs[index];
|
||||
if (!listedTab || listedTab.wsUrl || !openedTab.wsUrl) {
|
||||
return tabs;
|
||||
}
|
||||
const merged = tabs.slice();
|
||||
merged[index] = { ...listedTab, wsUrl: openedTab.wsUrl };
|
||||
return merged;
|
||||
}
|
||||
|
||||
function waitForTabDiscoveryPoll(): Promise<void> {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve, OPEN_TAB_DISCOVERY_POLL_MS);
|
||||
});
|
||||
}
|
||||
|
||||
/** Builds tab selection/focus/close operations for one resolved browser profile. */
|
||||
export function createProfileSelectionOps({
|
||||
profile,
|
||||
@@ -79,99 +41,16 @@ export function createProfileSelectionOps({
|
||||
const cdpHttpBase = normalizeCdpHttpBaseForJsonEndpoints(profile.cdpUrl);
|
||||
const capabilities = getBrowserProfileCapabilities(profile);
|
||||
|
||||
const ensureTabAvailable = async (
|
||||
targetId?: string,
|
||||
options?: EnsureTabAvailableOptions,
|
||||
): Promise<BrowserTab> => {
|
||||
const ensureTabAvailable = async (targetId?: string): Promise<BrowserTab> => {
|
||||
await ensureBrowserAvailable();
|
||||
const profileState = getProfileState();
|
||||
let lastNonEmptyTabs: BrowserTab[] = [];
|
||||
let lastListError: unknown;
|
||||
let sawSuccessfulList = false;
|
||||
let openedTab: BrowserTab | undefined;
|
||||
|
||||
const readTabs = async (): Promise<BrowserTab[]> => {
|
||||
try {
|
||||
const tabs = await listTabs();
|
||||
sawSuccessfulList = true;
|
||||
if (tabs.length > 0) {
|
||||
lastNonEmptyTabs = tabs;
|
||||
}
|
||||
return tabs;
|
||||
} catch (err) {
|
||||
lastListError = err;
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
const openWhenConfirmedEmpty = async (tabs: BrowserTab[]): Promise<void> => {
|
||||
if (!openedTab && sawSuccessfulList && lastNonEmptyTabs.length === 0 && tabs.length === 0) {
|
||||
openedTab = await openTab("about:blank");
|
||||
}
|
||||
};
|
||||
|
||||
const candidateTabs = (tabs: BrowserTab[]) =>
|
||||
capabilities.supportsPerTabWs ? tabs.filter((tab) => Boolean(tab.wsUrl)) : tabs;
|
||||
const canResolveSelection = (tabs: BrowserTab[]) => {
|
||||
const desiredTargetId =
|
||||
targetId ??
|
||||
openedTab?.targetId ??
|
||||
normalizeOptionalString(profileState.lastTargetId) ??
|
||||
undefined;
|
||||
if (!desiredTargetId) {
|
||||
return tabs.length > 0;
|
||||
}
|
||||
const resolved = resolveTargetIdFromTabs(desiredTargetId, tabs);
|
||||
return resolved.ok || resolved.reason === "ambiguous";
|
||||
};
|
||||
|
||||
const tabs1 = await readTabs();
|
||||
await openWhenConfirmedEmpty(tabs1);
|
||||
|
||||
let listedTabs = await readTabs();
|
||||
await openWhenConfirmedEmpty(listedTabs);
|
||||
let unfilteredTabs = mergeOpenedTabSnapshot(listedTabs, openedTab);
|
||||
let candidates = candidateTabs(unfilteredTabs);
|
||||
const preservedCanResolveSelection = () =>
|
||||
canResolveSelection(mergeOpenedTabSnapshot(lastNonEmptyTabs, openedTab));
|
||||
|
||||
if (
|
||||
capabilities.supportsPerTabWs &&
|
||||
!canResolveSelection(candidates) &&
|
||||
(candidates.length === 0 ||
|
||||
canResolveSelection(unfilteredTabs) ||
|
||||
preservedCanResolveSelection())
|
||||
) {
|
||||
const deadline = Date.now() + OPEN_TAB_DISCOVERY_WINDOW_MS;
|
||||
while (Date.now() < deadline) {
|
||||
await waitForTabDiscoveryPoll();
|
||||
listedTabs = await readTabs();
|
||||
await openWhenConfirmedEmpty(listedTabs);
|
||||
unfilteredTabs = mergeOpenedTabSnapshot(listedTabs, openedTab);
|
||||
candidates = candidateTabs(unfilteredTabs);
|
||||
if (canResolveSelection(candidates)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
const tabs1 = await listTabs();
|
||||
if (tabs1.length === 0) {
|
||||
await openTab("about:blank");
|
||||
}
|
||||
|
||||
if (!canResolveSelection(candidates)) {
|
||||
// Keep the last useful discovery snapshot across empty or failed relists.
|
||||
// Target-id-only fallback is opt-in because only Playwright-backed callers can use it safely.
|
||||
const preservedTabs = mergeOpenedTabSnapshot(lastNonEmptyTabs, openedTab);
|
||||
const preservedCandidates = candidateTabs(preservedTabs);
|
||||
if (canResolveSelection(preservedCandidates)) {
|
||||
candidates = preservedCandidates;
|
||||
} else if (options?.allowPlaywrightFallback && canResolveSelection(preservedTabs)) {
|
||||
candidates = preservedTabs;
|
||||
}
|
||||
}
|
||||
|
||||
if (candidates.length === 0 && !sawSuccessfulList && lastListError) {
|
||||
throw lastListError instanceof Error
|
||||
? lastListError
|
||||
: new Error(formatErrorMessage(lastListError));
|
||||
}
|
||||
const tabs = await listTabs();
|
||||
const candidates = capabilities.supportsPerTabWs ? tabs.filter((t) => Boolean(t.wsUrl)) : tabs;
|
||||
|
||||
const resolveById = (raw: string) => {
|
||||
const resolved = resolveTargetIdFromTabs(raw, candidates);
|
||||
|
||||
@@ -265,8 +265,7 @@ export function createBrowserRouteContext(opts: ContextOptions): BrowserRouteCon
|
||||
listProfiles,
|
||||
// Legacy methods delegate to default profile
|
||||
ensureBrowserAvailable: () => getDefaultContext().ensureBrowserAvailable(),
|
||||
ensureTabAvailable: (targetId, options) =>
|
||||
getDefaultContext().ensureTabAvailable(targetId, options),
|
||||
ensureTabAvailable: (targetId) => getDefaultContext().ensureTabAvailable(targetId),
|
||||
isHttpReachable: (timeoutMs) => getDefaultContext().isHttpReachable(timeoutMs),
|
||||
isTransportAvailable: (timeoutMs) => getDefaultContext().isTransportAvailable(timeoutMs),
|
||||
isReachable: (timeoutMs, options) => getDefaultContext().isReachable(timeoutMs, options),
|
||||
|
||||
@@ -43,17 +43,9 @@ export type BrowserServerState = {
|
||||
stopUnhandledRejectionHandler?: () => void;
|
||||
};
|
||||
|
||||
export type EnsureTabAvailableOptions = {
|
||||
/** Allow a target-id-only tab when the caller can continue through Playwright. */
|
||||
allowPlaywrightFallback?: boolean;
|
||||
};
|
||||
|
||||
type BrowserProfileActions = {
|
||||
ensureBrowserAvailable: (opts?: { headless?: boolean }) => Promise<void>;
|
||||
ensureTabAvailable: (
|
||||
targetId?: string,
|
||||
options?: EnsureTabAvailableOptions,
|
||||
) => Promise<BrowserTab>;
|
||||
ensureTabAvailable: (targetId?: string) => Promise<BrowserTab>;
|
||||
isHttpReachable: (timeoutMs?: number) => Promise<boolean>;
|
||||
isTransportAvailable: (timeoutMs?: number) => Promise<boolean>;
|
||||
isReachable: (
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@openclaw/byteplus-provider",
|
||||
"version": "2026.6.8",
|
||||
"version": "2026.6.9",
|
||||
"private": true,
|
||||
"description": "OpenClaw BytePlus provider plugin",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@openclaw/canvas-plugin",
|
||||
"version": "2026.6.8",
|
||||
"version": "2026.6.9",
|
||||
"private": true,
|
||||
"description": "OpenClaw Canvas plugin",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@openclaw/cerebras-provider",
|
||||
"version": "2026.6.8",
|
||||
"version": "2026.6.9",
|
||||
"private": true,
|
||||
"description": "OpenClaw Cerebras provider plugin",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@openclaw/chutes-provider",
|
||||
"version": "2026.6.8",
|
||||
"version": "2026.6.9",
|
||||
"private": true,
|
||||
"description": "OpenClaw Chutes.ai provider plugin",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@openclaw/clickclack",
|
||||
"version": "2026.6.8",
|
||||
"version": "2026.6.9",
|
||||
"private": true,
|
||||
"description": "OpenClaw ClickClack channel plugin",
|
||||
"type": "module",
|
||||
@@ -18,7 +18,7 @@
|
||||
"openclaw": "2026.5.28"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"openclaw": ">=2026.6.8"
|
||||
"openclaw": ">=2026.6.9"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"openclaw": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@openclaw/cloudflare-ai-gateway-provider",
|
||||
"version": "2026.6.8",
|
||||
"version": "2026.6.9",
|
||||
"private": true,
|
||||
"description": "OpenClaw Cloudflare AI Gateway provider plugin",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@openclaw/codex-supervisor",
|
||||
"version": "2026.6.8",
|
||||
"version": "2026.6.9",
|
||||
"private": true,
|
||||
"description": "OpenClaw Codex app-server fleet supervision plugin.",
|
||||
"type": "module",
|
||||
|
||||
4
extensions/codex/npm-shrinkwrap.json
generated
4
extensions/codex/npm-shrinkwrap.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@openclaw/codex",
|
||||
"version": "2026.6.8",
|
||||
"version": "2026.6.9",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@openclaw/codex",
|
||||
"version": "2026.6.8",
|
||||
"version": "2026.6.9",
|
||||
"dependencies": {
|
||||
"@openai/codex": "0.139.0",
|
||||
"typebox": "1.1.39",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@openclaw/codex",
|
||||
"version": "2026.6.8",
|
||||
"version": "2026.6.9",
|
||||
"description": "OpenClaw Codex app-server harness and model provider plugin with a Codex-managed GPT catalog.",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -34,10 +34,10 @@
|
||||
]
|
||||
},
|
||||
"compat": {
|
||||
"pluginApi": ">=2026.6.8"
|
||||
"pluginApi": ">=2026.6.9"
|
||||
},
|
||||
"build": {
|
||||
"openclawVersion": "2026.6.8"
|
||||
"openclawVersion": "2026.6.9"
|
||||
},
|
||||
"release": {
|
||||
"publishToClawHub": true,
|
||||
|
||||
@@ -18,7 +18,6 @@ describe("Codex app-server attempt context", () => {
|
||||
it("returns a run context report without deferred Codex dynamic tool schemas", () => {
|
||||
const tools = [
|
||||
{
|
||||
type: "function",
|
||||
name: "message",
|
||||
description: "Send a message.",
|
||||
inputSchema: {
|
||||
@@ -29,23 +28,15 @@ describe("Codex app-server attempt context", () => {
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "namespace",
|
||||
name: "openclaw",
|
||||
description: "",
|
||||
tools: [
|
||||
{
|
||||
type: "function",
|
||||
name: "web_search",
|
||||
description: "Search the web.",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
query: { type: "string" },
|
||||
},
|
||||
},
|
||||
deferLoading: true,
|
||||
name: "web_search",
|
||||
description: "Search the web.",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
query: { type: "string" },
|
||||
},
|
||||
],
|
||||
},
|
||||
deferLoading: true,
|
||||
},
|
||||
] as CodexDynamicToolSpec[];
|
||||
|
||||
|
||||
@@ -17,8 +17,7 @@ import {
|
||||
import { resolveAgentWorkspaceDir } from "openclaw/plugin-sdk/agent-runtime";
|
||||
import { buildMemorySystemPromptAddition } from "openclaw/plugin-sdk/core";
|
||||
import { MESSAGE_TOOL_DELIVERY_HINTS } from "openclaw/plugin-sdk/message-tool-delivery-hints";
|
||||
import type { CodexDynamicToolFunctionSpec, CodexDynamicToolSpec, JsonValue } from "./protocol.js";
|
||||
import { flattenCodexDynamicToolFunctions } from "./protocol.js";
|
||||
import type { CodexDynamicToolSpec, JsonValue } from "./protocol.js";
|
||||
import { isJsonObject } from "./protocol.js";
|
||||
import type { CodexAppServerThreadBinding } from "./session-binding.js";
|
||||
import { readCodexMirroredSessionHistoryMessages } from "./session-history.js";
|
||||
@@ -281,7 +280,7 @@ export function buildCodexSystemPromptReport(params: {
|
||||
skillsPrompt: string;
|
||||
tools: CodexDynamicToolSpec[];
|
||||
}): CodexSystemPromptReport {
|
||||
const toolEntries = flattenCodexDynamicToolFunctions(params.tools).map(buildCodexToolReportEntry);
|
||||
const toolEntries = params.tools.map(buildCodexToolReportEntry);
|
||||
const schemaChars = toolEntries.reduce((sum, tool) => sum + tool.schemaChars, 0);
|
||||
const skillsPrompt = params.skillsPrompt.trim();
|
||||
const bootstrapMaxChars = readPositiveNumber(
|
||||
@@ -345,7 +344,7 @@ function buildCodexSkillReportEntries(
|
||||
.filter((entry) => entry.blockChars > 0);
|
||||
}
|
||||
|
||||
function buildCodexToolReportEntry(tool: CodexDynamicToolFunctionSpec): CodexToolReportEntry {
|
||||
function buildCodexToolReportEntry(tool: CodexDynamicToolSpec): CodexToolReportEntry {
|
||||
const summary = tool.description.trim();
|
||||
if (tool.deferLoading === true) {
|
||||
return {
|
||||
@@ -855,15 +854,13 @@ function renderCodexMemoryToolSearchBridge(toolNames: readonly string[]): string
|
||||
}
|
||||
|
||||
/** Returns whether the current dynamic tool list can serve workspace memory. */
|
||||
export function hasCodexWorkspaceMemoryTools(tools: readonly CodexDynamicToolSpec[]): boolean {
|
||||
export function hasCodexWorkspaceMemoryTools(tools: readonly { name: string }[]): boolean {
|
||||
return getCodexWorkspaceMemoryToolNames(tools).length > 0;
|
||||
}
|
||||
|
||||
/** Lists available memory tool names understood by Codex workspace memory routing. */
|
||||
export function getCodexWorkspaceMemoryToolNames(tools: readonly CodexDynamicToolSpec[]): string[] {
|
||||
const availableToolNames = new Set(
|
||||
flattenCodexDynamicToolFunctions(tools).map((tool) => normalizeCodexDynamicToolName(tool.name)),
|
||||
);
|
||||
export function getCodexWorkspaceMemoryToolNames(tools: readonly { name: string }[]): string[] {
|
||||
const availableToolNames = new Set(tools.map((tool) => normalizeCodexDynamicToolName(tool.name)));
|
||||
return Array.from(CODEX_MEMORY_TOOL_NAMES).filter((name) => availableToolNames.has(name));
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,6 @@ import {
|
||||
shouldUseDirectCodexDynamicToolsForModel,
|
||||
} from "./dynamic-tool-profile.js";
|
||||
import { createCodexDynamicToolBridge } from "./dynamic-tools.js";
|
||||
import { flattenCodexDynamicToolFunctions } from "./protocol.js";
|
||||
import { createCodexTestModel } from "./test-support.js";
|
||||
|
||||
let tempDir: string;
|
||||
@@ -402,9 +401,7 @@ describe("Codex app-server dynamic tool build", () => {
|
||||
expect(shouldUseDirectCodexDynamicToolsForModel("gpt-5.4-nano")).toBe(true);
|
||||
expect(resolveCodexDynamicToolsLoadingForModel({}, "gpt-5.4-nano")).toBe("direct");
|
||||
expect(resolveCodexDynamicToolsLoadingForModel({}, "gpt-5.5")).toBe("searchable");
|
||||
const webSearch = flattenCodexDynamicToolFunctions(toolBridge.specs).find(
|
||||
(tool) => tool.name === "web_search",
|
||||
);
|
||||
const webSearch = toolBridge.specs.find((tool) => tool.name === "web_search");
|
||||
expect(webSearch).not.toHaveProperty("deferLoading");
|
||||
expect(webSearch).not.toHaveProperty("namespace");
|
||||
});
|
||||
|
||||
@@ -27,7 +27,7 @@ import {
|
||||
CODEX_OPENCLAW_DYNAMIC_TOOL_NAMESPACE,
|
||||
createCodexDynamicToolBridge,
|
||||
} from "./dynamic-tools.js";
|
||||
import type { CodexDynamicToolFunctionSpec, CodexDynamicToolSpec, JsonValue } from "./protocol.js";
|
||||
import type { JsonValue } from "./protocol.js";
|
||||
|
||||
function createTool(overrides: Partial<AnyAgentTool>): AnyAgentTool {
|
||||
return {
|
||||
@@ -115,20 +115,6 @@ function expectDynamicSpec(
|
||||
}
|
||||
}
|
||||
|
||||
function flattenSpecsWithNamespace(
|
||||
specs: readonly CodexDynamicToolSpec[],
|
||||
): Array<CodexDynamicToolFunctionSpec & { namespace?: string }> {
|
||||
return specs.flatMap((spec) =>
|
||||
spec.type === "namespace"
|
||||
? spec.tools.map((tool) => ({ ...tool, namespace: spec.name }))
|
||||
: [spec],
|
||||
);
|
||||
}
|
||||
|
||||
function specNames(specs: readonly CodexDynamicToolSpec[]): string[] {
|
||||
return flattenSpecsWithNamespace(specs).map((tool) => tool.name);
|
||||
}
|
||||
|
||||
function expectNoNamespace(spec: unknown) {
|
||||
const record = requireRecord(spec, "tool spec");
|
||||
expect(record).not.toHaveProperty("namespace");
|
||||
@@ -190,12 +176,11 @@ describe("createCodexDynamicToolBridge", () => {
|
||||
signal: new AbortController().signal,
|
||||
});
|
||||
|
||||
const specs = flattenSpecsWithNamespace(bridge.specs);
|
||||
const webSearch = specs.find((tool) => tool.name === "web_search");
|
||||
const message = specs.find((tool) => tool.name === "message");
|
||||
const heartbeat = specs.find((tool) => tool.name === HEARTBEAT_RESPONSE_TOOL_NAME);
|
||||
const sessionsSpawn = specs.find((tool) => tool.name === "sessions_spawn");
|
||||
const sessionsYield = specs.find((tool) => tool.name === "sessions_yield");
|
||||
const webSearch = bridge.specs.find((tool) => tool.name === "web_search");
|
||||
const message = bridge.specs.find((tool) => tool.name === "message");
|
||||
const heartbeat = bridge.specs.find((tool) => tool.name === HEARTBEAT_RESPONSE_TOOL_NAME);
|
||||
const sessionsSpawn = bridge.specs.find((tool) => tool.name === "sessions_spawn");
|
||||
const sessionsYield = bridge.specs.find((tool) => tool.name === "sessions_yield");
|
||||
|
||||
expectDynamicSpec(webSearch, {
|
||||
name: "web_search",
|
||||
@@ -227,21 +212,14 @@ describe("createCodexDynamicToolBridge", () => {
|
||||
directToolNames: ["message"],
|
||||
});
|
||||
|
||||
const specs = flattenSpecsWithNamespace(bridge.specs);
|
||||
expect(bridge.specs).toHaveLength(2);
|
||||
expectDynamicSpec(
|
||||
specs.find((tool) => tool.name === "message"),
|
||||
{ name: "message" },
|
||||
);
|
||||
expectDynamicSpec(
|
||||
specs.find((tool) => tool.name === "web_search"),
|
||||
{
|
||||
name: "web_search",
|
||||
namespace: CODEX_OPENCLAW_DYNAMIC_TOOL_NAMESPACE,
|
||||
deferLoading: true,
|
||||
},
|
||||
);
|
||||
expectNoNamespace(specs.find((tool) => tool.name === "message"));
|
||||
expectDynamicSpec(bridge.specs[0], { name: "message" });
|
||||
expectDynamicSpec(bridge.specs[1], {
|
||||
name: "web_search",
|
||||
namespace: CODEX_OPENCLAW_DYNAMIC_TOOL_NAMESPACE,
|
||||
deferLoading: true,
|
||||
});
|
||||
expectNoNamespace(bridge.specs[0]);
|
||||
});
|
||||
|
||||
it("can register a durable tool schema while denying execution for the current turn", async () => {
|
||||
@@ -258,8 +236,11 @@ describe("createCodexDynamicToolBridge", () => {
|
||||
hookContext: { runId: "run-unavailable", onToolOutcome },
|
||||
});
|
||||
|
||||
expect(specNames(bridge.availableSpecs)).toEqual(["message"]);
|
||||
expect(specNames(bridge.specs)).toEqual(["message", HEARTBEAT_RESPONSE_TOOL_NAME]);
|
||||
expect(bridge.availableSpecs.map((tool) => tool.name)).toEqual(["message"]);
|
||||
expect(bridge.specs.map((tool) => tool.name)).toEqual([
|
||||
"message",
|
||||
HEARTBEAT_RESPONSE_TOOL_NAME,
|
||||
]);
|
||||
|
||||
const result = await bridge.handleToolCall(
|
||||
{
|
||||
@@ -331,11 +312,11 @@ describe("createCodexDynamicToolBridge", () => {
|
||||
signal: new AbortController().signal,
|
||||
});
|
||||
|
||||
expect(flattenSpecsWithNamespace(bridge.availableSpecs)[0]?.inputSchema).toEqual({
|
||||
expect(bridge.availableSpecs[0]?.inputSchema).toEqual({
|
||||
type: "object",
|
||||
properties: { current: { type: "string" } },
|
||||
});
|
||||
expect(flattenSpecsWithNamespace(bridge.specs)[0]?.inputSchema).toEqual({
|
||||
expect(bridge.specs[0]?.inputSchema).toEqual({
|
||||
type: "object",
|
||||
properties: { durable: { type: "string" } },
|
||||
});
|
||||
@@ -371,8 +352,8 @@ describe("createCodexDynamicToolBridge", () => {
|
||||
unsubscribeDiagnostics();
|
||||
}
|
||||
|
||||
expect(specNames(bridge.availableSpecs)).toEqual(["message"]);
|
||||
expect(specNames(bridge.specs)).toEqual(["message"]);
|
||||
expect(bridge.availableSpecs.map((tool) => tool.name)).toEqual(["message"]);
|
||||
expect(bridge.specs.map((tool) => tool.name)).toEqual(["message"]);
|
||||
expect(bridge.telemetry.quarantinedTools).toEqual([
|
||||
{
|
||||
tool: "fuzzplugin_move_angles",
|
||||
@@ -469,8 +450,8 @@ describe("createCodexDynamicToolBridge", () => {
|
||||
signal: new AbortController().signal,
|
||||
});
|
||||
|
||||
expect(specNames(bridge.availableSpecs)).toEqual(["message"]);
|
||||
expect(specNames(bridge.specs)).toEqual(["message"]);
|
||||
expect(bridge.availableSpecs.map((tool) => tool.name)).toEqual(["message"]);
|
||||
expect(bridge.specs.map((tool) => tool.name)).toEqual(["message"]);
|
||||
expect(bridge.telemetry.quarantinedTools).toEqual([
|
||||
{
|
||||
tool: "tool[0]",
|
||||
@@ -528,8 +509,8 @@ describe("createCodexDynamicToolBridge", () => {
|
||||
signal: new AbortController().signal,
|
||||
});
|
||||
|
||||
expect(specNames(registeredBridge.availableSpecs)).toEqual(["message"]);
|
||||
expect(specNames(registeredBridge.specs)).toEqual(["message"]);
|
||||
expect(registeredBridge.availableSpecs.map((tool) => tool.name)).toEqual(["message"]);
|
||||
expect(registeredBridge.specs.map((tool) => tool.name)).toEqual(["message"]);
|
||||
});
|
||||
|
||||
it("can expose all dynamic tools directly for compatibility", () => {
|
||||
|
||||
@@ -48,7 +48,6 @@ import type {
|
||||
CodexDynamicToolCallParams,
|
||||
CodexDynamicToolCallResponse,
|
||||
CodexDynamicToolDiagnosticTerminalType,
|
||||
CodexDynamicToolFunctionSpec,
|
||||
CodexDynamicToolSpec,
|
||||
JsonValue,
|
||||
} from "./protocol.js";
|
||||
@@ -202,16 +201,20 @@ export function createCodexDynamicToolBridge(params: {
|
||||
...(params.directToolNames ?? []),
|
||||
]);
|
||||
return {
|
||||
availableSpecs: createCodexDynamicToolSpecs({
|
||||
entries: availableTools,
|
||||
loading: params.loading ?? "searchable",
|
||||
directToolNames,
|
||||
}),
|
||||
specs: createCodexDynamicToolSpecs({
|
||||
entries: registeredSpecTools,
|
||||
loading: params.loading ?? "searchable",
|
||||
directToolNames,
|
||||
}),
|
||||
availableSpecs: availableTools.map((entry) =>
|
||||
createCodexDynamicToolSpec({
|
||||
entry,
|
||||
loading: params.loading ?? "searchable",
|
||||
directToolNames,
|
||||
}),
|
||||
),
|
||||
specs: registeredSpecTools.map((entry) =>
|
||||
createCodexDynamicToolSpec({
|
||||
entry,
|
||||
loading: params.loading ?? "searchable",
|
||||
directToolNames,
|
||||
}),
|
||||
),
|
||||
telemetry,
|
||||
handleToolCall: async (call, options) => {
|
||||
const toolEntry = toolMap.get(call.tool);
|
||||
@@ -499,41 +502,24 @@ function wrapProjectedCodexDynamicTools(
|
||||
return { tools: wrappedTools, quarantinedTools };
|
||||
}
|
||||
|
||||
function createCodexDynamicToolSpecs(params: {
|
||||
entries: readonly ProjectedCodexDynamicTool[];
|
||||
function createCodexDynamicToolSpec(params: {
|
||||
entry: ProjectedCodexDynamicTool;
|
||||
loading: CodexDynamicToolsLoading;
|
||||
directToolNames: ReadonlySet<string>;
|
||||
}): CodexDynamicToolSpec[] {
|
||||
const specs: CodexDynamicToolSpec[] = [];
|
||||
const namespaceTools: CodexDynamicToolFunctionSpec[] = [];
|
||||
for (const entry of params.entries) {
|
||||
const functionSpec = createCodexDynamicToolFunctionSpec({ entry });
|
||||
if (params.loading === "direct" || params.directToolNames.has(entry.name)) {
|
||||
specs.push(functionSpec);
|
||||
continue;
|
||||
}
|
||||
namespaceTools.push({ ...functionSpec, deferLoading: true });
|
||||
}
|
||||
if (namespaceTools.length > 0) {
|
||||
specs.push({
|
||||
type: "namespace",
|
||||
name: CODEX_OPENCLAW_DYNAMIC_TOOL_NAMESPACE,
|
||||
description: "",
|
||||
tools: namespaceTools,
|
||||
});
|
||||
}
|
||||
return specs;
|
||||
}
|
||||
|
||||
function createCodexDynamicToolFunctionSpec(params: {
|
||||
entry: ProjectedCodexDynamicTool;
|
||||
}): CodexDynamicToolFunctionSpec {
|
||||
return {
|
||||
type: "function",
|
||||
}): CodexDynamicToolSpec {
|
||||
const base = {
|
||||
name: params.entry.name,
|
||||
description: params.entry.description,
|
||||
inputSchema: params.entry.inputSchema,
|
||||
};
|
||||
if (params.loading === "direct" || params.directToolNames.has(params.entry.name)) {
|
||||
return base;
|
||||
}
|
||||
return {
|
||||
...base,
|
||||
namespace: CODEX_OPENCLAW_DYNAMIC_TOOL_NAMESPACE,
|
||||
deferLoading: true,
|
||||
};
|
||||
}
|
||||
|
||||
function projectCodexDynamicTools(tools: readonly AnyAgentTool[]): {
|
||||
|
||||
@@ -45,14 +45,6 @@
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"credentialSource": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/AmazonBedrockCredentialSource"
|
||||
}
|
||||
],
|
||||
"default": "awsManaged"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"amazonBedrock"
|
||||
@@ -69,13 +61,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"AmazonBedrockCredentialSource": {
|
||||
"enum": [
|
||||
"codexManaged",
|
||||
"awsManaged"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"PlanType": {
|
||||
"enum": [
|
||||
"free",
|
||||
|
||||
@@ -861,14 +861,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"SubAgentActivityKind": {
|
||||
"enum": [
|
||||
"started",
|
||||
"interacted",
|
||||
"interrupted"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"SubAgentSource": {
|
||||
"oneOf": [
|
||||
{
|
||||
@@ -1055,14 +1047,6 @@
|
||||
"description": "Usually the first user message in the thread, if available.",
|
||||
"type": "string"
|
||||
},
|
||||
"recencyAt": {
|
||||
"description": "Unix timestamp (in seconds) used for thread recency ordering.",
|
||||
"format": "int64",
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"sessionId": {
|
||||
"description": "Session id shared by threads that belong to the same session tree.",
|
||||
"type": "string"
|
||||
@@ -1633,38 +1617,6 @@
|
||||
"title": "CollabAgentToolCallThreadItem",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"agentPath": {
|
||||
"type": "string"
|
||||
},
|
||||
"agentThreadId": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"kind": {
|
||||
"$ref": "#/definitions/SubAgentActivityKind"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"subAgentActivity"
|
||||
],
|
||||
"title": "SubAgentActivityThreadItemType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"agentPath",
|
||||
"agentThreadId",
|
||||
"id",
|
||||
"kind",
|
||||
"type"
|
||||
],
|
||||
"title": "SubAgentActivityThreadItem",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"action": {
|
||||
@@ -1723,32 +1675,6 @@
|
||||
"title": "ImageViewThreadItem",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"durationMs": {
|
||||
"format": "uint64",
|
||||
"minimum": 0,
|
||||
"type": "integer"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"sleep"
|
||||
],
|
||||
"title": "SleepThreadItemType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"durationMs",
|
||||
"id",
|
||||
"type"
|
||||
],
|
||||
"title": "SleepThreadItem",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
@@ -1864,6 +1790,11 @@
|
||||
]
|
||||
},
|
||||
"ThreadSource": {
|
||||
"enum": [
|
||||
"user",
|
||||
"subagent",
|
||||
"memory_consolidation"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"ThreadStatus": {
|
||||
|
||||
@@ -861,14 +861,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"SubAgentActivityKind": {
|
||||
"enum": [
|
||||
"started",
|
||||
"interacted",
|
||||
"interrupted"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"SubAgentSource": {
|
||||
"oneOf": [
|
||||
{
|
||||
@@ -1055,14 +1047,6 @@
|
||||
"description": "Usually the first user message in the thread, if available.",
|
||||
"type": "string"
|
||||
},
|
||||
"recencyAt": {
|
||||
"description": "Unix timestamp (in seconds) used for thread recency ordering.",
|
||||
"format": "int64",
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"sessionId": {
|
||||
"description": "Session id shared by threads that belong to the same session tree.",
|
||||
"type": "string"
|
||||
@@ -1633,38 +1617,6 @@
|
||||
"title": "CollabAgentToolCallThreadItem",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"agentPath": {
|
||||
"type": "string"
|
||||
},
|
||||
"agentThreadId": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"kind": {
|
||||
"$ref": "#/definitions/SubAgentActivityKind"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"subAgentActivity"
|
||||
],
|
||||
"title": "SubAgentActivityThreadItemType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"agentPath",
|
||||
"agentThreadId",
|
||||
"id",
|
||||
"kind",
|
||||
"type"
|
||||
],
|
||||
"title": "SubAgentActivityThreadItem",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"action": {
|
||||
@@ -1723,32 +1675,6 @@
|
||||
"title": "ImageViewThreadItem",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"durationMs": {
|
||||
"format": "uint64",
|
||||
"minimum": 0,
|
||||
"type": "integer"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"sleep"
|
||||
],
|
||||
"title": "SleepThreadItemType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"durationMs",
|
||||
"id",
|
||||
"type"
|
||||
],
|
||||
"title": "SleepThreadItem",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
@@ -1864,6 +1790,11 @@
|
||||
]
|
||||
},
|
||||
"ThreadSource": {
|
||||
"enum": [
|
||||
"user",
|
||||
"subagent",
|
||||
"memory_consolidation"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"ThreadStatus": {
|
||||
|
||||
@@ -610,14 +610,6 @@
|
||||
"minLength": 1,
|
||||
"type": "string"
|
||||
},
|
||||
"SubAgentActivityKind": {
|
||||
"enum": [
|
||||
"started",
|
||||
"interacted",
|
||||
"interrupted"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"TextElement": {
|
||||
"properties": {
|
||||
"byteRange": {
|
||||
@@ -1141,38 +1133,6 @@
|
||||
"title": "CollabAgentToolCallThreadItem",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"agentPath": {
|
||||
"type": "string"
|
||||
},
|
||||
"agentThreadId": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"kind": {
|
||||
"$ref": "#/definitions/SubAgentActivityKind"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"subAgentActivity"
|
||||
],
|
||||
"title": "SubAgentActivityThreadItemType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"agentPath",
|
||||
"agentThreadId",
|
||||
"id",
|
||||
"kind",
|
||||
"type"
|
||||
],
|
||||
"title": "SubAgentActivityThreadItem",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"action": {
|
||||
@@ -1231,32 +1191,6 @@
|
||||
"title": "ImageViewThreadItem",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"durationMs": {
|
||||
"format": "uint64",
|
||||
"minimum": 0,
|
||||
"type": "integer"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"sleep"
|
||||
],
|
||||
"title": "SleepThreadItemType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"durationMs",
|
||||
"id",
|
||||
"type"
|
||||
],
|
||||
"title": "SleepThreadItem",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
|
||||
@@ -610,14 +610,6 @@
|
||||
"minLength": 1,
|
||||
"type": "string"
|
||||
},
|
||||
"SubAgentActivityKind": {
|
||||
"enum": [
|
||||
"started",
|
||||
"interacted",
|
||||
"interrupted"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"TextElement": {
|
||||
"properties": {
|
||||
"byteRange": {
|
||||
@@ -1141,38 +1133,6 @@
|
||||
"title": "CollabAgentToolCallThreadItem",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"agentPath": {
|
||||
"type": "string"
|
||||
},
|
||||
"agentThreadId": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"kind": {
|
||||
"$ref": "#/definitions/SubAgentActivityKind"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"subAgentActivity"
|
||||
],
|
||||
"title": "SubAgentActivityThreadItemType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"agentPath",
|
||||
"agentThreadId",
|
||||
"id",
|
||||
"kind",
|
||||
"type"
|
||||
],
|
||||
"title": "SubAgentActivityThreadItem",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"action": {
|
||||
@@ -1231,32 +1191,6 @@
|
||||
"title": "ImageViewThreadItem",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"durationMs": {
|
||||
"format": "uint64",
|
||||
"minimum": 0,
|
||||
"type": "integer"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"sleep"
|
||||
],
|
||||
"title": "SleepThreadItemType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"durationMs",
|
||||
"id",
|
||||
"type"
|
||||
],
|
||||
"title": "SleepThreadItem",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
|
||||
@@ -65,43 +65,12 @@ export type CodexUserInput =
|
||||
path: string;
|
||||
};
|
||||
|
||||
export type CodexDynamicToolFunctionSpec = JsonObject & {
|
||||
type: "function";
|
||||
export type CodexDynamicToolSpec = JsonObject & {
|
||||
name: string;
|
||||
description: string;
|
||||
inputSchema: JsonValue;
|
||||
deferLoading?: boolean;
|
||||
};
|
||||
|
||||
export type CodexDynamicToolNamespaceTool = CodexDynamicToolFunctionSpec;
|
||||
|
||||
export type CodexDynamicToolNamespaceSpec = JsonObject & {
|
||||
type: "namespace";
|
||||
name: string;
|
||||
description: string;
|
||||
tools: CodexDynamicToolNamespaceTool[];
|
||||
};
|
||||
|
||||
export type CodexDynamicToolSpec = CodexDynamicToolFunctionSpec | CodexDynamicToolNamespaceSpec;
|
||||
|
||||
export type CodexLegacyDynamicToolFunctionSpec = JsonObject & {
|
||||
name: string;
|
||||
description: string;
|
||||
inputSchema: JsonValue;
|
||||
deferLoading?: boolean;
|
||||
namespace?: string;
|
||||
};
|
||||
|
||||
export type CodexThreadStartDynamicToolSpec =
|
||||
| CodexDynamicToolSpec
|
||||
| CodexLegacyDynamicToolFunctionSpec;
|
||||
|
||||
export function flattenCodexDynamicToolFunctions(
|
||||
tools: readonly CodexDynamicToolSpec[] | undefined,
|
||||
): CodexDynamicToolFunctionSpec[] {
|
||||
return (tools ?? []).flatMap((tool) => (tool.type === "namespace" ? tool.tools : [tool]));
|
||||
}
|
||||
|
||||
export type CodexTurnEnvironmentParams = JsonObject & {
|
||||
environmentId: string;
|
||||
cwd: string;
|
||||
@@ -117,7 +86,7 @@ export type CodexThreadStartParams = JsonObject & {
|
||||
approvalsReviewer?: string | null;
|
||||
sandbox?: string;
|
||||
serviceTier?: CodexServiceTier | null;
|
||||
dynamicTools?: CodexThreadStartDynamicToolSpec[] | null;
|
||||
dynamicTools?: CodexDynamicToolSpec[] | null;
|
||||
developerInstructions?: string;
|
||||
experimentalRawEvents?: boolean;
|
||||
environments?: CodexTurnEnvironmentParams[] | null;
|
||||
|
||||
@@ -14,10 +14,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { CodexAppServerClientFactory } from "./client-factory.js";
|
||||
import type { CodexServerNotification } from "./protocol.js";
|
||||
import { runCodexAppServerAttempt as runCodexAppServerAttemptImpl } from "./run-attempt.js";
|
||||
import {
|
||||
readCodexAppServerBinding,
|
||||
writeCodexAppServerBinding as writeRawCodexAppServerBinding,
|
||||
} from "./session-binding.js";
|
||||
import { readCodexAppServerBinding, writeCodexAppServerBinding } from "./session-binding.js";
|
||||
import { createCodexTestModel } from "./test-support.js";
|
||||
|
||||
let tempDir: string;
|
||||
@@ -245,23 +242,6 @@ function createContextEngine(overrides: Partial<ContextEngine> = {}): ContextEng
|
||||
return engine;
|
||||
}
|
||||
|
||||
const DISABLED_CODEX_WEB_SEARCH_THREAD_CONFIG_FINGERPRINT = JSON.stringify({
|
||||
"features.standalone_web_search": false,
|
||||
web_search: "disabled",
|
||||
});
|
||||
|
||||
function writeCodexAppServerBinding(...args: Parameters<typeof writeRawCodexAppServerBinding>) {
|
||||
const [sessionFile, binding, lookup] = args;
|
||||
return writeRawCodexAppServerBinding(
|
||||
sessionFile,
|
||||
{
|
||||
webSearchThreadConfigFingerprint: DISABLED_CODEX_WEB_SEARCH_THREAD_CONFIG_FINGERPRINT,
|
||||
...binding,
|
||||
},
|
||||
lookup,
|
||||
);
|
||||
}
|
||||
|
||||
type MockCallReader = { mock: { calls: unknown[][] } };
|
||||
|
||||
function requireRecord(value: unknown, label: string): Record<string, unknown> {
|
||||
@@ -347,9 +327,6 @@ describe("runCodexAppServerAttempt context-engine lifecycle", () => {
|
||||
const params = createParams(sessionFile, workspaceDir);
|
||||
params.contextEngine = contextEngine;
|
||||
params.contextTokenBudget = 321;
|
||||
params.requestedModelId = "gpt-5.4-codex-primary";
|
||||
params.fallbackReason = "provider_unavailable";
|
||||
params.degradedReason = "context_overflow";
|
||||
params.config = { memory: { citations: "on" } } as EmbeddedRunAttemptParams["config"];
|
||||
|
||||
const run = runCodexAppServerAttempt(params);
|
||||
@@ -366,17 +343,6 @@ describe("runCodexAppServerAttempt context-engine lifecycle", () => {
|
||||
expect(bootstrapParams.sessionId).toBe("session-1");
|
||||
expect(bootstrapParams.sessionKey).toBe("agent:main:session-1");
|
||||
expect(bootstrapParams.sessionFile).toBe(sessionFile);
|
||||
expect(bootstrapParams.runtimeSettings).toMatchObject({
|
||||
runtime: { mode: "degraded" },
|
||||
model: {
|
||||
requested: "gpt-5.4-codex-primary",
|
||||
resolved: "gpt-5.4-codex",
|
||||
},
|
||||
diagnostics: {
|
||||
fallbackReason: "provider_unavailable",
|
||||
degradedReason: "context_overflow",
|
||||
},
|
||||
});
|
||||
|
||||
expect(contextEngine["assemble"]).toHaveBeenCalledTimes(1);
|
||||
const assembleParams = requireFirstCallArg(contextEngine["assemble"], "assemble") as Parameters<
|
||||
@@ -387,17 +353,6 @@ describe("runCodexAppServerAttempt context-engine lifecycle", () => {
|
||||
expect(assembleParams.tokenBudget).toBe(321);
|
||||
expect(assembleParams.citationsMode).toBe("on");
|
||||
expect(assembleParams.model).toBe("gpt-5.4-codex");
|
||||
expect(assembleParams.runtimeSettings).toMatchObject({
|
||||
runtime: { mode: "degraded" },
|
||||
model: {
|
||||
requested: "gpt-5.4-codex-primary",
|
||||
resolved: "gpt-5.4-codex",
|
||||
},
|
||||
diagnostics: {
|
||||
fallbackReason: "provider_unavailable",
|
||||
degradedReason: "context_overflow",
|
||||
},
|
||||
});
|
||||
expect(assembleParams.prompt).toBe("hello");
|
||||
expect(assembleParams.messages.map((message) => message.role)).toEqual(["assistant"]);
|
||||
expect(assembleParams.availableTools).toEqual(new Set());
|
||||
@@ -1684,9 +1639,6 @@ describe("runCodexAppServerAttempt context-engine lifecycle", () => {
|
||||
const params = createParams(sessionFile, workspaceDir);
|
||||
params.contextEngine = contextEngine;
|
||||
params.contextTokenBudget = 111;
|
||||
params.requestedModelId = "gpt-5.4-codex-primary";
|
||||
params.fallbackReason = "provider_unavailable";
|
||||
params.degradedReason = "context_overflow";
|
||||
|
||||
const run = runCodexAppServerAttempt(params);
|
||||
await harness.waitForMethod("turn/start");
|
||||
@@ -1701,24 +1653,9 @@ describe("runCodexAppServerAttempt context-engine lifecycle", () => {
|
||||
expect(afterTurnCall.sessionKey).toBe("agent:main:session-1");
|
||||
expect(afterTurnCall.prePromptMessageCount).toBe(0);
|
||||
expect(afterTurnCall.tokenBudget).toBe(111);
|
||||
expect(afterTurnCall.runtimeSettings).toMatchObject({
|
||||
runtime: { mode: "degraded" },
|
||||
model: {
|
||||
requested: "gpt-5.4-codex-primary",
|
||||
resolved: "gpt-5.4-codex",
|
||||
},
|
||||
diagnostics: {
|
||||
fallbackReason: "provider_unavailable",
|
||||
degradedReason: "context_overflow",
|
||||
},
|
||||
});
|
||||
expect(afterTurnCall.messages.some((message) => message.role === "user")).toBe(true);
|
||||
expect(afterTurnCall.messages.some((message) => message.role === "assistant")).toBe(true);
|
||||
expect(maintain).toHaveBeenCalledTimes(1);
|
||||
const maintainCall = requireFirstCallArg(maintain, "maintain") as Parameters<
|
||||
NonNullable<ContextEngine["maintain"]>
|
||||
>[0];
|
||||
expect(maintainCall.runtimeSettings).toBe(afterTurnCall.runtimeSettings);
|
||||
});
|
||||
|
||||
it("reloads mirrored history after bootstrap mutates the session transcript", async () => {
|
||||
|
||||
@@ -23,11 +23,7 @@ import {
|
||||
emitDynamicToolTerminalDiagnostic,
|
||||
} from "./dynamic-tool-diagnostics.js";
|
||||
import { createCodexDynamicToolBridge } from "./dynamic-tools.js";
|
||||
import {
|
||||
flattenCodexDynamicToolFunctions,
|
||||
type CodexDynamicToolCallParams,
|
||||
type CodexDynamicToolSpec,
|
||||
} from "./protocol.js";
|
||||
import type { CodexDynamicToolCallParams } from "./protocol.js";
|
||||
import {
|
||||
createParams,
|
||||
createCodexRuntimePlanFixture,
|
||||
@@ -43,10 +39,6 @@ function flushDiagnosticEvents() {
|
||||
return waitForDiagnosticEventsDrained();
|
||||
}
|
||||
|
||||
function specNames(specs: readonly CodexDynamicToolSpec[]): string[] {
|
||||
return flattenCodexDynamicToolFunctions(specs).map((tool) => tool.name);
|
||||
}
|
||||
|
||||
function activeDiagnosticToolKeys(events: DiagnosticEventPayload[]): Set<string> {
|
||||
const active = new Set<string>();
|
||||
for (const event of events) {
|
||||
@@ -374,7 +366,7 @@ describe("runCodexAppServerAttempt dynamic tools", () => {
|
||||
"features.code_mode_only"?: boolean;
|
||||
mcp_servers?: Record<string, unknown>;
|
||||
};
|
||||
dynamicTools?: CodexDynamicToolSpec[];
|
||||
dynamicTools?: Array<{ name: string }>;
|
||||
environments?: unknown[];
|
||||
}
|
||||
| undefined;
|
||||
@@ -390,7 +382,7 @@ describe("runCodexAppServerAttempt dynamic tools", () => {
|
||||
},
|
||||
});
|
||||
expect(startParams?.environments).toBeUndefined();
|
||||
expect(specNames(startParams?.dynamicTools ?? [])).toEqual([
|
||||
expect(startParams?.dynamicTools?.map((tool) => tool.name)).toEqual([
|
||||
"message",
|
||||
"node_exec",
|
||||
"node_process",
|
||||
|
||||
@@ -41,12 +41,7 @@ import {
|
||||
} from "./event-projector.js";
|
||||
import { buildCodexPluginAppCacheKey } from "./plugin-app-cache-key.js";
|
||||
import { buildCodexPluginThreadConfig } from "./plugin-thread-config.js";
|
||||
import {
|
||||
flattenCodexDynamicToolFunctions,
|
||||
type CodexDynamicToolFunctionSpec,
|
||||
type CodexDynamicToolSpec,
|
||||
type CodexServerNotification,
|
||||
} from "./protocol.js";
|
||||
import type { CodexServerNotification } from "./protocol.js";
|
||||
import {
|
||||
assistantMessage,
|
||||
createAppServerHarness,
|
||||
@@ -154,7 +149,6 @@ function createMessageDynamicTool(
|
||||
actions: string[] = ["send"],
|
||||
): Parameters<typeof startOrResumeThread>[0]["dynamicTools"][number] {
|
||||
return {
|
||||
type: "function",
|
||||
name: "message",
|
||||
description,
|
||||
inputSchema: {
|
||||
@@ -175,7 +169,6 @@ function createNamedDynamicTool(
|
||||
name: string,
|
||||
): Parameters<typeof startOrResumeThread>[0]["dynamicTools"][number] {
|
||||
return {
|
||||
type: "function",
|
||||
name,
|
||||
description: `${name} test tool`,
|
||||
inputSchema: {
|
||||
@@ -389,20 +382,6 @@ type RuntimeDynamicToolForTest = Parameters<
|
||||
typeof createCodexDynamicToolBridge
|
||||
>[0]["tools"][number];
|
||||
|
||||
function flattenSpecsWithNamespace(
|
||||
specs: readonly CodexDynamicToolSpec[],
|
||||
): Array<CodexDynamicToolFunctionSpec & { namespace?: string }> {
|
||||
return specs.flatMap((spec) =>
|
||||
spec.type === "namespace"
|
||||
? spec.tools.map((tool) => ({ ...tool, namespace: spec.name }))
|
||||
: [spec],
|
||||
);
|
||||
}
|
||||
|
||||
function specNames(specs: readonly CodexDynamicToolSpec[]): string[] {
|
||||
return flattenCodexDynamicToolFunctions(specs).map((tool) => tool.name);
|
||||
}
|
||||
|
||||
function createRuntimeDynamicTool(name: string): RuntimeDynamicToolForTest {
|
||||
return {
|
||||
name,
|
||||
@@ -527,11 +506,11 @@ describe("runCodexAppServerAttempt", () => {
|
||||
const startRequest = request.mock.calls.find(([method]) => method === "thread/start");
|
||||
const startParams = startRequest?.[1] as Record<string, unknown> | undefined;
|
||||
const startConfig = startParams?.config as Record<string, unknown> | undefined;
|
||||
const startDynamicTools = startParams?.dynamicTools as CodexDynamicToolSpec[] | undefined;
|
||||
const startDynamicTools = startParams?.dynamicTools as Array<{ name: string }> | undefined;
|
||||
expect(startConfig?.["features.code_mode"]).toBe(false);
|
||||
expect(startConfig?.["features.code_mode_only"]).toBe(false);
|
||||
expect(startParams?.environments).toEqual([]);
|
||||
expect(specNames(startDynamicTools ?? [])).toEqual([
|
||||
expect(startDynamicTools?.map((tool) => tool.name)).toEqual([
|
||||
"message",
|
||||
"sandbox_exec",
|
||||
"sandbox_process",
|
||||
@@ -652,7 +631,7 @@ describe("runCodexAppServerAttempt", () => {
|
||||
const startParams = startRequest?.[1] as
|
||||
| {
|
||||
cwd?: string;
|
||||
dynamicTools?: CodexDynamicToolSpec[];
|
||||
dynamicTools?: Array<{ name: string }>;
|
||||
environments?: Array<{ environmentId?: string; cwd?: string }>;
|
||||
sandbox?: string;
|
||||
config?: {
|
||||
@@ -670,7 +649,7 @@ describe("runCodexAppServerAttempt", () => {
|
||||
expect(startParams?.config?.["features.code_mode"]).toBe(true);
|
||||
expect(startParams?.config?.["features.code_mode_only"]).toBe(false);
|
||||
expect(startParams?.config?.["features.apply_patch_streaming_events"]).toBe(true);
|
||||
expect(specNames(startParams?.dynamicTools ?? [])).toEqual(["message"]);
|
||||
expect(startParams?.dynamicTools?.map((tool) => tool.name)).toEqual(["message"]);
|
||||
expect(startParams?.environments).toEqual([
|
||||
{ environmentId: environmentAddParams?.environmentId, cwd: "/workspace" },
|
||||
]);
|
||||
@@ -923,10 +902,10 @@ describe("runCodexAppServerAttempt", () => {
|
||||
});
|
||||
|
||||
const startRequest = request.mock.calls.find(([method]) => method === "thread/start");
|
||||
const dynamicToolNames = specNames(
|
||||
(startRequest?.[1] as { dynamicTools?: CodexDynamicToolSpec[] } | undefined)?.dynamicTools ??
|
||||
[],
|
||||
);
|
||||
const dynamicToolNames = (
|
||||
(startRequest?.[1] as { dynamicTools?: Array<{ name: string }> } | undefined)?.dynamicTools ??
|
||||
[]
|
||||
).map((tool) => tool.name);
|
||||
|
||||
expect(dynamicToolNames).toContain("message");
|
||||
expect(dynamicToolNames).toContain("web_search");
|
||||
@@ -1593,12 +1572,11 @@ describe("runCodexAppServerAttempt", () => {
|
||||
directToolNames: ["message"],
|
||||
});
|
||||
|
||||
const specs = flattenSpecsWithNamespace(toolBridge.specs);
|
||||
const message = specs.find((tool) => tool.name === "message");
|
||||
const webSearch = specs.find((tool) => tool.name === "web_search");
|
||||
const heartbeat = specs.find((tool) => tool.name === "heartbeat_respond");
|
||||
const sessionsSpawn = specs.find((tool) => tool.name === "sessions_spawn");
|
||||
const sessionsYield = specs.find((tool) => tool.name === "sessions_yield");
|
||||
const message = toolBridge.specs.find((tool) => tool.name === "message");
|
||||
const webSearch = toolBridge.specs.find((tool) => tool.name === "web_search");
|
||||
const heartbeat = toolBridge.specs.find((tool) => tool.name === "heartbeat_respond");
|
||||
const sessionsSpawn = toolBridge.specs.find((tool) => tool.name === "sessions_spawn");
|
||||
const sessionsYield = toolBridge.specs.find((tool) => tool.name === "sessions_yield");
|
||||
|
||||
expect(message).not.toHaveProperty("namespace");
|
||||
expect(message).not.toHaveProperty("deferLoading");
|
||||
@@ -1646,7 +1624,7 @@ describe("runCodexAppServerAttempt", () => {
|
||||
const normalInstructions = testing.buildDeveloperInstructions(createRunParams(), {
|
||||
dynamicTools: normalBridge.availableSpecs,
|
||||
});
|
||||
const registeredToolNames = specNames(normalBridge.specs);
|
||||
const registeredToolNames = normalBridge.specs.map((tool) => tool.name);
|
||||
|
||||
expect(registeredToolNames).toContain("message");
|
||||
expect(registeredToolNames).toContain("heartbeat_respond");
|
||||
@@ -1668,8 +1646,8 @@ describe("runCodexAppServerAttempt", () => {
|
||||
registeredTools,
|
||||
);
|
||||
|
||||
expect(specNames(heartbeatBridge.specs)).toEqual(registeredToolNames);
|
||||
expect(specNames(nextNormalBridge.specs)).toEqual(registeredToolNames);
|
||||
expect(heartbeatBridge.specs.map((tool) => tool.name)).toEqual(registeredToolNames);
|
||||
expect(nextNormalBridge.specs.map((tool) => tool.name)).toEqual(registeredToolNames);
|
||||
});
|
||||
|
||||
it("keeps the persistent dynamic schema stable across heartbeat-only turns", async () => {
|
||||
@@ -1722,9 +1700,13 @@ describe("runCodexAppServerAttempt", () => {
|
||||
registeredTools,
|
||||
);
|
||||
|
||||
expect(specNames(heartbeatBridge.availableSpecs)).toEqual(["heartbeat_respond"]);
|
||||
expect(specNames(heartbeatBridge.specs)).toEqual(specNames(normalBridge.specs));
|
||||
expect(specNames(nextNormalBridge.specs)).toEqual(specNames(normalBridge.specs));
|
||||
expect(heartbeatBridge.availableSpecs.map((tool) => tool.name)).toEqual(["heartbeat_respond"]);
|
||||
expect(heartbeatBridge.specs.map((tool) => tool.name)).toEqual(
|
||||
normalBridge.specs.map((tool) => tool.name),
|
||||
);
|
||||
expect(nextNormalBridge.specs.map((tool) => tool.name)).toEqual(
|
||||
normalBridge.specs.map((tool) => tool.name),
|
||||
);
|
||||
});
|
||||
|
||||
it("disables Codex native tool surfaces when runtime toolsAllow is empty", async () => {
|
||||
@@ -1763,7 +1745,7 @@ describe("runCodexAppServerAttempt", () => {
|
||||
const startRequest = request.mock.calls.find(([method]) => method === "thread/start");
|
||||
const startParams = startRequest?.[1] as
|
||||
| {
|
||||
dynamicTools?: CodexDynamicToolSpec[];
|
||||
dynamicTools?: Array<{ name?: string }>;
|
||||
environments?: unknown[];
|
||||
developerInstructions?: string;
|
||||
config?: {
|
||||
@@ -5325,9 +5307,9 @@ describe("runCodexAppServerAttempt", () => {
|
||||
const startRequest = requests.find((request) => request.method === "thread/start");
|
||||
const startRequestParams = startRequest?.params as Record<string, unknown> | undefined;
|
||||
const startConfig = startRequestParams?.config as Record<string, unknown> | undefined;
|
||||
const dynamicToolNames = specNames(
|
||||
(startRequestParams?.dynamicTools as CodexDynamicToolSpec[] | undefined) ?? [],
|
||||
);
|
||||
const dynamicToolNames = (
|
||||
startRequestParams?.dynamicTools as Array<{ name?: string }> | undefined
|
||||
)?.map((tool) => tool.name);
|
||||
expect(startRequestParams?.model).toBe("local-model");
|
||||
expect(startRequestParams?.modelProvider).toBe("lmstudio");
|
||||
expect(startConfig?.web_search).toBe("disabled");
|
||||
|
||||
@@ -206,7 +206,6 @@ import {
|
||||
readCodexDynamicToolCallParams,
|
||||
} from "./protocol-validators.js";
|
||||
import {
|
||||
flattenCodexDynamicToolFunctions,
|
||||
isJsonObject,
|
||||
type CodexSandboxPolicy,
|
||||
type CodexTurnEnvironmentParams,
|
||||
@@ -855,12 +854,6 @@ export async function runCodexAppServerAttempt(
|
||||
sessionKey: contextSessionKey,
|
||||
sessionFile: activeSessionFile,
|
||||
runtimeContext: buildActiveContextEngineRuntimeContext(),
|
||||
contextEngineHostSupport: CODEX_APP_SERVER_CONTEXT_ENGINE_HOST,
|
||||
providerId: params.provider,
|
||||
requestedModelId: params.requestedModelId,
|
||||
modelId: params.modelId,
|
||||
fallbackReason: params.fallbackReason,
|
||||
degradedReason: params.degradedReason,
|
||||
runMaintenance: runHarnessContextEngineMaintenance,
|
||||
config: params.config,
|
||||
warn: (message) => embeddedAgentLog.warn(message),
|
||||
@@ -921,17 +914,10 @@ export async function runCodexAppServerAttempt(
|
||||
messages: historyMessages,
|
||||
tokenBudget: params.contextTokenBudget,
|
||||
availableTools: new Set(
|
||||
flattenCodexDynamicToolFunctions(toolBridge.availableSpecs)
|
||||
.map((tool) => tool.name)
|
||||
.filter(isNonEmptyString),
|
||||
toolBridge.availableSpecs.map((tool) => tool.name).filter(isNonEmptyString),
|
||||
),
|
||||
citationsMode: params.config?.memory?.citations,
|
||||
modelId: params.modelId,
|
||||
contextEngineHostSupport: CODEX_APP_SERVER_CONTEXT_ENGINE_HOST,
|
||||
providerId: params.provider,
|
||||
requestedModelId: params.requestedModelId,
|
||||
fallbackReason: params.fallbackReason,
|
||||
degradedReason: params.degradedReason,
|
||||
prompt: params.prompt,
|
||||
});
|
||||
if (!assembled) {
|
||||
@@ -1358,7 +1344,7 @@ export async function runCodexAppServerAttempt(
|
||||
threadId: thread.threadId,
|
||||
authProfileId: startupAuthProfileId,
|
||||
workspaceDir: effectiveWorkspace,
|
||||
toolCount: flattenCodexDynamicToolFunctions(toolBridge.specs).length,
|
||||
toolCount: toolBridge.specs.length,
|
||||
});
|
||||
recordCodexTrajectoryContext(trajectoryRecorder, {
|
||||
attempt: params,
|
||||
@@ -2807,12 +2793,6 @@ export async function runCodexAppServerAttempt(
|
||||
lastCallUsage: result.attemptUsage,
|
||||
promptCache: result.promptCache,
|
||||
}),
|
||||
contextEngineHostSupport: CODEX_APP_SERVER_CONTEXT_ENGINE_HOST,
|
||||
providerId: params.provider,
|
||||
requestedModelId: params.requestedModelId,
|
||||
modelId: params.modelId,
|
||||
fallbackReason: params.fallbackReason,
|
||||
degradedReason: params.degradedReason,
|
||||
runMaintenance: runHarnessContextEngineMaintenance,
|
||||
config: params.config,
|
||||
warn: (message) => embeddedAgentLog.warn(message),
|
||||
|
||||
@@ -97,12 +97,11 @@ describe("Codex app-server dynamic tool schema boundary contract", () => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("passes prepared executable dynamic tool schemas through legacy thread start specs", async () => {
|
||||
it("passes prepared executable dynamic tool schemas through thread start unchanged", async () => {
|
||||
const sessionFile = path.join(tempDir, "session.jsonl");
|
||||
const workspaceDir = path.join(tempDir, "workspace");
|
||||
const parameterFreeTool = createParameterFreeTool("message");
|
||||
const dynamicTool = {
|
||||
type: "function" as const,
|
||||
name: parameterFreeTool.name,
|
||||
description: parameterFreeTool.description,
|
||||
inputSchema: normalizedParameterFreeSchema(),
|
||||
@@ -128,13 +127,7 @@ describe("Codex app-server dynamic tool schema boundary contract", () => {
|
||||
throw new Error(`expected thread/start request, got ${method}`);
|
||||
}
|
||||
const startPayload = payload as CodexThreadStartParams | undefined;
|
||||
expect(startPayload?.dynamicTools).toStrictEqual([
|
||||
{
|
||||
name: dynamicTool.name,
|
||||
description: dynamicTool.description,
|
||||
inputSchema: dynamicTool.inputSchema,
|
||||
},
|
||||
]);
|
||||
expect(startPayload?.dynamicTools).toStrictEqual([dynamicTool]);
|
||||
expect(startPayload?.cwd).toBe(workspaceDir);
|
||||
expect(startPayload?.model).toBe("gpt-5.4");
|
||||
expect(startPayload?.modelProvider).toBeUndefined();
|
||||
@@ -187,7 +180,6 @@ describe("Codex app-server dynamic tool schema boundary contract", () => {
|
||||
cwd: workspaceDir,
|
||||
dynamicTools: [
|
||||
{
|
||||
type: "function",
|
||||
name: "message",
|
||||
description: "Permissive test tool",
|
||||
inputSchema: { type: "object" },
|
||||
@@ -202,7 +194,6 @@ describe("Codex app-server dynamic tool schema boundary contract", () => {
|
||||
cwd: workspaceDir,
|
||||
dynamicTools: [
|
||||
{
|
||||
type: "function",
|
||||
name: permissiveTool.name,
|
||||
description: permissiveTool.description,
|
||||
inputSchema: permissiveTool.parameters,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Codex tests cover thread lifecycle.binding plugin behavior.
|
||||
import path from "node:path";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import type { CodexDynamicToolFunctionSpec } from "./protocol.js";
|
||||
import {
|
||||
createParams as createRunAttemptParams,
|
||||
setupRunAttemptTestHooks,
|
||||
@@ -67,9 +66,8 @@ function writeCodexAppServerBinding(...args: Parameters<typeof writeRawCodexAppS
|
||||
function createMessageDynamicTool(
|
||||
description: string,
|
||||
actions: string[] = ["send"],
|
||||
): CodexDynamicToolFunctionSpec {
|
||||
): Parameters<typeof startOrResumeThread>[0]["dynamicTools"][number] {
|
||||
return {
|
||||
type: "function",
|
||||
name: "message",
|
||||
description,
|
||||
inputSchema: {
|
||||
@@ -86,9 +84,10 @@ function createMessageDynamicTool(
|
||||
};
|
||||
}
|
||||
|
||||
function createNamedDynamicTool(name: string): CodexDynamicToolFunctionSpec {
|
||||
function createNamedDynamicTool(
|
||||
name: string,
|
||||
): Parameters<typeof startOrResumeThread>[0]["dynamicTools"][number] {
|
||||
return {
|
||||
type: "function",
|
||||
name,
|
||||
description: `${name} test tool`,
|
||||
inputSchema: {
|
||||
@@ -103,10 +102,9 @@ function createDeferredNamedDynamicTool(
|
||||
name: string,
|
||||
): Parameters<typeof startOrResumeThread>[0]["dynamicTools"][number] {
|
||||
return {
|
||||
type: "namespace",
|
||||
name: "openclaw",
|
||||
description: "",
|
||||
tools: [{ ...createNamedDynamicTool(name), deferLoading: true }],
|
||||
...createNamedDynamicTool(name),
|
||||
namespace: "openclaw",
|
||||
deferLoading: true,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -290,47 +288,6 @@ describe("Codex app-server thread lifecycle bindings", () => {
|
||||
expect(request.mock.calls.map(([method]) => method)).toEqual(["thread/start", "thread/resume"]);
|
||||
});
|
||||
|
||||
it("sends legacy flat dynamic tools on thread start", async () => {
|
||||
const sessionFile = path.join(tempDir, "session.jsonl");
|
||||
const workspaceDir = path.join(tempDir, "workspace");
|
||||
const params = createParams(sessionFile, workspaceDir);
|
||||
const appServer = createThreadLifecycleAppServerOptions();
|
||||
const request = vi.fn(async (method: string, _requestParams?: unknown) => {
|
||||
if (method === "thread/start") {
|
||||
return threadStartResult("thread-flat-tools");
|
||||
}
|
||||
throw new Error(`unexpected method: ${method}`);
|
||||
});
|
||||
|
||||
await startOrResumeThread({
|
||||
client: { request } as never,
|
||||
params,
|
||||
cwd: workspaceDir,
|
||||
dynamicTools: [
|
||||
createMessageDynamicTool("Send a message."),
|
||||
createDeferredNamedDynamicTool("web_search"),
|
||||
],
|
||||
appServer,
|
||||
});
|
||||
|
||||
const startParams = request.mock.calls.find(([method]) => method === "thread/start")?.[1] as
|
||||
| { dynamicTools?: unknown[] }
|
||||
| undefined;
|
||||
expect(startParams?.dynamicTools).toEqual([
|
||||
expect.objectContaining({
|
||||
name: "message",
|
||||
description: "Send a message.",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
name: "web_search",
|
||||
namespace: "openclaw",
|
||||
deferLoading: true,
|
||||
}),
|
||||
]);
|
||||
expect(startParams?.dynamicTools?.[0]).not.toHaveProperty("type");
|
||||
expect(startParams?.dynamicTools?.[1]).not.toHaveProperty("type");
|
||||
});
|
||||
|
||||
it("keeps the bound local provider when recoverable resume failure starts a fresh thread", async () => {
|
||||
const sessionFile = path.join(tempDir, "session.jsonl");
|
||||
const workspaceDir = path.join(tempDir, "workspace");
|
||||
|
||||
@@ -196,31 +196,23 @@ describe("Codex app-server native code mode config", () => {
|
||||
const instructions = buildDeveloperInstructions(createAttemptParams({ provider: "openai" }), {
|
||||
dynamicTools: [
|
||||
{
|
||||
type: "function",
|
||||
name: "message",
|
||||
description: "Send a message",
|
||||
inputSchema: { type: "object" },
|
||||
},
|
||||
{
|
||||
type: "namespace",
|
||||
name: "openclaw",
|
||||
description: "",
|
||||
tools: [
|
||||
{
|
||||
type: "function",
|
||||
name: "music_generate",
|
||||
description: "Create music",
|
||||
inputSchema: { type: "object" },
|
||||
deferLoading: true,
|
||||
},
|
||||
{
|
||||
type: "function",
|
||||
name: "image_generate",
|
||||
description: "Create images",
|
||||
inputSchema: { type: "object" },
|
||||
deferLoading: true,
|
||||
},
|
||||
],
|
||||
name: "music_generate",
|
||||
description: "Create music",
|
||||
inputSchema: { type: "object" },
|
||||
namespace: "openclaw",
|
||||
deferLoading: true,
|
||||
},
|
||||
{
|
||||
name: "image_generate",
|
||||
description: "Create images",
|
||||
inputSchema: { type: "object" },
|
||||
namespace: "openclaw",
|
||||
deferLoading: true,
|
||||
},
|
||||
],
|
||||
});
|
||||
@@ -236,18 +228,11 @@ describe("Codex app-server native code mode config", () => {
|
||||
const instructions = buildDeveloperInstructions(createAttemptParams({ provider: "openai" }), {
|
||||
dynamicTools: [
|
||||
{
|
||||
type: "namespace",
|
||||
name: "openclaw",
|
||||
description: "",
|
||||
tools: [
|
||||
{
|
||||
type: "function",
|
||||
name: "skill_workshop",
|
||||
description: "Manage skill proposals",
|
||||
inputSchema: { type: "object" },
|
||||
deferLoading: true,
|
||||
},
|
||||
],
|
||||
name: "skill_workshop",
|
||||
description: "Manage skill proposals",
|
||||
inputSchema: { type: "object" },
|
||||
namespace: "openclaw",
|
||||
deferLoading: true,
|
||||
},
|
||||
],
|
||||
});
|
||||
@@ -265,7 +250,6 @@ describe("Codex app-server native code mode config", () => {
|
||||
const instructions = buildDeveloperInstructions(createAttemptParams({ provider: "openai" }), {
|
||||
dynamicTools: [
|
||||
{
|
||||
type: "function",
|
||||
name: "message",
|
||||
description: "Send a message",
|
||||
inputSchema: { type: "object" },
|
||||
@@ -287,7 +271,6 @@ describe("Codex app-server native code mode config", () => {
|
||||
};
|
||||
const directFingerprint = codexDynamicToolsFingerprint([
|
||||
{
|
||||
type: "function",
|
||||
name: "message",
|
||||
description: "Send a visible message",
|
||||
inputSchema,
|
||||
@@ -295,18 +278,11 @@ describe("Codex app-server native code mode config", () => {
|
||||
]);
|
||||
const searchableFingerprint = codexDynamicToolsFingerprint([
|
||||
{
|
||||
type: "namespace",
|
||||
name: "openclaw",
|
||||
description: "",
|
||||
tools: [
|
||||
{
|
||||
type: "function",
|
||||
name: "message",
|
||||
description: "Load and send a visible message",
|
||||
inputSchema,
|
||||
deferLoading: true,
|
||||
},
|
||||
],
|
||||
name: "message",
|
||||
description: "Load and send a visible message",
|
||||
inputSchema,
|
||||
namespace: "openclaw",
|
||||
deferLoading: true,
|
||||
},
|
||||
]);
|
||||
|
||||
|
||||
@@ -37,11 +37,8 @@ import {
|
||||
assertCodexThreadStartResponse,
|
||||
} from "./protocol-validators.js";
|
||||
import {
|
||||
flattenCodexDynamicToolFunctions,
|
||||
isJsonObject,
|
||||
type CodexDynamicToolFunctionSpec,
|
||||
type CodexDynamicToolSpec,
|
||||
type CodexLegacyDynamicToolFunctionSpec,
|
||||
type CodexSandboxPolicy,
|
||||
type CodexThreadResumeParams,
|
||||
type CodexThreadStartParams,
|
||||
@@ -325,7 +322,7 @@ export async function startOrResumeThread(params: {
|
||||
const dynamicToolsFingerprint = lifecycleTiming.measureSync("dynamic-tools-fingerprint", () =>
|
||||
fingerprintDynamicTools(params.dynamicTools),
|
||||
);
|
||||
const dynamicToolsContainDeferred = flattenCodexDynamicToolFunctions(params.dynamicTools).some(
|
||||
const dynamicToolsContainDeferred = params.dynamicTools.some(
|
||||
(tool) => tool.deferLoading === true,
|
||||
);
|
||||
const webSearchPlan = lifecycleTiming.measureSync("web-search-plan", () =>
|
||||
@@ -1068,33 +1065,12 @@ export function buildThreadStartParams(
|
||||
developerInstructions:
|
||||
options.developerInstructions ??
|
||||
buildDeveloperInstructions(params, { dynamicTools: options.dynamicTools }),
|
||||
dynamicTools: toCodexThreadStartDynamicTools(options.dynamicTools),
|
||||
dynamicTools: options.dynamicTools,
|
||||
experimentalRawEvents: true,
|
||||
persistExtendedHistory: true,
|
||||
};
|
||||
}
|
||||
|
||||
function toCodexThreadStartDynamicTools(
|
||||
dynamicTools: readonly CodexDynamicToolSpec[],
|
||||
): CodexLegacyDynamicToolFunctionSpec[] {
|
||||
// Managed stable Codex still accepts the legacy flat start payload. Keep
|
||||
// OpenClaw namespaces internally, but omit `type` on the wire so Codex does
|
||||
// not reject a mixed canonical/legacy shape before thread creation.
|
||||
return dynamicTools.flatMap((tool) =>
|
||||
tool.type === "namespace"
|
||||
? tool.tools.map((child) => toCodexLegacyDynamicTool(child, tool.name))
|
||||
: [toCodexLegacyDynamicTool(tool)],
|
||||
);
|
||||
}
|
||||
|
||||
function toCodexLegacyDynamicTool(
|
||||
tool: CodexDynamicToolFunctionSpec,
|
||||
namespace?: string,
|
||||
): CodexLegacyDynamicToolFunctionSpec {
|
||||
const { type: _type, ...legacyTool } = tool;
|
||||
return namespace ? { ...legacyTool, namespace } : legacyTool;
|
||||
}
|
||||
|
||||
export function buildThreadResumeParams(
|
||||
params: EmbeddedRunAttemptParams,
|
||||
options: {
|
||||
@@ -1513,25 +1489,17 @@ function fingerprintEnvironmentSelection(
|
||||
}
|
||||
|
||||
function fingerprintDynamicToolSpec(tool: JsonValue): JsonValue {
|
||||
return stabilizeDynamicToolFingerprintValue(tool);
|
||||
}
|
||||
|
||||
function stabilizeDynamicToolFingerprintValue(value: JsonValue): JsonValue {
|
||||
if (Array.isArray(value)) {
|
||||
return value.map(stabilizeDynamicToolFingerprintValue);
|
||||
if (!isJsonObject(tool)) {
|
||||
return stabilizeJsonValue(tool);
|
||||
}
|
||||
if (!isJsonObject(value)) {
|
||||
return value;
|
||||
}
|
||||
|
||||
const stable: JsonObject = {};
|
||||
for (const [key, child] of Object.entries(value).toSorted(([left], [right]) =>
|
||||
for (const [key, child] of Object.entries(tool).toSorted(([left], [right]) =>
|
||||
left.localeCompare(right),
|
||||
)) {
|
||||
if (key === "description") {
|
||||
continue;
|
||||
}
|
||||
stable[key] = stabilizeDynamicToolFingerprintValue(child);
|
||||
stable[key] = stabilizeJsonValue(child);
|
||||
}
|
||||
return stable;
|
||||
}
|
||||
@@ -1606,7 +1574,7 @@ function buildDeferredDynamicToolManifest(
|
||||
): string | undefined {
|
||||
const deferredToolNames = [
|
||||
...new Set(
|
||||
flattenCodexDynamicToolFunctions(dynamicTools)
|
||||
(dynamicTools ?? [])
|
||||
.filter((tool) => tool.deferLoading === true)
|
||||
.map((tool) => tool.name.trim())
|
||||
.filter(Boolean),
|
||||
@@ -1621,7 +1589,7 @@ function buildDeferredDynamicToolManifest(
|
||||
function buildSkillWorkshopInstruction(
|
||||
dynamicTools: readonly CodexDynamicToolSpec[] | undefined,
|
||||
): string | undefined {
|
||||
const hasSkillWorkshop = flattenCodexDynamicToolFunctions(dynamicTools).some(
|
||||
const hasSkillWorkshop = (dynamicTools ?? []).some(
|
||||
(tool) => tool.name.trim() === SKILL_WORKSHOP_TOOL_NAME,
|
||||
);
|
||||
if (!hasSkillWorkshop) {
|
||||
@@ -1635,7 +1603,7 @@ function buildVisibleReplyInstruction(
|
||||
dynamicTools: readonly CodexDynamicToolSpec[] | undefined,
|
||||
): string {
|
||||
const messageToolAvailable = dynamicTools
|
||||
? flattenCodexDynamicToolFunctions(dynamicTools).some((tool) => tool.name.trim() === "message")
|
||||
? dynamicTools.some((tool) => tool.name.trim() === "message")
|
||||
: params.disableMessageTool !== true;
|
||||
if (params.sourceReplyDeliveryMode === "message_tool_only" && messageToolAvailable) {
|
||||
return "Visible source replies are not automatically delivered for this run. Use `message(action=send)` for user-visible source-channel output. Do not repeat that visible content in your final answer.";
|
||||
|
||||
@@ -5,7 +5,6 @@ import path from "node:path";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import {
|
||||
createCodexTrajectoryRecorder,
|
||||
recordCodexTrajectoryContext,
|
||||
resolveCodexTrajectoryAppendFlags,
|
||||
resolveCodexTrajectoryPointerFlags,
|
||||
} from "./trajectory.js";
|
||||
@@ -121,55 +120,6 @@ describe("Codex trajectory recorder", () => {
|
||||
expect(parsed.modelId).toBe("gpt-5.5");
|
||||
});
|
||||
|
||||
it("records namespace dynamic tools as callable trajectory tool definitions", async () => {
|
||||
const tmpDir = makeTempDir();
|
||||
const sessionFile = path.join(tmpDir, "session.jsonl");
|
||||
const init = {
|
||||
cwd: tmpDir,
|
||||
attempt: {
|
||||
sessionFile,
|
||||
sessionId: "session-1",
|
||||
sessionKey: "agent:main:session-1",
|
||||
runId: "run-1",
|
||||
provider: "codex",
|
||||
modelId: "gpt-5.4",
|
||||
model: { api: "responses" },
|
||||
} as never,
|
||||
env: {},
|
||||
tools: [
|
||||
{
|
||||
type: "namespace",
|
||||
name: "openclaw",
|
||||
description: "",
|
||||
tools: [
|
||||
{
|
||||
type: "function",
|
||||
name: "web_search",
|
||||
description: "Search the web.",
|
||||
inputSchema: { type: "object" },
|
||||
deferLoading: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
} satisfies Parameters<typeof createCodexTrajectoryRecorder>[0];
|
||||
const recorder = createCodexTrajectoryRecorder(init);
|
||||
|
||||
recordCodexTrajectoryContext(expectTrajectoryRecorder(recorder), init);
|
||||
await recorder?.flush();
|
||||
|
||||
const parsed = JSON.parse(
|
||||
fs.readFileSync(path.join(tmpDir, "session.trajectory.jsonl"), "utf8"),
|
||||
);
|
||||
expect(parsed.data?.tools).toEqual([
|
||||
{
|
||||
name: "web_search",
|
||||
description: "Search the web.",
|
||||
parameters: { type: "object" },
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("sanitizes session ids when resolving an override directory", async () => {
|
||||
const tmpDir = makeTempDir();
|
||||
const recorder = createCodexTrajectoryRecorder({
|
||||
|
||||
@@ -15,7 +15,6 @@ import {
|
||||
resolveRegularFileAppendFlags,
|
||||
} from "openclaw/plugin-sdk/security-runtime";
|
||||
import { resolveCodexLocalRuntimeAttribution } from "./local-runtime-attribution.js";
|
||||
import { flattenCodexDynamicToolFunctions, type CodexDynamicToolSpec } from "./protocol.js";
|
||||
|
||||
/** Runtime trajectory recorder used by Codex run attempts and event projectors. */
|
||||
export type CodexTrajectoryRecorder = {
|
||||
@@ -29,7 +28,7 @@ type CodexTrajectoryInit = {
|
||||
cwd: string;
|
||||
developerInstructions?: string;
|
||||
prompt?: string;
|
||||
tools?: CodexDynamicToolSpec[];
|
||||
tools?: Array<{ name?: string; description?: string; inputSchema?: unknown }>;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
};
|
||||
|
||||
@@ -299,12 +298,12 @@ function resolveContainedPath(baseDir: string, fileName: string): string {
|
||||
}
|
||||
|
||||
function toTrajectoryToolDefinitions(
|
||||
tools: readonly CodexDynamicToolSpec[] | undefined,
|
||||
tools: Array<{ name?: string; description?: string; inputSchema?: unknown }> | undefined,
|
||||
): Array<{ name: string; description?: string; parameters?: unknown }> | undefined {
|
||||
if (!tools || tools.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
return flattenCodexDynamicToolFunctions(tools)
|
||||
return tools
|
||||
.flatMap((tool) => {
|
||||
const name = tool.name?.trim();
|
||||
if (!name) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@openclaw/comfy-provider",
|
||||
"version": "2026.6.8",
|
||||
"version": "2026.6.9",
|
||||
"private": true,
|
||||
"description": "OpenClaw ComfyUI provider plugin",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@openclaw/copilot-proxy",
|
||||
"version": "2026.6.8",
|
||||
"version": "2026.6.9",
|
||||
"private": true,
|
||||
"description": "OpenClaw Copilot Proxy provider plugin",
|
||||
"type": "module",
|
||||
|
||||
4
extensions/copilot/npm-shrinkwrap.json
generated
4
extensions/copilot/npm-shrinkwrap.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@openclaw/copilot",
|
||||
"version": "2026.6.8",
|
||||
"version": "2026.6.9",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@openclaw/copilot",
|
||||
"version": "2026.6.8",
|
||||
"version": "2026.6.9",
|
||||
"dependencies": {
|
||||
"@github/copilot-sdk": "1.0.0-beta.9"
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"id": "copilot",
|
||||
"name": "GitHub Copilot agent runtime",
|
||||
"description": "Registers the GitHub Copilot agent runtime.",
|
||||
"version": "2026.6.2",
|
||||
"version": "2026.6.9",
|
||||
"activation": {
|
||||
"onStartup": false,
|
||||
"onAgentHarnesses": ["copilot"]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@openclaw/copilot",
|
||||
"version": "2026.6.8",
|
||||
"version": "2026.6.9",
|
||||
"description": "OpenClaw GitHub Copilot agent runtime plugin (registers a `github-copilot` AgentHarness backed by @github/copilot-sdk over JSON-RPC to the GitHub Copilot CLI)",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -25,10 +25,10 @@
|
||||
"minHostVersion": ">=2026.5.28"
|
||||
},
|
||||
"compat": {
|
||||
"pluginApi": ">=2026.6.8"
|
||||
"pluginApi": ">=2026.6.9"
|
||||
},
|
||||
"build": {
|
||||
"openclawVersion": "2026.6.8",
|
||||
"openclawVersion": "2026.6.9",
|
||||
"bundledDist": false
|
||||
},
|
||||
"release": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@openclaw/deepgram-provider",
|
||||
"version": "2026.6.8",
|
||||
"version": "2026.6.9",
|
||||
"private": true,
|
||||
"description": "OpenClaw Deepgram media-understanding provider",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@openclaw/deepinfra-provider",
|
||||
"version": "2026.6.8",
|
||||
"version": "2026.6.9",
|
||||
"private": true,
|
||||
"description": "OpenClaw DeepInfra provider plugin",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@openclaw/deepseek-provider",
|
||||
"version": "2026.6.8",
|
||||
"version": "2026.6.9",
|
||||
"private": true,
|
||||
"description": "OpenClaw DeepSeek provider plugin",
|
||||
"type": "module",
|
||||
|
||||
494
extensions/diagnostics-otel/npm-shrinkwrap.json
generated
494
extensions/diagnostics-otel/npm-shrinkwrap.json
generated
@@ -1,23 +1,23 @@
|
||||
{
|
||||
"name": "@openclaw/diagnostics-otel",
|
||||
"version": "2026.6.8",
|
||||
"version": "2026.6.9",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@openclaw/diagnostics-otel",
|
||||
"version": "2026.6.8",
|
||||
"version": "2026.6.9",
|
||||
"dependencies": {
|
||||
"@opentelemetry/api": "1.9.1",
|
||||
"@opentelemetry/api-logs": "0.219.0",
|
||||
"@opentelemetry/exporter-logs-otlp-proto": "0.219.0",
|
||||
"@opentelemetry/exporter-metrics-otlp-proto": "0.219.0",
|
||||
"@opentelemetry/exporter-trace-otlp-proto": "0.219.0",
|
||||
"@opentelemetry/resources": "2.8.0",
|
||||
"@opentelemetry/sdk-logs": "0.219.0",
|
||||
"@opentelemetry/sdk-metrics": "2.8.0",
|
||||
"@opentelemetry/sdk-node": "0.219.0",
|
||||
"@opentelemetry/sdk-trace-base": "2.8.0",
|
||||
"@opentelemetry/api-logs": "0.218.0",
|
||||
"@opentelemetry/exporter-logs-otlp-proto": "0.218.0",
|
||||
"@opentelemetry/exporter-metrics-otlp-proto": "0.218.0",
|
||||
"@opentelemetry/exporter-trace-otlp-proto": "0.218.0",
|
||||
"@opentelemetry/resources": "2.7.1",
|
||||
"@opentelemetry/sdk-logs": "0.218.0",
|
||||
"@opentelemetry/sdk-metrics": "2.7.1",
|
||||
"@opentelemetry/sdk-node": "0.218.0",
|
||||
"@opentelemetry/sdk-trace-base": "2.7.1",
|
||||
"@opentelemetry/semantic-conventions": "1.41.1"
|
||||
}
|
||||
},
|
||||
@@ -72,9 +72,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@opentelemetry/api-logs": {
|
||||
"version": "0.219.0",
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.219.0.tgz",
|
||||
"integrity": "sha512-FFx7YnaYJlIjqWW/AG/yAZ0L/NEY724PipXXXQLdtZPbLwBGbUMTGL1i/esI56TWfTUXxhLfpgrnWJCG8aUJyg==",
|
||||
"version": "0.218.0",
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.218.0.tgz",
|
||||
"integrity": "sha512-fmEWp5kXlGEc3i/lR698Hz41DfGyN4Tbe4g7L1AxSc7fF8Xeh/FQ9Quqpa9dVA413Q1Ad43QOLzU4JoXgbFPWw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@opentelemetry/api": "^1.3.0"
|
||||
@@ -84,12 +84,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@opentelemetry/configuration": {
|
||||
"version": "0.219.0",
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/configuration/-/configuration-0.219.0.tgz",
|
||||
"integrity": "sha512-wXZUYv4ngu43nA4WEhuXNacm46LW+17LRM8nKyIhBzroRA24PBYjMnakwzR/w777nFUB5xlgsYTTeuXxumZM1Q==",
|
||||
"version": "0.218.0",
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/configuration/-/configuration-0.218.0.tgz",
|
||||
"integrity": "sha512-W8wIz7H2R1pufR5jfjb3gU2XkMpm2x/7b1RJcsuzvd70Il/rWWE+g5/Od7hQKrxRTSrTrOWlru101PWXz5I1EQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@opentelemetry/core": "2.8.0",
|
||||
"@opentelemetry/core": "2.7.1",
|
||||
"yaml": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
@@ -100,9 +100,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@opentelemetry/context-async-hooks": {
|
||||
"version": "2.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-2.8.0.tgz",
|
||||
"integrity": "sha512-/3FIraneMcng67SUJCxvyInk/oxzwsxyadufk0wwfOBLf5wqtAGX4MoQASwSbndBPeARzBryUM9Azr5kHIdWLw==",
|
||||
"version": "2.7.1",
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-2.7.1.tgz",
|
||||
"integrity": "sha512-OPFBYuXEn1E4ja3Y6eeA7O+ZnLBNcXTV5Cgsn1VaqBZ6hC5FnpZPLBNme1LJY8ZtF4aOujPKFoeWN4ik487KuQ==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": "^18.19.0 || >=20.6.0"
|
||||
@@ -112,9 +112,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@opentelemetry/core": {
|
||||
"version": "2.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.8.0.tgz",
|
||||
"integrity": "sha512-hd1Lfh8p545nNz+jq1Ejfz+Mn1hyLuxYn1YzTfFNrxr8urEWMNQLPf1Th8kjOH+HxwawCrtgBp8JpBUR4ZSgww==",
|
||||
"version": "2.7.1",
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.7.1.tgz",
|
||||
"integrity": "sha512-QAqIj32AtK6+pEVNG7EOVxHdE06RP+FM5qpiEJ4RtDcFIqKUZHYhl7/7UY5efhwmwNAg7j8QbJVBLxMerc0+gw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@opentelemetry/semantic-conventions": "^1.29.0"
|
||||
@@ -127,17 +127,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@opentelemetry/exporter-logs-otlp-grpc": {
|
||||
"version": "0.219.0",
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-grpc/-/exporter-logs-otlp-grpc-0.219.0.tgz",
|
||||
"integrity": "sha512-7SvzDCIclHWAcCwZ1MTOLcwn4BVNPGI3QxS/DJraPNe1TTL+4TvUBq5zeQV8tsnYvtDN7wKW2qocVmaCP2l7sQ==",
|
||||
"version": "0.218.0",
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-grpc/-/exporter-logs-otlp-grpc-0.218.0.tgz",
|
||||
"integrity": "sha512-hoxrNH1l/Xy6F9WTJ5IK+6j1r9nQFlPOmrnTlhYHTySdunfXLmUCPv3bQtKYntxag9h3wLYBZQ2HI6FOx+BT2g==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@grpc/grpc-js": "^1.14.3",
|
||||
"@opentelemetry/core": "2.8.0",
|
||||
"@opentelemetry/otlp-exporter-base": "0.219.0",
|
||||
"@opentelemetry/otlp-grpc-exporter-base": "0.219.0",
|
||||
"@opentelemetry/otlp-transformer": "0.219.0",
|
||||
"@opentelemetry/sdk-logs": "0.219.0"
|
||||
"@opentelemetry/core": "2.7.1",
|
||||
"@opentelemetry/otlp-exporter-base": "0.218.0",
|
||||
"@opentelemetry/otlp-grpc-exporter-base": "0.218.0",
|
||||
"@opentelemetry/otlp-transformer": "0.218.0",
|
||||
"@opentelemetry/sdk-logs": "0.218.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.19.0 || >=20.6.0"
|
||||
@@ -147,16 +147,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@opentelemetry/exporter-logs-otlp-http": {
|
||||
"version": "0.219.0",
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-http/-/exporter-logs-otlp-http-0.219.0.tgz",
|
||||
"integrity": "sha512-mhl2HL6GmZI8b8PwPfqMws/5ovJfbRTxwc9Y5agVVHiQ+e5SL1btsFr/kJDgt7YCexDtsUn5HAreHQO9szFS0A==",
|
||||
"version": "0.218.0",
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-http/-/exporter-logs-otlp-http-0.218.0.tgz",
|
||||
"integrity": "sha512-Qx+4rpVHzgg89dawcWRHyt+XRXeLnhFz/qBtvggmjkcgPUdr+NAB0/u/eIPA8yAeJV0J80Vz43JZCh/XFvZFGw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@opentelemetry/api-logs": "0.219.0",
|
||||
"@opentelemetry/core": "2.8.0",
|
||||
"@opentelemetry/otlp-exporter-base": "0.219.0",
|
||||
"@opentelemetry/otlp-transformer": "0.219.0",
|
||||
"@opentelemetry/sdk-logs": "0.219.0"
|
||||
"@opentelemetry/api-logs": "0.218.0",
|
||||
"@opentelemetry/core": "2.7.1",
|
||||
"@opentelemetry/otlp-exporter-base": "0.218.0",
|
||||
"@opentelemetry/otlp-transformer": "0.218.0",
|
||||
"@opentelemetry/sdk-logs": "0.218.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.19.0 || >=20.6.0"
|
||||
@@ -166,18 +166,18 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@opentelemetry/exporter-logs-otlp-proto": {
|
||||
"version": "0.219.0",
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-proto/-/exporter-logs-otlp-proto-0.219.0.tgz",
|
||||
"integrity": "sha512-Ayw4Gf71PS9jhBVaYywa4WsajnqfDehMkTdVH3TSAVHqPcsAv/AhH/wTNRYNt99szeYr6Gbd/D6RjZD77wAxHg==",
|
||||
"version": "0.218.0",
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-proto/-/exporter-logs-otlp-proto-0.218.0.tgz",
|
||||
"integrity": "sha512-1/noQNsp9gXD75HPzgjBrcF1+XTtry7pFAUfxVEJgg7mPv2AawKQuYkhMmJ8qjxz4Ubc3Y8bwvfxevXsKTq4cg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@opentelemetry/api-logs": "0.219.0",
|
||||
"@opentelemetry/core": "2.8.0",
|
||||
"@opentelemetry/otlp-exporter-base": "0.219.0",
|
||||
"@opentelemetry/otlp-transformer": "0.219.0",
|
||||
"@opentelemetry/resources": "2.8.0",
|
||||
"@opentelemetry/sdk-logs": "0.219.0",
|
||||
"@opentelemetry/sdk-trace-base": "2.8.0"
|
||||
"@opentelemetry/api-logs": "0.218.0",
|
||||
"@opentelemetry/core": "2.7.1",
|
||||
"@opentelemetry/otlp-exporter-base": "0.218.0",
|
||||
"@opentelemetry/otlp-transformer": "0.218.0",
|
||||
"@opentelemetry/resources": "2.7.1",
|
||||
"@opentelemetry/sdk-logs": "0.218.0",
|
||||
"@opentelemetry/sdk-trace-base": "2.7.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.19.0 || >=20.6.0"
|
||||
@@ -187,19 +187,19 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@opentelemetry/exporter-metrics-otlp-grpc": {
|
||||
"version": "0.219.0",
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-grpc/-/exporter-metrics-otlp-grpc-0.219.0.tgz",
|
||||
"integrity": "sha512-6LaaSrPxK5L55bXevWajvOMxGOpNm0n12tG53TeZaUeNzXwLPg6d2KCC1zAlGsojan+xRG71mA4Qqs9K2VVrKQ==",
|
||||
"version": "0.218.0",
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-grpc/-/exporter-metrics-otlp-grpc-0.218.0.tgz",
|
||||
"integrity": "sha512-YapQ9vNMX0NSZF6LK5pWAFfjpJleV2O9uYWfYGeb/5F1Kb9rPGK8tZDMJFa/sOksgdFuflDvYuA0B4qjDB4fjQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@grpc/grpc-js": "^1.14.3",
|
||||
"@opentelemetry/core": "2.8.0",
|
||||
"@opentelemetry/exporter-metrics-otlp-http": "0.219.0",
|
||||
"@opentelemetry/otlp-exporter-base": "0.219.0",
|
||||
"@opentelemetry/otlp-grpc-exporter-base": "0.219.0",
|
||||
"@opentelemetry/otlp-transformer": "0.219.0",
|
||||
"@opentelemetry/resources": "2.8.0",
|
||||
"@opentelemetry/sdk-metrics": "2.8.0"
|
||||
"@opentelemetry/core": "2.7.1",
|
||||
"@opentelemetry/exporter-metrics-otlp-http": "0.218.0",
|
||||
"@opentelemetry/otlp-exporter-base": "0.218.0",
|
||||
"@opentelemetry/otlp-grpc-exporter-base": "0.218.0",
|
||||
"@opentelemetry/otlp-transformer": "0.218.0",
|
||||
"@opentelemetry/resources": "2.7.1",
|
||||
"@opentelemetry/sdk-metrics": "2.7.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.19.0 || >=20.6.0"
|
||||
@@ -209,16 +209,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@opentelemetry/exporter-metrics-otlp-http": {
|
||||
"version": "0.219.0",
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-http/-/exporter-metrics-otlp-http-0.219.0.tgz",
|
||||
"integrity": "sha512-6CaDRbMVHZSDWzNXwrR8y/H4B/Z1eMNnkHiPQlTx3Ojz2OHY4X/aff/UC4P/3pHUQSuTfi3oh2UsPPZppw+Vrg==",
|
||||
"version": "0.218.0",
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-http/-/exporter-metrics-otlp-http-0.218.0.tgz",
|
||||
"integrity": "sha512-bV7d2OuMpZu2+gAaxUAhzfZ0h3WVZk8ETQUEE3DNSntbTaMpuITjtm8I0rNyHFdm7Ax57K6ty7SgFXlBmOLIvQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@opentelemetry/core": "2.8.0",
|
||||
"@opentelemetry/otlp-exporter-base": "0.219.0",
|
||||
"@opentelemetry/otlp-transformer": "0.219.0",
|
||||
"@opentelemetry/resources": "2.8.0",
|
||||
"@opentelemetry/sdk-metrics": "2.8.0"
|
||||
"@opentelemetry/core": "2.7.1",
|
||||
"@opentelemetry/otlp-exporter-base": "0.218.0",
|
||||
"@opentelemetry/otlp-transformer": "0.218.0",
|
||||
"@opentelemetry/resources": "2.7.1",
|
||||
"@opentelemetry/sdk-metrics": "2.7.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.19.0 || >=20.6.0"
|
||||
@@ -228,17 +228,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@opentelemetry/exporter-metrics-otlp-proto": {
|
||||
"version": "0.219.0",
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-proto/-/exporter-metrics-otlp-proto-0.219.0.tgz",
|
||||
"integrity": "sha512-DUS7XyIiEnoeccQUvuKy0G2/YqeKhpN8FVIrGbrLNIVMj10yeIFLRzRv0tibCI2kXXvlTTABVexGAk78wHk2ug==",
|
||||
"version": "0.218.0",
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-proto/-/exporter-metrics-otlp-proto-0.218.0.tgz",
|
||||
"integrity": "sha512-ubLddKjWULhla9YZRCj/rTBeppjJYE4e9w0icx5mTu3eFhWjQzbV75NYjXuIlEG+NJsBl6d+sTFw5Qu+oej4oQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@opentelemetry/core": "2.8.0",
|
||||
"@opentelemetry/exporter-metrics-otlp-http": "0.219.0",
|
||||
"@opentelemetry/otlp-exporter-base": "0.219.0",
|
||||
"@opentelemetry/otlp-transformer": "0.219.0",
|
||||
"@opentelemetry/resources": "2.8.0",
|
||||
"@opentelemetry/sdk-metrics": "2.8.0"
|
||||
"@opentelemetry/core": "2.7.1",
|
||||
"@opentelemetry/exporter-metrics-otlp-http": "0.218.0",
|
||||
"@opentelemetry/otlp-exporter-base": "0.218.0",
|
||||
"@opentelemetry/otlp-transformer": "0.218.0",
|
||||
"@opentelemetry/resources": "2.7.1",
|
||||
"@opentelemetry/sdk-metrics": "2.7.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.19.0 || >=20.6.0"
|
||||
@@ -248,14 +248,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@opentelemetry/exporter-prometheus": {
|
||||
"version": "0.219.0",
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/exporter-prometheus/-/exporter-prometheus-0.219.0.tgz",
|
||||
"integrity": "sha512-TxOnJ85eWJY5JyOJsNMXiRTYlkDcOv0u3KbXEzWCc+tUS9sjL/BC6BcdxZ0B9r2OFVqsrZFXUzSD2sZUy42Ucw==",
|
||||
"version": "0.218.0",
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/exporter-prometheus/-/exporter-prometheus-0.218.0.tgz",
|
||||
"integrity": "sha512-RT5oEyu1kddZJ1vt7/BUo5wV+P7hpNAESsR3dUd3+8deHuX7gWNoCOZn+SfDT+hJHlIJ5h/AxiCLXIrutswDJg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@opentelemetry/core": "2.8.0",
|
||||
"@opentelemetry/resources": "2.8.0",
|
||||
"@opentelemetry/sdk-metrics": "2.8.0",
|
||||
"@opentelemetry/core": "2.7.1",
|
||||
"@opentelemetry/resources": "2.7.1",
|
||||
"@opentelemetry/sdk-metrics": "2.7.1",
|
||||
"@opentelemetry/semantic-conventions": "^1.29.0"
|
||||
},
|
||||
"engines": {
|
||||
@@ -266,18 +266,18 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@opentelemetry/exporter-trace-otlp-grpc": {
|
||||
"version": "0.219.0",
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-grpc/-/exporter-trace-otlp-grpc-0.219.0.tgz",
|
||||
"integrity": "sha512-BkDNv1UD6BscW19MxbAxVmSYSSFuyeqR6buV2/HTYqA7GrR0EbTFzqG6h86T3PtXmpdbsWjMGLDdjG2rikG27Q==",
|
||||
"version": "0.218.0",
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-grpc/-/exporter-trace-otlp-grpc-0.218.0.tgz",
|
||||
"integrity": "sha512-3fXxVQEj9TNAFaCi79JeFKfeLd0sDtInaR3gaZDVlzNSPHtz8PZuCV34JKWjD4XXzT20IdMe8IpX6mRVNDA4Tw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@grpc/grpc-js": "^1.14.3",
|
||||
"@opentelemetry/core": "2.8.0",
|
||||
"@opentelemetry/otlp-exporter-base": "0.219.0",
|
||||
"@opentelemetry/otlp-grpc-exporter-base": "0.219.0",
|
||||
"@opentelemetry/otlp-transformer": "0.219.0",
|
||||
"@opentelemetry/resources": "2.8.0",
|
||||
"@opentelemetry/sdk-trace-base": "2.8.0"
|
||||
"@opentelemetry/core": "2.7.1",
|
||||
"@opentelemetry/otlp-exporter-base": "0.218.0",
|
||||
"@opentelemetry/otlp-grpc-exporter-base": "0.218.0",
|
||||
"@opentelemetry/otlp-transformer": "0.218.0",
|
||||
"@opentelemetry/resources": "2.7.1",
|
||||
"@opentelemetry/sdk-trace-base": "2.7.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.19.0 || >=20.6.0"
|
||||
@@ -287,16 +287,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@opentelemetry/exporter-trace-otlp-http": {
|
||||
"version": "0.219.0",
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-http/-/exporter-trace-otlp-http-0.219.0.tgz",
|
||||
"integrity": "sha512-9t6SvBXXBEjOBcIzgozvBbd3jWrv3Gt3ngGhl1fhdZ/zRc7oZDVOFEqbi2zlBpW9BXhgDMKv422J0DL/3iQWfw==",
|
||||
"version": "0.218.0",
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-http/-/exporter-trace-otlp-http-0.218.0.tgz",
|
||||
"integrity": "sha512-8dqezsmPhtKitIK/eTipZhYl9EX2/gNQ5zUMhaz3uxEURwfkNf8IPvo6yNfrzbxdtpAOybS/+h7wmIWYqFSpiw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@opentelemetry/core": "2.8.0",
|
||||
"@opentelemetry/otlp-exporter-base": "0.219.0",
|
||||
"@opentelemetry/otlp-transformer": "0.219.0",
|
||||
"@opentelemetry/resources": "2.8.0",
|
||||
"@opentelemetry/sdk-trace-base": "2.8.0"
|
||||
"@opentelemetry/core": "2.7.1",
|
||||
"@opentelemetry/otlp-exporter-base": "0.218.0",
|
||||
"@opentelemetry/otlp-transformer": "0.218.0",
|
||||
"@opentelemetry/resources": "2.7.1",
|
||||
"@opentelemetry/sdk-trace-base": "2.7.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.19.0 || >=20.6.0"
|
||||
@@ -306,16 +306,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@opentelemetry/exporter-trace-otlp-proto": {
|
||||
"version": "0.219.0",
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-proto/-/exporter-trace-otlp-proto-0.219.0.tgz",
|
||||
"integrity": "sha512-lF/LUBfhOFmxJa+SQsLN7ziV4MHa2pyKgOM6JNehSOfU+npjM4gwm9oIKEJrzrWcexMcqydiyoFy0XCb1Ql3wQ==",
|
||||
"version": "0.218.0",
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-proto/-/exporter-trace-otlp-proto-0.218.0.tgz",
|
||||
"integrity": "sha512-r1Msf8SNLRmwh9J6XQ5uh82D7CdDWMNHnPB7LAVHjzut0TkSeKc5KcIvr4SvHvfk/xwN5gxC+VLKQ1k0o8PSPw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@opentelemetry/core": "2.8.0",
|
||||
"@opentelemetry/otlp-exporter-base": "0.219.0",
|
||||
"@opentelemetry/otlp-transformer": "0.219.0",
|
||||
"@opentelemetry/resources": "2.8.0",
|
||||
"@opentelemetry/sdk-trace-base": "2.8.0"
|
||||
"@opentelemetry/core": "2.7.1",
|
||||
"@opentelemetry/otlp-exporter-base": "0.218.0",
|
||||
"@opentelemetry/otlp-transformer": "0.218.0",
|
||||
"@opentelemetry/resources": "2.7.1",
|
||||
"@opentelemetry/sdk-trace-base": "2.7.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.19.0 || >=20.6.0"
|
||||
@@ -325,14 +325,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@opentelemetry/exporter-zipkin": {
|
||||
"version": "2.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/exporter-zipkin/-/exporter-zipkin-2.8.0.tgz",
|
||||
"integrity": "sha512-Mj84UkEa17BK2o903VTXW3wM8CrSZexGs4tRGVZVIMM9ni1T6TuGx5IrRfoWKAbshx42D5/kc7YV+axypLPYyA==",
|
||||
"version": "2.7.1",
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/exporter-zipkin/-/exporter-zipkin-2.7.1.tgz",
|
||||
"integrity": "sha512-mfsD9bKAxcKrh5+y08TPodvClBO0CznBE3p79YAGnO81WI4LrdsGA65T53e4iTSbCalW4WaUpkbeJcbpyIUHfg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@opentelemetry/core": "2.8.0",
|
||||
"@opentelemetry/resources": "2.8.0",
|
||||
"@opentelemetry/sdk-trace-base": "2.8.0",
|
||||
"@opentelemetry/core": "2.7.1",
|
||||
"@opentelemetry/resources": "2.7.1",
|
||||
"@opentelemetry/sdk-trace-base": "2.7.1",
|
||||
"@opentelemetry/semantic-conventions": "^1.29.0"
|
||||
},
|
||||
"engines": {
|
||||
@@ -343,12 +343,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@opentelemetry/instrumentation": {
|
||||
"version": "0.219.0",
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.219.0.tgz",
|
||||
"integrity": "sha512-X5t7I8GyIO9rmGHwoedZLREpQqrF1WW2nxzNNym6HOKpFiE+rvqV3ngC0xcZVO2YwIGf3KKmRdWrYwdwz3H9RQ==",
|
||||
"version": "0.218.0",
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.218.0.tgz",
|
||||
"integrity": "sha512-mIZil8Es+sYDK5m+DQiwAwF57F14TF2YlEqvIjZ/RQWcxDBwRGsKfdK2Tv65OU9meQKCMzSIFS9mxAcnAb6Bkg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@opentelemetry/api-logs": "0.219.0",
|
||||
"@opentelemetry/api-logs": "0.218.0",
|
||||
"import-in-the-middle": "^3.0.0",
|
||||
"require-in-the-middle": "^8.0.0"
|
||||
},
|
||||
@@ -360,13 +360,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@opentelemetry/otlp-exporter-base": {
|
||||
"version": "0.219.0",
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.219.0.tgz",
|
||||
"integrity": "sha512-zvIxQX/AZUVKDU+hCuYx+7UkiP7GRdnk1ZbFQRYzHvYp47cAWR4j3IhoPhV9KaeXEv2xdGq3IA6PnpzDmLcmSA==",
|
||||
"version": "0.218.0",
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.218.0.tgz",
|
||||
"integrity": "sha512-ZwqpkNL5W7RyGJPDZ9g06DvKp8KFTWPJPN12anpMQYSKpTSU0z3EIZuPq9vPGpS8siFyOqDYDAuCwlNO9FqgbA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@opentelemetry/core": "2.8.0",
|
||||
"@opentelemetry/otlp-transformer": "0.219.0"
|
||||
"@opentelemetry/core": "2.7.1",
|
||||
"@opentelemetry/otlp-transformer": "0.218.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.19.0 || >=20.6.0"
|
||||
@@ -376,15 +376,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@opentelemetry/otlp-grpc-exporter-base": {
|
||||
"version": "0.219.0",
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/otlp-grpc-exporter-base/-/otlp-grpc-exporter-base-0.219.0.tgz",
|
||||
"integrity": "sha512-iIk/s8QQu39zpTrRRmsW/Eg3SE2+Hg8tLWepr2FLRgmwUpNd0IpCTLJEHJ77hpt4hgIS8MAh44UYI4xQPZwWlw==",
|
||||
"version": "0.218.0",
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/otlp-grpc-exporter-base/-/otlp-grpc-exporter-base-0.218.0.tgz",
|
||||
"integrity": "sha512-H/lCGJ536N98VpYJOaWTQOkv4Dx6TnmStK6Rqfu1W7KkFbPAx04hjdYEMZF/YbnHzPUSIK4kM6OE2GKGBTpV9A==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@grpc/grpc-js": "^1.14.3",
|
||||
"@opentelemetry/core": "2.8.0",
|
||||
"@opentelemetry/otlp-exporter-base": "0.219.0",
|
||||
"@opentelemetry/otlp-transformer": "0.219.0"
|
||||
"@opentelemetry/core": "2.7.1",
|
||||
"@opentelemetry/otlp-exporter-base": "0.218.0",
|
||||
"@opentelemetry/otlp-transformer": "0.218.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.19.0 || >=20.6.0"
|
||||
@@ -394,17 +394,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@opentelemetry/otlp-transformer": {
|
||||
"version": "0.219.0",
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.219.0.tgz",
|
||||
"integrity": "sha512-aaYKAyXhw9VchKZVGOopD3Gw/kPsyrX2c6IQ0AW32mTjqmZOh5Y6Gf5OYqTNqVktAeBjmFinhyFaCwW6GYK9YQ==",
|
||||
"version": "0.218.0",
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.218.0.tgz",
|
||||
"integrity": "sha512-CFaKH87WAzjuJ4awowTTLzUvMfaRfiOFG5+qm5S5ncyalRtN4ecQ+YmuANJSCrVPuvZFEkUgKhBPBndxi3rHsQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@opentelemetry/api-logs": "0.219.0",
|
||||
"@opentelemetry/core": "2.8.0",
|
||||
"@opentelemetry/resources": "2.8.0",
|
||||
"@opentelemetry/sdk-logs": "0.219.0",
|
||||
"@opentelemetry/sdk-metrics": "2.8.0",
|
||||
"@opentelemetry/sdk-trace-base": "2.8.0"
|
||||
"@opentelemetry/api-logs": "0.218.0",
|
||||
"@opentelemetry/core": "2.7.1",
|
||||
"@opentelemetry/resources": "2.7.1",
|
||||
"@opentelemetry/sdk-logs": "0.218.0",
|
||||
"@opentelemetry/sdk-metrics": "2.7.1",
|
||||
"@opentelemetry/sdk-trace-base": "2.7.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.19.0 || >=20.6.0"
|
||||
@@ -414,12 +414,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@opentelemetry/propagator-b3": {
|
||||
"version": "2.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/propagator-b3/-/propagator-b3-2.8.0.tgz",
|
||||
"integrity": "sha512-SazlvuSKi5533rPHTW2TwBwdMakhjZST4SYs0YauuvfGDkT13KbG1gJS75hV0uWVeevhtVP9sAIlaZLTHdSbMg==",
|
||||
"version": "2.7.1",
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/propagator-b3/-/propagator-b3-2.7.1.tgz",
|
||||
"integrity": "sha512-RJid6E2CKyeGfKBzXKF21ejabGMHypFkPAh3qZ+NvI+SGjuIye79t3PmiqcDgtRzdKH6ynXzbfslQ8DfpRUg2A==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@opentelemetry/core": "2.8.0"
|
||||
"@opentelemetry/core": "2.7.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.19.0 || >=20.6.0"
|
||||
@@ -429,12 +429,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@opentelemetry/propagator-jaeger": {
|
||||
"version": "2.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/propagator-jaeger/-/propagator-jaeger-2.8.0.tgz",
|
||||
"integrity": "sha512-Xnz9zZvvQzUw+9DrOn0MomR7BxFCkA2pcfXBQuHC28ndJpSbjLs7knzYb05kw5SyCjSsEWombkZMgGcJSk8JVg==",
|
||||
"version": "2.7.1",
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/propagator-jaeger/-/propagator-jaeger-2.7.1.tgz",
|
||||
"integrity": "sha512-KMjVBHzP4N60bOzxja76M1F1hZZ43lGPga5ix+mkv9+kk1nx9SbkxSvJsMbuVUxdPQmsPTqGShmhN8ulrMOg6Q==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@opentelemetry/core": "2.8.0"
|
||||
"@opentelemetry/core": "2.7.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.19.0 || >=20.6.0"
|
||||
@@ -444,12 +444,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@opentelemetry/resources": {
|
||||
"version": "2.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.8.0.tgz",
|
||||
"integrity": "sha512-qmXQ27ilDbUK/vGMqwL8D4/rhn76C+sherM4wTbjlfknR8Nvfc/hCxjRJPhkzZzUsPiNg16SA31NxMabwttRjg==",
|
||||
"version": "2.7.1",
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.7.1.tgz",
|
||||
"integrity": "sha512-DeT6KKolmC4e/dRQvMQ/RwlnzhaqeiFOXY5ngoOPJ07GgVVKxZOg9EcrNZb5aTzUn+iCrJldAgOfQm1O/QfPAQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@opentelemetry/core": "2.8.0",
|
||||
"@opentelemetry/core": "2.7.1",
|
||||
"@opentelemetry/semantic-conventions": "^1.29.0"
|
||||
},
|
||||
"engines": {
|
||||
@@ -460,14 +460,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@opentelemetry/sdk-logs": {
|
||||
"version": "0.219.0",
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.219.0.tgz",
|
||||
"integrity": "sha512-s6lTKRakaPClvKoWHRChxnXjDMkM/TQ30ff78jN6EBGf7MI7VzANE5PU3f4z9qDUudWjvZjOLHG0rBnBKYvoXA==",
|
||||
"version": "0.218.0",
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.218.0.tgz",
|
||||
"integrity": "sha512-QvnNdugatFTVCJXH0Mcu7GOOJSylA9j127kIezOE4YwTI4YbowRons2K4WZTv5FMS8T4q9P0NdaRHdkSmeAIag==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@opentelemetry/api-logs": "0.219.0",
|
||||
"@opentelemetry/core": "2.8.0",
|
||||
"@opentelemetry/resources": "2.8.0",
|
||||
"@opentelemetry/api-logs": "0.218.0",
|
||||
"@opentelemetry/core": "2.7.1",
|
||||
"@opentelemetry/resources": "2.7.1",
|
||||
"@opentelemetry/semantic-conventions": "^1.29.0"
|
||||
},
|
||||
"engines": {
|
||||
@@ -478,13 +478,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@opentelemetry/sdk-metrics": {
|
||||
"version": "2.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.8.0.tgz",
|
||||
"integrity": "sha512-UDBGaj6W0Rgy5rTTaoxs8gVGF/aGkAKyjurJv7se6wjRxJu7FoquTLT/vt54DZfo4crbprYfhX/SOK9+BPw1qg==",
|
||||
"version": "2.7.1",
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.7.1.tgz",
|
||||
"integrity": "sha512-MpDJdkiFDs3Pm1RHO3KByuZbuBdJEXEAkiC0+yJdsZGVCdf1RpHR6n+LHDcS7ffmfrt5kVCzJSCfm4z2C7v0uQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@opentelemetry/core": "2.8.0",
|
||||
"@opentelemetry/resources": "2.8.0"
|
||||
"@opentelemetry/core": "2.7.1",
|
||||
"@opentelemetry/resources": "2.7.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.19.0 || >=20.6.0"
|
||||
@@ -494,36 +494,35 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@opentelemetry/sdk-node": {
|
||||
"version": "0.219.0",
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/sdk-node/-/sdk-node-0.219.0.tgz",
|
||||
"integrity": "sha512-NWLpWLEb8gV3+JBHYoIrktbM385wyHpRJoh3J/4Q52d4PR+AlPMNGJT3DzBUrDSUEVbKAXoHR+EDAPxtiNcj8g==",
|
||||
"version": "0.218.0",
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/sdk-node/-/sdk-node-0.218.0.tgz",
|
||||
"integrity": "sha512-tPMjHrLV5gsfNdYqoRHjeGbCAZBXXD9c1Qo/2ut7VwnUABDNh76xNxrT0SEhkIIJuCN45bbN1vZnYL1gY0IkOg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@opentelemetry/api-logs": "0.219.0",
|
||||
"@opentelemetry/configuration": "0.219.0",
|
||||
"@opentelemetry/context-async-hooks": "2.8.0",
|
||||
"@opentelemetry/core": "2.8.0",
|
||||
"@opentelemetry/exporter-logs-otlp-grpc": "0.219.0",
|
||||
"@opentelemetry/exporter-logs-otlp-http": "0.219.0",
|
||||
"@opentelemetry/exporter-logs-otlp-proto": "0.219.0",
|
||||
"@opentelemetry/exporter-metrics-otlp-grpc": "0.219.0",
|
||||
"@opentelemetry/exporter-metrics-otlp-http": "0.219.0",
|
||||
"@opentelemetry/exporter-metrics-otlp-proto": "0.219.0",
|
||||
"@opentelemetry/exporter-prometheus": "0.219.0",
|
||||
"@opentelemetry/exporter-trace-otlp-grpc": "0.219.0",
|
||||
"@opentelemetry/exporter-trace-otlp-http": "0.219.0",
|
||||
"@opentelemetry/exporter-trace-otlp-proto": "0.219.0",
|
||||
"@opentelemetry/exporter-zipkin": "2.8.0",
|
||||
"@opentelemetry/instrumentation": "0.219.0",
|
||||
"@opentelemetry/otlp-exporter-base": "0.219.0",
|
||||
"@opentelemetry/otlp-grpc-exporter-base": "0.219.0",
|
||||
"@opentelemetry/propagator-b3": "2.8.0",
|
||||
"@opentelemetry/propagator-jaeger": "2.8.0",
|
||||
"@opentelemetry/resources": "2.8.0",
|
||||
"@opentelemetry/sdk-logs": "0.219.0",
|
||||
"@opentelemetry/sdk-metrics": "2.8.0",
|
||||
"@opentelemetry/sdk-trace-base": "2.8.0",
|
||||
"@opentelemetry/sdk-trace-node": "2.8.0",
|
||||
"@opentelemetry/api-logs": "0.218.0",
|
||||
"@opentelemetry/configuration": "0.218.0",
|
||||
"@opentelemetry/context-async-hooks": "2.7.1",
|
||||
"@opentelemetry/core": "2.7.1",
|
||||
"@opentelemetry/exporter-logs-otlp-grpc": "0.218.0",
|
||||
"@opentelemetry/exporter-logs-otlp-http": "0.218.0",
|
||||
"@opentelemetry/exporter-logs-otlp-proto": "0.218.0",
|
||||
"@opentelemetry/exporter-metrics-otlp-grpc": "0.218.0",
|
||||
"@opentelemetry/exporter-metrics-otlp-http": "0.218.0",
|
||||
"@opentelemetry/exporter-metrics-otlp-proto": "0.218.0",
|
||||
"@opentelemetry/exporter-prometheus": "0.218.0",
|
||||
"@opentelemetry/exporter-trace-otlp-grpc": "0.218.0",
|
||||
"@opentelemetry/exporter-trace-otlp-http": "0.218.0",
|
||||
"@opentelemetry/exporter-trace-otlp-proto": "0.218.0",
|
||||
"@opentelemetry/exporter-zipkin": "2.7.1",
|
||||
"@opentelemetry/instrumentation": "0.218.0",
|
||||
"@opentelemetry/otlp-exporter-base": "0.218.0",
|
||||
"@opentelemetry/propagator-b3": "2.7.1",
|
||||
"@opentelemetry/propagator-jaeger": "2.7.1",
|
||||
"@opentelemetry/resources": "2.7.1",
|
||||
"@opentelemetry/sdk-logs": "0.218.0",
|
||||
"@opentelemetry/sdk-metrics": "2.7.1",
|
||||
"@opentelemetry/sdk-trace-base": "2.7.1",
|
||||
"@opentelemetry/sdk-trace-node": "2.7.1",
|
||||
"@opentelemetry/semantic-conventions": "^1.29.0"
|
||||
},
|
||||
"engines": {
|
||||
@@ -534,13 +533,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@opentelemetry/sdk-trace-base": {
|
||||
"version": "2.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.8.0.tgz",
|
||||
"integrity": "sha512-mhU4jp+vW0mGbFRd+GeXHvmfA4aDqWjBjLC3pE5XMpLs0IE2ryYb019Ts2AQrOq67gaTF25D91+fgvEHDZEnuQ==",
|
||||
"version": "2.7.1",
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.7.1.tgz",
|
||||
"integrity": "sha512-NAYIlsF8MPUsKqJMiDQJTMPOmlbawC1Iz/omMLygZ1C9am8fTKYjTaI+OZM+WTY3t3Glo0wnOg/6/pac6RGPPw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@opentelemetry/core": "2.8.0",
|
||||
"@opentelemetry/resources": "2.8.0",
|
||||
"@opentelemetry/core": "2.7.1",
|
||||
"@opentelemetry/resources": "2.7.1",
|
||||
"@opentelemetry/semantic-conventions": "^1.29.0"
|
||||
},
|
||||
"engines": {
|
||||
@@ -551,14 +550,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@opentelemetry/sdk-trace-node": {
|
||||
"version": "2.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-node/-/sdk-trace-node-2.8.0.tgz",
|
||||
"integrity": "sha512-nZt9OGufioAc3AfoLTqA9bsAeaMJAictYDdI2VcNQ+PmT+3rfKjAZDZvgPfd8VPX0O5Bw1hdQF6kDK8VSpZiWg==",
|
||||
"version": "2.7.1",
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-node/-/sdk-trace-node-2.7.1.tgz",
|
||||
"integrity": "sha512-pCpQxU68lV+I9s9svqMyVu5iHdDDUnqUpSxqwyCU8A9ejEsSnMPCbearwsUO4yk08ZJzAIUCFuReMdVQvHrdvg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@opentelemetry/context-async-hooks": "2.8.0",
|
||||
"@opentelemetry/core": "2.8.0",
|
||||
"@opentelemetry/sdk-trace-base": "2.8.0"
|
||||
"@opentelemetry/context-async-hooks": "2.7.1",
|
||||
"@opentelemetry/core": "2.7.1",
|
||||
"@opentelemetry/sdk-trace-base": "2.7.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.19.0 || >=20.6.0"
|
||||
@@ -576,78 +575,6 @@
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/@protobufjs/aspromise": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
|
||||
"integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@protobufjs/base64": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz",
|
||||
"integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@protobufjs/codegen": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz",
|
||||
"integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@protobufjs/eventemitter": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.1.tgz",
|
||||
"integrity": "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@protobufjs/fetch": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.1.tgz",
|
||||
"integrity": "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"@protobufjs/aspromise": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@protobufjs/float": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz",
|
||||
"integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@protobufjs/inquire": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.2.tgz",
|
||||
"integrity": "sha512-pa0vFRuws4wkvaXKK1uXZMAwAX4/t8ANaJo45iw/oQHNQ9q5xUzwgFmVJGXiga2BeN+zpX7Vf9vmsiIa2J+MUw==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@protobufjs/path": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz",
|
||||
"integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@protobufjs/pool": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz",
|
||||
"integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@protobufjs/utf8": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.1.tgz",
|
||||
"integrity": "sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "25.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.1.tgz",
|
||||
"integrity": "sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": ">=7.24.0 <7.24.7"
|
||||
}
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
"version": "8.16.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
|
||||
@@ -821,23 +748,12 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/protobufjs": {
|
||||
"version": "7.6.3",
|
||||
"resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.3.tgz",
|
||||
"integrity": "sha512-+k0vdJKNdW+Vu+dYe8tZA/VvQb6XKNWexC6URwBFXxNnjLJz9nQJCemGyNgRAWD+B7+nGNc9qMPGwcD7s4nzUw==",
|
||||
"version": "8.4.1",
|
||||
"resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-8.4.1.tgz",
|
||||
"integrity": "sha512-oXf2UgIty8jnwfN4yvL1x79VLhL5uiKjZJbSGXGCIUmHmItTP4eS/UIlWDCeNx3seg+ujfn9vDlPMSrsh7wO+Q==",
|
||||
"hasInstallScript": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"@protobufjs/aspromise": "^1.1.2",
|
||||
"@protobufjs/base64": "^1.1.2",
|
||||
"@protobufjs/codegen": "^2.0.5",
|
||||
"@protobufjs/eventemitter": "^1.1.1",
|
||||
"@protobufjs/fetch": "^1.1.1",
|
||||
"@protobufjs/float": "^1.0.2",
|
||||
"@protobufjs/inquire": "^1.1.2",
|
||||
"@protobufjs/path": "^1.1.2",
|
||||
"@protobufjs/pool": "^1.1.0",
|
||||
"@protobufjs/utf8": "^1.1.1",
|
||||
"@types/node": ">=13.7.0",
|
||||
"long": "^5.3.2"
|
||||
},
|
||||
"engines": {
|
||||
@@ -892,12 +808,6 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "7.24.6",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz",
|
||||
"integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/wrap-ansi": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@openclaw/diagnostics-otel",
|
||||
"version": "2026.6.8",
|
||||
"version": "2026.6.9",
|
||||
"description": "OpenClaw diagnostics OpenTelemetry exporter for metrics and traces.",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -9,15 +9,15 @@
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@opentelemetry/api": "1.9.1",
|
||||
"@opentelemetry/api-logs": "0.219.0",
|
||||
"@opentelemetry/exporter-logs-otlp-proto": "0.219.0",
|
||||
"@opentelemetry/exporter-metrics-otlp-proto": "0.219.0",
|
||||
"@opentelemetry/exporter-trace-otlp-proto": "0.219.0",
|
||||
"@opentelemetry/resources": "2.8.0",
|
||||
"@opentelemetry/sdk-logs": "0.219.0",
|
||||
"@opentelemetry/sdk-metrics": "2.8.0",
|
||||
"@opentelemetry/sdk-node": "0.219.0",
|
||||
"@opentelemetry/sdk-trace-base": "2.8.0",
|
||||
"@opentelemetry/api-logs": "0.218.0",
|
||||
"@opentelemetry/exporter-logs-otlp-proto": "0.218.0",
|
||||
"@opentelemetry/exporter-metrics-otlp-proto": "0.218.0",
|
||||
"@opentelemetry/exporter-trace-otlp-proto": "0.218.0",
|
||||
"@opentelemetry/resources": "2.7.1",
|
||||
"@opentelemetry/sdk-logs": "0.218.0",
|
||||
"@opentelemetry/sdk-metrics": "2.7.1",
|
||||
"@opentelemetry/sdk-node": "0.218.0",
|
||||
"@opentelemetry/sdk-trace-base": "2.7.1",
|
||||
"@opentelemetry/semantic-conventions": "1.41.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -34,10 +34,10 @@
|
||||
"minHostVersion": ">=2026.4.25"
|
||||
},
|
||||
"compat": {
|
||||
"pluginApi": ">=2026.6.8"
|
||||
"pluginApi": ">=2026.6.9"
|
||||
},
|
||||
"build": {
|
||||
"openclawVersion": "2026.6.8"
|
||||
"openclawVersion": "2026.6.9"
|
||||
},
|
||||
"release": {
|
||||
"publishToClawHub": true,
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@openclaw/diagnostics-prometheus",
|
||||
"version": "2026.6.8",
|
||||
"version": "2026.6.9",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@openclaw/diagnostics-prometheus",
|
||||
"version": "2026.6.8"
|
||||
"version": "2026.6.9"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@openclaw/diagnostics-prometheus",
|
||||
"version": "2026.6.8",
|
||||
"version": "2026.6.9",
|
||||
"description": "OpenClaw diagnostics Prometheus exporter for runtime metrics.",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -21,10 +21,10 @@
|
||||
"minHostVersion": ">=2026.4.25"
|
||||
},
|
||||
"compat": {
|
||||
"pluginApi": ">=2026.6.8"
|
||||
"pluginApi": ">=2026.6.9"
|
||||
},
|
||||
"build": {
|
||||
"openclawVersion": "2026.6.8"
|
||||
"openclawVersion": "2026.6.9"
|
||||
},
|
||||
"release": {
|
||||
"publishToClawHub": true,
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@openclaw/diffs-language-pack",
|
||||
"version": "2026.6.8",
|
||||
"version": "2026.6.9",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@openclaw/diffs-language-pack",
|
||||
"version": "2026.6.8"
|
||||
"version": "2026.6.9"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user