maka-agent 0.2.0-dev.47.20260922 → 0.2.0-dev.49.20260924

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 (155) hide show
  1. package/dist/acp/session-event-mapper.js +1 -4
  2. package/dist/onboarding-catalog.js +20 -4
  3. package/dist/pi-transcript.js +0 -8
  4. package/dist/pi-tui-pickers.js +1 -1
  5. package/dist/pi-tui-runner.js +6 -4
  6. package/dist/runtime-host-capability-provider-command.js +25 -6
  7. package/dist/runtime-host-onboarding.js +5 -3
  8. package/dist/runtime-host-session-channel.js +9 -3
  9. package/dist/tui-mcp-control.js +37 -24
  10. package/native/runtime-host-windows-task-launcher/prebuilds/win32-x64/maka-runtime-host-task-launcher.exe +0 -0
  11. package/node_modules/@maka/acp-executor-plugin/dist/acp-errors.js +26 -0
  12. package/node_modules/@maka/acp-executor-plugin/dist/acp-filesystem.js +103 -0
  13. package/node_modules/@maka/acp-executor-plugin/dist/acp-process.js +119 -0
  14. package/node_modules/@maka/acp-executor-plugin/dist/acp-projection.js +146 -0
  15. package/node_modules/@maka/acp-executor-plugin/dist/index.js +653 -0
  16. package/node_modules/@maka/acp-executor-plugin/dist/plugin.mjs +24446 -0
  17. package/node_modules/@maka/acp-executor-plugin/package.json +15 -0
  18. package/node_modules/@maka/antigravity-acp-plugin/dist/index.js +88 -0
  19. package/node_modules/@maka/antigravity-acp-plugin/dist/model-presentation.js +53 -0
  20. package/node_modules/@maka/antigravity-acp-plugin/dist/plugin.mjs +115 -0
  21. package/node_modules/@maka/antigravity-acp-plugin/package.json +15 -0
  22. package/node_modules/@maka/core/dist/events.js +13 -0
  23. package/node_modules/@maka/core/dist/executor-catalog.js +106 -0
  24. package/node_modules/@maka/core/dist/llm-connections.js +2 -5
  25. package/node_modules/@maka/core/dist/mcp.js +4 -1
  26. package/node_modules/@maka/core/dist/model-facts.js +2 -4
  27. package/node_modules/@maka/core/dist/model-metadata.generated.js +345 -248
  28. package/node_modules/@maka/core/dist/model-metadata.js +28 -1
  29. package/node_modules/@maka/core/dist/model-thinking.js +17 -17
  30. package/node_modules/@maka/core/dist/model-web-search.js +7 -9
  31. package/node_modules/@maka/core/dist/models-dev-projection.js +1 -1
  32. package/node_modules/@maka/core/dist/onboarding.js +1 -4
  33. package/node_modules/@maka/core/dist/provider-registry.js +60 -86
  34. package/node_modules/@maka/core/dist/runtime-event-store.js +25 -0
  35. package/node_modules/@maka/core/dist/runtime-event.js +2 -2
  36. package/node_modules/@maka/core/dist/runtime-invocation.js +5 -0
  37. package/node_modules/@maka/core/dist/runtime-policy/connection-catalog-codec.js +28 -16
  38. package/node_modules/@maka/core/dist/runtime-policy/credential-vault-codec.js +6 -0
  39. package/node_modules/@maka/core/dist/runtime-policy/policy-codec.js +21 -0
  40. package/node_modules/@maka/core/dist/runtime-policy.js +1 -1
  41. package/node_modules/@maka/core/dist/session.js +25 -9
  42. package/node_modules/@maka/core/dist/settings.js +1 -0
  43. package/node_modules/@maka/core/dist/tool-ledger-scanner.js +365 -104
  44. package/node_modules/@maka/core/dist/transcript-search.js +1 -0
  45. package/node_modules/@maka/core/dist/turn-origin.js +14 -0
  46. package/node_modules/@maka/core/dist/usage-stats/types.js +1 -0
  47. package/node_modules/@maka/core/package.json +2 -0
  48. package/node_modules/@maka/eval/dist/harbor-maka-subject.js +3 -0
  49. package/node_modules/@maka/eval/dist/maka-subject.js +8 -1
  50. package/node_modules/@maka/mcp/dist/credential-coordinator.js +6 -1
  51. package/node_modules/@maka/mcp/dist/index.js +31 -5
  52. package/node_modules/@maka/mcp/dist/oauth.js +54 -6
  53. package/node_modules/@maka/runtime/dist/agent-run-inspect.js +1 -1
  54. package/node_modules/@maka/runtime/dist/ai-sdk-backend.js +1 -11
  55. package/node_modules/@maka/runtime/dist/ai-sdk-compaction.js +2 -12
  56. package/node_modules/@maka/runtime/dist/ai-sdk-turn.js +3 -71
  57. package/node_modules/@maka/runtime/dist/conversation-copy.js +58 -3
  58. package/node_modules/@maka/runtime/dist/local-web-fetch.js +23 -11
  59. package/node_modules/@maka/runtime/dist/mcp-tools.js +9 -2
  60. package/node_modules/@maka/runtime/dist/model-adapter.js +10 -5
  61. package/node_modules/@maka/runtime/dist/model-factory.js +33 -30
  62. package/node_modules/@maka/runtime/dist/model-fetcher.js +4 -1
  63. package/node_modules/@maka/runtime/dist/model-runtime.js +3 -9
  64. package/node_modules/@maka/runtime/dist/plugin-executor-backend.js +116 -3
  65. package/node_modules/@maka/runtime/dist/plugin-executor-service.js +212 -16
  66. package/node_modules/@maka/runtime/dist/read-page.js +11 -8
  67. package/node_modules/@maka/runtime/dist/recovery-resolver.js +47 -20
  68. package/node_modules/@maka/runtime/dist/runtime-event-backfill.js +1 -0
  69. package/node_modules/@maka/runtime/dist/runtime-ledger-repair.js +2 -2
  70. package/node_modules/@maka/runtime/dist/scheduled-task-tools.js +39 -8
  71. package/node_modules/@maka/runtime/dist/session-event-runtime-mapper.js +3 -0
  72. package/node_modules/@maka/runtime/dist/session-manager.js +182 -127
  73. package/node_modules/@maka/runtime/dist/shell-run-manager.js +3 -0
  74. package/node_modules/@maka/runtime/dist/stream-graph-coordinator.js +29 -4
  75. package/node_modules/@maka/runtime/dist/telemetry/model-pricing.generated.js +141 -134
  76. package/node_modules/@maka/runtime/dist/tool-result-archive-transition.js +3 -3
  77. package/node_modules/@maka/runtime/dist/workers/filesystem-worker.js +7 -6
  78. package/node_modules/@maka/runtime-host/dist/adapter/session-projector.js +28 -62
  79. package/node_modules/@maka/runtime-host/dist/candidate-entry.js +8 -5
  80. package/node_modules/@maka/runtime-host/dist/client/hosted-execution-target.js +7 -1
  81. package/node_modules/@maka/runtime-host/dist/client/index.js +1 -1
  82. package/node_modules/@maka/runtime-host/dist/client/reconnect-lifecycle.js +8 -1
  83. package/node_modules/@maka/runtime-host/dist/client/session-catalog-summary.js +6 -1
  84. package/node_modules/@maka/runtime-host/dist/client/session-subscription.js +3 -0
  85. package/node_modules/@maka/runtime-host/dist/composition-identity.js +19 -0
  86. package/node_modules/@maka/runtime-host/dist/protocol/connection-effects.js +6 -3
  87. package/node_modules/@maka/runtime-host/dist/protocol/index.js +14 -2
  88. package/node_modules/@maka/runtime-host/dist/protocol/operations.js +4 -1
  89. package/node_modules/@maka/runtime-host/dist/protocol/plugin-platform.js +34 -0
  90. package/node_modules/@maka/runtime-host/dist/protocol/prompt-suggestions.js +61 -0
  91. package/node_modules/@maka/runtime-host/dist/protocol/runtime-policy.js +4 -1
  92. package/node_modules/@maka/runtime-host/dist/protocol/session-catalog.js +40 -2
  93. package/node_modules/@maka/runtime-host/dist/protocol/session-continuity.js +2 -1
  94. package/node_modules/@maka/runtime-host/dist/server/builtin-external-agent-plugins.js +188 -0
  95. package/node_modules/@maka/runtime-host/dist/server/candidate.js +12 -7
  96. package/node_modules/@maka/runtime-host/dist/server/client-capability-recovery.js +44 -46
  97. package/node_modules/@maka/runtime-host/dist/server/connection-effect-coordinator.js +3 -0
  98. package/node_modules/@maka/runtime-host/dist/server/context-coordinator.js +2 -0
  99. package/node_modules/@maka/runtime-host/dist/server/execution-composition-factory.js +1 -1
  100. package/node_modules/@maka/runtime-host/dist/server/execution-composition.js +212 -24
  101. package/node_modules/@maka/runtime-host/dist/server/execution-model-authority.js +41 -2
  102. package/node_modules/@maka/runtime-host/dist/server/extension-bundle.js +4 -0
  103. package/node_modules/@maka/runtime-host/dist/server/generated-text.js +39 -0
  104. package/node_modules/@maka/runtime-host/dist/server/host-composition-source.js +47 -0
  105. package/node_modules/@maka/runtime-host/dist/server/host-composition.js +1 -29
  106. package/node_modules/@maka/runtime-host/dist/server/host-kernel.js +2 -9
  107. package/node_modules/@maka/runtime-host/dist/server/hosted-execution-recovery.js +75 -9
  108. package/node_modules/@maka/runtime-host/dist/server/hosted-execution-tool-profile.js +12 -0
  109. package/node_modules/@maka/runtime-host/dist/server/interaction-coordinator.js +45 -0
  110. package/node_modules/@maka/runtime-host/dist/server/jev-routing-model.js +157 -0
  111. package/node_modules/@maka/runtime-host/dist/server/listener-set.js +2 -2
  112. package/node_modules/@maka/runtime-host/dist/server/message-coordinator.js +4 -0
  113. package/node_modules/@maka/runtime-host/dist/server/plugin-platform-coordinator.js +11 -1
  114. package/node_modules/@maka/runtime-host/dist/server/process-lifecycle.js +1 -1
  115. package/node_modules/@maka/runtime-host/dist/server/process-termination-error.js +27 -0
  116. package/node_modules/@maka/runtime-host/dist/server/prompt-suggestion.js +179 -0
  117. package/node_modules/@maka/runtime-host/dist/server/root-turn-coordinator.js +93 -14
  118. package/node_modules/@maka/runtime-host/dist/server/runtime-policy-coordinator.js +2 -0
  119. package/node_modules/@maka/runtime-host/dist/server/session-catalog-coordinator.js +99 -12
  120. package/node_modules/@maka/runtime-host/dist/server/session-continuity-coordinator.js +48 -16
  121. package/node_modules/@maka/runtime-host/dist/server/session-effect-coordinator.js +12 -6
  122. package/node_modules/@maka/runtime-host/dist/server/session-title.js +3 -16
  123. package/node_modules/@maka/runtime-host/dist/server/session-transcript-reader.js +71 -3
  124. package/node_modules/@maka/runtime-host/dist/server/shared-session-transcript.js +3 -0
  125. package/node_modules/@maka/runtime-host/dist/server/workhub-coordination-action-gate.js +6 -13
  126. package/node_modules/@maka/runtime-host/dist/server/workhub-coordination-coordinator.js +39 -11
  127. package/node_modules/@maka/runtime-host/dist/server/workhub-result-coordinator.js +173 -0
  128. package/node_modules/@maka/runtime-host/dist/server/workhub-result-runtime.js +293 -0
  129. package/node_modules/@maka/runtime-host/package.json +2 -0
  130. package/node_modules/@maka/storage/dist/agent-run-store-contract.js +8 -0
  131. package/node_modules/@maka/storage/dist/config-transfer.js +14 -8
  132. package/node_modules/@maka/storage/dist/execution-stores.js +19 -6
  133. package/node_modules/@maka/storage/dist/interaction-store.js +13 -0
  134. package/node_modules/@maka/storage/dist/mcp-config-store.js +91 -2
  135. package/node_modules/@maka/storage/dist/operational-state-store.js +15 -1
  136. package/node_modules/@maka/storage/dist/plan-authority.js +2 -0
  137. package/node_modules/@maka/storage/dist/plan-store.js +30 -0
  138. package/node_modules/@maka/storage/dist/runtime-event-persistence.js +1 -0
  139. package/node_modules/@maka/storage/dist/runtime-policy/connection-catalog-document.js +28 -17
  140. package/node_modules/@maka/storage/dist/runtime-policy/coordinator.js +9 -3
  141. package/node_modules/@maka/storage/dist/runtime-policy/credential-vault-document.js +4 -1
  142. package/node_modules/@maka/storage/dist/runtime-policy/legacy-custom-connection.js +65 -0
  143. package/node_modules/@maka/storage/dist/runtime-policy/onboarding-transaction.js +10 -5
  144. package/node_modules/@maka/storage/dist/runtime-policy/policy-document.js +2 -0
  145. package/node_modules/@maka/storage/dist/session-bundle-policy.js +11 -0
  146. package/node_modules/@maka/storage/dist/session-store-values.js +16 -4
  147. package/node_modules/@maka/storage/dist/session-store.js +29 -6
  148. package/node_modules/@maka/storage/dist/shell-run-authority.js +1 -0
  149. package/node_modules/@maka/storage/dist/shell-run-store.js +15 -0
  150. package/node_modules/@maka/storage/dist/sqlite-core-execution-schema.js +3 -0
  151. package/node_modules/@maka/storage/dist/sqlite-runtime-schema.js +74 -1
  152. package/node_modules/@maka/storage/dist/sqlite-runtime-store.js +849 -91
  153. package/node_modules/@maka/storage/dist/sqlite-session-metadata-store.js +21 -3
  154. package/node_modules/@maka/storage/dist/usage-screen.js +138 -108
  155. package/package.json +1 -1
@@ -142,10 +142,7 @@ export class AcpSessionEventMapper {
142
142
  }
143
143
  }
144
144
  function deltaText(event, current = '') {
145
- return foldRuntimeHostAssistantDelta(current, {
146
- startOffset: event.startOffset ?? current.length,
147
- text: event.text,
148
- }).text;
145
+ return foldRuntimeHostAssistantDelta(current, event).text;
149
146
  }
150
147
  function streamKey(kind, messageId) {
151
148
  return `${kind}:${messageId}`;
@@ -16,9 +16,9 @@
16
16
  * specific language governing permissions and limitations
17
17
  * under the License.
18
18
  */
19
- import { CATALOG_PROVIDER_TYPES, PROVIDER_REGISTRY, providerAuthSupportsApiKey, } from '@maka/core/llm-connections';
19
+ import { CATALOG_PROVIDER_TYPES, MODEL_API_PROTOCOL_LABELS, MODEL_API_PROTOCOLS, PROVIDER_REGISTRY, providerAuthSupportsApiKey, } from '@maka/core/llm-connections';
20
20
  export function listApiKeyOnboardableProviders() {
21
- // Custom relays have no built-in base URL and stay listed: `requiresBaseUrl`
21
+ // Custom connections have no built-in base URL and stay listed: `requiresBaseUrl`
22
22
  // tells the wizard to collect an endpoint before the API key. The original
23
23
  // phase-1 wizard filtered every empty-baseUrl provider out because it had no
24
24
  // base-URL step to offer (#1254); that step exists now (#3405). Providers
@@ -30,13 +30,29 @@ export function listApiKeyOnboardableProviders() {
30
30
  return false;
31
31
  const definition = PROVIDER_REGISTRY[providerType];
32
32
  return Boolean(definition.baseUrl) || definition.category === 'custom';
33
- }).map((providerType) => {
33
+ }).flatMap((providerType) => {
34
34
  const definition = PROVIDER_REGISTRY[providerType];
35
- return {
35
+ const entry = {
36
36
  providerType,
37
37
  label: definition.label,
38
38
  requiresBaseUrl: !definition.baseUrl,
39
39
  setupMethod: 'api_key',
40
40
  };
41
+ if (providerType !== 'custom')
42
+ return [entry];
43
+ return MODEL_API_PROTOCOLS.map((defaultApiProtocol) => ({
44
+ ...entry,
45
+ defaultApiProtocol,
46
+ label: `${definition.label} (${MODEL_API_PROTOCOL_LABELS[defaultApiProtocol]})`,
47
+ }));
41
48
  });
42
49
  }
50
+ export function onboardingCreateTarget(provider) {
51
+ return {
52
+ kind: 'create',
53
+ providerType: provider.providerType,
54
+ ...(provider.defaultApiProtocol === undefined
55
+ ? {}
56
+ : { defaultApiProtocol: provider.defaultApiProtocol }),
57
+ };
58
+ }
@@ -941,14 +941,6 @@ function systemNoteText(message) {
941
941
  return 'Context compacted to keep this task within the model window.';
942
942
  case 'context_compaction_failed_open':
943
943
  return 'Context summary failed; the session continued without a new summary.';
944
- case 'context_provider_dropping': {
945
- const data = message.data;
946
- const used = typeof data?.inputTokens === 'number' ? data.inputTokens : undefined;
947
- const prior = typeof data?.priorInputTokens === 'number' ? data.priorInputTokens : undefined;
948
- if (used === undefined || prior === undefined)
949
- return "After content was appended, the provider-reported input token count did not grow; context may have been truncated or rewritten. If this persists, check that the model's actual context capacity and the connection settings agree.";
950
- return `After content was appended, the provider-reported input token count did not grow; context may have been truncated or rewritten (${used} tokens versus ${prior} before). If this persists, check that the model's actual context capacity and the connection settings agree.`;
951
- }
952
944
  case 'context_overflow_after_compaction':
953
945
  return 'History was compacted and the provider still called this request too large. What remains also carries the system prompt, the tool schemas, the summary and the recent tail; shortening this message is the part you control.';
954
946
  case 'context_reported_window_exceeded': {
@@ -822,7 +822,7 @@ export function onboardingProviderPickerItems(providers, locale) {
822
822
  function onboardingProviderKey(provider) {
823
823
  return provider.target.kind === 'existing'
824
824
  ? provider.target.connectionId
825
- : `create:${provider.target.providerType}`;
825
+ : `create:${provider.target.providerType}:${provider.target.defaultApiProtocol ?? ''}`;
826
826
  }
827
827
  export function thinkingLevelPickerItems(levels, current, locale) {
828
828
  const copy = getTuiPickerCopy(locale);
@@ -26,7 +26,7 @@ import { projectRevisionLinkedSessionTree } from '@maka/core/session-revisions';
26
26
  import { slashCommandsForSurface, } from '@maka/core/slash-command-catalog';
27
27
  import { latestAssistantModelId, } from '@maka/core/session';
28
28
  import { defineUiMessageCatalog, formatUiMessage, resolveUiMessageCatalog, } from '@maka/core/ui-locale';
29
- import { listApiKeyOnboardableProviders } from './onboarding-catalog.js';
29
+ import { listApiKeyOnboardableProviders, onboardingCreateTarget } from './onboarding-catalog.js';
30
30
  import { AUTO_RECAP_DISPLAY_LIMIT_BYTES, shouldAutoRecap } from './session-recap.js';
31
31
  import { MakaSkillHighlightEditor } from './skill-highlight-editor.js';
32
32
  import { parseGraphCommand } from '@maka/core/graph-command';
@@ -2256,7 +2256,7 @@ export async function runMakaPiTui(input) {
2256
2256
  // wizard can report unavailability in-frame at submit instead of throwing.
2257
2257
  providers = listApiKeyOnboardableProviders().map((provider) => ({
2258
2258
  ...provider,
2259
- target: { kind: 'create', providerType: provider.providerType },
2259
+ target: onboardingCreateTarget(provider),
2260
2260
  label: provider.label,
2261
2261
  suggestedSlug: deriveConnectionSlug(provider.providerType),
2262
2262
  enabledModelIds: [],
@@ -2288,8 +2288,10 @@ export async function runMakaPiTui(input) {
2288
2288
  onSubmitIdentity: (identity) => {
2289
2289
  if (wizardTarget?.kind === 'create') {
2290
2290
  wizardTarget = {
2291
- kind: 'create',
2292
- providerType: wizardTarget.providerType,
2291
+ ...onboardingCreateTarget({
2292
+ providerType: wizardTarget.providerType,
2293
+ defaultApiProtocol: wizardTarget.defaultApiProtocol,
2294
+ }),
2293
2295
  ...(identity.slug === null ? {} : { slug: identity.slug }),
2294
2296
  ...(identity.name === null ? {} : { name: identity.name }),
2295
2297
  };
@@ -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();
@@ -19,7 +19,7 @@
19
19
  import { randomUUID } from 'node:crypto';
20
20
  import { deriveConnectionSlug, deriveInteractiveOAuthConnectionSlug, offerableCatalogEntries, providerFallbackModelIds, PROVIDER_REGISTRY, } from '@maka/core/llm-connections';
21
21
  import { createOAuthPresentationClientProvider, readRuntimeHostConnectionCatalog, RuntimeHostOperationError, RuntimeHostRequestInterruptedError, } from '@maka/runtime-host/client';
22
- import { listApiKeyOnboardableProviders } from './onboarding-catalog.js';
22
+ import { listApiKeyOnboardableProviders, onboardingCreateTarget } from './onboarding-catalog.js';
23
23
  /** Adapt the TUI onboarding workflow to Host-owned verification and persistence. */
24
24
  export function createRuntimeHostOnboardingSurface(connection, options = {}) {
25
25
  const shutdown = new AbortController();
@@ -432,8 +432,10 @@ export function projectProviders(catalog, codexOAuthEnabled = false) {
432
432
  const existingSlugs = catalog.connections.map((connection) => connection.slug);
433
433
  for (const provider of listApiKeyOnboardableProviders()) {
434
434
  for (const connection of catalog.connections) {
435
- if (connection.providerType !== provider.providerType)
435
+ if (connection.providerType !== provider.providerType ||
436
+ connection.defaultApiProtocol !== provider.defaultApiProtocol) {
436
437
  continue;
438
+ }
437
439
  entries.push({
438
440
  ...provider,
439
441
  target: { kind: 'existing', connectionId: connection.connectionId },
@@ -444,7 +446,7 @@ export function projectProviders(catalog, codexOAuthEnabled = false) {
444
446
  }
445
447
  entries.push({
446
448
  ...provider,
447
- target: { kind: 'create', providerType: provider.providerType },
449
+ target: onboardingCreateTarget(provider),
448
450
  label: provider.label,
449
451
  suggestedSlug: deriveConnectionSlug(provider.providerType, existingSlugs),
450
452
  enabledModelIds: [],
@@ -19,7 +19,7 @@
19
19
  import { decodeStoredMessage as decodePersistedStoredMessage, } from '@maka/core/session';
20
20
  import { markPersisted } from '@maka/core/persisted-value';
21
21
  import { createRuntimeHostSessionProjectionSeed, RuntimeHostSessionProjector, isRuntimeHostTerminalTurn as isTerminalTurn, sameRuntimeHostTerminalTurn, } from '@maka/runtime-host/adapter';
22
- import { isRuntimeHostReconnectingConnection, RuntimeHostRequestInterruptedError, RuntimeHostSubscriptionError, } from '@maka/runtime-host/client';
22
+ import { isRuntimeHostReconnectingConnection, RuntimeHostOperationError, RuntimeHostRequestInterruptedError, RuntimeHostSubscriptionError, } from '@maka/runtime-host/client';
23
23
  import { SESSION_TRANSCRIPT_BOOTSTRAP_MAX_BYTES, SESSION_TRANSCRIPT_PAGE_MAX_BYTES, } from '@maka/runtime-host/protocol';
24
24
  const decodeStoredMessage = (value) => decodePersistedStoredMessage(markPersisted(value));
25
25
  const PROMPT_TRANSCRIPT_RANGE_MAX_BYTES = 16 * 1024 * 1024;
@@ -652,6 +652,11 @@ export class RuntimeHostSessionChannel {
652
652
  #canRecover(error) {
653
653
  if (!isRuntimeHostReconnectingConnection(this.#connection))
654
654
  return false;
655
+ // The Host can retire a transcript subscription before its close frame is
656
+ // consumed. Recover the invalidated read through the same bounded policy.
657
+ if (error instanceof RuntimeHostOperationError) {
658
+ return error.operation === 'session.transcript.page' && error.code === 'not_found';
659
+ }
655
660
  if (error instanceof RuntimeHostRequestInterruptedError) {
656
661
  return error.reason === 'connection_lost';
657
662
  }
@@ -659,6 +664,7 @@ export class RuntimeHostSessionChannel {
659
664
  (error.reason === 'connection_closed' ||
660
665
  error.reason === 'sequence_gap' ||
661
666
  error.reason === 'projection_revision_invalid' ||
667
+ error.reason === 'transcript_changed' ||
662
668
  error.reason === 'slow_consumer'));
663
669
  }
664
670
  #accept(frame) {
@@ -678,8 +684,8 @@ export class RuntimeHostSessionChannel {
678
684
  return;
679
685
  }
680
686
  if (frame.kind === 'subscription.closed') {
681
- if (frame.reason === 'slow_consumer') {
682
- throw new RuntimeHostSubscriptionError('slow_consumer', 'Runtime Host Session subscription consumer fell behind');
687
+ if (frame.reason === 'slow_consumer' || frame.reason === 'transcript_changed') {
688
+ throw new RuntimeHostSubscriptionError(frame.reason, 'Runtime Host Session transcript requires a fresh subscription');
683
689
  }
684
690
  this.#fail(new Error(`Runtime Host Session subscription closed: ${frame.reason}`));
685
691
  return;
@@ -17,10 +17,10 @@
17
17
  * under the License.
18
18
  */
19
19
  import { createHash, randomUUID } from 'node:crypto';
20
- import { MCP_CONFIG_VERSION, mcpConfigChangeRetiresCredentials, resolveMcpProtocolPreference, } from '@maka/core/mcp';
20
+ import { MCP_CONFIG_VERSION, resolveMcpProtocolPreference, } from '@maka/core/mcp';
21
21
  import { createCredentialMcpOAuthStorage, McpClientManager } from '@maka/mcp';
22
22
  import { createFileCredentialStore } from '@maka/storage/credential-store';
23
- import { AtomicFileWriteCommitUnknownError, createMcpConfigStore, assertMcpEndpointPolicyOnChanges, McpConfigSourceError, normalizeMcpConfig, normalizeMcpImport, } from '@maka/storage/mcp-config-store';
23
+ import { AtomicFileWriteCommitUnknownError, createMcpConfigStore, updateMcpConfiguration, McpConfigurationValidationError, McpConfigSourceError, normalizeMcpConfig, normalizeMcpImport, } from '@maka/storage/mcp-config-store';
24
24
  import { createMcpCapabilityProvider } from './mcp-capability-provider.js';
25
25
  import { McpCapabilityPublication } from './mcp-capability-publication.js';
26
26
  const RUNTIME_HOST_CREDENTIAL_ENV = 'MAKA_RUNTIME_HOST_ACCESS_CREDENTIAL';
@@ -43,6 +43,7 @@ class TuiMcpControllerImpl {
43
43
  #listeners = new Set();
44
44
  #disposeManagerChange;
45
45
  #disposeConnectionAvailability;
46
+ #disposeConfigChanges;
46
47
  #initialization;
47
48
  #availability = { kind: 'unavailable' };
48
49
  #closed = false;
@@ -112,6 +113,17 @@ class TuiMcpControllerImpl {
112
113
  this.#requestPublication();
113
114
  }
114
115
  });
116
+ // Desktop edits the same file. A change during startup waits for it
117
+ // rather than being dropped, since startup may have read the file first.
118
+ // Failing to follow leaves the TUI's own edits working, so it stays quiet
119
+ // rather than print over the screen.
120
+ this.#disposeConfigChanges = deps.configStore.subscribeChanges((error) => {
121
+ if (error)
122
+ return;
123
+ void this.#initialization
124
+ .then(() => this.#serializeAction(() => this.#followConfigChange()))
125
+ .catch(() => undefined);
126
+ });
115
127
  this.#initialization = this.#initialize();
116
128
  }
117
129
  snapshot() {
@@ -176,6 +188,7 @@ class TuiMcpControllerImpl {
176
188
  this.#closed = true;
177
189
  this.#disposeManagerChange();
178
190
  this.#disposeConnectionAvailability();
191
+ this.#disposeConfigChanges();
179
192
  this.#listeners.clear();
180
193
  this.#preparedImport = undefined;
181
194
  const publicationClosing = this.#publication.close().catch(() => undefined);
@@ -264,44 +277,32 @@ class TuiMcpControllerImpl {
264
277
  async #commitMutation(action) {
265
278
  let committed;
266
279
  try {
267
- committed = await this.#deps.configStore.transform(async (current) => {
268
- if (this.#closed) {
280
+ committed = await updateMcpConfiguration(this.#deps.configStore, (current) => {
281
+ if (this.#closed)
269
282
  throw new TuiMcpMutationError({ status: 'failed', reason: 'closed' });
270
- }
271
283
  const prepared = this.#prepareMutation(current, action);
272
284
  if ('status' in prepared)
273
285
  throw new TuiMcpMutationError(prepared);
274
- const { next } = prepared;
286
+ return prepared.next;
287
+ }, async (serverId, previous) => {
275
288
  try {
276
- assertMcpEndpointPolicyOnChanges(current, next);
289
+ await this.#deps.manager.forgetServerCredentials(serverId, previous);
277
290
  }
278
291
  catch {
279
- throw new TuiMcpMutationError({ status: 'failed', reason: 'invalid-config' });
280
- }
281
- try {
282
- for (const [serverId, previous] of Object.entries(current.mcpServers)) {
283
- if (!mcpConfigChangeRetiresCredentials(previous, next.mcpServers[serverId]))
284
- continue;
285
- await this.#deps.manager.forgetServerCredentials(serverId, previous);
286
- if (this.#closed) {
287
- throw new TuiMcpMutationError({ status: 'failed', reason: 'closed' });
288
- }
289
- }
290
- }
291
- catch (error) {
292
- if (error instanceof TuiMcpMutationError)
293
- throw error;
294
292
  throw new TuiMcpMutationError({
295
293
  status: 'failed',
296
294
  reason: 'credential-cleanup-failed',
297
295
  });
298
296
  }
299
- return next;
297
+ if (this.#closed)
298
+ throw new TuiMcpMutationError({ status: 'failed', reason: 'closed' });
300
299
  });
301
300
  }
302
301
  catch (error) {
303
302
  if (error instanceof TuiMcpMutationError)
304
303
  return error.result;
304
+ if (error instanceof McpConfigurationValidationError)
305
+ return { status: 'failed', reason: 'invalid-config' };
305
306
  if (error instanceof AtomicFileWriteCommitUnknownError) {
306
307
  // The transform has already published, including any credential
307
308
  // retirement. Reload its authority; never replay those effects.
@@ -336,12 +337,24 @@ class TuiMcpControllerImpl {
336
337
  }
337
338
  return { status: 'failed', reason: 'persist-failed' };
338
339
  }
340
+ this.#preparedImport = undefined;
339
341
  return (await this.#synchronizeCommittedConfig(committed)).result;
340
342
  }
343
+ async #followConfigChange() {
344
+ if (this.#closed)
345
+ return;
346
+ if (this.#snapshot.initialization === 'error')
347
+ return this.#initialize();
348
+ const latest = await this.#deps.configStore.get();
349
+ // An import preview survives: its commit re-checks each server it replaces.
350
+ if (this.#snapshot.configuration !== 'ready' ||
351
+ JSON.stringify(latest) !== JSON.stringify(this.#config)) {
352
+ await this.#synchronizeCommittedConfig(latest);
353
+ }
354
+ }
341
355
  async #synchronizeCommittedConfig(committed) {
342
356
  if (this.#closed)
343
357
  return { result: { status: 'failed', reason: 'closed' } };
344
- this.#preparedImport = undefined;
345
358
  this.#config = cloneConfig(committed);
346
359
  this.#updateSnapshot({ configuration: 'synchronizing' });
347
360
  this.#refreshManagerSnapshot();
@@ -0,0 +1,26 @@
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 class AcpRuntimeError extends Error {
20
+ code;
21
+ constructor(message, code) {
22
+ super(message);
23
+ this.code = code;
24
+ this.name = 'AcpRuntimeError';
25
+ }
26
+ }
@@ -0,0 +1,103 @@
1
+ /*
2
+ * Licensed to the Apache Software Foundation (ASF) under one
3
+ * or more contributor license agreements. See the NOTICE file
4
+ * distributed with this work for additional information
5
+ * regarding copyright ownership. The ASF licenses this file
6
+ * to you under the Apache License, Version 2.0 (the
7
+ * "License"); you may not use this file except in compliance
8
+ * with the License. You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing,
13
+ * software distributed under the License is distributed on an
14
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
+ * KIND, either express or implied. See the License for the
16
+ * specific language governing permissions and limitations
17
+ * under the License.
18
+ */
19
+ import { constants } from 'node:fs';
20
+ import { lstat, open, realpath, stat } from 'node:fs/promises';
21
+ import { dirname, isAbsolute, relative, resolve } from 'node:path';
22
+ const MAX_TEXT_FILE_BYTES = 8 * 1024 * 1024;
23
+ export async function readWorkspaceTextFile(cwd, path, line, limit) {
24
+ if (line != null && (!Number.isSafeInteger(line) || line < 1))
25
+ throw new Error('ACP file line must be a positive integer');
26
+ if (limit != null && (!Number.isSafeInteger(limit) || limit < 0))
27
+ throw new Error('ACP file limit must be a non-negative integer');
28
+ const file = await openWorkspaceFile(cwd, path, false);
29
+ try {
30
+ const info = await file.stat();
31
+ if (info.size > MAX_TEXT_FILE_BYTES)
32
+ throw new Error('ACP text file is too large');
33
+ const text = await file.readFile('utf8');
34
+ const start = line == null ? 0 : line - 1;
35
+ return line != null || limit != null
36
+ ? text
37
+ .split('\n')
38
+ .slice(start, limit == null ? undefined : start + limit)
39
+ .join('\n')
40
+ : text;
41
+ }
42
+ finally {
43
+ await file.close();
44
+ }
45
+ }
46
+ export async function writeWorkspaceTextFile(cwd, path, content) {
47
+ if (Buffer.byteLength(content) > MAX_TEXT_FILE_BYTES)
48
+ throw new Error('ACP text file is too large');
49
+ const file = await openWorkspaceFile(cwd, path, true);
50
+ try {
51
+ // Validation happens against this exact open file descriptor before any
52
+ // existing content is truncated, so later path swaps cannot redirect data.
53
+ await file.truncate(0);
54
+ await file.writeFile(content, 'utf8');
55
+ }
56
+ finally {
57
+ await file.close();
58
+ }
59
+ }
60
+ async function openWorkspaceFile(cwd, path, forWrite) {
61
+ if (!isAbsolute(path))
62
+ throw new Error('ACP file path must be absolute');
63
+ const root = await realpath(cwd);
64
+ const candidate = resolve(path);
65
+ if (forWrite) {
66
+ const parent = await realpath(dirname(candidate));
67
+ assertContained(root, parent);
68
+ const entry = await lstat(candidate).catch((error) => {
69
+ if (error.code !== 'ENOENT')
70
+ throw error;
71
+ return undefined;
72
+ });
73
+ if (entry?.isSymbolicLink())
74
+ throw new Error('ACP file path could not be resolved');
75
+ }
76
+ const flags = forWrite
77
+ ? constants.O_WRONLY | constants.O_CREAT | (constants.O_NOFOLLOW ?? 0)
78
+ : constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0);
79
+ const file = await open(candidate, flags, 0o600);
80
+ try {
81
+ const opened = await file.stat();
82
+ if (!opened.isFile())
83
+ throw new Error('ACP file path is not a regular file');
84
+ // Resolve and compare after opening. If the path or an ancestor changed
85
+ // between lookup and open, either containment or file identity no longer
86
+ // matches. Operations after this point use only the verified descriptor.
87
+ const canonical = await realpath(candidate);
88
+ assertContained(root, canonical);
89
+ const current = await stat(canonical);
90
+ if (opened.dev !== current.dev || opened.ino !== current.ino)
91
+ throw new Error('ACP file path changed during validation');
92
+ return file;
93
+ }
94
+ catch (error) {
95
+ await file.close();
96
+ throw error;
97
+ }
98
+ }
99
+ function assertContained(root, candidate) {
100
+ const relation = relative(root, candidate);
101
+ if (relation === '..' || relation.startsWith('../') || isAbsolute(relation))
102
+ throw new Error('ACP file path leaves the workspace');
103
+ }
@@ -0,0 +1,119 @@
1
+ /*
2
+ * Licensed to the Apache Software Foundation (ASF) under one
3
+ * or more contributor license agreements. See the NOTICE file
4
+ * distributed with this work for additional information
5
+ * regarding copyright ownership. The ASF licenses this file
6
+ * to you under the Apache License, Version 2.0 (the
7
+ * "License"); you may not use this file except in compliance
8
+ * with the License. You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing,
13
+ * software distributed under the License is distributed on an
14
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
+ * KIND, either express or implied. See the License for the
16
+ * specific language governing permissions and limitations
17
+ * under the License.
18
+ */
19
+ import { spawn } from 'node:child_process';
20
+ import { Readable, Writable } from 'node:stream';
21
+ import { setTimeout as delay } from 'node:timers/promises';
22
+ import { client, ndJsonStream, } from '@agentclientprotocol/sdk';
23
+ import { terminateProcessTree } from '@maka/runtime/process-tree-terminator';
24
+ import { AcpRuntimeError } from './acp-errors.js';
25
+ const PROCESS_EXIT_TIMEOUT_MS = 2_000;
26
+ export function createAcpConnection(input) {
27
+ let child;
28
+ try {
29
+ child = spawn(input.executable, [...(input.args ?? [])], {
30
+ cwd: input.cwd,
31
+ env: input.env,
32
+ stdio: 'pipe',
33
+ detached: true,
34
+ shell: false,
35
+ });
36
+ }
37
+ catch {
38
+ throw new AcpRuntimeError('ACP executable is unavailable', 'acp_executable_unavailable');
39
+ }
40
+ let disposing = false;
41
+ let disposed = false;
42
+ let disposal;
43
+ let rejectFailure;
44
+ const failed = new Promise((_resolve, reject) => {
45
+ rejectFailure = reject;
46
+ });
47
+ void failed.catch(() => undefined);
48
+ const fail = () => {
49
+ if (!disposing)
50
+ rejectFailure(new Error('ACP connection failed'));
51
+ };
52
+ child.once('error', fail);
53
+ child.stdin.once('error', fail);
54
+ child.stderr.once('error', fail);
55
+ child.stderr.on('data', () => undefined);
56
+ child.once('close', fail);
57
+ const app = client({ name: input.clientName });
58
+ input.configureClient(app);
59
+ const connection = app.connect(ndJsonStream(Writable.toWeb(child.stdin), Readable.toWeb(child.stdout)));
60
+ void connection.closed.then(fail, fail);
61
+ return {
62
+ connection,
63
+ failed,
64
+ dispose() {
65
+ if (disposed)
66
+ return Promise.resolve();
67
+ if (disposal)
68
+ return disposal;
69
+ disposing = true;
70
+ disposal = terminate(child, connection).then(() => {
71
+ disposed = true;
72
+ }, (error) => {
73
+ disposal = undefined;
74
+ throw error;
75
+ });
76
+ return disposal;
77
+ },
78
+ };
79
+ }
80
+ async function terminate(child, connection) {
81
+ const pid = child.pid;
82
+ const alive = () => {
83
+ if (!pid)
84
+ return false;
85
+ if (process.platform === 'win32')
86
+ return child.exitCode === null && child.signalCode === null;
87
+ try {
88
+ process.kill(-pid, 0);
89
+ return true;
90
+ }
91
+ catch (error) {
92
+ return error.code !== 'ESRCH';
93
+ }
94
+ };
95
+ try {
96
+ if (pid) {
97
+ await terminateProcessTree({ pid, signal: 'SIGTERM', fallback: () => child.kill('SIGTERM') });
98
+ for (let elapsed = 0; elapsed < PROCESS_EXIT_TIMEOUT_MS && alive(); elapsed += 50)
99
+ await delay(50);
100
+ if (alive()) {
101
+ await terminateProcessTree({
102
+ pid,
103
+ signal: 'SIGKILL',
104
+ fallback: () => child.kill('SIGKILL'),
105
+ });
106
+ for (let elapsed = 0; elapsed < PROCESS_EXIT_TIMEOUT_MS && alive(); elapsed += 50)
107
+ await delay(50);
108
+ }
109
+ if (alive())
110
+ throw new Error('ACP process cleanup failed');
111
+ }
112
+ }
113
+ finally {
114
+ connection.close();
115
+ child.stdin.destroy();
116
+ child.stdout.destroy();
117
+ child.stderr.destroy();
118
+ }
119
+ }