Plan Mode rebased onto upstream/main + executing-state subsystem#71676
Closed
100yenadmin wants to merge 193 commits intoopenclaw:mainfrom
Closed
Plan Mode rebased onto upstream/main + executing-state subsystem#71676100yenadmin wants to merge 193 commits intoopenclaw:mainfrom
100yenadmin wants to merge 193 commits intoopenclaw:mainfrom
Conversation
…w#71414) (openclaw#71478) * fix(heartbeat): clamp scheduler delay to Node setTimeout cap (openclaw#71414) When `agents.defaults.heartbeat.every` resolves to >2_147_483_647 ms (~24.85d), the previous scheduleNext() called setTimeout with the raw delay. Node clamps any delay > 2^31-1 to 1 ms, fires the callback, and the heartbeat re-arms with the same oversized value - a tight loop that floods the log with TimeoutOverflowWarning and crashes the gateway with exit code 1. Clamp the computed delay to HEARTBEAT_MAX_TIMEOUT_MS (2_147_483_647) before calling setTimeout. The worst case is now one heartbeat every ~24.85d instead of crash-loop. Warn once per process when clamping fires, so a misconfigured "365d" remains visible without flooding. This is a defense-in-depth fix at the scheduler layer; loadConfig-level rejection is a broader change with more blast radius and a separate question (some users may legitimately want "every: 365d" to mean "effectively never"). The clamped behaviour is closer to that intent than the crash is. Test: new scheduler test sets heartbeat.every="365d" with fake timers, advances 60s, and asserts runSpy was never called (with the bug, it would be called ~60_000 times). * style: format heartbeat scheduler clamp * fix: share safe timeout delay clamp (openclaw#71478) (thanks @hclsys) --------- Co-authored-by: Peter Steinberger <steipete@gmail.com> (cherry picked from commit fd74fc5)
(cherry picked from commit 734748d)
added 4 commits
April 26, 2026 02:11
…gram bridge re-wire
The C2 commit in this stack (`feat(plan-mode): C2 Telegram PR-14 re-wire`)
re-wired the plan-archetype bridge to dynamic-import
`../../plugin-sdk/telegram.js` for the `sendDocumentTelegram` runtime
entrypoint:
```ts
const { sendDocumentTelegram } = await import("../../plugin-sdk/telegram.js");
```
But the file was deleted in the upstream commit `d3eeadba94`
(`refactor: drop private channel sdk facades`) which dropped
src/plugin-sdk/{telegram,slack,discord}.ts as part of a channel-SDK
restructure. The C2 commit's tests pass because they mock the
import; runtime fails because the actual file is missing.
Live-observed:
[agent/embedded] plan-bridge: persisted plan-2026-04-20-...md
[agent/embedded] plan-bridge attachment failed: Cannot find module
'/private/tmp/plugin-sdk/telegram.js'
Bridge-side: caught by the outer try/catch and downgraded to a warn
(approval flow is unblocked), but the user-visible markdown attachment
delivery is silently skipped on EVERY plan submit in a Telegram-bound
session.
Fix: restore src/plugin-sdk/telegram.ts as a minimal facade that
re-exports `TelegramDocumentOpts` (type) + `sendDocumentTelegram`
(runtime function) via the existing `loadBundledPluginPublicSurfaceModule`
loader pattern that resolves bundled plugin public-surface modules
at run time. Discord and Slack facades stay dropped per upstream
intent — only Telegram is restored because it's the single hard
dependency of the plan-mode bridge today.
If a future upstream pass re-removes channel facades, the bridge
will need to migrate to the channel-runtime registry pattern
instead of dynamic-importing this facade directly.
… (2026-04-20) Four bugs surfaced by post-`2d5cda2fa0` codex reviews (review IDs 4139614141, 4139756224, 4139936730, 4140074845). All orphan-linked comments (line: null) but the underlying code issues are real against current HEAD. Verified each before fixing. File: src/auto-reply/reply/commands-plan.ts:96 normalizeLowercaseStringOrEmpty returns string (never undefined; see src/shared/string-coerce.ts:31-33). The check second !== undefined && second !== 'edits' && second !== 'edit' was therefore ALWAYS true for bare /plan accept (because second === ''), firing the usage-error path and breaking the documented default approval form across every text channel. Fix: treat empty string the same as missing; accept only the edits / edit qualifiers or no qualifier. Added 7 parser-regression tests pinning the fix plus the trailing-tokens discipline described below. Same file. The parser only validated the first (and for accept, second) token; extra trailing tokens were silently ignored. Inputs like /plan accept edits now still executed as approve; /plan off later still toggled plan mode off. Added a rejectTrailingTokens helper and applied it to the no-arg commands (status, view, on, off, restate) plus /plan accept [edits] so typos can no longer silently mutate state. File: src/agents/plan-mode/accept-edits-gate.ts:529-549 The regex matched '*** Move File: <src> -> <dst>' which does not exist in the repo's apply_patch grammar. The actual syntax (per src/agents/apply-patch.ts:22-23) uses '*** Move to: <dst>' as a sub-marker nested inside an '*** Update File: <src>' hunk. Pre-fix, every real move-hunk's destination path was silently skipped; a move INTO a protected config path (~/.openclaw/config.toml) bypassed the acceptEdits protected-path gate entirely. Fix: replace moveRe with moveToRe matching the real grammar. The source path is already extracted by singlePathRe on the surrounding '*** Update File:' line; the new regex catches the destination. Added 6 regression tests covering the security-critical cases (move INTO protected path, move OUT of protected path, multiple moves in one patch, plain hunks still extracted). File: src/auto-reply/reply/agent-runner-execution.ts:909 consumePendingAgentInjection lived INSIDE the outer while (true) attempt loop. First iteration drained the queue; subsequent iterations (transient-HTTP retry at ~line 1504, ~1640) re-invoked consume against an already-empty queue and lost the [PLAN_DECISION] / [QUESTION_ANSWER] context on every retry. Fix: hoisted the consume call OUTSIDE while (true) so the captured text survives across retries. The inner hoist for runWithModelFallback (cherry-pick of 70a6e4b) is preserved; this is a second, outer-scope hoist on top of it. Note: this is the same scope as the deferred C4.2 item in the 1.0 follow-up PR. Codex independently flagged it and the fix here is small enough to land directly rather than wait for the follow-up — the queue's once-per-turn drain contract remains intact. - pnpm tsgo:core + pnpm tsgo:core:test: clean for touched surface (pre-existing moonshot-stream-wrappers error in untracked WIP file remains; unrelated). - pnpm test src/auto-reply/reply/commands-plan.test.ts: 41 pass (34 existing + 7 new parser regressions). - pnpm test src/agents/plan-mode/accept-edits-gate.test.ts: 72 pass (66 existing + 6 new Move-to regressions).
5 build errors surfaced after rebasing PR openclaw#70071 onto upstream/main (95b7a85). Each is a small merge-fallout fix: - server-runtime-subscriptions.ts: dedupe duplicate startPlanSnapshotPersister import (one of the take-both merges added it twice) - transport-message-transform.ts: add missing PendingToolCall type import + default toolName for unpaired tool_use repair - telegram/send.ts: change TelegramDocumentOpts.cfg from ReturnType<typeof loadConfig> (which would require importing core into extension, violating boundary) to OpenClawConfig from plugin-sdk; remove default opts={} since cfg is now required - plan-archetype-bridge.ts: pass loadConfig() result to sendDocumentTelegram - exit-plan-mode-tool.ts: replace stringEnum() with Type.String({enum:...}) to satisfy upstream's stricter TSchema requirement (typebox API change) All 90 plan-mode tests pass after these fixes. Build is green end-to-end.
- types.ts: replace dynamic require("node:crypto") with module-scope
randomUUID import. The bare require was unsafe in ESM-only runtimes
(TS->ESM builds). Module-scope import works in both module systems
and is the correct fallback for the cryptographic approvalId mint
when globalThis.crypto is unavailable.
- plan-archetype-bridge.ts: rebuild buildPlanAttachmentCaption to use
explicit double-newline join between logical blocks. The prior
.filter(Boolean) stripped the intentional blank-line separator and
the leading-newline-in-summaryLine produced double-newlines via
join("\n"). Caption now correctly renders as: title \n\n summary
\n\n resolve-instructions.
Both addressed Copilot review comments on PR openclaw#71676.
0d4d37c to
e62ed4c
Compare
|
Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch. |
This was referenced Apr 25, 2026
7 tasks
This was referenced Apr 26, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this PR does
Rebases the Plan Mode work from PR #70071 onto current
upstream/main(was diverging from upstream by ~3,056 commits). Adds 5 small build-fix commits at the tip for compatibility with the newer host (typebox API change, plugin-SDK boundary type, missing imports surfacing from take-both merges).Same conceptual scope as #70071 — same 192 files, same 9-PR umbrella (#70101). Different SHAs because the entire commit history was replayed.
Why
PR #70071 was diverging from
upstream/mainby 6 days of upstream churn (~3,056 commits). Force-pushing to that PR's branch would have made the commit log noise unreadable. New PR with the rebased state is cleaner for review and easier to keep in sync going forward.Status
Carry-forward items (logged but deferred)
A few small things were dropped during rebase to keep merge surface small. Each is logged in `audit/REBASE_NOTES.md` for separate restoration:
None of these block plan-mode functionality.
Build fix details
The 5 small fixes at the tip:
Predecessor / related
Testing
CI will run the full sweep here. Locally verified: