maka-agent 0.2.0-dev.46.20260922 → 0.2.0-dev.48.20260923

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 (131) hide show
  1. package/DISCLAIMER-WIP +6 -1
  2. package/README.md +6 -1
  3. package/dist/acp/maka-acp-agent.js +1 -1
  4. package/dist/acp/session-mcp.js +212 -0
  5. package/dist/acp/session-registry.js +52 -8
  6. package/dist/acp/stdio-server.js +3 -0
  7. package/dist/cli-core.js +1 -1
  8. package/dist/mcp-capability-provider.js +6 -1
  9. package/dist/mcp-capability-publication.js +139 -0
  10. package/dist/pi-tui-layout.js +46 -17
  11. package/dist/pi-tui-pickers.js +25 -14
  12. package/dist/pi-tui-runner.js +24 -15
  13. package/dist/runtime-host-capability-provider-command.js +25 -6
  14. package/dist/runtime-host-service-manager.js +26 -2
  15. package/dist/tui-context-refresh.js +15 -26
  16. package/dist/tui-mcp-control.js +66 -112
  17. package/native/runtime-host-windows-task-launcher/prebuilds/win32-x64/maka-runtime-host-task-launcher.exe +0 -0
  18. package/node_modules/@maka/acp-executor-plugin/dist/acp-errors.js +26 -0
  19. package/node_modules/@maka/acp-executor-plugin/dist/acp-filesystem.js +103 -0
  20. package/node_modules/@maka/acp-executor-plugin/dist/acp-process.js +119 -0
  21. package/node_modules/@maka/acp-executor-plugin/dist/acp-projection.js +146 -0
  22. package/node_modules/@maka/acp-executor-plugin/dist/index.js +653 -0
  23. package/node_modules/@maka/acp-executor-plugin/dist/plugin.mjs +24446 -0
  24. package/node_modules/@maka/acp-executor-plugin/package.json +15 -0
  25. package/node_modules/@maka/antigravity-acp-plugin/dist/index.js +88 -0
  26. package/node_modules/@maka/antigravity-acp-plugin/dist/model-presentation.js +53 -0
  27. package/node_modules/@maka/antigravity-acp-plugin/dist/plugin.mjs +115 -0
  28. package/node_modules/@maka/antigravity-acp-plugin/package.json +15 -0
  29. package/node_modules/@maka/core/dist/client-capability-grant.js +2 -2
  30. package/node_modules/@maka/core/dist/executor-catalog.js +106 -0
  31. package/node_modules/@maka/core/dist/mcp.js +4 -1
  32. package/node_modules/@maka/core/dist/model-metadata.js +28 -1
  33. package/node_modules/@maka/core/dist/model-thinking.js +36 -1
  34. package/node_modules/@maka/core/dist/provider-registry.js +0 -1
  35. package/node_modules/@maka/core/dist/refresh-read-coordinator.js +62 -0
  36. package/node_modules/@maka/core/dist/runtime-event-store.js +25 -0
  37. package/node_modules/@maka/core/dist/runtime-invocation.js +5 -0
  38. package/node_modules/@maka/core/dist/runtime-policy/connection-catalog-codec.js +4 -0
  39. package/node_modules/@maka/core/dist/runtime-policy/credential-vault-codec.js +6 -0
  40. package/node_modules/@maka/core/dist/runtime-policy/policy-codec.js +21 -0
  41. package/node_modules/@maka/core/dist/session.js +19 -1
  42. package/node_modules/@maka/core/dist/settings.js +3 -0
  43. package/node_modules/@maka/core/dist/tool-ledger-scanner.js +365 -104
  44. package/node_modules/@maka/core/dist/turn-origin.js +14 -0
  45. package/node_modules/@maka/core/package.json +3 -0
  46. package/node_modules/@maka/mcp/dist/credential-coordinator.js +6 -1
  47. package/node_modules/@maka/mcp/dist/index.js +31 -5
  48. package/node_modules/@maka/mcp/dist/oauth.js +54 -6
  49. package/node_modules/@maka/runtime/dist/agent-run-inspect.js +1 -1
  50. package/node_modules/@maka/runtime/dist/ai-sdk-message-projection.js +15 -60
  51. package/node_modules/@maka/runtime/dist/ai-sdk-turn.js +18 -0
  52. package/node_modules/@maka/runtime/dist/apply-patch-profile.js +64 -14
  53. package/node_modules/@maka/runtime/dist/builtin-tools.js +3 -2
  54. package/node_modules/@maka/runtime/dist/codex-v4a-patch.js +18 -0
  55. package/node_modules/@maka/runtime/dist/conversation-copy.js +56 -1
  56. package/node_modules/@maka/runtime/dist/mcp-tools.js +9 -2
  57. package/node_modules/@maka/runtime/dist/model-adapter.js +80 -3
  58. package/node_modules/@maka/runtime/dist/model-runtime.js +6 -0
  59. package/node_modules/@maka/runtime/dist/openai-apply-patch.js +5 -0
  60. package/node_modules/@maka/runtime/dist/plugin-executor-backend.js +122 -3
  61. package/node_modules/@maka/runtime/dist/plugin-executor-service.js +212 -16
  62. package/node_modules/@maka/runtime/dist/runtime-ledger-repair.js +2 -2
  63. package/node_modules/@maka/runtime/dist/session-event-runtime-mapper.js +3 -0
  64. package/node_modules/@maka/runtime/dist/session-manager.js +184 -126
  65. package/node_modules/@maka/runtime/dist/shell-run-manager.js +3 -0
  66. package/node_modules/@maka/runtime/dist/stream-graph-coordinator.js +29 -4
  67. package/node_modules/@maka/runtime-host/dist/candidate-entry.js +8 -5
  68. package/node_modules/@maka/runtime-host/dist/client/capability-provider-service.js +2 -0
  69. package/node_modules/@maka/runtime-host/dist/client/client-capability-channel.js +59 -20
  70. package/node_modules/@maka/runtime-host/dist/client/connection.js +6 -4
  71. package/node_modules/@maka/runtime-host/dist/client/index.js +1 -1
  72. package/node_modules/@maka/runtime-host/dist/client/reconnect-lifecycle.js +8 -1
  73. package/node_modules/@maka/runtime-host/dist/client/reconnecting-connection.js +4 -4
  74. package/node_modules/@maka/runtime-host/dist/client/session-catalog-summary.js +6 -1
  75. package/node_modules/@maka/runtime-host/dist/composition-identity.js +19 -0
  76. package/node_modules/@maka/runtime-host/dist/protocol/client-capability.js +20 -3
  77. package/node_modules/@maka/runtime-host/dist/protocol/external-session.js +8 -5
  78. package/node_modules/@maka/runtime-host/dist/protocol/index.js +20 -2
  79. package/node_modules/@maka/runtime-host/dist/protocol/plugin-platform.js +34 -0
  80. package/node_modules/@maka/runtime-host/dist/protocol/session-catalog.js +75 -5
  81. package/node_modules/@maka/runtime-host/dist/protocol/workhub-coordination.js +1 -5
  82. package/node_modules/@maka/runtime-host/dist/server/builtin-external-agent-plugins.js +188 -0
  83. package/node_modules/@maka/runtime-host/dist/server/candidate.js +12 -7
  84. package/node_modules/@maka/runtime-host/dist/server/client-capability-coordinator.js +234 -70
  85. package/node_modules/@maka/runtime-host/dist/server/client-capability-invocation-broker.js +14 -8
  86. package/node_modules/@maka/runtime-host/dist/server/client-capability-recovery.js +44 -46
  87. package/node_modules/@maka/runtime-host/dist/server/context-coordinator.js +2 -0
  88. package/node_modules/@maka/runtime-host/dist/server/execution-composition-factory.js +1 -1
  89. package/node_modules/@maka/runtime-host/dist/server/execution-composition.js +188 -21
  90. package/node_modules/@maka/runtime-host/dist/server/execution-model-authority.js +6 -0
  91. package/node_modules/@maka/runtime-host/dist/server/extension-bundle.js +4 -0
  92. package/node_modules/@maka/runtime-host/dist/server/external-session-coordinator.js +29 -7
  93. package/node_modules/@maka/runtime-host/dist/server/host-composition-source.js +47 -0
  94. package/node_modules/@maka/runtime-host/dist/server/host-composition.js +1 -29
  95. package/node_modules/@maka/runtime-host/dist/server/host-kernel.js +2 -9
  96. package/node_modules/@maka/runtime-host/dist/server/hosted-execution-recovery.js +75 -9
  97. package/node_modules/@maka/runtime-host/dist/server/hosted-execution-tool-profile.js +5 -1
  98. package/node_modules/@maka/runtime-host/dist/server/interaction-coordinator.js +45 -0
  99. package/node_modules/@maka/runtime-host/dist/server/jev-routing-model.js +157 -0
  100. package/node_modules/@maka/runtime-host/dist/server/listener-set.js +2 -2
  101. package/node_modules/@maka/runtime-host/dist/server/message-coordinator.js +4 -0
  102. package/node_modules/@maka/runtime-host/dist/server/plugin-platform-coordinator.js +11 -1
  103. package/node_modules/@maka/runtime-host/dist/server/process-lifecycle.js +1 -1
  104. package/node_modules/@maka/runtime-host/dist/server/process-termination-error.js +27 -0
  105. package/node_modules/@maka/runtime-host/dist/server/root-turn-coordinator.js +91 -12
  106. package/node_modules/@maka/runtime-host/dist/server/runtime-policy-coordinator.js +2 -0
  107. package/node_modules/@maka/runtime-host/dist/server/session-bundle-coordinator.js +4 -1
  108. package/node_modules/@maka/runtime-host/dist/server/session-catalog-coordinator.js +128 -13
  109. package/node_modules/@maka/runtime-host/dist/server/session-effect-coordinator.js +12 -6
  110. package/node_modules/@maka/runtime-host/dist/server/session-retirement-coordinator.js +2 -2
  111. package/node_modules/@maka/runtime-host/dist/server/session-transcript-reader.js +33 -0
  112. package/node_modules/@maka/runtime-host/dist/server/workhub-coordination-coordinator.js +4 -0
  113. package/node_modules/@maka/runtime-host/dist/server/workhub-result-coordinator.js +173 -0
  114. package/node_modules/@maka/runtime-host/dist/server/workhub-result-runtime.js +293 -0
  115. package/node_modules/@maka/runtime-host/package.json +2 -0
  116. package/node_modules/@maka/storage/dist/agent-run-store-contract.js +8 -0
  117. package/node_modules/@maka/storage/dist/execution-stores.js +15 -3
  118. package/node_modules/@maka/storage/dist/mcp-config-store.js +91 -2
  119. package/node_modules/@maka/storage/dist/operational-state-store.js +15 -1
  120. package/node_modules/@maka/storage/dist/plan-authority.js +2 -0
  121. package/node_modules/@maka/storage/dist/plan-store.js +30 -0
  122. package/node_modules/@maka/storage/dist/runtime-event-persistence.js +1 -0
  123. package/node_modules/@maka/storage/dist/runtime-policy/credential-vault-document.js +4 -1
  124. package/node_modules/@maka/storage/dist/runtime-policy/policy-document.js +2 -0
  125. package/node_modules/@maka/storage/dist/session-store-values.js +16 -4
  126. package/node_modules/@maka/storage/dist/shell-run-authority.js +1 -0
  127. package/node_modules/@maka/storage/dist/shell-run-store.js +15 -0
  128. package/node_modules/@maka/storage/dist/sqlite-runtime-schema.js +74 -1
  129. package/node_modules/@maka/storage/dist/sqlite-runtime-store.js +724 -84
  130. package/node_modules/@maka/storage/dist/sqlite-session-metadata-store.js +16 -0
  131. package/package.json +1 -1
@@ -150,6 +150,13 @@ export class MakaPiLayoutComponent extends Container {
150
150
  previousLines;
151
151
  previousRows;
152
152
  previousWidth;
153
+ /**
154
+ * An in-turn question is live chrome, not a modal picker: reserving its rows
155
+ * keeps the transcript tail above it instead of compositing the prompt over
156
+ * model output. The runner owns the interaction lifecycle; this component
157
+ * owns the resulting terminal geometry.
158
+ */
159
+ blockingInteraction;
153
160
  constructor(state, transcript, activityStrip, pendingQueue, editor, statusLine, terminal, todoIndicator) {
154
161
  super();
155
162
  this.state = state;
@@ -168,45 +175,66 @@ export class MakaPiLayoutComponent extends Container {
168
175
  this.addChild(editor);
169
176
  this.addChild(statusLine);
170
177
  }
178
+ setBlockingInteraction(interaction) {
179
+ if (this.blockingInteraction === interaction)
180
+ return;
181
+ if (this.blockingInteraction)
182
+ this.removeChild(this.blockingInteraction);
183
+ this.blockingInteraction = interaction;
184
+ if (interaction)
185
+ this.addChild(interaction);
186
+ }
171
187
  render(width) {
172
188
  const transcriptLines = this.transcript.render(width);
173
189
  const activityLines = this.activityStrip.render(width);
174
190
  const allPendingLines = this.pendingQueue.render(width);
175
191
  const statusLines = this.statusLine.render(width);
176
- // Supplementary information must not steal the composer's minimum space.
192
+ const blockingInteraction = this.blockingInteraction;
193
+ // #1064: separate an active strip from the last transcript line. Reserve
194
+ // both that gap and a content row before budgeting a blocking question.
195
+ const activityActive = activityLines.length > 0 && activityLines.some((line) => line.length > 0);
196
+ const lastTranscriptLine = transcriptLines[transcriptLines.length - 1];
197
+ const needGap = activityActive && lastTranscriptLine !== undefined && lastTranscriptLine.length > 0;
198
+ const paddedTranscript = needGap ? [...transcriptLines, ''] : transcriptLines;
199
+ const transcriptRows = blockingInteraction
200
+ ? Math.min(paddedTranscript.length, needGap ? 2 : 1)
201
+ : 0;
202
+ const interactionMargin = blockingInteraction ? 1 : 0;
203
+ const input = blockingInteraction ?? this.editor;
204
+ const minimumInputRows = input.minimumViewportRows();
205
+ // Supplementary information yields to the active input and transcript.
177
206
  const todoLines = this.terminal.rows >
178
207
  activityLines.length +
179
208
  allPendingLines.length +
180
209
  statusLines.length +
181
- this.editor.minimumViewportRows()
210
+ transcriptRows +
211
+ interactionMargin +
212
+ minimumInputRows
182
213
  ? (this.todoIndicator?.render(width) ?? []).slice(0, 1)
183
214
  : [];
184
- const pendingRowsAvailable = this.editor.isShowingAutocomplete()
215
+ const pendingRowsAvailable = blockingInteraction || this.editor.isShowingAutocomplete()
185
216
  ? Math.max(0, this.terminal.rows -
186
217
  activityLines.length -
187
218
  statusLines.length -
188
219
  todoLines.length -
189
- this.editor.minimumViewportRows())
220
+ transcriptRows -
221
+ interactionMargin -
222
+ minimumInputRows)
190
223
  : allPendingLines.length;
191
224
  const pendingLines = fitPendingQueueLines(allPendingLines, pendingRowsAvailable);
192
- this.editor.setViewportRows(this.terminal.rows -
225
+ input.setViewportRows(this.terminal.rows -
193
226
  activityLines.length -
194
227
  pendingLines.length -
195
228
  statusLines.length -
196
- todoLines.length);
197
- const editorLines = this.editor.render(width);
198
- // #1064: when the activity strip is showing (a turn is running), separate
199
- // it from the last transcript line with a blank row. Without this, a
200
- // thinking or tool row (the agent-work stack, which has no internal blank
201
- // gaps) sits directly against `Working… 12s`.
202
- const activityActive = activityLines.length > 0 && activityLines.some((line) => line.length > 0);
203
- const lastTranscriptLine = transcriptLines[transcriptLines.length - 1];
204
- const needGap = activityActive && lastTranscriptLine !== undefined && lastTranscriptLine.length > 0;
205
- const paddedTranscript = needGap ? [...transcriptLines, ''] : transcriptLines;
229
+ todoLines.length -
230
+ transcriptRows -
231
+ interactionMargin);
232
+ const inputLines = input.render(width);
206
233
  const chromeRows = activityLines.length +
207
234
  pendingLines.length +
208
235
  todoLines.length +
209
- editorLines.length +
236
+ inputLines.length +
237
+ interactionMargin +
210
238
  statusLines.length;
211
239
  const viewportRows = Math.max(0, this.terminal.rows - chromeRows);
212
240
  const paddingRows = Math.max(0, viewportRows - paddedTranscript.length);
@@ -216,7 +244,8 @@ export class MakaPiLayoutComponent extends Container {
216
244
  ...activityLines,
217
245
  ...pendingLines,
218
246
  ...todoLines,
219
- ...editorLines,
247
+ ...inputLines,
248
+ ...(blockingInteraction ? [''] : []),
220
249
  ...statusLines,
221
250
  ];
222
251
  // #1097: record where pi-tui's live viewport starts for this render, in
@@ -16,7 +16,7 @@
16
16
  * specific language governing permissions and limitations
17
17
  * under the License.
18
18
  */
19
- import { CombinedAutocompleteProvider, Editor, Key, SelectList, decodeKittyPrintable, isKeyRelease, isKeyRepeat, matchesKey, truncateToWidth, visibleWidth, wrapTextWithAnsi, } from '@earendil-works/pi-tui';
19
+ import { CombinedAutocompleteProvider, CURSOR_MARKER, Editor, Key, SelectList, decodeKittyPrintable, isKeyRelease, isKeyRepeat, matchesKey, truncateToWidth, visibleWidth, wrapTextWithAnsi, } from '@earendil-works/pi-tui';
20
20
  import { defineUiMessageCatalog, formatUiMessage, resolveUiMessageCatalog, } from '@maka/core/ui-locale';
21
21
  import { providerDefaultsOf, validateSlug, } from '@maka/core/llm-connections';
22
22
  import { CONNECTION_NAME_MAX_LENGTH } from '@maka/core/runtime-policy';
@@ -333,6 +333,7 @@ const USER_QUESTION_ROW_PREFIX_WIDTH = 2;
333
333
  export class UserQuestionOverlay {
334
334
  input;
335
335
  editor;
336
+ viewportRows = Number.POSITIVE_INFINITY;
336
337
  // Highlight index over [0, options.length]. `options.length` is the input row.
337
338
  activeIndex = 0;
338
339
  constructor(tui, input) {
@@ -352,6 +353,13 @@ export class UserQuestionOverlay {
352
353
  get inputRowIndex() {
353
354
  return this.input.options.length;
354
355
  }
356
+ minimumViewportRows() {
357
+ // One title, every option, the answer field, and the closing divider.
358
+ return this.input.options.length + 3;
359
+ }
360
+ setViewportRows(rows) {
361
+ this.viewportRows = Math.max(this.minimumViewportRows(), Math.floor(rows));
362
+ }
355
363
  get onInputRow() {
356
364
  return this.activeIndex === this.inputRowIndex;
357
365
  }
@@ -411,30 +419,33 @@ export class UserQuestionOverlay {
411
419
  const hint = padLine(ansi.dim(this.input.hint), safeWidth);
412
420
  const blank = padLine('', safeWidth);
413
421
  const divider = padLine(ansi.accent('-'.repeat(safeWidth)), safeWidth);
414
- const inputRows = this.renderInputRow(safeWidth);
422
+ const allInputRows = this.renderInputRow(safeWidth);
423
+ const inputBudget = Math.max(1, this.viewportRows - this.input.options.length - 2);
424
+ const cursorRow = Math.max(0, allInputRows.findIndex((line) => line.includes(CURSOR_MARKER)));
425
+ const inputStart = Math.max(0, Math.min(cursorRow, allInputRows.length - inputBudget));
426
+ const inputRows = allInputRows.slice(inputStart, inputStart + inputBudget);
415
427
  const optionRows = this.input.options.map((option, index) => formatUserQuestionOptionRow(option, index === this.activeIndex, safeWidth));
416
- const assemble = (title, options) => [
428
+ const assemble = (title, options, help = [hint, blank]) => [
417
429
  ...title,
418
- hint,
419
- blank,
430
+ ...help,
420
431
  ...options.flat(),
421
432
  ...inputRows,
422
433
  divider,
423
434
  ];
424
435
  const full = assemble(titleLines, optionRows);
425
- const budget = this.input.maxRows?.() ?? Number.POSITIVE_INFINITY;
436
+ const budget = this.viewportRows;
426
437
  if (full.length <= budget)
427
438
  return full;
428
- // Over budget pi-tui would slice(0, maxHeight) — silently dropping the
429
- // input row and divider. Degrade instead: cap the title at two lines and
430
- // give every option an equal share of the remaining rows, each ending in
431
- // a visible ellipsis when clamped. Only a terminal too short for one row
432
- // per option still overflows, falling back to the pre-existing clip.
433
- const cappedTitle = clampRowsWithEllipsis(titleLines, 2, safeWidth);
434
- const fixedRows = cappedTitle.length + 2 + inputRows.length + 1;
439
+ // The layout owns the available height. Short screens first lose spacing
440
+ // and the hint, retaining the question, every choice, and the answer field.
441
+ const titleBudget = Math.min(2, Math.max(1, budget - this.input.options.length - inputRows.length - 3));
442
+ const cappedTitle = clampRowsWithEllipsis(titleLines, titleBudget, safeWidth);
443
+ const helpBudget = Math.max(0, budget - cappedTitle.length - this.input.options.length - inputRows.length - 1);
444
+ const help = [hint, blank].slice(0, helpBudget);
445
+ const fixedRows = cappedTitle.length + help.length + inputRows.length + 1;
435
446
  const optionBudget = Math.max(this.input.options.length, budget - fixedRows);
436
447
  const perOption = Math.max(1, Math.floor(optionBudget / Math.max(1, this.input.options.length)));
437
- return assemble(cappedTitle, optionRows.map((rows) => clampRowsWithEllipsis(rows, perOption, safeWidth)));
448
+ return assemble(cappedTitle, optionRows.map((rows) => clampRowsWithEllipsis(rows, perOption, safeWidth)), help);
438
449
  }
439
450
  renderInputRow(width) {
440
451
  const marker = this.onInputRow ? '→' : ' ';
@@ -240,7 +240,7 @@ export async function runMakaPiTui(input) {
240
240
  };
241
241
  };
242
242
  let userQuestionInFlight = false;
243
- let userQuestionOverlay;
243
+ let userQuestionPrompt;
244
244
  let userQuestionProgress;
245
245
  let formResponseInFlightRequestId;
246
246
  let formOverlay;
@@ -1783,9 +1783,14 @@ export async function runMakaPiTui(input) {
1783
1783
  maxHeight: '100%',
1784
1784
  margin: { bottom: BOTTOM_PICKER_MARGIN_ROWS },
1785
1785
  });
1786
- const closeUserQuestionOverlay = () => {
1787
- userQuestionOverlay?.hide();
1788
- userQuestionOverlay = undefined;
1786
+ const closeUserQuestionPrompt = () => {
1787
+ const prompt = userQuestionPrompt;
1788
+ if (!prompt)
1789
+ return;
1790
+ layout.setBlockingInteraction(undefined);
1791
+ userQuestionPrompt = undefined;
1792
+ if (tui.getFocusedComponent() === prompt)
1793
+ tui.setFocus(editorSurface);
1789
1794
  };
1790
1795
  const finishUserQuestion = (requestId, answers) => {
1791
1796
  if (userQuestionInFlight)
@@ -1796,7 +1801,7 @@ export async function runMakaPiTui(input) {
1796
1801
  return;
1797
1802
  }
1798
1803
  userQuestionInFlight = true;
1799
- closeUserQuestionOverlay();
1804
+ closeUserQuestionPrompt();
1800
1805
  void respond
1801
1806
  .call(input.driver, { requestId, answers })
1802
1807
  .then(() => {
@@ -1824,36 +1829,35 @@ export async function runMakaPiTui(input) {
1824
1829
  finishUserQuestion(request.requestId, progress.answers);
1825
1830
  return;
1826
1831
  }
1827
- closeUserQuestionOverlay();
1832
+ closeUserQuestionPrompt();
1828
1833
  const advance = (answer) => {
1829
1834
  progress.answers[progress.index] = answer;
1830
1835
  progress.index += 1;
1831
1836
  showUserQuestion();
1832
1837
  };
1833
- userQuestionOverlay = showBottomPicker(new UserQuestionOverlay(tui, {
1838
+ userQuestionPrompt = new UserQuestionOverlay(tui, {
1834
1839
  title: question.question,
1835
1840
  rightLabel: `${progress.index + 1} / ${request.questions.length}`,
1836
1841
  hint: '↑↓ move · type to answer · Enter select · Esc unanswered · Ctrl+C stop',
1837
1842
  placeholder: 'Other: type your answer…',
1838
1843
  options: question.options,
1839
- // Live budget: terminal.rows changes on resize, so read it per render
1840
- // rather than at overlay construction.
1841
- maxRows: () => Math.max(1, terminal.rows - BOTTOM_PICKER_MARGIN_ROWS),
1842
1844
  onSelectOption: (index) => advance(question.options[index]?.label ?? null),
1843
1845
  onSubmitText: (value) => advance(value),
1844
1846
  onSkip: () => advance(null),
1845
- }));
1847
+ });
1848
+ layout.setBlockingInteraction(userQuestionPrompt);
1849
+ tui.setFocus(userQuestionPrompt);
1846
1850
  };
1847
- const syncUserQuestionOverlay = () => {
1851
+ const syncUserQuestionPrompt = () => {
1848
1852
  const request = activeUserQuestionRequest(state);
1849
1853
  if (!request) {
1850
- closeUserQuestionOverlay();
1854
+ closeUserQuestionPrompt();
1851
1855
  userQuestionProgress = undefined;
1852
1856
  return;
1853
1857
  }
1854
1858
  if (userQuestionInFlight)
1855
1859
  return;
1856
- if (userQuestionProgress?.requestId !== request.requestId) {
1860
+ if (userQuestionProgress?.requestId !== request.requestId || !userQuestionPrompt) {
1857
1861
  userQuestionProgress = {
1858
1862
  requestId: request.requestId,
1859
1863
  index: 0,
@@ -1978,7 +1982,7 @@ export async function runMakaPiTui(input) {
1978
1982
  transcriptOverlay?.hide();
1979
1983
  transcriptOverlay = undefined;
1980
1984
  }
1981
- syncUserQuestionOverlay();
1985
+ syncUserQuestionPrompt();
1982
1986
  syncFormOverlay();
1983
1987
  };
1984
1988
  const showSelectPicker = (title, rightLabel, items, onSelect, options) => {
@@ -4090,6 +4094,11 @@ export async function runMakaPiTui(input) {
4090
4094
  requestTurnInterrupt();
4091
4095
  return { consume: true };
4092
4096
  }
4097
+ // AskUserQuestion is rendered inside the live layout so the transcript is
4098
+ // not covered by a modal overlay. Keep the same input-capture boundary as
4099
+ // the old overlay while the prompt owns focus.
4100
+ if (userQuestionPrompt)
4101
+ return undefined;
4093
4102
  if (tui.hasOverlay())
4094
4103
  return undefined;
4095
4104
  if (sideConversation && matchesSideConversationToggle(data)) {
@@ -22,7 +22,7 @@ import { homedir } from 'node:os';
22
22
  import { dirname, join, resolve } from 'node:path';
23
23
  import { createCredentialMcpOAuthStorage, formatMcpDiagnosticText, McpClientManager, } from '@maka/mcp';
24
24
  import { createFileCredentialStore } from '@maka/storage/credential-store';
25
- import { normalizeMcpConfig } from '@maka/storage/mcp-config-store';
25
+ import { normalizeMcpConfig, subscribeMcpConfigFileChanges } from '@maka/storage/mcp-config-store';
26
26
  import { connectRemoteRuntimeHost, loadOrCreateRuntimeHostClientInstanceId, remoteRuntimeHostUnavailableError, RuntimeHostPermanentReconnectError, startRuntimeHostCapabilityProviderService, } from '@maka/runtime-host/client';
27
27
  import { INTERACTIVE_RUNTIME_HOST_COMPOSITION_ID, RUNTIME_HOST_COMPATIBILITY_EPOCH, RUNTIME_HOST_PROTOCOL_VERSION, } from '@maka/runtime-host/protocol';
28
28
  import { runRuntimeHostProcessLifecycle } from '@maka/runtime-host/server';
@@ -39,11 +39,14 @@ export async function runRuntimeHostCapabilityProviderCli(options) {
39
39
  const credential = process.env[credentialEnv];
40
40
  if (!credential)
41
41
  throw new Error(`Runtime Host access credential is missing from ${credentialEnv}`);
42
- const configText = await readFile(configPath, 'utf8');
43
- if (Buffer.byteLength(configText, 'utf8') > MAX_MCP_CONFIG_BYTES) {
44
- throw new Error('MCP config exceeds 1 MiB');
45
- }
46
- const config = normalizeMcpConfig(JSON.parse(configText));
42
+ const readConfig = async () => {
43
+ const configText = await readFile(configPath, 'utf8');
44
+ if (Buffer.byteLength(configText, 'utf8') > MAX_MCP_CONFIG_BYTES) {
45
+ throw new Error('MCP config exceeds 1 MiB');
46
+ }
47
+ return normalizeMcpConfig(JSON.parse(configText));
48
+ };
49
+ const config = await readConfig();
47
50
  const clientInstanceId = await loadOrCreateRuntimeHostClientInstanceId(identityPath);
48
51
  const manager = new McpClientManager({
49
52
  clientName: 'maka-capability-provider',
@@ -55,6 +58,21 @@ export async function runRuntimeHostCapabilityProviderCli(options) {
55
58
  oauthStorage: createCredentialMcpOAuthStorage(createFileCredentialStore(dirname(configPath))),
56
59
  });
57
60
  await manager.sync(config);
61
+ // Desktop and the TUI edit this file while the provider runs. One change is
62
+ // read and applied at a time, so an older read never lands last.
63
+ let following = Promise.resolve();
64
+ const stopFollowing = subscribeMcpConfigFileChanges(configPath, (error) => {
65
+ if (error) {
66
+ process.stderr.write(`MCP config is no longer followed: ${error.message}\n`);
67
+ return;
68
+ }
69
+ following = following
70
+ .then(async () => manager.sync(await readConfig()))
71
+ .catch((failure) => {
72
+ const message = failure instanceof Error ? failure.message : String(failure);
73
+ process.stderr.write(`MCP config change was not applied: ${message}\n`);
74
+ });
75
+ });
58
76
  let service;
59
77
  let publishedRevision = manager.toolSnapshot().revision;
60
78
  const disposeChanges = manager.onChange(() => {
@@ -100,6 +118,7 @@ export async function runRuntimeHostCapabilityProviderCli(options) {
100
118
  }
101
119
  finally {
102
120
  clearInterval(reconnectTimer);
121
+ stopFollowing();
103
122
  disposeChanges();
104
123
  await service?.close().catch(() => undefined);
105
124
  await manager.close();
@@ -24,9 +24,10 @@ import { mkdir, open, readFile, realpath, rename, rm, stat, unlink, } from 'node
24
24
  import { homedir } from 'node:os';
25
25
  import { dirname, isAbsolute, join, resolve } from 'node:path';
26
26
  import { truncateUtf8 } from '@maka/core/diagnostic-log';
27
+ import { redactSecrets } from '@maka/core/redaction';
27
28
  import { canonicalProjectDirectoryRootSpec, isCanonicalRuntimeHostWebSocketPath, PROJECT_DIRECTORY_MAX_ROOTS, projectDirectoryRootSpecValid, RUNTIME_HOST_PROTOCOL_VERSION, } from '@maka/runtime-host/protocol';
28
29
  import { connectExistingRuntimeHost, prepareConnectedRuntimeHostRetirement, } from '@maka/runtime-host/client';
29
- import { resolveRuntimeHostManagedServiceId, RUNTIME_HOST_SERVICE_LOG_MAX_BYTES, } from '@maka/runtime-host/operator';
30
+ import { resolveRuntimeHostManagedServiceId, RUNTIME_HOST_SERVICE_LOG_MAX_BYTES, RUNTIME_HOST_SERVICE_ERROR_MESSAGE_MAX_BYTES, } from '@maka/runtime-host/operator';
30
31
  import { withProcessLifetimeFileUpdateLock } from '@maka/storage/process-lifetime-file-update-lock';
31
32
  import { discoverMarkedStorageRoot, resolveExistingStorageRoot, tryAcquireInteractiveRootOwner, } from '@maka/storage/root-authority';
32
33
  import { isRuntimeHostManagedDeploymentCli, removeRuntimeHostManagedDeployment, resolveExistingRuntimeHostManagedDeploymentRoot, resolveRuntimeHostManagedDeploymentForCli, resolveRuntimeHostManagedDeploymentRoot, } from './runtime-host-managed-deployment.js';
@@ -39,6 +40,7 @@ const DEFAULT_WEBSOCKET_PATH = '/runtime-host';
39
40
  const SERVICE_OPERATION_LOCK_TIMEOUT_MS = 60_000;
40
41
  const SERVICE_READY_TIMEOUT_MS = 45_000;
41
42
  const SERVICE_READY_POLL_MS = 50;
43
+ const SERVICE_READINESS_LOG_MAX_BYTES = 1_024;
42
44
  export class RuntimeHostServiceManagerError extends Error {
43
45
  code;
44
46
  constructor(code, message, options) {
@@ -451,6 +453,17 @@ export function formatRuntimeHostServiceLogs(sources) {
451
453
  })
452
454
  .join('\n');
453
455
  }
456
+ export function formatRuntimeHostServiceReadinessDiagnostic(input) {
457
+ const lines = [`Runtime Host service did not become ready: ${redactSecrets(input.lastFailure)}`];
458
+ if (input.status) {
459
+ lines.push(`service state: ${input.status.state}; active: ${String(input.status.active)}; pid: ${input.status.pid ?? 'none'}; last exit code: ${input.status.lastExitCode ?? 'unknown'}`);
460
+ }
461
+ if (input.logs?.trim()) {
462
+ const safeLogs = takeUtf8Tail(redactSecrets(input.logs.trim()), SERVICE_READINESS_LOG_MAX_BYTES);
463
+ lines.push(`service logs (tail):\n${safeLogs}`);
464
+ }
465
+ return truncateUtf8(lines.join('\n'), RUNTIME_HOST_SERVICE_ERROR_MESSAGE_MAX_BYTES, '\n<diagnostic truncated>');
466
+ }
454
467
  function takeUtf8Tail(value, maximumBytes) {
455
468
  const encoded = Buffer.from(value);
456
469
  if (encoded.byteLength <= maximumBytes)
@@ -738,7 +751,18 @@ export async function verifyRuntimeHostManagedServiceReady(config, backend) {
738
751
  }
739
752
  await new Promise((resolveWait) => setTimeout(resolveWait, SERVICE_READY_POLL_MS));
740
753
  }
741
- throw new RuntimeHostServiceManagerError('service_manager_operation_failed', `Runtime Host service did not become ready: ${lastFailure}`);
754
+ throw new RuntimeHostServiceManagerError('service_manager_operation_failed', await formatRuntimeHostServiceReadinessFailure(lastFailure, backend));
755
+ }
756
+ async function formatRuntimeHostServiceReadinessFailure(lastFailure, backend) {
757
+ const [status, logs] = await Promise.all([
758
+ backend.status().catch(() => undefined),
759
+ backend.logs().catch(() => undefined),
760
+ ]);
761
+ return formatRuntimeHostServiceReadinessDiagnostic({
762
+ lastFailure,
763
+ status,
764
+ logs,
765
+ });
742
766
  }
743
767
  async function prepareRuntimeHostRetirement(config, expectedPid, allowInterruptActiveTasks) {
744
768
  const connected = await connectExistingRuntimeHost({
@@ -16,6 +16,7 @@
16
16
  * specific language governing permissions and limitations
17
17
  * under the License.
18
18
  */
19
+ import { createRefreshReadCoordinator } from '@maka/core/refresh-read-coordinator';
19
20
  /**
20
21
  * How full the context is right now, re-asked mid-turn (#4545).
21
22
  *
@@ -55,44 +56,32 @@ export function isCtxRefreshRelevantEvent(event) {
55
56
  export const CTX_REFRESH_DEBOUNCE_MS = 400;
56
57
  /**
57
58
  * Coalesces a burst of refresh-worthy events into one query, and lets only
58
- * the latest issued query apply. A query that resolves after a newer one was
59
- * issued — or after `cancel` retired it — is dropped: the answer a slow read
60
- * brings back describes an older snapshot than the one already shown, and a
61
- * torn-down session is not owed an update at all. A failed query leaves the
62
- * last value standing: it is still the newest answer anyone has.
59
+ * the latest valid query apply. A query that resolves after a newer one was
60
+ * issued, after a relevant event invalidated it, or after `cancel` retired it
61
+ * is dropped: the answer a slow read brings back describes an older snapshot
62
+ * than the one already shown, and a torn-down session is not owed an update
63
+ * at all. A failed query leaves the last value standing: it is still the
64
+ * newest answer anyone has.
63
65
  *
64
66
  * The scheduler is injected so the policy is testable without a wall clock,
65
67
  * and follows the CLI's ticker convention: schedule returns the cancel.
66
68
  */
67
69
  export function createCtxRefresher(input) {
68
- let cancelScheduled;
69
- let revision = 0;
70
- const dropScheduled = () => {
71
- cancelScheduled?.();
72
- cancelScheduled = undefined;
73
- };
74
- const run = () => {
75
- cancelScheduled = undefined;
76
- const requestRevision = ++revision;
77
- void input.query().then((result) => {
78
- if (requestRevision !== revision)
79
- return;
80
- input.apply(result);
81
- }, () => { });
82
- };
70
+ const coordinator = createRefreshReadCoordinator({
71
+ read: input.query,
72
+ apply: input.apply,
73
+ delayMs: input.delayMs,
74
+ schedule: input.schedule,
75
+ });
83
76
  return {
84
77
  observe(event) {
85
78
  if (!isCtxRefreshRelevantEvent(event))
86
79
  return;
87
80
  // Restart rather than stack: the last event of a burst is the one whose
88
81
  // snapshot the reader wants, and an earlier timer would read before it.
89
- dropScheduled();
90
- cancelScheduled = input.schedule(run, input.delayMs);
91
- },
92
- cancel() {
93
- dropScheduled();
94
- revision += 1;
82
+ coordinator.observe();
95
83
  },
84
+ cancel: coordinator.cancel,
96
85
  };
97
86
  }
98
87
  /** Default one-shot timer; unref'd so a pending refresh never holds the CLI open. */