Skip to content

Plan Mode rebased onto upstream/main + executing-state subsystem#71676

Closed
100yenadmin wants to merge 193 commits intoopenclaw:mainfrom
electricsheephq:rebase/pr70071-onto-main-2026-04-25
Closed

Plan Mode rebased onto upstream/main + executing-state subsystem#71676
100yenadmin wants to merge 193 commits intoopenclaw:mainfrom
electricsheephq:rebase/pr70071-onto-main-2026-04-25

Conversation

@100yenadmin
Copy link
Copy Markdown
Contributor

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/main by 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

  • Build green locally (`pnpm build` clean)
  • Plan-mode unit tests passing (90/90)
  • CI validation (this draft)
  • Behavioral smoke test against rebased build
  • Maintainer review (when ready, mark as ready-for-review)

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:

  1. Plan Confidence Gate carve-out (4 lines from `OPENAI_GPT5_EXECUTION_BIAS`) — upstream refactored prompt-overlay to plugin-sdk module that doesn't have a "Plan Confidence Gate" section. Better restored in plugin-side archetype prompt.
  2. Realtime-talk button in chat input toolbar — dropped during the UX overhaul v2 commit (chip relocation). Re-integrate into the new toolbar layout.
  3. plan-snapshot-persister cleanup wiring may need update for upstream's new `createCloseHandler` signature (soft regression — listener leak on gateway restart).

None of these block plan-mode functionality.

Build fix details

The 5 small fixes at the tip:

  • `server-runtime-subscriptions.ts`: dedupe duplicate `startPlanSnapshotPersister` import
  • `transport-message-transform.ts`: add missing `PendingToolCall` type import + default toolName for unpaired tool_use repair
  • `telegram/send.ts`: change `TelegramDocumentOpts.cfg` from `ReturnType` (boundary violation) to `OpenClawConfig` from plugin-sdk
  • `plan-archetype-bridge.ts`: pass `loadConfig()` result to `sendDocumentTelegram`
  • `exit-plan-mode-tool.ts`: replace `stringEnum()` with `Type.String({enum:...})` for upstream's stricter `TSchema` requirement (typebox API change)

Predecessor / related

Testing

CI will run the full sweep here. Locally verified:

  • `pnpm install --frozen-lockfile` clean
  • `pnpm build` clean (all 8 build phases pass)
  • `pnpm test src/agents/plan-mode` → 90/90 passing

steipete and others added 19 commits April 25, 2026 09:16
…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)
@openclaw-barnacle openclaw-barnacle Bot added docs Improvements or additions to documentation channel: telegram Channel integration: telegram app: macos App: macos app: web-ui App: web-ui gateway Gateway runtime commands Command implementations agents Agent runtime and tooling size: XL labels Apr 25, 2026
@100yenadmin 100yenadmin marked this pull request as ready for review April 25, 2026 18:06
Eva 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.
@100yenadmin 100yenadmin force-pushed the rebase/pr70071-onto-main-2026-04-25 branch from 0d4d37c to e62ed4c Compare April 25, 2026 19:13
@100yenadmin 100yenadmin requested review from a team as code owners April 25, 2026 19:13
@vincentkoc vincentkoc added the dirty Maintainer-applied auto-close for dirty/unrelated PR branches. label Apr 25, 2026
@openclaw-barnacle
Copy link
Copy Markdown

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling app: macos App: macos app: web-ui App: web-ui channel: telegram Channel integration: telegram commands Command implementations dirty Maintainer-applied auto-close for dirty/unrelated PR branches. docs Improvements or additions to documentation extensions: qa-lab gateway Gateway runtime scripts Repository scripts size: XL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants