You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maka.apache.org by as...@apache.org on 2026/08/24 04:44:36 UTC

(maka) 12/20: fix(cli): refresh live transcript on durable advance

This is an automated email from the ASF dual-hosted git repository.

Astro-Han pushed a commit to branch fix/steering-durable-admission
in repository https://gitbox.apache.org/repos/asf/maka.git

commit 8110f831a15f5259822e0412134acacb819b2292
Author: AstroHan <le...@outlook.com>
AuthorDate: Mon Aug 24 00:29:02 2026 +0800

    fix(cli): refresh live transcript on durable advance
    
    Generated-by: Maka
---
 packages/cli/src/__tests__/pi-tui-runner.test.ts   | 39 +++++++++++++++---
 .../__tests__/runtime-host-session-driver.test.ts  | 46 +++++++++++++++++++++-
 packages/cli/src/pi-tui-runner.ts                  | 23 +++++++++--
 packages/cli/src/runtime-host-session-channel.ts   |  5 +++
 4 files changed, 103 insertions(+), 10 deletions(-)

diff --git a/packages/cli/src/__tests__/pi-tui-runner.test.ts b/packages/cli/src/__tests__/pi-tui-runner.test.ts
index 3bb0c4db8..8ed98e01e 100644
--- a/packages/cli/src/__tests__/pi-tui-runner.test.ts
+++ b/packages/cli/src/__tests__/pi-tui-runner.test.ts
@@ -52,6 +52,7 @@ import type {
   MakaSessionRewindResult,
   MakaSessionSwitchOptions,
   MakaSessionSwitchResult,
+  MakaTranscriptReplacementReason,
   RewindTarget,
   SessionResumeAvailability,
 } from '../session-driver.js';
@@ -1882,7 +1883,7 @@ describe('Maka Pi TUI runner', () => {
     terminal.input('also handle Y');
     terminal.input('\r');
     await waitFor(() => driver.steered.length === 1);
-    await delay(0);
+    await waitFor(() => plainTerminalOutput(terminal.screenOutput()).includes('also handle Y'));
     assert.deepEqual(driver.steered, ['also handle Y']);
     assert.equal(
       plainTerminalOutput(terminal.screenOutput()).includes('Steering: also handle Y'),
@@ -2145,10 +2146,7 @@ describe('Maka Pi TUI runner', () => {
     await waitFor(() => terminal.progressStates.at(-1) === false);
     // Only the followup that was still queued comes back into the editor; the
     // consumed steering text must not be resurrected from the stale mirror.
-    await waitFor(() => {
-      const screen = plainTerminalOutput(terminal.screenOutput());
-      return screen.includes('still queued') && !screen.includes('already consumed');
-    });
+    await waitFor(() => editorInputText(terminal) === 'still queued');
 
     terminal.input('\x03'); // clear the refilled draft
     terminal.input('/exit');
@@ -6358,6 +6356,14 @@ class SteeringTurnDriver implements MakaSessionDriver {
   readonly steered: string[] = [];
   readonly queuedMessages: string[] = [];
   readonly turnOrchestrations: Array<MakaPreparePromptOptions['turnOrchestration']> = [];
+  readonly transcriptListeners = new Set<
+    (
+      sessionId: string,
+      turnId: string,
+      messages: StoredMessage[],
+      reason: MakaTranscriptReplacementReason,
+    ) => void
+  >();
   retractCalls = 0;
   rewindTargets: RewindTarget[] = [];
   private steering: string[] = [];
@@ -6420,10 +6426,33 @@ class SteeringTurnDriver implements MakaSessionDriver {
     yield { type: 'complete', id: 'event-complete', turnId, ts: 2, stopReason: 'user_stop' };
   }
 
+  subscribeTranscriptReplacements(
+    listener: (
+      sessionId: string,
+      turnId: string,
+      messages: StoredMessage[],
+      reason: MakaTranscriptReplacementReason,
+    ) => void,
+  ): () => void {
+    this.transcriptListeners.add(listener);
+    return () => this.transcriptListeners.delete(listener);
+  }
+
   async steer(text: string): Promise<QueueEnqueueOutcome> {
     this.steered.push(text);
     this.steering.push(text);
     this.emitQueueUpdate();
+    const message: Extract<StoredMessage, { type: 'user' }> = {
+      type: 'user',
+      id: `steering-${this.steered.length}`,
+      turnId: 'turn-1',
+      ts: this.steered.length,
+      text,
+      steeringEventId: `steering-${this.steered.length}`,
+    };
+    for (const listener of this.transcriptListeners) {
+      listener(this.getSessionId(), 'turn-1', [message], 'reconcile');
+    }
     return { kind: 'queued' };
   }
 
diff --git a/packages/cli/src/__tests__/runtime-host-session-driver.test.ts b/packages/cli/src/__tests__/runtime-host-session-driver.test.ts
index 1b9dc7abb..bdc92590a 100644
--- a/packages/cli/src/__tests__/runtime-host-session-driver.test.ts
+++ b/packages/cli/src/__tests__/runtime-host-session-driver.test.ts
@@ -44,7 +44,11 @@ import {
   createRuntimeHostMakaSessionDriver,
   type RuntimeHostMakaSessionDriverInput,
 } from '../runtime-host-session-driver.js';
-import { SkillInvocationBlockedError, type MakaAttachedSessionTurn } from '../session-driver.js';
+import {
+  SkillInvocationBlockedError,
+  type MakaAttachedSessionTurn,
+  type MakaTranscriptReplacementReason,
+} from '../session-driver.js';
 import { WAIT_BUDGET_MS } from './tui-terminal-mock.js';
 
 describe('Runtime Host Maka Session driver', () => {
@@ -551,6 +555,46 @@ describe('Runtime Host Maka Session driver', () => {
     });
   });
 
+  test('publishes a durable transcript advance while the active turn is still running', async () => {
+    const attached = new FakeSubscription(continuitySnapshot(), Promise.resolve([]));
+    const durableMessages = [userMessage('turn-1', 'Steer now')];
+    const refresh = new FakeSubscription(
+      continuitySnapshot(),
+      Promise.resolve(durableMessages),
+      'subscription-2',
+    );
+    const connection = new FakeConnection([attached, refresh]);
+    const driver = createRuntimeHostMakaSessionDriver({
+      connection: connection.value,
+      cwd: '/tmp',
+      llmConnectionSlug: 'openai-main',
+      model: 'gpt-5',
+    });
+    await driver.switchSession('session-1');
+    const replacement = deferred<{
+      messages: readonly StoredMessage[];
+      reason: MakaTranscriptReplacementReason;
+    }>();
+    driver.subscribeTranscriptReplacements!((_sessionId, _turnId, messages, reason) =>
+      replacement.resolve({ messages, reason }),
+    );
+
+    attached.push({
+      kind: 'subscription.transcript_advanced',
+      hostEpoch: 'host-1',
+      subscriptionId: 'subscription-1',
+      sequence: 1,
+      sessionId: 'session-1',
+      throughSequence: 0,
+    });
+
+    await waitFor(() => connection.openedSubscriptions === 2);
+    assert.deepEqual(await replacement.promise, {
+      messages: durableMessages,
+      reason: 'reconcile',
+    });
+  });
+
   test('delivers completed thinking while a later step remains active', async () => {
     const subscription = new FakeSubscription(continuitySnapshot(), Promise.resolve([]));
     const connection = new FakeConnection([subscription]);
diff --git a/packages/cli/src/pi-tui-runner.ts b/packages/cli/src/pi-tui-runner.ts
index 5ea8b8f29..8fbfa783a 100644
--- a/packages/cli/src/pi-tui-runner.ts
+++ b/packages/cli/src/pi-tui-runner.ts
@@ -281,11 +281,13 @@ export async function runMakaPiTui(input: MakaPiTuiInput): Promise<void> {
   const tui = new TuiMainScreen(terminal);
   const state = createMakaPiTranscriptState();
   let transcriptLastUsedModel: string | undefined;
-  const rememberTranscriptModel = (messages: readonly StoredMessage[]): void => {
+  let transcriptMessageIds = new Set<string>();
+  const rememberTranscript = (messages: readonly StoredMessage[]): void => {
     transcriptLastUsedModel = latestAssistantModelId(messages);
+    transcriptMessageIds = new Set(messages.map((message) => message.id));
   };
   const replaceTranscript = (messages: readonly StoredMessage[]): void => {
-    rememberTranscriptModel(messages);
+    rememberTranscript(messages);
     replaceTranscriptWithStoredMessages(state, messages);
   };
   let cwd = input.cwd;
@@ -540,8 +542,21 @@ export async function runMakaPiTui(input: MakaPiTuiInput): Promise<void> {
         requestRender();
         return;
       }
-      rememberTranscriptModel(messages);
-      if (hydrateToolsWithStoredMessages(state, turnId, messages)) {
+      const newSteeringMessages = messages.filter(
+        (message): message is Extract<StoredMessage, { type: 'user' }> =>
+          message.type === 'user' &&
+          message.turnId === turnId &&
+          message.steeringEventId !== undefined &&
+          !transcriptMessageIds.has(message.id),
+      );
+      rememberTranscript(messages);
+      for (const message of newSteeringMessages) {
+        appendUserPrompt(state, message.displayText ?? message.text);
+      }
+      if (
+        newSteeringMessages.length > 0 ||
+        hydrateToolsWithStoredMessages(state, turnId, messages)
+      ) {
         shellRunElapsedTicker.sync();
         requestRender();
       }
diff --git a/packages/cli/src/runtime-host-session-channel.ts b/packages/cli/src/runtime-host-session-channel.ts
index 01714f225..d07666d5b 100644
--- a/packages/cli/src/runtime-host-session-channel.ts
+++ b/packages/cli/src/runtime-host-session-channel.ts
@@ -572,6 +572,11 @@ export class RuntimeHostSessionChannel {
   }
 
   #accept(frame: SubscriptionFrame): void {
+    if (frame.kind === 'subscription.transcript_advanced') {
+      const turnId = this.snapshot.rootTurn?.turnId;
+      if (turnId) this.#onTranscriptSettlement(turnId);
+      return;
+    }
     if (frame.kind === 'subscription.session_domain_changed') {
       if (frame.domain === 'runtime_resource') {
         for (const resource of frame.resources) {


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For additional commands, e-mail: commits-help@maka.apache.org