maka-agent 0.2.0-dev.41.20260918 → 0.2.0-dev.43.20260919

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 (54) hide show
  1. package/LICENSE +7 -4
  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/pi-tui-runner.js +57 -6
  8. package/native/runtime-host-windows-task-launcher/prebuilds/win32-x64/maka-runtime-host-task-launcher.exe +0 -0
  9. package/node_modules/@maka/core/dist/client-plugin-bridge.js +19 -0
  10. package/node_modules/@maka/core/dist/connection-usage.js +27 -0
  11. package/node_modules/@maka/core/dist/events.js +58 -7
  12. package/node_modules/@maka/core/dist/external-session.js +4 -3
  13. package/node_modules/@maka/core/dist/interaction.js +38 -4
  14. package/node_modules/@maka/core/dist/llm-connections.js +4 -0
  15. package/node_modules/@maka/core/dist/model-metadata.js +89 -0
  16. package/node_modules/@maka/core/dist/provider-auth.js +9 -1
  17. package/node_modules/@maka/core/dist/recall.js +198 -15
  18. package/node_modules/@maka/core/dist/redaction.js +8 -0
  19. package/node_modules/@maka/core/dist/session-reference.js +149 -0
  20. package/node_modules/@maka/core/dist/skill-locations.js +29 -0
  21. package/node_modules/@maka/core/package.json +4 -0
  22. package/node_modules/@maka/runtime/dist/commandcode-cli-language-model.js +27 -3
  23. package/node_modules/@maka/runtime/dist/connection-usage.js +232 -0
  24. package/node_modules/@maka/runtime/dist/model-factory.js +5 -3
  25. package/node_modules/@maka/runtime/dist/model-history.js +13 -2
  26. package/node_modules/@maka/runtime/dist/plugin-client-bridge-service.js +308 -0
  27. package/node_modules/@maka/runtime/dist/plugin-composition-loader.js +4 -3
  28. package/node_modules/@maka/runtime/dist/plugin-data-services.js +3 -3
  29. package/node_modules/@maka/runtime/dist/recall-tools.js +70 -2
  30. package/node_modules/@maka/runtime/dist/skills-discovery.js +17 -23
  31. package/node_modules/@maka/runtime/package.json +2 -0
  32. package/node_modules/@maka/runtime-host/dist/protocol/connection-effects.js +132 -1
  33. package/node_modules/@maka/runtime-host/dist/protocol/index.js +11 -1
  34. package/node_modules/@maka/runtime-host/dist/protocol/operations.js +6 -0
  35. package/node_modules/@maka/runtime-host/dist/protocol/plugin-platform.js +269 -0
  36. package/node_modules/@maka/runtime-host/dist/protocol/workhub-coordination.js +2 -0
  37. package/node_modules/@maka/runtime-host/dist/server/connection-effect-coordinator.js +57 -1
  38. package/node_modules/@maka/runtime-host/dist/server/execution-composition.js +50 -9
  39. package/node_modules/@maka/runtime-host/dist/server/extension-package-manifest.js +19 -1
  40. package/node_modules/@maka/runtime-host/dist/server/plugin-package-loader.js +88 -14
  41. package/node_modules/@maka/runtime-host/dist/server/plugin-package-store.js +22 -5
  42. package/node_modules/@maka/runtime-host/dist/server/plugin-platform-coordinator.js +122 -1
  43. package/node_modules/@maka/runtime-host/dist/server/plugin-platform.js +120 -1
  44. package/node_modules/@maka/runtime-host/dist/server/recall-material-fetch.js +92 -0
  45. package/node_modules/@maka/runtime-host/dist/server/root-turn-coordinator.js +14 -1
  46. package/node_modules/@maka/runtime-host/dist/server/session-catalog-coordinator.js +17 -1
  47. package/node_modules/@maka/runtime-host/dist/server/workhub-coordination-action-gate.js +67 -3
  48. package/node_modules/@maka/runtime-host/dist/server/workhub-coordination-coordinator.js +9 -3
  49. package/node_modules/@maka/runtime-host/dist/server/workhub-target-execution-authority.js +161 -0
  50. package/node_modules/@maka/storage/dist/codex-session-adapter.js +26 -28
  51. package/node_modules/@maka/storage/dist/runtime-policy/coordinator.js +34 -1
  52. package/node_modules/@maka/storage/dist/runtime-policy-stores.js +2 -0
  53. package/node_modules/@maka/storage/dist/session-store.js +5 -2
  54. 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')
@@ -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();
@@ -0,0 +1,19 @@
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
+ export {};
@@ -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
+ }
@@ -79,7 +79,16 @@ export function hasMeaningfulMessageContent(content) {
79
79
  (content.directoryReferences?.length ?? 0) > 0);
80
80
  }
81
81
  const ATTACHMENT_REF_SHAPE = defineObjectShape()(['kind', 'name', 'mimeType', 'bytes', 'ref'], []);
82
- const QUOTE_REF_SHAPE = defineObjectShape()(['text'], ['label', 'sourceTurnId']);
82
+ const QUOTE_REF_SHAPE = defineObjectShape()(['text'], [
83
+ 'label',
84
+ 'sourceTurnId',
85
+ 'sourceSessionId',
86
+ 'sourceSessionName',
87
+ 'sourceCapturedAt',
88
+ 'sourceTruncated',
89
+ ]);
90
+ const QUOTE_REF_SESSION_ID_MAX_LENGTH = 512;
91
+ const QUOTE_REF_SESSION_NAME_MAX_LENGTH = 200;
83
92
  const INLINE_REFERENCE_SHAPE = defineObjectShape()(['kind', 'value', 'label', 'start'], []);
84
93
  const INLINE_SKILL_REFERENCE_VALUE = /^\/skill:[A-Za-z0-9._-]+$/;
85
94
  export const INLINE_REFERENCE_MAX_COUNT = 32;
@@ -113,6 +122,22 @@ export function normalizeMessageContent(content) {
113
122
  text: quote.text,
114
123
  ...(quote.label !== undefined ? { label: quote.label } : {}),
115
124
  ...(quote.sourceTurnId !== undefined ? { sourceTurnId: quote.sourceTurnId } : {}),
125
+ ...(quote.sourceSessionId !== undefined
126
+ ? { sourceSessionId: quote.sourceSessionId }
127
+ : {}),
128
+ ...(quote.sourceSessionName !== undefined
129
+ ? { sourceSessionName: quote.sourceSessionName }
130
+ : {}),
131
+ ...(quote.sourceCapturedAt !== undefined
132
+ ? {
133
+ sourceCapturedAt: Object.is(quote.sourceCapturedAt, -0)
134
+ ? 0
135
+ : quote.sourceCapturedAt,
136
+ }
137
+ : {}),
138
+ ...(quote.sourceTruncated !== undefined
139
+ ? { sourceTruncated: quote.sourceTruncated }
140
+ : {}),
116
141
  })),
117
142
  }
118
143
  : {}),
@@ -205,11 +230,33 @@ export function isInlineReference(value) {
205
230
  })));
206
231
  }
207
232
  export function isQuoteRef(value) {
208
- return (isRecord(value) &&
209
- hasExactShape(value, QUOTE_REF_SHAPE) &&
210
- typeof value.text === 'string' &&
211
- (value.label === undefined || typeof value.label === 'string') &&
212
- (value.sourceTurnId === undefined || typeof value.sourceTurnId === 'string'));
233
+ const record = isRecord(value) ? value : undefined;
234
+ const sourceFields = record
235
+ ? [
236
+ record.sourceSessionId,
237
+ record.sourceSessionName,
238
+ record.sourceCapturedAt,
239
+ record.sourceTruncated,
240
+ ]
241
+ : [];
242
+ const hasSourceMetadata = sourceFields.some((field) => field !== undefined);
243
+ return (record !== undefined &&
244
+ hasExactShape(record, QUOTE_REF_SHAPE) &&
245
+ typeof record.text === 'string' &&
246
+ (record.label === undefined || typeof record.label === 'string') &&
247
+ (record.sourceTurnId === undefined || typeof record.sourceTurnId === 'string') &&
248
+ (!hasSourceMetadata ||
249
+ (typeof record.sourceSessionId === 'string' &&
250
+ record.sourceSessionId.length > 0 &&
251
+ record.sourceSessionId.length <= QUOTE_REF_SESSION_ID_MAX_LENGTH &&
252
+ typeof record.sourceSessionName === 'string' &&
253
+ record.sourceSessionName.length > 0 &&
254
+ record.sourceSessionName.length <= QUOTE_REF_SESSION_NAME_MAX_LENGTH &&
255
+ typeof record.sourceCapturedAt === 'number' &&
256
+ Number.isFinite(record.sourceCapturedAt) &&
257
+ record.sourceCapturedAt >= 0 &&
258
+ record.sourceCapturedAt <= 8.64e15 &&
259
+ typeof record.sourceTruncated === 'boolean')));
213
260
  }
214
261
  export function isAttachmentRef(value) {
215
262
  return (isRecord(value) &&
@@ -339,7 +386,11 @@ function inlineReferencesEqual(left, right) {
339
386
  function quoteRefsEqual(left, right) {
340
387
  return (left.text === right.text &&
341
388
  left.label === right.label &&
342
- left.sourceTurnId === right.sourceTurnId);
389
+ left.sourceTurnId === right.sourceTurnId &&
390
+ left.sourceSessionId === right.sourceSessionId &&
391
+ left.sourceSessionName === right.sourceSessionName &&
392
+ left.sourceCapturedAt === right.sourceCapturedAt &&
393
+ left.sourceTruncated === right.sourceTruncated);
343
394
  }
344
395
  function attachmentRefsEqual(left, right) {
345
396
  if (left.kind !== right.kind ||
@@ -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
  /**