You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maka.apache.org by wy...@apache.org on 2026/10/13 06:02:20 UTC

(maka) branch feat/runtime-host-rust updated: fix(tui): preserve history reading and composer choices

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

M4n5ter pushed a commit to branch feat/runtime-host-rust
in repository https://gitbox.apache.org/repos/asf/maka.git


The following commit(s) were added to refs/heads/feat/runtime-host-rust by this push:
     new c143e7e67f fix(tui): preserve history reading and composer choices
c143e7e67f is described below

commit c143e7e67f92787ab52d16043dd5cc4014f69195
Author: Wang <m4...@gmail.com>
AuthorDate: Tue Oct 13 14:02:02 2026 +0800

    fix(tui): preserve history reading and composer choices
---
 platform/render-terminal/src/application/run.rs    |  32 +++-
 .../src/application/tests/layout.rs                |   9 +
 .../src/application/tests/reading.rs               |  17 ++
 plugins/agent/agent-screen/component-events.ts     |  35 ++--
 plugins/agent/agent-screen/conversation-view.ts    |   5 +-
 plugins/agent/agent-screen/frame.ts                |   4 +-
 plugins/agent/agent-screen/messages.ts             |  37 +++-
 plugins/agent/agent-screen/results.ts              |  25 ++-
 plugins/agent/agent-screen/state.ts                |   1 +
 plugins/agent/agent-screen/task-overview.ts        |  13 +-
 plugins/settings/models/index.ts                   |  31 ++--
 plugins/shared/attachments.ts                      |   6 +-
 plugins/shared/components.ts                       |  17 +-
 plugins/shared/tool-cards.ts                       |   1 +
 products/maka/bundle/tests/connections.test.mjs    |  64 +++++++
 products/maka/bundle/tests/task-workbench.test.mjs | 190 +++++++++++++++++++++
 products/maka/bundle/tests/timeline.test.mjs       | 104 +++++++++++
 products/maka/cli/notices.json                     |  22 +--
 18 files changed, 545 insertions(+), 68 deletions(-)

diff --git a/platform/render-terminal/src/application/run.rs b/platform/render-terminal/src/application/run.rs
index dbb87b0882..7590ef0ac7 100644
--- a/platform/render-terminal/src/application/run.rs
+++ b/platform/render-terminal/src/application/run.rs
@@ -78,6 +78,7 @@ pub fn run(
         }
     }
     let mut exit_key = ExitKey::default();
+    let mut queued_input = None;
     loop {
         if failure.is_some()
             || ((!presenter.surface.is_live()
@@ -119,6 +120,7 @@ pub fn run(
                 // original root. A late receipt cannot close a replacement.
                 screen.take();
                 exit_key = ExitKey::default();
+                queued_input = None;
                 let cleanup = guard.finish().map_err(|e| sanitize(&e.to_string()));
                 if let Err(error) = presenter.surface_closed(&original, cleanup) {
                     presenter.report(&error);
@@ -165,8 +167,13 @@ pub fn run(
                 }
                 presenter.present(scene.expect("successful paint"));
             }
-            if event::poll(Duration::from_millis(16)).map_err(|e| e.to_string())? {
-                let input = event::read().map_err(|e| e.to_string())?;
+            if queued_input.is_some()
+                || event::poll(Duration::from_millis(16)).map_err(|e| e.to_string())?
+            {
+                let input = match queued_input.take() {
+                    Some(input) => input,
+                    None => event::read().map_err(|e| e.to_string())?,
+                };
                 if exit_key.ready(Some(&input), Instant::now()) {
                     return Ok(true);
                 }
@@ -190,7 +197,24 @@ pub fn run(
                             Instant::now(),
                         ));
                 }
+                let scrolling = is_wheel(&input);
                 presenter.input(input).map_err(|e| e.to_string())?;
+                // A trackpad gesture arrives as many small wheel events. Apply
+                // the queued gesture before another live update and repaint,
+                // so streaming cannot repeatedly move the tail ahead of it.
+                // Keep other gestures for the next presented frame.
+                let until = Instant::now() + Duration::from_millis(8);
+                while scrolling
+                    && Instant::now() < until
+                    && event::poll(Duration::ZERO).map_err(|e| e.to_string())?
+                {
+                    let input = event::read().map_err(|e| e.to_string())?;
+                    if !is_wheel(&input) {
+                        queued_input = Some(input);
+                        break;
+                    }
+                    presenter.input(input).map_err(|e| e.to_string())?;
+                }
                 if let Err(error) = presenter.flush_clipboard(&mut std::io::stdout().lock()) {
                     presenter.report(&error);
                 }
@@ -233,6 +257,10 @@ pub fn run(
         std::thread::sleep(Duration::from_millis(10));
     }
 }
+fn is_wheel(event: &Event) -> bool {
+    matches!(event, Event::Mouse(mouse) if matches!(mouse.kind,
+        crossterm::event::MouseEventKind::ScrollUp | crossterm::event::MouseEventKind::ScrollDown))
+}
 fn is_copy(event: &Event) -> bool {
     matches!(event,Event::Key(key) if key.kind!=KeyEventKind::Release && matches!(key.code,KeyCode::Char('c'|'C')) && key.modifiers==KeyModifiers::CONTROL|KeyModifiers::SHIFT)
 }
diff --git a/platform/render-terminal/src/application/tests/layout.rs b/platform/render-terminal/src/application/tests/layout.rs
index 7a96a34b5b..52b8761055 100644
--- a/platform/render-terminal/src/application/tests/layout.rs
+++ b/platform/render-terminal/src/application/tests/layout.rs
@@ -922,6 +922,15 @@ fn follow_end_uses_actual_flush_and_manual_interaction_survives_recipe_updates()
     refresh(&mut presenter);
     flushed(&mut presenter, 10, 5);
     assert_eq!(scroll_state(&presenter, "body"), (3, 3));
+    // One queued gesture is applied against the same presented frame, then
+    // streaming extends the content before its next paint.
+    wheel(&mut presenter, false, 1, 1);
+    wheel(&mut presenter, false, 1, 1);
+    source.borrow_mut()["children"][0]["children"][0]["text"] =
+        json!("0\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13");
+    refresh(&mut presenter);
+    flushed(&mut presenter, 10, 5);
+    assert_eq!(scroll_state(&presenter, "body"), (0, 0));
     assert!(Rc::ptr_eq(
         &owner,
         &presenter.resources.owners[&presenter.root.id()].identity
diff --git a/platform/render-terminal/src/application/tests/reading.rs b/platform/render-terminal/src/application/tests/reading.rs
index c1647033a1..cd44b25edf 100644
--- a/platform/render-terminal/src/application/tests/reading.rs
+++ b/platform/render-terminal/src/application/tests/reading.rs
@@ -93,6 +93,23 @@ fn close(presenter: &mut Presenter) {
     presenter.shutdown().unwrap();
 }
 #[test]
+fn wheel_above_a_short_page_reports_the_original_reading_boundary() {
+    let (_runtime, mut presenter, _) = native(vec![source("one", "first")]);
+    draw(&mut presenter, 30, 12);
+    mouse(&mut presenter, MouseEventKind::ScrollUp, 10, 8);
+    draw(&mut presenter, 30, 12);
+    presenter.sync_native().unwrap();
+    let observed = observation(&presenter);
+    assert_eq!(observed["scroll"]["direction"], "up");
+    assert_eq!(observed["viewport"]["atStart"], true);
+    assert_eq!(observed["viewport"]["atEnd"], true);
+    assert_eq!(
+        observed["viewport"]["readingRevision"],
+        observed["readingRevision"]
+    );
+    close(&mut presenter);
+}
+#[test]
 fn selected_child_follows_original_native_focus_and_only_committed_group_geometry() {
     let group = |key: &str, child| json!({"kind":"stack","key":key,"direction":"column","gap":0,"padding":0,"background":0,"children":[child]});
     let (_runtime, mut presenter, props) = native(vec![
diff --git a/plugins/agent/agent-screen/component-events.ts b/plugins/agent/agent-screen/component-events.ts
index 2d11e7781c..755ccac709 100644
--- a/plugins/agent/agent-screen/component-events.ts
+++ b/plugins/agent/agent-screen/component-events.ts
@@ -777,23 +777,26 @@ export function handleComponent<N extends Node>(
         };
         readHistorySource(state, actions, { kind: 'start' });
       }
-    } else if (
-      input.name === 'older' &&
-      page.before &&
-      !Object.values(state.pending).some(
-        (request) => request.kind === 'history' && request.epoch === state.epoch,
+    } else if (input.name === 'older' && page.before) {
+      // A short page is both at its start and end. Asking for older content
+      // must suspend following even while that native viewport still fits.
+      const visit = state.visits.find((entry) => entry.id === state.selected?.id);
+      if (visit) visit.following = false;
+      if (
+        !Object.values(state.pending).some(
+          (request) => request.kind === 'history' && request.epoch === state.epoch,
+        )
       )
-    )
-      history(
-        state,
-        actions,
-        { kind: 'before', cursor: page.before },
-        page.through,
-        false,
-        false,
-        target === 'tui',
-      );
-    else if (
+        history(
+          state,
+          actions,
+          { kind: 'before', cursor: page.before },
+          page.through,
+          false,
+          false,
+          target === 'tui',
+        );
+    } else if (
       input.name === 'newer' &&
       page.after &&
       !Object.values(state.pending).some(
diff --git a/plugins/agent/agent-screen/conversation-view.ts b/plugins/agent/agent-screen/conversation-view.ts
index f8797ef181..1a54087bec 100644
--- a/plugins/agent/agent-screen/conversation-view.ts
+++ b/plugins/agent/agent-screen/conversation-view.ts
@@ -126,7 +126,10 @@ function readingProps(
           )
           .flatMap((entry) => (entry.toolUseId === null ? [] : [entry.toolUseId]))
       : [],
-    live: !!state.run && !!state.window?.latest,
+    live:
+      !!state.run &&
+      !!state.window?.latest &&
+      state.visits.find((visit) => visit.id === state.selected?.id)?.following !== false,
   };
 }
 
diff --git a/plugins/agent/agent-screen/frame.ts b/plugins/agent/agent-screen/frame.ts
index d3434fd63b..bede4bf041 100644
--- a/plugins/agent/agent-screen/frame.ts
+++ b/plugins/agent/agent-screen/frame.ts
@@ -589,9 +589,9 @@ export function frame<N extends Node>(
       state.dialog === 'terminal' ? { width: pixels(900) } : {},
       state.dialog === 'attachments'
         ? {
-            width: 64,
+            width: state.attachments.selection?.browse ? 80 : 64,
             height: state.attachments.selection?.browse
-              ? 20
+              ? 28
               : Math.min(20, attached.length * 3 + 7),
           }
         : {},
diff --git a/plugins/agent/agent-screen/messages.ts b/plugins/agent/agent-screen/messages.ts
index 8e82b1a23e..82280b05fd 100644
--- a/plugins/agent/agent-screen/messages.ts
+++ b/plugins/agent/agent-screen/messages.ts
@@ -39,7 +39,6 @@ export function appendLive(
   invocationId: string,
   text: string,
 ) {
-  if (state.visits.find((visit) => visit.id === state.selected?.id)?.following === false) return;
   const messageId = part.messageId;
   const field = part.textKind === 'thinking' ? 'thinking' : 'body';
   if (
@@ -50,6 +49,13 @@ export function appendLive(
     )
   )
     return;
+  const previous = state.live.at(-1);
+  const adjacent =
+    previous?.closedAt === null &&
+    previous.messageId === messageId &&
+    previous.invocationId === invocationId &&
+    previous.stepId === part.stepId &&
+    previous.field === field;
   const ids = new Set(state.live.map((fragment) => fragment.messageId));
   let remaining = 16000 - state.live.reduce((total, fragment) => total + fragment.text.length, 0);
   // Thinking shares the existing prefix budget but leaves room for an answer
@@ -57,18 +63,30 @@ export function appendLive(
   if (field === 'thinking') {
     const thinking = state.live.filter((fragment) => fragment.field === 'thinking');
     const thinkingIds = new Set(thinking.map((fragment) => fragment.messageId));
-    if (thinking.length >= 32 || (!thinkingIds.has(messageId) && thinkingIds.size >= 8)) return;
+    if (
+      (!adjacent && thinking.length >= 32) ||
+      (!thinkingIds.has(messageId) && thinkingIds.size >= 8)
+    )
+      return;
     remaining = Math.min(
       remaining,
       8000 - thinking.reduce((total, fragment) => total + fragment.text.length, 0),
     );
   }
   // A truncated surrogate can leave one unused code unit; keep the prefix closed.
-  if (remaining <= 1 || state.live.length >= 64 || (!ids.has(messageId) && ids.size >= 16)) return;
+  if (
+    remaining <= 1 ||
+    (!adjacent && state.live.length >= 64) ||
+    (!ids.has(messageId) && ids.size >= 16)
+  )
+    return;
   text = text.slice(0, remaining);
   const last = text.charCodeAt(text.length - 1);
   if (last >= 0xd800 && last <= 0xdbff) text = text.slice(0, -1);
-  if (text)
+  if (text && adjacent) {
+    previous.text += text;
+    previous.sequence = sequence;
+  } else if (text)
     state.live.push({
       sequence,
       messageId,
@@ -90,9 +108,16 @@ export function closeLive(state: State, sequence: number, invocationId: string,
       fragment.closedAt = sequence;
 }
 
+function chunkId(chunk: NonNullable<State['window']>['page']['chunks'][number]) {
+  return (
+    (chunk.field === 'thinking' ? 'thinking-' + chunk.messageId : chunk.messageId) +
+    (chunk.offset ? '-offset-' + chunk.offset : '')
+  );
+}
+
 export function messages(state: State): Message[] {
   const canonical: Message[] = (state.window?.page.chunks ?? []).map((chunk) => ({
-    id: chunk.field === 'thinking' ? 'thinking-' + chunk.messageId : chunk.messageId,
+    id: chunkId(chunk),
     contentKey: `canonical-${chunk.sequence}-${chunk.offset}${chunk.field === 'thinking' ? '-thinking' : ''}`,
     role: chunk.field === 'thinking' ? 'thinking' : chunk.role,
     timestamp: chunk.timestamp,
@@ -143,7 +168,7 @@ export function turnTimings(state: State, displayed: Message[]): TimelineProps['
     turns.set(chunk.turnId, {
       ...chunk.timing,
       turnId: chunk.turnId,
-      afterMessageId: chunk.field === 'thinking' ? 'thinking-' + chunk.messageId : chunk.messageId,
+      afterMessageId: chunkId(chunk),
     });
   }
   if (state.window?.latest && state.run) {
diff --git a/plugins/agent/agent-screen/results.ts b/plugins/agent/agent-screen/results.ts
index 33550a72b7..f3a042b012 100644
--- a/plugins/agent/agent-screen/results.ts
+++ b/plugins/agent/agent-screen/results.ts
@@ -363,6 +363,7 @@ export function querySucceeded(state: State, actions: Action[], request: Request
       ) {
         state.run = observation.run ?? null;
         state.runThrough = observation.through;
+        if (!state.run && state.notice === 'stopping') state.notice = null;
       }
       if (!state.watch)
         state.parts = observation.parts.map((part) => ({
@@ -475,6 +476,11 @@ export function querySucceeded(state: State, actions: Action[], request: Request
     }
     case 'history': {
       if (request.epoch !== state.epoch || request.navigation !== state.navigation) break;
+      if (
+        request.implicit &&
+        state.visits.find((visit) => visit.id === state.selected?.id)?.following === false
+      )
+        break;
       const page = value as AgentHistoryPage;
       if (page.through !== request.through) {
         queryFailed(state, request);
@@ -513,12 +519,19 @@ export function querySucceeded(state: State, actions: Action[], request: Request
         break;
       }
       const old = state.window;
-      const splitMessage = old?.page.chunks.some((oldChunk) =>
-        page.chunks.some(
-          (chunk) => chunk.sequence === oldChunk.sequence && chunk.field === oldChunk.field,
-        ),
-      );
-      if (request.extend && old?.page.through === page.through && !splitMessage) {
+      if (request.extend && !page.chunks.length) {
+        const position =
+          request.position.kind === 'before' && page.before
+            ? { kind: 'before' as const, cursor: page.before }
+            : request.position.kind === 'after' && page.after
+              ? { kind: 'after' as const, cursor: page.after }
+              : null;
+        if (position) {
+          history(state, actions, position, page.through, request.latest, false, true);
+          break;
+        }
+      }
+      if (request.extend && old?.page.through === page.through) {
         const before = request.position.kind === 'before';
         const chunks = before
           ? [...page.chunks, ...old.page.chunks]
diff --git a/plugins/agent/agent-screen/state.ts b/plugins/agent/agent-screen/state.ts
index b2e1337732..026add76ea 100644
--- a/plugins/agent/agent-screen/state.ts
+++ b/plugins/agent/agent-screen/state.ts
@@ -197,6 +197,7 @@ export interface Visit {
   session: AgentSession;
   epoch: number;
   progress: {
+    turnId: string | null;
     through: number;
     goal: string;
     action: string;
diff --git a/plugins/agent/agent-screen/task-overview.ts b/plugins/agent/agent-screen/task-overview.ts
index 2db7ed7ced..9de6e42977 100644
--- a/plugins/agent/agent-screen/task-overview.ts
+++ b/plugins/agent/agent-screen/task-overview.ts
@@ -29,17 +29,22 @@ import type { State, Visit } from './state.js';
 // replace current progress, and an ended turn never claims a verified delivery.
 export function taskProgress(state: State, locale: string): Visit['progress'] {
   const visit = state.visits.find((entry) => entry.id === state.selected?.id);
-  const previous = visit?.progress ?? null;
+  const page = state.window?.page;
+  const turnId =
+    state.run?.turnId ??
+    (state.window?.latest ? page?.chunks.at(-1)?.turnId : null) ??
+    visit?.progress?.turnId ??
+    null;
+  const previous = visit?.progress?.turnId === turnId ? visit.progress : null;
   const queuedGoal = state.queue?.entries.find((entry) => entry.kind === 'in_flight')?.preview;
   if (queuedGoal && previous) previous.goal = compactLabel(queuedGoal, 100);
-  const page = state.window?.page;
   if (!page || !state.window?.latest || page.through < (previous?.through ?? 0)) return previous;
-  const last = page.chunks.at(-1);
-  const turn = last ? page.chunks.filter((chunk) => chunk.turnId === last.turnId) : [];
+  const turn = page.chunks.filter((chunk) => chunk.turnId === turnId);
   const goal = turn.find((chunk) => chunk.role === 'user' && chunk.field === 'body');
   const tool = [...turn].reverse().find((chunk) => chunk.tool);
   const timing = [...turn].reverse().find((chunk) => chunk.timing)?.timing;
   const progress = {
+    turnId,
     through: page.through,
     goal: queuedGoal
       ? compactLabel(queuedGoal, 100)
diff --git a/plugins/settings/models/index.ts b/plugins/settings/models/index.ts
index 970e812beb..4dca0301a6 100644
--- a/plugins/settings/models/index.ts
+++ b/plugins/settings/models/index.ts
@@ -61,6 +61,7 @@ interface State {
   reveal: ReadingCommand | null;
   open: boolean;
   draft: SelectedModel | null;
+  thinkingByModel: Record<string, ThinkingLevel | null>;
   thinkingOpen: boolean;
   choices: SettingsModelChoice[];
   windowStart: number;
@@ -111,11 +112,10 @@ function sameModel(a: SettingsModelChoice, b: SettingsModelChoice) {
   );
 }
 function selectDraft(state: State, choice: SettingsModelChoice) {
-  const prior = state.draft;
-  const same =
-    prior?.choice.selection.connectionId === choice.selection.connectionId &&
-    prior.choice.selection.modelId === choice.selection.modelId;
-  const selection = { choice, thinkingLevel: same ? prior.thinkingLevel : null };
+  const selection = {
+    choice,
+    thinkingLevel: state.thinkingByModel[modelKey(state, choice)] ?? null,
+  };
   state.draft = selection;
   state.active = state.choices.findIndex((row) => sameModel(row, choice));
   state.thinkingOpen = false;
@@ -124,6 +124,9 @@ function open(state: State, props: ModelsProps) {
   state.open = true;
   state.thinkingOpen = false;
   state.draft = state.staleSelection ? null : props.selection;
+  state.thinkingByModel = state.draft
+    ? { [modelKey(state, state.draft.choice)]: state.draft.thinkingLevel }
+    : {};
   const choice = state.draft
     ? state.choices.find((row) => sameModel(row, state.draft!.choice))
     : state.choices[0];
@@ -242,6 +245,7 @@ function close(state: State) {
   state.open = false;
   state.thinkingOpen = false;
   state.draft = null;
+  state.thinkingByModel = {};
   if (state.pending?.kind === 'text') state.pending.choice = null;
 }
 function frame<N extends Node>(
@@ -259,7 +263,11 @@ function frame<N extends Node>(
     state.publishedSetup = state.setup;
   }
   const chosen = state.staleSelection ? null : props.selection?.choice;
-  const trigger = ui.button('toggle', chosen ? chosen.label : text.chooseModel, props.busy, false, {
+  const effort = props.selection?.thinkingLevel;
+  const label = chosen
+    ? chosen.label + (effort ? ` · ${text.thinking} ${text[thinkingCopy[effort]]}` : '')
+    : text.chooseModel;
+  const trigger = ui.button('toggle', label, props.busy, false, {
     width: fill,
     minWidth: 0,
     justify: 'start',
@@ -623,6 +631,7 @@ export function models<N extends Node>(
         reveal: null,
         open: false,
         draft: null,
+        thinkingByModel: {},
         thinkingOpen: false,
         choices: [],
         windowStart: 0,
@@ -894,6 +903,8 @@ export function models<N extends Node>(
                 ...state.draft,
                 thinkingLevel: value === 'default' ? null : (value as ThinkingLevel),
               };
+              state.thinkingByModel[modelKey(state, state.draft.choice)] =
+                state.draft.thinkingLevel;
               state.thinkingOpen = false;
             }
           } else if (input.key === 'default-refresh') actions.push(...readPage(state));
@@ -925,13 +936,7 @@ export function models<N extends Node>(
                 state.windowStart + 64 < state.choices.length
               )
                 state.windowStart += 32;
-              if (
-                (input.event as { kind?: string }).kind === 'submit' &&
-                state.draft &&
-                supports(state.draft)
-              )
-                actions.push(...capture(state, state.draft));
-              else if (state.next && state.active >= state.choices.length - 3)
+              if (state.next && state.active >= state.choices.length - 3)
                 actions.push(...read(state, true));
             }
           }
diff --git a/plugins/shared/attachments.ts b/plugins/shared/attachments.ts
index 99e131633f..7c6ec2bb87 100644
--- a/plugins/shared/attachments.ts
+++ b/plugins/shared/attachments.ts
@@ -292,11 +292,7 @@ export function attachmentView<N extends Node>(
         ui.box(
           'attachment-browser-view',
           [
-            ui.box(
-              'attachment-browser-body',
-              [ui.text('attachment-help', text.attachmentHelp, 13, theme.muted), ...fileContent],
-              { scroll: 'manual', terminal: { grow: 1 } },
-            ),
+            ui.box('attachment-browser-body', fileContent, { gap: 0, terminal: { grow: 1 } }),
             ui.box(
               'attachment-browser-controls',
               [
diff --git a/plugins/shared/components.ts b/plugins/shared/components.ts
index b77e8252fb..032b2b821e 100644
--- a/plugins/shared/components.ts
+++ b/plugins/shared/components.ts
@@ -1629,9 +1629,22 @@ export function components<N extends Node>(
           observed.pendingCommand == null
         ) {
           next.scrollRevision = observed.scroll.readingRevision;
-          if (observed.scroll.direction === 'up' && observed.viewport.atStart && props.older)
+          const rows = toolCards(props.messages);
+          const edgeRows = Math.max(1, Math.min(2, Math.floor(rows.length / 2)));
+          const visible = new Set(observed.viewport.visibleChildKeys);
+          const visibleRow = ({ message }: (typeof rows)[number]) =>
+            visible.has('message-' + message.id) || visible.has('time-' + message.contentKey);
+          if (
+            observed.scroll.direction === 'up' &&
+            props.older &&
+            (observed.viewport.atStart || rows.slice(0, edgeRows).some(visibleRow))
+          )
             out.events.push({ name: 'older', value: {} });
-          else if (observed.scroll.direction === 'down' && observed.viewport.atEnd && props.newer)
+          else if (
+            observed.scroll.direction === 'down' &&
+            props.newer &&
+            (observed.viewport.atEnd || rows.slice(-edgeRows).some(visibleRow))
+          )
             out.events.push({ name: 'newer', value: {} });
         }
         if (
diff --git a/plugins/shared/tool-cards.ts b/plugins/shared/tool-cards.ts
index fb0cdbb1cb..a126ee153d 100644
--- a/plugins/shared/tool-cards.ts
+++ b/plugins/shared/tool-cards.ts
@@ -78,6 +78,7 @@ export function toolCards(
     .map((message) => ({
       message:
         message.tool?.call &&
+        bySequence.get(message.source!)?.length === 1 &&
         (message.role === 'tool_call' ||
           (!bySequence.has(message.tool.call.sequence) &&
             (resultsByCall.get(message.tool.call.sequence)?.size ?? 0) === 1))
diff --git a/products/maka/bundle/tests/connections.test.mjs b/products/maka/bundle/tests/connections.test.mjs
index dddf1b52f3..ed7dea3130 100644
--- a/products/maka/bundle/tests/connections.test.mjs
+++ b/products/maka/bundle/tests/connections.test.mjs
@@ -70,6 +70,11 @@ async function client(name, platform, extra = {}) {
       out = component.event(out.frame.state, input, props, {});
       return this;
     },
+    update(next) {
+      Object.assign(props, next);
+      out = component.update(out.frame.state, props, {});
+      return this;
+    },
     raw(key, kind = 'submit') {
       return this.event({ kind: 'raw', key, event: { kind } });
     },
@@ -97,6 +102,65 @@ async function client(name, platform, extra = {}) {
   };
 }
 for (const platform of ['tui', 'desktop']) {
+  test(`${platform} model and thinking edits stay open and survive navigation until applied`, async () => {
+    const choice = (id) => ({
+      selection: { connectionId: 'a', revision: 1, modelId: id },
+      connectionName: 'A',
+      label: id,
+      thinkingLevels: ['low', 'high'],
+      defaultThinkingLevel: 'low',
+    });
+    const a = choice('a'),
+      b = choice('b');
+    const selection = { choice: a, thinkingLevel: 'high' };
+    const c = await client('models', platform, {
+      catalogEpoch: 0,
+      openRequest: null,
+      selection,
+      busy: false,
+    });
+    const page = (choices) => ({
+      kind: 'ready',
+      revision: 3,
+      choices,
+      defaultModel: { kind: 'none' },
+      next: null,
+    });
+    c.reply(c.action('models'), page([a, b]));
+    c.query('model-query', 1, '').raw('toggle');
+    const key = (id) => c.state.choiceKeys[JSON.stringify(['a', id])];
+    c.raw(key('b'));
+    assert.equal(c.state.open, true, 'clicking a model leaves its thinking control available');
+    assert.equal(c.action('read'), undefined, 'row selection does not commit');
+    c.raw('thinking-low');
+    c.raw(key('a'), 'activate');
+    assert.equal(c.state.draft.thinkingLevel, 'high');
+    c.raw(key('b'), 'activate');
+    assert.equal(c.state.draft.thinkingLevel, 'low');
+    c.query('model-query', 2, 'a');
+    c.reply(c.action('read'), { text: 'a' });
+    c.reply(c.action('models'), page([a]));
+    assert.equal(c.state.draft.thinkingLevel, 'high', 'search preserves the applied effort');
+    c.query('model-query', 3, '');
+    c.reply(c.action('models'), page([a, b]));
+    c.raw(key('b')).raw('apply');
+    c.reply(c.action('read'), { text: '' });
+    const applied = c.out.events.find((event) => event.name === 'selected').value;
+    assert.equal(applied.thinkingLevel, 'low');
+    assert.equal(applied.choice.selection.modelId, 'b');
+    assert.equal(c.state.open, false);
+    c.update({ selection: applied }).raw('toggle');
+    c.raw(key('a'), 'activate').raw(key('b'), 'activate');
+    assert.equal(
+      c.state.draft.thinkingLevel,
+      'low',
+      'reopened selection survives keyboard navigation',
+    );
+    c.raw('thinking-high').raw('close').raw('toggle');
+    assert.equal(c.state.draft.thinkingLevel, 'low', 'cancel discards unapplied edits');
+    c.raw('thinking-default').raw(key('a'), 'activate').raw(key('b'), 'activate');
+    assert.equal(c.state.draft.thinkingLevel, null, 'explicit default survives navigation');
+  });
   test(`${platform} provider search Enter selects its visible result and exposes anonymous endpoints`, async () => {
     const c = await client('connection-form', platform);
     const anonymous = {
diff --git a/products/maka/bundle/tests/task-workbench.test.mjs b/products/maka/bundle/tests/task-workbench.test.mjs
index f8d239a401..51ad1ec13c 100644
--- a/products/maka/bundle/tests/task-workbench.test.mjs
+++ b/products/maka/bundle/tests/task-workbench.test.mjs
@@ -359,6 +359,62 @@ test('returning to a streaming task preserves its visible prefix until latest is
   assert.equal(c.action('history').input.through, 50);
 });
 
+test('returning to a running task buffers every delta while preserving the reading position', () => {
+  const run = { invocationId: 'run-a', turnId: 'turn-1', state: 'running' };
+  const c = client()
+    .observe(1, run)
+    .history(page(1, [chunk(1, 'Goal', { role: 'user' })]));
+  const reader = c.reader();
+  c.select('Task B').observe(1).history(page(1, []));
+  c.select('Task A').observe(1, run);
+  const landing = structuredClone(c.state.landing);
+  const events = [
+    {
+      sequence: 2,
+      invocationId: 'run-a',
+      turnId: 'turn-1',
+      fact: {
+        kind: 'part_started',
+        messageId: 'stream-a',
+        stepId: 'step-a',
+        partId: 'text',
+        textKind: 'text',
+      },
+    },
+    ...Array.from({ length: 100 }, (_, i) => ({
+      sequence: i + 3,
+      invocationId: 'run-a',
+      turnId: 'turn-1',
+      fact: { kind: 'delta', stepId: 'step-a', partId: 'text', text: `片段${i}🙂 ` },
+    })),
+  ];
+  c.event({
+    kind: 'action',
+    key: c.state.watch.key,
+    event: {
+      status: 'updated',
+      value: {
+        kind: 'events',
+        through: 102,
+        nextAfter: 102,
+        events,
+      },
+    },
+  });
+  assert.equal(c.find('task-state').text, 'Working');
+  assert.equal(c.find(reader).props.live, false, 'new text does not re-enable following');
+  assert.deepEqual(c.state.landing, landing);
+  assert.equal(c.action('history'), undefined);
+  const expected = events
+    .slice(1)
+    .map((event) => event.fact.text)
+    .join('');
+  assert.equal(c.state.live.map((fragment) => fragment.text).join(''), expected);
+  c.raw('task-latest').history(page(102, [chunk(1, 'Goal', { role: 'user' })]));
+  assert.equal(c.find(reader).props.live, true);
+  assert.ok(c.find(reader).props.messages.some((message) => message.text === expected));
+});
+
 test('output review opens original structured evidence and full output at the same history fence', () => {
   const tool = {
     toolUseId: 'write-1',
@@ -414,3 +470,137 @@ test('long task visits stay within the UI call envelope while recent readers rem
   assert.ok(c.state.visits.length <= 3);
   assert.ok(Buffer.byteLength(JSON.stringify(c.out)) < 1024 * 1024);
 });
+
+test('prefetching adjacent sections of one long message preserves the visible section and anchor', () => {
+  const part = (offset, text) => chunk(10, text, { offset, totalBytes: 60000 });
+  const cursor = (offset) => ({ sequence: 10, offset, field: 'body' });
+  const c = client()
+    .observe(20)
+    .history(page(20, [part(40000, 'Tail')], { before: cursor(40000) }));
+  const reader = c.reader();
+  const landing = structuredClone(c.state.landing);
+  const tail = c.find(reader).props.messages[0];
+  c.child(reader, 'older').history(
+    page(20, [part(20000, 'Middle')], {
+      before: cursor(20000),
+      after: cursor(40000),
+    }),
+  );
+  assert.deepEqual(c.state.landing, landing);
+  assert.deepEqual(c.find(reader).props.messages.at(-1), tail);
+  assert.equal(new Set(c.find(reader).props.messages.map((message) => message.id)).size, 2);
+  c.child(reader, 'older').history(page(20, [part(0, 'Start')], { after: cursor(20000) }));
+  assert.deepEqual(c.state.landing, landing);
+  assert.deepEqual(
+    c.find(reader).props.messages.map((message) => message.text),
+    ['Start', 'Middle', 'Tail'],
+  );
+  assert.equal(new Set(c.find(reader).props.messages.map((message) => message.id)).size, 3);
+});
+
+test('scrolling traverses empty history pages without another gesture or losing the visible anchor', () => {
+  const cursor = (sequence) => ({ sequence, offset: 0, field: 'body' });
+  for (const direction of ['older', 'newer']) {
+    const backward = direction === 'older';
+    const c = client()
+      .observe(100)
+      .history(
+        page(100, [chunk(50, 'Visible')], {
+          before: cursor(40),
+          after: cursor(60),
+        }),
+      );
+    const reader = c.reader();
+    const landing = structuredClone(c.state.landing);
+    c.child(reader, direction).history(
+      page(100, [], {
+        before: cursor(30),
+        after: cursor(70),
+      }),
+    );
+    assert.deepEqual(c.action('history').input.position, {
+      kind: backward ? 'before' : 'after',
+      cursor: cursor(backward ? 30 : 70),
+    });
+    assert.equal(c.action('history').input.through, 100);
+    assert.deepEqual(
+      c.find(reader).props.messages.map((message) => message.text),
+      ['Visible'],
+    );
+    c.history(
+      page(100, [chunk(backward ? 20 : 80, 'Adjacent')], {
+        before: backward ? null : cursor(70),
+        after: backward ? cursor(30) : null,
+      }),
+    );
+    assert.deepEqual(c.state.landing, landing);
+    assert.deepEqual(
+      c.find(reader).props.messages.map((message) => message.text),
+      backward ? ['Adjacent', 'Visible'] : ['Visible', 'Adjacent'],
+    );
+  }
+});
+
+test('a new turn cannot inherit the previous turn goal when its user message is outside the tail page', () => {
+  const c = client()
+    .observe(10)
+    .history(page(10, [chunk(1, 'Old goal', { role: 'user' })]));
+  assert.equal(c.find('task-goal').text, 'Old goal');
+  c.raw('task-latest').history(page(10, [chunk(9, 'New output', { turnId: 'turn-2' })]));
+  assert.equal(c.find('task-goal'), undefined);
+  c.raw('task-latest').history(
+    page(10, [chunk(8, 'New goal', { turnId: 'turn-2', role: 'user' })]),
+  );
+  assert.equal(c.find('task-goal').text, 'New goal');
+  c.raw('task-latest').history(page(10, [chunk(9, 'New output', { turnId: 'turn-2' })]));
+  assert.equal(c.find('task-goal').text, 'New goal');
+});
+
+test('reading older content on a short page suspends automatic following including an in-flight tail reply', () => {
+  const c = client()
+    .observe(10, { invocationId: 'run-a', turnId: 'turn-1', state: 'running' })
+    .history(
+      page(10, [chunk(9, 'Visible')], {
+        before: { sequence: 8, offset: 0, field: 'body' },
+      }),
+    );
+  const reader = c.reader();
+  c.state.latestKnown = 20;
+  c.child(reader, 'tailReady', { landing: c.state.landing.sequence, readingRevision: 1 });
+  const pending = c.action('history');
+  assert.ok(pending);
+  c.child(reader, 'older');
+  c.reply(pending, page(20, [chunk(19, 'New output')]));
+  assert.deepEqual(
+    c.find(reader).props.messages.map((message) => message.text),
+    ['Visible'],
+  );
+  c.child(reader, 'older').history(
+    page(10, [chunk(7, 'Older')], {
+      after: { sequence: 8, offset: 0, field: 'body' },
+    }),
+  );
+  c.child(reader, 'tailReady', { landing: c.state.landing.sequence, readingRevision: 2 });
+  assert.equal(c.action('history'), undefined);
+  assert.equal(c.find(reader).props.live, false);
+  assert.deepEqual(
+    c.find(reader).props.messages.map((message) => message.text),
+    ['Older', 'Visible'],
+  );
+  c.raw('task-latest').history(page(20, [chunk(19, 'New output')]));
+  assert.equal(c.find(reader).props.live, true);
+});
+
+test('a settled stop observation clears the stopping notice and shows the canonical stopped outcome', () => {
+  const c = client();
+  c.state.notice = 'stopping';
+  c.observe(10).history(
+    page(10, [
+      chunk(9, 'Partial output', {
+        timing: { startedAt: 1, finished: { at: 2, outcome: 'aborted' } },
+      }),
+    ]),
+  );
+  assert.equal(c.state.notice, null);
+  assert.equal(c.find('task-state').text, 'Stopped');
+});
diff --git a/products/maka/bundle/tests/timeline.test.mjs b/products/maka/bundle/tests/timeline.test.mjs
new file mode 100644
index 0000000000..a5eaabdaf8
--- /dev/null
+++ b/products/maka/bundle/tests/timeline.test.mjs
@@ -0,0 +1,104 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import assert from 'node:assert/strict';
+import { test } from 'node:test';
+const { activate } = await import('../dist/packages/appearance/tui.js');
+const theme = activate({ scheme: 'dark' }).Appearance.value;
+const { component } = await import('../dist/packages/timeline/tui.js');
+
+test('history prefetches before an edge without treating stale or commanded viewports as scrolling', () => {
+  const props = {
+    theme,
+    locale: 'en',
+    copyRequest: null,
+    browse: null,
+    timings: [],
+    markReadEnabled: false,
+    toolContext: null,
+    waitingTools: [],
+    liveTools: [],
+    messages: Array.from({ length: 8 }, (_, i) => ({
+      id: String(i),
+      contentKey: String(i),
+      role: 'assistant',
+      text: 'Message ' + i,
+      timestamp: null,
+      source: i + 1,
+      image: false,
+      continued: false,
+      tool: null,
+    })),
+    source: null,
+    search: null,
+    loading: false,
+    older: true,
+    newer: true,
+    latest: false,
+    landing: { sequence: 0, edge: 'end' },
+    live: true,
+  };
+  let out = component.mount(props, {});
+  const observed = (revision, direction, visibleChildKeys, extra = {}) => {
+    out = component.event(
+      out.frame.state,
+      {
+        kind: 'component',
+        child: 'reading',
+        name: 'observed',
+        event: {
+          readingRevision: revision,
+          scroll: { readingRevision: revision, direction },
+          pendingCommand: null,
+          viewport: { readingRevision: revision, atStart: false, atEnd: false, visibleChildKeys },
+          ...extra,
+        },
+      },
+      props,
+      {},
+    );
+    return out.events.filter((event) => event.name === 'older' || event.name === 'newer');
+  };
+  assert.deepEqual(observed(1, 'up', ['message-3', 'message-4']), []);
+  assert.deepEqual(observed(2, 'up', ['message-1', 'message-2']), [{ name: 'older', value: {} }]);
+  assert.deepEqual(observed(2, 'up', ['message-1']), [], 'one request for each original scroll');
+  assert.deepEqual(observed(3, 'down', ['message-5', 'message-6']), [{ name: 'newer', value: {} }]);
+  assert.deepEqual(
+    observed(4, 'up', [], {
+      viewport: {
+        readingRevision: 3,
+        atStart: true,
+        atEnd: false,
+        visibleChildKeys: ['message-0'],
+      },
+    }),
+    [],
+    'wait for the viewport actually produced by the scroll',
+  );
+  assert.deepEqual(observed(4, 'up', ['message-0'], { pendingCommand: 7 }), []);
+  assert.deepEqual(observed(4, 'up', ['message-0']), [{ name: 'older', value: {} }]);
+  props.messages = props.messages.slice(0, 2);
+  assert.deepEqual(
+    observed(5, 'up', ['message-1']),
+    [],
+    'do not evict the visible tail of a two-part message',
+  );
+  assert.deepEqual(observed(6, 'up', ['message-0']), [{ name: 'older', value: {} }]);
+  assert.deepEqual(observed(7, 'down', ['message-0']), []);
+});
diff --git a/products/maka/cli/notices.json b/products/maka/cli/notices.json
index 6e2a6be0bd..aa194fa09b 100644
--- a/products/maka/cli/notices.json
+++ b/products/maka/cli/notices.json
@@ -44,22 +44,22 @@
     "plugins/agent/acp-executor/native/Cargo.toml": "6a896cb424d361ef3a98efa56a92ffc8680156473822bd1d38374b60f3f1da66",
     "plugins/agent/acp-executor/package.ts": "9c055b7299b8d7eaf8c91bea7e684dd15164212ac2a6ef341322464bc88edfc5",
     "plugins/agent/agent-screen/actions.ts": "93d59bc6ec3108a96d7840c5f9a25c132fd99610c98c7250f7657b9f94df2ecf",
-    "plugins/agent/agent-screen/component-events.ts": "91a97fe4cdc654c9c218c1e0ad73b36a3b471fe88434b62d75d7078c5dd46960",
-    "plugins/agent/agent-screen/conversation-view.ts": "6082c6bedd560ca4044546967f828a9c4e50460f360ea05f1502b81cf96c5914",
+    "plugins/agent/agent-screen/component-events.ts": "7e606d950294903e6b049b074422ac4538fdbf9421c0c36a7372dfec16716093",
+    "plugins/agent/agent-screen/conversation-view.ts": "24969c432934403acafd0c8e3351eb34c0372154534dfdcc247cb75d83e01e01",
     "plugins/agent/agent-screen/desktop.ts": "d64f976e965c124ab736edf11adc5d998d22d021c8e85753f88a963d16f2834f",
     "plugins/agent/agent-screen/events.ts": "5b3a966990d8434729b2d01270aef5c57e7bdf42d08ab43c82e723c2002e3e10",
-    "plugins/agent/agent-screen/frame.ts": "c7952902574061f8918f9e9afd2312bdbfa4f315862fb71a5039a22d210edb36",
+    "plugins/agent/agent-screen/frame.ts": "4284b1d35dbfe70a36b751f77c02cb5bb084e3e0ef0d7896ee91e166e51cb32f",
     "plugins/agent/agent-screen/header-view.ts": "8adf2d03c1cb523138ffc3ac97cb7c598ed76683af97e54091cf271a0c5c861c",
     "plugins/agent/agent-screen/index.ts": "ef4ec3e1722887782d1d5f30b536ebb098abd026aa72e15067357ca3c23f98aa",
-    "plugins/agent/agent-screen/messages.ts": "3a4ec6dc2600580ecd8310990ac95370591b810b152b3fdfad4b8c6be6e7ef80",
+    "plugins/agent/agent-screen/messages.ts": "f9bf92286bce263e023a8497c3aba97ec6f86e033c17c31140056639550dcd37",
     "plugins/agent/agent-screen/navigation-view.ts": "5674c945ba8307387e295f8f398da61184dfcc662b1d10d9058b67c755a1096b",
     "plugins/agent/agent-screen/outputs-view.ts": "85458c8556b9a5da0c36e8a29b0c1208b4b9a0a7948732c990e8830d52490395",
     "plugins/agent/agent-screen/package.ts": "bc925efa98b53ff956ea7ec43b6d1b09e2a740b3b9a0525e4bef8e0877c44018",
     "plugins/agent/agent-screen/pages-view.ts": "2102dea3cc5c739fed04f6e4f0d17d1d22ac01c25fae924a5e46baad972e95b3",
     "plugins/agent/agent-screen/raw-events.ts": "b87b41494b30aa9847441496aa501798084df1d43500826f993bfd6e88b35182",
-    "plugins/agent/agent-screen/results.ts": "b24de0e4467e98ecf7dac6c7b804374193780f943293f46831b12ebde278bb5b",
-    "plugins/agent/agent-screen/state.ts": "f1ff2debdd571e7e80747549c05e18c3025bf04604094c932b935b4c8af04541",
-    "plugins/agent/agent-screen/task-overview.ts": "3970ba9d4421eabe7d694d8ea0b373de59a3d8c4edfb9d275c51fcdcf0cc3f93",
+    "plugins/agent/agent-screen/results.ts": "1f1fca229db8a7a6ab35ba18564ddf2d1b5e86e7dfee5aa1df1f4457043b4c4b",
+    "plugins/agent/agent-screen/state.ts": "605a9ab488da78be5d4cc37ea1ec7d5c527cbff03a784a2f074c5a99e583b64d",
+    "plugins/agent/agent-screen/task-overview.ts": "4ceca90536fcc91619571d8d46caab33146ba6298f88bd85a626cc995c4acb27",
     "plugins/agent/agent-screen/tui.ts": "c56f6d0f7e2382f67aef31a486b4524073a401d0ddc4dae4b92c7aaf9a7933db",
     "plugins/agent/agent/package.ts": "b0124f84e1701973067f3d6d38b40f71cc5f6cd7b80a5c5d93744e957603abd9",
     "plugins/agent/api/Cargo.toml": "876a95a3f126bcd1c0f590b84e307bebcf714117f7f1d28fe4a8eb7b524d703a",
@@ -257,7 +257,7 @@
     "plugins/settings/model-default/package.ts": "a9be3d2e7d9fd7c7db382b0012adfa1fa53c2968b2aa40d5b92e86ca2cc0f198",
     "plugins/settings/model-default/tui.ts": "a4601ba20b67e1d12c6738e094d290b8d0d40bfee83afca911e98ceeca9de92e",
     "plugins/settings/models/desktop.ts": "bf15c189eae2714e63c244a3f33543969b4a424af27f06251f45b8fd2a91c72e",
-    "plugins/settings/models/index.ts": "8bc904bd88103d92d0e76ae3489a5419e54f8537c1b5ad1d1b5aea2b7b2735cc",
+    "plugins/settings/models/index.ts": "8f1abf24c5f51102ca77e854f5eea8a07df703a9408c0ac35b20cbc599e9aef8",
     "plugins/settings/models/package.ts": "f20f7f940e9f0d5f2ad6660166341782a595ca8dd4f2341b3dea6b07dc29f1fb",
     "plugins/settings/models/tui.ts": "112e16f3166a7d3f2eaf11d557c7a18e94141229b183b1ea3d73986a413e1c1b",
     "plugins/settings/native/Cargo.toml": "fe9e5c419efb8b518d99f2fcf3250300e496c01f157d20ca753c51e049a93a08",
@@ -271,9 +271,9 @@
     "plugins/settings/store/Cargo.toml": "272ccd5e4f01936bb537bc96819be7ee13e12f00262b2a37a4844e482eedcf69",
     "plugins/shared/agent-imports.ts": "14ce39353611cab4d5d4aa043737929986d147f474bf2b3f71dded265179870c",
     "plugins/shared/appearance-editor.ts": "f564f53cd126f1ebe4fe8cca638823769dd6ff84512bf42f074053afa76ee4b5",
-    "plugins/shared/attachments.ts": "9584769234c14281eb7cbdd9a816f4f47ca9b6fd20a6c58e4d5532b0183d93c9",
+    "plugins/shared/attachments.ts": "f44b8800041f136370adc93011fe49905e0e3e631893914f13c87d037523f5b0",
     "plugins/shared/bookmark.ts": "d16b12b7aa9fd900271d6d8469c45d40e9295c8bd949adbcc7280f76e1d79dfc",
-    "plugins/shared/components.ts": "a2ac18038b63e057b6087a6a2d2fe008a1e92d63216a956eca68169cc2cd3674",
+    "plugins/shared/components.ts": "d79d8538cd76762fc8e73fb5a83bb57d074b468db2604c855a66b2690de92cb6",
     "plugins/shared/contracts.ts": "a5927f9d281e6057b2525fc5cb8e6f7f2073adfcaa8b25c26d4c60c7152c452a",
     "plugins/shared/conversation-search.ts": "0f5c759e9d290cfdc82c7fbd5bcfe688e6b0004ba29b85a38ab30844de917a57",
     "plugins/shared/deployment-document.ts": "0fd08cd83442f4300d8f4f6cadf18fada02f40bf46c523e93f99541a5319e0da",
@@ -291,7 +291,7 @@
     "plugins/shared/settings.ts": "36f6d6e17af54ca538fe3e0d355679598477f0f4551398085e4476ba7b893395",
     "plugins/shared/skill-import.ts": "cdca0bf2631cb25f5e420fbff799b10d3a0ed5f3ab0dd2fdcafa7e916fbe97f0",
     "plugins/shared/terminal-theme.ts": "9022a58c837bf39292f04b6383eed3cd3e875bd9635570f871b065c87f5ba395",
-    "plugins/shared/tool-cards.ts": "2fb0631f0ed68ab9b14e279a2adaae082fdc4b2cc59a8eea670eddc2bdbe0eef",
+    "plugins/shared/tool-cards.ts": "2eccd4e3445ca8f61c95dadb0d33adc549af1c217451421c5f6657db220c5426",
     "plugins/shared/tui-locale.ts": "4493d55c4e7e678f3187b552843d2295736ca16225a6fda19b82ab379090c525",
     "plugins/shared/value.ts": "9b1b52b05ca45a4e37fbab6454810fca2a474c8532941e25c15ab897aaa2d607",
     "plugins/shared/view.ts": "c4cf9fa7368adaf8d2da9541efcfc83b3328363c1ce617976af416e07fb316cc",


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