feat(tokenjuice): bundle the native adapter#69946
Conversation
🔒 Aisle Security AnalysisWe found 1 potential security issue(s) in this PR:
1. 🟠 Arbitrary code execution via untrusted npm_execpath in runtime deps installer
Description
Key points:
Vulnerable code: const rawNpmExecPath = ... ? env.npm_execpath : undefined;
const npmExecPath = rawNpmExecPath && isNpmCliPath(rawNpmExecPath) ? rawNpmExecPath : undefined;
...
const npmCliPath = npmCliCandidates.find((candidate) => pathImpl.isAbsolute(candidate) && existsSync(candidate));
if (npmCliPath) {
return { command: execPath, args: [npmCliPath, ...params.npmArgs] };
}RecommendationDo not trust Recommended fixes (pick one):
const npmCliCandidates = [
pathImpl.resolve(nodeDir, "../lib/node_modules/npm/bin/npm-cli.js"),
pathImpl.resolve(nodeDir, "node_modules/npm/bin/npm-cli.js"),
];
import fs from "node:fs";
const real = fs.realpathSync(rawNpmExecPath);
const realNodeDir = fs.realpathSync(nodeDir);
if (!real.startsWith(realNodeDir + pathImpl.sep)) throw new Error("Untrusted npm_execpath");Additionally, consider sanitizing the installer env:
Analyzed PR: #69946 at commit Last updated on: 2026-04-22T07:06:17Z |
Greptile SummaryThis PR adds a public Confidence Score: 5/5Safe to merge; implementation is consistent with existing plugin registry patterns and all cache paths are covered. All findings are P2: one style suggestion about awaiting the factory call in the test (harmless since the current factory is synchronous), and a minor temp-directory cleanup omission. No logic errors, no data integrity issues, and no security concerns beyond what the existing plugin trust model already handles. src/agents/pi-embedded-runner.extensions.test.ts — minor test hygiene (void factory call and missing tmpdir cleanup). Prompt To Fix All With AIThis is a comment left during a code review.
Path: src/agents/pi-embedded-runner.extensions.test.ts
Line: 98-103
Comment:
**Unawaited async factory call may cause flaky assertions**
The factory result is discarded with `void`, so if any future `ExtensionFactory` is async (returns a `Promise<void>` instead of `void`), `handlers.set` won't be called before the `expect` on line 103. The test would silently pass with an empty `handlers` map since the factory type allows async. Awaiting the call makes the test robust now and safe for async factories later.
```suggestion
await cachedFactories[0]?.({
on(event: string, handler: Function) {
handlers.set(event, handler);
},
} as never);
```
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: src/agents/pi-embedded-runner.extensions.test.ts
Line: 48
Comment:
**Temp directory not cleaned up after test**
`fs.mkdtempSync` creates a directory in the OS temp folder that is never removed. Other plugin seam tests in this codebase typically call `fs.rmSync(tmp, { recursive: true, force: true })` inside `afterEach`. This isn't a correctness issue, but it accumulates test artifacts on developer machines.
How can I resolve this? If you propose a fix, please make it concise.Reviews (1): Last reviewed commit: "feat(plugins): register embedded extensi..." | Re-trigger Greptile |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c11473c734
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0d477d7385
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2a6c8b4350
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 601e8ef865
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
601e8ef to
8d181fe
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f0e903dd9c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
f069c7a to
3579bfb
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9b05d48607
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d5a72de4d6
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| .toSorted((left, right) => left.localeCompare(right)); | ||
| const missingSpecs = deps | ||
| .filter((dep) => !hasDependencySentinel(dependencySearchRoots, dep)) | ||
| .filter((dep) => !hasDependencySentinel([installRoot], dep)) |
There was a problem hiding this comment.
Avoid npm installs inside source-checkout plugin roots
Restricting the missing-dependency check to installRoot here means source-checkout plugins now fall through to npm install in the plugin directory whenever <plugin>/node_modules is absent. For extensions/tokenjuice (and other workspace packages), that nested npm install fails with EUNSUPPORTEDPROTOCOL because the manifest contains workspace:* dev dependencies, so enabling the plugin can fail before it registers. This needs a source-checkout-safe install target (or a source-checkout bypass) instead of invoking npm in the workspace package root.
Useful? React with 👍 / 👎.
* fix: skip clean run-node runtime restaging * fix: stabilize testbox test suite * test: align plugin test contracts * fix(agents): normalize malformed assistant replay content * fix(agents): harden replay normalization guards * fix(agents): distill replay content normalization * fix: normalize assistant replay content (openclaw#69850) (thanks @fuller-stack-dev) * Make harness failures fail honestly (openclaw#69981) * Agents: fail honestly on harness errors * Docs: clarify Codex harness fallback * perf(ci): unblock node compat and trim runtime compat test * docs: record testbox full-suite profile * chore(agents): prefer local validation over testbox * refactor: generalize route target parsing * refactor: drop provider reconnect shim * test: generalize legacy state migration coverage * refactor: keep plugin login policy out of core * fix(googlechat): harden google auth transport (openclaw#69812) * fix(googlechat): localize google auth gaxios compat * fix(googlechat): declare undici for staged runtime deps * fix(googlechat): harden google auth transport * fix(googlechat): narrow credential file reads * fix(googlechat): preserve auth proxy transport * fix(googlechat): allow symlinked auth files * fix(googlechat): atomically load auth files * fix(googlechat): eagerly buffer auth responses * fix(googlechat): cap auth response buffering * fix(googlechat): pin staged auth runtime deps * fix(googlechat): buffer auth responses as array buffers * Update CHANGELOG.md * fix(googlechat): reject unstreamed auth responses * fix(googlechat): use ambient fetch for auth transport * fix(googlechat): keep guarded auth fetch on runtime path * fix(googlechat): align staged zod range * chore(lockfile): sync googlechat zod spec * test: reuse plugin auto-enable fixture environment * refactor: remove plugin tool display overrides from core * fix(agents): guard replay convert hook * test: generalize media fetch token fixtures * feat(tencent): remove Token Plan provider and auth (openclaw#69996) Co-authored-by: albertxyu <albertxyu@tencent.com> * docs: fix stale community links in README and CONTRIBUTING (openclaw#69945) Co-authored-by: Jonathan Amponsah <amponsahjonathan442@gmail.com> * docs: generalize core channel examples * fix(config): enforce resolved runtime channel config * fix(channels): thread runtime config through sends * fix(telegram): isolate sent-message cache stores * fix(channels): preserve setup promotion fallbacks * docs: remove bundled channel examples from core types * refactor: generalize voice audio compatibility * perf(test): avoid bundled channel fallback in model override tests * docs: generalize core routing comments * refactor: gate setup promotion by manifest feature * docs(tui): document local config repair flow (openclaw#69995) (thanks @fuller-stack-dev) * docs(tui): document local config repair flow * docs(tui): clarify local TUI examples * docs(config): gate local TUI repair flow * docs(tui): fix local repair docs --------- Co-authored-by: Ayaan Zaidi <hi@obviy.us> * docs: generalize plugin runtime comments * perf(test): skip setup promotion metadata fallback * perf(slack): narrow runtime-setter + lazy-load 4 modules + narrow 2 SDK surfaces (openclaw#69317) Lazy load modules showing a ~50% gateway startup performance improvement * feat(tokenjuice): bundle the native adapter (openclaw#69946) * feat(plugins): register embedded extension factories * feat(tokenjuice): bundle the native adapter * fix(tokenjuice): gate the bundled embedded extension seam * fix(tokenjuice): refresh runtime sidecar baseline * fix(plugins): harden bundled embedded extensions * fix(plugins): install source bundled runtime deps * fix(tokenjuice): sync lockfile importer * fix(plugins): validate reused runtime dep versions * fix(plugins): restore tokenjuice CI contract * fix(plugins): remove tokenjuice dts bridge * fix(tokenjuice): repair openclaw type shim * fix(plugins): harden bundled runtime deps * fix(plugins): keep source checkout runtime deps local * fix(plugins): isolate bundled runtime dep installs * fix(cli): keep plugin startup registration non-activating * fix(cli): keep loader overrides out of plugin cli options * runtime-taskflow: sync blocked waiting edge into durable jobs * docs(skill): tighten duplicate triage mirror rules * docs(tokenjuice): add bundled plugin guide (openclaw#70038) * docs(tokenjuice): add bundled plugin guide * docs(tokenjuice): sort nav entry * docs(changelog): mention tokenjuice embedded support (openclaw#70039) * Fix Codex auth handoff for the app-server harness (openclaw#69990) * Codex: fix auth bridge token shape * Codex: preserve selected auth tokens * Codex: prefer selected profile id token * Codex: honor inherited Codex home --------- Co-authored-by: Val Alexander <68980965+BunsDev@users.noreply.github.com> * fix(agents): keep mocked OpenAI Responses on HTTP (openclaw#69815) * fix(agents): keep mocked OpenAI responses on HTTP * docs(changelog): add entry for mocked responses fix * fix(release-check): assert bundled plugin runtime deps after packed postinstall (openclaw#70035) * fix(release-check): assert bundled plugin runtime deps after packed postinstall Release-check already validates source dist/extensions runtime deps are staged, but runPackedBundledChannelEntrySmoke never re-validates after the packed postinstall runs against the installed tarball. That gap is how 2026.4.21 shipped without @whiskeysockets/baileys in dist/extensions/whatsapp/node_modules, because the source staging passed while the installed layout was left broken. Re-use collectBuiltBundledPluginStagedRuntimeDependencyErrors against the installed packageRoot right after runPackedBundledPluginPostinstall and fail release-check if any declared runtime dependency is missing from the plugin-local node_modules. * fix(release-check): check postinstalled dep sentinels at packageRoot/node_modules Codex review on openclaw#70035 caught that collectInstalledBundledPluginRuntimeDepErrors was pointing at dist/extensions/<id>/node_modules, but packed postinstall installs and probes sentinels at packageRoot/node_modules (see dependencySentinelPath in scripts/postinstall-bundled-plugins.mjs). The previous implementation would have falsely failed release-check on healthy packed installs while still missing the original WhatsApp regression. Reuse discoverBundledPluginRuntimeDeps from postinstall-bundled-plugins.mjs so the release guard uses the exact same dep discovery and sentinel paths the packed postinstall uses. Update the test fixtures accordingly so they model the real install layout. * feat(openai): add codex device-code auth and fix login options in menu (openclaw#69557) Merged via squash. Prepared head SHA: 4918ed6 Co-authored-by: vincentkoc <25068+vincentkoc@users.noreply.github.com> Co-authored-by: BunsDev <68980965+BunsDev@users.noreply.github.com> Reviewed-by: @BunsDev * fix: make slack socket health event-driven * fix: clean up slack socket waiters on start hooks * fix: use transport activity for stale health * docs: update changelog for channel health (openclaw#69833) (thanks @bek91) * ci: serialize parity gate scenarios * ci: stabilize parity gate runner * ci: pin qa parity tool profile * ci: build runtime before parity gate * test: harden qa parity config cleanup * fix: drop stale socket mode opt-in * test: harden qa parity runtime staging * ci: build private qa parity runtime * test: harden qa private runtime staging * fix: harden tokenjuice host typing * fix: keep custom pi tools executable * tooling: add corepack pnpm fallback for git hooks * qa: harden parity gate execution (openclaw#70045) * fix: keep claude cli sessions warm (openclaw#69679) * feat(cli): keep claude cli sessions warm * test(cli): cover claude live session reuse * fix(cli): harden claude live session reuse * fix(cli): redact mcp session key logs * fix(cli): bound claude live session turns * fix(cli): reuse claude live sessions on resume * refactor(cli): canonicalize claude live argv * fix(cli): preserve claude live resume state * fix(cli): close dead claude live sessions * fix(cli): serialize claude live session creates * fix(cli): count pending claude live sessions * fix(cli): tighten claude live resume abort * fix(cli): reject closed claude live sessions * fix(cli): refresh claude live fingerprints * fix(cli): stabilize MCP resume hash * fix: preserve claude live inline resume (openclaw#69679) --------- Co-authored-by: Frank Yang <frank.ekn@gmail.com> * fix(pair): render /pair qr as media (openclaw#70047) * fix(pair): render pair qr as media * fix(gateway): preserve media reply threading * fix(gateway): harden webchat media replies * fix(plugin-sdk): keep trustedLocalMedia internal * docs(changelog): note pair qr media fix * Update CHANGELOG with recent fixes and enhancements Updated changelog to include recent fixes and enhancements. * fix(codex): unchain app-server defaults (openclaw#70082) * place permission under each branch of bot permissions for discord docs (openclaw#69218) Merged via squash. Prepared head SHA: dd6ae52 Co-authored-by: epicseven-cup <59263116+epicseven-cup@users.noreply.github.com> Co-authored-by: velvet-shark <126378+velvet-shark@users.noreply.github.com> Reviewed-by: @velvet-shark * fix: lower the log level from info to debug (openclaw#70108) * fix(cli): keep provider-owned sessions through implicit expiry * fix(cli): upgrade legacy mcp session reuse * fix(gateway): preserve cli session binding metadata * fix: update cli session changelog (openclaw#70106) * fix(config): accept truncateAfterCompaction (openclaw#68395) Merged via squash. Prepared head SHA: bf45148 Co-authored-by: MonkeyLeeT <6754057+MonkeyLeeT@users.noreply.github.com> Co-authored-by: hxy91819 <8814856+hxy91819@users.noreply.github.com> Reviewed-by: @hxy91819 * chore(pi): remove local pr prompts Remove repo-local /landpr and /reviewpr prompt templates so maintainers use the externally maintained workflow instead. These flows remain available from the external maintainers repo via globally installed Pi skills and prompts. * fix(plugins): avoid doctor crash on legacy interactive state (openclaw#70135) * fix(plugins): hydrate legacy interactive state * fix(plugins): avoid doctor crash on legacy interactive state (openclaw#70135) (thanks @ngutman) * fix(cli): stabilize oauth session auth epochs * test(cli): cover oauth auth epoch continuity * fix: update cli session changelog (openclaw#70132) * fix: require cli auth epoch version (openclaw#70132) * fix(qqbot): add interaction intents (openclaw#70143) * feat(qqbot): add intents interaction * fix(qqbot): add interaction intents (openclaw#70143) (thanks @cxyhhhhh) --------- Co-authored-by: sliverp <870080352@qq.com> * fix(codex): apply GPT-5 prompt overlay (openclaw#70175) * ci: consolidate test shard fanout * chore: update dependencies * fix(agent): align pi session tool options * ci: downsize blacksmith runners * ci: run aggregate checks off blacksmith * fix(gateway): harden WS pairing locality * fix: fail closed on plugin integrity drift * ci: keep long matrix aggregates on blacksmith * build: refresh a2ui bundle hash * ci: keep cpu-sensitive lanes larger * fix(doctor): skip token generation for trusted-proxy and none auth modes (openclaw#59055) runGatewayAuthHealth() only excluded 'password' and 'token' (with existing token) from its needsToken check. When gateway.auth.mode was set to 'trusted-proxy' or 'none', doctor --fix would incorrectly: 1. Flag the config as 'missing a token' 2. Prompt to generate a gateway token 3. Overwrite auth.mode to 'token' in openclaw.json This silently broke trusted-proxy deployments (common in SaaS/reverse-proxy setups) by replacing the delegated auth mode with token auth. The fix aligns runGatewayAuthHealth() with the existing hasExplicitGatewayInstallAuthMode() in auth-install-policy.ts, which already correctly returns false for 'password', 'none', and 'trusted-proxy'. Co-authored-by: wujiaming88 <wujiaming88@example.com> * fix: preserve restart continuations after reboot (openclaw#63406) (thanks @VACInc) * gateway: add restart continuation sentinel * gateway: address restart continuation review * gateway: handle restart continuation edge cases * gateway: keep restart continuations on threaded delivery path * fix(gateway): harden restart continuation routing * test(gateway): cover restart continuation edge cases * docs(agent): clarify restart continuation usage * fix: preserve restart continuations after reboot (openclaw#63406) (thanks @VACInc) --------- Co-authored-by: VACInc <3279061+VACInc@users.noreply.github.com> Co-authored-by: Ayaan Zaidi <hi@obviy.us> * ci: move lightweight automation off blacksmith * docs(changelog): fix cron attribution * docs(changelog): correct cron contributors * docs(changelog): thank cron contributors * ci: skip aggregate fan-in after cancellation * ci: refresh ci concurrency group * fix(gateway): write restart sentinels atomically * docs(changelog): note restart sentinel atomic writes * fix(gateway): allow silent metadata-upgrade pairing for loopback CLI clients (openclaw#70224) Loopback CLI clients (cli_container_local, shared_secret_loopback_local) with valid shared-secret auth previously got disconnected with 1008 pairing required whenever the paired device record's platform or deviceFamily string differed from what the CLI claimed at connect time. PR openclaw#69431 added the shared_secret_loopback_local locality but deferred the metadata-upgrade reason from the auto-approval allowlist. That deferral created an unrecoverable handshake loop in practice: every CLI connect triggers a fresh metadata-upgrade request, the Control UI has no approval surface for this reason, and non-interactive shells cannot complete pairing. This broke every non-interactive openclaw agent use case when paired device keys are replicated across hosts or installs are migrated across platforms. Extend shouldAllowSilentLocalPairing to auto-approve metadata-upgrade for cli_container_local and shared_secret_loopback_local localities only. Browser / Control-UI / remote paths retain existing approval- required behavior. Gateway still logs every metadata refresh via the existing security audit line for operator review. Add 4 unit tests covering the decision table for metadata-upgrade across all four localities. Related: openclaw#69397, openclaw#69431 * refactor(gateway): unify startup task execution * fix(pairing): clear stale requests on device removal (openclaw#70239) * fix(pairing): clear stale requests on device removal * docs(changelog): note pairing stale request cleanup * perf(test): avoid bundled setup in auto-enable tests * ci: rebalance agentic node tests * fix(gateway): preserve restart continuation chat type * ci: reduce blacksmith test pressure * ci: offload short linux checks * ci: keep build smoke on blacksmith * ci: rebalance runtime config tests * ci: consolidate short test workers * ci: keep native lanes native scoped * fix(dotenv): block connector endpoint workspace overrides (openclaw#70240) * fix(dotenv): block connector endpoint workspace overrides * docs(changelog): note dotenv endpoint blocklist * fix(dotenv): block Matrix per-account scoped homeserver overrides * feat: Add /models add hot-reload model registration (openclaw#70211) * feat(models): add chat model registration with hot reload * docs(changelog): add models entry for pr 70211 * fix(models): harden add flow follow-ups * fix models add review follow-ups * harden models add config writes * tighten plugin boundary invariant * move models add adapters behind sdk facades * avoid ollama-specific core facade * ci: reuse build artifacts for gateway topology * telegram: align model picker callback auth (openclaw#70235) * telegram: align model picker callback auth * docs(changelog): note telegram model callback auth fix * fix(telegram): use runtime config for model callback auth * Revert "ci: reuse build artifacts for gateway topology" This reverts commit be31776. * ci: trim gateway watch build profile * ci: keep workflow edits off windows lane * ci: parallelize additional boundary guards * fix: load staged dist-runtime plugins in docker * test: stabilize audio directive tag test * fix: add plugin load debug shape * fix(agents): accept silent no-reply turns * fix: default claude cli to stdio sessions * ci: add fast docker install smoke * docs: update claude cli stdio notes * fix(config): preserve source config during recovery * test: keep loader fixture inside plugin boundary * fix(discord): thread runtime config through guild actions * fix(discord): use resolveDiscordChannelParentIdSafe in voice command path openclaw#69908 switched native slash commands, listeners, and the model picker to the safe accessor for partial thread channels, but the voice /join command still reads channel.parentId through the unsafe "parentId" in channel pattern. Route it through the same helper so the voice command path does not crash with "Cannot access rawData on partial Channel" when invoked from inside a thread on @buape/carbon >=0.16. * fix(discord): use resolveDiscordChannelNameSafe for voice channel override name Applies the same safe-accessor pattern to the adjacent name field. If @buape/carbon implements name as a getter that also reads _rawData (like parentId), the previous `"name" in channel` pattern would throw for the same reason. Aligns with the fix for parentId in the same call site. * fix(plugins): harden bundled runtime dep staging * fix: harden Discord voice commands in threads * ci: run install smoke for runtime dep staging * fix(hooks): standardize outbound routing metadata * test(slack): drop obsolete adapter hook test * ci: downsize install smoke runner * fix(discord): make thread parent inheritance opt-in * fix: make Discord thread parent inheritance opt-in (openclaw#69986) (thanks @Blahdude) * refactor: move channel doctor migrations to plugins * refactor(memory): migrate lancedb recall to prompt-build hook * refactor: build channel setup input generically * tooling: finish corepack-only pnpm repo paths * fix(agent-runner): accept injected normalizeMediaPaths in runAgentTurnWithFallback * fix(agent-runner): share media-path normalizer with runAgentTurnWithFallback to prevent duplicate outbound media * test(agent-runner): regression — createReplyMediaPathNormalizer.runtime not called when normalizer injected * fix: share reply media context (openclaw#68111) (thanks @ayeshakhalid192007-dev) * refactor: move doctor capabilities to channel manifests * test: keep hook and slack tests on public boundaries * refactor: declare channel add flags in manifests * refactor: use memory slot defaults in core paths * test: keep config fallback test on generic plugin channel * fix(cli-session): only hash static extraSystemPrompt for session reuse The extraSystemPrompt includes per-message dynamic content from buildInboundMetaSystemPrompt() (timestamps, message IDs, sender metadata) that changes on every inbound message. This causes the extraSystemPromptHash to differ every turn, triggering a session reset with reason='system-prompt' and discarding all CLI session context. Fix: split extraSystemPrompt into dynamic (inbound meta) and static (group context, group intro, group system prompt, exec override hints) portions. Only hash the static portion for session reuse validation. The full extraSystemPrompt (dynamic + static) is still sent to the CLI as before — only the session stability hash uses the static subset. Fixes openclaw#70100 * fix: address review feedback — handle empty static prompt and remove stray blank lines - Always pass extraSystemPromptStatic as string (even when empty) so the fallback in prepare.ts never accidentally hashes dynamic content - Use explicit undefined check (params.extraSystemPromptStatic !== undefined) instead of ?? nullish coalescing to avoid edge case where empty static string falls through to hashing the full dynamic prompt - Remove extra blank line * fix(cli-session): forward static prompt hash input * fix: stabilize Claude CLI session prompt hashing * fix(hooks): expose typed gateway startup context * fix(plugins): preserve source activation config * ci: use dist cache instead of artifact upload * refactor(hooks): centralize bundled subagent hook wiring * refactor(discord): centralize thread channel context * refactor(discord): share channel action param parsing * refactor(discord): share partial channel test fixtures * ci: parallelize extension batch groups * fix(discord): break monitor threading import cycle * refactor(hooks): centralize matrix subagent hook wiring * fix(hooks): prefer shared outbound conversation context * ci: fold build smoke into artifact job * test(memory): drop stale dreaming hook doubles * fix: honor ACP spawn model overrides (openclaw#70210) Honor explicit ACP sessions_spawn model overrides and preserve ACP runtime cwd options.\n\nThanks @felix-miao. * fix(hooks): canonicalize thread ownership conversation ids * test(acpx): exercise registered reply_dispatch hook * fix: persist CLI session clearing atomically (openclaw#70298) Persist stale CLI session clearing through the session-store merge path and add regression coverage for Claude binding removal.\n\nThanks @HFConsultant. * ci: add scoped docker gateway e2e * test(skill-workshop): exercise registered prompt hook * fix: drop silent parent replies while subagents are pending (openclaw#69942) Drop bare parent NO_REPLY payloads while spawned subagents are pending, preserving quiet parent turns until child completion delivers the real reply.\n\nThanks @neeravmakwana. * ci: rotate stale concurrency group * test(memory): exercise registered auto-capture hook * ci: skip windows for test-only changes * fix(auto-reply): preserve streaming reply directives (openclaw#70243) Preserve streamed MEDIA/reply/audio directives across chunk boundaries and phase-aware final_answer delivery.\n\nThanks @zqchris. * test(memory): exercise registered auto-recall hook * Fix Slack HTTP route registry dispatch * fix: route Slack HTTP webhook dispatch (openclaw#70275) (thanks @FroeMic) * ci: narrow windows check scope * fix(slack): pass cfg into resolveToken from downloadSlackFile call site Commit 95331e5 ("fix(channels): thread runtime config through sends") migrated resolveToken to a 3-arg signature (explicit, accountId, cfg) and updated the getClient call site at actions.ts:83. The sibling call inside downloadSlackFile at actions.ts:445 was not migrated and still dropped opts.cfg, so the cfg-only resolution branch was unreachable from that path. Current production callers (action-runtime.ts:386-389) always inject a resolved readToken into opts.token before calling downloadSlackFile, so this is defense-in-depth today -- the broken path is not hit in runtime. Landing this closes the call-site migration gap and adds test coverage for the cfg-only resolution contract on downloadSlackFile. Note: pre-commit typecheck hook bypassed because upstream/main has 14 pre-existing TS errors in unrelated packages (discord, qa-lab, qqbot, slack/monitor/provider.ts, tokenjuice, pi-embedded-runner) -- verified reproducible on clean HEAD 4a16cf8 without this diff. * fix: preserve Slack download cfg token fallback (openclaw#70160) (thanks @martingarramon) * fix(telegram): mark polling transport dirty on 409 conflict (openclaw#69787) When getUpdates returns 409 Conflict (e.g. 'terminated by other getUpdates request'), the polling runtime previously retried on the same HTTP keep-alive TCP socket because markDirty() was only called in the isRecoverable branch. Telegram treats that connection as the 'old' session and keeps terminating it — producing a sustained low-rate 409 retry loop (observed a few per minute after eliminating duplicate pollers). Broaden the dirty-mark condition to fire on isConflict as well as isRecoverable so the next cycle forces a fresh TCP connection. Update the existing 'reuses transport after getUpdates conflict' test — which previously locked in the buggy behavior — to assert the new correct behavior: one fresh transport is built, the stale one is closed. * test(telegram): update monitor test for openclaw#69787 transport rebuild on 409 Sibling test in monitor.test.ts asserted the pre-fix behavior (single transport reused across cycles on 409). My openclaw#69787 change rebuilds the transport on 409 so Telegram sees a fresh TCP socket — update the assertion to match. Two transports are now expected: the initial one plus the rebuild after the conflict. * test: tighten Telegram polling conflict coverage (openclaw#69873) (thanks @hclsys) * test(plugins): guard legacy bundled hook regressions * fix(telegram): lower webhook callback timeout to 5s openclaw#16763 added `onTimeout: "return"` with `timeoutMilliseconds: 10_000` (grammY default). In practice, Telegram's webhook servers abort the read well before 10s when handler latency is LLM-bound: `getWebhookInfo` reports `last_error_message: "Read timeout expired"` and pending updates pile up, cascading into multi-minute reply lag. Reproducible A/B on identical infra (same region, same bot token): - Minimal Python echo bot: 5 back-to-back webhook RTTs 341-642ms, clean. - OpenClaw current main: intermittent Read timeout expired, 1-5 min lag. The handler still runs to completion; only the Telegram-facing ack is sooner. grammY's deployment guide suggests 5s for long-running handlers. No new config surface; minimal one-line change to the existing constant and its test assertion. If a configurable timeout is wanted, that can be a follow-up (see stale openclaw#7754). * fix: lower Telegram webhook callback timeout (openclaw#70146) (thanks @friday-james) * fix(amazon-bedrock): inject cache points for application inference profile ARNs (openclaw#69953) * fix(amazon-bedrock): inject cache points for application inference profile ARNs pi-ai's internal supportsPromptCaching checks model.id for specific Claude model name patterns (e.g. "-4-", "claude-3-7-sonnet"), which fails for application inference profile ARNs that don't contain the model name. This causes prompt caching to silently break for Bedrock users with application inference profiles. Work around this by detecting when pi-ai would miss cache point injection (via piAiWouldInjectCachePoints mirror) and patching the Converse API payload via onPayload to add cachePoint blocks to the system prompt and last user message — matching the same format pi-ai uses natively. The fix is safe: - Checks for existing cache points to avoid double-injection - Respects cacheRetention: "none" - Defaults to "short" retention (matching pi-ai default) - Becomes a no-op once upstream pi-mono#2925 is fixed Fixes openclaw#19279 Upstream: badlogic/pi-mono#2925 * fix(amazon-bedrock): tighten app-profile cache injection --------- Co-authored-by: Your Name <you@example.com> Co-authored-by: Vincent Koc <vincentkoc@ieee.org> * test(plugins): pin bundled hook registration surfaces * perf: keep gateway live probes off helper imports * test(plugins): pin bundled hook names * test: cover Telegram webhook timeout reply continuation * fix(hooks): fail open without thread ownership routing * fix(hooks): canonicalize slack thread ownership ids * fix(plugins): repair bundled deps on activation * fix(hooks): normalize thread ownership channel allowlists * fix: clear phantom Claude CLI resumes (openclaw#70317) Verify Claude CLI session transcripts before reuse and clear phantom bindings with transcript-missing instead of passing stale --resume ids.\n\nFixes openclaw#70177. * fix(discord): restore DM reactions and guild activation * fix(gateway): redact audio payloads from chat history * fix(media): load inbound media store URIs * fix(agents): dedupe emitted TTS media * fix(image): resolve custom provider model IDs * docs: note media delivery fixes * fix(hooks): normalize thread ownership slack id casing * fix(hooks): track thread ownership mentions case-insensitively * ci: move node aggregate checks off blacksmith * fix(hooks): tighten thread ownership mention matching * test(skill-workshop): pin disabled hook wiring * ci: rotate main concurrency queue * fix(hooks): skip skill workshop capture when review is off * fix(discord): harden partial thread channels * test(memory): pin disabled lifecycle hook wiring * ci: merge short auto-reply node shards * fix(hooks): use live config for memory dreaming runtime * feat(commands): gate /models add with modelsWrite (openclaw#70321) * fix: restore Pi embedded tool allowlist Restore the Pi embedded session tool allowlist for OpenAI/OpenAI Codex GPT-5 runs and compaction sessions after Pi 0.68.1 began treating session tools as a global allowlist. Local validation: pnpm check:changed. GitHub validation: check/check-additional/node shards green; parity gate red on unrelated config.patch stale/rate-limit QA harness scenario after plugins.allow restart. * ci: rotate cancelled docs queue * fix(hooks): refresh active memory config at runtime * ci: balance extension tests across fewer workers * fix: normalize opus 4.7 context window Normalize Anthropic-owned Opus 4.7 context reporting to 1M while keeping inferred and bare discovery paths conservative. - normalize Anthropic and claude-cli Opus 4.7 runtime/status context metadata to 1M - keep inferred-provider and bare discovery ids on discovered conservative limits - add regression coverage for provider, lookup, status, and discovery-cache paths - keep the Telegram abort-signal wrapper typing narrow so changed-scope validation stays green * fix(hooks): respect live skill workshop config * fix(hooks): use live thread ownership config * perf(plugins): cache normalized jiti aliases * fix: honor explicit strict-agentic retry contract Honor explicit strict-agentic execution contracts for incomplete-turn retry guards across providers, including local/compatible models that opt in without relying on OpenAI model inference. Validation: - pnpm test src/agents/pi-embedded-runner/run.incomplete-turn.test.ts - pnpm check:changed - GitHub CI + parity gate green Thanks @ziomancer. * test(media): harden media store URI validation * ci: keep extension test fanout under two minutes * fix(hooks): respect live lancedb memory config * test(slack): cover send.ts customize-scope fallback retry path (openclaw#69009) Adds 5 vitest cases for postSlackMessageBestEffort's silent retry behavior when Slack rejects a chat:write.customize-identity post: - Retry on err.data.needed matching chat:write.customize - Retry on chat:write.customize in response_metadata.acceptedScopes - Retry on chat:write.customize in response_metadata.scopes - Rethrow on different missing_scope (e.g. channels:history) - Rethrow when identity is empty (hasCustomIdentity returns false) * fix(discord): normalize ACP thread binding targets Normalize Discord ACP thread-binding channel targets at the REST/thread-create boundary while preserving current-conversation binding keys.\n\nThanks @Zetarcos. * test(slack): provide send config in identity fallback tests * fix(hooks): use live memory-core config during dreaming runs * fix(memory-lancedb): retry failed runtime initialization * runtime-taskflow: tighten waiting sync review fixes * refactor(hooks): centralize live plugin config lookup * fix(qa): deflake parity approval preflight * fix(agents): centralize native websocket endpoint checks * docs(changelog): note websocket endpoint classifier fix * fix: clarify browser playwright-core install guidance * fix(types): narrow live thread ownership config * test(plugins): pin live config hook guards * fix(agents): restore pi session tool activation * docs(changelog): note pi session tool activation fix * build: harden tsdown wrapper --------- Co-authored-by: Shakker <shakkerdroid@gmail.com> Co-authored-by: Peter Steinberger <steipete@gmail.com> Co-authored-by: FullerStackDev <263060202+fuller-stack-dev@users.noreply.github.com> Co-authored-by: Ayaan Zaidi <hi@obviy.us> Co-authored-by: pashpashpash <nik@vault77.ai> Co-authored-by: Vincent Koc <vincentkoc@ieee.org> Co-authored-by: JuniperSling <80268751+JuniperSling@users.noreply.github.com> Co-authored-by: albertxyu <albertxyu@tencent.com> Co-authored-by: Jonathan <82057176+mgalore@users.noreply.github.com> Co-authored-by: Jonathan Amponsah <amponsahjonathan442@gmail.com> Co-authored-by: Alex Knight <aknight@atlassian.com> Co-authored-by: Andy <andrew@artisticforge.studio> Co-authored-by: Onur Solmaz <2453968+osolmaz@users.noreply.github.com> Co-authored-by: Val Alexander <68980965+BunsDev@users.noreply.github.com> Co-authored-by: Dewaldt Huysamen <dhuysamen@gmail.com> Co-authored-by: vincentkoc <25068+vincentkoc@users.noreply.github.com> Co-authored-by: Bek <bek.akhmedov@gmail.com> Co-authored-by: Frank Yang <frank.ekn@gmail.com> Co-authored-by: Jacky <59263116+epicseven-cup@users.noreply.github.com> Co-authored-by: velvet-shark <126378+velvet-shark@users.noreply.github.com> Co-authored-by: Sliverp <38134380+sliverp@users.noreply.github.com> Co-authored-by: Ted Li <tl2493@columbia.edu> Co-authored-by: MonkeyLeeT <6754057+MonkeyLeeT@users.noreply.github.com> Co-authored-by: hxy91819 <8814856+hxy91819@users.noreply.github.com> Co-authored-by: Nimrod Gutman <nimrod.gutman@gmail.com> Co-authored-by: cxy <49286167+cxyhhhhh@users.noreply.github.com> Co-authored-by: sliverp <870080352@qq.com> Co-authored-by: Garming <jiaming_wu@foxmail.com> Co-authored-by: wujiaming88 <wujiaming88@example.com> Co-authored-by: VACInc <hixvac@gmail.com> Co-authored-by: VACInc <3279061+VACInc@users.noreply.github.com> Co-authored-by: Jason Perlow <jperlow@gmail.com> Co-authored-by: Devin Robison <drobison00@users.noreply.github.com> Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com> Co-authored-by: Claw Kowalski <szponeczek@forserial.org> Co-authored-by: Hana Chang <hana@hanamizuki.tw> Co-authored-by: Oliver Camp <olivercamp@Olivers-MacBook-Pro-3.local> Co-authored-by: ayeshakhalid192007-dev <ayeshakhalid192007@gmail.com> Co-authored-by: Zijun Lin <zijunlin@Zijuns-Mac-mini.local> Co-authored-by: Felix Miao <felix.us.miao@gmail.com> Co-authored-by: HFConsultant <62076556+HFConsultant@users.noreply.github.com> Co-authored-by: Neerav Makwana <neeravmakwana@gmail.com> Co-authored-by: zqchris <chrisz83@gmail.com> Co-authored-by: froemic <m.froehlich1994@gmail.com> Co-authored-by: Martin Garramon <martin@yulicreative.ai> Co-authored-by: HCL <chenglunhu@gmail.com> Co-authored-by: friday-james <lamcollin37@gmail.com> Co-authored-by: anirudhmarc <43162556+anirudhmarc@users.noreply.github.com> Co-authored-by: Your Name <you@example.com> Co-authored-by: Peter Steinberger <peter@steipete.me> Co-authored-by: Josh Lehman <josh@martian.engineering> Co-authored-by: Devin Matthews <zionitesoldier@gmail.com> Co-authored-by: Zetarcos <117005244+Zetarcos@users.noreply.github.com>
Summary
Describe the problem and fix in 2–5 bullets:
registerEmbeddedExtensionFactory(...)seam, added a bundledextensions/tokenjuiceplugin that importstokenjuice/openclawthrough a local runtime boundary, and added labeler/lockfile/test coverage for that bundled plugin.tool_result_persistfallback.Change Type (select all)
Scope (select all touched areas)
Linked Issue/PR
Root Cause (if applicable)
For bug fixes or regressions, explain why this happened, not just what changed. Otherwise write
N/A. If the cause is unclear, writeUnknown.Regression Test Plan (if applicable)
For bug fixes or regressions, name the smallest reliable test coverage that should catch this. Otherwise write
N/A.src/agents/pi-embedded-runner.extensions.test.ts,extensions/tokenjuice/index.test.ts,extensions/tokenjuice/manifest.test.tsUser-visible / Behavior Changes
List user-visible changes (including defaults/config).
If none, write
None.tokenjuiceplugin that can be enabled instead of installed as a separate plugin package.Diagram (if applicable)
For UI changes or non-trivial logic flows, include a small ASCII diagram reviewers can scan quickly. Otherwise write
N/A.Security Impact (required)
Yes/No): YesYes/No): NoYes/No): NoYes/No): YesYes/No): NoYes, explain risk + mitigation:Repro + Verification
Environment
openai/gpt-5.4plugins.entries.tokenjuice.enabled=trueSteps
tokenjuice/openclawexport exists indist.extensions/tokenjuice/node_modules/tokenjuiceso the bundled plugin resolves the new export through a normal package import path.tokenjuiceplugin, build embedded extension factories, and invoke thetool_resulthandler with agit statusexec result.Expected
Actual
git statusoutput plus tokenjuice metadata.Evidence
Attach at least one:
Human Verification (required)
What you personally verified (not just CI), and how:
pnpm test:serial extensions/tokenjuice/index.test.ts extensions/tokenjuice/manifest.test.ts src/agents/pi-embedded-runner.extensions.test.ts; real bundled-plugin smoke throughloadOpenClawPlugins(...)+buildEmbeddedExtensionFactories(...)using the tokenjuice worktree through a normal package import path.tool_resultoutput.pnpm check:changedstill fails on unrelated existing typecheck issues insrc/docs/clawhub-plugin-docs.test.ts,src/gateway/reconnect-gating.test.ts,src/i18n/registry.test.ts,src/ui-app-settings.agents-files-refresh.test.ts,extensions/qa-lab/web/src/main.ts, andextensions/qqbot/src/bridge/setup/finalize.ts.Review Conversations
If a bot review conversation is addressed by this PR, resolve that conversation yourself. Do not leave bot review conversation cleanup for maintainers.
Compatibility / Migration
Yes/No): YesYes/No): NoYes/No): NoRisks and Mitigations
List only real risks for this PR. Add/remove entries as needed. If none, write
None.tokenjuice@0.6.0does not exposetokenjuice/openclawyet.tokenjuice@^0.6.0, so once the tokenjuice release from feat(openclaw): export the embedded adapter vincentkoc/tokenjuice#25 lands, bundled runtime dependency staging can resolve the new export without another architecture change.AI Assistance