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.
- package/dist/acp/session-event-mapper.js +1 -4
- package/dist/onboarding-catalog.js +20 -4
- package/dist/pi-transcript.js +0 -8
- package/dist/pi-tui-pickers.js +1 -1
- package/dist/pi-tui-runner.js +6 -4
- package/dist/runtime-host-capability-provider-command.js +25 -6
- package/dist/runtime-host-onboarding.js +5 -3
- package/dist/runtime-host-session-channel.js +9 -3
- package/dist/tui-mcp-control.js +37 -24
- package/native/runtime-host-windows-task-launcher/prebuilds/win32-x64/maka-runtime-host-task-launcher.exe +0 -0
- package/node_modules/@maka/acp-executor-plugin/dist/acp-errors.js +26 -0
- package/node_modules/@maka/acp-executor-plugin/dist/acp-filesystem.js +103 -0
- package/node_modules/@maka/acp-executor-plugin/dist/acp-process.js +119 -0
- package/node_modules/@maka/acp-executor-plugin/dist/acp-projection.js +146 -0
- package/node_modules/@maka/acp-executor-plugin/dist/index.js +653 -0
- package/node_modules/@maka/acp-executor-plugin/dist/plugin.mjs +24446 -0
- package/node_modules/@maka/acp-executor-plugin/package.json +15 -0
- package/node_modules/@maka/antigravity-acp-plugin/dist/index.js +88 -0
- package/node_modules/@maka/antigravity-acp-plugin/dist/model-presentation.js +53 -0
- package/node_modules/@maka/antigravity-acp-plugin/dist/plugin.mjs +115 -0
- package/node_modules/@maka/antigravity-acp-plugin/package.json +15 -0
- package/node_modules/@maka/core/dist/events.js +13 -0
- package/node_modules/@maka/core/dist/executor-catalog.js +106 -0
- package/node_modules/@maka/core/dist/llm-connections.js +2 -5
- package/node_modules/@maka/core/dist/mcp.js +4 -1
- package/node_modules/@maka/core/dist/model-facts.js +2 -4
- package/node_modules/@maka/core/dist/model-metadata.generated.js +345 -248
- package/node_modules/@maka/core/dist/model-metadata.js +28 -1
- package/node_modules/@maka/core/dist/model-thinking.js +17 -17
- package/node_modules/@maka/core/dist/model-web-search.js +7 -9
- package/node_modules/@maka/core/dist/models-dev-projection.js +1 -1
- package/node_modules/@maka/core/dist/onboarding.js +1 -4
- package/node_modules/@maka/core/dist/provider-registry.js +60 -86
- package/node_modules/@maka/core/dist/runtime-event-store.js +25 -0
- package/node_modules/@maka/core/dist/runtime-event.js +2 -2
- package/node_modules/@maka/core/dist/runtime-invocation.js +5 -0
- package/node_modules/@maka/core/dist/runtime-policy/connection-catalog-codec.js +28 -16
- package/node_modules/@maka/core/dist/runtime-policy/credential-vault-codec.js +6 -0
- package/node_modules/@maka/core/dist/runtime-policy/policy-codec.js +21 -0
- package/node_modules/@maka/core/dist/runtime-policy.js +1 -1
- package/node_modules/@maka/core/dist/session.js +25 -9
- package/node_modules/@maka/core/dist/settings.js +1 -0
- package/node_modules/@maka/core/dist/tool-ledger-scanner.js +365 -104
- package/node_modules/@maka/core/dist/transcript-search.js +1 -0
- package/node_modules/@maka/core/dist/turn-origin.js +14 -0
- package/node_modules/@maka/core/dist/usage-stats/types.js +1 -0
- package/node_modules/@maka/core/package.json +2 -0
- package/node_modules/@maka/eval/dist/harbor-maka-subject.js +3 -0
- package/node_modules/@maka/eval/dist/maka-subject.js +8 -1
- package/node_modules/@maka/mcp/dist/credential-coordinator.js +6 -1
- package/node_modules/@maka/mcp/dist/index.js +31 -5
- package/node_modules/@maka/mcp/dist/oauth.js +54 -6
- package/node_modules/@maka/runtime/dist/agent-run-inspect.js +1 -1
- package/node_modules/@maka/runtime/dist/ai-sdk-backend.js +1 -11
- package/node_modules/@maka/runtime/dist/ai-sdk-compaction.js +2 -12
- package/node_modules/@maka/runtime/dist/ai-sdk-turn.js +3 -71
- package/node_modules/@maka/runtime/dist/conversation-copy.js +58 -3
- package/node_modules/@maka/runtime/dist/local-web-fetch.js +23 -11
- package/node_modules/@maka/runtime/dist/mcp-tools.js +9 -2
- package/node_modules/@maka/runtime/dist/model-adapter.js +10 -5
- package/node_modules/@maka/runtime/dist/model-factory.js +33 -30
- package/node_modules/@maka/runtime/dist/model-fetcher.js +4 -1
- package/node_modules/@maka/runtime/dist/model-runtime.js +3 -9
- package/node_modules/@maka/runtime/dist/plugin-executor-backend.js +116 -3
- package/node_modules/@maka/runtime/dist/plugin-executor-service.js +212 -16
- package/node_modules/@maka/runtime/dist/read-page.js +11 -8
- package/node_modules/@maka/runtime/dist/recovery-resolver.js +47 -20
- package/node_modules/@maka/runtime/dist/runtime-event-backfill.js +1 -0
- package/node_modules/@maka/runtime/dist/runtime-ledger-repair.js +2 -2
- package/node_modules/@maka/runtime/dist/scheduled-task-tools.js +39 -8
- package/node_modules/@maka/runtime/dist/session-event-runtime-mapper.js +3 -0
- package/node_modules/@maka/runtime/dist/session-manager.js +182 -127
- package/node_modules/@maka/runtime/dist/shell-run-manager.js +3 -0
- package/node_modules/@maka/runtime/dist/stream-graph-coordinator.js +29 -4
- package/node_modules/@maka/runtime/dist/telemetry/model-pricing.generated.js +141 -134
- package/node_modules/@maka/runtime/dist/tool-result-archive-transition.js +3 -3
- package/node_modules/@maka/runtime/dist/workers/filesystem-worker.js +7 -6
- package/node_modules/@maka/runtime-host/dist/adapter/session-projector.js +28 -62
- package/node_modules/@maka/runtime-host/dist/candidate-entry.js +8 -5
- package/node_modules/@maka/runtime-host/dist/client/hosted-execution-target.js +7 -1
- package/node_modules/@maka/runtime-host/dist/client/index.js +1 -1
- package/node_modules/@maka/runtime-host/dist/client/reconnect-lifecycle.js +8 -1
- package/node_modules/@maka/runtime-host/dist/client/session-catalog-summary.js +6 -1
- package/node_modules/@maka/runtime-host/dist/client/session-subscription.js +3 -0
- package/node_modules/@maka/runtime-host/dist/composition-identity.js +19 -0
- package/node_modules/@maka/runtime-host/dist/protocol/connection-effects.js +6 -3
- package/node_modules/@maka/runtime-host/dist/protocol/index.js +14 -2
- package/node_modules/@maka/runtime-host/dist/protocol/operations.js +4 -1
- package/node_modules/@maka/runtime-host/dist/protocol/plugin-platform.js +34 -0
- package/node_modules/@maka/runtime-host/dist/protocol/prompt-suggestions.js +61 -0
- package/node_modules/@maka/runtime-host/dist/protocol/runtime-policy.js +4 -1
- package/node_modules/@maka/runtime-host/dist/protocol/session-catalog.js +40 -2
- package/node_modules/@maka/runtime-host/dist/protocol/session-continuity.js +2 -1
- package/node_modules/@maka/runtime-host/dist/server/builtin-external-agent-plugins.js +188 -0
- package/node_modules/@maka/runtime-host/dist/server/candidate.js +12 -7
- package/node_modules/@maka/runtime-host/dist/server/client-capability-recovery.js +44 -46
- package/node_modules/@maka/runtime-host/dist/server/connection-effect-coordinator.js +3 -0
- package/node_modules/@maka/runtime-host/dist/server/context-coordinator.js +2 -0
- package/node_modules/@maka/runtime-host/dist/server/execution-composition-factory.js +1 -1
- package/node_modules/@maka/runtime-host/dist/server/execution-composition.js +212 -24
- package/node_modules/@maka/runtime-host/dist/server/execution-model-authority.js +41 -2
- package/node_modules/@maka/runtime-host/dist/server/extension-bundle.js +4 -0
- package/node_modules/@maka/runtime-host/dist/server/generated-text.js +39 -0
- package/node_modules/@maka/runtime-host/dist/server/host-composition-source.js +47 -0
- package/node_modules/@maka/runtime-host/dist/server/host-composition.js +1 -29
- package/node_modules/@maka/runtime-host/dist/server/host-kernel.js +2 -9
- package/node_modules/@maka/runtime-host/dist/server/hosted-execution-recovery.js +75 -9
- package/node_modules/@maka/runtime-host/dist/server/hosted-execution-tool-profile.js +12 -0
- package/node_modules/@maka/runtime-host/dist/server/interaction-coordinator.js +45 -0
- package/node_modules/@maka/runtime-host/dist/server/jev-routing-model.js +157 -0
- package/node_modules/@maka/runtime-host/dist/server/listener-set.js +2 -2
- package/node_modules/@maka/runtime-host/dist/server/message-coordinator.js +4 -0
- package/node_modules/@maka/runtime-host/dist/server/plugin-platform-coordinator.js +11 -1
- package/node_modules/@maka/runtime-host/dist/server/process-lifecycle.js +1 -1
- package/node_modules/@maka/runtime-host/dist/server/process-termination-error.js +27 -0
- package/node_modules/@maka/runtime-host/dist/server/prompt-suggestion.js +179 -0
- package/node_modules/@maka/runtime-host/dist/server/root-turn-coordinator.js +93 -14
- package/node_modules/@maka/runtime-host/dist/server/runtime-policy-coordinator.js +2 -0
- package/node_modules/@maka/runtime-host/dist/server/session-catalog-coordinator.js +99 -12
- package/node_modules/@maka/runtime-host/dist/server/session-continuity-coordinator.js +48 -16
- package/node_modules/@maka/runtime-host/dist/server/session-effect-coordinator.js +12 -6
- package/node_modules/@maka/runtime-host/dist/server/session-title.js +3 -16
- package/node_modules/@maka/runtime-host/dist/server/session-transcript-reader.js +71 -3
- package/node_modules/@maka/runtime-host/dist/server/shared-session-transcript.js +3 -0
- package/node_modules/@maka/runtime-host/dist/server/workhub-coordination-action-gate.js +6 -13
- package/node_modules/@maka/runtime-host/dist/server/workhub-coordination-coordinator.js +39 -11
- package/node_modules/@maka/runtime-host/dist/server/workhub-result-coordinator.js +173 -0
- package/node_modules/@maka/runtime-host/dist/server/workhub-result-runtime.js +293 -0
- package/node_modules/@maka/runtime-host/package.json +2 -0
- package/node_modules/@maka/storage/dist/agent-run-store-contract.js +8 -0
- package/node_modules/@maka/storage/dist/config-transfer.js +14 -8
- package/node_modules/@maka/storage/dist/execution-stores.js +19 -6
- package/node_modules/@maka/storage/dist/interaction-store.js +13 -0
- package/node_modules/@maka/storage/dist/mcp-config-store.js +91 -2
- package/node_modules/@maka/storage/dist/operational-state-store.js +15 -1
- package/node_modules/@maka/storage/dist/plan-authority.js +2 -0
- package/node_modules/@maka/storage/dist/plan-store.js +30 -0
- package/node_modules/@maka/storage/dist/runtime-event-persistence.js +1 -0
- package/node_modules/@maka/storage/dist/runtime-policy/connection-catalog-document.js +28 -17
- package/node_modules/@maka/storage/dist/runtime-policy/coordinator.js +9 -3
- package/node_modules/@maka/storage/dist/runtime-policy/credential-vault-document.js +4 -1
- package/node_modules/@maka/storage/dist/runtime-policy/legacy-custom-connection.js +65 -0
- package/node_modules/@maka/storage/dist/runtime-policy/onboarding-transaction.js +10 -5
- package/node_modules/@maka/storage/dist/runtime-policy/policy-document.js +2 -0
- package/node_modules/@maka/storage/dist/session-bundle-policy.js +11 -0
- package/node_modules/@maka/storage/dist/session-store-values.js +16 -4
- package/node_modules/@maka/storage/dist/session-store.js +29 -6
- package/node_modules/@maka/storage/dist/shell-run-authority.js +1 -0
- package/node_modules/@maka/storage/dist/shell-run-store.js +15 -0
- package/node_modules/@maka/storage/dist/sqlite-core-execution-schema.js +3 -0
- package/node_modules/@maka/storage/dist/sqlite-runtime-schema.js +74 -1
- package/node_modules/@maka/storage/dist/sqlite-runtime-store.js +849 -91
- package/node_modules/@maka/storage/dist/sqlite-session-metadata-store.js +21 -3
- package/node_modules/@maka/storage/dist/usage-screen.js +138 -108
- 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
|
|
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
|
-
}).
|
|
33
|
+
}).flatMap((providerType) => {
|
|
34
34
|
const definition = PROVIDER_REGISTRY[providerType];
|
|
35
|
-
|
|
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
|
+
}
|
package/dist/pi-transcript.js
CHANGED
|
@@ -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': {
|
package/dist/pi-tui-pickers.js
CHANGED
|
@@ -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);
|
package/dist/pi-tui-runner.js
CHANGED
|
@@ -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:
|
|
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
|
-
|
|
2292
|
-
|
|
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
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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:
|
|
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(
|
|
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;
|
package/dist/tui-mcp-control.js
CHANGED
|
@@ -17,10 +17,10 @@
|
|
|
17
17
|
* under the License.
|
|
18
18
|
*/
|
|
19
19
|
import { createHash, randomUUID } from 'node:crypto';
|
|
20
|
-
import { MCP_CONFIG_VERSION,
|
|
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,
|
|
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
|
|
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
|
-
|
|
286
|
+
return prepared.next;
|
|
287
|
+
}, async (serverId, previous) => {
|
|
275
288
|
try {
|
|
276
|
-
|
|
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
|
-
|
|
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();
|
|
Binary file
|
|
@@ -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
|
+
}
|