Skip to content

[Plan Mode 7/8] Docs, QA, and help#70019

Closed
100yenadmin wants to merge 9 commits into
openclaw:mainfrom
electricsheephq:restack/68939-pr9-docs-qa-help
Closed

[Plan Mode 7/8] Docs, QA, and help#70019
100yenadmin wants to merge 9 commits into
openclaw:mainfrom
electricsheephq:restack/68939-pr9-docs-qa-help

Conversation

@100yenadmin

Copy link
Copy Markdown
Contributor

Summary

[Plan Mode 7/8] — part of the 8-part decomposition of #68939, stacking on #69449 (Part 1).

Stacks on: previous part (see [Plan Mode 6/8] / [Plan Mode 1/9 #69449]).

Plan-mode operator runbook + skill-based docs + QA scenarios + /plan help text. Stacks on Part 6/8.

Lines: ~2371

Diff note

This PR is opened cross-repo (head: 100yenadmin:restack/68939-pr9-docs-qa-help) against openclaw:main. Because GitHub doesn't support stacked-PR bases that point at fork refs, the displayed diff cumulatively includes all previous parts in the stack. Reviewers should focus on the changes specific to this part — see commit list for the new content beyond what previous parts established.

Test status

After post-v2026.4.21 rebase: all targeted plan-mode tests pass (53/53 in unit-fast for approval + integration suites). The full suite has pre-existing vitest workspace project-name conflicts unrelated to this work.

Related

Eva added 9 commits April 22, 2026 12:39
…ion tests (rebase fixup)

Pure rebase fixup. The `exit_plan_mode` tool gained a mandatory `title`
parameter in upstream main between Part 5's original base and current
HEAD. After rebasing Part 5 onto upstream/main, four integration tests
(in `src/agents/plan-mode/integration.test.ts`) were failing with
`ToolInputError: exit_plan_mode requires a title field` instead of
the originally-asserted error messages.

This commit just adds `title: "..."` to the four failing test calls
so they exercise the validation paths they were originally testing
(empty plan, multiple in_progress, unknown status).

No production code changes.
Copilot AI review requested due to automatic review settings April 22, 2026 06:20
@greptile-apps

greptile-apps Bot commented Apr 22, 2026

Copy link
Copy Markdown
Contributor

Too many files changed for review. (185 files found, 100 file limit)

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Adds “Plan Mode” operator/docs and expands runtime support for plan-mode lifecycle (tools, gates, persistence, QA, and Telegram delivery) as part of the stacked decomposition for #68939.

Changes:

  • Register universal /plan command and introduce plan-mode tools (enter_plan_mode, exit_plan_mode, plan_mode_status, ask_user_question) plus related tool catalog/display metadata.
  • Add plan-mode infrastructure (mutation gate, approval state machine, pending injection bridge, nudges, auto-enable matching, plan hydration, plan archetype prompt + persistence + Telegram attachment bridge).
  • Expand/repair tests and add QA scenarios + docs/runbooks covering plan mode operations and troubleshooting.

Reviewed changes

Copilot reviewed 96 out of 185 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
src/auto-reply/reply/commands-handlers.runtime.ts Registers /plan handler in command runtime.
src/auto-reply/reply/agent-runner.misc.runreplyagent.test.ts Updates assertion to reflect skip-cache freshness behavior.
src/auto-reply/commands-registry.shared.ts Adds builtin plan slash command definition.
src/agents/transport-message-transform.ts Improves missing tool_result repair text + bounds log volume.
src/agents/tools/update-plan-tool.test.ts Updates expectations and adds coverage for close-on-complete + closure gate.
src/agents/tools/sessions-spawn-tool.ts Adds plan-mode concurrency cap, cleanup keep override, and parent/child run tracking.
src/agents/tools/sessions-spawn-tool.test.ts Updates tests for role removal + adds plan-mode concurrency cap tests.
src/agents/tools/plan-mode-status-tool.ts Introduces read-only plan-mode status introspection tool.
src/agents/tools/enter-plan-mode-tool.ts Introduces enter_plan_mode tool surface.
src/agents/tools/cron-tool.ts Documents “resume after wait” recipe for plan-mode workflows.
src/agents/tools/ask-user-question-tool.ts Adds ask_user_question tool and validation logic.
src/agents/tools/ask-user-question-tool.test.ts Adds validation + determinism + content tests for ask_user_question.
src/agents/tool-display-config.ts Adds display config for new plan-mode tools and fixes question detail keys.
src/agents/tool-description-presets.ts Adds display summaries + describes plan-mode tools and archetype guidance.
src/agents/tool-catalog.ts Registers plan-mode tools in the core tool catalog.
src/agents/test-helpers/fast-openclaw-tools-sessions.ts Updates update_plan mock signature + exports statuses for tests.
src/agents/subagent-registry.test.ts Adapts mocks/outcome shape for new parent-drain behavior.
src/agents/subagent-registry.steer-restart.test.ts Uses partial actual mock and adds parent openSubagentRunIds remap test.
src/agents/subagent-registry-run-manager.ts Drains completed/killed subagents from parents and remaps on steer restart.
src/agents/subagent-announce.ts Adds plan-mode-aware suffix to announce instruction and reads requester mode.
src/agents/skills/workspace.ts Carries skill plan templates into snapshots for seeding path.
src/agents/skills/types.ts Adds SkillPlanTemplateStep and snapshot resolvedPlanTemplates.
src/agents/skills/skill-planner.ts Adds plan template payload builder + normalization.
src/agents/skills/frontmatter.ts Parses skill plan templates from frontmatter (kebab/camel + content alias).
src/agents/skills/frontmatter.test.ts Adds test coverage for plan-template parsing semantics.
src/agents/plan-mode/types.ts Adds plan-mode core types + secure approvalId generation + decision injection builder.
src/agents/plan-mode/reference-card.ts Adds bootstrap-injected plan-mode reference card text.
src/agents/plan-mode/plan-nudge-crons.ts Adds scheduling/cleanup for plan-mode nudge cron jobs.
src/agents/plan-mode/plan-mode-debug-log.ts Adds structured plan-mode debug log + shared enablement helper w/ TTL cache.
src/agents/plan-mode/plan-archetype-prompt.ts Adds archetype prompt + plan filename helpers.
src/agents/plan-mode/plan-archetype-prompt.test.ts Tests archetype prompt content + filename helper behavior.
src/agents/plan-mode/plan-archetype-persist.test.ts Tests persistence, collisions, and storage error classification.
src/agents/plan-mode/plan-archetype-bridge.ts Persists plan markdown and (best-effort) sends Telegram document attachment.
src/agents/plan-mode/mutation-gate.ts Introduces plan-mode mutation gate with exec read-only whitelist.
src/agents/plan-mode/mutation-gate.test.ts Adds unit tests for mutation gate behavior.
src/agents/plan-mode/integration.test.ts Adds wiring-level integration test for plan mode enablement + gating.
src/agents/plan-mode/index.ts Exports plan-mode public surface.
src/agents/plan-mode/auto-enable.ts Adds regex-based auto-enable matching helper with compiled cache.
src/agents/plan-mode/auto-enable.test.ts Tests auto-enable matching and malformed pattern handling.
src/agents/plan-mode/approval.ts Adds approval state machine resolver + approved-plan injection builder.
src/agents/plan-hydration.ts Adds post-compaction plan hydration formatting.
src/agents/plan-hydration.test.ts Tests hydration formatting and filtering behavior.
src/agents/pi-tools.ts Threads planMode + live accessors into before-tool-call hook context.
src/agents/pi-tools.before-tool-call.ts Adds plan-mode mutation gate + post-approval acceptEdits constraint gate.
src/agents/pi-embedded-runner/skills-runtime.test.ts Updates snapshot reload logic tests for resolvedPlanTemplates presence/absence.
src/agents/pi-embedded-runner/run/params.ts Threads planMode + live accessors through embedded run params.
src/agents/pi-embedded-runner/run/helpers.ts Raises iteration defaults and adds user override + subagent cap.
src/agents/pi-embedded-runner/run/attempt.spawn-workspace.test-support.ts Stubs skill plan-template seeder for workspace spawn tests.
src/agents/pi-embedded-runner/run.overflow-compaction.test.ts Uses config override to keep retry-limit test fast under higher defaults.
src/agents/pi-embedded-runner/run.incomplete-turn.test.ts Updates retry counts and adds escalation + plan-mode disablement assertions.
src/agents/pi-embedded-runner/pending-injection.ts Adds compat shim over typed pending-injection queue.
src/agents/pi-embedded-runner/pending-injection.test.ts Tests injection consume/compose behavior against temp session store.
src/agents/openclaw-tools.ts Wires plan-mode tools into OpenClaw tools set and threads runId into tools.
src/agents/openclaw-tools.registration.ts Adds config gate for plan-mode tool registration.
skills/plan-mode-101/SKILL.md Adds Plan Mode 101 reference skill + self-test guidance.
qa/scenarios/gpt54-plan-mode-default-off.md QA scenario ensuring plan mode stays off by default for GPT‑5.4.
qa/scenarios/gpt54-mandatory-tool-use.md QA scenario ensuring tool use for factual queries.
qa/scenarios/gpt54-injection-scan.md QA scenario baseline for injection-scanner non-interference.
qa/scenarios/gpt54-cancelled-status.md QA scenario for cancelled plan steps behavior.
qa/scenarios/gpt54-act-dont-ask.md QA scenario for “act on obvious defaults” behavior.
extensions/telegram/src/send.ts Adds sendDocumentTelegram helper for file uploads with safety checks.
extensions/telegram/runtime-api.ts Exposes sendDocumentTelegram + opts type from runtime API.
docs/tools/slash-commands.md Documents /plan universal command surface.
docs/plans/PLAN-MODE-OPERATOR-RUNBOOK.md Adds operator runbook for plan-mode incident diagnosis.
docs/concepts/plan-mode.md Adds user-facing plan mode concept doc with multi-channel behavior.
apps/shared/OpenClawKit/Sources/OpenClawProtocol/GatewayModels.swift Adds plan-mode fields/error codes to protocol models.
apps/shared/OpenClawKit/Sources/OpenClawKit/Resources/tool-display.json Adds tool-display metadata for plan-mode tools.
apps/macos/Sources/OpenClawProtocol/GatewayModels.swift Mirrors protocol model updates in macOS target.

Comment on lines +19 to +32
const MUTATION_TOOL_BLOCKLIST = new Set([
"apply_patch",
"bash",
"edit",
"exec",
"gateway",
"message",
"nodes",
"process",
"sessions_send",
"sessions_spawn",
"subagents",
"write",
]);

Copilot AI Apr 22, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sessions_spawn and other plan-mode-affordance tools (ask_user_question, enter_plan_mode, and plan_mode_status) appear to be intended/required during plan mode (per the reference card/docs and the new sessions_spawn plan-mode concurrency logic), but the mutation gate currently blocks them by default-deny (and explicitly blocklists sessions_spawn). This makes plan-mode investigation flows unable to spawn research subagents or ask clarifying questions while plan mode is active. Update the gate to allow these plan-mode tools (and remove sessions_spawn from the blocklist / add it to the allowlist) so the runtime behavior matches the documented plan-mode lifecycle.

Copilot uses AI. Check for mistakes.
Comment on lines +41 to +50
const PLAN_MODE_ALLOWED_TOOLS = new Set([
"read",
"web_search",
"web_fetch",
"memory_search",
"memory_get",
"update_plan",
"exit_plan_mode",
"session_status",
]);

Copilot AI Apr 22, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sessions_spawn and other plan-mode-affordance tools (ask_user_question, enter_plan_mode, and plan_mode_status) appear to be intended/required during plan mode (per the reference card/docs and the new sessions_spawn plan-mode concurrency logic), but the mutation gate currently blocks them by default-deny (and explicitly blocklists sessions_spawn). This makes plan-mode investigation flows unable to spawn research subagents or ask clarifying questions while plan mode is active. Update the gate to allow these plan-mode tools (and remove sessions_spawn from the blocklist / add it to the allowlist) so the runtime behavior matches the documented plan-mode lifecycle.

Copilot uses AI. Check for mistakes.
Comment on lines +68 to +81
// Terminal-state guard. Approved, edited, and timed_out are terminal —
// they require a fresh exit_plan_mode call (which mints a new approvalId)
// before any new action can apply. Rejected stays open for re-approval
// or re-rejection.
if (
current.approval !== "pending" &&
current.approval !== "rejected" &&
current.approval !== "none"
) {
return current;
}
if (action === "timeout" && current.approval !== "pending") {
return current;
}

Copilot AI Apr 22, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Allowing approval actions to resolve when current.approval === "none" can incorrectly transition a session to "approved"/"edited" (and mode: "normal") without an active pending approval, if resolvePlanApproval() is called with action: "approve" | "edit" | "reject" and no expectedApprovalId. Tighten the guard so approve/edit/reject only apply to "pending" (and, if desired, "rejected" for re-approval semantics), but not "none".

Suggested change
// Terminal-state guard. Approved, edited, and timed_out are terminal —
// they require a fresh exit_plan_mode call (which mints a new approvalId)
// before any new action can apply. Rejected stays open for re-approval
// or re-rejection.
if (
current.approval !== "pending" &&
current.approval !== "rejected" &&
current.approval !== "none"
) {
return current;
}
if (action === "timeout" && current.approval !== "pending") {
return current;
}
// State-transition guard.
// - approve/edit/reject only apply while an approval is active ("pending")
// or after a rejection ("rejected"), preserving re-approval semantics.
// - timeout only applies to an actively pending approval.
// States like "none", "approved", "edited", and "timed_out" require a
// fresh exit_plan_mode call (which mints a new approvalId) before any new
// approval action can apply.
if (action === "timeout") {
if (current.approval !== "pending") {
return current;
}
} else if (current.approval !== "pending" && current.approval !== "rejected") {
return current;
}

Copilot uses AI. Check for mistakes.
Comment on lines +80 to +86
for (const minutes of intervals) {
if (minutes <= 0 || !Number.isFinite(minutes)) {
continue;
}
const fireAtMs = now + Math.floor(minutes * 60_000);
const fireAtIso = new Date(fireAtMs).toISOString();
try {

Copilot AI Apr 22, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

assertSafeCronSessionTargetId is imported inside the per-interval loop, which adds repeated module-load overhead (even if cached, it still adds async/await churn) and complicates error attribution. Since the validation is per-sessionKey (not per-interval), move the import and validation outside the loop (once per schedule call), then proceed to schedule intervals only if validation passes.

Copilot uses AI. Check for mistakes.
Comment on lines +104 to +122
// against the same constraints the cron service applies in
// `assertSafeCronSessionTargetId` (no `/`, `\`, or `\0`
// characters). Pre-fix, an exotic sessionKey could produce a
// sessionTarget the cron jobs.ts validator rejects, causing
// the plan-nudge schedule call to fail at gateway-side
// assertion time. Belt-and-suspenders here keeps the failure
// local + actionable instead of a generic cron-validator
// error 60 seconds later.
const { assertSafeCronSessionTargetId } = await import("../../cron/session-target.js");
try {
assertSafeCronSessionTargetId(params.sessionKey);
} catch (validationErr) {
params.log?.warn?.(
`plan-nudge schedule skipped: sessionKey "${params.sessionKey}" fails cron sessionTarget validation: ${
validationErr instanceof Error ? validationErr.message : String(validationErr)
}`,
);
continue;
}

Copilot AI Apr 22, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

assertSafeCronSessionTargetId is imported inside the per-interval loop, which adds repeated module-load overhead (even if cached, it still adds async/await churn) and complicates error attribution. Since the validation is per-sessionKey (not per-interval), move the import and validation outside the loop (once per schedule call), then proceed to schedule intervals only if validation passes.

Suggested change
// against the same constraints the cron service applies in
// `assertSafeCronSessionTargetId` (no `/`, `\`, or `\0`
// characters). Pre-fix, an exotic sessionKey could produce a
// sessionTarget the cron jobs.ts validator rejects, causing
// the plan-nudge schedule call to fail at gateway-side
// assertion time. Belt-and-suspenders here keeps the failure
// local + actionable instead of a generic cron-validator
// error 60 seconds later.
const { assertSafeCronSessionTargetId } = await import("../../cron/session-target.js");
try {
assertSafeCronSessionTargetId(params.sessionKey);
} catch (validationErr) {
params.log?.warn?.(
`plan-nudge schedule skipped: sessionKey "${params.sessionKey}" fails cron sessionTarget validation: ${
validationErr instanceof Error ? validationErr.message : String(validationErr)
}`,
);
continue;
}
// against the same constraints the cron service applies in
// `assertSafeCronSessionTargetId` (no `/`, `\`, or `\0`
// characters). Pre-fix, an exotic sessionKey could produce a
// sessionTarget the cron jobs.ts validator rejects, causing
// the plan-nudge schedule call to fail at gateway-side
// assertion time. Belt-and-suspenders here keeps the failure
// local + actionable instead of a generic cron-validator
// error 60 seconds later.
const { assertSafeCronSessionTargetId } = await import("../../cron/session-target.js");
try {
assertSafeCronSessionTargetId(params.sessionKey);
} catch (validationErr) {
params.log?.warn?.(
`plan-nudge schedule skipped: sessionKey "${params.sessionKey}" fails cron sessionTarget validation: ${
validationErr instanceof Error ? validationErr.message : String(validationErr)
}`,
);
return [];
}

Copilot uses AI. Check for mistakes.
Comment on lines +71 to +82
const safeTitle = (title ?? "").trim() || "Plan";
const escTitle = escapeHtml(safeTitle);
const safeSummary = (summary ?? "").trim();
const summaryLine = safeSummary ? `\n${escapeHtml(safeSummary)}` : "";
return [
`<b>${escTitle}</b> — plan submitted for approval. See attached.`,
summaryLine,
"",
"Resolve with: <code>/plan accept</code> | <code>/plan accept edits</code> | <code>/plan revise &lt;feedback&gt;</code>",
]
.filter(Boolean)
.join("\n");

Copilot AI Apr 22, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

summaryLine includes a leading newline (\n...) but is also joined with "\n", which effectively introduces an extra blank line when a summary is present and makes formatting harder to reason about. Prefer pushing the escaped summary as its own line (without embedding a leading newline) and let the join control all line breaks.

Copilot uses AI. Check for mistakes.
Comment on lines +240 to +250
const spawnParentCtx = opts?.runId ? getAgentRunContext(opts.runId) : undefined;
if (
spawnParentCtx?.inPlanMode === true &&
(spawnParentCtx.openSubagentRunIds?.size ?? 0) >= MAX_CONCURRENT_SUBAGENTS_IN_PLAN_MODE
) {
throw new ToolInputError(
`Plan mode allows only ${MAX_CONCURRENT_SUBAGENTS_IN_PLAN_MODE} concurrent research ${
MAX_CONCURRENT_SUBAGENTS_IN_PLAN_MODE === 1 ? "subagent" : "subagents"
}. Wait for the current child to return before spawning another.`,
);
}

Copilot AI Apr 22, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ACP path performs two separate getAgentRunContext(opts.runId) lookups (spawnParentCtx and later parentCtx) for closely related plan-mode decisions (concurrency cap and cleanup override). Reuse the already fetched context (or fetch once and share) to avoid duplicated logic paths and reduce the chance of divergence when the criteria evolve.

Copilot uses AI. Check for mistakes.
Comment on lines +314 to +315
const parentCtx = opts?.runId ? getAgentRunContext(opts.runId) : undefined;
const parentInPlanMode = parentCtx?.inPlanMode === true;

Copilot AI Apr 22, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ACP path performs two separate getAgentRunContext(opts.runId) lookups (spawnParentCtx and later parentCtx) for closely related plan-mode decisions (concurrency cap and cleanup override). Reuse the already fetched context (or fetch once and share) to avoid duplicated logic paths and reduce the chance of divergence when the criteria evolve.

Copilot uses AI. Check for mistakes.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 90b696c622

ℹ️ 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".

Comment on lines +412 to +414
if (prefix.startsWith("~/") && tildeForm.startsWith(prefix)) {
return matchedProtectedPath(filePath, prefix);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Block protected config roots without trailing slash

The protected-path check only uses startsWith against prefixes that all end in /, so exact root targets like ~/.openclaw or /etc/openclaw do not match and are allowed under acceptEdits. In a post-approval session this lets a delete/apply_patch-family call modify or remove the protected config root itself, bypassing the intended hard config-change block for these directories.

Useful? React with 👍 / 👎.

Comment on lines +144 to +147
if (verifiedCriteria && !acceptanceCriteria) {
throw new ToolInputError(
`plan[${index}].verifiedCriteria requires plan[${index}].acceptanceCriteria to be set`,
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Permit merge-time verifiedCriteria updates

This unconditional validation rejects any step that provides verifiedCriteria without also repeating acceptanceCriteria, which breaks merge-mode incremental verification for existing steps that already have acceptance criteria stored from a previous update. In practice, a valid merge: true patch that only advances verification progress will fail with a 400 unless the caller resends the full criteria list each time.

Useful? React with 👍 / 👎.

@100yenadmin

Copy link
Copy Markdown
Contributor Author

Closed for sequential rollout — please disregard cumulative diff.

This PR was opened with --base main against the upstream repo. Cross-repo PRs can't reference fork branches as bases, so opening all 8 stack parts simultaneously produced cumulative diffs (each PR included all prior parts, 10k → 30k lines). That's review-hostile and reproduces exactly the problem the original umbrella #68939 had.

The fix: sequential rollout. Each [Plan Mode N/9] PR opens only AFTER the previous one merges to main, so its diff is incremental.

Currently open:

Next: when 1/9 merges, [Plan Mode 2/9] Core backend MVP opens with a clean ~2,000-line diff. Etc.

This PR's branch is preserved on the fork and will be reopened with a clean diff in its turn.

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 docs Improvements or additions to documentation gateway Gateway runtime size: XL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants