@ken-jo/agent-connector 0.4.0 → 0.4.1
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 +8 -8
- package/dist/{action-PLZW5BGX.js → action-WT7GVRCC.js} +11 -10
- package/dist/{action-PLZW5BGX.js.map → action-WT7GVRCC.js.map} +1 -1
- package/dist/amazon-q-COAL3764.js +276 -0
- package/dist/amazon-q-COAL3764.js.map +1 -0
- package/dist/{amp-B524U6LO.js → amp-4U33CU7E.js} +3 -3
- package/dist/{antigravity-ANDKF3K7.js → antigravity-AMKKKHS4.js} +4 -4
- package/dist/{antigravity-cli-LLRPDDOT.js → antigravity-cli-XERXEH5W.js} +4 -4
- package/dist/{chunk-KGJWE6A2.js → chunk-24IDL5RW.js} +2 -2
- package/dist/{chunk-CIRDOY2V.js → chunk-2AVLX7GH.js} +3 -3
- package/dist/{chunk-ADTHT2FN.js → chunk-4ENJK5S2.js} +10 -173
- package/dist/chunk-4ENJK5S2.js.map +1 -0
- package/dist/{chunk-BHUCB6LY.js → chunk-5OWS5CMA.js} +8 -7
- package/dist/{chunk-BHUCB6LY.js.map → chunk-5OWS5CMA.js.map} +1 -1
- package/dist/{chunk-IW77FFNX.js → chunk-CPLK7GTE.js} +41 -5
- package/dist/chunk-CPLK7GTE.js.map +1 -0
- package/dist/chunk-DCKFYWGM.js +237 -0
- package/dist/chunk-DCKFYWGM.js.map +1 -0
- package/dist/{chunk-J3D5W5T2.js → chunk-EGCBZ2DC.js} +2 -2
- package/dist/{chunk-22Y24V7O.js → chunk-F5RX62T7.js} +2 -2
- package/dist/chunk-FAHIMJRU.js +177 -0
- package/dist/chunk-FAHIMJRU.js.map +1 -0
- package/dist/{chunk-BXGFUENQ.js → chunk-I5MLE6S7.js} +14 -1
- package/dist/{chunk-BXGFUENQ.js.map → chunk-I5MLE6S7.js.map} +1 -1
- package/dist/{chunk-LL4OBGRU.js → chunk-LQRAPXS4.js} +3 -3
- package/dist/{chunk-KGXDYGNJ.js → chunk-O5TWCIJN.js} +19 -19
- package/dist/{chunk-KGXDYGNJ.js.map → chunk-O5TWCIJN.js.map} +1 -1
- package/dist/{chunk-MAH6QQTO.js → chunk-QREL7LZJ.js} +5 -4
- package/dist/chunk-QREL7LZJ.js.map +1 -0
- package/dist/{chunk-G3ER4SY5.js → chunk-RIMVWCZY.js} +9 -9
- package/dist/{chunk-6XHB6BE3.js → chunk-SCVBYSF7.js} +1 -1
- package/dist/{chunk-KGGBNKI4.js → chunk-URHQMFB3.js} +90 -13
- package/dist/chunk-URHQMFB3.js.map +1 -0
- package/dist/chunk-WZA4CSGJ.js +28 -0
- package/dist/chunk-WZA4CSGJ.js.map +1 -0
- package/dist/{chunk-76FUSVIA.js → chunk-XJ4S47CT.js} +6 -5
- package/dist/chunk-XJ4S47CT.js.map +1 -0
- package/dist/{claude-code-MLWS7CW6.js → claude-code-W4PUFZL5.js} +20 -30
- package/dist/claude-code-W4PUFZL5.js.map +1 -0
- package/dist/cli/sdk.js +5 -4
- package/dist/cli/sdk.js.map +1 -1
- package/dist/cli.js +1 -1
- package/dist/cline-WRYMNS7Z.js +442 -0
- package/dist/cline-WRYMNS7Z.js.map +1 -0
- package/dist/{codebuff-SH2S3QAN.js → codebuff-DH5Y7GAT.js} +8 -4
- package/dist/codebuff-DH5Y7GAT.js.map +1 -0
- package/dist/{codex-BXA4FIF4.js → codex-DDHIV767.js} +43 -11
- package/dist/codex-DDHIV767.js.map +1 -0
- package/dist/continue-K43SUHLQ.js +790 -0
- package/dist/continue-K43SUHLQ.js.map +1 -0
- package/dist/{copilot-cli-ROIBTTDT.js → copilot-cli-Q3SSPT4V.js} +70 -12
- package/dist/copilot-cli-Q3SSPT4V.js.map +1 -0
- package/dist/{crush-QLEFLHUC.js → crush-MBEH2R3I.js} +130 -6
- package/dist/crush-MBEH2R3I.js.map +1 -0
- package/dist/{cursor-ASJPWZ4R.js → cursor-TMBXDYR4.js} +119 -19
- package/dist/cursor-TMBXDYR4.js.map +1 -0
- package/dist/{detect-ZIIS55LD.js → detect-KG5FG3EV.js} +5 -4
- package/dist/{detect-ZIIS55LD.js.map → detect-KG5FG3EV.js.map} +1 -1
- package/dist/{doctor-TJUGSQ5C.js → doctor-FLZF34Y3.js} +221 -95
- package/dist/doctor-FLZF34Y3.js.map +1 -0
- package/dist/{droid-NCSS3B5Y.js → droid-JC2NT6S4.js} +135 -13
- package/dist/droid-JC2NT6S4.js.map +1 -0
- package/dist/{gemini-cli-SLVWLXJW.js → gemini-cli-ILR7M5X3.js} +62 -10
- package/dist/gemini-cli-ILR7M5X3.js.map +1 -0
- package/dist/{goose-PBA7KTE5.js → goose-SFO5BBGW.js} +15 -8
- package/dist/goose-SFO5BBGW.js.map +1 -0
- package/dist/{hermes-ONDCECP2.js → hermes-3N7WN4WS.js} +329 -15
- package/dist/hermes-3N7WN4WS.js.map +1 -0
- package/dist/{hook-OU522ABV.js → hook-XR3Z45FB.js} +14 -11
- package/dist/hook-XR3Z45FB.js.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +3 -2
- package/dist/{install-UDCBL7HP.js → install-BSR62UHJ.js} +11 -10
- package/dist/{install-UDCBL7HP.js.map → install-BSR62UHJ.js.map} +1 -1
- package/dist/{introspect-DuZSPSHB.d.ts → introspect-Nw8hTReX.d.ts} +1 -1
- package/dist/{jetbrains-copilot-YABZ3HFA.js → jetbrains-copilot-NRZHI5PC.js} +85 -18
- package/dist/jetbrains-copilot-NRZHI5PC.js.map +1 -0
- package/dist/{kilo-F3ZDZKCT.js → kilo-5NWOXVSD.js} +16 -16
- package/dist/kilo-5NWOXVSD.js.map +1 -0
- package/dist/{kilo-cli-3HWPWBY4.js → kilo-cli-UZ7R5UOP.js} +3 -3
- package/dist/{kimi-NV72FVAO.js → kimi-2YFK2GBA.js} +87 -25
- package/dist/kimi-2YFK2GBA.js.map +1 -0
- package/dist/{kiro-C5W6AGDR.js → kiro-YL6AWUYG.js} +3 -3
- package/dist/{leaderboard-TIBVONVT.js → leaderboard-MJG24O35.js} +3 -3
- package/dist/{mimo-code-IMM5YM3M.js → mimo-code-FTMGSKMQ.js} +41 -5
- package/dist/mimo-code-FTMGSKMQ.js.map +1 -0
- package/dist/{mux-DSSH4AFS.js → mux-5XZFB44C.js} +156 -6
- package/dist/mux-5XZFB44C.js.map +1 -0
- package/dist/{nemoclaw-3EVNKD3I.js → nemoclaw-W5KEJ3WX.js} +4 -4
- package/dist/{omp-Z6FRNDPN.js → omp-2QMEY6HD.js} +138 -18
- package/dist/omp-2QMEY6HD.js.map +1 -0
- package/dist/{openclaw-7G437NT7.js → openclaw-EXJEFNZI.js} +4 -4
- package/dist/{opencode-Q6KBSAKW.js → opencode-WNXYJR4X.js} +84 -27
- package/dist/opencode-WNXYJR4X.js.map +1 -0
- package/dist/{package-RMPN57YZ.js → package-ACZXJHWB.js} +8 -7
- package/dist/{package-RMPN57YZ.js.map → package-ACZXJHWB.js.map} +1 -1
- package/dist/{pi-LZT6WWAZ.js → pi-PVVPVWKG.js} +3 -2
- package/dist/{pi-LZT6WWAZ.js.map → pi-PVVPVWKG.js.map} +1 -1
- package/dist/{qwen-code-WQBKRPWJ.js → qwen-code-G4CXKUHR.js} +426 -10
- package/dist/qwen-code-G4CXKUHR.js.map +1 -0
- package/dist/{roo-code-GWSNU3OW.js → roo-code-67TYJIQR.js} +3 -3
- package/dist/runtime/index.d.ts +1 -1
- package/dist/runtime/index.js +9 -8
- package/dist/sdk/index.d.ts +3 -3
- package/dist/sdk/index.js +4 -3
- package/dist/sdk/index.js.map +1 -1
- package/dist/sdk/test.d.ts +2 -2
- package/dist/sdk/test.js +6 -5
- package/dist/sdk/test.js.map +1 -1
- package/dist/{serve-D3GO36UT.js → serve-GER7URPR.js} +11 -10
- package/dist/{serve-D3GO36UT.js.map → serve-GER7URPR.js.map} +1 -1
- package/dist/{status-KOSKCETC.js → status-LAJE3BM5.js} +7 -6
- package/dist/{status-KOSKCETC.js.map → status-LAJE3BM5.js.map} +1 -1
- package/dist/{statusline-ZNTCL5YC.js → statusline-L376ZPZU.js} +10 -9
- package/dist/{statusline-ZNTCL5YC.js.map → statusline-L376ZPZU.js.map} +1 -1
- package/dist/{telemetry-XIKUUGTQ.js → telemetry-B3DX3PWU.js} +6 -5
- package/dist/{telemetry-XIKUUGTQ.js.map → telemetry-B3DX3PWU.js.map} +1 -1
- package/dist/{trae-NWT3TMSZ.js → trae-7CZZGOGI.js} +3 -3
- package/dist/{types-BEKsAium.d.ts → types-CRVrFWeU.d.ts} +64 -22
- package/dist/{uninstall-2LBLQVFA.js → uninstall-ZYTW7YMZ.js} +11 -10
- package/dist/{uninstall-2LBLQVFA.js.map → uninstall-ZYTW7YMZ.js.map} +1 -1
- package/dist/{upgrade-ZC2S7VRN.js → upgrade-4LUEEOZW.js} +11 -10
- package/dist/{upgrade-ZC2S7VRN.js.map → upgrade-4LUEEOZW.js.map} +1 -1
- package/dist/{usage-E2A4O2CY.js → usage-ZFBVQF3H.js} +3 -3
- package/dist/{usage-event-JKMC4QC4.js → usage-event-GZ32AGEB.js} +10 -9
- package/dist/{usage-event-JKMC4QC4.js.map → usage-event-GZ32AGEB.js.map} +1 -1
- package/dist/{vscode-copilot-ALV45XF6.js → vscode-copilot-MF2CDNMT.js} +23 -10
- package/dist/vscode-copilot-MF2CDNMT.js.map +1 -0
- package/dist/{warp-H6IX4U3J.js → warp-VKG55TAT.js} +55 -4
- package/dist/warp-VKG55TAT.js.map +1 -0
- package/dist/windsurf-FU5G4SHF.js +443 -0
- package/dist/windsurf-FU5G4SHF.js.map +1 -0
- package/dist/{zed-RH34TNSW.js → zed-SFUHL3GS.js} +3 -3
- package/package.json +2 -2
- package/dist/chunk-3PFFTFCU.js +0 -190
- package/dist/chunk-3PFFTFCU.js.map +0 -1
- package/dist/chunk-76FUSVIA.js.map +0 -1
- package/dist/chunk-ADTHT2FN.js.map +0 -1
- package/dist/chunk-IW77FFNX.js.map +0 -1
- package/dist/chunk-KGGBNKI4.js.map +0 -1
- package/dist/chunk-MAH6QQTO.js.map +0 -1
- package/dist/claude-code-MLWS7CW6.js.map +0 -1
- package/dist/codebuff-SH2S3QAN.js.map +0 -1
- package/dist/codex-BXA4FIF4.js.map +0 -1
- package/dist/copilot-cli-ROIBTTDT.js.map +0 -1
- package/dist/crush-QLEFLHUC.js.map +0 -1
- package/dist/cursor-ASJPWZ4R.js.map +0 -1
- package/dist/doctor-TJUGSQ5C.js.map +0 -1
- package/dist/droid-NCSS3B5Y.js.map +0 -1
- package/dist/gemini-cli-SLVWLXJW.js.map +0 -1
- package/dist/goose-PBA7KTE5.js.map +0 -1
- package/dist/hermes-ONDCECP2.js.map +0 -1
- package/dist/hook-OU522ABV.js.map +0 -1
- package/dist/jetbrains-copilot-YABZ3HFA.js.map +0 -1
- package/dist/kilo-F3ZDZKCT.js.map +0 -1
- package/dist/kimi-NV72FVAO.js.map +0 -1
- package/dist/mimo-code-IMM5YM3M.js.map +0 -1
- package/dist/mux-DSSH4AFS.js.map +0 -1
- package/dist/omp-Z6FRNDPN.js.map +0 -1
- package/dist/opencode-Q6KBSAKW.js.map +0 -1
- package/dist/qwen-code-WQBKRPWJ.js.map +0 -1
- package/dist/vscode-copilot-ALV45XF6.js.map +0 -1
- package/dist/warp-H6IX4U3J.js.map +0 -1
- /package/dist/{amp-B524U6LO.js.map → amp-4U33CU7E.js.map} +0 -0
- /package/dist/{antigravity-ANDKF3K7.js.map → antigravity-AMKKKHS4.js.map} +0 -0
- /package/dist/{antigravity-cli-LLRPDDOT.js.map → antigravity-cli-XERXEH5W.js.map} +0 -0
- /package/dist/{chunk-KGJWE6A2.js.map → chunk-24IDL5RW.js.map} +0 -0
- /package/dist/{chunk-CIRDOY2V.js.map → chunk-2AVLX7GH.js.map} +0 -0
- /package/dist/{chunk-J3D5W5T2.js.map → chunk-EGCBZ2DC.js.map} +0 -0
- /package/dist/{chunk-22Y24V7O.js.map → chunk-F5RX62T7.js.map} +0 -0
- /package/dist/{chunk-LL4OBGRU.js.map → chunk-LQRAPXS4.js.map} +0 -0
- /package/dist/{chunk-G3ER4SY5.js.map → chunk-RIMVWCZY.js.map} +0 -0
- /package/dist/{chunk-6XHB6BE3.js.map → chunk-SCVBYSF7.js.map} +0 -0
- /package/dist/{kilo-cli-3HWPWBY4.js.map → kilo-cli-UZ7R5UOP.js.map} +0 -0
- /package/dist/{kiro-C5W6AGDR.js.map → kiro-YL6AWUYG.js.map} +0 -0
- /package/dist/{leaderboard-TIBVONVT.js.map → leaderboard-MJG24O35.js.map} +0 -0
- /package/dist/{nemoclaw-3EVNKD3I.js.map → nemoclaw-W5KEJ3WX.js.map} +0 -0
- /package/dist/{openclaw-7G437NT7.js.map → openclaw-EXJEFNZI.js.map} +0 -0
- /package/dist/{roo-code-GWSNU3OW.js.map → roo-code-67TYJIQR.js.map} +0 -0
- /package/dist/{trae-NWT3TMSZ.js.map → trae-7CZZGOGI.js.map} +0 -0
- /package/dist/{usage-E2A4O2CY.js.map → usage-ZFBVQF3H.js.map} +0 -0
- /package/dist/{zed-RH34TNSW.js.map → zed-SFUHL3GS.js.map} +0 -0
|
@@ -0,0 +1,790 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
readYaml,
|
|
4
|
+
writeYaml
|
|
5
|
+
} from "./chunk-EXLS7BSI.js";
|
|
6
|
+
import {
|
|
7
|
+
extractSessionId,
|
|
8
|
+
toolResponseToString
|
|
9
|
+
} from "./chunk-WZA4CSGJ.js";
|
|
10
|
+
import {
|
|
11
|
+
resolveEnvRefsDeep
|
|
12
|
+
} from "./chunk-DBQMX75U.js";
|
|
13
|
+
import {
|
|
14
|
+
BaseAdapter
|
|
15
|
+
} from "./chunk-CPLK7GTE.js";
|
|
16
|
+
import "./chunk-7L6ITC4T.js";
|
|
17
|
+
import {
|
|
18
|
+
buildHomeBinHookCommand,
|
|
19
|
+
buildServeWrapperCommand,
|
|
20
|
+
isHomeBinHookCommand,
|
|
21
|
+
shouldWrapForTelemetry
|
|
22
|
+
} from "./chunk-I5MLE6S7.js";
|
|
23
|
+
import "./chunk-N6HH7K6P.js";
|
|
24
|
+
import "./chunk-TRQKHP7D.js";
|
|
25
|
+
|
|
26
|
+
// src/adapters/continue/index.ts
|
|
27
|
+
import { existsSync, statSync } from "fs";
|
|
28
|
+
import { homedir } from "os";
|
|
29
|
+
import { basename, join } from "path";
|
|
30
|
+
var HOST = "continue";
|
|
31
|
+
var MCP_ROOT_KEY = "mcpServers";
|
|
32
|
+
var SUPPORTED_EVENTS = /* @__PURE__ */ new Set([
|
|
33
|
+
"PreToolUse",
|
|
34
|
+
"PostToolUse",
|
|
35
|
+
"PostToolUseFailure",
|
|
36
|
+
"UserPromptSubmit",
|
|
37
|
+
"SessionStart",
|
|
38
|
+
"SessionEnd",
|
|
39
|
+
"Stop",
|
|
40
|
+
"Notification",
|
|
41
|
+
"SubagentStart",
|
|
42
|
+
"SubagentStop",
|
|
43
|
+
"PermissionRequest",
|
|
44
|
+
"PreCompact"
|
|
45
|
+
]);
|
|
46
|
+
var ContinueAdapter = class extends BaseAdapter {
|
|
47
|
+
id = HOST;
|
|
48
|
+
name = "Continue";
|
|
49
|
+
paradigm = "json-stdio";
|
|
50
|
+
capabilities = {
|
|
51
|
+
// Memory surface: WIRED. Continue reads `.continue/rules/*.md` (NOT
|
|
52
|
+
// AGENTS.md), so the AGENTS.md-first BaseAdapter default does not apply — the
|
|
53
|
+
// installMemory/uninstallMemory overrides below write a DEDICATED
|
|
54
|
+
// agent-connector-owned file (<projectDir>/.continue/rules/agent-connector.md)
|
|
55
|
+
// that LEADS with `alwaysApply: true` frontmatter (always-included). Project
|
|
56
|
+
// scope only; user scope skip-warns (no verified user/global rules dir).
|
|
57
|
+
supportsMemory: true,
|
|
58
|
+
//
|
|
59
|
+
// json-stdio: the `cn` CLI ships a Claude-Code-COMPATIBLE hooks system (PR
|
|
60
|
+
// #11029). Every event in HOOK_EVENT_NAMES ∩ canonical fires natively; the
|
|
61
|
+
// names are PascalCase 1:1 with Claude. PreCompact IS in continue's set
|
|
62
|
+
// (PreCompactInput); only PostCompact is absent → that stays false (install
|
|
63
|
+
// warn-skips it).
|
|
64
|
+
preToolUse: true,
|
|
65
|
+
postToolUse: true,
|
|
66
|
+
postToolUseFailure: true,
|
|
67
|
+
preCompact: true,
|
|
68
|
+
sessionStart: true,
|
|
69
|
+
sessionEnd: true,
|
|
70
|
+
userPromptSubmit: true,
|
|
71
|
+
stop: true,
|
|
72
|
+
notification: true,
|
|
73
|
+
subagentStart: true,
|
|
74
|
+
subagentStop: true,
|
|
75
|
+
permissionRequest: true,
|
|
76
|
+
// Continue's PreToolUse output carries updatedInput (HookOutput
|
|
77
|
+
// PreToolUseHookOutput.updatedInput) → it CAN rewrite tool input. It cannot
|
|
78
|
+
// rewrite already-emitted tool output (PostToolUse exposes updatedMCPToolOutput,
|
|
79
|
+
// but — matching the claude-code adapter's conservative stance — we do not
|
|
80
|
+
// wire output rewrite). It honors hookSpecificOutput.additionalContext.
|
|
81
|
+
canModifyArgs: true,
|
|
82
|
+
canModifyOutput: false,
|
|
83
|
+
canInjectSessionContext: true,
|
|
84
|
+
// Continue registers stdio + remote (SSE / Streamable HTTP) MCP servers
|
|
85
|
+
// (primary-verified: type ∈ stdio|sse|streamable-http). Mapped to the
|
|
86
|
+
// framework's transport enum: stdio + sse + http (http = streamable-http).
|
|
87
|
+
transports: ["stdio", "sse", "http"],
|
|
88
|
+
// Native (passthrough) hooks: continue's HOOK_EVENT_NAMES carries five
|
|
89
|
+
// host-specific events with NO canonical analog — ConfigChange, TeammateIdle,
|
|
90
|
+
// TaskCompleted, WorktreeCreate, WorktreeRemove (each a real HookInput
|
|
91
|
+
// member, types.ts:214-241). They sit below the ≥3-host promotion bar, so a
|
|
92
|
+
// connector reaches them via platforms.continue.nativeHooks; installHooks
|
|
93
|
+
// files the event-name key VERBATIM and the generic uninstallHooks reverses
|
|
94
|
+
// it by connector-id ownership (same proven pattern as copilot-cli/hermes/
|
|
95
|
+
// jetbrains-copilot).
|
|
96
|
+
supportsNativeHooks: true
|
|
97
|
+
// Content surfaces: Continue's Rules/prompts are NOT wired here. Leave the
|
|
98
|
+
// supports* flags UNSET (base skip-warns).
|
|
99
|
+
};
|
|
100
|
+
// ── Detection ────────────────────────────────────────────────────────────
|
|
101
|
+
detectInstalled(projectDir) {
|
|
102
|
+
const userDir = this.userConfigDir();
|
|
103
|
+
const userCfg = join(userDir, "config.yaml");
|
|
104
|
+
const projCfg = join(projectDir, ".continue", "config.yaml");
|
|
105
|
+
const userInstalled = existsSync(userDir) || existsSync(userCfg);
|
|
106
|
+
const projInstalled = existsSync(projCfg);
|
|
107
|
+
const installed = userInstalled || projInstalled;
|
|
108
|
+
const scope = projInstalled && !userInstalled ? "project" : "user";
|
|
109
|
+
const configPath = scope === "project" ? projCfg : userCfg;
|
|
110
|
+
return {
|
|
111
|
+
id: this.id,
|
|
112
|
+
name: this.name,
|
|
113
|
+
installed,
|
|
114
|
+
paradigm: this.paradigm,
|
|
115
|
+
capabilities: this.capabilities,
|
|
116
|
+
configPath,
|
|
117
|
+
scope,
|
|
118
|
+
reason: installed ? `found Continue config (${scope}) at ${configPath}` : `no Continue config at ${userCfg} or ${projCfg}`,
|
|
119
|
+
confidence: installed ? "high" : "low"
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
// ── Native paths ─────────────────────────────────────────────────────────
|
|
123
|
+
/** ~/.continue (user) or <projectDir>/.continue (project). */
|
|
124
|
+
getConfigDir(ctx) {
|
|
125
|
+
return ctx.scope === "project" ? join(ctx.projectDir, ".continue") : this.userConfigDir();
|
|
126
|
+
}
|
|
127
|
+
/** MCP config: config.yaml under the per-scope .continue dir. */
|
|
128
|
+
getServerConfigPath(ctx) {
|
|
129
|
+
return join(this.getConfigDir(ctx), "config.yaml");
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Hooks live in a SEPARATE settings.json (NOT the YAML config.yaml that holds
|
|
133
|
+
* MCP servers): user-global → <CONTINUE_GLOBAL_DIR|~/.continue>/settings.json;
|
|
134
|
+
* project → <projectDir>/.continue/settings.json (hookConfig.ts
|
|
135
|
+
* getSettingsFilePaths). The user-scope dir honors CONTINUE_GLOBAL_DIR exactly
|
|
136
|
+
* as the `cn` CLI's hook loader does — this is INTENTIONALLY independent of
|
|
137
|
+
* getConfigDir/userConfigDir (the MCP path), which the hook env must not perturb.
|
|
138
|
+
*/
|
|
139
|
+
getHookConfigPath(ctx) {
|
|
140
|
+
const dir = ctx.scope === "project" ? join(ctx.projectDir, ".continue") : process.env.CONTINUE_GLOBAL_DIR || join(homedir(), ".continue");
|
|
141
|
+
return join(dir, "settings.json");
|
|
142
|
+
}
|
|
143
|
+
/** ~/.continue — homedir() covers %USERPROFILE% on Windows. */
|
|
144
|
+
userConfigDir() {
|
|
145
|
+
return join(homedir(), ".continue");
|
|
146
|
+
}
|
|
147
|
+
// ── Memory surface: the `.continue/rules` content tree ────────────────────
|
|
148
|
+
// A DEDICATED agent-connector-owned file that MUST LEAD with `alwaysApply:
|
|
149
|
+
// true` frontmatter (always-included). Because of that leading frontmatter we
|
|
150
|
+
// do NOT use the base managed-block engine (it appends a block at EOF and
|
|
151
|
+
// cannot guarantee a leading frontmatter, nor delete a file that retains
|
|
152
|
+
// frontmatter on uninstall) — instead we own the whole file via the
|
|
153
|
+
// content-file writers: install writes/updates it idempotently, uninstall
|
|
154
|
+
// deletes it. PROJECT SCOPE ONLY (the user/global rules dir is unverified).
|
|
155
|
+
/** <projectDir>/.continue/rules/agent-connector.md — the dedicated owned file. */
|
|
156
|
+
memoryFilePath(ctx) {
|
|
157
|
+
return join(ctx.projectDir, ".continue", "rules", "agent-connector.md");
|
|
158
|
+
}
|
|
159
|
+
/** True when `<projectDir>/.continue/rules` exists and is a regular FILE. */
|
|
160
|
+
rulesDirIsFile(ctx) {
|
|
161
|
+
const p = join(ctx.projectDir, ".continue", "rules");
|
|
162
|
+
if (!existsSync(p)) return false;
|
|
163
|
+
try {
|
|
164
|
+
return statSync(p).isFile();
|
|
165
|
+
} catch {
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Compose the dedicated rule file: `alwaysApply: true` frontmatter (the
|
|
171
|
+
* always-on activation directive) + every declared memory entry's content,
|
|
172
|
+
* joined with a blank line. The connector's content is host-agnostic markdown
|
|
173
|
+
* (MemoryDef forbids its own frontmatter), so the directive is ours to add.
|
|
174
|
+
*/
|
|
175
|
+
renderMemoryFile(ctx) {
|
|
176
|
+
const body = (ctx.connector.memory ?? []).map((m) => m.content.trim()).filter((c) => c.length > 0).join("\n\n");
|
|
177
|
+
return this.renderFrontmatterMd({ alwaysApply: true }, body);
|
|
178
|
+
}
|
|
179
|
+
installMemory(ctx) {
|
|
180
|
+
const { connector } = ctx;
|
|
181
|
+
const entries = connector.memory ?? [];
|
|
182
|
+
if (connector.platforms[this.id]?.memory === false) {
|
|
183
|
+
return [{ platform: this.id, action: "skip", detail: `memory disabled for ${this.id}` }];
|
|
184
|
+
}
|
|
185
|
+
if (entries.length === 0) {
|
|
186
|
+
return [{ platform: this.id, action: "skip", detail: "connector declares no memory" }];
|
|
187
|
+
}
|
|
188
|
+
if (ctx.scope !== "project") {
|
|
189
|
+
return [
|
|
190
|
+
{
|
|
191
|
+
platform: this.id,
|
|
192
|
+
action: "warn",
|
|
193
|
+
detail: `no ${ctx.scope}-scope rules dir verified for continue (.continue/rules is project-scope); ${entries.length} skipped`
|
|
194
|
+
}
|
|
195
|
+
];
|
|
196
|
+
}
|
|
197
|
+
if (this.rulesDirIsFile(ctx)) {
|
|
198
|
+
return [
|
|
199
|
+
{
|
|
200
|
+
platform: this.id,
|
|
201
|
+
action: "warn",
|
|
202
|
+
path: join(ctx.projectDir, ".continue", "rules"),
|
|
203
|
+
detail: "existing .continue/rules is a file, not a directory; left untouched \u2014 convert it to a .continue/rules/ directory to receive agent-connector memory"
|
|
204
|
+
}
|
|
205
|
+
];
|
|
206
|
+
}
|
|
207
|
+
return [this.writeContentFile(this.memoryFilePath(ctx), this.renderMemoryFile(ctx), ctx.dryRun)];
|
|
208
|
+
}
|
|
209
|
+
uninstallMemory(ctx) {
|
|
210
|
+
if (ctx.scope !== "project" || this.rulesDirIsFile(ctx)) {
|
|
211
|
+
const path = this.memoryFilePath(ctx);
|
|
212
|
+
return [
|
|
213
|
+
{ platform: this.id, action: "skip", path, detail: `${basename(path)} absent` }
|
|
214
|
+
];
|
|
215
|
+
}
|
|
216
|
+
return [this.removeContentFile(this.memoryFilePath(ctx), ctx.dryRun)];
|
|
217
|
+
}
|
|
218
|
+
// ── MCP server install / uninstall (YAML — ARRAY merge) ───────────────────
|
|
219
|
+
installServer(ctx) {
|
|
220
|
+
const { connector, dryRun } = ctx;
|
|
221
|
+
const path = this.getServerConfigPath(ctx);
|
|
222
|
+
const server = this.effectiveServer(ctx);
|
|
223
|
+
if (!server) {
|
|
224
|
+
return [
|
|
225
|
+
{
|
|
226
|
+
platform: this.id,
|
|
227
|
+
action: "skip",
|
|
228
|
+
path,
|
|
229
|
+
detail: connector.server ? "server registration disabled for continue" : "connector declares no MCP server"
|
|
230
|
+
}
|
|
231
|
+
];
|
|
232
|
+
}
|
|
233
|
+
const entry = this.renderServerEntry(ctx, server);
|
|
234
|
+
const cfg = readYaml(path) ?? {};
|
|
235
|
+
const existingRoot = cfg[MCP_ROOT_KEY];
|
|
236
|
+
if (existingRoot !== void 0 && !Array.isArray(existingRoot)) {
|
|
237
|
+
return [{ platform: this.id, action: "skip", path, detail: `${MCP_ROOT_KEY} is not a YAML array \u2014 left untouched (manual fix needed)` }];
|
|
238
|
+
}
|
|
239
|
+
const list = this.serverArray(cfg);
|
|
240
|
+
const idx = list.findIndex((e) => this.entryName(e) === connector.id);
|
|
241
|
+
let action;
|
|
242
|
+
if (idx < 0) {
|
|
243
|
+
list.push(entry);
|
|
244
|
+
action = "create";
|
|
245
|
+
} else if (JSON.stringify(list[idx]) === JSON.stringify(entry)) {
|
|
246
|
+
action = "skip";
|
|
247
|
+
} else {
|
|
248
|
+
list[idx] = entry;
|
|
249
|
+
action = "update";
|
|
250
|
+
}
|
|
251
|
+
if (action !== "skip") {
|
|
252
|
+
cfg[MCP_ROOT_KEY] = list;
|
|
253
|
+
writeYaml(path, cfg, dryRun);
|
|
254
|
+
}
|
|
255
|
+
return [{ platform: this.id, action, path, detail: `${MCP_ROOT_KEY}[name=${connector.id}]` }];
|
|
256
|
+
}
|
|
257
|
+
uninstallServer(ctx) {
|
|
258
|
+
const { connector, dryRun } = ctx;
|
|
259
|
+
const path = this.getServerConfigPath(ctx);
|
|
260
|
+
const cfg = readYaml(path);
|
|
261
|
+
const rawList = cfg?.[MCP_ROOT_KEY];
|
|
262
|
+
if (!cfg || !Array.isArray(rawList)) {
|
|
263
|
+
return [
|
|
264
|
+
{
|
|
265
|
+
platform: this.id,
|
|
266
|
+
action: "skip",
|
|
267
|
+
path,
|
|
268
|
+
detail: `${MCP_ROOT_KEY}[name=${connector.id}] absent`
|
|
269
|
+
}
|
|
270
|
+
];
|
|
271
|
+
}
|
|
272
|
+
const list = rawList;
|
|
273
|
+
const kept = list.filter((e) => this.entryName(e) !== connector.id);
|
|
274
|
+
if (kept.length === list.length) {
|
|
275
|
+
return [
|
|
276
|
+
{
|
|
277
|
+
platform: this.id,
|
|
278
|
+
action: "skip",
|
|
279
|
+
path,
|
|
280
|
+
detail: `${MCP_ROOT_KEY}[name=${connector.id}] absent`
|
|
281
|
+
}
|
|
282
|
+
];
|
|
283
|
+
}
|
|
284
|
+
cfg[MCP_ROOT_KEY] = kept;
|
|
285
|
+
writeYaml(path, cfg, dryRun);
|
|
286
|
+
return [
|
|
287
|
+
{ platform: this.id, action: "remove", path, detail: `${MCP_ROOT_KEY}[name=${connector.id}]` }
|
|
288
|
+
];
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Render a normalized ServerDef into Continue's native mcpServers entry.
|
|
292
|
+
* Continue has no documented native ${env:VAR} token — resolve every reference
|
|
293
|
+
* to a literal at install time (the safe path, matching amazon-q/goose/hermes).
|
|
294
|
+
* Honors the telemetry serve-wrapper for stdio.
|
|
295
|
+
*/
|
|
296
|
+
renderServerEntry(ctx, server) {
|
|
297
|
+
const transport = server.transport;
|
|
298
|
+
if (transport === "stdio") {
|
|
299
|
+
let command = server.command ?? "";
|
|
300
|
+
let args = [...server.args ?? []];
|
|
301
|
+
if (shouldWrapForTelemetry(server, ctx.connector.telemetry)) {
|
|
302
|
+
const wrapped = buildServeWrapperCommand(
|
|
303
|
+
ctx.homeBinPath,
|
|
304
|
+
ctx.connector.id,
|
|
305
|
+
command,
|
|
306
|
+
args,
|
|
307
|
+
ctx.scope,
|
|
308
|
+
this.id
|
|
309
|
+
);
|
|
310
|
+
command = wrapped.command;
|
|
311
|
+
args = wrapped.args;
|
|
312
|
+
}
|
|
313
|
+
command = resolveEnvRefsDeep(command);
|
|
314
|
+
args = resolveEnvRefsDeep(args);
|
|
315
|
+
const entry = {
|
|
316
|
+
name: ctx.connector.id,
|
|
317
|
+
command
|
|
318
|
+
};
|
|
319
|
+
if (args.length > 0) entry.args = args;
|
|
320
|
+
const env = this.renderEnv(server.env);
|
|
321
|
+
if (env) entry.env = env;
|
|
322
|
+
if (typeof server.cwd === "string" && server.cwd.length > 0) {
|
|
323
|
+
entry.cwd = resolveEnvRefsDeep(server.cwd);
|
|
324
|
+
}
|
|
325
|
+
return entry;
|
|
326
|
+
}
|
|
327
|
+
const type = transport === "sse" ? "sse" : "streamable-http";
|
|
328
|
+
return {
|
|
329
|
+
name: ctx.connector.id,
|
|
330
|
+
type,
|
|
331
|
+
url: resolveEnvRefsDeep(server.url ?? "")
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Render stdio env values. Continue has no documented native interpolation
|
|
336
|
+
* token, so resolve `${env:VAR}` references to literals at install time.
|
|
337
|
+
*/
|
|
338
|
+
renderEnv(env) {
|
|
339
|
+
if (!env || Object.keys(env).length === 0) return void 0;
|
|
340
|
+
const out = {};
|
|
341
|
+
for (const [k, v] of Object.entries(resolveEnvRefsDeep({ ...env }))) {
|
|
342
|
+
out[k] = String(v);
|
|
343
|
+
}
|
|
344
|
+
return out;
|
|
345
|
+
}
|
|
346
|
+
// ── Hook install / uninstall (separate settings.json, Claude-shaped) ──────
|
|
347
|
+
// Continue's hooks live in settings.json (NOT the YAML config.yaml that holds
|
|
348
|
+
// MCP servers), under `hooks` keyed by PascalCase event, each value an array
|
|
349
|
+
// of { matcher?, hooks:[{ type:"command", command }] } — BYTE-IDENTICAL to
|
|
350
|
+
// Claude Code. Events outside SUPPORTED_EVENTS (PostCompact) warn-skip; the
|
|
351
|
+
// five host-specific events with no canonical analog (ConfigChange,
|
|
352
|
+
// TeammateIdle, TaskCompleted, WorktreeCreate, WorktreeRemove) ride the
|
|
353
|
+
// nativeHooks passthrough below. Merge-preserving: the user's own hooks + any
|
|
354
|
+
// sibling connector's hooks survive; uninstall strips ONLY this connector's
|
|
355
|
+
// home-bin command (anchored).
|
|
356
|
+
installHooks(ctx) {
|
|
357
|
+
const { connector } = ctx;
|
|
358
|
+
const override = connector.platforms[HOST];
|
|
359
|
+
const hooksDisabled = override?.hooks === false;
|
|
360
|
+
const normalizedEvents = hooksDisabled ? [] : connector.hookEvents;
|
|
361
|
+
const nativeHooks = override?.nativeHooks ?? {};
|
|
362
|
+
const nativeEvents = Object.keys(nativeHooks);
|
|
363
|
+
if (normalizedEvents.length === 0 && nativeEvents.length === 0) {
|
|
364
|
+
return [
|
|
365
|
+
{
|
|
366
|
+
platform: this.id,
|
|
367
|
+
action: "skip",
|
|
368
|
+
detail: hooksDisabled ? "hooks disabled for continue" : "connector declares no hooks"
|
|
369
|
+
}
|
|
370
|
+
];
|
|
371
|
+
}
|
|
372
|
+
const hookPath = this.getHookConfigPath(ctx);
|
|
373
|
+
const file = this.readJson(hookPath) ?? {};
|
|
374
|
+
const hooks = file.hooks ??= {};
|
|
375
|
+
const changes = [];
|
|
376
|
+
let mutated = false;
|
|
377
|
+
for (const event of normalizedEvents) {
|
|
378
|
+
if (!SUPPORTED_EVENTS.has(event)) {
|
|
379
|
+
changes.push({
|
|
380
|
+
platform: this.id,
|
|
381
|
+
action: "warn",
|
|
382
|
+
path: hookPath,
|
|
383
|
+
detail: `${event} has no Continue hook equivalent \u2014 skipped`
|
|
384
|
+
});
|
|
385
|
+
continue;
|
|
386
|
+
}
|
|
387
|
+
const command = buildHomeBinHookCommand(ctx.homeBinPath, HOST, event, connector.id);
|
|
388
|
+
const matcher = connector.hooks[event]?.matcher ?? "";
|
|
389
|
+
const entry = {
|
|
390
|
+
matcher,
|
|
391
|
+
hooks: [{ type: "command", command }]
|
|
392
|
+
};
|
|
393
|
+
const bucket = hooks[event] ??= [];
|
|
394
|
+
const existingIdx = bucket.findIndex((e) => this.entryHasOurCommand(e, ctx));
|
|
395
|
+
if (existingIdx >= 0) {
|
|
396
|
+
if (JSON.stringify(bucket[existingIdx]) === JSON.stringify(entry)) {
|
|
397
|
+
changes.push({
|
|
398
|
+
platform: this.id,
|
|
399
|
+
action: "skip",
|
|
400
|
+
path: hookPath,
|
|
401
|
+
detail: `hooks.${event} already registered`
|
|
402
|
+
});
|
|
403
|
+
continue;
|
|
404
|
+
}
|
|
405
|
+
bucket[existingIdx] = entry;
|
|
406
|
+
changes.push({
|
|
407
|
+
platform: this.id,
|
|
408
|
+
action: "update",
|
|
409
|
+
path: hookPath,
|
|
410
|
+
detail: `hooks.${event}`
|
|
411
|
+
});
|
|
412
|
+
} else {
|
|
413
|
+
bucket.push(entry);
|
|
414
|
+
changes.push({
|
|
415
|
+
platform: this.id,
|
|
416
|
+
action: "create",
|
|
417
|
+
path: hookPath,
|
|
418
|
+
detail: `hooks.${event}`
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
mutated = true;
|
|
422
|
+
}
|
|
423
|
+
for (const nativeEvent of nativeEvents) {
|
|
424
|
+
const command = buildHomeBinHookCommand(ctx.homeBinPath, HOST, nativeEvent, connector.id);
|
|
425
|
+
const entry = {
|
|
426
|
+
matcher: nativeHooks[nativeEvent]?.matcher ?? "",
|
|
427
|
+
hooks: [{ type: "command", command }]
|
|
428
|
+
};
|
|
429
|
+
const bucket = hooks[nativeEvent] ??= [];
|
|
430
|
+
const existingIdx = bucket.findIndex((e) => e.hooks?.[0]?.command === command);
|
|
431
|
+
if (existingIdx >= 0) {
|
|
432
|
+
if (JSON.stringify(bucket[existingIdx]) === JSON.stringify(entry)) {
|
|
433
|
+
changes.push({
|
|
434
|
+
platform: this.id,
|
|
435
|
+
action: "skip",
|
|
436
|
+
path: hookPath,
|
|
437
|
+
detail: `hooks.${nativeEvent} (native) already registered`
|
|
438
|
+
});
|
|
439
|
+
continue;
|
|
440
|
+
}
|
|
441
|
+
bucket[existingIdx] = entry;
|
|
442
|
+
changes.push({
|
|
443
|
+
platform: this.id,
|
|
444
|
+
action: "update",
|
|
445
|
+
path: hookPath,
|
|
446
|
+
detail: `hooks.${nativeEvent} (native)`
|
|
447
|
+
});
|
|
448
|
+
} else {
|
|
449
|
+
bucket.push(entry);
|
|
450
|
+
changes.push({
|
|
451
|
+
platform: this.id,
|
|
452
|
+
action: "create",
|
|
453
|
+
path: hookPath,
|
|
454
|
+
detail: `hooks.${nativeEvent} (native)`
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
mutated = true;
|
|
458
|
+
}
|
|
459
|
+
if (mutated) this.writeJson(hookPath, file, ctx.dryRun);
|
|
460
|
+
return changes;
|
|
461
|
+
}
|
|
462
|
+
uninstallHooks(ctx) {
|
|
463
|
+
const hookPath = this.getHookConfigPath(ctx);
|
|
464
|
+
const file = this.readJson(hookPath);
|
|
465
|
+
const hooks = file?.hooks;
|
|
466
|
+
if (!file || !hooks) {
|
|
467
|
+
return [
|
|
468
|
+
{
|
|
469
|
+
platform: this.id,
|
|
470
|
+
action: "skip",
|
|
471
|
+
path: hookPath,
|
|
472
|
+
detail: "no hooks section present"
|
|
473
|
+
}
|
|
474
|
+
];
|
|
475
|
+
}
|
|
476
|
+
const changes = [];
|
|
477
|
+
let mutated = false;
|
|
478
|
+
for (const event of Object.keys(hooks)) {
|
|
479
|
+
const bucket = hooks[event];
|
|
480
|
+
if (!Array.isArray(bucket)) continue;
|
|
481
|
+
const next = [];
|
|
482
|
+
let removed = 0;
|
|
483
|
+
for (const e of bucket) {
|
|
484
|
+
const innerBefore = e.hooks?.length ?? 0;
|
|
485
|
+
const inner = (e.hooks ?? []).filter((h) => !this.isOurCommand(h.command, ctx));
|
|
486
|
+
removed += innerBefore - inner.length;
|
|
487
|
+
if (inner.length > 0) next.push({ matcher: e.matcher ?? "", hooks: inner });
|
|
488
|
+
}
|
|
489
|
+
if (removed > 0) {
|
|
490
|
+
if (next.length > 0) hooks[event] = next;
|
|
491
|
+
else delete hooks[event];
|
|
492
|
+
changes.push({
|
|
493
|
+
platform: this.id,
|
|
494
|
+
action: "remove",
|
|
495
|
+
path: hookPath,
|
|
496
|
+
detail: `hooks.${event} (${removed})`
|
|
497
|
+
});
|
|
498
|
+
mutated = true;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
if (mutated) this.writeJson(hookPath, file, ctx.dryRun);
|
|
502
|
+
if (changes.length === 0) {
|
|
503
|
+
changes.push({
|
|
504
|
+
platform: this.id,
|
|
505
|
+
action: "skip",
|
|
506
|
+
path: hookPath,
|
|
507
|
+
detail: "no matching hook entries"
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
return changes;
|
|
511
|
+
}
|
|
512
|
+
entryHasOurCommand(entry, ctx) {
|
|
513
|
+
return (entry.hooks ?? []).some((h) => this.isOurCommand(h.command, ctx));
|
|
514
|
+
}
|
|
515
|
+
/** True when a hook command references our home binary AND this connector id
|
|
516
|
+
* (anchored so a shared-prefix id can't collide — see isHomeBinHookCommand). */
|
|
517
|
+
isOurCommand(command, ctx) {
|
|
518
|
+
return isHomeBinHookCommand(command, ctx.homeBinPath, ctx.connector.id);
|
|
519
|
+
}
|
|
520
|
+
// ── Runtime: parse Continue stdin JSON → normalized event ────────────────
|
|
521
|
+
// Continue's stdin wire is Claude-identical snake_case (PR #11029 types.ts
|
|
522
|
+
// HookInput), so the claude-code wire helpers (ClaudeWireInput / extractSessionId
|
|
523
|
+
// / toolResponseToString) apply verbatim.
|
|
524
|
+
parseEvent(event, raw) {
|
|
525
|
+
const input = raw ?? {};
|
|
526
|
+
const connectorId = typeof input.connector === "string" ? input.connector : "";
|
|
527
|
+
const sessionId = extractSessionId(input);
|
|
528
|
+
const projectDir = typeof input.cwd === "string" ? input.cwd : void 0;
|
|
529
|
+
const base = {
|
|
530
|
+
hostPlatform: HOST,
|
|
531
|
+
connectorId,
|
|
532
|
+
sessionId,
|
|
533
|
+
raw,
|
|
534
|
+
...projectDir !== void 0 ? { projectDir } : {}
|
|
535
|
+
};
|
|
536
|
+
switch (event) {
|
|
537
|
+
case "PreToolUse": {
|
|
538
|
+
const ev = {
|
|
539
|
+
...base,
|
|
540
|
+
toolName: input.tool_name ?? "",
|
|
541
|
+
toolInput: input.tool_input ?? {}
|
|
542
|
+
};
|
|
543
|
+
return ev;
|
|
544
|
+
}
|
|
545
|
+
case "PostToolUse": {
|
|
546
|
+
const ev = {
|
|
547
|
+
...base,
|
|
548
|
+
toolName: input.tool_name ?? "",
|
|
549
|
+
toolInput: input.tool_input ?? {},
|
|
550
|
+
...toolResponseToString(input.tool_response) !== void 0 ? { toolOutput: toolResponseToString(input.tool_response) } : {}
|
|
551
|
+
};
|
|
552
|
+
return ev;
|
|
553
|
+
}
|
|
554
|
+
case "PostToolUseFailure": {
|
|
555
|
+
const ev = {
|
|
556
|
+
...base,
|
|
557
|
+
toolName: input.tool_name ?? "",
|
|
558
|
+
toolInput: input.tool_input ?? {},
|
|
559
|
+
error: typeof input.error === "string" ? input.error : "",
|
|
560
|
+
...typeof input.tool_use_id === "string" ? { toolUseId: input.tool_use_id } : {},
|
|
561
|
+
...typeof input.is_interrupt === "boolean" ? { isInterrupt: input.is_interrupt } : {},
|
|
562
|
+
...typeof input.duration_ms === "number" ? { durationMs: input.duration_ms } : {}
|
|
563
|
+
};
|
|
564
|
+
return ev;
|
|
565
|
+
}
|
|
566
|
+
case "UserPromptSubmit": {
|
|
567
|
+
const ev = {
|
|
568
|
+
...base,
|
|
569
|
+
prompt: typeof input.prompt === "string" ? input.prompt : ""
|
|
570
|
+
};
|
|
571
|
+
return ev;
|
|
572
|
+
}
|
|
573
|
+
case "SessionStart": {
|
|
574
|
+
const ev = {
|
|
575
|
+
...base,
|
|
576
|
+
source: normalizeSessionSource(input.source)
|
|
577
|
+
};
|
|
578
|
+
return ev;
|
|
579
|
+
}
|
|
580
|
+
case "SessionEnd": {
|
|
581
|
+
const ev = {
|
|
582
|
+
...base,
|
|
583
|
+
...typeof input.reason === "string" ? { reason: input.reason } : {}
|
|
584
|
+
};
|
|
585
|
+
return ev;
|
|
586
|
+
}
|
|
587
|
+
case "Stop": {
|
|
588
|
+
const ev = {
|
|
589
|
+
...base,
|
|
590
|
+
...typeof input.stop_hook_active === "boolean" ? { stopHookActive: input.stop_hook_active } : {}
|
|
591
|
+
};
|
|
592
|
+
return ev;
|
|
593
|
+
}
|
|
594
|
+
case "Notification": {
|
|
595
|
+
const ev = {
|
|
596
|
+
...base,
|
|
597
|
+
message: typeof input.message === "string" ? input.message : ""
|
|
598
|
+
};
|
|
599
|
+
return ev;
|
|
600
|
+
}
|
|
601
|
+
case "PermissionRequest": {
|
|
602
|
+
const ev = {
|
|
603
|
+
...base,
|
|
604
|
+
toolName: input.tool_name ?? "",
|
|
605
|
+
toolInput: input.tool_input ?? {},
|
|
606
|
+
...Array.isArray(input.permission_suggestions) ? { permissionSuggestions: input.permission_suggestions } : {}
|
|
607
|
+
};
|
|
608
|
+
return ev;
|
|
609
|
+
}
|
|
610
|
+
case "SubagentStart": {
|
|
611
|
+
const ev = {
|
|
612
|
+
...base,
|
|
613
|
+
...typeof input.agent_id === "string" ? { agentId: input.agent_id } : {},
|
|
614
|
+
...typeof input.agent_type === "string" ? { agentType: input.agent_type } : {}
|
|
615
|
+
};
|
|
616
|
+
return ev;
|
|
617
|
+
}
|
|
618
|
+
case "SubagentStop": {
|
|
619
|
+
const ev = {
|
|
620
|
+
...base,
|
|
621
|
+
...typeof input.agent_id === "string" ? { agentId: input.agent_id } : {},
|
|
622
|
+
...typeof input.agent_type === "string" ? { agentType: input.agent_type } : {},
|
|
623
|
+
...typeof input.agent_transcript_path === "string" ? { agentTranscriptPath: input.agent_transcript_path } : {},
|
|
624
|
+
...typeof input.last_assistant_message === "string" ? { lastAssistantMessage: input.last_assistant_message } : {},
|
|
625
|
+
...typeof input.stop_hook_active === "boolean" ? { stopHookActive: input.stop_hook_active } : {}
|
|
626
|
+
};
|
|
627
|
+
return ev;
|
|
628
|
+
}
|
|
629
|
+
case "PreCompact": {
|
|
630
|
+
const ev = {
|
|
631
|
+
...base,
|
|
632
|
+
...input.trigger === "auto" || input.trigger === "manual" ? { trigger: input.trigger } : {}
|
|
633
|
+
};
|
|
634
|
+
return ev;
|
|
635
|
+
}
|
|
636
|
+
default: {
|
|
637
|
+
throw new Error(`unsupported continue hook event: ${String(event)}`);
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
// ── Runtime: normalized response → Continue native (Claude-shaped) reply ──
|
|
642
|
+
// Continue's HookOutput is Claude-identical, so this mirrors the claude-code
|
|
643
|
+
// adapter's formatReply byte-for-byte:
|
|
644
|
+
// PermissionRequest → nested decision{ behavior:"allow"|"deny" } envelope;
|
|
645
|
+
// PostToolUseFailure / SubagentStart → context-only (deny degrades to
|
|
646
|
+
// additionalContext carrying the reason);
|
|
647
|
+
// Stop / SubagentStop / UserPromptSubmit / PostToolUse deny → TOP-LEVEL
|
|
648
|
+
// { decision:"block", reason };
|
|
649
|
+
// other deny → hookSpecificOutput.permissionDecision:"deny"; ask → "ask";
|
|
650
|
+
// modify (PreToolUse) → updatedInput; context → additionalContext.
|
|
651
|
+
formatReply(event, response) {
|
|
652
|
+
const hookEventName = event;
|
|
653
|
+
const decision = response.decision ?? "allow";
|
|
654
|
+
if (event === "PermissionRequest") {
|
|
655
|
+
if (response.decision === "deny") {
|
|
656
|
+
return this.stdout({
|
|
657
|
+
hookSpecificOutput: {
|
|
658
|
+
hookEventName,
|
|
659
|
+
decision: { behavior: "deny", message: response.reason ?? "Blocked by hook" }
|
|
660
|
+
}
|
|
661
|
+
});
|
|
662
|
+
}
|
|
663
|
+
if (response.decision === "allow" || response.decision === "modify" && response.updatedInput) {
|
|
664
|
+
return this.stdout({
|
|
665
|
+
hookSpecificOutput: {
|
|
666
|
+
hookEventName,
|
|
667
|
+
decision: {
|
|
668
|
+
behavior: "allow",
|
|
669
|
+
...response.updatedInput ? { updatedInput: response.updatedInput } : {}
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
});
|
|
673
|
+
}
|
|
674
|
+
return { exitCode: 0 };
|
|
675
|
+
}
|
|
676
|
+
if (event === "PostToolUseFailure" || event === "SubagentStart") {
|
|
677
|
+
const context = decision === "context" ? response.additionalContext : decision === "deny" ? response.reason ?? response.additionalContext : void 0;
|
|
678
|
+
if (context) {
|
|
679
|
+
return this.stdout({
|
|
680
|
+
hookSpecificOutput: { hookEventName, additionalContext: context }
|
|
681
|
+
});
|
|
682
|
+
}
|
|
683
|
+
return { exitCode: 0 };
|
|
684
|
+
}
|
|
685
|
+
if (decision === "deny") {
|
|
686
|
+
if (event === "Stop" || event === "SubagentStop" || event === "UserPromptSubmit" || event === "PostToolUse") {
|
|
687
|
+
return this.stdout({
|
|
688
|
+
decision: "block",
|
|
689
|
+
reason: response.reason ?? "Blocked by hook"
|
|
690
|
+
});
|
|
691
|
+
}
|
|
692
|
+
return this.stdout({
|
|
693
|
+
hookSpecificOutput: {
|
|
694
|
+
hookEventName,
|
|
695
|
+
permissionDecision: "deny",
|
|
696
|
+
permissionDecisionReason: response.reason ?? "Blocked by hook"
|
|
697
|
+
}
|
|
698
|
+
});
|
|
699
|
+
}
|
|
700
|
+
if (decision === "ask") {
|
|
701
|
+
return this.stdout({
|
|
702
|
+
hookSpecificOutput: {
|
|
703
|
+
hookEventName,
|
|
704
|
+
permissionDecision: "ask",
|
|
705
|
+
permissionDecisionReason: response.reason ?? "Confirmation required by hook"
|
|
706
|
+
}
|
|
707
|
+
});
|
|
708
|
+
}
|
|
709
|
+
if (decision === "modify") {
|
|
710
|
+
if (event === "PreToolUse" && response.updatedInput) {
|
|
711
|
+
return this.stdout({
|
|
712
|
+
hookSpecificOutput: { hookEventName, updatedInput: response.updatedInput }
|
|
713
|
+
});
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
if (decision === "context" && response.additionalContext) {
|
|
717
|
+
return this.stdout({
|
|
718
|
+
hookSpecificOutput: { hookEventName, additionalContext: response.additionalContext }
|
|
719
|
+
});
|
|
720
|
+
}
|
|
721
|
+
return { exitCode: 0 };
|
|
722
|
+
}
|
|
723
|
+
stdout(payload) {
|
|
724
|
+
return { exitCode: 0, stdout: JSON.stringify(payload) };
|
|
725
|
+
}
|
|
726
|
+
// ── Diagnostics ──────────────────────────────────────────────────────────
|
|
727
|
+
getHealthChecks(ctx) {
|
|
728
|
+
const path = this.getServerConfigPath(ctx);
|
|
729
|
+
const id = ctx.connector.id;
|
|
730
|
+
return [
|
|
731
|
+
{
|
|
732
|
+
name: `${this.name}: config.yaml present`,
|
|
733
|
+
check: () => existsSync(path) ? { status: "OK", detail: path } : { status: "FAIL", detail: `not found: ${path}` }
|
|
734
|
+
},
|
|
735
|
+
{
|
|
736
|
+
name: `${this.name}: ${MCP_ROOT_KEY}[name=${id}] registered`,
|
|
737
|
+
check: () => {
|
|
738
|
+
if (!ctx.connector.server) return { status: "OK", detail: "no MCP server declared" };
|
|
739
|
+
const cfg = readYaml(path);
|
|
740
|
+
const list = cfg?.[MCP_ROOT_KEY];
|
|
741
|
+
const present = Array.isArray(list) && list.some((e) => this.entryName(e) === id);
|
|
742
|
+
return present ? { status: "OK", detail: `${MCP_ROOT_KEY}[name=${id}]` } : { status: "FAIL", detail: `${MCP_ROOT_KEY}[name=${id}] not found in ${path}` };
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
];
|
|
746
|
+
}
|
|
747
|
+
// ── Internal helpers ──────────────────────────────────────────────────────
|
|
748
|
+
/** Get-or-create the mcpServers ARRAY at `cfg.mcpServers` (tolerate non-array). */
|
|
749
|
+
serverArray(cfg) {
|
|
750
|
+
const existing = cfg[MCP_ROOT_KEY];
|
|
751
|
+
if (Array.isArray(existing)) return existing;
|
|
752
|
+
const fresh = [];
|
|
753
|
+
cfg[MCP_ROOT_KEY] = fresh;
|
|
754
|
+
return fresh;
|
|
755
|
+
}
|
|
756
|
+
/** The `name` of an array entry, or undefined when it is not a named object. */
|
|
757
|
+
entryName(entry) {
|
|
758
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) return void 0;
|
|
759
|
+
const name = entry.name;
|
|
760
|
+
return typeof name === "string" ? name : void 0;
|
|
761
|
+
}
|
|
762
|
+
/** Resolve the per-platform server override into an effective ServerDef. */
|
|
763
|
+
effectiveServer(ctx) {
|
|
764
|
+
const override = ctx.connector.platforms[this.id]?.server;
|
|
765
|
+
if (override === false) return void 0;
|
|
766
|
+
const base = ctx.connector.server;
|
|
767
|
+
if (!base) return void 0;
|
|
768
|
+
return override && typeof override === "object" ? { ...base, ...override } : base;
|
|
769
|
+
}
|
|
770
|
+
};
|
|
771
|
+
function normalizeSessionSource(source) {
|
|
772
|
+
switch (source) {
|
|
773
|
+
case "compact":
|
|
774
|
+
return "compact";
|
|
775
|
+
case "resume":
|
|
776
|
+
return "resume";
|
|
777
|
+
case "clear":
|
|
778
|
+
return "clear";
|
|
779
|
+
default:
|
|
780
|
+
return "startup";
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
var adapter = new ContinueAdapter();
|
|
784
|
+
var continue_default = adapter;
|
|
785
|
+
export {
|
|
786
|
+
ContinueAdapter,
|
|
787
|
+
adapter,
|
|
788
|
+
continue_default as default
|
|
789
|
+
};
|
|
790
|
+
//# sourceMappingURL=continue-K43SUHLQ.js.map
|