Skip to content

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

Closed
100yenadmin wants to merge 193 commits into
openclaw: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 into
openclaw: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
…#71478)

* fix(heartbeat): clamp scheduler delay to Node setTimeout cap (#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 (#71478) (thanks @hclsys)

---------

Co-authored-by: Peter Steinberger <[email protected]>
(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 6 commits April 26, 2026 02:11
…bug log events

Part 7 of Plan Mode 1.0 follow-up. Adds optional correlation fields
to the plan-mode debug event union so operators can trace a single
approval cycle across multiple log lines. Sessions can have many
approvals in their lifetime — sessionKey alone is insufficient for
cycle-level debugging.

Event shape changes (src/agents/plan-mode/plan-mode-debug-log.ts):
- Added optional `approvalRunId?: string` to: state_transition,
  gate_decision, synthetic_injection, nudge_event, subagent_event,
  approval_event, toast_event
- Added optional `approvalId?: string` to: state_transition,
  gate_decision, synthetic_injection, approval_event, toast_event
- Pre-existing emitters keep the current logging shape (both fields
  are optional); new emitters populate them when available.

Emitter wiring (src/gateway/sessions-patch.ts):
- Threaded `approvalRunId` + `approvalId` through the two
  approval_event emitters (rejected_by_subagent_gate and accepted
  paths) and the paired `synthetic_injection` event for
  [PLAN_DECISION] approved/edited. Operators can now grep a single
  approval cycle via:
    tail -F ~/.openclaw/logs/gateway.err.log |
      grep '\[plan-mode/' | grep approvalRunId=<id>

Test coverage (src/agents/plan-mode/plan-mode-debug-log.test.ts):
- 2 new tests pin the correlation-field pass-through for
  approval_event and synthetic_injection. Existing 17 tests still
  pass.

Deferred to future follow-ups:
- /plan self-test harness (synthetic plan-mode flow validator) —
  substantial new module + CLI subcommand.
- Full correlation-field wiring across all emitters (nudge crons,
  subagent lifecycle, gate decisions) — landed in subsequent commits
  as those surfaces are touched.

Verification:
- pnpm tsgo:core: clean for touched surface
- pnpm lint: clean for touched surface (5 pre-existing errors in
  untracked qa-lab/helpers remain out of scope)
- pnpm test on sessions-patch + plan-mode-debug-log: 68 pass
….0 follow-up

Part 8 of Plan Mode 1.0 follow-up. Closes the doc loop for the C1-C4
+ C7 commits landed on this branch.

New file: docs/plans/PLAN-MODE-OPERATOR-RUNBOOK.md
- Field guide for operators triaging plan-mode incidents.
- Covers debug log activation (persistent config flag + env var
  path), per-approval cycle tracing via the new C7 correlation
  fields, and 5 common symptom → fix paths:
    1. Approval card stays after click (PLAN_APPROVAL_EXPIRED)
    2. Subagent stall blocks approval (BLOCKED_BY_SUBAGENTS /
       WAITING_FOR_SUBAGENT_SETTLE)
    3. Empty-response stall post-approval (C4.2 deferred)
    4. Nudge cron noise during pending approval
    5. Plan-mode state lost after compaction / restart
- Plus dedicated sections on shell-escape bypass detection (C4.1),
  adversarial XSS regression (C1-R3), disk-full handling (C1-R4),
  and cross-channel dedup (C1-R5).

Updates: docs/concepts/plan-mode.md
- Troubleshooting section now links to the operator runbook for
  deeper incident triage scenarios beyond the basic PLAN_APPROVAL_EXPIRED
  case.

Updates: docs/plans/PLAN-MODE-ARCHITECTURE.md
- New 'Shipped in Plan Mode 1.0 follow-up' section enumerates the
  5 landed commits (C1 through C7) with their scope.
- Updated the 'Deferred items' list with post-1.0 status — marks
  C4.1 (shell-escape), C4.3 (legacy-scalar dedup verified closed),
  C4.4 (approvalRunId guard), and C7 (debug correlation) as
  shipped. C4.2 (retry re-hydration) remains deferred.
- New 'Still deferred out of 1.0 follow-up' table for C3b, C4.2,
  C5, C6, C7b with rationale for each.

No code changes. Verification: pnpm docs lint passes on the markdown
(Mintlify link rules: root-relative paths, no .md suffix).
…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.
…-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 #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 #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