@ken-jo/agent-connector 0.4.1 → 0.4.7
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 +2 -2
- package/dist/{action-WT7GVRCC.js → action-YCU53FNE.js} +8 -8
- package/dist/{amazon-q-COAL3764.js → amazon-q-W574ZX35.js} +2 -2
- package/dist/amp-JPBQJJVM.js +666 -0
- package/dist/amp-JPBQJJVM.js.map +1 -0
- package/dist/{antigravity-AMKKKHS4.js → antigravity-VK2Y3FGV.js} +3 -3
- package/dist/{antigravity-cli-XERXEH5W.js → antigravity-cli-D4PCCUG2.js} +3 -3
- package/dist/{chunk-DCKFYWGM.js → chunk-2F5QH3D4.js} +36 -36
- package/dist/{chunk-RIMVWCZY.js → chunk-6RYPUR6O.js} +5 -5
- package/dist/{chunk-QREL7LZJ.js → chunk-7ECYDK5T.js} +2 -2
- package/dist/{chunk-O5TWCIJN.js → chunk-CYOAH23F.js} +18 -18
- package/dist/{chunk-URHQMFB3.js → chunk-DT6Y7XBI.js} +62 -16
- package/dist/chunk-DT6Y7XBI.js.map +1 -0
- package/dist/{chunk-2AVLX7GH.js → chunk-HAIMYUEY.js} +3 -3
- package/dist/{chunk-4ENJK5S2.js → chunk-MDNX7HTN.js} +2 -2
- package/dist/{chunk-EXLS7BSI.js → chunk-NLFPH4S2.js} +10 -2
- package/dist/chunk-NLFPH4S2.js.map +1 -0
- package/dist/{chunk-CPLK7GTE.js → chunk-PFDUGDOA.js} +351 -34
- package/dist/chunk-PFDUGDOA.js.map +1 -0
- package/dist/{chunk-EGCBZ2DC.js → chunk-SDPIT4HW.js} +2 -2
- package/dist/{chunk-5OWS5CMA.js → chunk-SPWFUHHK.js} +5 -5
- package/dist/{chunk-LQRAPXS4.js → chunk-TZQBASI7.js} +4 -2
- package/dist/chunk-TZQBASI7.js.map +1 -0
- package/dist/{chunk-XJ4S47CT.js → chunk-WFXWJITA.js} +2 -2
- package/dist/{chunk-24IDL5RW.js → chunk-YQLQJZ7S.js} +2 -2
- package/dist/{claude-code-W4PUFZL5.js → claude-code-ROSIILTM.js} +36 -100
- package/dist/claude-code-ROSIILTM.js.map +1 -0
- package/dist/cli/sdk.js +4 -4
- package/dist/cli.js +1 -1
- package/dist/{cline-WRYMNS7Z.js → cline-JUTO4V27.js} +2 -2
- package/dist/{codebuff-DH5Y7GAT.js → codebuff-2QUHL6HO.js} +127 -6
- package/dist/codebuff-2QUHL6HO.js.map +1 -0
- package/dist/{codex-DDHIV767.js → codex-YBBTIDD4.js} +109 -114
- package/dist/codex-YBBTIDD4.js.map +1 -0
- package/dist/{continue-K43SUHLQ.js → continue-W4TU4JSB.js} +5 -3
- package/dist/continue-W4TU4JSB.js.map +1 -0
- package/dist/{copilot-cli-Q3SSPT4V.js → copilot-cli-Y74SOP5G.js} +4 -2
- package/dist/copilot-cli-Y74SOP5G.js.map +1 -0
- package/dist/{crush-MBEH2R3I.js → crush-GMEWLXK5.js} +4 -2
- package/dist/{crush-MBEH2R3I.js.map → crush-GMEWLXK5.js.map} +1 -1
- package/dist/{cursor-TMBXDYR4.js → cursor-JBS4B3Q4.js} +50 -134
- package/dist/cursor-JBS4B3Q4.js.map +1 -0
- package/dist/{detect-KG5FG3EV.js → detect-2NIXILMZ.js} +4 -4
- package/dist/{doctor-FLZF34Y3.js → doctor-QWJBWALA.js} +8 -8
- package/dist/{droid-JC2NT6S4.js → droid-TJMTNCQE.js} +48 -106
- package/dist/droid-TJMTNCQE.js.map +1 -0
- package/dist/{gemini-cli-ILR7M5X3.js → gemini-cli-RAONGZRG.js} +7 -2
- package/dist/gemini-cli-RAONGZRG.js.map +1 -0
- package/dist/{goose-SFO5BBGW.js → goose-G4KKM6YU.js} +91 -131
- package/dist/goose-G4KKM6YU.js.map +1 -0
- package/dist/{hermes-3N7WN4WS.js → hermes-PJDTJE6F.js} +29 -33
- package/dist/hermes-PJDTJE6F.js.map +1 -0
- package/dist/{hook-XR3Z45FB.js → hook-FDGIQPRC.js} +8 -8
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/{install-BSR62UHJ.js → install-TLG3QVP3.js} +8 -8
- package/dist/{introspect-Nw8hTReX.d.ts → introspect-Cn3MSA4_.d.ts} +1 -1
- package/dist/{jetbrains-copilot-NRZHI5PC.js → jetbrains-copilot-DNXSLQE5.js} +4 -2
- package/dist/jetbrains-copilot-DNXSLQE5.js.map +1 -0
- package/dist/{kilo-5NWOXVSD.js → kilo-FERJ3X46.js} +101 -5
- package/dist/kilo-FERJ3X46.js.map +1 -0
- package/dist/{kilo-cli-UZ7R5UOP.js → kilo-cli-TDCONH5G.js} +108 -12
- package/dist/kilo-cli-TDCONH5G.js.map +1 -0
- package/dist/{kimi-2YFK2GBA.js → kimi-RIRD4J5J.js} +12 -2
- package/dist/kimi-RIRD4J5J.js.map +1 -0
- package/dist/{kiro-YL6AWUYG.js → kiro-X7JVLAN7.js} +4 -2
- package/dist/kiro-X7JVLAN7.js.map +1 -0
- package/dist/{leaderboard-MJG24O35.js → leaderboard-MXGENYXN.js} +2 -2
- package/dist/{mimo-code-FTMGSKMQ.js → mimo-code-RFLQ727H.js} +2 -2
- package/dist/{mux-5XZFB44C.js → mux-D77HBSBU.js} +3 -17
- package/dist/mux-D77HBSBU.js.map +1 -0
- package/dist/{nemoclaw-W5KEJ3WX.js → nemoclaw-LXUTMR56.js} +3 -3
- package/dist/{omp-2QMEY6HD.js → omp-CO7IXURG.js} +2 -2
- package/dist/{openclaw-EXJEFNZI.js → openclaw-XWJLEW35.js} +3 -3
- package/dist/{opencode-WNXYJR4X.js → opencode-QKZ5EIYU.js} +5 -2
- package/dist/opencode-QKZ5EIYU.js.map +1 -0
- package/dist/{package-ACZXJHWB.js → package-IA56LSC3.js} +6 -6
- package/dist/{pi-PVVPVWKG.js → pi-YGBXYWY5.js} +2 -2
- package/dist/{qwen-code-G4CXKUHR.js → qwen-code-E2MSEZK6.js} +55 -123
- package/dist/qwen-code-E2MSEZK6.js.map +1 -0
- package/dist/{roo-code-67TYJIQR.js → roo-code-AGGJ473T.js} +2 -2
- package/dist/runtime/index.d.ts +1 -1
- package/dist/runtime/index.js +6 -6
- package/dist/sdk/index.d.ts +3 -3
- package/dist/sdk/index.js +3 -3
- package/dist/sdk/test.d.ts +2 -2
- package/dist/sdk/test.js +5 -5
- package/dist/{serve-GER7URPR.js → serve-RNUCK5F7.js} +8 -8
- package/dist/{status-LAJE3BM5.js → status-JOX6RLJU.js} +6 -6
- package/dist/{statusline-L376ZPZU.js → statusline-ZVFI5DRT.js} +7 -7
- package/dist/{telemetry-B3DX3PWU.js → telemetry-3FN6HDVU.js} +5 -5
- package/dist/{trae-7CZZGOGI.js → trae-6L226ZDU.js} +2 -2
- package/dist/{types-CRVrFWeU.d.ts → types-_6arHbX9.d.ts} +5 -4
- package/dist/{uninstall-ZYTW7YMZ.js → uninstall-SCKZYB4J.js} +8 -8
- package/dist/{upgrade-4LUEEOZW.js → upgrade-WU3KZWR4.js} +8 -8
- package/dist/{usage-ZFBVQF3H.js → usage-G72SF7JG.js} +2 -2
- package/dist/{usage-event-GZ32AGEB.js → usage-event-DHSEBO5T.js} +7 -7
- package/dist/{vscode-copilot-MF2CDNMT.js → vscode-copilot-6EOZDLXC.js} +4 -2
- package/dist/vscode-copilot-6EOZDLXC.js.map +1 -0
- package/dist/{warp-VKG55TAT.js → warp-N5YYN5BF.js} +2 -2
- package/dist/{windsurf-FU5G4SHF.js → windsurf-4TGSJIVV.js} +2 -2
- package/dist/{zed-SFUHL3GS.js → zed-6P2UFWXU.js} +2 -2
- package/package.json +1 -1
- package/dist/amp-4U33CU7E.js +0 -300
- package/dist/amp-4U33CU7E.js.map +0 -1
- package/dist/chunk-CPLK7GTE.js.map +0 -1
- package/dist/chunk-EXLS7BSI.js.map +0 -1
- package/dist/chunk-LQRAPXS4.js.map +0 -1
- package/dist/chunk-URHQMFB3.js.map +0 -1
- package/dist/claude-code-W4PUFZL5.js.map +0 -1
- package/dist/codebuff-DH5Y7GAT.js.map +0 -1
- package/dist/codex-DDHIV767.js.map +0 -1
- package/dist/continue-K43SUHLQ.js.map +0 -1
- package/dist/copilot-cli-Q3SSPT4V.js.map +0 -1
- package/dist/cursor-TMBXDYR4.js.map +0 -1
- package/dist/droid-JC2NT6S4.js.map +0 -1
- package/dist/gemini-cli-ILR7M5X3.js.map +0 -1
- package/dist/goose-SFO5BBGW.js.map +0 -1
- package/dist/hermes-3N7WN4WS.js.map +0 -1
- package/dist/jetbrains-copilot-NRZHI5PC.js.map +0 -1
- package/dist/kilo-5NWOXVSD.js.map +0 -1
- package/dist/kilo-cli-UZ7R5UOP.js.map +0 -1
- package/dist/kimi-2YFK2GBA.js.map +0 -1
- package/dist/kiro-YL6AWUYG.js.map +0 -1
- package/dist/mux-5XZFB44C.js.map +0 -1
- package/dist/opencode-WNXYJR4X.js.map +0 -1
- package/dist/qwen-code-G4CXKUHR.js.map +0 -1
- package/dist/vscode-copilot-MF2CDNMT.js.map +0 -1
- /package/dist/{action-WT7GVRCC.js.map → action-YCU53FNE.js.map} +0 -0
- /package/dist/{amazon-q-COAL3764.js.map → amazon-q-W574ZX35.js.map} +0 -0
- /package/dist/{antigravity-AMKKKHS4.js.map → antigravity-VK2Y3FGV.js.map} +0 -0
- /package/dist/{antigravity-cli-XERXEH5W.js.map → antigravity-cli-D4PCCUG2.js.map} +0 -0
- /package/dist/{chunk-DCKFYWGM.js.map → chunk-2F5QH3D4.js.map} +0 -0
- /package/dist/{chunk-RIMVWCZY.js.map → chunk-6RYPUR6O.js.map} +0 -0
- /package/dist/{chunk-QREL7LZJ.js.map → chunk-7ECYDK5T.js.map} +0 -0
- /package/dist/{chunk-O5TWCIJN.js.map → chunk-CYOAH23F.js.map} +0 -0
- /package/dist/{chunk-2AVLX7GH.js.map → chunk-HAIMYUEY.js.map} +0 -0
- /package/dist/{chunk-4ENJK5S2.js.map → chunk-MDNX7HTN.js.map} +0 -0
- /package/dist/{chunk-EGCBZ2DC.js.map → chunk-SDPIT4HW.js.map} +0 -0
- /package/dist/{chunk-5OWS5CMA.js.map → chunk-SPWFUHHK.js.map} +0 -0
- /package/dist/{chunk-XJ4S47CT.js.map → chunk-WFXWJITA.js.map} +0 -0
- /package/dist/{chunk-24IDL5RW.js.map → chunk-YQLQJZ7S.js.map} +0 -0
- /package/dist/{cline-WRYMNS7Z.js.map → cline-JUTO4V27.js.map} +0 -0
- /package/dist/{detect-KG5FG3EV.js.map → detect-2NIXILMZ.js.map} +0 -0
- /package/dist/{doctor-FLZF34Y3.js.map → doctor-QWJBWALA.js.map} +0 -0
- /package/dist/{hook-XR3Z45FB.js.map → hook-FDGIQPRC.js.map} +0 -0
- /package/dist/{install-BSR62UHJ.js.map → install-TLG3QVP3.js.map} +0 -0
- /package/dist/{leaderboard-MJG24O35.js.map → leaderboard-MXGENYXN.js.map} +0 -0
- /package/dist/{mimo-code-FTMGSKMQ.js.map → mimo-code-RFLQ727H.js.map} +0 -0
- /package/dist/{nemoclaw-W5KEJ3WX.js.map → nemoclaw-LXUTMR56.js.map} +0 -0
- /package/dist/{omp-2QMEY6HD.js.map → omp-CO7IXURG.js.map} +0 -0
- /package/dist/{openclaw-EXJEFNZI.js.map → openclaw-XWJLEW35.js.map} +0 -0
- /package/dist/{package-ACZXJHWB.js.map → package-IA56LSC3.js.map} +0 -0
- /package/dist/{pi-PVVPVWKG.js.map → pi-YGBXYWY5.js.map} +0 -0
- /package/dist/{roo-code-67TYJIQR.js.map → roo-code-AGGJ473T.js.map} +0 -0
- /package/dist/{serve-GER7URPR.js.map → serve-RNUCK5F7.js.map} +0 -0
- /package/dist/{status-LAJE3BM5.js.map → status-JOX6RLJU.js.map} +0 -0
- /package/dist/{statusline-L376ZPZU.js.map → statusline-ZVFI5DRT.js.map} +0 -0
- /package/dist/{telemetry-B3DX3PWU.js.map → telemetry-3FN6HDVU.js.map} +0 -0
- /package/dist/{trae-7CZZGOGI.js.map → trae-6L226ZDU.js.map} +0 -0
- /package/dist/{uninstall-ZYTW7YMZ.js.map → uninstall-SCKZYB4J.js.map} +0 -0
- /package/dist/{upgrade-4LUEEOZW.js.map → upgrade-WU3KZWR4.js.map} +0 -0
- /package/dist/{usage-ZFBVQF3H.js.map → usage-G72SF7JG.js.map} +0 -0
- /package/dist/{usage-event-GZ32AGEB.js.map → usage-event-DHSEBO5T.js.map} +0 -0
- /package/dist/{warp-VKG55TAT.js.map → warp-N5YYN5BF.js.map} +0 -0
- /package/dist/{windsurf-FU5G4SHF.js.map → windsurf-4TGSJIVV.js.map} +0 -0
- /package/dist/{zed-SFUHL3GS.js.map → zed-6P2UFWXU.js.map} +0 -0
|
@@ -0,0 +1,666 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
rewriteEnvRefs
|
|
4
|
+
} from "./chunk-DBQMX75U.js";
|
|
5
|
+
import {
|
|
6
|
+
BaseAdapter
|
|
7
|
+
} from "./chunk-PFDUGDOA.js";
|
|
8
|
+
import {
|
|
9
|
+
renderSkillMd
|
|
10
|
+
} from "./chunk-7L6ITC4T.js";
|
|
11
|
+
import {
|
|
12
|
+
buildServeWrapperCommand,
|
|
13
|
+
shouldWrapForTelemetry
|
|
14
|
+
} from "./chunk-I5MLE6S7.js";
|
|
15
|
+
import "./chunk-N6HH7K6P.js";
|
|
16
|
+
import "./chunk-TRQKHP7D.js";
|
|
17
|
+
|
|
18
|
+
// src/adapters/amp/index.ts
|
|
19
|
+
import {
|
|
20
|
+
chmodSync,
|
|
21
|
+
existsSync,
|
|
22
|
+
mkdirSync,
|
|
23
|
+
readFileSync,
|
|
24
|
+
rmSync,
|
|
25
|
+
writeFileSync
|
|
26
|
+
} from "fs";
|
|
27
|
+
import { homedir } from "os";
|
|
28
|
+
import { dirname, join } from "path";
|
|
29
|
+
var HOST = "amp";
|
|
30
|
+
var MCP_ROOT_KEY = "amp.mcpServers";
|
|
31
|
+
var EVENT_TO_AMP = {
|
|
32
|
+
SessionStart: "session.start",
|
|
33
|
+
UserPromptSubmit: "agent.start",
|
|
34
|
+
PreToolUse: "tool.call",
|
|
35
|
+
PostToolUse: "tool.result",
|
|
36
|
+
Stop: "agent.end"
|
|
37
|
+
};
|
|
38
|
+
var AmpAdapter = class extends BaseAdapter {
|
|
39
|
+
id = HOST;
|
|
40
|
+
name = "Amp";
|
|
41
|
+
paradigm = "ts-plugin";
|
|
42
|
+
capabilities = {
|
|
43
|
+
// Memory surface: AGENTS.md-first managed block via the BaseAdapter default
|
|
44
|
+
// (memoryTargets: project <projectDir>/AGENTS.md; user scope where documented).
|
|
45
|
+
supportsMemory: true,
|
|
46
|
+
// amp.on lifecycle events with a canonical analog (EVENT_TO_AMP):
|
|
47
|
+
// tool.call / tool.result / session.start / agent.start / agent.end.
|
|
48
|
+
preToolUse: true,
|
|
49
|
+
postToolUse: true,
|
|
50
|
+
// Amp has no documented compaction hook.
|
|
51
|
+
preCompact: false,
|
|
52
|
+
sessionStart: true,
|
|
53
|
+
// Amp documents NO session.end event — SessionEnd is an honest gap.
|
|
54
|
+
sessionEnd: false,
|
|
55
|
+
userPromptSubmit: true,
|
|
56
|
+
stop: true,
|
|
57
|
+
notification: false,
|
|
58
|
+
// Newer events: Amp documents no permission/approval, post-tool-failure, or
|
|
59
|
+
// subagent lifecycle hook with a canonical analog, so permissionRequest /
|
|
60
|
+
// postToolUseFailure / subagentStart / subagentStop stay unset — the
|
|
61
|
+
// generated bridge never references them and install reports them as
|
|
62
|
+
// "unsupported here".
|
|
63
|
+
// tool.call's documented decision surface is { action: "allow" |
|
|
64
|
+
// "reject-and-continue" | "modify" | "synthesize" } (ampcode.com/manual →
|
|
65
|
+
// Plugins → tool.call). We wire allow + reject-and-continue (block); the
|
|
66
|
+
// "modify" input-rewrite shape is not byte-documented, so canModifyArgs:false.
|
|
67
|
+
canModifyArgs: false,
|
|
68
|
+
// tool.result says "return a replacement status/output" but the manual never
|
|
69
|
+
// documents the replacement OBJECT SHAPE (which keys). Rather than guess it,
|
|
70
|
+
// PostToolUse is observe-only and canModifyOutput stays false until the shape
|
|
71
|
+
// is primary-source-verified (honesty bar: under-claim, never emit a guessed
|
|
72
|
+
// mutation contract).
|
|
73
|
+
canModifyOutput: false,
|
|
74
|
+
// session.start has no documented context-injection surface.
|
|
75
|
+
canInjectSessionContext: false,
|
|
76
|
+
// Amp registers stdio and Streamable HTTP MCP servers.
|
|
77
|
+
transports: ["stdio", "http"],
|
|
78
|
+
// Skills: Amp reads SKILL.md from ~/.config/agents/skills/<name>/SKILL.md
|
|
79
|
+
// (user) and <projectDir>/.agents/skills/<name>/SKILL.md (project) — a root
|
|
80
|
+
// OUTSIDE the config dir, so the skillDir() helper resolves it explicitly.
|
|
81
|
+
supportsSkills: true,
|
|
82
|
+
// Native passthrough hooks: amp.on events with no canonical HookEventName
|
|
83
|
+
// analog are reachable via platforms["amp"].nativeHooks; the generated plugin
|
|
84
|
+
// registers + bridges each declared native event verbatim and the home-bin
|
|
85
|
+
// runtime dispatches it host-generically via runNativeHook.
|
|
86
|
+
supportsNativeHooks: true
|
|
87
|
+
};
|
|
88
|
+
// ── Detection ────────────────────────────────────────────────────────────
|
|
89
|
+
detectInstalled(projectDir) {
|
|
90
|
+
const userDir = join(homedir(), ".config", "amp");
|
|
91
|
+
const userSettings = join(userDir, "settings.json");
|
|
92
|
+
const projectDirAmp = join(projectDir, ".amp");
|
|
93
|
+
const projectSettings = join(projectDirAmp, "settings.json");
|
|
94
|
+
const userInstalled = existsSync(userDir) || existsSync(userSettings);
|
|
95
|
+
const projectInstalled = existsSync(projectDirAmp) || existsSync(projectSettings);
|
|
96
|
+
const installed = userInstalled || projectInstalled;
|
|
97
|
+
const scope = projectInstalled && !userInstalled ? "project" : "user";
|
|
98
|
+
const configPath = scope === "project" ? projectSettings : userSettings;
|
|
99
|
+
return {
|
|
100
|
+
id: this.id,
|
|
101
|
+
name: this.name,
|
|
102
|
+
installed,
|
|
103
|
+
paradigm: this.paradigm,
|
|
104
|
+
capabilities: this.capabilities,
|
|
105
|
+
configPath,
|
|
106
|
+
scope,
|
|
107
|
+
reason: installed ? `found Amp config (${scope}) at ${configPath}` : `no Amp config at ${userSettings} or ${projectSettings}`,
|
|
108
|
+
confidence: installed ? "high" : "low"
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
// ── Native paths ─────────────────────────────────────────────────────────
|
|
112
|
+
getConfigDir(ctx) {
|
|
113
|
+
return ctx.scope === "project" ? join(ctx.projectDir, ".amp") : join(homedir(), ".config", "amp");
|
|
114
|
+
}
|
|
115
|
+
getServerConfigPath(ctx) {
|
|
116
|
+
return join(this.getConfigDir(ctx), "settings.json");
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* For this ts-plugin host the "hook config path" is the generated plugin
|
|
120
|
+
* MODULE. Amp loads every `.ts` file in `.amp/plugins/`, so writing this file
|
|
121
|
+
* IS the registration. The `.amp/plugins/` dir is documented at PROJECT scope
|
|
122
|
+
* (Amp documents no user-scope plugins dir — see pluginsDir); the user-scope
|
|
123
|
+
* path is returned for doctor/uninstall symmetry but installHooks warn-skips it.
|
|
124
|
+
*/
|
|
125
|
+
getHookConfigPath(ctx) {
|
|
126
|
+
return join(this.pluginsDir(ctx), `${ctx.connector.id}.ts`);
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Plugin directory Amp auto-loads `.ts` modules from. PROJECT scope is the
|
|
130
|
+
* documented `<projectDir>/.amp/plugins`; user scope mirrors the MCP config dir
|
|
131
|
+
* (`~/.config/amp/plugins`) for path symmetry only — installHooks never writes
|
|
132
|
+
* there because Amp documents no user-scope plugins dir.
|
|
133
|
+
*/
|
|
134
|
+
pluginsDir(ctx) {
|
|
135
|
+
return ctx.scope === "project" ? join(ctx.projectDir, ".amp", "plugins") : join(this.getConfigDir(ctx), "plugins");
|
|
136
|
+
}
|
|
137
|
+
// ── MCP server install / uninstall ───────────────────────────────────────
|
|
138
|
+
installServer(ctx) {
|
|
139
|
+
const { connector } = ctx;
|
|
140
|
+
const override = connector.platforms[HOST]?.server;
|
|
141
|
+
if (!connector.server || override === false) {
|
|
142
|
+
return [
|
|
143
|
+
{
|
|
144
|
+
platform: this.id,
|
|
145
|
+
action: "skip",
|
|
146
|
+
detail: connector.server ? "server registration disabled for amp" : "connector declares no MCP server"
|
|
147
|
+
}
|
|
148
|
+
];
|
|
149
|
+
}
|
|
150
|
+
const server = override && typeof override === "object" ? { ...connector.server, ...override } : connector.server;
|
|
151
|
+
const serverPath = this.getServerConfigPath(ctx);
|
|
152
|
+
const entry = this.renderServerEntry(ctx, server);
|
|
153
|
+
return [
|
|
154
|
+
this.upsertServerInJson(serverPath, MCP_ROOT_KEY, connector.id, entry, ctx.dryRun)
|
|
155
|
+
];
|
|
156
|
+
}
|
|
157
|
+
uninstallServer(ctx) {
|
|
158
|
+
const serverPath = this.getServerConfigPath(ctx);
|
|
159
|
+
return [
|
|
160
|
+
this.removeServerFromJson(serverPath, MCP_ROOT_KEY, ctx.connector.id, ctx.dryRun)
|
|
161
|
+
];
|
|
162
|
+
}
|
|
163
|
+
/** Render a normalized ServerDef into Amp's native `amp.mcpServers` entry. */
|
|
164
|
+
renderServerEntry(ctx, server) {
|
|
165
|
+
const transport = server.transport;
|
|
166
|
+
if (transport === "stdio") {
|
|
167
|
+
let command = server.command ?? "";
|
|
168
|
+
let args = [...server.args ?? []];
|
|
169
|
+
if (shouldWrapForTelemetry(server, ctx.connector.telemetry)) {
|
|
170
|
+
const wrapped = buildServeWrapperCommand(
|
|
171
|
+
ctx.homeBinPath,
|
|
172
|
+
ctx.connector.id,
|
|
173
|
+
command,
|
|
174
|
+
args,
|
|
175
|
+
ctx.scope,
|
|
176
|
+
this.id
|
|
177
|
+
);
|
|
178
|
+
command = wrapped.command;
|
|
179
|
+
args = wrapped.args;
|
|
180
|
+
}
|
|
181
|
+
const entry2 = { command: this.rewrite(command) };
|
|
182
|
+
if (args.length > 0) entry2.args = args.map((a) => this.rewrite(a));
|
|
183
|
+
const env = this.renderEnv(server.env);
|
|
184
|
+
if (env) entry2.env = env;
|
|
185
|
+
return entry2;
|
|
186
|
+
}
|
|
187
|
+
const entry = { url: this.rewrite(server.url ?? "") };
|
|
188
|
+
const headers = this.renderEnv(server.headers);
|
|
189
|
+
if (headers) entry.headers = headers;
|
|
190
|
+
return entry;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Render env/header values. Amp supports native `${VAR_NAME}` interpolation,
|
|
194
|
+
* so translate `${env:VAR}` refs to that native token rather than baking
|
|
195
|
+
* secrets into the file. Literals pass through unchanged.
|
|
196
|
+
*/
|
|
197
|
+
renderEnv(env) {
|
|
198
|
+
if (!env || Object.keys(env).length === 0) return void 0;
|
|
199
|
+
const out = {};
|
|
200
|
+
for (const [k, v] of Object.entries(env)) out[k] = this.rewrite(v);
|
|
201
|
+
return out;
|
|
202
|
+
}
|
|
203
|
+
/** Translate `${env:VAR(:-default)}` to Amp's native `${VAR}` token. */
|
|
204
|
+
rewrite(value) {
|
|
205
|
+
return rewriteEnvRefs(value, ampEnvToken);
|
|
206
|
+
}
|
|
207
|
+
// ── Skills surface ───────────────────────────────────────────────────────
|
|
208
|
+
// CONTENT-ONLY: pure native-file writers under the skill root. No runtime
|
|
209
|
+
// dispatch, no home-bin pointer, no telemetry wrap. Idempotent (byte-identical
|
|
210
|
+
// → skip) via writeContentFile and reversible via removeContentFile. Honors
|
|
211
|
+
// platforms["amp"].skills === false to skip.
|
|
212
|
+
//
|
|
213
|
+
// QUIRK: the skill root is NOT under getConfigDir (~/.config/amp). Amp reads
|
|
214
|
+
// SKILL.md from the sibling cross-agent `agents` tree:
|
|
215
|
+
// user scope → ~/.config/agents/skills/<name>/SKILL.md
|
|
216
|
+
// project scope → <projectDir>/.agents/skills/<name>/SKILL.md
|
|
217
|
+
// so skillDir() resolves it explicitly rather than reusing getConfigDir.
|
|
218
|
+
/** Skill root: user `~/.config/agents/skills`, project `<projectDir>/.agents/skills`. */
|
|
219
|
+
skillsDir(ctx) {
|
|
220
|
+
return ctx.scope === "project" ? join(ctx.projectDir, ".agents", "skills") : join(homedir(), ".config", "agents", "skills");
|
|
221
|
+
}
|
|
222
|
+
/** Native skill dir: <skillRoot>/<name>. */
|
|
223
|
+
skillDir(ctx, name) {
|
|
224
|
+
return join(this.skillsDir(ctx), name);
|
|
225
|
+
}
|
|
226
|
+
installSkills(ctx) {
|
|
227
|
+
const { connector } = ctx;
|
|
228
|
+
if (connector.platforms[HOST]?.skills === false) {
|
|
229
|
+
return [{ platform: this.id, action: "skip", detail: "skills disabled for amp" }];
|
|
230
|
+
}
|
|
231
|
+
if (connector.skills.length === 0) {
|
|
232
|
+
return [{ platform: this.id, action: "skip", detail: "connector declares no skills" }];
|
|
233
|
+
}
|
|
234
|
+
const changes = [];
|
|
235
|
+
for (const skill of connector.skills) {
|
|
236
|
+
const dir = this.skillDir(ctx, skill.name);
|
|
237
|
+
changes.push(
|
|
238
|
+
this.writeContentFile(join(dir, "SKILL.md"), this.renderSkill(skill), ctx.dryRun)
|
|
239
|
+
);
|
|
240
|
+
for (const [rel, contents] of Object.entries(skill.resources ?? {})) {
|
|
241
|
+
const target = this.resolveWithin(dir, rel);
|
|
242
|
+
if (target === null) {
|
|
243
|
+
changes.push({
|
|
244
|
+
platform: this.id,
|
|
245
|
+
action: "warn",
|
|
246
|
+
detail: `skill resource "${rel}" escapes the skill dir; skipped`
|
|
247
|
+
});
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
changes.push(this.writeContentFile(target, contents, ctx.dryRun));
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return changes;
|
|
254
|
+
}
|
|
255
|
+
uninstallSkills(ctx) {
|
|
256
|
+
const { connector } = ctx;
|
|
257
|
+
if (connector.skills.length === 0) {
|
|
258
|
+
return [{ platform: this.id, action: "skip", detail: "connector declares no skills" }];
|
|
259
|
+
}
|
|
260
|
+
const changes = [];
|
|
261
|
+
for (const skill of connector.skills) {
|
|
262
|
+
const dir = this.skillDir(ctx, skill.name);
|
|
263
|
+
changes.push(this.removeContentFile(join(dir, "SKILL.md"), ctx.dryRun));
|
|
264
|
+
for (const rel of Object.keys(skill.resources ?? {})) {
|
|
265
|
+
const target = this.resolveWithin(dir, rel);
|
|
266
|
+
if (target === null) continue;
|
|
267
|
+
changes.push(this.removeContentFile(target, ctx.dryRun));
|
|
268
|
+
}
|
|
269
|
+
changes.push(this.removeDirIfEmpty(dir, ctx.dryRun));
|
|
270
|
+
}
|
|
271
|
+
return changes;
|
|
272
|
+
}
|
|
273
|
+
/** Render a skill's SKILL.md (delegates to the shared renderer). */
|
|
274
|
+
renderSkill(skill) {
|
|
275
|
+
return renderSkillMd(skill);
|
|
276
|
+
}
|
|
277
|
+
// ── Hook install / uninstall (ts-plugin module) ──────────────────────────
|
|
278
|
+
installHooks(ctx) {
|
|
279
|
+
const pluginPath = this.getHookConfigPath(ctx);
|
|
280
|
+
if (ctx.scope !== "project") {
|
|
281
|
+
return [
|
|
282
|
+
{
|
|
283
|
+
platform: this.id,
|
|
284
|
+
action: "warn",
|
|
285
|
+
path: pluginPath,
|
|
286
|
+
detail: "amp plugins are project-scoped (.amp/plugins/) \u2014 no user-scope plugins dir is documented; skipped"
|
|
287
|
+
}
|
|
288
|
+
];
|
|
289
|
+
}
|
|
290
|
+
const canonicalDisabled = ctx.connector.platforms[HOST]?.hooks === false;
|
|
291
|
+
const hasCanonical = !canonicalDisabled && ctx.connector.hookEvents.length > 0;
|
|
292
|
+
const hasNative = this.nativeHookEvents(ctx).length > 0;
|
|
293
|
+
if (!hasCanonical && !hasNative) {
|
|
294
|
+
return [
|
|
295
|
+
{
|
|
296
|
+
platform: this.id,
|
|
297
|
+
action: "skip",
|
|
298
|
+
path: pluginPath,
|
|
299
|
+
detail: canonicalDisabled ? "hooks disabled for amp" : "connector declares no hooks"
|
|
300
|
+
}
|
|
301
|
+
];
|
|
302
|
+
}
|
|
303
|
+
const files = this.synthesizePlugin(ctx);
|
|
304
|
+
const changes = [];
|
|
305
|
+
for (const file of files) {
|
|
306
|
+
const before = existsSync(file.path) ? this.safeRead(file.path) : void 0;
|
|
307
|
+
let action;
|
|
308
|
+
if (before === void 0) action = "create";
|
|
309
|
+
else if (before === file.contents) action = "skip";
|
|
310
|
+
else action = "update";
|
|
311
|
+
if (action !== "skip" && !ctx.dryRun) {
|
|
312
|
+
ensureDir(dirname(file.path));
|
|
313
|
+
writeFileSync(file.path, file.contents, "utf8");
|
|
314
|
+
chmodSync(file.path, file.executable ? 493 : 420);
|
|
315
|
+
}
|
|
316
|
+
changes.push({
|
|
317
|
+
platform: this.id,
|
|
318
|
+
action,
|
|
319
|
+
path: file.path,
|
|
320
|
+
detail: `amp plugin module (${this.hookDetail(ctx)})`
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
return changes;
|
|
324
|
+
}
|
|
325
|
+
uninstallHooks(ctx) {
|
|
326
|
+
const pluginPath = this.getHookConfigPath(ctx);
|
|
327
|
+
if (!existsSync(pluginPath)) {
|
|
328
|
+
return [
|
|
329
|
+
{
|
|
330
|
+
platform: this.id,
|
|
331
|
+
action: "skip",
|
|
332
|
+
path: pluginPath,
|
|
333
|
+
detail: "no amp plugin module present"
|
|
334
|
+
}
|
|
335
|
+
];
|
|
336
|
+
}
|
|
337
|
+
if (!ctx.dryRun) rmSync(pluginPath, { force: true });
|
|
338
|
+
return [
|
|
339
|
+
{
|
|
340
|
+
platform: this.id,
|
|
341
|
+
action: "remove",
|
|
342
|
+
path: pluginPath,
|
|
343
|
+
detail: "amp plugin module"
|
|
344
|
+
}
|
|
345
|
+
];
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Human-facing summary of which declared events the synthesized module ACTUALLY
|
|
349
|
+
* wires. Only events present in EVENT_TO_AMP are mapped/wired; any declared
|
|
350
|
+
* event with no Amp mapping (e.g. SessionEnd) is reported separately as
|
|
351
|
+
* "unsupported here" so the detail never overstates coverage.
|
|
352
|
+
*/
|
|
353
|
+
hookDetail(ctx) {
|
|
354
|
+
const canonicalOff = ctx.connector.platforms[HOST]?.hooks === false;
|
|
355
|
+
const declared = canonicalOff ? [] : ctx.connector.hookEvents;
|
|
356
|
+
const mapped = declared.filter((e) => EVENT_TO_AMP[e] !== void 0);
|
|
357
|
+
const unsupported = declared.filter((e) => EVENT_TO_AMP[e] === void 0);
|
|
358
|
+
const native = this.nativeHookEvents(ctx);
|
|
359
|
+
const parts = [];
|
|
360
|
+
if (mapped.length > 0) parts.push(mapped.join(","));
|
|
361
|
+
if (native.length > 0) parts.push(`native: ${native.join(",")}`);
|
|
362
|
+
const base = parts.join("; ") || "no canonical hooks";
|
|
363
|
+
return unsupported.length > 0 ? `${base}; unsupported here: ${unsupported.join(",")}` : base;
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Amp-native passthrough events declared on platforms["amp"].nativeHooks
|
|
367
|
+
* (amp.on event names with no canonical HookEventName analog). Emitted as
|
|
368
|
+
* generic bridge registrations and dispatched host-generically by the home-bin's
|
|
369
|
+
* runNativeHook.
|
|
370
|
+
*/
|
|
371
|
+
nativeHookEvents(ctx) {
|
|
372
|
+
return Object.keys(ctx.connector.platforms[HOST]?.nativeHooks ?? {});
|
|
373
|
+
}
|
|
374
|
+
// ── ts-plugin synthesis ──────────────────────────────────────────────────
|
|
375
|
+
/**
|
|
376
|
+
* Build ONE self-contained Amp plugin module (`.amp/plugins/<id>.ts`). It
|
|
377
|
+
* imports nothing from agent-connector; each `amp.on(...)` handler shells out to
|
|
378
|
+
* the universal hook entrypoint via child_process and applies the normalized
|
|
379
|
+
* HookResponse. Its default export is the Amp plugin function `(amp) => void`.
|
|
380
|
+
*/
|
|
381
|
+
synthesizePlugin(ctx) {
|
|
382
|
+
const path = this.getHookConfigPath(ctx);
|
|
383
|
+
const contents = this.buildPluginSource(ctx);
|
|
384
|
+
return [{ path, contents, executable: false }];
|
|
385
|
+
}
|
|
386
|
+
/** Compose the generated plugin source with plain string concatenation. */
|
|
387
|
+
buildPluginSource(ctx) {
|
|
388
|
+
const homeBin = JSON.stringify(ctx.homeBinPath);
|
|
389
|
+
const connectorId = JSON.stringify(ctx.connector.id);
|
|
390
|
+
const canonicalOff = ctx.connector.platforms[HOST]?.hooks === false;
|
|
391
|
+
const events = canonicalOff ? [] : ctx.connector.hookEvents.filter(
|
|
392
|
+
(e) => EVENT_TO_AMP[e] !== void 0
|
|
393
|
+
);
|
|
394
|
+
const has = (e) => events.includes(e);
|
|
395
|
+
const header = `/**
|
|
396
|
+
* AUTO-GENERATED by agent-connector \u2014 DO NOT EDIT.
|
|
397
|
+
*
|
|
398
|
+
* Self-contained Amp plugin for connector ${ctx.connector.id}.
|
|
399
|
+
* It imports nothing from agent-connector: every hook invocation shells out to
|
|
400
|
+
* the stable home binary's universal entrypoint and JSON-parses the normalized
|
|
401
|
+
* response. Fail-open: any bridge error degrades to a no-op (never wedges Amp).
|
|
402
|
+
*
|
|
403
|
+
* Amp loads this module from .amp/plugins/${ctx.connector.id}.ts; the default
|
|
404
|
+
* export is the plugin function (amp) => void called once with the PluginAPI.
|
|
405
|
+
*/
|
|
406
|
+
import type { PluginAPI } from "@ampcode/plugin";
|
|
407
|
+
import { execFileSync, execSync } from "node:child_process";
|
|
408
|
+
|
|
409
|
+
const HOME_BIN = ${homeBin};
|
|
410
|
+
const CONNECTOR_ID = ${connectorId};
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Invoke the universal hook entrypoint for one event.
|
|
414
|
+
* @param {string} event canonical event name
|
|
415
|
+
* @param {object} payload amp-shaped payload posted on stdin
|
|
416
|
+
* @returns {object|null} normalized HookResponse, or null on any failure
|
|
417
|
+
*/
|
|
418
|
+
function bridge(event, payload) {
|
|
419
|
+
try {
|
|
420
|
+
// On Windows HOME_BIN is the agent-connector.cmd launcher: Node cannot
|
|
421
|
+
// execFile a batch file, and shell+args is deprecated (DEP0190), so run one
|
|
422
|
+
// quoted command line via a shell. POSIX keeps the direct execFile (no shell).
|
|
423
|
+
const args = ["hook", "amp", event, "--connector", CONNECTOR_ID];
|
|
424
|
+
const opts = { input: JSON.stringify(payload), encoding: "utf8" };
|
|
425
|
+
const stdout =
|
|
426
|
+
process.platform === "win32"
|
|
427
|
+
? execSync([HOME_BIN, ...args].map((a) => '"' + a + '"').join(" "), opts)
|
|
428
|
+
: execFileSync(HOME_BIN, args, opts);
|
|
429
|
+
const text = (stdout || "").trim();
|
|
430
|
+
if (text === "") return { decision: "allow" };
|
|
431
|
+
return JSON.parse(text);
|
|
432
|
+
} catch {
|
|
433
|
+
// Fail-open \u2014 never wedge an Amp tool call / lifecycle event.
|
|
434
|
+
return null;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Amp documents no project-dir env var; the plugin process runs in the workspace
|
|
439
|
+
// root, so resolve once at load. Session id is rebound on each session.start.
|
|
440
|
+
const PROJECT_DIR = process.cwd();
|
|
441
|
+
let SESSION_ID = "";
|
|
442
|
+
`;
|
|
443
|
+
const handlers = [];
|
|
444
|
+
if (has("SessionStart")) {
|
|
445
|
+
handlers.push(` // SessionStart \u2192 rebind the session id and notify the connector.
|
|
446
|
+
// Amp's session.start event carries event.thread.id (ampcode.com/manual \u2192
|
|
447
|
+
// Plugins \u2192 session.start: "Example session.start for \${event.thread.id}").
|
|
448
|
+
amp.on("session.start", async (event) => {
|
|
449
|
+
SESSION_ID =
|
|
450
|
+
(event && event.thread && event.thread.id) || "amp-session";
|
|
451
|
+
bridge("SessionStart", { sessionId: SESSION_ID, projectDir: PROJECT_DIR });
|
|
452
|
+
});`);
|
|
453
|
+
}
|
|
454
|
+
if (has("UserPromptSubmit")) {
|
|
455
|
+
handlers.push(` // UserPromptSubmit \u2192 agent.start "fires when the user submits a prompt"
|
|
456
|
+
// (ampcode.com/manual). The manual's example takes (_event) and documents no
|
|
457
|
+
// prompt field or return surface, so this is observe-only \u2014 the raw event is
|
|
458
|
+
// forwarded for the connector to inspect; any deny/context decision is a no-op.
|
|
459
|
+
amp.on("agent.start", async (event) => {
|
|
460
|
+
bridge("UserPromptSubmit", {
|
|
461
|
+
sessionId: SESSION_ID,
|
|
462
|
+
projectDir: PROJECT_DIR,
|
|
463
|
+
raw: event,
|
|
464
|
+
});
|
|
465
|
+
});`);
|
|
466
|
+
}
|
|
467
|
+
if (has("PreToolUse")) {
|
|
468
|
+
handlers.push(` // PreToolUse \u2192 Amp's tool.call returns a decision union (ampcode.com/manual \u2192
|
|
469
|
+
// Plugins \u2192 tool.call): { action: "allow" } to run, or { action:
|
|
470
|
+
// "reject-and-continue", message } to block and let the agent continue. The
|
|
471
|
+
// tool name is event.tool (verified); the input field is not byte-documented,
|
|
472
|
+
// so event.input is a best-effort hint for the connector's matcher (the matcher
|
|
473
|
+
// keys on the verified tool name). A deny/ask decision maps to reject-and-
|
|
474
|
+
// continue; everything else allows.
|
|
475
|
+
amp.on("tool.call", async (event) => {
|
|
476
|
+
const payload = {
|
|
477
|
+
toolName: (event && event.tool) || "",
|
|
478
|
+
toolInput: (event && event.input) || {},
|
|
479
|
+
sessionId: SESSION_ID,
|
|
480
|
+
projectDir: PROJECT_DIR,
|
|
481
|
+
};
|
|
482
|
+
const res = bridge("PreToolUse", payload);
|
|
483
|
+
if (res && (res.decision === "deny" || res.decision === "ask")) {
|
|
484
|
+
return {
|
|
485
|
+
action: "reject-and-continue",
|
|
486
|
+
message: res.reason || "Blocked by ${ctx.connector.id}",
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
return { action: "allow" };
|
|
490
|
+
});`);
|
|
491
|
+
}
|
|
492
|
+
if (has("PostToolUse")) {
|
|
493
|
+
handlers.push(` // PostToolUse \u2192 tool.result "fires after a tool finishes and before the result
|
|
494
|
+
// is sent back to the model" (ampcode.com/manual). The tool name is event.tool
|
|
495
|
+
// and the error signal is event.status === "error" (both verified). The manual
|
|
496
|
+
// says a replacement status/output CAN be returned but never documents the
|
|
497
|
+
// replacement object shape, so this is observe-only (canModifyOutput:false) \u2014
|
|
498
|
+
// the raw event is forwarded; we never return a guessed replacement.
|
|
499
|
+
amp.on("tool.result", async (event) => {
|
|
500
|
+
bridge("PostToolUse", {
|
|
501
|
+
toolName: (event && event.tool) || "",
|
|
502
|
+
isError: !!(event && event.status === "error"),
|
|
503
|
+
sessionId: SESSION_ID,
|
|
504
|
+
projectDir: PROJECT_DIR,
|
|
505
|
+
raw: event,
|
|
506
|
+
});
|
|
507
|
+
});`);
|
|
508
|
+
}
|
|
509
|
+
if (has("Stop")) {
|
|
510
|
+
handlers.push(` // Stop \u2192 observe turn end. Amp's agent.end exposes no decision surface, so
|
|
511
|
+
// this is observe-only (a deny decision degrades to a no-op).
|
|
512
|
+
amp.on("agent.end", async (event) => {
|
|
513
|
+
bridge("Stop", { sessionId: SESSION_ID, projectDir: PROJECT_DIR, raw: event });
|
|
514
|
+
});`);
|
|
515
|
+
}
|
|
516
|
+
for (const ev of this.nativeHookEvents(ctx)) {
|
|
517
|
+
const key = JSON.stringify(ev);
|
|
518
|
+
handlers.push(` // nativeHooks passthrough \u2192 ${ev}
|
|
519
|
+
amp.on(${key}, async (event) => {
|
|
520
|
+
bridge(${key}, { sessionId: SESSION_ID, projectDir: PROJECT_DIR, raw: event });
|
|
521
|
+
});`);
|
|
522
|
+
}
|
|
523
|
+
const factory = `
|
|
524
|
+
export default function plugin(amp: PluginAPI) {
|
|
525
|
+
${handlers.join("\n\n")}
|
|
526
|
+
}
|
|
527
|
+
`;
|
|
528
|
+
return header + factory;
|
|
529
|
+
}
|
|
530
|
+
// ── Runtime: parse OUR bridge payload → normalized event ─────────────────
|
|
531
|
+
/**
|
|
532
|
+
* `raw` is the payload OUR generated plugin posts (NOT a host-native shape):
|
|
533
|
+
* { toolName?, toolInput?, toolOutput?, isError?, prompt?, sessionId?, projectDir? }
|
|
534
|
+
* so this maps straight through.
|
|
535
|
+
*/
|
|
536
|
+
parseEvent(event, raw) {
|
|
537
|
+
const input = raw ?? {};
|
|
538
|
+
const base = {
|
|
539
|
+
hostPlatform: HOST,
|
|
540
|
+
connectorId: "",
|
|
541
|
+
sessionId: typeof input.sessionId === "string" ? input.sessionId : "",
|
|
542
|
+
raw,
|
|
543
|
+
...typeof input.projectDir === "string" ? { projectDir: input.projectDir } : {}
|
|
544
|
+
};
|
|
545
|
+
switch (event) {
|
|
546
|
+
case "PreToolUse": {
|
|
547
|
+
const ev = {
|
|
548
|
+
...base,
|
|
549
|
+
toolName: input.toolName ?? "",
|
|
550
|
+
toolInput: input.toolInput ?? {}
|
|
551
|
+
};
|
|
552
|
+
return ev;
|
|
553
|
+
}
|
|
554
|
+
case "PostToolUse": {
|
|
555
|
+
const ev = {
|
|
556
|
+
...base,
|
|
557
|
+
toolName: input.toolName ?? "",
|
|
558
|
+
toolInput: input.toolInput ?? {},
|
|
559
|
+
...typeof input.toolOutput === "string" ? { toolOutput: input.toolOutput } : {},
|
|
560
|
+
...typeof input.isError === "boolean" ? { isError: input.isError } : {}
|
|
561
|
+
};
|
|
562
|
+
return ev;
|
|
563
|
+
}
|
|
564
|
+
case "UserPromptSubmit": {
|
|
565
|
+
const ev = {
|
|
566
|
+
...base,
|
|
567
|
+
prompt: typeof input.prompt === "string" ? input.prompt : ""
|
|
568
|
+
};
|
|
569
|
+
return ev;
|
|
570
|
+
}
|
|
571
|
+
case "Stop": {
|
|
572
|
+
const ev = { ...base };
|
|
573
|
+
return ev;
|
|
574
|
+
}
|
|
575
|
+
case "SessionStart": {
|
|
576
|
+
const ev = { ...base, source: "startup" };
|
|
577
|
+
return ev;
|
|
578
|
+
}
|
|
579
|
+
default:
|
|
580
|
+
return { ...base, source: "startup" };
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
// ── Runtime: normalized response → reply the generated bridge parses ─────
|
|
584
|
+
/**
|
|
585
|
+
* Unlike json-stdio hosts (whose reply is the host's NATIVE control payload),
|
|
586
|
+
* OUR generated bridge consumes this stdout directly. So the reply body is the
|
|
587
|
+
* NORMALIZED HookResponse itself — the bridge JSON.parses it and, for tool.call,
|
|
588
|
+
* maps a deny/ask decision → { action: "reject-and-continue" } (else allow).
|
|
589
|
+
* Other events are observe-only, so their reply is ignored by the handler.
|
|
590
|
+
*/
|
|
591
|
+
formatReply(_event, response) {
|
|
592
|
+
return {
|
|
593
|
+
exitCode: 0,
|
|
594
|
+
stdout: JSON.stringify(response ?? { decision: "allow" })
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
// ── Diagnostics ──────────────────────────────────────────────────────────
|
|
598
|
+
getHealthChecks(ctx) {
|
|
599
|
+
const settingsPath = this.getServerConfigPath(ctx);
|
|
600
|
+
const connectorId = ctx.connector.id;
|
|
601
|
+
return [
|
|
602
|
+
{
|
|
603
|
+
name: `${this.name}: settings.json present`,
|
|
604
|
+
check: () => existsSync(settingsPath) ? { status: "OK", detail: settingsPath } : { status: "FAIL", detail: `not found: ${settingsPath}` }
|
|
605
|
+
},
|
|
606
|
+
{
|
|
607
|
+
name: `${this.name}: server entry registered`,
|
|
608
|
+
check: () => {
|
|
609
|
+
if (!ctx.connector.server) {
|
|
610
|
+
return { status: "OK", detail: "no MCP server declared" };
|
|
611
|
+
}
|
|
612
|
+
const cfg = this.readJson(settingsPath);
|
|
613
|
+
const bucket = cfg?.[MCP_ROOT_KEY];
|
|
614
|
+
if (!cfg || !bucket) {
|
|
615
|
+
return { status: "FAIL", detail: `no "${MCP_ROOT_KEY}" in ${settingsPath}` };
|
|
616
|
+
}
|
|
617
|
+
return connectorId in bucket ? { status: "OK", detail: `"${MCP_ROOT_KEY}".${connectorId} present` } : {
|
|
618
|
+
status: "FAIL",
|
|
619
|
+
detail: `no "${MCP_ROOT_KEY}".${connectorId} in ${settingsPath}`
|
|
620
|
+
};
|
|
621
|
+
}
|
|
622
|
+
},
|
|
623
|
+
{
|
|
624
|
+
name: `${this.name}: plugin module present`,
|
|
625
|
+
check: () => {
|
|
626
|
+
const hasHooks = ctx.connector.hookEvents.length > 0 || this.nativeHookEvents(ctx).length > 0;
|
|
627
|
+
if (!hasHooks) return { status: "OK", detail: "no hooks declared" };
|
|
628
|
+
if (ctx.scope !== "project") {
|
|
629
|
+
return {
|
|
630
|
+
status: "OK",
|
|
631
|
+
detail: "amp plugins are project-scoped (user scope skipped)"
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
const pluginPath = this.getHookConfigPath(ctx);
|
|
635
|
+
return existsSync(pluginPath) ? { status: "OK", detail: pluginPath } : { status: "FAIL", detail: `not found: ${pluginPath}` };
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
];
|
|
639
|
+
}
|
|
640
|
+
/** Read a file, returning undefined on any error (idempotency compare). */
|
|
641
|
+
safeRead(path) {
|
|
642
|
+
try {
|
|
643
|
+
return readFileSync(path, "utf8");
|
|
644
|
+
} catch {
|
|
645
|
+
return void 0;
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
};
|
|
649
|
+
function ensureDir(dir) {
|
|
650
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
651
|
+
}
|
|
652
|
+
function ampEnvToken(name, def) {
|
|
653
|
+
if (def !== void 0) {
|
|
654
|
+
const v = process.env[name];
|
|
655
|
+
return v != null && v !== "" ? v : def;
|
|
656
|
+
}
|
|
657
|
+
return `\${${name}}`;
|
|
658
|
+
}
|
|
659
|
+
var adapter = new AmpAdapter();
|
|
660
|
+
var amp_default = adapter;
|
|
661
|
+
export {
|
|
662
|
+
AmpAdapter,
|
|
663
|
+
adapter,
|
|
664
|
+
amp_default as default
|
|
665
|
+
};
|
|
666
|
+
//# sourceMappingURL=amp-JPBQJJVM.js.map
|