maka-agent 0.2.0-dev.42.20260918 → 0.2.0-dev.44.20260920

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 (59) hide show
  1. package/LICENSE +12 -6
  2. package/dist/acp/maka-acp-agent.js +17 -6
  3. package/dist/acp/session-event-mapper.js +6 -0
  4. package/dist/acp/session-interactions.js +520 -0
  5. package/dist/acp/session-registry.js +80 -9
  6. package/dist/acp/tool-event-mapper.js +60 -2
  7. package/dist/mcp-capability-provider.js +7 -0
  8. package/dist/pi-tui-runner.js +57 -6
  9. package/dist/runtime-host-tui-command.js +1 -1
  10. package/native/runtime-host-windows-task-launcher/prebuilds/win32-x64/maka-runtime-host-task-launcher.exe +0 -0
  11. package/node_modules/@maka/core/dist/chat-model-choice.js +3 -0
  12. package/node_modules/@maka/core/dist/connection-usage.js +27 -0
  13. package/node_modules/@maka/core/dist/external-session.js +4 -3
  14. package/node_modules/@maka/core/dist/llm-connections.js +3 -21
  15. package/node_modules/@maka/core/dist/model-catalog.js +8 -20
  16. package/node_modules/@maka/core/dist/model-metadata.generated.js +304 -304
  17. package/node_modules/@maka/core/dist/model-thinking.js +16 -1
  18. package/node_modules/@maka/core/dist/models-dev-projection.js +0 -1
  19. package/node_modules/@maka/core/dist/provider-auth.js +9 -1
  20. package/node_modules/@maka/core/dist/provider-registry.js +7 -51
  21. package/node_modules/@maka/core/dist/runtime-policy/connection-catalog-codec.js +7 -0
  22. package/node_modules/@maka/core/dist/runtime-policy/model-catalog-entry-codec.js +14 -1
  23. package/node_modules/@maka/core/dist/settings.js +2 -3
  24. package/node_modules/@maka/core/dist/skill-locations.js +29 -0
  25. package/node_modules/@maka/core/package.json +2 -0
  26. package/node_modules/@maka/mcp/dist/index.js +22 -0
  27. package/node_modules/@maka/mcp/dist/oauth.js +4 -2
  28. package/node_modules/@maka/mcp/dist/tool-progress.js +45 -0
  29. package/node_modules/@maka/runtime/dist/commandcode-cli-language-model.js +27 -3
  30. package/node_modules/@maka/runtime/dist/connection-usage.js +232 -0
  31. package/node_modules/@maka/runtime/dist/opencode-session-header.js +2 -3
  32. package/node_modules/@maka/runtime/dist/session-manager.js +1 -44
  33. package/node_modules/@maka/runtime/dist/skills-discovery.js +17 -23
  34. package/node_modules/@maka/runtime/dist/test-connection.js +3 -66
  35. package/node_modules/@maka/runtime/package.json +1 -0
  36. package/node_modules/@maka/runtime-host/dist/client/client-capability-channel.js +44 -9
  37. package/node_modules/@maka/runtime-host/dist/client/index.js +1 -1
  38. package/node_modules/@maka/runtime-host/dist/client/session-subscription.js +33 -3
  39. package/node_modules/@maka/runtime-host/dist/protocol/connection-effects.js +132 -1
  40. package/node_modules/@maka/runtime-host/dist/protocol/index.js +13 -3
  41. package/node_modules/@maka/runtime-host/dist/protocol/message.js +10 -0
  42. package/node_modules/@maka/runtime-host/dist/protocol/operations.js +1 -1
  43. package/node_modules/@maka/runtime-host/dist/protocol/session-catalog.js +1 -1
  44. package/node_modules/@maka/runtime-host/dist/protocol/turn.js +0 -33
  45. package/node_modules/@maka/runtime-host/dist/server/access-credential-store.js +2 -0
  46. package/node_modules/@maka/runtime-host/dist/server/bootstrap-runtime-policy.js +31 -104
  47. package/node_modules/@maka/runtime-host/dist/server/connection-effect-coordinator.js +57 -1
  48. package/node_modules/@maka/runtime-host/dist/server/execution-composition.js +4 -5
  49. package/node_modules/@maka/runtime-host/dist/server/interactive-turn-coordinator.js +1 -16
  50. package/node_modules/@maka/runtime-host/dist/server/message-coordinator.js +46 -28
  51. package/node_modules/@maka/runtime-host/dist/server/root-turn-coordinator.js +5 -16
  52. package/node_modules/@maka/runtime-host/dist/server/session-catalog-coordinator.js +21 -12
  53. package/node_modules/@maka/runtime-host/dist/server/session-turn-access-request-coordinator.js +7 -17
  54. package/node_modules/@maka/storage/dist/codex-session-adapter.js +26 -28
  55. package/node_modules/@maka/storage/dist/runtime-policy/connection-catalog-document.js +0 -62
  56. package/node_modules/@maka/storage/dist/runtime-policy/coordinator.js +34 -4
  57. package/node_modules/@maka/storage/dist/runtime-policy-stores.js +2 -1
  58. package/node_modules/@maka/storage/dist/session-store.js +5 -2
  59. package/package.json +1 -1
@@ -28,6 +28,7 @@ import { RuntimeHostSessionUpdateError, getRuntimeHostSession, requireRuntimeHos
28
28
  import { AcpSessionConfigInputError, createAcpSessionConfigPatch, projectAcpSessionConfigOptions, validateAcpSessionConfigOptionRequest, } from './session-configuration.js';
29
29
  import { AcpSessionEventMapper } from './session-event-mapper.js';
30
30
  import { mapAcpPromptContent, publishAcpPromptAttachments } from './prompt-content.js';
31
+ import { AcpSessionInteractions } from './session-interactions.js';
31
32
  const ACP_SESSION_CURSOR_MAX_BYTES = 8 * 1024;
32
33
  const ADMISSION_QUERY_MAX_ATTEMPTS = 5;
33
34
  const ADMISSION_QUERY_TIMEOUT_MS = 1_000;
@@ -39,6 +40,7 @@ export class AcpSessionRegistry {
39
40
  #newTurnId;
40
41
  #inFlightOperations = new Set();
41
42
  #ownedSessionIds = new Set();
43
+ #attachmentInteractions = new Map();
42
44
  #attachments = new Map();
43
45
  #attachmentOpenControllers = new Map();
44
46
  #attachmentConfigurations = new Map();
@@ -181,7 +183,7 @@ export class AcpSessionRegistry {
181
183
  const connection = await this.#getConnection('subscription.open');
182
184
  let attachment;
183
185
  try {
184
- attachment = await this.#ensureAttachment(params.sessionId, connection, context.notify);
186
+ attachment = await this.#ensureAttachment(params.sessionId, connection, context);
185
187
  }
186
188
  catch (error) {
187
189
  if (active.cancelled)
@@ -274,6 +276,11 @@ export class AcpSessionRegistry {
274
276
  active.projectionAbort.abort();
275
277
  active.reconciliationAbort.abort();
276
278
  active.transcript?.dispose();
279
+ this.#attachmentInteractions.get(active.sessionId)?.settleTurn(active.turnId);
280
+ const observed = active.attachment?.snapshot.rootTurn;
281
+ if (observed?.turnId === active.turnId && isRuntimeHostTerminalTurn(observed)) {
282
+ this.#attachmentInteractions.get(active.sessionId)?.terminalTurn(active.turnId);
283
+ }
277
284
  active.finished = true;
278
285
  this.#wake(active);
279
286
  this.#removeActivePrompt(active);
@@ -366,6 +373,7 @@ export class AcpSessionRegistry {
366
373
  }
367
374
  active.projectionAbort.abort();
368
375
  active.reconciliationAbort.abort();
376
+ this.#attachmentInteractions.get(active.sessionId)?.cancelTurn(active.turnId);
369
377
  active.stopTask ??= this.#stopPromptWhenObservable(active);
370
378
  await Promise.all([
371
379
  active.mapper.flush().catch(() => undefined),
@@ -468,14 +476,14 @@ export class AcpSessionRegistry {
468
476
  }, 'Runtime Host Turn admission could not be established; Stop could not be confirmed');
469
477
  this.#wake(active);
470
478
  }
471
- async #ensureAttachment(sessionId, connection, notify) {
479
+ async #ensureAttachment(sessionId, connection, context) {
472
480
  const existing = this.#attachments.get(sessionId);
473
481
  if (existing)
474
482
  return existing;
475
483
  const openingController = new AbortController();
476
484
  this.#attachmentOpenControllers.set(sessionId, openingController);
477
485
  const configuration = {
478
- notify,
486
+ notify: context.notify,
479
487
  // Setters can outlive an absent or failed attachment. Their responses
480
488
  // must precede refreshes delivered by the new attachment's queue.
481
489
  tail: Promise.allSettled([...(this.#pendingConfigSets.get(sessionId) ?? [])]),
@@ -491,6 +499,50 @@ export class AcpSessionRegistry {
491
499
  }
492
500
  this.#retireFailedAttachment(sessionId, task, attachment, error);
493
501
  };
502
+ const interactions = new AcpSessionInteractions({
503
+ sessionId,
504
+ connection,
505
+ client: context.interactions ?? {
506
+ capabilities: {},
507
+ requestPermission: async () => {
508
+ throw RequestError.methodNotFound('session/request_permission');
509
+ },
510
+ createElicitation: async () => {
511
+ throw RequestError.methodNotFound('elicitation/create');
512
+ },
513
+ },
514
+ onPending: async (pending) => {
515
+ for (const active of this.#activePrompts.get(sessionId) ?? []) {
516
+ if (active.turnId === pending.turnId && !active.cancelled) {
517
+ await active.mapper.pendingInteraction(pending);
518
+ }
519
+ }
520
+ },
521
+ onAnswered: (answered, pending) => attachment?.publishInteractionAnswer(answered, pending),
522
+ onResolved: async (resolved, pending) => {
523
+ for (const active of this.#activePrompts.get(sessionId) ?? []) {
524
+ if (active.turnId === pending.turnId && !active.cancelled && !active.finished) {
525
+ await active.mapper.resolvedInteraction(resolved, pending);
526
+ }
527
+ }
528
+ },
529
+ onFailure: (pending, error) => {
530
+ const active = [...(this.#activePrompts.get(sessionId) ?? [])].find((prompt) => prompt.turnId === pending.turnId && !prompt.finished);
531
+ if (active?.attachment) {
532
+ active.projectionFailure ??= error;
533
+ active.attachment.failTurn(active.turnId, error);
534
+ }
535
+ else if (!attachment)
536
+ failAttachment(error);
537
+ },
538
+ onCancelled: (pending) => {
539
+ for (const active of this.#activePrompts.get(sessionId) ?? []) {
540
+ if (active.turnId === pending.turnId)
541
+ void this.#cancelPrompt(active).catch(() => undefined);
542
+ }
543
+ },
544
+ });
545
+ this.#attachmentInteractions.set(sessionId, interactions);
494
546
  task = RuntimeHostSessionChannel.open({
495
547
  connection,
496
548
  signal: openingController.signal,
@@ -501,6 +553,9 @@ export class AcpSessionRegistry {
501
553
  onRuntimeResourceChanged: () => undefined,
502
554
  onSnapshotChanged: (snapshot) => {
503
555
  this.#wakeSession(sessionId);
556
+ if (snapshot.rootTurn && isRuntimeHostTerminalTurn(snapshot.rootTurn)) {
557
+ interactions.terminalTurn(snapshot.rootTurn.turnId);
558
+ }
504
559
  if (configuration.metadataRevision === undefined) {
505
560
  configuration.metadataRevision = snapshot.session.metadataRevision;
506
561
  return;
@@ -534,15 +589,20 @@ export class AcpSessionRegistry {
534
589
  }
535
590
  },
536
591
  onInteractionPending: (pending) => {
537
- if (![...(this.#activePrompts.get(sessionId) ?? [])].some((active) => active.turnId === pending.turnId && active.dispatchStarted)) {
538
- // An idle attachment may observe another client's Turn. Retain its
539
- // identity so a later ACP cancel/close can still stop that root.
592
+ if (!interactions.fencesTurn(pending.turnId) &&
593
+ ![...(this.#activePrompts.get(sessionId) ?? [])].some((active) => active.turnId === pending.turnId && active.dispatchStarted)) {
594
+ // Idle attachments may observe a Turn started by another client. Do not
595
+ // present its interaction through this ACP connection's client.
540
596
  return;
541
597
  }
542
- // Interaction mapping is added by the next independently reviewable ACP increment.
543
- failAttachment(RequestError.internalError({ source: 'adapter', code: 'unsupported_interaction', kind: pending.request.kind }, 'This ACP adapter does not support interactions yet; the prompt failed'));
598
+ void interactions.pending(pending);
599
+ },
600
+ onInteractionResolved: (pending) => {
601
+ if (!interactions.fencesTurn(pending.turnId) &&
602
+ ![...(this.#activePrompts.get(sessionId) ?? [])].some((active) => active.turnId === pending.turnId && active.dispatchStarted))
603
+ return;
604
+ void interactions.resolved(pending);
544
605
  },
545
- onInteractionResolved: () => undefined,
546
606
  onTranscriptSettlement: (turnId) => {
547
607
  for (const active of this.#activePrompts.get(sessionId) ?? []) {
548
608
  if (active.turnId === turnId)
@@ -583,6 +643,10 @@ export class AcpSessionRegistry {
583
643
  return channel;
584
644
  })
585
645
  .catch((error) => {
646
+ interactions.close();
647
+ if (this.#attachmentInteractions.get(sessionId) === interactions) {
648
+ this.#attachmentInteractions.delete(sessionId);
649
+ }
586
650
  if (this.#attachments.get(sessionId) === task) {
587
651
  this.#attachments.delete(sessionId);
588
652
  this.#attachmentConfigurations.delete(sessionId);
@@ -603,6 +667,8 @@ export class AcpSessionRegistry {
603
667
  if (this.#attachments.get(sessionId) === task) {
604
668
  this.#attachments.delete(sessionId);
605
669
  this.#attachmentConfigurations.delete(sessionId);
670
+ this.#attachmentInteractions.get(sessionId)?.close();
671
+ this.#attachmentInteractions.delete(sessionId);
606
672
  }
607
673
  for (const active of this.#activePrompts.get(sessionId) ?? []) {
608
674
  if (active.attachment !== attachment)
@@ -616,6 +682,8 @@ export class AcpSessionRegistry {
616
682
  }
617
683
  async #closeSession(sessionId, delivery) {
618
684
  const cancellation = await this.#cancelSession(sessionId);
685
+ this.#attachmentInteractions.get(sessionId)?.close();
686
+ this.#attachmentInteractions.delete(sessionId);
619
687
  const attachmentTask = this.#attachments.get(sessionId);
620
688
  this.#attachments.delete(sessionId);
621
689
  let closeError;
@@ -814,6 +882,9 @@ export class AcpSessionRegistry {
814
882
  ...(this.#activePrompts.get(sessionId) ?? []),
815
883
  ]);
816
884
  const cancellations = [...sessionIds].map((sessionId) => this.#cancelSession(sessionId));
885
+ for (const interactions of this.#attachmentInteractions.values())
886
+ interactions.close();
887
+ this.#attachmentInteractions.clear();
817
888
  const attachments = [...this.#attachments.values()];
818
889
  this.#attachments.clear();
819
890
  const configurations = [...this.#attachmentConfigurations.values()];
@@ -96,6 +96,59 @@ export class AcpToolEventMapper {
96
96
  await this.#result(this.#ensure(message.turnId, message.toolUseId), message.isError, message.content, message.durationMs, false);
97
97
  }
98
98
  }
99
+ async pendingInteraction(pending) {
100
+ const tool = this.#ensure(pending.turnId, interactionToolId(pending));
101
+ if (pending.request.kind === 'sandbox_boundary')
102
+ tool.synthetic = true;
103
+ if (tool.terminal)
104
+ return;
105
+ tool.status = 'pending';
106
+ tool.meta.interaction = {
107
+ id: pending.interactionId,
108
+ kind: pending.request.kind,
109
+ status: 'pending',
110
+ };
111
+ if (!tool.name)
112
+ tool.title = `Awaiting ${pending.request.kind.replaceAll('_', ' ')}`;
113
+ await this.#publish(tool);
114
+ }
115
+ async resolvedInteraction(resolved, pending) {
116
+ if (resolved.status === 'pending')
117
+ return;
118
+ const tool = this.#ensure(pending.turnId, interactionToolId(pending));
119
+ if (pending.request.kind === 'sandbox_boundary')
120
+ tool.synthetic = true;
121
+ const outcome = resolved.outcome;
122
+ tool.meta.interaction = {
123
+ id: pending.interactionId,
124
+ kind: pending.request.kind,
125
+ status: resolved.status,
126
+ ...(outcome.kind === 'closure' ? { reason: outcome.reason } : {}),
127
+ ...('decision' in outcome ? { decision: outcome.decision } : {}),
128
+ ...('action' in outcome ? { action: outcome.action } : {}),
129
+ };
130
+ if (!tool.terminal) {
131
+ if (pending.request.kind === 'sandbox_boundary') {
132
+ tool.terminal = true;
133
+ tool.status =
134
+ outcome.kind === 'sandbox_boundary_decision' && outcome.decision === 'allow'
135
+ ? 'completed'
136
+ : 'failed';
137
+ }
138
+ else
139
+ tool.status = 'in_progress';
140
+ }
141
+ const closure = outcome.kind === 'closure' ? `Interaction closed: ${outcome.reason}` : undefined;
142
+ if (closure && !tool.terminal) {
143
+ tool.progress ??= progressBuffer();
144
+ tool.progress.append(closure);
145
+ }
146
+ await this.#publish(tool, closure && pending.request.kind === 'sandbox_boundary'
147
+ ? { content: textContent(closure) }
148
+ : {});
149
+ if (tool.terminal)
150
+ this.#release(tool);
151
+ }
99
152
  /** Called only after authoritative turn settlement and transcript reconciliation. */
100
153
  async finishTools(turnId, terminalStatus) {
101
154
  let missingResult = false;
@@ -112,8 +165,8 @@ export class AcpToolEventMapper {
112
165
  }
113
166
  // A live result announcement promises a durable result. A start, delta or
114
167
  // progress event alone does not: completed turns can contain unfinished
115
- // tool calls, and those should remain visible as interrupted cards.
116
- if (terminalStatus === 'completed' && tool.resultAnnounced)
168
+ // tool calls, and synthetic interaction cards have no tool result.
169
+ if (terminalStatus === 'completed' && !tool.synthetic && tool.resultAnnounced)
117
170
  missingResult = true;
118
171
  this.#release(tool);
119
172
  }
@@ -308,6 +361,11 @@ function fixedStateChars(tool) {
308
361
  function digestValue(value) {
309
362
  return createHash('sha256').update(JSON.stringify(value)).digest('hex');
310
363
  }
364
+ function interactionToolId(pending) {
365
+ return pending.request.kind === 'sandbox_boundary'
366
+ ? pending.interactionId
367
+ : pending.request.toolUseId;
368
+ }
311
369
  function rawInput(toolName, args) {
312
370
  // The shared transcript intentionally projects these two tools' arguments.
313
371
  if (args === undefined || toolName === 'WriteStdin' || toolName === 'todo_write')
@@ -76,6 +76,13 @@ export function createMcpCapabilityProvider(manager) {
76
76
  return projectMcpResult(await manager.callTool(binding, frame.arguments, {
77
77
  signal: options.signal,
78
78
  requestInteraction: options.requestInteraction,
79
+ ...(options.progress
80
+ ? {
81
+ onProgress: (current, total) => {
82
+ options.progress?.(current, total);
83
+ },
84
+ }
85
+ : {}),
79
86
  }));
80
87
  },
81
88
  };
@@ -18,6 +18,7 @@
18
18
  */
19
19
  import { randomUUID } from 'node:crypto';
20
20
  import { Key, ProcessTerminal, SelectList, TuiMainScreen, isKeyRelease, isKeyRepeat, matchesKey, } from '@earendil-works/pi-tui';
21
+ import { normalizeExternalSessionQueryText, } from '@maka/core/external-session';
21
22
  import { CurrentTodoStore, TodoOverlay, renderTodoIndicator } from './pi-tui-todo.js';
22
23
  import { isThinkingLevel } from '@maka/core/model-thinking';
23
24
  import { deriveConnectionSlug } from '@maka/core/llm-connections';
@@ -53,6 +54,7 @@ import { formatMakaResumeCommand } from './cli-invocation.js';
53
54
  import { goalAttachedNoticeText, goalPausedNoticeText, goalStatusLabel, goalSummaryLines, isLiveGoalStatus, } from './pi-goal.js';
54
55
  import { getTuiPrimaryGuidance } from './tui-primary-guidance.js';
55
56
  import { TUI_COPY_RESOURCES } from './tui-copy-catalog.js';
57
+ const EXTERNAL_SESSION_SEARCH_DEBOUNCE_MS = 120;
56
58
  function externalSourceLabel(adapterId) {
57
59
  switch (adapterId) {
58
60
  case 'claude-code':
@@ -203,6 +205,7 @@ export async function runMakaPiTui(input) {
203
205
  let connectionIdentityNotice;
204
206
  let busy = false;
205
207
  let closed = false;
208
+ const cancelScheduledExternalSearches = new Set();
206
209
  let currentActivityCompletion;
207
210
  let permissionResponseInFlightRequestId = null;
208
211
  // Session recap (issue #1055): an in-flight lock shared by manual and
@@ -718,6 +721,9 @@ export async function runMakaPiTui(input) {
718
721
  attention.reset();
719
722
  // Stop asking the terminal for focus reports before handing it back.
720
723
  terminal.write(DISABLE_FOCUS_REPORTING);
724
+ for (const cancel of cancelScheduledExternalSearches)
725
+ cancel();
726
+ cancelScheduledExternalSearches.clear();
721
727
  tui.stop();
722
728
  };
723
729
  const beginClose = (error) => {
@@ -2580,17 +2586,41 @@ export async function runMakaPiTui(input) {
2580
2586
  let sessions = [];
2581
2587
  let nextCursor = null;
2582
2588
  let revision = 0;
2589
+ let normalizedQuery = normalizeExternalSessionQueryText(query);
2590
+ let cancelScheduledSearch;
2591
+ let pageClosed = false;
2583
2592
  let overlay;
2584
2593
  let search;
2585
2594
  let byValue = new Map();
2586
- const closeOverlay = () => overlay?.hide();
2595
+ const dropScheduledSearch = () => {
2596
+ const hadScheduledSearch = cancelScheduledSearch !== undefined;
2597
+ if (cancelScheduledSearch)
2598
+ cancelScheduledExternalSearches.delete(cancelScheduledSearch);
2599
+ cancelScheduledSearch?.();
2600
+ cancelScheduledSearch = undefined;
2601
+ return hadScheduledSearch;
2602
+ };
2603
+ const resetCatalogPage = () => {
2604
+ sessions = [];
2605
+ nextCursor = null;
2606
+ byValue = new Map();
2607
+ render();
2608
+ };
2609
+ const closeOverlay = () => {
2610
+ pageClosed = true;
2611
+ dropScheduledSearch();
2612
+ revision += 1;
2613
+ overlay?.hide();
2614
+ };
2587
2615
  const toggleScope = () => {
2588
2616
  const alternate = input.externalSessions
2589
2617
  ?.listScopes()
2590
2618
  .find((candidate) => candidate !== scope);
2591
2619
  if (!alternate)
2592
2620
  return;
2621
+ dropScheduledSearch();
2593
2622
  scope = alternate;
2623
+ resetCatalogPage();
2594
2624
  void load(false);
2595
2625
  };
2596
2626
  const render = () => {
@@ -2647,8 +2677,29 @@ export async function runMakaPiTui(input) {
2647
2677
  emptyText: sessions.length === 0 ? copy.externalEmpty : copy.externalUnavailable,
2648
2678
  notice,
2649
2679
  onQuery: (text) => {
2680
+ const nextNormalizedQuery = normalizeExternalSessionQueryText(text);
2650
2681
  query = text;
2651
- void load(false);
2682
+ if (nextNormalizedQuery === normalizedQuery)
2683
+ return;
2684
+ normalizedQuery = nextNormalizedQuery;
2685
+ dropScheduledSearch();
2686
+ resetCatalogPage();
2687
+ // Retire an older in-flight response immediately. Waiting until the
2688
+ // debounce fires would let it repaint results for the previous query.
2689
+ const requestRevision = ++revision;
2690
+ let cancel;
2691
+ const handle = setTimeout(() => {
2692
+ if (cancel)
2693
+ cancelScheduledExternalSearches.delete(cancel);
2694
+ cancelScheduledSearch = undefined;
2695
+ if (closed || pageClosed)
2696
+ return;
2697
+ void load(false, undefined, requestRevision);
2698
+ }, EXTERNAL_SESSION_SEARCH_DEBOUNCE_MS);
2699
+ handle.unref();
2700
+ cancel = () => clearTimeout(handle);
2701
+ cancelScheduledSearch = cancel;
2702
+ cancelScheduledExternalSearches.add(cancel);
2652
2703
  },
2653
2704
  onSelect: (item) => {
2654
2705
  if (item.value === 'external:load-more' && nextCursor) {
@@ -2702,8 +2753,8 @@ export async function runMakaPiTui(input) {
2702
2753
  });
2703
2754
  overlay = showBottomPicker(search);
2704
2755
  };
2705
- const load = async (append, cursor) => {
2706
- const requestRevision = ++revision;
2756
+ const load = async (append, cursor, scheduledRevision) => {
2757
+ const requestRevision = scheduledRevision ?? ++revision;
2707
2758
  try {
2708
2759
  const page = await input.externalSessions.listSessions({
2709
2760
  adapterId,
@@ -2711,14 +2762,14 @@ export async function runMakaPiTui(input) {
2711
2762
  ...(cursor ? { cursor } : {}),
2712
2763
  ...(query ? { text: query } : {}),
2713
2764
  });
2714
- if (requestRevision !== revision || closed || turnRunning)
2765
+ if (requestRevision !== revision || pageClosed || closed || turnRunning)
2715
2766
  return;
2716
2767
  sessions = append ? [...sessions, ...page.sessions] : page.sessions;
2717
2768
  nextCursor = page.nextCursor;
2718
2769
  render();
2719
2770
  }
2720
2771
  catch {
2721
- if (requestRevision !== revision)
2772
+ if (requestRevision !== revision || pageClosed || closed)
2722
2773
  return;
2723
2774
  state.entries.push({ kind: 'notice', level: 'error', text: copy.externalCatalogFailed });
2724
2775
  requestRender();
@@ -50,7 +50,7 @@ export async function runRuntimeHostTui(input) {
50
50
  throw error;
51
51
  const configured = await runFirstRunOnboarding(input.clientDataRoot, input.workspaceRoot, input.cwd, input.locale, input.hostProfileId);
52
52
  if (!configured)
53
- throw error;
53
+ return 0;
54
54
  context = await createRuntimeHostTuiContext(contextInput);
55
55
  }
56
56
  let ownerAction;
@@ -37,6 +37,9 @@ export function buildChatModelChoices(connections) {
37
37
  ...(provider.authKind === 'oauth_token' ? {} : { connectionName: connection.name }),
38
38
  isDefault: entry.isDefault,
39
39
  thinkingLevels: entry.thinkingLevels,
40
+ ...(entry.defaultThinkingLevel === undefined
41
+ ? {}
42
+ : { defaultThinkingLevel: entry.defaultThinkingLevel }),
40
43
  supportsVision: entry.supportsVision,
41
44
  ...(entry.contextWindow !== undefined ? { contextWindow: entry.contextWindow } : {}),
42
45
  ...(declaredWindow !== undefined ? { declaredContextWindow: declaredWindow } : {}),
@@ -0,0 +1,27 @@
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
+ /**
20
+ * The providers Maka can read account usage from. The single source of truth
21
+ * for both the settings section's visibility and the runtime dispatch, so a
22
+ * provider that gains a mapper is offered the card in the same change.
23
+ */
24
+ const USAGE_PROVIDERS = new Set(['commandcode-go']);
25
+ export function providerReportsUsage(providerType) {
26
+ return USAGE_PROVIDERS.has(providerType);
27
+ }
@@ -72,8 +72,8 @@ export function externalSessionMatchesQuery(summary, query = {}) {
72
72
  const text = normalizeExternalSessionQueryText(query.text);
73
73
  if (text === undefined)
74
74
  return true;
75
- // Title and path, because those are the two things a user remembers about a
76
- // conversation they are looking for. Both already sit on the summary, so
75
+ // Source id, title, and path are the bounded summary fields a user can paste
76
+ // or remember about a conversation. All already sit on the summary, so
77
77
  // matching costs no extra reads. Message content is deliberately excluded:
78
78
  // it would mean opening every transcript on every keystroke.
79
79
  //
@@ -86,7 +86,8 @@ export function externalSessionMatchesQuery(summary, query = {}) {
86
86
  // literal backslash-n, and folding the term without the title would stop
87
87
  // `\\n` from finding the very title it names. The path pair has no such
88
88
  // ambiguity: a separator there is a separator.
89
- return (normalizeExternalSessionMatchText(summary.name).includes(text) ||
89
+ return (normalizeExternalSessionMatchText(summary.id).includes(text) ||
90
+ normalizeExternalSessionMatchText(summary.name).includes(text) ||
90
91
  foldExternalSessionPathSeparators(normalizeExternalSessionMatchText(summary.cwd)).includes(foldExternalSessionPathSeparators(text)));
91
92
  }
92
93
  /**
@@ -17,9 +17,9 @@
17
17
  * under the License.
18
18
  */
19
19
  import { CODEX_SUBSCRIPTION_UNSUPPORTED_CHATGPT_MODELS } from './codex-model-compatibility.js';
20
- import { CATALOG_PROVIDER_TYPES, OPENCODE_FREE_DEFAULT_MODEL, PROVIDER_REGISTRY, RECOMMENDED_PROVIDER_TYPES, providerDefaultsOf, providerFallbackModelIds, providerMenuLabel, } from './provider-registry.js';
20
+ import { CATALOG_PROVIDER_TYPES, PROVIDER_REGISTRY, RECOMMENDED_PROVIDER_TYPES, providerDefaultsOf, providerFallbackModelIds, providerMenuLabel, } from './provider-registry.js';
21
21
  export { CODEX_SUBSCRIPTION_UNSUPPORTED_CHATGPT_MODELS };
22
- export { CATALOG_PROVIDER_TYPES, OPENCODE_FREE_DEFAULT_MODEL, PROVIDER_REGISTRY, RECOMMENDED_PROVIDER_TYPES, providerDefaultsOf, providerFallbackModelIds, providerMenuLabel, };
22
+ export { CATALOG_PROVIDER_TYPES, PROVIDER_REGISTRY, RECOMMENDED_PROVIDER_TYPES, providerDefaultsOf, providerFallbackModelIds, providerMenuLabel, };
23
23
  export function isRelayProviderType(providerType) {
24
24
  return providerType === 'openai-compatible' || providerType === 'openai-responses-compatible';
25
25
  }
@@ -69,7 +69,7 @@ export function connectionEnabledModelIds(connection) {
69
69
  * 3. the Host's entry says the connection can hold a chat on it.
70
70
  *
71
71
  * (3) already subsumes what clients used to re-derive locally: a retired
72
- * provider, a quarantined `brokenModelIds` id, and a model whose metadata says
72
+ * provider and a model whose metadata says
73
73
  * it cannot chat are all non-offerable before a client sees them. A client
74
74
  * re-testing any of those against its OWN registry answers for a build that is
75
75
  * not the one running the send.
@@ -135,12 +135,6 @@ export function authorizeConnectionModel(connection, modelId) {
135
135
  const model = modelId.trim();
136
136
  if (!model || !connectionEnabledModelIds(connection).includes(model))
137
137
  return undefined;
138
- // The one veto: quarantined ids fail in a shape the send cannot surface
139
- // (e.g. a billed 200 with an empty completion), so the request settling it
140
- // is not available as the arbiter. See ProviderDefaults.brokenModelIds.
141
- if (providerDefaultsOf(connection.providerType)?.brokenModelIds?.includes(model)) {
142
- return undefined;
143
- }
144
138
  // The observed row wins wherever it exists: it carries wire metadata such as
145
139
  // `apiProtocol`, and capabilities, which a synthesized entry cannot. Absent
146
140
  // capabilities already mean "unknown", not "unsupported".
@@ -268,18 +262,6 @@ export function reconcileConnectionAfterModelFetch(connection, models, options)
268
262
  }),
269
263
  };
270
264
  }
271
- /**
272
- * The models a connection created without an explicit selection starts with,
273
- * or undefined when the provider seeds nothing. Derived from the provider's
274
- * shipped baseline rather than listed a second time: the two can then never
275
- * disagree about what "all of them" means.
276
- */
277
- export function defaultEnabledModelIdsWhenOmitted(providerType) {
278
- const defaults = providerDefaultsOf(providerType);
279
- if (!defaults?.enableShippedModelsByDefault)
280
- return undefined;
281
- return providerFallbackModelIds(defaults);
282
- }
283
265
  export function providerAuthRequiresSecret(providerType) {
284
266
  const authKind = providerDefaultsOf(providerType)?.authKind;
285
267
  return authKind === 'api_key' || authKind === 'oauth_token';
@@ -16,7 +16,7 @@
16
16
  * specific language governing permissions and limitations
17
17
  * under the License.
18
18
  */
19
- import { applyModelOverride, resolveModelLimits } from './model-thinking.js';
19
+ import { applyModelOverride, defaultThinkingLevelForConnection, resolveModelLimits, } from './model-thinking.js';
20
20
  import { CODEX_SUBSCRIPTION_UNSUPPORTED_CHATGPT_MODELS, PROVIDER_REGISTRY, providerDefaultsOf, providerFallbackModelIds, providerSupportsModelDiscovery, } from './llm-connections.js';
21
21
  import { lookupModelMetadata, resolveModelVisionSupport } from './model-metadata.js';
22
22
  import { thinkingVariantsForConnection, } from './model-thinking.js';
@@ -95,36 +95,22 @@ export function buildConnectionModelCatalogEntries(input) {
95
95
  if (!defaults)
96
96
  return [];
97
97
  const supportsModelDiscovery = providerSupportsModelDiscovery(connection.providerType);
98
- // Quarantined ids never surface as offerable entries — from any source,
99
- // including inventories stored or selections made before the quarantine —
100
- // mirroring the `authorizeConnectionModel` veto.
101
- const broken = new Set(defaults.brokenModelIds ?? []);
102
98
  const fallbackModels = providerFallbackModelIds(defaults);
103
- // A quarantined id persisted as this connection's `defaultModel` must not
104
- // re-enter the catalog either. `models` and `enabledModelIds` are filtered
105
- // below, but a broken default reaches `makeMissingDefaultEntry` unfiltered and
106
- // would be re-added as a selectable `provider_default` row — picker-visible
107
- // and default-capable while `authorizeConnectionModel` vetoes the same id. A
108
- // reachable persisted state: the id was picker-visible before the quarantine.
109
- // Dropping it leaves the connection with no valid default (readiness reports
110
- // `missing_model`), which is what a model that can no longer send warrants.
111
- const defaultModel = broken.has((connection.defaultModel ?? '').trim())
112
- ? undefined
113
- : connection.defaultModel;
99
+ const defaultModel = connection.defaultModel;
114
100
  const fallbackModelIds = new Set(fallbackModels);
115
- const projectedModelsById = new Map((connection.models ?? []).filter(({ id }) => !broken.has(id)).map((model) => [model.id, model]));
101
+ const projectedModelsById = new Map((connection.models ?? []).map((model) => [model.id, model]));
116
102
  // Fallback providers have no live inventory, but a projected connection can
117
103
  // still carry enabled model-facts entries that are absent from the static
118
104
  // list. Keep both sets in the catalog so those user-declared models retain
119
105
  // their metadata.
120
106
  const models = supportsModelDiscovery
121
- ? connection.models?.filter(({ id }) => !broken.has(id))
107
+ ? connection.models
122
108
  : [
123
109
  ...fallbackModels.map((id) => projectedModelsById.get(id) ?? {
124
110
  id,
125
111
  ...displayNameForKnownModel(connection.providerType, id),
126
112
  }),
127
- ...(connection.models ?? []).filter((model) => !broken.has(model.id) && !fallbackModelIds.has(model.id)),
113
+ ...(connection.models ?? []).filter((model) => !fallbackModelIds.has(model.id)),
128
114
  ];
129
115
  return buildModelCatalogEntries({
130
116
  providerType: connection.providerType,
@@ -143,7 +129,7 @@ export function buildConnectionModelCatalogEntries(input) {
143
129
  savedModelIds: [
144
130
  ...(connection.enabledModelIds ?? []),
145
131
  ...Object.keys(connection.modelOverrides ?? {}),
146
- ].filter((id) => !broken.has(id)),
132
+ ],
147
133
  });
148
134
  }
149
135
  /**
@@ -239,6 +225,7 @@ function makeEntry(ctx, model, overrides = {}) {
239
225
  capabilities,
240
226
  ...(modalities !== undefined ? { modalities } : {}),
241
227
  });
228
+ const defaultThinkingLevel = defaultThinkingLevelForConnection(thinkingContext, normalizedModel.id);
242
229
  return {
243
230
  id: normalizedModel.id,
244
231
  ...displayNameForModel(input.providerType, normalizedModel),
@@ -253,6 +240,7 @@ function makeEntry(ctx, model, overrides = {}) {
253
240
  ? {}
254
241
  : { compactionThreshold: input.modelOverrides[normalizedModel.id].compactionThreshold }),
255
242
  thinkingLevels: thinkingVariantsForConnection(thinkingContext, normalizedModel.id),
243
+ ...(defaultThinkingLevel === undefined ? {} : { defaultThinkingLevel }),
256
244
  ...limits,
257
245
  ...(defaults.contextWindow === undefined
258
246
  ? {}