From c189880aad168f77e67114cdb7b6e9af2895cfc8 Mon Sep 17 00:00:00 2001 From: me2seeks Date: Sat, 22 Aug 2026 22:15:18 +0800 Subject: [PATCH 1/7] fix(cli): surface parked /resume plans as informational notices, not errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The TUI /resume command always failed with the raw protocol reason (e.g. 'Safe-boundary resume parked: continuation_unavailable') because the host's safeBoundaryResumeEnabled flag defaults to unset, so the resume plan is parked with 'resume_feature_disabled' on every stock install. Even with the feature enabled, a completed turn parks with 'resume_candidate_missing'. Both cases are informational — there is simply nothing safe to resume — but they rendered as red errors that read like session corruption. - runtime-host-session-driver: throw SafeBoundaryResumeParkedError carrying the protocol park reason instead of a plain Error - pi-tui-runner: catch it in /resume and print plain-language copy (feature disabled / nothing to resume / session busy) as an info notice; other reasons keep the raw detail for diagnosis - tests: pin the informational rendering for continuation_unavailable and resume_candidate_missing Refs #3505 Generated-by: Maka Agent (claude-opus-4-8) --- .../cli/src/__tests__/pi-tui-runner.test.ts | 76 ++++++++++++++++++- packages/cli/src/pi-tui-runner.ts | 53 +++++++++++-- .../cli/src/runtime-host-session-driver.ts | 26 +++++-- 3 files changed, 142 insertions(+), 13 deletions(-) diff --git a/packages/cli/src/__tests__/pi-tui-runner.test.ts b/packages/cli/src/__tests__/pi-tui-runner.test.ts index a3b5239894..597d070932 100644 --- a/packages/cli/src/__tests__/pi-tui-runner.test.ts +++ b/packages/cli/src/__tests__/pi-tui-runner.test.ts @@ -41,7 +41,7 @@ import type { } from '@maka/runtime-host/protocol'; import { SessionActivityRegistry } from '@maka/runtime/goal-turn-lifecycle'; import { type ContextDiagnostics } from '@maka/runtime/context-diagnostics'; -import type { GoalProjection } from '@maka/runtime-host/protocol'; +import type { GoalProjection, TurnResumeParkReason } from '@maka/runtime-host/protocol'; import type { MakaPreparePromptOptions, MakaPreparedSessionTurn, @@ -57,6 +57,7 @@ import type { SessionResumeAvailability, } from '../session-driver.js'; import { skillInvocationBlockedMessage } from '../session-driver.js'; +import { SafeBoundaryResumeParkedError } from '../runtime-host-session-driver.js'; import { listApiKeyOnboardableProviders } from '../onboarding-catalog.js'; import type { MakaOnboardingSurface, @@ -5467,6 +5468,74 @@ describe('Maka Pi TUI runner', () => { await run; }); + test('/resume parked by the host is informational, not a red error', async () => { + const terminal = new FakeTerminal(); + const driver = new SlashCommandDriver(); + driver.parkedResumeReason = 'continuation_unavailable'; + const run = runMakaPiTui({ + title: 'Maka', + driver, + cwd: '/repo', + model: 'm', + connectionSlug: 'c', + permissionMode: 'bypass', + terminal, + }); + + // Attach a session first so the driver actually has one to resume. + terminal.input('/session'); + terminal.input('\r'); + await waitFor(() => plainTerminalOutput(terminal.output()).includes('Resume Session')); + terminal.input('\r'); + await waitFor(() => driver.sessionIds.length === 1); + + terminal.input('/resume'); + terminal.input('\r'); + await waitFor(() => + plainTerminalOutput(terminal.output()).includes( + 'Safe-boundary resume is not enabled on this runtime.', + ), + ); + assert.equal(driver.resumeCalls, 1); + + terminal.input('/exit'); + terminal.input('\r'); + await run; + }); + + test('/resume with no interrupted run explains there is nothing to resume', async () => { + const terminal = new FakeTerminal(); + const driver = new SlashCommandDriver(); + driver.parkedResumeReason = 'resume_candidate_missing'; + const run = runMakaPiTui({ + title: 'Maka', + driver, + cwd: '/repo', + model: 'm', + connectionSlug: 'c', + permissionMode: 'bypass', + terminal, + }); + + terminal.input('/session'); + terminal.input('\r'); + await waitFor(() => plainTerminalOutput(terminal.output()).includes('Resume Session')); + terminal.input('\r'); + await waitFor(() => driver.sessionIds.length === 1); + + terminal.input('/resume'); + terminal.input('\r'); + await waitFor(() => + plainTerminalOutput(terminal.output()).includes( + 'Nothing to resume: no interrupted run exists in this session.', + ), + ); + + terminal.input('/exit'); + terminal.input('\r'); + await run; + }); + test('/model refuses instead of opening the picker behind the running turn', async () => { const terminal = new FakeTerminal(); const driver = new SteeringTurnDriver(); @@ -7356,6 +7425,8 @@ class SlashCommandDriver extends FakeSessionDriver { readonly moves: string[] = []; startNewSessionCalls = 0; resumeCalls = 0; + /** When set, resumeLatest throws SafeBoundaryResumeParkedError with this reason. */ + parkedResumeReason: TurnResumeParkReason | undefined; contextDiagnosticsRequests = 0; goal: GoalProjection | null = null; readonly goalListeners = new Set<(goal: GoalProjection | null) => void>(); @@ -7480,6 +7551,9 @@ class SlashCommandDriver extends FakeSessionDriver { async *resumeLatest(): AsyncIterable { this.resumeCalls += 1; + if (this.parkedResumeReason !== undefined) { + throw new SafeBoundaryResumeParkedError(this.parkedResumeReason); + } yield { type: 'text_complete', id: 'event-resume-text', diff --git a/packages/cli/src/pi-tui-runner.ts b/packages/cli/src/pi-tui-runner.ts index 51f2ff0630..86a3799917 100644 --- a/packages/cli/src/pi-tui-runner.ts +++ b/packages/cli/src/pi-tui-runner.ts @@ -74,7 +74,11 @@ import type { } from './pi-tui-contracts.js'; import { AUTO_RECAP_DISPLAY_LIMIT_BYTES, shouldAutoRecap } from './session-recap.js'; import type { InvocableSkillEntry } from '@maka/runtime/skill-invocation'; -import type { AgentGraphClientSnapshot, AgentGraphEpochSummary } from '@maka/runtime-host/protocol'; +import type { + AgentGraphClientSnapshot, + AgentGraphEpochSummary, + TurnResumeParkReason, +} from '@maka/runtime-host/protocol'; import type { AgentGraphEpochDirectory } from '@maka/runtime-host/client'; import { MakaSkillHighlightEditor } from './skill-highlight-editor.js'; import { parseGraphCommand, type ParsedGraphCommand } from '@maka/core/graph-command'; @@ -87,6 +91,7 @@ import { type MakaSideConversationParentStatus, type MakaSessionSwitchResult, } from './session-driver.js'; +import { SafeBoundaryResumeParkedError } from './runtime-host-session-driver.js'; import { appendTurnFailureToTranscript, appendUserPrompt, @@ -277,6 +282,25 @@ export function resolveTaskbarProgress( return environment.platform !== 'win32' && environment.windowsTerminalSession === undefined; } +/** + * User-facing copy for a parked safe-boundary resume. Parked is the host's + * way of saying "no safe continuation exists right now"; the reasons below + * are informational, so they read as a plain sentence instead of a protocol + * identifier. + */ +export function safeBoundaryResumeParkedCopy(reason: TurnResumeParkReason): string { + switch (reason) { + case 'continuation_unavailable': + return 'Safe-boundary resume is not enabled on this runtime.'; + case 'resume_candidate_missing': + return 'Nothing to resume: no interrupted run exists in this session.'; + case 'session_busy': + return 'Cannot resume: the session already has an active turn.'; + default: + return `Safe-boundary resume parked: ${reason}`; + } +} + export async function runMakaPiTui(input: MakaPiTuiInput): Promise { const locale = input.locale ?? 'en'; const primaryGuidance = getTuiPrimaryGuidance(locale); @@ -2251,11 +2275,28 @@ export async function runMakaPiTui(input: MakaPiTuiInput): Promise { text: 'Resuming from the latest safe boundary…', }); requestRender(); - for await (const event of input.driver.resumeLatest()) { - applyMakaSessionEventToTranscript(state, event); - shellRunElapsedTicker.sync(); - syncUserQuestionOverlay(); - requestRender(); + try { + for await (const event of input.driver.resumeLatest()) { + applyMakaSessionEventToTranscript(state, event); + shellRunElapsedTicker.sync(); + syncUserQuestionOverlay(); + requestRender(); + } + } catch (error) { + // A parked plan is the host saying "there is nothing safe to resume", + // not a failure: the runtime feature may be disabled or no interrupted + // run exists. Show that as information, not as a red error that reads + // like session corruption. + if (error instanceof SafeBoundaryResumeParkedError) { + state.entries.push({ + kind: 'notice', + level: 'info', + text: safeBoundaryResumeParkedCopy(error.reason), + }); + requestRender(); + return; + } + throw error; } }; diff --git a/packages/cli/src/runtime-host-session-driver.ts b/packages/cli/src/runtime-host-session-driver.ts index 2ab945eb41..ed5d5506cb 100644 --- a/packages/cli/src/runtime-host-session-driver.ts +++ b/packages/cli/src/runtime-host-session-driver.ts @@ -71,11 +71,10 @@ import { type GoalControlAction, type GoalProjection, type SessionContinuitySnapshot, + type TurnResumeParkReason, } from '@maka/runtime-host/protocol'; -import { - RuntimeHostSessionChannel, - type RuntimeHostSessionChannelOpenResult, -} from './runtime-host-session-channel.js'; +import { RuntimeHostSessionChannel } from './runtime-host-session-channel.js'; +import type { RuntimeHostSessionChannelOpenResult } from './runtime-host-session-channel.js'; import type { InspectCwdChanges, MakaAttachedSessionTurn, @@ -110,6 +109,21 @@ const decodeStoredMessage = (value: unknown): StoredMessage => decodePersistedStoredMessage(markPersisted(value)); const MAX_CATALOG_ATTEMPTS = 3; +/** + * The host declined to start a safe-boundary continuation and explained why. + * `reason` is the durable park reason from the `turn.resume` protocol, so + * surfaces can tell "nothing to resume" from a real failure. + */ +export class SafeBoundaryResumeParkedError extends Error { + readonly reason: TurnResumeParkReason; + + constructor(reason: TurnResumeParkReason) { + super(`Safe-boundary resume parked: ${reason}`); + this.name = 'SafeBoundaryResumeParkedError'; + this.reason = reason; + } +} + /** Optimistic-control retries for goal pause/resume/clear (mirrors the desktop client). */ const GOAL_CONTROL_MAX_ATTEMPTS = 3; @@ -370,7 +384,7 @@ class RuntimeHostMakaSessionDriverImpl implements RuntimeHostMakaSessionDriver { const sessionId = this.#requireSession('resume'); const plan = await this.#request('turn.resume.query', { sessionId }); if (plan.disposition !== 'ready') { - throw new Error(`Safe-boundary resume parked: ${plan.reason}`); + throw new SafeBoundaryResumeParkedError(plan.reason); } const channel = await this.#ensureChannel(sessionId); const turnId = this.#newId(); @@ -384,7 +398,7 @@ class RuntimeHostMakaSessionDriverImpl implements RuntimeHostMakaSessionDriver { sourceRuntimeEventHighWater: plan.sourceRuntimeEventHighWater, }); if (result.kind !== 'started') { - channel.failTurn(turnId, new Error(`Safe-boundary resume parked: ${result.plan.reason}`)); + channel.failTurn(turnId, new SafeBoundaryResumeParkedError(result.plan.reason)); } } catch (error) { channel.failTurn(turnId, error); From dd9a608a1919dcd09606bf29a6effaf5ae5e7825 Mon Sep 17 00:00:00 2001 From: me2seeks Date: Sat, 22 Aug 2026 22:23:25 +0800 Subject: [PATCH 2/7] fix(cli): point /resume at the opt-in env flag when it is disabled The continuation_unavailable notice now names MAKA_RUNTIME_SAFE_BOUNDARY_RESUME=1 so a user who has not enabled safe-boundary resume learns how to turn it on instead of only learning that it is off. Generated-by: Maka Agent (claude-opus-4-8) --- packages/cli/src/__tests__/pi-tui-runner.test.ts | 2 +- packages/cli/src/pi-tui-runner.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/cli/src/__tests__/pi-tui-runner.test.ts b/packages/cli/src/__tests__/pi-tui-runner.test.ts index 597d070932..4d00d5910e 100644 --- a/packages/cli/src/__tests__/pi-tui-runner.test.ts +++ b/packages/cli/src/__tests__/pi-tui-runner.test.ts @@ -5493,7 +5493,7 @@ describe('Maka Pi TUI runner', () => { terminal.input('\r'); await waitFor(() => plainTerminalOutput(terminal.output()).includes( - 'Safe-boundary resume is not enabled on this runtime.', + 'Safe-boundary resume is not enabled on this runtime', ), ); assert.equal(driver.resumeCalls, 1); diff --git a/packages/cli/src/pi-tui-runner.ts b/packages/cli/src/pi-tui-runner.ts index 86a3799917..8afded2b46 100644 --- a/packages/cli/src/pi-tui-runner.ts +++ b/packages/cli/src/pi-tui-runner.ts @@ -291,7 +291,7 @@ export function resolveTaskbarProgress( export function safeBoundaryResumeParkedCopy(reason: TurnResumeParkReason): string { switch (reason) { case 'continuation_unavailable': - return 'Safe-boundary resume is not enabled on this runtime.'; + return 'Safe-boundary resume is not enabled on this runtime (set MAKA_RUNTIME_SAFE_BOUNDARY_RESUME=1 to enable).'; case 'resume_candidate_missing': return 'Nothing to resume: no interrupted run exists in this session.'; case 'session_busy': From 0abe872712bec0e80161d85d9d23df82fff519de Mon Sep 17 00:00:00 2001 From: me2seeks Date: Wed, 26 Aug 2026 22:04:16 +0800 Subject: [PATCH 3/7] fix(runtime-host): preserve parked resume failure causes Keep feature-disabled, continuation-authority, and safety-observation outcomes distinct on the wire so only the true opt-in case receives informational CLI guidance. Generated-by: Codex --- .../cli/src/__tests__/pi-tui-runner.test.ts | 72 ++++++++++++++++++- packages/cli/src/pi-tui-runner.ts | 29 ++++---- .../execution-host-continuation.test.ts | 4 +- .../src/__tests__/protocol.test.ts | 19 +++++ packages/runtime-host/src/protocol/index.ts | 5 +- packages/runtime-host/src/protocol/turn.ts | 4 +- .../src/server/root-turn-coordinator.ts | 16 +++-- 7 files changed, 125 insertions(+), 24 deletions(-) diff --git a/packages/cli/src/__tests__/pi-tui-runner.test.ts b/packages/cli/src/__tests__/pi-tui-runner.test.ts index 4d00d5910e..2dcc36814a 100644 --- a/packages/cli/src/__tests__/pi-tui-runner.test.ts +++ b/packages/cli/src/__tests__/pi-tui-runner.test.ts @@ -5471,7 +5471,7 @@ describe('Maka Pi TUI runner', () => { test('/resume parked by the host is informational, not a red error', async () => { const terminal = new FakeTerminal(); const driver = new SlashCommandDriver(); - driver.parkedResumeReason = 'continuation_unavailable'; + driver.parkedResumeReason = 'resume_feature_disabled'; const run = runMakaPiTui({ title: 'Maka', driver, @@ -5536,6 +5536,76 @@ describe('Maka Pi TUI runner', () => { await run; }); + test('/resume keeps genuine parked recovery failures red', async () => { + const terminal = new FakeTerminal(); + const driver = new SlashCommandDriver(); + driver.parkedResumeReason = 'safety_check_failed'; + const run = runMakaPiTui({ + title: 'Maka', + driver, + cwd: '/repo', + model: 'm', + connectionSlug: 'c', + permissionMode: 'bypass', + terminal, + }); + + terminal.input('/session'); + terminal.input('\r'); + await waitFor(() => plainTerminalOutput(terminal.output()).includes('Resume Session')); + terminal.input('\r'); + await waitFor(() => driver.sessionIds.length === 1); + + terminal.input('/resume'); + terminal.input('\r'); + await waitFor(() => + plainTerminalOutput(terminal.output()).includes( + 'Error: Safe-boundary resume parked: safety_check_failed', + ), + ); + + terminal.input('/exit'); + terminal.input('\r'); + await run; + }); + + for (const reason of [ + 'continuation_authority_unavailable', + 'safety_observation_unavailable', + ] as const) { + test(`/resume keeps ${reason} red`, async () => { + const terminal = new FakeTerminal(); + const driver = new SlashCommandDriver(); + driver.parkedResumeReason = reason; + const run = runMakaPiTui({ + title: 'Maka', + driver, + cwd: '/repo', + model: 'm', + connectionSlug: 'c', + permissionMode: 'bypass', + terminal, + }); + + terminal.input('/session'); + terminal.input('\r'); + await waitFor(() => plainTerminalOutput(terminal.output()).includes('Resume Session')); + terminal.input('\r'); + await waitFor(() => driver.sessionIds.length === 1); + + terminal.input('/resume'); + terminal.input('\r'); + await waitFor(() => + plainTerminalOutput(terminal.output()).includes( + `Error: Safe-boundary resume parked: ${reason}`, + ), + ); + + terminal.input('/exit'); + terminal.input('\r'); + await run; + }); + } test('/model refuses instead of opening the picker behind the running turn', async () => { const terminal = new FakeTerminal(); const driver = new SteeringTurnDriver(); diff --git a/packages/cli/src/pi-tui-runner.ts b/packages/cli/src/pi-tui-runner.ts index 8afded2b46..9a067fc347 100644 --- a/packages/cli/src/pi-tui-runner.ts +++ b/packages/cli/src/pi-tui-runner.ts @@ -288,16 +288,22 @@ export function resolveTaskbarProgress( * are informational, so they read as a plain sentence instead of a protocol * identifier. */ -export function safeBoundaryResumeParkedCopy(reason: TurnResumeParkReason): string { +export function safeBoundaryResumeParkedCopy(reason: TurnResumeParkReason): { + level: 'info' | 'error'; + text: string; +} { switch (reason) { - case 'continuation_unavailable': - return 'Safe-boundary resume is not enabled on this runtime (set MAKA_RUNTIME_SAFE_BOUNDARY_RESUME=1 to enable).'; + case 'resume_feature_disabled': + return { + level: 'info', + text: 'Safe-boundary resume is not enabled on this runtime (set MAKA_RUNTIME_SAFE_BOUNDARY_RESUME=1 to enable).', + }; case 'resume_candidate_missing': - return 'Nothing to resume: no interrupted run exists in this session.'; + return { level: 'info', text: 'Nothing to resume: no interrupted run exists in this session.' }; case 'session_busy': - return 'Cannot resume: the session already has an active turn.'; + return { level: 'info', text: 'Cannot resume: the session already has an active turn.' }; default: - return `Safe-boundary resume parked: ${reason}`; + return { level: 'error', text: `Safe-boundary resume parked: ${reason}` }; } } @@ -2283,15 +2289,14 @@ export async function runMakaPiTui(input: MakaPiTuiInput): Promise { requestRender(); } } catch (error) { - // A parked plan is the host saying "there is nothing safe to resume", - // not a failure: the runtime feature may be disabled or no interrupted - // run exists. Show that as information, not as a red error that reads - // like session corruption. + // Preserve the Host's reason: expected user states are informational, + // while unavailable recovery authority and safety observations stay red. if (error instanceof SafeBoundaryResumeParkedError) { + const presentation = safeBoundaryResumeParkedCopy(error.reason); state.entries.push({ kind: 'notice', - level: 'info', - text: safeBoundaryResumeParkedCopy(error.reason), + level: presentation.level, + text: presentation.text, }); requestRender(); return; diff --git a/packages/runtime-host/src/__tests__/execution-host-continuation.test.ts b/packages/runtime-host/src/__tests__/execution-host-continuation.test.ts index a0dae34fea..4caff505ca 100644 --- a/packages/runtime-host/src/__tests__/execution-host-continuation.test.ts +++ b/packages/runtime-host/src/__tests__/execution-host-continuation.test.ts @@ -293,7 +293,7 @@ test('startup parks a provider-indeterminate continuation when resume is disable { sessionId: fixture.sessionId, disposition: 'parked', - reason: 'continuation_unavailable', + reason: 'resume_feature_disabled', }, ); const sibling = requireStartedTurn( @@ -392,7 +392,7 @@ test('Runtime Host keeps safe-boundary continuation opt-in', async () => { const plan = { sessionId: fixture.sessionId, disposition: 'parked' as const, - reason: 'continuation_unavailable' as const, + reason: 'resume_feature_disabled' as const, }; assert.deepEqual( await client.request('turn.resume.query', { diff --git a/packages/runtime-host/src/__tests__/protocol.test.ts b/packages/runtime-host/src/__tests__/protocol.test.ts index 32d0daa7fa..349c37803d 100644 --- a/packages/runtime-host/src/__tests__/protocol.test.ts +++ b/packages/runtime-host/src/__tests__/protocol.test.ts @@ -1035,6 +1035,25 @@ describe('Runtime Host bootstrap protocol', () => { }, }; assert.deepEqual(decodeHostFrame(parked), parked); + for (const reason of [ + 'resume_feature_disabled', + 'continuation_authority_unavailable', + 'safety_observation_unavailable', + ] as const) { + const unavailable = { + ...parked, + result: { ...parked.result, reason }, + }; + assert.deepEqual(decodeHostFrame(unavailable), unavailable); + } + assert.throws( + () => + decodeHostFrame({ + ...parked, + result: { ...parked.result, reason: 'continuation_unavailable' }, + }), + isInvalidFrame, + ); assert.throws( () => decodeHostFrame({ diff --git a/packages/runtime-host/src/protocol/index.ts b/packages/runtime-host/src/protocol/index.ts index 45adfa103a..aace106c3f 100644 --- a/packages/runtime-host/src/protocol/index.ts +++ b/packages/runtime-host/src/protocol/index.ts @@ -93,7 +93,10 @@ export const RUNTIME_HOST_REGISTRATION_SCHEMA_VERSION = 1 as const; export const RUNTIME_HOST_PROTOCOL_VERSION = 0 as const; // Increment when the same protocol version no longer guarantees safe Client-Host // interoperability. Mismatches are rejected before domain commands are admitted. -export const RUNTIME_HOST_COMPATIBILITY_EPOCH = 56 as const; +export const RUNTIME_HOST_COMPATIBILITY_EPOCH = 57 as const; +// 57: Parked safe-boundary resume plans preserve feature-disabled, missing +// continuation authority, and unavailable safety-observation reasons. +// Older peers collapse these causes and can misclassify recovery failures. // 56: Failed Turn snapshots preserve the structured context-budget exhaustion // detail. Epoch-55 peers reject the optional field on the closed snapshot shape. // 55: Local owners can atomically revoke every credential for one access diff --git a/packages/runtime-host/src/protocol/turn.ts b/packages/runtime-host/src/protocol/turn.ts index c96d19e411..2e5e6dd336 100644 --- a/packages/runtime-host/src/protocol/turn.ts +++ b/packages/runtime-host/src/protocol/turn.ts @@ -121,7 +121,9 @@ export const TURN_RESUME_PARK_REASONS = [ 'continuation_already_exists', 'continuation_repair_required', 'continuation_started_indeterminate', - 'continuation_unavailable', + 'resume_feature_disabled', + 'continuation_authority_unavailable', + 'safety_observation_unavailable', 'session_busy', ] as const; diff --git a/packages/runtime-host/src/server/root-turn-coordinator.ts b/packages/runtime-host/src/server/root-turn-coordinator.ts index adbdc14136..6fc1006222 100644 --- a/packages/runtime-host/src/server/root-turn-coordinator.ts +++ b/packages/runtime-host/src/server/root-turn-coordinator.ts @@ -408,7 +408,9 @@ export class RootTurnCoordinator implements HostedExecutionAuthority { if (continuation?.disposition === 'parked') { if ( continuation.plan.reason === 'safety_check_failed' || - continuation.plan.reason === 'continuation_unavailable' + continuation.plan.reason === 'resume_feature_disabled' || + continuation.plan.reason === 'continuation_authority_unavailable' || + continuation.plan.reason === 'safety_observation_unavailable' ) { this.parkContinuationAdmission(admission); return undefined; @@ -2854,12 +2856,12 @@ function projectTurnResumePlan( reason = 'continuation_started_indeterminate'; } else if (reasons.has('continuation_claim_repair_required')) { reason = 'continuation_repair_required'; - } else if ( - reasons.has('resume_feature_disabled') || - reasons.has('continuation_authority_unavailable') || - reasons.has('safety_observation_unavailable') - ) { - reason = 'continuation_unavailable'; + } else if (reasons.has('resume_feature_disabled')) { + reason = 'resume_feature_disabled'; + } else if (reasons.has('continuation_authority_unavailable')) { + reason = 'continuation_authority_unavailable'; + } else if (reasons.has('safety_observation_unavailable')) { + reason = 'safety_observation_unavailable'; } else { reason = 'safety_check_failed'; } From 77e326fd41965f17fc73ad4f05a8421172c21882 Mon Sep 17 00:00:00 2001 From: me2seeks Date: Wed, 26 Aug 2026 22:58:33 +0800 Subject: [PATCH 4/7] docs(runtime): explain parked resume reason boundary Document the current Host-to-CLI wire reasons, presentation severity, compatibility epoch, and unchanged ownership boundaries in both architecture counterparts. Generated-by: Codex --- .../runtime-resume-architecture.md | 23 +++++++++++++++++++ .../runtime-resume-architecture.zh-CN.md | 20 +++++++++++++++- 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/docs/architecture/runtime-resume-architecture.md b/docs/architecture/runtime-resume-architecture.md index aa971933c9..c09075e4ef 100644 --- a/docs/architecture/runtime-resume-architecture.md +++ b/docs/architecture/runtime-resume-architecture.md @@ -491,6 +491,29 @@ sequenceDiagram CLI/TUI `/resume` uses the same `SessionManager` plan/execute seam. Desktop startup auto-resume also reuses it. +### Current parked-reason boundary + +Runtime Host projects Runtime planner rejection reasons into the closed +`TurnResumeParkReason` wire union. A CLI must not infer the Host's internal +state again. The current Host preserves three previously conflated causes: + +- `resume_feature_disabled`: the feature flag is off; +- `continuation_authority_unavailable`: the Host cannot obtain continuation authority; +- `safety_observation_unavailable`: the Host cannot obtain authoritative safety observations. + +The current wire contract no longer contains `continuation_unavailable`. The +`/resume` driver carries the exact reason in `SafeBoundaryResumeParkedError`. +The TUI renders only expected user states as informational notices: +`resume_feature_disabled`, `resume_candidate_missing`, and `session_busy`. +Authority, safety, and other recovery failures remain red errors with the raw +reason preserved for diagnosis. + +Because this changes a closed protocol union, Runtime Host compatibility epoch +52 rejects mixed old/new Client-Host pairs during handshake instead of letting +a Client misclassify a recovery failure as a disabled feature. This change only +corrects Host projection and CLI presentation; it does not move ownership of +the planner, durable continuation claim, or feature flag. + ## Why continuation does not duplicate the user message A normal Run creates an initial user RuntimeEvent. A continuation already has a validated source history, so it: diff --git a/docs/architecture/runtime-resume-architecture.zh-CN.md b/docs/architecture/runtime-resume-architecture.zh-CN.md index 260d85a3b0..f064bc7ca2 100644 --- a/docs/architecture/runtime-resume-architecture.zh-CN.md +++ b/docs/architecture/runtime-resume-architecture.zh-CN.md @@ -497,7 +497,25 @@ sequenceDiagram end ``` -CLI/TUI 的 `/resume` 走同一个 `SessionManager` plan/execute seam,只是把 park 作为命令错误展示。Desktop startup auto-resume 也复用同一 planner 和 execution path,不维护第三套恢复逻辑。 +CLI/TUI 的 `/resume` 走同一个 `SessionManager` plan/execute seam。Desktop startup auto-resume 也复用同一 planner 和 execution path,不维护第三套恢复逻辑。 + +### 当前的 parked 原因边界 + +Runtime Host 负责把 Runtime planner 的 rejection reasons 投影成封闭的 +`TurnResumeParkReason` wire union;CLI 不能重新推断 Host 内部状态。当前 Host 会保留三种容易混淆的原因: + +- `resume_feature_disabled`:feature flag 未开启; +- `continuation_authority_unavailable`:Host 无法取得 continuation authority; +- `safety_observation_unavailable`:Host 无法取得安全观测事实。 + +旧的 `continuation_unavailable` 不再出现在当前 wire contract 中。`/resume` driver 通过 +`SafeBoundaryResumeParkedError` 原样携带原因,TUI 只把预期的用户状态显示为普通提示: +`resume_feature_disabled`、`resume_candidate_missing` 和 `session_busy`。authority、safety +以及其他恢复失败仍显示为红色错误,并保留原始 reason 供诊断。 + +这是不兼容的封闭协议变更,因此 Runtime Host compatibility epoch 推进到 52;旧 Client/Host +组合会在握手阶段被拒绝,而不是把新的 failure reason 误判成“功能未开启”。这次变更只修正 +Host 投影和 CLI 展示,不改变 planner、durable continuation claim 或 feature flag 的 owner。 ## 为什么 Continuation 不复制原用户消息 From 86467d0a660d42284767025002ad0fba1a4e118b Mon Sep 17 00:00:00 2001 From: me2seeks Date: Thu, 27 Aug 2026 01:27:01 +0800 Subject: [PATCH 5/7] chore: retrigger flaky CI From 3ab3375123bdf1beb888c4d80e9590e3d1507f8d Mon Sep 17 00:00:00 2001 From: me2seeks Date: Thu, 27 Aug 2026 21:02:25 +0800 Subject: [PATCH 6/7] docs(cli): name the resume park epoch actually shipped (54) Both language versions of the resume architecture note said epoch 52, but the wire change bumped RUNTIME_HOST_COMPATIBILITY_EPOCH to 54; epoch 52 is the unrelated steering-echo entry. --- docs/architecture/runtime-resume-architecture.md | 2 +- docs/architecture/runtime-resume-architecture.zh-CN.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/architecture/runtime-resume-architecture.md b/docs/architecture/runtime-resume-architecture.md index c09075e4ef..a4de9f3116 100644 --- a/docs/architecture/runtime-resume-architecture.md +++ b/docs/architecture/runtime-resume-architecture.md @@ -509,7 +509,7 @@ Authority, safety, and other recovery failures remain red errors with the raw reason preserved for diagnosis. Because this changes a closed protocol union, Runtime Host compatibility epoch -52 rejects mixed old/new Client-Host pairs during handshake instead of letting +54 rejects mixed old/new Client-Host pairs during handshake instead of letting a Client misclassify a recovery failure as a disabled feature. This change only corrects Host projection and CLI presentation; it does not move ownership of the planner, durable continuation claim, or feature flag. diff --git a/docs/architecture/runtime-resume-architecture.zh-CN.md b/docs/architecture/runtime-resume-architecture.zh-CN.md index f064bc7ca2..5d90ba9c82 100644 --- a/docs/architecture/runtime-resume-architecture.zh-CN.md +++ b/docs/architecture/runtime-resume-architecture.zh-CN.md @@ -513,7 +513,7 @@ Runtime Host 负责把 Runtime planner 的 rejection reasons 投影成封闭的 `resume_feature_disabled`、`resume_candidate_missing` 和 `session_busy`。authority、safety 以及其他恢复失败仍显示为红色错误,并保留原始 reason 供诊断。 -这是不兼容的封闭协议变更,因此 Runtime Host compatibility epoch 推进到 52;旧 Client/Host +这是不兼容的封闭协议变更,因此 Runtime Host compatibility epoch 推进到 54;旧 Client/Host 组合会在握手阶段被拒绝,而不是把新的 failure reason 误判成“功能未开启”。这次变更只修正 Host 投影和 CLI 展示,不改变 planner、durable continuation claim 或 feature flag 的 owner。 From 7701d9bbd4bb60820d79cf98cd3dc9d1be50cee6 Mon Sep 17 00:00:00 2001 From: me2seeks Date: Fri, 28 Aug 2026 09:32:30 +0800 Subject: [PATCH 7/7] docs(runtime): correct safe-boundary resume epoch --- docs/architecture/runtime-resume-architecture.md | 2 +- docs/architecture/runtime-resume-architecture.zh-CN.md | 2 +- packages/cli/src/pi-tui-runner.ts | 5 ++++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/docs/architecture/runtime-resume-architecture.md b/docs/architecture/runtime-resume-architecture.md index a4de9f3116..3d5754d026 100644 --- a/docs/architecture/runtime-resume-architecture.md +++ b/docs/architecture/runtime-resume-architecture.md @@ -509,7 +509,7 @@ Authority, safety, and other recovery failures remain red errors with the raw reason preserved for diagnosis. Because this changes a closed protocol union, Runtime Host compatibility epoch -54 rejects mixed old/new Client-Host pairs during handshake instead of letting +57 rejects mixed old/new Client-Host pairs during handshake instead of letting a Client misclassify a recovery failure as a disabled feature. This change only corrects Host projection and CLI presentation; it does not move ownership of the planner, durable continuation claim, or feature flag. diff --git a/docs/architecture/runtime-resume-architecture.zh-CN.md b/docs/architecture/runtime-resume-architecture.zh-CN.md index 5d90ba9c82..4e8d97f03e 100644 --- a/docs/architecture/runtime-resume-architecture.zh-CN.md +++ b/docs/architecture/runtime-resume-architecture.zh-CN.md @@ -513,7 +513,7 @@ Runtime Host 负责把 Runtime planner 的 rejection reasons 投影成封闭的 `resume_feature_disabled`、`resume_candidate_missing` 和 `session_busy`。authority、safety 以及其他恢复失败仍显示为红色错误,并保留原始 reason 供诊断。 -这是不兼容的封闭协议变更,因此 Runtime Host compatibility epoch 推进到 54;旧 Client/Host +这是不兼容的封闭协议变更,因此 Runtime Host compatibility epoch 推进到 57;旧 Client/Host 组合会在握手阶段被拒绝,而不是把新的 failure reason 误判成“功能未开启”。这次变更只修正 Host 投影和 CLI 展示,不改变 planner、durable continuation claim 或 feature flag 的 owner。 diff --git a/packages/cli/src/pi-tui-runner.ts b/packages/cli/src/pi-tui-runner.ts index 9a067fc347..82a080907a 100644 --- a/packages/cli/src/pi-tui-runner.ts +++ b/packages/cli/src/pi-tui-runner.ts @@ -299,7 +299,10 @@ export function safeBoundaryResumeParkedCopy(reason: TurnResumeParkReason): { text: 'Safe-boundary resume is not enabled on this runtime (set MAKA_RUNTIME_SAFE_BOUNDARY_RESUME=1 to enable).', }; case 'resume_candidate_missing': - return { level: 'info', text: 'Nothing to resume: no interrupted run exists in this session.' }; + return { + level: 'info', + text: 'Nothing to resume: no interrupted run exists in this session.', + }; case 'session_busy': return { level: 'info', text: 'Cannot resume: the session already has an active turn.' }; default: