Plan Mode rebased onto upstream/main + executing-state subsystem#71676
Closed
100yenadmin wants to merge 193 commits into
Closed
Plan Mode rebased onto upstream/main + executing-state subsystem#71676100yenadmin wants to merge 193 commits into
100yenadmin wants to merge 193 commits into
Conversation
…#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)
(cherry picked from commit 734748d)
100yenadmin
marked this pull request as ready for review
April 25, 2026 18:06
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
force-pushed
the
rebase/pr70071-onto-main-2026-04-25
branch
from
April 25, 2026 19:13
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: