Skip to content

feat(cli): /goal pause|resume|clear control from the TUI - #3026

Merged
Astro-Han merged 4 commits into
apache:mainfrom
me2seeks:feat/cli-goal-control
Aug 19, 2026
Merged

feat(cli): /goal pause|resume|clear control from the TUI#3026
Astro-Han merged 4 commits into
apache:mainfrom
me2seeks:feat/cli-goal-control

Conversation

@me2seeks

@me2seeks me2seeks commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

Summary

The only way to steer an autonomous goal from the TUI was asking the model to call GoalPause/GoalResume/GoalClear — a burned model turn that can hit the goal-control decline rules in goal-tools.ts. And the pause semantics were invisible: Ctrl+C on a goal continuation turn auto-pauses the durable goal, and attaching to a session with a live goal auto-continues it, both without a word.

  • /goal pause|resume|clear dispatch through the driver's new controlGoal (host goal.control with expectedRevision optimistic retry, mirroring the desktop client's clearGoal). Writes take the runControl gate; typed mid-turn they refuse with a clear message instead of steering into the model or being silently swallowed.
  • Auto-pause notice: a goal that transitions into paused while attached (typically the abort auto-pause after Ctrl+C) prints a one-line notice naming the reason and the /goal resume | /goal clear controls. A /goal pause we initiated prints its own confirmation instead of a duplicate notice.
  • Attach notice: attaching to a session whose goal is active/waiting announces the running loop — a token-burning loop never resumes silently.
  • Pre-validation: invalid transitions (pause from paused, resume from active, clear of a terminal record) get a plain message instead of the host's operation error.

Closes #3023. Stacked on #3025 (goal visibility) — merge that first; this branch will be rebased onto main.

Review process

Two independent read-only review passes (first-principles + Occam's razor). Findings addressed:

  • major — the host folds invalid transitions into operation_conflict alongside revision races; the retry loop burned 3 round-trips on a provably-futile status refusal, then threw a misleading "revision conflict after 3 attempts" that discarded the host's reason. Now: a conflict at an unchanged revision rethrows the host's message, and pause pre-validation matches the host rules exactly (isLiveGoalStatus wrongly included paused; clear had no check).
  • nit — whitespace collapsing deduped into one inlineGoalText helper.

Judged acceptable (documented, not fixed): the sub-frame staleness window between a successful control RPC and the pushed projection (self-heals on the next frame; expectedRevision prevents double-apply); a foreign controller pausing inside our own pause window has its notice suppressed (our confirmation still covers the state).

Test plan

  • packages/cli: 278 tests pass, including new coverage:
    • driver: clean control with snapshot expectedRevision, conflict retry sequence [1,2,3], goal-disappeared-mid-flight → null, status conflict rethrows host reason without futile retries
    • runner: mid-turn /goal pause refuses with a message and never steers; pause/resume/clear confirmations; self-initiated pause suppression vs host-pushed pause notice; pre-validation for all three actions; no-goal handling
    • pi-goal: pause/attach notice text, whitespace collapse, 120-char cap
  • biome check clean; tsc --noEmit clean for @maka/cli.

🤖 Generated by Maka

Visual evidence

The TUI handles pause, resume, and clear locally while the status line follows the Host-owned goal projection.

Goal pause, resume, and clear controls in the TUI\n\n## AI use\n- [ ] No generative tool was used for implementation.\n- [x] Generative tooling was used and the result was reviewed and verified by the author.\n\nTool(s) and scope: OpenAI Codex (Maka) assisted with implementation, tests, review remediation, and PR documentation.\n\nFinal squash trailer: Generated-by: Maka

@me2seeks
me2seeks force-pushed the feat/cli-goal-control branch 2 times, most recently from 850847b to 36a9b7f Compare August 17, 2026 15:18
@coderabbitai

coderabbitai Bot commented Aug 17, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 7417b8bd-8c41-4026-89a5-0762c02aafdc

📥 Commits

Reviewing files that changed from the base of the PR and between 32dbabf and 19b7bf3.

📒 Files selected for processing (2)
  • packages/cli/src/__tests__/pi-goal.test.ts
  • packages/cli/src/tui-ansi.ts

Included review availability: Your plan provides up to 3 included reviews per hour; 0 remain after this review.


📝 Walkthrough

Problem solved

Adds direct TUI controls for autonomous goals:

  • /goal pause
  • /goal resume
  • /goal clear

The commands avoid model turns. The TUI reports confirmations, automatic goal attachment, external pauses, invalid transitions, missing goals, and mutation rejection during active turns.

Source of truth

The PR extends the existing goal.control protocol and runtime-host GoalManager. It does not create a parallel goal state path. The CLI sends revision-checked actions through the existing session driver.

Complexity delta

The PR adds:

  • One optional MakaSessionDriver.controlGoal API.
  • Three TUI control branches.
  • Goal transition validation and busy-turn gating.
  • Optimistic revision retries in the runtime-host driver.
  • Notice, status, elapsed-time, and sanitization helpers.
  • Tests for driver, runner, display, and notice behavior.

The PR adds no new goal authority, durable state store, configuration, or protocol family. It adds control states, transition branches, conflict retries, user-visible notices, and test-maintenance burden. These additions are necessary for direct controls, concurrency handling, session recovery, and clear feedback.

The solution is the smallest coherent path identified in the current diff. Some retry and test patterns could be shared or simplified later. Those findings are non-blocking follow-up items. Overall maintenance complexity increases, but the increase is bounded and justified by the feature.

Validation

Tests cover goal controls, busy-turn rejection, invalid transitions, missing goals, revision conflicts and retries, goal removal, snapshot updates, pause and attachment notices, notice suppression, session restoration, status formatting, elapsed time, sanitization, truncation, and terminal states.

The current repository state provides no direct evidence for final required-check status. Required checks remain unverified.

Review-relevant risks

The PR changes user-visible TUI behavior. Material changes in this area require independent human review under repository policy.

The PR adds the optional MakaSessionDriver.controlGoal public contract. Material changes to this contract require independent human review under repository policy.

The PR changes runtime-host goal-control and optimistic-concurrency behavior. Material changes in this area require independent human review under repository policy.

No protected-area effect was identified for security, licensing, releases, or governance in the current diff.

The person performing the merge must review the final diff. A maintainer makes the final determination.

Walkthrough

The CLI adds direct /goal pause, /goal resume, and /goal clear controls. It announces goal pauses and session attachment states, sanitizes rendered goal text, and adds runtime-host conflict handling with tests.

Changes

Autonomous goal control

Layer / File(s) Summary
Runtime goal projection and control
packages/cli/src/session-driver.ts, packages/cli/src/runtime-host-session-driver.ts, packages/cli/src/__tests__/runtime-host-session-driver.test.ts
The session driver exposes controlGoal. The runtime host submits revision-checked actions, retries conflicts after querying current state, handles missing goals or sessions, and preserves transition errors.
Goal formatting and transcript rendering
packages/cli/src/tui-ansi.ts, packages/cli/src/pi-goal.ts, packages/cli/src/__tests__/pi-goal.test.ts
Goal text now removes ANSI and control characters, collapses whitespace, and trims values. Pause and attachment notices use sanitized formatting helpers.
TUI goal commands and live updates
packages/cli/src/pi-tui-runner.ts, packages/cli/src/__tests__/pi-tui-runner.test.ts
The TUI tracks goal transitions, announces externally induced pauses and recovered goals, suppresses duplicate self-generated notices, validates goal states, and handles /goal pause, resume, clear, and status commands.

Estimated code review effort: 4 (Complex) | ~45 minutes

Merge Risk: 🔵 Low · up to 19b7b

The terminal-output sanitization change may still allow certain escape sequences to alter rendering in the TUI, so the PR is mergeable with explicit owner awareness and follow-up to close that bounded terminal-safety risk.

Sequence Diagram(s)

sequenceDiagram
  participant Operator
  participant pi-tui-runner
  participant MakaSessionDriver
  participant RuntimeHost
  Operator->>pi-tui-runner: /goal pause, resume, or clear
  pi-tui-runner->>MakaSessionDriver: controlGoal(action)
  MakaSessionDriver->>RuntimeHost: goal.control(expectedRevision)
  RuntimeHost-->>MakaSessionDriver: updated projection or error
  MakaSessionDriver-->>pi-tui-runner: projection or null
  pi-tui-runner-->>Operator: confirmation or error notice
Loading

Possibly related issues

  • maka-agent/maka-agent#3022 — The PR adds the goal visibility, summaries, session-driver querying, and transition announcements described by this issue.

Possibly related PRs

  • maka-agent/maka-agent#3199 — This PR extends the same goal functionality with TUI controls and runtime-host handling for pause, resume, and clear.

Suggested reviewers: astro-han

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Ai Use Disclosure ⚠️ Warning The PR discloses OpenAI Codex (Maka), but code-changing commits b2faa94 and 32dbabf lack the required standalone Generated-by trailer. Add Generated-by: Maka to each affected commit and ensure it survives squash or amend. Review the “Human ownership and AI attribution” section of CONTRIBUTING.md.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the main change: adding TUI commands to pause, resume, and clear autonomous goals.
Description check ✅ Passed The description explains the problem, implementation, verification results, visual evidence, and AI use, but omits the template checklist.
Linked Issues check ✅ Passed The changes implement the linked issue objectives, including direct controls, optimistic retries, pause and attach notices, and comprehensive tests [#3023].
Out of Scope Changes check ✅ Passed The implementation and tests remain within the linked goal-control, notification, sanitization, and protocol requirements.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@me2seeks
me2seeks marked this pull request as ready for review August 18, 2026 01:55
@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Add autonomous goal visibility and controls to the TUI

✨ Enhancement 🧪 Tests 🕐 40+ Minutes

Grey Divider

AI Description

• Surface autonomous goals through status, summaries, provenance headers, and lifecycle notices.
• Add local /goal pause|resume|clear controls with transition validation.
• Synchronize projections and retry concurrent host controls using optimistic revisions.
Diagram

sequenceDiagram
  actor User
  participant TUI as CLI TUI
  participant Driver as Session Driver
  participant Host as Runtime Host
  participant Channel as Session Channel
  User->>TUI: /goal action
  TUI->>Driver: controlGoal action
  Driver->>Host: goal.control revision
  alt Revision conflict
    Host-->>Driver: operation_conflict
    Driver->>Host: goal.query
    Driver->>Host: retry goal.control
  end
  Host-->>Channel: projection frame
  Channel-->>Driver: goal changed
  Driver-->>TUI: live projection
  TUI-->>User: status and notices
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Poll goal.query from the TUI
  • ➕ Simpler than extending the subscription projection path.
  • ➕ Could fetch directly before every display or control action.
  • ➖ Introduces stale intervals and recurring host requests.
  • ➖ Cannot immediately surface external transitions or abort auto-pauses.
  • ➖ Duplicates state already carried by continuity snapshots.
2. Keep model-mediated goal tools
  • ➕ Requires no new TUI command or driver control surface.
  • ➕ Reuses existing GoalPause, GoalResume, and GoalClear tools.
  • ➖ Consumes model turns for deterministic user actions.
  • ➖ Can trigger model-side decline rules.
  • ➖ Leaves attach and automatic pause behavior invisible.
3. Share a cross-client goal controller
  • ➕ Could centralize retry and conflict semantics with the desktop client.
  • ➕ Reduces future behavioral drift between clients.
  • ➖ Expands this focused CLI change into a broader client abstraction.
  • ➖ TUI-specific busy gating, notices, and command validation still remain local.

Recommendation: Keep the PR's push-projection and direct host-control approach. It uses the existing authoritative continuity stream, avoids polling and model turns, and protects writes with expected revisions; a shared cross-client controller may be worthwhile later if additional clients adopt the same control workflow.

Files changed (12) +1308 / -11

Enhancement (6) +501 / -10
pi-goal.tsAdd shared TUI goal presentation helpers +150/-0

Add shared TUI goal presentation helpers

• Introduces pure helpers for live-status classification, labels, elapsed time, status segments, detailed summaries, and lifecycle notices. Terminal goals remain queryable while being excluded from persistent status chrome.

packages/cli/src/pi-goal.ts

pi-transcript.tsRender autonomous provenance and live goal status +52/-9

Render autonomous provenance and live goal status

• Adds a distinct transcript entry for goal continuation prompts and a reusable provenance renderer. Extends status metadata and rendering with active, waiting, or paused goal indicators.

packages/cli/src/pi-transcript.ts

pi-tui-runner.tsImplement local '/goal' inspection and controls +174/-0

Implement local '/goal' inspection and controls

• Adds local goal summaries, pause/resume/clear handling, transition pre-validation, busy-write gating, and command confirmations. Subscribes to goal changes to render attach and auto-pause notices while suppressing duplicate self-pause notifications.

packages/cli/src/pi-tui-runner.ts

runtime-host-session-channel.tsPublish goal changes from session projections +29/-0

Publish goal changes from session projections

• Detects goal identity or revision changes while folding subscription frames and canonical replacements. Publishes cloned projections so listeners cannot mutate channel state.

packages/cli/src/runtime-host-session-channel.ts

runtime-host-session-driver.tsExpose goal projections and revision-safe control +75/-1

Expose goal projections and revision-safe control

• Adds live goal reads and subscriptions backed by the session channel. Implements 'goal.control' with expected revisions, bounded conflict retries, fresh queries, stale-session fencing, and invalid-transition conflict preservation.

packages/cli/src/runtime-host-session-driver.ts

session-driver.tsExtend session drivers with goal capabilities +21/-0

Extend session drivers with goal capabilities

• Adds optional interfaces for reading, subscribing to, and controlling session goals, allowing unsupported runtimes to omit goal authority cleanly.

packages/cli/src/session-driver.ts

Refactor (1) +6 / -0
pi-transcript-format.tsExpose shared compact token formatting +6/-0

Expose shared compact token formatting

• Moves compact token-count formatting into the transcript formatting module so goal summaries and status rendering use the same representation.

packages/cli/src/pi-transcript-format.ts

Tests (4) +800 / -1
pi-goal.test.tsTest goal formatting and lifecycle notices +150/-0

Test goal formatting and lifecycle notices

• Adds exhaustive coverage for status classification, elapsed-time formatting, summaries, token budgets, whitespace normalization, and pause or attach notices.

packages/cli/src/tests/pi-goal.test.ts

pi-transcript.test.tsTest goal provenance and status-line rendering +60/-0

Test goal provenance and status-line rendering

• Verifies goal-origin prompts render as autonomous continuations and that only live goals appear in the TUI status line.

packages/cli/src/tests/pi-transcript.test.ts

pi-tui-runner.test.tsExercise '/goal' commands and live lifecycle behavior +361/-0

Exercise '/goal' commands and live lifecycle behavior

• Adds end-to-end TUI coverage for summaries, busy-turn routing, pause/resume/clear controls, attach and auto-pause notices, invalid transitions, and missing goals. Test drivers now simulate pushed projections and host control outcomes.

packages/cli/src/tests/pi-tui-runner.test.ts

runtime-host-session-driver.test.tsTest projected goal state and optimistic controls +229/-1

Test projected goal state and optimistic controls

• Verifies goal state arrives through continuity snapshots without polling. Covers expected revisions, conflict query-and-retry behavior, disappearing goals, invalid-transition conflicts, and listener notifications.

packages/cli/src/tests/runtime-host-session-driver.test.ts

Other (1) +1 / -0
slash-command-catalog.tsRegister the TUI '/goal' command +1/-0

Register the TUI '/goal' command

• Adds the session-required goal command to the shared slash-command catalog for the TUI surface.

packages/core/src/slash-command-catalog.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
packages/cli/src/__tests__/pi-goal.test.ts (1)

128-130: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider dropping this test; line 89 already covers it.

assert.deepEqual(detailed.slice(2), ['Tokens: 45k / 100k', …]) at line 89 already proves that goalSummaryLines routes token counts through formatTokenCount. This test re-asserts the formatter itself, which belongs to pi-transcript-format.ts.

♻️ Proposed removal
-  test('token formatting is the shared status-line formatter', () => {
-    assert.equal(formatTokenCount(45_200), '45k');
-  });
-

Then drop the now-unused import at line 5.

As per path instructions: "Flag tests that duplicate existing coverage, assert implementation details, or do not protect observable behavior."

Source: Path instructions

packages/cli/src/pi-tui-runner.ts (1)

1071-1079: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Reuse the command-token parse instead of hardcoding /goal.

Line 1075 re-implements the token extraction that handleSlashCommand already performs at line 2842, and it matches the literal name only. If /goal ever gains an alias in SLASH_COMMAND_CATALOG, the alias steers into the model mid-turn instead of answering locally.

♻️ Optional consolidation

Extract the token parse next to handleSlashCommand:

const slashCommandToken = (prompt: string): string => prompt.trim().split(/\s+/, 1)[0] ?? '';

Then use it in both places, and match against the resolved command instead of the literal:

-      if (prompt.trim().split(/\s+/, 1)[0] === '/goal') {
+      if (resolveSlashCommand(slashCommandToken(prompt))?.name === 'goal') {
         editor.addToHistory(prompt);
         handleSlashCommand(prompt, 0);
         return;
       }

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 6ef1278d-7a12-4741-a207-7fcf7cba9041

📥 Commits

Reviewing files that changed from the base of the PR and between 5d9ce0d and 36a9b7f.

📒 Files selected for processing (12)
  • packages/cli/src/__tests__/pi-goal.test.ts
  • packages/cli/src/__tests__/pi-transcript.test.ts
  • packages/cli/src/__tests__/pi-tui-runner.test.ts
  • packages/cli/src/__tests__/runtime-host-session-driver.test.ts
  • packages/cli/src/pi-goal.ts
  • packages/cli/src/pi-transcript-format.ts
  • packages/cli/src/pi-transcript.ts
  • packages/cli/src/pi-tui-runner.ts
  • packages/cli/src/runtime-host-session-channel.ts
  • packages/cli/src/runtime-host-session-driver.ts
  • packages/cli/src/session-driver.ts
  • packages/core/src/slash-command-catalog.ts

Included review availability: Your plan includes up to 3 reviews per rolling hour; 1 remains after this review.

Comment thread packages/cli/src/pi-tui-runner.ts
@qodo-code-review

qodo-code-review Bot commented Aug 18, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Resume attach notice never appears ✓ Resolved 🐞 Bug ≡ Correctness
Description
Fix now: the attach notice checks currentGoal only during runner initialization, when the
runtime-host driver has no attached session, while the actual resume happens later and the
goal-change callback does not announce active/waiting goals. Consequently, launching the TUI with a
live durable goal still auto-continues without the promised warning.
Code

packages/cli/src/pi-tui-runner.ts[R372-375]

+  if (
+    currentGoal !== null &&
+    (currentGoal.status === 'active' || currentGoal.status === 'waiting')
+  ) {
Relevance

●●● Strong

Accepted TUI notice regressions are fixed when they suppress explicit user-facing feedback; this
directly defeats the PR's stated attach-notice intent.

PR-#2945

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The production driver starts unattached, the runner performs resume only after startup, and channel
adoption publishes the goal through the listener. The newly added listener handles only transitions
into paused, so the initialization-only active/waiting check cannot announce a production resume.

packages/cli/src/runtime-host-session-driver.ts[129-143]
packages/cli/src/pi-tui-runner.ts[340-381]
packages/cli/src/pi-tui-runner.ts[3048-3077]
packages/cli/src/runtime-host-session-driver.ts[474-490]
packages/cli/src/runtime-host-session-driver.ts[795-800]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The attach notice is checked before `resumeSessionId` is attached, so a real resumed session with an active or waiting goal never receives the notice.

## Issue Context
Reuse the existing `switchSession` completion seam after the switched transcript has been adopted. Moving/consolidating the notice there is sufficient; no new state, public surface, configuration, or maintenance branch is needed. Do not emit it directly from the goal subscription callback before `applySwitchResult`, because transcript replacement can erase a notice emitted during channel adoption.

## Fix Focus Areas
- packages/cli/src/pi-tui-runner.ts[370-381]
- packages/cli/src/pi-tui-runner.ts[1419-1445]
- packages/cli/src/__tests__/pi-tui-runner.test.ts[4917-4967]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. Retry exhaustion still discards host reason ⊘ Outdated 🐞 Bug ≡ Correctness
Description
When three consecutive goal.control attempts each hit operation_conflict with a genuinely bumped
revision each time (real ongoing race, not a stuck invalid transition), the last attempt's break
skips the re-query and the loop falls through to the generic `throw new Error('Goal ... failed:
revision conflict after 3 attempts')`, discarding the host's actual conflict message. The PR
explicitly claims this class of bug (misleading exhaustion error swallowing host reason) was fixed,
but the fix only covers the same-revision case; the new test only exercises that path, not three
genuine consecutive races.
Code

packages/cli/src/runtime-host-session-driver.ts[R665-686]

+      } catch (error) {
+        if (!(error instanceof RuntimeHostOperationError) || error.code !== 'operation_conflict') {
+          throw error;
+        }
+        conflict = error;
+        if (attempt === GOAL_CONTROL_MAX_ATTEMPTS - 1) break; // a re-query would have no retry to serve
+      }
+      const current = (await this.#request('goal.query', { sessionId })).goal;
+      if (!current || current.goalId !== goalId) return null;
+      if (current.revision === goal.revision) {
+        // The host folds invalid transitions into operation_conflict too
+        // ("Goal cannot pause from status paused"). Every accepted transition
+        // bumps the revision, so a conflict at an unchanged revision is a
+        // status refusal, not a race — retrying is futile. Surface the host's
+        // reason instead of a misleading "revision conflict" exhaustion error.
+        throw conflict;
+      }
+      goal = current;
+    }
+    throw new Error(
+      `Goal ${action} failed: revision conflict after ${GOAL_CONTROL_MAX_ATTEMPTS} attempts`,
+    );
Relevance

●●● Strong

The finding identifies a concrete error-path regression contradicting the PR's explicit promise to
preserve host conflict reasons.

PR-#3169

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Tracing the loop: attempt 2 (last) hits operation_conflict, executes break before reaching the
re-query, so control flow exits the for-loop and reaches the generic fallback error, discarding
conflict's message even though it may be a real race (host reason available but never surfaced).

packages/cli/src/runtime-host-session-driver.ts[656-686]
packages/cli/src/tests/runtime-host-session-driver.test.ts[235-253]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
On the final retry attempt of `controlGoal`, an `operation_conflict` error causes an unconditional `break` that skips the re-query and host-reason-preserving logic, falling through to a generic `Error('Goal ... failed: revision conflict after N attempts')` that discards the host's actual error message — even when the conflict reflects a genuine, still-changing revision race rather than a stuck invalid transition.

## Issue Context
The PR's stated goal was: "a conflict at an unchanged revision rethrows the host's message" instead of a misleading generic exhaustion error. The fix only handles this for conflicts followed by a re-query; the last-attempt `break` bypasses the re-query entirely, so any conflict on the last attempt (race or not) loses its host reason.

## Fix Focus Areas
- packages/cli/src/runtime-host-session-driver.ts[665-686]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Informational

3. Stale sessionId reused across goal control retries ⊘ Outdated 🐞 Bug ☼ Reliability
Description
controlGoal captures sessionId and goalId once at the start and reuses them across all
retry/re-query RPCs; if the user calls startNewSession() or switches sessions while the retry loop
is in flight, the driver keeps sending goal.control/goal.query for the abandoned session since
#request performs no active-session/generation check. This can mutate a goal in a session the user
has already navigated away from, though the same unguarded pattern exists elsewhere in the file for
single-await operations; the multi-attempt retry loop meaningfully widens this exposure window.
Code

packages/cli/src/runtime-host-session-driver.ts[R645-663]

+  async controlGoal(action: GoalControlAction): Promise<GoalProjection | null> {
+    const sessionId = this.#sessionId;
+    if (!sessionId) return null;
+    let goal = this.getGoal();
+    if (!goal) return null;
+    // Optimistic concurrency with the same shape as the desktop client's
+    // clearGoal: expectedRevision guards against a concurrent controller, and
+    // an operation_conflict retries against a freshly queried projection —
+    // the pushed snapshot may lag the conflicting mutation by a frame.
+    const goalId = goal.goalId;
+    let conflict: RuntimeHostOperationError | null = null;
+    for (let attempt = 0; attempt < GOAL_CONTROL_MAX_ATTEMPTS; attempt += 1) {
+      try {
+        const result = await this.#request('goal.control', {
+          sessionId,
+          goalId,
+          expectedRevision: goal.revision,
+          action,
+        });
Relevance

●● Moderate

Session-generation safety during asynchronous retries is architectural and broader than the
repository's cited single-await patterns.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
sessionId/goalId are captured before any await and reused unchanged through up to 3 control attempts
plus re-queries, with no check against this.#sessionId changing due to startNewSession().

packages/cli/src/runtime-host-session-driver.ts[645-683]


4. selfInitiatedPauseGoalId not cleared on success ✓ Resolved 🐞 Bug ≡ Correctness
Description
controlGoalCommand's pause path sets selfInitiatedPauseGoalId before the RPC but only clears it
inside the goal-change listener's paused-transition branch or on error/null result; a successful
pause whose corresponding push transition is never observed as a qualifying active/waiting→paused
change (e.g. missed frame ordering) leaves the marker set, potentially suppressing a later genuine
auto-pause notice for the same goalId after a resume/pause cycle.
Code

packages/cli/src/pi-tui-runner.ts[R2507-2526]

+    if (action === 'pause') selfInitiatedPauseGoalId = goal.goalId;
+    let result: GoalProjection | null;
+    try {
+      result = await input.driver.controlGoal(action);
+    } catch (error) {
+      selfInitiatedPauseGoalId = null;
+      throw error; // runControl's reportError surfaces it
+    }
+    if (result === null) {
+      // The goal disappeared to a concurrent controller mid-flight.
+      selfInitiatedPauseGoalId = null;
+      notice(action === 'clear' ? 'Goal cleared.' : 'The goal no longer exists.');
+      return;
+    }
+    if (action === 'pause') {
+      notice('Goal paused. /goal resume continues it, /goal clear stops it.');
+    } else if (action === 'resume') {
+      notice('Goal resumed.');
+    } else {
+      notice('Goal cleared.');
Relevance

●● Moderate

A stale suppression marker is a real edge-case correctness concern, but no close precedent confirms
cleanup after missed push ordering.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The marker is set at line 2507 before the await; it is only reset in the catch block, the
null-result branch, or inside the subscribeGoalChanges listener's matching transition — the
successful non-null pause branch (lines 2521-2522) never resets it directly.

packages/cli/src/pi-tui-runner.ts[346-368]
packages/cli/src/pi-tui-runner.ts[2507-2526]


5. No notice when attaching to already-paused goal ⊘ Outdated 🐞 Bug ◔ Observability
Description
Resuming a session whose durable goal was already paused before this TUI session started produces
no transcript notice: the pause-transition listener requires a prior in-session projection
(currentGoal initialized to null before subscribing), and the attach notice only fires for
active/waiting goals, excluding paused. The paused state is still visible via the always-on
status line segment, but no explanatory notice (with resume/clear controls) is shown as it is for a
live/running goal.
Code

packages/cli/src/pi-tui-runner.ts[R372-381]

+  if (
+    currentGoal !== null &&
+    (currentGoal.status === 'active' || currentGoal.status === 'waiting')
+  ) {
+    state.entries.push({
+      kind: 'notice',
+      level: 'info',
+      text: goalAttachedNoticeText(currentGoal),
+    });
+  }
Relevance

●● Moderate

The missing paused attach notice is plausible, but historical evidence supports explicit notices
without this exact initial-state case.

PR-#2945

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The attach-notice condition only checks `currentGoal.status === 'active' || currentGoal.status ===
'waiting', and the paused-transition listener cannot fire on the very first push since previous`
was seeded before subscription, so a resumed already-paused goal gets no notice text explaining
resume/clear controls.

packages/cli/src/pi-tui-runner.ts[340-381]


Grey Divider

Context sources
Review mode: 🧠 Deep: This is a behavior-dense, cross-file change spanning TUI command routing, durable goal state transitions, push projections, optimistic concurrency retries, and session lifecycle, creating multiple independent opportunities for subtle defects.

Grey Divider

Tip of the day
💡 Did you know, you can show, collapse, or hide each part of a finding: code, evidence, and all

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread packages/cli/src/pi-tui-runner.ts
@Astro-Han

Copy link
Copy Markdown
Contributor

Thanks for the rework — the design is genuinely minimal: goal state stays in runtime-host's GoalManager (single authority), the CLI only projects/subscribes/forwards via the existing goal.control protocol with expectedRevision optimistic concurrency (same shape as desktop's clearGoal), and the revision-bump-on-every-transition invariant makes "re-query shows unchanged revision ⇒ genuine state rejection, not a race" a sound check — I verified the client-side pre-checks against the host rules (pause needs active|waiting, resume needs paused, clear refuses terminal) and they match. No new protocol, no polling, no parallel authority. The output is well-tested (driver revision sequences, conflict retries, goal disappearance, busy-gate, notice suppression, pre-checks). CI 18/18 green.

Conclusion: PASS — no P0/P1/P2.

P3 (optional): the last retry attempt still drops the host's reason, contradicting the PR's own fix claim ("a conflict at an unchanged revision rethrows the host's message") — if (attempt === GOAL_CONTROL_MAX_ATTEMPTS - 1) break; skips the re-query and lands on the generic "revision conflict after 3 attempts"; a client that races twice then gets a state rejection (e.g. another client paused then cleared, your pause arrives at cleared) shows the misleading generic message. One-line fix: throw the conflict (it's guaranteed non-null on the last pass) or move the re-query before the break, plus a test for "two races then a state rejection" (existing tests only cover the same-revision rejection). Also: controlGoal reuses the captured sessionId across retries with no session-generation guard (other async paths have #assertCurrentSession; the TUI's serial busy lock makes this near-theoretical, but a generation check per RPC would be cheap); resuming into an already-paused goal gives no notice (attach notice only covers active/waiting; the subscription only announces paused conversions when previous?.goalId === goal.goalId) — users must spot the yellow status-bar segment; mid-turn routing matches the literal /goal instead of the catalog-resolved command name (an alias added later would steer into the model); the "token formatting is the shared status-line formatter" test in pi-goal.test.ts:128-130 duplicates the test at :89 and belongs to pi-transcript-format — deletable; three copies of the goal-control retry loop now exist (desktop clearGoal, #3027's controlGoalWithRetry, this PR's controlGoal#3027 is still OPEN, not merged) — noted as "mirroring" in the PR, acceptable but worth extracting a shared controller later; formatGoalElapsed(24h) renders 1d 0h (could omit 0h on whole days).


AI-assisted review disclosure: this review was produced with AI assistance (pi review subagent on opencode-go/deepseek-v4-flash), which traced the retry/re-query logic and compared the client pre-checks against the host rules from the PR head. P3 items are code-path observations; no P0-P2 were found. Please weigh these findings with your own judgment.

中文摘要(AI 辅助审查)

结论:PASS(无 P0/P1/P2)。设计真最小:goal 状态仍由 runtime-host 的 GoalManager 持有(单一权威),CLI 只做投影读取/订阅/控制转发,经既有 goal.control 协议 + expectedRevision 乐观并发(与 desktop clearGoal 同构);"re-query 后 revision 未变 ⇒ 状态拒绝而非竞态"的判定成立(host 每次接受转换必 bump revision,已逐一核对客户端预校验与 host 规则:pause 需 active|waiting、resume 需 paused、clear 拒绝 terminal);无新协议/无轮询/无并行 authority。测试扎实(driver revision 序列/冲突重试/goal 消失/busy 门禁/notice 抑制/预校验)。CI 18/18 绿。P3(可选):末次重试仍丢 host 原因与 PR 自述修复矛盾——attempt===MAX-1 时 break 跳过 re-query 落到通用"revision conflict after 3 attempts";两次竞态后遇状态拒绝(另一客户端先 pause 再 clear、本端 pause 到达 cleared)显示误导消息。一行修复(throw conflict 或 re-query 移到 break 前)+ 补"两次竞态后状态拒绝"用例。另:controlGoal 跨重试复用捕获 sessionId 无会话代际护栏(其他异步路径有 #assertCurrentSession;TUI busy 串行锁使实际窗口近乎理论,每 RPC 比对 generation 成本低);resume 到已 paused 的 goal 无 notice(attach notice 只覆盖 active/waiting,订阅仅 previous?.goalId 相同时宣布暂停转换)——用户只能靠状态栏黄色段;mid-turn 路由匹配字面 /goal 而非 catalog 解析的命令名(将来加 alias 会 steering 进模型);pi-goal.test.ts:128-130 重复 :89 的表单测试属于 pi-transcript-format 可删;goal 控制重试循环已有三份(desktop clearGoal、#3027 的 controlGoalWithRetry(#3027 仍 OPEN 未合并)、本 PR controlGoal)——PR 已注明 mirroring 可接受但值得日后抽共享控制器;formatGoalElapsed(24h) 输出 1d 0h(整日可省 0h)。

@Astro-Han Astro-Han 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.

The Goal control design itself is coherent: Runtime Host remains authoritative, mutations carry goalId + expectedRevision, concurrent conflicts are retried, and the TUI projections/notices cover pause, resume, clear, attach, and auto-pause. Previously reported behavioral issues appear fixed.

The current head is nevertheless not reviewable as a merge result: it conflicts with latest main in the exact TUI/transcript files where main introduced locale and primary-guidance behavior. The simplest path is a clean rebase that preserves both contracts, followed by focused CLI/typecheck/CI validation; the existing green checks only validate the old base.

Review performed with two Codex reviewer agents and DeepSeek V4 Flash as advisory tools; I reproduced the conflicts against current main.

中文评论

Goal control 设计本身闭环:Runtime Host 保持权威,mutation 携带 goalId + expectedRevision,并发冲突会重试,TUI projection/notice 覆盖 pause、resume、clear、attach 和 auto-pause。此前公开的行为问题已修复。

但当前 head 不能作为最终 merge result 审查:它与最新 main 在同一组 TUI/transcript 文件中冲突,而 main 已在这些文件引入 locale 与 primary-guidance 行为。最简单的路径是 clean rebase,同时保留两边契约,再运行 focused CLI/typecheck/CI;现有绿色检查只验证旧 base。

本次审查使用了两位 Codex reviewer agents 与 DeepSeek V4 Flash 作为辅助工具;我已对当前 main 复现冲突。

Comment thread packages/cli/src/pi-tui-runner.ts
@me2seeks
me2seeks force-pushed the feat/cli-goal-control branch from bd02b63 to 4c20d9f Compare August 18, 2026 15:38

@Astro-Han Astro-Han 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.

The control flow is otherwise well-shaped: it reuses the Host goal authority, applies optimistic concurrency with bounded retries, and keeps TUI command handling local. One display-boundary issue should be addressed before treating this as merge-ready: goal conditions and evaluator reasons are durable user/model-controlled text and are now copied into terminal notices without control-sequence sanitization. The natural owner is the shared goal-to-terminal formatter, so one sanitization step can cover summaries, attach notices, and pause notices without parallel fixes.

AI-assisted review disclosure: Codex verified the final diff, goal-control retry semantics, Session switching, notice rendering, terminal formatting, focused tests, and live CI. Two independent reviewer-agent passes and an OpenCode Go DeepSeek V4 Flash (high) adversarial pass were used as inputs. No local tests were run.

中文复核

控制流程整体合理:复用 Host goal 权威,以有界重试实现 optimistic concurrency,并保持 TUI 命令本地处理。合并前应修复一处展示边界:goal condition 与 evaluator reason 都是持久化的用户/模型可控文本,现在会未经控制序列清理直接进入终端 notice。最自然的 owner 是共享的 goal-to-terminal formatter,一处清理即可覆盖 summary、attach notice 与 pause notice。

本次为 AI 辅助审查:Codex 核验最终 diff、goal-control 重试、Session 切换、notice rendering、terminal formatter、聚焦测试与实时 CI;另使用两次独立 reviewer 及一次 OpenCode Go DeepSeek V4 Flash(high)对抗审查。未运行本地测试。

Comment thread packages/cli/src/pi-goal.ts Outdated
Comment thread packages/cli/src/runtime-host-session-driver.ts Outdated

@Astro-Han Astro-Han 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.

The current head resolves the substantive issues from the earlier pass: goal text is sanitized consistently, the final host conflict reason is preserved, and resume/pause lifecycle behavior now has focused regression coverage. The command remains owned by the Runtime Host path rather than introducing a second goal authority.

No remaining P0-P2 findings on this head. The existing bot notes about future aliases and a theoretical cross-session race are reasonable non-blocking follow-ups, but neither warrants expanding this PR.

AI-assisted review disclosure: Codex reviewed exact head 29d3773, inspected the affected goal-control paths and current CI, and found all checks green with no unresolved review threads.

中文说明

当前 head 已修复上一轮的实质问题:goal 文本清理一致、最后一次 Host conflict 原因会保留,resume/pause 生命周期也有针对性回归测试。没有剩余 P0-P2。现有 bot 提到的 alias 扩展和极端跨 session 竞态可作为非阻塞后续,不需要扩大本 PR。

@Astro-Han

Copy link
Copy Markdown
Contributor

One little thing before merging, A screenshot would be great~

@me2seeks

Copy link
Copy Markdown
Contributor Author

Added a screenshot to the PR body showing /goal pause, resume, and clear together with the Host-projected status-line changes.

The only way to steer an autonomous goal from the TUI was asking the
model to call GoalPause/GoalResume/GoalClear — a burned model turn that
can hit the goal-control decline rules. And the pause semantics were
invisible: Ctrl+C on a goal continuation turn auto-pauses the durable
goal, and attaching to a session with a live goal auto-continues it,
both without a word.

- /goal pause|resume|clear dispatch through the driver's new controlGoal
  (host goal.control with expectedRevision optimistic retry, mirroring
  the desktop client's clearGoal). Writes take the runControl gate, and
  typed mid-turn they refuse with a clear message instead of steering
  into the model or being swallowed.
- A goal that transitions into paused while attached (typically the
  abort auto-pause) prints a one-line notice naming the reason and the
  /goal resume | /goal clear controls; a /goal pause we initiated prints
  its own confirmation instead of a duplicate notice.
- Attaching to a session whose goal is active/waiting announces the
  running loop — a token-burning loop never resumes silently.
- Invalid transitions are pre-validated against the live projection
  (cannot pause a cleared goal, cannot resume an active one) so they
  get a plain message instead of the host's operation error.

Ref apache#3023

Generated-by: Maka
…e resumed live goals

- /goal pause cleared selfInitiatedPauseGoalId only when the push handler
  observed the transition; a response-before-push ordering left the flag
  set and suppressed a later host-initiated pause notice. The success path
  now clears the flag and syncs the transition cache to the authoritative
  response, so the trailing push no longer duplicates the notice.
- The attach-time goal notice ran before the driver adopted a resumed
  session, so resuming into a session with a live durable goal never
  announced the auto-continuing loop. switchSession now syncs the goal
  transition cache and emits the notice after the transcript replacement
  that would erase an adoption-time notice.
@Astro-Han
Astro-Han force-pushed the feat/cli-goal-control branch from 29d3773 to 32dbabf Compare August 19, 2026 10:20
@coderabbitai

coderabbitai Bot commented Aug 19, 2026

Copy link
Copy Markdown

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

Astro-Han
Astro-Han previously approved these changes Aug 19, 2026
@Astro-Han
Astro-Han dismissed their stale review August 19, 2026 10:21

Replacing an accidentally bodyless approval with the complete exact-head review and disclosure.

@Astro-Han Astro-Han 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.

Approved on exact rebased head 32dbabf8c2c030a4ca267d6d049d5df444c46ab2.

The stack now cleanly contains only the #3026 Goal-control intent on top of merged #3025. The rebase preserves primaryGuidance.commands.goal as the single localization authority; the obsolete duplicate localization patch dropped cleanly.

Focused validation passed locally: CLI tests 310/310, CLI typecheck, Biome on every changed file, and git diff --check. No remaining P0-P2 findings.

AI-assisted review disclosure: Codex performed the controlled rebase and verified the exact delta and focused checks. Astro-Han authorized the rebase, approval, and merge.

中文说明

已批准精确的 rebase 后 head。当前分支仅保留 #3026 的 Goal control 意图,并正确叠在已合并的 #3025 之上;rebase 保留了 primaryGuidance.commands.goal 作为唯一文案权威,重复的 localization 补丁已自然消失。

本地聚焦验证全部通过:CLI 测试 310/310、CLI typecheck、全部变更文件的 Biome 检查及 git diff --check。没有剩余 P0-P2。

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 65a914af-480e-42d7-9014-06928461c467

📥 Commits

Reviewing files that changed from the base of the PR and between 39dd638 and 32dbabf.

📒 Files selected for processing (8)
  • packages/cli/src/__tests__/pi-goal.test.ts
  • packages/cli/src/__tests__/pi-tui-runner.test.ts
  • packages/cli/src/__tests__/runtime-host-session-driver.test.ts
  • packages/cli/src/pi-goal.ts
  • packages/cli/src/pi-tui-runner.ts
  • packages/cli/src/runtime-host-session-driver.ts
  • packages/cli/src/session-driver.ts
  • packages/cli/src/tui-ansi.ts
🚧 Files skipped from review as they are similar to previous changes (6)
  • packages/cli/src/tests/pi-goal.test.ts
  • packages/cli/src/session-driver.ts
  • packages/cli/src/tests/runtime-host-session-driver.test.ts
  • packages/cli/src/tests/pi-tui-runner.test.ts
  • packages/cli/src/pi-tui-runner.ts
  • packages/cli/src/pi-goal.ts

Included review availability: Your plan provides up to 3 included reviews per hour; 2 remain after this review.

Comment thread packages/cli/src/tui-ansi.ts

@Astro-Han Astro-Han 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.

Approved on exact head 19b7bf3bceb4e6b509585155302beef3e2567eb2.

The follow-up delta closes the remaining terminal-sanitization gap for generic ESC sequences such as character-set selection without adding another display path. The shared stripAnsi owner remains the right seam, and the focused regression test covers the concrete sequence.

Focused validation passed locally: CLI tests 311/311, CLI typecheck, Biome on the changed files, and git diff --check. There are no unresolved review threads and no remaining P0-P2 findings.

AI-assisted review disclosure: Codex reviewed the exact new delta and reran the focused workspace validation. Astro-Han authorized the approval and merge.

中文说明

已批准精确 head 19b7bf3。这次补丁补齐了 generic ESC sequence(例如 character-set selection)的终端清理边界,并继续由共享 stripAnsi 统一负责,没有新增平行展示路径。回归测试覆盖了具体序列。

本地聚焦验证全部通过:CLI 测试 311/311、CLI typecheck、变更文件的 Biome 检查和 git diff --check。当前无未解决审查线程,也无剩余 P0-P2。

@Astro-Han
Astro-Han merged commit d2d0121 into apache:main Aug 19, 2026
18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(cli): /goal pause|resume|clear — user control of autonomous goals without a model turn

2 participants