chore: update dependencies and migrate major contracts (#112963)

* build(deps): complete latest dependency migrations

* fix(deps): satisfy updated dependency types

* fix(deps): hold incompatible build tooling

* fix(deps): preserve portable tooling contracts

* build(deps): allow reviewed fresh transitive releases

* fix(deps): repair major upgrade validation

* build(deps): regenerate current dependency graph

* fix(logging): keep tslog adapter type private

* fix(agents): narrow grep subprocess handle

* fix(codex): prefer pinned managed binary

* fix(codex): fence managed native provenance

* build(deps): align codex ACP with managed harness

* fix(slack): use socket-mode Undici runtime

* fix(slack): detect cross-runtime responses

* fix(slack): bridge package-owned fetch types

* fix(deps): retain tslog v4 JSON contract

* build(plugin-sdk): refresh logging API manifest
This commit is contained in:
Peter Steinberger 2026-07-23 21:21:01 -07:00 committed by GitHub
parent 9fc1f0b96b
commit 3b7b2a2a1f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
64 changed files with 1563 additions and 948 deletions

View file

@ -272,11 +272,17 @@ jobs:
if ! docker buildx version >/dev/null 2>&1; then
arch="$(uname -m)"
case "$arch" in
aarch64|arm64) buildx_arch=arm64 ;;
x86_64|amd64) buildx_arch=amd64 ;;
aarch64|arm64)
buildx_arch=arm64
buildx_sha256=c4248d6cbc4a619a7e0b4609c11e509ad4ac0b475e1c64817c0ac20c5d90c766
;;
x86_64|amd64)
buildx_arch=amd64
buildx_sha256=d41ece72044243b4f58b343441ae37446d9c29a7d6b5e11c61847bbcf8f7dfda
;;
*) echo "unsupported buildx arch: $arch" >&2; exit 2 ;;
esac
buildx_version="${DOCKER_BUILDX_VERSION:-v0.15.1}"
buildx_version="v0.35.0"
mkdir -p "$HOME/.docker/cli-plugins"
curl --fail --show-error --location \
--connect-timeout "${OPENCLAW_CRABBOX_HYDRATE_DOWNLOAD_CONNECT_TIMEOUT_SECONDS:-15}" \
@ -286,6 +292,7 @@ jobs:
--retry-all-errors \
"https://github.com/docker/buildx/releases/download/${buildx_version}/buildx-${buildx_version}.linux-${buildx_arch}" \
-o "$HOME/.docker/cli-plugins/docker-buildx"
echo "${buildx_sha256} $HOME/.docker/cli-plugins/docker-buildx" | sha256sum -c -
chmod 0755 "$HOME/.docker/cli-plugins/docker-buildx"
fi

View file

@ -67,8 +67,8 @@ concurrency:
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
OCM_VERSION: v0.2.25
OCM_LINUX_X64_SHA256: 57530199d21eb5bfa29695749928b40fd2869484c7edff69b7c65bfc84f2f1aa
OCM_VERSION: v0.2.29
OCM_LINUX_X64_SHA256: d966098d6ba2bc10891be3c76e162a37b07f28c4f51da75d2eb509886eb7e1cf
KOVA_REPOSITORY: openclaw/Kova
PERFORMANCE_MODEL_ID: gpt-5.6-luna
# Release matrices cold-build the candidate runtime before measurement.

View file

@ -32,11 +32,11 @@ jobs:
- name: Install opengrep
env:
# Pin both the install script (by commit SHA) and the binary version.
# The script SHA must match the v1.22.0 release tag in opengrep/opengrep
# The script SHA must match the v1.25.0 release tag in opengrep/opengrep
# so a compromised or force-pushed `main` cannot RCE in our CI runner.
# Bump both together when upgrading.
OPENGREP_VERSION: v1.22.0
OPENGREP_INSTALL_SHA: f458d7f0d52cc58eae1ca3cf3d5caf101e637519
OPENGREP_VERSION: v1.25.0
OPENGREP_INSTALL_SHA: 27b5fba1b315fbc63d0fd474d01f5d8235cc86e2
run: |
# Download first so a timed-out transfer cannot execute a partial installer.
installer="$(mktemp "${RUNNER_TEMP}/opengrep-install.XXXXXX")"

View file

@ -58,11 +58,11 @@ jobs:
- name: Install opengrep
env:
# Pin both the install script (by commit SHA) and the binary version.
# The script SHA must match the v1.22.0 release tag in opengrep/opengrep
# The script SHA must match the v1.25.0 release tag in opengrep/opengrep
# so a compromised or force-pushed `main` cannot RCE in our CI runner.
# Bump both together when upgrading.
OPENGREP_VERSION: v1.22.0
OPENGREP_INSTALL_SHA: f458d7f0d52cc58eae1ca3cf3d5caf101e637519
OPENGREP_VERSION: v1.25.0
OPENGREP_INSTALL_SHA: 27b5fba1b315fbc63d0fd474d01f5d8235cc86e2
run: |
# Download first so a timed-out transfer cannot execute a partial installer.
installer="$(mktemp "${RUNNER_TEMP}/opengrep-install.XXXXXX")"

View file

@ -180,7 +180,7 @@ jobs:
shell: bash
run: |
set -euo pipefail
ACTIONLINT_VERSION="1.7.11"
ACTIONLINT_VERSION="1.7.12"
archive="actionlint_${ACTIONLINT_VERSION}_linux_amd64.tar.gz"
base_url="https://github.com/rhysd/actionlint/releases/download/v${ACTIONLINT_VERSION}"
# GitHub release downloads occasionally return transient 5xx responses.

View file

@ -31,13 +31,13 @@ repos:
# GitHub Actions linting
- repo: https://github.com/rhysd/actionlint
rev: v1.7.10
rev: v1.7.12
hooks:
- id: actionlint
# GitHub Actions security audit
- repo: https://github.com/zizmorcore/zizmor-pre-commit
rev: v1.22.0
rev: v1.28.0
hooks:
- id: zizmor
args:
@ -52,7 +52,7 @@ repos:
# Python checks for skills scripts
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.14.1
rev: v0.15.22
hooks:
- id: ruff
files: "^skills/.*\\.py$"

View file

@ -131,7 +131,7 @@ flags, and plugin allow/deny references into this block. Explicit canonical
## App-server transport
For ordinary harness turns, OpenClaw starts the managed Codex binary shipped
with the official plugin (currently `@openai/codex` `0.144.6`):
with the official plugin (currently `@openai/codex` `0.145.0`):
```bash
codex app-server --listen stdio://
@ -255,7 +255,7 @@ networking, so a full-access profile would not protect outbound traffic.
The plugin blocks older, newer-unvalidated, prerelease, build-suffixed, or
unversioned app-server handshakes. Codex app-server must report a stable version
from `0.143.0` through the bundled `0.144.6`.
from `0.143.0` through the bundled `0.145.0`.
OpenClaw treats non-loopback WebSocket app-server URLs as remote and requires
identity-bearing WebSocket auth through `appServer.authToken` or an
@ -605,7 +605,7 @@ If discovery fails or times out, OpenClaw uses a bundled fallback catalog:
| `gpt-5.4-mini` | GPT-5.4-Mini | low, medium, high, xhigh |
<Note>
The current bundled harness is `@openai/codex` `0.144.6`. A `model/list` probe
The current bundled harness is `@openai/codex` `0.145.0`. A `model/list` probe
against that bundled app-server returned these public picker rows:
| Model id | Input modalities | Reasoning efforts |
@ -614,8 +614,6 @@ against that bundled app-server returned these public picker rows:
| `gpt-5.6-terra` | text, image | low, medium, high, xhigh, max, ultra |
| `gpt-5.6-luna` | text, image | low, medium, high, xhigh, max |
| `gpt-5.5` | text, image | low, medium, high, xhigh |
| `gpt-5.4` | text, image | low, medium, high, xhigh |
| `gpt-5.4-mini` | text, image | low, medium, high, xhigh |
| `gpt-5.2` | text, image | low, medium, high, xhigh |
The app-server catalog can report `ultra`; OpenClaw reasoning controls currently

View file

@ -56,7 +56,7 @@ channel is the communication surface.
- The official `@openclaw/codex` plugin installed. Include `codex` in
`plugins.allow` if your config uses an allowlist.
- A stable Codex app-server from `0.143.0` through `0.144.6`. The plugin manages a compatible
- A stable Codex app-server from `0.143.0` through `0.145.0`. The plugin manages a compatible
binary by default, so a `codex` command on `PATH` does not affect normal
startup.
- Codex auth through `openclaw models auth login --provider openai`, an
@ -1108,7 +1108,7 @@ Doctor rewrites legacy model refs to `openai/*`, removes stale session and
whole-agent runtime pins, and preserves existing auth-profile overrides.
**The app-server is rejected:** use a stable Codex app-server from `0.143.0`
through the bundled `0.144.6`. Prereleases, build-suffixed versions, and newer
through the bundled `0.145.0`. Prereleases, build-suffixed versions, and newer
unvalidated releases are rejected because OpenClaw validates generated schemas
against the bundled app-server version.

View file

@ -9,7 +9,7 @@
"version": "2026.7.2",
"dependencies": {
"@agentclientprotocol/claude-agent-acp": "0.59.0",
"@agentclientprotocol/codex-acp": "1.1.4",
"@agentclientprotocol/codex-acp": "1.1.7",
"acpx": "0.12.0",
"smol-toml": "1.7.0",
"zod": "4.4.3"
@ -33,13 +33,13 @@
}
},
"node_modules/@agentclientprotocol/codex-acp": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/@agentclientprotocol/codex-acp/-/codex-acp-1.1.4.tgz",
"integrity": "sha512-DzusIpGwlQwMWuHgJhU8FWMsyQvzjenB93IEzQATkdbNulo5Rd9GKOz8+B+/C9iWWxmyXgtgmjzaL+iRFyDryQ==",
"version": "1.1.7",
"resolved": "https://registry.npmjs.org/@agentclientprotocol/codex-acp/-/codex-acp-1.1.7.tgz",
"integrity": "sha512-bhFLbGtOMEw6+PAp33vNERb6dXlULOfV3mWbRdps4v7sY7PHha/C2T1dnlG0yVcvBu9W+NYPzL0CAupnVoFTiQ==",
"license": "Apache-2.0",
"dependencies": {
"@agentclientprotocol/sdk": "^1.2.1",
"@openai/codex": "^0.144.4",
"@agentclientprotocol/sdk": "^1.3.0",
"@openai/codex": "^0.145.0",
"diff": "^9.0.0",
"open": "^11.0.0",
"vscode-jsonrpc": "^9.0.1",
@ -49,6 +49,15 @@
"codex-acp": "dist/index.js"
}
},
"node_modules/@agentclientprotocol/codex-acp/node_modules/@agentclientprotocol/sdk": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@agentclientprotocol/sdk/-/sdk-1.3.0.tgz",
"integrity": "sha512-i3h/efaeuMUFAO1HSfo97QZQnnvMd7wWBYtBsdL6UMZg3a78sk3Ffya5Xu7C7tYsXomXoDXJBAzQF2PcFKAhIQ==",
"license": "Apache-2.0",
"peerDependencies": {
"zod": "^3.25.0 || ^4.0.0"
}
},
"node_modules/@agentclientprotocol/sdk": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@agentclientprotocol/sdk/-/sdk-1.2.1.tgz",
@ -713,9 +722,9 @@
}
},
"node_modules/@openai/codex": {
"version": "0.144.6",
"resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.144.6.tgz",
"integrity": "sha512-wk+2CWiBNXiJLBoN2D08N9RceWkSBnlgk5g2K1a4CXrP/C0gdlHyRUG7RFzm9y41DCK/7tvCct233JVxyFmznw==",
"version": "0.145.0",
"resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.145.0.tgz",
"integrity": "sha512-/PSPSFujjjmiyVFvG2yu/grOFhsWdokTH8t2KGWhXSo/M5n/dIDsnbsnO82/7bLtIoDuzQf7ATBUMWqPWQINlQ==",
"license": "Apache-2.0",
"bin": {
"codex": "bin/codex.js"
@ -724,19 +733,19 @@
"node": ">=16"
},
"optionalDependencies": {
"@openai/codex-darwin-arm64": "npm:@openai/codex@0.144.6-darwin-arm64",
"@openai/codex-darwin-x64": "npm:@openai/codex@0.144.6-darwin-x64",
"@openai/codex-linux-arm64": "npm:@openai/codex@0.144.6-linux-arm64",
"@openai/codex-linux-x64": "npm:@openai/codex@0.144.6-linux-x64",
"@openai/codex-win32-arm64": "npm:@openai/codex@0.144.6-win32-arm64",
"@openai/codex-win32-x64": "npm:@openai/codex@0.144.6-win32-x64"
"@openai/codex-darwin-arm64": "npm:@openai/codex@0.145.0-darwin-arm64",
"@openai/codex-darwin-x64": "npm:@openai/codex@0.145.0-darwin-x64",
"@openai/codex-linux-arm64": "npm:@openai/codex@0.145.0-linux-arm64",
"@openai/codex-linux-x64": "npm:@openai/codex@0.145.0-linux-x64",
"@openai/codex-win32-arm64": "npm:@openai/codex@0.145.0-win32-arm64",
"@openai/codex-win32-x64": "npm:@openai/codex@0.145.0-win32-x64"
}
},
"node_modules/@openai/codex-darwin-arm64": {
"name": "@openai/codex",
"version": "0.144.6-darwin-arm64",
"resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.144.6-darwin-arm64.tgz",
"integrity": "sha512-6zgvh70MzBNSeT17HEhSOrmmGGZGAKzSC7x6JAq+edkJkdPYA9P0I1tG7aJ49GlBkBxuC+MKBH1qm6+2Cghcww==",
"version": "0.145.0-darwin-arm64",
"resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.145.0-darwin-arm64.tgz",
"integrity": "sha512-h6aQ0UxnaP8mIM/9/qPAH9MNkRliJo88toq1T36IxNM2L5JSU0TFamu+MZn7YkFgDsrp0RfiI+97Tm8AVVxqtA==",
"cpu": [
"arm64"
],
@ -751,9 +760,9 @@
},
"node_modules/@openai/codex-darwin-x64": {
"name": "@openai/codex",
"version": "0.144.6-darwin-x64",
"resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.144.6-darwin-x64.tgz",
"integrity": "sha512-THRyPG0zSU6M8NQAge1LHEHsJDnoH4BpKsfJHB/qe3Fm+Wf6zqAmWJFlOKzBm27m0K2Hq3za4Ac2I5p5i4yp/A==",
"version": "0.145.0-darwin-x64",
"resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.145.0-darwin-x64.tgz",
"integrity": "sha512-FCYzVKCa9VoLtg9gVyzKpqylonfgZrfcWZN6HsXAZPeuo8CukdMqdgTUOhDn2V6h3MbqS0z6VqQVKUllN/yKhA==",
"cpu": [
"x64"
],
@ -768,9 +777,9 @@
},
"node_modules/@openai/codex-linux-arm64": {
"name": "@openai/codex",
"version": "0.144.6-linux-arm64",
"resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.144.6-linux-arm64.tgz",
"integrity": "sha512-PGiLXMN+2IQRkf7tOLi64dMInjU1pRLbz0Rwfj/yt2Y97SZQqAjFQoi2wmswmqtqMDnfwCPTC1DRXVQkvU6T6Q==",
"version": "0.145.0-linux-arm64",
"resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.145.0-linux-arm64.tgz",
"integrity": "sha512-8OLcPXaAol/FOrRoDxWhIiHIFa73KRsM41EKocjRZOwiT4TcelzJWn3dHyiuSb7teWF25rrslvSPyvhULYRRCQ==",
"cpu": [
"arm64"
],
@ -785,9 +794,9 @@
},
"node_modules/@openai/codex-linux-x64": {
"name": "@openai/codex",
"version": "0.144.6-linux-x64",
"resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.144.6-linux-x64.tgz",
"integrity": "sha512-4E7EnzCg0OnBxCyYnwJ+qnZwWHYe0YScr5ucKWbngE9u4+0XrpWELqq2Kn9jl5GZK8MDjU7PrJwFIwusHOHjuw==",
"version": "0.145.0-linux-x64",
"resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.145.0-linux-x64.tgz",
"integrity": "sha512-u8w8LLv3DvsfrDCoswLIemZ0SoNEXyi511WsfFsSiYUazk9qMsB/NtU8N9vhAfN7mZAxLFoMex4v66JjHuZWwA==",
"cpu": [
"x64"
],
@ -802,9 +811,9 @@
},
"node_modules/@openai/codex-win32-arm64": {
"name": "@openai/codex",
"version": "0.144.6-win32-arm64",
"resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.144.6-win32-arm64.tgz",
"integrity": "sha512-SpMjXJLW43JzMP0K62mVcYfmFcpk0BK4AOgYmWSfyZHs3iRtHMd0UYw7605n/9lwkT2EqbwQLT2omZFeKJFzwA==",
"version": "0.145.0-win32-arm64",
"resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.145.0-win32-arm64.tgz",
"integrity": "sha512-sub61rjEFevi1i3Zx7nAd4JM5XxoNFqMqFc5LfTo2xSI8ixHjFvEYDFDXwXOftT04n3Ht1Wh271ioUZpDiEjEg==",
"cpu": [
"arm64"
],
@ -819,9 +828,9 @@
},
"node_modules/@openai/codex-win32-x64": {
"name": "@openai/codex",
"version": "0.144.6-win32-x64",
"resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.144.6-win32-x64.tgz",
"integrity": "sha512-dN39VnjEthKz5io1RNWwZDtErdSn07nW3pGUgvlA6DMxgm/nuGaIAZO/sG/Hgxq/x5j9HteAENfrFgVkpZ0lFg==",
"version": "0.145.0-win32-x64",
"resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.145.0-win32-x64.tgz",
"integrity": "sha512-u0h9lk094CaXRSqE34SBW2dRaQTPa6fASXqehczWH9QdsU62mBsiAgAdp6tCG4i+YzPmmhjD8FdXNnYGNmwuMg==",
"cpu": [
"x64"
],

View file

@ -9,7 +9,7 @@
"type": "module",
"dependencies": {
"@agentclientprotocol/claude-agent-acp": "0.59.0",
"@agentclientprotocol/codex-acp": "1.1.4",
"@agentclientprotocol/codex-acp": "1.1.7",
"acpx": "0.12.0",
"smol-toml": "1.7.0",
"zod": "4.4.3"

View file

@ -220,7 +220,7 @@ describe("prepareAcpxCodexAuthConfig", () => {
});
const wrapper = await fs.readFile(generated.wrapperPath, "utf8");
expect(wrapper).toContain('"@agentclientprotocol/codex-acp@1.1.4"');
expect(wrapper).toContain('"@agentclientprotocol/codex-acp@1.1.7"');
expect(wrapper).toContain('"--", "codex-acp"');
expect(wrapper).not.toContain("@zed-industries/codex-acp");
});

View file

@ -20,7 +20,7 @@ describe("acpx package manifest", () => {
it("keeps runtime dependencies in the package manifest", () => {
expect(packageJson.dependencies?.acpx).toBeTypeOf("string");
expect(packageJson.dependencies?.acpx).not.toBe("");
expect(packageJson.dependencies?.["@agentclientprotocol/codex-acp"]).toBe("1.1.4");
expect(packageJson.dependencies?.["@agentclientprotocol/codex-acp"]).toBe("1.1.7");
expect(packageJson.dependencies?.["@zed-industries/codex-acp"]).toBeUndefined();
expect(packageJson.dependencies?.["@agentclientprotocol/claude-agent-acp"]).toBe("0.59.0");
expect(packageJson.devDependencies?.["@agentclientprotocol/claude-agent-acp"]).toBeUndefined();

View file

@ -8,7 +8,7 @@
"name": "@openclaw/codex",
"version": "2026.7.2",
"dependencies": {
"@openai/codex": "0.144.6",
"@openai/codex": "0.145.0",
"semver": "7.8.5",
"smol-toml": "1.7.0",
"typebox": "1.3.6",
@ -17,9 +17,9 @@
}
},
"node_modules/@openai/codex": {
"version": "0.144.6",
"resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.144.6.tgz",
"integrity": "sha512-wk+2CWiBNXiJLBoN2D08N9RceWkSBnlgk5g2K1a4CXrP/C0gdlHyRUG7RFzm9y41DCK/7tvCct233JVxyFmznw==",
"version": "0.145.0",
"resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.145.0.tgz",
"integrity": "sha512-/PSPSFujjjmiyVFvG2yu/grOFhsWdokTH8t2KGWhXSo/M5n/dIDsnbsnO82/7bLtIoDuzQf7ATBUMWqPWQINlQ==",
"license": "Apache-2.0",
"bin": {
"codex": "bin/codex.js"
@ -28,19 +28,19 @@
"node": ">=16"
},
"optionalDependencies": {
"@openai/codex-darwin-arm64": "npm:@openai/codex@0.144.6-darwin-arm64",
"@openai/codex-darwin-x64": "npm:@openai/codex@0.144.6-darwin-x64",
"@openai/codex-linux-arm64": "npm:@openai/codex@0.144.6-linux-arm64",
"@openai/codex-linux-x64": "npm:@openai/codex@0.144.6-linux-x64",
"@openai/codex-win32-arm64": "npm:@openai/codex@0.144.6-win32-arm64",
"@openai/codex-win32-x64": "npm:@openai/codex@0.144.6-win32-x64"
"@openai/codex-darwin-arm64": "npm:@openai/codex@0.145.0-darwin-arm64",
"@openai/codex-darwin-x64": "npm:@openai/codex@0.145.0-darwin-x64",
"@openai/codex-linux-arm64": "npm:@openai/codex@0.145.0-linux-arm64",
"@openai/codex-linux-x64": "npm:@openai/codex@0.145.0-linux-x64",
"@openai/codex-win32-arm64": "npm:@openai/codex@0.145.0-win32-arm64",
"@openai/codex-win32-x64": "npm:@openai/codex@0.145.0-win32-x64"
}
},
"node_modules/@openai/codex-darwin-arm64": {
"name": "@openai/codex",
"version": "0.144.6-darwin-arm64",
"resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.144.6-darwin-arm64.tgz",
"integrity": "sha512-6zgvh70MzBNSeT17HEhSOrmmGGZGAKzSC7x6JAq+edkJkdPYA9P0I1tG7aJ49GlBkBxuC+MKBH1qm6+2Cghcww==",
"version": "0.145.0-darwin-arm64",
"resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.145.0-darwin-arm64.tgz",
"integrity": "sha512-h6aQ0UxnaP8mIM/9/qPAH9MNkRliJo88toq1T36IxNM2L5JSU0TFamu+MZn7YkFgDsrp0RfiI+97Tm8AVVxqtA==",
"cpu": [
"arm64"
],
@ -55,9 +55,9 @@
},
"node_modules/@openai/codex-darwin-x64": {
"name": "@openai/codex",
"version": "0.144.6-darwin-x64",
"resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.144.6-darwin-x64.tgz",
"integrity": "sha512-THRyPG0zSU6M8NQAge1LHEHsJDnoH4BpKsfJHB/qe3Fm+Wf6zqAmWJFlOKzBm27m0K2Hq3za4Ac2I5p5i4yp/A==",
"version": "0.145.0-darwin-x64",
"resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.145.0-darwin-x64.tgz",
"integrity": "sha512-FCYzVKCa9VoLtg9gVyzKpqylonfgZrfcWZN6HsXAZPeuo8CukdMqdgTUOhDn2V6h3MbqS0z6VqQVKUllN/yKhA==",
"cpu": [
"x64"
],
@ -72,9 +72,9 @@
},
"node_modules/@openai/codex-linux-arm64": {
"name": "@openai/codex",
"version": "0.144.6-linux-arm64",
"resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.144.6-linux-arm64.tgz",
"integrity": "sha512-PGiLXMN+2IQRkf7tOLi64dMInjU1pRLbz0Rwfj/yt2Y97SZQqAjFQoi2wmswmqtqMDnfwCPTC1DRXVQkvU6T6Q==",
"version": "0.145.0-linux-arm64",
"resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.145.0-linux-arm64.tgz",
"integrity": "sha512-8OLcPXaAol/FOrRoDxWhIiHIFa73KRsM41EKocjRZOwiT4TcelzJWn3dHyiuSb7teWF25rrslvSPyvhULYRRCQ==",
"cpu": [
"arm64"
],
@ -89,9 +89,9 @@
},
"node_modules/@openai/codex-linux-x64": {
"name": "@openai/codex",
"version": "0.144.6-linux-x64",
"resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.144.6-linux-x64.tgz",
"integrity": "sha512-4E7EnzCg0OnBxCyYnwJ+qnZwWHYe0YScr5ucKWbngE9u4+0XrpWELqq2Kn9jl5GZK8MDjU7PrJwFIwusHOHjuw==",
"version": "0.145.0-linux-x64",
"resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.145.0-linux-x64.tgz",
"integrity": "sha512-u8w8LLv3DvsfrDCoswLIemZ0SoNEXyi511WsfFsSiYUazk9qMsB/NtU8N9vhAfN7mZAxLFoMex4v66JjHuZWwA==",
"cpu": [
"x64"
],
@ -106,9 +106,9 @@
},
"node_modules/@openai/codex-win32-arm64": {
"name": "@openai/codex",
"version": "0.144.6-win32-arm64",
"resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.144.6-win32-arm64.tgz",
"integrity": "sha512-SpMjXJLW43JzMP0K62mVcYfmFcpk0BK4AOgYmWSfyZHs3iRtHMd0UYw7605n/9lwkT2EqbwQLT2omZFeKJFzwA==",
"version": "0.145.0-win32-arm64",
"resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.145.0-win32-arm64.tgz",
"integrity": "sha512-sub61rjEFevi1i3Zx7nAd4JM5XxoNFqMqFc5LfTo2xSI8ixHjFvEYDFDXwXOftT04n3Ht1Wh271ioUZpDiEjEg==",
"cpu": [
"arm64"
],
@ -123,9 +123,9 @@
},
"node_modules/@openai/codex-win32-x64": {
"name": "@openai/codex",
"version": "0.144.6-win32-x64",
"resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.144.6-win32-x64.tgz",
"integrity": "sha512-dN39VnjEthKz5io1RNWwZDtErdSn07nW3pGUgvlA6DMxgm/nuGaIAZO/sG/Hgxq/x5j9HteAENfrFgVkpZ0lFg==",
"version": "0.145.0-win32-x64",
"resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.145.0-win32-x64.tgz",
"integrity": "sha512-u0h9lk094CaXRSqE34SBW2dRaQTPa6fASXqehczWH9QdsU62mBsiAgAdp6tCG4i+YzPmmhjD8FdXNnYGNmwuMg==",
"cpu": [
"x64"
],

View file

@ -8,7 +8,7 @@
},
"type": "module",
"dependencies": {
"@openai/codex": "0.144.6",
"@openai/codex": "0.145.0",
"semver": "7.8.5",
"smol-toml": "1.7.0",
"typebox": "1.3.6",

View file

@ -48,20 +48,16 @@
},
{
"properties": {
"credentialSource": {
"allOf": [
{
"$ref": "#/definitions/AmazonBedrockCredentialSource"
}
],
"default": "awsManaged"
},
"type": {
"enum": [
"amazonBedrock"
],
"title": "AmazonBedrockAccountType",
"type": "string"
},
"usesCodexManagedCredentials": {
"default": false,
"type": "boolean"
}
},
"required": [
@ -72,13 +68,6 @@
}
]
},
"AmazonBedrockCredentialSource": {
"enum": [
"codexManaged",
"awsManaged"
],
"type": "string"
},
"PlanType": {
"enum": [
"free",

View file

@ -17,6 +17,13 @@
"image"
],
"type": "string"
},
{
"description": "Audio attachments included in user turns.",
"enum": [
"audio"
],
"type": "string"
}
]
},

View file

@ -459,6 +459,26 @@
],
"title": "InputImageDynamicToolCallOutputContentItem",
"type": "object"
},
{
"properties": {
"audioUrl": {
"type": "string"
},
"type": {
"enum": [
"inputAudio"
],
"title": "InputAudioDynamicToolCallOutputContentItemType",
"type": "string"
}
},
"required": [
"audioUrl",
"type"
],
"title": "InputAudioDynamicToolCallOutputContentItem",
"type": "object"
}
]
},
@ -567,12 +587,6 @@
"string",
"null"
]
},
"templateId": {
"type": [
"string",
"null"
]
}
},
"required": [
@ -1053,6 +1067,13 @@
"null"
]
},
"canAcceptDirectInput": {
"description": "Whether the app server accepts direct turn input for this loaded thread. `None` means the capability is unavailable, such as for an unloaded stored thread.",
"type": [
"boolean",
"null"
]
},
"cliVersion": {
"description": "Version of the CLI that created the thread.",
"type": "string"
@ -1795,6 +1816,15 @@
"query": {
"type": "string"
},
"results": {
"default": null,
"description": "Structured search results returned out-of-band by standalone web search.\n\nThese stay as opaque JSON at the extension/app-server boundary so new result fields and result types can pass through without a Codex release.",
"items": true,
"type": [
"array",
"null"
]
},
"type": {
"enum": [
"webSearch"
@ -1836,6 +1866,7 @@
"type": "object"
},
{
"description": "Display item emitted by the interruptible `clock.sleep` tool.",
"properties": {
"durationMs": {
"format": "uint64",
@ -2301,6 +2332,46 @@
"title": "LocalImageUserInput",
"type": "object"
},
{
"properties": {
"type": {
"enum": [
"audio"
],
"title": "AudioUserInputType",
"type": "string"
},
"url": {
"type": "string"
}
},
"required": [
"type",
"url"
],
"title": "AudioUserInput",
"type": "object"
},
{
"properties": {
"path": {
"type": "string"
},
"type": {
"enum": [
"localAudio"
],
"title": "LocalAudioUserInputType",
"type": "string"
}
},
"required": [
"path",
"type"
],
"title": "LocalAudioUserInput",
"type": "object"
},
{
"properties": {
"name": {
@ -2500,6 +2571,14 @@
},
"type": "array"
},
"itemsBackwardsCursor": {
"default": null,
"description": "Opaque head cursor for hydrating paginated items backwards.\n\nPass this as `cursor` to `thread/items/list` with `sortDirection: \"desc\"`. The first page includes the cursor's head item.",
"type": [
"string",
"null"
]
},
"model": {
"type": "string"
},
@ -2549,6 +2628,14 @@
},
"thread": {
"$ref": "#/definitions/Thread"
},
"turnsBackwardsCursor": {
"default": null,
"description": "Opaque head cursor for hydrating paginated turns backwards.\n\nPass this as `cursor` to `thread/turns/list` with `sortDirection: \"desc\"`. The first page includes the cursor's head turn.",
"type": [
"string",
"null"
]
}
},
"required": [

View file

@ -459,6 +459,26 @@
],
"title": "InputImageDynamicToolCallOutputContentItem",
"type": "object"
},
{
"properties": {
"audioUrl": {
"type": "string"
},
"type": {
"enum": [
"inputAudio"
],
"title": "InputAudioDynamicToolCallOutputContentItemType",
"type": "string"
}
},
"required": [
"audioUrl",
"type"
],
"title": "InputAudioDynamicToolCallOutputContentItem",
"type": "object"
}
]
},
@ -567,12 +587,6 @@
"string",
"null"
]
},
"templateId": {
"type": [
"string",
"null"
]
}
},
"required": [
@ -1053,6 +1067,13 @@
"null"
]
},
"canAcceptDirectInput": {
"description": "Whether the app server accepts direct turn input for this loaded thread. `None` means the capability is unavailable, such as for an unloaded stored thread.",
"type": [
"boolean",
"null"
]
},
"cliVersion": {
"description": "Version of the CLI that created the thread.",
"type": "string"
@ -1795,6 +1816,15 @@
"query": {
"type": "string"
},
"results": {
"default": null,
"description": "Structured search results returned out-of-band by standalone web search.\n\nThese stay as opaque JSON at the extension/app-server boundary so new result fields and result types can pass through without a Codex release.",
"items": true,
"type": [
"array",
"null"
]
},
"type": {
"enum": [
"webSearch"
@ -1836,6 +1866,7 @@
"type": "object"
},
{
"description": "Display item emitted by the interruptible `clock.sleep` tool.",
"properties": {
"durationMs": {
"format": "uint64",
@ -2275,6 +2306,46 @@
"title": "LocalImageUserInput",
"type": "object"
},
{
"properties": {
"type": {
"enum": [
"audio"
],
"title": "AudioUserInputType",
"type": "string"
},
"url": {
"type": "string"
}
},
"required": [
"type",
"url"
],
"title": "AudioUserInput",
"type": "object"
},
{
"properties": {
"path": {
"type": "string"
},
"type": {
"enum": [
"localAudio"
],
"title": "LocalAudioUserInputType",
"type": "string"
}
},
"required": [
"path",
"type"
],
"title": "LocalAudioUserInput",
"type": "object"
},
{
"properties": {
"name": {

View file

@ -378,6 +378,26 @@
],
"title": "InputImageDynamicToolCallOutputContentItem",
"type": "object"
},
{
"properties": {
"audioUrl": {
"type": "string"
},
"type": {
"enum": [
"inputAudio"
],
"title": "InputAudioDynamicToolCallOutputContentItemType",
"type": "string"
}
},
"required": [
"audioUrl",
"type"
],
"title": "InputAudioDynamicToolCallOutputContentItem",
"type": "object"
}
]
},
@ -463,12 +483,6 @@
"string",
"null"
]
},
"templateId": {
"type": [
"string",
"null"
]
}
},
"required": [
@ -1247,6 +1261,15 @@
"query": {
"type": "string"
},
"results": {
"default": null,
"description": "Structured search results returned out-of-band by standalone web search.\n\nThese stay as opaque JSON at the extension/app-server boundary so new result fields and result types can pass through without a Codex release.",
"items": true,
"type": [
"array",
"null"
]
},
"type": {
"enum": [
"webSearch"
@ -1288,6 +1311,7 @@
"type": "object"
},
{
"description": "Display item emitted by the interruptible `clock.sleep` tool.",
"properties": {
"durationMs": {
"format": "uint64",
@ -1649,6 +1673,46 @@
"title": "LocalImageUserInput",
"type": "object"
},
{
"properties": {
"type": {
"enum": [
"audio"
],
"title": "AudioUserInputType",
"type": "string"
},
"url": {
"type": "string"
}
},
"required": [
"type",
"url"
],
"title": "AudioUserInput",
"type": "object"
},
{
"properties": {
"path": {
"type": "string"
},
"type": {
"enum": [
"localAudio"
],
"title": "LocalAudioUserInputType",
"type": "string"
}
},
"required": [
"path",
"type"
],
"title": "LocalAudioUserInput",
"type": "object"
},
{
"properties": {
"name": {

View file

@ -378,6 +378,26 @@
],
"title": "InputImageDynamicToolCallOutputContentItem",
"type": "object"
},
{
"properties": {
"audioUrl": {
"type": "string"
},
"type": {
"enum": [
"inputAudio"
],
"title": "InputAudioDynamicToolCallOutputContentItemType",
"type": "string"
}
},
"required": [
"audioUrl",
"type"
],
"title": "InputAudioDynamicToolCallOutputContentItem",
"type": "object"
}
]
},
@ -463,12 +483,6 @@
"string",
"null"
]
},
"templateId": {
"type": [
"string",
"null"
]
}
},
"required": [
@ -1247,6 +1261,15 @@
"query": {
"type": "string"
},
"results": {
"default": null,
"description": "Structured search results returned out-of-band by standalone web search.\n\nThese stay as opaque JSON at the extension/app-server boundary so new result fields and result types can pass through without a Codex release.",
"items": true,
"type": [
"array",
"null"
]
},
"type": {
"enum": [
"webSearch"
@ -1288,6 +1311,7 @@
"type": "object"
},
{
"description": "Display item emitted by the interruptible `clock.sleep` tool.",
"properties": {
"durationMs": {
"format": "uint64",
@ -1649,6 +1673,46 @@
"title": "LocalImageUserInput",
"type": "object"
},
{
"properties": {
"type": {
"enum": [
"audio"
],
"title": "AudioUserInputType",
"type": "string"
},
"url": {
"type": "string"
}
},
"required": [
"type",
"url"
],
"title": "AudioUserInput",
"type": "object"
},
{
"properties": {
"path": {
"type": "string"
},
"type": {
"enum": [
"localAudio"
],
"title": "LocalAudioUserInputType",
"type": "string"
}
},
"required": [
"path",
"type"
],
"title": "LocalAudioUserInput",
"type": "object"
},
{
"properties": {
"name": {

View file

@ -257,7 +257,7 @@ describe("shared Codex app-server client", () => {
await sendInitializeResult(harness, "openclaw/0.117.9 (macOS; test)");
await expect(listPromise).rejects.toThrow(
"A stable Codex app-server from 0.143.0 through 0.144.6 is required",
"A stable Codex app-server from 0.143.0 through 0.145.0 is required",
);
expect(harness.process.stdin.destroyed).toBe(true);
startSpy.mockRestore();

View file

@ -6,6 +6,6 @@
/** Minimum Codex app-server version supported by the OpenClaw Codex bridge. */
export const MIN_CODEX_APP_SERVER_VERSION = "0.143.0";
/** Newest Codex app-server version validated by the OpenClaw Codex bridge. */
export const MAX_CODEX_APP_SERVER_VERSION = "0.144.6";
export const MAX_CODEX_APP_SERVER_VERSION = "0.145.0";
/** npm package name for the managed Codex app-server binary. */
export const MANAGED_CODEX_APP_SERVER_PACKAGE = "@openai/codex";

View file

@ -23,7 +23,7 @@
"@openclaw/slack": "workspace:*",
"@openclaw/whatsapp": "workspace:*",
"openclaw": "workspace:*",
"vite": "8.1.3"
"vite": "8.1.5"
},
"peerDependencies": {
"openclaw": ">=2026.7.2"

View file

@ -8,11 +8,13 @@
"name": "@openclaw/slack",
"version": "2026.7.2",
"dependencies": {
"@slack/bolt": "4.7.3",
"@slack/types": "2.21.1",
"@slack/web-api": "7.18.0",
"@slack/bolt": "5.0.0",
"@slack/socket-mode": "3.0.0",
"@slack/types": "3.0.0",
"@slack/web-api": "8.0.0",
"p-map": "7.0.5",
"typebox": "1.3.6",
"undici": "7.28.0",
"ws": "8.21.1",
"zod": "4.4.3"
},
@ -26,110 +28,106 @@
}
},
"node_modules/@slack/bolt": {
"version": "4.7.3",
"resolved": "https://registry.npmjs.org/@slack/bolt/-/bolt-4.7.3.tgz",
"integrity": "sha512-bODs8q/yNDWUPoxmQhFrRqLMA5vhB/PDizYWqb6CkQhLWEUo5JFtfJcmeU4ElGl6qSt++OKjSYNa4MPc77CleQ==",
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/@slack/bolt/-/bolt-5.0.0.tgz",
"integrity": "sha512-L0FTzidrDKTu4Ph8sdv0bB3acafBTAC02TBcD+hojPTTNSMX3hSK+rkpkUKkqbbc0vGpSjd6A/g71Rjy7EbBhw==",
"license": "MIT",
"dependencies": {
"@slack/logger": "^4.0.1",
"@slack/oauth": "^3.0.5",
"@slack/socket-mode": "^2.0.7",
"@slack/types": "^2.21.1",
"@slack/web-api": "^7.16.0",
"axios": "^1.12.0",
"@slack/logger": "^5.0.0",
"@slack/oauth": "^4.0.0",
"@slack/socket-mode": "^3.0.0",
"@slack/types": "^3.0.0",
"@slack/web-api": "^8.0.0",
"express": "^5.0.0",
"path-to-regexp": "^8.1.0",
"raw-body": "^3",
"tsscmp": "^1.0.6"
},
"engines": {
"node": ">=18",
"npm": ">=8.6.0"
"node": ">=20",
"npm": ">=9.6.4"
},
"peerDependencies": {
"@types/express": "^5.0.0"
}
},
"node_modules/@slack/logger": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@slack/logger/-/logger-4.0.1.tgz",
"integrity": "sha512-6cmdPrV/RYfd2U0mDGiMK8S7OJqpCTm7enMLRR3edccsPX8j7zXTLnaEF4fhxxJJTAIOil6+qZrnUPTuaLvwrQ==",
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/@slack/logger/-/logger-5.0.0.tgz",
"integrity": "sha512-VGXhmmgsAo9shdQYh4tFDndd+7nsgp0Y5h0UPDaUp8K359pBasI6YdkMqFW3mCOxLQkq09qj7o7cq6f3DuXcJQ==",
"license": "MIT",
"dependencies": {
"@types/node": ">=18"
"@types/node": ">=20"
},
"engines": {
"node": ">= 18",
"npm": ">= 8.6.0"
"node": ">= 20",
"npm": ">=9.6.4"
}
},
"node_modules/@slack/oauth": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/@slack/oauth/-/oauth-3.0.5.tgz",
"integrity": "sha512-exqFQySKhNDptWYSWhvRUJ4/+ndu2gayIy7vg/JfmJq3wGtGdHk531P96fAZyBm5c1Le3yaPYqv92rL4COlU3A==",
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@slack/oauth/-/oauth-4.0.0.tgz",
"integrity": "sha512-Aqs5bGghT+VtNcdwVOQy7CcyCfDNd9YYnZsCRukIF9p1Mxuq4uL+BjqlWQB0SeS4wzrLzePPgvj8JDuQil+ezA==",
"license": "MIT",
"dependencies": {
"@slack/logger": "^4.0.1",
"@slack/web-api": "^7.15.0",
"@slack/logger": "^5.0.0",
"@slack/web-api": "^8.0.0",
"@types/jsonwebtoken": "^9",
"@types/node": ">=18",
"@types/node": ">=20",
"jsonwebtoken": "^9"
},
"engines": {
"node": ">=18",
"npm": ">=8.6.0"
"node": ">=20",
"npm": ">=9.6.4"
}
},
"node_modules/@slack/socket-mode": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/@slack/socket-mode/-/socket-mode-2.0.7.tgz",
"integrity": "sha512-qYy07je71WnEHgRwmw12DlAnZLi5HXmdlI2WUzUK2LH/rYXQpP6uEg462S5CwfE8FoCKUdIigHtYnOOfzZH1lQ==",
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@slack/socket-mode/-/socket-mode-3.0.0.tgz",
"integrity": "sha512-QShO60SB0E+HH+TbcKj3CBEQbodToRyiXnxuSB4t1kvUlqEmuGA1nOOjrRDkDJbOECAZ13PLe4ek9SrntpfoYg==",
"license": "MIT",
"dependencies": {
"@slack/logger": "^4.0.1",
"@slack/web-api": "^7.15.0",
"@types/node": ">=18",
"@types/ws": "^8",
"eventemitter3": "^5",
"ws": "^8"
"@slack/logger": "^5.0.0",
"@slack/web-api": "^8.0.0",
"@types/node": ">=20",
"eventemitter3": "^5"
},
"engines": {
"node": ">= 18",
"npm": ">= 8.6.0"
"node": ">=20",
"npm": ">=9.6.4"
},
"peerDependencies": {
"undici": "^7.0.0"
}
},
"node_modules/@slack/types": {
"version": "2.21.1",
"resolved": "https://registry.npmjs.org/@slack/types/-/types-2.21.1.tgz",
"integrity": "sha512-I8vmSjNYWsaxuWPx6dz4yeh0h7vRBWbgAMK14LEmblbZ404BtrPbXs6jDPx4cYgGf8msDGF4A9opLZBu21FViQ==",
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@slack/types/-/types-3.0.0.tgz",
"integrity": "sha512-KNOqpnNAlsFt5Jk9XBclslQ0lobRIg/0tnhpmvZJAglHJx9E8oceN8hC3gaBzkR6UzQ9Wzq4rLsJ98wUcxWPfw==",
"license": "MIT",
"engines": {
"node": ">= 12.13.0",
"npm": ">= 6.12.0"
"node": ">= 20",
"npm": ">=9.6.4"
}
},
"node_modules/@slack/web-api": {
"version": "7.18.0",
"resolved": "https://registry.npmjs.org/@slack/web-api/-/web-api-7.18.0.tgz",
"integrity": "sha512-EWBsKUhOFFp87beQg/ToSC+asWB7BrGHuh7uPC1ZI9vr41GjS+3WmmyWIMqs+mF6U+mh4d6HhtFlv67TrJFsvw==",
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/@slack/web-api/-/web-api-8.0.0.tgz",
"integrity": "sha512-ORx3XQryQPq2Jnxv5giSKXVoQRUeylrrymIR2S9fPzLjPcCts8RayMeBSZMcpfpAqp6fnBRuPW2UB6dUPUTEZA==",
"license": "MIT",
"dependencies": {
"@slack/logger": "^4.0.1",
"@slack/types": "^2.21.0",
"@types/node": ">=18",
"@slack/logger": "^5.0.0",
"@slack/types": "^3.0.0",
"@types/node": ">=20",
"@types/retry": "0.12.0",
"axios": "^1.16.0",
"eventemitter3": "^5.0.1",
"form-data": "^4.0.4",
"is-electron": "2.2.2",
"is-stream": "^2",
"p-queue": "^6",
"p-retry": "^4",
"retry": "^0.13.1"
},
"engines": {
"node": ">= 18",
"npm": ">= 8.6.0"
"node": ">= 20",
"npm": ">=9.6.4"
}
},
"node_modules/@types/jsonwebtoken": {
@ -163,15 +161,6 @@
"integrity": "sha512-3xSjTp3v03X/lSQLkczaN9UIEwJMoMCA1+Nb5HfbJEQWogdeQIyVtTvxPXDQjZ5zws8rFQfVfRdz03ARihPJgw==",
"license": "MIT"
},
"node_modules/@types/ws": {
"version": "8.18.1",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
"integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/accepts": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
@ -185,36 +174,6 @@
"node": ">= 0.6"
}
},
"node_modules/agent-base": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
"integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
"license": "MIT",
"dependencies": {
"debug": "4"
},
"engines": {
"node": ">= 6.0.0"
}
},
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
"license": "MIT"
},
"node_modules/axios": {
"version": "1.18.1",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.18.1.tgz",
"integrity": "sha512-3nTvFlvpn9Zu/RkHUqtc7/+al4UpRW5az71ap5zccp6e8RAYEzhMTecX8Dz1wWDYrPpUoB1HAQEGEAEvUr7S9g==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.16.0",
"form-data": "^4.0.5",
"https-proxy-agent": "^5.0.1",
"proxy-from-env": "^2.1.0"
}
},
"node_modules/body-parser": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz",
@ -296,18 +255,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"license": "MIT",
"dependencies": {
"delayed-stream": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/content-disposition": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz",
@ -365,15 +312,6 @@
}
}
},
"node_modules/delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
"license": "MIT",
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/depd": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
@ -451,21 +389,6 @@
"node": ">= 0.4"
}
},
"node_modules/es-set-tostringtag": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.6",
"has-tostringtag": "^1.0.2",
"hasown": "^2.0.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/escape-html": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
@ -551,64 +474,6 @@
"url": "https://opencollective.com/express"
}
},
"node_modules/follow-redirects": {
"version": "1.16.0",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz",
"integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/RubenVerborgh"
}
],
"license": "MIT",
"engines": {
"node": ">=4.0"
},
"peerDependenciesMeta": {
"debug": {
"optional": true
}
}
},
"node_modules/form-data": {
"version": "2.5.6",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.6.tgz",
"integrity": "sha512-Ogz/E85h9tlfJzpI6TuFpGcHZFhLrb9Gw8wq9v40CxSCPnv7ahKr6Xgtkn0KYCDQJ8DNn5VoMO8EXr9V5PadyA==",
"license": "MIT",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"es-set-tostringtag": "^2.1.0",
"hasown": "^2.0.4",
"mime-types": "^2.1.35",
"safe-buffer": "^5.2.1"
},
"engines": {
"node": ">= 0.12"
}
},
"node_modules/form-data/node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/form-data/node_modules/mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"license": "MIT",
"dependencies": {
"mime-db": "1.52.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/forwarded": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
@ -697,21 +562,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-tostringtag": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
"license": "MIT",
"dependencies": {
"has-symbols": "^1.0.3"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/hasown": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz",
@ -744,19 +594,6 @@
"url": "https://opencollective.com/express"
}
},
"node_modules/https-proxy-agent": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
"integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
"license": "MIT",
"dependencies": {
"agent-base": "6",
"debug": "4"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/iconv-lite": {
"version": "0.7.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz",
@ -788,30 +625,12 @@
"node": ">= 0.10"
}
},
"node_modules/is-electron": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/is-electron/-/is-electron-2.2.2.tgz",
"integrity": "sha512-FO/Rhvz5tuw4MCWkpMzHFKWD2LsfHzIb7i6MdPYZ/KW7AlxawyLkqdy+jPZP1WubqEADE3O4FUENlJHDfQASRg==",
"license": "MIT"
},
"node_modules/is-promise": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
"integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
"license": "MIT"
},
"node_modules/is-stream": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
"integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
"license": "MIT",
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/jsonwebtoken": {
"version": "9.0.3",
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz",
@ -1100,15 +919,6 @@
"node": ">= 0.10"
}
},
"node_modules/proxy-from-env": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz",
"integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==",
"license": "MIT",
"engines": {
"node": ">=10"
}
},
"node_modules/qs": {
"version": "6.15.3",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz",
@ -1403,6 +1213,15 @@
"integrity": "sha512-Sc8RA0NCMEFmApHNU9ZMzqcpQj46She44J8ffpLM/bdhLNUZKq7DJumcLcsFx1gRmDfQPgCgOmFFJ7rcnfWNyA==",
"license": "MIT"
},
"node_modules/undici": {
"version": "7.28.0",
"resolved": "https://registry.npmjs.org/undici/-/undici-7.28.0.tgz",
"integrity": "sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA==",
"license": "MIT",
"engines": {
"node": ">=20.18.1"
}
},
"node_modules/undici-types": {
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz",

View file

@ -8,11 +8,13 @@
},
"type": "module",
"dependencies": {
"@slack/bolt": "4.7.3",
"@slack/types": "2.21.1",
"@slack/web-api": "7.18.0",
"@slack/bolt": "5.0.0",
"@slack/socket-mode": "3.0.0",
"@slack/types": "3.0.0",
"@slack/web-api": "8.0.0",
"p-map": "7.0.5",
"typebox": "1.3.6",
"undici": "7.28.0",
"ws": "8.21.1",
"zod": "4.4.3"
},

View file

@ -1,9 +1,24 @@
// Slack plugin module implements client options behavior.
import type { Agent } from "node:http";
import { createRequire } from "node:module";
import type { RetryOptions, WebClientOptions } from "@slack/web-api";
import { createNodeProxyAgent } from "openclaw/plugin-sdk/fetch-runtime";
import {
addActiveManagedProxyTlsOptions,
resolveEnvHttpProxyAgentOptions,
} from "openclaw/plugin-sdk/fetch-runtime";
import type { EnvHttpProxyAgent } from "undici";
export type SlackLookupClientOptions = Pick<WebClientOptions, "agent" | "slackApiUrl" | "timeout">;
type SlackUndiciRuntime = Pick<typeof import("undici"), "EnvHttpProxyAgent" | "fetch">;
type SlackProxyDispatcher = EnvHttpProxyAgent;
const requireFromSlackSocketMode = (() => {
const require = createRequire(import.meta.url);
return createRequire(require.resolve("@slack/socket-mode/package.json"));
})();
function loadSlackUndiciRuntime(): SlackUndiciRuntime {
return requireFromSlackSocketMode("undici") as SlackUndiciRuntime;
}
export type SlackLookupClientOptions = Pick<WebClientOptions, "fetch" | "slackApiUrl" | "timeout">;
export const SLACK_DEFAULT_RETRY_OPTIONS: RetryOptions = {
retries: 2,
@ -23,41 +38,46 @@ const SLACK_LOOKUP_RETRY_OPTIONS: RetryOptions = {
retries: 0,
};
/**
* Build an HTTPS proxy agent from env vars (HTTPS_PROXY, HTTP_PROXY, etc.)
* for use as the `agent` option in Slack WebClient and Socket Mode connections.
*
* When set, this agent is forwarded through @slack/bolt -> @slack/socket-mode ->
* SlackWebSocket as the `httpAgent`, which the `ws` library uses to tunnel the
* WebSocket upgrade request through the proxy. This fixes Socket Mode in
* environments where outbound traffic must go through an HTTP CONNECT proxy.
*
* Respects `NO_PROXY` / `no_proxy`; if `*.slack.com` (or a matching pattern)
* appears in the exclusion list, returns `undefined` so the connection is direct.
*
* Returns `undefined` when no proxy env var is configured or when Slack hosts
* are excluded by `NO_PROXY`.
*/
function resolveSlackProxyAgent(targetUrl: string): Agent | undefined {
try {
return createNodeProxyAgent({
mode: "env",
targetUrl,
});
} catch {
// Malformed proxy URL; degrade gracefully to direct connection.
/** Build the dispatcher shared by Slack Web API fetches and Socket Mode. */
export function resolveSlackProxyDispatcher(): SlackProxyDispatcher | undefined {
const options = resolveEnvHttpProxyAgentOptions();
if (!options) {
return undefined;
}
try {
const { EnvHttpProxyAgent } = loadSlackUndiciRuntime();
return new EnvHttpProxyAgent(addActiveManagedProxyTlsOptions(options));
} catch {
// Malformed proxy URL; degrade gracefully to direct connections.
return undefined;
}
}
function createSlackDispatcherFetch(
dispatcher: SlackProxyDispatcher,
): NonNullable<WebClientOptions["fetch"]> {
const { fetch: slackFetch } = loadSlackUndiciRuntime();
return ((input: RequestInfo | URL, init?: RequestInit) => {
// Slack Web API invokes this hook with URL/string inputs. The cast only bridges
// duplicate Undici Request types while the package-owned fetch and dispatcher stay paired.
const slackInput = input as Parameters<typeof slackFetch>[0];
const slackInit = { ...init, dispatcher } as Parameters<typeof slackFetch>[1];
return slackFetch(slackInput, slackInit);
}) as NonNullable<WebClientOptions["fetch"]>;
}
function resolveSlackApiUrlFromEnv(): string | undefined {
return process.env.SLACK_API_URL?.trim() || undefined;
}
function applySlackApiUrlAndProxyOptions(options: WebClientOptions): void {
function applySlackApiUrlAndProxyOptions(
options: WebClientOptions,
dispatcher?: SlackProxyDispatcher,
): void {
const slackApiUrl = options.slackApiUrl ?? resolveSlackApiUrlFromEnv();
const proxyTargetUrl = slackApiUrl ?? "https://slack.com/";
options.agent ??= resolveSlackProxyAgent(proxyTargetUrl);
if (dispatcher && !options.fetch) {
options.fetch = createSlackDispatcherFetch(dispatcher);
}
if (slackApiUrl !== undefined) {
options.slackApiUrl = slackApiUrl;
} else {
@ -65,25 +85,32 @@ function applySlackApiUrlAndProxyOptions(options: WebClientOptions): void {
}
}
export function resolveSlackWebClientOptions(options: WebClientOptions = {}): WebClientOptions {
export function resolveSlackWebClientOptions(
options: WebClientOptions = {},
dispatcher = resolveSlackProxyDispatcher(),
): WebClientOptions {
const resolved: WebClientOptions = Object.assign({}, options);
applySlackApiUrlAndProxyOptions(resolved);
applySlackApiUrlAndProxyOptions(resolved, dispatcher);
resolved.retryConfig ??= SLACK_DEFAULT_RETRY_OPTIONS;
return resolved;
}
export function resolveSlackWriteClientOptions(options: WebClientOptions = {}): WebClientOptions {
export function resolveSlackWriteClientOptions(
options: WebClientOptions = {},
dispatcher = resolveSlackProxyDispatcher(),
): WebClientOptions {
const resolved: WebClientOptions = Object.assign({}, options);
applySlackApiUrlAndProxyOptions(resolved);
applySlackApiUrlAndProxyOptions(resolved, dispatcher);
resolved.retryConfig ??= SLACK_WRITE_RETRY_OPTIONS;
return resolved;
}
export function resolveSlackLookupClientOptions(
options: SlackLookupClientOptions = {},
dispatcher = resolveSlackProxyDispatcher(),
): WebClientOptions {
const resolved: WebClientOptions = Object.assign({}, options);
applySlackApiUrlAndProxyOptions(resolved);
applySlackApiUrlAndProxyOptions(resolved, dispatcher);
// Slack otherwise sleeps through the full Retry-After window after receiving 429,
// outside the Axios request timeout.
resolved.rejectRateLimitedCalls = true;

View file

@ -24,6 +24,7 @@ let createSlackWriteClient: typeof import("./client.js").createSlackWriteClient;
let createSlackTokenCacheKey: typeof import("./client.js").createSlackTokenCacheKey;
let getSlackWriteClient: typeof import("./client.js").getSlackWriteClient;
let clearSlackWriteClientCacheForTest: typeof import("./client.js").clearSlackWriteClientCacheForTest;
let resolveSlackProxyDispatcher: typeof import("./client-options.js").resolveSlackProxyDispatcher;
let resolveSlackWebClientOptions: typeof import("./client.js").resolveSlackWebClientOptions;
let resolveSlackWriteClientOptions: typeof import("./client.js").resolveSlackWriteClientOptions;
let SLACK_DEFAULT_RETRY_OPTIONS: typeof import("./client.js").SLACK_DEFAULT_RETRY_OPTIONS;
@ -78,11 +79,11 @@ function restoreSlackApiUrlEnvForTest() {
}
}
function requireAgent<T extends { agent?: unknown }>(options: T): NonNullable<T["agent"]> {
if (!options.agent) {
throw new Error("expected proxy agent");
function requireFetch(options: WebClientOptions): NonNullable<WebClientOptions["fetch"]> {
if (!options.fetch) {
throw new Error("expected dispatcher-backed fetch");
}
return options.agent as NonNullable<T["agent"]>;
return options.fetch;
}
function writeTempCa(contents: string): string {
@ -95,6 +96,7 @@ function writeTempCa(contents: string): string {
beforeAll(async () => {
const slackWebApi = await import("@slack/web-api");
({ resolveSlackProxyDispatcher } = await import("./client-options.js"));
({
createSlackWebClient,
createSlackStartupAuthClient,
@ -179,27 +181,27 @@ describe("slack web client config", () => {
});
it("passes merged options into WebClient", () => {
const customAgent = {} as never;
const customFetch = vi.fn() as never;
createSlackWebClient("xoxb-test", { timeout: 1234, agent: customAgent });
createSlackWebClient("xoxb-test", { timeout: 1234, fetch: customFetch });
expect(WebClient).toHaveBeenCalledWith("xoxb-test", {
agent: customAgent,
fetch: customFetch,
retryConfig: SLACK_DEFAULT_RETRY_OPTIONS,
timeout: 1234,
});
});
it("bounds startup auth while preserving listener transport options", () => {
const customAgent = {} as never;
const customFetch = vi.fn() as never;
createSlackStartupAuthClient("xoxb-startup", {
agent: customAgent,
fetch: customFetch,
slackApiUrl: "https://slack.test/api/",
});
expect(WebClient).toHaveBeenCalledWith("xoxb-startup", {
agent: customAgent,
fetch: customFetch,
rejectRateLimitedCalls: true,
retryConfig: { retries: 0 },
slackApiUrl: "https://slack.test/api/",
@ -213,7 +215,6 @@ describe("slack web client config", () => {
createSlackWebClient("xoxb-test", { timeout: 1234 });
expect(WebClient).toHaveBeenCalledWith("xoxb-test", {
agent: undefined,
retryConfig: SLACK_DEFAULT_RETRY_OPTIONS,
timeout: 1234,
});
@ -229,12 +230,12 @@ describe("slack web client config", () => {
});
it("passes the bounded lookup policy into WebClient", () => {
const customAgent = {} as never;
const customFetch = vi.fn() as never;
createSlackLookupClient("lookup-fixture", { agent: customAgent });
createSlackLookupClient("lookup-fixture", { fetch: customFetch });
expect(WebClient).toHaveBeenCalledWith("lookup-fixture", {
agent: customAgent,
fetch: customFetch,
rejectRateLimitedCalls: true,
retryConfig: { retries: 0 },
timeout: 30_000,
@ -248,12 +249,12 @@ describe("slack web client config", () => {
});
it("passes no-retry config into the write client by default", () => {
const customAgent = {} as never;
const customFetch = vi.fn() as never;
createSlackWriteClient("xoxb-test", { timeout: 4321, agent: customAgent });
createSlackWriteClient("xoxb-test", { timeout: 4321, fetch: customFetch });
expect(WebClient).toHaveBeenCalledWith("xoxb-test", {
agent: customAgent,
fetch: customFetch,
retryConfig: SLACK_WRITE_RETRY_OPTIONS,
timeout: 4321,
});
@ -268,7 +269,6 @@ describe("slack web client config", () => {
expect(second).toBe(first);
expect(WebClient).toHaveBeenCalledTimes(1);
expect(WebClient).toHaveBeenCalledWith("xoxb-test", {
agent: undefined,
retryConfig: SLACK_WRITE_RETRY_OPTIONS,
});
} finally {
@ -336,7 +336,7 @@ describe("slack web client config", () => {
});
});
describe("slack proxy agent", () => {
describe("slack proxy dispatcher", () => {
beforeEach(() => {
clearProxyEnvForTest();
});
@ -348,122 +348,86 @@ describe("slack proxy agent", () => {
restoreProxyEnvForTest();
});
it("sets agent from HTTPS_PROXY env var", () => {
it("attaches one dispatcher-backed fetch for HTTPS_PROXY", async () => {
process.env.HTTPS_PROXY = "http://proxy.example.com:3128";
const options = resolveSlackWebClientOptions();
const agent = requireAgent(options);
const dispatcher = resolveSlackProxyDispatcher();
const options = resolveSlackWebClientOptions({}, dispatcher);
expect(agent.constructor.name).toBe("ProxylineNodeProxyAgent");
expect(dispatcher?.constructor.name).toBe("EnvHttpProxyAgent");
expect(requireFetch(options)).toBeTypeOf("function");
await dispatcher?.close();
});
it("creates Slack env proxy agents while managed proxy CA trust is active", () => {
it("creates the dispatcher while managed proxy CA trust is active", async () => {
const caFile = writeTempCa("slack-managed-proxy-ca");
process.env.HTTPS_PROXY = "https://proxy.example.com:8443";
process.env.OPENCLAW_PROXY_ACTIVE = "1";
process.env.OPENCLAW_PROXY_CA_FILE = caFile;
const options = resolveSlackWebClientOptions();
const agent = requireAgent(options);
expect(agent.constructor.name).toBe("ProxylineNodeProxyAgent");
const dispatcher = resolveSlackProxyDispatcher();
expect(dispatcher?.constructor.name).toBe("EnvHttpProxyAgent");
await dispatcher?.close();
});
it("falls back to HTTP_PROXY when HTTPS_PROXY is not set", () => {
it("falls back to HTTP_PROXY when HTTPS_PROXY is not set", async () => {
process.env.HTTP_PROXY = "http://proxy.example.com:3128";
const options = resolveSlackWebClientOptions();
const dispatcher = resolveSlackProxyDispatcher();
expect(requireAgent(options).constructor.name).toBe("ProxylineNodeProxyAgent");
expect(dispatcher?.constructor.name).toBe("EnvHttpProxyAgent");
await dispatcher?.close();
});
it("does not set agent when no proxy env var is configured", () => {
const options = resolveSlackWebClientOptions();
expect(options.agent).toBeUndefined();
it("does not attach a fetch when no proxy env var is configured", () => {
expect(resolveSlackProxyDispatcher()).toBeUndefined();
expect(resolveSlackWebClientOptions().fetch).toBeUndefined();
});
it("does not override an explicitly provided agent", () => {
it("preserves an explicitly provided fetch", async () => {
process.env.HTTPS_PROXY = "http://proxy.example.com:3128";
const customAgent = {} as never;
const options = resolveSlackWebClientOptions({ agent: customAgent });
const customFetch = vi.fn() as never;
const dispatcher = resolveSlackProxyDispatcher();
const options = resolveSlackWebClientOptions({ fetch: customFetch }, dispatcher);
expect(options.agent).toBe(customAgent);
});
it("prefers lowercase https_proxy over uppercase", () => {
process.env.https_proxy = "http://lower.example.com:3128";
process.env.HTTPS_PROXY = "http://upper.example.com:3128";
const options = resolveSlackWebClientOptions();
const agent = requireAgent(options);
// Proxyline stores the effective proxy URL in its resolver.
expect(
(agent as unknown as { getProxyForUrl: (url: string) => string }).getProxyForUrl(
"https://slack.com/",
),
).toContain("lower.example.com");
expect(options.fetch).toBe(customFetch);
await dispatcher?.close();
});
it("treats empty lowercase https_proxy as authoritative over uppercase", () => {
process.env.https_proxy = "";
process.env.HTTPS_PROXY = "http://upper.example.com:3128";
const options = resolveSlackWebClientOptions();
expect(options.agent).toBeUndefined();
expect(resolveSlackProxyDispatcher()).toBeUndefined();
});
it("also applies proxy agent to write client options", () => {
it("also applies the dispatcher-backed fetch to write clients", async () => {
process.env.HTTPS_PROXY = "http://proxy.example.com:3128";
const options = resolveSlackWriteClientOptions();
const agent = requireAgent(options);
const dispatcher = resolveSlackProxyDispatcher();
const options = resolveSlackWriteClientOptions({}, dispatcher);
expect(agent.constructor.name).toBe("ProxylineNodeProxyAgent");
expect(requireFetch(options)).toBeTypeOf("function");
await dispatcher?.close();
});
it("respects NO_PROXY excluding slack.com", () => {
it.each([
"localhost,slack.com,.internal.corp",
"localhost *.slack.com",
"*",
"localhost,.internal.corp",
])("keeps NO_PROXY matching inside the shared env dispatcher: %s", async (noProxy) => {
process.env.HTTPS_PROXY = "http://proxy.example.com:3128";
process.env.NO_PROXY = "localhost,slack.com,.internal.corp";
const options = resolveSlackWebClientOptions();
process.env.NO_PROXY = noProxy;
const dispatcher = resolveSlackProxyDispatcher();
const options = resolveSlackWebClientOptions({}, dispatcher);
expect(options.agent).toBeUndefined();
});
it("respects no_proxy (lowercase) excluding .slack.com", () => {
process.env.HTTPS_PROXY = "http://proxy.example.com:3128";
process.env.no_proxy = ".slack.com";
const options = resolveSlackWebClientOptions();
expect(options.agent).toBeUndefined();
});
it("respects space-separated no_proxy entries", () => {
process.env.HTTPS_PROXY = "http://proxy.example.com:3128";
process.env.no_proxy = "localhost *.slack.com";
const options = resolveSlackWebClientOptions();
expect(options.agent).toBeUndefined();
});
it("respects NO_PROXY wildcard", () => {
process.env.HTTPS_PROXY = "http://proxy.example.com:3128";
process.env.NO_PROXY = "*";
const options = resolveSlackWebClientOptions();
expect(options.agent).toBeUndefined();
});
it("does not skip proxy when NO_PROXY excludes unrelated hosts", () => {
process.env.HTTPS_PROXY = "http://proxy.example.com:3128";
process.env.NO_PROXY = "localhost,.internal.corp";
const options = resolveSlackWebClientOptions();
expect(requireAgent(options).constructor.name).toBe("ProxylineNodeProxyAgent");
expect(dispatcher?.constructor.name).toBe("EnvHttpProxyAgent");
expect(requireFetch(options)).toBeTypeOf("function");
await dispatcher?.close();
});
it("degrades gracefully on malformed proxy URL", () => {
process.env.HTTPS_PROXY = "not-a-valid-url://:::bad";
const options = resolveSlackWebClientOptions();
// Should not throw; falls back to no agent
expect(options.agent).toBeUndefined();
expect(resolveSlackProxyDispatcher()).toBeUndefined();
expect(resolveSlackWebClientOptions().fetch).toBeUndefined();
});
});

View file

@ -53,16 +53,14 @@ describe("resolveSlackEventScope", () => {
const teamScopedClient = new WebClient("xoxb-test", {
teamId: "T111",
retryConfig: { retries: 0 },
adapter: async (config) => {
encodedRequestBody = String(config.data);
return {
data: { ok: true, ts: "123.456", channel: "C123" },
status: 200,
statusText: "OK",
headers: {},
config,
request: {},
};
fetch: (_input, init) => {
encodedRequestBody = typeof init?.body === "string" ? init.body : "";
return Promise.resolve(
new Response(JSON.stringify({ ok: true, ts: "123.456", channel: "C123" }), {
status: 200,
headers: { "content-type": "application/json" },
}),
);
},
});
const methodPayload = { channel: "C123", text: "hello" };

View file

@ -309,6 +309,7 @@ export function createSlackBoltApp(params: {
signingSecret?: string;
slackWebhookPath: string;
clientOptions: Record<string, unknown>;
dispatcher?: SlackSocketModeReceiverOptions["dispatcher"];
wrapReceiver?: (receiver: SlackReceiver) => SlackReceiver;
}) {
const socketModeLogger = createSlackSocketModeLogger();
@ -317,6 +318,7 @@ export function createSlackBoltApp(params: {
autoReconnectEnabled: true,
clientPingTimeout: OPENCLAW_SLACK_CLIENT_PING_TIMEOUT_MS,
logger: socketModeLogger,
...(params.dispatcher ? { dispatcher: params.dispatcher } : {}),
installerOptions: {
clientOptions: params.clientOptions,
},

View file

@ -185,14 +185,11 @@ describe("slack socket reconnect helpers", () => {
);
client.emit(
"ws_message",
Buffer.from(JSON.stringify({ type: "hello", num_connections: 2 })),
false,
);
client.emit(
"ws_message",
Buffer.from(JSON.stringify({ type: "hello", num_connections: 3 })),
false,
Buffer.from(JSON.stringify({ type: "hello", num_connections: 4 })),
true,
);
client.emit("ws_message", JSON.stringify({ type: "hello", num_connections: 2 }), false);
client.emit("ws_message", JSON.stringify({ type: "hello", num_connections: 3 }), false);
expect(onSharedConnection).toHaveBeenCalledTimes(1);
expect(onSharedConnection).toHaveBeenCalledWith(2);

View file

@ -33,7 +33,7 @@ import {
resolveSlackAccountDmPolicy,
} from "../accounts.js";
import { isSlackAnyNativeApprovalClientEnabled } from "../approval-native-gates.js";
import { resolveSlackWebClientOptions } from "../client-options.js";
import { resolveSlackProxyDispatcher, resolveSlackWebClientOptions } from "../client-options.js";
import { createSlackStartupAuthClient } from "../client.js";
import { normalizeSlackWebhookPath, registerSlackHttpHandler } from "../http/index.js";
import { SLACK_TEXT_LIMIT } from "../limits.js";
@ -341,7 +341,8 @@ export async function monitorSlackProvider(opts: MonitorSlackOpts = {}) {
const ackReactionScope = cfg.messages?.ackReactionScope ?? "group-mentions";
const typingReaction = slackCfg.typingReaction?.trim() ?? "";
const mediaMaxBytes = (opts.mediaMaxMb ?? slackCfg.mediaMaxMb ?? 20) * 1024 * 1024;
const clientOptions = resolveSlackWebClientOptions();
const slackDispatcher = resolveSlackProxyDispatcher();
const clientOptions = resolveSlackWebClientOptions({}, slackDispatcher);
const durableIngress = createSlackDurableIngress({
accountId: account.accountId,
...(runtime.log ? { onLog: runtime.log } : {}),
@ -355,6 +356,7 @@ export async function monitorSlackProvider(opts: MonitorSlackOpts = {}) {
signingSecret: slackMode === "http" ? (signingSecret ?? undefined) : undefined,
slackWebhookPath,
clientOptions: clientOptions as Record<string, unknown>,
dispatcher: slackDispatcher,
wrapReceiver: durableIngress.wrapReceiver,
});
@ -853,6 +855,7 @@ export async function monitorSlackProvider(opts: MonitorSlackOpts = {}) {
unregisterHttpHandler?.();
await durableIngress.stop();
await gracefulStop();
await slackDispatcher?.close();
}
}

View file

@ -55,13 +55,16 @@ function isBufferArray(value: unknown): value is Buffer[] {
}
function resolveSlackSocketModeConnectionCount(message: unknown): number | undefined {
const buffer = Buffer.isBuffer(message)
? message
: message instanceof ArrayBuffer
const buffer =
typeof message === "string"
? Buffer.from(message)
: isBufferArray(message)
? Buffer.concat(message)
: undefined;
: Buffer.isBuffer(message)
? message
: message instanceof ArrayBuffer
? Buffer.from(message)
: isBufferArray(message)
? Buffer.concat(message)
: undefined;
if (!buffer?.includes(SLACK_SOCKET_HELLO_MARKER)) {
return undefined;
}

View file

@ -24,7 +24,8 @@ import { emitSlackMessageSentHooks } from "../message-sent-hook.js";
import {
buildSlackNativeDataAccessibilityText,
hasSlackNativeDataBlock,
isSlackInvalidBlocksError,
isSlackInvalidBlocksResponse,
isSlackNativeResponseUrlRejection,
} from "../native-data-blocks.js";
import {
buildSlackNativeDataDeliveryPlan,
@ -565,7 +566,7 @@ export async function deliverSlackSlashReplies(params: {
const deliverNativeFallback = async (messages: readonly SlackFormattingDisabledMessage[]) => {
for (const message of messages) {
const response = await respond(message);
if (isSlackInvalidBlocksError(response)) {
if (await isSlackInvalidBlocksResponse(response)) {
throw new Error("Slack rejected the native-data fallback blocks with invalid_blocks.");
}
}
@ -593,9 +594,9 @@ export async function deliverSlackSlashReplies(params: {
let rejectedNativeBlocks = false;
try {
const response = await respond(planned.message);
rejectedNativeBlocks = isSlackInvalidBlocksError(response);
rejectedNativeBlocks = await isSlackInvalidBlocksResponse(response);
} catch (error) {
if (!isSlackInvalidBlocksError(error)) {
if (!isSlackNativeResponseUrlRejection(error)) {
throw error;
}
rejectedNativeBlocks = true;

View file

@ -1038,7 +1038,7 @@ export async function registerSlackMonitorSlashCommands(params: {
respond ??
(async (message) => {
if (!body.channel?.id || !body.user?.id) {
return;
return new Response(null, { status: 204 });
}
const payload =
typeof message === "string"
@ -1056,6 +1056,7 @@ export async function registerSlackMonitorSlashCommands(params: {
...(payload.blocks ? { blocks: payload.blocks } : {}),
...(typeof payload.mrkdwn === "boolean" ? { mrkdwn: payload.mrkdwn } : {}),
});
return new Response(null, { status: 200 });
});
const actionValue = action?.value ?? action?.selected_option?.value;
const parsed = parseSlackCommandArgValue(actionValue);

View file

@ -1,9 +1,12 @@
import { Response as UndiciResponse } from "undici";
import { describe, expect, it } from "vitest";
import {
appendSlackNativeDataFallbackText,
buildSlackNativeDataAccessibilityText,
hasSlackNativeDataBlock,
isSlackInvalidBlocksError,
isSlackInvalidBlocksResponse,
isSlackNativeResponseUrlRejection,
} from "./native-data-blocks.js";
const chart = {
@ -52,6 +55,29 @@ describe("Slack native data blocks", () => {
expect(isSlackInvalidBlocksError(new Error("invalid_blocks"))).toBe(false);
});
it("matches Bolt 5 response_url responses and contextual RespondError failures", async () => {
const response = new Response(JSON.stringify({ error: "invalid_blocks" }), { status: 200 });
await expect(isSlackInvalidBlocksResponse(response)).resolves.toBe(true);
await expect(
isSlackInvalidBlocksResponse(
new UndiciResponse(JSON.stringify({ error: "invalid_blocks" }), { status: 200 }),
),
).resolves.toBe(true);
await expect(isSlackInvalidBlocksResponse(new Response("ok"))).resolves.toBe(false);
expect(
isSlackNativeResponseUrlRejection({
code: "slack_bolt_respond_error",
statusCode: 400,
}),
).toBe(true);
expect(
isSlackNativeResponseUrlRejection({
code: "slack_bolt_respond_error",
statusCode: 500,
}),
).toBe(false);
});
it("appends mixed native data in block order without collapsing repeated blocks", () => {
const chartText = "Revenue mix (pie chart)\n- Product: 60\n- Services: 40";
const tableText = "Pipeline report (table)\n- Account: Acme; ARR: $125k";

View file

@ -49,6 +49,41 @@ export function isSlackInvalidBlocksError(error: unknown): boolean {
return typeof code === "string" && code.trim().toLowerCase() === "invalid_blocks";
}
type SlackResponseLike = {
status: number;
clone: () => { text: () => Promise<string> };
};
function isSlackResponseLike(value: unknown): value is SlackResponseLike {
const record = asRecord(value);
return typeof record?.status === "number" && typeof record.clone === "function";
}
/** Inspect Bolt 5's native response_url Response without consuming the caller's body. */
export async function isSlackInvalidBlocksResponse(response: unknown): Promise<boolean> {
if (!isSlackResponseLike(response)) {
return isSlackInvalidBlocksError(response);
}
try {
const body = await response.clone().text();
if (body.trim().toLowerCase() === "invalid_blocks") {
return true;
}
return isSlackInvalidBlocksError(JSON.parse(body));
} catch {
return false;
}
}
/** Bolt 5 omits the response body from RespondError; 400 is contextual here. */
export function isSlackNativeResponseUrlRejection(error: unknown): boolean {
if (isSlackInvalidBlocksError(error)) {
return true;
}
const record = asRecord(error);
return record?.code === "slack_bolt_respond_error" && record.statusCode === 400;
}
/** Extract a complete accessible summary from a supported native data block. */
function renderSlackNativeDataFallbackText(value: unknown): string | undefined {
const type = asRecord(value)?.type;

70
npm-shrinkwrap.json generated
View file

@ -10,11 +10,11 @@
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
"@agentclientprotocol/sdk": "1.2.1",
"@agentclientprotocol/sdk": "1.3.0",
"@anthropic-ai/sdk": "0.112.3",
"@clack/core": "1.4.3",
"@clack/prompts": "1.7.0",
"@earendil-works/pi-tui": "0.80.10",
"@earendil-works/pi-tui": "0.81.1",
"@google/genai": "2.12.0",
"@grammyjs/runner": "2.0.3",
"@grammyjs/transformer-throttler": "1.2.1",
@ -34,7 +34,7 @@
"diff": "9.0.0",
"dotenv": "17.4.2",
"entities": "8.0.0",
"execa": "9.6.1",
"execa": "10.0.0",
"express": "5.2.1",
"file-type": "22.0.1",
"grammy": "1.45.1",
@ -63,7 +63,7 @@
"semver": "7.8.5",
"tar": "7.5.20",
"tree-sitter-bash": "0.25.1",
"tslog": "4.10.2",
"tslog": "4.11.0",
"typebox": "1.3.6",
"typescript": "6.0.3",
"undici": "8.6.0",
@ -84,9 +84,9 @@
}
},
"node_modules/@agentclientprotocol/sdk": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@agentclientprotocol/sdk/-/sdk-1.2.1.tgz",
"integrity": "sha512-jwYUdOQR7tc+Zfch53VL4JJyUNK/46q03uUTYb+PjECsmnNl94XFXOfYLJ8RBpMNidXd1rpOAVgb0vqD98xImA==",
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@agentclientprotocol/sdk/-/sdk-1.3.0.tgz",
"integrity": "sha512-i3h/efaeuMUFAO1HSfo97QZQnnvMd7wWBYtBsdL6UMZg3a78sk3Ffya5Xu7C7tYsXomXoDXJBAzQF2PcFKAhIQ==",
"license": "Apache-2.0",
"peerDependencies": {
"zod": "^3.25.0 || ^4.0.0"
@ -161,9 +161,9 @@
}
},
"node_modules/@earendil-works/pi-tui": {
"version": "0.80.10",
"resolved": "https://registry.npmjs.org/@earendil-works/pi-tui/-/pi-tui-0.80.10.tgz",
"integrity": "sha512-c2JO29PbhKPEQ6fgHQKAl0WhwuFqzWfzspMmP+8B5tpDuP+0mvarRbKKg8gq4b+pQx/QX+6aVS4ko7deoyjQjg==",
"version": "0.81.1",
"resolved": "https://registry.npmjs.org/@earendil-works/pi-tui/-/pi-tui-0.81.1.tgz",
"integrity": "sha512-OMEe+Zt8oQYi/rCq3upxsTlIScWL0FPhXwQus34TbQb3EmTx88S7Uzx32JxvQiEeWOw8eDCdJf2PBUBE9r6wIg==",
"license": "MIT",
"dependencies": {
"get-east-asian-width": "1.6.0",
@ -1369,31 +1369,44 @@
}
},
"node_modules/execa": {
"version": "9.6.1",
"resolved": "https://registry.npmjs.org/execa/-/execa-9.6.1.tgz",
"integrity": "sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==",
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/execa/-/execa-10.0.0.tgz",
"integrity": "sha512-Cxl6MKxB1dr1H0FHmiizJ+lavKF7pV+fcDZFyqMB8d5m7qUPm/OtZYcD5vPWePKxSnTQ57KuBd9mtdZ3oNCvyQ==",
"license": "MIT",
"dependencies": {
"@sindresorhus/merge-streams": "^4.0.0",
"cross-spawn": "^7.0.6",
"figures": "^6.1.0",
"get-stream": "^9.0.0",
"get-stream": "^9.0.1",
"human-signals": "^8.0.1",
"is-plain-obj": "^4.1.0",
"is-stream": "^4.0.1",
"npm-run-path": "^6.0.0",
"pretty-ms": "^9.2.0",
"path-key": "^4.0.0",
"pretty-ms": "^9.3.0",
"signal-exit": "^4.1.0",
"strip-final-newline": "^4.0.0",
"yoctocolors": "^2.1.1"
"which-command": "^0.1.0",
"yoctocolors": "^2.1.2"
},
"engines": {
"node": "^18.19.0 || >=20.5.0"
"node": ">=22"
},
"funding": {
"url": "https://github.com/sindresorhus/execa?sponsor=1"
}
},
"node_modules/execa/node_modules/path-key": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz",
"integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==",
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/express": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz",
@ -3605,9 +3618,9 @@
"license": "0BSD"
},
"node_modules/tslog": {
"version": "4.10.2",
"resolved": "https://registry.npmjs.org/tslog/-/tslog-4.10.2.tgz",
"integrity": "sha512-XuELoRpMR+sq8fuWwX7P0bcj+PRNiicOKDEb3fGNURhxWVyykCi9BNq7c4uVz7h7P0sj8qgBsr5SWS6yBClq3g==",
"version": "4.11.0",
"resolved": "https://registry.npmjs.org/tslog/-/tslog-4.11.0.tgz",
"integrity": "sha512-gBe0FTKkRpOv3DenGipjQWQe073jNd1cOLj3nYfVM0/NP29sQzAIx6kfyfTTsQOiWMC+B+tSr289lNXtu7HCmQ==",
"license": "MIT",
"engines": {
"node": ">=16"
@ -3800,6 +3813,21 @@
"node": ">= 8"
}
},
"node_modules/which-command": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/which-command/-/which-command-0.1.0.tgz",
"integrity": "sha512-XZyoF5/5hZtXitIwzrU4NKK+Wtbb9aB9CezUEw2Q0wlYK8NUYQxC1rRXgNueYLtBAJwXIb+/tFVk4dozciNJMA==",
"license": "MIT",
"bin": {
"which-command": "cli.js"
},
"engines": {
"node": ">=22"
},
"funding": {
"url": "https://github.com/sindresorhus/which-command?sponsor=1"
}
},
"node_modules/which-module": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz",

View file

@ -1884,11 +1884,11 @@
"verify": "node scripts/verify.mjs"
},
"dependencies": {
"@agentclientprotocol/sdk": "1.2.1",
"@agentclientprotocol/sdk": "1.3.0",
"@anthropic-ai/sdk": "0.112.3",
"@clack/core": "1.4.3",
"@clack/prompts": "1.7.0",
"@earendil-works/pi-tui": "0.80.10",
"@earendil-works/pi-tui": "0.81.1",
"@google/genai": "2.12.0",
"@grammyjs/runner": "2.0.3",
"@grammyjs/transformer-throttler": "1.2.1",
@ -1909,7 +1909,7 @@
"diff": "9.0.0",
"dotenv": "17.4.2",
"entities": "8.0.0",
"execa": "9.6.1",
"execa": "10.0.0",
"express": "5.2.1",
"file-type": "22.0.1",
"grammy": "1.45.1",
@ -1938,7 +1938,7 @@
"semver": "7.8.5",
"tar": "7.5.20",
"tree-sitter-bash": "0.25.1",
"tslog": "4.10.2",
"tslog": "4.11.0",
"typebox": "1.3.6",
"typescript": "6.0.3",
"undici": "8.6.0",
@ -1972,10 +1972,10 @@
"esbuild": "0.28.1",
"fast-glob": "3.3.3",
"ipaddr.js": "2.4.0",
"jscpd": "5.0.12",
"jscpd": "4.2.4",
"jsdom": "29.1.1",
"lit": "3.3.3",
"oxfmt": "0.59.0",
"oxfmt": "0.60.0",
"oxlint": "1.74.0",
"oxlint-tsgolint": "0.25.0",
"playwright": "1.61.1",
@ -1985,7 +1985,7 @@
"tsdown": "0.22.1",
"tsx": "4.23.1",
"unrun": "0.3.1",
"vite": "8.1.3",
"vite": "8.1.5",
"vitest": "4.1.10"
},
"optionalDependencies": {

1040
pnpm-lock.yaml generated

File diff suppressed because it is too large Load diff

View file

@ -16,6 +16,7 @@ minimumReleaseAgeExclude:
- "acpx"
- "tokenjuice"
- "@agentclientprotocol/sdk"
- "@agentclientprotocol/codex-acp@1.1.7"
- "axios"
- "basic-ftp"
- "baileys@7.0.0-rc13"
@ -28,15 +29,27 @@ minimumReleaseAgeExclude:
- "@cloudflare/workers-types"
- "@hono/node-server"
- "@mariozechner/*"
- "@aws-sdk/checksums@3.1000.19"
- "@aws-sdk/client-bedrock-runtime@3.1053.0"
- "@aws-sdk/client-bedrock@3.1053.0"
- "@aws-sdk/client-cognito-identity@3.1053.0"
- "@aws-sdk/client-s3@3.1053.0"
- "@aws-sdk/core"
- "@aws-sdk/credential-provider-env@3.972.60"
- "@aws-sdk/credential-provider-http@3.972.62"
- "@aws-sdk/credential-provider-ini@3.973.5"
- "@aws-sdk/credential-provider-login@3.972.67"
- "@aws-sdk/credential-provider-process@3.972.60"
- "@aws-sdk/credential-provider-sso@3.973.4"
- "@aws-sdk/credential-provider-web-identity@3.972.66"
- "@aws-sdk/credential-providers@3.1053.0"
- "@aws-sdk/middleware-sdk-s3@3.972.65"
- "@aws-sdk/middleware-websocket@3.972.42"
- "@aws-sdk/nested-clients@3.997.34"
- "@aws-sdk/s3-request-presigner@3.1053.0"
- "@aws-sdk/token-providers@3.1052.0"
- "@aws-sdk/token-providers@3.1053.0"
- "@aws-sdk/token-providers@3.1092.0"
- "@copilotkit/aimock@1.27.1"
- "@microsoft/mxc-sdk@0.7.0"
- "@earendil-works/pi-tui"
@ -50,7 +63,12 @@ minimumReleaseAgeExclude:
- "@openai/codex"
- "@openai/codex-*"
- "@pierre/diffs@1.2.3"
- "@smithy/core@3.29.7"
- "@smithy/credential-provider-imds@4.4.12"
- "@smithy/fetch-http-handler@5.6.9"
- "@smithy/shared-ini-file-loader@4.5.4"
- "@smithy/signature-v4@5.6.8"
- "postcss@8.5.22"
- "@typescript/native-preview*"
- "@types/node"
- "@rolldown/*"

View file

@ -71,7 +71,7 @@ const checks: Array<{ file: string; snippets: string[] }> = [
},
{
file: "ReviewDecision.ts",
snippets: ['"approved"', '"approved_for_session"', '"denied"', '"abort"'],
snippets: ['"approved"', '"approved_for_session"', "denied: { rejection: string }", '"abort"'],
},
{
file: "v2/PlanDeltaNotification.ts",

View file

@ -7,7 +7,7 @@ import { mkdtempSync, readdirSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
const ACTIONLINT_VERSION = "1.7.11";
const ACTIONLINT_VERSION = "1.7.12";
const PRE_COMMIT_VERSION = "4.2.0";
const WORKFLOW_DIR = ".github/workflows";

View file

@ -14,12 +14,12 @@ export async function prepareMinGitZip(tgzDir: string): Promise<string> {
import urllib.request
preferred_names = [
"MinGit-2.53.0.2-64-bit.zip",
"MinGit-2.53.0.2-arm64.zip",
"MinGit-2.55.0.3-64-bit.zip",
"MinGit-2.55.0.3-arm64.zip",
]
fallback_urls = {
"MinGit-2.53.0.2-arm64.zip": "https://github.com/git-for-windows/git/releases/download/v2.53.0.windows.2/MinGit-2.53.0.2-arm64.zip",
"MinGit-2.53.0.2-64-bit.zip": "https://github.com/git-for-windows/git/releases/download/v2.53.0.windows.2/MinGit-2.53.0.2-64-bit.zip",
"MinGit-2.55.0.3-arm64.zip": "https://github.com/git-for-windows/git/releases/download/v2.55.0.windows.3/MinGit-2.55.0.3-arm64.zip",
"MinGit-2.55.0.3-64-bit.zip": "https://github.com/git-for-windows/git/releases/download/v2.55.0.windows.3/MinGit-2.55.0.3-64-bit.zip",
}
try:

View file

@ -46,7 +46,7 @@ if ! command -v opengrep >/dev/null 2>&1; then
error: 'opengrep' not found on PATH.
Install with one of:
curl -fsSL https://raw.githubusercontent.com/opengrep/opengrep/v1.22.0/install.sh | bash -s -- -v v1.22.0
curl -fsSL https://raw.githubusercontent.com/opengrep/opengrep/v1.25.0/install.sh | bash -s -- -v v1.25.0
brew install opengrep/tap/opengrep
pipx install opengrep
@ -118,7 +118,7 @@ write_empty_sarif() {
"driver": {
"name": "Opengrep OSS",
"informationUri": "https://opengrep.dev",
"semanticVersion": "1.22.0",
"semanticVersion": "1.25.0",
"rules": []
}
},

View file

@ -96,7 +96,7 @@ export async function execCommand(
reject: false,
stdio: ["ignore", "pipe", "pipe"],
});
const releaseOutput = releaseChildProcessOutputAfterExit(proc);
const releaseOutput = releaseChildProcessOutputAfterExit(proc.nodeChildProcess);
let stdout: OutputCapture = { text: "", truncatedChars: 0 };
let stderr: OutputCapture = { text: "", truncatedChars: 0 };

View file

@ -81,7 +81,7 @@ export function createLocalBashOperations(options?: { shellPath?: string }): Bas
reject: false,
stdio: [invocation.stdin, "pipe", "pipe"],
});
const releaseOutput = releaseChildProcessOutputAfterExit(child);
const releaseOutput = releaseChildProcessOutputAfterExit(child.nodeChildProcess);
let timedOut = false;
let timeoutHandle: NodeJS.Timeout | undefined;
const timeoutMs = resolveBashTimeoutMs(timeout);

View file

@ -19,6 +19,7 @@ vi.mock("../../utils/tools-manager.js", () => ({
const tempDirs = useAutoCleanupTempDirTracker(afterEach);
type MockChild = ChildProcessWithoutNullStreams & {
nodeChildProcess: ChildProcessWithoutNullStreams;
stdout: PassThrough;
stderr: PassThrough;
killMock: ReturnType<typeof vi.fn>;
@ -30,7 +31,7 @@ afterEach(() => {
function createChild(): MockChild {
const kill = vi.fn(() => true);
return Object.assign(new EventEmitter(), {
const child = Object.assign(new EventEmitter(), {
stdin: new PassThrough(),
stdout: new PassThrough(),
stderr: new PassThrough(),
@ -38,6 +39,8 @@ function createChild(): MockChild {
kill,
killMock: kill,
}) as unknown as MockChild;
child.nodeChildProcess = child;
return child;
}
it("rejects partial fd output when fd exits with an error", async () => {

View file

@ -279,13 +279,13 @@ export function createFindToolDefinition(
reject: false,
stdio: ["ignore", "pipe", "pipe"],
});
releaseChildProcessOutputAfterExit(child);
releaseChildProcessOutputAfterExit(child.nodeChildProcess);
const rl = createInterface({ input: child.stdout });
let stderr = "";
const lines: string[] = [];
stopChild = () => {
if (!child.killed) {
if (!child.nodeChildProcess.killed) {
child.kill();
}
};
@ -318,12 +318,12 @@ export function createFindToolDefinition(
lines.push(line);
});
child.on("error", (error) => {
child.nodeChildProcess.on("error", (error) => {
cleanup();
settle(() => reject(new Error(`Failed to run fd: ${error.message}`)));
});
child.on("close", (code) => {
child.nodeChildProcess.on("close", (code) => {
cleanup();
if (signal?.aborted) {
settle(() => reject(new Error("Operation aborted")));

View file

@ -20,7 +20,11 @@ afterEach(() => {
vi.clearAllMocks();
});
type MockChild = ChildProcessWithoutNullStreams & { stdout: PassThrough; stderr: PassThrough };
type MockChild = ChildProcessWithoutNullStreams & {
nodeChildProcess: ChildProcessWithoutNullStreams;
stdout: PassThrough;
stderr: PassThrough;
};
function createChild(): MockChild {
let killed = false;
@ -34,6 +38,7 @@ function createChild(): MockChild {
killed = true;
return true;
});
child.nodeChildProcess = child;
return child;
}

View file

@ -3,7 +3,6 @@
*
* Searches files with ripgrep/local operations, optional context, and bounded output rendering.
*/
import type { ChildProcess } from "node:child_process";
import { readFileSync, statSync } from "node:fs";
import path from "node:path";
import { createInterface } from "node:readline";
@ -160,7 +159,12 @@ export function createGrepToolDefinition(
// Keep cancellation live from the first await through async result formatting.
// Settlement owns listener cleanup; spawned children stop without waiting for close.
let settled = false;
let child: ChildProcess | undefined;
let child:
| {
nodeChildProcess: { killed: boolean };
kill: () => void;
}
| undefined;
let childClosed = false;
let rl: ReturnType<typeof createInterface> | undefined;
let killedDueToLimit = false;
@ -178,7 +182,7 @@ export function createGrepToolDefinition(
return true;
};
const stopChild = (dueToLimit = false) => {
if (child && !childClosed && !child.killed) {
if (child && !childClosed && !child.nodeChildProcess.killed) {
killedDueToLimit = dueToLimit;
child.kill();
}
@ -267,7 +271,7 @@ export function createGrepToolDefinition(
reject: false,
stdio: ["ignore", "pipe", "pipe"],
});
releaseChildProcessOutputAfterExit(spawnedChild);
releaseChildProcessOutputAfterExit(spawnedChild.nodeChildProcess);
child = spawnedChild;
rl = createInterface({ input: spawnedChild.stdout });
let stderr = "";
@ -358,11 +362,11 @@ export function createGrepToolDefinition(
}
});
spawnedChild.on("error", (error) => {
spawnedChild.nodeChildProcess.on("error", (error) => {
childClosed = true;
settle(() => reject(new Error(`Failed to run ripgrep: ${error.message}`)));
});
spawnedChild.on("close", (code) => {
spawnedChild.nodeChildProcess.on("close", (code) => {
childClosed = true;
void (async () => {
if (settled) {

View file

@ -18,7 +18,7 @@ function fakeChild(result: Promise<unknown>) {
const kill = vi.fn();
const stderr = new PassThrough();
return {
child: Object.assign(result, { kill, stderr, unref }),
child: Object.assign(result, { kill, stderr, nodeChildProcess: { unref } }),
kill,
stderr,
unref,

View file

@ -46,7 +46,7 @@ async function observeXdgOpenStartup(command: OpenPathCommand): Promise<void> {
reject: true,
stdio: ["ignore", "ignore", "pipe"],
});
child.unref();
child.nodeChildProcess.unref();
let stderrText = "";
const stderr = child.stderr;
stderr?.setEncoding("utf8");

View file

@ -584,7 +584,6 @@ export function isFileLogLevelEnabled(level: LogLevel): boolean {
function buildLogger(settings: ResolvedRuntimeSettings): TsLogger<LogObj> {
const logger = new TsLogger<LogObj>({
name: "openclaw",
// Custom structured redaction runs at each transport boundary; avoid tslog pre-masking divergent records.
maskValuesOfKeys: [],
minLevel: levelToMinLevel(settings.level),
type: "hidden", // no ansi formatting

View file

@ -26,7 +26,7 @@ describe.skipIf(process.platform === "win32")("releaseChildProcessOutputAfterExi
reject: false,
stdio: ["ignore", "pipe", "pipe"],
});
const releaseOutput = releaseChildProcessOutputAfterExit(child);
const releaseOutput = releaseChildProcessOutputAfterExit(child.nodeChildProcess);
let output = "";
child.stdout?.on("data", (chunk: Buffer) => {
output += chunk.toString();
@ -36,7 +36,7 @@ describe.skipIf(process.platform === "win32")("releaseChildProcessOutputAfterExi
// writes while JS is parked, so its pipe data and the idle timer are both
// ready when the event loop resumes.
await new Promise<void>((resolve) => {
child?.once("exit", () => {
child?.nodeChildProcess.once("exit", () => {
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 250);
resolve();
});
@ -53,7 +53,7 @@ describe.skipIf(process.platform === "win32")("releaseChildProcessOutputAfterExi
reject: false,
stdio: ["ignore", "pipe", "pipe"],
});
const releaseOutput = releaseChildProcessOutputAfterExit(child);
const releaseOutput = releaseChildProcessOutputAfterExit(child.nodeChildProcess);
let output = "";
child.stdout?.on("data", (chunk: Buffer) => {
output += chunk.toString();

View file

@ -170,13 +170,14 @@ async function runCommandWithOutputEncoding(
stripFinalNewline: false,
windowsVerbatimArguments: options.windowsVerbatimArguments,
});
const releaseOutput = releaseChildProcessOutputAfterExit(child);
child.once("exit", (code, signalValue) => {
const nodeChild = child.nodeChildProcess;
const releaseOutput = releaseChildProcessOutputAfterExit(nodeChild);
nodeChild.once("exit", (code, signalValue) => {
childExited = true;
childExitState = { code, signal: signalValue };
});
const terminationController = createCommandTerminationController({
child,
child: nodeChild,
cancelController,
baseEnv,
env,
@ -374,7 +375,7 @@ async function runCommandWithOutputEncoding(
const isCauseLessWindowsShimResult =
!termination &&
invocation.usesWindowsExitCodeShim &&
typeof child.pid === "number" &&
typeof nodeChild.pid === "number" &&
result.code === undefined &&
result.cause === undefined &&
!result.timedOut &&
@ -392,8 +393,8 @@ async function runCommandWithOutputEncoding(
if (
childExitState?.code != null ||
childExitState?.signal != null ||
child.exitCode != null ||
child.signalCode != null
nodeChild.exitCode != null ||
nodeChild.signalCode != null
) {
break;
}
@ -421,10 +422,10 @@ async function runCommandWithOutputEncoding(
throw error;
}
const resolvedSignal = result.signal ?? childExitState?.signal ?? child.signalCode ?? null;
const resolvedSignal = result.signal ?? childExitState?.signal ?? nodeChild.signalCode ?? null;
const resolvedCode = resolveProcessExitCode({
explicitCode: result.exitCode ?? childExitState?.code,
childExitCode: child.exitCode,
childExitCode: nodeChild.exitCode,
resolvedSignal,
usesWindowsExitCodeShim: invocation.usesWindowsExitCodeShim,
timedOut: termination === "timeout",
@ -474,7 +475,7 @@ async function runCommandWithOutputEncoding(
};
return {
pid: child.pid,
pid: nodeChild.pid,
stdout: decodeCapturedOutput(stdoutCapture, stdoutCaptureMode),
stderr: decodeCapturedOutput(stderrCapture, stderrCaptureMode),
stdoutTruncatedBytes: stdoutCapture.truncatedBytes || undefined,
@ -485,7 +486,7 @@ async function runCommandWithOutputEncoding(
stderrCapture.preservedLines.length > 0 ? stderrCapture.preservedLines : undefined,
code: normalizedCode,
signal: resolvedSignal,
killed: child.killed,
killed: nodeChild.killed,
termination: termination === "output-limit" ? "signal" : termination,
noOutputTimedOut: termination === "no-output-timeout",
outputLimitExceeded: termination === "output-limit" || undefined,

View file

@ -55,13 +55,8 @@ export function shouldSpawnWithShell(params: {
return false;
}
type SpawnCommandOptions = Omit<
ExecaOptions,
"env" | "extendEnv" | "shell" | "windowsHide" | "windowsVerbatimArguments"
> & {
type SpawnCommandOptions = ExecaOptions & {
baseEnv?: NodeJS.ProcessEnv;
env?: NodeJS.ProcessEnv;
windowsVerbatimArguments?: boolean;
};
export function spawnCommandWithInvocation<
@ -88,7 +83,7 @@ export function spawnCommandWithInvocation<
shell: false,
windowsHide: invocation.windowsHide,
windowsVerbatimArguments: invocation.windowsVerbatimArguments,
}) as unknown as ResultPromise<OptionsType>;
} as ExecaOptions) as unknown as ResultPromise<OptionsType>;
return { child, invocation };
}

View file

@ -61,7 +61,7 @@ export async function runExec(
stripFinalNewline: false,
timeout,
});
const releaseOutput = releaseChildProcessOutputAfterExit(subprocess);
const releaseOutput = releaseChildProcessOutputAfterExit(subprocess.nodeChildProcess);
const { stdout, stderr } = await subprocess.finally(releaseOutput);
const windowsEncoding = resolveWindowsConsoleEncoding();
const decodedStdout = decodeWindowsOutputBuffer({

View file

@ -28,6 +28,7 @@ type MockResult = {
};
type MockSubprocess = EventEmitter & {
nodeChildProcess: MockSubprocess;
exitCode: number | null;
finish: (result?: Partial<MockResult>) => void;
kill: ReturnType<typeof vi.fn>;
@ -53,6 +54,7 @@ function createMockSubprocess(params?: {
stdoutChunks?: Buffer[];
}): MockSubprocess {
const child = new EventEmitter() as MockSubprocess;
child.nodeChildProcess = child;
child.pid = 1234;
child.exitCode = null;
child.signalCode = null;
@ -67,7 +69,7 @@ function createMockSubprocess(params?: {
const completion = new Promise<MockResult>((resolvePromise) => {
resolve = resolvePromise;
});
// oxlint-disable-next-line unicorn/no-thenable -- Stub matches Execa's event-emitting promise shape.
// oxlint-disable-next-line unicorn/no-thenable -- Stub combines Execa's promise with its exposed Node child.
child.then = completion.then.bind(completion);
child.catch = completion.catch.bind(completion);
child.finally = completion.finally.bind(completion);

View file

@ -71,7 +71,7 @@ describe("check-workflows", () => {
expect(result.status).toBe(0);
expect(readFileSync(markerPath, "utf8")).toContain(
"github.com/rhysd/actionlint/cmd/actionlint@v1.7.11",
"github.com/rhysd/actionlint/cmd/actionlint@v1.7.12",
);
const preCommitArgs = readFileSync(preCommitMarkerPath, "utf8");
expect(preCommitArgs).toContain("run --config .pre-commit-config.yaml zizmor --files");

View file

@ -112,9 +112,9 @@ describe("OpenClaw performance workflow", () => {
const workflow = readFileSync(WORKFLOW, "utf8");
const installRun = findStep("Install OCM and Kova").run ?? "";
expect(workflow).toContain("OCM_VERSION: v0.2.25");
expect(workflow).toContain("OCM_VERSION: v0.2.29");
expect(workflow).toContain(
"OCM_LINUX_X64_SHA256: 57530199d21eb5bfa29695749928b40fd2869484c7edff69b7c65bfc84f2f1aa",
"OCM_LINUX_X64_SHA256: d966098d6ba2bc10891be3c76e162a37b07f28c4f51da75d2eb509886eb7e1cf",
);
expect(installRun).toContain(
'"https://github.com/shakkernerd/ocm/releases/download/${OCM_VERSION}/ocm-x86_64-unknown-linux-gnu.tar.gz"',

View file

@ -1455,8 +1455,8 @@ exit 0
expect(combined).toContain("MinGit-");
expect(combined).toContain("portable-git");
expect(combined).toContain("where.exe git.exe");
expect(windowsGit.indexOf('"MinGit-2.53.0.2-64-bit.zip"')).toBeLessThan(
windowsGit.indexOf('"MinGit-2.53.0.2-arm64.zip"'),
expect(windowsGit.indexOf('"MinGit-2.55.0.3-64-bit.zip"')).toBeLessThan(
windowsGit.indexOf('"MinGit-2.55.0.3-arm64.zip"'),
);
expect(
combined.match(/curl\.exe -fsSL --connect-timeout 10 --max-time 120 --retry 2/g),

View file

@ -20,7 +20,7 @@ import { prepareMinGitZip } from "../../scripts/e2e/parallels/windows-git.ts";
describe("Parallels Windows MinGit preparation", () => {
it("bounds the host asset download across connections, transfers, and retries", async () => {
const assetName = "MinGit-2.53.0.2-64-bit.zip";
const assetName = "MinGit-2.55.0.3-64-bit.zip";
const assetUrl = `https://example.test/${assetName}`;
const targetDir = path.join("tmp", "windows-smoke");
const targetPath = path.join(targetDir, assetName);

View file

@ -113,7 +113,7 @@ describe("run-opengrep.sh", () => {
);
expect(sarif.version).toBe("2.1.0");
expect(sarif.runs[0].tool.driver.name).toBe("Opengrep OSS");
expect(sarif.runs[0].tool.driver.semanticVersion).toBe("1.22.0");
expect(sarif.runs[0].tool.driver.semanticVersion).toBe("1.25.0");
expect(sarif.runs[0].results).toEqual([]);
expect(fs.existsSync(argsPath)).toBe(false);
});

View file

@ -46,7 +46,7 @@
"jsdom": "29.1.1",
"openclaw": "workspace:*",
"playwright": "1.61.1",
"vite": "8.1.3",
"vite": "8.1.5",
"vitest": "4.1.10"
}
}