agent-trellis 0.7.0 → 0.8.2
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/README.md +48 -153
- package/dist/adapters/claude-code.js +3 -2
- package/dist/adapters/codex.js +3 -2
- package/dist/adapters/jsonMcp.d.ts +3 -1
- package/dist/adapters/jsonMcp.js +18 -4
- package/dist/adapters/kimi-code.d.ts +21 -0
- package/dist/adapters/kimi-code.js +107 -0
- package/dist/adapters/kiro.js +3 -2
- package/dist/adapters/mcpPlan.d.ts +6 -1
- package/dist/adapters/mcpPlan.js +106 -8
- package/dist/adapters/pi.js +22 -3
- package/dist/adapters/symlinkPlan.d.ts +7 -0
- package/dist/adapters/symlinkPlan.js +16 -7
- package/dist/adapters/zcode.d.ts +21 -0
- package/dist/adapters/zcode.js +234 -0
- package/dist/cli.js +145 -29
- package/dist/commands/doctor.d.ts +7 -2
- package/dist/commands/doctor.js +40 -2
- package/dist/commands/init.d.ts +4 -2
- package/dist/commands/init.js +29 -13
- package/dist/commands/kimi.d.ts +16 -0
- package/dist/commands/kimi.js +64 -0
- package/dist/commands/manage.d.ts +39 -0
- package/dist/commands/manage.js +126 -0
- package/dist/commands/mcp.d.ts +77 -14
- package/dist/commands/mcp.js +169 -29
- package/dist/commands/mcpGateway.d.ts +6 -0
- package/dist/commands/mcpGateway.js +35 -14
- package/dist/commands/mcpImport.d.ts +46 -0
- package/dist/commands/mcpImport.js +359 -0
- package/dist/commands/memory.d.ts +17 -3
- package/dist/commands/memory.js +27 -8
- package/dist/commands/migrate.d.ts +16 -5
- package/dist/commands/migrate.js +93 -17
- package/dist/commands/onboard.d.ts +74 -2
- package/dist/commands/onboard.js +469 -71
- package/dist/commands/rollback.js +28 -2
- package/dist/commands/secretsAudit.js +4 -1
- package/dist/commands/skill.d.ts +52 -6
- package/dist/commands/skill.js +114 -15
- package/dist/commands/sync.js +5 -1
- package/dist/core/adapter.d.ts +11 -1
- package/dist/core/canonical.d.ts +15 -7
- package/dist/core/canonical.js +101 -13
- package/dist/core/capabilitySelection.d.ts +45 -0
- package/dist/core/capabilitySelection.js +124 -0
- package/dist/core/types.d.ts +25 -2
- package/dist/core/types.js +8 -0
- package/dist/lib/agentBridge.d.ts +169 -0
- package/dist/lib/agentBridge.js +367 -0
- package/dist/lib/agentBridgeProvider.d.ts +14 -0
- package/dist/lib/agentBridgeProvider.js +95 -0
- package/dist/lib/backup.d.ts +32 -0
- package/dist/lib/backup.js +20 -1
- package/dist/lib/builtinSkills.d.ts +2 -0
- package/dist/lib/builtinSkills.js +4 -0
- package/dist/lib/chatAgents.d.ts +42 -0
- package/dist/lib/chatAgents.js +59 -0
- package/dist/lib/cliMetadata.d.ts +1 -0
- package/dist/lib/cliMetadata.js +14 -0
- package/dist/lib/envVarNames.d.ts +3 -0
- package/dist/lib/envVarNames.js +19 -0
- package/dist/lib/gatewayBackend.d.ts +28 -0
- package/dist/lib/gatewayBackend.js +72 -1
- package/dist/lib/installAgent.d.ts +7 -5
- package/dist/lib/installAgent.js +4 -3
- package/dist/lib/mcpConnect.js +5 -1
- package/dist/lib/mcpMigrateRead.d.ts +2 -0
- package/dist/lib/mcpMigrateRead.js +22 -1
- package/dist/lib/mcpRuntime.d.ts +46 -0
- package/dist/lib/mcpRuntime.js +185 -0
- package/dist/lib/mcpStatusProvider.d.ts +10 -0
- package/dist/lib/mcpStatusProvider.js +25 -0
- package/dist/lib/mcpToolRegistry.d.ts +18 -0
- package/dist/lib/mcpToolRegistry.js +107 -14
- package/dist/lib/memoryGraph.d.ts +1 -1
- package/dist/lib/memoryGraph.js +9 -3
- package/dist/lib/memoryProvider.d.ts +58 -0
- package/dist/lib/memoryProvider.js +175 -0
- package/dist/lib/memoryStatus.d.ts +17 -0
- package/dist/lib/memoryStatus.js +88 -0
- package/dist/lib/nativeMemory.d.ts +19 -0
- package/dist/lib/nativeMemory.js +129 -0
- package/dist/lib/realHomeSnapshot.js +5 -0
- package/dist/lib/runtimeControlProvider.d.ts +16 -0
- package/dist/lib/runtimeControlProvider.js +170 -0
- package/dist/lib/secretEnv.d.ts +2 -1
- package/dist/lib/secretEnv.js +6 -2
- package/dist/lib/skillProvider.d.ts +13 -0
- package/dist/lib/skillProvider.js +210 -0
- package/dist/lib/taskProvider.d.ts +7 -0
- package/dist/lib/taskProvider.js +43 -0
- package/dist/lib/taskStore.d.ts +46 -0
- package/dist/lib/taskStore.js +156 -0
- package/dist/lib/terminalPicker.d.ts +23 -49
- package/dist/lib/terminalPicker.js +80 -218
- package/dist/lib/zcodeOwnership.d.ts +17 -0
- package/dist/lib/zcodeOwnership.js +37 -0
- package/dist/pi-bridge/bundle.js +476 -37
- package/dist/pi-bridge/index.js +42 -21
- package/dist/pi-bridge/schemaTranslate.d.ts +4 -8
- package/dist/pi-bridge/schemaTranslate.js +4 -8
- package/dist/probes/codex.d.ts +7 -0
- package/dist/probes/codex.js +37 -5
- package/dist/probes/kimi-code.d.ts +12 -0
- package/dist/probes/kimi-code.js +55 -0
- package/dist/probes/zcode.d.ts +32 -0
- package/dist/probes/zcode.js +140 -0
- package/dist/sdk.d.ts +1 -1
- package/docs/architecture.md +230 -28
- package/docs/assets/brand-v3/trellis-mark-v3-a.png +0 -0
- package/docs/assets/brand-v3/trellis-mark-v3-a.svg +10 -0
- package/docs/assets/poster-series-v3/README.md +60 -0
- package/docs/assets/poster-series-v3/agent-trellis-poster-v3-horizontal-en.png +0 -0
- package/docs/assets/poster-series-v3/agent-trellis-poster-v3-horizontal-en.svg +51 -0
- package/docs/assets/poster-series-v3/agent-trellis-poster-v3-horizontal-zh.png +0 -0
- package/docs/assets/poster-series-v3/agent-trellis-poster-v3-horizontal-zh.svg +51 -0
- package/docs/assets/poster-series-v3/agent-trellis-poster-v3-memory-en.png +0 -0
- package/docs/assets/poster-series-v3/agent-trellis-poster-v3-memory-en.svg +48 -0
- package/docs/assets/poster-series-v3/agent-trellis-poster-v3-memory-zh.png +0 -0
- package/docs/assets/poster-series-v3/agent-trellis-poster-v3-memory-zh.svg +48 -0
- package/docs/assets/poster-series-v3/agent-trellis-poster-v3-migrate-en.png +0 -0
- package/docs/assets/poster-series-v3/agent-trellis-poster-v3-migrate-en.svg +56 -0
- package/docs/assets/poster-series-v3/agent-trellis-poster-v3-migrate-zh.png +0 -0
- package/docs/assets/poster-series-v3/agent-trellis-poster-v3-migrate-zh.svg +56 -0
- package/docs/assets/poster-series-v3/agent-trellis-poster-v3-switch-en.png +0 -0
- package/docs/assets/poster-series-v3/agent-trellis-poster-v3-switch-en.svg +47 -0
- package/docs/assets/poster-series-v3/agent-trellis-poster-v3-switch-zh.png +0 -0
- package/docs/assets/poster-series-v3/agent-trellis-poster-v3-switch-zh.svg +47 -0
- package/docs/assets/poster-series-v3/agent-trellis-poster-v3-unified-en.png +0 -0
- package/docs/assets/poster-series-v3/agent-trellis-poster-v3-unified-en.svg +51 -0
- package/docs/assets/poster-series-v3/agent-trellis-poster-v3-unified-zh.png +0 -0
- package/docs/assets/poster-series-v3/agent-trellis-poster-v3-unified-zh.svg +51 -0
- package/docs/getting-started.md +496 -42
- package/docs/overview.md +134 -0
- package/docs/overview.zh-CN.md +102 -0
- package/docs/research.md +59 -0
- package/docs/roadmap.md +11 -6
- package/docs/video/README.md +76 -0
- package/docs/video/build/audio/01-opening.aiff +0 -0
- package/docs/video/build/audio/01-opening.mp3 +0 -0
- package/docs/video/build/audio/02-pain.aiff +0 -0
- package/docs/video/build/audio/02-pain.mp3 +0 -0
- package/docs/video/build/audio/03-solution.aiff +0 -0
- package/docs/video/build/audio/03-solution.mp3 +0 -0
- package/docs/video/build/audio/04-install.aiff +0 -0
- package/docs/video/build/audio/04-install.mp3 +0 -0
- package/docs/video/build/audio/04-setup.mp3 +0 -0
- package/docs/video/build/audio/05-dryrun.mp3 +0 -0
- package/docs/video/build/audio/05-init.aiff +0 -0
- package/docs/video/build/audio/05-init.mp3 +0 -0
- package/docs/video/build/audio/05-interactive.mp3 +0 -0
- package/docs/video/build/audio/06-dryrun.aiff +0 -0
- package/docs/video/build/audio/06-dryrun.mp3 +0 -0
- package/docs/video/build/audio/06-onboard.mp3 +0 -0
- package/docs/video/build/audio/06-run.mp3 +0 -0
- package/docs/video/build/audio/07-onboard.aiff +0 -0
- package/docs/video/build/audio/07-onboard.mp3 +0 -0
- package/docs/video/build/audio/07-verify.mp3 +0 -0
- package/docs/video/build/audio/08-closing.mp3 +0 -0
- package/docs/video/build/audio/08-doctor.aiff +0 -0
- package/docs/video/build/audio/08-doctor.mp3 +0 -0
- package/docs/video/build/audio/09-rollback.aiff +0 -0
- package/docs/video/build/audio/09-rollback.mp3 +0 -0
- package/docs/video/build/audio/10-principles.aiff +0 -0
- package/docs/video/build/audio/10-principles.mp3 +0 -0
- package/docs/video/build/audio/11-closing.aiff +0 -0
- package/docs/video/build/audio/11-closing.mp3 +0 -0
- package/docs/video/build/frames/01-opening-0.png +0 -0
- package/docs/video/build/frames/02-pain-1.png +0 -0
- package/docs/video/build/frames/02-pain-2.png +0 -0
- package/docs/video/build/frames/03-solution-3.png +0 -0
- package/docs/video/build/frames/04-install-4.png +0 -0
- package/docs/video/build/frames/04-setup-4.png +0 -0
- package/docs/video/build/frames/04-setup-5.png +0 -0
- package/docs/video/build/frames/05-dryrun-6.png +0 -0
- package/docs/video/build/frames/05-init-5.png +0 -0
- package/docs/video/build/frames/05-interactive-5.png +0 -0
- package/docs/video/build/frames/05-interactive-6.png +0 -0
- package/docs/video/build/frames/06-dryrun-6.png +0 -0
- package/docs/video/build/frames/06-dryrun-7.png +0 -0
- package/docs/video/build/frames/06-onboard-7.png +0 -0
- package/docs/video/build/frames/06-onboard-8.png +0 -0
- package/docs/video/build/frames/06-run-7.png +0 -0
- package/docs/video/build/frames/06-run-8.png +0 -0
- package/docs/video/build/frames/07-onboard-10.png +0 -0
- package/docs/video/build/frames/07-onboard-7.png +0 -0
- package/docs/video/build/frames/07-onboard-8.png +0 -0
- package/docs/video/build/frames/07-onboard-9.png +0 -0
- package/docs/video/build/frames/07-verify-10.png +0 -0
- package/docs/video/build/frames/07-verify-8.png +0 -0
- package/docs/video/build/frames/07-verify-9.png +0 -0
- package/docs/video/build/frames/08-closing-10.png +0 -0
- package/docs/video/build/frames/08-closing-11.png +0 -0
- package/docs/video/build/frames/08-doctor-11.png +0 -0
- package/docs/video/build/frames/08-doctor-9.png +0 -0
- package/docs/video/build/frames/09-rollback-10.png +0 -0
- package/docs/video/build/frames/09-rollback-12.png +0 -0
- package/docs/video/build/frames/10-principles-11.png +0 -0
- package/docs/video/build/frames/10-principles-13.png +0 -0
- package/docs/video/build/frames/11-closing-12.png +0 -0
- package/docs/video/build/frames/11-closing-14.png +0 -0
- package/docs/video/build/segments/01-opening.mp4 +0 -0
- package/docs/video/build/segments/01-opening.txt +3 -0
- package/docs/video/build/segments/02-pain.mp4 +0 -0
- package/docs/video/build/segments/02-pain.txt +5 -0
- package/docs/video/build/segments/03-solution.mp4 +0 -0
- package/docs/video/build/segments/03-solution.txt +3 -0
- package/docs/video/build/segments/04-install.mp4 +0 -0
- package/docs/video/build/segments/04-install.txt +3 -0
- package/docs/video/build/segments/04-setup.mp4 +0 -0
- package/docs/video/build/segments/04-setup.txt +3 -0
- package/docs/video/build/segments/05-dryrun.mp4 +0 -0
- package/docs/video/build/segments/05-dryrun.txt +3 -0
- package/docs/video/build/segments/05-init.mp4 +0 -0
- package/docs/video/build/segments/05-init.txt +3 -0
- package/docs/video/build/segments/05-interactive.mp4 +0 -0
- package/docs/video/build/segments/05-interactive.txt +5 -0
- package/docs/video/build/segments/06-dryrun.mp4 +0 -0
- package/docs/video/build/segments/06-dryrun.txt +3 -0
- package/docs/video/build/segments/06-onboard.mp4 +0 -0
- package/docs/video/build/segments/06-onboard.txt +5 -0
- package/docs/video/build/segments/06-run.mp4 +0 -0
- package/docs/video/build/segments/06-run.txt +3 -0
- package/docs/video/build/segments/07-onboard.mp4 +0 -0
- package/docs/video/build/segments/07-onboard.txt +5 -0
- package/docs/video/build/segments/07-verify.mp4 +0 -0
- package/docs/video/build/segments/07-verify.txt +5 -0
- package/docs/video/build/segments/08-closing.mp4 +0 -0
- package/docs/video/build/segments/08-closing.txt +3 -0
- package/docs/video/build/segments/08-doctor.mp4 +0 -0
- package/docs/video/build/segments/08-doctor.txt +3 -0
- package/docs/video/build/segments/09-rollback.mp4 +0 -0
- package/docs/video/build/segments/09-rollback.txt +3 -0
- package/docs/video/build/segments/10-principles.mp4 +0 -0
- package/docs/video/build/segments/10-principles.txt +3 -0
- package/docs/video/build/segments/11-closing.mp4 +0 -0
- package/docs/video/build/segments/11-closing.txt +3 -0
- package/docs/video/build/segments/final.txt +8 -0
- package/docs/video/build-ai/audio/01-hook.mp3 +0 -0
- package/docs/video/build-ai/audio/02-pain.mp3 +0 -0
- package/docs/video/build-ai/audio/03-solution.mp3 +0 -0
- package/docs/video/build-ai/audio/04-how.mp3 +0 -0
- package/docs/video/build-ai/audio/05-safe.mp3 +0 -0
- package/docs/video/build-ai/audio/06-closing.mp3 +0 -0
- package/docs/video/build-ai/clips/01-hook.mp4 +0 -0
- package/docs/video/build-ai/clips/02-pain.mp4 +0 -0
- package/docs/video/build-ai/clips/03-solution.mp4 +0 -0
- package/docs/video/build-ai/clips/04-how.mp4 +0 -0
- package/docs/video/build-ai/clips/05-safe.mp4 +0 -0
- package/docs/video/build-ai/clips/06-closing.mp4 +0 -0
- package/docs/video/build-ai/first-frame-02-pain.png +0 -0
- package/docs/video/build-ai/first-frame-03-solution.png +0 -0
- package/docs/video/build-ai/first-frame-04-how.png +0 -0
- package/docs/video/build-ai/first-frame-05-safe.png +0 -0
- package/docs/video/build-ai/segments/01-hook.caption.txt +2 -0
- package/docs/video/build-ai/segments/01-hook.mp4 +0 -0
- package/docs/video/build-ai/segments/02-pain.mp4 +0 -0
- package/docs/video/build-ai/segments/03-solution.mp4 +0 -0
- package/docs/video/build-ai/segments/04-how.mp4 +0 -0
- package/docs/video/build-ai/segments/05-safe.mp4 +0 -0
- package/docs/video/build-ai/segments/06-closing.mp4 +0 -0
- package/docs/video/build-ai/segments/cap-01-hook.png +0 -0
- package/docs/video/build-ai/segments/cap-02-pain.png +0 -0
- package/docs/video/build-ai/segments/cap-03-solution.png +0 -0
- package/docs/video/build-ai/segments/cap-04-how.png +0 -0
- package/docs/video/build-ai/segments/cap-05-safe.png +0 -0
- package/docs/video/build-ai/segments/cap-06-closing.png +0 -0
- package/docs/video/build-ai/segments/final.txt +6 -0
- package/docs/video/build-posters/audio/01-hook.mp3 +0 -0
- package/docs/video/build-posters/audio/02-pain.mp3 +0 -0
- package/docs/video/build-posters/audio/03-solution.mp3 +0 -0
- package/docs/video/build-posters/audio/04-how.mp3 +0 -0
- package/docs/video/build-posters/audio/05-safe.mp3 +0 -0
- package/docs/video/build-posters/audio/06-closing.mp3 +0 -0
- package/docs/video/build-posters/cap-01-hook.png +0 -0
- package/docs/video/build-posters/cap-02-pain.png +0 -0
- package/docs/video/build-posters/cap-03-solution.png +0 -0
- package/docs/video/build-posters/cap-04-how.png +0 -0
- package/docs/video/build-posters/cap-05-safe.png +0 -0
- package/docs/video/build-posters/cap-06-closing.png +0 -0
- package/docs/video/build-posters/card-01-hook.png +0 -0
- package/docs/video/build-posters/card-06-closing.png +0 -0
- package/docs/video/build-posters/segments/01-hook.mp4 +0 -0
- package/docs/video/build-posters/segments/02-pain.mp4 +0 -0
- package/docs/video/build-posters/segments/03-solution.mp4 +0 -0
- package/docs/video/build-posters/segments/04-how.mp4 +0 -0
- package/docs/video/build-posters/segments/05-safe.mp4 +0 -0
- package/docs/video/build-posters/segments/06-closing.mp4 +0 -0
- package/docs/video/build-posters/segments/final.txt +6 -0
- package/docs/video/generate-video-ai.mjs +334 -0
- package/docs/video/generate-video-posters.mjs +204 -0
- package/docs/video/generate-video.mjs +426 -0
- package/docs/video/outline.zh-CN.md +102 -0
- package/docs/video/trellis-demo-video-ai.mp4 +0 -0
- package/docs/video/trellis-demo-video.mp4 +0 -0
- package/docs/video/voiceover.zh-CN.md +49 -0
- package/package.json +6 -2
- package/schema/builtin-skills/trellis-runtime/SKILL.md +117 -0
- package/schema/capability-selection.example.yaml +23 -0
- package/schema/scope.example.yaml +1 -1
- package/schema/servers.example.yaml +15 -1
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/** Ownership record for the two ZCode native-Skill controls Trellis changes
|
|
2
|
+
* during strict Runtime delivery. MCP ownership stays in mcpOwnership.ts;
|
|
3
|
+
* this separate record lets a later native/both delivery restore the user's
|
|
4
|
+
* prior values without treating an ordinary config key as an MCP server. */
|
|
5
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
export function zcodeSettingsOwnershipPath(homeDir) {
|
|
8
|
+
return join(homeDir, ".trellis", "zcode", "settings-ownership.json");
|
|
9
|
+
}
|
|
10
|
+
export function loadZcodeSettingsOwnership(homeDir) {
|
|
11
|
+
const path = zcodeSettingsOwnershipPath(homeDir);
|
|
12
|
+
if (!existsSync(path))
|
|
13
|
+
return undefined;
|
|
14
|
+
try {
|
|
15
|
+
const value = JSON.parse(readFileSync(path, "utf8"));
|
|
16
|
+
if (typeof value.configPath !== "string"
|
|
17
|
+
|| typeof value.managed?.featuresSkill !== "boolean"
|
|
18
|
+
|| typeof value.managed?.skillsEnabled !== "boolean"
|
|
19
|
+
|| (value.previous?.featuresSkill !== null && typeof value.previous?.featuresSkill !== "boolean")
|
|
20
|
+
|| (value.previous?.skillsEnabled !== null && typeof value.previous?.skillsEnabled !== "boolean"))
|
|
21
|
+
return undefined;
|
|
22
|
+
return value;
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export function saveZcodeSettingsOwnership(homeDir, value) {
|
|
29
|
+
const path = zcodeSettingsOwnershipPath(homeDir);
|
|
30
|
+
mkdirSync(join(homeDir, ".trellis", "zcode"), { recursive: true });
|
|
31
|
+
writeFileSync(path, `${JSON.stringify(value, null, 2)}\n`);
|
|
32
|
+
}
|
|
33
|
+
export function forgetZcodeSettingsOwnership(homeDir) {
|
|
34
|
+
const path = zcodeSettingsOwnershipPath(homeDir);
|
|
35
|
+
if (existsSync(path))
|
|
36
|
+
writeFileSync(path, "");
|
|
37
|
+
}
|
package/dist/pi-bridge/bundle.js
CHANGED
|
@@ -15149,12 +15149,17 @@ var ALL_AGENTS = [
|
|
|
15149
15149
|
"claude-code",
|
|
15150
15150
|
"codex",
|
|
15151
15151
|
"kiro",
|
|
15152
|
-
"pi"
|
|
15152
|
+
"pi",
|
|
15153
|
+
"kimi-code",
|
|
15154
|
+
"zcode"
|
|
15153
15155
|
];
|
|
15154
15156
|
function resolveScope(scope, managedAgents) {
|
|
15155
15157
|
const candidates = scope ?? managedAgents;
|
|
15156
15158
|
return candidates.filter((id) => managedAgents.includes(id));
|
|
15157
15159
|
}
|
|
15160
|
+
function capabilityDeliveryForAgent(agentId, mcp) {
|
|
15161
|
+
return mcp.runtime?.delivery?.[agentId] ?? "native";
|
|
15162
|
+
}
|
|
15158
15163
|
|
|
15159
15164
|
// src/core/canonical.ts
|
|
15160
15165
|
function fromServerDefYaml(def) {
|
|
@@ -15212,9 +15217,34 @@ function loadServersYaml(path) {
|
|
|
15212
15217
|
servers,
|
|
15213
15218
|
knownHostInjected: parsed.known_host_injected ?? [],
|
|
15214
15219
|
hub: parsed.hub,
|
|
15215
|
-
gateway: fromGatewayYaml(parsed.gateway)
|
|
15220
|
+
gateway: fromGatewayYaml(parsed.gateway),
|
|
15221
|
+
routes: fromRoutesYaml(parsed.routes),
|
|
15222
|
+
runtime: fromRuntimeYaml(parsed.runtime)
|
|
15216
15223
|
};
|
|
15217
15224
|
}
|
|
15225
|
+
function fromRuntimeYaml(runtime) {
|
|
15226
|
+
if (!runtime?.delivery) return void 0;
|
|
15227
|
+
const delivery = {};
|
|
15228
|
+
for (const [rawAgent, rawDelivery] of Object.entries(runtime.delivery)) {
|
|
15229
|
+
if (!ALL_AGENTS.includes(rawAgent)) continue;
|
|
15230
|
+
if (rawDelivery !== "native" && rawDelivery !== "mcp" && rawDelivery !== "both") continue;
|
|
15231
|
+
delivery[rawAgent] = rawDelivery;
|
|
15232
|
+
}
|
|
15233
|
+
return Object.keys(delivery).length > 0 ? { delivery } : void 0;
|
|
15234
|
+
}
|
|
15235
|
+
function fromRoutesYaml(routes) {
|
|
15236
|
+
if (!routes) return void 0;
|
|
15237
|
+
const out = {};
|
|
15238
|
+
for (const [rawAgent, rawRoute] of Object.entries(routes)) {
|
|
15239
|
+
if (!ALL_AGENTS.includes(rawAgent)) continue;
|
|
15240
|
+
if (!rawRoute?.mode || !["direct", "gateway", "hub"].includes(rawRoute.mode)) continue;
|
|
15241
|
+
out[rawAgent] = {
|
|
15242
|
+
mode: rawRoute.mode,
|
|
15243
|
+
...rawRoute.servers ? { servers: rawRoute.servers } : {}
|
|
15244
|
+
};
|
|
15245
|
+
}
|
|
15246
|
+
return Object.keys(out).length > 0 ? out : void 0;
|
|
15247
|
+
}
|
|
15218
15248
|
function fromGatewayYaml(gateway) {
|
|
15219
15249
|
if (!gateway) return void 0;
|
|
15220
15250
|
const out = { enabled: gateway.enabled === true };
|
|
@@ -15357,14 +15387,28 @@ function resolveSecretEnv(names, policy) {
|
|
|
15357
15387
|
|
|
15358
15388
|
// src/adapters/mcpPlan.ts
|
|
15359
15389
|
var HUB_ENTRY_NAME = "trellis-hub";
|
|
15360
|
-
var GATEWAY_ENTRY_NAME = "trellis
|
|
15390
|
+
var GATEWAY_ENTRY_NAME = "trellis";
|
|
15391
|
+
var RUNTIME_ENTRY_NAME = "trellis";
|
|
15361
15392
|
var GATEWAY_COMMAND = "trellis";
|
|
15393
|
+
var RUNTIME_COMMAND = "trellis";
|
|
15394
|
+
function isOAuthMcpServer(def) {
|
|
15395
|
+
return def.auth === "oauth";
|
|
15396
|
+
}
|
|
15362
15397
|
function isGatewayAgent(agentId, mcp, managedAgents) {
|
|
15398
|
+
const route = mcp.routes?.[agentId];
|
|
15399
|
+
if (route) return route.mode === "gateway";
|
|
15363
15400
|
const gateway = mcp.gateway;
|
|
15364
15401
|
if (!gateway?.enabled) return false;
|
|
15365
15402
|
if (gateway.agents) return gateway.agents.includes(agentId);
|
|
15366
15403
|
return managedAgents.includes(agentId);
|
|
15367
15404
|
}
|
|
15405
|
+
function routeForAgent(agentId, mcp, managedAgents) {
|
|
15406
|
+
const explicit = mcp.routes?.[agentId];
|
|
15407
|
+
if (explicit) return explicit;
|
|
15408
|
+
if (isGatewayAgent(agentId, mcp, managedAgents)) return { mode: "gateway" };
|
|
15409
|
+
if (mcp.hub) return { mode: "hub" };
|
|
15410
|
+
return { mode: "direct" };
|
|
15411
|
+
}
|
|
15368
15412
|
var CODEX_STDIO_URL_CRASH_NOTE = ' On Codex specifically, this crashes the entire process at startup ("url is not supported for stdio"), not just this one server \u2014 see docs/research.md.';
|
|
15369
15413
|
function collisionMessage(name, agentId) {
|
|
15370
15414
|
return `refusing to write MCP server "${name}": also appears in known_host_injected.${agentId === "codex" ? CODEX_STDIO_URL_CRASH_NOTE : ""}`;
|
|
@@ -15402,36 +15446,102 @@ function findUnresolvedEnvName(def, policy) {
|
|
|
15402
15446
|
return names.find((name) => !resolved[name]);
|
|
15403
15447
|
}
|
|
15404
15448
|
function resolveMcpPlan(agentId, mcp, managedAgents, policy) {
|
|
15405
|
-
|
|
15406
|
-
|
|
15449
|
+
const route = routeForAgent(agentId, mcp, managedAgents);
|
|
15450
|
+
const runtimeEnabled = capabilityDeliveryForAgent(agentId, mcp) !== "native";
|
|
15451
|
+
if (route.mode === "gateway") {
|
|
15452
|
+
const entryName = runtimeEnabled ? RUNTIME_ENTRY_NAME : GATEWAY_ENTRY_NAME;
|
|
15453
|
+
if (mcp.knownHostInjected.includes(entryName)) {
|
|
15407
15454
|
return {
|
|
15408
15455
|
desired: [],
|
|
15409
|
-
conflicts: [{ name:
|
|
15456
|
+
conflicts: [{ name: entryName, message: collisionMessage(entryName, agentId), remediation: collisionRemediation(entryName) }]
|
|
15410
15457
|
};
|
|
15411
15458
|
}
|
|
15459
|
+
const oauth = planDirectServers(agentId, mcp, managedAgents, policy, route, isOAuthMcpServer);
|
|
15412
15460
|
return {
|
|
15413
|
-
desired: [{
|
|
15414
|
-
|
|
15461
|
+
desired: [{
|
|
15462
|
+
name: entryName,
|
|
15463
|
+
def: {
|
|
15464
|
+
transport: "stdio",
|
|
15465
|
+
command: runtimeEnabled ? RUNTIME_COMMAND : GATEWAY_COMMAND,
|
|
15466
|
+
args: [runtimeEnabled ? "mcp-runtime" : "mcp-gateway", "--agent", agentId]
|
|
15467
|
+
}
|
|
15468
|
+
}, ...oauth.desired],
|
|
15469
|
+
conflicts: oauth.conflicts
|
|
15415
15470
|
};
|
|
15416
15471
|
}
|
|
15417
|
-
if (
|
|
15472
|
+
if (route.mode === "hub") {
|
|
15473
|
+
if (!mcp.hub) {
|
|
15474
|
+
return {
|
|
15475
|
+
desired: [],
|
|
15476
|
+
conflicts: [{
|
|
15477
|
+
name: HUB_ENTRY_NAME,
|
|
15478
|
+
message: `cannot resolve hub route "${agentId}": canonical mcp.hub.url is not configured`,
|
|
15479
|
+
remediation: `configure mcp.hub.url or choose direct/gateway mode for ${agentId}`
|
|
15480
|
+
}]
|
|
15481
|
+
};
|
|
15482
|
+
}
|
|
15418
15483
|
if (mcp.knownHostInjected.includes(HUB_ENTRY_NAME)) {
|
|
15419
15484
|
return {
|
|
15420
15485
|
desired: [],
|
|
15421
15486
|
conflicts: [{ name: HUB_ENTRY_NAME, message: collisionMessage(HUB_ENTRY_NAME, agentId), remediation: collisionRemediation(HUB_ENTRY_NAME) }]
|
|
15422
15487
|
};
|
|
15423
15488
|
}
|
|
15424
|
-
|
|
15489
|
+
if (route.servers) {
|
|
15490
|
+
return {
|
|
15491
|
+
desired: [],
|
|
15492
|
+
conflicts: [{
|
|
15493
|
+
name: HUB_ENTRY_NAME,
|
|
15494
|
+
message: `cannot restrict external hub route "${agentId}" to individual MCP servers: the hub owns its upstream set`,
|
|
15495
|
+
remediation: `remove the servers list from the ${agentId} hub route, or use direct/gateway mode for per-server selection`
|
|
15496
|
+
}]
|
|
15497
|
+
};
|
|
15498
|
+
}
|
|
15499
|
+
const desired = [{ name: HUB_ENTRY_NAME, def: { transport: "http", url: mcp.hub.url } }];
|
|
15500
|
+
if (runtimeEnabled) {
|
|
15501
|
+
if (mcp.knownHostInjected.includes(RUNTIME_ENTRY_NAME)) {
|
|
15502
|
+
return {
|
|
15503
|
+
desired: [],
|
|
15504
|
+
conflicts: [{ name: RUNTIME_ENTRY_NAME, message: collisionMessage(RUNTIME_ENTRY_NAME, agentId), remediation: collisionRemediation(RUNTIME_ENTRY_NAME) }]
|
|
15505
|
+
};
|
|
15506
|
+
}
|
|
15507
|
+
desired.push({
|
|
15508
|
+
name: RUNTIME_ENTRY_NAME,
|
|
15509
|
+
def: { transport: "stdio", command: RUNTIME_COMMAND, args: ["mcp-runtime", "--agent", agentId] }
|
|
15510
|
+
});
|
|
15511
|
+
}
|
|
15512
|
+
return { desired, conflicts: [] };
|
|
15513
|
+
}
|
|
15514
|
+
if (runtimeEnabled) {
|
|
15515
|
+
if (mcp.knownHostInjected.includes(RUNTIME_ENTRY_NAME)) {
|
|
15516
|
+
return {
|
|
15517
|
+
desired: [],
|
|
15518
|
+
conflicts: [{ name: RUNTIME_ENTRY_NAME, message: collisionMessage(RUNTIME_ENTRY_NAME, agentId), remediation: collisionRemediation(RUNTIME_ENTRY_NAME) }]
|
|
15519
|
+
};
|
|
15520
|
+
}
|
|
15521
|
+
return {
|
|
15522
|
+
desired: [{
|
|
15523
|
+
name: RUNTIME_ENTRY_NAME,
|
|
15524
|
+
def: { transport: "stdio", command: RUNTIME_COMMAND, args: ["mcp-runtime", "--agent", agentId] }
|
|
15525
|
+
}],
|
|
15526
|
+
conflicts: []
|
|
15527
|
+
};
|
|
15425
15528
|
}
|
|
15529
|
+
return planDirectServers(agentId, mcp, managedAgents, policy, route);
|
|
15530
|
+
}
|
|
15531
|
+
function planDirectServers(agentId, mcp, managedAgents, policy, route, filter) {
|
|
15426
15532
|
const desired = [];
|
|
15427
15533
|
const conflicts = [];
|
|
15428
15534
|
for (const [name, def] of Object.entries(mcp.servers)) {
|
|
15429
15535
|
if (def.enabled === false) {
|
|
15430
15536
|
continue;
|
|
15431
15537
|
}
|
|
15538
|
+
if (filter && !filter(def)) continue;
|
|
15432
15539
|
if (!isInScope(agentId, def.agents, managedAgents)) {
|
|
15433
15540
|
continue;
|
|
15434
15541
|
}
|
|
15542
|
+
if (route.servers && !route.servers.includes(name)) {
|
|
15543
|
+
continue;
|
|
15544
|
+
}
|
|
15435
15545
|
if (mcp.knownHostInjected.includes(name)) {
|
|
15436
15546
|
conflicts.push({ name, message: collisionMessage(name, agentId), remediation: collisionRemediation(name) });
|
|
15437
15547
|
continue;
|
|
@@ -27579,6 +27689,7 @@ function extractTemplateVarNames(value) {
|
|
|
27579
27689
|
|
|
27580
27690
|
// src/lib/mcpConnect.ts
|
|
27581
27691
|
var DEFAULT_CONNECT_TIMEOUT_MS = 1e4;
|
|
27692
|
+
var FAILED_TRANSPORT_CLEANUP_TIMEOUT_MS = 1e3;
|
|
27582
27693
|
function withTimeout(promise, timeoutMs, message) {
|
|
27583
27694
|
return new Promise((resolve, reject) => {
|
|
27584
27695
|
const timer = setTimeout(() => reject(new Error(message)), timeoutMs);
|
|
@@ -27600,7 +27711,11 @@ async function connectWithCleanup(client, transport, timeoutMs, message) {
|
|
|
27600
27711
|
return client;
|
|
27601
27712
|
} catch (err) {
|
|
27602
27713
|
try {
|
|
27603
|
-
await
|
|
27714
|
+
await withTimeout(
|
|
27715
|
+
Promise.resolve().then(() => transport.close()),
|
|
27716
|
+
FAILED_TRANSPORT_CLEANUP_TIMEOUT_MS,
|
|
27717
|
+
"transport cleanup timed out"
|
|
27718
|
+
);
|
|
27604
27719
|
} catch {
|
|
27605
27720
|
}
|
|
27606
27721
|
throw err;
|
|
@@ -27662,6 +27777,325 @@ function connectServer(def, secretsPolicy, timeoutMs, clientInfo) {
|
|
|
27662
27777
|
return connectStdio(def, secretsPolicy, timeoutMs, clientInfo);
|
|
27663
27778
|
}
|
|
27664
27779
|
|
|
27780
|
+
// src/lib/mcpToolRegistry.ts
|
|
27781
|
+
var MAX_EXPOSED_TOOL_NAME_LENGTH = 64;
|
|
27782
|
+
var SHORT_EXPOSED_TOOL_NAME_LENGTH = 12;
|
|
27783
|
+
var EXPOSED_TOOL_NAME_PATTERN = /^[a-zA-Z0-9_-]+$/;
|
|
27784
|
+
var TOOL_NAME_SEPARATOR = "__";
|
|
27785
|
+
function stableHash(value) {
|
|
27786
|
+
let hash = 2166136261;
|
|
27787
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
27788
|
+
hash ^= value.charCodeAt(index);
|
|
27789
|
+
hash = Math.imul(hash, 16777619);
|
|
27790
|
+
}
|
|
27791
|
+
return (hash >>> 0).toString(16).padStart(8, "0");
|
|
27792
|
+
}
|
|
27793
|
+
function safeSegment(value, fallback) {
|
|
27794
|
+
const normalized = value.replace(/[^a-zA-Z0-9_-]+/g, "_");
|
|
27795
|
+
return normalized || fallback;
|
|
27796
|
+
}
|
|
27797
|
+
function compactSourceSegment(value) {
|
|
27798
|
+
const normalized = safeSegment(value, "server");
|
|
27799
|
+
return safeSegment(normalized.replace(/^mcp(?:_|-)+/i, ""), "server");
|
|
27800
|
+
}
|
|
27801
|
+
function boundedName(value, identity) {
|
|
27802
|
+
if (value.length <= MAX_EXPOSED_TOOL_NAME_LENGTH && EXPOSED_TOOL_NAME_PATTERN.test(value)) return value;
|
|
27803
|
+
const suffix = `__${stableHash(identity)}`;
|
|
27804
|
+
const prefixLength = Math.max(1, MAX_EXPOSED_TOOL_NAME_LENGTH - suffix.length);
|
|
27805
|
+
return `${value.slice(0, prefixLength)}${suffix}`;
|
|
27806
|
+
}
|
|
27807
|
+
function normalizedToolName(toolName) {
|
|
27808
|
+
return boundedName(safeSegment(toolName, "tool"), `tool\0${toolName}`);
|
|
27809
|
+
}
|
|
27810
|
+
function prefixedToolName(serverName, toolName) {
|
|
27811
|
+
const server = compactSourceSegment(serverName);
|
|
27812
|
+
const tool = safeSegment(toolName, "tool");
|
|
27813
|
+
return boundedName(`${server}${TOOL_NAME_SEPARATOR}${tool}`, `prefixed\0${serverName}\0${toolName}`);
|
|
27814
|
+
}
|
|
27815
|
+
function allocateExposedToolNames(candidates) {
|
|
27816
|
+
const counts = /* @__PURE__ */ new Map();
|
|
27817
|
+
for (const candidate of candidates) counts.set(candidate.toolName, (counts.get(candidate.toolName) ?? 0) + 1);
|
|
27818
|
+
const ordered = candidates.map((candidate, index) => ({ candidate, index })).sort((left, right) => left.candidate.serverName.localeCompare(right.candidate.serverName) || left.candidate.toolName.localeCompare(right.candidate.toolName) || left.index - right.index);
|
|
27819
|
+
const allocated = new Array(candidates.length);
|
|
27820
|
+
const used = /* @__PURE__ */ new Set();
|
|
27821
|
+
for (const { candidate, index } of ordered) {
|
|
27822
|
+
const normalized = normalizedToolName(candidate.toolName);
|
|
27823
|
+
const source = compactSourceSegment(candidate.serverName);
|
|
27824
|
+
const sourceQualified = normalized.includes("__") || normalized === source || normalized.startsWith(`${source}_`) || normalized.startsWith(`${source}__`);
|
|
27825
|
+
const unique = (counts.get(candidate.toolName) ?? 0) === 1;
|
|
27826
|
+
const base = unique && normalized.length > SHORT_EXPOSED_TOOL_NAME_LENGTH ? normalized : unique && sourceQualified ? normalized : prefixedToolName(candidate.serverName, candidate.toolName);
|
|
27827
|
+
let exposed = base;
|
|
27828
|
+
let suffix = 2;
|
|
27829
|
+
while (used.has(exposed)) {
|
|
27830
|
+
exposed = boundedName(`${base}${TOOL_NAME_SEPARATOR}${suffix}`, `collision\0${candidate.serverName}\0${candidate.toolName}\0${suffix}`);
|
|
27831
|
+
suffix += 1;
|
|
27832
|
+
}
|
|
27833
|
+
used.add(exposed);
|
|
27834
|
+
allocated[index] = exposed;
|
|
27835
|
+
}
|
|
27836
|
+
return allocated;
|
|
27837
|
+
}
|
|
27838
|
+
var McpToolRegistry = class {
|
|
27839
|
+
entries = /* @__PURE__ */ new Map();
|
|
27840
|
+
order = [];
|
|
27841
|
+
candidates = [];
|
|
27842
|
+
rebuild() {
|
|
27843
|
+
this.entries.clear();
|
|
27844
|
+
this.order.length = 0;
|
|
27845
|
+
const allocated = allocateExposedToolNames(this.candidates);
|
|
27846
|
+
const ordered = this.candidates.map((candidate, index) => ({ candidate, index, exposed: allocated[index] })).sort((left, right) => left.candidate.serverName.localeCompare(right.candidate.serverName) || left.candidate.toolName.localeCompare(right.candidate.toolName) || left.index - right.index);
|
|
27847
|
+
for (const { candidate, exposed } of ordered) {
|
|
27848
|
+
this.entries.set(exposed, candidate);
|
|
27849
|
+
this.order.push(exposed);
|
|
27850
|
+
}
|
|
27851
|
+
}
|
|
27852
|
+
/**
|
|
27853
|
+
* Register one connected upstream's tools. Callers add each upstream
|
|
27854
|
+
* independently, so an upstream that failed to connect or to list its
|
|
27855
|
+
* tools is simply never added — every other upstream's tools remain
|
|
27856
|
+
* aggregated, with no special-casing here.
|
|
27857
|
+
*/
|
|
27858
|
+
add(serverName, upstream, tools) {
|
|
27859
|
+
for (const tool of tools) this.candidates.push({ serverName, toolName: tool.name, upstream, tool });
|
|
27860
|
+
this.rebuild();
|
|
27861
|
+
const added = tools.map((tool) => this.exposedNameFor(serverName, tool.name)).filter((name) => name !== void 0);
|
|
27862
|
+
return { added, skipped: [] };
|
|
27863
|
+
}
|
|
27864
|
+
exposedNameFor(serverName, toolName) {
|
|
27865
|
+
for (const [name, entry] of this.entries) {
|
|
27866
|
+
if (entry.serverName === serverName && entry.toolName === toolName) return name;
|
|
27867
|
+
}
|
|
27868
|
+
return void 0;
|
|
27869
|
+
}
|
|
27870
|
+
/** Every registered tool, under its prefixed name, in registration
|
|
27871
|
+
* order. Each tool's own `description`/`inputSchema` and any additional
|
|
27872
|
+
* fields the upstream sent are preserved verbatim; only `name` changes. */
|
|
27873
|
+
listTools() {
|
|
27874
|
+
this.rebuild();
|
|
27875
|
+
return this.order.map((name) => {
|
|
27876
|
+
const entry = this.entries.get(name);
|
|
27877
|
+
const title = name === entry.toolName || entry.tool.title !== void 0 ? entry.tool.title : `${entry.serverName} / ${entry.toolName}`;
|
|
27878
|
+
return { ...entry.tool, name, ...title === void 0 ? {} : { title } };
|
|
27879
|
+
});
|
|
27880
|
+
}
|
|
27881
|
+
/** The owning upstream and original tool name for a prefixed name, or
|
|
27882
|
+
* `undefined` if nothing owns it. */
|
|
27883
|
+
resolve(prefixedName) {
|
|
27884
|
+
this.rebuild();
|
|
27885
|
+
const entry = this.entries.get(prefixedName);
|
|
27886
|
+
if (!entry) return void 0;
|
|
27887
|
+
return { serverName: entry.serverName, toolName: entry.toolName, upstream: entry.upstream };
|
|
27888
|
+
}
|
|
27889
|
+
/**
|
|
27890
|
+
* Route a call to the owning upstream, unprefixing the name on the way
|
|
27891
|
+
* out — an upstream knows its tool only by its own name, never by the
|
|
27892
|
+
* aggregated one. A name no upstream owns throws rather than guessing.
|
|
27893
|
+
*/
|
|
27894
|
+
async callTool(prefixedName, args) {
|
|
27895
|
+
const target = this.resolve(prefixedName);
|
|
27896
|
+
if (!target) {
|
|
27897
|
+
throw new Error(`unknown tool "${prefixedName}"`);
|
|
27898
|
+
}
|
|
27899
|
+
return target.upstream.callTool({ name: target.toolName, arguments: args });
|
|
27900
|
+
}
|
|
27901
|
+
/** Distinct server names with at least one registered tool, in
|
|
27902
|
+
* registration order. */
|
|
27903
|
+
servers() {
|
|
27904
|
+
this.rebuild();
|
|
27905
|
+
const seen = [];
|
|
27906
|
+
for (const name of this.order) {
|
|
27907
|
+
const { serverName } = this.entries.get(name);
|
|
27908
|
+
if (!seen.includes(serverName)) seen.push(serverName);
|
|
27909
|
+
}
|
|
27910
|
+
return seen;
|
|
27911
|
+
}
|
|
27912
|
+
get size() {
|
|
27913
|
+
this.rebuild();
|
|
27914
|
+
return this.entries.size;
|
|
27915
|
+
}
|
|
27916
|
+
};
|
|
27917
|
+
|
|
27918
|
+
// src/lib/oauth/store.ts
|
|
27919
|
+
import { chmodSync, existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync3, renameSync, rmSync, writeFileSync as writeFileSync2 } from "node:fs";
|
|
27920
|
+
import { join as join2 } from "node:path";
|
|
27921
|
+
function oauthDir(homeDir) {
|
|
27922
|
+
return join2(homeDir, ".trellis", "mcp", "oauth");
|
|
27923
|
+
}
|
|
27924
|
+
function tokenPath(homeDir, serverName) {
|
|
27925
|
+
assertSafeServerName(serverName);
|
|
27926
|
+
return join2(oauthDir(homeDir), `${serverName}.json`);
|
|
27927
|
+
}
|
|
27928
|
+
function lockPath(homeDir, serverName) {
|
|
27929
|
+
assertSafeServerName(serverName);
|
|
27930
|
+
return join2(oauthDir(homeDir), `${serverName}.lock`);
|
|
27931
|
+
}
|
|
27932
|
+
function assertSafeServerName(serverName) {
|
|
27933
|
+
if (serverName === "" || serverName === "." || serverName === ".." || /[/\\]/.test(serverName) || serverName.startsWith(".")) {
|
|
27934
|
+
throw new Error(`refusing to use MCP server name "${serverName}" as an OAuth credential filename`);
|
|
27935
|
+
}
|
|
27936
|
+
}
|
|
27937
|
+
function readToken(homeDir, serverName) {
|
|
27938
|
+
const path = tokenPath(homeDir, serverName);
|
|
27939
|
+
if (!existsSync3(path)) return void 0;
|
|
27940
|
+
try {
|
|
27941
|
+
return JSON.parse(readFileSync3(path, "utf-8"));
|
|
27942
|
+
} catch {
|
|
27943
|
+
return void 0;
|
|
27944
|
+
}
|
|
27945
|
+
}
|
|
27946
|
+
function writeToken(homeDir, serverName, token) {
|
|
27947
|
+
const dir = oauthDir(homeDir);
|
|
27948
|
+
mkdirSync3(dir, { recursive: true, mode: 448 });
|
|
27949
|
+
const path = tokenPath(homeDir, serverName);
|
|
27950
|
+
const tmp = `${path}.tmp`;
|
|
27951
|
+
writeFileSync2(tmp, `${JSON.stringify(token, null, 2)}
|
|
27952
|
+
`, { mode: 384 });
|
|
27953
|
+
chmodSync(tmp, 384);
|
|
27954
|
+
renameSync(tmp, path);
|
|
27955
|
+
chmodSync(path, 384);
|
|
27956
|
+
}
|
|
27957
|
+
function isExpired(token, skewMs = 6e4, now = Date.now()) {
|
|
27958
|
+
if (token.expiresAt === void 0) return false;
|
|
27959
|
+
return token.expiresAt - skewMs <= now;
|
|
27960
|
+
}
|
|
27961
|
+
|
|
27962
|
+
// src/lib/oauth/lock.ts
|
|
27963
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync4, openSync, closeSync, readFileSync as readFileSync4, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "node:fs";
|
|
27964
|
+
import { dirname as dirname2 } from "node:path";
|
|
27965
|
+
var STALE_AFTER_MS = 6e4;
|
|
27966
|
+
var POLL_INTERVAL_MS = 50;
|
|
27967
|
+
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
27968
|
+
function processAlive(pid) {
|
|
27969
|
+
try {
|
|
27970
|
+
process.kill(pid, 0);
|
|
27971
|
+
return true;
|
|
27972
|
+
} catch (err) {
|
|
27973
|
+
return err.code === "EPERM";
|
|
27974
|
+
}
|
|
27975
|
+
}
|
|
27976
|
+
function isStale(path, now) {
|
|
27977
|
+
let contents;
|
|
27978
|
+
try {
|
|
27979
|
+
contents = JSON.parse(readFileSync4(path, "utf-8"));
|
|
27980
|
+
} catch {
|
|
27981
|
+
return true;
|
|
27982
|
+
}
|
|
27983
|
+
if (typeof contents.pid !== "number" || typeof contents.acquiredAt !== "number") return true;
|
|
27984
|
+
if (now - contents.acquiredAt > STALE_AFTER_MS) return true;
|
|
27985
|
+
return !processAlive(contents.pid);
|
|
27986
|
+
}
|
|
27987
|
+
async function withServerLock(homeDir, serverName, fn, opts = {}) {
|
|
27988
|
+
const path = lockPath(homeDir, serverName);
|
|
27989
|
+
const now = opts.now ?? Date.now;
|
|
27990
|
+
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
27991
|
+
const deadline = now() + timeoutMs;
|
|
27992
|
+
mkdirSync4(dirname2(path), { recursive: true, mode: 448 });
|
|
27993
|
+
for (; ; ) {
|
|
27994
|
+
try {
|
|
27995
|
+
const fd = openSync(path, "wx", 384);
|
|
27996
|
+
try {
|
|
27997
|
+
writeFileSync3(fd, JSON.stringify({ pid: process.pid, acquiredAt: now() }));
|
|
27998
|
+
} finally {
|
|
27999
|
+
closeSync(fd);
|
|
28000
|
+
}
|
|
28001
|
+
break;
|
|
28002
|
+
} catch (err) {
|
|
28003
|
+
if (err.code !== "EEXIST") throw err;
|
|
28004
|
+
if (existsSync4(path) && isStale(path, now())) {
|
|
28005
|
+
rmSync2(path, { force: true });
|
|
28006
|
+
continue;
|
|
28007
|
+
}
|
|
28008
|
+
if (now() >= deadline) {
|
|
28009
|
+
throw new Error(`timed out waiting for the OAuth refresh lock on "${serverName}" (${path})`);
|
|
28010
|
+
}
|
|
28011
|
+
await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
|
|
28012
|
+
}
|
|
28013
|
+
}
|
|
28014
|
+
try {
|
|
28015
|
+
return await fn();
|
|
28016
|
+
} finally {
|
|
28017
|
+
rmSync2(path, { force: true });
|
|
28018
|
+
}
|
|
28019
|
+
}
|
|
28020
|
+
|
|
28021
|
+
// src/lib/oauth/refresh.ts
|
|
28022
|
+
var RefreshError = class extends Error {
|
|
28023
|
+
};
|
|
28024
|
+
async function ensureFreshToken(homeDir, serverName, opts = {}) {
|
|
28025
|
+
const now = opts.now ?? Date.now;
|
|
28026
|
+
const existing = readToken(homeDir, serverName);
|
|
28027
|
+
if (!existing) return void 0;
|
|
28028
|
+
if (!isExpired(existing, opts.skewMs, now())) return existing;
|
|
28029
|
+
if (!existing.refreshToken) {
|
|
28030
|
+
throw new RefreshError(`OAuth token for "${serverName}" has expired and there is no refresh token \u2014 run \`trellis mcp auth ${serverName}\``);
|
|
28031
|
+
}
|
|
28032
|
+
return withServerLock(
|
|
28033
|
+
homeDir,
|
|
28034
|
+
serverName,
|
|
28035
|
+
async () => {
|
|
28036
|
+
const current = readToken(homeDir, serverName);
|
|
28037
|
+
if (current && !isExpired(current, opts.skewMs, now())) return current;
|
|
28038
|
+
const source = current ?? existing;
|
|
28039
|
+
if (!source.refreshToken) {
|
|
28040
|
+
throw new RefreshError(`OAuth token for "${serverName}" has expired and there is no refresh token \u2014 run \`trellis mcp auth ${serverName}\``);
|
|
28041
|
+
}
|
|
28042
|
+
const refreshed = await performRefresh(serverName, source, opts);
|
|
28043
|
+
writeToken(homeDir, serverName, refreshed);
|
|
28044
|
+
return refreshed;
|
|
28045
|
+
},
|
|
28046
|
+
{ timeoutMs: opts.lockTimeoutMs, now }
|
|
28047
|
+
);
|
|
28048
|
+
}
|
|
28049
|
+
async function performRefresh(serverName, token, opts = {}) {
|
|
28050
|
+
const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
|
|
28051
|
+
const now = opts.now ?? Date.now;
|
|
28052
|
+
if (!token.tokenEndpoint) {
|
|
28053
|
+
throw new RefreshError(`OAuth token for "${serverName}" has no recorded token endpoint \u2014 run \`trellis mcp auth ${serverName}\``);
|
|
28054
|
+
}
|
|
28055
|
+
const body = new URLSearchParams({ grant_type: "refresh_token", refresh_token: token.refreshToken });
|
|
28056
|
+
if (token.clientId) body.set("client_id", token.clientId);
|
|
28057
|
+
if (token.clientSecret) body.set("client_secret", token.clientSecret);
|
|
28058
|
+
const response = await fetchImpl(token.tokenEndpoint, {
|
|
28059
|
+
method: "POST",
|
|
28060
|
+
headers: { "content-type": "application/x-www-form-urlencoded", accept: "application/json" },
|
|
28061
|
+
body: body.toString()
|
|
28062
|
+
});
|
|
28063
|
+
if (!response.ok) {
|
|
28064
|
+
throw new RefreshError(`refresh_token grant for "${serverName}" failed with HTTP ${response.status}: ${await safeText(response)}`);
|
|
28065
|
+
}
|
|
28066
|
+
const payload = await response.json();
|
|
28067
|
+
if (!payload.access_token) {
|
|
28068
|
+
throw new RefreshError(`refresh_token grant for "${serverName}" returned no access_token`);
|
|
28069
|
+
}
|
|
28070
|
+
return {
|
|
28071
|
+
...token,
|
|
28072
|
+
accessToken: payload.access_token,
|
|
28073
|
+
// Rotation: a server that issues a new refresh token has invalidated
|
|
28074
|
+
// the old one, so keeping the old one would be keeping a dead
|
|
28075
|
+
// credential. A server that omits it is signalling the existing one
|
|
28076
|
+
// stays valid.
|
|
28077
|
+
refreshToken: payload.refresh_token ?? token.refreshToken,
|
|
28078
|
+
expiresAt: payload.expires_in !== void 0 ? now() + payload.expires_in * 1e3 : void 0,
|
|
28079
|
+
scope: payload.scope ?? token.scope
|
|
28080
|
+
};
|
|
28081
|
+
}
|
|
28082
|
+
async function safeText(response) {
|
|
28083
|
+
try {
|
|
28084
|
+
return (await response.text()).slice(0, 200);
|
|
28085
|
+
} catch {
|
|
28086
|
+
return "<unreadable body>";
|
|
28087
|
+
}
|
|
28088
|
+
}
|
|
28089
|
+
|
|
28090
|
+
// src/lib/gatewayBackend.ts
|
|
28091
|
+
async function withOAuthHeader(name, def, homeDir) {
|
|
28092
|
+
const hasExplicitAuth = Object.keys(def.headers ?? {}).some((key) => key.toLowerCase() === "authorization");
|
|
28093
|
+
if (hasExplicitAuth) return def;
|
|
28094
|
+
const token = await ensureFreshToken(homeDir, name);
|
|
28095
|
+
if (!token) return def;
|
|
28096
|
+
return { ...def, headers: { ...def.headers ?? {}, Authorization: `Bearer ${token.accessToken}` } };
|
|
28097
|
+
}
|
|
28098
|
+
|
|
27665
28099
|
// node_modules/typebox/build/system/memory/memory.mjs
|
|
27666
28100
|
var memory_exports = {};
|
|
27667
28101
|
__export(memory_exports, {
|
|
@@ -32066,12 +32500,6 @@ __export(typebox_exports, {
|
|
|
32066
32500
|
With: () => With2
|
|
32067
32501
|
});
|
|
32068
32502
|
|
|
32069
|
-
// src/lib/mcpToolRegistry.ts
|
|
32070
|
-
var TOOL_NAME_SEPARATOR = "__";
|
|
32071
|
-
function prefixedToolName(serverName, toolName) {
|
|
32072
|
-
return `${serverName}${TOOL_NAME_SEPARATOR}${toolName}`;
|
|
32073
|
-
}
|
|
32074
|
-
|
|
32075
32503
|
// src/pi-bridge/schemaTranslate.ts
|
|
32076
32504
|
function toParametersSchema(inputSchema) {
|
|
32077
32505
|
return typebox_exports.Unsafe(inputSchema);
|
|
@@ -32095,27 +32523,14 @@ function toPiContent(items) {
|
|
|
32095
32523
|
|
|
32096
32524
|
// src/pi-bridge/index.ts
|
|
32097
32525
|
var CLIENT_INFO = { name: "trellis-mcp-bridge", version: "0.0.0" };
|
|
32098
|
-
function
|
|
32099
|
-
return withTimeout(client.listTools(), timeoutMs, `listTools timed out after ${timeoutMs}ms`).then((result) =>
|
|
32100
|
-
for (const tool of result.tools) {
|
|
32101
|
-
pi.registerTool({
|
|
32102
|
-
name: prefixedToolName(serverName, tool.name),
|
|
32103
|
-
label: tool.name,
|
|
32104
|
-
description: tool.description ?? `MCP tool "${tool.name}" from server "${serverName}"`,
|
|
32105
|
-
parameters: toParametersSchema(tool.inputSchema),
|
|
32106
|
-
async execute(_toolCallId, params) {
|
|
32107
|
-
const result2 = await client.callTool({ name: tool.name, arguments: params });
|
|
32108
|
-
const content = Array.isArray(result2.content) ? result2.content : [];
|
|
32109
|
-
return { content: toPiContent(content), details: result2 };
|
|
32110
|
-
}
|
|
32111
|
-
});
|
|
32112
|
-
}
|
|
32113
|
-
});
|
|
32526
|
+
function listServerTools(client, timeoutMs) {
|
|
32527
|
+
return withTimeout(client.listTools(), timeoutMs, `listTools timed out after ${timeoutMs}ms`).then((result) => result.tools);
|
|
32114
32528
|
}
|
|
32115
32529
|
async function trellisMcpBridge(pi, homeDir = homedir2(), connectTimeoutMs = DEFAULT_CONNECT_TIMEOUT_MS) {
|
|
32116
32530
|
const canonical = loadCanonicalSource(homeDir);
|
|
32117
32531
|
const { desired } = resolveMcpPlan("pi", canonical.mcp, ALL_AGENTS, canonical.secretsPolicy);
|
|
32118
32532
|
const clients = /* @__PURE__ */ new Set();
|
|
32533
|
+
const registry2 = new McpToolRegistry();
|
|
32119
32534
|
const closeClient = async (client) => {
|
|
32120
32535
|
if (!clients.delete(client)) return;
|
|
32121
32536
|
try {
|
|
@@ -32138,24 +32553,48 @@ async function trellisMcpBridge(pi, homeDir = homedir2(), connectTimeoutMs = DEF
|
|
|
32138
32553
|
);
|
|
32139
32554
|
};
|
|
32140
32555
|
pi.on?.("session_shutdown", closeAllClients);
|
|
32141
|
-
await Promise.all(
|
|
32556
|
+
const discovered = await Promise.all(
|
|
32142
32557
|
desired.map(async ({ name, def }) => {
|
|
32143
32558
|
let client;
|
|
32144
32559
|
try {
|
|
32145
|
-
|
|
32560
|
+
const effectiveDef = def.auth === "oauth" && def.url ? await withOAuthHeader(name, def, homeDir) : def;
|
|
32561
|
+
client = await connectServer(effectiveDef, canonical.secretsPolicy, connectTimeoutMs, CLIENT_INFO);
|
|
32146
32562
|
} catch (err) {
|
|
32147
32563
|
console.error(`trellis-mcp-bridge: failed to connect to MCP server "${name}": ${err instanceof Error ? err.message : String(err)}`);
|
|
32148
|
-
return;
|
|
32564
|
+
return void 0;
|
|
32149
32565
|
}
|
|
32150
32566
|
clients.add(client);
|
|
32151
32567
|
try {
|
|
32152
|
-
await
|
|
32568
|
+
const tools = await listServerTools(client, connectTimeoutMs);
|
|
32569
|
+
return { name, client, tools };
|
|
32153
32570
|
} catch (err) {
|
|
32154
32571
|
console.error(`trellis-mcp-bridge: failed to list tools for MCP server "${name}": ${err instanceof Error ? err.message : String(err)}`);
|
|
32155
32572
|
await closeClient(client);
|
|
32573
|
+
return void 0;
|
|
32156
32574
|
}
|
|
32157
32575
|
})
|
|
32158
32576
|
);
|
|
32577
|
+
for (const result of discovered) {
|
|
32578
|
+
if (!result) continue;
|
|
32579
|
+
registry2.add(result.name, result.client, result.tools);
|
|
32580
|
+
}
|
|
32581
|
+
for (const tool of registry2.listTools()) {
|
|
32582
|
+
const target = registry2.resolve(tool.name);
|
|
32583
|
+
if (!target) continue;
|
|
32584
|
+
const originalName = target.toolName;
|
|
32585
|
+
const serverName = target.serverName;
|
|
32586
|
+
pi.registerTool({
|
|
32587
|
+
name: tool.name,
|
|
32588
|
+
label: originalName,
|
|
32589
|
+
description: tool.description ?? `MCP tool "${originalName}" from server "${serverName}"`,
|
|
32590
|
+
parameters: toParametersSchema(tool.inputSchema),
|
|
32591
|
+
async execute(_toolCallId, params) {
|
|
32592
|
+
const result = await registry2.callTool(tool.name, params);
|
|
32593
|
+
const content = Array.isArray(result.content) ? result.content : [];
|
|
32594
|
+
return { content: toPiContent(content), details: result };
|
|
32595
|
+
}
|
|
32596
|
+
});
|
|
32597
|
+
}
|
|
32159
32598
|
}
|
|
32160
32599
|
export {
|
|
32161
32600
|
trellisMcpBridge as default,
|