maka-agent 0.2.0-dev.24.20260907 → 0.2.0-dev.26.20260909

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (154) hide show
  1. package/README.md +38 -19
  2. package/README.zh-CN.md +34 -19
  3. package/dist/mcp-capability-provider.js +4 -1
  4. package/dist/pi-transcript-tools.js +6 -0
  5. package/dist/pi-tui-layout.js +56 -10
  6. package/dist/pi-tui-mcp-status.js +2 -2
  7. package/dist/pi-tui-pickers.js +121 -30
  8. package/dist/pi-tui-runner.js +156 -42
  9. package/dist/pi-tui-todo.js +221 -0
  10. package/dist/pi-tui-transcript-viewer.js +134 -7
  11. package/dist/runtime-host-cli-context.js +70 -13
  12. package/dist/runtime-host-handoff-surface.js +51 -26
  13. package/dist/runtime-host-local-handoff.js +11 -1
  14. package/dist/runtime-host-service-manager.js +0 -3
  15. package/dist/runtime-host-session-channel.js +13 -0
  16. package/dist/runtime-host-session-driver.js +42 -1
  17. package/dist/tui-autocomplete-layout.js +3 -1
  18. package/dist/tui-copy-catalog.js +85 -6
  19. package/dist/tui-editor-render.js +36 -0
  20. package/dist/tui-mcp-control.js +41 -6
  21. package/native/runtime-host-windows-task-launcher/prebuilds/win32-x64/maka-runtime-host-task-launcher.exe +0 -0
  22. package/node_modules/@maka/core/dist/agent-run.js +1 -0
  23. package/node_modules/@maka/core/dist/attachments.js +3 -0
  24. package/node_modules/@maka/core/dist/canonical-json.js +27 -0
  25. package/node_modules/@maka/core/dist/capabilities.js +35 -0
  26. package/node_modules/@maka/core/dist/computer-use.js +0 -1
  27. package/node_modules/@maka/core/dist/display-redaction.js +18 -0
  28. package/node_modules/@maka/core/dist/llm-connections.js +13 -0
  29. package/node_modules/@maka/core/dist/model-call-attempt.js +2 -0
  30. package/node_modules/@maka/core/dist/model-metadata.js +4 -0
  31. package/node_modules/@maka/core/dist/redaction.js +35 -0
  32. package/node_modules/@maka/core/dist/run-composition.js +77 -2
  33. package/node_modules/@maka/core/dist/runtime-event.js +3 -0
  34. package/node_modules/@maka/core/dist/session.js +57 -6
  35. package/node_modules/@maka/core/dist/slash-command-catalog.js +1 -0
  36. package/node_modules/@maka/core/dist/tool-result-record-schema.js +2 -1
  37. package/node_modules/@maka/core/dist/workhub-action-result.js +68 -0
  38. package/node_modules/@maka/core/package.json +3 -3
  39. package/node_modules/@maka/eval/dist/experiment-directory.js +4 -13
  40. package/node_modules/@maka/eval/dist/toolchain-verification.js +1 -1
  41. package/node_modules/@maka/mcp/dist/form-elicitation.js +213 -0
  42. package/node_modules/@maka/mcp/dist/index.js +337 -71
  43. package/node_modules/@maka/runtime/dist/agent-run-inspect.js +0 -13
  44. package/node_modules/@maka/runtime/dist/agent-run.js +90 -2
  45. package/node_modules/@maka/runtime/dist/ai-sdk-backend.js +16 -8
  46. package/node_modules/@maka/runtime/dist/ai-sdk-turn.js +57 -50
  47. package/node_modules/@maka/runtime/dist/archive-read-tool.js +2 -2
  48. package/node_modules/@maka/runtime/dist/context-budget-helpers.js +0 -16
  49. package/node_modules/@maka/runtime/dist/conversation-copy.js +153 -11
  50. package/node_modules/@maka/runtime/dist/github-copilot-oauth-enrollment.js +3 -3
  51. package/node_modules/@maka/runtime/dist/goal-continuation.js +5 -1
  52. package/node_modules/@maka/runtime/dist/ledger-tool-result-archive-reader.js +69 -1
  53. package/node_modules/@maka/runtime/dist/mcp-tools.js +7 -2
  54. package/node_modules/@maka/runtime/dist/model-fetcher.js +42 -2
  55. package/node_modules/@maka/runtime/dist/model-history.js +0 -112
  56. package/node_modules/@maka/runtime/dist/plugin-runtime.js +20 -10
  57. package/node_modules/@maka/runtime/dist/plugin-tool-service.js +205 -0
  58. package/node_modules/@maka/runtime/dist/provider-request-telemetry.js +8 -1
  59. package/node_modules/@maka/runtime/dist/request-shape.js +23 -0
  60. package/node_modules/@maka/runtime/dist/runtime-event-backfill.js +1 -1
  61. package/node_modules/@maka/runtime/dist/runtime-event-read-model.js +24 -1
  62. package/node_modules/@maka/runtime/dist/runtime-kernel.js +127 -13
  63. package/node_modules/@maka/runtime/dist/session-manager.js +20 -36
  64. package/node_modules/@maka/runtime/dist/stream-graph-coordinator.js +21 -0
  65. package/node_modules/@maka/runtime/dist/stream-graph-supervisor-tools.js +0 -5
  66. package/node_modules/@maka/runtime/dist/test-connection.js +0 -1
  67. package/node_modules/@maka/runtime/dist/tool-activation-identity.js +41 -0
  68. package/node_modules/@maka/runtime/dist/tool-availability.js +11 -1
  69. package/node_modules/@maka/runtime/dist/tool-result-archive-resource.js +63 -1
  70. package/node_modules/@maka/runtime/dist/tool-result-archive-transition.js +42 -12
  71. package/node_modules/@maka/runtime/dist/tool-result-archive.js +27 -4
  72. package/node_modules/@maka/runtime/package.json +1 -0
  73. package/node_modules/@maka/runtime-host/dist/client/connection.js +30 -5
  74. package/node_modules/@maka/runtime-host/dist/client/host-handoff-copy.js +52 -13
  75. package/node_modules/@maka/runtime-host/dist/client/host-handoff.js +2 -0
  76. package/node_modules/@maka/runtime-host/dist/client/host-retirement.js +4 -2
  77. package/node_modules/@maka/runtime-host/dist/client/peer-client.js +18 -1
  78. package/node_modules/@maka/runtime-host/dist/client/session-subscription.js +19 -0
  79. package/node_modules/@maka/runtime-host/dist/control/endpoint.js +1 -3
  80. package/node_modules/@maka/runtime-host/dist/node-error.js +22 -0
  81. package/node_modules/@maka/runtime-host/dist/operator/managed-deployment.js +1 -3
  82. package/node_modules/@maka/runtime-host/dist/peer-mesh/limits.js +0 -1
  83. package/node_modules/@maka/runtime-host/dist/peer-mesh/model.js +1 -1
  84. package/node_modules/@maka/runtime-host/dist/peer-reachability/publisher.js +1 -3
  85. package/node_modules/@maka/runtime-host/dist/protocol/index.js +9 -1
  86. package/node_modules/@maka/runtime-host/dist/protocol/operations.js +3 -2
  87. package/node_modules/@maka/runtime-host/dist/protocol/plan.js +1 -0
  88. package/node_modules/@maka/runtime-host/dist/protocol/plugin-platform.js +27 -5
  89. package/node_modules/@maka/runtime-host/dist/protocol/project-catalog.js +2 -2
  90. package/node_modules/@maka/runtime-host/dist/protocol/session-continuity.js +1 -0
  91. package/node_modules/@maka/runtime-host/dist/protocol/workhub-coordination.js +86 -157
  92. package/node_modules/@maka/runtime-host/dist/server/access-credential-store.js +4 -0
  93. package/node_modules/@maka/runtime-host/dist/server/agent-graph-coordinator.js +0 -3
  94. package/node_modules/@maka/runtime-host/dist/server/canonical-session-projection.js +2 -1
  95. package/node_modules/@maka/runtime-host/dist/server/connection-session.js +28 -8
  96. package/node_modules/@maka/runtime-host/dist/server/daily-review-coordinator.js +112 -56
  97. package/node_modules/@maka/runtime-host/dist/server/execution-artifacts.js +32 -32
  98. package/node_modules/@maka/runtime-host/dist/server/execution-composition.js +85 -25
  99. package/node_modules/@maka/runtime-host/dist/server/execution-model-composition.js +42 -14
  100. package/node_modules/@maka/runtime-host/dist/server/failure-diagnostic.js +24 -0
  101. package/node_modules/@maka/runtime-host/dist/server/goal-coordinator.js +7 -3
  102. package/node_modules/@maka/runtime-host/dist/server/host-kernel.js +3 -3
  103. package/node_modules/@maka/runtime-host/dist/server/host-session-availability.js +5 -2
  104. package/node_modules/@maka/runtime-host/dist/server/hosted-execution-recovery.js +3 -2
  105. package/node_modules/@maka/runtime-host/dist/server/hosted-execution-tool-profile.js +34 -4
  106. package/node_modules/@maka/runtime-host/dist/server/interactive-run-composer.js +51 -23
  107. package/node_modules/@maka/runtime-host/dist/server/operation-dispatcher.js +2 -7
  108. package/node_modules/@maka/runtime-host/dist/server/plugin-platform-coordinator.js +6 -0
  109. package/node_modules/@maka/runtime-host/dist/server/plugin-platform.js +6 -0
  110. package/node_modules/@maka/runtime-host/dist/server/root-turn-coordinator.js +110 -14
  111. package/node_modules/@maka/runtime-host/dist/server/runtime-resource-coordinator.js +11 -8
  112. package/node_modules/@maka/runtime-host/dist/server/scheduled-task-coordinator.js +31 -21
  113. package/node_modules/@maka/runtime-host/dist/server/serial-outbound-writer.js +1 -1
  114. package/node_modules/@maka/runtime-host/dist/server/session-catalog-coordinator.js +20 -8
  115. package/node_modules/@maka/runtime-host/dist/server/session-retirement-coordinator.js +5 -2
  116. package/node_modules/@maka/runtime-host/dist/server/session-revision-coordinator.js +4 -0
  117. package/node_modules/@maka/runtime-host/dist/server/session-transcript-reader.js +15 -104
  118. package/node_modules/@maka/runtime-host/dist/server/shared-session-transcript.js +3 -0
  119. package/node_modules/@maka/runtime-host/dist/server/workhub-coordination-action-gate.js +59 -82
  120. package/node_modules/@maka/runtime-host/dist/server/workhub-coordination-coordinator.js +132 -139
  121. package/node_modules/@maka/runtime-host/dist/server/workhub-message-attachments.js +46 -0
  122. package/node_modules/@maka/storage/dist/agent-run-store.js +14 -1
  123. package/node_modules/@maka/storage/dist/artifact-store.js +40 -3
  124. package/node_modules/@maka/storage/dist/atomic-file-write.js +74 -0
  125. package/node_modules/@maka/storage/dist/claude-code-session-adapter.js +0 -1
  126. package/node_modules/@maka/storage/dist/codex-session-adapter.js +284 -196
  127. package/node_modules/@maka/storage/dist/credential-store.js +12 -63
  128. package/node_modules/@maka/storage/dist/execution-stores.js +4 -1
  129. package/node_modules/@maka/storage/dist/git-exec.js +61 -0
  130. package/node_modules/@maka/storage/dist/git-worktree-child-executor.js +8 -35
  131. package/node_modules/@maka/storage/dist/goal-authority.js +2 -2
  132. package/node_modules/@maka/storage/dist/interaction-store.js +2 -2
  133. package/node_modules/@maka/storage/dist/mcp-config-store.js +10 -25
  134. package/node_modules/@maka/storage/dist/project-catalog.js +2 -23
  135. package/node_modules/@maka/storage/dist/runtime-event-invariants.js +2 -2
  136. package/node_modules/@maka/storage/dist/runtime-policy/connection-catalog-document.js +8 -3
  137. package/node_modules/@maka/storage/dist/scheduled-task-store.js +249 -283
  138. package/node_modules/@maka/storage/dist/session-bundle-manifest.js +5 -17
  139. package/node_modules/@maka/storage/dist/session-bundle-policy.js +2 -1
  140. package/node_modules/@maka/storage/dist/session-copy-cleanup.js +2 -1
  141. package/node_modules/@maka/storage/dist/session-store.js +14 -2
  142. package/node_modules/@maka/storage/dist/settings-store.js +8 -4
  143. package/node_modules/@maka/storage/dist/sqlite-artifact-metadata.js +11 -0
  144. package/node_modules/@maka/storage/dist/sqlite-runtime-store.js +1 -2
  145. package/node_modules/@maka/storage/dist/sqlite-session-metadata-schema.js +12 -1
  146. package/node_modules/@maka/storage/dist/sqlite-session-metadata-store.js +67 -3
  147. package/node_modules/@maka/storage/dist/stable-storage.js +14 -1
  148. package/node_modules/@maka/storage/dist/storage-id.js +28 -0
  149. package/node_modules/@maka/storage/dist/tool-result-archive-evidence.js +31 -4
  150. package/node_modules/@maka/storage/dist/workspace-identity.js +2 -15
  151. package/node_modules/@maka/storage/dist/workspace-version-authority-internal.js +1 -8
  152. package/package.json +1 -1
  153. package/node_modules/@maka/core/dist/workhub-creation-intent.js +0 -990
  154. package/node_modules/@maka/core/dist/workhub-session-resolver.js +0 -50
@@ -17,8 +17,8 @@
17
17
  * under the License.
18
18
  */
19
19
  import { randomUUID } from 'node:crypto';
20
- import { basename } from 'node:path';
21
20
  import { Key, ProcessTerminal, SelectList, TuiMainScreen, isKeyRelease, isKeyRepeat, matchesKey, } from '@earendil-works/pi-tui';
21
+ import { CurrentTodoStore, TodoOverlay, renderTodoIndicator } from './pi-tui-todo.js';
22
22
  import { isThinkingLevel } from '@maka/core/model-thinking';
23
23
  import { deriveConnectionSlug, } from '@maka/core/llm-connections';
24
24
  import { projectRevisionLinkedSessionTree } from '@maka/core/session-revisions';
@@ -49,7 +49,7 @@ import { CTX_REFRESH_DEBOUNCE_MS, createCtxRefresher, scheduleCtxRefreshTimeout,
49
49
  import { sessionStatusBadge } from './tui-session-status.js';
50
50
  import { AttentionController, DISABLE_FOCUS_REPORTING, ENABLE_FOCUS_REPORTING, FOCUS_IN_SEQUENCE, FOCUS_OUT_SEQUENCE, } from './tui-attention.js';
51
51
  import { MakaActivityStripComponent, MakaPendingQueueComponent, MakaPiLayoutComponent, MakaStatusLineComponent, MakaTranscriptComponent, } from './pi-tui-layout.js';
52
- import { MakaAutocompleteProvider, DirectoryPickerOverlay, ModelSearchOverlay, OnboardingWizard, PickerOverlay, UserQuestionOverlay, modelChoiceConnectionLabels, getTuiPickerCopy, modelPickerItems, onboardingFailureMessage, permissionModePickerItems, skillPickerItems, thinkingLevelPickerItems, } from './pi-tui-pickers.js';
52
+ import { MakaAutocompleteProvider, DirectoryPickerOverlay, ModelSearchOverlay, SessionSearchOverlay, OnboardingWizard, PickerOverlay, UserQuestionOverlay, modelChoiceConnectionLabels, getTuiPickerCopy, modelPickerItems, onboardingFailureMessage, permissionModePickerItems, skillPickerItems, thinkingLevelPickerItems, } from './pi-tui-pickers.js';
53
53
  import { formatMakaResumeCommand } from './cli-invocation.js';
54
54
  import { goalAttachedNoticeText, goalPausedNoticeText, goalStatusLabel, goalSummaryLines, isLiveGoalStatus, } from './pi-goal.js';
55
55
  import { getTuiPrimaryGuidance } from './tui-primary-guidance.js';
@@ -212,6 +212,20 @@ export async function runMakaPiTui(input) {
212
212
  // True while the /session picker is open mid-turn: Escape must close the
213
213
  // overlay, not arm the double-Escape interrupt for the running Turn (#3380).
214
214
  let sessionPickerOverlayOpen = false;
215
+ let transcriptOverlay;
216
+ let todoOverlay;
217
+ const closeTodoOverlay = () => {
218
+ todoOverlay?.hide();
219
+ todoOverlay = undefined;
220
+ };
221
+ let transcriptViewer;
222
+ let transcriptViewerSessionId;
223
+ const resetTranscriptViewer = () => {
224
+ transcriptOverlay?.hide();
225
+ transcriptOverlay = undefined;
226
+ transcriptViewer = undefined;
227
+ transcriptViewerSessionId = undefined;
228
+ };
215
229
  let lastTurnEscapeAt = 0;
216
230
  let lastIdleEscapeAt = 0;
217
231
  let lastIdleCtrlCAt = 0;
@@ -318,6 +332,34 @@ export async function runMakaPiTui(input) {
318
332
  const activityStrip = new MakaActivityStripComponent(metadata);
319
333
  const pendingQueue = new MakaPendingQueueComponent(state, locale);
320
334
  const statusLine = new MakaStatusLineComponent(metadata);
335
+ const currentTodo = new CurrentTodoStore({
336
+ read: async (sessionId) => {
337
+ if (!input.driver.queryTodo)
338
+ throw new Error('Todo query unavailable');
339
+ return input.driver.queryTodo(sessionId);
340
+ },
341
+ }, () => {
342
+ if (!closed)
343
+ tui.requestRender();
344
+ });
345
+ const syncTodoSession = () => {
346
+ currentTodo.setSession(input.driver.getSessionId() ?? undefined);
347
+ };
348
+ const unsubscribeTodoChanges = input.driver.subscribeTodoChanges?.((sessionId) => {
349
+ if (sessionId !== input.driver.getSessionId())
350
+ return;
351
+ syncTodoSession();
352
+ void currentTodo.refresh();
353
+ });
354
+ const todoIndicator = {
355
+ invalidate() { },
356
+ render(width) {
357
+ if (!input.driver.queryTodo)
358
+ return [];
359
+ const line = renderTodoIndicator(currentTodo.getState(), { locale, width });
360
+ return line === undefined ? [] : [line];
361
+ },
362
+ };
321
363
  // Use the vendor editor's full 20-item autocomplete capacity. Larger command
322
364
  // catalogs remain scrollable and keep an exact position/total counter.
323
365
  const editor = new MakaSkillHighlightEditor(tui, editorTheme(), {
@@ -326,7 +368,7 @@ export async function runMakaPiTui(input) {
326
368
  });
327
369
  let refreshEditorCwd;
328
370
  const editorSurface = new MakaAutocompleteAboveEditorComponent(editor);
329
- const layout = new MakaPiLayoutComponent(state, transcript, activityStrip, pendingQueue, editorSurface, statusLine, terminal);
371
+ const layout = new MakaPiLayoutComponent(state, transcript, activityStrip, pendingQueue, editorSurface, statusLine, terminal, todoIndicator);
330
372
  const attention = new AttentionController(terminal, {
331
373
  baseTitle: input.title,
332
374
  ...(input.attentionLongTurnThresholdMs !== undefined
@@ -608,6 +650,9 @@ export async function runMakaPiTui(input) {
608
650
  unsubscribeModelCatalogChanges?.();
609
651
  unsubscribeSessionTitleChanges();
610
652
  unsubscribeGoalChanges?.();
653
+ unsubscribeTodoChanges?.();
654
+ currentTodo.dispose();
655
+ closeTodoOverlay();
611
656
  void sideConversation?.stopParentObserver?.();
612
657
  unsubscribeStartedTurns();
613
658
  unsubscribeResolvedInteractions();
@@ -1209,6 +1254,7 @@ export async function runMakaPiTui(input) {
1209
1254
  });
1210
1255
  }
1211
1256
  const adoptSessionMetadata = (summary, announceIdentity = true) => {
1257
+ syncTodoSession();
1212
1258
  cwd = summary.cwd ?? cwd;
1213
1259
  setSessionTitle(summary.name);
1214
1260
  model = summary.model;
@@ -1297,6 +1343,8 @@ export async function runMakaPiTui(input) {
1297
1343
  // `messages`. Shared by switchSession and rewindToTurn so both land the same
1298
1344
  // runner state (model/connection/thinking/transcript/scroll).
1299
1345
  const applySwitchResult = async ({ summary, messages, activeTurn, }) => {
1346
+ resetTranscriptViewer();
1347
+ closeTodoOverlay();
1300
1348
  adoptSessionMetadata(summary, false);
1301
1349
  replaceTranscript(messages);
1302
1350
  syncInteractionOverlays();
@@ -1856,6 +1904,11 @@ export async function runMakaPiTui(input) {
1856
1904
  formOverlay = showBottomPicker(formOverlayComponent);
1857
1905
  };
1858
1906
  const syncInteractionOverlays = () => {
1907
+ if (state.pendingInteraction) {
1908
+ closeTodoOverlay();
1909
+ transcriptOverlay?.hide();
1910
+ transcriptOverlay = undefined;
1911
+ }
1859
1912
  syncUserQuestionOverlay();
1860
1913
  syncFormOverlay();
1861
1914
  };
@@ -2305,45 +2358,58 @@ export async function runMakaPiTui(input) {
2305
2358
  foreignByValue.set(`foreign:${summary.source}:${summary.id}`, summary);
2306
2359
  }
2307
2360
  }
2361
+ let overlay;
2362
+ let sessionSearch;
2308
2363
  const renderScope = () => {
2309
2364
  const visibleSessions = sessionListScope === 'current'
2310
2365
  ? projectedSessions.filter(({ session }) => session.cwd === cwd)
2311
2366
  : projectedSessions;
2312
- const items = visibleSessions.map(({ session, depth }) => {
2367
+ const choices = visibleSessions.map(({ session, depth }) => {
2313
2368
  const state = availability.get(session.id);
2314
2369
  const statusBadge = sessionStatusBadge(session, locale);
2315
2370
  const statusDetail = statusBadge ? ` · ${statusBadge}` : '';
2316
- const location = sessionListScope === 'all' && session.cwd ? ` ${basename(session.cwd)}` : '';
2371
+ const location = sessionListScope === 'all' && session.cwd ? ` ${session.cwd}` : '';
2317
2372
  const childDetail = session.subagentRuntime
2318
2373
  ? ` subagent:${session.subagentRuntime.profile}`
2319
2374
  : '';
2320
- return {
2375
+ const item = {
2321
2376
  value: session.id,
2322
2377
  label: `${depth > 0 ? `${' '.repeat(depth - 1)}↳ ` : ''}${session.name || session.id}`,
2323
2378
  description: state?.available === false
2324
2379
  ? `${shortSessionId(session.id)}${statusDetail} ${state.reason}`
2325
2380
  : `${shortSessionId(session.id)}${statusDetail}${location}${childDetail} ${session.llmConnectionSlug} ${session.model}`,
2326
2381
  };
2382
+ return {
2383
+ item,
2384
+ searchText: [
2385
+ session.name,
2386
+ session.id,
2387
+ session.cwd,
2388
+ session.model,
2389
+ session.llmConnectionSlug,
2390
+ ]
2391
+ .filter(Boolean)
2392
+ .join(' ')
2393
+ .toLocaleLowerCase(),
2394
+ };
2327
2395
  });
2328
2396
  // Foreign sessions are cwd-scoped; show them in both scope views (they
2329
2397
  // belong to this project) so a Tab toggle never makes them vanish.
2330
2398
  for (const [value, summary] of foreignByValue) {
2331
- items.push({
2332
- value,
2333
- label: summary.title,
2334
- description: `↩ resume from ${foreignSourceLabel(summary.source)}`,
2399
+ choices.push({
2400
+ item: {
2401
+ value,
2402
+ label: summary.title,
2403
+ description: `↩ resume from ${foreignSourceLabel(summary.source)}`,
2404
+ },
2405
+ searchText: `${summary.title} ${summary.id} ${summary.cwd} ${summary.source}`.toLocaleLowerCase(),
2335
2406
  });
2336
2407
  }
2337
- const list = new SelectList(items, 10, selectListTheme(), {
2338
- minPrimaryColumnWidth: 20,
2339
- maxPrimaryColumnWidth: Math.max(20, terminal.columns - 30),
2340
- });
2341
- let overlay;
2342
2408
  const closeOverlay = () => {
2343
2409
  sessionPickerOverlayOpen = false;
2344
2410
  overlay?.hide();
2345
2411
  };
2346
- list.onSelect = (item) => {
2412
+ const onSelect = (item) => {
2347
2413
  const foreign = foreignByValue.get(item.value);
2348
2414
  if (foreign) {
2349
2415
  closeOverlay();
@@ -2355,21 +2421,27 @@ export async function runMakaPiTui(input) {
2355
2421
  closeOverlay();
2356
2422
  void goToSession(item.value);
2357
2423
  };
2358
- list.onCancel = () => closeOverlay();
2359
- sessionPickerOverlayOpen = true;
2360
- overlay = showBottomPicker(new PickerOverlay(list, {
2361
- title: 'Resume Session',
2362
- rightLabel: sessionListScope === 'current' ? 'Current' : 'All',
2363
- hint: 'Tab scope · ↑↓ move · Enter select · Esc close',
2364
- onInput: (data) => {
2365
- if (!matchesKey(data, Key.tab) || isKeyRelease(data) || isKeyRepeat(data))
2366
- return false;
2424
+ const scopeLabel = sessionListScope === 'current'
2425
+ ? pickerCopy.sessionScopeCurrent
2426
+ : pickerCopy.sessionScopeAll;
2427
+ if (sessionSearch) {
2428
+ sessionSearch.updateChoices(choices, scopeLabel);
2429
+ sessionSearch.invalidate();
2430
+ return;
2431
+ }
2432
+ sessionSearch = new SessionSearchOverlay(tui, {
2433
+ locale,
2434
+ choices,
2435
+ scopeLabel,
2436
+ onSelect,
2437
+ onCancel: closeOverlay,
2438
+ onToggleScope: () => {
2367
2439
  sessionListScope = sessionListScope === 'current' ? 'all' : 'current';
2368
- overlay?.hide();
2369
2440
  renderScope();
2370
- return true;
2371
2441
  },
2372
- }));
2442
+ });
2443
+ sessionPickerOverlayOpen = true;
2444
+ overlay = showBottomPicker(sessionSearch);
2373
2445
  };
2374
2446
  renderScope();
2375
2447
  };
@@ -2427,6 +2499,9 @@ export async function runMakaPiTui(input) {
2427
2499
  requestRender();
2428
2500
  return false;
2429
2501
  }
2502
+ resetTranscriptViewer();
2503
+ closeTodoOverlay();
2504
+ syncTodoSession();
2430
2505
  // A fresh session is not bound by the previous one's boundary. Falling back
2431
2506
  // to the *current* label would keep the previous Session's mode, including
2432
2507
  // Auto while a changed Host default creates with full access; the launch
@@ -2508,20 +2583,42 @@ export async function runMakaPiTui(input) {
2508
2583
  requestRender();
2509
2584
  };
2510
2585
  const showTranscriptViewer = () => {
2511
- let overlay;
2512
- const renderTranscript = transcript.createDocumentRenderer();
2513
- const viewer = new TranscriptViewerOverlay({
2514
- renderTranscript,
2515
- viewportRows: () => terminal.rows,
2516
- onClose: () => overlay?.hide(),
2517
- onChange: () => tui.requestRender(),
2518
- });
2519
- overlay = tui.showOverlay(viewer, {
2586
+ if (state.pendingInteraction)
2587
+ return;
2588
+ const sessionId = input.driver.getSessionId() ?? undefined;
2589
+ if (!transcriptViewer || transcriptViewerSessionId !== sessionId) {
2590
+ transcriptViewerSessionId = sessionId;
2591
+ transcriptViewer = new TranscriptViewerOverlay({
2592
+ renderTranscript: transcript.createDocumentRenderer(),
2593
+ locale,
2594
+ viewportRows: () => terminal.rows,
2595
+ onClose: () => {
2596
+ transcriptOverlay?.hide();
2597
+ transcriptOverlay = undefined;
2598
+ },
2599
+ onChange: () => tui.requestRender(),
2600
+ });
2601
+ }
2602
+ transcriptOverlay = tui.showOverlay(transcriptViewer, {
2520
2603
  anchor: 'top-left',
2521
2604
  width: '100%',
2522
2605
  maxHeight: '100%',
2523
2606
  });
2524
2607
  };
2608
+ const showTodo = () => {
2609
+ if (state.pendingInteraction)
2610
+ return;
2611
+ syncTodoSession();
2612
+ void currentTodo.refresh();
2613
+ closeTodoOverlay();
2614
+ todoOverlay = tui.showOverlay(new TodoOverlay({
2615
+ locale,
2616
+ getState: () => input.driver.queryTodo ? currentTodo.getState() : { status: 'error', items: [] },
2617
+ viewportRows: () => terminal.rows,
2618
+ onClose: closeTodoOverlay,
2619
+ onChange: () => tui.requestRender(),
2620
+ }), { anchor: 'top-left', width: '100%', maxHeight: '100%' });
2621
+ };
2525
2622
  const showMcpStatus = () => {
2526
2623
  let overlay;
2527
2624
  const viewer = new McpManagementOverlay({
@@ -3311,6 +3408,22 @@ export async function runMakaPiTui(input) {
3311
3408
  showTranscriptViewer();
3312
3409
  },
3313
3410
  },
3411
+ todo: {
3412
+ description: primaryGuidance.commands.todo,
3413
+ midTurn: 'local',
3414
+ run: (parts) => {
3415
+ if (parts.length !== 1) {
3416
+ state.entries.push({
3417
+ kind: 'notice',
3418
+ level: 'error',
3419
+ text: TUI_COPY_RESOURCES.todo[locale].usage,
3420
+ });
3421
+ requestRender();
3422
+ return;
3423
+ }
3424
+ showTodo();
3425
+ },
3426
+ },
3314
3427
  permissions: {
3315
3428
  description: primaryGuidance.commands.permissions,
3316
3429
  midTurn: 'refuse',
@@ -3605,9 +3718,8 @@ export async function runMakaPiTui(input) {
3605
3718
  if (!matchesKey(data, Key.escape))
3606
3719
  lastIdleEscapeAt = 0;
3607
3720
  if (matchesKey(data, Key.ctrl('o')) && !isKeyRepeat(data)) {
3608
- if (handleExpansionToggleKey('tool')) {
3609
- return { consume: true };
3610
- }
3721
+ showTranscriptViewer();
3722
+ return { consume: true };
3611
3723
  }
3612
3724
  if (matchesKey(data, Key.ctrl('t')) && !isKeyRepeat(data)) {
3613
3725
  if (handleExpansionToggleKey('thinking')) {
@@ -3718,11 +3830,11 @@ export async function runMakaPiTui(input) {
3718
3830
  // re-wrapping) a full clear would wipe the scrollback the user scrolls through.
3719
3831
  // Differential rendering clears the vacated rows without the wipe.
3720
3832
  //
3721
- // The Ctrl+O / Ctrl+T toggles are viewport-anchored for the same reason: an
3833
+ // The live Ctrl+T thinking toggle is viewport-anchored for the same reason: an
3722
3834
  // entry above the live viewport lives in terminal scrollback, which cannot
3723
3835
  // be rewritten, so resizing it would push pi-tui's differential renderer
3724
3836
  // into a scrollback-clearing full redraw (its `firstChanged < viewportTop`
3725
- // path). The toggles therefore retarget only entries inside the viewport;
3837
+ // path). It therefore retargets only entries inside the viewport;
3726
3838
  // see entryInLiveViewport in pi-transcript.ts (#1097). A block whose own
3727
3839
  // expansion pushed its head above the viewport can consequently not be
3728
3840
  // collapsed by the next press (#1134): the toggle still flips the default
@@ -3731,6 +3843,7 @@ export async function runMakaPiTui(input) {
3731
3843
  // again within EXPANSION_COLLAPSE_CONFIRM_WINDOW_MS applies the collapsed
3732
3844
  // default to those blocks too and pays one scrollback-clearing full redraw
3733
3845
  // (requestRender(true)), re-anchoring the viewport at the tail.
3846
+ // Ctrl+O instead opens a detached reader whose details never resize live rows.
3734
3847
  tui.setClearOnShrink(false);
3735
3848
  tui.addChild(layout);
3736
3849
  tui.setFocus(editorSurface);
@@ -3772,6 +3885,7 @@ export async function runMakaPiTui(input) {
3772
3885
  }
3773
3886
  });
3774
3887
  }
3888
+ syncTodoSession();
3775
3889
  return closedPromise;
3776
3890
  }
3777
3891
  const BOTTOM_PICKER_MARGIN_ROWS = 4;
@@ -0,0 +1,221 @@
1
+ /*
2
+ * Licensed to the Apache Software Foundation (ASF) under one
3
+ * or more contributor license agreements. See the NOTICE file
4
+ * distributed with this work for additional information
5
+ * regarding copyright ownership. The ASF licenses this file
6
+ * to you under the Apache License, Version 2.0 (the
7
+ * "License"); you may not use this file except in compliance
8
+ * with the License. You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing,
13
+ * software distributed under the License is distributed on an
14
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
+ * KIND, either express or implied. See the License for the
16
+ * specific language governing permissions and limitations
17
+ * under the License.
18
+ */
19
+ import { Key, isKeyRelease, isKeyRepeat, matchesKey, truncateToWidth, visibleWidth, wrapTextWithAnsi, } from '@earendil-works/pi-tui';
20
+ import { projectSessionTodoItemsForDisplay, } from '@maka/core/session-todo';
21
+ import { ansi } from './tui-ansi.js';
22
+ import { TUI_COPY_RESOURCES } from './tui-copy-catalog.js';
23
+ /** Owns only the current session; late queries cannot cross session changes or disposal. */
24
+ export class CurrentTodoStore {
25
+ reader;
26
+ onChange;
27
+ snapshot = { status: 'idle', items: [] };
28
+ generation = 0;
29
+ disposed = false;
30
+ pending;
31
+ refreshAgain = false;
32
+ constructor(reader, onChange = () => undefined) {
33
+ this.reader = reader;
34
+ this.onChange = onChange;
35
+ }
36
+ getState() {
37
+ return this.snapshot;
38
+ }
39
+ setSession(sessionId) {
40
+ if (this.disposed || this.snapshot.sessionId === sessionId)
41
+ return;
42
+ this.generation++;
43
+ this.pending = undefined;
44
+ this.refreshAgain = false;
45
+ this.publish({ status: 'idle', ...(sessionId ? { sessionId } : {}), items: [] });
46
+ if (sessionId)
47
+ void this.refresh();
48
+ }
49
+ refresh() {
50
+ const { sessionId } = this.snapshot;
51
+ if (this.disposed || !sessionId)
52
+ return Promise.resolve(false);
53
+ if (this.pending) {
54
+ this.refreshAgain = true;
55
+ return this.pending;
56
+ }
57
+ const pending = this.load(sessionId, this.generation).finally(() => {
58
+ if (this.pending !== pending)
59
+ return;
60
+ this.pending = undefined;
61
+ if (this.refreshAgain) {
62
+ this.refreshAgain = false;
63
+ void this.refresh();
64
+ }
65
+ });
66
+ this.pending = pending;
67
+ return pending;
68
+ }
69
+ dispose() {
70
+ this.disposed = true;
71
+ this.generation++;
72
+ this.pending = undefined;
73
+ this.refreshAgain = false;
74
+ this.snapshot = { status: 'idle', items: [] };
75
+ }
76
+ async load(sessionId, generation) {
77
+ // Keep current data visible during background refreshes, without a second cache.
78
+ if (this.snapshot.status !== 'ready' && this.snapshot.status !== 'empty') {
79
+ this.publish({ status: 'loading', sessionId, items: [] });
80
+ }
81
+ try {
82
+ const result = await this.reader.read(sessionId);
83
+ if (generation !== this.generation)
84
+ return false;
85
+ const items = projectSessionTodoItemsForDisplay(result.items);
86
+ this.publish({ status: items.length ? 'ready' : 'empty', sessionId, items });
87
+ }
88
+ catch {
89
+ if (generation !== this.generation)
90
+ return false;
91
+ this.publish({ status: 'error', sessionId, items: [] });
92
+ }
93
+ return true;
94
+ }
95
+ publish(snapshot) {
96
+ this.snapshot = snapshot;
97
+ this.onChange();
98
+ }
99
+ }
100
+ /** Render the one-line current Todo indicator. Empty and initial states stay hidden. */
101
+ export function renderTodoIndicator(snapshot, input) {
102
+ const copy = TUI_COPY_RESOURCES.todo[input.locale];
103
+ if (snapshot.status === 'idle' || snapshot.status === 'loading' || snapshot.status === 'empty') {
104
+ return undefined;
105
+ }
106
+ if (snapshot.status === 'error')
107
+ return fitLine(`${copy.unavailable} · ${copy.open}`, input.width);
108
+ const current = snapshot.items.find((item) => item.status === 'in_progress');
109
+ const completed = snapshot.items.filter((item) => item.status === 'completed').length;
110
+ const suffix = `${copy.progress} ${completed}/${snapshot.items.length} · ${copy.open}`;
111
+ const prefix = current ? `${ansi.accent('◐')} ` : '';
112
+ const available = input.width - visibleWidth(prefix) - visibleWidth(suffix) - (current ? 3 : 1);
113
+ if (!current)
114
+ return fitLine(suffix, input.width);
115
+ if (available < 1)
116
+ return fitLine(suffix, input.width);
117
+ const content = truncateToWidth(current.content, Math.max(1, available), '…');
118
+ return fitLine(`${prefix}${content} · ${suffix}`, input.width);
119
+ }
120
+ /** Read-only, scrollable view of the current Todo projection. */
121
+ export class TodoOverlay {
122
+ input;
123
+ top = 0;
124
+ bodyRows = 1;
125
+ width = 80;
126
+ constructor(input) {
127
+ this.input = input;
128
+ }
129
+ invalidate() { }
130
+ handleInput(data) {
131
+ if (isKeyRelease(data))
132
+ return;
133
+ if (matchesKey(data, Key.escape) || matchesKey(data, Key.ctrl('c'))) {
134
+ if (!isKeyRepeat(data))
135
+ this.input.onClose();
136
+ return;
137
+ }
138
+ if (matchesKey(data, Key.up))
139
+ this.scrollBy(-1);
140
+ else if (matchesKey(data, Key.down))
141
+ this.scrollBy(1);
142
+ else if (matchesKey(data, Key.pageUp))
143
+ this.scrollBy(-this.bodyRows);
144
+ else if (matchesKey(data, Key.pageDown))
145
+ this.scrollBy(this.bodyRows);
146
+ else if (matchesKey(data, Key.home)) {
147
+ this.scrollTo(0);
148
+ this.input.onChange?.();
149
+ }
150
+ else if (matchesKey(data, Key.end)) {
151
+ this.scrollTo(Number.MAX_SAFE_INTEGER);
152
+ this.input.onChange?.();
153
+ }
154
+ }
155
+ render(width) {
156
+ const safeWidth = Math.max(1, width);
157
+ this.width = safeWidth;
158
+ const copy = TUI_COPY_RESOURCES.todo[this.input.locale];
159
+ const viewport = Math.max(1, Math.floor(this.input.viewportRows()));
160
+ const showFooter = viewport > 2;
161
+ this.bodyRows = Math.max(0, viewport - (showFooter ? 2 : 1));
162
+ const snapshot = this.input.getState();
163
+ const rows = this.rows(snapshot, safeWidth);
164
+ const maxTop = Math.max(0, rows.length - this.bodyRows);
165
+ this.top = Math.min(this.top, maxTop);
166
+ const visible = rows.slice(this.top, this.top + this.bodyRows);
167
+ const completed = snapshot.items.filter((item) => item.status === 'completed').length;
168
+ const summary = snapshot.status === 'ready'
169
+ ? ` · ${completed}/${snapshot.items.length} ${copy.markedComplete}`
170
+ : '';
171
+ const position = rows.length > this.bodyRows && this.bodyRows > 0
172
+ ? ` · ${this.top + 1}-${this.top + visible.length}/${rows.length}`
173
+ : '';
174
+ const header = padLine(`${ansi.bold(copy.title)}${summary}${ansi.dim(position)}`, safeWidth);
175
+ const body = [
176
+ ...visible,
177
+ ...Array.from({ length: this.bodyRows - visible.length }, () => ' '.repeat(safeWidth)),
178
+ ];
179
+ if (!showFooter)
180
+ return [header, ...body];
181
+ const footer = padLine(ansi.dim(copy.hint), safeWidth);
182
+ return [header, ...body, footer];
183
+ }
184
+ rows(snapshot, width) {
185
+ const copy = TUI_COPY_RESOURCES.todo[this.input.locale];
186
+ if (snapshot.status === 'loading')
187
+ return [padLine(copy.loading, width)];
188
+ if (snapshot.status === 'error')
189
+ return [padLine(copy.unavailable, width)];
190
+ if (snapshot.status === 'idle' || snapshot.status === 'empty')
191
+ return [padLine(copy.empty, width)];
192
+ return snapshot.items.flatMap((item) => {
193
+ const symbol = item.status === 'completed'
194
+ ? ansi.green('✓')
195
+ : item.status === 'in_progress'
196
+ ? ansi.accent('●')
197
+ : ansi.muted('○');
198
+ return wrapTodoItem(`${symbol} `, item.content, width);
199
+ });
200
+ }
201
+ scrollBy(delta) {
202
+ this.scrollTo(this.top + delta);
203
+ this.input.onChange?.();
204
+ }
205
+ scrollTo(top) {
206
+ const rows = this.rows(this.input.getState(), this.width);
207
+ this.top = Math.max(0, Math.min(top, Math.max(0, rows.length - this.bodyRows)));
208
+ }
209
+ }
210
+ function wrapTodoItem(prefix, content, width) {
211
+ const lines = wrapTextWithAnsi(content, Math.max(1, width - 2));
212
+ return (lines.length ? lines : ['']).map((line, index) => padLine(`${index === 0 ? prefix : ' '}${line}`, width));
213
+ }
214
+ function fitLine(text, width) {
215
+ return truncateToWidth(text, Math.max(1, width), '…');
216
+ }
217
+ function padLine(text, width) {
218
+ const safeWidth = Math.max(1, width);
219
+ const trimmed = fitLine(text, safeWidth);
220
+ return `${trimmed}${' '.repeat(Math.max(0, safeWidth - visibleWidth(trimmed)))}`;
221
+ }