@herbertgao/pi-extensions 2026.9.11 → 2026.9.12
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/node_modules/@herbertgao/pi-cc-extensions/package.json +1 -1
- package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +2 -2
- package/node_modules/pi-mcp-adapter/CHANGELOG.md +62 -0
- package/node_modules/pi-mcp-adapter/README.md +75 -7
- package/node_modules/pi-mcp-adapter/bearer-command-resolver.ts +193 -0
- package/node_modules/pi-mcp-adapter/cli.js +56 -5
- package/node_modules/pi-mcp-adapter/commands.ts +96 -5
- package/node_modules/pi-mcp-adapter/config.ts +73 -6
- package/node_modules/pi-mcp-adapter/direct-tool-surface.ts +22 -28
- package/node_modules/pi-mcp-adapter/direct-tools.ts +13 -5
- package/node_modules/pi-mcp-adapter/dist/abort.d.ts +2 -0
- package/node_modules/pi-mcp-adapter/dist/abort.js +36 -0
- package/node_modules/pi-mcp-adapter/dist/abort.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/bearer-command-resolver.d.ts +6 -0
- package/node_modules/pi-mcp-adapter/dist/bearer-command-resolver.js +194 -0
- package/node_modules/pi-mcp-adapter/dist/bearer-command-resolver.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/config.d.ts +5 -0
- package/node_modules/pi-mcp-adapter/dist/config.js +75 -6
- package/node_modules/pi-mcp-adapter/dist/config.js.map +1 -1
- package/node_modules/pi-mcp-adapter/dist/consent-manager.d.ts +18 -0
- package/node_modules/pi-mcp-adapter/dist/consent-manager.js +88 -0
- package/node_modules/pi-mcp-adapter/dist/consent-manager.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/elicitation-handler.d.ts +17 -0
- package/node_modules/pi-mcp-adapter/dist/elicitation-handler.js +316 -0
- package/node_modules/pi-mcp-adapter/dist/elicitation-handler.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/errors.d.ts +131 -0
- package/node_modules/pi-mcp-adapter/dist/errors.js +278 -0
- package/node_modules/pi-mcp-adapter/dist/errors.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/http-ca.d.ts +8 -0
- package/node_modules/pi-mcp-adapter/dist/http-ca.js +88 -0
- package/node_modules/pi-mcp-adapter/dist/http-ca.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/jev-client.d.ts +15 -0
- package/node_modules/pi-mcp-adapter/dist/jev-client.js +340 -0
- package/node_modules/pi-mcp-adapter/dist/jev-client.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/jev-contracts.d.ts +77 -0
- package/node_modules/pi-mcp-adapter/dist/jev-contracts.js +2 -0
- package/node_modules/pi-mcp-adapter/dist/jev-contracts.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/jev-key-store.d.ts +22 -0
- package/node_modules/pi-mcp-adapter/dist/jev-key-store.js +85 -0
- package/node_modules/pi-mcp-adapter/dist/jev-key-store.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/json-schema-validator.d.ts +2 -0
- package/node_modules/pi-mcp-adapter/dist/json-schema-validator.js +55 -0
- package/node_modules/pi-mcp-adapter/dist/json-schema-validator.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/lifecycle.d.ts +56 -0
- package/node_modules/pi-mcp-adapter/dist/lifecycle.js +410 -0
- package/node_modules/pi-mcp-adapter/dist/lifecycle.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/logger.d.ts +51 -0
- package/node_modules/pi-mcp-adapter/dist/logger.js +131 -0
- package/node_modules/pi-mcp-adapter/dist/logger.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-auth-fetch.d.ts +26 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-auth-fetch.js +103 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-auth-fetch.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-auth-flow.d.ts +118 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-auth-flow.js +1098 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-auth-flow.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-auth.d.ts +168 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-auth.js +1117 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-auth.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-bearer-store.js +7 -140
- package/node_modules/pi-mcp-adapter/dist/mcp-bearer-store.js.map +1 -1
- package/node_modules/pi-mcp-adapter/dist/mcp-callback-server.d.ts +53 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-callback-server.js +441 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-callback-server.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-oauth-provider.d.ts +148 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-oauth-provider.js +689 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-oauth-provider.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-probe.d.ts +6 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-probe.js +166 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-probe.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-tasks.d.ts +102 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-tasks.js +369 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-tasks.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-trace.d.ts +95 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-trace.js +242 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-trace.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/metadata-cache.js +2 -11
- package/node_modules/pi-mcp-adapter/dist/metadata-cache.js.map +1 -1
- package/node_modules/pi-mcp-adapter/dist/npx-resolver.d.ts +6 -0
- package/node_modules/pi-mcp-adapter/dist/npx-resolver.js +505 -0
- package/node_modules/pi-mcp-adapter/dist/npx-resolver.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/request-headers-command.d.ts +10 -0
- package/node_modules/pi-mcp-adapter/dist/request-headers-command.js +312 -0
- package/node_modules/pi-mcp-adapter/dist/request-headers-command.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/runtime-owner.d.ts +13 -0
- package/node_modules/pi-mcp-adapter/dist/runtime-owner.js +97 -0
- package/node_modules/pi-mcp-adapter/dist/runtime-owner.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/sampling-handler.d.ts +17 -0
- package/node_modules/pi-mcp-adapter/dist/sampling-handler.js +203 -0
- package/node_modules/pi-mcp-adapter/dist/sampling-handler.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/secure-keyring.d.ts +11 -0
- package/node_modules/pi-mcp-adapter/dist/secure-keyring.js +172 -0
- package/node_modules/pi-mcp-adapter/dist/secure-keyring.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/server-manager.d.ts +157 -0
- package/node_modules/pi-mcp-adapter/dist/server-manager.js +1743 -0
- package/node_modules/pi-mcp-adapter/dist/server-manager.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/session-approvals.d.ts +34 -0
- package/node_modules/pi-mcp-adapter/dist/session-approvals.js +140 -0
- package/node_modules/pi-mcp-adapter/dist/session-approvals.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/session-recovery.d.ts +26 -0
- package/node_modules/pi-mcp-adapter/dist/session-recovery.js +142 -0
- package/node_modules/pi-mcp-adapter/dist/session-recovery.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/state.d.ts +73 -0
- package/node_modules/pi-mcp-adapter/dist/state.js +2 -0
- package/node_modules/pi-mcp-adapter/dist/state.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/types.d.ts +36 -0
- package/node_modules/pi-mcp-adapter/dist/types.js +18 -0
- package/node_modules/pi-mcp-adapter/dist/types.js.map +1 -1
- package/node_modules/pi-mcp-adapter/dist/ui-resource-handler.d.ts +17 -0
- package/node_modules/pi-mcp-adapter/dist/ui-resource-handler.js +219 -0
- package/node_modules/pi-mcp-adapter/dist/ui-resource-handler.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/unix-socket-transport.d.ts +15 -0
- package/node_modules/pi-mcp-adapter/dist/unix-socket-transport.js +85 -0
- package/node_modules/pi-mcp-adapter/dist/unix-socket-transport.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/utils.d.ts +1 -0
- package/node_modules/pi-mcp-adapter/dist/utils.js +13 -0
- package/node_modules/pi-mcp-adapter/dist/utils.js.map +1 -1
- package/node_modules/pi-mcp-adapter/elicitation-handler.ts +29 -19
- package/node_modules/pi-mcp-adapter/examples/jev-accessibility-loop.mjs +85 -0
- package/node_modules/pi-mcp-adapter/examples/jev-semantic-filter.mjs +34 -0
- package/node_modules/pi-mcp-adapter/index.ts +129 -6
- package/node_modules/pi-mcp-adapter/init.ts +6 -19
- package/node_modules/pi-mcp-adapter/jev-client.ts +273 -0
- package/node_modules/pi-mcp-adapter/jev-contracts.ts +53 -0
- package/node_modules/pi-mcp-adapter/jev-key-store.ts +79 -0
- package/node_modules/pi-mcp-adapter/lifecycle.ts +14 -3
- package/node_modules/pi-mcp-adapter/mcp-bearer-store.ts +7 -142
- package/node_modules/pi-mcp-adapter/mcp-code.ts +119 -11
- package/node_modules/pi-mcp-adapter/mcp-output-guard.ts +4 -0
- package/node_modules/pi-mcp-adapter/mcp-references.ts +5 -3
- package/node_modules/pi-mcp-adapter/mcp-script-worker.mjs +5 -0
- package/node_modules/pi-mcp-adapter/mcp-tasks.ts +467 -0
- package/node_modules/pi-mcp-adapter/metadata-cache.ts +2 -13
- package/node_modules/pi-mcp-adapter/namespace-tools.ts +1 -1
- package/node_modules/pi-mcp-adapter/package.json +17 -9
- package/node_modules/pi-mcp-adapter/proxy-modes.ts +319 -166
- package/node_modules/pi-mcp-adapter/request-headers-command.ts +6 -3
- package/node_modules/pi-mcp-adapter/search-ranking.ts +38 -7
- package/node_modules/pi-mcp-adapter/secure-keyring.ts +170 -0
- package/node_modules/pi-mcp-adapter/semantic-search.ts +186 -0
- package/node_modules/pi-mcp-adapter/server-manager.ts +293 -50
- package/node_modules/pi-mcp-adapter/session-approvals.ts +14 -0
- package/node_modules/pi-mcp-adapter/skills/mcp-scripting/SKILL.md +2 -0
- package/node_modules/pi-mcp-adapter/state.ts +3 -1
- package/node_modules/pi-mcp-adapter/tool-approval.ts +26 -4
- package/node_modules/pi-mcp-adapter/tool-metadata.ts +6 -14
- package/node_modules/pi-mcp-adapter/tool-registrar.ts +8 -6
- package/node_modules/pi-mcp-adapter/tool-result-renderer.ts +4 -1
- package/node_modules/pi-mcp-adapter/types.ts +53 -0
- package/node_modules/pi-mcp-adapter/ui-server.ts +17 -9
- package/node_modules/pi-mcp-adapter/utils.ts +16 -0
- package/package.json +7 -6
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
1
2
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
3
|
import type { OverlayHandle } from "@earendil-works/pi-tui";
|
|
3
4
|
import type { McpExtensionState } from "./state.ts";
|
|
@@ -14,8 +15,10 @@ import {
|
|
|
14
15
|
previewSharedServerEntry,
|
|
15
16
|
previewStarterSharedConfig,
|
|
16
17
|
writeDirectToolsConfig,
|
|
18
|
+
writeJevSemanticSearchConfig,
|
|
17
19
|
writeProjectServerDisabledOverride,
|
|
18
20
|
writeSharedServerEntry,
|
|
21
|
+
writeSharedConfigText,
|
|
19
22
|
writeStarterSharedConfig,
|
|
20
23
|
} from "./config.ts";
|
|
21
24
|
import { markKeepAliveAfterConnect, notifyToolMetadataUpdated, updateMetadataCache, updateStatusBar, getFailureAgeSeconds, getFailureMessage, clearFailure, recordFailure } from "./init.ts";
|
|
@@ -26,8 +29,9 @@ import { supportsOAuth, authenticate, removeAuth, type McpOAuthRuntime } from ".
|
|
|
26
29
|
import { getAuthStorageOptions, inspectAuthForUrl } from "./mcp-auth.ts";
|
|
27
30
|
import { inspectBearerTokenForUrl, removeBearerToken } from "./mcp-bearer-store.ts";
|
|
28
31
|
import { loadOnboardingState, markSetupCompleted as persistSetupCompleted, markSharedConfigHintShown } from "./onboarding-state.ts";
|
|
29
|
-
import { openPath, resolveServerUrl, sanitizeTerminalText } from "./utils.ts";
|
|
32
|
+
import { formatTerminalError, openPath, resolveServerUrl, sanitizeTerminalText } from "./utils.ts";
|
|
30
33
|
import { isAbortError } from "./runtime-owner.ts";
|
|
34
|
+
import { resolveJevCredential } from "./jev-key-store.ts";
|
|
31
35
|
|
|
32
36
|
function terminalHyperlink(label: string, url: string): string {
|
|
33
37
|
return `\u001B]8;;${sanitizeTerminalText(url)}\u001B\\${sanitizeTerminalText(label)}\u001B]8;;\u001B\\`;
|
|
@@ -46,6 +50,93 @@ function canRenderPanel(ctx: ExtensionContext): boolean {
|
|
|
46
50
|
return ctx.hasUI && ctx.mode === "tui";
|
|
47
51
|
}
|
|
48
52
|
|
|
53
|
+
export async function editSharedConfig(ctx: ExtensionContext, target: SharedConfigTarget): Promise<boolean> {
|
|
54
|
+
if (!ctx.hasUI) return false;
|
|
55
|
+
const path = getSharedConfigPath(target, ctx.cwd);
|
|
56
|
+
const before = existsSync(path) ? readFileSync(path, "utf8") : '{\n "mcpServers": {}\n}\n';
|
|
57
|
+
const after = await ctx.ui.editor(`Edit ${path} (Ctrl+G opens $EDITOR)`, before);
|
|
58
|
+
if (after === undefined || after === before) return false;
|
|
59
|
+
try {
|
|
60
|
+
writeSharedConfigText(path, after);
|
|
61
|
+
} catch (error) {
|
|
62
|
+
ctx.ui.notify(`MCP: not saved: ${formatTerminalError(error)}`, "error");
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export async function setupJevSemanticSearch(
|
|
69
|
+
state: McpExtensionState,
|
|
70
|
+
ctx: ExtensionContext,
|
|
71
|
+
configOverridePath?: string,
|
|
72
|
+
): Promise<boolean> {
|
|
73
|
+
if (!ctx.hasUI) return false;
|
|
74
|
+
const credential = resolveJevCredential();
|
|
75
|
+
if (credential.status !== "present") {
|
|
76
|
+
const detail = credential.status === "unavailable" ? ` ${credential.message}` : "";
|
|
77
|
+
ctx.ui.notify(
|
|
78
|
+
`Jev needs a TypeSafe API key.${detail}\nRun \`pi-mcp-adapter key set typesafe\` in a terminal, then run \`/mcp jev setup\` again.`,
|
|
79
|
+
"error",
|
|
80
|
+
);
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const servers = Object.keys(state.config.mcpServers)
|
|
85
|
+
.filter((name) => !isServerDisabled(state.config.mcpServers[name]))
|
|
86
|
+
.sort((a, b) => a.localeCompare(b));
|
|
87
|
+
if (servers.length === 0) {
|
|
88
|
+
ctx.ui.notify("Enable or add an MCP server before setting up Jev semantic search.", "error");
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
const configuredJev = state.config.settings?.jev;
|
|
92
|
+
const payloadDisclosure = configuredJev && configuredJev.scriptEvaluation
|
|
93
|
+
? " Allowed servers can also be sources for script evaluations, which may send state and MCP results."
|
|
94
|
+
: " Semantic search does not send tool results.";
|
|
95
|
+
|
|
96
|
+
const choice = await ctx.ui.select("Configure Jev semantic search", [
|
|
97
|
+
`Use all ${servers.length} enabled servers (default)`,
|
|
98
|
+
"Restrict to selected servers",
|
|
99
|
+
"Cancel",
|
|
100
|
+
]);
|
|
101
|
+
if (!choice || choice === "Cancel") return false;
|
|
102
|
+
|
|
103
|
+
let allowedServers: string[];
|
|
104
|
+
if (choice.startsWith("Use all ")) {
|
|
105
|
+
const confirmed = await ctx.ui.confirm(
|
|
106
|
+
"Share MCP tool metadata with Jev?",
|
|
107
|
+
`Semantic searches send the query text, server names, tool paths, tool names, and descriptions from ${servers.length} servers to TypeSafe.${payloadDisclosure}`,
|
|
108
|
+
);
|
|
109
|
+
if (!confirmed) return false;
|
|
110
|
+
allowedServers = servers;
|
|
111
|
+
} else {
|
|
112
|
+
allowedServers = [];
|
|
113
|
+
for (const server of servers) {
|
|
114
|
+
if (await ctx.ui.confirm(
|
|
115
|
+
`Allow ${server}?`,
|
|
116
|
+
`Semantic searches send the query text, server name, tool paths, tool names, and descriptions to TypeSafe.${payloadDisclosure}`,
|
|
117
|
+
)) allowedServers.push(server);
|
|
118
|
+
}
|
|
119
|
+
if (allowedServers.length === 0) {
|
|
120
|
+
ctx.ui.notify("Jev setup cancelled because no servers were allowed.", "info");
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
try {
|
|
126
|
+
const result = writeJevSemanticSearchConfig(configOverridePath, ctx.cwd, allowedServers, state.config.settings?.jev);
|
|
127
|
+
ctx.ui.notify(
|
|
128
|
+
result.changed
|
|
129
|
+
? `Jev semantic search configured for ${allowedServers.length} server${allowedServers.length === 1 ? "" : "s"}. Reloading Pi…`
|
|
130
|
+
: "Jev semantic search is already configured for those servers.",
|
|
131
|
+
"info",
|
|
132
|
+
);
|
|
133
|
+
return result.changed;
|
|
134
|
+
} catch (error) {
|
|
135
|
+
ctx.ui.notify(`Jev setup failed: ${formatTerminalError(error)}`, "error");
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
49
140
|
export async function showStatus(state: McpExtensionState, ctx: ExtensionContext): Promise<void> {
|
|
50
141
|
if (!ctx.hasUI) return;
|
|
51
142
|
|
|
@@ -194,11 +285,11 @@ export async function reconnectServer(
|
|
|
194
285
|
}
|
|
195
286
|
|
|
196
287
|
try {
|
|
197
|
-
await state.manager.close(name);
|
|
198
288
|
state.owner?.throwIfInactive();
|
|
199
|
-
const
|
|
200
|
-
|
|
201
|
-
|
|
289
|
+
const current = state.manager.getConnection(name);
|
|
290
|
+
const connection = current
|
|
291
|
+
? await state.manager.reconnect(name, definition, current, signal)
|
|
292
|
+
: await state.manager.connect(name, definition, signal);
|
|
202
293
|
state.owner?.throwIfInactive();
|
|
203
294
|
if (connection.status === "needs-auth") {
|
|
204
295
|
if (ui) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// config.ts - Config loading with import support
|
|
2
|
-
import { existsSync, readFileSync, realpathSync, statSync, writeFileSync, mkdirSync, renameSync } from "node:fs";
|
|
2
|
+
import { chmodSync, existsSync, readFileSync, realpathSync, rmSync, statSync, writeFileSync, mkdirSync, renameSync } from "node:fs";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
5
5
|
import { parse as parseToml } from "smol-toml";
|
|
@@ -9,6 +9,7 @@ import { getAgentPluginSummaries, loadAgentPluginConfigs, type AgentPluginSummar
|
|
|
9
9
|
import { cloneBuiltInAgentPluginEntry, isBuiltInAgentPlugin, mergeBuiltInAgentPluginEntries } from "./agent-plugin-provenance.ts";
|
|
10
10
|
import { loadClaudePluginBundles } from "./claude-plugin-loader.ts";
|
|
11
11
|
import { loadPackageMcpConfigs } from "./package-mcp-loader.ts";
|
|
12
|
+
import { validateJevSettings } from "./jev-client.ts";
|
|
12
13
|
import { formatServerNamespace, isServerDisabled, type ClaudePluginConfig, type HostConfigDiscovery, type McpConfig, type ServerEntry, type McpSettings, type ImportKind, type ServerProvenance } from "./types.ts";
|
|
13
14
|
import { parseJsonWithComments, toStringRecord } from "./utils.ts";
|
|
14
15
|
|
|
@@ -846,11 +847,21 @@ function validateConfig(raw: unknown): McpConfig {
|
|
|
846
847
|
return {
|
|
847
848
|
mcpServers: toServerEntries(raw.mcpServers ?? raw["mcp-servers"]),
|
|
848
849
|
...(Array.isArray(raw.imports) ? { imports: raw.imports as ImportKind[] } : {}),
|
|
849
|
-
...(raw.settings !== undefined ? { settings: raw.settings
|
|
850
|
+
...(raw.settings !== undefined ? { settings: parseSettings(raw.settings) } : {}),
|
|
850
851
|
...(raw.claudePlugins !== undefined ? { claudePlugins: parseClaudePlugins(raw.claudePlugins) } : {}),
|
|
851
852
|
};
|
|
852
853
|
}
|
|
853
854
|
|
|
855
|
+
function parseSettings(value: unknown): McpSettings {
|
|
856
|
+
if (!isRecord(value)) throw new Error("settings must be an object");
|
|
857
|
+
const settings = { ...value } as McpSettings;
|
|
858
|
+
if (value.jev !== undefined) {
|
|
859
|
+
validateJevSettings(value.jev);
|
|
860
|
+
settings.jev = value.jev as NonNullable<McpSettings["jev"]>;
|
|
861
|
+
}
|
|
862
|
+
return settings;
|
|
863
|
+
}
|
|
864
|
+
|
|
854
865
|
function parseClaudePlugins(value: unknown): ClaudePluginConfig[] {
|
|
855
866
|
if (!Array.isArray(value)) {
|
|
856
867
|
console.warn("Invalid claudePlugins config: expected an array");
|
|
@@ -1139,11 +1150,67 @@ function readRawConfigObject(filePath: string): Record<string, unknown> {
|
|
|
1139
1150
|
}
|
|
1140
1151
|
}
|
|
1141
1152
|
|
|
1153
|
+
function writeConfigText(writePath: string, text: string): void {
|
|
1154
|
+
let mode: number | undefined;
|
|
1155
|
+
try {
|
|
1156
|
+
writePath = realpathSync(writePath);
|
|
1157
|
+
mode = statSync(writePath).mode & 0o777;
|
|
1158
|
+
} catch {}
|
|
1159
|
+
mkdirSync(dirname(writePath), { recursive: true });
|
|
1160
|
+
const tmpPath = `${writePath}.${process.pid}.tmp`;
|
|
1161
|
+
rmSync(tmpPath, { force: true });
|
|
1162
|
+
try {
|
|
1163
|
+
writeFileSync(tmpPath, text, mode === undefined ? "utf-8" : { encoding: "utf-8", mode });
|
|
1164
|
+
if (mode !== undefined) chmodSync(tmpPath, mode);
|
|
1165
|
+
renameSync(tmpPath, writePath);
|
|
1166
|
+
} catch (error) {
|
|
1167
|
+
try { rmSync(tmpPath, { force: true }); } catch {}
|
|
1168
|
+
throw error;
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1142
1172
|
function writeRawConfigObject(filePath: string, raw: Record<string, unknown>): void {
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1173
|
+
writeConfigText(filePath, `${JSON.stringify(raw, null, 2)}\n`);
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
export function writeSharedConfigText(filePath: string, text: string): void {
|
|
1177
|
+
if (!isRecord(parseJsonWithComments(text))) throw new Error("top-level value must be an object");
|
|
1178
|
+
writeConfigText(filePath, text);
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
export function writeJevSemanticSearchConfig(
|
|
1182
|
+
overridePath: string | undefined,
|
|
1183
|
+
cwd: string,
|
|
1184
|
+
allowedServers: string[],
|
|
1185
|
+
effectiveJev?: unknown,
|
|
1186
|
+
): { path: string; changed: boolean } {
|
|
1187
|
+
const filePath = overridePath ? getPiGlobalConfigPath(overridePath) : getProjectPiConfigPath(cwd);
|
|
1188
|
+
let raw: Record<string, unknown> = {};
|
|
1189
|
+
if (existsSync(filePath)) {
|
|
1190
|
+
try {
|
|
1191
|
+
const parsed = parseJsonWithComments(readFileSync(filePath, "utf8"));
|
|
1192
|
+
if (!isRecord(parsed)) throw new Error("top-level value must be an object");
|
|
1193
|
+
raw = parsed;
|
|
1194
|
+
} catch (error) {
|
|
1195
|
+
throw new Error(`Failed to update Jev settings at ${filePath}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
if (raw.settings !== undefined && !isRecord(raw.settings)) {
|
|
1199
|
+
throw new Error(`Failed to update Jev settings at ${filePath}: settings must be an object`);
|
|
1200
|
+
}
|
|
1201
|
+
const settings = raw.settings as Record<string, unknown> | undefined;
|
|
1202
|
+
const currentJev = settings?.jev;
|
|
1203
|
+
if (currentJev !== undefined && currentJev !== false && !isRecord(currentJev)) {
|
|
1204
|
+
throw new Error(`Failed to update Jev settings at ${filePath}: settings.jev must be an object or false`);
|
|
1205
|
+
}
|
|
1206
|
+
const jev = isRecord(effectiveJev) ? effectiveJev : isRecord(currentJev) ? currentJev : {};
|
|
1207
|
+
const nextServers = [...new Set(allowedServers)].sort((a, b) => a.localeCompare(b));
|
|
1208
|
+
const nextJev = { ...jev, semanticSearch: true, allowedServers: nextServers };
|
|
1209
|
+
validateJevSettings(nextJev);
|
|
1210
|
+
if (isRecord(currentJev) && JSON.stringify(currentJev) === JSON.stringify(nextJev)) return { path: filePath, changed: false };
|
|
1211
|
+
raw.settings = { ...settings, jev: nextJev };
|
|
1212
|
+
writeRawConfigObject(filePath, raw);
|
|
1213
|
+
return { path: filePath, changed: true };
|
|
1147
1214
|
}
|
|
1148
1215
|
|
|
1149
1216
|
function getServersObject(raw: Record<string, unknown>): Record<string, ServerEntry> {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Check, Errors } from "typebox/value";
|
|
2
2
|
import type { DirectToolSpec, McpConfig, ToolPrefix } from "./types.ts";
|
|
3
|
-
import { createToolSelectorCandidateIndex, formatToolName, getToolNameCandidates, isServerDisabled, isToolAllowed, resolveToolPrefix } from "./types.ts";
|
|
3
|
+
import { createToolSelectorCandidateIndex, formatToolName, getToolNameCandidates, isServerDisabled, isToolAllowed, resolveToolPrefix, resolveUniqueNameOwnership } from "./types.ts";
|
|
4
4
|
import type { MetadataCache } from "./metadata-cache.ts";
|
|
5
5
|
import { isServerCacheValid, parseDirectToolSelectors } from "./metadata-cache.ts";
|
|
6
6
|
export { getMissingConfiguredDirectToolServers } from "./metadata-cache.ts";
|
|
@@ -10,6 +10,13 @@ import { resourceNameToToolName } from "./resource-tools.ts";
|
|
|
10
10
|
const BUILTIN_NAMES = new Set(["read", "bash", "edit", "write", "grep", "find", "ls", "mcp"]);
|
|
11
11
|
export const DIRECT_TOOLS_ADVISORY_THRESHOLD = 75;
|
|
12
12
|
|
|
13
|
+
export function getLargeDirectToolsAdvisory(config: McpConfig, specs: readonly DirectToolSpec[]): string | undefined {
|
|
14
|
+
if (config.settings?.warnOnLargeDirectTools === false) return undefined;
|
|
15
|
+
const eagerCount = specs.filter((spec) => !spec.lazy).length;
|
|
16
|
+
if (eagerCount < DIRECT_TOOLS_ADVISORY_THRESHOLD) return undefined;
|
|
17
|
+
return `MCP: ${eagerCount} direct tools resolved. Each direct tool adds prompt context; README guidance recommends targeted sets of 5-20 tools and using the proxy or an explicit string[] when 75+ direct tools would be registered. Set settings.warnOnLargeDirectTools to false to hide this advisory.`;
|
|
18
|
+
}
|
|
19
|
+
|
|
13
20
|
/**
|
|
14
21
|
* Recover one model-emitted JSON layer for schema-declared object and array
|
|
15
22
|
* properties, then validate the complete input against the same schema.
|
|
@@ -28,14 +35,14 @@ export function prepareDirectToolArguments(inputSchema: unknown, args: unknown):
|
|
|
28
35
|
for (const [name, propertySchema] of Object.entries(properties)) {
|
|
29
36
|
if (!Object.hasOwn(input, name) || typeof input[name] !== "string"
|
|
30
37
|
|| !propertySchema || typeof propertySchema !== "object" || Array.isArray(propertySchema)) continue;
|
|
31
|
-
|
|
32
|
-
|
|
38
|
+
// A valid string may be intentional (for example, string | object).
|
|
39
|
+
// Only recover JSON when the advertised property rejects the raw value.
|
|
40
|
+
if (Check(propertySchema as never, input[name])) continue;
|
|
33
41
|
try {
|
|
34
42
|
const parsed: unknown = JSON.parse(input[name] as string);
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
if (matches) {
|
|
43
|
+
const isContainer = Array.isArray(parsed)
|
|
44
|
+
|| (parsed !== null && typeof parsed === "object");
|
|
45
|
+
if (isContainer && Check(propertySchema as never, parsed)) {
|
|
39
46
|
prepared ??= { ...input };
|
|
40
47
|
prepared[name] = parsed;
|
|
41
48
|
}
|
|
@@ -73,8 +80,6 @@ export function resolveDirectTools(
|
|
|
73
80
|
const specs: DirectToolSpec[] = [];
|
|
74
81
|
if (!cache) return specs;
|
|
75
82
|
|
|
76
|
-
const seenNames = new Set<string>();
|
|
77
|
-
|
|
78
83
|
const envSelection = envOverride ? parseDirectToolSelectors(envOverride) : null;
|
|
79
84
|
const globalDirect = config.settings?.directTools;
|
|
80
85
|
|
|
@@ -139,11 +144,6 @@ export function resolveDirectTools(
|
|
|
139
144
|
console.warn(`MCP: skipping direct tool "${prefixedName}" (collides with builtin)`);
|
|
140
145
|
continue;
|
|
141
146
|
}
|
|
142
|
-
if (seenNames.has(prefixedName)) {
|
|
143
|
-
console.warn(`MCP: skipping duplicate direct tool "${prefixedName}" from "${serverName}"`);
|
|
144
|
-
continue;
|
|
145
|
-
}
|
|
146
|
-
seenNames.add(prefixedName);
|
|
147
147
|
specs.push({
|
|
148
148
|
...(lazy ? { lazy: true } : {}),
|
|
149
149
|
serverName,
|
|
@@ -166,11 +166,6 @@ export function resolveDirectTools(
|
|
|
166
166
|
console.warn(`MCP: skipping direct resource tool "${prefixedName}" (collides with builtin)`);
|
|
167
167
|
continue;
|
|
168
168
|
}
|
|
169
|
-
if (seenNames.has(prefixedName)) {
|
|
170
|
-
console.warn(`MCP: skipping duplicate direct resource tool "${prefixedName}" from "${serverName}"`);
|
|
171
|
-
continue;
|
|
172
|
-
}
|
|
173
|
-
seenNames.add(prefixedName);
|
|
174
169
|
specs.push({
|
|
175
170
|
...(lazy ? { lazy: true } : {}),
|
|
176
171
|
serverName,
|
|
@@ -183,17 +178,16 @@ export function resolveDirectTools(
|
|
|
183
178
|
}
|
|
184
179
|
}
|
|
185
180
|
|
|
186
|
-
|
|
181
|
+
const ownership = resolveUniqueNameOwnership(specs, (spec) => spec.prefixedName);
|
|
182
|
+
for (const [name, colliding] of ownership.collisions) {
|
|
183
|
+
console.warn(`MCP: skipping colliding direct name "${name}" from ${colliding.map((spec) => `"${spec.serverName}"`).join(", ")}`);
|
|
184
|
+
}
|
|
185
|
+
const uniqueSpecs = ownership.unique;
|
|
186
|
+
for (const spec of uniqueSpecs) reservedNames?.add(spec.prefixedName);
|
|
187
187
|
|
|
188
188
|
const emittedSpecs = unavailableServers.size === 0
|
|
189
|
-
?
|
|
190
|
-
:
|
|
191
|
-
|
|
192
|
-
// Lazy specs cost nothing at turn start, so they do not count toward the advisory.
|
|
193
|
-
const eagerCount = emittedSpecs.filter((spec) => !spec.lazy).length;
|
|
194
|
-
if (config.settings?.warnOnLargeDirectTools !== false && eagerCount >= DIRECT_TOOLS_ADVISORY_THRESHOLD) {
|
|
195
|
-
console.warn(`MCP: ${eagerCount} direct tools resolved. Each direct tool adds prompt context; README guidance recommends targeted sets of 5-20 tools and using the proxy or an explicit string[] when 75+ direct tools would be registered. Set settings.warnOnLargeDirectTools to false to hide this advisory.`);
|
|
196
|
-
}
|
|
189
|
+
? uniqueSpecs
|
|
190
|
+
: uniqueSpecs.filter((spec) => !unavailableServers.has(spec.serverName));
|
|
197
191
|
|
|
198
192
|
return emittedSpecs;
|
|
199
193
|
}
|
|
@@ -5,7 +5,7 @@ import type { DirectToolSpec, McpContent } from "./types.ts";
|
|
|
5
5
|
import { lazyConnect, getFailureAgeSeconds, clearFailure } from "./init.ts";
|
|
6
6
|
import { abortable, throwIfAborted } from "./abort.ts";
|
|
7
7
|
import { formatSchema } from "./tool-metadata.ts";
|
|
8
|
-
import { resolveMcpResultContent,
|
|
8
|
+
import { resolveMcpResultContent, transformMcpResourceContents } from "./tool-registrar.ts";
|
|
9
9
|
import { guardMcpOutput, guardedMcpDetails, resolveMcpOutputGuardOptions } from "./mcp-output-guard.ts";
|
|
10
10
|
import { maybeStartUiSession, summarizeUiSessionResult, type UiSessionRuntime } from "./ui-session.ts";
|
|
11
11
|
import { isServerDisabled } from "./types.ts";
|
|
@@ -13,6 +13,7 @@ import { authenticate, supportsOAuth } from "./mcp-auth-flow.ts";
|
|
|
13
13
|
import { formatAuthRequiredMessage, normalizeToolArguments, resolveServerUrl } from "./utils.ts";
|
|
14
14
|
import { SessionRecoveryAuthRequiredError, withSessionRecovery } from "./session-recovery.ts";
|
|
15
15
|
import { combineAbortSignals, isAbortError } from "./runtime-owner.ts";
|
|
16
|
+
import { callToolViaTaskSession } from "./mcp-tasks.ts";
|
|
16
17
|
import { ensureToolCallApproved } from "./tool-approval.ts";
|
|
17
18
|
import { getInputRequiredNeedsUiDetails } from "./errors.ts";
|
|
18
19
|
|
|
@@ -27,6 +28,7 @@ type DirectAutoAuthResult =
|
|
|
27
28
|
export {
|
|
28
29
|
DIRECT_TOOLS_ADVISORY_THRESHOLD,
|
|
29
30
|
buildProxyDescription,
|
|
31
|
+
getLargeDirectToolsAdvisory,
|
|
30
32
|
getMissingConfiguredDirectToolServers,
|
|
31
33
|
prepareDirectToolArguments,
|
|
32
34
|
resolveDirectTools,
|
|
@@ -310,6 +312,15 @@ export function createDirectToolExecutor(
|
|
|
310
312
|
spec.serverName,
|
|
311
313
|
async (conn) => {
|
|
312
314
|
await state.manager.ensureListen?.(spec.serverName, conn);
|
|
315
|
+
if (conn.taskSession) {
|
|
316
|
+
return await callToolViaTaskSession(conn.taskSession, {
|
|
317
|
+
name: spec.originalName,
|
|
318
|
+
args: normalizedParams ?? {},
|
|
319
|
+
meta: uiSession?.requestMeta,
|
|
320
|
+
signal: ownedSignal,
|
|
321
|
+
requestTimeoutMs: requestOptions?.timeout,
|
|
322
|
+
}) as unknown as ClientCallToolResult;
|
|
323
|
+
}
|
|
313
324
|
return abortable(conn.client.callTool({
|
|
314
325
|
name: spec.originalName,
|
|
315
326
|
arguments: normalizedParams,
|
|
@@ -320,14 +331,11 @@ export function createDirectToolExecutor(
|
|
|
320
331
|
uiSession?.sendToolResult(result as unknown as import("@modelcontextprotocol/client").CallToolResult);
|
|
321
332
|
|
|
322
333
|
if (result.isError) {
|
|
323
|
-
const
|
|
324
|
-
const content = transformMcpContent(mcpContent, state.owner?.signal);
|
|
334
|
+
const content = resolveMcpResultContent(result as Record<string, unknown>, state.owner?.signal);
|
|
325
335
|
const outputContent = content.length > 0 ? content : [{ type: "text" as const, text: "(empty result)" }];
|
|
326
|
-
const schemaText = spec.inputSchema ? `\n\nExpected parameters:\n${formatSchema(spec.inputSchema)}` : "";
|
|
327
336
|
const guarded = await guardMcpOutput(outputContent, {
|
|
328
337
|
...outputGuardOptions,
|
|
329
338
|
prefix: "Error: ",
|
|
330
|
-
suffix: schemaText,
|
|
331
339
|
emptyTextFallback: "Tool execution failed",
|
|
332
340
|
...(state.config.settings?.directToolResultDetails === "bounded" ? { rawMcpResult: result } : {}),
|
|
333
341
|
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export function throwIfAborted(signal) {
|
|
2
|
+
if (!signal?.aborted)
|
|
3
|
+
return;
|
|
4
|
+
throw signal.reason instanceof Error ? signal.reason : new Error(String(signal.reason ?? "MCP request aborted"));
|
|
5
|
+
}
|
|
6
|
+
export async function abortable(promise, signal) {
|
|
7
|
+
if (!signal)
|
|
8
|
+
return promise;
|
|
9
|
+
throwIfAborted(signal);
|
|
10
|
+
return await new Promise((resolve, reject) => {
|
|
11
|
+
let settled = false;
|
|
12
|
+
const cleanup = () => signal.removeEventListener("abort", onAbort);
|
|
13
|
+
const onAbort = () => {
|
|
14
|
+
if (settled)
|
|
15
|
+
return;
|
|
16
|
+
settled = true;
|
|
17
|
+
cleanup();
|
|
18
|
+
reject(signal.reason instanceof Error ? signal.reason : new Error(String(signal.reason ?? "MCP request aborted")));
|
|
19
|
+
};
|
|
20
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
21
|
+
promise.then(value => {
|
|
22
|
+
if (settled)
|
|
23
|
+
return;
|
|
24
|
+
settled = true;
|
|
25
|
+
cleanup();
|
|
26
|
+
resolve(value);
|
|
27
|
+
}, error => {
|
|
28
|
+
if (settled)
|
|
29
|
+
return;
|
|
30
|
+
settled = true;
|
|
31
|
+
cleanup();
|
|
32
|
+
reject(error);
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=abort.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"abort.js","sourceRoot":"","sources":["../abort.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,cAAc,CAAC,MAAoB;IACjD,IAAI,CAAC,MAAM,EAAE,OAAO;QAAE,OAAO;IAC7B,MAAM,MAAM,CAAC,MAAM,YAAY,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,IAAI,qBAAqB,CAAC,CAAC,CAAC;AACnH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,SAAS,CAAI,OAAmB,EAAE,MAAoB;IAC1E,IAAI,CAAC,MAAM;QAAE,OAAO,OAAO,CAAC;IAC5B,cAAc,CAAC,MAAM,CAAC,CAAC;IACvB,OAAO,MAAM,IAAI,OAAO,CAAI,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QAC9C,IAAI,OAAO,GAAG,KAAK,CAAC;QACpB,MAAM,OAAO,GAAG,GAAG,EAAE,CAAC,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACnE,MAAM,OAAO,GAAG,GAAG,EAAE;YACnB,IAAI,OAAO;gBAAE,OAAO;YACpB,OAAO,GAAG,IAAI,CAAC;YACf,OAAO,EAAE,CAAC;YACV,MAAM,CAAC,MAAM,CAAC,MAAM,YAAY,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,IAAI,qBAAqB,CAAC,CAAC,CAAC,CAAC;QACrH,CAAC,CAAC;QACF,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QAC1D,OAAO,CAAC,IAAI,CACV,KAAK,CAAC,EAAE;YACN,IAAI,OAAO;gBAAE,OAAO;YACpB,OAAO,GAAG,IAAI,CAAC;YACf,OAAO,EAAE,CAAC;YACV,OAAO,CAAC,KAAK,CAAC,CAAC;QACjB,CAAC,EACD,KAAK,CAAC,EAAE;YACN,IAAI,OAAO;gBAAE,OAAO;YACpB,OAAO,GAAG,IAAI,CAAC;YACf,OAAO,EAAE,CAAC;YACV,MAAM,CAAC,KAAK,CAAC,CAAC;QAChB,CAAC,CACF,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
const DEFAULT_TTL_MS = 5 * 60_000;
|
|
3
|
+
const ENV_TTL_MS = "PI_MCP_ADAPTER_BEARER_COMMAND_TTL_MS";
|
|
4
|
+
const COMMAND_TIMEOUT_MS = 10_000;
|
|
5
|
+
const COMMAND_MAX_OUTPUT_BYTES = 1024 * 1024;
|
|
6
|
+
const USE_PROCESS_GROUP = process.platform !== "win32";
|
|
7
|
+
function resolveDefaultTtlMs() {
|
|
8
|
+
const parsed = Number(process.env[ENV_TTL_MS] || DEFAULT_TTL_MS);
|
|
9
|
+
return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : DEFAULT_TTL_MS;
|
|
10
|
+
}
|
|
11
|
+
function abortReason(signal) {
|
|
12
|
+
return signal.reason ?? new DOMException("The operation was aborted", "AbortError");
|
|
13
|
+
}
|
|
14
|
+
function runCommand(command, context, signal) {
|
|
15
|
+
return new Promise((resolve, reject) => {
|
|
16
|
+
let output = Buffer.alloc(0);
|
|
17
|
+
let settled = false;
|
|
18
|
+
let terminating = false;
|
|
19
|
+
const child = spawn(command.slice(1), {
|
|
20
|
+
shell: true,
|
|
21
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
22
|
+
windowsHide: true,
|
|
23
|
+
detached: USE_PROCESS_GROUP,
|
|
24
|
+
});
|
|
25
|
+
const kill = async () => {
|
|
26
|
+
if (child.pid === undefined)
|
|
27
|
+
return;
|
|
28
|
+
if (!USE_PROCESS_GROUP) {
|
|
29
|
+
await new Promise((resolveKill, rejectKill) => {
|
|
30
|
+
const killer = spawn("taskkill", ["/pid", String(child.pid), "/T", "/F"], {
|
|
31
|
+
stdio: "ignore",
|
|
32
|
+
windowsHide: true,
|
|
33
|
+
});
|
|
34
|
+
killer.on("error", () => rejectKill(new Error("Failed to stop bearer token command")));
|
|
35
|
+
killer.on("close", code => {
|
|
36
|
+
if (code === 0 || code === 128)
|
|
37
|
+
resolveKill();
|
|
38
|
+
else
|
|
39
|
+
rejectKill(new Error(`Failed to stop bearer token command: taskkill exited with code ${code ?? "unknown"}`));
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
try {
|
|
45
|
+
process.kill(-child.pid, "SIGKILL");
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
if (error.code !== "ESRCH")
|
|
49
|
+
throw error;
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
const finish = (error, token) => {
|
|
53
|
+
if (settled)
|
|
54
|
+
return;
|
|
55
|
+
settled = true;
|
|
56
|
+
clearTimeout(timer);
|
|
57
|
+
signal.removeEventListener("abort", onAbort);
|
|
58
|
+
if (error !== undefined)
|
|
59
|
+
reject(error);
|
|
60
|
+
else
|
|
61
|
+
resolve(token);
|
|
62
|
+
};
|
|
63
|
+
const terminate = async (error) => {
|
|
64
|
+
if (settled || terminating)
|
|
65
|
+
return;
|
|
66
|
+
terminating = true;
|
|
67
|
+
clearTimeout(timer);
|
|
68
|
+
signal.removeEventListener("abort", onAbort);
|
|
69
|
+
try {
|
|
70
|
+
await kill();
|
|
71
|
+
finish(error);
|
|
72
|
+
}
|
|
73
|
+
catch (cleanupError) {
|
|
74
|
+
finish(cleanupError);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
const onAbort = () => {
|
|
78
|
+
void terminate(abortReason(signal));
|
|
79
|
+
};
|
|
80
|
+
const timer = setTimeout(() => {
|
|
81
|
+
void terminate(new Error(`Failed to resolve ${context}: command timed out after ${COMMAND_TIMEOUT_MS}ms`));
|
|
82
|
+
}, COMMAND_TIMEOUT_MS);
|
|
83
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
84
|
+
if (signal.aborted) {
|
|
85
|
+
onAbort();
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
child.on("error", () => {
|
|
89
|
+
if (!terminating)
|
|
90
|
+
finish(new Error(`Failed to resolve ${context}: command failed to start`));
|
|
91
|
+
});
|
|
92
|
+
child.stdout.on("data", (chunk) => {
|
|
93
|
+
if (settled || terminating)
|
|
94
|
+
return;
|
|
95
|
+
output = Buffer.concat([output, Buffer.from(chunk)]);
|
|
96
|
+
if (output.byteLength > COMMAND_MAX_OUTPUT_BYTES) {
|
|
97
|
+
void terminate(new Error(`Failed to resolve ${context}: command output exceeded 1 MiB`));
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
child.on("close", code => {
|
|
101
|
+
if (settled || terminating)
|
|
102
|
+
return;
|
|
103
|
+
if (code !== 0) {
|
|
104
|
+
finish(new Error(`Failed to resolve ${context}: command exited with code ${code ?? "unknown"}`));
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
const token = output.toString("utf8").trim();
|
|
108
|
+
if (!token) {
|
|
109
|
+
finish(new Error(`Failed to resolve ${context}: command returned empty output`));
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
finish(undefined, token);
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
/** Resolve and periodically refresh one command-backed bearer token. */
|
|
117
|
+
export class BearerCommandResolver {
|
|
118
|
+
#command;
|
|
119
|
+
#context;
|
|
120
|
+
#ttlMs;
|
|
121
|
+
#cached;
|
|
122
|
+
#failure;
|
|
123
|
+
#inflight;
|
|
124
|
+
constructor(command, context, ttlMs = resolveDefaultTtlMs()) {
|
|
125
|
+
this.#command = command;
|
|
126
|
+
this.#context = context;
|
|
127
|
+
this.#ttlMs = ttlMs;
|
|
128
|
+
}
|
|
129
|
+
resolve(signal) {
|
|
130
|
+
if (signal?.aborted)
|
|
131
|
+
return Promise.reject(abortReason(signal));
|
|
132
|
+
const now = Date.now();
|
|
133
|
+
if (this.#cached !== undefined && now < this.#cached.expiresAt) {
|
|
134
|
+
return Promise.resolve(this.#cached.token);
|
|
135
|
+
}
|
|
136
|
+
if (this.#failure !== undefined && now < this.#failure.retryAt) {
|
|
137
|
+
return this.#cached !== undefined
|
|
138
|
+
? Promise.resolve(this.#cached.token)
|
|
139
|
+
: Promise.reject(this.#failure.error);
|
|
140
|
+
}
|
|
141
|
+
let inflight = this.#inflight;
|
|
142
|
+
if (inflight === undefined) {
|
|
143
|
+
const controller = new AbortController();
|
|
144
|
+
let current;
|
|
145
|
+
const promise = runCommand(this.#command, this.#context, controller.signal)
|
|
146
|
+
.then(token => {
|
|
147
|
+
this.#cached = { token, expiresAt: Date.now() + this.#ttlMs };
|
|
148
|
+
this.#failure = undefined;
|
|
149
|
+
return token;
|
|
150
|
+
})
|
|
151
|
+
.catch(error => {
|
|
152
|
+
// Request cancellation is not a helper outage. Do not let one
|
|
153
|
+
// cancelled request suppress refresh attempts for the next caller.
|
|
154
|
+
if (!controller.signal.aborted) {
|
|
155
|
+
this.#failure = { error, retryAt: Date.now() + this.#ttlMs };
|
|
156
|
+
}
|
|
157
|
+
if (this.#cached !== undefined)
|
|
158
|
+
return this.#cached.token;
|
|
159
|
+
throw error;
|
|
160
|
+
})
|
|
161
|
+
.finally(() => {
|
|
162
|
+
if (this.#inflight === current)
|
|
163
|
+
this.#inflight = undefined;
|
|
164
|
+
});
|
|
165
|
+
current = { controller, promise, waiters: 0 };
|
|
166
|
+
this.#inflight = current;
|
|
167
|
+
inflight = current;
|
|
168
|
+
}
|
|
169
|
+
return this.#wait(inflight, signal);
|
|
170
|
+
}
|
|
171
|
+
#wait(inflight, signal) {
|
|
172
|
+
inflight.waiters++;
|
|
173
|
+
let onAbort;
|
|
174
|
+
const result = signal === undefined
|
|
175
|
+
? inflight.promise
|
|
176
|
+
: Promise.race([
|
|
177
|
+
inflight.promise,
|
|
178
|
+
new Promise((_resolve, reject) => {
|
|
179
|
+
onAbort = () => reject(abortReason(signal));
|
|
180
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
181
|
+
}),
|
|
182
|
+
]);
|
|
183
|
+
return result.finally(() => {
|
|
184
|
+
if (onAbort)
|
|
185
|
+
signal.removeEventListener("abort", onAbort);
|
|
186
|
+
inflight.waiters--;
|
|
187
|
+
if (this.#inflight === inflight && inflight.waiters === 0) {
|
|
188
|
+
this.#inflight = undefined;
|
|
189
|
+
inflight.controller.abort(signal?.reason);
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
//# sourceMappingURL=bearer-command-resolver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bearer-command-resolver.js","sourceRoot":"","sources":["../bearer-command-resolver.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAE3C,MAAM,cAAc,GAAG,CAAC,GAAG,MAAM,CAAC;AAClC,MAAM,UAAU,GAAG,sCAAsC,CAAC;AAC1D,MAAM,kBAAkB,GAAG,MAAM,CAAC;AAClC,MAAM,wBAAwB,GAAG,IAAI,GAAG,IAAI,CAAC;AAC7C,MAAM,iBAAiB,GAAG,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC;AAEvD,SAAS,mBAAmB;IAC1B,MAAM,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,cAAc,CAAC,CAAC;IACjE,OAAO,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,cAAc,CAAC;AAC9E,CAAC;AAED,SAAS,WAAW,CAAC,MAAmB;IACtC,OAAO,MAAM,CAAC,MAAM,IAAI,IAAI,YAAY,CAAC,2BAA2B,EAAE,YAAY,CAAC,CAAC;AACtF,CAAC;AAED,SAAS,UAAU,CAAC,OAAe,EAAE,OAAe,EAAE,MAAmB;IACvE,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,IAAI,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAC7B,IAAI,OAAO,GAAG,KAAK,CAAC;QACpB,IAAI,WAAW,GAAG,KAAK,CAAC;QACxB,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE;YACpC,KAAK,EAAE,IAAI;YACX,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC;YACnC,WAAW,EAAE,IAAI;YACjB,QAAQ,EAAE,iBAAiB;SAC5B,CAAC,CAAC;QAEH,MAAM,IAAI,GAAG,KAAK,IAAmB,EAAE;YACrC,IAAI,KAAK,CAAC,GAAG,KAAK,SAAS;gBAAE,OAAO;YACpC,IAAI,CAAC,iBAAiB,EAAE,CAAC;gBACvB,MAAM,IAAI,OAAO,CAAO,CAAC,WAAW,EAAE,UAAU,EAAE,EAAE;oBAClD,MAAM,MAAM,GAAG,KAAK,CAAC,UAAU,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE;wBACxE,KAAK,EAAE,QAAQ;wBACf,WAAW,EAAE,IAAI;qBAClB,CAAC,CAAC;oBACH,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,UAAU,CAAC,IAAI,KAAK,CAAC,qCAAqC,CAAC,CAAC,CAAC,CAAC;oBACvF,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,IAAI,CAAC,EAAE;wBACxB,IAAI,IAAI,KAAK,CAAC,IAAI,IAAI,KAAK,GAAG;4BAAE,WAAW,EAAE,CAAC;;4BACzC,UAAU,CAAC,IAAI,KAAK,CAAC,kEAAkE,IAAI,IAAI,SAAS,EAAE,CAAC,CAAC,CAAC;oBACpH,CAAC,CAAC,CAAC;gBACL,CAAC,CAAC,CAAC;gBACH,OAAO;YACT,CAAC;YACD,IAAI,CAAC;gBACH,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;YACtC,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,IAAK,KAA+B,CAAC,IAAI,KAAK,OAAO;oBAAE,MAAM,KAAK,CAAC;YACrE,CAAC;QACH,CAAC,CAAC;QACF,MAAM,MAAM,GAAG,CAAC,KAAc,EAAE,KAAc,EAAE,EAAE;YAChD,IAAI,OAAO;gBAAE,OAAO;YACpB,OAAO,GAAG,IAAI,CAAC;YACf,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YAC7C,IAAI,KAAK,KAAK,SAAS;gBAAE,MAAM,CAAC,KAAK,CAAC,CAAC;;gBAClC,OAAO,CAAC,KAAM,CAAC,CAAC;QACvB,CAAC,CAAC;QACF,MAAM,SAAS,GAAG,KAAK,EAAE,KAAc,EAAE,EAAE;YACzC,IAAI,OAAO,IAAI,WAAW;gBAAE,OAAO;YACnC,WAAW,GAAG,IAAI,CAAC;YACnB,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YAC7C,IAAI,CAAC;gBACH,MAAM,IAAI,EAAE,CAAC;gBACb,MAAM,CAAC,KAAK,CAAC,CAAC;YAChB,CAAC;YAAC,OAAO,YAAY,EAAE,CAAC;gBACtB,MAAM,CAAC,YAAY,CAAC,CAAC;YACvB,CAAC;QACH,CAAC,CAAC;QACF,MAAM,OAAO,GAAG,GAAG,EAAE;YACnB,KAAK,SAAS,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC;QACtC,CAAC,CAAC;QACF,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;YAC5B,KAAK,SAAS,CAAC,IAAI,KAAK,CAAC,qBAAqB,OAAO,6BAA6B,kBAAkB,IAAI,CAAC,CAAC,CAAC;QAC7G,CAAC,EAAE,kBAAkB,CAAC,CAAC;QAEvB,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QAC1D,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;YACnB,OAAO,EAAE,CAAC;YACV,OAAO;QACT,CAAC;QACD,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;YACrB,IAAI,CAAC,WAAW;gBAAE,MAAM,CAAC,IAAI,KAAK,CAAC,qBAAqB,OAAO,2BAA2B,CAAC,CAAC,CAAC;QAC/F,CAAC,CAAC,CAAC;QACH,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAsB,EAAE,EAAE;YACjD,IAAI,OAAO,IAAI,WAAW;gBAAE,OAAO;YACnC,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;YACrD,IAAI,MAAM,CAAC,UAAU,GAAG,wBAAwB,EAAE,CAAC;gBACjD,KAAK,SAAS,CAAC,IAAI,KAAK,CAAC,qBAAqB,OAAO,iCAAiC,CAAC,CAAC,CAAC;YAC3F,CAAC;QACH,CAAC,CAAC,CAAC;QACH,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,IAAI,CAAC,EAAE;YACvB,IAAI,OAAO,IAAI,WAAW;gBAAE,OAAO;YACnC,IAAI,IAAI,KAAK,CAAC,EAAE,CAAC;gBACf,MAAM,CAAC,IAAI,KAAK,CAAC,qBAAqB,OAAO,8BAA8B,IAAI,IAAI,SAAS,EAAE,CAAC,CAAC,CAAC;gBACjG,OAAO;YACT,CAAC;YACD,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;YAC7C,IAAI,CAAC,KAAK,EAAE,CAAC;gBACX,MAAM,CAAC,IAAI,KAAK,CAAC,qBAAqB,OAAO,iCAAiC,CAAC,CAAC,CAAC;gBACjF,OAAO;YACT,CAAC;YACD,MAAM,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;QAC3B,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAQD,wEAAwE;AACxE,MAAM,OAAO,qBAAqB;IACvB,QAAQ,CAAS;IACjB,QAAQ,CAAS;IACjB,MAAM,CAAS;IACxB,OAAO,CAAmD;IAC1D,QAAQ,CAAkD;IAC1D,SAAS,CAAuB;IAEhC,YAAY,OAAe,EAAE,OAAe,EAAE,QAAgB,mBAAmB,EAAE;QACjF,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC;QACxB,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC;QACxB,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC;IACtB,CAAC;IAED,OAAO,CAAC,MAAoB;QAC1B,IAAI,MAAM,EAAE,OAAO;YAAE,OAAO,OAAO,CAAC,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC;QAChE,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,IAAI,IAAI,CAAC,OAAO,KAAK,SAAS,IAAI,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC;YAC/D,OAAO,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QAC7C,CAAC;QACD,IAAI,IAAI,CAAC,QAAQ,KAAK,SAAS,IAAI,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC;YAC/D,OAAO,IAAI,CAAC,OAAO,KAAK,SAAS;gBAC/B,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC;gBACrC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;QAC1C,CAAC;QAED,IAAI,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC;QAC9B,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;YAC3B,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;YACzC,IAAI,OAAiB,CAAC;YACtB,MAAM,OAAO,GAAG,UAAU,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,UAAU,CAAC,MAAM,CAAC;iBACxE,IAAI,CAAC,KAAK,CAAC,EAAE;gBACZ,IAAI,CAAC,OAAO,GAAG,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;gBAC9D,IAAI,CAAC,QAAQ,GAAG,SAAS,CAAC;gBAC1B,OAAO,KAAK,CAAC;YACf,CAAC,CAAC;iBACD,KAAK,CAAC,KAAK,CAAC,EAAE;gBACb,8DAA8D;gBAC9D,mEAAmE;gBACnE,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;oBAC/B,IAAI,CAAC,QAAQ,GAAG,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;gBAC/D,CAAC;gBACD,IAAI,IAAI,CAAC,OAAO,KAAK,SAAS;oBAAE,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC;gBAC1D,MAAM,KAAK,CAAC;YACd,CAAC,CAAC;iBACD,OAAO,CAAC,GAAG,EAAE;gBACZ,IAAI,IAAI,CAAC,SAAS,KAAK,OAAO;oBAAE,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;YAC7D,CAAC,CAAC,CAAC;YACL,OAAO,GAAG,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;YAC9C,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC;YACzB,QAAQ,GAAG,OAAO,CAAC;QACrB,CAAC;QACD,OAAO,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IACtC,CAAC;IAED,KAAK,CAAC,QAAkB,EAAE,MAAoB;QAC5C,QAAQ,CAAC,OAAO,EAAE,CAAC;QACnB,IAAI,OAAiC,CAAC;QACtC,MAAM,MAAM,GAAG,MAAM,KAAK,SAAS;YACjC,CAAC,CAAC,QAAQ,CAAC,OAAO;YAClB,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC;gBACX,QAAQ,CAAC,OAAO;gBAChB,IAAI,OAAO,CAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE;oBACvC,OAAO,GAAG,GAAG,EAAE,CAAC,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC;oBAC5C,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;gBAC5D,CAAC,CAAC;aACH,CAAC,CAAC;QACP,OAAO,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE;YACzB,IAAI,OAAO;gBAAE,MAAO,CAAC,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YAC3D,QAAQ,CAAC,OAAO,EAAE,CAAC;YACnB,IAAI,IAAI,CAAC,SAAS,KAAK,QAAQ,IAAI,QAAQ,CAAC,OAAO,KAAK,CAAC,EAAE,CAAC;gBAC1D,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;gBAC3B,QAAQ,CAAC,UAAU,CAAC,KAAK,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;YAC5C,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC;CACF"}
|